Listing

AskTom Home
ViewForPrinting
Home
Recent Archives Advanced Search FAQ by Category Most Popular Hot Articles Your Questions [ Listing ]
Subject | Article | Last Updated (East coast time)
Formatting results in decode function | article only | Jan 01, 2003 08:26 am
ALTER TABLE/Oracle 9i External tables/Invoker rights | article only | Jan 01, 2003 09:41 am
How CBO uses histograms/data distribution when using bind variables? | article only | Jan 01, 2003 02:31 pm
Size of column_name in all_cons_columns view | article only | Jan 01, 2003 05:14 pm
use webdb under apache | article only | Jan 02, 2003 07:33 am
How do we calculate memory usage per process ? | article only | Jan 02, 2003 07:34 am
parallel query | article only | Jan 02, 2003 07:37 am
Using C library in Stored Proc | article only | Jan 02, 2003 12:41 pm
sqlplus prompt and logon trigger | article only | Jan 02, 2003 01:03 pm
large update,rowid, | article only | Jan 02, 2003 02:52 pm
Advantage and/or Disadvanage of using Autoextend | article only | Jan 02, 2003 04:46 pm
JDBC and Cursors | article only | Jan 02, 2003 09:17 pm
Temporary tablespace appears to be "full" | article only | Jan 02, 2003 09:35 pm
Package or Stored Procedure ? | article only | Jan 03, 2003 08:39 am
Tracking the progress of a long running delete | article only | Jan 03, 2003 02:32 pm
dbms_job.submit question of intervals of time | article only | Jan 03, 2003 02:34 pm
Striping Datafiles across unix level disk partitions | article only | Jan 03, 2003 02:51 pm
SQL Server Guy having a Mutation problem, I understand why but cant think of a workaround. | article only | Jan 03, 2003 06:35 pm
naming convention | article only | Jan 03, 2003 06:43 pm
Selection of JDBC client side driver - Thin Vs OCI | article only | Jan 04, 2003 10:05 am
Row count info | article only | Jan 04, 2003 06:34 pm
SQLLDR loading Batch Files | article only | Jan 05, 2003 09:50 am
Copying datafiles between different operating systems | article only | Jan 05, 2003 06:33 pm
Query t | article only | Jan 06, 2003 07:22 am
Guideline for SQL | article only | Jan 06, 2003 07:25 am
Strange explain plan results | article only | Jan 06, 2003 09:47 am
bulk update | article only | Jan 06, 2003 12:14 pm
Last Row | article only | Jan 06, 2003 12:19 pm
Describing/viewing package spec with no body | article only | Jan 06, 2003 02:05 pm
Privileges to use XML | article only | Jan 06, 2003 03:29 pm
Database architecture choice | article only | Jan 06, 2003 04:50 pm
zero memory sort and one disk sort, is this possible? | article only | Jan 07, 2003 06:35 am
row(s) 1 - 32 of 32
January 01, 2003 Muhammad Ahmad -- Thanks for the question regarding "Formatting results in decode function", version 8.1.7
You Asked
Hi  Tom Once again I have chance to ask you a problem. I use a query which give
the results better but not formatted much more. How to accomplish this task?

The details are:

SQL> select * from comp;

CNAME                               COMID
------------------------------ ----------
A                                       1
B                                       2
C                                       3
D                                       4

SQL> select * from adv;

       SNO      COMID DOJ          BALANCE DD
---------- ---------- --------- ---------- --
         1          1 01-JAN-02         10 CR
         2          1 01-JAN-02         20 CR
         3          1 01-FEB-02         30 CR
         4          2 01-JAN-02         15 DR
         5          2 02-JAN-02         20 DR
         6          2 03-JAN-02         25 DR
         7          3 03-JAN-02         30 DR
         8          3 03-JAN-02         35 DR
         9          4 01-MAR-02         60 DR
        10          4 01-MAR-02         50 DR

10 rows selected.

Now I run this query ( this query I posted you in early time, how ever)

SQL> select decode(dd,'CR',cname||'  '||balance||'  '||dd) credits,
            decode(dd,'DR',cname||'  '||balance||'  '||dd) debits
     from (SELECT sno,a.comid,doj,balance,c.cname,dd,ROW_NUMBER()
     OVER (PARTITION BY a.comid ORDER BY doj desc,sno desc) rn
                  from adv a,comp c where a.comid = c.comid )
     where rn = 1
     /

The results appear as:

SQL> column debits format a30
SQL> column credits format a30

CREDITS                        DEBITS
------------------------------ ------------
A  30  CR
                               B  25  DR
                               C  35  DR
                               D  50  DR

the results are correct but i want them as there should be no extra spaces in
the resulting fields like showing above under the debits field the results looks
like this.

CREDITS                        DEBITS
------------------------------ ------------
A  30  CR                      B  25  DR
                               C  35  DR
                               D  50  DR  and so on....

thanks a lot.
and we said...
Using the same technique outlined on:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6923393629227
we can do this using this query:

ops$tkyte@ORA920> select max(credits), max(debits)
  2    from ( select decode(dd,'CR',cname||'  '||balance||'     '||dd) credits,
  3                  decode(dd,'CR', row_number() over
                           ( partition by dd order by dd ), null ) credits_rn,
  4                  decode(dd,'DR',cname||'  '||balance||'  '||dd) debits,
  5                  decode(dd,'DR', row_number() over
                           ( partition by dd order by dd ), null ) debits_rn
  6             from ( SELECT sno, a.comid, doj, balance, c.cname, dd,
  7                           ROW_NUMBER() OVER (PARTITION BY a.comid
                                                 ORDER BY doj desc,sno desc) rn
  8                      from adv a,
  9                           comp c
 10                       where a.comid = c.comid
 11                  )  a
 12            where rn = 1
 13         )
 14   group by coalesce( credits_rn, debits_rn)
 15  /

MAX(CREDITS)                   MAX(DEBITS)
------------------------------ -----------------------------------------------
A  30  CR                      B  25  DR
                               C  35  DR
                               D  50  DR

ops$tkyte@ORA920>
Review & Followup
Rating: 1
not executing query December 31, 2002
Reviewer: Muhammad Ahmad from pakistan
Hi tom

This line generates error at oracle 8.1.7

 group by coalesce( credits_rn, debits_rn)

what is wrong with it

Followup:
coalesce is a new 9i function.  it returns the first non-null value from a list
of values.

nvl( credits_rn, debits_nr )

is equivalent in this case.  (the other link explained that with examples as
well)

January 01, 2003 Kailash -- Thanks for the question regarding "ALTER TABLE/Oracle 9i External tables/Invoker rights", version 8.1.7
You Asked
1. What is the effect of using ALTER TABLE t1 MOVE command if you do not provide
the TABLESPACE clause and the tablespace name. Where will the table get moved?
2. Oracle9i provides external tables, but has restriction that they are
read-only and indexes cannot be created on them. In such situations, how does
Oracle guarantee that using external tables is faster than using SQL*Loader and
loading the data into a temporary work table? For large volumes is it a better
approach to use external tables or use SQL*Loader to load data into a temporary
work table and process it?
3. Assume the following situation. There are two schemas in A and B in my
database and they both individually have two tables T1. I have an invoker rights
procedure P1 in schema C who has given execute privilege to A and B and has the
logic to insert into T1.
If the logic to insert into T1 is different for both users or if the structure
of T1 in both schemas are different how can I use invoker rights procedure to
have a single code base. Is there any workaround for it?
and we said...
1) same tablespace -- different blocks

2) I never read that we guaranteed that?  But anyway -- I think you are missing
the point.

An external table is INSTEAD OF loading into a temporary table and then loading
into the real tables.

Instead of.

You would see if it is appropriate for you (hey, to some pass loads -- you can
either

a) script a whole bunch of parallel direct path load scripts and slice the input
data up yourself and determine the degree of parallelism, etc OR

b) you can create table T parallel as select * from external_table

to achieve the same net result.  Which one is easier?  b) was for me

Perhaps you are using EXTERNAL tables as a much easier SQLLDR - one that
supports complex WHEN conditions (just try to use an OR in a WHEN clause in
SQLLDR -- it is pretty trivial in SQL).  Perhaps you are using EXTERNAL tables
with MERGE (try that with sqlldr) to avoid having to plop the data into temp
just to merge.

It lets you do things you previously could not do, It makes things you could do
easier (try running sqlldr from a stored procedure - now, try using an external
table from a stored procedure -- sqlldr is just INSERT into T select * from ET
now.....)

3) obviously you cannot, unless the procedure used dynamic sql to build a unique
sql statement for each user based on what the data dictionary told the procedure
the structure was.
January 01, 2003 Moorthy -- Thanks for the question regarding "How CBO uses histograms/data distribution when using bind variables?", version 8.1.6
You Asked
Tom,

Please explain how the CBO uses histograms/data distribution when using bind
variables? I agree that parsing overhead will be minimal with bind variables.
But, how the optimizer knows which value will be used in run time and how to get
the best exeuction path?

If I use bind variables, my query will be
select * from t where state = :b1;

In run time, it can be either
select * from t where state = 'GA';
or
select * from t where state = 'AL';

My assumption is that 90% of the rows have state as GA and rest of the rows have
AL.

Thanks again for your help.

Moorthy Rekapalli
and we said...
Thats the issue.

If you use bind variables, the CBO will come up with the best guess plan in
general using heuristics (rules).

It will take the query "select * from t where state = :b1" and optimize it once
and for all -- one plan fits all.

If you know you need a different plan for different queries -- that is the one
time you would use constants instead of bind variables.  Rare cases, almost
exclusively in a data warehouse which has  a small number of users so the
contention for the library cache is not noticable.  Not something to be done in
a highly active system with tons of users.
Review & Followup
Rating: 5
May 29, 2001
Reviewer: Moorthy Rekapalli from Atlanta GA USA

Rating: 3
October 29, 2002
Reviewer: Sagi from India
Hi Tom,

As usual, I learn a lot from your site. Thanx for keeping up the good work.

I was reading on Histograms. I have a doubt. You mean to say that in dataware
house we would be gaining performance using Histograms??

Becuase in Normal cases and in OLTP we normally make use of Bind Variables. In
this case Histograms dont make any difference?

Thanx in Advance.

Regards,
Sagi

Followup:
pretty much -- that would be the gist of it, yes.

Rating: 4
histograms, bind variable and 'between' October 30, 2002
Reviewer: Dushan
I had select statement like this:
select * from t where a between :b1 and :b2;
I calculated statistics for table and indexes (there was an index on t(a)). CBO
decided to go full scan always, no matter which values were in :b1, :b2, causing
in general slow response.
After I calculated statistics 'for all indexed columns', CBO started to be
smarter. If the values :b1, :b2 were close to each other, CBO used the index,
otherwise it went full scan.
Could you, please, explain how CBO works in this case?

Rating: 3
Question to Dushan October 31, 2002
Reviewer: Wolfgang Breitling from calgary, Canada
Which Oracle version are you using?

Rating: 3
Re:histograms, bind variable and 'between' November 04, 2002
Reviewer: Dushan from Czech rep.
I am using 8.1.7.4

Rating: 5
generates histograms for non-indexed columns January 01, 2003
Reviewer: A reader
Hi

I have some doubts about histograms and analyze, analyze gives us option to
analyze

for all indexes
for table
for columns OR for all columns OR for all indexed columns

if I analyze using for all columns then I would be gathering histograms of 75
buckets, default in 8.1.7.4 my version.

My question what's the point gathering histograms for an unindexed column, isnt
histograms used only when determing using index or not? Also how can column
statistics help CBO to determine an execution plan?

Followup:
Not just an index but driving tables and such as well.

See
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76992/stats.htm#27065

Rating: 4
Height-based vs Value-based January 01, 2003
Reviewer: Dave from Ohio
I read in the reference you linked to a discussion of the merits of value and
height-based histograms, including the following quote ...

"If there are many different values anticipated for a particular column of your
table, it is preferable to use the value-based histogram rather than the
height-based histogram"

... which had me searching for a flag, or text in method_opt, to pass to
dbms_stats in order to generate different types of histogram.

However the Oracle Reference states that Oracle only uses height-based
histograms, and I cannot find anything that tells us how we might create a
value-based histogram. Is the documentation just teasing us here with a glimpse
of an unimplemented capability?

Followup:
It is teasing - it does this from time to time.  for example, the concepts
guide:
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96524/c21cnsis.htm#2458
discusses lots of isolation levels, we only support 2 of them directly...

Rating: 5
January 02, 2003
Reviewer: Dave from Ohio
Teasing, eh? The cheeky monkeys.

At least I proved that I read the documentation though. Unlike in my last
question.

January 01, 2003 Tony -- Thanks for the question regarding "Size of column_name in all_cons_columns view", version 8.1.6
You Asked
Hi Tom,
If we can not create a column for a table with a name > 30 characters then why
the size of column_name in all_cons_columns is varchar2(4000)?
Thanks
Tony
and we said...
Its all about objects...

ops$tkyte@ORA920> create or replace type myType as object
  2  ( long_name_here_long_name_here varchar2(30) )
  3  /

Type created.

ops$tkyte@ORA920> create or replace type myType2 as object
  2  ( long_name_here_long_name_here2 myType )
  3  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t
  2  ( a int primary key,
  3    x myType2 check (
x.long_name_here_long_name_here2.long_name_here_long_name_here is not null ) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec print_table( 'select * from user_cons_columns where
table_name = ''T'' ' )
OWNER                         : OPS$TKYTE
CONSTRAINT_NAME               : SYS_C005697
TABLE_NAME                    : T
COLUMN_NAME                   :
"X"."LONG_NAME_HERE_LONG_NAME_HERE2"."LONG_NAME_HERE_LONG_NAME_HERE"
POSITION                      :
-----------------
OWNER                         : OPS$TKYTE
CONSTRAINT_NAME               : SYS_C005698
TABLE_NAME                    : T
COLUMN_NAME                   : A
POSITION                      : 1
-----------------

PL/SQL procedure successfully completed.
Review & Followup
Rating: 5
Thanks a lot! January 01, 2003
Reviewer: Tony

January 02, 2003 -- Thanks for the question regarding "use webdb under apache", version 8.0.5
You Asked
how do I connect my site under Webdb to apache web server
and we said...
WebDB has a cgi-bin module you may be able to use.

As well, the new iAS runs under apache now and has the module
for apache natively.  Thats what you really want ultimately.

see
http://www.oracle.com/ip/deploy/ias/index.html
for more info
Review & Followup
Rating: 5
webdb January 02, 2003
Reviewer: atul from india
Sir,

I'm on 8.1.5(typical Installation) on red-hat linux 7.1
I want to use webdb features for development purpose..

Can i get webdb for development purpose???How??

or it comes with 8i...like apache web server

Thanks.
atul

Followup:
webdb used to be the name of portal a long long time ago.  It is called Portal
now, that is what you are looking for.

otn.oracle.com

has all software that is available for download/development.

January 02, 2003 Krishna -- Thanks for the question regarding "How do we calculate memory usage per process ?", version 8.1.6.3
You Asked
Hi Tom,

      We are using Oracle 8.1.6 on Sun Solaris and trying to calculate the RAM
size for 500 concurrent users. As per our calculation we are getting 3-5MB per
process and one of my friend calims that, we need 30MB per process. I used the
following query to calculate the average value per process.

SELECT name, AVG(value)
FROM v$session se, v$sesstat ss, v$statname sn
WHERE ss.sid=se.sid
AND sn.statistic# = ss.statistic#
AND sn.name = 'session pga memory'
AND username = 'ABE'
GROUP BY name;

could you please give me the average value per process.

Thanks a lot,
Krish.
and we said...
you did it right -- don't know (cannot IMAGINE) where your friend got "30mb"
from?

with 500 concurrent users, you could also look at using MTS (shared server).
Turn on shared server and then look at the UGA memory (to help you size the
large_pool in the SGA).  You need only enough PGA's for the shared servers (of
which you might only have 50 or so).
Review & Followup
Rating: 4
Thanks a lot for the quick reply Tom ! May 24, 2002
Reviewer: Krishna from San Francisco, Ca USA

Rating: 4
Reader June 03, 2002
Reviewer: A reader
Tom,

I recently was reading a BUG report 895995
In the report it was recommended, that Whatever highest value is
reached (pool heap session (MTS)), set the LARGE_POOL_SIZE to 3x's that size

On my system
SQL>select sum(value) from v$sesstat wher staistic# = 15 -- "session uga memory"
29524368 = ~29 M
SQL>select * from v$sgastat;
....
large pool session heap 26382240 = ~ 25M

Setting the large pool to 3 x 25 = 75 M. , is this a good guide line or that
recommendation
is specific to that environment . Why sum(session uga memory) show higher value
than "large pool session heap"

Thanks

Followup:
They were trying to find the right size -- they were getting 4031's (out of
memory).  Their first stab was "triple the size and set the large pool to that"

If you aren't hitting errors, I wouldn't change it.

Dedicated servers allocate their UGA out of the PGA (backend processes and
such).  You needn't count them.

Rating: 5
Total memory used by processes.. January 01, 2003
Reviewer: atul from india
Sir,

I am calculating total memory used by all logged in user..
I have 1GB ram..

I'm doing this by,

select decode(NAME,'session pga memory','session pga memory','f') ,sum(VALUE)
from
    v$session se, v$sesstat ss, v$statname sn
    WHERE ss.sid=se.sid
        AND sn.statistic# = ss.statistic#
   group by decode(NAME,'session pga memory','session pga memory','f')
SQL> /

DECODE(NAME,'SESSI SUM(VALUE)
------------------ ----------
f                  1312030183
session pga memory   41989456

So,is this correct ??

Thanks.
atul

Followup:
depends.  first I don't know the value of "f" -- it is rather meaningless.

there is pga memory and uga memory.  in dedicated server mode, uga memory is
session memory stored in the pga.  in shared server mode, uga memory is session
memory stored in the sga.  so, depends on how your users connect.

Rating: 4
Is the Result in Bytes January 02, 2003
Reviewer: Sunil Gururaj from Cyprus
Hi Tom,

I would like to know if the result of the above query to calculate average value
per process in bytes?

Thanks,

Followup:
yes, it is in bytes.

January 02, 2003 -- Thanks for the question regarding "parallel query", version 8.1.6
You Asked
Tom

Just one more ..

I have query which is taking 50 minutes to run...and is doing a full table scan
Now, I want to know if I can use parallel query in any way to parallelly process
this query..

Before that..

In which version was this concept introduced..For parallel query is it necessary
that we should have multiple processors.. What happens behind the scene , when
we divide a query into a parallel degree of 2 or 4.. what happens internally..
What is the criteria to use parallel query

Is there any way you send voice emails for some answers.. I have an account
which converts voice mails into emails and emails to my account.
and we said...
Yes, a full scan is a good candidate for parallel query.

You'll want to make sure you have sufficent reserve IO capabilities... If you
are totally IO bound now, parallel query will only make that worse.

it is very easy to try, won't hurt anything.  Give it a go.

Oracle7.1 introduced parallel query in 1994.  You do not need more then one CPU,
you frequently use a degree of parallelism greater then the number of cpus on
the system.  Consider this -- when doing an index build you read alot, sort
alot, write alot.  When you are reading, you are not using the CPU (hopefully
you don't have ide drives!).  So, if there were another process sorting -- that
would be great.  Likewise when you are sorting, you are not using the disk -- if
someone else could be reading or writing, that would be great.

Read this chapter:
http://technet.oracle.com/doc/oracle8i_816/server.816/a76965/c22paral.htm#365
it covers all about parallel query, how it works, what happens when you use it.

No, you cannot use voice mail to contact me ;)
Review & Followup
Rating: 4
PQ and CPUs July 24, 2001
Reviewer: Billy Verreynne from Arse Side of Africa
Good stuff TK. It is important to note that it is an OWT (old wife's tale) that
you need multiple CPUs for using PQ. It is simply not true.

As long as the CPU is not 100% busy, you can squeeze more performance from it -
which means loading the CPU with more processing.

PQ is simply another form of multithreading. Essentially, taking a big tasks
(like a full table scan) and breaking it into smaller chunks of work (like
Oracle adding the rowid clause to your SQL) and running these at the same time.
Irrespective of a single CPU or multiple CPU. Having multiple CPUs simply allow
you to load the CPUs with more PQs, nothing more.

Rating: 4
how to set degree? January 02, 2003
Reviewer: Rahul from India
Hi Tom

On which basis we should set the degree of parallelism and PARALLEL_MIN_SERVERS
AND PARALLEL_MAX_SERVERS parameters?

If Oracle server on which instance is running has 2 CPU then what are the
appropriate values for these parameters?

Rahul.

Followup:
use parallel automatic tuning instead.  the database will pick your appropriate
values.

January 02, 2003 -- Thanks for the question regarding "Using C library in Stored Proc", version 8i
You Asked
I'd like to call C-written library in a Oracle 8i stored proc.
The lib should be able to accept a few params and return a structure back.
If this is possible, could you, please, refer me to some documentation?
Thanks in advance
--
AN
and we said...
A structure -- no,  An object type -- yes.

If you have my book "Expert One on One Oracle" i go into some depth on this
topic in my chapter on external procedures.

You can also see:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76939/adg11rtn.htm#1656
January 02, 2003 ted -- Thanks for the question regarding "sqlplus prompt and logon trigger", version 8.1.6
You Asked
thomas,

how do i create a logon trigger to set sqlprompt to sid> ? so,
when connect from one instance to another the prompt will tells
me where am i ?

I tried execute immediate 'set sqlpromt to sid>' and it does not
work.
and we said...
you cannot.  SQLPlus is a tool to access the database.  Its
commands are implemented by it.  The database is the database --
there is no way for the database to set a SQLPlus setting as the
database is wholly unaware of SQLPlus.

What I do is use a login.sql file to set the prompt upon login.
I've trained myself to use:

SQL> @connect

instead of

SQL> connect

where connect.sql is:

connect &1
@login

in that way, the prompt is accurate.  If you are ever confused,
just run

SQL> @login

to clear up any confusion.
Review & Followup
Rating: 4
how about login.sql July 18, 2001
Reviewer: A reader
But Tom how to create this login.sql.

Rating: 2
How to set sysdate as sql prompt? April 04, 2002
Reviewer: Chidambara Barathi A from Bangalore
Hi Tom,

I want to set sysdate as sql prompt. How will i do it?

Thanx,

Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:284615949318
shows how to set sqlprompt based on a query result.

Rating: 3
If you only want time... April 04, 2002
Reviewer: Andrew from CA, USA
If you only want time then this will do.
SQL> set time on
10:17:27 SQL>

Rating: 3
How to change Sysdate? January 02, 2003
Reviewer: Alan from USA
Tom,
I remember reading in this forum a way you showed  to change the sysdate. I am
trying to debug some queries which ran fine two months ago and are failing
today. The queries use a lot of sysdate and I was wondering if I could change
the sysdate on a development system and test if it runs.

Thanks

Followup:
search for

"fixed_date"

on this site.

January 02, 2003 Vikas -- Thanks for the question regarding "large update,rowid,", version 8.1.7.0
You Asked
Q. 1  I have a table which is most queried and updated by many users at a
time.The table has 50000 plus rows. And i want to issue a update through a
procedure. I am  force to use commit inside a loop which i want to avoid how can
i avoid it. If i dont   use it the lock will not be released and another user
cannot issue an update. for example

    create table t (id   number,
                    first_name   varchar2(100),
                    last_name    varchar2(100),
                    mailing_name varchar2(200));
   create or replace procedure is as  p is
    begin
      for x in (select id,mailing_name,first_name||last_name cname from t) loop
       if x.mailing_name <> x.cname THEN
          update t set mailing_name = x.cname
          where id = x.id;
         commit;
       END IF;

      end loop;
    END;
    /

If i normally perform(w/o commit) than also this update it takes long time but
with commit is taking more time
to update. I want to avoid the commit inside the loop and at the same time the
other user trying to update are not blocked.
can there is some another better way to achieve it.

Q.2 For better performance (fast access) the rowid should be use for update and
delete instead of primary key. I am working on a web based application. If one
user of my application issue a query like select rowid,e.* from emp e; and store
the values of the rowid in the hidden field item of a form for every row
selected, form also has a     delete button for every row selected. when the
submit is clicked the hidden field rowid value is use to delete the
corresponding row by iseeuing the following delete command
        delete form emp where rowid = value_from_hidden_field;

    Now my question is Suppose if a user 'x' open the page which shows the data
from above query. the html page shows all the 14 rows. and stores the rowid in
the hidden items for all  corresponding rows.

Now user 'Y' open the same page he also get the same data displayed. and issues
a delete for first row. say first row has AAAGVWAAIAAAAOqAAE rowid. and the
commit is done automatic(stateless session)  the row gets deleted. Now he issues
few inserts  suppose just by chance for one new row get the same rowid
AAAGVWAAIAAAAOqAAE. because the rowid are reusable.

If the user 'x' click on delete  on the row which was queried before user'Y'
performed his actions(delete and insert) the the new will get delete. Which will
not be desired. to avoid this i have to include the primary col in delete
statement like
    delete form emp where rowid = value_from_hidden_field and empno  = 1234;
can you please give me your comments on the above scenario. can i do it with
only one  col that is rowid?

Q. 3 How can i do paging of the records if my select statement selects rowid
also. for example
    select * from (select *.emp,rowid,rownum r from emp) where r
    between :startrow and :endrow;
the above statement give following error  cannot select ROWID from view with
DISTINCT, GROUP BY, etc.  it is be because in lineview. I want rowid to be use
in the hidden fields of a html form for further
transactions when the  formn is submited.

Thanks
Regards
Vikas
and we said...
q1) it should take SECONDS to update 50k rows.  Seconds -- you cannot wait
seconds?

how long is it taking?

q2) if you have my book -- i go into a long discussion on this very topic.

You need to save the ROWID and the old values (the before images if you
will).

So, your delete will actually be:

delete from t
  where rowid = :rowid
    AND field1 = :field1
    AND field2 = :field2
    AND field3 = :field3
    ......

and check sql%rowcount (or use whatever feature of whatever language you are
using) to see if you deleted a row.

If not, you must report back to the user that "sorry, but the row you tried to
delete no longer exists as you saw it, you must requery the data and try again"

This is called optimistic locking.

When you update, you MUST:

   update t
     set c1 = :new_c1, c2 = :new_c2, ....
   where rowid = :rowid
         AND field1 = :field1
    AND field2 = :field2
    AND field3 = :field3
    ......

again -- checking the number of rows updated.  If you do not -- you suffer from
what is known as the LOST UPDATE problem.

for all of the details -- I'll have to recommend my book as it takes a couple of
pages of examples to make the point crystal clear

q3)

This works:

scott@ORA817DEV.US.ORACLE.COM> variable min number
scott@ORA817DEV.US.ORACLE.COM> variable max number
scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM> exec :min := 5; :max := 10;

PL/SQL procedure successfully completed.

scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM> select *
  2    from ( select rownum r, a.*
  3                     from ( select rowid rid, ename, deptno
  4                                          from emp
  5                                     order by ename ) a
  6                    where rownum <= :max )
  7   where r >= :min
  8  /

         R RID                ENAME          DEPTNO
---------- ------------------ ---------- ----------
         5 AAAJ7RAABAAAE5gAAM FORD               20
         6 AAAJ7RAABAAAE5gAAL JAMES              30
         7 AAAJ7RAABAAAE5gAAD JONES              20
         8 AAAJ7RAABAAAE5gAAI KING               10
         9 AAAJ7RAABAAAE5gAAE MARTIN             30
        10 AAAJ7RAABAAAE5gAAN MILLER             10

6 rows selected.

scott@ORA817DEV.US.ORACLE.COM>

You want to use the technique I used above (with a predicate on rownum AND THEN
a predicate on R later) -- it is more efficient.
Review & Followup
Rating: 3
large update June 14, 2002
Reviewer: oraquest@yahoo.com from NJ, USA
Hi Tom,

I don't know the business requirements in this case, but I was just wondering
whether it would be possible to identify the IDs that the users are trying to
update, so that the original procedure can take that as a parameter. Or maybe he
a trigger can be used instead?

What do you think?

Followup:
I don't see what that would do for us.

They have conflicting goals

a) do a mass update, one that should be done simply as:
   update t set mailing_name = first_name||last_name
     where mailing_name <> first_name || last_name;

b) let others update the rows updated by (a) before (a) commits

Rating: 3
large update June 14, 2002
Reviewer: oraquest@yahoo.com from NJ, USA
a) That's what I thought. It's really a batch update that they want.

b) I was just trying to suggest that maybe, they may want to rethink the need
for simultaneous batch updates by different users? Or maybe try to identify the
records that the users want to update -- either by ID or by Name? Hence the
suggestion for a trigger or a modified procedure.

I guess I was just trying to understand where they are coming from. I just find
it unusual to have batch updates done by several people at the same time...

Thanks.

Followup:
I don't think they are trying to do large batch updates by many people -- just
that they have a single batch update and don't want to block anyone...

Rating: 4
q1 explanation June 16, 2002
Reviewer: Vikas from Delhi India
Thanks for the paging solution.
yes tom you are right i trying to do single large batch update and don't want to
block anyone.
It may not possible to give you the real code. but i am providing you an
example:
hr_cm@ORCL.DEL1>create table test_update as select * from all_objects;

Table created.
hr_cm@ORCL.DEL1>insert into test_update select * from all_objects;
24884 rows created.

hr_cm@ORCL.DEL1>select count(*) from test_update;

  COUNT(*)
----------
     49768
hr_cm@ORCL.DEL1>alter table test_update add (type_name varchar2(100));

Table altered.

Elapsed: 00:00:00.30
hr_cm@ORCL.DEL1>update test_update set type_name =
object_type||'-'||object_name;

49768 rows updated.

Elapsed: 00:00:30.44
This takes about 30 sec. But suppose if the number of row grows to 100 k or more
then it will obviously take more time and if i do it procedurely instead of an
update command than it will take more time. As my requirment is such that i have
to do it procedurely.

Kindly guide me what is the best way to perform large batch update with blocking
others.

Followup:
30 seconds, 60 seconds, whatever.  If you do it procedurally, it'll take much
longer.  I just don't agree with your approach.  sorry.

In 9i, you could use the dbms_redefinition package to do this "online" I
suppose.  Interesting idea actually.  You would in effect be rebuilding the
table with the newly defined column -- whilst allowing updates to the old
table..

Rating: 5
update using rownum January 01, 2003
Reviewer: A reader
Hi

This is purely academic question!
I have a table with 1000 records, I added a column and I want to update 100 rows
each time, something like

select a.*
from (select rownum rn, x.*
      from x) a
where rn between 1 and 00

but update, I tried

update
(select rownum rn, x.*
   from x) a
set sex='M'
where rn between 1 and 100

and I got ORA-01732: data manipulation operation not legal on this view

How could I do this update?

Thank you

Followup:
Academic answer:

don't do it that way -- update all of the rows in a single update.

o it is faster
o it is easier
o it is less buggy since you are not writing code
o it is the right way to do it.

Rating: 4
Why the extra select ? January 01, 2003
Reviewer: A Reader from UK
Tom,

In the example you gave:

select *
from ( select rownum r, a.*
from ( select rowid rid, ename, deptno
from emp
order by ename ) a
where rownum <= :max )
where r >= :min

can't we drop the outer select * ?  As we are getting everything in the inner
select anyway ? i.e.:

select rownum r, a.*
from ( select rowid rid, ename, deptno
from emp
where rownum <= :max )
where r >= :min

Or is it because we wanted ordering that we need to do the extra select ? In my
example I don't have any 'order by' - is that the deciding factor in having the
extra outer select ?

Regards,

Paul

Followup:
try it - report back what happens ;)

fact: r is not available in the predicate you coded.  R doesn't exist yet - only
as an OUTPUT of that query.  R is not in the predicate:

ops$tkyte@ORA920> select rownum r from emp where r > 1;
select rownum r from emp where r > 1
                               *
ERROR at line 1:
ORA-00904: "R": invalid identifier

ops$tkyte@ORA920> select * from ( select rownum r from emp ) where r > 1;

         R
----------
         2
         3
         4
         5
...

fact: if you replace r with rownum, the ORA error goes away but...

ops$tkyte@ORA920> select * from emp where rownum > 1;

no rows selected

ops$tkyte@ORA920>

rownum is NEVER greater then 1 cause rownum is assigned AFTER the predicate is
resolved.

Hence the inline view is necessary -- very necessary.

Rating: 3
Oops January 02, 2003
Reviewer: A Reader from UK
Sorry Tom,

I did you the discourtesy of not doing what you do and proving my cut-and-paste
idea !!  What I meant was, doesn't something like this work (i got it wrong in
my example):

select *
from ( select object_name, rownum rnum
from user_objects
where rownum <= :max )
where rnum >= :min

where you appear to have:

select *
from ( select rownum r, a.*
from ( select object_name
from user_objects
order by object_name) a
where rownum <= :max )
where r >= :min

ANd my guess was that you had the extra level of select because of the order by
?  My result seems to be the same.

Followup:
Ahh -- gotcha.

If your query is "rownum safe" (no aggregates, no order bys), you can remove the
one layer, yes.

January 02, 2003 Jenny -- Thanks for the question regarding "Advantage and/or Disadvanage of using Autoextend", version 8.1.7
You Asked
Tom,

I have a few questions related to autoextends:

1. Can you tell me the advantage and/or disadvantage of having "autoextend on"
for every datafile I create in the database?
2. If I have a tablespace with all datafiles created to have autoextend on, then
I turned it off after all datafiles have been filled, would this have any impact
on the datafile or tablespace?

Thanks!
and we said...
1) some people like it (me), some people hate it.  Some people wish to
micromanage every last bit/byte.  Some people don't want to be paged when a
tablespace fills up but the file system has room.

(i use maxsize to make them not able to grow bigger then I want)

2) nope.
Review & Followup
Rating: 3
Jenny January 02, 2003
Reviewer: A reader
Tom,

Could you explain how maxsize works?  For example, if I have the following
create tablespace statement:
CREATE TABLESPACE tbspname_sml
DATAFILE '/usr/local/oracle/data02/${ORACLE_SID}/tbspname_sml_01.dbf' SIZE
524352K REUSE AUTOEXTEND ON NEXT 65536K MAXSIZE 786496K           EXTENT
MANAGEMENT LOCAL UNIFORM SIZE 128K           NOLOGGING
ONLINE
/

So, my datafile would be 512M, if it runs out of space, it will extend to the
maxsize of 768M, would that make the datafile a total size of 1280M (512+768)?
Could you elaborate more on it?

Thanks!

Followup:
76.8m max

Rating: 4
don't forget "next ##MB" for autoextensible datafiles January 02, 2003
Reviewer: Xuequn Xu from CA, USA
I think specifying a reasonably big space growth rate in "next ..." clause would
be a good idea, in addition to maxsize, if you want to set autoextend on (such
as "alter database datafile '/path/to/db01.dbf' autoextend on next 10MB maxsize
4000MB").

Followup:
sure, I make it be the extent size of my LMT.

Rating: 5
Answer to Jenny's question January 02, 2003
Reviewer: David Gibbs from Burnaby, BC Canada
Jenny,

Max size is just that max size.

In your example your file is initially 512K (or was that M). It will grow by 64K
(or M) - that is your next size until it reaches a max size of 768K (or M - you
get my point).

Rating: 3
Thanks! January 02, 2003
Reviewer: Jenny
For your answers.

Rating: 4
May 08, 2003
Reviewer: Mike from TX
config1:
One huge file in the tablespace, 15G;

config2:

more than one file in the tablespace, total of 15G;

Comparison of the two, what's the advantage / disadvantage?

TIA

January 02, 2003 Buck -- Thanks for the question regarding "JDBC and Cursors", version 7.3.4
You Asked
I have a Java class with JDBC calls to a 7.3.4 database in which
the main method calls another method, and deletes from that
table using rowid .  I commit every 10000 records to save on
rollback.

However, I get an error after a few hundred records saying that
I exceeded max cursors.

I was under the impression that a prepared statement won't be
reparsed, so it will be reused in the library cache.  Am I
confusing the terminology?  Is a cursor just a parsed statement,
or is there more to it that that?  Below is an excerpt from my
code.

Many thanks for any insights!

   public static void main(String args []) throws SQLException
      String sq = "select rowid from my_table where sched_id
like 'OE9%'";
      Statement stmt = oq.conn.createStatement ();
      ResultSet rset = stmt.executeQuery (sq);
      int rowsDeleted = 0;

      while (rset.next ()) {
         String rowid = rset.getString(1);
         oq.delBas(rowid,oq);
         rowsDeleted++;
         if(rowsDeleted % 10000 == 0) {
            System.out.println("Rows deleted " + rowsDeleted);
            oq.conn.commit();
         }
      }
   }

   private void delBas(String rowid,basDelete oq) throws
SQLException {
      String sq = "select x from my_table where rowid = ?";
      //  change to delete once fully tested
      PreparedStatement ps = oq.conn.prepareStatement(sq);
      ps.setString(1,rowid);
      ResultSet rset = ps.executeQuery ();
      while (rset.next()) {
         String rEmplid = rset.getString(1);
         System.out.println(" " + rEmplid);
      }
   }
}
and we said...
You need to use a SINGLE prepared statement that is global to
the class.

The problem here is that EVERYTIME you call delBas, it is
getting a brand new prepared statement, that is allocating a
cursor and then you are letting that go out of scope (never to
be seen again).  Since java doesn't have any destructors -- the
close cursor never ever happens.  This is classic.  If you every
let any of the prepared, callable, resultset type of statments
go out of scope without closing them -- you'll leak cursors.

Your "quick fix" is to:

   private void delBas(String rowid,basDelete oq) throws
SQLException {
      String sq = "select x from my_table where rowid = ?";
      //  change to delete once fully tested
      PreparedStatement ps = oq.conn.prepareStatement(sq);
      ps.setString(1,rowid);
      ResultSet rset = ps.executeQuery ();
      while (rset.next()) {
         String rEmplid = rset.getString(1);
         System.out.println(" " + rEmplid);
      }
      rset.close();
      ps.close;
   }

Your longer term fix is:

   String sq = "select x from my_table where rowid = ?";
   //  change to delete once fully tested
   PreparedStatement ps = oq.conn.prepareStatement(sq);

   private void delBas(String rowid,basDelete oq) throws
   SQLException {

      ps.setString(1,rowid);
      ResultSet rset = ps.executeQuery ();
      while (rset.next()) {
         String rEmplid = rset.getString(1);
         System.out.println(" " + rEmplid);
      }
      rset.close();
   }

You want to PREPARESTATEMENT once per program and bind/execute
many times.  that'll increase performance.  The first fix will
just stop you from leaking cursors like crazy.
Review & Followup
Rating: 5
JDBC and Cursors January 02, 2003
Reviewer: Rizwan Qazi from Hayward CA USA
Tom,
  Then where do you close the PreparedStatement ps.
I am a little confused. You mean to say we never close the ps?
Can you give a more complete example?
If a user connects to a servlet which accesses a databsase can you tell us when
to prepare the statement, how to execute it and where to close it.

Thanks very much,
Rizwan

Followup:
Close the PS when you are

a) done with it for the life of your program (eg: I just loaded 100 rows, all
done, close it)

b) you are getting ready to give a connection back to a connection pool

else, if you are going to keep on using it -- by all means, keep it open till
the cows come home.

January 02, 2003 Jen -- Thanks for the question regarding "Temporary tablespace appears to be "full" ", version 8.1.7
You Asked
Tom,

I understand that temporary tablespace is used for sorting.  We currently have a
TEMP tablespace (dictionary-managed) in our database.  In the OEM view, it
always show TEMP is at 99.98% full.  I understand that it's perfectly "normal"
that TEMP tablespace appears to be "full" after a while in a normally running
database.  Is there anyway we can get that "full" indicator flush out and show
the "real" percentage of TEMP is being used?  Besides having to shutdown the
database and restart?

Thanks!
and we said...
see the Reference guide for info on

v$sort_usage
v$sort_segment

it'll show the details of who is using what and how much.  The tablespace is
simply allocated -- the v$ tables show what is used.   Just like "rbs" appears
"full", temp should appear full.

shutdown/restart won't show you how FULL anything is.  The second after you
restart -- the temp space will show some extents (from people using it) but you
STILL cannot tell if they are CURRENTLY using it without looking at the v$
tables.
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76961/ch3149.htm#10370
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76961/ch3150.htm#11903

Review
      & Followup

      Rating:
      4
Thanks!  January 03, 2003
Reviewer:
      Jen  from San Francisco, CA
These views are very helpful.

January 03, 2003 Kwok -- Thanks for the question regarding
      "Package or Stored Procedure ?", version 8.1.7

      You Asked
Dear Tom:

I have some simple questions, please help me

1. What is the advantage and dis-advantage of using package and stored
procedure. Which one should I use in a multiple user database? any other choice
?

2. What access right/privilege should I assign to the database user if they only
allow to execute ? any better security method than just assign the 'execute
package ' rights to them ?

Thank you very much for you help !!!!

      and we said...
1) packages are the ONLY thing you should use for real code.  standalone
procedures/functions are great for a trivial utility but real code should be
(must be) written in packages.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:286816015990
for the major reason why

2) they just need CREATE SESSION and EXECUTE on the set of packages you want.
Nothing more.
January 03, 2003
      Ravi -- Thanks for the question regarding
      "Tracking the progress of a long running delete", version 8.1.7

      You Asked
Recently I came across a FAQ which gives a way to track the progress of a long
running delete. Is the below anwser correct. If it is why can't oracle provide
the tracking in a v$ kind of view.

Question: What query would you run to track the progress of an Oracle DELETE
statement ??
Correct Answer: select TRUNC(sum(USED_UREC)/&&num_of_indexes)
from v$transaction
where addr in
(select taddr
from v$session
where audsid in
(select audsid
from v$session
where sid=&&the_sid
)
)

You have to divide the answer by the number of indexes on the table, since the
query results include the deleted rows from all the indexes. Using AUDSID IN
(&&the_sid) includes the results from all parallel slaves associated with the
main delete SID.
:

      and we said...
We do -- it is called v$session_longops.

In fact, using dbms_application_info, you yourself could populate this dynamic
performance view during the execution of say a long running procedure so you
could monitor IT'S progress as well.  My book "Expert one on one Oracle" goes
into that in some detail.

Search for that (v$session_longops) here to read more about it.
Review &
      Followup

      Rating:
      4
v$session_longops  January 03, 2003

Reviewer:  Ravi Reddy  from Singapore
a. Is the sql given before true to track the delete statement.

Also v$session_longops some times do not show the delete progress.It shows long
running analyze statements but not delete's. The particular delete ran for 50mts
but there is no entry in v$session_longops.

          Followup:

that'll show you a measure of how much rollback that is using -- don't know how
to translate that directly into a "progress monitor".  The v$session_longops
view does.

BIG_TABLE(12,819) ospid = 4388 command = 7 program = sqlplus@aria-dev (TNS
V1-V3) dedicated server=4389
Friday    14:31  Friday    14:31 last et = 5
delete from big_table

ops$tkyte@ORA817DEV> @printtbl8 'select * From v$session_longops where sid = 12
and serial# = 819 and time_remaining > -0';
SID                           : 12
SERIAL#                       : 819
OPNAME                        : Table Scan
TARGET                        : BIG_TABLE.BIG_TABLE
TARGET_DESC                   :
SOFAR                         : 110
TOTALWORK                     : 22822
UNITS                         : Blocks
START_TIME                    : 03-jan-2003 14:31:26
LAST_UPDATE_TIME              : 03-jan-2003 14:32:01
TIME_REMAINING                : 7227
ELAPSED_SECONDS               : 35
CONTEXT                       : 0
MESSAGE                       : Table Scan:  BIG_TABLE.BIG_TABLE: 110 out of
22822 Blocks done
USERNAME                      : BIG_TABLE
SQL_ADDRESS                   : 86879A08
SQL_HASH_VALUE                : 734451454
QCSID                         : 0
-----------------

PL/SQL procedure successfully completed.

it works for me....

January 03, 2003 xavier -- Thanks for the question regarding
      "dbms_job.submit question of intervals of time", version 8.1.6

      You Asked
Hi tom , I have this question many days ago.

dbms_job.submit  question of intervals

I read the number of november/december of the oracle magazine.
I read and tested the article of the STATSPACK.

I see the statsauto.sql sql file.

--
--  Schedule a snapshot to be run on this instance every hour,
on the hour

variable jobno number;
variable instno number;
begin
  select instance_number into :instno from v$instance;
  dbms_job.submit(:jobno, 'statspack.snap;',
trunc(sysdate+1/24,'HH'), 'trunc(SYSDATE+1/24,''HH'')', TRUE,
:instno);
  commit;
end;
/

I want to submit the call to a statspack.snap each day at 7:00
and 15:00 only from monday to tuesday.

*****************************************************************
********************
I write this code to do this.
This code i call at startup the database
*****************************************************************
********************

CONNECT INTERNAL/ORACLE
/
DROP TABLE CONTROL_JOB
/
CREATE TABLE CONTROL_JOB
( FECHA DATE );
/
DELETE CONTROL_JOB
/
DECLARE
  v_job_number number;
  v_comando    varchar2(1000);
begin
--   FOR X IN (SELECT JOB FROM USER_JOBS) LOOP
--       DBMS_JOB.REMOVE(X.JOB);
--   END LOOP;
--   select instance_number into :v_instno from v$instance;

   v_comando := 'begin
                   insert into control_job
                   values (SYSDATE);
                   statspack.snap;
                  end;';

   dbms_job.submit(v_job_number , v_comando
,trunc(next_day(sysdate,'LUNES'))     +  7/24 ,
'trunc(next_day(sysdate,''LUNES''))     + 7/24' );
   dbms_job.submit(v_job_number , v_comando
,trunc(next_day(sysdate,'MARTES'))    +  7/24 ,
'trunc(next_day(sysdate,''MARTES''))    + 7/24' );
   dbms_job.submit(v_job_number , v_comando
,trunc(next_day(sysdate,'MIÉRCOLES')) +  7/24 ,
'trunc(next_day(sysdate,''MIÉRCOLES'')) + 7/24' );
   dbms_job.submit(v_job_number , v_comando
,trunc(next_day(sysdate,'JUEVES'))    +  7/24 ,
'trunc(next_day(sysdate,''JUEVES''))    + 7/24' );
   dbms_job.submit(v_job_number , v_comando
,trunc(next_day(sysdate,'VIERNES'))   +  7/24 ,
'trunc(next_day(sysdate,''VIERNES''))   + 7/24' );

   dbms_job.submit(v_job_number , v_comando
,trunc(next_day(sysdate,'LUNES'))     + 15/24 ,
'trunc(next_day(sysdate,''LUNES''))     + 15/24' );
   dbms_job.submit(v_job_number , v_comando
,trunc(next_day(sysdate,'MARTES'))    + 15/24 ,
'trunc(next_day(sysdate,''MARTES''))    + 15/24' );
   dbms_job.submit(v_job_number , v_comando
,trunc(next_day(sysdate,'MIÉRCOLES')) + 15/24 ,
'trunc(next_day(sysdate,''MIÉRCOLES'')) + 15/24' );
   dbms_job.submit(v_job_number , v_comando
,trunc(next_day(sysdate,'JUEVES'))    + 15/24 ,
'trunc(next_day(sysdate,''JUEVES''))    + 15/24' );
   dbms_job.submit(v_job_number , v_comando
,trunc(next_day(sysdate,'VIERNES'))   + 15/24 ,
'trunc(next_day(sysdate,''VIERNES''))   + 15/24' );

   commit;
end;
/
select *
  from user_jobs
/

¿ Is posible to simplify this code in one call to dbms_job or
two calls ?

SQLWKS> ALTER SESSION SET NLS_DATE_FORMAT = 'DD/MM/YYYY
HH24:MI:SS'
     2> /
Sentencia procesada.
SQLWKS> SELECT SYSDATE , TO_CHAR(SYSDATE,'Day') FROM DUAL
     2> /
SYSDATE              TO_CHAR(S
-------------------- ---------
22/11/2000 20:46:57  Miércoles
1 fila seleccionada.
SQLWKS> select trunc(next_day(sysdate,'LUNES'))     + 7/24 from
dual
     2> /
TRUNC(NEXT_DAY(SYSDA
--------------------
27/11/2000 07:00:00
1 fila seleccionada.
SQLWKS> select trunc(next_day(sysdate,'MARTES'))    + 7/24 from
dual
     2> /
TRUNC(NEXT_DAY(SYSDA
--------------------
28/11/2000 07:00:00
1 fila seleccionada.
SQLWKS> select trunc(next_day(sysdate,'MIÉRCOLES')) + 7/24 from
dual
     2> /
TRUNC(NEXT_DAY(SYSDA
--------------------
29/11/2000 07:00:00
1 fila seleccionada.
SQLWKS> select trunc(next_day(sysdate,'JUEVES'))    + 7/24 from
dual
     2> /
TRUNC(NEXT_DAY(SYSDA
--------------------
23/11/2000 07:00:00
1 fila seleccionada.
SQLWKS> select trunc(next_day(sysdate,'VIERNES'))   + 7/24 from
dual
     2> /
TRUNC(NEXT_DAY(SYSDA
--------------------
24/11/2000 07:00:00
1 fila seleccionada.

Thanks in advance

xavier sarrate

alfa@edisa.es

      and we said...
You say "monday - tuesday" but your logic seems to be
monday-friday.  So, I will do mon-fri (and if it is mon-tues,
just ask again -- its easy to fix)

Submit one job with a next =

decode( sign( 15-to_char(sysdate,'hh24')),
         1, trunc(sysdate)+15/24,
         trunc( sysdate +
                  decode( to_char(sysdate,'d'), 6, 3, 1))+7/24
      )

that logic says:

  if it is before 15:00 then
     return TODAY at 15:00 (eg: if we are running at 7am, we
                            want to run at 3pm today)
  else
     return today + either 3 (if it is friday) or 1 (otherwise)
            at 7am
  end if

This shows for a given Date -- what the next date would be:

scott@ORA8I.WORLD> select dt, decode( sign(
15-to_char(dt,'hh24')), 1, trunc(dt)+15/24,
  2  trunc( dt+ decode( to_char(dt,'d'), 6, 3, 1))+7/24) from
  3  ( select trunc(sysdate)+rownum+7/24 dt from all_objects
            where rownum < 12
  4    union all
  5    select trunc(sysdate)+rownum+15/24 dt from all_objects
            where rownum < 12
  6  )
  7  where to_char(dt,'dy') not in ( 'sat', 'sun' )
  8  order by 1
  9  /

DT                             DECODE(SIGN(15-TO_CHAR(DT,'HH2
------------------------------ ------------------------------
thursday  30-nov-2000 07:00:00 thursday  30-nov-2000 15:00:00
thursday  30-nov-2000 15:00:00 friday    01-dec-2000 07:00:00
friday    01-dec-2000 07:00:00 friday    01-dec-2000 15:00:00
friday    01-dec-2000 15:00:00 monday    04-dec-2000 07:00:00
monday    04-dec-2000 07:00:00 monday    04-dec-2000 15:00:00
monday    04-dec-2000 15:00:00 tuesday   05-dec-2000 07:00:00
tuesday   05-dec-2000 07:00:00 tuesday   05-dec-2000 15:00:00
tuesday   05-dec-2000 15:00:00 wednesday 06-dec-2000 07:00:00
wednesday 06-dec-2000 07:00:00 wednesday 06-dec-2000 15:00:00
wednesday 06-dec-2000 15:00:00 thursday  07-dec-2000 07:00:00
thursday  07-dec-2000 07:00:00 thursday  07-dec-2000 15:00:00
thursday  07-dec-2000 15:00:00 friday    08-dec-2000 07:00:00
friday    08-dec-2000 07:00:00 friday    08-dec-2000 15:00:00
friday    08-dec-2000 15:00:00 monday    11-dec-2000 07:00:00

14 rows selected.

Review &
      Followup

      Rating: 2
Wasn't able to
      get example to work.  January 03, 2003
Reviewer:
      JE  from col,oh
Wasn't able to get this to work:
variable jobno number;
variable instno number;
begin
  select instance_number into :instno from v$instance;
  dbms_job.submit(:jobno, 'statspack.snap;', decode( sign(
15-to_char(sysdate,'hh24')),
         1, trunc(sysdate)+15/24,
         trunc( sysdate +
                  decode( to_char(sysdate,'d'), 6, 3, 1))+7/24
      )

,'trunc(SYSDATE+1/24,''HH'')', TRUE, :instno);
  commit;
end;

gives me error:
                  decode( to_char(sysdate,'d'), 6, 3, 1))+7/24
                  *
ERROR at line 6:
ORA-06550: line 6, column 19:
PLS-00204: function or pseudo-column 'DECODE' may be used inside a SQL statement
only
ORA-06550: line 3, column 3:
PL/SQL: Statement ignored
Am I missing something.

          Followup:

decode cannot be called from PLSQL, must be done in SQL as the error says.

so, just

select decode( .... ) into l_date_variable from dual;
dbms_job.submit(:n,'x', l_date_variable, 'trunc....' );

January 03, 2003 Seshu -- Thanks for the question regarding
      "Striping Datafiles across unix level disk partitions", version 8.0.5

      You Asked
Hi,

I've 110 GB database (ARCHIVELOG),having 60 datafiles.Most of
the
datafiles are of 2 GB in size.

These datafiles spread across 10 volume groups(OS disks) with 26
different mount points.
ex:
VolGroups(OS)  File-MountPoints
-----------   ----------------
pd/vlg01       /appl/disk01
pd/vlg01       /appl/disk02
pd/vlg01       /appl/disk03
pd/vlg02       /appl/disk01
pd/vlg02       /appl/disk02
pd/vlg03       /appl/disk01
pd/vlg03       /appl/disk02
pd/vlg03       /appl/disk03
pd/vlg04       /appl/disk01
pd/vlg04       /appl/disk02
pd/vlg04       /appl/disk03
pd/vlg04       /appl/disk04 .. and so on.

We've implemented 'Table Partition' concept and we could gain
better performance, but still we're facing I-O issues.
Now,I want to re-organize the datafiles,across 4 newly striped
volume groups and only 4 mount points.,

I am planning to identify those datafiles,having more
reads/writes and want to spread them evenly across the new
striped disks,and then  rest of the files.But I am scared to
move the SYSTEM datafile.I am not sure,whether it matters.

Can you please suggest me the best way to do this?
Thanks in adv.

      and we said...
Don't be scared to move system, it is just a tablespace.  It
just happens to be the one we use internally but it is just a
tablespace all of the same.

You would want to distributed the files in such a way that at
the physical level the read/write IO was as evenly distributed
as possible.  In most production systems -- you would find
system to be fairly "quiet" after the database is warmed up with
little to no IO activity after a while.  If there is a lot of
reading/writing going on that would indicate lots of space
management (something you want to avoid) or recompilation of
code/ DDL being performed (something again that you want to
avoid).
Review
      & Followup

      Rating:
      3
  January 03, 2003
Reviewer:
      A reader
Hi Tom,
You said
<start quote>
If there is a lot of reading/writing going on that would indicate lots of space
management (something you want to avoid) or recompilation of code/ DDL being
performed (something again that you want to avoid).
<end quote>

Using locally manage tablespace with uniform size and segment space management
auto for all tablespaces including the system tablespace is the way to avoid
what you said? or Did I totally misunderstand your point?

Thanks

          Followup:

think about what writes to system -- DDL, compiling a procedure, etc....

think about what reads from system -- dictionary lookups, etc...

in a production system, you would not do ddl, compile procedures.  So the
writing should be low.

in a production system, you would have a shared pool sized right to hold your
working....

LMT, DMT - doesn't matter. Well, a DMT might have more read/write activity due
to uet$, fet$ (space management) but you generally don't have hundreds of
extents being added every day....

January 03, 2003 Henry -- Thanks for the question regarding "SQL
      Server Guy having a Mutation problem, I understand why but cant think of a
      workaround.", version 8.1.7

      You Asked
I have looked at Avoiding Mutating Tables and it does not seem to cover my
issue.

I have a table that is something like this

MyTable
( id integer,
  owner_id integer,
  name varchar(2))

One record in MyTable can own another. So I have the following data, if a record
is not owned by another record it's owner_id is it's id.
So I have the following data

1,1,Cat
2,1,Dog
3,3,Mouse

Now if I delete the Cat record ID= 1 I want to update the Dog record ID=2 so
it's owner_id=2 but I get a mutating table issue.
Any suggestions?

Thanks

Henry

      and we said...
You really have a model that does that??? wow.

Here is how I would implement that in Oracle:

ops$tkyte@ORA817DEV> create table t ( id int primary key,
  2                                   owner_id references t ON DELETE SET NULL,
  3                                   name varchar2(10) );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert into t values ( 1, 1, 'Cat' );

1 row created.

ops$tkyte@ORA817DEV> insert into t values ( 2, 1, 'Dog' );

1 row created.

ops$tkyte@ORA817DEV> insert into t values ( 3, 3, 'Mouse' );

1 row created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace view v
  2  as
  3  select id, owner_id, nvl(owner_id,id) fake_owner_id, name
  4    from t
  5  /

View created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select * from v;

        ID   OWNER_ID FAKE_OWNER_ID NAME
---------- ---------- ------------- ------------------------------
         1          1             1 Cat
         2          1             1 Dog
         3          3             3 Mouse

ops$tkyte@ORA817DEV> delete from v where id = 1;

1 row deleted.

ops$tkyte@ORA817DEV> select * from v;

        ID   OWNER_ID FAKE_OWNER_ID NAME
---------- ---------- ------------- ------------------------------
         2                        2 Dog
         3          3             3 Mouse

Tastes great, less filling -- no code, done

You can go as far as to create an index on (nvl(owner_id,id)) in order to search
if you like.

99.999% of the time when people hit the mutating table constraint -- there is a
far easier way to do it (or the model is really not a good design for an RDBMS).

Review &
      Followup

      Rating: 2
It does not
      solve the issue  January 03, 2003
Reviewer:
      SriDHAR  from USA
Tom,

Your solution is educating, yet it does not solve the issue posed. User wants
the owner_id to set to id, not to NULL value.

Just an observation from reader's perspective.

          Followup:

It solves the problem.

It more then solves the problem.  It is a different way to get there, but tell
me -- how does this NOT solve the problem which is:

o owner should be settable to a specific value (dog owned by cat)
o if the owner is deleted, the owner should go back to that rows key (dog owns
dog)

So, look at the data, look at the solution and tell me that it does not solve
the problem.

Can we make this 100% transparent even?  To make it look just like "sql server"
(which is not my goal in life, I propose people do things in the best way
possible given the software they are using -- do it the best way for SS, do it
the best way for Oracle).  Consider:

ops$tkyte@ORA817DEV> create table t ( id int primary key,
  2                   owner_id references t ON DELETE SET NULL,
  3                   name varchar2(10) );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace view MyTable
  2  as
  3  select id, nvl(owner_id,id) owner_id, name
  4    from t
  5  /

View created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace trigger myTable_trigger
  2  instead of insert or update on myTable
  3  begin
  4     if (inserting)
  5     then
  6         insert into t (id,owner_id,name) values (:new.id,
:new.owner_id,:new.name);
  7     else
  8         update t set owner_id = :new.owner_id, name = :new.name
  9          where id = :new.id;
 10     end if;
 11  end;
 12  /

Trigger created.

ops$tkyte@ORA817DEV> insert into myTable values ( 1, 1, 'Cat' );

1 row created.

ops$tkyte@ORA817DEV> insert into myTable values ( 2, 1, 'Dog' );

1 row created.

ops$tkyte@ORA817DEV> insert into myTable values ( 3, 3, 'Mouse' );

1 row created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select * from myTable;

        ID   OWNER_ID NAME
---------- ---------- ------------------------------
         1          1 Cat
         2          1 Dog
         3          3 Mouse

3 rows selected.

ops$tkyte@ORA817DEV> delete from myTable where name = 'Cat';

1 row deleted.

ops$tkyte@ORA817DEV> select * from myTable;

        ID   OWNER_ID NAME
---------- ---------- ------------------------------
         2          2 Dog
         3          3 Mouse

2 rows selected.

but the best practice implementation is as I described above.  I do not spoon
feed wrong ways here, I offer up the best way to accomplish your GOALS.  More
then once I've said:

I could tell you how to do what you ask however, what you are asking to do is
really bad.  I'll tell you instead how to do this....

That is exactly what I did here.

They lose no functionality here.  They gain alot (less code - like none)

      Rating: 3
Solves it
      now  January 03, 2003
Reviewer:  SriDHAR
       from USA
I only meant to point out that you might have overlooked the user's functinality
request. I was just giving my observation, often we do not know why in the world
users want it. Nevertheless, I was trying the solution myself and tried couple
of options,

1. First option was,

  1  create or replace trigger t_tgr after delete
  2  on t for each row
  3  declare
  4   pragma autonomous_transaction;
  5  begin
  6   update t set owner_id = id where owner_id is null;
  7   commit;
  8* end;
SQL> /

Trigger created.

 real: 100
SQL> select * from t;

        ID   OWNER_ID NAME
---------- ---------- ----------
         1          1 Cat
         2          1 Dog
         3          3 Mouse

 real: 60
SQL> delete from t where id = 1;
delete from t where id = 1
            *
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
ORA-06512: at "APPS.T_TGR", line 4
ORA-04088: error during execution of trigger 'APPS.T_TGR'

2. Second was the statement level trigger that you have just shown. By the time
I came back, you are already there with the solution.

SQL> create or replace trigger t_tgr after delete on t
  2  begin
  3   update t set owner_id = id where owner_id is null;
  4  end;
  5  /

Trigger created.

 real: 121
SQL> select * from t;

        ID   OWNER_ID NAME
---------- ---------- ----------
         1          1 Cat
         2          1 Dog
         3          3 Mouse

 real: 70
SQL> delete from t where id = 1;

1 row deleted.

 real: 30
SQL> select * from t;

        ID   OWNER_ID NAME
---------- ---------- ----------
         2          2 Dog
         3          3 Mouse

I DO AGREE THAT YOUR ORIGINAL SOLUTION TAKES IT IN THE RIGHT DIRECTION.

          Followup:

1) first option will lock the system up with deadlocks as quick as a blink of an
eye.

2) would be a performance nightmare.  Do you really want to full scan the table
everytime you delete?

      Rating: 5
How about
      letting each row take care of it's self?  February 24, 2003

Reviewer:  Chuck Jolley  from Oklahoma City, OK USA

If setting an un-owned row to own it's self is realy a business rule why not do
it this way?

chajol@tax.cupid> create table t ( id int primary key,
  2                   owner_id references t ON DELETE SET NULL,
  3                   name varchar2(10) );

Table created.

chajol@tax.cupid> create or replace trigger t_orphan
  2  before update of owner_id
  3  on t
  4  referencing new as new old as old
  5  for each row
  6  begin
  7     if :new.owner_id is null then
  8        :new.owner_id := :new.id;
  9     end if;
 10* end;
chajol@tax.cupid> /

Trigger created.

chajol@tax.cupid> insert into t values(1, 1, 'cat');

1 row created.

chajol@tax.cupid> insert into t values(2, 1, 'dog');

1 row created.

chajol@tax.cupid> insert into t values(3, 3, 'mouse');

1 row created.

chajol@tax.cupid> select * from t;

        ID   OWNER_ID NAME
---------- ---------- ----------
         1          1 cat
         2          1 dog
         3          3 mouse

chajol@tax.cupid> delete from t where name = 'cat';

1 row deleted.

chajol@tax.cupid> select * from t;

        ID   OWNER_ID NAME
---------- ---------- ----------
         2          2 dog
         3          3 mouse

chajol@tax.cupid>

Which does bring up the question:
Is the behind the curtain work done for a trigger like this more, less, or "who
knows?" than the corresponding NVL + view code?
Thanks,
chuck

January 03, 2003 Antoine -- Thanks for the question regarding
      "naming convention", version 8.1.7

      You Asked
Hi Tom,

I would like to have your point of view on naming convention?
For exemple, do you limit the number of characeter (let say 20) for the name of
a table?

Does Oracle provide a template for the naming convention in the database?

Thanks,

Antoine

      and we said...
I have no naming conventions personally.  I don't like to type really long
names, but I have hit the 30 character limit from time to time.

Oracle per-say does not.  Tools like Designer can help you enforce one.  It is a
matter of choice more then anything.

The only naming conventions I have are in PLSQL.

local variables must start with  L_
parameters must start with P_
global package variables must start with G_

That is so a Parameter ENAME won't get confused with a column named ENAME in a
query -- I never would have a column p_ename -- so there is no ambiguity.

Beyond that (and beyond disliking prefixing column names with their table names
-- talk about pedantic) I haven't any.

Review &
      Followup

      Rating:
      3
But...  December 03, 2002
Reviewer:
      A reader
Tom,
Thanks for sharing your thoughts. But don't you think that there should be some
recommended guidelines or standard practices from Oracle. Oracle has come a long
way now, and the time has come to put atleast some guidelines. I am sure that
people will atleast think in this direction. Otherwise, it is really a difficult
task especially for consultant who is running between projects, and can not make
any educated guess that table names may be starting with tb or ending with tb
etc.,  or code columns are starting/ending with cde, code or cd. Date columns is
a typical example, some sites have names having dte, date, dt etc., and views
are hard to identify by name until and unless you check for object type and list
goes on. One of the good example is about OFA. Most of the sites are following
OFA, and DBA need not to spend much time in understanding how the file system is
organised etc.
Thanks

          Followup:

But.... the same is true of C, of VB (remember MS trying to get us to use
"hungarian notation" -- variables were named after their datatypes and case was
important and all that -- i despised that when I was forced to program in
windows) and anything.

The military has naming conventions out the wazoo -- mil std 8320.1 for example.
 For all the good it did.

naming conventions are a very personal thing.  You either love them or they are
foisted on you.  It is upto a company or group to come to some conclusion as to
what is best for them.

      Rating: 3
Could you
      comment on this convention please?  December 03, 2002

Reviewer:  Ramakrishna. V  from India
Dear Tom,
  I have been waiting to post this for a long time but could never get past the
queue.  Actually, I have been unable to post a question for the last 3 weeks
from 9.30 AM to 7.30 PM IST which is 4 AM to 2 PM GMT.  Do you have any
suggestions on what time of day would be best to try?

Since this question has come up now anyway, could you
kindly review the following convention critically?

Object Naming Conventions:
-------------------------
1.  ALL objects in an application use a common 2 or 3 letter prefix
2.  Trigger names end with _TR, package names with _PKG and proc names
    with _PR
3.  The fact that an object is a table or view is NOT encoded in the
    object's name to avoid application changes if a view changes to
    a table later.

Column Naming Conventions:
--------------------------
1.  Every table will have a single column numeric primary key called
    'ID'.  In case the table needs a multi-column primary key, this
    will instead be replaced with a multi-column unique constraint in
    addition to having the surrogate ID as primary key.
2.  Column names will have a short description of the contents.  They
    do NOT have a table prefix (not normally) and do not encode the
    data type.  Examples of column names are SHORT_DSC, LONG_DSC,
    START_DATE, etc.
3.  Foreign key columns will have the table name (or an abbreviation of
    that) prefixed to ID.  For example, a table called MS_COMPANY that has
    a foreign key to MS_COUNTRY will have a column COUNTRY_ID.
4.  Denormalized columns from other tables will also have a table name
    prefix.  For example, in the above table where MS_Company has a foreign
    key to MS_Country, if I wanted to store the Country's description also
    since it is used frequently, then that will be called COUNTRY_SHORT_DSC

To sum up, all the table's 'own' columns will be un-prefixed while the
foreign key columns will be prefixed with a table name.

Are there some written (or unwritten) naming conventions (especially column
naming) that Oracle follows or recommends throughout its RDBMS, the
applications it develops, etc?

thanks,
Ramakrishna

          Followup:

4 AM to 2 PM GMT is 11pm to 9am EST which is where I am.  I generally start
attacking these about 7/7:30am mytime.  9-10am (got other things I do) is when
the queue opens up again.  I do a batch at night as well -- opening it up around
8/9pm typically.  you are right on the begin/end of my times (i sleep in between
;)

objects:

1) sounds good -- we do that here lots.  All of the asktom tables start with
wwc_ for example.

2) I've always used "meaningfulname_BIFER" for triggers where BIFER might be
AIFER, AU, etc:

bifer = before insert, for each row
bi = before insert
budfer = before update/delete, for each row
aiudfer = after insert/update/delete, for each row
and so on...

I many times use _PKG for packages to set them apart from tables.

I don't use procedures for real code -- very bad practice.  I use procedures for
standalong utilities like print_table, show_space and such.  It would just be a
hassle to have _PR (like SQLServer with "sp_" on the front, yuck). So, since I
don't use procedures for "real" code, I don't have any preferences there other
then the name should be meaningful -- i don't put _pr  (what do you do for
functions? _fu?)

3) good idea, a view is a table, a table is a view as far as I'm concerned.
There should be no difference between them.

columns:

1) Sure, almost every table I have has one of these

2) perfect

3) that sounds like a good idea, yes.

4) ok -- if you do this denormalization -- which is generally frowned upon...

Within Oracle -- the "older" APPS team (HR, FinApps -- ERP stuff) has a very
long and detailed set of coding standards.  the "newer" Apps team (crm) has one
as well -- they are now merged into one team "APPS" and are consolidating their
methods.  Yes, they have a very formal one.

The kernel code -- written in C -- has coding standards, naming conventions as
well -- they are necessarily different then the ones APPS uses, different
languages, different "things"

The dictionary guys -- well, sure but it changes over time and they don't roll
the changes back into the established dictionary over time so you see some
inconsistencies there....

      Rating: 5
naming
      convention  December 04, 2002
Reviewer:  Antoine
      Bocti  from Montreal, Canada
Thanks Tom,

BTW: Do you think Oracle is going to increase the 30 characters limit in futur
version?

          Followup:

I'm not aware of any plans to do so personally.

      Rating: 5
Roll your
      own  December 04, 2002
Reviewer:  Sean
       from Reston, VA
Your best bet would be to look for some standard naming convention practices
online, and produce some type of standard that you always use. About three years
ago, we came up with a five page naming conventions & coding practices document
that I memorized about 2.9 years ago and never looked at again.  It closely
resembled most of Oracle's supplied packages coding practices and naming
conventions, simply because it looked like their code.  If you look in
$ORACLE_HOME/rdbms/admin/* you'll find LOTS of example code to go by.  You can
also just do a google search on Oracle Naming Conventions and check out one of
the 22,200 hits it finds ;-).  Good luck

      Rating: 4
naming
      conventions  December 04, 2002
Reviewer:  Ed
       from Cheshire, UK
You could click your way to
http://examples.oreilly.com/orbestprac/
which points to a zip file containing some
suggested naming conventions (related to Steve Feuerstein's book).

Personally, I use Tom's l_, g_ and p_ prefixes and tailor anything else to the
demands of pedantic management and DBAs or what is appropriate to the
application I am developing.

      Rating: 5
Naming
      Conventions - QMS  December 04, 2002
Reviewer:
      jan van mourik  from Houston, TX
A while ago, around Oracle 6 as I recall correctly, Oracle had something called
QMS. A set of standards for design, development. It might have been written by
Oracle UK, I kinda forgot. I used to have a copy of it back in the Netherlands
but somehow somewhere I lost it.
Just wondering if anybody heard of it?

      Rating:
      5
Oops..  December 04, 2002
Reviewer:
      jan  from Houston, TX
"as I recall correctly"

IF I recall correctly -- my memory isn't that good...

      Rating: 4
global pkg
      variables names...  December 04, 2002
Reviewer:
      Robert  from PA
>> The only naming conventions I have are in PLSQL.
>>local variables must start with  L_
>>parameters must start with P_
>>global package variables must start with G_

global package variables --- defined in spec or just body  --- does that make
any difference to you in terms of naming convention ?

          Followup:

spec or body -- doesn't matter to me ( although, I try to keep them out of the
spec unless I really really have to )

      Rating: 5
Great
      description.......'pedantic'!  December 04, 2002

Reviewer:  Robert  from Memphis, USA!
We should have strong simple skeleton of standards similar to Tom and
Ramakrishna (above), etc.
After that it is up to the individual developer...
More than this is oppressive... less than this is chaos.

Thanks,

Robert.

      Rating: 4
What about
      cursors  December 04, 2002
Reviewer:  Chris
       from UK
I would add a convention for arguments to cursors so that there can be no
confusion in the select statement - which can access global and local variables
and procedure arguments. I think accessing global and local variables and
procedure arguments within the cursor is a bad thing as it is not obvious at the
point the cursor is invoked.

          Followup:

Being one that never uses EXPLICIT cursors -- i have no comment.

You'll not be seeing code from me that has:

  cursor c is .....

in it.  It'll all be

   select ... into ...from ...

or
   for x in (select .....)

or
   open <some cursor variable that starts with L_ or P_> for select.....

;)

      Rating: 3
Keep it simple
      or no one will use it...  December 04, 2002

Reviewer:  Colin Davies  from Pasadena, California

Here is a simple set of conventions I use (and document in each package
specification file):

Naming Convention Used (identifier prefixes)

gt_      Global Type
gc_      Global Constant
gv_      Global Variable
gcur_    Global Cursor Definition

t_       Local Type
c_       Local Constant
v_       Local Variable
cur_     Local Cursor Definition

i_       In     Parameter
io_      In Out Parameter
o_          Out Parameter

excp_    Exception

      Rating: 4
Hate Explicit
      Cursor  December 05, 2002
Reviewer:  Robert
       from PA
>> You'll not be seeing code from me that has:
>> cursor c is .....

Hi Tom, been wanting to ask you this...
So we know you hate Exp Cursor, and especially the Fetch Into/Loop part, (but
Explicit-Cursor-For-Loops are cool with you), So you have implicit cursor dotted
all over your app.
How would you say to a co-worker or boss who believes
strongly in the benefit of Explicit Cursor in "code organization" and that
SELECTs should be easily visible and
collected in declaration section as much as possible ?
Thanks

          Followup:

IMPLICIT cursor for loops

  for x in ( select ... )

I would say we have a difference of opinion.  I find code such as:

   open c;
   loop
      fetch c into record
      exit when c%notfound
      ...
   end loop

or
   for rec in c
   loop
      ...
   end loop;

Lots less readable, understandable (I'm asked to look at lots of code) than

   for x in ( select * from all_users )
   loop
      ...
   end;

You know -- what the HECK is "C" -- oh wait, I have to page up 5 times to read
what "C" was, now page back down -- oh wait, what table did the 4th column come
from?  Oh yeah -- page up, page down.  I find  it to flow alot better when the
SQL is plainly visible, in line, right there.  If the sql gets heinously large,
well, that is what a VIEW is all about in my opinion.

Matter of taste I guess, but from a guy who looks at lots of other peoples PLSQL
-- I can say that having the SQL inlined is infinitely easier for me.

      Rating: 4
Cursor
      Update  December 05, 2002
Reviewer:  Robert
       from Ireland
Hi Tom,

I agree, your cursor is much more readable.  Can you do a "select for update"
this way?  If so how do you refer to it in your "where current of" clause?

          Followup:

I generally see select for update on single rows (select into for update) -
hardly ever in a loop but in an implicit cursor loop (where you by definition
don't have a cursor "named") you DIY:

  for x in ( select rowid rid, a.* from t a for update )
  loop
       ...
       update t set ... where rowid = rid; /*+ instead of where current of */

if you check out a sql_text of a where current of update -- you'll find
(surprisingly) that plsql just turns that into "where rowid = :rid" ;) anyway.

      Rating: 4
declaring
      cursors  December 05, 2002
Reviewer:  Ed
       from Cheshire, UK
But Tom, I'm SURE I've read elsewhere on your site that you always try and code
to fit each routine on a screen, so paging up and down won't be an issue for you
;-)

An argument I heard for declaring cursors is in case you want to use it more
than once in a routine, though I would have thought if this is required, the
code should be modularized to another level.

If pedantic management / DBAs insist on having cursors in the DECLARE section,
you can at least make them more meaningful e.g. "c_get_outstanding_payments"
instead of "c".

          Followup:

but -- if you hide the cursor at the top of the package, or in the declare
section (which I don't count as code! ;)....

      Rating: 4
implicit or
      explicit cursors  December 06, 2002
Reviewer:
      Bruno  from Belgium
Hi Tom,

always amazed by your responses. Really great!  Please excuse me to ask you the
following...
I dislike the
  open c;
  loop
    fetch c into record
    exit when c%notfound
    ...
  end loop;
way, but I "always" use
cursor c1 is ...
  for r1 in c1
  ...
I find "your" way 'for r in ( select ... ) ...' less readable because I prefer
to separate the distinct parts of the code;  I mean that, for ex., instead of
writing a long list of instructions (a program), people usually define functions
and procedures and ultimately, the main program is made readable because it is a
"short" list of tests ans calls to procedures.

(
To give another example: it can be that re-assigning the same value again and
agin in a loop is more readable, but this is not a good idea:
loop
 x := 'value';
 ... (x not modified)
end loop;
)

Anyway, you write about the human being point of view (useful when READING the
code, not when EXECUTING it), and this is my question:
What does the kernel prefers?
Maybe we spend a lot of time on writing / modifying applications, but I hope
that the code will be used many many times, so we have to write in an "optimize
execution"-minded way.

Best regards and tanks a lot for the time spent!

          Followup:

I don't see the correlation between your example of assigning a constant in a
loop (inefficient code, bad code) and using a cursor for loop:

   for x in ( select ... )

seems to be comparing apples and flying toaster ovens.

Anyway, you seem to consider the SQL statement like a function call.  I simply
don't see it that way.  I see it (the sql statment) as being the single most
important thing in the procedure itself.  I prefer to see it inline.

We can see which is "better" by using runstats (seems I've put this up so many
times that others would start using it!!)

http://asktom.oracle.com/~tkyte/runstats.html

ops$tkyte@ORA920> create or replace package demo_pkg
  2  as
  3          procedure explicit;
  4          procedure implicit;
  5  end;
  6  /

Package created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package body demo_pkg
  2  as
  3          procedure explicit
  4          is
  5                  cursor c is select * from all_users;
  6          begin
  7                  for x in c
  8                  loop
  9                          null;
 10                  end loop;
 11          end;
 12
 13          procedure implicit
 14          is
 15          begin
 16                  for x in ( select * from all_users )
 17                  loop
 18                          null;
 19                  end loop;
 20          end;
 21  end;
 22  /

Package body created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      l_start number;
  3      l_run1  number;
  4      l_run2  number;
  5  begin
  6      insert into run_stats select 'before', stats.* from stats;
  7
  8      l_start := dbms_utility.get_time;
  9          for i in 1 .. 1000
 10          loop
 11                  demo_pkg.explicit;
 12          end loop;
 13      commit;
 14      l_run1 := (dbms_utility.get_time-l_start);
 15      dbms_output.put_line( l_run1 || ' hsecs' );
 16
 17      insert into run_stats select 'after 1', stats.* from stats;
 18      l_start := dbms_utility.get_time;
 19          for i in 1 .. 1000
 20          loop
 21                  demo_pkg.implicit;
 22          end loop;
 23      commit;
 24      l_run2 := (dbms_utility.get_time-l_start);
 25      dbms_output.put_line( l_run2 || ' hsecs' );
 26      dbms_output.put_line
 27      ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) || '% of the time' );
 28
 29      insert into run_stats select 'after 2', stats.* from stats;
 30  end;
 31  /
321 hsecs
319 hsecs
run 1 ran in 100.63% of the time

PL/SQL procedure successfully completed.

they are for all intents and purposes the same when used this way.

      Rating: 5
Thank
      you  December 06, 2002
Reviewer:  Bruno
       from Belgium
Thank you Tom.
My point about the example of bad code was just to write "more readable" dosn't
mean "better".
Thanks for the clear and fast answer; I'll try to change my coding habits.

      Rating: 5
parameter
      naming  January 03, 2003
Reviewer:  Will
       from Austin
Tom,

I've always used:

i_   In
io_  In/Out
o_   Out

for parameters, but am considering switching to your p_ convention.

Don't you think it's sort of handy to know the parameter's direction at a
glance?

If not, why?

          Followup:

I guess cause I rarely use OUT parameters and when I do -- i just "know" they
are out.  Besides - sometimes an IN becomes an OUT later -- then you have to
change the name everywhere...

      Rating: 3
Comment on
      Ramakrishna's Naming Conventions  January 08, 2003

Reviewer:  John Gilmore  from United Kingdom

"Every table will have a single column numeric primary key
 called 'ID'.  In case the table needs a multi-column
 primary key, this will instead be replaced with a multi-
 column unique constraint in addition to having the
 surrogate ID as primary key."

I once worked on a site where *every* table had a numeric surrogate primary key
driven by a sequence.  This was even the case when a perfectly adequate numeric
natural key existed in the table.

The result of this was that the content of every foreign key column was an
arbitrary, and meaningless, number.  In essence it meant that foreign key column
values were no longer informtion-bearing in isolation from the actual FK table.

As an extreme example, the FK column "gender" would have been populated with,
say, "11" and "32" instead of "M" and "F".

One of the great advantages of relational systems is the concept of tables being
"Content Addressable".  Using contrived numeric identifiers for tables seems to
me to be an attempt to make relational tables look more like segments in a
hierarchical database.

I for one would much prefer to see meaningful values in foreign key columns,
even if this implies somewhat less efficiency from a data processing viewpoint.

January 04, 2003 Nick -- Thanks for the question regarding
      "Selection of JDBC client side driver - Thin Vs OCI", version 8.1.6

      You Asked
Hi Tom,

As always, thanks for your help. I have a regarding which JDBC
client side driver to use: Thin or OCI.

Our web application uses Servlets and JSPs (we do not use
applets), which are housed in a Tomcat engine. We presently use
the thin driver to connect to an Oracle8i (8.1.6) database.

Do you think there would be any benefit of moving to the JDBC
OCI drivers.

Also, what would require to change if I do that - For eg. will I
have to include an Oracle client and relevant drivers on Tomcat.
What other impact can it have wrt to deployment, performance,
maintenance etc.

Thanks in advance,

Nic

      and we said...
When deploying to a client machine -- I prefer to use the THIN
drivers.

When deploying on a server machine, I prefer to use the OCI
drivers.

The reason is mostly performance.  The OCI drivers, in my
experience, tend to be faster, especially with some operations
like LOBS and such.

It will require the installation of the Oracle8 client on the
machine with the tomcat engine.  It should not increase
maintenance issues at all (instead of upgrading the thin jdbc
drivers you would upgrade the Oracle8 client software which has
the jdbc drivers in it).

What I would do first however is have both available so I can
switch BACK to the thin drivers at the drop of a hat.  They are
different pieces of code and you may find some behaviour between
the two to be different.
Review & Followup

      Rating: 4
tomcat / Oracle
      OCI Driver  September 30, 2002
Reviewer:
      A reader
Hi Tom,

  we have the same implemantation
orcle - (oracle client -- tomcat)

  but rigth now we are facing the problem of the connection pooling.
  ok. we create a connection pool and from that we use the
connection as an when needed. Now,
If the database session is abruptly killed
 (
  1.) due to havy network traffic
  2.) some other reason.
)
  the oracle client OCI does not genereate any exception.
so we can not check if the connection is lost/dropped
 and disply the appropriate message
and Noclassfound error is displayed on the resulting JSP page.

  we are on linux 73 platform running oracle 8.1.7.4
at back-end and 8.1.7.4 client with tomcat4

 Do you have any suggestion for this.

 secondally,
   we have checked that with the Thin JDBC driver,
this issue does not exist.
 But, then we have to query the database each time.
 our team is using
 ==>  select sysdate from dual
 to check if the connection exists

  I think this is very expansive.
do you have any suggestion for this.

=> do you think if we use
  select '1' from dual
 might be cheaper stmt.(But still it's a call)

Thanks,
Vivek

          Followup:

check out devtrends.oracle.com

select sysdate from dual vs select '1' from dual.  select whatever you like, you
are splitting very extremely thin fine hairs at that point.

      Rating: 5
oci vs. thin
      counter argument article  December 31, 2002

Reviewer:  john  from USA
tom,

please briefly skim this link which gives an excerpt for jdbc thin vs oci for
8.1.7. it almost says, always use thin, it performs better.  use statements,
instead of preparedstatements because they perform better without discussing the
impact on the database, only in terms of a single apps metrics.  i wouldn't have
given it a second look if it wasn't an o'reilly jdbc book.  Is this still
accurate for 9i oci drivers?  or is this info just extremely short sighted with
regards to overall db impact?

http://www.onjava.com/lpt/a//onjava/excerpt/oraclejdbc_19/index.html

          Followup:

It has never been true as far as I know.

OCI driver has always offered more features.

OCI driver has always been for me more performant -- especially with blob/clob
operations.

this paragraph:

...
There's a popular belief that using a PreparedStatement object is faster than
using a Statement object. After all, a prepared statement has to verify its
metadata against the database only once, while a statement has to do it every
time. So how could it be any other way? Well, the truth of the matter is that it
takes about 65 iterations of a prepared statement before its total time for
execution catches up with a statement. This has performance implications for
your application, and exploring these issues is what this section is all about.

....

is totally wrong, the guy was not looking at the database obviously.  He was
looking at the execution of a single statement in isolation.  I hope he never
develops on my database -- since his stupid Statement will never use bind
variables!

This:

....
When it comes to which SQL statement object performs better under typical use, a
Statement or a PreparedStatement, the truth is that the Statement object yields
the best performance. When you consider how SQL statements are typically used in
an application--1 or 2 here, maybe 10-20 (rarely more) per transaction--you
realize that a Statement object will perform them in less time than a
PreparedStatement object. In the next two sections, we'll look at this
performance issue with respect to both the OCI driver and the Thin driver.
.......

is truly frightening.  Downright foolish in fact.  That would be the STUPID,
foolish way to do it (i have harsher words but, this is a public forum and all).

That article just annoys me -- all rubbish.

      Rating: 1
Your Attention
      please Mr. Kyte  January 01, 2003
Reviewer:
      Mohammed Khan
package perftest;
import java.sql.*;
import oracle.jdbc.OracleDriver;
import java.util.Date;

public class PerfTest
{

  static void doStatement (Connection con, int count) throws Exception
  {
    long start = new Date().getTime();
    p ("doing statement start time : " + start);
    Statement st = con.createStatement();

    for (int i = 0; i < count; i++)
    {
      st.executeUpdate("insert into testxxxperf " +
                       "(id, code, descr, insert_user, insert_date)" +
                       " values (" + i  +
                                  ", 'ST - code" + i + "'" +
                                  ", 'St - descr" + i + "'" +
                                  ", user, sysdate ) ");
    }
    long end = new Date().getTime();
    p("end time : " + end);
    p ("done statment " + count + " times in " + (end - start) + " milli
seconds");
  }

  static void doPreparedStatement (Connection con, int count) throws Exception
  {
    long start = new Date().getTime();
    p ("doing prepared statement start time : " + start);
    PreparedStatement ps = con.prepareStatement("insert into testxxxperf " +
                       "(id, code, descr, insert_user, insert_date)"
                       + " values (?,?,?, user, sysdate)");

    for (int i = 0; i < count; i++)
    {
      ps.setInt(1,i);
      ps.setString(2,"PS - code" + i);
      ps.setString(3,"PS - desc" + i);
      ps.executeUpdate();
    }
    long end = new Date().getTime();
    p("end time : " + end);
    p ("done prepared statment " + count + " times in " + (end - start) + "
milli seconds");

  }

  public static void main (String arr[]) throws Exception
  {
    /*
     * Note : My DB is 9i Rel2, and classes12.jar which comes with this REL.
     *        It is (classes12.jar) also available in JDEVeloper 903

     *        I am flushing the shared pool after each run
     *        and also truncating the table testxxxperf
     *
     *
     */
    //this statement is needed for every run
    Connection con = null;
    DriverManager.registerDriver(new oracle.jdbc.OracleDriver());

    //using thin driver
    /* The Statement test with count of 100 gave me  :

       doing statement start time : 1041410728351
       end time : 1041410728945
       done statment 100 times in 594 milli seconds

      Now with 1000 inserts

        doing statement start time : 1041411410918
        end time : 1041411413699
        done statment 1000 times in 2781 milli seconds

      And Finally with 1 Insert
        doing statement start time : 1041413168367
        end time : 1041413168492
        done statment 1 times in 125 milli seconds

        These results are similar to what the author (in the article) has shown.

    *
     *
    */
      con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl",
"demo", "demo");
      con.setAutoCommit(false);
      //doStatement (con, 100);
      //doStatement (con, 1000);
      //doStatement (con, 1);
      /* doing prepared statement
       *
        doing prepared statement start time : 1041411007525
        end time : 1041411008134
        done prepared statment 100 times in 609 milli seconds
       *
       * I dont see much difference between the two with 100 inserts.
       *
          doing prepared statement start time : 1041411195667
          end time : 1041411197307
          done prepared statment 1000 times in 1640 milli seconds

        With One Insert
          doing prepared statement start time : 1041413303289
          end time : 1041413303461
          done prepared statment 1 times in 172 milli seconds

       *
       *
       */
       //doPreparedStatement(con, 100);
       //doPreparedStatement(con, 1000);
        doPreparedStatement(con, 1);
       con.commit();
       con.close();
      //con = DriverManager.getConnection ("jdbc:oracle:oci:@ORCL","demo",
"demo");
      // dont have time to test with OCI Driver

  }

  static void p (String s)
  {
    System.out.println (s);
  }
}

-----------------------------------------------------------

What does the above implies ?

This program is executed against oracle DB 9i Rel 2.

For a moment keep aside shared pool, hard parse and soft parse and talk about
PreparedStatement and Statement as they are the only way to execute statements
from java.

If you have more than 1000 inserts for every 2 seconds then use
PreparedStatement or else using statement will perform better.

          Followup:

warning: totally harsh rebuke coming.

what does this imply?  It implies you have learned nothing from this site.  It
implies you haven't read anything about scaling up or testing things to scale.
It implies you cannot see the forest for the trees.

You ask me to "keep aside for a moment" exactly that which you must be looking
at -- the most important stuff!

Tell me -- while you may not execute the same statement 1,000 times your program
-- what are the ODDS that your statement is executed 1,000 times ACROSS ALL
PROCESSES???

Tell me -- what happens when two users do this at the same time?  Do you know
what a latch is?  Do you understand the ramifications of flooding the shared
pool with literal SQL?   Do you???

Time after time after time after time I go into a site -- "database slow" they
say.  Well heck, it is falling over sideways PARSING STATEMENTS -- "what
happened with you tested with 100 users" I ask?  "test with more then one user?
why bother - it just scales up all by itself right?"

I see you say you didn't have the time to test with the OCI driver (but you did
have time to write this -- hmmm -- given using the oci driver would be chaning
one line?)  Anyway -- you need to TAKE the time to test things properly.

Sure, if I could say "in a dust free lab, on a pure frictionless surface -- do
this test (that is your single user mode looking at elapsed times and nothing
else, nothing relevant to seeing how this will really work)"  I might come
to the same mistaken, false, wrong conclusion you did.

I find your test to be flawed as well -- not that I even want to validate its
premise but -- a java programmer typically codes:

   create statment
   execute statment
   close statement

not
   create statment once
   execute over and over

So, moving the statment creation inside your loop as would be more normal -- the
break even comes at under 20 for me.....

But that aside, because that isn't what is relevant here tell me what
happens when you have N users (n>1) SIMULTANEOUSLY doing a statment.execute of a
unqiue statement?  Describe to me in detail what happens in the database.  then
and only then will you see why what you've said is a really truly baaaadddd
idea.

This is exactly why I think the best java programs never have the words

o select
o insert
o update
o delete
o merge

in them -- only BEGIN ... END;  calls to stored procedures (preferablly with an
OUT parameter -- FORCING you to use a CallableStatement even!  Making it so that
statements don't even work!)

Touched a sort spot you did -- I cannot believe how many times and in how many
ways I have to say the same thing over and over and over again.  Bind variables
-- use them or LOSE.

      Rating: 5
You know, this
      bothered me SO much  January 02, 2003
Reviewer:
      Tom Kyte  from Reston VA
This bothered me so much -- so so much, that I just had to followup again.  I
took the above so called "test" and modified it to meet what I believe would be
a true test.  I made it multi-user, just to show -- just to prove for once and
for all that using statements is a bad bad idea.

Here is my modified java code.  Pretty much the same but I log the timings to a
database table so we can use SQL to analyze the results and parameterized it so
we can use it over and over without recompiling each time:

mport java.sql.*;
import oracle.jdbc.OracleDriver;
import java.util.Date;

public class perftest
{

  static PreparedStatement saveTimesPs;
  static void saveTimes( Connection con,
                         String which,
                         long elap ) throws Exception
  {
    if ( saveTimesPs == null )
        saveTimesPs = con.prepareStatement
                      ("insert into timings " +
                       "( which, elap ) values "+
                       "( ?, ? )" );

    saveTimesPs.setString(1,which);
    saveTimesPs.setLong(2,elap);
    saveTimesPs.executeUpdate();
  }

  static void doStatement (Connection con,
                           int count) throws Exception
  {
    long start = new Date().getTime();
    Statement st = con.createStatement();
    for (int i = 0; i < count; i++)
    {
      st.executeUpdate
      ("insert into testxxxperf " +
       "(id, code, descr, insert_user, insert_date)" +
       " values (" + i  +
       ", 'ST - code" + i + "'" +
       ", 'St - descr" + i + "'" +
       ", user, sysdate ) ");
    }
    st.close();
    con.commit();
    long end = new Date().getTime();
    saveTimes( con, "STMT", end-start );
  }

  static void doPreparedStatement (Connection con,
                                int count) throws Exception
  {
    long start = new Date().getTime();
    PreparedStatement ps =
       con.prepareStatement("insert into testxxxperf " +
             "(id, code, descr, insert_user, insert_date)"
           + " values (?,?,?, user, sysdate)");

    for (int i = 0; i < count; i++)
    {
      ps.setInt(1,i);
      ps.setString(2,"PS - code" + i);
      ps.setString(3,"PS - desc" + i);
      ps.executeUpdate();
    }
    con.commit();
    long end = new Date().getTime();
    saveTimes( con, "PSTMT", end-start );
  }

  public static void main (String arr[]) throws Exception
  {
    Connection con = null;
    DriverManager.registerDriver
                        (new oracle.jdbc.OracleDriver());

    con = DriverManager.getConnection
          ("jdbc:oracle:oci8:@ora920.us.oracle.com",
          // ("jdbc:oracle:thin:@aria-dev:1521:ora920",
          "scott", "tiger");

    con.setAutoCommit(false);

    Integer iters = new Integer(arr[0]);

    Statement st = con.createStatement();
    System.out.println( "waiting for DO_STMT alert" );
    st.execute
    ( "declare l_msg varchar2(4000); " +
    "l_status number; begin " +
    "dbms_alert.register( 'DO_STMT' ); " +
    "dbms_alert.waitone( 'DO_STMT', l_msg, l_status ); " +
    "dbms_alert.remove( 'DO_STMT' ); end;" );

    doStatement (con, iters.intValue() );

    System.out.println( "waiting for DO_PSTMT alert" );
    st.execute
    ( "declare l_msg varchar2(4000); " +
    "l_status number; begin " +
    "dbms_alert.register( 'DO_PSTMT' ); " +
    "dbms_alert.waitone( 'DO_PSTMT', l_msg, l_status ); " +
    "dbms_alert.remove( 'DO_PSTMT' ); end;" );
    doPreparedStatement(con, iters.intValue() );

    System.out.println( "all done..." );

    con.commit();
    con.close();
  }
}

Now, the test driver I used is this csh script.  The goal here is have N users
doing M iterations concurrently -- you know, like you might have doing in the
real world for example.  So, I

a) use sqlplus to flush the shared pool.  drop and create the tables.  but also
warm up the shared pools dictionary cache by parsing and executing some inserts
against the tables in question.

b) start up $1 copies of the java program.  They all block on dbms_alert,
waiting to be told to "go".

c) tell them to go

d) wait for them to ask to do the pstmt

e) tell them to do that

f) print out the results

#!/bin/csh -f

set users = $1
set iters = $2

sqlplus scott/tiger <<EOF
    alter system flush shared_pool;

    drop table timings;
    create table timings ( which varchar2(10), elap number );
    insert into timings values ( 'x', 0 );
    commit;
    delete from timings;

    drop table testxxxperf;
    create table testxxxperf
    ( id number, code varchar2(25), descr varchar2(25),
      insert_user varchar2(30), insert_date date );
    insert into testxxxperf values(0,'x','x','x',sysdate);
    commit;
    delete from testxxxperf;

    exit
EOF

while ( $users > 0 )
   java perftest $iters &
   @ users = $users - 1
end

echo when all are ready, hit enter...
read

sqlplus scott/tiger <<EOF
    exec dbms_alert.signal( 'DO_STMT', '' );
    commit;
    exit
EOF

echo when all are ready for do_pstmt, hit enter...
read

sqlplus scott/tiger <<EOF
    exec dbms_alert.signal( 'DO_PSTMT', '' );
    commit;
    exit
EOF

echo when all are done, hit enter
read

sqlplus scott/tiger <<EOF
    select which, count(*), avg(elap), min(elap), max(elap) from timings group
by which;
    exit
EOF

amazingly, when I run

$ perftest 10 1

WHICH        COUNT(*)  AVG(ELAP)  MIN(ELAP)  MAX(ELAP)
---------- ---------- ---------- ---------- ----------
PSTMT              10       41.1         22         75
STMT               10       71.7         26        141

10 users doing a single statement.  or how about 10 users doing 10 statements:

$ perftest 10 10

WHICH        COUNT(*)  AVG(ELAP)  MIN(ELAP)  MAX(ELAP)
---------- ---------- ---------- ---------- ----------
PSTMT              10       83.1         37        155
STMT               10      144.7         94        272

or even 1 user doing 1 statement:

WHICH        COUNT(*)  AVG(ELAP)  MIN(ELAP)  MAX(ELAP)
---------- ---------- ---------- ---------- ----------
PSTMT               1         26         26         26
STMT                1         28         28         28

now, that was OCI thick, lets try thin:

$ perfstat 10 1

WHICH        COUNT(*)  AVG(ELAP)  MIN(ELAP)  MAX(ELAP)
---------- ---------- ---------- ---------- ----------
PSTMT              10         49         27         81
STMT               10      102.7         39        154

(and so on...)

That prepared statement is looking pretty good now..

          Followup:

And you know what -- I was wondering why MY test ran the PSTMT in the same time
as the STMT -- yet, yours did not.

I run your test:

done statment 1 times in 26 milli seconds
done prepared statment 1 times in 69 milli seconds

and it is very very very consistent.  Now, I run mine (without the dbms_alert
waits -- just let it run) and I get:

STMT (14)
PSTMT (14)

very very consistently.  So, questions:

a) why does my STMT run faster than yours? we run the same code
b) why does my PSTMT run faster than yours? we run the same code.

Hmmm -- well, I have some statment.executes BEFORE I call yours....
My modified statement routine calls Prepared statement stuff BEFORE I call
doPrepare

Hence -- here is what is happening

my code warms up the statement class, gets it all loaded.  Your doStatement
benefitted from that.

my code then warmed up the prepared statement class, gets it all loaded.  Your
doPrepared benefitted from that.

After the classes are loaded up -- they are the *same*.  Well, no let me
rephrase that.  They have the same elapsed time in a dust free lab, running
single user mode.

The prepared statement is superior in every way....

Here is the modified benchmark that throws out the initial overhead of calling
statement/preparedstatement (which in the grand scheme of things is just NOISE).
Tell me which is "better" now?

Here is the code (note the offset I added was to make all of the rows inserted
into the table UNIQUE -- because we used i in the statement -- i+offset will
keep them unique upto 1000 in my case):

import java.sql.*;
import oracle.jdbc.OracleDriver;
import java.util.Date;

public class perftest
{
  static void doStatement (Connection con,
                           int count,
                           int offset ) throws Exception
  {
    long start = new Date().getTime();
    Statement st = con.createStatement();
    for (int i = offset; i < count+offset; i++)
    {
      st.executeUpdate
      ("insert into testxxxperf " +
       "(id, code, descr, insert_user, insert_date)" +
       " values (" + i  +
       ", 'ST - code" + i + "'" +
       ", 'St - descr" + i + "'" +
       ", user, sysdate ) ");
    }
    st.close();
    con.commit();
    long end = new Date().getTime();
    System.out.println( "STMT" + " (" + (end-start) + ") times="+count );
  }

  static void doPreparedStatement (Connection con,
                                   int count,
                                   int offset ) throws Exception
  {
    long start = new Date().getTime();

    PreparedStatement ps = con.prepareStatement("insert into testxxxperf " +
                       "(id, code, descr, insert_user, insert_date)"
                       + " values (?,?,?, user, sysdate)");

    for (int i = offset; i < count+offset; i++)
    {
      ps.setInt(1,i);
      ps.setString(2,"PS - code" + i);
      ps.setString(3,"PS - desc" + i);
      ps.executeUpdate();
    }
    con.commit();
    long end = new Date().getTime();
    System.out.println( "PSTMT" + " (" + (end-start) + ") times="+count );
  }

  public static void main (String arr[]) throws Exception
  {
    Connection con = null;
    DriverManager.registerDriver(new oracle.jdbc.OracleDriver());

    con = DriverManager.getConnection
          //("jdbc:oracle:oci8:@ora920.us.oracle.com",
          ("jdbc:oracle:thin:@aria-dev:1521:ora920",
          "scott", "tiger");

    con.setAutoCommit(false);

    Integer iters = new Integer(arr[0]);

    System.out.println( "IGNORE THESE TIMES" );
    doStatement (con, iters.intValue(), 0 );
    doPreparedStatement(con, iters.intValue(), 0 );
    System.out.println( "IGNORE THESE TIMES" );

    System.out.println( "PAY ATTENTION TO THESE TIMES" );
    doStatement (con, iters.intValue(), 1000 );
    doPreparedStatement(con, iters.intValue(), 1000 );
    System.out.println( "PAY ATTENTION TO THESE TIMES" );

    con.commit();
    con.close();
  }
}

here is the script:

#!/bin/csh -f

sqlplus scott/tiger <<EOF
    alter system flush shared_pool;

    drop table timings;
    create table timings ( which varchar2(10), elap number );
    insert into timings values ( 'x', 0 );
    commit;
    delete from timings;

    drop table testxxxperf;
    create table testxxxperf
    ( id number, code varchar2(25), descr varchar2(25),
      insert_user varchar2(30), insert_date date );
    insert into testxxxperf values(0,'x','x','x',sysdate);
    commit;
    delete from testxxxperf;

    exit
EOF

java perftest $1

and here is the output:

$ perftest 1
IGNORE THESE TIMES
STMT (13) times=1
PSTMT (100) times=1
IGNORE THESE TIMES
PAY ATTENTION TO THESE TIMES
STMT (8) times=1
PSTMT (8) times=1
PAY ATTENTION TO THESE TIMES

hmmm, stmt = pstmt for a single execution, once the classes are loaded (and
how many times do you load a class?)

$ perftest 10
IGNORE THESE TIMES
STMT (37) times=10
PSTMT (108) times=10
IGNORE THESE TIMES
PAY ATTENTION TO THESE TIMES
STMT (33) times=10
PSTMT (25) times=10
PAY ATTENTION TO THESE TIMES

Hmmm, 10 statements PSTMT faster

$ perftest 100

IGNORE THESE TIMES
STMT (338) times=100
PSTMT (278) times=100
IGNORE THESE TIMES
PAY ATTENTION TO THESE TIMES
STMT (328) times=100
PSTMT (179) times=100
PAY ATTENTION TO THESE TIMES

much faster...

$ perftest 1000

IGNORE THESE TIMES
STMT (3078) times=1000
PSTMT (1724) times=1000
IGNORE THESE TIMES
PAY ATTENTION TO THESE TIMES
STMT (3031) times=1000
PSTMT (1794) times=1000
PAY ATTENTION TO THESE TIMES

truly fast

But remember -- its not just about elapsed time, its all about latches!!!

If you don't use prepared statements -- you lose.

      Rating:
      5
Splendid  January 02, 2003
Reviewer:
      Jer Smith  from Salt Lake City, UT
It's great when you provide simple tests to debunk misinformation.

I know you've got these scattered all over the place, in presentations and
whatnot, but for the basic ones:
The implicit cursor myth
The prepared statement myth
The hit-ration myth
The separate index and table myth.

It would be good to consolidate the "debunking" into a single
site.

      Rating: 5
Also a
      manageability difference  January 02, 2003

Reviewer:  Jake Spencer  from Charlotte, NC

It's probably also worth mentioning that because the thin driver doesn't use
Net8, you can't do any of the things you would normally be able to do by setting
a sqlnet.ora parameter - Oracle Names, sqlnet tracing, etc.

For example, we use Oracle Names in our environment to make database locations
transparent to our clients, but now we have clients who are using the thin
driver.  This means they can't use the Names server to connect, which in turn
makes manageability more difficult.

      Rating:
      3
  January 04, 2003
Reviewer:  Mohammed
      Khan
Help Mr. Kyte !!!!!!!!!

Sorry to bother you so much.

I've modified the unix script to work with windows.

:: r.bat
@ECHO OFF
:: %1 ACCEPTS USERS
:: %2 ACCEPTS ITERATIONS

sqlplus scott/tiger @G:\perftest\droptabs

FOR /L %%U IN (%1,-1,1) do START JAVA perftest %2

echo when all are ready for do_pstmt, hit enter...
pause

:: running prepared statements first shows drastic difference your and mine
results
sqlplus scott/tiger @G:\perftest\signal_pstmt

echo when all are ready, hit enter...
pause

sqlplus scott/tiger @G:\perftest\signal_stmt

echo when all are done, hit enter
pause

sqlplus scott/tiger @G:\perftest\rep

----------------- end of file r.bat

File droptabs.sql

alter system flush shared_pool;

    drop table timings;
    create table timings ( which varchar2(10), elap number );
    insert into timings values ( 'x', 0 );
    commit;
    delete from timings;

    drop table testxxxperf;
    create table testxxxperf
    ( id number, code varchar2(25), descr varchar2(25),
      insert_user varchar2(30), insert_date date );
    insert into testxxxperf values(0,'x','x','x',sysdate);
    commit;
    delete from testxxxperf;

    exit

------------------------ end droptabs.sql

File signal_stmt.sql

    exec dbms_alert.signal( 'DO_STMT', '' );
    commit;
    exit

------------------------ end signal_stmt.sql

File signal_pstmt.sql

    exec dbms_alert.signal( 'DO_PSTMT', '' );
    commit;
    exit

------------------------ end signal_pstmt.sql

File rep.sql

select which, count(*), avg(elap), min(elap), max(elap) from timings group
by which
/

------------------------ end rep.sql

Despite severel runs I am getting the following results.
I've just modified the perftest.java to first register 'DO_PSTMT' alert
and the tables are seemed to be turned, or my machine is insane.

Here is the source of modified perftest.java

import java.sql.*;
import oracle.jdbc.OracleDriver;
import java.util.Date;

public class perftest
{

  static PreparedStatement saveTimesPs;
  static void saveTimes( Connection con,
                         String which,
                         long elap ) throws Exception
  {
    if ( saveTimesPs == null )
        saveTimesPs = con.prepareStatement
                      ("insert into timings " +
                       "( which, elap ) values "+
                       "( ?, ? )" );

    saveTimesPs.setString(1,which);
    saveTimesPs.setLong(2,elap);
    saveTimesPs.executeUpdate();
  }

  static void doStatement (Connection con,
                           int count) throws Exception
  {
    long start = new Date().getTime();
    Statement st = con.createStatement();
    for (int i = 0; i < count; i++)
    {
      st.executeUpdate
      ("insert into testxxxperf " +
       "(id, code, descr, insert_user, insert_date)" +
       " values (" + i  +
       ", 'ST - code" + i + "'" +
       ", 'St - descr" + i + "'" +
       ", user, sysdate ) ");
    }
    st.close();
    con.commit();
    long end = new Date().getTime();
    saveTimes( con, "STMT", end-start );
  }

  static void doPreparedStatement (Connection con,
                                int count) throws Exception
  {
    long start = new Date().getTime();
    PreparedStatement ps =
       con.prepareStatement("insert into testxxxperf " +
             "(id, code, descr, insert_user, insert_date)"
           + " values (?,?,?, user, sysdate)");

    for (int i = 0; i < count; i++)
    {
      ps.setInt(1,i);
      ps.setString(2,"PS - code" + i);
      ps.setString(3,"PS - desc" + i);
      ps.executeUpdate();
    }
    con.commit();
    long end = new Date().getTime();
    saveTimes( con, "PSTMT", end-start );
  }

  public static void main (String arr[]) throws Exception
  {
    Connection con = null;
    DriverManager.registerDriver
                        (new oracle.jdbc.OracleDriver());

    con = DriverManager.getConnection
          //("jdbc:oracle:oci:@ORCL", ---- again dont have time to test with
OCI, I wonder how

//you manage your time, work, asktom........
           ("jdbc:oracle:thin:@localhost:1521:ORCL",
          "scott", "tiger");

    con.setAutoCommit(false);

    Integer iters = new Integer(arr[0]);

    Statement st = con.createStatement();

    System.out.println( "waiting for DO_PSTMT alert" );
    st.execute
    ( "declare l_msg varchar2(4000); " +
    "l_status number; begin " +
    "dbms_alert.register( 'DO_PSTMT' ); " +
    "dbms_alert.waitone( 'DO_PSTMT', l_msg, l_status ); " +
    "dbms_alert.remove( 'DO_PSTMT' ); end;" );
    doPreparedStatement(con, iters.intValue() );

    System.out.println( "waiting for DO_STMT alert" );
    st.execute
    ( "declare l_msg varchar2(4000); " +
    "l_status number; begin " +
    "dbms_alert.register( 'DO_STMT' ); " +
    "dbms_alert.waitone( 'DO_STMT', l_msg, l_status ); " +
    "dbms_alert.remove( 'DO_STMT' ); end;" );

    doStatement (con, iters.intValue() );

    System.out.println( "all done..." );

    con.commit();
    con.close();
  }
}

Here goes the results

G:\perftest>r 10 1
when all are ready for do_pstmt, hit enter...
Press any key to continue . . .
when all are ready, hit enter...
Press any key to continue . . .
when all are done, hit enter
Press any key to continue . . .

SQL*Plus: Release 8.0.6.0.0 - Production on Sat Jan 4 13:59:35 2003

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

WHICH       COUNT(*) AVG(ELAP) MIN(ELAP) MAX(ELAP)
---------- --------- --------- --------- ---------
PSTMT             10       997       578      1313
STMT              10      65.7        62        79

SQL> exit

-------with 10 * 10 combination

G:\perftest>r 10 10
when all are ready for do_pstmt, hit enter...
Press any key to continue . . .
when all are ready, hit enter...
Press any key to continue . . .
when all are done, hit enter
Press any key to continue . . .

SQL*Plus: Release 8.0.6.0.0 - Production on Sat Jan 4 14:06:44 2003

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

WHICH       COUNT(*) AVG(ELAP) MIN(ELAP) MAX(ELAP)
---------- --------- --------- --------- ---------
PSTMT             10    1140.7       860      1547
STMT              10     118.5        31       203

SQL> exit

---------- now with 60 inserts

G:\perftest>r 10 60
when all are ready for do_pstmt, hit enter...
Press any key to continue . . .
when all are ready, hit enter...
Press any key to continue . . .
when all are done, hit enter
Press any key to continue . . .

SQL*Plus: Release 8.0.6.0.0 - Production on Sat Jan 4 14:09:44 2003

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

WHICH       COUNT(*) AVG(ELAP) MIN(ELAP) MAX(ELAP)
---------- --------- --------- --------- ---------
PSTMT             10    1890.7      1172      2656
STMT              10     642.3       234      1156

SQL> exit

-------- finally with 10 * 1000 inserts

G:\perftest>r 10 1000
when all are ready for do_pstmt, hit enter...
Press any key to continue . . .
when all are ready, hit enter...
Press any key to continue . . .
when all are done, hit enter
Press any key to continue . . .

SQL*Plus: Release 8.0.6.0.0 - Production on Sat Jan 4 14:12:31 2003

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

WHICH       COUNT(*) AVG(ELAP) MIN(ELAP) MAX(ELAP)
---------- --------- --------- --------- ---------
PSTMT             10    9201.5      3422     13547
STMT              10   10952.8      5485     15093

SQL> exit

--------------------------------------------------------------------------------

Mr. Kyte I am a big fan of using bind variables and PreparedStatements, but
after seeing
the article and doing the tests, I am forced to think the other way.

I own a book named "Expert One On One Oracle"
"Chapter 3 Locking and concurrency" The author goes into the issues about
locking and concurency

in great details, and I understood that chapter.

The following is the single user test environment, but it doe'nt matter as one
should scale with

multiple users not with single user.

Microsoft Windows 2000 [Version 5.00.2195]
(C) Copyright 1985-2000 Microsoft Corp.

G:\JDEV903\jdev\mywork\JDBCPerfTest\TomTest\classes>java perftest.perftest 1
IGNORE THESE TIMES
STMT (16) times=1
PSTMT (109) times=1
IGNORE THESE TIMES
PAY ATTENTION TO THESE TIMES
STMT (0) times=1
PSTMT (16) times=1
PAY ATTENTION TO THESE TIMES

G:\JDEV903\jdev\mywork\JDBCPerfTest\TomTest\classes>java perftest.perftest 1
IGNORE THESE TIMES
STMT (0) times=1
PSTMT (109) times=1
IGNORE THESE TIMES
PAY ATTENTION TO THESE TIMES
STMT (0) times=1
PSTMT (16) times=1
PAY ATTENTION TO THESE TIMES

After executing two times also same results.

G:\JDEV903\jdev\mywork\JDBCPerfTest\TomTest\classes>java perftest.perftest 2
IGNORE THESE TIMES
STMT (0) times=2
PSTMT (110) times=2
IGNORE THESE TIMES
PAY ATTENTION TO THESE TIMES
STMT (15) times=2
PSTMT (16) times=2
PAY ATTENTION TO THESE TIMES

G:\JDEV903\jdev\mywork\JDBCPerfTest\TomTest\classes>java perftest.perftest 3
IGNORE THESE TIMES
STMT (0) times=3
PSTMT (125) times=3
IGNORE THESE TIMES
PAY ATTENTION TO THESE TIMES
STMT (0) times=3
PSTMT (0) times=3
PAY ATTENTION TO THESE TIMES

after 10 iters also

G:\JDEV903\jdev\mywork\JDBCPerfTest\TomTest\classes>java perftest.perftest 10
IGNORE THESE TIMES
STMT (31) times=10
PSTMT (125) times=10
IGNORE THESE TIMES
PAY ATTENTION TO THESE TIMES
STMT (31) times=10
PSTMT (32) times=10
PAY ATTENTION TO THESE TIMES

G:\JDEV903\jdev\mywork\JDBCPerfTest\TomTest\classes>java perftest.perftest 1000
IGNORE THESE TIMES
STMT (2234) times=1000
PSTMT (1172) times=1000
IGNORE THESE TIMES
PAY ATTENTION TO THESE TIMES
STMT (2172) times=1000
PSTMT (1000) times=1000
PAY ATTENTION TO THESE TIMES

G:\JDEV903\jdev\mywork\JDBCPerfTest\TomTest\classes>java perftest.perftest 60
IGNORE THESE TIMES
STMT (172) times=60
PSTMT (188) times=60
IGNORE THESE TIMES
PAY ATTENTION TO THESE TIMES
STMT (140) times=60 ---------note this
PSTMT (641) times=60
PAY ATTENTION TO THESE TIMES

G:\JDEV903\jdev\mywork\JDBCPerfTest\TomTest\classes>

          Followup:

you are MISSING THE ENTIRE POINT.  You you missing the point -- I just totally
give up.  (it is ALL about what happens with more then one user, what happens
with one user here - as i keep trying to say - it abosolutely meaningless.)

Do what you want, go ahead, -- but you know what -- stay away -- far far far
away from my dataases PLEASE.  Your databases are the ones that have latch free
waits like crazy, ora-4031's, people sitting there waiting to flush the shared
pool, enormous performance spikes.  Go for it -- if that is what you want.  Just
go for it.  Remember, its all about that 0.01 second you are trying to save in
the java client, seems worth it doesn't it?

(don't you see something wrong with your testing -- look at that last one --
perhaps you need to look inside your database --

GNORE THESE TIMES
STMT (172) times=60
PSTMT (188) times=60   <<<<==========
IGNORE THESE TIMES
PAY ATTENTION TO THESE TIMES
STMT (140) times=60 ---------note this
PSTMT (641) times=60    <<<<=========
PAY ATTENTION TO THESE TIMES

or your JVM.  turn off garbage collection for example -- did you stop to ask
"hmm, why 188 and then 641???  wonder, just wonder what is going on there?  )

And you are so wrong, so so wrong with your statement that a single user test is
even valid.  You are so missing the point -- so missing the point.

funny thing is -- how do you explain these:

PAY ATTENTION TO THESE TIMES
STMT (15) times=2
PSTMT (16) times=2
PAY ATTENTION TO THESE TIMES

PAY ATTENTION TO THESE TIMES
STMT (0) times=3
PSTMT (0) times=3
PAY ATTENTION TO THESE TIMES

Hmm, apparently -- just apparently -- on your machine, you should run 3
statements in all programs cause that is faster then 2???

Do you think there might -- just might -- be some bursty performance issues here
on your machine?  That you might -- just might -- have to run this test like 50
times and average the numbers out?

Ok, let me do that for you.  here is a modified yet again perftest.java:

import java.sql.*;
import oracle.jdbc.OracleDriver;
import java.util.Date;

public class perftest
{
  static PreparedStatement saveTimesPs;
  static void saveTimes( Connection con,
                         String which,
                         long elap ) throws Exception
  {
    System.out.println( which + " (" + elap + ")" );
    if ( saveTimesPs == null )
        saveTimesPs = con.prepareStatement
                      ("insert into timings " +
                       "( which, elap ) values "+
                       "( ?, ? )" );

    saveTimesPs.setString(1,which);
    saveTimesPs.setLong(2,elap);
    saveTimesPs.executeUpdate();
  }

  static void doStatement (Connection con,
                           int count,
                           int offset,
                           String which ) throws Exception
  {
    long start = new Date().getTime();
    Statement st = con.createStatement();
    for (int i = offset; i < count+offset; i++)
    {
      st.executeUpdate
      ("insert into testxxxperf " +
       "(id, code, descr, insert_user, insert_date)" +
       " values (" + i  +
       ", 'ST - code" + i + "'" +
       ", 'St - descr" + i + "'" +
       ", user, sysdate ) ");
    }
    st.close();
    con.commit();
    long end = new Date().getTime();
    System.out.println( "STMT" + " (" + (end-start) + ") times="+count );
    saveTimes( con, which, (end-start) );
  }

  static void doPreparedStatement (Connection con,
                                   int count,
                                   int offset,
                                   String which ) throws Exception
  {
    long start = new Date().getTime();

    PreparedStatement ps = con.prepareStatement("insert into testxxxperf " +
                       "(id, code, descr, insert_user, insert_date)"
                       + " values (?,?,?, user, sysdate)");

    for (int i = offset; i < count+offset; i++)
    {
      ps.setInt(1,i);
      ps.setString(2,"PS - code" + i);
      ps.setString(3,"PS - desc" + i);
      ps.executeUpdate();
    }
    con.commit();
    long end = new Date().getTime();
    System.out.println( "PSTMT" + " (" + (end-start) + ") times="+count );
    saveTimes( con, which, (end-start) );
  }

  public static void main (String arr[]) throws Exception
  {
    Connection con = null;
    DriverManager.registerDriver(new oracle.jdbc.OracleDriver());

    con = DriverManager.getConnection
          //("jdbc:oracle:oci8:@ora920.us.oracle.com",
          ("jdbc:oracle:thin:@aria-dev:1521:ora920",
          "scott", "tiger");

    con.setAutoCommit(false);

    Integer iters = new Integer(arr[0]);

    System.out.println( "IGNORE THESE TIMES" );
    doStatement (con, 1, 0, "I-STMT" );
    doPreparedStatement(con, 1, 0, "I-PSTMT" );
    System.out.println( "IGNORE THESE TIMES" );

    System.out.println( "PAY ATTENTION TO THESE TIMES" );
    doStatement (con, iters.intValue(), 1000, "STMT" );
    doPreparedStatement(con, iters.intValue(), 1000, "PSTMT" );
    System.out.println( "PAY ATTENTION TO THESE TIMES" );

    con.commit();
    con.close();
  }
}

and a perftest script:

#!/bin/csh -f

set ntimes = $1
set iters = $2

sqlplus scott/tiger<<EOF
    drop table timings;
    create table timings ( which varchar2(10), elap number );
    insert into timings values ( 'x', 0 );
    commit;
    delete from timings;
    exit
EOF

while( $ntimes > 0 )

sqlplus scott/tiger <<EOF
    alter system flush shared_pool;

    drop table testxxxperf;
    create table testxxxperf
    ( id number, code varchar2(25), descr varchar2(25),
      insert_user varchar2(30), insert_date date );
    insert into testxxxperf values(0,'x','x','x',sysdate);
    commit;
    delete from testxxxperf;

    exit
EOF

java perftest $iters

@ ntimes = $ntimes - 1

end

sqlplus scott/tiger<<EOF
    select which, count(*), avg(elap), min(elap), max(elap)
      from timings
     group by which;
    exit
EOF

$ perftest 10 1  <<<<=== run 10, single insert tests one after the other

WHICH        COUNT(*)  AVG(ELAP)  MIN(ELAP)  MAX(ELAP)
---------- ---------- ---------- ---------- ----------
I-PSTMT            10       13.2          9         17
I-STMT             10       15.6         11         20
PSTMT              10        8.3          6         15
STMT               10       13.6         13         15

and the thing about testing is consistency -- I want to make sure that if I run
this again and again -- the results are infinitely reproducible -- and they are:

WHICH        COUNT(*)  AVG(ELAP)  MIN(ELAP)  MAX(ELAP)
---------- ---------- ---------- ---------- ----------
I-PSTMT            10       11.1          8         16
I-STMT             10       16.9         12         22
PSTMT              10        7.4          6         15
STMT               10       14.3         13         22

WHICH        COUNT(*)  AVG(ELAP)  MIN(ELAP)  MAX(ELAP)
---------- ---------- ---------- ---------- ----------
I-PSTMT            10       15.2          9         25
I-STMT             10       17.3         11         28
PSTMT              10        6.7          6          7
STMT               10       16.2         13         23

WHICH        COUNT(*)  AVG(ELAP)  MIN(ELAP)  MAX(ELAP)
---------- ---------- ---------- ---------- ----------
I-PSTMT            10       21.4          9         93
I-STMT             10       17.6         12         26
PSTMT              10        8.8          6         16
STMT               10         16         13         29

it is very very consistent -- how about yours?  that was 9ir2 vs 9ir2, here is
817 vs 817:

WHICH        COUNT(*)  AVG(ELAP)  MIN(ELAP)  MAX(ELAP)
---------- ---------- ---------- ---------- ----------
I-PSTMT            10       13.1         12         16
I-STMT             10       17.1         13         27
PSTMT              10       12.4          6         16
STMT               10       13.1         13         14

WHICH        COUNT(*)  AVG(ELAP)  MIN(ELAP)  MAX(ELAP)
---------- ---------- ---------- ---------- ----------
I-PSTMT            10       16.6         10         26
I-STMT             10       17.7         12         31
PSTMT              10       10.7          6         22
STMT               10       13.3         13         15

WHICH        COUNT(*)  AVG(ELAP)  MIN(ELAP)  MAX(ELAP)
---------- ---------- ---------- ---------- ----------
I-PSTMT            10       17.1         12         30
I-STMT             10       18.2         13         41
PSTMT              10       14.9         10         24
STMT               10       13.1         12         14

WHICH        COUNT(*)  AVG(ELAP)  MIN(ELAP)  MAX(ELAP)
---------- ---------- ---------- ---------- ----------
I-PSTMT            10         13         10         25
I-STMT             10       15.1         12         18
PSTMT              10        8.5          6         14
STMT               10       15.1         12         25

I seem to be noticing some rather wide variations.... but the numbers are
rather close (benefitting pstmts mostly tho).

but the bottom line is again NOT ABOUT a single user.

I guess if you want to build single user systems -- go for it.  If you want to
build mulit-user systems that actually scale and work -- you better forget about
the existence of Statements in jdbc....

Run your test 100 times and average it out over the long haul.  See what happens
there.  If you see something quirky (like the two examples above) explore it --
explain it.

Just for grins, I did 100 iterations of a single statement against 817:

WHICH        COUNT(*)  AVG(ELAP)  MIN(ELAP)  MAX(ELAP)
---------- ---------- ---------- ---------- ----------
I-PSTMT           100      16.23          9         45
I-STMT            100      19.69         11        206
PSTMT             100      12.41          6         23
STMT              100      13.82         12         50

and remember my advice from

http://asktom.oracle.com/~tkyte/runstats.html
...
o How many resources each approach takes. This can be more meaningful then even
the wall clock timings. For example, if one approach is faster then the other
but it takes thousands of latches (locks), I might avoid it simply because it
will not scale as well.
....

single user tests -- elapsed times are virtually meaningless.

January 04, 2003 Steve -- Thanks for the question regarding "Row
      count info ", version 8.1.7

      You Asked
Hi Tom,

I have a quick question.
In tkprof's output file, we can get row count info associated with
each step of the execution plan.
How to get row count info with execution plan
in sql*plus if I set autotrace on? because I don't have permission to read trace
files on the server.

Thank you!

Steve

      and we said...
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7115831027951

You need the trace file -- that is the only place the STAT records containing
this information are.  Autotrace just does explain plans, that guesses.  Trace
files have STAT records and these are the real plans actually used with
rowcounts.

That link might be useful.
January
      05, 2003 Mohammed -- Thanks for the
      question regarding "SQLLDR loading Batch Files ", version 8i

      You Asked
Hi Tom :
        I have list of text file I want them to be loaded to Table . I want To
put these all text files in directory and by batch to be loaded  to the table
instead  of loading them one by one .I want INFILE to be parameter and from dos
run and Script or and exe file to read them all or any method to solve this .

load data
infile 'X'
replace
into table cdr_files
fields terminated by X'9'
(   RT     ,
   CTT     ,
 ....,
.... )

You know that Sometime I have about 20-30 files to be loaded in this Table and I
have one by One to load these text file to that table
.

Regards

      and we said...
sqlldr .... DATA=filename .....

overrides any infile, or just leave the infile statement out of the control file
and then you have to use the DATA=filename
Review &
      Followup

      Rating: 3
SQLLDR loading
      Batch Files   January 05, 2003
Reviewer:
      Mohammed Al-moayed  from Yemen
Ok thank u ,Could u give an Example of this I tried it but It does not work .

          Followup:

Umm, care to share what you tried and I'll tell you what you did wrong.

You see -- I don't use windoze at all -- so I cannot show you the windows way,
but I remember enough about it to tell you want you did wrong.

Maybe you are missing the "for" command in the "cmd scripting" language.  See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1245890152896
for a unix foreach example -- similar to what you are looking for.

January 05, 2003 Tomás A. -- Thanks for the question regarding
      "Copying datafiles between different operating systems", version 8.1.7

      You Asked
Dear Tom
I looking for a good user oriented answer to the question:
Why we can not copy the database files between our current system, running under
HP Tru64 Unix, to our target system, running under Sun Solaris 5.8? I'll
appreciate your help in this case.

Thanks in advance
Tomás A. Torres Ramírez

Background:
Current environment: HP Tru64 UNIX + EMC Symmetrix 8430 storage system
Target environment: Sun Solaris 5.8 + HP StorageWorks Enterprise Virtual Array
(EVA)

      and we said...
Because of differences in the way the operating systems might represent the
endianess of words.

Because of differences in the ported software (Oracle code).  They might have
put a different bit on in HP then Solaris -- would be unusual, but it is not
something we don't promise.

Because it is not supported as of 9iR2.

Review &
      Followup

      Rating: 5
Copying
      datafiles between different operating systems  January 05,
      2003
Reviewer:  Tomas A. Torres Ramirez  from
      Santiago; Chile
Thank you very much. Your answer is to us very useful, specially in this phase
of the project.   Your words about Oracle 9iR2 are very interesting, I'll ask
you more about the support of copy between different operating systems provided
by this Oracle release as soon as possible.

      Rating: 4
"endianess" --
      what is it ?   January 05, 2003
Reviewer:
      Robert  from PA
>>Because of differences in the way the operating systems might represent the
endianess of words.

What's "endianess", Tom ? just can't guess, didn't mean to pick...

          Followup:

it refers to how the machines physically dereference N bytes of memory.  On
intel for example -- a 4 byte long is stored "backwards" byte by byte from the
way it is stored on SPARC.  So, a 4 byte long on intel linux would be reversed
from a 4 byte long on SPARC.

High endian
Low endian

just general "computer terms"

      Rating: 5
More about
      "endianess". Big/Little-Endian  January 05, 2003

Reviewer:  Tomas A. Torres Ramirez  from Santiago,
      Chile
See
http://www.webopedia.com/TERM/b/big_endian.html

      Rating: 4
unlikely file
      compatability between OSs  January 05, 2003

Reviewer:  Jim  from Portland, OR
It is unlikely that Oracle will provide file compatibility betweeen different
operating systems.  The QA testing alone would be huge and difficult.  In
addition to the endian problem different OS have different byte alignment.  So
it is quite likely that for one particular OS the byte alignment is on 4 byte
bboundries, another on 8 byte boundies, and another on 2 byte boundries, and
still another with no alignment(or an alignment of 0).  So the physical storage
of the data may be slightly different by OS.  When a data block is read from
disk into memory (db block buffer) the byte alignment is the same.  That is the
format is the same in memory as it is on disk.  (Otherwise, it would have to go
through machinations in translating the structure on disk to ram and visa versa,
slowing the system down.)

This happened when Oracle on Netware went from 7 to 8 the byte alignment changed
and so to upgrade you had to do a full export in 7, build the db in 8, and do an
import.

January 06, 2003 Lakshmi -- Thanks for the question regarding
      "Query t", version 8.1.7

      You Asked
Hi Tom
         I have an table which stores a lifecycle  of a particular transcation.

SQL > DESC LIFECYCLE;

NAME          NULL          TYPE
------------------------------------
Account_id    not null        number(10)
trans_date                       date
Event                         char(100)
User                          Char(10)

SQL> SELECT * FROM LIFECYCLE ;
     Account_id    Trans_date       Event       User
--------------------------------------------------------
     12000          12/12/2002      OPEN         BOSCH
     12000          1/2/2003        DEPOSIT      BOSCH
     12000          2/2/2003        WITHDRAW     BOSCH
     12000          2/2/2003        FREEZE       MANAGER

     12001          1/1/2003       OPEN          JOE
     12001         2/2/2003        DEPOSIT       JOE
     12001         2/2/2002        FREEZE        MANAGER
     12001         3/2/2003        RELEASE       MANAGER
     12001         3.2.2003        CLOSED        JOE

THIS TABLE STORES THE LIFE CYCLE OF ALL TRANSCATIONS.
I want to write a sql query which gets Event as input and selects all event
preceeding and following it for a particular account _id.

Example

SQL > Accept Event->   WITHDRAW
      Accept Account_id-> 12000

SQL > Account_id    Trans_date       Event       User
---------------------------------------------------------

12000          1/2/2003        DEPOSIT      BOSCH
12000          2/2/2003        FREEZE       MANAGER

Best  Regards

Lakshmi

      and we said...
ick -- why char -- char stinks.

ops$tkyte@ORA817DEV> select *
  2    from (
  3  select account_id,
  4         lag(trans_date) over ( partition by account_id order by trans_date )
                    prior_td,
  5         lag(event) over ( partition by account_id order by trans_date )
                  prior_ev,
  6         lag(uname) over ( partition by account_id order by trans_date )
                    prior_uname,
  7             trans_date, event, uname,
  8         lead(trans_date) over ( partition by account_id order by trans_date
                    ) next_td,
  9         lead(event) over ( partition by account_id order by trans_date )
                                  next_ev,
 10         lead(uname) over ( partition by account_id order by trans_date )
                           next_uname
 11    from t
 12   where account_id = 12000
 13             )
 14   where event = 'WITHDRAW'
 15  /

ACCOUNT_ID PRIOR_TD   PRIOR_E PRIOR TRANS_DATE EVENT    UNAME NEXT_TD    NEXT_E
NEXT_UN
---------- ---------- ------- ----- ---------- -------- ----- ---------- ------
-------
     12000 01/02/2003 DEPOSIT BOSCH 02/02/2003 WITHDRAW BOSCH 02/02/2003 FREEZE
MANAGER

Analytics rock....

Review &
      Followup

      Rating: 5
Analytical
      query   January 04, 2003
Reviewer:  lakshmi
       from Bangalore India
Thank you very much Tom !!!! Your website is a excellent resource for developers
and DBA's.
Thank you very much indeed.

      Rating: 5
analytical
      query  January 05, 2003
Reviewer:
      A reader
Brilliant !!!

      Rating: 5
Analytical
      query   January 05, 2003
Reviewer:  Lakshmi
       from Bangalore India
Tom
     Is it possible to find previous two events and following two events.

          Followup:

read about lag and lead -- yes, yes it is.  They take another optional
parameter, how many rows to lag or lead by.

January 06, 2003 Steve -- Thanks for the question regarding
      "Guideline for SQL", version 8.1.7 & 9.1

      You Asked
Hi Tom,

I try to search this site's archive for writing efficientsql. But I can't find
one thread.

Do you have any guidelines, good practices or examples for writing efficient SQL
statements, especially writing efficient SQL statements for parallel execution?
Thank you!

Sorry to ask you question in the weekend. Have a good weekend!

Steve

      and we said...
Well, that is a couple of books + lots of experience + common sense + knowing
all of the available features.

If I could fit it on a web page like this, well, I'd put it into code and it
would be the worlds best optimizer.

Start here

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96524/toc.htm
which is the concepts guide.  Understand HOW the database works and you are well
on your way.

then after that, give this

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96533/toc.htm
a read through. Finish up with:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96520/toc.htm

if you read 100% of that, retain 10%, you'll know 90% more then most experts out
there.

Review &
      Followup

      Rating:
      5
reading  January 05, 2003
Reviewer:
      mo
Tom:

How about reading the two Wrox books that you wrote. Would not this be better to
start with or they are still missing a lot of info?  If you read the two books
would you still be better than the 90% of people out there?

      Rating: 5
effective
      SQL  January 05, 2003
Reviewer:  Ashiq
      Shamsudeen  from Trichy, Tamil Nadu, India
Hi Tom,

 From ur quote "Well, that is a couple of books + lots of experience + common
sense + knowing
all of the available features. "

  what's that "couple of books" ? Can u let us know?

          Followup:

well, read the rest of the answer which is mostly URLS to books?

January 06, 2003 Lewis -- Thanks for the question regarding
      "Strange explain plan results", version 8.1.7.3

      You Asked
Tom,

I have an issue I can't quite figure out.  I've reduced the query to as small an
example as I can.  The original query was a lot of tables and a couple of unions
and I've gotten it down to a single 2 table query.

When I run this query:

SELECT aot.transaction_id,
               aot.amount,
               aot.check_no,
               aot.entry_date,
               aot.chain_no,
               aot.station_no
          FROM ach_outgoing_transactions aot,
               ach_lookup_Codes alc
          WHERE aot.transaction_state = alc.alt_lookup_key
            AND alc.lookup_type = 'TRANSACTION_STATE'
            AND alc.lookup_code = 'ACK'
            and (entry_DATE BETWEEN to_date('01/01/2002 0000','mm/dd/yyyy
hh24mi')
                                AND to_date('01/01/2100 2359','mm/dd/yyyy
hh24mi'))

I get this explain plam:

SELECT STATEMENT Optimizer=CHOOSE (Cost=5294 Card=60948 Bytes=5607216)
  NESTED LOOPS (Cost=5294 Card=60948 Bytes=5607216)
    TABLE ACCESS (BY INDEX ROWID) OF ACH_LOOKUP_CODES (Cost=2 Card=1 Bytes=37)
      INDEX (UNIQUE SCAN) OF LOOKUP_CODES_PK (UNIQUE) (Cost=1 Card=1)
    TABLE ACCESS (FULL) OF ACH_OUTGOING_TRANSACTIONS (Cost=5292 Card=426633
Bytes=23464815)

But when I change the data in the to_dates in the where clause, to:

SELECT aot.transaction_id,
               aot.amount,
               aot.check_no,
               aot.entry_date,
               aot.chain_no,
               aot.station_no
          FROM ach_outgoing_transactions aot,
               ach_lookup_Codes alc
          WHERE aot.transaction_state = alc.alt_lookup_key
            AND alc.lookup_type = 'TRANSACTION_STATE'
            AND alc.lookup_code = 'ACK'
            and (entry_DATE BETWEEN to_date('12/16/2002 0000','mm/dd/yyyy
hh24mi')
                                AND to_date('12/31/2002 2359','mm/dd/yyyy
hh24mi'))

I get this explain plan:

SELECT STATEMENT Optimizer=CHOOSE (Cost=67 Card=80 Bytes=7360)
  NESTED LOOPS (Cost=67 Card=80 Bytes=7360)
    TABLE ACCESS (BY INDEX ROWID) OF ACH_LOOKUP_CODES (Cost=2 Card=1 Bytes=37)
      INDEX (UNIQUE SCAN) OF LOOKUP_CODES_PK (UNIQUE) (Cost=1 Card=1)
    TABLE ACCESS (BY INDEX ROWID) OF ACH_OUTGOING_TRANSACTIONS (Cost=65 Card=560
Bytes=30800)
      INDEX (RANGE SCAN) OF ACH_OUT_TRAN_ENTRY_DATE_NDX (NON-UNIQUE) (Cost=18
Card=560)

The lookup_codes table only has 339 records.  The outgoing_transactions table
has 426633 records.  I normally wouldn't really care about the different explain
plans but in this case the query goes from returning in a couple of seconds to
taking forever (actually I kill it after waiting about 5 minutes.  I'm not sure
really how long it would take.)

I didn't think the cbo looked at data in the where clause, just the columns
used.  Am I wrong?  Is not, any ideas why I'm getting these results?

Thanks a lot,

Lewis

Tom,

The first query returns 432 records and the second query returns 3 records.

I thought that the stats might be stale so I analyzed them right before running
the queries.  I analyzed all of the tables in the schema.

I'm definately not a tuning expert.  Am I doing something wrong on the analyze?
This is the analyze statement I run for each table:

EXECUTE IMMEDIATE 'analyze table ' || c1.table_name || ' compute statistics ' ||
                        ' for table ' ||
                        ' for all indexes'

I have a cursor that loops through user_tables.  Long story why I do that.  The
plan in the future is to use monitor and dbms_stats.

Thanks again,

Lewis

Tom,

You're right; all of the tables came back in the query (I had to add group by
table_name).

Here's a sample of the data (sorry about the wrap):

TABLE_NAME                     COLUMN_NAME                    ENDPOINT_NUMBER
ENDPOINT_VALUE ENDPOINT_ACTUAL_VALU
------------------------------ ------------------------------ ---------------
-------------- --------------------
ACH_LOOKUP_CODES               LOOKUP_TYPE                                  0
 3.3886E+35
ACH_LOOKUP_CODES               LOOKUP_CODE                                  0
 1.6713E+35
ACH_LOOKUP_CODES               LOOKUP_DESCR                                 0
 3.3816E+35
ACH_LOOKUP_CODES               ACTIVE_FLAG                                  0
 4.6211E+35
ACH_LOOKUP_CODES               ALT_LOOKUP_KEY                               0
 3.3848E+35
ACH_LOOKUP_CODES               LAST_DML_USER                                0
 4.1182E+35
ACH_LOOKUP_CODES               LAST_DML_DATE                                0
 2452622.54
ACH_OUTGOING_TRANSACTIONS      RAW_MICR                                     0
 4.2748E+35
ACH_OUTGOING_TRANSACTIONS      TRANSACTION_ID                               0
 3.6444E+35

I take it it's bad that I have those?

By the way, do you prefer people to add at the top of the message or the bottom
when the add to it like I'm doing now?

Thanks,

Lewis

      and we said...
If you have been gathering via:

EXECUTE IMMEDIATE 'analyze table ' || c1.table_name || ' compute statistics ' ||
                        ' for table ' ||
                        ' for all indexes'

then you have not been gathering histograms -- but once upon a time ago, someone
did.  That legacy information is being used as if it were current.  It will lead
to the wrong plan (garbage in, garbage out)

Consider:

ops$tkyte@ORA920> create table t as select 1 x, all_objects.* from all_objects;
Table created.

ops$tkyte@ORA920> create bitmap index t_idx on t(x);
Index created.

ops$tkyte@ORA920> analyze table t compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns
  5  /
Table analyzed.

ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select * from t where x = 1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=43 Card=29422 Bytes=2912778)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=43 Card=29422 Bytes=2912778)

ops$tkyte@ORA920> select * from t where x = 200;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=39 Card=196 Bytes=19404)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=39 Card=196 Bytes=19404)
   2    1     BITMAP CONVERSION (TO ROWIDS)
   3    2       BITMAP INDEX (SINGLE VALUE) OF 'T_IDX'

ops$tkyte@ORA920> set autotrace off

that is the right plan for right now -- x=1 for all rows, faster to blow off
the index and full scan the table... x=200 use that index

ops$tkyte@ORA920> drop index t_idx;
Index dropped.

ops$tkyte@ORA920> update t set x = 200;
29422 rows updated.

ops$tkyte@ORA920> create bitmap index t_idx on t(x);
Index created.

ops$tkyte@ORA920> analyze table t compute statistics
  2  for table
  3  for all indexes
  4  /
Table analyzed.

ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select * from t where x = 1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=43 Card=29422 Bytes=2912778)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=43 Card=29422 Bytes=2912778)

ops$tkyte@ORA920> select * from t where x = 200;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=39 Card=196 Bytes=19404)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=39 Card=196 Bytes=19404)
   2    1     BITMAP CONVERSION (TO ROWIDS)
   3    2       BITMAP INDEX (SINGLE VALUE) OF 'T_IDX'

ops$tkyte@ORA920> set autotrace off

that is NOT the right plan - it should know that x is not 1 for most rows --
but it still believes x = 1 for ALL rows.  It got it backwards now...

ops$tkyte@ORA920> analyze table t compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns
  5  /

Table analyzed.

ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select * from t where x = 1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=39 Card=196 Bytes=19404)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=39 Card=196 Bytes=19404)
   2    1     BITMAP CONVERSION (TO ROWIDS)
   3    2       BITMAP INDEX (SINGLE VALUE) OF 'T_IDX'

ops$tkyte@ORA920> select * from t where x = 200;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=43 Card=29422 Bytes=2912778)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=43 Card=29422 Bytes=2912778)

ops$tkyte@ORA920> set autotrace off

and now it is doing the right thing.

You can either get rid of the histograms or compute them -- try both and see
which works best in your environment

Adding to the bottom is best for me -- you see, I see your question with a text
box at the bottom of your text, so the newest stuff is right there...

Review &
      Followup

      Rating: 5
Excellent
      Info  January 06, 2003
Reviewer:  Lewis
      Cunningham  from St Pete, FL, USA
Thank you.  I would have bet money I found some kind of a bug.

      Rating:
      5
  January 06, 2003
Reviewer:
      A reader
Hi Tom,

 I heard that HISTOGRAM is obsolete in oracle 8i.

 Is that true?

Thanks,

          Followup:

umm, no -- it would be wrong.

      Rating: 5
excellent
      information  February 27, 2003
Reviewer:  Steven
      Tevault  from Wilbraham, MA USA
Thanks, Tom, for providing excellent info.  After researching through five or
six AskTom articles from a keyword search on "bitmap indexes", I was able to
find the answers I needed.  Using a combined attack of gathering histograms,
reducing the DB_FILE_MULTIBLOCK_READ_COUNT and setting the
OPTIMIZER_INDEX_COST_ADJ, along with liberal use of the AUTOTRACE SQL*Plus
setting, I've reduced a large DSS query from 3+ minutes to 12 seconds.  Thanks
again.

January 06, 2003 Prabhakar -- Thanks for the question regarding
      "bulk update", version 8.1.7

      You Asked
Hi ,

I need to update a table called resource which has three columns
namely empid,entry_ts,org_id .

The org_id column needs to be updated . The org_id column has been changed and i
have a new table called org_map which contains the new_org_id and old_org_id .
this old_org_id column corresponds to the org_id column in resource table .

also there is a unique constraint on empid,org_id columns .

now that multiple old_org_id will map into single new_org_id .
as a result when ever i update i get a unique key violation .

there are around 4 million records that need to be updated and when ever i
encounter a unique key violation i need to store the unique violations in a
separate table so that i delete them from resource table later .

i tried to use single row update but it is taking more than 25 hours to complete
.

there is a index on entry_ts .

can you please tell me is there any other solutions .

i heard about the bulk collect and forall usage but not tried .

can you give a sample code using the bulk collect clause .

thanks
Prabhakar

      and we said...
drop the constraint.
update ( select T.org_id old_org_id, NEW_CODES.new_org_id new_org_id
           from T, NEW_CODES
          where t.org_id = new_codes.old_org_id )
  set old_org_id = new_org_id;

re-enable the constraint using the EXCEPTIONS INTO clause to capture the rowids
of the bad records

clean up bad records

re-enable the constraint

should not take more then a couple of minutes actually.  As way of example, I
have a 1.8 million record table built from all_objects (over and over)..
object_id was set to rownum -- so it started unique.  I did this tho:

big_table@ORA920> insert into big_table ( owner, object_name, object_id,
created, last_ddl_time, object_type )
  2  select owner, object_name, object_id, created, last_ddl_time, 'XXX'
  3  from big_table where owner = 'BIG_TABLE';

big_table@ORA920> alter table big_table add constraint unq unique( object_id,
object_type );

big_table@ORA920> create table convert
  2  as
  3  select distinct object_type old_object_type, decode( object_type, 'XXX',
'TABLE', object_type ) new_object_type
  4  from big_table;

big_table@ORA920> alter table convert add constraint convert_pk primary
key(old_object_type);

(that'll create some DUP object_id, object_types in my big table when I
'convert' them).... Here is how your mass update should run then:

big_table@ORA920> alter table big_table
  2  disable constraint unq;

Table altered.

Elapsed: 00:00:01.44
big_table@ORA920>
big_table@ORA920> update ( select big_table.object_type old_object_type,
  2                  convert.new_object_type new_object_type
  3                     from big_table, convert
  4                    where big_table.object_type = convert.old_object_type )
  5    set old_object_type = new_object_type
  6  /

1833984 rows updated.

Elapsed: 00:03:10.64
big_table@ORA920>
big_table@ORA920> create table exceptions(row_id rowid,
  2                          owner varchar2(30),
  3                          table_name varchar2(30),
  4                          constraint varchar2(30));

Table created.

Elapsed: 00:00:00.48
big_table@ORA920>
big_table@ORA920> alter table big_table enable constraint unq
  2  exceptions into exceptions
  3  /
alter table big_table enable constraint unq
*
ERROR at line 1:
ORA-02299: cannot validate (BIG_TABLE.UNQ) - duplicate keys found

Elapsed: 00:00:49.85
big_table@ORA920>
big_table@ORA920> delete from big_table where rowid in ( select row_id from
exceptions );

128 rows deleted.

Elapsed: 00:00:01.24
big_table@ORA920>
big_table@ORA920> alter table big_table enable constraint unq
  2  exceptions into exceptions
  3  /

Table altered.

Elapsed: 00:00:28.48

less then 5 minutes -- this was on a 1ghz single cpu p4 with a simple IDE
drive, 512m of ram running linux in archivelog mode....

On a real box, shouldn't take too long at all.

Since you are updating every row - you will be locking every row -- so disabling
the constraint is "safe" -- you are doing this in ISOLATION anyway (other people
may query but no one will be updating see -- so it is the right way to do it)

Review &
      Followup

      Rating: 3
bulk
      update  January 06, 2003
Reviewer:
      Prabhakar  from Rockville,USA
Hi Tom ,

I think i confused you . let me explain the problem again .

 table prab_news
----------------------
 news_id
 org_id
 story_ts

table mapping1
-----------------------
old_org_id
new_org_id

The mapping1.old_org_id is same as prab_news.org_id .

while prabs has a unique_constraint on news_id,org_id .

Also multiple old_org_id is equal to single new_org_id .

There are around 100,000 records in mapping1 and there around 4.5 million
records in prabs_news .

I will have update the prab_news.org_id with mapping1.new_org_id  whenever the a
match is found between prab_news.org_id=mapping1.old_org_id  .

update (select org_id old , new_org_id new
        from prab_news , mapping1
        where prab_news.org_id=mapping1.old_org_id)
 set old=new ;

Is this what you meant ? but when i ran this query i got the below error

ERROR at line 4:
ORA-01779: cannot modify a column which maps to a non key-preserved table

Thanks
Prabhakar

          Followup:

put a primary key on mapping1 -- old_ord_id MUST be unique (else you don't know
what row to update prab_news with)

Your example is exactly the same as mine, just add that primary key constraint
to mapping1.

      Rating: 3
bulk
      update  January 06, 2003
Reviewer:
      Prabhakar  from Rockville , USA
Hi tom ,

The table mapping1 cannot have the constraint because of the business rule
involved otherwise i will not have matches for the old_org_id .

Once i update the prab_news table i will have many duplicates and i can chose to
keep one record and will delete the other records and then i will enable the
constraint on the mapping1 table .

but why am i getting the  error

update (select org_id old , new_org_id new
        from prab_news , mapping1
        where prab_news.org_id=mapping1.old_org_id)
 set old=new ;

ERROR at line 4:
ORA-01779: cannot modify a column which maps to a non key-preserved table

what does this error means ?

          Followup:

HUH?  mapping1 -- why cannot it have a primary key -- or at least UNIQUE
constraint on old_org_id.

If not -- your UPDATE is ambigous.  What you would be telling me is that:

OLD_ORD_ID     NEW_ORG_ID
-----------    ---------------
ABC            xyz
ABC            def

is valid -- if that is the case, what pray tell would be the new_org_id that
should be assigned.

You are getting the 1779 simply because you do not have a unique constraint on
OLD_ORG_ID, so the database cannot know that the above condition (of two ABC
records existing) is not true.

OLD_ORG_ID has to be unique or you need to go back to the drawing board
entirely!

January 06, 2003 Satar -- Thanks for the question regarding "Last
      Row", version 9

      You Asked
Hi Tom,

I want to write a query to display the last(or first) row of a table. I used the
following query

  select * from emp where rowid=(select max(rowid) from emp);

this worked. but when I use the same query with rownum I get no rows returned.
Also if I run a query like select * from emp where rownum>3; I get no rows
returned. My question is that is there any restrictions or conditions under
which rownum can be used and is there any other way of displaying the last row
of a table without using rowid. Tnank you
Satar

      and we said...
there IS NO SUCH THING AS A LAST ROW.

or middle row.

or first row.

rowids are physical addresses, they are NOT telling you what was inserted first,
second, third, Nth.

The ONLY way to get the "last" row is for the developer to have put a timestamp
or some sequence number in there.  then you can select * from t where x =
(select max(x) from t);

Your premise that you can retrieve the "last row" via rowid is itself flawed

Read this archived article I did for Oracle magazine on this topic:

http://otn.oracle.com/oramag/oracle/01-nov/o61asktom.html

Unless and until you have some field in that table that you can order by -- you
cannot get the "last" row.

Review &
      Followup

      Rating:
      4
hmm..metalink...indicates:  January 06, 2003

Reviewer:  patrick  from usa
Tom,
I am not disagreeing with you..but I just ran across this
in metalink..I agree with you on the timestamp commment.

p-

doc 208143.1

goal: How to obtain last rows of a select without PL/SQL
fact: Oracle Server - Enterprise Edition

fix:
1. Create an index (if it doesn't exist yet) on the column where the sort will
be done:

    CREATE INDEX "schema"."index_name" ON
      "schema"."table_name"("column_name");

2. Launch the select with index_desc and rownum (here for the 10 last rows):

    SELECT /*+ index_desc(table_name index_name) */ *
      FROM table_name
      WHERE rownum <=10;

          Followup:

that is the last row of a select -- I disagree with their premise all together.

What they are trying to do is

a) read the table via the index INDEX_NAME DESC (backwards)
b) get the first 10 rows

Now, if the index hint is ignored for whatever reason -- what do you have?
GARBAGE!  the only proper answer is:

select *
  from ( select * from table_name order by column_name desc )
 where rownum < 10;

that works with or without the index in place.

Also -- tell me, what does this have to do with getting the "last row from a
table" - there is no such thing as a "last row" in a table?  That note was
attempting to show (a lame) way to get the last N rows from a result set -- not
a table!

January 06, 2003 Tom -- Thanks for the question regarding
      "Describing/viewing package spec with no body", version 8.1.7

      You Asked
Is there a way to grant to a user the ability to view a package spec without
allowing them to modify it?  I have a spec with no body, since it is simply
error code constants.  I want a user to be able to view it, but when he does a
describe, SQLPLUS returns nothing.  No error either, just gives him back the
command line.

I'd like him to be able to see the SPEC and not be able to modify it.  He has
EXECUTE privs on it.

Example:

create or replace PACKAGE ERROR_CODES_PKG

  IS
    -- Error Codes
    ec_success constant integer := 0;
    ec_inconsistent_params constant integer := 1;
    ec_row_already_marked_deleted constant integer := 2;
    ec_no_rows_affected constant integer := 3;

END; -- Package spec

      and we said...
select text
  from all_source
 where name = 'ERROR_CODES_PKG'
   and type = 'PACKAGE'
 order by line;

if they have execute, they have the ability to read it (the spec -- but not a
body)
Review
      & Followup

      Rating:
      4
OK  January 06, 2003
Reviewer:  Tom
      Best  from PA
Thanks.  I thought that would work, but didn't try it, because I was hoping I
could always tell these guys to always use DESCRIBE, and not have to bother with
selecting.

January 06, 2003 Nagaraju -- Thanks for the question regarding
      "Privileges to use XML", version 9.2

      You Asked
Tom,

Happy New year!

1.Objective: is to parse an XML string and load the data to a table

this is the table:

CREATE TABLE my_xml
(
id NUMBER,
NAME VARCHAR2(50),
dob  DATE
);

this the stored proc:

CREATE OR REPLACE PROCEDURE testInsert( xmlDoc IN CLOB) IS
  insCtx DBMS_XMLSave.ctxType;
  doc CLOB;
  rows NUMBER;
BEGIN

  insCtx := DBMS_XMLSave.newContext('my_test'); -- get the save
context

   rows := DBMS_XMLSave.insertXML(insCtx, xmlDoc);

   DBMS_XMLSave.closeContext(insCtx);

END;

i get the following errors:

LINE/COL ERROR
--------
---------------------------------------------------------------------------------
---
2/10     PLS-00201: identifier 'DBMS_XMLSAVE.CTXTYPE' must be declared
2/10     PL/SQL: Item ignored
7/3      PLS-00320: the declaration of the type of this expression is incomplete
or malformed
7/3      PL/SQL: Statement ignored
10/12    PLS-00201: identifier 'DBMS_XMLSAVE.INSERTXML' must be declared
10/4     PL/SQL: Statement ignored
12/30    PLS-00320: the declaration of the type of this expression is incomplete
or malformed
12/4     PL/SQL: Statement ignored

do we need any priveleges

2.is there a way like thru my xmldoc if i pass 3 rows and the table has 5 rows
then update the table with these 3 rows(i.e. delete non existing, update
existing and insert new rows)

thanks in advance.

      and we said...
1) it is granted to PUBLIC by default -- are you sure it is

  a) installed
  b) installed correctly?

tkyte@tkyte-pc-isdn OraHome1]$ cd rdbms/admin
[tkyte@tkyte-pc-isdn admin]$ grep -i dbms_xmlsave *.sql
catxdbj.sql:create or replace public synonym dbms_xmlsave for dbms_xmlsave;
dbmsxsu.sql:Rem    vnimani     08/04/00 - add support for XSLT in dbms_xmlsave
dbmsxsu.sql:CREATE OR REPLACE PACKAGE DBMS_XMLSAVE AUTHID CURRENT_USER AS
dbmsxsu.sql:CREATE OR REPLACE PACKAGE BODY DBMS_XMLSAVE AS
dbmsxsu.sql:END DBMS_XMLSAVE;
dbmsxsu.sql:GRANT EXECUTE ON DBMS_XMLSAVE TO PUBLIC;

2)  DBMS_XMLSAVE has no inherint features to do either delete, update or insert
in the same statement.  The best way to manage what you're trying to do would be
to load all your rows into a temporary table (using DBMS_XMLSave), then do your
conditional processing afterwards.  I just wrote a quick procedure to accomplish
this, so here's an example (leaves out the dbms_xmlsave stuff, which you can
looking on asktom.oracle.com if you need some help there):

sdillon@FUNK92> -- my sample table i want to ins/upd/del into
sdillon@FUNK92> create table hash_vals (
  2    id       number(4) primary key,
  3    val      varchar2(30),
  4    orig_ts  timestamp
  5  )
  6  /
Table created.

sdillon@FUNK92> -- make a temp table to serve as our updating table
sdillon@FUNK92> create global temporary table hash_vals_t(
  2    id         number(4),
  3    val        varchar2(30),
  4    orig_ts timestamp
  5  )
  6  on commit delete rows
  7  /
Table created.

sdillon@FUNK92> -- procedure for your desired processing
sdillon@FUNK92> create or replace procedure process_temp_vals
  2  is
  3  begin
  4    delete
  5      from hash_vals
  6     where id not in (select id from hash_vals_t);
  7    --
  8    merge into hash_vals h
  9    using hash_vals_t ht on (h.id = ht.id)
 10    when matched then
 11      update set val = ht.val
 12    when not matched then
 13      insert (id, val, orig_ts) values (ht.id, ht.val, systimestamp);
 14  end process_temp_vals;
 15  /
Procedure created.

sdillon@FUNK92> -- insert some rows into the table
sdillon@FUNK92> insert into hash_vals values (1, 'Red', systimestamp);
1 row created.
sdillon@FUNK92> insert into hash_vals values (2, 'Orange', systimestamp);
1 row created.
sdillon@FUNK92> insert into hash_vals values (3, 'Yellow', systimestamp);
1 row created.
sdillon@FUNK92> insert into hash_vals values (4, 'Green', systimestamp);
1 row created.
sdillon@FUNK92> insert into hash_vals values (5, 'Blue', systimestamp);
1 row created.
sdillon@FUNK92> insert into hash_vals values (6, 'Indigo', systimestamp);
1 row created.
sdillon@FUNK92> insert into hash_vals values (7, 'Violet', systimestamp);
1 row created.

sdillon@FUNK92> -- what are the rows?
sdillon@FUNK92> select id,val,to_char(orig_ts,'MI:SSxFF') from hash_vals
  2  /
                 ID VAL                            TO_CHAR(ORIG_TS
------------------- ------------------------------ ---------------
                  1 Red                            21:00.715648
                  2 Orange                         21:00.720533
                  3 Yellow                         21:00.723827
                  4 Green                          21:00.727114
                  5 Blue                           21:00.730474
                  6 Indigo                         21:00.733754
                  7 Violet                         21:00.737123
7 rows selected.

sdillon@FUNK92> -- now simulate the dbms_xmlsave putting rows into your temp
table:
sdillon@FUNK92> insert into hash_vals_t values (1, 'Red', systimestamp);
1 row created.
sdillon@FUNK92> insert into hash_vals_t values (5, 'Blue', systimestamp);
1 row created.
sdillon@FUNK92> insert into hash_vals_t values (13, 'Yellow', systimestamp);
1 row created.

sdillon@FUNK92> -- now you need to call the procedure
sdillon@FUNK92> begin
  2    process_temp_vals;
  3  end;
  4  /
PL/SQL procedure successfully completed.

sdillon@FUNK92> -- show the results of the processing:
sdillon@FUNK92> select id, val, to_char(orig_ts,'MI:SSxFF') from hash_vals
  2  /
                 ID VAL                            TO_CHAR(ORIG_TS
------------------- ------------------------------ ---------------
                  1 Red                            21:00.715648
                  5 Blue                           21:00.730474
                 13 Yellow                         21:00.768551

You can see all the rows that weren't in the temp table were deleted.  the rows
that were there had the VAL column updated, but not the ORIG_TS column (you can
see that in the MERGE statement).  This shows us which rows stayed there and
which ones are new.  ID 13 wasn't there originally, so you can see it was
inserted in the process.

Hope that helps!

January 06, 2003 -- Thanks for the question regarding "Database
      architecture choice", version 9.2.0

      You Asked
We have a Web Application with a 500 Go Oracle 9i database at the backend, and
we now have two choices : 1. a Mono instance and multi schemas architecture and
2. the other is a mono instance and mono schema with partitioning.

We prefered the first choice until we realized that there's a problem with the
connection pooling !

Could you please suggest us some way to combine 9iAS connection pooling and 9i
VPD in order to stay with the Mono instance and multi schemas architecture which
we prefer for some reasons :

1. Four of our six schemas will accept a new Web Application several months
after the deployment, that means they will have another set of database objects.
And the other two schemas will also grow differently sometime later using JCA ti
integrate mainframe TP.

2.Users require their data kept "seperated" from those belonged to others users.

--------------------------------------------------------------------

Yes, there's nothing precluding me from using multi-schemas, in fact the
architecture "Mono instance and Multi-Schemas" is what I prefer , but the
problem is How can I use connection pooling that supposes to use "generic
username" to connect to a given schema ?

So if I resume my question

I have a Web application using 18 tables and I have 6 groups of users

1. If I create a single instance and duplicate six seperated schemas - one for
each group of users, I have to find a solution to use connection pooling - this
is where I need your help.

2. If I create a single instance and a single schema with tables partitioned by
the USER_CODE, I don't have problem with connection pooling because I can use a
"generic username" and when the user identifies himself, I can gather
information to use FGAC.

I'm sorry for not expressing myself very well in English.

      and we said...
why do you need to connect to a given schema?

I can, when logged in as "generic user" access scott.emp as well as
public.all_objects, as well as foobar.table?

but actually -- I despise the "single generic user".  I think you want to
investigate N-Tier Proxy Authentication - which works with our Jdbc connection
pool.  Here each user would have a database account so you can use vpd,
auditing, roles, grants -- the whole 9 yards.  The app server logs in "as
itself" but "on behalf of" a user.

goto:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/index.htm
and search for

proxy authentication

But once again - addressing #2, it doesn't matter if the tables are in 1 schema,
2 schemas, 1000 schemas.  The same exact logic applies 100%.  But -- give
serious consideration to using proxy authentication so you can take advantage of
the built in features of the database like auditing and grants and roles.  So
you don't have to recode all of that in your middle tier.
Review &
      Followup

      Rating:
      4
  January 06, 2003
Reviewer:
      A reader

January 07, 2003 -- Thanks for the question regarding "zero memory
      sort and one disk sort, is this possible?", version 8.1.7.0.1

      You Asked
Hi Tom

Recently one of our developers ran into a really simple join performance
problem, it is simple because he was simply joining two tables of 200000 rows
each and returning around 300 rows. The join columns were both indexes.

I ran the explain plan (used autotrace on in sql*plus) and saw it was doing sort
merge join and I observed that it did not do a single sort in memory, rather in
autotrace statistics I saw 0 memory sort and 1 disk sort. I thought there must
be memory sorts before disk? How come?

I then did all kind of things to change the query plan, nested loop, hash join,
first_rows, rule optimizer, I compared the costs but I am puzzled, can this cost
thing be trusted at all? Becaise in sort merge join the cost was 10 times lower
then nested loop! But it was much much slower than nested loop. Is cost worth
take into account at all??? Because after seeing this query in action the cost
is really missleading rather then helpful... Plus in any of Oracle documents
actually how is this cost calculated and in what unit it is based. For example
if I have a query which cost 10 and another different query with cost 100 does
this mean that the second one is 10 times more costly? Or the cost we see is
relative in same queries only (cannot compare costs between different queries?)

Cheers

      and we said...
It only counts as a memory sort if it were done in memory.  Once it touches the
disk -- it is a disk based sort.

So, it was a memory sort, until you used sort_area_size bytes and needed to
swap.  then it was a disk based sort.

We count the sort type at the end, when we know if it was a disk or memory based
sort.

Yes you can trust the cost -- however to you it has almost NO meaning.  (means
go ahead, trust it, it was the cost.  You cannot make any decisions yourself
based on it but trust that it was the cost)

the *cost* has no bearing, no implication, no impact on the runtime.

We take a query.
We generate umpteen plans for it.  Each plan is assigned a relative cost.
We take the query with the least cost and run it.

You cannot really take the costs of two different queries and compare them, it
is apples and oranges.

A query with a cost of 1 billion may return subsecond.
A query with a cost of 1 might take a day.

It is just that in all of the plans generated -- for their respective queries --
they had the lowest cost in that set of equivalent plans.

Review &
      Followup

      Rating:
      4
  April 21, 2002
Reviewer:
      A reader
But I thought the sort is always done first in sort_area_size then if it's too
small then we start to sort in disk so I have always have this in mind, there
must at least a sort in memory then disk and this is also the ILT course
materials suggests

Regarding the cost, so is it correct if I compare cost of different plans but
for SAME query? Or even that is incorrect? Since you have said EQUIVALENT plans
for same query am I wrong comparing cost of nested loop and sort merge join in
my query (same query)?

          Followup:

All sorts are done in memory.

Some sorts need disk.

Sorts that do not need disk are counted as sorts (memory)
Sorts that do need disk are counted as sorts (disk)

A sort is either memory or disk.  It is not counted as both.  the sorts
(memory) are a count of sorts done ENTIRELY in memory.  the sorts (disk) are a
count of sorts that needed at least 1 byte of disk to complete.

Even comparing the cost of two plans for the SAME query is incorrect.
Environmental factors such as (but not limited to)

o sort_area_size
o optimizer_goal
o hash_area_size
o etc etc etc.....

will artificially affect the cost (thats how they work).  Hence, by setting the
optimizer_goal from first_rows to all_rows -- you could make a full scan more
appealing then index access.  You are affecting the costs.

You cannot compare the costs of two queries.

      Rating: 5
A somewhat
      different scenario  April 22, 2002
Reviewer:
      Connor  from UK....still
I would propose that the cost number *can* be useful in the following scenario.
Lets say

"select cols from blah where ..."

returns a full table scan with a cost of 23.  You suspect that an index scan may
have been a better option so you're curious at knowing why Oracle did not chose
it.  You could then explain:

"select /*+ index...*/ cols from blah where ..."

and see what cost Oracle comes up with (presumably higher than 23 in this
instance).  This can assist with exploring *why* the optimizer decision was
swayed in a certain way.  I hasten to add its not the actual value of '23' thats
important.

          Followup:

but how would the cost number be useful here?

It would only be useful in that you could make a guess as to how hints work (by
artificially inflating the costs of other access methods, making them "appear"
more efficient).

Jonathan Lewis's awesome book:

http://www.amazon.com/exec/obidos/ASIN/0201715848/
has a good discussion on the cost and how to use it actually.  Highly recommend
that book for that discussion (and the other pages as well!)

      Rating: 4
how is this
      possible  April 22, 2002
Reviewer:
      A reader
Hi Tom

I was trying to get the query statistics for this strange query I have that has
0 memory sort and 1 disk sort however I get this result

set autotrace traceonly statistics

  1  SELECT *
  2  FROM age, acc
  3* WHERE age.c_age = acc.c_age

223722 rows selected.

Elapsed: 00:00:32.86

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

no statistics at all!

          Followup:

You ran it as sysdba or internal:

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from dual;

D
-
X

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

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

ops$tkyte@ORA817DEV.US.ORACLE.COM> @connect internal
sys@ORA817DEV.US.ORACLE.COM> set autotrace on
sys@ORA817DEV.US.ORACLE.COM> select * from dual;

D
-
X

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

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

sys@ORA817DEV.US.ORACLE.COM>

Don't run things as sysdba or internal.

      Rating: 5
yes I was
      running as internal  April 23, 2002
Reviewer:
      A reader
Hi Tom Didnt know that internal leads to these things

I have now succesfully get the stats and explain plan

SELECT *
FROM age, acc
WHERE age.c_age = acc.c_age

225559 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3292 Card=225453 Byt
          es=30661608)

   1    0   MERGE JOIN (Cost=3292 Card=225453 Bytes=30661608)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'ACC' (Cost=1509 Car
          d=225453 Bytes=15330804)

   3    2       INDEX (FULL SCAN) OF 'ACC_AGE_FK_I' (NON-UNIQUE)
           (Cost=655 Card=225453)

   4    1     SORT (JOIN) (Cost=1516 Card=133156 Bytes=9054608)
   5    4       TABLE ACCESS (FULL) OF 'AGE' (Cost=267 Card=133156
          Bytes=9054608)

Statistics
----------------------------------------------------------
          8  recursive calls
         25  db block gets
      37486  consistent gets
       3563  physical reads
          0  redo size
   55671392  bytes sent via SQL*Net to client
    1669532  bytes received via SQL*Net from client
      15039  SQL*Net roundtrips to/from client
          0  sorts (memory)
          1  sorts (disk)
     225559  rows processed

Why I have no memory sorts...?!?! And a disk sort

          Followup:

why not?

you have a table AGE that is sorted.  apparently age is bigger then your
sort_area_size and doesn't fit into memory.  Hence, we did a disk based sort for
it.

      Rating:
      3
  April 23, 2002
Reviewer:
      A reader
but I have always thought that sort_area_size is used, if not fit in it then the
sort is divided into sort runs and sort in disk so at least there must be a
memory sort :-?

          Followup:

There is a sort done for a query.

This sort can be done in one of two ways:

o totally in memory, no disk involved.
o not totally in memory, some disk is involved.

A sort is characterized as either being done:

o entirely in memory       sort (memory)
o NOT entirely in memory.  sort (disk)

As soon as a sort requires disk, it becomes the second type.  All sorts use
memory.  Sorts that only use memory are of type "sort (memory)" and sorts that
use 1 byte of disk at least are of type "sort (disk)"

      Rating:
      3
  April 23, 2002
Reviewer:
      A reader
This sort can be done in one of two ways:

o totally in memory, no disk involved.
o not totally in memory, some disk is involved.

A sort is characterized as either being done:

o entirely in memory       sort (memory)
o NOT entirely in memory.  sort (disk)

I guess my query is

o totally in memory, no disk involved.
o not totally in memory, some disk is involved.

so I should have 1 memory sort and 1 disk sort no? First sort in memory and the
rest in disk...

          Followup:

Why is this hard to understand?  I must not be saying it right.

This is the last I'll have to say on this:

a SORT is one of two types:

o memory -- meaning it NEVER NEEDED ANY DISK, AT ALL.
o disk   -- meaning it NEEDED SOME DISK

all sorts start in memory.  Some sorts need disk.  ONCE THEY NEED DISK they are
DISK SORTS.  until then they were memory sorts.  Once they touch the disk, they
cease being memory sorts.

      Rating:
      5
  April 23, 2002
Reviewer:
      A reader
My God!

Sir, I think it is pretty simple. If the query fits in memory it is a memory
sort. If it doesn't and spill out to disk then it is a disk sort. Oracle will
count it as a disk sort if you used atleast on byte on the disk ( doesnt matter
if everything else was sorted in memory).

Tom, you are just great.

      Rating:
      3
  April 23, 2002
Reviewer:
      A reader
Statistics
----------------------------------------------------------
         15  recursive calls
         52  db block gets
     861625  consistent gets
      36186  physical reads
          0  redo size
    4547686  bytes sent via SQL*Net to client
     415459  bytes received via SQL*Net from client
       3741  SQL*Net roundtrips to/from client
         95  sorts (memory)
          1  sorts (disk)
      56095  rows processed

The stats generated above shows that there were 95 sorts in memory and 1 to
disk. If a query can sort only in memory or only to disk, how do we explain
this?

          Followup:

How to explain this....

Like this:

we sorted 96 times during the processing of our query.
95 of those times, we did not need any disk to complete our sort.
Once, we did -- we had 1 disk sort.

Without the query, without plan (not that I want them) -- you cannot say more
(or less) based on the above.  You did 96 sorts.  1 used disk.  95 did not.
That's all.  It seems very clear to me...

scott@ORA817DEV.US.ORACLE.COM> set autotrace traceonly statistics;
scott@ORA817DEV.US.ORACLE.COM> set echo on
scott@ORA817DEV.US.ORACLE.COM> @test
scott@ORA817DEV.US.ORACLE.COM> select object_name, (select object_id from
(select * from all_objects order by object_id) where object_id = e.object_id and
rownum = 1 )
  2    from all_objects e
  3  /

20333 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
     216373  consistent gets
          9  physical reads
          0  redo size
    1002644  bytes sent via SQL*Net to client
     150830  bytes received via SQL*Net from client
       1357  SQL*Net roundtrips to/from client
      20333  sorts (memory)
          0  sorts (disk)
      20333  rows processed

scott@ORA817DEV.US.ORACLE.COM>

      Rating:
      5
Overflow  April 23, 2002
Reviewer:
      Igor  from France
Hi

 Is there a way to determine (after having disk sort)
 how much disk space process used or
 "here if we had this amount of sort_area_size it would
 be memory sort"

 or it's just test and try?

                                           Igor

          Followup:

You can use v$sort_segment and v$sort_usage to monitor these things from another
session.

      Rating: 5
How hard can it
      be?  April 23, 2002
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Why don't you throw in sort_area_retained_size and wait for the implosion?

You know, I started off single, but now I am married.  On my tax forms, I don't
show both single (memory sort) and married (disk sort).  A sort might have
started in memory but it used disk, so it is a disk sort only, not a memory sort
and a disk sort.  Now if I have only 1 sort, and it was a disk sort, would it
not make sense that I would have 0 memory sorts and 1 disk sort?

consider this:

SQL> create table scott.my_all_objects as
  2  select object_name, object_id, status from all_objects;

Table created.

SQL> set autotrace traceonly
SQL> select * from my_all_objects order by 2;

32296 rows selected.

Execution Plan
----------------------------------------------------------
          0
SELECT STATEMENT Optimizer=CHOOSE

          1                  0
  SORT (ORDER BY)

          2                  1
    TABLE ACCESS (FULL) OF 'MY_ALL_OBJECTS'

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
        192  consistent gets
        358  physical reads
          0  redo size
    1425639  bytes sent via SQL*Net to client
      88549  bytes received via SQL*Net from client
       2155  SQL*Net roundtrips to/from client
          0  sorts (memory)
          1  sorts (disk)
      32296  rows processed

SQL>

Look!  Only 1 sort and it is a disk sort!  I have an 8K block size and
sort_area_size is 524288.

      Rating: 4
Summarize
      v$sort_segment and v$sort_usage  April 23, 2002

Reviewer:  Rob  from Pittsburgh, PA
Tom:

Could you expand upon:
>You can use v$sort_segment and v$sort_usage to monitor >these things from
another session.

How would you actually determine the size of a sort.
What are key columns in these view and what do they mean.

          Followup:

did you look in the reference guide?  all of the v$ views are documented there.

basically, you can join to this normally by session_addr/session_num using info
from v$session.  You can even add in sqladdr -- the sql address if you like.

Then you can get a count of the blocks allocated to the sort for that user and
query (or just the user).

      Rating: 5
why
      ?  January 07, 2003
Reviewer:  Fenng
       from China
Hi ,Tom
Can you explain why internal or sysdba  get the following result ?

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

I have read some oracle document about it ,but can not get a good explain.Can
you explain it for me ?
I only want to know why .
Maybe this is a very very  naive question. :)
Thanks a lot!

          Followup:

because you should use internal and sysdba for starting up, shutting down,
recovery maybe and not much more.

they are special.
they are magical.
they are different.

don't use them.
don't test with them.
don't create things in their schema.
don't modify existing things in their schema.

get in the habit of doing things with your OWN account -- don't use sys, system,
outln, mdsys, ctxsys, etc etc, etc.  they are ours - special.  don't mess with
them.

      Rating: 5
Guys! please,
      try to think! before posting tons of primitive
      followup's  January 07, 2003
Reviewer:
      A reader
Tom, you are exceptionally patient.

      Rating: 5
Toms
      Office  January 07, 2003
Reviewer:  Mr Angry
       from UK
Glad I don't sit anywhere near Tom in the Office, when
he lets off steam !

I agree you are *too* patient, start venting some anger... it would make great
reading :D

(only joking!)

      Rating:
      5
  January 07, 2003
Reviewer:  Fenng

Dont' angry. :)

Thank you !

    Copyright © 2003 Oracle Corporation, All rights reserved.


Recent Archives Advanced Search FAQ by Category Most Popular Hot Articles Your Questions [ Listing ]
Subject | Article | Last Updated (East coast time)
More on space estimation | article only | Jan 08, 2003 01:49 pm
UTL_FILE -- can I write to the "front" of an existing file (no, you cannot) | article only | Jan 08, 2003 02:17 pm
Partitions with LMTs | article only | Jan 08, 2003 03:06 pm
To drop columns from large tables. | article only | Jan 08, 2003 06:27 pm
storing and retrieving of rich text format data in the database | article only | Jan 09, 2003 01:01 pm
application tuning | article only | Jan 09, 2003 01:56 pm
automatically generate a password | article only | Jan 09, 2003 07:02 pm
View Definitions | article only | Jan 09, 2003 07:07 pm
lobs | article only | Jan 10, 2003 07:15 am
Block checking | article only | Jan 10, 2003 07:28 am
All Parent - Child tables in the database | article only | Jan 10, 2003 07:32 am
dbms_flasback | article only | Jan 10, 2003 09:16 am
Row Space for tables with LOBS | article only | Jan 10, 2003 09:17 am
Configurable cancel command | article only | Jan 10, 2003 09:20 am
Deleting old records | article only | Jan 10, 2003 09:29 am
Verify value exist in table... | article only | Jan 10, 2003 10:03 am
Analyze temporary tables for using CBO | article only | Jan 10, 2003 11:25 am
Operation in DECODE ??? | article only | Jan 10, 2003 12:24 pm
Lobs or Longs | article only | Jan 10, 2003 12:32 pm
dbms_debug package - reg | article only | Jan 10, 2003 02:26 pm
bind variables in DDL with execute immediate | article only | Jan 10, 2003 03:49 pm
Blob, Clob | article only | Jan 10, 2003 04:18 pm
SQL join question. Is there a better way? | article only | Jan 10, 2003 04:26 pm
Tune "UPDATE" query | article only | Jan 10, 2003 04:26 pm
Extract >32k data in single field of XML document | article only | Jan 10, 2003 06:58 pm
Creating an unusable index | article only | Jan 11, 2003 08:14 am
UPDATE PRIMARY KEY | article only | Jan 11, 2003 08:25 am
ref cursor performance | article only | Jan 11, 2003 08:34 am
Better way to write Query | article only | Jan 11, 2003 08:38 am
Which is better "IF" or "OR" | article only | Jan 11, 2003 02:11 pm
DBMS_SESSION.SET_CLOSE_CACHED_OPEN_CURSORS | article only | Jan 12, 2003 02:54 pm
PL/SQL buffer overflow, limit 1000000 bytes | article only | Jan 12, 2003 07:16 pm
problems in pass arguments | article only | Jan 12, 2003 08:47 pm
Nologging | article only | Jan 13, 2003 07:54 am
Transaction and SCN | article only | Jan 13, 2003 08:00 am
Is there a tool to convert Sybase tables into Oracle tables? | article only | Jan 13, 2003 08:13 am
What privilege should I have to Create such a view? | article only | Jan 13, 2003 09:31 am
Practical Use For AnyData Type | article only | Jan 13, 2003 10:25 am
scalable design for logging hits | article only | Jan 13, 2003 12:30 pm
Database migration from earlier versions of oracle in windows platform to oracle 9i on linux platform | article only | Jan 13, 2003 01:07 pm
Required privilege | article only | Jan 13, 2003 02:01 pm
Cursors versus REF Cursors versus NDS | article only | Jan 13, 2003 02:50 pm
How to combine the result set from two ref cursors. | article only | Jan 13, 2003 07:04 pm
Drop RBS Tablespace | article only | Jan 13, 2003 08:31 pm
CPU Utilization | article only | Jan 14, 2003 07:35 am
Calendar | article only | Jan 14, 2003 07:37 am
using logminer placeholders | article only | Jan 14, 2003 07:43 am
Locking and Concurrency | article only | Jan 14, 2003 08:15 am
Generating scripts of views | article only | Jan 14, 2003 10:47 am
Physical Column Storage Sequence | article only | Jan 14, 2003 10:53 am
Parsing ratio | article only | Jan 14, 2003 12:40 pm
writing to a delelted datafile | article only | Jan 14, 2003 08:32 pm
Returning result set from stored procedure over a database link | article only | Jan 14, 2003 08:35 pm
row(s) 1 - 53 of 53
January 08, 2003 Randy -- Thanks for the question regarding "More on space estimation", version 8.1.6
You Asked
I currently have a table with <10000 rows that is going to be loaded with around
55 million rows. So I've already got some representative data, I've
analyzed it, and am using the rows-per-block method to determine whether the
pctfree/pctused needs to be adjusted.

The questions:

1)
Why would there be widely varying numbers of rows per block--are the very small
ones blocks that aren't full yet?

2)
In your previous answers, you haven't emphasized taking PCTFREE into account
when calculating total space (disk) needed for a segment.  Is there a way to do
this, in case I DO have to adjust the PCTFREE on my table, or would you say that
it is therefore not representative?

3)
I concluded from testing pctfree values (using a locally-managed TS) that, as
PCTFREE increases, the % of overhead within a block decreases.  It seems to be a
function of the number of records in the block.  Is this because there are less
records to keep track of?  Would this be the case for a dictionary-managed TS?

Thanks so much!!
and we said...
1) I cannot answer that without seeing your data (or being aware of what it
looks like) and understanding how you loaded it.   Having the actual create
table you used would be very useful as well.  Consider:

create table t
( x1 int,
  x2 int,
  x3 int,
  x4 int,
  x5 int,
  x6 int,
  x7 int,
  x8 int,
  x9 int,
  x10 int,
  y1  varchar2(100),
  y2  varchar2(100),
  y3  varchar2(100),
  y4  varchar2(100),
  y5  varchar2(100)
)
/

begin
    for i in 1 .. 10000
    loop
        insert into t
        values (dbms_random.value( 1, 10000 ),dbms_random.value( 1, 10000 ),
                dbms_random.value( 1, 10000 ),dbms_random.value( 1, 10000 ),
                dbms_random.value( 1, 10000 ),dbms_random.value( 1, 10000 ),
                dbms_random.value( 1, 10000 ),dbms_random.value( 1, 10000 ),
                dbms_random.value( 1, 10000 ),dbms_random.value( 1, 10000 ),
                rpad( '*', dbms_random.value(1,100), '*'),
                rpad( '*', dbms_random.value(1,100), '*'),
                rpad( '*', dbms_random.value(1,100), '*'),
                rpad( '*', dbms_random.value(1,100), '*'),
                rpad( '*', dbms_random.value(1,100), '*')
               );
    end loop;
end;
/

select avg(cnt), min(cnt), max(cnt)
from (
select dbms_rowid.ROWID_BLOCK_NUMBER(rowid), count(*) cnt
from t
group by dbms_rowid.ROWID_BLOCK_NUMBER(rowid)
)
/

the output from the query was:

  AVG(CNT)   MIN(CNT)   MAX(CNT)
---------- ---------- ----------
23.6406619          6         27

So, I got from 6 to 27 rows/block with an average of about 23.6 (this counting
of rowids ONLY works properly when you know the row will fit on a block -- and
you have just done inserts, no updates -- there is NO chaining going on here)

In my case, it is due to the fact that the number 1 takes less storage then 9999
and the string '*' takes less room then '*******************'  So sometimes I
get more rows/block then other times.

2) Well, if you do it the way I do it -- it need not be emphasized.  I say to
create the table, load it up and measure it.  If you create the table with the
pctfree you are actually going to use - then it will already be taken into
consideration.  If you change the storage characterstics AFTER the fact -- then
the table will grow to be a different size then your estimate.  Nothing can take
that into consideration.

In order to see what sort of utilization you would get with different pctfrees
-- you would be best off playing with different values.

pctfree 10:
  AVG(CNT)   MIN(CNT)   MAX(CNT)
---------- ---------- ----------
23.6406619          6         27

pctfree 20:
  AVG(CNT)   MIN(CNT)   MAX(CNT)
---------- ---------- ----------
21.1864407         12         26

pctfree 30:
  AVG(CNT)   MIN(CNT)   MAX(CNT)
---------- ---------- ----------
18.3823529         15         21

pctfree 40:
  AVG(CNT)   MIN(CNT)   MAX(CNT)
---------- ---------- ----------
15.6494523         12         18

so in my case (different data each time) it looks like I lose 2/3 rows for each
10% free I add/block.

3) yes, the row directory would be smaller.  The format of a block in an LMT is
the same as when in a dictionary managed one.
Review & Followup
Rating: 5
Great detailed information August 29, 2001
Reviewer: Randy from Texas
My records will fit into a block, were inserted (no updates or chained rows), so
I think it is the latter point (the fact that some rows are just larger).  I
guess that is kind of a 'duh', isn't it?  Thanks.  All your information is
great.

Rating: 4
row directory January 06, 2003
Reviewer: Ram from india
in the concepts guide ( data blocks overview ), its mentioned that the row
directory maintains the addresses for all the "actual row pieces".
now:
1) this implies that if the table has huge no of rows then the row directory
will increase ( and so the block overhead - row dir being part of block overhead
).
why isnt the address part stored along with the rows actual data ?
2) does this mean that  when row chaining occurs the pointer to the chained row
in the "new" block is also stored in the row dir. ?
3) is the row data part of the block scanned "only" for getting the actual data
? for the rest like count() etc only the overhead is scanned ?

Thanks for the wonderful work. Happy New Year.

Followup:
1) umm, that would be like have house numbers randomly assigned and no telephone
books.  How exactly would you find the row on the block?

It does not imply that if the table has huge number of rows....  What if the row
is 1000 bytes and you have an 8k block.  Say you get 8rows/block.  Ok -- row
directory = block thing = 8 slots on a block.  It is when you have tiny rows
that a row directory will be "full" on a block but again it is a BLOCK thing,
not a table thing.

You get a rowid - it has FILE/BLOCK/SLOT on block.  file + block tells us what
block to read.  slot on block tells us what row directory entry to read to find
the row on the block.  "overhead" is a rather subjective term here as well --
overhead is generally things you can get rid of, this you obviously cannot if
you want to be able to find the rows again!

2) the pointer to the chained row is in the block itself.

3) depends on the query

Rating: 4
but not quite clear... January 07, 2003
Reviewer: Ram from India
the part about 'File/block/slot' part is fine. but the row directory part is
still confusing me.
pls correct me if my understanding is wrong.
from rowid the file->block->slot-> is determined. then row directory is read.
the row directory has an entry pointing to the row in the block that has the
actual data.
ok so far ?
so this means that the more the number of entries (records), the more the no of
entries in the row directory, isnt it ?
i guess this is what you meant by the "row directory will be full when there are
lots of tiny rows" .?

Followup:
there will be a row directory entry for each row -- of course.

However, this is pure linear math here -- 1:1.

It is like saying "isn't it true that the more rows you have, the more blocks
you have".  of course.

I guess I'm missing the point?

Rating: 4
row dir January 09, 2003
Reviewer: Ram from India
i guess i am the one who missed the point :)
because you mentioned "it does not imply..." for my question no.1.
i didnt frame my question well, because the way i visualize this happening is a
bit vague.
so now, back to concepts guide.

January 08, 2003 Iceymole -- Thanks for the question regarding "UTL_FILE -- can I write to the "front" of an existing file (no, you cannot)", version 3.1
You Asked
The UTL_FILE utilty in Oracle Web Server Applications
helps to write or read a flat file in the UNIX box, where it is
installed.

There are options to read, write and append. If I have to insert

a line everytime at the beginning of the file I am writing into,

how can I accomplish it ? ie. I want to be able to append into
a file but not at the end of file but beginning of the file.

Thanks in advance.
and we said...
The only way to do that for any file API (in C, java, VB, etc,
etc, etc) is to rewrite the entire file from the beginning.  It
would be quite expensive to perform.  You would have to open the
old file, read it all in, close it, open it again for W, write
the one new line and then all of the old lines.

OS files just work like that -- you can overwrite data in them,
you can add to the end of them, you cannot "insert" into them as
you would like to do.

This is one of those times you should ask youself "can i change
the requirement" ;)
Review & Followup
Rating: 5
Rewriting some records January 07, 2003
Reviewer: Kalita from Bristol,UK
Hi,
  What if I want to update some records in a file after reading and validating
it. Do I have to follow a similar approach,like opening the file in read mode,
store it in a pl/sql table with the updated data and write back into the file
after closing and reopening it.
Is there any read-write mode which would allow me to read and rewrite the same
file with a single file handle?

Thanks,
Kalita

Followup:
Not with UTL_FILE, no.

You would need to rewrite the entire file.

January 08, 2003 -- Thanks for the question regarding "Partitions with LMTs", version 8.1.7.1.0
You Asked
Hi,

Does LMT creation requires more space compare to DMT?
Or what are the extra parameters need to be considered when considering for LMT
when doing DB Sizing?

For D/W environment, is it feasible to have LMTs than DMTs!!!

Please share something on these.

Thanks and Regards,
Jitendra
and we said...
LMTs -- if you are using system managed extents, just be aware we use the first
64k of a file for bitmaps and that the NEXT if you use autoextend should be 8m.

if you use uniform extents, be aware that the file size should be

N * uniform_size + 64k

where you pick N to be whatever.  We need the extra 64k for the bitmap.  Make
the NEXT if you use autoextend to be the uniform_size.

Do not even CONSIDER a dmt, just don't go there.  Not only are LMTs 'feasible',
they should be your only consideration.  Do not use DMT's anymore, they are
legacy, obsolete, going the way of the dodo bird.
Review & Followup
Rating: 4
What about header information? January 06, 2003
Reviewer: A reader
Tom,
Don't we need to keep +one block for header information?
Thanks

Followup:
64k - bitmap header overhead for LMTs

one block per segement overhead just like dictionary managed objects.

Rating: 4
Question about autoextend January 06, 2003
Reviewer: William from Australia
Tom,

I didn't understand what you meant by:

> and that the NEXT if you use autoextend should be 8m

Can you explain why the NEXT should be 8M? ie if I use a
uniform size of say 16M, shouldn't the NEXT be a multiple
of 16M?

Thanks

Followup:
...
LMTs -- if you are using system managed extents, just be aware we use the
first 64k of a file for bitmaps and that the NEXT if you use autoextend should
be 8m.
.......

if you use uniform extents, be aware that the file size should be

N * uniform_size + 64k

where you pick N to be whatever.  We need the extra 64k for the bitmap.  Make
the NEXT if you use autoextend to be the uniform_size.
.......

8m = system managed.

Uniform size = Uniform size.

Rating: 4
Header Information January 06, 2003
Reviewer: A reader
Tom,
When I create objects in LMTs, the extent size is automatically taken as what is
defined in LMT. Does this mean the while defining LMT with uniform extents, the
uniform_size should be uniform_size+one block?

Followup:
When definining with uniform extents the uniform size should be whatever you
want the extent size to be, nothing more, nothing less.

When creating FILES for these tablespace (uniform sized LMTs) make sure the FILE
is sized:

  N * uniform_size + 64k

where N is some integer you pick, uniform_size is the extent size you picked and
64k is a constant.

Rating: 4
Is this true for temporary tablespace also? January 07, 2003
Reviewer: A reader
Hi Tom,
Page 66 of your book expert-one-on-one...
Creating temporary tablespace does not contain +64k.
Also,
On page 54, you have mentioned as
. Files - we will go through set of five files...
But then you mentioned only four files as parameter, data, temp and redo log
files.

Followup:
page 66 -- look at the uniform size.  I said make the file be:

N*uniform_size + 64k

It is.  n=79 ;)   (got lucky on that one didn't I...)

If the extent size IS 64k -- anything that is divisible by 64k is OK.

The outline of chapter 2 is:

Architecture
The Files
   Parameter files
   Data files
   Temp file
   Control Files  <<<=====
   Redo log files

Rating: 4
Autoextend question with LMT January 07, 2003
Reviewer: A reader
Tom,

When a datafile auto-extends does it create another 64K
bitmap for the newly allocated space or does it just
rebuild the old bitmap?

Thanks

Followup:
64k/file.  there are a lot of bits in 64k to manage the free/allocated extents.
It can do strange things with DMTs converted to LMTs (i don't recommend
those)...

Rating: 5
The Book. The Temp. - what is the question actually ? January 08, 2003
Reviewer: A reader from Asteroids belt
To gentlement who wrote
---------
>Hi Tom,
>Page 66 of your book expert-one-on-one...
>Creating temporary tablespace does not contain +64k.
>Also,
>On page 54, you have mentioned as
>. Files - we will go through set of five files...
>But then you mentioned only four files as parameter, data, >temp and redo log
>files.
--------

What did you mean saying: "Creating temporary tablespace does not contain +64k."
??

Did you expected that SIZE clause will be like this:
SIZE 5.0625M or like this: SIZE 5308416 ? (these are both  (5M + 64K) in
different presentation)

I have found nothing on page 54 about FIVE files. The page mentions only five
databases which possibly could be installed on Tom's disk :)

In the book ALL the five files are mentioned:
Parameter - at page 60
Data - at 62
Temp - at 65
Control - at 66
Redo - at 66

Could you explain more exactly what is confusing you ?

Best Regards

January 08, 2003 Amit -- Thanks for the question regarding "To drop columns from large tables.", version 8.1.6.1
You Asked
Tom -

I am required to drop a lot of columns from a few tables off of a PROD schema.
The tables are big tables (for us) consisting of 13 Mil. rows each and
already have a large number of columns in them.
The tables do have referential integrity constraints and a lot of indices.
Columns getting dropped are not indexed.

I want to know what could be the best way to go about dropping these columns
from those tables:

a. Do I need to create intermidiate tables using "create table as select " and
then drop the original tables and then create indexes and constraints on the new
table and then rename it to original.

b. I drop column one statement at a time using "alter table drop column ..."
statement?
c. I drop a series of columns using "alter table drop (...) " statement?
d. I set the columns "unused" and not worry about the space they already
hogged? We are looking at reclaiming the space...

I also need to know the side-effects like "fragmentation" and "undo size"
We are "still" using dictionary managed tablespaces.

Do I need to use checkpoint option if i decide to use drop column ?

Does dropping columns leave the table fragmented or it rewrites it?

The downtime we can take is about 4 hours including some other release stuff.

Let me know what you think would be the best option keeping in perspective :
fragmentation,time and space usage.

Pls. elaborate any side effect of using "set unused..".

Any help would be highly appreciated.

Thanks,
Amit.
and we said...
if the goal is to reclaim space then the choice would probably be

e) set unused all columns and then DROP with checkpoints.

that'll rewrite each block (it will not move the rows off of the block they are
on -- that would force us to rebuild all indexes as well if we did that).  so
you should end up with more blocks in the freelists after you are finished.

the table will be very much as it was before, just each block will have more
free space and (depending on your pctused of course).

Here is an example (search for dbms_space on my web site for show_space):

ops$tkyte@DEV816> create table t ( x int, y char(2000), z char(2000) );
Table created.

ops$tkyte@DEV816> insert into t select rownum, 'x', 'y' from all_objects where
rownum < 1001;
1000 rows created.

ops$tkyte@DEV816> exec show_space( 'T' )
Free Blocks.............................5
Total Blocks............................1024
Total Bytes.............................8388608
Unused Blocks...........................19
Unused Bytes............................155648
Last Used Ext FileId....................7
Last Used Ext BlockId...................203209
Last Used Block.........................45

PL/SQL procedure successfully completed.

that shows the table is using 1005 blocks and 5 of those blocks are on the
freelist

  1* alter table t set unused column x
ops$tkyte@DEV816> /

Table altered.

ops$tkyte@DEV816> alter table t set unused column y;

Table altered.

ops$tkyte@DEV816> exec show_space('T')
Free Blocks.............................5
Total Blocks............................1024
Total Bytes.............................8388608
Unused Blocks...........................19
Unused Bytes............................155648
Last Used Ext FileId....................7
Last Used Ext BlockId...................203209
Last Used Block.........................45

PL/SQL procedure successfully completed.

that shows that setting the columns unused didn't do anything spacewise -- we
are still at the same point

ops$tkyte@DEV816> alter table t drop unused columns checkpoint 500;

Table altered.

ops$tkyte@DEV816> exec show_space('T')
Free Blocks.............................1004
Total Blocks............................1024
Total Bytes.............................8388608
Unused Blocks...........................19
Unused Bytes............................155648
Last Used Ext FileId....................7
Last Used Ext BlockId...................203209
Last Used Block.........................45

PL/SQL procedure successfully completed.

That shows that we now have 1004 blocks on the free list (pretty much EVERY
block is).
Review & Followup
Rating: 5
DBA May 10, 2001
Reviewer: Pauline from U.S.A.
Good answer with the example.

Rating: 3
May 11, 2001
Reviewer: Amit Barve from Chatilly, VA, USA.

Rating: 3
checkpoint 500 September 18, 2001
Reviewer: A reader
Tom,

How did you come up with the figure 500 ?

Followup:
I "made it up".  It was just a demo.  500 was about half of 1005 which is the
number of blocks in the table. It was "half way".  It is not a rule of thumb, it
is not guidance -- it was just a demo.

Rating: 4
Cool - but why... September 18, 2001
Reviewer: A reader
In the Example, why the total bytes remain the same size?

Rating: 4
Ramification for checkpoint 500 September 18, 2001
Reviewer: A Reader from USA
From the Manual
"
if this statement is interrupted
after a checkpoint has been applied, the table remains in an
unusable state. While the table is unusable, the only operations allowed on it
are DROP TABLE, TRUNCATE TABLE, and ALTER TABLE DROP COLUMNS CONTINUE
Submitting DROP COLUMNS CONTINUE this statement while the table is in a valid
state results in an error
"

Seems like must be used with great care. Any alternatives

Thanks

Followup:
I don't see the need for "great care".  All it is saying is that if the instance
fails, you must resume this command from where it left off when you restart --
thats all.

To me that would be preferable if I was halfway through my 100gig table when the
failure occurred.  Rather then looking at the glass being half empty here, I'm
looking at it as being half full.  I only have 1/2 the work to finish -- I
didn't have to ROLLBACK the 1/2 of the work I did.  I just pick up EXACTLY where
I left off.

Rating: 5
How come Total Blocks = 1005? September 27, 2001
Reviewer: PAwanputra from NJ, USA
Tom,
   Excellent example. But U say
that shows the table is using 1005 blocks and 5 of those blocks are on the
freelist

But I see total blocks =1024. Is this a typo??

Followup:
No, but could be more clear........ The numbers are:

Free Blocks.............................5
Total Blocks............................1024
Total Bytes.............................8388608
Unused Blocks...........................19

When I said "using" i meant "really really using, not allocated, using".

1005 = 1024-19

there are 19 UNUSED blocks.

Rating: 5
monitoring drop column progress September 10, 2002
Reviewer: A reader
Hi Tom

How can we monitor how many rows drop column command has progressed?
I thouught of watching checkpoint (setting checkpoint 1000) per session but I
cant find any view that shows that

thank you

Followup:
v$session_longops is useful for monitoring any long running operation.

I kicked off an alter table drop column on a 1.4 million row table.  Then,
waited a bit and queried v$session_longops from another session -- using "where
sid=<sid of the session doing the drop>"

It showed me:

SID                           : 8
SERIAL#                       : 3928
OPNAME                        : Rowid Range Scan
TARGET                        : BIG_TABLE.BIG_TABLE
TARGET_DESC                   :
SOFAR                         : 3603
TOTALWORK                     : 19630
UNITS                         : Blocks
START_TIME                    : 10-sep-2002 07:34:13
LAST_UPDATE_TIME              : 10-sep-2002 07:35:56
TIME_REMAINING                : 458
ELAPSED_SECONDS               : 103
CONTEXT                       : 0
MESSAGE                       : Rowid Range Scan:  BIG_TABLE.BIG_TABLE: 3603 out
of 19630 Blocks done
USERNAME                      : BIG_TABLE
SQL_ADDRESS                   : 829556CC
SQL_HASH_VALUE                : 2608864360
QCSID                         : 0
-----------------

PL/SQL procedure successfully completed.

Which shows how many blocks it has to process total, how many its done so far,
how long it has been running, a guesstimate as to when it'll be done....

Very useful.

Rating: 3
drop /unused columns January 08, 2003
Reviewer: Jack from California USA
Tom,
We have a production db and we want to keep the downtime as min. as possible for
db changes.
a) is it better to set some columns to unsed and then drop OR directly drop the
columns (appx.5 million rows). Which is faster ( I know unused takes less time
than drop)? I'm not sure how much time it takes to drop unused columns?
can you provide some numbers like
drop columns ...x time.
unused columns and drop unused.....y time.

b) can I drop unused columns when users are accessing the tables (online
customers).

c) If I keep unused columns does space usage will be still same or oralce won't
store data for unused columns.

thanks for your help.

Followup:
or how about

a) set them unused.  stop.  period.

otherwise -- benchmark -- it is what I would do (and only you have the hardware
you are running on).

My advice tho -- just set unused.

b) no, but you could use DBMS_REDEFINITION in 9i to accomplish that.

c) oracle will not RECLAIM existing used space.  Space will not be used for new
rows.

January 09, 2003 Leo -- Thanks for the question regarding "storing and retrieving of rich text format data in the database", version 8.1.7.0.0
You Asked
Hi Tom,
  How can i store and retrieve a rich text formatted data in the database?

Thanks for your help.
Leo.
and we said...
you would use a BLOB datatype.

"how" depends on the client you use -- the language/tool.  See:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/products.htm
and specificially:
http://otn.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/index.htm
the application developers guide for LOBS

and also:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/inter.817/index.htm
interMedia text -- so you can search, summarize, theme, categorize the documents
as well.
Review & Followup
Rating: 5
Thank you very much. January 09, 2003
Reviewer: Leo from Boston, MA

January 09, 2003 -- Thanks for the question regarding "application tuning", version 8.1.7
You Asked
Tom
Tom

I. Apart from sql tuning, what else is involved in application tuning.

II.While investigating into application for enhancing the performance, I see
that the following need to be investigated?

1.expensive sql
2.look into the views if any
3.look into indexes where applicable, and appropriate but missing.
4.use optimizer hints and force the use of indexes, as we know our data better.
5.Create partitioned tables where ever appropriate, i.e. for example history
tables where huge chunks of data is inserted and deleted.
6.Look into the possibility of creating index organized tables( where are they
most applicable ..a simple example please).
7.Create function based indexes( this I think is very important)
8.Creating indexes on foreign keys.

Can you tell me if my understanding is right, and add anything if I missed it ?

Bye
and we said...
I. architecture, logic, interfaces.

A bad architecture = slow performance.
Poor algorithms (logic) = slow performance.
Bad interfaces = slow performance (eg: passing XML between modules when a simple
subroutine call would have sufficed)

II.

1, 2, and 3 are all the same to me.

4. or set the optimizer_index_caching, optimizer_index_cost_adj init.ora
parameters.  I would avoid hints if at all possible.

5. sure

6. see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:627128171401
7. depends on the application, but yes it can be.

8. for OLTP apps, yes.

9. bitmapped indexes, interMedia for text searching, parallel query, re-design
your structures, materialized views, (the list is very long.  Oracle has a HUGE
toolbox to draw on).
Review & Followup
Rating: 3
Performance is very slow January 25, 2002
Reviewer: sivababu from Germany
Hello TOM,
   We have ERP module which is running under Oracle 8.1.6. It is working for all
customers except one customer with same database desgining.  Is it anything to
check in Linux server like CPU peformace or etc like u said?
(A bad architecture = slow performance.
Poor algorithms (logic) = slow performance.)

running server --- Oracle 8.1.6 on Suse linux 6.5
Cleint application --- Centura 1.5.1 & Oracle 7.3

So how i can check it this one and is there any command to check this things?.
Thanks and expecting your reply.

with regards,
sivababu

Followup:
I would start with statspack -- compare a report from a "successful" install
with this slow one.

Rating: 4
April 29, 2002
Reviewer: Sean from Atlanta
I have not been working on the APPS tuning. For other custom applications
designed by the shop, we find the bad parts, such as bad queries, we process the
changes in the shop. But what the process of changes, if we find bad queries in
Oracle APPS? My impression is  that Oracle does not recommend to modifications
of the APPS expect by Oracle patches.

Followup:
There are many ways to crack this

first, if you identify a poorly performing query, there is a good chance someone
else has and there is a patch.  Get patch, install patch.

Second, it could be the direct result of a customization (it is many times).  In
which case, you can fix it by modifying your customization.

Third, it could be a missing index or something like that.  They are easy enough
to add as part of a customization.

Lastly, it could be a "new problem", one that you need to alert support of so
they can develop a patch.

If you don't diagnose (find) the problem, you'll never get any "faster".

Rating: 5
application tuning", version 8.1.7 April 29, 2002
Reviewer: Jeff from Madison, Wi
What do you mean with your statement below?
<quote>

Bad interfaces = slow performance (eg: passing XML between modules when a simple
subroutine call would have sufficed)

</quote>

I will be working on an application soon that will use XML to generate letters
and would like to avoid this...

Followup:
Any technology, taken to an extreme, can be damaging.

Lets say you were building a web site, you were using language "X" to do this
(plsql, java, vb, whatever).

If instead of using parameters when you called a subroutine, someone had the
brilliant idea "hey -- lets make EVERYTHING a web service so we can enjoy the
ultimate in reuse", I would hazzard a guess that you would have the SLOWEST
performing system on the planet.

When thinking to yourself, you do not talk outloud right?  That would be
inefficient.  You just think (very fast).  Imagine if you limited yourself to
one form of communication -- talking ou loud.  Now to think, you have to have a
verbal conversation with yourself.  Painfully slow as compared to thinking.

So, use subroutines (methods, whatever) where appropriate.  Don't go crazy and
make everything in the world "XML" capable.  Identify your major integration
points (where data comes in from other sources, where data needs to go out to
other sources) and use xml there.  do NOT expose each and every function of your
application via XML, just the major "touch points".

That is what I meant (i know of a customer who demands EACH and EVERY function,
API, whatever be accessible via webservices from their COTS vendor.  I also know
of a customer that will not be buying COTS from anyone soon.  You can take
things to an illogical extreme sometimes)

Rating: 4
acronym. COTS -> Commercial Off the shelf April 30, 2002
Reviewer: JJ from Munich

Rating: 3
Application Tuning April 30, 2002
Reviewer: Shajan Joseph from Chennai, India

Rating: 5
What about tuning tools ? January 09, 2003
Reviewer: D
Tom, I've read a lot of your discussions about tuning. You often underscore that
rules of thumb aren't good, and that tuning can't be done "automagically". I
understand we can't rule out the human factor. What do you think of such tools
as Oracle Expert ?

Followup:
they are good starting points but they work inside of a "box".

for example, they look at a query like:

REM Query to compare each emp hiredate with the prior hired hiredate

ops$tkyte@ORA920> select a.empno, a.ename, a.hiredate, b.hiredate prior_hiredate
  2    from emp a, emp b
  3   where to_char(b.hiredate,'yyyymmdd')||b.rowid =
  4            ( select max(to_char(hiredate,'yyyymmdd')||rowid)
  5                from emp c
  6               where c.hiredate < a.hiredate )
  7   order by a.hiredate
  8  /

OEM will tell you "hey, if you index such and such, this will run faster".
It'll be many years I expect before it would suggest -- "you know what, if you
did this:"

ops$tkyte@ORA920> select * from (
  2  select a.empno, a.ename, a.hiredate,
  3         (select max(hiredate)
  4            from emp b
  5           where b.hiredate < a.hiredate ) prior_hiredate
  6    from emp a
  7  ) where prior_hiredate is not null
  8  order by hiredate
  9  /

"That'll be better.  Or even BETTER might be:"

ops$tkyte@ORA920> select * from (
  2  select a.empno, a.ename, a.hiredate,
  3         lag(hiredate) over ( order by hiredate ) prior_hiredate
  4    from emp a
  5  ) where prior_hiredate is not null
  6  order by hiredate
  7  /

eg:

ops$tkyte@ORA920> set autotrace traceonly
ops$tkyte@ORA920> select a.empno, a.ename, a.hiredate, b.hiredate prior_hiredate
  2    from emp a, emp b
  3   where to_char(b.hiredate,'yyyymmdd')||b.rowid =
  4            ( select max(to_char(hiredate,'yyyymmdd')||rowid)
  5                from emp c
  6               where c.hiredate < a.hiredate )
  7   order by a.hiredate
  8  /

13 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (ORDER BY)
   2    1     FILTER
   3    2       NESTED LOOPS
   4    3         TABLE ACCESS (FULL) OF 'EMP'
   5    3         TABLE ACCESS (FULL) OF 'EMP'
   6    2       SORT (AGGREGATE)
   7    6         TABLE ACCESS (FULL) OF 'EMP'

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

ops$tkyte@ORA920> select * from (
  2  select a.empno, a.ename, a.hiredate,
  3         (select max(hiredate)
  4            from emp b
  5           where b.hiredate < a.hiredate ) prior_hiredate
  6    from emp a
  7  ) where prior_hiredate is not null
  8  order by hiredate
  9  /

13 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (ORDER BY)
   2    1     FILTER
   3    2       TABLE ACCESS (FULL) OF 'EMP'
   4    2       SORT (AGGREGATE)
   5    4         TABLE ACCESS (FULL) OF 'EMP'

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

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from (
  2  select a.empno, a.ename, a.hiredate,
  3         lag(hiredate) over ( order by hiredate ) prior_hiredate
  4    from emp a
  5  ) where prior_hiredate is not null
  6  order by hiredate
  7  /

13 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   VIEW
   2    1     WINDOW (SORT)
   3    2       TABLE ACCESS (FULL) OF 'EMP'

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

So, while OEM might help you tune a problem -- it does so "in a box".  No
imagination, no creativity.  I find in order to tune a query -- I want to know
the QUESTION -- I don't want the query typically.  The query can confuse me
(confine me to a box, a way of thinking).

January 09, 2003 Madan -- Thanks for the question regarding "automatically generate a password", version
You Asked
Hello Tom,
here  I have created a table as shown below,can you please
give me a procedure for the auto system generation of password
and want to know the  probabality of that password being
occured.

CREATE TABLE USER_INFO
(Title            varchar2(9),
First_Name        Varchar2(30),
Last_Name        Varchar2(30),
Sex            Varchar2(7) check (sex in('MALE', 'FEMALE')),
Date_Of_Birth        Date,
Age            Number(2),
Address1        Varchar2(60),
Address2        Varchar2(55),
Address3        Varchar2(55),
City            Varchar2(30),
PIN_ZIP            Number(8),
State            Varchar2(35),
Country            Varchar2(50),
Phone_Home        Number(12),
Phone_Work        Number(12),
e_Mail_ID        Varchar2(75),
PKN             Varchar2(25) Primary Key,
COMPUTER_STATUS        VARCH(8),check (COMPUTER_STATUS
in('Y', 'N')));
and we said...
I use the following routine to generate random passwords for
people:

create or replace function generate_password (
    in_userid in varchar2, l_date in date
    )
    return varchar2
  is
    j                             number := 0;
    k                             number;
    str                           varchar2(30);
    result                        varchar2(30);
  begin
    if in_userid is null then
      return null;
    end if;
    str := substr (in_userid, 1, 4) || to_char (l_date, 'SSSS');
    for i in 1 .. least (length (str), 8)
    loop
      j := mod (j + ascii (substr (str, i, 1)), 256);
      k := mod (bitand (j, ascii (substr (str, i, 1))), 74) +
48;
      if k between 58 and 64 then
        k := k + 7;
      elsif k between 91 and 96 then
        k := k + 6;
      end if;
      result := result || chr (k);
    end loop;
    result := replace (result, '1', '2');
    result := replace (result, 'l', 'L');
    result := replace (result, '0', '9');
    result := replace (result, 'O', 'P');
    result := 'A' || substr (result, 2);
    return result;
  end generate_password;
/

You'd have to have the same first 4 digits and do it at the same
time of day to get a dup.
Review & Followup
Rating: 3
another way of doing this April 15, 2002
Reviewer: Jay from Scotland
Depending on your version of Oracle (this works in 9i) is to use DBMS_RANDOM
package

EG dbms_random.string('x',6)

Returns a 6 character alpha-numeric random string:

SQL> BEGIN
  2  DBMS_OUTPUT.PUT_LINE(DBMS_RANDOM.STRING('X',6));
  3  END;
  4  /
J3C3BF

PL/SQL procedure successfully completed.

Followup:
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec
DBMS_OUTPUT.PUT_LINE(DBMS_RANDOM.STRING('X',6));
D=MDO3

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter user ops$tkyte identified by D=MDO3;
alter user ops$tkyte identified by D=MDO3
                                    *
ERROR at line 1:
ORA-00922: missing or invalid option

I wouldn't use that.  It'll generate invalid passwords.

Rating: 5
2 quick ones April 16, 2002
Reviewer: J from MA
1. Why 4 S's?  why not 5 ?ie: sysdate,'SSSS'

2 How many times did you have to run the
DBMS_OUTPUT.PUT_LINE(DBMS_RANDOM.STRING('X',6));
before you got the invalid one ?

and
SQL> select to_date(sysdate,'SSSS') from dual;
ERROR:
ORA-01810: format code appears twice

3 I now know to_date should be to_char but why the
format code appears twice?

Thanks for all this good stuff!!

Followup:
1) it is just a seed.  it needs 8 bytes, you can use sssss if you like.

to_date expects a character string.  so that is like:

to_date( '01-jan-02', 'ssss' )

ss is the input format for seconds, sssss would be the input format for seconds
since midnight (you see ssss is seconds || seconds).

you have the ss format twice.  don't confuse that with sssss

Rating: 4
Duplicite values April 17, 2002
Reviewer: Stano from Slovakia
Hi Tom,

you said:

"You'd have to have the same first 4 digits and do it at the same time of day to
get a dup."

Even I think it is not necessary to avoid duplicate passwords at all costs,
using your method you'll will get the same password if you have the same first 4
digits and you are doing it at the same SECOND OF A MINUTE (e.g. if you have 61
users with the same first 4 digits, at least two of them will have the same
password). Thus, it is perhaps really better to use something like

str := substr (in_userid, 1, 4) || substr (to_char (l_date, 'SSSSS'), 2);

in your code.

Followup:
Fine, use whatever algorithm you like.  It is that easy.  See it however you
choose.  Seed it with random if you like.  seed it with the first three
characters and sssss.  whatever...

I think you meant substr (to_char (l_date, 'SSSSS'), 4)
                                                     ^^^ not two tho.

Rating: 4
January 09, 2003
Reviewer: John
Tom,

What is wrong with dbms_random.string?

My version is Oracle 8.1.7.3.4 on Windows.

The code for the function says:

------------------------------------------------
 -- Random string.  Pilfered from Chris Ellis.
 FUNCTION string (opt char, len NUMBER)
     RETURN VARCHAR2 is      -- string of <len> characters
     optx char (1)      := lower(opt);
     lo   NUMBER;
     rng  NUMBER;
     n    NUMBER;
     xstr VARCHAR2 (60) := NULL;
 BEGIN
     IF    optx = 'u' THEN    -- upper case alpha characters only
         lo := 65; rng := 26; -- ASCII 41 to 5A (hex)
     ELSIF optx = 'l' THEN    -- lower case alpha characters only
         lo := 97; rng := 26; -- ASCII 61 to 7A (hex)
     ELSIF optx = 'a' THEN    -- alpha characters only (mixed case)
         lo := 65; rng := 52; -- ASCII 41 to 5A and 61 to 7A (see below)
     ELSIF optx = 'x' THEN    -- any alpha-numeric characters (upper)
         lo := 48; rng := 36; -- ASCII 30 to 39 and 41 to 5A (see below)
     ELSIF optx = 'p' THEN    -- any printable characters
         lo := 32; rng := 95; -- ASCII 20 to 7E (hex)
     ELSE
         lo := 65; rng := 26; -- default to upper case
     END IF;
     FOR i IN 1 .. least(len,60) LOOP
         /* Get random ASCII character value in specified range */
         n := lo + TRUNC(rng * value); -- between lo and (lo + rng -1)
         /* Adjust FOR split range */
         IF    optx = 'A' AND n > 90 THEN
             n := n+6;               -- exclude ASCII characters 5B to 60
         ELSIF optx = 'X' AND n > 57 THEN
             n := n+7;               -- exclude ASCII characters 3A to 40
         END IF;
         xstr := xstr||chr(n);       -- Append character to string
     END LOOP;
     RETURN xstr;
 END string;
----------------------------------

I have a junk table with 6 rows for testing.

SQL> select dbms_random.string('u',40) from junk;

DBMS_RANDOM.STRING('U',40)
------------------------------------------------------
GCJFNGYGROJUGWAJWQAODDBBZKSVEJUYYSMCZKPV
SYGATQNJZZVTNNSGNBBRQSFPHWWGHQEPPHPWUPLY
KNPSXMEMUOTQCULZVDGAHRYOHLQICDEYPRJNEJCB
KQWKLPOKLWWBGKNRZFAZQJKJFDDHENAGTCKSHNZS
HMJJNRPOWJNUTMKPTZRQCPQEZGUOXMKGSJJDBAXC
MRCYZLWNCCIKDPXGTDXLEZVPTWYUMISFRUOCBMSC

6 rows selected.

SQL> select dbms_random.string('l',40) from junk;

DBMS_RANDOM.STRING('L',40)
------------------------------------------------------
jcybbhnlnhpfatrfnbsmuostwwmfcpjcgwnfhjbg
enzmbjlevgovalwfzlgexwoteclhcsmrpsywxeys
dbacafifitkefpaiqluecxwkznsypuxcnhhxcwym
vvmihmzfdtazcvcdpsbqafiwarcilzulggmkkwpd
hsyrqafdaiocchjwmpqtlurkankmrsgwlziqgozb
aqkogditwcdvrhnhfieitmaliwsivixmgahryzew

6 rows selected.

SQL> select dbms_random.string('a',40) from junk;

DBMS_RANDOM.STRING('A',40)
------------------------------------------------------
a\QSAiIdRZrtAtZRZGnCZMMEHX[aNdImmZdgNIGc
RdRlQVlgIJfWMUrhsksOYQ\JsagOHatlkLnjbEkQ
aLC[EGalQcAfZcmQQEFYBl^sU`MnZiD`CBi]WHBO
PhtaOlHGPsNdpUOBkOT^odXTdiOpjn_`lEEQdfFe
^Z]UJSbnQIGRlYcs]N]DfggGDSt_FTfUiqsECENt
qVNaRN_TSbTUXlkbm]tOgTOEEFVn]psZdjeCtQ[W

6 rows selected.

The "u" and "l" work fine but "a" does not.  It should return just alpha
characters only mixed case.

I would like to have something like this that returns both alpha and numeric
without any special characters.

Can you help?

Thanks

Followup:
...
V
W
X
Y
Z
[
\
]
^
_
`
a
b
c
d
...

[\]^_`

are in the middle, they are there by design apparently.  You'll have to take
their algorithm and DIY if you don't want those.

Rating: 4
Found the Problem January 10, 2003
Reviewer: John from St. Louis, MO
In the DBMS_RANDOM.STRING FUNCTION
------------------------------------------------
 FUNCTION string (opt char, len NUMBER)
        RETURN VARCHAR2 is    -- string of <len> characters
        optx char (1)      := lower(opt);
        lo   NUMBER;
        rng  NUMBER;
        n    NUMBER;
        xstr VARCHAR2 (60) := NULL;
    BEGIN

--------------------------------------------------
Notice optx is set to always lower case.

Now look at the part that adjust for split range it has the following:

--------------------------------------------------
            /* Adjust FOR split range */
            IF    optx = 'A' AND n > 90 THEN
                n := n+6;               -- exclude ASCII characters 5B to 60
            ELSIF optx = 'X' AND n > 57 THEN
                n := n+7;               -- exclude ASCII characters 3A to 40
            END IF;
            xstr := xstr||chr(n);    -- Append character to string
        END LOOP;
        RETURN xstr;
    END string;
-----------------------------------------------

I created a new package called my_random and modified the string function to
compare optx = 'a' and optx = 'x'.  It now works.

SQL> select my_random.string('a',40) from junk;

MY_RANDOM.STRING('A',40)
-----------------------------------------------------
lEoApcGblPocdAMHvNnKbnxkmvhnjlbDWqEJGASq
svrVJGmrrkapVqfXzVoZOieIGtlkBfxoJfglukcd
fIdFcHbWLtiVixyypdFLmkgAPHaQaIBfeSerpFeR
mUFjdvnyImwhUIeuPEVCQcBpLMMXDdQbxLuPfTyX
uXxuRgatWzuFifHygRHhYDaGxqwAbdSJcSxhJbji
LGdmIYaecBwEWYesOuvtJsCyRrnsHrhsXkThcAka

6 rows selected.

SQL>
SQL> select my_random.string('x',40) from junk;

MY_RANDOM.STRING('X',40)
-----------------------------------------------------
VV3Z1X1T8VICEQNKYBLBB5F0QBQZH9C5R0U8S3SL
P97JKAFF0YYV54IL7UZI7EY0VDK6TF5OLSUVFVFR
APVNEVQZUTR0HRLKNSQDPL3MRTQKZYDHJU5M9PZP
6C7A6R88LK95GIE2IM7ET4HEOXMQGLAU4XOWCCUC
E0PGHRK2GD13UG3U9T1NCJ2PIX2SV8FWMXUAW1G4
ANUA2DPDRPKPVX2WYR9C0ZZ2BWEQHKZJHVXEXR39

Thanks

Rating: 4
January 10, 2003
Reviewer: John
Tom,   Here is an updated function.  I is has the bug fix from above.  I have
also added a option to get a numeric and alpha (mixed case) string.

    FUNCTION string (opt char, len NUMBER)
        RETURN VARCHAR2 is    -- string of <len> characters
        optx char (1)      := lower(opt);
        lo   NUMBER;
        rng  NUMBER;
        n    NUMBER;
        xstr VARCHAR2 (60) := NULL;
    BEGIN
        IF    optx = 'u' THEN     -- upper case alpha characters only
            lo := 65; rng := 26; -- ASCII 41 to 5A (hex)
        ELSIF optx = 'l' THEN     -- lower case alpha characters only
            lo := 97; rng := 26; -- ASCII 61 to 7A (hex)
        ELSIF optx = 'a' THEN     -- alpha characters only (mixed case)
            lo := 65; rng := 52; -- ASCII 41 to 5A and 61 to 7A (see below)
        ELSIF optx = 'x' THEN      -- any alpha-numeric characters (upper)
            lo := 48; rng := 36; -- ASCII 30 to 39 and 41 to 5A (see below)
        ELSIF optx = 'r' THEN      -- any alpha-numeric characters (upper and
lower)
            lo := 48; rng := 68; -- ASCII 30 to 39 and 41 to 5A and 61 to 7A(see
below)
        ELSIF optx = 'p' THEN     -- any printable characters
            lo := 32; rng := 95; -- ASCII 20 to 7E (hex)
        ELSE
            lo := 65; rng := 26; -- default to upper case
        END IF;
        FOR i IN 1 .. least(len,60) LOOP
            /* Get random ASCII character value in specified range */
            n := lo + TRUNC(rng * value); -- between lo and (lo + rng -1)
            /* Adjust FOR split range */
            IF    optx = 'a' AND n > 90 THEN
                n := n+6;               -- exclude ASCII characters 5B to 60
            ELSIF optx = 'x' AND n > 57 THEN
                n := n+7;               -- exclude ASCII characters 3A to 40
            ELSIF optx = 'r' AND (n between 58 and 64
                                    OR n between 91 and 96) THEN
                n := n+7;               -- exclude ASCII characters 3A to 40
                                        -- and ASCII characters 5B to 60
            END IF;
            xstr := xstr||chr(n);    -- Append character to string
        END LOOP;
        RETURN xstr;
    END string;

January 09, 2003 Robert -- Thanks for the question regarding "View Definitions", version 8.1.7
You Asked
Hi Tom,

How can a view show one definition when you describe it, and another definition
when you select its text from dba_views ?

Following is an example...

====<cut>===
09:09:34 SQL> desc pbds.v_user_preferences
 Name                            Null?    Type
 ------------------------------- -------- ----
 MENU_OPERATION                  NOT NULL VARCHAR2(20)
 OP_RECORD_SEQ                   NOT NULL NUMBER(5)
 OWNER                                    VARCHAR2(12)
 PARAMETER_NAME                  NOT NULL VARCHAR2(60)
 PARAMETER_TYPE                           VARCHAR2(20)
 PARAMETER_VALUE                          VARCHAR2(256)
 DISPLAYED                                VARCHAR2(1)
 UPDATEABLE                               VARCHAR2(1)
 DISPLAYED_PARAMETER_ENTRY                VARCHAR2(1)
 PARAMETER_PROMPT                         VARCHAR2(50)
 VAL_FIELD_NAME                           VARCHAR2(60)
 NULL_ALLOWED                             VARCHAR2(1)
 LOV_FOR_VALIDATION                       VARCHAR2(1)
 VALIDATION_CODE                          VARCHAR2(20)
 HINT                                     VARCHAR2(150)
 GOTO_SCREEN                              VARCHAR2(20)
 DISPLAY_ORDER                            NUMBER(4)
 FORM_REPLACE_EXPRESSION                  VARCHAR2(60)
 SQL_REPLACE_EXPRESSION                   VARCHAR2(200)
 USER_INSERTING                           VARCHAR2(12)
 DATE_INSERTED                            DATE
 USER_UPDATING                            VARCHAR2(12)
 DATE_UPDATED                             DATE
 ROW_ID                                   ROWID

09:09:44 SQL> select text from dba_views
09:09:49   2  where owner = 'PBDS'
09:09:49   3  and view_name = 'V_USER_PREFERENCES';

TEXT
---------------------------------------------------------------------------------
---
SELECT o.menu_operation, o.op_record_seq, o.owner, p.parameter_name,
       p.parameter_type, p.parameter_value, p.displayed_user_preferences,
       p.updateable_user_preferences, p.displayed_parameter_entry,
       p.parameter_prompt, p.val_field_name, p.null_allowed,
p.lov_for_validation,
       p.validation_code, p.hint, p.goto_screen, p.display_order,
       p.form_replace_expression, p.sql_replace_expression,
       p.user_inserting, p.date_inserted, p.user_updating, p.date_updated,
p.rowid
FROM menu_operation_parameters p, menu_operations o
WHERE o.menu_operation = p.menu_operation
AND o.op_record_seq = p.op_record_seq
AND o.sts = 'A'
AND p.sts = 'A'
====<cut>===

Kindly notice the 7th and 8th columns in the describe and the text definition.
In the describe, they are 'DISPLAYED' and 'UPDATEABLE'.
But in the text definition they are 'displayed_user_preferences' and
'updateable_user_preferences'.

I understand that you could alias the columns in the view definition to change
the name of the column in the view... but in all my experiments, this aliasing
showed up in the view's text definition.

Can you please explain how the describe can show one thing but the view's text
definition shows something else?

Thanks,

Robert.
and we said...
whoever created the view named the columns explicitly and differently in the
create view statement itself:

ops$tkyte@ORA920> create view emp_view(a,b,c) as select empno, deptno, ename
from emp;
View created.

ops$tkyte@ORA920> desc emp_view
 Name              Null?    Type
 ----------------- -------- ------------
 A                 NOT NULL NUMBER(4)
 B                          NUMBER(2)
 C                          VARCHAR2(10)

ops$tkyte@ORA920> select text from user_views where view_name = 'EMP_VIEW';

TEXT
--------------------------------------------------------------------------------
select empno, deptno, ename from emp
Review & Followup
Rating: 5
That's it! January 09, 2003
Reviewer: Robert Wood from Memphis, USA
Thanks, Tom!

Rating: 4
how to correct view definition? January 09, 2003
Reviewer: A reader
Is there any easy way to transform it to

select empno a, deptno b, ename c from emp

?

Followup:
what do you mean "how to correct it"???

what is "wrong" with it?

If you want to know the columns in a view - goto user_tab_columns.

If you want to know the defining query -- go there.

There is nothing "wrong" and I don't now how you would "fix" it (cause it isn't
broken to me -- i don't know what you want "changed"?)

January 10, 2003 shah -- Thanks for the question regarding "lobs", version 8.3.5
You Asked
Hello Tom

      Hope u are fine.
I am new to oracle and this is the first time writing to u.

My questions is about lobs
it is written in the sql book that, LOB datatypes hold values called
locaters, that specify the location of the large objects that are stored out of
line (out isde the row).My question is that what do you mean by out of line,
deos it mean out of database or?
and we said...
It means it is not stored in the table with the other structured data.  Lets say
you have a 5 row table.  Each row in the table has a lob that is 2 gig in size.
By storing the LOB "out of line" -- meaning we store it off to the side, out of
the table, we can FULL SCAN or read any single row very fast (the rows -- the
structured data is very very small).  If we full scan the table, we don't read
10gig of data, we just read the 5 very small rows.  Only when you take the
locator and dereference it will we start to access the 2gig of data associated
with.

LONGS and LONG RAWS on the other hand are stored inline.  If you had that same
example with a LONG, a full scan would read 10gig of data.  A select * from t
where rownum=1 would read 2 gig of data.  Not so with LOBS.
Review & Followup
Rating: 3
December 26, 2001
Reviewer: Gururaj Kulkarni from Norcross,GA USA
You mean the with LOBs ..statment 'select * from t' will not read the LOB data.
How will it retieve the data ?

Followup:
It will read the LOB LOCATOR (a pointer).  It is not until you de-reference that
locator that we actually read the lob data.

Rating: 4
Thank you Tom December 27, 2001
Reviewer: Gururaj Kulkarni from Norcross , GA USA

Rating: 3
Doubts in LOBS January 09, 2003
Reviewer: balaji from India
Hi Tom
I have certain questions about Lobs.
1.When I delete a record from a table containing a lob column , what happens to
the lob data.does it just hang around or is the lob data also deleted.
Ie . If I say ,
Delete from emp where emp_id = 1001
if emp has a BLOB column call photo , what happenes to the data in the lob?
does it get erased or should we explicitly clean up .

2.When I copy one a lob to another lob

What happens to the old data .
Is a new lob Locator assigned to the lob column and a new area allocated for the
data or
is the old data overwritten and We still have the old lob Locator pointing to
the new copied data.

Followup:
1) it gets deleted

2) copy leaves data where it was and copies it.  just like copying a file.

January 10, 2003 Andre -- Thanks for the question regarding "Block checking", version 8.1.6
You Asked
Hi again !

I have asked this questions before, but maybe I was nor clear
enough. Would you please try to explain this to me, which is not
clear in the reference material ?

DB_BLOCK_CHECKSUM verifies only when block are written and read
from disk (physycal i/o). It seems to me DB_BLOCK_CHECKING
checks any modification to the block (in memory only, since
blocks can only be altered in the buffer cache). Am I right ?

Thanks again !
and we said...
first for the definitions:

DB_BLOCK_CHECKSUM determines whether DBW n and the direct loader
will calculate a checksum (a number calculated from all the
bytes stored in the block) and store it in the cache header of
every data block when writing it to disk. Checksums are verified
when a block is read only if this parameter is TRUE and the last
write of the block stored a checksum. In addition, Oracle will
give every log block a checksum before writing it to the current
log.

DB_BLOCK_CHECKING controls whether Oracle performs block
checking for data blocks. When this parameter is set to TRUE,
Oracle performs block checking for all data blocks.
When it is set to FALSE, Oracle does not perform block checking
for blocks in the user tablespaces. However, block checking for
the SYSTEM tablespace is always turned on. Oracle checks a block
by walking through the data on the block, making sure it is
self-consistent. Block checking can often prevent memory and
data corruption. Block checking typically causes 1% to 10%
overhead, depending on workload. The more updates or inserts in
a workload, the more expensive it is to turn on block checking.
Oracle recommends that you set DB_BLOCK_CHECKING to TRUE if the
performance overhead is acceptable.

So, the checksum will ensure that what was WRITTEN OUT is what
gets read back in.  It is useful to detect corruptions
introduced by IO operations (bad disk, bad cables, bad
hardware).  It will not detect that a block in memory is already
corrupt (bad memory chips, bad software, etc).  The checksum is
useful to find faulty disk operations.

The block checking is useful to verify that a block is
physically "correct" -- that it most likely is not corrupted.
If a varchar2 field on a block has a length > 4000 bytes -- we
know it is corrupt.  If the head of the block doesn't match the
tail, we know it is corrupt.  And so on.  Block checking
validates the integrity of the block.

The two parameters do different checks (checksum doesn't
validate only makes sure what we wrote is what we read, checking
doesn't ensure what we read is what we wrote but rather that
what we read was a VALID block) at different times.
Review & Followup
Rating: 3
these two are quite confusing January 09, 2003
Reviewer: A reader
Hi

Working with Oracle 8.1.7.4 and Suse Enterprise Server 7.

I have been doing some research about these two parameters forced by some block
corruptions we have faced (5 corruptions in last month and we dont have a clue
wheather it's physical or logical/software corruption). I visited Metalink, read
Oracle 8i backup & Recovery book by Velpuri and lastly visiting your site.

To start with when you said DIRECT Loader I guess you mean direct writes
bypassing the buffer cache am I correct?

Then quoting a setence from your reply:
"calculate a checksum (a number calculated from all the bytes stored in the
block) and store it in the cache header of every data block when writing it to
disk.
Checksums are verified when a block is read only if this parameter is TRUE and
the last write of the block stored a checksum."

I am confused by this sentence, do you mean when the block is read into buffer
cache and it's becomes dirty (modified) when it written back to disk a checksum
is written to the corresponding data block as well as the modified data? And
next time when this block is read again by some other server process, this
server process computes a checksum for the block and compared with the one
stored in the block?

Regarding DB_BLOCK_CHECKING I understand it checks for data block's integrity
but you said (quoting again)

"The block checking is useful to verify that a block is physically "correct""

However note.32969.1 states DB_BLOCK_CHECKING is a logical block checking
feature. Also in same note it states that when a corrupt block is found Oracle
tries to perform cache recovery and my question is what is cache recovery, if a
data block is badly written how can you make it "good" again? By reading the
redo log or what?
Then later in the note it states following: as of Oracle 8.1.6 onwards Oracle
reports ORA-00607 if block recovery fails and mark the block soft corrupted
however in note.77587.1 case 2 it says to perform sanity check in database set
events

event = "10210 trace name context forever, level 10"
event = "10211 trace name context forever, level 10"

which consequently equals DB_BLOCK_CHECKING in Oracle 8i and when block
corruption is found in cache prior to
writing the block to disk, it will signal an ORA-00600[3398] and will crash the
database instance. My question is which note is correct, crashing the instance
or marking the block soft corrupt if we set DB_BLOCK_CHECKING? (Or instance
crash only happens when set events)

Finally I have to quote again

"The two parameters do different checks (checksum doesn't validate only makes
sure what we wrote is what we read, checking doesn't ensure what we read is what
we wrote but rather that what we read was a VALID block) at different times."

It seems to me

checksum only makes sure what we read is what we wrote, I mean we cant write
non-dirty blocks to disk so how can checksum ensure what we read is what we
wrote? Isnt it the other way round? We must store some checksum first, imagine I
have never enable checksum but I decided now to enable it, consider I read block
X into memory if that block X is not modified will Oracle still perform a
checksum and make it dirty and writes to disk?

and block checking according to note.32969.1 this check is performed whenever
Oracle MODIFIES a data block, in other words performed when there is a write but
you suggest this feature ensures what we READ (not write) valid blocks. Isnt it
the other way round too?

As you can see I am quite confused by several explanations with these two
parameters and unfortunately I cant find much useful information on Metalink and
the documentations. What I want to do is determine wheather my corruptions are

media or memory or software

and I think enabling these parameters it can help me to discard the causes since
I am pretty sure we will have more corruptions.

My guess checksum will help me to discard media corruption and block checking
memory or software (we have ECC memory so i dont think it's memory anyway
though)

Thanks in advance

Followup:
direct -- yes, you got that right.

on the checksum -- if db_block_checksum is on -- then yes, DBWR when it flushed
the dirty block out will create a checksum.

ignore the term physically if that causes confusion.  The surrounding text
describes the process.  It checks that the data on the block "makes sense"

cache recovery would be in the cache, it would not use the redo or anything like
that.

that note seems to say the the _ parameter does the crashing -- not the events.
I would avoid the use of events and _ parameters as the two "normal" routes will
catch your issue.

as for the "read what we wrote" -- we:

a) compute checksum for dirty block
b) put checksum on block
c) write block to disk
d) later re-read block
e) recompute checksum and compare

That ensures what we READ is what we WROTE.  If the checksums don't match -- we
don't have what we wrote when we read it.

No, oracle will not do it to non-dirty blocks, we didn't WRITE them yet so the
phrase "ensures what we read is what we wrote" is accurate (to me anyway).

These will not help you determine "media" or "software".  They will just help
you proactively catch it when it happens.

To see if it is media, get some destructive read/write testing software on there
and beat the disks to death.

To me -- I read it totally backwards from you -- checksum failure would lead me
to blame hardware, block checking -- software (but not much more then hardware
really)

Rating: 4
so let me resume January 10, 2003
Reviewer: A reader
Hi

Thanks for your reply.

So I try to make a small conclusion

Checksum makes sure we read what we wrote, helps to detect media corruption

Block checking makes sure we read and write consistent blocks from and to disk,
helps to detect software or memory corruption

Correct :-?

Followup:
checksum makes sure we read what we wrote.  If the checksum is different upon
re-reading the block later, it points to an issue between Oracle and the
filesystem.  This could be hardware related (bad disk, bad cables, bad card) OR
it could be software related (bad interaction between Oracle and file system
drivers, logical volume manager, raid software, whatever)

block checking does a logical sanity check of the block.  it would catch logical
errors introduce by memory overwrites and such BUT it would also catch errors
introduced by media corruption.  It could be the disk is flipping a bit in a
length field causing logical corruption of the block (corruption in the data
structure of the block itself).

Neither points the finger clearly at one or the other.

I'll re-iterate.  If you want to get to the bottom of this quickly -- move the
datafiles from a disk that has experienced this corruption.  Get your hardware
vendor to supply some destructive low level read write testing software.  Beat
the disk to death and see if it passes or fails.

January 10, 2003 Srinivas -- Thanks for the question regarding "All Parent - Child tables in the database", version 8.x
You Asked
Hi Tom,

Can you please explain the way to get a list of all parent child relation in the
database.

The list should have the Grand parent as the first item and the last item will
be the grand child.

For Example,

Parent                          Child
----------------------------------------------------------------
 BDP_INFO                       BAA
 CONTR                          ABP
 CONTR                          B_INCE
 CONTR                          BDP_INFO

BDP_INFO is the parent to BAA and also Child to CONTR. So the list should have
Contr ---------> BDP_info
BDP_info ------> BAA

like wise...

This list should be for all tables that i have access
and we said...
as long as you DO NOT have any self referencing integrity:

create table emp ( empno int primary key, mgr int references emp(empno) );

this will work:

we need to use a temporary table to do this easily -- we cannot connect by a
join so we need to copy user_constraints:

tkyte@TKYTE816> create table p ( x int primary key );

Table created.

tkyte@TKYTE816> create table c1 ( x primary key references p );

Table created.

tkyte@TKYTE816> create table c2 ( x primary key references c1 );

Table created.

tkyte@TKYTE816> create table c3 ( x primary key references c2 );

Table created.

tkyte@TKYTE816> create table c4 ( x primary key references c2 );

Table created.

tkyte@TKYTE816>
tkyte@TKYTE816>
tkyte@TKYTE816> create global temporary table temp_constraints
  2  as
  3  select a.table_name,
  4         a.constraint_name pkey_constraint,
  5             b.constraint_name fkey_constraint,
  6             b.r_constraint_name
  7    from user_constraints a, user_constraints b
  8   where 1=0
  9  /

Table created.

tkyte@TKYTE816> alter table temp_constraints modify fkey_constraint NULL;

Table altered.

tkyte@TKYTE816>
tkyte@TKYTE816>
tkyte@TKYTE816> delete from temp_constraints;

0 rows deleted.

tkyte@TKYTE816>
tkyte@TKYTE816> insert into temp_constraints
  2  select table_name, constraint_name, null, null from user_constraints where
constraint_type
  3  = 'P';

5 rows created.

tkyte@TKYTE816>
tkyte@TKYTE816> insert into temp_constraints
  2  select a.table_name,
  3         a.constraint_name pkey_constraint,
  4             b.constraint_name fkey_constraint,
  5             b.r_constraint_name
  6    from user_constraints a, user_constraints b
  7   where a.table_name = b.table_name
  8     and a.constraint_type = 'P'
  9     and b.constraint_type = 'R'
 10  /

4 rows created.

tkyte@TKYTE816>
tkyte@TKYTE816>
tkyte@TKYTE816>
tkyte@TKYTE816> column table_name format a2
tkyte@TKYTE816> select * from temp_constraints;

TA PKEY_CONSTRAINT                FKEY_CONSTRAINT
R_CONSTRAINT_NAME
-- ------------------------------ ------------------------------
------------------------------
C1 SYS_C004927
C2 SYS_C004929
C3 SYS_C004931
C4 SYS_C004933
P  SYS_C004926
C1 SYS_C004927                    SYS_C004928                    SYS_C004926
C2 SYS_C004929                    SYS_C004930                    SYS_C004927
C3 SYS_C004931                    SYS_C004932                    SYS_C004929
C4 SYS_C004933                    SYS_C004934                    SYS_C004929

9 rows selected.

tkyte@TKYTE816> column table_name format a10
tkyte@TKYTE816>
tkyte@TKYTE816>
tkyte@TKYTE816> select rpad( '*', (level-1)*2, '*' ) || table_name table_name
  2    from temp_constraints
  3    start with fkey_constraint is null
  4   connect by prior pkey_constraint = r_constraint_name
  5  /

TABLE_NAME
----------
C1
**C2
****C3
****C4
C2
**C3
**C4
C3
C4
P
**C1
****C2
******C3
******C4

14 rows selected.
Review & Followup
Rating: 5
All Parent - Child tables in the database May 17, 2001
Reviewer: Srinivas from Memphis, TN
Yep, that's what i wanted, thanks a lot for the solution !!!

Rating: 5
How we can extend this table to generate dependancy tree? May 17, 2001
Reviewer: VJ from Boston, MA USA
Any example/idea on generating Dependancy tree including PL/sql packages,
procedures etc.(in fact, all the possible objects)?

Rating: 5
How can I derive DDL from the Data Dictionary ? October 15, 2001
Reviewer: Andre Whittick Nasser from Brazil
Thanks for your tip, very useful.

Now, Tom, I would be very glad if you could provide me with tips for writing a
script that derives DDLs like CREATE TABLE, etc, possibly for all objects, from
the D.D.

I know I can use EXP to get this info, but I'd like to format my own output.

If I am not mistaken, there is a script somewhere in Oracle's site with
something similar. Do have the link ?

Thanks again !

Followup:
I'm not personally aware of such a script -- I myself use EXP (actually, I use
exp ONLY to extract other peoples schema's cause I keep my scripts up to date
and never need to extract the ddl)...

In any case, 9i has a feature you'll like:

scott@ORA9I.WORLD> select dbms_metadata.get_ddl( 'TABLE', 'EMP' ) from dual;

DBMS_METADATA.GET_DDL('TABLE','EMP')
--------------------------------------------------------------------------------

  CREATE TABLE "SCOTT"."EMP"
   (    "EMPNO" NUMBER(4,0) NOT NULL ENABLE,
        "ENAME" VARCHAR2(10),
        "JOB" VARCHAR2(9),
        "MGR" NUMBER(4,0),
        "HIREDATE" DATE,
        "SAL" NUMBER(7,2),
        "COMM" NUMBER(7,2),
        "DEPTNO" NUMBER(2,0),
         CONSTRAINT "EMP_PK" PRIMARY KEY ("EMPNO")
  USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255
  STORAGE(INITIAL 524288 NEXT 524288 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCRE
ASE 0
  FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "USERS"  ENABLE,

         CONSTRAINT "EMP_FK_EMP" FOREIGN KEY ("MGR")
  REFERENCES "SCOTT"."EMP" ("EMPNO") ENABLE NOVALIDATE,
         CONSTRAINT "EMP_FK_DEPT" FOREIGN KEY ("DEPTNO")
  REFERENCES "SCOTT"."DEPT" ("DEPTNO") ENABLE NOVALIDATE
   ) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 LOGGING
  STORAGE(INITIAL 524288 NEXT 524288 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCRE
ASE 0
  FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "USERS"

Rating: 5
Thanks, anyway... October 16, 2001
Reviewer: Andre Whitick Nasser from Rio de Janeiro, Brazil
I'm aware of 9i's DDL extraction feature. But the only problem is I need to
format the output my own way. Thanks again for the eye-opener, reminding me to
keep the scripts of all my work. In fact, that is something I always do. And I
have a complete (somewhat) organized library for that purpose.

However, nothing is perfect, and every now and then I have to roll up my sleeves
and do a "reverse engeneering" on somebody else's database that does not have
this "healthy" (i.e. necessary) habit. Besides, I need to format the scripts
according to the company's needs .

Hence my question...

Thanks again !

Rating: 5
RAN INTO the following error March 19, 2002
Reviewer: Famiy
ERROR at line 2:
ORA-01436: CONNECT BY loop in user data

I tried to use your solution and ran into the above error.

and

cant we give a  filtering condition in a hierarchial query
i.e.for example if I want to use your solution only for tables starting with
'HEDGE'

Followup:
do you have any self referencing integrity?  If you do -- this'll not work (as I
said in the answer)....

Yes, you can filter a connect by

where tname like 'HEDGE%'
...
start with tname like 'HEDGE%'
...
connect by prior ..... and tname like 'HEDGE%'
....

But, it might be easier to just copy the info for tables like HEDGE% in the
first place.  But as I said, if a table refers to itself, you have a connect by
loop and this will NOT work

Rating: 5
Why are you inserting into the table twice March 19, 2002
Reviewer: Famiy
once you are inserting all the tablnames and constraints with constraint 'P'

and what are you inserting the second time.

Can you provide with some explanation.

Followup:
first I insert primary keys (constraint_type = 'P')

Then I insert all primary keys along with the foreign keys that point to them
(constraint_type = 'R')

Rating: 4
Recursive PL/SQL March 20, 2002
Reviewer: Mark J. Bobak from Ann Arbor, MI
I did something similar, but I implemented it in a recursive
PL/SQL routine.  This way, I was able to check for loops due
to self-referencing tables.

I don't have the code here at home, but could probably post
it tomorrow if folks are interested.  I have a LIST_CHILDREN
and a LIST_PARENTS.

-Mark

Rating: 4
What to do with self referencing Tables January 09, 2003
Reviewer: Vishal Sharma from TX
Hi Tom,
As U are the most reliable and last resort for questions. Regarding this
question of all parent -child tables. I have two tables which are self
referencing. I want to get a list of all other tables in parent child manner and
can take of these two tables seperately. how should i make my query run for rest
of all the tables excluding those 2 say table1 and table2.

Followup:
add a couple of where clauses to exclude them i suppose?

Rating: 3
Excluding pig's ears January 10, 2003
Reviewer: A P Clarke from London, England
Tom

I think it's quite simple to exclude self-referential foreign keys that use the
table's primary key:

    select a.table_name,
           a.constraint_name pkey_constraint,
           b.constraint_name fkey_constraint,
           b.r_constraint_name
      from user_constraints a, user_constraints b
     where a.table_name = b.table_name
       and a.constraint_type = 'P'
       and b.constraint_type = 'R'
       and a.constraint_name != b.r_constraint_name
/

...unless I am missing something.

Your query doesn't pick up foreign key relationships that use unique keys other
than primary keys.  Do you regard that as bad practice?

Cheers, APC

Followup:
don't see it as "bad", just unusual.  you could add them by adding a decode to
a.constraint_type making P and U "the same"

January 10, 2003 Juan Carlos -- Thanks for the question regarding "dbms_flasback", version 9.2
You Asked
Hi Tom, sorry,  I have 5 questions about flashback
1.
Is possible to convert a SCN to date and time
2.
Is possible to get an SCN from a date and time
3.
Which commands could you execute to avoid waiting that 5 minutes
to get old data
4.
Why if before the 5 minutes
I create the records and commit,
and delete de records and commit,

I can't see the data even waiting several minutes.

Thanks
and we said...
1) not exactly -- within +- 5 minutes, yes.  look at SYS.SMON_SCN_TIME. every 5
minutes, smon wakes up and puts the SCN in there with a timestamp.  Goes back
1440 rows (5 days)

2) see #1, within +- 5 minutes.

3) use the SCN instead of time!

4) because SMON only remembers SCNs every 5 minutes.  Use an SCN instead....
Review & Followup
Rating: 5
January 06, 2003
Reviewer: A reader
:) Thanksyou

Here is the example I forgot to include,
where you lost the data, only to illustrate.

13:05:04 SQL> create table ca( a varchar2(1));
Table created.
13:05:13 SQL> insert into ca values ('3');
1 row created.
13:05:21 SQL> commit;
Commit complete.
13:05:22 SQL> select sys.dbms_flashback.get_system_change_number from dual;
GET_SYSTEM_CHANGE_NUMBER
------------------------
                 3347555

HERE WE HAS THE SCN WHEN WE COMMITED
13:05:26 SQL> commit;
Commit complete.

13:05:28 SQL> delete from ca;
1 row deleted.
13:05:31 SQL> commit;
Commit complete.

IMMEDIATLY WE DELETE  AND COMMIT
13:05:32 SQL> select * from ca as of scn  3347555;
select * from ca as of scn  3347555
              *

IF YOU TRY TO QUERY YOU GET THIS ERROR
13:13:24 SQL> select * from ca as of scn  3347555;
select * from ca as of scn  3347555
              *
ERROR at line 1:
ORA-01466: no se han podido leer los datos - la definici¾n de tabla ha cambiado
13:14:24 SQL>

Followup:
DDL needs to be stable for a bit -- and in the real world, it will be.  It is
the TABLE definition that is the problem here.  Nothing you can do about that.

Rating: 5
January 06, 2003
Reviewer: A reader
thanks

Rating: 5
January 09, 2003
Reviewer: cachitok@yahoo.com
Hi Tom,

I would like to add that
SYS.SMON_SCN_TIME
only keeps track for five days.!!

I searched but I can't find the description that
SYS.SMON_SCN_TIME's fields has.

Could you please give the description that every fields has,
Thanks

Followup:
well -- i added that in the first place:

.... Goes back 1440 rows (5 days)

you only need to know two of them -- they are not documented.  you shouldn't
even need to look at that table except out of curiosity.

Rating: 5
January 10, 2003
Reviewer: A reader
thanks Tom.

Yes you add the 5 days, what I was trying to say
is what do you do if you have for example a undo
that keeps track for a month.

And suddenly some users says that he did an incorrect update in one table, 15
days ago.
How do you get the SCN to recover to exactly 15 days ago, not 13 neither 17,
because you can't use a datetime.

You can't?

Thanks.

Followup:
you have space for 30 days of UNDO?

Look -- lets be reasonable here.  hours, one two days -- that is reasonable for
undo retention.

a month, thats getting beyond "pratical".  5 days would be considered hugely
extreme.

If you want the ability to go back "infinitely far" that way -- I suggest you
look at workspace management instead.  Here, a DELETE of a row is turned into a
flag delete (row appears to go away, but it doesn't.  It is updated with a
timestamp to reflect when it was deleted and by whom).  An UPDATE is turned into
a flag delete and insert.  With this solution, you can keep an infinite history
of data if you like.

Rating: 5
January 10, 2003
Reviewer: A reader
:) Thanks, you are rigth.

We can do this in some small database we have that really has a small, small
amount of inserts, updates, and deletes in a year.

And when we configure the UNDO for a month, the undo tablespace almost don't
grow.

January 10, 2003 -- Thanks for the question regarding "Row Space for tables with LOBS", version 8.1.7
You Asked
Hi Tom,

This is regarding the calculation of rowsize  of a table having a clob column in
it.
before  asking this question to you, i searched  in your site and got some valid
points.
i would be thankful to you, if you could solve my doubt with an example.

db_block_size=8k.
chunk size=8k.
initial extent 131k

iam  creating a table  similar to this .

    create table clob_test(n number(3), text clob);

iam going to upload a file of size 100KB  into that clob column.

i need to know the average row  size of the table.

thanks in advance
shankar
and we said...
why do you have an initial extent?  We all know we should use locally managed
tablespaces -- where initial and next are never specified in a create table
anymore!

The only way I know to accurately get rowsizes of anything -- or to size
anything at all -- is to load representative data into the table and analyze.
Consider:

ops$tkyte@ORA817DEV> create table t ( n number(3), text clob ) pctfree 40;

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert into t select rownum, empty_clob() from all_objects
where rownum < 500;

499 rows created.

ops$tkyte@ORA817DEV> begin
  2      for x in ( select * from t )
  3      loop
  4          dbms_lob.writeAppend( x.text, 32000, rpad('*', 32000, '*' ) );
  5          dbms_lob.writeAppend( x.text, 32000, rpad('*', 32000, '*' ) );
  6          dbms_lob.writeAppend( x.text, 32000, rpad('*', 32000, '*' ) );
  7      end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select num_rows, blocks, empty_blocks, avg_space,
avg_row_len
  2    from user_tables
  3   where table_name = 'T';

  NUM_ROWS     BLOCKS EMPTY_BLOCKS  AVG_SPACE AVG_ROW_LEN
---------- ---------- ------------ ---------- -----------
       499         10           53       3261          93

there you go -- 93 bytes (a lob locator is 80 some odd bytes in length)
Review & Followup
Rating: 4
January 10, 2003
Reviewer: Roland Wallner from Norfolk, VA
Very, very helpful.  I was just wondering how you would calculate the average
row length of actual log segment/index, since you indicated that what we were
seeing was just the lob locator value and what ever else was in the row?  Or am
I missing something?
Thanks!

Followup:
select avg(dbms_lob.getlength(x)) from t;

But really -- you should know the average lob length just from knowing the data.
 It is that information that'll help you determine the appropriate chunksize for
that lob segment.  EG: I'm loading gif images, between 30 and 100k.  Or, I'm
loading documents between 500k and 5m.  Whatever.

January 10, 2003 David -- Thanks for the question regarding "Configurable cancel command", version 8.0.5.0.0
You Asked
Tom,
    as part of our database tidy up I was attempting to cut the
output from the SQL*Plus session on a unix terminal emulator
(AttachMate Extra) to include in an email to report progress to
other members of the project team. Imagine my grief, pain,
horror and self loathing when – having highlighted the relevant
text – I got my operating systems mixed up and attempted to use
the Windows CUT command (Ctrl C) to copy the text. YUP, you
guessed it :

“User requested cancel of current operation”

    This was rather unfortunate as the PL/SQL anonymous block
had been running for over 15 hours at this point and had gobbled
up 9 hours of CPU time. This is not simply a request for some
sympathy but an actual question.

    Is there any way that you can configure Oracle so that the
keystroke combination to cancel an operation can be changed from
Ctrl C? As Attachmate runs in a Windows window you could argue
that Ctrl C is a valid keystroke combination but that doesn’t
get me my time back and I’d just like to try and guard against
the possibility of future occurrences. I’m sure I’m not the only
person who has ever fallen foul of this unfortunate
juxtaposition of command interpretation.

Regards,

David.
and we said...
It'll depend on your terminal but typically STTY can do this
(this is a unix thing, not really a SQL thing).  What works for
me is

stty -isig

Consider:

$ stty -isig

$ ^C
^C: Command not found

^C is now a "command" or just another character -- not a
break...

$ sqlplus /

SQL*Plus: Release 8.1.5.0.0 - Production on Fri Aug 25 10...
(c) Copyright 1999 Oracle Corporation.  All rights reserved.
Connected to:
Oracle8i Enterprise Edition Release 8.1.5.0.0 - Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.0.0 - Production

ops$tkyte@8i> select count(*) from all_objects;
^C^C^C^C^C^C^C

  COUNT(*)
----------
     21302

See, I can ^C all day now and not get the ORA-1013....  I can
turn it back on:

$ stty isig

$ sqlplus /

SQL*Plus: Release 8.1.5.0.0 - Production on Fri Aug 25 ...
(c) Copyright 1999 Oracle Corporation.  All rights reserved.
Connected to:
Oracle8i Enterprise Edition Release 8.1.5.0.0 - Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.0.0 - Production

ops$tkyte@8i> select count(*) from all_objects;
^Cselect count(*) from all_objects
*
ERROR at line 1:
ORA-01013: user requested cancel of current operation

and it works once more...

"man stty", if isig doesn't work on your platform and try some
of the others...
Review & Followup
Rating: 5
cancel under win NT and within a script January 09, 2003
Reviewer: Brigitte from Germany
Hallo Tom,
how can I cancel a select as a Win NT Client?
Can I cancel a script from inside the script?
Thanks

Followup:
ctl-c always worked for me.  assuming sqlplus of course.  it is up to the tool
in question to do this.

Rating: 1
CTRL c doesn't work January 10, 2003
Reviewer: Brigitte from Germany
Hallo Tom,
thanks for your quick reply. Please excuse my unclear explanation.

Ctrl c cancels a script.

1. But whenn I have a select statement that's too long for me ctrl c doesn't
work. I must kill sqlplus. I tested many key combinations without success. How
can I cancel the statement?

2. I have scripts which must run under system. I can test within the script
whether the user is system or not (select user from dual). How can I program a
cancel in the script if the user is not system.

I hope you can understand me.
thanks

Followup:
1) contact support -- I don't do windows -- it always worked for me (ctl-c) when
I did have windows a while back.

2) like this:

ops$tkyte@ORA920> whenever sqlerror exit
ops$tkyte@ORA920> variable uname varchar2(30)
ops$tkyte@ORA920> begin select user into :uname from dual where user = 'SYSTEM';
end;
  2  /
begin select user into :uname from dual where user = 'SYSTEM'; end;
*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at line 1

Disconnected from Oracle9i Enterprise Edition Release 9.2.0.2.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.2.0 - Production
[tkyte@tkyte-pc tkyte]$

unless I'm logged in as SYSTEM, script bombs.

Rating: 5
January 14, 2003
Reviewer: Brigitte from Germany
Thanks, my script is now "bombproof".

January 10, 2003 Bill -- Thanks for the question regarding "Deleting old records", version 8.1.7
You Asked
Hi Tom,

I want to create a snapshot table of the most recent inserts into a historical
table.  To do this I want to be able to limit the number of records in the
snapshot table and I want this limit to be capable of being changed dynamically.

I was considering writing a trigger which executes on insert into the table and
after determining the limit, say 50 records, from a config table will workout if
the data table is at its limit (50) and if so will delete the oldest record from
the table.

Is a trigger the best way to do this or is it possible to use constraints to
achieve this?  Any recomendations would be much appreciated.

Thanks

Bill
and we said...
why would you not just:

a) add a timestamp to your existing table
b) query

  select *
    from ( select /*+ FIRST_ROWS */ *  <<=== hint if necessary
             from existing_table
            order by that_timestamp DESC )
   where rownum <= 50;

that would be the best, more performant, accurate, easiest, least resource
intensive way to do it....  if you have an index on that_timestamp and
that_timestamp is defined as NOT NULL, that query will be instantaneous.

That is what I would do.  The other method is a mess to maintain, inhibits
concurrency greatly (ever insert will more or less serialize on this little 50
row table)..
Review & Followup
Rating: 5
not null January 09, 2003
Reviewer: Meyer from Baltimore, MD
Tom, I would have done the same thing you advised...

but I can learn from one point.

Please explain the concept of how defining as not_null would speed it up.

Thanks,
Meyer

Followup:
given this query:

  select *
    from ( select /*+ FIRST_ROWS */ *  <<=== hint if necessary
             from existing_table
            order by that_timestamp DESC )
   where rownum <= 50;

if that_timestamp was b*tree indexed but that_timestamp was NULLABLE, we would
not be able to read the index to access the table.  The reason -- entire NULL
b*tree entries are not made.  Hence, if that_timestamp where allowed to be null,
the optimzer would NOT be able to use the index!

ops$tkyte@ORA920> create table t ( x int, y date );

Table created.

ops$tkyte@ORA920> create index t_idx on t(y);

Index created.

ops$tkyte@ORA920> exec dbms_stats.set_table_stats( user, 'T', numrows=> 100000,
numblks => 10000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select *
  2    from (select /*+ first_rows */ *
  3            from t
  4                   order by y desc )
  5   where rownum <= 50;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=1418 Card=50
Bytes=1100)
   1    0   COUNT (STOPKEY)
   2    1     VIEW (Cost=1418 Card=100000 Bytes=2200000)
   3    2       SORT (ORDER BY STOPKEY) (Cost=1418 Card=100000 Bytes=2200000)
   4    3         TABLE ACCESS (FULL) OF 'T' (Cost=963 Card=100000
Bytes=2200000)

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table t modify y NOT NULL;

Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select *
  2    from (select /*+ first_rows */ *
  3            from t
  4                   order by y desc )
  5   where rownum <= 50;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=826 Card=50
Bytes=1100)
   1    0   COUNT (STOPKEY)
   2    1     VIEW (Cost=826 Card=100000 Bytes=2200000)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=826 Card=100000
Bytes=2200000)
   4    3         INDEX (FULL SCAN DESCENDING) OF 'T_IDX' (NON-UNIQUE) (Cost=26
Card=100000)

ops$tkyte@ORA920> set autotrace off

Rating: 4
January 10, 2003
Reviewer: Bill from Cork, Ireland
Hi Tom,

Thank you very much for your quick responce.  Unfortunatly I did'nt explain the
rational for taking the two table approach.  The snapshot table resides in one
database and is used by a GUI to create Graphs and clculate averages on recent
data while the historical data is stored in an seperate archive database and
will have many millions of records so the data in the snapshot table is just
passing through on it's way to the archive.

Thanks for your assistance, it is greatly appreciated.

Regards

Bill

Followup:
then create the snapshot as:

  select *
    from ( select /*+ FIRST_ROWS */ *  <<=== hint if necessary
             from existing_table
            order by that_timestamp DESC )
   where rownum <= 50;

and refresh full every couple of minutes?  a snapshot is not real time, a
snapshot is a "every couple of minutes" thing.  That query should run fast...

Rating: 4
Not null and the indexed January 10, 2003
Reviewer: Sush from SJ,CA
Hi Tom ,suppose I use this table 'T' as my source and I need to capture the
incremental data from this table into my target table say 'Target' ...Based on
the above example the column 'Y'(date datatype) should be Not Null and it should
be indexed ?By default when we create an index I think it will be in ascending
order  ? But for incrementals we will interested in descending records...What if
the volume of the incrementals could be around 2 million everyday (it could be
updates or inserts)...Will the index be of use ?

My query for incremental could be

insert into target
select * from t where y > ( select last_date from d_jobs )

Thanks..Sush

Followup:
indexes can be read backwards or forwards.

ops$tkyte@ORA920> select *
  2    from (select /*+ first_rows */ *
  3            from t
  4                   order by y desc )
  5   where rownum <= 50;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=826 Card=50
Bytes=1100)
   1    0   COUNT (STOPKEY)
   2    1     VIEW (Cost=826 Card=100000 Bytes=2200000)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=826 Card=100000
Bytes=2200000)
   4    3         INDEX (FULL SCAN DESCENDING) OF 'T_IDX' (NON-UNIQUE) (Cost=26
Card=100000)

Rating: 4
How about just want to implement a history or audit trail records without any limitation? January 10, 2003
Reviewer: Jian Wu from Somerville, New Jersey
How about if I just want to implement a system that keep track of every single
changes in the database?  My plan is to use a set of shadow tables that will be
identical to working tables, except that all primary or referential constraints
are disabled.  Whenever a record is inserted or updated in working table, a new
record will be created in the shadow table.  And there will be no delete allowed
in the system.  I plan to implement this with after insert/update row trigger.
Of course, all table will have columns such as UPDATE_DATE, UPDATE_USER_ID,
UPDATE_REASON.  What do you think?

Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4529781014729
workspace mgmt might be something you can use out of the box.  we already do
that for you.

January 10, 2003 -- Thanks for the question regarding "Verify value exist in table... ", version 8.1.7
You Asked
Can you suggest the most CONCISE way to have SQL, NOT PL/SQL to verify that a
value exist in a table and if the value exist, continue with the script,
otherwise exit?

I know this is a simple matter in PL/SQL, but I'm curious of the method to do it
in SQL.

Thanks.
and we said...
ops$tkyte@ORA920> whenever sqlerror exit
ops$tkyte@ORA920>
ops$tkyte@ORA920> select to_number('a')
  2    from dual
  3   where NOT EXISTS ( select null
  4                        from all_users
  5                       where username = 'SCOTT' )
  6  /

no rows selected

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select to_number('a')
  2    from dual
  3   where NOT EXISTS ( select null
  4                        from all_users
  5                       where username = 'NOT_A_USER' )
  6  /
select to_number('a')
                 *
ERROR at line 1:
ORA-01722: invalid number

Disconnected from Oracle9i Enterprise Edition Release 9.2.0.2.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.2.0 - Production
[tkyte@tkyte-pc tkyte]$

is one way...
Review & Followup
Rating: 5
EXACTL Y January 10, 2003
Reviewer: A reader
Thanks Tom...exactly what I was looking for.

Rating: 5
January 10, 2003
Reviewer: Samir Ahuja from Albany, NY USA
such a small answer....wonderful

Rating: 5
What a simple, wonderful method! January 10, 2003
Reviewer: Haiqing Lu from New York, NY USA
Reading through ASKTOM become one of my habit in my life...
and I always get benefit from it, sometime I really got shocked by the wonderful
solution.
Thanks a lot Tom.

Haiqing

January 10, 2003 Justin -- Thanks for the question regarding "Analyze temporary tables for using CBO", version 8.1.7
You Asked
Facts: third party appliation ported from ms sql using a lot of temporary tables
for data aggregation.

I would like to use CBO instead of RBO, so far there are no statistics computed.

As I've read you should not have analyzed and non-analyzed tabels joined in your
queries.How could I get statistics on all those temp tables that are emptied
once the results are returned then? Could I create the statistics on the empty
temp tables inspite, because there are relativly few data inside even in use
compared to the real data holding table? Is there any feature such as "compute
statistics once, next time this table is used"?
Is there any way of predicting results before doing this on a production server?
I have a testing environment with identical data just no users using it.

Thanks a lot

justin
and we said...
3rd party application provider must support you using the CBO -- they wrote
their application (unfortunately, sounds like they just plopped MS code on
Oracle -- sort of like trying to run internet explorer on Solaris -- not going
to be "pretty")

You cannot just turn on the CBO on a 3rd party application without them saying
"we tested it, we support it, go ahead, it'll work fine"

3rd party apps on top of the database mean you have to work with the 3rd party
application vendor before you do anything.

If they support the CBO, they will have the procedures you need in order to use
it.  If not, you won't be using the CBO.

You can use dbms_stats to load into the database reasonable stats for the
temporary tables -- representing what you believe will be in the temp tables at
runtime.  But -- they (the 3rd party vendor) must provide you with the details.
Review & Followup
Rating: 4
Internet Explorer on Solaris December 07, 2002
Reviewer: Jer Smith from Salt Lake City
It wasn't pretty.  Not at all.

ftp://mirror.averse.net/pub/microsoft/internet-explorer/Solaris/

Followup:
wow -- i was kidding -- didn't know it would work at all!

Rating: 5
Thanks December 16, 2002
Reviewer: Justin from europe
Thanks, I handed this issue over to the vendor.

How do I get statistics tat look like this without actually analyzing the tables
in this case:

Instance Efficiency Percentages (Target 100%)
> > >>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > >>             Buffer Nowait %:  100.00       Redo NoWait %:  100.00
> > >>             Buffer  Hit   %:   77.69    In-memory Sort %:   99.41
> > >>             Library Hit   %:   86.63        Soft Parse %:   57.43
> > >>          Execute to Parse %:   56.65         Latch Hit %:   99.99
> > >>Parse CPU to Parse Elapsd %:             % Non-Parse CPU:
> > >>
> > >>  Shared Pool Statistics        Begin   End
> > >>                                ------  ------
> > >>              Memory Usage %:   73.13   78.14
> > >>     % SQL with executions>1:   15.21   14.83
> > >>   % Memory for SQL w/exec>1:   24.97   23.97
I've read on the dbms_stats package but I'm not sure what command to use for
just displaying?

thank you

Followup:
you do not analyze tables for that -- that is just stats pack.

set timed_statistics=true, use statspack to take a snapshot, 15 minutes later do
it again and generate a report.

read
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96533/statspac.htm#34837

Rating: 5
The company's response January 10, 2003
Reviewer: Justin from Europe
So here is the answer of the application vendor:

"we have not tested it, but go ahead it should work fine.
You don't need stats for the temp tables,as they are not joined with others
(this is not true) or add "Analyze table TORA..." before the "delete * from
tora" in every procedure using temp tables. At the worst the RBO will be used,
which is what you have now"

I'm not really sure whether I should do this.
What optimizer will be used when I join analyzed and non-analyzed tables in a
query?
What else could happen at worst through switching to CBO?
Could I really add such a line to those proc. using temp. tables and for example
analyze them every month just once (How?)?
Or is there a better way?

Thanks again for your kind help.
Through the knowledge provided here I got a lot more understanding for what's
happening inside.
(and the vendor is thinking about bind variables now as I found somewhat 36ths
executes for 90 different).

thanks

justin

Followup:
q) What optimizer will be used when I join analyzed and non-analyzed tables in a

query?
a) CBO

q) What else could happen at worst through switching to CBO?
a) system performance characteristics change dramatically

q) Could I really add such a line to those proc. using temp. tables and for
example analyze them every month just once (How?)?
a) temporary tables cannot be analyzed, you can use dbms_stats to set
REPRESENTATIVE stats on them if you like but you cannot analyze them

January 10, 2003 Iannick -- Thanks for the question regarding "Operation in DECODE ???", version 8.1.7
You Asked
Hi Tom,

I have a string contain time (ex : 09:00).
In a SELECT clause, I want to know if 09:00 it's AM or PM

SELECT DECODE((to_number(substr('09:00',1,2), --> <12 <-- , 'AM', 'PM') from
dual.

Is it possible with a DECODE ?

I can do it with a long DECODE but... it's a big SELECT clause...
SELECT DECODE((to_number(substr('09:00',1,2),
               1 , 'AM',
               2, 'AM',
               ... 'AM',
               11,'AM',
     'else     'PM') from dual

How can I optimize my SELECT ?
Thanks,

iannick
and we said...
just

ops$tkyte@ORA817DEV> select to_char(to_date('09:00','hh24:mi'),'am') from dual;

TO
--
am

ops$tkyte@ORA817DEV> c/09/19
  1* select to_char(to_date('19:00','hh24:mi'),'am') from dual
ops$tkyte@ORA817DEV> /

TO
--
pm

ops$tkyte@ORA817DEV>
Review & Followup
Rating: 5
Thanks January 10, 2003
Reviewer: Iannick from Quebec, Canada
Thanks Tom,

I think I was to minded on the decode way ...

January 10, 2003 Meyer -- Thanks for the question regarding "Lobs or Longs", version 8I and 9I
You Asked
1)It is my understanding, at that at this point it is always better to use Blobs
instead of Longs...is that correct or wrong (table defs and pl/sql)?

2)When (if ever) do you still use longs?

3)For asktom, is our posts stored as longs or blobs...and if you re-created it
now...would it be the same?

4)Specifically going to create a web app that will store/display standard text
that could probably fit in a varchar(4000), but want to be ready for longer
entries (just in case)...They will be displayed/sorted based on other columns in
the table. Lob or Long for the text?

Thanks,
Meyer
and we said...
1) in the database -- do not use long or long raw -- only use blobs and clobs

in plsql, long is really just varchar2(32765) and long raw is just raw(32765).
If you need a big string, go ahead and use long -- it is just a subtype that is
a varchar2 in disguise.

2) never

3) clobs -- all in clobs.  Yes, it would be the same.

4) CLOBS are the only type you should even think about considering.
Review & Followup
Rating: 5
Thanks January 10, 2003
Reviewer: Meyer from Baltimore, MD
When I read my post,
Realized Ioften use the term Blob where I should use Lob or Clob.

Thank you for the concise and accurate answer.

January 10, 2003 Koshal -- Thanks for the question regarding "dbms_debug package - reg", version 8.1.7.0
You Asked
Hi Tom

With yr valuable suggestions/guidance we have migrated to 8.1.7.0.

Can u pl give detailed explanation/example of how to use
dbms_debug package. I had gone through the documentation but
there is no detailed example.

Thanks in Advance.

Regards
and we said...
it is not really intended for you to use -- it is intended to be used by someone
who wants to write a PLSQL debugger.

Here is a note on it:

Article-ID:         <Note:67899.1>
Alias:              PLSQL:DBMS_DEBUG
Circulation:        PUBLISHED (EXTERNAL)
Folder:             PLSQL
Topic:              DBMS_DEBUG Package
Title:              PACKAGE    DBMS_DEBUG Specification
Document-Type:      REFERENCE
Impact:             MEDIUM
Skill-Level:        CASUAL
Server-Version:     08 to 08.01
Updated-Date:       05-JAN-1999 15:22:38
References:
Shared-Refs:
Authors:            AUTO
Attachments:        NONE
Content-Type:       TEXT/PLAIN
Products:           11/PLSQL;
Platforms:          GENERIC;

PACKAGE:DBMS_DEBUG
~~~~~~~~~~~~~~~~~~
Version/s:  8.0, 8.1   (Notes here based on Oracle8i)

 OVERVIEW
   DBMS_DEBUG is a PL/SQL API to the PL/SQL debugger layer ('Probe') in
   the Oracle server.  It is intended mainly for vendors implementing
   server-side debuggers, and provides a way to debug server-side PL/SQL
   (procedures, functions, packages, triggers, anonymous blocks, types,
    etc.)
   DBMS_DEBUG uses a 'pull' event model: in order to debug server-side
   code it is necessary to have two database sessions: a session in which
   the code will be executed in debug-mode (known as the target session),
   and a second session (known as the debug session) to supervise the
   target session.

   To use DBMS_DEBUG, the target session first makes the appropriate calls
   to initialize itself (described later).  This marks the session so that
   the PL/SQL interpreter runs in debug-mode and generates debug events
   (described later).  As debug events are generated, they are posted from
   the session.  In most cases, debug events require return notification:
   the interpreter pauses awaiting a reply.
   Meanwhile the debug session must also initialize itself via DBMS_DEBUG:
   this tells it what target session to supervise.  The debug session may
   then call entrypoints in DBMS_DEBUG to read events that were posted
   from the target session and to communicate with the target session.

   DBMS_DEBUG does not provide any interface to the PL/SQL compiler.  But
   it does depend on debug information that is optionally generated by
   the compiler.  Without debug information it is not possible to look at
   or modify the values of parameters or variables.  There are two ways
   to ensure that debug information is generated: via a session switch or
   via individual recompilation.
   To set the session switch, execute this statement:
       ALTER SESSION SET PLSQL_DEBUG=true;
   This instructs the compiler to generate debug information for the
   remainder of the session.  It does not recompile any existing PL/SQL.
   To generate debug information for existing PL/SQL code, use one of the
   following statements (the second recompiles a package or type body):
       ALTER [PROCEDURE | FUNCTION | PACKAGE | TRIGGER | TYPE] <name>
           COMPILE DEBUG;
       ALTER [PACKAGE | TYPE] <name> COMPILE DEBUG BODY;

 The diagrams below give the general idea.
 Each box contains a description and the appropriate DBMS_DEBUG call.

                             Target Session
                             **************
                  +--------------------------------------+
                  | Initialize session for debugging,    |
                  | and generate/specify unique debugID. |
                  | DBMS_DEBUG.initialize()              |
                  +--------------------------------------+
                                       |
   +---------------------------------->|
   |                                   V
   |              +--------------------+-----------------+
   |              V                    |                 V
   |   +-----------------------+       |      +------------------------+
   |   | Start debugging       |       |      | Stop debugging         |
   |   | DBMS_DEBUG.debug_on() |       |      | DBMS_DEBUG.debug_off() |
   |   +-----------------------+       |      +------------------------+
   |              V                    V                 V
   |              +--------------------+-----------------+
   |                                   V
   |                      +-------------------------+
   |                      | Execute PL/SQL programs |
   |                      +-------------------------+
   |                                   V
   +-----------------------------------+

                             Debug Session
                             *************
                         +-----------------------------+
    Input:               | Initialize                  |
      debugID from   --> | DBMS_DEBUG.attach_session() |
      target session     +-----------------------------+
                                       |
 +------------------------------------>|
 |                +-------------------<+<------------------+
 |                |                    |                   |
 |                |   +--------------------------------+   |
 |                |   | Manipulate breakpoints         |   |
 |                |   | DBMS_DEBUG.set_breakpoint()    |   |
 |                |   | DBMS_DEBUG.delete_breakpoint() |   |
 |                |   | DBMS_DEBUG.disable_breakpoint()|   |
 |                |   | DBMS_DEBUG.enable_breakpoint() |   |
 |                |   | DBMS_DEBUG.show_breakpoints()  |   |
 |                |   +--------------------------------+   |
 |                |                    V                   |
 |                +------------------->+>------------------+
 |                                     |
 |                                     V
 |                +--------------------------------------+
 |                | Read first event from target session |
 |                | DBMS_DEBUG.synchronize()             |
 |                +--------------------------------------+
 |                           |
 |   +---------------------->|
 |   |   +------------------>|
 |   |   |                   V
 |   |   |                   +----------------------->+
 |   |   |                   |                        |
 |   |   |                   V                        |
 |   |   |   +------------------------------+         |
 |   |   |   | Show stack                   |-------->+
 |   |   |   | DBMS_DEBUG.print_backtrace() |         V
 |   |   |   +------------------------------+         |
 |   |   |                                            |
 |   |   |                  +------------------------<+
 |   |   |                  V                         V
 |   |   |   +------------------------+               |
 |   |   |   | Get/set values         |               |
 |   |   |   | DBMS_DEBUG.get_value() |-------------->+
 |   |   |   | DBMS_DEBUG.set_value() |               V
 |   |   |   +------------------------+               |
 |   |   |                                            |
 |   |   |               +---------------------------<+
 |   |   |               V                            V
 |   |   |   +------------------------+               |
 |   |   |   | Manipulate breakpoints |-------------->+
 |   |   |   +------------------------+               V
 |   |   |                                            |
 |   |   |                +--------------------------<+
 |   |   |                V                           V
 |   |   |   +--------------------------+             |
 |   |   |   | Show source              |------------>+
 |   |   |   | DBMS_DEBUG.show_source() |             V
 |   |   |   +--------------------------+             |
 |   |   |                                            |
 |   |   +----------------------<+-------------------<+
 |   |                           |
 |   |                           V
 |   |      +--------------------------------------------+
 |   |      | Continue execution and wait for next event |
 |   |      | DBMS_DEBUG.continue()                      |
 |   |      +--------------------------------------------+
 |                           |
 |   |       +---------------------------------------+
 |   +- No - | Program terminated?                   |
 |           | (event is DBMS_DEBUG.reason_knl_exit) |
 |           +---------------------------------------+
 |                           |
 |                          Yes
 +---------------------------+
  next program to debug      V
                             |
                +-----------------------------+
                | Detach session              |
                | DBMS_DEBUG.detach_session() |
                +-----------------------------+

 Control of the interpreter
   The interpreter will pause execution in the following places:
     1. At startup of the interpreter (so that any deferred breakpoints
        may be installed prior to execution)
     2. At any line containing an enabled breakpoint
     3. At any line where an 'interesting' event occurs.  The set of
        interesting events is determined by the flags passed to
        DBMS_DEBUG.continue (in the 'breakflags' parameter).
        See the section on 'Break Flags' below for more details.

 Terminology
   + Program unit - a PL/SQL program of any kind (procedure, function,
     package, package body, trigger, anonymous block, object type, or
     object type body).

 General notes:
   + Session termination
     There is no event for session termination.  Therefore it is the
     responsibility of the debug session to check and make sure that
     the target session has not terminated.  A call to
     dbms_debug.synchronize() after the target session has terminated
     will cause the debug session to hang until it times out.

   + Deferred operations
     The diagram suggests that it is possible to set breakpoints prior
     to having a target session.  This is true: in this case Probe caches
     the breakpoint request and transmits it to the target session at
     first synchronization.  However note that if a breakpoint request is
     deferred in this fashion then:
       1. set_breakpoint() does not set the breakpoint number (it can be
          obtained later from show_breakpoints() if necesary)
       2. set_breakpoint() does not validate the breakpoint request: if
          the requested source line does not exist then the error will
          silently occur at synchronization and no breakpoint will be
          set.

   + Stepping into packages.
     Packages are instantiated upon first use, and any initialization
     code (in either the package spec or body) is executed at that time.
     Thus a call to a procedure/function in a package may look to the
     debugger like several calls all on the same line (one call to
     initialize the spec if necessary, one to initialize the body if
     necessary, and one to call the desired entrypoint).

   + Diagnostic output
     To debug Probe, there are 'diagnostics' parameters to some of the
     calls in DBMS_DEBUG.  These parameters specify whether to place
     diagnostic output in the rdbms tracefile.  (If output to the rdbms
     tracefile is disabled then these parameters will be no-ops.)
Review & Followup
Rating: 2
I did want to write a PL/SQL debugger March 01, 2002
Reviewer: change_on_install from China
i want to write a PL/SQL develop tool just
like DBArtisan. i use delphi and have almost
completed. but i have no experience on
DBMS_DEBUG,can you give me a example, or any
other advices is also welcome.

best regards,
change_on_install

Followup:
Preety much everything I know about it is listed above.

It is documented in the supplied packages guide as well
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76936/dbms_deb.htm#1003779
...

Rating: 5
Error While Executing alter session plsql_debug = true April 16, 2002
Reviewer: Kumar from USA
I am getting error while executing
alter session plsql_debug = true
Insufficent privileges

What type of privilige is needed to execute the abouve statement

Thanks
Kumar

Followup:
you need ALTER SESSION

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop user a cascade;

User dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> grant create session to a identified by a;

Grant succeeded.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> @connect a/a
ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout off
a@ORA817DEV.US.ORACLE.COM> REM GET afiedt.buf NOLIST
a@ORA817DEV.US.ORACLE.COM> set termout on
a@ORA817DEV.US.ORACLE.COM> alter session set plsql_debug=true;
ERROR:
ORA-01031: insufficient privileges

a@ORA817DEV.US.ORACLE.COM> @connect /
a@ORA817DEV.US.ORACLE.COM> set termout off
ops$tkyte@ORA817DEV.US.ORACLE.COM> REM GET afiedt.buf NOLIST
ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout on
ops$tkyte@ORA817DEV.US.ORACLE.COM> grant alter session to a;

Grant succeeded.

ops$tkyte@ORA817DEV.US.ORACLE.COM> @connect a/a
ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout off
a@ORA817DEV.US.ORACLE.COM> REM GET afiedt.buf NOLIST
a@ORA817DEV.US.ORACLE.COM> set termout on
a@ORA817DEV.US.ORACLE.COM> alter session set plsql_debug=true;

Session altered.

a@ORA817DEV.US.ORACLE.COM>

Rating: 5
Thx tom for quick response April 17, 2002
Reviewer: Kumar from USA

Rating: 5
Is there a Demo tool available which has implemented the debugger January 10, 2003
Reviewer: Mani from NJ USA
Is there a tool available, which I can use to debug by PL/SQL procedures.

Followup:
Yes, jdeveloper believe it or not.  It is pretty darn awesome actually at
helping you write and debug plsql:
http://otn.oracle.com/software/products/jdev/content.html
yes, it works with 8i databases.

January 10, 2003 ls -- Thanks for the question regarding "bind variables in DDL with execute immediate", version 8.1.7.2
You Asked
Hi Tom

I was wondering if it's possible use bind variables with execute immediate and
DDL

such as

begin
for i in (select * from dba_segments where owner='LSC'
          and (segment_type='INDEX' or segment_name='EMP'))
loop
  if i.segment_type = 'INDEX'
  then
  dbms_output.put_line(i.segment_name);
  execute immediate 'alter index '||:seg||' rebuild tablespace tools' using
i.segment_name;
  else
  dbms_output.put_line(i.segment_name);
  execute immediate 'alter table '||:seg||' move tablespace users'   using
i.segment_name;
  end if;
end loop;
exception
  when others
  then
  dbms_output.put_line(sqlerrm);
end;
/

This doesnt work, but is it possible to acomplish this?

Cheers
and we said...
you cannot use bind variables with DDL -- there is nothing "replaceable" in DDL.
 For example, the series of steps needed to perform:

alter table T move tablespace users

is TOTALLY different then

alter table EMP move tablespace users

In fact, you'll never see DDL in the shared pool for the simple reason that you
cannot really share it!

You just use concatenation here.
Review & Followup
Rating: 5
Thank you as usual December 23, 2001
Reviewer: ls cheng from madrid
Thank you Tom for answering my question on Sunday!

Rating: 4
January 10, 2003
Reviewer: A reader
Can you help me on this please? Why is this procedure generationg error? It is
compiled and run as a user with DBA privilage. I am getting no clue what is
going on here!!

Thanks for all your help.

SQL> ed
Wrote file afiedt.buf

line 17 truncated.
  1  create or replace procedure
  2   pr_create_user( p_user in varchar2,
  3                 p_passwd in varchar2 )
  4  as
  5   st varchar2(100);
  6  begin
  7  st:='create user '||p_user||' identified by '||p_passwd||'
  8    default tablespace users temporary tablespace temp';
  9  dbms_output.put_line(st);
 10  execute immediate st;
 11  -- execute immediate 'grant connect to '||p_user;
 12  dbms_output.put_line(p_user||' Created.');
 13  exception
 14   when others then
 15    dbms_output.put_line(sqlerrm);
 16* end;
 17  /

Procedure created.

SQL> exec pr_create_user('abcd', 'abcd');
create user abcd identified by abcd
  default tablespace users temporary
tablespace temp
ORA-01031: insufficient privileges

PL/SQL procedure successfully completed.

SQL> create user abcd identified by abcd
  2    default tablespace users temporary
  3  tablespace temp
  4  /

User created.

SQL> drop user abcd;

User dropped.

Followup:
read
http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html

January 10, 2003 Ajit -- Thanks for the question regarding "Blob, Clob", version 8.1.7
You Asked
We are planning to retire an existing mail/merge package. I am curious to know
if we can replace it by an Oracle/PL-SQL only solution. There are some form
letters (having specific spacing and control characters in them as they are
printed from IBM mainframe) which are merged with transaction data such as
customer name, open balance etc. and the merged letters are then FTP'ed to
mainframe for printing. Daily volume is about 1000 letters. Can we store the
entire letter template as CLOB and then bring it in temp CLOB and manipulate to
insert cust. number, balance at proper places.
Thanks,
Ajit
and we said...
sure -- dbms_lob.write "overwrites" -- so if you had enough whitespace in the
template -- you can "overwrite" a position.  Consider:

ops$tkyte@ORA817DEV> create table t ( x clob );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert into t values (
  2  'Last Name: _________________
  3  Manager:  _________________
  4  Thats it!' );

1 row created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> declare
  2      l_clob  clob;
  3  begin
  4      dbms_lob.createTemporary( l_clob, TRUE );
  5      for c in (select x, dbms_lob.getLength(x) len from t)
  6      loop
  7          for x in ( select * from scott.emp )
  8          loop
  9              dbms_lob.copy( l_clob, c.x, c.len );
 10              dbms_lob.write( l_clob, length(x.ename), 12, x.ename );
 11              if ( x.mgr is not null )
 12              then
 13                  dbms_lob.write( l_clob, length(x.mgr), 41, x.mgr );
 14              end if;
 15
 16              dbms_output.put_line( dbms_lob.substr( l_clob, 250, 1 ) );
 17          end loop;
 18      end loop;
 19  end;
 20  /
Last Name: SMITH____________
Manager:  _7902____________
Thats it!
Last Name: ALLEN____________
Manager:  _7698____________
Thats it!
Last Name: WARD_____________
Manager:  _7698____________
Thats it!
.......
Review & Followup
Rating: 3
CLOB Review January 13, 2003
Reviewer: Ajit Tawde from Boston, MA USA
Thanks Tom. I was not sure if I could manipulate the text inside a CLOB. I can
refer to your sample code for my task now.

January 10, 2003 Renee -- Thanks for the question regarding "SQL join question. Is there a better way?", version 8.1.7
You Asked
Need some SQL advice. The tricky part is, we develop applications in "Forest &
Trees" and use it to connect to Oracle so we cannot use things like cursors,
called procedures, etc., just "straight SQL".

I am joining 2 tables - one with discount info and one with subscriber info.
The dilemma is that subscriber info is built to down 3 levels and discount info
might be built down 2 or 3 levels. When the discount is is not built down all
three levels, I want to "step up" to the second level.

Example:
Subscriber info (table S)
sub   discount sys     prin     agnt
123   XX       8888    1000     10
789   XX       8888    1000     20

Discount info (table D)
discount  sys     prin     agnt
XX        8888    1000     0
XX        8888    1000     10

When I join these 2 tables, I want subscriber 123 joined with the
XX||8888||1000||10 row and subscriber 789 joined with the
XX||8888||1000||0 row.

Our DBA recommended we join as such:

where  D.discount = S.discount
and D.sys = S.sys
and D.prin = S.prin
and D.agnt = decode(D.agnt, S.agnt, S.agnt,D.agnt)

The problem with that is it will join a record like subscriber 123 twice - once
to agnt 10 and once to agnt 0.

My workaround has been using a union.

My first query is joined as:

where  D.discount = S.discount
and D.sys = S.sys
and D.prin = S.prin
and D.agnt = S.agnt

My second query in the union is joined as:

where  D.discount = S.discount
and D.sys = S.sys
and D.prin = S.prin
and D.agnt = 0
and S.discount||S.sys||S.prin||S.agnt not in
(select distinct A.discount||A.sys||A.prin||A.agnt
from discount A)

While this seems to work, I have to believe there's an easier way. I know it is
yucky. Unfortunately, we have a few tables that operate like this. :^/

Update: We can create and drop views as needed. As for the output,
the number of columns from table D will vary. However, we will definitely need
the sys/prin/agnt/discount from the S table and the discount description column
and cost column from the D table.
and we said...
we'll use the old "select a select" trick and hide it in a view in case forest
and trees cannot see the trees for the forest...

Consider:

ops$tkyte@ORA920> create table s ( sub int, discount char(2), sys int, prin int,
agnt int );

Table created.

ops$tkyte@ORA920> create table d ( discount char(2), sys int, prin int, agnt
int, discount_desc varchar2(25), cost number );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into s values ( 123, 'xx', 8888, 1000, 10 );

1 row created.

ops$tkyte@ORA920> insert into s values ( 789, 'xx', 8888, 1000, 20 );

1 row created.

ops$tkyte@ORA920> insert into d values ( 'xx', 8888, 1000, 0,  'A', 100 );

1 row created.

ops$tkyte@ORA920> insert into d values ( 'xx', 8888, 1000, 10, 'B', 500 );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select sys, prin, agnt, discount,
  2         to_number(substr(data,1,14)) cost,
  3         substr(data,15) discount_desc
  4    from (
  5  select sys, prin, agnt, discount,
  6          nvl( (select to_char(nvl(cost,0),'0000000009.99')||discount_desc
  7                  from d
  8                 where sys = s.sys
  9                   and prin = s.prin
 10                   and agnt = s.agnt ),
 11               (select to_char(nvl(cost,0),'0000000009.99')||discount_desc
 12                  from d
 13                 where sys = s.sys
 14                   and prin = s.prin
 15                   and agnt = 0 )
 16              ) data
 17    from s
 18         )
 19  /

       SYS       PRIN       AGNT DI       COST DISCOUNT_DESC
---------- ---------- ---------- -- ---------- -------------------------
      8888       1000         10 xx        500 B
      8888       1000         20 xx        100 A
Review & Followup
Rating: 5
January 10, 2003
Reviewer: Renee Cairns from Omaha, NE USA
I added joins for discount to S.discount in each select w/i select and a
distinct and it worked like a charm.  Ran faster, too.

Will you be donating your brain to science when you depart this mortal
existence?

THANK YOU!

January 10, 2003 Baqir -- Thanks for the question regarding "Tune "UPDATE" query", version 9.2.0.2
You Asked
Tom,
We are running the following query in the OLTP system and taking  long time to
execute.  Please suggest the best possible ways to optimize it. Thanks

UPDATE client_activity_by_agent caba SET num_clients =
(SELECT cnt FROM
(SELECT c.agent_id pid, count(c.client_id) cnt
 FROM client c
 WHERE c.is_active = 1 AND c.tier != 402 AND c.tier != 414
 GROUP BY c.agent_id
) st
WHERE caba.personnel_id = st.pid)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1    956.20     937.69          0    1482575       1979         694
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2    956.20     937.69          0    1482575       1979         694

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 115

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  UPDATE  (cr=1482575 r=0 w=0 time=937693619 us)
    694   TABLE ACCESS FULL CLIENT_ACTIVITY_BY_AGENT (cr=190 r=0 w=0 time=16471
us)
    319   VIEW  (cr=1482384 r=0 w=0 time=937396653 us)
    319    SORT GROUP BY (cr=1482384 r=0 w=0 time=937390526 us)
 390520     INDEX FAST FULL SCAN CLIENT_I_TIER_REGS (cr=1482384 r=0 w=0
time=936053088 us)(object id 75182)

Execution Plan
----------------------------------------------------------
   0      UPDATE STATEMENT Optimizer=CHOOSE (Cost=2 Card=694 Bytes=555
          2)

   1    0   UPDATE OF 'CLIENT_ACTIVITY_BY_AGENT'
   2    1     TABLE ACCESS (FULL) OF 'CLIENT_ACTIVITY_BY_AGENT' (Cost=
          2 Card=694 Bytes=5552)

   3    1     SORT (GROUP BY NOSORT) (Cost=131 Card=1 Bytes=12)
   4    3       INDEX (FAST FULL SCAN) OF 'CLIENT_I_TIER_REGS' (NON-UN
          IQUE) (Cost=131 Card=664 Bytes=7968)

Statistics
----------------------------------------------------------
          0  recursive calls
       1979  db block gets
    1482575  consistent gets
          0  physical reads
     217180  redo size
        785  bytes sent via SQL*Net to client
        909  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
        695  sorts (memory)
          0  sorts (disk)
694    rows processed

what does

UPDATE client_activity_by_agent caba SET num_clients =
(SELECT count(c.client_id) cnt
  FROM client c
 WHERE c.is_active = 1
   AND c.tier != 402
   AND c.tier != 414
   and c.agent_id = caba.personnel_id )

do for you.

is the right answer.  Thanks again for your great help.
and we said...
what does

UPDATE client_activity_by_agent caba SET num_clients =
(SELECT count(c.client_id) cnt
  FROM client c
 WHERE c.is_active = 1
   AND c.tier != 402
   AND c.tier != 414
   and c.agent_id = caba.personnel_id )

do for you.
Review & Followup
Rating: 5
lol January 11, 2003
Reviewer: A reader
great job tom, that is the answer :)

January 10, 2003 -- Thanks for the question regarding "Extract >32k data in single field of XML document", version 8.1.6
You Asked
Hello,

Thanks for taking my question

I have three tables product, prod_att_def & text_data with simplified structure
below.

<PRODUCT>
PRODUCT_ID           NUMBER
PROD_ATT_DEF_ID      NUMBER
PROD_GA50X1          VARCHAR2

<PROD_ATT_DEF>
PROD_ATT_DEF_ID      NUMBER
PROD_GA50X1          VARCHAR2

<TEXT_DATA>
TEXT_ID        NUMBER
TEXT_DATA      CLOB

PROD_ATT_DEF defines the type of data contained in PROD_GA50X1 column of Product
Table. while PROD_GA50X1 in product table contains the data itself. in this
particular case PROD_GA50X1 contains the pointer to TEXT_DATA column row.

I wish to create an XML document with following structure

<PRODUCT_ID>
<PROD_GA50X1>
             <NAME>
             <VALUE>

I create an OBJECT TYPE as following to create tree structure

create or replace type gatype as object(
definition    varchar2(50),
value        varchar2(32767));
/

a function

Create or replace Function get_msds (
        p_ga_name     IN varchar2,
        p_ga_value    IN varchar) return gatype IS
l_description     text_data.description%TYPE;
l_text_data       VARCHAR2(32767);
l_errnum          NUMBER;
l_errmsg          VARCHAR2(32767);
BEGIN
  IF p_ga_name='msds' and p_ga_value is not null then

pims_lob.get_textdata_32k(p_ga_value,l_description,l_text_data,l_errnum,l_errmsg)
;
    return gatype(nvl(p_ga_name,'N/A'),nvl(l_text_data,'N/A'));
  ELSE
    return gatype(nvl(p_ga_name,'N/A'),nvl(p_ga_value,'N/A'));
  END IF;
END;
/
and a view as
create or replace view test(product_id,PROD_GA50X1)
as
select
a.product_id,get_msds(b.PROD_GA50X1,a.PROD_GA50X1)
from product a,prod_att_def b,text_data t
where a.prod_att_def_id=b.prod_att_def_id
and   a.product_id=s.product_id
and   a.prod_desc_id=t.text_id;

I am creating XML document with XMLSQL facility with following query to generate
the document

select * from test;

but since the OBJECT TYPE GATYPE has varchar2 with 32767 limit I can not have
values >32k . Please suggest a solution.

Thanks
Shaleen
and we said...
Don't use a varchar2 -- use the CLOB.  CLOBs can be upto 4 gig and every
language I know of can manipulate them.

Thats what the CLOB is for.
Review & Followup
Rating: 4
extract has any limitation while using with getclobval() January 09, 2003
Reviewer: kumar from San Diego
Hi Tom,

I am using the below SQL query the reuslt set is showing only around 11K of that
XML document. But that document has around 75K . Well I am using Benthic tool to
check the result set. Is there any known limitation for this result set while
using these extract and getclobval()

Query :-

select
a.xml_message.extract('/child::PublicSpeakMessage/child::Payload/child::Attachmen
t[position()=2]','xmlns="PublicSpeakMessage.xsd"').getClobVal()
from table_temp a;

Followup:
never heard of "benthic"

try it in SQLPlus

SQL> set long 100000
SQL> select ....

Rating: 2
tried to insert this result set into another table January 10, 2003
Reviewer: Kumar from San Diego
Hi tom,

I have tried to use the same select statement with extract and getclobval() to
insert into another table. I did not get the full xmldoc. I got only 11K exactly
the result set got inserted into the table. Any inputs on this particualr type
of select extract and getclobval() usage.

Followup:
nope, one would need a full reproducible test case for me to look at -- i don't
have one.

Rating: 2
This selct is doing a classic Wrap up to exactly 64K January 10, 2003
Reviewer: Kumar from San Diego
Tom,
Well How can I send that entire whole 110K xmldocument to you? BTW I did some
research with that select extract and getclobval() command from a table. What I
found is if the size of the document is bigger than 64K ( ie greater than 65534
bytes) then it first skips those and producing the remained result set.

Here is the math I did I used a document size of 34 K I got full and then used
75KB then this select fetch exctly minus that 65534 ( 64K) and then produced the
remained doc.

Can you through some light on the similar statement with select extract and use
getclobval() and try to use this query on any xmldocument bigger than 75K. Tell
me did you get the entire document or did you get the remainder of after the 64K
part of the document.

Followup:
well, at this point, sounds like a bug - course of action would be to put
together a simple reproducible testcase (sounds like you have one) -- as small
as possible and open a tar with support.  You can ftp them the files.

January 11, 2003 David -- Thanks for the question regarding "Creating an unusable index", version 8.1.7
You Asked
I am looking for a way of creating unusable local indexes on a table, without
creating them and then making them unusable, but can't find a way of doing so.

The reason for this is that I have some large, range-partitioned fact tables to
which I want to add some local bitmap indexes. In order to minimize the amount
of physical I/O I would ideally build all indexes at the same time, and have
each index-creation accessing the same partition at the same time. The
underlying table's partitions are on average about one-half the size of the db
block buffer.

eg., for partitions Y2000_M01 ... Y2002_M12, the indexes would each build their
corresponding partition for Y2002_M01, then when all are complete I could move
on to Y2002_M02.

Experience has shown that if I just let the indexes be created at the same time,
in parallel, the process is slower due to the repeated re-loading of table data
that has been aged out of the buffer. As the index-creations proceed, they can
become out-of-step with each other and as soon as more than two partitions are
being accessed on the underlying table we incur unneccessary physical I/O's.

Is there a way of creating an unusable index?
and we said...
ops$tkyte@ORA817DEV> CREATE TABLE t
  2  (
  3    x int ,
  4    collection_year int
  5  )
  6  PARTITION BY RANGE (COLLECTION_YEAR) (
  7    PARTITION PART_95 VALUES LESS THAN (1996) ,
  8    PARTITION PART_96 VALUES LESS THAN (1997) ,
  9    PARTITION PART_97 VALUES LESS THAN (1998) ,
 10    PARTITION PART_98 VALUES LESS THAN (1999) ,
 11    PARTITION PART_99 VALUES LESS THAN (2000) ,
 12    PARTITION PART_00 VALUES LESS THAN (2001) ,
 13    PARTITION PART_01 VALUES LESS THAN (MAXVALUE)
 14  )
 15  ;

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create index t_idx1
  2  on t(x) local
  3  UNUSABLE;

Index created.

see
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76936/dbms_pcl.htm#998100
for a package that may be of some use to you as well....
Review & Followup
Rating: 5
Many thanks for the name of the useful package December 31, 2002
Reviewer: A reader from Switzerland
Happy New Year to you Tom.

Rating: 4
How about global indexes???? January 11, 2003
Reviewer: A reader
I have a package that performs this functionality.  It can (re)build multiple
(or all) partitions on a single index, multiple partitions of multiple indexes,
or all indexes on one partition at the same time.  While this works great for
local indexes, I find I pay serious time penalties when I to apply it to global
indexes.

While I realize the "span" of each partition on a global index is larger than
that of a local partition, I never envisioned such a difference (my numbers
below):

Oracle 8.1.7.4 and 9.2.0.1
On a 2+ billion row table across 104 partitions (range)

Create local B*Tree index on one column: approx. 7 hours
Create unusable/rebuild parallel same index: just under 1 hour
Create same index as global with 104 partitions: almost 11 hours
Create unusable/rebuild parallel global index: runs for 30 hours and has only
rebuilt 60 of the partitions before I kill it!!!

Another test (this one more sane):
On a 18 million row with 18 partitions (range)

Create global index: 1 minute, 20 seconds consistently
Create unusable/rebuild parallel 9 partitions at a time: 10 minutes
Create unusable/rebuild parallel all partitions at once: 38 to 45 minutes!!!!

Why is it that completely dropping and recreating the global index is faster
than rebuilding each partition?  It seems that the more global partitions I try
to rebuild at the same time, it hurts me more (the UPDATE GLOBAL INDEXES greatly
heals the pain though).

I know it is not and I/O or CPU issue.  I have tried playing with some of the
parallel_* init.ora parameters but no luck.  Any creative thoughts on this??

Followup:
Think about what must happen.

Say you have a 10 Xbyte (X=tera,giga,whatever) table.

It is in 10 1-Xbyte partitions

You want to build a locally partitioned index.  Great -- we have to do 10
1-Xbyte full scans to build those partitions.  Better yet -- there is ZERO IO
contention when doing this because each index partition reads its own unique set
of 1-Xbyte data segments.

Now, you want to build a globally partitioned index (with N partitions).  Great
-- now we have to do N 10 Xbyte full scans.  Suppose N = 10, we have to read
and process 100Xbyte of data - as opposed to the locally partitioned index
whereby we have to do 10Xbyte of reads.  Even worse -- each of the N full scans,
if done concurrently, all content with eachother for the same exact data!  IO
contention to the max

It is pure mathematics.

It *is* IO (logical IO if not physical).  It *is* CPU.  You are processing an
order of magnitude more data by each "slave".

January 11, 2003 SACHIN -- Thanks for the question regarding "UPDATE PRIMARY KEY", version 8
You Asked
Dear tom,
   please let me know how can i update a record in the primary
key field which behaves as a foreign key to other tables without
altering (disabling) the primary/foreign constraint???? please
reply sooon

yours faithfully,

SACHIN JAIN
and we said...
See
http://osi.oracle.com/~tkyte/update_cascade/index.html
for one solution.

Alternatively, you can use deferred constraints as well.  If you
create the foreign keys "deferrable", you can

set constraints deferred;
update parent_table;
update child_table(s);
commit;

and the constraint will be verified at the commit instead of at
the statement level, allowing you to "cascade" the update
yourself.
Review & Followup
Rating: 3
October 24, 2002
Reviewer: Sagi from India
Hi Tom,

SQL> select table_name, constraint_name, DEFERRABLE, DEFERRED, VALIDATED
  2  from user_constraints
  3  where table_name in ('EMP', 'DEPT')
  4  ORDER BY 1 ;

TABLE_NAME CONSTRAINT_NAME   DEFERRABLE  DEFERRED  VALIDATED
------------------------------ -----------------------------
DEPT        PK_DEPTNO   NOT DEFERRABLE IMMEDIATE VALIDATED
EMP         PK_EMPNO    NOT DEFERRABLE IMMEDIATE VALIDATED
EMP         FK_DEPT     NOT DEFERRABLE IMMEDIATE VALIDATED
EMP        SYS_C001402  NOT DEFERRABLE IMMEDIATE VALIDATED

I tried:

SQL> set constraints pk_dept, fk_dept deferred ;
set constraints pk_dept, fk_dept deferred
*
ERROR at line 1:
ORA-02447: cannot defer a constraint that is not deferrable

Check the Doc. It says:

Specify NOT DEFERRABLE to indicate that this constraint is checked at the end of
each DML statement. If you do not specify either word, then NOT DEFERRABLE is
the default.

INITIALLY IMMEDIATE
 Specify INITIALLY IMMEDIATE to indicate that at the start of every transaction,
the default is to check this constraint at the end of every DML statement. If
you do not specify INITIALLY, INITIALLY IMMEDIATE is the default.

INITIALLY DEFERRED
 Specify INITIALLY DEFERRED to indicate that this constraint is DEFERRABLE and
that, by default, the constraint is checked only at the end of each transaction.

Restrictions:

You cannot defer a NOT DEFERRABLE constraint with the SET CONSTRAINT(S)
statement.

You cannot specify either DEFERRABLE or NOT DEFERRABLE if you are modifying an
existing constraint directly (that is, by specifying the ALTER TABLE ... MODIFY
constraint statement).

You cannot alter a constraint's deferrability status. You must drop the
constraint and re-create it.

If the constraint is either "INITIALLY IMMEDIATE" or "INITIALLY DEFERRED" then
what is the need to use the SET CONSTRAINTS clause. Because anyway the check is
done at the end of DML or Transaction. Right?

I would appreciate if you could give us an example.

Thanx in advance.

Regards,
Sagi.

Followup:
an example of what exactly??? you just demonstrated that the documentation is
accurate:

...
Restrictions:

You cannot defer a NOT DEFERRABLE constraint with the SET CONSTRAINT(S)
statement.
......

Rating: 2
October 24, 2002
Reviewer: Sagi from India
Hi Tom,

If we create constraints on EMP and DEPT (Both INITIALLY DEFERRED) or
If we create RI constraint on EMP (INITIALLY DEFERRED) then

directly we can update the master (DEPT) and child (EMP).

So when do we use the SET CONSTRAINTS [<ALL>/<Constraint_Name>] DEFERRED.

I tried the below

SQL> CREATE TABLE DEPT1 AS SELECT * FROM SCOTT.DEPT ;

SQL> CREATE TABLE EMP1 AS SELECT * FROM SCOTT.EMP ;

SQL> ALTER TABLE DEPT1
  2  ADD CONSTRAINT PK_DEPTNO1 PRIMARY KEY(DEPTNO) INITIALLY DEFERRED ;
Table altered.

SQL> ALTER TABLE EMP1
  2  ADD CONSTRAINT FK_DEPTNO1 FOREIGN KEY (DEPTNO)
  3  REFERENCES DEPT1(DEPTNO) INITIALLY DEFERRED ;
Table altered.

SQL> UPDATE DEPT1
     SET DEPTNO=DEPTNO+1 ;

SQL> UPDATE EMP1
     SET DEPTNO=DEPTNO+1 ;

and it allows me to directly update without using SET CONSTRAINTS clause. Could
you give an example where we use SET CONSTRAINT clause. Please.

Regards,
Sagi

Followup:
umm, when the constraints are deferrable initially immediate would be a case.

when you wanted to verify that your update is commitable (eg: in your example,
you could use set constraints to test the constraints to see if the commit will
succeed or fail)

Rating: 2
October 25, 2002
Reviewer: A reader
Hi Tom,

Sorry, but could not catch you. Could you please demonstrate.

Thanx in advance.

Regards,
Sagi

Followup:
try the commands yourself.  Just  *TRY* them.

Or search for

"set constraints"

(with quotes) for other threads where this has been discussed.  the examples of
using this are out there.

Rating: 2
What about in a stored function? November 13, 2002
Reviewer: Doron Rippel from Canada
Why does it not work in a stored function?
When I try to compile the following stored function:
-------------------------------------------------------
CREATE OR REPLACE FUNCTION ttt
return number
IS
BEGIN
        SET CONSTRAINTS DEFERRED;
        return 0;
END ttt;
-------------------------------------------------------
I get:

PLS-00103: Encountered the symbol "CONSTRAINTS" when expecting one of the
following:   transaction

What's wrong? How can I defer constraints in a stored function?

Thanks

Doron

Followup:
xecute immediate 'SET CONSTRAINTS ALL DEFERRED'

Rating: 5
deferral November 14, 2002
Reviewer: mo
TOm:

1. do you recommend to make all primary key/foreign key
constraints deferred when I create them?

2.  If I did not do that, and decided to defer it after creation can I do
something like "Alter table emp set constraint xxx deferred".

Thank you,

Followup:
1) i recommend you understand the implications (almost none on a fkey, totally
different type of index with a pkey) and use your judgement

2) no, you need to recreate the constraint.

Rating: 5
Now it works! November 14, 2002
Reviewer: Doron Rippel from Canada
Thanks.
I struggled with this problem for several days and could not find proper
documentation or examples for deferring constraints within a stored function.

Rating: 5
deferral November 15, 2002
Reviewer: mo
Tom:

well as you know judgements keep changing as system requirements keep
developing. the initial database design is never final.

anyway it seems to that you always should defer a foreign key constraint when
you need to update parent table.

Thanks,

Followup:
and if you need to update the parent table -- you have chosen the wrong key for
the parent table ;)

Rating: 5
Very informative; Please guide January 11, 2003
Reviewer: Yogeeraj from Mauritius
Hello,

I did the following exercise (before implementation in our Forms applications):
=========================================================================
yd@yddb.MU> alter table drt555 drop constraint fk_drt555_1;

Table altered.

Elapsed: 00:00:00.11
yd@yddb.MU> ALTER TABLE DRT555 ADD  CONSTRAINT FK_DRT555_procnum_lcncod
 FOREIGN KEY (PROCNUM, LCNCOD)
  REFERENCES DRT540 (PROCNUM, LCNCOD) deferrable disable;

Table altered.

Elapsed: 00:00:00.07
yd@yddb.MU> alter table drt555 modify constraint fk_drt555_procnum_lcncod enable
novalidate;

Table altered.

Elapsed: 00:00:00.09
yd@yddb.MU> alter table drt555 enable constraint fk_drt555_procnum_lcncod;

Table altered.

Elapsed: 00:00:00.04
yd@yddb.MU>

yd@yddb.MU> update drt555 set procnum,='888'
  2* where procnum='777' and lcncod='PHX'
update drt555 set procnum='888'
*
ERROR at line 1:
ORA-02291: integrity constraint (YD.FK_DRT555_PROCNUM_LCNCOD) violated - parent
key not found

Elapsed: 00:00:00.02
yd@yddb.MU>

===================================================
that won't work until we defer the constraints:
   set constraints all deferred;
It makes it so that any constraint that is deferrable (to be checked at COMMIT,
not at the statement level) is in fact deferred.
===================================================
yd@yddb.MU> set constraints all deferred;

Constraint set.

Elapsed: 00:00:00.01
yd@yddb.MU> update drt555 set procnum='888'
  2* where procnum='777'

2 rows updated.

Elapsed: 00:00:00.02
yd@yddb.MU> commit;
commit
*
ERROR at line 1:
ORA-02091: transaction rolled back
ORA-02291: integrity constraint (YD.FK_DRT555_PROCNUM_LCNCOD) violated - parent
key not found

Elapsed: 00:00:00.03
yd@yddb.MU>

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
and then tried:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

yd@yddb.MU> alter table drt555 modify constraint fk_drt555_procnum_lcncod
initially deferred;

Table altered.

Elapsed: 00:00:00.03
yd@yddb.MU> update drt555 set procnum='888'
  2  where procnum='777'
  3  ;

2 rows updated.

Elapsed: 00:00:00.02
yd@yddb.MU> commit;
commit
*
ERROR at line 1:
ORA-02091: transaction rolled back
ORA-02291: integrity constraint (YD.FK_DRT555_PROCNUM_LCNCOD) violated - parent
key not found

Elapsed: 00:00:00.01
yd@yddb.MU>

============================================================

Is it true that if we create a constraint as "initially deferred", we do not
need to use:
  "set constraints all deferred;"
before doing an update?

What is the overhead?

Is this the only "advantage" of defining "initially deferred" constraints?

thank you in advance for you explanation and precious time.

Best Regards
Yogeeraj

PS. In which month will your most-awaited new book be available?  thanks

Followup:
it is true that if you set the constraint to initially deferred -- that as the
name implies -- it will be deferred initially and you need not set all
constraints or that constraint to deferred.  That is correct (and apparently
obvious?)

Overhead of what?  You benchmark to find overhead -- use
http://asktom.oracle.com/~tkyte/runstats.html
and simulate it.

"advantage" is a two way street.  This is neither an advantage or disadvantage.
It is a setting.  Do you want it initially deferred?  Most people do not -- it
fails the commit and I'll betcha most applications would not deal with that
nicely -- they are not expecting a commit to fail.  Most people would defer the
constraint when needed -- especially since a primary key should never in a
billion years be updated.

springtime is the closest you'll get me to commit to.

Rating: 5
DML_table_expression_clause November 17, 2003
Reviewer: A reader
In 9iR2 SQL Reference for Update statement:

"The ONLY clause applies only to views. Specify ONLY syntax if the view in the
UPDATE clause is a view that belongs to a hierarchy and you do not want to
update rows from any of its subviews."

I don't quite understand this. Could you provide an example?

Thanks.

January 11, 2003 Scott -- Thanks for the question regarding "ref cursor performance", version 8.1.7
You Asked
G'day Tom

I entered a search string on your website:
ref cursor implicit

Any it returned the following information

{ref} and {cursor} and {implicit}
Approximately 26 records found.

However there were only 20 results shown.

I was curious to know how do you extrapolate your approximate count?

The reason I tried this search was because I'm aware of your liking towards
implicit cursors over explicit cursor, but I would like to know your views on
using ref cursors, and how to best implement them.
I have a search screen with various criteria for selecting from a view over
multiple tables, and I use a ref cursor to build the query best suited to the
criteria entered.
I also would like to display the result set 20 rows at a time.
What would be the best approach?

Thanks.
and we said...
I use Oracle Text and it has a "guess how many".

On a relational only query -- you can use explain plan.
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4456921593765
has an example of that.

A ref cursor should be used

a) to return a result set to a client.
b) when the query predicate is not knowable at compile time -- but the select
   list is (dynamic sql with a known set of outputs)
c) when you have one of N queries with fixed outputs you will pick from at run
   time to run

otherwise, you should not use them.  Your condition sounds like (b).

That link above also has a link to "how to paginate"
January 11, 2003 Balachandra -- Thanks for the question regarding "Better way to write Query", version 8.1.7
You Asked
Hi Tom,
After going through your book and Philosophy that avoid writing pl/sql unless it
ia absolutely necessary I want to know from you if you have a better sql.
I have tables
Sample                       Sample_detail
Permit                        Lab_no
Lab_no                        constituent(eg Cu,Zn,Pb,Ni,Ag,Cr,Cd)
Sample_date                   mgl
                              mgl_over
                              lbs
                              lbs_over
The client wants me to display the result as
Permit Lab Sample_date Cu_mgl,Cu_mgl_Over,Cu_lbs,Cu_Lbs_over,Zn_mgl...

Is it possible using sql alone.
 Thanks Tom
and we said...
select s.permit, s.lab_no, s.sample_date,
       max(decode( sd.constituent, 'Cu', mgl, null )) cu_mgl,
       max(decode( sd.constituent, 'Cu', mgl_over, null )) cu_mgl_over,
       max(decode( sd.constituent, 'Cu', lbs, null )) cu_lbs,
       max(decode( sd.constituent, 'Cu', lbs_over, null )) cu_lbs_over,
       max(decode( sd.constituent, 'Zn', mgl, null )) zn_mgl,
       max(decode( sd.constituent, 'Zn', mgl_over, null )) zn_mgl_over,
       max(decode( sd.constituent, 'Zn', lbs, null )) zn_lbs,
       max(decode( sd.constituent, 'Zn', lbs_over, null )) zn_lbs_over,
       ....
  from sample s, sample_detail sd
 where s.lab_no = sd.lab_no
 group by s.permit, s.lab_no, s.sample_date;

Since you have my book -- look at the chapter on analytic functions -- I have a
generic "pivot" routine in there as well.
January 11, 2003 Raymond -- Thanks for the question regarding "Which is better "IF" or "OR" ", version 8.1.7
You Asked
In an SQL which is better in terms of performance ?

if x= 'A' then
   Select statement from table
elsif x = 'B' then
   Select statement from table

OR

writing a single statement with an OR
    Select ..
    from table
    where ...
    .. ( ( x = 'A' )
         OR
         ( x = 'B' and ...)
        )

Thks for answering.
Raymond.
and we said...
If the select statements differ radically in their predicates -- using a ref
cursor:

declare
   type rc is ref cursor;
   l_cursor rc;
begin
   if ( condition )
   then
      open l_cursor for select * from emp where empno = X;
   else
      open l_cursor for select * from emp where deptno = Y;
   end if;
   loop
       fetch l_cursor .....

would be the best approach as the optimizer would have two queries to optimize
and each would be able to take advantage of indexes and such -- if you tried to
do it in one big query:

  select * from emp
   where (condition AND empno = X )
      or (NOT condition AND deptno = Y)

the plan for that query would not be nearly as optimal.
January 12, 2003 Jay -- Thanks for the question regarding "DBMS_SESSION.SET_CLOSE_CACHED_OPEN_CURSORS", version 9.0.1
You Asked
Hi Tom: -
What is DBMS_SESSION.SET_CLOSE_CACHED_OPEN_CURSORS..?
It's defined in one of our apps, and shows up in the alert log as being
obsolete.
Once it even took the instance down.
Nevertheless, both it and the alter session sister cmd can be issued with no
harm done, so i'm wondering a) - what does/did this command do?           b) -
can i safely remove it from the package
(the package works fine with it commented out, so I'm curious whether other,
unseem & unforseen events might occur instead, thus the question.

As always thanks & regds Jay
and we said...
It should not take the instance down but it is totally obsolete.

search for close_cached_open_cursors on this site.  It was a setting in 7.x the
would cause plsql to release all cached cursors whenever you committed.  In 7.x
and before -- when plsql ran a cursor -- it would keep it open.  If you had open
cursors set to 100, ran alot of plsql that opened and cached lots of cursors and
then ran some more client code that opened some cursors - you could run out.

Setting this would have plsql close them upon commit -- freeing them up.

In 8.x the algorithm was changed.  Now plsql caches them until you need them --
so if your client tries to open a cursor but plsql was hogging them all -- plsql
will give up one for you automagically.  hence this setting is gone.
Review & Followup
Rating: 5
I understand now - gr. help January 12, 2003
Reviewer: Jay

January 12, 2003 Pushkar -- Thanks for the question regarding "PL/SQL buffer overflow, limit 1000000 bytes", version 8.1.7
You Asked
Hi Tom

Following is a procedure I have written which generated the abovementioned
error.

DECLARE

my_data1     varchar2(50);
my_data      varchar2(1000);
val_acct_nbr     varchar2(50);
val_adj_amt varchar2(50);
val_ind varchar2(50);
val_adj_dt varchar(50);
fileHandler utl_file.file_type;

CURSOR my_acct IS
SELECT A.ACCT_NBR, A.ADJ_AMT, A.ADJ_CR_DEBIT_IND, A.ADJ_DATE FROM ADHOC_VIEW.ADJ
A
WHERE A.FNCL_TRNSCT_TYPE_ID = 'W1' /*AND A.ACCT_NBR in ('0005814346',
'0010380951', '0013448887')*/;

BEGIN
dbms_output.disable;Dbms_Output.Enable (1000000);
OPEN my_acct;
LOOP

    FETCH my_acct INTO val_acct_nbr, val_adj_amt, val_ind, val_adj_dt;
       EXIT WHEN my_acct%NOTFOUND;

        for my_var in (SELECT b.acct_nbr||','
        ||b.curr_bal_amt||','
        ||b.ar_30_day_bal_amt||','
        ||b.ar_60_day_bal_amt||','
        ||b.ar_90_day_bal_amt||','
        ||b.ar_over_90_day_bal_amt||','
        ||b.last_bill_dt||','
        ||val_adj_amt||','
        ||val_ind||','
        ||val_adj_dt
         as mydata
        FROM ADHOC_VIEW.CURR_BILL b
        WHERE b.acct_nbr = val_acct_nbr)

loop

DBMS_OUTPUT.PUT_LINE(my_var.mydata);

end loop;

END LOOP;

CLOSE my_acct;

END;

Please advice as to how I can work around this error. Is UTL_FILE the only
workaround?

Thanks
Pushkar
and we said...
why not just run:

SELECT b.acct_nbr||','
        ||b.curr_bal_amt||','
        ||b.ar_30_day_bal_amt||','
        ||b.ar_60_day_bal_amt||','
        ||b.ar_90_day_bal_amt||','
        ||b.ar_over_90_day_bal_amt||','
        ||b.last_bill_dt||','
        ||a.adj_amt||','
        ||a.adj_cr_debit_ind||','
        ||a.adj_dt
   FROM ADHOC_VIEW.ADJ A, ADHOC_VIEW.CURR_BILL b
  WHERE b.acct_nbr = a.acct_nbr
    and A.FNCL_TRNSCT_TYPE_ID = 'W1';

at the sqlplus prompt???  why would you even consider using your own nested
loops join like that?  databases were born to join -- let them do it for you.
Review & Followup
Rating: 3
Thanks Tom! January 13, 2003
Reviewer: Pushkar from MO
I was extremely impressed by the quick response.

January 12, 2003 star -- Thanks for the question regarding "problems in pass arguments", version 9.2.0
You Asked
Hi, Tom!

  Sorry for my poor English, I hope you understand me.

  I have a table TEST:

    CREATE TABLE TEST
     ( Doc_ID  NUMBER(10),
       Item_ID NUMBER(3),
       Item_Data VARCHAR2(2000),
       CONSTRAINT  Ctrl_TEST PRIMARY KEY (Doc_ID,Item_ID) )

When I Search some Doc_ID,I pass some argument pairs (Item_ID,Item_Data):

  IF there are two pairs of (Doc_ID,Item_ID),I define SP like:

CREATE OR REPLACE
   PROCEDURE SearchTest( Item_ID1 IN NUMBER,
                         Item_Data1 IN VARCHAR2,
                         Item_ID2 IN NUMBER,
                         Item_Data2 IN VARCHAR2 )
   AS
   test_rec test%ROWTYPE;
   TYPE TestType IS REF CURSOR RETURN test_rec%TYPE;
   ...
BEGIN
   OPEN test_cv FOR
    select * from test where doc_id in
      (select distinct doc_id from test
        where doc_id in
             ( select doc_id from test where item_id = Item_ID1
              and item_data = Item_Data1
              INTERSECT
              select doc_id from test where item_id = Item_ID2
              and item_data = Item_Data2 ) )
           order by doc_id,item_id;
   LOOP
    ....
   end loop;
   close test_cv;
END;

  IF there are three pairs of (Doc_ID,Item_ID), I define SP like:

CREATE OR REPLACE
   PROCEDURE SearchTest( Item_ID1 IN NUMBER,
                         Item_Data1 IN VARCHAR2,
                         Item_ID2 IN NUMBER,
                         Item_Data2 IN VARCHAR2,
                         Item_ID3 IN NUMBER,
                         Item_Data3 IN VARCHAR2 )

   AS
   test_rec test%ROWTYPE;
   TYPE TestType IS REF CURSOR RETURN test_rec%TYPE;
   ...
BEGIN
   OPEN test_cv FOR
    select * from test where doc_id in
      (select distinct doc_id from test
        where doc_id in
             ( select doc_id from test where item_id = Item_ID1
              and item_data = Item_Data1
              INTERSECT
              select doc_id from test where item_id = Item_ID2
              and item_data = Item_Data2
              INTERSECT
              select doc_id from test where item_id = Item_ID3
              and item_data = Item_Data3 ))
           order by doc_id,item_id;
   LOOP
    ....
   end loop;
   close test_cv;
END;

 The problem is i don't know how many argument pairs will have until the
runtime. The stored procedure is called by java program. Can I define only one
procedure for all this situation ? how to do ? What is the best way?
and we said...
Well, that would be the least efficient sort of query I could come up with in
order to do that query.  Why would you not just query:

select *
  from test
 where (item_id = item_id1 and item_data = item_data1)
    or (item_id = item_id2 and item_data = item_data2)
 order by doc_id, item_id

??  You never need to DISTINCT a subquery (in Oracle -- sqlserver maybe, not in
a standard database).  You won't get "echoes" -- duplicates.  doing the triple
query would be less them optimal.

Also -- never use a ref cursor as you are.  You use ref cursors to return result
sets or write some generic top level code.  If you really do what you show
above, just:

  for x in ( select .... )
  loop
     ...
  end loop;

don't write all of that code!

What I would do is this:

create global temporary table in_list_tbl ( item_id number, item_data
varchar2(4000) )
on commit delete rows;
grant all on in_list_tbl to WHOMEVER;

create or replace package demo_pkg
as
   type rc is ref cursor;
   procedure get_data( p_cursor in out rc );
end;
/

create or replace package body demo_pkg
as

procedure get_data( p_cursor in out rc )
as
begin
    open p_cursor
    for
    select *
      from test
     where (item_id,item_data) in ( select item_id, item_data from in_list_tbl
);
end;

end;
/
grant execute on demo_pkg to WHOMEVER;

And then the java would look like this;

public static void main (String args [])
throws SQLException, ClassNotFoundException
{
   String query  = "begin "+
                        "ops$tkyte.demo_pkg.get_data( ? ); "+
                    "end;";

   String insert = "insert into ops$tkyte.in_list_tbl( item_id, item_data )
values (?,?)";

   DriverManager.registerDriver
        (new oracle.jdbc.driver.OracleDriver());

   Connection conn=
       DriverManager.getConnection
       ("jdbc:oracle:thin:@aria-dev:1521:oraxxx",
         "scott", "tiger");

   conn.setAutoCommit(false);

   PreparedStatement pstmt = conn.prepareStatement( insert );
   ((OraclePreparedStatement)pstmt).setExecuteBatch(100);
   for( int i = 0; i < 50; i++ )
   {
        pstmt.setInt( 1, i );
        pstmt.setString( 2, i+"" );
        pstmt.executeUpdate();
   }
   ((OraclePreparedStatement)pstmt).sendBatch();

   CallableStatement  cstmt = conn.prepareCall(query);
   cstmt.registerOutParameter(1,OracleTypes.CURSOR);
   cstmt.execute();

   ResultSet rset = (ResultSet)cstmt.getObject(1);

   for(int i = 0;  rset.next(); i++ )
        System.out.println( "rset " + rset.getString(1) );
......

That is how I would approach this problem in the easiest way possible.
Review & Followup
Rating: 3
Think the SQL gives different results January 09, 2003
Reviewer: Gary from Sydney, Australia
select * from test where doc_id in
 (select distinct doc_id from test
  where doc_id in
      ( select doc_id from test where item_id = Item_ID1
        and item_data = Item_Data1
        INTERSECT
        select doc_id from test where item_id = Item_ID2
        and item_data = Item_Data2 ) )
order by doc_id,item_id;

is not equivalent to

select *
  from test
 where (item_id = item_id1 and item_data = item_data1)
    or (item_id = item_id2 and item_data = item_data2)
 order by doc_id, item_id

but more like

select * from test t
where exists
  (select 1 from test t1
  where t1.item_id = Item_ID1
  and t1.item_data = Item_Data1
  and t1.doc_id = t.doc_id)
and
  (select 1 from test t2
  where t2.item_id = Item_ID2
  and t2.item_data = Item_Data2
  and t2.doc_id = t.doc_id)

That is, return all the rows for a document id where that document has entries
for both search criteria.

For the following data :

Doc Id    ItemID  ItemData   Comment
1         A       abc        first
1         B       bcd        second
1         C       cde        third
2         A       abc        fourth
2         C       cde        fifth

The original select using 'A'/'abc' and 'B'/'bcd' would produce the rows
commented 'first','second','third' whereas your select would give 'first',
'second' and 'fourth'

The original query did have an extra level in it as this works just as well :
select * from test where doc_id in
      ( select doc_id from test where item_id = Item_ID1
        and item_data = Item_Data1
        INTERSECT
        select doc_id from test where item_id = Item_ID2
        and item_data = Item_Data2 ) )
order by doc_id,item_id;

Now when the optimizer get smart enough to work that out for itself, we can
throw away most of the performance manuals :)

For a generic solution, I think the temp table is necessary.
You pick all the documents where the number of matches to the temp_tbl is the
same as the number records in the temp_tbl

select t.*
from test t,
     (select count(distinct item_id, item_data) cnt
      from temp_tbl) t1
where t1.cnt =
    (select count(distinct t2.item_id, t2.item_data)
    from test t2, temp_tbl t3
    where t2.doc_id = t.doc_id
    and t2.item_id = t3.item_id
    and t2.item_data = t3.item_data)

Followup:
ahh - you are right -- sorry about that.  good catch.  the intersect thru me for
a loop there.

Rating: 4
Very useful for selecting something that matches all criteria January 10, 2003
Reviewer: Eric Givler from Harrisburg, PA
The 2nd solution provided had a little "glitch" in the count:

scott@test.NRBITS09> run
  1  select t.*
  2  from test t,
  3       (select count(distinct item_id || ',' || item_data) cnt
  4        from in_list_tbl) t1
  5  where t1.cnt =
  6      (select count(distinct t2.item_id || ',' || t2.item_data)
  7         from test t2
  8            , in_list_tbl t3
  9        where t2.doc_id = t.doc_id
 10          and t2.item_id = t3.item_id
 11*         and t2.item_data = t3.item_data )

    DOC_ID    ITEM_ID ITEM_DATA            ITEM_INFO
---------- ---------- -------------------- --------------------
         1          1 abc                  first
         1          2 bcd                  second
         1          3 cde                  third

or...

scott@test.NRBITS09>  select t.*
  2   from test t,
  3        (select count(*) kount from in_list_tbl ) k
  4  where k.kount = ( select count(*)
  5               from test t2
  6       , in_list_tbl t3
  7   where t2.doc_id = t.doc_id
  8     and t2.item_id = t3.item_id
  9     and t2.item_data = t3.item_data )
 10
scott@test.NRBITS09> /

    DOC_ID    ITEM_ID ITEM_DATA            ITEM_INFO
---------- ---------- -------------------- --------------------
         1          1 abc                  first
         1          2 bcd                  second
         1          3 cde                  third

3 rows selected.

or this??

scott@test.NRBITS09> select *
  2  from test t
  3  where not exists
  4    (
  5       select item_id, item_data
  6      from in_list_tbl
  7    minus
  8    select t2.item_id, t2.item_data
  9     from test t2
 10        , in_list_Tbl ilt
 11    where ilt.item_id = t2.item_id
 12      and ilt.item_data = t2.item_data
 13      and t2.doc_id= t.doc_id
 14    )
 15
scott@test.NRBITS09> /

    DOC_ID    ITEM_ID ITEM_DATA            ITEM_INFO
---------- ---------- -------------------- --------------------
         1          1 abc                  first
         1          2 bcd                  second
         1          3 cde                  third

3 rows selected.

or???

scott@test.NRBITS09> edit
Wrote file afiedt.buf

  1  select t.*
  2    from test t
  3   where not exists
  4      (
  5       select 'x'
  6         from in_list_tbl ilt
  7        where not exists
  8           (
  9            select 'x'
 10              from test t2
 11             where t2.doc_id = t.doc_id
 12               and t2.item_id = ilt.item_id
 13               and t2.item_data = ilt.item_data )
 14*     )
scott@test.NRBITS09> /

    DOC_ID    ITEM_ID ITEM_DATA            ITEM_INFO
---------- ---------- -------------------- --------------------
         1          1 abc                  first
         1          2 bcd                  second
         1          3 cde                  third

3 rows selected.

Rating: 5
write very complicated query string January 12, 2003
Reviewer: zstar from china
I know that i have made a mistake for my query string. It should be:
   select * from test where doc_id in
             ( select doc_id from test where item_id = Item_ID1
              and item_data = Item_Data1
              INTERSECT
              select doc_id from test where item_id = Item_ID2
              and item_data = Item_Data2 )
           order by doc_id,item_id;

 For another query by using temp table, if there are many arguments(such as
(item_id1,item_data1,..,item_id20,item_data20, ..), I should write very
complicated query string , isn't it?

I do like your website, It's very useful.

Followup:
yes, you would put the item_idN's and item_dataN's into a temp table and then
you can query as such using sets:

scott@ORA920> create global temporary table temp ( item_id number, item_data
varchar2(20) );

Table created.

scott@ORA920> insert into temp values ( 7782, 'CLERK' );

1 row created.

scott@ORA920> insert into temp values ( 7839, 'MANAGER' );

1 row created.

scott@ORA920>
scott@ORA920> select doc_id, item_id, item_data, ename
  2    from test
  3   where doc_id in ( select doc_id
  4                       from test
  5                      where item_id = 7782 and item_data = 'CLERK'
  6                  INTERSECT
  7                     select doc_id
  8                       from test
  9                      where item_id = 7839 and item_data = 'MANAGER' )
 10  /

    DOC_ID    ITEM_ID ITEM_DATA ENAME
---------- ---------- --------- ----------
        10       7839 MANAGER   CLARK
        10            PRESIDENT KING
        10       7782 CLERK     MILLER

scott@ORA920> select doc_id, item_id, item_data, ename
  2    from test
  3   where doc_id in ( select doc_id
  4                       from ( select doc_id,
  5                                     count(*) over ( partition by doc_id )
cnt
  6                                from test
  7                               where (item_data,item_id) in ( select
item_data, item_id from temp ) )
  8                      where cnt = (select count(*) from temp)
  9                   )
 10  /

    DOC_ID    ITEM_ID ITEM_DATA ENAME
---------- ---------- --------- ----------
        10       7839 MANAGER   CLARK
        10            PRESIDENT KING
        10       7782 CLERK     MILLER

that second query is something you can use -- just fill up the temp table and
you'll get it

January 13, 2003 Vipin -- Thanks for the question regarding "Nologging", version 8.1.7
You Asked
Hi Tom,

I have one question. When I alter my index as nologging and table's logging is
also disable. how does it affect performance.

My database is in noarchivelog mode and i have batch run every weekend.These
batch does heavy updates so i made all the tables as no logging. but i guess my
index are still being logged and i do not want that. what should i do ?
This is what i have done but i am unable to understand.

Table XYZ
Structure : ABC VARCHAR2(30)
Index is created on ABC  column
DML -Insert

Statistics    With Append Hint    No Hint    Index with Logging but W/o Hint
Index with Logging but with Append Hint    Index W/o Logging but with Append
Hint    Index W/o Logging & W/o Append Hint
RECURSIVE CALLS    1063    108    310    195    227    311
DB BLOCK GETS    21    74    760    190    189    760
CONSISTENT GETS    13675    14319    14416    13430    13439    14416
PHYSICAL READS    1005    696    703    764    794    795
REDO SIZE    2512    30760    229728    115796    116000    229728
BYTES SENT VIA SQL*NET TO CLIENT    265    273    285    278    268    275
BYTES RECEIVED VIA SQL*NET FROM CLIENT    433    419    419    433    433    419
SQL*NET ROUNDTRIPS TO/FROM CLIENT    3    3    3    3    3    3
SORTS (MEMORY)    32    10    10    12    13    11
SORTS (DISK)    0    0    0    0    0    0
ROWS PROCESSED    976    976    976    976    976    976

Please help.
Thanks
and we said...
you say "but I don't want logging"

I say "oh yes you do"

are you willing to lose ALL OF YOUR DATA -- all of it -- instantly due to a
power failure?  due to a software failure? due to any failure?

No, you are not?  Ok, then in fact *you do want logging*

Anyway, read
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:485221567528
you'll learn about nologging and that it works for rare and infrequently used
things.

It is never (never) used for updates.  It is never (never ever) used for
deletes.  It can be used for special bulk inserts (but not if there are indexes!
the indexes must be logged).  It can be used for CREATES and rebuilds.

The amount of redo you show above is trivial.  Unless you are inserting many
hundreds of thousands or millions of records -- just do it "as is".  Else, you
can

o disable the indexes
o load
o rebuild them in parallel / nologging
Review & Followup
Rating: 2
January 13, 2003
Reviewer: vipin from Singapore
No Tom, i do not want this data because this data is processed from some text
files and these text files are always with us.so in no situation we will loose
data. so how should i avoid logging.
Thaaks

Followup:
you are missing the point vipin.

you DO want logging -- else your database is toast if there is a failure.

use features/functions that bypass logging when possible -- such as a direct
path load into UNINDEXED tables (rebuilding indexes afterwards in
parallel/nologging)

read the link -- it tells you what is possible SAFELY non-logged.

updates, deletes ALWAYS logged (because corruption is a terrible thing)

January 13, 2003 Frank -- Thanks for the question regarding "Transaction and SCN", version 8.1.7
You Asked
Tom
I need to gather the TPS(transactions per second)of my database.
I used two methods below to find the SCN
1) Prior to 9i
  alter system checkpoint
  select checkpoint_change# from v$database
2) Oracle 9i
  select dbms_flashback.get_system_change_number from dual;
My questions are:

1) Does Transaction has 1:1 relationship to the SCN. If i want to calculate how
many transactions happened in the sample period, can i use the difference of the
SCN (i.e, transactions = SCN_END-SCN_BEGIN).
2) How can i find the SCN of the DB if my methods above is not good?
3) If there is no 1:1 relationship between SCN and transaction, how can i
calculate the transaction number of my system?

My test shows there is not 1:1 relationship, below is the screen shot in single
user(myself)

SQL>  select dbms_flashback.get_system_change_number from dual;

GET_SYSTEM_CHANGE_NUMBER
------------------------
                  431623

SQL> update scott.dept set dname=dname;

4 rows updated.

SQL>  commit;

Commit complete.

SQL>  select dbms_flashback.get_system_change_number from dual;

GET_SYSTEM_CHANGE_NUMBER
------------------------
                  431626

There was only 1 transaction, but the SCN changed from 431623 to 431626.
and we said...
why wouldn't you just use v$sysstat?

  1* select * from v$sysstat where name like 'user%'
ops$tkyte@ORA9I> /

STATISTIC# NAME                                CLASS      VALUE
---------- ------------------------------ ---------- ----------
         4 user commits                            1   11436149
         5 user rollbacks                          1      27339

commits = successful transactions, rollbacks = failed.

look at this table, come back in a couple of minutes -- look again and subtract
the two observations.  Divide and you have TPS.

Statspack will help you keep a history of this -- just take a statspack.snap,
come back and do it again in 15 minutes.  Now, you can compute TPS from that
(actually - it does it for you!)  Nice thing is it'll keep it in tables for as
long as you like.
Review & Followup
Rating: 5
should we substract? January 11, 2003
Reviewer: A reader
Hi

Should we substract the two values? I mean if I do some manual DML through
sqlplus and then issue rollback isnt that a transactiont oo? In that case
substract shouldnt be correct right?

Followup:
ADD  -- user commits + user rollbacks = number of transactions performed.

some people might just use user commits as number of transactions performed.

Me, I would use statspack as it tells me:

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
...
                      Sorts:                 23.46                  3.35
                     Logons:                  0.43                  0.06
                   Executes:                156.05                 22.31
               Transactions:                  7.00

without me even having to think about it!

Rating: 4
Does SCN have 1:1 relationship to the transaction? January 11, 2003
Reviewer: Frank from FL
Tom,
You ansewers were very helpful. But could you answer the question in the subject
and another question about the dbms_flashback.get_system_change_number in my
previous message?
Thanks!

Followup:
geez, come on -- no, it is not.  You yourself said as much!!!!

...
My test shows there is not 1:1 relationship, below is the screen shot in single
user(myself)
......

but not for the reason you thing (SMON or job queues or AQ was doing work in the
background).  in a distributed system, you can see the scn fly up as the systems
sync their clocks.  But anyway.

since it didn't have anything to do with the ANSWER TO THE question -- the
question needed not look at SCN -- looking at SCN is wrong, I just gave you the
answer, you know, the information you actually need.

1) no
2) the above are ok, but not relevant to answering the question
3) answered

Rating: 4
user commits + rollbacks January 12, 2003
Reviewer: Shankar from CA, USA
Rollbacks are also considered as a transaction. so you should sum up 'user
commits' and 'user rollbacks' for that snap period.

transaction count = user commits + user rollbacks

"look at this table, come back in a couple of minutes -- look again and subtract
the two observations.  Divide and you have TPS."

he meant to subtract transaction counts between two snap periods.

Shankar

Followup:
yes -- thanks for the clarification -- that is what I mean

( ((user commits time 2)+(user rollbacks time 2)) -
  ((user commits time 1)+(user rollbacks time 1)) ) / elapsed_time_in_seconds

Rating: 5
Why is it so January 13, 2003
Reviewer: Brijesh
demo@ORA92> select * from v$sysstat where name like '%user%';

STATISTIC# NAME                      CLASS      VALUE
---------- -------------------- ---------- ----------
         4 user commits                  1         35
         5 user rollbacks                1          3
         6 user calls                    1       2534

demo@ORA92> drop table t;

Table dropped.

demo@ORA92> create table t (x int);

Table created.

demo@ORA92> select * from v$sysstat where name like '%user%';

STATISTIC# NAME                      CLASS      VALUE
---------- -------------------- ---------- ----------
         4 user commits                  1         36
         5 user rollbacks                1          3
         6 user calls                    1       2541

demo@ORA92> commit;

Commit complete.

demo@ORA92> commit;

Commit complete.

demo@ORA92>  select * from v$sysstat where name like '%user%';

STATISTIC# NAME                      CLASS      VALUE
---------- -------------------- ---------- ----------
         4 user commits                  1         36
         5 user rollbacks                1          3
         6 user calls                    1       2548

demo@ORA92> rollback;

Rollback complete.

demo@ORA92>  select * from v$sysstat where name like '%user%';

STATISTIC# NAME                      CLASS      VALUE
---------- -------------------- ---------- ----------
         4 user commits                  1         36
         5 user rollbacks                1          4
         6 user calls                    1       2553

The behaviour of rollback is different than commit.
I mean commit is only increasing when I make some changes, where as rollback
value is increasing whether I make some change or not.

What does 'user calls' indicate ?

Thanks

Followup:
so?  point is? ignore it -- it is noise.  If you commit with no changes database
is just saying "oh, never mind".  A rollback is a rollback is a rollback.

ignore user calls in the context of this discussion. All stats are described in
the documentation:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76961/apc2.htm#27558

Rating: 5
Transaction and SCN May 03, 2003
Reviewer: Varda from Hyderabad,INDIA
Hi Sir,
 This was exactly what i was lookin for.
Thank a Lot

January 13, 2003 Toby -- Thanks for the question regarding "Is there a tool to convert Sybase tables into Oracle tables?", version
You Asked
Is there a tool to convert Sybase tables into Oracle tables?
and we said...
see
http://technet.oracle.com/software/tech/migration/workbench/software_index.htm

To find this manually, goto

technet.oracle.com
    -> software
         -> select a driver or utility (drop down list)
             -> migration workbench
Review & Followup
Rating: 3
Sybase to Oracle January 13, 2003
Reviewer: Praveen KV from Bangalore, India.
Hi Tom,

Is it possible to write a stored procedure in pl/sql to pull data from Sybase
tables to create new tables or to populate some existing tables in Oracle server
?

Could you please give me a simple example which explains how to do this ?

Thanks and regards

Praveen KV

Followup:
read
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4406709207206

January 13, 2003 -- Thanks for the question regarding "What privilege should I have to Create such a view?", version oracle 8.1.6 Enterprise Edition
You Asked
  I have create a user named nari in the oracle8.1.6 database server on digital
unix 4.0d and I have granted dba to nari. There is another user named tmr.If I
want to create a view on a table of tmr,what system privilege and role should I
have? If there is any configration of database influencing such operation?
and we said...
TMR should grant SELECT on the table to NARI.  If NARI needs to grant select on
this view to others, NARI will need to be granted SELECT with the GRANT OPTION.

There is a very powerful privilege, SELECT ANY TABLE, that NARI could be given
as well (its in the DBA role but roles are not in effect for compiled stored
objects -- see
http://osi.oracle.com/~tkyte/Misc/RolesAndProcedures.html
for details).
Review & Followup
Rating: 3
Please elaborate January 13, 2003
Reviewer: Martin from The Netherlands
"If NARI needs to grant select on
this view to others, NARI will need to be granted SELECT with the GRANT OPTION."

Could you please elaborate on this?
Why won't it work if schema A has a view based upon a table in schema B and
wants to give select rights to schema C that has (directly) granted select
rights on the same object in schema B. What exactly does the with grant option
do?

Followup:
the "with grant option" allows you to convey the grant (as the name implies).

it is the way the security works.  You have a view V, you can only grant on V if
you can grant on the things V points to.

January 13, 2003 Elie -- Thanks for the question regarding "Practical Use For AnyData Type", version 9.2.0
You Asked
Hi, Tom.

I read the article posted recently in which you describe how to store various
types of data into the 9i AnyData type column in a table.  You also created a
function called GetData that displays the actual contents of the data stored in
such a column.

This si a "cool" feature in 9i but ...

What practical use is there for such a data type?

It seems to me that using a varchar2(4000) column would easily suffice to store
any SQL type data.  And since one needs to know in advance what type of data is
to be stored into the AnyData type column via the ConvertNumber(),
ConvertDate(), ConvertVarchar2() functions, this is the same as having to apply
a to_date() function to a varchar2(4000) column in which I already know is
holding a date string.

So then, can you come up with any practical use for this new AnyData type?

I was thinking of, perhaps, a situation in which an XML file needed to be parsed
and a certain data field in this file might hold data of different types
(string, date, number).  But, I'm not sure this is realistic.

Thank you.
and we said...
I described it in the article.  Instead of USING varchar2(4000), you would use
this.

One does not need to know in advance -- the program does.  The program might be
written next week, not today when I create the table.

If you don't see the need for it -- by all means -- don't use it.  it is just a
tool, a technique, a device.

It is very useful in queues for example. The man in the middle (the queue)
doesn't know what datatype, set, collection, etc will be in there -- but the
guys that QUEUE the message do, the guys that DEQUEUE the message do. So, for
the man in the middle, having anydatatype is a blessing -- the queuer and
dequeuer get to work with their native types and the guy in the middle just
passes them along.

So, when the guys on either end of a process know the types -- but someone in
the middle doesn't -- this would be useful for example.

It is one of those features that "you'll know when you need it" when and if you
ever need it.
Review & Followup
Rating: 5
Practical Use For AnyData Type January 13, 2003
Reviewer: Elie Grunhaus from Rochester, NY USA
Thank you, Tom, for your insights and quick response.

After I had launched my question to you, I did think of a situation in which an
AnyData type column would come in handy.  In my environment we have tables in
which several so called "flex fields" have been defined.  These are varchar2(50)
columns in which our clients may store data of various types.  Defining such
columns as AnyData may be useful to us in that we may be able to capture client
data without knowing in advance what data types are being retrieved from the
client.

Rating: 5
Link to original article January 13, 2003
Reviewer: Wendy from Cincinnati, OH USA
I am new to the asktom forum and would like the link to the original article if
someone would be kind enough to post it.
Thanks

Followup:
just search for

anydata

on this site.  it'll be the first one returned.

Rating: 5
Not essential but usefull January 13, 2003
Reviewer: Andy Gilfrin from Dover Kent UK
I havent read the original artical but I think you should try and use the
correct data type for the data being stored.

True you could use a Varchar2(4000) but is the value you are holding a Varchar2
a number or a date ??? Using anydata would confirm that this is not a
date/varchar2/number but could be any one of the three.

January 13, 2003 Sharkar -- Thanks for the question regarding "scalable design for logging hits", version 8.1.7.4
You Asked
hi tom,

i would really apprecaite if you could give some guidelines in designing a
scalable / efficient system which just logs web page hits.

As such system only has few tables but the growth of hits_logging table is so
rapid that it might have millions of records in a day. however, that totally
depends on how often the user gets to the web sites a day.

anyways, only activity on hits_logging table (has 2 indexes) would be insert and
selects (for reporting) as of now. also we plan to archive data on a monthly
basis so that we don't over grow too much on that table.

we are striving for system to be scalable /efficient and more managable on a
long run. so any guidelines would be of great help.

note : we have only standard edition, so we can't go for partitoning.

also, i've got some suggestions from your site, as to use lmt's for tablespaces,
pctfree 1 and pctused 99 for logging table, bind sqls, redo logs/archs on
dedicated drive

thanks
Shankar
and we said...
create sequence HIT_COUNTER;

and when you hit a page, just select hit_counter.nextval into l_cnt from dual;

it'll be a good "pretty close to exactly right" hit counter that is scalable.

will it be exact?  no, you might gain N extra "hits" (where n = cache size, 20
by default) if you shutdown abort or the sequence ages out (doubtful if you get
millions of hits a day). will it be more than close enough, yes, absolutely.
Review & Followup
Rating: 4
Another idea January 11, 2003
Reviewer: Brijesh
create table hit_counter (count number default 0);

and on every hit :

update hit_counter set count = count + 1;

Is this more scalable or sequence is more scalable ?

Followup:
sequences are more scalable.

That update is the way to set "serial=true; conncurrent=false"

a sequence will do the update every N rows instead of every row where N = cache
size and N can be set by you to "tune"

Rating: 3
_logging follow-up January 11, 2003
Reviewer: Shankar from CA, USA
Hi tom,

there could be n number of sites and n number of people surfing different sites.
All i care about is store the complete url path, date and couple other things in
a logging table. We don't really monitor hits generated to any site and
'hit_logging' table name was quite ambiguous in my earlier post. sorry about
that.

As i said, we need to log each and every page requests when they surf the net
and we could have any number of customers. so the more customers and more they
surf, faster the _logging table grows. due to its rapid growthness we have
designed something similar to sliding window where _logging table always has
48hrs of data and the rest gets pushed to fact tables and delete data that are
older than 48 hrs from _logging table by batch scripts which runs once in 12
hrs. my concern was the huge deletes. is this a scalable design for a long run ?

thanks for your inputs.

Shankar

Followup:
Oh, what I do on asktom (and what my group does on all of our stuff in general)
is this:

a) we use UNINDEXED tables only.
b) we use two of them.
c) we have a synonym that points to one of them.
d) we insert into the table pointed to by the synonym
e) we "roll" the tables whenever we want by changing the synonym.
f) when we roll, we archive the data in the inactive table and truncate it.

So, you could have a table you roll every day.  Every night, you "swap" the
tables by changing a pointer and then you load the days worth of unindexed data
into your "reporting" tables.

I would not have indexes on this logging table if at all possible.  It'll just
slow down the logging of hits.

Rating: 5
January 11, 2003
Reviewer: Dave from Ohio
Tom, one of the ways that I like to read your website is to try and predict what
your answer will be. I can nod my head sagely and say,"Ah yes, bind variables,
thought so", or "Should be storing dates in DATE-type columns of course".

I thought I had this one nailed, and was predicting that you would be loading
into an unindexed partitioned table, swapping out the partitions to a table,
building indexes, swapping into an indexed table -- something like that anyway.
The synonym method was rather surprising, although it seems like a nice simple
approach. Is it just for the sake of simplicity that this is a popular method
with your collegues?

Followup:
It is simple -- works in all databases (SE, EE, with or without partitioning),
has the same "invalidation" ramifications as an alter table partition operation
would...

Rating: 5
simple and clean January 12, 2003
Reviewer: Shankar from CA, USA
excellent. I really like this idea which is very simple and clean. you have
almost touched the concept of oracle's redolog / archiving mechanism and its a
very good alternative for users without partitioning. Now i could even create 5
tables ( one for each weekday ) and through nightly batch scripts swap synonyms
to the next table in sequence and populate reporting tables from the prior
in-active table and truncate them. so that we have more tolerance to respond to
failures of batch scripts.

however this solution has a trade-in for performance/scalibility to our
reporting intervals. All our reports will be delayed by a day.

<quote>
So, you could have a table you roll every day.  Every night, you "swap" the
tables by changing a pointer and then you load the days worth of unindexed data
into your "reporting" tables.
</quote>

without indexing the in-active table, won't the loading be too slow to reporting
tables. as we summarize data before loading to fact tables. also, i could
foresee the time to build necessary indexes/gather stats/populate data/drop
indexes

Thanks,
Shankar

Followup:
It only takes seconds to full scan a million rows on my laptop - how about you?

I always use unindexed tables. so the report on todays data takes a second (we
use materialized views that refresh in the background as well and query those
aggregates) or two.

Rating: 3
January 12, 2003
Reviewer: Shankar from CA, USA
hi tom,

probably i should test this in our environment with our expectation of a day
worth of data.

anyways, this thread was very useful and i'm sure for others too. thanks for all
your inputs.

Shankar

Rating: 5
Good stuff - great idea January 12, 2003
Reviewer: Scott from Perth, Western Australia

Rating: 5
How do you do the SWAP Tom...what if dbms_job fails .... January 13, 2003
Reviewer: pasko
Hi Tom,

Thanks for a Great answer...

sometime back i was thinking about this possibility, but was concerned : what if
my  SWAPPING  JOB( i suppose you also do it using dbms_job ?)  for some reason
fails .

Followup:
then i do it again (dbms_job does it again).

To minimize the impact, you could use

create or replace view V as select * from correct_table;

as that atomically swaps the definition in a single statement.

Rating: 5
what happens for current Database Write Operations... January 13, 2003
Reviewer: pasko
Hi Tom ,

Thanks for your prompt Response...

Should i explicitly lock the the Table by the time :
"create or replace view V as select * from correct_table;"
is executed ?

because there could be several " Insert/Update correct_table pending Operations
when the Swapping occurs.

Followup:
just do the create or replace the lock won't do anything for you.

January 13, 2003 ashok -- Thanks for the question regarding "Database migration from earlier versions of oracle in windows platform to oracle 9i on linux platform", version 9.1.2
You Asked
Dear tom,
 I would be grateful to you if you could pls help me in providing a solution for
my problem..
Actually we recently purchased Oracle 9i release 2. Our existing system is like
as follows...
1.oracle 7.3 database on Microsoft Windows NT server 4.0
2.Oracle 8 database on Microsoft windows NT Server 4.0
3.Oracle 8.1 database on microsoft wndows NT server 4.0
4.Oracle 8.1.7 database on microsoft windows 2000 server

Now we are required to migrate all these databases to Oracle 9i to Red Hat
Advanced Linux Server. What are the steps involved / implications in migrating
these databases from Windows platform to Linux platform. will normal export and
import be ok..even with different OS...
please help...
and we said...
First, let me congratulate you -- you will find development is fun and easy
again.  Welcome to redhat!

Export and Import will be the tools you use to accomplish this.  they are
platform independent.

Recommend you do NOT use full exports -- but rather schema level ones.

Make sure to use COMPRESS=N!!!!!

Use locally managed tablespaces with system allocated extents for all objects
4gig and under.

Use UNDO tablespaces -- not rollback segments!

Make sure to use true temporary tablespaces "create temporary tablespace"....

Use workspace_area_policy = AUTO and pga_aggregate target instead of *_size
parameters!
Review & Followup
Rating: 5
January 13, 2003
Reviewer: Harold from Managua, Nicaragua
Make sure to use COMPRESS=N!!!!!

Why this???

Followup:
because otherwise it'll try to create a huge initial extent when you import --
which in a LMT will be converted into many extents -- many of which may not be
used.

compress=n is the only way to export IMHO -- compress=y is the single worst
default for a tool we have

Rating: 5
January 13, 2003
Reviewer: A reader
Tom,

Am going through your book & in that to change block size of database you have
recommeded  export/imp if you have db size upto 15GB.

Lets say
1.if my db if more than 15GB that what do you recommend.
2.if i want to move from windows to linux & my db size is more than 15GB.how
should i go about.

Thanks for your help.

Followup:
1) schema level exp/imports  OR
   sqlplus copy of big objects OR
   create table as select over dblink OR
   dump and reload

2) schema level exp/imports -- dropping down to table level on an as needed
basis to move really big things.

Rating: 5
Testing with Redhad Professional? January 13, 2003
Reviewer: sPh from St. Louis, MO USA
Tom,
Realizing that you are not the designated expert on Oracle/Linux installation,
but guessing that you have a lot of practical experience ;-) I am curious if it
is possible to install 9i on RedHat 8.0 Professional rather than the Advanced
Server for testing (rather than production) purposes.  I would like to try out
9i on Linux but need to minimize the cost of testing.   Once a decision is made
I can get funding for a production system but the RHAS price is a bit steep to
"take a look".

Thanks for all your work on this site!

sPh

Followup:
that is what I'm running ;)

server went right on (db)

iAS gave some heartburn to those that tried it.  what they did was install on
7.3 RH and then upgrade it -- that seemed to have worked.

Rating: 5
All very true but one question January 13, 2003
Reviewer: Randy from Austin, TX
I've found all of this advice to make life much easier, but I have one question.
 Does the warning about full exports only apply to cross-platform
export/imports, or in general?  I've used full db exports to copy a database to
a like OS with no apparent problems.  Are there lurking problems there that will
someday surface?

Followup:
it is the cross version stuff 7.x to 9iR2 at the full level I don't like.

It is the create tablespaces.
It is the pulling along of all of the legacy privileges and garbage our
databases collect.
Is is the sheer size of them -- export N schemas at a time in parallel vs 1
database at a time serially....

for starters.

January 13, 2003 Venkat -- Thanks for the question regarding "Required privilege", version 8.1.7
You Asked
Hi Tom,

Happy new year to you!!!

I need your help,

We have 2 schema's say "A" and "B". Schema "B" has just
"create session" privilege. In schema "A" tables,procedure,
functions,packages and triggers are created and public synoym's
are created for all the objects. Now neccessary privileges are
given to schema "B".

Example:
For tables, select, insert,update and delete privilege.
For procedure's,function's and package's execute privilege are
provided.

My question is, how do i see the source code of Package body,
I am able to source code of procedure, function and also
package specification. I know oracle will not allow to see
the source code of package body if I have execute privilege.
Is there any privilege is there in oracle so that I can see
even source code of package body from the schema "B".
What is the privilege required to see the source code from other
schema?

Thank you,
Venkat
and we said...
you would have to have execute ANY procedure which is really powerful.

Or, you can write yet another package -- as using stored procedures is the
single most powerful way to give fine grained priveleges out.  You can use the
fact that the schemaid inside of a stored procedure is by default the OWNER --
any query run in there is run with the base privs of the OWNER.

So, say ops$tkyte is A, scott is B, then:

ops$tkyte@ORA817DEV> create or replace package demo_pkg
  2  as
  3          type rc is ref cursor;
  4          procedure show_me_the_code( p_pkg_name in varchar2, p_cursor in out
rc );
  5  end;
  6  /

Package created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace package body demo_pkg
  2  as
  3
  4  procedure show_me_the_code( p_pkg_name in varchar2, p_cursor in out rc )
  5  is
  6  begin
  7          open p_cursor
  8           for select text
  9                     from user_source
 10                    where name = upper(p_pkg_name)
 11                          and type = 'PACKAGE BODY'
 12                    order by line;
 13  end;
 14
 15  end;
 16  /

Package body created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> grant execute on demo_pkg to scott;

Grant succeeded.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> @connect scott/tiger
ops$tkyte@ORA817DEV> set termout off
scott@ORA817DEV> REM GET afiedt.buf NOLIST
scott@ORA817DEV> set termout on
scott@ORA817DEV>
scott@ORA817DEV> select text
  2    from all_source
  3   where name = 'DEMO_PKG'
  4     and owner = 'OPS$TKYTE'
  5     and type = 'PACKAGE BODY'
  6  /

no rows selected

No joy here but..

scott@ORA817DEV>
scott@ORA817DEV> variable x refcursor
scott@ORA817DEV> set autoprint on
scott@ORA817DEV> exec ops$tkyte.demo_pkg.show_me_the_code( 'demo_pkg', :x )

PL/SQL procedure successfully completed.

TEXT
----------------------------------------------------------------------
package body demo_pkg
as

procedure show_me_the_code( p_pkg_name in varchar2, p_cursor in out rc
 )

is
begin
        open p_cursor
         for select text
                   from user_source
                  where name = upper(p_pkg_name)
                        and type = 'PACKAGE BODY'
                  order by line;
end;

end;

15 rows selected.

scott@ORA817DEV>
Review & Followup
Rating: 5
Required privilege January 13, 2003
Reviewer: Venkat from Hyderabad, India
Hi Tom,

Thank you, this should solve my problem.
I was also searching in googles, I found you have given
solution for the similar problem in googles.
http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&selm=178d2795.0202280802.21e2c21c%40posting.google.com&rnum=5

We can create view on user_source or all_source and provide
the user select privilege.

create view view_source as
select name, type, text from
user_sources;

or

create view view_source as
select name, type, text from
all_sources where owner=user;

Thank you,
Venkat

January 13, 2003 Mo -- Thanks for the question regarding "Cursors versus REF Cursors versus NDS", version 8.1.7
You Asked
Tom:

Even though I have been using implicit cursors and REF cursors I am still
unclear on when do you use each:

1.  For regular cursors, which are pointers to a SQL statement result set, you
can use either explicit or implicit.  Implicit are better than explicit and can
do anything an explicit one will do.  Cursors are a MUST when you  have a SQL
statement that returns more than one row in a PL/SQL program. Correct?

2.  For REF cursors you use them when you want a pointer to a cursor result set.
 But I do not see what a REF cursor can do than a multiple IMPLICIT cursors will
not do.  Are you bascially avoiding several selects on the real table and thus
using a REF cursor on a small result set which means no hits against the real
table

open l_cursor for select * from scott where state=:X using 'CA'
open l_cursor for select * from scott where state=:Y using 'VA'

3.  Native dynamic SQL is used to run SQL statements at run time. IT works
similar to DBMS_SQL.  Is this used only when you have DDL SQL statements.  WHen
do you really have to use this?

Thank you,
and we said...
1) You have this backwards:

...and can do anything an explicit one will do

it should be "and can do MORE than an explicit one will do" ;)

but anyway -- cursors are needed when you will be procedurally processing more
then one row, yes.

2) a ref cursor is just a cursor.  a ref cursor is just a cursor.  a ref cursor
is just a cursor.... (keep saying it over and over ;)

with a ref cursor, you can do something like:

   if ( user_input = 'X' )
   then
      open l_cursor for select * from t1 where x = 5 and y = 120 and z = 2;
   else
      open l_cursor for select * from t2 where c = 23;
   end if;
   loop
       fetch l_cursor into ....;
       exit when l_cursor%notfound;
       ...procedural code to process results here...
   end loop;
   close l_cursor;

you can avoid alot of repetive code you would have to otherwise have in order to
process these two queries.  The offer flexibility in what result set is actually
be processed.

They are also the only way to return a result set to a client application.

3) NDS is used whenever the query isn't known until runtime.  It is useful for
doing DDL but can and is used for DML as well.
Review & Followup
Rating: 5
cursors and ref cursors September 02, 2002
Reviewer: Mo from reston, USA
Tom:

Great explanation and I am finally clear on it.

1.  I se how you can save a lot of repeating code using REF cursors.  However,
can I still accomplish what a REF cursor does using implicit cursors like:

If (user_input='X') then
   FOR x in (select * from t1 where x = 5 and y = 120 and z = 2) LOOP
....
    END LOOP;
ELSE
   FOR x in (select * from t2 where c = 23)
   LOOP
.....
   END LOOP;
end if;

2.  I did notice that when you use inline views like
select col1,col2,(select col from table2 where t1.c1=t2.c2) from table 1
you get an error with explicit or explicit cursors.  You have to do it using REF
cursors. Correct?

3. YOu say using NDS when you do not the query beforehand.  Is this like a user
querying a state column but he can enter up to 10 or 20 states to get a result
set.  IN this case you build a string for the query and run it using NDS?
COrrect?

Thank you,

Followup:
1) Dude --  your ....  IS THE REPEATED CODE the ref cursor allows you to not
repeat.

ref cursor:

 if ( user_input = 'x' ) then
     open c for ...
 else
     open c for ...
 end if;

 loop
    fetch c into ...
    exit when c%notfound;
    .... HERE YOUR REPEATED CODE ....
 end loop;
 close c;

2) You need to use dynamic sql in 8i.  In 9i -- you don't.

3) yes.

Rating: 5
ref cursor October 12, 2002
Reviewer: mo
Tom:

DO you something wrong in this code. I am trying to return a result set into
powerbuilder screen.

Package PKG_WRITER
IS
  TYPE  writer_rec is record (
       conno     mchar.conno%type;
       med       mchar.conno%type;
       auth      ttlinit.auth%type;
       ttl       ttlinit.casub%type;
       casub     ttlinit.annoinit%type;
       annoinit  ttlinit.ccdt%type;
       ccdt      ttlinit.ccdt%type;
       chno      ttlinit%type);
  TYPE  cur_curwriter is ref cursor
     return writer_rec;
END pkg_writer;

Procedure writer_proc (
  ccdtpar  IN varchar2,
  initials IN varchar2,
  curwriter IN OUT  pcadmin.pkg_writer.cur_curwriter
)
AS
  data_get    curwriter%ROWTYPE;
BEGIN
 OPEN curwriter FOR
   SELECT mchar.conno,mchar.med,
ttlinit.auth,ttlinit.ttl,ttlinit.casub,ttlinit.annoinit,to_char(ttlinit.ccdt,'mm/
dd/yyyy'),ttlinit.chno
from mchar,ttlinit
where mchar.chno = ttlinit.chno and ttlinit.annoinit = initials and ttlinit.ccdt
between to_date (ccdtpar,'mm/dd/yyyy') and
to_date(ccdtpar,'mm/dd/yyyy') + 6;
LOOP
  fetch curwriter into data_get;
  exit when curwriter%notfound;
exit loop;
close curwriter;
end;

procedure test1 as
type ref_cursor is ref cursor;

rcur ref_cursor;
arg_ccdt  varchar2(20);
arg_init varchar2(10);
begin
arg_ccdt:='07/01/2002';
arg_init:='BKEL';
pcsadmin.writer_proc(arg_ccdt,arg_init,rcur);
end;

WHen I run test1 to test it I keep getting:
Error at line 9
PRA-06502:PL/SQL: numeric or value error: character string buffer too small

Do you see anything wrong?

Thank you,

Followup:
Yes, two things

a)
LOOP
  fetch curwriter into data_get;
  exit when curwriter%notfound;
exit loop;
close curwriter;

should not be there at all if you want powerbuilder to fetch the results (so
your record isn't needed and your record is the root cause)

b) your record uses:

ccdt      ttlinit.ccdt%type;

and that is apparently a date (you are using a to_char on it with a date mask).
But you select:

to_char(ttlinit.ccdt,'mm/dd/yyyy')

into it.  that'll not be working right.  You are fetching a string into a date
see.  another quicky glance shows you are selecting "... conno, med ...." but
your record has:

  TYPE  writer_rec is record (
       conno     mchar.conno%type;
       med       mchar.conno%type;
       auth      ttlinit.auth%type;

so, that'll not be working very good either of conno is smaller then med or a
different datatype at all.....  but since that code shouldn't be there at all
the entire procedure should be just the OPEN and nothing more, nothing less....

but if powerbuild needs that record -- you'll be wanting to make the record
MATCH what you are actually selecting!

Rating: 5
ref cursor October 13, 2002
Reviewer: mo
Tom:

Are you saying to:

1. delete all of this :
LOOP
  fetch curwriter into data_get;
  exit when curwriter%notfound;
exit loop;
close curwriter;

You said before this how you write them:

open l_cursor for select * from t2 where c = 23;

   loop
       fetch l_cursor into ....;
       exit when l_cursor%notfound;
       ...procedural code to process results here...
   end loop;
   close l_cursor;

2. DO i need to delete the type writer_rec in the package.

3. is the test1 procedure fine?

THank you,

Followup:
if you want powerbuilder to get a result set, your plsql will simply OPEN the
ref cursor.  If you fetch from the ref cursor in the plsql as you are --
powerbuilder won't have anything to FETCH itself!

So, yes, get rid of the loop -- that is powerbuilders job, not yours.

I don't know, read the PB documentation -- see if they need a strongly typed ref
cursor or if they can use a weakly typed (just "create type rc as ref cursor;" )
type.  If they can use a weakly typed one, use it, else FIX your record to MATCH
what you are selecting.

No, test1 is flawed.  Why are you creating yet another ref cursor type.  Just
use the type you created in the package (as PB will be).

An easier way to test in plus:

variable x refcursor
exec pcsadmin.writer_proc( '07/01/2002', 'BKEL', :x );
print x

and thats all.

Rating: 5
ref cursor October 15, 2002
Reviewer: mo
Tom:

I did what you said and now getting this:

PCD> var x refcursor;
PCD> exec pcsadmin.writer_proc('07/01/2002','NBKEL',:x);

PL/SQL procedure successfully completed.

PCD> print x
ERROR:
ORA-24338: statement handle not executed

SP2-0625: Error printing variable "x"
PCD> exit
Disconnected from Oracle8i Enterprise Edition Release 8.1.7.2.0 - 64bit
Production
With the Partitioning option
JServer Release 8.1.7.2.0 - 64bit Production
/ $ oerr ora 24338
24338, 00000, "statement handle not executed"
// *Cause:  A fetch or describe was attempted before executing a
//          statement handle.
// *Action: Execute a statement and then fetch or describe the data.

Followup:
no you didn't.  show the ENTIRE example -- including the create package
pcsadmin.

You either

o never opened the cursor
o close the cursor
o fetched the data from the cursor.

(hey, before posting the code, try to figure it out -- try a couple of things,
look in the documentation, etc)

Rating: 5
ref cursor October 15, 2002
Reviewer: mo
Tom:

It works now. There was a close cursor statement left.  I guess yo udo not close
a ref cursor after you open it?

2.  The output is coming fine in sql*plus.  however when you run it from
powerbuilder data window it returns an oracle data format error.  Do you think
this is related to powerbuilder screen?

Thank you

Followup:
yes, did you FIX your record definition to actually MATCH what you are selecting
out?

Rating: 5
ref cursors January 13, 2003
Reviewer: mo
Tom:

1.  Can you tell me what would you use to formulate a query for the following:

I have a web page with four inputs:
p_country,p_state,p_city,p_county

If user entered a country value then I will only search based on country.

If user entered a country value and state then I will search on both.  etc.

If I did one SQL statement with AND clauses then it will give me the wrong
results if one of those is NULL.

Here I have to build the where clause based on user input which means I guess
use of ref cursors.  However since I have 4 columns that means I have 4x3x2=24
different combinations I have to check for.

How would you implement it?

Followup:
like this:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279

      Rating:
      5
  August 20, 2003
Reviewer:
      A reader

January 13, 2003 Shailandra -- Thanks for the question regarding
      "How to combine the result set from two ref cursors.", version 9.0.1

      You Asked
Tom,

I have five functions returning set of rows thru ref cursor. I need to do the
union or intersection on the their result set and return the final set of rows.

It is like if the function returns more than a defined no of rows then do the
intersection otherwise do the union on the resultset. The other condition is not
to exceed the total result set to max_limit. I have to stop processing after the
limit (Throttle Limit).

Currently I am doing it using a global temporary table. For implementing this, I
have copied one functions as procedure which is passed with different parameters
and I am inserting the result set into gloabl temprorary table. Then while
calling other function, I do the required union/intersection of its resultset
and records from temp table. The current process is woking fine as required.

But I feel this is inefficient way of doing this, inserting records into temp
table involves some overhead. This is causing the total procedure to slow down a
bit. I also need to reduce the process time to as min as possible.

Can you suggest some other faster way to do it.

      and we said...
You can use pipelined functions perhaps.  Here is an example:

tkyte@TKYTE9I.US.ORACLE.COM> create or replace type myScalarType
  2  as object
  3  ( x int, y date, z varchar2(80) )
  4  /

Type created.

tkyte@TKYTE9I.US.ORACLE.COM>
tkyte@TKYTE9I.US.ORACLE.COM> create or replace type myTableType
  2  as table of myScalarType
  3  /

Type created.

tkyte@TKYTE9I.US.ORACLE.COM>
tkyte@TKYTE9I.US.ORACLE.COM> create or replace
  2  function pipeline_demo
  3  ( p_nrows_to_generate in number )
  4  return myTabletype
  5  PIPELINED
  6  as
  7  begin
  8          for i in 1 .. p_nrows_to_generate
  9          loop
 10                  pipe row( myScalarType( i, sysdate+i, 'row ' || i ) );
 11          end loop;
 12          return;
 13  end;
 14  /

Function created.

tkyte@TKYTE9I.US.ORACLE.COM>
tkyte@TKYTE9I.US.ORACLE.COM> select * from table( pipeline_demo( 5 ) );

         X Y         Z
---------- ---------
-------------------------------------------------------------
-
         1 15-JUN-02 row 1
         2 16-JUN-02 row 2
         3 17-JUN-02 row 3
         4 18-JUN-02 row 4
         5 19-JUN-02 row 5

tkyte@TKYTE9I.US.ORACLE.COM>

you can write a function that fetches from a ref cursor -- returns the result
and decides

a) when to stop
b) when to get another result set to return as well.
Review &
      Followup

      Rating: 3
Combining
      resultset.  June 14, 2002
Reviewer:
      Shailandra  from US
Tom,

Can you please give an example how to query from a ref cursor being returned
from the function. I tried various combination of fetching the data from ref
cursor, but it does not work.

e.g.
select * from table(function_returning_ref_cursor);
select * from function_returning_ref_cursor;
var r refcursor;
exec :r := function_returning_ref_cursor();
select * from r; or
select * from :r;
-- Here only print r; worked.

I also looked at Oracle documentation, they talk about Pl/SQL tables,
Collections which does not fit to my requirement.

The example you gave will be useful in creating memory tables which can be later
queried for combining the resultset from two such memory table, but again how to
populate these memory tables from data returned thru ref cursor. A small example
of fetching the data from ref cursor being returned from a function will be very
useful.

Thanks

          Followup:

You need to use a procedural command -- FETCH.  You FETCH from a cursor.

Oracle9i allows you to pass a ref cursor to a plsql function in a sql statement
-- but even that is not what you are asking for.

You just need to FETCH from it (that is what print :r is doing)

      Rating: 5
Pipelined
      functions in packages  June 19, 2002
Reviewer:
      Pratap Deshmukh  from India
Hi Tom

When I try selecting from a pipelined function written in a package then it
gives an internal error

ORA-00600: internal error code, arguments: [17274], [4], [], [], [], [], [], []

The function works fine when I use a standalone function. Does pipelined
functions not work from packages or am I hitting a bug. (Searched on Metalink
but found no info there)

You have also used standalone functions in all the examples you have given on
pipelined functions.

Best Regards

Pratap

          Followup:

ora-00600 = bug, always.

Contact support to get one going.

It should be just dandy in a package.

      Rating: 5
Fetching from a
      ref cursor into another %rowtype  January 13, 2003

Reviewer:  Sujata  from Germany
Hi Tom,
I have a procedure that has an output refcursor say
P_1 (...a parameter list....,c_out_cur out a_types_pkg.T_REFCUR)
(where a_types_pkg has:
                        TYPE T_REFCUR IS REF CURSOR;)
This procedure will have a statement to open the cursor at the end..something
like
OPEN C_OUT_CUR FOR VSQL;
(where VSQL is being built dynamically in the procedure..using NDS)
Now the select columns from this procedure could differ...
for example it could be "select emp_name, salary, dept"
or "select emp_name, salary"

Now, if I want to use this procedure in another procedure..
like p_2..
I will declare a variable in p_2 as
c_out_cur a_types_pkg.T_REFCUR;
and in the body...I will call p_1
P_1(..in_parameters...,c_out_cur);
after this step..I need to fetch it into a variable, or a list of variables..or
a record...

Question:
How do I declare a %rowtype for a REF cursor..so, i can fetch into that..I have
tried..it gave me an error.....

          Followup:

You won't be - you'll be using DBMS_SQL instead which has procedures to

o describe a result set so you can discover the names, types, lengths and number
of columns

o a define column procedure so you can define output for the i'th column of a
dynamic query

o a variable value procedure so you can retreive the i'th column of a dynamic
query.

You will NOT use a ref cursor for this in plsql since plsql cannot describe a
ref cursor at runtime and plsql cannot fetch INTO <unknown number/types of
columns> at runtime.

January 13, 2003 Jenny -- Thanks for the question regarding "Drop
      RBS Tablespace", version 8.1.5

      You Asked
Tom,

I understand that DBAs can add/drop rollback segments within a database.  I was
wondering is it possible for DBAs to create a new rollback segment tablespace
and drop the old/current rollback segment tablespace?  The reason that I want to
do this is because I've inherited this database with many datafiles in the RBS
tablespace and with autoextends on.  The RBS tablespace is too big.  Can you
please advise if this operation is achiveable?  Or can you suggest better ideas?

Thanks for your help!

      and we said...
sure.

just create the new tablespace.
create new rbs's in it.
offline the old ones.
drop them.
drop the tablespace.

You might have to wait a bit between dropping them and dropping the tablespace
(they might be offline pending if they are active and you'll need to wait for
them to finish being active)

remember to update the init.ora if it has a list of rbs's -- you should create
the new ones as PUBLIC so the database automatically onlines them upon startup
next time without having to be told to do so.
Review &
      Followup

      Rating: 4
Precise and
      clear to the point!  January 14, 2003
Reviewer:
      Jenny  from San Francisco, CA
Thank you Tom!

January 14, 2003 Sunil -- Thanks for the question regarding "CPU
      Utilization", version 8.0.6.3

      You Asked
Hi Tom,

We have a Partitioned Table as below:

SQL> desc q2
 Name                            Null?    Type
 ------------------------------- -------- ----
 MNG_NO                          NOT NULL NUMBER(2) - Partition Key
 SRV_TRX_S_NO                    NOT NULL NUMBER(9)
 DVC_SFX_NO                      NOT NULL NUMBER(2)
 SYS_CREATION_DATE               NOT NULL DATE
 SYS_UPDATE_DATE                          DATE
 OPERATOR_ID                              NUMBER(9)
 APPLICATION_ID                           CHAR(6)
 DL_SERVICE_CODE                          CHAR(5)
 DL_UPDATE_STAMP                          NUMBER(4)
 PHD_ID                          NOT NULL VARCHAR2(4)

This table is just a Queue table and the records in this table are picked by the
process and cleared immediately. Effectively, at any time, this table will have
NOT more than 10-20 records. The partitioning is because of the design to handle
3 different types of records that comes into this table from another module.

We have two queries running on this table all the time in the form three daemon
jobs like below:

1. select /*+ INDEX ( Q2 Q2_PK ) */ SRV_TRX_S_NO ,DVC_SFX_NO ,PHD_ID into :b0:b1
,:b2:b3,:b4:b5  from Q2 where (MNG_NO=:b6 and rownum =1)

2. savepoint DL_START_TRAN_POINT

These three daemons are doing exactly the same thing but is being distributed to
balance the load. They are using upto 6% of CPU each. Even though, I am not very
worried about the CPU utilization, I would like to know why the above two
queries are using so much of CPU. I investigated further after going through the
Oracle Performance Tuning Guide and find below the results:

SELECT SQL_TEXT, PARSE_CALLS, EXECUTIONS FROM V$SQLAREA
ORDER BY PARSE_CALLS DESC;

In our case,

SQL_TEXT
--------------------------------------------------------------------------------
PARSE_CALLS EXECUTIONS
----------- ----------
savepoint DL_START_TRAN_POINT
   97912987   97912171

select /*+ INDEX ( Q2 Q2_PK ) */ SRV_TRX_S_NO ,DVC_SFX_NO ,PHD_ID into :b0:b1
,:b2:b3,:b4:b5  from Q2 where (MNG_NO=:b6 and rownum =1)
   95347331   95351085

AND

SELECT BUFFER_GETS, EXECUTIONS, SQL_TEXT FROM V$SQLAREA;

BUFFER_GETS EXECUTIONS
----------- ----------
SQL_TEXT
--------------------------------------------------------------------------------
   95840734   95841096
select  /*+  INDEX ( Q2 Q2_PK ) */ SRV_TRX_S_NO ,DVC_SFX_NO ,PHD_ID into :b0:b1
,:b2:b3,:b4:b5  from Q2 where (MNG_NO=:b6 and rownum =1)

Could you let me know what could be done in order to make the CPU utilization of
these jobs come down as we have many other processes which will need CPU. The
total CPU utilization is around 80-80% all the time. We are running on Sun Fire
15000 machine with 8 CPUs. This is a real time system.

Any suggestions to reduce the CPU utilization?

Thanks,
Sunil Gururaj

      and we said...
Well, first of all -- until you are running at 100% forever, you have plenty O'
cpu.  You cannot put CPU cycle into the bank for later.  you either use them or
lose them.

Look at the execute to parse ratio there.  If I had three daemons -- I would
expect no more then 3 parses of that statement.  You developers wrote the code
wrong.  They are doing:

   get_next_message
      open cursor
      bind
      execute
      close

they need to rewrite that logic as:

   get_next_message
       if ( first_time )
           open cursor

       bind
       execute

they should NEVER close that cursor -- never never never.  They should open it
once and bind/execute it over and over and over.

You re-invented the wheel by the way.  there is builtin queueing software in the
database.  It is called AQ (advanced queuing)
Review &
      Followup

      Rating:
      4
buffer_get/execution ratio  January 10, 2003

Reviewer:  Allan  from Kaysville, UT. USA
I ran the buffer_gets and parse_calls queries on our system and got the
following for our workhorse query:

BUFFER_GETS:  23512197
PARSE_CALLS:      8635
EXECUTIONS:      88281

Am I correct in assuming that what we've got is a completely backwords
situation?

          Followup:

don't understand what you mean "backwards situation"?

you parse once and execute 10 times. thats good.  you do 267 buffer
gets/execute, that is reasonable (unless of course you can make it less ;)

      Rating: 3
What about
      savepoint?  January 13, 2003
Reviewer:  Sunil
      Gururaj  from Cyprus
Hi Tom,

Thanks for the response. I have forwarded your recommendation for avoiding the
parses to the development team. They are working on it.

Meanwhile, could you let me know the impact of the "savepoint". I have observed
a few more such savepoints in the application which are "in oracle (according to
Precise)" for a long time and are using high amount of CPU.

Thanks again,
Sunil

          Followup:

savepoints happen so frequently and automagically -- every plsql call for
example has an implicit savepoint.

Now, while a savepoint incurrs additional processing (note I didn't use the word
overhead) -- in general it is unavoidable.

A savepoint is a transaction control statement.  If the developers used them
intelligently (eg: to control the flow of a transaction), they are not something
you can remove.

If they stuck them in there because they didn't know what they were really
doing, then by all means -- remove them.

If they are there because the transaction flow mandates they be there -- LEAVE
them.  Else you'll destroy the transactional integrity of your system

      Rating: 5
great
      stuff  January 13, 2003
Reviewer:
      A reader

      Rating: 3
No Cursor
      Used  January 13, 2003
Reviewer:  Sunil
      Gururaj  from Cyprus
Hi Tom,

1. I got an answer from the development team that the query posted in the
original question does not open a cursor. Could there be any other thing that is
causing so many parses?

2. Does adding of unwanted hints cause this? The MNG_NO column is the PK (both
partition key as well as Primary key) of this table.

3. Please provide a short list about what are the various scenarios that can
cause a bad execute / parse ratio.

Thanks,
Sunil

          Followup:

1) time for a new development team -- either that or they've somehow managed to
add new functionality to oracle.

Tell me -- how exactly did they do this without a cursor.  You see -- the
ONLY way is via a cursor...

2) hints -- no, hints are a bad idea sure, hints shouldn't be used -- but hints
is not causing this.

Bad code is causing this.

3) the developers wrote the code wrong.

Post the snippet of code surrounding this.

January 14, 2003 Paulina -- Thanks for the question regarding
      "Calendar", version 6.0.8.11.3

      You Asked
I need to generate a calendar but it only has to contain the day of the week
that the user chooses. For example Monday, so my calendar must have all de
mondays of the year(Monday july 1,Monday july 8 and so on) .

      and we said...
ops$tkyte@ORA920> alter session set nls_date_format = 'Dy Mon DD, yyyy';

Session altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select next_day(trunc(sysdate,'y')-1, 'MON') + r
  2    from ( select (rownum-1)*7 r
  3             from all_objects
  4                    where rownum <= 53 )
  5   where trunc(next_day(trunc(sysdate,'y')-1, 'MON') + r,'y') =
trunc(sysdate,'y')
  6  /

NEXT_DAY(TRUNC(S
----------------
Mon Jan 06, 2003
Mon Jan 13, 2003
Mon Jan 20, 2003
Mon Jan 27, 2003
Mon Feb 03, 2003
Mon Feb 10, 2003
Mon Feb 17, 2003
Mon Feb 24, 2003
.....

Review &
      Followup

      Rating:
      5
Alternatives?  January 13, 2003
Reviewer:
      Scott Wesley  from Perth, Western Australia
G'day Tom

I've seen you use this method before, and I find it quite clever.

Is it also possible to use a
... start with ... connect by ...
statement?

Thanks

          Followup:

on what table?

all I'm doing here is recognizing I need 53 rows (for every possible week).

I generate that from all_objects which has more then enough.

Then, I just add to sysdate....

I don't see how a hierarchical query would come into play here?

      Rating: 5
53
      weeks?  January 14, 2003
Reviewer:  Trevor
       from Australia
Tom,
    Very clever... you are definately the MacGyver(sp?)
of the Oracle world...

Can I ask why you are looking for 53 weeks?
Trevor

          Followup:

ops$tkyte@ORA920> select to_char( trunc(sysdate,'y')+r, 'Dy YYYY' ), count(*)
  2    from ( select rownum-1 r from all_objects where rownum <= 366 )
  3   group by to_char( trunc(sysdate,'y')+r, 'Dy YYYY' )
  4  /

TO_CHAR(   COUNT(*)
-------- ----------
Fri 2003         52
Mon 2003         52
Sat 2003         52
Sun 2003         52
Thu 2003         52
Thu 2004          1
Tue 2003         52
Wed 2003         53

8 rows selected.

There are 53 wednesday's in 2003 for example. You would want them all right?

      Rating: 3
A little bit
      more compact way  January 14, 2003
Reviewer:
      PAblo Rovedo  from Argentina
Maybe, a more compact way to do the same would be:

select next_day(trunc(sysdate,'y')-1+(rownum-1)*7,'WED')
from all_objects
where rownum <= 53

Regarding Scott Wesley comments about using start with and connect by, i guess
he is talking about using a recursive approach instead of an iterative one to
run into the days.

Cheers
Pablo

January 14, 2003 Jonathan -- Thanks for the question regarding
      "using logminer placeholders", version 8.1.5

      You Asked
I've enabled the placeholders in logminer but I'd like to have some informations
regarding this option :

If I got this table :

Products

Id_Product        Product_Name          Quantity
1                 'Product1'             25 000

and I set this logmnr.opt file :
collmap = SCHEM PRODUCTS (1,ID_PRODUCT,2,PRODUCT_NAME,3,QUANTITY);

Then !I execute this query :
'Update Products set quantity = 15 000 where Id_Product = 1;'

Using  logminer am I going to collect data in the three PH or only in the PH
bound to the column 'Quantity'?

      and we said...
Oracle logs changed bytes, so the columns NOT updated won't be there.  It is
easy enough to see this:

tkyte@TKYTE816> alter system switch logfile;
System altered.

tkyte@TKYTE816> update dept set dname = lower(dname) where deptno = 10;
1 row updated.

tkyte@TKYTE816> update dept set loc = lower(loc) where deptno = 20;
1 row updated.

tkyte@TKYTE816> commit;
Commit complete.

tkyte@TKYTE816> alter system switch logfile;
System altered.

tkyte@TKYTE816> exec sys.dbms_logmnr.add_logfile(
'c:\oracle\oradata\tkyte816\archive\TKYTE816T001S01258.ARC', dbms_logmnr.new );

PL/SQL procedure successfully completed.

tkyte@TKYTE816> exec sys.dbms_logmnr.start_logmnr( dictfilename =>
'c:\temp\miner_dictionary.dat', options => dbms_logmnr.use_colmap );

PL/SQL procedure successfully completed.

  1  begin
  2  print_table( 'select scn, ph1_name, ph1_undo, ph1_redo,
  3  ph2_name, ph2_undo, ph2_redo,
  4  ph3_name, ph3_undo, ph3_redo
  5  from v$logmnr_contents
  6  where seg_name = ''DEPT''
  7  ' );
  8* end;
tkyte@TKYTE816> /
SCN                           : 6442991019910
PH1_NAME                      :
PH1_UNDO                      :
PH1_REDO                      :
PH2_NAME                      : DNAME
PH2_UNDO                      : ACCOUNTING
PH2_REDO                      : accounting
PH3_NAME                      :
PH3_UNDO                      :
PH3_REDO                      :
-----------------
SCN                           : 6442991019911
PH1_NAME                      :
PH1_UNDO                      :
PH1_REDO                      :
PH2_NAME                      :
PH2_UNDO                      :
PH2_REDO                      :
PH3_NAME                      : LOC
PH3_UNDO                      : DALLAS
PH3_REDO                      : dallas
-----------------

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating:
      3
why  January 14, 2003
Reviewer:  wjc
       from china
after exec sys.dbms_logmnr.start_logmnr( dictfilename =>
'c:\temp\miner_dictionary.dat', options => dbms_logmnr.use_colmap );
the system respone ORA-01843: ÎÞÐ§µÄÔÂ·Ý
the invalid month
why???

          Followup:

it is a problem with the default dates they are using (found and fixed in the
current software).

you can either

a) pass in DATES that are the starttime/endtime

   exec sys.dbms_logmnr.start_logmnr( ...., starttime =>
add_months(sysdate,-24), enddate => add_months(sysdate,24) );

(for example, use whatever dates you want)

b) contact support and inquire as to the availability for a patch for

Hdr: 1809875 9.0.1 RDBMS 9.0.1 LOGMINER PRODID-5 PORTID-453 ORA-1843
Abstract: NLS: GET "INVALID MONTH" ERROR FROM LOGMINER START_LOGMNR WITH CHINESE
DATABASE

January 14, 2003 bala -- Thanks for the question regarding
      "Locking and Concurrency", version 8.1.7

      You Asked
1. Indexing Foreign Keys: If indexing foreign keys is an important issue why is
it not built in to Oracle?
2. DDL statements and Autonomous Transactions: Why not Build DDL statements with
Autonomous transactions so that everything else works normally (We continue to
work in the normal way and not bothered about DDL commiting our transaction or
not bothering about building our code wiht Autonomous Transaction).
3. In your example on page 125 you say assuming one database row per block. What
if more rows per block existed? How your examples change keeping this in view?

      and we said...
1) I answered that for you already

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:292016138754
2)
because autonomous-transactions are new in 8i.

because it never used to work that way (and people get really mad when the
way it works changes on them -- given that they were expecting it to work a
certain way).  That means -- it would break code.

because doing DDL should be so rare in real code that this is not a real world
problem that needs solving on this scale.

3) It only makes it easier to explain the concept without having to worry about
the implications of other databases doing page level locking.  The answer would
be the same -- it just might a) deadlock in the other databases or b) take alot
longer as you sit there and wait and wait and wait for your data to become
unlocked

January 14, 2003 Abhishek -- Thanks for the question regarding
      "Generating scripts of views", version 8.0

      You Asked
Hi Tom,

How do I generate a SQL script file for creation of existing views in the
database?

Thanks.

      and we said...
Here is a script I call getaview that gets one view to a file named
"viewname.sql"

--------------- getaview.sql ------------------------
set heading off
set feedback off
set linesize 1000
set trimspool on
set verify off
set termout off
set embedded on
set long 50000

column column_name format a1000
column text format a1000

spool &1..sql
prompt create or replace view &1 (
select decode(column_id,1,'',',') || column_name  column_name
  from user_tab_columns
 where table_name = upper('&1')
 order by column_id
/
prompt ) as
select text
  from user_views
 where view_name = upper('&1')
/
prompt /
spool off

set heading on
set feedback on
set verify on
set termout on
---------------------------------------------------

To get all views in a schema, you could use getallviews.sql:

------------ getallviews.sql ------------------------------

set heading off
set feedback off
set linesize 1000
set trimspool on
set verify off
set termout off
set embedded on

spool tmp.sql
select '@getaview ' || view_name
from user_views
/
spool off

set termout on
set heading on
set feedback on
set verify on
@tmp
----------------------------------------

Review &
      Followup

      Rating:
      5
  July 13, 2001
Reviewer:  D.FAESSEL
       from France
Thanks for your help

      Rating: 3
Generating
      scripts of views  November 20, 2002
Reviewer:
      Chris Barr  from Boston, MA USA
Nice - but there's a piece which this misses.

A view can define column headings, e.g.:
  create or replace view show_people
  (EMPLOYEE, MANAGER)
  as SELECT emp, mgr from personnel;

The "TEXT" column values in USER_VIEWS do not
contain this part of the view:
   Create or replace view show_people
   (EMPLOYEE, MANAGER)
?

          Followup:

Maybe you just never ran this script?

It starts with a query on user_tab_columns AND THEN queries user_views:

sys@ORA817DEV.US.ORACLE.COM> @getaview all_users
sys@ORA817DEV.US.ORACLE.COM> !cat all_users.sql
create or replace view all_users (
USERNAME
,USER_ID
,CREATED
) as
select u.name, u.user#, u.ctime
from sys.user$ u, sys.ts$ dts, sys.ts$ tts

it always generates a create view with a list of column names.

      Rating: 3
Generated
      scripts does not work  December 19, 2002
Reviewer:
      yazid  from France
Hi tom,
I used your script and it's very useful. But the generated script of views
contains some Empty lines and it fail the exécution of the scrip :
--Begining of generated script :
create or replace view ADDRESS_REQ_VIEW (
ADDRESS_ID
,PERSON_ID
,ADDRESS_TYPE
,ADDRESS_LINE_1_LL
,ADDRESS_LINE_2_LL
,ADDRESS_LINE_3_LL
,ZIP_CODE_ZP
,CITY_LL
,VALIDITY
,VALIDITY_CHECK_DA

,COUNTRY_ID
,COUNTRY_CODE
,COUNTRY_NAME
) as
select
A.address_id, L.person_id, L.address_ty, A.address_line_1_ll,
A.address_line_2_ll, A.address_line_3_ll, A.zip_code_zp, A.city_ll,
A.validity_id, A.validity_check_da, RC.country_id, RC.kw, RC.nm
from ADDRESS A, REF_COUNTRY RC, LOCATION L
where RC.country_id = A.country_id
and A.ADDRESS_ID = L.ADDRESS_ID

/

When i exeSQL> @views\ADDRESS_REQ_VIEW.sql
SP2-0734: commande inconnue au dÚbut de ",COUNTRY_I..." - le reste de la ligne
est ignorÚ.
SP2-0734: commande inconnue au dÚbut de ",COUNTRY_C..." - le reste de la ligne
est ignorÚ.
SP2-0734: commande inconnue au dÚbut de ",COUNTRY_N..." - le reste de la ligne
est ignorÚ.
SP2-0042: commande inconnue ") as" - reste de la ligne ignorÚ.
SP2-0044: Pour la liste des commandes connues entrez HELP
and et pour sortir EXIT.

aucune ligne sÚlectionnÚecute the script :

Any adea
thanks

          Followup:

sorry -- it was relying on one or two things I have in my login.sql --
specifically pagesize and a query.  Use this:

set heading off
set pagesize 9999
set long 99999999
set feedback off
set linesize 1000
set trimspool on
set verify off
set termout off
set embedded on

column column_name format a1000
column text format a1000

spool &1..sql
select 'REM extracted ' || to_char(sysdate,'dd-mon-yyyy hh24:mi' ) from dual;
prompt create or replace view &1 (
select decode(column_id,1,'',',') || column_name  column_name
  from user_tab_columns
 where table_name = upper('&1')
 order by column_id
/
prompt ) as
select text
  from user_views
 where view_name = upper('&1')
/
prompt /
spool off

set termout on
set heading on
set feedback on
set verify on

      Rating:
      4
  January 08, 2003
Reviewer:  Burak
      Karatepe  from ISTANBUL - TUR
ok thats great but ,
how can get the description of an existing
view ,dynamicly , like user_tab_columns view of sys for tables?
I 've searched all_objects to find one that does the same job for views but i
couldn't find any:(
thanks..

          Followup:

umm, did you think to look in user_tab_columns?

you see -- in my query above -- to get the column names for the CREATE VIEW
statement -- I actually use user_tab_columns to get it!

      Rating: 4
isn't it
      tricky  January 14, 2003
Reviewer:  Burak
      Karatepe  from Istanbul TUR
SQL> CREATE VIEW A AS
  2  SELECT table_name, tablespace_name FROM user_tables;
SQL> SELECT COUNT(*) FROM A;
  COUNT(*)
----------
       123
SQL> SELECT TABLE_NAME,column_name,DATA_LENGTH,NULLABLE,COLUMN_ID FROM
user_tab_columns
  2  WHERE table_name = 'A' ;
TABLE_NAME                     COLUMN_NAME                    DATA_LENGTH N
COLUMN_ID
------------------------------ ------------------------------ ----------- -
----------
A                              TABLESPACE_NAME                         30 Y
    2
A                              TABLE_NAME                              30 N
    1

Yes , now i see but don't u think it is pretty tricky to give a column such name
in user_tab_columns like "table_name" also for views.!!!

Thanks...

          Followup:

no more so then to use "u" for you or "r" for are or any of the other SMS type
abbreviations...

A view is a table, what you do to a table, you do to a view.  They are
synonymous.

To an application -- a view and a table are the same.  Would it not be pedantic
to have user_tab_columns and user_view_columns....

January 14, 2003 Gowrishankar -- Thanks for the question regarding
      "Physical Column Storage Sequence", version 8.1.7

      You Asked
Hello Tom
Oracle docs says that the table columns are stored in the sequence it is entered
during table creation. I do believe it but I wonder why is this. Why cant it
rearrange them internally to put the nulls at the end so that (as claimed by
docs again) it is most optimal to put nullable columns at the end of the list.
My problem is that I have audit tracking fields like created_by/date and
last_updated_by/date etc etc which are notnull columns, but I dont want to see
them in the middle of the column list and would like to put them at the end of
the list. But thats not going to impress the DBA. I think the data modeling
convention is to describe the columns in the order of importance like PK, then
all FKs, entity descriptions etc etc etc. and all generic tracking fields should
come to the end.

Even if the user changes the NULL/NOT NULL back and forth later with alter
command, Oracle can give an option of FORCE REARRANGE to rearrange of the
fields. Just a thought

Thanks

      and we said...
because if we automagically re-arranged columns physically on you -- you would
be mad then too.

Suppose this was a wide row -- you have an attribute X that is NULLABLE.
Conventional wisdom says "put that last, saves space (an ENTIRE BYTE -- think
about that)".  However, you know that X is queried frequently -- everytime in
fact.  Other fields are not always queried.  Since this row is wide -- it could
chain blocks (span blocks).  You would like X to be on the first block -- so we
can skip a table fetch continued row sometimes (if X is always queried and X is
always last -- you would have to assemble all chained blocks every time.  If X
is not last, you might not have to -- unless you ask for data that is on the
chained block).

See, this is what views are all about.  You want X to be last physically?
Great,

create table t ( a, b, c, d, e, f, ......, X );

but you want a, b, c to appear last for appearances sake -- great, just:

create view v
as
select d, e, f, ....., X, a, b, c
  from t;

use the view V.  Ignore the existence of T.  Now you can rearrange columns --
rename them even -- to your heart content.

Just my thought -- this is what views are all about.  Making things "look
pretty".
Review
      & Followup

      Rating: 3
Is it really
      matters?  January 13, 2003
Reviewer:  Tony
       from India
Havinng Not null columns first, and all nullable columns last in a table really
matters?

          Followup:

trailing null columns take no storage in the table.  if you have lots of columns
that might all be null, put them last and it'll take marginally less room

      Rating: 3
Physical Column
      Storage Sequence  January 13, 2003
Reviewer:
      Jack  from California USA
Tom,
Is there any difference in creating a column with
NUMBER(12) and NUMBER(12,0). The purpose of the column is to store numbers
obtained from sequence. Which one is best and Why ?

thanks

          Followup:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/c10datyp.htm#743
(that pesky concepts guide again ;) states:

If a precision is not specified, the column stores values as given. If no scale
is specified, the scale is zero.

they are the same.

      Rating: 4
Null columns at
      end of row  January 14, 2003
Reviewer:  Jim
      Dickson  from UK
Does column position affect the amount of redo generated?

I believe some databases generate transaction logs from start of column(s) being
updated to end of row.

          Followup:

nope:

ops$tkyte@ORA817DEV> create table t
  2  as
  3  select rownum x, rpad('*',500,'*') y, rownum z
  4    from all_objects
  5  /

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> set autotrace traceonly statistics;
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> update t set x = 1;

23820 rows updated.

Statistics
----------------------------------------------------------
          2  recursive calls
      24387  db block gets
       1709  consistent gets
       1702  physical reads
    5829112  redo size
       1009  bytes sent via SQL*Net to client
        792  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      23820  rows processed

ops$tkyte@ORA817DEV> update t set z = 1;

23820 rows updated.

Statistics
----------------------------------------------------------
          0  recursive calls
      24392  db block gets
       1708  consistent gets
          0  physical reads
    5829144  redo size
       1011  bytes sent via SQL*Net to client
        792  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      23820  rows processed

ops$tkyte@ORA817DEV>

January 14, 2003 Younes -- Thanks for the question regarding
      "Parsing ratio", version 8.1.6

      You Asked
Hi tom,
I have 3 questions
1.
I have a web application (JSP, using Apache tomcat)using oracle
8.1.6 database on linux, the application is using JDBC driver to
connect to oracle.
My problem is that the database have a very high Parsing
Ratio(>1), all the queries are binded, and i have enough memory
set for the shared pool, but still the parsing ratio is high, is
there anything you advise to do to fixe the problem.
2.
In the same application, programmeurs connect to database every
time they need to run a query, and disconnect rigth after that.
and they do that for each sql. The problem is that it take about
more 3 Seconds to connect and disconnect, i there anyway i could
speed up the proccess.
3.
In the New Features Doc for 8.1.6, litteral sqls are detected a
corrected by oracle, i tried it to see if it work, but when i
run 2 sqls,
select dummy from table where column=1
and,
select dummy from table where column=2
both sqls shows in the v$sqlarea. am i looking in the wrong
place? do i have to do something to make it work? or is it just
not working?

Thank you very much for your answers.
Younes

      and we said...
1) it is OK and normal to have parse counts that are up there --
in fact you want that.  They are "soft" parses.  If you had a
whole bunch of queries that had parse counts = 1, then you
should be worried!!!

What you want to make sure is that your program parses a
statement as infrequently as it can.  If you think you will use
the same statment more then once -- parse it once and keep it
parsed -- don't close it (don't let it go out of scope either!!)

2) it should not take three seconds to connect and disconnect.
You need to figure out why this is so.  connect and disconnect
should be be instaneous.  You need to track this down.

Why would they disconnect right away anyway?

3) have you set cursor sharing to force??  consider:

ops$tkyte@DEV816> alter system flush shared_pool;
System altered.

ops$tkyte@DEV816> select * from dual where dummy = 'A';
no rows selected

ops$tkyte@DEV816> select * from dual where dummy = 'B';
no rows selected

ops$tkyte@DEV816> select sql_text
  2  from v$sqlarea
  3  where sql_text like 'select * from dual%';

SQL_TEXT
--------------------------------------------------------------sel
ect * from dual where dummy = 'A'
select * from dual where dummy = 'B'

ops$tkyte@DEV816> alter system flush shared_pool;
System altered.

ops$tkyte@DEV816> alter session set cursor_sharing = force;
Session altered.

ops$tkyte@DEV816> select * from dual where dummy = 'A';
no rows selected

ops$tkyte@DEV816> select * from dual where dummy = 'B';
no rows selected

ops$tkyte@DEV816> select sql_text
  2  from v$sqlarea
  3  where sql_text like 'select * from dual%';

SQL_TEXT
-------------------------------------------------------------sele
ct * from dual where dummy = :SYS_B_0

Review &
      Followup

      Rating: 4
Just what I was
      looking for!  April 11, 2001
Reviewer:
      Sergio  from Bethesda, MD
This is great stuff.  Exactly what I needed.

      Rating: 4
internal error
      when using cursor_sharing = FORCE  January 14, 2003

Reviewer:  yazid  from France
Hi,
I use Oacle 8.1.7 on win2000. When cursor_sharing = FORCE, i have this error
when i use the schéma browser of toad, or some select of some tables from
sqlplus. The job takes a lot of time, then it generates this error :

Wed Jan 08 13:48:08 2003
Errors in file D:\oracle\admin\DECL\udump\ORA00712.TRC:
ORA-00600: code erreur interne, arguments : [17182], [322461692], [], [], [],
[], [], []

Any adea please ??

          Followup:

well, when I hit an ora-00600 -- i goto support....

that is what internal errors mean, support.

      Rating: 5
internal error
      when using cursor_sharing = FORCE   January 14, 2003

Reviewer:  Xuequn Xu
This is a known issue in some versions of 8.1.7.x (8.1.7.2 and before). Upgrade
to 8.1.7.4 is a *MUST*. You can get more detail from metalink.oracle.com.

January 14, 2003 Satar -- Thanks for the question regarding
      "writing to a delelted datafile", version 9.0.1

      You Asked
Hi Tom,

On Solaris 8.
I create a tablespace with one datafile and create a table in that tablespace. I
use OS commands to remove the datafile. After this operation I am still able to
insert and commit to that table. How can this be? Thank you

      and we said...
the file is still open -- all you did was erase a directory entry.  Only when
the file is closed by all processes will it "go away".

Then, you'll be hosed.

Also, an insert doesn't need to write to the file -- a commit does not either.
An insert -- buffer cache update.  A commit = redo log file write.  It could be
quite a while even if the file is really truly gone before we realize it.  It is
expected and normal behaviour.

Review &
      Followup

      Rating:
      4
  January 14, 2003
Reviewer:
      A reader
"the file is still open -- all you did was erase a directory entry.  Only when
the file is closed by all processes will it "go away"."

---Can you please elaborate little more? how can a deleted file be open? If I
accidentally deleted a file, can I get it back somehow?

          Followup:

It is the way in which unix operates in general, although some systems won't let
you erase a "busy" file.

I don't know how to explain it in 50 words or less.  Basically, if I open a file
in program1 -- and program2 erases it -- program1 doesn't know the directory
entry has been unlinked.  It just has a working file handle and it is totally
happy.

But as soon as program1 closes the file -- as soon as the last reference to that
open file goes away -- poof -- so does the file.

If you "accidently" deleted a file, it is time for your backups!

      Rating: 4
FUP - A
      Reader  January 14, 2003
Reviewer:  Jim
       from Australia
Like Tom said, on a Unix platform the "delete" operation
simply removes an entry which the OS uses to find the file.
If a process already has the file open then it already knows
where the file is.

I have seen many cases where a large file has been deleted but no disk space
freed... because a process which had the file open has not closed the file yet.

I think I heard a rumour that AIX has an "undelete"
but it is generally accepted that Unix has no "undelete"
and you must either recover the file from backups
or recreate it.

A little bit on Unix files follows.

To the system, the file is the inode number

an inode number acts as a pointer to where the system can find the information
it needs about this file.

Filenames are only used by the system to locate a file and its corresponding
inode number. This correspondence is called a link.

inode number
a unique number associated with each filename. This number is used to look up an
entry in the inode table which gives information on the type, size, and location
of the file and the userid of the owner of the file

      Rating: 5
Thanks very
      much to Jim. Very clear explanation.  January 14, 2003

Reviewer:  A reader

January 14, 2003 Paul -- Thanks for the question regarding
      "Returning result set from stored procedure over a database link", version
      9.2.0

      You Asked
Tom

I have a requirement to call a stored procedure, via a database link, which
returns several rows comprising several fields of data.
The only method I have been able to succesfully implement is to pass back a
PL/SQL table for each field of data. Is there a more elegant solution that I
have overlooked in ignorance.

Thanks in advance

Paul

      and we said...
Hey -- the FIRST 9iR2 question ever!

You can do this:

scott@ORA9I.WORLD> create or replace package my_pkg
  2  as
  3    type myScalarType is record
  4    ( x int, y date, z varchar2(80) );
  5
  6    type myTableType
  7    is table of myScalarType index by binary_integer;
  8
  9    procedure p( p_data out myTableType );
 10  end;
 11  /

Package created.

scott@ORA9I.WORLD> create or replace package body my_pkg
  2  as
  3    function scalarType( x in int, y in date, z in varchar2 )
  4    return myScalarType
  5    is
  6           l_data myScalarType;
  7    begin
  8           l_data.x := x; l_data.y := y; l_data.z := z;
  9           return l_data;
 10    end;
 11
 12    procedure p( p_data out myTableType )
 13    is
 14    begin
 15          for i in 1 .. 5
 16          loop
 17             p_data(p_data.count+1) := scalarType( i, sysdate+i, 'row ' ||
i);
 18          end loop;
 19    end;
 20  end;
 21  /

Package body created.

on the remote site (p is your procedure, it'll fill up p_data with whatever).

On the "near" end, you would call it like this:

scott@ORA920> declare
  2          l_data my_pkg.myTableType@ora9i;
  3  begin
  4          my_pkg.p@ora9i( l_data );
  5          for i in 1 .. l_data.count
  6          loop
  7                  dbms_output.put_line( l_data(i).x );
  8                  dbms_output.put_line( l_data(i).y );
  9                  dbms_output.put_line( l_data(i).z );
 10                  dbms_output.put_line( '-----------------' );
 11          end loop;
 12  end;
 13  /
1
15-JUN-02
row 1
-----------------
2
16-JUN-02
row 2
-----------------
3
17-JUN-02
row 3
-----------------
4
18-JUN-02
row 4
-----------------
5
19-JUN-02
row 5
-----------------

PL/SQL procedure successfully completed.

so you only need one parameter....
Review &
      Followup

      Rating:
      5
  June 17, 2002
Reviewer:  Paul
      Morris  from Horsham,UK
Thanks Tom, it works a treat, but leaves me slightly confused.
I had previously tried something similar, the difference being that i had not
defined my table with the INDEX BY BINARY_INTEGER parameter. Although my Stored
procedure worked when called from the same instance, I got an
ORA-03113: end-of-file on communication channel exception raised when calling it
remotely. Why does adding the INDEX BY parameter to the table type definition
make a difference.

          Followup:

index by binary_integer makes a plsql table type -- the other was a collection.
It should not have 3113'ed (that would be most likely a "bug").

      Rating: 3
SImilar problem
      (unsolved here)  August 03, 2002
Reviewer:
      Peter Korim  from Czech Republik
I need to repeat the described case with use of java stored procedure on the
remote site.

near site - oracle 8.1.7.3
remote site oracle 8.1.7.4

i need to pass a collection to remote databases's java stored procedure.

problems:

A remote site
1. 'index by ' type is rejected by compiler of java stored  procedure (the plsql
procedure dfinition)
2. 'package owned type' is rejected as well

B near site
3. it is allowed only use of package owned type through dblink otherwise
compiler reports PLS-00331: illegal reference error.

      Rating: 4
Passing a
      refcursor over a database link  January 14, 2003

Reviewer:  Niloufar  from USA
Tom,

In 9i, could we call a similar procedure to your my_pkg.p procedure over a
database link if it returns a refcursor?!

Thanks,
Niloufar

          Followup:

refcursors do not cross dblink, no.  you cannot return a refcursor over a
dblink.  a refcursor can reference a dblink, but it cannot be returned over one.


Subject | Article | Last Updated (East coast time)
LMT -- Tablespaces -- one or many DATA tbs | article only | Jan 15, 2003 11:16 am
Displaying Unicode with the UNISTR Function | article only | Jan 15, 2003 12:51 pm
temporary tablespace | article only | Jan 16, 2003 08:07 am
Migration - from DB2 to Oracle. | article only | Jan 16, 2003 08:40 am
Indexes/MTS | article only | Jan 16, 2003 11:20 am
performance ratio for all caches | article only | Jan 16, 2003 12:17 pm
Explain plan analysis | article only | Jan 16, 2003 08:53 pm
Speed vs Modular Code | article only | Jan 17, 2003 09:18 am
Connecting thru telephone line... | article only | Jan 17, 2003 09:23 am
Getting a large result from Java | article only | Jan 17, 2003 09:25 am
Calculating a percentage of the total in SQL*Plus reports | article only | Jan 17, 2003 09:29 am
calling a procedure with PL/SQL table as out parameter from JDBC | article only | Jan 17, 2003 10:13 am
Difference between IS and AS | article only | Jan 17, 2003 10:22 am
CBO on values greater than maximum during analyze | article only | Jan 17, 2003 12:18 pm
PL/SQL vs. SQL and VB | article only | Jan 17, 2003 02:26 pm
Generating XML without converting '&' to 'amp;' | article only | Jan 17, 2003 03:53 pm
How to alter a sequence? | article only | Jan 17, 2003 06:28 pm
How to find object I/O contention | article only | Jan 17, 2003 06:31 pm
Cursor reuse of static SQL in triggers | article only | Jan 17, 2003 06:43 pm
Tree Query. | article only | Jan 17, 2003 07:56 pm
CPU time consumed by SELECT,UPDATE,DELETE,INSERT statements | article only | Jan 18, 2003 08:55 am
consistent gets latch | article only | Jan 18, 2003 09:39 am
Can Global Temporary tables be cached or created in Memory? | article only | Jan 18, 2003 09:58 am
Using Resource Manager or not | article only | Jan 18, 2003 10:10 am
List of all databases on a Server and Exceptions from a Wrapped Package.Procedure | article only | Jan 18, 2003 05:02 pm
tables.. | article only | Jan 19, 2003 09:13 am
help for alter tracing | article only | Jan 19, 2003 09:38 am
Advise | article only | Jan 19, 2003 09:46 am
How is the estimated execution time calculated? | article only | Jan 19, 2003 02:16 pm
Dynamic Sql : where to put it in Java or Stored Procedure | article only | Jan 20, 2003 10:32 am
How to reclaim space from deleted lobs? | article only | Jan 20, 2003 10:51 am
Extent Allocation not happening as stated in the manuals | article only | Jan 20, 2003 10:53 am
Why my free memory always leavings a little | article only | Jan 20, 2003 11:13 am
Tuning update statement for better performance | article only | Jan 20, 2003 11:22 am
Personal Version | article only | Jan 20, 2003 03:28 pm
cursor_sharing | article only | Jan 20, 2003 03:35 pm
Can Ref Cursor be used here, if not then what and how ? | article only | Jan 20, 2003 04:41 pm
Refreshing the data from production to test instance | article only | Jan 20, 2003 07:37 pm
problem in execute immediate using CLOB datatype | article only | Jan 21, 2003 09:17 am
xmlgen.getxml does not give whole out put | article only | Jan 21, 2003 09:57 am
How to 'delete the statistics after running the analyze command.' | article only | Jan 21, 2003 10:00 am
ORA-29547 Java system class not available: oracle/aurora/rdbms/Compiler | article only | Jan 21, 2003 10:02 am
EXPORT timings differ wrt Optimizer Mode. | article only | Jan 21, 2003 10:14 am
Advanced Queueing | article only | Jan 21, 2003 01:02 pm
how do we retrieve LOBS from the database using JAVA interface | article only | Jan 21, 2003 05:35 pm
Purposes of ALTER SYSTEM SUSPEND/RESUME | article only | Jan 21, 2003 07:34 pm
Change Management | article only | Jan 21, 2003 08:57 pm
row(s) 1 - 47 of 47
January 15, 2003 ram -- Thanks for the question regarding "LMT -- Tablespaces -- one or many DATA tbs", version 9.2
You Asked
Hello Tom,
   We are on a 9.2 oracle db version. It is a dw project.
   Hardware -- Sun box with EMC disk storage and on a raid 0 + 1.
   Previously the dba has setup on gigantic DATA LMT tablespace(20 datafiles)
with 250 gb of autoallocation.
   When i saw this , i wanted to setup the objects based on their growths to
their specific tablespaces and give them uniform allocation extent sizes. Like
facts on their own tbs, dimensions, stage tables stc.
   When i mention the suggestion to the client dba, he does not understand the
reason. His reasoning is that since the tbs is autoallocation, oracle will
manage itself and since it is a raid 0 + 1 system, io problem pertaining to
single tablespace (my argument) won't exist as the underlining tables are
stripped across all the disks.
please advice me on which approach is better ?.
Single tablesapce (autoallocated) or multiple tablespace based on object growths
(uniform extent sizes) ?.
Also does io play any part in both the approachs takin raid 0 +1 into
consideration ?.

thank you for your advice ..

ram.
and we said...
well, you both have points.

He is correct that since the underlying file system is striped -- IO
distribution is pretty much evened out (and you can fix it at the hardware layer
later to fine tune it -- move stuff around)

With the autoallocate -- you'll find the first couple of extents are 64k, then
1m, then 8m, then 64m.  The biggest I've gone is 10gig on a segment (you really
would be using partitioning before then in most cases) and it was in 335 extents
-- well within the bounds of reason.

So, other then not liking the first couple of 64k extents, autoallocate is
reasonable for most segments.  I might take some of the larger objects, put them
into their own tablespaces with 64m extents.

You want more then one tablespace tho -- for backup, recovery, and just simple
"organization".
Review & Followup
Rating: 5
File size January 15, 2003
Reviewer: Paul from Ottawa, ON, Canada
I agree that additional tablespaces make alot of sense for backup and recovery
and I still break things down to the functional level...GL in GL, AR in AR etc.

What is your preference for tablespaces being comprised of
fewer large files (eg: >1GB) for a 2-3 GB tablespace versus more smaller (500M)
files for a similarily sized tablespace?

I understand that these tablespace sizes are not even approaching 'LARGE' in
themselves but I am migrating all our
client DBs to LMTs (finally --phew!!) and will be recreating all the tablespaces
and was wondering if I should do larger files (2GB) or stick with the current
max per datafile of 500M.

Followup:
depends on the size of the total database....

I like 2gig files personally, until the db gets big, then bigger files.

Rating: 5
EMC White Paper January 15, 2003
Reviewer: Angelo from Albany, NY USA
Are you saying that the entire database will be on a single 250 GB file system?

EMC has a white paper called "Striping in Oracle Environments."  I would suggest
taking a look at it.

Also, you might want to benchmark some disk setups before making a commitment.
Take a look at iozone (
http://www.iozone.org/
.

January 15, 2003 Jim -- Thanks for the question regarding "Displaying Unicode with the UNISTR Function", version 9.2.0.1
You Asked
Hi Tom,

Again I just want to say how helpful this web site has been for me.

Anyway, on my database I can execute the following SELECT statement to display
the "Registered Trademark Symbol".

SQL> SELECT UNISTR('\00AE') FROM DUAL;

U
-
®

SQL> /

However, when a friend run the same SELECT on his database, which is also 9i, he
gets a question mark ('?') as a result.

What could be the difference on his database vs. mine?  The parameter values in
the V$NLS_PARAMTERS view are th same on both.

Thanks for your help,
Jim
and we said...
What is the client character set?  I'll betcha your NLS_LANG is the same as the
database or at least one that supports this character and theirs is not.

My database is we8iso8859p1, when my nls_lang environment variable is set to
that, I get:

ops$tkyte@ORA920> SELECT UNISTR('\00AE') FROM DUAL;

U
-
®

ops$tkyte@ORA920> Disconnected from Oracle9i Enterprise Edition Release
9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

then then I unset it -- letting it default to US7ASCII (default on unix):

$ unsetenv NLS_LANG

$ sqlplus /

SQL*Plus: Release 9.2.0.1.0 - Production on Wed Jan 15 12:49:40 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

ops$tkyte@ORA920> SELECT UNISTR('\00AE') FROM DUAL;

U
-
?

ops$tkyte@ORA920>
Review & Followup
Rating: 5
Thanks ! January 15, 2003
Reviewer: Jim Knicely from Pittsburgh, PA
You are the best!

January 16, 2003 Charlie -- Thanks for the question regarding "temporary tablespace", version 8.1.7
You Asked
Hi Tom,

After creating a temporary tablespace using 'create temporary tablespace temp
tempfile '....'  nologging ...extent management local', I CANNOT see this temp
tablespace in DBA_DATA_FILES AND DBA_TABLESPACES view.  What happened?

Thanks,
and we said...
DBA_TEMP_FILES (they are not datafiles, they are in fact temp files)

It surely is in DBA_TABLESPACES tho, look again.
Review & Followup
Rating: 4
What happens when temp is created and propagated to standby January 15, 2003
Reviewer: DAP from San Mateo, CA
I have a standby database configured.  On the primary, I created a LM temp
tablespace, allocated it to the users and then dropped the previous Temp
tablespace.  I switched logs and the new tablespace appeared in v$tablespace on
the standby.  But I was waiting for it to complain about the datafile and was
prepared to enter a "alter database create datafile '...' as '...' command, but
it never did.  What you said explained it, because it's not a datafile but a
tempfile.  My question, is what happens if we activate the standby?  Will we
have to drop and create the Temp tablespace?

Followup:
just add files to it (and that is instantaneous).

Rating: 3
We've gotten the same problem January 15, 2003
Reviewer: david from US
Hi, Tom:

    After we upgrade Oracle to 8.1.7.4, we can not
see temp tablespace from dba_tablespace either.
Is this oracle bug?

Thanks
David

Followup:
let's see your query -- you aren't joining dba_tablespaces to anything are you
(and losing rows)

It is most likely a bug in your query.

ops$tkyte@ORA817DEV> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
PL/SQL Release 8.1.7.4.0 - Production
CORE    8.1.7.0.0       Production
TNS for Solaris: Version 8.1.7.4.0 - Production
NLSRTL Version 3.4.1.0.0 - Production

ops$tkyte@ORA817DEV>  create temporary tablespace foobar tempfile
'/tmp/foobar.dbf' size 5m;

Tablespace created.

ops$tkyte@ORA817DEV> select tablespace_name from dba_tablespaces;

TABLESPACE_NAME
------------------------------
SYSTEM
RBS_TS_01
RBS_TS_02
RBS_TS_03
RBS_TS_04
RBS_TS_05
USERS
DRSYS
TESTING
TESTING_LMT
XXX
TEMPORARY
TMP
FOOBAR
CLAMS

15 rows selected.

Rating: 4
January 15, 2003
Reviewer: David from US
Sorry, I was wrong,
temp tablespace is in dba_temp_files.

Thanks, Tom

Followup:
AND dba_tablespaces as well.

January 16, 2003 Veronica -- Thanks for the question regarding "Migration - from DB2 to Oracle.", version Oracle HRMS 11.0.3
You Asked
Hi

We are in the process of implementing the above product.
This migration will move data from  OS/390 (DB2)to Unix
(Oracle).

On the IBM website there are articles to assist with an Oracle
to OS/390 (DB2) conversion e.g. Tables with the different Data
types to assist with mapping etc.

I would really like to read more about this subject and was
wondering if information exist, but obviously from an Oracle
point of view.(especially on reformatting of the data)

Many Thanks
Veronica

PS: Using Oracle less than a year
and we said...
You would be interested then in going to:
http://technet.oracle.com/tech/migration/
and following the link to the "Oracle Migration Toolkits".  We
have one there for DB2 (although much like the IBM site, you'll
only find migration toolkits for going from Something to Oracle
;)
Review & Followup
Rating: 3
Migration of Teradata DW to Oracle January 16, 2003
Reviewer: Neku
Hi Tom,
My company is in thinking in the line of Migrating its current Datawarehouse
which is in Teradata to Oracle.
We are using Microstatergy with Teradata and now we want to use Oracle instead
of Teradata, We do already have Oracle Enterprise edition with Partiioning
Option,
What we need to know is that do we need to buy any other license option to use
Oracle as a DW.
Do we need to buy the OLAP option also, coz we are already using
microstratergy.We are having some reports already in microstratergy and would
not like to develop them, The code to extract Data (ascii Files) and upload in
Oracle Database can be developed.
Can you pls throw some light on the this, Its always a question if i have normal
database can i use it as DW, if i can then how and where do i created cubes,
etc, if that is done only with OLAP Option, does the reporting tool take care of
that and connect to the cubes etc created.

Followup:
I am not a license guy but - if you have enough licenses -- you have what you
need.

you do not need the olap option.  microtragedy sucks the data out of oracle (and
teradata etc) and does it itself.

January 16, 2003 -- Thanks for the question regarding "Indexes/MTS", version 8.1.7
You Asked
1) How can we make use of the index present on a given column when searching for
data using
the following where conditions?
is nulls
= null
<>
OR
Really appreciate it if you can give an example.

2)What parameter tells me wheather the database is running in MTS or the
dedicated server mode?

3) We have an application developed in J2EE with Oracle as the back end.  Are
there any disadvantages using connection pooling on the app server with the
Oracle database being in MTS mode.
and we said...
1) well, if the index is a concatenated index and one of the indexed columns is
NOT NULLABLE or is in the predicate itself (so it is known you want non-null
values) we will use it:

SQL> create table t ( x int NOT NULL, y int );

Table created.

SQL>
SQL> create index t_idx on t(y,x);

Index created.

SQL>
SQL> exec dbms_stats.set_table_stats( user, 'T', numrows=> 1000000, numblks =>
100000 );

PL/SQL procedure successfully completed.

SQL> set autotrace traceonly explain
SQL> select y from t where y is null;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=50000 Bytes=650000)
   1    0   INDEX (FAST FULL SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=50000
Bytes=650000)

SQL> set autotrace off

for example.  But basically, since entirely NULL entries are not made in an
index -- they are not excessively useful with indexes.

= null -- no need for an index, = null is neither TRUE or FALSE -- it always
returns NOTHING.

<>, similar to the index fast full scan above -- we'll use the index as a
"skinny" table.

SQL> set autotrace on
SQL> select y from t where y <> 5;

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=50000 Bytes=650000)
   1    0   INDEX (FAST FULL SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=50000
Bytes=650000)

2) show parameter mts shows if the server is configured to accepted shared
server (MTS) connections.

A database does not run in MTS mode -- it can run configured to allow them to
connect that way but it does not force it.

3) is a buffer for a buffer a good thing?  typically not.  I like to use ONE
connection pool.  Just like you should disable the unix buffer cache for the
file systems our files are on, you don't want to double buffer connections.

So, I say "no, don't use MTS and a middle tier connection pool"
Review & Followup
Rating: 5
Using MTS and a middle tier connection pool May 21, 2002
Reviewer: A reader from Houston
We are using MTS and a middle tier connection pool for developing an
application. Can you suggest anything in particular that would help me convence
my co-workers that this is not the way to go?
Thank you for your time.

Followup:
Use the analogy I just used.  Ask them if it makes sense to buffer a buffer.

MTS is good to reduce resources ( processes ) on the server.  It is slower then
dedicated server (the code path is much much longer).

If you have a connection pool -- your code should

a) grab a connection
b) use it
c) release it

that is a recipe for DEDICATED SERVER, it is the very design of the server.

if you do this:

a) grab a connection
b) use it
c) don't use it for a long while
d) goto b a couple of times
e) release it

MTS might (just might) make sense but that would go against the design goal of
the connection pool (share the connections).

Rating: 5
Using MTS and a middle tier connection pool May 21, 2002
Reviewer: A reader from Houston
We are using MTS and a middle tier connection pool for developing an
application. Can you suggest anything in particular that would help me convince
my co-workers that this is not the way to go?
Thank you for your time.

Rating: 5
middle tier connection pool and database state December 30, 2002
Reviewer: Brijesh
How do I manage concurrency when using middle tier connection pooling.

Please see
http://marvel.oracle.com/pls/otn/f?p=17000:8:82452::NO::F17000_P8_DISPLAYID,F17000_P8_CRITERIA:25754070915,
Actually this question is not initiated by me, but being a J2EE developer i'm
curious to know.

Thanks Brijesh.

Followup:
search for

"optimistic locking"

on this site -- many articles.

Rating: 5
Index usage with like January 16, 2003
Reviewer: A reader
How can I make use of an index present on the column 'empName' when doing the
following searches

select * from emp where empName like '%abc';
or
select * from emp where empName like 'abc%';
or
select * from emp where empName like '%abc%';

Followup:
the first one -- probably won't.  EVERY entry has to be inspected.
Same with the last.

The second one would most likely use it -- most likely.

Rating: 5
Index usage with like January 16, 2003
Reviewer: A reader
Will creating a function based index that will reverse the value present in the
column empName help the optmizer use the index in case I

Case I
select * from emp where empName like '%abc';

Is there anything else that can be done to speed up queries like

select * from emp where empName like '%abc';
select * from emp where empName like '%abc%';

Thank you for your time.

Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5968495479087
for the function based index for the '%abc' issue.

for '%abc%' -- nothing -- unless ABC is an entire word in a string -- then,
interMedia text (Oracle text in 9i) can help infinitely.

January 16, 2003 Juan Carlos -- Thanks for the question regarding "performance ratio for all caches", version 9.2.0.2
You Asked
Hi Tom, I had been looking for a cache ratio

1.
I found one for db_cache_size (db_block_buffers)
But I don't find one for keep and recycle cache size.
Could you please give a hint.

2.
I think the ratio is the first and simple way to know faster if something is
wrong.
Or do you suggest that anyway independently of what one gets with the ratio try
to use the cache advisor

Thanks
and we said...
I HATE CACHE RATIOS.......

wait -- i DESPISE them -- especially when someone says "I think this is the
simple way to know if something is wrong".

It is driving me nuts... It comes up about once a day now -- at least!

I have a theory -- a cache hit ratio of 99% or more indicates you have a serious
performance issue.  Why?  See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6749454952894
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6643159615303
and then set about ignoring all ratios with the exception of your soft parse
ratio (which in oltp/mixed workload better be above 99%).  Zero in on WAITS.
Zero in on queries with lots of LIO's.  Ignore ratios.  By themselves --
absolutely meaningless (with that one exception)
Review &
      Followup

      Rating:
      5
  January 16, 2003
Reviewer:
      A reader
:) Thanks Tom

      Rating:
      5
  January 16, 2003
Reviewer:
      A reader
Tom,

This is the query

test@db1>  set autotrace on statistics
test@db1>  select count(*) from all_objects;
  COUNT(*)
----------
     29196
Elapsed: 00:00:01.07

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

I am on single user environment using Oracle 9.0.2 on win2k server.Why am i
getting such high consistent gets, i read your note on reducing LIO. Does it
make sense to tune it or thats fine??. .

Thanks.

          Followup:

it is normal.  consistent gets is a function of the query plan.  if you look at
the plan for all_objects like that, you'll find it is a big one.

forget all_objects is a dictionary view for a minute, pretend it is a real
application view against real tables you own, then:

If I had a system that needed to know "how many objects" are there -- I would
not use the all_objects view, I would write a more optimal query directly
against the base tables to answer that question ASAP.  all_objects has tons of
non-relevant baggage in order to be a general purpose view.

      Rating:
      5
  January 16, 2003
Reviewer:
      A reader
Thanks Tom for your prompt response.

January 16, 2003 Scott -- Thanks for the question regarding
      "Explain plan analysis", version 8.1.7

      You Asked
G'day Tom

I'm a little confused on how to correctly interpret some results from the
explain plans.

If you have a simple customer table with 50000 records, with a first_name field
and a surname field, both are indexed. I'm using the CBO.

If you perform a simple count(*) from the table, this is the execution plan:

Which seems to pick up an arbitrary index, for the sake of this exercise, one
not previously referenced (just a unique key on another field. Disable this, and
the optimiser chooses another)
a) select count(*) from customers
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=45 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FAST FULL SCAN) OF 'TCCUST_TID_UK' (UNIQUE) (Cost

Add the surname to the search, and it performs a range scan on the index.
b) select count(*) from customers
where surname like 'SMITH%'
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=9)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'TCCUST_SURNAME_NU_I' (NON-UNIQUE)

Put a % on both sides of the surname, and it does a fast full scan on the index,
just as the first count(*) did on another index.
c) select count(*) from customers
where surname like '%SMITH%'
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=51 Card=1 Bytes=9)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FAST FULL SCAN) OF 'TCCUST_SURNAME_NU_I' (NON-UNI

1) Is query C just as fast as query A?

Search for the first_name field as well, and it starts to access the table.
d) select count(*) from customers
where surname like 'SMITH%'
and first_name like 'JOHN%'
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=1 Bytes=15)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (Cost=6
   3    2       INDEX (RANGE SCAN) OF 'TCCUST_SURNAME_NU_I' (NON-UNIQU

Change the format of the query slightly, and the optimiser performs two range
scans instead of a table access.
e) select count(*) from customers
where surname like 'SMITH%'
--and first_name like 'JOHN%'
and exists (select null from customers where first_name like 'JOHN%')
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=9)
   1    0   SORT (AGGREGATE)
   2    1     FILTER
   3    2       INDEX (RANGE SCAN) OF 'TCCUST_SURNAME_NU_I' (NON-UNIQU
   4    2       INDEX (RANGE SCAN) OF 'TCCUST_FIRST_NAME_NU_I' (NON-UN

2) Would query E faster than query D? (Since it does two range scans instead of
a table access?)

Thanks.

      and we said...
1) query A is faster probably -- it does not have to evaluate the prediate --
just count.  Query C does more work.

2) doubt it that and they *are not the same query at all!!!* the subquery would
have to be:

(select null from customers c2 where first_name like 'JOHN%' AND c2.rowid =
customers.rowid ) or something - you need to check THAT ROW, not the entire
table -- for JOHN%

but anyway, the table access by rowid = 1 LIO for each row found int he index
tcucust_surname_nu_i, the index range scan = 2 or 3 or 4 LIOS for the same since
it in effect runs that query for each and every row that it finds.

remember these FACTS:

o indexes do not mean "fast = true"
o indexes are not "pure goodness"
o using indexes could be a sign of poor performance
o indexes can be evil.

January 17, 2003 Johan -- Thanks for the question regarding "Speed
      vs Modular Code", version 8.1.6.2.0

      You Asked
Tom,

(1) I have implemented several PL/SQL functions that are callable from SQL, that
verify and correct fields in a table when invoked.
This makes for very modular code, but does come at a performance penalty due to
the SQL - PL/SQL engine context switches. I am looking at changing the functions
to procedures that will update the data directly, but this makes the code less
modular. What would you suggest ?

(2) In a similar fashion, I have PL/SQL code that loop through records in a
table using a cursor for loop and then perform complex lookups on other tables.
The lookups are based on a key field (like siteid) and a date that need to be
matched to an implementation date in the lookup table, to achieve historic data
versioning. Due to the relative complex nature of the lookups, each lookup is
implemented in a procedure and the various procedures are called inside the
cursor for loop.

Needless to say, this is a very modular piece of code, but not very performant.
It would certainly be more effective to perform the lookups simultaneously on
all the records in the batch using a single SQL statement and one can probably
even combine some of the lookups into a multi-table join. The problem is once
again that this is a tradeoff between modular code and more performant code.
Please let me know how you would approach this one.

Regards

      and we said...
1) why does that make it less modular?  A procedure the does the updates can
(and should be) as modular as SQL calling a lot of functions.  You would still
use good coding techniques.

2) here I differ with your definition of modular code.  I have a "mantra" i
follow:

if you can do it in a single SQL statement -- do it.
if you cannot, do it in plsql,
if you cannot, use a little java stored procedure,
if that doesn't cut it, use a c external procedure.

If you can do it in SQL -- thats the MOST modular of them all.  Anything you can
jam into that SQL statement -- thats great.  That single statement becomes the
program.  The LESS procedural code you have, the better.
Review &
      Followup

      Rating: 4
context
      switches : sql function vs sql statement   September 18,
      2002
Reviewer:  A reader
In a pl/sql loop inside a proc, if i do

for i in cursor
loop
SELECT SUBSTR(i.col1,1,2)||'somethigmore' INTO var FROM DUAL;
str :=str||var;
end loop

OR

for i in cursor
loop
var:= SUBSTR(i.col1,1,2)||'somethigmore' ;
str :=str||var;
end loop

which will have lesser sql/plsql context switches. Or are they same? which is
better?
Do context switches occur for sql statements or even just sql functions?
the above is  just a crude example though.
which is better in the above case?

          Followup:

the right way would be

for i in cursor_that_selects_substr(col1,1,2)||'somethingmore'
loop
   str := str || var;
end loop;

anytime you can use a direct assignment in plsql, do it (so selecting from dual
would be a bad idea in your case)

anytime you can avoid using plsql and do it in a single sql statement -- as i
did -- do it.

      Rating: 2
sql vs
      pl/sql  January 17, 2003
Reviewer:  John
       from California USA
Tom,
We have a debate on the following.
The issue is related to Production install.
During the install we are adding one column to the table an d dropping from
antoher table. Before we do this, we need to migrate the data from old column to
new column, in a very efficient way(shortest possible time).

So, it's like this (for eg.)
Assuming emp table is having 10 million rows
update emp a
set emp_account_no = (select account_no from emp_account b
                      where a.empno=b.empno);

one of our dba says: If we use single sql
a) we don't know the progress of the sql (like how many rows updated)
b) if something (don't ask how, just assume) fails during the update say last
but one row, rolling back the whole update takes lot of time.
c) he says, better use pl/sql and loop through and commit every 5000/10000 rows.
This way you know how many rows are processed and less rbs usage or less time to
rollback anything.

Tom, I want to understand completely which one is correct, use sql or pl/sql.
Pl. explain in detail.

thanks
John

          Followup:

I would not use the above approach at all.  But I'll comment on it.

Your DBA might like to look at v$session_longops.  It would tell you how far you
are during this long running operation and even tell you how much longer you
have to go.

For exmaple, I just kicked off:

  1* update big_table set subobject_name = ( select subobject_name from
big_table b2 where b2.id = big_Table.id )
big_table@ORA920> /

an update of 1.8 million rows.  In another session, we can monitor this:

SID                           : 9
SERIAL#                       : 6434
OPNAME                        : Table Scan
TARGET                        : BIG_TABLE.BIG_TABLE
TARGET_DESC                   :
SOFAR                         : 1685
TOTALWORK                     : 13010
UNITS                         : Blocks
START_TIME                    : 17-jan-2003 09:11:19
LAST_UPDATE_TIME              : 17-jan-2003 09:11:33
TIME_REMAINING                : 94
ELAPSED_SECONDS               : 14
CONTEXT                       : 0
MESSAGE                       : Table Scan:  BIG_TABLE.BIG_TABLE: 1685 out of
13010 Blocks done
USERNAME                      : BIG_TABLE
SQL_ADDRESS                   : 57663A64
SQL_HASH_VALUE                : 496941350
QCSID                         : 0
-----------------

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> @longops

  COUNT(*)
----------
         1

SID                           : 9
SERIAL#                       : 6434
OPNAME                        : Table Scan
TARGET                        : BIG_TABLE.BIG_TABLE
TARGET_DESC                   :
SOFAR                         : 4634
TOTALWORK                     : 13010
UNITS                         : Blocks
START_TIME                    : 17-jan-2003 09:11:19
LAST_UPDATE_TIME              : 17-jan-2003 09:11:54
TIME_REMAINING                : 63
ELAPSED_SECONDS               : 35
CONTEXT                       : 0
MESSAGE                       : Table Scan:  BIG_TABLE.BIG_TABLE: 4634 out of
13010 Blocks done
USERNAME                      : BIG_TABLE
SQL_ADDRESS                   : 57663A64
SQL_HASH_VALUE                : 496941350
QCSID                         : 0
-----------------

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>

by querying the v$session_longops table...  It even tells me how long we've been
going and how far we have to get.

He is right about the "failing" -- but what is the point?  If it fails, its
going to take a ton of time to redo the operation as well.

As for c) that would be the dog slowest way to do it -- so, that will more then
offset the time taken to rollback IN THE UNLIKELY EVENT of the need to rollback.
 In other words -- he would like to do it slowly everytime - instead of doing it
fast with the unlikely chance of having to wait for a rollback in the
EXCEPTIONAL case (eg: always run slow, vs maybe have to wait for a rollback)...

But again, I would not do it like that at all -- for a 10,000,000 row table.  I
would rather:

create table new_emp nologging
as
select emp.c1, emp.c2, emp.c3, ....., emp_account.account_no
  from emp, emp_account
 where emp.emp_account_no = emp_account.account_no    /* use (+) if and only
                                                     if outer join is needed */
/
drop table emp;
rename new_emp to emp;

create index emp_idx on emp(...) nologging parallel
(rest of indexes)

do grants

BACKUP

No redo, no undo, no migrated rows from a messy update making the rows all
larger, nice reorged table...

January 17, 2003 mubeen -- Thanks for the question regarding
      "Connecting thru telephone line...", version 8.0.5

      You Asked
hai tom,

can u clarify my basic doubts plz...

1) In forms 5.0, what is the maximum no. of records that can be
displayed/committed in a basetable block. ?

2) My application will be accessed by a branch user(single).  He
will be accessing the server of head office. For intial we want
to use telephone line.  I can install dial-up connection. After
this, is ther anything I have to do to connect to head office
server ? I am using oracle 8.0.5 with forms5.0

thanks tom,
mubeen

      and we said...
1) there are no hard limits other then those imposed by the
amount of RAM and disk space on the client.

2) if you are using a protocol such as PPP enabling TCP/IP over
the dialup line, no, you need install nothing else special.  I
do it all of the time over 56kbs dialup and ISDN.
Review &
      Followup

      Rating: 3
Using
      VPN  January 17, 2003
Reviewer:  Muhammad
      Mustafa  from Pakistan
 Dear sir
    Can we access the Oracle 8.1.6 server using Microsoft VPN(Virtual Private
Netwroking)installed on Windows 2000 Server using telephone line?
  If yes can you tell me the way please.

  Regards
   Muhammad Mustafa

          Followup:

as long as you can network freely, we don't care if there is a vpn, whatever.
if you can telnet, ping, ftp, http, smtp -- you can probably Oracle net as well.

you need to do "nothing", it is just a tcp/ip network as far as applications are
concerned.

January 17, 2003 Tran -- Thanks for the question regarding
      "Getting a large result from Java", version 8.1.6.0.0

      You Asked
Hi Tom,
I have 2 problems:
1/ I write a Java program that return a result of Varchar2 Datatype, then load
it into Oracle Database. When the retrun value is not so large, it's OK. But
when the length of the result is more than 8194, it's always truncated to 8194
characters.

I include here the sample codes:

Java program:

  class Test {

    private static String strContent_;

    public static void setContent(String strContent) {
        strContent_ = strContent;
    }

    public static String getContent() {
        return strContent_;
    }
  }

Package TestJava:

CREATE OR REPLACE PACKAGE TESTJAVA AS
    PROCEDURE setContent(p_content VARCHAR2);
    FUNCTION  getContent return VARCHAR2;
END TESTJAVA;
/
CREATE OR REPLACE PACKAGE BODY TESTJAVA AS
    PROCEDURE setContent(p_content VARCHAR2) IS
         LANGUAGE JAVA
         NAME 'Test.setContent(java.lang.String)';
    FUNCTION getContent
        RETURN VARCHAR2
        AS LANGUAGE JAVA
        NAME 'Test.getContent() return java.lang.String';
END TESTJAVA;
/

And the error occurs when I call procedure TESTJAVA.setContent in Oracle.

Could you pls show me why and how I can do that with large result?
My Oracle Server is 8.1.6.0.0, and the OS is Windows NT 4.0, SP6.

2/ The second problem is about Oracle Advanced Queue.
In my Application there is a center and about 100 branch, and I want to use
Queue Propagation to send messages from center to branches. In center database I
create 100 queues corresponding to 100 branches, and I hope when I put messages
into those queues, the messages will be automatically sent to branches.
When I try to modify the system variable JOB_QUEUE_PROCESS(ES) to 100, I receive
an error message that the variable can only between 1 and 36.

I am not familiar with OAQ, and I don't know if my application will run or not
(because the number of branches is more than 36)

Could you explain to me? Can I use Queue Propagation in that situation?

Like in the 1st problem, my Oracle Server is 8.1.6.0.0, and the OS is Windows NT
4.0, SP6.

Thanks,
Tran Luong.

      and we said...
This is bug1322857 (fixed in 9i) whereby a Java FUNCTION returns only about the
first 4k of data.  the solution is to use a procedure with an OUT parameter
instead.  the following is a rewrite of  your code that does this.  The PLSQL
layer exposes a FUNCTION -- so no loss of functionality occurrs:

tkyte@TKYTE816> create or replace and compile java source named "Test"
  2  as
  3    class Test {
  4
  5      private static String strContent_;
  6
  7      public static void setContent(String strContent) {
  8          strContent_ = strContent;
  9      }
 10
 11      public static void getContent( String[] p_content ) {
 12          p_content[0] = strContent_;
 13      }
 14    }
 15  /
Java created.

tkyte@TKYTE816> create or replace package testjava
  2  as
  3      procedure  setContent(p_content VARCHAR2)
  4      is language java
  5      name 'Test.setContent(java.lang.String)';
  6
  7      function getContent return varchar2;
  8
  9      procedure  getContent( p_content OUT varchar2 )
 10      is language java
 11      name 'Test.getContent(java.lang.String[])';
 12  end testjava;
 13  /
Package created.

tkyte@TKYTE816> create or replace package body testjava
  2  as
  3
  4  function getContent return varchar2
  5  is
  6      l_data long;
  7  begin
  8      getContent(l_data);
  9      return l_data;
 10  end;
 11
 12  end testjava;
 13  /
Package body created.

tkyte@TKYTE816> set serveroutput on

tkyte@TKYTE816> exec testjava.setcontent( rpad('*',20000,'*') )
PL/SQL procedure successfully completed.

tkyte@TKYTE816> exec dbms_output.put_line( length(testjava.getcontent) )
20000

PL/SQL procedure successfully completed.

tkyte@TKYTE816> declare
  2      data long;
  3  begin
  4      testjava.getcontent(data);
  5      dbms_output.put_line( length(data) );
  6  end;
  7  /
20000

PL/SQL procedure successfully completed.

So, both the procedure and function get 20,000 bytes now.

2) you only need 1 queue process to propagate 100 queues.  You can configure
more to propagate them in parallel.  The max degree of parallelism with AQ is
36.  You do not need (or want) more than that.  Start with somewhere between 1
and 5 and adjust it to your needs.

Review &
      Followup

      Rating: 4
Getting a large
      result from Java  May 16, 2001
Reviewer:  TRAN
      LUONG  from HANOI, VIET NAM
I received your response. It's very helpful for my work and my knowledge of
Oracle.
Thank again.

      Rating: 4
Works 8.1.6 but
      not 8.1.7  September 15, 2002
Reviewer:  Spencer
      Burgeon  from UK
A great article getting around an Oracle 8.1.6 bug (bug1322857) returning
>4000char VARCHAR2's in PL/SQL & Java.

Tom says the bug is fixed in Oracle9i, the workaround is for 8.1.6, but doesn't
work in 8.1.7 :(

          Followup:

sure it does -- "doesn't work" is pretty vague.  do you have a concise
example showing otherwise:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
PL/SQL Release 8.1.7.4.0 - Production
CORE    8.1.7.0.0       Production
TNS for Solaris: Version 8.1.7.4.0 - Production
NLSRTL Version 3.4.1.0.0 - Production

ops$tkyte@ORA817DEV.US.ORACLE.COM> @test
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace and compile java source
named "Test"
  2  as
  3    class Test {
  4
  5      private static String strContent_;
  6
  7      public static void setContent(String strContent) {
  8          strContent_ = strContent;
  9      }
 10
 11      public static void getContent( String[] p_content ) {
 12          p_content[0] = strContent_;
 13      }
 14    }
 15  /

Java created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package testjava
  2  as
  3      procedure  setContent(p_content VARCHAR2)
  4      is language java
  5      name 'Test.setContent(java.lang.String)';
  6
  7      function getContent return varchar2;
  8
  9      procedure  getContent( p_content OUT varchar2 )
 10      is language java
 11      name 'Test.getContent(java.lang.String[])';
 12  end testjava;
 13  /

Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package body testjava
  2  as
  3
  4  function getContent return varchar2
  5  is
  6      l_data long;
  7  begin
  8      getContent(l_data);
  9      return l_data;
 10  end;
 11
 12  end testjava;
 13  /

Package body created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set serveroutput on
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec testjava.setcontent( rpad('*',20000,'*')
)

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line(
length(testjava.getcontent) )
20000

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      data long;
  3  begin
  4      testjava.getcontent(data);
  5      dbms_output.put_line( length(data) );
  6  end;
  7  /
20000

PL/SQL procedure successfully completed.

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

I tried in 8174 and 8172, no issues I could find?

      Rating: 5
VARCHAR2 limit,
      perhaps return a CLOB  January 17, 2003
Reviewer:
      Peter J Sulikowski  from UK
Tom, great. But your solution still has a limit before truncation occurs, albeit
a larger one that meets the original posters requirements. Is there any chance
of extending your solution to return a CLOB instead of a VARCHAR2?

scott@dbtest> set serveroutput on
scott@dbtest> exec testjava.setcontent( rpad('*',32000,'*') )

PL/SQL procedure successfully completed.

scott@dbtest> exec dbms_output.put_line( length(testjava.getcontent) )
32000

PL/SQL procedure successfully completed.

scott@dbtest> exec testjava.setcontent( rpad('*',33000,'*') )

PL/SQL procedure successfully completed.

scott@dbtest> exec dbms_output.put_line( length(testjava.getcontent) )
ERROR:
ORA-24345: A Truncation or null fetch error occurred

Warning: PL/SQL compilation errors.

scott@dbtest>

          Followup:

sure you could, using blobs and clobs which are just streams in java, works
fine.

search for

java clob

on this site -- there are some examples out there to get you going.

January 17, 2003 Ivan -- Thanks for the question regarding
      "Calculating a percentage of the total in SQL*Plus reports", version 8.17

      You Asked
Tom,

I can not figure out how to calculate the percentage of a field that was
determined by compute

What I want to do is create a report sorted by date and code which I have done
below. However the percentage needs to be calculated by dividing the counter by
the total (29168).  Seems easy enough however the total is a compute value.  For
code a below the percent should be counter (7467) divided by the total (29168)
or (.26).  I also need to be able to total the percentage to hopefully get 100%.

Code                              COUNTER PERCENTAGE MONT
------------------------------ ---------- ---------- ----
A                                    7467      .26
B                                     452       xx
C                                     538       xx
D                                     300       xx
E                                    1012       xx
F                                     111       xx
G                                      44       xx
T                                   19244       xx
****************************** ----------            ****
                                    29168

set echo      off
set pause     off
set termout   off
set feedback  off
set embedded  off
set verify    off
set heading   on
set trimspool on
set linesize  176
set pagesize  59
set newpage   0
set space     1
col today   noprint     new_value report_date
col dbname  noprint     new_value prt_dbname
select to_char(sysdate,'dd-Mon-yy hh24:mi') today
from dual;
clear columns
clear breaks
clear computes
col code                format a30             head 'Code'
col substr(change_date,3,2) || substr(change_date,1,2) format a10
TTITLE -
left format a15 report_date -
center 'xxx Change File: ' format a30 -
right 'Page' format 999 SQL.PNO skip 2
break on Month skip 2 on code
compute sum label 'Totals' of counter on Month
spool xxx_change_file.176
SELECT
    code,
    COUNT(code) as Counter,
    COUNT(code) / 100 as Percentage,
    substr(change_date,3,2) || substr(change_date,1,2) as Month
  FROM
    duns_change_canada_us
  GROUP BY
    substr(change_date,3,2) || substr(change_date,1,2),
    code
  ORDER BY
    substr(change_date,3,2) || substr(change_date,1,2),
    code;
spool off

As always I really appreciate all the help you give me and the others.

- Ivan

      and we said...
Analytic functions are sooooo cooool.  If you want to read lots more about them
(other then whats in the documentation) I devoted a whole chapter to nothing but
in my book:

scott@ORA717DEV.US.ORACLE.COM> break on month skip 2
scott@ORA717DEV.US.ORACLE.COM> compute sum of counter on month
scott@ORA717DEV.US.ORACLE.COM> column pct format 999.99
scott@ORA717DEV.US.ORACLE.COM>
scott@ORA717DEV.US.ORACLE.COM> select owner,
  2         counter,
  3         round(100*(ratio_to_report(counter) over (partition by month)),2)
pct,
  4         month
  5    from ( select owner, count(owner) counter, to_char(created,'mm') month
  6             from all_objects
  7            group by to_char(created,'mm'), owner
  8         )
  9   order by month, owner
 10  /

OWNER                             COUNTER     PCT MO
------------------------------ ---------- ------- --
CTXSYS                                 51     .35 08
PUBLIC                               7416   50.89
SCOTT                                  19     .13
SYS                                  7086   48.62
SYSTEM                                  2     .01
                               ----------         **
                                    14574         su

OPS$CLBECK                              3    3.66 09
PUBLIC                                 29   35.37
SCOTT                                  31   37.80
SYS                                     3    3.66
SYSTEM                                 16   19.51
                               ----------         **
                                       82         su

COMMON                                  1    1.16 10
PUBLIC                                  1    1.16
SCOTT                                  48   55.81
SYS                                    35   40.70
TYPES                                   1    1.16
                               ----------         **
                                       86         su

Review &
      Followup

      Rating: 4
New Function to
      Play with  October 26, 2001
Reviewer:  Ivan
       from USA
I had no idea about the ratio_to_report function and your example answers my
questions.

Thanks again,

Ivan

      Rating: 5
Analytics are
      fun  January 16, 2003
Reviewer:  Josh
       from Oregon
Tom,

How could I use this to give me a simple ratio? For example:

(select count(*) from table where col_a = 'Y')/(select count(*) from table)

          Followup:

don't know what you mean.

      Rating: 3
For
      Josh.....  January 17, 2003
Reviewer:  A
      Reader  from UK
I think Josh means, can you use it to get a ratio of:
- values meeting criteria in a table / total rows in table

as a 'one-off' value.

Here's a nasty example which gives the ratio of invalid to all objects from the
all_objects table - I'm sure there's a better way:

select the_ratio from (
  select status, count(*), ratio_to_report(count(*)) OVER () the_ratio
  from   all_objects
  group by status
) where status != 'VALID'

Regards,

Paul

          Followup:

ahh, ok, i would do this:

select sum(decode(col_a,'Y',1,0))/count(*) from table;

or more readable:

select sum( (case when col_a = 'Y' then 1 else 0 end) ) / count(*) from table;

January 17, 2003 Ravi Chandra -- Thanks for the question regarding
      "calling a procedure with PL/SQL table as out parameter from JDBC",
      version 8.17

      You Asked
Hi,
I am facing a problem when using PL/SQL tables as out parameters in a procedure
and calling the procedure from my JDBC code..

I created one object and a sql table of this object as follows

create or replace type myObjType as object
(
  att1    number,
  att2    varchar2(10)
);
/
create or replace type myTableType is table of myObjType;
/

I created a package in which the procedure has a table type as out parameter
the package is as follows

create or replace package testPkg
as
procedure foo( p_num in number, p_tbl in out myTableType );
end testPkg;
/

create or replace package body testPkg
as
procedure foo( p_num in number, p_tbl out myTableType )
as
begin
     for i in 1 .. p_num loop
         p_tbl(i).att1 := i;
     p_tbl(i).att2 := 'Output ' || i;
     end loop;
end;
end testPkg;
/

i am call this procedure from the JDBC code, which is as follows

import java.sql.*;
import java.io.*;
import java.util.*;
import java.math.BigDecimal;
import oracle.sql.*;
import oracle.jdbc.driver.*;

public class TestInOut extends Object
{
 public static void main(String[] args) throws SQLException
 {
  DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
  Connection conn = DriverManager.getConnection
     ("jdbc:oracle:thin:@inyo:1531:YEMCSA_D","dspcs", "f0rtun3");

  StructDescriptor structdesc
    = StructDescriptor.createDescriptor("MYOBJTYPE", conn);

  Object[] attributes = new Object[2];
  attributes[0] = new BigDecimal(1);
  attributes[1] = "RAV";

  Object [] test = new Object[1];
  test[0] = new oracle.sql.STRUCT(structdesc, conn, attributes);

  ArrayDescriptor outAD
   = ArrayDescriptor.createDescriptor("MYTABLETYPE", conn);

  oracle.sql.ARRAY inArray = new oracle.sql.ARRAY(outAD, conn, test);

  OracleCallableStatement cs
    = (OracleCallableStatement)conn.prepareCall
         ("{CALL TESTPKG.FOO(?,?)}");

  cs.setInt (1, 5);
  cs.setObject(2, inArray, OracleTypes.ARRAY);
  cs.registerOutParameter(2, OracleTypes.ARRAY, "MYTABLETYPE");
  cs.execute();
  oracle.sql.ARRAY outArray = (oracle.sql.ARRAY)cs.getARRAY(2);
  ResultSet rset = outArray.getResultSet();

  int i = 0;
  while (rset.next())
  {
    System.out.println(" array e1 ( "+ i + " )" +rset.getInt(1));
    System.out.println(" array e2 ( "+ i + " )" +rset.getInt(2));
    System.out.println(" array e3 ( "+ i + " )" +rset.getString(3));
    i++;
  }
  cs.close();
  conn.close();
  }
}

i am getting ORA-06530 error..

I have tried the same by defining the table type as follows inside the same
package

type myTableType is table of myObjType index by binary_integer;

then i am getting invalid name pattern: DSPCS.MYTABLETYPE

even though i mentioned the name of table type as
TESTPKG.MYTABLETYPE

i am able to pass table type as in parameters to a procedure but am not able to
retrieve as out paramenter..

i cannot user indexed plsql tables as it requires JDBC OCI driver.. our
application is a web application and so we decided to use thin driver..
i am confined to use 8.1.7

pls give guide me with a the solution..

thank you
rav

      and we said...
Well -- a WEB application, running on an app server -- would be best served with
the OCI thick driver.  It will perform faster, it has more options and you only
need to install it on a handful of app servers.

I would only use the thin jdbc for client deployments -- thick is the only
sensible one for all server installs.

Please make sure the code you POST is the code you RUN....  the spec and body
above do not match

anyway - suggest you either

o use jpublisher and let it generate the java
o use "single dimension" objects instead of collections of collections of
objects
o use a REF CURSOR.

I'll demonstrate a ref cursor as I believe that is by far the easiest method.

Here is the PLSQL (yours compiles but does not run)

create or replace type myObjType as object
(
    att1    number,
    att2    varchar2(10)
);
/
create or replace type myTableType is table of myObjType;
/

create or replace package testPkg
as
    type rc is ref cursor;

    procedure foo( p_num in number, p_tbl in out rc );
end testPkg;
/

create or replace package body testPkg
as

procedure foo( p_num in number, p_tbl in out rc )
as
    l_tbl  myTableType := myTableType();
begin
     for i in 1 .. p_num loop
        l_tbl.extend;
        l_tbl(i) := myObjType( i, 'Output ' || i );
     end loop;

     open p_tbl
      for
     select *
       from TABLE( cast(l_tbl as myTableType) );
end;

end testPkg;
/

And here is the much simplified java code:

import java.sql.*;
import java.io.*;
import java.util.*;
import java.math.BigDecimal;
import oracle.sql.*;
import oracle.jdbc.driver.*;

public class TestInOut extends Object
{
 public static void main(String[] args) throws SQLException
 {
  DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
  Connection conn = DriverManager.getConnection
     ("jdbc:oracle:thin:@aria-dev:1521:ora817dev","scott", "tiger");

  OracleCallableStatement cs
    = (OracleCallableStatement)conn.prepareCall
         ("{CALL TESTPKG.FOO(?,?)}");

  cs.setInt (1, 5);
  cs.registerOutParameter(2, OracleTypes.CURSOR );
  cs.execute();
  ResultSet rset = (ResultSet)cs.getObject(2);

  for( int i = 0; rset.next(); i++ )
  {
    System.out.println(" array e1 ( "+ i + " )" +rset.getInt(1));
    System.out.println(" array e2 ( "+ i + " )" +rset.getString(2));
  }
  cs.close();
  conn.close();
  }
}

Review &
      Followup

      Rating: 4
Alternate
      Solution to original question  January 17, 2003

Reviewer:  A reader  from Germany
The reason why the original JAVA code did not work is that you were trying to
read from the ARRAY's ResultSet beyond
the maximum index.

  ResultSet rset = outArray.getResultSet();

  rs.getObject (1) yields the index of the array
  rs.getObject (2) yields an Object of type oracle.sql.STRUCT

  There is no rs.getObject (3) however.

You can read the attributes from the STRUCT

 STRUCT s = (STRUCT) rset.getObject (2);
 Object [] attrs = s.getAttributes ();
  attrs [0] maps to your att1
  attrs [1] maps to your att2

After correcting the PL/SQL in the package body (IN OUT Parameter and
p_tbl.EXTEND) you get exactly what you expected.

But I would definitely prefer Tom's solution in this case.

      Rating: 4
Thank You for
      ur handy solution  January 17, 2003
Reviewer:
      Ravi Chandra  from San Diego, CA, USA
Hi Tom,
Ur solution was very handy.
I implemented in my application and is working fine.

Thank You very much
Rav

January 17, 2003 abhilash -- Thanks for the question regarding
      "Difference between IS and AS ", version 9.0.1

      You Asked
Hi Tom,

I wanted to know whats the diffenece between "IS" and "AS" while writing stored
procedures..

please explain the differences, if so any...

thank you !
abhilash

      and we said...
just preference -- they are the same.

Me, I like:

create or replace procedure P
AS
begin....

and

create or replace package body demo_pkg
AS

procedure p
IS
begin
.....

It reads more like "english" that way.

January 17, 2003 David -- Thanks for the question regarding "CBO
      on values greater than maximum during analyze", version 9.0.1

      You Asked
Tom,

How does the 9i cost based optimiser handle values when they are greater than
the maximum found in the analyze, especially when there is a histogram ?

I am wondering about the effect of histograms on queries on tables I have where
some values are skewed (e.g. many rows have value zero in one column, another
column has clumps of rows with various values) and new values come out of a
sequence or a timestamp.  I am unsure about how the cost based optimiser will
handle things when there are database values which are greater than the maximum
value found during the analyze.

1. When there is no histogram, does it record minimum / maximum values at all ?
I can't find evidence that it does.

2. When there is a histogram, but the query specifies equality with a value
which is larger than the largest value in the database at the time of the
analyze, how will it handle values which are greater than the maximum value
found during the analyze.

Does the optimiser make some reasonable assumption about values out of the
histogram range, eg similar to the highest bucket, or does it assume that there
are no matching rows at all ?

Example data:
a) Data has a job number. Lost of data is loaded in a few early jobs. Later jobs
are usually much smaller. There are many queries by job number.

b) Data has a processing sequence number.  After all processing finishes, the
processng sequence is set to zero.  Queries which specify sequence = zero should
not use the index. Queries which specify non-zero sequence should consider the
index.  This would probably work very well if after processing the sequence was
set to NULL, since then the optimiser would recognise a sensible number of rows
per distinct value, the nulls won't be put in the index, and tests of IS NULL
won't use the index, but there's too much code already depending on the zero.

If the optimizer assumes than new larger values are not used (because of
slightly old  statistics) then I am better off not having it know the maximum.

Thanks.

      and we said...
1) see low_value, high_value in user_tab_columns -- it is there.

2) it uses the stats there and assumes there really aren't many/any values.
consider:

ops$tkyte@ORA920> create table t ( x int, y char(255) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert /*+ append */
  2  into t
  3  select mod(rownum,3), 'x'
  4    from all_objects;

29449 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index t_idx on t(x);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table t
  2  compute statistics
  3  for table
  4  for all indexes
  5  for all indexed columns;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select * from t where x = 3;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=259)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=1 Bytes=259)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=1)

ops$tkyte@ORA920> select * from t where x = 4;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=259)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=1 Bytes=259)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=1)

ops$tkyte@ORA920> select * from t where x = 0;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=109 Card=9816 Bytes=2542344)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=109 Card=9816 Bytes=2542344)

ops$tkyte@ORA920> select * from t where x = -1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=259)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=1 Bytes=259)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=1)

ops$tkyte@ORA920> set autotrace off

I don't really need to put -1 or 4 in there -- the plan would be the same with
or without them.

Now you say:

Does the optimiser make some reasonable assumption about values out of the
histogram range, eg similar to the highest bucket, or does it assume that there
are no matching rows at all ?

I would say the only "reasonable" assumption to be made is that there are no
values above/below.  Or at least if there were, they are few (cause you would
have re-analyzed if there were lots added since the last analyze right).  So, it
is a win win -- not an OR situation here...

January 17, 2003 -- Thanks for the question regarding "PL/SQL vs.
      SQL and VB", version 8.1.7

      You Asked
I'm a producer rather than a programmer, but faced with a question that requires
some objective programming expertise.

My client has an application for scheduling trips within a large fleet of
vehicles. The current schedule of each vehicle is stored in an Oracle database.
A Web interface allows users to enter a vehicle type (1 of 100), a departing
time/date/location and arrival time/date/location. On submitting a query, an
algorithm:
  1) sorts through the schedule openings
  2) matches the departure and arrival of each requested trip to
     the previous and subsequent commitments of each available
     vehicle to find the smallest length of travel between jobs
  3) calculates the cost of travel for each available vehicle
  4) returns the available vehicles sorted by projected cost

There are a number of other special case calculations contained in the
algorithm--like discounts for vehicles that are empty during a return from a
delivery, etc.

Here's the dilemma:

My development team has recommended an architecture consisting of a series of
stored procedures in PL/SQL, with a business layer written in  VB.NET or C#.NET
to control the logic and flow of the application. The argument is that this
architecture will be more modular and thus faster(cheaper)to develop and
maintain.

The project manager at the client has come back and asked us to provide an
additional quote for writing the entire algorithm in PL/SQL--both the operation
and control structures in PL/SQL.

My development team claims that this is not a smart request--that given the
choice between a hybrid and homogenous architecture, the hybrid architecture
offers greater flexibility, lower cost of ownership, and no relevant difference
in performance.

As a PL/SQL expert, where would you place your bets?

Thanks,

/c

      and we said...
I put my bets with the least moving pieces.  Always.

But here -- look at the languages -- is VB (doubt it) or C# (maybe) better than
plsql for doing this algorithm.  That is the question.

Doing it in VB -- cannot imagine.
C#, being C like, perhaps if the algorithm is complex.

However -- this sounds almost like the algorithm should be a QUERY -- which
would lead me back to plsql.

So, let me turn this around to your development team.  They feel strongly that a
"hybrid" environment offers:

o greater flexibility
o lower TCO
o no difference in performance....

why?  what is their reasoning.  Be glad to respond to that.  Me -- I find

o more moving pieces is harder (flexible?  no, harder?  yes, in many cases)
o most VB programmers don't use binds so I'm mad at them anyway
o you need people who know more than one language proficiently (tco? )

I'll publish this one cause I have a feeling others will have their 2cents to
chime in with ;)
Review & Followup

      Rating: 4
C# is more Java
      like  January 17, 2003
Reviewer:  Jim
      Kennedy  from Portland OR USA
C# is more Java like; it is MS's version of Java.  Since that is the case why
not put the Java in the Oracle DB.  Then the front end uses PL/sql to ask for
the best route etc. and the stored proc (in pl/sql only or pl/sql and Java -
depends upon what would be easier to program) would return the results.  You
have seperated the GUI from the buiness logic and so if you then wanted a web
service you could go right to the db (since the logic is already there)

      Rating: 4
Make sure
      database does database work.  January 17, 2003

Reviewer:  Jeremy Smith  from Salt Lake City, UT

Microsoft tends to promote a design where you think of the database as a big
ugly dump and just throw stuff in there and suck it back out.

My thoughts would be that regardless of which route you choose, you should make
sure the procedures in the database are enough to really give you the data
you'll want to work with, or the result you need.  A sure sign you're doing
something wrong is when you're "filtering" data inside the application.  If you
do have a "middle tier" make sure that it isn't doing things better done by
using what's in the database.  As Tom mentioned, some of the algorithms you're
talking about could be done entirely in the SQL.  That's going to be faster in
PL/SQL and SQL than you load every vehicle into a list and run through it
without the database's  help--which is what you might do otherwise.

Whether you use PL/SQL, Perl, PHP, ASP, or whatever for your front end
technology is pretty much a toss-up.  I'd look at the skills of your team and
the platform you wanted to deploy on, and decide from there.  If you're 100%
positive that you want your webserver to be Windoze, then go with a Microsoft
language.  It's usually going to perform better and give you a more supported
environment.  If you want it to be a UNIX, then don't even think about using a
Microsoft language.

January 17, 2003 Andy -- Thanks for the question regarding
      "Generating XML without converting '&' to 'amp;'", version 8.1.7

      You Asked
Hi Tom,

do you know of anyway of utilising the XDK to generate XML without certain
characters being displayed as HTML entities.

An example of this would be any '&' being replaced with 'amp;'

Any help on this would be appreciated

Thanks

Andy

      and we said...
When would you want to do this (as not escaping would result in "bad" XML).

They are not HTML entities, they are ISO entities and in this case, they are one
of the 5 defined entities that must be escaped in XML

http://wdvl.internet.com/Authoring/Languages/XML/XMLFamily/XMLSyntax/sall2_5.html
A very strict rule of XML is:

<quote>
 Note: You are required to use the predefined entities &lt; and &amp; to escape
the characters < and & in all cases other than when these characters are used as
markup delimiters, or in a comment, a processing instruction, or a CDATA
section. In other words, the literal "<"  and "&" characters can appear only as
markup
</quote>

Since the only time you see CDATA with the XML XDK is when we use the DOM API's
and you do it yourself (you would be doing the encoding) -- we must escape it
(else the resulting document is just ascii text, not and XML document)

Review &
      Followup

      Rating: 2
But what if we
      want to generate CDATA tags?  January 17, 2003

Reviewer:  Sjaiswal  from New York, NY USA

I have to enclose a column in CDATA tags for example..
select '<![CDATA[' || L.location_name || ']]>' from L.location

When I use this query in dbms_xmlgen.newContext() The output clob shows as

<Location_Name>&lt;![CDATA[Kirchliches Begegnungszentrum Karow
]]&gt;</Location_Name>

It should display as

<Location_Name><![CDATA[Kirchliches Begegnungszentrum Karow ]]></Location_Name>

What should be done to get the '<' and '>' characters in this case

          Followup:

if you read above -- you'll be doing it yourself.

Think about it -- we cannot do both -- not escape and escape.  You want generic
and easy - it works as documented.  You need specialized and "just for you",
you'll be participating a little more.

What if location_name = 'Greatest Place <as far as I know> On Earth'

January 17, 2003 A -- Thanks for the question regarding "How to
      alter a sequence?", version 9i

      You Asked
Tom,
How to alter a sequence and make it no cache. Right now we have a seq with cache
size 20 and i want to make it no cache. Without dropping it and again recraeting
it how can i change it?

Thanks

      and we said...
why do you want to make it nocache?

Is your system running too fast and you want to make it run slow???

Hopefully you do not mistakenly believe this will get you a gap free sequence --
it won't.

You can look up ALTER SEQUENCE right in the documentation -- I'll point you to
it.  But I suggest you alter it and set the cache size HIGHER, not off!!

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/statements_121a.htm#2052406

Review &
      Followup

      Rating: 2
How do you get
      a gap free sequence?  January 17, 2003
Reviewer:
      Bruce  from Edmonton, Alberta, Canada
You mention that this does not guarantee a gap free sequence.  Short of building
your own seqeunce generator and using normal transaction controls, how can you
do this in Oracle?  Can a sequence be made to do this declaratively?

I understand that if you select a number from a sequence and don't use the
number, it can appear to have gaps, but the sequence itself doesn't really have
a gap, only the data.  Is this type fo thing what you mean?

          Followup:

You cannot do this in any relational database on the market without

o building your own sequence generator
o using normal transaction controls
o processing serially (no concurrent inserts into tables with 'sequences'
--ever)

that is-- you'll have to build a table like:

create table sequences ( name varchar2, num number );

and

  update sequences set num = num+1 where name = 'FOO';
  select num into l_num from sequences where name = 'FOO';

in order to do this.

Tell me -- what is the difference between "appearing to have gaps" and "actually
having gaps"??? If you can live with one but not the other -- why or why not??

      Rating: 4
no cache
      sequence  January 17, 2003
Reviewer:  a
      reader  from KY
Tom,
Actually we have two batch(sp) programs. First One runs everyday at 10PM and
another the follwing morning 4AM. We are getting the sequence as (with cache
size 20)

121,122,141,142,161,162 and so on..

Only these two batch programs access that sequence object. That was the reason i
thought of changing to no cache.
But as you are saying it will slow down the response, i am definitely not going
to make it a no cache object. But that doesn't solve my problem.

Why it is craeting that gap?
What could be the reason?

Thanks Tom.

          Followup:

normal aging in the shared pool.

Why is it a problem?  even if we burn 100,000,000 sequences every second
forever, it'll take:

ops$tkyte@ORA920> select rpad('9',27,'9')/100000000/60/60/24/366 from dual;

RPAD('9',27,'9')/100000000/60/60/24/366
---------------------------------------
                             3.1623E+11

YEARS before you run out -- 100million a second for 3.16e10^11 years....

you could "pin" your sequence if it really bothers you.  search for

pinning sequence

      Rating: 4
Gap in
      sequence  January 17, 2003
Reviewer:  Raza
       from Canada
I had the same problem, it was just because Oracle was caching 20 sequences in
the memory and program used only 2 or 3 sequences, leaving 17 or 18 sequence in
the memory.
Overnight cold backup takes the database down, wiping those remaining sequences
from memory, whcih creats gap in the sequence.
Tom I would like to know the work around too??

          Followup:

the work around -- don't shutdown your database -- ever...

You must be in archive log mode (else you don't care about your data and we
wouldn't be having this discussion because you don't really care)....

Therefore doing anything OTHER then a hot backup is "not a good idea", in fact
it is "a really bad idea".

So, stop doing this to yourself, do the right thing and never shutdown.

      Rating:
      4
sequence  January 17, 2003
Reviewer:  A
      Reader  from KY
Tom,
one question

My programs are runnig just twice a day. It is not online than how it would make
a perfomrncae difference if i use a no cache sequence.

          Followup:

every seq.nextval will be an update instead of every 20 nextvals.  if your batch
job calls nextval more then once, it'll update 20 times as often.

maybe you won't notice it
maybe you can just ignore the gaps ( like I do )

      Rating: 4
pining a
      sequence  January 17, 2003
Reviewer:  A
      reader  from KY
do we need to pin a sequence everytime we start instance/server(using a trugger)
or it is just one time.

          Followup:

i would not really recommend it at all but if you do it, you'll need to do it
after each and every restart.

January 17, 2003 Terry -- Thanks for the question regarding "How
      to find object I/O contention", version 9.2.0

      You Asked
Hi Tom,

As some technical papers mentioned, DBA should find out which datafile(s) cause
I/O contentions and move them around physical hard disks in order to have better
physical disk channel utilization and I/O balance. This is what I normally do
now.

However, I read a paper and indicated that we should find out which object(s)
cause I/O contention rather than just move datafile(s) among disks. Once we
found out, and then do something on those "hot object". e.g . table or index
partitioning. This seems reasonable to me in fact.

My question is how to find out these hot objects. And, your comment on this
claim (find out hot object which caused I/O contention)

Thank you in advance

      and we said...
look at v$segstat -- it'll give you tons of stats by object.  This is new in 9i

If you are not using SAME (stripe and mirror everything) and you do not have a
segment/ datafile -- this will help you find the hot objects and more
appropriately distribute them across devices -- sure.  If you just know that
file 5 with 500 objects in it is "hot" -- what do you do?? if you know that file
5 is hot and apparently object 55 is really hot - now you can work to fix it.
Review &
      Followup

      Rating:
      5
  January 16, 2003
Reviewer:  terry
       from Taiwan
Thank you so much. Please mention more on the 9i segment- level statistics and
its applications can help me to do better job on this situation.

By the way, I have found a book (Expert One-On-One Oracle Performance, will
publish couple months later) at Amazon web site (Are you writing this book?) If
so, could you mention me the content of this book...I cannot wait to have it.
Thank you.

          Followup:

did you read about the view in the reference guide?

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96536/ch3166.htm#1098972
then just query it.  for example, to find the segments with the most
reads/writes, you might:

ops$tkyte@ORA9I> select owner, object_name, object_type, statistic_name, value
  2    from dba_objects,
  3         (select statistic_name, value, obj#
  4          from (select a.*, max(value) over (partition by statistic_name) mv
  5                    from v$segstat a
  6                   where statistic_name in ( 'logical reads',
  7                                             'physical reads',
  8                                             'physical writes',
  9                                             'physical reads direct',
 10                                             'physical writes direct' )
 11                 )
 12           where value = mv
 13             and mv <> 0) b
 14    where dba_objects.object_id = b.obj#
 15  /

OWNER      OBJECT_NAME                    OBJEC STATISTIC_NAME            VALUE
---------- ------------------------------ ----- -------------------- ----------
BOPPS      SYS_LOB0000039547C00007$$      LOB   physical writes           27026
                                                direct

PEOPLE1    DR$WWC_PEOPLE_PROFILE_IDX$X    INDEX physical writes          150350
PEOPLEA    WWC_PEOPLE_FAST_PEOPLE         TABLE logical reads         386025152
PEOPLE_RAW SYS_LOB0000039609C00009$$      LOB   physical reads          1858082
                                                direct

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6731060526417
for a blurb about the book -- title will most likely change and its due out in
the spring/summer.

      Rating: 5
I/O Contention
      on 8i  January 17, 2003
Reviewer:  Michael
      T.  from Dallas, Tx
Is there any method to get "hot" object information on 8i?

          Followup:

see

http://www.dbazine.com/connor.html

      Rating:
      5
v$segment_statistics  January 17, 2003

Reviewer:  Robert Xuequn Xu
I saw there was a v$segment_statistics. Can it be used intead of v$segstat? Then
we don't need to make a join with dba_objects.

          Followup:

of course

January 17, 2003 Heath -- Thanks for the question regarding
      "Cursor reuse of static SQL in triggers", version 9.2.0

      You Asked
Tom,

If a trigger body has static SQL statements in it, should it be expected that
the parsed cursors for those statements will be reused if the same triggers are
fired repeatedly in the same Oracle session?  Shouldn't this be similar to how
PL/SQL procedures work in that the PL/SQL engine manages these static SQL
cursors for you and reuses them for the duration of the session?  That is, if
you monitor the SQL statement usage you will see increased execution counts but
non increasing parse counts for the static SQL?

I'm asking because we are testing out application under load and are seeing
library cache latch contention.  We looked through the stats pack reports and
saw much more parsing than we expected for SQL that is being performed in
commonly fired triggers.  I had thought that by using static SQL we didn't have
to worry too much about these statements getting parsed excessively.  I thought
that Oracle would manage this for us.

I also put together a quick test where the same SQL statement was called from
with triggers and from within procedures in a package.  I executed them and
monitored the parse and execution counts.  For the SQL in the procedures, I saw
that the cursors were being reused upon successive invocations in the session
(execute count increases, parse count does not).  However, for the static SQL in
the triggers, the statements were always parsed and executed on each firing
(both counts increasing).

Is this correct?  And if so, what is a good method for getting cursor reuse for
the trigger SQL?  We've tested moving the SQL from within the trigger body to
procedures called from within the trigger body and that shows the cursors being
reused.  Is this a good approach?

Thanks,

Heath

      and we said...
it releases them at the end of the call -- if you

insert into t values ( 1 );
insert into t values ( 1 );

it'll show 2 parses -- but if you

insert into t select 1 from all_objects;

it'll show 3 -- even though the trigger fired thousands of times.

Your approach of moving the code into a procedure is not only sound, it is what
I would do with all but the most trivial of triggers.

Review &
      Followup

      Rating:
      5
Why?  January 17, 2003
Reviewer:
      Kashif  from Houston, TX
Hi Tom,

Is there a reason why this optimization was not done? That is, cursor reuse for
static sql in triggers? Has this been fixed in later versions?

Kashif

          Followup:

Sometimes things just are.

As there is a trivial solution -- and given that I would put any real logic into
a package -- it is sort of a "low bandwidth issue" I would guess (much bigger
fish to fry)....

I don't have the definitive answer -- did not really look to even see if there
was an enhancement request, there well could be.

January 17, 2003 Tushar -- Thanks for the question regarding "Tree
      Query.", version 8.1.7.2

      You Asked
Hello Tom,

One query is really giving me trouble. I want to display the last node and the
top node of the tree in a same row.Eliminating the intermediate nodes.

for example, my data looks like this. also the parent node will have exp_dt as
NULL

node id  parent id
======== =========
1          2
2          3
3       7
4          8
5          9
9          10

The query should return
1    7 (not 2 as 7 is the top most node)
2    7
3    7
4    8
5    10 (not 8)
9    10

Thanks,
Tushar

      and we said...
ops$tkyte@ORA920> select id, to_number( substr(
  2             (select max( to_char(level,'fm00000') || pid )
  3                from t t2
  4               start with t2.id = t.id
  5               connect by prior pid = id ), 6 ) ) top
  6    from t
  7  /

        ID        TOP
---------- ----------
         1          7
         2          7
         3          7
         4          8
         5         10
         9         10

6 rows selected.

Review &
      Followup

      Rating: 5
Just
      Great  January 17, 2003
Reviewer:  Tushar
       from reno, nv, usa
It solved my problem..works great, Thanks a Lot.

      Rating:
      5
  January 23, 2003
Reviewer:
      A reader

January 18, 2003 Mazhar -- Thanks for the question regarding "CPU
      time consumed by SELECT,UPDATE,DELETE,INSERT statements", version 8.1.6.3

      You Asked
I want to know the SQL or PL/SQL script which displays
the CPU time taken by all the  SELECT,UPDATE,DELETE,INSERT statements which run
against the Database.

      and we said...
We do not track that information.

See V$SQL for the data we do track but bear in mind that that list is NOT a list
of all SQL / PLSQL executed -- it can be flushed out of them if memory is short.

See V$SYSSTAT for system level stats like this:

ops$tkyte@ORA8I.WORLD> select * from v$sysstat where lower(name) like '%cpu%'
  2  /

STATISTIC# NAME                                CLASS      VALUE
---------- ------------------------------ ---------- ----------
         8 recursive cpu usage                     1   86477773
        11 CPU used when call started            128  168336166
        12 CPU used by this session                1 4.6141E+11
       169 parse time cpu                         64    3992541
       203 OS User level CPU time                 16          0
       204 OS System call CPU time                16          0
       205 OS Other system trap CPU time          16          0
       211 OS Wait-cpu (latency) time             16          0

8 rows selected.

Review &
      Followup

      Rating: 3
Estimating CPU
      in real world  January 17, 2003
Reviewer:
      Nihal
Hi Tom,
In the real world we need to estimate the CPU requirments, before building the
application we atleast need to give them the approximate speed of the CPU like a
recomended speed. Is there a way of estimating that say using 9.1 database on NT
4, estimated size of the database will be 12 GBS 1000 Transactions a day
updating 10 tables should cater for 25 concurrent user in peak time, with 100
reports executed in a day on the system
Has a batch process in the night.

How does one give a comfortable estimate in RAM and CPU for the size.

In other words what are the criteria do we consider and if and if we have those
criteria, how does one clculate the app size, These are very day to day problem
ppl face in giving proposals presenting cases to the management who actually
approves/disaproves the cost.

Management normally doesnt want to go to a stage where they want to analyze and
design the system and then think about the hardware and other cost. ?

Can you throw some light at estimate CPU/RAM size in planning stage.

Thank

          Followup:

One benchmarks -- in my experience, you can hypothesize, theorize, summarize all
you want -- and you'll always be wrong.

Simulate via a benchmark, and you'll get it.

Estimated size of the db has nothing to do with RAM/CPU.  I've seen a 5gig
database need tons more then a 500gig database.

1,000 transactions a day?  a palm pilot can do that -- that isn't very much.

If I had to swag it -- 25 users, low transactions, sounds like a low end dual
cpu machine with 2gig of ram.  it would run better on linux ;)

      Rating:
      3
CPU  January 17, 2003
Reviewer:
      Nihal
Hi Tom,
Thanks,
My problem is i hv to give the management,if they would need a upgrade in their
system if they made the current DB also as a DW, the DW as such is only 10 gigs
of data, They currently have a single CPU P3 1266 Mhtz with 2 gigs of ram
The OLtp system does not show any contention in the stats pack,the users are
also okay with the speed, if you had to suggest a dual CPU, what would the be
the ideal cpu you would you suggest, just from ur expeerience, based on this
limited input i have.

Thanks

Nihal

          Followup:

it is very small, practically any server on the market today would suffice.

January 18, 2003 Winston -- Thanks for the question regarding
      "consistent gets latch", version 8.1.7

      You Asked
Hi Tom,

You said,

Each consistent get is a latch, latches = contention

to get a block from the cache, we have to latch (to prevent someone from
modifying the data structures we are currently reading).

A latch is a lock.

Locks are serialization devices

Serialization devices inhibit scalability, the more you use them, the less
concurrency you get.

On the other hand, at the higher level we know that Oracle query (select)
operation will not block writing (update or insert or delete). SELECT will not
lock the underlying data (unless we use select ... for update ), but each select
statement (without for update) will have consistent gets...which means cause
latches. Can you please help me understand this? Why does Oracle need the latch
on the cache buffer chain for SELECT statement?

Thank you,
Winston

      and we said...
because you cannot have someone MODIFYING a data structure, such as a linked
list of cached blocks, at the same time as someone is READING such a data
structure.  A select may well modify the list of blocks in the cache (causing
some to be flushed, others to be read).  An update may modify the same
structure.

Someone wanting to read the cache buffer chain cannot permit someone else to be
MODIFYING the cache buffer chain.  For a split instant, they will wait in a line
to access this shared data structure.  They do not lock at the transaction level
like a shared lock on a row would -- rather they serialize on these very low
level operations on these shared, memory based, data structures.

Review &
      Followup

      Rating: 5
Thanks for your
      clarification  January 19, 2003
Reviewer:
      A reader
It's much cleaer now.

January 18, 2003 Joseph -- Thanks for the question regarding "Can
      Global Temporary tables be cached or created in Memory?", version 8.1.7

      You Asked
Curious if it is possible to create (mark permanently cached) glabal
Temporary tables in memory, to speed up process witing and reading from it?
Thank you.
Joseph

      and we said...
no, the database will take care of it.

If it doesn't need to use disk, it won't.

If it does, it will.

Don't try to second guess the database like this -- CACHE is not always
"fast=true".  DISK is not always "slow=true".  In fact, quite the opposite may
be the case.  Accessing things through the cache could be quite slow.  Accessing
things via disk may be much faster....

January 18, 2003 M -- Thanks for the question regarding "Using
      Resource Manager or not", version 9.0.1

      You Asked
I found my top 3 wait events are (also numbers of wait time are big):

resmgr: waiting in end wait
resmgr: waiting in check2
resmgr: waiting in check

Does that mean that we are using up the db resource (too many sessions?) ? If I
disable Resource Manager, will these events go away? Also, does using/or not
using Resource Manager help performace?

Thanks for any help.

      and we said...
well - think about this - you will ALWAYS have some wait events, you will never
get rid of them all.

Ask yourself this question:

o is my system running at or above the expected, documented, mandated response
times?

if so -- move on, you are done.  if not, lets find out why.

The wait "waiting in end wait" and the waiting in check/check2 means that the
process (thread) is ready to run but due to your policy (which you put in place
after considerable thought)we find that there are insufficient resources to
allow it to run so we have to wait.

That is -- YOU said "i want this resource group to have X resources, if they
exceed X resources, hold them back".  This wait event shows the resource manager
is doing exactly what you told it to.  It is functioning exactly the way it was
intended.  There is a group that is exceeding the resources you allocated to it
and the resource manager is holding their consumption of resources below that
threshold.

So, what happens if you remove this wait?  Eg: disable the resource manager.
Well, then this group will be able to consume an infinite amount of that
resource and your system may well "fall over" totally.  EG: you constrained a
group of reporting users to 40% of the CPU on the machine -- they are hitting
this limit and the other 60% is being used by the OLTP group.  Now, the resmgr
waits are all about these reports.  So, you remove this wait and let the reports
users go to town.  Now your system is running REALLY SLOW.  the OLTP users
cannot do a thing - why?  the reports have taken over.

So, think about what it is you want to accomplish.  Whats your goal?  Did you
mean to use the resource manager to control the usage of resources by group?  If
so, you got it.  If not, by all means turn it off -- you don't need it.  But --
think about it.
Review & Followup

      Rating:
      5
Excelent  November 14, 2003
Reviewer:
      Wilson Guerrero-Coltters  from Concepcion, Chile
Very fine explanation.

January 18, 2003 -- Thanks for the question regarding "List of all
      databases on a Server and Exceptions from a Wrapped Package.Procedure",
      version 8.1.7

      You Asked
Hi Tom,
Couple of questions:
1) Is there some way to list all the available databases on a NT server? I
sometimes have a situation where I know the name of the NT server but I do not
have access to it, but would still like to get a list of all the available
databases.

Example:

NT Server: TEST_ERP
Oracle Version: 8.1.7
Databases available: TST1, TST2, TST3 and TST4.

I have no access at all to server TEST_ERP. But I know the name of the server(or
HOST) is TEST_ERP and I would like to find out all the available databases on it
(in this case the above 4).

2) I am trying to debug an error message coming out of an ERP Application. This
exeption comes from a "WHEN OTHERS EXCEPTION" [Yes, we should avoid as much as
possible, maybe never use "WHEN OTHERS" exception, but I am stuck with it for
now]. My problem is the error message does not list any Oracle error number
directly, it raises an exception that points to another Package.Procedure.
Normally, I would have tried to trace this exception by adding trace messages in
this Package.Procedure, but in this case, this package is Wrapped. So my
question is, would SQL_TRACE be an ideal tool in this case to find out what is
causing the error. (meaning the SQL code behind it). I am going to try this
option but just wanted to know your views on it and any words of wisdom that you
can pass along based on your experience.

Thanks for your time,
Raghu

      and we said...
1) if you have lsnrctl on your machine, yes.

lsnrctl status hostname

2) search this site for dbms_trace to see a way to get all exceptions logged to
a database table at runtime automagically.

Review &
      Followup

      Rating:
      5
  January 18, 2003
Reviewer:
      A reader
Hi Tom
Output from

lsnrctl status hostname

is
Alias                     LISTENER
Version                   TNSLSNR for Solaris: Version 8.1.7.3.0 - Production
Start Date                26-OCT-2002 17:10:53
Uptime                    83 days 19 hr. 0 min. 21 sec
Trace Level               off
Security                  OFF
SNMP                      OFF
Listener Parameter File   /var/opt/oracle/listener.ora
Listener Log File         /oracle92/product/network/log/listener.log
Services Summary...
  CRMDEV                has 2 service handler(s) <---
  ORADEV9               has 2 service handler(s) <---
  ORAQA1                has 1 service handler(s)
  ORAQA3                has 2 service handler(s) <---
  ORAREP4               has 1 service handler(s)
  PLSExtProc            has 1 service handler(s)
  RENPDEV1              has 2 service handler(s) <---
  RENPDEV2              has 1 service handler(s)
  RENPDEV2              has 1 service handler(s)
The command completed successfully

Waht do you mean by
"has 2 service handler(s)" ??

Thanks

          Followup:

lsnrctl services hostname

will reveal yet more detail and tell you what each service handler is about (2
generally means "i'll take dedicated and shared server connections" )

      Rating:
      5
Thanks  January 18, 2003
Reviewer:
      A reader
Tom,

Thanks for your time and answers. I will try them and get back to you with the
results(especially the exception issue)

Thanks,
Raghu

      Rating: 5
What if the
      databases are not registered in the listener?  January 18,
      2003
Reviewer:  A reader
Hi Tom,
It's possible to have a database on the Server and have no entries of this
database in the listener. I guess, from Oracle 8i (not sure about the release),
we can still connect to this database from different clients. There are some
cases where the database has to be registered in the listener.

Based on the above, I am assuming, we will not be able to find out those
databases that are not registered in the listener. So, is there any other way?

Thanks,
Raghu

          Followup:

you can find them out -- either the servers dynamically register with the
listener OR the listener explicitly lists them out in its listener.ora.

so either way -- lsnrctl can tell you what is there.

January 19, 2003 Edgar -- Thanks for the question regarding
      "tables..", version 9iR2

      You Asked
Hello Tom,

Suppose I need to store data from a sensor(sensors) in the database for 1/2 hour
and I can delete the data that is older than 1/2 hour. (I need to create
something like queue). I've seen some applications that implemented this like
this: drop/create tbl_a, fill tbl_a with data for 1/2 hour, than drop/create
tbl_b and fill it with data, than in 1/2 hour start from a beginning. What I
want to ask is what do you think is the best approach to create data queue in
Oracle database?

Than another approach this app is using: it don't
create table tbl_a( s_id integer, time date, value float );, but they create
table tbl_a( s_id integer, timemax date, time1 date, value1 float, time2 date,
value2 float, time3 date, value3 float ); Can you, please, explain what
advantages they are trying to achieve by doing so?

Thank you.

      and we said...
I would create a partitioned table with a column called "seconds_since_midnight"
into which I insert "to_char( sysdate, 'sssss' )" when I put a new row in there.

values less than 10*60 into p1
values less than 20*60 into p2
                 30*60 into p3
...
and so on. each partition has 10 minutes worth of data.

Then, have a job that runs that truncates the old partitions as they "expire"...

Review &
      Followup

      Rating: 5
drop VS
      truncate  January 18, 2003
Reviewer:  Edgar
      Chupit  from Latvia
Hello Tom,

What is the difference between drop table and truncate table [table_name] reuse
storage. Thank you.

          Followup:

one drops the table and the other doesn't.  that would be the main difference.

      Rating: 5
drop/create VS
      truncate  January 19, 2003
Reviewer:  Edgar
      Chupit  from Latvia
Hello Tom,

Sorry, I incorrectly asked my question, I wanted to ask what is the difference
between recreating (drop/create) table and truncate reuse storage? Thank you.

          Followup:

one loses all grants, invalidates all code/views etc, could cause the object to
just "disappear" (system crashed after drop before create for example). (drop)

the other doesn't. (truncate)

if your goal is simply "empty table", truncate is the correct command to use.

January 19, 2003 yu -- Thanks for the question regarding "help for
      alter tracing ", version 8.1.7

      You Asked
   i am a DBA for oracle .

   i find that some understanding to oracle internals is very important to
oracle performence tuning .

   i am  instresting in oracle internals.

   but i am a newman for oracle internals and i am studying on it ;

   first i must know what oracle do internally in a operation,

   i knew perhaps there are some trace methods (not sql trace) to monitor
oracle internal operations,such as backgroud (forgroud) processes activities,
buffer managerments and so on

   i found one sql command "alter tracing",but i can not find some

   syntax descriptions for using it  , could you tell me how do i use it?
                  thanks a lot

      and we said...
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5098751796091

there is no such command.  I believe you might mean SQL_TRACE=true, that is
documented in the performance/tuning guide.

I don't think you want "internals".  I think you need the basics.  See that
other link, read those documents, master them -- comprehend them and the rest
will take care of itself.
January
      19, 2003 Vasaku -- Thanks for the
      question regarding "Advise ", version 8.1.7

      You Asked
Tom

If I have to make myself really really strong in Oracle fundamentals, what are
the chapters in your book( Ihave expert one to one) Ihave to read again and
again.

I have have the Jonathan Lewis oracle 8i Practical book. What are the chapters
in that book you recommend that I read to be very very good in fundamentals.

And

What do you think are the core fundamentals of Oracle, which one has to be
thorough in. Can you list them out please( please list them out as 1., 2.  etc)

thanks

      and we said...
The core fundementals -- you need to understand and be conversant in everything
in this document:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/toc.htm
the chapters are convienently numbered from 1 .. 31 -- giving you your required
list ;)

In my book -- the first 7 chapters are the "core fundemental" chapters, the
remainder of the book can be read "piecewise" as needed for reference.

JPL's book is much the same -- his section I is the "core" stuff.
Review &
      Followup

      Rating: 5
Do you
      recommend  June 13, 2002
Reviewer:  Vasaku

I have the expert one to one , do you still recommned to buy the other new book
of yours displayed on your asktom home page.

If the answer is yes, can you give the reasons.

          Followup:

Are you a beginner?

If so, then Beginning Oracle Programming would be recommend.  It is the
"prequel" to Expert one on one.

If you have mastered much of the material in Expert one on one (or the server
concepts guide) and do not consider yourself a beginner, then probably not.

      Rating: 5
let me post
      those 7 chapters  June 13, 2002
Reviewer:
      Vasaku
Chapter 1: Developing Successful Oracle Applications
Chapter 2: Architecture
Chapter 3: Locking and Concurrency
Chapter 4: Transactions
Chapter 5: Redo and Rollback
Chapter 6: Database Tables
Chapter 7: Indexes

How come you dont think tuning strategies is not part of the needed core
stuff...

          Followup:

Yes and no.

But - I could make that argument for virtually ANY chapter on oracle in ANY
book.

gotta draw the line somewhere.  You wanted the fundementals -- those are they,
in my opinion.

      Rating:
      4
novice  June 13, 2002
Reviewer:
      daisy  from usa
hello Tom,
I too need such advices from you, 2 days ago I had a chance to ASK A QUESTION by
the time I typed my question the time to ask was over and status had been
shifted to non acceptance.
Ihave two other related questions

1. To become a strong oracle DEVELOPER programmer [has done developer 2000]
what other programming languages you suggest (e:g java or .....).
2. To establish as a strong DBA what other courses you suggest (e:g operating
systems or .... )

I hope you will understand what I am trying to ask you, you an oracle expert.
thanks daisy.

          Followup:

No, the interface doesn't work that way.  If you get to the screen that lets you
type in a question, you are "grandfathered" in -- you get to submit.  I
sometimes have 15 or more questions even when I set the queue size to 10 due to
this.

1) Java won't hurt but it depends on what company you work for doesn't it.  Java
is fairly popular today (and no doubt something is coming soon to replace it) as
a language.

2) I personally suggest "real life".  You only learn so much in a course.
Knowing the OS is a pre-requisite.

      Rating:
      4
advice  June 13, 2002
Reviewer:
      DAISY
TOM
JAVA AND OS I GAVE AS EXAMPLES I REALLY NEED YOU TO SUGGEST ME SOME LANGUAGES OR
COURSES THANKS

          Followup:

Sorry, but I don't really dispense job advice -- i don't know you, your goals,
or even what country you are in.

Ask me about the database and I'll be glad to answer!

      Rating:
      1
sorry  June 14, 2002
Reviewer:
      daisy
tom sorry, did nt mean that way, did nt mean job,its just that we say physics is
the good combination with maths i meant that way.

      Rating: 5
So, Guru,
      What's coming???:)  June 14, 2002
Reviewer:
      J  from ma
1) Java won't hurt but it depends on what company you work for doesn't it.  Java

is fairly popular today (and no doubt something is coming soon to replace it) as

a language.

      Rating: 3
Sequel to
      expert one-on-one?  December 02, 2002
Reviewer:
      Tarry Singh  from The Netherlands
Tom,
I didn't want to waste a brand new thread on it so...
With so manu releases(I've heard another one next summer) how and what are you
going to do with your book?
Would there be a part 2 or second edition or what?

Tarry

          Followup:

I'm going to skip 9ir1/r2 and am considering a 10i "second edition".  So, it
would be 1.5 years at best (2003/2004).  100% of Expert one on one is 100%
applicable to 9i -- there are tweaks here and there (eg: the job queue
processing is totally rearchitected, so I don't explain those processes but the
FUNDEMENTALS are still 100% accurate)....

I'm doing a "best practices" book now.  Version independent (he says
confidently;)

      Rating: 3
French version
      of your book  January 16, 2003
Reviewer:
      yazid  from FRANCE
Hi tom,
Is there any french version of your book Expert one on one ?

I'll work soon on an application third-party and/or n-party which use XML in an
Oracle8i database. Do you know best books which speaks about:
- The best way to use XML with Oracle8i databases.
- Advices concerning the use of Oracle8i in n-party applications

Thank you

          Followup:

don't know if it is natively in french -- sorry

http://www.amazon.com/exec/obidos/tg/detail/-/1565926919/
is a good xml book for 8i

      Rating:
      5
  January 16, 2003
Reviewer:  Juan
      Carlos Reyes
yazid  from FRANCE
I say honestly that english is absolutely needed to be a DBA, not only because
most documentation is in english, else because the standard language for any new
document ( several of them are very important to read, because not all the
answers are in oracle's documentation ) or sites like this are in english.

I suggest you buy the english version and take a dictionary and start, The first
pages takes days to read, but with the time you'll read faster.

I remember the first 30 pages I read in english (about OBE)
takeme 3-5 days per pages, now I read fluently english.

I had seen how some people try to work with oracle without knowing english, and
I had seen how much time they lost because they don't know it.

Believe THERE IS NO OTHER CHANCE, you must learn english.
:)

      Rating:
      5
  January 19, 2003
Reviewer:
      A reader
Hi Tom,

I know you always recommend to read Oracle docs, what do you think about ILT's.

          Followup:

great - after you read the books - if you need it.

January 19, 2003 Subrata -- Thanks for the question regarding "How
      is the estimated execution time calculated?", version 9i

      You Asked
The database resource manager calculates the estimated execution time for a job
(corresponds to MAX_ESTIMATED_EXECUTION_TIME for dba_rsrc_plan_directives):

1. Is this estimate calculated for the whole job (say a procedure is submitted
by a dbms_job) or only for a particular SQL statement?

2. Does this take into account the current load on the system or is it purely
based on the explain-plan

3. How can we use this feature to estimate the time fom a job from outside (say
I am not interesetd in the resource plans etc.) Any inbuilt procedure, that can
be used to do the estimatation for any query or a dbsms-job?

With good wishes

      and we said...
1) sql statements only -- not a plsql routine, but the sql within

2) based on the cbo's costed plan.  does not take load into consideration unless
you collected system statistics -- then it would.

3) you cannot.  However, you can (should) use dbms_application_info to set
values in v$session_longops in all of your long running procedures.  We do it
for our stuff (creates, scans, sorts, etc).  If you query v$session_longops --
we register long running things in there and you can to.  If you have my book
"Expert one on one Oracle" -- i go into this package in some detail.  Using it,
you can easily track the progress and estimated completion time of your stuff.
Review &
      Followup

      Rating:
      4
  January 19, 2003
Reviewer:
      A reader
Thanks

      Rating: 4
How does
      Resource Manager handle when a job is submitted  January 19,
      2003
Reviewer:  Subrata Saha
A session is created for submitting  a job which is a PL-SQL procedure. How will
resource manager use the max_estimated_exec time to handle this session. Will it
consider the first SQL statement in the PL-SQL procedure? If so, the next SQL
statement might be very resource consuming. So it might be bad way because jobs
are not being handled in totality (say switching to other groups).

Thanks

          Followup:

Each statement is handled statement by statement.  It is done for a SQL query --
not a PLSQL procedure.

It is not the first, the last or the middle - it is each and every statement,
statement by statement.

January 20, 2003 -- Thanks for the question regarding "Dynamic Sql
      : where to put it in Java or Stored Procedure", version 817

      You Asked
Hello Sir,
Environment :Oracle 817,weblogic,jsp

Q1)We have a requirement of displaying some data from table on the front end but
the ordering is dynamic depending on the user input.
   What is more efficient
             a)Write a stored procedure which will construct a dynamic string
using open ref_cursor for 'string' and then return  a
Record set.
        Or
             b)Construct it in java DAO and issue that select statement.

I need to convince this to the java guys since I am an Oracle Guy.
How do i prove technically the answer to this question whatever it is a or b .
Though I am inclined to solution a.

Q2) In general even if we issue statements directly from java i think it is
immaterial whether it was dynamic or not the final sql contructed is always
static and will always be a hard parse.
   , whereas if we go for the stored proc approach we do construct dynamic sql,
but by using bind varaibles we may avoid hard parse atleast in cases where we
are not dealing with column names which cant be binded .Correct me if I am
wrong.

Can u pls give me directions/example to technically test this

Thanx

      and we said...
q1) it'll be 6 one way, 1/2 dozen the other.  They are both cursors at the end
of the day.

with a) you can tune infinitely easier, add security, whatever.  With b) well,
you cannot do very much without rebuilding the java app, redeploying it to N app
servers, etc.

q2) false.  all sql in Oracle is dynamic sql -- even static ;)  The shared pool
is what controls hard/soft.  If the developers use bind variables and build the
queries consistently -- they will be soft parsed.

Java can soft parse
Plsql can soft parse
Java can hard parse
Plsql can hard parse

it is not a function of the language at all.
Review &
      Followup

      Rating:
      5
Q  January 18, 2003
Reviewer:
      A reader
Also will not the stored proc approach better because it is using the pl/sql
engine which is a Block transfer of select statment where as for Java individual
select statments are send to Oracle.

Thanx much for your answer

          Followup:

there will not be signifcant differences between:

java executing

    begin get_result_set( ? ); end;
    fetching and printing result set

and java executing

    select .... where .... order by ....
    fetching and printing result set

Now, if it is the difference between:

java executing:

    begin get_lots_of_result_sets( ?,?,? ); end;
    fetching & print rset 1
    fetching & print rset 2
    fetching & print rset 3

and java executing:

    select ....
    fetch and print
    select
    fetch and print
    select
    fetch and print

you may have something there (reduced round trips)

      Rating: 3
One way to do
      this  January 19, 2003
Reviewer:  Mohammed
      Khan
import java.sql.*;
import java.sql.Date;
import oracle.jdbc.OracleDriver;
import java.util.*;

public class DynamicPreparedStatement
{

  public static void main (String arr[]) throws Exception
  {
    DriverManager.registerDriver(new OracleDriver());
    Connection con =
DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl","scott",
"tiger");
    EmpVO test = new DynamicPreparedStatement(). new EmpVO();
    //test.buildQuery(con);
    test.empno = new Long (7839);
    test.buildQuery(con);
    test.empno = null;
    test.ename = "%A%";
    test.buildQuery(con);
    test.sal = new Long(4000);
    test.buildQuery (con);
  }

  public DynamicPreparedStatement()
  {
  }

class EmpVO
{
  public Long empno;
  public String ename;
  public Long  sal;

  public void buildQuery (Connection con) throws Exception
  {
    Vector vec = new Vector();
    StringBuffer sb = new StringBuffer("SELECT * FROM EMP WHERE 1 = 1 ");
    int colCount = 0; //we will maintain the prepared statement bind parameter
values
    if (isNotNull(empno)) {
      sb.append("AND empno = ? ");
      vec.addElement(empno);
      colCount++;
    }
    if (isNotNull(ename)) {
      sb.append ("AND ename like ? ");
      vec.addElement (ename);
      colCount++;
    }
    if (isNotNull(sal)) {
      sb.append ("AND sal < ? ");
      vec.addElement (sal);
      colCount++;
    }
    System.out.println (sb.toString());
    //now we will bind the variables
    PreparedStatement ps = con.prepareStatement(sb.toString());
    for (int i = 0; i < vec.size(); i++)
    {
      ps.setObject(i + 1,vec.get(i));
    }

    ResultSet rs = ps.executeQuery();
    while (rs.next())
    {
      System.out.print ("empno : " + rs.getString("empno"));
      System.out.print ("......ename : " + rs.getString("ename"));
      System.out.println (".....sal : " + rs.getString("sal"));
    }

  }

  public boolean isNotNull (Object obj)
  {
    return obj != null && !obj.toString().equalsIgnoreCase("");
  }

}//end of inner class EmpVO

}

Execution Results

SELECT * FROM EMP WHERE 1 = 1 AND empno = ?

empno : 7839......ename : KING.....sal : 13524.08

SELECT * FROM EMP WHERE 1 = 1 AND ename like ?

empno : 7499......ename : ALLEN.....sal : 4327.72

empno : 7521......ename : WARD.....sal : 3381.09

empno : 7654......ename : MARTIN.....sal : 3381.09

empno : 7698......ename : BLAKE.....sal : 7708.73

empno : 7782......ename : CLARK.....sal : 6626.78

empno : 7876......ename : ADAMS.....sal : 2975.28

empno : 7900......ename : JAMES.....sal : 2569.53

SELECT * FROM EMP WHERE 1 = 1 AND ename like ? AND sal < ?

empno : 7521......ename : WARD.....sal : 3381.09

empno : 7654......ename : MARTIN.....sal : 3381.09

empno : 7876......ename : ADAMS.....sal : 2975.28

empno : 7900......ename : JAMES.....sal : 2569.53

---------------------------------------------------------

But one thing to note :
If you have many combinations (different users with different queries) you will
be better off by using Statement rather than PreparedStatement, because the
overhead of creating PreparedStatement is more than creating Statements.

Am I rignt Mr. Kyte ?

          Followup:

No -- I wish - really wish - that a "Statement" object were removed from the
jdbc spec alltogether.

Lets say you have 3 columns, you could have 8 different predicates.
1  - 0 column predicate
3  - 1 column predicate
3  - 2 column predicate
1  - 3 column predicate
-
8

Now 4

1  - 0
4  - 1
6  - 2
4  - 3
1  - 4
--
16

and so on.  You have a FINITE number  -- a very finite number.

Now, take just a single column predicate - empno.  Suppose you don't use a bind
variable for that.

select * from emp where empno = CONSTANT;

you all of a sudden have an INIFINITE NUMBER of them.  As demonstrated over and
over -- the cost of the prepared statment in jdbc is almost entirely in the
LOADING OF THE CLASS OBJECT the first time (eg: if you use it once, the code of
all other invocations is the same as a statment object)

We've had this discussion -- You (Mohammed Khan) already knew what I was going
to say.  Statements are terrible -- PreparedStatements are good, period.

btw: to see the way I would do this:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279
It would be in plsql so as to make it

o easier to tune
o easier to manage
o easier to debug
o easier to deploy
o reusable by anything that can connect to a database (which is a lot of things)

      Rating: 3
StoredProcedure
      Vs SQL  January 20, 2003
Reviewer:  Mohammed
      Khan
<QUOTE>
Now, if it is the difference between:

java executing:

    begin get_lots_of_result_sets( ?,?,? ); end;
    fetching & print rset 1
    fetching & print rset 2
    fetching & print rset 3

and java executing:

    select ....
    fetch and print
    select
    fetch and print
    select
    fetch and print

you may have something there (reduced round trips)
</QUOTE>

If I am getting only one resultset then I directly execute the query and avoid
the overhead of calling the Procedure which in turn executes the query.

I allready saw that discussion, and also read a similar technique in the chapter
on dynamic sql from expert one on one.

          Followup:

Mohammed - it is already known you would do it all in java.  But I don't get
your point about the above as I said (but you edited out) before that:

there will not be signifcant differences between:

java executing

    begin get_result_set( ? ); end;
    fetching and printing result set

and java executing

    select .... where .... order by ....
    fetching and printing result se

What "overhead" do you refer to....  We avoided the "overhead" of running some
java code, you avoided the "overhead" of running some plsql code.  it is a
"wash"

January 20, 2003 Michael -- Thanks for the question regarding "How
      to reclaim space from deleted lobs?", version 9.2.0.1.0

      You Asked
Hi Tom!

I have the tablespace "LOBTEST". The DDL:

CREATE TABLESPACE "LOBTEST"
    LOGGING
    DATAFILE 'E:\APPS\ORACLE\ORADATA\ORA92DB\LOBTEST.ORA' SIZE
    10M REUSE EXTENT MANAGEMENT LOCAL SEGMENT SPACE MANAGEMENT
    AUTO;

I have a table with a blob column:

CREATE TABLE "MIKE"."LOB_TAB" ("THEBLOB" BLOB)
    TABLESPACE "LOBTEST" PCTFREE 10 PCTUSED 0 INITRANS 1 MAXTRANS
    255
    STORAGE ( INITIAL 64K NEXT 0K MINEXTENTS 1 MAXEXTENTS
    2147483645 PCTINCREASE 0)
    LOGGING LOB("THEBLOB") STORE AS ( TABLESPACE "LOBTEST"
    STORAGE ( INITIAL 64K NEXT 0K MINEXTENTS 1 MAXEXTENTS
    2147483645 PCTINCREASE 0)
    ENABLE
    STORAGE IN ROW
    NOCACHE CHUNK 8192 PCTVERSION 10);

(The DDLs are from OEM.)
This table is the one and only object in the "LOBTEST" tablespace.

After the creation of the table ("lob_tab") OEM reports that 2.5% of the
tablespace are occupied. Seems OK.

Now i insert some blobs into the table by repeatedly executing the following
statement (the "files" table contains a blob column named "content"):

insert into lob_tab(theblob)
select content from files where id = 30;

...

commit;

The result:

select dbms_lob.getlength(theblob) from lob_tab;

DBMS_LOB.GETLENGTH(THEBLOB)
---------------------------
                     467858
                     467858
                     467858
                     467858
                     467858
                     467858
                     467858

OEM reports then that 6.188 MB (= 61.8%) are occupied. The "Tablespace Map" from
OEM also shows that the table itself uses 8 blocks (it's an 8K block db), the
lob index 8 blocks and the lob data 768 blocks.

Why is the sum of the length of the blobs 3.1 MB, but uses 6.2 MB in the
tablespace?

Now i delete all blobs:

delete lob_tab;
commit;

select count(*) from lob_tab;

  COUNT(*)
----------
         0

I would expect that the tablespace "lob_test" is now nearly empty again - but
OEM (and "Tablespace map") still report that 6.188 MB are occupied. Why is this
so?

Then i tried to insert some new blobs into the lob_tab table:

insert into lob_tab(theblob)
select content from files where id = 30;

I can execute this statement about 5 times succesfully but then i get the error
ORA-01691 (the lob segment xyz in tablespace lobtest cannot be extended). OEM
reports now that the tablespace in 81% full.

Now i delete all again:

delete lob_tab;
commit;

But still i get the ORA-01691 error when i try to insert and the tablespace
remains quite full (according to OEM). The space from the "deleted" blob seems
not to be reused.

(Only when i truncate the table the tablespace gets (nearly) empty again and i
am able to insert again - but to truncate the whole table is not what i want...)

How do i delete rows with lob and reclaim the space from the deleted lobs?

      and we said...
Here is my test setup:

drop table t;
drop tablespace lobtest;

CREATE TABLESPACE "LOBTEST"
LOGGING DATAFILE SIZE 10M
EXTENT MANAGEMENT LOCAL
SEGMENT SPACE MANAGEMENT AUTO;

create table t ( x blob );
declare
    l_blob blob;
    l_size number := 31191-7;
begin
    insert into t values ( empty_blob() ) returning x into l_blob;
    for i in 1 .. 15
    loop
        dbms_lob.writeappend( l_blob, l_size,
utl_raw.cast_to_raw(rpad('*',l_size,'*')) );
        l_size := 31191;
    end loop;
    commit;
end;
/

Ok, let me enumerate the questions here:

1) OEM reports then that 6.188 MB (= 61.8%) are occupied. The "Tablespace Map"
from  OEM also shows that the table itself uses 8 blocks (it's an 8K block db),
the lob index 8 blocks and the lob data 768 blocks.

there is some overhead -- each blob will take more room then it would if it
were just a file in the filesystem.  There is the lob index, there is the lob
segment itself.  All of the data has to be chained together -- managed as a data
structure.  If you run a block like this:

ops$tkyte@ORA920> CREATE TABLE "LOB_TAB" ("THEBLOB" BLOB)
  2  TABLESPACE "LOBTEST"
  3  PCTFREE 10 PCTUSED 0 INITRANS 1 MAXTRANS 255
  4  STORAGE ( INITIAL 64K NEXT 0K MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0)
  5  LOGGING LOB("THEBLOB") STORE AS
  6     ( TABLESPACE "LOBTEST"  STORAGE ( INITIAL 64K NEXT 0K MINEXTENTS 1
MAXEXTENTS 2147483645 PCTINCREASE 0)
  7      ENABLE STORAGE IN ROW NOCACHE CHUNK 8192 PCTVERSION 10);

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      l_blob blob;
  3      l_src  blob;
  4      l_bytes number;
  5  begin
  6      select x into l_src from t;
  7
  8      for i in 1 .. 15
  9      loop
 10          execute immediate 'truncate table lob_tab';
 11          insert into lob_tab (theblob)
 12          values ( empty_blob() )
 13          returning theblob into l_blob;
 14
 15          dbms_lob.copy( l_blob, l_src, i * 32765 );
 16          commit;
 17
 18          l_bytes := 0;
 19          for x in
 20          ( select segment_name || ' extent id ' ||
 21                   extent_id || ' bytes ' || bytes data, bytes
 22              from dba_extents
 23             where tablespace_name = 'LOBTEST'
 24               and segment_type = 'LOBSEGMENT'
 25             order by segment_name, segment_type, extent_id )
 26          loop
 27              dbms_output.put_line( x.data );
 28              l_bytes := l_bytes + x.bytes;
 29          end loop;
 30          dbms_output.put_line( 'l_blob = ' || dbms_lob.getlength(l_blob) ||
 31                                ' bytes, allocated = ' || l_bytes ||
 32                                ' pct used = ' ||
 33                        round(dbms_lob.getlength(l_blob)/l_bytes * 100,2 ) );
 34          dbms_output.put_line( rpad('-',70,'-') );
 35      end loop;
 36  end;
 37  /

SYS_LOB0000033291C00001$$ extent id 0 bytes 65536
l_blob = 32765 bytes, allocated = 65536 pct used = 50
----------------------------------------------------------------------
SYS_LOB0000033291C00001$$ extent id 0 bytes 65536
SYS_LOB0000033291C00001$$ extent id 1 bytes 65536
l_blob = 65530 bytes, allocated = 131072 pct used = 50
----------------------------------------------------------------------
SYS_LOB0000033291C00001$$ extent id 0 bytes 65536
SYS_LOB0000033291C00001$$ extent id 1 bytes 65536
SYS_LOB0000033291C00001$$ extent id 2 bytes 65536
l_blob = 98295 bytes, allocated = 196608 pct used = 50
----------------------------------------------------------------------
SYS_LOB0000033291C00001$$ extent id 0 bytes 65536
SYS_LOB0000033291C00001$$ extent id 1 bytes 65536
SYS_LOB0000033291C00001$$ extent id 2 bytes 65536
l_blob = 131060 bytes, allocated = 196608 pct used = 66.66
----------------------------------------------------------------------
SYS_LOB0000033291C00001$$ extent id 0 bytes 65536
SYS_LOB0000033291C00001$$ extent id 1 bytes 65536
SYS_LOB0000033291C00001$$ extent id 2 bytes 65536
SYS_LOB0000033291C00001$$ extent id 3 bytes 65536
l_blob = 163825 bytes, allocated = 262144 pct used = 62.49
----------------------------------------------------------------------
SYS_LOB0000033291C00001$$ extent id 0 bytes 65536
SYS_LOB0000033291C00001$$ extent id 1 bytes 65536
SYS_LOB0000033291C00001$$ extent id 2 bytes 65536
SYS_LOB0000033291C00001$$ extent id 3 bytes 65536
l_blob = 196590 bytes, allocated = 262144 pct used = 74.99
----------------------------------------------------------------------
SYS_LOB0000033291C00001$$ extent id 0 bytes 65536
SYS_LOB0000033291C00001$$ extent id 1 bytes 65536
SYS_LOB0000033291C00001$$ extent id 2 bytes 65536
SYS_LOB0000033291C00001$$ extent id 3 bytes 65536
SYS_LOB0000033291C00001$$ extent id 4 bytes 65536
l_blob = 229355 bytes, allocated = 327680 pct used = 69.99
----------------------------------------------------------------------
SYS_LOB0000033291C00001$$ extent id 0 bytes 65536
SYS_LOB0000033291C00001$$ extent id 1 bytes 65536
SYS_LOB0000033291C00001$$ extent id 2 bytes 65536
SYS_LOB0000033291C00001$$ extent id 3 bytes 65536
SYS_LOB0000033291C00001$$ extent id 4 bytes 65536
l_blob = 262120 bytes, allocated = 327680 pct used = 79.99
----------------------------------------------------------------------
SYS_LOB0000033291C00001$$ extent id 0 bytes 65536
SYS_LOB0000033291C00001$$ extent id 1 bytes 65536
SYS_LOB0000033291C00001$$ extent id 2 bytes 65536
SYS_LOB0000033291C00001$$ extent id 3 bytes 65536
SYS_LOB0000033291C00001$$ extent id 4 bytes 65536
SYS_LOB0000033291C00001$$ extent id 5 bytes 65536
l_blob = 294885 bytes, allocated = 393216 pct used = 74.99
----------------------------------------------------------------------
SYS_LOB0000033291C00001$$ extent id 0 bytes 65536
SYS_LOB0000033291C00001$$ extent id 1 bytes 65536
SYS_LOB0000033291C00001$$ extent id 2 bytes 65536
SYS_LOB0000033291C00001$$ extent id 3 bytes 65536
SYS_LOB0000033291C00001$$ extent id 4 bytes 65536
SYS_LOB0000033291C00001$$ extent id 5 bytes 65536
SYS_LOB0000033291C00001$$ extent id 6 bytes 65536
l_blob = 327650 bytes, allocated = 458752 pct used = 71.42
----------------------------------------------------------------------
SYS_LOB0000033291C00001$$ extent id 0 bytes 65536
SYS_LOB0000033291C00001$$ extent id 1 bytes 65536
SYS_LOB0000033291C00001$$ extent id 2 bytes 65536
SYS_LOB0000033291C00001$$ extent id 3 bytes 65536
SYS_LOB0000033291C00001$$ extent id 4 bytes 65536
SYS_LOB0000033291C00001$$ extent id 5 bytes 65536
SYS_LOB0000033291C00001$$ extent id 6 bytes 65536
l_blob = 360415 bytes, allocated = 458752 pct used = 78.56
----------------------------------------------------------------------
SYS_LOB0000033291C00001$$ extent id 0 bytes 65536
SYS_LOB0000033291C00001$$ extent id 1 bytes 65536
SYS_LOB0000033291C00001$$ extent id 2 bytes 65536
SYS_LOB0000033291C00001$$ extent id 3 bytes 65536
SYS_LOB0000033291C00001$$ extent id 4 bytes 65536
SYS_LOB0000033291C00001$$ extent id 5 bytes 65536
SYS_LOB0000033291C00001$$ extent id 6 bytes 65536
SYS_LOB0000033291C00001$$ extent id 7 bytes 65536
l_blob = 393180 bytes, allocated = 524288 pct used = 74.99
----------------------------------------------------------------------
SYS_LOB0000033291C00001$$ extent id 0 bytes 65536
SYS_LOB0000033291C00001$$ extent id 1 bytes 65536
SYS_LOB0000033291C00001$$ extent id 2 bytes 65536
SYS_LOB0000033291C00001$$ extent id 3 bytes 65536
SYS_LOB0000033291C00001$$ extent id 4 bytes 65536
SYS_LOB0000033291C00001$$ extent id 5 bytes 65536
SYS_LOB0000033291C00001$$ extent id 6 bytes 65536
SYS_LOB0000033291C00001$$ extent id 7 bytes 65536
l_blob = 425945 bytes, allocated = 524288 pct used = 81.24
----------------------------------------------------------------------
SYS_LOB0000033291C00001$$ extent id 0 bytes 65536
SYS_LOB0000033291C00001$$ extent id 1 bytes 65536
SYS_LOB0000033291C00001$$ extent id 2 bytes 65536
SYS_LOB0000033291C00001$$ extent id 3 bytes 65536
SYS_LOB0000033291C00001$$ extent id 4 bytes 65536
SYS_LOB0000033291C00001$$ extent id 5 bytes 65536
SYS_LOB0000033291C00001$$ extent id 6 bytes 65536
SYS_LOB0000033291C00001$$ extent id 7 bytes 65536
SYS_LOB0000033291C00001$$ extent id 8 bytes 65536
l_blob = 458710 bytes, allocated = 589824 pct used = 77.77
----------------------------------------------------------------------
SYS_LOB0000033291C00001$$ extent id 0 bytes 65536
SYS_LOB0000033291C00001$$ extent id 1 bytes 65536
SYS_LOB0000033291C00001$$ extent id 2 bytes 65536
SYS_LOB0000033291C00001$$ extent id 3 bytes 65536
SYS_LOB0000033291C00001$$ extent id 4 bytes 65536
SYS_LOB0000033291C00001$$ extent id 5 bytes 65536
SYS_LOB0000033291C00001$$ extent id 6 bytes 65536
SYS_LOB0000033291C00001$$ extent id 7 bytes 65536
SYS_LOB0000033291C00001$$ extent id 8 bytes 65536
l_blob = 467858 bytes, allocated = 589824 pct used = 79.32
----------------------------------------------------------------------

PL/SQL procedure successfully completed.

you can see we are getting about a 50-80% utilization depending

Now, if this were me -- I'd be using a uniformly sized extent tablespace for my
blobs.  I know how big they are -- I can be a little smarter.  Here is the end
result using a 128k sized extent:

----------------------------------------------------------------------
SYS_LOB0000033342C00001$$ extent id 0 bytes 131072
SYS_LOB0000033342C00001$$ extent id 1 bytes 131072
SYS_LOB0000033342C00001$$ extent id 2 bytes 131072
SYS_LOB0000033342C00001$$ extent id 3 bytes 131072
l_blob = 467858 bytes, allocated = 524288 pct used = 89.24
----------------------------------------------------------------------

256k:

SYS_LOB0000033393C00001$$ extent id 0 bytes 262144
SYS_LOB0000033393C00001$$ extent id 1 bytes 262144
l_blob = 467858 bytes, allocated = 524288 pct used = 89.24
----------------------------------------------------------------------

2) I would expect that the tablespace "lob_test" is now nearly empty again - but

OEM (and "Tablespace map") still report that 6.188 MB are occupied. Why is this
so?

once an extent is allocated to a table -- it will belong to that table until
you drop the table, truncate the table, or explicitly deallocate the extent
(which only works if the extent NEVER contained any data whatsoever)

This is totally expected. the space is there, the space is available, the space
is free for use but ONLY by this segment.  The tablespace will not change in
regards to free/used space -- the extent belongs to that table and will stay
with that table.  It is however FREE SPACE in the table.

3) I can execute this statement about 5 times succesfully but then i get the
error  ORA-01691 (the lob segment xyz in tablespace lobtest cannot be extended).
OEM reports now that the tablespace in 81% full.

this i cannot reproduce at all. Here is my test (back to system allocated
extent sizes here):

ops$tkyte@ORA920> select dbms_lob.getlength(x) from t;

DBMS_LOB.GETLENGTH(X)
---------------------
               467858

ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2      for i in 1 .. 7
  3      loop
  4          insert into lob_tab(theBlob) select x from t;
  5      end loop;
  6      commit;
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select dbms_lob.getlength(theBlob) from lob_tab;

DBMS_LOB.GETLENGTH(THEBLOB)
---------------------------
                     467858
                     467858
                     467858
                     467858
                     467858
                     467858
                     467858

7 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select sum(dbms_lob.getlength(theBlob))/1024/1024, count(*)
  2    from lob_tab;

SUM(DBMS_LOB.GETLENGTH(THEBLOB))/1024/1024   COUNT(*)
------------------------------------------ ----------
                                3.12328911          7

ops$tkyte@ORA920>
ops$tkyte@ORA920> select a.tablespace_name,
  2        (select sum(bytes)/1024 from dba_free_space where tablespace_name =
a.tablespace_name) free,
  3        (select sum(bytes)/1024 from dba_extents where tablespace_name =
a.tablespace_name) used,
  4        (select count(*) from dba_extents where tablespace_name =
a.tablespace_name) extents
  5    from dba_tablespaces a
  6   where tablespace_name = 'LOBTEST';

TABLESPACE_NAME                      FREE       USED    EXTENTS
------------------------------ ---------- ---------- ----------
LOBTEST                              3904       6272         23

ops$tkyte@ORA920>
ops$tkyte@ORA920> select segment_name, segment_type, extent_id, bytes,
  2         sum(bytes) over ( partition by segment_name, segment_type
)/1024/1024 sum_bytes
  3    from dba_extents
  4   where tablespace_name = 'LOBTEST'
  5   order by segment_name, segment_type, extent_id
  6  /

SEGMENT_NAME                   SEGMENT_TYPE        EXTENT_ID      BYTES
SUM_BYTES
------------------------------ ------------------ ---------- ----------
----------
LOB_TAB                        TABLE                       0      65536
.0625
SYS_IL0000033501C00001$$       LOBINDEX                    0      65536
.0625
SYS_LOB0000033501C00001$$      LOBSEGMENT                  0      65536
6
SYS_LOB0000033501C00001$$      LOBSEGMENT                  1      65536
6
SYS_LOB0000033501C00001$$      LOBSEGMENT                  2      65536
6
SYS_LOB0000033501C00001$$      LOBSEGMENT                  3      65536
6
SYS_LOB0000033501C00001$$      LOBSEGMENT                  4      65536
6
SYS_LOB0000033501C00001$$      LOBSEGMENT                  5      65536
6
SYS_LOB0000033501C00001$$      LOBSEGMENT                  6      65536
6
SYS_LOB0000033501C00001$$      LOBSEGMENT                  7      65536
6
SYS_LOB0000033501C00001$$      LOBSEGMENT                  8      65536
6
SYS_LOB0000033501C00001$$      LOBSEGMENT                  9      65536
6
SYS_LOB0000033501C00001$$      LOBSEGMENT                 10      65536
6
SYS_LOB0000033501C00001$$      LOBSEGMENT                 11      65536
6
SYS_LOB0000033501C00001$$      LOBSEGMENT                 12      65536
6
SYS_LOB0000033501C00001$$      LOBSEGMENT                 13      65536
6
SYS_LOB0000033501C00001$$      LOBSEGMENT                 14      65536
6
SYS_LOB0000033501C00001$$      LOBSEGMENT                 15      65536
6
SYS_LOB0000033501C00001$$      LOBSEGMENT                 16    1048576
6
SYS_LOB0000033501C00001$$      LOBSEGMENT                 17    1048576
6
SYS_LOB0000033501C00001$$      LOBSEGMENT                 18    1048576
6
SYS_LOB0000033501C00001$$      LOBSEGMENT                 19    1048576
6
SYS_LOB0000033501C00001$$      LOBSEGMENT                 20    1048576
6

23 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> delete from lob_tab;

7 rows deleted.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2      for i in 1 .. 10000
  3      loop
  4          insert into lob_tab(theBlob) select x from t;
  5          commit;
  6      end loop;
  7  end;
  8  /
begin
*
ERROR at line 1:
ORA-01691: unable to extend lob segment OPS$TKYTE.SYS_LOB0000033501C00001$$ by
128 in tablespace LOBTEST
ORA-06512: at line 4

ops$tkyte@ORA920>
ops$tkyte@ORA920> select sum(dbms_lob.getlength(theBlob))/1024/1024, count(*)
  2    from lob_tab;

SUM(DBMS_LOB.GETLENGTH(THEBLOB))/1024/1024   COUNT(*)
------------------------------------------ ----------
                                 5.3542099         12

ops$tkyte@ORA920>
ops$tkyte@ORA920> select a.tablespace_name,
  2        (select sum(bytes)/1024 from dba_free_space where tablespace_name =
a.tablespace_name) free,
  3        (select sum(bytes)/1024 from dba_extents where tablespace_name =
a.tablespace_name) used,
  4        (select count(*) from dba_extents where tablespace_name =
a.tablespace_name) extents
  5    from dba_tablespaces a
  6   where tablespace_name = 'LOBTEST';

TABLESPACE_NAME                      FREE       USED    EXTENTS
------------------------------ ---------- ---------- ----------
LOBTEST                              1856       8320         25

ops$tkyte@ORA920>
ops$tkyte@ORA920> select segment_name, segment_type, extent_id, bytes,
  2         sum(bytes) over ( partition by segment_name, segment_type
)/1024/1024 sum_bytes
  3    from dba_extents
  4   where tablespace_name = 'LOBTEST'
  5   order by segment_name, segment_type, extent_id
  6  /

SEGMENT_NAME                   SEGMENT_TYPE        EXTENT_ID      BYTES
SUM_BYTES
------------------------------ ------------------ ---------- ----------
----------
LOB_TAB                        TABLE                       0      65536
.0625
SYS_IL0000033501C00001$$       LOBINDEX                    0      65536
.0625
SYS_LOB0000033501C00001$$      LOBSEGMENT                  0      65536
8
SYS_LOB0000033501C00001$$      LOBSEGMENT                  1      65536
8
SYS_LOB0000033501C00001$$      LOBSEGMENT                  2      65536
8
SYS_LOB0000033501C00001$$      LOBSEGMENT                  3      65536
8
SYS_LOB0000033501C00001$$      LOBSEGMENT                  4      65536
8
SYS_LOB0000033501C00001$$      LOBSEGMENT                  5      65536
8
SYS_LOB0000033501C00001$$      LOBSEGMENT                  6      65536
8
SYS_LOB0000033501C00001$$      LOBSEGMENT                  7      65536
8
SYS_LOB0000033501C00001$$      LOBSEGMENT                  8      65536
8
SYS_LOB0000033501C00001$$      LOBSEGMENT                  9      65536
8
SYS_LOB0000033501C00001$$      LOBSEGMENT                 10      65536
8
SYS_LOB0000033501C00001$$      LOBSEGMENT                 11      65536
8
SYS_LOB0000033501C00001$$      LOBSEGMENT                 12      65536
8
SYS_LOB0000033501C00001$$      LOBSEGMENT                 13      65536
8
SYS_LOB0000033501C00001$$      LOBSEGMENT                 14      65536
8
SYS_LOB0000033501C00001$$      LOBSEGMENT                 15      65536
8
SYS_LOB0000033501C00001$$      LOBSEGMENT                 16    1048576
8
SYS_LOB0000033501C00001$$      LOBSEGMENT                 17    1048576
8
SYS_LOB0000033501C00001$$      LOBSEGMENT                 18    1048576
8
SYS_LOB0000033501C00001$$      LOBSEGMENT                 19    1048576
8
SYS_LOB0000033501C00001$$      LOBSEGMENT                 20    1048576
8
SYS_LOB0000033501C00001$$      LOBSEGMENT                 21    1048576
8
SYS_LOB0000033501C00001$$      LOBSEGMENT                 22    1048576
8

25 rows selected.

See, after the delete -- I get 12 in there -- no problem.  Don't know what to
say about that one..

4) But still i get the ORA-01691 error when i try to insert and the tablespace
remains quite full (according to OEM). The space from the "deleted" blob seems
not to be reused.

that I cannot reproduce.  If you run my test -- and you get a different result
-- post it here and we'll take a look at it.

Review &
      Followup

      Rating: 4
Works ...
      sometimes  January 13, 2003
Reviewer:
      Michael  from Germany
Thanks Tom!

According to your answer i tried the following:

drop tablespace lobtest including contents;

CREATE TABLESPACE "LOBTEST"
LOGGING DATAFILE 'E:\APPS\ORACLE\ORADATA\ORA92DB\LOBTEST.ORA' SIZE 10M REUSE
EXTENT MANAGEMENT LOCAL
SEGMENT SPACE MANAGEMENT AUTO;

create table t ( x blob );

declare
    l_blob blob;
    l_size number := 31191-7;
begin
    insert into t values ( empty_blob() ) returning x into l_blo
    for i in 1 .. 15
    loop
        dbms_lob.writeappend( l_blob, l_size,
utl_raw.cast_to_raw(rpad('*',l_size,'*')) );
        l_size := 31191;
    end loop;
    commit;
end;
/

select dbms_lob.getlength(x) from t;

DBMS_LOB.GETLENGTH(X)
---------------------
               467858

Table t is ready ...

CREATE TABLE "LOB_TAB" ("THEBLOB" BLOB)
    TABLESPACE "LOBTEST"
    PCTFREE 10 PCTUSED 0 INITRANS 1 MAXTRANS 255
    STORAGE ( INITIAL 64K NEXT 0K MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0)
    LOGGING LOB("THEBLOB") STORE AS
       ( TABLESPACE "LOBTEST"  STORAGE ( INITIAL 64K NEXT 0K MINEXTENTS 1
MAXEXTENTS 2147483645 PCTINCREASE 0)
        ENABLE STORAGE IN ROW NOCACHE CHUNK 8192 PCTVERSION 10)
/

The script with the dbms_outputs then reports exactly the same results as yours.

The next tests:

truncate table lob_tab;

begin
  for i in 1 .. 7
    loop
      insert into lob_tab(theBlob) select x from t;
    end loop;
    commit;
 end;
/

select dbms_lob.getlength(theBlob) from lob_tab;

DBMS_LOB.GETLENGTH(THEBLOB)
---------------------------
                     467858
                     467858
                     467858
                     467858
                     467858
                     467858
                     467858

select sum(dbms_lob.getlength(theBlob))/1024/1024, count(*)
from lob_tab;

SUM(DBMS_LOB.GETLENGTH(THEBLOB))/1024/1024   COUNT(*)
------------------------------------------ ----------
                                3,12328911          7

select a.tablespace_name,
          (select sum(bytes)/1024 from dba_free_space where tablespace_name =
a.tablespace_name) free,
          (select sum(bytes)/1024 from dba_extents where tablespace_name =
a.tablespace_name) used,
          (select count(*) from dba_extents where tablespace_name =
a.tablespace_name) extents
      from dba_tablespaces a
     where tablespace_name = 'LOBTEST'
/

TABLESPACE_NAME                      FREE       USED    EXTENTS
------------------------------ ---------- ---------- ----------
LOBTEST                              3904       6272         23

So, it reported exactly the same. Your next query (which shows the extents from
the lob segment) reports exactly the "correct" result, too.

The (last) test:

delete from lob_tab;
commit;

select sum(dbms_lob.getlength(theBlob))/1024/1024, count(*)
from lob_tab;

SUM(DBMS_LOB.GETLENGTH(THEBLOB))/1024/1024   COUNT(*)
------------------------------------------ ----------
                                                    0

begin
   for i in 1 .. 10000
     loop
        insert into lob_tab(theBlob) select x from t;
        commit;
    end loop;
end;
/
... ERROR about lob segment ...

select sum(dbms_lob.getlength(theBlob))/1024/1024, count(*)
from lob_tab
/

SUM(DBMS_LOB.GETLENGTH(THEBLOB))/1024/1024   COUNT(*)
------------------------------------------ ----------
                                4,90802574         11

Looks good, but then:

delete lob_tab;
commmit;

begin
        for i in 1 .. 10000
        loop
            insert into lob_tab(theBlob) select x from t;
            commit;
        end loop;
    end;
/
... ERROR about lob segment ...

select sum(dbms_lob.getlength(theBlob))/1024/1024, count(*)
from lob_tab;

SUM(DBMS_LOB.GETLENGTH(THEBLOB))/1024/1024   COUNT(*)
------------------------------------------ ----------
                                2,23092079          5

Strange ... sometimes i can insert 11 or 10 and sometimes  only 4 or 5 blobs
after delete+commit.
Why?

          Followup:

do you -- in a single transaction -- frequently delete all rows and then reuse
them?  If so, go for truncate.

If not, let's simulate what you do for real....  The space will get used and
reused differently depending on what freelists things get placed on -- how the
automatic segment space management feels like doing it - pctversion will kick in
(for read consistency)  many things.

bottom line -- over time, space is reused.

      Rating: 5
Space is
      reused  January 14, 2003
Reviewer:  Michael
       from Germany
Hi Tom!

I tested this case with a test table and simulated the "real world" with the
following script:

declare
  l_del_id number(38);
  l_ins_id number(38);
begin
  for i in 1 .. 100 loop
    l_del_id := round(dbms_random.value(0.51, 6.49), 0);
    delete test_tab
    where id = l_del_id;
    dbms_output.put_line('Deleted id = ' || l_del_id);
    commit;
    l_ins_id := round(dbms_random.value(0.51, 3.49), 0);
    insert into test_tab(id, content)
    select l_del_id, content
    from source_tab
    where id = l_ins_id;
    dbms_output.put_line('Inserted id = ' || l_ins_id);
    commit;
  end loop;
end;

It worked without any problems. So the space gets reuse :-)

Of course the tablespace is quite "full".
When i delete nearly all the records (and blobs) (no truncate) is it possible
(and how) to resize (= make smaller) the tablespace/datafile?

          Followup:

once allocated -- extents stay with the table (they are complex data structures
remember) until

o drop
o truncate
o explicit deallocate (but only works for NEVER USED extents)

so no, they are part of that table.

      Rating: 4
Good
      idea???  January 14, 2003
Reviewer:  Michael
       from Germany
Hi Tom!

What's your opinion of using the following statements to reclaim space:

For "normal" (= without lobs):
alter table t move;

Additional for tables with one or more lobs:
alter table lob_tab lob(theblob) store as (tablespace lobtest);

Is it a good/bad idea to use this statements after deletes that affected (=
deleted) many rows to get free space for other tables/objects?

          Followup:

If I was deleting a huge percentage of the table to begin with -- i would not
have used delete!

create table as select <rows to keep>

or use partitioning to just drop old data.

but yes, if you delete a ton and want to reorg alter table move or
dbms_redefinition (in 9i) would be the way to do it.

      Rating: 4
Query for
      used/total ratio  January 15, 2003
Reviewer:
      Michael  from Germany
One (last) thing:

Could you provide a query/procedure which shows the ratio/percentage (and
absolute values) of really used space (=filled with data at the moment) and the
total allocated space of a table, index etc.
e.g.:

TABLE    TOTAL_BLOCKS   USED_BLOCKS
-----------------------------------
EMP               200           100
DEPT               50            10

(I tried DBMS_SPACE package, but i didn't succeeded.)

          Followup:

well, user_tables will give you that information after an analyze. but you can
do this with a function if you like:

ops$tkyte@ORA920> create or replace function get_block_info
  2  ( p_segname in varchar2,
  3    p_owner   in varchar2 default user,
  4    p_type    in varchar2 default 'TABLE',
  5    p_partition in varchar2 default NULL )
  6  return number
  7  authid current_user
  8  as
  9      l_total_blocks              number;
 10      l_total_bytes               number;
 11      l_unused_blocks             number;
 12      l_unused_bytes              number;
 13      l_LastUsedExtFileId         number;
 14      l_LastUsedExtBlockId        number;
 15      l_LAST_USED_BLOCK           number;
 16  begin
 17      dbms_space.unused_space
 18      ( segment_owner     => p_owner,
 19        segment_name      => p_segname,
 20        segment_type      => p_type,
 21        partition_name    => p_partition,
 22        total_blocks      => l_total_blocks,
 23        total_bytes       => l_total_bytes,
 24        unused_blocks     => l_unused_blocks,
 25        unused_bytes      => l_unused_bytes,
 26        LAST_USED_EXTENT_FILE_ID => l_LastUsedExtFileId,
 27        LAST_USED_EXTENT_BLOCK_ID => l_LastUsedExtBlockId,
 28        LAST_USED_BLOCK => l_LAST_USED_BLOCK );
 29
 30      return l_total_blocks || '.' || (l_total_blocks-l_unused_blocks);
 31  exception
 32      when others then return NULL;
 33  end;
 34  /

Function created.

ops$tkyte@ORA920> set define on
ops$tkyte@ORA920>
ops$tkyte@ORA920> select table_name,
  2         trunc(gbi) total_blocks,
  3         to_number(substr(gbi,instr(gbi,'.')+1)) used_blocks
  4    from ( select table_name, get_block_info( table_name ) gbi, rownum
  5             from user_tables
  6         )
  7  /

TABLE_NAME                     TOTAL_BLOCKS USED_BLOCKS
------------------------------ ------------ -----------
A                                         8           3
AO                                      512         417
ATTRIBUTES                                8           8
B                                         8           3
CLUSTERED                              1152        1132
D                                         8           8
DEPT                                      8           4
DR$MY_CONTENT_IX$I                        8           8

      Rating:
      4
USER_TABLES  January 16, 2003
Reviewer:
      Michael  from Germany
Hi Tom!

Which columns of user_tables contain the information about used or empty blocks
of a table?

I tried EMPTY_BLOCKS and NUM_FREELIST_BLOCKS, but it didn't delivered the data i
would expect.
(Perhaps my expectations are wrong ;-)

          Followup:

did you analyze the table -- they are the ones.  only current right after an
analyze.

      Rating: 4
USER_TABLES: It
      IS analyzed  January 20, 2003
Reviewer:
      Michael  from Germany
Yes, the table/fields are analyzed:

desc t

Name                         Null?    Typ
---------------------------- -------- ----------------
X                                     VARCHAR2(2000)

truncate table t;

analyze table t compute statistics
for table for all columns;

select num_rows, blocks, empty_blocks, num_freelist_blocks
from user_tables
where table_name = 'T';

  NUM_ROWS     BLOCKS EMPTY_BLOCKS NUM_FREELIST_BLOCKS
---------- ---------- ------------ -------------------
         0          0            8                   0

insert into t
select dbms_random.string('X', 2000)
from all_objects where rownum <= 1000;
commit;

analyze table t compute statistics
for table for all columns;

select num_rows, blocks, empty_blocks, num_freelist_blocks
from user_tables
where table_name = 'T';

  NUM_ROWS     BLOCKS EMPTY_BLOCKS NUM_FREELIST_BLOCKS
---------- ---------- ------------ -------------------
      1000        370           14                   0

delete t;
commit;

analyze table t compute statistics
for table for all columns;

select num_rows, blocks, empty_blocks, num_freelist_blocks
from user_tables
where table_name = 'T';

  NUM_ROWS     BLOCKS EMPTY_BLOCKS NUM_FREELIST_BLOCKS
---------- ---------- ------------ -------------------
         0        370           14                   0

It still shows the same information about the blocks. (NUM_ROWS is OK.)
I expected:
BLOCKS = 370, EMPTY_BLOCKS = 14,
and NUM_FREELIST_BLOCKS (= blocks which are "owned" by the table, but not used
at the moment?) about 350-360.

Where is my error?

          Followup:

maybe you are not using freelists.  hard to say -- no version info, no
environment issue.  It does make it more of a brain teaser for me this way --
but does make it take longer to answer questions.  Oh well...

I set up two tablespaces -- auto_tbs -- using automatic segment space management
(NO freelists) and manual_tbs -- using manual segment space managment
(freelists)

ops$tkyte@ORA920> create table t ( x varchar2(2000) ) tablespace auto_tbs;
Table created.

ops$tkyte@ORA920> insert into t
  2  select dbms_random.string('X', 2000)
  3  from all_objects where rownum <= 1000;
1000 rows created.

ops$tkyte@ORA920> commit;
Commit complete.

ops$tkyte@ORA920> exec show_space( 'T' );
Auto segment space managed...
Unformatted Blocks .....................0
FS1 Blocks (0-25)  .....................0
FS2 Blocks (25-50) .....................0
FS3 Blocks (50-75) .....................1
FS4 Blocks (75-100).....................36
Full Blocks        .....................333
Free Blocks.............................
Total Blocks............................384
Total Bytes.............................3145728
Total MBytes............................3
Unused Blocks...........................0
Unused Bytes............................0
Last Used Ext FileId....................11
Last Used Ext BlockId...................60680
Last Used Block.........................128

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> delete t;
1000 rows deleted.

ops$tkyte@ORA920> commit;
Commit complete.

ops$tkyte@ORA920> analyze table t compute statistics
  2  for table for all columns;
Table analyzed.

ops$tkyte@ORA920> select num_rows, blocks, empty_blocks, num_freelist_blocks
  2  from user_tables
  3  where table_name = 'T';

  NUM_ROWS     BLOCKS EMPTY_BLOCKS NUM_FREELIST_BLOCKS
---------- ---------- ------------ -------------------
         0        370           14                   0

0 on the freelist -- but since I have no freelists -- this is expected and
OK

ops$tkyte@ORA920> exec show_space( 'T' );
Auto segment space managed...
Unformatted Blocks .....................0
FS1 Blocks (0-25)  .....................0
FS2 Blocks (25-50) .....................0
FS3 Blocks (50-75) .....................0
FS4 Blocks (75-100).....................370
Full Blocks        .....................0
Free Blocks.............................
Total Blocks............................384
Total Bytes.............................3145728
Total MBytes............................3
Unused Blocks...........................0
Unused Bytes............................0
Last Used Ext FileId....................11
Last Used Ext BlockId...................60680
Last Used Block.........................128

PL/SQL procedure successfully completed.

they are all marked as 75-100% EMPTY (free)
ops$tkyte@ORA920>

now, doing the same in a manual tablespace:

ops$tkyte@ORA920> drop table t;

Table dropped.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t ( x varchar2(2000) ) tablespace manual_tbs;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t
  2  select dbms_random.string('X', 2000)
  3  from all_objects where rownum <= 1000;

1000 rows created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec show_space( 'T' );
Dictionary space managed...
Free Blocks.............................2
Free Blocks.............................2
Total Blocks............................384
Total Bytes.............................3145728
Total MBytes............................3
Unused Blocks...........................48
Unused Bytes............................393216
Last Used Ext FileId....................12
Last Used Ext BlockId...................68873
Last Used Block.........................80

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> delete t;

1000 rows deleted.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table t compute statistics
  2  for table for all columns;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select num_rows, blocks, empty_blocks, num_freelist_blocks
  2  from user_tables
  3  where table_name = 'T';

  NUM_ROWS     BLOCKS EMPTY_BLOCKS NUM_FREELIST_BLOCKS
---------- ---------- ------------ -------------------
         0        335           48                 335

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec show_space( 'T' );
Dictionary space managed...
Free Blocks.............................335
Free Blocks.............................335
Total Blocks............................384
Total Bytes.............................3145728
Total MBytes............................3
Unused Blocks...........................48
Unused Bytes............................393216
Last Used Ext FileId....................12
Last Used Ext BlockId...................68873
Last Used Block.........................80

PL/SQL procedure successfully completed.

different strokes for different things...

January 20, 2003 rashmi -- Thanks for the question regarding
      "Extent Allocation not happening as stated in the manuals", version 7.1.3

      You Asked
Although the problem I am about to state is related to a 7.1.3 database. I would
like to know if the same would happen on 8.1.6.3 databases as most of ours are
8.1.6.3 databases.

While monitoring space allocation we have noticed that one of the tables needing
40m space have not used up space from one of the contguous free spaces in the
tablespace of the size of 100m,120m,60m. Instead it has used space from the
largest contiguous free space i.e 703m.As per our understanding Oracle should
have allocated from one of the smaller bits of space mentioned earlier. I have
just read a document on Oracle metalink(DOC ID :69343.1)allocation but none of
the scenarios there matches our case.

Could you please tell me under what circumstances would Oracle ignore
these other smaller chunks of free space that are closer to the required next
extent size for an object?If this happens how can we predict space requirements
for a tablespace? Is it possible to order the extents listed as free in the SGA
by their size ? How can we control the allocation such that an object uses up
smaller chunks closer to its requirement instead of picking up from anywhere.

      and we said...
The algorithm is well documented in the Server Concepts Guide.

This is a cut and paste from the Oracle7 Server concepts guide:

<quote>

How Extents Are Allocated for Segments

Oracle controls the allocation of extents for a given segment. The procedure to
allocate a new extent for a segment is as follows:

1. Oracle searches through the free space (in the tablespace that contains the
segment) for the first free, contiguous set of data blocks of an incremental
extent's size or larger. Oracle finds the free space for the new extent by using
the following algorithm:

a. Oracle searches for a contiguous set of data blocks that matches the size of
new extent, rounded up to reduce internal fragmentation. For example, if a new
extent requires 19 data blocks, Oracle searches for exactly 20 contiguous data
blocks. However, if the new extent is 5 or fewer blocks, Oracle does not round
up the request.

b. If an exact match is not found, Oracle then searches for a set of contiguous
data blocks equal to or greater than the amount needed. If Oracle finds a group
of contiguous blocks that is at least five blocks greater than the size of the
extent that is needed, it splits the group of blocks into separate extents, one
of which is the size it needs; if Oracle finds a group of blocks that is larger
than the size it needs, but less than five blocks larger, it allocates all the
contiguous blocks.

Continuing with the example, if Oracle does not find a set of exactly 20
contiguous data blocks, Oracle then searches for a set of contiguous data blocks
greater than 20. If the first set that Oracle finds contains 25 or more blocks,
it breaks the blocks up and allocates twenty of them to the new extent.
Otherwise, it allocates all of the blocks (between 21 and 24) to the new extent.

c. If Oracle does not find a larger set of contiguous data blocks, Oracle then
coalesces any free, adjacent data blocks in the corresponding tablespace so that
larger sets of contiguous data blocks are formed. (The SMON background process
also periodically coalesces adjacent free space.) After coalescing a
tablespace's data blocks, Oracle performs the searches described in a. and b.
again. If an extent cannot be allocated after the second search, Oracle returns
an error.

2. Once Oracle finds the necessary free space in the tablespace, Oracle
allocates a portion of the free space that corresponds to the size of the
incremental extent. If Oracle had found a larger amount of free space than was
required for the extent, Oracle leaves the remainder as free space (no smaller
than five contiguous blocks).

3. Oracle updates the segment header and data dictionary to show that a new
extent has been allocated and that the allocated space is no longer free.

Usually, Oracle zeros out the blocks of a newly allocated extent when the extent
is first used; in a few cases (such as when a database administrator issues an
ALTER TABLE or ALTER CLUSTER statement with the ALLOCATE EXTENT option while
using free list groups), Oracle clears the extent's blocks when it allocates the
extent.

</quote>

There is nothing in there to suggest it would search for the smallest free
extent, it just searches FET$ to find one that fits -- first row it finds, it
uses.

The extents are not listed in the SGA, they are in a table.

What you should do is place objects into tablespaces based on their EXTENT SIZES
(eg: all objects in a tablespace use the same exact INITIAL=NEXT and PCTINCREASE
always is ZERO).

In this case, fragmentation is IMPOSSIBLE (any object can use any extent since
all extents are exactly the same size).

It is OK to have hundreds, even 1,000's of extents.  It really is OK.

You need probably 3 tablespaces -- one for small, medium, and large objects and
you can hold EVERY object.  See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:730289259844
and look for "living" and read that paper for a good strategy.

In 8163, you would be using Locally managed tablespacs with uniform extent sizes
(search my site for locally managed to see discussions).  This just enforces the
initial=next and pctincrease=0 at the tablespace level, making it IMPOSSIBLE for
an object to have just any extent size it wanted to.
Review &
      Followup

      Rating:
      5
  November 30, 2001
Reviewer:
      A reader  from GA, USA

      Rating: 5
That was a Good
      One...  November 30, 2001
Reviewer:  Ganesh
      Raja  from Dubai, UAE
Hi Tom,

I am being stunned by the way Oracle Allocates Extents.

The Oracle Docs say that Initial Should have a Minimum of two Extents and The
Next Should Have 1 Extent.

I am using Dictionary Managed tablespace.

Now...

Create Table A ( A Number)
Storage (Initial 64K Next 64K pctincrease 0 Minextents 1 Maxextents 255);

The Tablespace is Dictionary Managed and the Block Size is 8K. Minmum Extent is
1

Now when i see this i say ... Boss Oracle Creates me these...

1 Extent with 8 Blocks.

And when i ask him for the next extent he is going to give me another 8 Blocks.

But...

it gives me 16 Blocks a Whooping 128K which is not need by me and the Next is
also 128K.

Now when i go and see the DBA_Segments view the inital_extent is 8 Blocks or 64
K and the next is 64k.

What is wrong here and what is happening.  or am i wrong in the calculation.

Your comments are really appreciated.

Regards,
Ganesh R.

PS Already Posted in the Google groups No replies.

          Followup:

It'll all depend on the free space and how its organized in the tablespace.  We
will not fragment the free extents to a point.  For example, on my system it
JUST SO HAPPENS that:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( A Number)
  2  tablespace system Storage (Initial 64K Next 64K pctincrease 0 Minextents 1
Maxextents 255);

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t allocate extent;

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t allocate extent;

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t allocate extent;

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t allocate extent;

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select bytes, blocks from user_extents where
segment_name= 'T';

     BYTES     BLOCKS
---------- ----------
     81920         10
     81920         10
     81920         10
     81920         10
     81920         10

each extent was 80k -- not 64k.  Its just the way the data was laid out.

That was my 817 database, my 815 database got this:

     BYTES     BLOCKS
---------- ----------
     81920         10
     81920         10
     81920         10
    114688         14
     81920         10

In 8i and up -- I would strongly discourage the use of dictionary managed
tablespaces -- especially if you are using different sized extents for
everything.  LMTS take all of the guess work out of this and are very
predicable.

      Rating: 4
But The
      Difference  December 03, 2001
Reviewer:
      A reader
Tom, But the difference in the Dba_Segments and DBA_Extents how do u attribute
that.

Regards,
Ganesh R

          Followup:

dba_extents tells you what was allocated.  Its what is really there.

dba_segments shows you the definition, what you might use to generate a CREATE
TABLE script with.

      Rating: 5
The Was One
      Information.  December 03, 2001
Reviewer:
      A reader
That was a insight.

Thanks Tom.

Regards,
Ganesh R

      Rating: 5
Block Size Vs
      Tablespace Storage  July 26, 2002
Reviewer:
      Mike  from Dallas,TX
Tom,
My Block Size is 16k, what happens If I give the Initial as 16K in my tablespace
Storage Parameter? This is not a realistic scenario but I was asked this
question in an interview.
Thanks a lot

Mike

          Followup:

My answer might be:

a) well, don't know if I want to work with you guys actually as you appear to be
totally hung up on meaningless trivia.  Why not ask me something relevant like
"how does a hot backup work, draw me a 'picture' of Oracle on that whiteboard
and explain the relevant processes, how they work together, what the files and
memory structures are'....

A wise-acre answer could be:

b) A tablespace gets created and objects that are created in it by default would
have 16k as their initial extent.  or do you mean a STORAGE parameter on a
create table/index statement cause there isn't anything called a "tablespace
storage parameter". There are storage parameters (initial, next, pctincrease for
example) and there is a tablespace parameter (what tablespace do I go into).  A
tablespace does have default storage parameters but then I wouldn't know what
the question meant.

Or you could say,

c) well, geez, it depends.  Am I using an LMT or DMT here?  Cause with an LMT I
would need to know if it was UNIFORM -- if so, then I need the uniform extent
size to comment further.  If it is system allocated then it'll be one of
probably 64k, 128k and so on but we cannot really say as it depends on the space
available and a totally undocumented algorithm used by Oracle (i've seen
different sized initial/next extents using system allocated extents)

If it is a DMT, then I would need to know -- why, why are you using them?  They
are evil.  If you are, I would further need to know if there were a minimum
extent size on the tablespace -- to which Oracle would round up.  Even after
that, if there were no minimum extent size, I would either get a 5 block initial
extent as Oracle likes/tends to reuse free extents like that ( in multiples of 5
blocks ) but I would get at least 2 blocks -- one for the segment header and one
for the table data itself.  It depends -- somewhere between 2 and say about 6
blocks depending.

I might actually answer with B, C and then A before I left ;)  I despise
interview questions that have nothing to do with the real world.

      Rating:
      4
  October 16, 2002
Reviewer:  Sagi
       from India
Hi Tom!

My doubt might seem a bit silly. But I am confussed.

My DB Block Size is 8K. So if i try to create a table with

initial=next=8K  or
initial=next=40K or
initial=next=64K

we can'nt say definetly that Oracle would give such and such blocks. That is if
it was 8K it should have given me 1 Block (it would be 5 Blocks if oracle rounds
up) in CASE-1, 5 Blocks in CASE-2 and 8 Blocks in CASE-3 (It would say 10 blocks
if oracle rounds)

My Test Case and Output:
========================
SQL> CREATE TABLE T (X NUMBER)
  2  STORAGE (INITIAL 8K NEXT 8K MINEXTENTS 3) ;

Table created.

SQL> CREATE TABLE T1 (X NUMBER)
  2  STORAGE (INITIAL 40K NEXT 40K MINEXTENTS 3) ;

Table created.

SQL> CREATE TABLE T2 (X NUMBER)
  2  STORAGE (INITIAL 64K NEXT 64K MINEXTENTS 3) ;

Table created.

SQL> COLUMN SEGMENT_NAME FORMAT A30
SQL> SELECT SEGMENT_NAME, BYTES, BLOCKS FROM USER_EXTENTS
  2  WHERE SEGMENT_NAME LIKE 'T%'
  3  ORDER BY 1;

SEGMENT_NAME                        BYTES     BLOCKS
------------------------------ ---------- ----------
T                                  131072         16
T                                  131072         16
T                                  131072         16
T1                                 131072         16
T1                                 131072         16
T1                                 131072         16
T2                                 131072         16
T2                                 131072         16
T2                                 131072         16

9 rows selected.

Therefore the conclusion is that we dont know how many blocks are consumed when
a Table is created by specifying INITIAL and NEXT Storage Parameters in the
TABLE Creations....Right?

Thanx in advance.

Regards,
R. K.

          Followup:

USE LOCALLY MANAGED TABLESPACES SAGI

STOP USING DICTIONARY MANAGED

and all things will be nice.....

initial, next, pctincrease -- i hate them.  glad to see them go.

      Rating:
      3
  October 17, 2002
Reviewer:  Sagi
       from India
Hi Tom !

I am using LMT only. That question was just for understand and knowing the
facts.

Therefore I hope you would explain. Please

Regards,
Sagi.

          Followup:

why you want to understand that which is not relevant when there is so much to
understand.... OK, here is the algorithm:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/c02block.htm#2846
(that pesky concepts manual strikes again)

      Rating: 5
Space
      Allocation  October 17, 2002
Reviewer:  George
      Spears  from Dayton, OH
Tom,

If I recall right, under dictionary managed space allocation, Oracle will try to
find an appropriate sized extent anywhere (aka any datafile), and if unable, at
some point, will use some algorythm (round-robin maybe??) to lay out the extents
across ALL datafiles.  Although SIMPLISTIC, think of it as extent 1 in datafile
1, extent 2 in datafile 2, etc.  This helps to ensure a better disk I/O
pattern....  This is why Oracle is NOT trying to allocate a 40MB extent in the
smallest datafile.  If Oracle did NOT do this, then it would try to fill up the
entire datafile before using a different datafile, which would negatively impact
I/O.  The original question states that this behaviour is seen in 7.1.3.  I do
NOT recall at what point Oracle introduced this behaviour...

      Rating:
      4
Clarification  January 20, 2003
Reviewer:
      r  from london
Hi Tom,

Please help me understand this. I read your book and was trying to understand
something about oracle blocks. In the book you say that a single data block may
contain rows from many tables. My question is that when we create a database
object say a table ,oracle allocates an extent( which is 'n' number of blocks)
if there is none available. If this is true then a data block should always
belong to a single table and store data for only 1 table.

Am I getting some concepts wrong here ?

Please clarify

Thanks

          Followup:

when Oracle creates a SEGMENT -- Oracle allocates an extent.  That extent can
only be used by that SEGMENT.

On of the segment types we have is called a CLUSTER.

Now, you are assuming that all tables are SEGMENTS and they are not.

create table t ( x int ); <<<===== creates a segment for T

create table t ( x int ) partitioning_clause <<<=== does NOT create a segment
for T, but rather each of T's partitions

create table t ( x int ) cluster foo <<<=== does not create a segment for T,
puts T into the already created segment which is a cluster.

So, re-read chapter 5 on clusters ;)

      Rating:
      5
Thanks  January 20, 2003
Reviewer:
      raju  from london
Tom,

Thanks very much for the answer. Actually, I got this doubt while I was reading
the chapter on "Architecture" where you have briefly mentioned about the blocks.
Haven't got far to Chapter 5 yet but will be there soon ;)

Thanks again.

January 20, 2003 -- Thanks for the question regarding "Why my free
      memory always leavings a little", version 8.1.7

      You Asked
version:
Red Hat Linux release 7.2 (Enigma)
Kernel 2.4.7-10 on an i686

database:
SQL> select * from v$version;
BANNER
----------------------------------------------------------------
Oracle8i Enterprise Edition Release 8.1.7.0.1 - Production

[root@XXXX sbin]# free
             total       used       free     shared    buffers     cached
Mem:        513304     504892       8412      85696      93316     184316
-/+ buffers/cache:     227260     286044
Swap:       522104          0     522104

So,i want know why free memory is always 3-10M,even nobody is use this
machine,and machine is only installed ORACLE,no others appliaction.Only when i
reset DATABASE, it is 200-300M free mem,but AFTER a heavy load,it became a
little men again.

Does ORACLE do not free mem after it used?

thanks

      and we said...
No, it means the OS allocates all free memory for file system buffer caches and
gives it back when and if it is needed.

Normal, expected, desired behaviour.

Review &
      Followup

      Rating:
      4
Link  January 20, 2003
Reviewer:  Jeremy
      Smith  from Salt Lake City, UT
Here's a link to an explanation of Linux swapping algorithm.

http://www.surriel.com/lectures/linux24-vm.html
or look at the 'Understanding the Linux Kernel' O'Reilly book.

January 20, 2003 Yogeeraj -- Thanks for the question regarding
      "Tuning update statement for better performance", version 8.1.7.4

      You Asked
Hello,

Please allow me to ask another question today.

We often UPDATE the same table as follows:
update mkt510 set pricedz = ( select price2
                        from mkt500
                      where mkt500.ordnum = mkt510.ordnum
                        and mkt500.gmtseq = mkt510.gmtseq
                        and mkt500.opcode = mkt510.opcode
                        and mkt500.maklcn = mkt510.maklcn)
where maklcn = :p_unit
  and exists ( select 'x'
                 from v_pet100_2
                where empcode = :p_empcode
                  and overcat = 'I')

then,

update mkt510
       set totprt = round((counter * hanger) * (nvl(pricedz,0)/12),2) where
maklcn = :p_unit
  and exists ( select 'x'
                 from v_pet100_2
                where empcode = :p_empcode
                  and overcat = 'I')

We observe that the first and second update statements have the same "where
clause". Is it possible to merge these two update statements into a single one?
Can we expect any performance improvement?

thank you for a reply.

best regards
Yogeeraj

      and we said...
yes and yes.

update mkt510 set (totprt,pricedz) =
                    ( select
                     round((mkt510.counter*mkt512.hanger)*(nvl(price2,0)/12),2),
                             price2
                        from mkt500
                      where mkt500.ordnum = mkt510.ordnum
                        and mkt500.gmtseq = mkt510.gmtseq
                        and mkt500.opcode = mkt510.opcode
                        and mkt500.maklcn = mkt510.maklcn)
where maklcn = :p_unit
  and exists ( select 'x'
                 from v_pet100_2
                where empcode = :p_empcode
                  and overcat = 'I')

should run in 1/2 the time.
Review &
      Followup

      Rating: 5
thanks you for
      the reply. this is great!  January 21, 2003

Reviewer:  Yogeeraj  from Mauritius
hello,

thank you a lot for this guidance, will help us a lot in all our future
implementations.

Best Regards
Yogeeraj

January 20, 2003 Kevin -- Thanks for the question regarding
      "Personal Version", version 8.1.5i

      You Asked
HI Tom. I always thanks for your reply.
Now I am using Personal Oracle on my desktop PC on Win98.(8.1.5i)
When I connect SQL*PLUS on PC, it runs well without specifying the 3rd column of
Database Identification.
But when I connect Personal Oracle from TOAD, a ORACLE Operation SW from QUEST,
it requested Database name and user-id and password. I put in User name as
'System' and password as 'Manager' but did not keyin any word on Database area
of TOAD login screen.
After I hit the enter key-board, it did not work. I mean the AOL, my internet
connection S/W, was activated and I received TNS-12360 ( ? ) TNS name error
message.
As I contacted TOAD support pesonel, she told me "Install database using Easy
Net Config S/W."  Because TOAD approaches ORACLE through SQL*Net, I have to set
up Database name to communicate through SQL*Net.
Currently I do not know how to define Database on Personal Oracle and How to
define Tnsname.ora entry in Personal Oracle ENvironment.
Basically I wonder that Could I connect Personal Oracle on one PC Desk top
environment.
Thanks.

      and we said...
You need to configure the Net8 listener on your Personal Oracle database and
then setup the tnsnames.ora file.

A sample might be, in \oracle_home\network\admin

- listener.ora -
# LISTENER.ORA Network Configuration File: C:\oracle\NETWORK\ADMIN\listener.ora
# Generated by Oracle configuration tools.

LISTENER =
  (DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC))
    )
    (DESCRIPTION =
     (ADDRESS = (PROTOCOL = TCP)(HOST = tkyte-dell)(PORT = 1521))
    )
  )

SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (SID_NAME = ExtProc)
      (ORACLE_HOME = c:\oracle)
      (PROGRAM = extproc)
    )
    (SID_DESC =
      (GLOBAL_DBNAME = tkyte816)
      (ORACLE_HOME = c:\oracle)
      (SID_NAME = tkyte816)
    )
  )

--------------------------
(replace my specific stuff like tkyte816 with your specifics).

You then need a tnsnames.ora file like:

tkyte816.us.oracle.com =
  (DESCRIPTION =
    (ADDRESS_LIST =
    (ADDRESS = (PROTOCOL = TCP)(HOST = tkyte-dell)(PORT = 1521))
    )
    (CONNECT_DATA =
      (ORACLE_SID = tkyte816)
    )
  )

again, replacing my specifics with yours.  Then, you can start the listener via:

c:> lsnrctl start

If you use the network configuration assistant -- available on your start menu
-- these configuration files will be generated for you and a service to start
your listener setup (no need for the command line).  this would be the preferred
method.
Review
      & Followup

      Rating:
      3
And...  November 07, 2001
Reviewer:
      Salas  from Mexico
Is possible connect to [Personal Oracle Database] from another host (like a
[Server])?

          Followup:

yes, if you have a listener running.

      Rating: 3
starting
      listener and database at the same time  December 20, 2001

Reviewer:  Godwin  from Ghana
I am very sorry for changing my e-mail address. Sorry for the inconvenience
caused you. i won't do that again.But Tom i tried to get you on your stipulated
times of askering questions but always can't get u. Since i heard about you it
was only once i sent you a question and you answered through my e-mail.
Help me please for am a beginner.
My problem is can i start the listener and the personal oracle database at the
same time?
This is because i have to first start the listener followed by the personal
oracle database before i can have my form running.
Can't there be a short-cut so that immediately you start either the form builder
or report builder the listener and the database will also start?
This is a hectic problem for me after i have finished developing my application
package,i need to go through such a tedious process b.4 the whole thing works.

          Followup:

On windows, we run as a service.  In know on NT/Xp/2000 you can have it
autostart via the services.  If you are on 95/98 -- I don't know if they have
services or not (never used them).  Maybe someone out there can help you...

      Rating: 5
RE: starting
      listener and database at the same time  December 20, 2001

Reviewer:  Mark A. Williams  from Indianapolis, IN
      USA
The easiest way I have found to start the listener/database on Windows 9x is to
create a shortcut in the 'Startup' menu that points to a script.  You can create
a single script that starts the listener and then the database.  There are also
some registry keys you can use, but I don't happen to have a Windows 9x version
available to look at right now, so I can't tell you exactly what they are.  They
are under HKLM\Software\Oracle\xxx...

HTH,

- Mark

      Rating: 5
personal
      oracle  January 19, 2003
Reviewer:  mo

Tom:

If I want to make a local copy of a web application using htp and enterprise
oracle 8iR2(running on ibm aix server) to a laptop or personal PC.  Can I
replicate the same development environment with personal oracle for windows 98.
Does it support the htp and utl_smtp etc?

DO I need to install an application server or OWA)UTIL gets installed
automatically and I just have to configure it?

or you recommend installing standard or enterprise   release?

          Followup:

sorry -- never used windows98 or personal oracle myself.  as far as I know -- it
is all there.  htp for sure. utl_smtp -- pretty sure.

get a real OS ;)

      Rating:
      5
personal  January 20, 2003
Reviewer:
      mo
well 90% of home PCs are shipped with windows 98 or XP.

Windows 2000 is more for servers and I do not think anyone buys a linux for home
use yet.

Thank you,

          Followup:

Hey hey -- I'm someone aren't I?

My home machines -- as well as work, well, they are virus free -- lets put it
that way ;)

January 20, 2003 wallacel -- Thanks for the question regarding
      "cursor_sharing", version 8.1.6

      You Asked
Tom:

 my developer didn't use bind variable in they development,
so they are lot of similar statement when i check v$sqlarea,
such as
update tableA set column1=1;
update tableA set column1=2;
update tableA set column1=3;

in version 8i, instead of ask them to rewrite they statement,
i can archive the same performance by set cursor_sharing=force.
am i right?

2.can you give me a link to the usage of CURSOR_SHARING?
i got one from your archive, but it doesn't work.

      and we said...
Yes, cursor_sharing set to force will help this situation.  It
won't be as fast as if they actually used bind variables, as
they should but it is better then nothing.

See chapter 19 of the "Designing and Tuning for Performance"
guide for more info.

The link works -- technet just appears unavailable right this
instant.

Review &
      Followup

      Rating: 5
Cursor
      Sharing  May 10, 2002
Reviewer:  Rukshan
      Soza  from England
Tom,

Is there something similar in v805 ?.

Rgds

Rukshan

          Followup:

No, it is a new 8i feature.

      Rating:
      3
Cursor_sharing=force & Query Output Releated
      Issues  January 20, 2003
Reviewer:  Logan
      Palanisamy  from Santa Clara, CA, USA
Tom,

Thanks for all your invaluable and dedicated service.

This is with reference to page 448 of Expert One on One book. I was expecting
the column length of the second query to 30 after cursor_sharing=force. But it
is still coming out as 2. What could be the problem.

Connected to:
Oracle8i Enterprise Edition Release 8.1.6.3.0 - Production
With the Partitioning option
JServer Release 8.1.6.3.0 - Production

SQL> set echo on
SQL> @p448.sql
SQL> select substr(object_name, 1, 2) from dba_objects where rownum = 1;

SU
--
/1

SQL> alter session set cursor_sharing = force;

Session altered.

SQL> select substr(object_name, 1, 2) from dba_objects where rownum = 1;

SU
--
/1

SQL> exit
Disconnected from Oracle8i Enterprise Edition Release 8.1.6.3.0 - Production
With the Partitioning option
JServer Release 8.1.6.3.0 - Production

          Followup:

just an artifact -- it was already parsed.  try this:

select substr(object_name, 1, 2) from dba_objects A where rownum = 1;
alter session set cursor_sharing=force;
select substr(object_name, 1, 2) from dba_objects B where rownum = 1;

      Rating: 5
Works
      indeed  January 20, 2003
Reviewer:  Logan
      Palanisamy  from Santa Clara, CA, USA
SQL> select substr(object_name, 1, 2) from dba_objects A where rownum = 1;

SU
--
/1

SQL> alter session set cursor_sharing=force;

Session altered.

SQL> select substr(object_name, 1, 2) from dba_objects B where rownum = 1;

/

SUBSTR(OBJECT_NAME,1,2)
--------------------------------------------------------------------------------
/1

SQL> SQL>
SUBSTR(OBJECT_NAME,1,2)
--------------------------------------------------------------------------------
/1

SQL>

Thanks

January 20, 2003 Sudha -- Thanks for the question regarding "Can
      Ref Cursor be used here, if not then what and how ?", version 8.1.6

      You Asked
I'm trying to compile this procedure and I error out because I'm unable to
define a variable for fetching cursor into.

My question is I'm thinking that this is the wrong place for REF CURSORS so if
DBMS_SQL can be used then how ?

This procedure is quite self-explanatory I think.

CREATE OR REPLACE PROCEDURE PRC_UHC_REFC_EXT_TBL(v_src_tbl_nm     VARCHAR2,
                                                 v_trg_tbl_nm     VARCHAR2,
                                                 v_col1_nm        VARCHAR2,
                                                 v_col2_nm        VARCHAR2
DEFAULT NULL,
                                                 v_col3_nm        VARCHAR2
DEFAULT NULL,
                                                 v_col4_nm        VARCHAR2
DEFAULT NULL)
IS

  TYPE  rc    IS REF CURSOR;
  v_cursor    rc;
  v_query1    VARCHAR2(2000);
  v_query2    VARCHAR2(2000);
  v_where_cl1 VARCHAR2(100);
  v_where_cl2 VARCHAR2(100);
  v_where_cl3 VARCHAR2(100);
  v_where_cl4 VARCHAR2(100);

BEGIN

  IF v_col1_nm IS NOT NULL THEN

     v_where_cl1 := ' WHERE '||v_col1_nm||' = '||'v_cursor.'||v_col1_nm;

  ELSE

     v_where_cl1 := ' ';

  END IF;

  IF v_col2_nm IS NOT NULL THEN

     v_where_cl2 := ' AND '||v_col2_nm||' = '||'v_cursor.'||v_col2_nm;

  ELSE

     v_where_cl2 := ' ';

  END IF;

  IF v_col3_nm IS NOT NULL THEN

     v_where_cl3 := ' AND '||v_col3_nm||' = '||'v_cursor.'||v_col3_nm;

  ELSE

     v_where_cl3 := ' ';

  END IF;

  IF v_col4_nm IS NOT NULL THEN

     v_where_cl4 := ' AND '||v_col4_nm||' = '||'v_cursor.'||v_col4_nm;

  ELSE

     v_where_cl4 := ' ';

  END IF;

  v_query1 := 'SELECT * FROM '||v_src_tbl_nm||' ';
  v_query1 := v_query1||'WHERE INSERT_DATE >= SYSDATE - 1 ';
  v_query1 := v_query1||'OR UPDATE_DATE >= SYSDATE - 1;';

  DBMS_OUTPUT.PUT_LINE(v_query1);

  v_query2 := 'UPDATE '||v_trg_tbl_nm||' ';
  v_query2 := v_query2||'SET DATA_LOADED_FLAG = '||''''||'Y'||''''||',';
  v_query2 := v_query2||'UPDATE_DATE = SYSDATE ';
  v_query2 := v_query2||v_where_cl1||v_where_cl2||v_where_cl3||v_where_cl4;
  v_query2 := v_query2||' AND DATA_LOADED_FLAG = '||''''||'N'||'''';
  v_query2 := v_query2||' AND TOT_ERR_CNT = 0;';

  DBMS_OUTPUT.PUT_LINE(v_query2);

  BEGIN  -- PROCESS THE RECORDS

    OPEN v_cursor FOR v_query1;
    LOOP

      EXECUTE IMMEDIATE (v_query2);

    END LOOP;

  END;

END PRC_UHC_REFC_EXT_TBL;

Please help.

Thanks
Sudha

      and we said...
well, there are so many things wrong with this -- i'll just guess as to what it
was to do and take it from there.

Looks like you build a query like:

  select * from T1 where insert_date >= sysdate=1 or update_date >= sysdate-1;

And this query might have upto 4 special columns in it whose names will be
passed in via v_col1_nm .. v_col4_nm.  Additionally -- at least v_col1_nm is
passed in else the word WHERE would never get added to the update (and running
the update would be a waste)

So, the update could really be (assuming you really store 'Y' and 'N' and not
just Y and N in the table??? is the data_loaded_flag 3 characters???):

  update T2
     set data_loaded_flag = '''Y''', update_date = sysdate
   where ( column_list ) in ( select column_list
                                from T1
                               where insert_date >= sysdate-1
                                  or update_date >= sysdate-1 )
      and data_loaded_flag = '''N'''
      and tot_err_cnt = 0;

No procedural code !!!!

procedure prc_uhc_refc_ext_tbl(v_src_tbl_nm     VARCHAR2,
                               v_trg_tbl_nm     VARCHAR2,
                               v_col1_nm        VARCHAR2,
                               v_col2_nm        VARCHAR2  DEFAULT NULL,
                               v_col3_nm        VARCHAR2  DEFAULT NULL,
                               v_col4_nm        VARCHAR2  DEFAULT NULL)
IS
    l_yes   varchar2(3) := '''Y''';
    l_no    varchar2(3) := '''N''';
    l_query long;
    l_cols  long :=
rtrim(v_col1_nm||','||v_col2_nm||','||v_col3_nm||','||v_col4_nm,',');
BEGIN
    l_query := '
    update ' || v_trg_tbl_nm || '
       set data_loaded_flag = :yes,
           update_date = sysdate
     where ( ' || l_cols || ') in ( select ' || l_cols || '
                                      from ' || v_src_tbl_nm || '
                                     where insert_date >= sysdate-1
                                        or update_date >= sysdate-1 )
      and data_loaded_flag = :no
      and tot_err_cnt = 0';

    execute immediate l_query using l_yes, l_no;
end;
/

is all you need -- i used binds on :yes and :no because I think you might be
"wrong" in your attempt above -- that they really don't have quotes -- but tis
easy to fix.

Review &
      Followup

      Rating: 4
I'm getting an
      error though !  January 20, 2003
Reviewer:
      Sudha Bhagavatula  from Buffalo, NY
Thanks Tom, I was doing it completely wrong. That set me right.

January 20, 2003 sreekanth -- Thanks for the question regarding
      "Refreshing the data from production to test instance", version 8.1.7.3

      You Asked
Tom,

We are running oracle applications 11i(11.5.7) (with self service applications)
with oracle 8.1.7.3 on windows 2000.  We want to refresh our test system with
production data every week.  Our data base is around 50 gig big and we has some
big tables(around 1 mil. rows) Every body tells that importing data only wont
work with oracle applications 11i. Is this true? so right now our dba clones the
test system from production, which means installing from scrach it takes lot of
time.  is there a better way to do this?

please advice.

thanks

      and we said...
Take your hot backup from the production instance, restore it to test.

great way to test your backups actually work and very fast.
January 21, 2003 Ashiq -- Thanks for the question regarding
      "problem in execute immediate using CLOB datatype", version 8.1.7

      You Asked
Hi Tom

1)  I've  problem executing a native dynamic sql using clob data type.Then i
referred the docs its say native dynamic won't support CLOB datatypes.

 Below i've give the table , pl/sql block and error.

ashiq@RMSREAL8> desc a
 Name                                                  Null?    Type
 ----------------------------------------------------- -------- ------
 NAME                                                           CLOB

ashiq@RMSREAL8> select * from a;

NAME
--------------------------------------------------
select * from tab

ashiq@RMSREAL8> ed
Wrote file afiedt.buf

  1  declare
  2  v_strsql  clob;
  3  begin
  4   select name
  5      into v_strsql
  6    from  a;
  7  execute immediate v_strsql;
  8* end;
ashiq@RMSREAL8> /
execute immediate v_strsql;
                  *
ERROR at line 7:
ORA-06550: line 7, column 19:
PLS-00382: expression is of wrong type
ORA-06550: line 7, column 1:
PL/SQL: Statement ignored

   Can u give me another way around to do this with an example.

Even if i try to delete the records ,its giving

ashiq@RMSREAL8> delete from a where name = 'ashiq';
delete from a where name = 'ashiq'
                    *
ERROR at line 1:
ORA-00932: inconsistent datatypes

  Why its throwing this error???

 Since the sql statement what we store in database  will be morethan 4000 bytes
,so we changed to CLOB datatype.

2) what's the maximum number index can i've for a table?

3) what is the max number of lines(in the sense number of codes of lines) in can
i've procedure / function  ? ? ?

      and we said...
In 9i, when a string and a clob are virtually synonymous -- this is easy.  In
8i, you must use the dbms_lob package for most lob interactions.

ops$tkyte@ORA817DEV> create table t (name clob);

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert into t values ( 'select * from dual' );

1 row created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> declare
  2          l_strsql clob;
  3  begin
  4          select name into l_strsql from t;
  5          execute immediate l_strsql;
  6  end;
  7  /
        execute immediate l_strsql;
                          *
ERROR at line 5:
ORA-06550: line 5, column 20:
PLS-00382: expression is of wrong type
ORA-06550: line 5, column 2:
PL/SQL: Statement ignored

ops$tkyte@ORA817DEV> declare
  2          l_strsql LONG;
  3          l_dummy  dual.dummy%type;
  4  begin
  5          select dbms_lob.substr( name, 32765, 1 ) into l_strsql from t;
  6          execute immediate l_strsql into l_dummy;
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> delete from t where name = 'x';
delete from t where name = 'x'
                    *
ERROR at line 1:
ORA-00932: inconsistent datatypes

ops$tkyte@ORA817DEV> delete from t where dbms_lob.substr( name, 32765, 1 ) =
'x';

0 rows deleted.

2) infinite

3) there is the max number possible.

Then there is the max number PRACTICAL.  Max number possible is many orders of
magnitude higher then practical.

A procedure or function shouldn't have more then a couple dozen/hundred lines of
code.

It can have 10's of thousands.  But don't do it!

There is no hard limit on the number of lines, rather it is a function of the
complexity of the code itself.

Review &
      Followup

      Rating: 4
problem in
      execute immediate using CLOB datatype  December 24, 2002

Reviewer:  Ashiq  from Trichy, Tamil Nadu ,India

what this actually do dbms_lob.substr( name, 32765, 1 ) ?

          Followup:

fortunately, we document all of this stuff!!

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76936/toc.htm
ctl-f for dbms_lob -- but feel free to read some of the others.

      Rating: 4
Over 4000
      characters  January 20, 2003
Reviewer:  John
       from Sydney, Australia
Is there a way to get around this in PL/SQL (using 8.1.7):

<PRE>
SQL> select dbms_lob.getlength( name ) from t;

DBMS_LOB.GETLENGTH(NAME)
------------------------
                    4001
SQL> declare
  2   l_strsql LONG;
  3   l_dummy  dual.dummy%type;
  4  begin
  5   select dbms_lob.substr( name, 32765, 1 ) into l_strsql from t;
  6   execute immediate l_strsql into l_dummy;
  7  end;
  8  /
declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at line 1
ORA-06512: at line 5
</PRE>

          Followup:

....

  subtype LONG is VARCHAR2(32760);

.....

as defined in package standard, you can get another 5 characters like this:

ops$tkyte@ORA817DEV> declare
  2          l_strsql varchar2(32765);
  3  begin
  4          l_strsql := rpad( '*', 32765, '*' );
  5          dbms_output.put_line( length(l_strsql) );
  6  end;
  7  /
32765

PL/SQL procedure successfully completed.

is that what "get around this" means?  not sure what "this" is.

      Rating: 1
I think this is
      what he meant...  January 20, 2003
Reviewer:
      Jon  from Sydney, Australia
Hi Tom,

I think what John from Sydney was saying is this:

declare
    vStr    long;
begin
    select dbms_lob.substr(xml,4000,1) into vStr
    from correspondence
    where correspondence_id = 1;
end;

PL/SQL procedure successfully completed

declare
    vStr    long;
begin
    select dbms_lob.substr(xml,4001,1) into vStr
    from correspondence
    where correspondence_id = 1;
end;

ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at line 1
ORA-06512: at line 4

It is something that is a real pain in 8.1.7 and earlier - can't select into
anything longer than 4000 chars even in PL/SQL.  In the same way, you can't
concat > 4000 chars in PL/SQL:

declare
    vStr long;
begin
    select rpad('*',2000,'*')||rpad('#',2000,'#')
    into vStr
    from dual;
end;

PL/SQL procedure successfully completed

declare
    vStr long;
begin
    select rpad('*',2001,'*')||rpad('#',2000,'#')
    into vStr
    from dual;
end;

ORA-01489: result of string concatenation is too long
ORA-06512: at line 4

I rate right up there as the most annoying *feature* - part of that artificial
distinction between PL/SQL and SQL.  I understand that has been addressed in 9i
with the combining of the PL/SQL and SQL compilers. Is that correct?

          Followup:

It is not PLSQL -- it is a SQL limitation -- still there.  in SQL varchar2 is
4000 characters -- period.

this is the way to accomplish the above:

ops$tkyte@ORA817DEV> create table t ( x clob );

Table created.

ops$tkyte@ORA817DEV> declare
  2          l_clob  clob;
  3  begin
  4          insert into t values ( empty_clob() ) returning x into l_clob;
  5          for i in 1 .. 10
  6          loop
  7                  dbms_lob.writeAppend( l_clob, 32000, rpad('*', 32000, '*' )
);
  8          end loop;
  9  end;
 10  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select dbms_lob.getlength( x ) from t;

DBMS_LOB.GETLENGTH(X)
---------------------
               320000

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> declare
  2          l_clob  clob;
  3          l_str   long;
  4  begin
  5          select x into l_clob from t;
  6          l_str := dbms_lob.substr( l_clob, 32760, 1 );
  7
  8          l_str := rpad( '*', 2001, '*' ) || rpad( '#', 2000, '#' );
  9  end;
 10  /

PL/SQL procedure successfully completed.

why would you use a select to concatenate two strings??  anyway -- it is all
about SQL, not PLSQL here -- 4000 is the limit.  Fortunately they preserved this
"artificial barrier" and keep it so plsql can do 32k strings, not 4000 character
stringsg

      Rating: 3
Agree with you,
      but...  January 20, 2003
Reviewer:  Jon
       from Sydney, Australia
Yes, understand that.  However try explaining to someone new to PL/SQL that:

declare
    vName long;
begin
    select title||','||name||','||address||','||hobbies
    into vName
    from person
    where person_id = 1234;
end;

1) This is part SQL, part PL/SQL, where abouts one ends and the other begins,
and that rules can be different for both
-and-
2) could produce an error depending on data in these columns if, say hobbies was
an varchar2(4000), even though we are working with a 32K string.

That is what I mean about artificial distinction - it is quirky to say the
least.  From the inside it makes sense (knowing architecture) - but from the
outside looking in, it certainly causes confusion and frustration.  Seen it many
a times, especially for those coming from other programming environments, or
other DBs.  T-SQL doesn't have such restrictions.

Has this behaviour been modified in 9i?

          Followup:

1) well, the SQL starts with one of 5 keywords -- select, insert, update, delete
and merge....  I guess I would start there?

T-SQL, yes, funny to compare t-sql to plsql.  I've written with both.  writing
with tsql is like trying to write with the full functionality of a command.com
scripting language.

sql = 4000
plsql = bigger than 4000

      Rating: 5
4000
      limit  January 20, 2003
Reviewer:  John
       from Sydney, Australia
Thanks Jon, that was kind of what I was getting at.
The problem actually was executing native dynamic SQL, where the text in the
CLOB was over 4000 characters.

Tom's answer above can be used with a slight modification to do the clob->long
conversion in PL/SQL rather than in SQL(for all the reasons you've talked
about):

SQL> DECLARE
  2   l_clob   CLOB;
  3   l_strsql LONG;
  4   l_dummy  dual.dummy%TYPE;
  5  BEGIN
  6   SELECT name INTO l_clob FROM t;
  7   l_strsql := dbms_lob.SUBSTR( l_clob, 32765, 1 );
  8   EXECUTE IMMEDIATE l_strsql INTO l_dummy;
  9* END;
SQL> /

PL/SQL procedure successfully completed.

          Followup:

or even:

SQL> DECLARE
  2   l_clob   CLOB;
  4   l_dummy  dual.dummy%TYPE;
  5  BEGIN
  6   SELECT name INTO l_clob FROM t;
  8   EXECUTE IMMEDIATE dbms_lob.SUBSTR( l_clob, 32765, 1 ) INTO l_dummy;
  9* END;
SQL> /

Or in 9i when clobs are just big "strings" in plsql (where you can use a
varchar2, you can use a clob -- you can LIKE a clob, instr a clob, whatever)

ops$tkyte@ORA920> create table t ( x clob );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          l_str long;
  3  begin
  4          l_str := rpad( ' ', 32000, ' ' ) || 'select ''Hello World'' from
dual';
  5          insert into t values ( l_str );
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select dbms_lob.getlength(x) from t;

DBMS_LOB.GETLENGTH(X)
---------------------
                32030

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          l_dummy varchar2(25);
  3          l_clob  long;
  4  begin
  5          select x into l_clob from t;
  6          execute immediate l_clob into l_dummy;
  7          dbms_output.put_line( l_dummy );
  8  end;
  9  /
Hello World

PL/SQL procedure successfully completed.

January 21, 2003 -- Thanks for the question regarding
      "xmlgen.getxml does not give whole out put", version 8.1.7.3.0

      You Asked
Hi tom,

I am trying to generate data in xml format using xmlgen.getxml.  But  it
produces only one row and some times it gives
oracle.jdbc.driver.OracleSQLException: ORA-00942. Error

Please help me.

SQL>  select xmlgen.getxml('select a.full_name, a.employee_number from
per_all_people_f a') from dual;

XMLGEN.GETXML('SELECTA.FULL_NAME,A.EMPLOYEE_NUMBERFROMPER_ALL_PEOPLE_FA')
--------------------------------------------------------------------------------
<?xml version = '1.0'?>
<ROWSET>
   <ROW num="1">
      <FULL_NAME>gadhiraju, sreekanth

SQL>

SQL> select xmlgen.getxml('select e.ename, e.empno, e.sal, d.dname from emp e,
dept d where e.deptno
 = d.deptno') xml
  2        from dual;

XML
--------------------------------------------------------------------------------
<?xml version = '1.0'?>
<ERROR>oracle.jdbc.driver.OracleSQLException: ORA-00942:

SQL>

thanks
sreekanth.g

      and we said...
SQL> set long 50000

to control how much of a long/clob sqlplus will display.
Review &
      Followup

      Rating: 5
it is
      working  August 14, 2002
Reviewer:  sreekanth
      gadhiraju
thanks it works great

      Rating: 3
What about this
      one?  January 21, 2003
Reviewer:  Rahul
       from India
Hi Tom

I am facing problem during xml generation. I have generated many reports using
the same method but in a simple query oracle is giving Internal Error Code.

I have a table ER_STUDY with 496 records. When I fire below statements , It
works fine.

select xmlgen.getxml('SELECT
PK_STUDY,STUDY_TITLE,STUDY_SAMPLSIZE,IP_ADD,FK_ACCOUNT FROM er_study where
rownum<=471') from dual;

but when I fire simple query with out any where clause, then Oracle gives
problem below...

select xmlgen.getxml('SELECT
PK_STUDY,STUDY_TITLE,STUDY_SAMPLSIZE,IP_ADD,FK_ACCOUNT FROM er_study) from dual
       *
ERROR at line 1:
ORA-29532: Java call terminated by uncaught Java exception:
oracle.xml.sql.OracleXMLSQLException: ORA-00600: internal error code,
arguments: [kdlseek-kgbtnscb], [], [], [], [], [], [], []
ORA-06512: at "ER.XMLGEN", line 486
ORA-06512: at "ER.XMLGEN", line 477
ORA-06512: at line 1

I couldn't understand the reason.
In my first query if I change the rownum from 471 to 472 then also Oracle give
same problem.
And in other case , in second query if I remove any one of column from SELECT
list then query works fine.

Why it is behaving so peculiarly?
Please response.

Thanks.

          Followup:

Ora-00600 = time to call support.

      Rating: 3
What about this
      one?  January 21, 2003
Reviewer:  Rahul
       from India
Hi Tom

I am facing problem during xml generation. I have generated many reports using
the same method but in a simple query oracle is giving Internal Error Code.

I have a table ER_STUDY with 496 records. When I fire below statements , It
works fine.

select xmlgen.getxml('SELECT
PK_STUDY,STUDY_TITLE,STUDY_SAMPLSIZE,IP_ADD,FK_ACCOUNT FROM er_study where
rownum<=471') from dual;

but when I fire simple query with out any where clause, then Oracle gives
problem below...

select xmlgen.getxml('SELECT
PK_STUDY,STUDY_TITLE,STUDY_SAMPLSIZE,IP_ADD,FK_ACCOUNT FROM er_study) from dual
       *
ERROR at line 1:
ORA-29532: Java call terminated by uncaught Java exception:
oracle.xml.sql.OracleXMLSQLException: ORA-00600: internal error code,
arguments: [kdlseek-kgbtnscb], [], [], [], [], [], [], []
ORA-06512: at "ER.XMLGEN", line 486
ORA-06512: at "ER.XMLGEN", line 477
ORA-06512: at line 1

I couldn't understand the reason.
In my first query if I change the rownum from 471 to 472 then also Oracle give
same problem.
And in other case , in second query if I remove any one of column from SELECT
list then query works fine.

Why it is behaving so peculiarly?
Please response.

Thanks.

January 21, 2003 Monika -- Thanks for the question regarding "How
      to 'delete the statistics after running the analyze command.'", version
      8.0.4.1.0

      You Asked
While refering to your "Ask Tom Archive" have seen this Note
many times. I would like to Analyse some tables. But also tell
me how to delete the statistics after running the analyze
command? Do i need to analyse each and every table? After
running ANALYSE which tables and view to look in for results and
how to use them???

Waiting for your response.
--
MP

      and we said...
Why would you want to analyze a table and then immediately
delete the statistics?

To analyze and then delete, you would:

scott@DEV8I.WORLD> analyze table emp compute statistics;
Table analyzed.

scott@DEV8I.WORLD> analyze table emp delete statistics;
Table analyzed.

You need to analyze any table for which you want statistics for.
 The result of an analyze is typically used by the optimizer --
not necessarily by you but the information is stored in the data
dictionary.  For example:

scott@DEV8I.WORLD> select num_rows, blocks, empty_blocks,
  2             avg_space, chain_cnt, avg_row_len,
  3             AVG_SPACE_FREELIST_BLOCKS, NUM_FREELIST_BLOCKS ,
  4             LAST_ANALYZED, sample_size
  5    from user_tables
  6   where table_name = 'EMP'
  7  /

  NUM_ROWS     BLOCKS EMPTY_BLOCKS  AVG_SPACE  CHAIN_CNT
---------- ---------- ------------ ---------- ----------
AVG_ROW_LEN AVG_SPACE_FREELIST_BLOCKS NUM_FREELIST_BLOCKS
----------- ------------------------- -------------------
LAST_ANAL SAMPLE_SIZE
--------- -----------

Nothing there, all NULL values... Now:

scott@DEV8I.WORLD> analyze table emp compute statistics
  2  /

Table analyzed.

scott@DEV8I.WORLD> select num_rows, blocks, empty_blocks,
  2             avg_space, chain_cnt, avg_row_len,
  3             AVG_SPACE_FREELIST_BLOCKS, NUM_FREELIST_BLOCKS ,
  4             LAST_ANALYZED, sample_size
  5    from user_tables
  6   where table_name = 'EMP'
  7  /

  NUM_ROWS     BLOCKS EMPTY_BLOCKS  AVG_SPACE  CHAIN_CNT
---------- ---------- ------------ ---------- ----------
AVG_ROW_LEN AVG_SPACE_FREELIST_BLOCKS NUM_FREELIST_BLOCKS
----------- ------------------------- -------------------
LAST_ANAL SAMPLE_SIZE
--------- -----------
        14          1           62       7507          0
         40                      7507                   1
11-JUL-00           0

Every column has a value.  The same will be true of
user_indexes and other views as well (histograms and so on).

How to use them --  you let the cost based optimizer at them
-- it uses them to optimize queries.

Review &
      Followup

      Rating: 5
Wondering Why
      ?  January 21, 2003
Reviewer:  Ashok Shinde
       from India, Pune
From the two queries on user_tables in which one of the row returned all null
and the other returned some values (after analyse). Does it mean that some of
the columns in the user_tables get populated after the analyze command? Are
these values temporary in the table or are they set to null immediately ? Can
you please explain me the flow ?

          Followup:

many of the columns in user_tables are NULL until an analyze fills them in.

they stay there until you do something to make them go away or change such as:

o deleting the stats
o dropping the table
o analyzing again

January 21, 2003 Omar -- Thanks for the question regarding
      "ORA-29547 Java system class not available: oracle/aurora/rdbms/Compiler",
      version 8.1.5

      You Asked
Hi Tom,
  I tried to use your code

CREATE or replace JAVA SOURCE
 NAMED "MyTimestamp"
 AS
 import java.lang.String;
 import java.sql.Timestamp;
 public class MyTimestamp
 {
   public static String getTimestamp()
   {
    return (new
    Timestamp(System.currentTimeMill is())). toString();
   }
 };
/

but the following error occured
ORA-29547 Java system class not available:
oracle/aurora/rdbms/Compiler

I know that it is because java system not loaded yet but I would

like to know which script will do so and the its location.
I am using Oracle8i 8.1.5 on True64 Unix

thanks in advance

      and we said...
Perform the manual installation of JServer by running the
following:

    $ORACLE_HOME/javavm/install/initjvm.sql

Initjvm.sql must be run by the SYS user using svmrgrl

Review &
      Followup

      Rating:
      5
  April 02, 2002
Reviewer:
      A reader

      Rating: 2
Error
      ORA-29541  September 05, 2002
Reviewer:
      Subhro  from Mumbai, India
Tom,
    When I run initjvm.sql, I get this error repeatedly.
ORA-29541: class SYS.oracle/aurora/rdbms/security/PolicyTableManager could not
be resolved

    What could be the reason...
Thanks

          Followup:

don't know - you haven't given me much information.  (oh like, did you run it as
sys, what version are you using, did you have a bad java install that you need
to remove first with rmjvm, etc)

Please contact support and open a TAR.

      Rating: 2
I did as u
      said  September 05, 2002
Reviewer:  Subhro
       from Mumbai, India
Well, Tom,
   The Oracle Version is 8.1.7. I did run the script as Sys and thru svrmgrl.
This time, I did run rmjvm.sql and then ran the initjvm again. But still the
same problem arrives.

I have set
java_pool_size = 70000000.

I guess I will have to raise a TAR.

Thanks anyway for the help...

      Rating:
      4
  September 05, 2002
Reviewer:  Tianhua
      Wu  from Atlanta, GA
for 8.1.6 and 8.1.7, you also need to run several more scripts besides
initjvm.sql, check metalink for detail.

      Rating: 3
Didn't
      work...  January 21, 2003
Reviewer:  Rahul
       from India
Hi Tom

I am getting the same problem
ORA-29547: Java system class not available: oracle/aurora/rdbms/Compiler

and tried the solution which you gave to Omar, but it didn't workout to me. I
have run the script INITJVM.SQL manually script by script and got the problem
when I ran INIT_SECURITY.SQL, it gave me same problem
ORA-29540: class oracle/aurora/rdbms/security/PolicyTableManager does not exist

Now tell me what should I do ?
I am unable to complete the initjvm.sql to install the JVM in oracle.

Please response.

Thanks.

          Followup:

use rmjvm to get it backed out of there and try again.

contact support if the problem persists.  given I don't know your version, os,
etc -- i cannot really help (don't put it here, use support -- this is what they
are really good at -- install, configuration, help "my database is down")

January 21, 2003 -- Thanks for the question regarding "EXPORT
      timings differ wrt Optimizer Mode. ", version 9.2.0

      You Asked
I was assigned the task to benchmark timings for Export in 9.2.0, as reported to
me that they took more time to finish wrt 8.1.7.

The default optimizer_mode was set to FIRST_ROWS.
I DID THE FOL. TEST. FOR THE SAME DATABASE ON 9.2.0 ON HP MACHINE.

THE OPTIMIZER_MODE WAS CHANGED BY ON LOG ON TRIGGER EVERYTIME, THE SAME NO. OF
TABLES WITH SAME AMOUNT OF DATA WERE EXPORTED IN EVERY TEST RUN. I was the only
person on the database at that time doing these tests. Each export was take
three times with same set of tables ( 200 tables )

BELOW ARE THE STATS OF AVERAGE RUNTIME IN EACH MODE.

Result :

OPTMIZER_MODE          AVERAGE RUN TIME
------------------      --------------------
CHOOSE             : 1:05 MIN
RULE             : 1.07 MIN
FIRST_ROWS         : 9:03 MIN
ALL_ROWS         : 14.30 MIN

QUESTIONS
==========
1. Why this difference,
2. The tables had stats analyzed, was CHOOSE internally using RULE .
3. Based on the above tests I recommended CHOOSE for exports. Any comments on
this ?

      and we said...
but the export tables themselves did not have stats - the data dictionary did
not.  the queries export does against your data --trivial "select * from
tables", against the dictionary -- none trivial.

You used CBO on tables with no stats and got bad performance.  That is to be
expected.

Review &
      Followup

      Rating:
      2
followup  January 20, 2003
Reviewer:
      A reader
Before the test. All the tables were analyzed. But even then , I got the above
results. The table data was exported with exp parameter "STATISTICS=NONE".
Any comments

Also should the schema of SYS should be analyzed in Oracle9i for better exp
performance ?

Any comments

          Followup:

*data dictionary*  *sys tables*  they were not.  they were the issue.

leave the sys schema alone.
let the optimizer default to choose...

and sleep better at night.

      Rating: 2
feed back
        January 20, 2003
Reviewer:  A reader

Are u suggesting the fol.
   For export keep the optimizer_mode=choose
   Never analyze the sys objects.

Before I did the tests , I had deleted any stats on sys or system objects, also
before starting the export all the tables were analyzed ....
Still the above results ....
Comments/Clues

          Followup:

I'm saying

"that because the sys.* tables do not have stats and the most intense queries
run during an export are against the sys.* table themselves -- the queries
against YOUR tables are all simple full scans -- using anything other then
choose it not a good idea"

It is because you had no stats on sys.* tables that first and all rows -- both
of which DEMAND stats -- did not do so well.

I do not suggest you analyze them, i suggest you let the optimizer default to
the default -- CHOOSE.

The fact that your tables had stats -- not relevant.

      Rating:
      3
  January 20, 2003
Reviewer:
      A reader
Thanks.
Will follow the recommendation, Is there any documentation for 9i which
recommends not to analyze the sys.* objects.
I came across a paper on metalink  but was for 8i. Did not came across anything
suggesting "ANALYZE OR DO NOT ANALYZE" SYS SCHEMA ....
ANY COMMENTS ON THIS WOULD BE HELPFUL.

          Followup:

i would wait for the next release.

while supported with 9i, it is not yet recommended...

with the next release, given that CBO is the only game in town, it'll be
recommended...

      Rating: 5
CBO is the only
      game in town  January 21, 2003
Reviewer:
      Reader  from Nowhere, USA
Just curious as to what will happen with no RBO and tables not analyzed?
Wouldn't there have to be RBO? Or am I just being dense?

          Followup:

it'll use the CBO, make the default assumptions and go about its business.

Just like what happens when the optimizer_goal is set to first_rows today.

      Rating:
      3
  January 21, 2003
Reviewer:
      A reader
Thanks ....

January 21, 2003 Raj -- Thanks for the question regarding
      "Advanced Queueing", version 8.1.7

      You Asked
Hi Tom,

I have two questions-

1- I was reading FAQs chapter 6 in Oracle 8i AQ manual where it says "If a
remote queue is a different database, we use sequencing algorithm to avoid a
need of two phase commit." It doesn't have any example to support. Can you
display this by example?

2- Is there any built in provided by Oracle using PL/SQL that dequeues the
message automatically from the queue table as soon it's ready for process. How
do I automate this procedure?

Thanks.

      and we said...
1) I asked the developers and they said:

<quote>
Messages are propagated in batches (of 256 messages) from one queue to another
queue.

Two-phase commit (and its associated overhead) can be eliminated if a strict
ordering can be maintained between messages that are to be sent to a particular
destination.

Each batch propagated is associated with a a unique tag, sequeunce number
(generated by a database sequence) and both the source and destination database
keeps track of the latest batch number (sequence number) sent and received
respectively. This information is stored in the sys.aq$_propagation_status
tables.

On recovery, the propagator will query the remote database
(sys.aq$_propagation_status table)  for all propagated messages (since the
previous checkpoint) from the source site to avoid sending the message again. A
checkpoint is periodically taken to (one per batch of messages propagated) limit
the amount of state necessary to perform recovery. Fundamentally, a unique tag
(a sequence number) needs to be associated with each unit of work (a batch of
message) that can be committed independently at the destination site.

</quote>

2) In Oracle9i -- yes, this is a new feature of AQ added in that release.  See

http://technet.oracle.com/docs/products/oracle9i/doc_library/901_doc/appdev.901/a88890/qwnew.htm#969817
for details.

In 8i, you would have to run a plsql procedure that constantly blocked on a
dequeue -- got a message, processed it, went back to dequeue again.

Review &
      Followup

      Rating:
      4
  January 21, 2003
Reviewer:
      A reader  from USA
In question 2-
Is setting up a DBMS_JOB that constantly looks in the queue table and dequeues
as soon as message is arrived, good idea?
Or would you suggest some kind of deamon process running in the background? Can
you provide an example of deamon process?

Thanks.

          Followup:

you need a background process if you want them to be processed as they arrive,
dbms_job just looks every N seconds -- not constantly.

depends on your needs -- you have to answer this one for yourself.

easy background process:

sqlplus / <<EOF &
begin
   loop
       dequeue
       process
   end loop
end;
/
EOF

in shell script.

January 21, 2003 vikas -- Thanks for the question regarding "how
      do we retrieve LOBS from the database using JAVA interface ", version

      You Asked
In our application we are storing images as BLOB's. By
using DBMS_LOB package we were able to insert the image .
Now how do we retrieve it from the database using JAVA interface

. Also where to store the image in JAVA once retrieved from the
database.
Can you send us some sample code for it.

      and we said...
see

http://technet.oracle.com/doc/oracle8i_816/appdev.816/index.htm
or

http://technet.oracle.com/doc/java.815/a64685/basic4.htm#1010491
they have examples of streaming blobs.  As an alternative to
streaming (as shown above) you can simply create a prepared
statement and use dbms_lob.read() to read the java out.  I'll
put an example of WRITING a blob below to show how to define and
bind a BLOB column but the concept for reading is identical --
just call dbms_lob.read instead of write.

As for "where to store the image in Java" -- you'll have to
answer that.  What do you want to do with it?  You need to store
it where you need to store it.  I don't understand what you mean
by this question.

import java.sql.*;
import java.io.*;
import oracle.jdbc.driver.*;
import oracle.sql.*;

class test {

public static void main (String args []) throws SQLException ,
FileNotFoundException, IOException
{
    DriverManager.registerDriver (new
oracle.jdbc.driver.OracleDriver());

    Connection conn =
DriverManager.getConnection("jdbc:oracle:thin:@aria:1521:ora8i",
"scott", "tiger");

    conn.setAutoCommit(false);

    Statement stmt = conn.createStatement();

    stmt.execute( "alter session set sql_trace=true" );

    stmt.execute( "delete from demo" );
    System.out.println( "deleted from demo" );

    stmt.execute( "insert into demo (id,theBlob) values
(1,empty_blob())" );
    System.out.println( "inserted into demo" );

    conn.commit();
    System.out.println( "committed" );

    ResultSet rset = stmt.executeQuery ("SELECT theBlob FROM
demo where id = 1 FOR UPDATE");
    System.out.println( "Executed Query" );

    if(rset.next())
    {
        System.out.println( "Fetched row " );

        BLOB l_mapBLOB = ((OracleResultSet)rset).getBLOB(1);

        File binaryFile = new File("/tmp/binary.dat");
        FileInputStream instream=new
FileInputStream(binaryFile);

        int chunk = 32000;

        System.out.println( "Chunk = "+ chunk );

        byte[] l_buffer = new byte[chunk];

        int l_nread = 0;

        OracleCallableStatement cstmt =
            (OracleCallableStatement)conn.prepareCall( "begin
dbms_lob.writeappend( :1, :2, :3 ); end;" );

        cstmt.registerOutParameter( 1, OracleTypes.BLOB );

        while ((l_nread= instream.read(l_buffer)) != -1) // read
from file
        {
            cstmt.setBLOB(  1, l_mapBLOB );
            cstmt.setInt(   2, l_nread );
            cstmt.setBytes( 3, l_buffer );

            cstmt.executeUpdate();

            l_mapBLOB = cstmt.getBLOB(1);
        }
        instream.close();

        conn.commit();
        rset.close();
        stmt.close();

      }
}

}

Review &
      Followup

      Rating: 4
Dealing with
      BLOB datatype  July 02, 2001
Reviewer:  Joshua
      CH Yan  from Malaysia
I found that this article it useful to me as I know how to deal BLOB. But it
would be better that it have more samples.

      Rating: 4
working on
      server but fails to load files from client machine.  May 28,
      2002
Reviewer:  shamim ahmad  from New Delhi India

Hi Tom
I have tested above program that is woorking fine at the server , even after
making it JSP and calling it from a PLsql Procedure , But whenever i tried to
store a file from client(client-server) using above procedure ,it shows
java.io.FileNotFoundException.
I have Oracle Client on all client machines and using java 1.4 and oracle 8.1.7.
So ps suggest for that what i should do.

          Followup:

is the file to be found on the machine where the java code is running?  doesn't
sound like it.  How would java be able to read a file it cannot see?

      Rating: 3
detail about
      query  May 31, 2002
Reviewer:  shamim ahmad
       from New Delhi India
Hi
 I am downloading  mails into databse. and Storing  attachments into a blob
column using javamail.what i want is each individual to whom mail are concerned
, should sit on his machine and access there respective mails.I am using d2k as
front end and oracle8.1.7 as databse. Now for seeing attachment contents I am
using JSP and accessing it from my form using a PL/SQL procedure.
 On DATABASE server machine  This application works fine but when I run from a
machine that have only client Installed on it and connected to databse server,
This does'nt show attachment.
And raise exception java.io.FileNotFoundException
so, ps suggest me what i should do to my jsp , show that i could see attachment
from client machine.
thanx and regards
shamim

          Followup:

Yes, well, it sounds like your jsp is saving the attachment to a file on the
SERVER and hence it is not on the client.

In any case -- there is truly insufficient data, nor is this the appropriate
place to ask a question really, to make any sort of recommendation.

Why would you use forms for this?  This seems much more appropriate to be done
in HTML.  That way the browser would take care of displaying the attachments
pretty nicely and the JSP would just stream the lob back to the browser.

      Rating: 5
closing
      statement before closing lob stream  January 21, 2003

Reviewer:  Sam  from San Mateo, CA
Tom,

In the code you posted above, will we be able to continue to use the lob
variable (e.g write to the lob stream) if we close the statment but hang on to
the lob variable?

...
    if(rset.next())
    {
...
        BLOB l_mapBLOB = ((OracleResultSet)rset).getBLOB(1);
...
        rset.close();
        stmt.close();  // WE CLOSE THE STATEMENT HERE,
        return l_mapBLOB;  // return the lob to the caller
      }

The caller will then read from or write to the lob using the lob object
returned.  This way, the caller does not have to worry about closing the
statements that retrieved the lob.

It works on our test environments, but one particular customer is getting
intermittent errors.

          Followup:

As long as you turned off the auto commit silliness in jdbc, yes -- you can
write to it if you locked it...  gotta wonder who came up with the "enterprise"
jdbc spec but made autocommit the default.

      Rating:
      5
  December 17, 2003
Reviewer:
      A reader

January 21, 2003 -- Thanks for the question regarding "Purposes of
      ALTER SYSTEM SUSPEND/RESUME ", version 8.1.5

      You Asked
What are the purposes of ALTER SYSTEM SUSPEND/RESUME ?
The 8.1.5 doc says "uspends all I/O (datafile, control file, and
file header) as well as queries, in all
                    instances, enabling you to make copies of
the database without having to handle
                    ongoing transactions."
When ALTER SYSTEM SUSPEND is issued and I alter tablespace users
begin backup,
the begin backup statement waits in sever manager.
It suspends the begin backup statement as well.
It seems that  V$instance and V$database are the only views that
queries can be run against.
Even V$thread , v$backup are not allowed.
So how can it speed up the backup ?

Thanks

      and we said...
It is intended to be used in extremely special cases.  One case
where it might come in handy is when you are using a triple
mirrored disk array from EMC for example.  You put your
tablespaces into backup mode, alter system suspend IO -- use EMC
software to "break" the mirrors into a pair and one singleton.
You then resume IO on the mirrored pairs and the database
continues on.  You then backup the singleton set of disks at
your leisure.  Later, you "resilver" the 3'rd disk back into the
mirror and away you go.

Very very specialized command for a particular implementation.
99.999% of the people out there will never use it.

<quote 8i backup and recovery guide>

Splitting Mirrors in Suspend Mode

Sometimes systems allow you to mirror a disk or file, i.e.,
maintain an exact duplicate of the primary data in another
location, and then split the mirror. Splitting the mirror
involves separating the copies so that you can use them
independently of one another.

Some RAID devices benefit from suspending writes while the split
operation is occurring; your RAID vendor can advise you on
whether your system would benefit from this feature.

The basic steps for using SUSPEND and RESUME are as follows:

Place the database tablespaces in hot backup mode using the
ALTER TABLESPACE BEGIN BACKUP statement. For example, to place
tablespace USERS in hot backup mode enter:

ALTER TABLESPACE users BEGIN BACKUP;

If your mirror system has problems with splitting a mirror while
disk writes are occurring, issue the following:

ALTER SYSTEM SUSPEND;

Split your mirrors at the O/S or hardware level.

Issue the following:

ALTER SYSTEM RESUME;

Take the specified tablespaces out of hot backup mode. For
example, to take tablespace USERS out of hot backup mode enter:

ALTER TABLESPACE users END BACKUP;

Copy the control file and archive the online redo logs as usual
for a backup.
</quote>
Review & Followup

      Rating:
      5
  December 15, 2002
Reviewer:
      Nystel  from Atlanta
When the database is not having any activity, will Oracle processes be still
performing I/O's against the database? If yes then what process and what
activity is it performing against the files?

Regards

          Followup:

DBWR -- flushing blocks.
LGWR -- flushing redo buffers (every 3 seconds)
CKPT -- updating datafile headers

constrol files getting written to every 3 seconds at least...

yes, a database with no active sessions could be doing tons and tons of IO
(writes).

      Rating: 5
what is there
      to flush?  December 16, 2002
Reviewer:  GUY
       from LA
tom you said
---------------
DBWR -- flushing blocks.
LGWR -- flushing redo buffers (every 3 seconds)
CKPT -- updating datafile headers

constrol files getting written to every 3 seconds at least...

yes, a database with no active sessions could be doing tons and tons of IO
(writes).
------------
but if there is no activity then
DBWR -- no blocks for flushing blocks.
LGWR -- no activity so no flushing redo buffers (every 3 seconds)
CKPT -- updating datafile headers -- update for what?

It would really help if you could explain with examples.

          Followup:

you do an update.  you go away.  you don't commit.

database has no active sessions now....

LGWR -- flushes redo from your update after a bit... sure, you haven't committed
but your redo is flushed

DBWR -- times out, time for a checkpoint, finds dirty blocks from 1/2 hour ago
and writes them out.

CKPT -- signalled by end of checkpoint to update datafile headers....

SMON wakes up and coalesces free space, generating log, dirty blocks and
eventually causing a checkpoint.  SMON does tons in the background -- all of the
time.

Datafiles are constantly being written to, even when "nothing is happening"

      Rating:
      3
  December 16, 2002
Reviewer:
      Nystel  from Atlanta
If there is no database activity then what information is being written into the
control files every three seconds?

If the redo log buffers are not written to (since database is not active), then
what is the reason to flush the redo log buffers? Does that mean when there is
no db activity still we will see online redo log file switched at the same
frequency (since redo log buffers gets flushed to online redo log files)?

Regards

          Followup:

stuff -- just stuff.  stuff we have no control over.  look at your disk drive
light and you'll see it like clockwork (it is clockwork stuff that is written).

SMON is doing stuff.  SMON is generating redo.  Your inactive database isn't
INACTIVE, you have taken the premise that "if I'm not doing anything, it is
not".  well, that premise is wrong.

SMON is just one of them, SNPn's, intermedia, AQ, many other background
processes out there with the potential to do stuff

redo logs will only switch as they fill -- smon may eventually fill one and
switch it sure.  will it be at the same rate as when you have tons of users?  of
course not, it'll be much slower.

      Rating: 5
suspend
      activity  January 21, 2003
Reviewer:  Baqir
      Hussain  from Emeryville, CA USA
Tom,
When the database was put into suspend mode
alter system suspend;
then any kind of activities being happened to redo logs and control files??
can we achive a consistent redo logs and control file during the following:
1. Alter tablespace user begin backup;
2. Alter system suspend;
3. copy data , redo and control files

4. alter system resume;
5. alter tablespace user end backup;

          Followup:

You will NOT achieve consistent backups, they will be HOT FUZZY backups.

The only way to get "consistent" (and here consistent is not a goal, not a
"good" thing) is a cold backup after a normal or immediate shutdown.

Those are normal (good, desirable) fuzzy hot backups.  I would NOT use
suspend/resume for that purpose.  only for the purpose above.

      Rating:
      5
  September 16, 2003
Reviewer:
      A reader
Sorry I think I misplaced the link. This is what I meant:

http://asktom.oracle.com/pls/ask/f?p=4950:8:4496575172726829804::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:373618158622,

January 21, 2003 David -- Thanks for the question regarding
      "Change Management", version 8.1.7

      You Asked
We have been working on a relatively new project
and are currently beginning to attempt to gain control
over a rapidly developed application.

Our dba has introduced a new configuration/version management
software (CVS) tool to hold our definitions, for releases.

I have no problems with the introduction of the tool
the problem I have is the method adopted.

When I have used tools like this in the past such as the
windows based PVCS package it was just a case of checking the object
out (pessimistic locking style) either manually editting the script to add a
column or creating a script containing the current table definition from
designer and checking it back in.

Each version would then hold the physical definition of the table. Simple.

Unfortunately our dba wants to mix change management with release management and
rather than hold the actual definition of the table he wants to hold all the
changes
to the table, so if you wanted to add a column to a table you would see:

Create Table <definition>

Alter table add column <definition>

His arguement being that the dba's can simply plug in these definition files
into any release script.

Our problems arise because there are objects out there where this can't be done,
such as changing a partition key on a partitioned table, also what do we do when
we want to add a mandatory column which requires a datafix. In addition we'd
require input from the
DBA's at this stage, which would be a potential bottleneck.

I also suspect we'll have problems with row chaining using this method.

My preferred method would be to hold the definition scripts, and seperate
release migration
scripts which would rename table, create new table, insert into new_table select
from old_table

I suspect that the dba's are trying to make their own life as simple as
possible, and attempting to kill two birds with one stone, the problem is as a
developer I have to do extra work coding the migration path in what should be
just a simple definition file.

What is your opinion on this matter, am I being unreasonable ?

      and we said...
Well, not to be on one side or the other (cause you guys are a team), I'm with
the DBA on this one.

You have (hopefully) a DEV, TEST and PROD instance.  You need to hand over to
TEST "upgrade scripts" -- not new create tables with extra columns -- but a
series of ALTERS and such that migrate TEST (which is a copy of PROD) from its
current state into the new state.  You cannot just give TEST a new create table
for a table and say "go for it", you have to actually MIGRATE the data from its
old format to the new format.

Now, if the BEST way to migrate is:

alter table T add foo number;

so be it -- if the BEST way to migrat is:

create table NEW_T as select t.*, 12345 foo from t;
drop table t;
rename new_t to t;
create index ... on t(...);
grant on t .....

so be it -- you should not have to do an ALTER for everything BUT your migrate
script should be something that takes TEST version 1.1 and upgrades it to TEST
version 1.2 in the best way possible.

You HAVE to provide a migration path.  Suppose you didn't use a database, just
used flat files.  Would you tell your users "oh, here is your new version, sorry
but all of your old files won't work -- through them out and start over".  No,
you would have to migrate the data.  In the database we do that with alters,
drops, creates and so on.

Bottlenecks be darned -- you guys do work AGAINST eachother -- you work WITH
eachother.  The DBA's aren't bottlenecks, they are just doing their jobs.  So
are you.

I think both sides need to give a little here.  Not everything will be an alter,
but you cannot just give them new create tables either.  You must provide
upgrade scripts that can safely take a copy of PROD and upgrade it to a new
release.  If you can supply downgrade scripts -- ALL THE BETTER (but not always
possible, sometimes a point in time recovery is the only way to achieve that)
Review &
      Followup

      Rating: 5
Thanks for
      taking the time to share your philosophy about
      this!  January 21, 2003
Reviewer:  Robert
       from Memphis, USA

      Rating: 5
You've gotta do
      both  January 21, 2003
Reviewer:  Jeremy
      Smith  from Salt Lake City, UT
You have to keep the database upgrade path somewhere, and you also need to keep
a set of current scripts that creates the tables and DDL from scratch for the
particular release.

Saving everything only as a "patch" will eventually become too great a headache,
since your original configuration will look nothing like your final one.  You
don't want to go into release 11 by patching "alter table" by "alter table" from
release .01.  Yuck.  Not to mention things will take unreasonably longer.

Here would be my suggestion:

Keep a zeroth revision which was your first run at the DDL.
$sourceroot/database/schema/version0.sql

Keep all your "non-release" revisions as your DBA suggestions (as changes to
that original DDL).
$sourceroot/database/schema/version0to1.sql

Then, every time you do a release, export the schema and save that as a
point-in-time revision.  This should end up with an identical configuration as
version0 + version0-1
$sourceroot/database/schema/version1.sql

Then, as developers, or on new installs, you'll only need to run max two scripts
(DDL for last version plus changes)--the DBAs will have an upgrade path from any
release to any release, and your sanity will be preserved.

          Followup:

Good point -- if you are making an "off the shelf" type product that must be
installed on many systems -- you do want both -- but, you can get the second
after doing the first.

The create scripts may be extracted via dbms_metadata in 9i and up.
Export/Import do the trick nicely in 8i and before with minor edits.

      Rating: 4
be
      thankful  January 21, 2003
Reviewer:
      A reader
that your dba's are involved. our developers create tables, throw LONGs in the
middle of tables, design GUI first then table to "fit" the GUI - then 'oh too
late to change the code is done'
just awful
i'm jealous you have such interaction

      Rating:
      5
Agreed  January 21, 2003
Reviewer:  Dave
      D  from US
Tom agreed we *need* an upgrade path too, my arguement would be that should be
held in a seperate release script,
and not within the object creation script.

So we'd have ObjectName.sql  (Create Table ....
         and Releasexxx.sql  (Alter/Drop/Create etc..)

As Jeremy says if all the migration is done alter table, alter table then it
doesn't take long before the script looks terrible.

ps. When I say bottleneck, its because I mean are dba is v busy :D

          Followup:

Yes, that makes sense.  I do not see how it could be in the original "create"
script.

The DBA wants a "run this to upgrade" script.  Not a "run this to have lots of
create errors with a couple of "success" messages"

I did not get that from your original question -- but I think my answer sort of
had that.

The thing the DBA wants to make his life easier is "run this script to upgrade".
 If he wants to put all of the creates/alters in one script -- he will not have
that.  To upgrade will be painfully hard for them.  It would make their life
really hard.

So, maybe you guys need to sit down and talk through "what happens when release
2.0 comes out".

      Rating: 5
It's a matter
      of which is "primary"  January 21, 2003
Reviewer:
      Jeremy Smith  from Salt Lake City, UT
As a developer, you're probably going to change the definition many times per
release.  You have three choices:
1) save the full definition on every change, and generate a "upgrade" per
release.
2) save the "diff" on every change, and generate the full definition per release
3) save the full definition and the diff for every change.

I am, and I think Tom is, suggesting option 2.  1 and 3 run a much higher risk
of being "wrong" (because the developers forgot to modify the diff when they
should have--or because they made a change for which they don't know how to
create a diff) and also don't allow a DBA to create a perfect point-in-time
picture of what your application looks like.

          Followup:

#2 gets my vote.

      Rating: 3
Option
      2  January 21, 2003
Reviewer:  Dave
       from US
Jeremey, can you explain what you mean by the "diff" in option 2.

If the "diff" is how you get from v1 to v1.1 then for something simple like
adding a column its not a problem we can just add an alter table clause.

If you want to add a mandatory column in a specific position on the table  then
it becomes more of a problem.

          Followup:

You need to supply a script that takes the table from "state A" to "state B" --
whatever that takes.

(and if you waste time "putting a column in the 'middle' of a table" -- well,
read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6700238804926

that is what we call "a view"

      Rating: 3
Dare to
      disagree  January 21, 2003
Reviewer:  Dave D
       from US
.. but was always taught to have PK columns listed
in order together at the top of the table.

          Followup:

did they teach you why?

i can think of logical performance reasons to have them nearer to the end
myself.

eg:
it is faster to get the first physical column then the N'th.
you generally query:

select some_columns from t where PK = value;

therefore, if the PK was first on the row, we have to read and parse over it
most of the time.  We use the PK to get the row, we don't goto the row to get
the PK in most cases.

So, what is the scientific, sound reason, to have the primary key at the head?

      Rating: 3
No performance
      reasons  January 21, 2003
Reviewer:  Dave
       from US
Purely from a design perspective, easy to read and looks tidy. It is common
practise - Apps uses this approach too.

          Followup:

so?  why would I waste time, energy and effort to move columns around -- to me,
the pratical answer is "a view".  Easy to read.  Looks tidy (don't get the
"tidy" part -- I mean, If I want to know what the primary key is I'm looking
over in user_cons_columns -- not user_tab_columns but anyway...)

But as you are pointing out -- it is a matter of "taste".  You believe because
you were taught this is what you should do -- you will do it.  I'm learning no
amount of talking talk people out of their beliefs....  You want those columns
first -- go for it.  I'll meet you at the bar later, I'll have that head start.

      Rating: 5
Primary keys at
      the start, nullable cols at the end  January 21, 2003

Reviewer:  Gary  from Sydney, Australia
"why have PK columns listed in order together at the top of the table ?"

Because you put your nullable columns at the end, and primary keys are the only
columns that will never ever become nullable ?
Or in practice, cos they are the first columns you think of when doing the
design, so they tend to end up as the first columns in the table.

I do like the suggestion about putting them at the end (and anything else which
questions 'habits' which have become engrained as design principles).

          Followup:

don't get me wrong -- I'm not suggesting putting primary keys at the end, or the
middle, or the front.

Just that in a relational database, the order of the columns in a table is
technically meaningless.  Rebuilding a table just to have a column "in the
middle" or tidy is just a waste of cycles.  Use a view if you want tidy....

      Rating: 5
tracking change
      from front end  January 21, 2003
Reviewer:  Sean
       from NJ, USA
Hi Tom,

We are developing an application for multiple clients.  Our PROD contains
carefully crafted data for demo and version control purpose.   We also use PROD,
DEV and TEST cycle, but we try not to let users to change PROD through frond end
because it may delete our carefully crafted data.

We only apply script to PROD after it is developed on DEV and tested on TEST
database, such as

   Atler table t1 add  ………
   Insert into t1 ………….

The script has been check in by version control tool.

But some of the data can only be entered through front end.  How can we control
this portion of change by script?

Thanks,

          Followup:

your upgrade script cannot just do the inserts?   don't you "own" the schema?
You can modify the data without the front end (really -- you must be able to)

      Rating: 5
translate front
      end data entry to script  January 21, 2003

Reviewer:  Sean  from NJ, USA
Hi Tom,

Sure, I own the schema and I can do whatever I want.

Some of the front end entry form involves a lot tables, stored procedures and
triggers (Most of the insert, update and business rule are in the VB code).  It
is difficult to translate front end data entry to script.

Let me ask this way, do people really use the script to catch all front end data
entry (insert or update)?  If they do, how?

Thanks,

Sean

          Followup:

I don't undersstand even why we are talkinga bout a front end data entry thing
in an upgrade script i guess?

Your upgrade script should be doing schema alterations.  After which, all data
in the database should be OK.  there shouldn't be anything left to do.  So, I
guess I'm confused.


Subject | Article | Last Updated (East coast time)
Can XML Type tables be replicated | article only | Jan 22, 2003 07:31 am
Returning to Yesterday's Data Using Copied Datafiles | article only | Jan 22, 2003 07:33 am
Format Command | article only | Jan 22, 2003 08:09 am
no effect with specifying _db_block_hash_buckets explicitly | article only | Jan 22, 2003 10:06 am
Procedure works but not when in a shutdown trigger | article only | Jan 22, 2003 01:50 pm
How to prevent users from changing table storage parameters | article only | Jan 23, 2003 09:53 am
Jobs | article only | Jan 23, 2003 10:19 am
Calculate fourth Thursday of a Month | article only | Jan 23, 2003 01:23 pm
Building a N-Tier app | article only | Jan 23, 2003 06:34 pm
Database link | article only | Jan 23, 2003 07:18 pm
privot table | article only | Jan 23, 2003 07:29 pm
oracle web application update | article only | Jan 23, 2003 08:18 pm
Question in Exception hanling | article only | Jan 23, 2003 09:04 pm
In-line view and rownum | article only | Jan 24, 2003 07:12 am
SELECT * v SELECT column_names | article only | Jan 24, 2003 07:35 am
Tablespace increased by resize datafiles seems not being used | article only | Jan 24, 2003 07:43 am
Rollback Segment | article only | Jan 24, 2003 08:16 am
requirements for functions on which function-based index can be created | article only | Jan 24, 2003 08:28 am
Months in year Query | article only | Jan 24, 2003 09:43 am
MAX,MIN | article only | Jan 24, 2003 11:26 am
Java SAP Connector | article only | Jan 24, 2003 12:54 pm
users wrongly dropped | article only | Jan 24, 2003 01:29 pm
Objects types ! ! | article only | Jan 24, 2003 02:16 pm
Matching an in-memory collection with a dataset | article only | Jan 24, 2003 06:03 pm
Tuning SQL statements (general) | article only | Jan 25, 2003 11:01 am
Trying to run /usr/bin/env from PL/sql using Java Stored Procedure | article only | Jan 25, 2003 11:05 am
forms locking question | article only | Jan 25, 2003 11:06 am
Several Doubts | article only | Jan 25, 2003 11:12 am
Hybrid Replication | article only | Jan 25, 2003 11:14 am
How to change the value of MAX_ENABLED_ROLES | article only | Jan 25, 2003 11:17 am
Index table space | article only | Jan 25, 2003 11:29 am
Read consistency accross cursors in one procedure | article only | Jan 25, 2003 12:04 pm
Upgrading to 8i | article only | Jan 25, 2003 03:28 pm
Unusable Index on a Partitioned table | article only | Jan 25, 2003 06:52 pm
How to enter into an INACTIVE state | article only | Jan 26, 2003 09:15 am
PeopleSpoft with RAC on Win2k | article only | Jan 26, 2003 09:40 am
Keep pool and multiple versions of blocks | article only | Jan 26, 2003 09:47 am
Questions | article only | Jan 26, 2003 09:50 am
Delete Function with utl_file | article only | Jan 26, 2003 09:51 am
How to verify impact of an index, without building the index | article only | Jan 26, 2003 12:24 pm
Returning Values Not In the Table | article only | Jan 26, 2003 06:46 pm
High CPU. Is there a way go GRACEFULLY limit CPU per user? | article only | Jan 26, 2003 08:10 pm
Cursors | article only | Jan 27, 2003 07:27 am
Performance Checklist | article only | Jan 27, 2003 07:42 am
Creating Single Row from Multiple Rows | article only | Jan 27, 2003 07:45 am
Redo logs -Archived logs - do you anyway lose data? | article only | Jan 27, 2003 07:50 am
about parsing | article only | Jan 27, 2003 08:40 am
Speed Factor (Partitioned Table) | article only | Jan 27, 2003 10:47 am
Objects in pl sql | article only | Jan 27, 2003 11:17 am
How to get real time trace info | article only | Jan 27, 2003 04:29 pm
location of ocijdbc8 on Solaris system | article only | Jan 27, 2003 07:04 pm
invalid objects | article only | Jan 27, 2003 07:07 pm
Logical Standby database | article only | Jan 27, 2003 07:10 pm
Case with in | article only | Jan 28, 2003 07:28 am
Multiple Database tiers for an application | article only | Jan 28, 2003 09:49 am
Few Questions relating to Parsing,Replication | article only | Jan 28, 2003 03:02 pm
Rollup/Cube and Percentages | article only | Jan 28, 2003 03:04 pm
how to get x to y rows in PL/SQL and ORA-00932 | article only | Jan 28, 2003 03:30 pm
About connect,resource and DBA | article only | Jan 28, 2003 04:30 pm
What is the best way to pass result sets between procedures (remote) | article only | Jan 28, 2003 08:02 pm
row(s) 1 - 60 of 60
January 22, 2003 Bala -- Thanks for the question regarding "Can XML Type tables be replicated ", version 9.2.0
You Asked
Hi Tom,

Thanks for your help for the Oracle User community.
I have a question regarding replication. I know that we can replicate
tables which are using LOB types. Does Oracle support replication of
tables which are using XML types.

Could you please confirm.

Thanks
and we said...
It works, see
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96567/repmaster.htm#39763
that second restriction does mean however that you have to use:

XMLTYPE xmlcolumn STORE AS CLOB

instead of

XMLTYPE xmlcolumn STORE AS OBJECT RELATIONAL

as the object relational one uses type inheritance and that is not supported.
Review & Followup
Rating: 5
So Object relational based XML types can not be replicated ? January 21, 2003
Reviewer: A reader
Tom,

Does it mean that i can not replicate
"Object Releational" based XML types
using advanced replication.

Or only inheritance is not supported.

Followup:
Inheritance is not supported in replication.
XMLTypes that use OR stores use inheritance.

If you use clob storage, replicate away.

Rating: 5
Thanks Tom. How to convert the storage model January 21, 2003
Reviewer: A reader
Tom,

Eventhough Replication is the drawback with "Object-Relation" storage based
XMLTypes, i was hearing that
performance point of view, it is better than "CLOB".

Can you list some advantages of "Object-Relational" storage  XMLType.

How to convert the existing "CLOB" based XMLTypes to "Object-Relational" storage
XMLTypes.

Thanks

Followup:
where'd you hear it and what did they say.  Did they benchmark it?  Did you?

Try it and see -- with your application.  We've played with it -- totally
depends on what you are doing.

Rating: 5
Here is the excerpt January 21, 2003
Reviewer: Bala
Following is the info. i got from one person. I don't know where he got from.
--
Oracle 9i can create a more efficient XMLTYPE column or table, when an XML
schema is provided and registered in the database. Normally XMLTYPE is stored
unparsed as CLOB data. When a schema is present, Oracle will pre-parse the XML
data into object-relational format and stored it that way. When queries are run
against a CLOB stored XML column, query execution cannot take advantage of query
rewrites. The existing tables can be converted to object-relational storage
option by using export/import per Oracle's claim in the release notes.
--

Followup:
Well, it all depends -- do you need relational access or does searching do it
for you?

You can index the XML clob type.  "find me all xml documents that have an
attribute "FOO" with a value "BAR"" -- very fast.

Return it to me as a relational set of rows -- not as fast (fast to find but
then you parse).

You need high speed ingest?  storing in OR-types will be much slower.
You need high speed relational access?  storing in OR-types will be faster.
You just need to ingest, index, search and retrieve?  clobs will be what you are
looking for.

As I said -- it all depends.  If one method were superior in every way shape and
form - well, I guess we would only supply one method.

January 22, 2003 -- Thanks for the question regarding "Returning to Yesterday's Data Using Copied Datafiles", version 8.1.6
You Asked
Hello Tom,

We have a data warehouse-like application that does data extraction every night
from Oracle Apps R11i. The application itself has its own datafiles to store the
extracted records. The user requires that in case the extraction failed the
application should provide the last valid set of data.

Is it possible to copy those datafiles every night to a safe place before the
extraction starts so that we can simply copy those files back to the original
directory in case a failure really occurs?

Or, is there any better solution?

Regards,
Benson
and we said...
Yes, it is called a BACKUP.  If you run in archivelog mode, just take a hot
backup every now and again.  If you want, you can do a point in time recovery to
"last night".

Else, if not in archive log mode, you would need to do a cold backup.

Else, you could use transportable tablespaces as well -- in no archivelog mode
-- in order to avoid having to shutdown the database.
Review & Followup
Rating: 3
Returning to Yesterday's Data Using Copied Datafiles January 21, 2003
Reviewer: Benson from Hong Kong SAR
Thank you for the advice! Sure, I need to spare some times to dwell on the
topic--BACKUP--which is the area I seldom touch. What concerns us most is the
time spent on recovering quite a large amount of data in a fairly limited time
in the moring.

Followup:
The fastest way to do it will be

a) backup
b) do your load
c) restore a in the event you want to -- which will take basically as long as it
takes to put the files back since there isn't anything to really recover.

January 22, 2003 -- Thanks for the question regarding "Format Command", version 8.1.6
You Asked
Hello, Tom

Can I use SQL*Plus format command like 'Break' in PL/SQL environment?
I was trying to create a list item using CREATE_GROUP_FROM_QUERY and wanted to
break on duplicate columns. Is that technically possible?
Thanks for helping!

Tina
and we said...
nope -- that is a sqlplus reporting directive only.

Using analytic functions  however ;)

scott@ORA817DEV> select decode( lag(deptno) over ( partition by deptno order by
ename ), deptno, to_number(NULL), deptno ) dno,
  2             ename
  3    from emp
  4   order by deptno, ename
  5  /

       DNO ENAME
---------- ----------
        10 CLARK
           KING
           MILLER
        20 ADAMS
           FORD
           JONES
           SCOTT
           SMITH
        30 ALLEN
           BLAKE
           JAMES
           MARTIN
           TURNER
           WARD

14 rows selected.

you can....
Review & Followup
Rating: 5
You forgot to mention... January 21, 2003
Reviewer: Arup Nanda from Norwalk CT USA
...that Analytic Functions rock!

Rating: 5
to_number(null) January 22, 2003
Reviewer: hrishy from PA
Hi Tom

This again is an excellent response ..but just wundering why did you cast the
null to a number..in the query when we could have written the query like this
..and it was easy to decipher

SQL> l
     select decode( lag(deptno) over ( partition by deptno order by
     ename ), deptno, NULL, deptno ) dno,ename
    from emp
SQL> /

DNO                                      ENAME
---------------------------------------- ----------
10                                       CLARK
                                         KING
                                         MILLER
20                                       ADAMS
                                         FORD
                                         JONES
                                         SCOTT
                                         SMITH
30                                       ALLEN
                                         BLAKE
                                         JAMES
                                         MARTIN
                                         TURNER
                                         WARD

Followup:
look how your deptnos are formatted versus mine.

Yours are strings, mine are numbers.

I wanted to preserve the original datatype -- hence the cast of the null to a
number.  decode's return datatype is the datatype of the FIRST return value.
NULL defaults to varchar2.

Rating: 5
to_numbe(NULL) to be Explicit ? January 22, 2003
Reviewer: pasko
Hi Tom ,

The trick you showed  us above s Great !

To the user above :

I think Tom used  to_numbe(NULL) inside the DECODE to avoid Implicit Conversions
, because all variables  inside Decode are assumed to be Varchar2's ..

Rating: 5
Thanks, Tom! January 22, 2003
Reviewer: Tina from Maryland, USA
This really works. You're great, Tom!

Rating: 5
April 24, 2003
Reviewer: Randy from Detroit
This works awesome. Been trying to figure this out for a while now!

January 22, 2003 Sean -- Thanks for the question regarding "no effect with specifying _db_block_hash_buckets explicitly", version 8.1.7
You Asked
Hi Tom,

Our database allocates 512 hash buckets for 2048 block buffers.  After
specifying _db_block_hash_buckets to a value ie. 256 in the parameter file and
restarting the database instance, the number of buckets still is 512 and stays
unchanged. I got this conclusion by selecting from v$latch_children. Is there
any possible reason which makes the explicit setting on this hidden parameter
not work?

SQL> show parameter bucket

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
_db_block_hash_buckets               integer 256
SQL> select count('*') from v$latch_children where latch# = 66;

COUNT('*')
----------
       512

Thanks
and we said...
because hidden parameters should only be set under the guidance of support as

o you don't know what side effects they might have
o you don't know what they really do
o you don't know if they still work the way you think they do
o you don't know what undocumented mins and maxs and thresholds they might have

that last point is your case.  So, don't do that.

My advice on undocumented parameters has been and always will be -- don't go
there.

Oh I know, you'll hate that advice -- people who play with them always do...
There are so many other things you could be doing instead.
Review & Followup
Rating: 3
The root reason why I wanna change the parameter January 21, 2003
Reviewer: Sean Wong from HK
Tom, thank you for your advice!

In this case, I may have to change it.

One of our servers has been experiencing the 'cache buffer chains' latch
contention problem. There are only 2,048 hash buckets for 185,000 cache buffers
in SGA. The number of buckets is not fourth of the number of the cache buffers.
I think the number of bucket is too small and it causes the latch contention a
lot. That's why I wanna change the hidden parameter to allocate more buckets to
lessen the contention. I understand the hidden parameter should not be changed
without the advice from Oracle Support. But... don't you think the number of the
hash buckets is too small?

Followup:
did you figure out why you have cache buffer chains contention?

how bad is bad (you do realize -- you will ALWAYS have waits)?

if you have a hot block -- guess what, don't care HOW many chains you got -- you
are not going to fix it.

Have you identified and isolated the cause and tried to fix it through "normal"
channels (eg: tuning of queries) first.....

No, I don't think it is too small.

Rating: 3
I always get 'latch free' in v$session_wait January 21, 2003
Reviewer: Sean Wong from HK
Tom, your comment is helpful. Now I know the figure, 2048 buckets for 185,000
buffers, is not small. But... why is it not fourth of 185,000 by default? Many
mentioned that the bigger the buffer cache, the more the hash buckets. Is that
ture? Seems not in this case.

I always get 'latch free' waits in the system. When looking into it, I am told
it's 'cache buffer chains' contention. I understand some hot buffers will cause
this kind of contention also, no matter how many buckets. In our system, I do
believe the problem is caused by the hot buffers from time to time. However,
apart from the hot buffers, I thought the problem was also caused by the long
chains. I will look into the queries and try to fix it via a normal channel.

Followup:
You will always get some waits.  If you don't, give me a call -- I'd LOVE to see
it.

question is: are these waits causing you any significant performance issues?
eg: take the amount of waits in a 15 minute period, now divide by the average
number of concurrent sessions in that 15 minute period.  Now, look at that
number and ask yourself "is that big?"

question is: do you have a real performance problem -- or are you suffering from
CTD -- compulsive tuning disorder.

You do realize that 185000/2048:

ops$tkyte@ORA920> select 185000/2048 from dual;

185000/2048
-----------
 90.3320313

that is only 91 dba's per bucket on average...  somewhere between 1gig and
1.5gig - we flip from 1024 to 2048 cbc child latches.  it is the expected,
normal size.

Rating: 5
Reply to Sean Wong from HK January 21, 2003
Reviewer: Wolfgang Breitling from Calgary, Canada
"cache buffer chains" latch waits are, in my experience, a symptom of
inefficient sql and not of too few hach buckets and consequently my advice to
you is find and cure the cause rather than try and treat the symptom.

Rating: 5
Different figure in another database January 22, 2003
Reviewer: Sean Wong from HK
Hi Tom,

One more question. In another 8.0.5 database, same number of the cache buffers
(185,000), 46261 buckets have been allocated without any explicit setting. Why?
I just wanna have a deeper view. Thanks.

Followup:
what query are you using -- watch that latch# -- it is not a constant...

ops$tkyte@ORA806> select * from v$latchname where name = 'cache buffers chains';

    LATCH# NAME
---------- ------------------------------
        26 cache buffers chains

ops$tkyte@ORA817DEV> select * from v$latchname where name = 'cache buffers
chains';

    LATCH# NAME
---------- ------------------------------
        66 cache buffers chains

ps$tkyte@ORA734> select * from v$latchname where name = 'cache buffers chains';

    LATCH# NAME
---------- ------------------------------
        11 cache buffers chains

Rating: 3
Here are the queries I am using January 22, 2003
Reviewer: Sean Wong from HK
SQL> select * from v$latchname where name = 'cache buffers chains';

    LATCH# NAME
---------- ----------------------------------------------------------------
        26 cache buffers chains

SQL> select count(1) from v$latch_children where latch# = 26;

  COUNT(1)
----------
     46261

Followup:
so, the algorithms change between releases, what we do is different. (this is
also the poster child example of why using _ parameters is a truly horrible
idea).

Ignore it -- fix the cause (you still haven't answered any of MY questions -- we
don't even know if this is a problem and even if it were -- the fix lies in the
code, not some magic silver bullet)

I see the same thing on my 8.0 versus 8i instance.  It is correct.

Rating: 5
Now I understand the difference about this between releases January 22, 2003
Reviewer: Sean Wong from HK
Tom, thank you for your comprehensive answers.

Actually, I didn't hear any complaint on the database performance from users. I
just saw the cbc wait happen much more often than in the other DBs. That made me
interested in digging out.

Most of times, the cbc waits in our database are caused by the hot buffers. I
don't see other performance issues.

Anyways, thank you again!

January 22, 2003 -- Thanks for the question regarding "Procedure works but not when in a shutdown trigger", version 9.2
You Asked
I have a procedure that analyze a couple of tables on startup of the database.
The procedure works fine but I have placed this into a trigger to fire on
startup of the database but I am getting

ORA-00604: error occurred at recursive SQL level 1
ORA-30511: invalid DDL operation in system triggers
ORA-06512: at "SYS.ANALYZE_TAB", line 3
ORA-06512: at line 2

The trigger code is

trigger analyze_tables after startup on database
begin
analyze_tab;
END;

Procedure code is

PROCEDURE analyze_tab AS
BEGIN
EXECUTE IMMEDIATE('ANALYZE TABLE LIB.TRADES COMPUTE STATISTICS');
EXECUTE IMMEDIATE('ANALYZE TABLE LIB.BONDS COMPUTE STATISTICS');
EXECUTE IMMEDIATE('ANALYZE TABLE LIB.CLIENT_ADDRESS COMPUTE STATISTICS');
END;

Please Help
and we said...
Well, the error seems fairly clear:

[tkyte@tkyte-pc tkyte]$ oerr ora 30511
30511, 00000, "invalid DDL operation in system triggers"
// *Cause:  An attempt was made to perform an invalid DDL operation
//          in a system trigger. Most DDL operations currently are not
//          supported in system triggers. The only currently supported DDL
//          operations are table operations and ALTER?COMPILE operations.
// *Action: Remove invalid DDL operations in system triggers.
[tkyte@tkyte-pc tkyte]$

It seems most bizzare to want to analyze upon startup since you would only
startup after a system failure -- so it could be months or years between
analyzes.  (because we all know you don't shutdown to back up or anything like
that!)  Best to use DBMS_JOB to automate such a process - suggest you

a) alter these tables "monitoring"
b) use dbms_stats
c) use the GATHER_STALE option

and schedule this to run on a recurring basis....
Review & Followup
Rating: 4
What is the alter table monitoring January 22, 2003
Reviewer: Craig Richards from England
I not to sure what this does?

Followup:
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/statements_32a.htm#2085223
it causes us to watch modifications to a table so we know when about 10% of it
has been modified so that when you gather stats, you only gather them on objects
that actually NEED them instead of all objects in the schema....

      Rating:
      5
Brilliant  January 22, 2003
Reviewer:
      Craig Richards  from London, England
Thanks very much for the quick response

January 23, 2003 Michael -- Thanks for the question regarding "How
      to prevent users from changing table storage parameters", version 8.1.7

      You Asked
Greetings,

Is there any way to allow users to create tables but prevent them from adding a
storage clause?

Our developers insist on adding a storage clause to new tables which allocates
HUGE chunks of space to the table.  They believe this prevents fragmentation of
the table / tablespace.  I have argued that this just is not the case, but they
won't listen.  So, is there any way to allow them to create a table but not use
a storage clause?

All of our tablespaces are LMT.

Regards,
Mike

      and we said...
why are you granting create table to the developers -- revoke it and tell them
when they pass the DBA test and know what they are talking about -- they can
create tables again.  Until then, creates would have to go through you.

Am i kidding?  no, not really.  You need to get some semblance of control over
the infrastructure there....

In 9i, you could use a CREATE Trigger to inspect the DDL they are executing, and
fail it if it has a storage clause.

In 8i, you could use a create trigger to schedule a job to dellocate unused
extents right afterward:

alter table T deallocate unused keep 1k;

as well.

January 23, 2003 Eduardo -- Thanks for the question regarding
      "Jobs", version 8.1.6

      You Asked
Hi Tom:

I have a Store Procedure (SP_INSERT_ORDER) that insert records (orders) to
Oracle and AS/4000 (we have Oracle transparent gateway).

SP_INSERT_ORDER submit a Job (because the user don't want to wait, this reduce
the time a lot) that execute another SP and this insert the order to AS/400.

The problem is that some times the ORDER is available in Oracle (in our
aplication), but not en JDE (AS/400) because the job not finished yet, then when
the supplier confirm the order we execute another SP that update status in
Oracle and submit a Job for update status en JDE(AS/400).

Is there any form for run the Jobs in dependent form?

Example:
Submit Job1 at 12:00
Submit Job2 at 12:02 execute only if Job1 is finished
 Current time 12:02 but Job1 is not finished.

      and we said...
You would have to sort of "do this yourself" -- or run just one
job_queue_process.

One idea would be for your procedure (job) that does the update_status "requeue"
itself if the data is not present in the AS/400 machine yet.  eg:

create procedure update_status( p_job in number, p_next_date in out DATE )
is
...
begin
    if ( record does not exist in as/400 )
    then
       p_next_date := sysdate + 1/24/60; -- one minute from now
       return;
    end if;
    ....... existing logic .....
end;

and schedule this job as:

  dbms_job.submit( l_job, 'update_status( JOB, NEXT_DATE );' );
  insert into update_status_parameters
  ( jobid, data.... )
  values ( l_job, data..... );

Review &
      Followup

      Rating: 5
Very goog
      idea  January 23, 2003
Reviewer:  Eduardo
      Campos  from Monterrey, NL, Mexico
Thanks Tom

January 23, 2003 Cindy -- Thanks for the question regarding
      "Calculate fourth Thursday of a Month", version 8.1.7

      You Asked
Is there a simple way using SQL Plus to find out what is the fourth Thursday of
a given month?

      and we said...
ops$tkyte@ORA817DEV> alter session set nls_date_format = 'Dy Month DD, YYYY';
Session altered.

ops$tkyte@ORA817DEV> select dt,
  2             next_day( (trunc(dt,'mm')-1), 'THU' ) + 21
  3    from (select add_months( to_date('31-dec-2002','dd-mon-yyyy'), rownum )
dt
  4                    from all_objects
  5                   where rownum <= 12 )
  6  /

DT                     NEXT_DAY((TRUNC(DT,'MM
---------------------- ----------------------
Fri January   31, 2003 Thu January   23, 2003
Fri February  28, 2003 Thu February  27, 2003
Mon March     31, 2003 Thu March     27, 2003
Wed April     30, 2003 Thu April     24, 2003
Sat May       31, 2003 Thu May       22, 2003
Mon June      30, 2003 Thu June      26, 2003
Thu July      31, 2003 Thu July      24, 2003
Sun August    31, 2003 Thu August    28, 2003
Tue September 30, 2003 Thu September 25, 2003
Fri October   31, 2003 Thu October   23, 2003
Sun November  30, 2003 Thu November  27, 2003
Wed December  31, 2003 Thu December  25, 2003

12 rows selected.

Review &
      Followup

      Rating:
      5
Elegant  January 23, 2003
Reviewer:
      pawan  from USA

      Rating: 5
Calculate
      fourth Thursday of a Month  January 23, 2003

Reviewer:  Rich  from NJ
Thanks for the answer. I went about it a little differently, using analytic
functions and had a question.

I got the same answer you did writing it this way:

select DT, DAY, SEQ
from (select dt, rj, DAY,
             row_number() over (partition by to_char(dt,'mm') order by dt) "SEQ"
      from (select trunc(sysdate,'year')+rownum-1 dt,
             to_char(trunc(sysdate,'year')+rownum-1, 'd') rj,
             to_char(trunc(sysdate,'year')+rownum-1, 'Day') "DAY"
            from all_objects
            where rownum <= 365) rs
      where rj = '5')
where SEQ = '4'

DT        DAY              SEQ
--------- --------- ----------
23-JAN-03 Thursday           4
27-FEB-03 Thursday           4
27-MAR-03 Thursday           4
24-APR-03 Thursday           4
22-MAY-03 Thursday           4
26-JUN-03 Thursday           4
24-JUL-03 Thursday           4
28-AUG-03 Thursday           4
25-SEP-03 Thursday           4
23-OCT-03 Thursday           4
27-NOV-03 Thursday           4
25-DEC-03 Thursday           4

12 rows selected.

Now, when I write it like this to try and avoid a level of nesting it doesn't
work:

select dt,
       rj,
       DAY,
       SEQ
from (select trunc(sysdate,'year')+rownum-1 dt,
             to_char(trunc(sysdate,'year')+rownum-1, 'd') rj,
             to_char(trunc(sysdate,'year')+rownum-1, 'Day') "DAY",
             row_number() over (partition by
to_char(trunc(sysdate,'year')+rownum-1, 'mm') order by
trunc(sysdate,'year')+rownum-1) "SEQ"
            from all_objects
            where rownum <= 365
              and to_char(trunc(sysdate,'year')+rownum-1, 'd') = '5'
            ) rs
where seq = '4'

The "and to_char(trunc(sysdate,'year')+rownum-1, 'd') = '5'" within the inline
statement doesn't seem to work. Can you let me know why?

Thanks.

          Followup:

like an atom bomb to crack a walnut isn't it?

given any date DT:

 next_day( (trunc(dt,'mm')-1), 'THU' ) + 21

answers the question....

You cannot remove a level of nesting -- especially with analytics.  they work on
the results AFTER the predicate -- it would totally change the meaning.

Sort of like the difference between:

select * from ( select * from t order by x ) where rownum < 5;

gets the first rows AFTER sorting where as

select * from t where rownum < 5 order by x

gets five rows AND THEN sorts just those 5.

Apples and oranges

      Rating: 4
calculate
      fourth thursday of November  January 23, 2003

Reviewer:  Cindy  from Denver, CO
thanks for the help..

to take this a couple of steps further I would like to find just the 4th
Thursday of November, basically Thanksgiving and I would ultimately like to have
this available to do in Oracle Forms..any ideas?

I did the following, but all_objects is only available for DBA..any other way?

   select trunc(sysdate,'year')+r-1
   from (select rownum r from all_objects where rownum <= 366)
  where to_char(trunc(sysdate,'year')+r-1,'Mon')=('Nov')
   and to_char(trunc(sysdate,'year')+r-1,'Dy')=('Thu')
    and to_char(trunc(sysdate,'year')+r-1,'W')=(4);

          Followup:

  1  select
  2   next_day( (trunc(to_date('nov','mon'),'mm')-1), 'THU' ) + 21
  3* from dual
ops$tkyte@ORA920> /

NEXT_DAY(
---------
27-NOV-03

does that.

All_objects is available to ALL USERS -- if not, your database is NOT SUPPORTED.

      Rating:
      5
  January 23, 2003
Reviewer:  Cindy
       from Denver
Thanks!

January 23, 2003 Mike -- Thanks for the question regarding
      "Building a N-Tier app", version 8.1.7

      You Asked
How do I use the role level security if the web based app is using a generic
user. I want Oracle to use the user's client/server app id so it will work w/th
role level sercurity..

Meaning the weblogic apps server has a generic super user login and uses a
connection pool. I want the procedures to use the real users id to handle the
role level security..but I do not know how.

      and we said...
Funny you should ask -- Cameron O'Rourke at devtrends.oracle.com just finished
his column for Oracle Magazine and it is exactly on this topic:

http://devtrends.oracle.com/articles/devtrends_0305_nTierAuth.html

January 23, 2003 rajeev -- Thanks for the question regarding
      "Database link", version oracle8.1.5

      You Asked
Hi Tom
I wanted to connected two oracle database which r distributed as
they have same version oracle8.1.5 ?
I have tried to connect but getting following errors.I followed
the following steps to connect the database

STEP1
SQL>  create database link raani
  2  connect to scott identified by tiger
  3  using 'raani';

Database link created.

STEP2

SQL> select ename
  2  from raani.emp@raani;
from raani.emp@raani
               *
ERROR at line 2:
ORA-12154: TNS:could not resolve service name

SQL> SELECT * FROM GLOBAL_NAME;

GLOBAL_NAME
--------------------------------------------------------------
ORACLE

SQL> show parameter global_name

NAME                                 TYPE    VALUE
------------------------------------ ------- -----------------
global_names                         boolean TRUE

global_name paramete is true for this

thanks in advance

bye
raut

      and we said...
on the SERVER (not your client) can you go:

sqlplus user/pass@raani

raani must be a TNSNAMES entry on the SERVER, not on the client.
 Make sure the tnsnames entry is there, right now according to
the error message -- it is not.
Review &
      Followup

      Rating:
      4
tnsnames  March 11, 2002
Reviewer:
      mohammad taha  from dubai,UAE
The tnsnames entry should be always from where we want to connect.Suppose,I want
to connect from A,client to B,server.Then I must I have the tnsnames entry on A
not on B.
Am I correct?

If I never connect to any other database from my Server,B then I may not have a
tnsnames.ora file even on B.Correct?

          Followup:

when using a DBLINK -- the tnsnames.ora entry must be on the server, the
database server (that is where the link starts from, the database server).

So, if you have a PC, server a, server b.    You use sqlplus to connect from PC
to server a -- you need a tnsnames.ora file on the PC that has the connection
info describing how to connect to server a.  Now, whilst logged onto server a
from PC -- you open a dblink to server b.  Now you need a tnsnames.ora entry ON
SERVER A that tells server a how to connect to server b.

      Rating:
      4
  March 12, 2002
Reviewer:  Drew
       from Travose PA
When I queries data from a remote database through DB links, I can always find a
live "transaction" by querying v$session and v$transaction views.  Since it's
not a real transaction, I don't want to see it as a transaction in
v$transaction.  What I did was issueing a commit or rollback statement.  Is that
the right way to do it?  This way will cause some errors in applications when
some real transactions be commited or rolled even though we don't want to.
Thanks.

          Followup:

you can either "commit/rollback" or close the dblink.

I don't see what the heartburn with having an entry in v$transaction is
personally.  It is 100% harmless, normal and expected.  I would personally
ignore it and NOT commit/rollback -- for as you say, its the WRONG thing to do.

      Rating: 3
Need
      Clarification  March 12, 2002
Reviewer:  selva
      muthukumar  from Mumbai, India
Hi,
     As you said in this case that tnsnames should be on server, but again in
explanation of PC, Server A, Server B u had explained that PC ---> A means
tnsnames should be on PC.

     That means in our question Tnsnames entry should be on client (raani
machine assume).(ie) it is on client machine.

     If i had intrepreted it wrong please help me to get it.

Thanks
selva

          Followup:

the tnsnames.ora should always be available to the client.

Sometimes the client is the "PC", sometimes the client is the server.

In my example, the PC is the client of server a.  Server a is the client of
server b.

      Rating: 3
oracle7.3 to
      oracle8.1.7  November 09, 2002
Reviewer:
      Victor  from Russia
Hello,Tom
I would like to go from Oracle7 to Oracle8.1.7.
They are on the same machine. Software of Oracle8.1.7.
Listener and others also.
But I met a trouble in impossibility to create any
working db link from Oracle7.3 to oracle8.1.7 (with
Oracle8.1.6 it is OK).
I need it for the some real tests ahead of migration.
I have an error :
Connected to:
Oracle7 Server Release 7.3.2.2.0 - Production Release
With the distributed option
PL/SQL Release 2.3.2.2.0 - Production

SQL>  select * from all_users@plu8.world;
 select * from all_users@plu8.world
                         *
ERROR at line 1:
ORA-02068: following severe error from PLU8
ORA-03113: end-of-file on communication channel

Could you give me some tip how,how to connect the servers?
Whether it is impossible ?
Thanks in advance.

          Followup:

Sorry -- but 7.3.2.2 hasn't been even remotely supported or tested for many many
years.

I'm not aware of any specific issues but I'm sure they abound.  Don't know what
to tell you here -- that would be a configuration we never tested with.

7.3.4 to 8.1.7 -- that'll work fine.  All other bets are off though.

      Rating:
      3
  January 23, 2003
Reviewer:  John

Hi Tom,

I tried to access from oracle 9.0.1 to oracle 7.1.5 using database link.
everything is fine if I just use
   insert into table_b select * from table_a@reomte through sqlplus.

however, I always got error (ORA-03113: end-of-file on communication channel) if
I try to compile a package which has same statement into an package. I can
compile the package without the statement above.

Any idea?

Thanks.

          Followup:

sorry -- nobody even dreamed of testing this configuration.

And -- I know for a fact, 7.x -> 9iR2 -- is not in the cards at all.  doesn't
work in general (and won't)

January 23, 2003 Jack -- Thanks for the question regarding "privot
      table", version 8173

      You Asked
Tom,
I have table :

Name                            Null?    Type
------------------------------- -------- ----
ROUTE                           NOT NULL CHAR(10)
WO_NBR                          NOT NULL CHAR(10)
...
DATE_REF                                 DATE
DATE_FIRM                                DATE
DATE_ALLOC                               DATE
DATE_FST_RCV                             DATE
DATE_LST_RCV                             DATE
DATE_PLAN                                DATE
...

I want the query out as

all the DATE_ convert  one new column STATUS_DT with
if DATE_REF then new column STATUS_DSC = REF
if DATE_FIRM then new column STATUS_DSC = FIRM
if DATE_ALLOC then new column STATUS_DSC = ALLOC
if DATE_FST_RCV then new column STATUS_DSC = FST_RCV
if DATE_FST_PLAN then new column STATUS_DSC = PLAN

ROUTE WO_NBR DATE_REF DATE_FIRM DATE_ALLOC
--------------------------------------------
E454  WQ      01-03-01 01-04-01 01-05-01

Wanted to be as:

ROUTE WO_NBR STATUS_DT STATUS_DSC
-------------------------------------
E454  WQ      01-03-01  REF

E454  WQ      01-04-01  FIRM

E454  WQ      01-05-01  ALLOC

I know it is possible using the union all to do it,
 but I would know if other method (no pl/sql)?

      and we said...
select route, wo_nbr,
       decode( status_dsc, 'REF', date_ref, 'FIRM', date_firm, .... ) status_dt,
       status_dsc
  from your_table,
       (select 'REF' status_dsc from dual union all
        select 'FIRM' status_dsc from dual union all
        ...
        select 'PLAN' status_dsc from dual )

will do that (or just put the values for status_dsc in a table and select * from
that table instead of using dual)

Review &
      Followup

      Rating:
      5
  January 20, 2003
Reviewer:  Jack

      Rating:
      3
  January 23, 2003
Reviewer:  DEE
       from Indiana
I can tell it will result the CARTESIAN products.
Which one performs better?

1)
select DATE_REF STATUS_DT ,'REF' status_cd
from table
union all
select DATE_FIRM STATUS_DT ,'FIRM' status_cd
from table
...
2)
select route, wo_nbr,
       decode( status_dsc, 'REF', date_ref, 'FIRM', date_firm, .... ) status_dt,
       status_dsc
  from your_table,
       (select 'REF' status_dsc from dual union all
        select 'FIRM' status_dsc from dual union all
        ...
        select 'PLAN' status_dsc from dual )

          Followup:

there is nothing *wrong* with a cartesian product.  Nothing.

If you want to make a single pass on YOUR_TABLE, you will do it my way -- the
"cartesian product" way.

      Rating:
      3
  January 23, 2003
Reviewer:  DEE
       from Indiana
create table t as select * from dba_objects
/
sql> select timestamp, owner obj_dt, 'OWNER_N' obj_dc
  2  from t
  3  union all
  4  select  timestamp, OBJECT_NAME obj_dt, 'OBJECT_N' obj_dc
  5  from t
  6  union all
  7  select timestamp, status obj_dt, 'status' obj_dc
  8  from t
  9  union all
 10  select timestamp, OBJECT_TYPE obj_dt, 'OBJECT_T' obj_dc
 11  from t
 12  /

149088 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   UNION-ALL
   2    1     TABLE ACCESS (FULL) OF 'T'
   3    1     TABLE ACCESS (FULL) OF 'T'
   4    1     TABLE ACCESS (FULL) OF 'T'
   5    1     TABLE ACCESS (FULL) OF 'T'

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      16735  consistent gets
       1345  physical reads
          0  redo size
    7645742  bytes sent via SQL*Net to client
     984235  bytes received via SQL*Net from client
      14910  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     149088  rows processed

sql> select timestamp,
  2  decode(obj_dc, 'OWNER_N', owner,'OBJECT_N',OBJECT_NAME
  3  , 'STATUS', status,'OBJECT_T', OBJECT_TYPE) obj_dt
  4  , obj_dc
  5  from
  6  t
  7  ,(select 'OWNER_N' obj_dc from dual union all
  8  select 'OBJECT_NAME' obj_dc from dual union all
  9  select 'STATUS' obj_dc from dual union all
 10  select 'OBJECT_TYPE' obj_dc from dual
 11  );

149088 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     VIEW
   3    2       UNION-ALL
   4    3         TABLE ACCESS (FULL) OF 'DUAL'
   5    3         TABLE ACCESS (FULL) OF 'DUAL'
   6    3         TABLE ACCESS (FULL) OF 'DUAL'
   7    3         TABLE ACCESS (FULL) OF 'DUAL'
   8    1     TABLE ACCESS (FULL) OF 'T'

Statistics
----------------------------------------------------------
         29  recursive calls
          0  db block gets
      16751  consistent gets
       1274  physical reads
          0  redo size
    6883901  bytes sent via SQL*Net to client
     984235  bytes received via SQL*Net from client
      14910  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     149088  rows processed

sql>

The performances are about the same

          Followup:

Yes, yet another reason to move away from the RBO.... Consider (going to use a
small analyzed table instead of dual tho)

ops$tkyte@ORA920> create table small_t
  2  as
  3  select 'OWNER_N' obj_dc from dual union all
  4  select 'OBJECT_NAME' obj_dc from dual union all
  5  select 'STATUS' obj_dc from dual union all
  6  select 'OBJECT_TYPE' obj_dc from dual
  7  /

Table created.

ops$tkyte@ORA920> analyze table small_t compute statistics for table;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t as select * from all_objects
  2  /

Table created.

ops$tkyte@ORA920> analyze table t compute statistics for table;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA920> set autotrace traceonly ;
ops$tkyte@ORA920>
ops$tkyte@ORA920> select timestamp, owner obj_dt, 'OWNER_N' obj_dc
  2  from t
  3  union all
  4  select     timestamp, OBJECT_NAME obj_dt, 'OBJECT_N' obj_dc
  5  from t
  6  union all
  7  select timestamp, status obj_dt, 'status' obj_dc
  8  from t
  9  union all
 10  select timestamp, OBJECT_TYPE obj_dt, 'OBJECT_T' obj_dc
 11  from t
 12  /

118260 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=168 Card=118260 Bytes=2779110)
   1    0   UNION-ALL
   2    1     TABLE ACCESS (FULL) OF 'T' (Cost=42 Card=29565 Bytes=827820)
   3    1     TABLE ACCESS (FULL) OF 'T' (Cost=42 Card=29565 Bytes=827820)
   4    1     TABLE ACCESS (FULL) OF 'T' (Cost=42 Card=29565 Bytes=473040)
   5    1     TABLE ACCESS (FULL) OF 'T' (Cost=42 Card=29565 Bytes=650430)

Statistics
----------------------------------------------------------
        102  recursive calls
          0  db block gets
       9418  consistent gets
          0  physical reads
          0  redo size
    4076495  bytes sent via SQL*Net to client
      87212  bytes received via SQL*Net from client
       7885  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     118260  rows processed

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select timestamp,
  2  decode(obj_dc, 'OWNER_N', owner,'OBJECT_N',OBJECT_NAME
  3  , 'STATUS', status,'OBJECT_T', OBJECT_TYPE) obj_dt
  4  , obj_dc
  5  from
  6  t
  7  , small_t
  8  /

118260 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=170 Card=118260 Bytes=8041680)
   1    0   MERGE JOIN (CARTESIAN) (Cost=170 Card=118260 Bytes=8041680)
   2    1     TABLE ACCESS (FULL) OF 'SMALL_T' (Cost=2 Card=4 Bytes=28)
   3    1     BUFFER (SORT) (Cost=168 Card=29565 Bytes=1803465)
   4    3       TABLE ACCESS (FULL) OF 'T' (Cost=42 Card=29565 Bytes=1803465)

Statistics
----------------------------------------------------------
         22  recursive calls
          0  db block gets
        415  consistent gets
          0  physical reads
          0  redo size
    3358329  bytes sent via SQL*Net to client
      87212  bytes received via SQL*Net from client
       7885  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
     118260  rows processed

ops$tkyte@ORA920>

not the same at all.

January 23, 2003 Munzer -- Thanks for the question regarding
      "oracle web application update", version 8i

      You Asked
Tom:

I have a simple update statement in a PL/SQL procedure that updates a record.
IT works fine, however when I add the "WHERE" clause to make sure I only update
when no user updated the record while I am updating it fails.  I was wondering
whether it was due to updating a null column since you have to use "is" instead
of "=".

create or replace procedure do_update (
  p_log_no    VARCHAR2  DEFAULT NULL,
  p_old_name  VARCHAR2 DEFAULT NULL,
  p_new_name  VARCHAR2 DEFAULT NULL,
  p_old_DOB   VARCHAR2 DEFAULT NULL,
  p_new_DOB   VARCHAR2 DEFAULT NULL,
  p_old_note  VARCHAR2 DEFAULT NULL,
  p_new_note  VARCHAR2 DEFAULT NULL )
IS
v_log_no NUMBER(10);
v_DOB    DATE;
Begin
v_log_no:=to_number(p_log_no);
v_old_DOB:=to_date(p_old_dob,'MM-DD-YYYY');
v_new_dob:=to_date(p_new_dob,'MM-DD-YYYY');

  update intreq set name = p_new_name,
                    dob  = v_new_dob,
                    note= p_new_note
       WHERE log_no  =  v_log_no and
             rtrim(name) = rtrim(p_old_name) and
             dob = v_old_dob    and
             rtrim(note) = rtrim(p_old_note);

2. I saw in an oracle web book that you can use
  select ename,job into dbename,dbjob from emp where empno=empnumber for update
nowait;

to lock the row.  Then they look for ORA-000054 to tell other users that record
is locked.

 I though in a web environemnt you can not really lock rows as the connection is
maintained per page only.

can you do it?

THank you,

      and we said...
1) please define "fails" for me.  what exactly are your symptons.

Yes, if any of p_old_name, v_old_dob, p_old_note are NULL, the equals will never
be true (it won't be false either, it'll be UNKNONW)

I would

1) select the rowid out in the procedure that calls this procedure.  Use
htf.escape_sc( rowid ) when you save it as a hidden field on the form as the
rowid contains +'s and other characters that can mess up GET or POST data.

2) code the update as:

   update t set c1 = p_new_c1, c2 = p_new_c2
     where rowid = p_rowid
       and (c1 = p_old_c1 OR (c1 is null and p_old_c1 is null))
       and (c2 = p_old_c2 OR (c2 is null and p_old_c2 is null));

As for the select for update, in a stateless environment you cannot use it to
lock rows from page to page, this is true.  You can (and when necessary should)
use it in your transactions to prevent concurrent updates to the same record.
There are still times when you might need it in a single atomic transaction --
to read the data out, lock it, update something else, then come back and update
the record you just read out for example.

Review &
      Followup

      Rating: 5
Update on web
      application  January 12, 2002
Reviewer:
      Munzer  from Reston, VA USA
Tom:

1.  What i mean by fail is that it does not do an update for the record.
What is" htf.escape_sc(rowid) " used for?  Can't I use the primary key for the
record instead?

2.  Can you also do the "WHERE clause" in an if statement before you do the
update such as:

If name=p_old_name and dob=v_old_dob then UPDATE ..... ;

3. You say I should lock record to prevent updates to the same record.  Does
this apply when I am using "STATEFUL" web connection.  I thought in a
HTML/PL-SQL application you can never lock record in any way.

If this is the case then do i need a "WHERE" clause in my update statement since
I am locking record anyway?

Can I test this locking feature on the web?do you know of any documentation or
examples on this subject?

Thanks

          Followup:

1) you can use the primary key -- but rowid is faster then primary key, rowid is
always there, rowid is a known length, rowid is reliable.

htf.escape_sc() is used to escape special characters, things that will break
HTML if you don't escape them.  If you are programming web stuff with HTP and
don't know about this -- you are in BIG trouble.

For example, insert a row into a table that has this in a string:

  insert into t values ( 'This is what a sqlplus prompt looks like "SQL>" ' );

Now, create a form to edit that!  The > is going to mess things up horribly.  If
we htf.escape_sc before we print out using htp:

ps$tkyte@ORA817DEV.US.ORACLE.COM> select htf.escape_sc( 'This is what a sqlplus
prompt looks like "SQL>"' )
  2  from dual
/  3

HTF.ESCAPE_SC('THISISWHATASQLPLUSPROMPTLOOKSLIKE"SQL>"')
---------------------------------------------------------------------------------
--------------------------------------------------
This is what a sqlplus prompt looks like &quot;SQL&gt;&quot;

it ESCAPES things that will mess up HTML -- quotes, >, +, whatever...

You need to revisit ALL of your code to make sure you are escaping text before
putting it in HTML else you run the risk of having a very bad thing happen to
your data someday (lost data, incorrect data, etc)

2) I fail to see where an if statement comes into play.  We don't have any name,
dob variables to compare to???

again, you would need to add the OR condition for NULLs as well.

3) In an HTML plsql application - the second you updated that record, you locked
it.  Its locked until your transaction ends.  There is no difference here.  What
you cannot do in a STATELESS environment is lock a record in one page, send the
page back to the browser and expect that record to remain locked.

You need a where clause in an update to avoid updating records you don't mean to
update.

There is no "locking feature for the web", locking is NO DIFFERENT on the web
then anywhere else.  It is just that on the web -- you typically do not maintain
state, hence you do not maintain locks, hence you must use a technique such as
above when updating to ensure the data did not change.

If you have my book -- i do go into great detail on this.  This technique you
are doing is known as OPTIMISTIC locking.  In a client server environment with a
stated connection we would typically use  PESSIMISTIC locking instead.  I cover
how to do it, what it is and more in the book.

      Rating: 5
htf.escape_sc(
      )   January 13, 2002
Reviewer:  Munzer
       from Reston, VA USA
Tom:

1.  When you say it is faster to update using rowid rather than primary key, are
we talking about milliseconds.  let us say record length is 500 characters?

2.  I have not had a problem not using htf.escape( ) because maybe I was not
testing data entry with special characters.

However, are you saying to use it always before you do "SELECT", INSERT, UPDATE,
or DELETE.

If you do it before insert then it will it still save those characters in the
database?

THank you

          Followup:

1) if you use the primary key the access plan will probably be:

  step 1) index range scan (unique) on the primary key index
  step 2) using the rowid we just found in step 1, table access by rowid

using the rowid, we can simply do step 2, go directly to the data.

2) correct -- the first time you get any data with quotes, &, >, <, etc -- you
will have grievous problems.

You use HTF.ESCAPE_SC when you are calling htp.p -- after you selected, as you
are generating the page.  It is an HTML formatting issue.  Just do it when you
print the page and thats it.

      Rating: 5
Followup on
      htf.escape_sc  January 14, 2002
Reviewer:
      Munzer  from Reston, VA
Tom:

I did try inserting values of '> and <' in a table and retrieved it later using
htp.p and it came fine.  However if you use it in " " it will not give you data.

However, I am a little ocnfused on how and when you use this function.

If you are inserting data from a web screen, I guess you do not use use it
unless you are worried about passing the data from the first page to the 2nd
page in the URL.
IF user enters (&, >, <, + ) it seems it goes fine to  the insert PL/SQL
program.  If you want some values hidden would you use?
HTP.FormHidden('dept',htf.escape_sc(p_dept)); -- to pass hidden values
HTP.FormText('p_name',20,20,htf.escape_sc(p_name); --
to pass unhidden values?

2. If you are updating data and you select the record from the database then are
you saying that you always use the function like:

FOR x in (Select ... from tab )
LOOP
htp.p(htf.escape_sc(x.col1) );
END

Is this correct?

3.  Do you know of any examples reference on using this package?

Thanks,

          Followup:

sigh, cause you can do it once does NOT mean it works.

Trust me -- if your text can have <, >, and other special characters in it --
you are heading for DISASTER unless you escape it.

Consider if the value in the database is:

   blah blah " >  blah blah blah

and you do this:

   htp.p( '<input type=text value=" || p_variable || '">' );

Normally, that'll print out:

   <input type=text value="Your Value">

But it can now print out:

  <input type=text value="blah blah " > blah blah blah blah ">

See that THAT does to your form!!!  the proper way to print that out is:

  <input type=text value="blah blah &quot; &gt; blah blah blah blah ">

If the USER enters &, <, > etc -- it goes fine cause the BROWSER encodes the
data on the way up and the app server is responsible for decoding it (so you'll
never get &gt; unless that is what the END USER typed in.

If you think this sort of data never gets entered -- just look at this followup
-- if we did NOT escape this text, you would not be reading this -- it would be
garbage (do a view source on this page to see it escaped).

I would say in generate that ALL calls to htp.formXXXXXX should use
htf.escape_sc on the text to protect yourself.

2) yes.

3) the documentation?

      Rating: 4
Is there
      support for 'umlaut's?  February 20, 2002

Reviewer:  Dieter  from Germany
Hello, Tom

Oracle 8.1.7#

I tried to encode several other characters as well using htp.escape_sc, mostly
German umlauts.

E.g. my last name is 'Bücherl', and I expected to receive
to 'B&uuml;cherl' or 'B&#252;cherl', but this didn't get
encoded .

The same applies to other language specific characters.

I wrote a wrapper which encodes the usual suspects,
but I would prefer to have a genuine solution.

Did I overlook something?

Thanks

Dieter

          Followup:

You will have to write your own wrapper.  The supplied one does not do that.

      Rating: 5
Encoding blanks
      in NEtscape  July 03, 2002
Reviewer:  Munz
       from Reston, USA
TOm:

1.  My web application works just fine in Internet Explorer 6.  However because
some users have NEtscape 4.7 I had to test it using it too.

The problem I am seeing is that because I pass a user_id from one page to
another, if the user id has two words separated by a space, IE puts a %20 while
NEtscape only passes first word or sometimes two words separated by space.

THe query does not work becuase it uses that user id.

DO you know what should I do to substitute or encode those blank when passing
parameters from one page to another in NEtscape.

          Followup:

Arg, I hate IE for this reason (i use netscape).

IE is hiding your error at the expense of every other browser.

YOU are responsible for URL encoding YOUR strings.  YOU have a bug in your
code!!!

Here is a handy little function you can use:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function urlencode( p_str
in varchar2 ) return varchar2
  2  as
  3          l_tmp   varchar2(12000);
  4          l_len   number default length(p_str);
  5          l_bad   varchar2(100) default ' >%}\~];?@&<#{|^[`/:=$+''"' ||
chr(10);
  6          l_char  char(1);
  7  begin
  8          if ( p_str is NULL ) then
  9                  return NULL;
 10          end if;
 11
 12          for i in 1 .. l_len loop
 13                  l_char :=  substr(p_str,i,1);
 14                  if ( instr( l_bad, l_char ) > 0 )
 15                  then
 16                          l_tmp := l_tmp || '%' || to_char(ascii(l_char),
'fm0X');
 17                  else
 18                          l_tmp := l_tmp || l_char;
 19                  end if;
 20          end loop;
 21
 22          return l_tmp;
 23  end;
 24  /

Function created.

Use that on all fields you pass as data to links and it'll do the right thing:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select urlencode( 'user name' ) from dual;

URLENCODE('USERNAME')
---------------------------------------------------------------------------------
--------------------------------------------------
user%20name

This is not a Netscap'ism -- this is the way the HTTP protocol is defined to
work (and MS is "breaking" that protocol, again, as usual)

      Rating: 5
URL
      Encoding  July 07, 2002
Reviewer:  munz
       from Reston, USA
Tom:

Thanks a lot for excellent help.

1.  If I have an HTML form with 30 text fields, and let us say some date and
number fields, DO I have to use this function for each SUBMIT. E.g.

htp.formText(urlencode(i_name),40,40,null);
or
htp.p('<input name="'||urlencode||p_name||'" size=40 maxlength=40 values=null>;

or is it only  for when calling hardcoded links?
2.  I have created so many forms without the urlencode.  Does it mean I have to
visit each PL/SQL procedure and use that function for each submit.

3.  Would not using the "post" method in a form automatically does that while
"get" does not.

          Followup:

you do not need to urlencode the names in general -- especially if you are using
PLSQL -- as plsql only accepts characters that are OK in urls.

it is the VALUE part you want to urlencode.

You use it with links.  You don't need to use it in a form -- there you need to
HTF.ESCAPE_SC the strings (consider an input item whose default value contains
&, ", or > for example -- that would really mess things up.

2) only if you want things to work right all of the time.  If any of your field
values can contain &, ", <, > -- you'll want to make sure they are
htf.escape_sc'ed and if they are in a htp.anchor call, you want to make sure
they are URLENCODED.

3) doesn't matter -- FORMS = escape_sc, ANCHORS = urlencode.

      Rating: 5
web
      insert  January 08, 2003
Reviewer:  mo

Tom:

I have a we bform that displays blank entries in tabular form where user can
enter up to 10 records and then hits save to save it. the table is

create table invent as
(a   varchar2(10),
 b   varchar2(10),
 user_id  varchar2(10),
 creation_date  date );

a+b make a primary key.  The problem is that after user enters several records I
show him a confimration page with all records he entered.  How would I select
the records that he inserted in that page only.  I am thinking of adding a
column "key" that would have a sequence, same value for all records inserted and
then query the table for that sequence value.  Would you use a temporary table
here instead or do it in a different way?

      Rating:
      5
update  January 22, 2003
Reviewer:
      m
Tom:

are you sure about the update statement for columns that have null values. I did
the same thing for birth_date below but update fails when I have a null value in
that record.

 UPDATE  inventory
                     SET  warehouse_id  = i_warehouse_id(i),
                          stock_number   = i_stock_number(i),
                          effective_Date =
to_date(i_effective_Date(i),'dd-mon-yyyy'),
                          storage_Code = i_storage_code(i),
                          quantity = i_quantity(i),
                          birth_date = to_date(i_birth_date(i),'dd-mon-yyyy'),
                          user_id = i_user_id,
                          creation_date = sysdate
                      WHERE warehouse_id = i_old_warehouse_id(i) and
                            stock_number = i_old_stock_number(i) and
                            trunc(effective_date) =
to_Date(i_old_effective_date(i),'dd-mon-yyyy') and
                            storage_code = i_old_storage_code(i) and
                            quantity =  i_old_quantity(i)  and
                            (trunc(birth_date) =
to_date(i_old_birth_date(i),'dd-mon-yyyy') or (i_old_birth_date(i) is null and
i_birth_date(i) is null) );

          Followup:

umm, so, can you define "update fails" -- got an example to look at, error
code/msg, anything (throw me a bone here...)

but:
(trunc(birth_date) =
to_date(i_old_birth_date(i),'dd-mon-yyyy') or (i_old_birth_date(i) is null and
i_birth_date(i) is null) );

doesn't look right -- shouldn't that be:

(trunc(birth_date) =  to_date(i_old_birth_date(i),'dd-mon-yyyy')
 or (i_old_birth_date(i) is null and birth_date is null)
)

      Rating:
      4
update  January 23, 2003
Reviewer:
      mo
Tom:

I do not see any difference between your statement and mine other than you
misses the (i) for i_birth_Date. It is an array so I have to use (i).

I think I know why the update was failing:

update t set c1 = p_new_c1, c2 = p_new_c2
     where rowid = p_rowid
       and (c1 = p_old_c1 OR (c1 is null and p_old_c1 is null))
       and (c2 = p_old_c2 OR (c2 is null and p_old_c2 is null));

If p_old_c1 is null and c1=value then the above will never update. I do not see
why did not you write:

 and (c1 = p_old_c1 OR (p_old_c1 is null) )

This should update in both cases:
1.  c1 is not null and p_old_c1 is null.
2.  c1 is null and p_old_c1 is not null.

do you agree?

          Followup:

 and (c1 = p_old_c1 OR (c1 is null and p_old_c1 is null))

I compare:

c1 = p_old_c1  --> database column to variable
OR
c1 is null and p_old_c1 is null)  --> database column to NULL and variable to
NULL

You have:

(trunc(birth_date) =  to_date(i_old_birth_date(i),'dd-mon-yyyy') --> db to var
or
(i_old_birth_date(i) is null and i_birth_date(i) is null) );  --> var & var

You are saying:

if the birth_date = variable OR (both the old and the new VARIABLES are null)

which doesn't make sense.  Your code is *wrong*.  You want to update the row IF

birth_date = old_birth_date OR
(birth_date is NULL nad old_birth_date is NULL)

you said:

If p_old_c1 is null and c1=value then the above will never update. I do not see
why did not you write:

that is 100% correct.  If p_old_c1 is NULL and c1 is not NULL then -- hey,
someone else UPDATED THE DATA and the entire point of this exercise is to avoid
blindly overwriting someone elses changes.  This means someone come along AFTER
we read the data out - changed the column C1 and we are coming back and trying
to overwrite their changes without ever looking at them

      Rating:
      5
update  January 23, 2003
Reviewer:
      mo
Thanks, you are 100% correct.

      Rating:
      5
update  January 23, 2003
Reviewer:
      mo
Tom:

I am creating a list of update statements by running select on a table as :

  1  select 'update shipment set state = '''||state||''''||
  2  ', zip = '''||zip||''''
  3*  from shipment
SQL> /

update shipment set state = 'OH', zip = '45215-1190'

When I want to add "WHERE STATE = 'OH' as a string at the end it does not like
it.

select 'update shipment set state = '''||state||''''||
', zip = '''||zip||''''
 from shipment  ||'where state = ''OH'' '

ERROR at line 3:
ORA-00933: SQL command not properly ended

Any hints on doing this?

          Followup:

look twice at what you are doing.

do you want to build an update with a where?  if so, your where is in the wrong
place.

do you want to where on the select?  if so, your where shouldn't be a character
string literal concatenated onto the select, it should "just be there" like from
shipment is.

      Rating: 5
thanks for the
      url encoding function!  April 21, 2003
Reviewer:
      Dave  from San Diego, CA
I was in the middle of writing my own url encoding to encode some urls so that i
can have a "breadcrumb trail" of links so a user can backtrack through my pl/sql
web app.  I was using owa_pattern.change to fix the symbols that were screwing
up my links but this function does it in a less piecemeal fashion.

again, you rock.

January 23, 2003 Murali -- Thanks for the question regarding
      "Question in Exception hanling", version 8.1.7

      You Asked
Good Morning Tom

I have created a package called ( TEST ) which contains a procedure called -
P_VALID_ORGID. Basically this procedure validates the user entered ORG_ID
against the oracle apps table. When I call this Procedure through normal sql
program the exception is raised correctly and displays the error message
correctly.  Instead of terminating the program it continues to go ahead and
process the remaining SQL statemnts.  Like in this case it goes and displays the
number of records present in the table AP_INVOICES_INTERFACE.
I want my program to be terminated as and when ir raises an exception.

What is the best and correct way of handling these sought of exceptions.  Any
suggestions please.

Thanks for your services rendering to the Oracle World.

MY PACKAGE CODE ( sample only )
=================================================================================
======
SET echo OFF
SET docu OFF
SET show OFF

CREATE OR REPLACE PACKAGE TEST IS

    PROCEDURE F_VALID_ORGID
            ( pi_org_id             IN  ap_invoices_interface.org_id%TYPE )  ;

END TEST ;
/
show errors

CREATE OR REPLACE PACKAGE BODY TEST
IS

     PROCEDURE  P_VALID_ORGID
            ( pi_org_id             IN  ap_invoices_interface.org_id%TYPE )  IS

      TYPE ty_sql_cur   IS REF CURSOR   ;
      c_ORG_ID_SQL          ty_sql_cur  ;

    c_org_id  ap_invoices_interface.org_id%TYPE ;

    NO_ORG_ID            EXCEPTION              ;

    BEGIN

        OPEN c_ORG_ID_SQL FOR

             ' SELECT  ORGANIZATION_ID FROM HR_ALL_ORGANIZATION_UNITS '||
                  ' WHERE   ( ORGANIZATION_ID  IS NULL OR '||
                  '           ORGANIZATION_ID = '||  pi_org_id || ' )' ;

                  LOOP

                     FETCH c_ORG_ID_SQL INTO c_org_id  ;

                           IF  c_org_id IS NOT NULL THEN
                                  dbms_output.put_line ( ' The ORG_ID you
entered is CORRECT' );
                           ELSE
                              RAISE NO_ORG_ID ;
                           END IF              ;

                        EXIT WHEN c_ORG_ID_SQL%NOTFOUND ;

               END LOOP;
           CLOSE c_ORG_ID_SQL ;

    EXCEPTION

        WHEN NO_ORG_ID THEN

         dbms_output.put_line(' THE ORG ID < '|| pi_org_id || ' > You have
Entered is not Correct') ;
            ROLLBACK     ;

    END P_VALID_ORGID ;

END TEST ;

/
show errors

My SQL PROGRAM  ( Sample Only ) -  TEST_MYPROGRAM.sql
===================================================================

SET SERVEROUTPUT ON SIZE 1000000

DECLARE

    l_org_id               VARCHAR2(5)  :=  '&P_ORG_ID'   ;
      get_org_id             NUMBER       :=   0            ;
      l_count                NUMBER       :=   0            ;
      po_org_id              NUMBER       :=   0            ;

BEGIN

     TEST.F_VALID_ORGID ( l_org_id) ;

      SELECT count(*) INTO l_count FROM AP_INVOICES_INTERFACE ;
      dbms_output.put_line ('  The Number or Records in Table
AP_INVOICES_INTERFACE is  '||l_count) ;

END;
/

MY OUTPUT ....

================================================================

SQL> @c:\TEST_MYPROGRAM.sql;
Enter value for p_org_id: 99
old   4:        l_org_id               VARCHAR2(5)  :=  '&P_ORG_ID'   ;
new   4:        l_org_id               VARCHAR2(5)  :=  '99'   ;
THE ORG ID < 99 > You have Entered is not Correct
The Number or Records in Table AP_INVOICES_INTERFACE is  103

PL/SQL procedure successfully completed.

Regards
Murali

      and we said...
Suggestion, no wait, mandate:  USE BIND VARIABLES!!!!!!

Suggestion:  don't rollback , don't commit.  let the caller handle the
transactions.  besides, you DIDN'T DO ANY WORK!!! what is there to rollback???

Here is how the procedure SHOULD be coded in order to make a missing value an
error and have the program "exit"

CREATE OR REPLACE PACKAGE BODY TEST
IS

PROCEDURE  P_VALID_ORGID( pi_org_id IN ap_invoices_interface.org_id%TYPE )
IS
BEGIN
   select organization_id
     into l_dumm
     from hr_organization_units
    where organization_id = pi_org_id
      and rownum = 1;
exception
   when no_data_found then
      raise_application_error
      ( -20001, 'The org ' || p_org_id || ' is not valid');
end;

end;

Thats it -- if not data found, you get error message to that effect and the
caller (unless it catches this exception) will bail out.

January 24, 2003 Ram -- Thanks for the question regarding "In-line
      view and rownum", version 9.0.1

      You Asked
Hi Tom,
Please help to understand the logic behind in-line-view when use in complex
nested query(mostly used in Dev2k Reports 2.5),and using Rownum to generate
Serial no.and sorting.
Below is one part of  query that is also connect to other to query link with
column "D_RECID" .
When use below query it will order by Rownum ,but the ordering of  C_number is
altered and is not in sequence with Rownum.
Please break up a nested query and explain if possible.so as netx time we able
to make use of it.

SELECT
     ROWNUM Sequence
    ,A.COMPANY
    , A.C_WEIGHT
    , A.C_GWEIGHT
    , A.C_NUMBER
    , A.I_TYPE
    , A.I_SERIES
    , A.I_NO
    , A.GRADE
    ,A.B_ID
    ,A.N_BON NB
    ,B.D_RECID
FROM  DO B , C_ITEM  A
WHERE
    B.DOPKCITM_RECID =A.I_RECID
ORDER BY ROWNUM,C_NUMBER
Thanks
Ram

      and we said...
The ordering of C_NUMBER is most certainly NOT altered and is definitely "in
sequence" with rownum.

The above query will do this conceptually (for example, assuming A is the
driving table)

   rownum = 0;
   for x in ( select * from c_item )
   loop
       for y in ( select * from do where dopkcitm_recid = X.I_RECID )
       loop
            rownum = rownum+1;
            OUTPUT rownum, x.company, x.c_weight, ...., y.d_recid to TEMP RESULT
       end loop
   end loop

   SORT temp result by ROWNUM, C_NUMER (but rownum was ASSIGNED inside the loop
   above so the sort doesn't really do *anything* here -- the data is already
   sorted by rownum by definition!!!)

If what you want is a rownum assigned AFTER SORTING by C_NUMBER, then

select rownum seq, /* RECOMMEND you avoid using things like "sequence" as a name
                      since we have this thing called a sequence */
       x.*
  from ( SELECT A.COMPANY
    , A.C_WEIGHT
    , A.C_GWEIGHT
    , A.C_NUMBER
    , A.I_TYPE
    , A.I_SERIES
    , A.I_NO
    , A.GRADE
    ,A.B_ID
    ,A.N_BON NB
    ,B.D_RECID
FROM  DO B , C_ITEM  A
WHERE
    B.DOPKCITM_RECID =A.I_RECID
ORDER BY C_NUMBER ) X

That is, ORDER the set and then assign rownum.
Review &
      Followup

      Rating: 4
Little More
      clarification   August 14, 2002
Reviewer:
      Ram
Hi Tom
Thanks a lot,
Please clarify will this query run in DEV2k reports 2.5 or not?
As when Running its giving error
"Rep:0496 internal error,unable to parse the query"
but same is running successfully in Sqlplus.

2."SELECT X.*" is same as select all the individual column in
in-line view query.?
Thnaks

          Followup:

You'll need to use dynamic sql if reports 2.5 doesn't understand that sql
syntax.

2) yes.

      Rating: 4
Top
      N  January 20, 2003
Reviewer:  Sri
       from India
Dear Tom,

  I have a problem in getting the top 3 records.  Let me explain.  My table is
as follows: -

Create Table CostSheet
(Batch_No Varchar2(20),
 Item Varchar2(100),
 Actual_Value Number(12,2)
)
/

  One Batch will have lot of Items in it and each will have a value.  What I
need to find is the top 3 items when sorted by 'Actual_Value' for each batch.

  Expecting your help.  Thank you.

          Followup:

select *
  from (select a.*, row_number() over ( partition by batch_no
                                        order by actual_value DESC nulls last )
rn
          from costSheet )
 where rn <= 3;

will get the top 3 items ordered by actual value within each batch no.  I think
that is what you are asking for.

      Rating: 3
Top
      N  January 22, 2003
Reviewer:  Sri
       from India
Dear Tom,

  That SQL is giving some error.  The Partition option works only with 9i?  Tom,
I am using Oracle 8i, 8.1.5.0.  Sorry for not telling this first.

select *
  from (select a.*, row_number() over
            (partition by batch_no order by actual_value DESC nulls last) rn
          from CostSheet
       )
 where rn <= 3

SQL> /
(partition by batch_no order by actual_value DESC nulls last) rn
*
ERROR at line 3:
ORA-00923: FROM keyword not found where expected

          Followup:

816 and up -- one more release for you.

      Rating: 5
Top
      N  January 23, 2003
Reviewer:  Sri
       from India
Tom,

  Anyway we can do this with Oracle 8.1.5?

  Thank you.

          Followup:

short of upgrading to a supported release?

not in sql.

January 24, 2003 Steve -- Thanks for the question regarding
      "SELECT * v SELECT column_names", version 8.1.7

      You Asked
Hi Tom

Currently involved in a debate where one of those myths surfaced today.
Basically, which is more efficient:

1.  SELECT * FROM <table>
or
2.  SELECT <long list of column names> FROM <table>.

Personally, I can't see a difference (although from a readability point of view
I prefer 2) but the myth (and consequently some of my peers), says that the 2nd
query is more efficient.

Regards

Steve

      and we said...
There are dozens of reasons to avoid the use of SELECT *

o it can break your application.  Add a column -- bamm, application breaks.
Drop a column -- bamm, application breaks. (not in plsql if you do it right tho)

o it can be slower with * -- if the long list of columns is NOT all columns....

o it is less obvious what data is actually used by the program.  You'll find
that people will use select * -- even though they want/need 2 columns.  It would
be faster and more meaningful and more "robust" (less likely to break) if you
select a,b,c,...

I would go with #2, not just for speed -- but readability, dependency tracking,
maintainence -- the performance boost you may or may not see is just a BONUS.

Review &
      Followup

      Rating:
      3
Goood  January 24, 2003
Reviewer:
      Yogesh Hingmire  from Mumbai
it was good not only fgor begineers burt also for  some pro's , also i wid like
to add whether the order in which u specify the columns , and the way the exist
in the table affect the retrival, I have personally not experienced much of a
speed difference though...
Thanks ...

          Followup:

You will see this most often when the coder used select *, but only need a few
columns from the tables and after specifying JUST the columns they need,
discover the query can skip some steps in the execution plan (such as a TABLE
ACCESS BY ROWID) because it was able to get all of the data it needed from
somewhere else -- like an index.

      Rating:
      5
Thanks  January 24, 2003
Reviewer:
      Steve  from UK

      Rating:
      5
  January 24, 2003
Reviewer:  JP
       from UK
Your explanation is excellent.

Can you please elaborate the case of memory management?

Following is example.

Table CUSTOMER have 30 attributes and 10M records. Generally we use batch
process and access 1M data from the table only using 4 attributes.

Case 1

Select CustID, CustName, CustPin, ServiceNo from CUSTOMER Where CustID between 1
and 1000000;

Case 2

Select * from CUSTOMER Where CustID between 1 and 1000000;

Now my questions are

1.    Is Case2 takes much SGA (buffer cache) than Case1?
2.    Oracle cache data block not particular rows. Then what is the different
between Select * and Select A1, A2,….. If I am wrong please guide me.

Thanks in advance.

          Followup:

1) could be -- if I had an index on custid, custname, custpin, serviceno, the
database would use the index and never goto the table.

If it uses an index and has to goto the table (eg: index only on custid, need
the table block for custname, custpin, serviceno), then it'll take the same
amount of buffer cache.

But -- in the client application, it will

o receive tons less network data (less network traffic)
o receive it faster -- it takes less time to get 4 columns ready to go then 30
o since array (bulk fetching) is key for good performance, the client will need
4/30'th the amount of memory (eg: it could increase its array size alot and
still use the same amount of ram)

January 24, 2003 James -- Thanks for the question regarding
      "Tablespace increased by resize datafiles seems not being used", version
      8.0.6.3

      You Asked
Hi Tom,
We have a tablespace DIST
           File                                   Size
Tablespace  ID                    Datafile       in MB.
---------- ---- ----------------------------- -------------
DIST          5 /d03/oradata/prod/dist01.dbf       1,500.00
             15 /d03/oradata/prod/dist02.dbf       1,500.00
             22 /d03/oradata/prod/dist03.dbf       1,800.00
File id 5 and 15 is original 1G.  When space in tablespace is used up, I enlarge
them to increase disk space by resizing.  It works at the beginning.  However, I
found that later on the largest extents always become very small like 23MB while
there is still 764MB free in this tablespace.  However, the largest next extent
of objects using this tablespace is only 8MB.
     I have tried to resize it back to smaller size to gain back space but fail
due to block used by objects.
     What is the reason for that?  How can I make those free space usable by
objects?

Best Regards
James

      and we said...
you have classic fragmentation.

You must create and drop objects lots in this tablespace.

You do not use a uniform extent size (eg: every object uses it's own sizing
strategy).

Over time, your tablespace becomes like swiss cheese, with lots of little holes.

In Oracle8i -- you will use locally managed tablespaces -- which enforce uniform
sizing of all extents.

In 8.0 and before, you'll have to either

o live with it -- reset the NEXT extent on your objects in that tablespace to
something smaller (smaller then the largest free contigous extent) and set their
pctincrease to zero (i despise pctincrease)

o take everything out, put it back in (exp/imp, reorg)

I would vote for living with it.  even after an exp/imp -- unless you go and fix
all of the storage parameters -- making initial=next and pctincrease =0 and
making initial = same value for all objects in tablespace, it'll just happen all
over again.

The space is usuable, it is just that your objects are asking for NEXT extents
then are larger then you have free contigous space.
Review &
      Followup

      Rating: 5
Thank
      you  January 25, 2003
Reviewer:  James Wong
       from Hong Kong
You really understand the difficulties of my situation.  It is really horrible
to imp/exp a tablespace using by 4 Oracle Applications modules.  Anyway, I will
keep maintaining the next extent of objects.  If possible, I will rebuild
indexes according to the initial=extent pctincreae=0 rule.

Best Regards
James

January 24, 2003 Rosidi -- Thanks for the question regarding
      "Rollback Segment", version 7.3.4

      You Asked
Hi ,
I have questions regarding Rollback Segments.
I set my database to have 3 rollback segments with storage parameter
as follow : INITIAL 2M,Next 2M,MINEXTENTS 2,MAXEXTENTS 249,
OPTIMAL 20M.
When I run my PL/SQL I got an error ORA-01555.
My question is Why my rollback segment can not grow to the next extents. But
when I delete a very large record from the table , then only the rollback
segment grow to the next extent.

2. I read some documentation and found a term "SELECT ACROSS COMMIT" . Could you
guide me to the link where I can get more information about "SELECT ACROSS
COMMIT" or You can just briefly explain.

Thanks

Rosidi

      and we said...
1) the problem is because an ORA-1555 is raised on a QUERY and the only things
that make rollback grow are modifications.

The 1555 means "your query needed some rollback data generated by a
modification.  Unfortunately, the data is no longer in the rollback segments, it
was overwritten".

That happens because your modifications did not need to make the rollback
"grow", so they did not -- they wrapped around and reused already allocated
space.

The answer: size your rollback segments so they do not wrap during the time it
takes to execute your longest running query.  For you that might be:

minextents 10

to make your RBS's be AT LEAST 20 me (instead of 4 as they are now).  And --
lose that OPTIMAL.  OPTIMAL only kicks in when you least want it to, will case
the 1555, and is a waste of time (in my opinion)
Review &
      Followup

      Rating:
      4
  January 26, 2003
Reviewer:  Rosidi
      Mohd  from Malaysia
Thanks Tom.

January 24, 2003 Peter -- Thanks for the question regarding
      "requirements for functions on which function-based index can be created",
      version 8.1.7

      You Asked
I want to enable users to do SOUNDEX searches on an unparsed name column, such
that they can find matches in any part of the string. That is, if the user
enters "MILLER", my code will first take SOUNDEX of "MILLER" and then find the
portion of the unparsed name column (if any) whose SOUNDEX value equals the
SOUNDEX value of "MILLER".

To do this, I have to use an algorithm that loops through the unparsed name
column character-by-character, and takes SUBSTR beginning at each position in
the string, then does SOUNDEX on the SUBSTR.

In order to accomplish this using a function-based index, I need a function that
takes a numeric parameter (representing the START position of the SUBSTR) as
well as a string parameter (which will be the column name when the index is
created).  The function will still be DETERMINISTIC, in the sense that with
KNOWN input values, the function will always return the same result.

QUESTION: I know I can create a function-based index on a function that only
takes column names as arguments - no problem. But can I do a function-based
index on a function that has a numeric parameter as well as a database column
name? When I try, so far I am getting an error. 0RA-00904 Invalid column name -
when I use the formal parameter name for the numeric parameter. If on the other
hand I use a literal value for the numeric parameter, the index is created
without any problem but if I use this approach I would have to create 90
function-based indexes on this column, since it is a VARCHAR2(90) column the
SOUNDEX could be taken on a SUBSTR beginning at any of the 90 positions within
the column.
I understand why the compiler doesn't recognize the formal parameter name for
the numeric parameter when I try to create my index. My question: is there a way
to do what I am trying to do with a single function-based index ? Any help would
be appreciated. Thanks.

      and we said...
The function is evaluated WHEN THE ROW is inserted/updated -- not at runtime
when we query -- that would sort of destroy the entire purpose of the index!!

There is no way to accomplish what you are trying to do, think about it -- you
need to run procedural code on each row to find out if you have a hit, by
definition you cannot "precompute" the answser ( in the sense that  UPPER(ename)
precomputes the answer)

What you are trying to do would be akin to, insert of using UPPPER, having an
algorithm that computes every possible "case" of a string --eg:

BOB = bob, Bob, bOb, boB, BOb, bOB, BoB, BOB

to compare to the user input to see if there is a match.  Just like you could
never do that -- you cannot really do it with soundex.

What you would have to do is create a table like:

create table soundex_match( word varchar2(n),
                            soundex_val varchar2(n),
                            primary key ( soundex_val, word ) )
organization index;

and a trigger after insert or update for each row on this table.  The trigger
would look then compute all of the soundex'es and attempt to insert them into
this table (dup_val_on_index on first insert indicates word is already there and
you can just return).

then, you would query:

select *
  from t
 where word in ( select word
                   from soundex_match
                  where soundex_val = soundex(:input) );

You might want to have a "rebuild" routine that rebuilds the soundex_match table
every now and again (to handle deleted words -- you cannot really have a good
delete trigger that does this using this approach)

The IOT will work as your index in this case.
Review &
      Followup

      Rating: 5
requirements
      for functions on which function-based index can be
      created  January 24, 2003
Reviewer:  Peter
      Hartgerink  from Ottawa, ON, Canada
Hi Tom

As always, a most useful reply.  I suspected as much, but needed to know for
sure.  There is some resistance to creating new tables in the environment where
I currently work (a bit of turf war between developers and DBAs unfortunately)
so I was hoping to be able to implement a solution without requesting new
structures. However it seems that a new structure is the best (only?) solution.
Thank you for confirming this.

          Followup:

but wouldn't a function based index "be a new structure"?

if it isn't, just tell the DBAs an IOT is a index, not a table ;)

January 24, 2003 Ashwin -- Thanks for the question regarding
      "Months in year Query", version 8.1.7

      You Asked
Hi,

How to retreive the distinct months in a date period.I mean if we have two
parameters as date from and date to, how to retreive the months that fall
between these two dates(i.e. in this period)?

Thanks
Ashwin

      and we said...
ops$tkyte@ORA920> variable start_date varchar2(20)
ops$tkyte@ORA920> variable end_date varchar2(20)
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec :start_date := '01-jan-2002'; :end_date := '22-jan-2003';

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select to_char( add_months(
to_date(:start_date,'dd-mon-yyyy'),rownum-1), 'Month YYYY' )
  2    from all_objects
  3   where rownum <= months_between( to_date( :end_date, 'dd-mon-yyyy'),
to_date(:start_date,'dd-mon-yyyy'))+1
  4  /

TO_CHAR(ADD_MO
--------------
January   2002
February  2002
March     2002
April     2002
May       2002
June      2002
July      2002
August    2002
September 2002
October   2002
November  2002
December  2002
January   2003

13 rows selected.

Review &
      Followup

      Rating: 5
Terrific
      Solution  January 24, 2003
Reviewer:  Walker
       from Hyattsville, M.D, USA
Terrific Solution

      Rating: 5
Months in a
      Year Query  January 24, 2003
Reviewer:  Ashwin
       from India
Fantastic solution..This is gr8 stuff..thanks a lot

January 24, 2003 -- Thanks for the question regarding "MAX,MIN",
      version 8i

      You Asked
Tom,

I have a following query that produces following results:

select outlet.outletid ,call.callid,
                      call.datecompleted,
                        question.questiontext,
                        answer.answervalue

                from msp_fdm.outlet,
                     msp_fdm.question,
                     msp_fdm.answer,
                     msp_fdm.region,msp_fdm.questionnaire,
                     msp_fdm.chain,
                     msp_fdm.call,
                     msp_fdm.mapregionoutlet
            where answer.callid=call.callid

            and   call.outletid=outlet.outletid
            and   outlet.outletid=mapregionoutlet.outletid
            and   mapregionoutlet.regionid=region.regionid
            and   call.questionnaireid=questionnaire.questionnaireid
            and   chain.chainid=outlet.chainid
            and   answer.questionid=question.questionid
            and   question.questiontext= 'Enter Display Width in Feet'
            and   region.regionid=24
            and   chain.chainid=132
            and   questionnaire.name in ('Secondary Rep Reset Time
Tracking','Second Day Reset','Maybelline Reset')
            and call.datecompleted between to_date('1/1/2002','mm/dd/yyyy') and
to_date('12/31/2002','mm/dd/yyyy')
            order by outlet.outletid

RESULTS:
INSERT INTO &&table_name
VALUES
(3374,1867469,'TUESDAY, MARCH 05, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(3637,1865365,'TUESDAY, MARCH 05, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(3637,2170654,'THURSDAY, AUGUST 29, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(3822,2281022,'THURSDAY, SEPTEMBER 26, 2002','Enter Display Width in Feet','12')
/
INSERT INTO &&table_name
VALUES
(3822,2170655,'FRIDAY, AUGUST 30, 2002','Enter Display Width in Feet','12')
/
INSERT INTO &&table_name
VALUES
(3992,2170656,'WEDNESDAY, AUGUST 28, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(3992,1944729,'MONDAY, APRIL 15, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(4093,1866155,'MONDAY, FEBRUARY 25, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(4100,1866575,'WEDNESDAY, MARCH 13, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(4100,2084674,'TUESDAY, AUGUST 20, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(4129,1866685,'MONDAY, MARCH 04, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(4129,2084625,'THURSDAY, AUGUST 22, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(4142,1865770,'MONDAY, MARCH 25, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(4142,2085133,'TUESDAY, AUGUST 06, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(4149,1865533,'MONDAY, MARCH 18, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(4149,2086038,'WEDNESDAY, JULY 31, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(4222,1866898,'MONDAY, MARCH 04, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(4337,1928388,'THURSDAY, APRIL 04, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(4337,2086108,'WEDNESDAY, AUGUST 21, 2002','Enter Display Width in Feet','12')
/
INSERT INTO &&table_name
VALUES
(4535,1865580,'WEDNESDAY, FEBRUARY 27, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(4535,2085948,'TUESDAY, AUGUST 20, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(4547,1866789,'TUESDAY, FEBRUARY 26, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(4547,2085018,'WEDNESDAY, AUGUST 14, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(4921,1865271,'THURSDAY, MARCH 07, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(4921,2170659,'THURSDAY, AUGUST 29, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(5285,2170660,'WEDNESDAY, AUGUST 28, 2002','Enter Display Width in Feet','15')
/
INSERT INTO &&table_name
VALUES
(5285,1865969,'THURSDAY, MARCH 14, 2002','Enter Display Width in Feet','8')
/
INSERT INTO &&table_name
VALUES
(27416,1866230,'TUESDAY, MARCH 19, 2002','Enter Display Width in Feet','12')
/
INSERT INTO &&table_name
VALUES
(27416,2085297,'MONDAY, AUGUST 19, 2002','Enter Display Width in Feet','12')
/

Out of that query I need to get sum of answervalue for Max(datecompleted) and
sum of answervalue for Min(datecompleted).
However, if there is only one datecompleted
(max(datecompleted)=min(datecompleted)) I need to default answervalue to 8 (for
example, case for outlet id 3374). Last condition only applies when I calculate
min(answervalue).

So, the results need to smth like that:

3374    8   15
3637    15  15
3832    12  12
....
4337    15  12

Thank You very much

It needs to be grouped on outlet.outletid and as far as callid and questiontext
you can skip them at all. The important columns are datecompleted,outletid,
answervalue, as I need to get maximum of datecompleted and answervalue
accodingly and minimum of datecompleted and answervalue accordingly. But with
minimum, answervalue needs to be defaulted to 8 if
max(datecompleted)=min(datecompleted)

Thank You

      and we said...
Analytics rock and roll:

ops$tkyte@ORA920> select outlet_id,
  2         decode( max(min_dt), max(max_dt), 8, sum( decode(
datecompleted,min_dt,answervalue,null) ) ) min_av,
  3             sum( decode( datecompleted, max_dt, answervalue, null ) ) max_av
  4    from (
  5  select outlet_id, datecompleted,
  6         min(datecompleted) over (partition by outlet_id) min_dt,
  7         max(datecompleted) over (partition by outlet_id) max_dt,
  8             answervalue
  9    from t
 10         )
 11   group by outlet_id
 12  /

 OUTLET_ID     MIN_AV     MAX_AV
---------- ---------- ----------
      3374          8         15
      3637         15         15
      3822         12         12
      3992         15         15
      4093          8         15
      4100         15         15
      4129         15         15
      4142         15         15
      4149         15         15
      4222          8         15
      4337         15         12
      4535         15         15
      4547         15         15
      4921         15         15
      5285          8         15
     27416         12         12

16 rows selected.

Review &
      Followup

      Rating: 5
Lots of
      Thanks  January 24, 2003
Reviewer:
      A reader
That's the class...You are a GENIUS! Incredible!!!!

January 24, 2003 Marcelo -- Thanks for the question regarding
      "Java SAP Connector", version 8.1.6+

      You Asked
Hi Tom,
I loaded the SAP Java Connector (called JCo) into my Oracle (8.1.6 and 9i)
Server in order to use java to connects to external R/3 systems.

No problem loading, but when I run a procedure calling the java program that
references SAP Java Connector I get the error:

ORA-29532: Java call terminated by uncaught Java exception:
java.lang.ExceptionInInitializerError: JCO.classInitialize(): Could not load
middleware layer 'com.sap.mw.jco.rfc.MiddlewareRFC'  the Permission
(java.lang.RuntimePermission loadLibrary.sapjcorfc ) has not been granted to LF.
The PL/SQL to grant this is dbms_java.grant_permission( 'LF',
'SYS:java.lang.RuntimePermission', 'loadLibrary.sapjcorfc', '' ) ORA-06512: at
"LF.SAP_TEST", line 0 ORA-06512: at line 2

When I try to grant the above permision, I get the error:

 ORA-29532: Java call terminated by uncaught Java exception:
java.lang.SecurityException: policy table update
SYS:java.lang.RuntimePermission, loadLibrary.sapjcorfc ORA-06512: at
"SYS.DBMS_JAVA", line 0 ORA-06512: at line 2

Searching Oracle8i Java Developer's Guide, chapter 5, I've found the  note:
SYS is granted permission to load libraries that come with Oracle. However,
Aurora does not support other users loading libraries, because loading C within
the database is insecure. So, you are not allowed to grant permission for
loadLibrary.* of RuntimePermission.

I'm trying to load java classe that makes references to c library
(librfc32.dll). Is it the problem? There is no way to load classes like that?

Thanks for your attention,
Marcelo

      and we said...
You cannot link C code into the kernel (scary though, isn't it).

Only we can.

SAP's code could run a little wild, touch the SGA, corrupt data and we would be
to blame.

No native code, you'll have to ask SAP for a pure java implementation.
Review &
      Followup

      Rating: 4
pure java
      implementation  July 29, 2002
Reviewer:
      Marcelo  from Brazil
I think we will not see a pure java implementation from SAP... To do this,
sounds like to reinvent the wheel.

Thanks Tom

          Followup:

You can use external procedures to call the DLL if they publish that interface
as well.  We can call the DLL itself using that technology.

      Rating: 3
DB2 access from
      a OSP  January 23, 2003
Reviewer:  Eric
      Worthy  from Denver, CO
So, what type of jdbc driver should I use to connect to DB2 from a Java Stored
Procedure?

If I try to use a thick (Type 2) driver, I get the error:
java.lang.ExceptionInInitializerError: java.security.AccessControlException: the
Permission (java.lang.RuntimePermission loadLibrary.db2jdbc) has not been
granted to SYSTEM. The PL/SQL to grant this is dbms_java.grant_permission(
'SYSTEM', 'SYS:java.lang.
RuntimePermission', 'loadLibrary.db2jdbc', '' )

For me to use the Type4 (thin) driver, there has to be a service running on the
db2 database which isn't possible at this time.

Thanks for the help.

          Followup:

sorry that their thin driver doesn't work for you as a type4 is the only type
you can drop in.

So, do you have ODBC access to DB2 -- use the heterogenous services, then all
you need is a dblink.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4406709207206

      Rating: 4
db2
      access  January 24, 2003
Reviewer:  Eric
      Worthy  from Denver, CO
Tom, that is how I currently connect to DB2. I use HS via ODBC. Problem is
performance and outer joins,etc..
I cannot perform outer joins and when I use functions, (ie. decode, trim,etc..)
the performance suffers. I have written my data pulls using jdbc (type2 driver
which requires the db2 client to be installed) and I wanted to make them a java
stored procecdure but according to everything I read (including this site) you
can't access a 3rd party C program. (ie. db2 client C libraries). So, I'm stuck
with using the type3 which is all java but db2 ver7.2 requires a service to be
running on the db2 side. (7.2 doesn't support a type4).

So, I guess I will just leave it java/jdbc and insert into Oracle. This just
leaves all my business logic outside the database which I don't like because all
of my peers here aren't java knowledgeable.

Thanks for the help.

          Followup:

I don't see how using jdbc is going to help get around using outer joins,
decode, etc???  You have two connections -- not one.  You won't even be ABLE to
join Oracle data to DB2 data?

      Rating: 5
Db2 via
      Oracle  January 24, 2003
Reviewer:  Eric
      Worthy  from Denver, CO
Ok, I think I confused you. Sorry about that. Currently, I use HS via ODBC to
connect to a DB2 database. I open a pl/sql cursor and query the db2 and insert
this data into a Oracle table. If I try to use outer joins in my queries, it
never comes back. Also, if I use decode, trim,etc.. the performance drops to a
standstill. (So, I am forced to bring data back in to Oracle temp table and pair
down the data there. I'm not joining oracle to db2 tables)

Hence, I would like to write java/jdbc programs which run the sql on the db2
database and returns just the data to me. (My db2 jdbc queries have ansi outer
joins in them). I wanted to put these java programs into a java stored procedure
in Oracle. I can't do this because of the JDBC driver types. DB2 jdbc type2 uses
the Db2 client C libraries and from what I read, for security reasons, I can't
make them into a java stored procedure. Is this true?

          Followup:

did you try a PASSTHRU query using the dbms_hs_passthrough package?  it is sort
of like using dbms_sql ..

you can only put type4 drivers (pure java) into the database.  No foreign
compiled code.

      Rating:
      5
Passthru  January 24, 2003
Reviewer:
      Eric Worthy  from Denver, CO
c := DBMS_HS_PASSTHROUGH.OPEN_CURSOR@SalesDB;

Tom, I have tried this but unfortunately, the data I'm after is located in 2
different boxes. Example, I get a telephone number, well, I have a local table
that tells me what DB2 database to go after. So, in the example above,
its either SalesDBWest or SalesDBEast...

I have tried to make the db connection a variable, but it won't let me. ie.
v_sales_db := 'SalesDBEast';

Again, thanks for your help. It does seem silly that I would need a service
running on the DB2 database to use
a thin driver, but I guess there is nothing I can do about that.

January 24, 2003 rr -- Thanks for the question regarding "users
      wrongly dropped", version 8.1.6

      You Asked
due to wrong operation , 2 users were droped this morning, how can i recover
them?? oracle is running under noarchivelog mode.

      and we said...
unless you have a time machine and can go back in time, your only chance of
recovery is to restore your last full cold backup.

This is reason 1302342 why all databases really need to be in archivelog mode.
At least then you could do a PITR to the moment right before the two users were
dropped -- export their data and import into this database.

Now, you'll restore your cold backups (hope you got one!) to another location,
export these users and import them -- losing all changes since the backup itself
(with exactly zero percent chance of recovering those changes)
Review &
      Followup

      Rating:
      4
  January 24, 2003
Reviewer:
      A reader
This is a hypothetical question. Can you please discuss if it is possible.

IF, as soon as this guy realize that he dropped a user, can he do the following
(eventhough he is running in noarchivelog mode)?

1. Shutdown the database.
2. Start up in restricted mode.
3. Do a 'alter database recover until <5 mins back>;
4. Shutdown and restart.

This is assuming that he will have atleast 5-10 mins of transactions in his
online redo logs. This is far better than having to restore a cold backup. Only
5 mins worth of work is lost (and in all likelyhood in this case there was
nothing much is going-on  on the db - in which case he would be in archivelog
mode in the first place!). BTW, the above is applicable to archivelog database
as well.

I know Oracle will never recommend this. But is it possible in the first place?
And what are the cons other than losing the 5/10/15 mins worth of work? Thank
you for your time.

          Followup:

nope.

dbwr flushes blocks all of the time.  The datafiles are newer then 5 minutes
ago.  You cannot roll them back with redo -- only forward and they are already
past the point in time you want to recover to -- so they are "not from a
sufficiently old backup".

The above is NOT applicable to archivelog mode -- in archivelog mode, they would
have done a PITR of the database (or affected tablespaces) and gotten the lost
data that way.

      Rating:
      5
  January 24, 2003
Reviewer:
      A reader
So what you are saying is the only way to take my database (irrespective of the
archivelog mode) 1 hour back in time is to restore yesterdays backup and then
rollforward upto that time. There is no way of just going back from where I am
currently. Right?

Thank you very much for clearing my misconception. One more thing. Can logminer
be used to go backwords in time like that?

          Followup:

correct.

logminer can be used to give you inserts/updates/deletes to "take you back".
you can use flashback query to do the same.

neither will help you get your tables back from a dropped user really -- tons
and tons of work to do that.

January 24, 2003 Fabio -- Thanks for the question regarding
      "Objects types ! ! ", version 8.1.5

      You Asked
Hi Tom

when  move table and indx for new tablespace and drop old tablespace as objects
type (procedures , packages, triggers )
are drop too?
which tablespace (or no) stays this objects types?

thanks

      and we said...
only segments in the tablespace you drop would go away.

Since procedures, packages, triggers, etc are stored in the data dictionary in
SYSTEM, never in your own tablespaces -- they will not "go away"

January 24, 2003 Alex -- Thanks for the question regarding
      "Matching an in-memory collection with a dataset", version 9.2.0

      You Asked
Hi Tom,

My PLSQL code prepares an in-memory collection of text strings associated with
some document. It then searches a database table for an existent document, that
has a matching collection of text strings (stored in another database table)
associated with it. I would like to find how to do it in the most efficient
fashion.

Let me illustrate this task with an example.

1. I have a string collection type -

create or replace type strings as table of varchar2(4000)
/

2. My PLSQL code prepares an in-memory collection of text strings associated
with some document.

declare
  data strings;
begin
  data :=  strings(
    ‘Hi Tom,’,
    ‘’,
    ‘My PLSQL code prepares ...’
  );
end;
/

3. I have a table that stores documents and a table that stores text strings
associated with documents.

create table document(
  id integer primary key,
  subject varchar2(100)
)
/

create table document_text(
  id integer not null,
  line integer not null,
  text varchar2(4000),
  constraint pk_document_text primary key(id,line),
  constraint fk_document_text foreign key (id) references document(id)
)
/

4. And now I would like to find all documents from DOCUMENT table that have
associated collection of strings from DOCUMENT_TEXT table so that this
associated collection matches my in-memory collection.

This pseudocode does what I want -

select * from document D where
  cast( multiset(select text from document_text T where T.id=D.id) as strings)=
  strings(
    ‘Hi Tom,’,
    ‘’,
    ‘My PLSQL code prepares ...’
  )
/

Now I need to find a way of doing it in Oracle.

As I mentioned earlier the comparison happens in PLSQL code, so the procedural
logic is not ruled out as long as it performs and scales.

Could you give me a hand with it please?

Many Thanks,
Alex

Oracle 9.2.0 for Solaris

      and we said...
Ignoring that '' string -- which would be NULL in the table and would never be
"equal" or "not equal" -- we can do this:

ops$tkyte@ORA920> insert into document values ( 1, 'all about tom' );
ops$tkyte@ORA920> insert into document values ( 2, 'something else' );

ops$tkyte@ORA920> insert into document_text values ( 1, 1, 'Hi Tom,' );
ops$tkyte@ORA920> insert into document_text values ( 1, 2, 'My PLSQL code
prepares...' );
ops$tkyte@ORA920> insert into document_text values ( 1, 3, 'rest of document...'
);
ops$tkyte@ORA920> insert into document_text values ( 2, 1, 'Hi George,' );
ops$tkyte@ORA920> insert into document_text values ( 2, 2, 'My PLSQL code
prepares...' );
ops$tkyte@ORA920> insert into document_text values ( 2, 3, 'rest of document...'
);

ops$tkyte@ORA920> declare
  2      l_data strings := strings( 'Hi Tom,', 'My PLSQL code prepares...' );
  3  begin
  4      for x in ( select document.id
  5                   from document,
  6                        document_text,
  7                        (select column_value, count(*) over () cnt, rownum r
  8                           from TABLE( l_data ) ) text
  9                  where document.id = document_text.id
 10                    and document_text.text = text.column_value
 11                  group by document.id, text.cnt
 12                 having count(*) = text.cnt )
 13      loop
 14          dbms_output.put_line( x.id );
 15      end loop;
 16  end;
 17  /
1

PL/SQL procedure successfully completed.

But -- i would be infinitely more inclined to use Oracle Text to index text.
It'll be slightly more scalable and performant...

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-TEX

Review &
      Followup

      Rating: 5
Thanks a
      Lot!  January 23, 2003
Reviewer:  Alex
      Miroshnikov
Thanks Tom,

Your answer is brilliant as usual!!!

I am planning to use your analytical function approach and in addition I would
like to maintain a new indexed column in DOCUMENT table. This column will
contain a hash value calculated on associated string collection.

Thus my code will prepare a string collection, calculate for this collection a
hash value and then run a query that will execute your SQL not on the whole
DOCUMENT table but only on a small subset of it.

select document.id
  from document,
       document_text,
       (select column_value, count(*) over () cnt, rownum r
          from TABLE( l_data ) ) text
 where document.hashvalue = calculate_hash_value( l_data )
   and document.id = document_text.id
   and document_text.text = text.column_value
 group by document.id, text.cnt
having count(*) = text.cnt
/

Not sure what to use for a hash function yet, perhaps MD5 from
dbms_obfuscation_toolkit.

Kind Regards,
Alex

      Rating: 4
The code
      provided does not work  January 23, 2003
Reviewer:
      eric givler  from harrisburg, PA
in 9.2.0.1:

> set serveroutput on size 10000
> run
  1  declare
  2          l_data strings := strings( 'Hi Tom,', 'My PLSQL code prepares...'
);
  3      begin
  4          for x in ( select document.id
  5                       from document,
  6                            document_text,
  7                            (select column_value, count(*) cnt, rownum r
  8                               from TABLE( l_data ) ) text
  9                      where document.id = document_text.id
 10                       and document_text.text = text.column_value
 11                     group by document.id, text.cnt
 12                    having count(*) = text.cnt )
 13         loop
 14             dbms_output.put_line( x.id );
 15         end loop;
 16*    end;
declare
*
ERROR at line 1:
ORA-00937: not a single-group group function
ORA-06512: at line 4

Changed to:

> l
  1  declare
  2          l_data strings := strings( 'Hi Tom,', 'My PLSQL code prepares...'
);
  3      begin
  4          for x in ( select document.id
  5                       from document
  6                          , document_text
  7                          , (
  8                             select column_value
  9                               from TABLE( l_data )
 10                            ) text
 11                      where document.id = document_text.id
 12                        and document_text.text = text.column_value
 13                      group by document.id
 14                    having count(*) = ( select count(*)
 15                                          from table( l_data) )
 16                   )
 17         loop
 18             dbms_output.put_line( x.id );
 19         end loop;
 20     end;
 21*
> /
1

PL/SQL procedure successfully completed.

What was the original query supposed to be?

          Followup:

well, you changed the code.

  6                     document_text,
  7                     (select column_value, count(*) over () cnt, rownum r
  8                        from TABLE( l_data ) ) text

code "as provided" works.

code "as typed by you" not ;)

that analytic function makes all of the difference!

      Rating: 5
Why the
      hashvalue and the count(*) = cnt?  January 24, 2003

Reviewer:  ramakrishna  from India
Hi,
  As per my understanding, the count(*) = cnt clause will only pick up those
documents where all the lines of text match all the tokens in the input
collection.  However, if the hashvalue of document_text is being computed and
stored on the document, is it not enough to just compare the hash value on the
document with the hash value of the collection?  Why should we also include the
count(*) = cnt clause?
  Further, while computing the hash value, should we also ensure that the order
of the strings is the same in both the collection and the document?  The
count(*) = cnt will not have that restriction though.

regards
Ramakrishna

      Rating: 5
About hash
      function  January 24, 2003
Reviewer:  Alex

Hi,

A hash function provides mapping of an infinite set of character sequences onto
a finite set of hash values. This is why it is not enough to just compare the
hash value on the document with the hash value of the collection. We still need
to compare collections, which have the same hash value. The hash value will help
to reduce the number of these comparisons.

At the moment I am considering a candidate for the hash function. It is PLSQL
implementation of MD5 algorithm that would work on input of any length, and not
just on one varchar2 input parameter (as dbms_obfuscation_toolkit implementation
does.

It is here
http://www.gt.ed.net/keith/plsql/
Kind Regards,
Alex

      Rating:
      5
Oops!  January 24, 2003
Reviewer:  Eric
      Givler  from Hbg, PA
Thanks for the quick reply.  Guess I need a cut&paste course.  I think the
problem occurred when I pasted this into our 8.1.7.4.1 instance, instead of
9.2.0.1:

PARKS_DBA@test [NRBITS09] > ed
Wrote file afiedt.buf

  1  declare
  2          l_data strings := strings( 'Hi Tom,', 'My PLSQL code prepares...'
);
  3      begin
  4          for x in ( select document.id
  5                       from document,
  6                            document_text,
  7                            (select column_value, count(*) over () cnt,
rownum r
  8                               from TABLE( l_data ) ) text
  9                      where document.id = document_text.id
 10                       and document_text.text = text.column_value
 11                     group by document.id, text.cnt
 12                    having count(*) = text.cnt )
 13         loop
 14             dbms_output.put_line( x.id );
 15        end loop;
 16*    end;
 17  /
                          (select column_value, count(*) over () cnt, rownum r
                                                              *
ERROR at line 7:
ORA-06550: line 7, column 63:
PLS-00103: Encountered the symbol "(" when expecting one of the following:
, from

It worked "as provided" on 9.2.0.1.

It required modifications to run on 8.1.7.4.1.  Is this a legitimate solution
then (for 8i rel 3)??

declare
        l_data strings := strings( 'Hi Tom,', 'My PLSQL code prepares...' );
begin
        for x in ( select document.id
                     from document,
                          document_text,
                          ( select column_value
                             from THE( SELECT CAST( l_data AS strings ) FROM
DUAL )
                          ) text
                    where document.id = document_text.id
                      and document_text.text = text.column_value
                   group by document.id
                   having count(*) =
                                  (
                                    select count(*)
                                      from THE(SELECT CAST( l_data AS strings )
FROM DUAL )
                                  )
                  )
       loop
           dbms_output.put_line( x.id );
       end loop;
end;

          Followup:

legit but slower -- suggest you use a dynamically opened ref cursor and bind
l_data in using the original query.

January 25, 2003 Georg -- Thanks for the question regarding
      "Tuning SQL statements (general)", version 8.1.6

      You Asked
Hi Tom,

maybe - this is a  question for your next book on tuning (as you mentioned one
time on these pages).

The first step - recognizing the problem (slow SQL statement) is described very
well in several books (e.g. using tracing and tkprof)

Examples in books how to tune a statement are always so simple but the reality
is not. How about very complex views - say more than 15 tables and one or more
inline views.

I can see the problem in the tkprof output (in one step several billions of rows
are affected and just one step further filtered to only some hunderts) and in
most of the cases I have an idea where it comes from (e.g. inline view produces
too much data).

I am not satisfied with my tuning skills - it's still a little bit try and
error.

To solve a "complex slow query problem" I usually:
- try to understand the statement
- try to split the statement (are subqueries fast enough?)
- check the joins (are existing indexes used and if not is that maybe correct?)
...

What is your way to solve this task? Could you give some practical examples in
your next book?

By the way - what will be the date, when I can buy the next best seller from
you?

Many thanks
Georg

      and we said...
This is a question i get alot.

It is a question to which I have no answer.  Well, I sort of do.

It is a matter of knowing all of the available access paths and when to use them
and how to make them available.

It is a matter of knowing the question (many times I don't tune a query, I tune
the question and develop an entirely new query).

It is a matter of knowing all of the available bits of functionality -- I mean,
suppose you were looking a query that did a self join of a table with itself --
joining each record with the record that "came before it" (eg: to compare the
difference in time between the last visit and the prior visit to the doctors
office).  Unless you knew about LAG() and LEAD() -- you would not be able to
tune this.

Mastering all aspects of the SQL language.  Tell me -- can you in a single query
tell me what the most frequently occuring object_type in your database is?  That
is -- just write a query that returns the name(s) of the object_type that has
the highest count(*).  I can do it in so many different ways -- many people
cannot do it without writing code.

Understanding that indexes != fast, full scans != slow.  Being prepared to throw
out every existing "rule of thumb" you have.  The only rule of thumb that holds
water is "that every rule of thumb is wrong".

Thinking about what the machine might have to do in order to process your query.

Do you really *need* that outer join, or is it there just in case?  what if you
select a select instead... (all about different access paths)...

and so on.  Sorry -- if there were a way to encode in writing "here are the 15
steps to tune your query" or even "the 15,000,000 steps" -- I would be off
turning that into software.  It would be the worlds best query rewrite engine --
generating queries that were optimally tuned (we have a couple developers
working on stuff like that).

Book -- spring time (i hope, it is slow going).  Very different from last book.
More like this site -- best practices sort of stuff.  More pointers to where to
get more information sometimes than information itself...  Very different from
Expert one on one tho.
Review & Followup

      Rating: 4
We are really
      looking forward your next book  January 06, 2003

Reviewer:  Lakshmi narasimhan R  from Dubai

Hi tom

We will be Glad if we get your book.  We will be really happy if you can include
how to read Explain Plan and its different meanings and interpretations. Even
though its been with lot of books including Oracle Documentation, i am of the
opinion that it is not in easy word.  Bit hard and confusing way.  We hope you
can give it in straight forward simple way.

thanks in advance.

          Followup:

I'm not doing that -- I find the performance guide does it well?   do you have a
specific question.  it is just a tree???  it is pretty straight forward to read.

Give me a question and I'll answer it -- but I'm not going to write how to read
it as that is well documented.

      Rating: 5
Oracle SQL
      Tuning Book  January 06, 2003
Reviewer:  Moorthy
      Rekapalli  from Atlanta, GA USA
Hi,

First of all, let me thank Tom and his team for their contributions to the
Oracle community.  I learned a lot from Tom's book.

Oracle SQL High-Performance Tuning by Guy Harrison is a very good book that is
dedicated to different access paths, how to interpret explain plan, tkprof
etc...

This book will be a very good companion to Tom's Oracle expert one-on-one book.

Just thought of sharing my 2 cents with the Oracle community.

Hope this helps,
Moorthy Rekapalli.

      Rating: 5
an idea for the
      next book :)  January 06, 2003
Reviewer:  John
       from MA
when you are all done, break it into 3 volumns and INCREASE THE POINT SIZE!!!!
:)
You'll be able to sell 3 books, and each will have more info than most, and I'll
be able to read 2 chapters without getting a headache!

(what where you using on Expert? 2-point, 3-point?)
(maybe I'm just getting old)
Thanks for the knowledge

          Followup:

Hey -- I write it on 21" crt's or 20" lcds (bought myself a christmas present
for at home -- a pair of 20" lcd monitors -- man does that jam!).

The fonts are big to me when I look at them ;)

Seriously -- I'll send this along to WROX -- they do that stuff, I have no
control over it really.

      Rating: 5
I'm glad I had
      a part in that!  January 06, 2003
Reviewer:
      John  from MA
(financing you Christmas present):)
I've been through the appendixes and just started the
first few chapters. Very well written and informative.
Your Joe User style makes it an easy read (once I put the bottlenecks on, \OO/
heh, heh)
I can't express enough how useful this sight has been for me.
(btw I also have an LCD 17" and find it too "crisp")
If "Knowledge is Power" you are one with much power.

Looking forward to the next book (or 3)
Thanks again

      Rating: 5
Your next
      book  January 06, 2003
Reviewer:
      A reader
Hi Tom,

When is your 3rd book out? May I peek the table of contents? Is there any draft
on the web site?
I just can't wait to get your new book. Thanks!

Steve

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6731060526417

      Rating: 4
Print of Expert
      one on one  January 06, 2003
Reviewer:
      A reader
Tom,
Just to let you know that I buy books and try to read books as soon as possible
(before new version of book or software version is available, and I loose
interest), and I did the same with your book. But I also found the print hard to
read (I am talking of printing from WROX here). In fact, when I first bought and
tried to read, I just stopped because of print. But more I visited your site,
more I came to know about your authority on the subject, and I have started
reading again, but print wise it is really hard to read. I know print of
O'Reilly books <> print of WROX books but I found print of O'Reilly is really
comfortable for which I have already written to wrox.
Regards,

      Rating: 5
fast
      query  January 07, 2003
Reviewer:  ASHRAF
       from kuwait
in data warehouse database i need to run the query fast and i care about only
how can i get output fast so i wonder about  CBO IT IS MAY BE not the fast

          Followup:

In a datawarehouse -- CBO is the only way to go....

bitmap indexes = CBO
partitioning = CBO
parallel = CBO
function based indexes = CBO
star transformations = CBO
materialized views = CBO

my point would be, if you stand up a data warehouse and DON'T use the CBO, you
got it sdrawkcab  -- thats backwards ;)

      Rating:
      5
  January 07, 2003
Reviewer:
      A reader
Tom,

Just a suggestion, is it possible to get the archives of Q & A from this site on
cdrom based on dates.It will be a cool thing to have.I am pretty much sure
viewers of this site don't mind to spend some amount if need be.

Thanks.

          Followup:

nope, all online, all of the time.

feel free to download -- i try to make that really easy.  A simple "save link to
disk" and that week it done.

      Rating: 5
archiving
      asktom  January 22, 2003
Reviewer:  hrishy
       from PA
Hi Tom

certainly this is a great place to be for any kinda of technology releated
question..wheather your a DBA/developer or you have anythin remotely to do with
oracle :-D..so  many times i find myself archiving the threads some of them for
the way the query was written..some for innovative way of explaning things (my
favourite one being consistent gets ;-D)..but when i try to save it this web
page it has  some funny name which i later change to the topic of the thread.

1)Can we have an option of saving it by thread name when i save it to the disk
(i am just being lazy of renaming )
2)what is the problem with oracle folks burning this forum on a CD and selling
it or bundling it along with your books..as i find it handy when i visit client
places ..just the way i carry oracle manuals ..as we dont have internet acesses
at many client places..as increasingly find myself slightly modify your query to
come out with innovative solutions to problems

          Followup:

1) at the bottom of every article is a:

Bookmark this page with the link HERE

link -- that gives you a clean URL that won't change over time.

2) I don't get it -- just goto the archive by week and "save link to disk".
there you go.

the second I put it onto CD, it's out of date.  It takes seconds to download a
weeks worth of stuff -- you can put it on a floppy or burn a CD from it or just
put it on your laptop....

It is a matter of distribution, time, etc....

      Rating: 4
With regards
      to...  January 22, 2003
Reviewer:  Kashif
       from Houston, TX
"Mastering all aspects of the SQL language.  Tell me -- can you in a single
query
tell me what the most frequently occuring object_type in your database is?  That

is -- just write a query that returns the name(s) of the object_type that has
the highest count(*).  I can do it in so many different ways -- many people
cannot do it without writing code."

I've come up with two, using techniques I learned from this site:

select object_type
  from all_objects
 group by object_type
having count (object_type) >= ALL (select count (object_type)
from all_objects
group by object_type)

AND

select object_type from
(
select object_type, count (object_type) cnt
from all_objects
group by object_type
order by cnt desc
)
where rownum = 1

Can you show us some other queries to solve the same problem? The first of my
queries runs for quite a bit, which makes sense since there's so much sorting
going on. Thanks.

Kashif

          Followup:

well -- your queries point out an interesting issue with this question.

ops$tkyte@ORA920> create table t
  2  as
  3  select * from all_objects
  4   where object_type not in ( 'JAVA CLASS', 'SYNONYM' );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create trigger t_trigger before insert on t for each row
  2  begin
  3          select decode(:new.object_type,'TABLE','VIEW','TABLE')
  4            into :new.object_type
  5            from dual;
  6  end;
  7  /

Trigger created.

ops$tkyte@ORA920> insert into t select * from t where object_type in ( 'TABLE',
'VIEW' );

3566 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select object_type, count(*)
  2    from t
  3   group by object_type
  4   order by 2
  5  /

OBJECT_TYPE          COUNT(*)
------------------ ----------
CONSUMER GROUP              2
MATERIALIZED VIEW           4
CONTEXT                     6
LOB                         6
DIRECTORY                   9
INDEXTYPE                   9
EVALUATION CONTEXT         11
RULE SET                   15
JAVA SOURCE                18
OPERATOR                   28
PROCEDURE                  50
TABLE PARTITION            61
TYPE BODY                  61
TRIGGER                    95
LIBRARY                   104
SEQUENCE                  127
INDEX PARTITION           134
FUNCTION                  147
JAVA RESOURCE             190
JAVA DATA                 291
PACKAGE BODY              533
PACKAGE                   590
TYPE                      862
INDEX                    1043
TABLE                    3566
VIEW                     3566

26 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select object_type
  2    from t
  3   group by object_type
  4  having count (object_type) >= ALL (select count (object_type)
  5                                       from t
  6                                      group by object_type)
  7  /

OBJECT_TYPE
------------------
TABLE
VIEW

ops$tkyte@ORA920>
ops$tkyte@ORA920> select object_type
  2    from ( select object_type, count (object_type) cnt
  3             from t
  4            group by object_type
  5            order by cnt desc
  6     )
  7   where rownum = 1
  8  /

OBJECT_TYPE
------------------
TABLE

ops$tkyte@ORA920>

So, which one is right?  both are, neither is, the first one is, the second one
is.  Depends on your perspective..... (eg: the question is ambigous ;)

Anyway -- here are some others:

ops$tkyte@ORA920> select *
  2    from (
  3  select object_type, row_number() over ( order by cnt desc nulls last ) rn
  4    from ( select object_type, count(*) cnt
  5             from t
  6            group by object_type )
  7         )
  8   where rn = 1
  9  /

OBJECT_TYPE                RN
------------------ ----------
TABLE                       1

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from (
  2  select object_type, cnt, max(cnt) over () max_cnt
  3    from ( select distinct object_type,
  4                  count(*) over (partition by object_type) cnt
  5             from t )
  6  ) where cnt = max_cnt
  7  /

OBJECT_TYPE               CNT    MAX_CNT
------------------ ---------- ----------
TABLE                    3566       3566
VIEW                     3566       3566

ops$tkyte@ORA920>
ops$tkyte@ORA920> select object_type
  2    from t
  3   group by object_type
  4  having count(*) = ( select max(count(*))
  5                        from t
  6                       group by object_type )
  7  /

OBJECT_TYPE
------------------
TABLE
VIEW

ops$tkyte@ORA920>
ops$tkyte@ORA920> select substr( max(
to_char(count(*),'fm0000000000009')||object_type ), 14 )
  2    from t
  3   group by object_type
  4  /

SUBSTR(MAX(TO_CHAR(
-------------------
VIEW

      Rating:
      5
  January 22, 2003
Reviewer:
      A reader
Hi Tom,

 You are using ALL operator in sql query.

 What is that ?

ops$tkyte@ORA920>
ops$tkyte@ORA920> select object_type
  2    from t
  3   group by object_type
  4  having count (object_type) >= ALL (select count  (object_type) <------
  5                                       from t
  6                                      group by object_type)
  7  /

          Followup:

just read it.

lines 4-6 in english are:

keep the rows having the count that is greater than or equal to ALL of the
counts by object type....

There is an ANY modifier as well --   expression <operator> ANY ( set )  just
like
                                      expression <operator> ALL ( set )

      Rating: 4
again, a
      definition might be of help here...  January 22, 2003

Reviewer:  A reader
At the Mirriam-Webster webiste,

http://www.m-w.com/
the following definition for the word 'ALL' was found:

1 a : the whole amount or quantity of <needed all the courage they had> <sat up
all night> b : as much as possible <spoke in all seriousness>
2 : every member or individual component of <all men will go> <all five children
were present>
3 : the whole number or sum of <all the angles of a triangle are equal to two
right angles>
4 : EVERY <all manner of hardship>
5 : any whatever <beyond all doubt>
6 : nothing but : ONLY: a : completely taken up with, given to, or absorbed by
<became all attention> b : having or seeming to have (some physical feature) in
conspicuous excess or prominence <all legs> c : paying full attention with <all
ears>
7 dialect : used up : entirely consumed -- used especially of food and drink
8 : being more than one person or thing <who all is coming>
synonym see WHOLE
- all the : as much of... as : as much of a... as <all the home I ever had>

      Rating: 5
compare the two
      queries  January 22, 2003
Reviewer:  Winston
       from Canada
I compared the last two ways using runSTAT, one approach is running faster then
another, but occupying more cache buffer chain latch, shared pool latch  but
with less library cache latch.

Which approach would you choose to use and why? How do you compare the cost of
different latches?

Thanks

declare
  2      l_start number;
  3      l_run1  number;
  4      l_run2  number;
  5
  6      type rc is ref cursor;
  7      l_rc    rc;
  8  begin
  9      insert into run_stats select 'before', stats.* from stats;
 10
 11      l_start := dbms_utility.get_time;
 12      for i in 1 .. 1000
 13      loop
 14          open l_rc for 'select object_type
 15                        from t
 16                       group by object_type
 17                      having count(*) = ( select max(count(*))
 18                                            from t
 19                                           group by object_type )';
 20          close l_rc;
 21      end loop;
 22      l_run1 := (dbms_utility.get_time-l_start);
 23      dbms_output.put_line( l_run1 || ' hsecs' );
 24
 25      insert into run_stats select 'after 1', stats.* from stats;
 26      l_start := dbms_utility.get_time;
 27      for i in 1 .. 1000
 28      loop
 29          open l_rc for 'select substr( max(
 30                      to_char(count(*),''fm0000000000009'')||object_type ),
14 )
 31                            from t
 32                           group by object_type' ;
 33          close l_rc;
 34      end loop;
 35      l_run2 := (dbms_utility.get_time-l_start);
 36      dbms_output.put_line( l_run2 || ' hsecs' );
 37      dbms_output.put_line
 38      ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) || '% of the time' );
 39
 40      insert into run_stats select 'after 2', stats.* from stats;
 41  end;
 42  /
27 hsecs
36 hsecs
run 1 ran in 75% of the time

PL/SQL procedure successfully completed.

select a.name, b.value-a.value run1, c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

NAME
      RUN1
--------------------------------------------------------------------------------
----------
      RUN2       DIFF
---------- ----------
LATCH.cache buffers lru chain
         1
         0         -1

LATCH.checkpoint queue latch
         2
         3          1

LATCH.undo global data
         0
         1          1

LATCH.session allocation
         1
         0         -1

STAT...messages sent
         0
         1          1

STAT...session cursor cache hits
      1000
      1001          1

STAT...free buffer requested
         3
         2         -1

STAT...table fetch continued row
         2
         0         -2

STAT...redo entries
        16
        13         -3

LATCH.redo allocation
        15
        19          4

STAT...sorts (memory)
         8
         4         -4

STAT...enqueue releases
         7
         3         -4

STAT...cluster key scans
         5
         0         -5

STAT...session cursor cache count
         5
         0         -5

STAT...recursive cpu usage
        31
        36          5

STAT...enqueue requests
         8
         3         -5

STAT...db block changes
        27
        22         -5

LATCH.enqueue hash chains
        12
         6         -6

STAT...opened cursors current
         7
         1         -6

STAT...parse time cpu
         5
        12          7

STAT...parse time elapsed
         5
        12          7

STAT...db block gets
        25
        17         -8

LATCH.messages
         3
        13         10

STAT...cluster key scan block gets
        10
         0        -10

LATCH.enqueues
        24
        12        -12

LATCH.library cache load lock
        14
         0        -14

STAT...rows fetched via callback
        18
         0        -18

STAT...opened cursors cumulative
      1024
      1005        -19

STAT...sorts (rows)
      2117
      2085        -32

STAT...parse count (total)
      1038
      1005        -33

STAT...execute count
      1042
      1005        -37

STAT...calls to get snapshot scn: kcmgss
      1043
      1005        -38

STAT...redo size
     21552
     21512        -40

STAT...buffer is pinned count
        49
         0        -49

LATCH.row cache objects
       142
        72        -70

STAT...table fetch by rowid
        85
         8        -77

STAT...no work - consistent read gets
        93
         8        -85

STAT...buffer is not pinned count
       179
        16       -163

STAT...consistent gets
       209
        12       -197

STAT...session logical reads
       234
        29       -205

LATCH.cache buffers chains
       412
       115       -297

STAT...recursive calls
      3506
      3030       -476

LATCH.shared pool
      1322
       146      -1176

LATCH.library cache
      2714
      5256       2542

STAT...session uga memory
     -4264
     27464      31728

45 rows selected.

          Followup:

well, i would not run it using NDS, and when I run it statically:

ops$tkyte@ORA920> declare
  2      l_start number;
  3      l_run1  number;
  4      l_run2  number;
  5
  6      l_seq   number;
  7  begin
  8      insert into run_stats select 'before', stats.* from stats;
  9
 10      l_start := dbms_utility.get_time;
 11      for i in 1 .. 100
 12      loop
 13          for x in ( select object_type
 14                       from t
 15                      group by object_type
 16                     having count(*) = (select max(count(*))
 17                                          from t
 18                                         group by object_type ) )
 19          loop
 20              null;
 21          end loop;
 22      end loop;
 23      l_run1 := (dbms_utility.get_time-l_start);
 24      dbms_output.put_line( l_run1 || ' hsecs' );
 25
 26      insert into run_stats select 'after 1', stats.* from stats;
 27      l_start := dbms_utility.get_time;
 28      for i in 1 .. 100
 29      loop
 30          for x in ( select substr(
max(to_char(count(*),'fm0000000000009')||object_type ), 14 )
 31                       from t
 32                      group by object_type )
 33          loop
 34              null;
 35          end loop;
 36      end loop;
 37      l_run2 := (dbms_utility.get_time-l_start);
 38      dbms_output.put_line( l_run2 || ' hsecs' );
 39      dbms_output.put_line
 40      ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) || '% of the time' );
 41
 42      insert into run_stats select 'after 2', stats.* from stats;
 43  end;
 44  /
230 hsecs
120 hsecs
run 1 ran in 191.67% of the time

PL/SQL procedure successfully completed.

I get totally the opposite of your results -- 0.27 seconds for 1,000 executions
is a pretty fast number to say the least -- these stats:

STAT...consistent gets

       209
        12       -197

STAT...session logical reads

       234
        29       -205

show that your table was pretty much "empty"??

      Rating: 5
I missed the
      fetch of ref cursor...table contains 4123 records  January
      24, 2003
Reviewer:  Winston  from Canada
HOw do you interprete the following benchmark difference?
run 1 ran in 183.91% of the time but took less consistent gets and session logic
reads. Details as below

declare
    l_start number;
    l_run1  number;
    l_run2  number;
    l_object_type t.object_type%type;

    type rc is ref cursor;
    l_rc    rc;
begin
    insert into run_stats select 'before', stats.* from stats;

    l_start := dbms_utility.get_time;
    for i in 1 .. 100
    loop
        open l_rc for 'select object_type from (
                                select object_type, cnt, max(cnt) over ()
max_cnt
                                  from ( select distinct object_type,
                                                count(*) over (partition by
object_type) cnt
                                           from t )
                                ) where cnt = max_cnt';
          loop
                   fetch l_rc into l_object_type;
                   exit when l_rc%notfound;
           end loop;
           close l_rc;
    end loop;
    l_run1 := (dbms_utility.get_time-l_start);
    dbms_output.put_line( l_run1 || ' hsecs' );

    insert into run_stats select 'after 1', stats.* from stats;
    l_start := dbms_utility.get_time;
    for i in 1 .. 100
    loop
        open l_rc for 'select object_type
                      from t
                     group by object_type
                    having count(*) = ( select max(count(*))
                                          from t
                                         group by object_type )';
          loop
                   fetch l_rc into l_object_type;
                   exit when l_rc%notfound;
          end loop;
          close l_rc;

    end loop;
    l_run2 := (dbms_utility.get_time-l_start);
    dbms_output.put_line( l_run2 || ' hsecs' );
    dbms_output.put_line
    ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) || '% of the time' );

    insert into run_stats select 'after 2', stats.* from stats;
end;
/

pause
select a.name, b.value-a.value run1, c.value-b.value run2,
       ( (c.value-b.value)-(b.value-a.value)) diff
  from run_stats a, run_stats b, run_stats c
 where a.name = b.name
   and b.name = c.name
   and a.runid = 'before'
   and b.runid = 'after 1'
   and c.runid = 'after 2'
   and (c.value-a.value) > 0
   and (c.value-b.value) <> (b.value-a.value)
 order by abs( (c.value-b.value)-(b.value-a.value))
/
      RUN2       DIFF
---------- ----------
LATCH.active checkpoint queue latch
         2
         1         -1

LATCH.session allocation
         1
         0         -1

LATCH.undo global data
         0
         1          1

STAT...cursor authentications
         0
         1          1

STAT...messages sent
         0
         1          1

STAT...opened cursors current
         3
         4          1

STAT...session cursor cache hits
       100
       101          1

STAT...free buffer requested
         3
         2         -1

STAT...cluster key scans
         2
         1         -1

LATCH.cache buffers lru chain
         2
         0         -2

STAT...parse time elapsed
         4
         2         -2

STAT...table fetch continued row
         2
         0         -2

LATCH.redo writing
         8
         5         -3

STAT...redo entries
        16
        13         -3

LATCH.redo allocation
        15
        19          4

STAT...cluster key scan block gets
         2
         6          4

STAT...db block changes
        27
        22         -5

STAT...enqueue releases
         7
         2         -5

STAT...session cursor cache count
         5
         0         -5

STAT...enqueue requests
         8
         2         -6

LATCH.enqueue hash chains
        12
         4         -8

LATCH.library cache load lock
        10
         2         -8

STAT...opened cursors cumulative
       119
       108        -11

LATCH.enqueues
        24
         8        -16

STAT...rows fetched via callback
        18
         0        -18

STAT...execute count
       133
       112        -21

STAT...calls to get snapshot scn: kcmgss
       134
       112        -22

STAT...parse count (total)
       133
       108        -25

STAT...redo size
     21508
     21468        -40

LATCH.checkpoint queue latch
        98
        51        -47

LATCH.row cache objects
       126
        78        -48

STAT...buffer is pinned count
        49
         0        -49

STAT...table fetch by rowid
        80
        13        -67

STAT...table scans (short tables)
       100
       200        100

STAT...sorts (memory)
       308
       204       -104

LATCH.shared pool
       412
       246       -166

STAT...recursive cpu usage
       431
       237       -194

STAT...recursive calls
       974
       701       -273

LATCH.library cache
       855
       497       -358

STAT...db block gets
       424
       817        393

STAT...sorts (rows)
    827917
    826685      -1232

STAT...consistent gets
      5376
     10439       5063

STAT...buffer is not pinned count
      5357
     10436       5079

STAT...no work - consistent read gets
      5276
     10423       5147

STAT...table scan blocks gotten
      5200
     10400       5200

STAT...session logical reads
      5800
     11256       5456

LATCH.cache buffers chains
     11554
     22563      11009

STAT...session pga memory max
     15104
         0     -15104

STAT...session pga memory
     77400
         0     -77400

STAT...table scan rows gotten
    412300
    824600     412300

* I have to use NDS to work around PLS-00103 error
* select count(*) from t;

  COUNT(*)
----------
      4123

Many thanks!

          Followup:

I don't know

I don't know the tables you used
I cannot really read your input as it wraps and goes for page after page after
page....

I can say the first one it more cpu intensive -- on a CPU challenged machine, it
could tend to run slower.

      Rating: 4
interprete
      runstats  January 24, 2003
Reviewer:  Winston
       from Canada
From what you're saying I got the feeling that when we use runstat to compare
two approaches...we should care more about "STAT...consistent gets"
and "STAT...session logical reads    "  then total elapse time. Please confirm.

I generated table t using the same way you used in the thread.
try sending the result again as below

423 hsecs
229 hsecs
run 1 ran in 184.72% of the time

PL/SQL procedure successfully completed.

> pause

> select a.name, b.value-a.value run1, c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

NAME
      RUN1
--------------------------------------------------------------------------------
----------
      RUN2       DIFF
---------- ----------
LATCH.session allocation
         1
         0         -1

LATCH.undo global data
         1
         0         -1

STAT...cluster key scans
         2
         1         -1

STAT...cursor authentications
         0
         1          1

STAT...hot buffers moved to head of LRU
         2
         1         -1

STAT...opened cursors current
         3
         4          1

STAT...session cursor cache hits
       100
       101          1

STAT...free buffer requested
         3
         2         -1

LATCH.active checkpoint queue latch
         2
         0         -2

LATCH.redo writing
         6
         4         -2

STAT...table fetch continued row
         2
         0         -2

STAT...parse time cpu
         1
         4          3

STAT...redo entries
        16
        13         -3

LATCH.redo allocation
        15
        19          4

STAT...cluster key scan block gets
         2
         6          4

STAT...db block changes
        27
        22         -5

STAT...session cursor cache count
         5
         0         -5

STAT...enqueue releases
         7
         2         -5

STAT...enqueue requests
         8
         2         -6

LATCH.enqueue hash chains
        12
         4         -8

LATCH.library cache load lock
        10
         2         -8

LATCH.messages
        14
         5         -9

STAT...opened cursors cumulative
       119
       108        -11

LATCH.enqueues
        24
         8        -16

STAT...rows fetched via callback
        18
         0        -18

STAT...execute count
       133
       112        -21

STAT...calls to get snapshot scn: kcmgss
       134
       112        -22

STAT...parse count (total)
       133
       108        -25

STAT...redo size
     21524
     21480        -44

LATCH.checkpoint queue latch
        74
        27        -47

LATCH.row cache objects
       126
        78        -48

STAT...buffer is pinned count
        49
         0        -49

STAT...table fetch by rowid
        80
        13        -67

STAT...table scans (short tables)
       100
       200        100

STAT...sorts (memory)
       308
       204       -104

LATCH.shared pool
       414
       250       -164

STAT...recursive cpu usage
       433
       235       -198

STAT...recursive calls
       974
       701       -273

LATCH.library cache
       855
       497       -358

STAT...db block gets
       424
       817        393

STAT...sorts (rows)
    827917
    826685      -1232

STAT...consistent gets
      5376
     10439       5063

STAT...buffer is not pinned count
      5357
     10436       5079

STAT...no work - consistent read gets
      5276
     10423       5147

STAT...table scan blocks gotten
      5200
     10400       5200

STAT...session logical reads
      5800
     11256       5456

LATCH.cache buffers chains
     11555
     22562      11007

STAT...session pga memory
     25800
         0     -25800

STAT...table scan rows gotten
    412300
    824600     412300

49 rows selected.

          Followup:

need to teach you a simple command:

SQL> column name format a20
SQL> column run1 format 9999999
....

be really nice if your followup was about 1/4 of its current size (and readable)

LATCHES are what I look at most.
Consistent gets -- sure, certainly.
CPU time can be very important as well -- something we are not seeing here.  For
these two queries I would be tempted to use tkprof.  runstats is good for
"approaches" (techniques), tkprof for queries.

Doing that on this and running each query 3 times we see:

select object_type
  from t
 group by object_type
having count(*) = ( select max(count(*))
                      from t
                     group by object_type )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        3      0.00       0.00          0          0          0           0
Execute      3      0.00       0.00          0          0          0           0
Fetch        6      0.07       0.22          0       1108          1           6
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       12      0.07       0.22          0       1108          1           6

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 241

Rows     Row Source Operation
-------  ---------------------------------------------------
      2  FILTER
     25   SORT GROUP BY
  11439    TABLE ACCESS FULL T
      1   SORT AGGREGATE
     25    SORT GROUP BY
  11439     TABLE ACCESS FULL T

********************************************************************************

select object_type
  from ( select object_type, cnt, max(cnt) over () max_cnt
           from ( select distinct object_type,
                         count(*) over (partition by object_type) cnt
                    from t )
        ) where cnt = max_cnt

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        3      0.00       0.00          0          0          0           0
Execute      3      0.00       0.00          0          0          0           0
Fetch        6      0.13       0.21          0        552          0           6
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       12      0.13       0.22          0        552          0           6

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 241

Rows     Row Source Operation
-------  ---------------------------------------------------
      2  VIEW
     25   WINDOW BUFFER
     25    VIEW
     25     SORT UNIQUE
  11439      WINDOW SORT
  11439       TABLE ACCESS FULL T

So, as I said -- on a CPU challenged machine, the second query is somewhat MORE
cpu intensive and this will show up as slower performance.

However, it does significantly less logical IO which is a bonus.

So -- it is a toss up.  Do you have some extra cpu power (seems you do not), the
second might be a little more scalable as it will do less latching and such.
Are you CPU challenged?  the first might be more appealing.

      Rating: 4
Best
      Practices  March 24, 2003
Reviewer:  David
       from AL
You make some very good points, as always.  I'd be interested to know if you
have some SQL 'best practices' in terms of performance.  More than likely, "You
shall use bind variables" is your #1 commandment.  I'm wondering if you have a
Top Ten.  I'm sure you probably have a list that comes to mind for performance.
Some knowledgable people also have 'readability' best practices or 'robust
coding' best practices.  For example, some might say that

insert into t1 select * from t2

is bad code because it's not explicit and if the order of the columns in the ddl
ever changes, the results could vary.  I know that you're swamped with requests
and you don't have time to write out 'proper' sql in asktom.com, so I'm not
suggesting that you should be so precise, but if you saw that piece of code in
an application, would you say it's fine the way it is?

January 25, 2003 Jayanta -- Thanks for the question regarding
      "Trying to run /usr/bin/env from PL/sql using Java Stored Procedure",
      version 9.0.1

      You Asked
Hi Tom,
I am trying to execute /usr/bin/env from inside PL/sql using java stored
procedure.

Here is my java program.
import java.io.*;
import java.lang.*;
import java.util.*;
public class testout
{
  public static int SysProps(String p_cmd,
                             String errmsg[])
  throws Exception
  {
    Properties SystemProps = new Properties();
    Runtime rt = Runtime.getRuntime();
    int rc = -1;
    try
    {
      Process p = rt.exec(p_cmd);
      int  bufsize = 10000;
      BufferedInputStream bis =
            new BufferedInputStream(p.getInputStream(), bufsize);
      int len;
      byte buffer[]  = new byte[bufsize];
      while((len = bis.read(buffer, 0, bufsize)) != -1)
      {
        System.out.write(buffer, 0, len);
        rc = p.waitFor();
        System.out.println("Return Code => " + rc);
      }
      SystemProps.load(rt.exec(p_cmd).getInputStream());
      System.out.println("Value of XXO_CUST_TOP => " +
                        SystemProps.getProperty("XXO_CUST_TOP"));
    }
    catch ( Exception e)
    {
     System.out.println("Error = " + e.getMessage());
     errmsg[0] = e.getMessage();
    }
    finally
    {
      System.out.println("Return Code from finally => " + rc);
      return rc;
    }
  }
}

Here is my PL/SQL wrapper.
create or replace package xxx_test_wrapper
as
  procedure getsysprops(p_cmd in varchar2);
end xxx_test_wrapper;
/
show errors
create or replace package body xxx_test_wrapper
as
  function jsysprops(p_cmd in varchar2,
                     p_errmsg out varchar2)
  return number
  is
  language java
  name 'testout.SysProps(java.lang.String,
                         java.lang.String[]) return integer';

  procedure getsysprops(p_cmd in varchar2)
  is
    return_value    number;
    l_cmd           varchar2(1000);
    l_errmsg        varchar2(1000);
  begin
    dbms_output.put_line('Calling jsysprops');
    l_cmd := p_cmd;
    return_value := jsysprops(l_cmd,
                              l_errmsg);
    dbms_output.put_line('Return Value => '||return_value);
    dbms_output.put_line('Error Message => '||l_errmsg);
  exception
    when others then
      dbms_output.put_line('Error = '||SQLERRM);
  end getsysprops;
end xxx_test_wrapper;
/
show errors

I login to sqlplus
sqlplus <userid>/<password>
SQLPLUS> exec xxx_test_wrapper.getsysprops('/usr/bin/env');

I get the following output
Return Code from finally => -1
Return Value => -1
Error Message =>

      and we said...
if you let the java messages get displayed -- you would discover real fast what
the problem is.  right now, the system.out.printlns are going to trace files.
using this:

set serveroutput on size 1000000
exec dbms_java.set_output(1000000)
/
exec xxx_test_wrapper.getsysprops('/usr/bin/env');
/

we'll see them.  immediately I get:

Calling jsysprops
Error = the Permission (java.io.FilePermission /usr/bin/env execute) has not
been granted to OPS$TKYTE. The PL/SQL to grant this is
dbms_java.grant_permission( 'OPS$TKYTE', 'SYS:java.io.FilePermission',
'/usr/bin/env', 'execute' )
Return Code from finally => -1
Return Value => -1
Error Message => the Permission (java.io.FilePermission /usr/bin/env execute)
has not been granted to OPS$TKYTE. The PL/SQL to
grant this is dbms_java.grant_permission( 'OPS$TKYTE',
'SYS:java.io.FilePermission', '/usr/bin/env', 'execute' )

PL/SQL procedure successfully completed.

Which has got to be the best error Message I've seen Oracle ever give in my
life...

Fixing that you'll find you need also:

dbms_java.grant_permission( 'OPS$TKYTE', 'SYS:java.lang.RuntimePermission',
'writeFileDescriptor', '' )

dbms_java.grant_permission( 'OPS$TKYTE', 'SYS:java.lang.RuntimePermission',
'readFileDescriptor', '' )

and then -- you're good to go:

ops$tkyte@ORA920> exec xxx_test_wrapper.getsysprops('/usr/bin/env');
Calling jsysprops
SSH_AGENT_PID=5333
KDE_MULTIHEAD=false
HOSTNAME=tkyte-pc
SHELL=/bin/bash
TERM=xterm
HISTSIZE=1000
.....

Review &
      Followup

      Rating:
      5
Followup  January 24, 2003
Reviewer:
      Jayanta Banerjee  from Wilsonville, OR USA
Hi Tom,
I have granted the following permissions to my user APPS.
Forgot to mention in my earlier submission.

exec dbms_java.grant_permission('APPS', 'java.io.FilePermission',
'/usr/bin/env', 'execute);
exec dbms_java.grant_permission('APPS', 'java.lang.RuntimePermission', '*',
'writeFileDescriptor');

Do I need the readFileDescriptor too to execute this command.
I can execute /usr/bin/ls -lrt, /usr/bin/pwd', /bin/sh -c /usr/ucb/printenv'
successfully.

          Followup:

run it as I showed you (with dbms java set output) and you'll SEE what you
need..

I tried to show you how to diagnose these things yourself ;)

(yes, you need readFileDescriptor, else I would not have put it there)

And as Pink Floyd used to say "careful with that axe eugene..."  execute on
/bin/sh!  hey, think I'll just:

/bin/sh -c rm -rf /*

and see what happens.... hmmm

      Rating: 5
***** just for
      the Pink Floyd reference  January 24, 2003

Reviewer:  Dan Kefford  from New Haven, CT

Man... I used to _love_ that song. I actually never had "Ummagumma", but instead
have "Relics". "Interstellar Overdrive" was another one that I really liked off
of that album. I always thought older Floyd was more adventurous.

          Followup:

before syd totally lost it...

      Rating: 3
final follow
      up  January 24, 2003
Reviewer:  Jayanta

The readFileDescriptor permission did not make any difference. And I had already
set those dbms_java.set_output and serveroutput on earlier.

The trick that made the difference was

instead of executing /usr/bin/env
I executed
/bin/sh -c /usr/bin/env.

Then it worked.

Anyway thanks for all the help.

          Followup:

DO NOT DO THAT.

/bin/sh -c rm -rf /*

do you see???  that would be what is known as "bad".  do NOT give execute on
/bin/sh -- a hole big enough for the space shuttle.

Work a little more on /usr/bin/env - give it a go, you do NOT want to use
/bin/sh, trust me.

      Rating: 3
Keep it
      going  January 24, 2003
Reviewer:  jayanta

When I check the dictionary object user_java_policy,
I don't see any execute permission given to /bin/sh.

But it runs. I am a little confused. I gave execute only to /usr/bin/env to my
APPS account.

Anyway this has to be bundled with Oracle Applications which is a different
animal. He uses listener (I suppose) to make connection to the database. That
opens up a entire box of worms.

I am able to create a trace file of my program when it runs from inside the
application and it does set some environment variables. Of course not the ones
which I want.

Lastly, a word on rm -rf. I KNOW THE CONSEQUENCES. It was fun exchanging emails
with you.

          Followup:

if this is to be bundled with Oracle apps -- and you have it setup so that
/bin/sh can be run -- well, stay away from my apps machines PLEASE.

security hole = as big as a space shuttle = true

the fact you ran /bin/sh *implies* it has been granted so.

You really need to do a little more here -- really.  Especially if you want to
drop this in someones Oracle Apps environment.

      Rating: 3
Final
      Say  June 10, 2003
Reviewer:  Jayanta
      Banerjee  from I am in a small part of this huge world. That
      place is in Portland, OR USA.
Hi Tom,
The problem was with the database version. When I used to get the error by
running /usr/bin/env, the database version was 9.0.1.3. When we patched it to
9.0.1.4 the same code started working fine. So now I do not have to use /bin/sh
-c anymore. Life is much better.

Also one of my friends pointed out that APPS ( a powerful Oracle Applications
Database schema) has JAVASYSPRIVS which enables it to run pretty much any
command and you do not have to grant explicit permissions for that.

January 25, 2003 George -- Thanks for the question regarding
      "forms locking question", version

      You Asked
I have a main-detail form. I want to stop two people
from editing the same screen at the same time. Consider the main

table record 10 has 5 records in the detail table.  All these
records are available for editing if a user goes to record 10 in

main table. If a user edits any one of the records that appear
on
the screen another user should not be permitted to do any
editing
in any of the 6 records(one record of main table and 5 records
of
detail table) that appear on the screen.
How can I do this?

      and we said...

program your own ON-LOCK trigger on the detail form.  Lets say
you have an DEPT - EMP form such that the master record is the
DEPT row and the details are the EMP rows.  Say DEPT has a
primary key DEPTNO and EMP has a foreign key to DEPT called
DEPTNO (just like scott/tiger has)

If you override the ON-LOCK trigger on the EMP and DEPT blocks
and put in logic similar the following:

declare
  res_busy exception;
  pragma exception_init( res_busy, -54 );
begin
    for x in ( select *
                 from emp
                where deptno = :EMP_BLOCK.deptno
                  FOR UPDATE NOWAIT )
    loop
        null;
    end loop;
    Lock_Record;
end;

(you'll need to change EMP_BLOCK to DEPT_BLOCK or use one of the
builtins to get the current block name of course...)

that'll do 2 things:

- it'll lock ALL of the deptno records in the emp table for that
deptno.
- it'll still do the more detailed lost update detection forms
does in the normal lock_record builtin.

Review &
      Followup

      Rating: 3
In Other Case
      Lock a record Detail in Dev. 2000  January 24, 2003

Reviewer:  Zorro_Gris  from Mexico
That schema is useful but anybody can help me with my case?...

In the above schema suppousse that we have in the emp block (Detail Block) a
payment field, where is a check box item indicating that this employee recived
his payment, and I have to lock the record but the others records that are
unckecked I still can update the amount of payment until I check the payment
field.
I am Using Developer 2000 R. 2.1 for Win 95/NT

Thanks.

          Followup:

huh?

the amount of what payment on whose record??

      Rating:
      3
Locking  January 26, 2003
Reviewer:
      Asif Momen  from Riyadh
Hi,

  As Tom described above, you could add one more condition on the select
statement in the ON-LOCK trigger, something like this
select  ....
from ....
where dept_no = :dept_blk.dept_no
and payment = 'YES'
for Update NoWait;

  Alternatively, you could use the forms built-in's like

When-New-Record-Instance (Block Level Trigger)
-------------------------
If Get_Item_Property('emp_blk.payment', database_value) = 'YES' Then
Set_Item_Instance_Property('emp_blk.payment', current_record, update_allowed,
property_FALSE);
else
Set_Item_Instance_Property('emp_blk.payment', current_record, update_allowed,
property_TRUE);
end if;

==> Get_Item_Property('emp_blk.payment', database_value) will ensure that the
record is locked when queried from the database, while you may check and uncheck
the record with value 'NO'.

  This is what I have understood, let me know if I'm wrong.

Regards,

Asif Momen.

      Rating: 4
More
      feedback  January 28, 2003
Reviewer:  eric
      givler  from harrisburg, pa
I think the problem that is occurring is that he is able to edit the details
(any of them) and then no one else should be able to edit ANY other detail.

If that's the case, why not just change the form generated querys to issue an
execute_query( for_update, nowait) on the detail block, then all the details are
automatically queried and locked, and if the master is touched, it will be
locked too.

This might be overkill though, and that's where tom's suggestion comes in.  IF
you edit the master, but want the details locked too, you have to reprogram the
on-lock trigger at the master level.  If you want every other detail locked too,
then should probably reprogram the details on-lock trigger in a similar manner.

January 25, 2003 Guru -- Thanks for the question regarding
      "Several Doubts", version 7.3 & above

      You Asked
Dear Tom,

   I have several doubts.  Pl. clarify

1) I have declared purity level for packaged functions.  But i
don't know whether i can declare purity level for Stand alone
Stored procedures & functions.  Pl.  tell me whether i can
declare purity level for stored procedures & functions.  I also
read that a function that is being called in the sql statement
must have a purity level WNDS.  Why is it so?

2) Can you please tell me what are the areas to be looked in for
performance tuning.  If you can suggest some online
documentation, it will be very good.

3) After quering a multi record block in a form if i delete some
records, update some of them & insert some records, in what
sequence the records will be applied to the database.  I know
that Deleted records will be applied first.  I don't know if
update is done next or insert.  Pl. tell me in a detail why it
is done in that sequence.

      and we said...
1) no you cannot, you do not need to.  It is automatically
computed for you at compile time for standalone functions and
procedures based on what they call and what they do.  This
cannot be done for packaged procedures, so in 8.0 and before you
must do so.  In Oracle8i and up, it is no longer mandatory.

A function called from SQL must promise at least to not Write to
the database (WNDS).  It is "protective" -- for example, given
the following query:

   select * from T where x = function;

If function writes to the database state, how many times will it
be called?  It totally depends on the query plan generated by
the optimizer.  It may be called ONCE per query, it may be
called once per ROW per query.  Further in:

   select * from T where x = function and y > 5;

Will -- I can make it so that against the same exact set of
data, x = function NEVER gets call and later, without changing
the data or the query, x = function gets called for each and
every row.  All I need to do is use different optimization
plans.

Since there is this ambiguity, and the results of executing the
same exact query against the same exact data can result in two
totally different results we do no permit it.

In Oracle8i, we do via AUTONOMOUS transactions.  See

http://osi.oracle.com/~tkyte/autonomous/index.html
for more info.

2) see

http://technet.oracle.com/doc/oracle8i_816/server.816/a76992/toc.htm
Especially chapter 6.  That is where you will get your biggest
bang for the buck.

3) forms applies your changes to the database in the same exact
order you performed them on screen.  In your case, it'll delete,
update, insert.
Review & Followup

      Rating: 2
Question on SQL
      coding standards:  January 24, 2003
Reviewer:
      Matt  from Australia
Chapter 13 of the Performance/Design doco reference says:

"Most Oracle products convert the SQL before passing statements to the database.
Characters are uniformly changed to upper case, white space is compressed, and
bind variables are renamed so that a consistent set of SQL statements is
produced."

This seems like a pretty good thing to do. Does it really happen?

I imagine this would involve setting up a function to accept and manipulate and
format the statement according to some rules. Maybe even insert some comments in
the statement to identify that the statement came from a particular application.
This function could also replace any string literals to enforce the use of
binds, AND provide an option to switch this replacement off for values that
filter on particular columns (so the optimiser can use any histogram data). Over
all it seems a pretty neat approach to generated sharable SQL provided that you
can police developer to ensure that all SQL and PL/SQL blocks are called via
this interface.

What are your thoughts?

Obviously this adds an overhead to each SQL statement BUT look at the advantages
in the shared pool.

          Followup:

PLSQL does it.

developer does it.

not much else does it.  if you write java -- you have to do it, if you write vb,
you have to do it.  If you use a tool -- we do do it.

January 25, 2003 Siba -- Thanks for the question regarding "Hybrid
      Replication", version 8.1.7

      You Asked
Hi,

I have situation in which I want to configure multimaster as well as updateable
snapshot replication (hybrid configuration). Lets say We have 2 DB's DB1 and
DB2.

1: Can I keep an updateable snapshot site of DB1 (Site 1) in DB2 (Site 2) and
DB2 in DB1. Also at the same time have DB1 and DB2 configured for multimaster
replication.

2: If DB1 (Site 1) goes down, and I change master site for Snapshot of DB1 to
DB2. Will the "changes/updates" available in updateable Snapshot of DB1
propagate to DB2 (Site 2).

Is the above scenario possible ? Are there any other alteratives to it.

Thanks in Advance
Siba

      and we said...
1) good gosh -- why????  you want the system to run really slow??

A table will either be multi-mastered OR it will be an updatable snapshot.  It
will not be both.

So, you can do a hybrid such that:

D1.T1 is multi-mastered with D2.T1
D1.T2 is an updatable snapshot of D2.T2
D2.T3 is an updatable snapshot of D1.T3

but thats it.

2) a snapshot cannot be remastered.  Snapshots are all at different points in
time.  That cannot be done.

If you are looking for failover/disaster recovery -- you are looking at the
WRONGEST technology in the world.  replication isn't appropriate for this.
Standby databases - -they are.
Review &
      Followup

      Rating: 4
Thanks for the
      input  July 26, 2002
Reviewer:  Siba Prasad
       from Delhi
Hi,
I would like to thank you for the input.
But I have this situation. I have 2 DB sites, load balanced at the application
level. So a user may be accessing data from DB1 and some other time he may be
accessing data from DB2. This means both the databases should be in Sync. We are
considering Async Multimaster replication. This means changes from DB1 to DB2
and vice-versa is done in a store-forward mechanism. If one of the site goes
down say DB1 then the updates made on DB1 which are not available on DB2 are not
visible to user till the time DB1 is not available.

Now How do we ensure that these changes are available on DB2 if the downtime of
DB1 is more or till the time standby for DB1 is made the active. What solution
would you propose
for this.

Regards
Siba

          Followup:

why?  You do realize that in order to do this system BOTH MACHINES will have to
be BIGGER then a single machine with a disaster recovery site would be?
Replication is NOT for DR.

Consider this.  Suppose you do 100 tps (transactions per second).  Now, if you
use replication, you'll double the size of each transaction ( to populate the
deferred queue ).  So, you have to have a machine capable of doing 200 tps
actually (2x the work).

So, you decide "hey, I'll load balance", now I need two machines that can do
100tps each right?  WRONG.  You need two machines that can each do 200tps since
each will be doing ITS 100tps PLUS the others 100tps!

So, you need two boxes that can do 200tps and a really fast network.

Now, suppose you decide to use RAC (real application clusters) instead.  You now
need two boxes that each can do 50tps.  RAC gives you high availability and load
balancing immediately - out of the box -- no extra design or thought about it.
Now, suppose further you want a disaster recovery box.  Enter data guard and a
standby database.  This box only need to do 50tps (or even less actually) to
keep up and keep a copy.

Replication is the wrongest answer in the world for your business problem

Using async replication by definition the data is different in the 2
databases.  If one is down, its changes are NOT AVAILABLE.  Period

You are looking at the wrong technologies here!

      Rating: 4
How to choose
      replicate strategy  January 25, 2003
Reviewer:
      Abdal-Rahman Agha  from Yemen
Hi Tom,

  If I have one center (main) Database and other 5 databases (branches), One
main schema in the main called "main" and one application. The same application
will be distributed among all my branches. But the data differ from each branch.
Should I use the same name of the schema in all branches or I have to use
different names so as to replicate  some of my tables from the center as
snapshot and multi-master replicate other tables between the branches and the
center? Please help.

          Followup:

you would read the documentation on replication first (there is plenty of it).

There you would find that you build the schema once and then we replicate the
schema for you (tables, procedures, triggers, indexes, etc).  We manage not only
the replication of the data but the schema itself.

Or you are using the create snapshot command to replication slices of data --
there using a common naming convention (eg: Table T on master is snapshot T on
copy) would only make sense as you have the same application -- it is the same
data, just a different slice or subset of it.

January 25, 2003 Brij -- Thanks for the question regarding "How to
      change the value of MAX_ENABLED_ROLES", version

      You Asked
How to change the value of MAX_ENABLED_ROLES
parameter in Oracle 8i on WIN NT Operating
system.

How to view the process information with the
help of oracle database configuration assistant
(After installing Oracle Application Server
sqlnet.ora file of Oracle 8i RDBMS is missing
so sqlnet.authentication_services paramter can't be set).
Please suggest the solutioin

      and we said...
cd $ORACLE_HOME\admin\$ORACLE_SID\pfile
edit the init.ora file and add:

max_enabled_roles = 100

stop and restart the database (eg: from the command line,
svrmgrl [enter], connect internal [enter], shutdown [enter],
startup [enter])

sqlnet.ora is not needed for dbassist?  I do not have one, all I
need to know is my internal password.  What is the error you are
getting -- or do you not know the internal password?

You can reset the internal password by using the orapwd utility.

Review &
      Followup

      Rating: 3
maximum
      roles  January 25, 2003
Reviewer:  Abdal-Rahman
      Agha  from Yemen
I am surprised why Oracle limited the max_enabled_roles to 148 (til 9.2)!!! In
my case I need over the 148 roles to be enabled. While in some other parameters
there are very great quantities that we do not need even? Please, reply why? Or,
explain the philosophy behind that.
Thanks in advance.

          Followup:

sometimes things "just are the way they are because"

for a single user to have more then 148 roles that need be concurrently active
would imply to me that you do not have a handle on "least privileges" to say the
least.  148 SIMULTANEOUS roles???

January 25, 2003 Sridhar -- Thanks for the question regarding
      "Index table space", version 8.1.7

      You Asked
When i create a user (DBA for that project) in the datbase I specify the default
table space and temp table space for that user. When the user creates objects
all the objects are getting created in the default table space as expected.
I have an index table space INDX1 created and gave qouta for the user. When the
user run the 'CREATE INDEX ...' with out specifiyng the table space INDX1 the
index gets created in default table space as expected.

My questions is is there any way I can specify when creating the user or
globally that when ever an index gets created by the user(s) it automatically
goes to the index table space INDX1.

Thanks
Sridhar

      and we said...
Nope -- and you know, you don't need to have indexes goto one tablespace and
data to another.

I would recommend this for "ease of use"

o create a tablespace using locally managed auto allocated extents for an
application.
o create everything in that tablespace for that application.

done -- easy, trivial.  To evenly distribute IO, I use file system striping.
thats all...

for most small/medium applications (tables less then 1-2gig in size each), that
would be more then sufficient.  Now, if that single tablepace gets to be
10-20gig, we might reconsider that approach -- but then we would segment not by
object type but rather by SIZE.
January 25, 2003 Sean -- Thanks for the question regarding "Read
      consistency accross cursors in one procedure", version 8.1.7

      You Asked
I am looking for read consistency across multiple cursors in a packaged
procedure.  In the past I have opened the cursors that I wanted to be consistent
at the start of the procedure, used them, and closed them at the end.  I am
starting to think that the time the first cursor takes to open, and resolve it's
result set is making the subsequent cursor inconsistent, although this seems to
have worked 99% of the time.

Example:

DECLARE
  CURSOR Cur1 IS
    SELECT SUM(X) FROM A WHERE SummaryFlag = 'N';
  CURSOR Cur2 IS
    SELECT ROWID FROM A WHERE SummaryFlag = 'N';
BEGIN
  OPEN Cur1;
  OPEN Cur2;
  .
  FOR Rows IN Cur1
  UPDATE ASummary
  .
  .
  FOR Rows IN Cur2
  UPDATE A SET SummaryFlag = 'Y' WHERE RowId = Cur2.ROWID;

I have had a few occasions where the summary table does not contain the
information that has now been flagged as summarized.

Does opening the cursors one right after the other guarantee a consistent result
set, and if not why?  Will using "ALTER TRANSACTION ISOLATION LEVEL
SERIALIZABLE" fix this?  How can I set my ISOLATION LEVEL and ROLLBACK segment
at the same time?

Thanks in advance.

      and we said...
serializable is what you are looking for -- I would say that picking a rollback
segment is a terrible thing to do (hey, I have said that!  even put it in
writing in my book).  So, here are my suggestions:

1) So, one solution would be to just set serializable and erase all lines of
code that specify a rollback segments.  You should do this regardless, just a
bad practice...

2) Another would be to use an autonomous transaction to open the result sets at
point "T" in time.  Consider:

ops$tkyte@ORA817DEV> create or replace package demo_pkg
  2  as
  3
  4      procedure do_stuff;
  5
  6  end;
  7  /

Package created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> drop table t;

Table dropped.

ops$tkyte@ORA817DEV> create table t ( x int, summaryFlag char(1) );

Table created.

ops$tkyte@ORA817DEV> insert into t select rownum, 'N' from all_users where
rownum < 10;

9 rows created.

so, we have nine rows in there...

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace package body demo_pkg
  2  as
  3
  4  cursor c1 is select sum(x) from t where summaryFlag = 'N';
  5  cursor c2 is select rowid from t where summaryFlag = 'N';
  6
  7  procedure slip_some_in
  8  is
  9      pragma autonomous_transaction;
 10  begin
 11      insert into t select rownum*10000, 'N' from all_users;
 12      commit;
 13  end;
 14
 15
 16  procedure open_cursors
 17  is
 18      pragma autonomous_transaction;
 19  begin
 20      execute immediate 'set transaction isolation level serializable';
 21      open c1;
 22      slip_some_in;
 23      open c2;
 24      commit;
 25  end;

note how I slipped new rows in there in between the two opens -- in another
transaction tho, since we are serializable IN THIS OPEN code only -- we won't be
able to see those changes

 26
 27
 28  procedure do_stuff
 29  is
 30      l_sum    number;
 31      l_rowid rowid;
 32  begin
 33      open_cursors;
 34
 35      loop
 36          fetch c1 into l_sum;
 37          exit when c1%notfound;
 38          dbms_output.put_line( 'sum = ' || l_sum );
 39      end loop;
 40      close c1;
 41
 42      loop
 43          fetch c2 into l_rowid;
 44          exit when c2%notfound;
 45          dbms_output.put_line( 'rowid = ' || l_rowid );
 46      end loop;
 47      close c2;
 48  end;
 49
 50  end;
 51  /

Package body created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> exec demo_pkg.do_stuff
sum = 45
rowid = AAAMhOAAHAAAk3KAAA
rowid = AAAMhOAAHAAAk3KAAB
rowid = AAAMhOAAHAAAk3KAAC
rowid = AAAMhOAAHAAAk3KAAD
rowid = AAAMhOAAHAAAk3KAAE
rowid = AAAMhOAAHAAAk3KAAF
rowid = AAAMhOAAHAAAk3KAAG
rowid = AAAMhOAAHAAAk3KAAH
rowid = AAAMhOAAHAAAk3KAAI

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> select count(*) from t;

  COUNT(*)
----------
        61

so the cursors, opened as of the same points in time, see consistent results

3) use materialized views to maintain these summaries and erase ALL of your code
:)  That is what materialized views do -- no more flags, no more code, just let
the database do what the database does....
January 25, 2003 Hatem -- Thanks for the question regarding
      "Upgrading to 8i", version 8.1.7

      You Asked
Hi Tom
i'm going to upgrade from 8 to 8i, i want to use locally managed tablespaces ,
the default when creating new 8i db is dictionary managed tablespaces what
should i do ? should i migrate these new tablespaces to locally managed ts and
use import/export ?
whats the best way to use locally managed ts in new 8i db, migrate from 8.0.X to
8.1.7?
thanks

      and we said...
why would you go from unsupported to soon to be unsupported? Why would you use
software that is 3 releases old and soon to be 4 releases old?

sigh.....

I would upgrade the database "as is" and then over time use alter table move and
alter index rebuild to move existing objects from DMTs to LMTs (using auto
extent allocation)

I wouldn't do it into 8i -- that is for sure.  To go through the pain of
upgrading everything -- only to be instantly and already out of date?
January 25, 2003 Ajeet -- Thanks for the question regarding
      "Unusable Index on a Partitioned table", version 8.1.7

      You Asked
Tom - I have a partitioned table. And there is non unique and non partitioned
index --PART_IE1 on this table (which I plan to partition ).
This table is being loaded daily using SQL Loader Direct path option.Everything
has been working ok since quite some time.Then we got an error -- index PART_IE1
is unusable.And data load failed.
I added skip_unsed_indexes = true in sql loader control file script to avoid
load failure ,and it worked.But still this index becomes unusable.We do re build
after each load.
There are few other details too -
1. we disbale constraints before load.
2. we trucate the particular partitions before load.
3. we enable constraints after load.
4. we rebuild the index after the load.
My Question is
1. When and why an index becomes unusable.
2. If I will partition this index , will it help me. or will I not get this
error.
I have searched this site and read through your book but i have not found an
answer.yes i have not gone through oracle documentation which I will.
Thanks for your help.

--Ajeet

      and we said...
well, a global index will definitely become unusable if you truncate one of the
underlying partitions.

Think of it this way "If I were Oracle and you did operation X -- would I be
able to maintain the index".  99.99% of the time you should be able to answer --
it is the tricky (but very uncommon 0.01%) that we have to think about it.

If you know how a globally partitioned index works (which is what you have, it
is just in one partition) -- you should be able to make the leap that "hey, the
index will be unusable if I just wipe out part of the underlying data with a
truncate".

One of the most subtle ones might be if you have a UNIQUE index -- direct path
load -- but load some duplicates.  They will load into the segment and when
sqlldr tries to update the index at the end, it'll fail -- and mark the index as
unusable.

Now, if you have a locally partitioned index -- and you truncate a partition --
oracle won't have to mark it unusable, we can simply truncate the table
partition AND the index partition and they are in sync.

Does that mean "go out and do a local partitioned index" -- no way, it might
KILL your query performance....

Review &
      Followup

      Rating: 5
So If I
      partition this index -- i will not have to rebuild it
      ?  January 25, 2003
Reviewer:  Ajeet
       from cincinnati,oh
Tom - Thanks for help.
Per your answer -- is it correct to conclude that if I partition this index , I
will not have to rebuild as it will not be set unusable ( unlike in the case of
a global index). Another thing is why it is becoming unusable even I have a
explicit re-build statement in my script ?

          Followup:

it becomes unusable the second you truncate the partion, stays that way until
you rebuild it.

making it locally partitioned will stop that HOWEVER -- big HOWEVER, it could
literally mess everything else up.  Make sure you understand how partitioning
works -- how your existing queries use this index -- before doing anything.

Else you could turn a single index range scan per query into N index range scans
per query -- causing your LIOs to go up by a factor of N, causing performance to
go down the tubes.

January 26, 2003 Patrick -- Thanks for the question regarding "How
      to enter into an INACTIVE state", version 9.0.1

      You Asked
I have a PL/SQL (daemon) procedure that looks like this:

DBMS_ALERT.REGISTER(alert_name);
loop
   select
   update
   insert
   etc.

   DBMS_ALERT.WAITANY(alert_name, msg, status, sleep_secs);
   if status != 1 then
     exit;
   end if;
end loop

The purpose of this procedure is to periodically check a table for some things
to do, do them, and then sleep until it has to do it again.  While it is
sleeping, a user can send an alert to the process and shut it down.

This all works just fine.  The problem is that this procedure is always ACTIVE.
This makes it difficult to shut down the database because "shutdown immediate"
waits for ACTIVE processes to complete.

My question is, is there a way to "sleep" while in an INACTIVE state?  The
alternative is to provide the DBA with a script to assign to a database shutdown
trigger that will send out the shutdown alert to this process, but I would
rather not have to be dependent on this.

      and we said...
shutdown immediate does not wait for actives?

shutdown normal does.  Consider:

ops$tkyte@ORA920> exec dbms_alert.register( 'x' );
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> variable n varchar2(25)
ops$tkyte@ORA920> variable m varchar2(255)
ops$tkyte@ORA920> variable s number
ops$tkyte@ORA920> exec dbms_alert.waitany( :n, :m, :s );

at this point, connected as sysdba in another window and did shutdown
immediate.  after a very short period of time:

ERROR:
ORA-01089: immediate shutdown in progress - no operations are permitted

BEGIN dbms_alert.waitany( :n, :m, :s ); END;

*
ERROR at line 1:
ORA-20000: ORU-10015: error:3 waiting for pipe message.
ORA-06512: at "SYS.DBMS_ALERT", line 148
ORA-06512: at "SYS.DBMS_ALERT", line 281
ORA-06512: at line 1

So, if shutdown immediate isn't shutting down immediately -- you have a
different problem (eg: a long running transaction is being gently rolled back
and you are just waiting on it)

that block of code, by definition, will be active always -- until it RETURNS
from the database.  The client running this block of code is the only thing that
can make us go inactive.

Review &
      Followup

      Rating: 4
I left out some
      important details  January 26, 2003
Reviewer:
      Patrick Mealey  from Eden Prairie, MN USA
My DBA had told me about the ACTIVE session/shutdown immediate thing which got
me off track.  Your example got me back on track.  The details I left out are
significant to my problem.  Consider this fragment:
set serveroutput on size 1000000
declare
   alert_name VARCHAR2(30);
   alert_msg  VARCHAR2(1024);
   alert_status NUMBER;
   timeout NUMBER;
begin
   alert_name := 'foo';
   timeout := 30;
   dbms_alert.register(alert_name);

   loop

      begin

         --do various user defined actions here

         dbms_alert.waitany(alert_name, alert_msg, alert_status, timeout);
         if alert_status != 1 then
            dbms_output.put_line('ending normally');
            exit;
         end if;
         dbms_output.put_line('looping');
      exception
         when others then
            --log the exception and keep going
            dbms_output.put_line('ignoring exception');
      end;

   end loop;

exception
   when others then
      dbms_output.put_line(sqlerrm);
end;

The code ignores exceptions in the loop because it is performing various user
defined SQL tasks which may throw exceptions.  These exceptions cannot be fatal
to the loop because this is supposed to be a daemon-like process.  The
unfortunate side effect is that it ignores the exception caught during database
shutdown which then causes a shutdown hang.
I will move the exception handler above the DBMS_ALERT.WAIT line to let any
excpetions thrown during the WAIT get handled.

          Followup:

Your code should be:

set serveroutput on size 1000000
declare
   alert_name VARCHAR2(30) := 'foo';
   alert_msg  VARCHAR2(1024);
   alert_status NUMBER;
   timeout NUMBER := 30;
begin
   dbms_alert.register(alert_name);
   loop
      begin
         BEGIN
              --do various user defined actions here
         exception when others then
               --log the exception and keep going
               dbms_output.put_line('ignoring exception');
         END;
         dbms_alert.waitany(alert_name, alert_msg, alert_status, timeout);
         exit when ( alert_status != 1 );
   end loop;
end;

Your code goes into an infinite loop if the user defined action flings an
exception time after time even in the BEST of situations.  you had:

loop
    user code

    dbms_alert code
end loop

if user code flung an exception, you would goto the bottom of the code and do
user code all over again.  having dbms_output in there is not really useful as
no one will ever see it.

January 26, 2003 Steve -- Thanks for the question regarding
      "PeopleSpoft with RAC on Win2k", version 9014

      You Asked
Hello,
Perhaps somewhat OS related but perhaps you can help.

I am looking at a configuration where the existing DBA's are saying that they
cannot implement RAC on WIN2K as they need 80 raw devices
for PeopleSoft as their configuration (HRMS, Payroll and Portal)
requires 80 tablespaces all told. They are saying that this is not possible in
WIN2K as the number of raw devices are limited to the number of available drive
letters so consequently they could not
create more than 26 raw devices.

Am I correct in saying that they are not right? Don't we simply create extended
partitions and then logical partitions (without drive letters) within thus not
limiting us to the number of drive letters?

Also I understand OCFS is the new deal for 9.2. We might consider that if we can
get the APP upgraded to it's latest version for compatibility. It seems to me
that OCFS is much easier than raw, have you experimented with OCFS yet and do
you have any comments to offer on it?

tks

steve

      and we said...
Umm, if they couldn't do it with raw, they would be hosed with cooked files too
wouldnt they?????

there are two ways to refer to devices with windoze.  The old fashioned c:, d:
etc.  Or the raw device way (for raw) or UNC for cooked....

tell them to read:

http://technet.oracle.com/docs/products/oracle9i/doc_library/901_doc/win.901/a90164/apb.htm#622755
and ask them to point to the drive letter in that create tablespace command.
Then tell them to page up to the top of that chapter and read it from start to
finish.

Review &
      Followup

      Rating: 5
tks
      tom,  January 26, 2003
Reviewer:  Steve
      Montgomerie  from dallas/orlando

      Rating: 5
OCFS on single
      node.  January 27, 2003
Reviewer:
      A reader
Does some one know if OCFS is OK/SAFE to use on a single node as a prefered FS
for Linux/Oracle configuration. I was wondering if it will be more robust in
case of a single server configuration . I know that it is mainly was developed
for RAC but I tried to play with it on a single machine and it looked preaty
good to me.

January 26, 2003 Rene -- Thanks for the question regarding "Keep
      pool and multiple versions of blocks", version 8.1.7

      You Asked
Hi Tom,

Firstable I would like to thank you for your formidable contribution to our
Oracle constellation.

Question:
After defining a table (data + index) in the keep pool, we are still getting
physical reads.
We are performing select and update on this table.
I presume that the reason is a too small keep pool overfilled by multiple
versions of the same blocks ? Does it make sens ?
Is it possible to check how many versions of a specific block I have in the keep
pool ? Do you have some experience/recommendation about keep pool size according
to table size ?

Many thanks,
Rene

      and we said...
v$bh will help you do that

physical IO is ok,
physical IO is unavoidable,
physical IO is not something to run away from
physical IO is something you want to have evenly distributed
physical IO is something we need to do when we don't find the block there --
which happens even on a read only table in a keep buffer pool 5 times the size
of the objects.  You will read the data AT LEAST once.

But on a heavily modified object, we'll be flushing blocks to disk as part of
normal processing.  It is expected.  The theoretical goal of 0 pio is just not
really achievable.
January 26,
      2003 bhabani -- Thanks for the question
      regarding "Questions", version 8.1.6

      You Asked
1] is there any file size limit of the exported dump file? will specifying FILE
and FILESIZE parameters makes it possible to export any size of
database(schemas)? is there any limitation ?

2] i went for a COMPUTE STATISTICS and the num_rows column value for the table
is different from COUNT(*) - i suppose COMPUTE STATISTICS go for an FTS then why
the difference - is there any approximation here alike ESTIMATE STATISTICS
(Even DBMS_STATS.GATHER_SCHEMA_STATS for the corresponding schema the table
belong to does not give correct rowcount)

Thanks and Regards

      and we said...
1) depends on your OS and version of Oracle.  Generally the 32bit releases will
have a 2gig filesize.  64bit releases -- no practical limit (like really really
big).  using file and filesize is one method of avoiding a 2gig physical limit,
yet.  exporting to a named pipe on unix is another (easier) method.

2) compute statistics would be accurate as of a point in time.  immediately
after the analyze starts -- people could be inserting/deleting from the table --
changing the answer (changing the count).  statistics are a POINT IN TIME
PICTURE of the object.

It gives the correct rowcount, it is just a point in time statistic, only
relevant at the exact instant it was computed.
Review &
      Followup

      Rating:
      5
  January 26, 2003
Reviewer:
      A reader
Adequate explanation Tom
Thanks

January 26, 2003 Sulinder -- Thanks for the question regarding
      "Delete Function with utl_file", version 9.0.1.1.1

      You Asked
Hi Tom,
is there a function to delete files in 9.0.1.1.1 I had heard its there in 9i
Release 2.
If i need to do it in 9i release 1 how can that be done,

Thanks

Sulinder

      and we said...
In 9iR2 there is utl_file.fremove

In 9iR1 you would either

o upgrade to 9iR2
o use a java stored procedure
o write a c based external procedure

January 26, 2003 Thierry -- Thanks for the question regarding "How
      to verify impact of an index, without building the index", version 8.1.7

      You Asked
Hi Tom,

I'm wondering how I can see the impact of a new index on execution paths,
without actually creating the new index. Something like a 'virtual index'.

thanks,
thierry

      and we said...
By using Oracle Enterprise Manager which

o can create these virtual indexes
o analyze the expected change
o report back the %increase / decrease in performance you would expect.

It is part of the tuning pack.
Review &
      Followup

      Rating: 3
but what is the
      technique used to create those virtual indexes ?  January
      26, 2003
Reviewer:  thierry  from Brussels, Belgium

Also other tools claim to offer similar functionalities, and I was wondering on
what it is based. I have never seen any parameter in the create index syntax
that can do that for me.

          Followup:

ops$tkyte@ORA920> create table t ( x int );

Table created.

ops$tkyte@ORA920> create index t_idx on t(x) NOSEGMENT;

Index created.

totally undocumented.
totally unsupported.
use at your own risk.

Not giving anything away here -- a peek at v$sql during the execution of one of
these -- or a sql_trace would show you this syntax.

January 26, 2003 -- Thanks for the question regarding "Returning
      Values Not In the Table", version 9.2.0

      You Asked
Tom,

Is there any way to create a select statement with an IN clause
that returns the rows satisfying the values in the IN clause along
with those values in the IN clause but not in the table?

For example:

  select col1, col2  from tableX where col1 in (1,2);

Col1   Col2
1       Y
2      No value

Thanks,
Joe

      and we said...
select a.col1, nvl( b.col2, 'No Value' )
  from (select 1 col1 from dual UNION ALL select 2 col1 from dual ) A,
       tableX b
 where a.col1 = b.col1(+)
/

January 26, 2003 Bob -- Thanks for the question regarding "High
      CPU. Is there a way go GRACEFULLY limit CPU per user?", version 9.0.1

      You Asked
Tom,
I've set up a few profiles to test CPU limits for users on a box that is
currently CPU starved. However, what seems to happen is that the query that the
user issues gets abruptly terminated if they go over the values set in the
profile. They get ORA- messages instead of what I thought would be a slower
completion. Is there a way to set up a graceful limit of CPU, where the query
will finish, just slower and not overtake the box? The profiles for users over
Database Links also create excessive trace files when they are terminated by the
system CPU limit. Is there a way to limit the query over a DB-Link without
generating the trace? These are mainly Portal Application accessing data in
another instance.
Thanks
 Bob Maggio

      and we said...
The profiles are a resource governor -- kill it when it hits a threshold.

To restrain them from running away with resources -- you want the resource
manager:

http://technet.oracle.com/docs/products/oracle9i/doc_library/901_doc/server.901/a90117/dbrm.htm#5080
that'll let you setup a graceful CPU, Logical IO limit -- limit the max number
of active sessions in a group, control the amount of undo they can generate and
so on.
Review
      & Followup

      Rating:
      5
renice  January 26, 2003
Reviewer:
      Robert Xuequn Xu
Just a thought: suppose the database is running on a Unix system, and the
clients connect to it in dedicated server mode. Would it be a simple solution
(although unconventional) to just renice the specific server process?

          Followup:

Nope -- not only not suggested -- proactively "de-suggested"...

first, maintenance nightmare of the 1st degree.  the practicality of this is
really questionable.  it would be a full time job just finding and re-niceing
things.

second, totally unsupported.  there can be un-anticipated side effects when you
play with the OS scheduler and the database...

January 27, 2003 Venugopal -- Thanks for the question regarding
      "Cursors", version 8.1.7

      You Asked
Tom,

Does Cursors or SQL Queries build up Rollback Segments in any case? If so, can
you please explain why.

regards

      and we said...
They generally do not "build up" in the rollback segments -- they frequently USE
the contents of the rollback segments in order to generate non-blocking reads
and read consistent views of data however.

In general SELECTS are consumes of rollback segment data, not producers.

The one glaring exception to that is the SELECT ... FOR UPDATE statement.
Review &
      Followup

      Rating: 5
Thank you very
      much  January 26, 2003
Reviewer:  Vg
       from India

      Rating:
      5
  January 26, 2003
Reviewer:
      A reader
Is it true that with 'select for update' writers can block readers?

          Followup:

a select for update is an update + read.  It is not just a read.

writes block writes -- select for update, well, that is in fact another name for
a write.

makes sense that you cannot LOCK a record (select for update) that is already
locked (by another writer) doesn't it?

      Rating:
      4
  January 26, 2003
Reviewer:
      A reader
I know that writers block wtiters. The question was "can writers block readers".
Will a "update" or "select for update" will prevent anyone from just reading the
affected rows?

          Followup:

Nope,

consider update and select for update to be the same.

A normal "select" will be blocked by neither.

January 27, 2003 Craig -- Thanks for the question regarding
      "Performance Checklist", version 816

      You Asked
Is there a checklist you follow or a set of rules when a user reports a
performance problem with the database.

Thanks in advance

      and we said...
I try to figure out where it is coming from.

Tell me the problem -- we'll work backwards from there.  For example user says
"system slow"

is that

o everything in the system
o just a single application
o just a screen in an application

say they say it is the entire system.  Now I would use statspack (and I've kept
a good history of them) and I would be able to say "you know what, the
transactions per second haven't changed in 2 months, the top sql is still the
same top sql and doing the same amount of work -- not the database probably,
lets start looking at the network or the end users system itself"

say they say it is a screen in the appliation -- see if you can reproduce it, if
not, might be something specific to their account (they can see lots more data
then you can or just different data).  In any case, reproduce it, then sql_trace
it and tkprof it.

Everytime it is a little different -- sort of like sluething.  If you have my
book "expert one on one Oracle" -- i discuss this and the tools I use in chapter
10 in detail
Review & Followup

      Rating: 4
Time for CPU
      upgrade?  January 27, 2003
Reviewer:
      Learner  from Pittsburgh USA
In many prior tuning Tips we learnt ways to tune DB, what are the symptoms and
methods to verify that CPU really needs to be upgraded.
e.g  hot files  can be verified from OS Sar commands.
similiarly what would confirm CPU upgrade ( for this case whole Database would
be slow and no real improvement factor from stats )
Does excessive Paging and Swapping  indicate so ??? but that might even indicate
memory problem

          Followup:

paging/swapping would indicate RAM is a bit underconfigured.

The next guy answered this for me tho, see below

      Rating: 5
To: Learner
      from Pittsburgh USA   January 27, 2003
Reviewer:
      David
If you are getting good io and memory metrics -- that is, low io, no
swapping/paging -- and still you have high cpu usage (low idle time), it's time
you think of upgrading it.

However... that would normally *not* happen in a db machine, since db apps are
nearly 100% io-bound. You would get io issues first instead.

This situation would probably indicate something is really *wrong* (os, apps,
database) -- not directly a cpu upgrade case.

      Rating:
      5
  January 27, 2003
Reviewer:
      Learner
Thanks Tom & David

January 27, 2003 Lee -- Thanks for the question regarding
      "Creating Single Row from Multiple Rows", version 8.1.7

      You Asked
I have a table that contains 2 columns, as defined below:

site_cd
related_site_cd

The table is populated from another system that maps a salesman to a specific
area, and maps an area to a certain market, and maps that market to a section,
and that section a region.  The codes are all distinct and have meaning.  For
example, a salesman code would be in the 10000 range, an area in the 20000
range, market in the 30000 range, etc.  Below I provided some sample data to
show salesman 10001 and how he fits into the different geographies:

site_cd   related_site_cd
-------   ---------------
10001     20001
20001     30001
30001     40001
40001     50001
50001     60001
10002     20002
20002     30001
30001     40001 ...

My goal is to transpose these records into a single record to insert into a
salesman_hierarchy table.  For example, the above records would look like this
in the new table:

salesman_cd  area_cd  market_cd  sect_cd  region_cd  national
-----------  -------  ---------  -------  ---------  --------
10001        20001    30001      40001    50001      60001
10002        20002    30001      40001    50001      60001

The existing code is a mess, having 5 nested IF statements and tons of code
within each of those IF statements.

Could you provide an easier way to do this?

      and we said...
Well, you could do this.

Assuming that last row is a bad row (it is a dup),
Assuming that the numbers are all postive and number(5)'s,

this might work for you:

ops$tkyte@ORA920> select salesman_cd,
  2         area_cd,
  3         to_number( substr( data,  1, 5 ) ) market_cd,
  4         to_number( substr( data,  6, 5 ) ) sect_cd,
  5         to_number( substr( data, 11, 5 ) ) region_cd,
  6         to_number( substr( data, 16, 5 ) ) national
  7    from (
  8  select site_cd salesman_cd,
  9         related_site_cd area_cd,
 10         (select max( to_char( decode( level, 1, related_site_cd, NULL
),'fm00009' )) ||
 11                 max( to_char( decode( level, 2, related_site_cd, NULL
),'fm00009' )) ||
 12                 max( to_char( decode( level, 3, related_site_cd, NULL
),'fm00009' )) ||
 13                 max( to_char( decode( level, 4, related_site_cd, NULL
),'fm00009' ))
 14            from t t2
 15           start with t2.site_cd = t.related_site_cd
 16         connect by prior related_site_cd = site_cd and level <= 4 ) data
 17    from t
 18   where site_cd < 20000
 19         )
 20  /

SALESMAN_CD    AREA_CD  MARKET_CD    SECT_CD  REGION_CD   NATIONAL
----------- ---------- ---------- ---------- ---------- ----------
      10001      20001      30001      40001      50001      60001
      10002      20002      30001      40001      50001      60001

you would consider replacing line 18 with "where site_cd = :n" if this is to be
for a single sales person....

Review &
      Followup

      Rating:
      3
Thought....  January 23, 2003
Reviewer:
      Steve  from UK
Hi Tom

Would you not create this as a view rather than create another table effectively
duplicating data?  Otherwise if the data in the base table changes then you'd
have to remember to re-populate the new table.

Just a thought and would like others views.

Regards
Steve

          Followup:

I would use it as a query -- unless I needed a view.

I would NOT create a table as a copy of it - don't even know where that idea
came from in the context of this question?

      Rating: 5
Thanks! Very
      Helpful!  January 23, 2003
Reviewer:
      A reader  from Michigan

      Rating:
      4
Table  January 23, 2003
Reviewer:
      Steve  from UK
I guess I thought the following sentence:

"My goal is to transpose these records into a single record to insert into a
salesman_hierarchy table.  For example, the above records would look like this
in the new table:"

suggested (to me) that the goal was to create an additional table.

Cheers

Steve

          Followup:

ahh -- got it.

yes, I would not suggest doing that - just run the query as you need the data.
sorry about that -- was reading my answer, not their question!

      Rating: 4
Would this be a
      good candidate for a Materialized view?  January 23, 2003

Reviewer:  A reader
Hi Tom,

Would this type of solution lend itself for creating a MW instead of a fixed
table ?

          Followup:

MW -- do you mean MV....

No, i would just query the base table -- that'll not be a changes only
refreshable thing, so it would be refreshed full every time.  That has its
issues and it gets out of sync.

There is no reason that this query, given the proper indexes, would not perform
very well for a given salesperson.

      Rating: 4
Won't compile
      in a procedure  January 27, 2003
Reviewer:  Lee
      Frankhouse  from Michigan
I modified the SQL statement a bit as follows and placed it in a procedure:

insert into geo_relt (
    SELECT to_number( substr( data,  1, 6 ) ) area_site,
           to_char( substr( data, 17, 4) || substr( data,  7, 4) ) area_cd,
           to_number( substr( data,  11, 6 ) ) zone_site,
           to_char( substr( data,  17, 4) ) zone_cd,
           to_number( substr( data, 21, 6 ) ) section_site,
           to_char( substr( data, 27, 2) ) section_cd,
           to_number( substr( data, 29, 6 ) ) region_site,
           to_char( substr( data, 35, 2) ) rgn_cd
      FROM ( SELECT
           (SELECT max( decode( level, 1, relt_site_nbr || trim(mo_cd) , NULL ))
||
                  max( decode( level, 2, relt_site_nbr || trim(mo_cd), NULL ))
||
                  max( decode( level, 3, relt_site_nbr || trim(mo_cd), NULL ))
||
                  max( decode( level, 4, relt_site_nbr || trim(mo_cd), NULL ))
             FROM st.geo_map t2
            START WITH t2.relt_site_nbr = t.relt_site_nbr
          CONNECT BY PRIOR site_nbr = relt_site_nbr AND level <= 4 ) data
     FROM st.geo_map t
    WHERE mo_lvl_nbr = 40
    )
    );

When I compile I get the following error:

PLS-00103: Encountered the symbol "SELECT" when expecting one of the following:

   ( - + mod not null others <an identifier>
   <a double-quoted delimited-identifier> <a bind variable> avg
   count c

I tried to compile this in SQL*Plus and in TOAD with the same results.

Why can I execute this statement stand-alone in SQL*Plus, but I can't compile it
in a procedure?

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3027089372477
same issue
same solution set works (view, ref cursor)

January 27, 2003 Tomas -- Thanks for the question regarding "Redo
      logs -Archived logs - do you anyway lose data?", version 8i 8.1.5.0.0

      You Asked
Hey Tom

I have thought much about hot backup and posibility still lose a
lot of data.

The achive log-funtkion is wt active.

When you make hot backup of tablespaces, the datachanges are
registered in redo logs and automaticaly archived for backup.

But when you f.ex. are backing up a tablespace, then the tables
must be offline until the operation i finished.

Therefore there must be a lot of uncomitted transactions stored
in the archived logs.

What happens after the tablespace is set online again with the
uncomitted transactions?

It mus give a lose of data?? OR ???

How often do you must make a hot backup per day to make the data
los so little as possible??

Thanks

Tomas

      and we said...
See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:271815712711
Hot backups would be useless as backups if they lost data or
prevented you from working with the data while it is being
backed up.

In Oracle a hot backup will not lose data.

It will not prevent access to any of the objects at any time
either.

Datafiles are constantly written to during the backup, committed
changes are checkpointed to this files.
Review &
      Followup

      Rating: 3
Applying
      Archived Log files  January 27, 2003
Reviewer:
      Muhammad Mustafa  from Pakistan
Dear Sir,
   I am using hot backup in archived log mode. I have copied all datafiles,
control files,redo log files, archived log files using OS utility at 27/1/03
09:24 AM.
I have restored all the files in another database at 13:00
I also restored all the archived log files generated between 09:24 and 13:00
 My procedure...
   Shutdown the database
    Copy all data files, control files, redo files, pwdimage.ora and archived
log files to their default destination.
    Startup mount
     set autorecovery on
      recover database
    alter database open;
  Successfully opened without applying any archived log files.
 But i have got no data which is added between 09:24 and 13:00 although all the
Archived log files are in the correct destination.
 How should i apply the Archived log files to get complete data.

  Please guide.
   Thanks & regards
     Muhammad Mustafa

          Followup:

...
I am using hot backup in archived log mode. I have copied all datafiles,
control files,redo log files, archived log files using OS utility at 27/1/03
09:24 AM.
.....

then you are doing a hot backup wrong!  you never backup online redo logs!!

And you are doing the restore totally wrong.  A binary backup of the controlfile
is useful only really to make a TEXTUAL create control file statement.

You want to

a) read:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76993/toc.htm
from cover to cover

b) stop backing up the online redo logs

c) to do your simulation, you would be simulating the loss of all ONLINE control
files and redo -- so follow:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76993/recoscen.htm#11848
or grab the CURRENT online control files from the "crashed" machine and follow:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76993/recoscen.htm#11724
or grab the CURRENT online redo logs AND current control files and follow:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76993/recoscen.htm#11702

but read that document from start to finish, understand it -- live it.

There is only one thing a DBA really has to get right -- and this is it.  Backup
and recovery.  You can mess up virtually everything else but if you lose my data
-- well, I'd be really mad at you

January 27, 2003 chirayu -- Thanks for the question regarding
      "about parsing", version 8.1.7

      You Asked
Dear Tom,
    I have a procedure which selects different departments
    These procedure then passes the departments retrived, one by one,
    to a second  procedure.
    This second procedure then opens a cursor for the department that is
    passed. some processing is done and then this cursor is closed.
    This means that if the cursor in first procedure  returns 100
departments,
    the cursor in the second procedure will be opened and closed for 100
    times.

   Question
   (1)    What I want to know is, is the query contained by the cursor
      hard-parsed everytime.
   (2)    If No, What should I do, so that  parsing
          etc stages are avoided everytime(dept is used as a bind
          variable, and I know you vehmently advocate the use
          of bind variables )

  Thanks in advance for 'Being There'.

  Chirayu sutaria

P.S. When is the second edition of your book coming out.

      and we said...
1) definitely not, in fact it won't even be SOFT PARSED

2) well, you should try to do it in a single query -- instead of:

  for x in ( select * from t1 )
  loop
         ...
         for y in ( select * from t2 where t2.COLUMN = x.column )
         loop

that should just be:

  for x in ( select * from t1, t2 where t1.column = t2.column )
  loop

Here is how to prove #1 though, just do this:

ops$tkyte@ORA920> create or replace procedure show_objects( p_owner in varchar2
)
  2  is
  3  begin
  4      for x in ( select * from all_objects where owner = p_owner )
  5      loop
  6          null;
  7      end loop;
  8  end;
  9  /

Procedure created.

ops$tkyte@ORA920> create or replace procedure show_users
  2  is
  3  begin
  4      for x in ( select * from all_users )
  5      loop
  6          show_objects( x.username );
  7      end loop;
  8  end;
  9  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA920> exec show_users

the tkprof shows us:

select *
from
 all_users

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.02          0          2          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch       43      0.00       0.01          0        298          0          42
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       45      0.01       0.03          0        300          0          4

parsed once, executed once -- as expected..

select *
from
 all_objects where owner = :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          5          0           0
Execute     42      0.00       0.01          0          0          0           0
Fetch    12297      4.28       6.05        121      83001          0       12255
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    12340      4.30       6.08        121      83006          0       122

parsed once executed 42 times, once for each user....

January 27, 2003 Kamran -- Thanks for the question regarding
      "Speed Factor (Partitioned Table)", version 9.2.0

      You Asked
We are designing a database schema for our organization's accounts and inventory
system. There are several stores, and we
We required info about speed in case of using partitioned table. Whether we
create separate table for each user(store)
(like parts, sales etc.) or centralized partitioned table where each partition
is dedicated for a certain store. The later strategy appeals us because we can
get categorized as well as aggregate info about our inventory, sales etc.
Thanks in advance for your reply
Kamran

      and we said...
You would need to look at your queries.

You would need to understand how your application will use the data.

You need to understand how partitioning works.

If you could do it as a bunch of separate tables -- one set for each "store", it
does sound like you would be able to easily partition by store and

o not affect performance negatively
o possibly affect performance positively
o ease administration considerably

by partitioning by store, using only locally partitioned indexes and always
including "where store = :x" in all queries (except queries of course that want
to go across stores)
January 27,
      2003 Sajid -- Thanks for the question
      regarding "Objects in pl sql", version 9.0.2

      You Asked
Hi Tom,

I have object srvc_data(srvc_id number, srvc_cost number) and srvc_table as
table of srvc_data.
I have a table SERVICE ( srvc_id number, srvc_cost number, srvc_desc, xxx, yyy,
zzz);

procedure update_list (p_srvc_table IN OUT srvc_table) as
begin
   select srvc_data(srvc_id, srvc_cost)
     into p_srvc_data
     from services;
end;

This procedure overwrite the existing data in the list with any new data that
comes from the table SERVICE. I want to retain the origin list and add new (if
any) to it.

Thanks in advance.

Regards,
Sajid Anwar
London

      and we said...
ops$tkyte@ORA920> create or replace type srvc_data as object (srvc_id number,
srvc_cost number)
  2  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace type        srvc_table as table of srvc_data
  2  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table services( srvc_id number, srvc_cost number )
  2  /

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace procedure update_list( p_srvc_table in out
srvc_table )
  2  as
  3  begin
  4          if ( p_srvc_table is null )
  5          then
  6                  p_srvc_table := srvc_table();
  7          end if;
  8          for x in ( select srvc_id, srvc_cost from services )
  9          loop
 10                  p_srvc_table.extend;
 11                  p_srvc_table( p_srvc_table.count ) := srvc_data( x.srvc_id,
x.srvc_cost );
 12          end loop;
 13  end;
 14  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into services values ( 5, 6 );

1 row created.

ops$tkyte@ORA920> declare
  2          x srvc_table := srvc_table( srvc_data(1,2), srvc_data(3,4) );
  3  begin
  4          update_list( x );
  5          for i in 1 .. x.count
  6          loop
  7                  dbms_output.put_line( x(i).srvc_id || ', ' ||
x(i).srvc_cost );
  8          end loop;
  9  end;
 10  /
1, 2
3, 4
5, 6

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating:
      5
Brilliant  January 27, 2003
Reviewer:
      Sajid Anwar  from London
Many thanks Tom for your response.

Sajid Anwar
London

      Rating: 5
Is it
      ok?  January 27, 2003
Reviewer:  Sajid Anwar
       from London
Hi Tom,
Sorry to bother you again, just a small request.
The solution you gave is what i needed. I have one
question for you. Is it ok to call update_list
procedure when i have thousands of thausand of records
in services table?

Many thanks in advance.

Sajid Anwar
London

          Followup:

Ok - sure

will it consume some amount of RAM -- definitely.

thousands -- ok, 10's of thousands, starting to get beyond where I would feel
comfortable, 100's of thousands -- nope, I would not do it.

      Rating: 5
updating
      list  January 27, 2003
Reviewer:  Sajid
      Anwar  from London
Hi Tom,

Here is the original procedure in our package:

PROCEDURE inc_std_services (
    p_CHAN_ID     IN     NUMBER,
    p_MAINTENANCE IN     VARCHAR2,
    p_RSET_ID     IN     NUMBER,
    p_SRVC_TABLE  IN OUT SRVC_TABLE
  ) AS
    v_SRVC_TABLE SRVC_TABLE := p_SRVC_TABLE;
    x_SRVC_TABLE SRVC_TABLE;

  BEGIN
    IF p_RSET_ID IS NOT NULL THEN

      IF v_SRVC_TABLE.COUNT = 0 THEN

        SELECT srvc_data(rsrv_srvc_id, null)
          BULK COLLECT INTO v_SRVC_TABLE
          FROM rule_services
         WHERE rsrv_rset_id = p_RSET_ID;

      ELSE

        SELECT srvc_data(rsrv_srvc_id, null)
          BULK COLLECT INTO x_SRVC_TABLE
          FROM rule_services
         WHERE rsrv_rset_id = p_RSET_ID
     UNION ALL
        SELECT srvc_data(srvc_id, srvc_cost)
          FROM TABLE(CAST(v_SRVC_TABLE AS SRVC_TABLE));
        v_SRVC_TABLE := x_SRVC_TABLE;

      END IF;

    ELSE

      IF v_SRVC_TABLE.COUNT = 0 THEN
        SELECT srvc_data(chas_srvc_id, null)
          BULK COLLECT INTO v_SRVC_TABLE
          FROM channel_services
         WHERE chas_chan_id = p_CHAN_ID
           AND chas_included_in_full_maint_yn = p_MAINTENANCE;

      ELSE

        SELECT srvc_data(chas_srvc_id, null)
          BULK COLLECT INTO x_SRVC_TABLE
          FROM channel_services
         WHERE chas_chan_id = p_CHAN_ID
           AND chas_included_in_full_maint_yn = p_MAINTENANCE
     UNION ALL
        SELECT srvc_data(srvc_id, srvc_cost)
          FROM TABLE(CAST(v_SRVC_TABLE AS SRVC_TABLE));
        v_SRVC_TABLE := x_SRVC_TABLE;

      END IF;

    END IF;

    p_SRVC_TABLE := v_SRVC_TABLE;

  END; /* of procedure */

I modified it using your method. Just have a look, does it require any more
change?
---------------------------------------------------------------------------------
-----------------------------

PROCEDURE inc_std_services (
    p_CHAN_ID     IN     NUMBER,
    p_MAINTENANCE IN     VARCHAR2,
    p_RSET_ID     IN     NUMBER,
    p_SRVC_TABLE  IN OUT SRVC_TABLE
  ) AS

BEGIN
    IF (p_srvc_table IS NULL) THEN
        p_srvc_table := srvc_table();
    END IF;

    IF (p_RSET_ID IS NOT NULL) THEN
        FOR v_Row IN (SELECT rsrv_srvc_id FROM rule_services WHERE rsrv_rset_id
= p_RSET_ID) LOOP
            p_srvc_table.extend;
            p_srvc_table(p_srvc_table.COUNT) := srvc_data(v_Row.rsrv_srvc_id,
null);
        END LOOP;
    ELSE
        FOR v_Row IN (SELECT chan_srvc_id FROM channel_services WHERE
chas_chan_id = p_CHAN_ID AND chas_included_in_full_maint_yn = p_MAINTENANCE)
LOOP
            p_srvc_table.extend;
            p_srvc_table(p_srvc_table.COUNT) := srvc_data(v_Row.chas_srvc_id,
null);
        END LOOP;
    END IF;
END;

---------------------------------------------------------------------------------
----------------------------

One question related to this :

PROCEDURE update_list (p_srvc_table IN OUT srvc_table)
AS
BEGIN
    IF (p_srvc_table IS NULL) THEN
        p_srvc_table := srvc_table();
    END IF;
    FOR v_Row ( -- GET THE SRVC_COST FROM SERVICES table FOR THE GIVEN SRVC_ID
IN THE p_srvc_table ) LOOP
        -- update the p_srvc_table
    END LOOP;
END;

Say,if i pass in srvc_data(100, null), srvc_data(101, null) to update_list
procedure. It should get the srvc_cost
of srvc_id 100 and 101 from the services table and update the give p_srvc_table.

Many many thanks for all your help.

Regards,

Sajid Anwar
London

          Followup:

does it work when you test it?

that would be the best way -- I've got a psuedo compiler in my head but its not
as good as writing a test case and seeing if your function performs as expected.

January 27, 2003 Steve -- Thanks for the question regarding "How
      to get real time trace info", version 8.1.7 / 9.0.3

      You Asked
Hi tom,

1) Is there a way I can do instrumentation to a single long run
SQL e.g, "insert ... select" so that I can monitor the status of sql i.e., in
select phase or in insert phase and how many rows
already inserted? just as DBMS_APPLICATION_INFO example in your book.

2) When I set sql_trace=true. oracle server will save trace info in a trace
file. Can I call some Package's API or write some SQL or pL/SQL to get real time
trace info for a long run (> 1 hour) SQL?
for example, the query is in the phase of hash join t1 and t2

Regard!

Steve

      and we said...
1) already done for you.

in one session i:

big_table@ORA817DEV> create table big_table2 as select * from big_table where
1=0;
Table created.

big_table@ORA817DEV> insert into big_table2 select * from big_table;

In another, query v$session_longops:

ops$tkyte@ORA817DEV> @printtbl8 'select * from v$session_longops where
time_remaining > 0'

SID                           : 7
SERIAL#                       : 10991
OPNAME                        : Table Scan
TARGET                        : BIG_TABLE.BIG_TABLE
TARGET_DESC                   :
SOFAR                         : 8768
TOTALWORK                     : 22822
UNITS                         : Blocks
START_TIME                    : 24-jan-2003 07:46:41
LAST_UPDATE_TIME              : 24-jan-2003 07:47:35
TIME_REMAINING                : 87
ELAPSED_SECONDS               : 54
CONTEXT                       : 0
MESSAGE                       : Table Scan:  BIG_TABLE.BIG_TABLE: 8768 out of
22822 Blocks done
USERNAME                      : BIG_TABLE
SQL_ADDRESS                   : 866E9F44
SQL_HASH_VALUE                : 1640864020
QCSID                         : 0
-----------------

PL/SQL procedure successfully completed.

And it shows me

2) v$session_longops
Review & Followup

      Rating:
      5
  January 24, 2003
Reviewer:  Steve
       from NYC, USA
Hi Tom,

Thank you so much for the answer!

1) when I run
select * from v$session_longops where time_remaining > 0,
I found sometime there is no record in a short period of time. Does that mean in
WAIT mode?

2) If I already have an excution plan for this query, How do I associate the
step in execution plan with the opname in the v$session_longops?

3) By the way, May I have your script printtbl8.

Thank you so much!

Steve

          Followup:

1) means the step in the query being processed hasn't taken 3/5 seconds.  if
your query is a multi-step query, you'll see it start and stop

2) you guess sometimes.  you try to match it up and guess...

3)
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1035431863958

      Rating:
      5
  January 27, 2003
Reviewer:  Steve
       from NYC, NYC
Hi Tom,

When I run a quert in parallel mode and I run
select * from v$session_longops where time_remaining > 0

I just get one entry for 12 parallel session, Is that Oracle desige goal or I
could set something to get all 12 parallel session display in 12 entry in the
view of   v$session_longops

Thanks!

Steve

          Followup:

In parallel, only the QC (query coordinator) makes such calls.  The servers send
statistics messages to the QC with their amount of completed cost, who
aggreagates these normalized costs and makes the call to dbms_application_info.

January 27, 2003 Jason -- Thanks for the question regarding
      "location of ocijdbc8 on Solaris system", version 8.1.6.0.0

      You Asked
Hi Tom,

I'm trying to run a JDBC enabled java app on a Solaris system
and keep getting UnsatisfiedLinkError for ocijdbc8. I can't find
this file, so I don't know what to put on the java.library.path
to fix the problem.  Do you know where this file should be
located on a Solaris system?

Thanks!

      and we said...
You need to have the Oracle client software installed in order
to use the OCI Thick drivers (that is what you are using).

If you have that installed, you would simply have your
LD_LIBRARY_PATH set to $ORACLE_HOME/lib and thats it.

If you do not have the Oracle client installed, I would
recommend you use the JDBC Thin drivers as they are 100% java
and do not require OCI at all.
Review &
      Followup

      Rating: 5
Same error on
      Windows NT  January 27, 2003
Reviewer:  Bala
      Subramanian
Hi Tom

I am getting the same error on Win NT
I do have the classes12.jar in classpath.

I do have a tnsnames entry for the service_name.

Thanks
Bala

          Followup:

then your path and or oracle_home are not set.

nothing to do with tnsnames.ora, everything to do with finding a DLL.

      Rating: 5
Same error on
      Windows NT   January 27, 2003
Reviewer:  Bala
      Subramanian
I was using 9i jdbc classes against 8.1.7 db.
When i replaced them with 8.1.7, the problem was solved.
And i did set the path to ORACLE_HOME\bin.

Don't know which one corrected the problem.

Does the oracle client/database version and jdbc driver version should be the
same for OCI?.
or for 9i driver should i use ocijdbc9.dll?

Thanks for your help.
Bala.

          Followup:

if you have the 9i thick drivers -- you need a 9i CLIENT install.

If you have an 8i client install -- you can use the 8i THICK drivers.

mixing 9i thick with 8i client or 8i thick with 9i client will never work.

January 27, 2003 Budhi -- Thanks for the question regarding
      "invalid objects", version 8.1.7

      You Asked
Hi Tom,

quite often when we are doing some release to any production database, we only
compile the procedures or packages that needs to be changed. But a problem that
appears along with this mode of operation is several other packages or
procedures which used to depend on any of these packages or procedures will
become invalid. Then we get into this confused whirl of comiling and recompiling
the invalidates packages.

Can u suggest a query which will return me all user objects based on the
dependency level, that is the object on which the maximum dependency is there
must be recompiled first.

Or is there any DBMS package that will do this for me, I knwo
DBMS_UTILITY.COMPIL_SCHEMA is not soing this for me.

Thanks and Regards

Budhi

      and we said...
Just use packages -- never procedures.

Then, just install all of the new specs.

Then install all of the new bodies.

Then, walk away -- don't both recompiling, it'll take care of itself!

Or, if you have my book -- I have a routine in there that shows you a way to do
this.  Basically, you just take the "first" invalid thing you see -- compile it,
eg:

  select * from user_objects where status = 'INVALID' and rownum = 1;
  compile it.

Just keep doing that over and over and everything will compile.  YOu need not do
them in ANY order -- just do them one at a time (and if one fails -- bail out --
or remember you tried to do it and skip it next time)

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1418403973266
is an example
Review & Followup

      Rating: 3
Compiling
      invalid objects  January 27, 2003
Reviewer:
      Logan Palanisamy  from Santa Clara, CA USA
Tom,

How about runnnig ORACLE_HOME/rdbms/admin/utlrp.sql
as SYS or SYSTEM in SQL*Plus? Won't that compile all the invalid objects?

          Followup:

you can do lots of things

Me, well, I'm lazy and don't like sitting staring at consoles -- so I would just
let it be.

but the code I provided does basically the same thing for a schema.

January 27, 2003 shanmugam -- Thanks for the question regarding
      "Logical Standby database", version 9.2.0.1.0

      You Asked
Hi Tom,

1) What is advantage and disadvantage of Logical Standby database?

2) undo_management  = Auto  or   undo_management = manual
   Which is best method?

3) What is the use of following users in 9.2 database
 ODM
,ODM_MTR,OE,OLAPSYS,PM,QS,QS_ADM,QS_CB,QS_CBADM,QS_CS,QS_ES,QS_OS,QS_WS,SH,WKPROX
Y,WKSYS,WMSYS,XDB

With best regards
shanmugam

      and we said...
1)

key advantages:

o database is opened READ WRITE while in "apply mode" (eg: it can be used to
generate reports, host another application if you like, whatever.  It is a fully
functional database)

o the tables you are protecting from production can have ADDITIONAL structures
added to them (extra indexes, materailized views, etc).  These structures are
ones you would not have in "production" since they would slow your transactional
applications down -- but the reports you run against this failover copy would
benefit from them.

key disadvantage:

o not all datatypes supported.  See the docs for a list of supported vs not
supported datatypes.

2) I like auto.

3)

ODM schemas -> Oracle Data Mining support

OE, PM, QS*, SH -> demo accounts for various features see:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96539/toc.htm
OLAPSYS -> OLAP support (owns olap metadata structures)

WKPROX, WKSYS -> ultrasearch support

WMSYS -> workspace managment support

XDB -> XML db support

see also

http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/win.920/a95493/startrdb.htm#1006823

Review &
      Followup

      Rating: 4
Logical Standby
      Database  October 08, 2002
Reviewer:
      shanmugam  from Singapore
The information is very useful to me.
Thank you very much Mr.Tom.

      Rating: 4
9.2 Database
      sample schemas  October 09, 2002
Reviewer:
      shanmugam  from Singapore
Rite now we are not using any of the sample schemas. Shall I drop the users?  or
any unInstall script?

          Followup:

the accounts are locked upon install.  they are harmless.  you can leave them or
not, it is your call

      Rating: 5
logical standby
      -- unsupported tables with funtional indexes  January 27,
      2003
Reviewer:  Baqir Hussain  from Emeryville, CA
      USA
Tom,
Oracle9 r2 says in its chapter 4 "data guard concept and administration" under
"Unsupported Tables and Sequences" that
* Tables used to support functional indexes.
What it means is that oracle9.2 will not support tables with functional indexes
if we implement logical standby database.  If this is correct what i understand,
please tell me what is work around. Because we have few tables that use
funtional indexes heavily.
I would appreciate your help in this regard. Thanks

          Followup:

currently function based indexes are not supported with LOGICAL (sql apply) data
guard.

PHYSICAL (redo apply) they are.

      Rating: 5
XMLTypes,
      Rowids are also not supported .  January 27, 2003

Reviewer:  Bala
Hi Tom,

As per documentation, CLOB datatype is supported but XMLType based on CLOB is
not supported in 'Logical Stdby'
along with 'Functional Indexes' and 'Rowids' .

Am i correct.

          Followup:

because objects are not....

we can save some time here:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96653/createls.htm#1035815
has the restrictions all listed..

January 28, 2003 Reader -- Thanks for the question regarding "Case
      with in", version 8.1.7

      You Asked
I want to switch the select in the in-clause according to a flag I get from the
application calling the procedure containing it.
Is there another way than doing this with if-then-else having three almost
identical statements for then and else

Is there a way to wrap the case for the 'In' to work or is there a way of using
decode for that purpose?

That does not work:
flag number;

select a,b,c from t1,t2,t3...
where....
AND t1.b in (case flag='1' then (select t4.a_nr from t4 where name like
'string') case flag='2' (select t4.a_nr from t4 where name not like
'string')else (select t4.a_nr from t4) end )

Thank you

      and we said...
Those are THREE very different queries that you want to have THREE very
different query plans for.

If you do this in one query (which I could do, but I'm not going to show you how
to cause it is just *wrong*), we would have 1 query plan -- not the optimal plan
for ANY of the queries.

Ref cursors are good for this situation:

is
   type rc is ref cursor;
   l_cursor rc;
   ...
begin
   if ( flag = '1' )
   then
       open l_cursor for
           select ....
            where ...
              and t1.b in ( select a_nr from t4 where name like p_string );
   elsif ( flag = '2' )
   then
       open l_cursor for
           select ....
            where ...
              and t1.b in ( select a_nr from t4 where name not like p_string );
   else
       open l_cursor for
           select ....
            where ...
              and t1.b in ( select a_nr from t4 );
   end if;

   loop
      fetch l_cursor into l_rec;
      exit when l_cursor%notfound;
      ...
   end loop;
   close l_cursor;
end;

so, the only thing in the if then else is the query itself (not the code to
process said query).  This is the correct and optimal approach.

Review & Followup

      Rating: 5
Sorry for
      putting this in...but...  December 13, 2002

Reviewer:  A reader  from India
Tom,
    This could also be an alternate approach. Right? This could give a better
query plan. Please advise.

SELECT
  a,b,c
FROM
  t1,t2,t3...t4
WHERE
  :flag = 1
AND t1.b = t4.a_nr
AND name LIKE 'string'
UNION ALL
SELECT
  a,b,c
FROM
  t1,t2,t3...t4
WHERE
  :flag = 2
AND t1.b = t4.a_nr
AND name NOT LIKE 'string'
UNION ALL
SELECT
  a,b,c
FROM
  t1,t2,t3...t4
WHERE
  :flag NOT IN (1,2)
AND t1.b = t4.a_nr

          Followup:

well, IN and joins are not equivalent -- in general

select * from t1 where x in ( select y from t2 )

CANNOT be written as

select t1.* from t1, t2 where t1.x = t2.y

reason:  the relationship between t1 and t2 is not known, you must:

select t1.* from t1, (select DISTINCT y from t2 ) t2 where t1.x = t2.y;

and the union all would just add extra work -- I would use the the ref cursors.
Then it is 100% clear what you intend as well...

      Rating: 4
Multiple
      procedures vs. single procedure  January 28, 2003

Reviewer:  Andy  from Germany
Tom, if I have a manageable list of possible SELECTs (about 10, say), all of
which I know at design time, is there a difference (from a parsing point of
view) between:

1.) Using a ref cursor with if-elsif clauses listing the possibilites (as you
have done above), or

2.) Having a discrete package per each possible SELECT?

i.e. in the example you give, does each SELECT get parsed when the procedure is
first called, or only the relevant SELECT?

Andy

          Followup:

Only one of the selects gets parsed per call -- but, one select per call would
be parsed using ref cursor.

If you have 10 discrete routines that use static sql, at most, one select could
get parsed -- but if you already ran the routine in that session once before,
the cursor would most likely not be reparsed as plsql would have cached it.

ref cursors = parse per open.
static sql = parse per session (typically)

January 28, 2003 -- Thanks for the question regarding "Multiple
      Database tiers for an application ", version 9.2.x

      You Asked
Architecture type of question for you:
We have an e-commerce application which should be available 24X7. In order to
minimize the download and to make the new releases as transparent and smooth as
possible we have created mutliple tiers on the database ie for a given
application we have 1 table owner schema, we have 2 plsql schema and then we
have 2 user schemas which calls the plsql in the plsql schema to access the
tables. All access to the tables have to go thru the plsql that is no inline sql
in the application at all. Because of this we have a ton of private synonyms
(for the plsql schema accessing the tables and the user schema accessing the
plsql in the plsql schema).. Everytime we have to release a new version we
switch the plsql and the user schema from user1 to user2 and plsql1 to plsql2 ..
Create new private synonym privs etc.
Powers to be don't think that there be any changes to the tables hence we don;t
have 2 table schemas ..
What are your thoughts ? Any alternatives of doing it better or any changes to
the current setup ..
thanks for your help ..

      and we said...
sounds viable -- as long as you understand that when you change the synonyms for
the connecting user to point to the new plsql codebase - you will have downtime
at that point.

I would try to avoid the synonyms all together.  the PLSQL code bases do not
need them -- they only have ONE schema to consider.  they can select * from
SCOTT.emp (and you can use &SCHEMA in the create scripts in the event you are
worried about the schema name changing -- so that to install you would:

SQL> @install SCOTT

and that would replace &SCHEMA with scott everywhere.

And, if you had the application get the schema name from a configuration file --
all you would need do is bounce the application server in order to flip flop
schemas -- seems that would be faster and even safer then dropping and
recreating all of the synonyms.  The application would just issue:

alter session set current_schema=SCOTT;

right after connecting -- so that a call like:

begin  foobar( ? ); end;

would really be

begin SCOTT.foobar( ? ); end;
Review &
      Followup

      Rating:
      3
  January 28, 2003
Reviewer:  Andrew
       from Minsk, Belarus
Good idea!

      Rating: 5
Re:Multiple
      Database tiers for an application   January 28, 2003

Reviewer:  A reader
Tom, appreciate your help and quick response ..

>"sounds viable -- as long as you understand that when you >change the synonyms
for the connecting user to point to >the new plsql codebase - you will have
downtime at that >point."

Actually, the down time will be minimum since we are planning on using a web
farm .. we would shutdown 1or2 servers and have their configuration file point
to the new user schemas .. restart them and do the same to the remaining web
servers the down side is that while the configuration file is being changed and
the cache on the web servers being purged the load on the remaining web servers
will be high .. no big deal ..

"And, if you had the application get the schema name from a >configuration file
-- all you would need do is bounce the >application server in order to flip flop
schemas -- seems >that would be faster and even safer then dropping and
>recreating all of the synonyms.  "

As for not dropping the synonyms etc.. we don't do that 'cause when we
change/switch from user1/plsql1 schema to user2/plsql2 we leave the user1/plsql1
intact just in case we need to rollback to user1/plsql1 because of bug in the
new version being deployed in user2/plsql2 .. the
only time synonyms and privs in user1/plsql1 will be recreated/dropped is when
we switch from 2 to 1..
Hope I am making sense..

"The application would >just issue:
>alter session set current_schema=SCOTT;
>right after connecting -- so that a call like:
>
>begin  foobar( ? ); end;
>
>would really be
>
>begin SCOTT.foobar( ? ); end;
"
Sorry, I might have missed it in my previous listing ... I can't use the alter
session set schema .. since in the plsql the plsql schema accesses at least one
lookup schema which houses the lookup tables .. so the alter session would not
work .. And we don;t want to hard code the schema name in the plsql at all ..

Thanks again for your help ..

          Followup:

the alter session will work if your approach works.

there is nothing but nothing to stop it.

You should:

o use one schema to connect with.
o two schemas (C1 and C2) for the code

the one connecting schema simply does the alter session to set the current
schema to c1 or c2.  That lets this one schema find the correct code base.

The correct code base then used hard references to EXACTLY the stuff it needs.

No confusion, no mess, no muss, no missing grants in "user1" or "user2",
consistency, less moving parts.

You asked for advice and that is mine.  skip the synonyms.

      Rating:
      5
  January 29, 2003
Reviewer:
      A reader
Thanks Tom .. Appreciate your candid response .. It all makes sense ..
Your column online and in oracle magazine has been a god send for some of us ..

January 28, 2003 Vikas -- Thanks for the question regarding "Few
      Questions relating to Parsing,Replication", version 8.0.3

      You Asked
Hello Tom, Good Morning!!

Another request to  be looked into and finding a resolution :

Thanks for the support & recommendations about the subject : Using Bind
Variables In VJ++.

I have used your technique, to get the things going in one of our applications &
after doing the same have raised doubts in my mind. Please clarify them to my
understanding.

1) When I  viewed the contents of V$SQLAREA : Sql_Text,Parse_Calls & the
Executions Column I found that some statements had :

0 Parsed Calls & 23 Executions . How can there be such a statement with is
executed without getting it parsed even once because if I do believe that the 7
stages before a query gets executed has the Ist stage as Parsing of the Query.

2) Then I tried to run the application which has been modified for bind
variables.It is an batch oriented application and when executed resulted in :

754 Records Processed Time : 13 Min 59 Sec means 1.11 sec/Record
At the Back-end I Viewed the V$SQLAREA and it started with count of 30 rows &
reached at count of 1473 records with every SQL of the application specifying 1
Parsing & 1 Execution.

Now I ran the previous EXE with the concept of host variables

754 Records Processed Time : 10 Min 54 Sec means 0.86  sec/Record.
At the Back-end I Viewed the V$SQLAREA and it started with count of 30 rows &
reached at a count of 43 records with every SQL of the application specifying 1
Parsing & Many Executions.

This was run after all the statements from the Shared Pool have been flushed out
by specifying :
Alter System flush Shared_Pool;
Also getting the Data Tablespace & Index Tablespace OFFLINE and then bringing
them ONLINE.

I believe these steps are just like starting the Oracle Server as fresh. Am I
missing Something ??. Is n't it a strange behaviour ?? Please do explain.

3) I have an Oracle database installed on one of the machines with default
settings. The Database name is 'ORACLE' and the instance name is 'ORCL', I tried
to create a database link from the other machine which has the instance name as
'ORCL' and the database name as 'ORACLE'.

I gave the command as Create public database link ORCLVK connect to scott
identified by tiger using 'ORCL'. It gave me the error :

ORA-02085: database link name connects to name
Cause: The database link attempted to connect to a database with a different
name. The name of the database link must be the same name as the name of the
database.

When taken action it gave me this error ??
Create public database link ORCL connect to scott identified by tiger using
'ORCL'. It gave me the error :
ORA-02082: a loop-back database link must have a connection qualifier
Cause: An attempt was made to create a database link with the same name as the
current database.

How it can be done in case of default instance ORCL as I am able to create the
public db links having the instance name other than ORCL.

4) I am working on configuring MASTER - SITE Replication.
I created a Master Site & created Snapshot Log for the Emp Table under Scott
Schema.
CREATE SNAPSHOT LOG ON "SCOTT"."EMP" TABLESPACE "USR"
For the Slave Site created the Snapshot referencing the  log on the Master Site.
Created the database link named IBM referencing the Master Site SCOTT Schema.

CREATE SNAPSHOT "SCOTT".EMP_SNAPSHOT TABLESPACE "USER_DATA"  REFRESH FORCE START
WITH to_date('23-NOV-2001     05.24.16 PM', 'DD-MON-YYYY HH.MI.SS AM') NEXT
sysdate + 1/(24*60) AS SELECT  *  FROM "SCOTT"."EMP"@IBM.WORLD;

Also in the init.ora file redefined the parameters :

job_queue_processes = 2
job_queue_interval = 10
job_queue_keep_connections = false

Now I Updated a record from the Emp table under SCOTT schema of the Master
Site.It created another table with the name MLOG$_EMP with columns
EMPNO,SNAPTIME$$,DMLTYPE$$,OLD_NEW$$,CHANGE_VECTOR$$. The Empno is the Primary
key so is understandable that it will refresh the SNAPSHOT at the COPY site
using the EMPNO. Snaptime is the time when the snapshot for that table has been
taken. DMLTYPE$$ is what kind of operation it is (Update/Insert/Delete).
OLD_NEW$$ might be the old values to be replaced with the new ones at the COPY
site. AM I Correct ? Can u please explain me the CHANGE_VECTOR$$ Column .

In the UI of OEM it displays, Current Snapshot recorded 23-Nov-2001 05:19:08 PM.
Now at the COPY site : the SNAPSHOT is not getting refrehed and the changes in
the EMP table  are not getting reflected. No Trigger has been created
automatically but after viewing the V$SQLAREA it showed the Updateable
statements referencing the table. Then why it didnot commit them.Please explain.

What needs to be done to resolve this problem ??

I am having the  Oracle8 release 8.0.3.0.0 is that means that the Replication
Environment is built in this release OR it has to be purchased separately to get
it going.

Waiting for the feedback from you so that these doubts could also be resolved.

Thanks in anticipation,

Vikas Khanna
Vkhanna@quark.stpm.soft.net

      and we said...
PLEASE -- ask but one question per question (its one of the guidelines I have on
the page before you can ask a question).  Large multi-part questions, especially
when the questions are NOT related to eachother are:

o hard to answer
o hard to understand by other people

also you really shouldn't put your email address in the question unless you want
the world to have access to your email address.

1) the parse count gets reset when you invalid the query.  For example:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select sql_text, executions, invalidations,
parse_calls from v$sqlarea where
  2  sql_text like 'insert into t values ( 1 )%';

SQL_TEXT                       EXECUTIONS INVALIDATIONS PARSE_CALLS
------------------------------ ---------- ------------- -----------
insert into t values ( 1 )              1             0           1

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select sql_text, executions, invalidations,
parse_calls from v$sqlarea where
  2  sql_text like 'insert into t values ( 1 )%';

SQL_TEXT                       EXECUTIONS INVALIDATIONS PARSE_CALLS
------------------------------ ---------- ------------- -----------
insert into t values ( 1 )              1             1           0

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

See how the invalidations go up and parse_calls gets reset when we drop the
table....  Its still in the shared pool -- just invalid right now...

2) I don't understand what you did in #2, you'll really need to be more clear.

You say:

"Then I tried to run the application which has been modified for bind
variables.It is an batch oriented application and when executed resulted in :

754 Records Processed Time : 13 Min 59 Sec means 1.11 sec/Record
At the Back-end I Viewed the V$SQLAREA and it started with count of 30 rows &
reached at count of 1473 records with every SQL of the application specifying 1
Parsing & 1 Execution.

Now I ran the previous EXE with the concept of host variables

754 Records Processed Time : 10 Min 54 Sec means 0.86  sec/Record.
At the Back-end I Viewed the V$SQLAREA and it started with count of 30 rows &
reached at a count of 43 records with every SQL of the application specifying 1
Parsing & Many Executions."

Now, HOST variables and BIND variables are really one in the same (the HOST
variable is what you BIND to the BIND variable).  They go hand in handl.  It
sounds like the first run is really the run WITHOUT binds and the second is the
one with the binds.  But that is not what you say.  You'll need to be more
clear.

3) You have global names on in your database.  Use the alter database rename
global_name to new_name command to give EACH database a unique name.

The connection qualifier for the loop back link is simply saying -- you must
name the database link name AFTER the global name (select global_name from
global_name).  You do this with an "@".  For example:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create database link
  2  ora817dev.us.oracle.com
  3  connect to scott identified by tiger
  4  using 'ora817dev.us.oracle.com';
connect to scott identified by tiger
*
ERROR at line 3:
ORA-02082: a loopback database link must have a connection qualifier

thats my global name - cannot do that... so

ops$tkyte@ORA817DEV.US.ORACLE.COM> 2
  2* ora817dev.us.oracle.com
ops$tkyte@ORA817DEV.US.ORACLE.COM> c/ora817dev.us.oracle.com/ora817dev@loopback1
  2* ora817dev@loopback1
ops$tkyte@ORA817DEV.US.ORACLE.COM> l
  1  create database link
  2  ora817dev@loopback1
  3  connect to scott identified by tiger
  4* using 'ora817dev.us.oracle.com'
ops$tkyte@ORA817DEV.US.ORACLE.COM> /

Database link created.

that works....

4) search for CHANGE_VECTOR$$ on mysite.  The answer is right there.

Things to check:

o check your alert log, see if there are any messages indicating the failure of
the refresh.

o check your user_jobs table -- look at the job that is scheduled, verify the
last_date, next_date, broken, and failures columns for the refresh job.

o verify that you in fact DID restart the database after changing the init.ora
parameters.  use show parameter job in svrmgrl to see the actual values

o make sure the job init.ora settings were made on the COPY site as the COPY
site "pulls" changes for snapshots -- the master site doesn't need jobs setup
for snapshots.

803 supports this type of replication (in fact, 7.0 and up do)

Review &
      Followup

      Rating: 4
Suggestions
        November 28, 2001
Reviewer:  Vikas Khanna
       from INDIA
Hello tom,
Fist of all I would like to thank you for all your efforts in making me
understand some of the concepts which could be useful in near future. I will
take care of your suggestions regarding questioning & e-mail address.

2)Host Variables for my understanding means queries like :
"Select * from emp where empno =" + empno  and empno value gets substituted
during the program run to display as :
Select * from emp where empno = 2;
Select * from emp where empno = 3; and so on in V$SQLAREA

For Bind Variables I put my statement as :
PStmtempquery = c.preparestatement("Select * from emp where empno = ?")
Now when the Batch Program runs, the values for empno are binded to get the
results. One time Parsing & Many times Executions.

But In my case the behaviour is strange that the Case I is running at a fast
speed rather than Case II.Please do explain how this could happen??

4) These are the error messages reported in the alert log file. How these errors
at various lines of different objects can be rectified
ORA-12012: error on auto execute of job 2
ORA-01001: invalid cursor
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 380
ORA-06512: at "SYS.DBMS_IREFRESH", line 450
ORA-06512: at "SYS.DBMS_REFRESH", line 182
ORA-06512: at line 1

Rest I have checked all the points mentioned by you and they are correct on your
guidelines.

Thanks again in anticipation
Vikas Khanna

          Followup:

2) that is NOT a host variable -- it is a string literal.

Use TKPROF + SQL_TRACE + timed_statistics

http://asktom.oracle.com/~tkyte/tkprof.html
to review how your program is running, verify that you are in fact doing it once
(the parse) and executing over and over.

Your text here:

<quote>
2) Then I tried to run the application which has been modified for bind
variables.It is an batch oriented application and when executed resulted in :

754 Records Processed Time : 13 Min 59 Sec means 1.11 sec/Record
At the Back-end I Viewed the V$SQLAREA and it started with count of 30 rows &
reached at count of 1473 records with every SQL of the application specifying 1
Parsing & 1 Execution.

Now I ran the previous EXE with the concept of host variables

754 Records Processed Time : 10 Min 54 Sec means 0.86  sec/Record.
At the Back-end I Viewed the V$SQLAREA and it started with count of 30 rows &
reached at a count of 43 records with every SQL of the application specifying 1
Parsing & Many Executions.
</quote>

either directly conflicts with what you have said (using bind variables results
in LESS queries in the shared pool -- not many hundreds MORE) or you did not
implement bind variables properly.  The tkprof report will show you.

4) sounds like your database link is not setup properly.  how did you define it.

      Rating: 5
one more
      parsing question  June 21, 2002
Reviewer:
      john
if i issue a two sql statments with only different columns and where clause and
from clause is different..

if the parse tree for the both is same or different..

for ex:

1.select empno,emname from emp where empno = 1000

2.select sal, deptid from emp where empno = 2000

will the parse tree is same or differnt for the above two.

how can tell this by looking at v$sqlarea?

thanks

          Followup:

totally different.  100% different.

consider:

ops$tkyte@ORA8I.WORLD> set autotrace traceonly explain
ops$tkyte@ORA8I.WORLD> select empno, ename from emp where empno = 1000;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   INDEX (RANGE SCAN) OF 'EMP_IDX' (NON-UNIQUE)

ops$tkyte@ORA8I.WORLD> select sal, deptno from emp where empno = 2000;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
   2    1     INDEX (RANGE SCAN) OF 'EMP_IDX' (NON-UNIQUE)

      Rating: 4
Problem in
      update query using database link  October 14, 2002

Reviewer:  Riaz Shahid  from PRAL, Islamabad,
      Pakistan
Hello Tom!

I am trying to insert/update data through database link to another server. The
init.ora configuration info for both servers is:

Server 1:

open_cursors = 6000
max_enabled_roles = 148
db_file_multiblock_read_count = 8

db_block_buffers = 10000

shared_pool_size = 46483955

large_pool_size = 1214400
java_pool_size = 0

log_checkpoint_interval = 10000
log_checkpoint_timeout = 1800

processes = 530

parallel_max_servers = 5

log_buffer = 32768
db_block_size = 8192
job_queue_processes = 4
job_queue_interval = 60
open_links = 255
open_links_per_instance = 255
distributed_transactions = 6000

Server 2:
open_cursors = 2000
max_enabled_roles = 30
db_file_multiblock_read_count = 16
db_block_buffers = 3200
shared_pool_size = 9000000
large_pool_size = 6144000
java_pool_size = 0
log_checkpoint_interval = 100000
log_checkpoint_timeout = 1800
processes = 200
log_buffer = 32768
db_block_size = 8192
job_queue_processes = 4
job_queue_interval = 10
open_links = 255
distributed_transactions = 2000

I run the following procedure from Server 2:

PROCEDURE REPLICATION_INV(clmno in refund.refund_claim.claim_no%type,FLG IN
BOOLEAN DEFAULT FALSE) IS
begin

begin
update cr.claimantinvoice_master@starr.lhr a
    set
    (
SELLER_REGNO           ,
INVOICE_NO             ,
INVOICE_DATE           ,
INVOICE_STATUS         ,
CLAIM_NO               ,
TAX_PERIOD             ,
SELLER_NAME            ,
SELLER_ADDRESS         ,
BUYER_REGNO            ,
BUYER_NAME             ,
BUYER_ADDRESS          ,
EXCLUDED_SALTAX_TOT    ,
TOTAL_SALESTAX         ,
EXISTENCE_FLAG         ,
ALREADY_CLAIMED_NO     ,
OFFICER_CODE           ,
CR_VERIFICATION        ,
COLL_VERIFICATION      ,
CR_VERIFICATION_UID    ,
COLL_VERIFICATION_UID  ,
CR_VARIFICATION_DATE   ,
COLL_VARIFICATION_DATE ,
UPDATION_UID           ,
UPDATION_DATE          ,
UPDATION_REMARKS       ,
INVOICE_OFLAG          ,
STAGE                  ,
PAID
    )=
    (
    select
SELLER_REGNO           ,
INVOICE_NO             ,
INVOICE_DATE           ,
INVOICE_STATUS         ,
CLAIM_NO               ,
TAX_PERIOD             ,
SELLER_NAME            ,
SELLER_ADDRESS         ,
BUYER_REGNO            ,
BUYER_NAME             ,
BUYER_ADDRESS          ,
EXCLUDED_SALTAX_TOT    ,
TOTAL_SALESTAX         ,
EXISTENCE_FLAG         ,
ALREADY_CLAIMED_NO     ,
OFFICER_CODE           ,
CR_VERIFICATION        ,
COLL_VERIFICATION      ,
CR_VERIFICATION_UID    ,
COLL_VERIFICATION_UID  ,
CR_VARIFICATION_DATE   ,
COLL_VARIFICATION_DATE ,
UPDATION_UID           ,
UPDATION_DATE          ,
UPDATION_REMARKS       ,
INVOICE_OFLAG          ,
STAGE                  ,
PAID
from     refund.claimantinvoice_master
  where   CLAIM_NO       =clmno
  AND     SELLER_REGNO   =a.SELLER_REGNO
  and     INVOICE_NO     =a.INVOICE_NO
  and     INVOICE_DATE   =a.INVOICE_DATE
  and     INVOICE_STATUS =a.INVOICE_STATUS

    )

    where (CLAIM_NO,SELLER_REGNO,INVOICE_NO,INVOICE_DATE,INVOICE_STATUS)=
    (select CLAIM_NO,SELLER_REGNO,INVOICE_NO,INVOICE_DATE,INVOICE_STATUS
    from refund.claimantinvoice_master b
where   b.CLAIM_NO       =clmno
AND b.SELLER_REGNO   =a.SELLER_REGNO
 and     b.INVOICE_NO     =a.INVOICE_NO
 and     b.INVOICE_DATE   =a.INVOICE_DATE
 and     b.INVOICE_STATUS =a.INVOICE_STATUS);

if sql%rowcount=0 then

insert into cr.CLAIMANTINVOICE_MASTER@starr.lhr
(
SELLER_REGNO           ,
INVOICE_NO             ,
INVOICE_DATE           ,
INVOICE_STATUS         ,
CLAIM_NO               ,
TAX_PERIOD             ,
SELLER_NAME            ,
SELLER_ADDRESS         ,
BUYER_REGNO            ,
BUYER_NAME             ,
BUYER_ADDRESS          ,
EXCLUDED_SALTAX_TOT    ,
TOTAL_SALESTAX         ,
EXISTENCE_FLAG         ,
ALREADY_CLAIMED_NO     ,
OFFICER_CODE           ,
CR_VERIFICATION        ,
COLL_VERIFICATION      ,
CR_VERIFICATION_UID    ,
COLL_VERIFICATION_UID  ,
CR_VARIFICATION_DATE   ,
COLL_VARIFICATION_DATE ,
UPDATION_UID           ,
UPDATION_DATE          ,
UPDATION_REMARKS       ,
INVOICE_OFLAG          ,
STAGE                  ,
PAID
)
select
SELLER_REGNO           ,
INVOICE_NO             ,
INVOICE_DATE           ,
INVOICE_STATUS         ,
CLAIM_NO               ,
TAX_PERIOD             ,
SELLER_NAME            ,
SELLER_ADDRESS         ,
BUYER_REGNO            ,
BUYER_NAME             ,
BUYER_ADDRESS          ,
EXCLUDED_SALTAX_TOT    ,
TOTAL_SALESTAX         ,
EXISTENCE_FLAG         ,
ALREADY_CLAIMED_NO     ,
OFFICER_CODE           ,
CR_VERIFICATION        ,
COLL_VERIFICATION      ,
CR_VERIFICATION_UID    ,
COLL_VERIFICATION_UID  ,
CR_VARIFICATION_DATE   ,
COLL_VARIFICATION_DATE ,
UPDATION_UID           ,
UPDATION_DATE          ,
UPDATION_REMARKS       ,
INVOICE_OFLAG          ,
STAGE                  ,
PAID
from refund.claimantinvoice_master
where claim_no=clmno
AND nvl(CR_VERIFICATION,'0')<>'1';

      IF FLG=TRUE THEN
commit;
END IF;
    end if;

  exception
  when others then null;

 end;

/**********************************************claimantinvoice_detail*******/
begin
update cr.claimantinvoice_detail@starr.lhr A
set
(
SELLER_REGNO           ,
INVOICE_NO             ,
INVOICE_DATE           ,
INVOICE_STATUS         ,
S_NO                   ,
PCT_CODE               ,
QUANTITY               ,
EXCLUDED_SALTAX        ,
INCLUDED_SALETAX           ,
SALTAX                 ,
UPDATION_UID           ,
UPDATION_DATE          ,
PCT_DESC               ,
SRO_STATUS             ,
SRO_SERIAL             ,
UOM                    ,
CLAIM_NO
)=
(
select
SELLER_REGNO           ,
INVOICE_NO             ,
INVOICE_DATE           ,
INVOICE_STATUS         ,
S_NO                   ,
PCT_CODE               ,
QUANTITY               ,
EXCLUDED_SALTAX        ,
INCLUDED_SALETAX             ,
SALTAX                 ,
UPDATION_UID           ,
UPDATION_DATE          ,
PCT_DESC               ,
SRO_STATUS             ,
SRO_SERIAL             ,
UOM                    ,
CLAIM_NO
from refund.claimantinvoice_detail where claim_no=clmno)
where (SELLER_REGNO,INVOICE_NO,INVOICE_DATE,INVOICE_STATUS)=
(select SELLER_REGNO,INVOICE_NO,INVOICE_DATE,INVOICE_STATUS
from claimantinvoice_detail b
where B.CLAIM_NO=CLMNO
AND   B.SELLER_REGNO=A.SELLER_REGNO
AND   B.INVOICE_NO=A.INVOICE_NO
AND   B.INVOICE_DATE=A.INVOICE_DATE
AND   B.INVOICE_STATUS=A.INVOICE_STATUS);

if sql%rowcount=0 then

insert into cr.claimantinvoice_detail@starr.lhr
    (SELLER_REGNO         ,
INVOICE_NO              ,
INVOICE_DATE           ,
INVOICE_STATUS         ,
S_NO                   ,
PCT_CODE               ,
QUANTITY               ,
EXCLUDED_SALTAX               ,
INCLUDED_SALETAX             ,
SALTAX                 ,
UPDATION_UID           ,
UPDATION_DATE          ,
PCT_DESC               ,
SRO_STATUS             ,
SRO_SERIAL             ,
UOM                    ,
CLAIM_NO
)
select
SELLER_REGNO           ,
INVOICE_NO             ,
INVOICE_DATE           ,
INVOICE_STATUS         ,
S_NO                   ,
PCT_CODE               ,
QUANTITY               ,
EXCLUDED_SALTAX        ,
INCLUDED_SALETAX       ,
SALTAX                 ,
UPDATION_UID           ,
UPDATION_DATE          ,
PCT_DESC               ,
SRO_STATUS             ,
SRO_SERIAL             ,
UOM                    ,
CLAIM_NO
from refund.claimantinvoice_detail where claim_no = clmno;

IF FLG=TRUE THEN
commit;
END IF;
end if ;

  exception
  when others then null;

 end;

END;

Then i execute the procedure as:

SQL > execute REPLICATION_INV('G3453435434');

But it is taking too long (about 15 minutes) even if the
clmno G3453435434 doesn't exists in the tables.

I have the following indexes on both servers:

Server1:

Table Name:
claimantinvoice_master

Indexes

Index1 (PK)
(CLAIM_NO,SELLER_REGNO,INVOICE_NO,INVOICE_DATE,INVOICE_STATUS)

Table Name:
claimantinvoice_detail

Indexes

Index1 (PK)

(CLAIM_NO,SELLER_REGNO,INVOICE_NO,INVOICE_DATE,INVOICE_STATUS,S_NO)

Server2:

Table Name:
claimantinvoice_master

Indexes

Index1
(SELLER_REGNO,INVOICE_NO,INVOICE_DATE,INVOICE_STATUS)

Index2
(CLAIM_NO)

Table Name:
claimantinvoice_detail

Indexes

Index1

(SELLER_REGNO,INVOICE_NO,INVOICE_DATE,INVOICE_STATUS,S_NO)

Index2
(CLAIM_NO)

The inserts in procedures work fine BUT the updation are taking a lots of time.

Is there any way to get rid of this ?

Regards

Riaz

          Followup:

Save this for a real question sometime...

but, peek at:

from refund.claimantinvoice_detail where claim_no=clmno)
where (SELLER_REGNO,INVOICE_NO,INVOICE_DATE,INVOICE_STATUS)=
(select SELLER_REGNO,INVOICE_NO,INVOICE_DATE,INVOICE_STATUS
from claimantinvoice_detail b
where B.CLAIM_NO=CLMNO
AND   B.SELLER_REGNO=A.SELLER_REGNO
AND   B.INVOICE_NO=A.INVOICE_NO
AND   B.INVOICE_DATE=A.INVOICE_DATE
AND   B.INVOICE_STATUS=A.INVOICE_STATUS);

and ask yourself "should I send EVERY row from the remote site to this site and
do a correlated subquery?  or, maybe IN would be better to use here"

      Rating: 3
Create
      snapshot  January 28, 2003
Reviewer:
      Abdal-Rahman Agha  from Yemen
Hi Tom,
  I need explanation for the following generated script from the Replication
Tool, which I failed to find the meaning of it from the syntax in the Oracle
Documentation:

...
CREATE SNAPSHOT "USER1"."ACCOUNT_CODE"
    USING ("ACCOUNT_CODE",(3, 'DB1.COMPANY.COM', 1, 0, "USER1",
    "ACCOUNT_CODE", '2003-01-13:15:22:18',0, 25996,
        '1950-01-01:12:00:00', '', 0, (1, "ACC_STATUS", "ACC_STATUS",
        0, 321, 0)),2097249, 3, ('1950-01-01:12:00:00', 123, 0, 0),
        '@DB1.COMPANY.COM')
REFRESH FORCE WITH PRIMARY KEY
AS
Select ACC_STATUS,STATUS_TYPE,UPDATE_DATE,USERNAME
FROM "USER1".ACCOUNT_CODE@DB1.COMPANY.COM
;
...

Here, what is the 'USING' and what are the after 'USING'?

Thanks.

          Followup:

nothing meaningful to you and me.  nothing documented -- just stuff for the
internal tool to use and make use of.  just stuff they put in there that we
don't use if we do it by hand.

January 28, 2003 Andrew -- Thanks for the question regarding
      "Rollup/Cube and Percentages", version 8.1.7

      You Asked
Tom,

Is it possible to include a percentage result within a cube or rollup statement
that gives a correct result ?

I have the following statement;

SELECT decode(grouping(product_id), 1, 0, 2) + decode(grouping(warehouse_id), 1,
0, 1) as aggregate_level,
       decode(grouping(product_id), 1, 'All Products', product_id) as
product_id,
       decode(grouping(warehouse_id), 1, 'All Warehouses', warehouse_id) as
warehouse_id,
       sum(amount_in_stock) as amount_in_stock
  FROM s_inventory
  group by cube(product_id,warehouse_id)
  order by 1,2 desc,3 desc
Consider

Which gives (only included the first two levels)

Level   Product         Warehouse       Total
----------------------------------------------
0    All Products    All Warehouses    38177
1    All Products    401        2986
1    All Products    301        874
1    All Products    201        2813
1    All Products    10501        5326
1    All Products    101        26178

I want to add another column at level 1 which gives the percentage of total
products at each warehouse. Can this be done ?

Thanks

Andy

      and we said...
that would be:

ops$tkyte@ORA920> select agg_lvl,
  2         prod,
  3         wh,
  4         amt,
  5         decode( agg_lvl, 1,
  6           to_char(round( ratio_to_report(amt)
  7               over (partition by agg_lvl)*100, 2),
  8               '999.99')||'%', null ) pct
  9    from (
 10  select
 11   decode( grouping(prod), 1, 0, 2 )+decode( grouping(wh), 1, 0, 1 ) agg_lvl,
 12   decode( grouping(prod), 1, 'All Products', prod ) prod,
 13   decode( grouping(wh),      1, 'All Warehouses', wh ) wh,
 14   sum(amt) amt
 15    from t
 16   group by cube( prod, wh )
 17         )
 18  /

   AGG_LVL PROD         WH                    AMT PCT
---------- ------------ -------------- ---------- --------
         0 All Products All Warehouses      18425
         1 All Products 0000                 3934   21.35%
         1 All Products 0001                 5318   28.86%
         1 All Products 0002                 5439   29.52%
         1 All Products 0003                 3734   20.27%
         2 0000         All Warehouses       2244
         2 0002         All Warehouses       1602
         2 0001         All Warehouses       2293
         2 0003         All Warehouses       2634
         2 0009         All Warehouses        705
         2 0008         All Warehouses       1265
         2 0007         All Warehouses       1421
         2 0006         All Warehouses       1908
         2 0005         All Warehouses       1999
         2 0004         All Warehouses       2354
         3 0000         0000                 1141
         3 0001         0001                 1405
         3 0002         0000                  119
         3 0003         0001                 1188
         3 0005         0001                 1243
         3 0009         0003                  237
         3 0009         0001                  468
         3 0008         0002                  582
         3 0008         0000                  683
         3 0007         0003                  407
         3 0007         0001                 1014
         3 0006         0002                  860
         3 0006         0000                 1048
         3 0005         0003                  756
         3 0004         0002                 1411
         3 0004         0000                  943
         3 0003         0003                 1446
         3 0002         0002                 1483
         3 0001         0003                  888
         3 0000         0002                 1103

35 rows selected.

Review &
      Followup

      Rating:
      4
  January 28, 2003
Reviewer:  Andrew
      Briggs  from Nottingham, UK
Thanks,

Is it possible to generate these percentages at every level ? Is it just a mteer
of building up the decode statement for the pct column ?

i.e. For levels 2 and 3 plus any other dimensions that might be included later.

          Followup:

sure, the decode is doing it just for that level -- just remove the decode and
keep the ratio to report and there you go.

January 28, 2003 George -- Thanks for the question regarding "how
      to get x to y rows in PL/SQL and ORA-00932", version 9.2

      You Asked
Hi Tom,

I need to return a subset of rows of the query result set, say from row 100 to
row 200 , in my pl/sql I ignored the first 99 records by doing a count and
compare to the input lowNo, then fetch the rest into a weakly ref cursor. But I
get a ORA-00932.

My question are:

1. Is something wrong with my cursors? Can I fetch from a weakly ref cursor to
another weakly ref cursor like what I did here.

2. Is my way of select a subset of results correct? Do you have some samples in
PL/SQL?

Thanks very much

-- my code below

create or replace package test_range AS
PRAGMA SERIALLY_REUSABLE;
TYPE curType IS REF CURSOR;
function myrange (
lowNo IN NUMBER,
highNo IN NUMBER )
RETURN curType;
END;
/
CREATE OR REPLACE PACKAGE BODY test_range AS
PRAGMA SERIALLY_REUSABLE;
function myRange (
lowNo IN NUMBER,
highNo IN NUMBER )
RETURN curType IS
sum_cv curType;
err_num NUMBER;
err_msg VARCHAR2(200);
sql_statement VARCHAR2(3000);
res_cur curType;
cnt NUMBER(20) := 0;
BEGIN
sql_statement :=' select * from emp where rownum < :p' ;
OPEN sum_cv FOR sql_statement using highNo ;
loop
cnt := cnt+1;
DBMS_OUTPUT.PUT_LINE(to_char(cnt));
IF cnt >=lowNo THEN
FETCH sum_cv INTO res_cur;
ELSE
null;
END IF;
END LOOP;
CLOSE sum_cv ;
RETURN res_cur;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('No Records Found');
WHEN OTHERS THEN
err_num :=SQLCODE;
err_msg :=SUBSTR(SQLERRM, 1, 200);
DBMS_OUTPUT.PUT_LINE(err_msg);
raise_application_error (-20006, err_msg);
END myRange ;
END;
/

Thank you very much

      and we said...
you fetch into records -- not into "cursor variables".

anyway, just

begin
  open res_cur for '
  select *
    from ( select a.*, rownum r
             from ( select * from emp ) a
            where rownum < :max_row )
   where r > :min_row )' using highNo, lowNo;
end;

that is all  -- NO exceptions, not needed (when no data found -- that would
never fire unless you had a select into and you don't).  when others -- don't
use it.
January 28, 2003 Om -- Thanks for the question regarding "About
      connect,resource and DBA", version 8.1.7

      You Asked
Hi Tom,

I read your book and a article and read this quote where you have quoted that

"connect,resource and DBA should not be used in a system for security reasons".

Could you please elaborate on this.As in our project to perform dba
role we do not make use of any other administrative tool and mostly
we work with sql*plus to carry out our tasks and altough the end users make use
of front end tools to access database in most of the applications.

How can this statement is valid for our database.

If you can please elaborate and give us some tips and expalin how it's not safe
to use these roles.

Many thanks in advance.

Best regards,
Om

      and we said...
look at what connect conveys:

ops$tkyte@ORA920> select * from dba_sys_privs where grantee='CONNECT';

GRANTEE                        PRIVILEGE                                ADM
------------------------------ ---------------------------------------- ---
CONNECT                        CREATE VIEW                              NO
CONNECT                        CREATE TABLE                             NO
CONNECT                        ALTER SESSION                            NO
CONNECT                        CREATE CLUSTER                           NO
CONNECT                        CREATE SESSION                           NO
CONNECT                        CREATE SYNONYM                           NO
CONNECT                        CREATE SEQUENCE                          NO
CONNECT                        CREATE DATABASE LINK                     NO

8 rows selected.

create table?  create view? why all of that -- CREATE SESSION should be more
then sufficient for a normal user.

How about resource then?
see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:853427230099
for user_role_hierarchy

sys@ORA920> drop user a cascade;
User dropped.

sys@ORA920> grant create session, resource to a identified by a;
Grant succeeded.

sys@ORA920> grant select on dba_sys_privs to a;
Grant succeeded.

sys@ORA920> @connect a/a

a@ORA920> select distinct PRIVILEGE
  2    from dba_sys_privs
  3   where grantee in ( select * from sys.user_role_hierarchy )
  4  /

PRIVILEGE
----------------------------------------
CREATE CLUSTER
CREATE INDEXTYPE
CREATE OPERATOR
CREATE PROCEDURE
CREATE SEQUENCE
CREATE SESSION
CREATE TABLE
CREATE TRIGGER
CREATE TYPE
UNLIMITED TABLESPACE

10 rows selected.

unlimited tablespace?  do you want joe or josephine develop to really create
junk in SYSTEM at will?  (granting resource ALWAYS conveys unlimited
tablespace)

Did you even know what grants you were giving with connect and resource?

And how about your DBA's.  Are all DBA's created equal?  No, they are not.  Do
you even know what DBA conveys?  How much power that is?  do they all need that?
 No -- they don't *need* most of it.  The concepts of least privs (giving each
person the LEAST privs they need) to do their job is always more secure then
giving them the keys to the kingdom.

Review &
      Followup

      Rating:
      5
  January 28, 2003
Reviewer:  Harold
       from Managua, Nicaragua
If you only give CONNECT (only this role) but don't give users quotas?? they
cannot create any objects 'cause they will not have privileges.

Harold

          Followup:

they can create these:

CONNECT                        CREATE VIEW                              NO
CONNECT                        ALTER SESSION                            NO
CONNECT                        CREATE SYNONYM                           NO
CONNECT                        CREATE SEQUENCE                          NO
CONNECT                        CREATE DATABASE LINK                     NO

do you want them to?

did you even know they CAN? (many people do not)

and most importantly -- are they supposed to? does your system require they have
this?  If not, why are you granting it to them?

that is my point -- it is called "least privileges".  If you have just enough --
not one more, not one less privilege then you need, you are doing just great.
Too many -- problems can arise.

You want to see me fill up your user|background_dump_destination in a psuedo
denial of service?  grant me connect.  don't want me to be able to do that, even
accidently (unintentionally) -- grant me just create session and whatever
application roles I need.

January 28, 2003 Vipul -- Thanks for the question regarding "What
      is the best way to pass result sets between procedures (remote)", version
      8.1.7

      You Asked
Hi Tom,

What is the best way to pass a result from one procedure to another procedure
when both procedures are residing on seperate database instances.

I know that refcursor is giving error that ref-cusrsor can not be passed for
RPC.

Pl. suggest way and If you have example, that would be of great help.

Thanks.

Additional Information :

The result is essentially a result-set. made up of 7 columns of varchar2 type
(size of each columns is between 18 to 30 characters) and there could be 1 to
100 rows.

      and we said...
Send a table of records.

Here is an exmaple.  This is the "remote" code:

create or replace package ora815_pkg
as
    type tblType is table of dept%rowtype index by binary_integer;

    procedure p( x in tblType );

end;
/

drop table t;
create table t as select * from dept where 1=0;

create or replace package body ora815_pkg
as

procedure p( x in tblType )
as
begin
    for i in 1 .. x.count
    loop
        insert into t (deptno,dname,loc)
        values
        ( x(i).deptno, x(i).dname, x(i).loc );
    end loop;
end;

end;
/

And here is the results of a "local" execution:

scott@ORA717DEV.US.ORACLE.COM> select * from t@ora815;

no rows selected

scott@ORA717DEV.US.ORACLE.COM>
scott@ORA717DEV.US.ORACLE.COM> declare
  2          l_data ora815_pkg.tblType@ora815;
  3  begin
  4          for x in ( select * from dept )
  5          loop
  6                  l_data(l_data.count+1).deptno := x.deptno;
  7                  l_data(l_data.count  ).dname := x.dname;
  8                  l_data(l_data.count  ).loc := x.loc;
  9          end loop;
 10
 11          ora815_pkg.p@ora815( l_data );
 12  end;
 13  /

PL/SQL procedure successfully completed.

scott@ORA717DEV.US.ORACLE.COM>
scott@ORA717DEV.US.ORACLE.COM> select * from t@ora815;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING     NEW YORK
        20 RESEARCH       DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS     BOSTON

The trick is to define your local variable based on the REMOTE type!
Review &
      Followup

      Rating: 4
Thanks for the
      solution.  October 18, 2001
Reviewer:  VJ
       from Boston, MA USA

      Rating: 4
What if remote
      /local tables contain nested tables  January 23, 2002

Reviewer:  prasad rallapalli  from Fremont, CA USA

Tom:This is a good trick - declaring local variable on remote table object but
when I tried it on remote table with nested table columns it fails with PLS00453
remote operations not allowed on object tables or user defined type columns

          Followup:

you cannot do objects over dblinks in 8i, not supported.  Don't know of anyway
to do it in 8i.

      Rating: 4
Returning the
      other way  January 27, 2003
Reviewer:  Steve
       from UK
I have to update rows on a remote table which contains customer information and
then insert rows for those same customers that I've just updated.

On a local database I guess I would return the results of the update into an
array - but I can't do this over a remote link.  What options (if any) do I
have?  I thought about passing the sql query for the update to a remote
procedure which would then handle the insert statement as well.  But how could I
return the results back to the local database?

          Followup:

why not:

update t@remote set ...
  where <condition>;

insert into t
select *
  from t@remote
 where <condition>;

      Rating:
      5
Yes...but.....  January 27, 2003
Reviewer:
      Steve  from UK
Ah, of course but I don't think it's that simple.  The update statement means
that the where clause that updated the rows would no longer identify the same
rows anymore.

The table is an activity table and records any interaction between us and the
customer - it has an "outcome" column which is NULL if the interaction hasn't
taken place yet - for example a meeting.  Now, if that meeting gets cancelled I
have to "close" off the open appointment by updating the "outcome" column to
something and then inserting a new row for the same customer either with details
of the forthcoming meeting or a diary type entry (which is then picked up by a
third party application) so that sales will be reminded in 6 months time to make
further contact.

As the fact that the outcome is NULL at the time of the update but has a value
after means that the Where clause is no longer valid.  However, I don't
necessarily know who the customer is especially if marketing is running a
campaign to identify everyone in a specific district (or in a particular market)
who hasn't been contacted for 3 months and they want to ring them in 3 days time
(or whatever!).
Cheers

          Followup:

Ok, use a global temporary table and:

insert into gtt@remote select ROWID from t@remote where <condition>;
update t@remote set .... where rowid in ( select * from gtt@remote );
insert into local select * from t@remote where rowid in ( select * from
gtt@remote );

      Rating: 5
Perhaps I
      should add.......  January 28, 2003
Reviewer:
      Steve  from UK
that the remote is v.7.3.4!!!  Although I also have the job of upgrading it to
8.1.7 (I know but the company's hierarchy won't approve 9.2.0 until later this
year!!) in the next couple of months.

          Followup:

Ok, use a real table and add a delete from gtt@remote to kick it off with.

      Rating: 4
index_by
      table.COUNT vs index_by table.LAST  January 28, 2003

Reviewer:  robert  from pa
>>  l_data(l_data.count+1).deptno := x.deptno;
    l_data(l_data.count  ).dname := x.dname;
    l_data(l_data.count  ).loc := x.loc;

Tom, can one always use l_tab.LAST + 1 in place of l_tab.COUNT + 1 ? isn't it
more "logical" ?

          Followup:

ops$tkyte@ORA920> declare
  2          type array is table of number index by binary_integer;
  3          l_data array;
  4  begin
  5          dbms_output.put_line( 'last = ' || l_data.last );
  6          dbms_output.put_line( 'count = ' || l_data.count );
  7  end;
  8  /
last =
count = 0

PL/SQL procedure successfully completed.

You have a problem "getting started" with that.  Before there is a last, you
have nothing-ess.  You would have to

  l_data( nvl(l_data.last+1,1) ).deptno := .....

which is more keystrokes then

  l_data( l_data.count+1 ).deptno := .....

I find very little use for first, last, next in real life (as I tend to use
arrays from 1 .. N so first is always 1, next is always i+1 and last is
count)...


Subject | Article | Last Updated (East coast time)
ora-12028(snapshot) | article only | Jan 29, 2003 07:52 am
About Oracle Intenet Directory and Java JNDI | article only | Jan 29, 2003 07:54 am
very large redo generation. How to trace? | article only | Jan 29, 2003 08:32 am
About constraints | article only | Jan 29, 2003 12:39 pm
hot backup for a tablespace with multiple data files | article only | Jan 29, 2003 01:11 pm
Database Replication | article only | Jan 29, 2003 03:39 pm
Restricted access to V$ views? | article only | Jan 29, 2003 04:23 pm
Number passed to me is a date 1234, how to convert | article only | Jan 30, 2003 09:02 am
Gathering stats on chained rows | article only | Jan 30, 2003 11:04 am
Granting select on all tables and views of one user to other | article only | Jan 30, 2003 11:30 am
Managing indexes | article only | Jan 30, 2003 12:51 pm
writing a stand-alone application to continuously monitor a database queue (AQ) | article only | Jan 30, 2003 01:04 pm
How to use Cursor in Java | article only | Jan 30, 2003 01:42 pm
Loading a master detail file using external tables and multi-table insert. | article only | Jan 30, 2003 03:09 pm
public synonym | article only | Jan 30, 2003 05:30 pm
How To View a view from the diff. user ? | article only | Jan 30, 2003 06:11 pm
email - sending from D2K | article only | Jan 31, 2003 07:50 am
Regarding Table extents ! | article only | Jan 31, 2003 07:59 am
SQL query for history / as-of a certain date | article only | Jan 31, 2003 08:23 am
How many days to keep Archived Log Files | article only | Jan 31, 2003 08:24 am
maximum limit, number of arguments in decode function | article only | Jan 31, 2003 08:25 am
User Password | article only | Jan 31, 2003 08:28 am
More than one queue of jobs | article only | Jan 31, 2003 10:16 am
comparing two files with content of a two database table | article only | Jan 31, 2003 02:26 pm
Oracle error | article only | Jan 31, 2003 03:34 pm
Difference between V$Process and v$session | article only | Jan 31, 2003 03:34 pm
Display the tablespaces that cant allocate another extent for a segment | article only | Jan 31, 2003 06:20 pm
Synonyms vs Alter session current_schema | article only | Feb 01, 2003 09:33 am
Dynamic View with Bind Variables | article only | Feb 01, 2003 09:34 am
name_in and copy | article only | Feb 01, 2003 09:35 am
General About Oracle | article only | Feb 01, 2003 09:39 am
Mixed case table names and export(/import) | article only | Feb 01, 2003 11:28 am
Replication Plan | article only | Feb 01, 2003 01:42 pm
JOB SCHEDULE | article only | Feb 01, 2003 02:46 pm
Parsing a stored procedure call. | article only | Feb 01, 2003 05:30 pm
Equivalent of like clause | article only | Feb 01, 2003 05:34 pm
ANY privilege | article only | Feb 01, 2003 06:42 pm
Altering table columns to a smaller size | article only | Feb 01, 2003 07:28 pm
select count(*) vs. select * | article only | Feb 02, 2003 11:02 am
How to reset sequences? | article only | Feb 03, 2003 07:15 am
Is it possible to call a Stored Procedure with a BLOB parameter from Pro*C | article only | Feb 03, 2003 08:05 am
Complex Query! | article only | Feb 03, 2003 08:24 am
Partitioned tables rowcount automated script | article only | Feb 03, 2003 09:42 am
changing primary key? | article only | Feb 03, 2003 12:44 pm
Simple Trigger Strategy | article only | Feb 03, 2003 01:53 pm
Why does it takes almost same amount of time to roll back a transaction | article only | Feb 03, 2003 02:15 pm
FGCA for 3rd party applications | article only | Feb 03, 2003 02:29 pm
Changing Index from Non-Unique to Unique | article only | Feb 03, 2003 02:38 pm
Selective Commit | article only | Feb 03, 2003 03:00 pm
Multiple stored procedures | article only | Feb 03, 2003 06:55 pm
Dynamically vary the number of columns in a query | article only | Feb 03, 2003 07:16 pm
Are private synonyms bad for performance? | article only | Feb 03, 2003 07:18 pm
IF statements versus using EXCEPTIONS in PL/SQL | article only | Feb 03, 2003 07:38 pm
Query behaviour from time to time | article only | Feb 04, 2003 07:24 am
some blob questions | article only | Feb 04, 2003 10:16 am
select statement for a query described below : article only Feb 04, 2003 10:55 am
Refreshing Materialized Views | article only | Feb 04, 2003 12:28 pm
bind variables | article only | Feb 04, 2003 12:33 pm
Trigger to capture program name | article only | Feb 04, 2003 04:07 pm
Off-topic question from recent posting | article only | Feb 04, 2003 04:35 pm
Passing parameter during LOGON | article only | Feb 04, 2003 07:41 pm
row(s) 1 - 61 of 61
January 29, 2003 victor -- Thanks for the question regarding "ora-12028(snapshot)", version 7/8/8i
You Asked
Hi, Tom
I try to create a snapshot on copy site (oracle 8i) for master table T
having primary key (oracle 7.3). I created snapshot log in database for master
table T . But when I try to create snapshot in copy site I retrieve a message :
snapshot type is not supported by master site.
Though when I create snapshot using union in statement select (ex. add another
some table with the same structure) it is created.
In error message guide i read : create ROWID snapshot or ... .
How to do it and why I get this error?
Thank in advance.
Victor.
and we said...
The error message cause/action seems clear in this case:

> oerr ora 12028
12028, 0000, "snapshot type is not supported by master site %s"
// *Cause:  Pre-Oracle8 master sites are not able to support primary key or
//          subquery snapshots that are able to perform a fast refresh.
// *Action: Create a rowid snapshot or use a master table from an Oracle8 site.

You have a master (7.3).  Its snapshot log has a ROWID in it.  7.3 snapshot logs
did not understand how to store a primary key -- only the rowid.

When you did it with a union, it ignored your "refresh fast" request, union
snapshots are always done complete -- hence it can create (no reliance on a
snapshot log in that case).

Create a rowid based snapshot.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create snapshot
  2  t_snap
  3  refresh fast
  4  as
  5  select * from t@ora734.world;
select * from t@ora734.world
       *
ERROR at line 5:
ORA-12028: snapshot type is not supported by master site

ops$tkyte@ORA817DEV.US.ORACLE.COM> create snapshot
  2  t_snap
  3  refresh fast WITH ROWID
  4  as
  5  select * from t@ora734.world;

Materialized view created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
Review & Followup
Rating: 5
Liked it November 25, 2001
Reviewer: Jim from Oz
Always learning something on this site

Rating: 3
unexpected consequence November 26, 2001
Reviewer: victor from Russia
Thank you very much for the clear explanation.
But may be I ought to be more precise. As I have Oracle 7.3.1 so after doing all
of your advice on Oracle 8i I've received a message:
SQL> create snapshot t_arr
  2   refresh fast with rowid
  3   as select * from training.arrival@WG73.GSRAS.RU ;
 as select * from training.arrival@WG73.GSRAS.RU
           *
ERROR at line 3:
ORA-12018: following error encountered during code generation for
"DEVELOPER"."T_ARR"
ORA-00942: table or view does not exist
ORA-02063: preceding line from WG73.GSRAS.RU
But now master allows to create me a complete snapshot for
the master table.
SQL>  create snapshot t_arr
  2    refresh complete with rowid
  3  next sysdate +1
  4   as select * from training.arrival@WG73.GSRAS.RU ;

Materialized view created.
All need snapshot logs exists on master site for master table as I mentioned.
Whether may I get fast snapshot nevetheless ? I'l very appreciate for answer.

Followup:
Do you have select privs granted directly on the snapshot log to the userid
associated with the database link?  (i think not -- that explains why the FULL
works -- doesn't need the snapshot logs where as the fast refresh does.

Try:

grant select on mlog$_arrival to <USERNAME>;

in the 7.3 instance where username is the user the database link is connecting
as.

Rating: 4
Still getting this error January 28, 2003
Reviewer: Hitesh from IN
Following stmt geneater error :
ORA-12028: snapshot type is not supported by master site

 CREATE materialized VIEW cust
 PCTFREE 0
 STORAGE(INITIAL 32k NEXT 32k PCTINCREASE 2)
 build IMMEDIATE
 refresh on commit
 ENABLE query rewrite
 AS
 SELECT
 cc.appl_no AS appno,
 TO_CHAR(cc.appl_date,'dd-mon-yy') AS dt,
 TO_CHAR(cc.appl_date,'hh:mi:ss') AS tm,
 cc.associate_code AS ass_code,
 cc.gross_income AS gr_income ,
 cc.user_id AS userid,
 cc.business_name AS emp_name,
 a.first_name AS fname,
 a.designation AS desg,
 a.last_name AS lname,
 a.res_tel1 AS res_ph,
 a.off_tel1 AS off_ph ,
 a.off_ext AS ext,
 a.mobile AS mob ,
 a.email_id AS email ,
 a.res_address AS res_bld,
 a.off_address AS off_bld,
 FROM
 CREDIT_CARD cc,
 CUST_DTL a
 WHERE
 cc.user_id = a.user_id AND
 a.res_city_code IN (67,102,105)

I have installed Oracle 8.1.5. Can u pls describe in detail why this error comes
and how to solve this ?

Followup:
due to insuffient information -- nope.

no idea what any of the base objects look like, what commands you did to get
here, nothing.

nope, no idea.

(i'll comment on your storage clause -- it stinks. use LMTS, never put a storage
clause on an object ever again)

Rating: 5
January 28, 2003
Reviewer: A reader
Tom,

In your new book do you have topics on Replication?

Thanks.

Followup:
Nope, not a big fan of the "technology", especially with the web.  makes it
virtually obsolete except in the rarest of circumstances.

Rating: 2
Full detail January 29, 2003
Reviewer: Hitesh from IN
Following r list of my tables.

 SELECT
 cc.appl_no AS appno,
 TO_CHAR(cc.appl_date,'dd-mon-yy') AS dt,
 TO_CHAR(cc.appl_date,'hh:mi:ss') AS tm,
 cc.associate_code AS ass_code,
 cc.gross_income AS gr_income ,
 cc.user_id AS userid,
 a.first_name AS fname,
 a.designation AS desg,
 a.last_name AS lname,
 a.res_tel1 AS res_ph,
 a.off_tel1 AS off_ph ,
 a.off_ext AS ext,
 a.mobile AS mob ,
 a.email_id AS email ,
 a.res_address AS res_bld,
 a.off_address AS off_bld,
 FROM
 CREDIT_CARD cc,
 CUST_DTL a
 WHERE
 cc.user_id = a.user_id AND
 a.res_city_code IN (67,102,105)

Following stmt geneater error :
ORA-12028: snapshot type is not supported by master site

desc CREDIT_CARD

 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------
 USER_ID                                   NOT NULL VARCHAR2(20) --> foreign key
maps to cust_dtl
 APPL_NO                                   NOT NULL NUMBER(7) --> Primary key
 APPLIED_DATE                                       DATE
 ASSOCIATE_CODE                                     VARCHAR2(25)
 GROSS_INCOME                                    NUMBER(13)

desc CUST_DTL
Name                                      Null?    Type
----------------------------------------- -------- ----------------------------
USER_ID                                   NOT NULL VARCHAR2(20) --> Primary key
PASSWORD                                  NOT NULL VARCHAR2(20)
REGISTRATION_DATE                         NOT NULL DATE
FIRST_NAME                                NOT NULL VARCHAR2(30)
MIDDLE_NAME                                        VARCHAR2(30)
LAST_NAME                                          VARCHAR2(30)
DATE_OF_BIRTH                             NOT NULL DATE
SEX                                                VARCHAR2(6)
RES_TEL1                                           VARCHAR2(40)
OFF_TEL1                                           VARCHAR2(40)
EMAIL_ID                                              VARCHAR2(40)
MOBILE                                             VARCHAR2(40)
PAGER                                              VARCHAR2(40)
OFF_EXT                                            VARCHAR2(40)
RES_CITY_CODE                                      NUMBER(7)

I wants to create a snapshot which would be update on every commit. But when I
execute this command its giving me the error. Its important coz in these tables
there r 20 lacs records and this query takes more time. So, I thought that
materialized view is better for this.

Pls help me

Followup:
the mv is totally the wrong approach here.

if you have 2,000,000 records -- it is going to take 10 seconds or so.  Is that
too much time?

how much time is it taking?

you are not using the RBO are you which would use indexes and take forever
probably.

what is your tkprof for this query....

you don't want to DUPLICATE this data.  databases were born to join, it is
one of the things they do best.

January 29, 2003 Sankar -- Thanks for the question regarding "About Oracle Intenet Directory and Java JNDI", version 8i
You Asked
what are the java JNDI API search methods are supported by
Oracle Internet Directory ? Any pointers to the place where I
can find some examples.
and we said...
jndi is an api to find things, oid is a place to store
things and jndi is able to query an ldap source to find things
(we might use oid to store things we would expose to a jndi
client) but, out of the box, ldap doesn't "do" jndi....

It is sort of apples and oranges.

JNDI is ideally suited to talking to an LDAP to find things. It
can also be used to find other stuff like JDBC connection pools.
I'd say the best place to find examples is to go to java.sun.com
and look under their JNDI section. Also, their tutorials should
have examples of how to have JNDI talk to an LDAP.
Review & Followup
Rating: 2
Oracle Internet Directory January 28, 2003
Reviewer: Sankar Chowdhury from Australia
I am looking for some concise outline, steps, sample structure/blueprint (with
respect to schema, DIT, entry, additional object class, password policy, etc)
and sample code to implement Oracle Internet Directory along with Single Sign On
feature on Oracle 9iAS Release 2. Any help, suggestion, guidance and reference
will be very much appreciated. Most useful will be some extracts of some project
where similar technology has been implemented.

Regards
Sankar

Followup:
I'll send you over to devtrends.oracle.com -- that is more up Cameron's alley!

Rating: 3
sources January 29, 2003
Reviewer: ashraf
kindly can you mention any link for free java source code in oracle database i
was read link before in your site but i miss which is not oracle site

Followup:
just find any java code out there you want.  unless it is a GUI application, you
can stuff it in the database if you want.

look around, thousands of sites out there with tons of questionable, albeit
free, code for you.

January 29, 2003 gs -- Thanks for the question regarding "very large redo generation. How to trace?", version 9.2.0.2
You Asked
Dear Tom,

Recently I have noticed the DB is generating lot of redo log. I am not aware of
any application changes or init.ora parameter change.

How do I trace, what is causing the excessive redo?
(I am planning to use statspack and see what I can find). Is there a better
option to track it?

Thanks.
and we said...
log miner.
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96521/logminer.htm#17869
Review & Followup
Rating: 5
Thanks Tom. January 29, 2003
Reviewer: gs
After sending you the question, I realised logminer is the way to go and started
exploring it. Now, your answer confirm it. Thanks.

January 29, 2003 Louis -- Thanks for the question regarding "About constraints", version 8.1.7.4
You Asked
Hi tom,

I know you have stressed in the past that we must name our
constraints. But let's say I have inherited a database that
does not have them. How can I name them without rebuilding
the database?

thanks,

Louis
and we said...
You'll have to wait until you upgrade to 9iR2:

ops$tkyte@ORA920> create table t ( x int primary key );

Table created.

ops$tkyte@ORA920> @dbls

OBJECT_TYPE  OBJECT_NAME                    S TABLESPACE_NAME
------------ ------------------------------ - ------------------------------
INDEX        SYS_C004432                      USERS

TABLE        T                                USERS

ops$tkyte@ORA920> alter table t rename constraint SYS_C004432 to t_pk;

Table altered.

Or, before that you can

o drop the constraint
o add the constraint back with a new name with ENABLE NOVALIDATE (fast)

it'll work on everything but a primary key -- which will lose the index (unless
you use keep index in 9i on the drop) and cause it to be rebuilt.
Review & Followup
Rating: 4
Primary key index January 29, 2003
Reviewer: Tony from Canada
Hi Tom,
       I tried your example with primary key constraint and was able to rename
the constraint but now index is still there with the old name:

create table t ( x int primary key );

sql>select table_name, index_name from user_indexes where table_name='T';

TABLE_NAME                     INDEX_NAME
------------------------------ -------------------
T                              SYS_C001803

sql>alter table t rename constraint SYS_C001803 to pk_t
/

sql>sql>select table_name, index_name from user_indexes where table_name='T';

TABLE_NAME                     INDEX_NAME
------------------------------ -------------------
T                              SYS_C001803

regards
Tony

Followup:
alter index xxx rename to yyy;

the index doesn't have to be and many times is not the same as the constraint
name.

January 29, 2003 Shelly -- Thanks for the question regarding "hot backup for a tablespace with multiple data files", version 8.1.7
You Asked
I have a tablespace with multiple data files. Is there any
problem if we backup in the following way:

Alter tablespace tablespace1 begin backup;
cp datafile101 to backupdir/datafile101
Alter tablespace tablespace1 end backup;

Alter tablespace tablespace1 begin backup;
cp datafile102 to backupdir/datafile101
Alter tablespace tablespace1 end backup;

We put the tablespace in backup mode multiple times - once for
each datafile. Can we recovery the tablespace with this backup
approach?

Really appreciate your help.

Thanks

Shelly
and we said...
Yes you can.  But, it would be better if you did not if you did
not have to (just from a book keeping perspective, it just
doesn't "feel" right).

To us, if you had to restore tablespace1, it would look like you
took datafile101 from last weeks backup and datafile102 from
this weeks backup.  We can deal with it.

This can happen naturally -- for example if you keep a rolling
backup so you have the last 3 full sets around (a good idea, you
need at least the last 2 to protect yourself), you can take a
mixture of datafiles from each of the three backup sets and use
them. You might have to do this for example if the tape with the
last backup could get datafile101 but not 102 and the other tape
could read 102 but not 101.

If you use RMAN, you don't even have to put the tablespaces into
backup mode!
Review & Followup
Rating: 5
Reader December 16, 2001
Reviewer: Reader from USA
Tom,

“
If you use RMAN, you don't even have to put the tablespaces into
backup mode!
“
Per Manual:

The Oracle server session reads whole Oracle blocks and checks to see whether
the block is fractured by comparing control information stored in the header and
footer of each block. If the session detects a
fractured block, then it re-reads the block. For this reason, do not put
tablespaces into hot backup mode when using Recovery Manager to back up or copy
database files.

If the Block is found fuzzy, RMAN tries to read the block from SGA in CUR mode.
If the application is using these blocks, could it lead to
buffer busy waits and RMAN to wait till the blocks are freed from the
application ( provided 1 datafile/channel/backupset )

Rating: 5
December 16, 2001
Reviewer: Reader from USA
Tom,

I am getting a huge amount of BUFFER BUSY WAITS ( as gathered from statspack)
intermittently at a regular intervals during execution of RMAN.
During these outburst of BUFFFER BUSY WAITS, the whole network traffic dips
way down, stays like that for about 5-19 minutes and then resumes normal

Are these buffer busy waits due to contention of RMAN trying to read
fractured blocks from SGA while the application for some reason has
them being used. If not, is there anything else I may be looking for,
that causes the glitch in performance (sorry to be too vague)

Thanks

Followup:
RMAN is getting the blocks in a mode similar to what an UPDATE would.  So, if
you have heavy duty modifications going on to the data being backed up -- RMAN
could be hitting buffer busy waits  -- definitely.  Its not fractured but
"fuzzy" blocks (inconsistent, old).

Rating: 5
Reader December 17, 2001
Reviewer: Reader from USA
Tom,

Thanks very useful information

1. Since RMAN gets blocks similar to UPDATE
   ,dose it first get the blocks in QUERY mode
   and then look for the blocks in CURRENT mode or
   straight away gets the blocks in CURRENT mode

   If the block keeps changing during backup, does RMAN keep re-reading
   the blocks until backup completes to store the most current version
   of the block in the backup set

2. When RMAN tries to backup a datafile, does it
   first look at SGA, and skip those blocks in
   SGA and read the remaining blocks from disk (physical read)

3. When RMAN completes a backup of a file, does backed up
   file consistent to the header SCN of the file when the
   backup is completed

Thanks

Followup:
it should only be doing this re-read when it finds fractured blocks (blocks
where the head != tail).  rman does not care about the last change to the block,
only in getting a consistent (logically consistent) block.

it should be reading from the disk.

The datafiles in general are fuzzy (inconsistent) if the database is open and
activity takes place.

Rating: 5
Reader December 18, 2001
Reviewer: Reader from USA
Tom,

Sorry for prolonging this posting.

<quote>
RMAN is getting the blocks in a mode similar to what an UPDATE would.
Its not fractured but "fuzzy" blocks (inconsistent, old).
<quote>

If the hot backup set is always "fuzzy" because the database is open, and data
changing all the time, and RMAN does not care about the versions of DATA BLOCK,
could you clarify, why Rman getting blocks similar to what update would.

Thanks

Followup:
sorry -- i seem to have that backwards don't I.  its fractured (maybe you could
call them "fuzzy", I've always applied fuzzy to files but it might make sense
for a block) not fuzzy.

Rating: 4
fuzzy column in v$datafile_header 9.2.0 September 10, 2002
Reviewer: A reader
Hi Tom

Why fuzzy column in v$datafile_header in Oracle 9.2.0.1.0 always shows YES?

SQL> select * from v$backup;

     FILE# STATUS                CHANGE# TIME
---------- ------------------ ---------- --------------------
         1 NOT ACTIVE                  0
         2 NOT ACTIVE                  0
         3 NOT ACTIVE                  0
         4 NOT ACTIVE                  0
         5 NOT ACTIVE                  0
         6 NOT ACTIVE                  0
         7 NOT ACTIVE                  0
         8 NOT ACTIVE                  0
         9 NOT ACTIVE           13173827 2002-SEP-10 13:10:32
        10 NOT ACTIVE                  0
        11 NOT ACTIVE                  0
        12 NOT ACTIVE           13173827 2002-SEP-10 13:10:32
        13 NOT ACTIVE           13173827 2002-SEP-10 13:10:32
        14 NOT ACTIVE                  0
        15 NOT ACTIVE                  0

SQL> select * from v$datafile_header;

     FILE# STATUS  ERROR                  FORMAT REC FUZ CREATION_CHANGE#
CREATION_TIME        TABLESPACE_NAME
---------- ------- ------------------ ---------- --- --- ----------------
-------------------- ----------------
         1 ONLINE                              8 NO  YES               13
2002-AUG-04 16:23:55 SYSTEM
         2 ONLINE                              8 NO  YES          8111170
2002-AUG-28 16:34:21 UNDOTBS1
         3 ONLINE                              8 NO  YES             6108
2002-AUG-04 16:24:55 CWMLITE
         4 ONLINE                              8 NO  YES             6125
2002-AUG-04 16:24:59 DRSYS
         5 ONLINE                              8 NO  YES             6148
2002-AUG-04 16:25:16 EXAMPLE
         6 ONLINE                              8 NO  YES             6166
2002-AUG-04 16:25:20 INDX
         7 ONLINE                              8 NO  YES             6187
2002-AUG-04 16:25:23 ODM
         8 ONLINE                              8 NO  YES             6205
2002-AUG-04 16:25:25 TOOLS
         9 ONLINE                              8 NO  YES             6223
2002-AUG-04 16:25:29 USERS
        10 ONLINE                              8 NO  YES             6240
2002-AUG-04 16:25:33 XDB
        11 ONLINE                              8 NO  YES           157909
2002-AUG-04 17:48:26 OEM_REPOSITORY
        12 ONLINE                              8 NO  YES           174958
2002-AUG-05 12:15:51 USERS
        13 ONLINE                              8 NO  YES         13173267
2002-SEP-10 12:51:13 USERS
        14 ONLINE                              8 NO  YES          7824550
2002-AUG-22 17:30:16 DICT_USERS
        15 ONLINE                              8 NO  YES          7824957
2002-AUG-22 17:37:04 DICT_USERS2

is it a bug?

Followup:
It is not

ops$tkyte@ORA920.US.ORACLE.COM> alter tablespace users read only;

Tablespace altered.

ops$tkyte@ORA920.US.ORACLE.COM> @test

     FILE# FUZ FNAME
---------- --- --------------------------------------------------
         1 YES /usr/oracle/ora920/oradata/ora920/system01.dbf
         2 YES /usr/oracle/ora920/oradata/ora920/undotbs01.dbf
         3 YES /usr/oracle/ora920/oradata/ora920/cwmlite01.dbf
         4 YES /usr/oracle/ora920/oradata/ora920/drsys01.dbf
         5 YES /usr/oracle/ora920/oradata/ora920/example01.dbf
         6 YES /usr/oracle/ora920/oradata/ora920/indx01.dbf
         7 YES /usr/oracle/ora920/oradata/ora920/odm01.dbf
         8 YES /usr/oracle/ora920/oradata/ora920/tools01.dbf
         9 NO  /usr/oracle/ora920/oradata/ora920/users01.dbf
        10 YES /usr/oracle/ora920/oradata/ora920/xdb01.dbf

10 rows selected.

ops$tkyte@ORA920.US.ORACLE.COM> alter tablespace users read write;

Tablespace altered.

ops$tkyte@ORA920.US.ORACLE.COM> @test

     FILE# FUZ FNAME
---------- --- --------------------------------------------------
         1 YES /usr/oracle/ora920/oradata/ora920/system01.dbf
         2 YES /usr/oracle/ora920/oradata/ora920/undotbs01.dbf
         3 YES /usr/oracle/ora920/oradata/ora920/cwmlite01.dbf
         4 YES /usr/oracle/ora920/oradata/ora920/drsys01.dbf
         5 YES /usr/oracle/ora920/oradata/ora920/example01.dbf
         6 YES /usr/oracle/ora920/oradata/ora920/indx01.dbf
         7 YES /usr/oracle/ora920/oradata/ora920/odm01.dbf
         8 YES /usr/oracle/ora920/oradata/ora920/tools01.dbf
         9 YES /usr/oracle/ora920/oradata/ora920/users01.dbf
        10 YES /usr/oracle/ora920/oradata/ora920/xdb01.dbf

10 rows selected.

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

Rating: 4
so datafile are always fuzzy now? September 10, 2002
Reviewer: A reader
hi tom

in 8.1.7 we nirmally see null in FUZZY column, in 9i datafiles are always fuzzy
now?

Followup:
It was a bug in 817 #1158714, fixed in 901.  The correct values are YES and NO
(null if offline and hence UNKNOWN)

Rating: 4
What does "fuzzy" tell us then? January 29, 2003
Reviewer: Dwayne Croteau from Calgary, Alberta
Hi Tom,

From page 4-19 of Oracle University's Backup & Recovery text for 8i I read the
following sentence:

"When the ALTER TABLESPACE BEGIN BACKUP command is issued, the value in the
FUZZY column for the tablespace's data files changes to YES to indicate that the
corresponding files are in backup mode"

As others have noticed in 9.2.0, the fuzzy column in V$DATAFILE_HEADER is "YES"
even when the file is not being backed up, except in the case when the
tablespace is read-only, as you pointed out.

So then, what does fuzzy tell us about our datafiles?  I looked at the 9.2.0
docs on this column and it only tells me what values are possible in this
column, but not what they mean.

Your response is greatly appreciated,

Dwayne

Followup:
in 9i you cannot use this to tell the status (in backup or not) of the file.

it'll be fuzzy = YES if the file is writeable -- period.  The file is "online
fuzzy"

January 29, 2003 jags -- Thanks for the question regarding "Database Replication", version 8.16
You Asked
Hi Tom,
Let me first explain what we want to do.
We have two similar databases say DB1 and DB2 ,having exactly same data at a
point of time say June 22 at  10:00 AM.
There are no users working on DB1. Everybody is logged out.
At 10:00 AM users logged in DB2 and used the database for 3 days.

Again on June 25 , 10:00 AM ,  everybody logged out from DB2 and no user is
logged in either DB1 or DB2.
Now we want to apply the changes made in DB2 to DB1.
Don’t know how to make it. Is there any way to get the SQL submitted to DB2 in
the last 3 days. If we can get that SQL  then  we can run that SQL on DB1 to
synchronize these two databases.

My friend told me that SYBASE has some thing that can achieve this. I don’t know
how to do this in ORACLE.

Your input would be very helpful.

Thanks in advance.

Jagdish
and we said...
It is called replication in both cases (sybase and Oracle).

You would setup replication to not "stream" the changes continously but rather
to queue them up and apply them all on demand.

See
http://technet.oracle.com/doc/oracle8i_816/server.816/a76959/toc.htm
Review
      & Followup

      Rating:
      5
  January 29, 2003
Reviewer:
      A reader
Tom,

I want to setup replication for testing purposes on 2 computers, is it necessary
that the computers have to be in domain, as i have windows 2k
professional(workgroup).

Thanks

          Followup:

just connected via tcp/ip.

January 29, 2003 Nitin -- Thanks for the question regarding
      "Restricted access to V$ views?", version 9.2.1.0

      You Asked
Hi Tom,

I am a DBA working as a developer on the current assignment. My problem is that
in this project, the DBAs have a policy of "no access to all V$ views" even in
the development environment. I can see a lot of situations arising during
development/testing/debugging where we will need information easily accessed
from the v$ views (locks, SQLs in SGA, running processes... list goes on). Even
for auditing purposes, there is only one way to get the OSUSER - V$SESSION.

I have not got any explanation regarding this hands-off policy on v$views other
than the fact that it is a matter of "policy".  I was wondering if you could
explain why should the v$ views be hands-off especially because they are
read-only views! Maybe I am missing something here - it's been troubling me for
the past two days.

Looking for a reason that makes sense...
Nitin.

      and we said...
in development they should not be.

In production -- they should be.  over use of the V$ views, which are in memory
tables generally, causes excessive latching.

You want to see a system run slow?  turn on those fancy dancy pretty GUIS with
blinking red lights that query the v$ tables every 15 seconds -- that'll really
slow down the system you are trying to fix.

For auditing --well, I would propose that -- hey, the DATABASE sort of already
does that for us!  we can just use the audit commands and let the DB do the
work.  it'll capture pretty much what we need without writing code.

In production -- I would be hard pressed (but not have a policy forbidding) to
understand why you would need access to it.  In development, you should for
tuning purposes....

I have a hard and fast policy -- there are to be no policies forbidding
anything.  that is silly.  If you have a BUSINESS CASE that the DBA's cannot
solve in any other fashion that requires access to the v$ tables -- so be it.
For exmaple, you tell them:

Nitin:  Hey, I need access to v$session
DBA: why?
Nitin: Because I need the OS USER name
DBA: elect sys_context( 'userenv', 'os_user' ) from dual
Nitin: oh, that's even easier ;)

Review &
      Followup

      Rating: 4
Totally
      agree!  January 29, 2003
Reviewer:  Nitin
      Sood  from Albany, NY
Thanks for the tip on using sys_context. The lesson in there is to look at "all
available resources" before knocking on the DBAs door.

Nicely answered... thanks again!

January 30, 2003 Stefan -- Thanks for the question regarding
      "Number passed to me is a date 1234, how to convert", version 8.1.7

      You Asked
We have external information passed into us from a company.
They pass to us a number, exp. 1234.
The first digit is the year, 1 = 2001.
The last three are the day of the year. 234 = 234th day of the year.

How can I convert the above to dd/mon/yyyy?

Thanks in advance!

      and we said...
ps$tkyte@ORA817DEV> variable n number
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> exec :n := 1234

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> select to_date( :n + 2000000, 'yyyyddd' )
  2    from dual
  3  /

TO_DATE(:N+
-----------
22-AUG-2001

ops$tkyte@ORA817DEV> exec :n := 2005

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> select to_date( :n + 2000000, 'yyyyddd' )
  2    from dual
  3  /

TO_DATE(:N+
-----------
05-JAN-2002

Review &
      Followup

      Rating:
      4
Smart  January 29, 2003
Reviewer:
      A reader
Quick thinking

      Rating: 5
very
      smart  January 29, 2003
Reviewer:  Pauline
       from NY,U.S.A

      Rating: 5
can you put
      your solution in English i.e. provide verbage for
      it  January 29, 2003
Reviewer:
      A reader
what is 2000000 ?

          Followup:

they give us a number like

1234

I know, if that number were:

2001234
yyyyddd

so, to turn 1234 into 2001234, we add 2000000.  that is all.

      Rating:
      5
  January 30, 2003
Reviewer:
      Abdal-Rahman Agha  from Yemen
Intelligent way.

      Rating: 4
a less
      intelligent way........  January 30, 2003

Reviewer:  Anirudh  from N.Delhi, India
well, also it can be:-
select to_date(200||substr(:n,1,1),'yyyy')+substr(:n,2) from dual;
more crude...perhaps

      Rating: 3
Bug in
      Anirudh's Code  January 30, 2003
Reviewer:
      Sajid Anwar  from London
Dear Anirudh,

I found your code incorrect. Here is the correct version.

select to_date(200||substr(:n,1,1),'yyyy')+ to_number(substr(:n,2)- 1) from
dual;

Regards,
Sajid Anwar
London

      Rating: 4
Elegant
      coding  January 30, 2003
Reviewer:  Chandra
       from Delhi, India
No ...

select to_date(200||substr(:n,1,1),'yyyy')+substr(:n,2) from dual;
 gives wrong result. The result is one day ahead of the right answer. WHY?? you
are adding 234 (in the above eg) to the first day of the year ie 01-JAN
effectively resulting in displaying the 235th day of the year.

The one provided by Tom is very elegant ...

      Rating: 4
How many digits
      make a date  January 30, 2003
Reviewer:
      Gary  from Sydney, Australia
"The first digit is the year, 1 = 2001"

Then what happens in 2010 ?

          Followup:

ops$tkyte@ORA817DEV> exec :n := 10001

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select to_date( :n+2000000, 'yyyyddd' ) from dual;

TO_DATE(:N+
-----------
01-JAN-2010

it continues to work.  it is just a number?

      Rating: 5
Very
      interesting  January 30, 2003
Reviewer:  Helena
      Markova  from Bratislava, Slovakia
select to_date(10125 + 2000000, 'yyyyddd' ) from dual;

TO_DATE(
--------
05.05.10

January 30, 2003 Colin -- Thanks for the question regarding
      "Gathering stats on chained rows", version 8.1.7

      You Asked
I have a question about analyzing tables to get chained rows data.  I used to
use something like this: ANALYZE TABLE DEPARTMENT LIST CHAINED ROWS INTO
CHAINED_ROWS;

Now I use DBMS_STATS.GATHER_TABLE_STATS, but the Oracle docs don't indicate any
way to specify that I want the chained_row stats.

How does the CHAIN_CNT column of DBA_TABLES get populated?  I thought that
ANALYZE TABLE did it, but DBMS_STATS doesn't seem to.

I am using Oracle 8.1.7.0.

      and we said...
Analyze does it.

dbms_stats gathers ONLY information used by the CBO -- the CBO doesn't consider
chained rows one way or the other -- hence dbms_stats does not get it.

Suggest you use dbms_stats for stats and

analyze table t list chained rows;

to report on row chaining AND to identify the culprit rows -- and only do that
if your statspack reports report EXCESSIVE table fetch continued row counts....
Review &
      Followup

      Rating: 5
I wish this
      kind of detail was available in the Oracle docs...  January
      30, 2003
Reviewer:  Colin Davies  from Pasadena, CA

This explained it completely for me.  Thanks again, Tom.

January 30, 2003 Ravi -- Thanks for the question regarding
      "Granting select on all tables and views of one user to other", version
815

      You Asked
Tom,

I have a user with x tables any y views. I wish to grant select
on all the tables and views owned by this user to another user.
Is there any thing like "select any table" for this purpose.

I am thinking to implement by granting the select on all the
tables and views to role and then granting the role to the
second user. But this way, if I create a new table/view for the
first user, then I may have to add this privs to the role again.
So I want to be more dynamic.

That is why I am coming to the expert(you). Will you please give
the best approach for this.

Thanks
Ravi

      and we said...
Well, there is no grant that allows a user or role access to all
objects in a schema like that however ...

If you start out granted select on all tables/views to some
user/role right now - I have a DDL trigger that works in
Oracle8i and up that will make it so new objects are granted in
a reasonable amount of time to that user/role.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:646423863863
for how this might work.
Review &
      Followup

      Rating: 3
Granting
      select, insert, update, delete on all tables of one user to a
      role  January 30, 2003
Reviewer:  an
       from DE
Hi Tom:

if i am not worry about new tables that will be created in the future from a
user, now i need to create a role, that should have all select, update, insert,
delete privs from the user, if it's no syntax as grant select, update, ... from
the user to the role, how can i create a procedure that will grant select,
update, insert, delete privs from the user to the role?
thanks,

          Followup:

begin
   for x in ( select tname from tab )
   loop
      execute immediate 'grant select, insert, update, delete on ' || x.tname ||

                        ' to whatever_role';
   end loop;
end;

January 30, 2003 Monther -- Thanks for the question regarding
      "Managing indexes", version 8.1.5

      You Asked
there is a sentence it the Oracle documentation says :

if two columns are used in the WHERE clause equaly often , ordering them from
most selective to least selective in the create index statement improves the
query performance.

what does this mean exactly , with some examples please .

for my example :
I have two columns : empno_type which specifies that each employee must have a
number type , and
                   empno_ser which spcifies that each employee must have a
serial number .

both of the columns has a VARCHAR2 datatype (even we have a serial for the
second column)and both will be used as a key to identify each employee , how the
previous statement imply on  such a case.

another small question :
does the create index statement have an option to specify a
descinding or an acsending order for a column to be indexed , ex.

create index  emp_idx on ( empno_type desc , empno_ser);

if yes , what is the benifit of that and why it used ?

regards,

      and we said...
it is false.

the proof that it is false (a myth) is in my book "Expert one on one Oracle".

It matters not -- in fact, putting the least selective column first can be
better - the index would be more compressable.

The order of items in an index should be dictated by how you use it.

if you query:

select * from t where b = :x
select * from t where a = :y and b = :x

then the index should be on (b,a) so it can be used for both. selectivity didn't
even come into play.  In fact, in 9i with skip scan index accesses if you query:

select * from t where b = :x
select * from t where a = :y and b = :x
select * from t where a = :y

I might want an index on LEAST selective column then MOST selective column in
order to permit a single index to be used for all three queries!

Review &
      Followup

      Rating: 3
both side
      index  January 30, 2003
Reviewer:  sandeep
       from Nagpur India
hi tom
i have created index on the table
(userid,parentid)
and i got that performance had improved
but not so much
when icreate another index as
(parentid,userid)
then the performance improve significantly
why it is so
and what is the diffrence between
(userid,parentid)
and
(parentid,userid)

          Followup:

why is the sky blue?

you need to provide an example -- ok.  Here is mine:

ops$tkyte@ORA920> create table t ( x int, y int, z char(255) );

Table created.

ops$tkyte@ORA920> insert into t select rownum, 1, 'x' from all_objects;

29614 rows created.

ops$tkyte@ORA920> create index t_idx on t(x,y);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA920> declare
  2          l_rec t%rowtype;
  3  begin
  4          for i in 1 .. 20000
  5          loop
  6                  select * into l_rec from t t_x_y where x = i and y = 1;
  7          end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> drop index t_idx;

Index dropped.

ops$tkyte@ORA920> create index t_idx on t(y,x);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          l_rec t%rowtype;
  3  begin
  4          for i in 1 .. 20000
  5          loop
  6                  select * into l_rec from t t_y_x where x = i and y = 1;
  7          end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

 now, arguably -- these are "lopsided".  Y=1.  X = unique value.

SELECT * from t t_x_y where x = :b1 and y = 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute  20000      2.42       2.22          0          0          0           0
Fetch    20000      1.81       1.77         52      60051          0       20000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    40001      4.24       4.00         52      60051          0       20000

Rows     Row Source Operation
-------  ---------------------------------------------------
  20000  TABLE ACCESS BY INDEX ROWID T
  20000   INDEX RANGE SCAN T_IDX (object id 38144)

SELECT * from t t_y_x where x = :b1 and y = 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.12          0          0          0           0
Execute  20000      2.27       2.21          0          0          0           0
Fetch    20000      1.83       1.69         52      60051          0       20000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    40001      4.11       4.03         52      60051          0       20000

Rows     Row Source Operation
-------  ---------------------------------------------------
  20000  TABLE ACCESS BY INDEX ROWID T
  20000   INDEX RANGE SCAN T_IDX (object id 38145)

Interesting, same LIO's, same rows - arguably same CPU/elapsed (for all
intents and purposes they are the same -- run 100 times and they would vary back
and forth)

Definitely different indexes -- the object id's changed in the plans....

So -- do like I do, provide a nice tidy sample that demonstrates the issue!

      Rating: 5
Excellent
      example  January 30, 2003
Reviewer:  Pawan
       from USA
I have learnt more from this site than anywhere else. I think that if we could
classify the questions by subject and group them together and then publish it as
a book - it will be the greatest Oracle book.
On a sidenote on Why the sky is blue :
Blue light's short wavelength causes it to get scattered around 10 times more by
oxygen and nitrogen molecules than the longer wavelengths (like red) of the
other colors visible to us. The blue in the sky we see is scattered blue light.

      Rating: 5
A
      side-side-note  January 30, 2003
Reviewer:
      A reader
On a side-side-note, why transform a dynamic, growing, flexible and wonderful
medium (aka this website) into a static, stagnant, rigid and wonderful medium
(aka a hard copy of this site)? :)

January 30, 2003 -- Thanks for the question regarding "writing a
      stand-alone application to continuously monitor a database queue (AQ)",
      version oracle server 8.1.5 enterprise edition

      You Asked
Hi Tom,
A question regarding oracle AQ...
I wish to write a small stand-alone application that would
*constantly* monitor a queue (only one queue) for the arrival of
a message and as soon as a mesage arrives, take some action. I
figured I could use the DBMS_AQ.DEQUEUE with wait option of
FOREVER (the default). What's unclear to me however, is that
would this application be running as a daemon all the time in
the background? Or is there a way to register the application
name with the queuing system so that any time a message arrives
in thte queue it executes this application.

Your help is appreciated...

      and we said...
The way queues work is that you would start your process
externally (eg: I put them in my startup scripts so that after
the database is started -- the daemons get started).  It is upto
you to get them going and keep them going.  We do not manage the
"dequeue" processes (which may or may not even be on the same
machine with the database)

Review &
      Followup

      Rating: 4
Multiple
      consumers  January 21, 2003
Reviewer:
      Quenton  from Victoria, BC, Canada
I would like to create an application pool to dequeue messages. Based on the
answer above, I would have to start my application multiple times (say 10) to
fill the pool (which could be on a different machine than my database). Each
consumer in the pool would have a different aq$_agent.name.  Now when a producer
enqueues a message it must define which consumer the message will be directed
to.

I am reasonably confident that this scenario is correct.  My question: 'Is there
a mechanism or API available that would enable my producers to distribute the
messages evenly across all consumers?'

          Followup:

why would you do it like that?  just have multiple dequeuers on the same queue
and don't make anyone direct a message to a specific dequeuer.

AQ is built do to that last part -- it is what it does.

      Rating: 3
Multiple
      Identical Dequeuers  January 21, 2003
Reviewer:
      Quenton  from Victoria, BC, Canada
If I have multiple identical dequeuers (the same process started many times) on
the same queue how do I prevent them from attempting to handle the same message?

The following excerpts from the "Application Developer's Guide - Advanced
Queuing" have me a little confused.

On page 7-3 udner Basic Queuing — Many Producers, Many Consumers of Discrete
Messages
it says "In this next stage, many producers may enqueue messages, each message
being processed by a different consumer depending on type and correlation
identifier.
All of my consumers would have the same type and correlation wouldn't they?

Page 8-63 under Multiple Recipients
it says "A message is considered PROCESSED only when all intended consumers have
successfully dequeued the message."
Since all of my consumers are identical won't they all have to process the
message before it can be dequeued?

          Followup:

that -- is what AQ is about.  It does the "at least once, at most once"
processing.  AQ is all about allowing multiple consumers feed off of a single
queue table -- at the same time, without blocking.

this is what AQ is all about....

Do this (stolen right from the AQ app developers guide basically)

set echo on

connect / as sysdba

DROP USER aq CASCADE;
CREATE USER aq IDENTIFIED BY aq;
GRANT EXECUTE ON DBMS_AQADM TO aq;
GRANT Aq_administrator_role TO aq;
GRANT CONNECT, RESOURCE TO aq;
GRANT EXECUTE ON dbms_aq TO aq;
connect aq/aq

CREATE type aq.Message_typ as object ( subject VARCHAR2(30), text VARCHAR2(80));
/

EXECUTE DBMS_AQADM.CREATE_QUEUE_TABLE ( queue_table => 'aq.objmsgs80_qtab',
queue_payload_type =>  'aq.Message_typ' );
EXECUTE DBMS_AQADM.CREATE_QUEUE ( queue_name => 'MSG_QUEUE', queue_table =>
'aq.objmsgs80_qtab');
EXECUTE DBMS_AQADM.START_QUEUE (  queue_name => 'MSG_QUEUE');

create procedure enqueue_msg( p_msg in varchar2 )
as
 enqueue_options dbms_aq.enqueue_options_t;
 message_properties dbms_aq.message_properties_t;
 message_handle RAW(16);
 message aq.message_typ;
BEGIN
   message := message_typ('NORMAL MESSAGE',  p_msg );
   dbms_aq.enqueue(queue_name => 'msg_queue', enqueue_options =>
enqueue_options,
                   message_properties => message_properties, payload => message,
                   msgid => message_handle);
   COMMIT;
end;
/

create procedure dequeue_msg
as
 dequeue_options dbms_aq.dequeue_options_t;
 message_properties dbms_aq.message_properties_t;
 message_handle RAW(16);
 message aq.message_typ;
BEGIN
   DBMS_AQ.DEQUEUE(queue_name => 'msg_queue', dequeue_options =>
dequeue_options,
                   message_properties => message_properties,
                   payload => message, msgid => message_handle);

   DBMS_OUTPUT.PUT_LINE ('Message: ' || message.subject || ' ... ' ||
message.text );
   COMMIT;
END;
/

Ok, now, open 3 separate windows.  I'm using unix (linux) so I wrote a small
script:

[tkyte@tkyte-pc-isdn tkyte]$ cat dq.csh
#!/bin/csh -f

while (1)
sqlplus aq/aq <<EOF
exec dequeue_msg
exit
EOF
end
[tkyte@tkyte-pc-isdn tkyte]$

In 2 of the 3 windows -- I ran that.  They each block on the dequeue.

Now, in the other window do this:

sqlplus aq/aq

SQL> exec enqueue_msg( 'Hello' );

and you'll see ONE of them get it, process it and go back for more.  Do it again
and you'll probably see the OTHER one get it, process it and go back for more
(or the first one will get it again).

Now, put that into a loop

begin
   for i in 1 .. 50
   loop
     equeue_msg( 'hello ' || i );
   end loop;
end;
/

and just watch them goto town.

This is what AQ does!

      Rating: 5
Excellent
      Example  January 22, 2003
Reviewer:  Quenton
       from Victoria, BC, Canada
Thanks, thats exactly what I needed to know.

      Rating: 3
Enqueue and
      Dequeue ADT with Pro*C  January 29, 2003
Reviewer:
      Robin
I am trying to enqueue and dequeue messages using Pro*C. I am following the
example provided by Pro*C/C++ Application Developer's Guide Release 8.1.6.
I have defined my payload as follows;
create or replace type xml_pyld_typ AS OBJECT (xml_pyld CLOB);

Here is the code I am using
EXEC SQL BEGIN DECLARE SECTION;

struct XML_PYLD_TYP
{
   OCIClobLocator * xml_pyld;
};
typedef struct XML_PYLD_TYP XML_PYLD_TYP;

XML_PYLD_TYP     *message = (XML_PYLD_TYP*)0;  /* payload */
char             user[60]="aquser/aquser@aq";  /* user logon password */
char             subject[30];  /* components of the */
char             txt[80];      /* payload type */
OCIClobLocator   *a_clob;      /* CLOB payload */

EXEC SQL END DECLARE SECTION;

EXEC SQL CONNECT :user;

/* Allocate memory for the host variable from the object cache : */
EXEC SQL ALLOCATE :message;

/* ENQUEUE */

strcpy(subject, "NORMAL ENQUEUE");
strcpy(txt, "The Enqueue was done through PLSQL embedded in PROC");

/* Initialize the components of message : */
EXEC SQL OBJECT SET xml_pyld OF  :message TO :a_clob;

/* Embedded PLSQL call to the AQ enqueue procedure : */
EXEC SQL EXECUTE
DECLARE
message_properties   dbms_aq.message_properties_t;
enqueue_options      dbms_aq.enqueue_options_t;
msgid                RAW(16);

BEGIN
/* Bind the host variable 'message' to the payload: */
dbms_aq.enqueue('xmlmsg_q',
enqueue_options,
message_properties,
:message,
msgid);
END;
END-EXEC;

I am getting the precompiler error "...PLS-S-00306, wrong number or types of
arguments in call to 'ENQUEUE'".

I have not been able to find any other Pro*C examples using ADT and CLOB's. Can
you provide an example of enqueing/dequeueing ADT messages containing CLOBs
using Pro*C on an 8.1.7 database?

          Followup:

there is a full example here:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76938/adq_aex3.htm#19176

      Rating: 1
Enqueue and
      Dequeue ADT with Pro*C using CLOB's  January 30, 2003

Reviewer:  Robin
This is the example I am working with, but I have modified it to use CLOB's
within the Object payload type. The problem is I cannot enqueue or dequeue
messages with the CLOB type in Pro*C.

Are there any Pro*C examples of enqueueing and dequeuing using CLOB's?

          Followup:

don't know -- you might try metalink.

      Rating:
      5
OCIClobLocator...  January 30, 2003

Reviewer:  Mark A. Williams  from Indianapolis, IN
      USA
I could be way off base here - it has been a long time since I've done any real
coding!  Anyway, here's an observation that may be relevant:

a_clob is:

OCIClobLocator   *a_clob;      /* CLOB payload */

Then the xml_pyld attribute of the XML_PYLD_TYP struct is set to a_clob:

EXEC SQL OBJECT SET xml_pyld OF  :message TO :a_clob;

but what does a_clob point to at this point?

It doesn't look like it has been initialized.  Doing a 'EXEC SQL ALLOCATE
:a_clob' should allocate the lob locator and initialize it to empty.

Again, don't know if that has anything to do with your problem or not - just
wanted to through it out there.

- Mark

      Rating: 5
I can't
      spell...  January 30, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Meant to write "throw" not "through" in that review.

Doh!

          Followup:

then/than
through/throw
from/form

i do that all of the time...

      Rating:
      2
OCIClobLocator...  January 30, 2003

Reviewer:  Robin
Thanks for the followup.  Tried your suggestion, but still have the same error.
I welcome any other suggestions you have.

Thanks again.

January 30, 2003 Anthony -- Thanks for the question regarding "How
      to use Cursor in Java", version version 8.1.5

      You Asked
I would like to get the previous/next record using cursor in
Java,
How i can get the previous/next record?please send me the syntax
or your suggestion
phrase Asap.
Thanking you,
with Regards
Raju

      and we said...
You need to use the JDBC 2.0 drivers (8.1.6 drivers) for
scrollable result sets.  It could look like this:

import java.io.*;
import java.sql.*;

class Scrollable extends Object {

public static void main(String args[]) throws Exception
{
    String sqlstr = "select empno from emp";

    DriverManager.registerDriver
        (new oracle.jdbc.driver.OracleDriver());

    Connection con = DriverManager.getConnection
            ("jdbc:oracle:thin:@think:1521:THINK1",
             "tkyte", "tkyte");

    //Create an scrollable insensitive updatable cursor
    Statement stmt =
       con.createStatement
          (ResultSet.TYPE_SCROLL_INSENSITIVE,
           ResultSet.CONCUR_UPDATABLE);

    ResultSet rset = stmt.executeQuery(sqlstr);

    boolean rtn = rset.first();
    System.out.println("Boolean value of rset.first:  " +
                    rtn + " row = " + rset.getRow() );
    System.out.println(rset.getInt("empno") );

    rtn = rset.last();
    System.out.println("Boolean value of rset.last:  "
                    + rtn + " row = " + rset.getRow() );
    System.out.println(rset.getInt("empno") );

    rtn = rset.relative(-1);
    System.out.println(
      "Boolean value of rset.relative(-1):  " +
       rtn + " row = " + rset.getRow() );
    System.out.println(rset.getInt("empno") );

    rset.close();
    stmt.close();
}

}
Review &
      Followup

      Rating:
      5
  May 22, 2002
Reviewer:
      A reader
Hi Tom,

I was reading DB2 Magazine today and found they have in DB2 V7 SCROLLABLE
CURSOR. I serach your site for "SCROLLABLE CURSOR" and found this hit.

  I like SCROLLABLE CURSOR approch.Do we have SCROLLABLE CURSOR  in oracle. If
yes, then how can we use it.
If not, then how we can get that functinality.

Thanks,

          Followup:

it is available in JDBC and OCI with 9i.

      Rating: 4
Big
      resultsets  May 23, 2002
Reviewer:  Pavel

what about big tables (like >20000 records)? if I want to go to the last record,
the JDBC driver has to read all rows?

          Followup:

We have to get to the last row in the resultset, yes.  The last record isn't
KNOWN until we get all of the intervening ones.  Consider this:

import java.io.*;
import java.sql.*;

class Scrollable extends Object {

public static void main(String args[]) throws Exception
{
    String sqlstr = "select object_id from all_objects";

    DriverManager.registerDriver
        (new oracle.jdbc.driver.OracleDriver());

    Connection con = DriverManager.getConnection
            ("jdbc:oracle:thin:@aria-dev:1521:ora817dev",
             "scott", "tiger");

    Statement sql_trace = con.createStatement ();
    sql_trace.execute( "alter session set sql_trace=true" );

    //Create an scrollable insensitive updatable cursor
    Statement stmt =
       con.createStatement
          (ResultSet.TYPE_SCROLL_INSENSITIVE,
           ResultSet.CONCUR_UPDATABLE);

    ResultSet rset = stmt.executeQuery(sqlstr);

    boolean rtn = rset.first();
    System.out.println("Boolean value of rset.first:  " +
                    rtn + " row = " + rset.getRow() );
    System.out.println(rset.getInt("object_id") );

    rtn = rset.last();
    System.out.println("Boolean value of rset.last:  "
                    + rtn + " row = " + rset.getRow() );
    System.out.println(rset.getInt("object_id") );

    rtn = rset.relative(-1);
    System.out.println(
      "Boolean value of rset.relative(-1):  " +
       rtn + " row = " + rset.getRow() );
    System.out.println(rset.getInt("object_id") );

    rset.close();
    stmt.close();
}

}

The tkprof shows:

select object_id
from
 all_objects

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      1      0.01       0.00          0          0          0           0
Fetch     2299      3.42       3.40         88      82224          4       22989
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2301      3.43       3.41         88      82224          4       22989

that we had to fetch all of the rows in the middle to get the first and the
last.

      Rating:
      5
  May 23, 2002
Reviewer:
      A reader
Hi tom,

canu you explain me

Connection con = DriverManager.getConnection
            ("jdbc:oracle:thin:@think:1521:THINK1",
             "tkyte", "tkyte");

what is think and THINK1

I am trying to connect but it is giving me error
my info
username = system
password = manager
connectstring = swami

error is

SQL>  exec scroll;
BEGIN scroll; END;

*
ERROR at line 1:
ORA-29532: Java call terminated by uncaught Java exception:
java.sql.SQLException: Io exception: The Network Adapter could not establish
the connection
ORA-06512: at "SYSTEM.SCROLL", line 0
ORA-06512: at line 1

Thanks

          Followup:

think is the hostname.  THINK1 is the oracle_sid on that host.

You need to read about jdbc -- this is the standard connect string for thin jdbc
connections.

DO NOT USE SYSTEM, that is our account.  Create your OWN account and work in
there.

      Rating:
      5
  May 24, 2002
Reviewer:
      A reader
Tom,

Thanks for your reply.

Your java code gave me the effect of scrollble cursor what DB2 is claming.
I rad DB2 documnetation and it very clumsy process if you want to have
scrollable cursor.You need to create global temporary table etc.. etc.. and lots
of limitation in it.

Your java stored procedure VERY VERY SIMPLE.

Another question.

DB2 magazine was taking about DYNAMIC SCROLLING of cursor.

Can you explain me about that.

Thanks for very informative very site(THE BEST VEB SITE).

Gaurang

          Followup:

I don't know what they might have meant by dynamic scolling of cursor (unless
they meant you can pass a variable to request the n'th row?)

      Rating:
      5
  May 28, 2002
Reviewer:
      A reader
Hi Tom,

 I have question regarding memory usage if we use java stored procedure.

 I am trying to create scrollable cursor.

 I have table with 1.5 Million rows.

 i am trying to select first,last and last-1 records through java stored
procedure(Excatly what your code is doing but my table has 1.5 Milloin rows).

My query only select one column from table.

I am getting following error.

 SQL> exec scroll_csr
BEGIN scroll_csr; END;

*
ERROR at line 1:
ORA-29532: Java call terminated by uncaught Java exception:
java.lang.OutOfMemoryError
ORA-06512: at "EDITDEV.SCROLL_CSR", line 0
ORA-06512: at line 1

I think it is memoery realted error(run out of memory).

Mermory statistics for session is

NAME                                VALUE
------------------------------ ----------
session uga memory                  58292
session uga memory max              91212
session pga memory              105941052
session pga memory max          105941052

Why so much memory is being used?

Can you explain?.

2)would JAVA_POOL be used for this process?

Can you explain about that.

Thanks.

Gaurang

          Followup:

what is the query -- there are infinitely more efficient ways to do this.

the java pool is for shared objects, it is part of the SGA.  PGA is always
process memory, never SGA memory.

      Rating: 5
Stored
      procedures vs updatable resultsets  January 30, 2003

Reviewer:  Bala Subramanian
Hi Tom

Do we get any performance benefit
with updatable cursors against stored procs?

With JDBC 2.0 API, you can build the new row and then insert it into both the
result set and the table in one step.
Is this better than calling a stored procedure to insert a row, and then return
a result set (ref cursor).

Thanks
Bala.

          Followup:

I seriously doubt it.  All of those things are "fake" -- they are implemented
client side.

You should benchmark it, report back if you like.

      Rating: 5
"Fake"
      scrollable cursor?  April 02, 2003
Reviewer:
      Dave Anderson  from Rhode Island, USA
Tom,
I'm trying to understand the architecture of the 9i scrollable cursor.  Is it
native, or does it have to materialize the result set.  If the latter, where,
the PGA?  I'm thinking this would be a negative performance characteristic.  (I
know, I will test.)

Thanks
Dave

      Rating: 4
Scrollable ref
      cursor?  August 12, 2003
Reviewer:  John
       from San Jose, CA
Hi Tom,

Ref cursor is never scrollable with ORACLE JDBC driver, yes?

John

January 30, 2003 Maurizio -- Thanks for the question regarding
      "Loading a master detail file using external tables and multi-table
      insert.", version 9.2.0.2

      You Asked
I'd like to load a file containing master/detail records using external tables
and multi-table insert.

Let's say the file contains records like these:

header (
record_type,
file_type,
file_id,
other file summary info
)

master (
record_type,
master_id,
creation_date,
...
other master columns
)

detail (
record_type,
master_id,
detail_id,
amount,
...
other detail columns
)

... in this sequence:

header record
master 1
detail 1, 1
detail 1, ..
detail 1, x
master 2
detail 2, 1
detail 2, ..
detail 2, y
..
master m
detail m, 1
detail m, ..
detail m, z

Also let's say that:
Header records have record_type 'H' and should be loaded in the HEADERS table.
Master records have record_type 'M' and should be loaded in the MASTERS table.
Detail records have record_type 'D' and should be loaded in the DETAILS table.

Is it possible to follow this approach?
Does it have performance issues I should take care of?
Could you provide an example?

The example above is just an example. Real info contained in the file could be
purchase orders, invoices, goods movement (and so on) but with a master/detail
structure. As a kind of standard, the file begins with a file header record
which contains some summary data on the file itself.

Thanks.
Maurizio De Giorgi.

      and we said...
what you would do is set up a very generic table which can query ANY of the
records in the input file -- a CSV file would work.  If the record is really:

master 1
detail 1, 1

and it is not possible to:

master,1
detail,1, 1

have commas there -- you'll just use substr(c1,N) instead of C2 in the following
example (to get rid of the record type):

ops$tkyte@ORA920> create or replace directory data_dir as '/tmp'
  2  /
Directory created.

ops$tkyte@ORA920> create table csv_table
  2  (c1 varchar2(80),
  3   c2 varchar2(80),
  4   c3 varchar2(80),
  5   c4 varchar2(80),
  6   c5 varchar2(80),
  7   c6 varchar2(80),
  8   c7 varchar2(80),
  9   c8 varchar2(80),
 10   c9 varchar2(80),
 11   c10 varchar2(80)
 12  )
 13  ORGANIZATION EXTERNAL
 14  ( type oracle_loader
 15    default directory data_dir
 16    access parameters
 17    ( fields terminated by ','
 18      optionally enclosed by "'"
 19      missing field values are null
 20    )
 21    location ('test.dat')
 22  )
 23  /
Table created.

ops$tkyte@ORA920> create table header( col1 number, col2 date, data varchar2(20)
);
Table created.

ops$tkyte@ORA920> create table master( master_id number, creation_date date,
data varchar2(20) );
Table created.

ops$tkyte@ORA920> create table detail( master_id number, detail_id number,
amount number, data varchar2(20) );
Table created.

ops$tkyte@ORA920> !echo header,53,01/01/2003,Hello World > /tmp/test.dat
ops$tkyte@ORA920> !echo master,1,01-sep-2002 01:02:03,some stuff >>
/tmp/test.dat
ops$tkyte@ORA920> !echo detail,1,1,52321,data data data >> /tmp/test.dat
ops$tkyte@ORA920> !echo detail,1,2,233,data data data >> /tmp/test.dat
ops$tkyte@ORA920> !echo master,2,11-mar-2001 11:02:03,some stuff >>
/tmp/test.dat
ops$tkyte@ORA920> !echo detail,2,1,52324,data data data >> /tmp/test.dat
ops$tkyte@ORA920> !echo detail,2,2,25323432,data data data >> /tmp/test.dat

ops$tkyte@ORA920> insert /*+ APPEND */ first
  2    when (c1 = 'header' ) then
  3        into header (col1,col2,data)
  4           values ( to_number(c2), to_date(c3,'dd/mm/yyyy'), c4 )
  5    when (c1 = 'master' ) then
  6      into master (master_id,creation_date,data)
  7           values ( to_number(c2), to_date(c3,'dd-mon-yyyy hh24:mi:ss'), c4 )
  8    when (c1 = 'detail' ) then
  9      into detail (master_id,detail_id,amount,data)
 10           values (to_number(c2),to_number(c3),to_number(c4),c5 )
 11  select * from csv_table;

7 rows created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from header;

      COL1 COL2      DATA
---------- --------- --------------------
        53 01-JAN-03 Hello World

ops$tkyte@ORA920> select * from master;

 MASTER_ID CREATION_ DATA
---------- --------- --------------------
         1 01-SEP-02 some stuff
         2 11-MAR-01 some stuff

ops$tkyte@ORA920> select * from detail;

 MASTER_ID  DETAIL_ID     AMOUNT DATA
---------- ---------- ---------- --------------------
         1          1      52321 data data data
         1          2        233 data data data
         2          1      52324 data data data
         2          2   25323432 data data data

And that is a direct path load of the single input file into the three tables.
Review &
      Followup

      Rating: 3
fixed record
      formats?  January 30, 2003
Reviewer:  Maurizio
      De Giorgi  from ITALY
There are no alternative to "substr" generic columns if records are totally in
fixed formats?

Thanks again.
Maurizio De Giorgi.

          Followup:

I don't understand the problem -- can you post an example?  you can trim blanks
(leading or trailing) right in the external table definition.

      Rating:
      4
  January 30, 2003
Reviewer:
      A reader
hi Tom,

When i tried your example i am getting this error
O.S. Win 2k Server, DB.9.0.2 EE

test@DB1.ORADOM.COM> insert /*+ APPEND */ first
  2        when (c1 = 'header' ) then
  3           into header (col1,col2,data)
  4              values ( to_number(c2), to_date(c3,'dd/mm/yyyy'), c4 )
  5       when (c1 = 'master' ) then
  6         into master (master_id,creation_date,data)
  7              values ( to_number(c2), to_date(c3,'dd-mon-yyyy hh24:mi:ss'),
c4 )
  8       when (c1 = 'detail' ) then
  9         into detail (master_id,detail_id,amount,data)
 10              values (to_number(c2),to_number(c3),to_number(c4),c5 )
 11     select * from csv_table;
insert /*+ APPEND */ first
*
ERROR at line 1:
ORA-29913: error in executing ODCIEXTTABLEOPEN callout
ORA-29400: data cartridge error
KUP-00554: error encountered while parsing access parameters
KUP-01005: syntax error: found "identifier": expecting one of: "are"
KUP-01008: the bad identifier was: as
KUP-01007: at line 3 column 23
ORA-06512: at "SYS.ORACLE_LOADER", line 14
ORA-06512: at line 1

          Followup:

you made a typo in the CREATE TABLE CSV_TABLE somewhere then -- that is what
this is saying.

I just ran this in 9.0, worked fine.

      Rating: 3
The solution
      provided is really good to load any CSV file...  January 30,
      2003
Reviewer:  Maurizio De Giorgi.  from ITALY

...the problem is that I have to load mainly fixed format files.
I was not clear -- my fault -- I'am sorry.
So, if you still would help with a solution, here follow some additional
information.
A file contains only one document type.
A document is composed of a master record followed by 1 to N detail records.
All fields are of char type (although they should be checked to be date,
integer, decimal, etc).
I would avoid to manually define a different external table for each file.
I'am looking for a generic solution, perhaps a combination of external tables,
table functions (data checking/transformation), multi-table insert and so on.
A good subordinate solution would be something like your load/unload script
generator (possibly pl/sql based).
I'am thinking about creating a "repository" containing record formats
definitions, field, record and file level checking function names, and so on.

Header record (same format for all files).
field          desc                                 len offset

z_typrec       record type                          1   1

z_flowid       flow id                              8   2
z_sendappl     source system/application code       4   10

z_rcvprn       destination system/partner code      10  14
z_idoctyp      flow name                            30  24
z_mestyp       flow type                            30  54
credat         file creation date      (DDMMYYYY)   8   84

cretim         file creation time      (HH24:MI:SS) 6   92
z_totrec       number of documents (master records) 12  98

A Master record format (not complete).
E1BPDOCHDRP    record type                          1   1

FILLER         filler                               61  2

CO_AREA        Controlling area                     4   63

DOCDATE        Document date   (DDMMYYYY)           8   67

POSTGDATE      Document posting date   (DDMMYYYY)   8   75

VERSION        Version code                         3   83

DOC_NO         Document number                      10  86

VARIANT        Variant code                         5   96

DOC_HDR_TX     Document header text/description     50  101

USERNAME       Document created by ...              12  151

...

A Detail record format (not complete).
E1BPAAITM      record type                          1   1

FILLER         filler                               61  2

SEND_CCTR      cost center code                     10  63

ACTTYPE        activity type                        6   73

SENBUSPROC     business process code                12  79

ACTVTY_QTY     activity quantity       (INTEGER)    15  91

ACTIVITYUN     activity unit of measure             3   108

ACTIVITYUN_ISO activity unit of measure ISO CODE    3   111

PRICE          price                   (DECIMAL)    11  114

CURRENCY       price currency code                  5   139

CURRENCY_ISO   price currency ISO code              3   144
...

Thanks anyway.
Maurizio De Giorgi.

          Followup:

if you have different lengthed fixed format records in the same file, you'll
either

o setup a separate external table def for each with its unique postition and a
WHEN condition to pick off the right types

o setup a single one and create N views -- using substr to pick of the fields.

nothing magical we can do there.  It is a very "unusual" format to mix into a
single file fixed width, positional data.  Too bad you cannot get just delimited
data -- whereby the file would be a fraction of its current size.

      Rating: 3
You are a
      gentleman...  January 30, 2003
Reviewer:
      Maurizio De Giorgi  from ITALY
If I could know who is the "artist" that created such an "unusual" masterpiece,
I would change him/her nose to an "unusual" shape.

Thanks.
Maurizio De Giorgi.

January 30, 2003 Nadya -- Thanks for the question regarding
      "public synonym", version 8i

      You Asked
Tom, we read your answers about using public synonyms.
But I can't exactly understand is it recommended to use public synonyms, or
their number should be limited.
The matter is: historically we use public synonyms for every object such as
table, view, sequence. From your answers we have known they lead to non-existing
objects and reduce performance. How much the using of public synonyms influences
on performance,so must we reduce
the number of public synonyms or it's not very important thing.
Ones more question: One of our server procedures gives compilation error it's
too long. From documentation we know, we must divide it.
But we want know what factors restrict the size of storage procedure?
Are they permanent or tuned?
Thanks.
Nadya

      and we said...
You should limit your use of public synonyms.

When I write PLSQL code, I never use public synonyms in the code itself.  I use
schema names in the code.

I might create a single (or two or three) public synonyms for the TOP LEVEL
procedure that calls the package -- the "entry point" into the system for web
based systems, so users can use a URL like:

http://host/pls/application_name
instead of

http://host/pls/schema.pkg.procedure
but all URLS after that would be in the form of the second one above.

Public synonyms -- they should be used purely for the convienence of the end
user (limited number of entry points = limited number of synonyms).

In your code you can use:

o schema names
o alter session set current_scheme = <some name> if you don't want to use schema
names.

If you have a procedure that gives you a compilation error due to size, it must
be VERY large indeed (i shudder to think how large).  I'm a guy who firmly
believes a PROCEDURE should fit on a page -- a single screen (at the highest
resolution I can get about 90 lines of code on a screen).

If it doesn't -- its too big to be called a procedure, you need to recode it in
a more modular fashion.

Hopefully when you say "procedure" you really mean package -- you should only
use packages and packages should have lots of little, modular, understandable
procedures/functions in them.

The limit is based on the size of the generated pcode.  they are permanent,
burned into the database, you cannot set them.

Review &
      Followup

      Rating:
      5
Performance  December 10, 2001
Reviewer:
      Jim  from Australia
Tom,
Totally agree with you on the one page limit
for procedures.

You have previously mentioned packages increase your namespace, what is
"namespace" please ?

Also I seem to recall using a network monitoring program once that showed when
referencing a public synonym in a client server app, that there was at least 3
network
trips to resolve the synonym, the first 2 times there
was an Oracle error code returned, the sniffer program
logged it but the error code did not cause the client application to halt.
I have this theory, the first time Oracle trys to resolve
the object (table, stored procedure etc)
second trip it looks for a private synonym,
third time it looks for a public synonym.

Do you know if I'm correct ot how I might test it
I don't have access to the network monitoring app anymore

          Followup:

The namespace thing I refer to is the namespace you have to create object names
with.

Lets say you use procedures -- no packages.  You can have one procedure "push"
and one procedure "pop" in your schema.  If someone else writes a "push" and a
"pop" in your schema, it'll overwrite your push and pop.

Now, you put it into a packages "utils".  You can put procedures push, pop, x,
y, z in there (whatever).  You have one NAME in the database, "utils".  anyone
can put another package in there -- say "foo" with procedures push, pop, x, y,
z.  You've increased your "namespace".

There would not be any increased roundtrips made by correctly programmed
software with public synonyms.  If you submit "select * from emp" to the
database -- all of the work is done in that one trip.  What you might have been
seeing was a client server app that itself was trying to figure out what the
heck "emp" was.

      Rating:
      5
Thanks  December 10, 2001
Reviewer:
      Jim
Thanks Tom..
The client app was built using Powerbuilder,
perhaps thats all that needs to be said ;-)

      Rating: 4
private synonym
      vs alter session   January 30, 2003
Reviewer:
      Dilip  from NY, NY USA
As mentioned fully qualifying or executing alter session set current_scheme is
better than having public synonym.
a) Do we have the same performance hit if we use private synoymn's?
b) Will there be difference in preformance if using private synonym vs alter
session set current_sch..?
Thanks

          Followup:

a) no, much less
b) no, i did not really observe any

The problem with public synonyms is they are sort of "anti objects".  all kind
of dependency hoops to jump through since they are not in your schema.  with
private synonyms -- there is a real object.

January 30, 2003 Venkateswaran -- Thanks for the question
      regarding "How To View a view from the diff. user ?", version 8.1.5

      You Asked
I created a view called "V_emp" in one user object called cdsmanager and i want
to view the content of the view "V_Emp" from diff. user called cdsbatch which is
there in the same database.

I dont want to use

select * from cdsmanager.v_emp;
(Or)
I dont want to use synonym

Is there any way around to get the content without mention the username (I mean
without using CDSMANAGER.view name)?

      and we said...
Your options are (I'm going to list ones you don't want to use as well just to
get the exhaustive list)

o use a private synonym.  This will make it so that cdsbatch only does not have
to qualify the tablename.  As cdsbatch issue:

  SQL> create synonym v_emp for cdsmanager.v_emp;

o use a public syonym.  This will make it so that all users could use v_emp if
they have been granted access to the base table.  By that I mean all users will
see the synonym v_emp but they might not be able to see the base table
cdsmanager.v_emp.  As a user with the create public syonym privelege execute:

  SQL> create public synonym v_emp for cdsmanager.v_emp;

o use ALTER SESSION set current_schema=cdsmanager; command in cdsbatch's
session.  Then, instead of adding cdsbatch as the default schema name when
resolving objects -- we will use cdsmanager as the default name.  For example:

ops$tkyte@DEV816> desc emp
ERROR:
ORA-04043: object emp does not exist

ops$tkyte@DEV816> alter session set current_schema=scott;
Session altered.

ops$tkyte@DEV816> desc emp
 Name                          Null?    Type
 ----------------------------- -------- --------------------
 EMPNO                         NOT NULL NUMBER(4)
 ENAME                                  VARCHAR2(10)
 JOB                                    VARCHAR2(9)
 MGR                                    NUMBER(4)
 HIREDATE                               DATE
 SAL                                    NUMBER(7,2)
 COMM                                   NUMBER(7,2)
 DEPTNO                                 NUMBER(2)

It finds scott.emp for me the second time without having to specify scott.  This
does not affect your priveleges at all -- you still need to be granted access to
the object.  This alter session simply changes the way we attempt to resolve
names.

o use a view.  As cdsbatch issue:

  create view v_emp as select * from cdsmanager.v_emp;

that has the advantage over the synonyms in that cdsbatch has a copy of the
metadata about v_emp in his schema.  A query on user_tab_columns and such when
logged in as cdsbatch will return rows for v_emp.  A synonym would not do that.

Thats about the extent of your options.  If you do not like them and have a
specific reason for not wanting to use a synonym or a view or an alter session
-- you can post a question with those reasons and maybe we can work through your
concerns about using that feature (eg: why don't you want to use a private
synonym?  what is the reasoning behind not wanting a synonym??)'

followup to comment one below

an excerpt from above:

...
It finds scott.emp for me the second time without having to specify scott.  This
does not affect your priveleges at all -- you still need to be granted access to
the object.  This alter session simply changes the way we attempt to resolve
names.
...

the alter session command does NOT give you the ability to do anything you
cannot do already.  It ONLY changes the default schema name we use to find
objects -- thats it.

Review &
      Followup

      Rating:
      4
  June 20, 2001
Reviewer:  Gururaj
       from GA,USA
Excellent one!! I liked your answers with questions :))

It is very useful. But using alter session set current_schema=system, I can get
to system schema without
any privilege. How to avoid this "problem" ?

      Rating:
      3
  June 21, 2001
Reviewer:  Nirmal
       from Qatar
Your answers are good. But still, Assume that, i have the EMP table in both
schema S1, S2  and my current session schema is S1. Such a case, if u alter the
session's schema as S2, how can i access the EMP table which is in S1 schema.
Hope that i should access by S1.EMP, isn't.

So what is the use on changing the schema then. Hope that the person should
explan why he don't want synonym/views.

      Rating: 5
One
      more!!!  January 30, 2003
Reviewer:  Kumar
       from USA
Our application login to database using the schema user.But due to some auditing
problem we have been told to change the way we are login to the application with
the new user.

Possible ways:
Create a Role
Create a New User(Login to the application)
Create the Public Synonym for the used objects
Grant the public Synonym to the created role
Grant the Role to the Created User.

But what i have seen is creating Public Synonym or private affects the
scalibilty and it also pollutes the Namespace.
(Reference from Steve Adams Article)i tought doing the alter session set
current_schema = "Definer User" so this one will solve from creating thousands
of synonyms.

Do you think we are on the right Track!

          Followup:

if you can avoid creating thousands of public synonyms -- sure

January 31, 2003 Steven -- Thanks for the question regarding
      "email - sending from D2K", version v7.3.2

      You Asked
How to trigger email sending to clients while using Forms of Dev
2K?    Or from database?

Thanks a lot.

Steven

      and we said...
With 7.3.2 which is the database you have, sending email is
"hard" from the database.  It can be done, but it takes an
amount of work.

With D2k, we can use ole automation to do this and since you are
willing to do it from there -- here we go.

This is a support note on this very topic:

Article-ID:         <Note:61736.1>

Exchange using OLE2
Keywords:
DEVELOPER/2000;FORMS;REPORTS;FAX;EMAIL;EXCHANGE;OLE;OLE2
_________________________________________________________________
Programmatically sending a Fax/Email

==========================================================

INTRODUCTION:
-------------

This bulletin explains how to programmatically send a fax/email
message from a Forms/Reports application via Microsoft Exchange
without any kind of user interaction. It shows the general usage
of the 'Mail' package as well as a fully coded Forms sample
application.

The concept of OLE (Object Linking and Embedding) automation is
used to control the OLE server application (Microsoft Exchange)
using the client application. The client in this case may be a
Developer/2000 Forms or Reports application. It uses the objects
and methods exposed by the OLE Messaging Library which are
much more robust than the MSMAPI OCX controls and allow access
to many more MAPI properties.

Oracle provides support for OLE automation in its applications
by means of the OLE2 built-in package. This package contains
object types and built-ins for creating and manipulating the OLE
objects. Some of these built-ins for e.g. OLE2.create_obj,
OLE2.invoke, OLE2.set_property have been extensively used in
the code.

GENERAL USAGE:
--------------

The Mail package contains three procedures:

1. Procedure Mail_pkg.logon( profile IN varchar2 default NULL);
             ----------
   Use this procedure to logon to the MS Exchange mail client.
The procedure takes a character argument which specifies the
Exchange Profile to use for logon. Passing a NULL argument to
the logon procedure brings up a dialog box which asks you to
choose a profile from a list of valid profiles or create a new
one if it doesn't exist.

2. Procedure Mail_pkg.send(
             ---------  Recipient    IN varchar2,
                        Subject      IN varchar2 default NULL,
                        Text         IN varchar2 default NULL,
                        Attachment   IN varchar2 default NULL
                      );

   This is the procedure that actually sends the message and
attachments, if any, to the recipient. The recipient may be
specified directly as a valid email address or as an alias
defined in the address book. If the message is intended for a
fax recipient then a valid alias must be used that is defined as
a fax address in the address book.

3. Procedure Mail_pkg.logoff;
             -----------

   This procedure closes the Exchange session and deallocates
the resources used by the OLE automation objects.

SAMPLE FORMS APPLICATION:
-------------------------

1. Create the Mail Package using the following two Program
Units:

   (a) Mail Package Spec
   (b) Mail Package Body

Mail Package Spec:
------------------

PACKAGE Mail_pkg IS

  session OLE2.OBJ_TYPE;  /* OLE object handle */
  args OLE2.LIST_TYPE;    /* handle to OLE argument list */

procedure logon( Profile IN varchar2 default NULL );

procedure logoff;

procedure send( Recp       IN varchar2,
                Subject    IN varchar2,
                Text       IN varchar2,
                Attch      IN varchar2
               );

END;

Mail Package Body:
------------------

PACKAGE BODY Mail_pkg IS

 session_outbox OLE2.OBJ_TYPE;
 session_outbox_messages OLE2.OBJ_TYPE;
 message1 OLE2.OBJ_TYPE;
 msg_recp OLE2.OBJ_TYPE;
 recipient OLE2.OBJ_TYPE;
 msg_attch OLE2.OBJ_TYPE;
 attachment OLE2.OBJ_TYPE;

procedure logon( Profile IN varchar2 default NULL )is
 Begin

  session := ole2.create_obj('mapi.session');
                     /* create the session object */
  args := ole2.create_arglist;

  ole2.add_arg(args,Profile);/* Specify a valid profile name  */
  ole2.invoke(session,'Logon',args);
                 /* to avoid the logon dialog box */
  ole2.destroy_arglist(args);

 End;

procedure logoff is
 Begin

  ole2.invoke(session,'Logoff');
   /* Logoff the session and deallocate the */

 /* resources for all the OLE objects     */

  ole2.release_obj(session);
  ole2.release_obj(session_outbox);
  ole2.release_obj(session_outbox_messages);
  ole2.release_obj(message1);
  ole2.release_obj(msg_recp);
  ole2.release_obj(recipient);
  ole2.release_obj(msg_attch);
  ole2.release_obj(attachment);

 End;

procedure send( Recp       IN varchar2,
                Subject    IN varchar2,
                Text       IN varchar2,
                Attch      IN varchar2
               )is
Begin

/* Add a new object message1 to the outbox */

    session_outbox := ole2.get_obj_property(session,'outbox');
    session_outbox_messages :=
ole2.get_obj_property(session_outbox,'messages');
    message1 := ole2.invoke_obj(session_outbox_messages,'Add');

   ole2.set_property(message1,'subject',Subject);
   ole2.set_property(message1,'text',Text);

/* Add a recipient object to the message1.Recipients collection
*/

    msg_recp := ole2.get_obj_property(message1,'Recipients');
    recipient := ole2.invoke_obj(msg_recp,'add') ;

   ole2.set_property(recipient,'name',Recp);
   ole2.set_property(recipient,'type',1);
   ole2.invoke(recipient,'resolve');

/* Add an attachment object to the message1.Attachments
collection */

    msg_attch := ole2.get_obj_property(message1,'Attachments');
    attachment := ole2.invoke_obj(msg_attch,'add') ;

   ole2.set_property(attachment,'name',Attch);
   ole2.set_property(attachment,'position',0);
   ole2.set_property(attachment,'type',1);      /* 1 => MAPI
File Data */
   ole2.set_property(attachment,'source',Attch);

/* Read the attachment from the file */

    args := ole2.create_arglist;
    ole2.add_arg(args,Attch);
    ole2.invoke(attachment,'ReadFromFile',args);
    ole2.destroy_arglist(args);

  args := ole2.create_arglist;
  ole2.add_arg(args,1);   /* 1 => save copy */
  ole2.add_arg(args,0);   /* 0 => no dialog */

/* Send the message without any dialog box, saving a copy in the
Outbox */

   ole2.invoke(message1,'Send',args);

  ole2.destroy_arglist(args);
  message('Message successfully sent');

End;

END;

2. Create a block called MAPIOLE with the following canvas
layout:

|-------------------------------------------------------------|
|                                                             |
|  Exchange Profile: |====================|                   |
|                                                             |
|  To:               |============================|           |
|                                                             |
|  Subject:          |============================|           |
|                                                             |
|  Message:          |============================|           |
|                    |                            |           |
|                    |                            |           |
|                    |                            |           |
|                    |                            |           |
|                    |                            |           |
|                    |============================|           |
|                                                    |-----|  |
|  Attachment:       |============================|  |SEND |  |
|                                                    |-----|  |
|-------------------------------------------------------------|

The layout contains 5 text-itmes:

- Profile
- To
- Subject
- Message (multiline functional property set to true)
- Attach

and a 'Send' button with the following WHEN-BUTTON-PRESSED
trigger:

---------------------------
 mail_pkg.logon(:profile);
 mail_pkg.send(:to,:subject,:message,:attch);
 mail_pkg.logoff;

CONCLUSION:
-----------

This bulletin explained how to create a sample Forms application
which can send a fax/email using OLE automation. The same
concept/code can be extended to programmatically fax/email an
Oracle Report without any user interaction. In order to fax a
report, first generate the report output as a PDF file and then
send this to the fax recipient as an attachment using the same
Mail Package.

Review &
      Followup

      Rating:
      5
  March 29, 2001
Reviewer:
      A reader

      Rating: 5
The idea has
      helped us with the project  May 15, 2001
Reviewer:
      Sharath Shivanna  from New Bombay, Mumbai India
This article has really helped us take a leap in our project
Thanks a lot

      Rating:
      5
Doubt  June 14, 2001
Reviewer:
      Murali  from Bangalore, India
Hi tom,

i tried with the same but it is not working. when i try to create a sesison
session id is coming as 0.  after that it is failing to get the object
properties.  can you tell me what is the solutions for the same.

Thanks in advance
Murali

      Rating:
      4
Problem  June 20, 2001
Reviewer:
      Sayantan  from Mumbai,India
It is working perfectly with Outlook 97 but errors are raised when the same
thing is used with Outlook 2000.
Any soultion?

      Rating: 5
And finally it
      Works .... Yeh..!!!!!  July 05, 2001
Reviewer:
      Praveen Kumar Talla [Pune]  from India [Pune]
This is very facinating to send mail by using forms. And finally it works very
well. Thanx for the support. Thanks Exclusive team of Oracle online @ Ask Tom.

      Rating:
      1
Error  October 23, 2001
Reviewer:
      Marco  from Europe
Sorry, but this code gives me a

ORA - 305500

Can you help me !?

Please send me some solution

Thanks in advance

      Rating: 4
E-mail from D2K
      Forms  October 31, 2001
Reviewer:  Harish
      Betrabet  from Baroda, India
The code runs excellently with Forms 6i and Microsoft Outlook 97 (which is part
of the MS-Office 97 suite of applications). We configured Micosoft Outlook 97 to
connect to our Lotus Notes e-mail server using POP3.
However, the same code gave an error ORA - 305500
when we tried using Outlook Express 5 instead of Microsoft Outlook 97.

          Followup:

You gotta ask MS to stop changing their "standards" and their interfaces.  You
need to find out from MS what the proper, magic sequence of events is for
Outlook Express.

      Rating:
      1
  November 01, 2001
Reviewer:
      A reader
i think all applicaton development must tie the windows integration not window
tie the application as i like oracle but oracle developing must upgrade for this
point i meet with a lot of developer and they think that

          Followup:

Huh?  that makes no sense.  No clue what point you are trying to make there.

      Rating: 5
mapi
      session  November 16, 2001
Reviewer:  ceylan
      kortan
hi, I tried article-id = 61736.1. I wanted to send txt file by email. but
couldn't. my attachement file : c:\hello.txt and send it. when I entered inbox,
attachement file appeared "Untitled".

cutted source is below
-------------------------------------
msg_attch := OLE2.GET_OBJ_PROPERTY(message1, 'Attachments');
  attachment := OLE2.INVOKE_OBJ(msg_attch, 'add') ;
  OLE2.SET_PROPERTY(attachment, 'name', Attch);
  OLE2.SET_PROPERTY(attachment, 'position', 0);
  OLE2.SET_PROPERTY(attachment, 'type', 1);
  OLE2.SET_PROPERTY(attachment, 'source', 'C:\hello.txt');

what can I do?

many thanks.

      Rating: 5
mapi
      session  November 16, 2001
Reviewer:  ceylan
      kortan
hi, I tried article-id = 61736.1. I wanted to send txt file by email. but
couldn't. my attachement file : c:\hello.txt and send it. when I entered inbox,
attachement file appeared "Untitled".

cutted source is below
-------------------------------------
msg_attch := OLE2.GET_OBJ_PROPERTY(message1, 'Attachments');
  attachment := OLE2.INVOKE_OBJ(msg_attch, 'add') ;
  OLE2.SET_PROPERTY(attachment, 'name', Attch);
  OLE2.SET_PROPERTY(attachment, 'position', 0);
  OLE2.SET_PROPERTY(attachment, 'type', 1);
  OLE2.SET_PROPERTY(attachment, 'source', 'C:\hello.txt');

what can I do?

many thanks.

      Rating: 3
Solution to
      Outlook 2000 problem  December 14, 2001
Reviewer:
      Tamas zecsyu  from Budapest, Hungary
There is an article on metalink on the Outlook 2000 problem. Look for it under
Forms white papers (With a title 'Cracking Outlook' or something similar).

Tamas Szecsy

      Rating: 3
Senior Software
      Engineer  April 23, 2002
Reviewer:  Maneesh
      Kholay  from INDIA
If I am runing the form in GUI MODE it is working fine,
but when i run the same for on web server  it is gives Error 305500 non-oracle
exception..

As we have created a single profile on server side and setting it as default in
the form then also it is not working

How to solve this problem...
please help me out

Thanks

      Rating: 1
Trying with MS.
      Office XP  May 01, 2002
Reviewer:  Fancy
       from Tanzania
I was trying to send mail using Outlook Express 6 and it did not work. Maybe,
its because i didn't know what to put in the 'Profile' field. Please let me
know. Will it work with this version? Thanks.

          Followup:

As I say from time to time on this question:

You gotta ask MS to stop changing their "standards" and their interfaces.  You
need to find out from MS what the proper, magic sequence of events is for
Outlook Express.

If you want to do this (send email) reliably, without having to change it, tweak
it for each and every little service pack and upgrade -- suggest you do it in
the database.  Search for UTL_SMTP.   I haven't had to change my code -- in
forever.

      Rating: 5
Download Sample
      Form  June 16, 2002
Reviewer:  Sikandar Hayat
      Awan  from Pindi Bhattian, Pakistan
I had placed a sample form to send email from forms "with attachements" to
facilitate the oracle developers. You may download the sample from,

http://ourworld.compuserve.com/homepages/peter_koletzke/tip.htm#formstips1

      Rating: 1
Error
        July 17, 2002
Reviewer:  debjit  from
      india
Error Ora - 305500 is coming. How to solve the problem.

          Followup:

search through this for 305500 and you'll see my answer. You are hitting a
protocol error -- MS changed the interface - you'll need to figure it out from
that end (i don't even have a windows machine anymore to play with)

      Rating: 4
eMAIL
      CLIENT  July 18, 2002
Reviewer:  N

Hi Tom,
In most of the discussion we talk about D2k and mail client outlook/outlook
express (MS prduct),.
What about other client like Eudora and other email...
Will it work with them also or not?
Thanks
N

          Followup:

Don't know, are they OLE enabled?

Me -- I would just be using UTL_SMTP or JavaMail in the database and not even
bothering with trying to run an email client.  It is by far the "hard way" to do
it.

      Rating:
      4
question  January 31, 2003
Reviewer:
      amit karkhanis  from India
will this be machine specific

          Followup:

no, it'll be very specific to the version of MS software you have to talk to
however.  they like to change things -- as you should be able to see from some
of the other followups.

January 31, 2003 Om -- Thanks for the question regarding
      "Regarding Table extents !", version 8.1.7

      You Asked
Hi Tom,

How are you doing !

My Question is related with extents today.

We on regular basis get extents problem on daily basis.There are always some
objects in the database ( most of the times Tables and Indexes ) which
generally fail to allocate extent.

I have seen my senior DBA's who sometimes alter the next extents parameter to
solve the problem and sometimes add or increase the datafile size to have more
space in the respective tablespace.

My Question is

1) In what cases I should go and alter the extent for a particular object or
on what basis.How should I analyse that the problem is going to be resolved by
just increasing the next extents.

2) I gerenally add datafile or resize the datafile whenever we ger ORA -1654
Error.

Can you please expalin the above two steps ( if possible with SQL statements
to analyse the extents and resolve it)

3) In case I want to know in advance the Objects which are going to fail because
of the extents problem,Can you please tell me how to Query this information
also.

Please if you can kindly explain the difference if any when I deal with the
extents
problem with tables and Indexes.

4) Do I really need to worry about the extents problem in case it is existing
with the temporary tablespace ? If yes, Kindly explain how to resolve that also.

Many Thanks and Warm Regards,
Om

      and we said...
You need more senior DBA's then you have I'm afraid.  (sorry to be harsh on this
one but this is really basic -- for a senior dba anyway, for a new person -- not
but since you say they are "senior")

This is "DBA 101" stuff here, things they should know how to plan for and solve
in their sleep.

If you get the error "max extents", you need to alter the max extents for the
table.  If you have to do this on a given table over and over -- you need to
rethink your extent size for that table.  It might need to be larger.

In order to analyze if that problem would be solved by altering max extents --
you need only look at the error message.  A good DBA might be proactive and run
a query every now and then like:

select owner, segment_name, segment_type, extents, max_extents
  from dba_segments
 where max_extents-extents <= 3;

that'll tell you the objects that are getting near to failing.

If you get the "unable to extend" due to lack of space -- you need to increase
the size of your datafile or add more datafiles.  Its pretty clear actually --
this error message includes that advice.

Search my site for

free.sql

to find a script that'll show you how big your tablespaces are, how much free
space there is, how big it can autoextend to and so on.  You can run this to see
where you are running low BEFORE you run low.

With temporary tablespaces, if you are hitting this -- you DBA's haven't
allocated sufficient space for your applications.  Either they need to help you
fix your queries to use less temp or they need to allocate an appropriate amount
of temp.

Review &
      Followup

      Rating:
      4
  October 13, 2001
Reviewer:  mike
       from atlanta
I tried it on my laptop:

SQL> select owner, segment_name, segment_type, extents, max_extents
  2        from dba_segments
  3       where max_extents-extents <= 3;

OWNER SEGMENT_NAME    SEGMENT_TYPE         EXTENTS MAX_EXTENTS
----- --------------- ------------------ --------- -----------
SYS   1.537           CACHE                      1           0

what can we do about it?
it is 8.1.5

          Followup:

That one you need not worry about.

When the database is created, a segment of type 'CACHE' is created. It is
owned by 'SYS' and resides in the system tablespace. It can been found
by querying DBA_SEGMENTS or USER_SEGMENTS for a segment_type of 'CACHE'
and has one extent allocated but MAX_EXTENTS = 0.

The name of the segment will be 1.<number> which is derived from the values
of HEADER_FILE.HEADER_BLOCK in DBA_SEGMENTS.  This file and block marks
the beginning of non-bootstrap data dictionary objects.  All objects which
are created prior to bootstrap$ in SQL.BSQ are required for warmstarting
the instance. These objects can only be created from CREATE DATABASE.
The CACHE object SYS.1.<header_block_number> marks the starting block
where non-bootstrap objects will reside.

      Rating:
      4
Reader  October 13, 2001
Reviewer:
      Reader  from USA
Tom,
"
ops$tkyte@8i> create table t ( x int, y char(2000) default '*' )
  2  storage ( initial 40k next 40k minextents 5 )
  3  tablespace system;

 EXTENT_ID      BYTES     BLOCKS
---------- ---------- ----------
         2      40960          5
         3      81920         10
         4      57344          7
         0      40960          5
         1      40960          5
                      ----------
sum                           32
"

Why the 4th extent took 80K and 5th extent took 7K instead
of 5K

Thanks

          Followup:

well, without knowing the default pctincrease, its hard but...

The extents took:

40k 40k 40k 80k 56k

(not 80k and then 7k -- which would be impossible as you have a 8k blocksize
apparently)

When we look for space, we do not like to leave chunks of 5 or less blocks
behind (too small for anything).  Hence, we allocate them all for you.

see

http://technet.oracle.com/doc/oracle8i_816/server.816/a76965/c02block.htm#2846
to read how extents are allocated in a dictionary managed tablespace such as
system.

      Rating:
      4
Reader  October 13, 2001
Reviewer:
      Reader  from USA
My previous posting, rephrasing my question ;)

why the 4th extent took 10 blocks and 5th 7 blocks instead
of 5 blocks (40K / 8K db_block_size)

Thanks

      Rating:
      4
Reader  October 13, 2001
Reviewer:
      Reader  from USA
Tom,

I was referring to your web-post

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:231414051079
where I found extents of sizes 40k, 40k, 40k, 80k, 56k
allocated when you created your sample table.

Thanks

          Followup:

Well, that makes it a little more clear then.

My pctincrease was probably 0% (it was a while ago, that database doesn't exist
anymore)

my extents should have been 40k apiece.

my free space wasn't full of 40k chunks.  We don't like to leave little bits and
pieces about so when we hit a free extent with a couple more blocks then we need
-- we use it.

Hence, we had some nice 40k chunks we used -- then we allocated the 80k chunk
and then moved onto the 56k chunk -- rather then leaving bits of free space
around.

See the referenced link (above) to see the search algorithm we go through.

If and only if you use locally managed tablespaces will you get predicable
extent allocations.  Otherwise they are rounded up to fit better.

      Rating: 4
Max
      extents  June 26, 2002
Reviewer:
      A reader
We are monitoring extents utilization, to avoid obtaining ORA-1631 max extents
reached error.

Can we alter all the tables and indexes to have max extents UNLIMITED? Is there
any negative effect of doing this?

Thanks

          Followup:

negative is in the eye of the beholder.

There are arguments both ways.  I would not want objects in 10's of thousands of
extents, but hundreds or thousands -- that is OK.

If you really truly honestly don't have a clue about the ultimate size of your
objects, you should use a locally managed tablespace with system managed extent
sizes.  They will have unlimited extents but the extents will grow larger as the
number goes up.  Works well when you quite simply "have no idea".  In 9i-- its
the default tablespace mgmt policy!

      Rating:
      3
  October 17, 2002
Reviewer:  Sagi
       from India
Hi Tom,

You said that

"extents will grow larger as the number goes up" if we use LMT.

It would of great help if you could give us an example. It not that we dont
believe. It would make use understand more.

Regards,
Sagi

Regards,
Sagi

          Followup:

ops$tkyte@ORA920.US.ORACLE.COM> create table t as select * from all_objects;

Table created.

ops$tkyte@ORA920.US.ORACLE.COM> begin
  2      for i in 1 .. 5
  3      loop
  4          insert /*+ append */ into t select * from t;
  5          commit;
  6      end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> exec print_table( 'select * from dba_tablespaces
where tablespace_name = ''USERS'' ' )
TABLESPACE_NAME               : USERS
BLOCK_SIZE                    : 8192
INITIAL_EXTENT                : 65536
NEXT_EXTENT                   :
MIN_EXTENTS                   : 1
MAX_EXTENTS                   : 2147483645
PCT_INCREASE                  :
MIN_EXTLEN                    : 65536
STATUS                        : ONLINE
CONTENTS                      : PERMANENT
LOGGING                       : LOGGING
FORCE_LOGGING                 : NO
EXTENT_MANAGEMENT             : LOCAL
ALLOCATION_TYPE               : SYSTEM
PLUGGED_IN                    : NO
SEGMENT_SPACE_MANAGEMENT      : AUTO
-----------------

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select extent_id, blocks
  2    from user_extents
  3   where segment_name = 'T'
  4  /

 EXTENT_ID     BLOCKS
---------- ----------
         0          8
         1          8
         2          8
         3          8
         4          8
         5          8
         6          8
         7          8
         8          8
         9          8
        10          8
        11          8
        12          8
        13          8
        14          8
        15          8
        16        128
        17        128
        18        128
        19        128
        20        128
        21        128
        22        128
        23        128
        24        128
        25        128
        26        128
        27        128
        28        128
        29        128
        30        128
        31        128
        32        128
        33        128
        34        128
        35        128
        36        128
        37        128
        38        128
        39        128
        40        128
        41        128
        42        128
        43        128
        44        128
        45        128
        46        128
        47        128
        48        128
        49        128
        50        128
        51        128
        52        128
        53        128
        54        128
        55        128
        56        128
        57        128
        58        128
        59        128
        60        128
        61        128
        62        128
        63        128
        64        128
        65        128
        66        128
        67        128
        68        128
        69        128
        70        128
        71        128
        72        128
        73        128
        74        128
        75        128
        76        128
        77        128
        78        128
        79       1024
        80       1024
        81       1024
        82       1024
        83       1024

84 rows selected.

there you go, see how the extent sizes just increased all by themselves in
this SYSTEM allocation type tablespace...

      Rating: 5
Extents for an
      Audit LOG table  October 17, 2002
Reviewer:
      sm  from usa
Tom,

We are using Dictionary Managed Tablespaces :( .. moving hopefully to LMT in a
month or so.. :)
database size is about 2.5 gig.
We have a table that we use for auditing the DMLs for lot of tables...
Of course..this table is growing "really" fast..
and it gave ora-01631 today...
max extent on the tablespace as well as the table were set to 505 (8k
db_block_size)..
We can increase that..to fix the issue...or set it to unlimited..
but the real reason this happened was because of the small next extent..
The table has 200000 rows now..
It just gets inserts...no updates or deletes

Question
1. Is there a method (a mathematical equation of some sort) to estimate the size
of the initial and next extent
2. Will this table/tablespace be a good candidate for unlimited maxextents...
3.UNLIMITED sort of scares me...
What are some other guidelines for managing these large..always growing
tables..besides watching them with free.sql...
4.Can you recommend a good book....that has lot of real world cases...please...

Thanks a zillion...

          Followup:

1) you want initial = next and pctincrease = 0.  Thats easy.

You want the object to maybe extend once or twice a week at most perhaps.

So, figure out how much data goes into it in a week.  Make the extent size that
big.

2) sure, why not.  you are constrained by disk space at the end of the day.

3) LMT's only do unlimited!!!  you have *no choice* going forward

4) Jonathan Lewis's Practical Oracle8i Building Efficient databases is a
favorite of mine....

      Rating: 4
number of
      extents for index  January 29, 2003
Reviewer:
      June  from MD, US
Hi Tom,

I have one snapshot with two indexes on it, see following detail info:

select index_name, column_name,column_length,column_position
from all_ind_columns where table_name='SNPSH_TRD_CNCLN_ASOF_TRD_DTL'
 and table_owner='IFDF'
SQL> /

INDEX_NAME     COLUMN_NAME     COLUMN_LENGTH COLUMN_POSITION
-------------------- --------------- ------------- --------
SYS_C003576        FDTP                  7                1
SYS_C003576   SBMTG_FIRM_ID              22               2
SYS_C003576    CLRG_FIRM_ID              22               3
SYS_C003576    CLRG_MPID_TX               5               4
SYS_C003576    CRSPT_FIRM_ID             22               5
SYS_C003576    CRSPT_MPID_TX              5               6
SYS_C003576          SEQ_NB              22               7
PF1_xxx        FIRM_PRCSD_DT              7               1

For these two indexes, the extent information is as following:
SEGMENT_NAME  EXTENTS INITIAL_EXTENT NEXT_EXTENT MAX_EXTENTS
------------------ ---------- -------------- ----------- ---
SYS_C003576     100        1048576     1048576         500
PF1_xxx         747        2097152     2097152         999

Notice that index of PF1_xxx is an index on one column however have more 600
more extents than SYS_C003576, while the later one indexes on more columns and
bigger sizes. We manually increase max_extents for PF1_xxx when the ratio
extents/max_extents exceeds 80% not long ago, and it seems extents number for
PF1_xxx is still growing. The table size is about 1.1million records, with about
180 distinct FIRM_PRCSD_DT values. the growth is about 20k records/day.

They are in same snapshot table (completed refreshed daily). How does it happen?
Will drop and rebuild PF1_xxx with bigger extent and smaller help reduce the
extent growth?

I thought the index size is based on total column size; however this is conflict
case. What should I do?

any suggestion is appreciated.

          Followup:

what kind of index is pf1_xxx

      Rating: 3
number of
      extents for index   January 29, 2003
Reviewer:
      June  from MD, USA
followup:
it is regular one as:

create index PF1_xxx on snpsh_trd_cncln_asof_trd_dtl
(firm_prcsd_dt)
tablespace  snpsh_index_tablespace
storage(initial 2m next 2m pctincrease 0)
nologging
;

          Followup:

the only thing I can think then is you load data like this:

01-jan-2003   <<<=== this is the 1st entry on leaf block 1
02-jan-2003
03-jan-2003
....
31-jan-2003   <<<==== this is the last entry on leaf block 1
01-feb-2003   <<<==== this is the first entry on leaf block 2
....
              <<<==== and so on

then, you come back later and delete 02-jan-2003, ... 29-jan-2003.  ALMOST
emptying block 1, but not quite.  Now, that space is available to be reused but
ONLY by values around january (data goes in "sorted").  But you don't insert
anymore jan-2003 data so that space effectively goes unused (it is available for
reuse, you just are not inserting data that will reuse it).

As this goes on over time, most of the blocks in the index are near empty but
have some data.  So, they cannot be moved within the index structure and reused
just anywhere but due to the nature of your data -- you never will actually
reuse the space.

This index is a candidate for coalescing -- to undo block splits if you will --
to merge jan/feb/mar together after a while.  Check out the coalesce option of
the alter index command.

This is the one case where index maintenance is deserved if I've guessed right.
You might want to do a one time rebuild (to compact and free up the space) and
then coalesce from time to time -- to get some empty blocks onto the freelists
for reuse after "compacting"

      Rating: 5
Algorithm for
      "SYSTEM allocation type tablespace"  January 30, 2003

Reviewer:  SB  from CA
Hi Tom,

We monitor a lot of customer databases thru standard database scripts. One such
script is to find the availability of space for next extent of a segment. For
DMTs we just query the size of next_extent, and initial_extent size for LMTs.
Now, my problem is determining the size of next extent for "SYSTEM allocation
type" LMTs. From your example above, as you can see, the next extent size
increased from 8 to 128 and then to 1024 blocks for extent ids 16 and 79
respectively. Can you please explain me how these next extent sizes are
determined so that I can incorporate this logic in my script.

Thankx Tom

          Followup:

Nope -- it is undocumented and I can show you examples where it doesn't follow
that same progression.

You'll have to make assumptions, seems to go

64k
1m
8m
64m

Here is from a 10gig table:

ops$tkyte@ORA920>

  1  select sum(mbytes*extents) over ()/1024 gig,
  2         sum(mbytes*extents) over (order by start_ext)/1024 cumgig,
  3         sum(extents) over () extents,
  4        a.*
  5    from (
  6  select bytes/1024/1024 mbytes,
  7         min(extent_id) start_ext,
  8        max(extent_id) end_ext,
  9        max(extent_id)-min(extent_id)+1 extents
 10    from user_extents
 11   where segment_name = 'T'
 12   group by bytes/1024/1024
 13*        ) a
ops$tkyte@ORA920> /

       GIG     CUMGIG    EXTENTS     MBYTES  START_EXT    END_EXT    EXTENTS
---------- ---------- ---------- ---------- ---------- ---------- ----------
    9.9375 .000976563        342      .0625          0         15         16
    9.9375      .0625        342          1         16         78         63
    9.9375          1        342          8         79        198        120
    9.9375     9.9375        342         64        199        341        143

so, the first 200extents are 8meg or less, you would be safe for those assuming
that next is 8m (if they have less then 8m free, it is as good as full in my
opinion).  If an object gets above 200 extents (larger then about 1gig in size),
then you should start assuming at least 64m must be free.

      Rating: 5
Thankx,
      Tom  January 31, 2003
Reviewer:  SB
       from sburri@sitelite.com

January 31, 2003 -- Thanks for the question regarding "SQL query
      for history / as-of a certain date", version 8.1.7

      You Asked
I have a simple history table that captures all the :new values in a row-level
after insert/update/delete (:old, of course) trigger.

I want to answer the question: show me the value for column A as of 1/1/2001

I would need to do something like

select columna from hist_table
where timestamp=(select max(timestamp) from hist_table where timestamp <
'1/1/2001')

This seems very awkward.

Using the new analytic functions in 8i, is there a more elegant/efficent way to
get SQL to do this?

Thanks a lot.

      and we said...
yes, analytics could do this but -- this would be the most efficient:

select *
  from ( select *
           from history
          where timestamp <= to_date('01/01/2001','dd/mm/yyyy')
          order by timestamp DESC )
 where rownum = 1
/

assumming index on timestamp that is.
Review &
      Followup

      Rating:
      4
Why?  January 29, 2003
Reviewer:  A
      Reader  from UK
Tom,

I always wonder "how do you know this", know this to be more efficient in this
case, when I see your answers - is it from running both and looking at the
stats, or just from experience ?

Regards,

Paul

          Followup:

In this case -- i knew.  most of the time I benchmark and supply that.

one thing I neglected, you would consider using first rows optimization:

ops$tkyte@ORA817DEV> create table t as select * from all_objects;

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create index t_idx on t(created);

Index created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> analyze table t compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;

Table analyzed.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> set autotrace traceonly
ops$tkyte@ORA817DEV> select *
  2    from t
  3   where created = ( select max(created)
  4                       from t
  5                       where created < to_date( '01/01/2003', 'dd/mm/yyyy' )
);

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=38 Bytes=3686)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=38 Bytes=3686)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=38)
   3    2       SORT (AGGREGATE)
   4    3         FIRST ROW (Cost=2 Card=23751 Bytes=166257)
   5    4           INDEX (RANGE SCAN (MIN/MAX)) OF 'T_IDX' (NON-UNIQUE) (Cost=2
Card=23751)

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

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select *
  2    from (select /*+ FIRST_ROWS */ * from t order by created DESC )
  3   where rownum = 1
  4  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=1544 Card=24103
Bytes=3085184)
   1    0   COUNT (STOPKEY)
   2    1     VIEW (Cost=1544 Card=24103 Bytes=3085184)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=1544 Card=24103
Bytes=2337991)
   4    3         INDEX (FULL SCAN DESCENDING) OF 'T_IDX' (NON-UNIQUE) (Cost=65
Card=24103)

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

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> set autotrace off

like that.

      Rating: 5
Where's the
      rating "6 - I OWE TOM" ?!?!?!  January 29, 2003

Reviewer:  A Reader  from UK
Tom,

I do this many times in my app, in, dare I say it, loops - I tried it with a
sample 100,000 iterations and your way takes half the LIO which for me equates
to about half the time!!!!

Thanks again - its so good to pick up these things each day and add them to your
toolkit !

Cheers

Paul

      Rating: 4
Seldom
      error  January 30, 2003
Reviewer:  Michael
       from Germany
I tried this rownum solution, but i get a seldom error:

select e.ename,
       (select max(e1.empno)
        from (select e2.empno
              from emp e2
              where e2.ename > e.ename
              order by e2.ename asc) e1
        where rownum = 1) next_empno
from emp e
where e.empno = 7839;

I expected to get ENAME = 'KING' and NEXT_EMPNO = 7654 - the empno of 'MARTIN',
but i get:

SQL> select e.ename,
  2         (select max(e1.empno)
  3          from (select e2.empno
  4                from emp e2
  5                where e2.ename > e.ename
  6                order by e2.ename asc) e1
  7          where rownum = 1) next_empno
  8  from emp e
  9  where e.empno = 7839;
              where e2.ename > e.ename
                               *
ERROR in Line 5:
ORA-00904: invalid identifier

It seems that oracle doesn't recognize the table e in the subquery. Why?

          Followup:

because the correlation names only go one level down.

you should query:

select ename, lead(empno) over (order by ename)
  from emp;

easier no?

      Rating: 4
Easier, but
      ...  January 30, 2003
Reviewer:  Michael
       from Germany
Yes, it would be easier, but when i query:

select ename, lead(empno) over (order by ename)
from emp
where empno = 7839;

I get:

ENAME      LEAD(EMPNO)OVER(ORDERBYENAME)
---------- -----------------------------
KING

The where-clause "killed" the sense of the lead function.
(I tried using the lead function in an inline view, but - with a big emp table
this is quite slow...)

          Followup:

you can use min/max to do this in a single non-nested query

scott@ORA920> select ename
  2    from (select ename from emp order by hiredate)
  3   where rownum = 1
  4  /

ENAME
----------
SMITH

scott@ORA920>
scott@ORA920> select substr( min( to_char(hiredate,'yyyymmddhh24miss') || ename)

, 15 )
  2    from emp
  3  /

SUBSTR(MIN
----------
SMITH

scott@ORA920>
scott@ORA920>
scott@ORA920> select ename
  2    from (select ename from emp order by SAL desc)
  3   where rownum = 1
  4  /

ENAME
----------
KING

scott@ORA920>
scott@ORA920> select substr( max( to_char(sal,'fm000000000.00') || ename) , 13 )
  2    from emp
  3  /

SUBSTR(MAX(
-----------
KING

scott@ORA920>
scott@ORA920>
scott@ORA920> select empno
  2    from (select empno from emp order by ENAME desc)
  3   where rownum = 1
  4  /

     EMPNO
----------
      7521

scott@ORA920>
scott@ORA920> select substr( max( rpad(ename,30) || empno) , 31 )
  2    from emp
  3  /

SUBSTR(MAX(RPAD(ENAME,30)||EMPNO),31)
----------------------------------------
7521

      Rating: 4
What i want to
      do ...  January 31, 2003
Reviewer:  Michael
       from Germany
Thanks for your answer.

I thought of this rownum and min/max approach, too ...

Perhaps it's best to explain what i want to do (even when this will open a
completly new question ;-).
I want to get some data about a certain emp and the rowids of the first,
previos, next and last emp (first and next etc. in the sense of order by ename).

I came up with the following query:

select e.empno, e.ename, e.mgr, e.sal, e.comm,
       e.deptno, d.rowid,
       (select min(e1.rowid) from emp e1
        where e1.ename in
              (select min(e2.ename) from emp e2)) first,
       (select max(e1.rowid) from emp e1
        where e1.ename in
              (select max(e2.ename) from emp e2
               where e2.ename < e.ename)) prev,
       (select min(e1.rowid) from emp e1
        where e1.ename in
              (select min(e2.ename) from emp e2
               where e2.ename > e.ename)) next,
       (select max(e1.rowid) from emp e1
        where e1.ename in
              (select max(e2.ename) from emp e2)) last
from emp e, dept d
where e.rowid = 'SOME_ROWID_OF AN EMP'
and e.deptno = d.deptno (+);

This query works - there is an index on ename. But it's speed could (better:
should) be better. Any idea or is this a (nearly) optimal solution?

(Seldom - i can reference e in the prev and next sub-subquery ...)

          Followup:

say you have:

ops$tkyte@ORA920> /*
DOC>drop table emp;
DOC>drop table dept;
DOC>
DOC>create table emp as
DOC>select owner || '.' || object_name || '.' || object_type ename, a.*
DOC>  from all_objects a;
DOC>
DOC>create index ename_idx on emp(ename);
DOC>
DOC>create table dept as
DOC>select username deptno, a.*
DOC>  from all_users a;
DOC>
DOC>alter table dept add constraint dept_pk primary key(deptno);
DOC>
DOC>analyze table emp compute statistics
DOC>for table for all indexes for all indexed columns;
DOC>analyze table dept compute statistics
DOC>for table for all indexes for all indexed columns;
DOC>*/
ops$tkyte@ORA920>
ops$tkyte@ORA920> variable rid varchar2(255)
ops$tkyte@ORA920> begin
  2          select rowid into :rid
  3            from emp
  4           where object_id = ( select percentile_cont(0.5) within group
(order by object_id)
  5                             from emp );
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> print rid

RID
---------------------------------------------------------------------------------
--------------------------------------------------
AAAJVcAAJAAAVeQAAg

ops$tkyte@ORA920>

then:

ops$tkyte@ORA920> set autotrace on
ops$tkyte@ORA920> select object_id empno, ename, data_object_id mgr, dept.deptno
dname
  2    from (
  3  select *
  4    from ( select /*+ first_rows */ *
  5             from emp
  6            where ename < (select ename from emp where rowid = :rid)
  7            order by ename DESC
  8              )
  9   where rownum = 1
 10   union all
 11  select *
 12    from emp
 13   where rowid = :rid
 14   union all
 15  select *
 16    from ( select /*+ first_rows */ *
 17             from emp
 18            where ename > (select ename from emp where rowid = :rid)
 19            order by ename
 20              )
 21   where rownum = 1
 22         ) e, dept
 23   where e.owner = dept.deptno(+)
 24  /

     EMPNO ENAME                                 MGR DNAME
---------- ------------------------------ ----------
------------------------------
      8051 SYS./b7cb866f_Imports.JAVA CLA            SYS
           SS

     14983 SYS./b7d18c9f_ObjectTypeChange            SYS
           dExc.JAVA CLASS

      7469 SYS./b7fa67b8_CustomizerHarnes            SYS
           sMan.JAVA CLASS

3 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=19 Card=3 Bytes=273)
   1    0   NESTED LOOPS (OUTER) (Cost=19 Card=3 Bytes=273)
   2    1     VIEW (Cost=19 Card=3 Bytes=255)
   3    2       UNION-ALL
   4    3         COUNT (STOPKEY)
   5    4           VIEW (Cost=9 Card=1481 Bytes=125885)
   6    5             TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=9 Card=1481
Bytes=198454)
   7    6               INDEX (RANGE SCAN DESCENDING) OF 'ENAME_IDX'
(NON-UNIQUE) (Cost=3 Card=267)
   8    7                 TABLE ACCESS (BY USER ROWID) OF 'EMP' (Cost=1 Card=1
Bytes=44)
   9    3         TABLE ACCESS (BY USER ROWID) OF 'EMP' (Cost=1 Card=1 Bytes=87)
  10    3         COUNT (STOPKEY)
  11   10           VIEW (Cost=9 Card=1481 Bytes=125885)
  12   11             TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=9 Card=1481
Bytes=198454)
  13   12               INDEX (RANGE SCAN) OF 'ENAME_IDX' (NON-UNIQUE) (Cost=3
Card=267)
  14   13                 TABLE ACCESS (BY USER ROWID) OF 'EMP' (Cost=1 Card=1
Bytes=44)
  15    1     INDEX (UNIQUE SCAN) OF 'DEPT_PK' (UNIQUE)

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

ops$tkyte@ORA920>
ops$tkyte@ORA920> select e.object_id empno, e.ename, e.data_object_id mgr,
d.deptno dname,
  2         (select min(e1.rowid) from emp e1
  3          where e1.ename in
  4                (select min(e2.ename) from emp e2)) first,
  5         (select max(e1.rowid) from emp e1
  6          where e1.ename in
  7                (select max(e2.ename) from emp e2
  8                 where e2.ename < e.ename)) prev,
  9         (select min(e1.rowid) from emp e1
 10          where e1.ename in
 11                (select min(e2.ename) from emp e2
 12                 where e2.ename > e.ename)) next,
 13         (select max(e1.rowid) from emp e1
 14          where e1.ename in
 15                (select max(e2.ename) from emp e2)) last
 16  from emp e, dept d
 17  where e.rowid = :rid
 18  and e.owner = d.deptno (+);

     EMPNO ENAME                                 MGR DNAME
   FIRST              PREV
---------- ------------------------------ ----------
------------------------------ ------------------ ------------------
NEXT               LAST
------------------ ------------------
     14983 SYS./b7d18c9f_ObjectTypeChange            SYS
   AAAJVcAAJAAAGFMAAU AAAJVcAAJAAAVeQAAf
           dExc.JAVA CLASS
AAAJVcAAJAAAVeQAAh AAAJVcAAJAAAGEmAAj

1 row selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=93)
   1    0   NESTED LOOPS (OUTER) (Cost=1 Card=1 Bytes=93)
   2    1     TABLE ACCESS (BY USER ROWID) OF 'EMP' (Cost=1 Card=1 Bytes=87)
   3    1     INDEX (UNIQUE SCAN) OF 'DEPT_PK' (UNIQUE)

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

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace off

but if you can get away from ROWID and just get back to basics and use ENAME

ops$tkyte@ORA920> variable ename varchar2(255)
ops$tkyte@ORA920> begin
  2          select ename into :ename
  3            from emp
  4           where rowid = :rid;
  5  end;
  6  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> print ename

ENAME
------------------------------
SYS./b7d18c9f_ObjectTypeChange
dExc.JAVA CLASS

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace on
ops$tkyte@ORA920> select object_id empno, ename, data_object_id mgr, dept.deptno
dname
  2    from (
  3  select *
  4    from ( select /*+ first_rows */ *
  5             from emp
  6            where ename < :ename
  7            order by ename DESC
  8              )
  9   where rownum = 1
 10   union all
 11  select *
 12    from emp
 13   where rowid = :rid
 14   union all
 15  select *
 16    from ( select /*+ first_rows */ *
 17             from emp
 18            where ename > :ename
 19            order by ename
 20              )
 21   where rownum = 1
 22         ) e, dept
 23   where e.owner = dept.deptno(+)
 24  /

     EMPNO ENAME                                 MGR DNAME
---------- ------------------------------ ----------
------------------------------
      8051 SYS./b7cb866f_Imports.JAVA CLA            SYS
           SS

     14983 SYS./b7d18c9f_ObjectTypeChange            SYS
           dExc.JAVA CLASS

      7469 SYS./b7fa67b8_CustomizerHarnes            SYS
           sMan.JAVA CLASS

3 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=19 Card=3 Bytes=273)
   1    0   NESTED LOOPS (OUTER) (Cost=19 Card=3 Bytes=273)
   2    1     VIEW (Cost=19 Card=3 Bytes=255)
   3    2       UNION-ALL
   4    3         COUNT (STOPKEY)
   5    4           VIEW (Cost=9 Card=1481 Bytes=125885)
   6    5             TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=9 Card=1481
Bytes=198454)
   7    6               INDEX (RANGE SCAN DESCENDING) OF 'ENAME_IDX'
(NON-UNIQUE) (Cost=3 Card=267)
   8    3         TABLE ACCESS (BY USER ROWID) OF 'EMP' (Cost=1 Card=1 Bytes=87)
   9    3         COUNT (STOPKEY)
  10    9           VIEW (Cost=9 Card=1481 Bytes=125885)
  11   10             TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=9 Card=1481
Bytes=198454)
  12   11               INDEX (RANGE SCAN) OF 'ENAME_IDX' (NON-UNIQUE) (Cost=3
Card=267)
  13    1     INDEX (UNIQUE SCAN) OF 'DEPT_PK' (UNIQUE)

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

ops$tkyte@ORA920> set autotrace off

we can do even better.  some food for thought

January 31, 2003 Venkateswara Reddy -- Thanks for the question
      regarding "How many days to keep Archived Log Files", version 8.1.5

      You Asked
Hi Tom

After a long time I posting my doubt. I have installed Oracle 8.1.5 around
1.5years back with Archived Log Mode. But so far I never used any archived log
files for recovery purpose(that situation din't arise ). Now the current files
gone around 3.5GB only *.ARC Files. current number is 3729. So my doubt is how
many days I should preserve this files.

Thanks in advance

Reddy

      and we said...
You need to keep the archives generated during and since your last hot backup.

It is not "some number of days"

It is dependent on your backup schedule.  Before you do a backup, alter system
switch logfile.  remember the current log at that point.  You need that log plus
all generated logs from that point in time to recover that backup.  As long as
you have that backup, you need those logs.  When you get rid of that backup, you
can get rid of the archives that are needed for it (but not for the next one!)

You need to read the backup and recovery guide:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76993/toc.htm

Review &
      Followup

      Rating:
      2
Comment  August 16, 2002
Reviewer:
      Salim  from Pakistan
Not to be arrogant, I think if someone is using oracle for 1.5 years, probably
this not the place for oracle 101 questions. This denies other folks waiting in
line to ask their questions.

          Followup:

Salim --

I've read some of the other comments that follow this and here is my 2cents.

For ever question you actually see published here, there are 2 more you do not.
I could easily have answered this and not shown anyone.  I made a concious
decision to publish it -- why?  Because this question comes up with
frightening frequency.

Backup and recovery is a very important topic.  I took the the opportunity to
use this question as a "wake up" for others that might be thinking along the
same lines (that archives just "expire" after some number of days or something).

It happens alot, it is a very vitial, important topic -- and one that lots of
people think they understand but they don't.  If they think they
understand, they have no motivation to actually learn it.  It is a catch 22.
The day they learn they got it wrong is most likely the day they lose their
database as well (and guess what, they get really mad at us/me).

I am of the belief that there aren't any dumb questions, just questions.  What
is obvious to me is totally foreign to others (and vice versa, I've been at
round tables talking about bio-warfare topics.  Man was I just totally out of
it, had really "stupid" things to say).

Just one "subtle" point.  If you start a sentence with "Not to be arrogant"....
The rest of it might sound that way.

The only questions I really don't like are ones with bogus email addresses (had
3 more of those today again).  Those are denying others.

      Rating: 4
I don't agree
      with Saleem's coment  August 16, 2002
Reviewer:
      A reader  from USA
I don't see what Saleem's problem is , when person like Tom whose time is so
premium and  got his hands busy all the
took his time and answered this question .
     Yes this is not Oracle 101 site . This is Oracle 100001
Site . I browse and learn every day from this site . Before posting any question
I search this site as much as I can
and post the question only when I couldn't find the answer .
Most likely everyone does that . The question may seems to be easy for you but
not for the one who asked it. So please lean some professionalism from on very
same site from none other than the man behind it(Tom) , and please don't be so
rude to others .
Thanks

      Rating: 2
Please spell my
      name correctly  August 16, 2002
Reviewer:
      SALIM  from Pakistan
I dont not agree with your analysis, my intent was not to be rude but to be
polite to others in queue.

I appreciate that you search the site before posting your question/s and I hope
more people start doing this. However, majority of the people do not follow this
route. I think some basic common sense and knowledge of oracle beyond oracle 101
will help fellow forumers...:)

      Rating: 4
Archive
      Logs  August 18, 2002
Reviewer:  Reddy
       from Bangalore, India
Thank you Mr. Tom

I am very happy to inform, I got it clear how to maintain archive log from you.

Thank you very much

Reddy

      Rating: 2
on Saleem's
      Comment  August 18, 2002
Reviewer:  Reddy
       from Bangalore, India
Mr. Saleem

If Some one using Oracle since 1.5 years, they will come to know about
everything in Oracle ( its a  Ocean man). Why you are feeling bad about this
question. I am just borning kid in oracle ocean.

      Rating: 3
Minimum How
      many archivelog files required   January 31, 2003

Reviewer:  Reddy  from Reddy,Bangalore, India

HI tom

Thanks for your my previous questions.

Here  I have one questions regarding archive log files.

If I am not taking backup in archive log mode from past 5 years. Now I want to
take hot backup then I will send the source machine permanently. The backup
machine cannot hold the 5 years archieve log file.  So for my complete database
protection minimum how many files required.

Thanks

Reddy

          Followup:

you need to hire a DBA asap.

they would be able to explain to you that 5 years between backups is about 4.99
years too long.

I don't really understand your question i guess, or you have no real backups.

      Rating: 5
Lets not be so
      hard on people  January 31, 2003
Reviewer:
      Andrew Gilfrin  from Dover, Kent, UK
So what people are saying is that if its a question which is so simple everybody
should know, then dont tell them. Well how did you find out when you should
delete archive log files. Many companies do not provide training and people just
have to get by with the little knowledge they can bring together. I remember
before I was given the chance to take an Oracle DBA course I had a serious lack
of knowledge, going on the course filled so many gaps for me.

However I do think its a little scary that they havent taken a backup for 5
years. :)

I'm sure there are many things people do not know which others feel they should,
lets all be a little more patient and allow Tom to answer and publish the
questions people want to ask and he feels are helpfull to others however scary
that may be :) .

January 31, 2003 Sanjay -- Thanks for the question regarding
      "maximum limit, number of arguments in decode function", version 8.1.7

      You Asked
Hi Tom,

I have a statement as below
                inset into table table_name
                (select col1,
                        col2,
                 decode(col3,1,'...',
                             2,'...',
                             ...,
                             ...,
                           256,'...','default'),
                        col4,
                        ....
                        col25
                 from another_table)

When I try to compile package, that has above statement, it gives me ORA-939
error. Then I tried to find what is the maximum number of arguments I can pass
in this decode, I could compile successfully upto 125.

My questions are : (if these are too many, please answer first two)

(1) Is this error generated due to max limit of argument to a general function
or decode ?
(2) Is there any workaround to this problem, so that I can still have one sql
statement to include all 256 combinations.

(3) What is the maximum number of arguments we can pass to a function ?
(4) What is the maximum number of arguments we can pass to decode function ?

Thanks

Sanjay

      and we said...
1) decode limit:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a85397/expressi.htm#1002845
255 components in a decode include the initial expression, the "value" and
"return" pairs and default.

So, you can have about 126 a,b pairs in:

  decode( X, a,b, a,b, a,b, ...., DEFAULT );

I think you would want to use a table for this:

create table lookup (id int primary key, val ... ) organization index;
insert into lookup values ( 1, '... );
...
insert into lookup values ( 300, '....' );

then you can use

  insert into t
  select col1, col2, (select val from lookup where id=col3), .....

instead.

2) see #1

3) generally undefined (no limit) -- unless the function says "this is my max",
which decode does.

4) see #1

Review &
      Followup

      Rating: 4
It was exactly
      what I was looking for. Thanks  January 30, 2003

Reviewer:  Sanjay  from Colorado
Next time, I will make sure to read technical reference document first. Thanks

      Rating: 4
Warning: SELECT
      a SELECT in PL/SQL  January 31, 2003
Reviewer:
      Adrian  from UK
I notice the version is 817 and the poster mentions trying "to compile a
package". This means the "SELECT (SELECT)" won't compile statically (until 9i),
so you'll need either dynamic SQL or a view with the SELECT (SELECT) SQL in it.

Regards

          Followup:

yup.

January 31, 2003 Karl -- Thanks for the question regarding "User
      Password", version 8.1.5

      You Asked
How do i create an account with no password or a blank password?

      and we said...
Well, a blank password can be achieved:

ops$tkyte@DEV8I.WORLD> alter user scott identified by " ";
User altered.

but you still need to supply it:

ops$tkyte@DEV8I.WORLD> connect scott/" "
Connected.

to log in (it is not as if you do not have a password -- you
do, its a blank).  I don't think this is what you meant though.

We do not have the concept of a user with no password -- all
users must have a password to log in.  If you are trying to find
out how to have an account anyone can log into without have to
know some password, a convention many people use is to set the
username = password.  That way, they would log in using

SQL> connect scott/scott

for example.

On a user by user basis, you can use OS or External
authentication.  For example, I've setup my OS_AUTHENT_PREFIX in
my init.ora to be "ops$".  I have a Unix account "tkyte".  I've
created a database user:

SQL> create user ops$tkyte identified EXTERNALLY;

Now, as long as I'm logged into Unix, I can simply:

$ sqlplus /

SQL*Plus: Release 8.1.5.0.0 - Production on Tue Jun 6 13:25:26
2000
(c) Copyright 1999 Oracle Corporation.  All rights reserved.
Connected to:
Oracle8i Enterprise Edition Release 8.1.5.0.0 - Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.0.0 - Production
ops$tkyte@DEV8I.WORLD>

The database picks up my username from the OS -- not from the
command line.  It trusts that I am TKYTE (ops$tkyte in the
database as I've set it up) since I'm logged into the Unix
system.

Review &
      Followup

      Rating: 5
Can these be
      shared  April 24, 2002
Reviewer:  AB
       from UK
Can a bunch of OS users all with the same GID share an OS Authenticated Oracle
account rather than have a schema per OS user ?

Thanks

          Followup:

No.  They would all have to share the same OS account (and you wouldn't do that
I hope).

Account sharing = bbbbaaaaaddddd.

      Rating: 3
How about Java,
      Pro*C, Perl  August 02, 2002
Reviewer:
      Piotr  from Braunschweig, Germany
Can you write a custom, say Perl script and run it as OS cron job using an
externally authenticated account? I want to run it only on the local database
machine. Could you, please, give an example. Thanx!

          Followup:

I would *assume* so but you would have to consult the perl docs to see if it is
the case.

If perl supports you just passing "/" as the username and password -- it'll
work.

I have no perl examples, never wrote anything in perl.

A search for

perl "os authentication" oracle

on www.google.com turned up:

.....

# create a database handle.  the syntax for this is very flexible.
# the quoted string second argument is of the form USERNAME/PASSWORD.
# if the username is "scott" and the password is "tiger", then
# the second parameter would be "scott/tiger".
# if you want to connect using OS authentication as the current
# unix id then you can omit the username and the password but
# leave the slash, so the parameter would then be "/" (as shown).
my $dbh = DBI->connect("dbi:Oracle:", "/");

      Rating: 5
It
      works!  September 18, 2002
Reviewer:  Piotr
       from Braunschweig, Germany
Exactly as you said!

--------------------------------------
#!/usr/bin/perl -w

use DBI;

my $dbh = DBI->connect("dbi:Oracle:", "/");
my $sth=$dbh->prepare('select banner from v$version');
$sth->execute();
my $row=$sth->fetchrow_arrayref();
print("$$row[0]\n");
$sth->finish();

$dbh->disconnect;
----------------------------------

Thank you!

      Rating: 5
But it doesn't
      in Java :-(  September 18, 2002
Reviewer:
      Piotr  from Braunschweig, Germany
con=DriverManager.getConnection("jdbc:oracle:oci8:@testdb");
raises:

Exception in thread "main" java.sql.SQLException: ORA-01004: default username
feature not supported; logon denied

        at oracle.jdbc.dbaccess.DBError.throwSqlException(DBError.java)
        at oracle.jdbc.oci8.OCIDBAccess.check_error(OCIDBAccess.java)
        at oracle.jdbc.oci8.OCIDBAccess.logon(OCIDBAccess.java)
        at oracle.jdbc.driver.OracleConnection.<init>(OracleConnection.java)
        at
oracle.jdbc.driver.OracleDriver.getConnectionInstance(OracleDriver.java)
        at oracle.jdbc.driver.OracleDriver.connect(OracleDriver.java)
        at java.sql.DriverManager.getConnection(DriverManager.java:517)
        at java.sql.DriverManager.getConnection(DriverManager.java:199)
        at Test.main(Test.java:20)

Program was run on the machine where DB server runs. We use Oracle
classess111.jar. I assume that it is possible but the driver somehow can't
handle it. Another question: How does Bequeath work? I've heard it uses OS pipe
to a dedicated server. How can I configure my Java connection string to use it
or does the JDBC driver do it automatically once it knows the connection is
local?

          Followup:

DriverManager.getConnection ("jdbc:oracle:oci8:/@");

is what you want.  your error is coming from trying to connect over the network
and not have remote_os_authent set to true (which is GOOD, don't set it to
true).

      Rating: 5
Now it
      works!  September 19, 2002
Reviewer:  Piotr
       from Braunchsweig, Germany
Thanks!

      Rating: 1
ORA-01004
      ProC  January 30, 2003
Reviewer:  Rose
       from Sao Paulo
Hi Tom,

I create an application using C and ProC.
The connection is using Embedded SQL :
EXEC SQL CONNECT :uid IDENTIFIED BY :pwd AT :nome_db USING :db_string;

all bind variables are corrects.

When I execute this aplication (trying to connect to Oracle 9i), it returns
"ORA-01004: default username feature not supported; logon denied"

Can you help me?
Thanks

          Followup:

can you help me out and show me what your definition of "correct" is.

Just post an entire "main" (as SMALL as possible) showing us what you really do.

      Rating: 2
ora-1004
      ProC  January 31, 2003
Reviewer:  Rose
       from sao paulo
1) My program :

char *gDBName = { "ORACLE" };
char *gUser =   { "CAUSU" };
char *gPwd =  { "CAUSUX" };
char *gAlias = { "TESTE");

EXEC SQL BEGIN DECLARE SECTION;
   VARCHAR uid[20];
   VARCHAR pwd[20];
   VARCHAR db_string[30];
   VARCHAR nome_db[30];
EXEC SQL END DECLARE SECTION;

long int Call_Conect(void)
{
    EXEC SQL WHENEVER SQLERROR GOTO conerr;
    EXEC SQL CONNECT :uid IDENTIFIED BY :pwd AT :nome_db USING :db_string;
    return (1L);

conerr:
    printf ("%s", sqlca.sqlerrm.sqlerrmc);
    return (sqlca.sqlcode);
}

void main(void)
{
long int cConecOk;

    strcpy ((char *)uid.arr, (char *) gUser);
    strcpy ((char *)pwd.arr, (char *) gPwd);
    strcpy ((char *)db_string.arr (char *) gAlias);
    strcpy ((char *)nome_db.arr (char *) gDBname);

    uid.len=strlen((char *)uid.arr);
    pwd.len=strlen((char *)pwd.arr);
    db_string.len = strlen ((char *)db_string.arr);
    nome_db.len = strlen ((char *)nome_db.arr);

    cConecOk = -1L;
    cConecOk = Call_Conect();
    printf ("cConec = %dl", cConecOk);
}

2) I user ProC 2.2

3) My TNSNAMES.ORA is

TESTE.world =
  (DESCRIPTION =
    (ADDRESS_LIST =
        (ADDRESS =
          (COMMUNITY = tcp.world)
          (PROTOCOL = TCP)
          (Host = 30.10.1.23)
          (Port = 1521)
        )
    )
    (CONNECT_DATA = (SID = ora920)
    )
  )

4) I connect to Oracle 9i (9.2.0.1.0) with SQLPLUS :
connect CAUSU/CAUSUX@TESTE;

          Followup:

2) is going to be the problem.  7.3 clients are

a) not supported against 9iR2
b) are know to have many serious issues
c) do not work

You need to use AT LEAST an 8.0 client.

the fact that 4) works accidently (i can show you more cases where it does not
work with 7.x sqlplus clients then it does) is an accident.  you need to use a
8.x and up client at least.

January 31, 2003 Eduardo -- Thanks for the question regarding
      "More than one queue of jobs", version 8.1.6

      You Asked
Hi Tom:

I develop a SP that create Orders in Oracle and as/400 (we have several AS/400
servers) the order is inserted in the Oracle DB online ,but the SP create a job
for insert in AS/400.

The DB is setup to 30 jobs at the same time

One of the AS/400 servers is slower than others, therefore when the queue es
full whit jobs that insert orders to this server, the delay is to long for jobs
that insert to other machines.

Example:

server1 (AS/400) slowest
server2 (AS/400)

queue = 30 jobs

server1 35 jobs (30 in process and  five in queue)
server2 10 job all of them in queue

How can I divide the queue or have diferent queues for each server?

Queue1 for server1
Queue2 for server2

      and we said...
You would "DIY" do it yourself with AQ- the advanced queues.

Here you would set up a process to dequeue and process messages from a queue.
You can set up as many queues as you like and as many consumes of the queues you
would like as well.

So, you can set up a "fast queue" with 15 dequeuers and "slow queue" with 10
dequeuers.  Messages would be processed as fast as possible out of each queue.

See the Programmers guide to AQ for details.
Review &
      Followup

      Rating: 4
Thanks
      Tom  January 31, 2003
Reviewer:
      A reader

      Rating: 4
Thanks
      Tom  January 31, 2003
Reviewer:  Eduardo
      Campos  from Monterrey, NL., Mexico.

January 31, 2003 laura -- Thanks for the question regarding
      "comparing two files with content of a two database table", version Oracle
      7.3.2

      You Asked
Hi Tom,

I have the following problem:
I have to compare and then merge two files (or tables of a
oracle database) by content.
I work in a Unix environment and i don t like the result of a
diff or comm commands.
If you have any ideas of how to do it i apreciate this very
much.
Or if there is on net some free util who can do that i apreciate
also ?

Thank,
laura

      and we said...
If they are tables -- I can help you.

To find the rows in T1 that are not in T2, you can:

select * from t1
MINUS
select * from t2;

(reverse the order to find the rows in t2 not in t1)....

To find rows that are in both t1 and t2, you would use:

select * from t1
intersect
select * from t2;

By merge - You probably mean either:

select * from t1
UNION ALL
( select * from t2
  minus
  select * from t1
)
/

for example:

ops$tkyte@ORA8I.WORLD> create table t1 ( x int );

Table created.

ops$tkyte@ORA8I.WORLD> create table t2 ( x int );

Table created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> insert into t1 values ( 1 );

1 row created.

ops$tkyte@ORA8I.WORLD> insert into t1 values ( 2 );

1 row created.

ops$tkyte@ORA8I.WORLD> insert into t2 values ( 2 );

1 row created.

ops$tkyte@ORA8I.WORLD> insert into t2 values ( 3 );

1 row created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> create table merge
  2  as
  3  select * from t1
  4  UNION ALL
  5  ( select * from t2
  6    minus
  7    select * from t1
  8  )
  9  /

Table created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select * from merge;

         X
----------
         1
         2
         3

ops$tkyte@ORA8I.WORLD>

Review &
      Followup

      Rating: 4
Simple and very
      useful in learning about UNIONS, INTERSECT AND MINUS.  March
      22, 2002
Reviewer:  A reader  from Seattle, WA

      Rating: 5
Time
      saver  April 26, 2002
Reviewer:  dbdiva
       from Northern VA
Quick and easy...I had a huge script to compare and output the difference! This
took two seconds.

      Rating: 5
Day and Night
      Difference  August 28, 2002
Reviewer:  Gary
      Gushue  from Toronto, Ontario, CANADA
Tom,

Great tip.  Literally saved me hours of run time.  Can go home and eat dinner
for a change.

Likewise, had a procedure to cursor and compare values.  This way was lightning
fast.

Thanks!!!

      Rating:
      4
  January 31, 2003
Reviewer:  reader
       from Mo USA
Hi Tom ,
I compare the following two tables  (actually two views)like

SELECT * from test ;

      A          B
------- ----------
      1          2
      1          2
      3          4
      5          6

select * from test_2;

        A          B
--------- ----------
        1          2
        3          4
        5          6
        3          4
I use your query
 select count(*)
    from
  (
  ( select * from TEST
    minus
    select * from TEST_2 )
  union all
  ( select * from TEST_2
    minus
    select * from TEST )

and get the result:

 COUNT(*)
---------
        0
but this is not what I expected. So can you give me a better way to figure out
this case ? I want to get the complete information for the two tables (views).

Reader from MO

          Followup:

what did you expect?

Everything that is in test is in test_2
Everything that is in test_2 is in test

they are the same.  Generally, tables have primary keys so the pure dups you
have wouldn't be there.  if you want them to count "different", just:

select count(*)
    from
  (
  ( select a,b,count(*) from TEST group by a,b
    minus
    select a,b,count(*) from TEST_2 group by a,b )
  union all
  ( select a,b,count(*) from TEST_2 group by a,b
    minus
    select a,b,count(*) from TEST group by a,b )
  )

January 31, 2003 -- Thanks for the question regarding "Oracle
      error", version 8.1.7

      You Asked
Hi Tom,
I'm getting unique constraint error and I don't see any reason why. Please help.

Parameter 'p_surrogate_key' is passed by
'process_status_pkg.store_surrogate_key' function that returns the next value of
the sequence.

PROCEDURE insert_new_dimension_records
  ( p_dimension     IN  user_tables.table_name%TYPE,
    p_temptable     IN  user_tables.table_name%TYPE,
    p_surrogate_key IN  NUMBER,
    p_sysdate       IN  DATE,
    p_rowcount      OUT NUMBER ) AS

  v_sql VARCHAR2(4000);
BEGIN
  v_sql := 'INSERT INTO ' || p_dimension || ' SELECT ' || p_surrogate_key ||
',a.*,' || '''' || p_sysdate || '''' || ',NULL' || ' FROM ' || p_temptable || '
a';

  EXECUTE IMMEDIATE v_sql;

  p_rowcount := SQL%ROWCOUNT;
EXCEPTION
WHEN OTHERS then
  dbms_output.put_line( SQLERRM );
  RAISE;
END insert_new_dimension_records;

      and we said...
gee, no schemas -- no idea what is unique.  ok, i'll guess.

well, how many rows does p_temptable have

betcha it is more then 1.

if so, you are inserting the SAME value over and over and over.  so, that would
be the duplicate.

You NEED TO LEARN ABOUT THE IMPORTANCE OF BIND VARIABLES

that should be at worst:

   execute immediate
  'insert into ' || p_dimension || '
   select :surrogate_key, a.*, :dt, null
     from ' p_temptable || ' a'
   using p_surrogate_key, p_sysdate;

And just lose the WHEN OTHERS.  what is up with that?  I've never been able to
figure out why people do that.  The ONLY thing that will do for you is HIDE
which line the error actually occured on.  Say you get "Numeric or value error".
 So -- the line number will point to the RAISE.  Tell me

o did the v_sql := raise that error?
o did the execute immediate raise that error?
o or was it a bad assignment to p_rowcount that did it?

by having that when others, that does not handle the error, you LOSE that vital
information (so, lose the WHEN OTHERS)
Review &
      Followup

      Rating:
      4
  January 31, 2003
Reviewer:
      A reader
Yes. p_temptable will have more than 1 row. But how come it works if I just
strip the 'insert into select' statement out of the procedure and run it
separately? How can I make it work in one statement in procedure?
I always use bind variables though, I don't remember when I ended up with not
using it here.

          Followup:

Umm, not getting you.

what do you mean by "strip the insert into select out" ??

If p_dimension has a unique constraint on it.

And that constraint is on the column p_surrogate_key is going into.
    OR that constraint is on the column P_sysdate is going into

And p_temptable has more then one row -- by definition you will have a unique
constraint violation.

Not getting what you mean.

      Rating:
      3
  January 31, 2003
Reviewer:
      A reader
OK. I have a SQL from similar procedure that has a 'insert into select' that
loads the dimension initially. It uses sequence here the same way I'm trying to
use in a procedure to handle slowly changing dimension. It works fine.
  INSERT /*+ APPEND */ INTO product_dim
  SELECT PROCESS_STATUS_PKG.PRODUCT_SURROGATE_KEY, <--product key
         a.tikey,
         UPPER(a.title),
         NULL,
         UPPER(b.description),
         UPPER(c.description),
         UPPER(e.studio_name),
         a.release_date,
         UPPER(d.description),
         UPPER(f.long_genre),
         a.box_office,
         GLOBAL_UTILITIES.SYSDATE_NOTIMESTAMP,
         NULL
    FROM titles a,
         item_departments b,
         item_sub_departments c,
         item_categories d,
         studios e,
         prebook_genre f,
         prebook_title_genre g
   WHERE a.base_cat     = d.catcode(+)
     AND a.bsi_dept     = b.deptcode(+)
     AND a.bsi_sub_dept = c.subdeptcode(+)
     AND a.studio_key   = e.studio_key(+)
     AND a.tikey        = g.tikey(+)
     AND f.genre(+)     = g.genre;

Now I need to handle slowly changing dimension. So I get the changed records
from the source, load it in a global temp table and then insert it by creating a
dynamic SQL. p_surrogate_key is a sequence that I'm passing and it goes to
dimension's key(STORE_KEY) that's a primary key. That's where it fails. Hope I
cleared the smoke now.

          Followup:

that is totally different.

In your procedure, you "glue" in the return value from a single call to
PROCESS_STATUS_PKG.PRODUCT_SURROGATE_KEY.

Your generated SQL if you were to print it out would be:

insert into dim
select 1234, a.*, '01-jan-2002' from temptable;

that isn't what you are executing in sqlplus - -in sqlplus you are referencing a
function, which will return a different value for each row.

Maybe what you want to do is pass the NAME OF THE FUNCTION in p_surrogate_key --
instead of a SINGLE VALUE.  Pass in the STRING
'process_status_pkg.store_surrogate_key', not the output of a single call to
that function.

      Rating:
      3
  January 31, 2003
Reviewer:
      A reader
I'm sorry, this ain't going anywhere. I'm already doing what you suggested.
Here's function that generates the sequence and I'm passing this function in the
load dimension procedure. So it should always be next value but somehome it
isn't. What am I doing wrong here?

/* load store dimension */
BEGIN
load_prebookdss_datamart.load_dimension(
  global_pkg.g_type2_store_subroutine_desc,
  global_pkg.g_store_dim,
  global_pkg.g_type2_store_subroutine_name,
  global_pkg.g_dss_package_name,
  global_pkg.g_store_dim_temp,
  global_pkg.g_store_delta,
  global_pkg.g_nstore_key,
  process_status_pkg.store_surrogate_key, <----
  global_utilities.sysdate_notimestamp,
  global_pkg.g_init_stor_load_start_action,
  global_pkg.g_init_stor_load_compl_action,
  global_pkg.g_init_stor_load_failed_action );
END;

FUNCTION store_surrogate_key RETURN NUMBER AS
  v_id  NUMBER;
BEGIN
  /* Get the next sequence for store surrogate key */
  SELECT store_dim_seq.NEXTVAL
    INTO v_id
    FROM dual;
  RETURN v_id;
END store_surrogate_key;

          Followup:

no you aren't.

look at the function your provided me:

PROCEDURE insert_new_dimension_records
  ( p_dimension     IN  user_tables.table_name%TYPE,
    p_temptable     IN  user_tables.table_name%TYPE,
    p_surrogate_key IN  NUMBER,           <<<<<============== IN NUMBER
    p_sysdate       IN  DATE,
    p_rowcount      OUT NUMBER ) AS

  v_sql VARCHAR2(4000);
BEGIN
  v_sql := 'INSERT INTO ' || p_dimension || ' SELECT ' || p_surrogate_key ||
',a.*,' || '''' || p_sysdate || '''' || ',NULL' || ' FROM ' || p_temptable || '
a';

p_surrogate_key is a CONSTANT in this routine.  the query you build has the
number "1234" in it -- not a call to a function that returns a number -- the
NUMBER itself.

You pass a SINGLE NUMBER -- say 1234 -- and hard code it into a select that
returns many rows.  Therefore EVERY SINGLE ROW returned by that query will have
that number -- therefore, you have a DUPLICATE.

So, my suggestion -- do not pass a NUMBER, pass a string that contains the name
of a function that generates a number and glue (don't bind) that into the query.

So the insert into will be

insert into dim-table select FUNCTION,  ......

instead of what you have now which is:

insert into dim-table select 1234, .....

January 31, 2003 Bhavani -- Thanks for the question regarding
      "Difference between V$Process and v$session", version 8.1.7

      You Asked
Hi Tom,

Why v$process and V$session shows different values when we issue

Select count(1) from v$session / v$process;

How many Processes we can run for a Session? and what is the difference between
these two?

Thanks and Advance... By the way, i started reading your book Expert One to One
: Oracle.  I bet, at least 30% of its contents is not knows to even most
experienced DBAs.  Thanks for sharing valuable information....

Regards

Bhavani

      and we said...
a process can have many sessions.
a session may or may not have a process.

I can use connection multi-plexing to have one physical connection to the
database with a single dedicated server (process) and have many sessions going.

I can have a session via a shared server whereby unless I'm active -- I don't
have a process (many sessions share a process).

So a process is a process and a session is a session.  A session eventually
needs a process, is not tied to a single process.  A process can have zero one
or more sessions using it.
Review & Followup

      Rating: 5
some
      questions  August 13, 2002
Reviewer:
      Brain.Xu  from China.
Hi,tom:

   you said"I can have a session via a shared server whereby unless I'm active
-- I don't have a process (many sessions share a process)." i think that is a
procedure of a "user process" idle for v$session "inactive" and when a user
process contact with server process,it's active.right?

i puzzled about "session" concept, I understand process concept(Oracle use
server process process user's request or job.it's a really existed things),but
don't see difference about it and session so.

Thanks,Tom!

          Followup:

I do not understand the text in the first paragraph.

In shared server - multi-threaded server -- a user has a session.  A session is
your login, it means you can do SQL, you are connected and authenticated to the
database.  You have a row in v$session.

When you are not doing SQL, you are inactive and you will NOT be associated with
any process.  In shared server, there might be 10 shared servers (just like a
dedicated server) but you might have 1000 sessions -- all of the sessions will
be sharing these processes.  It is like a connection pool.

When a shared server session becomes active, it is associated with some process
while active.  when it is done being active, it is dis-associated from that
process.  It only uses a process for the duration of the request/call to the
database.

A session is your session.
A process is something you might use from time to time during your session.

Your session does not have a process (in general).  A process does not have
sessions.  A process is a resource used by sessions over time.

      Rating: 5
Excellent
      Answer  August 13, 2002
Reviewer:  Leo
       from Boston,MA

      Rating: 4
thank you for
      your answer!  August 15, 2002
Reviewer:
      Brain.Xu  from China

      Rating:
      5
  December 24, 2002
Reviewer:
      A reader
Where will the PGA in MTS process?

 Is it in dispatcher??

          Followup:

shared server -- pga = PROCESS global area, process specific memory.  It's in
the dedicated or shared servers.

      Rating:
      5
  January 31, 2003
Reviewer:
      A reader
Hi Tom,

 I am running one sql statement from sql*plus, after fetching some records it
hangs.The command column in v$session is 3. I open couple other sessions and
when I run same sql statement same thing is happning. If I check from v$session
command shows 3 for three sessions(I.e select statement).

I started query at 11:00 AM and I am running below sql at 12:30PM

It seems all three session is hanging for the same query.

Why it is hanging ??

I check v$process table and column
LATCHWAIT shows me value '8431269C' for all three session.

Is it waiting for latch ?

Here is the outcome of my query

  1  select sid,paddr,latchwait,status,c.name from v$session a,v$process b,
  2  v$latch c,
  3  v$latch_children d
  4  where a.paddr = b.addr
  5    and b.latchwait = d.addr
  6    and c.latch# = d.latch#
  7    and c.level# = d.level#
  8*   and a.command = 3
SQL> /

       SID PADDR    LATCHWAI STATUS   NAME
---------- -------- -------- --------
------------------------------------------------
        24 8409A39C 8431269C ACTIVE   cache buffers chains
        27 8409A69C 8431269C ACTIVE   cache buffers chains

I have never seen case like this .

Please advice.
Thanks

          Followup:

consider opening a tar i would suggest.

      Rating:
      5
  January 31, 2003
Reviewer:
      A reader
SQL> SELECT ADDR,PROGRAM,LATCHWAIT FROM V$PROCESS
  2  WHERE LATCHWAIT IS NOT NULL;

ADDR     PROGRAM                                          LATCHWAI
-------- ------------------------------------------------ --------
84095E9C oracle@mhdev1 (DBW0)                             8431269C <<------
8409649C oracle@mhdev1 (CKPT)                             8431269C <<------
8409A69C oracle@mhdev1 (TNS V1-V3)                        8431269C
8409B59C oracle@mhdev1 (TNS V1-V3)                        8431269C

What do you think about DBW0 and CKPT ??

Please explain what is going on here.

Thanks

          Followup:

I think you should contact support and open a tar....

January 31, 2003 Anthony -- Thanks for the question regarding
      "Display the tablespaces that cant allocate another extent for a segment",
      version 8.1.6

      You Asked
HI Tom

1.    Can you look at this query and c if this script is useful.

   Select f.tablespace_name, cant_allocate
from dba_free_space f, (select ( (max(next_extent)/1024/1024))  * 3
CANT_ALLOCATE, TABLESPACE_NAME
                       from dba_segments t
               group by tablespace_name) MAX_ALOC
where f.tablespace_name=max_aloc.tablespace_name
AND F.TABLESPACE_NAME IN (SELECT TABLESPACE_NAME FROM DBA_DATA_FILES DF
                  WHERE DF.TABLESPACE_NAME=MAX_ALOC.TABLESPACE_NAME
                       HAVING SUM(MAXBYTES/1024/1024)   < MAX_ALOC.CANT_ALLOCATE
* 2
                  GROUP BY TABLESPACE_NAME)
having sum(f.bytes/1024/1024) < cant_allocate
group by f.tablespace_name, cant_allocate
/

2. How can i calculate if the buffer cache, shared pool, log buffer
are not over sized. Can you give me a script if possible.

3. What's the most efficient strategy when using the 'IN' condition in the
'WHERE' clause than using 'UNION'

  We have an index on the pln_tran by
  tran_date and tran_type
- this table has 1.6 Million records

    select tran_Date from pln_tran
    where tran_type in ('TEX','RTP','RPF','XXO')

  than
    select tran_date from pln_tran
    where tran_Type='TEX'
    UNION all
    select tran_date from pln_tran
    where tran_Type='RTP'
    UNION ALL
    select tran_date from pln_tran
    where tran_Type='PRF'
    UNION ALL
    select tran_date from pln_tran
    where tran_Type='XXO'

4.  I red in the metalink that when you do a full table scan,
    oracle will read up to the High water mark, if that so, then
    if we have a table that has large number of deletes but small    amount of
inserts that would affect our performance?

Thank you very much for your precious time TOM.....
MORE POWER.....

      and we said...
1) seems:

ops$tkyte@ORA8I.WORLD> select a.tablespace_name, a.largest_extent,
b.largest_next, a.largest_extent-b.largest_next diff
  2    from (select tablespace_name, max(bytes) largest_extent
  3            from dba_free_space
  4           group by tablespace_name) a,
  5         (select tablespace_name, max(next_extent) largest_next
  6            from dba_segments
  7           group by tablespace_name ) b
  8   where a.tablespace_name = b.tablespace_name
  9  /

TABLESPACE_NAME                LARGEST_EXTENT LARGEST_NEXT       DIFF
------------------------------ -------------- ------------ ----------
ATCDEMO_DATA                            65536       221184    -155648
DES2K_IDX                              524288       524288          0
DRSYS                                 3309568       335872    2973696
FLOWS_DATA                           26214400       524288   25690112
GPS_DATA                               524288       524288          0
ISOC                                   524288       524288          0
OEM_REPOSITORY                         524288       524288          0
OLR_DATA                              1572864       524288    1048576
ORA_SP_DATA                             40960      1720320   -1679360
PEOPLE                                4194304       524288    3670016
PKT_DATA                               524288       524288          0
PPR_DATA                              4259840      1146880    3112960
PPR_IDX                                598016       262144     335872
RFP_DATA                               540672     45342720  -44802048
SARA                                   524288       524288          0
SYSTEM                                 114688     13090816  -12976128
TQM_DATA                               524288       524288          0
USERS                                 8388608       262144    8126464
UTILS                                  524288       524288          0

19 rows selected.

would be easier -- find the biggest free extent by tablespace, find the biggest
NEXT_EXTENT by tablespace and subtract.  If negative -- you MIGHT have a problem
(i do not, i use autoextend, I'm OK there)

2) look at your hit ratios, use stats pack to see how much memory is actually
used in the shared pool.  The log buffer is generally so small as to not be a
concern -- you could make it smaller until you start seeing waits on redo writes
and such.

3) They are basically equivalent if tran_type is indexed and that index is used.
 Look at the plans and you would see this.  If there is no index being used, the
union all is really quite inefficient as it would full scan the table 4 times -
the in would do it once.

Use IN

4) If you load up a billion records and then delete them all.  A select * from t
will take about the same amount of time if the table was full.  It'll full scan
every block that HAD data.  So yes, a table that was big and is now "small" due
to a delete (how you have very small number of inserts but lots of deletes???
by definition you can only delete a row after its inserted so they would be at
least the same) would take longer to full scan then you might think.
Review &
      Followup

      Rating:
      4
Excellent  February 03, 2002
Reviewer:
      Anthony K. Velarde  from Philippines
Thank you very much TOM.... More power and God bless.

      Rating: 4
Reg Question
      #4  February 03, 2002
Reviewer:  Irfan
       from Kuwait
Hi Tom,

Can you guide me a better way to increase the performance after large number of
rows are deleted from a table. (Normally history tables that heap around
millions of records in a small time). Is there a way to dealloacte
blocks/extents. Since Truncate command will delete all the rows. We have Oracle
734 Parallel Server.
Thanks.

          Followup:

Not in that release.

Partitioning is the answer.  It allows you to easily "age" data out of the
database.  You can simply drop a partition -- a slice of the data -- without
touching the rest.  Sort of like a selective delete.

You see - the rows you delete might be intermingled with other rows.  Sure, you
started with 10,000,000 rows, you delete 5,000,000 of them -- but you might not
have EMPTIED a single block (eg: you deleted every other record and you get 2
records/block).  There is quite simple "nothing" to deallocate.  You would need
to "rebuild".

      Rating: 5
Free space
      query  February 04, 2002
Reviewer:  Connor
      McDonald  from Battersea
Maybe a "(+)" might be useful for the poster for the (hopefully rare) condition
where there are no remaining entries in DBA_FREE_SPACE for a tablespace.

          Followup:

YES, definitely -- good point, it should be:

ops$tkyte@ORA8I.WORLD> select a.tablespace_name, a.largest_extent,
b.largest_next, a.largest_extent-b.largest_next diff
  2    from (select tablespace_name, max(bytes) largest_extent
  3            from dba_free_space
  4           group by tablespace_name) a,
  5         (select tablespace_name, max(next_extent) largest_next
  6            from dba_segments
  7           group by tablespace_name ) b
  8   where a.tablespace_name(+) = b.tablespace_name
  9  /

      Rating: 5
The
      disappearing next extent....  April 05, 2002

Reviewer:  Connor  from London
Just about every query I've ever seen for free space mgt involves "Can the next
extent be allocated without error?" which makes enough sense.  Any suggestions
on how this can be achieved for an auto-allocate lmt? (Not my choice of use -
but of course it is the default for 9i).

sys@cust9> create tablespace small datafile
'/u02/oracle/oradata/cust9/small.dbf' size 2m;

Tablespace created.

sys@cust9> create table small_tab ( x number ) tablespace small;

Table created.

sys@cust9> select segment_name, bytes, next_extent
  2  from user_segments
  3  where segment_name = 'SMALL_TAB'

SEGMENT_NAME              BYTES NEXT_EXTENT
-------------------- ---------- -----------
SMALL_TAB                 65536

Cheers
Connor

          Followup:

If there is free space -- and the free space exceeds the extent size, there be
enough room.

Me, I use auto-extend datafiles (you either love them or hate them, i know.  I
love them).  So to me, those queries are useless.  "df -k" tells me if i have
enough space or not.

On my system, this query looks at locally managed, uniform allocation
tablespaces:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.tablespace_name, a.initial_extent,
  2  case when b.bytes > a.initial_extent then 'Got space' else 'No space' end
got_space
  3  from dba_tablespaces a,
  4       ( select tablespace_name, max( bytes ) bytes from dba_free_space group
by tablespace_name ) b
  5  where a.tablespace_name = b.tablespace_name (+)
  6  and a.extent_management = 'LOCAL'
  7  and a.allocation_type = 'UNIFORM'
  8  /

TABLESPACE_NAME                INITIAL_EXTENT GOT_SPACE
------------------------------ -------------- ---------
RBS_TS_01                              524288 No space
RBS_TS_02                              524288 No space
RBS_TS_03                              524288 No space
RBS_TS_04                              524288 No space
RBS_TS_05                              524288 Got space
TEMP                                   524288 No space
USERS                                  524288 Got space
DRSYS                                  524288 No space

8 rows selected.

and shows which have space or not.  Looks like I'm in trouble -- but not really.
 if I run my free.sql (search for free.sql) I would find that all of my files
can autoextend as needed.

      Rating: 3
Tuning
      monitoring scripts  January 31, 2003
Reviewer:
      A reader
Hi,

We have the following scripts run every 15minutes to monitor
1. if the any object (table/index) is close to reaching its max extents

SELECT
    segment_type,
    segment_name,
    extents,
    max_extents,
    ( ( extents/max_extents ) *100 )
FROM
    user_segments
WHERE
    segment_type
    IN     ( 'TABLE',  'INDEX' )  and   extents     > (90/100) * max_extents

Performs approximately
DISK READS         1000
BUFFER GETS        280205

2. if the the tablespace has contiguous space big enough for the next extent.

SELECT
    substr ( segment_name,1,20 ),
    substr ( segment_type,1,8 ),
    substr ( tablespace_name,1,15 ),
    bytes,
    next_extent
FROM
    sys.dba_segments a
WHERE
    rtrim ( a.Owner ) =  'SCOTT'  AND a.tablespace_name in  ( select
tablespace_name from sys.dba_data_files group by tablespace_name )  and
next_extent  >  ( select max ( bytes )  from sys.dba_free_space b where
b.tablespace_name = a.tablespace_name )  order by next_extent desc

Performs approximately:

DISK READS         18450
BUFFER GETS        2828560

These are very expensive on production boxes. Is there any way to write them
efficiently? Can you please help to provide a better sql?

Thanks a lot.

          Followup:

1) alter table set the maxetents unlimited and stop running it -- or move
everything to locally managed tablespaces where the only option is maxextents
unlimited.

problem solved - you don't need to run the query anymore. that query was
relevant in 1995 and before.

2) see #1 and move to LMTS which remove this issue all together.

(to check every 15 minutes?  that seems a tad extreme, maybe once a day -- off
peak, but every 15 minutes???)

February 01, 2003 David -- Thanks for the question regarding
      "Synonyms vs Alter session current_schema", version 8.1.7

      You Asked
I've been reading a lot of articles that talk about private or public synonyms
(including your site and ixora site). I use private synonyms, and all users
connect to database with their accounts. I've experimented that volume of SYN$
table is very big. Ok, slow queries of dictionary, more space, internal overhead
...

I've tested with "alter session set current_schema .." and my results are very
good versus synonyms. I'd like to know if there would any problem if I do "Alter
session set current_schema = <owner>" versus use of owner.object for all users
in a transaccional system that today there are 500 users. (addicional internal
overhead, lost performance, ..). This application is developed with Oracle Forms
6i and is executing with Forms Server.

For example, using private synonyms I'm getting:

INSERT into sinonim_privat
values
 (:b1,:b1, 'prova inserció tkprof 1')

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        3      0.00       0.00          0          0          0           0
Execute 299997     47.27      57.16          0       1469     312826      299997
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   300000     47.27      57.16          0       1469     312826      299997

I imagine that parse = 3 because I've used a anonymous block for this test. This
test has been executed 3 times.

and I'm getting this results using alter session:

INSERT into sinonim
values
 (:b1,:b1, 'prova inserció tkprof 1')

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        3      0.00       0.00          0          0          0           0
Execute 299997     43.04      49.49          0       1440     312848      299997
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   300000     43.04      49.49          0       1440     312848      299997

Do you think that is a good idea use alter session for a big transaccional
system? (addicional internal overhead, lost performance, ..).

Atentament
David Pujol
Thanxs.

      and we said...
I've been recommending the alter over synonyms for a while.

I don't like synonyms (not even a big fan of the alter -- believe the schema
should be qualified in the app but anyway...)

Alter is the lesser of two evils.

Review &
      Followup

      Rating: 3
Synonyms vs
      Alter session  November 04, 2002
Reviewer:
      David Pujol  from Barcelona, Catalonia
Ok Tom, thanxs, I understand that I can use "alter session" for transactional
systems and don't lost more performance than synonyms. I've seen that alter
session changes the current schema, but users are same so, I don't think that
there are any problem if I use auditTrail.

          Followup:

correct, the only thing the current_schema changes is the default schema (owner)
name that is pre-pended to unqualified object references.

it does not change your identity
it does not change your privileges
just the default owner added to unqualified references.

      Rating: 3
Oracle Metalink
      note: 1036894.6   January 31, 2003
Reviewer:
      sudhakar nagisetty  from Bristol, CT USA
Use SET CURRENT_SCHEMA
Note that CURRENT_SCHEMA is not a documented parameter, and therefore its
functionality could be changed or the parameter could be made unavailable at any
time.

          Followup:

Not to worry:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a85397/statem18.htm#2053087
tis fully documented and supported these days. (gotta look at the versions and
age of the documents sometimes in metalink -- even here.  I answer with regards
to the version in the title of the question -- the answer maybe different for
your release)

      Rating: 5
Oracle Metalink
      note: 1036894.6   January 31, 2003
Reviewer:
      Sudhakar Nagisetty  from Bristol, CT, USA
Thanks, your input is very helpful

      Rating: 4
what happens
      when owner changes  January 31, 2003
Reviewer:
      eric givler  from Harrisburg PA
you said:

---
I don't like synonyms (not even a big fan of the alter -- believe the schema
should be qualified in the app but anyway...)
---

The DBAs, in there infinite wisdom, decide that our ownername, RECORDS, should
conform to the standard of RECORDS_DBA.  All data is exported and brought back
in with a fromuser touser import.  Now we have to change *all* frontend source
code - go into EVERY record group, every block property, search and replace all
code, revise all scripts that had the hard-coded owner, etc.

And we'd better go in and fix all backend source code, including view
definitions and stored source.  What's worse is that triggers didn't load if the
owner was there, and our DB jobs were submitted on import as a person that
doesn't even exist anymore as that schema was dropped.  (they are seen in
dba_jobs, but not in user_jobs in the new schema)

In a case like this, the public synonyms would have avoided all the frontend
mess at least.  The backend stuff really didn't need explicit usernames.

Is this too contrived to be appropriate?  (It has happened to me MORE THAN
ONCE).

Again, apologies for another question, I just thought I'd share an issue I've
had to deal with.

          Followup:

so parameterize your installation scripts?  when I goto install code -- I
generally have to run:

SQL> @install A B C D E F G

where a b c d e f g are the parameters to my install scripts.  I set the define
variable to something like ^ (instead of & which happens in real code too often
with the web stuff).  then my code has:

   select x,y,z from ^schema..table_name

Also, if the DBA's say "gotta use a standard name cause we said so", just give
your manager the estimate of how many $$$'s this will cost and ask him to ask
them to justify such an expenditure.  Seems like a really bad post-decision by
the DBA's eh?

Show the $$$ effect and you might well find you are not in such a situation.

but -- you brought up a good point, VIEWS.  I like views alot.  they can achieve
the same thing as synonyms, don't pollute the namespace, import the metadata
into my schema (so user_tab_columns has data for example), let me "rename a
column" in a flash, let me "reorder the columns" in a table instantly.....

February 01, 2003 Wengli -- Thanks for the question regarding
      "Dynamic View with Bind Variables", version 8.1.7

      You Asked
Greetings,

I want to create a view dynamically but when I use bind variables, I get this
error:

ORA-01027: bind variables not allowed for data definition operations

What I want to do is to create a script that will dynamically create a view
based on the dates the user requested.

Example:
  Procedure CREATE_DYN_VIEW ( p_StartDt In Date, p_EndDt In Date )
  Is
  Begin
     Execute Immediate 'Create View CLAIMSVIEW As ' ||
        'Select DRCODE, TCCODE, Sum( PACK_QTY ) TTL_QTY ' ||
        'From CLAIMS ' ||
        'Where EFFTDT Between :pStart And :pEnd ' ||
        'Group By DRCODE, TCCODE '
        Using p_StartDt, p_EndDt;
  End;
  /

Is this possible?

Thanks for your valuable input.

      and we said...
You cannot use bind variables in DDL

'where efftdt between to_date( ''' || to_char(p_startDt,'yyyymmddhh24miss') ||
''', ''yyyymmddhh24miss'' ) and ......

is the only way to do it.
Review & Followup

      Rating: 3
Use temporary
      tables  January 22, 2003
Reviewer:  Anand
      Singh  from India
Hi Tom

I always use temporary tables in this type of situation. With the help of DML i
inserts rows to temporary table. Do i does right.

Anand

          Followup:

how could a temporary table replace a VIEW?  er?

      Rating: 5
Couldn't this
      be achieved with application contexts ?  January 22, 2003

Reviewer:  A Reader  from UK
I'm not a guru of this but from what I've seen on your site couldn't application
contexts be used to solve this rather than dynamically creating views ?

          Followup:

For that matter -- the caller could just as well open a query itself.

I have to assume in this case this is a process they run every now and again and
many people query from it.  Hence, the people USING the view don't necessary
know what startdt/enddt are at all -- in that case, no sys_context would not be
useful.

      Rating: 5
Dynamic View
      with Bind Variables  January 22, 2003
Reviewer:
      Wengli  from California, USA
Tom,

Thank you so much for your response, my script is now working the way I want it.

      Rating: 3
More
      clerification  January 31, 2003
Reviewer:  Anand
      Singh  from India
Hi Tom,
Sorry for incomplete question.
I am saying, usually i use temoprary tables in place of dynamic view where the
result will based on user input.
First i creates temporary table and then i insert rows thru select command. Is
this method of obtaining result is right OR i should use dynamic view. Is using
temporary table creates overhead over dynamic view in oracle.

May now my question will clear to u.

Thanx
Anand

          Followup:

I would not use a temp table -- no.  I would just submit a single query.

      Rating: 3
what in
      application  January 31, 2003
Reviewer:  Anand
      Singh  from India
I am a application developer. I have to display the query result on canvas(d2k).
what should be the best practice. should i create dynamic view from my
application or should i stick to my existing method(insert rows in temporary
table).

anand

          Followup:

insert into global temporary table would be prefered over dynamically creating a
view per query.

      Rating: 5
Very
      Usefull  February 04, 2003
Reviewer:  Anand
      singh  from india
thanks Tom,
Your answer is very usefull to me.

February 01, 2003 Reddi -- Thanks for the question regarding
      "name_in and copy", version forms 6i

      You Asked
Tom

I have used name_in and copy commands very recently. But I am not very clear
about them. Can you explain to me what is there importance and utility.

Kindly give an example. I read that it helps avoiding hard coded values and
promote indirect referncing, can I know how. Please give an example in your
typical simple style.

Reddi

      and we said...
they allow you to indirectly reference a variable at run time.

Normally, you have to say:

   if ( some_variable = 5 )
   then
      some_other_variable = 10;
   end if;

the variables you are working on are known at compile time and will never
change.  At runtime, it is sometimes useful to make this more generic.  For
example -- you might have 10 blocks each of which have a item X and Y on them.
You could write a routine:

   variable_name = :system.current_block || '.x';

   if ( name_in( variable_name ) = 5 )
   then
      copy( :system.current_block || '.y', 10 );
   end if;

That performs an operation on the X and Y of the current block -- instead of
having to have:

   if ( :system.current_block = 'b1' )
   then
      if ( :b1.x = 5 )
      then
            :b1.y = 10;
      end if;
   elsif ( :system.current_block = 'b2' )
   then
       if ( :b2.x = 5 )
       then
          ..............

Review &
      Followup

      Rating:
      5
how  January 30, 2003
Reviewer:
      A reader
I have 20 text items :test_block.textitem1  .. :test_block.textitem20. I need to
display editor for
all the 20 items when the user clicks on the item. I want to put one when mouse
click trigger
at block level instead of one each for each item. How should I apply the above
example to my scenario?

DECLARE

Editor  BOOLEAN;
val_in VARCHAR2(4000);
val_out VARCHAR2(4000);

BEGIN

    val_in := :test_block.textitem1;

  SHOW_EDITOR('Test_Editor', val_in, val_out, Editor);

  IF Editor THEN

  :test_block.textitem1:=val_out;

  END IF;

      Rating:
      5
  January 30, 2003
Reviewer:
      A reader
will this work for the above question.., or we have to declare a variable and
pass the value of :SYSTEM.CURRENT_BLOCK||'.'||SYSTEM.CURRENT_ITEM to it.

DECLARE

Editor  BOOLEAN;
val_in VARCHAR2(4000);
val_out VARCHAR2(4000);

BEGIN

    val_in := :SYSTEM.CURRENT_BLOCK||'.'||SYSTEM.CURRENT_ITEM;

  SHOW_EDITOR('Test_Editor', val_in, val_out, Editor);

  IF Editor THEN

  :SYSTEM.CURRENT_BLOCK||'.'||SYSTEM.CURRENT_ITEM=val_out;

  END IF;

          Followup:

when you typed it in a tried it -- what happened?  did it work?  ;)

      Rating: 2
It did not
      work  February 01, 2003
Reviewer:
      A reader
I tried the below

DECLARE

Editor  BOOLEAN;
val_in VARCHAR2(4000);
val_out VARCHAR2(4000);

var varchar2(50):=:SYSTEM.CURRENT_BLOCK||'.'||:SYSTEM.CURRENT_ITEM ;

BEGIN

    val_in := var;

  SHOW_EDITOR('Test_Editor', val_in, val_out, Editor);

  IF Editor THEN

  var=val_out;

  END IF;

but in the editor , it returned me myblockname.myitemname i.e. it did not return
me the contents of the text item , but the block.item name.

Can you help me in finding a common routine as I requested earlier. Well I tried
as you suggested..

          Followup:

try otn.oracle.com under discussion forums.

Me, I haven't touched forms since 1995 and don't have the docs installed on my
machine.  on otn you'll find an active discussion forum with people who use
forms every day -- they'll be better suited to answer.

      Rating: 4
Re: Doesn't
      work  February 01, 2003
Reviewer:  Tony
      Andrews  from London, UK
... but "A Reader" is NOT using COPY and NAME_IN.  Try:

DECLARE

Editor  BOOLEAN;
val_in VARCHAR2(4000);
val_out VARCHAR2(4000);

var varchar2(50):=:SYSTEM.CURRENT_BLOCK||'.'||:SYSTEM.CURRENT_ITEM ;

BEGIN

    val_in := NAME_IN(var);

  SHOW_EDITOR('Test_Editor', val_in, val_out, Editor);

  IF Editor THEN

  COPY(val_out,var);

  END IF;

February 01, 2003 Chacravarthi -- Thanks for the question regarding
      "General About Oracle", version 8.0

      You Asked
Dear Sir,
Well and wish the same from you.I have some general questions about
Oracle.They are
1)What are the pl/sql  objects that cannot be declared inside a Package?
2)What can be the maximum number of joins that can be performed on
tables?
3)Do views have rowids?Oracle documentation specifies that only non-
joined views have rowids.But I am getting for joined views also.Please
clarfiy on this.

      and we said...
1) none -- all plsql objects can be declared by definition inside of plsql.

2) no documented limit.  I've tested over 500

3) a row has a rowid.  the rows returned by views may (are allowed to) return a
rowid as part of their select lists.   additionally many simple views on single
tables are able to provide a rowid when it is non-ambigous.  the rowid is for
the key preserved table (the table and can be UPDATED in the join view)
Review &
      Followup

      Rating: 4
Very
      Nice  February 06, 2003
Reviewer:
      Chacravarthi  from Chennai,India
Tom, Thanks for your response

February 01, 2003 Tomás A. -- Thanks for the question regarding
      "Mixed case table names and export(/import)", version 8.1.7

      You Asked
Dear Tom

We have some mixed case table names in our database and we need to export it,
but export converts the name to upper case and the table is not found. We have
the following export parfile:

USERID=/
FILE=/dba1/tools/export/export.dmp
LOG=/dba1/tools/export/export.log
COMPRESS=y
GRANTS=y
INDEXES=y
ROWS=n
CONSTRAINTS=y
DIRECT=y
TABLES=
(SISCEL.TAH_TRAFICO_CELULAR_MES
,SOP_SISCEL."TMCTAH_traficoCelularMes"
,OPS$TOOLS."dbt_UnusedSpace"
)

The table names SOP_SISCEL."TMCTAH_traficoCelularMes" and
OPS$TOOLS."dbt_UnusedSpace" are converted to
SOP_SISCEL."TMCTAH_TRAFICOCELULARMES" and OPS$TOOLS."DBT_UNUSEDSPACE". ¿How we
must specify this table names? We already have tried to recognize these names
using \" and "".

Regards and Thanks in Advance
Tomás.

      and we said...
$ exp userid=/ parfile=exp.par

Export: Release 8.1.7.4.0 - Production on Sat Feb 1 11:27:21 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production
Export done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set

About to export specified tables via Conventional Path ...
Current user changed to SCOTT
. . exporting table                            EMP         14 rows exported
. . exporting table                             t1         14 rows exported
. . exporting table                             t2          4 rows exported
Export terminated successfully without warnings.

$ cat exp.par
userid=/
tables=(scott.emp,
\"SCOTT.t1\",
\"SCOTT.t2\")

it is strange -- you have to put the ENTIRE schema.tablename in the single set
of quotes - not as you would normally do

February 01, 2003 Abdal-Rahman -- Thanks for the question regarding
      "Replication Plan", version 817 & 92

      You Asked
Hi Tom,

  I asked you more than one time about this issue, but I still confused how to
implement this techniques. OK, I think if we discuss a cenario, we will achieve
the good understanding. Look, I have 7 sites, say MAIN,
Branch1,Branch2,...,Branch6. I developed application (in Developer 6i) which is
now working properly in the MAIN site. This application using one schema called
MAIN_SCH. Now, they asked me to install this application in the other sites
taking into account that these sites needed to be linked as a Distributed &
Centerllized Databases. I am thinking now to create the same MAIN_SCH's objects
in each site but with different name,e.g. BR1_SCH, BR2_SCH,...,BR6_SCH. Each
schema contains the same Objects but should have different data.

  Most of the objects need to be one-way replication from the center to the
branches. Other small number of objects need to be 2-way replication. The main
missed point to me is, are creating of different names of schemas a good idea?
Or creating the same schema MAIN_SCH in each site is the best way? This is the
first time I do project like this. Do you have any place that provide sample
projects like this one? Please, I need your help, I need guidelines considering
all the sides of affecting a project like this. Let me call it "Inter-Branching
Application".

Thanks in advance.

      and we said...
do the tables need to be "consolidated" at "main" or do you want them to remain
separate?

That is the ONLY consideration here.

If you want a single table as main containing all of the data from MAIN and the
branches, everyone MUST use the same schema and the branches will use updatable
snapshots.

If you want an independent table per branch at main -  then everyone MUST use
their own schema.  You can use master to master or updatable snapshots in this
case.

So -- you tell me -- do you want

a) one table which infers one schema
b) 8 tables which infers 8 schemas.
Review &
      Followup

      Rating: 4
Replication
      Plan  February 01, 2003
Reviewer:  Abdal-Rahman
      Agha  from Yemen
Hi Tom,
  Thank you for the quick response.
'do the tables need to be "consolidated" at "main" or do you want them to remain
separate?'

What do you mean by "consolidated" and "remain separate"?
I need more explanation.

          Followup:

do you want 8 tables or 1 table at main.

Say there was an EMP table in your system.  Do you want

a) 8 tables -- one for "main" data, one for branch 1 data, .....

b) 1 table -- with all of the CONSOLIDATED emp data in it.

      Rating:
      4
  February 01, 2003
Reviewer:
      Abdal-Rahman Agha  from Yemen
OK Tom, Now I have understood what you mean.
1. If I want to create 8 schemas with 8 tables, should the schemas' names be
different in the sites, or I might use the same name of schema in each site?

2. How can I controll the risk of using a single table as main containing all of
the data from MAIN and the
branches? If one, for example delete all the rows from this table, the only copy
of the data will be gone!

          Followup:

1) use different ones - else just think about what happens at "main", that is
the only thing to think about here, what happens at main

2) you have much much larger problems to solve then.  You have security, data
integrity and backup and recovery to get down pat before you go about
replicating stuff.  If you have the opportunity for someone to "erase" all of
the data in a single table, they can just as easily do that in 8 tables.  this
is not an issue with one vs eight table -- this is an issue with regards to your
ability to secure, limit access to, backup and recover your data -- period.

what if you lose the disks?  same thing eh?

February 01, 2003 -- Thanks for the question regarding "JOB
      SCHEDULE", version 8.1.7

      You Asked
Hi, Tom,

I'm wondering how to schedule a job based on certain kind of rule?
We are in accounting program, and have a table like:
PERIOD_TB:
e.g.:
...       ....
...       ....
  id sdate       edate
  1  01-JAN-2002 15-JAN-2002
  2  16-JAN-2002 31-JAN-2002
  3  01-FEB-2002 15-FEB-2002
  4  16-FEB-2002 29-FEB-2002
  5  01-MAR-2002 15-MAR-2002
  6  16-MAR-2002 31-MAR-2002
  7  01-APR-2002 15-APR-2002
...      ....
...      ....
We have a processing procedure(p) needs to be run periodically at every start
date of the next period, i.e. run p by passing the id 1 at 16-JAN-2002(at
16-JAN-2002 00:30:10AM, process data during the period id 1), run p by passing 2
at 01-FEB-2002, ..... and the request running time should be 00:30:10AM in the
early morning at each running date. What I want to know is how to get right id
number at 16th every month(or 01 at every month)and pass it to p in order to
have p executed for the right period of accounting system time. Would you give
me detailed example please?

Thanks

      and we said...
write your procedure P like this:

procedure p( p_id in number, ... )
as
begin
    ..... existing code here ....
    select sdate into l_sdate
      from period_tb
     where id = p_id+1;

    dbms_job.submit( l_job, 'p('||p_id+1||');' );
end;
/

and you kick it off with:

dbms_job.submit( :n, 'p(1);' );
commit;

just have P shedule itself every time it runs....
Review &
      Followup

      Rating: 4
More
      explain  February 01, 2003
Reviewer:
      A reader
Hi, Tom,

Thank you for your useful reponse, but could you tell me what the use of:
select sdate into l_sdate
      from period_tb
     where id = p_id+1;
what purpose of l_sdate here?
Note, I want the job run at every 16th and 1st day of each month, is the above
select stmt achieve that?

Thanks

          Followup:

you said you have a table with dates to run:

PERIOD_TB:
e.g.:
...       ....
...       ....
  id sdate       edate
  1  01-JAN-2002 15-JAN-2002
  2  16-JAN-2002 31-JAN-2002
  3  01-FEB-2002 15-FEB-2002
  4  16-FEB-2002 29-FEB-2002
  5  01-MAR-2002 15-MAR-2002
  6  16-MAR-2002 31-MAR-2002
  7  01-APR-2002 15-APR-2002

that select just selected out the next date.  you could also just use an
interval like:

decode( to_char(sysdate,'dd'),
        1, trunc(sysdate)+15, add_months(trunc(sysdate,'mm'),1) )+3/24

as well -- that says "if today is the first, return the 16th, else return the
1st of next month, then add 3 hours so it runs at 3am"

      Rating: 4
Certain
      Dates  February 01, 2003
Reviewer:
      A reader
hi, Tom,

What is the most efficient way to tell today(sysdate) is certain date? e.g. if
the sysdate is on 16th or 1st date of each month within a submitted daily job
procedure, that is at every 16th and 1st of the date, we execute the procedure,
otherwise, pass this procedure and do nothing.

Thanks

February 01, 2003 Piotr -- Thanks for the question regarding
      "Parsing a stored procedure call.", version 9.2.0

      You Asked
Hello Tom,

If in my Java program I have a SQL statement like this:

"begin ?:=demo.foo(?,?,?); end;"

Would you use the "oracly correct" coding technique: parse it once, save
statement handle in a class variable and reuse it whenever possible later on and
never close it. Or perhaps wouldn't you bother doing this and would do a soft
parse all the time?

In other words: is the cost of parsing a stored procedure call very high? Would
you agree that a choice between the two methods might be dictated if number of
opened cursors reaches the limit on the client?

Thank you.

      and we said...
I would parse once, execute many.

I've been know to say that over and over and over ;)

It shouldn't be just the "oracle correct" way -- it should be best for
anydatabase.

Tell me, if you had the ability would you:

o compile a class
o load it
o use it
o through it out
o do it again the next time you needed it in the same app

or would you compile it once, keep it loaded and use it over and over.

The same holds true for these calls.  If you have more then hundreds of cursors
(rare), you could consider putting them into a hash table or some other lookup
and keeping a "bound" on them (eg: I'll cache 500 cursors at most -- closing one
when they ask for a new one and I've already gotten 500).

If the odds are HIGH that you'll use this statement over and over -- you should
not close it.
Review & Followup

      Rating: 4
Good
      analogy  February 01, 2003
Reviewer:  Piotr
      Jarmuz  from Braunschweig, Germany
Your analogy with loading a Java class is very convincing ;-) at least for
someone working with Java for a while.

I never thought of parsing a stored procedure call that way i.e: that it
requires some heavy activity like loading something at least not all the time.
It shouldn't be that hard...

But, yes if you can do something once then do it once. Simple enough.

Another question: if I keep a stored procedure call statement open in an
application and meanwhile redefine only the package body for that statement,
will this render this open handle invalid?

Thank you.

          Followup:

you may get "package state reset" but that would happen regardless of whether
you had a cached handle or not.

The database takes care of all cursor invalidations for  you automagically.

February 01, 2003 -- Thanks for the question regarding "Equivalent
      of like clause", version 8.1.7.3

      You Asked
Tom,

Can u please show me the trick to retrieve the records, which should not effect
the performance.

select user,table_name from user_tables
where table_name like '/BIC%';

From above query i'm getting total count of 7000 records and i want's to get the
rowcount of every table from above definition.

Note: I tried several ways to get the rowcount using below.

create or replace
function get_rows( p_tname in varchar2 ) return number
as
    l_columnValue    number default NULL;
begin
    execute immediate
       'select count(*)
          from ' || p_tname INTO l_columnValue;

    return l_columnValue;
end;
/

Then i used following query:

select user, table_name,
       get_rows( user||'.'||table_name) cnt
from user_tables
where table_name like '/BIC%'
/

My notes:

1) Above query took me "10" minutes to get the 7000 tables rowcount.

2) I removed "user" column from select statement it took me "3" minutes, i.e.,
if i keep more columns in sql statement it takes longer time, else it takes less
time.

Please tweak my above query and i should be able to get the response within one
minute. Is there any other way of above "like clause".

I remember somebody modified the like clause to get the good performance without
creating the index or function based index on the table_name column in where
clause.

Can u please give me your opinion and comments on the above.

I 'm analyzing the tables every month and i should get the rowcounts from
num_rows from dba_tables which i don't want to depend.

Early response is appreciated.

Thanks

      and we said...
if you put the user back in it'll take 3 minutes as well again.

It is called "you cached some of the data the first time and didn't do so much
physical IO"

You are counting the rows from 7000 table!  what do you expect?  This is pure
mathematics here.  You do lots of IO, it'll take a while.

You physically count the rows in 7000 tables, you are by definition doing lots
of IO.

No amount of fancy "like" tricks is going to do anythere here -- plain and
simple -- you are doing a ton of IO.

use

alter session set timed_statistics=true;
alter session set sql_trace=true;
select user, table_name,
       get_rows( user||'.'||table_name) cnt
from user_tables
where table_name like '/BIC%'
/

and run tkprof -- you'll see exactly what is taking many minutes -- 7000
COUNT(*)'s
February 01, 2003
      -- Thanks for the question regarding "ANY
      privilege", version 8.1.7.0

      You Asked
We want to grant users privileges to CREATE, DROP, ALTER, SELECT, INSERT and
DELETE objects from SCOTT (example user) users schema. What will be an
appropriate privilege to grant?

Regards

      and we said...
there is no such privilege.  We didn't anticipate that one.

Not to worry though, we did anticipate not anticipating....

connect scott/tiger
create procedure do_ddl( p_sql in varchar2 )
as
begin
   execute immediate p_sql;
end;
/

grant execute on do_ddl to some_other_user;

Now, if SCOTT has create table granted directly to them -- anyone who can do
do_ddl can do those things.

for insert/select/alter/delete -- I would grant those to a role and grant
execute on do_ddl to that role and grant that role to some other user.
Review &
      Followup

      Rating: 5
Small, simple,
      perfect!  May 06, 2003
Reviewer:  Kashif
       from Houston, TX

February 01, 2003 -- Thanks for the question regarding "Altering
      table columns to a smaller size", version 8.1.7

      You Asked
Hi Tom,

We have a big customer table which is referenced by many other tables. The
table's primary key is NUMBER(12). However the foreign key columns of the some
of the tables which is referencing this customer table were defined as NUMBER.
(Seen as NUMBER(38) in data dictionary). This datatype difference causes a data
conversion during the checking of the foreign keys and thus a performance loss.
In order to prevent it, we tried to alter the NUMBER columns to the same type of
our customer table's primary key - NUMBER(12). Since Oracle doesn't allow us to
do this unless the column values are NULL, we had to insert them with their
rowids to a temporary table, setting them to NULL, altering the column to
NUMBER(12), and populating the original values from the temporary table again.
This is a very slow process. So that we can't even see its end after 8 hours of
working and we had to stop it. My question is, could you please offer us a
faster way of doing this operation?

(The total size of the tables we have to alter is about 220 million rows, we are
running all DML in parallel and nologging mode, we are creating the temporary
table by create as select)

Thanks for your help...

      and we said...
really -- do you have hard numbers to quantify this "performance difference"?

Funny thing is that all numbers are -- well -- numbers in Oracle.

A number(38) is not stored any differently then a number(12).

A number(38) is not "converted" to a number(12) for comparision. A number(12) is
not "converted" to a number(38) for comparision.

It is not like comparing a C int to a C long or a float to a double...

Consider this test -- p1(number(12)) -> c1(number)
                      p2(number(12)) -> c2(number(12))

we'll compare:

o bulk insert
o singleton inserts
o bulk delete from parent (with on delete cascade)
o singleton delete from parent (with on delete cascade)

The script is:

create table p1 ( x number(12) primary key);
create table c1 ( x number references p1  on delete cascade);
create index c1_idx on c1(x);

create table p2 ( x number(12) primary key);
create table c2 ( x number(12) references p2 on delete cascade );
create index c2_idx on c2(x);

insert into p1 select rownum from all_objects;
insert into p2 select rownum from all_objects;
commit;

alter session set sql_trace=true;
insert into c1 select * from p1;
insert into c2 select * from p2;

begin
    for x in ( select * from p1 )
    loop
        for i in 1 .. 5
        loop
            insert into c1 values ( x.x );
        end loop;
        commit;
    end loop;
end;
/
begin
    for x in ( select * from p2 )
    loop
        for i in 1 .. 5
        loop
            insert into c2 values ( x.x );
        end loop;
        commit;
    end loop;
end;
/
delete from p1 where mod(x,2) = 0;
commit;

begin
    for x in ( select * from p1 )
    loop
        delete from p1 where x = x.x;
    end loop;
end;
/
commit;

delete from c2 where x < 12000;
commit;

delete from p2 where mod(x,2) = 0;
commit;

begin
    for x in ( select * from p2 )
    loop
        delete from p2 where x = x.x;
    end loop;
end;
/
commit;

And tkprof says.....

insert into c1 select * from p1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      1.64       1.74          0        242      49429       23939
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      1.65       1.75          0        242      49429       23939

insert into c2 select * from p2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      1.72       1.78          0        239      49427       23939
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      1.72       1.78          0        239      49427       23939

well, for the bulk insert, C1 and C2 are for all intents and purposes - the
same!  a difference of 0.07 cpu seconds is not meaningful here (run a couple of
times anc c2 would best c1 sometimes, c1 best c2 the others).  Not only that --
but C1 is the NUMBER type and C2 is the NUMBER(12)...

Now, onto the single row inserts:

INSERT INTO C1 VALUES ( :b1  )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute 119695     36.61      38.81          0       1205     657138      119695
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   119696     36.61      38.81          0       1205     657138      119695

********************************************************************************
INSERT INTO C2 VALUES ( :b1  )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute 119695     37.66      38.90          0       1210     657146      119695
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   119696     37.66      38.90          0       1210     657146      119695

Once again, no measurable difference (although C1 again bests C2 -- maybe
there is in fact a slight edge to being unconstrained??)

Looking at the deletes, bulk delete first:

delete from p1 where mod(x,2) = 0

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      3.29       4.41         61         40      66705       11969
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      3.29       4.41         61         40      66705       11969

delete from p2 where mod(x,2) = 0

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      3.55       5.04        276         40      66699       11969
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      3.55       5.04        276         40      66699       11969

well, no smoking gun there... looking at single row deletes:

DELETE FROM P1 WHERE X = :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute  11970      8.24       8.08          0      23956     818445       11970
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    11971      8.24       8.08          0      23956     818445       11970

********************************************************************************
DELETE FROM P2 WHERE X = :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute  11970      8.09       8.65          0      23956     818471       11970
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    11971      8.10       8.65          0      23956     818471       11970

Finally -- C2 bested C1 -- but not by much and it is totally arbitrary

So, my suggestions:

a) benchmark these things, get hard and fast numbers before you go about doing
long painful changes

b) stop doing your conversion -- it is quite simply "not worth it"

February 02, 2003 Trinh -- Thanks for the question regarding
      "select count(*) vs. select *", version 9.2

      You Asked
Could you explain why "select *" is more expensive than "select count(*)"?

TOOLMAN@JNAS> analyze table t1 compute statistics
  2  for table
  3  for all indexes
  4   for all indexed columns;

Table analyzed.

TOOLMAN@JNAS> select count(*) from t1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=407 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'T1' (Cost=407 Card=479216)

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

TOOLMAN@JNAS> select * from t1;

479216 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=407 Card=479216 Byte
          s=46004736)

   1    0   TABLE ACCESS (FULL) OF 'T1' (Cost=407 Card=479216 Bytes=46
          004736)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      53793  consistent gets
       6165  physical reads
          0  redo size
   57847036  bytes sent via SQL*Net to client
    5319661  bytes received via SQL*Net from client
      47923  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     479216  rows processed

TOOLMAN@JNAS>

      and we said...
well, look at:

        373  bytes sent via SQL*Net to client
 57,847,036  bytes sent via SQL*Net to client

that is pretty telling -- 57meg vs 373bytes.

Also, the number of roundtrips:

          2  SQL*Net roundtrips to/from client
      47923  SQL*Net roundtrips to/from client

that should be obvious as well -- it takes alot more to return 479,219 rows then
it does 1 right....

But, your question is probably about the consistent gets:

       6542  consistent gets
      53793  consistent gets

right -- why does the count(*) do about 1/8th the logical IO?  That centers
around how Oracle processes the blocks and how many round trips you made.  It
would appear based on your round trips that your ARRAYSIZE in sqlplus was 10
(rows fetched/round trips).  I'm also going to guess that you have about 73
rows/block on average (the count(*) logical IO's divided by number of rows).

When we did the count(*) -- we did it in a single call -- we read each block
once -- counted the rows and went onto the next block.  So, there are about
6,542 blocks in the table and we read each one -- one by one -- in its entirety
and returned the answer.

When we did the select *, we did it in 47,923 calls.  It is interesting to note
that if we do:

ops$tkyte@ORA817DEV> select 53793-47923 from dual;

53793-47923
-----------
       5870

that number is pretty close to the consistent gets for the count(*).  Where did
the other 47k consistent gets come from?  It is the way the array fetching is
processed.  We did this:

o fetch 10 rows -> goto server -> get block 1 (1 lio) -> get 10 rows -> return
o fetch 10 more -> goto server -> get block 1 (2 lio) -> get 10 rows -> return
o ......                          get block 1 (3 lio) .....

you see -- to get the first 70 rows -- 10 at a time -- required 7 lios!  Each
time we went to the server to get the next 10, we got the block again to get
them.  Now, when we get to the rows that span blocks:

o fetching 60..70                  get block 1 (7 lio) ....
o fetching 70..80                  get block 1 (8 lio) and get block 2(9 lio)
....

we had to do 2 lio's on that one since 3 of the rows would have been on block 1
and the next 7 on block 2.

Do that 47k times (for the round trips) and that accounts for the disparity yous
see.

Consider:

ops$tkyte@ORA817DEV> create table t as select * from all_objects;
Table created.

ops$tkyte@ORA817DEV> set autotrace traceonly
ops$tkyte@ORA817DEV> select * from t;
23947 rows selected.

Statistics
----------------------------------------------------------
       1911  consistent gets
       1598  SQL*Net roundtrips to/from client
      23947  rows processed

ops$tkyte@ORA817DEV> set arraysize 100
ops$tkyte@ORA817DEV> select * from t;
23947 rows selected.

Statistics
----------------------------------------------------------
        572  consistent gets
        241  SQL*Net roundtrips to/from client
      23947  rows processed

ops$tkyte@ORA817DEV> set arraysize 1000
ops$tkyte@ORA817DEV> select * from t;
23947 rows selected.

Statistics
----------------------------------------------------------
        357  consistent gets
         25  SQL*Net roundtrips to/from client
      23947  rows processed

ops$tkyte@ORA817DEV> set arraysize 5000
ops$tkyte@ORA817DEV> select * from t;
23947 rows selected.

Statistics
----------------------------------------------------------
        338  consistent gets
          6  SQL*Net roundtrips to/from client
      23947  rows processed

ops$tkyte@ORA817DEV> select count(*) from t;

Statistics
----------------------------------------------------------
        333  consistent gets
          2  SQL*Net roundtrips to/from client
          1  rows processed

Review &
      Followup

      Rating: 5
More mysteries
      revealed  February 01, 2003
Reviewer:  Jerry
       from Washington, DC USA
Made things very clear for me.  I am still confused about the 5528 physical
reads on the count(*).  What does that represent?  Is that in addition to the
consistent gets?

          Followup:

some of your data was not found in the buffer cache for the count(*), it was
read from disk.

same with the select * -- it did physical IO to read the data from disk to the
buffer cache.

Each of those PIO's were LIO's as well -- we do a physical IO to get it into the
buffer cache, then you do LIO to get it out and process it.

      Rating: 5
Confused about
      accounting of LIO/PIO  February 01, 2003
Reviewer:
      Jerry  from Washington, DC USA
I guess I always thought LIO was accounted for separately from PIO.

Are you saying that if the query is run again, then PIO could go to 0, but LIO
would remain at 6K?

I guess I was thinking that the PIO would become LIO the 2nd time around after
caching, and would then be 11k.  Sounds like PIO is a subset of the LIO.

          Followup:

yup -- LIO would stay more or less constant (in a single user system) and PIO's
will go up and down.

MOST PIO is a subset of LIO.  MOST PIO's turn immediately into LIOs

But then, there are direct reads from temp -- which do not.  they are PIO's that
bypass the buffer cache all together and never get to be a LIO.

      Rating: 5
Tom, you really
      look like from "ORACLE"!!!  February 01, 2003

Reviewer:  Trinh  from Atlanta

      Rating: 5
Reads from temp
      bypass the buffer cache?  February 02, 2003

Reviewer:  Mario  from Rome, Europe
Hi Tom, in the second follow-up you have said:

"But then, there are direct reads from temp --
which do not. they are PIO's that bypass the
buffer cache all together and never get to be a LIO."

 that is the reads from a temporary segment
bypass the buffer cache: Is it true even if
you have a datafile for your temporary
tablespace (instead of a tempfile)? Could
you kindly add anything?

Thank you very much

          Followup:

direct reads/writes to/from temp bypass the buffer cache regardless of the file
type.  there is no need for the buffer cache for these things.

February 03, 2003 Vince Crismer -- Thanks for the question
      regarding "How to reset sequences?", version Oracle 8.1.5

      You Asked
Sir,

Greetings.
I would just like to know if it is possible to truncate a
sequence to reset back to its original starting number? Pls.
help me. Thank you. Merry Christmass.

- Vince Crismer C. Villena

      and we said...
There is no "alter sequence reset" but you can, by playing with
the increment by, reset it.  Consider:

ops$tkyte@DEV816> create sequence seq;
Sequence created.

ops$tkyte@DEV816> select seq.nextval from emp;

   NEXTVAL
----------
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14

14 rows selected.

ops$tkyte@DEV816> column S new_val inc;
ops$tkyte@DEV816> select seq.nextval S from dual;

         S
----------
        15

ops$tkyte@DEV816> alter sequence seq increment by -&inc minvalue
0;
old   1: alter sequence seq increment by -&inc minvalue 0
new   1: alter sequence seq increment by -        15 minvalue 0

Sequence altered.

ops$tkyte@DEV816> select seq.nextval S from dual;

         S
----------
         0

ops$tkyte@DEV816> alter sequence seq increment by 1;

Sequence altered.

ops$tkyte@DEV816>
ops$tkyte@DEV816> select seq.nextval from dual;

   NEXTVAL
----------
         1

So, by finding out the current value of the sequence and
altering the increment by to be negative that number and
selecting the sequence once -- we can reset it.  Just beware
that if others are using the sequence during this time - they
(or you) may get

ORA-08004: sequence SEQ.NEXTVAL goes below MINVALUE and cannot
be instantiated

until you set the sequence increment back to +1.

This would be preferred to dropping and recreating the sequence
which would invalidate any dependent objects (such as
triggers/stored procedures and so on)
Review &
      Followup

      Rating:
      5
Sequences  June 08, 2002
Reviewer:
      Monz  from Reston, USA
Tom:

1.  How would dropping the sequence invalidate objects.  I have several
procedures that do inserts and use sequences and was thinking of dropping and
recreating sometimes. Would I get an error?  I think I tried it before without a
problem?  I guess you would if you run the procedure and the sequence was not
there?

2.  Let us say I want to reset a sequence dynamically based on the maximum
number in a column. If the max number is 20, I want start with 21.

Can I use a variable to do the increment like:

select max(col) into v_seq from table;

column S new_val inc;
select seq.nextval S from dual;
alter sequence seq increment by -&inc minvalue 0;
alter sequence seq increment by v_seq;
alter sequence seq increment by 1;

3.  Problem is that on one web form I use a seqeunce to show user a unique order
number that he wants to see before saving record. If user cancels then I have a
gap in my orders saved.  Is there a way around it?

          Followup:

1) You have a procedure.
   This procedure does an insert.
   This insert references a sequence.

Hence, the procedure is dependent on the sequence.  If you drop the sequence --
the procedure will go invalid since an object it depends on no longer exists.
If you run the procedure right now, it'll fail to compile.  If you create the
sequence and then run the procedure, it'll successfully recompile itself
automagically.

So, if you drop and recreate, it'll just invalidate which is a waste of your
computer resources but will not result in an error.

2) No, no it won't.  You never select from it after the alters, hence the
numbers will never change.

You could of course do this in two alters as follows (simple math going on
here):

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t (x int);
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values (
dbms_random.value(1,99999999) );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

         X
----------
  68637521

ops$tkyte@ORA817DEV.US.ORACLE.COM> column inc new_val INC;

ops$tkyte@ORA817DEV.US.ORACLE.COM> select max_x-s.nextval+1 inc
  2    from ( select max(x) max_x from t );

       INC
----------
  68637399

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter sequence s increment by &inc;
old   1: alter sequence s increment by &inc
new   1: alter sequence s increment by   68637399

Sequence altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select s.nextval from dual;

   NEXTVAL
----------
  68637522

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter sequence s increment by 1;
Sequence altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select s.nextval from dual;

   NEXTVAL
----------
  68637523

ops$tkyte@ORA817DEV.US.ORACLE.COM> update t set x = 2;
1 row updated.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select max_x-s.nextval+1 inc
  2    from ( select max(x) max_x from t );

       INC
----------
 -68637521

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter sequence s increment by &inc;
old   1: alter sequence s increment by &inc
new   1: alter sequence s increment by  -68637521

Sequence altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select s.nextval from dual;

   NEXTVAL
----------
         3

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter sequence s increment by 1;

Sequence altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select s.nextval from dual;

   NEXTVAL
----------
         4

3) the way around this is TO NOT WORRY ABOUT GAPS AS GAPS ARE INEVITABLE IN A
SEQUENCE.

There are three things I am sure of in life:

o Death
o Taxes
o Gaps in my sequences

      Rating: 5
Neat
      idea  June 09, 2002
Reviewer:  Doug
       from Danbury, CT USA
I always drop and recreate.  This is an interesting idea to just increment
backwards.

      Rating: 4
6 months
      early!  June 10, 2002
Reviewer:  Santa's
      helper  from like a rat in a cage I am trapped inside the cubicle

Didn't Christmas used to come in December and not June? :-)

      Rating: 2
follow
      up  June 28, 2002
Reviewer:  ramesh Konda
       from Minnesota
I tried the following and ran into errors. Please advise.
  1* alter sequence s_cust_mgmt_group_cmg_id increment by -&inc minvalue
SQL> /
Enter value for inc: 0
old   1: alter sequence s_cust_mgmt_group_cmg_id increment by -&inc minvalue
new   1: alter sequence s_cust_mgmt_group_cmg_id increment by -0 minvalue
alter sequence s_cust_mgmt_group_cmg_id increment by -0 minvalue
                                                                *
ERROR at line 1:
ORA-01722: invalid number

SQL> alter sequence s_cust_mgmt_group_cmg_id increment by -&inc minvalue 0
  2  /
Enter value for inc: 0
old   1: alter sequence s_cust_mgmt_group_cmg_id increment by -&inc minvalue 0
new   1: alter sequence s_cust_mgmt_group_cmg_id increment by -0 minvalue 0
alter sequence s_cust_mgmt_group_cmg_id increment by -0 minvalue 0
*
ERROR at line 1:
ORA-04002: INCREMENT must be a non-zero integer

SQL> ed
Wrote file afiedt.buf

  1* alter sequence s_cust_mgmt_group_cmg_id increment by -&inc minvalue 1
SQL> /
Enter value for inc: 1
old   1: alter sequence s_cust_mgmt_group_cmg_id increment by -&inc minvalue 1
new   1: alter sequence s_cust_mgmt_group_cmg_id increment by -1 minvalue 1

Sequence altered.

SQL> select s_cust_mgmt_group_cmg_id.nextval from dual;

   NEXTVAL
----------
        23

****** It did not set the sequence to initial value ******

          Followup:

you missed a zero

old   1: alter sequence s_cust_mgmt_group_cmg_id increment by -&inc minvalue
new   1: alter sequence s_cust_mgmt_group_cmg_id increment by -0 minvalue
alter sequence s_cust_mgmt_group_cmg_id increment by -0 minvalue
                                                                *
ERROR at line 1:
ORA-01722: invalid number

after MINVALUE.

and then you tried to set the increment to 0, which just isn't allowed (as
evidenced by the ora-4002 error)

You need to read the example above again (slower).  We get the next val, change
the increment to be that (negative), select once to get it to zero and then
reset the increment to +1.  You didn't do any of that.

Just use the code I provided:

column S new_val inc;
select seq.nextval S from dual;
alter sequence seq increment by -&inc minvalue 0;
select seq.nextval S from dual;
alter sequence seq increment by 1;
select seq.nextval from dual;

do that, as is, and it'll work as advertised.

      Rating: 5
How to change
      START WITH for a sequence  February 02, 2003

Reviewer:  Ajeet  from cincinnati
Tom- based on the above disucssion --can we change the start with value of
sequence too.
?

          Followup:

just reset the value to be whatever "start with" value you want.

say the sequence was created:

create sequence s;

and you are currently at 1,000 and want the sequence to now "start with 50",
just use -950 instead of -1000 when you change the increment by to "reset" it.

      Rating:
      5
  June 25, 2003
Reviewer:
      A reader

February 03, 2003 Andy -- Thanks for the question regarding "Is it
      possible to call a Stored Procedure with a BLOB parameter from Pro*C",
      version 8.1.7

      You Asked
Tom,

thanks in advance for your help.

I have a number of stored procedures that have BLOB and CLOB parameters. I was
wondering if there is a way to call these Stored Procedures in Pro*C and pass in
a host variable for the BLOB or CLOB parameter.

I have looked around and can find no obvious way, my guess is that the Anonymous
block that calls the Stored Procedure could declare a BLOB variable and then use
DBMS_LOB to open a file etc to create a BLOB that has binary data present that
could be passed to the Stored Procedure.

An alternative to this may be to wrap the Stored Procedure by another Stored
Procedure that takes a file name and does the necessary DBMS_LOB operations.

When these Stored Procedures are called via ADO in VB it appears that a a stream
is associated with the LOB paramter, is there something similar in Pro*C?

Thanks again

Andy

      and we said...
hmm, there is an application developers guide for lobs just for proc

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/index.htm
additionally -- an entire chapter on it:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76942/pc_16lob.htm#997954
in the proc guides??

You have "exec sql LOB" directives in proc....

Review &
      Followup

      Rating:
      5
Thanks  February 03, 2003
Reviewer:
      Andy  from United kingdom
Thanks for the pointer don't quite know how I missed that.

February 03, 2003 Karthik -- Thanks for the question regarding
      "Complex Query!", version 8.1.7

      You Asked
I have a table with two columns which has data like this:

C1            C2
Ax            ax
Ax            ay
Ax            az
Bs            ba
Bs            bb
Bs            bc
Bs            bd
Cv            c1
Cv            c2
Cv            c3
Cv            c4
Da            dw

I need a query which gives me an output like this:

C1        C1_Index     C2     C2_Index_for_C1
Ax        1            ax        1
Ax        1            ay        2
Ax        1            az        3
Bs        2            ba        1
Bs        2            bb        2
Bs        2            bc        3
Bs        2            bd        4
Cv        3            c1        1
Cv        3            c2        2
Cv        3            c3        3
Cv        3            c4        4
Da        4            dw        1

Is there a way to form a query which gives such an output.

      and we said...
Analytics rock and they roll once again to make the complex trivial:

ops$tkyte@ORA920> select c1, dense_rank() over ( order by c1 ) c1_index,
  2     c2, row_number() over ( partition by c1 order by c2 ) c2_index_for_c1
  3    from t
  4   order by c1, c2
  5  /

C1   C1_INDEX C2 C2_INDEX_FOR_C1
-- ---------- -- ---------------
Ax          1 ax               1
Ax          1 ay               2
Ax          1 az               3
Bs          2 ba               1
Bs          2 bb               2
Bs          2 bc               3
Bs          2 bd               4
Cv          3 c1               1
Cv          3 c2               2
Cv          3 c3               3
Cv          3 c4               4
Da          4 dw               1

12 rows selected.

Review &
      Followup

      Rating:
      5
Great!!!!  February 03, 2003
Reviewer:
      Karthik Chakrapani  from Chennai, India
Thanks a lot....it works just great. You are great.

February 03, 2003 -- Thanks for the question regarding "Partitioned
      tables rowcount automated script", version 8.1.7.3

      You Asked
Tom,

Can u please provide me the script to get the each partition rowcount for all
tables that are partitioned, so that i can execute that script as
@get_partitioned_rows.sql.

Note: Each partition rowcount group by table_name

Thanks for ur cooperation.

      and we said...
why not just analyze and use num_rows in user_tab_partitions

tons faster

set heading off
set feedback off
set lineszie 80
set trimspool on
spool tmp.sql
prompt set echo on
select 'select count(*) from ' || table_name ||
       ' partition(' || partition_name || ');'
  from user_tab_partitions
/
spool off
@tmp

would do that in a script tho.
Review &
      Followup

      Rating: 3
But it may not
      be accurate?  February 03, 2003
Reviewer:
      A reader

          Followup:

what?  i provided two answers:

use num_rows -- faster, accurate ENOUGH for most things

and here is a script that'll get you the counts (that immediately after running
is not accurate either eh)

February 03, 2003 Damon -- Thanks for the question regarding
      "changing primary key?", version 9iAS R1

      You Asked
Hi Tom,

Sorry for the vague question...when reading your site I often think that you
must be a mind reader to answer as many questions as you do from so many
different people...

Anyway, here goes.

We have a standard 3-tier J2EE environment:
Apache/Tomcat <=> Oracle 9iAS (BC4J) <=> Oracle 8.1.7

Our java developers have requested that we place an UPDATE_COUNT NUMBER(10)
column on each of our tables to maintain persistence between their entity beans
and our tables.  UPDATE_COUNT will increment each time an entity bean updates a
row in the table.  If the UPDATE_COUNT in the table is not equal to the value of
the bean's updateCount property the transaction is rolled back.  This is to
reduce/eliminate the chance of lost updates.

So my question...is this a good strategy?  I would have thought that container
managed persistence would mean that the container would manage the persistence
of the data?  Perhaps my understanding of CMP is lacking.

Could you clear this up for me?  If my question is still to vague I'll RTFM some
more and try posting again when I can ask produce a better question.

Thanks for all your help.  I use your site every day and recommend your book to
anyone who is doing anything with oracle.

Damon

      and we said...
that is called OPTIMISTIC locking and in a stateless environment is very very
common.

It is very good that they are thinking about it -- many times, java (C, VB, many
languages can go here) programmers don't even consider database things, this is
a good sign indeed!

the problem with CMP is it tries to do it all outside the database, all by
itself.  Hence, if some other nefarious application tries to come along and play
with the data -- the beans will just wipe it out.  It is a very good sign they
are thinking along these lines.  It'll let the bean stuff peacefully and safely
coexist with existing code and any new code that isn't a bean.
Review &
      Followup

      Rating: 5
Excellent as
      usual  January 28, 2003
Reviewer:  Damon
      Torgerson  from Victoria, BC Canada
Thanks Tom.

      Rating: 3
Never use
      Beans. Why?  January 29, 2003
Reviewer:
      Tony  from India
Tom, You never recommend beans stuff. Can you elaborate why Beans shouldn't be
used?. In our company, Java developers always talk about beans and claim it is a
wonderful stuff.

          Followup:

I never say never.

Look at this question and my response and my pleasure that the java developers
are actually thinking about data integrity and making sure the data is still
accessible by other things.

Then, you'll have an insight into my perspective.

When I started programming, the rage was "modular programming", "structured
programing", then portable programming, then object oriented programming, then
client server programming, then three tier programming (which when you peel back
the layers is -- gasp MODULAR programming all over again ;)

programming paradigms change.

the data was always there and had to be used by each "paradigm".

If a paradigm tied the data down (many green screen apps for example where the
data, logic, security -- everything was in the screen code itself, preventing
ANYTHING from touching the data except through that screen) -- you lose.

If a paradigm doesn't tie the data down, it is a good one.

It is not that I don't recommend beans, it is that I see people seeing this
current paradigm as being the "last one", and killing accessibility to data.

One of my favorite questions recently was this one from a java shop:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6692296628899
that helps see my perspective on this.  the java programmers -- using the
coolest java stuff -- serialized their data into the database in a blob.  Guess
what -- those nasty end users actually had the nerve to ask for their data via
some other tool!

      Rating: 5
that is called
      OPTIMISTIC locking for Child Tables ...  January 31, 2003

Reviewer:  pasko
Hi Tom ,

speaking of OPTIMISTIC locking (WEB) , i would also like to implement that for
our small web GUI which accesses a few Oracle tables .

We have decided to use a Timestamp Column ( instead of just a number )  to cater
for lost updates.

I was just wondering how to implement this Logic for tables in a parent/Child
relationships...i mean , having foreign keys.

Should the parent and child tables share a common Timestamp ? /Have separate
Timestamp ? /   or may be i should write a trigger for Child tables to update
the Timestamp of the parent  table whenever theres an update on the Child ?

Thanks .

          Followup:

it is done at the row level.

If I update parent and you update child-1 and they update child-2 -- that is
just dandy.  no problems there.

      Rating: 5
OPTIMISTIC
      locking and java  January 31, 2003
Reviewer:
      A reader
Hi tom,

   Do we have to check in all applications for this?
I mean we have 3-tier jsp-tomcat-ora817.

  but I provide them stored procedure service to update,
insert,or delete so from java they call the stored procs.
and the dml operation is done via stored proc.

 what exactly OPTIMISTIC locking  is ? when should I look for it ? and what is
CMP ?

please explain !

          Followup:

it is making sure the data did not change between the time you READ IT OUT and
the time you MODIFY IT.  that is optimistic locking, you are optimistic no one
will change it while you have it -- thus wasting your time.

if you have my book "Expert one on one Oracle" -- i go over it in some detail.

If you have all of the actual inserts/updates/deletes in a stored procedure and
everyone calls that -- you can do optimistic locking simply by fixing them.
It'll fix ALL of the apps.

      Rating:
      4
definitions...  January 31, 2003
Reviewer:
      Damon Torgerson  from Victoria, BC Canada
CMP - Container Managed Persistence

J2EE concept that ensures manages transactions between EJBeans (usually entity
beans) and the database.

However, as Tom pointed out either in his book or on the site or both, you
probably shouldn't rely on the application managing persistence/transactions as
there is a good chance that a new application platform will be coming just
around the corner (i.e., .Net) which doesn't have the features necessary to
ensure your data maintains integrity.

I highly recommend getting his book.

      Rating: 5
is it same as
      keeping for current of clause ?  January 31, 2003

Reviewer:  A reader
Thanx for your reply is it same as
keeping

is it same as keeping for current of clause ?

thanks,

          Followup:

if you are using "where current of", you did a select for update (locked the
rows) and "own them".

that is pessimistic locking...

      Rating: 5
OPTIMISTIC
      locking   February 03, 2003
Reviewer:
      A reader
"if you are using "where current of", that is pessimistic locking...
"

  can you just show me with the small simple example how can I implement
OPTIMISTIC locking then ?

Thanks,

          Followup:

search for

optimistic

on this site for a couple of discussions on this topic.  You can use things like
the owa_opt_lock package and compute a checksum on a row when you fetch it out
-- then when you update you would:

  update t set ...
    where primary_key = ....
      and CHECKSUM=CHECKSUM_YOU_FETCHED_OUT

  if ( sql%rowcount = 0 ) then you lose, row was changed

to make sure the row did not change.

You can use the BEFORE images -- keep the old and new values and do the update:

update t set columns = NEW_VALUES
 where columns = OLD_VALUES

if ( sql%rowcount = 0 ) then you lose....

you can use a "timestamp" type of column - a number you fetch out with the row
and increment upon update:

update t set columns= NEW_VALUES, time_stamp = time_stamp+1
 where primary_key = ....
  and time_stamp = VALUE_YOU_FETCHED_OUT

if ( sql%rowcount = 0 ) then you lose....

February 03, 2003 David -- Thanks for the question regarding
      "Simple Trigger Strategy", version 8.1.6

      You Asked
Consider a simple Invoice table with Sub_Total, Sales_tax & Total cols.
Would you rec using a trigger on insert to calc sales tax or leave it on the app
level?

      and we said...
sounds like an appropriate use of a trigger

begin
  if ( :new.sales_tax is null )
  then
     :new.sales_tax := compute_tax( ..... );
  end if;
end;

that way - if the APP needs to override the sales_tax it can, or if you load
data in from an external source where it was already done...
February 03, 2003 -- Thanks for the question regarding "Why does it
      takes almost same amount of time to roll back a transaction", version
8.1.7

      You Asked
Hi Tom

Here is my doubt, when I insert 1 million records it takes 45 mins, if I
rollback the inserts why does the rollback process takes almost same amount of
time.  When you rollback a transaction why cant the transactions be marked bad
and the next transaction can over write the bad once.

Thanks

      and we said...
it has to be undone -- things are not done by "marking a transaction bad".  The
insert not only modified a table, but indexes -- it may have had triggers fire
to update other data, delete some stuff.

All in all it is complex, we are optimized to make COMMITS instantaneous (since
most all transactions end happily with a commit). Rolling back is something that
has to take all of the work you did and undo it all.
Review &
      Followup

      Rating:
      5
  February 03, 2003
Reviewer:
      A reader

February 03, 2003 Aldo -- Thanks for the question regarding "FGCA
      for 3rd party applications ", version 9.2.0

      You Asked
Hi,

I have a peoplesoft application that connects to an oracle database using only
one user. I need to apply fine grained control access to some tables since the
application is shared by two departments. I CANNOT modify the application. What
is the way to apply FGCA? Can I use the network information to identify the
users? I've been to Oracle World and I heard many times that with Oracle 9.2.
now this can be done but all the solutions I found required to modify the
application so it sends user information to the database and I do not have
access to the code.

Thanks!!!

      and we said...
You would have to ask peoplesoft to help you do this.

If you even think about turning on something at the database level without their

o help
o consent
o input

you would be going down a path from which you will never recover.  YOU cannot
implement security for peoplesoft, only peoplesoft can do that.

Peoplesoft does most of the work in their own middle tier -- way way outside of
the database.  You cannot do something like this without THEIR help.

Review &
      Followup

      Rating: 2
I know
      that  February 03, 2003
Reviewer:  Aldo
      Bravo  from Ottawa, ON, Canada
I know that but this is not the normal peoplesoft application. It is called
online marketing, which they bought from Annuncio Software. Peoplesoft doesn't
support their own security module until next release ... that is why I was asked
to analyze options on the database side.
So I should say then: No, I cannot do anything about it to my boss.

          Followup:

yes, that is exactly the ONLY thing you can say.

Look -- you bought a packaged application.

It works the way it works.

It cannot work any other way.

You live within the confines of what the packaged application does -- nothing
more, nothing less.  You cannot silently slip in a feature behind the scenes
without the application vendor support, consent and willingness to support you
when you hit a problem.

Can you imagine:

ring ring: hello, peoplesoft support?  I'm having a problem with your
application.  Well, after we put this FGAC thing on it <click -- dialtone>...
Hello?  Anyone there?

      Rating: 5
What Peoplesoft
      version and 2-tier or 3-tier  February 04, 2003

Reviewer:  Wolfgang Breitling  from Calgary, Canada

What Peoplesoft version is it and are you using it in 2-tier or 3-tier mode?
If it is 7.5 and you are using it in 2-tier mode you could use the accessid to
separate/distinguish between the users of the two departments. Use of different
accessids is of course supported by Peoplesoft.
We are using it to give different users different profiles which have different
resource limits: general users have a 3 minute limit on sql they can run wile
power users have a higher limit.

February 03, 2003 Daniel -- Thanks for the question regarding
      "Changing Index from Non-Unique to Unique", version 8.1.7

      You Asked
Is there a better, more efficient way to change an index from non-unique to
unique rather than dropping the non-unique one and recreating it as unique?  We
have a table (450M rows) that had duplicate records that we cleaned up.  It took
12+ hours to build the non-unique index and we would like to avoid having to
take another 12 hours to rebuild it as unique.  Thanks Tom.

      and we said...
parallel - no logging.

You don't have to build it as unique however to have uniqueness enforced.

Just alter table t add constraint x_unique unique(x) novalidate;

the unique constraint will just "kidnap" the existing index.  novalidate can
be used if you are SURE the existing data is "ok", it'll go faster.

Here is an example using a 1.5million row table:

big_table@ORA817DEV> select count(*), count(distinct id) from big_table;

  COUNT(*) COUNT(DISTINCTID)
---------- -----------------
   1576192           1576192
big_table@ORA817DEV> create index big_table_idx on big_table(id);
big_table@ORA817DEV> set timing on
big_table@ORA817DEV> set feedback on
big_table@ORA817DEV> alter table big_table add constraint id_unique unique(id)
NOVALIDATE;

Table altered.

Elapsed: 00:00:00.05

big_table@ORA817DEV> insert into big_table select * from big_table where rownum
= 1;
insert into big_table select * from big_table where rownum = 1
*
ERROR at line 1:
ORA-00001: unique constraint (BIG_TABLE.ID_UNIQUE) violated

February 03, 2003 David -- Thanks for the question regarding
      "Selective Commit", version 9.2

      You Asked
We are running a huge PL/SQL package that validates our incoming data.  The
package calls several other packages and procedures.  Data is being inserted
into many tables throughout the process.  If we encounter bad data that results
in a terminal error, we don't want to commit our data.  Instead, we want all of
the incoming data to be rolled back.  That's no problem.  But, our error
messages that we have been writing to our error log table get rolled back, too.
We need the error log data to tell us what went wrong.  Is there any way we can
commit the data in the error log table only and roll back everything else?  Yes,
we could visit each table and delete the unwanted data by deleting all records
since a certain record number or date/time stamp, but we were wondering if there
was a simpler method.  Ideas?  Thanks.

      and we said...
write your "error log" procedure as an autonomous transaction:

http://asktom.oracle.com/~tkyte/autonomous/index.html

Review &
      Followup

      Rating:
      3
Rollback  February 03, 2003
Reviewer:
      darryl
How does autonomous transaction compare well to the idea of having savepoints
and rolling back to that savepoint in this scenario?

          Followup:

it would be easier with autonomous transactions.  You log it and the row is
there -- no one can accidently "roll it back".

You could do it with savepoints but it requires careful coordination and nesting
in order to work.  given that

a) it is existing code
b) in lots of other packages

trying to use savepoints would result in a spaghetti like piece of work with
everything innertwined together.

February 03, 2003 Yee -- Thanks for the question regarding
      "Multiple stored procedures", version 9.0.1

      You Asked
I have created a series of procedures for update and I have to run them in order
daily. I am wondering how can I automate them, so I can just call one script to
execute them. If any of them fail, I have to rollback all of them. Here are some
of my procedures:

PROCEDURE 100
IS
BEGIN
        UPDATE mib.mstr_programwork set
        cancel_complete_reason_cd = 'ERRLD',
        status = 'CANCELBC',
               status_update_date = SYSDATE
 WHERE MIB.MSTR_PROGRAMWORK.MTR_ID IN
              (Select b.mtr_id
        From MIB.V_MSTR_PROGRAMWORK_GAS b,
                    MIB.V_MSTR_MTR_GAS c
        WHERE b.MTR_ID = c.MTR_ID(+)
               AND c.mtr_id is null
        AND b.status = 'UNASSIGNED');
END;

PROCEDURE 200
IS
BEGIN
UPDATE mib.mstr_programwork b
SET b.fa_stat_flg = ' ', b.status_update_date = SYSDATE,
    b.STATUS = 'UNASSIGNED'
WHERE
     exists(SELECT c.*
                FROM mib.v_mstr_field_act_info c
                WHERE
                   b.sp_id = c.fa_sp_id
                AND
                   b.fa_type_cd = c.fa_type_cd
                AND
                   b.create_date - c.extract_dt < 0
                AND
                   c.fa_seq_nbr = '1'
                AND
                   c.fa_stat_flg = 'X'
          )
        AND b.status = 'PENDING'

TEXT
------------------------------------------------------------------------
        AND b.rec_id = 'DD';
END;

      and we said...
just

begin
   p1;
   p2;
   p3;
   p4;
   p5;
   commit;
end;
/

that is all -- if any fail, the statement fails (the statement begin begin/end)
and all work done by the statement is automagically undone.

ops$tkyte@ORA817DEV> create table t ( msg varchar2(40) );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace procedure p( p_fail boolean default false
)
  2  as
  3  begin
  4          insert into t values ( 'Hello' );
  5          if ( p_fail )
  6          then
  7                  raise program_error;
  8          end if;
  9  end;
 10  /

Procedure created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> begin
  2          p;
  3          p;
  4          p;
  5          p(true);
  6          commit;
  7  end;
  8  /
begin
*
ERROR at line 1:
ORA-06501: PL/SQL: program error
ORA-06512: at "OPS$TKYTE.P", line 7
ORA-06512: at line 5

ops$tkyte@ORA817DEV> select * from t;

no rows selected

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> begin
  2          p;
  3          p;
  4          p;
  5          p(false);
  6          commit;
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> select * from t;

MSG
----------------------------------------
Hello
Hello
Hello
Hello

when p fails, nothing sticks around.  when p succeeds, the data is there.

Review &
      Followup

      Rating:
      4
  February 03, 2003
Reviewer:  YEE
       from san francisco
Thank you. so i will just write another procedure to call all procedures like
below?
Create or replace procedure all
as
begin
    P1;
    P2;
    P3;
    P4;
    P5;
exception others then
          RAISE;
END;

          Followup:

you will leave the exception block out -- the ONLY time you want an exception
block is when you expect an exception, can deal with it, recover from it and
move on.

An exception block like yours would only hide from the caller where the REAL
error was!  if p3 blew up, and you caught it just to re-raise it - NO ONE would
ever know p3 blew up.

You can use a procedure if you like, or just an anonymous block as I
demonstrated, your call.

February 03, 2003 Scott -- Thanks for the question regarding
      "Dynamically vary the number of columns in a query", version 8.1.7

      You Asked
G'day Tom

I have a question regarding layout of a query based on the data within a table.
Consider a simplified table as:
DATE
FILENAME
DATA_TYPE
DATA_COUNT

Which logs the number of records for each particular type of data in a filename.
eg:
FILE1   WO   3
FILE1   WT   5
FILE2   WO   1
...
The variable here could be the data_type (this actually presents a type of
letter in our application, and new letter types could evolve).

If we had a query that displayed the following results:
DATE   LOG    WO   WT   Total
...                     z
      Totals: x    y

In my current understanding of how to query this data, there would be a static
number of columns. Adding a new data_type would require modifying the
query/report.

Is there a way to make this query dynamic enough to cater for an expanding
amount of data_types?
I know it would be possible to use dynamic sql (for eg - the owa_sylk package
you've demonstrated in this website), but I'm curious to know whether it could
be done in a static SQL statement (varying number of columns)?

Thanks Tom.

      and we said...
static sql by definition has a FIXED NUMBER of columns.

You can use some more "obscure" column types such as

o CURSOR types -- return a list of columns/row
o varray/nested table types -- returns an array or table of values/row

but if you want them to be columns -- you will be using dynamic sql.  If you are
interested, in my chapter on Analytics in "Expert one on one Oracle", I have a
stored procedure there that does just this -- returning a ref cursor.
Review &
      Followup

      Rating:
      3
pivots?  February 03, 2003
Reviewer:
      Scott  from Perth, Western Australia
G'day

After some further reading, would investigating pivot queries be of any use?
Could it apply in this case?

Thanks.

          Followup:

A pivot query would have to be dynamically written -- as the number of columns
change

if the number of columns were fixed - a static pivot query would work just fine.

The example I suggested in my book dynamically writes a pivot query!  You tell
it the dimension to pivot on -- it finds the distinct values and creates a pivot
query based on them

February 03, 2003 Arun -- Thanks for the question regarding "Are
      private synonyms bad for performance?", version 8.1.7

      You Asked
Tom
I have all tables in schema x and all users connect to the database as user y. I
have created private synonyms owned by user y for every table owned by x. As a
result, instead of select * from x.t1, users just have to type select * from t1
where t1 is a synonym for x.t1. Would this lead to poor performance? There are
about 150 tables.
Thanks

      and we said...
Private synonyms are much less evil then public.

Public pollute the global namespace, lead to confusion -- that is my issue with
them.

Private -- well, they do not pollute the namespace, no confusion and no real
overhead.

Review &
      Followup

      Rating:
      5
  January 29, 2003
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
I agree. We share database environments among projects and there are tables with
same names in different projects with totally different business use and
structure. The public synonyms were a big headache, so we decided to switch to
private synonyms. As you said, they are clean and contained within a schema. I
did a test case with sql_trace and tkprof. It turned out that private synonyms
took 2.5% to 4% more execution time. This doesn't seem to be a big overhead.
Let's see if I can convince client.
Thanks very much...!!

      Rating: 5
Help needed
      !!  February 01, 2003
Reviewer:  Arun Gupta
       from Harrisburg, PA USA
Tom
Help. In reply to another question "Poor performance using public synonyms"
elsewhere in your forum, you have refuted claims by Steve Adams article about
public synonyms and private synonyms being several times slower than object
access by fully qualified name. The hyperlink to the article is:

http://www.ixora.com.au/newsletter/2001_05.htm
where a partial test case is provided. This article is being quoted by the
client to ask us to drop private synonyms. However, I conducted the tests
described in the article by creating 960 one column tables, inserting one row
into each of these tables and tracing the user session using sql_trace and
tkprof. I cannot find the kind of difference between public and private synonyms
as indicated in the article. My other test cases using select statements
indicate less than 4% difference if I use private synonyms vs. no synonyms at
all.

If it is not too much trouble, can you please repeat the test case described on
IXORA site to lay the matter to rest permanently.

Another problem: Since this question is not entered in the forum, how can I see
all the responses posted? If I click on the link I got with the first answer, I
just see my question and your first answer. Is there a way to see the entire
thread?

Thanks...

          Followup:

when I click on the "followup" button and send you an email (the one you just
got), you should be seeing the WHOLE thing -- if not, please send me the email
you just recieved and I'll see why not....

Anyway, I've run his test in 9iR2 and find (as measured by v$mystat):

       Hard Parse Performance

               NO synonyms          private synonyms         public synonyms
CPU            168                  165                      171
row cache      25,920               27,840                   29,818
shared pool    27,162               29,124                   31,240
library cache  21,287               27,048                   33,213
TOTAL LATCHES  127,054              142,659                  161,192

        Soft Parse Performance

               NO synonyms          private synonyms         public synonyms
CPU            81                   83                       83
row cache      20,160               20,160                   20,218
shared pool    7,786                7,787,                   8,013
library cache  15,453               15,454                   15,852
TOTAL LATCHES  82,676               83,232                   85,708

So, I do not see the egregious results he does on the soft parse, however, I
zero in on the total latches personally -- as latched = locks = scalability
killers.

During the hard parse -- it is clear, no synonyms use 20-25% less latches,
during the soft parse, about 5% less.

But my major gripe with synonyms has never been purely performance based.  When
used JUDICIOUSLY (in small quantities), they can be good.  Hey -- I use them on
asktom -- that procedure F you see in my urls?  that's a public synonym -- but
it is the ONLY one, everything else is a qualifitied schema call.

My gripe is the namespace pollution, the confusion, the human errors it lends
itself towards -- not so much the performance -- although that is a
consideration.

Here is my test script -- should run in at last 817 and up:

spool test

set echo on

define NITERS=&1

@connect /

drop table run_stats;
create table run_stats
( runid varchar2(20), what varchar2(10), name varchar2(80), value int );
REM pause

create or replace view run_stats_report
as
select runid, what, name, diff, sum(diff) over(partition by what) tot
  from (
select b.runid, b.what, b.name, b.value-a.value diff
  from run_stats a, run_stats b
 where a.name = b.name
   and a.what = b.what
   and a.runid = 'before'
   and b.runid <> 'before'
   and b.value-a.value <> 0
       )
 order by what, diff
/
REM pause

create or replace procedure save_stats( p_str in varchar2 )
as
begin
    if (p_str = 'before')
    then
        delete from run_stats;
    end if;
    insert into run_stats (runid, what, name, value)
    select p_str, stats.*
      from ( select 'STAT', a.name, b.value
               from v$statname a, v$mystat b
              where a.statistic# = b.statistic#
              union all
             select 'LATCH', name,  gets
               from v$latch ) stats;
end;
/
REM pause

grant execute on save_stats to public;

drop user tbl_owner cascade;
begin
    for x in ( select synonym_name from dba_synonyms where synonym_name like
'SYNTEST_T%' )
    loop
        execute immediate 'drop public synonym ' || x.synonym_name;
    end loop;
end;
/
drop user u1 cascade;
drop user u2 cascade;
REM pause

grant dba to tbl_owner identified by tbl_owner;
alter user tbl_owner default tablespace assm;

grant dba to u1 identified by u1;
grant dba to u2 identified by u2;
REM pause

@connect tbl_owner/tbl_owner

begin
    for i in 1 .. &NITERS
    loop
        execute immediate 'create table syntest_t' || i || ' ( x number )';
        execute immediate 'create public synonym syntest_t' || i || ' for
syntest_t' || i;
    end loop;
end;
/
REM pause

@connect u1/u1

begin
    for i in 1 .. &NITERS
    loop
        execute immediate 'create synonym syntest_t' || i || ' for
tbl_owner.syntest_t' || i;
    end loop;
end;
/
REM pause

alter system flush shared_pool;

begin
    for i in 1 .. &NITERS
    loop
        execute immediate 'insert into syntest_t' || i || ' values (1)';
   end loop;
end;
/
REM pause

exec ops$tkyte.save_stats( 'before' );
begin
    for i in 1 .. &NITERS
    loop
        execute immediate 'insert into syntest_t' || i || ' values (0)';
    end loop;
end;
/
exec ops$tkyte.save_stats( 'after hard privsyn' );
select * from ops$tkyte.run_stats_report;
REM pause

exec ops$tkyte.save_stats( 'before' );
begin
    for i in 1 .. &NITERS
    loop
        execute immediate 'insert into syntest_t' || i || ' values (0)';
    end loop;
end;
/
exec ops$tkyte.save_stats( 'after soft privsyn' );
select * from ops$tkyte.run_stats_report;
REM pause

@connect u2/u2

alter system flush shared_pool;

begin
    for i in 1 .. &NITERS
    loop
        execute immediate 'insert into syntest_t' || i || ' values (1)';
    end loop;
end;
/
REM pause

exec ops$tkyte.save_stats( 'before' );
begin
    for i in 1 .. &NITERS
    loop
        execute immediate 'insert into syntest_t' || i || ' values (0)';
    end loop;
end;
/
exec ops$tkyte.save_stats( 'after hard pubsyn' );
select * from ops$tkyte.run_stats_report;
REM pause

exec ops$tkyte.save_stats( 'before' );
begin
    for i in 1 .. &NITERS
    loop
        execute immediate 'insert into syntest_t' || i || ' values (0)';
    end loop;
end;
/
exec ops$tkyte.save_stats( 'after soft pubsyn' );
select * from ops$tkyte.run_stats_report;
REM pause

@connect /

alter system flush shared_pool;

begin
    for i in 1 .. &NITERS
    loop
        execute immediate 'insert into tbl_owner.syntest_t' || i || ' values
(1)';
    end loop;
end;
/
REM pause

exec ops$tkyte.save_stats( 'before' );
begin
    for i in 1 .. &NITERS
    loop
        execute immediate 'insert into tbl_owner.syntest_t' || i || ' values
(0)';
    end loop;
end;
/
exec ops$tkyte.save_stats( 'after hard NOsyn' );
select * from ops$tkyte.run_stats_report;
REM pause

exec ops$tkyte.save_stats( 'before' );
begin
    for i in 1 .. &NITERS
    loop
        execute immediate 'insert into tbl_owner.syntest_t' || i || ' values
(0)';
    end loop;
end;
/
exec ops$tkyte.save_stats( 'after soft NOsyn' );
select * from ops$tkyte.run_stats_report;

spool off

      Rating: 5
Dummy comment
      to view response to follow up question  February 03, 2003

Reviewer:  A reader

      Rating:
      5
Thanks!!  February 03, 2003
Reviewer:
      Arun Gupta  from Harrisburg, PA USA
Tom
Thanks a lot...I also did some testing but nothing close to as extensive as you
have done. I also got similar results. Now when I face the development team, I
will have test results to back up the recommendation to keep private synonyms to
minimum and where absolutely necessary. We do not use public synonyms at all. I
personally do not want to make any recommendations unless backed by solid
reasoning and repeatable, substantiated numbers. Your test cases provides
precisely that kind of insight into working of Oracle. Sorry about posting the
dummy link. The reply link you sent works just fine.

Thanks again...

      Rating: 4
Concurrency and
      Users  February 04, 2003
Reviewer:  Jonathan
      Lewis  from UK
I think one of the issues that makes it hard to test the impact of
"public/private/fully qualified/become user" is the need for emulating the
correct usage.

I believe that the most significant point in Steve's article relates to the
production of increasinng numbers of objects in the library cache as the number
of users increases, and the increasing impact this has on the LENGTH OF TIME
that latches are held as the concurrency goes up.

The library cache will hold various structures relating to object definitions
(such as private synonyms, and negative dependencies for public synonyms), and
object permissions.

On a small test, with one user, you can count the latch accesses and see
significant differences that may not appear to be terribly threatening . But
with large numbers of users, the number of 'clones' of objects with the same
name in the same namespace and therefore on the same latch, goes up, and the
time taken to search a chain can become a serious threat if everyone is trying
to access some information about their verion of the 'same' object.  The scale
of the problem is order n-squared with respect to the number of different users.

February 03, 2003 Reena -- Thanks for the question regarding "IF
      statements versus using EXCEPTIONS in PL/SQL", version 8.1.7

      You Asked
Hi Tom,

I need help in deciding whether to use if statements in my PL/SQL program and
check for the validity for certain things and decide whether or not to execute a
certain code or to throw a user defined exception.

Which one is better? How do I decide?

e.g. I have to execute about 20 lines of code depending on if

plsql_tableofrecords.valid = 'y'

Should I put an if statement around the code or should I do something like

if plsql_tableofrecords.valid = 'n' then raise e_valid_false;

where e_valid_false is a user defined exception.

Thank you for your help.
Reena

      and we said...
I don't know -- is it an ERROR to have 'n' in valid?

If it is not an error -- i would not use an exception to "by pass" code -- that
means someone would have to catch and ignore the "error" elsewhere.  Not any
better then a goto!

If it is an error, then an IF statement would be inappropriate -- it would
result in nasty spaghetti code with checking of return codes, unwinding the
stack back out -- yuck.

Review &
      Followup

      Rating: 5
IF statements
      versus using EXCEPTIONS in PL/SQL  February 04, 2003

Reviewer:  Reena  from USA
Thank you for your help.  The answer was really helpful.  No, having 'n' in
valid is not an error, it just means that I don't want to do certain things on
that record, in that case.

So I guess I won't use exceptions.

Thank you!
Reena

February 04, 2003 -- Thanks for the question regarding "Query
      behaviour from time to time", version 8.1.7.4

      You Asked
hi, we have a application which measures response time by oracle using the
following logic.
Start Time Stamp ---->Execute Query------------->End Of Time Stamp
We found that  the response time captured by this occasionally goes above
threshold value.
Question:
Is the delay related to oracle response time really.
IS there a way to measure this at oracle side only except making trace on(as
this occurs randomly and the application is heavily used)

      and we said...
It could be anything between the client and the database.

It could be the client is really slow selecting the data (eg: say the client was
sqlplus -- and you ran it on NT -- with lots of other stuff going on.  the time
to paint the screen would be counted in your logic and that would take a long
time)

It could be the network is clogged.

It could be the database is overloaded.

I would write my "response time measurer" in two places.  One -- I would have
representative queries in a stored procedure -- just run, fetch and throw out
the data -- time that and insert the timings.  use dbms_job to run this every N
minutes.

The other -- in the application where it is now.

Now, you can compare them -- one in the database (no slow client, no slow
network) the other in the client.  If they are the same timing -- you know you
have an issue in the response time of the DB.  If they are different -- all you
need to figure out now is "slow client" or "slow network"

Review &
      Followup

      Rating: 4
Good review
        February 03, 2003
Reviewer:  subbu
       from Eden Prairie, MN USA
Thanks for the information. Now i can go to my network and nt admin and find out
what was happening during those time periods.
The application server runs CGI application which uses tns connection(no OCI, no
Thin, no ODBC).

          Followup:

...The application server runs CGI application which uses tns connection(no OCI,
no Thin, no ODBC).  ....

it must be running ONE of those.  tns is just networking stuff, you need an API
to talk to the database ;)

February 04, 2003 vikas -- Thanks for the question regarding "some
      blob questions", version

      You Asked
1.Can you tell the package which is used for inserting
images as BLOB datatype in a table.Also if the size of the
images
are in megha bytes.

2.Can it be possible to store the image as BLOB which already
there in flat files.

      and we said...
See the DBMS_LOB package for details.  It has functions such as
"read", "write", "substr", and so on.

See
http://technet.oracle.com/doc/server.815/a68004/toc.htm
One of the functions is dbms_lob.getlength().  It returns the
length of the lob in BYTEs.

Yes, there is an API call "dbms_lob.loadfromfile".  To load a
file from disk on the server you can code:

create table demo
( id        int primary key,
  theBlob    blob
)
/

create or replace directory my_files as
'/export/home/tkyte/public_html';

declare
    l_blob    blob;
    l_bfile    bfile;
begin
    insert into demo values ( 1, empty_blob() )
    returning theBlob into l_blob;

    l_bfile := bfilename( 'MY_FILES', 'aria.gif' );
    dbms_lob.fileopen( l_bfile );

    dbms_lob.loadfromfile( l_blob, l_bfile,
                           dbms_lob.getlength( l_bfile ) );

    dbms_lob.fileclose( l_bfile );
end;
/

Review &
      Followup

      Rating: 3
Blob
      Insertion  February 04, 2003
Reviewer:  Vikas
       from INDIA
Hi Tom,

We have images for .bmp and .gif files and we need to upload them in the
database. Can we write a procedure which could upload these files without
specifying the Create Directory command at the begining to prove that my files
are located under this directory.

Also please verify that can they be uploaded for any size or we have any
constraint on this?

Please do reply.
Thanks in anticipation

          Followup:

you can write a program to read a file, and write a blob.  You can write that
program in any language you like really.

you can use sqlldr to load them.

if you want to use dbms_lob.loadfromfile -- you must use a directory object as
that requires a bfile and bfiles only work with directory objects.

So -- yes, you can (do it without a directory object).  You would either write
code or use sqlldr.

February 04, 2003 -- Thanks for the question regarding "select
      statement for a query described below :", version 8.1.5

      You Asked
I have the following data in the table 'xyz'

table : xyz
====    ====    =======
name       type      amount
====    ====    =======
abc    a    100
xyz    b    300
abc    b    100
xyz    a    200
xyz    b    200
abc    a    100
....
====    ====    =======

I wish to have the output like this.

name    amount    type

abc    400    a
xyz    700    b

The explanation of various output columns is as below :

( amount = total amount for a name, type = the type for which we get max of sum
amount for group by name,type )

Can you please give the select statement?  ( Is it  possible to get the output
in a single sql statement? )

Thank you in advance.

      and we said...
ops$tkyte@ORA815> select name,
  2          sum(amount),
  3          substr( max( to_char(amount,'fm0000000000') || type ) , 11 ) type
  4    from (
  5  select name, type, sum(amount) amount
  6    from t
  7   group by name, type
  8             )
  9   group by name
 10  /

NAME                           SUM(AMOUNT) TY
------------------------------ ----------- --
abc                                    300 a
xyz                                    700 b

ops$tkyte@ORA815>

does that, we started with:

ops$tkyte@ORA815> select name, type, sum(amount) amount
  2    from t
  3   group by name, type
  4  /

NAME                           T     AMOUNT
------------------------------ - ----------
abc                            a        200
abc                            b        100
xyz                            a        200
xyz                            b        500

in the inline view -- then we summed amount up by name to get 300/700 (NOT
400/700 -- your math is wrong there in the example).  Additionally, we formatted
the AMOUNT from this inline view as a "sortable" string using fm0000000000 -- so
the numbers where 0000000200, 0000000100 and so on -- that way, MAX() would find
the max amount by NAME (our group by) and since we hid TYPE on the end of that
-- we can just substr it back off again....
Review &
      Followup

      Rating: 4
Analytics
      rock  February 04, 2003
Reviewer:  Urs
       from Germany
For those who feel that analytic functions rock:

SELECT DISTINCT name, FIRST_VALUE(type) OVER(PARTITION BY name ORDER BY s DESC),
total
FROM (SELECT name, type, SUM(amount) OVER(PARTITION BY name, type) s,
      SUM(amount) OVER(PARTITION BY name) total FROM xyz)

          Followup:

Oh believe me -- my first answer was analytics -- all ready with the cut and
paste and then --- detected the 815 in the version ;(

That is EXACTLY the answer I first came up with tho!

      Rating:
      5
Query  February 04, 2003
Reviewer:
      Tony  from Canada
Hi Tom,
       What will all of us(DBAs) do without your help, I have a table x with
columns c1 and c2 with following data:
C1 C2
----     --- -
         1 a
         1 b
         1 c
         2 x
         2 y
         2 z
         3 n
         3 a
         3 b
         4 s
         4 f
         4 a

Can you please help me out with a query that generates the following results:

c1
--
1
3

The selection is based on values of c2 columns i.e. I have to select distinct
values of c1 where c2 is both 'a' and 'b'

          Followup:

that one is easy ;)

ops$tkyte@ORA920> select c1
  2    from t
  3   where c2 in ( 'a', 'b' )
  4   group by c1
  5  having count(distinct c2) = 2;

        C1
----------
         1
         3

      Rating: 5
select
      statement for a query   February 05, 2003

Reviewer:  jjcb  from India
Fantastic!  Thank you very much!!

February 04, 2003 Joshua -- Thanks for the question regarding
      "Refreshing Materialized Views", version 8.1.7

      You Asked
Hi Tom,

Here is the question, is it possible to refresh a materialized view, via
DBMS_SNAPSHOT.REFRESH or some other method, without leaving that time at which
the table you are refreshing is empty?

I am refreshing a materialized view every hour from a VAX/VMS data source,
during that refresh time, which is around 45 seconds, if a user selects from the
materialized view they get no records back.

Thanks in advance.

      and we said...
Yes, if a snapshot (mv) is part of a snapshot GROUP, we do a delete -- followed
by a "refill" of the snapshot -- instead of a TRUNCATE followed by a refill.
This allows us to refresh many snapshots in a single transaction.

So, if you create a group of ONE, instead of truncate which commits, followed by
a "refill", we will delete, refill, and then commit.

Be prepared -- it'll generate more log (delete will), rollback (undo) this way
and the refresh will take longer then 45 seconds due to this.
Review &
      Followup

      Rating:
      5
  June 06, 2002
Reviewer:  Joshua
      Hernandez  from Zeeland, MI
Thanks Tom, your the man.

      Rating: 5
More question
      on delete  September 05, 2002
Reviewer:
      Ashok  from USA
>>Yes, if a snapshot (mv) is part of a snapshot GROUP we do >>a delete and
refill

How to make a MV part of a group?

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1389964969205

      Rating: 5
refresh the MVs
      in other database?  February 04, 2003
Reviewer:
      steve  from NYC< USA
Hi Tom,

In a procedure I run in the database A, is it possible
to refresh the MAterilized views in database B by
calling  DBMS_SNAPSHOT.REFRESH(mvname@DB_B)?

Thanks!

Steve

          Followup:

it would in theory be dbms_mview.refresh@remote(mvname) however, since the
refresh commits and you don't commit over a dblink.....

I can suggest you:

  dbms_job.submit@remote_site( 'dbms_mview.refresh(''mvname'');' );
  commit;

to schedule a refresh "real soon"

February 04, 2003 Michael -- Thanks for the question regarding
      "bind variables", version 8.1.7

      You Asked
Hello Tom,

From reading your articles on bind variables, I would like to start using them.
I have a procedure that checks the databbase for any invalid objects or disabled
triggers.

CREATE OR REPLACE PROCEDURE CHECK_STATUS
IS
  ct   PLS_INTEGER;

BEGIN
  SELECT count(*) INTO ct FROM (
    SELECT owner, object_name, object_type, status
    FROM   dba_objects
    WHERE  status <> 'VALID'
    UNION
    SELECT owner, trigger_name, 'TRIGGER', status
    FROM   dba_triggers
    WHERE  status <> 'ENABLED')
  WHERE owner NOT IN ('MDINH');

  IF ct>0 THEN
    sendmail('mdinh@nereuspharm.com',null,'INVALID/DISABLED Objects',NULL);
  END IF;

END;
/

How can I use bind variables in this situation?  Thank you for your assistance.

Michael.

      and we said...
You don't need to!

You see - every time, each and every time, this procedure runs - the same exact
SQL will be submitted -- it never changes!  Never.

You use bind variables when you discover you have hundreds or thousands copies
of a query that only differ based on their values -- eg:

insert into t values ( 1 );
insert into t values ( 2 );

That needs to use a bind variable -- not a literal.  In your case - no matter
HOW MANY TIMES that procedure runs -- the SQL is the same.

Bind variables are only a concern when you do dynamic sql -- when you build a
query in a string.  There you must take CARE to use bind variables.  That is not
the case here.

Your query could be more efficient if it simply:

  for x in ( select *
               from dual
              where exists ( select null
                               from dba_objects
                              where status <> 'VALID'
                                and owner <> 'MDINH' )
                 or exists ( select null
                               from dba_triggers
                              where status <> 'ENABLED'
                                and owner <> 'MDINH' ) )
  loop
     sendmail( ..... );
  end loop;

the where exists will stop as soon as it finds a row -- and only part of the
query would run if there are any hits.
Review &
      Followup

      Rating:
      5
  February 03, 2003
Reviewer:
      A reader
Once again, Tom comes through!!!

      Rating: 4
Multiple
      instances of the same type of query  February 03, 2003

Reviewer:  Gary  from Peterborough, Ontario

What if throughout your system you have essentially the same query?  At what
point should you say a bind variable is not required?

For example:

Most of the web applications that we have check if access should be allowed or
not through a table containing app status's:

select status
  from app_status
 where app_name='Timetabling';

The select is the same throughout different apps however the app_name string
would change.  We have a few that fit this category.

Thanks.

          Followup:

If you have the SAME query -- without change -- you need not bind (it is after
all -- the same).

If on the other hand you have "the same" query all over your system but it
really is:

select * from emp where empno = 1

in one place, select * from emp where empno = 2, in another and select * from
emp where empno = 3 and so on -- then, you should use a bind variable so there
is just one "select * from emp where empno = :x" everywhere.

As for your select -- sounds like a PERFECT candidate for a stored function
doesn't it?  PLSQL binds naturally -- just call a procedure --send it the string
and get the answer.  Don't PUT the sql everywhere.

      Rating: 5
In the case of
      ref cursor  February 04, 2003
Reviewer:
      John  from New Jersey
So this procedure does not need a bind variable since the predicate already
contains variables (p_num, p_char):

create or replace
procedure sp_search( p_num in number,
                     p_char in varchar2,
                     p_cursor in out types.rc )
is
begin
     open p_cursor for
     select *
        from dept
        where deptno > p_num
          and dname like p_char;
end;
/

The following would be unnecessary:

create or replace
procedure sp_search( p_num in number,
                     p_char in varchar2,
                     p_cursor in out types.rc )
is
begin
     open p_cursor for
     'select *
        from dept
        where deptno > :x
          and dname like :y'
       USING p_num, p_char;
end;
/

Am I right?

          Followup:

Yes, p_num and p_char ARE BIND VARIABLES already.  You can see this in TKPROF,
consider:

ops$tkyte@ORA920> create or replace procedure p( p_num in number, p_char in
varchar2, p_cursor in out sys_refcursor )
  2  as
  3  begin
  4          open p_cursor
  5          for select *
  6                from dept LOOK_FOR_ME
  7                   where deptno > p_num
  8                     and dname like p_char;
  9  end;
 10  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> variable x refcursor
ops$tkyte@ORA920> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA920> exec p( 10, 'x', :x )

PL/SQL procedure successfully completed.

SELECT *
          from dept LOOK_FOR_ME
         where deptno > :b2
           and dname like :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.10          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.10          0          0          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 140     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS FULL OBJ#(39013) (cr=0 r=0 w=0 time=0 us)

      Rating: 5
Thank you!
      Clearly illustrated.  February 04, 2003
Reviewer:
      John  from New Jersey

February 04, 2003 Ramesh -- Thanks for the question regarding
      "Trigger to capture program name", version 8.1.6

      You Asked
Hi Tom,

We have a Table Customer, Recently some of the records are getting deleted and
we could not find the reason

So is it possible to capure the customer id,username,machine,program which is
deleting the record using a database trigger. if so please let me know how

Thanks in advance

Regards

Ramesh

      and we said...
sure it is

create trigger t after delete on t for each row
begin
  insert into log_table (cust_id,uname,machine,program)
  select :old.cust_id, user, machine, program
    from v$session
   where sid = ( select sid from v$mystat where rownum = 1 );
end;

the owner of this trigger will need to have SYS (or someone connected as
internal or SYSDBA) execute:

grant select on v_$session to THAT_USER;
grant select on v_$mystat to THAT_USER;

a role won't cut it for those grants, they must be directly granted as shown.

But then again, the EASIEST way to get a good audit would be -- well, the audit
command -- no code, just audit delete on table;  and make sure audit_trail=true.
 it might not capture the program -- but the program is easy to fudge anyway
(i'll just copy sqlplus.exe to valid_program_to_use.exe and wah-lah, i'll be
valid_program_to_use)
Review & Followup

      Rating: 5
Very Useful
      tip  February 03, 2003
Reviewer:  Helena

      Rating: 5
Difference in
      select statement  February 04, 2003
Reviewer:
      Ramesh  from Japan
Hi Tom

Thanks very much for the reply. This is what i wanted

I tried using the following select statement and yours in the trigger. I am
getting the same result. Can you tell me what is the advantage of using v$mystat
view?

  select :old.cust_id, user, machine, program
    from v$session
     where audsid=userenv('sessionid');

Thanks

Ramesh

          Followup:

v$mystat works in all environments -- logon triggers, for sys, everywhere.

userenv('sessionid') sometimes returns 0...  rendering it useless.  probably
safe enough for your purposes.  I like the other one to be bullet proof.

      Rating: 3
Another
      approach  February 04, 2003
Reviewer:  Bill
       from Bristol, CT
sys_context('userenv',....)

Can be used to get all types of useful information, even program name (if memory
serves me right).

Not as bullet proof as Tom's approach but don't have to worry about grants.

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/functions122a.htm#1191436
has a list of them all (no program tho ;(

but yes, a wealth of information

February 04, 2003 Casey -- Thanks for the question regarding
      "Off-topic question from recent posting", version 8.1.7.0.0.

      You Asked
Hi Tom

In a recent reply you made to question "Mapping a long varchar
to a number"  on 02-MAR-01, you made a comment I want to follow
up on.

On the last line you said:

    'select/into is faster and easier to code then
OPEN/fetch/close is.'

referring to a snippet of code like:

            select sampleid into m_id
              from table_a
             where sampleno = i_sno;

I was always under the assumption that a sql statement like the
above causes overhead as described below:

- during initial execution of the sql, the database is hit once
  to find the first row
- if no row is returned the 'no_data_found' exception is raised
- if a row is returned, the database is hit a second time to
  determine whether to raise the 'too_many_rows' exception

The overhead being the second hit to the database.  Using a
cursor (open/fetch/close) to get the row eliminates this second
hit.

Ignoring the issue of data integrity (where you may want the
'too_many_rows' exception raised if you 'select' expectation one
row), is the return trip to the database not a performance
issue?

thanks!

Casey

      and we said...
Here are examples from 7.1, 7.3, 8.0, 8.1 (all done on different
machines so don't compare times across versions, just within
versions).

IMPLICIT cursors rule.  They are faster, they are easier.

The "double" hit is optimized away.  Internally, it does an
array fetch and askes for 2 rows.  The whole round trip
processing with the select into is less then the explicit
cursor.  Not only that but you have less plsql code -- which is
faster as well.  The more that can be done for you in C the
better.

scott@ORA716.WORLD> declare
  2      cursor c1 is select * from dual;
  3
  3      l_dummy dual.dummy%type;
  4      l_start number default dbms_utility.get_time;
  5  begin
  6      for i in 1 .. 10000
  7      loop
  8          open c1;
  9          fetch c1 into l_dummy;
 10          close c1;
 11      end loop;
 12      dbms_output.put_line
 13      ( 'Elapsed ' ||
 14         round( (dbms_utility.get_time-l_start)/100, 2 ) );
 15  end;
 16  /
Elapsed 12.18

PL/SQL procedure successfully completed.

scott@ORA716.WORLD>
scott@ORA716.WORLD>
scott@ORA716.WORLD> declare
  2      l_dummy dual.dummy%type;
  3      l_start number default dbms_utility.get_time;
  4  begin
  5      for i in 1 .. 10000
  6      loop
  7          select dummy into l_dummy from dual;
  8      end loop;
  9      dbms_output.put_line
 10      ( 'Elapsed ' ||
 11         round( (dbms_utility.get_time-l_start)/100, 2 ) );
 12  end;
 13  /
Elapsed 8.83

PL/SQL procedure successfully completed.

scott@ORA734.US.ORACLE.COM> declare
  2      cursor c1 is select * from dual;
  3
  3      l_dummy dual.dummy%type;
  4      l_start number default dbms_utility.get_time;
  5  begin
  6      for i in 1 .. 10000
  7      loop
  8          open c1;
  9          fetch c1 into l_dummy;
 10          close c1;
 11      end loop;
 12      dbms_output.put_line
 13      ( 'Elapsed ' ||
 14         round( (dbms_utility.get_time-l_start)/100, 2 ) );
 15  end;
 16  /
Elapsed 13.55

PL/SQL procedure successfully completed.

scott@ORA734.US.ORACLE.COM>
scott@ORA734.US.ORACLE.COM>
scott@ORA734.US.ORACLE.COM> declare
  2      l_dummy dual.dummy%type;
  3      l_start number default dbms_utility.get_time;
  4  begin
  5      for i in 1 .. 10000
  6      loop
  7          select dummy into l_dummy from dual;
  8      end loop;
  9      dbms_output.put_line
 10      ( 'Elapsed ' ||
 11         round( (dbms_utility.get_time-l_start)/100, 2 ) );
 12  end;
 13  /
Elapsed 9.93

PL/SQL procedure successfully completed.

ops$tkyte@8.0> declare
  2      cursor c1 is select * from dual;
  3
  3      l_dummy dual.dummy%type;
  4      l_start number default dbms_utility.get_time;
  5  begin
  6      for i in 1 .. 10000
  7      loop
  8          open c1;
  9          fetch c1 into l_dummy;
 10          close c1;
 11      end loop;
 12      dbms_output.put_line
 13      ( 'Elapsed ' ||
 14         round( (dbms_utility.get_time-l_start)/100, 2 ) );
 15  end;
 16  /
Elapsed 1.82

PL/SQL procedure successfully completed.

ops$tkyte@8.0>
ops$tkyte@8.0>
ops$tkyte@8.0> declare
  2      l_dummy dual.dummy%type;
  3      l_start number default dbms_utility.get_time;
  4  begin
  5      for i in 1 .. 10000
  6      loop
  7          select dummy into l_dummy from dual;
  8      end loop;
  9      dbms_output.put_line
 10      ( 'Elapsed ' ||
 11         round( (dbms_utility.get_time-l_start)/100, 2 ) );
 12  end;
 13  /
Elapsed 1.24

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD> declare
  2      cursor c1 is select * from dual;
  3
  4      l_dummy dual.dummy%type;
  5      l_start number default dbms_utility.get_time;
  6  begin
  7      for i in 1 .. 10000
  8      loop
  9          open c1;
 10          fetch c1 into l_dummy;
 11          close c1;
 12      end loop;
 13      dbms_output.put_line
 14      ( 'Elapsed ' ||
 15         round( (dbms_utility.get_time-l_start)/100, 2 ) );
 16  end;
 17  /
Elapsed 2.15

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> declare
  2      l_dummy dual.dummy%type;
  3      l_start number default dbms_utility.get_time;
  4  begin
  5      for i in 1 .. 10000
  6      loop
  7          select dummy into l_dummy from dual;
  8      end loop;
  9      dbms_output.put_line
 10      ( 'Elapsed ' ||
 11         round( (dbms_utility.get_time-l_start)/100, 2 ) );
 12  end;
 13  /
Elapsed 1.66

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating: 3
Why this
      Difference  April 11, 2002
Reviewer:  Senthil
      Kumar S  from Chennai, TN,India,
Hi Tom,
I am a regular reader of your valuable questions. I have gained a lot from this
site.
Well, please have a look at the following  block of code.
DECLARE
  TYPE name_num_rc IS REF CURSOR;
       cur_1 name_num_rc;
       rec1 emp.ename%type;
       rec2 emp.ename%type;
   cursor cur_2 is  select ename from emp order by ename;
BEGIN
    open cur_1 for select ename from emp order by ename;
        fetch cur_1 into rec1;
        LOOP
            exit when cur_1%notfound;
            dbms_output.put_line('Name1 '|| rec1);
            fetch cur_1 into rec1;
        end loop;
    dbms_output.put_line('***********************************' );
    for  i in cur_2 loop
        fetch cur_2 into rec2;
        dbms_output.put_line('Name '|| rec2 );
    end loop;
end;
/
I find a difference in number of rows obtained from the above block. I could
find no bug/error. Please let me know why I am unable to see it.

Thanks in Advance
Senthil
( Sorry for filling up this section with a query, but I could not do other than
this)

          Followup:

 for  i in cur_2 loop
        fetch cur_2 into rec2;   <<<<====================
        dbms_output.put_line('Name '|| rec2 );
    end loop;

remove that.  you are double fetching, skipping every other record in effect.

This is my preferred way to code what you have above (well, except for the
CURSOR CUR_2 is part... i would never do it that way, i would use for i in (
select... :

  1  DECLARE
  2    TYPE name_num_rc IS REF CURSOR;
  3    cur_1 name_num_rc;
  4    rec1 emp.ename%type;
  5    cursor cur_2 is  select ename from emp order by ename;
  6  BEGIN
  7      open cur_1 for select ename from emp order by ename;
  8      LOOP
  9          fetch cur_1 into rec1;   <<<<====== fetch here, not outside the
loop
 10          exit when cur_1%notfound;
 11          dbms_output.put_line('Name1 '|| rec1);
 12      end loop;
 13      dbms_output.put_line('***********************************' );
 14      for  i in cur_2
 15      loop   <<<<================= don't fetch here, don't define a record!
 16          dbms_output.put_line('Name '|| i.ename );
 17      end loop;
 18* end;
scott@ORA817DEV.US.ORACLE.COM> /
Name1 ADAMS
Name1 ALLEN
Name1 BLAKE
Name1 CLARK
Name1 FORD
Name1 JAMES
Name1 JONES
Name1 KING
Name1 MARTIN
Name1 MILLER
Name1 SCOTT
Name1 SMITH
Name1 TURNER
Name1 WARD
***********************************
Name ADAMS
Name ALLEN
Name BLAKE
Name CLARK
Name FORD
Name JAMES
Name JONES
Name KING
Name MARTIN
Name MILLER
Name SCOTT
Name SMITH
Name TURNER
Name WARD

PL/SQL procedure successfully completed.

      Rating: 4
Then Why
      ??  April 13, 2002
Reviewer:  A reader

Tom,

Then why is Forms When asked to generate the Code for The Validation Events
Write Tem Using Explicit Cursors.

Your comments pls.

Regards,
Ganesh R

          Followup:

Because I did not write it I suppose.  To each their own.

There is no disputing the *facts* that:

o implicit cursors are easier for my fingers to type in
o implicit cursors result in less code
o implicit cursors are faster

YOU make the choice when YOU write the code.

      Rating: 1
Somewhat silly
      comparison  June 12, 2002
Reviewer:  Michael
      O'Neill  from Garland, TX
Someone sent me the link to this "benchmark" to prove to me that implicit
cursors were not only easier to use but that they were also faster.

What your benchmark does that is silly, in my opinion, is open and close the
cursor needlessly in the explicit cursor sample.  If one puts the OPEN and CLOSE
before and after the LOOP, the results are dramatically different.  Opening and
closing cursors outside of the loop is the norm.  I've never opened and closed a
cursor inside the same loop I was fetching iteratively from.

On my box the improvement was 750-fold by moving the opening and closing of the
cursor outside the loop.

I think this "benchmark" misleads many about the value of using explicit cursors
in the code.  For quick and dirty work implicit is fine, but for most long-use
code, explicit will yield superior performance.

          Followup:

Dude,

If one puts the OPEN and CLOSE outside the loop -- one has a HUGE BUG in their
code.  They have made an error, they have not thought about what they heck they
were doing (opps -- sorry -- that would be you)

If you take this:

scott@ORA716.WORLD> declare
  2      cursor c1 is select * from dual;
  3
  3      l_dummy dual.dummy%type;
  4      l_start number default dbms_utility.get_time;
  5  begin
  6      for i in 1 .. 10000
  7      loop
  8          open c1;
  9          fetch c1 into l_dummy;
 10          close c1;
 11      end loop;
 12      dbms_output.put_line
 13      ( 'Elapsed ' ||
 14         round( (dbms_utility.get_time-l_start)/100, 2 ) );
 15  end;
 16  /
Elapsed 12.18

and code it as this:

scott@ORA716.WORLD> declare
  2      cursor c1 is select * from dual;
  3
  3      l_dummy dual.dummy%type;
  4      l_start number default dbms_utility.get_time;
  5  begin
  6      for i in 1 .. 10000
         open c1;
  7      loop
  9          fetch c1 into l_dummy;
 11      end loop;
         close c1;
 12      dbms_output.put_line
 13      ( 'Elapsed ' ||
 14         round( (dbms_utility.get_time-l_start)/100, 2 ) );
 15  end;
 16  /

you know what you just did?  you fetched 10000 times from a cursor that
returns 1 record.  9,999 of the FETCHES DID NOTHING, RETURNED NO DATA -- IN FACT
-- Oracle is so smart, it didn't even bother to do a context switch to the
kernel for that -- it KNEW the result set was empty.  That is why you got 750
times faster -- when you do NOTHING, it goes faster then doing SOMETHING

If you want to compare 10,000 "select intos" versus explicit cursor fetches --
guess what?  YOU HAVE TO OPEN and CLOSE a cursor 10,000 times!!!!!  Think
about it

Your supposed improvement is a bug in your code, does not work.  Does not
fetch any data.  Doesn't do squat.  I KNOW your benchmark is fatally flawed
and erroneous.  It is the thing that misleads

For quick and dirty, for production quality, for the long haul, for EVERYTHING
-- implicit cursors rule.  They are:

o faster
o easier to code
o less error prone (as you just so nicely demonstrated for all of us)

A bunch of us had a discussion on this "somewhat silly comparison" on the
newsgroups.

You can see the crux of the discussion here:

http://groups.google.com/groups?hl=en&lr=&ie=UTF8&oe=UTF8&selm=9jpff01657%40drn.newsguy.com&rnum=9
Someone tried to make the same argument you did.  To see him get beat soundly
about the head by others pointing out the same deficiency (that his code quite
simply is a really huge BUG)

http://groups.google.com/groups?hl=en&lr=&ie=UTF8&oe=UTF8&threadm=44ff9953.0107241139.496f2ddf%40posting.google.com&rnum=1&prev=/groups%3Fhl%3Den%26lr%3D%26ie%3DUTF8%26oe%3DUTF8%26selm%3D44ff9953.0107241139.496f2ddf%2540posting.google.com
is the entire thread.

I take alot of time before posting a benchmark.  I do not concoct examples to
prove MY point (eg: i try to not be biased, try not to prove my position but
rather try to show scienticially that this is the WAY IT IS).

You just did not think this one thru at all.

      Rating: 5
Erroe code for
      benchmarking  June 13, 2002
Reviewer:  Kumar
       from USA
declare
        cursor c1 is select * from dual;

        l_dummy dual.dummy%type;
        l_start number default dbms_utility.get_time;
    begin
        for i in 1 .. 10000
         open c1;
        loop
            fetch c1 into l_dummy;
       end loop;
         close c1;
       dbms_output.put_line
       ( 'Elapsed ' ||
          round( (dbms_utility.get_time-l_start)/100, 2 ) );
   end;
   /

When u try to execute the above the code from sqlplus i get an error
ERROR at line 7:
ORA-06550: line 7, column 10:
PLS-00103: Encountered the symbol "OPEN" when expecting one of the following
* & - + / at loop mod rem <an exponent (**)> ||
The symbol "loop was inserted before "OPEN" to continue.
ORA-06550: line 14, column 7:
PLS-00103: Encountered the symbol ";" when expecting one of the following:
loop

So i changed to
declare
        cursor c1 is select * from dual;
        l_dummy dual.dummy%type;
        l_start number default dbms_utility.get_time;
    begin
      for i in 1 .. 10000
      loop
         open c1;
         fetch c1 into l_dummy;
         --dbms_output.put_line( 'Elapsed ' ||round(
(dbms_utility.get_time-l_start)/100, 2 ) );
         close c1;
       end loop;
       dbms_output.put_line( 'Elapsed ' ||round(
(dbms_utility.get_time-l_start)/100, 2 ) );
   end;

Now it compiles.
The Reason i am asking this bcos i have seen somebody benchmarked the above code
by placing open cursor statment before the loop,i want to know how does it
compile.

          Followup:

You missed the LOOP keyword after 10,000

Btw:

     loop
            fetch c1 into l_dummy;
       end loop;

is what we call "an infinite loop".  You need:

       loop
            fetch c1 into l_dummy;
            exit when c1%notfound;
       end loop;

If they put the OPEN before the for i in 1 .. 10000 loop -- they just

a) wasted their time
b) have meaningless numbers
c) ran a big ol buggy piece of code

This code:

 for i in 1 .. 10000
         open c1;
        loop
            fetch c1 into l_dummy;
       end loop;
         close c1;

is SO WRONG.

      Rating: 5
Array fetch of
      2 rows  February 04, 2003
Reviewer:  Sam
       from San Mateo
Tom,

You mentioned PL/SQL will automatically set array size to 2 for implicit
cursors.  Is this feature introduced in a recent release?  In the newgroup
discussion link you posted, you and others didn't make this argument.  Instead,
the suggestion is to add "rownum = 1" to the query.  Does pl/sql actually look
at the query and figure out if there is a possibility of more than 1 row being
returned?  Similarly, if I have an implicit cursor "select 1 into my_var from
dual where 1=0", does pl/sql even try to execute the query?  Or does it
immediately throws the "No Data Found" exception?

Thank you very much.

          Followup:

for SELECT INTO type cursors.

it has been so for many years:

tkyte@ORA716> alter session set sql_trace=true;
Session altered.

tkyte@ORA716>
tkyte@ORA716> declare
  2          l_x dual.dummy%type;
  3  begin
  4          select dummy into l_x from dual look_for_me;
  5  end;
  6  /
PL/SQL procedure successfully completed.

SELECT DUMMY
FROM
 DUAL LOOK_FOR_ME

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           2
Fetch        1      0.00       0.01          2          1          3           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.01          2          1          3           3

Misses in library cache during parse: 1
Optimizer hint: CHOOSE
Parsing user id: 10  (TKYTE)   (recursive depth: 1)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   HINT: CHOOSE
      1   TABLE ACCESS (FULL) OF 'DUAL'

see, one fetch -- if it did not ask for 2 rows, we would see 2 fetches there
-- one to get the row and one to make sure another row does not exist

The "where rownum=1" is a way to make it so that code:

  open c;
  fetch c;
  close c;

can be replaced with

  select into from ...

without getting a TOO_MANY_ROWS error.  If you just want the first row from a
result set, just select into with ROWNUM=1.

It is the SELECT INTO semantics that tell PLSQL what to do.

PLSQL executes that query with the 1=0, only the SQL engine is smart enough to
know it needs to do nothing.

      Rating: 5
Array fetch of
      2 rows  February 04, 2003
Reviewer:  Sam
       from San Mateo, CA
Please ignore my previous comment, I wasn't reading the newsgroup posting
carefully.  Sorry for the unnecessary posting.

February 04, 2003 saradha -- Thanks for the question regarding
      "Passing parameter during LOGON", version 8.1.7

      You Asked
Tom,

Is it possible to pass a parameter (value) during the logon
to the database by which the parameter value can be grabbed from
the AFTER LOGON trigger.

The whole idea is to set the application context value based on the parameter
value that is being passed. This is to prevent the user from
accessing the entire table data but give him the only data that he
is suppossed to. The users input data is not fixed where I can store his id and
all his possible input in a table and query the table from the LOGON trigger
based on the username.

I have searched your site and many other documentation and I do not
see an example for passing the parameter during the LOGON or to the AFTER LOGON
trigger. But there are many examples of setting the application context from the
AFTER LOGON trigger but those are all based on the USERNAME or OSUSER which we
can get it from the V$SESSION or other V$ tables.

The current alternative is after the successfull logon to the database
the application has to call the procedure that sets the application context for
that session before querying the underlying tables.

Thanks
Saradha

      and we said...
see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7573153772528
we just had a discussion on this.

It would be POINTLESS to have the end user pass you in anything that was used to
control security.  Think about it - you would be saying that the end user is
telling you what data they are supposed to have access to!  What kind of
security is that.

See that thread -- especially the followups -- there I describe that you need a
TABLE in the database that is used to control who can see what.  You CANNOT have
the end user tell you what they are allowed to see - that would be even worse
then just not having security at all as it would lull you into a false sense of
"we are secure"
Review & Followup

      Rating: 5
passing
      parameter while connecting to the database  February 04,
      2003
Reviewer:  Saradha Bavanandam
Tom,

We have taken your solution to have the table method and deriving from there.

But in general and not for this particular requirement, please tell me is it
possible at all to pass a parameter while connecting to the database and so that
the parameter value can be grabbed from the LOGON trigger. Is there any way ???

Thanks
Saradha

          Followup:

Nope -- none


Subject | Article | Last Updated (East coast time)
PL/SQL vs external procedure | article only | Feb 05, 2003 08:34 am
Retrieving Delimited LOB data | article only | Feb 05, 2003 10:34 am
Locking scheme. | article only | Feb 05, 2003 11:49 am
How we can eliminate chained rows? | article only | Feb 05, 2003 12:12 pm
Help on SQL Stmt | article only | Feb 05, 2003 06:13 pm
v$session_longops | article only | Feb 05, 2003 06:25 pm
Temporary Tablespace (8 vs 8i) | article only | Feb 05, 2003 06:26 pm
inconsistent datatypes when using inline view and (case when then end) | article only | Feb 05, 2003 06:31 pm
Display field value based on another field value | article only | Feb 05, 2003 06:41 pm
SQL Problem | article only | Feb 06, 2003 08:44 am
SQLJ Bind Variables | article only | Feb 06, 2003 01:23 pm
Exporting from 8.0.4 then importing in to 8.1.5 | article only | Feb 06, 2003 01:25 pm
os block and db block | article only | Feb 06, 2003 06:48 pm
Automated end of year job. | article only | Feb 07, 2003 09:08 am
adding day to dates but not counting sat and sun | article only | Feb 07, 2003 12:33 pm
connect string, sid , dbname, instance name | article only | Feb 07, 2003 01:32 pm
Optimise procedure | article only | Feb 07, 2003 02:19 pm
Changing datatype in materialized views | article only | Feb 07, 2003 05:21 pm
Trunc vs To_Char. | article only | Feb 08, 2003 09:25 am
Split row over more than one group | article only | Feb 08, 2003 09:51 am
Effect of Database Buffers on Buffer Hit Ratio | article only | Feb 08, 2003 10:10 am
Variables in IN operator | article only | Feb 08, 2003 01:21 pm
Mutating Trigger.. | article only | Feb 10, 2003 07:49 am
order by specific values | article only | Feb 10, 2003 03:23 pm
Is it possible to do this in SQL instead of PL/SQL? | article only | Feb 10, 2003 07:37 pm
Tracking unused objects | article only | Feb 11, 2003 08:01 am
SQL Tuning for UPdate Statement | article only | Feb 11, 2003 08:07 am
Any ideas on how to make SELECT faster | article only | Feb 11, 2003 04:52 pm
Multi Dollar Question | article only | Feb 11, 2003 04:55 pm
row(s) 1 - 29 of 29
February 05, 2003 minnie -- Thanks for the question regarding "PL/SQL vs external procedure", version Oracle 8i 8.1.5
You Asked
Hi, Tom:
   I have two relational questions today.
   1. we wrote stored procedure in PL/SQL, but the running speed
was too slow. I tried to find the reasons by various methods,
and found the main possible reason was a cursor that includes a
column user_defined function(say 'select
deptno,get_emp_cnt(deptno) from dept;') and fetches thousands of
rows. because the user_defined function itself includes a cursor
which has to fetches hundreds of rows too and processes many
calculations.
The whole cursor loop took about 20 seconds. The total expected
timing of our application should be 7 seconds, so I planned to
re-write the code in OCI and call it as an external procedure in
PL/SQL, my question is whether the fetch timing in OCI will be
quicker than in PL/SQL?
   2. could OCI programs be integrated derectly in programs
which running in client such as activeX or ASP, rather than as
external procedures in PL/SQL?

thanks

Minnie
sofeware engineer
Medical Online, Inc.
and we said...
1) the fetching will not be any faster as an extproc.  It is the
overhead of a context switch you are seeing.  Calling user
defined functions from SQL is great in certain cases.  In your
case -- it is not (not with THOUSANDS of rows anyway).

We can almost certainly rewrite get_emp_cnt in pure SQL and
plsql will be more then fast enough.

If the defintion of "get_emp_cnt()" is something like:

create function get_emp_cnt(p_deptno in number ) return number
as
  l_cnt number;
begin
  select count(*) into l_cnt from emp where deptno = p_deptno;
  return l_cnt;
exception
  when no_data_found then
     return NULL;
end;

We can rewrite your query above simply as:

select depno,
       (select count(*) from emp where deptno = dept.deptno)
  from dept
/

That'll be faster.

Let me see get_emp_cnt() and your real queries if you have
problems converting them to pure SQL.

2) I do not know the ability/openess of ActiveX or ASP in this
regards -- sorry.
Review & Followup
Rating: 5
PL/SQL vs OCI February 05, 2003
Reviewer: Zoran Martic from Dublin, Ireland
Hi Tom,

I have one question in regards of this.
I have tested PL/SQL vs. C-OCI performance.
The test was 50000 unique lookups (SELECT's) in the loop.
I used all (to me known) techniques to make C-OCI as fast as PL/SQL.
From my point of understanding, if the application is SQL intensive (especially
SELECTS, no bulk SELECTS like inserts), you will loose by using C-OCI.

My test was:
1. PL/SQL - 50000 SELECT - 28sec
2. C-OCI -DIFFERENT MACHINE- 50000 SELECT - 72 sec
3. C-OCI -THE SAME MACHINE, TCP/IP - 50000 SELECT - 48sec
4. C-OCI -THE SAME MACHINE, IPC - 50000 SELECT - 44sec
5. C-OCI -THE SAME MACHINE, BEQ - 50000 SELECT - 42sec

I used 9.2 database, SQL processing from v$sqlarea was about 20sec for these
50000 SELECTs in all tests. No waits for foreground processes. TCP/IP with
nodelay (in this case not important). The BEQ protocol used CPU much more in
user space then other protocols.

The difference in statistics were:
PL/SQL
STAT...SQL*Net roundtrips to/from client              26
STAT...bytes received via SQL*Net from client       3068
STAT...bytes sent via SQL*Net to client             1911

ALL C-OCI methods
STAT...SQL*Net roundtrips to/from client           50002
STAT...bytes received via SQL*Net from client    1300702
STAT...bytes sent via SQL*Net to client          7701274

That means that SQL*Net roundtrips (including kernel context switching/blocking
on response) are the main cause for PL/SQL to be faster. When I calculated times
from both processes for example C-OCI-beq, I had 20 sec SQL execution plus 8 sec
client time (user + kernel) and 14 seconds missing (the stupid network time
overhead). That means overall time when you exclude probably the network time
was the same as in Pl/SQL but these SQL*Net roundtrips added too big overhead.

Interesting part is that whatever I did (change how many binds, ...) the amount
of bytes send/receive was the same (that showed me that there is some packet
size (as we know for TCP/IP) but why BEQ/IPC. I could not find any info on all
Oracle resources except for TCP/IP - known to everybody.

From your knowledge Tom, that looks to me that if you exclude bulk-inserts,
PL/SQL will be faster then C-OCI.
Also if there is not too many mathematical or other computations you need to
perform, Pl/SQL will be faster.

Also I tested PL/SQL compiling to native C. It was the same speed (as you stated
in your book that it is pointless to compile to the native-C if you do not have
computational problems).

I was never dig that far before, and I was thinking that C-OCI is like PL/SQL
and could be faster because of computations, soft soft parse, but ....

Would you give me maybe your way of thinking here?
What is your knowledge about this?
Do you have anything (paper, discussion, ...) on this?

Best Regards,
Zoran

Followup:
My mantra has always been -- when DEALING WITH DATA:

o do it in a single sql statement (or set of statements) if at all possible.
o if you cannot do it set wise with SQL, use as little plsql as you can for
procedural code.
o if you cannot do it in plsql (feature you need not in language) do a teeny
tiny bit of java to help plsql out
o if you cannot do it in plsql or java or those are too slow (compute intensive
thing needs to be done), use a little C - external procedures are dandy but you
can pull the data out if need be.

You see -- in the above -- even if the PLSQL was "slower", I would probably opt
for it unless it was egregiously slow.   Why?  Because it would take me seconds
or minutes to code in PLSQL what would take an hour in C.   The cost savings I
see are not limited to runtime performance but in development, maintainability
and so on.

To see the converse of your example however -- see
http://asktom.oracle.com/~tkyte/flat/index.html
and benchmark plsql vs proc (i like proc lots better then OCI for the speed of
development, I can in minutes do what takes 10s of minutes using proc vs oci) vs
sqlplus.

You'll find plsql < sqlplus < proc in that case.

to show you how far you can take this -- I was just asked to help someone.  The
problem was:

a) we have 8 people
b) they need to be randomly assigned to two groups of 4
c) over a 12 week period to play golf...

Well, some might write code, others might try paper and pencil.  I just fired up
sqlplus:

ops$tkyte@ORA920> with players
  2  as
  3  ( select rownum username from all_objects where rownum <= 8 ),
  4  weeks
  5  as
  6  ( select rownum week from all_objects where rownum <= 12 )
  7  select week,
  8         max(decode(rn,1,username,null)) grp1,
  9         max(decode(rn,2,username,null)) grp1,
 10         max(decode(rn,3,username,null)) grp1,
 11         max(decode(rn,4,username,null)) grp1,
 12         max(decode(rn,5,username,null)) grp2,
 13         max(decode(rn,6,username,null)) grp2,
 14         max(decode(rn,7,username,null)) grp2,
 15         max(decode(rn,8,username,null)) grp2
 16   from ( select username, week, row_number()
                                    over (partition by week order by rnd) rn
 17            from ( select username, dbms_random.random rnd, week
 18                     from players, weeks
 19                 )
 20       )
 21   group by week
 22  /

      WEEK GRP1 GRP1 GRP1 GRP1 GRP2 GRP2 GRP2 GRP2
---------- ---- ---- ---- ---- ---- ---- ---- ----
         1    8    4    3    7    2    1    6    5
         2    2    1    5    3    8    7    6    4
         3    7    4    6    5    8    1    3    2
         4    4    3    6    1    7    5    8    2
         5    4    3    5    8    6    7    2    1
         6    6    1    8    7    4    5    2    3
         7    5    2    4    7    8    1    6    3
         8    8    7    3    6    5    1    2    4
         9    3    8    7    4    5    6    2    1
        10    7    3    8    4    5    6    1    2
        11    4    1    5    8    3    6    7    2
        12    6    3    8    5    2    1    7    4

12 rows selected.

and there you go ;)

Rating: 5
To write PL/SQL you need minutes not hours February 05, 2003
Reviewer: Zoran Martic from Dublin, Ireland
Hi Tom,

Thanks a lot Tom.

"TO WRITE PL/SQL YOU NEED MINUTES NOT HOURS"

This is exactly the pure benefit from PL/SQL.
I wrote PL/SQL in 5 minutes (3 minutes to find column names).
To do anything with SQL and PL/SQL you need minutes, hours, not days.
I needed a few hours to finish C-OCI because of type conversions, very easy OCI
interface :)

I will always use PL/SQL.
We are using OCI for a long time (C, C++ telecom application) but nobody ever
meassured overhead.
But other people are coding that part, but we never compared this and there is
nothing on the Internet to find about comparisons.

50% to 100% overhead is too much in some cases, but there is not other way
(after you reduce to the maximum number of transactions per second and tune SQL
up to the end).
Of course if statements are too slow you will not see that big overhead, but
this is not the case with real-time applications at all.

Thanks a lot again.

Regards,
Zoran

February 05, 2003 Daya -- Thanks for the question regarding "Retrieving Delimited LOB data", version 8.1.7
You Asked
Hi Tom!
  I have a procedure ins_lob by which i insert file with contents into database.
   For reading the data from database i have a procedure called read_lob which
is like:
SQL> set serveroutput on size 100000
SQL> exec read_lob(7);
 PL/SQL procedure successfully executed.
 procedure read_lob(p_id in demo.id%type) is
       Lob_loc         BLOB;
       m integer;
       n integer;
    BEGIN
       SELECT theBlob INTO Lob_loc
         FROM demo
        WHERE id = p_id;
   m := dbms_lob.getlength(lob_loc);
       dbms_output.put_line('THE LENGTH IS: '||dbms_lob.getlength(lob_loc));
      -- just print out the first 200 bytes since put_line
      -- cannot do more then 255
     for i in 1..m loop
      if i =1 then
      dbms_output.put_line(utl_raw.cast_to_varchar2( dbms_lob.substr( lob_loc,
230, 1 ) ) );
       n := 230+230;
      else
          if n<m then
         dbms_output.put_line(utl_raw.cast_to_varchar2( dbms_lob.substr(
lob_loc,230,n ) ) );
         n := n+230;
         end if;
      end if;
    end loop;
   END;

  Using the above procedure, i am able to display the details too. But i need to
retrieve the blob data which is having delimiters (like ','or '|') into separate
fields of another table. That is first i will retrieve the data and give it as
an input to another table. Can i try the same with BLOB datatype or shall i use
CLOB datatype?
  Hope to get your valuable feedback.
Thanks in advance
Daya
and we said...
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1352202934074
you can use a blob -- that shows how i "parse" a file using dbms_lob on a bfile
-- you can do the same with a blob if you like.

clob would be easier of course.  and if the data is actually TEXT, a better
solution as it would support the various character set conversions we do --
whereas a blob would not.
Review & Followup
Rating: 5
December 17, 2003
Reviewer: A reader

February 05, 2003 Kashif -- Thanks for the question regarding "Locking scheme.", version 9iR1
You Asked
Hi Tom,

I've learned so much on this website, thanks for the work you put in. The
situation:

We are planning to implement a new application in our company, and one of the
things which did not settle well with me was their mechanism for locking
records. Specifically, they "lock" all the associated records for a loan (which
is the key), by updating a column value in the main table, and anytime another
user tries to access this loan, that column value is checked, and if it is 'Y',
it returns a message saying that the loan is locked by another user, please try
later, or something to that effect. The requirement here is to disallow any user
from accessing a loan if another user has "locked" it.

my questions:
a) Is there a better way to implement this locking scheme?
b) I know you're big on concurrency, so I'm wondering if this is a good idea at
all, i.e. to prevent reads/writes to all associated records for a loan.

Thanks in advance.

Kashif
and we said...
Ask them "so, what happens when a session dies?  goes away without unlocking the
records????"

A 'better' method is:

  select *
    from t
   where ( PREDICATE ) and (LOCKED_UNTIL < SYSDATE)
     FOR UPDATE;  <<<=== to lock the records, prevent others from selecting for
                         update

  update t
     set locked_until = sysdate + (1/24/60) * 10,
         locked_by    = p_session_identifier_of_some_sort   /* 10 minutes */
   where (PREDICATE);

Now, you have 10 minutes in which to complete your transaction -- when you goto
update, you would make sure YOUR session is the session that has the records
locked - if not, you inform the end user "you were just too slow, you loose".

That is one approach.  You can also use OPTIMISTIC concurrency control where by
you assume no one else will touch the data and upon updating -- you make sure it
is the same as it was when you started (via a timestamp, a checksum or a
complete record check)
Review & Followup
Rating: 4
Followup... October 23, 2002
Reviewer: Kashif
Thanks Tom. I think the 10 minute (or any fixed time) limit will not work for
all the users since we have users who need to do chunks of work at a time.
Though it would have been better to create roles to define the different kinds
of users, and THEN implement the locking mechanism you mention, for each role,
which they have not done.

The workaround they have for the problems you mention, by the way, is that they
have a procedure kicked off every night to unlock all locked records. During the
day, if a loan needs to be unlocked, a separate utility is used to unlock that
one loan. Just seems very manual though. Thanks for your help again.

Followup:
Why not have the application then update with a different interval based on
different classes of users.  Or even better, keep it at 10 minutes and make the
application refresh the locks as the user contacts the website for another 10
minutes.

That "we'll unlock manually as needed" WILL become a full time job for one or
more people.  "Whoops, my browser crashed -- unlock those rows".  "Whoops --
clicked close on the browser -- unlock those rows".  "Whoops, I'm working from
down the hall and forgot to unlock them -- unlock those rows"

and the list goes on.  they are building a super high maintenance system that
your help desk will utterly despise (and so will the end users).

Rating: 2
This is a common problem ... October 23, 2002
Reviewer: Peter Boswell from Chicago
Often we need to protect a whole logical set of data from modification by more
than one person at a time.  In Kashif's case, it is a loan.  In my case it has
been the compensation plan for an expatriate, an entire insurance claim and all
of the information about a depreciable asset.  In all of these situations, rows
from dozens of tables must be kept consistent - and some state is "in logic" or
memory - enough that row locks alone are insufficient or "artifical".  What we
need is a lock mechanism for an entire logical (application) set of information
that lets only one client at a time work on it, but lets many view it.
Furthermore, we need to be able to report back to a user attempting to acquire
such a lock with information to trace back to the person/actor holding the lock.
 Finally, should the client application fail (not time out, it could take hours
to complete an expatriate compensation plan), we need the application lock to be
removed automatically.  In the past I have used the client-oracle session
information to track who has a lock and to automatically release them when the
next person asks for the lock after a client failure.  But with middleware,
Oracle no longer knows the end client.  This is a very common problem where the
unit of work is substantial and is apt to be passed between people at any given
step.  Certain critical functions must not operate until no one has certain type
of lock.  Optimistic locking just angers end users in these situations.
Timeouts aren't meaningful either.  Blame may turn out to be important (Fred
went to lunch while his client app instance held an application lock on a
critical bit of work).
MS has some sp's that pretend to address this real issue, but most of this stuff
is still home grown.  I'd love to see something better.  Otherwise, we will just
build something homegrown again (or continue to anger end users)...

Followup:
I would say most of your requirements are covered by
http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96628/toc.htm
I read Kashif's question a little differently then you did.  I'm assuming that
since they cannot use row locks -- they must be stateless (hey, in Oracle WRITES
never never block reads remember!!) and needed a stateless environment way to
lock rows.

In fact the above link could be applied to their problem as well.

Rating: 4
Try Not locking, but flagging updates February 05, 2003
Reviewer: Thomas from USA
Rather than locking the record, can you:
1. Update the record to have an 'in-use' flag that does not prevent others from
using it - just a notification.
2. Read a timestamp-type column from the database
3. Hold the timestamp in the client (local memory)
4. Before you save the record, compare the timestamp value from the client to
the timestamp in the database.  If they are the same, fine - if not, someone
else edited the record and you have problems.  (You then need to decide what to
do; try to keep it simple.)
5. After saving the record, increment the timestamp and clear the 'in-use' flag.

This approach is not suitable for all apps, but is more flexible than many.

Followup:
step 1 is fraught with danger -- it is either useless (why bother if it is just
a "nofication") or a showstopper (client updates it but never comes back again)
if you use it as a true in use flag.

Then 2-5 are valid, just skip the in-use flag if it is just "notification"

February 05, 2003 amit -- Thanks for the question regarding "How we can eliminate chained rows?", version Oracle 7.3.4
You Asked
Sir,

Oracle 7.3.4 on HP-UX 10.20

We have a table with 25000 rows with lot of chaining. MAny
tables are also have a foreign keys with respect to this table.
When we tried to eliminate chained rows (first by
 inserting chained rows into intermediate table, and the
deleting them), Oracle didnot let us do it, returning the error
that Child records found.
We could not disable the constraints as tables are very
important.

Please tell me how we can eliminate chained rows.
and we said...
First -- couple of sanity checks

o you are sure the rows can be unchained.  They are smaller then
your blocksize correct?  if they contain a long or long raw,
they will be chained no matter what.

o you are sure the chained rows are actually causing a
performance problem right?  I mean, if they are not the cause of
a serious performance issue, don't bother -- it is not worth it.

If you need to do this AND there are no other unique
indexes/constraints on the table and there are no triggers this
will have a side effect with (e.g. make sure you understand what
this is going to do before doing it), you can do it as follows:

- clone the parent row with a dummy primary key (some currently
  non-existent but valid value for a primary key).
- swap the primary keys of the chained row with the dummy row
- delete the dummy row (which is the original migrated row)

If you cannot do that, cannot clone the row -- i'll show you how
to do it another way after this.  First I setup the parent and
child tables and force some row migrations:

ops$tkyte@ORA734.US.ORACLE.COM> create table p ( x int primary
key,
  2                   data1 varchar2(2000),
  3                   data2 varchar2(2000),
  4                   data3 varchar2(2000) );

Table created.

ops$tkyte@ORA734.US.ORACLE.COM>
ops$tkyte@ORA734.US.ORACLE.COM> create table c ( x references p
);

Table created.

ops$tkyte@ORA734.US.ORACLE.COM>
ops$tkyte@ORA734.US.ORACLE.COM> begin
  2      for i in 1 .. 10
  3      loop
  4          insert into p ( x ) values ( i );
  5          insert into c ( x ) values ( i );
  6      end loop;
  7      commit;
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA734.US.ORACLE.COM>
ops$tkyte@ORA734.US.ORACLE.COM> REM chained rows table for 7.3
ops$tkyte@ORA734.US.ORACLE.COM> create table CHAINED_ROWS (
  2    owner_name       varchar2(30),
  3    table_name       varchar2(30),
  4    cluster_name     varchar2(30),
  5    head_rowid       rowid,
  6    timestamp        date
  7  );

Table created.

ops$tkyte@ORA734.US.ORACLE.COM>
ops$tkyte@ORA734.US.ORACLE.COM> analyze table p list chained
rows into chained_rows;

Table analyzed.

ops$tkyte@ORA734.US.ORACLE.COM> select count(*) from
chained_rows;

  COUNT(*)
----------
         0

ops$tkyte@ORA734.US.ORACLE.COM>
ops$tkyte@ORA734.US.ORACLE.COM> update p
  2     set data1 = rpad('x',2000,'x'),
  3         data2 = rpad('x',2000,'x'),
  4         data3 = rpad('x',2000,'x')
  5         where mod(x,2) = 1;

5 rows updated.

ops$tkyte@ORA734.US.ORACLE.COM> commit;

Commit complete.

ops$tkyte@ORA734.US.ORACLE.COM>
ops$tkyte@ORA734.US.ORACLE.COM> analyze table p list chained
rows into chained_rows;

Table analyzed.

ops$tkyte@ORA734.US.ORACLE.COM> select count(*) from
chained_rows;

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

Ok, so now I am in your position, I have chained rows and a
child row dependent on them.  I will clone each row with a dummy
primary key, swap the keys and delete the old row:

ops$tkyte@ORA734.US.ORACLE.COM>
ops$tkyte@ORA734.US.ORACLE.COM>
ops$tkyte@ORA734.US.ORACLE.COM> declare
  2      dummy_primary_key number default -1;
  3      l_rec   p%rowtype;
  4  begin
  5      for cur in ( select * from chained_rows )
  6      loop
  7          select * into l_rec
  8            from p
  9            where rowid = cur.head_rowid;
 10
 10          insert into p
 11          ( x, data1, data2, data3 )
 12          values
 13          ( dummy_primary_key, l_rec.data1,
 14            l_rec.data1, l_rec.data3 );
 15
 15          update p
 16             set x = decode( x, -1, l_rec.x, -1 )
 17           where x in ( -1, l_rec.x );
 18
 18          delete from p where x = -1;
 19      end loop;
 20      commit;
 21  end;
 22  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA734.US.ORACLE.COM> truncate table chained_rows;

Table truncated.

ops$tkyte@ORA734.US.ORACLE.COM> analyze table p list chained
rows into chained_rows;

Table analyzed.

ops$tkyte@ORA734.US.ORACLE.COM> select count(*) from
chained_rows;

  COUNT(*)
----------
         0

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

And now they are gone.  I can do the same with an update to
the child instead:

ops$tkyte@ORA734.US.ORACLE.COM> declare
  2      type myArray is table of rowid index by binary_integer;
  3      l_array myArray;
  4      l_empty myARray;
  5      l_rec   p%rowtype;
  6  begin
  7      for cur in ( select * from chained_rows )
  8      loop
  9          select * into l_rec
 10            from p
 11            where rowid = cur.head_rowid;
 12
 12          l_array := l_empty;
 13          for cur2 in ( select rowid rid from c
                            where x = l_rec.x )
 14          loop
 15              update C
 16                    set x = NULL
 17               where rowid = cur2.rid;
 18
 18              l_array(l_array.count+1) := cur2.rid;
 19          end loop;
 20
 20          delete from p where x = l_rec.x;
 21
 21          insert into p
 22          ( x, data1, data2, data3 )
 23          values
 24          ( l_rec.x, l_rec.data1,
 25            l_rec.data1, l_rec.data3 );
 26
 26          for i in 1 .. l_array.count
 27          loop
 28              update c
 29                 set x = l_rec.x
 30               where rowid = l_array(i);
 31          end loop;
 32      end loop;
 33      commit;
 34  end;
 35  /

PL/SQL procedure successfully completed.

Here the process is more complex (slower), we have to NULL out
the fkeys to the parent for a moment (remembering the rows we
need to fixup later), we delete and reinsert the parent and then
fixup the fkeys again.
Review & Followup
Rating: 5
May 06, 2001
Reviewer: A reader
Excellent !!

Rating: 4
Lots of rows are chained ... April 18, 2002
Reviewer: Lisa
I have a table name API_BLOB_ITEM, it contains long rows,
analyze table api_blob_item list chained
rows into chained_rows;

Table analyzed.
select count(*) from chained_rows;
 COUNT(*)
---------
      563

select count(*) from api_blob_item;

 COUNT(*)
---------
      292

The table only has 3 columns defined:
SQL> desc api_blob_item
 Name                                      Null?    Type
 ----------------------------------------- -------- --------------
 ENTITY_NO                                 NOT NULL NUMBER
 ISSUE                                     NOT NULL NUMBER
 BLOB                                               LONG RAW

The storage is :
initial 40k, next 40k, pct 0, the rest is default.

the blocksize of the database is 8k, you see I only have 292 records but the
chained is up to 563 rows and the file size is growing in a light of speed.
Please quide me to resolve this problem.

Followup:
any table with a LONG or LONG RAW will almost virtually BY DEFINITION have
chained rows.

Nothing you can do about it.  Just insert a row with a BLOB > about 8,000 bytes
and INSTANT chained row!!! a chained row is a row that doesn't fit on a block or
it migrated.  If your rows are less then your blocksize -- they are migrated.
if you rowsize can exceed your blocksize -- they are CHAINED.

USE the correct datatypes for 8.0 and above!!!! use a BLOB, not a long raw.

Rating: 4
April 18, 2002
Reviewer: Lisa
correction-
after I truncated the chained_rows table, the count are 292
rows

Rating: 4
Silly Question April 19, 2002
Reviewer: A reader
Dear Sir,

From your response, following statement has confused me

15          update p
16             set x = decode( x, -1, l_rec.x, -1 )
17           where x in ( -1, l_rec.x );

Why it doesn't violate the primary key constraint ?

Please explain, how the above statement swaps the primary keys of the chained
row with the dummy row ?

Thanks

Followup:
That update updates two records.

The record with x = -1 (the record we inserted)
The record with x = l_rec.x (the original record)

The decode swaps the keys, setting the record with x=-1 to l_rec.x and the
record with x=l_rec.x to -1.  A simple swap.

It does not violate the primary key cause by the end of the statement, the
primary keys are A-OK.

Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int primary key, msg
varchar2(20) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 5, 'This record had 5'
);

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values (-1, 'This record had
-1' );

1 row created.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

         X MSG
---------- --------------------
         5 This record had 5
        -1 This record had -1

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> update t
  2     set x = decode( x, -1, 5, 5, -1 )
  3  /

2 rows updated.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

         X MSG
---------- --------------------
        -1 This record had 5
         5 This record had -1

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

Rating: 4
Paul February 05, 2003
Reviewer: Paul from UK
Can "alter TABLE CUSTOMERS move TABLESPACE" be used to fix chained rows (I've
got about 1 miilion out of 5 million rows chained)?

Followup:
yup -- just remember to kick in a rebuild of all indexes after that.

and make sure the rows are MIGRATED, not truly CHAINED.  make sure they can fit
on a block!

February 05, 2003 Prince -- Thanks for the question regarding "Help on SQL Stmt", version 8.1.7.2
You Asked
Tom,

I have a table with following format,

mytab (
Col1   number,
Col2   varchar2,
Value  number)

Col1     Col2           Col3

1        EndUsers       100
1        Clients         50
2        EndUsers       120
2        Clients         55
3        EndUsers       160
3        Clients         70
4        EndUsers       170
4        Clients         72
5        EndUsers       300
5        Clients        100

What is the best way to get the diff between the adjacent values of Col3 for
each col2.

ex,

EndUsers          Clients
---------------   --------------
0   (100 - 100)    0  (50 - 50)
20  (120 - 100)    5  (55 - 50)
40  (160 - 120)   15  (70 - 55)
10  (170 - 160)    2  (72 - 70)
130 (300 - 170)   28  (100 - 72)

Note: the values in  "( xx - yy )" is only for explaining. It is not needed in
the output.

Also, Col2 can have upto 5 distinct values

Thanks,
and we said...
Seems like more and more of the questions are answered with analytic functions!

Here is the two column example:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select col1,
  2    endusers - nvl( lag(endusers) over ( order by col1 ), endusers) endusers,
  3    clients -  nvl( lag(clients) over ( order by col1 ), clients )  clients
  4    from (
  5  select col1,
  6             max(decode(col2,'Endusers',value,null)) endusers,
  7             max(decode(col2,'Clients',value,null))  clients
  8    from t
  9   group by col1
 10             )
 11  /

      COL1   ENDUSERS    CLIENTS
---------- ---------- ----------
         1          0          0
         2         20          5
         3         40         15
         4         10          2
         5        130         28

ops$tkyte@ORA817DEV.US.ORACLE.COM>
Review & Followup
Rating: 3
can you explain the query December 21, 2001
Reviewer: raju
tom,

can you please explain the query.....

Followup:
sure.  The query is:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select col1,
  2    endusers - nvl( lag(endusers) over ( order by col1 ), endusers) endusers,
  3    clients -  nvl( lag(clients) over ( order by col1 ), clients )  clients
  4    from (
  5  select col1,
  6             max(decode(col2,'Endusers',value,null)) endusers,
  7             max(decode(col2,'Clients',value,null))  clients
  8    from t
  9   group by col1
 10             )
 11  /

The inline view:

  5  select col1,
  6             max(decode(col2,'Endusers',value,null)) endusers,
  7             max(decode(col2,'Clients',value,null))  clients
  8    from t
  9   group by col1

is used to TRANSPOSE or PIVOT the result set.  It takes:

Col1     Col2           Col3

1        EndUsers       100
1        Clients         50

and (before doing the GROUP BY) turns it into:

col1  endusers   clients
1     100        <null>
1    <null>        50

The max and group by turns that into:

col1  endusers   clients
1     100           50

so, now we have a set that looks like:

1      100        50
2      120        55
....

We use the ANALYTIC functions then to get the "row in front of our row after
ordering by COL1".  That was done with:

  2    endusers - nvl( lag(endusers) over ( order by col1 ), endusers) endusers,
  3    clients -  nvl( lag(clients) over ( order by col1 ), clients )  clients

the LAG function can get the row that was logically before our row (lead can get
the row AFTER our row)..  If that is NULL (as it will be for the first row in
the set), we return the value of the current row using NVL().  We subtract...

Analytic functions are very powerful -- i devoted an entire chapter to just them
in my book.  I think they are the coolest addition to SQL since SELECT.

Rating: 4
Best of the best December 21, 2001
Reviewer: A reader

Rating: 5
best answer December 21, 2001
Reviewer: raju
thanks for the answer,

tom can we use these in analytic functions in pl/sql.

Followup:
Yes, in 8i you'll need to "hide them" from plsql using

o dynamic sql
o a view

In 9i, they are recognized.

See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:186812348071
for an example of what I mean.

Rating: 5
about the inline view December 21, 2001
Reviewer: raju
for the inline view, will the query work if col1 and col2 combination
duplicates....

Col1     Col2           Col3

1        EndUsers       100
1        EndUsers       200
1        Clients         50
2        EndUsers       120
2        Clients         55
2        Clients         90
3        EndUsers       160
3        Clients         70
3        EndUsers       380
3        Clients         90
4        EndUsers       170
4        Clients         72
5        EndUsers       300
5        Clients        100

Followup:
Yes and No -- you would get the MAX value for endusers. You would probably have
to use SUM() instead of max and contribute 0 instead of NULL, eg:

instead of:

 6             max(decode(col2,'Endusers',value,null)) endusers,

code:

 6             sum(decode(col2,'Endusers',value,0)) endusers,

(assuming you wanted to SUM, you could avg, whatever...)

Rating: 5
Great answer. December 21, 2001
Reviewer: Prince
Thank you tom,

I thought, Analytical Function would be the best solution. But couldn't think of
any way. (these analytical functions seem to be harder to grasp). Probably, I
have to read the chapter on your book more times.

Oracle docs have to be improved on this one.

Followup:
see also:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76994/analysis.htm#11237
a section in the docs most people miss.

Also, I'd like to point out that it wasn't a great understanding of analytic
functions that permitted me to answer this question.  It was mostly many years
of writing SQL virtually everyday, understanding how this stuff really works,
being able to visualize the question and come up with the answer.  This answer
is really quite complicated when you break it down -- pivoting a result set,
using the lag() function, etc.  It looks simple but there is alot of a
complexity there.
The documentation explains whats there -- it cannot possible have a section on
howto answer every question you might ever ask fo the database.  That is
something we have to do -- taking the functions that are there and learning how
to apply them to the problem at hand.

Rating: 5
I totally agree with ur point December 21, 2001
Reviewer: Prince..
Tom,

Actually I got it in the Pivotal form and then I didn't know what to do (The lag
function never came across my mind and even if it, I don't know whether I could
have done). As you pointed out, it is applying the right function in the right
fashion.

Your answers not only answer the corresponding question, but answers many
others.

Thank you for your good work.

Rating: 4
How select many rows into one row February 05, 2003
Reviewer: Marc from Switzerland
Hi Tom

How can I aggregate many rows into one row. See following example:

select decode(user_id, 0,username,null) col1,
       decode(user_id, 5,username,null) col2,
       decode(user_id,17,username,null) col3,
       decode(user_id,18,username,null) col4,
       decode(user_id,19,username,null) col5
from   all_users;

This query will give many rows with null values in col1 till col5. My question
is, how can i get only one row of this dataset. Many thanks for your answer.

Followup:
select max( decode( user_id, 0, username, null) ) col1,
       .....

just max em all.

February 05, 2003 NAVSHAD -- Thanks for the question regarding "v$session_longops", version 8.1.7
You Asked
Tom,

The v$session_longops  view is one of the greatest thing Oracle has put into
it's DB. (In my opinion).  I work with databases for a few  ERP applications,
and when there is some really large update or delete going on, it is extremely
accurate in reflecting approximately how much time is left.

My issue is with selects.  Some selects show up in v$session_longops,and some
donot. I am aware that if the cost of the query is less than 1000, or whatever,
it does  not work.  But I'm talking about huge selects, that have very high
costs and take a  long time to finish.  Why don't they show up.  I always have
timed_statistics set to true, and sql trace is off.

Do you think it would be possible for someone to code a more intelligent version
of v$session_longops?  One that does not just show how much time is left for
that particular segment, but shows how much time is left for the overall
transaction?  If I knew some of the algorithms it uses, I guess it's  possible
to code a better one, but there are just so many factors involved.

Thanks again for this fantastic website.  This site runs CIRCLES around
metalink.  AND I MEAN CIRCLES!!

Best Regards.
and we said...
it depends on how they process.

If the query has a big full scan -- that'll show up -- since it is the FULL SCAN
that is instruemented.  If on the other hand, the query is in nested loops
"heck" cause the developers thought "indexes are the only way to process any
query", then this long running query involves billions of tiny steps -- none of
whice are long enough to be placed in there.

There is no way for us to even know "how long a transaction would be".  that is
totally up to you guys -- when you code it.

It sounds like some of these selects are index happy and should be "full scan"
happy -- happens all of the time, especially when you use the RBO instead of the
CBO.  The rbo will blindly using an index -- even when that means the query will
take forever to finish.
Review & Followup
Rating: 3
Well .. February 05, 2003
Reviewer: Navshad from San Diego
It's not really  up to us to code it - it's  upto the third party to do so, and
frankly it's never coded too gracefully.  (This  is an ERP application  that is
over 500 GB).

We are running the cost based optimizer,  but you are  right about the full
scans.

Thanks for your  help.

February 05, 2003 Tatiane -- Thanks for the question regarding "Temporary Tablespace (8 vs 8i)", version 8.1.7
You Asked
Hello Tom.

What is the difference between a temporary tablespace up to Oracle8 (create
tablespace ... temporary) and from Oracle 8i up (create temporary tablespace
...) ?

I wish you a good day.
and we said...
one uses datafiles (create tablespace ... temporary) the other uses tempfiles
(create temporary tablespace...)

datafiles should be backed up, it is a pain to restore without them.
tempfiles -- cannot be backed up, well you can but restoring them is pointless.

It is a more efficient temporary tablespace and can never ever hold a permanent
object (wereas the other type can -- you can alter the tablespace).

create temporary tablespace also does not init the files so it happens "very
fast"
February 05, 2003 Jan -- Thanks for the question regarding "inconsistent datatypes when using inline view and (case when then end)", version 8.1.7
You Asked
Hello Tom,

Let's have a table:
create table tmp (D1 DATE, xD1 DATE);

I would like to substitute D1 with xD1 when D1<xD1 :
select (case when D1<xD1 then xD1 else D1 end) from tmp;

works well.

Now I would like to substitute D1 with another date (parameter), which is not a
part of table.

select (case when D1<Y then Y else D1 end)
from
(select
   D1, xD1,
   to_date('01-JAN-2003','DD-MON-YYYY') Y  from tmp
);

ORA-00932: inconsistent datatypes

I don't understand, why I get inconsistent datatypes when all fields are DATE
format.
Is there a restriction for (case when then end) or database is not able to
propagate datatype when using select * from (select ...); ?

I'm running AIX 4.3 with Oracle8i Enterprise Edition Release 8.1.7.4.0 - 64bit
Production

Thanks in advance for your comments
and we said...
appears to be fixed in 9i and up -- must be a bug.  this is a workaround in 8i:

select (case when D1<Y then Y else D1 end)
from
(select
   D1, xD1,
   cast( to_date('01-JAN-2003','DD-MON-YYYY') as date ) y  from tmp
);
Review & Followup
Rating: 5
Excellent workaround February 06, 2003
Reviewer: JanK from Prague, Czech Republic
Thanks for "tricky" workaround.

February 05, 2003 Boris -- Thanks for the question regarding "Display field value based on another field value", version 8.i
You Asked
Hi Tom,
I have a select statement like this:
select "cp",
       (("CurrentF"-"PriorF")/"PriorF")*100 "% Change"
from (select T1."PRD_KEY" "cp",

sum(T1."UNITS_OPEN_F")+sum(T1."UNITS_DISPLAY_F")+sum(T1."UNITS_PIPE_F") "PriorF"
      from "LOGILITY"."AGGREGATE_TABLE" T1,
           "LOGILITY"."ARCHIVED_PERIOD" T2
      where T1."ARCHIVED_DATE_KEY"=T2."ADT_KEY"
            and T1."ARCHIVED_DATE_KEY"<>0
            and T2."DATAID" ='Current'
      group by T1."PRD_KEY"
      order by T1."PRD_KEY" desc) D1,

    (select T1."PRD_KEY" "cf",

sum(T1."UNITS_OPEN_F")+sum(T1."UNITS_DISPLAY_F")+sum(T1."UNITS_PIPE_F")
"CurrentF"
     from "LOGILITY"."AGGREGATE_TABLE" T1,
           "LOGILITY"."ARCHIVED_PERIOD" T2
     where T1."ARCHIVED_DATE_KEY"=T2."ADT_KEY"
           and T1."ARCHIVED_DATE_KEY"<>0
           and T2."DATAID" ='Current+1'
     group by T1."PRD_KEY"
     order by T1."PRD_KEY" desc) D2
where  "cp"="cf"
order by "% Change" desc

I need to add one more field to this select that will translate value of the "%
Change" field into different ranges such as “-20% and Down”,  “-10% and Down” ,
“-5% and Down” ,  0% and Down” ,  0% and Up” ,  “5% and Up” ,  “10% and Up” ,
“20% and Up”.
Please Help
Thanks a Lot.
and we said...
select "cp", "% Change",
       case when "% Change" <= -20 then "-20% and Down"
            when "% Change" <= -10 then "-10% and Down"
            when "% Change" <= 0   then "0% and Down"
            ...
            else "20% and Up"
        end new_field
from
(
select "cp",
       (("CurrentF"-"PriorF")/"PriorF")*100 "% Change"
from (select T1."PRD_KEY" "cp",

sum(T1."UNITS_OPEN_F")+sum(T1."UNITS_DISPLAY_F")+sum(T1."UNITS_PIPE_F") "PriorF"
      from "LOGILITY"."AGGREGATE_TABLE" T1,
           "LOGILITY"."ARCHIVED_PERIOD" T2
      where T1."ARCHIVED_DATE_KEY"=T2."ADT_KEY"
            and T1."ARCHIVED_DATE_KEY"<>0
            and T2."DATAID" ='Current'
      group by T1."PRD_KEY"
      order by T1."PRD_KEY" desc) D1,

    (select T1."PRD_KEY" "cf",

sum(T1."UNITS_OPEN_F")+sum(T1."UNITS_DISPLAY_F")+sum(T1."UNITS_PIPE_F")
"CurrentF"
     from "LOGILITY"."AGGREGATE_TABLE" T1,
           "LOGILITY"."ARCHIVED_PERIOD" T2
     where T1."ARCHIVED_DATE_KEY"=T2."ADT_KEY"
           and T1."ARCHIVED_DATE_KEY"<>0
           and T2."DATAID" ='Current+1'
     group by T1."PRD_KEY"
     order by T1."PRD_KEY" desc) D2
where  "cp"="cf"
)
order by "% Change" desc
Review & Followup
Rating: 4
Thanks a lot Tom February 06, 2003
Reviewer: A reader

February 06, 2003 bala -- Thanks for the question regarding "SQL Problem", version 8.1.7
You Asked
We encountered a strange problem, where my concurrent program(under oracle
applications) took a long time. I am pasting the problem sql trace below

Trace file part
---------------
=====================
PARSING IN CURSOR #46 len=334 dep=1 uid=173 oct=3 lid=173 tim=1312135361
hv=2325481649 ad='485ec730'
select msii.transaction_id
     from mtl_system_items msi, mtl_system_items_interface msii
    where msii.inventory_item_id is NULL
     and msii.organization_id is not NULL
     and msii.process_flag = 1
     and msii.set_process_id = :set_id_bind
     and msii.organization_id = msi.organization_id and  msi.segment1 =
msii.segment1
END OF STMT
PARSE #46:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=1,og=0,tim=1312135361
EXEC #46:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=1312135361
*** 2003-01-30 16:26:51.559
FETCH
#46:c=171678,e=170690,p=22,cr=9766936,cu=0,mis=0,r=0,dep=1,og=4,tim=1312306051
STAT #46 id=1 cnt=0 pid=0 pos=0 obj=0 op='NESTED LOOPS '
STAT #46 id=2 cnt=3400 pid=1 pos=1 obj=38506 op='INDEX FULL SCAN '
STAT #46 id=3 cnt=0 pid=1 pos=2 obj=38266 op='TABLE ACCESS BY INDEX ROWID
MTL_SYSTEM_ITEMS_INTERFACE '
STAT #46 id=4 cnt=193780925 pid=3 pos=1 obj=38279 op='INDEX RANGE SCAN '
=====================

TKProf Output
-------------
********************************************************************************

select msii.transaction_id
     from mtl_system_items msi, mtl_system_items_interface msii
    where msii.inventory_item_id is NULL
     and msii.organization_id is not NULL
     and msii.process_flag = 1
     and msii.set_process_id = :set_id_bind
     and msii.organization_id = msi.organization_id and  msi.segment1 =
msii.segment1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        1   1716.78    1706.90         22    9766936          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5   1716.78    1706.90         22    9766936          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 173     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  NESTED LOOPS
   3400   INDEX FULL SCAN (object id 38506)
      0   TABLE ACCESS BY INDEX ROWID MTL_SYSTEM_ITEMS_INTERFACE
193780925    INDEX RANGE SCAN (object id 38279)

********************************************************************************

The maximum number of rows we ever had in this table is around 70000 why does
the index range scan 193 million records. What could possibly be the problem?
Not able to trace out the problem. The problem was solved after we freshly
lodaded the table, analyzed and re ran the concurrent process. Please throw some
light.
When does a fetch take long time like the way it has happened here?
and we said...
ops$tkyte@ORA817DEV> select 3400*70000 from dual;

3400*70000
----------
238,000,000

is the possible number of index range scan rows.  You full scanned that first
index.  That returned 3400 rows.  For each of those 3,400 rows -- you took the
resulting (set_process_id, organization_id, segment1 ) value and went to the
index (whatever index object id 38279) is and found all of the records that
matched those.

Then, you went to the base table and found the rest of the predicate was not
satisfied.

Sounds like you have an index (38279) that is on very non-selective data and it
is killing you.

One would need to know the tables and indexes (and especially what index 38279
is) in order to determine anything else.  But that is what happened -- 193
million is far short of the maximum 238million it could have been.

Looks like you have an index that should never have been created.  A hash join
would be much prefered here over that nested loops "heck" you've gotten yourself
into.  See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6749454952894
for an example very much like your current case.
Review & Followup
Rating: 5
The index details here February 06, 2003
Reviewer: A reader
I am sorry for not providing the index details with the quesition. I think the
explanation might change now.
index 38506 is on table msi and the columns are
(ORGANIZATION_ID,SEGMENT1)
index 38279 is on table msii and the columns are
(INVENTORY_ITEM_ID,ORGANIZATION_ID)

the problem was solved(I mean it ran fast) later the index used was different in
the plan i paste the trace here
WE ANALYZED THE TABLE BEFORE THE SECOND RUN

Iam not pasting the trace part of it as there are no extra details in that than
the TKProf here
After
********************************************************************************

select msii.transaction_id
     from mtl_system_items msi, mtl_system_items_interface msii
    where msii.inventory_item_id is NULL
     and msii.organization_id is not NULL
     and msii.process_flag = 1
     and msii.set_process_id = :set_id_bind
     and msii.organization_id = msi.organization_id and  msi.segment1 =
msii.segment1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.01       0.00          0          0          0           0
Fetch        1      0.66       0.65          0      62068          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.67       0.65          0      62068          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 173     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  NESTED LOOPS
  61470   INDEX FULL SCAN (object id 38506)
      0   TABLE ACCESS BY INDEX ROWID MTL_SYSTEM_ITEMS_INTERFACE
  61485    INDEX RANGE SCAN (object id 38289)

********************************************************************************

index 38506 is still the same

the new index used this time 38289 is on table msii and on columns
(ORGANIZATION_ID,SEGMENT1)

any new ideas?

Followup:
any ideas for what?  looks like "problem solved" ?

I mean -- something is going to be full scaned here unless you have an index on

set_process_id,process_flag

there is nothing for the predicate to "grab onto" and avoid a full scan....

Rating: 5
The index was never used February 06, 2003
Reviewer: Bala
Tom,
In fact I created the index you were talking about on columns
(inventory_item_id
organization_id
process_flag
set_process_id) of table msii. though I could have dropped one of the previous
indexes(object 38279 - the one that was used in the slow query, original
posting)index as the new index has those columns as the leading columns, I did
not as it was created by oracle it self (The table is an Oracle Applications
Interface table). But the data in columns set_process_id and process_flag is
constant through out(majority of the times). So it really doesent make sense to
use an index there.

February 06, 2003 Robert -- Thanks for the question regarding "SQLJ Bind Variables", version 8.1.6
You Asked
Hi Tom,

We are currently designing an app to run in JDBC/SQLJ/JSP, the final decision
has still to be made.  As I am not a Java developer I don't know too much about
the coding.  As a database guy I have insisted all queries on the current apps
use bind variables and have been able to find the resources to help the
developers write their code (coldfusion mainly) accordingly.  The Java
developers have asked me to give them an example of bind variables for Java but
I have been unable to find any clear ones.

I read one of the posts here and found you saying:
"In Java -- you would use a prepared/callable statement and prepare it ONCE per
session -- not once per execution."

Which I understand but it is not the syntax.  Is the syntax on your book, page
442, coupled with the afore mentioned statement the Holy Grail that I seek?

Thanks

Robert
and we said...
Well, the decision will be between JDBC and SQLJ as JSP is not a database
"access" layer.  You'll be using JSP's that either use jdbc or SQLJ to access
the db...

Page 442 would be an excellent example -- on the web (where I pulled that
example from), the URL would be:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1478004815829

A SQLJ example would be:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:439619916584

In my opinion, the best Java programs are those that have NO "insert", "update",
"delete" and even "select" statements in them.  They would be chock full of
"begin / end" blocks (or CALL statements if they like).  The database stuff
would be done in good old fashioned stored procedures where I could trace them
easily, tune them easily and control access to the database easily.
Review & Followup
Rating: 5
July 03, 2002
Reviewer: Robert from Cork, Ireland
Perfect, thanks Tom.

Rating: 3
But for those of us slow on the uptake..... February 06, 2003
Reviewer: Steve from UK
I've taken this from the Oracle documentation (yes I do read it  - but probably
not enough!).  Am I right in thinking it doesn't use bind variables?  If I am,
how could it be re-written to do so?

User-Specified Query--JDBCQuery.jsp
This page queries the scott.emp table according to a user-specified condition
and outputs the results.

<%@ page import="java.sql.*" %>

<HTML>
<HEAD> <TITLE> The JDBCQuery JSP  </TITLE> </HEAD>
<BODY BGCOLOR=white>

<% String searchCondition = request.getParameter("cond");
   if (searchCondition != null) { %>
      <H3> Search results for : <I> <%= searchCondition %> </I> </H3>
      <%= runQuery(searchCondition) %>
      <HR><BR>
<% }  %>

<B>Enter a search condition:</B>
<FORM METHOD=get>
<INPUT TYPE="text" NAME="cond" SIZE=30>
<INPUT TYPE="submit" VALUE="Ask Oracle");
</FORM>
</BODY>
</HTML>
<%!
  private String runQuery(String cond) throws SQLException {
     Connection conn = null;
     Statement stmt = null;
     ResultSet rset = null;
     try {
        DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
        conn = DriverManager.getConnection((String)session.getValue("connStr"),
                                           "scott", "tiger");
        stmt = conn.createStatement();
        rset = stmt.executeQuery ("SELECT ename, sal FROM scott.emp "+
                                  (cond.equals("") ? "" : "WHERE " + cond ));
        return (formatResult(rset));
     } catch (SQLException e) {
         return ("<P> SQL error: <PRE> " + e + " </PRE> </P>\n");
     } finally {
         if (rset!= null) rset.close();
         if (stmt!= null) stmt.close();
         if (conn!= null) conn.close();
     }
  }

  private String formatResult(ResultSet rset) throws SQLException {
    StringBuffer sb = new StringBuffer();
    if (!rset.next())
      sb.append("<P> No matching rows.<P>\n");
    else {  sb.append("<UL><B>");
         do {  sb.append("<LI>" + rset.getString(1) +
                            " earns $ " + rset.getInt(2) + ".</LI>\n");
            } while (rset.next());
         sb.append("</B></UL>");
    }
    return sb.toString();
  }
%>

Thanks

Followup:
yuck -- bad example eh.

Here they are taking ANY where clause against emp.  one could type in "1=1" or
"ename = 'KING'" or "empno = 1234 or to_char(sysdate,'dy') = 'sat'" or whatever.

In order to "bind" that you would have to

stmt.execute( 'alter session set cursor_sharing=force');

bit of an atypical example though -- hopefully we wouldn't be taking a predicate
like that and opening ourselves upto a SQL Injection security bug ;)

Rating: 4
And...... February 06, 2003
Reviewer: steve from UK
Great!  We have a third party application that uses jsp's and the users keep
complaining about the speed.  As far as I can see they don't use bind variables
(anywhere) and I want to get them to do so.  In cases such as above would I be
able to use a log on trigger to set the session to cursor share?  Ideally, I'd
like to get them to change their code (but it could be a long haul).  Here's a
fairly typical example:

<%
Logger logger = Logger.getLogger("uk.co.telepartner.webtext.Inbox.jsp");
String userName = (String)(session.getAttribute("userName"));
String sql;
if("admin".equals(userName)) {
  sql  = "SELECT safsmain.DESTID, safsmain.originid, safsstatus.statusdate,
safsdata.dataid, safsdata.dataitem";
  sql += " from safsmain, safsdata, safsstatus";
  sql += " where safsmain.dataid = safsdata.dataid";
  sql += " and safsmain.safsid=safsstatus.safsid and safsstatus.statusid =
'SUBMITED'";
  sql += " order by safsstatus.statusdate desc";
} else {
  sql  = "SELECT safsmain.DESTID, safsmain.originid, safsstatus.statusdate,
safsdata.dataid, safsdata.dataitem";
  sql += " from safsmain, safsdata, safsstatus";
  sql += " where safsmain.dataid = safsdata.dataid and safsmain.destid = '" +
userName + "'";
  sql += " and safsmain.safsid=safsstatus.safsid and safsstatus.statusid =
'SUBMITED'";
  sql += " order by safsstatus.statusdate desc";
}
%>

<table Align="center" border="1"  style="HEIGHT: 116px; WIDTH: 527px">
<tr>
<th><font face="arial" size="3"><tp:getLocalText name="locale"
key="WebtextInbox.to"/></font></th>
<th><font face="arial" size="3"><tp:getLocalText name="locale"
key="WebtextInbox.from"/></font></th>
<th><font face="arial" size="3"><tp:getLocalText name="locale"
key="WebtextInbox.received"/></font></th>
<th><font face="arial" size="3"><tp:getLocalText name="locale"
key="WebtextInbox.message"/></font></th></FONT>
</tr>
<%
DataSource dsSafs         = null;
Connection connSafs       = null;
PreparedStatement psInbox = null;
ResultSet rsInbox         = null;
try {
  dsSafs   =
(DataSource)getServletContext().getAttribute("uk.co.telepartner.database.safs");
  connSafs = dsSafs.getConnection();
  psInbox  = connSafs.prepareStatement(sql);
  rsInbox  = psInbox.executeQuery();
  while(rsInbox.next()) {
  %>
    <tr>
    <td width="100"><font size="2"><%= rsInbox.getString("destid")%></font></td>
    <td width="100"><font size="2"><%=
rsInbox.getString("originid")%></font></td>
    <td width="100"><font size="2"><%=
locale.getDateTimeString(rsInbox.getTimestamp("statusdate"))%></font></td>
    <td width="100"><font size="2"><%
  String strDataid = rsInbox.getString("dataid");
  byte[] buffer  = new byte[10];
  InputStream in = rsInbox.getBinaryStream("dataitem");
  in.read(buffer);
  in.close();
  String strMsgStart = new String(buffer);
  %><%= strMsgStart %>...</font></td>
    <td width="50" align="middle">
    <form action="details" method="post">
    <input type="hidden" value="<%= strDataid %>" name="txtid" style="HEIGHT:
22px; WIDTH: 28px">
    <input type="submit" value="Details" name="submit" style="HEIGHT: 24px;
WIDTH: 67px">
    </td></FORM>
    </tr>
  <%
  }
} catch (Exception e) {
//  logger.error(new StackTracePrinter().getStackTraceAsString(e));
} finally {
  if( connSafs != null ){ connSafs.close(); }
  if( psInbox  != null ){ psInbox.close();  }
  if( rsInbox  != null ){ rsInbox.close();  }
}
%>
</table>
</body>
</html>

Obviously the userName in the else statement doesn't use a bind - would the use
of cursor force solve the issue or how could I get them to re-code to use a
bind?

Thanks

Steve

Followup:
you can set cursor sharing -- while they set about using PreparedStatements
instead of Statements.

Ask them "hey, what happens if someone entered something like

x' or 'a'='a

in a screen where you do code like:

sql += " where safsmain.dataid = safsdata.dataid and safsmain.destid = '" +
userName + "'";

wouldn't that result in a predicate like:

where col = col and col = 'x' or 'a'='a'

??

Have them search google.com for:

"SQL Injection"

and see what they say.

Rating: 3
bind varriable February 06, 2003
Reviewer: amit from india

Rating: 3
bind varriable February 06, 2003
Reviewer: amit from india

Rating: 5
SQL Injection February 07, 2003
Reviewer: Steve from UK
Thanks for the info - I hadn't come across that before!

February 06, 2003 Neil -- Thanks for the question regarding "Exporting from 8.0.4 then importing in to 8.1.5", version 8.1.5
You Asked
Looking at documentation about exporting/importing for
migration/upgrade to 8i, i have found that i need to run
catexp.sql in the oracle 8i db.  The script says to connect as
sys or internal.  How? How can i determine the logins?  There is
no security problems as i am running oracle locally on my
machines.

Also, is my upgrade procedure correct.
1.  Export from 8.04 using exp80.
2.  On another machine install 8.1.5.
3.  Create a new database in 8.1.5.
4.  Run catexp.sql ?????
5.  Import the dump file(from 8.0.4) into 8.1.5

Note. Operating system is NT.

Thanks in advance for your assistance.
and we said...
catexp.sql is normally run as part of the install.  It would
need be done only if it was not.  It should be there already.
If not, your DBA would run it.  They would connect as SYS or
INTERNAL into the target 8.1.5 database and run it for you.
Internal is SYS so either will do.

you would:

1) exp from 8.0.4 using exp80
2) install 8.1.5
3) create the database
4) catexp only if it wasn't run.  it won't hurt to run it again
   but its installed by default
5) transfer the dmp file to this machine
6) using IMP from 8.1.5, import the file.
Review & Followup
Rating: 3
IMP contraint error January 09, 2002
Reviewer: Sivababu from INDIA
Hello TOM,
   I'm having problem with importing. I took the dump from Oracle 8.1.7 On
Linux. But when i want to import into Oracle 9i on Linux it is giving error like

ALTER TABLE TEST
ADD PRIMARY KEY(NO) TABLESPACE TSCHARLI etc.

i'm getting this error IMP-00017.
If i run it thru Sqlplus i got "Can not validate (WINDEMO.SYS_C00890) constraint
violated'.
When i checked the records in the table i found some duplicate record. How it is
possible?. But it is working in same version 8.1.7.

Is there anything to do with catexp.sql.

Thanks and expecting your reply.

Followup:
1) make sure you did not import 2 times.

2) goto to source database and do a

   select primary_key, count(*)
     from table
    group by primary_key
   having count(*) > 1;

to verify the source data.

Rating: 3
Imp problem January 10, 2002
Reviewer: sivababu from INDIA
Hello TOM,
  Thanks for fast response.

THIS IS EXPORTING LOG FILE:

Connected to: Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production
With the Partitioning option
JServer Release 8.1.7.0.0 - Production
Export done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set

About to export specified users ...
. exporting pre-schema procedural objects and actions
. exporting foreign function library names for user TOUR
. exporting object type definitions for user TOUR
About to export TOUR's objects ...
. exporting database links
. exporting sequence numbers
. exporting cluster definitions
. about to export TOUR's tables via Conventional Path ...

             ----------- other tables  ---------

. . exporting table               KOSTENARTENSTAMM         16 rows exported

             ----------- other tables  ---------

. exporting synonyms
. exporting views
. exporting stored procedures
. exporting operators
. exporting referential integrity constraints
. exporting triggers
. exporting indextypes
. exporting bitmap, functional and extensible indexes
. exporting posttables actions
. exporting snapshots
. exporting snapshot logs
. exporting job queues
. exporting refresh groups and children
. exporting dimensions
. exporting post-schema procedural objects and actions
. exporting statistics
Export terminated successfully without warnings.

THIS IS IMPORTING LOG FILE:

Connected to: Oracle9i Enterprise Edition Release 9.0.1.0.0 - Production
With the Partitioning option
JServer Release 9.0.1.0.0 - Production

Export file created by EXPORT:V08.01.07 via conventional path
import done in WE8ISO8859P1 character set and UTF8 NCHAR character set
import server uses US7ASCII character set (possible charset conversion)
export server uses WE8ISO8859P1 NCHAR character set (possible ncharset
conversion)
. importing TOUR's objects into TOUR

             ----------- other tables  ---------

. . importing table             "KOSTENARTENSTAMM"         16 rows imported
IMP-00017: following statement failed with ORACLE error 1452:
 "CREATE UNIQUE INDEX "I_CODE_TYPE" ON "KOSTENARTENSTAMM" ("KOST_TYPEN" )  PC"
 "TFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 16384 NEXT 40960 MINEXTENT"
 "S 1 MAXEXTENTS 505 PCTINCREASE 50 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL"
 " DEFAULT) TABLESPACE "TSCHARLI" LOGGING"
IMP-00003: ORACLE error 1452 encountered
ORA-01452: cannot CREATE UNIQUE INDEX; duplicate keys found
IMP-00017: following statement failed with ORACLE error 2437:
 "ALTER TABLE "KOSTENARTENSTAMM" ADD  PRIMARY KEY ("KOST_TYPEN") ENABLE"
IMP-00003: ORACLE error 2437 encountered
ORA-02437: cannot validate (TOUR.SYS_C0010464) - primary key violated

             ----------- other tables  ---------

Why the same export file is giving the error?. I didn't get it. Is it somthing
related to setting character set?.

thanks and expecting the reply.

regards,
sivababu

Followup:
0) yes, the character set conversion can cause this.  You are going from an 8bit
character set with 256 possible characters to a 7bit character set with 128
possible characters.  You could definitely introduce dups this way.

1) make sure you did not import 2 times.

2) goto to source database and do a

   select primary_key, count(*)
     from table
    group by primary_key
   having count(*) > 1;

to verify the source data.

As there are ONLY 16 rows -- it should be trivial for you to manually check
this.  Compare the results of

SELECT DUMP(KOST_TYPEN) from "KOSTENARTENSTAMM"

when done in the source and target databases.

Rating: 3
Imp Problem January 10, 2002
Reviewer: sivababu from INDIA
Hello TOM,
   I got it. it is the problem of Character set.

Thanks.

with regards,
sivababu

Rating: 3
IMP problem January 10, 2002
Reviewer: A reader
Hello TOM,
   Pl see the log message.

Export file created by EXPORT:V08.01.07 via conventional path
import done in WE8ISO8859P1 character set and UTF8 NCHAR character set
import server uses US7ASCII character set (possible charset conversion)
export server uses WE8ISO8859P1 NCHAR character set (possible ncharset
conversion)
.

The export server set in German standard. But import server et in American
language. Where i have to change US7ASCII to WE8IS08859P1 in importing server.
But i changed german language in oracle.sh.

How i'm doing the exp/imp is,

       Oracle 8.1.7 on NT - old dump
       Oracle 9i on Linux - new dump
       Oracle 8.1.7 client (my machine ) - Doing export/import utlity used to
export from NT and import into Linux.

thanks in advance.

with regards,
sivababu

Rating: 3
Character setting January 10, 2002
Reviewer: sivababu from INDIA
Hello TOM,
     The character set is not working now. I set it in the following way in
oracle.sh. like
 NLS_LANG = American_America.WE8ISO8859P1

But The german character's is not stored in the table.

 create table t(a varchar(10));

 insert into  t values('ü+öä');

 select * from t;
 -----
 u+oap

How can i set the character set globally and session based.

Thanks and expecting your mail.

with regards,
sivababu

Followup:
You have to set it in the clients environment.  Not just in Oracles environment.

Rating: 3
Exporting from 8.0.4 then importing in to 8.1.5", version 8.1.5 January 11, 2002
Reviewer: Hassan Ayub from Islamabad,PAKISTAN
Hi there,
    I just went through your comment regarding exporting from a lower version of
Oracle and then importing into a higher version. It was very
informative...thanks a lot.
   Now I was wondering , if there is any possibility that one can do the other
way around..I mean exporting from a higher version and then importing to a lower
version of Oracle, cause I have tried it several times but the import procedure
always ends up without succession.

Thanks

Hassan

Followup:
You have to use the LOWER version export against the HIGHER version.

Use 8.0 exp against 8.1

Rating: 3
Exporting from 8.0.4 then importing in to 8.1.5", January 11, 2002
Reviewer: Hassan Ayub from Islamabad,PAKISTAN
Hi there,
    Tom, could you be a little more specific, what I need to know is that when I
export a table from Oracle 8i and when I try to import it into Oracle 8, it
always give errors and the table is not imported at all.

Thanks

Hassan

Followup:
no, can you be more specific.

did you use the 8.0 export tool?
did you use the 8.0 import tool?

if the answer to either is no, do that.

if the answer to both is yes, one would need to see an error message in order to
help you out.

Also -- your subject says "export from 8.0, import to 815", your body says
"export from 8i, import to 8.0".  which is it???

Rating: 2
Exporting From 8.1 and then Importing into 8.0 January 11, 2002
Reviewer: Hassan Ayub from Islamabad,PAKISTAN
Hi there,
     The reason why I was using the title "Exporting from 8.0.4 then importing
in to 8.1.5" because I did'nt want to post a new question; since my question was
relevant to this one, thats why I was using the same question thread.
Anyway I have used the exp80 and imp80 (Oracle 8.0) and exp and imp (Oracle8.1)
both, but when I try to import a dmp file which was exported from a Oracle 8i
database to a Oracle 8 database, the DOS screen simply closes without giving any
messages and the table is not imported at all. That is why I cannot give you the
errors given.
 I hope this is pretty specific now.

thanks

Hassan

Followup:
Fire up the dos window and type in the IMP80 .....

that way the window won't "go away" and we'll see what it says.

Rating: 2
Exporting from Oracle 8i and Importing into Oracle 8 January 12, 2002
Reviewer: Hassan Ayub from Islamabad,PAKISTAN
Hi Tom,
    We run the the imp80/imp and exp80/exp at the command line and this always
open the DOS window, so how can I avoid this? I again tried importing a table in
Oracle 8 which was exported in Oracle 8i and as I said the DOS window closes
right after the it asks you for the buffer size and obviously no errors are
reported. Please let me know if there is any possibilty that I can achieve this
task?

regards

Hassan

Followup:
Well, I've never seen it open a new window, when I run imp -- it runs in the
same window (gotta love windows don't you -- not)

Anyway, don't do it interactive, use the imp command line like this:

imp userid=u/p ....<other paramters>.... LOG=MY_LOG.TXT

read my_log.txt after its done.

Rating: 3
Exporting from Oracle 8i and then Importing into Oracle 8 January 12, 2002
Reviewer: Hassan Ayub from Islamabad, PAKISTAN
Hi there,
    The my_log.txt file contains the following errors:

Connected to: Personal Oracle8 Release 8.0.3.0.0 - Production
With the Partitioning option
PL/SQL Release 8.0.3.0.0 - Production

IMP-00002: failed to open EXPDAT.DMP for read
IMP-00021: operating system error - error code (dec 2, hex 0x2)
IMP-00009: abnormal end of export file
IMP-00000: Import terminated unsuccessfully

Followup:
ok, silly question here -- is expdat.dmp THERE, in whatever magic directory
windows is running your program in.

Try using the FULLY QUALIFIED FILENAME.

I still have no idea what kind of setup you have whereby when you type in:

C:\> imp80

it starts it in another WINDOW.  That just seems "wrong".  Are you SURE you are
starting a dos window and typing in imp80 on a command line?  are you in the
right directory????

Rating: 2
Importing into Oracle 8 using export file from Oracle 8i January 13, 2002
Reviewer: Hassan Ayub from Islamabad,Pakistan
Hi there,
     Yes I type in the imp80 (in Oracle 8) at the command line which opens the
DOS window and the errors which I posted are from the log file which you
suggested earlier.
One thing more is that when I import the same table.dmp file in Oracle 8i, it is
imported successfully....

Rating: 3
Start--> RUN January 13, 2002
Reviewer: Mark J. Bobak from Dearborn Heights, MI
Hassan,

Are you perhaps starting imp80 by entering that at the
RUN command prompt?

Start Menu-->RUN-->imp80<return>
??

If so, you need to go to:
Start->Programs->Accesories->DOS Prompt
And then CD to the directory you want to be in and type:
imp80

Hope that helps,

-Mark

Rating: 2
Importing into Oracle 8 using Oracle 8i Exported File January 13, 2002
Reviewer: Hassan Ayub from Islamabad, PK
Hi there,
 I even tried going into the oracle home directory and then exporting and
importing from their specific directories but it does not work at all. Has
anybody ever done this actually?

Followup:
I've said pretty clearly here:  a DMP file created by 8i EXP will NOT be
readable by the 8.0 IMP.  You cannot take the file created by the 8.1 exp and
have it be read by the 8.0 imp tool.

just as you cannot use word 95 to read a word 2000 formatted document.

You cannot use 8.0 IMP to read an 8.1 formatted DMP file.

Use the 8.0 EXP tool against 8i.

Rating: 4
generally ok to go to a higher release from a lower one? September 29, 2002
Reviewer: Doug from CT, USA
So Tom - it is generally ok to import into a higher release with no tricks
involved?  I tried an 8.1.7.0 into an 8.1.7.4 and I ran into some errors ( I
can't remember what they were but they involved serious time delays).. and then
ended up with a strange segment in my temp space with a name like "23.288"  or
something.

Followup:
Yes, it is ok.

With things like "ran into some errors", "can't remember them" and "strange
segment like 23.288" -- I cannot really comment (other then to say that the
segment was a temporary segment and would be freed automagically by SMON after a
bit)

Rating: 5
Exporting only specified Objects September 30, 2002
Reviewer: Bharat from USA
Is it possible to Export only views from the schema using export command

Followup:
No, but see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:683824732911
for an idea.

Rating: 5
Exporting only views October 01, 2002
Reviewer: Ashok from USA
Is there any known issue in exporting the materialized views and importing it
back.Somewhere in metalink i read that complex materialized view are exported as
table instead of view.

Version:8.1.7.4
Unix OS

Followup:
I'm not personally aware of any -- but -- since you found it on metalink
once....?

Rating: 2
How does one set client side variables in NT October 28, 2002
Reviewer: A reader from Nashville
I upgraded an 8.1.7 db to 9.2.0.1.0

When I try to export (no rows) I get ora-00091 Exporting questionable
statistics.

setenv isn't working on W2000.  I tried editing glogin.sql, isn't working.  It's
a lot easier in Unix, but how do I fix it for W2000?

Thanks

-Dean
----------------------------------------------------------
Connected to: Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
Export done in WE8MSWIN1252 character set and AL16UTF16 NCHAR character set
server uses WE8ISO8859P1 character set (possible charset conversion)
Note: table data (rows) will not be exported

... blah blah
EXP-00091: Exporting questionable statistics.
... blah blah

Followup:
c:\> set NLS_LANG=......

Rating: 3
importing dump file February 06, 2003
Reviewer: Godwin from Ghana
Hi Tom,
Is it possible to import dump exported file in Unix OS into windows OS? We
currently have a database residing in Unix environment and need to export file
from it into another database created in windows environment.
The Oracle database version 7.3.2.1
Thanks.

Followup:
Yes, dmp files are cross platform.

Just make sure to BINARY ftp the file!!!  99 times out of 100, people ftp it and
that of course corrupts the binary file.

Rating: 3
Import export not a reorg. tool - why? June 25, 2003
Reviewer: Habibulla from India
Your book 'expert one ' is excellent.

In pafge 34 chapter-8, you said that - import / export is no a reorg tool
anymore.

But in real time, most of the people find that, this is more easier and reliable
for re-org. (than 'move tablespace')

1. Can you substantiate your claim plz?

2. Can you provide suitable link for complete steps of 'move tablespace'
procedure?

Followup:
who out there finds it

a) more reliable
b) easier

then ALTER TABLE T MOVE;

???????????   I cannot believe that.

Can YOU substantiate YOUR claim.

You want to "reorg" a table -- simply "alter table T move", optionally supply a
tablespace, storage parameters, pctfree, whatever - then alter index I rebuild
for each index.  Done.

February 06, 2003 parker -- Thanks for the question regarding "os block and db block", version 8.1.7
You Asked
Hi Tom
What is the relationship between the os blocks and db blocks. Why should the db
block size be a multiple of os block.

What should be thte db block size for small, medium, and large databases, are
there any standards.
parker
and we said...
The database does IO at the database block level.  The OS does it at the os
block level.  If your OS block size was say 4k and you used a 2k database block
size - every read would read 2k of data you didn't care about.  Here using a 4k
or 8k block size would make more sense.

The block size is not a function of  the size of a database, it is a function of
the TYPE of data base. OLTP = smaller blocks, DSS = larger blocks.  See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:329017235004
for a discussion on this.
Review & Followup
Rating: 3
os block and db block February 06, 2003
Reviewer: brian from St. Louis
I am trying to understand how oracle use's a larger block size.

If the OS block size is 8K, would it not take 4 reads to fill a 32K buffer?
and if so in this case would a 8K buffer be better?

thanks,

Brian

Followup:
Well, an OS blocksize of 8k would be pretty huge.

But, you just want your blocksize to be a MULTIPLE of theirs.

We'll ask for 32k for a single block IO -- and in ONE call to the os kernel,
we'll get it.

Much more efficient then asking for 4-8k reads one after the other.

We use this concept even more with db_file_multiblock_read_count where we can do
one OS request to read more then one Oracle block which in turn may be many many
more OS blocks.

Rating: 4
OS utilisation February 07, 2003
Reviewer: Nimesh from India Bombay
My salute and  respect to TOM ,

I am trying to understand  OS Internals for oracle and what i could explore abt
blocks and architecture is that every OS uses its own file system tables which
stores inodes... block bitmaps.. address etc..for file management/ IO purpose.
Oracle uses Rowids to identify the Vertical subset of data (Tuple) and
Horizontal attribute columns ,all together stored in one or many OS blocks.DB
blocks is a logical identity used by oracle to facilitate its own mechanism to
retrieve data.
Index is a similiar OS storage table used to retrieve particulars of data (
Rowid ) via different attributes. Rowid will identify Physical file & logical
block and the subset. This information will be processed by controllers and
filesystem tables to identify physical OS block. THe same is retrieved in buffer
for data management...
Please Correct me if my above understanding is errred..

My curiosity is !!!
1> can filesystem tables/ controller Drivers be modified further to avoid or
limit the use of index.
2>Is it feasible that data be stored in matrix and use retrieval algorithm for
data management.
3>If there is limitation from the OS side  Is there a possibility to develop an
Oracle OS :-)

Thanks for everything on this website

February 07, 2003 lar -- Thanks for the question regarding "Automated end of year job.", version 8.1.7
You Asked
How can one set up a job to run at the end of each year, taking into
consideration weather the year is a leap year or not.
and we said...
use dbms_job.

schedule it to start on

add_months(trunc(sysdate,'y'),12)-1

that'll be the last day of the current year regardless.

Set the NEXT to be

add_months(trunc(sysdate),12)

but you know what -- for a special "end of year" processing, I would just as
soon "press a button and watch it start".
February 07, 2003 john -- Thanks for the question regarding "adding day to dates but not counting sat and sun", version 8.1.2
You Asked
I am trying to add days to a scheduling task, but i cannot seem
to add days to a day and disregard saturday and sunday as an
example:

tuesday feb 13 + 5 days would yield sunday feb 18
what i would really like to be able to do is say that if i add
5 to tuesday feb 13 my answer would be tuesday feb 20. I wnat to
just be able to add days to the work week

Any help is greatly appreciated

Thanks

JOHN LOCONTE
and we said...
Thanks to Mike Clegg a Consultant specializing in Oracle, we
have a better answer to this then the first one I gave.  He came
up with a procedure that is about 2/3 times faster then my
version and makes interesting use of ROWNUM.  It is as follows:

ops$tkyte@DEV816> create table
  2  holidays
  3  ( holiday date primary key )
  4  organization index;

Table created.

ops$tkyte@DEV816> -- insert into exclude_dates values (
'14-FEB-2001' );

CREATE OR REPLACE FUNCTION ADD_BUS_DAYS(
  P_DATE                           IN     DATE
 ,P_ADD_NUM                        IN     INTEGER
 ) RETURN DATE AS
  --
  V_CNT                            NUMBER;
  V_BUS_DAY                        DATE := TRUNC(P_DATE);
  --
BEGIN
  --
  SELECT MAX(RNUM)
  INTO   V_CNT
  FROM   (SELECT ROWNUM RNUM
          FROM   ALL_OBJECTS)
  WHERE  ROWNUM <= P_ADD_NUM
    AND  TO_CHAR(V_BUS_DAY + RNUM, 'DY' ) NOT IN ('SAT', 'SUN')
    AND  NOT EXISTS
      (  SELECT 1
         FROM   HOLIDAYS
         WHERE  HOLIDAY = V_BUS_DAY + RNUM );
  V_BUS_DAY := V_BUS_DAY + V_CNT;
  --
  RETURN V_BUS_DAY;
  --
END ADD_BUS_DAYS;
/

His query can do determinisically what mine "guessed" at.   He
uses the same concept but does it a tad more efficiently.

We start by creating a large set of numbers 1, 2, 3, .... That
is what the

(SELECT ROWNUM RNUM FROM   ALL_OBJECTS)

inline view does for us (all_objects is a good pick, it
generally has thousands of rows in it).  So, now we just have a
big set of numbers.  Upon that set we start counting the number
of days that are not sat and sun and not a holiday.  Each time
we get a day that is not one of those days -- ROWNUM in the
outer query gets incremented by 1.  As soon as ROWNUM in the
outer query gets to be the number of business days we wanted to
add -- this query terminates.  We simply take the biggest RNUM
(from the inline view) and add it to the original date -- we are
done.

Thanks Mike.
Review & Followup
Rating: 4
Love your dedication and insight June 22, 2002
Reviewer: Hersel Ahdout from Los Angeles, CA USA
The user's question was the first step in how to account for weekends and that
could have been just to add 7 days instead of each 5 days or (7*FLOOR(ndays/5)+
MOD(ndays,5).

You answered a bigger question not only to exclude weekends but holidays as
well.

The code thought me of things I would never have suspected to use.
Thanks.

Rating: 4
Need more explanation February 06, 2003
Reviewer: John from New Jersey
Tom: This is exactly what I was looking for.  But could you elaborate a little
more on the select statement of the procedure?

I ran this query to add 6 business days to 01/17/2003.  I expect to get 8 days
(because there are 2 weekend days in between).  But I got nothing.

select max(rownum)
from (select rownum from all_objects)
where rownum <= 6
and to_char(to_date('01/17/2003', 'mm/dd/yyyy') + rownum, 'dy') not in ('sat',
'sun');

MAX(ROWNUM)
-----------

BTW, do you know which Oracle document list the complete built-in functions (eg
max, min, length, etc.) for most recent release?

Followup:
the problem with your query is all about rownum.  You have TWO different rownums
in there.

select max(rownum)   <<<<=== rownum1
  from (select rownum   <<==== rownum2
          from all_objects)
 where rownum <= 6  <<<==== rownum1
   and to_char(to_date('01/17/2003', 'mm/dd/yyyy') + rownum, 'dy') <<<===
rownum1
    not in ('sat', 'sun');

and you didn't alias the inner one so you could use it in the outer one.

Use the supplied query "as is" -- with rnum.  the answer is 10, not 8

ops$tkyte@ORA920> select max(rnum)
  2  from (select rownum rnum from all_objects)
  3  where rownum <= 6
  4  and to_char(to_date('01/17/2003', 'mm/dd/yyyy') + rnum, 'dy') not in
('sat',
  5  'sun');

 MAX(RNUM)
----------
        10

Rating: 5
Thank you. You are absolutely right! February 07, 2003
Reviewer: John from New Jersey

Rating: 5
Is there a way to go backwards February 07, 2003
Reviewer: Srimal from Trenton, USA
Hello Mr Tom,

The code that you have here goes forward from the date. I have a situation where
I have to go backwards.

example: 7 business days prior to today 02/07/2003 was 29th January. Is there a
way to calculate on the fly the same using something similar to what you have
for going forward.

Thanks in advance

Followup:
try substraction?

ops$tkyte@ORA920> !cal 1 2003
    January 2003
Su Mo Tu We Th Fr Sa
          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

ops$tkyte@ORA920> !cal
    February 2003
Su Mo Tu We Th Fr Sa
                   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

ops$tkyte@ORA920> select max(rnum), sysdate, sysdate-max(rnum)
six_business_days_AGO
  2    from (select rownum rnum from all_objects)
  3   where rownum <= 6
  4     and to_char(to_date('01/17/2003', 'mm/dd/yyyy') - rnum, 'dy') not in
('sat','sun')
  5  /

 MAX(RNUM) SYSDATE   SIX_BUSIN
---------- --------- ---------
         8 07-FEB-03 30-JAN-03

ops$tkyte@ORA920> select max(rnum), sysdate, sysdate+max(rnum)
six_business_days_from_now
  2    from (select rownum rnum from all_objects)
  3   where rownum <= 6
  4     and to_char(to_date('01/17/2003', 'mm/dd/yyyy') + rnum, 'dy') not in
('sat','sun')
  5  /

 MAX(RNUM) SYSDATE   SIX_BUSIN
---------- --------- ---------
        10 07-FEB-03 17-FEB-03

Rating: 4
Bussiness days to go backwards February 07, 2003
Reviewer: Manish Upadhyay from Madison,WI,USA
select max(rnum)
   from (select rownum rnum from all_objects)
   where rownum <= 7
    and to_char(sysdate - rnum, 'dy') not in ('sat', 'sun');

Rating: 4
The following query will give you date backwards February 07, 2003
Reviewer: Manish Upadhyay from Madison,WI
SQL> select (sysdate-max(rnum))BACKDATE
   from (select rownum rnum from all_objects)
   where rownum <= 7
    and to_char(sysdate - rnum, 'dy') not in ('sat', 'sun');

BACKDATE
---------------
29-JAN-03

Rating: 5
Thank You February 07, 2003
Reviewer: A reader
Thanks a lot all of you for a quick response

February 07, 2003 ps -- Thanks for the question regarding "connect string, sid , dbname, instance name", version 8.1.7
You Asked
Tom

what is the difference between connect string, sid , dbname, instance name?

can I have multiple connect strings for the same database..

what is the relation between sqlnet.ora and tnsnames.ora?

what is the meaning of the parameters..

SQLNET.AUTHENTICATION_SERVICES= (NTS)

NAMES.DIRECTORY_PATH= (TNSNAMES, ONAMES, HOSTNAME)

and

where does v$instance get its value for instance_name from..

and

what is the difference mts and dedicated server..

thats all for now..

more networking questions later..
and we said...
A connect string is simply a user defined alias.  It is an alias to a longer
connect string that tells the software how to connect over the network to a
database.

A sid is the Site Identifier.  It, plus the oracle_home, uniquely identify a
database instance on a single machine.  If you are on the machine with the
database, you only need the oracle_sid and oracle_home to get connected
directly.

A dbname is the name of the database, it typically is not relevant.

The instance name is generally the sid.

You can definitely have dozens of connect strings for the same database.  They
could specify the same or different connect time options.

SQLNET.AUTHENTICATION_SERVICES= (NTS)
enabled OS authentication on windows.

NAMES.DIRECTORY_PATH= (TNSNAMES, ONAMES, HOSTNAME)
says when you get:   scott/tiger@db

use the local tnsnames.ora file to try and resolve db, if not found, then use
Oracle Names to resolve it, if not found, then using hostname naming to resolve
it (that involves connecting to a machine named DB on the default port 1521)

The instance name is the SID

for mts/dedicated see
http://technet.oracle.com/doc/oracle8i_816/network.816/a76933/concepts.htm#1011542
...
Review & Followup
Rating: 4
February 06, 2003
Reviewer: A reader
Can you explain once again about instance name and database name? Can they be
different? Can you please explain this with an example?

Followup:
Yes, an instance name could be different -- an instance is just the set of
processes -- the dbwr, lgwr, etc and some memory.

A database -- a set of files.

A single database could have many instances mounting it all at the same time --
that is called RAC or OPS (real application clusters, oracle parallel server).

Here -- I just exported my Oracle sid as "notOra920" instead of my normal
"ora920" and copied the init files:

[ora920@tkyte-pc-isdn dbs]$ export ORACLE_SID=notOra920
[ora920@tkyte-pc-isdn dbs]$ cp orapwora920 orapwnotOra920
[ora920@tkyte-pc-isdn dbs]$ cp spfileora920.ora spfilenotOra920.ora

Now I startup

[ora920@tkyte-pc-isdn dbs]$ sqlplus "/ as sysdba"

SQL*Plus: Release 9.2.0.1.0 - Production on Thu Feb 6 16:34:21 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to an idle instance.

idle> startup
ORACLE instance started.

Total System Global Area  303108296 bytes
Fixed Size                   450760 bytes
Variable Size             134217728 bytes
Database Buffers          167772160 bytes
Redo Buffers                 667648 bytes
Database mounted.
Database opened.
idle> select instance_name from v$instance;

INSTANCE_NAME
----------------
notOra920

idle> show parameter db_name

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
db_name                              string      ora920
idle>

and my instance name is notOra920 (just used to find files to startup) whilst
my db_name (burned into the datafiles) is ora920

Rating: 5
February 07, 2003
Reviewer: A reader
Interesting. This is what I understood: (Please correct me if I am wrong.)

So when someone talks about renaming an instance, that is just setting a new
name to the ORACLE_SID. Right? In the above example, even if you dont copy the
init files, still you can open the instance by giving the pfile parameter at
startup. So in short, the renaming of an instance is just setting a new name to
the memory areas and processes and mounting the same old database. (Seems like a
meeningless excercise unless under OPS.). I think most of the time when people
talk about renaming an instance, they mean renaming both the instance and the
database.

Thank you for time.

Followup:
I cannot say what someone means by "renaming an instance" since the term is
bandied about by so many people to mean so many different things.

They *probably* mean a database, not an instance.

You see -- I didn't really "rename" my instance -- you cannot rename an
instance.  An instance is a set of processes and memory.  When you shutdown --
that INSTANCE goes away, never to exist EVER again.  An instance is transient.
shutdown and its gone forever.

February 07, 2003 Kashif -- Thanks for the question regarding "Optimise procedure", version 8.1.7
You Asked
Hi Tom,

In many of my procedures, I need to check how many rows a query returns before
continuing processing.

I would typically have something like:

declare
   v_count             number(6);
   v_contract_date     date;
begin
   .
   .
   --check if query would return rows
   select count(*)
   into   v_count
   from   imp_contract
   where  accnum = '12345';

   if v_count <> 0 then
   -- query returns rows, execute query and continue processing
      select contract_date
      into   v_contract_date
      from   imp_contract
      where  accnum = '12345';
   .
   .
   .
end;

Is there a more effecient way of doing this, maybe in a single query, instead if
querying the database twice?

Regards
Kashif
and we said...
why not just:

   v_contract_date     date;
begin

...
   begin
     select contract_date
       into v_constract_date
       from imp_constract
      where accnum = '12345';

     .... code that is currently in "if v_count <> 0 then" block ...

   exception
      when no_data_found then NULL;
   end;
February 07, 2003 v -- Thanks for the question regarding "Changing datatype in materialized views ", version 8i
You Asked
Hi,
I'm trying to create a materialized view v1 from two tables a &b.
Table 'a' has column acol1(varchar2(100),acol2(number).Table 'b' has column
bcol1.Syntax used is
Create materialized view v1
as
select to_number(a.acol1) as matl_col
from a,b
where a.acol2=b.col1;
COLUMN OF MATL.VIEW matl_col has floating point values.After describing view v1
,I get the datatype and data length of column matl_col as NUMBER and NULL
respectively.But I like to have the data length(precision,scale) to be defined
for the column matl_col.I tried to use the CAST function instead of to_number in
the query. But still I get no defintion for datalength of Matl_col.To end, I
need it to be number(p,s) not double or just number. Please suggest.
Thanks
or_dw_fan
and we said...
build it on a prebuilt table:

ops$tkyte@ORA817DEV> create table t1 ( a int primary key, x varchar2(5) );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert into t1 values ( 1, '1' );

1 row created.

ops$tkyte@ORA817DEV> insert into t1 values ( 2, '2' );

1 row created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create table t2 ( matl_col number(5,2) );

Table created.

ops$tkyte@ORA817DEV> insert into t2 select to_number(x) from t1;

2 rows created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create materialized view t2
  2  ON PREBUILT TABLE
  3  WITH REDUCED PRECISION
  4  as
  5  select to_number(x) matl_col from t1;

Materialized view created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> desc t2;
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 MATL_COL                                     NUMBER(5,2)

ops$tkyte@ORA817DEV>
February 08, 2003 Kashif -- Thanks for the question regarding "Trunc vs To_Char.", version 8.1.6.3
You Asked
Hi Tom,

I read in one of your responses to a post here that trunc works faster than
to_char when comparing dates, and your specific response related to their
performance in a where clause. Can you point me to that question? I tried
searching using a variety of keywords but I couldn't find that specific
response. If you do not recollect such a question, then can you please give us a
small sample case which proves that trunc does indeed work faster? Thanks.

Kashif
and we said...
It is easy to show this.  Consider:

ops$tkyte@ORA920> create table t as select created from all_objects;

Table created.

ops$tkyte@ORA920> insert into t select * from t;

32437 rows created.

ops$tkyte@ORA920> insert into t select * from t;

64874 rows created.

ops$tkyte@ORA920> insert into t select * from t;

129748 rows created.

ops$tkyte@ORA920> insert into t select * from t;

259496 rows created.

ops$tkyte@ORA920> insert into t select * from t;

518992 rows created.

ops$tkyte@ORA920> insert into t select * from t;

1037984 rows created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA920> select count(*) from t where to_char(created,'dd-mon-yyyy') =
'01-jan-2003';

  COUNT(*)
----------
         0

ops$tkyte@ORA920> select count(*) from t where trunc(created) =
to_date('01-jan-2003','dd-mon-yyyy');

  COUNT(*)
----------
         0

and now use tkprof to see the differences:

select count(*)
from
 t where to_char(created,'dd-mon-yyyy') = '01-jan-2003'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      4.63       4.63          0       3779          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      4.63       4.63          0       3779          0           1

********************************************************************************
select count(*)
from
 t where trunc(created) = to_date('01-jan-2003','dd-mon-yyyy')

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      2.78       2.80          0       3779          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      2.78       2.80          0       3779          0           1

The trunc only used about 60% of the CPU of the to_char.  Makes sense as all
trunc needs to do is set a couple of bytes at the end of the date -- to_char,
lots of work to format a date.
Review & Followup
Rating: 5
Just curious... February 08, 2003
Reviewer: Connor McDonald from Perth
I have done this myself but you might also want to post your own timing results
(for consistency with the rest of this thread) for:

where dte between trunc(some_date) and trunc(some_date)+0.9999

Cheers
Connor

Followup:
good point, anytime you can do

db_column OPERATOR f(constant)

instead of

f(db_column) OPERATOR constant

you'll probably be the better for it.  I redid the example using binds:

exec :dtstr := '01-jan-2003';
alter session set sql_trace=true;

select count(*) from t where to_char(created,'dd-mon-yyyy') = :dtstr

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      4.74       4.81          0       3779          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      4.74       4.81          0       3779          0           1

********************************************************************************
select count(*) from t where trunc(created) = to_date(:dtstr,'dd-mon-yyyy')

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      2.74       2.84          0       3779          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      2.74       2.84          0       3779          0           1

********************************************************************************
select count(*) from t
 where created >= to_date(:dtstr,'dd-mon-yyyy')
   and created < to_date(:dtstr,'dd-mon-yyyy')+1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      1.98       2.08          0       3779          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      1.98       2.08          0       3779          0           1

another 30% gone

February 08, 2003 John -- Thanks for the question regarding "Split row over more than one group", version 8.0.6
You Asked
Hi, Tom

Wonder if you can help

I have a table of sales data where a customer can be served by more than one
salesman using a composite salesman code to represent either 2 or 3 real
salesmen. This is because the salesman sells one group of products, but the
customer can buy up to 3 groups.

I need to add the customer sales into the totals of each of the real salesmen.

Customer Table
(customer,rep,sales)
1010  10  3000.00
1020  20  4500.00
1030  30  2000.00
1040  40  3500.00
1050  20  6000.00
1060  20  2500.00
1070  30  3000.00
1080  10  2400.00
1090  40  3000.00

Rep Table
(rep,name)
10  John Smith
20  Ray Jones
30  Ray Jones/Jim Gray
40  Jim Gray

The scenario for this is that customers 1030 and 1070 buy electrical and
plumbing products, but we have a different rep for each range

What I require is a total for each of the 3 reps, so that sales for rep 30 are
added to both rep 20 and rep 40 totals.
ie.

10   5400.00
20  13000.00+5000.00
40   6500.00+5000.00

Regards

John
and we said...
what is the max number of reps permitted in such a string -- what number less
than 2000 is that magic number for you (since you are encoding in a varchar2, it
has to be less than 2000).

what we need to do is temporarily materialize a view that has the data in a
"relational format"

this sort of design you have is what is commonly known as "a really bad idea",
you should consider fixing it.  It'll cause you no end of heartburn over time.

so, say t is your rep_table, and we assume there are at most 10 reps in this
composite thing.  A query like:

ops$tkyte@ORA920> select rep,
  2         substr( name, instr(name,'/',1,r)+1,
instr(name,'/',1,r+1)-instr(name,'/',1,r)-1 ) name
  3    from ( select rep, '/'||name||'/' name from t ),
  4         ( select rownum r from all_objects where rownum <= 10 )
  5   where nvl( instr(name,'/',1,r+1), 0 ) > 0
  6  /

       REP NAME
---------- ------------------------------
        10 John Smith
        20 Ray Jones
        30 Ray Jones
        40 Jim Gray
        30 Jim Gray

gives is a "normal" view of this information.  We can then join to that:

ops$tkyte@ORA920> select name, sum(sales)
  2    from ( select rep,
  3                  substr( name, instr(name,'/',1,r)+1,
instr(name,'/',1,r+1)-instr(name,'/',1,r)-1 ) name
  4             from ( select rep, '/'||name||'/' name from t ),
  5                  ( select rownum r from all_objects where rownum < 10 )
  6            where nvl( instr(name,'/',1,r+1), 0 ) > 0 ) rep_table,
  7             t2 customer_table
  8   where customer_table.rep = rep_table.rep
  9   group by name
 10  /

NAME                           SUM(SALES)
------------------------------ ----------
Jim Gray                            11500
John Smith                           5400
Ray Jones                           18000

and if you really need the rep number, we can join back again:

ops$tkyte@ORA920> select rep_table2.rep, rep_table1.name, rep_table1.sales
  2    from (
  3  select name, sum(sales) sales
  4    from ( select rep,
  5                  substr( name, instr(name,'/',1,r)+1,
instr(name,'/',1,r+1)-instr(name,'/',1,r)-1 ) name
  6             from ( select rep, '/'||name||'/' name from t ),
  7                  ( select rownum r from all_objects where rownum < 10 )
  8            where nvl( instr(name,'/',1,r+1), 0 ) > 0 ) rep_table,
  9             t2 customer_table
 10   where customer_table.rep = rep_table.rep
 11   group by name
 12         ) rep_table1,
 13             t rep_table2
 14   where rep_table1.name = rep_table2.name
 15  /

       REP NAME                                SALES
---------- ------------------------------ ----------
        40 Jim Gray                            11500
        10 John Smith                           5400
        20 Ray Jones                           18000
February 08, 2003 Gunjan -- Thanks for the question regarding "Effect of Database Buffers on Buffer Hit Ratio", version 8.1.7
You Asked
Hi Tom, Your site is great and you are doing great service to DBA community.

Now, Give the following information (database Size & Statspack)
    , What is the recommended size of  db_block_buffers.

Also, I would greatly appreciate your insite on the following situation:
a. If the Data in the Database is 100M and
   if I make db_block_buffers*db_block_size=110M
   Does it mean I would be getting 100% buffer Hit Ration

1. Database Size

Tablespace                 Total       Total    %      Contg.      Chunks
Name                        Size        Free Free        Free        Free
-------------------- ----------- ----------- ---- ----------- -----------
CORP_DATA                     10          10   96          10           1
CORP_INDX                     10          10   96           9           2
MGR_DATA                     100          42   42          41           2
MGR_INDX                      25          10   39           6          24
MGR_LOB_DATA                 200         115   57         115           1
MGR_LOB_INDX                 200         200  100         200           1
PERFSTAT                     250         202   81         202           1
PFM_DATA                      50          16   32          16           1
PFM_INDX                      15           4   28           2           4
RBS                           50          30   59          30           1
SYSTEM                       150          70   47          70           2
TEMP                          20          20  100          20           1
TOOLS                         10           7   74           7           2
USERS                         10           8   82           8           1
WEB_DATA                      50          18   36          18           1
WEB_INDX                      10           5   45           2           6
---------------------------------------------------------------------------
                           1,160         766   66
--------------------------------------------------------------------------
2. Statspack Report
                Snap Id     Snap Time      Sessions
                ------- ------------------ --------
 Begin Snap:        361 05-Feb-03 09:00:06       53
   End Snap:        362 05-Feb-03 10:00:05       53
    Elapsed:                  59.98 (mins)

Cache Sizes
~~~~~~~~~~~
           db_block_buffers:       5000          log_buffer:       8192
              db_block_size:       8192    shared_pool_size:   25600000

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:                636.09              1,705.89
              Logical reads:              1,724.92              4,625.93
              Block changes:                  3.83                 10.27
             Physical reads:              1,355.48              3,635.16
            Physical writes:                  0.42                  1.12
                 User calls:                 18.99                 50.93
                     Parses:                  4.84                 12.99
                Hard parses:                  0.99                  2.64
                      Sorts:                  2.79                  7.48
                     Logons:                  0.09                  0.25
                   Executes:                  5.21                 13.96
               Transactions:                  0.37

  % Blocks changed per Read:    0.22    Recursive Call %:   51.28
 Rollback per transaction %:    0.00       Rows per Sort:   38.43

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:   99.95       Redo NoWait %:   99.97
            Buffer  Hit   %:   21.42    In-memory Sort %:   99.82
            Library Hit   %:   89.40        Soft Parse %:   79.64
         Execute to Parse %:    6.98         Latch Hit %:   99.95
Parse CPU to Parse Elapsd %:   55.54     % Non-Parse CPU:  100.00

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   71.03   72.63
    % SQL with executions>1:   91.35   85.03
  % Memory for SQL w/exec>1:   69.20   65.14

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
db file scattered read                            858,262       54,360   67.63
log file sync                                       1,400       15,384   19.14
db file sequential read                           290,377        4,686    5.83
log file parallel write                             1,458        2,137    2.66
buffer busy waits                                   2,954        1,103    1.37
          -------------------------------------------------------------

Thanks in Advance
Gunjan
and we said...
read the paper on www.hotsos.com:

Are you still using cache hit ratios?

as for (a) the answer is: yes, no, maybe, so what?  All of those are valid
answers to that.  You may or may not get 100% cache hit over time -- it depends
on thousands of variables but at the end of the day -- the real answer is "yawn,
so what".  Your goal is not to achieve 100% cache hit (i can do that easily --
just through into a hard loop a query against dual.  eventually you'll have 100%
cache hit -- but so what?)

Now, you have a 39meg buffer cache which I would put at the "small end if the
spectrum" as far as size goes.

You also have a 21% buffer hit, which is pretty small.  You would probably
benefit from increasing the buffer cache -- but you still want to look at your
TOP SQL and make sure they are doing as little IO (logical and physical) as
possible.

the best way to fix an io problem?  reduce io (too many people try to fix the
symptoms - the wait for IO by moving files, striping whatnot.  they ignore the
easier path "fix the low hanging fruit, the queries")....

But in your case, you would probably benefit from a little more ram in the
buffer cache unless your system is already at capacity memorywise.

More alarming to me are the

soft parse %
execute to parse %

soft parse %'s are the only ratio I really pay attention to -- and they should
generally be at 99% or higher!  You need to beat the people generating the
queries about the head and shout "bind variables -- use them"....

execute to parse % is computed as

100 * (1-parses/executes).  This means you have an almost 1 to 1 relationship
between PARSE and EXECUTE -- which probably means you have some statements that
you parse MORE then you execute -- which probably means you are using some tool
to generate code and it uses the bad practice of "parse a statement" just to
describe the outputs and then "parse the statement again" in order to execute
it.  You want to parse ONCE and execute many times...
Review & Followup
Rating: 4
Awesome February 08, 2003
Reviewer: Gunjan from Florida, USA
I really appreciate you expert advise.

February 08, 2003 Dan -- Thanks for the question regarding "Variables in IN operator", version 8.1.7
You Asked
Tom,
    I noticed that when values provided to IN list are sorted in ascending for
example

  Select * from loans where loan_id IN ( 5, 6, 3);

  Resulted in
   3,
   5,
   6

   Why is this??

Dan
and we said...
One very very very important thing to understand about SQL -- all SQL -- is that
the order the rows in a result set is random (or effectively random) unless
you have an explicit ORDER BY on the query

Your premise that the rows come out -- or always will come out -- in the order
3, 5, 6 is wrong.  Consider this very simple example:

ops$tkyte@ORA920> create table t ( x int );
Table created.

ops$tkyte@ORA920> insert into t values (0 );
1 row created.

ops$tkyte@ORA920> insert into t values (6 );
1 row created.

ops$tkyte@ORA920> insert into t values (5 );
1 row created.

ops$tkyte@ORA920> insert into t values (3 );
1 row created.

ops$tkyte@ORA920> insert into t values (1 );
1 row created.

ops$tkyte@ORA920> select * from t where x in ( 5, 6, 3 );

         X
----------
         6
         5
         3

ops$tkyte@ORA920>

Now, that is NOT 3, 5, 6 -- but wait:

ops$tkyte@ORA920> create index t_idx on t(x);
Index created.

ops$tkyte@ORA920> select * from t where x in ( 5, 6, 3 );

         X
----------
         3
         6
         5

Now, they appear to come out in the REVERSE order of the items in the in list
-- but wait

ops$tkyte@ORA920> analyze table t compute statistics;
Table analyzed.

ops$tkyte@ORA920> select * from t where x in ( 5, 6, 3 );

         X
----------
         3
         5
         6

Now they appear to come out sorted

It is 100% dependent on how we decide to process the query!

In the first select, we did a full scan and checked out the in list row by row.
The rows came out of that query in the order we found them on disk (and no, ROWS
DO NOT come out in the order of insertion either!)

In the second select, the RBO saw the index and the in list and did 3 index
probes.  Only the rbo likes to work right to left and bottom up -- so the rows
came out in the reverse order of the in list.

In the last select -- the CBO used an inlist iterator on a single index range
scan -- the rows came out apparently sorted.

the bottom line -- if you want rows in some order, you need to have an order
by or they will come back to you as we please
Review & Followup
Rating: 5
February 08, 2003
Reviewer: A reader
This Site ROCKS !!!!!!!!!!

Rating: 4
Simply Superb February 09, 2003
Reviewer: Lakshminarasimhan R from Dubai

Rating: 5
February 10, 2003
Reviewer: Helena Markova from Bratislava, Slovakia

Rating: 5
Very Important February 10, 2003
Reviewer: Andrew Gilfrin from Dover, Kent, UK
I had to fix a problem with some code here. Before I arrived a team of
developers developed a screen which it was essential had the rows in a specific
order. However then simply assumed that by inserting the rows in order would
mean they would be returned in order.

This worked 99% of the time as there was no index or order on the query but
every now and again it wouldn’t work. nobody here could figure out why then I
had a look and like I say no order by. In fact they didn’t even bother to store
the line number so there was no way you could order the results in the same way
as they were input.

I had to virtually rewrite the form so that it stored the position so we could
order by that when doing the select.

I think this is a very good illustration why people should try and learn a least
a little bit about databases before they start to program with one.

February 10, 2003 Bhavesh -- Thanks for the question regarding "Mutating Trigger..", version 8.1.5
You Asked
Hi Tom:
I have a mutating trigger problem....
Here is the SIMPLIFIED problem I need the help for...
I have a table customer as shown below:
SQL> select * from customer;

    CUSTID CUSTNAME             STATEMENT   NETSALES
---------- -------------------- --------- ----------
         1 best buy             08-JUN-99       1000
         2 best buy             08-JUN-98       1000
         3 best buy             08-MAY-00       2000
Hi Tom:
I have a mutating trigger problem....
Here is the SIMPLIFIED problem I need the help for...
I have a table customer as shown below:
SQL> select * from customer;

    CUSTID CUSTNAME             STATEMENT   NETSALES
---------- -------------------- --------- ----------
         1 best buy             08-JUN-99       1000
         2 best buy             08-JUN-98       1000
         3 best buy             08-MAY-00       2000

Datatypes are obvious! Now, when ever we have >=3 rows PER
CUSTOMER in this table(grab the latest 3 rows based on
statementDate),
my trigger should fire which executes a procedure which does
calculations and dumps results
in the OUTPUT table which is shown below with the one result for
each customer
Best Buy
SQL> select * from output;

    CUSTID CUSTNAME             NETSALES_C NETSALES_2 NETSALES_3
---------- -------------------- ---------- ---------- ----------
         1 best buy                   2000       1000       1000
Since I need to know how many rows are there in customer table
for every customer being updated/inserted
in the customer table, I have to use AFTER INSERT OR UPDATE ROW
trigger...and that gives me mutating trigger error.

When I use before BEFORE INSERT OR UPDATE trigger to know the
customer being inserted/updated which stores the custid in the
package vairable,
and use AFTER INSERT OR UPDATE statement level trigger, it works
only for the one company.
If batch insert or update takes place, the trigger fires my
procedure only once for the last customer being
inserted/updated.

In brief, my requirement is, when a customer is added/modified
in the customer table and we have >=3 rows in customer table,
use the latest three rows
to do calculation. This should work for batch updates and
inserts too. So, the after INSERT OR UPDATE should be a row
level trigger...

Hope I made myself clear..thanks a lot in advance...
Regards,
Bhavesh Tailor
and we said...
I have details on the mutating table issue at
http://osi.oracle.com/~tkyte/Mutate/index.html
It deals with the >1 row being updated/inserted at a time by
using plsql tables.  You should read through that paper to see
why I do some things I do below...

This would be easier if you didn't have that 3 row rule, is it
really necessary?  (it would be easier from a programming
perspective with or without the mutating table issue)...

Here is what I would code in 2 different ways.  The first way
follows your "3 row" rule.  The second is the way I would do it
-- i would just track the netsales for the last 3 regardless of
whether they had 1, 2, 3, or more records.  Its shows how simple
the logic is then (regardless of mutating table or not -- the 3
row rule makes this *hard*).  If you can, I would give serious
consideration to dropping that requirement and just tracking it
for everyone.  It would make the process of inserting/updating
much faster.

ops$tkyte@8i> create table t
  2  ( custid    int,
  3    custname  varchar2(25),
  4    statement date,
  5    netsales  int
  6  )
  7  /
Table created.

ops$tkyte@8i>
ops$tkyte@8i> create table t2
  2  ( custid      int primary key,
  3     netsales_1 int,
  4     netsales_2 int,
  5     netsales_3 int
  6  )
  7  /
Table created.

ops$tkyte@8i> create or replace package state_pkg
  2  as
  3      type myArray is table of number
  4          index by binary_integer;
  5
  6      empty   myArray;
  7      newOnes myArray;
  8  end;
  9  /
Package created.

ops$tkyte@8i>
ops$tkyte@8i> create or replace trigger t_biu
  2  before insert or update on t
  3  begin
  4      state_pkg.newOnes := state_pkg.empty;
  5  end;
  6  /

Trigger created.

ops$tkyte@8i>
ops$tkyte@8i> create or replace trigger t_aiuFEF
  2  after insert or update on t for each row
  3  begin
  4      state_pkg.newOnes(:new.custid) := 1;
  5  end;
  6  /

Trigger created.

ops$tkyte@8i>
ops$tkyte@8i> create or replace trigger t_aiu
  2  after insert or update on t
  3  declare
  4      l_custid    number;
  5      l_vals      state_pkg.myArray;
  6  begin
  7      l_custid := state_pkg.newOnes.FIRST;
  8      loop
  9          l_vals := state_pkg.empty;
 10          for x in ( select netsales
 11                       from t
 12                      where custid = l_custid
 13                      order by statement desc )
 14          loop
 15              l_vals( l_vals.count+1 ) := x.netsales;
 16              exit when ( l_vals.count = 3 );
 17          end loop;
 18          if ( l_vals.count = 3 )
 19          then
 20              update t2
 21                 set netsales_1 = l_vals(1),
 22                     netsales_2 = l_vals(2),
 23                     netsales_3 = l_vals(3)
 24               where custid = l_custid;
 25
 26              if (sql%rowcount = 0)
 27              then
 28                  insert into t2
 29                  ( custid, netsales_1,
 30                    netsales_2, netsales_3 )
 31                  values
 32                  ( l_custid, l_vals(1),
 33                    l_vals(2), l_vals(3) );
 34              end if;
 35          end if;
 36
 37          exit when ( l_custid = state_pkg.newOnes.last );
 38          l_custid := state_pkg.newOnes.NEXT(l_custid);
 39      end loop;
 40      state_pkg.newOnes := state_pkg.empty;
 41  exception
 42      when others then state_pkg.newOnes := state_pkg.empty;
 43  end;
 44  /

Trigger created.

ops$tkyte@8i>
ops$tkyte@8i> insert into t values ( 1, 'one', sysdate-5, 100 );

1 row created.

ops$tkyte@8i> insert into t values ( 1, 'one', sysdate-4, 200 );

1 row created.

ops$tkyte@8i> select * from t order by statement;

    CUSTID CUSTNAME                  STATEMENT   NETSALES
---------- ------------------------- --------- ----------
         1 one                       03-JUN-00        100
         1 one                       04-JUN-00        200

ops$tkyte@8i> select * from t2;

no rows selected

ops$tkyte@8i> insert into t values ( 1, 'one', sysdate-3, 250 );

1 row created.

ops$tkyte@8i> select * from t order by statement;

    CUSTID CUSTNAME                  STATEMENT   NETSALES
---------- ------------------------- --------- ----------
         1 one                       03-JUN-00        100
         1 one                       04-JUN-00        200
         1 one                       05-JUN-00        250

ops$tkyte@8i> select * from t2;

    CUSTID NETSALES_1 NETSALES_2 NETSALES_3
---------- ---------- ---------- ----------
         1        250        200        100

ops$tkyte@8i> insert into t values ( 1, 'one', sysdate-2, 300 );

1 row created.

ops$tkyte@8i> select * from t order by statement;

    CUSTID CUSTNAME                  STATEMENT   NETSALES
---------- ------------------------- --------- ----------
         1 one                       03-JUN-00        100
         1 one                       04-JUN-00        200
         1 one                       05-JUN-00        250
         1 one                       06-JUN-00        300

ops$tkyte@8i> select * from t2;

    CUSTID NETSALES_1 NETSALES_2 NETSALES_3
---------- ---------- ---------- ----------
         1        300        250        200

ops$tkyte@8i>
ops$tkyte@8i> insert into t
  2  select 2, 'two', statement, netsales*10
  3  from t
  4  /

4 rows created.

ops$tkyte@8i> select * from t order by statement;

    CUSTID CUSTNAME                  STATEMENT   NETSALES
---------- ------------------------- --------- ----------
         1 one                       03-JUN-00        100
         2 two                       03-JUN-00       1000
         1 one                       04-JUN-00        200
         2 two                       04-JUN-00       2000
         1 one                       05-JUN-00        250
         2 two                       05-JUN-00       2500
         1 one                       06-JUN-00        300
         2 two                       06-JUN-00       3000

8 rows selected.

ops$tkyte@8i> select * from t2;

    CUSTID NETSALES_1 NETSALES_2 NETSALES_3
---------- ---------- ---------- ----------
         1        300        250        200
         2       3000       2500       2000

ops$tkyte@8i>
ops$tkyte@8i>
ops$tkyte@8i> insert into t
  2  select custid+2, custid+2, statement-10, netsales*10
  3  from t
  4  /

8 rows created.

ops$tkyte@8i> select * from t order by statement;

    CUSTID CUSTNAME                  STATEMENT   NETSALES
---------- ------------------------- --------- ----------
         3 3                         24-MAY-00       1000
         4 4                         24-MAY-00      10000
         3 3                         25-MAY-00       2000
         4 4                         25-MAY-00      20000
         3 3                         26-MAY-00       2500
         4 4                         26-MAY-00      25000
         3 3                         27-MAY-00       3000
         4 4                         27-MAY-00      30000
         1 one                       03-JUN-00        100
         2 two                       03-JUN-00       1000
         1 one                       04-JUN-00        200
         2 two                       04-JUN-00       2000
         1 one                       05-JUN-00        250
         2 two                       05-JUN-00       2500
         1 one                       06-JUN-00        300
         2 two                       06-JUN-00       3000

16 rows selected.

ops$tkyte@8i> select * from t2;

    CUSTID NETSALES_1 NETSALES_2 NETSALES_3
---------- ---------- ---------- ----------
         1        300        250        200
         2       3000       2500       2000
         3       3000       2500       2000
         4      30000      25000      20000

ops$tkyte@8i>
ops$tkyte@8i>

Now, here is the simple way I would do it.  It'll perform
better and have less code to debug/maintain

ops$tkyte@8i> create table t
  2  ( custid    int,
  3    custname  varchar2(25),
  4    statement date,
  5    netsales  int
  6  )
  7  /

Table created.

ops$tkyte@8i>
ops$tkyte@8i> create table t2
  2  ( custid      int primary key,
  3     netsales_1 int,
  4     netsales_2 int,
  5     netsales_3 int
  6  )
  7  /

Table created.

ops$tkyte@8i>
ops$tkyte@8i> create or replace trigger t_aiuFER
  2  after insert or update on t
  3  for each row
  4  begin
  5          update t2
  6             set netsales_2 = netsales_1,
  7                     netsales_3 = netsales_2,
  8                     netsales_1 = :new.netsales
  9           where custid = :new.custid;
 10
 11          if (sql%rowcount=0)
 12          then
 13                  insert into t2
 14                  ( custid, netsales_1 )
 15                  values
 16                  ( :new.custid, :new.netsales );
 17          end if;
 18  end;
 19  /

Trigger created.

ops$tkyte@8i>
ops$tkyte@8i> insert into t values ( 1, 'one', sysdate-5, 100 );

1 row created.

ops$tkyte@8i> insert into t values ( 1, 'one', sysdate-4, 200 );

1 row created.

ops$tkyte@8i> select * from t order by statement;

    CUSTID CUSTNAME                  STATEMENT   NETSALES
---------- ------------------------- --------- ----------
         1 one                       03-JUN-00        100
         1 one                       04-JUN-00        200

ops$tkyte@8i> select * from t2;

    CUSTID NETSALES_1 NETSALES_2 NETSALES_3
---------- ---------- ---------- ----------
         1        200        100

ops$tkyte@8i> insert into t values ( 1, 'one', sysdate-3, 250 );

1 row created.

ops$tkyte@8i> select * from t order by statement;

    CUSTID CUSTNAME                  STATEMENT   NETSALES
---------- ------------------------- --------- ----------
         1 one                       03-JUN-00        100
         1 one                       04-JUN-00        200
         1 one                       05-JUN-00        250

ops$tkyte@8i> select * from t2;

    CUSTID NETSALES_1 NETSALES_2 NETSALES_3
---------- ---------- ---------- ----------
         1        250        200        100

ops$tkyte@8i> insert into t values ( 1, 'one', sysdate-2, 300 );

1 row created.

ops$tkyte@8i> select * from t order by statement;

    CUSTID CUSTNAME                  STATEMENT   NETSALES
---------- ------------------------- --------- ----------
         1 one                       03-JUN-00        100
         1 one                       04-JUN-00        200
         1 one                       05-JUN-00        250
         1 one                       06-JUN-00        300

ops$tkyte@8i> select * from t2;

    CUSTID NETSALES_1 NETSALES_2 NETSALES_3
---------- ---------- ---------- ----------
         1        300        250        200

ops$tkyte@8i>
ops$tkyte@8i> insert into t
  2  select 2, 'two', statement, netsales*10
  3  from t
  4  /

4 rows created.

ops$tkyte@8i> select * from t order by statement;

    CUSTID CUSTNAME                  STATEMENT   NETSALES
---------- ------------------------- --------- ----------
         1 one                       03-JUN-00        100
         2 two                       03-JUN-00       1000
         1 one                       04-JUN-00        200
         2 two                       04-JUN-00       2000
         1 one                       05-JUN-00        250
         2 two                       05-JUN-00       2500
         1 one                       06-JUN-00        300
         2 two                       06-JUN-00       3000

8 rows selected.

ops$tkyte@8i> select * from t2;

    CUSTID NETSALES_1 NETSALES_2 NETSALES_3
---------- ---------- ---------- ----------
         1        300        250        200
         2       3000       2500       2000

ops$tkyte@8i>
ops$tkyte@8i>
ops$tkyte@8i> insert into t
  2  select custid+2, custid+2, statement-10, netsales*10
  3  from t
  4  /

8 rows created.

ops$tkyte@8i> select * from t order by statement;

    CUSTID CUSTNAME                  STATEMENT   NETSALES
---------- ------------------------- --------- ----------
         3 3                         24-MAY-00       1000
         4 4                         24-MAY-00      10000
         3 3                         25-MAY-00       2000
         4 4                         25-MAY-00      20000
         3 3                         26-MAY-00       2500
         4 4                         26-MAY-00      25000
         3 3                         27-MAY-00       3000
         4 4                         27-MAY-00      30000
         1 one                       03-JUN-00        100
         2 two                       03-JUN-00       1000
         1 one                       04-JUN-00        200
         2 two                       04-JUN-00       2000
         1 one                       05-JUN-00        250
         2 two                       05-JUN-00       2500
         1 one                       06-JUN-00        300
         2 two                       06-JUN-00       3000

16 rows selected.

ops$tkyte@8i> select * from t2;

    CUSTID NETSALES_1 NETSALES_2 NETSALES_3
---------- ---------- ---------- ----------
         1        300        250        200
         2       3000       2500       2000
         3       3000       2500       2000
         4      30000      25000      20000
Review & Followup
Rating: 5
Eye Opener July 18, 2002
Reviewer: Santosh Shenoy from India - Bangalore
Step by step explaination is highly useful. Things are greatly simplified.

Rating: 5
Mutating Trigger in case of Insert into as Select * from..... February 10, 2003
Reviewer: Jignesh Karia from India
Hi Tom,
Thank you very much for your reply.  But I am facing one problem.  --- >

Consider the following Trigger -->

CREATE TABLE MS_COMPANY (
  CM_COMP_CD    VARCHAR2 (6)  NOT NULL,
  CM_COMP_NAME  VARCHAR2 (60),
  CM_TDS_PAN    VARCHAR2 (30));

CREATE TABLE MS_COMPS (
  CM_COMP_CD    VARCHAR2 (6)  NOT NULL,
  CM_COMP_NAME  VARCHAR2 (60),
  CM_TDS_PAN    VARCHAR2 (30));

INSERT INTO MS_COMPS (CM_COMP_CD) VALUES ('COMP1');

CREATE OR REPLACE TRIGGER MS_Comp_Trigger
BEFORE INSERT ON Ms_Company
FOR EACH ROW
DECLARE
vNL_Count NUMBER ;
BEGIN
SELECT count(1)
INTO   vNL_Count
FROM   ms_Company ;
IF vNL_Count = 1 THEN
Raise_Application_Error(-20000, 'No More Company is allowed to add.') ;
END IF ;
END ;

INSERT INTO MS_COMPANY SELECT * FROM MS_COMPS;

Here i receive error for Mutating Table;

But if i write following statement it will work fine ->

INSERT INTO MS_COMPANY (CM_COMP_CD) VALUES ('COMP1');

Could you please explain why this is happening  ? ? ? ?

Followup:
well, you need to look at your logic anyway -- say I just inserted a single row:

insert into ms_company values ( .... );

and did not get the mutating table.  Does my trigger work???  Nope, it will not
do what I want because there is this little thing called "multi-user
concurrency" out there lurking about.  If N sessions all inserted a single row
at about the same time - your trigger would not see their inserts and make a bad
decision -- logically corrupting the data

tell me -- what business rule are you trying to enforce.  Lets start with the
RULE you want to enforce, then i can tell you how to enforce that rule.

February 10, 2003 Florin -- Thanks for the question regarding "order by specific values", version 8.1.7
You Asked
Hi Tom,

I'd like to know if there is any posibility to select from a table by specific
column's values. Example:

SQL> select * from tab1;

PRIORITY
----------
HIGH
LOW
MEDIUM
NONE
TOP

My goal is:

PRIORITY
----------
TOP
HIGH
MEDIUM
LOW
NONE

Thanks a lot in advance.

Florin
and we said...
select * from tab1
order by
decode( column, 'TOP', 1, 'HIGH', 2, 'MEDIUM', 3, 'LOW', 4, 'NONE', 5, 6);
February 10, 2003 Jeremy -- Thanks for the question regarding "Is it possible to do this in SQL instead of PL/SQL?", version 9.0.1
You Asked
I'm am trying to see if there is a way to do in SQL what I'm currently doing in
PL/SQL.

table t1

search_text varchar2(100);

TOM
JIM
BRADY
HR

table t2

code varchar2(20), desc_text varchar2(100);

11, TOM WORKS IN HR WITH JIM
11, JIM IS IN THE NORTH AVENUE BUILDING
12, BRADY DOESN'T WORK HERE ANY MORE, JIM DOES

For each value in t1, I want to do a intermedia text search on t2
and display the results like:

search_text, code, count(*) cnt

TOM,   11, 1
JIM,   11, 2
JIM,   12, 1
BRADY, 12, 1
HR,    11, 1

The results are then inserted into a table.

The procedure is
begin
for x in (select search_text from t1) loop
   execute immediate 'insert into search select :x, code, count(*) cnt
   from t2 where contains (desc_text, :x) > 0
   group by code' using x.search_text, x.search_text;
end loop;
end;
/

So, is this possible just using SQL?

Thanks.
and we said...
ops$tkyte@ORA920> insert into t3
  2  select search_text, code, count(*)
  3    from t1, t2
  4   where contains( desc_text, search_text ) > 0
  5   group by search_text, code
  6  /

5 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t3;

SEARCH_TEX       CODE        CNT
---------- ---------- ----------
HR                 11          1
JIM                11          2
JIM                12          1
TOM                11          1
BRADY              12          1

ops$tkyte@ORA920>
Review & Followup
Rating: 3
Oracle PL/SQL Ansi Sql or Not February 08, 2003
Reviewer: A reader
Hi tom,
The other day i was having a discussion with the Teradata Developer and they
said oracle does not support ansii sql,
I am of view that pl/sql supports that and it also has addiotinal
features,Offcourse i just said that out of my pro Oracle view Is it true..?

Followup:
Why would you ask teradata about Oracle?  but anyway
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/ap_standard_sql.htm#10293
Before I was with Oracle -- I verified as a customer that Oracle was sql89
compliant.  When I joined Oracle I was on the team that first certified Oracle7
under sql92 way back when (and we were the first certified database as well).
We keep up with the standards.

So, how many NIST certificates did teradata ever get?

And -- if you are building a DW (assumption on my part here -- since you mention
teradata) do you really think that

a) you want to stick with only ANSI, lowest common denominator SQL or....
b) you will use every vendor specific extension to its maximum in order to
squeeze every bit of performance and functionality out of the software?

Rating: 5
February 08, 2003
Reviewer: A reader
Tom,

What does "Teradata Developer" mean ??

Thanks

Followup:
teradata = name of a product
teradata developer = someone who develops using teradata

just like I'm an Oracle developer.....

Rating: 3
A Reader February 09, 2003
Reviewer: A Reader from India
What contains does? Is this equal to In operator?

Followup:
contains is a TEXT index -- can be used after

create index iname on table(cname) indextype is ctxsys.context;

See
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-TEX
the text application developers guide

Rating: 4
ctxsys.context index allows function-based index? February 10, 2003
Reviewer: A reader
Tom,

1. So 'contains' is similar to doing:
... where text_field like '%substr%' ...
on a varchar2 field, only now the ctxsys index will be used so the search will
be faster?

2. If I had use UPPER/LOWER such as :
... where UPPER(text_field) like '%substr%' ...
would I be able to create function-based ctxsys.context index?

Followup:
1) similar but totally 100% different.

2) context indexes are case insensitve by default.

suggest you give the material a quick read through -- its part of the database,
you might find it useful

February 11, 2003 Tatiane -- Thanks for the question regarding "Tracking unused objects", version 9.2.0.1
You Asked
Hello Tom.

I'm doing some housekeeping in my environment and I wish to know what objects
(in my case views) are not being used. I thought of using audit for that. But
someone said that I could simply invalidate these views and check if they get
valid over time. Those that are not valid are not being used.

What do you do to check what object are not being used so you can drop them ? Do
you have some script for that ?

Thanks

Tatiane
and we said...
but views can go invalid for other legitimate reasons -- and things that don't
actually query the view may indirectly cause them to be compiled (eg: a pacakge
that references the view in a procedure no one actually calls)

Auditing is the one way to find these "stray" objects that gives you 100%.

and once you get a handle on this, you'll of course keep better system
documentation so you don't run into this again right ;)
Review & Followup
Rating: 5
Unused objects February 10, 2003
Reviewer: Tatiane
Tom, you're lovely. Thanks ! 8)

As for the documentation, that's what I want the housekeeping for. I'm taking
over this system right now and I want to tidy up the mess, before it goes to
production.

Please, let me disturb you a little more... What would be the command for me to
audit my views, and how can I track them more effectively (for example, which
Oracle views should I use) ?

Followup:
well, you can use either of fine grained auditing (dbms_fga) which will capture
the EXACT query against the view or the AUDIT command.

Both of which, you'll want to check out the admin guide for details - the audit
tables are pretty simple and straightforward as well and are all in the
reference guide.

February 11, 2003 Howie -- Thanks for the question regarding "SQL Tuning for UPdate Statement", version 8.0.5
You Asked
Hi Tom

I have following two tables
PS_PAY_EARNINGS   (1320492 Records)
PS_PAY_MESSAGE    ( 27 Records)

I have to run the following query :

update  ps_pay_earnings a
set a.ok_to_pay = 'N'
where exists
(select 'a1' from ps_pay_message a1   where
a1.run_id = 'U07'
and a1.paygroup   = a.paygroup
and a1.company    = a.company
and a1.pay_end_dt = a.pay_end_dt
and a1.off_cycle  = a.off_cycle
and a1.page#      = a.page#
and a1.line#      = a.line# )
AND A.PAY_END_DT = '30-MAR-2001'

The execution plan and cost of this query is as under :

1.10 UPDATE STATEMENT    BB cost = 10
  2.1 UPDATE  PS_PAY_EARNINGS
    3.1 FILTER
      4.1 TABLE ACCESS BY INDEX ROWID PS_PAY_EARNINGS
        5.1 INDEX RANGE SCAN PS_PAY_EARNINGS_L5 NON-UNIQUE
      4.2 TABLE ACCESS FULL PS_PAY_MESSAGE

As we see the cost of this query is low but still it takes lots of time evenif
there is ZERO row to be updated.

Could you help me to tune this query

Thanks
Howie
and we said...
the cost has NO BEARING on the runtime of a query -- no, zero, zippo.  A query
with a cost of 1,000,000,000 might run in 1/100th of a second and a query with a
cost of 1 might take 2 days to complete.

Add an index on ps_pay_earnings(ok_to_pay,pay_end_dt) perhaps.  that'll avoid
the full scan.
Review & Followup
Rating: 3
April 17, 2001
Reviewer: XMW from New Jersey, USA
I have a same kind of problems even I created index. I wrote a PL/SQL to fix
this problem. I declared a cursor to fetch a record one by one from the table to
update. The runtime was decreased from 3-4 days to 6 hours.

Rating: 3
How to tune EXISTS February 10, 2003
Reviewer: A reader
Hi,

I have a similar SQL (which cannot be modified since it is generated by the
application).

UPDATE table_a
set col1='a'
WHERE
EXISTS
(select 'x' from table_b
where table_b.col2=table_a.col2
and table_b.col3=table_a.col3
)

table_a has unique index on (col2, col3)
&
table_b has unique index on (col2, col3)

table_a has 4million rows and table_b has 10 rows.

The execution plan shows that the SQL uses a FTS on table_a. Please let me know
how we can tune this sql.

Thanks

Followup:
sounds like you are using RBO, use CBO and it'll fix itself automagically.  RBO
-- nothing short of blowing up the query and writing it correctly will fix it.

In this example, big_table is 1.8 million rows, t is 10

big_table@ORA920> delete from plan_table;

0 rows deleted.

big_table@ORA920> explain plan for
  2  update big_table set object_name = object_name
  3  where exists ( select 'x' from t where t.id = big_table.id )
  4  /

Explained.

big_table@ORA920> set echo off

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
--------------------------------------------------

----------------------------------------------------------------------
| Id  | Operation            |  Name         | Rows  | Bytes | Cost  |
----------------------------------------------------------------------
|   0 | UPDATE STATEMENT     |               |    10 |   290 |    14 |
|   1 |  UPDATE              | BIG_TABLE     |       |       |       |
|   2 |   NESTED LOOPS       |               |    10 |   290 |    14 |
|   3 |    SORT UNIQUE       |               |       |       |       |
|   4 |     INDEX FULL SCAN  | T_IDX         |    10 |    20 |     1 |
|*  5 |    INDEX UNIQUE SCAN | BIG_TABLE_PK  |     1 |    27 |     1 |
----------------------------------------------------------------------

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

   5 - access("T"."ID"="BIG_TABLE"."ID")

Note: cpu costing is off

18 rows selected.

that full scans the index on T and for each row it finds, does an index access
to big_table -- 10 times

big_table@ORA920>
big_table@ORA920> delete from plan_table;

6 rows deleted.

big_table@ORA920> explain plan for
  2  update /*+ RULE */ big_table set object_name = object_name
  3  where exists ( select 'x' from t where t.id = big_table.id )
  4  /

Explained.

big_table@ORA920> set echo off

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
--------------------------------------------------

--------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
--------------------------------------------------------------------
|   0 | UPDATE STATEMENT     |             |       |       |       |
|   1 |  UPDATE              | BIG_TABLE   |       |       |       |
|*  2 |   FILTER             |             |       |       |       |
|   3 |    TABLE ACCESS FULL | BIG_TABLE   |       |       |       |
|*  4 |    INDEX UNIQUE SCAN | T_IDX       |       |       |       |
--------------------------------------------------------------------

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

   2 - filter( EXISTS (SELECT 0 FROM "T" "T" WHERE "T"."ID"=:B1))
   4 - access("T"."ID"=:B1)

Note: rule based optimization

18 rows selected.

No matter what i do, until I rewrite that query, RBO will FULL SCAN big table
and for each row -- index scan little table

February 11, 2003 Vladimir -- Thanks for the question regarding "Any ideas on how to make SELECT faster", version 8.1.7
You Asked
Hi Tom.
We have SELECT statement that runs too long.
There is table that is accessed very , very often.
KEY column contain first digits of value. Key_len stores the key length. The
SELECT may return multiple rows. All we need is to make sure that there is key
in slow_select table that maps to first digits of value. Value is longer(has
more digits) than the key.

SQL> create table slow_select(key number(12,0), key_len number(2,0), indicator
number(2,0), other_columns varchar2(32)) nologging;

Table created.

SQL> insert into slow_select values(1, 1,23, 'fdvewa');

1 row created.

SQL> insert into slow_select values(12, 2, 45, 'fdvewa');

1 row created.

SQL> insert into slow_select values(123, 3, 12, 'arwef');

1 row created.

SQL> insert into slow_select values(123456, 6, 12, 'arwef');

1 row created.

SQL> insert into slow_select values(10, 2, 43,'aweraw');

1 row created.

SQL> insert into slow_select values(100, 3, 43,'awer');

1 row created.

SQL> insert into slow_select values(1000, 4, 3, 'esfd');

1 row created.

SQL> insert into slow_select values(10000120,8,34,'sefg');

1 row created.

SQL> alter table slow_select add primary key(key);

Table altered.

SQL> analyze table slow_select compute statistics;

Table analyzed.

SQL> set autot trace
SQL> select 1 from slow_select where key=SUBSTR(&&value, 1, key_len) and
other_columns = 'fds';
Enter value for value: 1234567890123456
old   1: select 1 from slow_select where key=SUBSTR(&&value, 1, key_len) and
other_columns = 'fds'
new   1: select 1 from slow_select where key=SUBSTR(1234567890123456, 1,
key_len) and other_columns = 'fds'

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=10)
   1    0   TABLE ACCESS (FULL) OF 'SLOW_SELECT' (Cost=1 Card=1 Bytes=
          10)

Statistics
----------------------------------------------------------
         39  recursive calls
          4  db block gets
          8  consistent gets
          0  physical reads
          0  redo size
        175  bytes sent via SQL*Net to client
        310  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          0  rows processed
SQL>select 1 from slow_select where key=SUBSTR(&&value, 1, key_len) and
other_columns = 'arwef';
old   1: select 1 from slow_select where key=SUBSTR(&&value, 1, key_len) and
other_columns = 'arwef'
new   1: select 1 from slow_select where key=SUBSTR(1234567890123456, 1,
key_len) and other_columns = 'arwef'

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=10)
   1    0   TABLE ACCESS (FULL) OF 'SLOW_SELECT' (Cost=1 Card=1 Bytes=
          10)

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        305  bytes sent via SQL*Net to client
        421  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          2  rows processed
SQL> spool off

Sure it uses FULL table scan because I inserted just a few rows.
However in real life this table has 100000 rows. It takes 2 seconds to get the
result. And this table is accessed very, very often.
I tried to build index on key and key_len. I used the index hint to make Oracle
optimizer to use this index. It was even worse since it did INDEX FULL scan and
then accessed table by ROWID.

I cannot come up with any good SELECT in this case. I thought of partitioning
this table but data in KEY column are not distributed evenly. You may have 10
keys starting with 1, 50000 key starting with 4, and 4000 starting with 7 for
example.

Thank you very much in advance.
Vladimir
and we said...
Oh man -- another case of

'our most popular query, the one that will make our system live or die, runs
really slow.  why didn't we try tons of other methods of storing the data first,
before we rolled it out'....

using numbers to store strings -- or comparing strings to numbers and converting
-- bummer.

select 1 from slow_select where key=SUBSTR(1234567890123456, 1,
key_len) and other_columns = 'fds'

Lets see, you could query:

select *
  from your_problem_table
 where other_columns = 'fds'     <<<<=== this is useful maybe
   and key_len <= length(:bind_variable)  <<<== if key_len >, substr fails
   and substr(key,1,1) = substr(:bind_variable,1,1)  <<<=== that has to be true
   and key = substr(:bind_variable,1,key_len)

and then a function based index on

(other_columns,key_len,substr(key,1,1))

might help you reduce the number of rows you need to scan through.
see
http://asktom.oracle.com/~tkyte/article1/index.html

Partitioning?  how would that help -- probably make it worse.  You need to find
a way to phrase your query such that every row need not be inspected -- no magic
here...
Review & Followup
Rating: 5
All ingeniuos is simple February 08, 2003
Reviewer: Vladimir from Albany
Tom,
thank you very much for your help and time.
Vladimir

Rating: 5
Let me thank you again February 08, 2003
Reviewer: Vladimir from Albany
Thank you very, very much.

Rating: 3
Differebce between an Join and small Diff queries February 09, 2003
Reviewer: A reader
Hi Tom,
Is there a difference between using  a join or using small queries.
For eg
I have join on 10 tables
Instead of joining the 10 tables i try and use the following
select id1
from table
where columnid = 10,(select id2
                     from table2
                     where column = 10),....

If there a difference in execution of queires in this manner or using joins,
which method is more quicker and suggested

Thanks

Followup:
syntax error -- don't get what you are saying here.  the above is not valid SQL

Hopefully you are not running multiple queries -- just one query would be the
right answer.

Rating: 4
Alternative query February 09, 2003
Reviewer: David from Melbourne, Australia
Tom,
I entirely agree that the data model is wrong.

I think this alternative query can be faster - since key can only be between 1
and 12 digits, try each possibility:
select 1 from slow_select where key in
(SUBSTR(&&value, 1, 1),  SUBSTR(&&value, 1, 2),  SUBSTR(&&value, 1, 3),
SUBSTR(&&value, 1, 4), SUBSTR(&&value, 1, 5), SUBSTR(&&value, 1, 6),
SUBSTR(&&value, 1, 7), SUBSTR(&&value, 1, 8), SUBSTR(&&value, 1, 9),
SUBSTR(&&value, 1, 10), SUBSTR(&&value, 1, 11), SUBSTR(&&value, 1, 12) )
 and other_columns = 'fds'

On 35,000 rows for which the original query takes 189 consistent gets, this
takes  13  consistent gets
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=6)
   1    0   INLIST ITERATOR
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'SLOW_SELECT' (Cost=3 Card=1
Bytes=6)
   3    2       INDEX (RANGE SCAN) OF 'SYS_C0067815' (UNIQUE) (Cost=2 Card=1)

It may be even faster with an index on key plus other_columns.

Thanks for your very helpful answer to my earlier question about DBMS_STATS.

Followup:
Excellent idea -- bummer I did not think of it myself ;)

Very nice

Rating: 5
Got embarassed twice. February 10, 2003
Reviewer: Vladimir from Albany
Thank you, David and Tom.

Rating: 5
A remark February 10, 2003
Reviewer: Vladimir from Albany
Got embarassed and happy twice:-)

Rating: 5
Data model February 10, 2003
Reviewer: Vladimir from Albany
Now that I calmed down my next question would be
what changes can be made to improve data model?
Could you elaborate on this?
Thank you in advance.

Followup:
well, it seems we have to convert numbers to strings or strings to numbers - be
nicer if we did not -- guess we could:

select 1 from slow_select where key in
( value, trunc(value/10), trunc(value/100), .... )

and just beware of false hits on ZERO values.  There key_len is not relevant
anymore....

look to remove any and all implicit or explicit conversions.

Rating: 3
Data model. February 11, 2003
Reviewer: Vladimir from Albany
Thanks, Tom.
I already recreated table with appropriate datatypes.

Followup:
Now that we have an appropriate way thanks to David -- seems dandy to me if
performance is OK for you.

It is a sticky problem -- the question you are asking. You want every row that
is "like" your input string -- in reverse

  where INPUT_VARIABLE like db_column || '%';

the inlist, since it is short enough, looks like a valid approach.

February 11, 2003 Raja -- Thanks for the question regarding "Multi Dollar Question", version 8.1.5
You Asked
Dear Tom ,

1. Can you please eloborate ( importance and differences )
of the  v$ and gv$ Views ?

2. Why is Oracle maintaining same two views :

   v$parameter  & v$parameter2

   v$system_parameter & v$system_parameter2

Regards
Raja
and we said...
v$ are single instance views.
gv$ are multiple instance views (for Oracle Parallel Server)

From the Server Reference:

<quote>
V$ Views
The actual dynamic performance views are identified by the
prefix V_$. Public synonyms for these views have the prefix V$.
Database administrators and other users should access only the
V$ objects, not the V_$ objects. The dynamic performance views
are used by Enterprise Manager and Oracle Trace, which is the
primary interface for accessing information about system
performance. Once the instance is started, the V$ views that
read from memory are accessible. Views that read data from disk
require that the database be mounted, and some require that the
database be open.

GV$ Views
For almost every V$ view described in this chapter, Oracle has a
corresponding GV$ (global V$) view. In a parallel server
environment, querying a GV$ view retrieves the V$ view
information from all qualified instances. In addition to the V$
information, each GV$ view contains an extra column named
INST_ID of datatype INTEGER. The INST_ID column displays the
instance number from which the associated V$ view information
was obtained. The INST_ID column can be used as a filter to
retrieve V$ information from a subset of available instances.
</quote>

V$parameter shows "denormalized" views of parameters, eg:

  1* select * from v$parameter where num  = 240
ops$tkyte@ORA8I.WORLD> /

       NUM NAME                                 TYPE
---------- ------------------------------ ----------
VALUE
------------------------------------------------------------
ISDEFAULT ISSES ISSYS_MOD ISMODIFIED ISADJ
--------- ----- --------- ---------- -----
DESCRIPTION
------------------------------------------------------------
       240 rollback_segments                       2
rbs_01, rbs_02, rbs_03, rbs_04, rbs_05, rbs_06, rbs_07, rbs_
08, rbs_09, rbs_10, rbs_11, rbs_12, rbs_13, rbs_14, rbs_15,
rbs_16, rbs_17, rbs_18, rbs_19, rbs_20, rbs_21, rbs_22, rbs_
23, rbs_24, rbs_25, rbs_26, rbs_27, rbs_28, rbs_29, rbs_30
FALSE     FALSE FALSE     FALSE      FALSE
undo segment list

if we goto v$parameter2, the one line is turned into many
rows

ops$tkyte@ORA8I.WORLD> c/ter/ter2
  1* select * from v$parameter2 where num  = 240
ops$tkyte@ORA8I.WORLD> /

       NUM NAME                                 TYPE
---------- ------------------------------ ----------
VALUE
------------------------------------------------------------
ISDEFA ISSES ISSYS_MOD ISMODIFIED ISADJ
------ ----- --------- ---------- -----
DESCRIPTION
------------------------------------------------------------
       240 rollback_segments                       2
rbs_01
FALSE  FALSE FALSE     FALSE      FALSE
undo segment list

       240 rollback_segments                       2
rbs_02
FALSE  FALSE FALSE     FALSE      FALSE
.....
Review & Followup
Rating: 3
Is there any way ? April 04, 2002
Reviewer: Ravi
Hi Tom,

I have a procedure that opens a cursor and loops through it and do some
processing. It is taking too long to finish. Can we findout from any of the
dynamic views, how many rows the cursor has selected and which row it is
currently processing.

Thanks,
Ravi.

Followup:
You can call dbms_application_info (a supplied package, if you have my book -- i
have a chapter on it, if not see the supplied packages guide)

Using that you can

o set client_info, module, action in v$session easily
o use session_longops to do a more detailed progress report.

Rating: 3
October 02, 2002
Reviewer: Sagi from India
Hi Tom!

I think you forgot about v$system_parameter.

I saw that the different between V$SYSTEM_PARAMETER and V$SYSTEM_PARAMETER2. You
explanation for V$PARAMETER & V$PARAMETER2 stands good here too.

But what is the difference between V$PARAMETER and V$SYSTEM_PARAMETER?

Thanx in advance.

REgards,
Sagi

Followup:
check out that nifty reference guide I refered to and quoted heaving from.

Amazingly -- it describes each and every single one of these magic v$ view (he
says totally tongue in cheek)

Rating: 4
dbms_application_info and parallel February 11, 2003
Reviewer: David from Nashville, TN
Tom,

I'm using dbms_application_info.set_module and I see this when I query from
v$session in the module_name column.  However, if a process is running in
parallel, I don't see the module_name filled in for the parallel sessions.  For
example, we have 4 cpu's - I see my main session with the module name filled in,
but the four sessions spawned off for parallel operations have null in the
module_name column.  Is there any way to have the module_name filled in for
these other four sessions?

Thanks!

Followup:
Nope, but you can use v$px_session to see who goes with who so you know the
QC/slave relationship


Subject | Article | Last Updated (East coast time)
Redo Log switches | article only | Feb 12, 2003 08:25 am
After RAISE_APPLICATION_ERROR in the trigger, it should do the task | article only | Feb 12, 2003 08:27 am
WITH statement in 9i release 2 | article only | Feb 12, 2003 04:39 pm
display CLOB data with formatting information | article only | Feb 12, 2003 05:11 pm
Q1: Outer Query, Q2: oinstall vs dba | article only | Feb 12, 2003 06:16 pm
cursor questions | article only | Feb 13, 2003 09:31 am
sql tuning | article only | Feb 13, 2003 09:59 am
Generate report for recreating Procedures | article only | Feb 13, 2003 05:29 pm
Optimize datatypes | article only | Feb 13, 2003 06:51 pm
cbo parameters | article only | Feb 13, 2003 06:54 pm
select the n'th highest record | article only | Feb 14, 2003 07:34 am
Searching CLOB Data | article only | Feb 14, 2003 07:36 am
Upgrade from 8.1.6 to 8.1.7 | article only | Feb 14, 2003 07:56 am
Performance of Oracle Analytic Functions | article only | Feb 14, 2003 08:22 pm
question about order by statements in select cast(multiset( | article only | Feb 14, 2003 08:48 pm
about scalability | article only | Feb 15, 2003 03:02 pm
Not a real Question!. Just want to thank ones again Tom and his great asktom.com team and his great service. | article only | Feb 15, 2003 06:37 pm
For calculating data growth | article only | Feb 15, 2003 06:44 pm
How to get MIN and MAX of Consecutive Numbers | article only | Feb 15, 2003 06:54 pm
Customizing Oracle Reports | article only | Feb 16, 2003 11:15 am
Get all Partition Rowcounts for a table | article only | Feb 16, 2003 12:48 pm
partition by clause | article only | Feb 16, 2003 01:01 pm
check constraints | article only | Feb 16, 2003 01:04 pm
Tunning heterogeneous services generic connectivity to Sql Server | article only | Feb 16, 2003 03:34 pm
sql load | article only | Feb 16, 2003 07:01 pm
Mod PL/SQL flush function? | article only | Feb 17, 2003 09:50 am
Username's and Passwords | article only | Feb 17, 2003 10:27 am
Need a SQL query to pick Recent month data entered and it's previous month too. | article only | Feb 17, 2003 11:15 am
External C call vs Java | article only | Feb 17, 2003 11:24 am
replication of 24/7 database. | article only | Feb 17, 2003 03:41 pm
Parse to execute ratio greater than 100% | article only | Feb 18, 2003 07:11 am
Database reorg | article only | Feb 18, 2003 07:19 am
Loading CSV files into Oracle | article only | Feb 18, 2003 07:32 am
PARTITION RANGE SINGLE PARTITION | article only | Feb 18, 2003 08:20 am
shared_pool_size | article only | Feb 18, 2003 07:02 pm
row(s) 1 - 35 of 35
February 12, 2003 Laura -- Thanks for the question regarding "Redo Log switches", version 8.0.5.2.1
You Asked
I'm in a situation where my production system goes through peak periods of
activity throughout the day, and my 30 mg redo logs are switching as often as
once a minute.  At other times during the day, though, it can literally be hours
and hours before a log switch happens.

The reason that I would like to generate redo logs every 30 minutes (instead of
just performing a checkpoint) is that the redo log switch forces an archive to
be written and once the archive file is written out, then I have a backup
process that copies those archives to a backup machine.  This way, if my
production machine goes down, and for some reason I have to restore the database
from a backup tape from the night before, I will only lose 30 minutes of work at
any given time.

What I was thinking of doing was creating a simply procedure that would perform
an "alter system switch logfile" and then using dbms_job to run it every 30
minutes.  That way, I don't have to decrease the size of my logfiles and run
into contention problems during peak processing times due to the high number of
log switches, but also don't go a very long time without having a log switch.

What do you think of this idea?  Are there any drawbacks to it that I haven't
thought of?  It seems to work without problem on my test system, but that system
doesn't see the same kind of activity as the production system.
and we said...
Using DBMS_JOB in that fashion is totally appropriate -- use the FIRST_TIME
column in v$log for the CURRENT log to see if you need to switch it or not.

See
http://osi.oracle.com/~tkyte/Misc/RolesAndProcedures.html
for a hint on doing procedures that do DDL.

DBMS_UTILITY.EXEC_DDL_STATEMENT will come in handy as well.

follow up to comment two below

I believe you have misread the question.  They want to ensure that a log switch
occurrs AT LEAST every N minutes.  during peak, they will switch as fast as they
will naturally switch.  this will not affect any thing that is currently
happening at peak.
Review & Followup
Rating: 5
June 19, 2001
Reviewer: Laura from Chicago, IL

Rating: 2
add more switchs June 19, 2001
Reviewer: Trinh from Atlanta
In the peak hours Adding more switchs would not reduce the contentions, it
increases the contention, if the redo logs' sizes are not increased.

Rating: 3
June 20, 2001
Reviewer: Trinh from Atlanta
Thanks Tom.
If that was the case, I think the redo size should be increased so that at peak
hours less switching, it is not a good ideal to have a every min switch.
And have the job to force switching at light hours.

Rating: 5
FIRST_TIME.V$LOG & dbms_job September 26, 2002
Reviewer: Logan Palanisamy from Logan Palanisamy, Santa Clara, CA, USA
I am currently switching my redo log files every 30 minutes using dbms_job.

variable n number;
exec dbms_job.submit( :n, 'declare l_first_time date; begin select first_time
into l_first_time from v$log where status = ''CURRENT''; if (sysdate -
l_first_time) > 1/48 then execute immediate ''alter system switch logfile''; end
if; end;', sysdate, 'sysdate + 1/48');
commit;

I have a problem with the interval 'sysdate + 1/48'. Let's say

at 5:48 pm - redo log becomes full and switches
at 6:00 pm - dbms_job executes, doesn't switch the log file as expected.
at 6:30 pm - dmbs_job executes, switches the log file if there was no log switch
between 6:00 and 6:30

I want the dbms_job to check back at 6:18pm, insead of 6:30pm. Otherwise, there
could be cases where redo logs were not switched for more than 30 minutes.

Basically, I need to change the interval to 'first_time.v$log + 1/48' instead of
'sysdate + 1/48'. How do I do that?

Thanks.
Logan Palanisamy

Followup:
why why why are you doing that?  whats the goal here?

(i can tell you how to do this but -- i want to know "why")

Rating: 5
Reason September 30, 2002
Reviewer: Logan Palanisamy from Santa Clara, CA USA
Tom,

The reason I want to execute the dbms_job 30 minutes from the last switch redo
log switch(5:48 + 30 = 6:18) instead of 30 minutes from the last dbms_job
execution (6:30) is: If there were no "automatic" redo log switches between 6:00
and 6:30, then the same redo log will be in CURRENT mode for more than 30
minutes (5:48 to 6:30). I want to avoid that.

Here is my criteria for switches
1. No redo log should be CURRENT for more than 30 minutes.
2. Don't switch redo if you don't have to. i.e. Don't switch if it has been
CURRENT for less than 30 minutes when the dbms_job runs.

I know I can blindly switch the redo whenever the dbms_job runs (every 30
minutes). But then, this will cause more redo log switches than necessary.

It is more of a possibility question than anything else.  Basically, can the
dbms_job adjust its interval based on "events"?

Thanks,
Logan Palanisamy

Followup:
Why?  Why do you want to avoid that?  What is the reasoning behind that?

I'm curious -- I've had logs in current mode for days, no issues there....

the job could run every 30 minutes -- peek into v$archived_log to see the
completion time of the last archive and ..... (you can take it from here, but it
seems like an unnecessary exercise to me)

Rating: 4
Reason October 01, 2002
Reviewer: Logan Palanisamy from Santa Clara, CA USA
Tom,

The main reason for manually switching the redo log every 30 minutes using
dbms_job is to keep the standby database to be in sync with the primary and let
it not "lag" the primary by more than 30 minutes at any point in time.

Thanks

Followup:
Great -- thats a valid one -- use the timestamp in v$archive log

In 9i, there is an archive_lag_target init.ora setting that will automate this
for you (reason #12412 for upgrading!)

Rating: 4
Update required for 9.0.1.4.0 February 11, 2003
Reviewer: Matt from Australia
Can you update your response to this one to cover 9.0.1.40?

Basically, a similar situation:

Peak Load - Log switch every 2minutes
Low Load - Log switch every 5 hours

The average looks better (roughlu every 20 minutes), but what I want is to
enforce log switches to occur normally and at least every twenty minutes. If for
some reason the switches are to take longer I want to be able to force the
switch (similar to the original question)

Is there a better was to do this in 9.0.1.4?

Followup:
dbms_job is still a cool thing in 9i but see
http://technet.oracle.com/docs/products/oracle9i/doc_library/901_doc/server.901/a90117/onlinere.htm#9454

February 12, 2003 Senthil -- Thanks for the question regarding "After RAISE_APPLICATION_ERROR in the trigger, it should do the task", version 8.1.7
You Asked
Hi Tom,
   I have following trigger code

CREATE OR REPLACE TRIGGER SetSecureMessageIncoming AFTER INSERT OR UPDATE ON
KC_MESSAGE
REFERENCING OLD AS OLD NEW AS NEW
FOR EACH ROW WHEN (NEW.TYPE = 1)
DECLARE
  vMailboxid Varchar2(100);
  vCaseid   Kc_Message.Caseid%TYPE;
  vPlatformmessageid INTEGER;
BEGIN
  IF :NEW.TYPE = 1 AND :NEW.CASEID IS NOT NULL  AND :OLD.CASEID IS NULL  Then
      GetIncomingCustomFieldValue(:NEW.MESSAGEID,vMailboxid,vPlatformmessageid);
      /*If Mailboxid is not null then the incoming message is a secure then
Update the KP_MESSAGEHEADER table */
      If upper(vMailboxid) = 'MAILBOXID' THEN
          RAISE_APPLICATION_ERROR ( -20601, 'Mailbox Id has the literal Value
MailboxId');
      ElsIF  NVL(TO_NUMBER(vMailboxid),0) > 0  then
              UPDATE Kp_Messageheader
                 SET responsemessageid=To_Char(:NEW.MESSAGEID),
caseid=To_Char(:NEW.CASEID)
              WHERE Kp_messageheader.Messageid=vPlatformmessageid;
      End If;
  End If;
END;

When i execute following sql

Update kc_message set caseid = 1000112 where messageid = 1052434;

This trigger code executes
 upper(vMailboxid) = 'MAILBOXID', it raise an error

My question is after raising the error the trigger should continue

updating  caseid = 1000112 where messageid = 1052434;
and we said...
Umm, no it shouldn't.  You raised an error.  The error will fail the current
statement, undoing all of the work performed by that statement AND ALL OF THE
SIDE-EFFECTS (eg: sql done in the trigger and any other triggers that fired).

Statements must be atomic, that update either succeeds totally or fails totally.
 It does not do a bit of this, a little of that but fail on something else.

You cannot achieve the "illogical" logic you propose.  The database would be in
a very unknown state otherwise.
Review & Followup
Rating: 4
After RAISE_APPLICATION_ERROR in the trigger, it should do the task August 14, 2002
Reviewer: Senthil Kumar from Oakland, Ca USA
Thanks for the quick reply. Appreciated.

Is there Anyway i can achieve this operation.

I am using a java application to insert this record, when the value for
MailboxId has literal value 'MailboxId'. Though i am  validating the value in
the java application, i would like to validate this in the database level as
well and raise an error in the java application log, the only way to do in the
backend is to raise an error while inserting the record in the database.

Any Information would be very appriciable.

Followup:
I'm not really at all certain what it is you actually intend to achieve, can you
spell that out.

Rating: 5
How to trap errors in database triggers February 12, 2003
Reviewer: Nasser
Tom

We have no exception section for our datbase triggers i.e. neither we are
raising raise application errors or have error logging into a error log table.

The problem now is ..

While doing an update from our application ( forms application) I'm getting a
dbms_error_code of ora-1403 on an update statement.

An update can either fail or succeed but cannot return a no_data_found according
to me. So I'm dam sure that the error is coming from one of the database
triggers on the table I'm trying to update.

Now how can I trap the error and zero in on the trigger which it is coming from.
 Infact I want to zero in on the sql which it is coming from.

When I trace will the trace file contain the no_data_found error encountered. I
have traced but could not find such an error.

Let me know your method of tackling this situation.

Followup:
If you do "help view error" (i believe it is something like that) -- you should
be able to see the entire error stack which should include the trigger name,
sort of like this:

ops$tkyte@ORA920> create table t ( x int );

Table created.

ops$tkyte@ORA920> insert into t values ( 1 );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create trigger t_trigger before update on t for each row
  2  declare
  3     l_x varchar2(1);
  4  begin
  5          select * into l_x from dual where 1=0;
  6  end;
  7  /

Trigger created.

ops$tkyte@ORA920> update t set x = 1;
update t set x = 1
       *
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at "OPS$TKYTE.T_TRIGGER", line 4
ORA-04088: error during execution of trigger 'OPS$TKYTE.T_TRIGGER'

February 12, 2003 Sajid -- Thanks for the question regarding "WITH statement in 9i release 2", version 9.0.2
You Asked
Hi Tom,

Trying to use WITH statement:

Server : Oracle 9i Release 2.
Client : SQL Plus 8.1.6

with t_sysdate as (select sysdate as sdate from dual)
select sdate+1 from t_sysdate

SQL> with t_sysdate as (select sysdate as sdate from dual)
SP2-0734: unknown command beginning "with t_sys..." - rest of line ignored.
SQL> select sdate+1 from t_sysdate

BUT THIS WORKS FINE

select * from ( with t_sysdate as (select sysdate as sdate from dual)
select sdate+1 from t_sysdate )

What am i doing wrong here?

One simple question for you:

i have table (say) companies ( comp_id, comp_name )
and then i execute this
SQL> SELECT comp_id comp_name FROM companies;

COMP_NAME
---------
     1000

Where as i was expecting error "Comma missing". Could you please explain this
behaviour?

Many thanks,

Sajid Anwar
London
and we said...
you are using a release of sqlplus that doesn't feel commands should start with
WITH -- hence you are getting a sqlplus error -- not a SQL error here.

it is the sqlplus parser saying "i have NO CLUE" what to do with a statement
that begins with WITH.

You either need to use the sqlplus client for the highest release database you
use or forgo the use of some new features (try to use create type against 8i
with 7.x sqlplus for example)
Review & Followup
Rating: 5
thanks February 13, 2003
Reviewer: Sajid Anwar from London
Hi Tom,

You forgot to say anything about my little question in the previous request.
Here is once again for you.

i have table (say) companies ( comp_id, comp_name )
and then i execute this
SQL> SELECT comp_id comp_name FROM companies;

COMP_NAME
---------
     1000

Whereas i was expecting error "Comma missing". Could you please explain this
behaviour?

Following 2 statements are OK as 'Company' is not one of the columns in the
companies table.
SQL> SELECT comp_name as Company FROM companies;
Company
---------
Test

or

SQL> SELECT comp_name Company FROM companies;
Company
---------
Test

I dont know whether its really worth wasting your time asking this question.

Many thanks,

Sajid Anwar
London

Rating: 5
Clever but not a mind reader February 13, 2003
Reviewer: A reader
The behavious you describe is a common cause for mistakes. There is no error as
the parser sees nothing wrong with the fact you have missed a comma.

The fact you are using an actual name used by another column has no consequence,
the fact you did not specify a comma means you want Oracle to display the first
column with the heading shown.

Its good practice to use

Select emp_name AS employee_name from emp

but its the same as using the following

Select emp_name empoyee_name from emp

It only becomes apparent when you include a third column

select location_reference location_name short_name from location
                                        *
ERROR at line 1:
ORA-00923: FROM keyword not found where expected

or you get back a list with less rows than you wanted.

February 12, 2003 Roland -- Thanks for the question regarding "display CLOB data with formatting information ", version 8.1.7
You Asked
How do I display CLOB data with formatting information using the
htp.p procedure

If I get the data in sql*plus, you can see that I have 4 lines in the CLOB.
Yet If get the data through the htp.p procedure, then the text is displayed on 1
line.
I'd like to display the information like sql*plus shows it but then through the
web browser.

If I select the data with sql*plus:

SQL> select dbms_lob.substr( text, 4000, 1) from TBL_ART_BANK
  2  where id=1270;

DBMS_LOB.SUBSTR(TEXT,4000,1)
--------------------------------------------------------------------------------
Dit is de
text die in de
detailscherm
naar voren komt.

SQL>

This is what I've tried with pl/sql:

Veriable decleration

  clob_selected     clob;
  read_amount       INTEGER;
  read_offset       INTEGER;
  write_amount      number;
  write_offset      INTEGER;
  buffer            VARCHAR2(4000);

I've enitialized the veriable in 2 ways with the same result and I've also tried
selecting the data into the clob_selected veriable (which doesn't seem to work
with the dbms_lob.substr.

  select dbms_lob.substr( text, 4000, 1) into buffer
  from TBL_ART_BANK
  WHERE id = g_id;

read_amount := 4000;
  read_offset := 1;
  dbms_lob.read(clob_selected, read_amount, read_offset,
       buffer);
  dbms_output.put_line('clob_selected value: ' || buffer);
    htp.p(buffer);

thanxx,

ROland Daane
and we said...
This has nothing to do with a clob.

It has everything to do with HTML.

You can either:

htp.p( '<pre>' );

 select dbms_lob.substr( text, 4000, 1) into buffer
  from TBL_ART_BANK
  WHERE id = g_id;

read_amount := 4000;
  read_offset := 1;
  dbms_lob.read(clob_selected, read_amount, read_offset,
       buffer);
  dbms_output.put_line('clob_selected value: ' || buffer);
    htp.p(buffer);

htp.p( '</pre>' );

to print it out "preformatted" or:

 select dbms_lob.substr( text, 4000, 1) into buffer
  from TBL_ART_BANK
  WHERE id = g_id;

  read_amount := 4000;
  read_offset := 1;
  dbms_lob.read(clob_selected, read_amount, read_offset,
       buffer);
  dbms_output.put_line('clob_selected value: ' || buffer);
  htp.p(  REPLACE( buffer, CHR(10), '<BR>' ) );

simply replace the linefeeds with html <br> (break) tags.

Whitespace -- it doesn't count in HTML!!!  It ignores it.  To see this, just
create a file:

hello                                      World
hello

world

You'll get an HTML page with:

hello World hello world

on it.
Review & Followup
Rating: 5
thanxx January 06, 2002
Reviewer: A reader
Now why didn't I think of adding the pre tag to the html code???

thanks lots!

Roland Daane

Rating: 5
Thanks and Questions February 12, 2003
Reviewer: Meyer from Baltimore, MD
This is very helpful

My table has a clob that is updated and displayed on the web...never used <pre>,
always used replace(char(10),'<br>')...I am going to use <pre>.

This is going to be a low use program so this matters not...but wondering if
this the easiest way or am I making Oracle work harder then it needs to to
display the clob:
---START snip

 buffer         varchar2(32000);
  buffer_size    integer := 32000;
  offset         integer := 1;
  length         number(8);

   begin

      length := dbms_lob.getlength(ww_articles_row.article_html);
      while offset < length loop
        dbms_lob.read(ww_articles_row.article_html, buffer_size, offset,
buffer);
        --htp.p('My clob Length ' || length);
        htp.prn(buffer);
        offset := offset + buffer_size;
     end loop;
  exception
      when others then
    htp.p(sqlerrm);
  end;

---------END

Now...for getting the data INTO the clob or updating...

Can I use a textarea (standard HTML) and have a clob input parameter into my
procedure...if yes...what basic syntax works to define the parameter's default
value, and update the clob in the table (Basic is ok, I should pick it up).

I get a:

PLS-00306: wrong number or types of arguments in call to PROCEDURE_NAME

but if my input parameter is defined as a long (instead of clob)...works like a
charm...

If you need to see code, let me know (thought you may not).

Also, what is the max size of data you can get into the clob, using an html
textarea (the long limit should work in my case, but wondering)?

Thanks,
Meyer

Followup:
that routine to print is just fine.

As for getting "in" you have to understand that a CLOB is a pointer of sorts --
it isn't really a string (although the distinction blurs in 9i a bit more).  so,
using LONG which is really a

  subtype LONG is VARCHAR2(32760);

as defined in standard.sql -- is the right type to use.

The limit is 32k

Rating: 5
Interesting February 12, 2003
Reviewer: Meyer from Baltimore, MD
Thanks for the quick reply.

Now I am just curious...

Do you see this becoming an issue as more applications are web based..even if
just for outliers in applications being used in ways not defined as normal.

examples...

Replying to a super SUPER long email thread in a web based email client, that
puts everything previous into the reply...the kind where the entire company
needs to add their 2 cents?

A physicians diagnosis report being transcribed and him writing a virtual book.

A web based program for reporters to send the stories to the home office..and
publish on the web in real time.

What current decent client besides html can go over 32k for Clobs.

BTW, there are javascripts that can limit textarea input (count length at each
key and stop at max)....so the user will not get an error message from the web
server by sending over 32k. I know relying on Javascript is crappy...but this is
the best solution I have found to keep someone who does "write a book" from
losing it.

Thanks,
Meyer

Followup:
examples...
1) super long email thread -- your reply only needs to be 32k -- the rest is in
the server.  Just append that text to what is in the server already?

2) see #1...

3) file upload perhaps of a html file (with formatting) or an rtf file or
something

You can use clobs and blobs --  you would be using a file upload for them

Rating: 5
Makes Sense February 12, 2003
Reviewer: Meyer from Baltimore, MD
This all makes sense to me...
Thanks for your opinion/expertise.

February 12, 2003 Prince -- Thanks for the question regarding "Q1: Outer Query, Q2: oinstall vs dba", version 8.1.7.2
You Asked
Q1:
----
Can you explain the behaviour of the following query:

SQL> select * from t1 where cny# = 10 ;

      CNY#      ACCT#
---------- ----------
        10         89

SQL> select * from t3 where cny# = 10 ;

      CNY#    RECORD#  RANGEFROM
---------- ---------- ----------
        10         11
        10         13

SQL> select t1.*, t3.* from t1, t3 where t1.cny# = 10 and t1.cny# = t3.cny#(+) ;

      CNY#      ACCT#       CNY#    RECORD#  RANGEFROM
---------- ---------- ---------- ---------- ----------
        10         89         10         11
        10         89         10         13

SQL> select t1.*, t3.* from t1, t3 where t1.cny# = 10 and t1.cny# = t3.cny#(+)
and t1.acct# >= t3.RANGEFROM (+) ;

      CNY#      ACCT#       CNY#    RECORD#  RANGEFROM
---------- ---------- ---------- ---------- ----------
        10         89

Why does the above returns just one row while the one above returns two
(expected) rows?

Q2:
---
When you install the oracle s/w, oracle recomends to have one group (oinstall)
for the s/w owner and one group (dba) from the OSDBA group.

If the s/w is installed as the user (oracle) with primary group oinstall and, a
demo DB is created, all the files will have the "oinstall" as the group.

Now, where is the "dba" group used?
(place, other than connecting to the instance from the same machine as sysdba)

Is it better to just install the s/w as the oracle.oinstall user and creating
the DB as <user>.dba?

Thanks,
and we said...
q1) well, the first one didn't need an outer join, but even so -- there was 1
record in T1 that satisfied the criteria.  There were 2 rows in T3 that
satisified it.  When joined by t1.cny# = t3.cny# - you get two rows.

In the second case, there is one row in T1.  Looking at the predicate piece:

  t1.cny# = t3.cny#(+) and t1.acct# >= t3.RANGEFROM (+) ;

there are ZERO rows in T3 that satisfy this (rangefrom is NULL, NULL is not less
then anything, hence there are ZERO rows in T3).  Therefore, you get the row
from T1 back along with "made up nulls" from T3 -- just one row (thats the
semantics of an outer join -- when no data, make up ONE null row)

Basically -- it is doing exactly what an outer join is supposed to do.

q2)  the DBA group is used for OS authentication to allow you to "connect / as
sysdba".  In 8i and before it allows you to "connect internal" -- if you are a
member of that group.
Review & Followup
Rating: 4
I got it. June 04, 2002
Reviewer: Prince
Thanks Tom,

I have one more question on Q2:

Do the users of DBA group need to have "oinstall" as their one of the secondary
groups?.

Most of the files can only be writen by oracle or the installer group (say
network config files)

Followup:
Depends, do you want the DBA to have that group?  "have", no, "want" -- maybe.

Rating: 5
February 12, 2003
Reviewer: A reader
lets say we are running in the environment with sw owner (oisntall group) and
other two accounts having osdba and osoper os groups
if the operations are to be done using account having osoper group, how can this
user restore the datafiles from backup (user having oinstall group is datafiles
owner) in case user managed backup is implemented

Followup:
simple OS file permissions and who needs be in what groups to do what should be
pretty simple to figure out no?

The guy in the osoper group would obviously need the ability to write the files
in question -- choices abound:

o datafiles owned by yet a third group "foobar" that all necessary parties are
part of...

o osoper is considered "not good enough" to restore files

o files are in the osoper group and writable by that group

whatever you want to do I guess?

February 13, 2003 sushant -- Thanks for the question regarding "cursor questions", version 8.1.5
You Asked
what is the advantage of using ref cursor.
cant we just have more than 1 cursor

in a cursor why can we use where current of only with for update clause
and not for cursors which have not obtained locks.

when to use implicit cursors over explicit cursors.(i have never really
understood the difference)
and we said...
You use a ref cursor when

o you don't know until runtime what the query will be, you can either
DYNAMICALLY open the ref cursor using "open l_cursor for 'select * from ' ||
tname" or you can use if/else blocks

o you need to return a result set to a client, ref cursors are the only way to
do it.  see
http://osi.oracle.com/~tkyte/ResultSets/index.html
If you need neither of those bits of functionality -- use STATIC SQL with
regular cursors.  There is a certain overhead to ref cursors....

As for the "where current of", its designed to protect you from your self.
Basically, if you have read the data and you turn around and update without

a) re-reading to see if the data changed since you read it
b) locking it whilst re-reading to prevent someone from changing it

you have written code that suffers from what is known as a lost update.
Therefore, SQL does not allow you to do the "where current of" unless you read
and lock the data at the same time.

It just doens't make sense to READ out data and then update it without checking
to see if someone hasn't already updated it.  If you KNOWN you will read and
update -- then by all means, do the select for update, otherwise you need to
update like this:

   update t
      set c = :new_value
     where primary_key = :primary_key
       and c = :old_value

and check to make sure only ONE row gets updated (and at least one row gets
updated)

Always use implicit cursors -- only use explicit cursors when you cannot use an
implicit one (eg: ref cursors are pretty much the ONLY time i use explicit
cursors -- never any other time).

Implicit cursors are easier to code.
Implicit cursors execute faster.
Implicit cursors are easier to maintain.
IMHO -- Implicit cursors make the code 100% more understandable.

search for

implicit explicit

on my site for more examples/benchmarks/etc.
Review & Followup
Rating: 4
Related doubt November 28, 2001
Reviewer: Nirmal from Qatar
Assume that i'm executing a delete statement which is going to delete 100 rows.
In this case how many rows oracle will process, either 1 or 100?...

my answer is 100, but selftest says it's single.

which is correct?

Nirmal.

Followup:
insufficient information.

I can run a delete that says:

SQL> delete from t;
1 row deleted.

and it really deleted 100 rows ( there was a child table with ON DELETE CASCADE)

I can run a delete that says:

SQL> delete from t;
100 rows deleted.

and zero rows will be deleted (t was a view with an instead of trigger that
turned deletes into updates)

and so on...   Your example provides insufficient data to make any conclusion.

Rating: 4
November 28, 2001
Reviewer: A reader
Well.

SCOTT@ORANS.QTEL.COM.QA> select empno, job, sal from emp where deptno=10;

    EMPNO JOB             SAL
--------- --------- ---------
     7782 MANAGER        2450
     7839 PRESIDENT      5000
     7934 CLERK          1300

SCOTT@ORANS.QTEL.COM.QA> update emp set sal=sal+10 where deptno=10;

3 rows updated.

Now the update statement processed how many rows, either 3 rows or one row?

I had this doubt since i found one question in selftest, as follows:

Which characteristic applies to an implicit cursor?

A: will process only one row
B: will attempt only one fetch
C: allows the programmer to control the number of fetches performed
D: will perform only one fetch and will process all the rows returned by the
query.

The correct answer is (A).

Can you justify, this answer please.

As per my example it's not necessarily only one fetch, it can be more than one
also. Isn't it? or else i'm missing something here?

Regards,
Nirmal.

Followup:
that update (assuming EMP is a table not a view, assuming that there are no
TRIGGERS on emp) updated 3 rows.

The correct answer to that question is NOT (a) -- none of the answers are
correct.

Proof by example:

(a)
begin
   update emp set sal = sal+10 where deptno = 10;
end;

that update is an implicit cursor.  it touches more then one row.  So (A) is
false.

(b)  update emp set sal = sal+10 where deptno = 10;

Well, that attempts 0 fetches. So, (B) is false.

(c) see b

(d) see b

Now, it seems they were talking ONLY about implicit SELECT's although they
didn't say that.  Then, (c) is the 'most' correct answer....

(a)  for x in ( select * from emp )
     loop
            .....

will process ALL rows in emp and if emp has more then one row -- it'll process
more then one.  (A) is false.

(b) see (a)

(c) for x in ( select * from emp )
    loop
        exit when (I_feel_like_it);
         .....
    end loop;

I can stop fetching whenever.

(d) see (a) -- it fetches once per row.

In all, an ambigous (at best) and wrong question.

Rating: 5
my goodness November 29, 2001
Reviewer: A reader
Tom,

Thank you very much.... initially when i was practicing the test, every time i
failed with in this question, i thought that i was wrong. But with ur help, i'm
have been cleared now. Hope that i should not beleive, all test questions were
correct, instead i'll be clear with concepts of oracle.

Thanks.

Nirmal.

Rating: 3
update locking and waiting February 12, 2003
Reviewer: Scott Wesley from Perth, Western Australia
G'day

New to the use of implicit cursors, I'm unsure of the correct approach to
something I used to do.
If I wanted to update a record, I used to
CURSOR z
SELECT x
FROM y
WHERE a=b
FOR UPDATE OF x NOWAIT;
OPEN z;
FETCH z INTO v;
UPDATE y SET ... WHERE CURRENT OF z;
CLOSE z;
And if the record was locked, I caught the -54 exception.
Now, if I do
UPDATE y SET ... WHERE a=b;
and the record is locked, the pl/sql just waits until the record is free.
Is there an alternative, or should I retain the use of explicit cursors in these
instances?

Followup:
  for x in ( select x, rowid rid from y where a=b for update of x nowait )
  loop
     .....
     update y set ... where rowid = x.rid;
  end;

that is all the "where current of" did -- silently added a "rowid" to the select
and "where rowid = rid" to the update.

Rating: 3
rowid usage February 13, 2003
Reviewer: John from Columbus, OH USA
Tom,
In older versions of Oracle, there is no guarantee for rowid to be the same as
the selected one? Is this changed in 8i or 9i?

Followup:
In version 8.0 and before it is actually the case that the ROWID is never
changing, once assigned to a row, it'll be with that row forever.

In the older releases there WAS a guarentee

February 13, 2003 satyam -- Thanks for the question regarding "sql tuning", version 8.1.5
You Asked
   what is the way to find out that a particular sql
statement is bad or there is some technical
specification that if a sql is performing this much
disk access and sort then it is bad.
and we said...
well, it is a trick question.

You see -- sometimes questions (queries) are really complex, really hard.  They
involve alot of work.

Sometimes queries are (or should be) really simple -- they do not involve alot
of work.

After time, experience and having expectations founded in time and experience --
I can more or less look at a query and sometimes say "it is doing far too much
work".  But the problem is -- there are too many queries in the world to look at
each one.  So, I zero in on the "low hanging fruit", the top queries, the ones
doing the most work.

So, you use stats pack -- find the top "bad" or at least POTENTIALLY bad sql and
ask yourself -- is it doing the amount of work I believe it should be doing?  A
case in point:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5051676080968
and ctl-f for

steve

to see a recent thread.  I looked at the query in the form of:

select min(id) from t where a = :a and b = :b

I know a query like that could be answered in 3 or 4 logical IOS at most.  It
was executed a ton of times (many times per second). In their system, it was
taking 850 logical IO's per execution.  That was a "bad query".

That same query however, if executed once a day -- may well have been a "good
query".

It is a matter of weights and balances here -- you fix that which is broken --
finding what it broken is the adventure.   Tools like statspack,
sql_trace+tkprof and dbms_profiler will get you far if you master them.
February 13, 2003 Umesh -- Thanks for the question regarding "Generate report for recreating Procedures", version ORACLE 8.1.5
You Asked
I want to recreate all procedures in a database in a differnet
database on a different machine and compile them . I am trying
to use this script but, I am not succesful and getting a pile of
error messages. Where could be the problem and how to fix it.
The scriot was taken from Ault's book.

Thanks.

Umesh
set termout off verify off feedback off lines 80 pages 0 heading
off

set recsep off space 0

column text format a79 word_wrap

column line noprint

select 'create or replace '||text,line

from

dba_source

where

owner = upper('&&1') and

type = upper('&&2') and

name = upper('&&3') and

line = 1;

select text,line

from

dba_OBJECTS s1,

dba_source s2

where

s1.OBJECT_type = upper('&&2') and

s1.owner = upper('&&1') and

s1.object_name = upper('&&3') and

s1.OBJECT_type = s2.type and

s1.owner = s2.owner and

s1.OBJECT_NAME = s2.name and

line > 1

order by

2;

select '/' from dual;

Example script to call fprc_rct.sql for all objects of a
specific type for a specific user:

REM

REM NAME : RUN_FPRC.SQL

REM FUNCTION : Generate and execute the exe_fprc.sql procedure

REM USE : Document the procedures and packages and functions

REM for a user or users

REM Limitations : Must have access to dba_source and
dba_objects.

rem The FPRC_RCT.SQL procedure must be in same directory

REM

set termout on

column dbname new_value db noprint

pause Use % for a wildcard - Press enter to continue

accept owner prompt 'Enter object owner:'

accept type prompt 'Enter object type:'

accept name prompt 'Enter object name:'

prompt Working....

set echo off heading off verify off feedback off

select value dbname from v$parameter where name='db_name';

spool rep_out\&db\do_fprc.sql

select unique('start fprc_rct '||a.owner||'
'||'"'||a.type||'"'||' '||a.name)

from

dba_source a, dba_objects b

where

a.owner like upper('&owner') and

a.type like upper('&type') and

a.name like upper('&name')

and a.owner=b.owner and

a.type=b.object_type and

a.name=b.object_name;

spool off

set termout off

spool rep_out\&db\exe_fprc.sql

start rep_out\&db\do_fprc.sql

spool off

undef owner

undef type

undef name

clear columns

set echo on heading on verify on feedback on
and we said...
Well, instead of me debugging that, how about if I tell you how
I do it.

I use a script called "getcode" that is simply:

---------------- getcode.sql ----------------------------
set feedback off
set heading off
set termout off
set linesize 1000
set trimspool on
set verify off
spool &1..sql
prompt set define off
select decode( type||'-'||to_char(line,'fm99999'),
               'PACKAGE BODY-1', '/'||chr(10),
                null) ||
       decode(line,1,'create or replace ', '' ) ||
       text text
  from user_source
 where name = upper('&&1')
 order by type, line;
prompt /
prompt set define on
spool off
set feedback on
set heading on
set termout on
set linesize 100
------------------- eof --------------------------------

it extracts one procedure, function or package to a file.  Thats
it.  If you wanted to get all of the procedures in a schema
extracted to the current working directory, you would run a
script:

--------------- getallcode ---------------------------
set termout off
set heading off
set feedback off
set linesize 50
spool xtmpx.sql
select '@getcode ' || object_name
from user_objects
where object_type in ( 'PROCEDURE', 'FUNCTION', 'PACKAGE' )
/
spool off
spool getallcode_INSTALL.sql
select '@' || object_name
from user_objects
where object_type in ( 'PROCEDURE', 'FUNCTION', 'PACKAGE' )
/
spool off
set heading on
set feedback on
set linesize 130
set termout on
@xtmpx.sql
---------------- eof ---------------------------------

You can see how to filter on that one by adding to the where
clause if you want.

Just run @getallcode_INSTALL to run all of the scripts...
Review & Followup
Rating: 4
really helpful April 11, 2002
Reviewer: Arun Kumar from USA
tom U are great. Simple effective approach.

Rating: 5
just what I needed June 19, 2002
Reviewer: Damien Cobbs from VA, USA
Tom, this is EXACTLY what I was looking for. Thanks.

Rating: 3
getcode February 13, 2003
Reviewer: reddy
I am trying to use your code ,it gives an err ,unable to open the file at the
last .sql when executed.
spool off
set heading on
set feedback on
set linesize 130
set termout on
@xtmpx.sql ...............

SP2-0310: unable to open file "getcode.sql"

Followup:
well, you are not getting the error when executing the last line of the script.

You see, my script is named getcode - you obviously do NOT have a script called
getcode.

I edited a file called getcode.sql.  In that i put the above statements.  That
way -- I can simply

SQL> @getcode <name>

the script itself doesn't reference getcode.sql, it IS getcode.sql.  name it
such.

February 13, 2003 juan -- Thanks for the question regarding "Optimize datatypes", version 9,01
You Asked
Hi Tom,

Can you please giveme a hint.

Which datatypes are better to use.
In the documentation is settled clearly that pls_integer is better, but they
doesn´t settle clearly other situations.

To optimize performance
and minimize memory.

For a value (1,0) which value is better positive, number(1), number, etc.?

For a value between 1..1000 ( integer only )
For a value between 1.00 .. 1000.00 ( with two decimals )
For a value between 10000.00 .. 100000.00 ( with two decimals )

Thanks.

For
and we said...
Me, I just use number and integer types and give it no thought otherwise (memory
or performance wise).

It is only when I have a truly tight piece of code that I need to optimize that
I even goto this level of "thought"

memory wise -- they will be more or less the same.  Even with the pls_integer as
it is CONSTANTLY implicitly converted into a number for many uses.

performance wise -- it is very specific uses that will tell you which one to use
(hence why i only would dream of bothering with this level of detail in a truly
tight piece of code that must be optimized).  If you do mixed mode stuff --
you'll be doing implicit conversions all over the place (removing any percieved
benefit of the optimized type).  If you use functions -- you'll be converting.

It is just not worth it -- I'm sure there is significantly "lower hanging fruit"
out there waiting to be tuned.
Review & Followup
Rating: 5
Thanks June 13, 2002
Reviewer: A reader
Thanks

Rating: 4
OK, but what about in very big tables? June 13, 2002
Reviewer: Colin Davies from Pasadena, CA
I agree with you about datatypes in code (BTW, isn't integer converted to number
anyway?), but what if you have billions of rows in your database?  How do you
figure the storage that Oracle uses for different numeric values?  Is there any
point in constraining numbers (e.g number(10) instead of just number)?

Thanks.

Followup:
yes, integer is number under the covers.  It just removes decimals for us

In the database, now this is different.  There you use the scale and precision
as EDITS (just as you would not use varchar2(4000) for everything, you use
varchar2(30), varchar2(80) and so on -- you would use scale and precision).

You do this so the number doesn't exceed a certain scale - -it is an edit.  It
is meta data.  It imparts meaning.

A value stuffed into a number(10) will take the same storage as in a number:

ops$tkyte@ORA9I.WORLD> create table t ( x number(10), y number );

Table created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> insert into t values ( 9999999999, 9999999999 );

1 row created.

ops$tkyte@ORA9I.WORLD> select vsize(x), vsize(y)
  2  from t;

  VSIZE(X)   VSIZE(Y)
---------- ----------
         6          6

It is just that you can stick things into a number that you cannot into a
number(10).  Use them for EDITS (like a constraint)

Rating: 5
Oracle stores numbers like packed decimals June 13, 2002
Reviewer: Colin Davies from Pasadena, CA
Following Tom's example, I ran a few tests, storing numbers of different values,
including one of 38 digits (all 9's).  Oracle seems to store them just like the
EBCDIC packed decimals familiar to IBM mainframers.

Stored length = ((# of digits + 1 for the sign) / 2) rounded up.  The 38 digit
number occupied 20 bytes.

Followup:
Sorry, I could have saved you some time on this -- it is in the all important
Server Concepts Manual:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/c10datyp.htm#784
yes, they are stored in a similar fashion to a packed decimal.

Rating: 4
August 01, 2002
Reviewer: Juan Carlos from bolivia
Numeric data is variable length format
for example 345000 is stores as 345x10n
leading and trailing zeroes are not stored

 as varchar2

Followup:
In the database, on disk, this is correct.

In PLSQL (which it where this question applies) we allocate the storage for the
numbers in a fixed size (max size needed to store the number).

Rating: 5
Using precision to constraint values February 13, 2003
Reviewer: Cefers.br from Brasil
Tom,

Once I read that it's slower to assign values to constrained datatypes (like
NATURALN or POSITIVEN) than to non-constrained ones, because Oracle will first
verify the value before assign it.

By reading this thread, I understood that the precision and scale in a NUMBER
type is nothing more than a constraint (an EDIT, like you said).

So, is it slower to use NUMBER(10,2) instead NUMBER?

Followup:
is it relevant?

Here is my take on it -- if you have a business rule that says "this field must
be 10,2" you have NO CHOICE, none -- zippo, zero.  It is a rule, you must
perform that edit.

On the other hand, if you use 10,2 cause it "looks nice", stop it.  Just use a
number type.

It is faster to insert into a table without a primary key enabled.  Should we do
it?  Same thing here -- regardless of the speed -- we use number(10,2) because
our rules tell us to.

February 13, 2003 Alex -- Thanks for the question regarding "cbo parameters", version 8.1.7
You Asked
Tom,

First I would like to thank you for the great job that you are doing for the
Oracle community.
Lately I've been reading couple of articles explaining the importance of the
parameters optimizer_index_caching and optimizer_index_cost_adj. Most of them
suggesting changing those to different values than default. Looks like that most
of the suggestions are valid for pure OLTP databases . What is your take for
those parameters in databases applications (OLTP/DDS )? To me looks like that
these parameter will make the plans most likely to use indexes.I don't think
that full table scans are bad thing in the appropriate situation. I will like to
hear your take on that. Also when your next book will be available ?  I cannot
wait to get that one.

Regards,
Alex
and we said...
for most systems, they are probably set wrong.  If you find that your developers
"hint" all over the place and the hints are not of the type:

o all rows
o first rows

(eg: hints that tell the optimizer how to optimize but allow IT to optimize) but
are of the type:

o index
o index_ffs

(eg: hints that tell the optimizer what to do step by step) -- you would be a
candidate for looking at these parameters.

You may want different settings for OLTP vs DSS users -- logon triggers can help
there -- or the DSS apps can just "know to set them" (or the oltp apps).  You
want to avoid putting hints in queries.

Springtime for the book -- the second time is so much harder ;)
Review & Followup
Rating: 5
Thanks February 13, 2003
Reviewer: Alex from New York, NY USA
Tom,
Thank you very much for your response. It's as always really good one. The main
hints that I see are ordered, use_nl and index, so I guess I will give it a try
on the development machine. Besides all_rows and first_rows are there any other
hints that are no fit for those parameters ?
Springtime for the book - Man I was hopping that it is going be sooner :)

Regards,
Alex

Followup:
all_rows
first_rows
choose
optimizer_dynamic_sampling (9i)

trust me -- springtime seems too soon for me.

Rating: 5
Thanks February 13, 2003
Reviewer: Alex from New York, NY USA
Tom,

Thank you very much for your responses. The best as usual.

Regards,
Alex

Rating: 5
February 14, 2003
Reviewer: Helena Markova from Bratislava, Slovakia

Rating: 4
Another take on the parameters February 14, 2003
Reviewer: Steve from Madison, WI USA
Jonathan Lewis has said that OPTIMIZER_INDEX_COST_ADJ is
the cost of a single-block read vs. the cost of a
multi-block read - from my notes, I have "it changes the
CBO's assumption that a single block read costs the same
as a multi-block read" - so, the default of 100% is not
always accurate, because a single block read isn't as
"expensive" as a multi-block read - hence the parameter
should be adjusted down from 100%, according to your
environment.

He also stated that for OPTIMIZER_INDEX_CACHING, a setting
of 75% is not unreasonable, and even 90%+ may be accurate,
depending on your environment.

I was scribbling fast and furiously when he said it, so I
may have missed something - any errors are mine and not
Jonathan's.

February 14, 2003 ashish -- Thanks for the question regarding "select the n'th highest record", version
You Asked
write a select statment
to select the nth highest person
and we said...
The question is ambigous.  the n'th highest WHAT?  If you say
"salary", find the person making the N'th highest salary -- then
the question is a 'bad' question.  What if you asked for the
4'th highest paid person and your data was:

empno     sal
1         100
2         100
3         100
4         100
5         99
6         98
7         97
8         97

What is the right answer?  is it #4 (i could argue yes and no).
Is it #7, is it #8?  again -- i could argue either side.

What you really might want is:

"the set of people making the fourth highest salary"

There are a couple of ways to do this.  In Oracle8i, release 8.1
I might use the first query below (new feature - order by in
subquery).  In 8.0 and before the second:

ops$tkyte@8i> select empno, sal from emp;

     EMPNO        SAL
---------- ----------
      7369        800
      7499       1600
      7521       1250
      7566       2975
      7654       1250
      7698       2850
      7782       2450
      7788       3000
      7839       5000
      7844       1500
      7876       1100
      7900        950
      7902       3000
      7934       1300

14 rows selected.

ops$tkyte@8i>
ops$tkyte@8i> select empno, sal
  2    from emp
  3   where sal = ( select min(sal)
  4                   from ( select sal from emp order by sal
desc )
  5                  where rownum <= 4 )
  6  /

     EMPNO        SAL
---------- ----------
      7566       2975

ops$tkyte@8i>
ops$tkyte@8i> select empno, sal
  2    from emp a
  3   where 3 = ( select count(*)
  4                 from emp b
  5                where b.sal > a.sal )
  6  /

     EMPNO        SAL
---------- ----------
      7566       2975
Review & Followup
Rating: 5
your answer is right but... February 13, 2003
Reviewer: A reader
why are you comparing with 3 , when you want the 4th highest salary...

Tell me the fundamentals of your  query
ops$tkyte@8i> select empno, sal
  2    from emp a
  3   where 3 = ( select count(*)
  4                 from emp b

Please put in in verbage....i.e. put in in plain english...

Followup:
ops$tkyte@8i> select empno, sal
  2    from emp a
  3   where 3 = ( select count(*)
  4                 from emp b
  5                where b.sal > a.sal )
  6  /

that is something you should strive to do (put it in english).  it is what I do
when asked to tune a query -- put it in english, verify the requirements and
then code a new query based on the question.

But basically that query says:

  for every row in emp
     count the number of salaries GREATER then that row
        when you find a row with only 3 GREATER salaries, you must have the 4th
        highest right there

Rating: 3
confused ? parsing conventional v/s pl/sql. February 13, 2003
Reviewer: Ian from CT
When I am creating a trigger , is the compiled code not stored in the database ?
Then why would it only parse pl/sql and not  the conventional sql.
So that when trigger is actually fired at time of execution it gets parsed
statement and executes it.
Also then why is parse not happening in procedure body ? Is the conventional SQL
in procedure body parsed and stored.
I guess if you could clarify this in a little detail , this will be totally
clear as this applies to all sql code we write.

Followup:
The compiled code is stored.

It PARSED the sql (in order to REWRITE the sql).

I'm confused too -- what does this have to do with top-n queries?  but anyway
when you submit a trigger like:

create trigger t before insert on t for each row
declare
   n number;
   i number := 5;
begin
   select count(*) into n from another_t where x = :new.y and b = i;
end;

a) the code gets compiled into pcode and stored
b) the query gets parsed and rewritten as:

     select count(*) from another_t where x = :bv0 and b = :bv1;

(sort of looks like a query you might program in a 3gl doesn't it....)

then, at runtime -- just like YOUR 3gl code would -- the query is parsed by the
database and executed.

If we changed the trigger to a procedure -- the SAME steps take place.

You see in Oracle, there is no such thing as "true static sql", all sql is
parsed and optimized at runtime, we do not precompute plans and store them

consider what would happen if we did and...

o you added an index - we wouldn't see it, you'd have to recompile
o dropped an index -- we would fail
o analyzed a table for the first time -- we would stay with RBO
o analyzed a table after loading it -- we would still have a plan that thinks
the table is empty

and so on....

February 14, 2003 Daya -- Thanks for the question regarding "Searching CLOB Data", version 8.1.7
You Asked
Hi Tom!
  Thanks for your help always. I have a question regarding CLOB datatype. I have
inserted 2.7MB file into database having table structure as

SQL> Desc LOB_DEMO:

Column    Null?         Type
----------------------------
Id        NOT NULL      Number
theclob                 CLOB

  Here id is the primary key.  Now if i try to search a particular word, i am
able to proceed upto 4000 bytes only. I am not able search the same file after
4000 bytes.
  The procedure is as given below:
create or replace procedure srch_lob(srch_val in varchar2)
is
 declare
     lobd        CLOB;
     position    INTEGER  := 10000;
 BEGIN
 -- get the LOB locator
     SELECT theclob INTO lobd
         FROM lob_demo
         WHERE id = 1;
     position := DBMS_LOB.INSTR(lobd,
                         srch_val, 1, 1);
     IF position = 0 THEN
         dbms_output.put_line('Pattern not found');
     ELSE
         dbms_output.put_line('The pattern occurs at '
                 || position);
     END IF;
 END;

My search is done as given below:

SQL> exec srch_lob('Augmentation');
The pattern occurs at 3934

PL/SQL procedure successfully completed.

SQL> exec srch_lob('e-Development');
Pattern not found

   What is wrong here? 'e-Development' is coming after the augmentation text in
the file.
 Thanks in advance.
Daya
and we said...
read
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/inter.817/a77063/toc.htm
you need to index that text and search it using a text index.  searching a
single 2.7 meg clob -- while technically possible in plsql -- would be about as
slow as a glacier moves.  And, if you add another row -- even slower yet.

search this site for

indextype context ctxsys

for other examples using this technology.  When you do that search, you'll be
using interMedia text to do it!
Review & Followup
Rating: 3
Searching CLOB Data February 13, 2003
Reviewer: Daya from Chennai,India
Hi Tom!
  Thanks for your reply. Can we search .doc,.xls files which are stored using
CLOB with the help of CTXSYS?
  Our requirement is to store these files and do a search then. Will Oracle 9i
TEXT help in doing that?
  Thanks
Daya

Followup:
Yes, we support tons of file formats.  see
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/inter.817/a77063/afilsupt.htm#620714

February 14, 2003 Jesse -- Thanks for the question regarding "Upgrade from 8.1.6 to 8.1.7", version 8.1.7
You Asked
Q1. When I try to do a query on a view, I got this error:
    ERROR at line 33:
    ORA-03232: unable to allocate an extent of 107 blocks from tablespace 3

    Why and how should I resolve this?

Q2. To upgrade from 8.1.6(NT) to 8.1.7(NT).

Is the following steps ok:
1.  Install Oracle8.1.7
2.  Create a DB in Oracle8.1.7
3.  Export the data from Oracle8.1.6
4.  Import into Oracle8.1.7

Or is there any document on this?

Thanks.
and we said...
q1)

> oerr ora 3232
03232, 00000, "unable to allocate an extent of %s blocks from tablespace %s"
// *Cause:  An attempt was made to specify a HASH_MULTIBLOCK_IO_COUNT value
//          that is greater than the tablespace's NEXT value
// *Action: Increase the value of NEXT for the tablespace using
//          ALTER TABLESPACE DEFAULT STORAGE or decrease the value of
//          HASH_MULTIBLOCK_IO_COUNT.

q2) There are windows admin guides here:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/win.817/index.htm
and specifically the MIGRATION GUIDE which is mandatory reading before each
upgrade is here for 817:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/index.htm
the migration guide covers all of the steps (and no, your steps in my opinion
are not correct.  I would not use export import to upgrade from 816 to 817.  I
would just upgrade my database.  faster, easier, better)
Review & Followup
Rating: 3
In our case June 20, 2002
Reviewer: GU Wang from Canada
Hi Tom,
On our AIX server, it is running Oracle 816 with 3 databases. Now one of them
need to be upgraded to Oracle 817. After install Oracle 817 on a seperate home,
how to upgrade the database if I don't use Export/Import?

Followup:
read the directions in the manual I pointed you to.

You'll just upgrade the database.

Rating: 4
hash_multiblock_io_count February 14, 2003
Reviewer: Alex Daher from Brazil
Tom, quoted from Oracle Documentation -- Initialization Parameters:

"HASH_MULTIBLOCK_IO_COUNT specifies how many sequential blocks a hash join reads
and writes in one I/O. The maximum value is operating system dependent. It is
always less than the maximum I/O size of the operating system expressed as
Oracle blocks (MAX_IO_SIZE / DB_BLOCK_SIZE).

You need not set or change the value of this parameter, because Oracle computes
the value individually for every query. If you let Oracle do the automatic
computation, the value of the parameter appears as 0 in the V$PARAMETER dynamic
performance view.

Oracle Corporation does not recommend that you set or change the value of this
parameter. If you must set it to investigate its effect on performance, make
sure that the following formula remains true:

R / M <= Po2(M/C)

where:

      R = number of bytes in the smaller relation to be joined. The number of
bytes is the product of the size of each column in the smaller relation times
the number of rows in that relation.

M = HASH_AREA_SIZE * 0.9
C = HASH_MULTIBLOCK_IO_COUNT * DB_BLOCK_SIZE

      Po2(n) = a function that returns the largest power of 2 that is smaller
than its argument."

Can you please clarify? Do I leave hash_multiblock_io_count on 0, or I should
set it like the others *multiblock*?

Thank you!!!
Alex Daher

Followup:
it is saying "don't set it at all"

ignore that man behind the curtain, pretend it doesn't exist.

February 14, 2003 Jijnes -- Thanks for the question regarding "Performance of Oracle Analytic Functions ", version 8.1.6
You Asked
I have a table which represents statistics for various users of an application:

SQL> desc stat
 Name                                      Null?    Type
 ----------------------------------------- --------
----------------------------
 KEYID                                     NOT NULL NUMBER(10)
 OWNERID                                   NOT NULL NUMBER(10)
 VALUE                                     NOT NULL NUMBER(10)
 EXTRA_INFO                                         VARCHAR2(10)

I am trying to understand the performance implications of returning the TopN of
the table as the # of rows increase for this STAT table.

I have two queries (see below) for TopN; one using rownum and another using
ROW_NUMBER.  Using rownum proves to be much quicker than ROW_NUMBER.  I would
assume both of them to be the same, or close to each other.  I see sub 1s times
for rownum, and 10s for ROW_NUMBER.  I have an index on keyid, and a combination
index of value, keyid.  Statistics have been generated for the table.  I would
like to use ROW_NUMBER because I may need to get a Top 50-200, or the rank of a
statistic.  Since rownum is assigned upon fetch, I would not be able to do any
>= type comparisons.  Any ideas on how I can improve performance?  How I should
go about analyzing this further?  From a low level perspective, should
row_number just be assigning a incremented value instead of comparing the
pervious and next values?  Especially since I have an index on value, keyid, the
order by clause should be taking care of the sort before hand.  Any help would
be appreciated, thank you.

SQL> select * from (select value, rownum rn from stat where keyid = 1
order by value desc) where rn <= 5000;
5000 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=26 Card=100000 Bytes
          =2600000)

   1    0   VIEW (Cost=26 Card=100000 Bytes=2600000)
   2    1     COUNT
   3    2       INDEX (FULL SCAN DESCENDING) OF 'IDX_VAL_KEY' (NON-UNI
          QUE) (Cost=26 Card=100000 Bytes=800000)

SQL> select * from (select value, ROW_NUMBER() over (order by value
desc) rn from stat where keyid = 1 ) where rn <= 5000;

5000 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=26 Card=100000 Bytes
          =2600000)

   1    0   VIEW (Cost=26 Card=100000 Bytes=2600000)
   2    1     WINDOW (SORT)
   3    2       INDEX (FULL SCAN) OF 'IDX_VAL_KEY' (NON-UNIQUE) (Cost=
          26 Card=100000 Bytes=800000)
and we said...
Well, let me say that:

select * from (select value, rownum rn from stat where keyid = 1
order by value desc) where rn <= 5000;

does not return the top N.  It unfortunately for you -- gets all of the data -
ASSIGNS ROWNUM rn and then sorts the data.  You then just keep the first
5,000 rows that happened out.  They are NOT the top 5000 rows.  They are the
first 5000 rows, sorted ;)

consider:

ops$tkyte@ORA920> create table t ( value number );
Table created.

ops$tkyte@ORA920> insert into t values ( 1 );
ops$tkyte@ORA920> insert into t values ( 2 );
ops$tkyte@ORA920> insert into t values ( 3 );
ops$tkyte@ORA920> insert into t values ( 4 );
ops$tkyte@ORA920> insert into t values ( 500000 );

now, the answer should be 500000, 4, 3, 2 right???

ops$tkyte@ORA920> select *
  2    from ( select value, rownum rn
  3             from t
  4                    order by value desc )
  5   where rn <= 4;

     VALUE         RN
---------- ----------
         4          4
         3          3
         2          2
         1          1

whoops -- that is the first 4 rows it hit in T, sorted ;)  This is what you
meant to type in:

ops$tkyte@ORA920>
ops$tkyte@ORA920> select *
  2    from ( select value
  3             from t
  4                    order by value desc )
  5   where rownum <= 4;

     VALUE
----------
    500000
         4
         3
         2

ops$tkyte@ORA920>

and that will be faster then row_number()

So, when do you use rownum vs row_number() and why is rownum so good?

here is how you decide.

If you need the TOP-N rows out of a set (eg: show me the highest paid person in
the COMPANY), ROWNUM is the best thing to use.

If you need the TOP-N rows out of groups within a set (eg: show me the highest
paid person in each department), ROW_NUMBER() (or dense_rank whatever) is the
ONLY thing to use (rownum cannot do it)

So,
top-n from a set = rownum
top-n by group within a set = analytics
Review & Followup
Rating: 5
Rownum assignment February 14, 2003
Reviewer: Ram from India
what am i missing here ? i am not able to reproduce the same results.
ram@ORCL 8.1.7.0.0> insert into t values ( 1) ;

1 row created.
ram@ORCL 8.1.7.0.0> ed
Wrote file afiedt.buf

  1* insert into t values ( 2 )
ram@ORCL 8.1.7.0.0> /

1 row created.
ram@ORCL 8.1.7.0.0> ed
Wrote file afiedt.buf

  1* insert into t values ( 3 )
ram@ORCL 8.1.7.0.0> /

1 row created.
ram@ORCL 8.1.7.0.0> ed
Wrote file afiedt.buf

  1* insert into t values ( 4 )
ram@ORCL 8.1.7.0.0> /

1 row created.
ram@ORCL 8.1.7.0.0> ed
Wrote file afiedt.buf

  1* insert into t values ( 50000 )
ram@ORCL 8.1.7.0.0> /

1 row created.
ram@ORCL 8.1.7.0.0> select * from  (select value, rownum from t order by value
desc )
  2  where rownum <= 4;

     VALUE     ROWNUM
---------- ----------
     50000          5
         4          4
         3          3
         2          2

Elapsed: 00:00:00.10
ram@ORCL 8.1.7.0.0> select * from ( select value from t order by value desc )
  2  where rownum <= 4;

     VALUE
----------
     50000
         4
         3
         2
ram@ORCL 8.1.7.0.0>

Q1: is this because it (un)fortunately hit the rows in that order ? will this
query return diff results randomly ?
Q2: the outer where clause 'where rownum <= 4' , will it again assign rownums to
the rows returned by the inner query ? why is rownum appearing as 5,4,3,2 in the
first query above inspite of mentioning where rownum <= 4 ?

Thank you.

Followup:
q1) No, your query:

select * from  (select value, rownum from t order by value
desc ) where rownum <= 4;

is the same as

select * from  (select value from t order by value
desc )  where rownum <= 4;

which is the same as my query!!!  The inner rownum is hidden by the outer
rownum -- you aliased rownum to rn in your example -- thus "exposing" the inner
rownum to the outer query -- giving you the WRONG answer.

This:

ops$tkyte@ORA920> select *
  2    from ( select value, rownum rn
  3             from t
  4                    order by value desc )
  5   where rn <= 4;

is the logical equivalent of your original query.  that'll reproduce the issue.

q2) yes....   rownum is appearing as 5,4,3,2 becuase that is what you
selected.... your query:

select * from  (select value, rownum from t order by value
desc ) where rownum <= 4;

is also the same as:

select * from  (select value, rownum rn from t order by value
desc ) where rownum <= 4;

-- does that make more sense -- you are SEEING the inner rownum (cause that is
what you selected) but you are "whereing" on the outer rownum -- cause that is
what you coded.

Rating: 4
What about duplicates ? February 14, 2003
Reviewer: AB from UK
Tom

You say "If you need the TOP-N rows out of a set (eg: show me the highest paid
person in
the COMPANY), ROWNUM is the best thing to use.".

What if there are two or more people on the same (highest) salary? ROWNUM might
be quicker - but it won't give you a true answer, unless you ask what is the
highest salary regardless of gets paid it.

Regards

Adrian

Followup:
Yes, obviously you have to ask the right question to get the right answer.  I
like top-n queries cause most people expect N rows - but in general you don't
get N rows!! (but that is what they want)

select * from emp where sal in
( select sal from ( select distinct sal from emp order by sal desc ) where
rownum <= n ) )

would get the employees that make the TOP-N salaries

select * from ( select * from emp order by sal desc ) where rownum <= n

would get the top-n employees -- although there might be top+N+M (eg: n = 10 but
55 people make the top salary -- you get 10 of them and ignore the other 45)

you get exactly what you ask for

Rating: 5
Brilliant - as usual February 14, 2003
Reviewer: Ram from India
I wonder how you make it so simple.
It took me quite a few readings of your follow up + a couple of experiments
before i got it.
Definitely the best site for oracle faqs !
Hats off to you.

Followup:
I've see rownum abused a billion times before -- probably one of the most common
mistakes made -- besides using MM as a format for minutes that is!  Don't know
how many times i've see:

select to_char(sysdate,'hh mm ss' ) from dual

and people wondering why the clock is "stuck"

February 14, 2003 rajat -- Thanks for the question regarding "question about order by statements in select cast(multiset(", version 8.1.5
You Asked
hi tom:
to get away from ref cursors, i am trying the following and it
works great except the fact that the "order by" statement
doesn't
seem to work. the code compiles fine but at runtime it gives the
results in random order. is there a way around it / or what am i
doing wrong ?

-- beginning of code
-- creating an object type
drop type jobTableType;
drop type jobType;

create or replace type jobType as object
(name        varchar2(32),
task_order    number(2));

-- creating  a table of that object type
create or replace type jobTableType as table of jobType;

-- creating a function to return the tabletype
create or replace function proto_job(x_job_type_name in
varchar2)
return jobTableType
as
    l_job    jobTableType := jobTableType();

cursor     c1  is
SELECT     cast(multiset(
    select t.name,
    jtt.task_order
FROM     job_type jt,
        job_type_task jtt,
        task t
WHERE     jt.name = x_job_type_name AND
        jt.date_expired IS NULL AND
        jt.id = jtt.job_type_id AND
        jtt.task_id = t.id
ORDER BY -- here is where the order by doesn't seem to work
    jtt.task_order,
        t.name)
as    jobTableType)
from dual;
begin
open c1;
fetch c1 bulk collect into l_job;
close c1;
return l_job;
end;

-- end of code

thanks in advance !

rajat
and we said...
Well, I cannot understand why you would want to get away from
ref cursors as they are extremely useful but...

you've made it alot harder then it needs be.  Here is a small
example that does it (fills in the array) in a simple select
into.

ops$tkyte@DEV816> create or replace type myScalarType as object
  2  ( name varchar2(30),
  3    id   number
  4  )
  5  /

Type created.

ops$tkyte@DEV816>
ops$tkyte@DEV816> create or replace type myTableType as
  2  table of myScalarType
  3  /

Type created.

ops$tkyte@DEV816>
ops$tkyte@DEV816>
ops$tkyte@DEV816> create or replace
  2  function orderbyName return myTableType
  3  as
  4      l_data myTableType;
  5  begin
  6      select cast( multiset( select username, user_id
  7                               from all_users
  8                              order by username )
  9                   AS myTableType )
 10        into l_data
 11        from dual;
 12
 13      return l_data;
 14  end;
 15  /

Function created.

ops$tkyte@DEV816>
ops$tkyte@DEV816> create or replace
  2  function orderbyId return myTableType
  3  as
  4      l_data myTableType;
  5  begin
  6      select cast( multiset( select username, user_id
  7                               from all_users
  8                              order by user_id )
  9                   AS myTableType )
 10        into l_data
 11        from dual;
 12
 13      return l_data;
 14  end;
 15  /

Function created.

ops$tkyte@DEV816>
ops$tkyte@DEV816>
ops$tkyte@DEV816> declare
  2      data myTableType;
  3  begin
  4      data := orderByName;
  5      for i in 1 .. data.count loop
  6          dbms_output.put_line( data(i).id || ' ' ||
  7                                data(i).name );
  8      end loop;
  9      dbms_output.put_line( '-----------------' );
 10      data := orderById;
 11      for i in 1 .. data.count loop
 12          dbms_output.put_line( data(i).id || ' ' ||
 13                                data(i).name );
 14      end loop;
 15  end;
 16  /
90 A
53 ANOTHER_USER
24 AURORA$ORB$UNAUTHENTICATED
88 B
16 DBSNMP
....
54 WEBDB
-----------------
0 SYS
5 SYSTEM
...
284 OPS$TKYTE
291 DEMO_DDL
293 DEMO
296 TEST

PL/SQL procedure successfully completed.
Review & Followup
Rating: 4
July 25, 2002
Reviewer: A reader
Hi Tom

I need to create a batch process which is going to deal with huge tables (the
size of this tables goes between 1.5Gigabytes and 3 Gigabytes) and this batch
process needs to work with a collection of data from this tables.
In order to achieve a good performance, I was thinking to use ref cursors or
arrays in order to decrease
the times I need to go to the tables and look for the information.

In order to let you have an idea, the batch process needs to do something:

1-Find all the records that need to be processed, in some place I will have
something like:
   cursor reco_proc is
   select * from huge_tab_a where column1 between 1 and n;
and then I will use this records in order to use the functions and the procedure
that I detail below.

2-After I identify the records that need to be processed by the batch program,
for each of these records I need to find more information in the others huge
tables. That new information sometimes implies calculation like sum(), or joins
between two or three tables in order to find the additional info.

So my idea is to put all the information connected with these records in the
cursor reco_proc in a ref_cursor or an array in order to access those records
just one time and use this ref_cursor or arrays in order to make the
calculations and the comparisons in order to  find the data that I am going to
update in another table.

The following is a prototype procedure in order to learn how to manage this
memory structure and see if I can use the arrays in order to avoid many access
to the tables, the problem is that I wasn't able to make it run. Can you tell me
what I am doing wrong?

create or replace type myScalarType_sh as object
       (column1              NUMBER(10),
        column2              NUMBER(10),
        column3              varchar2(13),
        column4              date);

create or replace type myScalarType_oi as object
       (column_a               NUMBER(10),
        column_2               NUMBER(10),--> this column is equal to column2
        column_3               NUMBER(10),
        column_1               NUMBER(10), --> this column is equal to column1
        column_b               number (10));

create or replace type myTableType_sh as  table of myScalarType_sh
/

create or replace type myTableType_oi as  table of myScalarType_oi
/

create or replace function get_data_ship (param1 in number) return
myTableType_sh  as
 l_data myTableType_sh;
begin
   select cast( multiset( select * from table_sh where column2=param1
                         order by column1,column2,column3) AS myTableType_sh )
         into l_data  from dual;
       return l_data;
end;
/

create or replace function get_data_ordit (param1 in number) return
myTableType_oi  as
 l_data myTableType_oi;
begin
   select cast( multiset( select * from table_oi where column_2=param1 order by
column_1,column_2)
                               AS myTableType_oi)
         into l_data from dual;
       return l_data;
end;
/

create or replace  procedure show_data_ship (v_param1 in number) as
 var_count   number;
 data_sh myTableType_sh;
 data_oi myTableType_oi;
 vcolumn1 number;
 vcolumn2 number;
 vcolumn3 varchar2(20);
    begin
        data_sh  := get_data_ship(v_param1);
        data_oi  := get_data_ordit(v_param1);
        dbms_output.put_line( 'column1   column2     column3    column4
column4');
        for i in 1 .. data_sh.count loop
          dbms_output.put_line( to_char(data_sh(i).column1)||'
'||to_char(data_sh(i).column2)||'      '||data_sh(i).column3||'
'||to_char(data_sh(i).column4,'mm-dd-yyyy')||'
'||to_char(data_sh(i).column5));
          var_count:=0;
          vcolumn1:=data_sh(i).column1;
          vcolum2:=data_sh(i).column2;
          vcolumn3:= data_sh(i).column3;
          for x in 1 .. data_oi.count loop
               if (data_oi(x).column_1=vcolumn1) and
(data_oi(x).column_2=vcolumn2) then
                  var_count:=var_count+data_oi(x).column_b;
               else
                  if var_count=0 then
                   var_count:=-1;
                  end if;
               end if;
          end loop;
            dbms_output.put_line( 'column1   column2     column3    var_count');
            dbms_output.put_line( to_char(vcolumn1)||'
'||to_char(vcolumn2)||'      '||vcolumn3||'  '||to_char(var_count));

dbms_output.put_line('-----------------------------------------------------------
----------------------');
        end loop;
end;
/

The idea with the code of the loops in this procedure is avoiding to run the
following query:

SELECT a.column1,
       a.column2,
     SUM(b.column_b) var_Count
FROM table_sh a,
     table_oi b
WHERE a.column1= :x
  AND b.column_1= a.column1
GROUP BY a.column1,a.column2
/

As I have the data for :x in the memory structures (data_sh and data_oi), I want
to use them in order to avoid accessing the tables for info that I already have
in memory.

When I try to execute the procedure show_data_ship I receive the following error
SQL>exec show_data_ship (10525491)
BEGIN show_data_ship (10525491); END;

*
ERROR at line 1:
ORA-03113: end-of-file on communication channel

1- Do you know why I am not able to run this??
2- Is there any way to use the data_sh(i).column1..4 in the second loop? I am
using the variables because I
received an error for the second loop telling me that I have to declare the
data_sh(i), but I did it at the
begining of the procedure.
3- Is there any way to use bind variables in the select statment in the
functions get_data_ship and get_data_ordit.
4- I would like to use ref cursors for doing this but I don't know how taking
into account that I need to manipulate
every single record that I would have in the ref cursor. Do you think that ref
cursor will be better than the arrays?
Can you tell me how to do the same thing using ref cursors?

I am working with Oracle 8.1.7.3
I will really appreciate any help that you can give me with this.

Thanks in advance

Followup:
I have to say -- I did not read this entire thing (looks like a new question to
me, doesn't it?)

Anyway -- I rarely (if ever) see user written procedural code to do lookups be
faster then just running the SQL (the database is pretty darn good at running
sql)

My suggestion -- do it in sql, use tkprof and dbms_profiler to tune the code and
write as LITTLE code as humanly possible.

Rating: 5
July 25, 2002
Reviewer: A reader
Hi Tom,
Thanks for answering me.

So, is it not a good idea to use ref cursor or arrays in order to avoid access
the same tables and the same records to caluculate different things? (I mean
sum(), distinct, etc). Is it better to run different sql which are going to
access the tables and the same blocks three or four time just for looking
different views of the same information? Is this more efficient than using
memory structures?

The problem is I cannot do all the calculations at the same time using the same
sql because the sorts and group by operations are different or the combination
of columns are different. That's why I was thinking of using these memory
structures in order to read the blocks just one time and then use the arrays in
order to work in the different calculations.

When is it best way to use these memory structures ?

Thanks for your invaluable help

Followup:
well, ref cursors won't "help you avoid" -- they are in fact just a cursor after
all and by definition access the database.

The database was designed to access stuff very fast, that is what it does.

Let the database do the work -- you'll be surprised.  I was not suggesting that
you do it in a single statement, just that you don't try to do it yourself with
lookup tables.   I mean -- what are you going to do with these arrays?
Implement your own binary search and such?  Thats a ton of code (and the
database already has it!!!)

Rating: 5
July 26, 2002
Reviewer: A reader
Hi Tom,

I understand your point and as usuall you are right.
But can you tell me When is it best way to use these memory structures ?
Because all the examples that I have seen in your web (perhaps there are others
that I didn't see ) are connecting with reading a just one single table and
print the ref cursor on the screen. Does it mean that ref cursors and arrays are
not good for beign used in batch processes that need to work with large amount
of records and after filtering the data through differenct if/else/endif
conditions finally update the final table?

Please bear with me, I am confused when it is good to use this kind of memory
structure and when it isn't?

Thanks in advance

Followup:
arrays are good when you have to procedurally process the data you fetch from
the database, or when you have a routine that generates lots of data to be
inserted/updated.

Don't use them when some database construct (eg: a simple lookup into a table)
would do.  Perhaps -- just perhaps -- in 9iR2 this will change with associative
arrays (arrays where the subscripts can be STRINGS) but I haven't benchmarked
that yet.

Rating: 5
July 28, 2002
Reviewer: A reader
Hi Tom

Thanks for answering me!

You said:
<quote>"arrays are good when you have to procedurally process the data you fetch
from the database, or when you have a routine that generates lots of data to be
inserted/updated." <quote>

Is this applicable when you have to insert/update more than five hundred
thousand records in the table? Do the arrays perform well in this kind of case?.

Thanks

Followup:
Well, my first choice will be -- do a single update statement.  You would be
very surprise at what you can do in an update!

Are you fetching the 500,000 records out and procedurally processing them?  If
so, you might array fetch them out say 100 at a time, process 100 and array
update/insert them back.  500k  -- that would be rather large for an array.  100
at a time, thats nice.

Rating: 4
explain plan in tkprof February 14, 2003
Reviewer: Charlie
Hi Tom,

You had the following function to return a collection.

<quote>
ops$tkyte@DEV816> create or replace
  2  function orderbyName return myTableType
  3  as
  4      l_data myTableType;
  5  begin
  6      select cast( multiset( select username, user_id
  7                               from all_users
  8                              order by username )
  9                   AS myTableType )
 10        into l_data
 11        from dual;
 12
 13      return l_data;
 14  end;
 15  /
</quote>

The question is if I turn on sql_trace and run tkprof against trace file.

The explain plan for "

  6      select cast( multiset( select username, user_id
  7                               from all_users
  8                              order by username )
  9                   AS myTableType )
 10        into l_data
 11        from dual; "

in this function is
 Rows     Row Source Operation
 -------  ---------------------------------------------------
      1  TABLE ACCESS FULL DUAL

It is always "TABLE ACCESS FULL DUAL".  I'd like to see the explain plan for "
select username, user_id from all_users order by username" INSIDE of query.  Is
there any way?

thanks,

Followup:
nope, scalar subqueries are not explainable currently.

February 15, 2003 ganesh -- Thanks for the question regarding "about scalability", version 9i
You Asked
please give me information about Oracle9i Scalability(No.of
users,size,hardware,software)?
and we said...
see www.tpc.org

plenty of benchmarks there.

hundreds of thousands of users.
dozens of cpu's
terabytes of data.
February 15, 2003 -- Thanks for the question regarding "Not a real Question!. Just want to thank ones again Tom and his great asktom.com team and his great service.", version 8.1.7
You Asked
Not a real Question. Just want to thanks ones again Tom and his great asktom.com
team and his great service.

Tom ,

I am the same guy,who posted the below Question on 21st November 2002.

"How Old is "Session Wait Methodology"?Is this method Not Marketed properly:"
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6575484748500
Finally I got great job in the same month.Your feedback and great tip from "JIM"
(one of the Reviewer of my above question) helped to get pass the interview. If
possible please pass on my special thanks to "JIM".

Just want to share my interview experience,especially on performance tuning
question.

1. "How the buffers are managed when you do FTS vs Index scans."

Interviewer intention is How the Oracle database Block Buffers are managed when
you do Full table scan of a table Vs Index Scan .

I know that,the answer depends on : Oracle Versions and table properties. So
instead of saying :

======
Wells,It depends: Which Oracle version ,we are speaking and What table
properties( like CAHCE,buffer  pool) are set. Block Buffer management is changed
lot.

In Prior Oracle versions some where before 7.3.x - Oracle uses standard
LRU(least recently used) algorithm.In which FTS or Index scanned blocks are
placed in MRU(most recently used) end of LRU chain.So no differences in this
case.

But some where between 7.3.4 and 8.0.x - it uses modified LRU algorithm ,when
done FTS, the blocks are placed in LRU end of LRU chain ,instead of MRU end and
for Index scanned blocks they were placed in MRU end of LRU chain. But When
Table properties like CAHCE is set ,  FTS blocks are placed in MRU end of LRU
chain.

and in 8i onwards - there is no more standard/modified LRU algorithm for buffer
block management.Oracle uses more complex algorithm,some times called  as
"touch-count" algorithm.In this when ever a block is accessed/touched its
counter is incremented and pointer of block is moved to towards "HOT" region.
There are few hidden parameters which reveal information about hot and cold
regions.

_db_percent_hot_default
_db_aging_touch_time
_db_aging_cool_count.
_db_aging_hot_criteria

==============

I just simply answered that

" Full table scanned blocks are placed at LRU end of LRU chain and Index-scanned
blocks are placed in MRU end of LRU chain" and when CHACE is set for table."FTS
blocks are placed at MRU end of LRU end , similar of indexed scans.I avoid
mention of Oracle version/touch-count /...any other things...

Interview is happy,with my answer.Well how I know that.Its a in-person interview
and his body language shows,that he is happy. I used "JIM" tip to gauge the
knowledge of interviewer,this helped me lot to this answer.

2.Follow-up question on same topic : "If a sql query is doing lot of physical
I/O",how you reduced it.

I ones again gauge the knowledge and approach of his tuning a sql query.Instead
of saying all about LIO ( logical i/o) and importance of it .I just simply
said.There are couple of ways. Use CHACE option to cache small and frequently
used table and/or use new KEEP buffer pool,to cache the data, to avoid LRU
aging.

Well I guess,I Lied and I got the job.Its so funny ,that your tip worked.I guess
some or other reason , you are alway come correct.why ?

Thanks ones to your great service.
and we said...
vbg (very big grin).  sort of funny that the abbreviations/acronyms we all think
are so ubiquitous are not and have to be spelt out anyway....

congratulations.  Just publishing this one for the "smile" effect -- and maybe
Jim will see it too.
Review & Followup
Rating: 5
Thanks for allowing my message in your archive February 15, 2003
Reviewer: A reader

Rating: 5
I saw it... March 19, 2003
Reviewer: Jim Shang from Australia
I'm the "Jim" !

Congratulations on getting the job,
your kind words and recap of the interview
have put a very big grin on my face too.
Very Happy that my tip was helpful..

All the best with your new job

Regards
Jim Shang

February 15, 2003 Fan -- Thanks for the question regarding "For calculating data growth", version 8.X
You Asked
Hi,

I just wanted to calculate the monthly database growth. We are not using any
tools for database monitoring. Pl. let me know how can I calculate the database
growth per month, mean how much data size increased in the last month.

Thanks in advance,

Regards,

Fan kumar
and we said...
create some tables:

create table my_dba_data_files
as
select sysdate dt, dba_data_files.* from dba_data_files;

create table my_dba_segments
as
select sysdate dt, dba_segments.* from dba_segments;

and any others you are inserted in "watching".  then create a procedure:

create procedure collect_sizes
as
begin
    insert into my_dba_data_files select trunc(sysdate), dba_data_files.*
 from dba_data_files;
    insert into my ......
end;

and use dbms_job to schedule that to run on the first of each month.  You'll
have a history of exactly how large everything was for as far back as you wish
to keep.

You can use that to see where you've been and more importantly -- where you are
going.
February 15, 2003 -- Thanks for the question regarding "How to get MIN and MAX of Consecutive Numbers", version 9.2.0
You Asked
Thanks for your awesome help on "Trying to split serial number ranges" question
today.
I have another opportunity to ask a question and it's somewhat related to my
earlier question.
I hope this would be an easy one.

I have a table with the following numbers

1
2
3
4
5
6
7
10
13
14
15
16
17
18
25
26
27
28
29
30
32
33
34

I need to have an output as shown below

1-7
10-10
13-18
25-30
32-34

Please note, that 1 through 7 are consecutive and hence I need MIN and MAX for
this range of numbers. Next comes 10 and since the next number 13 is not
consecutive, my MIN and MAX should both be 10 and 10 and so on.. So basically, I
need to group these numbers
if they are consecutive and then get MIN and MAX for that set of numbers. I am
able to viusualize this but cannot put it in SQL.

Thanks
and we said...
Ahh, zen and the art of SQL -- once you get into the zone, you can see it all.

Well, this is a simple variation on that first theme (glad you asked the other
one first, made this one a 5 second answer!)
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7866543931332
was the first answer and this just builds on it - almost the same query (except
for the addition of the cartesian product with dual that is)

ops$tkyte@ORA920> select max( decode(mod(rn,2),1,x,null) ),
  2         max( decode(mod(rn,2),0,x,null) )
  3    from (
  4  select x, row_number() over ( order by x ) rn
  5    from (
  6  select *
  7    from (
  8  select x,
  9         nvl(lag(x) over (order by x)+1,-1) last_x,
 10         nvl(lead(x) over (order by x)-1,-1) next_x
 11    from t
 12         ), ( select 1 xx from dual
 13               union all
 14              select 2 xx from dual )
 15         )
 16   where (x <> last_x and xx = 1) OR (x <> next_x and xx = 2)
 17         )
 18   group by ceil(rn/2)
 19  /

MAX(DECODE(MOD(RN,2),1,X,NULL)) MAX(DECODE(MOD(RN,2),0,X,NULL))
------------------------------- -------------------------------
                              1                               7
                             10                              10
                             13                              18
                             25                              30
                             32                              34

ops$tkyte@ORA920>

I'll leave it to the jedi sql guys to explain it.  just feel the "force" ;)

analytics rock and roll don't they....

The explaination is hidden in the other answer -- the cartesian product and
modified where clause:

 16   where (x <> last_x and xx = 1) OR (x <> next_x and xx = 2)

was just to get the even number of rows I needed for the row_number pivot to
work right and get those single digit ranges working -- in fact you could add
that to the other answer to get 99-99 to pop out for you.
Review & Followup
Rating: 5
AWESOME February 15, 2003
Reviewer: A reader
Tom,

Thanks once again. Analytic functions rock as you say. Cannot wonder but feel,
that I should start looking at this chapter in your book ASAP.

Truly appreciate your help on these 2 questions :-)

Rating: 4
Additional question November 06, 2003
Reviewer: Mauro Papandrea from Italy
Analytics are really powerful, however, in the very special case asked the
following query seemingly gives the correct answer.
May i ask you when the analytic is superior ( for instance because the other one
fails ) ?

SELECT MIN (x) , MAX (x)
  FROM ( SELECT x FROM t ORDER BY x )
 GROUP BY x - ROWNUM

Thanks

Best regards

February 16, 2003 Rory -- Thanks for the question regarding "Customizing Oracle Reports", version Oracle 8i, Oracle Apps 11i, Oracle Report Developer 6i
You Asked
I have been working with the Oracle Database for several years
now, but have recently taken a position with a company
implementing Oracle Financials.  As part of the implementation,
a mandate has been made to minimize customizations (no new
forms), but report enhancement is necessary.

I took the course on Oracle Reports Developer, and now I'd like
to start working on report customization.  I've spent the last
couple weeks sifting through documentation, and to my knowledge
I have been unable to find anywhere where oracle talks about
modifying existing reports when your Apps server is on a
different platform from your desktops.

Basically, I need to take the report executable from a UNIX box,
and transfer it along with any attached libraries to my
developer environment (Windows NT 4.0).  How do I go about doing
this?  Correct me if I am wrong, but if I just use FTP, I have a
problem with differet compiled versions (Unix as opposed to NT).
 I see the same problem arising if I create a new report for use
with Oracle Apps.

Can you point me in the direction of how to proceed, or to
documentation that I may have missed that would be of use?

Thanks a million!
Rory Monteith
Programmer
and we said...
In reports, the .rdf files may be transferred between platforms.
 The compiled version of the reports are .rep files.  Reports
can run directly off of the rdf files as well as the compiled
rep  files.    For the libraries, you need the .pll files to
move across platforms.

Make sure to issue "BIN" in ftp !
Review & Followup
Rating: 3
Anyother Method to Create a RDF file programmattically August 31, 2002
Reviewer: Kamini from INDIA
Hi Tom

Is it possible to Create a RDF file programmattically (i.e. like creating a FRM
file in VB through IDE inteface) without using report builder.  Is there any API
to do this?  I saw a convert option in report builder to convert a RDF file to
REX file (ASCII Format).  But there is no documentation for the structure of REX
file.  Can u please provide the structure of REX File so that we can open the
REX file in a text editor and add/change field settings and again covert it back
to RDF File.

Thanks
Kamini

Followup:
No there is not.

Rating: 2
Bit urgent if possible February 15, 2003
Reviewer: priyaramnan from T.N. (India)
i am using Report 6i.
I have requirement in which case my paper size is 8 X 11 inch so i want the page
skip to be controlled after the 11 inch but by default it is skipping 12 inch .
I want that to be handled in report itself and also i want adjust the top and
left of the printing i.e i need continous printing with top as zero becoz i am
using it for check printing. Is there any way out.

its very urgent....

Followup:
no idea.  I

o never print anything
o don't program reports

suggest you try

otn.oracle.com -> discussion forums -> developer

someone there may be able to help you.

Rating: 3
For Mr. Priyaramnan February 15, 2003
Reviewer: Riaz Shahid from PRAL, Islamabad, Pakistan
Hello Mr. priyaramnan !

Please mail me ur problem in detail at riazjee@yahoo.com. I think i can solve ur
problem

Regards

Riaz

Followup:
why not post it here if you have an idea?  so others may see it as well?

Rating: 4
Converting .rep to .rdf February 16, 2003
Reviewer: Jamil Alshaibani from Saudi
Hi tom
  How can I convert the .rep files to .rdf files?
would you please help me
Thanks
Jamil

Followup:
trick question -- you don't.

rdf = your source code.

rep = compiled version of your source code.

rdf goes to rep

rep runs, rep cannot goto source again.

Rating: 3
Feedback February 16, 2003
Reviewer: shyampaliyath from TN(India)
greetings,

First of all , I want to thank u for ur kind commitment towards the problem
faced by me and priyaramnan.

The problem is that by default the page  skip in report 6i is after 12 inch but
for our check printing case we have 3 cheques in 11 inch paper. we have so
designed our report such that three cheque data are printed in one paper but
when it comes to the next page it is not printin properly becoz it skips after
12 inch paper but the size of the three cheques together is 11 inch so the
printing is not taking place in the designated position in the cheque. so if u
could me an idea it will be of very useful.
             Thanking u

Regards
Shyam

Rating: 3
Setting the width and higth of a report February 17, 2003
Reviewer: Abubaker Khared from Jeddah - Saudi Arabia
Hi,

To solve the problem of printing 3 cheques in one page.  You have to define the
layout page size (width/height) in three places:

Layout Model

1. Header Section    Width  11   Height ??
2. Main Section      Width  11   Height ??
3. Trailer Section   Width  11   Height ??

Then make sure to view the layout model in Edit Margin mode (the black frame).
Is it properly place, otherwise drag it from the corner to fit the required page
size.

Hopefully it will solve the problem.

Regards,
Abubaker

Rating: 4
original question February 17, 2003
Reviewer: mo
TOm:

I used to develop reports for oracle financials. My opinion is you can not nor
you want to copy dlls and libraries from unix to run on windows.  oracle
developer 6i for unix will run on unix only and oracle 6i for windows will run
on windows only.

What I used to do is:

The server was unix. Reports server was configured on unix.  I used to develop
the reports using 6i on windows because the interface is easier.

After that, I ftp the file (rdf) to the unix server and run it to see how it
looks like. You will have some font problems between the two platforms.

February 16, 2003 Chandu -- Thanks for the question regarding "Get all Partition Rowcounts for a table", version 8.1.7.4
You Asked
Tom,

1)I need the query to get all the partitions that are present in for a given
table.

Eg: I have a table called "employee" and i partitioned this table to move the
data to different years.(say i have 5 partitions).
When i pass the tablename as argument to my script it should give me the all the
rowcounts of each and individual partition.

Output should be like this..

Tablename   partition_name   rowcount
**************************************
employee    emp_part_1       2000
employee    emp_part_2       3000
employee    emp_part_3       4000
employee    emp_part_4       3000
employee    emp_part_5       3000

Can u please help me out how to achive above result set.

2) If i have several views which are invalid, is it going to do any harm to the
database or any performance degrade, please give me your opinion.

Thanks in advance
and we said...
well, if you know the partitioning clause, you would just use CASE to "emulate
it"

select part_name, count(*)
from
(
select case when date_field between A and B then 'part1'
            when date_field between C and C then 'part2'
            ...
       end part_name
  from table
)
group by part_name;

else, you can

select 'part1', count(*) from table partition(part1) union all
select 'part2', count(*) from table partition(part2) union all
....
select 'part3', count(*) from table partition(part1)

or, if your stats are realistic and up to date, there is NOTHING at all wrong
with

select partition_name, num_rows from user_tab_partitions where table_name = 'T';

that'll by far be the fastest of course.

2) nope, not a problem.
February 16, 2003 muhammad ahmad -- Thanks for the question regarding "partition by clause", version 8.1.7
You Asked
Hi Tom How it is possible to get the results from scott schema like these( I
think there should be the partition clause must be used)

I run this query

select deptno,ename,sal from emp order by deptno,sal;

but results want as

1. Deptno appears only once

    DEPTNO ENAME             SAL
---------- ---------- ----------
        10 MILLER           1300
           CLARK            2450
           KING             5000
        20 SMITH             800
           ADAMS            1100
           JONES            2975
           SCOTT            3000
           FORD             3000
        30 JAMES             950
           WARD             1250
           MARTIN           1250
           TURNER           1500
           ALLEN            1600
           BLAKE            2850

14 rows selected.

And Secondly

2. in another query deptno appears when it is started and ended

    DEPTNO ENAME             SAL
---------- ---------- ----------
        10 MILLER           1300
           CLARK            2450
        10 KING             5000
        20 SMITH             800
           ADAMS            1100
           JONES            2975
           SCOTT            3000
        20 FORD             3000
        30 JAMES             950
           WARD             1250
           MARTIN           1250
           TURNER           1500
           ALLEN            1600
        30 BLAKE            2850

14 rows selected.
and we said...
1) is easy -- just

scott@ORA920> break on deptno skip 1
scott@ORA920> select deptno, ename, sal from emp order by deptno, sal;

    DEPTNO ENAME             SAL
---------- ---------- ----------
        10 MILLER           1300
           CLARK            2450
           KING             5000

        20 SMITH             800
           ADAMS            1100
           JONES            2975
           SCOTT            3000
           FORD             3000

        30 JAMES             950
           WARD             1250
           MARTIN           1250
           TURNER           1500
           ALLEN            1600
           BLAKE            2850

14 rows selected.

BREAK in sqlplus does that.

2) is harder -- analytics can be used:

  1  select case when lag(ename) over (partition by deptno order by sal,empno)
is NULL or
  2                   lead(ename) over (partition by deptno order by sal,empno)
is NULL then deptno
  3                 else null
  4         end deptno_fake,
  5             ename, sal
  6*   from emp order by deptno, sal
scott@ORA920> /

DEPTNO_FAKE ENAME             SAL
----------- ---------- ----------
         10 MILLER           1300
            CLARK            2450
         10 KING             5000
         20 SMITH             800
            ADAMS            1100
            JONES            2975
            SCOTT            3000
         20 FORD             3000
         30 JAMES             950
            WARD             1250
            MARTIN           1250
            TURNER           1500
            ALLEN            1600
         30 BLAKE            2850

14 rows selected.
February 16, 2003 muhammad ahmad -- Thanks for the question regarding "check constraints", version 8.1.7
You Asked
How I impliment a check constraint that restrict the user to input only odd
multiples of a number column field.

If I used a trigger then guide me advice which is best, a constraint or trigger
and we said...
Assuming you want just "odd numbers in a column" then

ops$tkyte@ORA920> create table t
  2  ( x int constraint x_is_odd check (mod(x,2) = 1) )
  3  /

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( 1 );

1 row created.

ops$tkyte@ORA920> insert into t values ( 2 );
insert into t values ( 2 )
*
ERROR at line 1:
ORA-02290: check constraint (OPS$TKYTE.X_IS_ODD) violated

ops$tkyte@ORA920> insert into t values ( 3 );

1 row created.

is the best way to do that.  Never do in a trigger that which can be done
declaratively.
February 16, 2003 Cristian -- Thanks for the question regarding "Tunning heterogeneous services generic connectivity to Sql Server", version 8.1.7
You Asked
I'm loading data from a Sql Server 2000 to an Oracle 8i DB both DB are  in
diferent machines, both with windows 2000 Server. For this task I'm using
heterogeneous services generic connectivity to Sql Server, with an ODBC driver
for SQL Server. I managed to get it working, I can select the data from Sql
Server, and insert it in Oracle. I'm looking foward to improve the speed of the
data load. I have to load aprox. about 3 million rows, distributed between aprox
15 tables. So far I have:

1.- Truncate the tables prior to loading them, using the reuse storage clause.
2.- Do the inserting on rows with insert /*+ Append */ into t1 select * from
t1@sqlserver.
3.- Prior to inserting set the index as unusable and after loading  rebuild the
with nologing.
4.- Making the inserts at the same time, but no more of 4 at the same time.
Opening multiple Sql Plus windows.

What I want to know is if this could be performance  improvements:

1.- Using an Ole DB driver instead of ODBC.
2.- Configuring multiple agents (I noticed that when I open multiple sqlplus
windows and do at the same time the insert select from sql server multiple
agents are started, multiple instances of hsodbc.exe running)
3.- Changing the values HS_RPC_FETCH_SIZE, HS_RPC_FETCH_REBLOCKING to see what
happens.

Any recommendation would be greatly appreciated. Are there any benchmarks about
this ?
and we said...
I would never use odbc for 3million of anything.

You want to load fast?  bcp the data out of sqlserver into a flat file and use
sqlldr to reload.

On my laptop, I can do about 1.8 million records in about 30 seconds that way.

You want speed -- use load utilities.
You want slow -- odbc ;)

the sqlldr utility, when loading into a nologging table using the direct=y
option is blindingly fast -- no undo, no redo, just raw speed.
Review & Followup
Rating: 3
Tunning heterogeneous services generic connectivity February 16, 2003
Reviewer: Cristian Perez from Dominican Republic
Thanks for your answer. I completely agree with you, regarding the sqlldr to
reload approach. I Wanted, to know how fast could it go with the heterogeneous
services generic connectivity and if any improvements could be made to using
that approach.

Followup:
Not very fast.

the odbc layer is going to kill you here.

bcp+sqlldr would be the only method I would consider -- in the time we took to
have this back and forth -- it would have been loaded (and indexed and queried a
couple of times)

but if you go down this path:

insert /*+ append */ into a NOLOGGIGING table

set the indexes unusable

in as many sessions as you can - if you have 2 cpus, 8 sessions would not be
bad.  User a where clause that doesn't kill sqlserver to split the data up.

February 16, 2003 Jimmy -- Thanks for the question regarding "sql load", version 8.1.6
You Asked
I am converting Access database to Oracle.
I export data from access in text formet, then I use sqlldr
load data into Oracle database.

The Access data format is
     entrydate  "02/14/2003 0:00:00"
     checkamt   $8888.00

 in the control file I used
  entrydate  data "mm/dd/yyyy hh:mi:ss" nullif entrydate = blanks,
  checkamt   number,

  It does not work, for entrydate it says hh must be 1 to 12,
      checkamt must be number .

  Is there anyway I can load the data into oracle table.

 thanks in advances
and we said...
  1* select to_date('02/14/2003 0:00:00', 'mm/dd/yyyy hh24:mi:ss' ) from dual
ops$tkyte@ORA817DEV> /

TO_DATE('02
-----------
14-FEB-2003

simply use the 24 hour clock -- since that must be what access is giving you
(otherwise there is no zero hundred hours).  hh24 will solve that.

on the checkamt, add

....
checkamt    "to_number(ltrim(:checkamt,'$'))",
....

to the control file.  It'll get rid of that little symbol for you.
Review & Followup
Rating: 3
Access to Oracle February 16, 2003
Reviewer: J Maruti from VA, USA
But from Access, data can be exported to Oracle using ODBC and Import option.

Rating: 3
Try Using Oracle Migration Workbench February 17, 2003
Reviewer: Ishaq Baig from Riyadh, Saudi Arabia
Try to Use Oracle Migration Workbench it is an efficient tool for migrating from
Access Database to Oracle Database

February 17, 2003 Scott -- Thanks for the question regarding "Mod PL/SQL flush function?", version 8.1.7
You Asked
G'day Tom

Is there a way to *flush* what has currently been generated within a Mod PL/SQL
routine before the page is displayed?

BEGIN
  htp.p('before');
  IF some_condition THEN
    some_owa_flush_function;
  END IF;
  htp.p('after');
  owa_util.showpage;
END x;

SQL> /
after

And on a side note, if I connect to SQL*Plus, and paste this routine (without
the psuedocode of course) the first execution of this outputs:
  1  BEGIN
  2    htp.p('before');
  3    htp.p('after');
  4    owa_util.showpage;
  5* END x;
SCOTT@devdb817> /
BEGIN
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SYS.OWA_UTIL", line 315
ORA-06512: at "SYS.HTP", line 859
ORA-06512: at "SYS.HTP", line 974
ORA-06512: at "SYS.HTP", line 992
ORA-06512: at line 2

 real: 109
SCOTT@devdb817> /
before
after

PL/SQL procedure successfully completed.

 real: 15
SCOTT@devdb817>

Is this a known bug?
(this occurs every time I output mod pl/sql in sql*plus during first execution)

Thanks.
and we said...
No - there is no way to "flush the output" -- since the client (mod_plsql)
simply calls a stored procedure that must run from start to finish -- the client
(mod_plsql) is waiting for the stored procedure to finish completely.

As for the second issue -- that is because owa_init was never called -- htp
references OWA_UTIL, which fails the first time because it was not initialized.
It is not a bug, it is what happens when the necessary code to initialize the
toolkit is not invoked.

For this, I keep a small script:

ops$tkyte@ORA817DEV> @owainit

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> l
  1  declare
  2     nm      owa.vc_arr;
  3     vl      owa.vc_arr;
  4  begin
  5     nm(1) := 'WEB_AUTHENT_PREFIX';
  6     vl(1) := 'WEB$';
  7     owa.init_cgi_env( nm.count, nm, vl );
  8* end;
ops$tkyte@ORA817DEV>

it does just enough to init the environment and avoid this error.  You can set
whatever cgi arguments your application might be expecting here before calling
it.
Review & Followup
Rating: 5
Another finding February 16, 2003
Reviewer: Scott from Perth, Western Australia
G'day

That worked, thankyou, but in my investigation, I found a procedure in the htp
package called init.
It seems to *flush* the buffered output.

  1  DECLARE
  2    nm      owa.vc_arr;
  3    vl      owa.vc_arr;
  4  BEGIN
  5    nm(1) := 'REQUEST_PROTOCOL';
  6    vl(1) := 'HTTP';
  7    owa.init_cgi_env( nm.count, nm, vl );
  8    htp.p('before');
  9    htp.init;
 10    htp.p('after');
 11    owa_util.showpage;
 12* END x;
SCOTT@devdb817> /
Content-type: text/html
Content-length: 6
after

PL/SQL procedure successfully completed.

 real: 31
SCOTT@devdb817>

Is there a problem with using this procedure?

Followup:
init is called automagically the first time the htp package it touched -- look
at the bottom of the htp package and you'll see:

....
      end if;
      htbuf(nline) := cbuf || NL_CHAR;
   end if;
end;
/* END SPECIAL PROCEDURES */

begin
   init;    <<<======
end;
/
show errors

You should never call it yourself.

February 17, 2003 ben -- Thanks for the question regarding "Username's and Passwords", version 7.3.4.3.0
You Asked
Seems similar to a couple of other questions that you have been asked, but not
identical enough!

I have to implement "C2" security features within our application.

Unfortunately the 3rd party suppliers are not yet ready for Oracle 8 let alone
8i.

They have a Forms 4.5 Application. Which already allows the user to change thier
password, and ONLY ensures that the New password is not the same as the OLD.

(I have copied the idea from ORACLE 8 (PASSWORD_VERIFY_FUNCTION))

I am faced with having to implement (the following):

Password must be at least 6 chars long, they should contain at least one figure
(or special character) and one letter,they should comprise at least four
different characters, they may not remain valid for more than 90 days

I have persuaded the 3rd party to call a procedure (eg MINE) from their FORM,
passing a USERNAME and PASSWORD IN, and taking a success/failure status, and
erorr text (on failure) OUT.

Using this method I have coded the above restrictions without any problem.

However, the I need some help with the last restriction:
the password may not be changed to any of the last 12 passwords used

I have read your other "PASSWORD" atricle's and they come close...

I envisage a NEW table, one row per user,
TABLE (username,passuser1,passuser2,pass.....,passuser12)

Obviously the passwords needs to be non "plain text" so that they hidden from
the 3rd party and any SQL-users...

So this is where the DIGEST bit comes in...

I would code in MY_PROC
simply SELECT * where username = pin_USERNAME..
and then IF pin_NEW_PASS IN (nvl(passuser1,'X), nvl(passuser2,'Y',,....etc) THEN
'SAME AS OLD' ; return FAIL;
ELSE RETURN SUCCESS 'PASSWORD CHANGED!';

--On successful password change I would then have to move the
--ORACLE DBA_USERS.passord column into my table...

UPDATE BEN.PASSWORD_TABLE
set passuser1=NEWPASSWORD, passuser2=passuser1, etc....
WHERE username = pin_USERNAME

However, I need to guarantee that the method of digest that I use is the same as
ORACLE's.... eg What is their SALT! (as you put it!)

So after all that....

If I am given a USERNAME and PASSWORD how do I generate the ORACLE
'DBA_USERS.PASSWORD' column!

Thanks
Ben
and we said...
The only way to do that is to temporarily alter the users password to the one
you want to digest.  You can then put back the current digest.

I have a package called "check_password" that implements the mechanics you need.
 It was designed to verify a password supplied by a user is in fact their
current password (many people writing these change password screens want to get
the OLD_PASSWORD and NEW_PASSWORD, NEW_PASSWORD like Unix.  They verify the
old_password is correct and new_password = new_password and then change it.  The
function below allows this check to take place).

It works by:

1) saving the current digest from dba_users
2) alter the user identified by "password to get digest of"
3) read that digest out
4) compare the 2
   - if the same return TRUE
   - if not the same, put the digest from (1) BACK and return
     FALSE

You can use steps 1, 2, and the "failure" part of 4.  The result of 2) is what
you want.

Here is the code to "check a password".  You'll just need to modify it slightly.
 Note that if the system crashes between steps 2 and 4 -- the users password
will be changed.  There is no way to avoid this.

SQL>create or replace package check_password
  2  as
  3      function is_valid( p_username in varchar2,
  4                         p_password in varchar2 )
  5      return boolean;
  6
  7  end;
  8  /

Package created.

SQL>
SQL>create or replace package body check_password
  2  as
  3
  4  g_alter_cmd        varchar2(50)
  5  default 'alter user $U identified by $P';
  6
  7  g_reset_cmd        varchar2(50)
  8  default 'alter user $U identified by values ''$P''';
  9
 10
 11  function rep_up( p_str in varchar2,
 12                   p_u in varchar2,
 13                   p_p in varchar2 )
 14  return varchar2
 15  is
 16  begin
 17        return replace(replace(p_str,'$U',p_u),'$P',p_p);
 18  end;
 19
 20  procedure execute_immediate( stmt  in varchar2 )
 21  as
 22      exec_cursor     integer default dbms_sql.open_cursor;
 23      rows_processed  number  default 0;
 24  begin
 25      dbms_sql.parse(exec_cursor, stmt, dbms_sql.native );
 26      rows_processed := dbms_sql.execute(exec_cursor);
 27      dbms_sql.close_cursor( exec_cursor );
 28  exception
 29      when others then
 30        if dbms_sql.is_open(exec_cursor) then
 31          dbms_sql.close_cursor(exec_cursor);
 32        end if;
 33        raise;
 34  end execute_immediate;
 35
 36
 37
 38
 39  function is_valid( p_username in varchar2,
 40                     p_password in varchar2 )
 41  return boolean
 42  is
 43      l_dba_users1        dba_users%rowtype;
 44      l_dba_users2        dba_users%rowtype;
 45  begin
 46      select * into l_dba_users1
 47        from dba_users
 48       where username = upper(p_username);
 49
 50      execute_immediate(
 51          rep_up( g_alter_cmd, p_username, p_password ) );
 52
 53      select * into l_dba_users2
 54        from dba_users
 55       where username = upper(p_username);
 56
 57      if ( l_dba_users1.password <> l_dba_users2.password )
 58      then
 59          execute_immediate(
 60             rep_up( g_reset_cmd, p_username,
 61                            l_dba_users1.password));
 62      end if;
 63
 64      return l_dba_users1.password = l_dba_users2.password;
 65  end is_valid;
 66
 67  end;
 68  /

Package body created.

SQL>show errors
No errors.
SQL>
SQL>set serveroutput on
SQL>begin
  2      if ( check_password.is_valid( 'scott', 'tiger' ) ) then
  3          dbms_output.put_line( 'scott/tiger is valid' );
  4      else
  5          dbms_output.put_line( 'scott/tiger is NOT valid' );
  6      end if;
  7      if ( check_password.is_valid( 'scott', 'lion' ) ) then
  8          dbms_output.put_line( 'scott/lion is valid' );
  9      else
 10          dbms_output.put_line( 'scott/lion is NOT valid' );
 11      end if;
 12  end;
 13  /
scott/tiger is valid
scott/lion is NOT valid

PL/SQL procedure successfully completed.

SQL>connect scott/tiger
Connected.
SQL>connect /
Connected.
Review & Followup
Rating: 4
Very good routine May 30, 2002
Reviewer: Jgbb from USA

Rating: 3
Why can't Oracle make this be easier and more secure February 10, 2003
Reviewer: Michael O'Neill from Texas
When executing check_password, isn't there a potential significant security risk
during the period of time between altering to the offered (potential) password
and the switching back to the original password digest?

What if there where an instance failure in between the two alter commands?

A more sensible alternative would be for Oracle to document what function
converts/hashes plain text into the the password digest that is stored in user$.
 I'm not asking for the algorithm, just a chance to call the "blackbox" directly
without monkeying around with altering users via inefficient and security-hole
commands like alter user.

The only risk I can see is that it would be easier to build cracking tools, but
since the same algorithm has been used for many many years - I'm confident
someone has already cracked it.  The same cracking tools could be built with the
altering user mumbo jumb, with less efficiency.

Just my two cents worth.  Thanks for all the great insights.

Followup:
The real way to check a password is to LOG IN WITH IT.

This is a 99% solution -- yes, there is a chance that if you fail between the
alters -- you leave the password in a known, different state.

I've done implementations that queue a job to put it back in the event it fails
-- so before we alter it, we queue a job, check it, remove job (before it runs).
 if we fail, the job will run shortly after startup -- exposing a window of
opportunity - but it would take alot of things conspiring against you for this
to be an "attack" ...

Rating: 4
check against different user February 16, 2003
Reviewer: Scott from Perth, WA.
G'day

We attempted this algorithm, but to overcome the potential hole, we compared the
password to test by changing the password dummy account that cannot logon.
ie
SCOTT/TIGER.
Change dummy account (PWCHECK) pwd to TIGER
Compare encrypted passwords.

However these encrypted passwords differ. I presume this is because the
encryption algorithm does not allow passwords of different users to be the same
encrypted, or the encryption is unique to each user somehow...

Followup:
well, first -- passwords are not stored encrypted.  they are stored using a one
way hash (you cannot get the password back)

the password for user1 will necessarily hash differently then the password for
user2.  Now, an interesting phenomena is that we just glue the user/password
together to hash.  So we:

hash( scotttiger ) => password hash

that means a user "S" with a password cotttiger will hash to the same value.

So, if you don't mind setting up 26 dummy accounts (LOCK THEM), you can:

create or replace package check_password
as
    function is_valid( p_username in varchar2,
                       p_password in varchar2 )
    return boolean;

end;
/

create or replace package body check_password
as

function is_valid( p_username in varchar2,
                   p_password in varchar2 )
return boolean
is
    l_dba_users1        dba_users%rowtype;
    l_dba_users2        dba_users%rowtype;
begin
    select * into l_dba_users1
      from dba_users
     where username = upper(p_username);

    execute immediate
    'alter user ' || substr( p_username, 1, 1 ) || ' identified by ' ||
     substr(p_username,2) || p_password;

    select * into l_dba_users2
      from dba_users
     where username = upper(substr(p_username,1,1));

    return l_dba_users1.password = l_dba_users2.password;
end is_valid;

end;
/

No more window, no more "known password".

Only problem would be if you used really long passwords and usernames ;)

ops$tkyte@ORA920> declare
  2          l_username varchar2(30) default 'abcdefghijklmnopqrstuvwxyz1234';
  3          l_password varchar2(30) default 'abcdefghijklmnopqrstuvwxyz1234';
  4  begin
  5      if ( check_password.is_valid( l_username, l_password ) ) then
  6          dbms_output.put_line( l_username || '/' || l_password || ' is
valid' );
  7      else
  8          dbms_output.put_line( l_username || '/' || l_password || ' is NOT
valid' );
  9      end if;
 10  end;
 11  /
declare
*
ERROR at line 1:
ORA-00972: identifier is too long
ORA-06512: at "OPS$TKYTE.CHECK_PASSWORD", line 15
ORA-06512: at line 5

the username || password part would have to be 30 characters or less.

February 17, 2003 RS -- Thanks for the question regarding "Need a SQL query to pick Recent month data entered and it's previous month too.", version 8.1.6
You Asked
Hi Tom, I need little help in one SQL query.

I want to find-out SUM values for any choosen PRODUCT,for  which  2 recent
months it has got data(entered).

Consider a table, which has columns:
product-id Varchar2(10), ' MonthYear' Date, and 'ValueNbr' Number(20).

MonthYear column will have 'Full date' (dd-mon-yy hh24:mi:ss)entries.

For ex, product_cd " A " has got "value" entries till Jul2001.
When we query the table any month, ti should pick latest month it has got data
and a month prior to that also.

the query should return :

Product_cd    MonthYear    Sum(ValueNbr)
----------  ------------  ---------------

A                Jul2001            125
A                Jun2001            235

( we can mention Product_cd in Where clause, Not a problem).
Your quick help is appreciated.

Awaiting your reply,
and we said...
ops$tkyte@ORA9I.WORLD> select object_type, to_char(created,'Mon YYYY')
MonthYear, sum(object_id)
  2    from all_objects
  3   where created >= ( select trunc( add_months( max(created),-1 ), 'mm' )
  4                                            from all_objects
  5                                           where object_type = 'TABLE' )
  6     and object_type = 'TABLE'
  7   group by object_type, to_char(created,'Mon YYYY' )
  8  /

OBJECT_TYPE        MONTHYEA SUM(OBJECT_ID)
------------------ -------- --------------
TABLE              Oct 2001          19981
TABLE              Sep 2001         412913

Is one way to do it....
Review & Followup
Rating: 3
Dear Tom October 01, 2001
Reviewer: Nag
Is there anything you would say at least 'Ummmmmmm this is a difficult question
, needs some thinking'

Are you a super human being, you have solutions and answers for each and every
question.

what goes into making an expert like you , personality wise?

It is a personal question, but Iam forced to ask.

Yu are amazing.

Rating: 4
Need a SQL query to pick Recent month data entered and it's previous month October 02, 2001
Reviewer: Nathan from Singapore
Hi. Mr.Tom..
U r really Great.How can i Post my Questions to You...
pl give the details expecting your reply
Regards
Nathan

Rating: 4
SQL query to pick Recent months data entered October 03, 2001
Reviewer: Gonela from IL,Chicago
We appreicate your very quick and valuble reply , Thank you.
Though query works fine in SQL and returning exactly the same with our
expectations.But we're experiencing problems in translating the same (query) to
'Business Objects' query.
The nested query mentioned in SQL, being translated to "HAVING clause" in BO,
which is throwing out wrong result set.
Is there any possibility to acheive the same output in any other way? Response
from BO forum is not quick unfrtunately.If time permits you, please suggest
other possible ways to do. Otherwise, Thank you very much for your reply.

Followup:
Can you create a view and hide the constructs from BO all together.

for example:

create view V
as
select object_type, to_char(created,'Mon YYYY') MonthYear, sum(object_id)
sum_oid
  from all_objects
 where created >= ( select trunc( add_months( max(created),-1 ), 'mm' )
                                          from all_objects
                                    where object_type = userenv('client_info') )
   and object_type = userenv('client_info')
 group by object_type, to_char(created,'Mon YYYY' )
/

and then if BO permits the calling of PLSQL you would:

begin dbms_application_info.set_client_info( 'TABLE' ); end;

and just select * from V;

(i call this a "parameterized view")

does that get around this BO limitation?

Rating: 4
Extremely use for DSS system application October 03, 2001
Reviewer: Bhagat Singh from INDIA
Hello Tom,

Can we transpose the same result in this way

OBJECT_TYPE        OCT    SEP      Total
----------------------------------------------
TABLE              10     30       40

Please help regarding this.

Regards
Bhagat Singh

Followup:
well, we cannot do the column headings like that unless you want to do dynamic
sql (then I can write a procedure that returns this result set exactly).

In straight sql, one approach is:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select object_type,
  2         max( decode( rn, 1, sum_oid, null ) ) month1,
  3         max( decode( rn, 2, sum_oid, null ) ) month2,
  4         max( decode( rn, 1, sum_oid, null ) ) +
  5         max( decode( rn, 2, sum_oid, null ) ) summed,
  6         min( monthYear ),
  7         max( monthYear )
  8  from (
  9  select object_type,
 10         monthYear,
 11         row_number() over ( partition by object_type
 12                             order by monthYear ) rn,
 13         sum_oid
 14    from (
 15  select object_type, trunc(created,'mm') MonthYear, sum(object_id) sum_oid
 16    from all_objects
 17   where created >= ( select trunc( add_months( max(created),-1 ), 'mm' )
 18                                            from all_objects
 19                                           where object_type = 'TABLE' )
 20     and object_type = 'TABLE'
 21   group by object_type, trunc(created,'mm')
 22     )
 23  )
 24  group by object_type
 25  /

OBJECT_TYPE            MONTH1     MONTH2     SUMMED MIN(MONTH MAX(MONTH
------------------ ---------- ---------- ---------- --------- ---------
TABLE                  225648      57131     282779 01-SEP-01 01-OCT-01

Rating: 5
AVG_ROW_LEN October 04, 2001
Reviewer: Abhitha Kuj from Singapore
sQL> create table temp(tdate date);

Table created.

SQL> analyze table temp compute statistics;

Table analyzed.

SQL> select avg_row_len from all_tables where table_name='TEMP';

AVG_ROW_LEN
0

SQL> insert into temp values(sysdate);

1 row created.

SQL>  analyze table temp compute statistics;

Table analyzed.

SQL> select avg_row_len from all_tables where table_name='TEMP';

AVG_ROW_LEN
11

SQL> insert into temp values(null);

1 row created.

SQL>  analyze table temp compute statistics;

Table analyzed.

SQL> select avg_row_len from all_tables where table_name='TEMP';

AVG_ROW_LEN
7

SQL> delete temp where tdate is null;

1 row deleted.

SQL> commit;

Commit complete.

SQL>  analyze table temp compute statistics;

Table analyzed.

SQL> select avg_row_len from all_tables where table_name='TEMP';

AVG_ROW_LEN
11

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`
Our Business billing requirement (B2B) calls for data storage consumption per
month by the Clients and partners. We normally use AVG_ROW_LEN to calculate the
data storage consumption ... By this you would have understood what is our
problem by going through above sql spool file. Is AVG_ROW_LEN reliable for such
type of storage consumption calculations?? Pls Help.

Followup:
NOT that this question in the followup section here has ANYTHING WHATSOEVER to
do with the question at hand (what possible link is there between avg_row_len
and need a sql query to pick recent month data...???  I couldn't find any.)

No, I don't understand your issue.  What is it exactly?  If the issue is "why
does 1 row with a date have an avg_row_len of 11", thats called overhead.  If
the issue is why do two rows, one null and the over not null, have an
avg_row_len of 7 -- the answer is 11 bytes for one row, 3 or 4 bytes for the
other -- thats 14/15 which has an average of 7.

Looks all very normal to me.

Rating: 5
Need a SQL query to pick Recent month data entered and it's previous month too.", October 04, 2001
Reviewer: Ram from India
Hi Mr.Tom,
Is this calculation includes new table creation ?..Pl let me know

Followup:
no idea what you mean there.

Rating: 2
What if there are mutiple rows February 17, 2003
Reviewer: A reader
Hi,
What query can you use if we need to join the tables, and display data of
multiple months,
My requirement is a litle different, i need to display mutiple rows and the
previous month data as zero even if there were no records.
There may be many types i.e object types and many months of data. I cannot
restrict that.
To sum it up for a Row it should display the previous months data.if there is no
data found for previos month then display Zero,
If there is data just for the Previous month then display current months data as
zero and previous months actual data.

The result set would look something like whats there below

Object_type     Month     Amount       PrevMonthAmount
----------------------------------------------------
Table           Jan2003       1000       0
Procedure       JAN2003       2000       10000
Table           Feb2003       20020      10000
Procedure       Feb2003       0          1000

I need to have this in a single query, i can create views or MV or anything like
that, but a single query cant have a procedure is it possible

Followup:
well, your output is a little funky.

for example, why is there a procedure with amount = 0 but no table with amount =
0?

why is there a table with a prevmonthamount = 0, yet no procedure with the same?

I mean -- according to your rules, there would be both for both?

But in any case -- here is one that shows the amounts by month and if there is a
prior months amount -- that is included, else it is zero:

ops$tkyte@ORA920> select object_type, month, amount,
  2         decode( add_months(month,-1), last_month, last_amt, 0 ) last_month
  3    from (
  4  select object_type, month, amount,
  5         lag(amount) over (partition by object_type order by month )
last_amt,
  6         lag(month) over (partition by object_type order by month )
last_month
  7    from (
  8  select object_type, month, sum(amount) amount
  9    from t
 10   group by object_type, month
 11         )
 12         )
 13  /

OBJECT_TYPE        MONTH         AMOUNT LAST_MONTH
------------------ --------- ---------- ----------
PROCEDURE          01-MAY-02     505564          0
PROCEDURE          01-SEP-02      31158          0
PROCEDURE          01-OCT-02      31886      31158
PROCEDURE          01-DEC-02      34561          0
PROCEDURE          01-JAN-03     221431      34561
TABLE              01-MAY-02   11727977          0
TABLE              01-AUG-02     182975          0
TABLE              01-OCT-02      32177          0
TABLE              01-NOV-02      34031      32177
TABLE              01-DEC-02     104999      34031
TABLE              01-JAN-03    2377920     104999
TABLE              01-FEB-03   38537263    2377920

12 rows selected.

Now, if you REALLY need those extra rows (not clear) with amount=0 and
prevmonthamounts -- then we can cartesian product with a 2 row table (we need to
output that last row for each object type twice see) and do a little decodeing:

ops$tkyte@ORA920> select object_type,
  2         decode( xx, 1, month, add_months(month,1)) month,
  3             decode( xx, 1, amount, 0 ) amount,
  4             decode( xx, 1, last_month, amount) last_month,
  5             xx
  6    from (
  7  select object_type, month, amount,
  8         decode( add_months(month,-1), last_month, last_amt, 0 ) last_month,
  9             max(month) over ( partition by object_type ) max_month,
 10             xx
 11    from (
 12  select object_type, month, amount,
 13         lag(amount) over (partition by object_type order by month )
last_amt,
 14         lag(month) over (partition by object_type order by month )
last_month
 15    from (
 16  select object_type, month, sum(amount) amount
 17    from t
 18   group by object_type, month
 19         )
 20         ), (select 1 xx from dual union all select 2 xx from dual )
 21         )
 22   where xx = 1 OR ( xx = 2 and month = max_month )
 23   order by 1,2
 24  /

OBJECT_TYPE        MONTH         AMOUNT LAST_MONTH         XX
------------------ --------- ---------- ---------- ----------
PROCEDURE          01-MAY-02     505564          0          1
PROCEDURE          01-SEP-02      31158          0          1
PROCEDURE          01-OCT-02      31886      31158          1
PROCEDURE          01-DEC-02      34561          0          1
PROCEDURE          01-JAN-03     221431      34561          1
PROCEDURE          01-FEB-03          0     221431          2
TABLE              01-MAY-02   11727977          0          1
TABLE              01-AUG-02     182975          0          1
TABLE              01-OCT-02      32177          0          1
TABLE              01-NOV-02      34031      32177          1
TABLE              01-DEC-02     104999      34031          1
TABLE              01-JAN-03    2377920     104999          1
TABLE              01-FEB-03   38537263    2377920          1
TABLE              01-MAR-03          0   38537263          2

14 rows selected.

there you are.

February 17, 2003 Rich -- Thanks for the question regarding "External C call vs Java", version 8.1.7.4
You Asked
Hi Tom,

I have an application which I need to make a DNS query from the
database.  I can do it with Java from within the database or
using an external C call.  Do you think external C call will
offer a much better performance than using Java?  I am leaning
towards Java, but performance is important, so if external C call
far exceeds Java then I might do it in C.

Your opnion is greatly appreciated.

Thanks

Rich
and we said...
depends....

there are startup times associated with both (the first call takes the longest).
 Java has a much higher startup overhead.

Java will be easier to code, port and maintain...

So, if you have "long sessions" -- java might be better for ease of use reasons.

If you have "short sessions" -- C will be better for performance.
Review & Followup
Rating: 4
Comment on Rich's problem February 16, 2003
Reviewer: Dan from Whitehorse, Yukon Territory, Canada
Rich - I'm not sure what you're up to so this is kind of a  wild guess - strikes
me that your bottleneck here is likely to be in talking to the DNS server -
network overhead etc.  Actual code execution speed is likely to be down the list
a ways.

Unless I had the C code pretty much in hand I'd lean towards java.  Of course
depends on your environment too.  If you have lots of C to support and no java,
don't introduce a new language.

Rating: 3
Why not use UTL_INADDR? February 17, 2003
Reviewer: A reader
There is already PL/SQL + Java implementation for DNS client.

<quote>
Oracle8i Supplied PL/SQL Packages Reference
Release 2 (8.1.6)
...
The UTL_INADDR package provides a PL/SQL procedure to support internet
addressing. Provides an API to retrieve host names and IP addresses of local and
remote hosts.
...
This package requires that you install the JServer option.
</quote>

Followup:
good point ;)

February 17, 2003 Surya -- Thanks for the question regarding "replication of 24/7 database.", version 8.1.7
You Asked
We would like to replicate (Multi Master replication) a 24/7 database (about 60G
in size) to a remote site to act as a failover database. This database is being
updated constantly. It takes at least a day to transfer export file to remote
site. How do I go about doing this with very minimal downtime?.
and we said...
You do not want to implement multi-master replication for failover.

Replication is the wrongest solution for failover.

What you want is a standby database:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76995/toc.htm
In Oracle9i this is known as Data Guard and has a ton more capabilities.

You will have zero downtime on the production box to set up a standby -- it
starts by taking your HOT backup to the standby site and plopping it down --
then logs are transferred to the standby.

Little to no impact on production, it runs as it always has run.
February 18, 2003 Siobhan -- Thanks for the question regarding "Parse to execute ratio greater than 100%", version 8.1.x
You Asked
This figure is being shown through TOAD - however a quick study of other
performance stats indicate 99 - 100% hit ratio on the sql cache etc.  How can
this hit ratio be so good when the parse to execute ratio is so bad?  The
application uses many views instead of base tables - would this contribute to
these figures?
and we said...
Ask the makers of toad why their product is broken.  The ONLY way to compute a
execute to parse ratio is:

round(100*(1-:prse/:exe),2)

so either parse or execute would have to be negative -- they are botching
something up.  Not knowing a THING about toad (not a windoze user), I cannot
comment on their products output.

I can say -- statspack will never lie and its free and it contains a wealth of
information.

a hit of 99-100% on the sql cache has nothing to do with the parse to execute
ratio.  the execute to parse ratio can be ZERO and you still might have 100% hit
on the shared pool, consider:

   loop
        parse 'select * from dual'
        execute it
        close it
   end loop

parses = execute = parse to execute ratio = 0% but the shared pool hit would be
100%

I do not think your parse to execute ratio is bad -- I think toad has a problem.

I'll publish this in case any toad users out there that have seen this and
gotten an explanation can share it with us if they like...
Review & Followup
Rating: 4
February 17, 2003
Reviewer: Mike from TX
"(not a windoze user)"

Do you use SQLPLUS in the client of the windoze? You might just use the sqlplus
on the unix servers?

Followup:
when I used windoze, I used character mode SQLPlus yes.

In unix land, I use sqlplus.

Rating: 3
February 17, 2003
Reviewer: Siobhan from UK
Thanks, I will check out the statspack and see what figures it gives

Rating: 5
Could it be an Oracle bug? February 17, 2003
Reviewer: Mark J. Bobak from Ann Arbor, MI
Just a thought: Occasionally, we see Oracle bugs where
the V$ views from which these types of statistics are derived
have bugs that cause invalid values to be displayed.  Not sure
if there are bugs specific to the number of parses and/or
executions, but it's a possibility.

-Mark

Followup:
that is why I would want to see statspack saying it.  I've never seen it
personally.

February 18, 2003 elias -- Thanks for the question regarding "Database reorg", version 8i
You Asked
I want to reorg a Datawarehouse database.I want to change the storage clauses of
all the objects.
The database is very fragmented all the extents have 1MB size.
What would be a good recommendation for the storage clause(initial,next,PCTused
,etc...) for both Tables and indexes.
Thanks
Elias
and we said...
Use locally managed tablespaces and do not use initial, next, pctincrease at
all.

Have "small" tablespaces, "medium" and "large".

The extents for "small" could be 64k.  You would put any small lookups in there.
 Anything less then 50meg in size would be ok here.

The extents for "medium" could be 512k.  Anything less then 300-400 meg would
fit nicely in there.

the extents for "large" could be Xmeg where X is reasonable for you.  Say X was
10meg.. then anything upto about 10gig would do fine in there.

Your pctused -- if you are a datawarehouse -- it not relevant.  set pctfree=0
and pack the data in there. pctused it only relevent for data that is
updated/deleted.

Use ALTER TABLE MOVE and ALTER INDEX REBUILD to move the objects to the new
tablespaces.
Review & Followup
Rating: 5
Fragmented December 28, 2001
Reviewer: Mark A. Williams from Indianapolis, IN USA
> all the extents have 1MB size...

If ALL the extents are 1MB in size, how could there be fragmentation?  Maybe my
definition of fragmentation is not the same as what is being referred to here?

- Mark

Followup:
I ignored that in the question (i agree with you) -- it is 100% true that if you
use equi-sized extents fragmentation is pretty much impossible.  People
sometimes equate "oh my gosh, lots of extents" with "fragementation".
Fragmentation is when you have 500meg of free space in a tablespace for example
but cannot allocate a new 1 meg extents (cause the 500 meg of free space is in
tiny bits and chunks, fragmented all over the place).  If you used equi-sized
extents, and you had 500meg of free space -- you would have 500 1-meg extents
out there ready to be used (no fragmentation).

Perhaps I should have cautioned that questioner, anything less then 1,000
extents/segment is OK, not to be worried about.  If you are going to reorg just
to get rid of tables with many extents -- stop, don't do that.

Rating: 5
Database reorg January 14, 2002
Reviewer: Elias Akouri from cleveland ,oh
Thanks for the answer. I guess I should not say fragmented database , I should
have said that I want to reduce the number of extents in the tables. (because I
think less extents means better performance).
Let me know if I still can do that with your recommendations.
Thanks
Elias

Followup:
You think wrong.  Less extents does not mean better performance.  It is a MYTH,
it is fantasy, it is wrong.

Unless you have well over 1,000 extents -- don't waste your time, there are
better things you could be doing with it.

If you look at my examples above, I for example suggest:

...
The extents for "medium" could be 512k.  Anything less then 300-400 meg would
fit nicely in there.
...

that means I have no problems with 800 extents in that tablespace (400meg would
need 800 extents)...

read
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1667293495336
..

Rating: 2
Reader January 15, 2002
Reviewer: Reader
The original question was about the storage clause for tables and indexes, no
mention of the type of tablespace.
IF the tablespace is dictionary managed, large amount extents in the order of
800 extents will definitely imapact the dictionary tables fet$ and uet$.
These tables are clustered with c_file#_block# size of which is fixed during
creation. Large number of extents will cause cluster chaining and cause
performance problems. Also, there is a possiblity that
database objects (tables,indexes) try to create extents at the same
time, leading to contention of the only one ST (space transaction latch) latch.

Followup:
dude, test it out -- prove it -- post it and then we'll talk.  Show us the
measurable, scientific proof.  Lets see it.  No theories here -- practical
proof.

1 extent, 10 extents, 100 extents, 1000 extents -- I don't really care. I have
tables in the hundreds/thousands of extents in DMT's (my system tablespace for
example).....  I don't lose sleep.

If it is a very measurable thing, it should be trivial to show.

Rating: 2
Reader January 15, 2002
Reviewer: Reader
I have created a test case to show that the
data dictionary and dictionary acahe work much harder to
truncate a table with large number of extents ( 326)
than the same table with 1 extent

SQL> create table t14 (col1 varchar2(2000))
  2  storage (initial 8k next 8k pctincrease 0)
  3  tablespace tools;

Table created.

SQL> begin
  2  for i in 1..1000
  3  loop
  4  insert into t14 values (rpad('*',2000,'*'));
  5  commit;
  6  end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

SQL> select FILE_ID,EXTENT_ID,BLOCK_ID,BLOCKS,BYTES from dba_extents where
SEGMENT_NAME = 'T14';

   FILE_ID  EXTENT_ID   BLOCK_ID     BLOCKS      BYTES
---------- ---------- ---------- ---------- ----------
         7        205      19310          1       8192
         7        203      19309          1       8192
         7        201      19308          1       8192
         7        199      19307          1       8192
         7        197      19306          1       8192
        13        220      10410          1       8192
        13        218      10409          1       8192
        13        216      10408          1       8192
        13        214      10407          1       8192
        13        212      10406          1       8192
        13        210      10405          1       8192
        13        208      10404          1       8192
        13        206      10403          1       8192

326 rows selected.

SQL> set timing on
SQL> set time on
13:44:41 SQL> truncate table t14;

Table truncated.

Elapsed: 00:00:02.05
=====================

create table t14 (col1 varchar2(2000))
storage (initial 10m next 10m) tablespace tools;

Table created.

SQL> begin
  for i in 1..1000
  loop
  insert into t14 values (rpad('*',2000,'*'));
  commit;
  end loop;
  end;
  /

PL/SQL procedure successfully completed.

13:47:21 SQL> truncate table t14;

Table truncated.

Elapsed: 00:00:00.41
====================

The difference is 2.05 seconds to 0.41 seconds
This is a UNIX server , idle no TX going on except my test.

This is just to *show the impact on data dictionay* due to
proliferation of extents ( this case ony 326, you can
extrapolate to 1000s).

I have seen it took days to *drop tables* taling days
because of proliferation of extents in 1000s.

Followup:
fine -- I'll give you this - DOING DDL (truncate, drop) on the table will take
longer -- however -- having a table in 1,000 extents is not any problem, its not
a reason to REORG.  In fact, you give the great argument against reorging!!! It
takes a long time to give back all of those dictionary managed extents!!! that
is yet another reason NOT to reorg a table with hundreds of extents.

Show me the queries that are affected...

If you constantly truncate this table, right size it.
If you constantly drop this table, right size it.

If it currently is in hundreds/thousands of extents -- leave it be.  As you just
demonstrated, "fixing" the problem incurrs way lots of overhead (so don't do
it)...

(the reason you probably were dropping the table was to fix the extents -- but
the extents were not a problem, hence you didn't need to wait days)...

So, if you have a table with lots of extents -- leave it.

Rating: 5
Truncate example.... January 15, 2002
Reviewer: Connor from Battersea
Yeah, but how often do you truncate an entire table in a production environment?
 If its for temporary data - use a temporary table.

Rating: 4
Please more example January 16, 2002
Reviewer: A reader
Hi,

Would you mind give more examples others then "truncate" so that we can fully
understand the # of extents inpacts on table manipulations and database, such as
some query or insert/update/delete.

Followup:
truncate and drop thats pretty much it.  I don't even count them as they are
excessively rare (and if you truncate/drop tons of times, use a temporary table
or right size it).

Rating: 4
Reader January 16, 2002
Reviewer: Reader from USA
Tom has demonstrated with excellent examples
in this site using DMLs with large number of extents and
proved that having lot of extents does not
affect performance. The REVIEWER can search
for the topics in this site.

My only concern was the DDLs on objects with
large number of extents taking long time. We do
sometimes have an option to truncate/drop the
table for maintenace purpose.
Searh in this site for
" Deleting many rows from a big table"

Other than that I could not think of any
adverse effect of having lots of extents as
long as the extent size is a multiple
of multiblock read size.

Rating: 4
Excellent on table level, what about tablespace September 10, 2002
Reviewer: Chris Sirois from Edmonton, Alberta, Canada
I understand your extent and performance theory, but what about in extreme
cases.  I have a tablespace with 1.8 million extents associated with it in the
sys.fet$ table.  I cannot drop the table.  It is a production instance and there
is not enough down time to drop or even coalesce.  Will performance be hurt or
can I expect problems in the future if I let the tablespace be?  The sys.fet$
already has more than 2 million reocrds in it.

Followup:
Well, you are in the proverbial rock and really hard place.

I suppose, you'll have to wait till you get 9i so you can do some online reorgs.
 You might be able to fix some of it online (index related issues) by alter
rebuilding the indexes online into a new tablespace with LOCALLY managed extents
and then dropping the tablespaces associated with the old indexes. (that drop
will take a while depending on how much cleanup has to happen)

Rating: 4
Is anything large for Oracle? January 26, 2003
Reviewer: 'Abdur-Razzaaq from Singapore
There is a table (OLTP) with 600m rows which keeps growing.  It is an LMT with
250 extents of 1G size.

My questions are,
(a) Is there any limitation in oracle as for as the number of rows are
concerned?
(b) What would be the appropriate and maximum extent size for such tables?
(c) What would be the best practice considering such tables other than
partitioning?

Thanks.

Followup:
I would suggest that objects larger then say 10gig apiece are prime candidates
to consider partitioning.

a) The reason is not hard limits like number of rows (limited by the number of
files and blocks only -- you are billions and billions of rows away from that).

b) The reason is not hard limits on extent sizes (which is 2gig by the way)

c) tie my hands behind my back why don't you?  I guess the best practice would
be to question the practice of not considering partitioning....

The reason is one of managebility and administration.

Rating: 5
What happens if storage constraint specified with LMT January 27, 2003
Reviewer: A Reader from UK
Tom,

You mention not to use the storage constraints with LMT - what is the impact if
you do this - does it harm anything ?  We have scripts that will be run in a LMT
and non-LMT environment, so they have storage constraints (pctfree, initial,
next etc.) spec'd against them - what is the impact of this if any ?

Regards,

Paul

Followup:
pctfree isn't a storage parameter so its ok.

when you create a table using an LMT it'll take a storage parameter like:

ops$tkyte@ORA920> create table t
  2  ( x int )
  3  storage ( initial 512k next 1m pctincrease 100 minextents 5 );

Table created.

and compute out what WOULD have been allocated:

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          l_init number := 512;
  3          l_next number := 1024;
  4          l_tot  number := l_init;
  5  begin
  6          for i in 2 .. 5
  7          loop
  8                  l_tot := l_tot + l_next;
  9                  l_next := l_next * 2;
 10          end loop;
 11          dbms_output.put_line( 'we are asking for ' || l_tot || 'kbytes' );
 12  end;
 13  /
we are asking for 15872kbytes

PL/SQL procedure successfully completed.

just shy of 16meg -- and then allocate

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select extent_id, sum(bytes) over ()/1024 tot_kbytes,
bytes/1024  kbytes
  2    from user_extents
  3   where segment_name = 'T'
  4   order by extent_id
  5  /

 EXTENT_ID TOT_KBYTES     KBYTES
---------- ---------- ----------
         0      16384       1024
         1      16384       1024
         2      16384       1024
         3      16384       1024
         4      16384       1024
         5      16384       1024
         6      16384       1024
         7      16384       1024
         8      16384       1024
         9      16384       1024
        10      16384       1024
        11      16384       1024
        12      16384       1024
        13      16384       1024
        14      16384       1024
        15      16384       1024

16 rows selected.

at LEAST that much -- if I had used a uniform sized extent management policy
of 10m extents -- it would have done 20m, that being the smallest segment size
possible that is greater then the requested size

So, in general, it'll take your storage parameters and say "if i had created
this in a DMT, about how much space would I have allocated" and then will
allocate AT LEAST that much space initially in the LMT.

So, you end up with about the same sized allocations -- although, you could end
up with a much larger allocation in an LMT depending on the extent sizes.

Rating: 4
Biggest extent size January 30, 2003
Reviewer: 'Abdur-Razzaaq from Singapore
You quoted, "The reason is not hard limits on extent sizes (which is 2gig by the
way)".

Our OS currently supports 5GB file.  Would it be alright to have 5G extents?
Any implications?

Followup:
If you can get Oracle to give you a 5gig extent, more power to you.

The fact is, an extent is limited to 2gig.  It is a hard limit, they go no
larger -- nor do they ever need to.

Rating: 4
What would happen January 30, 2003
Reviewer: Craig
If I had a table with initial of 1m ext of 1m 5 extents, if i created a LMT with
uniform size of 64k.  I I done a alter table move to the new tablespace would
the extent size be 64k because of the new tablespace

Followup:
if you have an LMT with uniform sizes of 64k -- it matters not HOW the segment
gets in there, it will in fact have 64k extents.

No extent OTHER then 64k can be created in that LMT by its very definition.

Rating: 3
Volume or Size February 17, 2003
Reviewer: Abu Ahmadh
You said, "I would suggest that objects larger then say 10gig apiece are prime
candidates to consider partitioning."

Consider a table with 50 million rows and <5G in size.  Is it a good candidate
for partitioning?  Should we always go by size?

Followup:
Size, much like cache hit ratios, can be used in one direction.

What I mean by that is

o a cache hit ratio of 10% might indicate a problem
o a cache hit ratio of 99% might mean
  - well tuned
  - poorly tuned
  - medicore tuned

It can only be an indicator that something *might* be wrong when it is small.
When it is high, cache hit ratios don't mean anything by themselves.

The same with partitioning.  At about 10gig, I believe a segment starts becoming
un-manageable.  Takes too long to "alter table move", my exposure to downtime
gets larger for this segment (the larger the segment, the more likely the disk
that fails belongs to it), it takes longer to recover, etc.

So, I start considering partitioning for ease of administration at that point.

Now, in the other direction -- towards small -- there are no size
considerations.  1gig -- might partition it.  100meg -- might partition it.
5gig might partition that as well (number of rows really doesn't ever come into
play).  IF my application would benefit from it.

Why might I partition a 100meg table?  Well, I might be using partitioning to
enable a fast "hot swap".  Every night at 2am I reload this table from another
system.  I could:

a) truncate + load --> most downtime
b) delete + load --> no downtime but I would find my indexes at 2x constantly
c) load another table + drop + rename -> chance I lose my table if we fail after
drop before rename
d) load another table + alter table exchange partition <<=== tiny downtime, no
chance of loss....

Would I consider partitioning a 5gig table?  Sure, consider I am a data
warehouse, I partition this 5gig table into 10 500meg partitions.  All queries
are of the format:

   select <bunch of aggregates> from table where partition_key = value;

Now, instead of full scanning 5gig, I full scan 500meg...

Understand the physics and you'll "just know" when to apply.

February 18, 2003 John -- Thanks for the question regarding "Loading CSV files into Oracle", version 8.1.5.0.0
You Asked
I have a control file which is written as follows:

load data
infile 'c:\test\test.fil'
replace
into table test_tab
fields terminated by "," optionally enclosed by '"'
trailing nullcols
(field1,
 field2,
 field3)

The data is as follows:

001,"This is a test","Test1"
002,"Length 2'3" of hose","Test2"
003,"Pipe 23'4"","Test3"

The problems are with records 2 and 3 as they have embeded
quotes within the records.  I cannot control the delimiter or
the terminator that dumps the data and I really do not want to
go through the data to correct the quotes.
and we said...
Well, we won't be able to use sqlldr's enclosed by then -- it
cannot tell the difference between a " in the middle as opposed
to the end of the string with your data.  We can use SQL to fix
this on input.  If we use a decode that does:

  if ( the first character is " ) then
     if ( the LAST character is " ) then
          return the string minus the FIRST and LAST character
     else
          return the string
     end if;
  else
     return the string
  end if

we can load this data.  Nicely formatted, this decode looks like
this:

    "decode( substr(:field2,1,1),
             chr(34),
                 decode(substr(:field2,length(:field2)),
                        chr(34),
                          substr(:field2,2,length(:field2)-2),
                          :field2 ),
             :field2 )",

In a ctl file (due to limits on the length of sql functions by
sqlldr) we would code it like this:

load data
infile *
replace
into table test_tab
fields terminated by ","
trailing nullcols
(field1,
 field2
"decode(substr(:field2,1,1),chr(34),decode(substr(:field2,
 length(:field2)),chr(34),substr(:field2,2,length(:field2)-2),
 :field2 ),:field2 )",
 field3
"decode(substr(:field3,1,1),chr(34),decode(substr(:field3,
 length(:field3)),chr(34),substr(:field3,2,length(:field3)-2),
 :field3 ),:field3 )"
)
BEGINDATA
001,"This is a test","Test1"
002,"Length 2'3" of hose","Test2"
003,"Pipe 23'4"","Test3"
Review & Followup
Rating: 5
CSV with SQL Loader August 30, 2002
Reviewer: Balaji.M from Singapore
Hi Tom,
  Excellent piece of Code.
Thanks and Regards
M.balaji

Rating: 5
how to load the following data file (8.1.7) October 23, 2002
Reviewer: A reader
"Nixon Technology, inc",CO,US,4177,05-06-2002 16:25:29,BDX US,'Y',,,Credit
Editor,F12B,A2,A+

1.) it is comma delimetted
2.) but it  has  optional delimeter "double quote"
3.) and also has 'Y' in it  ( I just want Y from it)

can you please tell ?

TIA

Followup:
use a ctl file like this:

load data
infile 'c:\test\test.fil'
replace
into table test_tab
fields terminated by "," optionally enclosed by '"'
trailing nullcols
(field1,
 field2 "trim( both '''' from :field2 )",
 field3)

Rating: 5
Thanks !! October 23, 2002
Reviewer: A reader

Rating: 5
thanks !! October 23, 2002
Reviewer: A reader
Has oracle reserved a word both for this !! ??

Rating: 3
Single quote versus double quote February 17, 2003
Reviewer: Andy from Loughborough, England
002,"Length 2'3" of hose","Test2"

The error records have single quotes in them, the 'enclosed by' statement refers
to double quotes. Why is sqlldr getting confused?

Followup:
because there is a double quote in the middle of the string there.

2'3"

2 feet, 3 inches -- double quote. End of string, but it isn't the end of string
is it -- it is the middle.  Hence -  confusion.

Rating: 5
Alternative February 18, 2003
Reviewer: Johan Snyman from JHB South Africa
An alternative could be:

"decode( length(trim(both '"' from :field2)),
             length(:field2)-2, trim(both '"' from :field2),
                                :field2 )"

If trim(both... reduces the length by 2, there were matching quotes, so apply
trim(both..., else there are unbalanced quotes, so return original field.

Followup:
different but probably OK

ops$tkyte@ORA920> variable field2 varchar2(25)
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec :field2 := '""hello'

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select decode( length(trim(both '"' from :field2)),
  2               length(:field2)-2, trim(both '"' from :field2),
  3                                                           :field2 )
  4    from dual;

DECODE(LENGTH(TRIM(BOTH'"'FROM:F
--------------------------------
hello

ops$tkyte@ORA920>
ops$tkyte@ORA920> select decode( substr(:field2,1,1),
  2               chr(34),
  3                   decode(substr(:field2,length(:field2)),
  4                            chr(34),
  5                               substr(:field2,2,length(:field2)-2),
  6                                      :field2 ), :field2)
  7    from dual;

DECODE(SUBSTR(:FIELD2,1,1),CHR(3
--------------------------------
""hello

February 18, 2003 Winston -- Thanks for the question regarding "PARTITION RANGE SINGLE PARTITION", version 8.1.7
You Asked
Hi Tom,

I am trying out the partition on Oracle8i8.1.7.3 as below.

create table range_part (col1 number(9))
partition by range(col1)
(partition p1 values less than (10) tablespace swspda_01,
partition p2 values less than(20) tablespace swspda_diary_ent,
partition p3 values less than (30) tablespace users,
partition p4 values less than(maxvalue) tablespace swspix_ord_itm
)
/

 insert into range_part values (1);

1 row created.

> insert into range_part values (11);

1 row created.

> insert into range_part values (21);

1 row created.

> insert into range_part values (31);

1 row created.

> insert into range_part values (41);

1 row created.

iomadmin@9iR2> COMMIT;

Commit complete.

then tried select * from range_part=15 for both cursor_sharing=force and exact.
and I got the following trace result.

********************************************************************************

select *
from
 range_part where col1=:"SYS_B_0"

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        4      0.01       0.00          0          2          8           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        8      0.01       0.00          0          2          8           2

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 103

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  PARTITION RANGE SINGLE PARTITION: KEY KEY
      1   TABLE ACCESS FULL RANGE_PART PARTITION: KEY KEY

********************************************************************************

********************************************************************************

select *
from
 range_part where col1=15

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          1          4           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.01          0          1          4           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 103

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS FULL RANGE_PART PARTITION: START=2 STOP=2

Three questions:
1. What does 'partition range single partition: key key' mean?
2. If we set cursor_sharing=force, will "Eliminate Partition" be taking effect
for 'select * from range_part where col1=15'?
3. If I use bind variable plus cursor_sharing=exact, will the "Eliminate
Partition" be working well?

By the way I tried also on Oracle9iR2, the execution plan is the same and I got
'partition range single partition' too.

Many thanks!
and we said...
1) it means that Oracle knows only ONE partition will be accessed however when
it developed the query plan -- it did not know WHICH partition.  Hence it uses
the variables "key" "key" to say it will be partition "key" where "key" is
specified at query execution time.

2) yes, the plan shows that.

3) yes, same as #1 and #2
Review & Followup
Rating: 4
Is there any reason why oracle doesn't know which partition it is at this point? April 16, 2003
Reviewer: A reader
And obviously in the 2 tkprof results presented, one time Oracle doesn't know,
and the next time it knows: "TABLE ACCESS FULL RANGE_PART PARTITION: START=2
STOP=2". Why the difference here?

Sorry I used this page but it answered exactly my question.

Thanks.

February 18, 2003 sudhakar -- Thanks for the question regarding "shared_pool_size", version 8.1.6
You Asked
how to determine the shared_pool_size at the time of database
creation?
  some say it should be 55% of the  RAM.
 could u pl.suggest

thanking u
sudhakar
and we said...
It is *never* a function of the amount of ram you have.  the
amount of ram you have might constrain it smaller then you want
however -- you would not look at the amount of ram and say "i
want a 100meg shared pool".

the shared pool needs to be sized according to how you are going
to use it.  It is typically not something you can accurately
estimate at database creation time unless you are creating a new
database instance that will do exactly what some existing
instance is already doing. (and hence you can get the values
from it).

Your goal is to have a shared pool such that after running for a
couple of days - 10% or so of it is free.  The shared pool will
have all of the cached objects (sql plans, plsql, dictionary
data and so on).  It should stop growing (if it does not that
indicates you are not using bind variables and you need to
correct that).

So, sizing the shared pool is somewhat empirical.  50meg is
probably a good place to start.  Then monitor its usage via the
v$SGASTAT view over time to ensure you have right sized it.
Review & Followup
Rating: 5
shared pool monitoring August 14, 2002
Reviewer: Faithful Reader from NY
So, are you saying that we need only to look at free memory in v$sgastat after a
period of time, not monitor it constantly? i.e. free memory will only go down?

Many thanks.

Followup:
free memory will go up and down over time.  You want it to be nearly all used
and not have lots of reloads into the library cache if you can (meaning, your
working set is always in cache)

Rating: 5
V$SGASTAT December 31, 2002
Reviewer: atul from India
sir,

In response you said shared_pool used size should stop increasing and keep
monitoring v$sgastat..

What values should we monitor?

I got output like this..
POOL        NAME                            BYTES
----------- -------------------------- ----------
            fixed_sga                       59020
            db_block_buffers            176488448
            log_buffer                   20971520
shared pool free memory                   3759272
shared pool miscellaneous                 1055168
shared pool PLS non-lib hp                   2136
shared pool transaction_branches            82176
shared pool table columns                   20392
shared pool Checkpoint queue               164000
shared pool db_files                       285768
shared pool PL/SQL MPCODE                  947488

POOL        NAME                            BYTES
----------- -------------------------- ----------
shared pool table definiti                  14520
shared pool trigger defini                  21240
shared pool KGK heap                        26080
shared pool transactions                   628056
shared pool SYSTEM PARAMETERS               80448
shared pool log_buffer                     327680
shared pool state objects                  172464
shared pool State objects                  588136
shared pool db_block_buffers             13098752
shared pool KQLS heap                     2090928
shared pool KGFF heap                      112200

POOL        NAME                            BYTES
----------- -------------------------- ----------
shared pool db_handles                     270480
shared pool dictionary cache              3708536
shared pool DML locks                      128000
shared pool messages                        56000
shared pool PL/SQL DIANA                  1420376
shared pool enqueue_resources              104000
shared pool library cache                25735448
shared pool ktlbk state objects            229944
shared pool sql area                     39803808
shared pool processes                      347200
shared pool sessions                       973440

POOL        NAME                            BYTES
----------- -------------------------- ----------
shared pool branches                       123840
shared pool event statistics per sess      954720
shared pool fixed allocation callback        1120
shared pool db_block_hash_buckets         2069952

Thanks.
atul

Followup:
free memory.  use statspack tho, it'll show you the differences in sizes of the
pieces over a window of time.

Rating: 3
Shared Pool Monitoring February 18, 2003
Reviewer: David Piazza from San Mateo, CA
I have the following values:

SQL> show parameter shared_pool_size

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
shared_pool_size                     big integer 25165824

SQL> select sum(bytes) from v$sgastat where pool='shared pool';

SUM(BYTES)
----------
 268435456

SQL> select pool,name,bytes from v$sgastat where name='free memory' and
pool='shared pool';

POOL        NAME                            BYTES
----------- -------------------------- ----------
shared pool free memory                  65181796
***********                            ----------
sum                                      65181796

I'm glad I read your last comment on pg. 82 of your book that says there is no
relationship between the outcome of the query from v$SGASTATS and the
shared_pool_size init.ora parameter, because the values are so different in our
case, 268MB to 25MB.  At first I thought the 25MB value of shared_pool_size
seemed oftly small, but since there's 65MB(and let's say this is representative
over a few day period) of free memory, then it's not too small??  Also, how is
this free memory being calculated.  At first I thought it was part of the
shared_pool_size init.ora parameter which is 25MB, but I guess not.

Followup:
depends on the release.  Tell you what -- look for a time to submit a question
and put this there.  would need to see the output of select * from v$sgastat,
know the version of the database, the output of show sga

If this is 9i, there are these things called granules, sga_max_size settings and
such that complicate the explanation.  I think what we are seeing here is you
have a setting of SGA_MAX_SIZE that exceeds your real allocation -- consider:

ops$tkyte@ORA920> show sga;

Total System Global Area  537989600 bytes
Fixed Size                   451040 bytes
Variable Size             369098752 bytes
Database Buffers          167772160 bytes
Redo Buffers                 667648 bytes

ops$tkyte@ORA920> show parameter sga_max_size

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
sga_max_size                         big integer 537989600

ops$tkyte@ORA920> show parameter shared_pool_size;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
shared_pool_size                     big integer 67108864

See the disparity between the variable size and the shared pool size -- it is
huge.  That is because the amount of memory I asked for in the sga_max_size is
psuedo allocated to the variable size.  Depending in the OS it may or may not be
allocated.  On shared memory systems (unixes), the SGA will dynammically grow
and shrink by adding more shared memory chunks.  On poor old flat models, like
windows, it is allocated -- but not paged in.  As it grows, we page in the
memory we need, as it shrinks, the OS will eventually page it back out.

I believe you have a large sga_max_size and this accounts for the difference -
check it out and check out the individual rows in v$sgastat.


Subject | Article | Last Updated (East coast time)
Return a REF cursor with fields from Previous and Current table rows. | article only | Feb 19, 2003 08:48 am
Why am I getting "PLS-00201: identifier 'ROLLUP' must be declared" in 8.1.5 | article only | Feb 19, 2003 08:51 am
Putting database online | article only | Feb 19, 2003 03:08 pm
blob displays unprintable characters on a web page | article only | Feb 19, 2003 04:29 pm
how to access variable | article only | Feb 20, 2003 07:25 am
Alter table move | article only | Feb 20, 2003 07:34 am
Analyze table does not analyze context index | article only | Feb 20, 2003 07:47 am
Insert Binary file into Database | article only | Feb 20, 2003 08:47 am
Reactive space monitoring alerts | article only | Feb 20, 2003 08:50 am
LMT's segment header and FTS | article only | Feb 20, 2003 09:14 am
Sorting -- sort a string like a number. | article only | Feb 20, 2003 06:53 pm
cipher tables or columns | article only | Feb 21, 2003 09:22 am
Sql subquery returning null | article only | Feb 21, 2003 09:24 am
Java pool and tuning tips8 | article only | Feb 21, 2003 09:47 am
Explain plan, to believe or not to believe. | article only | Feb 21, 2003 09:53 am
Joins | article only | Feb 21, 2003 03:17 pm
Oracle version | article only | Feb 21, 2003 03:45 pm
updatable view | article only | Feb 22, 2003 10:46 am
Sequences | article only | Feb 23, 2003 09:00 am
cross-tab query with a single select statement | article only | Feb 23, 2003 12:45 pm
cursor for loop | article only | Feb 23, 2003 03:02 pm
problem with STEM in context index | article only | Feb 23, 2003 03:36 pm
Processing of DML Statements. | article only | Feb 23, 2003 04:34 pm
Tune SQL statement with max subquery | article only | Feb 23, 2003 05:46 pm
ORA-01036: illegal variable name/number in pro*C trigger | article only | Feb 23, 2003 06:41 pm
Trying to Split Number ranges (having real tough time !) | article only | Feb 23, 2003 07:13 pm
Optimizing Bulk Collects | article only | Feb 23, 2003 08:37 pm
two-phase commit from 9.2.0 to 7.3.x | article only | Feb 23, 2003 08:38 pm
Import from JA16SJIS to UTF8 ... | article only | Feb 23, 2003 08:44 pm
Two iAS related questions | article only | Feb 24, 2003 07:36 am
How to create a block on a procedure in forms. | article only | Feb 24, 2003 02:25 pm
ORA-00666 | article only | Feb 24, 2003 02:52 pm
capturing dup val on ind exception | article only | Feb 25, 2003 08:58 am
Complicated sorting required | article only | Feb 25, 2003 09:20 am
DML UTILITY | article only | Feb 25, 2003 09:21 am
Question on alter table and views | article only | Feb 25, 2003 09:41 am
Min, Max | article only | Feb 25, 2003 07:43 pm
Monitoring Alert file | article only | Feb 25, 2003 08:13 pm
Oracle compared to other databases | article only | Feb 25, 2003 08:36 pm
owa_cookie -- i don't see it in the cookies.txt file | article only | Feb 25, 2003 08:38 pm
row(s) 1 - 40 of 40
February 19, 2003 Ronnie -- Thanks for the question regarding "Return a REF cursor with fields from Previous and Current table rows.", version 8.0.5.0.0
You Asked
I need to return in a Ref Cursor columns from a select (current
record) as well as columns from the prior (or next) record for
the same patient. This needs to be in a procedure on the server,
and will be called by Crystal Reports. Visual Basic is not used.
I can get the result from the select returned to Crystal but I
cannot figure out how to change each row in the cursor.

If the select returned:

PATIENT_ID  Date         Status
11          01/01/1999     A
11          01/05/1999     L
12          03/11/1999     A
12          03/12/1999     L
12          03/12/1999     R
12          03/12/1999     D

I need the returned REF Cursor to previous row info and the next
row info for the same ID

ID  Date      Stat Prev_Date  Prev_Stat  Next_date  Next_Stat
11 01/01/1999   A                        01/05/1999    L
11 01/05/1999   L  01/01/1999    A
12 03/11/1999   A                        03/12/1999    L
12 03/12/1999   L  03/11/1999    A       03/13/1999    R
12 03/13/1999   R  03/12/1999    L       03/14/1999    D
12 03/14/1999   D  03/13/1999    R
and we said...
Well -- in your data above, I believe the Dates for patient 12
should be 11, 12, 13 and 14 -- not 11, 12, 12, 12 right?
Otherwise its really hard ;)

I believe, while this could be done in "pure" sql, it would be
unbearably hard and sometimes the easy way is the way to go.  I
think PLSQL can help here a great deal.  Here is how I might do
this:

ops$tkyte@8.0> create table t
  2  ( id int,
  3    thedate date,
  4    status char(1)
  5  );

Table created.

ops$tkyte@8.0> alter session set nls_date_format = 'mm/dd/yyyy';
Session altered.

ops$tkyte@8.0> insert into t values ( 11,'01/01/1999','A' );
ops$tkyte@8.0> insert into t values ( 11,'01/05/1999','L' );
ops$tkyte@8.0> insert into t values ( 12,'03/11/1999','A' );
ops$tkyte@8.0> insert into t values ( 12,'03/12/1999','L' );
ops$tkyte@8.0> insert into t values ( 12,'03/13/1999','R' );
ops$tkyte@8.0> insert into t values ( 12,'03/14/1999','D' );

Thats our test data.  Now, we will spec out a package that
provides the functions we need

ops$tkyte@8.0> create or replace package get_info
  2  as
  3      function prev_date( p_id in t.id%type,
  4                          p_thedate in t.thedate%type )
  5      return t.thedate%type;
  6      pragma restrict_references( prev_date, wnds );
  7
  7      function prev_status( p_id in t.id%type,
  8                            p_thedate in t.thedate%type )
  9      return t.status%type;
 10      pragma restrict_references( prev_status, wnds );
 11
 11      function next_date( p_id in t.id%type,
 12                          p_thedate in t.thedate%type )
 13      return t.thedate%type;
 14      pragma restrict_references( next_date, wnds );
 15
 15      function next_status( p_id in t.id%type,
 16                            p_thedate in t.thedate%type )
 17      return t.status%type;
 18      pragma restrict_references( next_status, wnds );
 19
 19      pragma restrict_references( get_info, wnds, wnps,
                                     rnps );
 20  end;
 21  /

Package created.

Now we will implement those functions:

ops$tkyte@8.0> create or replace package body get_info
  2  as
  3
  3  g_id        t.id%type;
  4  g_thedate   t.theDate%type;
  5
  5  g_prev_date t.theDate%type;
  6  g_prev_stat t.status%type;
  7  g_next_date t.theDate%type;
  8  g_next_stat t.status%type;
  9
  9
  9  procedure get_data( p_id      in t.id%type,
 10                      p_thedate in t.thedate%type )
 11  is
 12  begin
 13      if ( p_id = g_id and g_theDate = p_theDate ) then
 14          return;
 15      end if;
 16
 16      begin
 17          select theDate, status
 18            into g_prev_date, g_prev_stat
 19            from t
 20           where id = p_id
 21             and theDate = ( select max(theDate)
 22                               from t t2
 23                               where t2.id = p_id
 24                                 and t2.theDate < p_theDate
);
 25      exception
 26          when NO_DATA_FOUND then
 27              g_prev_date := null;
 28              g_prev_stat := null;
 29      end;
 30      begin
 31          select theDate, status
 32            into g_next_date, g_next_stat
 33            from t
 34           where id = p_id
 35             and theDate = ( select min(theDate)
 36                               from t t2
 37                               where t2.id = p_id
 38                                 and t2.theDate > p_theDate
);
 39      exception
 40          when NO_DATA_FOUND then
 41              g_next_date := null;
 42              g_next_stat := null;
 43      end;
 44      g_id := p_id;
 45      g_theDate := p_theDate;
 46  end;
 47
 47
 47  function prev_date( p_id in t.id%type,
 48                      p_thedate in t.thedate%type )
 49  return t.thedate%type
 50  is
 51  begin
 52      get_data( p_id, p_thedate );
 53      return g_prev_date;
 54  end;
 55
 55  function prev_status( p_id in t.id%type,
 56                        p_thedate in t.thedate%type )
 57  return t.status%type
 58  is
 59  begin
 60      get_data( p_id, p_thedate );
 61      return g_prev_stat;
 62  end;
 63
 63  function next_date( p_id in t.id%type,
 64                      p_thedate in t.thedate%type )
 65  return t.thedate%type
 66  is
 67  begin
 68      get_data( p_id, p_thedate );
 69      return g_next_date;
 70  end;
 71
 71  function next_status( p_id in t.id%type,
 72                        p_thedate in t.thedate%type )
 73  return t.status%type
 74  is
 75  begin
 76      get_data( p_id, p_thedate );
 77      return g_next_stat;
 78  end;
 79
 79  end;
 80  /

Package body created.

Now that we have that, we can simply:

ops$tkyte@8.0> select id, theDate, status,
  2         get_info.prev_date( id, theDate ) prev_date,
  3         get_info.prev_status( id, theDate ) prev_status,
  4         get_info.next_date( id, theDate ) next_date,
  5         get_info.next_status( id, theDate ) next_status
  6  from t
  7  /

 ID THEDATE    S PREV_DATE  PREV_S NEXT_DATE  NEXT_S
--- ---------- - ---------- ------ ---------- ------
 11 01/01/1999 A                   01/05/1999 L
 11 01/05/1999 L 01/01/1999 A
 12 03/11/1999 A                   03/12/1999 L
 12 03/12/1999 L 03/11/1999 A      03/13/1999 R
 12 03/13/1999 R 03/12/1999 L      03/14/1999 D
 12 03/14/1999 D 03/13/1999 R

6 rows selected.

ops$tkyte@8.0>

Which gets us our data.  This will work great for a couple of
hundred records -- too many records and the overhead of using
PLSQL will be too much probably (if so, resubmit the question
and we'll try for a SQL only solution that might be faster...)
Review & Followup
Rating: 3
Date Updates From Previous Columns February 18, 2003
Reviewer: Abhijit from Mumbai, India
Hi Tom,
I have got data in the following rows.
My objective is to update the CLOSE DATE column. The CLOSEDATE of a particular
ID
is the next OPENDATE of the same ID.
For example - in the data given below, for ID "02283ESWM001302" and QUEUE -
'AUS-HANDOFF',
the CLOSEDATE will be the OPENDATE for the same id in the second row.

ID        OPENDATE        CLOSEDT QUEUENAME
02283ESWM001302    24 Dec 2002 18:03:05    NULL    AUS-HANDOFF
02283ESWM001302    24 Dec 2002 16:28:00    NULL    AUS-VALIDATE
02283ESWM001302    10 Oct 2002 16:10:51    NULL    AUS-UNCAT
02283ESWM001302    02 Jan 2003 20:20:02    NULL
02267ESWM000743    25 Sep 2002 18:13:19    NULL

My questions are
1) Can this be done without resorting to use a PL / SQL procedure?
2) If I use a cursor in a PL / SQL procedure, how do I update the column in the
previous row?

Thanks in Advance,

Followup:
If the closedt is the opendt of the next row, don't update -- just query.

select id, opendate, lead(opendate) over (partition by id order by opendate)
closedt, ....
 from t;

just like you don't store "age", you store date_of_birth and derive it at
runtime.

Yes, I can write an update to do it (think correlated subquery....)

update t set close = (select min(open) from t t2 where t2.id = t.id and t2.open
> t.open);

Rating: 3
Shame you can't upgrade February 19, 2003
Reviewer: Adrian Billington from UK
...then you could use LAG and LEAD analytics and save all the bother... ;)

February 19, 2003 David -- Thanks for the question regarding "Why am I getting "PLS-00201: identifier 'ROLLUP' must be declared" in 8.1.5", version
You Asked
You had a reponse to a question about procedures that
return resultsets.  I used the example you gave and created the
following:

SQL> CREATE OR REPLACE PACKAGE resultsets
2   AS
3   TYPE rsltcurtyp is REF CURSOR;
4  END resultsets;
5  /

Package created.

SQL> CREATE OR REPLACE PROCEDURE poll_results
2   (v_pollno in number, v_polldttm in varchar, poll_cv IN OUT
resultsets.rsltcurtyp) AS
3  BEGIN
4  OPEN poll_cv FOR
5  SELECT choice_no, count(*)
6      FROM poll_result
7     WHERE poll_no   = v_pollno
8       and to_char(poll_dttm, 'MM/DD/YY') = v_polldttm
9     group by rollup(choice_no);
10  END;
11  /

However, I am getting an error with the "ROLLUP" option on the
cursor.

This query runs successfully in SqlPlus, but when I put it in
the
procedure I get the following error:

SQL> show errors
Errors for PROCEDURE POLL_RESULTS:

LINE/COL ERROR
--------
-----------------------------------------------------------------

5/1      PL/SQL: SQL Statement ignored
9/13     PLS-00201: identifier 'ROLLUP' must be declared
SQL>

Is there something I need to do in order to use the rollup
feature in a cursortype definition?

I am attaaching a sample execution of the query.

Oracle8i Enterprise Edition Release 8.1.5.0.0 - Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.0.0 - Production

SQL> SELECT choice_no, count(*)
2      FROM poll_result
3     WHERE poll_no   = &v_pollno
4       and to_char(poll_dttm, 'MM/DD/YY') = &v_polldttm
5     group by rollup(choice_no);
Enter value for v_pollno: 1
old   3:    WHERE poll_no   = &v_pollno
new   3:    WHERE poll_no   = 1
Enter value for v_polldttm: '02/18/00'
old   4:      and to_char(poll_dttm, 'MM/DD/YY') = &v_polldttm
new   4:      and to_char(poll_dttm, 'MM/DD/YY') = '02/18/00'

CHOICE_NO  COUNT(*)
--------- ---------
1         1
2         1
2

Also, I am attaching a sample output listing of the table and
its rows.

SQL> desc poll_result
Name                                                  Null?
Type
----------------------------------------------------- --------
-----------------------------
RESULT_NO                                             NOT NULL
NUMBER(10)
MEMBER_NO                                             NOT NULL
NUMBER(10)
POLL_NO                                               NOT NULL
NUMBER(10)
POLL_DTTM                                             NOT NULL
DATE
CHOICE_NO                                             NOT NULL
NUMBER(10)
CREATED_DTTM
DATE
UPDATED_DTTM
DATE

SQL> select * from poll_result;

RESULT_NO MEMBER_NO   POLL_NO POLL_DTTM CHOICE_NO CREATED_D
UPDATED_D
--------- --------- --------- --------- --------- ---------
---------
1         1         1 18-FEB-00         1
2         1         1 18-FEB-00         2

SQL>
and we said...
There is sometimes a lag between the SQL the you can enter in
straight from the command line and the SQL you can put in PLSQL
(the PLSQL sql parser might not recognize all of the
latest/greatest).  For example, the above error would be raised
with the TO_LOB call as well.

The workaround is to use dynamic sql to open the cursor
(bypassing the PLSQL SQL parser).  For example:

ops$tkyte@8i> CREATE OR REPLACE PROCEDURE poll_results
  2   (v_pollno in number, v_polldttm in varchar, poll_cv IN OUT
resultsets.rsltcurtyp) AS
  3  BEGIN
  4  OPEN poll_cv FOR
  5  SELECT choice_no, count(*)
  6      FROM emp
  7     WHERE deptno   = v_pollno
  8       and to_char(hiredate, 'MM/DD/YY') = v_polldttm
  9     group by rollup(deptno);
 10  END;
 11  /

Warning: Procedure created with compilation errors.

ops$tkyte@8i> show err
Errors for PROCEDURE POLL_RESULTS:

LINE/COL ERROR
--------
-----------------------------------------------------------------

5/1      PL/SQL: SQL Statement ignored
9/13     PLS-00201: identifier 'ROLLUP' must be declared

ops$tkyte@8i> CREATE OR REPLACE PROCEDURE poll_results
  2   (v_pollno in number, v_polldttm in varchar, poll_cv IN OUT
resultsets.rsltcurtyp) AS
  3  BEGIN
  4  OPEN poll_cv FOR
  5  'SELECT choice_no, count(*)
  6      FROM emp
  7     WHERE deptno   = :v_pollno
  8       and to_char(hiredate, ''MM/DD/YY'') = :v_polldttm
  9     group by rollup(deptno)' using v_pollno, v_polldttm;
 10  END;
 11  /

Procedure created.

So, you need to double up the quotes in the to_char() and then
put the query into a string and then put the USING clause at the
end.  It'll work the same as the static query would have now.
Review & Followup
Rating: 5
explain step wise January 13, 2002
Reviewer: Sam
as to how we are

"The workaround is to use dynamic sql to open the cursor (bypassing the PLSQL
SQL parser).  "

Followup:
that doesn't make sense.  If you are asking "how do we use dynamic sql to
workaround this issue", read the above answer I have.  The example is right
there.   Dynamically open a ref cursor.

Rating: 5
January 21, 2002
Reviewer: PARESH PATEL from BARODA ,GUJARAT , INDIA
I LIKE YOUR REPLING CAPACITY AND YOUR FABULOUS SENSERITY OF
ANSWERING OUR QUESTIONS.
I LIKE IT.

Rating: 5
January 21, 2002
Reviewer: PARESH PATEL from BARODA ,GUJARAT , INDIA
I LIKE YOUR REPLING CAPACITY AND YOUR FABULOUS SENSERITY OF
ANSWERING OUR QUESTIONS.
I LIKE IT.

Rating: 4
what to do in cursor? March 27, 2002
Reviewer: Charlie
Hi Tom,

I want to use rollup function in pl/sql with a CURSOR NOT a Ref Cursor.  I
searched everything in your site, but it all comes with REF CURSOR example.  Can
you help me compile the following example?

create or replace procedure sp_get_stats
is
    cursor c_cursor is
        select id, sum(salary) as total
          from employee
          group by rollup(id);
begin
    for x in c_cursor loop
        dbms_ouput.put_line(x.id || '...' || x.total);
    end loop;
end;
/

The compile failed 'cause there is a rollup even though I use your trick put it
in quote.  It still doesn't work.  Can you help me out?

Thanks,

Followup:
options:

o ref cursor (my preference, not much different from a "cursor cursor"

o upgrade to 9i, this is not an issue.

o hide the group by rollup in a VIEW, query the view in PLSQL

Rating: 4
ref cursor March 27, 2002
Reviewer: Charlie
Followup:
options:

o ref cursor (my preference, not much different from a "cursor cursor"

Hi Tom,  can you show me how to change the following procedure to use ref
cursor, AND use dbms_output to output results in a loop within one pl/sql?

Thanks,

----------------
create or replace procedure sp_get_stats
is
    cursor c_cursor is
        select id, sum(salary) as total
          from employee
          group by rollup(id);
begin
    for x in c_cursor loop
        dbms_ouput.put_line(x.id || '...' || x.total);
    end loop;
end;
/

Followup:
either of these do it.  The second shows how, if you don't want to define
individual columns to fetch into, you can set up a record to fetch into:

scott@ORA817DEV.US.ORACLE.COM> create or replace procedure sp_get_stats
  2  is
  3          type rc is ref cursor;
  4          c_cursor rc;
  5          l_id    emp.empno%type;
  6          l_total emp.sal%type;
  7  begin
  8      open c_cursor for
  9          'select empno, sum(sal) as total
 10             from emp
 11             group by rollup(empno)';
 12          loop
 13                  fetch c_cursor into l_id, l_total;
 14                  exit when c_cursor%notfound;
 15          dbms_output.put_line(l_id || '...' || l_total);
 16          end loop;
 17          close c_cursor;
 18  end;
 19  /

Procedure created.

scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM> create or replace procedure sp_get_stats
  2  is
  3          type rc is ref cursor;
  4          c_cursor rc;
  5
  6          cursor template is select empno, sum(sal) total from emp;
  7          l_rec  template%rowtype;
  8  begin
  9      open c_cursor for
 10          'select empno, sum(sal) as total
 11             from emp
 12             group by rollup(empno)';
 13          loop
 14                  fetch c_cursor into l_rec;
 15                  exit when c_cursor%notfound;
 16          dbms_output.put_line(l_rec.empno || '...' || l_rec.total);
 17          end loop;
 18          close c_cursor;
 19  end;
 20  /

Procedure created.

Rating: 5
EXCELLENT March 27, 2002
Reviewer: Charlie
Thank you for your follow up!!!

Rating: 4
Bulk collect the cursor into a pl/sql table February 19, 2003
Reviewer: bala from Kuala Lumpur
Tom how do I bulck collect in this case. I mean bulk collecting into a pl/sql
table from the cursor.
I am trying to
select a.* bulk collect into pl_tb from (select a,b,(select x from abc) x from
t);
the inline view doesnt solve the problem
still end up getting the same
PLS-00103: error
Need some help

Followup:
you need to supply a full example of what you are trying -- versions are needed
too.

depends on the release whether you can bulk collect into a table of records or
whether you need a RECORD of TABLES.

You probably need a record of tables -- that is, you cannot in your version bulk
collection into an array of records.  In your version you have to bulk collect
into simple scalar arrays only.

Rating: 5
December 19, 2003
Reviewer: A reader

February 19, 2003 -- Thanks for the question regarding "Putting database online", version 8i
You Asked
I want to ask that when a database has been prepared in oracle8i, how to put it
online? What software product should be used to do this to make it searchable on
the WEB? Can we use Active Server Pages( ASP)?
and we said...
Yes you can use ASP if you want to limit your platform to Windows.

There are litterally dozens of ways to do this.

asktom.oracle.com -- I use PLSQL to publish my database (all of this stuff is in
the database)

Java Server Pages (jsp) are another -- many tools available to develop these and
they work on many different platforms (more open)

Java Servlets are yet another method

There are tools like Oracle's Portal that can be used to publish information
without writing much code at all as well.

There is perl, php, etc etc etc as well.  You might want to do some research in
this area to see what suites your needs the best.
Review & Followup
Rating: 2
Making only SOME of the data public February 19, 2003
Reviewer: Benoit Hebert from Quebec, QC Canada
Those tools are great if you want to publish the entire database, but what if
you only want to publish part of it?
We have a fairly large bioinformatics database, and are looking at making some
of the data available to the public and the scientific community.
I've created a view on one of the tables, like this:

CREATE VIEW public_data AS
SELECT * FROM EXP_DATA WHERE Public_Flag > 0 WITH READ ONLY;

GRANT SELECT ON public_data TO PUBLIC;

I then wrote a simple PL/SQL procedure that would display the data from this
view on a Web page, and granted execution of that procedure to public. But the
database still asks for my user name and password. The procedure doesn't use any
of the other "private" PL/SQL scripts, and selects data only from the public
view.

One idea I had was to grant connect to public, but I'm not so sure that would be
a good idea, from a security standpoint.

We're using Oracle 9.0.1 with Oracle Internet Application server 9.0.2.0.1

Any thoughts, or better ideas on how we should proceed?

Thank you!

Followup:
you can grant connect to public but so what?  you would then need to still "log
in".

What you want to do is setup a "DAD" that stores a username/password -- like I
do on asktom.  When you goto my home page -- you are logging in as
PUBLIC_USER/password.  You are all "public_user" in my database.

If I needed to authenticate, to give you more access - then I would ask you for
your database username and password.

This is a very natural thing to do -- in fact the site you are on right now
reading this DOES that.

When I log in -- I can see a lot more data then I can when I'm not logged in.

February 19, 2003 Les -- Thanks for the question regarding "blob displays unprintable characters on a web page", version 8.1.7.0.0
You Asked
Good monring Tom,

I have used some of your previous answers, and hence I am able to
populate a blob column. I know it is populated because when I put character data
in it, my web page shows the length and the characters.
However, when I put a jpg or gif in it, the length is shown, but the gif or jpg
show up as if it was being edited by something like word pad. The image is
returned as uprintable characters. This happens on both netscape 4 and ie 4.

Here is the code I am using to display the image:

   procedure gif( p_id in web_picts.pic_id%type )
   is
      l_lob          blob;
      buffer         raw(32000);
      buffer_size    integer := 32000;
      offset         integer := 1;
      length         number(8);
   begin
      owa_util.mime_header('image/gif');
      select picture into l_lob
        from web_picts
       where pic_id = p_id;
      length := dbms_lob.getlength(l_lob);
      while offset < length loop
        dbms_lob.read(l_lob, buffer_size, offset, buffer);
        htp.p('Picture Length ' || length);
        --htp.p(l_lob);
        htp.prn(utl_raw.cast_to_varchar2(buffer));
        offset := offset + buffer_size;
     end loop;
  exception
      when others then
    htp.p(sqlerrm);
  end;

 And here is the code that calls the above:

   Procedure home

    is

   theImage blob;
   Begin
       htp.p ('test...just to be sure a page is showing');
       image_get.gif(1);

   Exception
      When others Then
          null ;
   End;

The above is a condensed example dealing with only getting the blob.

This is what the web page shows for the image:

test...just to be sure a page is showing Content-type: image/gif Picture Length
17050
ÿØÿà‘¡±B#$RÁb34r‚ÑC%’Sðáñcs5¢²ƒ&D“TdEÂ£t6ÒUâeò³„ÃÓuãóF'”¤…´•ÄÔäô¥µÅÕåõVfv†–¦¶
ÆÖæö7GWgw‡—§·Ç×ç÷UËk7HqLyÙ»Wåî)32ÀdsÒLH7í,òÔn–Ú}²>¥sqÆùÝRS§ŽÙ¸q9q9ùÌ¦w&þO
 ©} B’„'¡)zÓ‡hÁ©+ætŽ.vekbö”ùÈó
7Ö®u~K–µkU[n¿?ó‰µî:«1N’*²*u”P„P„P„P„P„P„P„P…«?€‰2R§
'§¤Ô3I¤,ÐñXùù%ˆøñåGOç+úë€Üww=Ä4égÄäÎÖÃV%6Àù`ÚÒ!õ¬žzx~ÚK¬”Ëíãnem1ò×
?5´Ué*?º±Ä³H‡búvîÊŽt8üD*×²Ý@6õª³¡Ë4C±ª0Û5õ†#®+î(!§¥pô$šÁ

This happens on both the server machine and my work machine, so I think it is a
setting of some kind...something simple.

At any rate, many thanks in advance for any assistance you may offer.

Les Davidner
and we said...
The procedure that "calls" it should be:

 Procedure home
    is
   theImage blob;
   Begin
       htp.p ('test...just to be sure a page is showing');
       htp.p( '<img src=image_get.gif?p_id=1>' );
   Exception
      When others Then
          null ;
   End;

you yourself will NEVER call image_get.gif -- the web browser will.  Think about
if, if you made a static web page -- just an html file, you would never "read
the jpg" into the HTML right?  your home procedure does that -- reads the jpg
right into the html.  You want to generate HTML that references via the IMG tag
the jpg.
Review & Followup
Rating: 2
Helpful but need an example February 19, 2003
Reviewer: Joyce Fowler from Scott AFB O'Fallon, Illinois
I have PL/SQL procedures to create a table with a BLOB field and a procedure to
load images into that table.  But I am having trouble displaying those images in
a Browser.
You said "You want to generate HTML that references via the IMG tag the jpg."
How to I do this in HTML where an image is called from the database and
displayed in a Browser?  Do I use a procedure call????  There is <IMG
SRC=<procedure call>">  Do you have any example of using this?
Thanks,

Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:232814159006

February 20, 2003 Teki -- Thanks for the question regarding "how to
      access variable", version 9.2.0

      You Asked
Q1.  How to access variable which is declared in oracle package for JDBC driver
=======================

Ex.
 create or replace package test is

   type p_data is record
   (
      eno   dbms_sql.number_table,
      ename dbms_sql.varchar2_table
   ) ;

   procedure testrec (outrec out p_data) ;

end test ;

If i want to register p_data in jdbc driver. how to implement ?

Q2.  when I issue command
=========================

     create type p_record is record
     ( eno number(2), ename varchar2(40))

this is the error i am getting

LINE/COL ERROR
-------- -----------------------------------------------------------------
1/15     PLS-00103: Encountered the symbol "RECORD" when expecting one of
         the following:
         array VARRAY_ table object fixed varying opaque
         The symbol "object was inserted before "RECORD" to continue.

Note : I have downloaded oracle database version from otn.oracle.com

      and we said...
q1) record types are not accessible from any language other then PLSQL.  A
PLSQL record type can ONLY be accessed by another PLSQL routine.

You could code:

 create or replace package test is

   type p_data is record
   (
      eno   dbms_sql.number_table,
      ename dbms_sql.varchar2_table
   ) ;

   global_data p_data;
   procedure testrec (outrec out p_data) ;

end test ;

and then execute:

   begin
       test.testrec( test.global_data );
       ? := test.global_data.eno;
       ? := test.global_data.ename;
    end;

from your jdbc application and bind in each attribute.  OR you can use Oracle
Object types instead of PLSQL records.  Then you can use the JPUBLISHER utility
to create java classes that can bind to these object types.

Q2) the syntax is

create type p_record AS OBJECT
( .....

RECORDS are specific to plsql -- you want to use a SQL object type.
Review &
      Followup

      Rating: 4
That was
      helpful, but can you go one step further?  February 18, 2003

Reviewer:  John  from New Jersey, USA
Hi Tom:

I have a question related to this.  I need to return and take record sets from
Java code.  And I declared record types and PL/SQL table types in packages and
use them as parameters for stored procedures.  Java programmers now want to use
JPublisher to create Java classes in order to access my stored procedures.  I
don't like the idea of creating standalone Oracle objects because 1) I want to
package the related variables in a package to take the performance advantages of
PL/SQL packages; 2) I can't declare Oracle objects based on table definition, eg
emp.empno%type.  Is there a way that I don't have to create standalone Oracle
objects and Java programmer can still use JPublisher to create Java classes?
Thanks.

          Followup:

You will either use Oracle object types or you will have no joy.

Object Types are visible outside of PLSQL.

PLSQL types are visible inside of PLSQL only.

      Rating: 5
Thank you,
      though that's not what would like to hear.  February 19,
      2003
Reviewer:  John  from New Jersey, USA

Can JPublisher be used to create Java classes for Ref cursor, and how?

          Followup:

don't need to -- they are just result sets.  You would turn the ref cursor into
a result set and then use getString, etc on the rows in the result set.

See

http://asktom.oracle.com/~tkyte/ResultSets/index.html

      Rating: 5
Thanks,
      Tom  February 19, 2003
Reviewer:  John
       from New Jersey, USA
So I declared Oracle object type, and created a function passing the object type
as parameters.  But I got error message when I tried to test the function from
PL/SQL.  Can you help me on this?

create or replace type o_add_inventory as object (
  rep_id                 number,
  form_id                varchar2(15),
  form_date              date,
  inventory_type_id      number,
  inv_name_id            number,
  form_comment           varchar2(1000),
  user_id                number)
/

create or replace type o_add_inventory_item as object (
  product_id             number,
  prod_lot_no            number,
  quantity               number(5))
/

create or replace type ot_add_inventory_item as table of o_add_inventory_item;

create or replace function add_inventory (p_inventory o_add_inventory,
                                          p_inventory_item
ot_add_inventory_item)
return number IS
      v_status              binary_integer;
      v_inventory_id        inventory.inventory_id%type;
      v_inventory_item_id   inventory_item.inventory_item_id%type;
      v_prod_lot_id         product_lot.prod_lot_id%type;

BEGIN
   for i in 1 .. p_inventory_item.count loop
         insert into inventory_item (inventory_item_id,
                             product_id,
                     init_form_prod_lot_id,
                     init_form_quantity,
                     create_user_id)
                 values (seq_inventory_item_id.nextval,
                     p_inventory_item(i).product_id,
                     v_prod_lot_id,
                     p_inventory_item(i).quantity,
                     p_inventory.user_id);

   end loop;
   commit;
   v_status := 0;
   return v_status;

END add_inventory;
/

My test script:

set serveroutput on

declare
   v_inventory        O_ADD_INVENTORY;
   vo_inventory_item     O_ADD_INVENTORY_ITEM;
   vot_inventory_item     OT_ADD_INVENTORY_ITEM;
   v_status             binary_integer := null;

begin

   v_inventory := O_ADD_INVENTORY (null, null, null, null, null, null, null);

   v_inventory.rep_id             := 1;
   v_inventory.form_id            := 1;
   v_inventory.form_date          := to_date('12/11/2002', 'MM/DD/YYYY');
   v_inventory.inventory_type_id  := 1;
   v_inventory.inv_name_id        := 1;
   v_inventory.form_comment       := 'This is my first inventory';
   v_inventory.user_id            := 1;

   vo_inventory_item := O_ADD_INVENTORY_ITEM (null, null, null);
   vot_inventory_item := OT_ADD_INVENTORY_ITEM (vo_inventory_item);

   vot_inventory_item(1).product_id  := 1;
   vot_inventory_item(1).prod_lot_no  := 1;
   vot_inventory_item(1).quantity  := 100;

   vot_inventory_item(2).product_id  := 1;
   vot_inventory_item(2).prod_lot_no  := 1;
   vot_inventory_item(2).quantity  := 100;

   v_status := add_inventory(v_inventory, vot_inventory_item);

  dbms_output.put_line('v_status ='||v_status);

end;
/
declare
*
ERROR at line 1:
ORA-06533: Subscript beyond count
ORA-06512: at line 26

          Followup:

you allocated an array of one on line 20

   vot_inventory_item := OT_ADD_INVENTORY_ITEM (vo_inventory_item);

you are trying to use the second item -- it does not exist.  code:

ops$tkyte@ORA920> declare
  2     v_inventory          O_ADD_INVENTORY;
  3     vot_inventory_item   OT_ADD_INVENTORY_ITEM := ot_add_inventory_item();
  4     v_status             binary_integer := null;
  5  begin
  6     v_inventory := O_ADD_INVENTORY (1, 1,
                                        to_date('12/11/2002', 'MM/DD/YYYY'),
  7                                     1, 1, 'this is my first', 1);
  8
  9     vot_inventory_item.extend;
 10     vot_inventory_item(1) := o_add_inventory_item(1,1,100);
 11
 12     vot_inventory_item.extend;
 13     vot_inventory_item(2) := o_add_inventory_item(1,1,100);
 14
 15  --   v_status := add_inventory(v_inventory, vot_inventory_item);
 16
 17    dbms_output.put_line('v_status ='||v_status);
 18
 19  end;
 20  /
v_status =

PL/SQL procedure successfully completed.

      Rating: 5
back to the
      question:  February 19, 2003
Reviewer:  John
       from New Jersey, USA
Back to the question of using JPublisher to generate Java classes.  It seems
that the only way to do that is to create Oracle object type which can be
accessed by Java program.  If my only purpose of creating object type is to
easily generate Java classes, what happen if I drop those object types after I
finish creating Java classes?

I'm thinking about creating record type or PL/SQL table in packages that has the
same structure as oracle object type.  I'll use record type or PL/SQL table as
parameters in Oracle stored procedures, whereas use object type to create Java
classes.  Those stored procedure should be accessible by Java classes because
the record type and PL/SQL table share the same structure as object type.  Hope
this is not a really crazy idea.

          Followup:

then the java classes become as obsolete as if you dropped the tables they
insert into.

PLSQL record types CANNOT BE BOUND TO BY A 3GL.  It is not that it is "hard to
generate the mapping" to a plsql record from java.  It is that it is IMPOSSIBLE,
none of C, C++, VB, Java -- nothing can send PLSQL a record type.

      Rating: 5
Thanks,
      Tom.  May 20, 2003
Reviewer:  John
       from New Jersey, USA
We implemented successfully Oracle objects and passed data from Java to Oracle
via the objects.  Thanks for your invaluable advice!

February 20, 2003 sreedhar -- Thanks for the question regarding
      "Alter table move", version 8.1.6

      You Asked
Hello Tom,

once again ... sreedhar

 Tom Need sum sugestions..
Reg: Alter table  < t > move tablespace <ts>;

1.How this command will actullly work.
2.Why  the related indexes got unsuable state after moveing
table.
3. what is impact of corresponding relations to the table.

IMP:
Tom please suggest me..
What happen  when i move the table   to the existing tablespace
itself  can i expect  any sort performance growth..
alter table <t> tablespace < source same as old>
2. and when i move table to the same tabelspace with  the
different storage parameters.... what is impact..

Thanking  you..

Sreedhar

      and we said...
1) it totally rebuilds (rewrites) the entire table.

2) because every rowid in in the index now points to the wrong
place.  We need to rebuild the indexes in order to get the
rowids they contain pointing to real data.

3) none, all constraints, grants, etc are preserved.  that is
what makes this command superior to exp/imp.

Can you expect performance increase?  Maybe, maybe not (that is
the answer to virtually all questions ;)  If the table had tons
of chained and migrated rows -- you may experience some positive
increases in performance.  If the table was already nicely
organized, you should expect to see the same relative
performance.

If you move it with different storage parameters -- it'll have
different storage.  thats it.  In 8.1.6, I use locally managed
tablespace exclusively so the only storage parameters that
matter to me are pctfree and pctused.  You would pick these
based on the data in the table and how it is use.

Review &
      Followup

      Rating: 2
But different
      results with 9.2 dictionary managed tablespaces  February
      19, 2003
Reviewer:  Rem  from Ashburn, VA USA

Given my db_block_size = 8k ...
create tablespace foo datafile 'c:\temp\foo.dbf' size 10M
extent management dictionary segment space management manual
default storage (initial 128K next 8K minextents 1
   maxextents unlimited pctincrease 0);
create table bar (col1 char);

When I query dba_segments and dba_extents I can see I have a table that uses 1
extent of size 128k.

alter table bar move tablespace foo storage (initial 192k next 8k);

Now, when I query dba_extents I seem to have a table with 5 extents.  The first
is 192K followed by 4 extents of size 8K.

Therefore, it appears that by issuing the ALTER TABLE ... MOVE command on a
table (even with no data) Oracle has decided to allocate 4 new extents.  In
subsequent tests with other extent sizes I always got 1 or more extra extents.

Not sure why.  I just did an ALTER TABLE ... DEALLOCATE UNUSED and the extra
extents did not disappear.

          Followup:

Not sure either but hey -- you know what -- I refuse to even set up an instance
to even TRY this on.

None of my 9.2 databases are EVEN CAPABLE of hosting a DMT.

Now, I did this on 9iR1 but I don't see anything close to what you see:

ps$tkyte@ORA9I> create tablespace foo datafile '/tmp/foo.dbf' size 1M reuse
  2  extent management dictionary
  3  segment space management manual
  4  default storage
  5  (initial 128K next 8K minextents 1 maxextents unlimited pctincrease 0);

Tablespace created.

ops$tkyte@ORA9I>
ops$tkyte@ORA9I> create table bar (col1 char) tablespace foo;

Table created.

ops$tkyte@ORA9I>
ops$tkyte@ORA9I> select extent_id, bytes
  2  from dba_extents where segment_name = 'BAR' and owner = user;

 EXTENT_ID      BYTES
---------- ----------
         0     163840

ops$tkyte@ORA9I> alter table bar move tablespace foo storage( initial 192k next
8k );

Table altered.

ops$tkyte@ORA9I> select extent_id, bytes
  2  from dba_extents where segment_name = 'BAR' and owner = user;

 EXTENT_ID      BYTES
---------- ----------
         0     204800

ops$tkyte@ORA9I>

Maybe you can run a test *just like that* and cut and paste the source of truth
-- "sqlplus output, unedited"

      Rating: 5
ALTER TABLE ...
      MOVE to give back space after insert/*+ APPEND*/ ?  February
      20, 2003
Reviewer:  pasko  from HH,Germany

Hi Tom,

I think i might have read this one from your site,
that we can use :

ALTER TABLE t_name MOVE  ;

to give back space allocated to the table after  deleting many rows ,  i.e - (
Lower the HWM) ,
when we are frequently  using the :

 insert /*+ APPEND */ into  t_name
 select ....from ...t_name_2

That is to say ,  instead of doing Truncate , we can use this statement as well
?

Thanks in advance , and the new features from your site are great !

I have just done a : 'get all questions asked/reviewed  by me '

          Followup:

just use insert then?

why write and rewrite the object when you can just "write" the object.

February 20, 2003 Sameer -- Thanks for the question regarding
      "Analyze table does not analyze context index", version 8.1.6.3.0

      You Asked
Hi Tom,

I analyzed a table containing CLOB column by analyze table <tablename> compute
statistics.

I checked the last_analyze column in user_indexes ,it contains the analyze data
for my primay key index, but empty for my context index on clob column .

I also tried analyze index <index_name> compute statistics on context index.
Still i found last_analyze blank.

Could you please highlight , how can i analyze my context indexes.
I do not want to rebuild index.

Any help
Thanks
Sameer

      and we said...
Domain indexes do not update this field.  If you look at the underlying
interMedia tables/indexes, you'll find they are analyzed.

You have analyzed them.
Review & Followup

      Rating:
      3
  May 07, 2001
Reviewer:  Sameer
      Wadhwa  from Westborough,MA

      Rating: 3
which
      tables  February 20, 2003
Reviewer:  Scott
       from Perth, Western Australia
G'day

Which underlying tables should we be checking to see if the context indexes are
analysed?

Are the statistics up to date, or reasonably up to date after a
ctx_ddl.sync_index is performed on an index?
Or should the sync_all_ctx_indexes script you mentioned here

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:350017644883
also perform a compute (statistics)?

Thanks.

          Followup:

well, you don't want to compute every couple of minutes do you??  So no, I would
not put an analyze in there.

unfortunately, the last analyzed time is not captured for domain indexes, hence
it is not possible to ask "when was it last analyzed".

You would schedule an analyze based on your anticipated volume of modification
the the underlying data.  The more you modify, the more often you would analyze
the index.

February 20, 2003 -- Thanks for the question regarding "Insert
      Binary file into Database", version 9.2

      You Asked
Hi Tom,

  I'm trying to insert (or import) a binary file about 3K into a row of  of the
table type Long Raw.  So my C++ application can retreive it later.  How should I
do it.

Thanks

David

      and we said...
stop -- go back a step -- fix the table.  It must use a BLOB, not a LONG RAW.

Then, see:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96591/toc.htm

do NOT use a long raw in newly developed code.  longs and long raws stink, so
much less functional then blobs and clobs.
February 20, 2003 Liz -- Thanks for the question regarding
      "Reactive space monitoring alerts", version 8.1.7

      You Asked
Hi

How are you today?

ok triggers - I know you can have log on triggers and trigger to fire on update
insert delete statements etc. OK...... We have developed a space monitoring
report that is sent to us every 2 hours showing tablespaces that are at 80% full
 - or tables and indexes that are with in x amount of reaching max extents. Now
this report is not reactive... we don't see the problem until we get the report.

We want to have a reactive report or alert that sends us an email warning that a
particular table or tablespace is in danger of filling - as soon as that x
number is reached. (if that makes sense?) so when a tablespace is 80% full, it
straight away alerts us and we can fix it.

Is there anyway to use triggers or something to do this???

      and we said...
Well, suggestion -- start using locally managed tablespaces exclusively and half
of the problem goes away.  There is no such thing as "max extents" with LMTS.
All extents are unlimited.  That is the approach to persue in the future.  I
would stop looking for maxextents right away -- just set them all unlimited.

You need to "poll" otherwise, there are no triggers for space related events
like that.  A query to find tablespace utilitization is fairly small in its
overhead.

Of course, if your action is "resize datafile larger", you could let the system
do it FOR YOU and then report on the fact that it did so.
Review &
      Followup

      Rating: 4
OEM has
      built-in event to handle this  February 20, 2003

Reviewer:  Bogie  from Atlanta, GA
If you use Enterprise Manager, you can set up events that monitor space usage at
almost every conceivable level.  The configuration allows for a warning limit
(i.e. tablespace reaches 75% full) and critical limit (i.e. 90% full).  The
event can be configured to run at any interval from 1 min. to 1 month.  The
events are VERY handy!

      Rating: 4
More OEM
      goodies  February 20, 2003
Reviewer:  Bogie
       from Atlanta, GA
...forgot to mention that OEM also has a 'fixit job' to handle event failures
(i.e. a fixit job can be set up to resize the datafile for a tablespace).  The
fixit jobs can do almost anything a dba can do and do it when the event occurs.

February 20, 2003 biti_rainy -- Thanks for the question regarding
      "LMT's segment header and FTS", version 817

      You Asked
hi,tom
i  create a  table T
i dump the LMT's  blocks(datafile header,segment header, map extent,etc.)

the uet$ and fet$ do not have data about the table T,
if i select * from dba_extents, from some documents i know that will return data
from the segment header  because the DATA DICT do not have the information.

and my question is:

when i  do a FTS or select * from dba_extents, how to know where the  segment
header is?

      and we said...
well, if you look at the text of the view that is dba_extents:

create or replace view dba_extents (
OWNER
,SEGMENT_NAME
,PARTITION_NAME
,SEGMENT_TYPE
,TABLESPACE_NAME
,EXTENT_ID
,FILE_ID
,BLOCK_ID
,BYTES
,BLOCKS
,RELATIVE_FNO
) as
select ds.owner, ds.segment_name, ds.partition_name, ds.segment_type,
       ds.tablespace_name,
       e.ext#, f.file#, e.block#, e.length * ds.blocksize, e.length, e.file#
from sys.uet$ e, sys.sys_dba_segs ds, sys.file$ f
where e.segfile# = ds.relative_fno
  and e.segblock# = ds.header_block
  and e.ts# = ds.tablespace_id
  and e.ts# = f.ts#
  and e.file# = f.relfile#
  and bitand(NVL(ds.segment_flags,0), 1) = 0
union all
select /*+ ordered use_nl(e) use_nl(f) */
       ds.owner, ds.segment_name, ds.partition_name, ds.segment_type,
       ds.tablespace_name,
       e.ktfbueextno, f.file#, e.ktfbuebno,
       e.ktfbueblks * ds.blocksize, e.ktfbueblks, e.ktfbuefno
from sys.sys_dba_segs ds, sys.x$ktfbue e, sys.file$ f
where e.ktfbuesegfno = ds.relative_fno
  and e.ktfbuesegbno = ds.header_block
  and e.ktfbuesegtsn = ds.tablespace_id
  and e.ktfbuesegtsn = f.ts#
  and e.ktfbuefno = f.relfile#
  and bitand(NVL(ds.segment_flags, 0), 1) = 1

/

You'll see it is a union all of uet$ and x$ktfbue basically.  (File Bitmap Used
Extents)

The magic is in the x$ tables -- there is where you find that other stuff (lmt
info)
Review
      & Followup

      Rating:
      5
  February 20, 2003
Reviewer:
      A reader
Hello all,

 Good artical on this written by jonathan lewis...

http://www.dbazine.com/jlewis8.html

      Rating:
      4
thanks  February 20, 2003
Reviewer:
      biti_rainy  from zhuhai
i see

February 20, 2003 -- Thanks for the question regarding "Sorting --
      sort a string like a number.", version version8.1.6

      You Asked
I have a alphanumeric column in the database.
It contains only numeric values.Is it possible to do
a numeric sort directly by a sql command like "order by <column>"

      and we said...
Yes, if they are all numbers in the character field, you can:

select * from T order by to_number(column);

In fact, in Oracle8i, you could even create an index on
to_number(column) and stand a chance of using that index in
order to sort the data.  See

http://osi.oracle.com/~tkyte/article1/index.html
for more information on that feature.

Review &
      Followup

      Rating: 5
help for a
      specific sort required.  May 14, 2002
Reviewer:
      Bhawna Rajesh  from New Delhi, India
Hi Tom,

Our client has a column FUNDNUMBER, which is alphanumeric, contains values
0..99, 0A..0Z, 1A..1Z, ..so on, A..Z, A0..A9, B0..B9, ..so on, AA..AZ,
BA..BZ,... ZA..ZZ
We are supposed to show the drop-down of FUNDNUMBER in this specific order. The
normal orderby on fundnumber works fine except 0..99 range, where the string
sorting does not give us desired result. After 1, 11 is shown.
Can we have the resultset retrived in the client-specific order by using some
trick?

Please help us out, I know you are the only person who can.

          Followup:

order by lpad(column,2) to make everything 2 characters long.  Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x varchar2(2) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select rownum from all_users;

36 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select x from t order by x;

X
--
1
10
11
12
13
14
15
16
17
18
19
2
20
21
22
23
24
25
26
27
28
29
3
30
31
32
33
34
35
36
4
5
6
7
8
9

36 rows selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select x from t order by lpad( x, 2 );

X
--
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.

      Rating:
      3
  May 16, 2002
Reviewer:  sagi
       from India.
You are simply superb with answers are to the point.

Admirer of your knowledge.
Regards

      Rating: 2
We did the
      following  February 20, 2003
Reviewer:
      Murali  from Norfolk,MA
create table ting(tno varchar2(4));

insert into ting values('1');
insert into ting values('2');
insert into ting values('3');
insert into ting values('10');
insert into ting values('11');
insert into ting values('1a');
insert into ting values('20');
insert into ting values('2a');
insert into ting values('3a');
insert into ting values('10a');

We expect to see the order of the values as 1,1a,2,2a,3,3a.... and so on with
your suggested solution. But what we have seen is the following order after we
executed:

select tno from ting order by lpad(tno,4);

tno
----
1
2
3
10
11
1a
20
2a
3a
10a

What can we do to get the desired result?. Please suggest.

          Followup:

why?  That is not the way character strings sort. think "ascii collating
sequence"

ops$tkyte@ORA920> select lpad(tno,4), tno from ting order by lpad(tno,4);

LPAD TNO
---- ----
   1 1
   2 2
   3 3
  10 10
  11 11
  1a 1a
  20 20
  2a 2a
  3a 3a
 10a 10a

10 rows selected.

That is just the way that string sorts...

So, it LOOKS to me like you want to sort this field AS IF it were 2 fields yes?
A number followed by a string?  Sort numerically and within the number by
string?

1
1   a
2
2   a
3
3   a
10
10  a
11
20

right?  if so, we'll just break it in two:

ops$tkyte@ORA920> select
substr(tno,1,length(tno)-nvl(length(replace(translate(tno,'0123456789','000000000
0'),'0','')),0)),
  2
substr(tno,1+length(tno)-nvl(length(replace(translate(tno,'0123456789','000000000
0'),'0','')),0)) ,
  3         tno
  4    from ting
  5   order by
  6
to_number(substr(tno,1,length(tno)-nvl(length(replace(translate(tno,'0123456789',
'0000000000'),'0','')),0))),
  7
substr(tno,1+length(tno)-nvl(length(replace(translate(tno,'0123456789','000000000
0'),'0','')),0)) NULLS FIRST
  8  /

SUBS SUBS TNO
---- ---- ----
1         1
1    a    1a
2         2
2    a    2a
3         3
3    a    3a
10        10
10   a    10a
11        11
20        20

10 rows selected.

And maybe next time think "2 fields = 2 columns.  Numbers should be stored in
Number types and Strings in varchar2's and dates in dates"  and don't store data
in such an awkward style ;)

      Rating: 4
appropriate
      query for Bhavna's requirement  February 21, 2003

Reviewer:  Chandra  from Delhi, India
I think a more appropriate query for the requirement of Bhavna is:

select x from t
order by decode(sign(ascii(substr(lpad(x, 2), 2))-64), 1,
   lpad(x, 3, chr(58)), lpad(x, 2));

For eg:-
chandra@ora817> select x from t order by lpad(x, 2);

X
--
1
2
3
4
5
6
7
8
9
A
B
C
0A
0B
0C
10
11
12
13
14
A1
AA
B1
BB
C1
CC
DD

where as

chandra@ora817> select x from t
  2  order by decode(sign(ascii(substr(lpad(x, 2), 2))-64), 1,
  3     lpad(x, 3, chr(58)), lpad(x, 2));

X
--
1
2
3
4
5
6
7
8
9
10
11
12
13
14
0A
0B
0C
A
B
C
AA
BB
CC
DD
A1
B1
C1

      Rating: 4
Sorting -- sort
      a string like a number  December 09, 2003

Reviewer:  A reader  from Grand Rapids, MI USA

The last example was perfect for what I needed.  Thank you !!

February 21, 2003 david -- Thanks for the question regarding
      "cipher tables or columns", version 8x

      You Asked
Hello,

Does Oracle provide any mechanism to cipher table or columns ?
We are storing confidential informations in the database
(ie credit card numbers) and we would like those informations
not to be stored in clear.
If Oracle provide such a mechanism, what kind of encrytion
mechanism is used (DES , TDES ?) and how and where are the
cypher keys stored ?

thansk for your help.

      and we said...
In Oracle8i release 2 (8.1.6), we introduced the
dbms_obfuscation_toolkit.  This package supported DES encryption
of data using a 56bit key.

In Oracle8i release 3 (8.1.7) we upgraded this package to
support triple des in either 2 or 3 key mode (112 or 168 bit
keys) and added MD5 checksum functions as well.

See

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/addendum.817/a85455/packadde.htm#1021368
Key management is left totally up to you.  You can store them
and manage them in any way you see fit.

Review &
      Followup

      Rating: 4
thank a
      lot  February 20, 2003
Reviewer:  weisj
       from china
but , if the encrypted data is larger than the origin data,
how insert the encrypted to the table?
thx!

          Followup:

that means the column in the table is the wrong size for the data you wish to
store.

The size of the "original data" here is not relevant.
The size of the data you ACTUALLY want to store is.  <<--- that is what dictates
the size of your column!

February 21, 2003 Pras -- Thanks for the question regarding "Sql
      subquery returning null", version 8.1.7

      You Asked
Hi Tom,

If i want for e.g. to find out the names of the employees who draw the same
commission as some employee say SCOTT and if SCOTT does not get commission the
query should list all those who do not get commission and if he does get then
list those which get commission equal to him.In short the query should work for
all the cases returned by sub-query in all circumstances.

Thanks in advance
pras

      and we said...
scott@ORA920> break on ename skip 1
scott@ORA920>
scott@ORA920> select a.ename, b.ename "This guy makes the same"
  2    from emp a, emp b
  3   where (a.comm = b.comm OR (a.comm is null and b.comm is null))
  4     and a.empno <> b.empno
  5   order by a.ename
  6  /

ENAME      This guy m
---------- ----------
ADAMS      SMITH
           JONES
           CLARK
           KING
           FORD
           MILLER
           JAMES
           SCOTT
           BLAKE

BLAKE      SMITH
           MILLER
           FORD
           JAMES
           CLARK
           SCOTT
           ADAMS
           KING
           JONES

.........

no subqueries to be found there -- this is a simple "join"
Review &
      Followup

      Rating: 4
could nvl be
      used instead of "or" / do u see any performance issue with
      this  February 20, 2003
Reviewer:
      A reader

          Followup:

Only if you want to

o confuse people (it is CLEAR what a=b or (a is null and b is null) is doing)
o possibly get the wrong answer (what value do you nvl with - will that ALWAYS
be an invalid value for that column?  say you choose -1 and a year from now
someone puts -1 in there to indicate "something".  now you are hosed)

Code what you mean, mean what you code ;)

I would not use nvl(), too "risky".

      Rating: 4
could nvl be
      used instead of "or" / do u see any performance issue with
      this  February 20, 2003
Reviewer:
      A reader

February 21, 2003 -- Thanks for the question regarding "Java pool
      and tuning tips8", version 8.1.6

      You Asked
Is the Java pool part of the SGA?
How do we know if the default of 20M is not enough ?
What "v$_" views should we use ?
Can you give us some pointers to tuning the Java pool ?

Thanks

      and we said...
Java Pool is part of the SGA.

These parameters affect the tuning of the Aurora JVM in the
database:

1. SHARED_POOL_SIZE:
Default shared pool memory is used during the java class loading
and resolving process. During this process you the loader uses
approximately 8Kb for each class. The database initialization
process (invoked with INITJVM.SQL) load about 8000 classes and
for this reason you need to reset SHARED_POOL_SIZE to 50Mb at
least.

2. JAVA_POOL_SIZE:
Using the amount of JAVA_POOL_SIZE, the Aurora's memory manager
store all the Java states (except load and resolve as described
in point 1) during runtime execution. This portion of memory
stores also the shared in-memory representation for the Java
objects, and the Java objects information migrated to session at
the end-of-call. Different behaviour could be observed if you
are running in MTS mode.

3. JAVA_SOFT_SESSIONSPACE_LIMIT:
The user, with this parameter,  can specify a soft memory limit
for the for the sessions. Please note that when a session is
started, the memory specified for this parameter wil be
allocated for the session. When the session exceeds this size, a
trace will be generated  under USER_DUMP_DEST as warning.
The defult value is 1Mb.

4. JAVA_MAX_SESSIONSPACE_SIZE:
You can use this parameter, to limit the memory usage
for the Java objects during the user's session-duration.
When the session will exceed this value, it will get
an out-of-memory error. To avoid problems during normal
operations, the default value for this parameter is 4Gb.

The view v$sgastat is what you want to look at.  Basically -- if
you do not hit "out of memory" errors -- it is tuned.

The memory specified in shared_pool_size is consumed transiently
during use of loadjava.  The database initialization process
(i.e., executing initjvm.sql against a clean database, as
opposed to the installed seed database) requires
shared_pool_size to be set to 50MB as it loads the Java binaries
for approximately 8000 classes and resolves them.  Shared_pool
resource is also consumed as call-specifications are defined,
and as dynamically loaded Java classes are tracked at runtime.

All other Java state during runtime execution of Java is taken
from the amount allocated using java_pool_size.  This memory
includes the shared in-memory representation of Java method and
class definitions, as well as the Java objects that are migrated
to session space at end-of-call.  In the former case, the memory
cost is shared among all users.  In the latter case, in MTS
mode, you will need to adjust java_pool_size allocations based
on the actual amount of state held in static variables for each
session.

As in Sun's JDK, the Java compiler (i.e., the compiler, written
in Java, that compiles Java source code to Java binaries) is
known to be a voracious consumer of memory.  If you intend to
compile significant amounts of Java source on the JServer, or
you will be deploying complex EJB's (which use the Compiler on
the server), you should consider increasing java_pool_size.  The
default value for java_pool_size is 20MB.

Review &
      Followup

      Rating: 4
Good
      information on SGA as it relates to Java  June 04, 2001

Reviewer:  Harrison  from Haymarket, VA USA

This answer has a lot of information that (often)
seems hard to find, or lacking detail, in the Oracle
documentation (or maybe it is the repetition finally
makes us go "Ahhaaaahh").  Anyway it is important and
definitely worth knowing when you try to tune a database.
One thing that the install documentation could use is
lots of notes that their OS/orcl settings are enough to get
going and the writers could not have imagined how much
memory you actually have. One person's minimum is another's
maximum, so understanding what you might do if you have
the resources is better than knowing a single number.

      Rating: 4
When java_pool
      is used  February 21, 2003
Reviewer:
      A reader
Is java_pool used only when jvm is installed. And if jvm is not installed good
value for java_pool is 1m.

          Followup:

only for jvm stuff

you can set it to 0 in the current releases -- some 8i releases had a lower
limit of 1m.

February 21, 2003 Darrell -- Thanks for the question regarding
      "Explain plan, to believe or not to believe.", version 8.1.7.4

      You Asked
Tom,

I've got a comment and a question.  First and foremost, Hotsos last week was
awesome.  The concepts presented are already proving quite valuable.
This leads to my question.
Is explain plan a product of the parse and not necessarily of the execution?
This may have been discussed at Hotsos and got past me, but after
troubleshooting a query problem with event 10046, I'm of the belief that the
explain plan is just an intended course of action and doesn't necessarily always
reflect the work being done.

Here's details of my case leading to that belief:
( I've got a lot of information in here, so please excuse the length of this
one.  I have the trace analyzer output if you need or would like to see it.  I
only included select parts for space reasons.)

Our problem was with a report, which normally runs in 15 minutes, suddenly
taking over 3 hours.

//
I have a sales table:
(This table is range partitioned on the trans_date column by the month.
 Each partition contains an average of 4.5 million records and the table
contains records from August 1998 to the present.)
SQL> desc sales
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 TRANS_DATE                                NOT NULL DATE
 STORE                                     NOT NULL NUMBER(4)
 TRANS_NUMBER                              NOT NULL NUMBER(10)
 SEQ_NUMBER                                NOT NULL NUMBER(3)
 SKU                                       NOT NULL NUMBER(8)
 POS_TYPE                                           NUMBER(2)
 BUS_TIME                                           VARCHAR2(8)
 POST_DATE                                          DATE
 SALES_EMP1                                         NUMBER(9)
 SALES_EMP2                                         NUMBER(9)
 RETAIL_UNITS                                       NUMBER(9,2)
 RETAIL_NET_SALE                                    NUMBER(9,2)
 RETAIL_TOTAL_SALE                                  NUMBER(9,2)
 RETAIL_MD_CODE                                     NUMBER(2)
 RETAIL_MD_AMT                                      NUMBER(9,2)
 RETAIL_MD_CODE2                                    NUMBER(2)
 RETAIL_MD_AMT2                                     NUMBER(9,2)
 COST_NET_SALE                                      NUMBER(9,2)
 COST_MD_AMT                                        NUMBER(9,2)
 COST_PERM_MD_AMT                                   NUMBER(9,2)
 RETAIL_OVERING_UNDERING                            NUMBER(9,2)
 PIERCING_STATUS                                    VARCHAR2(3)
 JEWELRY_CLUB_NUMBER                                NUMBER(15)
 RETAIL_PERM_MD_AMT                                 NUMBER(9,2)

//
Indexes on this sales table: (all local)

SALES_JEWELRY_CLUB_IDX1 (NONUNIQUE) [jewelry_club_number]
SALES_POST_DATE_IDX (NONUNIQUE) [post_date]
SALES_SKU_IDX (NONUNIQUE) [sku]
SALES_STORE_IDX (NONUNIQUE) [store]
SALES_TRANS_DATE_IDX (NONUNIQUE) [trans_date]
SA_UNQ_IDX1 (UNIQUE) [trans_date store trans_number seq_number]

//
Text of the affected query:

SELECT
SKU.DEPT,
SKU.DEPT_NAME,
SKU.CLASS_NAME,
SKU.CLASS,
SKU.SUBCLASS,
SKU.SUBCLASS_NAME,
SKU.MAIN_SKU,
sum(LY_YTD_SALES.RETAIL_UNITS),
sum(LY_YTD_SALES.RETAIL_NET_SALE),
sum(LY_YTD_SALES.RETAIL_MD_AMT + LY_YTD_SALES.RETAIL_MD_AMT2),
sum(LY_YTD_SALES.RETAIL_NET_SALE - LY_YTD_SALES.COST_NET_SALE
        + LY_YTD_SALES.COST_MD_AMT  -  LY_YTD_SALES.COST_PERM_MD_AMT)
FROM
SKU,
CORP_CAL,
SALES  LY_YTD_SALES,
ORG_FLAT_INV
WHERE
( LY_YTD_SALES.SKU=SKU.SKU  )
AND  ( LY_YTD_SALES.STORE=ORG_FLAT_INV.STORE_NUM  )
AND  ( CORP_CAL.COMP_DATE=LY_YTD_SALES.TRANS_DATE  )
AND  ( CORP_CAL.DAY BETWEEN decode(to_date('09-02-2003 00:00:00'),
to_date('01-AUG-'||to_char(to_date('09-02-2003 00:00:00'),'YYYY')),
to_date('09-02-2003 00:00:00'),
DECODE(GREATEST(to_date('09-02-2003 00:00:00')-TO_DATE('01-AUG-'
||TO_CHAR(to_date('09-02-2003 00:00:00'),'YYYY')),0), 0,
TO_DATE('01-AUG-'||TO_CHAR(TO_CHAR(to_date('09-02-2003 00:00:00'),'YYYY')-1)),
TO_DATE('01-AUG-'||TO_CHAR(to_date('09-02-2003 00:00:00'),
'YYYY'))))AND TRUNC(to_date('09-02-2003 00:00:00'))  )
AND  (
ORG_FLAT_INV.DIVISION  =  60
AND  SKU.DEPT  IN  '1' )
GROUP BY
SKU.DEPT,
SKU.DEPT_NAME,
SKU.CLASS_NAME,
SKU.CLASS,
SKU.SUBCLASS,
SKU.SUBCLASS_NAME,
SKU.MAIN_SKU;

//
Unfortunately, that code is generated by an application, so I can't change it.

Here is the explain plan as shown in the trace from the day it was performing
poorly:
I'd like to call attention to the last line as it shows a range scan of
the sales_trans_date_idx which is one preferred way we want to get the data
because the
users input a date range covering a nine month span.

Explain Plan
---------------------------------------------------------------
..14 SELECT STATEMENT  (COST=3248 CARD=5546 BYTES=682158)
..13 .SORT (GROUP BY)  (COST=3248 CARD=5546 BYTES=682158)
..12 ..HASH JOIN  (COST=3127 CARD=5546 BYTES=682158)
...3 ...TABLE ACCESS (BY INDEX ROWID) OF 'DWMGR.SKU'  (COST=430 CARD=10371
BYTES=632631)
...2 ....BITMAP CONVERSION (TO ROWIDS)
...1 .....BITMAP INDEX (SINGLE VALUE) OF 'DWMGR.SKU_DEPT_BM'
..11 ...HASH JOIN  (COST=2690 CARD=57666 BYTES=3575292)
...4 ....TABLE ACCESS (FULL) OF 'DWMGR.ORG_FLAT_INV'  (COST=12 CARD=343
BYTES=2058)
..10 ....NESTED LOOPS  (COST=2677 CARD=430561 BYTES=24111416)
...6 .....TABLE ACCESS (BY INDEX ROWID) OF 'DWMGR.CORP_CAL'  (COST=1 CARD=7
BYTES=98)
...5 ......INDEX (RANGE SCAN) OF 'DWMGR.SYS_C00616' (UNIQUE)  (COST=2 CARD=7 )
...9 .....PARTITION RANGE (ITERATOR)
...8 ......TABLE ACCESS (BY LOCAL INDEX ROWID) OF 'DWMGR.SALES'  (COST=383
CARD=91647892 BYTES=3849211464)
...7 .......INDEX (RANGE SCAN) OF 'DWMGR.SALES_TRANS_DATE_IDX' (NON-UNIQUE)
(COST=244 CARD=91647892 )

//
Here's where it was fun and very useful to look at this using event 10046 and
Oracle's
new trace analyzer (a wonderful tool).
(This is still on the day of the problem.)

Table / index info provided by 10046:
OWNER.TABLE_NAME
...owner.index_name                                     num rows     blocks
sample last analyzed date
----------------------------------------------------- ---------- ----------
---------- -------------------
DWMGR.CORP_CAL.......................................       2556         20
 2556 2003-02-10 18:41:02
...dwmgr.corp_cal_idx3...............................       2556          9
 2556 2003-02-10 18:41:02
...dwmgr.corp_cal_idx4...............................       2556          6
 2556 2003-02-10 18:41:02
...dwmgr.idx1........................................       2556          8
 2556 2003-02-10 18:41:02
...dwmgr.sys_c00616..................................       2556          7
 2556 2003-02-10 18:41:02
DWMGR.ORG_FLAT_INV...................................       4105        189
 4105 2003-02-10 23:09:34
...dwmgr.org_flat_inv_div_bm.........................         12          1
   12 2003-02-10 23:09:34
...dwmgr.org_flat_inv_idx1...........................       4105          8
 4105 2003-02-10 23:09:34
...dwmgr.org_flat_inv_region_bm......................        197          5
  197 2003-02-10 23:09:34
DWMGR.SALES..........................................   91647892    1084672
22911973 2002-08-31 21:21:03
...dwmgr.sales_jewelry_club_idx1.....................   37949251      99075
    0 2003-02-11 17:22:17
...dwmgr.sales_post_date_idx.........................  115087306     331335
    0 2003-02-11 17:22:23
...dwmgr.sales_sku_idx...............................  121014551     300983
    0 2003-02-11 17:20:25
...dwmgr.sales_store_idx.............................  118866250     259778
    0 2003-02-11 17:21:17
...dwmgr.sales_trans_date_idx........................  115086662     304910
    0 2003-02-11 17:21:28
...dwmgr.sa_unq_idx1.................................  113075300     480909
    0 2003-02-11 17:22:08
DWMGR.SKU............................................     217779       7323
217779 2003-02-10 23:44:19
...dwmgr.sku_class_bm................................        174        192
  174 2003-02-10 23:44:19
...dwmgr.sku_dept_bm.................................         94        124
   94 2003-02-10 23:44:19
...dwmgr.sku_idx1....................................     217779        511
217779 2003-02-10 23:44:19
...dwmgr.sku_subclass_bm.............................        835        330
  835 2003-02-10 23:44:19

Details for wait event                                                Times
Count      Max.     Total    Blocks
'db file sequential read (single block index scan)'                  Waited Zero
Time      Wait    Waited  Accessed
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
dwmgr.sales.p_st_40..............................................     66624
41100      0.94    539.25     66624
dwmgr.sales.p_st_28..............................................     53536
31931      0.59    514.61     53536
dwmgr.sales.p_st_16..............................................     36780
20643      0.57    452.05     36780
dwmgr.sales.p_st_4...............................................     32890
18104      0.58    401.67     32890
dwmgr.sales.p_st_1...............................................     22694
11767      0.64    318.68     22694
dwmgr.sales.p_st_45..............................................     33186
20214      2.99    311.71     33186
dwmgr.sales.p_st_33..............................................     23646
13558      0.33    243.90     23646
dwmgr.sales.p_st_39..............................................     30572
19505      0.91    237.56     30572
dwmgr.sales.p_st_30..............................................     22369
12821      0.57    235.34     22369
dwmgr.sales.p_st_42..............................................     28436
17784      0.34    233.69     28436
dwmgr.sales.p_st_27..............................................     23030
13411      0.92    233.51     23030
dwmgr.sales.p_st_46..............................................     26696
16776      2.73    219.15     26696
dwmgr.sales.p_st_47..............................................     26740
16771      0.48    210.46     26740
dwmgr.sales.p_st_44..............................................     24524
15207      0.78    207.02     24524
dwmgr.sales.p_st_15..............................................     16812
9207      0.79    205.84     16812
dwmgr.sales.p_st_36..............................................     20599
11881      0.36    202.95     20599
dwmgr.sales.p_st_34..............................................     19629
11416      0.62    201.30     19629
dwmgr.sales.p_st_43..............................................     23641
14691      0.22    199.59     23641
dwmgr.sales.p_st_35..............................................     19097
11200      0.64    194.49     19097
dwmgr.sales.p_st_29..............................................     17190
9847      0.90    184.12     17190
dwmgr.sales.p_st_38..............................................     22850
14764      0.56    181.50     22850
dwmgr.sales.p_st_3...............................................     14954
8038      0.47    181.29     14954
dwmgr.sales.p_st_37..............................................     19023
11222      0.58    180.40     19023
dwmgr.sales.p_st_48..............................................     24965
16362      0.34    179.18     24965
dwmgr.sales.p_st_31..............................................     16946
9670      0.61    177.77     16946
dwmgr.sales.p_st_41..............................................     22353
14055      0.45    176.81     22353
dwmgr.sales.p_st_26..............................................     16489
9528      0.35    173.28     16489
dwmgr.sales.p_st_11..............................................     15755
8973      0.94    167.64     15755
dwmgr.sales.p_st_13..............................................     12975
6833      0.92    166.26     12975
dwmgr.sales.p_st_32..............................................     16185
9335      0.31    163.12     16185
dwmgr.sales.p_st_2...............................................     12331
6556      0.87    162.16     12331
dwmgr.sales.p_st_24..............................................     14299
7976      0.35    161.92     14299
dwmgr.sales.p_st_17..............................................     11972
6564      0.31    158.43     11972
dwmgr.sales.p_st_10..............................................     13208
7077      0.44    155.05     13208
dwmgr.sales.p_st_9...............................................     12018
6244      0.90    154.90     12018
dwmgr.sales.p_st_21..............................................     13004
7256      0.55    154.32     13004
dwmgr.sales.p_st_19..............................................     13478
7563      0.89    153.64     13478
dwmgr.sales.p_st_18..............................................     13510
7653      0.87    151.28     13510
dwmgr.sales.p_st_14..............................................     11896
6173      0.21    149.51     11896
dwmgr.sales.p_st_7...............................................     10791
5560      0.58    147.27     10791
dwmgr.sales.p_st_8...............................................      9767
5017      2.76    139.75      9767
dwmgr.sales.p_st_12..............................................     11255
5924      0.37    137.79     11255
dwmgr.sales.p_st_23..............................................     13400
7730      0.37    136.72     13400
dwmgr.sales.p_st_22..............................................     11953
6529      0.24    135.33     11953
dwmgr.sales.p_st_5...............................................     10576
5669      0.37    132.99     10576
dwmgr.sales.p_st_25..............................................     13713
7998      0.35    132.93     13713
dwmgr.sales.p_st_6...............................................     11014
5998      0.36    128.66     11014
dwmgr.sales.p_st_20..............................................      9792
5211      0.36    124.22      9792
dwmgr.sales.p_st_52..............................................      7057
5778      0.96     28.75      7057
dwmgr.sales.p_st_49..............................................      3583
2128      0.27     26.08      3583
dwmgr.sales.p_st_50..............................................      3580
2231      0.14     23.54      3580
dwmgr.sales.p_st_51..............................................      4477
3341      0.14     21.81      4477
dwmgr.sales_store_idx.p_st_52....................................      1237
 305      0.07     11.85      1237
dwmgr.sales.p_st_53..............................................      3762
3069      0.11     10.65      3762
dwmgr.sales_store_idx.p_st_51....................................       582
 145      0.06      5.89       582
dwmgr.sales_store_idx.p_st_45....................................       804
 455      0.25      5.21       804
dwmgr.sales_store_idx.p_st_53....................................       487
 148      0.13      4.44       487
dwmgr.sales.p_st_54..............................................      1264
1036      0.16      4.23      1264
dwmgr.sales_store_idx.p_st_49....................................       410
 114      0.04      4.06       410
dwmgr.sales_store_idx.p_st_40....................................      1227
 954      0.30      3.97      1227
dwmgr.sales_store_idx.p_st_50....................................       408
 119      0.03      3.84       408
dwmgr.sales_store_idx.p_st_28....................................      1151
 945      0.25      3.11      1151
dwmgr.sales_store_idx.p_st_44....................................       511
 281      0.03      2.99       511
dwmgr.sales_store_idx.p_st_22....................................       423
 273      0.25      2.48       423
dwmgr.sales_store_idx.p_st_18....................................       499
 350      0.25      2.33       499
dwmgr.sales_store_idx.p_st_27....................................       544
 394      0.30      2.27       544
dwmgr.sales_store_idx.p_st_33....................................       493
 359      0.28      2.24       493
dwmgr.sales_store_idx.p_st_42....................................       479
 335      0.19      2.24       479
dwmgr.sales_store_idx.p_st_29....................................       394
 253      0.09      2.17       394
dwmgr.sales_store_idx.p_st_14....................................       422
 272      0.06      2.16       422
dwmgr.sales_store_idx.p_st_12....................................       407
 268      0.29      2.15       407
dwmgr.sales_store_idx.p_st_38....................................       398
 260      0.06      2.15       398
dwmgr.sales_store_idx.p_st_34....................................       436
 292      0.27      2.14       436
dwmgr.sales_store_idx.p_st_9.....................................       457
 319      0.13      2.12       457
dwmgr.sales_store_idx.p_st_25....................................       409
 281      0.31      2.11       409
dwmgr.sales_store_idx.p_st_39....................................       552
 382      0.03      2.09       552
dwmgr.sales_store_idx.p_st_3.....................................       535
 389      0.09      2.08       535
dwmgr.sales_store_idx.p_st_35....................................       419
 269      0.04      2.05       419
dwmgr.sales_store_idx.p_st_54....................................       257
 105      0.05      2.02       257
dwmgr.sales_store_idx.p_st_4.....................................      1053
 889      0.03      2.00      1053
dwmgr.sales_store_idx.p_st_16....................................      1115
 965      0.04      1.94      1115
dwmgr.sales_store_idx.p_st_7.....................................       428
 290      0.04      1.93       428
dwmgr.sales_store_idx.p_st_8.....................................       399
 264      0.10      1.91       399
dwmgr.sales_store_idx.p_st_19....................................       482
 333      0.04      1.88       482
dwmgr.sales_store_idx.p_st_1.....................................       725
 565      0.03      1.83       725
dwmgr.sales_store_idx.p_st_32....................................       360
 231      0.05      1.82       360
dwmgr.sales_store_idx.p_st_17....................................       431
 299      0.19      1.79       431
dwmgr.sales_store_idx.p_st_36....................................       412
 281      0.04      1.78       412
dwmgr.sales_store_idx.p_st_2.....................................       448
 313      0.04      1.75       448
dwmgr.sales_store_idx.p_st_11....................................       525
 386      0.04      1.73       525
dwmgr.sales_store_idx.p_st_47....................................       395
 266      0.04      1.73       395
dwmgr.sales_store_idx.p_st_30....................................       491
 362      0.03      1.71       491
dwmgr.sales_store_idx.p_st_43....................................       374
 244      0.04      1.68       374
dwmgr.sales_store_idx.p_st_48....................................       392
 259      0.04      1.66       392
dwmgr.sales_store_idx.p_st_5.....................................       416
 292      0.04      1.66       416
dwmgr.sales_store_idx.p_st_10....................................       480
 356      0.04      1.65       480
dwmgr.sales_store_idx.p_st_15....................................       553
 425      0.05      1.63       553
dwmgr.sales_store_idx.p_st_41....................................       398
 272      0.04      1.63       398
dwmgr.sales_store_idx.p_st_31....................................       389
 265      0.04      1.61       389
dwmgr.sales_store_idx.p_st_37....................................       373
 244      0.03      1.59       373
dwmgr.sales_store_idx.p_st_6.....................................       444
 322      0.04      1.57       444
dwmgr.sales_store_idx.p_st_21....................................       503
 374      0.04      1.56       503
dwmgr.sales_store_idx.p_st_24....................................       448
 324      0.03      1.54       448
dwmgr.sales_store_idx.p_st_13....................................       450
 325      0.03      1.52       450
dwmgr.sales_store_idx.p_st_26....................................       444
 328      0.03      1.48       444
dwmgr.sales_store_idx.p_st_46....................................       388
 268      0.03      1.48       388
dwmgr.sales_store_idx.p_st_20....................................       392
 269      0.03      1.45       392
dwmgr.sales_store_idx.p_st_23....................................       432
 324      0.03      1.33       432
dwmgr.sku_dept_bm................................................         6
   1      0.02      0.07         6
dwmgr.org_flat_inv...............................................         1
   1      0.00      0.00         1
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
total............................................................   1020974
601499      2.99  10155.14   1020974

//
Now, the problem here is two fold:  1) We have no waits at all for the
sales_trans_date_idx, which from explain plan
 I believed we were using.  2) To get 7 months of data (7 partitions out of 54),
we had to read from every partition;
we weren't pruning.
Any occasion when I have a query using what appears to be the wrong index and my
statistics aren't up to date, I analyze.

So, after a dbms_stats.gather_table_stats with cascade, the stats looked like
this:

OWNER.TABLE_NAME
...owner.index_name                                     num rows     blocks
sample last analyzed date
----------------------------------------------------- ---------- ----------
---------- -------------------
DWMGR.CORP_CAL.......................................       2556         20
 2556 2003-02-12 18:43:36
...dwmgr.corp_cal_idx3...............................       2556          9
 2556 2003-02-12 18:43:36
...dwmgr.corp_cal_idx4...............................       2556          6
 2556 2003-02-12 18:43:36
...dwmgr.idx1........................................       2556          8
 2556 2003-02-12 18:43:36
...dwmgr.sys_c00616..................................       2556          7
 2556 2003-02-12 18:43:36
DWMGR.ORG_FLAT_INV...................................       4105        189
 4105 2003-02-12 23:16:01
...dwmgr.org_flat_inv_div_bm.........................         12          1
   12 2003-02-12 23:16:01
...dwmgr.org_flat_inv_idx1...........................       4105          8
 4105 2003-02-12 23:16:01
...dwmgr.org_flat_inv_region_bm......................        197          5
  197 2003-02-12 23:16:01
DWMGR.SALES..........................................   91647892    1084672
22911973 2002-08-31 21:21:03
...dwmgr.sales_jewelry_club_idx1.....................   32980635      89638
    0 2003-02-12 18:07:29
...dwmgr.sales_post_date_idx.........................  109850167     320229
    0 2003-02-12 18:07:33
...dwmgr.sales_sku_idx...............................  109850167     280445
    0 2003-02-12 18:07:37
...dwmgr.sales_store_idx.............................  109850167     254056
    0 2003-02-12 18:07:56
...dwmgr.sales_trans_date_idx........................  109850167     294919
    0 2003-02-12 18:08:00
...dwmgr.sa_unq_idx1.................................  109850167     484195
    0 2003-02-12 18:07:20
DWMGR.SKU............................................     217876       7323
217876 2003-02-12 23:52:21
...dwmgr.sku_class_bm................................        174        192
  174 2003-02-12 23:52:21
...dwmgr.sku_dept_bm.................................         94        124
   94 2003-02-12 23:52:21
...dwmgr.sku_idx1....................................     217876        511
217876 2003-02-12 23:52:21
...dwmgr.sku_subclass_bm.............................        835        330
  835 2003-02-12 23:52:21

And the waits...

Details for wait event                                                Times
Count      Max.     Total    Blocks
'db file sequential read (single block index scan)'                  Waited Zero
Time      Wait    Waited  Accessed
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
dwmgr.sales.p_st_40..............................................     74826
72855      0.09     31.79     74826
dwmgr.sales.p_st_41..............................................     20805
20147      0.10     10.59     20805
dwmgr.sales.p_st_42..............................................      9958
9644      0.60      5.77      9958
dwmgr.sales.p_st_39..............................................     26414
26066      1.44      5.50     26414
dwmgr.sales.p_st_36..............................................     23239
22884      0.06      4.45     23239
dwmgr.sales.p_st_37..............................................     19546
19296      0.06      3.20     19546
dwmgr.sales.p_st_38..............................................     18688
18440      0.06      2.92     18688
dwmgr.sales_trans_date_idx.p_st_40...............................     16539
16358      0.05      1.94     16539
dwmgr.sales_trans_date_idx.p_st_36...............................      5255
5189      0.03      0.72      5255
dwmgr.sales_trans_date_idx.p_st_37...............................      4384
4327      0.08      0.70      4384
dwmgr.sales_trans_date_idx.p_st_39...............................      5923
5869      0.03      0.56      5923
dwmgr.sales_trans_date_idx.p_st_41...............................      4642
4591      0.03      0.54      4642
dwmgr.sales_trans_date_idx.p_st_38...............................      4193
4143      0.02      0.53      4193
dwmgr.sales.p_st_29..............................................       546
 527      0.03      0.32       546
dwmgr.sales_trans_date_idx.p_st_42...............................      2203
2180      0.02      0.24      2203
dwmgr.sales_trans_date_idx.p_st_29...............................       121
 117      0.02      0.05       121
dwmgr.plan_table.................................................         1
   1      0.00      0.00         1
dwmgr.sku........................................................        15
  15      0.00      0.00        15
dwmgr.sku_dept_bm................................................         6
   6      0.00      0.00         6
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
total............................................................    237304
232655      1.44     69.82    237304

The good execution ran only 3 minutes including time to return data to the
screen.
This is down from the 3 hours on the bad day and even better than the
15 minutes it ran before.
Without the great information from hotsos last week, this situation would not
have been easy to evaluate or correct.

      and we said...
thanks for the comments on the hotsos conference.  The attendees were among the
smartest group of Oracle technical people I've seen assembled (meaning the
people that came in general really knew what they were doing).  It was cool to
see so many like minded people in one place -- it seemed each session was
building on each previous session -- we were all saying the same thing, just in
different ways, with different examples and case studies.  It was scary ;)

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5051676080968
and ctl-f for

autotrace lie

Yes, explain plan can "lie" too (autotrace uses explain plan).  Explain plan is
pretty good -- it generally (almost ALWAYS) shows you the plan that WOULD be
used to execute the query if you executed it right there and then.  There are
subtle nuances however, due to the way things work internally, that can cause
explain plan to come up with the wrong "plan".  cursor sharing, stored outlines,
new optimizer features -- can sometimes cause explain plan to come to the wrong
answer -- since it only does part of the parsing work (to get the plan).  The
only way to see the ACTUAL plan is to use sql_trace=true and see the stat
records.  They record HISTORY -- what happened.  Explain plan shows the FUTURE,
what is most likely to happen.

Here is the way to get the real plan for a query that executed in 9i -- use
v$sql_plan.  That is HISTORY (actually happened)

In 8i -- IF your environment is EXACTLY THE SAME (eg: sort_area_size,
cursor_sharing -etc) as the environment that ran the query then:

ops$tkyte@ORA920> alter session set events '10046 trace name context forever,
level 12';

Session altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          cursor c1 is
  3          select * from scott.emp, scott.dept;
  4  begin
  5          open c1;
  6          close c1;
  7  end;
  8  /

PL/SQL procedure successfully completed.

and tkprof will show you HISTORY:

SELECT *
from
 scott.emp, scott.dept

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          0          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 299     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  NESTED LOOPS
      0   TABLE ACCESS FULL DEPT
      0   TABLE ACCESS FULL EMP

Note that for MOST selects -- open will be fast -- it is the rare query that
does work during the open (but it can happen)....

But -- let me ask -- do you use table monitoring?  It may help prevent this --
Oracle will tell you when it believes the table is in need of analyzing
again....

Review &
      Followup

      Rating: 4
trace
      analyzer?  February 18, 2003
Reviewer:  Josh
       from Oregon
Oracle's
new trace analyzer ???

What is this and where to get? Check the usual suspects and can't locate a word
about it.

          Followup:

believe he is refering to the sql scripts to be found at  MetaLink Note:
224270.1.

      Rating:
      5
Partner  February 19, 2003
Reviewer:
      Darrell  from Irving, TX
Don't know if it was intentional, but you've become a business partner to many
DBAs and Developers worldwide.
Metalink and asktom sites are part of my daily casual reading (drives my wife
nuts).
Thanks Tom!

          Followup:

thank you, appreciate the feedback.

      Rating:
      5
Reply  February 19, 2003
Reviewer:
      Darrell  from Irving, TX
Also, yes my reference to trace analyzer is the one covered in metalink note
224270.1.
We're not yet using table monitoring.  It's overdue that we start testing with
that.

      Rating:
      4
  February 21, 2003
Reviewer:
      A reader
will your presentations be available for downlaod

          Followup:

http://asktom.oracle.com/~tkyte/
I added them to the list of stuff at the bottom.

February 21, 2003 Nikhil -- Thanks for the question regarding
      "Joins", version 9.0.2

      You Asked
hi tom ....

  its difficult to catch u .... so putting two questions simultaneosly... both
must be very trivial to u.

1] I am using external tables in my database ...When i analyze the whole
schema... how do i exclude the external tables. B'coz external tables , i guess,
need not be and cant be analyzed.

   exec dbms_stats.analyze_schema('USER','COMPUTE);

 how do i analyze the whole schema without including the external tables.

2. This is a query that i have written.

   My requrirement is that i have to combine four rows into one... only one
column changes in the four rows

 eg     138 ,002,555, 01
        138 ,002,555, 02
        138 ,002,555, 03
        138 ,002,555, 04

  i need the result as follows in one row

      138,002,555, 01,02,03,04

  so i am self joining the table .... and i want the result even     if     one
of the rows is missing... so i have used full join.

  i feel .. there must be a better way to write this query .. can u please help
me in that.

    SELECT * FROM
       (
        SELECT
            pid_vpk_country_code,
            pid_vpk_company_code,
            pid_vpk_docseq_nbr,
                pid_vnm_casenbr,
                pid_vnm_price,
            pid_npk_detailkey,
            pid_vpk_product_code,
            pid_vpk_selling_code,
            pid_vpk_document_nbr,
            pid_npk_routekey,
            pid_vpk_cyclenbr,
            pid_vnm_routeid,
            pid_vnm_document_dt,
            pid_vnm_case_total damaged_case_total
     FROM
        t_phyinv_dtl_uhh
       WHERE
        pid_vnm_transtype = '07'--damaged
      )
FULL JOIN
      (
        SELECT
            pid_vpk_country_code,
            pid_vpk_company_code,
            pid_vpk_docseq_nbr,
               pid_vnm_casenbr,
                pid_vnm_price,
            pid_npk_detailkey,
            pid_vpk_product_code,
            pid_vpk_selling_code,
            pid_vpk_document_nbr,
            pid_npk_routekey,
            pid_vpk_cyclenbr,
            pid_vnm_routeid,
            pid_vnm_document_dt,
            pid_vnm_case_total stales_case_total
         FROM
        t_phyinv_dtl_uhh
        WHERE
          pid_vnm_transtype = '10'--stales
      )
      USING(
            pid_vpk_country_code,
            pid_vpk_company_code,
            pid_vpk_docseq_nbr,
          pid_vnm_casenbr,
          pid_vnm_price,
            pid_npk_detailkey,
            pid_vpk_product_code,
            pid_vpk_selling_code,
            pid_vpk_document_nbr,
            pid_npk_routekey,
            pid_vpk_cyclenbr,
            pid_vnm_routeid
           )
FULL JOIN
 (
    SELECT
         pid_vpk_country_code,
            pid_vpk_company_code,
            pid_vpk_docseq_nbr,
          pid_vnm_casenbr,
          pid_vnm_price,
            pid_npk_detailkey,
            pid_vpk_product_code,
            pid_vpk_selling_code,
            pid_vpk_document_nbr,
            pid_npk_routekey,
            pid_vpk_cyclenbr,
            pid_vnm_routeid,
            pid_vnm_document_dt,
         pid_vnm_variance  damage_variance
      FROM
        t_phyinv_dtl_uhh
        WHERE
          pid_vnm_transtype = '18'--difference in damage returns
 )
 USING(
            pid_vpk_country_code,
            pid_vpk_company_code,
            pid_vpk_docseq_nbr,
          pid_vnm_casenbr,
          pid_vnm_price,
            pid_npk_detailkey,
            pid_vpk_product_code,
            pid_vpk_selling_code,
            pid_vpk_document_nbr,
            pid_npk_routekey,
            pid_vpk_cyclenbr,
            pid_vnm_routeid
           )
FULL JOIN
 (
      SELECT
         pid_vpk_country_code,
            pid_vpk_company_code,
            pid_vpk_docseq_nbr,
          pid_vnm_casenbr,
          pid_vnm_price,
            pid_npk_detailkey,
            pid_vpk_product_code,
            pid_vpk_selling_code,
            pid_vpk_document_nbr,
            pid_npk_routekey,
            pid_vpk_cyclenbr,
            pid_vnm_routeid,
            pid_vnm_document_dt,
         pid_vnm_variance  stales_variance
        FROM
        t_phyinv_dtl_uhh
        WHERE
          pid_vnm_transtype = '17'--difference in stales returns
 )
 USING(
            pid_vpk_country_code,
            pid_vpk_company_code,
            pid_vpk_docseq_nbr,
          pid_vnm_casenbr,
          pid_vnm_price,
            pid_npk_detailkey,
            pid_vpk_product_code,
            pid_vpk_selling_code,
            pid_vpk_document_nbr,
            pid_npk_routekey,
            pid_vpk_cyclenbr,
            pid_vnm_routeid
)

  Also the above query works fine on windows machine, but on unix   server it
gives me an internal error ...can u solve this mess for me please.

  thanx in advance.
  Nikhil

      and we said...
1) external tables can and if you plan on doing more then single table queries
against them -- should be analyzed.

In order to not analyze them, you would not use GATHER_SCHEMA_STATS (which is
what I assume you meant), you would gather table stats on those tables you want
to.

2) to do a pivot like that -- you should always just:

select pid_vpk_country_code,
            pid_vpk_company_code,
            pid_vpk_docseq_nbr,
          pid_vnm_casenbr,
          pid_vnm_price,
            pid_npk_detailkey,
            pid_vpk_product_code,
            pid_vpk_selling_code,
            pid_vpk_document_nbr,
            pid_npk_routekey,
            pid_vpk_cyclenbr,
            pid_vnm_routeid,
            pid_vnm_document_dt,
       max(decode( pid_vnm_transtype, '07', pid_vnm_case_total, null )),
       max(decode( pid_vnm_transtype, '10', pid_vnm_case_total, null )),
       max(decode( pid_vnm_transtype, '18', pid_vnm_case_total, null )),
       max(decode( pid_vnm_transtype, '17', pid_vnm_case_total, null ))
  from t_phyinv_dtl_uhh
 where pid_vnm_transtype in ( '07', '10', '18', '17' )
group by pid_vpk_country_code,
            pid_vpk_company_code,
            pid_vpk_docseq_nbr,
          pid_vnm_casenbr,
          pid_vnm_price,
            pid_npk_detailkey,
            pid_vpk_product_code,
            pid_vpk_selling_code,
            pid_vpk_document_nbr,
            pid_npk_routekey,
            pid_vpk_cyclenbr,
            pid_vnm_routeid,
            pid_vnm_document_dt;

Review &
      Followup

      Rating:
      3
  February 19, 2003
Reviewer:  Paul
      Summit
There  seems to be ALOT of people believing
you should not analyze external tables!

Sadly it also seems as though there are still
plenty of people with "broken keyboards"

      Rating:
      4
Mike  February 20, 2003
Reviewer:
      A reader
 "max(decode( ..."

Could you explain the MAX there for?

          Followup:

sure, lets say you have a query like:

scott@ORA920> select deptno, job, count(*) from emp group by deptno, job
  2  /

    DEPTNO JOB         COUNT(*)
---------- --------- ----------
        10 CLERK              1
        10 MANAGER            1
        10 PRESIDENT          1
        20 CLERK              2
        20 ANALYST            2
        20 MANAGER            1
        30 CLERK              1
        30 MANAGER            1
        30 SALESMAN           4

9 rows selected.

but what you really wanted was DEPTNO to be a column (dept10, dept20, dept30)
and just have jobs going down the page (pivot).  So, you want an output like:

JOB       MAX(DEPT10) MAX(DEPT20) MAX(DEPT30)
--------- ----------- ----------- -----------
ANALYST                         2
CLERK               1           2           1
MANAGER             1           1           1
PRESIDENT           1
SALESMAN                                    4

well, we first start by taking the counts and putting the "across" the page:

scott@ORA920>
scott@ORA920> select job,
  2         decode( deptno, 10, count(*), NULL ) dept10,
  3         decode( deptno, 20, count(*), NULL ) dept20,
  4         decode( deptno, 30, count(*), NULL ) dept30
  5    from emp
  6   group by job, deptno
  7   order by job
  8  /

JOB           DEPT10     DEPT20     DEPT30
--------- ---------- ---------- ----------
ANALYST                       2
CLERK              1
CLERK                         2
CLERK                                    1
MANAGER            1
MANAGER                       1
MANAGER                                  1
PRESIDENT          1
SALESMAN                                 4

9 rows selected.

Notice that there are 3 clerk rows right -- one each for each of the deptnos.
We want one clerk row though -- need to "squash them down".  Due to the fact
that JOB/DEPTNO is our "primary key" (unique) in this query -- we know that
across the 3 rows of clerks -- there is only ONE value in the dept10 column
(cannot be more then 1 -- job+deptno is unique).  MAX() can be used to pick this
off since MAX() ignores nulls.  So, we group by job now:

scott@ORA920>
scott@ORA920> select job, max(dept10), max(dept20), max(dept30)
  2    from (
  3  select job,
  4         decode( deptno, 10, count(*), NULL ) dept10,
  5         decode( deptno, 20, count(*), NULL ) dept20,
  6         decode( deptno, 30, count(*), NULL ) dept30
  7    from emp
  8   group by job, deptno
  9         )
 10   group by job
 11  /

JOB       MAX(DEPT10) MAX(DEPT20) MAX(DEPT30)
--------- ----------- ----------- -----------
ANALYST                         2
CLERK               1           2           1
MANAGER             1           1           1
PRESIDENT           1
SALESMAN                                    4

and there you go -- a pivot.

      Rating: 5
How to do if
      dept is dynamic  February 20, 2003
Reviewer:
      Sri  from NJ
Tom,

  Simply superb.

  I have a similar requirement.  But my problem is that I don't know how many
departments are there?  I mean, it is dynamic.  Still, can I have a similar
result?

  Thank you.

          Followup:

If you have my book "expert one on one Oracle", in the chapter on analytic
functions -- I develop a stored procedure that returns a ref cursor that will
pivot any result set by any dimension.

Requires a two step -- query to get the list of distinct values that are cross
record and need to go in record (go from rows to columns) and then a query that
used that information to figure out what columns it really wanted to have.

So yes you can -- you need to build the query dynamically (since the number of
columns changes).

      Rating: 4
can a sum be
      used  February 20, 2003
Reviewer:  umesh
       from bangalore , India
select job, sum(decode(deptno,10,1) ) ,
sum(decode(deptno,20,1) ) ,
sum(decode(deptno,30,1) )
from emp
group by job

          Followup:

obviously -- for numbers.  I use max() as it works with numbers, dates,
strings....

      Rating:
      5
What?  February 21, 2003
Reviewer:
      J  from ma
stepping through you demo:
select job,
          decode( deptno, 10, count(*), NULL ) dept10,
          decode( deptno, 20, count(*), NULL ) dept20,
          decode( deptno, 30, count(*), NULL ) dept30
     from emp
    group by job, deptno
/
(forgot the order by)
It wasn't ordered.
I thought Group by implied order?
Did something change? I'm sure we've relied on this
in the past.

          Followup:

If you did -- you have a bug.

Group by never had to sort.

The ONLY way you can expect rows to come to you in a certain order is to -- well
-- order by.

Group by might do a BINARY SORT sometimes -- which could result in a different
sort order then Order by would  as well!

Group by is not a sort.
Order by is a sort.

see

http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=36886d96.28433695%40192.86.155.100
for some examples from a while ago ;)

February 21, 2003 Suresh -- Thanks for the question regarding
      "Oracle version", version 8i

      You Asked
Hi Tom,
   I have a computer at home with Windows Millenium Edition on it. I would like
to install Oracle on this. Please let me know which one is ideal, Oracle 8i
Personal or enterprise edition ?

      and we said...
Neither, currently (as of 13-apr-2001) only version 8.0.6 PERSONAL is  supported
on that consumer oriented operating system.

We support the following different windows operating systems:

Windows 95
Windows 98
Windows NT
Windows 2k

with 8i.
Review
      & Followup

      Rating: 5
Too
      good  April 12, 2001
Reviewer:  Suresh
       from NewYork
Tom,
  You are doing a very good job. Thanks so much.

      Rating: 5
Oracle 8i
      Personal Edition Installation  April 17, 2001

Reviewer:  Ravi  from India
Tom, you are doing an excellent job in providing useful tips and
troubleshooting. Keep up the good work.

      Rating: 4
Good
      JOB  October 03, 2001
Reviewer:  Lakshmi
      nathan  from Chennai
You are really doing a Great job.

      Rating:
      5
  February 20, 2003
Reviewer:
      A reader
Hi Tom,

 Our DBA is planning to upgrade our database from
Oracle 8.1.6.1.0 to Oracle 8.1.7.3 .

 Is Oracle 8.1.7.3 supported by Oracle ?

 Please respond early.

 Thanks,

          Followup:

8174 is generally the terminal (supported) release of Oracle but it varies by
OS.  Open an iTar with support and they'll tell you what you should be on given
your OS

      Rating:
      5
  February 21, 2003
Reviewer:
      A reader
Hi Tom,

Thanks for the reply,

We have Sun Solaris 2.6 OS.

So I am assuming Oracle 8.1.7.4 is Supported by Oracle

Thanks,

          Followup:

well, you would contact support to get the real information as to when support
terminates.  8174 is currently supported as of the day I typed this in.

      Rating: 4
Most popular
      version?  February 21, 2003
Reviewer:  Doug
       from Salt Lake City, Utah
It would be of interest to know the what the mix of Oracle licenses is. That is,
what are the number of licenses by version and by OS platform. Any idea? or is
that somewhat confidential info?

          Followup:

cannot really comment ;)

February 22, 2003 Prince -- Thanks for the question regarding
      "updatable view", version 8.1.7.4

      You Asked
--This is updatable
CREATE OR REPLACE VIEW V_myView AS
SELECT
                A.CNY#,
                A.RECORD#,
                A.STATUS,
                A.TIMEUnit
FROM    myTable A
WHERE   (A.CNY#, nvl(A.LOCATION#,0)) IN
                        (       SELECT  SB.CNY#, SB.LOCATIONKEY
                                FROM    mySlb SB, myUSERPROF U
                                WHERE   SB.SESSIONKEY =
sys_context('userenv','SESSION_USER')
                                AND     SB.CNY#       = U.CNY#
                                AND     SB.SESSIONKEY = U.SESSION#
                        )
/

--This view is not updatable. All the columns from all_updatable_columns show
they are not updatable!

Can I rewrite the above with join, and still be able to update/insert the view?

CREATE OR REPLACE VIEW V_myView AS
SELECT
                A.CNY#,
                A.RECORD#,
                A.STATUS,
                A.TIMEUnit
FROM    myTable A, mySlb slb, myUSERPROF u1
where   a.cny# = slb.cny#
and     nvl(a.location#,0) = slb.locationkey
and     slb.SESSIONKEY = sys_context('userenv','SESSION_USER')
and     slb.CNY#       = U1.CNY#
and     slb.SESSIONKEY = U1.SESSION# ;

Note, I tried without the NVL function, as well.

Thanks.

      and we said...
Well, it has to do with KEY Preserved tables, see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:548422757486
to read about that concept.

Basically -- in the first view -- it understands that each row in myTable will
(can) appear in the view AT MOST ONCE.  A given row in myTable will be selected
at most ONCE in the view.  (eg: if you join EMP to DEPT and given that a primary
key exists on DEPTNO in DEPT, you know that each row in EMP will be returned AT
MOST once.  EMP is "key preserved".  DEPT on the other hand -- well, each row in
DEPT might be returned 0, 1 or more times -- DEPT is not key preserved).

We only allow modifications to objects in the key preserved table.

Using the IN assures us (because there is only ONE table in the FROM clause of
the view) that the rows are in fact key preserved.

Moving onto your other one -- if the right keys are in place AND we drop the
NVL() which clouds the issue -- the view can be key preserved.  Consider:

ops$tkyte@ORA920.US.ORACLE.COM> create table myTable
  2  ( cny# int, record# int, status int, timeUnit int, location# int );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create table mySlb ( cny# int, locationkey int,
sessionkey int );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create table myuserprof ( cny# int, session# int
);

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> CREATE OR REPLACE VIEW V_myView AS
  2  SELECT
  3                  A.CNY#,
  4                  A.RECORD#,
  5                  A.STATUS,
  6                  A.TIMEUnit
  7  FROM       myTable A, mySlb slb, myUSERPROF u1
  8  where      a.cny# = slb.cny#
  9  and        nvl(a.location#,0) = slb.locationkey
 10  and        slb.SESSIONKEY = sys_context('userenv','SESSION_USER')
 11  and        slb.CNY#            = U1.CNY#
 12  and        slb.SESSIONKEY = U1.SESSION# ;

View created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select column_name,UPDATABLE,
  2          insertable,deletable
  3    from user_updatable_columns
  4   where table_name = 'V_MYVIEW'
  5  /

COLUMN_NAME                    UPD INS DEL
------------------------------ --- --- ---
CNY#                           NO  NO  NO
RECORD#                        NO  NO  NO
STATUS                         NO  NO  NO
TIMEUNIT                       NO  NO  NO

that is what you are seeing now...

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> alter table myslb
  2  add constraint myslb_pk primary key(cny#,locationkey);

Table altered.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> alter table myuserprof
  2  add constraint myuserprof_pk primary key(cny#,session#);

Table altered.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> CREATE OR REPLACE VIEW V_myView AS
  2  SELECT
  3                  A.CNY#,
  4                  A.RECORD#,
  5                  A.STATUS,
  6                  A.TIMEUnit
  7  FROM       myTable A, mySlb slb, myUSERPROF u1
  8  where      a.cny# = slb.cny#
  9  and        nvl(a.location#,0) = slb.locationkey
 10  and        slb.SESSIONKEY = sys_context('userenv','SESSION_USER')
 11  and        slb.CNY#            = U1.CNY#
 12  and        slb.SESSIONKEY = U1.SESSION# ;

View created.

ops$tkyte@ORA920.US.ORACLE.COM> /

View created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select column_name,UPDATABLE,
  2          insertable,deletable
  3    from user_updatable_columns
  4   where table_name = 'V_MYVIEW'
  5  /

COLUMN_NAME                    UPD INS DEL
------------------------------ --- --- ---
CNY#                           NO  NO  NO
RECORD#                        NO  NO  NO
STATUS                         NO  NO  NO
TIMEUNIT                       NO  NO  NO

same thing as before, it is because of the NVL on the table we are trying to
keypreserve

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> CREATE OR REPLACE VIEW V_myView AS
  2  SELECT
  3                  A.CNY#,
  4                  A.RECORD#,
  5                  A.STATUS,
  6                  A.TIMEUnit
  7  FROM       myTable A, mySlb slb, myUSERPROF u1
  8  where      a.cny# = slb.cny#
  9  and        a.location# = slb.locationkey
 10  -- and     nvl(a.location#,0) = slb.locationkey
 11  and        slb.SESSIONKEY = sys_context('userenv','SESSION_USER')
 12  and        slb.CNY#            = U1.CNY#
 13  and        slb.SESSIONKEY = U1.SESSION# ;

View created.

ops$tkyte@ORA920.US.ORACLE.COM> /

View created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select column_name,UPDATABLE,
  2          insertable,deletable
  3    from user_updatable_columns
  4   where table_name = 'V_MYVIEW'
  5  /

COLUMN_NAME                    UPD INS DEL
------------------------------ --- --- ---
CNY#                           YES YES YES
RECORD#                        YES YES YES
STATUS                         YES YES YES
TIMEUNIT                       YES YES YES

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

and there you go -- no it is the same as your IN view

Is there another workaround?  Yes, namely INSTEAD of triggers.  You can code the
logic behind the INSERT yourself.  Consider:

ops$tkyte@ORA920.US.ORACLE.COM> CREATE OR REPLACE VIEW V_myView AS
  2  SELECT
  3                  A.CNY#,
  4                  A.RECORD#,
  5                  A.STATUS,
  6                  A.TIMEUnit
  7  FROM       myTable A, mySlb slb, myUSERPROF u1
  8  where      a.cny# = slb.cny#
  9  and        nvl(a.location#,0) = slb.locationkey
 10  and        slb.SESSIONKEY = sys_context('userenv','SESSION_USER')
 11  and        slb.CNY#            = U1.CNY#
 12  and        slb.SESSIONKEY = U1.SESSION# ;

View created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select column_name,UPDATABLE,
  2          insertable,deletable
  3    from user_updatable_columns
  4   where table_name = 'V_MYVIEW'
  5  /

COLUMN_NAME                    UPD INS DEL
------------------------------ --- --- ---
CNY#                           NO  YES NO
RECORD#                        NO  YES NO
STATUS                         NO  YES NO
TIMEUNIT                       NO  YES NO

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> insert into v_myView values ( 1,1,1,1 );
insert into v_myView values ( 1,1,1,1 )
            *
ERROR at line 1:
ORA-01732: data manipulation operation not legal on this view

ops$tkyte@ORA920.US.ORACLE.COM> create or replace trigger v_myView_IOI
  2  instead of insert on v_myView
  3  begin
  4     insert into myTable ( cny#, record#, status, timeUnit )
  5     values ( :new.cny#, :new.record#, :new.status, :new.timeUnit );
  6  end;
  7  /

Trigger created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> insert into v_myView values ( 1,1,1,1 );

1 row created.

Review &
      Followup

      Rating: 5
Excellent
      explantion with examples. Not insertable view with UQ key and NVL in
      qry.  August 31, 2002
Reviewer:  Prince,

Thanks Tom, for the detailed explanation.

I am aware of the instead of trigger and  thought of getting it done straight.

The question, I had in mind was though both queries are the same, why oracle
couln't find the joined view is key preserved. But, by definition the joining
tables (other than the key preserved one) should have unique/pk

BTW, if I convert the join/in with exists, it works.

*****
Aslo, at home, I am testing this on 9.2.0.1. But some reason, I don't get the
same result you got.

SQL> create table myTable ( cny# int, record# int, status int, timeUnit int,
location# int );

Table created.

SQL> create table mySlb ( cny# int, locationkey int, sessionkey int );

Table created.

SQL> create table myuserprof ( cny# int, session# int );

Table created.

SQL>
SQL> alter table myslb add constraint myslb_pk primary key(cny#,locationkey);

Table altered.

SQL> alter table myuserprof add constraint myuserprof_pk primary
key(cny#,session#);

Table altered.

SQL>
SQL>
SQL> CREATE OR REPLACE VIEW V_myView AS
  2  SELECT A.CNY#,A.RECORD#, A.STATUS, A.TIMEUnit
  3  FROM myTable A, mySlb slb, myUSERPROF u1
  4  where a.cny# = slb.cny#
  5  and nvl(a.location#,0) = slb.locationkey
  6  and slb.SESSIONKEY = sys_context('userenv','SESSION_USER')
  7  and slb.CNY# = U1.CNY#
  8  and slb.SESSIONKEY = U1.SESSION# ;

View created.

SQL> select column_name,UPDATABLE,
  2            insertable,deletable
  3      from user_updatable_columns
  4      where table_name = 'V_MYVIEW';

COLUMN_NAME                    UPD INS DEL
------------------------------ --- --- ---
CNY#                           NO  NO  NO
RECORD#                        NO  NO  NO
STATUS                         NO  NO  NO
TIMEUNIT                       NO  NO  NO

SQL>

Note, You have the view as INSERTABLE, though not UPDATABLE/DELETABLE. Mine
shows, no DMLs allowed. I am wondering why is it so? But the final result seem
to  be the same in both cases. You get ORA-01732 when trying to insert.

Have I missed anything?

*****

          Followup:

Mine didn't show insertable - it is the NVL() on the tables column that is
breaking the "key preserved".

Here is a simplier rule for you:

if the FROM clause in the view contains a single table, it is modifiable.  That
is why the IN/Where exists works, there is but ONE TABLE in the view (forget
subqueries, they don't count).  By definition it must be key preserved.

When there is more then one table, you must be joining to primary/unique
constraints on the n-1 tables and you cannot apply any functions to the tables
columns you are trying to key preserve.

So, if you change line 5 above to not have the NVL(), it'll be updatable.

      Rating: 4
My question
      is.  August 31, 2002
Reviewer:  Prince.

Thanks Tom,

I understand that point. But my question is

-------------------------------------------------------
ops$tkyte@ORA920.US.ORACLE.COM> CREATE OR REPLACE VIEW V_myView AS
  2  SELECT
  3                  A.CNY#,
  4                  A.RECORD#,
  5                  A.STATUS,
  6                  A.TIMEUnit
  7  FROM       myTable A, mySlb slb, myUSERPROF u1
  8  where      a.cny# = slb.cny#
  9  and        nvl(a.location#,0) = slb.locationkey
 10  and        slb.SESSIONKEY = sys_context('userenv','SESSION_USER')
 11  and        slb.CNY#            = U1.CNY#
 12  and        slb.SESSIONKEY = U1.SESSION# ;

View created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select column_name,UPDATABLE,
  2          insertable,deletable
  3    from user_updatable_columns
  4   where table_name = 'V_MYVIEW'
  5  /

COLUMN_NAME                    UPD INS DEL
------------------------------ --- --- ---
CNY#                           NO  YES NO
RECORD#                        NO  YES NO
STATUS                         NO  YES NO
TIMEUNIT                       NO  YES NO
---------------------------------------------------------

The above is what you got with the PKs defined. But When I did the same, I get
the following.

See, both of us have the NVL.

==========================================================
CREATE OR REPLACE VIEW V_myView AS
SELECT A.CNY#,A.RECORD#, A.STATUS, A.TIMEUnit
FROM myTable A, mySlb slb, myUSERPROF u1
where a.cny# = slb.cny#
and nvl(a.location#,0) = slb.locationkey
and slb.SESSIONKEY = sys_context('userenv','SESSION_USER')
and slb.CNY# = U1.CNY#
and slb.SESSIONKEY = U1.SESSION# ;

SQL> select column_name,UPDATABLE,
  2         insertable,deletable
  3  from user_updatable_columns
  4  where table_name = 'V_MYVIEW'
  5  /

COLUMN_NAME                    UPD INS DEL
------------------------------ --- --- ---
CNY#                           NO  NO  NO
RECORD#                        NO  NO  NO
STATUS                         NO  NO  NO
TIMEUNIT                       NO  NO  NO
=========================================================

          Followup:

Interesting, looks like a bug in the data in the view -- it is definitely NOT
insertable (as my example shows).  didn't notice it the first time.

I can sort of reproduce it on my system.  If I drop my account and start fresh
-- I cannot.  Must be some cached information that is mucking it up.

In any case -- with NVL, not insertable (without instead of trigger)

      Rating: 4
Just one more
      question.  August 31, 2002
Reviewer:  Prince

I will do a bench mark with logon trigger.

But, mean time have you noticed much difference in the performance on
insert/update/delete trigger on the view?

Thanks,

          Followup:

The view with the IN will definitely perform better then the join view in this
case.

anytime, ANYTIME, you can avoid procedural code -- do it. This seems like a
clear cut choice -- since the two are 100% equivalent result wise but
semanitically different as far as we are concerned, why would you even consider
the join view if your goal is modification?

      Rating: 4
I am sorry! I
      meant "after insert trigger"  August 31, 2002

Reviewer:  Prince

      Rating: 4
again got it
      wrong. It is "INSTEAD of trigger"  August 31, 2002

Reviewer:  Prince

          Followup:

I knew what you meant ;)

      Rating: 5
A similar view
      was used in another complex query and was taking lot of
      time  August 31, 2002
Reviewer:  Prince.

I have a similar (infact the same query with more columns and different table
name) and was used in another complex query.

It was taking lot of time to execute (49 seconds). After I just changed the view
definition's in with EXISTS (there was absolutely no other changes), the complex
query was taking less than a second.

Thats the reason, I wanted to test different scenarios.

Even, the select same as,

"Select * from v_myview_withIN where cny# = ?" was taking considerably more time
than (50 to 60%)
"select * from v_myview_withEsists".

Yes, I would prefer to keep it simple without going thru the trigger.

Thanks again.

          Followup:

There is a huge difference between IN and EXISTs.

there should be no difference between IN and JOIN as an IN is a JOIN.

      Rating: 5
Updateable
      View: Different behaviour in Production Database and Development
      Database  February 20, 2003
Reviewer:
      Yogeeraj  from Mauritius
Hello,

Please allow me to seek some guidance on how to troubleshoot this strange
problem where my updateable view is not behaving on my development database and
production database alike. thank you in advance

Best Regards
Yogeeraj

Below my homework:
=================================================================================
=======================================
swprod@PROD.YD.MU> CREATE OR REPLACE VIEW V_EMT100_yd
AS (select emt100.emchcod, emt100.ordnum, emt100.gmtseq, emt100.pancod,
 emt100.lcncod, emt100.mstatus, ort100.sshpdat
   from emt100, ort100
   where emt100.ordnum = ort100.ordnum);

View created.

Elapsed: 00:00:00.13
swprod@PROD.YD.MU>
swprod@PROD.YD.MU> update v_emt100_yd
set mstatus = 'Y' where ordnum = '000409C';
set mstatus = 'Y' where ordnum = '000409C'
    *
ERROR at line 2:
ORA-01779: cannot modify a column which maps to a non key-preserved table

Elapsed: 00:00:00.03
swprod@PROD.YD.MU> @connect swprod@dev
Enter password:
Connected.
swprod@DEV.YD.MU> CREATE OR REPLACE VIEW V_EMT100_yd
AS (select emt100.emchcod, emt100.ordnum, emt100.gmtseq, emt100.pancod,
 emt100.lcncod, ort100.sshpdat, mstatus
   from emt100, ort100
   where emt100.ordnum = ort100.ordnum);

View created.

Elapsed: 00:00:00.05
swprod@DEV.YD.MU> update v_emt100_yd
set mstatus = 'Y' where ordnum = '000409C';

9 rows updated.

Elapsed: 00:00:00.07
swprod@DEV.YD.MU> rollback;

Rollback complete.

Elapsed: 00:00:00.01
swprod@DEV.YD.MU>

Data in both in my Development database (dev.yd.mu) and Production Database are
the same (prod.yd.mu).
==========================================================================

swprod@DEV.YD.MU> select ordnum, sshpdat
  2  from ort100
  3  where ordnum='000409C';

ORDNUM   SSHPDAT
________ __________
000409C  15/03/2001

Elapsed: 00:00:00.01
swprod@DEV.YD.MU> select emchcod, ordnum, gmtseq, pancod, lcncod, mstatus
  2  from emt100
  3  where ordnum='000409C';

EMCH ORDNUM   G PAN LCN M
____ ________ _ ___ ___ _
PT04 000409C  B FTR NPX Y
PB01 000409C  B FTL NPX Y
PB01 000409C  B FTR NPX Y
PT04 000409C  B FTL NPX Y
PB01 000409C  A FT  NPX Y
PB01 000409C  A FTL NPX Y
PB01 000409C  A FTR NPX Y
PB03 000409C  B FTL NPX Y
PT01 000409C  A FT  NPX Y

9 rows selected.

Elapsed: 00:00:00.03
swprod@DEV.YD.MU>

swprod@DEV.YD.MU> @connect swprod@prod
Enter password:
Connected.
swprod@PROD.YD.MU> select ordnum, sshpdat
from ort100
where ordnum='000409C';

ORDNUM   SSHPDAT
________ __________
000409C  15/03/2001

Elapsed: 00:00:00.00
swprod@PROD.YD.MU> select emchcod, ordnum, gmtseq, pancod, lcncod, mstatus
from emt100
where ordnum='000409C';

EMCH ORDNUM   G PAN LCN M
____ ________ _ ___ ___ _
PT04 000409C  B FTR NPX Y
PB01 000409C  B FTL NPX Y
PB01 000409C  B FTR NPX Y
PT04 000409C  B FTL NPX Y
PB01 000409C  A FT  NPX Y
PB01 000409C  A FTL NPX Y
PB01 000409C  A FTR NPX Y
PB03 000409C  B FTL NPX Y
PT01 000409C  A FT  NPX Y

9 rows selected.

Elapsed: 00:00:00.04

swprod@PROD.YD.MU> select index_name, status from user_indexes
  2* where table_name in ('EMT100','ORT100')

INDEX_NAME                     STATUS
______________________________ ________
IND_ORT100_1                   VALID
IND_ORT100_2                   VALID
IND_ORT100_3                   VALID
PK_EMT100                      VALID
PK_ORT100                      VALID

5 rows selected.

Elapsed: 00:00:00.03

swprod@PROD.YD.MU> select substr(column_name,1,10) col, column_position,
index_name from user_
ind_columns
  2* where table_name in ('EMT100','ORT100')

COL        COLUMN_POSITION INDEX_NAME
__________ _______________ ______________________________
EMCHCOD                  1 PK_EMT100
ORDNUM                   2 PK_EMT100
GMTSEQ                   3 PK_EMT100
PANCOD                   4 PK_EMT100
SSHPDAT                  1 IND_ORT100_1
EKNTDAT                  1 IND_ORT100_2
EDYEDAT                  1 IND_ORT100_3
ORDNUM                   1 PK_ORT100

8 rows selected.

Elapsed: 00:00:00.03
swprod@PROD.YD.MU> @connect swprod@dev
Enter password:
Connected.

swprod@DEV.YD.MU> select index_name, status from user_indexes
  2* where table_name in ('EMT100','ORT100')

INDEX_NAME                     STATUS
______________________________ ________
IND_ORT100_1                   VALID
IND_ORT100_2                   VALID
IND_ORT100_3                   VALID
PK_EMT100                      VALID
PK_ORT100                      VALID

5 rows selected.

Elapsed: 00:00:00.05

swprod@DEV.YD.MU> select substr(column_name,1,10) col, column_position,
index_name from user_ind_columns
  2  where table_name in ('EMT100','ORT100')

COL        COLUMN_POSITION INDEX_NAME
__________ _______________ ______________________________
EMCHCOD                  1 PK_EMT100
ORDNUM                   2 PK_EMT100
GMTSEQ                   3 PK_EMT100
PANCOD                   4 PK_EMT100
ORDNUM                   1 PK_ORT100
SSHPDAT                  1 IND_ORT100_1
EKNTDAT                  1 IND_ORT100_2
EDYEDAT                  1 IND_ORT100_3

8 rows selected.

Elapsed: 00:00:00.04
swprod@DEV.YD.MU>

________________
PS. Thanks a lot for the latest changes on this site. Helps a lot. Maybe if you
could post only the latest information on top...

          Followup:

I'll be like the RBO today -- bottom up.  what do you mean by "if you could post
only the latest information on top..."?

Now for the view -- perhaps you used a DEFERRABLE constraint in production?  Set
up a test case like this for me to see the creates and all:

ops$tkyte@ORA817DEV> create table emt100 ( emchcod int, ordnum int, gmtseq int,
pancod int, lcncod int, mstatus int );

Table created.

ops$tkyte@ORA817DEV> create table ort100 (sshpdat int, ordnum int constraint
ort100_Pk PRIMARY KEY );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> CREATE OR REPLACE VIEW V_EMT100_yd
  2  AS (select emt100.emchcod, emt100.ordnum, emt100.gmtseq, emt100.pancod,
  3   emt100.lcncod, emt100.mstatus, ort100.sshpdat
  4     from emt100, ort100
  5     where emt100.ordnum = ort100.ordnum);

View created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> update v_emt100_yd
  2  set mstatus = 'Y' where ordnum = '000409C';

0 rows updated.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> drop table emt100;

Table dropped.

ops$tkyte@ORA817DEV> drop table ort100;

Table dropped.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create table emt100 ( emchcod int, ordnum int, gmtseq int,
pancod int, lcncod int, mstatus int );

Table created.

ops$tkyte@ORA817DEV> create table ort100 (sshpdat int, ordnum int constraint
ort100_Pk PRIMARY KEY deferrable );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> CREATE OR REPLACE VIEW V_EMT100_yd
  2  AS (select emt100.emchcod, emt100.ordnum, emt100.gmtseq, emt100.pancod,
  3   emt100.lcncod, emt100.mstatus, ort100.sshpdat
  4     from emt100, ort100
  5     where emt100.ordnum = ort100.ordnum);

View created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> update v_emt100_yd
  2  set mstatus = 'Y' where ordnum = '000409C';
set mstatus = 'Y' where ordnum = '000409C'
    *
ERROR at line 2:
ORA-01779: cannot modify a column which maps to a non key-preserved table

      Rating: 5
Yes, you are
      right! Deferrable constraint in the production
      database  February 21, 2003
Reviewer:
      Yogeeraj  from Mauritius
hi,

Yes, the PK constraints were deferrable in the production database as you
correctly mentioned.

===========================================================
===============================================================================
swprod@DEV.YD.MU> select substr(table_name,1,15),
  2  substr(constraint_name,1,15),
  3  constraint_type,
  4  deferrable,
  5  deferred,
  6  validated,
  7  rely
  8  from user_constraints
  9* where table_name in ('ORT100','EMT100')

SUBSTR(TABLE_NA SUBSTR(CONSTRAI C DEFERRABLE     DEFERRED  VALIDATED     RELY
_______________ _______________ _ ______________ _________ _____________ ____
EMT100          PK_EMT100       P NOT DEFERRABLE IMMEDIATE VALIDATED
EMT100          FK_EMT100_1     R NOT DEFERRABLE IMMEDIATE VALIDATED
EMT100          NN_EMT100_1     C NOT DEFERRABLE IMMEDIATE VALIDATED
EMT100          NN_EMT100_2     C NOT DEFERRABLE IMMEDIATE VALIDATED
EMT100          NN_EMT100_3     C NOT DEFERRABLE IMMEDIATE VALIDATED
EMT100          NN_EMT100_4     C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_1     C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_2     C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_4     C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_5     C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_6     C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_7     C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_8     C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_9     C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_10    C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_11    C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_12    C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_13    C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_14    C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          PK_ORT100       P NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          FK_ORT100_1     R NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          FK_ORT100_2     R NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          FK_ORT100_3     R NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          FK_ORT100_4     R NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          FK_ORT100_5     R NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          FK_ORT100_6     R NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          FK_ORT100_7     R NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          NN_ORT100_1     C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          NN_ORT100_2     C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          NN_ORT100_3     C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          NN_ORT100_4     C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          NN_ORT100_5     C NOT DEFERRABLE IMMEDIATE VALIDATED

32 rows selected.

Elapsed: 00:00:00.08
swprod@DEV.YD.MU> @connect swprod@prod
Enter password:
Connected.

swprod@PROD.YD.MU> select substr(table_name,1,15),
  2  substr(constraint_name,1,15),
  3  constraint_type,
  4  deferrable,
  5  deferred,
  6  validated,
  7  rely
  8  from user_constraints
  9* where table_name in ('ORT100','EMT100')

SUBSTR(TABLE_NA SUBSTR(CONSTRAI C DEFERRABLE     DEFERRED  VALIDATED     RELY
_______________ _______________ _ ______________ _________ _____________ ____
EMT100          PK_EMT100       P DEFERRABLE     IMMEDIATE VALIDATED     RELY
EMT100          FK_EMT100_1     R NOT DEFERRABLE IMMEDIATE VALIDATED
EMT100          NN_EMT100_EMCHC C NOT DEFERRABLE IMMEDIATE VALIDATED
EMT100          NN_EMT100_ORDNU C NOT DEFERRABLE IMMEDIATE VALIDATED
EMT100          NN_EMT100_GMTSE C NOT DEFERRABLE IMMEDIATE VALIDATED
EMT100          NN_EMT100_PANCO C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          PK_ORT100       P DEFERRABLE     IMMEDIATE VALIDATED     RELY
ORT100          FK_ORT100_1     R NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          FK_ORT100_2     R NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          FK_ORT100_3     R NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          FK_ORT100_4     R NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          FK_ORT100_5     R NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          FK_ORT100_6     R NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          FK_ORT100_7     R NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          NN_ORT100_ORDNU C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          NN_ORT100_CLICO C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          NN_ORT100_ORDDA C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          NN_ORT100_MRCID C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          NN_ORT100_ORDST C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_1     C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_2     C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_4     C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_5     C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_6     C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_7     C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_8     C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_9     C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_10    C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_11    C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_12    C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_13    C NOT DEFERRABLE IMMEDIATE VALIDATED
ORT100          CK_ORT100_14    C NOT DEFERRABLE IMMEDIATE VALIDATED

32 rows selected.

Elapsed: 00:00:00.09
swprod@PROD.YD.MU>
===============================================================================

Hence, i will have to re-create the Primary Key constraints as the documentation
clearly states:
<quote>
Restrictions:

You cannot defer a NOT DEFERRABLE constraint with the SET CONSTRAINT(S)
statement.

You cannot specify either DEFERRABLE or NOT DEFERRABLE if you are modifying an
existing constraint directly (that is, by specifying the ALTER TABLE ... MODIFY
constraint statement).

You cannot alter a constraint's deferrability status. You must drop the
constraint and re-create it.

</quote>

But why is the deferrable constraint preventing me from updating the view? I
cannot undestand the logic behind it. Please clarify.

Best Regards
Yogeeraj

PS. Sorry for the confusion, i was referring to the home page of ASKTOM. Where
the "message" is not "visible" (have to scroll down) on browsers on PCs
configured on 800x600 pixel displays... ;) Also, i believe everyone would have
loved to have the option where they could "register" themselves to be notified
by email on any particular updates to questions....  Thanks

          Followup:

because the primary key created an index and that index is NOT unique hence the
update of the join can be ambigous -- the result of the update of the SAME EXACT
DATA could be different depending on the physical "order" of the data in the
database.

The table joined to must be joined to by a key that is assured always to be
unique -- with a deferrable pk constraint -- this is no longer true.

I understand the message thing now, I find it more useful to have the search and
list at the top with the messages at the bottom.

I don't want to get into the "email" thing really -- I get enough bounced emails
as it it.  An automatic system like that scares me.  Everytime I touch a
question that is older then a couple of weeks -- I get a bounced email from the
original submitter as their hotmail/yahoo/whatever account is over quota,
cancelled whatever.

      Rating: 1
Ignore bounced
      emails  February 21, 2003
Reviewer:
      A reader

          Followup:

easy for you to say -- them, er, come to me you see.

How about this -- you give me your email address and I'll just set up a rule
that forwards them to you -- OK?

I'll wait for the email address.

      Rating: 5
please clarify
      further   February 22, 2003
Reviewer:
      Yogeeraj  from Mauritius
hello,

This is quite difficult to visualize. Maybe a white board would have helped!
=======================================
sQL> @connect swprod@pfs
Enter password:
Connected.
swprod@PROD.YD.MU> select ordnum
  2  from ort100
  3  having count(*) > 1
  4  group by ordnum;

no rows selected

Elapsed: 00:00:00.45
swprod@PROD.YD.MU> @connect swprod@dev
Enter password:
Connected.

swprod@DEV.YD.MU> select ordnum
  2  from ort100
  3  having count(*) > 1
  4* group by ordnum

no rows selected

Elapsed: 00:00:00.78
swprod@CMTDB.CMT.MU>

======================================

Despite the Deferrable PK constraint and the index being UNIQUE (from query
above), we still have the problem. Is it that "this is not possible when you
have deferrable pk constraints"?

thank you in advance for your usual patience and precious time.

best regards
yogeeraj

PS. I can undestand the pain you endure in managing this site especially with
all those email addresses that bounce! I hate that too! Anyway, thanks a lot for
the latest improvements - i love the new feature "Your Questions".

          Followup:

the index is NOT unique for a deferrable constraint, that is the problem. You
never showed us a "unique" constraint above.

look:

ops$tkyte@ORA920> create table t ( x int primary key deferrable );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set constraints all deferred;

Constraint set.

ops$tkyte@ORA920> insert into  t select 1 from all_users;

47 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select x from t group by x having count(*) > 1;

         X
----------
         1

See -- i have 47 rows in that table with the number 1 in the primary key.  The
constraint check is DEFERRED until commit time.  So when I commit:

ops$tkyte@ORA920> commit;
commit
*
ERROR at line 1:
ORA-02091: transaction rolled back
ORA-00001: unique constraint (OPS$TKYTE.SYS_C007468) violated

See -- there can be dups in there since the constraint is deferrable.  They are
not allowed to persist AFTER a commit, the data integrity is checked at
transaction commit time instead of after each statement.

      Rating: 5
now
      clear  February 24, 2003
Reviewer:  Yogeeraj
       from Mauritius
hello,

I guess this explains that...
============================================================
swprod@DEV.YD.MU> @connect swprod@prod
Enter password:
Connected.

swprod@PROD.YD.MU> select index_name, uniqueness
  2  from user_indexes
  3  where table_name in ('EMT100','ORT100')
  4  and index_name like 'PK%';

INDEX_NAME                     UNIQUENES
______________________________ _________
PK_EMT100                      NONUNIQUE
PK_ORT100                      NONUNIQUE

Elapsed: 00:00:00.02
swprod@PROD.YD.MU> @connect swprod@dev
Enter password:
Connected.
swprod@DEV.YD.MU> select index_name, uniqueness
  2  from user_indexes
  3  where table_name in ('EMT100','ORT100')
  4* and index_name like 'PK%'

INDEX_NAME                     UNIQUENES
______________________________ _________
PK_EMT100                      UNIQUE
PK_ORT100                      UNIQUE

Elapsed: 00:00:00.02
swprod@DEV.YD.MU>
============================================================

I believe, i will have to drop and recreate all my PKs and FKs to be able to use
updateable views.

i.e. (at the same time reducing performance degradation time)
a. create index with same columns and position as in the PK.
b. Drop FK constraints
b. Drop PK constraints
c. Create PK constraints with rely option.
d. create FK constraints.

thank you a lot.

Best Regards
Yogeeraj

February 23, 2003 Nimish -- Thanks for the question regarding
      "Sequences", version 9.2.0.1

      You Asked
Tom,

I have a question regarding sequence. I have a table in which I have id column
which is generated using sequence and before insert trigger. It is also primary
key on the table. But during certain processing (mainly for first time
conversion) I have to disable the trigger to get the data as it is from host
system. I can get the data by disabling the trigger. But after getting the data
into the table, I have to enable the trigger. And to make sure that I don't get
primary key violation, I need to increment the 'START WITH' value of sequence
number with max value of id for this table.

Can I achieve this without dropping and re-creating sequence? How can I do it in
the PL/SQL block?

Thanks,
Nimish

      and we said...
well, it would always be my preference to NOT use a trigger for the sequence
value, rather

insert into t .... values ( my_seq.nextval, .... )

not only will you find that to be more performant, you'll find your need to
disable "goes away".

To fix the sequence after the load, just alter the sequences increment (say you
needed the sequence to be 1000 more then it is -- make the increment by be
1000), select my_seq.nextval from dual, and then alter the sequence increment
back down.  You need not drop it.

Yes, you can do this in plsql using EXECUTE IMMEDIATE 'ddl command....';
Review &
      Followup

      Rating: 3
Not using
      sequences in trigger  February 23, 2003
Reviewer:
      Vikas  from Delhi India
Hi Tom,

The one reason i could see for not using sequences in the trigger is as the sql
(select seq.nextval INTO :new.id from dual) will be parse and executed every
time. Which will slow the execution. Any other reason why you would not like
using seq in triggers?

Regards ,

Vikas Sharma

          Followup:

time it with and without triggers -- see the performance different.

look at the statistics involved (work performed)

compare the latching -- and remember latches = locks = serialization device =
LESS SCALABLE

using:

http://asktom.oracle.com/~tkyte/runstats.html
we test:

ops$tkyte@ORA920> create sequence s1;
Sequence created.

ops$tkyte@ORA920> create table t1 ( x int primary key, y int );
Table created.

ops$tkyte@ORA920> create sequence s2;
Sequence created.

ops$tkyte@ORA920> create table t2 ( x int primary key, y int );
Table created.

ops$tkyte@ORA920> create or replace trigger t2_trigger
  2  before insert on t2 for each row
  3  begin
  4          select s2.nextval into :new.x from dual;
  5  end;
  6  /
Trigger created.

ops$tkyte@ORA920> declare
  2      l_start number;
  3      l_run1  number;
  4      l_run2  number;
  5  begin
  6      insert into run_stats select 'before', stats.* from stats;
  7
  8      l_start := dbms_utility.get_time;
  9      for i in 1 .. 30000
 10      loop
 11                  insert into t1(x,y) values ( s1.nextval, i );
 12      end loop;
 13      l_run1 := (dbms_utility.get_time-l_start);
 14      dbms_output.put_line( l_run1 || ' hsecs' );
 15
 16      insert into run_stats select 'after 1', stats.* from stats;
 17      l_start := dbms_utility.get_time;
 18      for i in 1 .. 30000
 19      loop
 20                  insert into t2(y) values ( i );
 21      end loop;
 22      l_run2 := (dbms_utility.get_time-l_start);
 23      dbms_output.put_line( l_run2 || ' hsecs' );
 24      dbms_output.put_line
 25      ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) || '% of the time' );
 26
 27      insert into run_stats select 'after 2', stats.* from stats;
 28  end;
 29  /
1621 hsecs
3257 hsecs
run 1 ran in 49.77% of the time

PL/SQL procedure successfully completed.

No trigger, twice as fast by the wallclock

ops$tkyte@ORA920> select a.name, b.value-a.value run1, c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

NAME                                 RUN1       RUN2       DIFF
------------------------------ ---------- ---------- ----------
...
LATCH.SQL memory manager worka        335        737        402
rea list latch

LATCH.enqueue hash chains           10265      10873        608
LATCH.simulator hash latch           4531       3907       -624
LATCH.cache buffers lru chain         760       1837       1077
STAT...recursive cpu usage           1386       2834       1448
STAT...redo size                 15852896   15855048       2152
LATCH.checkpoint queue latch          463       3041       2578
STAT...table scan blocks gotte          0      30000      30000
n

STAT...table scan rows gotten           0      30000      30000
STAT...table scans (short tabl          0      30000      30000
es)

STAT...execute count                31541      61543      30002
STAT...no work - consistent re         63      30068      30005
ad gets

STAT...buffer is not pinned co        106      30116      30010
unt

STAT...recursive calls              51373      81395      30022
LATCH.session allocation             3408      45606      42198
STAT...session pga memory max       65536          0     -65536
STAT...calls to get snapshot s      34798     124804      90006
cn: kcmgss

STAT...consistent gets               1886      91906      90020
STAT...session logical reads        99471     189522      90051
LATCH.shared pool                   68530     159033      90503
LATCH.library cache pin            133000     253421     120421
LATCH.library cache                169902     320753     150851
LATCH.cache buffers chains         423405     608926     185521

89 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select run1, run2, diff, round( run1/run2*100,2 )
run1_pct_of_run2
  2    from (
  3  select sum(b.value-a.value) run1, sum(c.value-b.value) run2,
  4         sum( (c.value-b.value)-(b.value-a.value)) diff
  5    from run_stats a, run_stats b, run_stats c
  6   where a.name = b.name
  7     and b.name = c.name
  8     and a.runid = 'before'
  9     and b.runid = 'after 1'
 10     and c.runid = 'after 2'
 11     and (c.value-a.value) > 0
 12     and (c.value-b.value) <> (b.value-a.value)
 13     and a.name like 'LATCH%'
 14   )
 15  /

      RUN1       RUN2       DIFF RUN1_PCT_OF_RUN2
---------- ---------- ---------- ----------------
    912971    1509124     596153             60.5

ops$tkyte@ORA920>

that last part there, the reduction in latching, that's all I need to convince
me

      Rating: 4
Thanks
        February 23, 2003
Reviewer:  Vikas
       from Delhi India
Hi Tom,

Thanks lot for explaining it.

Vikas Sharma

February 23, 2003 Hrishy -- Thanks for the question regarding
      "cross-tab query with a single select statement", version 8.1.7

      You Asked
Hi Tom

I have a table T.with the following table structure..

create table T(
a1 varchar2(10),
b1 varchar2(10),
c1 varchar2(10),
d1 varchar2(10),
)
It has four columns, A1, B1, C1 and D1.

The data in this table is as follows:

insert into table T
values (a1,b1,c1,d1)

insert into table T
values (a1,b1,c1,d2)

insert into table T
values (a1,b1,c1,d3)

insert into table T
values (a2,b2,c2,d4)

insert into table T
values (a2,b2,c2,d5)

insert into table T
values (a3,b3,c3,d6)

select * from t

A1    B1    C1    D1

a1    b1    c1    d1
a1    b1    c1    d2
a1    b1    c1    d3

a2    b2    c2    d4
a2    b2    c2    d5

a3    b3    c3     d6

I want the output like this:

a1    b1    c1    d1    d2    d3
a2    b2    c2    d4    d5
a3    b3    c3    d6

Is it possible to get such output with a single SELECT statement?

      and we said...
Yes (well no really, don't understand why D would take three columns, I'll do
two instead)  -- BUT you need to know so much information before you begin to
code the query.  The query would have to be dynamically built, since the number
of columns would change as the number of values in the table changed.

You would have to run a query to get the number of distinct values of a1, b1,
c1, and d1. Then we could construct a query to get that output.

This is the general form of the query I'm going for, 3 = num_rows in this query:

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select max(decode( ceil(r/3), 1, data, null )),
  2         max(decode( ceil(r/3), 2, data, null )),
  3         max(decode( ceil(r/3), 3, data, null )),
  4         max(decode( ceil(r/3), 4, data, null )),
  5         max(decode( ceil(r/3), 5, data, null ))
  6    from (
  7      select data, rownum-.9 r
  8         from ( select distinct decode( r, 1, a, 2, b, 3, c, 4, d ) data
  9                  from t,
 10                       (select rownum r from all_objects where rownum <= 4)
 11                  order by 1)
 12         )
 13   group by mod( r, 3 )
 14  /

MA MA MA MA MA
-- -- -- -- --
a1 b1 c1 d1 d4
a2 b2 c2 d2 d5
a3 b3 c3 d3 d6

We'll have to generate that dynamically as the results vary as the data changes:

ops$tkyte@ORA817DEV> create or replace package demo_pkg
  2  as
  3      type rc is ref cursor;
  4
  5      procedure cross_tab( p_query out rc, p_num_rows in number default 3 );
  6  end;
  7  /

Package created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace package body demo_pkg
  2  as
  3
  4  procedure cross_tab( p_query out rc, p_num_rows in number default 3 )
  5  is
  6      l_cnt       number;
  7      l_query     long;
  8  begin
  9      select ceil(count( distinct decode( r, 1, a, 2, b, 3, c, 4, d ) ) /
p_num_rows)
 10        into l_cnt
 11        from t,
 12             (select rownum r from all_objects where rownum <= 4);
 13
 14      l_query := 'select max(decode(ceil(r/' || p_num_rows || '), 1, data,
null ))';
 15      for i in 2..l_cnt
 16      loop
 17          l_query := l_query || ',max(decode(ceil(r/' || p_num_rows ||
 18                                 '), '||i||', data, null ))';
 19      end loop;
 20      l_query := l_query || 'from (
 21      select data, rownum-.9 r
 22         from ( select distinct decode( r, 1, a, 2, b, 3, c, 4, d ) data
 23                  from t,
 24                       (select rownum r from all_objects where rownum <= 4)
 25                  order by 1)
 26      )
 27      group by mod(r,'|| p_num_rows ||')';
 28      open p_query for l_query;
 29  end;
 30
 31  end;
 32  /

Package body created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> variable x refcursor
ops$tkyte@ORA817DEV> set autoprint on
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> exec demo_pkg.cross_tab(:x);

PL/SQL procedure successfully completed.

MA MA MA MA MA
-- -- -- -- --
a1 b1 c1 d1 d4
a2 b2 c2 d2 d5
a3 b3 c3 d3 d6

ops$tkyte@ORA817DEV> exec demo_pkg.cross_tab(:x,4);

PL/SQL procedure successfully completed.

MA MA MA MA
-- -- -- --
a1 b2 c3 d4
a2 b3 d1 d5
a3 c1 d2 d6
b1 c2 d3

ops$tkyte@ORA817DEV> exec demo_pkg.cross_tab(:x,2);

PL/SQL procedure successfully completed.

MA MA MA MA MA MA MA MA
-- -- -- -- -- -- -- --
a1 a3 b2 c1 c3 d2 d4 d6
a2 b1 b3 c2 d1 d3 d5

ops$tkyte@ORA817DEV>

ops$tkyte@ORA817DEV>
Review & Followup

      Rating:
      5
Cool  January 03, 2003
Reviewer:
      hrishy  from PA
Hi Tom

I was lookin for..a particular solution..but you came out with a fabolous
generic solution...this is really amazing..for sql chaellenged..;-D

      Rating: 2
Materialised
      view  February 23, 2003
Reviewer:  kit
       from England
I did post question and you wanted more info.
what we have is 5 Mat views. the select and from, where is same most. mall
difference between them is 2 lines of a where clause

mv1 - and colX = 'A'
   2      colx  = 'B'
   3   colx = 'C'

each one of these matviews get generated different times per day.
reconcillations and reports are then driven of these mv.

I was hoping todo dyn sql - create materialistic view when an event gets
generated. Is there a way of passing a where clause part of the refresh

If we dont do it dynamically, we'll have 8 views which are the same but few
lines of the where clause seperate.

all the mv gets refreshed on a daily basis

please dont publish to the whole world

          Followup:

this is already published to the entire world.

So -- question is why you have 8 MVs and not just 1. or even N mv's instead of
just one.

You would need to give me a concrete example that would need such a situation.
Yes, you can create mv's using dynamic sql -- however, I would question
seriously the need to do so.  that would be like creating "indexes on the fly".
a seriously bad idea.

February 23, 2003 Steve -- Thanks for the question regarding
      "cursor for loop ", version 9.2.0

      You Asked
Tom,

I am trying to accomplish the following:

I have a procedure with the following procedure definition

procedure get_user_app_roles(i_usr_nm varchar2,i_app_nm varchar2, p_out in out
types.rc);

I am trying to call it in another procedure as follows

procedure x (i_usr_nm varchar2,i_app_nm varchar2)
as
 l_cursor types.rc
begin

  get_user_app_roles(i_usr_nm,i_app_nm ,l_cursor);

  for x in (l_cursor)  -- having trouble here
  loop
    .....
  end loop;

end;

I cant seem to get the syntax right in the for loop

I have tried
for x in (get_user_app_roles(i_usr_nm,i_app_nm ,l_cursor))
for x in (select * from (
          get_user_app_roles(i_usr_nm,i_app_nm ,l_cursor)))

and other variations with no luck.

What is the proper way to use a ref cursor from an out param in a cursor for
loop?

Thanks in advance

      and we said...
...
begin
   get_user_app_roles(i_usr_nm,i_app_nm ,l_cursor);

   loop
      fetch l_cursor into <variables>;
      exit when l_cursor%notfound;
      .....
   end loop;
   close l_cursor;
end;

You must use explicit SQL syntax with explicit cursors and ref cursors are
ALWAYS explicit cursors.

Only with an implicit cursor can you use the "nice" syntax.
February 23, 2003 -- Thanks for the question regarding "problem
      with STEM in context index", version 816

      You Asked
select count(*) from table where contains(desc_name,'$KIT')>0;

  COUNT(*)
----------
      3868

user> select count(*) from table where contains(desc_name,'$KITS')>0;

  COUNT(*)
----------
      3868

 select count(*) from table where contains(desc_name,'$CAMCORDER')>0;

  COUNT(*)
----------
       912

user> select count(*) from table where contains(desc_name,'$CAMCORDERS')>0;

  COUNT(*)
----------
       449

I am using the STEM operator in context index so the first and second query
returns same amount is what i expected plural and singular should be the same
however the second pair 3rd and 4th query why do they return different results ?

      and we said...
I asked Joel Kallman, our resident Text expert and he says:

This has to do with the term expansion of the search operator.
For whatever reason, the stemming logic of Oracle Text is
expanding your query into a different set of search terms.
Stemming is somewhat of a black box to use....we license the
technology from Xerox.

To see the list of tokens Oracle Text is expanding this stem
search to, use the CTX_QUERY.EXPLAIN procedure.

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/inter.817/a77063/cqrypkg4.htm#8025

February 23, 2003 Aquib -- Thanks for the question regarding
      "Processing of DML Statements.", version 8.0.5

      You Asked
Dear Tom,
Could you explain to me the steps that take place in the
processing and commit of a DML statement?
I have read the documentation as well as the Oracle Education
book "Oracle 8 Database Administration"(I am also preparing for
my OCP tests,by the way), but I could not grasp the concept
fully. Specifically,what are the exact functions of the Buffer
Cache and the Log Buffer(the presence of two different buffers
to store data changes is confusing)?
I would be very much obliged.
Thanks.
Aquib.

      and we said...
I'll handle the high level but defer to the concepts manual for
the nitty gritty details here.

The buffer cache vs log buffer....

The block buffer cache is where we store blocks that are read in
from disk.  We also make modifications to these cached blocks --
marking them dirty.  Blocks are flushed (written to disk) from
this cache by DBWR (database block writer) during checkpoints or
when we need to make room in the bufer cache for other stuff.

When you COMMIT your work -- we do NOT goto the buffer cache and
find all of the blocks you may have modified and write them out
(that would be far too expensive).  Rather, we write the changes
you did to a thing called the ONLINE Redo Log.  This redo log
contains all of the after images (what we need to REDO your
transaction).  We use that upon a system failure to REDO to the
updates to the blocks that didn't get flushed before.

While you are doing your transaction, rather they writing this
log information directly to disk, we buffer it -- we buffer it
in a place called the log buffer.  The log buffer gets flushed
whenever:

o someone commits -- makes sure we can REDO the transaction in
the event of failure.

o it gets 1/3 full.

o every three seconds pass.

So, the block buffer cache holds cached, read blocks and may
contains versions of dirty blocks that not exist on disk.  The
log buffer caches changes that will be written to the redo logs
very very soon.

See

http://technet.oracle.com/doc/oracle8i_816/server.816/a76965/c15trans.htm#297
for all of the low level details of this.
Review &
      Followup

      Rating:
      4
  August 23, 2002
Reviewer:
      A reader  from Sweden

      Rating: 3
How to find
      commits  February 23, 2003
Reviewer:  reader
       from USA
which dictionary table/view has the information that if the transactions are
being commited or not?

Thanks

          Followup:

  1  select *
  2    from v$sysstat
  3*  where name in ( 'user commits', 'user rollbacks' )
ops$tkyte@ORA920> /

STATISTIC# NAME                                CLASS      VALUE
---------- ------------------------------ ---------- ----------
         4 user commits                            1      57998
         5 user rollbacks                          1          0

ops$tkyte@ORA920> rollback
  2  /

Rollback complete.

ops$tkyte@ORA920> @afiedt.buf

STATISTIC# NAME                                CLASS      VALUE
---------- ------------------------------ ---------- ----------
         4 user commits                            1      57998
         5 user rollbacks                          1          1

February 23, 2003 josh -- Thanks for the question regarding "Tune
      SQL statement with max subquery", version 9.2

      You Asked
Greetings Tom,

I have been wracking my brain to figure out a different way of doing this. I
have had no luck. So before I quit on this statement all together, I thought I'd
throw it your way.

SELECT /*+ PARALLEL (table_a,4) */
             COL_A
            ,COL_B
            ,COL_C
            ,COL_D
            ,COL_E
            ,COL_F
            ,COL_CHNG_ID
      FROM table_a
      WHERE COL_G = 'ZQ'
        AND COL_H= (
                     SELECT MAX(COL_H) col_h
                     FROM table_a sub_table_a
                     WHERE table_a.COL_A= sub_table_a.COL_A
                       AND table_a.COL_B= sub_table_a.COL_B
                       AND table_a.COL_C= sub_table_a.COL_C
                       AND table_a.COL_D= sub_table_a.COL_D
                       AND sub_table_a.COL_G =ZQ')

COL_A, B C D are a natural primary key. Can't change that.

There can be several values for col_h and I need the row that is returned to be
the one with the max value for col_h. I am trying to figure out how to do this
in one pass on the table, its quite large.

Update:

Sorry for the mistake, I had to rename the columns to take out the business
information stuff :).
I need to select the max col_h where Col_g = 'ZQ'. Then I need the row set that
comes with the max col_h for the natural key and a couple other columns.

Update:
For each primary key, there can be multiple combinations of
col_e, col_f COL_CHNG_ID and col_h. Further more Col A B C D are only a part of
the natural key. Apparently, the natural key also has a few more columns, but I
think that's irrelevant, the key thing to note here is that there are multiple
combinations of col_h in conjuction with col_e col_f col_chng_id, along with
multiple combinations of col_h in conjuction with the columns in the subquery
where clause? Follow me? If not I can restate the question, perhaps with better
column names! :)

      and we said...
select *
  from (
select a, b, c, d, e, f, chgn_id,
       H,
       max(H) over (partition by a,b,c,d) max_h
  from table_a
 where g = 'ZQ'
       )
 where h = max_h
/

should do it.

analytics rock and roll....
Review &
      Followup

      Rating: 5
indeed
      analytics Rock  January 10, 2003
Reviewer:
      J
Thanks Tom!

      Rating: 5
Additional
      difficulty   January 10, 2003
Reviewer:
      Josh  from oregon
Tom,

I am using this as an inline view. In the query that it runs in, it is joined
with an outer join to the driving table. When the predicate gets pushed to the
view it puts an outer join on both h and max_h.

so that the predicate that gets put against the inline view looks like

h(+)=max_h(+)

          Followup:

er?  not getting it.  examples please

      Rating: 5
Never mind my
      friend  January 10, 2003
Reviewer:  Josh
       from oregon
Thanks for your sweet code Tom.

I figured it out. But to explain futher, because of the self join, when I used
this query as an inline view and outer joined on a,b,c,d to the driving table,
the outer join predicate got pushed down to the underlying table in the inline
view. This resulted in a predicate that looked like
h(+)=max_h(+) when it got rewritten. I got the predicates from the dbms_xplan.
Which Rocks also!

I just removed the self join so now the query looks like

select a,b,c,d,e,f,chng_id
  from (
select a, b, c, d, e, f, chgn_id
       max(H) over (partition by a,b,c,d) max_h
  from table_a
 where g = 'ZQ'
       )

      Rating: 4
Max query to
      return NULL when there is no data.  February 20, 2003

Reviewer:  Matt  from Australia
I have an inline view defined as below. The view is used to identify the values
of t_sid for a set of days. The t_sid value is then checked against tables in
the remainder of the query as a filter. THere is also a filter on the day
column. The remaining tables have a similar t_sid column which is a number > 0,
but may be NULL. As a result I need my query to either return the t_sid (if it
exists) or else a NULL. I NVL the results (t_sid and day) in the remainder of
the query.

This is the inline view:

SELECT t_sid, day FROM
                   (SELECT t_sid
                          ,day
                          ,MAX(ver) AS version
                      FROM
                      ((SELECT lt.t_sid, lt.day, lt.ver
                               FROM my_tab lt
                             WHERE lt.rep_p = ' SOME TEXT'
                               AND lt.t_type          = 'MY_TYPE'
                               AND lt.stat            = 'S'
                       )
                       UNION ALL
                      (SELECT NULL AS t_sid, NULL AS day, NULL AS ver FROM
sys.dual)
                   )
                   GROUP BY t_sid, day

This works well, since I always get a row returned. This row is all NULL if
there is no data in the my_tab table that satisfy the query criteria. If there
is data I get the results I want.

Can the query be written in a more efficient way?

There is an index on:

my_table(rep_p,day,t_type,stat)

Ideally I would like to avoid the sort and the FTS. Is this possible?

Thanks and Regards.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=331 Card=1766 Byte
          s=42384)

   1    0   SORT (GROUP BY) (Cost=331 Card=1766 Bytes=42384)
   2    1     VIEW (Cost=325 Card=1766 Bytes=42384)
   3    2       UNION-ALL
   4    3         TABLE ACCESS (FULL) OF 'MY_TABLE' (Cost=324 Card
          =1684 Bytes=183556)

   5    3         TABLE ACCESS (FULL) OF 'DUAL' (Cost=1 Card=82)

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

          Followup:

well, your index is "out of order" -- not very useful -- for this query as you
stuck DAY in the middle of it.  if rep_p,t_type,stat were on the leading edge,
it would make more sense.

I do not understand the spurious reference to VER in the query.  what is its
purpose -- is this a whittled down example of something else?

Here is an example with a "better" index:

ops$tkyte@ORA920> @test
ops$tkyte@ORA920> /*
DOC>drop table t;
DOC>
DOC>create table t ( t_sid number, day number, ver number, rep_p varchar2(9),
t_type varchar2(7), stat varchar2(1) );
DOC>create index t_idx on t(rep_p,t_type,stat,day,t_sid);
DOC>
DOC>insert into t
DOC>select 1, 1, 1, 'SOME TEXT', 'MY_TYPE', 'S'
DOC>  from all_objects;
DOC>insert into t
DOC>select 1, 1, 1, 'TEXT', 'MY_TYPE', 'S'
DOC>  from all_objects;
DOC>commit;
DOC>*/
ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace on
ops$tkyte@ORA920> select rownum, a.* from (
  2  SELECT t_sid, day
  3    FROM t lt
  4   WHERE lt.rep_p = 'SOME TEXT'
  5     AND lt.t_type = 'MY_TYPE'
  6     AND lt.stat = 'S'
  7   UNION
  8  SELECT null, null
  9    from dual
 10  ) a
 11  /

    ROWNUM      T_SID        DAY
---------- ---------- ----------
         1          1          1
         2

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   COUNT
   2    1     VIEW
   3    2       SORT (UNIQUE)
   4    3         UNION-ALL
   5    4           INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE)
   6    4           TABLE ACCESS (FULL) OF 'DUAL'

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

ops$tkyte@ORA920>
ops$tkyte@ORA920> select rownum, a.* from (
  2  SELECT t_sid, day
  3    FROM (SELECT t_sid ,day ,MAX(ver) AS version
  4            FROM ((SELECT lt.t_sid, lt.day, lt.ver
  5                     FROM t lt
  6                    WHERE lt.rep_p = 'SOME TEXT'
  7                      AND lt.t_type          = 'MY_TYPE'
  8                      AND lt.stat            = 'S')
  9                    UNION ALL
 10                   (SELECT NULL AS t_sid, NULL AS day, NULL AS ver
 11                                      FROM sys.dual)
 12                 )
 13           GROUP BY t_sid, day)
 14  ) a
 15  /

    ROWNUM      T_SID        DAY
---------- ---------- ----------
         1
         2          1          1

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   COUNT
   2    1     VIEW
   3    2       SORT (GROUP BY)
   4    3         VIEW
   5    4           UNION-ALL
   6    5             TABLE ACCESS (BY INDEX ROWID) OF 'T'
   7    6               INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE)
   8    5             TABLE ACCESS (FULL) OF 'DUAL'

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

ops$tkyte@ORA920> set autotrace off
ops$tkyte@ORA920>

      Rating: 3
Doh! - Missed
      the index ordering.  February 23, 2003
Reviewer:
      Matt  from Australia
I missed the index ordering. I was focussed too much on generating the required
results using analytics. I was trying something like

max(decode(ver)) over (partition by ...)

In order to avoid the union all to dual. I mistakenly believed that this would
also avoid a sort. Not the case.

Can this the same results be achieved using analytics?

ver is a numeric that is a "count" of how many unique combinations of data there
are in the table. This could be calculated on the fly I guess, but this value is
typically this value is displayed to the end user. To avoid the calculation each
time we just store it.

What you see is indeed a whittled down example.

Thanks for your response.

          Followup:

I'd need to see an unwhittled or less whittled one to understand really what is
needed -- so i do not know if analytics can be used to answer your question.

February 23, 2003 Hugo -- Thanks for the question regarding
      "ORA-01036: illegal variable name/number in pro*C trigger ", version 8.1.7

      You Asked
Hi,

Im trying to create a simple trigger using pro*c (im running linux
version 7.3 and oracle 8i) according to some arguments that are passed by user
of the C application.

The idea of the creation of trigger is the follwing.: I want that a user can
subscribe (meaning being nofied by) to any data change in one table (being the
data change an insertion of a row in a table). So i thought that through the use
of triggers i could achieve this .

The example i send was just my first attempt of creation of a simple trigger in
Pro*C.  But unfortunately im getting the following
error:

Oracle error--->>>>>>BUMMM
ORA-01036: illegal variable name/number

in "..."
on line 0 of .

The code related to the mistake:

/*******************************************************************/
VARCHAR dynstmt[100];
emp_number = 2;
new_salary = 300;

.......

strcpy(dynstmt.arr,
         "CREATE OR REPLACE TRIGGER LOG_SALARY_AGAIN AFTER UPDATE ON
EMP_TAB FOR EACH ROW BEGIN INSERT INTO EMP_LOG (EMP_ID, NEW_SALARY,
ACTION) VALUES (:v1,:v2,'OUT'); END;");
  dynstmt.len = strlen(dynstmt.arr);
  EXEC SQL PREPARE S FROM :dynstmt;
  EXEC SQL EXECUTE S  USING :emp_number, :new_salary;

/*******************************************************************/

Can someone give a hint of what could be causing this trouble??? i
presume that is related with the host variables (or
placeholders)... but im getting no where.

Or at least provide an example of the creation of a trigger in proc*C where we
have to make use of placeholders for passing arguments specified by the user of
the C application

Thanks a lot,
Hugo Caçote

      and we said...
Well, you certainly would not want to use a trigger PER user to accomplish this!

What you should do is set up a table "interested_users".  You would insert into
this table via your pro*c application those people that are interested.

There would then be a single, static, created at INSTALL time trigger that would

begin
   for x in ( select * from interested_users )
   loop
      notify them somehow
   end loop;
end;

DDL cannot have bind variables.

If you find yourself doing DDL in an application there is a 99.999% chance you
are doing something very wrong (need to look for a different solution).

DDL is something to do at INSTALL time, and never during runtime!

Review &
      Followup

      Rating:
      3
  February 24, 2003
Reviewer:  Hugo
      Caçote  from Geneva, Switzerland
Thanks for the answer, maybe your advise has prevented me from getting into an
unfeasible option to accomplish my purposes

February 23, 2003 -- Thanks for the question regarding "Trying to
      Split Number ranges (having real tough time !)", version 9.2.0

      You Asked
Tom,

Sorry. Yes, you are correct I can execute the SQL that you sent and this is the
output.

SQL> select row_number() over (order by dummy) from dual;

ROW_NUMBER()OVER(ORDERBYDUMMY)
------------------------------
                             1

My aplologies for troubling you further, but how can I use this in my case? I
guess I sound dumb, but I have never used much of Analytic functions. I have
only seen you do it and admired its power. Can you guide me?

Thanks

Hi Tom,

Thanks ! No the gaps are not typo. It is to be expected. The serial numbers can
be asked in any order. The check that I have to perform is, has it ever been
used before. I can give out only those serial numbers that have never been used.
In the first example, the user was checking if he can use serial numbers 30-40,
and based on the data in the table, the only serials that I could assign was 30
- 39 (40 being already used.)

Please let me know if I have provided enough information.

THANK YOU :-)

Hi Tom,
I am having hard time trying to split number ranges. Following 3 examples would
help explain better.
If we can do this in SQL it would be great, if not a procedure or function would
also be fine. I have tried both,
but cannot seem to handle all the cases as described below. Could you please
help me. I cannot use Analytic functions
as I have Oracle 9.2 Standard Edition.

Table with 2 columns serial_start and serial_end has the following data:

SERIAL_START   SERIAL_END
-------------  ----------
40             140
60             145
100            110
150            250

User Input: Serial_Start = 30, Serial_End = 40

Expected Output: 30-39

When a user inputs a serial_begin=30 and serial_end=40, I have to look into this
table and say, OK,
the only serials that can be assigned are 30 - 39. This is because, 40 is
already used in the table.

Another example: Same table has the following data

SERIAL_START   SERIAL_END
-------------  ----------
200            230

User Input: Serial_Start = 150, Serial_End = 250

Expected Output: 150 - 199
                 231 - 250

When a user inputs a serial_begin=150 and serial_end=250, I have to look into
this table and say, OK,
the only serials that can be assigned are 150 - 199 and 231 - 250. This is
because, 200 - 230 is already used in the table.

Another example: Same table has the following data

SERIAL_START   SERIAL_END
-------------  ----------
50             98
100            150
160            190
200            230

User Input: Serial_Start = 90, Serial_End = 120

Expected Output: 99 - 99

Thanks

      and we said...
See the "reviews" below -- we worked through it there.
Review &
      Followup

      Rating:
      5
Excellent  February 15, 2003
Reviewer:
      A reader
Tom,

It makes me feel better to know that there is no easy way for this. Since
yesterday, I had serious doubts on whether I was really missing something very
obvious.

On the other hand, I was excited to be able to ask you this question and was
expecting a magical solution (as you always do for others). I have tried
procedural approach, but don't seem to succeed even with these 3 cases. I am
sure there are other combinations and permutations out there that I am not even
aware of.

I have copied my procedure to show what I have been up to.
(This is just a temporary version)
You don't have to write code for me, but if there is some algorithm that I could
always use or if you have any advice on how to approach this procedurally, it
would help me a lot.

-- Drop serial tables
DROP TABLE serial_ranges;
DROP TABLE temp_serial_ranges;

-- Store existing Serial Ranges
CREATE TABLE serial_ranges(
serial_begin     NUMBER,
serial_end       NUMBER);

-- Insert new serial ranges based on user input. This would be truly a global
temp table.
CREATE TABLE temp_serial_ranges(
serial_begin     NUMBER,
serial_end       NUMBER);

CREATE OR REPLACE PROCEDURE Insert_Exclusions(
   serial_begin_exclude_   NUMBER,
   serial_end_exclude_     NUMBER)

IS

   CURSOR GetAll IS
      SELECT *
      FROM   serial_ranges;
BEGIN

   FOR rec_ IN GetAll LOOP
      IF serial_begin_exclude_ NOT BETWEEN rec_.serial_begin AND rec_.serial_end
AND
         serial_end_exclude_ BETWEEN rec_.serial_begin AND rec_.serial_end THEN

         INSERT INTO temp_serial_ranges VALUES (serial_begin_exclude_,
rec_.serial_begin - 1);

      ELSIF serial_end_exclude_ NOT BETWEEN rec_.serial_begin AND
rec_.serial_end AND
            serial_begin_exclude_ BETWEEN rec_.serial_begin AND rec_.serial_end
THEN

         INSERT INTO temp_serial_ranges VALUES (rec_.serial_end + 1,
serial_end_exclude_);

      ELSIF serial_begin_exclude_ NOT BETWEEN rec_.serial_begin AND
rec_.serial_end AND
            serial_end_exclude_ NOT BETWEEN rec_.serial_begin AND
rec_.serial_end AND
            rec_.serial_begin BETWEEN serial_begin_exclude_ AND
serial_end_exclude_ AND
            rec_.serial_end BETWEEN serial_begin_exclude_ AND
serial_end_exclude_  THEN

         INSERT INTO temp_serial_ranges VALUES (serial_begin_exclude_,
rec_.serial_begin - 1);
         INSERT INTO temp_serial_ranges VALUES (rec_.serial_end + 1,
serial_end_exclude_);

      END IF;
   END LOOP;
END Insert_Exclusions;
/

          Followup:

tell me -- is there a reasonable upper bound on serial start and serial end --
eg: 1-10000 or something?

Or is the range infinite -- I mean the values supplied AND the values in the
table?

      Rating: 5
Thanks for your
      patience  February 15, 2003
Reviewer:
      A reader
Tom,

We control the UPPER BOUND of the serial range(i.e. SERIAL END).

Assumption here is,

SERIAL START will always be > 0 (no negatives allowed)
SERIAL END can have a max value of say 999999. (This is setup by the user before
they use the application)

So, in essence, my MAX SERIAL START can be 999999 and
my MAX SERIAL END can be 999999. We cannot exceed 999999, for either SERIAL
START or SERIAL END.

Thanks

          Followup:

Ok, so you make me think about it and -- well, there is a SQL solution after all
(isn't there always....)

ops$tkyte@ORA920> create table t ( ss int, se int );
Table created.

ops$tkyte@ORA920> variable serial_start number;
ops$tkyte@ORA920> variable serial_end   number;
ops$tkyte@ORA920> exec :serial_start := 30; :serial_end := 40;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from t;

        SS         SE
---------- ----------
        40        140
        60        145
       100        110
       150        250

ops$tkyte@ORA920> select max( decode(mod(rn,2), 1, r, null ) ),
  2         max( decode(mod(rn,2), 0, r, null ) )
  3    from (
  4  select r, last_r, next_r, row_number() over ( order by r ) rn
  5    from (
  6  select r,
  7         nvl(lag(r) over ( order by r )+1, -1) last_r,
  8         nvl(lead(r) over ( order by r )-1, -1 ) next_r
  9    from ( select rownum + :serial_start-1 r
 10             from all_objects
 11                    where rownum <= :serial_end-:serial_start+1 )
 12   where 0 = (select count(*)
 13                from t where ss <= r
 14                             and se >= r
 15                             AND rownum = 1)
 16         )
 17   where r <> last_r or r <> next_r
 18         )
 19   group by ceil(rn/2)
 20  /

MAX(DECODE(MOD(RN,2),1,R,NULL)) MAX(DECODE(MOD(RN,2),0,R,NULL))
------------------------------- -------------------------------
                             30                              39

ops$tkyte@ORA920> exec :serial_start := 150; :serial_end := 250;
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from t;

        SS         SE
---------- ----------
       200        230

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select max( decode(mod(rn,2), 1, r, null ) ),
  2         max( decode(mod(rn,2), 0, r, null ) )
  3    from (
  4  select r, last_r, next_r, row_number() over ( order by r ) rn
  5    from (
  6  select r,
  7         nvl(lag(r) over ( order by r )+1, -1) last_r,
  8         nvl(lead(r) over ( order by r )-1, -1 ) next_r
  9    from ( select rownum + :serial_start-1 r
 10             from all_objects
 11                    where rownum <= :serial_end-:serial_start+1 )
 12   where 0 = (select count(*)
 13                from t where ss <= r
 14                             and se >= r
 15                             AND rownum = 1)
 16         )
 17   where r <> last_r or r <> next_r
 18         )
 19   group by ceil(rn/2)
 20  /

MAX(DECODE(MOD(RN,2),1,R,NULL)) MAX(DECODE(MOD(RN,2),0,R,NULL))
------------------------------- -------------------------------
                            150                             199
                            231                             250

ops$tkyte@ORA920> exec :serial_start := 90; :serial_end := 120;
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from t;

        SS         SE
---------- ----------
        50         98
       100        150
       160        190
       200        230

ops$tkyte@ORA920> select max( decode(mod(rn,2), 1, r, null ) ),
  2         max( decode(mod(rn,2), 0, r, null ) )
  3    from (
  4  select r, last_r, next_r, row_number() over ( order by r ) rn
  5    from (
  6  select r,
  7         nvl(lag(r) over ( order by r )+1, -1) last_r,
  8         nvl(lead(r) over ( order by r )-1, -1 ) next_r
  9    from ( select rownum + :serial_start-1 r
 10             from all_objects
 11                    where rownum <= :serial_end-:serial_start+1 )
 12   where 0 = (select count(*)
 13                from t where ss <= r
 14                             and se >= r
 15                             AND rownum = 1)
 16         )
 17   where r <> last_r or r <> next_r
 18         )
 19   group by ceil(rn/2)
 20  /

MAX(DECODE(MOD(RN,2),1,R,NULL)) MAX(DECODE(MOD(RN,2),0,R,NULL))
------------------------------- -------------------------------
                             99

Now that works as long as the number of rows in all objects exceeds the
difference between start and stop.  If not, you can always go one more step:

ops$tkyte@ORA920> create or replace type numArray as table of number
  2  /
Type created.

ops$tkyte@ORA920> create or replace function virtual_table( p_numrows in number
) return numArray
  2  pipelined
  3  as
  4  begin
  5          for i in 1 .. p_numrows loop
  6                  pipe row(i);
  7          end loop;
  8          return;
  9  end;
 10  /

Function created.

ops$tkyte@ORA920> variable serial_start number;
ops$tkyte@ORA920> variable serial_end   number;
ops$tkyte@ORA920> exec :serial_start := 30; :serial_end := 40;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from t;

        SS         SE
---------- ----------
        40        140
        60        145
       100        110
       150        250

ops$tkyte@ORA920> select max( decode(mod(rn,2), 1, r, null ) ),
  2         max( decode(mod(rn,2), 0, r, null ) )
  3    from (
  4  select r, last_r, next_r, row_number() over ( order by r ) rn
  5    from (
  6  select r,
  7         nvl(lag(r) over ( order by r )+1, -1) last_r,
  8         nvl(lead(r) over ( order by r )-1, -1 ) next_r
  9    from ( select rownum + :serial_start-1 r
 10             from TABLE(virtual_table(:serial_end-:serial_start+1))
 11             )
 12   where 0 = (select count(*)
 13                from t where ss <= r
 14                             and se >= r
 15                             AND rownum = 1)
 16         )
 17   where r <> last_r or r <> next_r
 18         )
 19   group by ceil(rn/2)
 20  /

MAX(DECODE(MOD(RN,2),1,R,NULL)) MAX(DECODE(MOD(RN,2),0,R,NULL))
------------------------------- -------------------------------
                             30                              39

Now for the "how and why" it works.

Read the query from the inside out.  Take each query and run it in turn
(removing aggregates).  Like this:

ops$tkyte@ORA920> select rownum + :serial_start-1 r
  2    from TABLE(virtual_table(:serial_end-:serial_start+1))
  3  /

         R
----------
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40

11 rows selected.

there is every number in our "series"...

ops$tkyte@ORA920> select r,
  2         nvl(lag(r) over ( order by r )+1, -1) last_r,
  3         nvl(lead(r) over ( order by r )-1, -1 ) next_r
  4    from ( select rownum + :serial_start-1 r
  5             from TABLE(virtual_table(:serial_end-:serial_start+1))
  6             )
  7   where 0 = (select count(*)
  8                from t where ss <= r
  9                             and se >= r
 10                             AND rownum = 1)
 11  /

         R     LAST_R     NEXT_R
---------- ---------- ----------
        30         -1         30
        31         31         31
        32         32         32
        33         33         33
        34         34         34
        35         35         35
        36         36         36
        37         37         37
        38         38         38
        39         39         -1

10 rows selected.

now for each number in our series -- only keep the ones that are NOT already
in the table (thats what 0 = (select count(*) ...) does.

For each one not in the table already -- look backwards and forwards in the set
and grab the prior/next R.  If null (end points in the set), use -1 (you said
they must be > 0 so that is "safe")

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select r, last_r, next_r, row_number() over ( order by r ) rn
  2    from (
  3  select r,
  4         nvl(lag(r) over ( order by r )+1, -1) last_r,
  5         nvl(lead(r) over ( order by r )-1, -1 ) next_r
  6    from ( select rownum + :serial_start-1 r
  7             from TABLE(virtual_table(:serial_end-:serial_start+1))
  8             )
  9   where 0 = (select count(*)
 10                from t where ss <= r
 11                             and se >= r
 12                             AND rownum = 1)
 13         )
 14   where r <> last_r or r <> next_r
 15  /

         R     LAST_R     NEXT_R         RN
---------- ---------- ---------- ----------
        30         -1         30          1
        39         39         -1          2

Next, we only keep the ones where the prior or next R is NOT minus or plus
1...  Also, we assign row_number() to each one in turn -- 1,2,3,4,5,6.... we'll
use this to pivot below.  Each ODD row will be a start point, each EVEN row an
end point:

ops$tkyte@ORA920> select ( decode(mod(rn,2), 1, r, null ) ),
  2         ( decode(mod(rn,2), 0, r, null ) ),
  3             ceil(rn/2)
  4    from (
  5  select r, last_r, next_r, row_number() over ( order by r ) rn
  6    from (
  7  select r,
  8         nvl(lag(r) over ( order by r )+1, -1) last_r,
  9         nvl(lead(r) over ( order by r )-1, -1 ) next_r
 10    from ( select rownum + :serial_start-1 r
 11             from TABLE(virtual_table(:serial_end-:serial_start+1))
 12             )
 13   where 0 = (select count(*)
 14                from t where ss <= r
 15                             and se >= r
 16                             AND rownum = 1)
 17         )
 18   where r <> last_r or r <> next_r
 19         )
 20  /

(DECODE(MOD(RN,2),1,R,NULL)) (DECODE(MOD(RN,2),0,R,NULL)) CEIL(RN/2)
---------------------------- ---------------------------- ----------
                          30                                       1
                                                       39          1

see how the decode makes a "sparse matrix", if we had more in the series --
like

MAX(DECODE(MOD(RN,2),1,R,NULL)) MAX(DECODE(MOD(RN,2),0,R,NULL))
------------------------------- -------------------------------
                            150                             199
                            231                             250

did, this would look like:

150                       1
            199           1
231                       2
            250           2

and the max "squishes out" the nulls for us nicely

QED ;)

      Rating: 5
You are GOD
      :-)  February 15, 2003
Reviewer:
      A reader
Tom,

Words are not sufficient to describe what you have done and I don't want to
embarrass you by saying anything else. I tested the 3 cases (you had that too !)
and it works like a charm. Now, the next difficult part is to take this master
query of yours and understand. I went through your explanation (thank you for
that, it was a nice bonus) and to be honest don't seem to understand much. I
will keep going through it and hopefully it will make sense.

What else can I say Tom, except a sincere THANK YOU. By the way you mentioned
about QED. What does it stand for?

Thanks Tom. You are THE MAN !

      Rating: 5
Great Query
      But...........  February 16, 2003
Reviewer:
      Rasin
dread@ORA92>  exec :serial_start := 99; :serial_end := 200

PL/SQL procedure successfully completed.

dread@ORA92> select * from t;

        SS         SE
---------- ----------
        50         98
       100        150
       160        190
       200        230

dread@ORA92>  select max( decode(mod(rn,2), 1, r, null ) ),
  2            max( decode(mod(rn,2), 0, r, null ) )
  3       from (
  4     select r, last_r, next_r, row_number() over ( order by r ) rn
  5       from (
  6     select r,
  7            nvl(lag(r) over ( order by r )+1, -1) last_r,
  8            nvl(lead(r) over ( order by r )-1, -1 ) next_r
  9       from ( select rownum + :serial_start-1 r
 10                from TABLE(virtual_table(:serial_end-:serial_start+1))
 11                )
 12      where 0 = (select count(*)
 13                   from t where ss <= r
 14                                and se >= r
 15                                AND rownum = 1)
 16            )
 17      where r <> last_r or r <> next_r
 18            )
 19      group by ceil(rn/2)
 20  /

MAX(DECODE(MOD(RN,2),1,R,NULL)) MAX(DECODE(MOD(RN,2),0,R,NULL))
------------------------------- -------------------------------
                             99                             151
                            159                             191
                            199

The expected output from above should be
start end
99
151   159
191   199

The numbers are correct but the order is changed.

          Followup:

Ok, so i'll set up an infinite loop here.... related question:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7867939004010
had a similar problem and I mentioned there we could use the same cartesian
product on this question to fix the 99 NULL problem above.  Looks like I need it
for the more general case, here is the fix:

ops$tkyte@ORA920>    select max( decode(mod(rn,2), 1, r, null ) ),
  2            max( decode(mod(rn,2), 0, r, null ) )
  3       from (
  4     select r, last_r, next_r, row_number() over ( order by r ) rn
  5       from (
  6     select r,
  7            nvl(lag(r) over ( order by r )+1, -1) last_r,
  8            nvl(lead(r) over ( order by r )-1, -1 ) next_r
  9       from ( select rownum + :serial_start-1 r
 10                from TABLE(virtual_table(:serial_end-:serial_start+1))
 11                )
 12      where 0 = (select count(*)
 13                   from t where ss <= r
 14                                and se >= r
 15                                AND rownum = 1)
 16            ), (select 1 xx from dual union all select 2 xx from dual )
 17      where ( r <> last_r and xx = 1 ) or ( r <> next_r and xx = 2 )
 18            )
 19      group by ceil(rn/2)
 20  /

MAX(DECODE(MOD(RN,2),1,R,NULL)) MAX(DECODE(MOD(RN,2),0,R,NULL))
------------------------------- -------------------------------
                             99                              99
                            151                             159
                            191                             199

The code in bold is the "fix"

      Rating: 5
Thank You
      :-)  February 16, 2003
Reviewer:
      A reader

      Rating: 5
Great query
      after fix is now Greatest  February 17, 2003

Reviewer:  Rasin

      Rating: 5
Follow up and
      additional questions  February 20, 2003
Reviewer:
      A reader
Hi Tom,

I am running into 2 issues for this query. Maybe you can help me again.

1) When my table T has 1 record
SERIAL_START   SERIAL_END
-------------  ----------
1              200

User Input: Serial_Start = 1, Serial_End = 999999999

It takes a longgggg time to execute this query (more than 5 minutes). I
understand why it does this. Can this be in anyway improved. I am really happy
with the query but I will use this a few times in my application and the
response time is not very good.

2)I want to use this Query in a Package, but get compilation errors when I use
the VIRTUAL_TABLE. Is there any way I can get around this? Any pointers to
existing example would be really helpful.

Thanks for all your help

          Followup:

1) well, you just changed the design goals, remember when you said:

....
SERIAL START will always be > 0 (no negatives allowed)
SERIAL END can have a max value of say 999999. (This is setup by the user before

they use the application)
.......

You'll have to start investigating procedural implementations -- whereby you can
optimize the processing by using some nice algorithms (that you come up with ;)

2) what errors specifically?  If you do it the way I showed -- create the object
type, create the function, code the sql, it should work?

ops$tkyte@ORA920.US.ORACLE.COM> begin
  2      for x in ( select * from TABLE( virtual_table(10) ) )
  3      loop
  4          dbms_output.put_line( 'fetching.... ' || x.column_value );
  5      end loop;
  6  end;
  7  /

works dandy.  You are not trying to create the object type IN the plqsl package
are you?  It MUST be done in SQL.

      Rating:
      5
Thanks  February 23, 2003
Reviewer:
      A reader
Hi Tom,
I got your response but could not reply back soon. Thanks for your time. With
regards to your last reply:

1) Yes, I did say that the upper bound would be 9999. My fault. I should have
thought more about it before replying to that one. Next time, you ask a
question, I would probably think twice as this might make a real difference to
the solution you would be proposing.. But, procedural option is the way to go
and it is working OK. No problems with performance. The good thing about your
master query is it has made me realize how powerful Analytic functions can be.
So, THANK YOU.

2) I should have been more specific. I was able to create the object type,
create the function, code the sql and was able to run in SQL-Plus. I was trying
to use the SQL Code in PL/SQL package. I had already created the Object Type in
SQL-Plus. I guess, I cannot call use it in PL/SQL packages?

Tom, once again, thanks for all your time. It was a pleasure.

          Followup:

2) yes you can -- you need to give us an example.  I use them in plsql all of
the time (packages or not).

      Rating:
      5
  February 25, 2003
Reviewer:
      A reader
Tom,

I have not tried this again. I did not try hard enough to see what the problem
was. Since you say you have used it, I am sure the problem is on my side. I will
re-test it carefully again.

Thanks

February 23, 2003 Vinod -- Thanks for the question regarding
      "Optimizing Bulk Collects", version 8.1.7

      You Asked
Hi Tom,

The following code will work fast if the resultset is small (say 300 Records),
but if there are around 100,000 records how can this be made to work faster. Is
there is a way in PL/SQL to fetch the records asynchronously and insert (i.e.
Fetch for first 300 records and insert, then the other 300 and so on in
parallel).

Declare
  Type tName Is Table Of Varchar2(30) Index By Binary_Integer ;
  vName tName ;
Begin
  Select Name Bulk Collect Into vName From Emp ;
  ForAll I In vName.First..vName.Count
     Insert Into Temp(Name) Values (vName(I)) ;
  Commit ;
End ;

Thanks in advance.

Regards,
Vinod

      and we said...
umm,

insert into temp select name from emp;

and away you go.  Never ever do procedurally that which can be done in a single
SQL statement.

February 23, 2003 -- Thanks for the question regarding "two-phase
      commit from 9.2.0 to 7.3.x", version 9.2.0

      You Asked
hi, i am choi.

this is my urgent issue.

is it possible to make two-phase commit from 9.2.0 to 7.3.x ?
is it possible to make two-phase commit from 8.1.x to 7.3.x ?
is it possible to make dblink from 9.2.0 to 7.3.x ?

thank you for your kindness..

      and we said...
9.2 is known NOT TO WORK AT ALL WITH 7.x.  It is not only unsupported -- there
are major known issues that are not resovable.

9.2 and 7.x cannot talk.

8i and 7.x -- that was a supported configuration, yes, that works.
Review &
      Followup

      Rating: 5
Thank you for
      your kindness.  February 23, 2003
Reviewer:
      Byoungwan Choi
Thank you for your kindness.

February 23, 2003 Venkatesh -- Thanks for the question regarding
      "Import from JA16SJIS to UTF8 ...", version 8.1.6

      You Asked
Hi Tom ,
Need to export a JA16SJIS database and import the same into a UTF8 Db . Problems
encountered are as follows .

On the JA16SJIS source db set NLS_LANG=AMERICAN_AMERICA.UTF8
Then perform the export which results in ...

Export: Release 8.1.6.0.0 - Production on Sun Feb 23 12:42:17 2003

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.6.0.0 - Production
With the Partitioning option
JServer Release 8.1.6.0.0 - Production
Export done in UTF8 character set and JA16SJIS NCHAR character set
server uses JA16SJIS character set (possible charset conversion)

About to export specified tables via Conventional Path ...
Current user changed to EXHELP2YMS
. . exporting table                   HPSYS_COMENT
EXP-00015: error on row 0 of table HPSYS_COMENT, column TOKUBETU_COMENT, datatyp
e 1
EXP-00001: data field truncation - column length=600, buffer size=600 actual siz
e=0
Export terminated successfully with warnings.

Does not work despite setting high buffer value and there is sufficient disk
space to perform the export (Metalink suggests this could be a reason)
*********************************************************************

Solution : I set the NLS to JA16SJIS  and exported successfully .

IMPORTING TO TARGET UTF8 DB : SET NLS_LANG to JA16SJIS it throws an error like

IMP-00019: row rejected due to ORACLE error 1401
IMP-00003: ORACLE error 1401 encountered
ORA-01401: inserted value too large for column
Column 1 JR0038

WHEN SET TO UTF-8 Reports CONVERSION NOT POSSIBLE ,this I understand as Import
utility does not perform conversion from one multibyte char set to another . But
why does the former method fail . I set the buffer to a very large value ,but
fails .

Please suggest a solution and an explanation for the same .
Thanx in advance .
Venki

      and we said...
Well export and import actually do do the character set conversions -- whenever
possible the CLIENT, not the database does them.

but, that aside -- is the column JR0038 actually large enough -- you know,
something that takes 30 bytes in one database might take 60 bytes in the other.
Have you looked at the actual size of that column to see if it might really need
to be increased???

In fact, in looking at note <Note:99120.1>, i would definitely say this is the
cause of BOTH errors -- that in ja16sjis this string takes X bytes and in UTF8
it is taking X+N bytes and X+N exceeds the MAXIMUM string length.

<quote>
Explanation 1
-------------
In the UTF8 characterset a lot of characters (like the ones from the extended
ASCII set) need more than 1 byte to be stored. For example in WE8ISO8859P1 and
similar single byte charactersets the character "ê" takes 1 byte of storage but
it takes 2 bytes to store the same character in UTF8.

A definition of a VARCHAR2 column gives the size in bytes, not in characters.
That means a varchar2(2) in a WE8ISO8859P1 database can store the string "êe"
without problems. A similar varchar2(2) column in a UTF8 database would not be
able to store it since it would take 3 bytes (2 for ê and 1 for the simple e)
to store that string.

When export starts it simply takes a look at the definition of a table and
expects data of that size. However, if the characterset in the NLS_LANG variable
is set to UTF8, the strings selected from the database will be converted into
UTF8. If we follow the above example again the following would happen: The
WE8ISO8859P1 database has got a varchar2(2) column that happily stores the
string "êe" in 2 bytes. Export starts and expects the strings to be of a
maximum size of 2 bytes. When the rows are returned they are converted into
UTF8, meaning that for the row with "êe" 3 bytes are presented to exp instead
of the expected 2 bytes. When this happens the EXP-15 and EXP-1 errors are
returned.

Solution 1
----------
Set the characterset in NLS_LANG to a single byte characterset that is correct
for your platform. There is no reason why this shouldn't be possible.
</quote>

so, you followed the work around but upon IMPORT you see the other side effect -
-the string in the dmp file is still TOO LARGE to fit in the column (it was too
large to have exp convert it, it is too large for imp to import it)

You must widen this column to hold this data.
Review &
      Followup

      Rating: 5
Character Set
      Scanner Utility - CSSCAN  February 24, 2003

Reviewer:  Logan Palanisamy  from Santa Clara, CA USA

How about using the Character Set Scanner Utility (CSSCAN) available in 9i.

http://download-west.oracle.com/docs/cd/A91202_01/901_doc/server.901/a90236.pdf
You specify the FROMCHAR and TOCHAR sets. It will tell you whether the data is
convertible. If not convertible, it will tell you which tables and which columns
have the problem data. You fix the data or increase the column width etc.

Hope that helps

February 24, 2003 Bhavesh -- Thanks for the question regarding "Two
      iAS related questions", version 8.1.7

      You Asked
Hi Tom,
    Hope you must be doing fine. Thanks for all your help in past.

A. I have written a search engine using intermedia aka context search. The
column on which search is done is CLOB data type. After the data is inserted
into the CLOB column of main table I have a back end procedure which breaks CLOB
data into individual word (this is determined by using space delimiter) and
inserts into the dict$ with word and soundex(word). Say what I have done is :

drop table dict$;
create table dict$
  (word varchar2(255),
   word_soundex varchar2(10));
insert into dict$ (word, word_soundex) values ('actor', soundex('actor'));
insert into dict$ (word, word_soundex) values ('acronym', soundex('acronym'));
--------------------------------------------------------------------
Now we have
SQL > select substr(word, 1, 15), word_soundex from dict$;

SUBSTR(WORD,1,1 WORD_SOUND
--------------- ----------
actor           A236
acronym         A265

Say user searches for word 'assent' which of course does not exists in the dict$
table. Now what I want do is :

1. If the word (assent here in this case) is not found then find
soundex('assent') which is 'A253'.
2. Next we compare the first character of the soundex which is 'A'. Now we
search for the nearest occurence soundex in dict$ table with first character is
'A'. From the above we see that A265 is more close to A253 than compared to
A236.
So the system should return that 'assent' not found. Are you looking for
'Acronym'.
Can this be done by a single sql statement or I need to write a procedure to
achieve this functionality.

B. Another place where I have got stuck up is that I have a web based html form
(page) which is getting generated from PSP procedure. Here I am generating two
drop down lists getting data from database.
Say I have two tables company master and branch master. One company can have
multiple branches. First list depicts the company name from company master. Now
in the list two which shows branch name should only populate the branches for
the company selected above. I mean something like when list changed should fire
and I can get only the values of selected company in the drop down list for
branch. Is this possible and if yes how ?
Thanks a million in advance. Awaiting to hear soon from you.
Best Regards

Bhavesh

      and we said...
why the heck would you do this?  Why would you not just use the native, builtin,
fast, easy, powerful feature interMedia already in the database?  It does
soundex, it does fuzzy, it does "near", it does and, it does or, it does a
million things that would take you literally months or years to write yourself.

I don't get it.

I don't understand your logic in #2 -- why do you believe a265 to be "closer" --
you are apparently using number comparisions on a soundex which just doesn't
make logical sense at all.

Yes, you can do this in a single query, but it would most likely be very
expensive with your existing structure.

for B, see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:234614318676

Review &
      Followup

      Rating: 4
Updated
      Info  June 27, 2002
Reviewer:  Bhavesh

Hi Tom,
   Thanks a million for your answer. Well my basic idea here is that suppose
user searches for word "dictionary". Now there can be two cases :
1. User keys in wrong spelling. Say he types "dictionry" instead of
"dictionary". Our database has got the word dictionary. So now search engine
instead of showing simply no or Zero records found, should retrun following
message :
Sorry there is no result for the word dictionry
Are you looking for dictionary instead (here the dictionary will be the url
which when clicked will pass dictionary to the search engine).
2. May be user is looking for "Horse" but my database does not contain this word
and the nearest meaning to this word in database is "Hose". So now the result
should state
Sorry there is no result for the word Horse.
Are you looking for Hose instead (here the dictionary will be the url which when
clicked will pass dictionary to the search engine).
I know Intermedia can return me the search results but how can I use Intermedia
to make it artificially intelligent ?
Thanks in advance.
Regards

          Followup:

That is not "artificially" intelligent -- It is a structured program that uses a
faulty (in my opinion) algorithm to find a "close" word.

You cannot sort by soundex and get anything "meaningful".  consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select soundex('horse'), soundex('hockey'),
soundex('hose') from dual;

SOUN SOUN SOUN
---- ---- ----
H620 H200 H200

hockey and hose are close?  no, not at all.

intermedia will allow you to say "Hey, I could not find horse - would you like
to try to look for things that sound like horse or maybe would you like to do a
FUZZY search for horse?"

That would be even better then trying to guess via soundex what word they were
trying to type in.

      Rating:
      4
Thanks  June 27, 2002
Reviewer:
      Bhavesh
Hi Tom,
   Thanks for your valuable comments. Well I think here Fuzzy search will be
more reasonable then going for soundex. Thanks once again for your time and
suggestions.
Regards

      Rating: 3
fuzzy
      preferences  February 24, 2003
Reviewer:
      Scott  from Perth, WA
G'day

I'm trying to find some more information on the fuzzy operator. This note found
at Metalink

http://metalink.oracle.com/metalink/plsql/ml2_documents.showDocument?p_database_id=FOR&p_id=436028.999
suggests "you better create a preference based on the
BASIC_WORDLIST", but doesn't actually clarify why.
The Oracle Text reference documentation doesn't seem to get too involved with
preference's attributes, just documents how to set them.
I can't seem to find further information on attributes such as (second parm):
ctx_ddl.set_attribute('STEM_FUZZY_PREF','FUZZY_MATCH','ENGLISH');
ctx_ddl.set_attribute('STEM_FUZZY_PREF','FUZZY_SCORE','0');
ctx_ddl.set_attribute('STEM_FUZZY_PREF','FUZZY_NUMRESULTS','5000');
ctx_ddl.set_attribute('STEM_FUZZY_PREF','SUBSTRING_INDEX','TRUE');
ctx_ddl.set_attribute('STEM_FUZZY_PREF','STEMMER','ENGLISH');

What do these preferences do?

Queries to the values currently in ctx_preferences and ctx_preference_values
don't let on much either.

Where can I get futher information?

          Followup:

They are documented in the text reference:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/text.920/a96518/cdatadic.htm#34699

February 24, 2003 Amitabha -- Thanks for the question regarding
      "How to create a block on a procedure in forms.", version

      You Asked
I have been using forms 6.0.5.29.4
I wanted to develop a screen based on a procedure
I followed all the steps described in the help file.

Created a package

Follwed the steps described in
"Creating a data block from a
procedure that uses a ref cursor"

When I try to run the new fmb the .fmx file is being created but

nothing is coming on the screen
What may be going wrong?
I am trying this for the first time,
we are going to develop something
based on procedures to improve performance of some of our query
only screens.

Can you send me a very simple sample of how to do this?

      and we said...
the step by step I just did was:

1) created the following package in the database:

create or replace package demo_pkg
as
    type refcur is ref cursor return emp%rowtype;

    procedure get_emp_data( p_cursor in out refcur );
end;
/

create or replace package body demo_pkg
as

procedure get_emp_data( p_cursor in out refcur )
is
begin
    open p_cursor for select * from emp;
end;

end;
/

2) using the data block wizard I entered

o build on stored procedure
o entered demo_pkg.get_emp_data and selected ALL of the columns
it showed me.
o left the insert/update/delete/lock procedures empty
o finished and went into the layout wizard.
o selected all columns for layout
o chose tabular
o set records to display to 4
o and finished

then I ran the form, hit F8 and the data was all there.

That was it.

Review &
      Followup

      Rating:
      3
insert/update/delete/lock procedures  October 30,
      2001
Reviewer:  Bai  from Ireland
Hi Tom,

is there any examples about insert/update/delete/lock procedures, if we don't
let them to be empty?

      Rating: 5
Client
      side  February 24, 2003
Reviewer:  Famy

Instead of creating the package in the database, can I create it at the client
i.e. as a forms package and base my block on it.

I tried to do that , and I'm getting the error  that 'forms is unable to query'

          Followup:

the ref cursor needs to come FROM the database.  it isn't a client side thing.

February 24, 2003 Marc -- Thanks for the question regarding
      "ORA-00666", version 8.1.7

      You Asked
Dear Tom,

I'm in serious trouble. Perhaps you could help me:

Endusers complained that the database wasn't accessible.
In the alert.log I found as last entry an ORA-00666.
The documentation says:

ORA-00666 Internal error code

Cause: There cannot be any doubt that Satan came up from hell and ate your
database.

Action: Pray!

What should I do by now?

Regards
Marc

      and we said...
I found this site:

http://www.stmichael.pair.com/
to be helpful in getting that error to go away.
Review &
      Followup

      Rating:
      5
  February 24, 2003
Reviewer:
      A reader

      Rating:
      5
;)  February 24, 2003
Reviewer:  Chuck
      Jolley  from OKC, OK USA
Thanks, I had been wondering about that one for a while now.
chuck

      Rating:
      4
];-P  February 24, 2003
Reviewer:  Dan
      Kefford  from New Haven, CT
O.K... how many of you readers went to the command line and typed oerr ora 666?
Come on... be honest.

      Rating: 5
me, for the
      moment.... :-)  February 24, 2003
Reviewer:
      Mario  from Rome, Europe
SQL> select name, where
 2   from (readers_went_to_the_command_line_and_typed_oerr_ora_666)

NAME          WHERE
------------- ----------------
Mario         Roma

          Followup:

oh, come on -- we all know that would get:

ORA-00972: identifier is too long

;)

      Rating:
      5
Hmm  February 24, 2003
Reviewer:
      Steve  from UK
Update l
set deem='ON'
where pan='Ex' OR Cyst IS NULL

Should solve the problem

      Rating: 5
Should also
      add.....  February 24, 2003
Reviewer:  Steve
       from UK
That l mentioned above is a view derived from:
v$fire and v$brimstone

Cheers
Steve

ps:  Perhaps I should bring out a book!!  "Don't ask Steve!"

      Rating: 5
Idiot =
      Me  February 24, 2003
Reviewer:  Embarassed
      Reader  from USA
update idiots
set idiots_who_clicked_on_link =
    idiots_who_clicked_on_link + 1
where username = me;

      Rating:
      5
  February 24, 2003
Reviewer:  Doug

Marc,
     First think I would say is
"I'm a gettin" outta here...

Then go get Pheobe Piper and Page
I see them beat demons in Charmed
on TV every week!

You've got nothing to lose, even if they can't do anything
whos going to complain about having to hang around Alyssa Milano

      Rating:
      5
What???????  February 24, 2003
Reviewer:
      Cefers.br  from Brasil
I have a question!!!

I clicked that link ten minutes ago...

Now, in the name of God, why am I still in there, reading the sections????

Perhaps I'am possessed by the site! Ohh my...

      Rating:
      5
Possesed...  February 25, 2003
Reviewer:
      Martin  from The Netherlands
Well I can't get away from this site since I found it, so probably I'm possesed.
No excorist needed though... ;-)

      Rating: 5
Too
      much  February 26, 2003
Reviewer:  Joel
       from Columbus, OH USA
After reading these responses, there's clearly too much freebasing of St. John's
Wort going on in the world.

      Rating: 5
Make this the
      number one topic  February 28, 2003
Reviewer:
      A reader
We need to put this topic in the number one spot on the "Hot" list.

      Rating:
      4
ORA-00666  February 28, 2003
Reviewer:
      Fred  from Seattle WA
I experienced this error running advanced replication on version 7.2.1.  We had
no tools to deal with it and actually had to eat the database and rebuild from
scratch.  Since then I've kept chicken,whiskey and cigars in my bottom cabinet.

February 25, 2003 Angelo -- Thanks for the question regarding
      "capturing dup val on ind exception", version 8.1.7.4

      You Asked
Tom,

I want to be able to capture a DUP_VAL_ON_INDEX exception but it seems I might
be having a scope problem.

I want to be able to set a counter(rollup) in a row when duplicate entries are
attempted to be input.

I have coded the DUP_VAL_ON_INDEX in the exception block of a before insert
trigger hoping to capture this exception.  The insert occurs in a java
application.  During testing the exception occurs but it seems that the
exception block is not executing.

I then thought that if I built a function based index for this table that I
could capture the exception there but that is not happening either.

What's my problem?  Does it have to do with the scope of the exception?

Is there a work around or another way of doing this.

I beleive i've been able to capture other exceptions in a trigger what makes
this one different?

Thanks

      and we said...
The java program is the guy getting the dup val error -- the trigger won't get
that exception UNLESS the trigger itself does some modification that throws this
error.

You cannot catch a callers error - as their is no error UNTIL AFTER the
statement has completed.  You've never been able to do this or anything similar
-- ever.

The exception block in the trigger is useful to catch errors the TRIGGER itself
encounters.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1629338325252
you can use a view and an INSTEAD OF TRIGGER.  The instead of trigger will
attempt  the insert and upon dup_val_on_index -- do an update.

The java programmers will only see the view -- not the table.  Just rename the
table, create a view by the same name using "select * from table" and they will
not even know you did this.

(in general, it will be more performant to attempt the update and then do the
insert if zero rows were updated if you can)

Review &
      Followup

      Rating:
      3
  February 24, 2003
Reviewer:
      A reader
Thanks,

I thought about the instead trigger, they work well but the table is partitoned
and creates other problems.  I'll talk to the developer and see if they can
handle it in their code.

          Followup:

it is all about stored procedures -- revoke insert and make them use a stored
procedure.

Not that I can forsee the problem with partitions?

      Rating: 4
what about
      MERGE ...  February 24, 2003
Reviewer:
      J.Nemec  from Austria
Good point to know that it is better first to update and than to insert!
What ist the main difference to "first insert and than update"? Is it the
additional overhead with the unique constraint exception?
What about using MERGE statement, will the performance be better compared to
instead of trigger or PL/SQL solutions?

Thanks

Jaromir

http://www.db-nemec.com/

          Followup:

it is the handling of the unique constraint (expensive) if it happens alot.  The
insert does a bit of work to find out "it is already there"

so you would see more recursive sql, more redo generated.

MERGE is basically doing in a single sql statement what the procedural code
would be doing -- and that is good ;)

February 25, 2003 Karthik -- Thanks for the question regarding
      "Complicated sorting required", version 8.1.7

      You Asked
I have a table in which a varchar column contains data like shown below:

100
20A
200A
1000
30
200d
35C
400D
500U

etc. I want a query or a function which will return the ascending sorted set
like this:

20A
30
35C
100
200d
200A
400D
500U
1000

i.e the numbers should be given preference in ascending or descending sort in
normal manner and then uppper case letters like A,B,C... should be given
preference for descending sort and lower letters like a,b,c.. for ascending
sort. The above data in descending sort would be like this:

1000
500U
400D
200A
200d
100
35C
30
20A

is there a way to achieve this? Please help.

      and we said...
the way to achieve this would be to store NUMBERS as NUMBERS and STRINGS as
STRINGS.  You obviously have two fields here -- not one.  You should definitely
have stored them as TWO fields.

If the fields are always simple like you have above -- some numbers followed by
some characters, you can do this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select x,
  2  to_number(
substr(x,1,length(x)-nvl(length(replace(translate(x,'0123456789','0000000000'),'0
','')),0)) ) n,
  3
substr(x,length(x)-nvl(length(replace(translate(x,'0123456789','0000000000'),'0',
'')),0)+1) s
  4  from t
  5  order by 2 DESC, 3 DESC
  6  /

X                   N S
---------- ---------- ----------
1000             1000
500U              500 U
400D              400 D
200d              200 d
200A              200 A
100               100
35C                35 C
30                 30
20A                20 A

9 rows selected.

Review &
      Followup

      Rating: 4
More details
      required  February 24, 2003
Reviewer:
      Sampreetha  from India
HI tom,
  The solution is very good. But the solution seems to be tricky also. Can you
please explain the steps involved in more detail. I am trying to figure it out,
but finding it difficult to understand, especially the portion after order by
i.e. the details regarding the 2 & 3 in order by clause.

          Followup:

I'm just using standard builtin functions substr, instr, replace and translate?

Just add more columns to the query to see what it is doing:

see what translate(x,'0123456789','0000000000')  returns

and then replace(translate(x,'0123456789','0000000000'),'0','')

and so on -- it is just "math"

February 25, 2003 jamal -- Thanks for the question regarding " DML
      UTILITY", version 8.17

      You Asked
 Hay iwant to develop a utility ie write a database trigger to find any dml
operation in the database .
iam bale to write a trigger for DDL but not successful in writing for DML .
Help me out
thanx in advance.

      and we said...
This is generally referred to as "auditing"

It is a builtin function of the database, you just "enable it".

The admin guide has information on doing this.  You do not code triggers for
this, it just happens.
Review & Followup

      Rating: 3
audit vs.
      triggers  February 24, 2003
Reviewer:  Jens

what are the pros & cons on triggers and auditing to catch DML operations. can
one use audit to implement (customisable) DML notification?

          Followup:

what is "customisable" dml notification

anyway -- here is my list of pros/cons

supplied auditing
pros: builtin, no code, fast, easy to turn on, easy to turn off
cons:

do it yourself auditing
pros: feeling of satisfaction that you did it yourself
      questionable ability to capture before images of records (there are
      other ways to do that if you really need to)
cons: not builtin
      lots of code
      potential massive impact on performance
      must write code object by object by object to turn on

      Rating:
      4
  February 24, 2003
Reviewer:  Jens

that sounds clear.

"customisable" means (for us) that the option could be turned on/off as needed
and that the granularity of observation could be configured.

could you please explain, what "questionable ability to capture before images of
records (there are other ways to do that if you really need to)" means? did you
miss the "NO" there? as far as I have read this can't be achieved via auditing.
am I right?

          Followup:

I was using questionable to modify the word "ability".  I see people develop
this sort of "audit" for every table in their system (well, we don't know what
we might want...) and then wonder "why the heck is this so slow"

      Rating: 3
Customizable
      Auditing  February 24, 2003
Reviewer:  Kamal
      Kishore  from New Jersey
We have a requirement to do the auditing of all the actions
on certain tables (Insert/Update/Delete) and capture all the
data values (in all the columns) that have changed into a
separate table (one for each base table). This provides
a way to maintain the audit (more precisly the "History")
of updates to the data.
Is Oracle inbuilt auditing mechanism able to do this kind and level of auditing?
As far as I know, Oracle does not provide column level auditing.
Is this correct?

          Followup:

search this site for

workspace manager

and see if it meets your needs.  we don't use another table, no reason too.

      Rating:
      3
  February 25, 2003
Reviewer:
      vijays
In 9iR2 there is something called workspace management. I ve recently used
this..It is really brilliant...It can keep the AUDIT TRAIL for your tables..

Except that Tom, It creates whole lots of views and tables...!!!!

          Followup:

so?  they are the views that show you what's happened.

As the line in a famous movie goes "ignore that man behind the curtain".

If the "clutter" is bothersome -- put the tables in schema A, version them,
grant on the main "view" to schema B, in schema B create a view as select * from
a.view and just use B.

      Rating: 5
workspace
      manager  February 26, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, US
At this time we are running Oracle8i (have plans to move to Oracle9i soon).
so, I guess, till we get Oracle9i, the only option we have is to develop a
customized auditing system (obviously with the drawback of writing a lot of code
and maintaining it).

The example that you provided for the use of workspace manager to version your
tables was excellent. It showed exactly what we would get (and we get a lot), by
using workspace manager feature.
Thanks,

February 25, 2003 Ramachandran -- Thanks for the question regarding
      "Question on alter table and views", version 8.1.7

      You Asked
1. The following scenario confused me a bit about views.

table t with columns c1, c2, c3.
view t_v1 : which has only 2 cols, c1 & c2.
view t_v2 : which has all 3 cols c1, c2, c3.

i havent (yet) used t_v2 anywhere in my application yet.
i have an insert trigger on table t where i explicitly assign value like:

:new.c3 := 'somevalue';

now i know that all inserts are thru the view t_v1 and none are directly thru
the table t. my question is how does the insert work in this case and sets the
value for the column c3 ?

i am assuming that the insert on the view will be replaced with something like:

insert into t_v1....will be replaced by
insert into ( select c1, c2 from t )...

i mean we specifically have a view because we dont want to see the column c3. so
how come we are able to set the value for c3 ? is query rewrite taking place
here ?

2. this is about adding a "new default not null column " to a table.

say i have table t with a few columns.
i now do :

alter table t add new_column char(1) default 'Y' not null;

when i look at tkprof i see 2 statements for the above one:
one is the exact statement that i typed; another one is:
update t set new_column = 'Y';

this makes sense, but alter table statement is a DDL and gets committed. what if
there is a problem during the update statement ?
what happens in this situation ?
i know that my questions are very basic. pls bear with me.
thanks !

      and we said...
well, the trigger is on the table -- an insert like:

insert into t_v1 values ( x,y )

is really

insert into T (c1,c2) values (x,y);

no different really.

Why would
insert into t_v1....will be replaced by
insert into ( select c1, c2 from t )...

??? that doesn't even make sense -- the view is just a "synonym" of sorts, it
contains no data, it is just a stored query.

Your CALLER is not setting the value for C3, your trigger is.  You are not able
to set the value for c3, the owner of the trigger is doing that.  two separate
and distinct events.

2) ddl is done like this:

begin
    commit;
    begin
       do the ddl -- including any and all recursive sql needed, like your
       update statement for example;

       commit;
    exception
       when others then
          rollback;
          raise;
    end;
end;

the update IS PART OF the ddl itself.

Review &
      Followup

      Rating: 5
thank
      you.  February 24, 2003
Reviewer:  Ram
       from india
got it now. since a select * from view is equivalent to a select * from (select
* from table) i guessed (wrongly) that an insert will take the same route.
thanks once again.

      Rating:
      5
but...  February 25, 2003
Reviewer:
      Ram  from india
there is still something that i am missing here.
when i look at tkprof i see:
insert into T (c1,c2) values (x,y);
this is exactly as you pointed out it will be.
but i dont see any statement that indicates a value was set for column c3 via
the trigger. if this is taken care of by the owner of the trigger and not the
caller of the insert where is this event logged ?

          Followup:

the trigger runs in the background - you'll see any sql the trigger runs but the
trigger is just a "side effect".  you won't see it in the trace per say

February 25, 2003 -- Thanks for the question regarding "Min, Max",
      version 8.1.7

      You Asked
Tom,

I have following task to accomplish:

DateCompleted:
9/25/2002
4/12/2002
7/26/2002

I need to get max(datecompleted) as well as min(datecompleted) which
would be the closest to the max(datecompleted), that is,

DateCompleted:       Max          Min
9/25/2002           9/25/2002   7/26/2002
4/12/2002           9/25/2002   7/26/2002
7/26/2002           9/25/2002   7/26/2002

Also, there are could be cases like (where min and max would be just regular min
and max :

DateCompleted:       Max          Min
7/24/2002           7/24/2002    2/14/2002
2/14/2002           7/24/2002    2/14/2002

Thank You, very much

In case # 1
For min I have to get the maximum value of all minimum
values(4/12/2002,7/26/2002), closest to my maximum value(9/25/2002) - the most
recent historical value in relation to my max value(9/25/2002)

Lots of thanks

      and we said...
ops$tkyte@ORA920> select *
  2    from all_users,
  3        (select created min_create
  4           from (select created, rownum rn
  5                   from (select created
  6                           from all_users
  7                          order by created desc )
  8                   where rownum <= 2 )
  9              where rn = 2 )
 10   order by created desc
 11  /

USERNAME                          USER_ID CREATED   MIN_CREAT
------------------------------ ---------- --------- ---------
MV_DEMO                               114 13-FEB-03 11-FEB-03
DEMO010                               113 11-FEB-03 11-FEB-03
DEMO008                               112 11-FEB-03 11-FEB-03
DEMO007                               111 11-FEB-03 11-FEB-03
.......

just cartesian product with that query that gets the "2cnd" row
February 25, 2003 Marc -- Thanks for the question regarding
      "Monitoring Alert file", version 8.1.6

      You Asked
Dear Tom,

I would like to monitor ORA- messages on a Db whithout having
access to the filesystem (to the alert file...)
Is there a view which allows that or do you have an idea ?
Thanks for your help

Marc

      and we said...
There is no view but if you have an account with:

o select on v_$parameter
o select on v_$thread
o create any directory

(you could remove the dependencies on v$parameter and v$thread
but you would have to supply the alert log name to this routine)
you could use a setup like the following:

drop table alert_log;

create global temporary table alert_log
( line   int primary key,
  text   varchar2(4000)
)
on commit preserve rows
/

create or replace procedure load_alert
as
    l_background_dump_dest   v$parameter.value%type;
    l_filename               varchar2(255);
    l_bfile                  bfile;
    l_last                   number;
    l_current                number;
    l_start                  number := dbms_utility.get_time;
begin
    select a.value, 'alert_' || b.instance || '.log'
      into l_background_dump_dest, l_filename
      from v$parameter a, v$thread b
     where a.name = 'background_dump_dest';

    execute immediate
    'create or replace directory x$alert_log$x as
    ''' || l_background_dump_dest || '''';

    dbms_output.put_line( l_background_dump_dest );
    dbms_output.put_line( l_filename );

    delete from alert_log;

    l_bfile := bfilename( 'X$ALERT_LOG$X', l_filename );
    dbms_lob.fileopen( l_bfile );

    l_last := 1;
    for l_line in 1 .. 50000
    loop

        dbms_application_info.set_client_info( l_line || ', ' ||
        to_char(round((dbms_utility.get_time-l_start)/100, 2 ) )

        || ', '||
        to_char((dbms_utility.get_time-l_start)/l_line)
        );
        l_current := dbms_lob.instr( l_bfile, '0A', l_last, 1 );
        exit when (nvl(l_current,0) = 0);

        insert into alert_log
        ( line, text )
        values
        ( l_line,
          utl_raw.cast_to_varchar2(
              dbms_lob.substr( l_bfile, l_current-l_last+1,
                                                    l_last ) )
        );
        l_last := l_current+1;
    end loop;

    dbms_lob.fileclose(l_bfile);
end;
/

It'll use a bfile to load up a temporary table with each line of
your alert log.  On my particular system, this loaded up 50,000
lines in about 1.5 minutes so its not the speediest but it
works.

Enhancements:

- this should be a package.  the package would remember where I
left off bytewise so that I could call a refresh that would load
just NEW lines instead of the entire file again.  That way, if I
kept a session open for a while -- i could load it once and just
refresh during the day.

- this should let me pass in some percentage of the file to load
-- eg: load the last 10% of the alert log.  That would make this
apparently faster as well.  you would just use
dbms_lob.getlength to figure out what 10% of the file represents
and start at that offset (l_last := 1 at the top of the loop
would be changed).
Review & Followup

      Rating: 3
It's improper
      to manage the alert files within the database  February 24,
      2003
Reviewer:  Jianhui (Frank)  from Washington, DC

Tom,
  Although you gave a method to load alert file into DB, but the goal of alert
file is to log the errors when the DB goes wrong, so it's not right to manage
the alert file on top of the DB layer. For example, when your DB goes down or
halt, how could you get the alert message from the DB?
  It should be up to the OS level to check the alert files.
I use the shell script to rename the alert file for backup, then scan the
important keywords in the file to generate the alert message (email or pager) to
DBAs. On the other hand, the Oracle will generate a new alert file which will be
backuped and scaned when the next scheduled time comes.

          Followup:

guess what -- if you cannot log into the database to view the alert log -- hmm,
maybe that would be enough of an indication that the DB is down?

and if you read the question:

I would like to monitor ORA- messages on a Db whithout having access to the
filesystem (to the alert file...)

.......

      Rating: 5
Alert log in
      external table?  February 25, 2003
Reviewer:
      Will  from Austin
Would you have any issues with using an external table to access an alert log
from the DB?

          Followup:

nope, that would work nicely.

February 25, 2003 spawn -- Thanks for the question regarding
      "Oracle compared to other databases", version 8.1.7

      You Asked
Hi Tom,

     Sorry but this is not really much of a technical question.  hope you'd bear
with me. Just like to burst out some emotions. he he he. Sometime ago, I had an
interview. Not knowing that the company I was applying for did not use Oracle
RDBMS. The interviewer was kind to me. ALthough I can see the disbelief in his
eyes as he scanned my cv. He said that my experience was really mostly on Oracle
products. They were using SQL Server, DB2 and he said SQLBase(dont know what
that is). Then, their big boss suddenly came. Curious, he was informed that I
was an applicant. He learned that I was an Oracle DBA.(FYI, I learned that he
was formerly from IBM). He started to ask me non-technical questions on the spot
as if trying to catch me off-guard). He asked me the difference between
hierarchical and relational databases. I didn't give it a thought and told him
that I didnt know. Then there were questions that were sort of techincal like
schema(and of course I know it). But he didnt give me time to answer and just
started to underestimate the powers of the oracle database. I was just smiling
at him all that time(I said to myself, this man doesn't know the many features
of oracle).I knew that Oracle RDBMS was number one so I didn't think that his
argument was nonsense. I tried to persuade them that Oracle being number one is
true because of the various features it has that is way beyond compare from
other databases.

Tom, I am not knowledgeable on other databases and when I do get the chance to
defend Oracle again, I would like to be able to say something that would leave
my audience(non-oracle believers) tongue-tied. I know the many features of
oracle but am not sure if other databases have them. I would like to be able to
argue with them in a way that I would be able to convince them that Oracle is
better, and that they should use it instead of other databases.(and that they
should hire me to help them develop their systems on oracle. he he he). anyway,
thanks tom. sorry for this non-technical question. It's just that it was my
first time to encounter an oracle antagonist.

      and we said...
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1886476148373
I use that story every time.  We all have literally thousands of features.
going "tit for tat" will never work.

I would ask them "so, 5 years ago -- 1997 -- were you running large mission
critical applications on windows" (this invariably gets a chuckle, "no of course
not").  What makes you believe that 5 years from now you will still be?  Go back
5 years more, it is 1992 -- Unix was *just starting* to be totally trusted in
the data center (at Oracle, it was all about VAX back then).  So, if you had
choosen a database that was tied to an operating system -- where exactly would
you be?  (you can ask the fed govt actually -- they bought into this "one
hardware", "one os", "one software vendor" thing big time in the early 90s -- do
you remember a company called Wang?  sold the machine, the OS, the
spreadsheets/word processors/database on a system that followed their standards
and no one elses.  Sound like something you are familar with?)

So, why would you pick a database that locks you into an OS like sqlserver?
Even DB2 does this -- if you build an application on DB2/AIX can you put it on
DB2/Linux?  Maybe but probably not if it is a non-trivial application as their
linux port is missing tons of functionality they have on their AIX port (their
AIX port has the most features -- all other unixes have bits and pieces of the
functionality as they port the bits and pieces).  How about moving from DB2
mainframe to DB2 windows -- is that just "export import" and you are running
(no, not even close).  Ask IBM how many databases (relational and hierarchical
and flat file) it took for them to even come close to the market share Oracle
has with a single, homogenous product set?  I know of something like 7 or 8
RELATIONAL databases and then there is ims, idms, etc etc etc -- all of that
lumped together.  None of them interoperable to a large degree....

Review &
      Followup

      Rating:
      5
oracle  November 11, 2002
Reviewer:
      mo
Excellent answer.

Plain and simple "Oracle" is the best DBMS in the world and "Tom" is the best
oracle guru on planet

      Rating: 4
What about
      pl/sql?  November 11, 2002
Reviewer:  Shrek
       from MD
Among the many other things I sorely miss in other databases, one is pl/sql. I
have used Sybase, and SQL-Server, there are times you need loops, conditions,
and arrays in a program and you need to use (sybase, sql) cursors and temp
tables in a convoluted fashion, which are very slow and resource consuming
operations, and just not implemented the same as in Oracle. From time to time, I
use these other databases, and the one thing I consistently miss is the ease and
convenience of pl/sql.

      Rating: 5
I battle this
      everyday  November 11, 2002
Reviewer:  Bill
       from Cal
Everyday i get this from our developers: why is sql server faster than Oracle,
why spend the money on Oracle when it is slower than sql server, why is Oracle
so complex i can drop my database in sql server with a right-mouse click, etc
etc
The developers point-of-view is based on ONE O/S locked application that was not
designed nor implemented correctly. Frequent commits out of transactions and
lack of bind variables and thrashing shared pool KILLS our Oracle
implementation.
I should get a copy of Toms book for each of them...

      Rating: 4
can't make a
      cow dance....  November 11, 2002
Reviewer:
      Robert  from PA
>>I should get a copy of Toms book for each of them...

And you really believe they are going to read past the first chapter ???

      Rating: 5
Oracle database
      difference from other databases.  November 11, 2002

Reviewer:  spawn  from Philippines
Thanks Tom for the very informative answer. And the link was really helpful. Now
I know how to address people who dont believe in the "Powers of Oracle
Database". he he he. Next time I encounter one, I'm ready with the knowledge you
just shared.

      Rating: 5
Oracle
      Technology cannot be compared  November 12, 2002

Reviewer:  Tarry Singh  from Assen,The Netherlands

The dedicated professionals that oracle has, including tom kyte himself, are the
driving force behind oracle technologies.
Oracle has not only been the first to do all that may not need any introduction
but also the one where you can derive more from it.
If you want to talk about marketing, I'm gonna tell you, market it the way tom
does, [b]show it!!![/b] It's eventually a (amalgam of financial/technical)
choice to choose for whatever you want, but by choosing oracle you don't have to
worry about [b] the bus stop[/b] like if windos goes bust ,so does sqlserver.

      Rating: 4
Your
      views...  February 25, 2003
Reviewer:
      A reader
Hi Tom,
Regarding
"Even DB2 does this -- if you build an application on DB2/AIX can you put it on
DB2/Linux? "
Correct me if I am wrong, I came to know that Oracle is releasing oracle 10i
which may support RAC for other Unix platforms of importance. So, If I have an
application running on supported unix RAC, how can I say that oracle is same if
I want to port to AIX/unix the application(i.e., currently not supported for
RAC)?
Thanks

          Followup:

Well, Oracle 9i -- in fact Oracle8i, Oracle8, Oracle7 -- all have supported
clusters on AIX for years and years

So, the premise you are coming from 'that rac isn't available' is flawed.

We support RAC on all platforms of consequence today -- already.

      Rating: 4
Thanks. What
      about this...  February 25, 2003
Reviewer:
      A reader
Hi Tom,
Actually, I was referring to the News
from...

http://www.internetweek.com/breakingNews/INW20021115S0002
"
Oracle 10i To Offer Clustering On Sun, HP, IBM Unix Versions

By Techweb News

Oracle plans to upgrade its database next year with improvements in capacity and
clustering, according to reports.

The clustering functionality of Oracle Real Application Clusters, which detects
when nodes are failing, will be extended to Unix systems from Sun Microsystems,
Hewlett-Packard, and IBM. That functionality is now supported only on Windows
and Linux.

Capacity will be improved to the petabyte range, for applications such as data
warehousing and life sciences. The current capacity is hundreds of terabytes.

Oracle will also improve the performance and capabilities of XML DB support,
which allows the database to act as an XML store.

Oracle is also developing portal technology, called OmniPortlet, for aggregating
information from different sources. And the company plans manageability
improvements to its database.

The next database version, referred to internally as Oracle 10i, is expected to
ship in mid-2003."

          Followup:

I believe they MIGHT be talking about the cluster FILE SYSTEM.

I believe news reports are written by reporters ;)  they don't do databases.

I believe they are confused...

goto

http://technet.oracle.com/documentation/oracle9i.html
you can pick up

Oracle9i Real Application Clusters Real Application Clusters Guard I
Configuration Guide for Unix Systems: AIX-Based Systems, Compaq Tru64 UNIX, HP
9000 Series HP-UX, Linux Intel, and Sun Solaris

      Rating: 5
Thanks a
      lot!  February 25, 2003
Reviewer:
      A reader

February 25, 2003 KK -- Thanks for the question regarding
      "owa_cookie -- i don't see it in the cookies.txt file", version 2.2

      You Asked
I would like to use the package "owa_cookie" to
set and get cookie, but when I write:
"owa_cookie.send('test','value')"
the procedure is executed successfully.
However, when I edit the cookie.txt, nothing is written.

      and we said...
Cookies by default are session based.  Only cookies that have
some expiration date associated with them would be stored
permanently.   By default, cookies with no expiration go away
when you close the browser.  Those cookies that go away when you
close the browser are never written to the cookies.txt file.

Send the optional third parameter to send() -- the expiration
date.
Review
      & Followup

      Rating: 3
Expring the
      Cookie  February 25, 2003
Reviewer:  Ray
      White  from Ottawa, ON,CA
I am having a problem with the session cookie expiring. IE not there after a few
minutes.

I set it without any date
Each http request reads it and re-writes it
I can use the app navigating to and frow without any problem. If I let it sit
for a few minutes bingo the cookie is gone and the session cannot be
established.

Note1: 8.17 using either IIS with oracle plugin or Apache with mod-plsql plugin
Note2 : All PLSQL genned HTML
Note3: All Packages are genned exactly the same

any ideas?

          Followup:

got an example?

      Rating:
      3
cookies  March 28, 2003
Reviewer:
      ray  from Ottawa,ON
Sorry I didnt reply to this earlier, I guess I lost the thread but I found my
problem.
No problem with the session cookie expiring as first thought. Thanks


Subject | Article | Last Updated (East coast time)
CheckPoint, Log Sequence Number and SCN | article only | Feb 27, 2003 07:03 am
Number of Rows and performance | article only | Feb 27, 2003 07:14 am
PL/SQL | article only | Feb 27, 2003 07:26 am
Data file size | article only | Feb 27, 2003 07:29 am
PL/SQL is faster than SQL in this Query Why? | article only | Feb 27, 2003 07:30 am
Returning sequence numbers to a third party software tool | article only | Feb 27, 2003 06:53 pm
AUTOEXTEND and LMT's | article only | Feb 27, 2003 06:58 pm
unique vs. non unique index | article only | Feb 27, 2003 07:01 pm
Rollback segment status(FULL) | article only | Feb 28, 2003 09:51 am
How to insert a string from java program to a table containing blob | article only | Feb 28, 2003 10:16 am
Comparing Two CLOB columns | article only | Feb 28, 2003 01:28 pm
Expensive | article only | Feb 28, 2003 03:46 pm
table size and tablespace | article only | Feb 28, 2003 04:01 pm
update sql, subquery_factoring_clause | article only | Feb 28, 2003 05:20 pm
standby database -- have to have same directory structures? | article only | Feb 28, 2003 07:37 pm
Backup stratergy | article only | Mar 01, 2003 09:53 am
Space allocation | article only | Mar 01, 2003 12:01 pm
DDL in Procedure | article only | Mar 01, 2003 12:18 pm
external table and date format | article only | Mar 01, 2003 03:04 pm
DBMS_JOB in after insert/update row trigger against mutating tables | article only | Mar 01, 2003 06:47 pm
Question on your HOTSOS presentation, "Developing Efficient ..." Demo002.sql | article only | Mar 02, 2003 09:44 am
How to Use where clause in Truncate table? | article only | Mar 02, 2003 09:49 am
Chained Rows and Autotrace | article only | Mar 02, 2003 09:53 am
Advanced replication tuning | article only | Mar 02, 2003 10:25 am
Insert with append hint vs SQLLoader | article only | Mar 02, 2003 10:35 am
Row chaining and migration | article only | Mar 03, 2003 06:49 am
Doubt about bind variables usage | article only | Mar 03, 2003 07:00 am
can i use sql_trace to know "update DICT" | article only | Mar 03, 2003 07:28 am
UPDATE (Select...) | article only | Mar 03, 2003 10:50 am
Free ids | article only | Mar 03, 2003 10:56 am
Returning Rows from a plsql procedure | article only | Mar 04, 2003 06:42 am
Overwritten the PK in copy | article only | Mar 04, 2003 06:43 am
UTF8 and VARCHAR2 | article only | Mar 04, 2003 06:59 am
Using merge statement to update data warehouse | article only | Mar 04, 2003 07:00 am
Redo and Rollback | article only | Mar 04, 2003 06:09 pm
Analyze object privilege | article only | Mar 04, 2003 06:37 pm
Upper() in LOB Column? | article only | Mar 04, 2003 06:51 pm
row(s) 1 - 37 of 37
February 27, 2003 M. -- Thanks for the question regarding "CheckPoint, Log Sequence Number and SCN", version 8.1.6
You Asked
Hi Tom,

Can you please define the CheckPoint, Log Sequence Number and System Change
Number.

What is the difference in these three and when do they Occur?
What is the Relationship in these three? and Where can I search them.

2) Is there any Reference where I could find information and usage about the
Oracle Utilities (Bin Executables) like bulkmodify, DBV, LSNRCTL, MIG, OCOPY,
ORADIM, ORAKILL , ORASTACK and there are Lot More...

Thanks a Lot, I apprecite your help.
and we said...
A checkpoint is the act of flushing direct buffers from the buffer cache to
disk.

A log sequence number is just what it sounds like.  It is a sequential number
assigned to the logs as we fill them up.

A system change number is Oracle's internal clock.  Every time you commit, it
goes up by one.  it is what we use to coordinate transactional consistency and
recovery.

The server concepts and admin guides discuss them at various points.

Most, like lsnrctl -- are documented in the product that uses them.  lsnrctl for
example starts the listener.  see the networking guide.

Bulkmodify is another example of that.  It is documented in the OID (Oracle
Internet Directory) docs as would be ldifwrite....

The utilities are documented in the       Server Utilities guide. that would
cover tools like imp/exp/sqlldrdbv.

Mig is the migration  tool -- you use that when you read the server migration
guide.

Others, such as oradim, ocopy -- which are OS specific (windows) are found in
the Admin guide for that OS.  see the Oracle8 i Administrator’s Guide, Release 2
(8.1.6) for Windows NT for details.
Review & Followup
Rating: 3
What about ORAKILL? February 25, 2003
Reviewer: Phil from London, UK
Hello Tom
What about documentation for orakill? I have searched through the documentation
and alter session kill is described but no reference to orakill can be found.
We have some microsoft components that are not dropping their connections. The
sqlnet.expire_time setting does not seem to drop these - perhaps it is because
of the confirm dialog that is issued and acknowledment that is not being
recieved?

Followup:
well -- sqlnet.expire_time is for DCD detection and the clients are *not* dead.

The alter system kill session does kill the session and releases the resources
-- the session is "sort of there" in order to be able to let the client know
that "hey, you were killed -- goodbye" -- else they would just get a 3113 eof on
communication channel (which is what they'll get when you orakill them).  the
killed session is there so when the client eventually touches the db again -- it
can "gracefully" exit.

So, since the client did not "crash" and the client is not touching the database
(not making a call) -- everything is as it should be.

you can find support notes on metalink for orakill but it is not a general
purpose utility to be used "willy nilly" -- it can be a really bad idea to just
kill threads.

why not fix the client and make it correct?

Rating: 3
about kill February 26, 2003
Reviewer: Nadya from Russia
Tom,
we have 10 licencies for the application (7 for department1 and 3 for
department2). In order to devide them between these departmets our administrator
wrote the programm which once a minute checks (from view on v$session) this
correlation and kills the department's session (ALTER SYSTEM KILL..)if quota
exceedes. I have no weighty arguments to say him that it is bad to kill. "Why? "
- he will ask me - "It's allowed  ORACLE.". And really WHY? How can this
permanent process affect on database?
Thanks.

Followup:
alter system kill is fine, draconian, but fine.

February 27, 2003 -- Thanks for the question regarding "Number of Rows and performance", version 8.1.7
You Asked
Hi,

We have a table with 8million rows. The developer suggests that he can archive
the data from the table and reduce the row count to 4million. And is expecting a
huge improvement in performance of SQLs against this table. My opinion is that:
1. It won't drastically increase the performance for SQLs which are presently
doing INDEX (range or unique scans) scans. Since the index scans accesses the
table (if reqd) using rowids and it won't matter if there are million or 2
million or 8 million rows
2. Presently it won't improve performance of SQLs which were doing a FTS. Since
the deletion has not lowered the HWM. (The developer will be deleting rows for
the archiving process.)
3. Since the table is huge, all SQLs (98%) use index scans. Hence my conclusion
is that the archive process will not improve performance.

Please let me know your views.

Thanks
and we said...
1) as with everything in this life the answer is "yes", "no", "maybe", "it
depends".  At least you are thinking about it though!

Consider two cases -- you have a table:

CUSTOMER_ID, PROCESSED_FLAG, <other data>

you have 100,000 customers -- about 80 rows / customer in the table.  You have
an index on

(customer_id)

you query:

select * from t where customer_id = :x and processed_flag = 'N';

Suppose on average, a customer has 40 records in 'N' state and 40 in 'Y' state.
That will do 80 table access by rowids.

Now, you delete all of the data with processed_flag = 'N'.  That same query will
automagically do 1/2 the IO to get the same answer now.  The "archival" works.

However, suppose you had PROPERLY indexed this table in the first place, so the
index was

(customer_id,processed_flag)

Now, the archive will have no material affect.  You will do 40 IO's no
matter what.

In an index situation, you generally will see no benefit from purging, if you do
-- it points to bad index design, it does not mean "archive good", rather it
means "indexes were probably wrong to begin with".

2) correct, you would have to rebuild the table (and if you do that, suggest you

a) create table as ROWS_TO_KEEP from old_table
drop old table
rename new table to old table
index, grant, constrain new table

since the CTAS can be done in parallel, nologging

and the delete -- well, it'll take quite a bit of time and leave the indexes in
need of a probable rebuild/coalesce at the end anyway....

3) if the indexes and queries include in their predicate the information you
would be using to archive by -- that would be correct (like the example in #1)

In general, if you are getting 100 rows from a

8 BILLION row table
8 MILLION row table
800 row table

the performance will be more or less the same using an index range scan, as long
as the number of table access by rowids are the same.
Review & Followup
Rating: 5
Number of columns and performance February 23, 2003
Reviewer: Sri
Dear Tom,

  1. I am in a confusion.

  You said: -

"create table as ROWS_TO_KEEP from old_table
 drop old table
 rename new table to old table
 index, grant, constrain new table"

  Of this, I didn't get the last line 'index, grant, ...'.
How can we grant all the privileges that were in one table to another table?
Can we do it in a single statement?  Will all the constraints come when we do a
'create table as ... "?  What about the indexes?  Can you please explain?  I
have two databases, one is 8.1.5 and the other 9.0.1.  That adds to the
confusion, I believe.

  2. Has the number of columns in a table got anything to do with Performance?
I am talking about a table I have with a large number of columns, 100+.  The
interesting thing is that we can actually categorise the fields into two groups
and hence into two tables.  But since there is no other complications, we merged
both into one single table and we can say that it is a normalised one.  But
strictly speaking, we never need all these columns in a single Select.  I mean,
we are never using 'Select * from ...'.  My doubt is Are we compromising on
performance by keeping this as a single table when we can do otherwise?  Can you
please explain?

  Thank you.

Followup:
1) you would sort of have to know what grants there where?  you can retrieve
them from the data dictionary using exp or SQL or -- you might actually just
have them somewhere in your install scripts??

No, the constraints won't come -- you have to do it.
No, the indexes won't come -- you have to do it.

There are tools -- like OEM (enterprise manager) that create table "like" but --
here we are going for SPEED right, efficiency right?  Well, you will set up an
optimized script that you practice with on a test instance over and over to get
it just right.  it'll do each and every piece as fast as possible.

It won't matter if you have version 6, 7, 8, 9, 10 whatever -- it would be the
samel

2) if it is to be one table, it should be one table.  imagine the hit when you
do need to put them back together.

Under 254 columns -- just fine.  There are strange cases with really wide rows
or rows that start out mostly empty and balloon up, they would show up as high
"table fetch continued row" counts in your statspack reports.

Rating: 4
Rebuild February 26, 2003
Reviewer: A reader
Hi,

"and the delete -- well, it'll take quite a bit of time and leave the indexes in

need of a probable rebuild/coalesce at the end anyway....
"

Do we really need to rebuild the index? If I feel that the SQL against this
table is not going to perform a FTS, hence I don't need to ALTER TABLE ... MOVE.
In this case do I still need to rebuild the indexes?

Please advice.

Thanks

Followup:
If you are not going to "compact" the table, you do not *need* to rebuild the
indexes *but* you might want to (do both) after removing most of alot of rows

February 27, 2003 senthil -- Thanks for the question regarding "PL/SQL", version 7.3.4
You Asked
I have a situation to update two remote databaases, I need to
get data from the remote databases, also after fetching I need
to update the remote database.  How do I do this without
creating two different queries using dblink to fetch and update
in PL/SQL Procedure / Package.
and we said...
send the code to the remote site for execution.  It would be
easiest to have a procedure at the remote sight that did the
fetch/update and all you do is call the procedure.  Short of
that, you can dynamically execute a an anonymous PLSQL block at
the remote site using DBMS_SQL.  In that fashion, you'll send
over:

   begin
     for x in ( select * from t ) loop
        update t2 ...
     end loop;
   end;

to be executed instead of coding:

  for x in ( select * from t@x ) loop
     update t2@x ...
  end loop;

So, by using a remote procedure call to dbms_sql, you can ship
the code over to be executed.
Review & Followup
Rating: 2
May 21, 2002
Reviewer: Petr Bily from The Czech Rep.
Please, could you give us an example of executing anonymous pl/sql block using
remote calls of dbms_sql?

Followup:
Just use dbms_sql.procedure@remote_site instead of dbms_sql.procedure.  That is
the only difference.  You want to call the remote dbms_sql instead of the local.

Rating: 4
what about local call remote anonymous pl/sql February 22, 2003
Reviewer: Victor from Russia
Tom,
is it possible to use local dbms_sql to call remote anonymous pl/sql block and
if yes, what is the difference between this way and yous ? (i came across this
in the article about remote call on dbasupport )

Thanks in advance.

Followup:
what is a "remote anonymous block" -- there is no such thing.

An anonymous block doesn't "live anywhere".

You would use dbms_sql@remote_site to execute an anonymous block ON the remote
site.

You would use dbms_sql to execute an anonymous block on the LOCAL site.

So, no -- you cannot use the local dbms_sql to execute a block on the remote
site.

Rating: 4
about dbms_sql February 25, 2003
Reviewer: Victor from Russia
I misled by myself about terminology. I meant block with
remote call under "remore block". It's my fault, sorry.
But could you,please,demonstrate a little sample
with usage of dbms_sql.bind@db (for 8i, for example). As I could not do it, and
wonder if it is possible.
Thanks.

Followup:
scott@ORA815> create or replace procedure demo
  2  as
  3      cursor_name     pls_integer default
dbms_sql.open_cursor@ora817dev.us.oracle.com;
  4      ignore          pls_integer;
  5      l_n          number;
  6  BEGIN
  7      dbms_sql.parse@ora817dev.us.oracle.com(cursor_name,
  8                    'begin :x := 55; end;',
  9                     dbms_sql.native);
 10      dbms_sql.bind_variable@ora817dev.us.oracle.com( cursor_name, ':x', l_n
);
 11      ignore := dbms_sql.execute@ora817dev.us.oracle.com(cursor_name);
 12      dbms_sql.variable_value@ora817dev.us.oracle.com( cursor_name, ':x', l_n
);
 13      dbms_sql.close_cursor@ora817dev.us.oracle.com(cursor_name);
 14          dbms_output.put_line( l_n );
 15  END;
 16  /

Procedure created.

scott@ORA815> exec demo
55

PL/SQL procedure successfully completed

Rating: 4
underwater stones February 26, 2003
Reviewer: Victor from Russia
Fine,thanks.
Perhaps something's wrong with one of
my database as I am getting an error
during compilation
the package in Oracle8i6 (Win2000)
with dbms_sql.bind_variable@ on (db link -> Oracle8i7 Solaris)
:
PLS-00801:internal error [1407],
though the same database works well
with another servers Oracle9i (WinNT2000 or solaris).
Best wishes.

Rating: 5
how about oracle 817 to ora 73 February 26, 2003
Reviewer: A reader
if I have a proc. taht uses dbms_sql in v817 on 1-server
and I want that proc. to be executed on server-2 (v73) via db_link ?

 so, the question is
1.) if i have proc. on server-1 can I execute it on server2 ?
2.) when we send the proc. is it compiled code or runtime code?
 in other words the code in the proc. will be bided by the actual values ?

Followup:
a procedure that exists in database A runs in database A.

the procedure would have to exist in database B for database A to be able to run
it in B.

1) you have to install the procedure on server2

2) you do not "send" a procedure.

Rating: 5
March 02, 2003
Reviewer: A reader
Handy to know about rempote pl/sql

February 27, 2003 Stanley -- Thanks for the question regarding "Data file size", version 8.0.5
You Asked
Hi Tom,
i was happy to see the Ask Tom link again. You had gone
somewhere
before and yesterday u had lot of questions.
Now here goes mine:

1. Is there any max data file size in Ora 8.0.5? When i tried to

create a file with 2 GB size, i got the following error:

IMP-00003: ORACLE error 2237 encountered
ORA-02237: invalid file size

Thanx in advance

Stan
and we said...
Sorry -- sometimes I get too many questions in one day and have
to shut it down.  For every question you see published -- I
might have 2 or 3 more that I did not publish...

The maximum file size is OS specific. With 8.0.5, almost all
platforms supported file sizes >2gig.  I myself avoid files
>2gig at all costs (just too many gotchas and no real benefit to
having them that big).  I would need to know your OS to verify.

Also, until 8.1.5, "DATAFILE ... SIZE xxxxxx" clauses of SQL
commands in Oracle must be specified in 'M' or 'K' to create
files larger than 2Gb otherwise the error "ORA-02237: invalid
file size" is reported. Perhaps that was it...
Review & Followup
Rating: 3
Datafile Sizing February 26, 2003
Reviewer: Patty from Seattle, WA
I was interested in this article as I was trying to find a rule of thumb for
datafile sizing.  When you said "I myself avoid files >2gig at all costs (just
too many gotchas and no real benefit to having them that big)" - I was wondering
what some of those "gotchas" might be.  I work with an individual who thinks
less datafiles that are "huge" would be preferable to more smaller datafiles.
What are some of the gotchas of large datafile sizes?

Followup:
we need a temporal option on questions I see.

This was written a long time ago -- I now (on linux) use files >2gig whenever.
The OS support for such things is tons better.

some of the gotchas of big files - recovery is working on bigger things (have a
16 gig file and archives for 16 gig of data to recover from).  backups are
"bigger" -- i'm backing up a single 16gig file instead of 8x2gig files one at a
time (more chance of failure as things get bigger)

but -- it is upto you ultimately.  large file support is much better in 2003
then it was in 2000

February 27, 2003 A -- Thanks for the question regarding "PL/SQL is faster than SQL in this Query Why?", version 8.1.7
You Asked
Hi ,
 I have this query in one of my package. Where it runs faster if i loop the
subselect SQL in a PL/SQL loop. Can u tell me why ?
Or can you help to optimize it.

Both the Tables are Global Temporary Tables.

SLOWER SQL :

---------------------------------------------------------------------
UPDATE kk_batch.CAS_CORP_ALLOC_STAT_RPT_TMP cas_m
   SET    ( in_bank,
             pullable,
           ord_reqd,
           add_ord) =
   (   select  IN_BANK,
                     PULLable,
               ORD_REQD,
               ADD_ORD
        from   (
            SELECT cas.record_type
            ,cas.MDL_YR
            ,cas.fcty_cde
            ,cas.carln_cde
            ,cas.acct_num
              ,cas.mdl_cde_or_rcl_ident
            ,oba.ORD_IN_BANK   as IN_BANK
               ,oba.ORD_MEET_ALLCTN as pullable
            ,DECODE( sign(nvl(cas.ALLCTN,0) + nvl(cas.TRANSF,0) -
nvl(cas.USED_ALLCTN,0) - nvl(oba.ORD_MEET_ALLCTN,0))
                     ,1, nvl(cas.ALLCTN,0) + nvl(cas.TRANSF,0) -
nvl(cas.USED_ALLCTN,0) - nvl(oba.ORD_MEET_ALLCTN,0)
                     , null) as  ORD_REQD
            ,nvl(oba.ORD_MRKD_PULL - ORD_MEET_ALLCTN,0) as ADD_ORD
           FROM    kk_batch.CAS_CORP_ALLOC_STAT_RPT_TMP cas ,
             (
                    SELECT
                      REGN_CDE
                      ,AREA_CDE
                      ,RETLR_ID
                      ,CARLN_CDE
                      ,MDL_YR
                      ,SALE_MDL_CDE
                      ,FCTY_CDE
                      ,SUM(ALLCTN_BAL)     as  ALLCTN_BAL
                      ,SUM(ORD_MEET_ALLCTN) as   ORD_MEET_ALLCTN
                      ,SUM(ORD_IN_BANK)    as  ORD_IN_BANK
                      ,SUM(ORD_MRKD_PULL)  as  ORD_MRKD_PULL
                 FROM kk_batch.OBA_ORD_BNK_ANALYS_TMP
                 where REC_TYPE = 'S'
                 group by REGN_CDE
                           ,AREA_CDE
                         ,RETLR_ID
                         ,CARLN_CDE
                         ,MDL_YR
                         ,SALE_MDL_CDE
                         ,FCTY_CDE
             ) oba
             where  cas.record_type = 'M'
          and oba.MDL_YR(+)  = cas.MDL_YR
          and oba.FCTY_CDE(+) = cas.fcty_cde
          and oba.CARLN_CDE(+) = cas.carln_cde
          and oba.RETLR_ID(+) = cas.acct_num
          and oba.SALE_MDL_CDE(+) = cas.mdl_cde_or_rcl_ident
        )
          obam
   where  obam.record_type = 'M'
          and obam.MDL_YR  = cas_m.MDL_YR
          and obam.FCTY_CDE = cas_m.fcty_cde
          and obam.CARLN_CDE = cas_m.carln_cde
          and obam.acct_num = cas_m.acct_num
          and obam.mdl_cde_or_rcl_ident = cas_m.mdl_cde_or_rcl_ident )
   WHERE cas_m.record_type = 'M';
/
--------------------------------------------------------------------

FAST SQL _ WITH PL/SQL

---------------------------------------------------------------------
CURSOR upd_cur is
 SELECT cas.record_type
        ,cas.MDL_YR
        ,cas.fcty_cde
        ,cas.carln_cde
        ,cas.acct_num
        ,cas.mdl_cde_or_rcl_ident
        ,oba.ORD_IN_BANK   as IN_BANK
           ,oba.ORD_MEET_ALLCTN as pullable
        ,DECODE( sign(nvl(cas.ALLCTN,0) + nvl(cas.TRANSF,0) -
nvl(cas.USED_ALLCTN,0) - nvl(oba.ORD_MEET_ALLCTN,0))
                     ,1, nvl(cas.ALLCTN,0) + nvl(cas.TRANSF,0) -
nvl(cas.USED_ALLCTN,0) - nvl(oba.ORD_MEET_ALLCTN,0)
                     , null) as  ORD_REQD
        ,nvl(oba.ORD_MRKD_PULL - ORD_MEET_ALLCTN,0) as ADD_ORD
           FROM    kk_batch.CAS_CORP_ALLOC_STAT_RPT_TMP cas ,
             (
                    SELECT
                      REGN_CDE
                      ,AREA_CDE
                      ,RETLR_ID
                      ,CARLN_CDE
                      ,MDL_YR
                      ,SALE_MDL_CDE
                      ,FCTY_CDE
                      ,SUM(ALLCTN_BAL)     as  ALLCTN_BAL
                      ,SUM(ORD_MEET_ALLCTN) as   ORD_MEET_ALLCTN
                      ,SUM(ORD_IN_BANK)    as  ORD_IN_BANK
                      ,SUM(ORD_MRKD_PULL)  as  ORD_MRKD_PULL
                 FROM kk_batch.OBA_ORD_BNK_ANALYS_TMP
                 where REC_TYPE = 'S'
                 group by REGN_CDE
                           ,AREA_CDE
                         ,RETLR_ID
                         ,CARLN_CDE
                         ,MDL_YR
                         ,SALE_MDL_CDE
                         ,FCTY_CDE
             ) oba
             where  cas.record_type = 'M'
          and oba.MDL_YR(+)  = cas.MDL_YR
          and oba.FCTY_CDE(+) = cas.fcty_cde
          and oba.CARLN_CDE(+) = cas.carln_cde
          and oba.RETLR_ID(+) = cas.acct_num
          and oba.SALE_MDL_CDE(+) = cas.mdl_cde_or_rcl_ident
          ;
BEGIN
  FOR idx IN upd_cur LOOP
     UPDATE kk_batch.CAS_CORP_ALLOC_STAT_RPT_TMP  cas_m
     SET   in_bank = idx.in_bank
             ,pullable = idx.pullable
           ,ord_reqd = idx.ord_reqd
           ,add_ord = idx.add_ord
     where cas_m.record_type = 'M'
          and cas_m.MDL_YR  = idx.MDL_YR
          and cas_m.FCTY_CDE = idx.fcty_cde
          and cas_m.CARLN_CDE = idx.carln_cde
          and cas_m.acct_num = idx.acct_num
          and cas_m.mdl_cde_or_rcl_ident = idx.mdl_cde_or_rcl_ident;

  END LOOP;
---------------------------------------------------------------------
Regards
and we said...
because plsql executed the query once and got N rows.

the update in this case execute the query for EVERY row in the table.

This is why I say "usually", "typically", "in most cases".  there are always
exceptions and this is one of them.  Here is an example of this to make it more
interesting.

The update is "slower" then PLSQL but using a 9i MERGE (which gets around the
key preserved issue that prevents us from doing just a straight update) -- we
can do this in a single statement just as good:

ops$tkyte@ORA920.US.ORACLE.COM> create table t ( owner varchar2(30), cnt number
);
Table created.

ops$tkyte@ORA920.US.ORACLE.COM> create index t_idx on t(owner);
Index created.

ops$tkyte@ORA920.US.ORACLE.COM> create table ao as select * from all_objects;
Table created.
ops$tkyte@ORA920.US.ORACLE.COM> insert into t select username, 0 from all_users;

38 rows created.

ops$tkyte@ORA920.US.ORACLE.COM> insert into t select * from t;

38 rows created.

.... snip ...

ops$tkyte@ORA920.US.ORACLE.COM> insert into t select * from t;
1216 rows created.

ops$tkyte@ORA920.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA920.US.ORACLE.COM> set timing on
ops$tkyte@ORA920.US.ORACLE.COM> update t set cnt = ( select count(*) from ao
where owner = t.owner )
  2  /

2432 rows updated.

Elapsed: 00:00:10.06

ops$tkyte@ORA920.US.ORACLE.COM> rollback;
Rollback complete.
Elapsed: 00:00:00.17

ops$tkyte@ORA920.US.ORACLE.COM> begin
  2          for x in ( select owner, count(*) cnt from ao group by owner )
  3          loop
  4                  update t set cnt = x.cnt where owner = x.owner;
  5          end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.
Elapsed: 00:00:00.20

Now that was faster since running the correlated subquery 2k + times was less
efficient then doing the mass query ONCE and doing 18 updates.

ops$tkyte@ORA920.US.ORACLE.COM> rollback;
Rollback complete.
Elapsed: 00:00:00.12

ops$tkyte@ORA920.US.ORACLE.COM> update (select t.cnt t_cnt, ao.cnt ao_cnt
  2            from t, (select count(*) cnt, owner from ao group by owner) ao
  3                   where t.owner = ao.owner )
  4    set t_cnt = ao_cnt
  5  /
  set t_cnt = ao_cnt
      *
ERROR at line 4:
ORA-01779: cannot modify a column which maps to a non key-preserved table

Elapsed: 00:00:00.00

now that is the update i REALLY wanted to do, but... cannot.  So, I can use
merge:

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> merge into t
  2  using ( select count(*) cnt, owner from ao where owner in (select owner
from t) group by owner ) ao
  3  on ( t.owner = ao.owner )
  4  when matched then
  5          update set t.cnt = ao.cnt
  6  when not matched then
  7    insert (owner) values (null)
  8  /

2112 rows merged.

Elapsed: 00:00:00.22
ops$tkyte@ORA920.US.ORACLE.COM>

Note that I went out of my way to make sure the when not matched NEVER
happened by augmenting the query.  that is not necessary if you know the thing
you are merging to has the same set of keys (or less).  Or maybe -- you do want
to insert the misses
Review & Followup
Rating: 4
November 27, 2002
Reviewer: A reader

Rating: 5
November 27, 2002
Reviewer: A reader

Rating: 5
excellent November 28, 2002
Reviewer: A reader

Rating: 4
How come figures are different? December 13, 2002
Reviewer: Anton from Phil.
Hi Tom,
Just wondering with the number of records that was updated.

ops$tkyte@ORA920.US.ORACLE.COM> update t set cnt = ( select count(*) from ao
where owner = t.owner )
  2  /

2432 rows updated.

when you executed merge it turns out...

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> merge into t
  2  using ( select count(*) cnt, owner from ao where owner in (select owner
from t) group by owner ) ao
  3  on ( t.owner = ao.owner )
  4  when matched then
  5          update set t.cnt = ao.cnt
  6  when not matched then
  7    insert (owner) values (null)
  8  /

2112 rows merged.

They should update the same number of rows right???

I'm also facing with this kind of problem. How can I achieve this in 8.1.6?

cheers,

Followup:
Interesting -- what I missed is that the update updates EVERY row in T -- even
rows that did not have a mate in AO.

So, there were rows in T with an OWNER that had zero objects.  I updated them to
ZERO in the update.  We did not touch them in the MERGE.

So, there is a subtle difference -- the update will set EVERY row in T that
matches the predicate against T (in the original question -- they had " WHERE
cas_m.record_type = 'M';" -- every record that matched that would be UPDATED --
set to NULL if no match, set to a value upon a match).

With the MERGE -- only records that "had a match" would be updated.  So,
something to be aware of.

Thanks for pointing it out.

In 816, use the method I talked about above, or create a global temporary table
and:

insert group by subquery into gtt
update the join

Like this:

ops$tkyte@ORA920> create global temporary table gtt ( owner varchar2(30) primary
key, cnt number );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into gtt
  2  select owner, count(*) from ao group by owner;

36 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> update (select t.cnt t_cnt, ao.cnt ao_cnt
  2            from t, gtt ao
  3                   where t.owner = ao.owner )
  4    set t_cnt = ao_cnt
  5  /

2112 rows updated.

or, if you need all rows updated -- even if they do not exist in the other
table

ops$tkyte@ORA920> update (select t.cnt t_cnt, ao.cnt ao_cnt
  2            from t, gtt ao
  3                   where t.owner = ao.owner(+) )
  4    set t_cnt = ao_cnt
  5  /

2432 rows updated.

Rating: 5
A modified version of the MERGE statement. January 23, 2003
Reviewer: Kashif from Houston, TX
Hi Tom,

For the merge statement which updates 2112 rows intead of 2432 rows, would it be
possible to rewrite it as follows to more closely reflect the update statement?

ops$tkyte@ORA920.US.ORACLE.COM> merge into t
  2  using ( select count(*) cnt, owner from ao where owner in (select owner
from t) group by owner ) ao
  3  on ( t.owner = ao.owner )
  4  when matched then
  5          update set t.cnt = ao.cnt
  6  when not matched then
  7    update set t.cnt = null  <---------
  8  /

Would it then update all (2432) rows? I don't have 9i so I can't test it. Thanks
in advance.

Kashif

Followup:
when not matched -- well, there is no T.CNT as that point, there was no match ;)

Rating: 4
number of rows being updated/inserted in MERGE February 20, 2003
Reviewer: A reader from CA, USA
Hi Tom,

How can we get the numbers being updated (matched) and the number being inserted
(not matched) from a MERGE statement?

The sql%rowcount appears having the total rows (updated + inserted) though. Is
there any way we can obtain the number for update and insert seperately?

Followup:
No, merge is a statement that affects N rows like delete affects N rows and
update affects N rows and so on.

MERGE reports back the number of rows merged.

Rating: 5
full scan ? February 26, 2003
Reviewer: A reader
so is it true that when you are updating oracle just does the full scan but when
mergin, it has to perform a search ?
to find 2112 out of 2432 ? -- overhead ?

Followup:
where the heck did the "full scan" come from?

but the merge might use a full scan
the merge might use index acess
the update might have full scanned
the update might have index accessed

February 27, 2003 Tad -- Thanks for the question regarding "Returning sequence numbers to a third party software tool", version 8.0.4
You Asked
Hi Tom

I have set up a sequence for adding a unique ID column to a
table.
There is a trigger in place to fetch the next sequence number
before the INSERT into the table.

The trouble is that the INSERT is being done by a third party
tool
(C Code) and it needs to know the number that has been
assigned by the trigger for the ID.  I was wondering if you knew
a way to return the assigned value to this program without
having to re-read the inserted row ?

Any ideas ?

Thanks

Tad
and we said...
At least three ways to do this:

o insert into t values ( .... ) RETURN COLUMN_NAME INTO :bindvar

  That syntax, used with inserts and updates, is useful to
  retrieve the value of any column(s) in one round trip

o select SEQUENCE_NAME.currval FROM dual;

  Since you are populating with a trigger -- they can get
  the last value you selected via NEXTVAL by calling currval.
  CURRVAL is "session safe".  The currval they get is the result
  of the last NEXTVAL made in their session -- not any other
  session

o have the trigger place the sequence value it assigned into a
  package variable.  they can access or reference the package
  variable in SQL later on.

  eg:

  create package state_pkg
  as
     last_id number;
  end;

  create trigger trig before insert on t for each row
  begin
     select sequence.nextval into state_pkg.last_id;
     :new.column := state_pkg.last_id;
  end;

  they now have access to state_pkg.last_id...
Review & Followup
Rating: 3
The things you learn... February 27, 2003
Reviewer: Andrew Taylor from Perth, Western Australia
I never realised that it was possible to get/set the state of a variable
initialised/modified by a table trigger from within the session that caused the
trigger to fire...

The question I have is if this is good practice? Does Oracle officially support
the ability of a trigger to see and modify the calling session's state (eg.
package global variables, sequence values)? I've always considered trigger
activity to be 'local' to the trigger (while recognising they share transaction
state).

Followup:
sure, it is all in the same "session".

a trigger is just plsql code that is called by an insert/update/delete if you
want to think about it that way.

February 27, 2003 Jeff -- Thanks for the question regarding "AUTOEXTEND and LMT's", version 9.0.1.0
You Asked
I am trying to understand how AUTOEXTEND works with Locally Managed Tablespaces.
 For example, I create a tablespace that I want to store tables with 320K
extents.  I size the initial datafile for the LMT Bitmap (64K) and 10 extents
(3200K).  The tablespace definition is:
create tablespace test_lmt
datafile '/u01/oradata/dev901/test_lmt01.dbf'
size 3264K
autoextend on next 1M maxsize 20M
extent management local uniform size 320K;

system@dev901.us> select file_name, tablespace_name, bytes/1024 k
  2  from dba_data_files;

FILE_NAME                           TABLESPACE_NAME          K
----------------------------------- --------------- ----------
/u01/oradata/dev901/test_lmt01.dbf  TEST_LMT              3264

I then create an object in that tablespace with 11 extents so I know that the
datafile will autoextend:

create table xyz (x number, y number, z number)
tablespace test_lmt storage (minextents 11);

If I query dba_data_files again, it shows I have 4544K in my data file and not
4288 or 5312 like I would expect because the data file extends by 1M.

system@dev901.us> select file_name, tablespace_name, bytes/1024 k
  2  from dba_data_files;

FILE_NAME                          TABLESPACE_NAME          K
---------------------------------- --------------- ----------
/u01/oradata/dev901/test_lmt01.dbf TEST_LMT              4544

My table, indeed, has 11 extents, all 320K:
system@dev901.us> l
  1  select bytes/1024 k
  2  from dba_extents
  3* where tablespace_name = 'TEST_LMT'
system@dev901.us> /

         K
----------
       320
       320
       320
       320
       320
       320
       320
       320
       320
       320
       320

11 rows selected.

Why does the datafile extend NOT by the AUTOEXTEND NEXT value?
and we said...
It does not make sense to autoextend by 1m - it would leave 2/10 of the file
unusable at the end.

If you notice,

1024/320 = 3.2   (thats what you asked for, 3.2 MORE extents)
ceil(3.2) = 4    (it wants to have uniform extents)
4*320 = 1280     (the difference between 3264 and 4544)

It does not do what you asked since what you asked is not logical given the
uniform sizing strategy.

I'll agree that the doc is a little hazy on this.  The SQL ref says NEXT is the
size of the next chunk added, the admin guide states:

....
The value of NEXT is the minimum size of the increments added to the file
when it extends. The value of MAXSIZE is the maximum size to which the file can
automatically extend.
....
Review & Followup
Rating: 5
November 13, 2001
Reviewer: Jeff Hunter from Greenwich, CT
Ah, "NEXT is the minimum size" is the part I missed.  Thanks!

Rating: 4
Excellent explanation December 07, 2001
Reviewer: Jimmy Lo from Troy, NY
However, I have a question regarding
"It does not make sense to autoextend by 1m - it would leave 2/10 of the file
unusable at the end."

1. How do you come up this number (2/10)? and why is it unusable?

2. It doesn't make sense to make autoextend by 1M,
but doesn't Oracle fixes it automatically? ( in this case the 1st extended
chunck is 1280K)
and all 1280K (4 320K extents) is usable, right?

Thank you!

Followup:
1) your right, its not 2/10s, its 40k.  There would be 40k at the end of the
file that couldn't be used.

2) yes, that is what this demonstrated here -- we rounded the autoextend UP to
accomidate the LMT extent size.

Rating: 4
Just need a little more clarification December 07, 2001
Reviewer: Jimmy Lo from Troy, NY
Tom, thanks for your prompt response.
Continue on your follow up:

1. 1M=1024K, so it should be 64K left
(1024K - 3x320K)

2. It's not reasonalbe to extend on 1M. Because if Oracle really allocates 1M,
there will be 64K left.
But Oracle WILL NOT allocate 1M, instead it will allocate 1280K (320K x4).

If the above are true. That means in this example, there will NOT be any
unusable space.

I guess I'm more confused by the word 'unusable'.
In a LMT environment, if the uniform size is specified,
the datafile will extend as what you explained above.
I don't see any unusable space.
If uniform size is not specified,
let's suppose initial and next are also 320K , and
PCTINCREASE=0. let's do the same test again.
when the tablespace extends, oracle will extends exactly 1M.
The new table will have the same 11 extents.
but the datafile(or tablespace) will have 3264K + 1M
we can increase the number of extents of the table we just careated to 13, then
we'll have 64K left
and this 64KB can be used
1. for other objects in the same tablespace with a smaller extent size
2. for the same table, but oracle has to extend the size of the
datafile(tablespace) first.
Are these correct?
If they are correct, the 64K will always be usable, it's just a matter of how
If my statement aren't correct, would you explain more about the concept of
'unusable'?

Thank you so much for your time!

Followup:
That is right, there will not be any unusable space -- because we don't allow
the 1m extension, we bumped it up.

I was just pointing out that 1m was not a sensible size, we adjusted it, that is
all.

Yes, you are correct in the other assumptions with regards to dictionary managed
tablespaces.

With uniform LMTS, unless the file is 64k + N * uniform_extent_size, there is
some space in that file that cannot be used (unless the file gets larger).  As
the database knows that space cannot be used -- it will round up the allocations
to avoid having to re-extend the file (to gain yet more space that cannot be
used until it again grows the file).  Its just being a little bit smarter about
how it allocates the file.

Rating: 4
can u explain more February 27, 2003
Reviewer: umesh from bangalore, India
I could not follow the calculations 1024/320

1.How do I calculate the minimum number of extents allocate used for t.space
extension here

2.But another review says  as 1024 - 3*320 and how is that done please explain
thanks for your patience

Followup:
320 = amount in k of an extent in this tablespace:

create tablespace test_lmt
datafile '/u01/oradata/dev901/test_lmt01.dbf'
size 3264K
autoextend on next 1M maxsize 20M
extent management local uniform size 320K;

1024 = amount in k of requested autoextend file size (1m)

1) don't understand what you mean.  A segment will request some number of
extents -- a tablespace doesn't....

2) they are just showing there would be 64k left over (which is still NOT 320k
hence same issue....)

February 27, 2003 Ishaque -- Thanks for the question regarding "unique vs. non unique index", version 8.1.7
You Asked
Hi, I searched in the archives for my question but I was unable to find an
answer and I do apologize for asking what seems like such an easy question.  I
have always assumed that unique indexes were faster than non-unique indexes but
I have never been completely sure of this.

Scenario, I have table TEST with columns colA,colB,colC,colD,colE and with about
1.3 million rows. No two rows in this table have the same values for the 5
columns mentioned above. Here I can create a concatenated unique index or a
non-unique index on the 5 columns mentioned above (unique indexes are also good
for data integrity). Will the unique index be faster in a select query than a
non-unique index here?  Also in general, are unique indexes faster in select
queries data than non-unique indexes?

Thanks
and we said...
you know what the structural difference between a unique and non-unique index in
Oracle is?

Nothing.  bit for bit, byte for byte, they would be the same.

It is just that in a unique index, the rowid is not considered "part of the key"
and in a non-unique index "the rowid is considered part of the key".

But -- what is the performance difference?

well, the optimizer can look at an index that is unique and say "ah-hah, if you
use "where x =:x and y = :y and ...." I'm going to get ONE row back, I can cost
that much better"

If the index is non-unique, the optimizer will say -- "ah-hah, index range scan,
I'm going to get 0..N rows back" and it'll cost it differently.

So, a unique index will affect the generated plan -- it is more information for
the optimizer to grab onto.

If the data must be UNIQUE, you should use a UNIQUE constraint - not an index.
We will take care of the index for you.  If the constraint is not deferrable,
we'll create a unique index for you.  If the constraint is deferrable -- we'll
use a non-unique index.
Review & Followup
Rating: 4
unique vs. non unique index February 04, 2003
Reviewer: Ishaque Hussain from Sewaren, NJ
Thanks Tom, this was very helpful.  Could you explain then how does Oracle
identify the rows for a unique index quickly.  I thought that rowid is the
fastest way of identifying a row in an Oracle table.  Bear with me I am studying
for my 4th (Perf tuning) Oracle 9i DBA test.

Followup:
It is a chicken and egg problem here.

Yes, if you give me a rowid, I can get the row very fast for the rowid is the
address of the row on disk.

But -- how did you get that rowid?  typically from an index!

The index is just an efficient data structure that can hold many millions of
entries in a data structure that requires 3 maybe 4 block reads to find an entry
on.  We start at the root and navigate a very complex tree to find the rowid of
the row in question.

Rating: 4
February 05, 2003
Reviewer: Mike from TX
"
well, the optimizer can look at an index that is unique and say "ah-hah, if you
    use "where x =:x and y = :y and ...." I'm going to get ONE row back, I can
cost
    that much better"

    If the index is non-unique, the optimizer will say -- "ah-hah, index range
scan,
    I'm going to get 0..N rows back" and it'll cost it differently.
"

Do you mean for the same table, and "where x =:x and y = :y and ....", the
unique index adopted will be faster than non-unique count-part?

Followup:
No, I mean it will be treated with a different cost, the plan KNOWS that you
will get at most one row -- it can do things differently then if it will get
0..N rows where N is unbounded.

A merge join cartesian is much more appropriate for a single row source then for
a N row source for example.

So, the extra knowledge gained -- that there is ONE row -- can influence the
plans that fall out for a given query.

Rating: 5
a very complex tree February 05, 2003
Reviewer: A reader
what is the data structure for that
 B or B+ tree ? or anything else ?

Followup:
it's a b*tree with modifications (no, i'm not going into the specifics)

see
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/c08schem.htm#5766
for a good overview.

Rating: 5
Unique constraint? February 05, 2003
Reviewer: Doug from CT, USA
This might tie in with a workspace manager follow up I have floating out here
too.  I have a problem with non primary-key unique indexes that are being used
to enforce uniqueness.  This prevents me from using versioning on tables.  From
what you are saying I could use a defferable unique constraint and get around
that? because it wouldn't require a unique index?  I.e, can I really add a
unique constraint without using a unique index? What are the ramifications?

Rating: 5
Unique Constraint? Never mind found the answer - February 06, 2003
Reviewer: Doug C from CT, USA
I read the documentation (the what?) and did a little experiment - deferrable
and non-deferrable

ORA-20109: a table with unique constraints cannot be version enabled

Boy that is really bumming me out.
& I get the impression hardly anyone is using Workspace Manager because there
are no hits on this. Even had a tar about it and they didn't know either - hence
the enhancement request.

Rating: 5
index February 25, 2003
Reviewer: mo
Tom:

If you have a query like this:

select t1.col1,
       t2.col1,t2.col2,t2.col3,t2.col4,t2.col5,
       t3.col1,t3.col2,
       t4.col1,t4.col2,
       t5,col1
from t1,t2,t3,t4,t5 where
   t1.col1 = t2.col1 and
   t2.col1 = t3.col1 and
   t3.col1 = t4.col1 and
   t4.col1 = t5.col1 and
 t1.col2 > 0 and t2.col3 in ('AC','DE','DD') and
 t3.col2 in ('T','D') and
  t4.col3 in ('A','C','R') and t4.col5 = :var;

Would you be creating three indexes on t1.col2,
another on t2.col3 , t3.col2, t4.col3.col5?

Then you would compare a run without an index and one with an index to see which
is faster?

Followup:
well, you need to look at more then A query to determine what indexes you might
or might not have.

My answer -- maybe I would have zero indexes. Maybe more.

If trying to tune JUST this query and don't care about anything else

probably col1 in all tables
pick the most selective predicate out of the non join conditions -- and index
that column.

Say t4.col5 is unique -- that one.

Rating: 5
index February 25, 2003
Reviewer: mo
Tom:

1.  If col1 in all/some tables is Primary key then it should be automatically
indexed. Correct?

2.  Can you explain what you mean by <do not care for anyhting else> like what?

3.  <pick the most selective predicate out of the non join conditions >  Do you
mean the predicate on the table with highest count of records or the column that
has unique values?

4.  When I set autotrace traceonly, the query takes about 45 seconds. However
when I run it without it retrieves 20,000 records and keeps outputing data for a
while.  If the query is supposed to insert into another table or return results
to a powerbuilder client screen, then would the 45 seconds make any sense in
timing it since it will probably take much longer to do insert or send results
to a client.

Thank you,

Followup:
1) "then it will be" would be more accurate.  creating a primary key constraint
will either kidnap an index or create on if one does not exist

2) "If trying to tune JUST this query and don't care about anything else"

meaning the rest of a system.  You cannot tune a query in isolation with indexes
-- you must look at the entire system and index appropriately

3) the predicate that returns the least number of rows

4) yes, of course the 45 seconds makes sense.  That is the time it takes for the
database to nominally return the 20,000 records to the client.  If the client
takes 5 hours to display it, so be it.  If the client takes 15 seconds to
display it, so be it.  The 45 seconds is the amount of time it takes to get the
data back to the client.

does it apply to an insert as select?  no, not nearly as much.  apples and
toaster oven comparision there.

Rating: 3
index range scan on non-unique indexes February 26, 2003
Reviewer: John from Columbus, OH USA
Tom,
We have a order entry system and overnight (everyday) we would like to process
the orders (which are not processed).
I have order_create_date and my query looks like

update OrdersTable
set orderProcessedStatus='Inprocess'
where order_create_date <= trunc(sysdate)
and orderStatus='Not Processed';

we want to convert this query to look for one week orders (as we process orders
everyday and any order missed becasue of business conditions usually picked up
in a week)
and so we are planning to write like

update OrdersTable
set orderProcessedStatus='Inprocess'
where order_create_date between trunc(sysdate-7) and trunc(sysdate)
and orderStatus='Not Processed';

Out of the above two queries which will be faster (orders table volume is 6
million and per day processing will be about 200,000).

I looked at the execution plan and both are using range based index scans.

Expecting a convincing answer.
thanks

Followup:
convincingly I will say:

o if you use the same plan
o to get the same rows
o from the same place
o they will execute -- well -- obviously "the same"

NOW --

o if you use the same plan
o to get DIFFERENT ROWS (as your query does)
o from the same place
o then no one on this planet can answer the question accurately

but in your case, since I see the queries, the second one may ran faster ONLY
SINCE it might return 10 rows where as that other could return those same 10
rows PLUS 10000000 more.

Rating: 2
index range scan February 26, 2003
Reviewer: John from Columbus, OH USA
Tom,
Before I post the question, I was sure the between will execute fast, but after
your responsed
***** then no one on this planet can answer the question accurately*****
AND
**but in your case, since I see the queries, the second one ####may#### ran
faster ONLY ****
I'm confused.
Can you pl. explain me in which case the above will be false(my assumption with
the kind of data I'm processing.)
pl.provide examples
thanks again

Followup:
Look at the queries:

update OrdersTable
set orderProcessedStatus='Inprocess'
where order_create_date <= trunc(sysdate)
and orderStatus='Not Processed';

update OrdersTable
set orderProcessedStatus='Inprocess'
where order_create_date between trunc(sysdate-7) and trunc(sysdate)
and orderStatus='Not Processed';

that second one is just

update OrdersTable
set orderProcessedStatus='Inprocess'
where order_create_date <= trunc(sysdate)
and orderStatus='Not Processed';
AND order_create_date >= trunc(sysdate-7)

that is - the first and second query are basically the same except the second
query stops processing data after a while (hence making it potentially faster,
does less work)

My point was about the plans -- if you have two queries with the same plan --
you cannot tell which will be faster.

the queries:

update OrdersTable
set orderProcessedStatus='Inprocess'
where order_create_date <= trunc(sysdate)
and orderStatus='Not Processed';

update OrdersTable
set orderProcessedStatus='Inprocess'
where order_create_date <= trunc(sysdate-3000)
and orderStatus='Not Processed';

will have the same plan -- the second will probably run faster (less data to
process)

Rating: 5
How to define a good index? February 26, 2003
Reviewer: Marcio from Brazil
Could you explain and demonstrate (if possible) how to define a good index --
kind of rule of thumb.
Thanks,

Marcio

Followup:
well, you should always use the syntax as defined in the SQL Reference manual --
that'll be the only way to create "good" indexes.

I don't really know how to answer this -- it is so broad.  seems you are asking
"how do i set about indexing my system".

you use indexes to speed things up.
you want as few as possible BUT
as many as you need

Rating: 5
February 27, 2003
Reviewer: Ram from india
<quote 1>
It is just that in a unique index, the rowid is not considered "part of the key"
and in a non-unique index "the rowid is considered part of the key".
well, the optimizer can look at an index that is unique and say "ah-hah, if you
use "where x =:x and y = :y and ...." I'm going to get ONE row back, I can cost
that much better"
<quote 1>

<quote 2>
Yes, if you give me a rowid, I can get the row very fast for the rowid is the
address of the row on disk.
But -- how did you get that rowid?  typically from an index!
<quote 2>

these two quotes gives the impression that for a 'unique index' the rowid
'should be' considered "part of the key"
rather than not.
what am i missing here ? was it a typo ?

Followup:
In a b*tree index, there is no such thing as a non-unique index.

Therefore, when you create a "non-unique index", we really have to make it
unique.  Hence, we add the rowid to your key.  if your index is on (a,b,c) -- it
is really on (a,b,c,rowid)

If you create a unique index, then the rowid is just stored in the index
structure but is not part of the key  -- the index key is really (a,b,c) in that
case, the rowid is just along for the rid.

Physically, the data is stored differently in the leaf nodes for a unique vs
non-unique index on (a,b,c) but you don't really care about that in the real
world.

The rowid is always there.

In a non-unique index it is actually part of the index key.
In a unique index, it is just hanging out with the key but is not part of it

Rating: 5
Great explanation! February 28, 2003
Reviewer: Ram from india

Rating: 5
Very good explanation between unique and non-unique index November 05, 2003
Reviewer: Sami from NJ,USA

February 28, 2003 Sateesh Kumar -- Thanks for the question regarding "Rollback segment status(FULL)", version 8.1.5
You Asked
Hi Tom,
   The question is regarding the status of the rollback status.
We have a very small sized rollback segments which suits our
needs each of 50M about 12 in number. After 1week to 10days, the
rollback segment extends and reaches the maxextents of 50. And
after reaching the maxextents it will set the status of the
rollback to FULL. And it will never shrink to the optimal size
which i have set as 20M. I do it manually to shrink.
The question is if the oracle database is down will not the
extents used are freed up automatically. Do we have to shrink it
manually. can we change the status of rollback segment to
online.

Thanks
Sateesh
and we said...
A rollback segment is marked as FULL when an error ORA-1562
(failed to extend) is signalled.  When the rollback segment is
marked with a status of FULL, no new transactions will use the
rollback segment.  The FULL status is intended to signal
transactions to use other valid rollback segments.  All
transactions in that rollback segment must complete before the
full status is cleared. This means that all transactions must
either be committed or rolled back.

If it is not possible to commit every transaction, possible
workarounds are:

a.  Taking the rollback segment offline and bringing back online
should also clear the full status.  This could potentially take
awhile if there are a large number of transaction that need to
be rolled back.

Alter rollback segment <name> offline;
Alter rollback segment <name> online;

b. Adding a datafile to the rollback segment tablespace so that
the segment can extend. You can then continue with the *current*
txn (that caused the rollback segment to get status FULL) and
the status will then get reset to  ONLINE.

If you are unable to wait for the full status to be cleared,
then bouncing the database will clear the rollback segment.

Basically -- this means that you have some transactions that are
still open and active preventing us from wrapping around and
reusing the rollback segment extents that are already allocated.
 You have some transactions that are taking longer to complete
then it takes to fill up 50m of rollback.  They will prevent us
from reusing their space and we get "stuck".  Until they and all
active transactions in that rollback segment commit -- the
status will remain full.

You can use the view v$transaction to see what transations these
might be.
Review & Followup
Rating: 2
question February 28, 2003
Reviewer: slmak from HK
Please look at the following statistics:

Rollback segemnt Status     Exist extents Current extent Current block
Transactions
---------------- ---------- ------------- -------------- -------------
------------
R02              FULL                 360            192            33
 2
SYSTEM           ONLINE                 5              0             7
 0
IBSRB01          ONLINE             1,605            720            19
 3
R04              ONLINE               421            364             4
 3
R03              ONLINE               358            193            16
 2
R01              ONLINE               548            166            13
 2

It shows that the R02 is FULL, but it only writes up to 192 extents while there
are 360 extents allocated to it.  It has two transactions, I use another query
to find that these two transations are INACTIVE, then how come the status of R02
is FULL?

Followup:
because RBS's are used as a circular buffer -- their heads and tails are not
"extent 1 and last extent" -- they are where ever they happen to be (the head it
constantly chasing the tail around and around -- like a dog chasing its tail)...

So, your inactive transaction is using "extend 193".  We cannot advance into it
-- even if 194..360 are not being used.  Hence the RBS is effectively "full" --
we won't use it until it is not "full" for new transactions (as they would just
fail)

February 28, 2003 Sudip -- Thanks for the question regarding "How to insert a string from java program to a table containing blob", version 8.1.5
You Asked
hi Tom,

 how r u?

 I'm staing you a problem regarding storing a string content
from a java program by calling procedure to a database table
containg blob type .

A more detail is :

table was created as :

create table files_org (id number,content blob);
/

a procedure has been created as:

 create or replace procedure ins_blob(id in number,content blob)
 as
 begin
  insert into files_org values(id,content);
 end;
/

this procedure was called from a java segment is given below....

        String s = "Supal";

        oracle.sql.BLOB BL = new
    oracle.sql.BLOB((OracleConnection)con,s.getBytes());
        String sProcName = "ins_blob";
            Vector vInParams = new Vector();
            vInParams.addElement(new Integer(123));
            vInParams.addElement(BL);

com.macmet.util.db.DatabaseUtility.executeStoredProcedure
             (con, sProcName,vInParams,null);

            in executeStoredProcedure()
 method((OracleCallableStatement)rcProc).setBLOB(index,
                     (oracle.sql.BLOB)obj);

====

the class executeStoredProcedure exists...

at the time of execution of this clas the following error
occurs:

Exception in thread "main" java.sql.SQLException: ORA-24812:
character set conve
rsion to or from UCS2 failed
ORA-06512: at "SCOTT.INS_BLOB", line 3
ORA-06512: at line 1

        at
oracle.jdbc.dbaccess.DBError.throwSqlException(Compiled Code)
        at oracle.jdbc.ttc7.TTIoer.processError(Compiled Code)
        at oracle.jdbc.ttc7.Oall7.receive(Compiled Code)
        at oracle.jdbc.ttc7.TTC7Protocol.doOall7(Compiled Code)
        at
oracle.jdbc.ttc7.TTC7Protocol.parseExecuteFetch(Compiled Code)
        at
oracle.jdbc.driver.OracleStatement.executeNonQuery(Compiled
Code)
        at
oracle.jdbc.driver.OracleStatement.doExecuteOther(Compiled Code)
        at
oracle.jdbc.driver.OracleStatement.doExecuteWithBatch(Compiled
Code)
        at oracle.jdbc.driver.OracleStatement.doExecute(Compiled
Code)
        at
oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(Compiled
Code
)
        at
oracle.jdbc.driver.OraclePreparedStatement.executeUpdate(Compiled
 Cod
e)
        at
oracle.jdbc.driver.OraclePreparedStatement.execute(Compiled
Code)
        at
com.macmet.util.db.DatabaseUtility.executeStoredProcedure(Compile
d Co
de)
        at tst.main(Compiled Code)

please observe it and try to find a solution...

a typical error ORA-24812 occured .. you can notice while
observing it ...

however

rest is okay,,

waiting for  your reply,,

good luck

Sudip
and we said...
Well, if you do it in straight JDBC -- I'd be glad to look at
it.  I do not know exactly what
com.macmet.util.db.DatabaseUtility might be doing under the
covers.

I can say that if I wanted to insert a string into a blob and
the string was reasonably sized (32k or less), I would use the
following logic instead:

create table files_org (id number,content blob);

create or replace
procedure ins_blob(id in number,content varchar2)
as
    l_blob  blob;
begin
    insert into files_org values( id, empty_blob() )
    returning content into l_blob;

    dbms_lob.writeappend
   ( l_blob, length(content), utl_raw.cast_to_raw(content) );
end;
/

That lets me send a string to the database and then convert it
(cast it) into a RAW.  then my java can look like:

import java.io.*;
import java.sql.*;
import java.util.*;

class test
{

public static void main(String args[])
{
    try
    {
    DriverManager.registerDriver (new
oracle.jdbc.driver.OracleDriver());

    Connection connection =
      DriverManager.getConnection
("jdbc:oracle:thin:@aria-dev:1521:ora816dev",
                                   "scott", "tiger");

    PreparedStatement ps = connection.prepareStatement
       ( "begin ins_blob( ?, ? ); end;" );

    ps.setInt( 1, 1 );
    ps.setString( 2, "Test String" );

    ps.executeQuery();

    ps.close();

    }
    catch( Exception e )
    {
        e.printStackTrace();
    }

}

}

It just deals with strings and lets the stored procedure worry
about any conversions.

If you want -- you can post an example that shows your issue
using the jdbc stuff straight out -- I can help figure out why
you are getting what you are that way...
Review & Followup
Rating: 5
An image file February 28, 2003
Reviewer: Ik from BG
Tom,

How do i insert an Image file into a BLOB column?

regards

Followup:
search for

load lob

on this site for many many articles and pointers on doing this

February 28, 2003 Rajesh -- Thanks for the question regarding "Comparing Two CLOB columns", version 8.1.7.4.1
You Asked
Hello Tom,

         Thank you for taking my question. Congratulations !! with your
excellent web site.

Trying to compare two Clob datatype columns in a procedure.
  Table has million of records, 15% of the records may exceed 32767 bytes.
   if P_clobA dbms_lob.getlength matches exactly with P_ClobB should return true
flag, but if the content differs even though byte count matches should return
false.
   These clob columns have Intermedia text enabled and sync happens every 5
minutes.

   Currently I'm testing with dbms_lob.compare function and I get 0 if no match
or 1 if matched.

What is the BEST approach to address this without running into performance issue
?

Thanks
Raj
and we said...
wow, you really have to compare documents larger than a couple thousand bytes
over millions of records -- that is a bummer.

It will not be fast, that is for certain.

intermedia text will not be useful for comparing documents.

dbms_lob.compare is as good as anything -- short of having the person that
stores the document also store persistently:

o a checksum of some sort

then you could create an index on:

create index exact_match on t( decode( checksum_a, checksum_b, 1, null ) );

and then a search for

select * from t where decode(checksum_a,checksum_b,1,null) = 1;

would be very fast -- but the developer would have to store the checksum for you
when they modified/created the document.
February 28, 2003 -- Thanks for the question regarding "Expensive", version 8.1.7
You Asked
Hi Tom,

  I came across your site recently. Very informative one. I guess the question I
am going to ask is very silly in nature. Pardon me if it is.

AT the end of a transaction involving many DML statements, which of the
following statement would be more expensive? COMMIT OR ROLLBACK. Can you give me
a technical explanation with examples for your answer.

Thanks,
and we said...
It is really quite a simple question actually when you think about it.

COMMIT is virtually free.
ROLLBACK is terribly expensive.

Why -- well, what do we have to do when we commit -- pretty much nothing, we did
all of the work getting to the point of commit.  The blocks were already
modified, the redo already generated.  All we really need to do at commit time
is flush our redo to disk if it wasn't already (may very well have).

Commits are virtually instantaneous.

A rollback on the otherhand -- that could take longer to do then the work
performed to get to that state!  If you updated 500 rows -- a rollback must
"unupdate them" -- including "un modifying the index" and all.  It is really
hard work.

We are optimized to commit -- rolling back is something you don't do very often
and takes a really long time to do it.
February 28, 2003 Kenneth -- Thanks for the question regarding "table size and tablespace", version 8.0.5
You Asked
Hey Tom,

I want to find the size of a table in bytes. What is the query I
must generate?

Secondly , I want to find all the objects on a single tablespace
. What is the query that i must run.

Thanks
Kenneth
and we said...
select bytes from user_segments where segment_name =
'TABLE_NAME' and segment_type = 'TABLE';

select owner, segment_name, segment_type from dba_segments where
tablespace_name = 'TABLESPACENAME';

the SERVER REFERENCE has an exhaustive description of all the
data dictionary views and what is available:
http://technet.oracle.com/doc/server.804/a58242/ch2.htm#745
...
Review & Followup
Rating: 3
Bytes columns February 28, 2003
Reviewer: reader from USA
I am assuming the 'BYTES' column indicates 'occupied' not 'allocated' bytes ?

Thanks

Followup:
bytes is allocated space -- amount of space dedicated to that table.

If you want the amount of space actually used by a table -- see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:231414051079

February 28, 2003 gs -- Thanks for the question regarding "update
      sql, subquery_factoring_clause", version 9.2.0

      You Asked
Dear Tom,

Ques1:
--------

I have the following tables.

create table t1 (
        id      number,
        k1      number,
        k2      number,
        dt      date,
    CONSTRAINT pk_t1 Primary Key(id)
);

create table t2 (
        id      number,
        k1      number,
        dt      date,
    CONSTRAINT fk_t2_t1 FOREIGN KEY(id)
    REFERENCES t1 (id)
) ;

create table t3 (
        id      number,
        k2      number,         --Fk to t1.k2
        dt      date,
    CONSTRAINT fk_t3_t1 FOREIGN KEY(id)
    REFERENCES t1 (id)
) ;

I want to update t1.dt with the minimum of the t2.dt and t3.dt for the following
condition. What is the best way to do this?

Cond:
=======
t1.id = t2.id and t1.k1 = t2.k1
t1.id = t3.id and t1.k1 = t3.k2

update t1 set dt = (select min(mdt) from
        (
        select t2.id, t2.dt mdt from t2, t1 where t1.id = t2.id and t1.k1 =
t2.k1
        union all
        select t3.id, t3.dt mdt from t3, t1 where t1.id = t3.id and t1.k1 =
t3.k2
        ) x
where t1.id = x.id
)
where exists (select null from ....)

or

update t1 set dt = (select min(mdt) from
        (
        select t2.id, min(t2.dt) mdt from t2, t1 where t1.id = t2.id and t1.k1 =
t2.k1
        group by t2.id
        union all
        select t3.id, min(t3.dt) mdt from t3, t1 where t1.id = t3.id and t1.k1 =
t3.k2
        group by t3.id
        ) x
where t1.id = x.id
)
where exists (select null from ....)

Is the above approach correct?

Ques2:
--------

Is subquery_factoring_clause doesn't work with updates?

In the above case, I want to have the subquery "x"  under "WITH" clause and
replace the subquery "x" on the "exists" part and the other part of the update.

thanks,

      and we said...
1) me, well, me I would not update T1 at all.  T1 should get mdt from t2 and t3
at run time.  The INSTANT you do the update, the data is all out of date again -
-people modify t2 and t3 and bamm, data is not valid.

Just use the view v:

ops$tkyte@ORA920> insert into t1 values ( 1, 1, 1, sysdate );

1 row created.

ops$tkyte@ORA920> insert into t2 values ( 1, 1, sysdate );

1 row created.

ops$tkyte@ORA920> insert into t3 values ( 1, 1, sysdate-10 );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t1 values ( 2, 2, 2, sysdate );

1 row created.

ops$tkyte@ORA920> insert into t2 values ( 2, 2, sysdate-20 );

1 row created.

ops$tkyte@ORA920> insert into t3 values ( 2, 2, sysdate );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace view v
  2  as
  3  select t1.*, least( (select min(dt) from t2 where t2.id = t1.id),
  4                      (select min(dt) from t3 where t3.k2 = t1.k1) ) mdt
  5    from t1
  6  /

View created.

ops$tkyte@ORA920> select * from v;

        ID         K1         K2 DT        MDT
---------- ---------- ---------- --------- ---------
         1          1          1 28-FEB-03 18-FEB-03
         2          2          2 28-FEB-03 08-FEB-03

But if you really wanted, you can:

ops$tkyte@ORA920>
ops$tkyte@ORA920> update v set dt = mdt;

2 rows updated.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from v;

        ID         K1         K2 DT        MDT
---------- ---------- ---------- --------- ---------
         1          1          1 18-FEB-03 18-FEB-03
         2          2          2 08-FEB-03 08-FEB-03

2) WITH doesn't make sense.  You can up date a view however like this:

ops$tkyte@ORA920> update (
  2  select t1.*, least( (select min(dt) from t2 where t2.id = t1.id),
  3                      (select min(dt) from t3 where t3.k2 = t1.k1) ) mdt
  4    from t1 )
  5   set dt = mdt;

2 rows updated.

that is your "factored" subquery.

Review &
      Followup

      Rating: 4
This is just
      one time process,  February 28, 2003
Reviewer:
      gs
thanks Tom,

This is just one time process after that t2.dt and t3.dt cease to exist.

I was asking for the WITH clause, because in the update, if you have something
like

update t1 set col1 = (
--begin
       select xx
       from term t2
       where <join cond>
--end
       )
where exists (
--begin
       select xx
       from term t2
       where <join cond>
--end
        )

I was thinking, I would put the query part between the "--begin" and "--end" in
thw "WITH" clause and refer them once. but, it doesn't seem to be right as it
has correlation in this particular case.

BTW, the site looks excellent with the new changes. I like the new menu very
much.

February 28, 2003 satish -- Thanks for the question regarding
      "standby database -- have to have same directory structures?", version
      8.1.6

      You Asked
hai Tom,
do we need to have similar directory structure in the standby
server as of main server.i.e can we place all the datafiles and
redologs in one filesystem on different server and perform all
the steps(modify in init.ora files)thanks

      and we said...
No, you do not need the same/similar structure (although that
does ease things a bit).

On the standby you can:

o Set the filename conversion initialization parameters
DB_FILE_NAME_CONVERT
  Converts primary database datafile filenames to
  standby datafile filenames, for example, from tbs_*
  to standbytbs_*.
LOG_FILE_NAME_CONVERT
  Converts primary database redo log filenames to
  standby database redo log filenames, for example,
  from log_* to standbylog_*.

o Rename the files manually using ALTER DATABASE
statements

SQL> ALTER DATABASE RENAME FILE ’/data/df3.f’ to
’/standby/df3.f’;

o Use a combination of conversion parameters and
manual renames.
Review & Followup

      Rating: 3
oracle9i R2 --
      physical standby  February 28, 2003
Reviewer:
      Baqir Hussain  from Emeryville, CA USA
Tom,
oralce9i support multiple point conversion on standby database.  We have the
following situation:

PRIMARY database TEST1: all the files are under directory
/export/home
STANDBY database TEST2: two files are copied from test1 to test2 under directory
/u04/ora1 and rest of them under dir
/u06/ora1
STANDBY databse init file the following entry:

db_file_name_convert=('/export/home','/u04/ora1','/export/home','/u06/ora1')

the standby database was put into recovery mode as follows:
1. startup nomount pfile=
2. alter database mount standby database;
3. alter database recover managed standby disconnect from session;

alert log of the standby database indicates it is not able to find files in
/u06/ora1 directory, therefore, deactivate the recovery process.
Then I flipped the directory structure as:
db_file_name_convert=('/export/home','/u06/ora1','/export/home','/u04/ora1')
after runnig all sql to establish stanby, now alert log complains about files
not found in /u04/ora1.

Now a couple of questions :
1. The same directory structure to mount to multiple directory structure is not
supported in oracle9i OR something I am not doing right to achieve it.
2. You also mentioned we can achieve it also by
alter database rename file ..
I am little bit confuse here.  Standby database can not be started as:

startup mount

to rename file because we have already copied control file from the primary
database to the standby database. Standy won't let us mount database with the
following errors:

ORA-01666: controlfile is for a standby database

Please advise how can I rename datafiles in such a scenerio.
Thanks

          Followup:

1) symbolic links can help you out here -- only one mapping can be used here.

or read the docs:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96653/sbydbmanappx.htm#634680

it shows you how to use rename as I described

March 01, 2003 atul -- Thanks for the question regarding "Backup
      stratergy", version 8.1.7

      You Asked
Sir,

I have a customer site where oracle 8i is running on unixware 8.1.7
He has only 2 disks.On one disk O/S is there and on other disk oracle software
and all datafiles etc is there..(He is not afford to take more than 2 disks..)

He is in noarchive log mode...

so in this scenario..
1)Can i suggest him to take oracle down and take dump of the disk where oracle
is there ...so its same as cold backup...
2)And daily full export...
3)what happens if we take dump of the disk keeping oracle up???
4)Is that correct backup stratergy??

Thanks,
atul

      and we said...
make sure this customer knows that they will lose committed transactions some
day.

Not "might"
Not "could"
"WILL" -- it is but a simple matter of time -- 5 minutes, 5 days whatever --
they WILL lose data.

In noarchive log mode, that is the only thing I can assure you of 100%.

Your only backup solution is a full cold backup in this case.  They must
shutdown and take full copies of the datafiles, control files and init.ora
files.  Make sure the shutdown was "immediate" or "normal" -- else you have to
backup the online redo log files as well.

An export is a poor substitution for a backup.  If you do it that way however
make sure to use CONSISTENT=Y on the export command line -- else the logical
consistency of the tables as they relate to eachother will be messed up for
sure.

Do not copy oracle datafiles while the database is running in noarchivelog mode.
 That only succeeds in wasting time, resources and tape.
Review &
      Followup

      Rating: 4
Another
      Suggestion  February 28, 2003
Reviewer:  Todd
      Cox  from Birmingham, AL USA
If the customer really won't purchase additional disks, I would at least see if
there's room to put the archives on the OS disk, and run in archivelog mode.
Also put duplexed online log members as well as duplexed control files on both
disks.  Just make sure that the archived logs are on separate disks from the
datafiles.

With a good hot backup each night, this should be a workable solution, and you
should be able to reduce your data loss scenario to the case where both disks
are lost at the same time.

      Rating: 4
Another disk
      option.  February 28, 2003
Reviewer:  Jeremy
      Smith  from Salt Lake City
On my home machine, I recently purchased a 120 gigabyte external firewire drive
with card for less than 200 dollars.

This works great for all sorts of backup.

They're hot-plug, so you can swap them and bring one offsite for disaster
purposes.

If your customer has enough money to afford you and to afford Oracle, they
probably have enough to afford another disk.

      Rating:
      5
Thanks..  February 28, 2003
Reviewer:
      atul  from india
Thanks for prompt reply as usual..
In export case if our export file > 2gb then will it gives error or we will find
error at the time of import???

Is 2gb is the limit of export file??

Thanks.
atul

          Followup:

depends on the OS and version of Oracle you are using.

64bit Oracle -- no limit.
32bit Oracle -- 2 gig limit which can be overcome using

  o named pipes (see link in next followup)
  o filesize= file= options to create files of max filesize bytes

      Rating: 5
Export >
      2Gig  March 01, 2003
Reviewer:  Michael T.
       from Dallas, Tx
Atul,

Refer to the following link for information about exports greater than 2Gig.  It
also contains a Unix script to get around this problem by writing the export to
a named pipe and compressing/splitting the contents of the pipe into smaller
chunks.

http://asktom.oracle.com/pls/ask/f?p=4950:8:53337509159045681::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:32412348052,

March 01, 2003 -- Thanks for the question regarding "Space
      allocation", version 9201

      You Asked
Hi Tom

During my exercise to find the space used by a table 'X' I find huge difference
of sizes between the following 2 methods:

TAble X has the following
INitial Extent :65K
PCT_FREE       :ZERO
MIN_EXTENTS    : 1
X is in a Tablespace that is : Extent Management Local AUtoAllocate

When I issue the following to calculate the size of a table:

A)
select sum(bytes) from dba_segments where segment_type='TABLE'
And segment_name='x';

SUM(BYTES)
------------
6020923392   or approx 6GB

B) Your recommended way of computing allocated space:

compute sum of blocks on report
break on report

select extent_id, bytes, blocks
     from user_extents
    where segment_name = 'X'
      and segment_type = 'TABLE';

The sum of blocks is reported to be:734976

select blocks, empty_blocks,
     avg_space, num_freelist_blocks
    from user_tables
    where table_name = 'X';

  BLOCKS EMPTY_BLOCKS  AVG_SPACE NUM_FREELIST_BLOCKS
---------- ------------ ---------- -------------------
733845        0      0        0

db_block_size = 8192

734976* 8192= 6020923392
Which is similar to (A)

C)Now the following gives me a totally different number

After analyzing a sample of table x

select avg_row_len from user_tables where table_name='x';

avg_row_len
----------
100

Select count(*) from x;

-----------

110,049,060

So the total space used by the table X is:

100 * 110049060  =11 GB

In A and B I get 6GB of space used by table X but in C the total space is 11 GB.
WHat is contributing to the 5GB . The overhead cannot be so huge?

Regards
TS

      and we said...
An average row length of 100 is the DEFAULT row length.

I'm going to guess -- since you didn't really say -- that you are gathering
stats with method-opt => null, which then sets the default row length.

You need to gather stats on at least one column for dbms_stats to set the avg
row length to a non-default value:

ops$tkyte@ORA920> exec dbms_stats.gather_table_stats(
user,'T',method_opt=>null);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select avg_row_len from user_tables where table_name = 'T';

AVG_ROW_LEN
-----------
        100

ops$tkyte@ORA920> exec dbms_stats.gather_table_stats( user,'T');

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select avg_row_len from user_tables where table_name = 'T';

AVG_ROW_LEN
-----------
          6

ops$tkyte@ORA920>

And average row length would be an EXCESSIVELY poor predictor of actual space
used.  The only way to do it is look at the blocks, that is all that matters.

Review &
      Followup

      Rating:
      4
  March 01, 2003
Reviewer:
      A reader
You are right about the method opt. It was null.

March 01, 2003 P.Elamaran -- Thanks for the question regarding
      "DDL in Procedure", version 8i

      You Asked
Sir,

How to execute DDL statements inside the Procedure. On using dbms_Sql package on
system user, its giving as 'Insufficient Privileges' on executing it.

Give tiprs about normalisation

Thanks in Adavance.

      and we said...
DO NOT use sys, system or any of the oracle accounts for coding or even
practicing.

pretend they do not exist, just leave them be.  Create your own users.

See:

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html
for why -- you want to use "EXECUTE IMMEDIATE" also, not dbms_sql, to execute
DDL.  It is much easier.

March 01, 2003 Bill -- Thanks for the question regarding
      "external table and date format", version 9.2.0

      You Asked
Hi Tom,
What i am trying to do is load in bank transactions ( downloaded in a comma
delimited format from the bank ) into my database.  My approach is to create an
external table from the file and then create a regular table from the external
one. then the data can be manipulated etc.
the problem i am having is that the .csv file has a date format that the insert
( from external_table to table t ) is failing on.

an example line from my .csv file looks like:
2/24/2003,-40,*,,"ATM WITHDRAWAL "

my external table definition looks like:
create table external_table (
    trandate date,
    amount number(10,2),
    ignore1 char(1),
    ignore2 char(1) null,
    descr varchar2(4000)
)
organization external (
    type oracle_loader
    default directory filebackup
    access parameters
        (     fields terminated by ','
            optionally enclosed by '"'
            missing field values are null )
    location ('Checking.csv')
) ;

When i load i get this error:
ERROR at line 1:
ORA-29913: error in executing ODCIEXTTABLEFETCH callout
ORA-30653: reject limit reached
ORA-06512: at "SYS.ORACLE_LOADER", line 14
ORA-06512: at line 1

which when i view the log i see:
LOG file opened at 03/01/03 09:29:10

Field Definitions for table EXTERNAL_TABLE
  Record format DELIMITED BY NEWLINE
  Data in file has same endianness as the platform
  Rows with all null fields are accepted

  Fields in Data Source:

    TRANDATE                        CHAR (255)
      Terminated by ","
      Enclosed by """ and """
      Trim whitespace same as SQL Loader
    AMOUNT                          CHAR (255)
      Terminated by ","
      Enclosed by """ and """
      Trim whitespace same as SQL Loader
    IGNORE1                         CHAR (255)
      Terminated by ","
      Enclosed by """ and """
      Trim whitespace same as SQL Loader
    IGNORE2                         CHAR (255)
      Terminated by ","
      Enclosed by """ and """
      Trim whitespace same as SQL Loader
    DESCR                           CHAR (4000)
      Terminated by ","
      Enclosed by """ and """
      Trim whitespace same as SQL Loader
error processing column TRANDATE in row 1 for datafile D:\sql\Checking.csv
ORA-01843: not a valid month

So it appears it is chocking on the date format. I tried to_date on the insert
but that didn't work. That date in the .csv file can either be in M:DD:YYYY or
MM:DD:YYYY format.
How best is this solved ?

Thanks -
Bill

      and we said...
Use a date format in the CREATE TABLE.  Here is one I've used:

create table big_table_external
( OWNER              VARCHAR2(30),
  OBJECT_NAME        VARCHAR2(30),
  SUBOBJECT_NAME     VARCHAR2(30),
  OBJECT_ID          NUMBER,
  DATA_OBJECT_ID     NUMBER,
  OBJECT_TYPE        VARCHAR2(18),
  CREATED            DATE,
  LAST_DDL_TIME      DATE,
  TIMESTAMP          VARCHAR2(19),
  STATUS             VARCHAR2(7),
  TEMPORARY          VARCHAR2(1),
  GENERATED          VARCHAR2(1),
  SECONDARY          VARCHAR2(1)
)
ORGANIZATION EXTERNAL
( type oracle_loader
  default directory data_dir
  access parameters
  (
    records delimited by newline skip 21
    fields terminated by '|'
    missing field values are null
    ( owner ,object_name ,subobject_name ,object_id ,data_object_id ,object_type
      ,created date 'dd-mon-yy' ,last_ddl_time date 'dd-mon-yy'
      ,"TIMESTAMP" ,status ,temporary ,generated ,secondary
    )
  )
    location ('big_table.dat')
  )
/

for example -- your date format mask would be mm:dd:yyyy which would handle
both.

ALTERNATIVELY

you could have trandate be a VARCHAR -- just a string.  And then you can use
to_date() on the string in your SQL.

I would use the create table approach however, it'll put bad records to the .bad
file and not fail your SQL statement when you encounter a bad date (if you set
rejects on the create table that is...)

Review &
      Followup

      Rating: 5
thanks - that
      did it  March 01, 2003
Reviewer:  Bill

thanks again Tom

March 01, 2003 Marko -- Thanks for the question regarding
      "DBMS_JOB in after insert/update row trigger against mutating tables",
      version 8.1.7

      You Asked
I've read all the questions regarding mutating tables in Ask Tom, but (that's
odd) it seams we've encountered a "new" problem: when the last item in the ITEM
table (= child table) gets the status 'DONE', the corresponding item in ORDER
table (=master table) should also get the status 'DONE'. So the following
trigger will fail:

create or replace trigger items_rai
        after update of status
        on items
        for each row
declare
        i       number;
        j       number;
begin
        select count(*) into i from items
         where status like 'DONE'       and
               order_id = :new.order_id;
        select count(*) into j from items
         where order_id = :new.order_id;

        if i = j and i != 0 then
                update orders set status = 'DONE'
                 where order_id = :new.order_id;
        end if;
end;
/

Now, I think we can manage that with solutions described in earlear posts, but
(I think) they're quite ... cumbersome (I like thinks simple). So, there's my
suggestion:

create procedure SetOrderStatus (order_P IN number) is
declare
        i       number;
        j       number;
begin
        select count(*) into i from items
         where status like 'DONE'       and
               order_id = order_P;
        select count(*) into j from items
         where order_id = order_P;

        if i = j and i != 0 then
                update orders set status = 'DONE'
                 where order_id = order_P;
        end if;
end;
/

create or replace trigger items_rai
        after update of status
        on items
        for each row
declare
        job_number      number;
begin
        dbms_job.submit (job_number,
                         'SetOrderStatus ('||:new.order_id||');',
                         sysdate);
end;
/

The solution works, but I'm not quite sure we've done the thing in the "spirit
of relational and transactional architecture". Any comment?

Thanks for Your help!

      and we said...
Not only "not in the spirit" but -- what happens when someone "undoes" something
after they mark it done but BEFORE the job runs????  ugh.

The spirit of the relational system would have you simply query:

select ...,
    decode( (select count(*) from items where ... status<>'DONE' and ROWNUM=1),
            0, 'DONE', 'NOT DONE' ) status
  from parent_table;

that is -- derived information would never be stored, just like you would never
store AGE - you would store DATE_OF_BIRTH and derive AGE at runtime.

Just create yourself a view with this derived column and be done with it.

Alternatively, you could:

create or replace trigger items_rai
after insert
for each row
begin
   if (:new.status <> 'DONE')
   then
       update orders set not_done = not_done+1 where order_id = :new.order_id;
   end if;
end;

create or replace trigger items_rai2
after update of status  on items
for each row
   if (:old.status <> :new.status )
   then
       update orders set
          not_done = not_done + decode(:new.status,'DONE',-1,+1)
        where order_id = :new.order_id;
   end if;
end;

or something similar (not tested -- you would need to flesh this out), you'll
have a not_done counter in the orders table.

not_done <> 0 implies "not done"
not_done = 0 implies "all done"

March 02, 2003 John -- Thanks for the question regarding
      "Question on your HOTSOS presentation, "Developing Efficient ..."
      Demo002.sql", version 9.2.0

      You Asked
Tom,

I didn't understand the end part of your "Demo002.sql".

DOC>Now we'll do the same but with different stats, using similar again to show
that when the
DOC>plans wouldn't change    -- cursor sharing similar works like exact
DOC>*/
SQL> analyze table emp delete statistics;

Table analyzed.

Elapsed: 00:00:00.01
SQL> analyze table emp compute statistics
  2  for table
  3  for all indexes
  4  /

Table analyzed.

Elapsed: 00:00:02.09
SQL> set autotrace traceonly explain
SQL> select * from emp where deptno = 99;
Elapsed: 00:00:00.00

WHY DID WE GET A DIFFERENT EXECUTION PLAN JUST BECAUSE YOU RE-ANALYZED?  THE
STATITICS SHOULD BE THE SAME; THEREFORE, THE EXECUTION PLANS SHOULD BE THE SAME,
BUT THEY ARE NOT.  WHAT IS THE REASON FOR THIS?  WAS YOUR POINT JUST TO SHOW
THAT WE CAN'T TRUST THE CURSOR_SHARING FEATURE SINCE IT IS UNPREDICTABLE?

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=107 Card=573 Bytes=2
          2920)

   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=107 Card=573
          Bytes=22920)

   2    1     INDEX (RANGE SCAN) OF 'DEPT_IDX' (NON-UNIQUE) (Cost=115
          Card=573)

SQL> select * from emp where deptno = 1;
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=107 Card=573 Bytes=2
          2920)

   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=107 Card=573
          Bytes=22920)

   2    1     INDEX (RANGE SCAN) OF 'DEPT_IDX' (NON-UNIQUE) (Cost=115
          Card=573)

SQL> set autotrace off
SQL> pause

SQL> /*
DOC>and now to see that cursor sharing=similar doesn't come up with the "same"
plan anymore:
DOC>*/
SQL> alter system flush shared_pool;

System altered.

Elapsed: 00:00:00.00
SQL> alter session set cursor_sharing=similar;

Session altered.

Elapsed: 00:00:00.00
SQL> set autotrace traceonly explain
SQL> select * from emp where deptno = 99;
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=107 Card=573 Bytes=2
          2920)

   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=107 Card=573
          Bytes=22920)

   2    1     INDEX (RANGE SCAN) OF 'DEPT_IDX' (NON-UNIQUE) (Cost=115
          Card=573)

SQL> select * from emp where deptno = 1;
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=107 Card=573 Bytes=2
          2920)

   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=107 Card=573
          Bytes=22920)

   2    1     INDEX (RANGE SCAN) OF 'DEPT_IDX' (NON-UNIQUE) (Cost=115
          Card=573)

SQL> pause

SQL> set autotrace off
SQL> alter session set cursor_sharing=exact;

Session altered.

Elapsed: 00:00:00.00
SQL> select * from my_v$sql;

SQL_TEXT                                      VERSION_COUNT CHILD_NUMBER
OPERATION
--------------------------------------------- ------------- ------------
---------------------------
select * from emp where deptno = :"SYS_B_0"               1            0 SELECT
STATEMENT
select * from emp where deptno = :"SYS_B_0"               1            0 INDEX
select * from emp where deptno = :"SYS_B_0"               1            0 TABLE
ACCESS

Elapsed: 00:00:00.01
SQL> pause

      and we said...
quote

WHY DID WE GET A DIFFERENT EXECUTION PLAN JUST BECAUSE YOU RE-ANALYZED?

because I did not gather column level histograms.  Hence, the optimizer didn't
know almost all of the data had one value for deptno and a single row had
another value for deptno.

It assumed an even distribution between the two values....

If you look further up in the script -- i used:

analyze table
compute statistics
for table
for all indexes
FOR ALL INDEXED COLUMNS <<<<=====
Review &
      Followup

      Rating: 5
Question on
      your HOTSOS presentation, "Developing Efficient ...  March
      24, 2003
Reviewer:  John Cantu  from Dallas, Texas

Thanks, Tom.

March 02, 2003 Jayakanthan -- Thanks for the question regarding
      "How to Use where clause in Truncate table?", version 9.1.2

      You Asked
Hi tom,

Very Nice to meet you in this forum.

I explain the scenario what we need to discuss now.

my customer is having large database with some million records.i need to archive
those data in monthly basis to another schema in another server(structure same
like source schema).I want to move the data from source schema to destination
with some condition.i can use 'DELETE COMMAND' to do the same.but it is not
advisable.('cos it creates rollback segment).so that i want to use truncate
statement with where clause.otherwise if you have any alternative way to do the
same,please direct me to do the same.

I hope you'll find the answer and assit me.

Thanks in advance

Jayakanthan

      and we said...
Think about it -- if you understand what truncate does, it would be very clear
that truncate cannot use a where clause.

This is something that should have been designed into the application from day
one (but never is for some reason).

Here are some options:

a) use delete.  so what, it generates redo and rollback -- size your system
properly for the work it does and this is not an issue.

b) use partitioning <<<==== superb idea here

partition the table by date.   Now an "archive" is just simple DDL commands (no
redo, no undo):

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:928294572145

c) CREATE TABLE NEW_TABLE as select * from old_table where <rows to keep>

that can be done with nologging and in parallel -- no redo, no undo

create index on new_table -- in parallel no logging

drop table old_table
rename new_table to old table

March 02, 2003 Prince -- Thanks for the question regarding
      "Chained Rows and Autotrace", version 8.1.7.4

      You Asked
Dear Tom,

I have a table with 25% chained rows.

I am trying to determine the impact of this chaining and rebuild it if needed.

To determine, whether I need to reorg this table I did the following test
(Logically it seems, what I am doing is correct).

Though the statistics shows there are lot of chained rows, the "table fetch
continued row" of autotrace doesn't validate it.

Could you pl, give me an explanation on this behaviour? Am I doing anything
wrong here?

Note: Though I didn't choose the "explain" of autotrace, OEM showed that it was
doing a FTS.

Thanks,

SQL> select count(*) from chained_rows where table_name = 'MYTABLE';

  COUNT(*)
----------
    258683

SQL> select table_name, last_analyzed, avg_row_len, num_rows, chain_cnt from
dba_tables
where owner = 'DB_USER' and table_name = 'MYTABLE'

TABLE_NAME                     LAST_ANALYZED    AVG_ROW_LEN   NUM_ROWS
CHAIN_CNT
------------------------------ ---------------- ----------- ----------
----------
MYTABLE                        2002-06-25 11:35          35    1061399
277139

SQL> select     a.name, b.value
    from     v$statname a, v$mystat b
    where     a.statistic#=b.statistic#
        and name like 'table%';

NAME                                                                   VALUE
----------------------------------------------------------------- ----------
table scans (short tables)                                                28
table scans (long tables)                                                  7
table scans (rowid ranges)                                                 0
table scans (cache partitions)                                             0
table scans (direct read)                                                  0
table scan rows gotten                                               2329430
table scan blocks gotten                                              104510
table fetch by rowid                                                    1573
table fetch continued row                                                 99

SQL> set autotrace traceonly stat ;
/* there is index only on the CNY# col */
SQL> select /*+ full */ CNY#, PAYMENTDATE  from DB_USER.MYTABLE ;

1064428 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
         13  db block gets
      81903  consistent gets
       8377  physical reads
        180  redo size
   56728523  bytes sent via SQL*Net to client
    7877096  bytes received via SQL*Net from client
      70963  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
    1064428  rows processed

SQL> select  a.name, b.value
    from    v$statname a, v$mystat b
    where   a.statistic#=b.statistic#
        and     name like 'table%';

9 rows selected.

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

SQL> set autotrace off
SQL> /

NAME                                                                   VALUE
----------------------------------------------------------------- ----------
table scans (short tables)                                                30
table scans (long tables)                                                  8
table scans (rowid ranges)                                                 0
table scans (cache partitions)                                             0
table scans (direct read)                                                  0
table scan rows gotten                                               3393860
table scan blocks gotten                                              186411
table fetch by rowid                                                    1583
table fetch continued row                                                 99

9 rows selected.

      and we said...
This is a cool question actually - lots to be learned from this one aobut how
the data is processed.

I'll guess that 100% of your rows that are chained are actually MIGRATED, which
is a special kind of "chaining".  (if you have my book, I go into great detail
on this -- with pictures and everything).

My other guess would be that CNY#, PAYMENTDATE  are near the "front" of your
table (they come first in the create table statement).

Now lets see why.... We need a little background first.

We will migrate a row when an update to that row would cause it to not fit
on the block anymore (with all of the other data that exists there currently).
A migration means that the entire row will move and we just leave behind the
"forwarding address".  So, the original block just has the rowid of the new
block and the entire row is moved.

Then there is the true "chained row" (well, a migrated row is a specialized case
of a chained row...)  With a true "chained row", part of the DATA of a row is on
one block and part of it is on another block.  So, instead of just having a
forwarding address on one block -- and the data on another (thats a migrated
row), we have data on two or more blocks....

When we FULL SCAN a table, we actually *ignore* the forwarding addresses (the
head rowpiece we call it for a row).  We know that as we continue the full scan,
we'll eventually get to that row so we can ignore the forwarding address and
just process the row when we get there.  Hence, in a full scan migrated rows
don't cause us to really do any extra work -- they are meaningless.  Oh sure,
the forwarding address is consuming a couple of bytes on the block -- it is
overhead -- but frankly, it is meaningless.

When we INDEX READ into a table -- then a migrated row will cause additional
IO's.  That is because the index will tell us "goto file X, block Y, slot Z to
find this row".  But when we get there we find a message that says "well, really
goto file A, block B, slot C to find this row".  We have to do another IO
(logical or physical) to find the row.

"truly" chained rows affect us differently.  Here, it depends on the data we
need.  If we had a row with two columns that was spread over two blocks -- the
query:

select column1 from t

where column1 is the "first" column -- would not cause any table fetch continued
row.  It would not actually have to get column2 -- it would not follow the
chained row all of the way out.  On the other hand, if we ask for

select column2 from t

then you would in fact see a table fetch continued row.

This behavior is so predicable that setting up an illustrative example is easy.
That is what I'll do here.  I am using an 8k block size so if you use something
different, you'll have to adjust the column sizes appropriately (or add more
columns as the case may be).

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int primary key, a
char(2000), b char(2000), c char(2000), d char(2000), e char(2000) );

Table created.

that is our table.  The char(2000)'s will let us easily cause rows to migrate
or chain.  I used 5 columns a,b,c,d,e so that the total rowsize can grow to
about 10k -- bigger then my block, ensuring I can truly chain a row...

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t(x) values ( 1 );
1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t(x) values ( 2 );
1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t(x) values ( 3 );
1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

we don't care about seeing a,b,c,d,e -- just fetching them.  they are really
wide so we'll surpress their display....

ops$tkyte@ORA817DEV.US.ORACLE.COM> column a noprint
ops$tkyte@ORA817DEV.US.ORACLE.COM> column b noprint
ops$tkyte@ORA817DEV.US.ORACLE.COM> column c noprint
ops$tkyte@ORA817DEV.US.ORACLE.COM> column d noprint
ops$tkyte@ORA817DEV.US.ORACLE.COM> column e noprint

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

         X
----------
         1
         2
         3

ops$tkyte@ORA817DEV.US.ORACLE.COM> @mystat continued
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  --and b.value > 0
  6  /

NAME                                VALUE
------------------------------ ----------
table fetch continued row               0

Now that is to be expected -- the rows came out in the order we put them in
(Oracle full scanned this query, it processed the data as it found it).  Also
expected is the table fetch continued row (TFCR from now on) is zero.  This data
is so small right now, we know that all three rows fit on a single block.  No
chaining.

Now, lets do some updates in a specific way.  I want to demonstrate the
MIGRATION issue and how it affects (or rather DOESN'T affect) the full scan:

ops$tkyte@ORA817DEV.US.ORACLE.COM> update t set a = 'x', b = 'x', c = 'x' where
x = 3;
1 row updated.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> update t set a = 'x', b = 'x', c = 'x' where
x = 2;
1 row updated.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> update t set a = 'x', b = 'x', c = 'x' where
x = 1;
1 row updated.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

Note the order of updates -- I did last row first, first row last...

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

         X
----------
         3
         2
         1

ops$tkyte@ORA817DEV.US.ORACLE.COM> @mystat continued
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  --and b.value > 0
  6  /

NAME                                VALUE
------------------------------ ----------
table fetch continued row               0

Interesting, the rows came out "backwards" now (yet further proof that rows do
NOT come out in the order of insertion generally -- but thats another question).
 That is because we updated row 3 first.  It did not have to migrate, but it
filled up block 1.  We then updated row 2.  It migrated to block 2 -- with row 3
hogging all of the space, it had to.  We then updated row 1, it migrated to
block 3.  We migrated rows 2 and 1, leaving 3 where it started (you can analyze
the table to confirm this yourself).

So, when Oracle full scanned the table, it found row 3 on block 1 first, row 2
on block 2 second and row 1 on block 3 third.  It IGNORED the head rowid piece
on block 1 for rows 1 and 2 and just found the rows as it scanned the table.
That is why the TFCR = 0 still.

So, lets see a migrated row affecting the TFCR...

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where x = 3;

         X
----------
         3

ops$tkyte@ORA817DEV.US.ORACLE.COM> @mystat continued
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  --and b.value > 0
  6  /

NAME                                VALUE
------------------------------ ----------
table fetch continued row               0

I'm using RBO and RBO is notorious for being index happy -- hence, this was an
index range scan / table access by rowid.  We didn't increment the TFCR yet
since row 3 isn't migrated...

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where x = 1;

         X
----------
         1

ops$tkyte@ORA817DEV.US.ORACLE.COM> @mystat continued
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  --and b.value > 0
  6  /

NAME                                VALUE
------------------------------ ----------
table fetch continued row               1

but row 1 is!  and there it is.  Using the index, we forced a TFCR...

Now, lets see the effect of a CHAINED row:

ops$tkyte@ORA817DEV.US.ORACLE.COM> update t set d = 'x', e = 'x' where x = 3;
1 row updated.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

row 3 no longer fits on block 1.  With d and e set, the rowsize is about 10k.
It is truly chained...

ops$tkyte@ORA817DEV.US.ORACLE.COM> select x, a from t where x = 3;

         X
----------
         3

ops$tkyte@ORA817DEV.US.ORACLE.COM> @mystat continued
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  --and b.value > 0
  6  /

NAME                                VALUE
------------------------------ ----------
table fetch continued row               1

fetch something from the "head" of the table and lo and behold -- it will NOT
cause a TFCR.  Column A is on the "head rowpiece" block.  No extra IO to get
it....

ops$tkyte@ORA817DEV.US.ORACLE.COM> select x, d, e from t where x = 3;

         X
----------
         3

ops$tkyte@ORA817DEV.US.ORACLE.COM> @mystat continued
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  --and b.value > 0
  6  /

NAME                                VALUE
------------------------------ ----------
table fetch continued row               2

But going after columns D and E via the index do increment the TFCR.  We had
to put the row back together from its head to its tail to get that data.

Interestingly the full scan is now affected as well:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

         X
----------
         3
         2
         1

ops$tkyte@ORA817DEV.US.ORACLE.COM> @mystat continued
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  --and b.value > 0
  6  /

NAME                                VALUE
------------------------------ ----------
table fetch continued row               3

the TFCR was incremented here because of row 3 -- we had to assemble it in its
entirety to get the trailing columns.  Rows 1 and 2, even though they are
"chained" -- migrated really -- don't add to the TFCR since we full scanned.

continuing on:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select x, a from t;

         X
----------
         3
         2
         1

ops$tkyte@ORA817DEV.US.ORACLE.COM> @mystat continued
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  --and b.value > 0
  6  /

NAME                                VALUE
------------------------------ ----------
table fetch continued row               3

No TFCR since we didn't have to assemble row 3, we just needed the first two
columns

ops$tkyte@ORA817DEV.US.ORACLE.COM> select x, e from t;

         X
----------
         3
         2
         1

ops$tkyte@ORA817DEV.US.ORACLE.COM> @mystat continued
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  --and b.value > 0
  6  /

NAME                                VALUE
------------------------------ ----------
table fetch continued row               4

but by asking for d and e -- we did.....

there you go.  You most likely have only MIGRATED ROWS but even if they are
truly chained, the columns you are selecting are at the front of the table.

Migrated rows affect OLTP systems which use indexed reads to read singleton
rows.  In the worst case, you can add an extra IO to all reads which would be
really bad.

Truly chained rows -- well, they affect everyone.

So, how can you decide if you have migrated or truly chained?  glad you
asked...

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(e) from t;

  COUNT(E)
----------
         1

ops$tkyte@ORA817DEV.US.ORACLE.COM> @mystat continued
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  --and b.value > 0
  6  /

NAME                                VALUE
------------------------------ ----------
table fetch continued row               5

Just count the LAST column in that table -- That'll force us to construct the
entire row.  Now, we can see that the count did 1 TFCR but we have:

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select chain_cnt from user_tables
  2  where table_name = 'T';

 CHAIN_CNT
----------
         3

three rows that are chained.  Apparently, 2 of them are MIGRATED (rows 1 and
2) and one is truly chained (row 3)

Review &
      Followup

      Rating:
      5
  June 27, 2002
Reviewer:  Dave
       from Ohio
Now that was a real education. Everything you need to know about chaining and
migration in one response.

      Rating: 5
Excellent
      explanation, useful information  June 27, 2002

Reviewer:  Alan  from Washington DC

      Rating:
      5
wow  June 27, 2002
Reviewer:
      A reader
I agree.  Talk about thorough.  And fun to read too.  Nice answer.

Thanks Tom.

      Rating: 5
Fabulous
      explanation - especially liked change in FT scan order  June
      27, 2002
Reviewer:  Bill Bunke  from Madison, WI

Tom,

Simple (yet powerful) examples that are easy to follow as the scenario unfolds.

The real-world example of how the query order of rows can change is especially
helpful; I always knew this was true, but had nothing to back it up with. Now I
can refer to your data when the topic arises.

      Rating:
      5
Excellent!  June 27, 2002
Reviewer:
      Prince
Wow! Perfect explanation.

It makes lot of sense now. I was puzzled by the output.

I am very sure that it is caused by the migrated rows. (the avg rowlen is 35 and
the table has only a few columns). I guess it was caused by adding another
column with default value after the table is in use (populated) for a while.
Also, this table undergoes lots of update. (May be I have to increase the
PCTFREE further)

Also I made sure to take the first and last column of the table while I was
doing the test.

As always you covered the subject so throughly and answered lot of other
(related) questions as well.

Just a ques: I am thinking export/import (lot of rows) is the best way to reorg
this table, if I have to. Do you see any other better solution? (I have 8.1.7.4
SE)

Thanks,

          Followup:

Alter table T move;

then rebuild the indexes.

Much better then export/import.

      Rating: 5
How did I
      forget that?  June 27, 2002
Reviewer:
      Prince.
sigh! Why didn't I think of this one? (Infact, I did this once for another
purpose). May be my brain has been overwritten by some of the docs I read on
metalink on row-chaining and wasn't thinking other alternatives.

Thank you very very much for the suggestion.

      Rating: 5
Finding table
      chained rows  July 17, 2002
Reviewer:  PN

Hi Tom
Please look at the query and its output:-

1.
SELECT 'Chained or Migrated Rows = '||value
FROM    v$sysstat
WHERE   name = 'table fetch continued row';

output=Chained or Migrated Rows = 652572

2.
select chain_cnt
from   user_tables
where  CHAIN_CNT >0;
output = 0 records.
I don't know weather I am right or not in finding the table name for chained
rows and/or migrated rows.
Please help how to find for corrective action.
Rgds
PN
/

          Followup:

You are comparing apples with toaster ovens here.

V$sysstat tells you how many times, since the system (database) was started --
you did a table fetch cont row.  Over ALL TABLES

user_tables tells you IMMEDIATELY AFTER AN ANALYZE (will be null otherwise) how
many rows in a table are chained.

You could have 1 table with 1 chained row that was fetched 652,572 times.
You could have 652,572 tables -- each with a chained row -- each of which was
fetched once.
You could have any combination of the above -- any combo.

Also, 652k -- maybe that's good, maybe that's bad.  it is a function of
a) how long has the db been up
b) how many rows is this as a percentage of total fetched rows (eg: if 0.001% of
your fetched are table fetch cont row -- WHO CARES)

      Rating:
      5
  July 17, 2002
Reviewer:
      A reader
Excellent Explanation!!!!

I have this data from v$sysstat table.
We are using SIEBEL crm application.Major
tables(Account,contact,Opportunity,Activity,Service Request ETC) in application
has more then 200 column.

We have 8k block size.

STATISTIC# NAME                                CLASS      VALUE
---------- ------------------------------ ---------- ----------
       150 table scans (short tables)             64    1471099
       151 table scans (long tables)              64      11391
       152 table scans (rowid ranges)             64          0
       153 table scans (cache partitions)         64          0
       154 table scans (direct read)              64          0
       155 table scan rows gotten                 64 1980298337
       156 table scan blocks gotten               64   58991204
       157 table fetch by rowid                   64 1960637410
       158 table fetch continued row              64   11510010 <-----

What is your Opinion on this(Row chaining and migration and performance)?

Since major table have more then 200 column, Shuold we increase block size to
16K or more?

Thanks for very good above explanation.

          Followup:

One way to look at that is 0.58% of your fetches result in a table fetch
continued row....

OLTP should not goto 16k blocks.

Look at the avg row size for that table - is it in fact that table?

steps for you:

a) determine that this is really an issue you must solve (0.5% -- hmmm)
b) isolate the table(s) first, then
c) figure out the average row size and see if you can fix
d) if you can and you still want to -- just fix those rows that are chained

      Rating: 5
Capital
      Tom!!!!!  July 17, 2002
Reviewer:  Ashok
       from India

      Rating:
      5
Great  July 17, 2002
Reviewer:
      A reader
Got It.
Thanks
PN

      Rating: 5
over 96% rows
      chained  August 31, 2002
Reviewer:  Mike
       from Universal city, TX
Tom:
I found one of table in my production database (datamart) has over 96% chained,
the block size is 4k:

select  CHAIN_CNT,chain_cnt/num_rows pct_chained,AVG_ROW_LEN, PCT_FREE ,
PCT_used, table_name
  from dba_tables where CHAIN_CNT/num_rows >0.6 and chain_cnt>0;SQL>   2

                       AVG
  CHAIN                ROW
    CNT PCT_CHAINED    LEN   PCT_FREE   PCT_USED TABLE NAME
------- ----------- ------ ---------- ---------- ------------------------------
 39,222  .964918323    529         10         90 SF75_VIEW

The table definition, which does not look like a fat table. Why this table so
chained easy?

SQL> desc sf75_view
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 BAA_SSAN                                  NOT NULL VARCHAR2(9)
 QHB_CURR_APPRAISAL_DATE                            DATE
 QHC_CURR_APPRAISAL_RATING                          VARCHAR2(1)
 QBJ_CURR_APPRAISAL_RATING                          VARCHAR2(1)
 QBJ_CT263_DESC                                     VARCHAR2(90)
 QBK_APPR_PATTERN_CURR                              VARCHAR2(1)
 QBK_CT270_DESC                                     VARCHAR2(80)
 QHB_PRIOR_APPRAISAL_DATE1                          DATE
 QHC_PRIOR_APPRAISAL_RATING1                        VARCHAR2(1)
 QHC_CT269_DESC1                                    VARCHAR2(80)
 QHJ_PRIOR_APPR_TYP_RATING1                         VARCHAR2(1)
 QHJ_CT263_DESC1                                    VARCHAR2(90)
 QHK_APPR_PATTERN_HIST1                             VARCHAR2(1)
 QHK_CT270_DESC1                                    VARCHAR2(80)
 QHB_PRIOR_APPRAISAL_DATE2                          DATE
 QHC_PRIOR_APPRAISAL_RATING2                        VARCHAR2(1)
 QHC_CT269_DESC2                                    VARCHAR2(80)
 QHJ_PRIOR_APPR_TYP_RATING2                         VARCHAR2(1)
 QHJ_CT263_DESC2                                    VARCHAR2(90)
 QHK_APPR_PATTERN_HIST2                             VARCHAR2(1)
 QHK_CT270_DESC2                                    VARCHAR2(80)
 QHB_PRIOR_APPRAISAL_DATE3                          DATE
 QHC_PRIOR_APPRAISAL_RATING3                        VARCHAR2(1)
 QHC_CT269_DESC3                                    VARCHAR2(80)
 QHJ_PRIOR_APPR_TYP_RATING3                         VARCHAR2(1)
 QHJ_CT263_DESC3                                    VARCHAR2(90)
 QHK_APPR_PATTERN_HIST3                             VARCHAR2(1)
 QHK_CT270_DESC3                                    VARCHAR2(80)
 QHB_PRIOR_APPRAISAL_DATE4                          DATE
 QHC_PRIOR_APPRAISAL_RATING4                        VARCHAR2(1)
 QHC_CT269_DESC4                                    VARCHAR2(80)
 QHJ_PRIOR_APPR_TYP_RATING4                         VARCHAR2(1)
 QHJ_CT263_DESC4                                    VARCHAR2(90)
 QHK_APPR_PATTERN_HIST4                             VARCHAR2(1)
 QHK_CT270_DESC4                                    VARCHAR2(80)

SQL>

          Followup:

I'll guess -- hypothesize....

The life of a row in this table starts with the row being skinny.  Makes sense,
based on the column names -- the rows look like they store history so when first
inserted -- most all of the trailing columns (the history) are NULL.

So, you insert a bunch of skinny rows....  You get hundreds / block.  Now, you
start modifying these rows over time -- adding the history as the present
becomes the past.  The row gets fatter (hey, sort of like us, the older we
are...).  Now, where 100 rows used to fit -- only 6 or 7 do (avg rowsize = 500
bytes over time, 4k blocks 3500 bytes and its full).  the other 95 or so rows
MIGRATE to other blocks to live.  No more room for them on the block.

So, now that you know that about 6/7 rows fit per block on average here -- what
should you do?  Well, you should set the PCTFREE much much higher so that only
6/7 rows can get inserted per block.  Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int, y varchar2(4000) );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t
  2  select object_id, null
  3    from all_objects
  4   where rownum <= 662*3;

1986 rows created.

I picked 662*3 cause I knew I would get 662 rows / block on my system and I
wanted just 3 blocks of data to start... We'll end up with 7 rows/block soon

ops$tkyte@ORA817DEV.US.ORACLE.COM> select dbms_rowid.rowid_block_number(rowid),
count(*)
  2    from t
  3   group by dbms_rowid.rowid_block_number(rowid)
  4  /

DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)   COUNT(*)
------------------------------------ ----------
                                  74        662
                                  75        662
                                  76        662

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;
Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select CHAIN_CNT,
  2         round(chain_cnt/num_rows*100,2) pct_chained,
  3         AVG_ROW_LEN, PCT_FREE , PCT_used
  4    from user_tables
  5   where table_name = 'T'
  6  /

 CHAIN_CNT PCT_CHAINED AVG_ROW_LEN   PCT_FREE   PCT_USED
---------- ----------- ----------- ---------- ----------
         0           0           8         10         40

No chaining of course.  Our rows started small, so now we update them

ops$tkyte@ORA817DEV.US.ORACLE.COM> update t set y = rpad( '*', 1000, '*' );
1986 rows updated.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;
Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select CHAIN_CNT,
  2         round(chain_cnt/num_rows*100,2) pct_chained,
  3         AVG_ROW_LEN, PCT_FREE , PCT_used
  4    from user_tables
  5   where table_name = 'T'
  6  /

 CHAIN_CNT PCT_CHAINED AVG_ROW_LEN   PCT_FREE   PCT_USED
---------- ----------- ----------- ---------- ----------
      1986         100        1017         10         40

There we go, we have the worst case, all of them moved -- all of them!

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t move;
Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select dbms_rowid.rowid_block_number(rowid),
count(*)
  2    from t
  3   group by dbms_rowid.rowid_block_number(rowid)
  4  /

DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)   COUNT(*)
------------------------------------ ----------
                               53834          7
                               53835          7
                               53836          7
                               53837          7
...
                               54115          7
                               54116          7
                               54117          5

284 rows selected.

after unchaining the table -- we see -- 7 rows/block basically.  And none of
them are chained (they all fit on the block itself

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;
Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select CHAIN_CNT,
  2         round(chain_cnt/num_rows*100,2) pct_chained,
  3         AVG_ROW_LEN, PCT_FREE , PCT_used
  4    from user_tables
  5   where table_name = 'T'
  6  /

 CHAIN_CNT PCT_CHAINED AVG_ROW_LEN   PCT_FREE   PCT_USED
---------- ----------- ----------- ---------- ----------
         0           0        1011         10         40

So, armed with that knowledge, what can you do?  Well, if you KNOW you'll
insert lots of rows with little data and be updating it over time, you can
reserve most of the blocks for these updates.  I played around a bit to get the
right pctfree

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop table t;
Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t
  2  ( x int, y varchar2(4000) )
  3  pctfree 99 pctused 1;
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t
  2  select object_id, null
  3    from all_objects
  4   where rownum <= 35;

35 rows created.

and put in 5 blocks of data..

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select dbms_rowid.rowid_block_number(rowid),
count(*)
  2    from t
  3   group by dbms_rowid.rowid_block_number(rowid)
  4  /

DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)   COUNT(*)
------------------------------------ ----------
                                  74          7
                                  75          7
                                  76          7
                                  77          7
                                  78          7

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;
Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select CHAIN_CNT,
  2         round(chain_cnt/num_rows*100,2) pct_chained,
  3         AVG_ROW_LEN, PCT_FREE , PCT_used
  4    from user_tables
  5   where table_name = 'T'
  6  /

 CHAIN_CNT PCT_CHAINED AVG_ROW_LEN   PCT_FREE   PCT_USED
---------- ----------- ----------- ---------- ----------
         0           0           8         99          1

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> update t set y = rpad( '*', 1000, '*' );

35 rows updated.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select CHAIN_CNT,
  2         round(chain_cnt/num_rows*100,2) pct_chained,
  3         AVG_ROW_LEN, PCT_FREE , PCT_used
  4    from user_tables
  5   where table_name = 'T'
  6  /

 CHAIN_CNT PCT_CHAINED AVG_ROW_LEN   PCT_FREE   PCT_USED
---------- ----------- ----------- ---------- ----------
         0           0        1011         99          1

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

and now you can see there is no migration -- the rows can expand.  I've set
the space aside in the beginning

So, what should you do?  I would recommend an alter table MOVE to unmigrate
these rows (and then alter index rebuilds as well -- since the indexes will be
invalid after this operation).  AFTER the alter table move, you would do an
ALTER TABLE to up the pctfree setting for all subsequent inserts (you don't
necessarily want to play with the pctfree on the move -- if the existing rows
are as "fat" as they are going to be -- on the other hand, if the existing rows
are only about "half as fat" as they are going to get -- you could use a higher
pctfree on the alter table move, like 40% or 50% for their updates)

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t pctfree 75
  2  ;

Table altered.

      Rating: 5
Avoiding Row
      Migration  September 01, 2002
Reviewer:
      Basharat  from Dubai
Tom, whats the best technique to avoid row migration; breaking the big columns
into smaller ones or re-creating the data base with larger block size, albeit
none of the above guarantee zero row migration.

Isn't it possible for Oracle server to update the rowid of migrated row instead
of keeping a pointer for new location to avoid additional logical/physical IO?

          Followup:

You would have to goto each index and update it as well.  Meaning, we would have
to take the before image of the row and do an index range scan on every index to
find the single leaf entry that points back to us and move it in the index (the
rowid is changing and the rowid is part of the entry in the leaf node -- so we
most likely need to move the index entry as well now)

Applications that use rowids like forms, bc4j and dozens of others would be
totally broken as well.  They count on the rowid being stable for the life of
the row.

The best way to avoid it is to have some understanding of the life of a typical
row in your table and set pctfree accordingly.  (or realizing that it is
generally only the truly exaggerated cases like this one that cause concern --
just worry about it on tables that need worrying)

      Rating: 5
Another cool
      trick...  September 02, 2002
Reviewer:
      Connor  from UK
... is to use minimise_records_per_block when (re)loading a table that has
disparate row sizes throughout.

(Courtesy: JL's book)

Cheers
Connor

          Followup:

Yeah, I thought about that BUT, it is a little trickier.

What you would have to do is:

rebuild the table
then minimize records/block.

But if you were INSTALLING your app, you would have to

create table
insert N rows where N = number of rows/block you want
alter minimize records/block
delete the rows

So, for a rebuild -- it works good.  For a create, PCTFREE is perhaps "better"

      Rating: 5
Very useful!
        September 25, 2002
Reviewer:  Cynthia
      Pinchot  from Denver, USA

      Rating: 5
Hats off to you
      Tom.  October 16, 2002
Reviewer:
      A reader  from Canada

      Rating: 5
Chained Rows
      and Autotrace  October 27, 2002
Reviewer:
      Venkat  from Hyd
Other than Tom, who can explain like this???

      Rating:
      5
  February 28, 2003
Reviewer:
      A reader
Thanks for your great explanation on row migrating/chaining.

I have a similar question. I have a table “A” with 70% row chaining. The table
size is 400 Mb. When I created a copy of that table, i.e.,

CREATE TABLE B AS SELECT * FROM A;

Then the table B size is 35 Mb.

I am sure that table got populated with tons of inserts and update, there were 0
deletes on that table.

Is row chaining/migrating going to increasing the table size very badly?

Here are the Table storage parameters::

TABLESPACE myTablespace NOLOGGING
   PCTFREE 10
   PCTUSED 40
   INITRANS 1
   MAXTRANS 255
  STORAGE (
   INITIAL 65536
   NEXT 65536
   PCTINCREASE 0
   MINEXTENTS 1
   MAXEXTENTS 10000
   FREELISTS 1 FREELIST GROUPS 1 )
   NOCACHE;

          Followup:

do you have the original storage parameters for the TABLE in question -- not the
tablespace but the table.

The tablespace provides defaults which can and many times are overridden by the
create table itself.

      Rating:
      5
Redaer  March 01, 2003
Reviewer:
      A reader
"
ops$tkyte@ORA817DEV.US.ORACLE.COM> select CHAIN_CNT,
  2         round(chain_cnt/num_rows*100,2) pct_chained,
  3         AVG_ROW_LEN, PCT_FREE , PCT_used
  4    from user_tables
  5   where table_name = 'T'
  6  /

 CHAIN_CNT PCT_CHAINED AVG_ROW_LEN   PCT_FREE   PCT_USED
---------- ----------- ----------- ---------- ----------
         0           0           8         10         40
"

The average row length = 8
maximum size of each column is actually 3 +null
DUMP(X)
-----------------------
DUMP(Y)
-----------------------
Typ=2 Len=3: 194,20,87
NULL

What are the overhead included in the avg_row_len value

          Followup:

max size is not 3 -- it is much larger (numbers are varying width strings)

ops$tkyte@ORA920> create table t ( x int );

Table created.

ops$tkyte@ORA920> insert into t select object_id from all_objects;

30540 rows created.

ops$tkyte@ORA920> insert into t values ( rpad('9',38,'9') );

1 row created.

ops$tkyte@ORA920> insert into t values ( '-'||rpad('9',38,'9') );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select max(to_number(rtrim(substr( dump(x),
instr(dump(x),'Len=')+4, 2 ),':'))) d,
  2         max(vsize(x))
  3    from t;

         D MAX(VSIZE(X))
---------- -------------
        21            21

but anyway -- null/not null flags, length bytes, some row overhead.

      Rating:
      5
  March 01, 2003
Reviewer:
      A reader
>>do you have the original storage parameters for the TABLE in question -- not
the
tablespace but the table.

>>The tablespace provides defaults which can and many times are overridden by
the
create table itself.

They are table storage parameters only. Table doesn't have any storage
parameter, it inherited the tablespace default parameters.

Thanks,

          Followup:

which could have been different, altered.

No, I do not see how simple row chaining could account for a 400meg down to
35meg occurrence.

Something else is as work here -- storage parameters were different or someone
allocated many more extents manually or the data actually was deleted or the
data was allowed to grow really big and then shrunk back (i'll demo that one
below) -- but without an example, or all of the details, we cannot really say
what.

Consider:

ops$tkyte@ORA920> create table t
  2  ( a varchar2(4000) default rpad('*',4000,'*'),
  3    b varchar2(4000) default rpad('*',4000,'*'),
  4    c varchar2(4000) default rpad('*',4000,'*'),
  5    d varchar2(4000) default rpad('*',4000,'*'),
  6    e varchar2(4000) default rpad('*',4000,'*'),
  7    f varchar2(4000) default rpad('*',4000,'*'),
  8    g varchar2(4000) default rpad('*',4000,'*'),
  9    h varchar2(4000) default rpad('*',4000,'*'),
 10    i varchar2(4000) default rpad('*',4000,'*'),
 11    j varchar2(4000) default rpad('*',4000,'*')
 12  );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t (a)
  2  select rpad('*',4000,'*')
  3    from all_objects
  4   where rownum <= 1000;

1000 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec show_space( 'T' );
Free Blocks.............................
Total Blocks............................6144
Total Bytes.............................50331648
Total MBytes............................48
Unused Blocks...........................0
Unused Bytes............................0
Last Used Ext FileId....................9
Last Used Ext BlockId...................118664
Last Used Block.........................128

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> update t set b='x', c='y', d='z', e=null,
f='a',g=1,h=2,i=3,j=4;

1000 rows updated.

ops$tkyte@ORA920> drop table t2;

Table dropped.

ops$tkyte@ORA920> create table t2 as select * from t;

Table created.

ops$tkyte@ORA920> exec show_space( 'T2' )
Free Blocks.............................
Total Blocks............................1024
Total Bytes.............................8388608
Total MBytes............................8
Unused Blocks...........................0
Unused Bytes............................0
Last Used Ext FileId....................9
Last Used Ext BlockId...................102024
Last Used Block.........................128

PL/SQL procedure successfully completed.

So, T was 45meg -- T2 is 8meg.... same data - why?  because T got really fat,
then really skinny but a table never decreases until re-orged so....  T2 is
smaller than T

      Rating: 5
Great
      explanation!!!! (as usual)  July 07, 2003

Reviewer:  A reader

March 02, 2003 -- Thanks for the question regarding "Advanced
      replication tuning", version 8.1.7

      You Asked
Hi Tom,

   Could you provide tips on how to speed up the process of the advanced
replication?

   I want to insert over 1 million rows to a replicated table. The time
difference between replication on and off is over 10 hours. Is there any way to
speed up the replication process?

Thanks,
David

      and we said...
you can turn off replication -- add the 1million rows to both tables using the
direct path loader for example -- and then enable replication.

For large bulk operations -- that would be a much more efficient approach then
using replication to trickle the data row by row.  You should be able to do that
in under a minute or two -- as opposed to ten hours.

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76959/repadv.htm#7562

Review &
      Followup

      Rating:
      4
  March 02, 2003
Reviewer:  David

Hi Tom,

   Is there any general guideline on creating repadmin replication table (such
as size of extent, pctused, pctfree, or even create repadmin table on LMT)?

Thanks,
David

          Followup:

not that I'm aware of -- I'm sure there is probably a metalink note out there
somewhere though ;)

March 02, 2003 --
      Thanks for the question regarding "Insert with append hint vs SQLLoader",
      version 8.1.7

      You Asked
Tom,
I would appreciate if you could give a few pointers on when to use Direct load
insert(with the append hint) vs SQLLoader

insert into /*+ append */ t select * from a;
vs.
SQLLoader

Thank you

      and we said...
If the data already exists in a table A -- using sqlldr would be "not
appropriate" -- you would have to dump A to a flat file.

In 9iR1 and up with external tables (the ability to query a flat file using
SQL), the need to use sqlldr is lessened even more.  I find the flexibility of
SQL over SQLLDR control files to be so useful that I would opt for an external
table over sqlldr in most all cases.

Use SQLLDR when SQL cannot be used to accomplish the job.

In 9iR1 -- the only time I can think that would be true would be a parallel
load of varying width files.  If the files were fixed width, we can parallel
load using SQL.
Review & Followup

      Rating:
      5
  March 02, 2003
Reviewer:
      A reader

March 03, 2003 Jayaraman -- Thanks for the question regarding
      "Row chaining and migration", version 8.1.6.0.0

      You Asked
Hi Tom,

Kindly correct me if i am wrong in my understanding and please explain.

Row chaining occurs when the row is too large to fit into any one block and
hence it is split and stored in different blocks. This is done only after oracle
tries to fit the row into any one block in the particular segment and failing
which the row is split and stored. Each piece of the row will have a head and a
tail like a linked list that has the address of the block of its preceding piece
and succeeding piece.

Now row migration is different in the aspect that it is possible to fit the row
into any one block in the segment that has enough space to accomodate the row.
This will have only a pointer in the original block of the row that points to
its new block address.

Both the above conditions will occur only when the requisite pctfree/pctused
block storage parameters are satisfied. Correct?

Will the analyze command for listing chained rows include the migrated rows as
well? Going through the earlier questions in asktom has not cleared my doubt.

Thanks in advance,

Regards,
Ashok

      and we said...
A migrated row is just a special case of a chained row (a migrated row is a
chained row, a chained row may or may not be a migrated row)

So, when you analyze the table to list chained rows, it includes migrated rows
as well.

The explaination you give is correct in its content.
Review &
      Followup

      Rating:
      3
Thanks  October 14, 2001
Reviewer:
      A reader
Thanks

      Rating: 4
Why do migrated
      rows need a pointer.  March 03, 2003
Reviewer:
      Amar  from INDIA
Hi,
What i wanted to ask was why does ORACLE need a pointer in the original block to
point to the new block? Why not just directly go to the new block the way it
goes to the older block??
Why doesn't Oracle simply update the table directory and row directory to point
to the new block when migration occurs??
One reason i could think of was to preserve the rowid... any other reasons ??

          Followup:

to preserve the rowid.  to do otherwise would require us to update each and
every index during a migration, and if you are using rowid based snapshots....

      Rating:
      5
Thanks  March 03, 2003
Reviewer:
      Amar  from INDIA
And also i would need to rebuild the indexes again if that happens :)

      Rating:
      5
Oops!  March 03, 2003
Reviewer:
      Amar  from INDIA
oops missed that, you had already said regarding the indexes

Any way,Thanks a lot.

March 03, 2003 Tony -- Thanks for the question regarding "Doubt
      about bind variables usage", version 8.1.6

      You Asked
Hi Tom,
I have got a doubt about bind variables. In a environment where different users
have their accounts for an application (in most of the real world applications),
and the access to objects is through synonyms/roles. If different users issue
the same query, then how bind variables are useful?, as same query can not be
used anyway because of access rights.
Also while reading Chapter 1 of your book Expert one-on-one, I found that you
used an example of selecting from dual (page 42), when it can be done by just
assigning the value. Will it not be slow?, as mentioned in your other answers.

Regards,

      and we said...
In most real world applications every user does not have their own unique object
-- that is, 100's of people access "TABLE_A", you don't have hundreds of
TABLE_A's out there.

So, as long as the tables are the same -- the plans are shared, even if
different people access it (DUAL for example).

Here is a concrete example.  I own a table EMP, SCOTT owns a table EMP, there is
a view ALL_OBJECTS that we both have access to.  So I run a script:

ops$tkyte@ORA920> @connect /

ops$tkyte@ORA920> alter system flush shared_pool;
System altered.

ops$tkyte@ORA920> @connect scott/tiger

scott@ORA920> variable n number
scott@ORA920> exec :n := 0;

PL/SQL procedure successfully completed.

scott@ORA920>
scott@ORA920> select * from all_objects LOOK_AT_ME where rownum = :n;

no rows selected

scott@ORA920> select * from emp LOOK_AT_ME where rownum = :n;

no rows selected

scott@ORA920>
scott@ORA920> @connect /

ops$tkyte@ORA920> variable n number
ops$tkyte@ORA920> exec :n := 0;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from all_objects LOOK_AT_ME where rownum = :n;

no rows selected

ops$tkyte@ORA920> select * from emp LOOK_AT_ME where rownum = :n;

no rows selected

ops$tkyte@ORA920>
ops$tkyte@ORA920> select child_number, sql_text
  2    from v$sql
  3   where sql_text like '%LOOK_AT_ME%';

CHILD_NUMBER
------------
SQL_TEXT
---------------------------------------------------------------------------------
--------------------------------------------------
           0
select * from all_objects LOOK_AT_ME where rownum = :n

           0
select child_number, sql_text   from v$sql  where sql_text like '%LOOK_AT_ME%'

           0
select * from emp LOOK_AT_ME where rownum = :n

           1
select * from emp LOOK_AT_ME where rownum = :n

See, two entries for EMP (has to be in this case).  There might be 500 people
that use the first version and 500 that use the second -- but there is only two.
 Also note that there is only one against all_objects -- we all share that one.

Bind variables -- just use em...
Yes, they are relevant -- even more so in the real work with real applications
then I can possible put into writing in a book.  They are understated if
anything in my book.

As for page 42 -- that shows us selecting a sequence.nextval from dual.  You
cannot assign sequences, you must select them.
Review &
      Followup

      Rating: 4
Thanks for
      clearing the doubts!  December 29, 2002
Reviewer:
      A reader

      Rating: 3
Would like to
      know about v$sql table  March 03, 2003
Reviewer:
      Abhijit  from Mumbai
Hi Tom,
Is it possible to clear the v$sql table /view?

how do you do it?

Regards,
Abhijit

          Followup:

alter system flush shared_pool;

will get rid of much of it -- but never all.

March 03, 2003 biti_rainy -- Thanks for the question regarding
      "can i use sql_trace to know "update DICT"", version 817

      You Asked
someone  ask me: his  table is very large,by  some  reason , he  want to  change
the table's  owner from a to b .

maybe  CTAS, maybe  EXP/IMP , etc.

i do  a  test
alter session set  sql_trace = true ;
then i looked up the delete/update/insert sql ,and  found that many table are
changed, for  example:
obj$
tsq$
seg$
objauth$
.........

if  i can change the tables by  myself, is  that  safe  ?

(by  my  simple  test ,it  can  work !)

      and we said...
If you dare update the data dictionary owned by SYS using SQL -- you have just
made the biggest mistake of your life.

You never update the data dictionary - never.

To do so will terminate all supportability for that database.

To do so will invite spurious ora-00600 errors (internal errors) today and in
the future.

Don't even think about doing it.
March 03, 2003 Thomas -- Thanks for the question regarding
      "UPDATE (Select...)", version 8.1.7

      You Asked
Hi Tom,

I am looking for someone who can give me a short checklist considering updating
a view or inline-view. Wich are the main rules to think about first, except for
reading the documentation, before I start??

Thanks for your support.

Best regards

Thomas

      and we said...
there is no difference between updating a view:

update v set ....

and updating an inline view

update ( select ..... ) set

where the inline view = the view really.  they are the same functionally.  There
are subtle security nuances:

ops$tkyte@ORA817DEV> create user a identified by a;
User created.

ops$tkyte@ORA817DEV> grant create session to a;
Grant succeeded.

ops$tkyte@ORA817DEV> create table p ( x int primary key );
Table created.

ops$tkyte@ORA817DEV> create table c ( x references p, y int );
Table created.

ops$tkyte@ORA817DEV> create or replace view v
  2  as
  3  select p.x, c.y from p, c where p.x = c.x;
View created.

ops$tkyte@ORA817DEV> grant select on p to a;
Grant succeeded.

ops$tkyte@ORA817DEV> grant select,update on c to a;
Grant succeeded.

ops$tkyte@ORA817DEV> grant select, update on v to a;
Grant succeeded.

ops$tkyte@ORA817DEV> @connect a/a

a@ORA817DEV> update ops$tkyte.v set y = 5;
0 rows updated.

a@ORA817DEV> update (select p.x, c.y from ops$tkyte.p, ops$tkyte.c where p.x =
c.x ) set y = 5;
update (select p.x, c.y from ops$tkyte.p, ops$tkyte.c where p.x = c.x ) set y =
5
                                       *
ERROR at line 1:
ORA-01031: insufficient privileges

see, even though P is not updated -- it is in the update portion of the view
so update access needs to exist

But, thats about it
March 03,
      2003 Max -- Thanks for the question
      regarding "Free ids", version 8.1.7

      You Asked
Hi, Tom!

I have EMP table with emp_no, name fields. Value for emp_no isn`t computed using
seq.nextval. It is given to an employee directly. Lets say, that min(emp_no) = 1
 and max(emp_no)=15000. The question is: how can i get a list of FREE emp_no,
which are between 1 and 15000.

I do undersand, that this can be done by checking inside a 'for' cycle from 1 to
15000 if such a value exists in emp table. But I think that is not the fastest
way.

Thanx a lot.

      and we said...
select empno+1
  from (
select empno, lead(empno) over ( order by empno ) next_empno
  from emp
       )
 where empno+1 <> next_empno
   and rownum = 1;

or

select empno
  from emp
 where not exists ( select null from emp e2 where empno = emp.empno+1 );

will give you all of the starting gaps.  Or

select r empno
  from ( select rownum r from all_objects where rownum <= 15000 ) a,
       emp
 where a.r = emp.empno(+)
   and emp.empno is null;

would generate the whole set, assuming that all_objects has sufficient rows of
course.
Review
      & Followup

      Rating: 5
I have also had
      to do something similar  March 03, 2003
Reviewer:
      Jeremy  from Chicago, IL
I also had to support an application where I've needed to find gaps of ids to
insert records into that couldn't just be appended to the end.  Here is a query
that I use to find the top five biggest gaps to insert recs into.

select * from (
select empno, one_before, Diff, dense_rank() over (order by Diff desc) rank from
(
     select empno, one_before,
                case when (empno - one_before) > 1 then (empno - one_before)
               else 1
               end Diff
     from (
           select empno, lag(empno) over(order by empno) one_before
          from emp order by empno))
)
where rank <= 5

Out put like:

     EMPNO ONE_BEFORE       DIFF       RANK
---------- ---------- ---------- ----------
       212        101        111          1
        99         55         44          2
        54         36         18          3
        30         19         11          4
        10          3          7          5
        19         12          7          5

      Rating: 4
Great
      Query  March 04, 2003
Reviewer:  Logan
      Palanisamy  from Santa Clara, CA USA
Jeremy,

Certainly a great query. How about eliminating "case when .." and making it even
simpler.

select * from (
select empno, one_before, Diff, dense_rank() over (order by Diff desc) rank from

(
     select empno, one_before,(empno - one_before) Diff
     from (
           select empno, lag(empno) over(order by empno) one_before
          from emp order by empno))
)
where rank <= 5

My two cents

      Rating: 5
Case
      When  March 04, 2003
Reviewer:  Jeremy
       from Chicago
The case when is for the first row (ID = 1).  The first row when compared to the
lag/previous row (which would evaluate to null) will always show up in your
results unless you have the "case when" in there.  So... thanks for your two
cents, but the "case when" does have a purpose which would be noticable if you
actually try the query out :).

      Rating: 4
very cool
      query  March 28, 2003
Reviewer:  noinim

I find myself thinking that if you are counting the number of empty slots,
gapsize is overstated by one.  If on the other hand you are counting say
dollars, the gapsize is correct as a difference in dollars.

That is a very cool query.

March 04, 2003 -- Thanks for the question regarding "Returning
      Rows from a plsql procedure", version 8.1.6.0.0

      You Asked
Hi ,

How can I return rows from a select statement within a plsql
procedure . What I mean is I want to pass certain values to the
procedure and based on the value the select statement within the
procedure returns some rows . Can these rows be retrieved by
executing the procedure ( eg. sql> execute a('1111') ; or select
a('1111') from dual ; ) from sqlplus ? I have been trying with
ref cusrors but it doesnot work out . Pls advise how to go about
it .

Rgds .

      and we said...
for ref cursors with examples see

http://osi.oracle.com/~tkyte/ResultSets/index.html
If what you are trying to do is have a stored procedure return a
set of data that IS the result of a query (without actually
running a query in the proceudre), you can use object types for
this.  For example:

ops$tkyte@DEV816> create or replace type myRecordType
  2  as object
  3  ( x int,
  4    y date,
  5    z varchar2(25)
  6  )
  7  /

Type created.

ops$tkyte@DEV816> create or replace type myTableType
  2  as table of myRecordType;
  3  /

Type created.

ops$tkyte@DEV816> create or replace function my_function return
myTableType
  2  as
  3      some_data myTableType := myTabletype();
  4  begin
  5      for i in 1 .. 10 loop
  6          some_data.extend;
  7          some_data(i) :=
  8              myRecordType( i, sysdate+i, 'record ' || i );
  9      end loop;
 10
 11      return some_data;
 12  end;
 13  /

Function created.

ops$tkyte@DEV816> select * from TABLE ( cast( my_function() as
mytableType ) )
  2  /

         X Y         Z
---------- --------- -------------------------
         1 02-DEC-00 record 1
         2 03-DEC-00 record 2
         3 04-DEC-00 record 3
         4 05-DEC-00 record 4
         5 06-DEC-00 record 5
         6 07-DEC-00 record 6
         7 08-DEC-00 record 7
         8 09-DEC-00 record 8
         9 10-DEC-00 record 9
        10 11-DEC-00 record 10

10 rows selected.

Review &
      Followup

      Rating: 3
returning rows
      from pl/sql procedure across a db link  February 04, 2002

Reviewer:  Lindsay Hess  from Brisbane, Queensland,
      Australia
Have successfully returned rows from a function locally.

Tried calling function across a dblink and got error PLS-00453 'remote
operations not permitted on object tables or user-defined type columns'.

Metalink Note 136352.1 provides a workaround which involves referencing columns
as individual components.

Are there any other (more elegant) workarounds of which you are aware?

      Rating: 4
Hope you will
      answer this related question. I really need your
      help.  March 01, 2003
Reviewer:  John
       from New Jersey, USA
In this example, if I just want to assign a value to one element of the record:

instead of

    some_data(i) := myRecordType( i, sysdate+i, 'record ' || i );

do

some_data(i).y := sysdate + i

When I execute the function, I got error:

ORA-06530: Reference to uninitialized composite

Here is why I want to do that:

I need to return a cursor result to an object that is a table of a user defined
record.

The structure of my object (just use emp as an example, my real code is more
complicated than this):

create or replace type emp as object (
    empno            number,
    ename            varchar2(20),
    total_sick_days        number,
    total_vacation_days    number,
    total_salary        number,
    total_commission        number)
/

create or replace type t_emp as table of emp
/

I am thinking about filling in the object with empno, ename first, and then
adding in other values through query other tables and do some calculations.  The
reason I chose object is because the result need to be returned to java program.
 I've spent days on this and really don't know if my approach is possible.

I would appreciate it very much if you can give me some advice.

          Followup:

that error:

ORA-06530: Reference to uninitialized composite

simply means "some_data(i) is atomically null, it has no value, there is no .y
yet"

You need to assign at least an empty record to it, either of:

ops$tkyte@ORA817DEV> declare
  2          some_data myTableType := myTableType();
  3  begin
  4          some_data.extend;
  5          some_data(1) := myRecordType(null,null,null);
  6          some_data(1).b := sysdate;
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> declare
  2          some_data myTableType := myTableType();
  3  begin
  4          some_data.extend;
  5          some_data(1) := myRecordType(null,sysdate,null);
  6  end;
  7  /

PL/SQL procedure successfully completed.

it what you are looking for.

      Rating: 4
Thanks for your
      prompt response.  March 01, 2003
Reviewer:
      John  from New Jersey, USA
Your response answered part of my question.
The other part of the question is I'm trying to solve the following problem, but
I don't even know if my plan is feasible or if there is a better way of solving
it.

The problem is I need to create something like the following and pass it as an
out parameter to Java.

(I'll use emp to illustrate this)

empno    ename    sick_days    total_salary    total_commission
1    David    5        $55000        $3000
2    Mike    3        $60000
3    Mark    2        $40000        $4000
4    Smith    0        $70000        $1000

The above data can't be obtained by one single query (at least for my real
problem).  Otherwise, I would just return a ref cursor.
A query can get empno and ename, but other data have to be obtained and
calculated one by one.  My plan was to use Oracle object of nested table type.
Fill in empno and ename first, then add other value one by one.

Your previous answer helped me on filling in empno, ename part.  But how can I
fill in other data?
Again, any suggestion/advice will be greatly appreciated!

          Followup:

Well, I actually find it almost unbelievable that you cannot do that in a query
-but..  here you go:

ops$tkyte@DEV816> create or replace function my_function return myTableType
  2  as
  3      some_data myTableType := myTabletype();
  4  begin
  5      for x in ( select empno, ename from .... )
  6          some_data.extend;
  7          some_data(some_data.count) :=
  8              myRecordType( x.empno, x.ename, null, null, null, null );
             some_data(some_data.count).sick_days := function();
             some_data(some_data.count).total_salary := function();
             ....
  9      end loop;
 10
 11      return some_data;
 12  end;
 13  /

and there you go.

      Rating: 5
Thanks
      Tom!  March 02, 2003
Reviewer:  John
       from New Jersey, USA
The answer was very inspiring and helpful!  I used emp to illustrate my problem.
 The real problem is to deal with some product inventory report between 2 dates
(BTW, this is one of the most complicated output of out project!).  I would love
it if it can be done with one query.  I'll list the real problem below, which
may be a little too detail and too specific for a forum like this.  But I would
love to hear from you for suggestions, even a brief one.

My second question is:  I read that you can do SELECT, UPDATE, and DELETE
operation for nested table, but I have never seen a real life example.  I wonder
if I could make use of them here?

Here is the raw data:

db1:dev> select a.product_id, c.tx_type_name, sum(a.final_quantity)
  2  from transaction_item a, transaction b, transaction_type c
  3  where a.tx_id = b.tx_id
  4  and b.tx_type_id = c.tx_type_id
  5  group by a.product_id, c.tx_type_name;

PRODUCT_ID TX_TYPE_NAME                   SUM(A.FINAL_QUANTITY)
---------- ------------------------------ ---------------------
         1 Return                                           102
         1 Shipment                                        1000
         1 Transfer In                                       96
         1 Distribution                                     115
         1 Transfer Out                                      50
         2 Return                                           103
         2 Shipment                                        1005
         2 Transfer In                                       80
         2 Distribution                                     135
         2 Transfer Out                                      60
         3 Return                                           104

PRODUCT_ID TX_TYPE_NAME                   SUM(A.FINAL_QUANTITY)
---------- ------------------------------ ---------------------
         3 Shipment                                         900
         3 Transfer In                                      306
         3 Distribution                                     246
         3 Transfer Out                                      70

15 rows selected.

select a.product_id, a.final_quantity as begin_inv_qty
from inventory_item a, inventory b
where a.inventory_id =  b.inventory_id
and b.inv_name_id = 1
order by product_id;

PRODUCT_ID BEGIN_INV_QTY
---------- -------------
         1           354
         2           503
         4           150
         5            12

select a.product_id, a.final_quantity as end_inv_qty
from inventory_item a, inventory b
where a.inventory_id =  b.inventory_id
and b.inv_name_id = 2
order by product_id;

PRODUCT_ID    END_QTY
---------- ----------
         1        612
         2        445
         3        228

The desired output should be:

Product_id    begin_inv_qty    Return    Shipment    ...    end_inv_qty
1             354              102       1000        ...    612
2             445              103       1005        ...    445
3             0                104       900         ...    228
4             150              0         0           ...    0
5             12               0         0           ...    0
Grand total   961              309       2905        ...    1957

          Followup:

simple transpositions:

select inventory.product_id,
       inventory.begin_inv_qty,
       transaction.return,
       transaction.shipment,
       ...
       transaction.transfer_out,
       inventory.end_inv_qty
  from ( select a.product_id,
                sum( decode( c.tx_type_name,
                      'Return', a.final_quantity, 0 ) ) Return,
                sum( decode( c.tx_type_name,
                      'Shipment', a.final_quantity, 0 ) ) Shipment,
                ...
                sum( decode( c.tx_type_name,
                      'Transfer Out', a.final_quantity, 0 ) ) Transfer_Out
           from transaction_item a, transaction b, transaction_type c
          where a.tx_id = b.tx_id
            and b.tx_type_id = c.tx_type_id
          group by a.product_id
       ) TRANSACTION,
       ( select a.product_id,
                sum( decode( b.inv_name_id,
                      1, a.final_quantity 0 ) ) as begin_inv_qty,
                sum( decode( b.inv_name_id,
                      2, a.final_quantity 0 ) ) as end_inv_qty
           from inventory_item a, inventory b
          where a.inventory_id =  b.inventory_id
            and b.inv_name_id in ( 1, 2 )
          group by a.product_id
       ) INVENTORY
 where inventory.product_id = transaction.product_id(+)
/

should do it.  Run each of the inline views in sqlplus by themselves.  You'll
find that:

       ( select a.product_id,
                sum( decode( b.inv_name_id,
                      1, a.final_quantity 0 ) ) as begin_inv_qty,
                sum( decode( b.inv_name_id,
                      2, a.final_quantity 0 ) ) as end_inv_qty
           from inventory_item a, inventory b
          where a.inventory_id =  b.inventory_id
            and b.inv_name_id in ( 1, 2 )
          group by a.product_id
       ) INVENTORY

for example will return

PRODUCT_ID BEGIN_INV_QTY  END_INV_QTY
---------- -------------  -----------
         1           354          612
         2           503          445
         3             0          228
         4           150            0
         5            12            0

And:

  from ( select a.product_id,
                sum( decode( c.tx_type_name,
                      'Return', a.final_quantity, 0 ) ) Return,
                sum( decode( c.tx_type_name,
                      'Shipment', a.final_quantity, 0 ) ) Shipment,
                ...
                sum( decode( c.tx_type_name,
                      'Transfer Out', a.final_quantity, 0 ) ) Transfer_Out
           from transaction_item a, transaction b, transaction_type c
          where a.tx_id = b.tx_id
            and b.tx_type_id = c.tx_type_id
          group by a.product_id
       ) TRANSACTION,

returns something like:

Product_id    Return    Shipment    ...
1             102       1000        ...    612
2             103       1005        ...    445
3             104       900         ...    228

Now, just join them (used an outer join so that INVENTORY drives the result set
regardless of transaction activity)

      Rating: 5
HTML format
      issue  March 02, 2003
Reviewer:  Sudhir
       from Lexington, KY USA
Tom,

your web site is very good. However, if the answer
sesction is embedded in

<table width=100% cellpadding="0" cellspacing="0" order="0">
<tr valign=top>
 <td valign="top"> forum discussion </td>
</tr>
</table>

would keep it within screen width. Due to my eye weakness I
have to increase font slightly and that ruins the format. It scrolls
horizontally so much, I lose track of what I am
reading.
Is it doable?

          Followup:

The answer section is in a PRE tag so as to use courier fixed width font.

I don't see what a table would do for that -- I cannot "not" use <PRE> tags as I
rely on hard returns 100%

Using a table would not change anything -- as the pre tag would cause it to
spill out wider anyway.

Sorry -- I'm just using a 75 character fixed width screen here.  If you save the
page and tried putting in the table tag -- you would see it has zero net effect.

One idea for you -- on all of my desktops, I work with 2 monitors (so, my screen
is really 3200x1200).  It might be a reasonable accomidation for your employer
to let you have 2 monitors to effectively widen your display area for all things
-- not just this web site.

I'll ask the guys who work on the site if they have any good ideas though for
this.  Maybe we can set up a preference that would remove the <PRE> tag and let
things wrap -- but I've a feeling that would be equally as hard to read.

      Rating: 4
How does SQL
      Plus print out the Query?  March 03, 2003

Reviewer:  Mike  from England
Brilliant, and I can think of several places where ref_cursors could be very
handy.  My Question though relates to how SQL Plus manages to print out the sql
results set.

If my reckoning is right, all ":C" is is a bind variable pointer the the SQL
query.  SQL Plus pulls the results back with a call to print.

My question is how does SQL Plus know what the result set looks like (i.e column
names and types)

I assume that there are some OCI calls going on that are similar in operation to
DBMS_SQL.DESCRIBE_COLUMNS??

Or is it just a miracle from SQL +??

          Followup:

A client can describe a result set -- java can do it, C can do it (OCI anyway)
and the ref cursor is just a result set.

Yes, there are describe calls in OCI but your lanaguage probably supports it as
well.

      Rating: 5
Tom, Thank you
      so much!!!   March 03, 2003
Reviewer:  John
       from New Jersey, USA
Your query runs beautifully!  I never dreamed this could be done with a single
query.  I guess there's so much to learn for me, and your website is a great
place for that.  I have to be really patient to wait for the opportunity to
submit my question.  Keep up with the great work.

BTW, do you have any example of how to use SELECT, UPDATE, DELETE for nested
table object type in PL/SQL?

          Followup:

You cannot use insert/update/delete on a PLSQL based collection.  You can select
it

select * from TABLE( cast( l_variable as collection_type ) );

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96594/toc.htm
has lots of info regarding object types in plsql and the database.

      Rating: 5
Thanks, Tom.
      I'll take a look at that link.  March 06, 2003

Reviewer:  John  from New Jersey, USA

      Rating: 5
Very good
      Tom!  March 07, 2003
Reviewer:
      A reader  from A reader

      Rating:
      4
  April 13, 2003
Reviewer:
      A reader

March 04, 2003 -- Thanks for the question regarding "Overwritten
      the PK in copy", version 9i

      You Asked
Hi Tom,

  I try to copy 5 records from table 1 to table 2 with the
same structure by using insert.  However, from table 2 there's 1
PK is the same with table 1 so it return with error and do
nothing.  If I eliminate that duplicate PK it copy sucessfully.

  Is there anyway that I can copy to another table when it see the
duplicate PK it just skip that PK and move to the next one? (I disable
the trigger because I want to reserve the same PK number)

Thanks

David

      and we said...
insert into t2
select *
  from t1
 where t1.pkey not in ( select pkey from t2 );

or

insert into t2
select *
  from t1
 where NOT EXISTS ( select null from t2 where t2.pkey = t1.pkey );

Review &
      Followup

      Rating:
      5
  March 02, 2003
Reviewer:
      A reader
thanks Tom

      Rating: 5
Using MERGET
      INTO  March 03, 2003
Reviewer:  Logan
      Palanisamy  from Santa Clara, CA USA
Tom,

Oracle developer and DBA community is indeed blessed to have someone like you
working tirelessly in helping and guiding us.

How about using the new MERGE INTO (The upsert feautre)

Like:

MERGE INTO t2
USING t1
ON t1.pk = t2.pk
WHEN MATCHED THEN update t2.c2 = t2.c2
WHEN NOT MATCHED THEN INSERT (t2.pk, t2.c2, t2.c3, ..) VALUES (t1.pk, t1.c2,
t1.c3, ...);

I feel MERGE INTO will be useful if
  1) there is no update trigger on T2
  2) Number of rows to be inserted is more compared the number of rows already
in t2

What is your opinion?

          Followup:

Benchmark it!  post the results.

March 04, 2003 Reji -- Thanks for the question regarding "UTF8
      and VARCHAR2", version 8.1.7

      You Asked
Tom:
   We have a VARCHAR2(4000) column in a UTF8 database.  When we try to
insert a string which is 4000 bytes the oracle server giving error message "the
value is too large and giving a size of 4496 (around this number ))".  We are
using JDBC thin driver (jdbc2 thin driver ).
Is it a jdbc limitation or we have to do something special here.  When we try to
reduce the size of the input string to 3000 it worked
fine.

Please explain why we are getting this behavior.

Thx
Reji

      and we said...
varchar2(4000) holds 4000 BYTES.

A string which is 4000 CHARACTERS in UTF8 may be MUCH larger then 4000 BYTES.
It could be 16000 BYTES.

This is not a jdbc limitation, it is rather a fact of UTF8 and multi-byte
character sets in general.  They (by definition) need more space.  A
varchar2(4000) can hold between a 1000 and 4000 character UTF8 string.
Review &
      Followup

      Rating: 4
api - and can i
      use substr for japanese ( utf8)  March 04, 2003

Reviewer:  umesh  from bangalore India
I have an application where user enters more than 2000 characters (bytes) in a
text field. I get the data and insert into a column in a chunk of 2000 bytes.
i.e if user enters 3000 bytes I take 2000 bytes in a row and the remaining in
the next row. So far our application supported only english so no problem.
now our application needs to support japanese, korean etc. i.e utf8 if i take a
substr of 2000 for the data entered by user it is going to be byte wise. but i
want yi character wise split to happen does oracle provide any api's for the
same
thanks

          Followup:

substr is character wise.

substrB would be byte wise.

you should just use a CLOB and do away with chunking (which since utf8 is a
4byte encoding scheme means you would have to use 1000, not 2000)

but -- USE A CLOB

March 04, 2003 Ruben -- Thanks for the question regarding "Using
      merge statement to update data warehouse", version 9.2

      You Asked
Hello Tom,

Please have a look at this.

SQL> create table emp as
  2  select empno, ename,
  3  sysdate date_created,
  4  to_date(null) date_modified
  5  from scott.emp;

Table created.

SQL>
SQL> update scott.emp
  2  set ename = 'SCOTTY'
  3  where ename = 'SCOTT';

1 row updated.

SQL>
SQL> insert into scott.emp (empno, ename)
  2  values (7400, 'JANSEN');

1 row created.

SQL>
SQL> begin dbms_lock.sleep(1); end;
  2  /

PL/SQL procedure successfully completed.

SQL>
SQL> merge into emp e1
  2  using (select empno, ename from scott.emp) e2
  3  on (e1.empno = e2.empno)
  4  when matched then
  5  update set e1.ename = e2.ename
  6  , date_modified = sysdate
  7  -- where e1.ename != e2.ename
  8  when not matched then
  9  insert (e1.empno, e1.ename, date_created)
 10  values (e2.empno, e2.ename, sysdate);

15 rows merged.

SQL>
SQL> select empno, ename
  2  , to_char(date_created, 'dd-mon-yyyy hh24:mi:ss')
  3  , to_char(date_modified, 'dd-mon-yyyy hh24:mi:ss')
  4  from emp;

     EMPNO ENAME      TO_CHAR(DATE_CREATED TO_CHAR(DATE_MODIFIE
---------- ---------- -------------------- --------------------
      7369 SMITH      02-mar-2003 17:15:42 02-mar-2003 17:15:43
      7782 CLARK      02-mar-2003 17:15:42 02-mar-2003 17:15:43
      7788 SCOTTY     02-mar-2003 17:15:42 02-mar-2003 17:15:43
      7839 KING       02-mar-2003 17:15:42 02-mar-2003 17:15:43
...
      7934 MILLER     02-mar-2003 17:15:42 02-mar-2003 17:15:43
      7400 JANSEN     02-mar-2003 17:15:43

15 rows selected.

SQL>

Besides a row level trigger, is there a way to implement
the commented where clause in the merge statement?

I would like emp to look like this:

     EMPNO ENAME      TO_CHAR(DATE_CREATED TO_CHAR(DATE_MODIFIE
---------- ---------- -------------------- --------------------
      7369 SMITH      02-mar-2003 17:15:42
      7782 CLARK      02-mar-2003 17:15:42
      7788 SCOTTY     02-mar-2003 17:15:42 02-mar-2003 17:15:43
      7839 KING       02-mar-2003 17:15:42
...

It would be nice if only the modified row was updated.

Thank you.

      and we said...
using (select empno, ename from scott.emp MINUS select empno, ename from emp )
e2

would accomplish that.
Review & Followup

      Rating: 4
INTERSECT, not
      MINUS  March 03, 2003
Reviewer:  Logan
      Palanisamy  from Santa Clara, CA USA
Tom,

I think MINUS will insert only the new rows without updating existing rows.

using (select empno, ename from scott.emp MINUS select empno, ename from emp )
e2

What he wants is the otherway. He wants to use only the UPDATE portion, not the
INSERT portion. I think we should use INTERSECT.

using (select empno, ename from scott.emp INTERSECT select empno, ename from emp
) e2

I guess it must be a little oversight on your part.

          Followup:

did you try it?

select empno, ename from scott.emp MINUS select empno, ename from emp

gives me every empno, ename in scott.emp that is NOT ALREADY IN emp.

it gives me empno,ename pairs for empno's that already exist in EMP but the
ename is different.

it gives me empno,ename pairs for empno's that do not exist yet in EMP.

He wants to

a) insert new rows (got it)
b) update only when ename changed for an empno (got it)

if you INTERSECT, you would only get rows that

o exist in EMP
o haven't changed

totally the opposite of what they ask for

      Rating: 5
Minus helps
      me  March 04, 2003
Reviewer:  Ruben
       from Amsterdam, The Netherlands
Logan,

What i ideally want is this:

         /       \
        /  match  \___No___ insert new row
        \  empno? /
         \       /
            |
            | Yes
            |
         /       \
        / changed \___No___ do nothing
        \  cols?  /
         \       /
            |
            | Yes
            |
         update old row
         insert new row

The only thing I didn't get with the 'minus merge'
Tom suggested is the insert after the update.

I don't think that is possible with only a single merge statement.

          Followup:

why didn't you?  the minus would give you that....

      Rating: 5
Reason for
      using INTERSECT  March 04, 2003
Reviewer:  Logan
      Palanisamy  from Santa Clara, CA USA
Tom,

Probably I misinterpreted the requirement. That is why I said INTERSECT is
needed.

When the poster said he wants

"I would like emp to look like this:

     EMPNO ENAME      TO_CHAR(DATE_CREATED TO_CHAR(DATE_MODIFIE
---------- ---------- -------------------- --------------------
      7369 SMITH      02-mar-2003 17:15:42
      7782 CLARK      02-mar-2003 17:15:42
      7788 SCOTTY     02-mar-2003 17:15:42 02-mar-2003 17:15:43
      7839 KING       02-mar-2003 17:15:42
...
"

I thought he doesn't want any inserts.  See he doesn't have the record "7400,
JANSEN"

Now that I understand his requirement better after his second posting, MINUS
will work perfectly.

      Rating:
      5
  March 04, 2003
Reviewer:  Ruben

Sorry for the confusion Tom, the minus completly answers my question.
It's fast, also works with external tables, very nice!

I made the remark above hoping to find out how an intersect might
be useful here but (as you have mentioned) it's not.

(Indeed i forgot to include 'JANSEN')

March 04, 2003 balraj -- Thanks for the question regarding "Redo
      and Rollback", version 8.1.6.3

      You Asked
Sir,I still have some doubts in how Redo and Rollback are used.
Answers to following queries will remove my doubts

I have 5 records of a table in Data Block buffers

  1.When I update the table,What are the entries made to the Rollback
    and Redo logfiles at this stage
  2.Now i make a commit,what changes get written to Redo and Rollback
  3.Again I issue a Update command,and issue a rollback ,is any entry
    made to the Redo at this stage
  4.In an other scenerio,I make some changes in the table(no commit
    or rollback) and the instance goes down,Now when I startup the
    database how will Oracle try to Rollback.

      Pl. explain the last question more nicely as it is a big doubt
      in my mind these days.

Thanks in advance for the nice Answers
Regards/Balraj

      and we said...
1) we record to the rollback segments the before images of the changes.  We
record to the redo log buffer the AFTER images of the changes.

2) nothing special happens to the rollback (its managed as buffered data).  The
redo log entries however are flushed from the SGA and written to disk.

3) yes, when you did the update, the redo log buffer was written to.  Before you
rolled back, this entry may very well have been written to disk already.  The
log buffer is constantly flushing.

4) It takes at least 4 pages to do that (60 lines / page).  I know because it is
in my book.  A brief synopsis:

- oracle will roll forward from the REDO logs both the data on disk in data
segments and the rollback data.

- oracle will then roll back from the newly recovered rollback segments.

It is important to know that changes will be flushed to the redo logs BEFORE a
block that is protected by the redo is flushed to disk -- so redo is always
ahead of your data.  In this fashion, if a transaction has committed -- we know
the rollforward will succeed (all redo is present on disk to redo that
transaction).  If a transaction has not committed -- we know the redo necessary
to rollback (rollback is protected by redo) is present on disk as well.

Review &
      Followup

      Rating:
      3
Thanks  June 23, 2001
Reviewer:
      A reader
Thanks a lot...

may have been more helpful..had it been a bit more explanatory

Thanks..Regards/Balraj

      Rating:
      3
  December 14, 2001
Reviewer:
      mathew  from india
hi,
    I would like to know what does the rollback segments and the redolog
contain?
   When a user modifies records the modified value is avaliable to his session
only and the old value is written to the rollback segments. Is this new value
written to the data blocks as well?
    Do the redologs contain the changes(sql statements issued) or the changed
values?
regards,
mathew

          Followup:

#1 above answers that.

rollback is UNDO, the before image.
redo is REDO, the after image.

redo contains the changes bytes, not the sql statements.

the server concepts guide is an excellent document that I believe you would find
of use.  It goes over this in total.

      Rating: 5
More of an
      explanation  December 14, 2001
Reviewer:
      Harrison  from Haymarket "Glasgow without the brogue", VA USA

Hi:

Besides the Oracle Server Concepts, Tom has written on redo and rollback (undo)
many times and you can find it by using the search tools he provides.  Tom's
answers are often volumes of working code, it is clearly impossible for him
spend too much time answering questions that have already been covered well
elsewhere or which he has answered many times before.  It would help anyone who
wants to know more if they used the archives offered here, and looked at the
references before they asked for more.  This site, more than any other, helps
with specific problems; asking for "more" of an explanation does not even
suggest which points the reviewer actually is lacking an understanding of.  If
you are not using the search tools you are wasting your time, no matter how Tom
tries, he can't answer every question, or do so "right now" but the search tools
can find much of what you are looking for, or you can look at Chapter 5 "Redo
and Rollback" in Tom's book "Expert one-on-one Oracle", where it is better
covered IMHO.  Anyway, if you have time to wait on Tom to answer a vague hint of
question, you may have too much time on your hands.

      Rating: 4
One
      doubt.  March 04, 2003
Reviewer:  Amar
       from INDIA
Hi,

I have a database in no archivelogmode. I did the following.

--------------------------------------------------------------------------------
SQL> create table test (enumber number);

SQL> declare
  1  x number := 1;
  2  begin
  3  for x in 1 .. 200000 loop
  4  insert into test values (10000000);
  5  end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

SQL> select count(1) from test;

  COUNT(1)
----------
    200000
--------------------------------------------------------------------------------

extract from alert logfile...
my redolog files a 5 MB in size.

--------------------------------------------------------------------------------

  Current log# 2 seq# 36 mem# 0: E:\ORACLE\ORADATA\ORCL\REDO02.LOG
Tue Mar 04 13:24:04 2003
Thread 1 cannot allocate new log, sequence 37
Checkpoint not complete
  Current log# 2 seq# 36 mem# 0: E:\ORACLE\ORADATA\ORCL\REDO02.LOG
Thread 1 advanced to log sequence 37
  Current log# 1 seq# 37 mem# 0: E:\ORACLE\ORADATA\ORCL\REDO01.LOG
Thread 1 cannot allocate new log, sequence 38
Checkpoint not complete
  Current log# 1 seq# 37 mem# 0: E:\ORACLE\ORADATA\ORCL\REDO01.LOG
Thread 1 advanced to log sequence 38
  Current log# 2 seq# 38 mem# 0: E:\ORACLE\ORADATA\ORCL\REDO02.LOG
Tue Mar 04 13:24:29 2003
Thread 1 cannot allocate new log, sequence 39
Checkpoint not complete
  Current log# 2 seq# 38 mem# 0: E:\ORACLE\ORADATA\ORCL\REDO02.LOG
Thread 1 advanced to log sequence 39
  Current log# 1 seq# 39 mem# 0: E:\ORACLE\ORADATA\ORCL\REDO01.LOG
--------------------------------------------------------------------------------

Then i did a shutdown abort, remember i haven't committed the transaction yet.

--------------------------------------------------------------------------------
SQL*Plus: Release 9.2.0.2.0 - Production on Tue Mar 4 13:19:15 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Enter user-name: sys as sysdba
Enter password:

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.2.1 - Production
With the Partitioning, Oracle Label Security, OLAP and Oracle Data Mining
options
JServer Release 9.2.0.2.0 - Production

SQL> shutdown abort
ORACLE instance shut down.
SQL> startup
ORACLE instance started.

Total System Global Area  101784268 bytes
Fixed Size                   453324 bytes
Variable Size              75497472 bytes
Database Buffers           25165824 bytes
Redo Buffers                 667648 bytes
Database mounted.
Database opened.
SQL>
--------------------------------------------------------------------------------

Didn't ask for any recovery.

--------------------------------------------------------------------------------
SQL> select * from test;

no rows selected

SQL>
--------------------------------------------------------------------------------

I understand that during rollforward the redo is actually rebuildng the rollback
segments so that it can roll back any uncomitted transaction.

Here in the above case the redologs were over written, then where did the
database or the SMON come to know what to rollback?
Here i assume that before overwriting, all information regarding the rollback
and data in the redo was moved to disk. If yes then how does the SMON know this?

Regards

          Followup:

There are two things to understand here

o we need UNDO to rollback.
o we need REDO for blocks in the buffer cache that are dirty.

that is all we need.

Hence, BEFORE we reused a redo log file -- we made sure all of the blocks it
protected were flushed from the buffer cache to disk first (a checkpoint
basically).  When those blocks were on disk -- we no long need that redo to
reconstitute them in the event of a failure.

So....  When you aborted -- there were some UNDO blocks safely tucked away in
the UNDO tablespace -- there were some in the buffer cache.  The ones in the
buffer cache were protected by the online REDO logs.

When we recovered -- we need only recover the blocks that were not safely on
disk already.

So, we rolled forward using the online redo logs -- got to exactly the same
point we were when you crashed us -- and then rolled back the outstanding work.

      Rating:
      5
Thanks  March 05, 2003
Reviewer:
      Amar  from INDIA
Thanks and Regards
Amar

March 04, 2003 srinivas -- Thanks for the question regarding
      "Analyze object privilege", version 816

      You Asked
Hi Tom ,
My requirement is as follows

On my server i have 7 databases.
1. I want to have cron schedule to analyze the objects in all the databases.
model I
Steps
1. connect to one user in DB1, i want to analyze all the schema's in others
databases including his own database, i have Database links between all of them.
2. I will generate the sql script for
  <analyze object_type schema.object_name@db_link >
3. So does the  privilege < Analyze All > will help  me to analyze  all the
objects in all the schemas in all databases.

Model II
steps
1.connect to respective databases as sys/system
2.generate sql script to get the users.
3.exec DBMS_UTILITY.ANALYZE_SCHEMA(<schema>,<method>)

Which one will be best, using the resources to a minimum and  inceasing the
performance.

Regards
srinivas

      and we said...
Model III

Have each schema schedule a job via DBMS_JOB that is simply:

declare
  l_job  number;
begin
  dbms_job.submit( l_job,
                  'dbms_utility.analyze_schema(user,''compute'');',
                   trunc(sysdate)+1+3/24,
                  'trunc(sysdate)+1+3/24 );
end;

that would cause their schema to be analyzed every morning at about 3am if the
database is up.

If you wanted to have just one big job to do it, simply

SQL> grant ANALYZE ANY to user_name;
SQL> connect user_name/password

and create a procedure that looks something like:

create procedure analyze_schemas
as
begin
   for x in ( select username from all_users where username not in
                 ('SYS','SYSTEM','DBSNMP','AURORA$ORB$UNAUTHENTICATED',
                  'ORDSYS','ORDPLUGINS','MDSYS','CTXSYS' ) )
   loop
       dbms_utility.analyze_schema( x.username, 'compute' );
   end loop;
end;
/

and schedule that single job in each database to run whenever you want.

Note that you CANNOT analyze over a database link (analyze is DDL, DDL doesn't
work over a dblink) so model I will not work.

Model II -- you would put a password into a file, bad idea.  using dbms_job
makes it so you don't have to do that AND the job only runs if the database is
up.  You could even make the job more sophisticated so that if the database was
down at 3am when it should have run and is started at 7am -- the job could look
at the time and say "I don't want to start the analyze after 5am, so I won't
analyze today" and exit.

Review &
      Followup

      Rating:
      5
How?  March 04, 2003
Reviewer:  Josh
       from oregon
<i>You could even make the job more sophisticated so that if the database was
down at 3am when it should have run and is started at 7am -- the job could look
at the time and say "I don't want to start the analyze after 5am, so I won't
analyze today" and exit.
</I>

How?

          Followup:

create procedure sophisticated
as
begin
   if to_char(sysdate,'hh24') not between 3 and 5
   then
      return;
   end if;
   ..........

March 04, 2003 Arun -- Thanks for the question regarding
      "Upper() in LOB Column?", version 8.1.6

      You Asked
Hi Tom,
  I am new to using LOBS. I found that we cannot use  upper() or lower()
functions on CLOBS
  1.  How do i go about a case insensitive search on them?
  2.  How do i create an index on the CLOB columns?. I find  retriving data from
them are slow. I use inline CLOB...

Your site is very very helpful. But this is the first time i got an opportunity
to post my question.
Regards,
Arun

      and we said...
you would not really want to run a query like:

select * from t where upper(clob) like something;

that would take forever.

You use interMedia (Oracle text in 9i) to index those documents:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/inter.817/index.htm
and then you can use the contains operator to search the documents (case
insensitive if you like by default).

search this site for

create index indextype ctxsys context

for examples.

Review &
      Followup

      Rating: 5
does oracle has
      the reference hosting of the data base ?  March 03, 2003

Reviewer:  A reader
does oracle provide the reference hosting of the 9i /8i database ?

 like anybody who whants some space (like 2 - 5 mb) and play with it  hosted
database solution by oracle for evaluation ?

      Rating: 5
upgrade
      time...  March 03, 2003
Reviewer:  Connor
      McDonald  from Perth
Alternatively you could upgrade to the latest version of Oracle where a large
number of 'typical' functions such as upper, concatenation etc become available
directly on clobs, within sensible limits.

Cheers
Connor

      Rating: 3
Found a way
      out  March 03, 2003
Reviewer:  Arun
       from India
I found a way out to do that. But as you said it is taking long time to execute.
Though much of my data is in the range of 2000 characters some go more than 4000
to 20000 maximum....

 This is how i do it.

  Select * from Table_name where upper(dbms_lob.substr(column_name, 4000,1))
like upper('abcd%') or upper(dbms_lob.substr(column_name, 4000,4000)) like
upper('abcd%') ...

   Regarding the index creation using intermedia for lobs, Will the performance
be the equivalent (if not the same) as that of a varchar2(4000) column index.  I
find there is no use of using a LOB in case performance comes down
drastically...

  The Table i use contains only about 30000 rows and will be adding to itself
around 20-30 rows per day.
The LOB columns will contain only 2-3% of data which is above 4000 characters
long.

Regards,
Arun

          Followup:

and that doesn't really work (you have things broken on 4000 byte boundaries --
you'll miss everything where things span that boundary.

The performance will be comparable to an index on a varchar2 -- but I fail to
see how you can compare them as the intermedia index contains a thousand percent
more functionality and is the only thing that supports something > 4000
characters.

do what I do -- benchmark it.  It is what you use on asktom here to search with
all of the time.

      Rating: 4
does Oracle
      Text requires additional license? is it available for Oracle SE 9.2?
        March 04, 2003
Reviewer:  gs
Thanks Tom for all your valuable responses.

Can you please respond to the question on the title?

          Followup:

it is part of SE and EE, you have it


Subject | Article | Last Updated (East coast time)
Query Cost | article only | Mar 05, 2003 07:47 am
query performance | article only | Mar 05, 2003 08:07 am
Script to generate create tablespace... | article only | Mar 05, 2003 09:25 am
strange ORA-01652 error | article only | Mar 05, 2003 07:07 pm
read only snapshots for insert DML only | article only | Mar 05, 2003 07:30 pm
Database Backup Method | article only | Mar 05, 2003 07:36 pm
Using Inline Query In Update Statement | article only | Mar 05, 2003 07:39 pm
using named notation versus call func(? ? ?) in calling stored procedures | article only | Mar 06, 2003 07:49 am
Updating a view | article only | Mar 06, 2003 07:57 am
Evaluation precedence and the optimiser | article only | Mar 06, 2003 09:12 am
Stats Pack 8.1.7 parameter question | article only | Mar 06, 2003 03:34 pm
List of Migration Issues | article only | Mar 06, 2003 03:36 pm
dump file size | article only | Mar 06, 2003 03:38 pm
Calling OS command from PL/SQL(ext proc)or Java way | article only | Mar 06, 2003 07:16 pm
Copy of Database on the same machine | article only | Mar 07, 2003 07:56 am
what is lobsegment,lobindex | article only | Mar 07, 2003 07:58 am
Regarding create index and drop table privileges | article only | Mar 07, 2003 08:05 am
freelist, | article only | Mar 07, 2003 10:56 am
Tuning tips. Use select count(*) or select * for tuning purposes. | article only | Mar 07, 2003 11:00 am
resize datafiles | article only | Mar 07, 2003 11:10 am
ORA-06519 | article only | Mar 07, 2003 01:52 pm
reducing resource used for a function | article only | Mar 07, 2003 02:40 pm
Dynamic SQL | article only | Mar 07, 2003 06:22 pm
Repeat Rows Returned By Select | article only | Mar 07, 2003 07:30 pm
Elapsed time format change | article only | Mar 07, 2003 07:32 pm
dbms_alert_info | article only | Mar 07, 2003 07:38 pm
ORA-22818: subquery expressions not allowed here. | article only | Mar 08, 2003 09:12 am
Limiting Web Page Rows | article only | Mar 08, 2003 01:46 pm
Optimal Flexible Architecture | article only | Mar 09, 2003 12:30 pm
Field Level Security | article only | Mar 10, 2003 07:09 am
storing passwords in the database. | article only | Mar 10, 2003 07:22 am
copying inserted values | article only | Mar 10, 2003 07:23 am
COMPLEX QUERY | article only | Mar 10, 2003 08:15 am
Displaying time elapsed between dates | article only | Mar 10, 2003 08:18 am
DBMS_AQ and commit | article only | Mar 10, 2003 09:30 am
bitor | article only | Mar 10, 2003 09:47 am
password lost | article only | Mar 10, 2003 05:56 pm
exception | article only | Mar 10, 2003 06:05 pm
How to trace a Oracle Form | article only | Mar 11, 2003 07:56 am
Application File Upload | article only | Mar 11, 2003 08:01 am
Using array w/ IN list, ordering results by order of items in input array. | article only | Mar 11, 2003 08:21 am
exception handling in batch processing | article only | Mar 11, 2003 08:41 am
Bind variables within package | article only | Mar 11, 2003 08:49 am
Searching PDF Document | article only | Mar 11, 2003 09:08 am
row(s) 1 - 44 of 44
March 05, 2003 Yogesh -- Thanks for the question regarding "Query Cost", version 8.1.7
You Asked
IF I use the following query

SELECT
 C.CUSTOMERCODE,
 C.FIRSTNAME,
 C.MIDDLEINITIAL,
 C.LASTNAME ,
 C.SSN ,
 C.QCPCUSTOMERCODE,
 C.ADDRESS
FROM
 CUSTOMER_MASTER C
WHERE
 Customercode =323232;

here is the  plan

Plan Table
--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |     1 |   44 |      2 |       |       |
|  TABLE ACCESS BY INDEX ROW|CUSTOMER_ |     1 |   44 |      2 |       |       |
|   INDEX UNIQUE SCAN       |PK_CUSTCO |     1 |      |      1 |       |       |
--------------------------------------------------------------------------------

If I execute query with not condition i.e.

 SELECT
  C.CUSTOMERCODE,
  C.FIRSTNAME,
  C.MIDDLEINITIAL,
  C.LASTNAME ,
  C.SSN ,
  C.QCPCUSTOMERCODE,
  C.ADDRESS
 FROM
  CUSTOMER_MASTER C
 WHERE
  Customercode <>323232;

its plan is

--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |    46K|    1M|     45 |       |       |
|  TABLE ACCESS FULL        |CUSTOMER_ |    46K|    1M|     45 |       |       |
--------------------------------------------------------------------------------

i.e. here it is not using the index. why it is so ?

Secondly if I use the following query

 SELECT
  C.CUSTOMERCODE,
  C.FIRSTNAME,
  C.MIDDLEINITIAL,
  C.LASTNAME ,
  C.SSN ,
  C.QCPCUSTOMERCODE,
  C.ADDRESS
 FROM
  CUSTOMER_MASTER C
 WHERE
  Customercode in
  (select custcode from x)

its plan is

--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |    37K|    2M|     84 |       |       |
|  HASH JOIN                |          |    37K|    2M|     84 |       |       |
|   VIEW                    |VW_NSO_1  |    82 |    1K|      3 |       |       |
|    SORT UNIQUE            |          |    82 |    1K|      3 |       |       |
|     TABLE ACCESS FULL     |X         |    82 |    1K|      1 |       |       |
|   TABLE ACCESS FULL       |CUSTOMER_ |    46K|    1M|     45 |       |       |
--------------------------------------------------------------------------------
Here costs gone up like anything .... It do not use the index. Can we force here
to make use of index ? Here solution may be use of join but, when the tables
(customer master) have large volume of data, join really takes long time.

Here Customercode is PK, so Unique index is available.

Any solutions ?
and we said...
Answer this for me -- why would it use the index when it must get MOST all of
the rows in the table anyway?  It would be very very inefficient to read every
row from the table via the index.

Indexes are NOT the best way ALWAYS to get to the data, in many cases -- they
would make the query run many thousands times slower.  Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t as select * from all_objects;
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t add constraint t_pk primary key
(object_id);
Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from t;

  COUNT(*)
----------
     17276

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set sql_trace=true;
Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where object_id <> -1;

17276 rows selected.

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

Statistics
----------------------------------------------------------
         79  recursive calls
         15  db block gets
       1401  consistent gets
        182  physical reads
          0  redo size
    2003944  bytes sent via SQL*Net to client
     128191  bytes received via SQL*Net from client
       1153  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      17276  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ index( t t_pk ) */  * from t where
object_id <> -1;

17276 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=66 Card=960 Bytes=122880)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=66 Card=960 Bytes=122880)
   2    1     INDEX (FULL SCAN) OF 'T_PK' (UNIQUE) (Cost=26 Card=960)

Statistics
----------------------------------------------------------
        270  recursive calls
          0  db block gets
      17468  consistent gets
        252  physical reads
          0  redo size
    1902907  bytes sent via SQL*Net to client
     128191  bytes received via SQL*Net from client
       1153  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      17276  rows processed

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

Just from that you should be able to see the second query that uses the index
performs horribly -- 17,468 consistent gets (logical IO's) versus 1,401.  Many
more physical IO's (gotta read the index AND the table)..

The tkprof *nails* it that the index is horrible to use in this case:

select *
from
 t where object_id <> -1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     1153      0.21       0.26         10       1367         15       17276
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1155      0.22       0.27         10       1367         15       17276

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 187  (OPS$TKYTE)

Rows     Row Source Operation
-------  ---------------------------------------------------
  17276  TABLE ACCESS FULL T

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
  17276   TABLE ACCESS (FULL) OF 'T'

********************************************************************************

select /*+ index( t t_pk ) */  *
from
 t where object_id <> -1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     1153      0.65       1.32        192      17449          0       17276
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1155      0.66       1.32        192      17449          0       17276

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 187  (OPS$TKYTE)

Rows     Row Source Operation
-------  ---------------------------------------------------
  17276  TABLE ACCESS BY INDEX ROWID T
  17277   INDEX FULL SCAN (object id 28369)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
  17276   TABLE ACCESS (BY INDEX ROWID) OF 'T'
  17277    INDEX (FULL SCAN) OF 'T_PK' (UNIQUE)

I like the query that takes .27 seconds, not the one that takes 1.32 seconds.

indexes do not imply, mean or infer "faster" in every case.  In fact, the
converse is FREQUENTLY true

Ok, so now that that myth has been sufficiently beaten back, lets look at the
next one.  This is tricker since I do not know the cardinality of custcode (and
Oracle probably doesn't either).  I do not know if you are using stats or not.
Lets see what happens in various cases (continueing from above).

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t2 ( x number );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where object_id in ( select x
from t2 );

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     VIEW OF 'VW_NSO_1'
   3    2       SORT (UNIQUE)
   4    3         TABLE ACCESS (FULL) OF 'T2'
   5    1     TABLE ACCESS (BY INDEX ROWID) OF 'T'
   6    5       INDEX (UNIQUE SCAN) OF 'T_PK' (UNIQUE)

so, there we can see that is it using a NESTED LOOPS join -- is that the best?
 Don't know -- depends, totally depends on how much (or how little) data is in
T2 doesn't it?

Right now, I'm using the RBO (rule based optimizer) since I haven't analyzed a
thing.  So, lets analyze the big table and see what happens:

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where object_id in ( select x
from t2 );

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=12 Card=14166 Bytes=1402434)
   1    0   HASH JOIN (Cost=12 Card=14166 Bytes=1402434)
   2    1     VIEW OF 'VW_NSO_1' (Cost=3 Card=82 Bytes=1066)
   3    2       SORT (UNIQUE) (Cost=3 Card=82 Bytes=1066)
   4    3         TABLE ACCESS (FULL) OF 'T2' (Cost=1 Card=82 Bytes=1066)
   5    1     TABLE ACCESS (FULL) OF 'T' (Cost=7 Card=17276 Bytes=1485736)

So, there is your hash join.  That is because it doesn't know that T2 is
current "empty", it is not sure how many rows you'll be accessing via the index
so it's going with a full scan.  Now, lets complete the picture for the CBO:

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t2 compute statistics;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where object_id in ( select x
from t2 );

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=99)
   1    0   NESTED LOOPS (Cost=4 Card=1 Bytes=99)
   2    1     VIEW OF 'VW_NSO_1' (Cost=3 Card=1 Bytes=13)
   3    2       SORT (UNIQUE) (Cost=3 Card=1 Bytes=13)
   4    3         TABLE ACCESS (FULL) OF 'T2' (Cost=1 Card=1 Bytes=13)
   5    1     TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=1 Card=17276
Bytes=1485736)
   6    5       INDEX (UNIQUE SCAN) OF 'T_PK' (UNIQUE)

There we go -- back to a nested loops join.  Makes sense in this case since it
now knows T2 is "empty".  Lets fill up t2 however and analyze:

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t2 select object_id from t;

17276 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t2 compute statistics;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where object_id in ( select x
from t2 );

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=74 Card=17276 Bytes=1710324)
   1    0   HASH JOIN (Cost=74 Card=17276 Bytes=1710324)
   2    1     VIEW OF 'VW_NSO_1' (Cost=36 Card=17276 Bytes=224588)
   3    2       SORT (UNIQUE) (Cost=36 Card=17276 Bytes=69104)
   4    3         TABLE ACCESS (FULL) OF 'T2' (Cost=1 Card=17276 Bytes=69104)
   5    1     TABLE ACCESS (FULL) OF 'T' (Cost=7 Card=17276 Bytes=1485736)

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA817DEV.US.ORACLE.COM>

In this case, using full scans and hash joins is 100% correct -- as was NOT
using the index in the <> case!

I suggest you use autotrace instead of the explain plan command -- much
nicer/easier output and it includes the cardinalities AND it would tell me what
optimizer you are using (no card in the explain plan means you used RBO, card in
the explain plan means you used CBO.

So -- the moral to this story is that an INDEX is NOT the best way in all
cases for accessing a table.  The optimizer makes its decision based on facts --
and I don't know what exactly your facts where in this case (row counts, etc)
Review & Followup
Rating: 4
Excellent March 15, 2002
Reviewer: pawan from USA
Excellent explanation and analysis Tom.

Rating: 4
Great Explaination March 16, 2002
Reviewer: Yogesh from PUNE, India
Cleared most of the things.

Rating: 4
CBO and indexing ? November 19, 2002
Reviewer: Chris
Tom, I am a bit confused.
Can I compare queries using index VS not using index (but both with CBO
(analyzed table)) ?
OR when we use index...we don't analyzed the table, so :
indexing VS CBO ?
How to made a table not analyzed again when I want to test index without
analyzed table (but I already analyzed those tables) ?

Followup:
You can compare them -- but use TKPROF and real world results.  That is --
actually run the things and look at the logical IO's, the CPU time, the real
world performance.  Not a cost.

Rating: 4
CBO and indexing ? November 19, 2002
Reviewer: Chris
Tom, I am a bit confused.
Can I compare queries using index VS not using index (but both with CBO
(analyzed table)) ?
OR when we use index...we don't analyzed the table, so :
indexing VS CBO ?
How to made a table not analyzed again when I want to test index without
analyzed table (but I already analyzed those tables) ?

Rating: 4
Thx Tom :) November 21, 2002
Reviewer: Christyna
Yes, I use TKPROF. Oh so the cost is not 'real world' estimation ?

Another question, Tom.
When I do full scan table of 1385 rows with RBO vs CBO...why the result is
faster with RBO ? Is CBO doen't guarantee faster queries ?

Followup:
COST is a number.  You cannot look at cost and say "it'll take 2 seconds for
that one".

as for the full scan thing -- i question your testing methods.  You've made a
mistake somewhere.

once the query plan is developed -- the optimizer is out of the picture.  Hence
the use of the CBO over RBO will not influence the performance of a full scan.
If both optimizers come up with the same plan -- it will perform the same.

Either your test was flawed in some way or the plans were in fact NOT the same.

Rating: 5
One more doubt January 18, 2003
Reviewer: Yogesh from Pune, India
I'm executing following query
SELECT
    ID,
    CCODE,
    WCODE,
    SUM(DECODE(RC,'201',1,0)),
    SUM(DECODE(RC,'202',1,0)),
    SUM(DECODE(RC,'203',1,0)),
    SUM(DECODE(RC,'204',1,0))
FROM
     TAB1
WHERE
     DID='11' AND
     AID='12' AND
     TIMESTAMP BETWEEN
     TO_DATE('01/17/2002 00:00:00','MM/DD/YYYY HH24:MI:SS') AND
     TO_DATE('01/17/2003 23:59:59','MM/DD/YYYY HH24:MI:SS')
GROUP BY
    ID,
    CCODE,
    WCODE

on a table of 100000 rows lets say .. NO indexes are present .... how internally
it executes the query ?

first it filter data based on first condition then within that next condition
and so on ... or how is it ? in short
How to check sequence of condition execution ? Does interchange of conditions
have impact on query cost?

Followup:
It'll be a full scan -- the predicate will be evaluated in some order -- an
order you have no control over, an order that can change as the optimizer sees
fit over time. you cannot rely on the order of evaluation of a predicate EVER.
It is by definition not defined.

Rating: 5
Thanx a ton January 21, 2003
Reviewer: Yogesh from Pune, India
Thats what I was looking for.

Rating: 1
Cost Converted into Time March 05, 2003
Reviewer: A reader
Why cant the CBO of Oracle be smarter, CBO of teradata gives us the app time it
will take to execute the query...
does oracle let us know app time it will take to execute the query. Explain
command of teradata gives the app time.

cheers

Followup:
tell me, is it always right too?

Rating: 5
Very clear and useful..as always May 22, 2003
Reviewer: ZS from Australia
Thanks Tom for all the responses you have made so far. and thanks for spending
the time to help us understand Oracle better.
I always come to your website to learn more and I really learn. I also have your
book on my desk that helps me alot in understanding oracle.

March 05, 2003 tammy -- Thanks for the question regarding "query performance", version 8.1.7.3.0
You Asked
Hi Tom:
Thank you for your replying my question so quick last Saturday.... I have
another question here. Developer send me a script to create a Materialized View
which is taking forever to run, the table is only around 3 million rows, there
is no index on all the columns as well. Last week I tried to create index, it
took forever as well.

Here is the MV creation script:
==========================
spool DW_CUR_HST_DHS_DLY.lst

alter table DOW_DATA.HST_DHS_DAILY_EXTRACT parallel 8;

alter session enable PARALLEL DML;

drop materialized view DOW_DATA.DW_CUR_HST_DHS_DLY
/

CREATE MATERIALIZED VIEW DOW_DATA.DW_CUR_HST_DHS_DLY
PCTFREE 10
PCTUSED 80
MAXTRANS 255
TABLESPACE DATA_4M
LOGGING
BUILD IMMEDIATE
REFRESH FORCE
ON DEMAND
AS SELECT
  A.DEC_APP_STATUS           ,
  A.APP_AUTOMATIC_NUMBER     ,
  A.APP_APPLICATION_DATE     ,
  A.APP_FIRST_NAME           ,
  A.APP_LAST_NAME            ,
  A.APP_MIDDLE_INITIAL       ,
  A.APP_DATE_OF_BIRTH        ,
  A.APP_SOCIAL_SECURITY      ,
  A.APP_ADDRESS_NUMBER       ,
  A.APP_ADDRESS_STREET       ,
  A.APP_STREET_TYPE          ,
  A.APP_UNIT_NO              ,
  A.APP_FULL_ADDRESS_CB      ,
  A.APP_CITY                 ,
  A.APP_STATE                ,
  A.APP_ZIP                  ,
  A.APP_ZIP_COMPLEMENT       ,
  A.APP_RENT_OWNER           ,
  A.APP_TIME_AT_RESIDENCE    ,
  A.APP_HOME_AREA_CODE       ,
  A.APP_HOME_PHONE           ,
  A.APP_MOTHER_S_MAIDEN_N    ,
  A.APP_ID_TYPE              ,
  A.APP_ID_STATE             ,
  A.APP_ID_NO                ,
  A.APP_EMPLOYER_NAME        ,
  A.APP_POSITION_CODE        ,
  A.APP_TIME_AT_JOB          ,
  A.APP_WORK_AREA_CODE       ,
  A.APP_WORK_PHONE_NUMBER    ,
  A.APP_E_MAIL_ADDRESS       ,
  A.CB_REPORTDATE            ,
  A.CB_MODEL_TYPE            ,
  A.CB_RISKSCORE             ,
  A.CB_FICO_SCORE            ,
  A.CB_FRAUDSHIELD_SCORE     ,
  A.CB_PUBLIC_RECORDSCOUN    ,
  A.CB_INSTALLMENT_BALANC    ,
  A.CB_REVOLVING_BALANCE     ,
  A.CB_PAST_DUE_AMOUNT       ,
  A.CB_MONTHLY_PAYMENT       ,
  A.CB_REVOLV_AVAIL_PERC     ,
  A.CB_TOTAL_TRADE_COUN      ,
  A.CB_SATISF_ACC_COUNTER    ,
  A.CB_NOW_DELINQ_DEROG_C    ,
  A.CB_WAS_DELINQ_DEROG_C    ,
  A.CB_OLDEST_TRADE_DATE     ,
  A.CB_DAY_30_COUNTER        ,
  A.CB_DAY_60_COUNTER        ,
  A.CB_DAY_90_COUNTER        ,
  A.CB_DEROG_COUNTER         ,
  A.CB_SCORE_FACTOR_CODE     ,
  A.CB_SCORE_FACTOR_CODE_1   ,
  A.CB_SCORE_FACTOR_CODE_2   ,
  A.CB_SCORE_FACTOR_CODE_3   ,
  A.DEC_DFS_CUSTOMER_FLAG    ,
  A.DEC_DECISION             ,
  A.DEC_DECISION_DATE        ,
  A.DEC_CREDIT_LIMIT         ,
  A.DEC_APR                  ,
  A.DEC_EXPLANATION_1        ,
  A.DEC_TRS_CUSTOMER_FOUN    ,
  A.DEC_LAST_TRS_DEC_DATE    ,
  A.DEC_LAST_TRS_DECISION    ,
  A.DEC_LMS_CUSTOMER_FOUN    ,
  A.DEC_LAST_LMS_DEC_DATE    ,
  A.DEC_LAST_LMS_DECISION    ,
  A.DEC_LMS_CREDIT_ON_HOL    ,
  A.DEC_LMS_LAST_DECSTATU    ,
  A.DEC_LMS_LAST_DEC_CRED    ,
  A.COMPANY_NUMBER           ,
  A.LMS_LEASE_QUOTE_NUMBE    ,
  A.LMS_LEASE_CREATE_DATE    ,
  A.SALES_REP_NAME           ,
  A.ORDER_AMOUNT             ,
  A.DOMS_ORDER_NUMBER        ,
  A.CUST_DFS_CUSTOMER_NO_1   ,
  A.CUST_DFS_CUSTOMER_NO_2   ,
  A.CUST_DFS_CUSTOMER_NO_3   ,
  A.CUST_DFS_CUSTOMER_NO_4   ,
  A.CUST_DELL_CUSTOMER_NO_1  ,
  A.CUST_DELL_CUSTOMER_NO_2  ,
  A.CUST_DELL_CUSTOMER_NO_3  ,
  A.CUST_DELL_CUSTOMER_NO_4  ,
  A.ORIGIN                   ,
  A.DECISION_CODE_1          ,
  A.DECISION_CODE_2          ,
  A.DECISION_CODE_3          ,
  A.DECISION_CODE_4          ,
  A.MATRIX_CODE              ,
  A.MATRIX_CODE_COMPARISO    ,
  A.BK_SCORE                 ,
  A.CB_TYPE_CB_REPORT        ,
  A.LMS_CREDIT_USED          ,
  A.CB_FRAUDSHIELD_SCOR_1    ,
  A.CB_FRAUDSHIELD_SCOR_2    ,
  A.CB_FRAUDSHIELD_SCORE_3   ,
  A.CB_FRAUDSHIELD_SCORE_4   ,
  A.BK_SC_FACTOR_CODE_1      ,
  A.BK_SC_FACTOR_CODE_2      ,
  A.BK_SC_FACTOR_CODE_3      ,
  A.BK_SC_FACTOR_CODE_4      ,
  A.FS_SCORE                 ,
  A.FS_SC_FACTOR_CODE_1      ,
  A.FS_SC_FACTOR_CODE_2      ,
  A.FS_SC_FACTOR_CODE_3      ,
  A.FS_SC_FACTOR_CODE_4      ,
  A.AU_INWALLET_SCORE        ,
  A.AU_ACCEPT_REFER_CODE     ,
  A.LMS_NEG_FILE_ACTION      ,
  A.LMS_60_DAY_ACTION        ,
  A.LMS_REF_ACTION           ,
  A.FIRST_BUREAU             ,
  A.EQ_CONNECTION_ERROR      ,
  A.EQ_PROC_ERROR_CODE       ,
  A.CB_BEACON                ,
  A.CB_REAS1                 ,
  A.CB_REAS2                 ,
  A.CB_REAS3                 ,
  A.CB_REAS4                 ,
  A.TR_DECL_SOURCE           ,
  A.FPDSC_SCORECARD_TOTAL    ,
  A.FRAUD_OVERRIDE_REASON    ,
  A.CHANNEL                  ,
  A.OVERRIDE_REASON_CODE     ,
  A.UPDATE_DATE              ,
  A.UPDATE_NAME              ,
  A.INSERT_DATE              ,
  A.INSERT_NAME
  FROM HST_DHS_DAILY_EXTRACT A
  WHERE A.insert_date = (SELECT MAX(B.insert_date)
                         FROM HST_DHS_DAILY_EXTRACT B
                         WHERE A.APP_AUTOMATIC_NUMBER = B.APP_AUTOMATIC_NUMBER)

/
===================

Here is the indexes creation I tried to use:

=======================

spool HST_DHS_DAILY_EXTRACT_IND.lst

alter session enable PARALLEL DML;

CREATE INDEX DOW_DATA.HST_DHS_DLY_EXT_INS_DTE
    ON DOW_DATA.HST_DHS_DAILY_EXTRACT(INSERT_DATE)
PCTFREE 10
INITRANS 2
MAXTRANS 255
TABLESPACE INDEX_128M
STORAGE(FREELISTS 1
        FREELIST GROUPS 1
        BUFFER_POOL DEFAULT)
NOLOGGING
PARALLEL(DEGREE 8 INSTANCES 1)
/

CREATE INDEX DOW_DATA.HST_DHS_DLY_EXT_APP_AUT_NUM
    ON DOW_DATA.HST_DHS_DAILY_EXTRACT(APP_AUTOMATIC_NUMBER)
PCTFREE 10
INITRANS 2
MAXTRANS 255
TABLESPACE INDEX_128M
STORAGE(FREELISTS 1
        FREELIST GROUPS 1
        BUFFER_POOL DEFAULT)
NOLOGGING
PARALLEL(DEGREE 8 INSTANCES 1)
/

CREATE INDEX DOW_DATA.HST_DHS_DLY_EXT_AUT_NUM_INS_DTE
    ON DOW_DATA.HST_DHS_DAILY_EXTRACT(APP_AUTOMATIC_NUMBER,INSERT_DATE)
PCTFREE 10
INITRANS 2
MAXTRANS 255
TABLESPACE INDEX_128M
STORAGE(FREELISTS 1
        FREELIST GROUPS 1
        BUFFER_POOL DEFAULT)
NOLOGGING
PARALLEL(DEGREE 8 INSTANCES 1)
/
====================

neither of them are successful. today I use MV creation script without indexes,
I kicked off at 2:45pm, it is still running right now (9:00pm). What
recommendation I should give to developer? I believe this MV will need to be
refreshed frequently (complete refresh). table HST_DHS_DAILY_EXTRACT is around 3
millions rows of data.
and we said...
Well, I personally cannot understand what you mean by "neither of them are
successful" (it only takes me a couple of minutes to create indexes on a one
million row table on my LAPTOP).  Would need more info about that.

Their query should be:

AS SELECT
  A.DEC_APP_STATUS           ,
  A.APP_AUTOMATIC_NUMBER     ,
  A.APP_APPLICATION_DATE     ,
....
  A.INSERT_DATE              ,
  A.INSERT_NAME
from ( select hst_dhs_daily_extract.*,
              row_number() over ( partition by app_automatic_number
                                  order by insert_date DESC nulls last ) rn
         FROM HST_DHS_DAILY_EXTRACT ) A
 where rn = 1
/

it'll do in a single pass what you are trying to do with a correlated subquery.

(you guys do use the COST Based optimizer and have upto date stats right???)
Review & Followup
Rating: 5
September 05, 2002
Reviewer: A reader
Hi Tom:
in the init.ora file the setting is:"optimizer_mode = choose". so I think we are
using choose mode, and we do analyze all table estimate statistics sample 10
percent;

Is it wrong? I thought choose mode is the right way to go for data warehouse?

Followup:
if you analyze and have CHOOSE set, you are using the Cost based optimizer

Rating: 3
Please explain September 05, 2002
Reviewer: Neeraj Verma from Atlanta, GA
could you please explain this subquery in little more detail
...
...
from ( select hst_dhs_daily_extract.*,
              row_number() over ( partition by app_automatic_number
                                  order by insert_date DESC nulls last ) rn
         FROM HST_DHS_DAILY_EXTRACT ) A
 where rn = 1

Thanks

Followup:
search this site for

analytic

if you have my book "expert one on one" read the chapter on analytic functions.

or read the data warehousing guide (on otn.oracle.com) -- the chapter on
analytics

Rating: 4
window function versus corellated subquery September 05, 2002
Reviewer: mikito harakiri
They should have the same performance, if you have an index on INSERT_DATE. I
compared both on 9.2 and got about 42K buffer gets each. Window query is much
faster, of course, if there is no index on semijoin column. Check your plan,
whether it's something like this:

SELECT STATEMENT
   FILTER
      TABLE ACCESS  FULL   HST_DHS_DAILY_EXTRACT
      SORT  AGGREGATE
         FILTER
            FIRST ROW
               INDEX FULL SCAN (MIN/MAX) HST_DHS_DLY_EXT_INS_DTE

Rating: 3
confused October 04, 2002
Reviewer: tammy pickett from USA
tom: I did run the query you provide but explain plan tells me that it is more
expensive. could you explain why?

I have indexes on app_automatic_number and ins_dt,plus composite index on
(app_automatic_number, ins_dt).

15:22:13 SQL>
15:22:13 SQL>
15:22:13 SQL> set autotrace traceonly exp
15:22:13 SQL>
15:22:13 SQL>
15:22:13 SQL> SELECT
15:22:13   2      A.DEC_APP_STT  AS DEC_APP_STATUS,
15:22:13   3      A.APP_AUTOMATIC_NBR AS APP_AUTOMATIC_NUMBER,
15:22:13   4      A.APP_APPLCTN_DT AS APP_APPLICATION_DATE,
15:22:13   5      A.APP_FRST_NM AS APP_FIRST_NAME ,
15:22:13   6      A.APP_LST_NM AS APP_LAST_NAME ,
15:22:13   7      A.APP_MID_INIT AS APP_MIDDLE_INITIAL,
15:22:13   8      A.APP_DT_OF_BRTH AS APP_DATE_OF_BIRTH ,
15:22:13   9      A.APP_SOC_SEC AS APP_SOCIAL_SECURITY ,
15:22:13  10      A.APP_ADDR_NBR AS APP_ADDRESS_NUMBER ,
15:22:13  11      A.APP_ADDR_STR AS APP_ADDRESS_STREET,
15:22:13  12      A.APP_STR_TYP AS APP_STREET_TYPE ,
15:22:13  13      A.APP_UNT_NBR AS APP_UNIT_NO ,
15:22:13  14      A.APP_FULL_ADDR_CB AS APP_FULL_ADDRESS_CB ,
15:22:13  15      A.APP_CTY AS APP_CITY,
15:22:13  16      A.APP_ST AS APP_STATE,
15:22:13  17      A.APP_ZIP  AS APP_ZIP ,
15:22:13  18      A.APP_ZIP_COMPLM  AS APP_ZIP_COMPLEMENT,
15:22:13  19      A.APP_RENT_OWNR         AS APP_RENT_OWNER,
15:22:13  20      A.APP_TM_AT_RSDNC        AS APP_TIME_AT_RESIDENCE ,
15:22:13  21      A.APP_HOME_AREA_CD        AS APP_HOME_AREA_CODE ,
15:22:13  22      A.APP_HOME_PH            AS APP_HOME_PHONE ,
15:22:13  23      A.APP_MOTHERS_MAIDEN_NM     AS APP_MOTHER_S_MAIDEN_N,
15:22:13  24      A.APP_ID_TYP            AS APP_ID_TYPE,
15:22:13  25      A.APP_ID_ST            AS APP_ID_STATE,
15:22:13  26      A.APP_ID_NBR            AS APP_ID_NO,
15:22:13  27      A.APP_EMPLR_NM            AS APP_EMPLOYER_NAME,
15:22:13  28      A.APP_POS_CD            AS APP_POSITION_CODE,
15:22:13  29      A.APP_TM_AT_JOB         AS APP_TIME_AT_JOB ,
15:22:13  30      A.APP_WRK_AREA_CD        AS APP_WORK_AREA_CODE,
15:22:13  31      A.APP_WRK_PH_NBR        AS APP_WORK_PHONE_NUMBER,
15:22:13  32      A.APP_E_MAIL_ADDR        AS APP_E_MAIL_ADDRESS,
15:22:13  33      A.CB_RPTDT           AS CB_REPORTDATE,
15:22:13  34      A.CB_MOD_TYP            AS CB_MODEL_TYPE ,
15:22:13  35      A.CB_RSKSCORE            AS CB_RISKSCORE ,
15:22:13  36      A.CB_FICO_SCORE         AS CB_FICO_SCORE ,
15:22:13  37      A.CB_FRAUDSHIELD_SCORE        AS CB_FRAUDSHIELD_SCORE,
15:22:13  38      A.CB_PUBLC_RECS_CNT        AS CB_PUBLIC_RECORDSCOUN,
15:22:13  39      A.CB_INSTLMNT_BAL        AS CB_INSTALLMENT_BALANC,
15:22:13  40      A.CB_REVOLVING_BAL        AS CB_REVOLVING_BALANCE,
15:22:13  41      A.CB_PAST_DUE_AMT        AS CB_PAST_DUE_AMOUNT,
15:22:13  42      A.CB_MTHLY_PMT            AS CB_MONTHLY_PAYMENT,
15:22:13  43      A.CB_REVOLV_AVAIL_PERC        AS CB_REVOLV_AVAIL_PERC,
15:22:13  44      A.CB_TOT_TRD_CNT        AS CB_TOTAL_TRADE_COUN,
15:22:13  45      A.CB_SATISF_ACC_CTR        AS CB_SATISF_ACC_COUNTER,
15:22:13  46      A.CB_NOW_DELINQ_DEROG_C     AS CB_NOW_DELINQ_DEROG_C,
15:22:13  47      A.CB_WAS_DELINQ_DEROG_C     AS CB_WAS_DELINQ_DEROG_C ,
15:22:13  48      A.CB_OLDEST_TRD_DT       AS CB_OLDEST_TRADE_DATE,
15:22:13  49      A.CB_DY_30_CTR            AS CB_DAY_30_COUNTER,
15:22:13  50      A.CB_DY_60_CTR            AS CB_DAY_60_COUNTER,
15:22:13  51      A.CB_DY_90_CTR            AS CB_DAY_90_COUNTER,
15:22:13  52      A.CB_DEROG_CTR            AS CB_DEROG_COUNTER ,
15:22:13  53      A.CB_SCORE_FACTOR_CD        AS CB_SCORE_FACTOR_CODE ,
15:22:13  54      A.CB_SCORE_FACTOR_CD_1        AS CB_SCORE_FACTOR_CODE_1 ,
15:22:13  55      A.CB_SCORE_FACTOR_CD_2        AS CB_SCORE_FACTOR_CODE_2 ,
15:22:13  56      A.CB_SCORE_FACTOR_CD_3        AS CB_SCORE_FACTOR_CODE_3 ,
15:22:13  57      A.DEC_DFS_CUST_FL        AS DEC_DFS_CUSTOMER_FLAG,
15:22:13  58      A.DEC_DCSN            AS DEC_DECISION ,
15:22:13  59      A.DEC_DCSN_DT           AS DEC_DECISION_DATE,
15:22:13  60      A.DEC_CRD_LIMIT         AS DEC_CREDIT_LIMIT,
15:22:13  61      A.DEC_APR            AS    DEC_APR,
15:22:13  62      A.DEC_EXPLANATION_1       AS  DEC_EXPLANATION_1 ,
15:22:13  63      A.DEC_TRS_CUST_FOUN        AS DEC_TRS_CUSTOMER_FOUN ,
15:22:13  64      A.DEC_LST_TRS_DEC_DT       AS DEC_LAST_TRS_DEC_DATE,
15:22:13  65      A.DEC_LST_TRS_DCSN        AS DEC_LAST_TRS_DECISION ,
15:22:13  66      A.DEC_LMS_CUST_FOUN        AS DEC_LMS_CUSTOMER_FOUN,
15:22:13  67      A.DEC_LST_LMS_DEC_DT       AS DEC_LAST_LMS_DEC_DATE,
15:22:13  68      A.DEC_LST_LMS_DCSN        AS DEC_LAST_LMS_DECISION,
15:22:13  69      A.DEC_LMS_CRD_ON_HOL        AS DEC_LMS_CREDIT_ON_HOL,
15:22:13  70      A.DEC_LMS_LST_DECSTATU        AS DEC_LMS_LAST_DECSTATU ,
15:22:13  71      A.DEC_LMS_LST_DEC_CRED        AS DEC_LMS_LAST_DEC_CRED,
15:22:13  72      A.COMPANY_NBR            AS COMPANY_NUMBER,
15:22:13  73      A.LMS_LEASE_QUOTE_NBRBE     AS LMS_LEASE_QUOTE_NUMBE,
15:22:13  74      A.LMS_LEASE_CREATE_DT       AS LMS_LEASE_CREATE_DATE ,
15:22:13  75      A.SALES_REP_NM            AS SALES_REP_NAME ,
15:22:13  76      A.ORD_AMT            AS ORDER_AMOUNT,
15:22:13  77      A.DOMS_ORD_NBR            AS DOMS_ORDER_NUMBER,
15:22:13  78      A.CUST_DFS_CUST_NO_1        AS CUST_DFS_CUSTOMER_NO_1,
15:22:13  79      A.CUST_DFS_CUST_NO_2        AS CUST_DFS_CUSTOMER_NO_2 ,
15:22:13  80      A.CUST_DFS_CUST_NO_3        AS CUST_DFS_CUSTOMER_NO_3,
15:22:13  81      A.CUST_DFS_CUST_NO_4        AS CUST_DFS_CUSTOMER_NO_4,
15:22:13  82      A.CUST_DELL_CUST_NO_1        AS CUST_DELL_CUSTOMER_NO_1,
15:22:13  83      A.CUST_DELL_CUST_NO_2        AS CUST_DELL_CUSTOMER_NO_2,
15:22:13  84      A.CUST_DELL_CUST_NO_3        AS CUST_DELL_CUSTOMER_NO_3,
15:22:13  85      A.CUST_DELL_CUST_NO_4        AS CUST_DELL_CUSTOMER_NO_4,
15:22:13  86      A.ORIG                AS ORIGIN ,
15:22:13  87      A.DCSN_CD_1            AS DECISION_CODE_1 ,
15:22:13  88      A.DCSN_CD_2            AS DECISION_CODE_2 ,
15:22:13  89      A.DCSN_CD_3            AS DECISION_CODE_3    ,
15:22:13  90      A.DCSN_CD_4            AS DECISION_CODE_4 ,
15:22:13  91      A.MATRIX_CD            AS MATRIX_CODE,
15:22:13  92      A.MATRIX_CD_CMPRSN        AS MATRIX_CODE_COMPARISO ,
15:22:13  93      A.BK_SCORE            AS BK_SCORE ,
15:22:13  94      A.CB_TYP_CB_RPT         AS CB_TYPE_CB_REPORT,
15:22:13  95      A.LMS_CRD_USED            AS LMS_CREDIT_USED ,
15:22:13  96      A.CB_FRAUDSHIELD_SCORE_1    AS CB_FRAUDSHIELD_SCOR_1,
15:22:13  97      A.CB_FRAUDSHIELD_SCORE_2    AS CB_FRAUDSHIELD_SCOR_2 ,
15:22:13  98      A.CB_FRAUDSHIELD_SCORE_3    AS CB_FRAUDSHIELD_SCORE_3 ,
15:22:13  99      A.CB_FRAUDSHIELD_SCORE_4    AS CB_FRAUDSHIELD_SCORE_4 ,
15:22:13 100      A.BK_SC_FACTOR_CD_1        AS BK_SC_FACTOR_CODE_1,
15:22:13 101      A.BK_SC_FACTOR_CD_2        AS BK_SC_FACTOR_CODE_2 ,
15:22:13 102      A.BK_SC_FACTOR_CD_3        AS BK_SC_FACTOR_CODE_3,
15:22:13 103      A.BK_SC_FACTOR_CD_4        AS BK_SC_FACTOR_CODE_4 ,
15:22:13 104      A.FS_SCORE            AS FS_SCORE ,
15:22:13 105      A.FS_SC_FACTOR_CD_1        AS FS_SC_FACTOR_CODE_1  ,
15:22:13 106      A.FS_SC_FACTOR_CD_2        AS FS_SC_FACTOR_CODE_2,
15:22:13 107      A.FS_SC_FACTOR_CD_3        AS FS_SC_FACTOR_CODE_3,
15:22:13 108      A.FS_SC_FACTOR_CD_4        AS FS_SC_FACTOR_CODE_4,
15:22:13 109      A.AU_INWALLET_SCORE        AS AU_INWALLET_SCORE,
15:22:13 110      A.AU_ACPT_REFER_CD        AS AU_ACCEPT_REFER_CODE,
15:22:13 111      A.LMS_NEG_FILE_ACTN        AS LMS_NEG_FILE_ACTION,
15:22:13 112      A.LMS_60_DY_ACTN        AS LMS_60_DAY_ACTION ,
15:22:13 113      A.LMS_REF_ACTN            AS LMS_REF_ACTION,
15:22:13 114      A.FRST_BUREAU            AS FIRST_BUREAU,
15:22:13 115      A.EQ_CONNECTION_ERR        AS EQ_CONNECTION_ERROR,
15:22:13 116      A.EQ_PROC_ERR_CD        AS EQ_PROC_ERROR_CODE,
15:22:13 117      A.CB_BEACON            AS CB_BEACON ,
15:22:13 118      A.CB_REAS1            AS CB_REAS1   ,
15:22:13 119      A.CB_REAS2            AS CB_REAS2 ,
15:22:13 120      A.CB_REAS3            AS CB_REAS3,
15:22:13 121      A.CB_REAS4            AS CB_REAS4 ,
15:22:13 122      A.TR_DECL_SRC            AS TR_DECL_SOURCE ,
15:22:13 123      A.FPDSC_SCORECARD_TOT        AS FPDSC_SCORECARD_TOTAL,
15:22:13 124      A.FRAUD_OVERRIDE_RSN        AS FRAUD_OVERRIDE_REASON,
15:22:13 125      A.CHNL                AS CHANNEL,
15:22:13 126      A.OVERRIDE_RSN_CD        AS OVERRIDE_REASON_CODE,
15:22:13 127      A.UPDT_DT           AS UPDATE_DATE,
15:22:13 128      A.UPDT_NM            AS UPDATE_NAME ,
15:22:13 129      A.INS_DT           AS INSERT_DATE ,
15:22:13 130      A.INS_NM           AS INSERT_NAME
15:22:13 131  from ( select DW_DHS_DAILY_EXTR_HST.*,
15:22:13 132                row_number() over ( partition by APP_AUTOMATIC_NBR
15:22:13 133                        order by INS_DT DESC nulls last ) rn
15:22:13 134           FROM DW_DHS_DAILY_EXTR_HST ) A
15:22:13 135  where rn = 1
15:22:13 136  /
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=255560 Card=3703478
          Bytes=3066479784)

   1    0   VIEW* (Cost=255560 Card=3703478 Bytes=3066479784)          :Q347700
                                                                       1

   2    1     WINDOW* (SORT)                                           :Q347700
                                                                       1

   3    2       TABLE ACCESS* (FULL) OF 'DW_DHS_DAILY_EXTR_HST' (Cost= :Q347700
          8146 Card=3703478 Bytes=3129438910)                          0

   1 PARALLEL_TO_SERIAL            SELECT C2,C0,C4,C5,C6,C7,C8,C9,C10,C11,C12,C
                                   13,C14,C15,C16,C17,C18,C19,C20,C21,C

   2 PARALLEL_COMBINED_WITH_PARENT
   3 PARALLEL_TO_PARALLEL          SELECT /*+ Q3477000 NO_EXPAND ROWID(A1) */ A
                                   1."APP_AUTOMATIC_NBR" C0,A1."INS_DT"

15:22:13 SQL>
15:22:13 SQL>
15:22:13 SQL>
15:22:13 SQL> SELECT
15:22:13   2      A.DEC_APP_STT  AS DEC_APP_STATUS,
15:22:13   3      A.APP_AUTOMATIC_NBR AS APP_AUTOMATIC_NUMBER,
15:22:13   4      A.APP_APPLCTN_DT AS APP_APPLICATION_DATE,
15:22:13   5      A.APP_FRST_NM AS APP_FIRST_NAME ,
15:22:13   6      A.APP_LST_NM AS APP_LAST_NAME ,
15:22:13   7      A.APP_MID_INIT AS APP_MIDDLE_INITIAL,
15:22:13   8      A.APP_DT_OF_BRTH AS APP_DATE_OF_BIRTH ,
15:22:13   9      A.APP_SOC_SEC AS APP_SOCIAL_SECURITY ,
15:22:13  10      A.APP_ADDR_NBR AS APP_ADDRESS_NUMBER ,
15:22:13  11      A.APP_ADDR_STR AS APP_ADDRESS_STREET,
15:22:13  12      A.APP_STR_TYP AS APP_STREET_TYPE ,
15:22:13  13      A.APP_UNT_NBR AS APP_UNIT_NO ,
15:22:13  14      A.APP_FULL_ADDR_CB AS APP_FULL_ADDRESS_CB ,
15:22:13  15      A.APP_CTY AS APP_CITY,
15:22:13  16      A.APP_ST AS APP_STATE,
15:22:13  17      A.APP_ZIP  AS APP_ZIP ,
15:22:13  18      A.APP_ZIP_COMPLM  AS APP_ZIP_COMPLEMENT,
15:22:13  19      A.APP_RENT_OWNR         AS APP_RENT_OWNER,
15:22:13  20      A.APP_TM_AT_RSDNC        AS APP_TIME_AT_RESIDENCE ,
15:22:13  21      A.APP_HOME_AREA_CD        AS APP_HOME_AREA_CODE ,
15:22:13  22      A.APP_HOME_PH            AS APP_HOME_PHONE ,
15:22:13  23      A.APP_MOTHERS_MAIDEN_NM     AS APP_MOTHER_S_MAIDEN_N,
15:22:13  24      A.APP_ID_TYP            AS APP_ID_TYPE,
15:22:13  25      A.APP_ID_ST            AS APP_ID_STATE,
15:22:13  26      A.APP_ID_NBR            AS APP_ID_NO,
15:22:13  27      A.APP_EMPLR_NM            AS APP_EMPLOYER_NAME,
15:22:13  28      A.APP_POS_CD            AS APP_POSITION_CODE,
15:22:13  29      A.APP_TM_AT_JOB         AS APP_TIME_AT_JOB ,
15:22:13  30      A.APP_WRK_AREA_CD        AS APP_WORK_AREA_CODE,
15:22:13  31      A.APP_WRK_PH_NBR        AS APP_WORK_PHONE_NUMBER,
15:22:13  32      A.APP_E_MAIL_ADDR        AS APP_E_MAIL_ADDRESS,
15:22:13  33      A.CB_RPTDT           AS CB_REPORTDATE,
15:22:13  34      A.CB_MOD_TYP            AS CB_MODEL_TYPE ,
15:22:13  35      A.CB_RSKSCORE            AS CB_RISKSCORE ,
15:22:13  36      A.CB_FICO_SCORE         AS CB_FICO_SCORE ,
15:22:13  37      A.CB_FRAUDSHIELD_SCORE        AS CB_FRAUDSHIELD_SCORE,
15:22:13  38      A.CB_PUBLC_RECS_CNT        AS CB_PUBLIC_RECORDSCOUN,
15:22:13  39      A.CB_INSTLMNT_BAL        AS CB_INSTALLMENT_BALANC,
15:22:13  40      A.CB_REVOLVING_BAL        AS CB_REVOLVING_BALANCE,
15:22:13  41      A.CB_PAST_DUE_AMT        AS CB_PAST_DUE_AMOUNT,
15:22:13  42      A.CB_MTHLY_PMT            AS CB_MONTHLY_PAYMENT,
15:22:13  43      A.CB_REVOLV_AVAIL_PERC        AS CB_REVOLV_AVAIL_PERC,
15:22:13  44      A.CB_TOT_TRD_CNT        AS CB_TOTAL_TRADE_COUN,
15:22:13  45      A.CB_SATISF_ACC_CTR        AS CB_SATISF_ACC_COUNTER,
15:22:13  46      A.CB_NOW_DELINQ_DEROG_C     AS CB_NOW_DELINQ_DEROG_C,
15:22:13  47      A.CB_WAS_DELINQ_DEROG_C     AS CB_WAS_DELINQ_DEROG_C ,
15:22:13  48      A.CB_OLDEST_TRD_DT       AS CB_OLDEST_TRADE_DATE,
15:22:13  49      A.CB_DY_30_CTR            AS CB_DAY_30_COUNTER,
15:22:13  50      A.CB_DY_60_CTR            AS CB_DAY_60_COUNTER,
15:22:13  51      A.CB_DY_90_CTR            AS CB_DAY_90_COUNTER,
15:22:13  52      A.CB_DEROG_CTR            AS CB_DEROG_COUNTER ,
15:22:13  53      A.CB_SCORE_FACTOR_CD        AS CB_SCORE_FACTOR_CODE ,
15:22:13  54      A.CB_SCORE_FACTOR_CD_1        AS CB_SCORE_FACTOR_CODE_1 ,
15:22:13  55      A.CB_SCORE_FACTOR_CD_2        AS CB_SCORE_FACTOR_CODE_2 ,
15:22:13  56      A.CB_SCORE_FACTOR_CD_3        AS CB_SCORE_FACTOR_CODE_3 ,
15:22:13  57      A.DEC_DFS_CUST_FL        AS DEC_DFS_CUSTOMER_FLAG,
15:22:13  58      A.DEC_DCSN            AS DEC_DECISION ,
15:22:13  59      A.DEC_DCSN_DT           AS DEC_DECISION_DATE,
15:22:13  60      A.DEC_CRD_LIMIT         AS DEC_CREDIT_LIMIT,
15:22:13  61      A.DEC_APR            AS    DEC_APR,
15:22:13  62      A.DEC_EXPLANATION_1       AS  DEC_EXPLANATION_1 ,
15:22:13  63      A.DEC_TRS_CUST_FOUN        AS DEC_TRS_CUSTOMER_FOUN ,
15:22:13  64      A.DEC_LST_TRS_DEC_DT       AS DEC_LAST_TRS_DEC_DATE,
15:22:13  65      A.DEC_LST_TRS_DCSN        AS DEC_LAST_TRS_DECISION ,
15:22:13  66      A.DEC_LMS_CUST_FOUN        AS DEC_LMS_CUSTOMER_FOUN,
15:22:13  67      A.DEC_LST_LMS_DEC_DT       AS DEC_LAST_LMS_DEC_DATE,
15:22:13  68      A.DEC_LST_LMS_DCSN        AS DEC_LAST_LMS_DECISION,
15:22:13  69      A.DEC_LMS_CRD_ON_HOL        AS DEC_LMS_CREDIT_ON_HOL,
15:22:13  70      A.DEC_LMS_LST_DECSTATU        AS DEC_LMS_LAST_DECSTATU ,
15:22:13  71      A.DEC_LMS_LST_DEC_CRED        AS DEC_LMS_LAST_DEC_CRED,
15:22:13  72      A.COMPANY_NBR            AS COMPANY_NUMBER,
15:22:13  73      A.LMS_LEASE_QUOTE_NBRBE     AS LMS_LEASE_QUOTE_NUMBE,
15:22:13  74      A.LMS_LEASE_CREATE_DT       AS LMS_LEASE_CREATE_DATE ,
15:22:13  75      A.SALES_REP_NM            AS SALES_REP_NAME ,
15:22:13  76      A.ORD_AMT            AS ORDER_AMOUNT,
15:22:13  77      A.DOMS_ORD_NBR            AS DOMS_ORDER_NUMBER,
15:22:13  78      A.CUST_DFS_CUST_NO_1        AS CUST_DFS_CUSTOMER_NO_1,
15:22:13  79      A.CUST_DFS_CUST_NO_2        AS CUST_DFS_CUSTOMER_NO_2 ,
15:22:13  80      A.CUST_DFS_CUST_NO_3        AS CUST_DFS_CUSTOMER_NO_3,
15:22:13  81      A.CUST_DFS_CUST_NO_4        AS CUST_DFS_CUSTOMER_NO_4,
15:22:13  82      A.CUST_DELL_CUST_NO_1        AS CUST_DELL_CUSTOMER_NO_1,
15:22:13  83      A.CUST_DELL_CUST_NO_2        AS CUST_DELL_CUSTOMER_NO_2,
15:22:13  84      A.CUST_DELL_CUST_NO_3        AS CUST_DELL_CUSTOMER_NO_3,
15:22:13  85      A.CUST_DELL_CUST_NO_4        AS CUST_DELL_CUSTOMER_NO_4,
15:22:13  86      A.ORIG                AS ORIGIN ,
15:22:13  87      A.DCSN_CD_1            AS DECISION_CODE_1 ,
15:22:13  88      A.DCSN_CD_2            AS DECISION_CODE_2 ,
15:22:13  89      A.DCSN_CD_3            AS DECISION_CODE_3    ,
15:22:13  90      A.DCSN_CD_4            AS DECISION_CODE_4 ,
15:22:13  91      A.MATRIX_CD            AS MATRIX_CODE,
15:22:13  92      A.MATRIX_CD_CMPRSN        AS MATRIX_CODE_COMPARISO ,
15:22:13  93      A.BK_SCORE            AS BK_SCORE ,
15:22:13  94      A.CB_TYP_CB_RPT         AS CB_TYPE_CB_REPORT,
15:22:13  95      A.LMS_CRD_USED            AS LMS_CREDIT_USED ,
15:22:13  96      A.CB_FRAUDSHIELD_SCORE_1    AS CB_FRAUDSHIELD_SCOR_1,
15:22:13  97      A.CB_FRAUDSHIELD_SCORE_2    AS CB_FRAUDSHIELD_SCOR_2 ,
15:22:13  98      A.CB_FRAUDSHIELD_SCORE_3    AS CB_FRAUDSHIELD_SCORE_3 ,
15:22:13  99      A.CB_FRAUDSHIELD_SCORE_4    AS CB_FRAUDSHIELD_SCORE_4 ,
15:22:13 100      A.BK_SC_FACTOR_CD_1        AS BK_SC_FACTOR_CODE_1,
15:22:13 101      A.BK_SC_FACTOR_CD_2        AS BK_SC_FACTOR_CODE_2 ,
15:22:13 102      A.BK_SC_FACTOR_CD_3        AS BK_SC_FACTOR_CODE_3,
15:22:13 103      A.BK_SC_FACTOR_CD_4        AS BK_SC_FACTOR_CODE_4 ,
15:22:13 104      A.FS_SCORE            AS FS_SCORE ,
15:22:13 105      A.FS_SC_FACTOR_CD_1        AS FS_SC_FACTOR_CODE_1  ,
15:22:13 106      A.FS_SC_FACTOR_CD_2        AS FS_SC_FACTOR_CODE_2,
15:22:13 107      A.FS_SC_FACTOR_CD_3        AS FS_SC_FACTOR_CODE_3,
15:22:13 108      A.FS_SC_FACTOR_CD_4        AS FS_SC_FACTOR_CODE_4,
15:22:13 109      A.AU_INWALLET_SCORE        AS AU_INWALLET_SCORE,
15:22:13 110      A.AU_ACPT_REFER_CD        AS AU_ACCEPT_REFER_CODE,
15:22:13 111      A.LMS_NEG_FILE_ACTN        AS LMS_NEG_FILE_ACTION,
15:22:13 112      A.LMS_60_DY_ACTN        AS LMS_60_DAY_ACTION ,
15:22:13 113      A.LMS_REF_ACTN            AS LMS_REF_ACTION,
15:22:13 114      A.FRST_BUREAU            AS FIRST_BUREAU,
15:22:13 115      A.EQ_CONNECTION_ERR        AS EQ_CONNECTION_ERROR,
15:22:13 116      A.EQ_PROC_ERR_CD        AS EQ_PROC_ERROR_CODE,
15:22:13 117      A.CB_BEACON            AS CB_BEACON ,
15:22:13 118      A.CB_REAS1            AS CB_REAS1   ,
15:22:13 119      A.CB_REAS2            AS CB_REAS2 ,
15:22:13 120      A.CB_REAS3            AS CB_REAS3,
15:22:13 121      A.CB_REAS4            AS CB_REAS4 ,
15:22:13 122      A.TR_DECL_SRC            AS TR_DECL_SOURCE ,
15:22:13 123      A.FPDSC_SCORECARD_TOT        AS FPDSC_SCORECARD_TOTAL,
15:22:13 124      A.FRAUD_OVERRIDE_RSN        AS FRAUD_OVERRIDE_REASON,
15:22:13 125      A.CHNL                AS CHANNEL,
15:22:13 126      A.OVERRIDE_RSN_CD        AS OVERRIDE_REASON_CODE,
15:22:13 127      A.UPDT_DT           AS UPDATE_DATE,
15:22:13 128      A.UPDT_NM            AS UPDATE_NAME ,
15:22:13 129      A.INS_DT           AS INSERT_DATE ,
15:22:13 130      A.INS_NM           AS INSERT_NAME
15:22:13 131      FROM DW_DHS_DAILY_EXTR_HST A
15:22:13 132      WHERE A.INS_DT = (SELECT MAX(B.INS_DT)
15:22:13 133                     FROM DW_DHS_DAILY_EXTR_HST B
15:22:13 134                     WHERE A.APP_AUTOMATIC_NBR =
B.APP_AUTOMATIC_NBR)
15:22:13 135  /
Elapsed: 00:00:00.16

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=8146 Card=42569 Byte
          s=35970805)

   1    0   FILTER
   2    1     TABLE ACCESS* (FULL) OF 'DW_DHS_DAILY_EXTR_HST' (Cost=81 :Q347900
          46 Card=42569 Bytes=35970805)                                0

   3    1     SORT (AGGREGATE)
   4    3       TABLE ACCESS (BY INDEX ROWID) OF 'DW_DHS_DAILY_EXTR_HS
          T' (Cost=5 Card=2 Bytes=32)

   5    4         INDEX (RANGE SCAN) OF 'DW_DHS_EXTR_HST_APP_AUT_NBR'
          (NON-UNIQUE) (Cost=3 Card=2)

   2 PARALLEL_TO_SERIAL            SELECT /*+ Q3479000 NO_EXPAND ROWID(A1) */ A
                                   1."DEC_APP_STT",A1."APP_AUTOMATIC_NB

15:22:13 SQL>
15:22:13 SQL> spool off

Followup:
and what is the runtime.

Rating: 3
it is so fast October 04, 2002
Reviewer: tammy pickett from USA
Hi Tom: I did run the query you provide:
from ( select DW_DHS_DAILY_EXTR_HST.*,
                  row_number() over ( partition by APP_AUTOMATIC_NBR
                                      order by INS_DT DESC nulls last ) rn
             FROM DW_DHS_DAILY_EXTR_HST ) A
  where rn = 1
/

it took 24 minutes to create view contains  2352155 rows;
the original query:
FROM DW_DHS_DAILY_EXTR_HST A
  WHERE A.INS_DT = (SELECT MAX(B.INS_DT)
                         FROM DW_DHS_DAILY_EXTR_HST B
                         WHERE A.APP_AUTOMATIC_NBR = B.APP_AUTOMATIC_NBR)
/
it took about 2 hour and 20 minutes.

Followup:
Well then, it is the right answer ;)

You cannot compare the costs of two different queries.  The cost is used WITHIN
a query to compare the alternative plans for A QUERY to find the one that should
be used for THAT QUERY.

These are two wholly different queries.

Rating: 3
different result October 04, 2002
Reviewer: A reader
Tom: there is some difference between first select statement and the one you
wrote:

SQL> select count(*) from DW_CUR_HST_DHS_DLY_TP;-- this is the improved query
result

  COUNT(*)
----------
   2352155

SQL> select count (*) from DW_CUR_HST_DHS_DLY;-- this is the original query
result

  COUNT(*)
----------
   2678051
not sure why??

Followup:
The one with row_number is getting the first record for each
app_automatic_number, your original gets one or more records for each.  Sorry
about that -- use DENSE_RANK() instead of ROW_NUMBER() -- thats the only change
you need to make.

Rating: 3
What Type of Query & Indexing is best March 05, 2003
Reviewer: A reader
Hi tom,
If we have a table on DW with historrical data which would be the best query and
what type of indexing should be used.

Query currently being used
SELECT *
FROM    CUSTOMERS
WHERE (ID,START_DATE) IN  (SELECT ID,MAX(START_DATE)
                           FROM CUSTOMERS
                   WHERE  START_DATE <= TO_DATE(SYSDATE)
                           AND    (END_DATE IS NULL
                           OR      END_DATE > TO_DATE(SYSDATE))
GROUP BY ID)

sometimes there maybe more than 1 column which uniquely identify the unique
record foe eg many queries

Followup:
NEVER to_date a DATE -- do you know what that does, here is the short list:

o puts a bug in your code
o adds overhead to everything you do

You are taking something that is already a DATE -- turning it into a string --
then turning it back into a date.  if you want to lose the time component, use a
function that works on a date already -- TRUNC().

the optimal query will depend on many factors.  do you commonly select ALL of
the rows from this table?  If so, a materialized view would be very appropriate
here -- full refreshed once a day.  that way, it becomes a trivial "select *
from t"

March 05, 2003 Jenny -- Thanks for the question regarding "Script to generate create tablespace...", version 8.1.7
You Asked
Tom,

I'm trying to collect DDLs for tablespaces within a database.  Instead of using
OEM and do copy and paste to another file, I was wondering if you happen to have
a script that you can share with me that would generate "CREATE TABLESPACE ... "
ddls?

Thanks,
Jenny
and we said...
exp userid=u/p full=y rows=n

you can ctl-c that right after it says "exporting profiles", for example:

$ exp userid=/ full=y rows=n

Export: Release 8.1.7.4.0 - Production on Wed Mar 5 09:22:20 2003
(c) Copyright 2000 Oracle Corporation.  All rights reserved.
Connected to: Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production
Export done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set
Note: table data (rows) will not be exported

About to export the entire database ...
. exporting tablespace definitions
. exporting profiles
. exporting user definitions
. exporting roles
. exporting resource costs
. exporting rollback segment definitions^C
EXP-00008: ORACLE error 1013 encountered
ORA-01013: user requested cancel of current operation
EXP-00000: Export terminated unsuccessfully

And then:

imp userid=u/p full=y show=y

will show the create tablespace commands.  They will be "wrapped" and in need of
fixing.  If you are using UNIX, strings works nicely here:

$ strings expdat.dmp | grep 'CREATE TABLESPACE'
CREATE TABLESPACE "RBS_TS_01" DATAFILE  '/d01/oradata/ora817dev/rbs_ts_01.dbf'
SIZE 156344320 REUSE AUTOEXTEND ON NEXT 1048576  MAXSIZE 2000M EXTENT MANAGEMENT
LOCAL  UNIFORM SIZE 524288 ONLINE PERMANENT
CREATE TABLESPACE "RBS_TS_02" DATAFILE  '/d02/oradata/ora817dev/rbs_ts_02.dbf'
SIZE 665952256 REUSE AUTOEXTEND ON NEXT 1048576  MAXSIZE 2000M EXTENT MANAGEMENT
LOCAL  UNIFORM SIZE 524288 ONLINE PERMANENT
CREATE TABLESPACE "RBS_TS_03" DATAFILE  '/d03/oradata/ora817dev/rbs_ts_03.dbf'
SIZE 200384512 REUSE AUTOEXTEND ON NEXT 1048576  MAXSIZE 2000M EXTENT MANAGEMENT
LOCAL  UNIFORM SIZE 524288 ONLINE PERMANENT
CREATE TABLESPACE "RBS_TS_04" DATAFILE  '/d04/oradata/ora817dev/rbs_ts_04.dbf'
SIZE 48340992 REUSE AUTOEXTEND ON NEXT 1048576  MAXSIZE 2000M EXTENT MANAGEMENT
LOCAL  UNIFORM SIZE 524288 ONLINE PERMANENT
CREATE TABLESPACE "RBS_TS_05" DATAFILE  '/d01/oradata/ora817dev/rbs_ts_05.dbf'
SIZE 26320896 REUSE AUTOEXTEND ON NEXT 1048576  MAXSIZE 2000M EXTENT MANAGEMENT
LOCAL  UNIFORM SIZE 524288 ONLINE PERMANENT
CREATE TABLESPACE "USERS" DATAFILE  '/d04/oradata/ora817dev/users.dbf' SIZE
1553M REUSE AUTOEXTEND ON NEXT 524288  MAXSIZE 2000M EXTENT MANAGEMENT LOCAL
UNIFORM SIZE 524288 ONLINE PERMANENT  NOLOGGING
CREATE TABLESPACE "DRSYS" DATAFILE  '/d04/oradata/ora817dev/drsys.dbf' SIZE
36700160 REUSE AUTOEXTEND ON NEXT 524288  MAXSIZE 2000M EXTENT MANAGEMENT LOCAL
UNIFORM SIZE 524288 ONLINE PERMANENT
CREATE TABLESPACE "CLAMS" DATAFILE  '/d03/oradata/ora817dev/clams.dbf' SIZE
78118912 REUSE AUTOEXTEND ON NEXT 524288  MAXSIZE 2000M EXTENT MANAGEMENT LOCAL
UNIFORM SIZE 524288
Review & Followup
Rating: 4
Also can be done using dynemic sql March 05, 2003
Reviewer: A Reader from New York, NY
By joining dba_tablespaces and dba_data_files you can easily create a spool file
which will dump create statements for tablespaces.
It may take some efforts to write sql but once you have it you can reuse quiet
easily.

Rating: 5
That's exactly what I was looking for! March 05, 2003
Reviewer: Jenny
Thank you!!!

March 05, 2003 -- Thanks for the question regarding "strange ORA-01652 error", version 9.0.1
You Asked
Hi, Tom,

I got this error message when I tried to rebuild an index.
ORA-01652: unable to extend temp segment by 1024 in tablespace INDX
I have INDX tablespace to hold all indexes. INDX tablespace
is LMT, PERMENT, LOGGING.
I also have TEMP tablespace as default temporary tablespace.
I don't understand why Oracle is trying to extend temp segment on
a perment tablespace INDEX instead of TEMP where the sorting happens?

Thanks.
and we said...
Oracle will put the newly rebuilt index structure into a TEMP segment in the
tablespace where the index is going to go.  This is so that if the system just
crashed right in the middle, when we restart -- SMON will be kind enough to seek
out these orphan TEMP segments and get rid of them.

Upon completion of the index rebuild -- Oracle will convert this TEMP segment
into a permanent segment, remove the old index segment and let this one take
over.

So, this TEMP segment is really your newly rebuilt index as it is rebuilding.

You did not have enough space for both the old and new index in that tablespace
is all.
March 05, 2003 Olga -- Thanks for the question regarding "read only snapshots for insert DML only", version 8174
You Asked
Is there a way to trick simple replication , so it will replicate only inserts
thank you
Olga
and we said...
Nope.  Replication is pretty finicky about actually replicating data -- it'll
replicate the modifications as well as deletes.  Its only goal in life is to
replicate exact copies.

Oracle Streams in Oracle9iR2 can do what you want however
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-STR
Review
      & Followup

      Rating: 3
Manually
      replicating   March 06, 2003
Reviewer:  Nimesh
      Desai  from India
Hi Tom, Olga
May be this could be an alternative
I had done manually with triggers and replication procedures.
Steps
1>  Create m$$log for the table to be replicated in primary site.
2> Create a trigger(Insert) which inserts a row in m$$log table (  rowid ,
timestamp, primary key  and all the columns )
under normal replication procedure dml_Type (I,D,U) would  be stored and apart
from primary key no  table data would be stored
3> write a procedure which reads from m$$log table order by timestamp  and
accordingly insert into required secondary database via dblinks. After
successful execution of above procedure delete all entries in m$$log table.
5> Error handling routines can be developed for each row in m$$log table to  set
completion flag , error desc with timestamp of execution.
6> refresh at regular time intervals ie  call the procedure using oracle or OS
scheduler.

7> To completely replicate a Table ,trigger should capture all inserts updates
and deletes.
8> procedure must first start with delete and while processing it must delete
all prior  entries of (I,U) in m$$log table.
9> read  updates and delete all prior entries of inserts in m$$log table and
update ( one can maintain history) in secondary
10> Read inserts and insert in
Note: In OLTP environment 2 inserts can happen in  milliseconds. only in oracle
9.2 it stores 1/10the of  second .so there can be many rows at a same time for a
given oracle date column.
There can be scenario where a row is inserted deleted and new row is inserted
which gets the same rowid as prior inserts. so use primary key & timestamp with
rowid.
For complete replication call the proc recursively till all the rows in m$$log
are executed.

For normal replication I did use oracle snapshots but for certain tables we
required audit and past history so had to develop replication procs. THe above
procs were developed in oracle 7.3.3, so may be in 9i it can be done is better
way.

March 05, 2003 Keanu -- Thanks for the question regarding
      "Database Backup Method", version 8.1.5 to 9i

      You Asked
Hi Tom:

Thank you Tom for providing such a great site for all oracle user to share our
knowledge here.

I am providing some database backup choices to my customer.
For NOARCHIVELOG and ARCHIVELOG mode database, Windows/Unix, I would suggest to
use RMAN to backup the database. I think the RMAN is easy to use than phyiscal
OS copy method. Beside RMAN, a recovery catalog is used. Since all backup
history can be kept.

IF the Hardware of the database failure e.g. the PC/Unix crash. Any method to
recover the database in the shortest time.

1. Is RMAN a good choice to choose ?

2. Clone databae is a good backup method for hardware crash ? As I don't know
much about clone database.

Thank you, Tom

      and we said...
1) yes.

2) "clone" is just a full backup+restore.  It is a backup.

You want your customers to use ARCHIVELOG mode unless they sign a piece of paper
saying "we fully understand that we WILL lose data someday and it is not your
fault".
Review
      & Followup

      Rating: 1
Database Backup
      Method  August 15, 2002
Reviewer:  Hamid
       from USA
Not sure I agree with RMAN being the 'best' solution for backup/restore.
'Catalog' piece is another Oracle instance, which needs to be maintained and can
go down just like any other database..!

RMAN may be a good choice for a small operation, however, I would not recommend
it for an enterprise enviornment. I have experience with RMAN/EMC/others, and I
personally prefer EMC solutions/OS backups. I think the restore time is faster
and the operation is clean and it works! Plus let UNIX admin take on some
responsibility he/she is there anyway :)

          Followup:

To each their own I suppose.  The catalog is the key feature of RMAN, not a
downside.

I personally wouldn't let a UNIX admin take responsibility for my databases
(small, med or large).  I've seen more than one "oh, you mean I cannot just copy
the files while its running -- I have to do something special" in my life --
usually right after we ask them to restore.

      Rating: 4
Backup using
      RMAN  August 15, 2002
Reviewer:  Keanu
       from HK
Tom, can you tell which backup method you are using ? I am still thinking which
backup method that we'll provide to our customer (OS method and RMAN method )

Can you provide me some link about clone database ? Is is the same as "duplicate
database"

          Followup:

clone = duplicate

We use rman internally here at Oracle, we use OS backups, we use everything
under the sun probably.  Me -- I don't actually backup databases, Fred does (and
currently Fred uses tar)

      Rating:
      5
Agreed...  August 16, 2002
Reviewer:
      Connor  from London
Being a "long termer" DBA and unix admin I was the ultimate RMAN cynic, and that
distrust was well warranted in 8.0.x!

But I recently worked on a site that was deploying their "Enterprise Backup
Solution", and they had decided on rman for all db's (8.1+).  With my sceptics
hat firmly in place, I struggled for days to make rman fail but it stood up to
the test.  We went through dozens of possible recovery scenarios and rman did
what was expected each time. I'm now an RMAN convert...its top notch

      Rating: 1
Database Backup
      Method   August 16, 2002
Reviewer:  Hamid
       from USA
If a UNIX admin does not know how to make backups then in my humble opinion
he/she is in the wrong line of work...!

          Followup:

Why?  Most unix admins I know don't know the database from a hole in the ground.
 They naively assume "its just files, let me copy them like I do for all other
backups" and away they go.

There are DBA's
There as SA's
In small shops there are SA's who are forced to be DBA's or DBA's forced to be
SA's.

They are two different people where I come from generally.

      Rating: 1
Backup
      Method  August 16, 2002
Reviewer:  Hamid
       from USA
Well, I do not agree with that, just like there are some overlaps with
DBA/developer role, I see an overlap with DBA/SA groups. This makes the 3 groups
developer/DBA/SA working together mandatory to the level that warrants smooth
operation of Databases.

I just find it easy to ask the Unix admin to restore files and apply the redo
logs for recovery. I have not had a problem yet. We have mapped out our mount
points for each database in detail and where files are located etc.

With RMAN I would have to come up a plan to backup RMAN also-i do not want to be
concerned about my 'backup media's failure'...!

With OS/EMC all this is not needed. This is my understanding unless i missed
some piece of the RMAN process.

          Followup:

Overlap yes, same jobs, no.  Restoring a file from a backup tape is quite
different from developing and implementing your backup and recovery plan.
Working together is HUGELY different from "SA backs up database".  It is more
like "SA runs scripts developed by DBA to backup database" or "SA uses piece of
software that is Oracle aware and nows how to put Oracle into hot backup mode
and do all of the book-keeping necessary".  Anyway...

So tell me -- with EMC/OS -- where is your recovery catalog?  (it's equivalent).
 Is it on a piece of paper?  Is it in your head.  Or -- do they have it in a
database on their equipment and you are just neglecting to back that up yourself
manually?

Think about it -- the recovery catalog is just book-keeping information.  All
backup tools *need to have book-keeping information*.  If you percieve that it
is "easier" with EMC cause you don't have to back up the book keeping info,
maybe you are just neglecting to back that up (and are in for a surprise
someday).

All backup stuff needs it.  With RMAN you don't HAVE to use a recovery catalog,
it is not mandatory.  We stuff the minimal needed info into the control files of
the database itself.  It just opens up lots of opportunities.

Tell me, when one 8k block goes bad in a 16 or 32gig datafile -- what will
EMC/OS backups do for you?  (nothing, you'll have to take the affected
tablespace offline, restore the entire backup file, apply all of the archived
redo log to the entire file to restore it).  With RMAN, take NOTHING offline --
restore just 8k of data and apply archives only to 8k of data.

Amongst other things.

      Rating: 5
Restoring
      datafiles backed up without begin backup  March 05, 2003

Reviewer:  David
Let me explain...

I usually use standard hot backup routines with begin/end backup in "my own
territory". They work pretty well -- no problem with them.

But one day, a big big big boss (from sky high) told me to fix the problem in an
SNOC (Security Network Operating Center) in another department.

To my surprise, their mission-critical Oracle crashed. They only had OS backups.
I remembered my old Oracle classes and the AskTom forum. I said... "Your backup
is UNUSABLE" (I trusted Tom, who said it would be)

But just in case, we tried to restore their datafiles from the OS backup. It
asked for some archives (there were archives btw).

Finally their batabase came up ok, and they looked at me like "Hey, didn't you
say the backup was unusable ?"

1) Is it possible that their database only *seemed*  to come up okay, but some
internal structures (fractured blocks, whatever) were no good -- but Oracle
couldn't spot them ? They might discover that some day in the future ?

2) What should I say instead of "Your database is UNUSABLE" so I wouldn't look
like a clown ? Maybe "You'd better not trust your backup, although it might come
up ok ?"

3) How can i know it "came up ok" ? Should I do some testing ? They may be
trusting a system that's corrupted ?

          Followup:

they got lucky.  you can get lucky.

1) no, it recovered.

2) I'd rather put the fear of whomever you believe in in everyone.

3) no, the recovery process should have taken care of all of that.

they got very very very lucky.  More often then not, it can go the other way.
I've seen it time and time and time again.

March 05, 2003 Elie -- Thanks for the question regarding "Using
      Inline Query In Update Statement", version 8.1.7

      You Asked
Hi, Tom.

I am attempting to update two columns in a table using an inline
query.  From what I've seen you do with such queries, I thought
I should be able to do this.  However, I am getting a syntax
error that I am unable to resolve.  I am hoping you would see
what I am doing wrong.  Here is my query and the error I am
getting when run in SQLPLUS.

  1  update client_revenue_summary a
  2  set (a.fsl_1996,
  3       a.ril_1996) =
  4  (select x1.fsl_1996_sum,
  5     x2.ril_1996_sum
  6     from
  7     (
  8             (select sum(b.fiscal_month_amt) fsl_1996_sum
  9                from client_revenue b
 10               where b.loc_id = a.loc_id
 11                 and b.fiscal_year = '1996'
 12                 and b.product_line_id = 1000537
 13             )
 14             where exists
 15             (select sum(b.fiscal_month_amt)
 16                from client_revenue b
 17               where b.loc_id = a.loc_id
 18                 and b.fiscal_year = '1996'
 19                 and b.product_line_id = 1000537
 20             )
 21     ) x1,
 22     (
 23             (select sum(b.fiscal_month_amt) ril_1996_sum
 24                from client_revenue b
 25               where b.loc_id = a.loc_id
 26                 and b.fiscal_year = '1996'
 27                 and b.product_line_id = 1000538
 28             )
 29             where exists
 30             (select sum(b.fiscal_month_amt)
 31                from client_revenue b
 32               where b.loc_id = a.loc_id
 33                 and b.fiscal_year = '1996'
 34                 and b.product_line_id = 1000538
 35             )
 36     ) x2,
 37* )
SQL> /
                where exists
                *
ERROR at line 14:
ORA-00907: missing right parenthesis

Thank you.

      and we said...
the open/close parens on lines 8, 13, 23, and 28 don't belong.

The "where exists" should be "and exists"

Actually -- after reading this -- i'm totally lost as to your logic.  I'm pretty
sure you want to do this:

update client_revenue_summary a
set (a.fsl_1996, a.ril_1996) =
    ( select sum(decode(product_line_id,1000537,fiscal_month_amt,0)),
             sum(decode(product_line_id,1000538,fiscal_month_amt,0))
        from client_revenue b
       where a.loc_id = b.loc_id
         and b.fiscal_year = '1996'
         and b.product_line_id in ( 1000537, 1000538 )
    )
/

and nothing more???
Review & Followup

      Rating: 5
Followup on
      "Using Inline Query In Update Statement"  September 08, 2002

Reviewer:  Elie Grunhaus  from Rochester, NY USA

Thanks very much, Tom, for the quick response.

The reason I included the "WHERE EXISTS" sections (and, in your response, you
corrected me by saying this should be "AND EXISTS") is to avoid rows not meeting
the "WHERE" criteria in the main SELECT being updated to "NULL".

However, I now see how you re-wrote my query without using the "EXISTS"
predicate and I believe will do the same intended UPDATE in a simpler, easier to
follow fashion.

I wonder which method (using "EXISTS" versus without) is faster in my
environment (Oracle 8.1.7 on solaris 8)?  I suppose I will have to check this
out.

Thank you very much.

          Followup:

But all rows in the main SELECT will always satisfy the WHERE criteria when the
subquery you are updating to includes an AGGREGATE without a group by!!!!!

select sum(1) from dual where 1=0;

that ALWAYS returns at least one and at most one row -- no matter HOW many rows
dual actually has.

No use of where exists here is beneficial or useful -- my update is the correct
one.

You only need to use where exists when the subquery in the set clause might
return 0 records and you don't want to update those rows. EG:

update T
   set x = ( select y from t2 where t2.x = t2.y )
 where exists ( select y from t2 where t2.x = t2.y )
/

makes sense, however:

update T
   set x = ( select sum(y) from t2 where t2.x = t2.y )
 where exists ( select sum(y) from t2 where t2.x = t2.y )
/

does not (however...

update T
   set x = ( select sum(y) from t2 where t2.x = t2.y )
 where exists ( select y from t2 where t2.x = t2.y )
/

might make sense... depends on your needs)

Best to understand what is really going on and why you sometimes want where
exists and sometimes it just wasted CPU cycles...

That and the where exists does not belong in the SUBQUERY -- it would belong in
the where clause of the UPDATE statement.

      Rating: 3
Something a
      little more complicated.  March 05, 2003
Reviewer:
      ken from toronto  from toronto Canada
Our code is like this:
declare
    cursor cur1 is
        select sql_dt from day
        where sql_dt >  to_date('01-MAR-2003','DD-MON-YYYY')
        and sql_dt <= (select max(src_extract_dt) from dw_run_history_aud where
load_status_cd = 'Completed')
        order by sql_dt;

begin
    for rec1 in cur1 loop
        update dr_foff_acf_day_pshassets_agg
           set avg_dsc_units_qty = (tot_dsc_units_qty +
                   (select avg_dsc_units_qty from dr_foff_acf_day_pshassets_agg
prev
                     where prev.accnt_dealer_rep_sid =
dr_foff_acf_day_pshassets_agg.accnt_dealer_rep_sid
                       and prev.fund_offering_sid =
dr_foff_acf_day_pshassets_agg.fund_offering_sid
                       and prev.pship_cd =
dr_foff_acf_day_pshassets_agg.pship_cd
                       and prev.vntg_sid =
dr_foff_acf_day_pshassets_agg.vntg_sid
                       and prev.bus_process_dt =
dr_foff_acf_day_pshassets_agg.bus_process_dt - 1
                       and prev.dw_load_ty_sid = 1)
                    *
(to_number(to_char(dr_foff_acf_day_pshassets_agg.bus_process_dt,'DD')) - 1) )
                    /
to_number(to_char(dr_foff_acf_day_pshassets_agg.bus_process_dt,'DD')) ,
               avg_dsc_mkt_val_amt = (tot_dsc_units_qty * navps_rt +
                   (select avg_dsc_mkt_val_amt from
dr_foff_acf_day_pshassets_agg prev
                     where prev.accnt_dealer_rep_sid =
dr_foff_acf_day_pshassets_agg.accnt_dealer_rep_sid
                       and prev.fund_offering_sid =
dr_foff_acf_day_pshassets_agg.fund_offering_sid
                       and prev.pship_cd =
dr_foff_acf_day_pshassets_agg.pship_cd
                       and prev.vntg_sid =
dr_foff_acf_day_pshassets_agg.vntg_sid
                       and prev.bus_process_dt =
dr_foff_acf_day_pshassets_agg.bus_process_dt - 1
                       and prev.dw_load_ty_sid = 1)
                    *
(to_number(to_char(dr_foff_acf_day_pshassets_agg.bus_process_dt,'DD')) - 1) )
                    /
to_number(to_char(dr_foff_acf_day_pshassets_agg.bus_process_dt,'DD'))
         where accnt_dealer_rep_sid = 3897709
           and fund_offering_sid = 1522
           and pship_cd = 'FID8'
           and vntg_sid in (53, 55)
           and bus_process_Dt = '04-MAR-2003'; --rec1.sql_dt ;
    end loop;
end;

And ORACLE is returning this error:
ERROR at line 12:
ORA-06550: line 12, column 21:
PLS-00103: Encountered the symbol "SELECT" when expecting one of the following:
( - + mod not null others <an identifier>
<a double-quoted delimited-identifier> <a bind variable> avg
count current exists max min prior sql stddev sum variance
execute forall time timestamp interval date
<a string literal with character set specification>
<a number> <a single-quoted SQL string>

It appears when adding to tot_dsc_units_qty with the value returned by the
subqyyert is causing the problem.

THe update by itself executed alright, but we got this error when it's an
anyomous block

          Followup:

Yes, it is cause plsql in 8i did not recognize scalar subqueries could go
anywhere an expression could (9i does)

 update dr_foff_acf_day_pshassets_agg
           set avg_dsc_units_qty = (tot_dsc_units_qty +
                  (select avg_dsc_units_qty from dr_foff_acf_day_pshassets_agg

is the same as:

 update dr_foff_acf_day_pshassets_agg XX
           set avg_dsc_units_qty =
                   (select xx.tot_dsc_units_qty + avg_dsc_units_qty
                     from dr_foff_acf_day_pshassets_agg prev
             ....

however and that should make it "happy"

March 06, 2003 Marc -- Thanks for the question regarding "using
      named notation versus call func(? ? ?) in calling stored procedures",
      version 8.1.7

      You Asked
Do U have a good reason not to use named notation versus positional notation
using(? ? ?) in calling a oracle store procedure using jdbc.

Example:
The Oracle Store Procedure:
myProc(fname, lname).

JDBC can be coded in 2 ways:
Named Notation:
cs = conn.prepareCall({call myProc(fname=>'john', lname=>'doe');
rs = cs.executeQuery();

Position way:
cs = conn.prepareCall({call myProc(? ?);
cs = setString(1, 'john');
cs = setString(2, 'doe');
rs = cs.executeQuery();

Does Oracle care which way the procedure is called?
Is there a performace hit for using name notation(Parse excution time and/or
bind variables?
Does JDBC have a performace hit?

I want to use setObject(String var_name,Object value) method in the callable
statment, but my app server does not have the latest version of jdbc e which the
set object is not supported.  If I used named notion it is easier to see the
code for easier debggugning of the sql.

Thanks!!!

      and we said...
the first way stinks.  It doesn't use this thing called a BIND VARIABLE and not
using BIND VARIABLES is the worst thing you could do as a developer.

The second way is the ONLY way -- however, for readability, there is no reason
why it should not be:

    CallableStatement cs =
       conn.prepareCall( "{call myProc(fname=>?, lname=> ?)}" );
    cs.setString(1, "john");
    cs.setString(2, "doe");
    cs.executeQuery();

If you do not use bind variables -- you will fail.
If you use bind variables -- you might succeed.

There is no reason you cannot use named notation with bind variables.
Review &
      Followup

      Rating: 3
Recent convert
      to named notation  March 06, 2003
Reviewer:
      A reader
Long ago I hated using named notation, the extra typing was just sooooo
outrageous.  However, I've recently been converted to always using named
notation (well nearly always :) ) and have to say that my codes readability has
gone up for it.  I've also noticed that sometimes you have to do it when doing
certain sorts of overloading, but anyways with the exception of *some* noddy get
and set routines I always use named notation. no longer do I have to describe a
routine to see what is being passed and my old favorite of passing 2 numbers to
a routine the wrong way round has disappeared

March 06, 2003 Prashant -- Thanks for the question regarding
      "Updating a view", version 9.2

      You Asked
Hi,

I am trying to update a view, actually just one really big table(fact) that is
part of the view.

The view consist of a one really big table (fact) and another inline view joined
together. The inline view does full outer join of two small tables. Because of
full outer join Oracle does not maintain the rowids, the update fails with
error:
ORA-01779: cannot modify a column which maps to a non key-preserved table

I only want to limit the rows for which it tries the update. By having a view,
the big table is accessed in nested loop over the small tables inline view. If I
put exists clause then it does a full on the big table, which is very slow.

Is there a option by which I can say that the inlinve view inside the view I am
trying to update is read only and Oracle should not worry about maintaining the
rowids for that, just the rowids for big table will be sufficient.

I tried "with read only" on the inline view, but with no success.

Any ideas?

Thanks,
Prashant Rane

      and we said...
It is not that the rowids are not preserved -- they are -- it is that Oracle
cannot tell if the table you are updating is key preserved or not.

It has nothing to do with outer joins.

It has everything to do with the fact that Oracle doesn't know what the primary
key of this inline view is.

Consider this example and tell me what the outcome of an update should be:

ops$tkyte@ORA920> create table emp
  2  as
  3  select * from scott.emp;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table your_inline_view
  2  ( empno int );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into your_inline_view
  2  select empno from emp
  3  where rownum = 1;

1 row created.

ops$tkyte@ORA920> insert into your_inline_view
  2  select * from your_inline_view;

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace view v
  2  as
  3  select emp.*
  4    from emp, your_inline_view
  5   where emp.empno = your_inline_view.empno
  6  /

View created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select empno, ename, sal from v;

     EMPNO ENAME             SAL
---------- ---------- ----------
      7369 SMITH             800
      7369 SMITH             800

ops$tkyte@ORA920>

So -- see how the same empno record can be in the view more then once?  Which
one to update? one? both? the first? the second? neither?  why -- or better yet
-- why not?

The results of such an update would be ambigous -- hence, prohibited.

What will work is this view:

ops$tkyte@ORA920> create or replace view v
  2  as
  3  select emp.*
  4    from emp
  5   where empno in( select * from your_inline_view )
  6  /

View created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select rowid, empno, ename, sal from v;

ROWID                   EMPNO ENAME             SAL
------------------ ---------- ---------- ----------
AAAJqqAAJAAAACMAAA       7369 SMITH             800

ops$tkyte@ORA920>
ops$tkyte@ORA920> update v set sal = sal * 1.1;

1 row updated.

Now the database KNOWS each emp row appears AT MOST once in the result set.

March 06, 2003 Scott -- Thanks for the question regarding
      "Evaluation precedence and the optimiser", version 8.1.7

      You Asked
G'day Tom

I have a question on where clause evaluation precendence, and how this ties in
with the optimiser.

Consider this function, where pr_debug just inserts the value of v into some
simple debugging table.

create or replace function foo(v number) return number is
begin
pr_debug(v);
return v;
end;

When executing this statement
select * from dual
where (1=foo(1) or 2=foo(2));

One expects that only the value of 1 would be inserted; ie - only the first
expression of the OR would be evaluated, since the first expression returns
true.
(I believe in some languages both statements are evaluated.)

If this statement is used
select * from dual
where (1=foo(3) or 2=foo(2));

Both values 3 and 2 would be inserted, because the first evaluation failed.

Using this behaviour, if you wanted to place precedence on evaluating the OR
conditions; ie - if the first condition took precedence over the second (in
joining to a table), could the optimiser re-arrange the query and destroy the
precedence set?

Thanks.

      and we said...
absolutely.  SQL is totally by definition AMBIGOUS as to how it evaluates the
query predicates.

It will (and does) reorder the predicates to it's liking.  that is its job.
Review &
      Followup

      Rating: 5
Oh
      dear!  March 06, 2003
Reviewer:  Steve
       from UK
I've seen plenty of code that seems to rely on this precedence.  Is there any
way to enforce it?

          Followup:

Not really (sort of yes and no).  I know of tricks that may help but they are
not promises.

Using CASE would be one reliable way.
Using DECODE as well.

March 06, 2003 David -- Thanks for the question regarding "Stats
      Pack 8.1.7 parameter question", version 8.1.6

      You Asked
Tom,

Thanks for all of your help, I really enjoy your work.

In the statspack reaport, what is the execute to parse %, and is
there a rule of thumb for it?  Also, in the Top 5 Wait Events, I
see PX deq...  What are these?, parallel queries?

>>>I'm using version 8.1.7 of statspack because of the ability
to >>>purge.  Below is the query for the exececute to parse
>>>percentage.  So since the PX is waiting, is that a good
>>>thing??
---------
--  Instance Efficiency Percentages

column ldscr  format a50

column nl format a60 newline;
select 'Instance Efficiency Percentages (Target 100%)' ldscr
      ,'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~' ldscr
      ,'            Buffer Nowait %:'                  dscr
      , round(100*(1-:bfwt/:gets),2)                   pctval
      ,'      Redo NoWait %:'
      , decode(:rent,0,to_number(null),
round(100*(1-:rlsr/:rent),2))  pctval
      ,'            Buffer  Hit   %:'                  dscr
      , round(100*(1-:phyr/:gets),2)                   pctval
      ,'   In-memory Sort %:'
      , decode((:srtm+:srtd),0,to_number(null),
                               round(100*:srtm/(:srtd+:srtm),2))
      pctval
      ,'            Library Hit   %:'                  dscr
      , round(100*:lhtr,2)                             pctval
      ,'       Soft Parse %:'
      , round(100*(1-:hprs/:prse),2)                   pctval
      ,'         Execute to Parse %:'                  dscr
      , round(100*(1-:prse/:exe),2)                    pctval
      ,'        Latch Hit %:'
      , round(100*(1-:lhr),2)                          pctval
      ,'Parse CPU to Parse Elapsd %:'                  dscr
      , decode(:prsela, 0, to_number(null)
                      , round(100*:prscpu/:prsela,2))  pctval
      ,'    % Non-Parse CPU:'
      , decode(:tcpu, 0, to_number(null)
                    , round(100*1-(:prscpu/:tcpu),2))  pctval
  from sys.dual;

Thanks,

/Dave

-----------------------------------------------------------------

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:
99.85
            Buffer  Hit   %:   99.37    In-memory Sort %:
99.89
            Library Hit   %:   98.64        Soft Parse %:
97.36
         Execute to Parse %:    0.61         Latch Hit %:
99.98
Parse CPU to Parse Elapsd %:   92.78     % Non-Parse CPU:
100.00

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   18.75   22.09
    % SQL with executions>1:   61.63   57.51
  % Memory for SQL w/exec>1:   64.99   61.70

Top 5 Wait Events
~~~~~~~~~~~~~~~~
Wait     % Total
Event                                               Waits  Time
(cs)   Wt Time
-------------------------------------------- ------------
------------ -------
PX Deq Credit: send blkd                          143,391
410,734   40.91
PX Deq: Execution Msg                               3,861
347,994   34.66
PX Deq: Execute Reply                               1,380
79,523    7.92
control file parallel write                         7,285
61,570    6.13
SQL*Net more data to client                     1,329,905
29,823    2.97

      and we said...
Ok, so parse to execute is:

      ,'         Execute to Parse %:'                  dscr
      , round(100*(1-:prse/:exe),2)                    pctval

if prse = exe, this goes to 0.  That is very very bad.

If exe is two times parse, that means we parsed once for every
two executes -- thats getting better.

As exe starts to get much larger then parse -- prse/exe goes to
zero and this percent becomes 100%.  This is unobtainable but
where you want to drive this number.

What this means is you have alot of programs that PARSE a query
every time they use it.  This is instead of parsing a statment
(like an insert for example) and using it over and over and
over.  A parse is expensive.  If you used PLSQL stored
procedures for example, all of the static SQL in there is going
to be parsed at most ONCE per program execution -- never twice.
PLSQL will parse the cursor once and then keep it open.  When
you re-execute the statement -- it'll just bind new values and
execute the statement again.

this is the difference between:

ops$tkyte@ORA8I.WORLD> create table t ( x int );

Table created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> declare
  2      p_sql           varchar2(255);
  3      cursor_name     pls_integer;
  4      ignore          pls_integer;
  5  BEGIN
  6      p_sql := 'insert into t values ( :x )';
  7      for i in 1 .. 10
  8      loop
  9          cursor_name  := dbms_sql.open_cursor;
 10          dbms_sql.parse(cursor_name, p_sql,
dbms_sql.native);
 11          dbms_sql.bind_variable(cursor_name, ':x', i );
 12          ignore := dbms_sql.execute(cursor_name);
 13          dbms_sql.close_cursor(cursor_name);
 14      end loop;
 15  END;
 16  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> declare
  2      p_sql           varchar2(255);
  3      cursor_name     pls_integer;
  4      ignore          pls_integer;
  5  BEGIN
  6      p_sql := 'insert into t values ( :x )';
  7      cursor_name  := dbms_sql.open_cursor;
  8      dbms_sql.parse(cursor_name, p_sql, dbms_sql.native);
  9      for i in 1 .. 10
 10      loop
 11          dbms_sql.bind_variable(cursor_name, ':x', i );
 12          ignore := dbms_sql.execute(cursor_name);
 13      end loop;
 14      dbms_sql.close_cursor(cursor_name);
 15  END;
 16  /

PL/SQL procedure successfully completed.

Both of those snippets of code do the same thing only the first
one does it as inefficiently as possible while the second one is
more efficient.  The second one does not parse the statement
each time -- it doesn't have to.  That would be like compiling a
subroutine each time you wanted to call it.  The second one
compiles the statement once and then executes it over and over
again.  That is what you want to see.

Review &
      Followup

      Rating: 4
what about PX
      Deq ...  July 09, 2002
Reviewer:  Nemec
       from Austria
What about PX deq events? Is it possible to react changing some parameters or
using hints? Or are they simple "given".

Thanks

Nem

          Followup:

see

http://www.orafaq.com/archive/oracle-l/2002/05/17/020325.htm
for a discussion on this topic.

      Rating: 1
LINK
      BROKEN  March 06, 2003
Reviewer:  John
      Bartch  from England
I need info on a specific question in the followups to the original Q&A and the
link is broken so it's no use whatsoever!

          Followup:

they must have moved it.  i went to orafaq and searched for it there -- check it
out.

March 06, 2003 Sridevi -- Thanks for the question regarding
      "List of Migration Issues", version Oracle 7.x to Oracle 8

      You Asked
Hello Tom,
I had recently asked you a question regarding "Migrating to Oracle 8 and
Developer 6". The answer was immensly helpful. Thanks a lot.

In the answer you had explained the riskier approach thus .....
"Upgrade the forms. You will be dealing with unknown "feature related" issues --
subtle changes to the software. There will be bugs, there will be subtle changes
to features (and you rely on the old behavior)."

Could I get a little more information on what are the Oracle/Developer ues that
we may face on migration? This would be very useful to us when we ultimately
migrate to the newer versions later. It is always better to know before hand
what issues we may face so that we can avoid the "murky waters". Forewarned is
Forearmed - right?

Where could I get a list of such issues already discovered and documented?

Can U Help Again? Thanks in Advance.....

      and we said...
I wish I could -- the problem is -- you will be doing something that no one else
has done.  It is these UNKNOWN issues that wil bite you.  That is what I was
refering to.

In general, there is no list of specific bugs that will affect anyone upgrading
from 4.5 to 6i -- there are various bugs you might hit -- if you use a feature
in a particular way.

http://metalink.oracle.com/
provides online access to the bug database, you can query up forms 6i
issues that way - but 99% of them will not apply in your case.
Review &
      Followup

      Rating: 3
Migration from
      forms 3 to GUI version   March 06, 2003
Reviewer:
      Jignesh  from india
Hi Tom,

We have one product developed in Forms 3. We are in a process to convert the
whole product in forms 4.5 / 5 / 6i.  I have downloaded oracle whitepaper and
got information that we can convert form 3 (character mode) to gui version using
forms compiler with parameter upgrade=yes.  I have not found anywhere example
and proper steps to be followed.  Could you guide me on this.

Please it is very urgent for me.  I have almost given up for this point.

Thanks and regards.
Jignesh

          Followup:

umm -- seems you have the paper?  what kind of "example" would I be able to
provide beyond "use upgrade=yes"?

Please contact support -- forms 3 is so old, it predates my very existence here
at oracle!

March 06, 2003 --
      Thanks for the question regarding "dump file size", version 8.1.6

      You Asked
Tom,

I am getting DUMP FILE SIZE IS LIMITED TO 0 BYTES in my
$ORACLE_HOME/rdbms/log/*.trc file.  I tried setting the max_dump_file_size at
session level but still there is no change.
There was an error in alert file ORA-00313 open failed for members of log group
1 of thread 1.  Before this error I added a member to the group.

Thanks

additional information

SQL> alter session set max_dump_file_size=unlimited;

Session altered.

SQL> alter session set sql_trace=true;

Session altered.

SQL> select c.value || '/' || instance || '_ora_' ||
  2  ltrim(to_char(a.spid,'fm99999')) || '.trc'
  3  from v$process a, v$session b, v$parameter c, v$thread c
  4  where a.addr = b.paddr
and b.audsid = userenv('sessionid')
and c.name = 'user_dump_dest' ;

C.VALUE||'/'||INSTANCE||'_ORA_'||LTRIM(TO_CHAR(A.SPID,'FM99999'))||'.TRC'
--------------------------------------------------------------------------------
/u01/app/oracle/admin/oradata/db/udump/prod_ora_12230.trc

SQL> !head /u01/app/oracle/admin/oradata/db/udump/prod_ora_12230.trc
/u01/app/oracle/admin/oradata/db/udump/prod_ora_12230.trc: No such file or
directory

===
Yes I am using MTS.  The dump files are supposed to go to
/u01/app/oracle/admin/oradata/db/udump/ but it goes to $ORACLE_HOME/rdbms/log

      and we said...
under MTS the dumps are owned by the shared server and will goto the BACKGROUND
dump dest -- not user dump dest.

The dump file size is set for the shared server, not the dedicated server --
don't try to do "dumps" with MTS -- immediately after your SQL statement
finished, you'll get assigned to a DIFFERENT shared server for the next
statment.

Doing dumps (sql_trace and such) is only useful in dedicate server mode.  don't
even try it in MTS -- your trace file will be interleaved with many other
sessions and spread across many physical OS files in the background dump dest.
Review &
      Followup

      Rating: 2
*** DUMP FILE
      SIZE IS LIMITED TO 0 BYTES ***  January 31, 2002

Reviewer:  Pierluigi Pasini  from Milan, IT, EUROPE

I still have the same problem without mts.

      Rating: 5
max dump file
      size  March 06, 2003
Reviewer:  Chenna

What is the max value for max_dump_file_size .

The min is 500 , but what is the max we can set.

If I'm tracing at a level 8, what should be the ideal value for this parameter.

If I want to trace at level 8 , will the below syntax suffice...

alter session set events '10046 trace name context forever, level 8'

          Followup:

unlimited is the max setting (set it to unlimited)

if you run a single query, max dump can be very small
if you run a million queries, it should be much larger

it is a function of how many trace you output -- there is no "ideal"

yes, that looks like the correct command.

March 06, 2003 Trevor -- Thanks for the question regarding
      "Calling OS command from PL/SQL(ext proc)or Java way ", version 9i

      You Asked
Tom,

Calling OS command from PL/SQL(ext proc)or Java way

(Lets assume I have smart PL/SQL people and cluey Java people
so we exclude and issues regarding lack of knowledge )

Which way is better? Pros and cons?

Regards
Trevor

      and we said...
Well, both are calling OS command from PLSQL -- the question is "what language
-- C or Java"

I would use java for this because

- it is simple.
- you can download the code from here (search for dbms_java host)
- it is portable.
- it requires little setup.
- it is very securable.

whereas extprocs would take a C programmer, setup on the server, a little less
secure (it can run any program, not just the ones you specify should be run)
Review &
      Followup

      Rating:
      5
Thanks  March 06, 2003
Reviewer:
      Trevor
Thanks Tom

seems like you are almost wokring this
site 24 X 7!

      Rating:
      5
  March 07, 2003
Reviewer:  Helena
      Marková  from Bratislava, Slovakia

March 07, 2003 Rakesh -- Thanks for the question regarding "Copy
      of Database on the same machine", version 9.0.1.2

      You Asked
Hi Tom,
I would like to ask the best option to achieve the task of making copy of our
development database on the same machine with a different name twice a week (on
Monday and Wednesday evenings). The changes will be made to the development on
Monday and Wed. evenings and before making the changes(menas requested by
developer like adding some columns, changing view definitins etc) I want to save
the existing version of the database (including data and structures) so  that in
case we want to go back the previous version it could be done without loosing
any time. So in sum I have a database say DEV and another copy is made say as
DEVB and DEVB is always one version behind the DEV. I am using oracle9i (64 bit)
on Solaris 2.8.  Now it could be achieved by
1) copying the DEV database on same machine with different name(DEVB) using
backing up control file and then copying the datafiles/logfiles and tempfiles to
different dir and then starting the db using new control file.
2) Using Export and Import
3) Data Guard (havent used it until now)
4) Something else?
Could someone advise what would be most efficient, safe and fastest way to do it
and that could be automated as well.

Thanks for your help.

Regards,
Rakesh

      and we said...
#1 is probably the easiest.  You would alter database backup controlfile to
trace on the "primary" database to create a create controlfile script.  shutdown
the primary database, copy the data and log files over the secondary database.
Then you can restart the primary.

At that point, you'll edit the create control file statement generated prior.
You'll change the paths and after switching the oracle sid to the second
instance, use sqlplus or svrmgrl to run that create control file script to
recreate the control files for this "copied" instance.

Make sure to have a good backup of the primary instance before doing this the
first time in case you have "an accident" and overwrite the wrong files or
something (better safe then sorry)
Review &
      Followup

      Rating: 3
Review of Copy
      of Database on the same machine  March 29, 2002

Reviewer:  Rakesh Arora  from CA, USA
Thanks Tom for your quick response.
Actually I did that using first option and was afraid of damaging my primary
database so I asked for your valuable advise. I couldn't find any document on
Metalink to perfrom this as last time I missed the part for spfile and tempfile
as for copying the datafiles I selected everything from dba_data_files and in 9i
the tempfiles are associated with TEMP tablespace and the tempfiles were not
appearing in DBA_DATA_FILES or v$datafiles and I missed the spfile part as well
but since I used pfile with startup mount while creating the new database so it
was fine. But is ther any other precaustion/difference using controlfile option
to recreate the database as compared to oracle 8i.
Also I was more leaning towards Export/Import option but not sure how to use EXP
and IMP without much of hasstles.
Is there any neat and clean way to get the definitions of tables, views, indexes
etc using exp/imp without much editing the resultant file in 9i.

Thanks for your help and advise.

Regards,
Rakesh

          Followup:

If you want to use exp/imp -- suggest you use transportable tablespaces.  Much
much faster, just create a secondary instance and plug your primary tablespaces
into it.  when you want to refresh -- drop the tablespaces and copy them over
again.

      Rating: 3
Similar
      Scenario - Oracle 8.1.6.   March 30, 2002

Reviewer:  A reader  from India
Hi Tom, I was looking to pose a similar question today. We
have an OLTP Production Database 24X7. The users enter data
in 3 shifts i.e. round the clock.

We have exhaustive reports and queries (inserting / using
it's own tables , functions , procedures etc.) that utilise
heavy CPU & hit the performance.

Our company plans to run these reports & queries on another
server that will be a copy of the prodn database. It has to
be updated daily. Our problem is that we have no down time
& can spare minimum server resource's for this task. Our
dba's have told us that an incremental export backup's will
not be a solution as the database in the reports server
will also be updated. They have proposed an intermediate
database for incremental import & then a copy. Is the
additional database resource the best solution?

Tom, Pls advise what you feel will be the best solution in
this scenario .........

          Followup:

What I think would be best -- and would give you additional protection -- would
be a standby database.

It will have zero impact on the current systems performance.

It'll give you your reporting instance during the day.

It'll also give you a failover site for the OLTP system in the even of a really
bad failure.

See the standby database guide (in 9i, it is called data guard and has lots more
features like switchover and switchback).

For you -- that is what I would recommand.

      Rating: 4
What about the
      SID in the datafiles?  January 09, 2003
Reviewer:
      Robert Boyle  from Cork
Hi Tom

I am trying to copy a database to a test machine.  I have made a hot backup,
with archivelogs, and have created the CONTROLFILE to trace.

I am running 9.2 on win 2000 server.

The database was created using the wizard on the installer.  I figured if I was
copying the files it wouldn't matter too much.  Anyway, I have modified the
trace controlfile and try to start with the copied files but it complains about
the SID in the actual datafile headers.  How do I get around this problem?  Do I
need to manually edit them in a hex editor?  Or have I missed a stage somewhere
when I was backing up?

Cheers
Robert

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:318216852435

      Rating: 5
performance
      hit  March 06, 2003
Reviewer:  Tommy
       from USA
Tom,
In your response, you mentioned that Data Guard would not have a performance hit
on the system.
If I am generating redo logs at the rate of about 10G per day, shouldn't I be
concerned about Data Guard having to apply those same logs (or sql streams)
across my network?

          Followup:

data guard runs in 4 modes.

1) 2 highest levels, most impact, syncronous transfer of redo by LGWR
2) 3 highest level (which I see most people using in the real world),
asyncronous transfer of redo by LGWR.  Call this a "fire and forget", LGWR
doesn't wait for confirmation from remote site, just sends redo.  Low impact
3) lowest level, ARCH transfers redo after the fact in the background, tiny
impact -- but largest lag between production and standby.

10gig over a network isn't very much these days.

March 07, 2003 Ji
      Sook -- Thanks for the question regarding "what is lobsegment,lobindex",
      version 8.1.7

      You Asked
I query table 'user_segments'
sql>select segment_name, segment_type, tablespace_name, bytes, max_extents
from user_segments
where segment_type like 'LOB%'

result is

SEGMENT_NAME                    SEGMENT_TYPE    TABLESPACE_NAME
SYS_IL0000012099C00002$$    LOBINDEX    CPOCKET_DATA
SYS_LOB0000012099C00002$$    LOBSEGMENT    CPOCKET_DATA

=> I don't know what is lobsegement, lobindex.
What is lobsegment, lobindex and why need it.
Can't I delete it?
Please explain detail about lobsegement, lobindex.

Thank you.....

      and we said...
You have a table with a LOB column in it.

A LOB is simply a pointer.  It points to an index.  the index points to the
chunks that make up the LOB.

Hence when you create a LOB, you will always get a lob index created (to find
the chunks for the lob fast) and a segment that holds the lob data (chunks).

You can only "delete" it by dropping the table or column that contains the LOB.
Review &
      Followup

      Rating: 5
Discussion on
      LOBs  November 14, 2001
Reviewer:  Andre
      Whittick Nasser  from Rio de Janeiro, Brazil
Getting back to our discussion at:

http://asktom.oracle.com/pls/ask/f?p=4950:8:260177::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1465222650804,%7Blob%7D
I was asking you about migrating VARCHAR2's to CLOBS, placing some (often
mistaken) analogies.

I have this new piece of information from you I was not aware of before.

When I do:

a) CREATE TABLE t1 ( a VARCHAR2(4000) );

and

b) CREATE TABLE t2 ( a CLOB );

In a) what I understand is you are creating a VARCHAR2 field for every row
ocurrence (with respective lenght data, etc.)

In b) what I see is that you have only one CLOB for the table and each CLOB
field stores a pointer to the index, which in turn points to the chunks.

Questions:

1) You have only one CLOB segment for every CLOB fields of any regular table ?

2) What about in-line CLOBS ? Do they really "share" the table blocks or
continue to be separated segments ? What about pointers and indexes here ?

          Followup:

1) you have A clob index and A clob segment for each CLOB column in a table.

2) inline clobs, the ability to "inline" the data in original block -- if the
clob is less than about 4,000 bytes -- will still have an index and lob segment
since the moment they exceed the threshold -- they need SOMEWHERE to go.  Hence
even inlined CLOBS have the index and segment.

      Rating:
      5
Documentaion  November 14, 2001
Reviewer:
      Andre Whittick Nasser  from Rio de Janeiro, Brazil
Sorry Tom,

This discussion on LOBs is getting very long, probably I miss some points. I
have the Oracle University books, but they lack a bit of consistency. Would you
please suggest me some good material, peferrably on line ?

          Followup:

http://technet.oracle.com/doc/oracle8i_816/appdev.816/a76940/toc.htm
and exploration.

      Rating: 4
Renaming LOB
      INDEX  March 07, 2003
Reviewer:  Mohan K
       from Bangalore, India
I was able to give my own name for LOB SEGMENT in create table statement. I want
to rename the LOBINDEX segment  and give a meaningful name instead of
SYS_IL0000003838C00006$$. Please tell me how to do it.

          Followup:

you cannot do it.

As a famous movie once said

"ignore that man behind the curtain".

March 07, 2003 Om
      -- Thanks for the question regarding "Regarding create index and drop
      table privileges", version 8.1.7

      You Asked
Hi Tom,

I have few privileges questions to ask .

There is no direct create index and drop table privilege.

Q1) How do you give a particular use "create index" privilege.

Q2) How to give drop table privilege to a user or it is not
just possible for a user to drop a table which he doesn't own ?

Best regards,
Om

      and we said...
q1) if you own a table, you can index it (hence CREATE TABLE conveys the ability
to create an index on that table)

you can grant CREATE ANY INDEX (bad idea) in order to create an index on ANY
table in the system

you can grant CREATE INDEX ON TABLENAME to ANOTHER_USER to allow another schema
to index some tables in your schema.

q2) I use stored procedures for such customized, fine grained granting of
privileges.  It is very easy to do.  For example,

create procedure drop_table( p_tname in varchar2 )
as
begin
     .... whatever complex logic you want to determine if they CAN drop it ....
     execute immediate 'drop table ' || p_tname;
end;
/

and if the owner of that procedure either OWNS the table in question to be
dropped OR the owner of the procedure has the super powerful "drop any table"
priv, they can now drop the table on behalf of someone after performing whatever
security checks you want.

The other user just needs EXECUTE on that procedure.
Review &
      Followup

      Rating:
      5
Security?  September 12, 2002
Reviewer:
      Dennis  from Missouri, USA
Tom,

Wouldn't you want to be very protective of such a procedure and make it only
drop "the table" and not potentially drop any table?  Of course, I don't know
what logic you're sticking in, I just fear the openness of the procedure as you
could plug any name in there.  I do something similar for truncating tables
currently, and I did allow a parameter in your fashion, but checked that the
p_table_name was one of the three values I approved of.  Is that what your logic
would be, or would you also have some user checks (I'm relying on the EXECUTE
privilege...should I not (nobody except other DBAs have EXECUTE ANY))?

Thanks Tom.

Dennis

          Followup:

 .... whatever complex logic you want to determine if they CAN drop it ....

should cover my behind on this one ;)

that is what .... whatever .... meant -- do your check, whatever it may be, to
make darn sure they CAN drop it.

      Rating: 5
Just
      wondering  September 12, 2002
Reviewer:
      Dennis  from Missouri, USA
Yeah, you were covered.  I worded my question incorrectly.  We have two
different sets of procedures in place.  One, where you call the procedure and it
truncates a specific table (there are something like five of these).  Another,
where you call a procedure with a value to truncate one of three tables.  I
didn't want to make the developers call 100  billion procedures to truncate
(well ok, three) ;) so I chose to pass in a parameter, but put in the logic to
make sure it was one of the three tables.

Now, is that wrong?  Is the other way better (one proc per table)?  Or should I
have logic to check that the user is a certain user and not worry about checking
the tables (hoping the user knows what he/she/it is doing), or do the table
check as an extra precaution (yes, I see big red abandon ship, missile(s)
incoming warning lights all over the not including it option, but I gotta ask)?

Does checking for user 'X' do what I think it does and just protect me from
those who have the execute any privilege that I don't know about and don't
particularly want truncating (or dropping or whatever) my tables?
Leaving me at the point where only certain people can run my procedure, but I'm
still not safe from anyone like you who can rewrite my stuff at will
(hey...maybe you can also make it really fast while you're at it...).

I almost posted a reply right away, but hoped that your crystal ball was back
from the shop ;)  Guess I should've known better, but I was also trying to be
lazy :)  As well as horde time so I could think of a better way to ask it.

Thanks Tom.
Dennis

          Followup:

You should do what you feel needs to be done in order to have this happen
securely, reliably and correctly.

I might have used a table with "username" "tablename" and insert rows in there
(sort of like DBA_SYS_PRIVS for example) to make it table driven and in a single
procedure (to cut down on code)....

You need to write the procedure in a way that is only lets those you want --
truncate.  Anyone with EXECUTE on it, or EXECUTE ANY PROCEDURE can run it -- a
table driven approach will make it so only those you actually give permission to
at the table level have it.

      Rating:
      3
Doubt......  September 13, 2002
Reviewer:
      Ashok  from India
Hi

"you can grant CREATE INDEX ON TABLENAME to ANOTHER_USER to allow another schema
to index some tables in your schema."

Will this index be considered by the optimizer if the owner of the table fires
any query on this table that affects the indexed column (created by another
user)?

          Followup:

Yes, an index is an index is an index.  The optimizer sees them all.

      Rating: 4
yay I get to
      help!  September 13, 2002
Reviewer:  Dennis
       from Missouri, USA
Yes the index would.  You can thank Tom for my example skills, I'm trying "to be
like Mike".

06:48:48 WINKID1@HIS2:scsp05> create table test_me as select * from dba_objects;

Table created.

Elapsed: 00:00:00.51
06:49:00 WINKID1@HIS2:scsp05> insert into test_me select * from test_me;

8597 rows created.

Elapsed: 00:00:00.40
06:49:15 WINKID1@HIS2:scsp05> /

17194 rows created.

Elapsed: 00:00:00.10
06:49:16 WINKID1@HIS2:scsp05> /

34388 rows created.

Elapsed: 00:00:01.02
06:49:18 WINKID1@HIS2:scsp05> /

68776 rows created.

Elapsed: 00:00:02.04
06:49:21 WINKID1@HIS2:scsp05> /

137552 rows created.

Elapsed: 00:00:06.70
06:49:29 WINKID1@HIS2:scsp05> commit;

Commit complete.

Elapsed: 00:00:00.20
06:49:30 WINKID1@HIS2:scsp05> create index tab_owner_idx01 on test_me(owner);

Index created.

Elapsed: 00:00:08.92
06:50:03 WINKID1@HIS2:scsp05> @connect
userid:dbaoper
password:*********
sid:his2
Connected.
06:50:12 DBAOPER@HIS2:scsp05> create index other_owner_idx01
06:50:31   2                    on winkid1.test_me(object_name, object_type);

Index created.

Elapsed: 00:00:08.03
06:50:53 DBAOPER@HIS2:scsp05> @connect
userid:winkid1
password:********
sid:his2
Connected.
06:51:00 WINKID1@HIS2:scsp05> set autotrace traceonly explain
06:51:07 WINKID1@HIS2:scsp05> select object_name, object_type
06:51:18   2                   from test_me
06:51:24   3                    where object_name like 'S%' and
06:51:38   4                     object_type = 'TABLE'
06:51:48   5  /
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   INDEX (RANGE SCAN) OF 'OTHER_OWNER_IDX01' (NON-UNIQUE)

06:51:49 WINKID1@HIS2:scsp05> spool off
06:51:57 WINKID1@HIS2:scsp05>

          Followup:

All right - it is working!!! The ability to try out things is spreading ;)

thanks.

      Rating: 3
CREATE INDEX ON
      TABLENAME TO USER is not working  January 02, 2003

Reviewer:  Howie  from Arlington, Virginia, UA

Hi

I am using oracle 8.1.7 and I was trying to grant privilege to other user so
that he can create index on my tables ( not all but some of them). I tried to
user CREATE INDEX ON HR_TAB to USER_01 but it failed with following error
ERROR at line 1:
ORA-00990: missing or invalid privilege

Can you please explain how to use this?

          Followup:

ops$tkyte@ORA817DEV> grant index on t to scott;

Grant succeeded.

      Rating: 4
Dropped Table
      Information  March 07, 2003
Reviewer:
      A reader
Hi Tom,

I dropped a table. The table contains no data. I have no backups.

1)Which data dictionary table contains information about the dropped tables. ie
information like the table structure, indexes associated with it, primary key ,
foriegn key etc ?
Or does such information exists in the datadictionary at all?

2) Is there any other way you can find those informations
about a recently dropped table (other than from the backups)?

Thanks and regards
 Praveen KV

          Followup:

you are what they officially term as "hosed".

it is all gone.  if you are on 9i, there is a chance we can use flashback query
to see the DEFINITION of the table (but not the data).

March 07, 2003 Sudhir -- Thanks for the question regarding
      "freelist, ", version 8.1.7.1

      You Asked
Hi Tom,

I have a few questions,

1) what is the downside to high freelists value? Wasting a few blocks is not
much of a problem. However changing these values in production to higher value
has any risk?

2) Are blocks registered to one of the freelists as they become available? If so
each freelist holds distinct blocks?

3) I read somewhere that 'initrans' and freelists kind of share play area.
Higher one means lower other etc. Is it true? Can you give some information on
that.

4) Is there a difference in performance freelist handling in locally managed
tablespaces vs dictonary managed one? Is this another reason for using LMT?

And finally unrelated question

- If I take tablespace offline, drop it and later on thru unix prompt remove
datafiles. I see space is not free. df -k shows smaller value and du -sk shows
big value. I do not have any use of these files in order for unix to keep them
alive. I reran sync command many times but space remains unusable.

Thank you for your help

      and we said...
1) none that I am aware of.  It'll increase performance of concurrent inserts
and updates, it'll not use free space as efficiently.

see also

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1382416016797
2) each freelist holds a small number of blocks and will get more blocks as
needed from a master list of free blocks (added by transactions that deleted
data or updated it "smaller").  If there are none there, they go above the HWM
to allocate more.

see also

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:950845531436
3) No, I see them as being somewhat mutually exclusive from each other.
inittrans will be on of the contributing factors to constraining the number of
concurrent modifications (insert/update/delete) to a block.  Set too low in a
case where there is a "hot block" and you could see decreased concurrency if
there is insufficient space to extend the transaction table on the block.

Freelists impact new space allocation, mostly inserts and occasionally updates
that need to migrate or chain a row.

4) not really -- the BIG change comes in in 9i with automatic space allocation.
This removes the need for freelists, freelist groups and even PCTFREE.  It has
most of the benefits of a fine tuned (perfectly set number of freelists and
such) system without the worry.  It is for space management in a segment what
LMTS were for tablespaces.

-- unrelated question --
it should not do that.  when you offlined the tablespace, we should have closed
those files.  This is indicative of someone still having them open.  You might
see about getting "lsof" (search google for that) to inspect who has them open
still.
Review
      & Followup

      Rating:
      5
Thanks  September 28, 2001
Reviewer:
      A reader
I downloaded lsof, now I need to actually get it working.

thanks

      Rating: 4
Mutually
      exclusive ??  March 07, 2003
Reviewer:  Kim Berg
      Hansen  from Middelfart, Denmark
I'm trying to figure out how best to set freelists and initrans for some tables
with heavy insert/updates.

I've been searching AskTom and I thought I had it about figured out, when I came
across this question.

You say :
"3) No, I see them as being somewhat mutually exclusive from each other."

Does that mean that EITHER freelists OR initrans should be set for a "hot"
table/index and not BOTH at the same time ?

If so, what determines which parameter should be used ?

Thanks in advance.

          Followup:

initrans has to do with how many active transactions can I have on a block.

freelists has to do with "how many totally concurrent requests for freespace can
I have going on".

They are mutually exclusive.

If you have a block with 50 rows and 50 people want to update one of the rows
(each going after a different row), initrans needs to be high.

If you have a table where you have 50 concurrent load routines going into it,
you might want lots of freelists.

The two are for two totally different things.  If each insert routine has its
own freelist, initrans can be quite modest since only ONE guy is inserting in
the block -- UNLESS after the guy that inserts into the block committs -- 50
users pounce on that block and all modify it -- then you need lots of initrans

See the point -- they are different tools for different purposes.

freelists low, initrans high makes as much sense as
freelists high, initrans low makes as much sense as
freelists high, initrans high makes as much sense as
freelists low, initrans low

depending on the CIRCUMSTANCES.  You would set each independent of the other.

      Rating: 3
The LMT Issue
      leads me to a question of my own!  March 07, 2003

Reviewer:  Anand H S  from New Delhi, India

In Oracle 8i, I was able to deallocate free space in a segment "BELOW" the HIGH
WATER MARK. Has that feature been removed from Oracle 9i??

    This is my situation: I create a table Emp and insert records into it. when
I create the table I see 1 extent and 5 blocks. After the insert is over, I see
8 extents and 195 blocks.

    Now I rollback. I still see 8 extents and 195 blocks. THIS IS ABSOLUTELY
OK!!

    But when I issue the command:

    ALTER TABLE EMP DEALLOCATE UNUSED KEEP 0;

    Only the unused space above the HIGH WATER MARK gets freed. But The HIGH
WATER MARK does not Get RESET. But In Oracle 8i I believe this used to happen!

Please clarify.

--Regards,
Anand H S

          Followup:

you are mistaken. it never worked like that.

ops$tkyte@ORA817DEV> create table emp1
  2  tablespace system
  3  storage ( initial 1k next 1k pctincrease 0 )
  4  as
  5  select * from scott.emp where 1=0;

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> drop table emp2;

Table dropped.

ops$tkyte@ORA817DEV> create table emp2
  2  tablespace users
  3  as
  4  select * from scott.emp where 1=0;

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert into emp1
  2  select * from scott.emp;

14 rows created.

ops$tkyte@ORA817DEV> insert into emp1
  2  select * from emp1;

14 rows created.

ops$tkyte@ORA817DEV> /

28 rows created.

....

ops$tkyte@ORA817DEV> /

57344 rows created.

ops$tkyte@ORA817DEV> insert into emp2 select * from emp1;

114688 rows created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select segment_name, max(extent_id), sum(blocks)
  2    from user_extents
  3   where segment_name in ( 'EMP1', 'EMP2' )
  4   group by segment_name;

SEGMENT_NAME                   MAX(EXTENT_ID) SUM(BLOCKS)
------------------------------ -------------- -----------
EMP1                                      671         673
EMP2                                       10         704

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> rollback;

Rollback complete.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select segment_name, max(extent_id), sum(blocks)
  2    from user_extents
  3   where segment_name in ( 'EMP1', 'EMP2' )
  4   group by segment_name;

SEGMENT_NAME                   MAX(EXTENT_ID) SUM(BLOCKS)
------------------------------ -------------- -----------
EMP1                                      671         673
EMP2                                       10         704

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> alter table emp1 deallocate unused keep 0;

Table altered.

ops$tkyte@ORA817DEV> alter table emp2 deallocate unused keep 0;

Table altered.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select segment_name, max(extent_id), sum(blocks)
  2    from user_extents
  3   where segment_name in ( 'EMP1', 'EMP2' )
  4   group by segment_name;

SEGMENT_NAME                   MAX(EXTENT_ID) SUM(BLOCKS)
------------------------------ -------------- -----------
EMP1                                      671         673
EMP2                                       10         704

      Rating: 5
THANKS A TON
      TOM!  March 14, 2003
Reviewer:  Anand HS
       from New Delhi, India
Thank you very much for clearing this misconception in my mind about the space
usage.
I guess I must have misundestood it in the first place while working on Oracle
8i.

Regards,
Anand H S

March 07, 2003 david -- Thanks for the question regarding
      "Tuning tips. Use select count(*) or select * for tuning purposes. ",
      version 8.1.7

      You Asked
Should I use select count(*) or select *  for tuning purposes.

I have conflicting answers when I ask tuning experts  on this what is your
opinion?

One argument is that select count(*)  don;t always give you the real execution
plans because it may use an index to count the rows.

      and we said...
you have to use the QUERY YOU WILL ACTUALLY USE.

not select count(*)
not select *

you need to use select a, b, c, d, ....

select count(*) --- that'll give you totally different plans then select * and
select a, b, c, ... will be the only thing to give you the same plan as your
application will actually execute.

You must use the query you will actually use.  count(*) -- don't even consider
that one unless you application does a count(*)

and... if your application does a count(*) ask -- why?  why does it do that, get
rid of it....

The best way to speed up something is to not do it.  I find people doing
count(*)'s all over the place to no useful end...

Review &
      Followup

      Rating: 5
Get rid of this
      one too..?  March 07, 2003
Reviewer:  pasko
       from Hamburg,Germany
Hi Tom,

should we also get rid of selects  of the form :

select count(*)  /* check if   value_2B_checked  exists
                 in  table_to_check  */
into l_cnt
from dual
where exists(
select null from  table_to_check
where  checked_col   =  value_2B_checked  ) ;

Thanks in advance .

          Followup:

depends -- why are you doing that?  is the logic:

select count(*)  /* check if   value_2B_checked  exists
                 in  table_to_check  */
into l_cnt
from dual
where exists(
select null from  table_to_check
where  checked_col   =  value_2B_checked  ) ;

if ( l_cnt = 1 )
then
    process table_to_check
end if;

if so, the logic should just be:

for x in ( select * from table_to_check where checked_col = value_2b_checked )
loop
   process....
end loop;

March 07, 2003 Ken -- Thanks for the question regarding "resize
      datafiles ", version 8.1.5

      You Asked
Hi Tom,

I know you are very busy, but it would be much appreciated if
you can help me with the following:

I created a schema that contains 2 tablespaces - abc_data,
abc_idx for example, then loaded data into tables within these
tablespaces when we have the program written and sometimes we
loaded a whole month's worth of data so these tablespaces got
extented.  when the data is no longer needed, or the program
needs modifications we truncate the tables using the truncate
command with the drop storage clause but when I looked at the
datafiles for these tablespaces in Oracle storage manager, I see
for example :

NAME
  TABLESPACE              SIZE (M)              USED(M)
/d6/oradata/JIBPP_ORADATA/abc_data01.dbf      abc_data
            465                     149

so I tried to resize the datafile manually with

"alter database datafile
'/d6/oradata/JIBPP_ORADATA/abc_data01.dbf' resize 200m ;

but Oracle returned the error message "file contains used data
beyond requested value".

Is the a way that I can resize the datafile without dropping the
tablespace and re-creating it.

Thanks very much.

Ken

      and we said...
See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:153612348067
It'll provide a script to resize this file as small as we can --
which may be 465meg.

Think of the file as a collection of extents (it is).  Let's say
the layout is like this (X = allocated extent, O = free extent):

0meg                                                        465m
XXXX0000000000X000000000000XXXXXX0000000000XX000000000000000000X

So, as you can see here -- that last X out by 465m will prevent
us from shrinking this file.  If you can determine what object
is out there (see query following for how to do that) you can
perhaps "move or rebuild" that object -- or perhaps just drop it
and recreate it later.

To see what object is hanging out out there -- run:

column tablespace_name format a20
column "Name" format a45
break on file_id skip 1
ttitle &1
select file_id, block_id, blocks,
       owner||'.'||segment_name "Name"
from   sys.dba_extents
where  tablespace_name = upper('&1')
UNION
select file_id, block_id, blocks,
       'Free'
from   sys.dba_free_space
where  tablespace_name = upper('&1')
order by 1,2,3
/

and pass it in the name of the tablespace.  Look for the large
block_id at the end of the query and it'll show you the object
you are looking for.

Review &
      Followup

      Rating: 5
Exactly what I
      needed to know  December 10, 2002
Reviewer:  Ron
      Plank  from Raleigh NC USA
Exactly what I needed to know plus a bonus for the script show the physical
distribution of objects in a tablespace's datafiles

      Rating: 5
Recreate
      Database due to the huge system tablespace.  March 07, 2003

Reviewer:  Alex  from Seoul Korea
Great Tom's Answer
======================================================
Ouch -- looks like someone let people create some really big things in system.
There is a chance you can shrink some of the datafiles (see below -- it'll tell
you if you can and will generate the alter statements if they apply).  We can
only shrink files back to their high water mark -- if there is an extent way out

at the end of a file -- we'll not be able to shrink it.
If not, the only thing that will undo this is to recreate the database (exp,
imp).
====================================================
My question ++++++++++++++

It worked..Yes, I could shrink my system tablespace file. a little bit.( Thank
you very much any way.
Accoding to your answer.. I'd better recreate my database.
Unfortunely..I don't know how to recreate database with same
parameters/configuration. ( absolutely with shrinked actual size of system
file.. other datafiles aren't important..I have all of creating other tablespace
scripts and data)

I'll be appreciate your help.....

(I'm not good at writting my problem in English..Sorry)

          Followup:

I didn't say "you'd better".

I said "you would have to if shrinking this file is your only goal in life"

I would most likely just let it be myself.

      Rating: 5
Very
      Interesting and Potentially Helpful!  March 07, 2003

Reviewer:  Robert  from Memphis, USA
Tom,

I added this script to my own toolkit of utilities (being careful to give you
due acknowledgment, Tom!).

Thanks,

Robert.

March 07, 2003 Xavier -- Thanks for the question regarding
      "ORA-06519", version 9.2.0.2.0

      You Asked
I have a stored proc that uses Execute Immediate to run a dynamic SELECT query
into a variable.  It has worked fine for a long time.  However, when the query
references a linked table, the procedure blows up with the indicated error.  The
error only happens if I use the pragma AUTONOMOUS_TRANSACTION (which I do
because the real procedure does SELECT's on tables that may be mutating).

Here is a toy procedure that demonstrates the problem:

PROCEDURE TEST
IS
    PRAGMA AUTONOMOUS_TRANSACTION;  /* needed in actual proc */

    vSQL_A VARCHAR2(2000) := 'SELECT tagname_cd from tagname WHERE
tagname_cd=''FAC_GN''';
    vSQL_B VARCHAR2(2000) := 'SELECT fac_gn from fac@ebmud WHERE
fac_gn=TO_NUMBER(123)';
    vTempStr VARCHAR2(2000);
BEGIN
    /* works with vSQL_A, fails with vSQL_B */
    execute immediate vSQL_A INTO vTempStr;
END;

Now I know about your general caveats and recommendations about using dynamic
sql.  But nonetheless it all worked great until I did a select on a linked
table.

      and we said...
you cannot use an autonomous transaction in a distributed transaction -- period,
plain and simple.

Also, to use an autonomous transaction to get around a mutating table sounds
very very very VERY suspicious to me.  I would question

a) the need
b) the results

I've seen far too many people use this as the way to get around "mutating
tables" only to totally miss the point.  They are not able to enforce the
business rules they believe they are -- data integrity is out the window.

I'd be interested in hearing what you are doing exactly that necessitates an
a-tran to read over a mutating table.  I find pretty much every time I can show
a huge hole in the logic -- meaning the integrity you were trying to achieve is
gone.

The mutating table constraint is not an error, it is protecting you from
yourself.

Review &
      Followup

      Rating: 3
OK, but can I
      push the autonomous transaction down  March 07, 2003

Reviewer:  Xavier Irias  from Oakland, CA USA

OK, if I can't use a distributed transaction WITHIN an autonomous transaction,
and the PRAGMA is there to handle not the distributed SELECT query, but a
different SELECT, it sounds like I could just push the autonomous transaction
down into a separate stored proc, to minimize the scope of the AUTONOMOUS
pragma?  Sound right?

Re the need for and results of the autonomous transaction:  I have two tables,
call them Parent and Child, with one Parent row for zero to many Children.  The
parent row has a "group" column indicating what "group" the parent is in.  Given
a userid and a group, I have a function that returns True or False on group
membership.

The business rule for updating either table is that you may only modify a child
or parent if you belong to the same "group" as the child or parent.  So when the
proposed update is on a child, it is necessary to lookup the parent row in order
to determine group membership.  Since I am doing the check within a trigger, I
use an autonomous transaction for the lookup because it's possible that the
parent and child are both being modified within a single transaction (making the
parent mutating).  The autonomous nature of the transaction means that I see the
parent as it existed before the current transaction started.  If the parent did
not even exist before the current transaction started, then the autonomous
transaction finds no parent which can be handled fine.

Clearly the update logic would be simpler to code and test, and would perform
better, if I used a stored proc and simply forbade direct UPDATE's to both
tables.  However from the point of view of higher-level code, the stored procs
are an inferior interface (taken to an extreme, if we use stored procs for all
data updates, why even have a SQL-compliant database).  I think I understand the
pros and cons of using stored procs versus trigger code, and I have chosen
stored procs in cases where their benefits outweighed their negatives.  The case
in question here is just not one of those times, and I would like to make the
trigger option work.

          Followup:

you do not need an atran, just as I thought.

What command would you be executing that in a single statement would modify
both the parent and child.

mutating table constraints are statement level issues, NOT transaction level
issues.

You would have to be issuing a single DML statement that modified both tables at
the same time.

Tell me -- what statement could that be?   I am failing to see the mutating
table issue here.

      Rating: 4
Single
      statement causing updates to two tables  March 07, 2003

Reviewer:  Xavier Irias  from Oakland CA USA

How about cascading logic?  Either a parent trigger wants to cascade primary key
changes to children for example, or a trigger-maintained value in the parent
table, e.g. ChildCount, needs to be incremented or decremented during insert or
delete of a child.  The appropriate table then issues its own updates.

Though I must admit that when I look closely at the actual situation I face, I
think I CAN do with the autonomous transaction as you said.

          Followup:

Either a parent trigger wants to cascade primary key
changes to children for example,

that'll mutate all by its lonesome...

or a trigger-maintained value in the parent
table, e.g. ChildCount, needs to be incremented or decremented during insert or
delete of a child.

that'll have no issues, try it....

I need a concrete example, then I can address it.

did you mean to so "i think i CAN do withOUT the atran"?

      Rating: 5
Yes that's what
      I meant  March 07, 2003
Reviewer:  Xavier
      Irias  from Oakland, CA USA
Yes, thank you.

March 07, 2003 Juan Carlos -- Thanks for the question regarding
      "reducing resource used for a function", version 9.2

      You Asked
Hi I has a function that does a simple select in a tables that has less than 20
records

funcion getage( ccode ) is
select age from customer where code =ccode;
 if age between 10 and 15 then
  age = 12;
 end if;
return age;
end;

I execute a select
select x from sales where getage( ccode ) = 12;

Tracing this query this condition takes 60 secods,

I need to reduce the time but
1) I can create an index based on this function, the true is that most time this
value are going to be fixed, but they can change.
2) using deterministic It took 80 seconds, 20 seconds more.
3) I can do a join, neither use a case, because the conditions in the function
can change and this function is used in several querys.
4) it is correctly binded.

What can I do to increase the performance of this function.
because what takes 60 seconds is the
select x from sales where getage( ccode ) = :1;
executed several times.

:) Thanks

      and we said...
1) then it is not a candidate for a function based index

2) no it didn't -- you would have to prove that to me with TKPROF.  I can
believe that by the wall clock, you got unlucky and one time you ran the query
it took 80 seconds -- but that is only because you waited more.  deterministic
cannot have any bearing on performance like that -- none.

what you saw was the normal run to run variations you see with queries on a
system with many other users doing stuff.

3) use a VIEW then.

4) that is a correct statement.

What you want to do is use a VIEW

create view sales_view
as
select ....,
      (select case when age between 10 and 15 then 12 else age end
         from customer
        where code = sales.ccode ) getage
  from sales;

use that and when and if the function changes, update the view like you would
have updated the function.

I know what you are going to say "i don't want to do it that way", well, if
you want any semblance of performance, you will -- you will always use SQL,
CASE, whatever tricks you can do in order to AVOID calling PLSQL from SQL.
Calling PLSQL from SQL for a couple dozen rows -- great.  Against thousands --
bad idea.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1969395750585#8239693304403
why a good example of "why"
Review &
      Followup

      Rating:
      5
  March 07, 2003
Reviewer:
      A reader
:) Thanks

March 07, 2003 Ken -- Thanks for the question regarding "Dynamic
      SQL", version 9.02

      You Asked
Tom,

Although I have seen many examples of dynamic Sql and written many lines of it,
I am still confused about it's implementation and cannot find anyone
...yet...who can give me a definite answer.

My question is this.  What determines when I can use a bind variable as opposed
to concatenation in the query string?

For example:

Create or replace procedure do_something_to_table(p_invar1 IN varchar2,p_invar2
IN varchar2,p_proctype_in IN varchar2)
is
begin
if p_proctype_in = 'SELECT' then
  execute immediate 'select * from table where col1=:1 and col2=:2' using
p_invar1,p_invar2;
elsif p_proctype_in = 'DELETE'  then
  execute immediate DELETE table where col1=:1 and col2=:2' using
p_invar1,p_invar2;
else
  RAISE_APPLICATION_ERROR(-20001,g_invalid_operation_msg)
end if;
exception...
END;

Sometimes the execute immediate statement works fine.  On other times I have to
change the code to be...
execute immediate 'select * from table where col1= '||p_invar1||' and col2=
'||p_invar2||''';

In my original SQL environment (MDBS III) the 'using' command was commonplace
and worked all the time.  In order to create 'pass-through' queries in the MS
environments (Access and VB) we learned to do the concatenation route.

I, and I am sure that many others, would like to know going in what the
determining factor(s) is/are so that I can code it right the first
time...optimize the coder as opposed to the code.

Thanks in advance,

Ken T

      and we said...
give me an example of when the above would fail unless you used concatenation.

Anyway -- the answer is quite simple really.  Here it is.

You can use a bind variable ANYWHERE you can use a character string constant.
For example:

select :x from dual;

is legitimate because:

select 'X' from dual;

is a valid query.  I know I can code:

select * from t where y = :x;

because

select * from t where y = 'X';

is valid.  Now, I know I cannot do this:

select * from :x;

because I cannot code:

select * from 'X';

that just doesn't make sense.  You cannot use bind variables where an IDENTIFIER
MUST appear.

Review &
      Followup

      Rating: 4
EXCECUTE
      IMMMEDIATE 'select...' INTO  October 07, 2002

Reviewer:  Robert C  from PA
>>execute immediate 'select * from table where col1=:1 and >>col2=:2' using
p_invar1,p_invar2;
what good is this going to do ?

Tom, we can only EXCECUTE IMMMEDIATE 'select...' INTO a single variable or
record, not a collection object, correct ?, no bulk fetch with EI ?

Thanks

          Followup:

I assumed the select example was just psuedo code, not real code.  It is missing
the INTO clause yes.

You can execute immediate into a single variable, record or collection:

scott@ORA920.US.ORACLE.COM> declare
  2          l_rec emp%rowtype;
  3  begin
  4          execute immediate 'select * from emp where rownum = 1' into l_rec;
  5  end;
  6  /

PL/SQL procedure successfully completed.

scott@ORA920.US.ORACLE.COM> declare
  2          l_empno number;
  3  begin
  4          execute immediate 'select empno from emp where rownum = 1' into
l_empno;
  5  end;
  6  /

PL/SQL procedure successfully completed.

scott@ORA920.US.ORACLE.COM>
scott@ORA920.US.ORACLE.COM> create or replace type myScalarType as object
  2  ( ename varchar2(30), empno number, hiredate date )
  3  /

Type created.

scott@ORA920.US.ORACLE.COM>
scott@ORA920.US.ORACLE.COM> create or replace type myArrayType as table of
myScalarType
  2  /

Type created.

scott@ORA920.US.ORACLE.COM>
scott@ORA920.US.ORACLE.COM> declare
  2          l_collection myArrayType;
  3  begin
  4          execute immediate
  5          'select cast( multiset( select ename, empno, hiredate from emp ) as
myArrayType )
  6             from dual' into l_collection;
  7
  8          dbms_output.put_line( l_collection.count );
  9  end;
 10  /
14

PL/SQL procedure successfully completed.

      Rating: 2
Ken -- Thanks
      for the question regarding "Dynamic SQL", version 9.02--one
      question  March 07, 2003
Reviewer:  Radhika
       from NJ,USA
I have a question regarding this..you spoke about how to collect the output as a
collection, I have a problem in sending the input as a selection.
Like
select * from emp where emp_no in (:emplist);

here the emplist is a varchar2(500) := '23456','1234','34567' ;

Any suggestions/solutions will be greatly appreciated.
Thanks
Radhika

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:110612348061

March 07, 2003 Sri -- Thanks for the question regarding "Repeat
      Rows Returned By Select", version 9.0.1

      You Asked
Dear Tom,

  I have a peculiar requirement.  I don't know how to put it in english.  So
please go through the following scripts: -

create table issue
(Code Varchar2(10) Primary Key,
 Item Varchar2(100),
 Lot_No Varchar2(20),
 Quantity Number(12,2)
)
/

create table issuepacking
(Issue_Code Varchar2(10) References Issue(Code),
 Pack_Size Number(3),
 Num_Pack Number(3),
 Remarks Varchar2(100),
 Primary Key (Issue_Code, Pack_Size)
)
/

insert into issue values ('1', 'Keyboard', 'K001', 100)
/
insert into issuepacking values ('1', 25, 4, '25X4')
/
insert into issue values ('2', 'Mouse', 'M001', 200);
/
insert into issuepacking values ('2', 50, 3, '50X3 + 25X2')
/
insert into issuepacking values ('2', 25, 2, '50X3 + 25X2')
/

column item format a10
column remarks format a15

select * from issue
/
CODE       ITEM       LOT_NO                 QUANTITY
---------- ---------- -------------------- ----------
1          Keyboard   K001                        100
2          Mouse      M001                        200

select * from issuepacking
/
ISSUE_CODE  PACK_SIZE   NUM_PACK REMARKS
---------- ---------- ---------- ---------------
1                  25          4 25X4
2                  50          3 50X3 + 25X2
2                  25          2 50X3 + 25X2

My requirement is that I want to join both the tables and the number of rows
returned should be equal to the value in the Num_Pack column.

For eg: -

Select I.Code, I.Item, I.Lot_No, IP.Pack_Size, IP.Num_Pack
From Issue I, IssuePacking IP
Where IP.Issue_Code = I.Code And Issue_Code = '1'

should return me

CODE       ITEM       LOT_NO                PACK_SIZE   NUM_PACK
---------- ---------- -------------------- ---------- ----------
1          Keyboard   K001                         25          4
1          Keyboard   K001                         25          4
1          Keyboard   K001                         25          4
1          Keyboard   K001                         25          4

and

Select I.Code, I.Item, I.Lot_No, IP.Pack_Size, IP.Num_Pack
From Issue I, IssuePacking IP
Where IP.Issue_Code = I.Code And Issue_Code = '2'

should return me

CODE       ITEM       LOT_NO                PACK_SIZE   NUM_PACK
---------- ---------- -------------------- ---------- ----------
2          Mouse      M001                         50          3
2          Mouse      M001                         50          3
2          Mouse      M001                         50          3
2          Mouse      M001                         25          2
2          Mouse      M001                         25          2

Thank you.

      and we said...
Select I.Code, I.Item, I.Lot_No, IP.Pack_Size, IP.Num_Pack
From Issue I, IssuePacking IP,
     (select rownum r from all_objects where rownum < 1000 ) x
Where IP.Issue_Code = I.Code And Issue_Code = '2' and ip.num_pack <= x.r;

Review &
      Followup

      Rating:
      5
Excellent  March 07, 2003
Reviewer:
      Sri  from NJ
  Thanks a lot Tom.  But I think, you meant '... x.r <= ip.num_pack' instead of
'ip.num_pack <= x.r'.  I mean, like

Select I.Code, I.Item, I.Lot_No, IP.Pack_Size, IP.Num_Pack
From Issue I, IssuePacking IP,
     (select rownum r from all_objects where rownum < 1000 ) x
Where IP.Issue_Code = I.Code And Issue_Code = '2' and x.r <= ip.num_pack;

Am I right?

          Followup:

doh -- yes.

that is what happens when I just type it in and don't actually run it ;)

March 07, 2003 Valeri -- Thanks for the question regarding
      "Elapsed time format change", version SQL*Plus: Release 8.0.6.0.0

      You Asked
Can I change elapsed time format to HH:MI:SS ?
and column TIMECOL have only DATE format?

SQL*Plus: Release 8.0.6.0.0 - Production on Ïòí Ñåí 1 15:13:14
2000
(c) Copyright 1999 Oracle Corporation.  All rights reserved.
Ïðèñîåäèíåí ê:
Oracle8 Enterprise Edition Release 8.0.5.1.0 - Production
With the Partitioning and Objects options
PL/SQL Release 8.0.5.1.0 - Production

orcl:demo> set timing on
orcl:demo> select empno from emp;

     EMPNO
----------
      7369
      7499
      7521
      7566
      7654
      7698
      7782
      7788
      7839
      7844
      7876

     EMPNO
----------
      7900
      7902
      7934

14 ñòðîê âûáðàíî.

 èñòèííîå âðåìÿ: 550 <--------?
orcl:demo>

      and we said...
On NT, SQLPlus does this format and this format only until
version 8.1.  In Oracle8i -- sqlplus on NT does the same as the
Unix variants do.  Prior to that, no, you cannot change the
format.
Review
      & Followup

      Rating: 2
WHAT IS THIS
      FORMAT  October 01, 2001
Reviewer:
      A reader
Elapsed: 00:00:01.90

what is the above format

and I heard , the format differs from platform to platform

can you kindly list the format by platform

          Followup:

hh:mm:ss.hh

hours, minutes, seconds, hsecs.

The format no longer varies -- should be the same for all in 8i and up.

      Rating: 2
Trouble in
      windows98   March 07, 2003
Reviewer:  Adrian
       from Lima, Peru
Hi Tom, i have tried to change the format in DB 8i standard edition can u help
me ?
there is the code:

SQL> select sysdate from dual;

SYSDATE
-------------------
07/03/2003 18:00:08

 real: 490

SQL>

why isn't the format in hh:mm:ss ???

Best regards

          Followup:

because you are using sqlplus from version 8.0....

I believe it was 8i that they changed that on windows -- but it is "burnt" into
sqlplus -- you have no control over it whatsoever.  You achieve the same effect
-- you are getting a timing in thousands of seconds.

if you are using 8i sqlplus - then maybe it was 9i they changed it -- I forget,
it was a while ago anyway.  I have no windows machines to test with and see.

March 07, 2003 Ambruzs -- Thanks for the question regarding
      "dbms_alert_info", version 8.1.6.1

      You Asked
Hello Tom,

I just want to ask you about the Alerts in Oracle.
Especially the dbms_alert_info table.

When a session is registered for an alert a new record inserted
into the dbms_alert_info table with its session Id and the
registered alert name etc...
But when the session is logged out the record is still in the
table if I have not removed by the dbms_alert.remove procedure
for example.
If I forget to do that or the session is killed how will be
deleted this record in the dbms_alert_info table?
Is it possible to delete by DELETE command from SQL, or there is
something in the database what delete automatically all rows of
all 'not alive' sessions?

Thank you very much!

 Ambruzs

      and we said...
If a session dies without removing the alert, that alert is
eventually (but not immediately) cleaned up.

One way to clean up a dead alert is to in another session
register for and then remove that alert.  that'll get the last
dead one out of there.

Review &
      Followup

      Rating: 5
what's SID
      column in dbms_alert_info  March 07, 2003

Reviewer:  Robert Xuequn Xu
Sorry to wake up this old thread, but the data of SID in dbms_alert_info is a
little cryptic. The doc says it's session ID - but what session ID? For example
here's what I got:

15:24:32 SQL> select * from sys.dbms_alert_info;

NAME               SID                            C MESSAGE
------------------ ------------------------------ - ----------
LIMS$BACKGROUND    0045FEB10001                   Y

Which session is waiting for this alert? In other words, how can I find which
session is "0045FEB10001"? Thanks!

          Followup:

ops$tkyte@ORA920> select to_number( '0045', 'xxxx' ) sid, to_number( 'feb1',
'xxxx' ) serial#
  2  from dual;

       SID    SERIAL#
---------- ----------
        69      65201

1 row selected.

Now -- on some ports, you may have to swap bytes so that would be:

  1  select to_number( '4500', 'xxxx' ) sid, to_number( 'b1fe', 'xxxx' ) serial#
  2* from dual
ops$tkyte@ORA920> /

       SID    SERIAL#
---------- ----------
     17664      45566

1 row selected.

instead.

March 08, 2003 --
      Thanks for the question regarding "ORA-22818: subquery expressions not
      allowed here.", version 9

      You Asked
Hi Tom I have the follwoing query when i run this query i get this error Need
help on the same ORA-22818: subquery expressions not allowed here.

SELECT
  B.Vendorcat,
  A.vendorid,
  A.VendorFIRST_NM||' ' ||A.Vendor_MI_NM||' ' ||A.Vendor_LAST_NM,
  A.Vendoreftcd,
  B.vendorcatbegindate,
  decode(to_char(A.VendorADD_DT, 'mm/yyyy'), to_char((select max(VendorADD_DT)
from A), 'mm/yyyy'), 'Y', 'N')
FROM
  A,
  B,
  C,
  D,
  E
WHERE
  ( D.TRANS=C.TRANS and D.PAIDDT=C.PAIDDT  )
  AND  ( A.vendorid=E.vendorid  )
  AND  ( B.vendorid=A.vendorid  )
  AND  ( D.TRANS=E.TRANS and D.PAIDDT=E.PAIDDT  )
GROUP BY
  B.Vendorcat,
  A.vendorid,
  A.VendorFIRST_NM||' ' ||A.Vendor_MI_NM||' ' ||A.Vendor_LAST_NM,
  A.Vendoreftcd,
  B.vendorcatbegindate,
  decode(to_char(A.VendorADD_DT, 'mm/yyyy'), to_char((select max(VendorADD_DT)
from A), 'mm/yyyy'), 'Y', 'N')

      and we said...
in the following, pretend HIREDATE is your vendorAdd_DT and EMP = A, B, C, d, E

This query most aptly describes what you are trying to do,  you are using group
by to distinct:

scott@ORA920> select distinct deptno,
  2         decode( to_char(hiredate,'mm/yyyy'), to_char((select max(hiredate)
from emp), 'mm/yyyy'), 'Y', 'N' )
  3    from emp
  4  /

    DEPTNO D
---------- -
        10 N
        20 N
        20 Y
        30 N

This works -- gives the same answer but isn't as obvious as to your intentions:

scott@ORA920> select deptno,
  2         decode( trunc(hiredate,'mm'), max_hiredate, 'Y', 'N' )
  3    from emp,
  4         (select max(trunc(hiredate,'mm')) max_hiredate from emp)
  5   group by deptno,
  6         decode( trunc(hiredate,'mm'), max_hiredate, 'Y', 'N' )
  7  /

    DEPTNO D
---------- -
        10 N
        20 N
        20 Y
        30 N

Review &
      Followup

      Rating:
      3
  March 10, 2003
Reviewer:
      A reader
Hi Tom

Thank you

March 08, 2003 Munzer -- Thanks for the question regarding
      "Limiting Web Page Rows", version 8.1.7

      You Asked
Tom:

1.  When you create a stored procedure that creates a list of recrods in html
format and let us say the query may result in hundreds or thousands of records.

Can you run into a problem if you do not limit the number of records listed on
one page.

2.  Do you usually implement it so that procedure will list 50 records and then
you will create two buttons "Next 50" and "Previous 50".

3.  If you do the buttons would those be stored procedures.  Did you already put
an example of this on your web site.

Thank you,

      and we said...
1) sure, the page will be really big and the end users will get really mad.

2) I use the technique demonstrated here:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064
that is how asktom works for example with its search.

3) everything to me is a stored procedure -- yes, the ACTION of the form
surrounding the buttons would be a stored procedure that would run the query as
shown in #2 with different max/min values.
Review &
      Followup

      Rating: 5
Limiting
      Records  March 30, 2002
Reviewer:  munz
       from reston, usa
Tom:

1.  OK let us say you create a hyperlink ir a button and when clicked or presses
it runs the SQL query you define.

If min_rows=1 and Max_rows=25 is hardcoded in the procedure it will always
select the same rows.  Where do you change those parameters in the link and how
do you know that this is the second set user asks for or fourth set and so on.

You are basically calling the same page with the link.

          Followup:

why would you hardcode it?  pass in the row to start with, let it default to 1.
This routine paginates over EMP:

create or replace procedure paginate( p_start in number default 1 )
as
    l_next     boolean default false;
    l_pagesize number default 5;
begin
    htp.bold( 'p_start = ' || P_start );
    htp.p( '<table>' );
    for x in ( select *
                 from ( select a.*, rownum r
                          from emp a
                         where rownum <= p_start+l_pagesize ) a
               where r >= p_start )
    loop
        l_next := ( x.r = p_start+l_pagesize );
        htp.p( '<tr><td>' || x.r || '</td><td>' ||
                x.ename || '</td><td>' || x.empno || '</td></tr>' );
    end loop;
    htp.p( '</table>' );

    if ( p_start > 1 )
    then
        htp.formOpen( 'paginate' );
        htp.formHidden( 'p_start', p_start-l_pagesize );
        htp.formSubmit( cvalue => 'prev '  || (p_start-l_pagesize) );
        htp.formClose;
    end if;
    if ( l_next )
    then
        htp.formOpen( 'paginate' );
        htp.formHidden( 'p_start', (p_start+l_pagesize) );
        htp.formSubmit( cvalue => 'next ' || (p_start+l_pagesize)  );
        htp.formClose;
    end if;
end;
/

      Rating:
      5
Parag  March 31, 2002
Reviewer:
      Parag  from India
Very Usefull Material.

      Rating: 5
Limiting
      Rows  March 31, 2002
Reviewer:  Munz
       from Reston, USA
Tom:

This is great answer.  You are one of the very few great people on earth.

      Rating: 5
paginate
      code  January 21, 2003
Reviewer:  mo

Tom:

There is a problem with the above paging code. Here are the results:

p_start = 1
1 SMITH 7369
2 ALLEN 7499
3 WARD 7521
4 JONES 7566
5 MARTIN 7654
6 BLAKE 7698
NEXT 6

p_start = 6
6 BLAKE 7698
7 CLARK 7782
8 SCOTT 7788
9 KING 7839
10 TURNER 7844
11 ADAMS 7876
PREV 1
NEXT 11

p_start = 11
11 ADAMS 7876
12 JAMES 7900
13 FORD 7902
14 MILLER 7934
PREV 6

1. You can see that some names are repeated each page.  Each page should display
only 5 records.  The buttons should say "PREV 5" and "NEXT 5". I took the equal
sign from the query but it seems I can not get the "NEXT" button.

What is this mean:
 l_next := ( x.r = p_start+l_pagesize );

l_next is boolean. are you comaring x.r to a total. I am using a ref cursor and
not an implicit cursor like this example.

Any fixes?

Thanks,

          Followup:

no problem -- i did that on purpose actually.  If you don't like the way it
works -- just nudge the numbers.

Me -- I like to have the last row from the prev page on the top of the next
page...

Any fixes?  you got the code -- and it is just a mathematical alogorithm --
adjust as you see fit!  (really, use some *creativity* -- this is all about
ideas here, take them, make them your own, apply them)

      Rating:
      5
  January 21, 2003
Reviewer:  mo

Tom:

I did but I can't understand this:

l_next := ( x.r = p_start+l_pagesize );

what does this mean in pl/sql?

How do you decide if there should be a "NEXT" button or not? DO I have to count
all records in the result set before I present the 5 record-page query?

THank you

          Followup:

l_next is a boolean (takes true, false or NULL)

x.r = p_start+l_pagesize

is a boolean expression that returns true, false or NULL.

I am assiging:

o true to l_next IF x.r is equal to p_start+l_pagesize
o FALSE to l_next if x.r is NOT equal to ....
o NULL if x.r, p_start, or l_pagesize is null (and hence the answer is not
known)

I am going through the result set.  x.r is in the domain of:

p_start TO p_start+l_pagesize

(the query does that).  If we get to where x.r (the current rownumber) is
p_start+l_pagesize - that means "hey, there is more data to come, we aint' going
to see it with this query cause we said only give us up to p_start+l_pagesize
rows -- but they are there"...  so, we give them a next button to press in order
to starting showing the data at a different p_start offset.

Now -- do you see me counting anything anywhere there?  did I have to count them
all first?  No -- you do not.

      Rating:
      5
paging  January 21, 2003
Reviewer:
      mo
Tom:

Let us say emp table has only 6 records. BAsed on the code it will display one
page with 6 records and a next button.

p_start = 1;
l_pagesize := 5;

for x in ( select *
                 from ( select a.*, rownum r
                          from emp a
                         where rownum <= 6 ) a
               where r >= 1 )
    loop
        l_next := ( x.r = p_start+l_pagesize );

The innermost query will result in 6 rows selected. When we get inside the loop,
the sixth pass will have x.r =6 = p_start + l_pagesize which will result in
l_next being true.

So it will show a "NEXT" button even though there are only six records in the
table and they are displayed on the page.

The only way this will work if we retrieve 6 records in the innermost query but
only display 5 within the loop and check if there was a sixth one and then set
l_next to true based on that one. Am I missing somthing here?

          Followup:

I know, it does that by design.  I figured "hey, that is not a big deal, end
user will actually live with it"

YOU (as i've said) can do ANYTHING AT ALL you wish to do.  go for it!  Please,
take the ideas from here, apply them as you see fit.  Me, I did it my way --
you, do it your way.

This is just *math* here, make the numbers add up the way you want.

      Rating: 3
Limiting Web
      Page Rows in Oracle 7.3.4  January 22, 2003

Reviewer:  Michael Maramzin  from Kharkov, Ukraine

This statement will be work in Oracle 7.3.4 if don't include the ORDER BY
clause.

select * from (
  select a.*, rownum rnum
  from ( YOUR_QUERY_GOES_HERE -- including the order by ) a
  where rownum <= MAX_ROWS
)
where rnum >= MIN_ROWS
/

Can I limiting web page rows in Oracle 7.3.4?

Thank you

          Followup:

nope.  you cannot use this technique prior to 8i

      Rating: 5
web
      paging  March 08, 2003
Reviewer:  mo

Tom:

The pagination code you listed works very nicely in applications. However, I am
having a proble with the <FORM> tags. Here is WHat I have:

User enters a search criteria in a query page and then i go to "results page"
with 10 records displayed.  If there are more than 10 I display "next 10
records".  The link has its own form that submits it to the results page again
with a different start count.

The problem is that when I display the 10 records I display them in their own
form which submits it to an "update procedure".  IF the user decide to update a
value and hit "SAVE" it should submit the first form values to the update
procedure.  Problem is that "SAVE" is not working because of the above two forms
"NEXT" and "RREV".  Is there a way around this other than moving the save button
on top of the "PREV" and "NEXT" links since it does not look very good.

<form name=save method="post" action="update_list">

<form name=next method="post" action="results_list">
<A HREF="javascript:doSubmit_next();">Next 10 Records</A>
</form>
<form name= prev method="post" action="results_list">
<A HREF="javascript:doSubmit_prev();">Previous 10 Records</A>
</form>

<INPUT type="submit"  name="button"  value="    Save     ">
</form>

          Followup:

time to get an html and javascript book.

if you have an Oracle database question....

March 09, 2003 Yakgna -- Thanks for the question regarding
      "Optimal Flexible Architecture ", version 9.2.0

      You Asked
Tom,

I would like to know about Optimal Flexible Architecture (OFA)from you. I dont
find it here. if its been already in this archive, please let me know the link.

Thanks for your great job,..

Yakgna

      and we said...
I don't really have anything to say about it

http://tahiti.oracle.com/pls/db92/db92.drilldown?word=ofa&book=&preference=
that link does however.

It is just a naming/placement convention.  Albeit a useful one but just a
naming/placement convention.

If you have a specific question about it -- fire away.
Review &
      Followup

      Rating: 4
Optimal
      Flexible Architecture - eliminates fragmentation ?  March
      09, 2003
Reviewer:  Yakgna  from India
from the link....

OFA is designed to:
.
.
.
.
.Help eliminate fragmentation of free space in the data dictionary, isolate
other fragmentation, and minimize resource contention
-this is the one in which i am interested to ask/know. how is that??

...Thanks

          Followup:

good question, do you have a direct url to those rather mysterious comments.
out of context, I cannot say.

that link points to many documents -- please be a tad more specific as to what
I'm to look at.

      Rating: 3
here's that
      link...  March 09, 2003
Reviewer:  Yakgna
       from India
http://download-west.oracle.com/docs/html/A97297_01/appg_ofa.htm#sthref787

          Followup:

if you read on down, they mention:

Separation of Tablespace Contents

Tablespace contents are separated to:

    * Minimize tablespace free space fragmentation
    * Minimize I/O request contention
    * Maximize administrative flexibility

meaning they start by putting things in separate tablespaces by how they are
used.

In this day and age of LMTs -- it is fairly meaningless to talk of free space
fragmentation anymore.  mostly applies to people who are using the old way
(DMT's) and only applies to the initial setup of the DB

You are free to totally mess it up after the install ;)

March
      10, 2003 Mudassar -- Thanks for the
      question regarding "Field Level Security", version 8.1.5

      You Asked
Hello Tom,

Is it possible to use the fine-grained access control feature to
implement field-level security because this feature only
modifies the where predicate, if yes how?

If not the above mentioned way, is there any other way to
implement field-level security?

By field level security I mean that for every row you have
different requirements of data display. For e.g. for the first
row you want the salary to be hidden lets say for the manager,
but for the second row, which is lets say your sub-ordinate you
want to see all columns and so on.

Best Regards,
Thanks in advance,
Mudassar Shahzad

      and we said...
No, but using extensible SQL -- this is doable.

For example, you can write a function in PLSQL that takes the
EMPNO and the SAL fields and decides -- depending who is logged
in -- whether to return the SAL field or some other value (eg:
NULL).  You would then create a view that exposed THIS column --
not the sal column.  You can even go as far as to create an
instead of trigger on this view that would permit the end user
to update (or not) this field.
Review &
      Followup

      Rating: 5
Can you please
      comment on this.  March 09, 2003
Reviewer:
      Ajeet  from oh,usa
Hi Tom --
I have 2 sets of users and based on their user id , they need to get following
results
First set of users  --

Select fiscal_month,decode(cost_center_sec_ind,'Y','N/A',Line_Item_Purpose)
Line_Item_Purpose
from po_line_items

and second set of users --

select fiscal_month , line_Item_purpose
from po_line_items.

These are just example --- I have to extend these to many other tables and
requirements are similar but field nanes etc changes.
What is a scalable and good solution for this.Here is there any way I can use
Row Level Security (as in all other cases I have implemented RLS but is there
any way I can use RLS here).

Like you said -- I am planning to use a Pl/SQL function --
sample function which -I am thinking would be something like this -- (I have
tested it on EMP table --not on my actual production table)...

create or replace function test(v_colname in varchar2)
return number as
v_user varchar2(20) ;
v_sal  number(10) ;
begin
select user into v_user from dual ;
if v_user = 'SCOTT' then
select v_colname*1000 into v_sal from dual ;
else
select v_colname into v_sal from  dual ;
end if ;
return v_sal ;
end ;
/

What I was thinking that I can make this function generic enough --- to handle
all the tables and fields by adding more code.

Do you think it is the right approach--if not can you please suggest me a better
way to do it.I have a very large database and all the tables where I plan to
implement this approach has alot of data (table size 1GB-3 GB).

Thanks so much .
Ajeet

          Followup:

RLS isn't appropriate, this is column level.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:8358236165616
it'll tell you why you don't want to use a PLSQL function for this.

A view such as:

Select fiscal_month,
       decode( sys_context('my_ctx', 'lip_cost_center_sec_ind' ),
               'CHECK', decode(cost_center_sec_ind,'Y','N/A',Line_Item_Purpose),
               line_item_purpose ) line_item_purpose,
       ....
from po_line_items;

would be more performant.  I would assume you would/could set the application
context either in a procedure you called directly in your application or using a
logon trigger.  It would perform ONCE any procedural logic to figure out what
this person can/cannot see, set the application context and the views would use
it from then on in.

March 10, 2003 Chris -- Thanks for the question regarding
      "storing passwords in the database.", version

      You Asked
Any suggestions on a method of encrypting a column in a
table. E.g. a USERS table with a PASSWORD column which is used
by
an application to authenticate application users. We don't want
the password in plain text which could be perused using SQL*plus

or other tools even by DBA's.

      and we said...
I'll get to some possible solutions for 8.1.5 and before in a
moment but in Oracle8i, release 8.1.6 there is:

<quote>
The DBMS_OBFUSCATION_TOOLKIT Package

For applications dealing with this highly sensitive data, Oracle
provides the
DBMS_OBFUSCATION_TOOLKIT PL/SQL package to encrypt and decrypt
data, including string inputs and raw inputs. The function is
limited to selected algorithms, such as the Data Encryption
Standard (DES). Developers may not plug in their own encryption
algorithms, and the key length is also fixed. The function
prohibits making multiple passes of encryption; that is, you
cannot nest encryption calls, thereby encrypting an encrypted
value. These restrictions are required by U.S. laws governing
the export of cryptographic products.
</quote>

You can read more about it at

http://technet.oracle.com/doc/oracle8i_816/server.816/a76936/dbms_obf.htm#6518

Now, in 8.1.5 and before -- what could you do?  Well, most
passwords are not stored encrypted, but rather they are stord
HASHED or DIGESTED.  For example, in Oracle, the password is not
encrypted (that would imply there is a decrypt but there is
not).  Instead, to validate a username/password we take them,
plus some magic "salt" and hash it.  This results in a fixed
length string of some bytes of data.  We compare that to the
stored hash and if they match -- you are in.  If not -- you are
not.

So, if I was to write my own password check function, I would
simply glue the USERNAME together with the supplied PASSWORD.  I
would call dbms_utility.get_hash_value to generate some hashes.
See

http://technet.oracle.com/doc/oracle8i_816/server.816/a76936/dbms_ut2.htm#1002139
for info in the hashing package.  For example:

ops$tkyte@8i> declare
  2      function digest( p_username in varchar2, p_password in
varchar2 ) return varchar2
  3      is
  4      begin
  5          return ltrim( to_char( dbms_utility.get_hash_value(
upper(p_username)||'/'||upper(p_password),
  6
1000000000, power(2,30) ),
  7                          rpad( 'X',29,'X')||'X' ) );
  8      end digest;
  9  begin
 10      for x in ( select username from all_users where rownum
< 20 )
 11      loop
 12          dbms_output.put_line( 'User: ' || rpad( x.username
, 30 ) ||
 13                                ' digest: ' || digest(
x.username, 'TIGER' ) );
 14      end loop;
 15  end;
 16  /
User: SYS                            digest: 6869FA1A
User: SYSTEM                         digest: 79F08AFC
User: OUTLN                          digest: 5ABFB255
User: DBSNMP                         digest: 43415F6B
User: TRACESVR                       digest: 49CF26F6
User: CTXSYS                         digest: 4910C297
User: OEM                            digest: 69463BC2
User: ORDSYS                         digest: 6F048B2B
User: ORDPLUGINS                     digest: 6547459C
User: MDSYS                          digest: 43C0B367
User: AURORA$ORB$UNAUTHENTICATED     digest: 5073BBFC
User: WEB$CDEJESUS                   digest: 6FB5CDB6
User: SCOTT                          digest: 4307767C
User: WEB$SMAYFIEL                   digest: 71ED5065
User: UTILS                          digest: 5B7912B7
User: OAS_PUBLIC                     digest: 502BAE3A
User: WEBDB                          digest: 5A7AC149
User: WEB$RDRISCOL                   digest: 3E72D3F6
User: WEB$KWARREN                    digest: 7123F5A1

PL/SQL procedure successfully completed.

So, I have a function digest that takes a username and password,
hashes it into 1 of 1073741824 different numeric values, adds
1000000000 to it (to make it big) and turns it into HEX.  This
is what I would store in the database -- not the password (which
I really don't ever need to know).

Now when the user presents me a username/password, I digest it
and compare -- if they match, you get in.  If not you do not.

Note that before 8.1.5, the to_char( .., 'XXXXXX' ) format does
not work (not implemented).  See
http://osi.oracle.com/~tkyte/hexdec/index.html
for a solution in 8.0 and
7.x  (I have a to_hex routine you can use).

Review &
      Followup

      Rating:
      4
  March 21, 2001
Reviewer:  Kannan
      Arumugam  from USA
Thanks

      Rating:
      4
  April 23, 2001
Reviewer:
      A reader  from Frankfurt
just what i was looking for. Thanks a lot.

      Rating:
      5
Great  June 01, 2001
Reviewer:
      Rajashekar Vellore Natt  from San Francisco , California US

Great Job Tom. U made my job easy.

      Rating: 3
USE
      FUL  June 15, 2001
Reviewer:  RAMANA
       from HYD,INDIA
ALL THE CASES CAN BE USED
DEPENDING ON THE REQUIREMENT

      Rating: 5
Very good
        June 18, 2001
Reviewer:  Rajesh George
       from India
Thanks

      Rating:
      4
Thanks  June 19, 2001
Reviewer:
      Ariel  from Buenos Aires, Argentina
It was just that I need.

      Rating: 4
It solved all
      custom user authentification problem  June 21, 2001

Reviewer:  Ade Realdy  from Freising - Germany

Thanks very much

      Rating: 4
Cool
      one  June 26, 2001
Reviewer:  Gururaj
      Kulkarni  from Norcross GA USA
Thanks Tom

      Rating: 4
thanks, i look
      for it for a long time.  July 13, 2001
Reviewer:
      michaelzhang  from china

      Rating: 4
Password
      encryption & Decryption  July 24, 2001

Reviewer:  Vijay Fernando  from Intel India,
      Bangalore
Hey Tom,
Great stuff regarding this encryption and decryption !!!  Thanks a lot friend!
Regds,
Vijay

      Rating: 5
Greate
      solution!  September 03, 2001
Reviewer:
      dulimin  from BJ,CHINA

      Rating: 5
Greate
      solution!  September 03, 2001
Reviewer:
      dulimin  from BJ,CHINA

      Rating: 4
Great
      help  September 10, 2001
Reviewer:  Anjani
      Shetty  from Bangalore, India
Thank you, Thank YOU, THANK YOU

      Rating:
      5
  September 19, 2001
Reviewer:  Paul
      Hefford  from Brisbane, Australia
That information is just super!  Thanks!

      Rating: 4
I need a
      clarification  November 27, 2001
Reviewer:
      Mrs.Srikanth  from UK
I would like to know whether it is possible to decrypt the password when it is
encrypted using Hashing algo (DBMS_UTILITY.get_hash_value) or is there any
method in which I can achieve it

          Followup:

Ummm, sorry I thought that was clear from the statement:

....
Well, most passwords are not
stored encrypted, but rather they are stord HASHED or DIGESTED.  For example, in

Oracle, the password is not encrypted (that would imply there is a decrypt but
there is not).  Instead, to validate a username/password we take them, plus some

magic "salt" and hash it.  This results in a fixed length string of some bytes
of data.  We compare that to the stored hash and if they match -- you are in.
If not -- you are not.
.......

A hash or digest is NOT reversible (be neat if it were -- it would be the
universal compression algorithm of choice!!! ).  Two different strings can (and
will sometime) hash to the same exact value.  We are mapping a potentially
infinite space (the set of all strings) into a very finite space (the size of
our hash).  They are not reversible -- they do not have to be reversible.  All
we need to do is given a username and password verify if that username and
password is valid, hashing does this very well and very securely (for the simple
reason that it is NOT reversible)

      Rating: 5
Thanks Tom,
        November 27, 2001
Reviewer:  Andre Whittick
      Nasser  from Rio de Janeiro, Brazil
Living and learning...

This last consideration was very interesting...

      Rating:
      5
Thanks  November 27, 2001
Reviewer:
      Raj  from USA
Hats off to Tom !!!

      Rating: 4
Works right but
      one more thing  January 12, 2002
Reviewer:
      Godwin  from Ghana
Tom can u give me a simple PLSQL(procedure code) that will accept the username
and password passed as parameters to the procedure from forms data block items
to check/authenticate the user. Thus the procedure should retrieve the user from
a database table and compare it to the parameter being passed to the procedure.

e.g procedure logon(usern varchar2,passw varchar2) IS
    declare
       user_1 varchar2(40),
       passw_1 varchar2(4),
    select username, passw into user_1,passw_1
    from myuser_tab;
     begin
         IF user_1=usern AND passw_1=passw THEN

          ...Accept user)
         ELSE
          message(not a valid user try again)
         end if;
     end;
Can this code do the job?

          Followup:

you appear not to have learned how to program PLSQL.  You will not get very far
without this knowledge.  I suggest you read:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a77069/toc.htm
pretty much from cover to cover.

I would not call this procedure logon as it appears you are using forms and
forms has a builtin by that name.

procedure verify_credentials( p_username in varchar2, p_password in varchar2 )
is
   l_cnt number;
   l_password varchar(255) default get_hashed_pw( p_password );
begin
   select count(*) into l_cnt
     from myuser_tab
    where username = p_username
      and password = l_password;

   if ( l_cnt = 0 ) then
        raise_application_error( -20001, 'Invalid username/password' );
   end if;
end;

      Rating:
      4
DBMS_UTILITY.GET_HASH_VALUE  April 10, 2002

Reviewer:  Lokesh Wuluvarana  from Wisconsin

Tom,
I was planning on using using get_hash_value to create ID for customers by
putting the full name and address to make this unique. I didn't want to use the
Oracle sequence, because those can be typed by a customer incorrectly and to us
it could be valid ID for some other customer!

I am not sure if this is given out by Oracle. I wanted to know the what
algorithm is involved within this function. Is this something that I can get
from you or is it proprietory? The reason I want to know is because our system
is in Oracle9i but a system related this (having same data) is in Paradox
Database. In the paradox database the same algorithm needs to be written in
Delphi (or C) to do similar masking and validating for the customer ID. We want
to keep the two systems in sync. Please let me know.
Thanks
Lokesh

          Followup:

But a hash won't be unique!

Given a full name "Tom" and address "123 Main Street", i can get a hash of
"ABCD"
Given a full name "Bob" and address "4522 5th Avenue", I can get a hash of
"ABCD"

You have even a worse problem -- not only that but you don't solve this issue:

"because those can be typed by a customer incorrectly and to us
it could be valid ID for some other customer! "

with the hash either (at least i don't see how you do!)

We change the hash algorithm from release to release (it has happened at least
once in the past and could happen again).  You need to look at alternative ways.

      Rating:
      4
Reader  April 30, 2002
Reviewer:
      A reader
Tom,

I like to get 1000 unique numbers to concatenate with a
column to make it unique

If I do
dbms_utility.get_hash_value(to_char(sysdate,'yyyymmddh24miss.sssss'),1000,1000000
)
How to estimate if there will be collision or not , and
what is the probablity.

Date itself is unique but it will take 7 bytes, if I can
get a smaller number I can use save space

Thanks

          Followup:

Well, there is almost a 100% chance since

to_char(sysdate,'yyyymmddhh24miss.sssss')

would undoubtably return the same string if called in a loop (it is only down to
the second, hopefully your code executes faster then that).

I don't know what you mean by "date itself is unique".

I don't follow what you are trying to do, but why not juse use the numbers
1..1000?

      Rating:
      4
Reader  April 30, 2002
Reviewer:
      A reader
I am using date down to fraction of a second, this is why
I am saying date itself will be unique

yyyymmddh24miss.sssss

Thanks

          Followup:

sssss is the number of seconds since midnight.

It is not the fractional seconds.

9i has fractional second and even the -- the risk of a duplicate is still THERE.

      Rating: 4
storing
      passwords in the database  April 30, 2002

Reviewer:  Shajan Joseph  from India
This is helpful and supportive

      Rating:
      4
Confused  May 02, 2002
Reviewer:
      Pratap  from India
Hi Tom

You have used dbms_utility.get_hash_value to generate a password. But you have
also mentioned that two different strings can generate the same hash value.
(Hash values are not unique). Then is there a possibility that a person entering
the wrong password can enter the database just because that combination of
userid and password generates the same hash value as the real userid and
password? Also how does Oracle then do a hash join on tables when the hash
values are not unique.

Thanks and regards

Pratap

          Followup:

Yes, this is true of Unix, of Oracle the database, and of this method (because
they all use the same technique -- a hash).

It is considered to an a rare enough freak occurrence as to be statistically
impossible.

The hash space is HUGE, in my example, you have a 1 in 1,000,000,000 chance of
taking some arbitrary string and having it be equivalent to your password.  Your
odds of winning the lottery are many order of magnitude larger.

When doing a hash join, the hash is just the first part.  That gets us to where
the  data is POTENTIALLY located.  We still have to look at the data to see if
it is the "right" data.  The hash gets us to the data, we then look at the
actual key in the data to see if it matches (and we look at all of the data that
hashed to that place)

      Rating: 5
simply
      great  May 18, 2002
Reviewer:  Deva
       from Nagpur India
simply great tom

      Rating: 5
How to
      calculate the probability of getting a collision  June 18,
      2002
Reviewer:  Pratap  from India
Hi Tom

Is there any way we can calculate the chances of getting a collision in the
get_hash_value procedure.

Best Regards

Pratap

          Followup:

It is based on

o number of elements you are hashing
o size of hash table

as it is with all hash tables.  (in short, yes and no.  if you have 100 elements
and a hash table of 50, you expect N collisions -- at least 50.  In a perfect
world, there would be 50 but, in real life there will be more then 50 in all
probability).

      Rating:
      5
hashing  September 18, 2002
Reviewer:
      mo
TOm:

1.can you explain why you combine username and password in hashing as below? why
not just password? No one can read any hex anyhow?

2. what is the power(2,30) and rpad statement for?

Are you putting blanks before the hashed password and for what? I used on a
varchar2(15) column and it did not work?
do I need leading blanks in my hashed password.

 return ltrim( to_char( dbms_utility.get_hash_value(
   upper(p_username)||'/'||upper(p_password),          1000000000, power(2,30)
), rpad( 'X',29,'X')||'X' ) );

          Followup:

1) if we just did password and you and i both used "tiger" as the password -- it
would hash to the same value.

I would know your password then.  by putting the username in there, our
passwords "tiger" hash to different values.

2) select out the power( 2, 30 ) and you'll see its a number -- i'm setting the
bounds for the hash table.  rpad is used to build a string of x's for me to
convert from decimal to hex.

      Rating: 4
Thank you Tom
      and Kannan Arumugam   October 04, 2002
Reviewer:
      Sarath Chander  from Hyderabad, AP, INDIA
This has been a point in our web based application. Though we deferred from
implementing any logic immediately, I hope we can implement the logic provided
here.

Thanks once again.

      Rating: 4
An important
      clarification on Storing Passwords  October 04, 2002

Reviewer:  Suresh  from India
AS YOU SAID
We change the hash algorithm from release to release (it has happened at least
once in the past and could happen again).  You need to look at alternative ways
***********

What do you mean by this. Will the password created using this hash algorithm
may become unreadble after a new release? If the password is 'tiger' do we get
the same hash value through an old hash algorithm and new hash algorithm?
What impact does an database upgrade have on this password hashing technique?

Please clarify. This is important for implementation of this technique.

          Followup:

it means you might consider using your own hashing algorithm if you want to
permanently store a hash just for your own protection (although if you use the
MD5 hash in the dbms_obfuscation_toolkit package available with 817 and up --
you would be safe since MD5 hashes are standard)

      Rating: 4
context of
      customized password verification  January 29, 2003

Reviewer:  Chandra  from DELHI, INDIA
Hi Tom,

In what context one would be using this kind of a customized credential
checking? I am little confused ... because in order to run the digest function
and compare the passed credentials with that stored in the DB, one need to
connect to DB. Are you saying we'll be using some kind of generic user (say
LOGON_USER) which will have just select privileges on the password stored table.
And once the credentials supplied are correct, it will disconnect and re-connect
as the new user?? Not really convincing ... I know I am missing something ...
please throw some light ...

Thanks, Chandra

          Followup:

it is when you are doing your own authentication -- connect to db as common user
and verify that the user/pw the end user gave you is valid and then continue.

it is for the "do it yourself" people.

      Rating:
      5
  January 29, 2003
Reviewer:
      A reader
Why did you use rpad( 'X',29,'X')||'X'

and not simply

rpad( 'X',30,'X')

          Followup:

good question -- if I had a reason, I've forgotten what it was ;)

probably snipped a piece of code where the 29 was "length(variable)-1" and in
the event variable was null -- I wanted at least one 'x' (but I'm totally
guessing ;)

      Rating:
      5
Excellent  January 29, 2003
Reviewer:
      Mike  from St. Louis, MO
Great thread -- very beneficial to me.

      Rating:
      5
  January 29, 2003
Reviewer:  Nat

      Rating: 4
could not find
      question  February 20, 2003
Reviewer:  Scott
       from Perth, WA
G'day Tom.

RE:
http://asktom.oracle.com/pls/ask/f?p=4950:61:15468414540126688::::P61_ID:258715248980

The two suggestions for password changing algorithms have been fair, but with
minor drawbacks, well, major because they can't be used.

The first, by changing the password, changing it back if invalid etc. does not
work when the profile of the user has restrictions on the type of password that
can be used - ie - can't re-use the same password. (default
PASSWORD_VERIFY_FUNCTION)

The 2nd idea of creating 26 'dummy' one letter accounts doesn't go down to well
with our DBA, besides the fact that it does restrict the combined
username||password to 30 characters.

I hate to ask (but my DBA would like me to), but is there *another* alternative?

And in attempting to find this alternative, I found another minor problem: The
package used to change the user's password MUST be AUTHID CURRENT_USER, and so
must be the caller.

On a side note, as far as parameter passing goes for verifying these passwords,
are the plain text password strings sent across the web during this validation?
Should this be an issue?

And as for your upgrade, is there a particular reason why we can't add a review
after navigating to a question from the "Your Questions" tab (no question date
either)? I had MUCH difficulty finding the post (linked at the top of the page)
to add this review (even looked through the archives). This is the most similar
question I could find.

Thanks.

          Followup:

set up a test database -- one you want to "crack against".

then in the "real database"

select 'create user ' || username || ' identified by values "' || password ||
'";'
from dba_users;

spool that, run it in the test database and test them.  I'm assuming you want to
do a password verification, to make sure they are "good passwords" so you would
be guessing from a dictionary somewhere.  A sample database on a machine
elsewhere in the network will do that for you.

26 dummy accounts shouldn't be a major issue -- the accounts would be locked --
i don't see the issue there (other then the obvious hit on the data dictionary).

the package does not need to be authid current_user, the owner of the package
would need ALTER USER granted to them (directly, NOT via a role)

If the passwords are sent in plain text "over the web" -- that would be only
because you are not using SSL between the browser and the webserver.  If you use
SSL -- no clear text.  If you do not use SSL, clear text.  That choice is yours.

If you are concerned about the network between the webserver and the database --
we have encryption technologies for that as well.

I'll ask them to add that to the "your questions" linked documents.

      Rating: 2
Can't do it
      remotely  February 27, 2003
Reviewer:  Scott
       from Perth, WA
G'day

Tried the remote option, where a 'test' db is set up to create dummy accounts
upon a password verification request;

ERROR at line 1:
ORA-02064: distributed operation not supported
ORA-06512: at "SYS.FN_CHECK_PW", line 94
ORA-06512: at line 4

Cannot perform DDL over a RPC, since it performs an implicit commit.

Further details:

http://metalink.oracle.com/metalink/plsql/ml2_documents.showDocument?p_database_id=FOR&p_id=88935.999

          Followup:

i know you cannot -- if you want to do it in "real time", you need to do it as
described above or not at all.

If your goal is to "crack" -- verify the passwords are not guessable -- you
would not be doing this over a dblink.

those are the ONLY options.

      Rating:
      4
dbms_obfuscation_toolkit and MD5?  February 27, 2003

Reviewer:  Chuck Jolley  from OKC, OK USA
I am using your digest function to mangle passwords for our application users so
that they can't log into the database using other tools. (They don't know their
REAL password)
I was going to convert over to the suggested MD5 functionality so that we won't
have to reset everyones password if we upgrade.
But I can't find ANY documentation about MD5 hashing using the obfuscation
toolkit.
Our DB version is 8.1.7
Any pointers would be helpfull.
Thanks,
Chuck

          Followup:

If you have my book "Expert one on one Oracle" -- I've got it in there (even
pointed out "they forgot to document it"

but basically -- the routines are very simple:

    -------------------------------- MD5 --------------------------------------
    -- The following routines generate MD5 hashes of data.
    -- There are two versions: one for raw data and the other for strings.
    ---------------------------------------------------------------------------

    PROCEDURE MD5(input    IN  RAW,
                  checksum OUT raw_checksum);

    FUNCTION MD5(input    IN  RAW)
        RETURN raw_checksum;

    PROCEDURE MD5(input_string    IN     VARCHAR2,
                  checksum_string    OUT varchar2_checksum);

    FUNCTION MD5(input_string    IN     VARCHAR2)
        RETURN varchar2_checksum;

send a string in, get a checksum back.  Suggestion

o use the RAW functions
o use utl_raw.cast_to_raw(str) on input
o assign the output to a varchar2 -- it'll be a big hex string that is "safe"

      Rating:
      4
MD5  February 28, 2003
Reviewer:
      Doug  from Salt Lake City, Ut
   -------------------------------- MD5 --------------------------------------
    -- The following routines generate MD5 hashes of data.
    -- There are two versions: one for raw data and the other for strings.
    ---------------------------------------------------------------------------

    PROCEDURE MD5(input    IN  RAW,
                  checksum OUT raw_checksum);

    FUNCTION MD5(input    IN  RAW)
       RETURN raw_checksum;

    PROCEDURE MD5(input_string    IN     VARCHAR2,
                  checksum_string    OUT varchar2_checksum);

    FUNCTION MD5(input_string    IN     VARCHAR2)
        RETURN varchar2_checksum;

END dbms_obfuscation_toolkit;

1. How can you have two procedures (or functions) named the same? It seems like
you would have to select the type of routines you want (Raw or string) and then
'comment out' or rename the other type.

2. What if you wanted to use both types? We can't rename the procedure or
function because the package body is wrapped.

          Followup:

1) it is called overloading -- it works.

2) use them, they work

ops$tkyte@ORA920> declare
  2      something_raw  raw(20) := utl_raw.cast_to_raw( 'Hello World' );
  3      something_cooked varchar2(20) := 'Hello World';
  4  begin
  5      dbms_output.put_line
  6      ( dbms_obfuscation_toolkit.md5( input => something_raw ) );
  7
  8      dbms_output.put_line
  9      ( dbms_obfuscation_toolkit.md5( input_string => something_cooked ) );
 10
 11      dbms_output.put_line
 12      ( utl_raw.cast_to_raw(
 13           dbms_obfuscation_toolkit.md5( input_string => something_cooked )
 14      ));
 15  end;
 16  /
B10A8DB164E0754105B7A99BE72E3FE5
±
±dàuA·©ç.?å
B10A8DB164E0754105B7A99BE72E3FE5

PL/SQL procedure successfully completed.

      Rating: 5
Works
      Great!  February 28, 2003
Reviewer:  Chuck
      Jolley  from OKC, OK USA
Naturaly, the book was 18" from my elbow. :)
I just didn't think to look.
Here's what I came up with.

function blenderize_logon_user(username varchar2, password varchar2) return
varchar2
is
v_count number := 0;
v_new_pw varchar2(256);
begin
   select count(*)
     into v_count
     from sys.dba_role_privs
    where grantee = upper(username)
      and granted_role = 'APP_ROLE';

   if v_count > 0 then --if app user user
      v_new_pw := dbms_obfuscation_toolkit.md5(input =>
utl_raw.cast_to_raw(upper(username)||'|'||upper(password)));
      v_new_pw := 'x' || substr(v_new_pw, 1, 29);
   else
      v_new_pw := password;
   end if;

   return v_new_pw;
end;

Thanks,
chuck

      Rating: 3
Reversibility
      of the Hashing Algorithm  March 10, 2003
Reviewer:
      Nurlan  from Kazakhstan
Hello Tom,
The message roll has been very useful to me. I was actually reading some stuff
on hashing algorithm. Let me ask one question, maybe I am missing something
here.
If I change password to a new value and keep old password's hash value from
dba_users view and issue
ALTER USER USER_NAME IDENTIFIED BY VALUES 'HASH_VALUE',
I shall end up with the old password. Could it be considered as hash algorithm
reversing?

          Followup:

No, do you know the password?  Nope.  It would be reversing if you could get the
password back.

all you have is a bunch of bytes.

      Rating: 3
pin number
      generation  August 26, 2003
Reviewer:  vj
       from in
Tom,

My Database : 9IR2.

We got to generate the PIN number for the account holders. This has got to be an
8 digit number. Which is the right approach for the generation / cryption.

i came up with something like this. but the overall feeling is exposing the
magic key...

PACKAGE BODY Pk_Encryption AS

--declare the values of the key
v_keystring   VARCHAR2(32):='MYMAGICSTRING';
v_encryptstr  VARCHAR2(2048);
v_decryptstr  VARCHAR2(2048);

--function encrypt encrypts the string
FUNCTION fn_encrypt(i_inputstring IN VARCHAR2) RETURN VARCHAR2 IS

BEGIN
    v_encryptstr := NULL;

    DBMS_OBFUSCATION_TOOLKIT.DES3ENCRYPT(input_string=> i_inputstring,key_string
=>v_keystring,encrypted_string =>v_encryptstr);
--    RETURN UTL_RAW.cast_to_raw(v_encryptstr);
    RETURN v_encryptstr;
END fn_encrypt;

--function decrypt encrypts the string
FUNCTION fn_decrypt(i_inputstring IN VARCHAR2) RETURN VARCHAR2 IS
BEGIN
    v_encryptstr  := NULL;
    v_decryptstr  := NULL;

DBMS_OBFUSCATION_TOOLKIT.des3decrypt(input_string=>i_inputstring,key_string=>v_ke
ystring,decrypted_string=>v_decryptstr);
    RETURN v_decryptstr;
END fn_decrypt;

END Pk_Encryption;

Rgds

      Rating:
      5
  August 27, 2003
Reviewer:
      A reader

March 10, 2003 Eugene -- Thanks for the question regarding
      "copying inserted values", version 8.1.7

      You Asked
Hello Tom,
I have what's probably an easy task, but I cannot seem to figure out how to
accomplish it. I have a situation where user is inserting a "comment" into one
of the lines on the purchase order. I need to copy that same "comment" on all
remaining lines of the same purchase order. I was trying "after insert" trigger,
but it gives me mutating table error. What is the proper/best way to do it?
Thanks for you help,
Eugene

      and we said...
design your tables properly.  Obviously this comment belongs in the parent
table, not the child table.

That is the proper/best way to do this -- no if and or buts about it.

Short of that, you can write a ton of code:

http://asktom.oracle.com/~tkyte/Mutate/index.html
but after many years what I've discovered is that if you are actually hitting a
mutating table constraint -- 9,999 times out of 10,000 -- it is due to a fatal
design flaw.  the mutating table should be taken as a warning that "you're doing
it wrong, think about it again"

Review &
      Followup

      Rating: 3
I guess I'll
      hate to "write a ton of code"  March 10, 2003

Reviewer:  Eugene  from Seattle
Tom,
I understand what you are saying, but I didn't designed the software that my
company uses. I guess I'll have to write a ton of code as you mentioned.
Thanks,
Eugene

          Followup:

but you are coding it?  so you can in fact fix it?

I mean, if you are asking this question -- you are in the code right now.

March 10, 2003 --
      Thanks for the question regarding "COMPLEX QUERY", version 9.2

      You Asked
HI TOM

i have table contain

sql> select UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE from tcon where
unit_prefix='UACU5001497';

UNIT_PREFIX     PORT            ACTIVITY_COD ACTIVD          CONT
--------------- --------------- ------------ --------------- ----
UACU5001497     IQUQR         * DCHF         05-JAN-02       HC
UACU5001497     IQUQR           LODE         23-MAR-02       HC
UACU5001497     IQUQR           SNTC         23-MAR-02       HC
UACU5001497     IQUQR           RCVE         23-MAR-02       HC
UACU5001497     AEKLF           DCHE         01-APR-02       HC
UACU5001497     AEKLF           LODE         06-APR-02       HC
UACU5001497     KRPUS           DCHE         20-APR-02       HC
UACU5001497     KRPUS           LODE         27-APR-02       HC
UACU5001497     JPHKT           DCHE         02-MAY-02       HC
UACU5001497     JPHKT           SNTS         15-MAY-02       HC
UACU5001497     JPHKT           RCVS         12-JUN-02       HC
UACU5001497     JPHKT           LODF         20-JUN-02       HC
UACU5001497     KRPUS           DCHT         21-JUN-02       HC
UACU5001497     KRPUS           LODT         22-JUN-02       HC
UACU5001497     AEKLF           DCHT         07-JUL-02       HC
UACU5001497     AEKLF           LODT         07-JUL-02       HC
UACU5001497     KWSAA         * DCHF       * 11-JUL-02       HC
UACU5001497     KWSAA           SNTC         17-JUL-02       HC
UACU5001497     KWSAA         * RCVC       * 20-JUL-02       HC
UACU5001497     KWSAA           SNTR         20-JUL-02       HC
UACU5001497     KWSAA           RCVR         24-JUL-02       HC
UACU5001497     KWSAA           SNTS         24-JUL-02       HC
UACU5001497     KWSAA           RCVS         30-JUL-02       HC
UACU5001497     KWSAA           LODF         03-AUG-02       HC
UACU5001497     AEKLF           DCHT         05-AUG-02       HC
UACU5001497     AEKLF           LODT         10-AUG-02       HC
UACU5001497     HKHKG         * DCHF       * 21-AUG-02       HC
UACU5001497     HKHKG           SNTC         22-AUG-02       HC
UACU5001497     HKHKG         * RCVC       * 23-AUG-02       HC
UACU5001497     HKHKG           SNTS         12-SEP-02       HC
UACU5001497     HKHKG           RCVS         14-SEP-02       HC
UACU5001497     HKHKG           LODF         16-SEP-02       HC
UACU5001497     USORF           DCHF         24-OCT-02       HC
UACU5001497     USORF           SNTC         30-OCT-02       HC
UACU5001497     USORF           RCVE         08-NOV-02       HC
UACU5001497     USORF           SNTS         22-NOV-02       HC
UACU5001497     USORF           RCVF         25-NOV-02       HC
UACU5001497     USORF           LODF         02-DEC-02       HC
UACU5001497     AEKLF           DCHT         30-DEC-02       HC

the table conatin a lot of unit_prefix and each unit have a diffrent type

and i need to run query that get diff DAYS between activd for unit_prefix ie. i
look at first 'DCHF' date - first'RCVC' date
and then next 'DCHF' DATE with next 'RCVC' date
for the above sample query the first 'DCHF' at '5-jan-02' there is no activity
'RCVC' since it reach to next 'DCHF'
for example diff for 'DCHF'-'RCVC' IS '20-JUL-02' - '11-JUL-02' = 9day's
 and then
the second Diff is '21-AUG-02' -'23-AUG-02'
and so on

if the diffrent is more than 10 days for type 'HC' then the cost is the number
of different days * cost for 'HC',if the  diffrent is more than 5 days for type
'ot' then the cost for diffrent days * cost for 'ot' .

example of output

unit_prefix     diff   cost
------------    -----  ----
xxxx             10    150

also i should exclude the off days ('sat','sun') from my calculation for
different days

second

i need to run query get the unit_prefix waiting (no activity) for more than 60
day's

create table tcon (unit_prefix varchar2(25),port varchar2(10),activity_code
varchar2(10),activd date,container_type varchar2(7))

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","AEKLF","DCHE","01-APR-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","JPHKT","DCHE","02-MAY-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","KRPUS","DCHE","20-APR-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","IQUQR","DCHF","05-JAN-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (

"UACU5001497","KWSAA","DCHF","11-JUL-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","HKHKG","DCHF","21-AUG-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","USORF","DCHF","24-OCT-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","AEKLF","DCHT","07-JUL-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","AEKLF","DCHT","05-AUG-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","AEKLF","DCHT","30-DEC-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","KRPUS","DCHT","21-JUN-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","IQUQR","LODE","23-MAR-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","AEKLF","LODE","06-APR-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","KRPUS","LODE","27-APR-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","KWSAA","LODF","03-AUG-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","USORF","LODF","02-DEC-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","JPHKT","LODF","20-JUN-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (

"UACU5001497","HKHKG","LODF","16-SEP-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","AEKLF","LODT","07-JUL-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","KRPUS","LODT","22-JUN-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","AEKLF","LODT","10-AUG-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","KWSAA","RCVC","20-JUL-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","HKHKG","RCVC","23-AUG-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","IQUQR","RCVE","23-MAR-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","USORF","RCVE","08-NOV-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","USORF","RCVF","25-NOV-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","KWSAA","RCVR","24-JUL-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","KWSAA","RCVS","30-JUL-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","JPHKT","RCVS","12-JUN-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","HKHKG","RCVS","14-SEP-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (

"UACU5001497","IQUQR","SNTC","23-MAR-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","KWSAA","SNTC","17-JUL-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","HKHKG","SNTC","22-AUG-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","USORF","SNTC","30-OCT-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","KWSAA","SNTR","20-JUL-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","KWSAA","SNTS","24-JUL-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","JPHKT","SNTS","15-MAY-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","HKHKG","SNTS","12-SEP-02","HC")

insert into tcon( UNIT_PREFIX,PORT,ACTIVITY_CODE,ACTIVD,CONTAINER_TYPE) values (
"UACU5001497","USORF","SNTS","22-NOV-02","HC")

thank's TOM

      and we said...
Ok, here is most of the first query - you can add the calculations as you need
for the container_type -- you didn't really give me all of the necessary
informtion for that -- but it should be really easy to add:

ops$tkyte@ORA920> select unit_prefix,
  2         container_type,
  3         activity_code,
  4         activd,
  5         decode( next_activity_code,
  6                'RCVC', next_activity_code ) next_activity_code,
  7         decode( next_activity_code,
  8                'RCVC', next_activd ) next_activd,
  9         case when next_activity_code = 'RCVC'
 10              then ( select sum(decode(to_char((activd+rownum-1), 'DY'),
 11                                'SAT', 0, 'SUN', 0, 1))
 12                       from all_objects
 13                      where rownum <= next_activd-activd )
 14              else null
 15         end days_btwn
 16    from (
 17  select unit_prefix,
 18         container_type,
 19         activity_code,
 20         activd,
 21         lead(activity_code) over
 22             ( partition by unit_prefix order by activd ) next_activity_code,
 23         lead(activd)        over
 24             ( partition by unit_prefix order by activd ) next_activd
 25    from tcon
 26   where unit_prefix = 'UACU5001497'
 27     and activity_code in ( 'DCHF', 'RCVC' )
 28         )
 29   where activity_code = 'DCHF'
 30   order by activd
 31  /

UNIT_PREFIX CONTAIN ACTIVITY_C ACTIVD    NEXT_ACTIV NEXT_ACTI  DAYS_BTWN
----------- ------- ---------- --------- ---------- --------- ----------
UACU5001497 HC      DCHF       05-JAN-02
UACU5001497 HC      DCHF       11-JUL-02 RCVC       20-JUL-02          7
UACU5001497 HC      DCHF       21-AUG-02 RCVC       23-AUG-02          2
UACU5001497 HC      DCHF       24-OCT-02

Now, as for:

...
i need to run query get the unit_prefix waiting (no activity) for more than 60
day's....

I didn't get that part -- however, now that you've been introduced to lag/lead
and analytics, you should be able to accomplish that one.  Analytics rock and
roll.

Review &
      Followup

      Rating:
      5
  March 11, 2003
Reviewer:
      A reader
And so does Tom.

March 10, 2003 -- Thanks for the question regarding "Displaying
      time elapsed between dates", version 8.1.7

      You Asked
I need to generate a report that shows system downtime and uptime by day.  The
end of the report will total downtime for the specified period, total number of
days within period, min downtime segment, and maximum downtime segment.

The table has only startdate, enddate.

A user will enter a period startdate and enddate.

I think lag will be useful here but I think that I'll use it in a second SQL
statement after generating the body of the report.  Am I correct that this will
require two SQL statements.  One to generate the body of the report and the
second to generate the report summary?

      and we said...
If I've understood -- you are looking for a rollup function

ops$tkyte@ORA920> create table t ( startdate date, enddate date );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( trunc(sysdate-1) + 1/24,
trunc(sysdate)-1 + 2/24 );

1 row created.

ops$tkyte@ORA920> insert into t values ( trunc(sysdate-1) + 14/24,
trunc(sysdate)-1 + 14/24 + 30/24/60 );

1 row created.

ops$tkyte@ORA920> insert into t values ( trunc(sysdate)+ 3/24,
trunc(sysdate)+3/24+45/24/60 );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select nvl( to_char(trunc(startdate)), 'Rollup' ) day,
  2         sum(enddate-startdate) elaps,
  3             count(distinct trunc(startdate)) count_days,
  4             min(enddate-startdate) min_period,
  5             max(enddate-startdate) max_period
  6    from t
  7   group by rollup( to_char(trunc(startdate)) )
  8  /

DAY            ELAPS COUNT_DAYS MIN_PERIOD MAX_PERIOD
--------- ---------- ---------- ---------- ----------
08-MAR-03      .0625          1 .020833333 .041666667
09-MAR-03     .03125          1     .03125     .03125
Rollup        .09375          2 .020833333 .041666667

Review &
      Followup

      Rating: 5
I'd have so
      much free time  March 09, 2003
Reviewer:
      A reader
If I could carry you around in my pocket.

      Rating: 4
A downtime
      segment could space multiple days  March 09, 2003

Reviewer:  A reader  from Northern Virginia

I think it's appropriate to end a day's downtime segment at 23:59:59 since the
detail of the report is by day.  However, I would need to include those days for
which there might not be an entry in the table (the system was down all day).
How do I include those days in my detail listing?  Or what if I wanted to list
each downtime intervals from start to end.   A downtime interval could span
multiple days so I would want to report the difference between an uptime START
and the previous uptime END.

When I determine the MAX downtime I need to include these downtime intervals
that span multiple days.

2/13/2003 00:07:59 2/13/2003 13:59:59
2/13/2003 15:59:23 2/13/2003 22:00:00
2/15/2003 11:00:00 2/15/2003 23:59:59

The report total needs to show a maximum downtime segment for the downtime
between 2/13/2003 22:00:00 and 2/15/2003 11:00:00.  So, the days reported have
to be all days within the selected period including those without data in the
table.

Thanks very much for your help

          Followup:

now you are talking nasty.

RDBMS's *hate* to make up data that doesn't exist.  And sticking in dummy
records is going to be equally as painful.

You would want to have the break records AS WELL as the non-break records.

Sorry -- no clean pretty SQL answer this time.  We can show something like:

startdate  of downtime

maximum ENDDATE of downtime started that day (this - start date gives number of
days down)

amount of downtime incurred total over all possible days
count of times the system was down that day
min period
max period

but getting each day output for a multi-day span where the days are more then 2
days apart gets gnarly.

      Rating: 4
I won't try
      that approach  March 10, 2003
Reviewer:
      A reader
Thanks then, I won't attempt to fill the detail with dates that don't exist in
the table.  I'll only show the segments that occur within a 24 hour period from
a day posted to the table.

For my report summary to show downtime that spans multiple days, I can use LEAD,
correct?  I'll get the downtime that might start on one day (ENDDATE) and end
several days later (the next STARTDATE).  I'll use those periods to determine
MAXDOWN in my report summary.  And that would have to be a second run through
the table I suppose.

Thanks very much.

          Followup:

I'm confused -- i thought startdate was the beginning of the downtime and
enddate was the end of the downtime??  are you saying enddate is the start of
the downtime and startdate is the end of the downtime?

      Rating: 4
Records show
      UPTIME  March 10, 2003
Reviewer:
      A reader
I'm very sorry, the records show UPTIME.  If the system is up all day, there
will be one record and no downtime.

STARTDATE          ENDDATE
3/1/2003 00:00:00  3/1/2003 23:59:59
3/2/2003 00:00:00  3/2/2003 08:00:00
3/2/2003 12:00:00  3/2/2003 23:59:59

I need to calculate both UPTIME and DOWNTIME and PERCENTAGE DOWN for each day.
For the selected period, I need to show TOTAL DOWN, MAX DOWN, MIN DOWN, and
PERCENTAGE DOWN.

Sorry for the lack of detail and thanks for your patience.

          Followup:

Ok, then you would need to apply my query to this view:

select *
  from (
select enddate startdate,
       lead(startdate) over ( order by startdate ) enddate
  from t
       )
 where startdate <> enddate-1/24/60/60;

i think (to get the representation of the records as downtime intervals, instead
of uptime intervals)

March 10, 2003 Michael -- Thanks for the question regarding
      "DBMS_AQ and commit", version 8.1.7

      You Asked
Hi,

I use AQ for communicating selected data between a master schema and a slave,
which I use for external communication purpopses.

Somehow I got the understanding that enqueueing with DBMS_AQ was coupled to the
user committing data. But now my tests show me that this is not the fact: The
objects are accessible for the consumer right after the call to dbms_aq.enqueue
and not waiting for a commit/rollback.

This is a point of failure for me. Although I have all my objects being enqueued
at the latest possible point in the after insert/update/delete statement
triggers of my tables, I have the risk that the transaction is being rolled back
at a later step e.g. when one insert/update/delete generate another
insert/update/delete in some other table.

Do I have a design problem here, or am I just missing some points regarding AQ
and commit.

Regards, Michael Ringbo

      and we said...
You've gone out of your way then, if you read:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76938/adq01in5.htm#57491
...
ENQUEUE/DEQUEUE requests are normally part of a transaction that contains the
requests, thereby providing the desired transactional behavior. Users can,
however, specify that a specific request is a transaction by itself making the
result of that request immediately visible to other transactions. This means
that messages can be made visible to the external world either as soon as the
ENQUEUE or DEQUEUE statement is issued, or only after the transaction is
committed.
.......

Normally the message is NOT autocommitted.

You would have to give me a simple example showing me otherwise.  Here is my
simple simple example showing the default behavior:

-----------------------    test.sql -------------------------------
connect / as sysdba

DROP USER aq CASCADE;
CREATE USER aq IDENTIFIED BY aq;
GRANT EXECUTE ON DBMS_AQADM TO aq;
GRANT Aq_administrator_role TO aq;
GRANT CONNECT, RESOURCE TO aq;
GRANT EXECUTE ON dbms_aq TO aq;
connect aq/aq

CREATE type aq.Message_typ as object ( subject VARCHAR2(30), text VARCHAR2(80));
/

EXECUTE DBMS_AQADM.CREATE_QUEUE_TABLE ( queue_table => 'aq.objmsgs80_qtab',
queue_payload_type =>  'aq.Message_typ' );
EXECUTE DBMS_AQADM.CREATE_QUEUE ( queue_name => 'MSG_QUEUE', queue_table =>
'aq.objmsgs80_qtab');
EXECUTE DBMS_AQADM.START_QUEUE (  queue_name => 'MSG_QUEUE');

create procedure enqueue_msg( p_msg in varchar2 )
as
 enqueue_options dbms_aq.enqueue_options_t;
 message_properties dbms_aq.message_properties_t;
 message_handle RAW(16);
 message aq.message_typ;
BEGIN
   message := message_typ('NORMAL MESSAGE',  p_msg );
   dbms_aq.enqueue(queue_name => 'msg_queue', enqueue_options =>
enqueue_options,
                   message_properties => message_properties, payload => message,
                   msgid => message_handle);
end;
/

create procedure dequeue_msg
as
 dequeue_options dbms_aq.dequeue_options_t;
 message_properties dbms_aq.message_properties_t;
 message_handle RAW(16);
 message aq.message_typ;
BEGIN
   DBMS_AQ.DEQUEUE(queue_name => 'msg_queue', dequeue_options =>
dequeue_options,
                   message_properties => message_properties,
                   payload => message, msgid => message_handle);

   DBMS_OUTPUT.PUT_LINE ('Message: ' || message.subject || ' ... ' ||
message.text );
   COMMIT;
END;
/

---------------------------------------------------------

Now, in one session issue:

exec dequeue_msg

that'll just sort of hang there.  In another session issu:

exec enqueue_msg( 'Hello' );

The dequeue will just be hanging there.  Now, commit the enqueue and the dequeue
will immediately take off.

Review &
      Followup

      Rating: 4
As
      always....  March 10, 2003
Reviewer:  Michael
      Ringbo  from Denmark
Hi,

As always your answers are extremely bullet proof!

My mistake came from the enqueueing procedure, where I gave an enqueue option
with visibility=>immediate. This ought to be visiibility=>on_commit.

I have not tried it out yet, but I'm very sure the problem is there.

Thanks for your reply.

March 10, 2003 -- Thanks for the question regarding "bitor",
      version 8.1.7

      You Asked
Hi Tom,
I have a question about bitor. I know I can implement a function
to do this for me but is it possible to do this in one  select statement.
Currently what I have is a table that holds view names,username and privileges(1
= select,2 = insert,4 = delete,8 = update).
At any point in time someone can update their privileges on a particular view.So
it's during this time that I find all privileges
on that view and bitor them to get back one value. When that is done
I remove the original entries for that view
and then insert the new updated values .
In order to do this I have to loop/select but I would like to know if this can
be done in one select statement.Maybe it can't be done the way im asking but
would appreciate your experience on this one.
Here is an example of whay I'm currently doing. Thanks!

table currently contains something like this:
obj_name  user   priv
-------------------------
view1     newusr   1
view1     newusr   2
view1     newusr   4
-----------------------
view2    newusr    8
view2    newusr    4
view2    newusr    2
-----------------------
view2    newusr2   8
view2    newusr2   4
view2    newusr2   2

after my update the above information will be inserted into another table like
this:

obj_name  user   priv
-----------------------
view1     newusr    7
view2     newusr   14
view2     newusr2  14

procedure UPDATE_OBJ_PRIV
 (object_name in varchar2)
 as
   type priv_num is table of int index by binary_integer;
   type login is table of varchar2(30) index by binary_integer;
        type privileges_to_user is record
        ( x intArray,
          y varArray
        );

        priv_rec privileges_to_user ;
        cnt1 number := 0;
        l_action_id number := 0;

    begin

for x in(select distinct user from tab_obj_permission where obj_name =
UPDATE_OBJ_PRIV.object_name)
loop

     priv_rec.y(cnt1) := x.user;

for y in(select priv from tab_obj_permission where user = x.user and obj_name =
UPDATE_OBJ_PRIV.object_nameobject_name)
loop
 ----------------------
 l_action_id := (l_action_id + y.priv) - bitand(l_action_id,y.priv);
  ---is there any way i can do this in a nested select somehow
-----------------------
end loop;

    priv_rec.x(cnt1) := l_action_id;
    l_action_id := 0;
    cnt1 := cnt1 + 1;

end loop;

delete from imtbl_login_permissions where object_name
=UPDATE_OBJ_PRIV.object_name;

    forall i in 0 .. cnt1

     insert into imtbl_login_permissions login,object_name,action_id)
       values(priv_rec.y(i),object_name,priv_rec.x(i));
  commit;
end;

      and we said...
Umm, just add?

ops$tkyte@ORA920> select * from t;

NM         UNAME            PRIV
---------- ---------- ----------
view1      newusr              1
view1      newusr              2
view1      newusr              4
view2      newusr              8
view2      newusr              4
view2      newusr              2
view2      newusr2             8
view2      newusr2             8
view2      newusr2             4
view2      newusr2             2

10 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select nm, uname, sum( distinct priv )
  2    from t
  3   group by nm, uname
  4  /

NM         UNAME      SUM(DISTINCTPRIV)
---------- ---------- -----------------
view1      newusr                     7
view2      newusr                    14
view2      newusr2                   14

Review &
      Followup

      Rating:
      5
  March 10, 2003
Reviewer:
      A reader
Ahh that was way too easy(for you that is).
Why didn't I think of that, I'm having a brain cramp.
Anyway thanks for your prompt reply and simple but excellent solution. :)

March 10, 2003 bhabani -- Thanks for the question regarding
      "password lost", version 8.1.7

      You Asked
Hi tom

i have the following doubts

1] by any chance (using a decrypt function or whatever) is it possible to get
the password of oracle user. we have the username and the encrypted password
from dba_users.

2] is it possible to get the text code after using the wrap utility to wrap a
pl/sql code?

3] i need to audit the users who is querying DBA_SOURCE, USER_SOURCE views.
audit_trail is NONE and i cannot shutdown the production database. can't use a
trigger for SELECTs also
how can i accomplish this?

Thank You

      and we said...
1) nope, that would be a slight security bugaboo wouldn't it.

passwords are NOT stored encrypted.  See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:95412348059
for a discussion of this.  they are stored hashed.

2) see #1.  that would defeat the purpose.  Wrap is there to obscure the code.
To protect it from prying eyes.  If there was a reverse wrap, wrap would be
useless.

3) you have a problem then ;)

I need to do X.
In order to do X I must do Y.
I cannot do Y.

It follows therefore, that X cannot be done.  You will have to enable auditing,
period.

Review &
      Followup

      Rating:
      5
  March 08, 2003
Reviewer:  bh

excellent

      Rating: 3
Prob. a silly
      answer...   March 10, 2003
Reviewer:  Chris
      Slattery  from Dublin, Ireland.
But
(assumes lots of free space and a desire only to see who is reading from those
views - performance prob. goes out the window )

what about adding either a logon trigger to put the session into trace  or do a
DBMS_SYSTEM activate trace for all active sessions (keep doing this for all
active sessions) - then search  the trace for the view access ?

Probably a lame duck because of the hit on space and performance but it might
achieve the info he wants....

          Followup:

it would be a killer for performance.  and just one joker creating

create synonym my_syn for user_source

to hide from you what they are really doing.

March 10, 2003 wallacel -- Thanks for the question regarding
      "exception", version 8.1.6

      You Asked
Tom:

below is an example under $ORACLE_HOME/jdbc/demo
my question is what if we really got some kind of exception?
maybe there are chance that we didn't close stmt, or conn , etc.

can you give me a simple code to show me how to
resolve exception problem and close all opened things

import java.sql.*;

class Employee
{
  public static void main (String args [])
       throws SQLException
  {
    // Load the Oracle JDBC driver
    DriverManager.registerDriver(new
oracle.jdbc.driver.OracleDriver());

    // Connect to the database
    // You can put a database name after the @ sign in the
connection URL.
    Connection conn =
      DriverManager.getConnection ("jdbc:oracle:oci8:@",
"scott", "tiger");

    // Create a Statement
    Statement stmt = conn.createStatement ();

    // Select the ENAME column from the EMP table
    ResultSet rset = stmt.executeQuery ("select ENAME from
EMP");

    // Iterate through the result and print the employee names
    while (rset.next ())
      System.out.println (rset.getString (1));

    // Close the RseultSet
    rset.close();

    // Close the Statement
    stmt.close();

    // Close the connection
    conn.close();
  }
}

      and we said...
Here are two examples by Mark Piermarini.  They show the use of
the finally block in an exception handler to catch and clean up
any out standing work:

import java.sql.*;

class Employee1
{
  public static void main (String args []) throws SQLException
  {
    Connection conn = null;
    Statement stmt = null;
    ResultSet rset = null;

    try {
       // Load the Oracle JDBC driver
       DriverManager.registerDriver
            (new oracle.jdbc.driver.OracleDriver());

       // Connect to the database
       // You can put a database name after the @ sign in the
       // connection URL.
       conn = DriverManager.getConnection ("jdbc:oracle:oci8:@",
                                           "scott",
                                           "tiger");

       // Create a Statement
       stmt = conn.createStatement ();

       // Select the ENAME column from the EMP table
       rset = stmt.executeQuery ("select ENAME from EMP");

       // Iterate through the result and
       // print the employee names
       while (rset.next ()) {
         System.out.println (rset.getString (1));
       }
   } catch (SQLException e) {
      // Handle the error by setting some flag or whatever.
      //  Here, I just
      // print a message.
      e.printStackTrace();
   } finally {
       // These will only be closed if they were created/opened.

       // Note that
       // calling close() can generate SQLExceptions. In that
       // case the best
       // probable idea is to just let that bubble-up out of
       // this method.
       System.out.println("Closing up shop....");
       if (rset != null) { rset.close(); rset = null; }
       if (stmt != null) { stmt.close(); stmt = null; }
       if (conn != null) { conn.close(); conn = null; }
   }
  }
}

Another, more verbose but perhaps more bullet proof method, is:

import java.sql.*;
import java.util.*;

class Employee2
{
  public static void main (String args []) {
    Connection conn = null;
    Statement stmt = null;
    ResultSet rset = null;
    List exceptions = new ArrayList();

    try {
      // Load the Oracle JDBC driver
      DriverManager.registerDriver
          (new oracle.jdbc.driver.OracleDriver());

      // Connect to the database
      // You can put a database name after the @ sign in the
      // connection URL.
      conn = DriverManager.getConnection ("jdbc:oracle:oci8:@",
                                          "scott",
                                          "tiger");

      // Create a Statement
      stmt = conn.createStatement ();

      // Select the ENAME column from the EMP table
      rset = stmt.executeQuery ("select ENAME from EMP");

      // Iterate through the result and print the employee names
      while (rset.next ()) {
        System.out.println (rset.getString (1));
      }
   } catch (SQLException e) {
      // Handle the error by setting some flag or whatever.
      // Here, I just
      // save the exception.
      exceptions.add(e);
   } finally {
      // These will only be closed if they were created/opened.
      // Note that
      // calling close() can generate SQLExceptions. Since I'm
      // purposely
      // being pedantic, I surround each one of these calls
      // with another
      // try-catch block to insulate each one from the other. I
      // also, set
      // the local variable back to null.

      System.out.println("Closing up shop....");

      try {
         if (rset != null) { rset.close(); }
      } catch (SQLException e) {
         exceptions.add(e);
      } finally {
         rset = null;
      }

      try {
         if (stmt != null) { stmt.close(); }
      } catch (SQLException e) {
         exceptions.add(e);
      } finally {
         stmt = null;
      }

      try {
         if (conn != null) { conn.close(); }
      } catch (SQLException e) {
         exceptions.add(e);
      } finally {
         conn = null;
      }

      // For fun, we list all the exceptions we got....
      for (int i = 0; i < exceptions.size(); i++) {
         Exception currEx = (Exception) exceptions.get(i);
         currEx.printStackTrace();
      }

      // And now clean up...
      exceptions.clear();
      exceptions = null;
    }
  }
}

Review &
      Followup

      Rating:
      5
  March 11, 2001
Reviewer:  Jovan
      Sarai  from Australia, Sydney

      Rating:
      5
  October 23, 2001
Reviewer:  Branka

      Rating: 4
How to do this
      in PL/SQL  July 12, 2002
Reviewer:  Doug
       from CT, USA
Tom - what's a bullet proof way to do this in PL/SQL?
Let's say I want to make sure all bfiles, cursors etc., are closed.. couldn't
the act of closing them cause an exception in and of themselves?

          Followup:

Sure, but if closing a cursor or bfile raises an exception -- it'll be one of
two things that are true:

o they were not open in the first place

o the error we have encountered is SO HEINOUS that the session is pretty much
dead in the water, time to bail out (I've never seen this personally, not in 10
years of stored procedures)

Use an exception to catch the error, close the requisite resources and RE-RAISE
the error.

Note that cursors of a local scope (eg: cursors NOT defined in a package body or
spec outside of a function or procedure) will "auto close" when they go out of
scope in plsql -- not java tho

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package demo_pkg
  2  as
  3      procedure p1;
  4      procedure p2;
  5  end;
  6  /

Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package body demo_pkg
  2  as
  3      cursor c1 is select * from emp;
  4
  5  procedure p1
  6  is
  7  begin
  8      open c1;
  9  end;
 10
 11
 12  procedure p2
 13  is
 14      cursor c2 is select * from emp;
 15  begin
 16      open c2;
 17  end;
 18
 19  end;
 20  /

Package body created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> show parameter open_cursor

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
open_cursors                         integer 50
ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2      for i in 1 .. 500
  3      loop
  4          demo_pkg.p2;
  5      end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2      for i in 1 .. 500
  3      loop
  4          demo_pkg.p1;
  5      end loop;
  6  end;
  7  /
begin
*
ERROR at line 1:
ORA-06511: PL/SQL: cursor already open
ORA-06512: at "OPS$TKYTE.DEMO_PKG", line 3
ORA-06512: at "OPS$TKYTE.DEMO_PKG", line 8
ORA-06512: at line 4

If plsql didn't auto close -- p2 would either have

o hit that error,
o hit ora-00100 max open cursors exceeded

      Rating: 4
Doesn't appear
      to be an error closing an un-opened bfile.  July 15, 2002

Reviewer:  Doug  from CT, USA
Tom I can run this code just fine:

  1  create or replace procedure bfiletest
  2  AS
  3  l_bfile        bfile:=NULL;
  4  begin
  5  l_bfile := bfilename( 'DIRSHIP','file_name' );
  6  dbms_lob.fileclose(l_bfile);
  7* end;
SQL> /

Procedure created.

SQL> exec bfiletest;

PL/SQL procedure successfully completed.

I close the file but it is never open.  I assume this applies to bfile (can you
confirm?) but not all other types for example:

  1  create or replace procedure bfiletest
  2  AS
  3  cursor     c1 is select * from intf_job;
  4  begin
  5  close c1;
  6* end;
SQL> /

Procedure created.

SQL> exec bfiletest;
BEGIN bfiletest; END;

*
ERROR at line 1:
ORA-01001: invalid cursor
ORA-06512: at "PL10.BFILETEST", line 5
ORA-06512: at line 1

so it is a problem with cursors.
You mentioned and demonstrated if this was done within a package the cursor
would go out of scope and close.

however, what about a small procedure where I want to handle common exceptions
but ensure that there are no open cursor or file handle leaks.  The bfile seems
easy enough, what about the cursor?

You mentioned - "Use an exception to catch the error, close the requisite
resources and RE-RAISE the error."  I don't think I am getting it.  Let's assume
in my example below that I really want to do something when I get a type
mismatch (6502)... but I want the cursor closed and if it's already closed I
want to do something neat and clean.

  1  create or replace procedure bfiletest
  2  AS
  3  thing varchar2(50):='Throw an error';
  4  numthing number;
  5  conversionprob exception;
  6  PRAGMA EXCEPTION_INIT   (CONVERSIONPROB,-6502);
  7  cursor     c1 is select * from intf_job;
  8  begin
  9  numthing:=thing;
 10  EXCEPTION
 11     WHEN CONVERSIONPROB
 12   THEN      close c1;
 13* end;
SQL> /

Procedure created.

SQL> exec bfiletest;
BEGIN bfiletest; END;

*
ERROR at line 1:
ORA-01001: invalid cursor
ORA-06512: at "PL10.BFILETEST", line 12
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at line 1

SQL>

How can this be done cleanly?
By the way... am I missing the forest through the trees here? Is there something
I'm missing like "Don't bother with this sort of thing anyhow?"..or "If I just
put my procedure in a small package and call it inside the package I don't have
to worry about it?"..  Then there's utl_file which has a limit of 50 but I
didn't test that here.

          Followup:

if the bfile is never assigned -- it'll fail:

ops$tkyte@ORA920.US.ORACLE.COM> declare
  2  x bfile;
  3  begin
  4  dbms_lob.fileclose( x );
  5  end;
  6  /
declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: invalid LOB locator specified:
ORA-22275
ORA-06512: at "SYS.DBMS_LOB", line 474
ORA-06512: at line 4

ops$tkyte@ORA920.US.ORACLE.COM> spool off

In your example -- the cursor is local to bfiletest, hence it will NEVER remain
open after you leave bfiletest -- it goes out of scope.

Of course you could code "if c1%isopen then close c1; end if;" but in this case,
that is not really needed.

what you should code if you want to close it is:

 10  EXCEPTION
 11     WHEN CONVERSIONPROB
 12     THEN
          if c1%isopen then
             close c1;
          end if;
          RAISE; -- re-raise, so the ERROR IS NOT LOST!!!!
 13* end;
SQL> /

but here, you don't have to worry about closing c1, it'll be closed -- just like
my P2 example

      Rating:
      5
exceptions  March 10, 2003
Reviewer:
      Steve  from NYC, USA
Hi Tom,

In the example you give us:

finally {
       // These will only be closed if they were created/opened.
       // Note that
       // calling close() can generate SQLExceptions. In that
       // case the best
       // probable idea is to just let that bubble-up out of
       // this method.
       System.out.println("Closing up shop....");
       if (rset != null) { rset.close(); rset = null; }
       if (stmt != null) { stmt.close(); stmt = null; }
       if (conn != null) { conn.close(); conn = null; }
   }

Since Closed connection object also close any statement open by that connection,
and statement close all the result set.

Is it reduntant to call rset.close() and  stmt.close() here?

Thanks!

Steve

          Followup:

redundant - no (that would imply it does it twice, it doesn't)
explicit - yes
100% necessary - perhaps not, it would be done at the db level when you break
the connection

      Rating:
      4
  March 10, 2003
Reviewer:
      A reader
hi Tom,
we have oracle appl. server.
How can I catch unexpected extra input parameter.
I have:
OWS -05101: Execution failed due to Oracle error-6550.

Thanks a lot.

          Followup:

You would have to use flexible parameter passing which would be highly NOT
recommended in general.

      Rating:
      5
  March 11, 2003
Reviewer:  Helena
      Marková  from Bratislava, Slovakia

March 11, 2003 Nag -- Thanks for the question regarding "How to
      trace a Oracle Form", version 8.1.7

      You Asked
Tom

1.How can we trace the performance of an Oracle Form/oracle Report? Is there any
built in Oracle Form utility/Oracle Report Utility?

2.Can we use an optimizer hint with a query like 'select * from emp;' i.e. a
query which does a full table scan. Will it serve any purpose.

3.What is the difference between Run_product and Run_report_object ?

Thank you

      and we said...
1) you can either put

forms_ddl( 'alter session set sql_trace=true' );

in the form somewhere (eg: in a menu item or something, so you can turn it on)
or, when you run the form you can use "ifrun60 .... statistics=yes" and it'll
turn on sql_trace for you.

2) sure, yes it can.  the Parallel hint for example would affect it dramatically
perhaps.

3) one can do more then the other.  One runs either reports or graphics or
forms.  the other only runs a report and has more options for doing so.
Review &
      Followup

      Rating:
      5
statistics=yes  July 27, 2002
Reviewer:
      A reader
Dear Tom,
With Forms 6i ( ifrun60 ) statistics=yes, does not work. I think this is another
of the feature that would be taken off, like keyin and keyout. There is the
workaround for statistics ( forms_ddl ),is there a workaround for keyin and
keyout also. I plan to do some scalability testing using these features.
Thanks.

          Followup:

I don't know what keyin/keyout are but if there are documented parameters that
are NOT working as advertised, you really need to contact support and open a
bug.

I haven't written a form myself since about 1995.

      Rating: 3
To Tune SQL in
      ERP package   March 10, 2003
Reviewer:  Deepak
      Gupta  from India
Hello Mr. Tom,

I have in-house developed ERP Package in D2K and to tune this ERP package I make
sql_trace =true and timed_statistics=true in initorcl.ora.  I got no. Of trace
Files.
You have mention in your book only for individual form, what about if individual

Oracle Form can not run due to global parameter etc.

Now my questions are

Question 1. Which trace file should I use to generate the report.txt?
I mean that I choose max. size of file or any other condition or all the trace
file one by one or randomly

Question 2. How to know, Particular SQL belong to which Oracle form and Oracle
report?

Question 3. In your book, You have not told about if one find problem in SQL
statement then how to tune that SQL, I mean common problems and their solution?
If it is in book then on which page?

o/s : windows 98
Server o/s : window NT 4.0
Oracle database: 8.1.6.0.0

Please help me.

Thanks in advance.

          Followup:

I've no idea what you might mean by:

what about if individual Oracle Form can not run due to global parameter etc.

question 1: you need to use a trace file for a given session you want to tune.
it would make no sense whatsoever to pick a trace file based on size.

question 2: you would inspect the sql and figure out which is what.  there is
nothing really magical here, you are just getting a dump.  You SHOULD trace at
the application -- no database level.  You should make it so you can trace your
individual apps.

question 3: Here is a blurb from my next book, a chapter on efficient sql.  i
have a feeling many people won't "like it" but I feel so strongly about it:

1.1 Efficient SQL

This was probably the hardest part of the book to write - this chapter.  That is
not because the material is all that complex, rather because I know what people
want - and I know what can be delivered.  What people want:  The 10 step process
by which you can tune any query.  What can be delivered:  Knowledge about how
queries are processed, knowledge you can use and apply day to day as you develop
them.

Think about it for a moment.  If there were a 10 step or even 1,000,000 step
process by which any query can be tuned (or even X% of queries for that matter),
we would write a program to do it.  Oh don't get me wrong, there are many
programs that actually try to do this - Oracle Enterprise Manager with its
tuning pack, SQL Navigator and others.  What they do is primarily recommend
indexing schemes to tune a query, suggest materialized views, offer to add hints
to the query to try other access plans.  They show you different query plans for
the same statement and allow you to pick one.  They offer "rules of thumb" (what
I generally call ROT since the acronym and the word is maps to are so
appropriate for each other) SQL optimizations - which if they were universally
applicable - the optimizer would do it as a matter of fact.  In fact, the cost
based optimizer does that already - it rewrites our queries all of the time.
These tuning tools use a very limited set of rules that sometimes can suggest
that index or set of indexes you really should have thought of during your
design.

I'll close this idea out with this thought - if there were an N step process to
tuning a query, to writing efficient SQL - the optimizer would incorporate it
all and we would not be having a discussion about this topic at all.  It is like
the search for the holy grail - maybe someday the software will be sophisticated
enough to be perfect in this regards, it will be able to take our SQL,
understand the question being asked and process the question - rather then
syntax.

To me - writing efficient SQL requires a couple of things:

q    Knowledge of the physical organization of what I'm asked to query against.
That is - the schema.  Knowledge that the physical organization was actually
designed in order to help me answer my frequently asked questions (refer back to
the chapter on designing an efficient schema for advice in that arena)
q    Knowledge of what the database is capable of doing.  If I did not know
about "skip scan indexes" and what they did (we'll cover them below) - I might
look at a schema and say "ah hah, we are missing an index" when in fact we are
not.
q    Knowledge of all of the intricacies of SQL - from the lowly "WHERE" clause
on up to analytics and psuedo columns.  Knowledge of what using a particular
construct will do to my runtime processing.
q    And most importantly of all - a solid understanding of the goal, of what
the question is.  Tuning a query or process is really hard (impossible I would
say) - unless you understand the question in the first place.  I cannot tell you
how many times I've not been able to tune a query until I had the question in
hand.  Certainly you can derive a question from a query - however, many times
that derived question is much more confining then the real question being asked.
 For example, many people use outer joins in all queries - they are "afraid" of
losing a row (perhaps they got "burned" in some past experience and now use
outer joins everywhere).  If the objects are related in a one to one mandatory
fashion - we don't need an outer join at all.  The question derived from the
query is much more confining then reality.

That last topic or point is so important, I'll close out this section with it.
In this chapter we'll cover the topics of what the database is capable of doing
in general - looking at many of the access paths and join operations available
to us.  We'll look at what SQL is capable of doing - not by discussing the
entire language, that in itself is a book.  Rather, we'll look at a couple of
things that will whet you appetite - show you how powerful this language can be,
how much more than just "SELECT" "FROM" "WHERE" and  "ORDER BY" there is.  Then
we'll close up with a look at that most important topic - why understanding the
question is more important then having a query at hand to tune.

So, this section will not provide you with the N steps you need to follow in
order to tune a query or write the best queries in the world.  For every rule of
thumb out there anyone has ever shown me regarding writing "efficient SQL", I've
been able to come up with a slew of common (not esoteric) counter cases to prove
that rule of thumb is wrong in as many cases as it is right.  I've talked to
people who swear "NOT IN" is fatal, never use it - always use NOT EXISTS.  Then
I show them NOT IN running a query 10 times faster then NOT EXISTS.   I talk
with people who feel NOT EXISTS is the worst construct on the planet - you must
use IN.  Then I do the same - showing them how NOT EXISTS can run many times
faster then IN.

What this section will provide is the foundation knowledge you need in order to
begin thinking about how queries could be processed.  Once you can get into the
state of mind of thinking of how a query could be processed and thinking about
what would be best in your circumstances - you can get to the point of making
the query do what you think might be best.  That is how you write efficient SQL
- it is no different then writing efficient C, or Java or English for that
matter.  It takes an understanding of how things work, could work and what is
available.  If you don't know something exists - some technique is available -
you'll never be able to optimize to use it.  Hopefully, one of the outcomes of
this section will be a desire to learn more about this language SQL and what it
can do.

      Rating:
      4
Excellent  March 10, 2003
Reviewer:
      Reader  from MN, USA

      Rating: 4
what steps or
      procedures to follow to tune already running ERP Package
        March 11, 2003
Reviewer:  Deepak Gupta
       from India
Hello Mr. Tom,

        Thanks a lot for in-depth reply.

Please clarified following questions

Question 1. I  want to know what steps or procedures to follow to  tune already
running  ERP Package (developed in D2K) (System is working in Client /Server
technology ).

Question 2. Some time  users start complaining that system response is very slow
than in this what and how to identified problem(s) ?

Client o/s : windows 98
Server o/s : window NT 4.0
Oracle database: 8.1.6.0.0
Front end : D2K 6i

Please help me.

Thanks in advance.

          Followup:

1) step 1: set a goal
   step 2: determine bottleneck
   step 3: remove bottleneck
   step 4: test system, if meeting goal, go home else
   step 5: goto 2, repeat forever.

2) use the tools available to you at the OS and database level to identify the
bottleneck.  are you IO bound?  are you cpu bound?  are you paging like mad?  is
your network flooded?  hows the database doing (statspack)?  is your application
having any low hanging fruit (tkprof+sql_trace)?

the same tools you use to tune anything apply, "erp" is just an application...

March 11, 2003 Randy -- Thanks for the question regarding
      "Application File Upload", version 8.1.7

      You Asked
HI Tom,
  I was hoping you could give me some advice on a web-based application I've
been working on using the tools provided by Project Marvel.  The application is
nearly complete, with the exception of the document upload.  My requirements are
to create an upload option where users can fill out a form describing the
document then upload their document to a temporary area on the server.  The
approvers will then review the form and files.  Once approved the documents will
then be moved to a directory on a file server (not in the DB). I would like to
accomplish this with as little steps as possible for the approvers.  I've
considered storing in BLOBS, then moving to file server, but I'm not sure if
that's the best method.  Any advice you can give will be greatly appreciated.

Many thanks in advance,
Randy

      and we said...
I'm Joel Kallman, Tom asked me to respond to your question, as I'm pretty
familiar with it (worked on development of Project Marvel since 1999).

If I were in your shoes, I would leave these files inside the database until you
need to "publish" them to the filesystem.  This way, you can easily provide
access to the content within your HTML application (for reviewers, approvers,
etc.).

Within your company on Project Marvel is a view WWV_FLOW_FILES, which is your
private repository of all the files that have been uploaded using the FILE item
type on a page.  Even if you wish to ultimately store the BLOB files in your own
separate application table, using the FILE item type is the easiest way for you
to provide document upload capability.

The column is called BLOB_CONTENT which will actually store the binary content
of the uploaded file.  You can use the "p" procedure to provide a direct link to
access this content.  If you access the online help, and navigate to the
category of Doc -> Flow Builder -> Concepts -> API, you will see the
specification of the "p" procedure.

As an example, let's say a user uploaded a file using the FILE item type, and
upon insert into the WWV_FLOW_FILES view, it generate the ID of 12345.  You can
then provide a link directly to the uploaded file via:

     <a href="p?n=12345">Your uploaded document</a>

Then, once your document has completed its workflow and you need to write it out
to the filesystem, you can simply use the UTL_FILE (in 9iR2 and up since it
supports RAW text io now) or a small Java stored procedure to dump the binary
BLOB content to a file.

Hope this helps.

Review &
      Followup

      Rating:
      5
  December 31, 2002
Reviewer:
      A reader
Hi Joel,
  Thanks for the advice.  This really helps!!

      Rating: 4
need more
      information ...!  March 11, 2003
Reviewer:
      Jack  from Malaysia
it is good to hear that 9iR2 and up include the a function in UTL_FILE package
to write the binary file to OS already.
But, unfortunately I am now using the 9iR1 only plus I am no familiar with JAVA,
so still facing the problem to upload write the binary file from database to OS.
May I know that where can I find the sample Java stored procedure or any other
way to include the new function to UTL_FILE package in 9iR1.

Thanks in advanced.

          Followup:

you will not be using utl_file in 9iR1.

search for

blob to file java

on this site.

March 11, 2003 Mark -- Thanks for the question regarding "Using
      array w/ IN list, ordering results by order of items in input array.",
      version 8.1.7

      You Asked
Hi Tom,

We ran into a pretty severe performance problem recently.
(8x 900 MHz UltraSparcIII CPUs w/ 100% CPU usage and 350+ runqueues,
and vmstat reported 100,000+ context switches/second)

The problem was traced back to a PL/SQL that took a PL/SQL table input
and did this:

for i in array.first..array.last
loop
  select <some data> from documents where doc_id = array[i];
end loop;

for significantly large values of array.  (Around 1,500....)

So, said I:
"Who designed this thing?? I see the problem here!  We need to bulk
collect!  The context switching between SQL and PL/SQL is killing the
CPU!  If we re-write this API as bulk collect, our performance problem
will disappear!"

Now, due to the serial nature of the original code, we go thorugh
each doc_id in the array, and populate some data in a correspondingly
indexed PL/SQL table that we pass back the data for.  In this array,
we simply populate the output column w/ some column indicating an
error when the data is not found.

In my bulk collect design, I thought, ok, the way this will change,
I'll need to return only those doc_ids for which there is valid data.

So, now, the calling program gives Oracle a list of doc_ids, and they
get back a subset of that list.  Problem is, they need to go through
the original array, and validate which docs came back.

So, they want the data back from Oracle in the same order as they
passed it to us in the array.  Problem is, though the input array
is sorted, it's not in any order that Oracle is aware of.

So, question:

How can I maintain the benefits of a BULK COLLECT, and still get
the data back in the input data order?

Here is the cursor that we do the BULK COLLECT operation on.

CURSOR doc_ascii_cur(Cur_doc_id_tab IN PQDNUMTABTP) IS
  SELECT TO_CHAR(doc.doc_id) l_doc_id,
         acv.ac_number
    FROM documents doc,
         ascii_collection_versions acv,
         (SELECT COLUMN_VALUE doc_id
            FROM THE(SELECT CAST(Cur_doc_id_tab AS PQDNUMTABTP)
                       FROM DUAL
                     )
         ) DOC_LIST
   WHERE doc.doc_id = doc_list.doc_id
         AND doc.acv_id = acv.acv_id
         AND NVL(doc.doc_hold_status, 'N') != 'Y';

This works, but I can't guarantee the order.  I'd like to return
the data in the order the DOC_IDs appear in CUR_DOC_ID_TAB.

Any Ideas?

Thanks,

-Mark

      and we said...
You will have to use a temp table with something to order by.  In this example,
l_data represents the ordered list of cur_doc_id_tab you have.  we set up
another table l_idx with 1, 2, 3, .... N.

We JOIN to this global temporary table -- I used MAX() just to make sure that Y
(your doc_id) was unique.  If you already know that doc_id is unique, this is
not necessary.

In order to get ordered data -- you must have an order by -- this gives us an
order by:

ops$tkyte@ORA920> create global temporary table t ( x int, y int );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      type array is table of number;
  3
  4      l_data array := array( 80, 239, 250, 101, 309, 300, 294, 252, 270, 271,
292, 301 );
  5      l_idx  array := array();
  6  begin
  7
  8      l_idx.extend( l_data.count );
  9      for i in 1 .. l_data.count
 10      loop
 11          l_idx(i) := i;
 12      end loop;
 13
 14      forall i in 1 .. l_data.count
 15          insert into t values ( l_idx(i), l_data(i) );
 16
 17
 18      for x in ( select user_id, username
 19                   from all_users, ( select max(x) x, y
 20                                       from t
 21                                      group by y ) T
 22                 where all_users.user_id = t.y
 23                 order by t.x )
 24      loop
 25          dbms_output.put_line( x.user_id || ',' || x.username );
 26      end loop;
 27  end;
 28  /
80,BIG_TABLE
239,DEVELOPER
250,PERFSTAT
101,OPS$ORA920
309,OPS$TKYTE
300,S
294,A
252,CSMIG
270,AQ
271,UTILITY
292,U2
301,B

PL/SQL procedure successfully completed.
Review &
      Followup

      Rating: 5
Excellent
      Solution!  March 11, 2003
Reviewer:  Mark J.
      Bobak  from Ann Arbor, MI
Thanks Tom!

March 11, 2003 Freek -- Thanks for the question regarding
      "exception handling in batch processing", version 9.0.1

      You Asked
Tom,

I am currently developing some routines which will be run at night.
For the exception handling I would like that all errors would be logged into an
"error table". The code will be mainly written in PL/SQL and some part of it
will be written in java (to do some ftp work). Some packages will be later used
as well for other development (the package to work with ftp for example), which
may be non batch
based.

My question is, where in the code should I place the call to the procedure which
logs the error into this table? In the exception section of the routine where
the error occured (followed by a raise)? Or should I allow it to bubble up, and
let the "most upper" routine log the error?

In case of the first option, how can we best avoid then that when some of these
modules are used later in non batch processing, the errors will be written tot
his log table? (an extra parameter ?)

Regards

Freek D'Hooge

      and we said...
this is one of those questions I cannot answer.

You should do it where it makes the most sense.   Where it "feels right" from a
programming perspective.

I would probably lean towards "the closer to the error the better". the top
level routines will get an error like "hey, the column was too large to fit" but
they won't have any idea *where* this exception was raised.  A localized error
trap/log would localize the error -- you would know where it happened.

If you needed to be able to disable/enable this, you could make it either as:

o a single package global variable that people set to TRUE or FALSE to enable or
disable logging

o an extra parameter

I would probably go with the variable -- that way, a single "set" of the
variable would turn it on/off -- like dbms_output.enable/disable does.
Review &
      Followup

      Rating:
      4
thanks  January 18, 2003
Reviewer:
      Freek  from Belgium
Thanks for your advise.
I will put the logging in the local exception sections and put a package
variable in the logging package.

regards

Freek D'Hooge

      Rating: 3
Error handling
      is still a unclear part   January 20, 2003

Reviewer:  Tony  from India
Tom, Error handling is still a unclear part even for experienced programmers. In
general, we store application specific, user defined error codes, error messages
in a table.
1.Which is the best way in client sever model as well as in Web model?
2.Which is the best way to handle exceptions in nested block and nested function
calls?
3. Is the following approach correct for Web apps?

  Ex:     FUNCTION fn1
          IS
             l_retval   INTEGER;
          BEGIN
              --
              --
              l_retval := fn2(....);
              /*
                fn2 returns 0 if successfully executed,
                else return user defined error code.
              */

              IF l_retval <> 0 THEN
                  RETURN l_retval;
              END IF;

              --
              --
          END;

          FUNCTION fn2(... )
          IS
          BEGIN
              --
              --
              --
              --
              RETURN 0;
          EXCEPTION
               WHEN no_data_found THEN
                   RETURN 101;
                  -- Error description for 101 is stored
                  -- in a error_message table.
          END;

Note: I hope you will cover this topic extensively in your next book on best
practices.

          Followup:

1) there is no difference to me between client server and web.  In web, you
still have a client, you have a server -- there is a man in the middle.  but why
would error detection, reporting and handling be any different?  As far as the
client is concerned -- it is just the same application.  they care not how it is
physically hosted as long as they can run it.

2) I let the exceptions propagate out to the layer that can handle it.  If you
cannot handle it - don't catch it.  If it bubbles out to the topmost layer -- so
be it, catch it, record it, report it to the end user.

I don't like your approach above of "return codes".  I would just let the
exception wind its way back up the stack (making it IMPOSSIBLE to ignore it --
how many bugs I've seen with return code programming where someone ignores the
return code -- exceptions cannot be ignored unless you write explicit code to
ignore them)...

3) web apps, client server apps, they are all just apps.  No, I am not fond of a
return code approach.  Unless fn2 is prepared to DEAL with no_data_found (eg: it
is an EXPECTED condition), fn2 should not catch it -- should not "hide it" as
you are doing.

      Rating: 4
which errors to
      log  January 20, 2003
Reviewer:  error logging
      telecommuter
Depending on the design of your system, you might want to log the error where it
first occurs, re-raise the error, then as it bubbles up log any other useful
contextual information at each of the higher levels.  E.g., with triggers that
cascade multiple levels of inserts/updates, each row-level trigger has an error
handler that logs key information that might not be available at other levels.
If a particular level can't contribute useful information that isn't found at
other levels,  then don't bother.  I'd rather have too much information in the
error log than too little.  More information means you can quickly establish the
context of the error.  It also means that you have a better chance of filling up
the disk that contains the error log or blowing out the tablespace that contains
the error table.

          Followup:

excellent advice.

      Rating: 5
How can we
      continue after catch an error?  January 27, 2003

Reviewer:  A Reader
Hi Tom,
I am writing a batch deleting procedure like the following.  The problem I have
is after an error is caught, the procedure quits.  I want to continue to delete
all the records after the error is caught.  How can I achive that?  Without the
exceptions, the procedure will also quit after a fk violation.  Thank you for
your help.

CREATE OR REPLACE PROCEDURE PRO_DELETE AS
   fk_violated EXCEPTION;
   PRAGMA EXCEPTION_INIT(fk_violated, -2292);
BEGIN
  FOR x in (select id user_ID from t1) LOOP
    delete from t2
    where id=x.user_id;
    COMMIT;
  END LOOP;
EXCEPTION
  WHEN fk_violated THEN
    DBMS_OUTPUT.PUT_LINE('FK_');
    -- or insert into a log table;
  WHEN OTHERS THEN
   ROLLBACK;
   raise_application_error(-20002, 'Error deleting');
END;
/

          Followup:

the code should just be:

delete from t2
  where user_id in ( select user_id from t1 )
    and not exists ( select fk from child_table where child_table.fk = t2.pk );

no muss, no fuss, no exception, no code...

but, if you persist with writing lots of code -- it'll be

CREATE OR REPLACE PROCEDURE PRO_DELETE AS
   fk_violated EXCEPTION;
   PRAGMA EXCEPTION_INIT(fk_violated, -2292);
BEGIN
  FOR x in (select id user_ID from t1)
  LOOP
     begin
        delete from t2 where id=x.user_id;
     exception
        when fk_violated then
            dbms_output.put_line( .... );
     end;
     COMMIT;
  END LOOP;
END;

loose the when others -- it does nothing for you.  the statement was already
rolled back and you lose the error message with your approach -- you have no
idea what the when others caught!

      Rating: 3
3-tier
      Applications  January 28, 2003
Reviewer:
      Tony  from India
Tom, as per your advice, its not a good idea to return user-defined error code.
We are developing 3-tier web application using ASP, COM(VB) & Oracle. We want to
display custom error messages. So, for example, whenever we try to insert new
invoice details, we check for duplication. If invoice already exists, we return
an error code to the COM object. Another procedure is executed to get the error
message "Duplicate Invoice" from the message table. In this case, we have got to
capture the exception in the procedure and return user-defined error code to the
COM object.

How will I achieve the same if I don't capture exception and return error code?
Please enlight me.

If we don't handle exceptions and return error code.

          Followup:

too bad you picked the most proprietary of mechanisms to build an 'open' system.
 the only thing in that mix that gives you a choice is your database --
everything else locks you into a single vendor, single platform....  oh well.

first of all -- 3 tier, 1 tier, 17 tier -- matters not, error handling is error
handling is error handling.

If you want to map the oracle error into a customized error, do it at the VERY
VERY VERY top level and re-raise the error, like this:

Have the client submit this block:

begin
   procedure;
exception
   when others then
      raise_application_error(-20001,customized_error_msg);
end;

instead of just

begin
   procedure;
end;

that way

o the error can be caught and handled at a lower leve IF IT MAKES SENSE to.
o the error will never be hidden
o you get your "customized" error message in a consistent fashion.

the Oracle Error Message will be customized error msg

      Rating: 4
OTHERS and the
      RAISE - lost line number  February 07, 2003

Reviewer:  Tom Best  from PA
The advise to catch everything at the lowest level so it can be logged and
reraised means the line number is meaningless, right?  It will always point to
the line containing the "RAISE" statement.

          Followup:

My point was -- if you are going to use this "log all errors" approach - lower
level is best.  I would rather have:

begin

  ....
  begin
     insert into t values ...
  exception
     when .... then call_logger;
                    raise;
  ......

end;

then

begin
   ....
   insert into t values ....
   .....

exception
  when .... then call_logger;
                 raise;
end;

the closer to the error, the better the line number.  If you catch the error "at
the top level of plsql" -- you have lost that entirely.  The closer to the
error, the better.

      Rating: 5
One more
      question about an exception  March 10, 2003

Reviewer:  Alla Gribov  from Boston, MA USA

You said above, that delete statement should be written like this:
-----------------
delete from t2
  where user_id in ( select user_id from t1 )
    and not exists ( select fk from child_table where child_table.fk = t2.pk );

no muss, no fuss, no exception, no code....
-----------------

and not just a delete and then catch exception.

My question is: I have quite a few children table for the parent table than I am
trying to delete from. What is going to happen to the performance of my
procedure if I add at least 4 or 5 "not exists" to the delete statement? When I
find only appropriate rows to delete (with extra "not exists") my code will look
cleaner, but then Oracle would check for referential integrity anyway. Then why
do the same thing twice??? (i.e. check for the existance of data in a delete
statement and then let Oracle do the same thing by enforcing a referential
integrity)

Thanks for your response

          Followup:

try it and SEE....

consider a single delete -- that deletes only that which you want

vs

procedural row by row code.....

a single delete will win out -- consider (i used timing this time but tkprof
shows exactly how bad this really is -- using procedural code that is)

ops$tkyte@ORA920> create table p ( x int, data char(80) );

Table created.

ops$tkyte@ORA920> insert into p select rownum, 'x' from all_objects;

30553 rows created.

ops$tkyte@ORA920> alter table p add constraint p_pk primary key(x);

Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2          for i in 1 .. 5
  3          loop
  4              execute immediate 'create table c'||i||'( x references p )';
  5                  execute immediate 'insert into c'||i||'
  6                                    select x
  7                                      from p
  8                                    where mod(x,4) = 0
  9                               and x between :x1 and :x2'
 10                                       using (6000*(i-1)), 6000*i;
 11                  execute immediate 'create index c'||i||
                          '_idx on c'||i||'(x)';
 12          end loop;
 13  end;
 14  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set timing on
ops$tkyte@ORA920>
ops$tkyte@ORA920> delete from p
  2    where not exists ( select null from c1 where c1.x = p.x
  3                        union all
  4                        select null from c2 where c2.x = p.x
  5                        union all
  6                        select null from c3 where c3.x = p.x
  7                        union all
  8                        select null from c4 where c4.x = p.x
  9                        union all
 10                        select null from c5 where c5.x = p.x );

23053 rows deleted.

Elapsed: 00:00:11.30
ops$tkyte@ORA920>
ops$tkyte@ORA920> rollback;

Rollback complete.

Elapsed: 00:00:05.43
ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2          for x in ( select rowid rid from p )
  3          loop
  4          begin
  5                  delete from p where rowid = x.rid;
  6          exception
  7                  when others then null;
  8          end;
  9          end loop;
 10  end;
 11  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:40.67
ops$tkyte@ORA920>

      Rating: 5
Sorry for the
      confusion  March 11, 2003
Reviewer:  Alla
      Gribov  from Boston, MA USA
Dear Tom;

Thanks for your reply. I guess it's my fault, but I did not make myself clear

Using your example, here is what I had in mind:

begin
    delete from p
    where not exists ( select null from c1 where c1.x = p.x
                       union all
                       select null from c2 where c2.x = p.x
                       union all
                       select null from c3 where c3.x = p.x
                       union all
                       select null from c4 where c4.x = p.x
                       union all
                       select null from c5 where c5.x = p.x )
      and x = 32;

    if sql%rowcount = 0 then
        raise_application_error(-20002, 'Did not delete');
    end if;
exception
    when others then
        raise_application_error(-20001, 'Unable to delete');
end;
/

AND

begin
    delete from p
    where x = 32;
exception
    when others then
        raise_application_error(-20001, 'Unable to delete');
end;
/

Second code is much smaller and easier to read. As for the performance - they
executed in the EXACT SAME number of seconds. So, I guess, there is no harm to
do it the "smaller" way and I even can distinguish between not deleted, because
there was nothing to delete and not deleted, because of the referential
integrity constraint (I catch ORA-2292, not just "others")

Thanks again for your help!!!! As always you are right - just DO AND SEE!  Sorry
again for taking your time :-)

Sincerely

Alla

          Followup:

well -- they are apples and toaster ovens entirely!

The second one is obviously the only correct approach.  Look at the deletes --
you are assuming that sql%rowcount = 0 means "child key found" -- but maybe it
means "x=32" was not found.

My point was -- if you want to "delete all parents that have no child", the
single delete is correct.  If you are just deleting a single row -- you would
not go out of your way to check for child records -- that would be incorrect.

March 11, 2003 Richer -- Thanks for the question regarding "Bind
      variables within package", version 8.1.7

      You Asked
Hi Tom,

I have two questions about the following piece of code (which is in a procedure
within a package).

1- In my INSERT stmt, do I need to use bind variables?  If yes, can you give me
a small example of doing so.

2- As you can see I need to see if different tables are empty (S1, S2) in order
to find out if I should flag an event or not.  Is there a different way I could
achieve this more efficiently?

v_count:=0;

SELECT count(*) INTO v_count
FROM S1;

    IF v_count >0 THEN
        INSERT INTO S_VERIFICATIONS ( event_id)
        VALUES (305);
    END IF;

v_count:=0;

SELECT count(*) INTO v_count
FROM S2;

    IF v_count > 0 THEN
        INSERT INTO S_VERIFICATIONS ( event_id)
        VALUES (306);
    END IF;

...

      and we said...
1) You could use a bind there:

is
   l_s1_event_id  number := 305;
   l_s2_event_id  number := 306;
begin
   insert into s_verifications
   select l_s1_event_id
     from dual
    where exists ( select null from s1 );

   insert into s_verifications
   select l_s2_event_id
     from dual
    where exists ( select null from s2 );

but it really isn't necessary -- no matter how many times this runs, the same
insert will be done -- no chance of flooding the shared pool with thousands of
unique SQL.

The mistake you have made is that count(*) thing.  At the very least make it:

select count(*) into v_count from s1 where rownum=1;

but the insert into select from dual is just less code to write and maintain.
Review &
      Followup

      Rating: 4
Quick
      Question  March 10, 2003
Reviewer:  Scott
       from Perth, WA
G'day Tom

When doing a SELECT COUNT(*) INTO ...
why do you recommend adding WHERE ROWNUM = 1
since as far as I can tell, you are only going to get more than one row if you
add a group by.

          Followup:

They were just looking to see if AT LEAST one row existed:

SELECT count(*) INTO v_count
FROM S1;

    IF v_count >0 THEN
....

so, if there was 1 row, it would do the same as if it were 1,000,000 rows.  But
consider the performance difference:

big_table@ORA920> select count(*) from big_table;

  COUNT(*)
----------
   1833792

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FULL SCAN) OF 'BIG_TABLE_IDX' (NON-UNIQUE) (Cost=26
Card=1833792)

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

big_table@ORA920> select count(*) from big_table where rownum = 1;

  COUNT(*)
----------
         1

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     COUNT (STOPKEY)
   3    2       INDEX (FULL SCAN) OF 'BIG_TABLE_IDX' (NON-UNIQUE) (Cost=26
Card=1833792)

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

why count them all when one will do.

This is a "performance tweak" I see all of the time.  Scary how much code out
there is in the form:

SELECT count(*) INTO v_count
FROM S1;

    IF v_count >0 THEN

but -- it makes my job easy "hey, please tune our system...." Ok, I find the N
hundred occurrences of that construct, turn them into one of the above solutions
and leave ;)

      Rating: 5
Excellent
      point.  March 10, 2003
Reviewer:  Scott
       from Perth WA

      Rating:
      5
  March 11, 2003
Reviewer:  Rich
       from Montreal
Hi Tom,

What about calling a procedure to do the insert.  Would that be good?

IF v_count >0 THEN
    INSERT_EVENT (312);
END IF?

          Followup:

why bother with running a query to see if I need to insert.  Just insert the
query.

   insert into s_verifications
   select l_s1_event_id
     from dual
    where exists ( select null from s1 );

do as little procedural code as you humanly can.  That is my idea.

      Rating:
      5
  March 11, 2003
Reviewer:  Rich
       from Montreal
Hi Tom,

What about the following situation.  How would you deal with it?  Thanks again,
your web site is "une mine d'or"

v_count:=0;

SELECT count(*) INTO v_count
FROM S1;

IF v_count > 0 THEN
        INSERT INTO S_VERIFICATIONS ( event_id)
        VALUES (305);
ELSE
        INSERT INTO S4 ( event_id)
        VALUES (305);
END IF;

          Followup:

SELECT count(*) INTO v_count FROM S1 where rownum = 1;

IF v_count > 0 THEN
        INSERT INTO S_VERIFICATIONS ( event_id)
        VALUES (305);
ELSE
        INSERT INTO S4 ( event_id)
        VALUES (305);
END IF;

or in 9i,

ops$tkyte@ORA920> create table t1( x int );

Table created.

ops$tkyte@ORA920> create table t2( x int );

Table created.

ops$tkyte@ORA920> create table t3( x int );

Table created.

ops$tkyte@ORA920>

ops$tkyte@ORA920> insert
  2   when (cnt = 1) then into t1 (x) values ( 111 )
  3   when (cnt = 0) then into t2 (x) values ( 222 )
  4  select count(*) cnt from t3 where rownum = 1;

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t1;

no rows selected

ops$tkyte@ORA920> select * from t2;

         X
----------
       222

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t3 values( 1 );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert
  2   when (cnt = 1) then into t1 (x) values ( 111 )
  3   when (cnt = 0) then into t2 (x) values ( 222 )
  4  select count(*) cnt from t3 where rownum = 1;

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t1;

         X
----------
       111

ops$tkyte@ORA920> select * from t2;

         X
----------
       222

ops$tkyte@ORA920>

March 11, 2003 Daya -- Thanks for the question regarding
      "Searching PDF Document", version 8.1.7

      You Asked
Hi Tom!
  Happy for your prompt help always. I have  a procedure to insert files to the
database. I have indexed it on the text column too.
  The table is as:
    SQL> desc lob_demo
 Name                                      Null?    Type
 ----------------------------------------- -------- ------------------
 ID                                        NOT NULL NUMBER
 THECLOB                                            CLOB

 The procedure to do a search on the document is as:
  procedure srch_lob(srch_val in varchar2,pid number)
is
     lobd        CLOB;
     position    INTEGER  := 10000;
 BEGIN
 -- get the LOB locator
     SELECT theclob INTO lobd
         FROM lob_demo
         WHERE id = pid;
     position := DBMS_LOB.INSTR(lobd,
                         srch_val, 1, 1);
     IF position = 0 THEN
         dbms_output.put_line('Pattern not found');
     ELSE
         dbms_output.put_line('The pattern occurs at '
                 || position);
     END IF;
 END;
  I am able to search .txt,.doc,.xls files using the above. But if i look for
some word from the .pdf document to be searched using the above, i am not
getting the desired result. It tells pattern not found.
  It retrieves only ascii characters when i display the particular file using:
  SQL> select dbms_lob.substr( theclob, 4000, 1 ) from lob_demo
       where  id = 6;
 Is it a problem due to the CLOB datatype? Will BLOB help here? But i need to do
a search!

Thanks in advance
Daya

      and we said...
You are using the wrongest approach.

First -- I hope you are not putting XLS, PDF, DOC files in a clob -- that would
the wrong datatype.  It should be a blob.

Second -- instr won't work reliably on any of those document types.  You are
making the assumption (false) that the word "HELLO" will be stored as HELLO --
or that the presence of the word HELLO in the .doc file means if you view the
document HELLO will appear.  Neither is true.

You want to use an intermedia text index.  This supports filtering and indexing
hundreds of document formats including doc, xls, pdf AND it indexes them so it
actually works pretty fast.

When you search on asktom, it is using that technology.

search this site for

create index indextype ctxsys context

for examples and read:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/inter.817/index.htm

Review
      & Followup

      Rating: 3
Searching
      PDF  March 11, 2003
Reviewer:  Daya
       from India
Hi Tom!
  Thanks for your response. I forgot to mention that i created index of type
ctxsys context on the column.
  As you told, i created a table of Blob datatype and inserted a PDF document to
the blob and id=1 to it.
  The table is:
SQL> desc ind_demo;
 Name                                 Null?    Type
 ---------------------------------- -------- -------------
 ID                                 NOT NULL NUMBER(38)
 THEBLOB                                     BLOB
    The index is created as
SQL> create index my_index on ind_demo(theblob) indextype is ctxsys.context;
  Index created.
  Now when i try to use the contains clause, i am not getting any result.

SQL> select score(1) title from ind_demo where contains(theblob,'Security
Guide',1) >0;

no rows selected

  But the same word exists in the .pdf document. What is wrong here?
Thanks in advance
Daya

          Followup:

was the document in there when you created the index?
did you sync the index?

      Rating:
      4
  March 11, 2003
Reviewer:  Daya
       from India
Hi Tom!
  Thanks for your solution. I recreated the index and it worked. Do we need to
create the index everytime after storing the documents?
  If i store the document on everyday basis what to do?
  Thanks
Daya

          Followup:

You need to sync the index periodically.

search for

sync ctx_ddl

On asktom, I have a job about every 15 minutes now that syncs the index --
adding newly added documents to it.  Your procedure that adds a document could
sync the index or you can batch them up.

      Rating: 4
Searching
      PDF  March 11, 2003
Reviewer:  Daya
       from India
Thank you so much Tom.

Daya


Subject | Article | Last Updated (East coast time)
Need Query to create a view | article only | Mar 12, 2003 07:22 am
Getting the Day Count | article only | Mar 12, 2003 07:35 am
Index clustered table | article only | Mar 12, 2003 07:45 am
timezone information -- getting the timezone | article only | Mar 12, 2003 07:47 am
session pga memory | article only | Mar 12, 2003 08:41 am
Update statement help | article only | Mar 12, 2003 11:14 am
table(cast(...)) performance tunning question | article only | Mar 13, 2003 07:23 am
getting jdbc 2.0 drivers | article only | Mar 13, 2003 07:24 am
Spliting | article only | Mar 13, 2003 07:26 am
Coming out after first find | article only | Mar 14, 2003 05:05 pm
DDL Trigger | article only | Mar 14, 2003 05:16 pm
Truly complex pivot | article only | Mar 14, 2003 05:23 pm
Sql query | article only | Mar 14, 2003 05:29 pm
questions on LONGS | article only | Mar 14, 2003 05:32 pm
IMPORT -- how to load delimited text data. | article only | Mar 14, 2003 05:34 pm
Week end Count | article only | Mar 14, 2003 05:46 pm
Evaluation of expressions | article only | Mar 14, 2003 05:49 pm
When should we use partitioned table? | article only | Mar 14, 2003 05:54 pm
Raw Device vs File System | article only | Mar 14, 2003 06:02 pm
_trace_files_public | article only | Mar 14, 2003 06:03 pm
SQL Statement | article only | Mar 15, 2003 09:07 am
DDL triggers in 8.1.7 | article only | Mar 15, 2003 09:09 am
Piecewise update of XML document in 9i release 2 | article only | Mar 15, 2003 09:10 am
Should time_statistic=true on production database? | article only | Mar 15, 2003 01:28 pm
Why the same sql querry takes different times while running | article only | Mar 15, 2003 01:37 pm
what is redo log sync and what more can be done about it | article only | Mar 16, 2003 09:41 am
Ref cursor query | article only | Mar 16, 2003 10:13 am
OMF + a 9i export question | article only | Mar 16, 2003 10:52 am
Developer privileges versus features | article only | Mar 16, 2003 04:14 pm
RE: Ask Tom Submission, capture rowid after insert | article only | Mar 16, 2003 04:17 pm
CURSOR_SHARING SIMILAR Vs FORCE | article only | Mar 16, 2003 04:33 pm
Your latest Oracle Magazine article | article only | Mar 16, 2003 05:00 pm
Use of optimal | article only | Mar 17, 2003 08:47 am
Password file | article only | Mar 17, 2003 08:51 am
How to create csv file from oracle db | article only | Mar 17, 2003 09:08 am
CLOB | article only | Mar 17, 2003 09:28 am
treeview/node/child | article only | Mar 17, 2003 09:34 am
Sort Area Size | article only | Mar 17, 2003 10:02 am
Passing c_my_cursor%RowType as IN parameter of procedure ? | article only | Mar 17, 2003 10:09 am
Error in using bulk collect with REF cursors | article only | Mar 17, 2003 12:29 pm
performance manager in OEM | article only | Mar 17, 2003 12:32 pm
database | article only | Mar 17, 2003 01:05 pm
Look up Table | article only | Mar 17, 2003 01:13 pm
How to retrieve values from bind variables on other user sessions. | article only | Mar 17, 2003 01:14 pm
PLSQL function returning object type causes full table scan | article only | Mar 17, 2003 03:36 pm
100 partitions, their names, and there count(*) | article only | Mar 18, 2003 07:37 am
How to Handler Errors when Function returns only Ref Cursors | article only | Mar 18, 2003 07:44 am
Comparison of Oracle DBA on Unix and Windows 2000 | article only | Mar 18, 2003 08:44 am
How do I move a control file when using an spfile? | article only | Mar 18, 2003 09:41 am
How to select a particular rollback segment ? | article only | Mar 18, 2003 09:44 am
how to enable plan stability for window 2000 oracle 8.1.6 | article only | Mar 18, 2003 10:54 am
Queries againt the partitioned table | article only | Mar 18, 2003 10:59 am
parallel query on win2000 | article only | Mar 18, 2003 12:49 pm
Switching from Rule-based optimizer to Cost-based | article only | Mar 18, 2003 12:53 pm
How to Monitor Object Usage | article only | Mar 18, 2003 12:57 pm
sql loader with scheduler | article only | Mar 18, 2003 04:40 pm
Interpretation of sql_trace | article only | Mar 18, 2003 04:59 pm
row(s) 1 - 57 of 57
March 12, 2003 sadiq -- Thanks for the question regarding "Need Query to create a view", version 8.1.6
You Asked
Dear Sir

SQL> CREATE TABLE A(PRODUCT VARCHAR2(1),ITEM VARCHAR2(1));

Table created.

INSERT INTO A VALUES('X','1');
INSERT INTO A VALUES('X','2');
INSERT INTO A VALUES('X','3');

SQL> CREATE TABLE B(ITEM VARCHAR2(1),QTY NUMBER(5));

Table created.

INSERT INTO B VALUES('1',100);
INSERT INTO B VALUES('2',200);
INSERT INTO B VALUES('3',80);
INSERT INTO B VALUES('4',400);
INSERT INTO B VALUES('5',200);

QUERY    1

SQL> SELECT PRODUCT,MIN(QTY) FROM A,B WHERE A.ITEM=B.ITEM GROUP BY PRODUCT;

P  MIN(QTY)
- ---------
X        80

SQL> DELETE FROM B WHERE ITEM='2';

1 row deleted.

QUERY    2

SQL>  SELECT PRODUCT,MIN(QTY) FROM A,B WHERE A.ITEM=B.ITEM GROUP BY PRODUCT;

P  MIN(QTY)
- ---------
X        80

My Question

The output of both query is same here

I need a query which gives the above result only
if all 3 items of table A present in table B
else
no row selected

Thanks in Advance
and we said...
An outer join will be useful, then we can count the number of distinct items
from A and from B and compare them:

ops$tkyte@ORA920> select PRODUCT,
  2         MIN(QTY),
  3         count(distinct a.item),
  4         count(distinct b.item)
  5    FROM A,B
  6   WHERE A.ITEM=B.ITEM(+)
  7   GROUP BY PRODUCT;

P   MIN(QTY) COUNT(DISTINCTA.ITEM) COUNT(DISTINCTB.ITEM)
- ---------- --------------------- ---------------------
X         80                     3                     3

ops$tkyte@ORA920>
ops$tkyte@ORA920> delete from b where item = '2';

1 row deleted.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select PRODUCT,
  2         MIN(QTY),
  3         count(distinct a.item),
  4         count(distinct b.item)
  5    FROM A,B
  6   WHERE A.ITEM=B.ITEM(+)
  7   GROUP BY PRODUCT;

P   MIN(QTY) COUNT(DISTINCTA.ITEM) COUNT(DISTINCTB.ITEM)
- ---------- --------------------- ---------------------
X         80                     3                     2

ops$tkyte@ORA920>
ops$tkyte@ORA920> select PRODUCT,
  2         MIN(QTY),
  3         count(distinct a.item),
  4         count(distinct b.item)
  5    FROM A,B
  6   WHERE A.ITEM=B.ITEM(+)
  7   GROUP BY PRODUCT
  8  having count(distinct a.item) = count(distinct b.item)
  9  /

no rows selected
March 12, 2003 Srini -- Thanks for the question regarding "Getting the Day Count", version 817
You Asked
Hi Tom!
  I have a question regarding the number of days in a financial year. The year
starts from July 1st and ends next year June 30th.
  I need to get sum(salary) where date beween 07/01/00 to 06/30/01 and 07/01/01
to 06/30/02 etc.
 It should be proceed with further inputs.
  Thanks
Harry
and we said...
just add six months, and to_char to the yyyy.  consider:

ops$tkyte@ORA817DEV> select dt, to_char( add_months(dt,6), 'yyyy' )
  2    from ( select add_months(to_date('06/01/2000','mm/dd/yyyy'),rownum-1) dt
  3                     from all_objects where rownum <= 15
  4                    union all
  5           select add_months( last_day(to_date( '06/01/2000','mm/dd/yyyy' )),
                                                                     rownum-1 )
  6                     from all_objects where rownum <= 15
  7         )
  8   order by dt
  9  /

DT          TO_C
----------- ----
01-JUN-2000 2000
30-JUN-2000 2000
01-JUL-2000 2001
31-JUL-2000 2001
01-AUG-2000 2001
31-AUG-2000 2001
01-SEP-2000 2001
30-SEP-2000 2001
01-OCT-2000 2001
31-OCT-2000 2001
01-NOV-2000 2001
30-NOV-2000 2001
01-DEC-2000 2001
31-DEC-2000 2001
01-JAN-2001 2001
31-JAN-2001 2001
01-FEB-2001 2001
28-FEB-2001 2001
01-MAR-2001 2001
31-MAR-2001 2001
01-APR-2001 2001
30-APR-2001 2001
01-MAY-2001 2001
31-MAY-2001 2001
01-JUN-2001 2001
30-JUN-2001 2001
01-JUL-2001 2002
31-JUL-2001 2002
01-AUG-2001 2002
31-AUG-2001 2002

30 rows selected.
Review & Followup
Rating: 4
March 12, 2003
Reviewer: Srini
Hi Tom!

  Thanks. I got it.

Srini

March 12, 2003 Winston -- Thanks for the question regarding "Index clustered table", version 8.1.7.3
You Asked
Hi Tom,

I have been benchmarking Index cluster tables but I couldn't find it  reduce the
Logical I/o. We have two tables order(sp_ord) and order item (sp_ord_itm), which
are almost access together. We usually join them by order id, which is primary
ky of sp_ord and foreign ky of sp_ord_itm.
I have followed the steps in your 1st book and the Oracle documents. After I
created the cluster, clustered tables (sp_ord_tab1 and sp_ord_itm_tab1) I used
the following query to veriy the storage.
 SELECT COUNT(*) FROM (
  2  SELECT DBMS_ROWID.ROWID_BLOCK_NUMBER(SP_ORD_TAB1.ROWID) ORD_RID,
  3  DBMS_ROWID.ROWID_BLOCK_NUMBER(SP_ORD_ITM_TAB1.ROWID) ITM_RID,
  4  SP_ORD_TAB1.ORDER_ID
  5  FROM SP_ORD_TAB1, SP_ORD_ITM_TAB1
  6  WHERE SP_ORD_TAB1.ORDER_ID=SP_ORD_ITM_TAB1.ORDER_ID
  7  )
  8  WHERE ORD_RID <>ITM_RID
  9  /

  COUNT(*)
----------
       287

Elapsed: 00:21:1275.08
iomadmin@WSPS> select count(*) from sp_ord_tab1, SP_ORD_ITM_TAB1
  2   WHERE SP_ORD_TAB1.ORDER_ID=SP_ORD_ITM_TAB1.ORDER_ID;

  COUNT(*)
----------
   1708414

Elapsed: 00:10:637.19
iomadmin@WSPS> select 287/ 1708414 from dual;

287/1708414
-----------
 .000167992

Hence the cluster is successful. below is the tkprof result
********************************************************************************

select count(try4.order_id)
from
 sp_ord try4, sp_ord_itm where try4.order_id=sp_ord_itm.order_id and
  try4.order_num like '780%'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      4.78       4.84        342      20800         24           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      4.79       4.84        342      20800         24           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 103

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
 392677   HASH JOIN
 330075    TABLE ACCESS FULL SP_ORD
1708414    INDEX FAST FULL SCAN (object id 21152)

********************************************************************************

select try5.order_id
from
 sp_ord_tab1 try5, sp_ord_itm_tab1 where try5.order_id=
  sp_ord_itm_tab1.order_id and try5.order_num like '780%'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch    39268    344.71     608.54    1412489    3499787         12      392677
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    39270    344.71     608.54    1412489    3499787         12      392677

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 103

Rows     Row Source Operation
-------  ---------------------------------------------------
 392677  NESTED LOOPS
1708415   INDEX FAST FULL SCAN (object id 22150)
 392677   TABLE ACCESS CLUSTER SP_ORD_TAB1

********************************************************************************

select  try5.order_id
from
 sp_ord try5, sp_ord_itm where try5.order_id=sp_ord_itm.order_id and
  try5.order_num like '780495%'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.02       0.02          0       1065          0         385
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.02       0.02          0       1065          0         385

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 103

Rows     Row Source Operation
-------  ---------------------------------------------------
    385  NESTED LOOPS
    358   TABLE ACCESS BY INDEX ROWID SP_ORD
    358    INDEX RANGE SCAN (object id 21136)
    385   INDEX RANGE SCAN (object id 21152)

select  try6.order_id
from
 sp_ord_tab1 try6, sp_ord_itm_tab1 where try6.order_id=
  sp_ord_itm_tab1.order_id and try6.order_num like '780495%'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.02       0.02          0       1075          0         385
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.02       0.02          0       1075          0         385

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 103

Rows     Row Source Operation
-------  ---------------------------------------------------
    385  NESTED LOOPS
    358   TABLE ACCESS BY INDEX ROWID SP_ORD_TAB1
    358    INDEX RANGE SCAN (object id 22143)
    385   TABLE ACCESS CLUSTER SP_ORD_ITM_TAB1

Could you please advise what could be wrong? I thougth I should be able to
benefit from this feature.

Thanks in advance,
Winston
and we said...
You will not really reduce the LIO's.

What you will be able to achieve with clusters:

o locale of data.  The data for the given cluster key will be on the same
block(s).  If you have a block for a cluster key in the cache, you will have
lots of rows with that cluster key.  result -- less PIO to get the data

o the other result -- better cache efficiency.  Say you cluster on DEPTNO and
sort EMP and DEPT in that cluster.  When you get deptno = 10, you use one block
to cache the dept record AND the 10 emp records.  Compare to that a heap where
it would be very likely that you use 11 blocks of buffer cache to do the same.

So, with a cluster -- you may have a small decline in LIO's but the thing you
are trying to achieve is to have all of the SAME data stored and managed
together.

The main benefits of the b*tree cluster are:

o reduced PIO, when you do a PIO, you are not only reading the immediate row of
interest but ALL rows of interest (since you are accessing all data related to
deptno = 10 for example)

o increased cache efficiency -- you use as few blocks as possible since you've
put all of the related data for deptno = 10 on as few blocks as possible.
March 12, 2003 prashant -- Thanks for the question regarding "timezone information -- getting the timezone", version 8.1.5
You Asked
can you please tell me how to see the current timezone in oracle
and we said...
In 8.1.5, the way I would do this is via a java stored procedure
or a C extproc (in 8.0 it would just be the extproc).

In Java, it could look like this:

ops$tkyte@8i> create or replace and compile java source
              named "TZ"
  2  as
  3  import java.net.*;
  4  import java.io.*;
  5  import java.util.*;
  6  import java.text.*;
  7  public class TZ
  8  {
  9  static public void java_get_timezone( String[] p_timezone )
 10  {
 11  Date d = new Date();
 12  DateFormat df2 = new SimpleDateFormat( "z" );
 13
 14      df2.setTimeZone( TimeZone.getDefault() );
 15      p_timezone[0] = df2.format(d);
 16  }
 17  }
 18  /

Java created.

ops$tkyte@8i>
ops$tkyte@8i> create or replace
  2  procedure get_timezone( p_timezone   out varchar2 )
  3  as language java
  4  name 'TZ.java_get_timezone( java.lang.String[] )';
  5  /

Procedure created.

ops$tkyte@8i>
ops$tkyte@8i> declare
  2          tz varchar(25);
  3  begin
  4          get_timezone( tz );
  5          dbms_output.put_line( tz );
  6  end;
  7  /
EDT

PL/SQL procedure successfully completed.

In C, it might be:

long
get_timezone( OCIExtProcContext  * ctx,
              char * p_data, short * p_data_i,
              int * p_data_maxl, int * p_data_l )
{
time_t  theTime = time(NULL);

    debugf( "get_timezone() " );

    if ( *p_data_maxl < 4 )
        raise_application_error( ctx, 20001,
                      "TZ buffer must be 4 bytes or more" );

    strftime( p_data, *p_data_maxl, "%Z", gmtime(&theTime) );

    debugf( "get_timezone() returns %s", p_data );

    *p_data_l = strlen(p_data);
    *p_data_i = 0;

    return 0;
}

with a plsql wrapper that looks like:

    procedure get_timezone( p_timezone   out varchar2 )
    is external
    name "get_timezone"
    library some_lib
    language C
    with context
    parameters ( CONTEXT,
                 p_timezone     STRING,
                 p_timezone     INDICATOR short,
                 p_timezone     MAXLEN int,
                 p_timezone     LENGTH int );
Review & Followup
Rating: 4
Oracle Application Developer March 11, 2003
Reviewer: Dawar from CA USA
I like to add version number with time stamp or just version number. we are
implementing oracle application globally. Request has come to display version
number on it.

Regards,
Dawar
Hollywood, CA

Rating: 5
Oracle Application Developer March 11, 2003
Reviewer: Dawar from CA USA
appears to return the OS timezone value,is it correct?

Regards,
Dawar
Hollywood, USA

Followup:
it returns the tz of the server.

Rating: 5
Student March 11, 2003
Reviewer: Dave from CA USA
I like to write log procedures such as

procedure z_log
begin
null;
/* this log file shall track all changes to the form
Version    Date    Who        Description */

end;

could you please help me out to complete above procedure.
Background (Probably will help you to understand what I am doing? )
I also have this procedure in the template form.
On form level I wrote hard code in the pre form. I can get version number and
time but not the name.  code is as follows:

FND_STANDARD.FORM_INFO('1.0.0'  -- version number
                , '<--actual form name>'
                , 'LOFT'
                ,'96/08/06 16:20:52'                  , '<--user Name>');

Regards,
Dave

Followup:
Dawar,

not getting it. do not understand the question.

Rating: 4
student March 11, 2003
Reviewer: Dave
Tom,

I like to write procedure to keep track on all changes in  production
environment.Assume they are more than one users. This procedure will dispaly
following varaibles:

form_version, date , who(user) ,description.

Thanks. sorry for the last email.

Dave

Dave

Followup:
ok, so write said procedure?  still not getting it.

Rating: 4
student March 12, 2003
Reviewer: Dave from USA
Its done. Thanks for output.

Dave

March 12, 2003 joon -- Thanks for the question regarding "session pga memory", version 8.1.6.2
You Asked
Server have problem in memory, It have 1GB memory and 500M swap space.
Usually 300 user connected.

os : HP-UX B.11.00 A 9000/800  32 bits

Here are parameter for sort and hash

sort_area_size 500k
sort_area_retained_size 500k
hash_area_size=0
Enabled hash join.

User connect dedicated server.

So I check v$sessstat view using following sql.

select s.osuser osuser,s.serial# serial,se.sid,n.name,
       max(se.value) maxmem
from v$sesstat se,
     v$statname n
,v$session s
where n.statistic# = se.statistic#
and   n.name in ('session pga memory','session pga memory max',
                 'session uga memory','session uga memory max')
and s.sid=se.sid
group by n.name,se.sid,s.osuser,s.serial#
order by 2
;

some user's pga like this all day,pag memory didn't shrink.
username        name                         size
dougammc       session pga memory          22318856
mmcdouga       session pga memory max      22318856
mmcdouga       session uga memory            408312
mmcdouga       session uga memory max       1046160

some user's pga shrinked.
username        name                         size
imacfarl       session pga memory           1760960
imacfarl       session pga memory max      10328424
imacfarl       session uga memory            473920
imacfarl       session uga memory max       1133288

1. which init parameter dicide session pga memory?

2. How can I found user run or not  followin sql

alter session set sql_area_size=10000000;
alter session set sql_area_retained_size=1000000;

3. Can I assume username "dougammc" run following sql,
because so much pga memory allocated this user.

alter session set sql_area_size or
alter_session_retained_size

thanks.
and we said...
The PGA holds alot more then sort area's.  The PLSQL data segment is there.
There are hundreds, thousands of things that could be there.  Consider this:

ops$tkyte@DEV816> @mystat pga
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('pga')||'%'

NAME                                VALUE
------------------------------ ----------
session pga memory                 144688
session pga memory max             144688

ops$tkyte@DEV816> declare
  2  type myarray is table of varchar2(255) index by binary_integer;
  3  x  myarray;
  4  begin
  5     for i in 1 .. 1000 loop x(i) := rpad('x',255,'x'); end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@DEV816> @mystat pga
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('pga')||'%'

NAME                                VALUE
------------------------------ ----------
session pga memory                 479044
session pga memory max             479044

Now, I certainly haven't done a sort.  There are thousands of things that will
affect the size of the PGA...

So, can I shrink that?

ops$tkyte@DEV816> exec dbms_session.FREE_UNUSED_USER_MEMORY

PL/SQL procedure successfully completed.

ops$tkyte@DEV816> @mystat pga
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('pga')||'%'

NAME                                VALUE
------------------------------ ----------
session pga memory                 158400
session pga memory max             479044

ops$tkyte@DEV816>

Yes -- BUT unless you are using MTS it is an artificial shrink!!  You haven't
given any memory back to the OS.

In dedicated server mode the PGA is allocated in the process heap.  This heap
grows as needed.  You PGA is allocated using C malloc's and such in this heap.
The heap really can never shrink if you shrink the PGA, all you have done is
freed up some memory in the middle of the process heap -- but there is always
some bytes of memory at the far end of the heap that will prevent it from
shrinking.  So the amount of memory allocated to the process remains
unchanged.  You might think -- yes, but I've given back so much memory to the
process to reuse now.  No, you haven't done that either because the PGA is
itself a heap with free and used chunks in it.  All you've done in this case is
make us run slower because we'll have to rebuild that PGA heap again real soon.

The size of the PGA is totally dependant on that process does.  You can try
putting a free unused user memory in there, but unless you are using MTS it
won't make a difference.  Under MTS, the PGA is allocated out of the LARGE_POOL
(if you have it configured) in the SGA and is part of a bigger heap.  There that
free memory call would have some effect.

300 users, 1 gig of ram, you might be a candidate for MTS.  If you decide to
test that path -- make sure to allocate a large enough LARGE_POOL to avoid
trashing your shared pool (and remember that the pga's will be in the sga so
make the sga really big, 800 meg or more on a 1gig system would not be
unreasonable with MTS).
Review & Followup
Rating: 5
What is process heap? January 19, 2002
Reviewer: A reader
As subject

these technical words are really confusing me... sigh

thank you

Followup:
A process heap is the memory used by a process. Memory is managed in a "heap"
(see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:531274103649
and look for "what does heap table mean" for a quickie definition of a heap).

Process memory is the memory in use by an OS process.  A process heap is just
the heap of memory its using.

Rating: 4
Database is very slow after few minutes February 21, 2002
Reviewer: A reader
Hello TOM,
      How was your holiday?. We had very big problem using Oracle 9i on SUSE
linux 7.2. The database is very slow now. But it was working very fast with
oracle 8i and SUSE Linux 7.0. Is there any special paramaters to consider in
Oracel 9i?.
   I will give you all the parameters now. If i set wrong size please correct
me.

About Suse settings:
            SuSE Linux 7.2 (i386) - Kernel 2.4.7-4GB.

Rating: 4
Database is very slow after few minutes February 21, 2002
Reviewer: SIVABABU from GERMANY
Hello TOM,
      How was your holiday?. We had very big problem using Oracle 9i on SUSE
linux 7.2. The database is very slow now. But it was working very fast with
oracle 8i and SUSE Linux 7.0. Is there any special paramaters to consider in
Oracel 9i?.
   I will give you all the parameters now. If i set wrong size please correct
me.

About Suse settings:
            SuSE Linux 7.2 (i386) - Kernel 2.4.7-4GB.
            SHMMAX=4147483648
            SHMMNI=4096
            SEMMSL=350
            SEMMNS=32000
            SEMOPM=200
            SEMMNI=228
and Our Initsid.Ora file has..
            db_block_size=8192
            db_cache_size=67108864
            java_pool_size=117440512
            large_pool_size=151048576
            shared_pool_size=520440512
            processes=150
            sort_area_size=1524288
            timed_statistics=FALSE
 We installed with dedicated server and running Our ERP software.
 If i will shutdown the database and start it again. it is realy fast. But after
few times it goes slow. We tried ANALYSE and ALTER SYSTEM FLUSH SHARED_POOL etc.
But no helpful for us.
Can u please explain why it is so slow and what i have to tune for Oracle 9i.

  If it is not possible to solve it we will go to again down oracle 8i and SUSE
Linux 7.0.

  WE ARE WORKING LAST ONE WEEK AROUND THIS PROBLEM. BUT NO SOLUTION.

with regards,
Sivababu

Rating: 2
Confused July 24, 2002
Reviewer: H from CT, USA
Tom, you said :
"If you decide to test that path -- make sure to allocate a large enough
LARGE_POOL to avoid trashing your shared pool (and remember that the pga's will
be in the sga so make the sga really big, 800 meg or more on a 1gig system would
not be unreasonable with MTS)."

And you also said (in question titled PGA/UGA MEMORY)
"The PGA is never allocated out of Oracle's SGA – it is always allocated locally
by the process or thread."

Which is true?

Followup:
In dedicated server mode, the UGA is allocated in the PGA (in the process, not
the SGA)

In MTS (aka shared server) mode, the UGA is allocated in the SGA (NOT in the
process, in the SGA). If you have a large pool, the UGA will be allocated in
there, if not -- it'll be in the shared pool.

The PGA is *never* in the SGA.  The UGA is sometimes in the PGA, sometimes in
the SGA.

So, all of the above -- is true ;)

Rating: 5
What about hash area? November 14, 2002
Reviewer: Alex Daher from Brazil
Tom,

 What about the hash_area_size? Where is it allocated from?

Regards,
Alex

Followup:
pga

Rating: 4
And... November 19, 2002
Reviewer: Alex Daher from Brazil
Another easy one...

Once hash area is alocated, it's never released to OS, only
to use of PGA, right? Just like sort_area_size?

Cheers!
Alex

Followup:
it is like the sort area size -- however to say it is "not released" is an
overstatement if you ask me.

Rating: 5
Please clarify. November 19, 2002
Reviewer: Yogeeraj from Mauritius
hello,

You said:
<quote>
300 users, 1 gig of ram, you might be a candidate for MTS.
</quote>

Please explain how we would decide whether we have to go the MTS way or the
DEDICATED server one. Here, it seems like you base yourself on the number of
users and available RAM...

In Chapter 2, you wrote (more about OLTP, transactions and their duration):
<quote>
This is rule number one for MTS - make sure your transactions are short in
duration. They can be frequent, but they should be short (as characterized by
OLTP systems). If they are not, you will get what appears to be a total system
slowdown due to shared resources being monopolized by a few processes. In
extreme cases, if all of the shared servers are busy, the system will appear to
be hang.

So, MTS is highly appropriate for an OLTP system characterized by short,
frequent transactions. In an OLTP system, transactions are executed in
milliseconds - nothing ever takes more than a fraction of a second. MTS on the
other hand is highly appropriate for a data warehouse. Here, you might execute a
query that takes one, two, five, or more minutes. Under MTS, this would be
deadly. If you have a system that is 90 percent OLTP and 10 percent 'not quite
OLTP', then you can mix and match dedicated servers and MTS on the same
instance. In this fashion, you can reduce the number of processes on the machine
dramatically for the OLTP users, and make it so that the 'not quite OLTP' users
do not monopolize their shared servers.
</quote>

Would you also recommend a "mix and match of dedicated servers and MTS on the
same instance" in systems - "75% OLTP and 25% 'not quite OLTP'" with a very
large user community (300 - 400 users, 4 GB RAM)?

thank you in advance for the clarifications and guidance. I am currently
reviewing my system with has had a considerable increase in the number of users
recently. (Maybe your next long awaited book on Performance Tuning would
definitely have helped me a lot ;) )

Regards
Yogeeraj

Followup:
Here was my thought process.

1-3meg of ram per user.  Say 3m.  That is 900meg of ram.  That is almost the
entire machine.  Bummer.  Not much left for an SGA.

So, what can we do?  If we enable MTS and set shared servers to 10, we'll have
at most 10 concurrent users (hence the need for SHORT transactions) but we'll
also have ONLY 10 pga's (sort areas and other stuff).  We'll still have 300
UGA's (session specific memory) and they'll ALL be in the large_pool in the SGA
now, not dynamically allocated from the OS.  We *may* be able to fit them all in
there now given that we have 290 LESS pga's to deal with -- maybe.

If you have 4gig of ram, you might not be using MTS for the same reason.  You
need 1 to 2 gig of ram for the dedicated servers maybe (large, I rounded up) and
you have 1gig left over for an SGA and 1 gig for everything else.

I would definitely mix MTS and dedicated server processes -- if you have long
running batch jobs, they almost certainly NEED dedicated servers.

Rating: 4
Some clarification November 20, 2002
Reviewer: Vijay Sehgal from India
Could you throw some light on deciding factors for Dedicated/Mutithreaded server
setups

Followup:
see above...  memory, number of processes on the server, duration of
transactions...

Rating: 3
non-standard block size buffers March 12, 2003
Reviewer: Reader from India
Tom,
when do we need to set the non-standard block size buffers?..

DB_2K_CACHE_SIZE,DB_4K_CACHE_SIZE,DB_8K_CACHE_SIZE,DB_16K_CACHE_SIZE,DB_32K_CACHE
_SIZE ...what should i set as my non-standard block size when i have a block
size of 8k. is it always neccessary to set the non-standard block size
buffers!!! or its dependent on ????

please through some info..

Thanks

Followup:
only if you actually use them.

if you use a single block size, don't set them
if you want to use more then one block size, set them

Rating: 4
reader March 12, 2003
Reviewer: reader from India
Tom, i need some more info from you...

Under what circumstances do we need to set different blocksize?...hope the
blocksize is defined at tablespace level - am i right here?. Please, explain me
with a scenario where we may need different block size - if u can spare some
time on this!

and if i use different block size, how should the setting of DB_nK_CACHE_SIZE be
calculated?.

Thanks for your time and effort...

Followup:
you would use it primarily to transport a tablespace from a small blocksize
database (OLTP) to a big blocksize database (DW) to perform an ETL
(extract/transform/load)

You would not use it day to day generally.  I've yet to see a site using more
then one block size day to day.

Rating: 4
Thanks for your prompt reply - as always ! March 12, 2003
Reviewer: reader

March 12, 2003 CJ -- Thanks for the question regarding "Update statement help", version 8.1.6
You Asked
I'm trying to write an efficient update statement, but I'm not a SQL guru and am
wondering if my thinking is correct.

The gist of the update is to set an exception flag if multiple occurances of a
record occur. This is a data heap instance where records get stuck in without
being cleansed first, so there could be multiples with the same key. The records
get a sequence number added, so the update sets the exception flag to 'E' for
those records not having the latest sequence number for that key. The flag may
already be set from a previous update. The table contains about 20 million rows
and there may be only a handful of duplicates.

Here's my SQL:
update /*+ PARALLEL(t1,4) */ tab t1
  set exception_flag='E'
    where exception_flag is null
      and seqno <
          (select max(seqno) from tab t2
             where t1.key=t2.key)

Could you comment?

Thanks very much.
and we said...
Need some setup for PDML
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/c22paral.htm#880
in particular, table must be partitioned in your release.

update t
   set exception_flag = 'E'
 where rowid in ( select rid
                    from (select rowid rid, seqno,
                                 max(seqno) over (partition by key) maxseq
                            from t
                          )
                   where seqno <> maxseq
                )
   and exception_flag is not null
/

might do it well.  You can try running the inner query to

a) see how it'll perform
b) see how many rows will be affected

first.  I'd rather do that then run a correlated subquery 20 million times!
That inner query will be a big scan and then it will be rowid access to do the
update.

Your current query will be a big scan to drive the update and then 20 million
correlated subqueries....
Review & Followup
Rating: 5
Thank you! November 27, 2002
Reviewer: CJ from Albany, NY, USA
Excellent. Thanks!

Rating: 5
additional functionality needed March 12, 2003
Reviewer: CJ from Albany, NY
The sql you suggested works great, however I now additionally have to set the
exception_flag to null where the seqno = maxseq, since there are some deletes
that could be processed that delete the non-exception record and I have to set
the next one in line to be not an exception. Can I do that within the same
statement?

So basically, I need to update the records that aren't the maxseq to be 'E' and
the one record that is = maxseq to be null. Can I do that in one statement?

Followup:
try:

ops$tkyte@ORA817DEV> select * from t;

E      SEQNO        KEY
- ---------- ----------
X          1          1
X          2          1
X          3          1
X         11          2
X         22          2
X         33          2

6 rows selected.

ops$tkyte@ORA817DEV> update ( select exception_flag, seqno,
  2                  (select max(seqno)
  3                     from t t2
  4                    where t2.key = t.key ) maxseq
  5             from t
  6            where exception_flag is not null )
  7    set exception_flag = decode( seqno, maxseq, NULL, 'E' )
  8  /

6 rows updated.

ops$tkyte@ORA817DEV> select * from t;

E      SEQNO        KEY
- ---------- ----------
E          1          1
E          2          1
           3          1
E         11          2
E         22          2
          33          2

6 rows selected.

Rating: 3
Any other options? March 12, 2003
Reviewer: CJ
You suggested:
ops$tkyte@ORA817DEV> update ( select exception_flag, seqno,
  2                  (select max(seqno)
  3                     from t t2
  4                    where t2.key = t.key ) maxseq
  5             from t
  6            where exception_flag is not null )
  7    set exception_flag = decode( seqno, maxseq, NULL, 'E' )
  8  /

That's great, but for a 129K row table, the execution time has gone from < 10
seconds on average to, well, I control-C'd after 5 minutes. That is one of my
small tables. I also have tables with over 20M rows that take about 45 minutes
with the original sql. Can't imagine what they'd be with the new. Should I just
run the original sql twice, once the existing way and once with the opposite
update (set exception_flag=null where seqno=maxseq?

Followup:
yup, twice will do it.

Rating: 5
Thanks! March 13, 2003
Reviewer: CJ
As always, thanks for your help.

March 13, 2003 Li -- Thanks for the question regarding "table(cast(...)) performance tunning question", version 8.1.7
You Asked
I'm using table(cast(..)) feature to create a view by calling a PL/SQL package.
But I had a performance problem which is caused by calling the PL/SQL package
too many time.
Here is my view's sample definition:
CREATE OR REPLACE VIEW my_vl_not_perf AS
SELECT  B.TYPE, T.NAME, 'Y' as VIEW_FLAG, 'Y' as UPDATE_FLAG
FROM   my_tl T, my_b B,
      TABLE(CAST(my_package.my_function('UPDATE') as my_inst_pk_vals)) UPDATE_V
WHERE  B.LIST_HEADER_ID = T.LIST_HEADER_ID and
       T.LANGUAGE = userenv('LANG') and
        B.LIST_HEADER_ID = UPDATE_V.INSTANCE_PK1_VALUE
UNION
SELECT  B.TYPE, T.NAME, 'Y' as VIEW_FLAG, 'N' as UPDATE_FLAG
FROM   my_tl T, my_b B,
      TABLE(CAST(my_package.my_function('VIEW') as my_inst_pk_vals)) VIEW_V
WHERE  B.LIST_HEADER_ID = T.LIST_HEADER_ID and
       T.LANGUAGE = userenv('LANG') and
       B.LIST_HEADER_ID = VIEW_V.INSTANCE_PK1_VALUE and
       B.LIST_HEADER_ID NOT IN(
                SELECT instance_pk1_value
                FROM TABLE(CAST(my_package.my_function('UPDATE') as
my_inst_pk_vals)) UPDATE_V)
UNION
SELECT  B.TYPE, T.NAME, 'N' as VIEW_FLAG, 'N' as UPDATE
_FLAG
FROM   my_tl T, my_b B
WHERE  B.LIST_HEADER_ID = T.LIST_HEADER_ID and
       T.LANGUAGE = userenv('LANG') and
       B.LIST_HEADER_ID NOT IN(
                SELECT instance_pk1_value
                FROM TABLE(CAST(my_package.my_function('VIEW') as qp
_inst_pk_vals)) VIEW_V )
/
I have 130 rows in the base table and the PL/SQL package was called 266 times. I
notice that the problem is putting 'table(cast(...))' nested in the where
clause. If I have a view like this:
CREATE OR REPLACE VIEW my_vl_not_perf AS
SELECT  B.TYPE, T.NAME, 'Y' as VIEW_FLAG, 'Y' as UPDATE_FLAG
FROM   my_tl T, my_b B,
      TABLE(CAST(my_package.my_function('UPDATE') as my_inst_pk_vals)) UPDATE_V
WHERE  B.LIST_HEADER_ID = T.LIST_HEADER_ID and
       T.LANGUAGE = userenv('LANG') and
        B.LIST_HEADER_ID = UPDATE_V.INSTANCE_PK1_VALUE
/
The PL/SQL pakcage was called only once. Is there any way to tune the view
definition and solve the problem of calling PL/SQL package too many times? We
can only stand the PL/SQL package being called no more than 4 times. But now,
the times is depending on the rows' number. It's very bad.
Thanks for your help!

Thanks, Tom. I tried the suggestion you have given, by using:
CREATE OR REPLACE VIEW my_vl_not_perf AS
SELECT  B.TYPE, T.NAME, 'Y' as VIEW_FLAG, 'Y' as UPDATE_FLAG
FROM   my_tl T, my_b B,
      TABLE(CAST(my_package.my_function('UPDATE') as my_inst_pk_vals)) UPDATE_V
WHERE  B.LIST_HEADER_ID = T.LIST_HEADER_ID and
       T.LANGUAGE = userenv('LANG') and
        B.LIST_HEADER_ID = UPDATE_V.INSTANCE_PK1_VALUE
UNION
SELECT  B.TYPE, T.NAME, 'Y' as VIEW_FLAG, 'N' as UPDATE_FLAG
FROM   my_tl T, my_b B,
      TABLE(CAST(my_package.my_function('VIEW') as my_inst_pk_vals)) VIEW_V
WHERE  B.LIST_HEADER_ID = T.LIST_HEADER_ID and
       T.LANGUAGE = userenv('LANG') and
       B.LIST_HEADER_ID = VIEW_V.INSTANCE_PK1_VALUE and
       B.LIST_HEADER_ID NOT IN(
                SELECT instance_pk1_value
                FROM (select a.*, rownum R
                      from TABLE(CAST(my_package.my_function('UPDATE') as
my_inst_pk_vals)) a) UPDATE_V)
UNION
SELECT  B.TYPE, T.NAME, 'N' as VIEW_FLAG, 'N' as UPDATE
_FLAG
FROM   my_tl T, my_b B
WHERE  B.LIST_HEADER_ID = T.LIST_HEADER_ID and
       T.LANGUAGE = userenv('LANG') and
       B.LIST_HEADER_ID NOT IN(
                SELECT instance_pk1_value
                FROM (select a.*, rownum R
                      from TABLE(CAST(my_package.my_function('VIEW') as
qp_inst_pk_vals)) a) VIEW_V )
/

There is no change to the times of PL/SQL package called.(130 records, called
272 times). No change to Select count(*) from my_vl_not_perf; took Elapsed:
00:00:04.15).For your first suggestion, we can take advantage of UNION ALL. But
currently, the major performance lose is lying on too many times of PL/SQL
package calls(130 records, 272 times of PL/SQL package calls). Ideally, it
should only enter the package 2 times, one for table of list_header_ids for
'UPDATE' and one for table of list_header_ids for 'VIEW'.
Thanks again,
Li
and we said...
Here is my full test case modeled after yours.  We can do this by "anti-joining"
instead of NOT IN'ing.

that is, the construct:

from a, b
  where ...
     and b.c NOT IN ( select d from t )

can be rewritten as:

from a, b, (select distinct * from t ) t
 where ...
   and b.c = t.d(+) AND t.d IS NULL
/

now, the distinct will cause us to materialize that unindexed result set into
TEMP (we want that -- to avoid calling Plsql to materialize it).  the other
rownum trick does the same.  So, here is the before (96 calls to plsql in my
example) and the after with the entirely rewritten view (4 calls, always 4)

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace type myScalarType as object
  2  ( instance_pk1_value number )
  3  /

Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace type my_inst_pk_vals as
table of myScalarType
  2  /

Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table my_tl( name varchar2(5),
language varchar2(25), list_header_id number );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table my_b( type varchar2(5),
list_header_id number );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into my_tl
  2  select rownum,     userenv('LANG'), rownum from all_users;

47 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into my_b
  2  select rownum, rownum from all_users;

47 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package my_package
  2  as
  3          function my_function( p_str in varchar2 ) return my_inst_pk_vals;
  4  end;
  5  /

Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package body my_package
  2  as
  3          function my_function( p_str in varchar2 ) return my_inst_pk_vals
  4          is
  5                  l_data my_inst_pk_vals;
  6          begin
  7                  select cast( multiset(select rownum from all_users) as
my_inst_pk_vals)
  8                    into l_data
  9                    from dual;
 10                  dbms_application_info.set_client_info(
to_number(userenv('client_info'))+1 );
 11                  return l_data;
 12          end;
 13  end;
 14  /

Package body created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE OR REPLACE VIEW my_vl_not_perf AS
  2  SELECT     B.TYPE, T.NAME, 'Y' as VIEW_FLAG, 'Y' as UPDATE_FLAG
  3  FROM   my_tl T, my_b B,
  4        TABLE(CAST(my_package.my_function('UPDATE') as my_inst_pk_vals))
UPDATE_V
  5  WHERE  B.LIST_HEADER_ID = T.LIST_HEADER_ID and
  6         T.LANGUAGE = userenv('LANG') and
  7          B.LIST_HEADER_ID = UPDATE_V.INSTANCE_PK1_VALUE
  8  UNION ALL
  9  SELECT     B.TYPE, T.NAME, 'Y' as VIEW_FLAG, 'N' as UPDATE_FLAG
 10  FROM   my_tl T, my_b B,
 11        TABLE(CAST(my_package.my_function('VIEW') as my_inst_pk_vals)) VIEW_V
 12  WHERE  B.LIST_HEADER_ID = T.LIST_HEADER_ID and
 13         T.LANGUAGE = userenv('LANG') and
 14         B.LIST_HEADER_ID = VIEW_V.INSTANCE_PK1_VALUE and
 15         B.LIST_HEADER_ID NOT IN(
 16                  SELECT instance_pk1_value
 17                  FROM (select a.*, rownum R
 18                        from TABLE(CAST(my_package.my_function('UPDATE') as
my_inst_pk_vals)) a) UPDATE_V)
 19  UNION ALL
 20  SELECT     B.TYPE, T.NAME, 'N' as VIEW_FLAG, 'N' as UPDATE_FLAG
 21  FROM   my_tl T, my_b B
 22  WHERE  B.LIST_HEADER_ID = T.LIST_HEADER_ID and
 23         T.LANGUAGE = userenv('LANG') and
 24         B.LIST_HEADER_ID NOT IN(
 25                  SELECT instance_pk1_value
 26                  FROM (select a.*, rownum R
 27                        from TABLE(CAST(my_package.my_function('VIEW') as
my_inst_pk_vals)) a) VIEW_V )
 28  /

View created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec
dbms_application_info.set_client_info(0);

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from my_vl_not_perf;

TYPE  NAME                           V U
----- ------------------------------ - -
1     1                              Y Y
2     2                              Y Y
...
46    46                             Y Y
47    47                             Y Y

47 rows selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
96

see, 96 total calls for all of the NOT IN full scans..., now we'll anti
join:

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> cREATE OR REPLACE VIEW my_vl_not_perf AS
  2  SELECT     B.TYPE, T.NAME, 'Y' as VIEW_FLAG, 'Y' as UPDATE_FLAG
  3  FROM   my_tl T, my_b B,
  4        TABLE(CAST(my_package.my_function('UPDATE') as my_inst_pk_vals))
UPDATE_V
  5  WHERE  B.LIST_HEADER_ID = T.LIST_HEADER_ID and
  6         T.LANGUAGE = userenv('LANG') and
  7          B.LIST_HEADER_ID = UPDATE_V.INSTANCE_PK1_VALUE
  8  UNION ALL
  9  SELECT     B.TYPE, T.NAME, 'Y' as VIEW_FLAG, 'N' as UPDATE_FLAG
 10  FROM   my_tl T, my_b B,
 11            (select a.*, rownum r1 from
 12        TABLE(CAST(my_package.my_function('VIEW') as my_inst_pk_vals))a)
VIEW_V,
 13            (select distinct * from
 14        TABLE(CAST(my_package.my_function('UPDATE') as my_inst_pk_vals)))
UPDATE_V
 15  WHERE  B.LIST_HEADER_ID = T.LIST_HEADER_ID and
 16         T.LANGUAGE = userenv('LANG') and
 17         B.LIST_HEADER_ID = VIEW_V.INSTANCE_PK1_VALUE and
 18         B.LIST_HEADER_ID = update_v.instance_pk1_value (+)
 19             and update_v.instance_pk1_value IS NULL
 20  UNION ALL
 21  SELECT     B.TYPE, T.NAME, 'N' as VIEW_FLAG, 'N' as UPDATE_FLAG
 22  FROM   my_tl T, my_b B,
 23                  (SELECT distinct instance_pk1_value
 24                  FROM TABLE(CAST(my_package.my_function('VIEW') as
my_inst_pk_vals)) ) update_v
 25  WHERE  B.LIST_HEADER_ID = T.LIST_HEADER_ID and
 26         T.LANGUAGE = userenv('LANG') and
 27         B.LIST_HEADER_ID = update_v.instance_pk1_value(+)
 28             and update_v.instance_pk1_value IS NULL
 29  /

View created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec
dbms_application_info.set_client_info(0);

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from my_vl_not_perf;

TYPE  NAME                           V U
----- ------------------------------ - -
1     1                              Y Y
2     2                              Y Y
3     3                              Y Y
...
46    46                             Y Y
47    47                             Y Y

47 rows selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
4

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
Review & Followup
Rating: 5
It is very helpful. November 23, 2002
Reviewer: Li from USA
Thanks a lot, Tom. It exactly solves my problem.

Rating: 5
- March 11, 2003
Reviewer: A reader from Bangalore
Tom,

A general question. Would selecting from a database table be faster than
selecting from a PLSQL table of the same size?
We have in our application a function that populates a PL/SQL table and returns
a ref cursor based on that PLSQL table using CAST. The other option was to
insert into a temporary database table using FOR ALL and fetch from it. The
function has to return a refcursor.
Which approach seems better given that there could be around 20000 records.

Thanks a lot for your time.

Followup:
When you benchmarked it using something like:
http://asktom.oracle.com/~tkyte/runstats.html
so you can see elapsed time as well as latching, what did you observe?

Rating: 5
- March 11, 2003
Reviewer: - from Bangalore
Tom,

Thanks for the quick reply.

Actually no. We didnt try the two approaches. Situation was such that we had to
make a choice there.

It was a tradeoff between Insert+select from temp table vs select from the
PL/SQL table which was already built. We settled for the second because it
appeared much less work to us.
Is there any knows caveats for the PL/SQL table with CAST approach on 20,000 to
30,000 records?

thanks

Followup:
20-30k records -- RAM, CPU intensive.  very much so.

Rating: 4
March 12, 2003
Reviewer: A reader
hi tom,

a very interesting solution. but did I get it right: the DISTINCT forces ORACLE
to temporarily "store" the results for "future use"? can this in general be
assumed in cases like this (inline views)? and what did you mean by "the other
rownum trick does the same"?

Followup:
yes.

Rating: 4
March 12, 2003
Reviewer: A reader
hello tom,

but "yes" fits the first two questions only ...

Followup:
the rownum trick does the same thing as the distinct.  it materialized the
inline view for us, making it so that the plsql is not called over and over.

Rating: 4
Problems with CBO statistical info on table(cast(...)) March 12, 2003
Reviewer: Dr. John Doe from Dallas, TX USA
Tom,

There appears to be a problem with the CBO being unable to estimate the
cardinality / cost of a casted PL/SQL table:

create table t as select * from all_objects;
create index t_n00 on t (object_id);
analyze table t compute statistics;
create or replace type myNumTabType as table of number
/
set autotrace traceonly
select t.*
from (select column_value from table(cast(myNumTabType(50) as myNumTabType))) f,
t
where f.column_value = t.object_id;
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=46 Card=8168 Bytes=628936)
   1    0   HASH JOIN (Cost=46 Card=8168 Bytes=628936)
   2    1     COLLECTION ITERATOR (CONSTRUCTOR FETCH)
   3    1     TABLE ACCESS (FULL) OF 'T' (Cost=14 Card=7041 Bytes=542157)

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

Trying again adding the following hint:
/*+ ORDERED USE_NL(f,t) */
(You can use the dreaded RULE hint too :-()
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=16352 Card=8168 Bytes=628936)
   1    0   NESTED LOOPS (Cost=16352 Card=8168 Bytes=628936)
   2    1     COLLECTION ITERATOR (CONSTRUCTOR FETCH)
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=7041
Bytes=542157)
   4    3       INDEX (RANGE SCAN) OF 'T_N00' (NON-UNIQUE) (Cost=1 Card=7041)

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

Looks like the CBO thinks there are 8168 rows in the table(cast(...))

(Tested on 8.1.6)

Followup:
It is using a heuristic, yes.  There is not much else it can do.

Rating: 5
Agreed, but... March 13, 2003
Reviewer: Dr. John Doe from Dallas, TX USA
Wouldn't it be nice if it peeked somehow and got the count?  Maybe a hint like
ROWS(f,100) where we could help it along?

Rating: 4
Is it possible that the two hints won't work? April 28, 2003
Reviewer: Li from CA, USA
Tom:
This is Li again. Recently one customer is having a very bad performance problem
with my view which is prototyped like here.
From the tkprof they provided, my PL/SQL package used in the view definition is
called hundrends of times instead of 4 times for thousands of rows. I couldn't
reproduce it in any internal environment. All the development environments I can
test has that view calling my PL/SQL package 4 times, which is what was
expected.
Is there any possibility the hints in the prototypes won't work, or didn't work
very well? Customer db is 8.1.7.3.6.
Thanks,
Li

Rating: 3
Problem can be solved by adding leading hint May 30, 2003
Reviewer: Li from USA
There is a way I have exercised which fixes the performance problem. It turned
out that some versions of Optimizer need more hint to come out an optimal plan.

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production

SQL> CREATE OR REPLACE VIEW my_vl_not_perf AS
  2  SELECT /*+ leading(update_v) */ B.TYPE, T.NAME, 'Y' as VIEW_FLAG, 'Y' as
UPDATE_FLAG
  3  FROM   my_tl T, my_b B,
  4        TABLE(CAST(my_package.my_function('UPDATE') as my_inst_pk_vals))
UPDATE_V
  5  WHERE  B.LIST_HEADER_ID = T.LIST_HEADER_ID and
  6         T.LANGUAGE = userenv('LANG') and
  7          B.LIST_HEADER_ID = UPDATE_V.INSTANCE_PK1_VALUE
  8  UNION ALL
  9  SELECT /*+ leading(view_v) */    B.TYPE, T.NAME, 'Y' as VIEW_FLAG, 'N' as
UPDATE_FLAG
 10  FROM   my_tl T, my_b B,
 11            (select a.*, rownum r1 from
 12        TABLE(CAST(my_package.my_function('VIEW') as my_inst_pk_vals))a)
VIEW_V,
 13            (select distinct * from
 14        TABLE(CAST(my_package.my_function('UPDATE') as my_inst_pk_vals)))
UPDATE_V
 15  WHERE  B.LIST_HEADER_ID = T.LIST_HEADER_ID and
 16         T.LANGUAGE = userenv('LANG') and
 17         B.LIST_HEADER_ID = VIEW_V.INSTANCE_PK1_VALUE and
 18         B.LIST_HEADER_ID = update_v.instance_pk1_value (+)
 19             and update_v.instance_pk1_value IS NULL
 20  UNION ALL
 21  SELECT  /*+ leading(view_v) */   B.TYPE, T.NAME, 'N' as VIEW_FLAG, 'N' as
UPDATE_FLAG
 22  FROM   my_tl T, my_b B,
 23                  (SELECT distinct instance_pk1_value
 24                  FROM TABLE(CAST(my_package.my_function('VIEW') as
my_inst_pk_vals)) ) view_v
 25  WHERE  B.LIST_HEADER_ID = T.LIST_HEADER_ID and
 26         T.LANGUAGE = userenv('LANG') and
 27         B.LIST_HEADER_ID = view_v.instance_pk1_value(+)
 28             and view_v.instance_pk1_value IS NULL

March 13, 2003 Niraj -- Thanks for the question regarding "getting jdbc 2.0 drivers", version
You Asked
Does oracle provides a jdbc thin/oci driver that
supports JDK 1.2 and JDBC 2.0 ? We are having a problem in using

jdbc drivers with JDK 1.2 and classes111.zip. Where we can get a

jdbc driver that supports JDBC 2.0 ?
and we said...
Yes.

Get the 8.1.6 drivers from
http://technet.oracle.com/
under software.  They are jdk
1.2 and JDBC 2.0 compliant.
Review & Followup
Rating: 5
JDBC Version March 12, 2003
Reviewer: Ashok from USA
How to identify the JDBC version which is loaded inside oracle?

Oracle version:8.1.7
JDK inside Oracle:1.2.1

Followup:
just write a small java stored procedure to

    DatabaseMetaData meta=conn.getMetaData();
    System.out.println
    ("JDBC driver version is "+meta.getDriverVersion());

March 13, 2003 Bijay -- Thanks for the question regarding "Spliting ", version 9.0.2.
You Asked
Hi Tom
I have a table containing some records and I want to split into one record from
multiple reocrds
It is like this:
In the source table I have IND which can be 'A','E','B' and a measure column
which can be BR,CA,CC,GA,GR,WH and amount column that corresponds to each record
with respect to Indicator(IND) and MEASURE for a particular PFCNTR,BRAND and
CAL_WE_DATE in the source table.

I want to make a single record from source table in the target table where the
PFCNTR,BRAND,CAL_WE_DATE and based on indicatior abd measure the corresponding
amount will go to corresponding columns like IND='A' and 'MEASURE='GR' that
amount will go to ACT_GROSS and
like IND='E' and 'MEASURE='GR' that amount will go to EST_GROSS
like IND='B' and 'MEASURE='GR' that amount will go to BUD_GROSS
and so on.

What is the simplest procedure to this.

Source Table
=============
SQL> desc reported
 Name                                      Null?    Type
 ----------------------------------------- -------- ------------------
 PFCNTR                                    NOT NULL NUMBER(6)
 BRAND                                     NOT NULL VARCHAR2(2)
 MEASURE                                   NOT NULL VARCHAR2(2)
 CAL_WE_DATE                               NOT NULL DATE
 IND                                       NOT NULL VARCHAR2(1)
 AMOUNT                                             NUMBER(12,2)
 USER_ID                                            VARCHAR2(12)
 UPDATE_DATE                                        DATE

Target Table:
SQL> desc reported_flat
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------
 PFCNTR                                             NUMBER(6)
 BRAND                                              VARCHAR2(2)
 CAL_WE_DATE                                        DATE
 ACT_GROSS                                          NUMBER(10,2)
 ACT_RETAIL                                         NUMBER(10,2)
 ACT_WHOLESALE                                      NUMBER(10,2)
 ACT_BRANDED                                        NUMBER(10,2)
 ACT_CUST_CNT                                       NUMBER(10,2)
 ACT_GALLONS                                        NUMBER(10,2)
 BUD_GROSS                                          NUMBER(10,2)
 BUD_RETAIL                                         NUMBER(10,2)
 BUD_WHOLESALE                                      NUMBER(10,2)
 BUD_BRANDED                                        NUMBER(10,2)
 BUD_GALLONS                                        NUMBER(10,2)
 EST_GROSS                                          NUMBER(10,2)
 EST_GALLONS                                        NUMBER(10,2)
and we said...
create table reported_flat
as
select pfcntr, brand, cal_we_date,
max( case when ind='A' and measure='GR' then AMOUNT else null end) act_gross,
max( case when ind='E' and measure='GR' then AMOUNT else null end) est_gross,
max( case when ind='B' and measure='GR' then AMOUNT else null end) bud_gross,
.....
from reported
group by pfcntr, brand, cal_we_date
/

will do that -- a simple "pivot".  See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7914418133213
for an explanation of how i pivoted data using MAX() here...
Review & Followup
Rating: 4
ONE COULMN SHOULD HAVE VALUE AT ONE TIME March 12, 2003
Reviewer: bijay from MA,USA
Hi Tom
This question is from the above split question,
I would like if act_gross is having value then est_gross should be null, and
vice versa.

Both should not have value at the same time,

How will I check in SQL.

Thanks

Followup:
create table ...
( ....,
  constraint check_one_or_the_other check ( NOT( act_gross is not null and
est_gross is not null ) );

Rating: 4
ONE COULMN SHOULD HAVE VALUE AT ONE TIME March 12, 2003
Reviewer: bijay from MA,USA
Hi Tom
This question is from the above split question,
I would like if act_gross is having value then est_gross should be null, and
vice versa.

Both should not have value at the same time,

How will I check in SQL.

Thanks

March 14, 2003 Bhavani -- Thanks for the question regarding "Coming out after first find", version 8.1.7
You Asked
Hi Tom,

I want to find out existance of a value in a table.  As soon the control finds
out first existance, i want the control to come out from the select
statement(basically stopping full table scan or fetching all the records even if
using indexes) so that, lot of time will be saved.  There are 40 Million records
in that table.

I heard, hints can be used for that.  Is that true?  Can you please explain me?

Thank you
Sincerely
Bhavani
and we said...
select count(*)
  from dual
 where EXISTS ( select null
                  from your_table
                 where condition.... )

That'll return 0 or 1.  It'll stop running the subquery the moment it finds the
first row.

No hints needed (and a hint could not help here at all)
Review & Followup
Rating: 5
Thank you Tom April 26, 2002
Reviewer: Bhavani from India
Great.  Thank you very much Tom.

Rating: 5
Great Going April 26, 2002
Reviewer: Parag from India
Too Good

Rating: 5
Good April 26, 2002
Reviewer: A reader
Hi Tom,
How about adding rownum=1 in the where clause, for this?
Regards,

Followup:
Yes you can HOWEVER, I prefer the select count(*) approach as it

a) returns AT LEAST one record
b) returns AT MOST one record

hence my code is easier (don't have to deal with "no data found" -- i always get
0 or 1)

Rating: 4
How to do this? March 13, 2003
Reviewer: Bhavani from India
Hi Tom,

Is there any way to ask the control to come out of the query if there are more
than X rows (say 100) with the given where condition.......without doing the
full table scan...

Thanks in Advance
Bhavani

Followup:
select *
  from ( YOUR_QUERY_HERE )
 where rownum <= 100;

March 14, 2003 Abdullah -- Thanks for the question regarding "DDL Trigger", version 7.3.4 & 8.0.5
You Asked
Hi, Tom

1)I create this trigger to monitor create and drop statement,bu
if the user was open two session from the same machine when
doing create or drop statement the  trigger will fail, but if i
open one session it will successfull. So how can i get the
sid,and serial# number for the current session, which i use to
issue create statement -so the trigger will fire- the log table
is already created.

create or replace trigger ddl_trigger
after create or alter or drop on SCHEMA
declare
l_machine  varchar2(15);
l_terminal varchar2(15);
l_prog     varchar2(15);
l_osuser   varchar2(15);
begin
select machine,terminal,program,osuser into
l_machine,l_terminal,l_prog,l_osuser
 from v$session
 where user = upper(osuser);
   insert into log
 select
ora_sysevent,ora_dict_obj_owner,ora_dict_obj_name,ora_dict_obj_ty
pe,
       to_char(sysdate,'fm dd/Mon/yyyy HH:MI:SS
AM'),l_machine,l_terminal,l_prog,l_osuser
   from dual;
end;

2) How can i write a trigger to write logs  for any create or
drop statement for all object in the database or in a spacefic
schema in oracle8i and oracle7.3.4.

3) can i spool the trigger result to OS file or write to the os
direct instead of insert into table or write to databse table.

 Thanks
Abdullah
and we said...
1) the trigger would look like this:

create or replace trigger ddl_trigger
after create or alter or drop on SCHEMA
begin
    insert into log
    select ora_sysevent,ora_dict_obj_owner,
           ora_dict_obj_name,ora_dict_obj_type,
           to_char(sysdate,'fm dd/Mon/yyyy HH:MI:SS AM'),
           machine,terminal,program,osuser
      from v$session
     where audsid = sys_context( 'userenv', 'sessionid' );

    if ( sql%rowcount <> 1 )
    then
        raise_application_error
        ( -20001, 'Unable to id your session' );
    end if;
end;
/

2) you cannot, event triggers are a new feature of Oracle8i
release 8.1 and up only.  You would have to enable AUDITING
which frankly captures the same type of information you are
above.  You might consider just using auditing in 8i as well.

3) UTL_FILE allows PLSQL to write to an OS file.
Review & Followup
Rating: 3
GRANT TO V$SESSION March 07, 2002
Reviewer: neeti
I am trying to use the same example on my schema but getting the following
error...

Warning: Trigger created with compilation errors.

Elapsed: 00:00:00.17
Errors for TRIGGER DDL_TRIGGER:

LINE/COL ERROR
-------- -----------------------------------------------------------------
2/5      PL/SQL: SQL Statement ignored
7/12     PLS-00201: identifier 'SYS.V$SESSION' must be declared

Please help..

Followup:
As SYS or "as sysdba" or internal (8i and before) issue:

grant select on v_$session to YOUR_SCHEMA;

Rating: 5
It worked.... March 07, 2002
Reviewer: neeti
Thanks TOM

Rating: 5
Another issue..... March 15, 2002
Reviewer: Neeti
Hi Tom,

    Thanks for your quick response. Well the trigger works
fine as long as the user logs into sqlplus in the same schema where the trigger
is, but when a diffrent user who
has create any table prev. create table in this schema from his login then the
trigger does not get fired and I can not log those changes happening on the
schema......

eg.

    1) Trigger is on schema A.
    2) User logs into schema B with create any table prev.
    3) User run the statment from schema B

            create table a.t(t number);

            table created.

    4) No changes are logged through the trigger in schema A.

Please help...

Thanks
Neeti

Followup:
Yah -- I used an ON SCHEMA trigger.

Why in the world would you give your developers CREATE ANY TABLE in the first
place?  But anyway, what you are looking for then is an ON DATABASE trigger, one
that is for the database, not just the schema.

Rating: 4
UTL_FILE allows PLSQL to write to an OS file March 16, 2002
Reviewer: mohammad taha from Dubai,UAE
Mr.Tom,

Please give an example until reading that OS file again
to understand it clearly .

Followup:
this site is all about examples.  If you search for

UTL_FILE

you might just be surprised at how many you find

Rating: 4
Reader June 14, 2002
Reviewer: A reader
Tom,

Does this DDL_TRIGGER has any potential to interact with
other before insert trigger on another table in the schema.
In the log table for DDL_TRIGGER, I added DATE field also

Recently I created a DDL_TRIGGER and the log was written to
my user schema. On the application schema there is a
before insert trigger on a table.

When I tried to disable the application trigger in the
application schema got errors
ORA-00604 error occurred at recursive SQL level 1
ORA-01401 inserted value too large for that column
ORA-06512

Once I dropped the DDL_TRIGGER , the errors went away

Thanks

Followup:
read
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3109217869403
You have a bug in your DDL_TRIGGER -- your "log" table has a column that is
insufficient in width to hold the name of the table you are altering.

Rating: 4
June 14, 2002
Reviewer: A reader
I am hesitant to ask, this must be pretty basic......

Could you please explain what are these columns??

"select ora_sysevent,ora_dict_obj_owner,
           ora_dict_obj_name,ora_dict_obj_type.."

I dont see any ora_ columns in v$session. It returned an invalid column error on
8.1.5 but did not return any errors on 8.1.7.3. But the desc v$session does not
show these columns on both versions. Nor does the ref manual for 8.1.7

Could you, Tom,  kindly explain more about this...(link to doc would be enough).
thanks.

Followup:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76939/adg14evt.htm#1000872
they are event attribute functions -- many added in 816 and more in 817.

      Rating: 5
Thanks Very
      Much  June 14, 2002
Reviewer:  A reader

      Rating: 3
A
      Reader  June 14, 2002
Reviewer:
      A reader
Thanx Tom.

      Rating: 5
errors occur
      during the execution of DDL  September 04, 2002

Reviewer:  an  from DE. usa
Hi Tom:

the DDL tigger works fine until the database
is rebooted. Once the database is rebooted, errors occur during the execution
of DDL, such as the following:
The following error has occurred:
ORA-00604: error occurred at recursive SQL level 1
ORA-06550: line 123, column 22:
PLS-00103: Encountered the symbol "end-of-file" when expecting one of the
following:
. ( * @ % & - + ; / at for mod rem <an exponent (**)> and or
group having intersect minus order start union where connect
||

When recompile the trigger, it then works fine.
The database is currently rebooted nightly for backups.
now we have to disable the trigger, but we need the trigger stay alive, would
you tell me why the errors occur?
Thank you very much.
an

          Followup:

Well, first of all -- let me get on my podium and shout really loud:

Don't shutdown for backups.  Never.  It is wasteful (i hesitate to say "stupid"
cause peoples feelings get hurt).

Never never never shutdown unattended!!! Never (it is probably a major cause of
pagers going off)

Dismount podium....

Now, obviously we have what appears to be a bug of some sort here -- not knowing
versions, code, etc etc etc -- I can make these comments:

o contact support, open a tar

or

o shoot yourself for having more then 40/50 lines of code in a trigger.  I have
a rule that says the entire procedure must fit on a screen (you can run at
whatever resolution you like).  If the logic is so complex as to require lots of
page up/down to read -- you need to modularize the code.   At at least 123 lines
of code -- I think you should take the body of the trigger -- put it in a
package and modularize it.  Your trigger will become a single line of code and
perhaps whatever was causing the issue will disappear.

You should still do "contact support, open a tar" cause it does appear to be a
bug but without an example -- I cannot tell.

      Rating: 1
"Schema"
      Triggers Are A Lie ...  March 13, 2003
Reviewer:
      Barry Johnson  from Washington DC
The "Another Issue ..." followup on this item is at best disingenuous. The fact
is that Oracle's labelling of this feature as a "Schema Trigger" is a *gross*
distortion ... *or* Oracle's needs to rewrite its definition of "schema", which
would put it at odds with the standard industry definition.

If I log on as "Barry" and ALTER Scott.Emp (via an ALTER ANY TABLE privilege), a
DDL trigger tagged to Barry.SCHEMA will fire, but a trigger tied to Scott.SCHEMA
will NOT fire.  Since Oracle define "schema" as the thing that owns an object
(which would be "Scott", NOT "Barry" in this case), how can this feature
possibly be honestly described as a "Schema Trigger"??

As for why you would want to do this "ANY" privilege thing ... I guess it is
fundamentally because of deficiencies in the standard SQL security model, with
Oracle's extensions not doing enough to help.  And I plan to talk about it at
the IOUG Live! 2003 Conference in Orlando on May 1, and I've spelled it out in
the supporting paper which I'd be happy to share beforehand.

In the meantime, the workaround to get a properly implemented Schema trigger is
to create a data base trigger that fires when a DDL occurs, and includes a

  WHEN( Ora_Dict_Obj_Owner = "<schema of interest>" )

condition as part of its specification, tho' the privilege threshold to be able
to create a data base trigger is a bit higher than the other trigger types.

          Followup:

whatever....

ok, here is the simplified definition:

the schema trigger fires in accordance to the schema you be logged in as.

I guess that is how it gets to be defined as a schema trigger, it is assocated
with the schema you are logged into.

      Rating: 1
Schema is
      Associated With an Object ...  March 18, 2003

Reviewer:  Barry Johnson  from Washington DC

... and to say it is simply Session_User is to trivialise the issue.
"Application Developers Guide - Fundamentals" defines "schema" as "a security
domain that can contain data base objects".

If I log on as "Barry" and do an "ALTER Scott.Emp ...", there is *no* reason for
a TRIGGER predicated on "BEFORE DDL ON Barry.SCHEMA" to fire (although this is
what currently happens).  There is *every* reason for a TRIGGER predicated on
"BEFORE DDL ON Scott.SCHEMA" to fire because it is the "Scott" schema that owns
the object being changed.  There is *no* DDL action being performed "*ON*
Barry.SCHEMA".

Let me put it another way: I want to protect "Scott" schema objects against DDL
changes.  Why would it occur to me - especially given the syntax of "BEFORE DDL
ON <schema>.SCHEMA" - that I need to create a separate "schema" trigger against
*each and every username* in the data base and, within each trigger, check to
see if Ora_Dict_Obj_Owner = "Scott" to decide whether I need to do anything to
protect "Scott".  Since, in general, I can't know who is going to have what
privileges when that may empower them to affect "Scott" so I have to be
comprehensive.

(The workaround is actually to create a trigger predicated on "BEFORE DDL ON
DATABASE' and add a "WHEN( Ora_Dict_Obj_Owner = 'Scott' )" but, alas, that
requires access to an elevated data base privilege.)

No, Oracle need to change the syntax to "BEFORE DDL *BY* <UserName>.*USER*" to
make it clear that this is the way it works.  No wonder people are confused in
the meantime.

Or Oracle could fix it to use Ora_Dict_Obj_Owner as the triggering schema
criterium and turn this trigger type into something that would be wonderfully
useful.  As it is, I've yet to find a redeeming value for it.

Sincerely ... Barry Johnson

March 14, 2003 Mark -- Thanks for the question regarding "Truly
      complex pivot", version 8.1.7

      You Asked
I'm trying to do a complicated 2-way pivot in SQL, and can't work
out where to start. Assume a table as follows

Event   Group   Month
-----   -----   ----
X       A       Jan
Y       A       Jan
Z       B       Jan
A       C       Jan
XX      A       Feb
YY      A       Feb
ZZ      B       Feb
AA      C       Feb
XXX     A       Mar
YYY     A       Mar
ZZZ     B       Mar
AAA     C       Mar

Note that each event/group combo is unique, the list of groups is
reasonably fixed, but there can be many months.  each group can have 1 or more
events, varied by month. Basically this is a schedule application -  the groups
represent internal development groups, the
events are milestones within the month.

I need something like

        Jan     Feb     Mar
---------------------------
A       X       XX      XXX
A       Y       YY      YYY
B       Z       ZZ      ZZZ
C       A       AA      AAA

The only way I can see to do this is with some rather ugly UNION
statements. Any Ideas?

      and we said...
Of course, you must first query out the:

ops$tkyte@ORA920> select min(month), max(month),
months_between(max(month),min(month))+1 num_months
  2    from t;

MIN(MONTH)  MAX(MONTH)  NUM_MONTHS
----------- ----------- ----------
01-jan-2002 01-mar-2002          3

And we'll be building this query dynamically (have to -- you said the number of
months varies greatly -- SQL wants to know the number of columns hard and fast -
the number of columns in this set of data is 4 -- groupname and 3 months of
data...)

Then given this data (added some spurious rows abc, xxx just to make sure there
were uneven groups in there)

ops$tkyte@ORA920> select groupnm, month, event
  2    from t
  3   order by groupnm, month, event;

G MONTH       EVE
- ----------- ---
A 01-jan-2002 X
A 01-jan-2002 Y
A 01-jan-2002 abc
A 01-feb-2002 XX
A 01-feb-2002 YY
A 01-mar-2002 XXX
A 01-mar-2002 YYY
B 01-jan-2002 Z
B 01-feb-2002 ZZ
B 01-mar-2002 ZZZ
B 01-mar-2002 xxx
C 01-jan-2002 A
C 01-feb-2002 AA
C 01-mar-2002 AAA

14 rows selected.

We can do the following (I'll build the answer iteratively -- the answer is at
the end...)

We'll first start by assigning a row_number() to each event in a group by month:

ops$tkyte@ORA920> select event, month, groupnm,
  2         row_number() over ( partition by month, groupnm
  3                             order by event ) rn
  4     from t
  5  /

EVE MONTH       G         RN
--- ----------- - ----------
X   01-jan-2002 A          1
Y   01-jan-2002 A          2
abc 01-jan-2002 A          3
Z   01-jan-2002 B          1
A   01-jan-2002 C          1
XX  01-feb-2002 A          1
YY  01-feb-2002 A          2
ZZ  01-feb-2002 B          1
AA  01-feb-2002 C          1
XXX 01-mar-2002 A          1
YYY 01-mar-2002 A          2
ZZZ 01-mar-2002 B          1
xxx 01-mar-2002 B          2
AAA 01-mar-2002 C          1

14 rows selected.

Then, well begin the PIVOT process using decode:

ops$tkyte@ORA920> select groupnm,
  2         decode( month, '01-jan-2002', rn, null ) rn1,
  3         decode( month, '01-jan-2002', event, null ) ev1,
  4         decode( month, '01-feb-2002', rn, null ) rn2,
  5         decode( month, '01-feb-2002', event, null ) ev2,
  6         decode( month, '01-mar-2002', rn, null ) rn3,
  7         decode( month, '01-mar-2002', event, null ) ev3
  8    from ( select event, month, groupnm,
  9                  row_number() over ( partition by month, groupnm
 10                                      order by event ) rn
 11             from t
 12         ) t
 13  /

G        RN1 EV1        RN2 EV2        RN3 EV3
- ---------- --- ---------- --- ---------- ---
A          1 X
A          2 Y
A          3 abc
B          1 Z
C          1 A
A                         1 XX
A                         2 YY
B                         1 ZZ
C                         1 AA
A                                        1 XXX
A                                        2 YYY
B                                        1 ZZZ
B                                        2 xxx
C                                        1 AAA

14 rows selected.

Now, we need to squash out those NULL entries, slide things up.  We'll use a 9i
new function coalese in this example, but we could use

  nvl( rn1, nv(rn2, rn3) )

instead in 8i. Coalesce returns the first NON-NULL entry from a list of values.
We'll group by GROUPNM and the coalesce of all of those rn's:

ops$tkyte@ORA920> select groupnm,
  2         coalesce( rn1, rn2, rn3 ) rn,
  3         max(ev1) "Jan", max(ev2) "Feb", max(ev3) "Mar"
  4    from ( select groupnm,
  5                  decode( month, '01-jan-2002', rn, null ) rn1,
  6                  decode( month, '01-jan-2002', event, null ) ev1,
  7                  decode( month, '01-feb-2002', rn, null ) rn2,
  8                  decode( month, '01-feb-2002', event, null ) ev2,
  9                  decode( month, '01-mar-2002', rn, null ) rn3,
 10                  decode( month, '01-mar-2002', event, null ) ev3
 11             from ( select event, month, groupnm,
 12                           row_number() over ( partition by month, groupnm
 13                                               order by event ) rn
 14                      from t
 15                  ) t
 16         ) t
 17   group by groupnm, coalesce( rn1, rn2, rn3 )
 18  /

G         RN Jan Feb Mar
- ---------- --- --- ---
A          1 X   XX  XXX
A          2 Y   YY  YYY
A          3 abc
B          1 Z   ZZ  ZZZ
B          2         xxx
C          1 A   AA  AAA

6 rows selected.

ops$tkyte@ORA920>

throw in a little SQLPlus formatting and there you go:

ops$tkyte@ORA920> break on groupnm skip 1
ops$tkyte@ORA920> /

G         RN Jan Feb Mar
- ---------- --- --- ---
A          1 X   XX  XXX
           2 Y   YY  YYY
           3 abc

B          1 Z   ZZ  ZZZ
           2         xxx

C          1 A   AA  AAA

6 rows selected.

A nice matrix that shows by GROUP what events are taking place in each month...

QED -- analytics rock
Review & Followup

      Rating: 5
Brilliant
        December 16, 2002
Reviewer:  Andrew
      Gilfrin  from Dover, Kent, UK
Had a requirement here to produce a pivot table effect,
had everybody stumped but using this post and Toms's book managed to meet the
requirement.

As usual Tom rocks big time.

      Rating: 4
A Date Column
      to pivit into Week1, Week2.....  December 16, 2002

Reviewer:  Robert  from PA
I think I can definitely achieve what I need based on your
solutuin here.

But I have a date col I need to pivot into something like:
Count, Week1, Week2....

Is there a way to return 1/4/02 as 1 (week 1), 1/16/02 as 2...etc

Thanks

          Followup:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select dt, trunc(dt,'iw')
  2    from ( select to_date('01/01/2002','dd/mm/yyyy')+rownum-1 dt from
all_objects where rownum < 30 )
  3  /

DT        TRUNC(DT,
--------- ---------
01-JAN-02 31-DEC-01
02-JAN-02 31-DEC-01
03-JAN-02 31-DEC-01
04-JAN-02 31-DEC-01
05-JAN-02 31-DEC-01
06-JAN-02 31-DEC-01
07-JAN-02 07-JAN-02
08-JAN-02 07-JAN-02
09-JAN-02 07-JAN-02
10-JAN-02 07-JAN-02
11-JAN-02 07-JAN-02
12-JAN-02 07-JAN-02
13-JAN-02 07-JAN-02
14-JAN-02 14-JAN-02
15-JAN-02 14-JAN-02
16-JAN-02 14-JAN-02
17-JAN-02 14-JAN-02
18-JAN-02 14-JAN-02
19-JAN-02 14-JAN-02
20-JAN-02 14-JAN-02
21-JAN-02 21-JAN-02
22-JAN-02 21-JAN-02
23-JAN-02 21-JAN-02
24-JAN-02 21-JAN-02
25-JAN-02 21-JAN-02
26-JAN-02 21-JAN-02
27-JAN-02 21-JAN-02
28-JAN-02 28-JAN-02
29-JAN-02 28-JAN-02

29 rows selected.

'iw' might do it for you...

      Rating:
      4
  December 16, 2002
Reviewer:  rchin
       from PA
My requirement is that Friday marks end of a week (so 1/4/02 should be end of
week 1) so
I hacked around and comeup with something like below...

Do you think this is gonna hold up or is there some session
variable I can so I can use 'IW' or 'WW' ?

FUNCTION weeknumber(p_Date IN VARCHAR2) RETURN INTEGER
IS
i INTEGER;
p_year VARCHAR2(4) := to_char(to_date(p_Date), 'yyyy') ;
BEGIN
  i := round((round(next_day(p_d1,'FRIDAY') - next_day(to_date('1-Jan-'||
p_year),'FRIDAY')) / 365) * 52) + 1;
  RETURN i;
END;

          Followup:

why not just

ops$tkyte@ORA817DEV.US.ORACLE.COM> select dt, to_char( dt+2, 'iw' )
  2    from ( select to_date('01/01/2002','dd/mm/yyyy')+rownum-1 dt
                from all_objects where rownum < 30 )
  3  /

DT        TO
--------- --
01-JAN-02 01
02-JAN-02 01
03-JAN-02 01
04-JAN-02 01
05-JAN-02 02
06-JAN-02 02
07-JAN-02 02
08-JAN-02 02
09-JAN-02 02
10-JAN-02 02
11-JAN-02 02
12-JAN-02 03
13-JAN-02 03
14-JAN-02 03
15-JAN-02 03
16-JAN-02 03
17-JAN-02 03
18-JAN-02 03
19-JAN-02 04
20-JAN-02 04
21-JAN-02 04
22-JAN-02 04
23-JAN-02 04
24-JAN-02 04
25-JAN-02 04
26-JAN-02 05
27-JAN-02 05
28-JAN-02 05
29-JAN-02 05

29 rows selected.

instead -- don't go the plsql route unless you have to and I don't think you do.

      Rating: 4
Pls Repeat
      it....  December 16, 2002
Reviewer:  robert
       from PA
Please do us all a favor and repeat your famous mantra
in BIG CAPITAL fonts in your new book...
Warning ! suggestion coming up:
(or perhaps even put it on disk in an audio file in a soothing hypnotic voice so
we can play it over and over....)

      Rating: 4
using Oracle
      8i  December 18, 2002
Reviewer:  Sivababu
       from INDIA
Hi TOM,
   It is great. But how can i use with oracle 8.1.7?.

Waiting for your reply.

regards,
Siva

          Followup:

as i said -- use nvl instead of coalesce

ops$tkyte@ORA817DEV.US.ORACLE.COM> select groupnm,
  2         nvl(rn1,nvl(rn2,rn3)) rn,
  3         max(ev1) "Jan", max(ev2) "Feb", max(ev3) "Mar"
  4    from ( select groupnm,
  5                  decode( month, '01-jan-2002', rn, null ) rn1,
  6                  decode( month, '01-jan-2002', event, null ) ev1,
  7                  decode( month, '01-feb-2002', rn, null ) rn2,
  8                  decode( month, '01-feb-2002', event, null ) ev2,
  9                  decode( month, '01-mar-2002', rn, null ) rn3,
 10                  decode( month, '01-mar-2002', event, null ) ev3
 11             from ( select event, month, groupnm,
 12                           row_number() over ( partition by month, groupnm
 13                                               order by event ) rn
 14                      from t
 15                  ) t
 16         ) t
 17   group by groupnm, nvl(rn1,nvl(rn2,rn3))
 18  /

G         RN Jan Feb Mar
- ---------- --- --- ---
A          1 X   XX  XXX
A          2 Y   YY  YYY
A          3 abc
B          1 Z   ZZ  ZZZ
B          2         xxx
C          1 A   AA  AAA

6 rows selected.

      Rating: 5
pivot
      question  March 13, 2003
Reviewer:  vf

Just simplifying my problem with a sample table:
I have a table1 with col1 and col2.
col1        col2
------      ------
1            a
1            b
2            e
2            f
2            g
3            x
3            y

I need the out put as :
col1    col
----    ----
1       a,b
2       e,f,g
3       x,y

For using a pivot query and concatenate the pivoted columns i need to know
beforehand the max number of unique col1 values(meaning max 3 rows for value
'2'). Then i can use:

select col1,
max(decode(r,1,col2))||','||max(decode(r,2,col2))||','||max(decode(r,3,col2))
col from
(select col1,col2, row_number() over (partition by col1 order by col1) as r from
table1)

But the problem is that i do not know the max no of col1 values.(I am getting
this result 'select col1,col2 from table1' at runtime and the  result can vary
as per the where clause).
Is ther any way to acheive this thru SQL only?

I can do this with a procedure which make a dynamic query (or thru someother
plsql means) but is ther a way to do this with SQL only?

          Followup:

in 8i - no
in 9i - yes, search for stragg on this site.

      Rating:
      5
  March 13, 2003
Reviewer:  vf

oracle 8i  is my environment for teh above question

March 14, 2003 -- Thanks for the question regarding "Sql query",
      version 7.3.3.4.0

      You Asked
Hi Tom,

Trying to mail u since 2 weeks.

I will appreciate ur help in solving my query.

I have 1 table which have a date field
and stores data in the format-->dd-mon-yy hh24:mi:ss.
the time interval(15 mins)is stored in a parameter table which will vary from
5,12,15,30,45,60,90,secs(any value).
Depending on the time interval ,
i should get how many records are there on
particular date.

Dates  Time(15 mins) 9:30-9:45 9:45-10 10-10:15 10:15-10:30
-----
1/jan                  4         3        3         10
2/jan                  2         5        3         12
3/jan
4/jan                  2         3        1          1

4 are the no of recs present on 1/jan for 9:30-9:45

Thanx,

      and we said...
You'll use something like this (set interval to the number of minutes you want
each interval to be -- i used 15)

ops$tkyte@ORA817DEV.US.ORACLE.COM> select to_char(dt,'dd/mon'),
  2             sum(decode(to_char(dt,'hh24mi'), '0000', 1, 0 )) "00:00-15",
  3             sum(decode(to_char(dt,'hh24mi'), '0015', 1, 0 )) "00:15-30",
  4             sum(decode(to_char(dt,'hh24mi'), '0030', 1, 0 )) "00:30-45",
  5             sum(decode(to_char(dt,'hh24mi'), '0045', 1, 0 )) "00:45-00",
  6             sum(decode(to_char(dt,'hh24mi'), '0100', 1, 0 )) "01:00-15",
  7             sum(decode(to_char(dt,'hh24mi'), '0115', 1, 0 )) "01:15-30",
  8             sum(decode(to_char(dt,'hh24mi'), '0130', 1, 0 )) "01:30-45",
  9             sum(decode(to_char(dt,'hh24mi'), '0145', 1, 0 )) "01:45-00"
 10   from (select
trunc(dt,'hh24')+floor(to_char(dt,'mi')/:interval)*:interval/24/60 DT from t)
 11   group by to_char(dt,'dd/mon')
 12  /

TO_CHA   00:00-15   00:15-30   00:30-45   00:45-00   01:00-15   01:15-30
01:30-45   01:45-00
------ ---------- ---------- ---------- ---------- ---------- ----------
---------- ----------
01/jan          1          1          0          1          0          0
 0          1
02/jan          0          0          1          0          1          0
 1          0
03/jan          0          0          0          0          0          1
 1          1
04/jan          0          0          0          0          0          0
 2          0
05/jan          0          0          0          0          0          1
 2          0
06/jan          0          0          0          1          1          1
 1          0
07/jan          0          0          0          1          0          0
 1          3
08/jan          1          0          0          0          0          1
 2          1
10/jan          0          0          0          0          1          1
 0          0
11/jan          1          0          0          1          0          0
 1          0
12/jan          0          0          0          0          0          1
 0          0
13/jan          2          0          0          0          0          0
 3          1
14/jan          1          0          2          0          0          2
 0          1
15/jan          1          1          2          0          1          0
 0          1
16/jan          1          1          0          0          0          2
 0          1
18/jan          1          0          0          1          1          0
 0          0
19/jan          0          0          2          0          1          1
 1          1
21/jan          0          0          2          0          2          0
 0          1
22/jan          2          0          0          0          0          1
 0          2
23/jan          0          0          0          0          0          2
 1          1
24/jan          1          0          1          0          0          1
 0          0
25/jan          2          0          0          0          0          1
 0          0
26/jan          0          0          0          0          0          0
 0          1
27/jan          0          1          0          1          1          0
 1          0
28/jan          0          1          0          1          0          0
 0          0
29/jan          1          0          0          0          0          0
 0          1
30/jan          0          0          0          1          0          0
 0          0
31/jan          0          0          0          1          0          0
 1          1

28 rows selected.
Review & Followup

      Rating: 5
set time
      interval  March 12, 2003
Reviewer:  Tommy
       from VA USA
Tom is great.
I have the same problem but in reverse.  I have a table that store the time and
date in 2 columns, and it store each record detail down to the second.  I try to
count the records for each 30min. interval.

Please show the way with example

          Followup:

read the answser again.  it is parameterized there -- :interval.  set it  to 30

      Rating:
      4
  March 13, 2003
Reviewer:
      A reader
Tom,

I have similar situation, i have table orders which has columns
ordno,orddate,itemno,qty,rate.
I want to find count of orders for each month and max orderno for each month.

Thanks.

          Followup:

uh huh.  and you don't know how to do a fairly simple:

select trunc(orddate,'mm'), count(*), max(ordno) from t group by
trunc(orddate,'mm')

??

      Rating: 5
group data in
      time interval  March 13, 2003
Reviewer:  Dan
       from NY
I run oracle 8.1.7 on win2k OS
I have a call_detail table

track_num number(10)
t_date date,
t_time number(6)
....

right now the data is group by the date. Like this

T_DATE     TRACK_NUM  TERM_TIME
--------- ---------- ----------
12-MAR-03    1521788     201531
12-MAR-03    1521794     201542
12-MAR-03    1521790     201557
12-MAR-03    1521795     201609
12-MAR-03    1521800     201734
12-MAR-03    1521802     201741
12-MAR-03    1521801     201804
12-MAR-03    1511342      91411
12-MAR-03    1511335      91411
12-MAR-03    1511332      91419
12-MAR-03    1511349      91426

11 rows selected.

I need to generate a report that break this same data down to 30min. interval

It should look like this

T_DATE     TRACK_NUM  TERM_TIME
--------- ---------- ----------
12-MAR-03    1521788     201531
12-MAR-03    1521794     201542
12-MAR-03    1521790     201557
12-MAR-03    1521795     201609
12-MAR-03    1521800     201734
12-MAR-03    1521802     201741
12-MAR-03    1521801     201804
                   7
12-MAR-03    1511342      91411
12-MAR-03    1511335      91411
12-MAR-03    1511332      91419
12-MAR-03    1511349      91426
                   4

This is best

TERM_TIME       TRACK_NUM  xxxx
---------       ---------- ----
90000 to 93000       4          XX
200000 TO 213000     7       XX

I read your book on the analytics function but could not figure out how to write
a query to do this. Please help

          Followup:

you might be able to play with group by rollup or cube but basically -- your
result isn't a "square".  analytics -- i don't see how they would apply here.

March 14, 2003 Billy -- Thanks for the question regarding
      "questions on LONGS", version

      You Asked
Can you perform string functions on the datatype LONG?

      and we said...
In PLSQL yes -- in SQL no.

If the long is 32k or less, you can do some interesting things
in PLSQL.  Lets say you have a table T with a long column like
this:

ops$tkyte@8i> create table t ( x int, y long );
Table created.

ops$tkyte@8i> insert into t values ( 1, 'how now brown cow' );
1 row created.

ops$tkyte@8i> select substr( y, 1, 5 ) from t;
select substr( y, 1, 5 ) from t
               *
ERROR at line 1:
ORA-00932: inconsistent datatypes

ops$tkyte@8i> create or replace function t_substr
                  ( p_rowid in rowid,
                    p_from in number,
                    p_for in number ) return varchar2
  2  as
  3          l_str   long;
  4  begin
  5          select y into l_str from t where rowid = p_rowid;
  6
  7          return substr( l_str, p_from, p_for );
  8  end;
  9  /

Function created.

ops$tkyte@8i> select t_substr( rowid, 1, 5 ) from t;

T_SUBSTR(ROWID,1,5)
------------------------------
how n

So, for longs that are 32k or less, we can write functions that
are callable in SQL to allow us to do some operations on these
long columns.

Additionally -- we can use dbms_sql to READ longs (but not
write) them that are of any size.  We would piecewise read them.
 You cannot call a function that uses dbms_sql from SQL as  did
above but you can use it in any other procedure/function you
write.  Here is an example of a piecewise read of a long in
plsql:

create or replace procedure showlong( p_query in varchar2 )
as
    l_cursor    integer default dbms_sql.open_cursor;
    l_n         number;
    l_long_val  varchar2(250);
    l_long_len  number;
    l_buflen    number := 250;
    l_curpos    number := 0;
begin
    dbms_sql.parse( l_cursor, p_query, dbms_sql.native );

    dbms_sql.define_column_long(l_cursor, 1);
    l_n := dbms_sql.execute(l_cursor);

    if (dbms_sql.fetch_rows(l_cursor)>0)
    then
        loop
            dbms_sql.column_value_long(l_cursor, 1, l_buflen,
l_curpos ,
                                        l_long_val, l_long_len
);
            l_curpos := l_curpos + l_long_len;
            dbms_output.put_line( l_long_val );
            exit when l_long_len = 0;
      end loop;
   end if;
   dbms_output.put_line( '====================' );
   dbms_output.put_line( 'Long was ' || l_curpos || ' bytes in
length' );
   dbms_sql.close_cursor(l_cursor);
exception
   when others then
      if dbms_sql.is_open(l_cursor) then
         dbms_sql.close_cursor(l_cursor);
      end if;
      htp.preClose;
      raise;
end showlong;
/

set echo on
begin
    showlong( 'select text from all_views where rownum = 1' );
end;
/

Review &
      Followup

      Rating: 4
tried to
      implement instr but not successful  March 20, 2002

Reviewer:  Ravi Kumar  from Chicago, IL
Hi Tom,

I tried to implement INSTR on long column but not successful. What I am trying
to get is, all the trigger_names from user_triggers where trigger_body has some
string.

something like this :

select trigger_name
from user_triggers
where long_instr(params_list, 'EMP') = 0;

should give all the trigger with EMP in their body.

But not sure what parameters to send. Expecting a general answer that can be
used on any view.

Thanks,
Ravi.

          Followup:

Expecting alot aren't you....

You could use dynamic sql (in 8i and up) to make a "generic solution" but I'll
give one that works for all releases:

create or replace function t_instr
         ( p_trigname in varchar2,
           p_lookfor in varchar2 ) return number
as
   l_str long;
begin
   select trigger_body into l_str
     from user_triggers
    where trigger_name = p_trigname;
   return instr( l_str, p_lookfor );
end;
/

Now, it looks like you are looking for triggers that are NOT dependent on EMP.
A better approach would be to use USER_DEPENDENCIES to find triggers that are
not dependent on EMP, that way - triggers that refer to EMP_TABLE, EMP_VIEW,
EMPLOYEES, EMP_TAB, etc -- wouldn't return from your query.

      Rating: 4
Problem with
      hard coded values not the dependencies  March 21, 2002

Reviewer:  Ravi Kumar  from Chicago, IL
Hi Tom,

Thanks for your answer.

But, 'EMP' in that query, I just gave as an example. My actual scenario is, we
have lot of triggers and views with some hardcoded values. I would like to get
all the trigger_names and views with those hard coded values in their body. From
the idea you gave and searching your site, I created this function :

CREATE OR REPLACE function linstr ( p_object    in varchar2,
                                    p_lookin    in varchar2,
                                    p_column    in varchar2,
                                    p_colval    in varchar2,
                                    p_string    in varchar2)
return number
as
    l_long_val  varchar2(32760);
begin
   execute immediate
           'select ' || p_lookin ||
           ' from '  || p_object ||
           ' where ' || p_column || ' = :val'
           into  l_long_val
           using p_colval;
   return instr(l_long_val, p_string);
end linstr;
/

and I am able to issue queries like

select trigger_name
from user_triggers
where linstr('user_triggers', 'trigger_body', 'trigger_name', trigger_name,
'01011900') <> 0;

and I am getting all the triggers. It is working fine with user_views and also
regular tables.

Please let me know if there is any better way to check for the hardcoded values.

Thank You again.

Ravi.

      Rating:
      5
Code  March 12, 2003
Reviewer:
      Marcio  from Brazil
As you coded --

<quote>
You could use dynamic sql (in 8i and up) to make a "generic solution" but I'll
give one that works for all releases:

create or replace function t_instr
         ( p_trigname in varchar2,
           p_lookfor in varchar2 ) return number
as
   l_str long;
begin
   select trigger_body into l_str
     from user_triggers
    where trigger_name = p_trigname;
   return instr( l_str, p_lookfor );
end;
/
</quote>

Could be missing AUTHID CURRENT_USER in your code?

Thanks,
Marcio

          Followup:

no, especially as I said "in all versions"

could you add it?  sure in 8i and up.
is it necessary?  maybe, maybe not, depends on HOW you use it or want to use it.

      Rating: 4
I agree to you
      but...  March 13, 2003
Reviewer:  Marcio
       from Brazil
Yes, but look that. "As you coded" -- You coded a FUNCTION, What am I doing
wrong?

ops$mportes@MRP816> create or replace function f_instr ( p_trigger in varchar2,
p_long in varchar2 )
  2     return number
  3   as
  4      l_str long;
  5   begin
  6      select trigger_body into l_str
  7        from all_triggers
  8       where trigger_name = p_trigger;
  9      return instr( l_str, p_long );
 10   exception
 11      when no_data_found then
 12        return 999999;
 13      when others then
 14        return 888888;
 15  end;
ops$mportes@MRP816> /

Function created.

ops$mportes@MRP816> select f_instr('RELEASE_AU', 'exception') from dual;

F_INSTR('RELEASE_AU','EXCEPTION')
---------------------------------
                           999999

-- But the trigger exists and there is "exception" word there.

ops$mportes@MRP816> select trigger_body
  2     from all_triggers
  3   where trigger_name = 'RELEASE_AU'
ops$mportes@MRP816> /

TRIGGER_BODY
--------------------------------------------------------------------------------
begin
     if ( not state_pkg.avoid_recursion )
     then
        begin
           state_pkg.avoid_recursion := TRUE;
           for i in 1 .. state_pkg.newRows.count loop
              update release
                 set (usuario, terminal, last_update) = ( select osuser, termina
l, sysdate
                                                            from minha_sessao )
               where rowid = state_pkg.newRows(i);
           end loop;
           state_pkg.avoid_recursion := FALSE;
        exception
           when others then
           state_pkg.avoid_recursion := FALSE;
           RAISE;
        end;
      end if;
end;

-- So, when I create with AUTHID CURRENT_USER works.

ops$mportes@MRP816> create or replace function f_instr ( p_trigger in varchar2,
p_long in varchar2 )
  2    return number
  3  AUTHID CURRENT_USER
  4  as
  5     l_str long;
  6  begin
  7     select trigger_body into l_str
  8       from all_triggers
  9      where trigger_name = p_trigger;
 10     return instr( l_str, p_long );
 11  end;
 12  /

Function created.

ops$mportes@MRP816> select f_instr('RELEASE_AU', 'exception') from dual;

F_INSTR('RELEASE_AU','EXCEPTION')
---------------------------------
                              500

ops$mportes@MRP816>

??

Thanks,
Marcio

          Followup:

use user_triggers.

You have done what I call "moving the goalposts".  Yes, if you want to use it
for what you are doing -- against ALL_ tables, you would probably want authid
current_user since

o a procedure runs without roles
o all_* views look at the roles enabled to see what you can see
o all_* views in DR (definer rights) procedures have different rows visible then
when not in DR procedures

March 14, 2003 Rajashekar -- Thanks for the question regarding
      "IMPORT -- how to load delimited text data.", version 8.0.4

      You Asked
Hi tom

I have a data in the text file delimited by :

I want to import the same into a table can i do that by writing
a stored proc. if so how do i do that.

Is there any way so that i can use and import the same into a
table .

Awaiting for your reply
Regards
Raj

      and we said...
SQLLDR is the most probable tool for doing this.  If you look in
$ORACLE_HOME/rbms/demo, you'll find a couple of examples.  One
of them might look like:

LOAD DATA
INFILE *
INTO TABLE DEPT
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
(DEPTNO, DNAME, LOC)
BEGINDATA
12,RESEARCH,"SARATOGA"
10,"ACCOUNTING",CLEVELAND
11,"ART",SALEM
13,FINANCE,"BOSTON"
21,"SALES",PHILA.
22,"SALES",ROCHESTER
42,"INT'L","SAN FRAN"

If you alter the terminated by ',' into terminated by ':', it'll
load a : delimited file.  Please see the server utilities
manual:

http://technet.oracle.com/doc/server.804/a58244/toc.htm
for complete details on sqlldr.

If you wish to do this in PLSQL, it is easy enough.  Here is a
sample routine that will load a delimited text file into a
table.  You must configure UTL_FILE for this to work correctly
(see
http://technet.oracle.com/doc/server.804/a58241/ch8.htm#1939
for information on setting that up).

drop table badlog;
create table badlog( errm varchar2(4000), data varchar2(4000) );

create or replace
function  load_data( p_table     in varchar2,
                     p_cnames    in varchar2,
                     p_dir       in varchar2,
                     p_filename  in varchar2,
                     p_delimiter in varchar2 default '|' )
return number
is
    l_input         utl_file.file_type;
    l_theCursor     integer default dbms_sql.open_cursor;
    l_buffer        varchar2(4000);
    l_lastLine      varchar2(4000);
    l_status        integer;
    l_colCnt        number default 0;
    l_cnt           number default 0;
    l_sep           char(1) default NULL;
    l_errmsg        varchar2(4000);
begin
    l_input := utl_file.fopen( p_dir, p_filename, 'r' );

    l_buffer := 'insert into ' || p_table || ' values ( ';
    l_colCnt := length(p_cnames)-
                  length(replace(p_cnames,',',''))+1;

    for i in 1 .. l_colCnt
    loop
        l_buffer := l_buffer || l_sep || ':b'||i;
        l_sep    := ',';
    end loop;
    l_buffer := l_buffer || ')';

    dbms_sql.parse(  l_theCursor, l_buffer, dbms_sql.native );

    loop
        begin
            utl_file.get_line( l_input, l_lastLine );
        exception
            when NO_DATA_FOUND then
                exit;
        end;
        l_buffer := l_lastLine || p_delimiter;

        for i in 1 .. l_colCnt
        loop
            dbms_sql.bind_variable( l_theCursor, ':b'||i,
                            substr( l_buffer, 1,
                            instr(l_buffer,p_delimiter)-1 ) ) ;
            l_buffer := substr( l_buffer,
                          instr(l_buffer,p_delimiter)+1 );
        end loop;

        begin
            l_status := dbms_sql.execute(l_theCursor);
            l_cnt := l_cnt + 1;
        exception
            when others then
                l_errmsg := sqlerrm;
                insert into badlog ( errm, data )
                values ( l_errmsg, l_lastLine );
        end;
    end loop;

    dbms_sql.close_cursor(l_theCursor);
    utl_file.fclose( l_input );
    commit;

    return l_cnt;
end load_data;
/

And here is an example using the above:

ops$tkyte@8i> create table t1 ( x int, y int, z int );
Table created.

ops$tkyte@8i> host echo 1,2,3 > /tmp/t1.dat
ops$tkyte@8i> host echo 4,5,6 >> /tmp/t1.dat
ops$tkyte@8i> host echo 7,8,9 >> /tmp/t1.dat
ops$tkyte@8i> host echo 7,NotANumber,9 >> /tmp/t1.dat

ops$tkyte@8i> begin
  2     dbms_output.put_line(
  3         load_data( 'T1',
  4                    'x,y,z',
  5                    '/tmp',
  6                    't1.dat',
  7                    ',' ) || ' rows loaded' );
  8  end;
  9  /
3 rows loaded

PL/SQL procedure successfully completed.

ops$tkyte@8i> select * from badlog;

ERRM                         DATA
-------------------          -------------------
ORA-01722: invalid number    7,NotANumber,9

ops$tkyte@8i> select * from t1;

         X          Y          Z
---------- ---------- ----------
         1          2          3
         4          5          6
         7          8          9

Review &
      Followup

      Rating: 3
please help me
      with this related question   December 11, 2001

Reviewer:  Richard  from Ghana
I know it is against your principle to ask question through this channel but i
can't catch you at the specified time since am not connected to net all times.
Please this the question:
Hello Tom,
    My question is that I have 3 tables, 2 of which contain many records. I have
to write a code(plsql) that will select all matching records and insert them
into the 3rd table(C) .The real problem is that table C must contain records in
either Excel or Access  file format which is saved on a diskette. Thus apart
from selecting records from table B to be inserted into table C, I should also
find a way of reading or selecting matching records saved on the floppy
diskette.
Can this be achievable in oracle7.3.2? Can you give me a simple code to do that?
NB. Table A(studnumber,subject,level)
        Table B(studnumber,dept,year)
         Table C(studnumber,subject,level,dept,year).
Thanks a lot.

          Followup:

well, putting the data into table c is trivial:

insert into c
select a.*, b.dept, b.year
  from a, b
  where a.studnumber = b.studnumber;

Geting the data OUT in a format for excel or access is easy as well.  you can
use a delimited file for both (see:

http://osi.oracle.com/~tkyte/flat/index.html
or for excel you can use the sylk format to get better control over the look of
the data:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:728625409049
...

      Rating:
      5
Perfect  December 11, 2001
Reviewer:
      maxu
Tom, it's just owesome !!!

      Rating: 3
What about the
      dates?  March 13, 2003
Reviewer:  Eduardo
      Campos  from Monterrey, NL, Mexico
Tom your SP only work with int, but, how could it work  with dates?

thanks.

          Followup:

did you try it?

any date will be converted using the default nls_date_format from a string.  it
works.

March 14, 2003 Amin -- Thanks for the question regarding "Week
      end Count", version 7.3.4.5

      You Asked
Is there a standard function which can count number of Saturday's and Sunday's
in a given period.

For e.g

From 01-Jan-2001 to 28-Feb-2001
number of Sat and Sunday's
Ans would be 16

if not, can u advise a idea to count weekends.

      and we said...
There is no built-in however, you can easily do this like this:

create or replace function weekends( p_date1 in date, p_date2 in date )
return number
as
    l_date1 date default least(p_date1,p_date2);
    l_date2 date default greatest(p_date1,p_date2);
    l_days  number default trunc(l_date2-l_date1)+1;
    l_cnt   number;
begin
    select count(*) into l_cnt
      from (select rownum r
              from all_objects where rownum <= l_days)
     where to_char(l_date1+r-1,'dy') in ( 'sat','sun' );

    return l_cnt;
end;
/

I chose all_objects as the set to generate numbers from since all_objects
typically has thousands of rows for us -- any suitably large table will do.

Here is an update to this question with a faster way to do this (much faster in
fact)

Demidenko Sergey from Russia chimed in with an improved version that does
weekends:

CREATE OR REPLACE
FUNCTION weekends2( p_date1 IN DATE, p_date2 IN DATE )
RETURN NUMBER
AS
  l_monday varchar2(255)
           default to_char( to_date( '03052001', 'mmddyyyy' ), 'DAY' );
  l_date1 DATE DEFAULT LEAST(trunc(p_date1),trunc(p_date2));
  l_date2 DATE DEFAULT GREATEST(trunc(p_date1),trunc(p_date2));
  l_date2_monday DATE DEFAULT NEXT_DAY(l_date2,l_monday)-7;
  l_date1_monday DATE DEFAULT NEXT_DAY(l_date1,l_monday)-7;
  l_some_add NUMBER DEFAULT greatest((l_date2-l_date2_monday)-4,0);
  l_some_sub NUMBER DEFAULT greatest((l_date1-l_date1_monday)-5,0);
BEGIN
    RETURN
       2*TRUNC((l_date2_monday-l_date1_monday)/7,0)+l_some_add-l_some_sub;
END;
/

It is a couple of order magnitude faster then the original one above.  The
original one above was taken from another example where I had to support
arbitrary holidays as well - it had a table "holidays" and the query was:

select count(*) into l_cnt
      from (select rownum r
              from all_objects where rownum <= l_days)
     where to_char(l_date1+r-1,'dy') in ( 'sat','sun' )
       AND not exists ( select null
                          from holidays
                         where a_holiday = trunc(l_date1+r-1) );

it would skip saturday and sundays as well as any user defined holidays in the
period.

Thanks for the feedback on this one.

followup to the comment:

Though they both provide solution to the weekend problem, the first example
refers to a large table which may or may not exist for a user and the updated
version uses hard code for the date part. The effective solution should be
independent of both these factors. We should attempt for a stand-alone function.

Well, the first one refers to a large table that exists for everyone.
ALL_OBJECTS is a standard data dictionary that is granted SELECT on to PUBLIC.
If you do not have access to this view -- you have larger problems then getting
this to work!!  There will be thousands of other things that don't work in
addition to this little routine.  The first one is safe.

As for the second one using a "hard code" -- yes, it uses a hard coded date that
is MONDAY.  What we are doing there is getting the correct SPELLING for monday
in your language so that the rest of the code works.  It is indepedent of the
hard code -- it works anywhere.  Without the hard code it works only in english,
the hard code is just a way to get the spelling for monday.
Review &
      Followup

      Rating:
      5
  March 15, 2001
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating:
      4
Weekend  April 01, 2001
Reviewer:
      Rajeev Ranjan  from Mumbai, India
Yes, it's helpfull.

      Rating: 3
Computation of
      Weekend days.  April 01, 2001
Reviewer:
      Gulamali Zakir  from Chicago, USA.
Though they both provide solution to the weekend problem, the first example
refers to a large table which may or may not exist for a user and the updated
version uses hard code for the date part. The effective solution should be
independent of both these factors. We should attempt for a stand-alone function.

      Rating: 4
Weekend
        April 04, 2001
Reviewer:  Tiamiyu Salau
       from North Carolina
This also works

CREATE OR REPLACE FUNCTION wk_end(date1 IN DATE DEFAULT SYSDATE,date2 IN DATE
DEFAULT SYSDATE+7) RETURN number AS
max_date date := GREATEST(TRUNC(date1),TRUNC(date2));
min_date date := LEAST(TRUNC(date1),TRUNC(date2));
no_of_wkend number := 0;
count_date date := min_date;
BEGIN
  WHILE count_date <= max_date LOOP
    IF TO_CHAR(count_date,'DY') IN ('SAT','SUN') THEN
       no_of_ekend := no_of_wkend+1;
     END IF;
     count_date := count_date+1;
  END LOOP;
END;

      Rating:
      5
  April 08, 2001
Reviewer:  Gautam
       from BC, Canada

      Rating:
      4
Ultimate  April 19, 2001
Reviewer:
      Harish Babu.N  from Bangalore,India
As always Tom has been Ultimate in giving answers

      Rating: 3
Count No. of
      weekends  April 27, 2001
Reviewer:  Chetan
      Kashikar  from Auckland, New Zealand
I have a better solution for this.

Create a function as below

create or replace function
count_weekends(p_start_date date
              ,p_end_date   date
             ) RETURN NUMBER IS
       p_count number := 0;
       tdate date ;
       x varchar2(20);
    begin
      tdate := p_start_date;
      LOOP
        if tdate > p_end_date then
            exit;
        end if;
        x := to_char(tdate,'DAY');
         if rtrim(x) in ('SATURDAY','SUNDAY') then
            p_count := p_count +1;
         end if;
         tdate := tdate + 1;
     end loop;
     return p_count;
  end;
/
select count_weekends(to_date('01-JAN-01',
'dd-mon-rr'),
to_date('31-DEC-01','dd-mon-rr'))
no_of_weekends from dual
/

Hope this helps

      Rating: 3
questions of
      week  April 28, 2001
Reviewer:  Asif Mahmood
       from Karachi, Pakistan
it was really helpful for me

      Rating:
      4
Excellence  May 03, 2001
Reviewer:
      Absalam Hichri  from Los Angeles California
 I find that this function can be one of the best solutions to this problem. As
always it is Tom's creation and Search .Thank you Tom for your continuous
searches

      Rating: 4
Count of Week
      Ends  May 09, 2001
Reviewer:  Anand Singh
       from Delhi-India
It is very usefull to me.

      Rating: 4
It Is Just
      Great  May 18, 2001
Reviewer:  Ahmad Sha
      Khan  from Karachi,Pakistan
It is an amazing and easy solution to this problem

      Rating:
      4
  June 03, 2001
Reviewer:  Vijay
       from Singapore

      Rating:
      5
  June 07, 2001
Reviewer:  Alan
       from U.S.A

      Rating: 5
I have another
      solution for this  June 15, 2001
Reviewer:
      sadiq.n  from chennai
declare
 m_var  varchar2(4);
 m_var2 varchar2(15);
 y      varchar2(4):='&year';
 mm     number;
begin
for j in 1..12 loop
  select to_number(to_char(last_day(to_date(lpad(j,2,0)||y,'mmyyyy')),'dd'))
into mm from dual;
  for i in 1..mm loop
    select to_char(to_date(lpad(i,2,0)||lpad(j,2,0)||y,'ddmmyyyy'),'dy'),

to_char(to_date(lpad(i,2,0)||lpad(j,2,0)||y,'ddmmyyyy'),'dd-mon-yyyy')
                     into m_var,m_var2 from dual;
    if m_var in('sat','sun') then
       insert into week_end values(m_var,m_var2);
      end if;
  end loop;
 end loop;
end;

      Rating:
      4
  July 09, 2001
Reviewer:  Miguel
      Icaza  from Troy, Mi
No only the Demidenko Sergey solution is faster: it works!. The COUNT_WEEKENDS
function does not work more than 18 years range; at least in my site, where
ALL_OBJECTS has 6723 rows.

      Rating: 2
Sonali
      Kelkar  August 13, 2001
Reviewer:  Sonali
      Kelkar  from MA USA
This one works fast:
Create or replace function num_Business_Days(start_date in date, end_date in
date)
return number is
countBusiness   number := 0;            /* counter for business days */

begin

countBusiness:= (to_date(end_date,'dd-mon-yy')- to_date(start_date,
'dd-mon-yy')) +1
            - (Next_Day(to_date(end_date,'dd-mon-yy'), 'Sunday')
            - Next_Day(to_date(start_date,'dd-mon-yy'), 'Sunday') )/7
            - (Next_Day(to_date(end_date,'dd-mon-yy'), 'Saturday')
            - Next_Day(to_date(start_date,'dd-mon-yy'), 'Saturday') )/7;

return (countBusiness);
end;
/

      Rating:
      5
  August 29, 2001
Reviewer:  Chandra
      Mohan Reddy  from Hyderabad India
 Tom, Pretty Useful one.

      Rating: 4
How to get
      coming saturday by a given date?  March 13, 2003

Reviewer:  Steve  from NYC, USA
Hi Tom,

What I am going to do is to get coming saturday by a given date. after I read
this thread i come up the following sql:

select trunc(:mydate + rr)
from (
  select to_char(:mydate + rownum,'dy') r1, rownum as rr
  from user_Objects
  where rownum<8
)t
where  t.r1 ='sat'

But the performance is too bad, it took about 7 seconds.
Do you have any better and faster way to do it?

Thanks!

Steve

          Followup:

You must have an awful slow system:

ops$tkyte@ORA920> declare
  2          mydate date := sysdate;
  3          x date;
  4  begin
  5  select trunc(mydate + rr) into x
  6  from (
  7    select to_char(mydate + rownum,'dy') r1, rownum as rr
  8    from user_Objects
  9    where rownum<8
 10  )t
 11  where  t.r1 ='sat';
 12  end;
 13  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00

But anyway -- look at next_day
ops$tkyte@ORA920> select next_day( sysdate, 'sat' ) from dual;

NEXT_DAY(
---------
15-MAR-03

March 14, 2003 Jon -- Thanks for the question regarding
      "Evaluation of expressions", version 8.1.7

      You Asked
Does Oracle perform short-circuit boolean evaluation when evaluating
expressions?

For example, consider the following part of a query:

select lcs_id, loan_id,
       client_id, security_id, masterfund_id,
       category, calc_lvr, price,
       greatest(0, decode(value_flag,1,
           decode(category,
******>        'D',(select least(mtmv,sum(d.hold_amount))
                    from dds_loan d
                    where d.lcs_id = lcs_id),
               mtmv+unconfirmed_mtmv),
           decode(category,
               'O',units_available*price+unconfirmed_mtmv+
                  (nvl(units_reserved,0)*nvl(option_contract_size,0)*
                   least(strike_price,price)),
               units_available*price+unconfirmed_mtmv))) market_value,
       sort_order,
       min(sort_order) over(partition by lcs_id) min_sort_order
from (
   select lcs.lcs_id, lvrt.sort_order,
          lc.loan_id, lc.client_id,
          sm.security_id,  sm.masterfund_id,
          case when lvr.aged_lvr_date >= sysdate then lvr.aged_lvr
               when lvr.lvr_type_id = 1 and lvr.aged_lvr is not null
                   then lvr.aged_lvr
               when lvr.single_stock_lvr is not null
******>            and (select count(*)
                        from loan_client_security vlcs, loan_client vlc
                        where vlcs.loan_client_id = vlc.loan_client_id
                        and vlc.loan_id = lc.loan_id) = 1
                   then single_stock_lvr
               else lvr.default_lvr
          end calc_lvr,
          ...

In the case statement makred *****> I have put the lvr.single_stock_lvr is not
null condition before the inline subquery expression because I know it is false
most of the time.  So will Oracle only evaluate the subquery when the first
condition is true, or will it be evaluated for every row?  Same question for the
decode also marked ******>.

What about in the where clause, if there is two or more columns involved in a
join between two tables:

select *
from users u1, users u2
where u1.first_name = u2.first_name
and u1.last_name = u2.last_name

Will the join "fail" after the one of the predicates evaluate to false, or does
that depend on the joining logic being used (nested loop, hash join etc...)

Thanks
Jon

      and we said...
yes, decode is performed left to right and only the FIRST true condition has its
expression evaluated.

Now, for the PREDICATE -- it (the optimizer) will reorder the predicate as it
sees fit.  The order of evaluation of the predicate is totally up to IT.  But by
using CASE, you are again forcing its hand -- having something only done if and
when you want it.

However -- you have to watch that.  You are forcing it to now do that select
count(*) as a single row sort of thing, exactly like a correlated subquery.  It
could be that if it were to turn it into an outer join for example and do it in
bulk it could be faster.  So, watch out -- you can be too smart for your own
good sometimes ;)

In the where clause ALL BETS are off - it'll short circut the evaluation but you
have no idea what order the predicate will happen in .
Review &
      Followup

      Rating: 2
DECODE
      evaluates the lot ???  March 13, 2003
Reviewer:
      Adrian Billington  from UK
Tom

You mention that "decode is performed left to right and only the FIRST true
condition has its expression evaluated.".

So why does the following happen?

816> create sequence seq;

Sequence created.

816>
816> select decode(1,1,1,seq.nextval)
  2  from all_objects
  3  where rownum <= 10;

DECODE(1,1,1,SEQ.NEXTVAL)
-------------------------
                        1
                        1
                        1
                        1
                        1
                        1
                        1
                        1
                        1
                        1

10 rows selected.

816>
816> select seq.currval from dual;

   CURRVAL
----------
        10

Looks to me like the whole lot is being evaluated.

Regards

Adrian

          Followup:

No, it goes to the internals as to why Seq.nextval is done that way - but a
sequence is a horse of a different color.  It is "special".  Using a function,
you can observe the behavioral differences.  The sequence is actually replaced
in there with the value as the rowsource is evaluated.  The function(s) are
evaluated as they are encountered:

ops$tkyte@ORA817DEV> create or replace function f return number
  2  as
  3  begin
  4          dbms_application_info.set_client_info( userenv('client_info')+1 );
  5          return 0;
  6  end;
  7  /

Function created.

ops$tkyte@ORA817DEV> drop sequence s;

Sequence dropped.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> exec dbms_application_info.set_client_info( 0 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> select decode(1,1,1,f)
  2   from all_objects
  3   where rownum <= 10;

DECODE(1,1,1,F)
---------------
              1
              1
              1
              1
              1
              1
              1
              1
              1
              1

10 rows selected.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select userenv('client_info' ) from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
0

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select f
  2   from all_objects
  3   where rownum <= 10;

         F
----------
         0
         0
         0
         0
         0
         0
         0
         0
         0
         0

10 rows selected.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select userenv('client_info' ) from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
10

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create sequence s;

Sequence created.

ops$tkyte@ORA817DEV> select s.nextval from dual;

   NEXTVAL
----------
         1

ops$tkyte@ORA817DEV> select decode(1,1,1,s.nextval)
  2   from all_objects
  3   where rownum <= 10;

DECODE(1,1,1,S.NEXTVAL)
-----------------------
                      1
                      1
                      1
                      1
                      1
                      1
                      1
                      1
                      1
                      1

10 rows selected.

ops$tkyte@ORA817DEV> select s.nextval from dual;

   NEXTVAL
----------
        12

      Rating: 3
Evaluation of
      expressions   March 13, 2003
Reviewer:  Ranga
      Chakravarthi  from Orlando, FL USA
If you wrap the selection of the sequence in a function,
it doesn't get executed. However, this does not answer
Adrian's question

8.1.6>create sequence seq;

Sequence created.

8.1.6>
8.1.6>create function foo
  2  return number
  3  is
  4     dummy number;
  5  begin
  6     select seq.nextval
  7     into dummy
  8     from dual;
  9     return dummy;
 10  end;
 11  /

Function created.

8.1.6>
8.1.6>select decode(1,1,1,foo)
  2  from all_objects
  3  where rownum <= 10;

DECODE(1,1,1,FOO)
-----------------
                1
                1
                1
                1
                1
                1
                1
                1
                1
                1

10 rows selected.

8.1.6>
8.1.6>select seq.nextval from dual;

  NEXTVAL
---------
        1

8.1.6>

          Followup:

see above.

March 14, 2003 Tarek -- Thanks for the question regarding "When
      should we use partitioned table?", version 8.1.7

      You Asked
Hi Tom,

We get better performance partitioning large tables. It there any minimum limit
(like min number of rows 50K) below which partitioning does not offer
satisfactory change in response time?

Take care,
Tarek

      and we said...
Well, not everyone will get better performance partitioning large tables -- some
get worse, some get the same, some get better.

It is a tool -- you have to design to use it.  You have to look at your problem
and understanding how partitioning works say "Hmm, you know, partitioning would
really speed this up if we partition like this"

It is not a "freebie" -- you have to work to obtain better performance via
partitioning (or more often the case, you have to work to not NEGATIVELY impact
performance!).

The number of rows is not really relevant here -- the physical design, it is all
about the physical design.  partitioning a 50 billion record table could slow
things down.  partitioning a 50 record table could speed things up.  It all
depends on how you use the data, what's going on in your system.

My advice:

o truly understand what happens under the covers physically with partitioning
o truly understand what will happen with local indexes vs global indexes
o truly understand the questions you will be asking of the data in general

then -- apply partitioning in the same way you would apply indexing,
materialized views and other PHYSICAL design characteristics.

Review &
      Followup

      Rating:
      5
Partitioning  January 17, 2003
Reviewer:
      Tarek  from AUS
Hi Tom,

Nice answer.

Would you please try to give an example to show the difference?

Have a nice time,
Tarek

      Rating: 3
Partitioning
      for Online and Batch application  March 13, 2003

Reviewer:  jack  from Columbus, OH USA
Hi,
We are having an order entry table and our's is order entry processing system.
We show customer placed orders on the web based on the status and also we
process orders based on their status and date of order to be processed. We are
currently at 10 million rows. Can you suggest me on what columns partitioning
the table will help. Currently we have indexes on order status and date of order
to be processed.
thanks

          Followup:

see above, re-read "my advice"

March 14, 2003 Anthony -- Thanks for the question regarding "Raw
      Device vs File System", version 8.1.7

      You Asked
Hi Tom,

I would just like to ask, how true that you can gain performance when you are
using Raw Device instead of a File System.

Regards,
ANTON

      and we said...
depends.  You could see it go slower, you could see it go faster.

Lets say you are using a unix buffered file system.  Your system does more
physical IO then it should due to poorly tuned applications.  You want to make
things go faster.  You've heard that "raw is faster" so you move your files to
raw.  The next day it is REALLY REALLY slow.  What happened?

Well, your system was double buffered.  There was the Oracle cache, and the OS
file system cache.  maybe 90% of your "physical io" when using the cooked
(non-raw) file system was satisfied via the unix buffer cache.  So, only 10% of
your physical IO really went to physical disk.  When you moved it to raw -- you
took that cache away -- now 100% of your physical IO is TRUE physical IO, really
goes to disk.  You just killed performance (because you tried a quick fix to fix
your problem instead of fixing the problem -- the way to fix IO related issues
is generally to DECREASE your IO ;)

Conversely, you are using a unix buffered file system.  Your system does more
physical IO then it should due to poorly tuned applications.  You want to make
things go faster.  You've heard that "raw is faster" so you move your files to
raw.  The next day it is marginally *faster*.  What happened?

Well, you apparently didn't make much use of the unix buffer cache, perhaps your
SGA was so large and the PGA's just crowded the unix buffer cache out so 90% of
your physical IO was true physical IO.  You are seeing the nominal increase in
performance of RAW disk IO over cooked.

Conversely, you are using a unix buffered file system.  Your system does more
physical IO then it should due to poorly tuned applications.  You want to make
things go faster.  You've heard that "raw is faster" so you move your files to
raw.  The next day it is really really fast.  What happened?

Perhaps you removed an IO bottleneck.

In short -- what you need to do is

o determine cause of poor performance
o develop a custom plan to remove that cause

Just moving files -- well, maybe you get lucky once out of one hundred times but
more typically it makes it worse, makes is marginally better or does nothing
whatsoever.

Review &
      Followup

      Rating: 3
raw devices
      enable kaio  February 20, 2003
Reviewer:
      volkmar buehringer  from germany
on many plattforms raw devices enable
the use of kaio (kernelized async io), which is not possible
on file systems

to remove the overhead of double buffering
a direct mount option for the file system
is sufficent,
but for kaio raw devices or veritas
qio are mandatory, thats the advantage of
raw devices

          Followup:

you missed my point.  Here the effect of double buffering is HIDING the terrible
effect of too much physical IO.  The OS we buffering the data that Oracle
believed was a PIO.

So you look at your system and see "oh my, 100,000 pios an hour.  That is
terrible. I've heard RAW is faster at reads -- let's switch".  Bummer -- you
really only did 10,000 PIO's an hour cause 90k of them were buffered by the OS
for you -- now you are REALLY doing 100k PIO's

So, beware -- DIAGNOSE YOUR PROBLEM first and then apply logical corrections.

I always say, if you watch the LIO's the PIO's take care of themselves....
Reduce the amount of LIO's and you'll most likely reduce the PIO's and then you
don't need to remove the double buffering (which mostly affects WRITES, not
READS anyway so if you are READ bound not WRITE bound -- kaio isn't going to do
you very much good)

My point is -- beware the silver bullet syndrome.  There aren't any.

      Rating: 5
The benefits of
      raw  February 20, 2003
Reviewer:  Connor
      McDonald  from Perth, Australia
Whilst I agree with Tom on not converting to (or from) raw just for the sake of
doing so, I am an ardent fan when putting a system together to always go for
raw.  The reasons being:

a) a PIO as reported as Oracle is very likely to be a real PIO, so you get a
true picture of whats going on

b) the buffering is controlled by Oracle so as a DBA I have more control over
it, and Oracle should yield smarter buffering, since it knows thing like extent
and segment boundaries

c) you typically get some benefits from the OS layer (kernelised aio etc)

d) you do *not* get autoextend.  Yep, I see this as a benefit because it forces
you to do some decent sizing and planning up front (sadly lacking in many
databases)

The historical arguments against raw were always management, admin etc, but
nowadays, every OS as a simple gui or equivalent to resize them, move them,
create/drop them as if they were file systems anyway.

          Followup:

All valid and correct points.

I do see tons of people "afraid of raw" -- like it is "harder".  Perhaps that is
because the DBA needs SA help to set things up (needs root everynow and again)
and they feel a loss of control.  Or, they really don't understand what RAW
means.  A major "fear" for OPS/RAC adopters has always been "oh, i have to use
raw".  We had to create a cluster file system just to work around that!

To me, /dev/.... looks no different then /d01/oradata, they are just files after
all.

      Rating: 4
Raw
      Device  February 21, 2003
Reviewer:  Anthony
       from Phil.
Tom,

Suppose we are in a process of putting up a system, and suppose almost
everything is properly coded (all of the rules/advices/recommendations on the
application side given by "T. Kyte" are followed), and we are just figuring out
what to use (RAW or FS) in our setup, what would you give? What are the pros and
cons of using a RAW as compared to FS?

Regards,
ANTON

          Followup:

If you have no fear of RAW, go for it.

If you fear RAW, don't go for it.

With tools like rman to do backups -- raw vs cooked doesn't come into play there
(you used to have less "tools" to backup raw -- dd, cpio -- but not "tar" or
"cp" for example).

You never touch online redo logs for anything -- so they are safe on raw (and
benefit the most from it -- write intensive)...

archives are always turned into cooked files -- so it is not relevant for them.

controlfiles can be a sticky thing -- gotta make a relatively smallish slice for
them, make sure they are on separate physical things as well (the 3 copies you
have of them)

      Rating: 4
What is your
      comment about this DBA?  March 14, 2003
Reviewer:
      A reader
I have a story about using the raw device:

We have an application running very slow. We don't know the reason. Our
application team cannot find any clue in solving this problem. The application
involves many components such as appliction server, Powerbuilder, 9i Database.
Our DBA find that the disk IO of the redo log is very busy. So he said, "maybe
we move the redo log files to the raw device and see...". Later, he moves the
redo log files to the raw device, and the performance seem improve (but still
not acceptable). And then he said, "ok, the performance is improve now, maybe we
move the whole database to the raw device and see.."

What is your comments about this DBA?

          Followup:

If the DBA's does anything and you were working in a state of "we don't know the
reason" -- well, all I can say is:

  Even a blind squirrel occasionally finds a nut

it could:

a) go slower
b) go faster
c) stay the same

but if you just blindly flip switches, you could get the same effect without
having to take the system down.

One generally at least has a CLUE before one does stuff.

March 14, 2003 juan -- Thanks for the question regarding
      "_trace_files_public", version 9.01

      You Asked
HI,

Could you give an example about the advantage of using
_trace_files_public=true as you says in you book.

howing the different outputs.

Thanks alot.

      and we said...
There isn't any difference in the output -- it is just the permissions on the
trace files.

Without this trace_files_public - trace files are only readable by your DBA.

With it -- you too can read them.

That is the difference.  Not content diffs, just permissions.
Review &
      Followup

      Rating:
      5
  June 21, 2002
Reviewer:
      A reader
Thanks

      Rating: 5
Does this
      affect permissions on utl_file files?  March 14, 2003

Reviewer:  A reader
I was just browsing your book and noticed your comments on this parameter.  I
was wondering if it also affected the permissions on files created by utl_file?

          Followup:

just traces.  utl_file is done with the umask of oracle.

March 15, 2003 Krishan -- Thanks for the question regarding "SQL
      Statement", version 8.1.7

      You Asked
Question:
There is a Table T with two columns Empno and Sal
EMP NO  SAL
1       100
2       150
3       200

I want the out from a select query in this way ?

Empno   Sal    Sum
1       100    100
2       150    250
3       200    450

What will be the select query to retrieve this result ?

      and we said...
  1  select empno, sal, sum(sal) over (order by empno)
  2   from emp
  3*  order by empno
scott@ORA920> /

     EMPNO        SAL SUM(SAL)OVER(ORDERBYEMPNO)
---------- ---------- --------------------------
      7369        800                        800
      7499       1600                       2400
      7521       1250                       3650
      7566       2975                       6625
      7654       1250                       7875
      7698       2850                      10725
      7782       2450                      13175
      7788       3000                      16175
      7839       5000                      21175
      7844       1500                      22675
      7876       1100                      23775
      7900        950                      24725
      7902       3000                      27725
      7934       1300                      29025

14 rows selected.

Review &
      Followup

      Rating:
      5
  March 02, 2003
Reviewer:  GVN
       from Houston
I learn atlest one or two new things in Oracle spending couple of min. on this
site.

      Rating: 3
can u pls.
      point out the possible error   March 14, 2003

Reviewer:  Kumar  from KL
scott:JAVA8.US.ORACLE.COM> l
  1  select empno, sal, sum(sal) over (order by empno)
  2    from emp
  3*  order by empno
scott:JAVA8.US.ORACLE.COM> /
select empno, sal, sum(sal) over (order by empno)
                                 *
ERROR at line 1:
ORA-00923: FROM keyword not found where expected

          Followup:

you are using version 815 or before and you need 8iR2 or above.

ops$tkyte@ORA806> select empno, sal, sum(sal) over ( order by empno ) from emp;
select empno, sal, sum(sal) over ( order by empno ) from emp
                                 *
ERROR at line 1:
ORA-00923: FROM keyword not found where expected

March 15, 2003 Frederic -- Thanks for the question regarding
      "DDL triggers in 8.1.7", version 8.1.7

      You Asked
Hello,

My question is related to DDL triggers, which have undergone
quite significant improvements from 8.1.6 to 8.1.7.

Still, the available level of information about the DDL that
fired the trigger is very poor.

Do you know of any trick that may allow us to see the complete
DDL statement (yeah, with storage clauses and all...) ?

I thought about retrieving from v$SQLAREA, but only ALTER DDL
can be seen here.

Any idea?

Thanks.

/Frederic.

      and we said...
You can read whatever you want out of the data dictionary. It is
not possible to read the CREATE statment out from anywhere.  The
DROP we do not need to read (you can reconstruct that).  The
alter we can get (but don't forget -- some alters are CREATES in
disguise!!! alter table t add constraint, that is really a
CREATE constraint)..

For example:

tkyte@TKYTE816> create table log
  2  ( operation varchar2(25),
  3    owner     varchar2(25),
  4    name      varchar2(25),
  5    extra     varchar2(4000) );

Table created.

tkyte@TKYTE816>
tkyte@TKYTE816> create or replace trigger ddl_trigger
  2  after create or alter or drop on SCHEMA
  3  declare
  4      l_sysevent varchar2(25);
  5      l_extra    varchar2(4000);
  6  begin
  7      select ora_sysevent into l_sysevent from dual;
  8
  9      if ( l_sysevent in ('DROP','CREATE') )
 10      then
 11          if l_sysevent = 'CREATE'
 12          then
 13          begin
 14              select 'storage ( initial ' || initial_extent
||
 15                     ' next ' || next_extent || ' .... )'
                   into l_extra
 16                from all_tables
                  where table_name = ora_dict_obj_name
 17                 and owner = user;
 18          exception
 19              when no_data_found then null;
 20          end;
 21          end if;
 22
 23          insert into log
 24          select ora_sysevent, ora_dict_obj_owner,
 25                         ora_dict_obj_name, l_extra
 26            from dual;
 27      elsif ( l_sysevent = 'ALTER' )
 28      then
 29          insert into log
 30          select ora_sysevent, ora_dict_obj_owner,
 31                         ora_dict_obj_name, sql_text
 32            from v$open_cursor
 33           where upper(sql_text) like 'ALTER%'  ||
 34                                ora_dict_obj_name || '%'
 35             and sid = ( select sid
 36                           from v$session
 37                          where audsid=userenv('sessionid')
);
 38      end if;
 39  end;
 40  /

Trigger created.

tkyte@TKYTE816> drop table t;
Table dropped.

tkyte@TKYTE816> create table t ( x int );
Table created.

tkyte@TKYTE816> alter table t add y date;
Table altered.

tkyte@TKYTE816> select * from log;

OPERATION OWNER NAME EXTRA
--------- ----- ---- -------------------------------------------
DROP      TKYTE T
CREATE    TKYTE T    storage ( initial 131072 next 131072 .... )
ALTER     TKYTE T    alter table t add y date

Review &
      Followup

      Rating: 4
Track DDL
      statements  December 04, 2001
Reviewer:
      Gonela  from Chicago,USA
Hi Tom,

Your answer was very helpful.
I've created "DDL_TRIGGER" as you referred and it is
working fine and inserting details into LOG table.
But If any other user having DDL privileges to my  schema & executing DDL from
remote then changes are not capturing by the trigger..I understand that this
trigger is limited to schema level.But to avoid 'AUDIT' option, I am trying to
use this trigger. Can you please suggest me, how to track DDLs issued by any
privileged user on one particular schema?

Regards,

          Followup:

you would have to create a DATABASE level DDL trigger that looked at object's
schema that was being affected.  You would monitor ALL DLL and capture only that
which you wanted to capture.

      Rating:
      5
  December 19, 2001
Reviewer:  Alex
       from USA
Is it possibal to track a select on a table using triggers

like:
Create or replace trigger for select

          Followup:

No, not in 8i, in 8i you can AUDIT selects and in 9i, there is fine grained
auditing that takes this functionality up a level (very similar to a before
select trigger).

      Rating: 4
Some
      exception  January 15, 2002
Reviewer:  Sue
      Hsieh  from USA
Hi Tom,

Thank you very much for this. I use your example to create my own database
trigger to track DDL events, and it works very well.

However, there is some exceptions that can cause this trigger to fail. One
exception I encounted was that someone inserted a row to a table to cause a
invalid trigger to compile(an alter event occurred). However, it is not captured
in v$open_cursor, and cause a no_data_found exception. Currently, I only use
exception statement to avoid exceptions. Do you have better suggestion with it.

Also do you have suggestions to distinguish alter xxx compile and the real alter
object events? When one object got changed, the dependent objects got
recompiled, and the last_ddl_time column on dba_objects for those dependent
objects got updated as well. So, how can we distinguish from data dictionary
tables for this?

Thanks,

Shuhsin

          Followup:

upgrade to 9i and you'll be able to get the firing statement via a function
call...

If its not in v$open_cursor -- you'll have to not audit it.

      Rating:
      5
timestamp  January 16, 2002
Reviewer:
      an  from DE, USA
Hi Tom:

it's very hopeful, i ran it at our db, works very well.
can i also get timestamp from the trigger?
thank you very much,

          Followup:

just use sysdate

      Rating: 3
Where Can we
      get the Trigger_Code in Dictionary.  January 16, 2002

Reviewer:  Qaisar  from CANADA
It's so helpful.

Where can we extract the Trigger Body or Code from data Dictionary like the
Other trigger's code is in DBA_TRIGGERS.

Can you please through some light for extracting the code. Thanks

          Followup:

This works in many cases:

set echo off
set long 50000
set verify off
set feedback off
set termout off
set heading off
set pagesize 0
spool &1..sql

select
'create or replace trigger "' ||
         trigger_name || '"' || chr(10)||
 decode( substr( trigger_type, 1, 1 ),
         'A', 'AFTER', 'B', 'BEFORE', 'I', 'INSTEAD OF' ) ||
              chr(10) ||
 triggering_event || chr(10) ||
 'ON "' || table_owner || '"."' ||
       table_name || '"' || chr(10) ||
 decode( instr( trigger_type, 'EACH ROW' ), 0, null,
            'FOR EACH ROW' ) || chr(10) ,
 trigger_body
from user_triggers
where trigger_name = upper('&1')
/
prompt /

spool off
set verify on
set feedback on
set termout on
set heading on

      Rating:
      4
excellent  January 17, 2002
Reviewer:
      zhu chao  from china
A little question:
 How can i record the truncate statement? In my test, truncate command is not
recorded.
  I add it in
     if ( l_sysevent in ('DROP','CREATE','TRUNCATE' ))
  But it doesnot work.

And:
  Which Oracle document talk about the usage of commands like:
  select ora_sysevent into l_sysevent from dual;.
   How can i findout all the possible ora_sysevent ? from which document?
  Thanks.

      Rating: 4
add
      on  January 17, 2002
Reviewer:  zhu chao
       from china
Sorry, tom, i ask a foolish question, i find it out.
Just add or truncate in the after statement.
Thanks.

      Rating: 3
Sorry if I
      could not make it clear.  January 17, 2002

Reviewer:  Qaisar  from Canada
My Question is to get the code about the Database Triggers/ Instance Triggers.

I knew that we can get the code from User_, All_ and DBA_Triggers, I am under
the impression that this stores only Table Triggers, is it right? If yes, Where
can I get the Info about Instance Triggers.

Thanks.

          Followup:

tkyte@TKYTE816> select distinct triggering_event from dba_triggers;

TRIGGERING_EVENT
--------------------------------------------------------------------------

DELETE
DROP
INSERT
SHUTDOWN   <<<<====
STARTUP    <<<<====
UPDATE

6 rows selected.

they are in the same place...

      Rating: 4
how about
      pwk  March 08, 2002
Reviewer:  A reader
       from DE, usa
Hi Tom:

i was very happy when i ran ddl_triger script in our db, we only have one major
db, but many users use and modify it, so i do need to set at database level to
monitor the db. recently i found out the log table restored also pwd when a user
changes his pwd.  i just wonder it's possible if i get rid of the use pwd
record?

thank you very much,
an

          Followup:

You own the trigger - you can make it do whatever you want!

Instead of

 29          insert into log
 30          select ora_sysevent, ora_dict_obj_owner,
 31                         ora_dict_obj_name, sql_text
 32            from v$open_cursor
 33           where upper(sql_text) like 'ALTER%'  ||
 34                                ora_dict_obj_name || '%'
 35             and sid = ( select sid
 36                           from v$session
 37                          where audsid=userenv('sessionid') );

select OUT the sql_text and if it is like 'ALTER%USER%IDENTIFIED%BY%' --
substring it and then put it into the log.

I should also point out that in 9i there is a function -- ora_sql_text --
that'll give you the text of the statement that fired the trigger, it'll be much
easier to capture that stuff in 9i using that function.

      Rating:
      2
  April 11, 2002
Reviewer:  Am
       from Greece
I dont know what is wrong but I get error
(1):PL/SQL: ORA-00942: table or view does not exit
when I try to save it

but when I comment the "
elsif ( l_sysevent = 'ALTER' )
       then
           insert into log
           select ora_sysevent, ora_dict_obj_owner,
                          ora_dict_obj_name, sql_text
             from v$open_cursor
            where upper(sql_text) like 'ALTER%'  ||
                                 ora_dict_obj_name || '%'
              and sid = ( select sid
                            from v$session
                           where audsid=userenv('sessionid') ); " it works

Can you help me

          Followup:

read

http://osi.oracle.com/~tkyte/Misc/RolesAndProcedures.html
to understand why and then have sys or someone connected as sysdba:

grant select on v_$open_cursor to OWNER_OF_TRIGGER;
grant select on v_$session to OWNER_OF_TRIGGER;

and try again.

      Rating: 2
DDL triggers in
      8.1.7", version 8.1.7  January 29, 2003
Reviewer:
      Ted  from Philadelphia, Pa
When searching for triggers with the ALL_TRIGGERS views for an ID with without
triggers, no triggers are returned.  The sys.all_triggers has "select" granted
to public.  Looks like a privilege issue.  (System DBA's will not grant select
on DBA_TRIGGERS to app id's).  Any idea on how to find the triggers on all id's
via the "all_triggers" view?
If it is a privilege issue, what privilege is needed.

Thanks for help.

          Followup:

what?

do you mean "when I query all_triggers -- i see no rows".  that is the only
conclusion I could come to.

All_triggers shows you the triggers you are allowed to see, no more, no less

You can read the text of all_triggers out of all_views.  In there you will find
things like:

     (
        trigobj.owner# = userenv('SCHEMAID') or
        tabobj.owner# = userenv('SCHEMAID') or
        exists (select null from sys.sysauth$
             where grantee# in (select kzsrorol from x$kzsro)
               and privilege# = -152 /* CREATE ANY TRIGGER */)))
     )

which says

trigobj.owner# = userenv('SCHEMAID') or  ==> you OWN the trigger
tabobj.owner# = userenv('SCHEMAID') or   ==> you OWN the table the trigger is
                                             on

exists (select null from sys.sysauth$ ==>  you have the CREATE ANY TRIGGER
                                          priv

So, by its very definition (and this follows the way all of the ALL_ views are
designed to work)

o you have to own the trigger
o own the table the trigger is on
o have the CREATE ANY TRIGGER priv

DBA_TRIGGERS lets you see *all* triggers, regardless.  That is "how to find the
triggers on all id's" -- by its very definition you cannot use ALL_TRIGGERS
(unless you have CREATE ANY TRIGGER -- but that would be less likely then
getting a DBA to grant on DBA_TRIGGERS)

Now, that aside for a moment -- if your application truly needs this
information, that is you have a BUSINESS REQUIREMENT for this data -- and the
DBA's say "nope, no go", then you have a problem with your work environment.
 The DBA views are not mystical/magical, they just let you see the definition of
everything.  If you have a business need to see everything there is no reason
they should not give you access to it.

      Rating: 3
DDL triggers in
      8.1.7", version 8.1.7  January 29, 2003
Reviewer:
      Ted  from Philly
Tom,

Just found the answer to my question in another question/answer on your web page
"No data in All_Triggers table".

Thanks Again

      Rating:
      3
DDL-Trigger-Attributes  January 29, 2003

Reviewer:  Jens  from GERMANY
hi tom,

i created two simple schema triggers - firing before and  after DDL-events
create, drop, alter, rename.

why do i get the same (old) object name in both triggers for attribute
"ORA_DICT_OBJ_NAME" when I issue a rename?

is there any way to get the object-id (which stays the same in the event of a
rename) from a trigger-attribute?

          Followup:

you would take the name/owner/type and goto *_objects with that to find out
anything else you wanted.

      Rating: 4
Dictionary
      shows OLD data in AFTER ddl trigger?  March 15, 2003

Reviewer:  Yuliy  from CA, USA
Tom,

Excellent info.

I noticed, however, that even in an AFTER DDL (ON SCHEMA) trigger, some data
dictionary views (USER_TAB_COLUMNS is what I am interested in) supply OLD
information, that is, as it was BEFORE the ddl. I have the test code, but you'll
see what I mean if you try 'alter table add...' and 'alter table modify...' to
add columns/change columns' datatype and watch the result from within an AFTER
DDL ON SCHEMA trigger. You'll find that USER_TAB_COLUMNS still shows the columns
and types as they were *prior* to ddl.

On the other hand, the trigger/dictionary work as expected when you CREATE a new
table or ALTER TABLE DROP COLUMN -- they show columns as if the ddl has
completed, just as one would expect for an AFTER trigger.

In contrast, a BEFORE DDL trigger shows a very consistent view of the dictionary
BEFORE any modifications.

Confused... I really hoped for the "new" dictionary information in my AFTER
trigger. Help! (version 9.2.0.1.0)

          Followup:

the information you are looking for is in the ora_ functions.

March 15, 2003 Arindom -- Thanks for the question regarding
      "Piecewise update of XML document in 9i release 2", version 9.0.2

      You Asked
Hello,
Had a question about the piecewise update functionality of XML documents in 9i
release 2.
Would an update of a particular node lock up the entire row ?
Is the granuality of the lock row-level or has it been broken down to a node
level? Could multiple users/sessions update the same XML document without being
blocked?

Thanks.

      and we said...
I asked Sean Dillon, our local XML Technologist, to answer this question and
here's what he had to say:
--

Very interesting question, I'm glad you asked it.  The question, unfortunately,
is a tad misleading so lets explain some assumptions so we can answer the
question appropriately:

1.  Would an update of a particular node lock up the entire row ?  Well,
updating a node will lock the row which contains the updated data.  That doesn't
necessarily mean the entire row, or what I think you're asking is will that
update lock up the entire XML document...  the answer to that question is YES
and NO, depending on how the XML document is stored.  I'll explain in a minute.

2.  Is the granularity of the lock row-level or has it been broken down to a
node level?  Oracle supports row-level locking.  As far as "node-level-locking",
there's really no such thing.  Oracle doesn't LOCK based on a  node update, it
locks based on a row update.  If your entire XML document is stored in one row
then yes, updating the node will acquire a write lock the entire XML document.
There is a way, however, to update a node without locking any other nodes on a
peer, parent or child level of the XML document. Again, I'll explain in a little
more detail below.

3.  Could multiple users/sessions update the same XML document without being
blocked?  The answer to this is YES, although it depends on how the XML document
is stored.  Let's look at this in detail.

There are two different storage architectures for XML storage in Oracle 9i
release 2 XML DB.  You can either store your XML documents as CLOBs or have
Oracle decompose the XML documents into object-relational structures.  This is
completely transparent to the developer/user, so don't worry about mapping XML
to tables or anything like that.  Now, decomposing XML documents into
object-relational structures is only supported if your XML documents are based
on an XML Schema that has been registered with XML DB.  For example, I can store
an XML document into XML DB that is neither based on a DTD or an XML Schema, and
under the covers Oracle stores it as a CLOB column.  If I want my XML documents
decomposed, I need to store & register an XML Schema into XML DB.  This
instructs Oracle to create object-relational tables under the covers where the
element and attribute values will be stored behind the scenes.  Oracle will
automatically do this for you, but if you want more granular control then you
can tell Oracle where to store data by annotating the XML Schema you register
(more about this in the documentation listed below). Once you've loaded the XML
Schema, Oracle creates the objects, object-tables, nested tables, etc. for your
XML content. When this is done, you store an XML document that is based on the
XML Schema, at which time Oracle automatically decomposes the document into
those object-relational tables.  (Don't worry, when you query up the XML
document, the data is recomposed into an XML document transparently).  Depending
on your application, this can be a much more efficient mechanism for storing
your XML.  Now that we understand that there are TWO storage architectures for
XML documents in Oracle9i release 2...

(I can appreciate the fact that this is fairly complex stuff, and my
one-paragraph explanation may be lacking... for more information see the
Oracle9iR2 XML Database Developer's Guide on OTN, specifically Chapter 1 section
- XMLType Storage Architecture:
http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96620/toc.htm
So if we store all the XML content into a CLOB, there is no way to have two
different sessions edit the same CLOB at the same time.  Writers (of a row)
block writers (of the same row).  If we use XML Schema-based storage, however,
we introduce all kinds of possibilities.  We can tell Oracle to store nodes in
different tables, thereby making MANY rows for a single XML document.  Now, more
than one user/session can edit the same XML document.

OK, enough talk let's look at a practical example.  Say we want to store
departmental information in an XML document.  I'll create an XML Schema to model
my departments and employees.  You can find it at
http://asktom.oracle.com/~sdillon/xml/department.xsd
 You'll notice there are
"xdb" attributes that are in the Oracle XML DB Namespace, such as the xmlns:xdb
attribute in "schema", the xdb:defaultTable attribute in the "Department"
element.  This lets us tell Oracle how to store particular parts of the XML
document. Using these XML DB attributes, I'm telling Oracle to create a
DEPARTMENT_OBJECTS table and an EMPLOYEE_OBJECTS table.  In the
DEPARTMENT_OBJECTS table, I'll store the "departments" nodes.  In the
EMPLOYEE_OBJECTS table, I'll break out the "employees" nodes.  This effectively
means there will be one row for each department element and one row for each
employee node.

I load this document into a folder in XML DB (to see how to do this, reference
the documentation listed above) using FTP into a folder named
/home/SDILLON/depts/xsd.  Using
http://asktom.oracle.com/~sdillon/xml/registerSchema.sql,
I register this XML Schema with the
database, and then this operation creates the DEPARTMENT_OBJECTS table, the
EMPLOYEE_OBJECTS table, and the EMPS_TYPE & EMP_TYPE objects.  Now, when I save
an XML document into the XML DB repository, it is stored in these tables.  We'll
use an example document
http://asktom.oracle.com/~sdillon/xml/adept.xml
 When I ftp this into the XML DB repository, it
creates one row in the DEPARTMENT_OBJECTS table, and three rows in the
EMPLOYEE_OBJECTS table.

NOW FOR THE BIG MOMENT. :-)

Once this is done, I can submit these three SQL statements, all in different
sessions, at the same time, none of which block each other:
http://asktom.oracle.com/~sdillon/xml/updateDept.sql
http://asktom.oracle.com/~sdillon/xml/updateEmp2.sql
http://asktom.oracle.com/~sdillon/xml/updateEmp3.sql
Each can commit or rollback at their own leisure with the confidence of any
other standard relational table update.  The final document looks like this:

http://asktom.oracle.com/~sdillon/xml/updatedDept.xml
I apologize for the long post but if you made it this far congratulations! If
you couldn't tell, I find this stuff pretty slick ;-).  I hope that answers your
questions.

_smd_

_____________________________________________________________________
Sean Dillon - Principal Technologist, Oracle Corporation
Author "Beginning Oracle Programming"
http://www.amazon.com/exec/obidos/ASIN/186100690X
Review
      & Followup

      Rating:
      5
  December 03, 2002
Reviewer:
      Arindom  from Boston, MA
Excellent ... The examples make it even better...
Thanks.

      Rating: 4
Correction to
      link  December 03, 2002
Reviewer:  dw
       from Dallas, TX USA
Minor correction:

http://asktom.oracle.com/~sdillon/adept.xml
should be

http://asktom.oracle.com/~sdillon/xml/adept.xml
Article is great.  Thank, Sean.

      Rating: 5
Inserting an
      employee into a department  March 15, 2003

Reviewer:  Chip  from Denver, CO USA
Using Sean Dillon's example, how can another employee be added to an existing
department (using SQL) ?

Example employee to add to department 100:
  <Employees>
    <Employee>
      <EmployeeID>4</EmployeeID>
      <FirstName>Christopher</FirstName>
      <LastName>Beck</LastName>
      <Salary>25</Salary>
    </Employee>
  </Employees>

Thanks :)

          Followup:

you mean besides insert into emp values?

      Rating: 5
An example
      insert into employees_object would be helpful  March 17,
      2003
Reviewer:  Chip  from Denver, CO USA
My sql*plus attempt to add an employee by inserting into employee_objects gets
an non-descriptive ORA-19007 error:

oo@MSORA920>desc employee_objects
 Name                                                                     Null?
  Type
 ------------------------------------------------------------------------
-------- -------------------------------------------------

TABLE of SYS.XMLTYPE(XMLSchema "home/SDILLON/depts/xsd/deparment.xsd" Element
"Employee") STORAGE Object-relational TYPE "EMP_TYPE"

oo@MSORA920>desc emp_type
 emp_type is NOT FINAL
 Name                                                                     Null?
  Type
 ------------------------------------------------------------------------
-------- -------------------------------------------------

 SYS_XDBPD$
  XDB.XDB$RAW_LIST_T
 EmployeeID
  NUMBER(38)
 FirstName
  VARCHAR2(30)
 LastName
  VARCHAR2(30)
 Salary
  NUMBER(10,2)

oo@MSORA920>SELECT * FROM department_objects;

SYS_NC_ROWINFO$
---------------------------------------------------------------------------------
---------------------------------------------------

<?xml version="1.0"?>
<Department
xmlns:xdb="
http://xmlns.oracle.com/xdb
xmlns:xsi="
http://www.w3.org/2001/XMLSchema-instance
xsi:noNamespaceSchemaLocation="home/sdillon/depts
/xsd/department.xsd">
  <ID>100</ID>
  <Name>AskTomDepartment</Name>
  <Location>Reston, VA</Location>
  <Employees>
    <Employee>
      <EmployeeID>1</EmployeeID>
      <FirstName>Tom</FirstName>
      <LastName>Kyte</LastName>
      <Salary>100</Salary>
    </Employee>
    <Employee>
      <EmployeeID>3</EmployeeID>
      <FirstName>Cameron</FirstName>
      <LastName>ORourke</LastName>
      <Salary>10</Salary>
    </Employee>
    <Employee>
      <EmployeeID>2</EmployeeID>
      <FirstName>Sean</FirstName>
      <LastName>Dillon</LastName>
      <Salary>1</Salary>
    </Employee>
  </Employees>
</Department>

oo@MSORA920>SELECT * FROM employee_objects;

SYS_NC_ROWINFO$
---------------------------------------------------------------------------------
---------------------------------------------------

<?xml version="1.0"?>
<Employee>
  <EmployeeID>2</EmployeeID>
  <FirstName>Sean</FirstName>
  <LastName>Dillon</LastName>
  <Salary>1</Salary>
</Employee>

<?xml version="1.0"?>
<Employee>
  <EmployeeID>1</EmployeeID>
  <FirstName>Tom</FirstName>
  <LastName>Kyte</LastName>
  <Salary>100</Salary>
</Employee>

<?xml version="1.0"?>
<Employee>
  <EmployeeID>3</EmployeeID>
  <FirstName>Cameron</FirstName>
  <LastName>ORourke</LastName>
  <Salary>10</Salary>
</Employee>

oo@MSORA920>INSERT INTO employee_objects
  2  VALUES ( xmltype.createXML(
  3  '<?xml version="1.0"?>
  4  <Department
  5   xmlns:xdb="
http://xmlns.oracle.com/xdb
  6   xmlns:xsi="
http://www.w3.org/2001/XMLSchema-instance
  7   xsi:noNamespaceSchemaLocation="home/sdillon/depts/xsd/department.xsd"
  8   >
  9    <ID>100</ID>
 10    <Name>AskTomDepartment</Name>
 11    <Location>Reston, VA</Location>
 12    <Employees>
 13      <Employee>
 14        <EmployeeID>4</EmployeeID>
 15        <FirstName>Christopher</FirstName>
 16        <LastName>Beck</LastName>
 17        <Salary>25</Salary>
 18      </Employee>
 19    </Employees>
 20  </Department>
 21  '))
 22  /
INSERT INTO employee_objects
            *
ERROR at line 1:
ORA-19007: Schema and element do not match

oo@MSORA920>edit
Wrote file afiedt.buf

  1  INSERT INTO department_objects
  2  VALUES ( xmltype.createXML(
  3  '<?xml version="1.0"?>
  4  <Department
  5   xmlns:xdb="
http://xmlns.oracle.com/xdb
  6   xmlns:xsi="
http://www.w3.org/2001/XMLSchema-instance
  7   xsi:noNamespaceSchemaLocation="home/sdillon/depts/xsd/department.xsd"
  8   >
  9    <ID>100</ID>
 10    <Name>AskTomDepartment</Name>
 11    <Location>Reston, VA</Location>
 12    <Employees>
 13      <Employee>
 14        <EmployeeID>4</EmployeeID>
 15        <FirstName>Christopher</FirstName>
 16        <LastName>Beck</LastName>
 17        <Salary>25</Salary>
 18      </Employee>
 19    </Employees>
 20  </Department>
 21* '))
oo@MSORA920>/

1 row created.

oo@MSORA920>select * from department_objects;

SYS_NC_ROWINFO$
---------------------------------------------------------------------------------
---------------------------------------------------

<?xml version="1.0"?>
<Department xmlns:xdb="
http://xmlns.oracle.com/xdb
xmlns:xsi="
http://www.w3.org/2001/XMLSchema-instance
xsi:noNamespaceSchemaLocat

ion="home/sdillon/depts/xsd/department.xsd">
  <ID>100</ID>
  <Name>AskTomDepartment</Name>
  <Location>Reston, VA</Location>
  <Employees>
    <Employee>
      <EmployeeID>1</EmployeeID>
      <FirstName>Tom</FirstName>
      <LastName>Kyte</LastName>
      <Salary>100</Salary>
    </Employee>
    <Employee>
      <EmployeeID>3</EmployeeID>
      <FirstName>Cameron</FirstName>
      <LastName>ORourke</LastName>
      <Salary>10</Salary>
    </Employee>
    <Employee>
      <EmployeeID>2</EmployeeID>
      <FirstName>Sean</FirstName>
      <LastName>Dillon</LastName>
      <Salary>1</Salary>
    </Employee>
  </Employees>
</Department>

<?xml version="1.0"?>
<Department xmlns:xdb="
http://xmlns.oracle.com/xdb
xmlns:xsi="
http://www.w3.org/2001/XMLSchema-instance
xsi:noNamespaceSchemaLocat

ion="home/sdillon/depts/xsd/department.xsd">
  <ID>100</ID>
  <Name>AskTomDepartment</Name>
  <Location>Reston, VA</Location>
  <Employees>
    <Employee>
      <EmployeeID>4</EmployeeID>
      <FirstName>Christopher</FirstName>
      <LastName>Beck</LastName>
      <Salary>25</Salary>
    </Employee>
  </Employees>
</Department>

oo@MSORA920>select * from employee_objects;

SYS_NC_ROWINFO$
---------------------------------------------------------------------------------
---------------------------------------------------

<?xml version="1.0"?>
<Employee>
  <EmployeeID>2</EmployeeID>
  <FirstName>Sean</FirstName>
  <LastName>Dillon</LastName>
  <Salary>1</Salary>
</Employee>

<?xml version="1.0"?>
<Employee>
  <EmployeeID>1</EmployeeID>
  <FirstName>Tom</FirstName>
  <LastName>Kyte</LastName>
  <Salary>100</Salary>
</Employee>

<?xml version="1.0"?>
<Employee>
  <EmployeeID>3</EmployeeID>
  <FirstName>Cameron</FirstName>
  <LastName>ORourke</LastName>
  <Salary>10</Salary>
</Employee>

<?xml version="1.0"?>
<Employee>
  <EmployeeID>4</EmployeeID>
  <FirstName>Christopher</FirstName>
  <LastName>Beck</LastName>
  <Salary>25</Salary>
</Employee>

Now, the employee_objects has 4 employees, but department_objects has 2 XML
documents for 1 department.  Using XML and SQL, how can an employee be added to
a department within 1 XML document ?  Also, how can SQL be used to delete an
employee from a department in an XML document ?

Thanks :)

March 15, 2003 -- Thanks for the question regarding "Should
      time_statistic=true on production database?", version 8.1.7

      You Asked
Hi Tom,

   I want to set the time_statistic to true on the production database such that
I can know the timing information. However, the existing DBA said it will affect
the performance. I want to know your opinion about this issue. Should
time_statistic set to true on the production database? And the performance
impact?

      and we said...
turn it on, gather your stats and then turn it off then.

otherwise, you are clueless -- you've no idea.

it will not materially affect performance (eg: no one will even know)
March 15, 2003 Bijay -- Thanks for the question regarding "Why
      the same sql querry takes different times while running ", version 9.0.2

      You Asked
Hi tom
Is there any reason why the same sql querry takes different times while
running,if i run continuously the same querry some 1st time say 10 secs and next
time onwards some times 4sec some times 8 sec some times 10 sec.

and if i run the same querry in differt times that also take different time.

is there any parameter to check all this.
thanks
bijay

      and we said...
it is called "multi-user processing".

sometimes you get all of the resources, just right when you ask them.

sometimes the blocks you ask for are nicely tucked in the buffer cache.

sometimes the CPU is just sitting there waiting for you and you along.

sometimes the network is free of traffic and your stuff zips along.

sometimes -- sometimes, all of the above are false.  sometimes, some of the
above are true.  in really good times -- all of them are.

Also, if you are using sqlplus to time this with lots of rows showing up on a
screen -- you have to add in all of the above with respect to your own PC.
sometimes it scrolls a window really fast.  sometimes there is nothing running i
the background.  sometimes.... none of the above are true and it is really your
client machine that is being slow.

You can use tools like tkprof+ sql_trace to see how long the query ACTUALLY
takes cpu timewise and elapsed time wise...

March 16, 2003 Sudhir -- Thanks for the question regarding "what
      is redo log sync and what more can be done about it", version 8.1.7.2

      You Asked
If I see redo logs are causing log sync wait event on the application processes,
what could be the cause?

Too often commits, too small log buffer (1M at present on solaris 2.6), some
resource limit? How does one figure if 1M log buffer is sufficient (its filling
up in less than 3sec).

what are some queries I can run to find what should be minimum log buffer size.
Also, having data files, etc on the redo log drive  - would this seriously
impact performance of the LGWR and consequently application?

Thanks very much in advance.

      and we said...
log sync waits are most commonly caused by frequent commits.  When you commit,
you must wait for LGWR to flush the contents of the redo log buffer to disk
(else we could not assure you of recovery).

A small log buffer would manifest itself as a LOG_BUFFER_SPACE wait.

Having data files and other things on the same device as your online redo log
can definitely impact log writers ability to do its job as fast as possible.

Things you can do:

o look at your transactions.  If you are "over committing" -- commiting in the
mistaken belief that you are saving resources and not on true transactional
boundaries -- stop it.  Commit only when your transaction is over and do not
commit too frequently (if you have very small transactions, so be it -- you MUST
commit but if you are committing just for the sake of committing, don't)

o speed up LGWR.  Make it more efficient.  Ensure you are not using something
like RAID 5 for log devices.  Reduce contention on that device (let LGWR own
it).  Ensure LGWR and ARCH are not contending with eachother (you want at lead 5
devices dedicated to logging -- you NEED to mirror redo members and you NEED to
archive.  So, if you have disks 1..5 you can:

have redo log groups 1, 3, 5, 7, .... on disks 1 and 3
     redo log groups 2, 4, 6, 8, .... on disks 2 and 4
     archive destination to disk 5

Now, when LGWR is writing to groups 1, 3, 5, 7, .... on disks 1/3, ARCH will be
reading groups 2, 4, 6, 8, ... on disks 2/4 and writing to disk 5.  When LGWR
advances, so does ARCH and they'll switch disks.  Never does ARCH and LGWR
contend with eachother.
Review & Followup

      Rating:
      4
Reader  October 29, 2001
Reviewer:
      Reader  from USA
Tom,

I have heard from someone that Oracle recommends that there should be at least 3
log members to a group. Is there any truth to it.

Thanks

          Followup:

Depends on your level of paranoia.  If you believe 2 disks will fail
simultaneously -- but three of them will not -- yes, there is truth to it.

2 is the minimum there should be.  It goes up from there.

      Rating: 5
Other waits
      events  October 29, 2001
Reviewer:  Andre
      Whittick Nasser  from Rio de Janeiro, Brazil
In fact, you should get rid of all (or most) factors affecting redo log
contention. In addition, maybe:

- Making sure DBWR checkpoiting and ARCH copying are not blocking log switches
(check out ALTER.LOG);

- You can also verify that the following statistics in V$SYSSTAT are either high
or increasing:

"redo buffer allocation retries" - Total number of retries necessary to allocate
space in the redo buffer. Retries are needed either because the redo writer has
fallen behind or because an event such as a log switch is occurring.

"redo log space requests" - Number of times the active log file is full and
Oracle must wait for disk space to be allocated for the redo log entries. Such
space is created by performing a log switch.

In these cases, you can try some things, the main ones being:

. Make DBWR more infrequent (performance will inscrease but recoverbility could
be compomised). Do that adjusting FAST_START_IO_TARGET (8i) or
FAST_START_MTTR_TARGET (9i).

. Enlarge your redo files. This will make checkpoiting more infrequent on log
switches.

. Spawn more DBRWs, ARCHs, or their IO Slaves to increase throughput.

Hope this helps !

      Rating:
      4
Reader  October 29, 2001
Reviewer:
      Reader  from USA
Actually, this person is a consultant and he was
claiming that Oracle recommends a minimum of 3 log members.
He was explaining like very similar to "philosopher's
algorithm". If Oracle finds one member of redo log
is corrupt, it will compare the other two, to determine
which redo log member contains valid data.

I was not impressed, but I thought, you would definitely
know if Oracle used such an algorithm

Thanks

      Rating: 5
Following some
      information presented ...  October 29, 2001

Reviewer:  A reader
Tom, and others who pitched in, Thanks

I understand following triggers redo log buffer flush
1 - commits
2 - redo log buffer 1/3 full
3 - more than a meg change in redo log buffer
4 - before flushing dirty data buffers to data files

So if I have 3MB of buffer than as soon as it fills 1MB it will dump it to redo
files and clean the redo log memeory buffer? Then 2MB will never be used?

Or if I have very small update and a commit (and no one else is working), redo
log buffer will get flushed. Say I have 10K worth change, does it mean all redo
log buffer will get cleaned?

I understand you mention make sure LGWR & ARCH are working on different disks.

How can I find how often LGWR is flushing the buffer and what kind of requests
its putting on hold since its not done writing its information? What to look for
in iostat command?

Is there a way to avoide redo log flush in case of commit? I understand for
recovery guarantee oracle needs it but can it can wait for another 3 seconds? (I
know this is silly question but it seems like in multiuser environment there
will be 10s of commits (We have 500 servers running), so there is very high
chance next redo log flush will occur within 3seconds. I am just trying to
understand what can be done to maximise redo log buffer memory use and take
3seconds risk(lose maximum 3sec worth of data, say 3MB)

Thanks for you help

          Followup:

It does not mean the other 2mb will never get used, it just means that when we
get to 1mb, LGWR (who does not FILL the redo log buffer) will start emptying it
(whilst we are busy filling the other 2mb that is free).  It might just keep
emptying it if we fill it as fast (or faster) then he can empty it.

Yes, if you have a small update and ocmmit -- your changes, all of them, are
written to disk.  Any outstanding stuff in the log buffer will go to disk.

You won't use IOSTAT, you'll monitor the stats/events Andre Whittick Nasser
mentioned to see if there is an issue (v$tables..)

NO, there is not really a way to avoid the redo log flush in case of a commit.
Once you commit, we've made a solemn promise to you that cannot be broken.

      Rating: 5
Following some
      information presented (cont'd)  October 29, 2001

Reviewer:  Andre Whittick Nasser  from Rio de
      Janeiro, Brazil
Well..

1) That's right, it suffices that you fill up 1/3 of the log buffer so it to be
flushed. The log buffer works in a circular fashion, so the (inevitably) old
data beyond that will be overwritten.

2) Yes, all data will be flushed on a commit. See 4.

3) There is no way to monitor LGWR activity, but tools like UNIX's iostat on the
respective disk. There is nothing like V$FILESTAT on redo files.

4) As you mentioned, the only way Oracle has to enforce that commited data will
be rolled forward in case of recovery is flushing it immediately after a commit.
The big "compromise" Oracle has towards you is that all commited data will be in
redo files. The golden rule: "All commited data will be available in datafiles
after a recovery". The data that is rolled back and/or never explicitly commited
will not have a commit record in the redo file and will not be applied during
recovery.

Hope this helps !

      Rating: 5
Following some
      information presented (cont'd)   October 29, 2001

Reviewer:  Andre Whittick Nasser  from Rio de
      Janeiro, Brazil
Right,

As to my answer number 3) you can also check for contention using some info in
V$SYSSTAT:

"redo buffer allocation retries" - Total number of retries necessary to allocate
space in the redo buffer. Retries are needed either because the redo writer has
fallen behind or because an event such as a log switch is occurring.

"redo log space requests" - Number of times the active log file is full and
Oracle must wait for disk space to be allocated for the redo log entries. Such
space is created by performing a log switch.

... although there is not really a "monitoring tool".

Thanks !

      Rating: 5
Thanks for all
      help,   October 29, 2001
Reviewer:
      A reader
Given that whilest 1MB buffer is getting flushed some other DML activity is
filling up rest of the redo log buffers,

how much should be the redo log buffer size? Say database is very active with
250 active connections at any time. I understand 50MB is probably too big but
would it be bad idea to have size of 5MB (is it too small?). SGA is close to
2.5GB. Current redo log buffer size is 5MB. Redo log file size is 1GB and it is
Veritas quickio.

Any suggestions. Thanks again.

      Rating: 5
Following some
      information presented (cont'd)  October 29, 2001

Reviewer:  Andre Whittick Nasser  from Rio de
      Janeiro, Brazil
Unix's IOSTAT (sar, etc) is the only "tool" to monitor redo file I/O activity on
the respective disk the file is placed on. It terms of CONTENTION -- that is
what you asked about -- the above V$SYSSTAT statistics apply.

      Rating: 5
Andre, thanks
      for you help!  October 29, 2001
Reviewer:
      A reader
I really appreciate great and very timely help from you. Tom, as usual you have
provided great help and stage for people to learn great things.

Thanks

      Rating: 5
Thanks for all
      help (cnt'd)  October 29, 2001
Reviewer:  Andre
      Whittick Nasser  from Rio de Janeiro, Brazil
The tuning guideline for you to size redo log buffer is checking for contention,
again using those two above mentioned statistics:

"redo buffer allocation retries" and "redo log space requests" in V$SYSSTAT. Try
and find a value for LOG_BUFFER to stabilize theses statistics. It means that
the log buffer is too small and LGWR tries to flush so frequently that
contention is generated.

Thanks !

      Rating: 4
A followup on
      redo log space request  November 01, 2001

Reviewer:  A reader
"
"redo log space requests" - Number of times the active log file is full and
Oracle must wait for disk space to be allocated for the redo log entries. Such
space is created by performing a log switch.
"

- Since redo log file is pre-allocated at OS level, what is above statement
talking about? I assume its just writing in linear fashion, correct? then
wherever the disk head is resting (in case only redo is on the disk), it will
resume from there. Then how does log switch help?

Can you please explain/

Thanks

          Followup:

you are waiting for a log switch -- that is all it is saying.  switching of
logs, like the changing of the guard, is an event.  Things happen.  Things can
prevent it from being fast.

If you've ever seen:

"checkpoint not complete, cannot allocate new log"

or

"archival required, cannot allocate new log"

for example, you've seen a case where a log switch can indeed take a very long
time.  Even when its fast, its not instantaneous -- just fast.

      Rating: 5
clarification
      on one item please ...  November 07, 2001

Reviewer:  A reader
From the 8i reference guide:

LOG_BUFFER
Parameter type: Integer
Parameter class: Static
Default value: Operating system specific. Maximum: 500K or 128K * CPU_COUNT,
whichever is greater
Range of values: Operating system dependent
LOG_BUFFER specifies the amount of memory, in bytes, that Oracle uses when
buffering
redo entries to a redo log file. Redo log entries contain a record of the
changes that have been
made to the database block buffers. The LGWR process writes redo log entries
from the log
buffer to a redo log file.
In general, larger values for LOG_BUFFER reduce redo log file I/O, particularly
if
transactions are long or numerous. In a busy system, a value 65536 or higher is
reasonable.
See Also:
n Oracle8i Designing and Tuning for Performance for more information on setting
this
parameter.
n Your operating system specific Oracle documentation for the default value and
range of
values.

If we have 40 CPU, wouldn't the log buffer size be 40*128k = 5MB? Is this
correct?

thanks

          Followup:

According to the documentation -- yes, I don't have a 40 cpu machine to test
with but yes.

      Rating:
      4
Reader  November 08, 2001
Reviewer:
      Reader  from USA
Tom,

As referenced from the manual, "Maximum: 500K or 128K * CPU_COUNT":

The maximum size of redo_buffer increase as a function # CPU
Is the reasoning behind this:
while redo gets flushes to disk, redo continually gets created and writtn to
remaining part of redo_buffer, the more # of CPU there is, the possibility of
more concurrent processes which leads to higher volume of redo generated, while
LGWR busy.

Is this a resonable conclusion.

Thanks
flushing the redo_buffer to disk.

          Followup:

exactly -- dead on.

The more cpu you have, the more redo log you generate in parallel, the more
there is to cache.

      Rating: 4
Followup on
      Reader from USA  November 09, 2001
Reviewer:
      Andre Whittick Nasser  from Rio de Janeiro, Brazil
Yes, correct. LOG_BUFFER should be proportional to the number of CPUs to avoid
"overcrowding" this area.

Quote from Oracle8i Designing and Tuning for Performance:

" The LOG_BUFFER parameter reserves space for the redo log buffer that is fixed
in size. On machines with fast processors and relatively slow disks, the
processors may be filling the rest of the buffer in the time it takes the redo
log writer to move a portion of the buffer to disk. The log writer process
(LGWR) always starts when the buffer begins to fill. For this reason, a larger
buffer makes it less likely that new entries collide with the part of the buffer
still being written. "

...

" When LGWR writes redo entries from the redo log buffer to a redo log file or
disk, user processes can then copy new entries over the entries in memory that
have been written to disk. LGWR normally writes fast enough to ensure that space
is always available in the buffer for new entries, even when access to the redo
log is heavy. "

.. still, just to refresh:

" The statistic REDO BUFFER ALLOCATION RETRIES reflects the number of times a
user process waits for space in the redo log buffer. This statistic is available
through the dynamic performance view V$SYSSTAT. "

Hope this helps !

      Rating:
      5
Reader  November 09, 2001
Reviewer:
      Reader  from USA
Tom,

Thanks very much

      Rating: 5
Andre, Tom and
      others - Thank you!  November 10, 2001
Reviewer:
      A reader
I wish one statment needs little clarification

When Tom, or someone says - you would never need more than 1MB of log buffer -
specify on what #cpu(Mhz as well), how furiously application is
working/commiting(very hard to know but needed any way). Otherwise as some of
document listed above say - it seem to matter how many cpu you got

Thanks

          Followup:

I never say you never need more then 1mb of log buffer?  Maybe I'm
misunderstanding you here -- but I've never said you would never need more then
1mb, regardless of the number of CPU's

      Rating: 4
thanks Tom, see
      following links  November 11, 2001
Reviewer:
      A reader
Following links suggests log buffer size without regard to cpu:

http://asktom.oracle.com/pls/ask/f?p=4950:8:251760::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:621023586146,%7Bredo%7D%20and%20%7Blog%7D%20and%20%7Bbuffer%7D
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:55812348055
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1724586308922
May be it is my misunderstanding but thats why I was little confused, since
these links do not tell about 128k * #cpu equation for log_buffer size.

Thanks in advance

          Followup:

Umm, I'm not getting the point here?

First -- I cannot tell what comments are yours or someone elses so the context
is a little murky here, I'm assuming "this reader" is the same "reader" as the
comment right above about 1m.  If you actually use a name or provide some
context, it would make more sense.

Second -- so, they didn't talk about 128k * #cpu which is simply the documented
default value for this parameter as stated in the Oracle8i reference manual?
They do talk about how the redo log buffer gets flushed, when you would think
about increasing its size and so on.  So, I'm not understanding the point here.
I do assume people read the documentation and the computation for the default
size really wasn't relevant in the various discussions.

These threads are all about the redo log buffer getting flushed, not its size --
not really even about sizing it.  It would have been "off topic" to just stick
in there "oh by the way, the default size is ....."

      Rating: 5
Tom, thanks for
      all your help  November 11, 2001
Reviewer:
      Sudhir M.  from Cincinnati
I am sorry for expressing incorrectly my thoughts. Any way, I agree, main focus
of the thread was log sync event and I should have stayed with it. Sorry for
that.

Once again, I apologize for that.

I must say I am extremely happy with the service this site provides.

Thanks again

Sudhir

      Rating:
      4
Reader  October 17, 2002
Reviewer:
      A reader
Tom,

log file sync wait time and
log file parallel write are both
waits,waiting for LGWR writing log buffer
redo log. How to determine the
difference in these waits.

Does log file sync happen only when
user commits or as posted by DBWR, and
log file parallel write takes place ,
when there is no commit,
and log buffer is 1/3 full or 3 seconds
interval.

Thanks

          Followup:

log file parallel write takes place inside of LGWR, it is LGWR waiting for this
-- not a user.

If waits for "log file parallel write" are significant then this will show up as
other user wait events (such as long "log file sync" wait times).

So, just cause LGWR waits for the parallel write -- that doesn't mean users are,
so this is one that you have to look elsewhere to see if it is a problem.

      Rating: 4
Log files
      configuration with a few large disks  March 09, 2003

Reviewer:  David Piazza  from San Mateo, CA

Environment is Oracle 9.2.1 and Windows 2K.
We were doing a performance test of an application using load runner, and I
noticed log sync wait events using OEM.  I also saw that "redo buffer allocation
retries", and "redo log space requests" were increasing.  I increased the
log_buffer and increased the log file size and added more groups, and noticed
that there weren't as many log sync wait events and "redo buffer allocation
retries", and "redo log space requests" were increasing at a far less rate.  We
have a high speed RAID 10 SAN, and also two local disks.  I ended up using disks
on the SAN and the local disks for log files(local).  I put all even numbered
groups on one disk(SAN), all odd numbered groups on another(SAN), all second
members on a third disk(local) and archives on another disk (local).  We have a
large amount of disk space with just a few disks.  My Questions are: 1) Since we
are running RAID 10 on the SAN and having data striped on different drives, does
it matter if all the log files are on the same disks on the SAN. 2) I'm thinking
of running three tests, all log files on the SAN, all log files local, and log
files on both the SAN and local drives to see which has better performance.  Do
you have any thoughts on this?

Also, on some of our other applications, we only have four drives and can't
implement odds, evens, odd members, even members and archives on different disks
like you have suggested.  We have a setup like the following:

Disk A: 1P, 3M, 4M
Disk B: 2P, 4P
Disk C: 1M, 2M, 3P
Disk D: Archives

M-member log file
P-primary log file

With this setup, the archiver is reading from a disk that is not currently
having redo information written to the log file.

Also given four disks, do you have any comments on the following setup, with all
members on one disk:

Disk A: 1P, 3P
Disk B: 2P, 4P
Disk C: 1M, 2M, 3M, 4M
Disk D: Archives

M-member log file
P-primary log file

Thanks.

          Followup:

1) well, it sounds like a "disk" on the san isn't a disk really but a big old
striped volume.  I don't know if it matters or not -- the process you are going
through will determine that however (excellent that you are benchmarking)

2) excellent idea, test all, test to destruction.  You are doing the right
things

#2 setup looks more "understandable"

      Rating: 5
When is the LOG
      BUFFER Flushed  March 15, 2003
Reviewer:
      A reader
Tom,

Is this is an accurate statement:

Log buffer is flushed when "any" of the following events occur. (as of both 8i
and 9i)

(1) When 1/3rd full
(2) When 1 MB full
(3) Every three seconds
(4) When a commit occurs
(5) When a checkpoint occurs

I was told that point (1) was not correct as of 9.2; it was 2/3rd. Is is so?

In the light of the above, it seems there is no advantage to creating a log
buffer of more than 3 MB, is there?

Thanks a lot in advance.

          Followup:

I have not heard of any change in that regards.

There are definitely advantages to larger, much larger log buffers.

Think of it as a pipe.  lgwr is responsible for emptying the pipe but may acrue
a large backlog -- while lgwr is writing, others can be filling -- so lgwr may
continously be active writing to disk whilst others are filling it.  If you have
lots of big transactions, a larger log buffer can help the "piping" of data flow
smoothly.  people don't have to wait for lgwr to empty the buffer to get space -
there is always space for them.

March 16, 2003 Arun -- Thanks for the question regarding "Ref
      cursor query", version 9.0.2

      You Asked
Tom
I recently encountered the following code in a procedure:

OPEN ref_cursor FOR
SELECT col1 INTO v_1 FROM t1 WHERE...;

01. If I do not declare v_1 in the current scope, the procedure compiles without
error in 8i but fails to compile in 9i saying invalid identifier v_1. How can
this code compile in 8i?

02. What will this code do when run? Select into v_1 or open a ref_cursor? What
if the query returns multiple rows?

ref_cursor is declared as weakly typed. I was hoping that Oracle will fail to
compile such code.

Thanks

      and we said...
that "INTO V_1" is meaningless -- I'm surprise it accepted it.  INTO :variable
is just syntatic sugar, something implemented by the host environment (PLSQL).
It isn't part of SQL.

PLSQL must just be parsing it out and ignoring it.

In 8i, it must totally ignore it.
In 9i, they must be checking the variables.

In all cases -- it is worse then meaningless since it APPEARS to convey meaning
but does not.  It is a bug in the PLSQL code.

erase the INTO v_1
March 16,
      2003 Bill -- Thanks for the question
      regarding "OMF + a 9i export question", version 9.0.1

      You Asked
Hi Tom
Question re: Oracle Managed Files:
1)I'm curious: How does Oracle 'know' that a file is an OMF file?
is it once you set the DB_CREATE_FILE_DEST and DB_CREATE_ONLINE_LOG_DEST_n
parameters (in init.ora or dynamically w/ ALTER SYSTEM ), oracle will create
OMF's -and if the parms are not set OMF's will not be used ?  -and/or does
Oracle 'know' something internally to tell it that is using OMFs ?

2) Re: 9i export
I know that export parm TABLESPACES in 9i will export a table and its index,
even if the index is in another tablespace
- what about partitioned tables: say table t is partitioned across 5 tablepaces,
will they all be included in the export
-i guess i could test to find out :)
Thanks, as always
Bill

      and we said...
1) It is done by name, this is part of support note  <Note:159888.1>:

...
  This bug fix changes the format of OMF file names on Windows and
  Unix-based systems.  OMF files created before this fix will not
  be recognized as OMF files unless they are renamed to conform to
  the new OMF file name format.  For example, prior to this fix
  OMF file names had the following format:

     File Type       Format           Example
     ---------       ------           -------
     Datafile        ora_%t_%u.dbf    ora_tbs1_2ixfh90q.dbf
     Tempfile        ora_%t_%u.tmp    ora_temp1_6dygh80r.tmp
     Redo log file   ora_%g_%u.log    ora_1_wo94n2xi.log
     Control file    ora_%u.ctl       ora_cmr7t90p.ctl

  Where:
     %t is the tablespace name (possibly truncated)
     %u is an eight character string that guarantees uniqueness
     %g is the online redo log file group number

  A file was considered OMF if its base file name had:

     - an "ora_" prefix
     - and a ".dbf", ".tmp", ".log" or ".ctl" extension

  The new format for OMF file names on Windows and Unix-based
  systems is:

     File Type       Format             Example
     ---------       ------             -------
     Datafile        o1_mf_%t_%u_.dbf   o1_mf_tbs1_2ixfh90q_.dbf
     Tempfile        o1_mf_%t_%u_.tmp   o1_mf_temp1_6dygh80r_.tmp
     Redo log file   o1_mf_%g_%u_.log   o1_mf_1_wo94n2xi_.log
     Control file    o1_mf_%u_.ctl      o1_mf_cmr7t90p_.ctl

  Where:
     %t is the tablespace name (possibly truncated)
     %u is an eight character string that guarantees uniqueness
     %g is the online redo log file group number

  A file is now considered OMF if its base file name has:

     - a "o1_mf_" prefix
     - and a ".dbf", ".tmp", ".log", or ".ctl" extension
     - and an "_" character immediately preceding the extension

2)

<quote>...
The TABLESPACES parameter specifies that all tables in the tablespace be
exported to the Export dump file. This includes all tables contained in the list
of tablespaces and all tables that have a partition located in the list of
tablespaces. Indexes are exported with their tables, regardless of where the
index is stored.
</quote>

it should include the partitioned table according to that.  And tests show:

ops$tkyte@ORA920> create tablespace t1 datafile size 1m;
Tablespace created.

ops$tkyte@ORA920> create tablespace t2 datafile size 1m;
Tablespace created.

ops$tkyte@ORA920> CREATE TABLE t
  2  (
  3    x int primary key,
  4    collection_year int,
  5    y int
  6  )
  7  PARTITION BY RANGE (COLLECTION_YEAR) (
  8    PARTITION PART_95 VALUES LESS THAN (1996) tablespace t1,
  9    PARTITION PART_01 VALUES LESS THAN (MAXVALUE) tablespace t2
 10  )
 11  ;
Table created.

ops$tkyte@ORA920> !exp userid=/ tablespaces=t1

Export: Release 9.2.0.1.0 - Production on Tue Jan 21 12:00:43 2003
Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.
Connected to: Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
Export done in WE8ISO8859P1 character set and AL16UTF16 NCHAR character set

About to export selected tablespaces ...
For tablespace T1 ...
. exporting cluster definitions
. exporting table definitions
. . exporting table                              T
. . exporting partition                        PART_95          0 rows exported
. . exporting partition                        PART_01          0 rows exported
. exporting referential integrity constraints
. exporting triggers
Export terminated successfully without warnings.

ops$tkyte@ORA920>
ops$tkyte@ORA920> drop table t;

Table dropped.

ops$tkyte@ORA920> drop tablespace t1;

Tablespace dropped.

ops$tkyte@ORA920> drop tablespace t2;

Tablespace dropped.

that is does...

You know -- I think they invented oracle managed files for me!  gotta love the
create tablespace/drop tablespace ease there....

Review &
      Followup

      Rating:
      5
agreed  January 21, 2003
Reviewer:
      Bill
Once i learned about OMF's, i use them all the time in my Development
environments - so nice ... so simple

      Rating: 2
i did not get
      the format !! -anything i missed out  March 16, 2003

Reviewer:  reader  from India
tom,

i used dbca to install a new db, i set db_create_file_dest as c:\oracle\omf
(only change i did to my init parameter at the time of db creation) - i thought
i would get my datafiles but i did not get in the omf directory neither in the
oradata with the above specified format.

i changed the parameter using the edit window that pop up when we click the
initilization parameters button on dbca.

anything i missed out?

Thanks ...

          Followup:

the datafiles Oracle uses for system, tools, etc are not OMF.  on step 6 out of
7 (using 9iR2 dbca anyhow), you have the opportunity to see these filenames and
change them if you like.

      Rating: 5
OMF and
      temp  November 24, 2003
Reviewer:  Shankar
       from Houston, TX
When I create a database after setting DB_CREATE_FILE_DEST and
DB_CREATE_ONLINE_LOG_DEST_1 parameters, will oracle create a dafault temporary
tablespace temp file in DB_CREATE_FILE_DEST destination? Or do I have to create
one after the database is created? Thanks.

March 16, 2003 Ganesh -- Thanks for the question regarding
      "Developer privileges versus features", version 8.1.7

      You Asked
I work in a team with around 6 developers. We constantly coordinate with other
similar teams. Each team works does its development in a different database. We
follow the typical dev/QA or test/prod  setup for development. There is one
architecture team that is responsible for security/space etc. issues for all the
databases. The architecture team is very protective of its territory. But too
often it goes a bit too far. Recently I requested that the 'DEV' database be
given the ability to create own data types. Was told to come back with the pros
and cons of requiring that privilege so that they can analyze and see whether it
is really required. Too bad that Oracle introduces such neat features and
developers do not have access to it.

Anyway my question is, first of, do you agree with my stance. Secondly, I feel
bad asking this, but what are the pros and cons(not sure there are any) of
creating own data types.

Thanks much for your time.

Regards,
Ganesh.

      and we said...
well -- I agree with them.

Was told to come back with the pros
and cons of requiring that privilege so that they can analyze and see whether it

is really required.

does not seem onerous or horrible.  Actually, it sounds like a good exercise
that will decrease "computer science projects" (i've seen tons of those, use a
feature cause it is "cool") and increase understanding of why people are doing
what they are doing.

The pro's are all documented well enough in:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76976/toc.htm
Inside of PLSQL -- using TYPES to extend PLSQL -- i've not seen any cons, in
fact, I really dig them and use them all of the time, constantly.

Outside of PLSQL -- to persistently store data in tables -- I am more cautious.

Oracle Text (interMedia) uses this facility.
XMLType in 9i uses this concept heavily.
Spatial, Audio, Image, Video -- these types use it heavily.

If you model your approaches after that -- using it for some really special
datatype to be stored in a table, then it is very useful, very handy.  But,
quite esoteric.  We in the field are generally not in need of creating a brand
new datatype in that fashion.  Rather, we use the types others have created for
us.

If you have my book "Expert one on one Oracle" -- i do discuss this as well (the
cons).  In there I describe how I use types in PLSQL constantly but have yet to
actually create a table that uses a type I myself constructed.  I use OTHERS
types -- the types that come with the database or the types that someone builds
for general purpose use.  But for 99.99% of the developers out there -- creating
a new datatype is something we don't do (we have no need to).

So, I would justify the use of CREATE TYPE for extending the PLSQL programming
language, giving it many salient features borrowed from other languages like
Java or C++.
Review & Followup

      Rating: 5
Well
      said  March 16, 2003
Reviewer:
      A reader
But the request was to have those privileges in DEV instance which is freeforall
for developers. Well anyway thanks again for your perspective. I do have your
book and will go thru the appropriate material.

Thanks,
Ganesh.

          Followup:

to what end is having privs in DEV that you cannot use in the real world?

March
      16, 2003 Kelly -- Thanks for the
      question regarding "RE: Ask Tom Submission, capture rowid after insert",
      version

      You Asked
Tom,
I would like to implement an
oracle trigger to store the
rowid on an update or insert
on one certain table into
a different table with the table name.
Then I have ajob that will
select out that row based
on rowid and table_name and
write to a disk file.

My chg table looks like
row_id ROWID
table_name varchar2(55)

Select * from table2, chg
where table2.rowid = chg.row_id;
This gives me an error of invalid rowid.

I have done this in the past with
different versions of oracle but
it doesn't seem to work with 8.1.5.
What am I doing work?

      and we said...
It sounds like CHG might have rowids from >1 table?  If so, that
would be the cause.  Here is an example that shows in general,
the above works but if you have rowids from >1 table -- it won't
(using your exact query).

tkyte@ORACLE> create table chg( row_id rowid, table_name
varchar2(30) );
Table created.

tkyte@ORACLE> create table table2( x int );
Table created.

tkyte@ORACLE> create or replace trigger table2_aifer
  2  after insert on table2 for each row
  3  begin
  4    insert into chg values ( :new.rowid, 'TABLE2' );
  5  end;
  6  /

Trigger created.

tkyte@ORACLE> insert into table2 values ( 1 );
1 row created.

tkyte@ORACLE> commit;
Commit complete.

tkyte@ORACLE> select * from table2, chg where table2.rowid =
chg.row_id
  2  /

         X ROW_ID             TABLE_NAME
---------- ------------------ ------------------------------
         1 AAADldAACAAAAEMAAA TABLE2

Now, that works OK since all of the rowids are from table2....
Now, lets add another:

tkyte@ORACLE> insert into chg select rowid, 'DUAL' from dual
  2  /

1 row created.

tkyte@ORACLE> select * from table2, chg where table2.rowid =
chg.row_id
  2  /
ERROR:
ORA-01410: invalid ROWID

And I get your error -- the rowid from DUAL is not valid when
used with TABLE2.  To fix that we can:

tkyte@ORACLE> select * from table2, chg where table2.rowid =
chg.row_id and chg.table_name = 'TABLE2'
  2  /

         X ROW_ID             TABLE_NAME
---------- ------------------ ------------------------------
         1 AAADlgAACAAAAEMAAA TABLE2

tkyte@ORACLE> select *
  2    from chg
  3   where not exists
  4           ( select null from table2 where rowidtochar(rowid)
= rowidtochar(chg.row_id) )
  5  /

ROW_ID             TABLE_NAME
------------------ ------------------------------
AAAADCAABAAAAVUAAA DUAL

Or, if you have only 1 tables worth of rowids in the table --
you can find the problem rowids by the above (someone might have
deleted a row after it was inserted -- that would do it as
well).

Beware -- there are features in Oracle8i, release 8.1 that will
cause a rowid to change for a row after an UPDATE (partitioned
tables with row movement and index organized tables).  That
would cause this as well.  You might consider using the primary
key in the future.

Review &
      Followup

      Rating:
      4
  January 13, 2003
Reviewer:
      A reader
Tom ,
You said,
<start quote>
Beware -- there are features in Oracle8i, release 8.1 that will cause a rowid to
change for a row after an UPDATE (partitioned tables with row movement and index

organized tables).  That would cause this as well.  You might consider using the
primary key in the future.
<end quote>

Besides index organize tables and paritioned tables, are there any other
features that can cause a rowid to change for a row?

Thanks

          Followup:

not that I am aware of currently -- but that is subject to change of course.

      Rating: 1
I'm just using
      this because I don't see on the Ask Tom page a way to
      ask!!  January 13, 2003
Reviewer:
      A reader
Does Oracle support PrepareStatement.getParameterMetaData()?
Documentaion say yes, but when I run it with driver 1.4 I get "Unsupported
Feature" error.

          Followup:

didn't you see the home page where it must have said:

"Sorry, I have a large backload -- try later"??

anyway - if you get a chance (eg: i'm taking questions) put together a testcase
(smallest possible piece of standalone java code you can come up with) that I
too can compile and run to reproduce.

      Rating: 4
Another
      way....(probably)  January 14, 2003
Reviewer:
      Anirudh Sharma  from New Delhi, INDIA
Hi Tom,
or we can have the column of the table(into which we wish to insert the rowid
and table information) as varchar2(18) and then use rowidtochar function in the
query on the original rowid column on the table on which the trigger is created.

Anna itext_usr@SUCC1> create table rowid_table_info(tbl_name varchar2(20),id_row
VARCHAR2(18));

Table created.

Anna itext_usr@SUCC1> create trigger trigg_1 after insert or update on ann for
each row
  2  declare
  3  v_tbl_name VARCHAR2(20);
  4  v_id_row   ROWID;
  5  BEGIN
  6  v_id_row := :new.rowid;
  7  v_tbl_name := 'ANN';
  8  INSERT INTO rowid_table_info VALUES (v_tbl_name,v_id_row);
  9  END;
 10  /
and then same trigger on another table 'ani_test'

Anna itext_usr@SUCC1> ed
Wrote file afiedt.buf

  1  create or replace trigger trigg_2 after insert or update on ani_test for
each row
  2   declare
  3   v_tbl_name VARCHAR2(20);
  4   v_id_row   ROWID;
  5   BEGIN
  6   v_id_row := :new.rowid;
  7   v_tbl_name := 'ANI_TEST';
  8   INSERT INTO rowid_table_info VALUES (v_tbl_name,v_id_row);
  9*  END;
 10  /

THEN query like
 select tbl_name,id_row from rowid_table_info r, ann a where
r.id_row=rowidtochar(a.rowid);

TBL_NAME             ID_ROW
-------------------- ------------------
ANN                  AAABDAAAHAAAQEXAAB

          Followup:

obviously you can?  I mean -- you did didn't you??

why you want to store a rowid in a string is another question...

      Rating:
      3
  March 13, 2003
Reviewer:  j.

hi tom,

lets imagine the following simplified scenario: there 's a view, joining one
base table and another dependent table. there is an instead-of-trigger on this
view. this trigger propagates DML against the view to their base table and the
dependent table as well:

-- base table view --
select B.RowID  B_RID, B.x,
       D1.RowID D_RID, D.y
from   <base table>      B, -- base table
       <dependent table> D  -- dependent table
where  -- join B and D

-- instead-of-trigger's body for base table view --
update <base table>
...

update <dependent table>
set    ...
where  B_RID = :OLD.D_RID; <-- PROBLEM???

can it happen here, that the record addressed through :OLD.D_RID is deleted and
a new/OTHER one with DIFFERENT identity gets inserted (re)using the SAME old
rowid? is there any BEST way to make sure, that this won't happen (except of
using primary keys instead of rowids)?

          Followup:

if (updating) ....

jumps immediately into mind (or just use an update trigger instead of using an
"instead of update or delete" trigger...

      Rating:
      1
  March 16, 2003
Reviewer:  j.

sorry tom, but I really didn't get it. and I have to admit, that the view
mentioned includes other views (rather than tables) - so "normal" triggers can't
be used.
what I just wanted to know: can "OLD:D_RID" - derived from view and used by its
instead-of-trigger to propagate all DML issued against this view across its
tables/views underneath - become invalid due to DELETION a certain record and
INSERTATION of a NEW ONE with the SAME rowid by a concurrent session?

          Followup:

so do it by primary key then.

Yes, I suppose it is theoretically possible -- improbable but possible.

      Rating:
      4
  March 17, 2003
Reviewer:  j.

March 16, 2003 Ramasamy -- Thanks for the question regarding
      "CURSOR_SHARING SIMILAR Vs FORCE", version 8.1.7

      You Asked
Dear Tom,

Thanks for your answers and sorry for multiple small questions.

1. Could you please tell me the exact difference between
   SIMILAR Vs FORCE on cursor_sharing parameter.

2. I have studied in this site, Group by will not
   guarantee Order of rows. To be sure about the order of rows
   you must use Order by clause. I am 100% agree that.
   But i did a different combination of that topic to show
   it to my colleauge. I couldn't find. It will be really great
   if you could mention the link.

3. I am Being new to DBA related , How will i find ORACLE_HOME
   value from SQL*PLUS for the database connected?.

Thanks In Advance,
Ramasamy T

      and we said...
1)
see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6090129761218
for an example but in a nutshell:

cursor_sharing=force will autobind every literal, all of them, and generate the
same plan for everything (a single plan)

cursor_sharing=similar will recognize when substituting a bind variable COULD
radically change a plan (eg: "select * from t where x = 5" would not use an
index since 99.9% of the data is 5, but "select * from t where x = 6" would use
an index -- just putting in a bind would be fatal) and will generate multiple
plans based on the bind variable value.  It recognizes that more then one plan
is necessary and permits that.

2) easy enough -- I can come up with dozens of them but a simple, small two row
example is compelling:

ops$tkyte@ORA920> create table t ( x int not null, y int );

Table created.

ops$tkyte@ORA920> create index t_idx on t(y,x);

Index created.

ops$tkyte@ORA920> insert into t values ( 1,2 );

1 row created.

ops$tkyte@ORA920> insert into t values ( 2,1 );

1 row created.

ops$tkyte@ORA920> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA920> select x, y, count(*)
  2    from t
  3   group by x, y
  4  /

         X          Y   COUNT(*)
---------- ---------- ----------
         2          1          1
         1          2          1

ops$tkyte@ORA920> select x, y, count(*)
  2    from t
  3   group by x, y
  4   order by x, y
  5  /

         X          Y   COUNT(*)
---------- ---------- ----------
         1          2          1
         2          1          1

ops$tkyte@ORA920>

you want data ordered?  You had BETTER use an order by, if you do not -- you
lose.

3) you will not, in general that is not available to you.  The database knows
what it's oracle_home is and that is all that is needed ;)

March 16, 2003 Adrian -- Thanks for the question regarding "Your
      latest Oracle Magazine article", version 8.1.6

      You Asked
Tom

I read with interest the March/April Oracle Magazine and in particular the
question on using PL/SQL tables as pre-loaded replacements for database lookups.
Your response was as I expected inasmuch that you suggested using a straight SQL
join to the database lookup table rather than a procedural approach.

However, I must say your comparison example was way skewed towards SQL and you
did not, to coin a favourite phrase of your's, "compare apples with oranges".
You provided a straight SQL insert statement, joining the staging table to the
lookup table in one hit. But your PL/SQL comparison was to loop the staging
table a row at a time and fetch a lookup value from a pre-loaded index-by table
as you looped.

Now surely this is nowhere near a fair comparison. Sure, the SQL approach would
beat any PL/SQL one (you didn't mention that the index-by table approach is
great for large data loads using tools such as SQL*Loader), but at the least the
PL/SQL should have had one embedded INSERT .. SELECT statement in it, containing
the index-by table lookup.

I just thought I'd point this out, as I know you are a stickler for fair tests
and in this case I thought you failed to offer one.

Regards

Adrian

      and we said...
this quote in that article:

http://otn.oracle.com/oramag/oracle/03-mar/o23asktom.html
...
This single insert does the same exact thing in a mere fraction of the time.
Almost any time you can remove procedural code and do the same thing in a single
SQL statement, you'll be better off. There's less code to type and maintain, and
it's generally much faster, to boot.
......

was my bottom line point -- the concept provided by the questioner "should I

a) lookup using select into
b) lookup using a plsql index by table

" was answered with

c) none of the above, do it with SQL in the first place.

for the entirely long winded version

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5408706816128
(and if my book ever gets published, since WROX just went out of business -- I
actually compared a DOZEN different approaches to this from:

o select into
o using plsql
o adding bulk processing (array fetches)
o using heap, iot and single table hash clusters
o doing it in a single statement using the above.

In the end the fastest, most concise and least resource intensive approaches
were:

create or replace procedure
array_fetch_hash_insert( p_arraysize in number default 100 )
as
    type array is table of all_objects.object_name%type
           index by binary_integer;
    l_key_col array;
    cursor c is select object_name from all_objects;
begin
    open c;
    loop
        fetch c bulk collect into l_key_col limit p_arraysize;

        -- whatever processing you feel you truly cannot do in SQL goes here

        forall i in 1 .. l_key_col.count
            insert into built_by_us ( key_col, key_val )
            values ( l_key_col(i),
                     (select key_val
                        from lookup_hash
                       where key_col = l_key_col(i))
                   );
        exit when c%notfound;
    end loop;
    close c;
end;
/

If you truly needed procedural code (I find over and over -- you don't).  And
the fastest, the winner:

    insert into built_by_us
    select object_name, key_val
      from all_objects, lookup_hash
     where all_objects.object_name = lookup_hash.key_col(+);

I used a single table HASH cluster to store my lookup data -- no indexes, just a
hash key lookup....
Review & Followup

      Rating: 4
You seem to
      think I don't agree with you...  March 16, 2003

Reviewer:  Adrian Billington  from UK
Tom

As per my quote "Sure, the SQL approach would beat any PL/SQL one..." you should
be able to see that I agree with you. That wasn't my point. My point was that to
the originator of the question you answered for Oracle Magazine (who is clearly
a novice in this area), it will now seem that the use of PL/SQL tables to store
lookups is a completely worthless exercise. The two sets of results you showed
were MASSIVELY skewed in favour of SQL. You, I and most of the Oracle community
know that SQL will beat PL/SQL for this and almost any other application, but it
could be far less marginal, and had you compared "apples with oranges", you
could have demonstrated this. I know you have your mantras, and we all agree
with them, but that doesn't rule out the possibility that sometimes there might
just be the need to do it slightly differently.

Anyway, the only reason I'm making a deal of this is because your tests and
demonstrations are always fair and spot on. There, with that compliment I've
just paid you it looks like I've redeemed myself and I should now go back on
your Christmas card list... ;)

Regards

Adrian

          Followup:

I concurr with you actually -- it is hard in the print column to make it all
fit.  In the book I was working on -- this TOPIC alone consumed 10 pages in a
section entitled "looking it up"

the last bit of it was to be:

So - just to wrap this all up.  If you find yourself doing lots of single row
processing, with lookups (as we all do at one time or another) realize that

o Procedural lookups can help you go 50-100% faster but
o Bulk processing will start you off in the order of magnitude increase in
performance and
o Doing it in a single SQL statement if possible will not only reduce the code
you must write, it'll be among the fastest ways to do it in most cases

The trick of inserting the select above is the most efficient procedural
approach (and memory de-intensive as well)...

      Rating: 3
It's a shame
      about the book...  March 17, 2003
Reviewer:
      Adrian Billington  from UK
Tom

It's a real shame about Wrox and the book - surely there must be a potential
publisher out there...? Now you've tempted us with the samples, there's no
turning back ;).

Actually, I always wondered why Oracle Press wasn't publishing your stuff...

Regards

Adrian

      Rating: 2
Wrox
      books  March 17, 2003
Reviewer:  Tarry
       from Holland
I've always been a sucker for worx books, it's a pity the trying times are
taking tolls in tons.........

I've pre-ordered your stuff, Tom.You'll find a publisher alright.
So I guess i can wait a little longer.............

March 17, 2003 Chandru -- Thanks for the question regarding "Use
      of optimal", version 8.0.5

      You Asked
What exactly Oracle does when I set optimal parameter for my rollback segments
(rbs)? For example, When Oracle assigns a rbs (with optimal value set) to a
transaction, at that time does Oracle check the optimal value and shrink the rbs
to that value and then puts the rollback entry into that rbs? My question is
when does Oracle shrink rbs with optimal value set? Does oracle shrink it the
next time a rbs is assigned to a transction or once the transaction is committed
or rollback?

Assume that Oracle shrinks a rbs to the optimal value, does it means that the
data in those extents/blocks were lost forever? If that is the case, could it be
a reason for "snapshot too old" error? Do you recommend to set optimal value for
rbs? What are the "real" benefits in setting optimal value for rbs? Do the
benefits outweigh performance? I would imagine Oracle has to do more work behind
the scene to shrink. If optimal is not set, what do we lose?

I would greatly appreciate if you could find time to take up my question.

Thanks so much.

      and we said...
I hate (despise, as in "don't like even a little") optimal.

My advice would be "pretend it doesn't exist"

Optimal kicks in when you LEAST want it to and removes data you might actually
need (to avoid 1555's)....

see

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/c02block.htm#4699
for the when/how of them...

me, i let them grow and keep them there.  they must want to be that big.
Review &
      Followup

      Rating: 4
Good to
      know.  March 16, 2003
Reviewer:  Chandru
       from Harlingen, TX
Thanks again for your response.

Just for the sake of curiosity, could you please clarify the following.

My question is when does Oracle shrink rbs with optimal value set? Does oracle
shrink it the next time a rbs is assigned to a transction or once the
transaction is committed or rollback?

Thanks.

          Followup:

just click on the link i provided above ^^^^

it tells you exactly what you are asking.

      Rating: 4
Thanks
      gain.  March 16, 2003
Reviewer:
      A reader
I read the link provided by you and got it clarified. Please ignore my previous
query.

Chandru

March 17, 2003 -- Thanks for the question regarding "Password
      file", version 8.0.5

      You Asked
I use terminal service from remote to access my databases on prod server at
work. I use my work-domain login/password to first get into terminal service.
Once I connect to terminal service, I am inside my company network (inside the
firewall). I then telnet to server and do the DBA activities including
startup/shutdown as authenticated by OS user who is in DBA group.

Oracle doc says that if you have a secured connection (I believe we have), and
if you have OS authentication (yes, I do), one can perform privileged admin like
startup and shutdown as OS user. This is exacly what I do and understand. Oracle
doc then says that if we do not have secured connection, use password file. Here
I need help in understanding what this means. Does it mean that I can logon to
my database from remote say using scott/tiger if scott is added to the password
file by granting sysdba to scott? Why should we allow scott to startup and
shutdown as I want only my DBAs do. What I am driving at is why Oracle provides
password file at all and encourage access to databases in a non-secure
connection? Thanks in advance in clarifying this.

      and we said...
Why should we allow scott to startup and
shutdown as I want only my DBAs do.

who said anything about granting scott sysdba (besides you of course).

The remote password login allows your DBAs to connect from sqlplus running on
THEIR desktops to the server using their credentials securely over a network.
So they can:

connect dba_user/dba_pass@remote_db as sysdba

to prevent the need to have them log directly INTO the remote machine itself.
It facilitates the use of GUI tools for example that run on their desktop and
connect to a remote server.  It permits tools like OEM to manage many database.

The "secure" thing is a comment about authenticating users via OS authentication
over a network.  In most cases -- that is not a very secure thing to do.  This
permits the flexibility of remote administration without having to rely on OS
authentication.
Review & Followup

      Rating:
      5
Thanks  March 16, 2003
Reviewer:
      Chandru  from Harlingen, TX
<quote>
to prevent the need to have them log directly INTO the remote machine itself
<quote>

This clarified the issue. THANKS -- Chandru

      Rating: 4
A
      doubt  March 16, 2003
Reviewer:  Raja
       from Madras, India
<quote>
The remote password login allows your DBAs to connect from sqlplus running on
THEIR desktops to the server using their credentials securely over a network.
So they can:

connect dba_user/dba_pass@remote_db as sysdba

to prevent the need to have them log directly INTO the remote machine itself.
<quote>

In 8.0.5, one cannot perform startup and shutdown using sqlplus. I have to user
svrmgrl to do the same. So, how what you said above is useful? Could you please
clarify a little more for me to understand remote autentication using pwd file
facility in this case? Thanks.

          Followup:

SVRMGR> connect ops$tkyte/tiger@ora806.us.oracle.com as sysdba;
Connected.

it works in svrmgrl as well.

      Rating:
      5
EXCELLENT  March 29, 2003
Reviewer:
      karunasree  from INDIA
I have been following Tom's articles in the site
ORACLE-BASE from the last one year.
I have learnt so many things from this site.
Thanks alot to Tom.

March 17, 2003 sanjai -- Thanks for the question regarding "How
      to create csv file from oracle db", version 8.1.6

      You Asked
Hi Tom,

Here is what I am trying to do.

1. We have two databases here lets say d1 and d2. The structures in both the
dababases are different but the data is same.
For example. In d1 we have emp and dept table while in d2 we just have one table
emp_dept (having all the columns from emp, dept of d1).

2. I am asked to write PL/sql code that creates csv file one for each table from
database d1 on a nightly basis (using dbms_job). The record struture of csv file
should match with the table structure of d2. I need to pull the data from emp,
dept table in such a way that the csv file has record that matches with emp_dept
table of d2 database.

3. I also need to write a PL/SQL code that pulls the data from the csv file and
insert it into d2. The PL/SQL should make sure if the record is csv file already
exists in d2 then it should update or else it should insert.

4. Once the data is inserted in d2 then I need to send this csv file on a ftp
server.

Can you please give me your suggestions to achieve this functionality.

Thank a ton.

      and we said...
see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:235814350980
for the program to dump the data, you'll just give it a query

select emp.*, dept.*
from emp, dept
where emp.deptno = dept.deptno;

(just join and pick off whatever fields you want)

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:464420312302
for the code to load the table on the other end.  You'll need to modify it to
"merge" but basically its there.

As for #4 -- thats upto you -- thats outside of the database.  You could
certainly use a java stored procedure to do it, that works.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1615330671789
for how 9i will obviate the need for steps #2 and #3 with external tables and
the new merge command.

Review &
      Followup

      Rating: 2
dump_csv
      function referenced  November 20, 2001
Reviewer:
      Mohammed Ahmed  from Arabia
Tom ,

The dump_csv function referenced by first link do not work.
it gives error "ORA-06571: Function DUMP_CSV does not guarantee not to update
database" when I invoke it this way

select dump_csv( 'select * from all_users where rownum < 5', ',', '/tmp',
'test.dat' ) from dual;

When i invoke it this way:

exec apps.dump_csv( 'select * from all_users where rownum < 5', ',', '/tmp',
'test.dat' );

it gives me error:
ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00221: 'DUMP_CSV' is not a procedure or is undefined
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

What to do?

Ahmed,

          Followup:

it is a function -- functions in general are NOT TO be called from SQL -- that
is a special feature, that if they follow some rules they CAN be called from
SQL.

You know, my example shows you exactly how to call this from sqlplus:

ops$tkyte@8i> declare
  2      l_rows    number;
  3  begin
  4      l_rows := dump_csv( 'select *
  5                             from all_users
  6                            where rownum < 5',
  7                          ',',
  8                          '/tmp',
  9                          'test.dat' );
 10      dbms_output.put_line( to_char(l_rows) ||
 11                            ' rows extracted to ascii file' );
 12  end;
 13  /
4 rows extracted to ascii file

PL/SQL procedure successfully completed.

Its a function, use it as such.

If you must put it on a single line, then call:

exec dbms_output.put_line( apps.dump_csv( 'select * from all_users where rownum
< 5', ',', '/tmp', 'test.dat' ) )

      Rating: 5
How to create
      csv file from oracle db  November 21, 2001

Reviewer:  sanjai  from MI, USA
Thanks Tom.

How can I use dbms_job for executing the function dump_csv so that it creates a
csv file for certain tables everyday.

          Followup:

Easy, just schedule it!

Here is what I would do (so we can maximize shared sql reuse -- a certain habit
of mine).

create table dump_csv_parameters
( job_id int primary key,
  query  clob,
  separator varchar2(5),
  dir  varchar2(255),
  filename varchar2(255),
  last_time date,
  last_row_count number
);

we will use this table to store the parameters for a dump_csv call.  Also, for
ease, we'll stuff the last time run and the last count of rows in here as well
-- so we can easily verify row counts and times and such...

Next, we'll create a small interface routine to read the parameters for a given
job and run dump_csv:

create or replace procedure run_dump_csv( p_job_id in number )
as
    l_cnt number;
begin
  for x in ( select * from dump_csv_parameters where job_id = p_job_id )
  loop
     l_cnt := dump_csv(dbms_lob.substr(x.query,32000,1), x.separator, x.dir,
x.filename);
     update dump_csv_parameters
        set last_time = sysdate, last_row_count = l_cnt
      where job_id = p_job_id;
  end loop;
end;
/

And now we can actually schedule the job itself.  Here we schedule every night
at midnight.  Note how we pass the VARIABLE job -- not a VALUE for a job -- into
the procedure (if you are interested in how this all works -- I have a chapter
on it in my book showing how the job queues actually do their stuff).  When the
job is run, it'll be passed its unique job ID.  If we schedual all jobs in our
system this way -- we'll have exactly one query in the shared pool -- "begin
run_dump_csv( JOB ); end;" -- instead of

begin dump_csv( 'select * from t1', '|', '/tmp/', 't1.dat' ); end;
begin dump_csv( 'select * from t2', '|', '/tmp/', 't2.dat' ); end;
....

declare
   l_job  number;
begin
   dbms_job.submit( l_job,
                   'run_dump_csv( JOB );',
                    trunc(sysdate)+1,
                   'trunc(sysdate)+1' );

   insert into dump_csv_parameters
   (job_id, query,separator,dir,filename)
   values
   ( l_job, 'select * from emp', '|', '/tmp/', 'emp.dat' );

   commit;
end;
/

and when this job runs, it'll create the file and update the table for us --
every night at midnight....

      Rating:
      3
dump_csv_parameters  March 17, 2003

Reviewer:  Tasneem Mia  from South Africa
Thanks for the procedures and function. It is quite useful. However, I have 2
problems with the dump_csv_table:
insert into dump_csv_parameters(job_id,query,separator,dir,filename)
   values(l_job,'SELECT pin,to_char(start_time,'yyyymmdd
hh:mi:ss'),duration,service_code from billing_record','/tmp','tas.csv');
1) it does not accept the to_char(start_time,'yyyymmdd hh:mi:ss') formatting in
the query part. I have to select just start_time which does not give me all the
information which I need.How can I fix this?
2)filename: I need the filename in the format (sysdate-2)||.csv eg
2003MAR13.csv. I have to update the table again to change the date format to
what I want. This is not very effective as the filename appears as (sysdate-3)
and not (sysdate-2). here is what I add:
update dump_csv_parameters
   set  FILENAME= (to_char((sysdate-2),'YYYYMONDD')||'.csv')
   where job_id = (select max(job_id) from dump_csv_parameters);

Your assistance will be most highly appreciated as I cannot get assistance from
any other sources.
Thanking you kindly.

          Followup:

1) you do know that in order to put a quote in a character string literal -- one
needs to use ''

values(l_job,'SELECT pin,to_char(start_time,''yyyymmdd hh:mi:ss''), duration,
service_code from b

2) sounds like you might want a custom DUMP_CSV routine then eh?

Don't know why you cannot get help from other sources as this is somewhat
"programming 101" stuff?  #1 is trivial to fix.  #2 -- i would suggest you
modify the dump_csv routine to be:

 13      l_separator     varchar2(10) default '';
 14      l_cnt           number default 0;
         l_filename      varchar2(255);
 15  begin
         execute immediate 'select ' || p_filename || ' from dual'
            INTO l_filename;
 16      l_output := utl_file.fopen( p_dir, l_filename, 'w' );

that way, if you insert:

...  from billing_record','/tmp','to_char(sysdate,''yyyymondd'')||''.csv''');

then as runtime, we'll dynamically execute a query that looks like:

select to_char(sysdate,'yyyymondd')||'.csv' from dual;

into your l_filename variable.  If you wanted a fixed filename, you would
insert:

...  from billing_record','/tmp','''tas.csv''');

so the query would be:

select 'tas.csv' from dual;

instead.

 17

      Rating: 4
Problem
      resolved  March 24, 2003
Reviewer:  Tasneem
      Mia  from South Africa
Hi, Tom
Thank you so very much for responding to my email.In the interim however, I
managed to resolve my problems by doing the following:
CREATE OR REPLACE  PROCEDURE RUN_DUMP_CSV
 ( p_job_id in number )
   as
    l_cnt number;
begin
update dump_csv_parameters
      set filename = (select to_char((sysdate-2,'YYYYMONDD') ||'.csv' from dual)
      where job_id = p_job_id;
  for x in ( select * from dump_csv_parameters where job_id = p_job_id )
  loop
     l_cnt := dump_csv(dbms_lob.substr(x.query,32000,1), x.separator, x.dir,
                                    x.filename);
     update dump_csv_parameters
      set last_time = sysdate, last_row_count = l_cnt
      where job_id = p_job_id;
  end loop;
end;

This gives me the filename in the required format.I was updating the filename in
the schedule procedure instead of the run_dump_csv procedure.
Thanks once again.

March 17, 2003 San -- Thanks for the question regarding "CLOB",
      version 8.1.5

      You Asked
Tom:

We are trying to store XML pages into CLOB datatype. How do I
make a serch for a specific string on that XML page? Can we use
Inter Media or some thing..if yes, please let me know How?

Regards,
San

      and we said...
I asked Joel Kallman, our resident interMedia expert to take a
look at this.  He gave this very detailed example that shows how
to use interMedia to index XML and do section searching on the
attributes contained within an XML document:

 InterMedia Text supports indexing of XML via
 the specification of a section group.  A section group
 is a collection of predefined sections of a document.

 interMedia Text sections let you search for text
 within a particular named section, rather than
 across an entire document.  This can dramatically improve
 the accuracy of searching across a set of tagged documents.

 1) Firstly, create the table to store our XML documents

create table employee_xml(
    id number primary key,
    xmldoc clob )
/

 2) Insert a sample document (the DTD is not required)

insert into employee_xml values( 1,
'<?xml version="1.0"?>
<!DOCTYPE employee [
<!ELEMENT employee (Name, Dept, Title)>
<!ELEMENT Name (#PCDATA)>
<!ELEMENT Dept (#PCDATA)>
<!ELEMENT Title (#PCDATA)>
]>
<employee>
<Name>Joel Kallman</Name>
<Dept>Oracle Service Industries Technology Group</Dept>
<Title>Technologist</Title>
</employee>');

 3) Create our interMedia Text section group called
    'xmlgroup', Add the tags Name and Dept to the section
      group.
    (Caution: in XML, tag names are case-sensitive, but
    tag names in section groups are case-insensitive)

begin
    ctx_ddl.create_section_group('xmlgroup',
'XML_SECTION_GROUP');
    ctx_ddl.add_zone_section( 'xmlgroup', 'Name', 'Name' );
    ctx_ddl.add_zone_section( 'xmlgroup', 'Dept', 'Dept' );
end;
/

 4) Create our interMedia Text index, specifying the section
      group we created above.  Also, specify the
      null_filter, as the Inso filter is not required.

create index employee_xml_index on employee_xml( xmldoc )
    indextype is ctxsys.context
    parameters( 'filter ctxsys.null_filter section group
xmlgroup' )
/

 5) Now, execute a query, searching for a name within a
      specific section
select id from employee_xml where
contains(xmldoc, 'Joel within Name') > 0;

 6) Only non-empty tags will be indexed, but not the tag names

      themselves. Thus, the following queries will return
      zero rows.

select id from employee_xml
 where contains(xmldoc, 'title') > 0;

select id from employee_xml
  where contains(xmldoc, 'employee') > 0;

 7) But the following query will locate our document, even
      though we have not defined Title as a section.

select id from employee_xml where contains(xmldoc,
'Technologist') > 0;

 Let's say you want to get going right away with indexing XML,
and don't want to have to specify sections for every element in
your XML document collection.  You can do this very easily
by using the predefined AUTO_SECTION_GROUP.  This section
group is exactly like the XML section group, but the
pre-definition of sections is not required.  For all
non-empty tags in your document, a zone section will be created
with the
 section name the same as the tag name.

 Use of the AUTO_SECTION_GROUP is also ideal when you may not
know in advance all of the tag names that will be a part of
your XML document set.

 8)  Drop our existing interMedia Text index.

drop index employee_xml_index
/

 9) And this time, recreate it specifying the
      AUTO_SECTION_GROUP.
    We do not need to predefine the sections of our group, it
is
    handled for us automatically.

create index employee_xml_index on employee_xml( xmldoc )
    indextype is ctxsys.context
    parameters( 'filter ctxsys.null_filter section group
ctxsys.auto_section_group' )
/

 10) And once again, we should be able to locate our document
using a section search

select id from employee_xml
where contains(xmldoc, 'Technologist within Title') > 0;

 interMedia Text in 8.1.6 and 8.1.7 has added many exciting
features in support of XML.  For example, AUTO_SECTION_GROUP
also automatically indexes tag attributes.  Additionally, in
8.1.6 and later, you can define sections qualified with a
doctype delimiter.  This avoids the problem of tag "collisions"
when you may have different document types with identical
tag names.

 These and many other new features can be reviewed at:

http://technet.oracle.com/sample_code/products/text/htdocs/text_samples/Lite/Samples/imt_816_techover.html
...
Review
      & Followup

      Rating: 4
Simply
      super  September 18, 2002
Reviewer:  anthony
       from INDIA
Its a superp explanation.

      Rating: 4
Empty
      Tags  December 10, 2002
Reviewer:  Rukshan
      Soza  from London
Tom,

Can you please explain how we can find documents with empty tags?. You explained
here that empty tags are not indexes. We have a need to query based on empty
tags.

Also, we already have an intermedia index to do free text search with "contains"
clause. If we want to add increase functionality to search via tags then do we
create a new index with auto xml sections or can we modify the existing index
with alter command. An example would be much appreciated.

Many Thanks,

Rukshan

          Followup:

I asked Sean Dillon, our local XML technologist, to take a look at this and
here's what he had to say:
--

In 8.1.5, searching for empty tags will require you to use the XML Developer's
Kits.  This means in order to check an element to see if it's empty, you have to
load it up into a DOM or SAX parser and perform an XPath expression against the
document.  This is a programmatic endeavor, and will be CONSIDERABLY processor
and memory intensive (If your documents aren't too large, I would strongly urge
you to use the DOM parser for this, just keep in mind the entire document needs
to fit into memory).  It's resource intensive because basically you'll have to
full scan the table and for each row perform a series of operations.  Parse the
document into memory, then perform the XPath expression to check for an empty
string.  If you MUST do this in 8.1.5, I would think of alternative ways to
accomplish what you're trying to do.  This all depends on the behavior of your
application of course, but maybe you want to try checking to see if an element
is null when you insert the row, and then add a column to the table that
indicates whether it's null or not.  That way, you can index that column and
quickly find those rows that satisfy the "null element" requirement.

In Oracle9i Release 2, this is exponentially easier.  Not only do you not have
to write the code to perform the parse and XPath search, all you have to do is
use a built-in function to the XMLType data type known as "EXTRACT" or
"EXISTSNODE".  You can also create function-based indexes on XPath expressions
in 9iR2, which would also speed up your searches considerably.

If you want to play with XPath expressions, a fellow Oracle employee, Steve
Meunch, wrote a book entitled "Building Oracle XML Applications" for O'Reilly.
In it, he has a utility called "XPathGrep" that lets you quickly perform an
XPath expression against an XML document.  As soon as you get the XPath
expression you need to look for an empty tag, you've got your answer.  You can
find all the code for Steve's book on O'Reilly's website, or you can find the
two classes needed (along with the XML Developer's Kits, of course) for
XPathGrep here:
http://asktom.oracle.com/~sdillon/xml/xpathgrep.tar
For the intermedia index, I'm not so sure I understand what you mean by "search
via tags".  This is precisely what the CONTAINS clause does for you, it lets you
search for text or values within particular elements.

_____________________________________________________________________
Sean Dillon - Principal Technologist, Oracle Corporation
Author "Beginning Oracle Programming"
http://www.amazon.com/exec/obidos/ASIN/186100690X

      Rating:
      4
CLOB  December 17, 2002
Reviewer:  Art
       from San Francisco, CA USA
You supplied link:

http://technet.oracle.com/sample_code/products/text/htdocs/text_samples/Lite/Samples/imt_816_techover.html

Sorry. This page does not exist

          Followup:

goto technet
goto documentation
goto database
goto text

its all there.

      Rating: 5
Great
      Explaination  March 17, 2003
Reviewer:  Anirudh
      Sharma  from N.Delhi, INDIA
Hi Tom
Actually we also have a same sort of requiremnet, in the sense that we also need
to store the XML file data in the database and then go for the search. I have
not worked much on interMedia before what I would like to know is how can we
store a really long XML file in the CLOB column(file with about 10K lines).
Tried it using SQLLDR but it wasn't fruitful. It seems to be storing only the
path that I gave. Kindly rectify.
ctl file for SQLLDR
load data
INFILE 'd:\ldr.dat'
INTO TABLE inter_XML
APPEND
FIELDS TERMINATED BY ','
(id,
ext_fname FILLER CHAR(80),
 xml_doc LOBFILE(ext_fname) TERMINATED BY EOF)
-------
file  ldr.dat has path to the xml file:-
1,D:\test.xml
-----

Or if there is any other way to acheive this coz simple insert statment gave me
string too long error;
Thanks

          Followup:

ops$tkyte@ORA920> create table inter_xml( id number, xml_doc clob );

Table created.

And then after running sqlldr:

ops$tkyte@ORA920> select * from inter_xml;

        ID XML_DOC
----------
--------------------------------------------------------------------------------
         1 load data
           INFILE *
           INTO TABLE inter_XML
           APPEND
           FIELDS TERMINATED BY ','
           (id,
            ext_fname FILLER CHAR(80),
            xml_doc LOBFILE(ext_fname) TERMINATED BY EOF)
           begindata
           1,/home/tkyte/t.ctl

I loaded my ctl file -- so you can see it.  You've done something wrong, such as
using a control file different then the one posted here.  Your CTL file is
dandy, you made some other mistake.

March 17, 2003 yasser -- Thanks for the question regarding
      "treeview/node/child", version 8

      You Asked
hi,
i have table dept(deptno , ename) how i use treeview to
select deptno as parent and ename as children into treeview

      and we said...
did you get the note i sent to you already on July -14'th?  It
does exactly that.  Just change the cursors:

  -- Cursor to select from Departments
  cursor get_dept is select deptno, dname from dept;

  -- Cursor to select the employees for each dept
  cursor get_dept_employees (this_department in number) is
    select empno, ename from emp
        where deptno = this_department
        order by ename;

into

  -- Cursor to select from Departments
  cursor get_dept is select DISTINCT deptno, dname from dept;

  -- Cursor to select the employees for each dept
  cursor get_dept_employees (this_department in number) is
    select empno, ename from dept
        where deptno = this_department
        order by ename;

Article-ID:         <Note:52736.1>
Circulation:        PUBLISHED (EXTERNAL)
Folder:             client.Dev2000.Forms.V5
Topic:              Hints: OLE and ActiveX in Forms 5
Title:              Implementing the Treeview Control from
COMCTL32.OCX
Document-Type:      BULLETIN
Impact:             MEDIUM
Skill-Level:        ACCOMPLISHED
Updated-Date:       27-AUG-1999 04:21:23
Attachments:        NONE
Content-Type:       TEXT/PLAIN
Products:           45;
Platforms:          DESKTOP;

          Implementing the Treeview Control from COMCTL32.OCX
          ===================================================

PURPOSE
-------
This Note aims to give you an introduction to using the
Microsoft Treeview OCX.  This OCX file is bundled into a single
OCX called COMCTL32.OCX bundle that is supplied with Visual
Basic Version 5.  As well as providing the TreeView control,
this OCX also contains other widgets such as a Slider control
and a Tab Control.

You can use this TreeView OCX to provide an object-navigator
type interface similar to the type of interface used within the
Developer Forms Designer.   This Note should provide you with
enough information to fulfill most initial requirements for
coding with this control.

SCOPE & APPLICATION
-------------------
The information supplied in this note applies only to Forms
Version 5.0 and above on a 32-bit Windows platform in a
Client/Server environment.

RELATED DOCUMENTS
-----------------
Before attempting to integrate any OCX control with Forms,
ensure that you are familiar with the documentation supplied
with the OCX that you are attempting to use.

See the "ActiveX support in Form Builder" Help Topic in the
Forms Online Help.

INTRODUCTION TO THE CONTROL
============================
It is important to understand the structure of the Microsoft
Treeview control.  The control itself consists of a three level
object hierachy: the Tree Control itself, the NODES collection,
and the NODE object.

Within the control at runtime, there will be one instance of
both the TREEVIEW control and the NODES object, but there will
be multiple instances of the NODE object.

        +--------------------------------+
        | OCX Control - TreeView Object  |
        +--------------------------------+
                       |
        +--------------------------------+
        |        Nodes Collection        |
        +--------------------------------+
         |         |         |         |
      +------+  +------+  +------+  +------+
      |Node 1|  |Node 2|  |Node 3|  |Node n|
      +------+  +------+  +------+  +------+

Remember that each of these objects (or instances of objects)
will export properties and methods, and when interacting with
the control, you must be addressing the correct object that is
relevant to the task in hand.  For instance, asking the TreeView
object to add a new NODE will fail to work.  The ADD method is
exported by the NODES collection not the TreeView Object.

CREATING THE CONTROL
====================
When you create (draw) the OCX item to contain the control
within a Non-Base table block, set the "Single Record" property.
 The Single Record property ensures that the control is
correctly instantiated as soon as the form starts up.

Right-mouse-click and choose "Microsoft Tree View Control" from
the list presented.

To set some of the properties of the control at design time,
right-mouse-click on the OCX item again and choose "TreeCtrl
Object --> Properties".  For instance, you might want to set the
font used by the control at this point because
setting the font of an OCX at runtime can be difficult.  For
more information on doing this at runtime, see:

   Note:46165.1   Setting the Font Of ActiveX Controls

IMPORTING THE OLE ACCESSOR CODE
===============================
Most of the hard work in interacting with the OCX control has
been done for you.  Forms will ask the Control what properties
and methods it can support and will create the PL/SQL access
code to create the interface for you.

1) Choose "Program -> OLE Importer" from the Menu, and select
"COMCTL.TreeCtrl" from the top pane.

2) A long list of Method packages will appear in the botton
left-hand pane.   Select the following (using Control Click in
the standard way):
       INode10
       INodes10
       ITreeView10
   In the right-hand pane, choose the single entry:
       ITreeViewEvents.

3) This will generate several PL/SQL packages for you in your
form:

   ComctlLib_CONSTANTS -> A package containing all the constants
used by the control.  For instance the datatype NODE is declared
here, as a Subtytpe of OLEOBJ. Additionally numerical constants
such as tvwPlusMinusText are created.  You will see such
constants used in the help file for the control.

   COMCTL_ITreeView10  -> Contains PL/SQL functions and
procedures used to access all of the properties exported by the
TreeView Object, and also wrappers for the Methods exposed by
that object.

   COMCTL_INodes10     -> Wrappers for Methods and Properties
exported by the NODES collection object.

   COMCTL_INode10      -> Wrappers for Methods and Properties
exported by the NODE object.  One instance of the node object
will exist for each entry that you create on the tree.

   COMCTL_TreeCtrl_1_EVENTS -> The events package for the
control.  When the control raises an event, for instance a mouse
click on a particular node.  You have to edit the package body
and respond to each event as you see fit.

POPULATING THE CONTROL
======================
Here is example code to populate the control with a list of all
the departments in the DEPT table, and under each department,
all the employees who work there.

-----------------------------------------------------------------
---------------
declare
  -- Cursor to select from Departments
  cursor get_dept is select deptno, dname from dept;

  -- Cursor to select the employees for each dept
  cursor get_dept_employees (this_department in number) is
    select empno, ename from emp
        where deptno = this_department
        order by ename;

  iDeptno    number;
  vcDname    varchar2(25);
  iEmpno    number;
  vcEname    varchar2(25);

  hNodes    ComctlLib_CONSTANTS.Nodes;
  hNode     ComctlLib_CONSTANTS.Node;

 -- Error Handler variables
  errCode             pls_integer;
  errSrc              varchar2(200);
  errDescription      varchar2(2000);
  errHelpfile         varchar2(200);
  errHelpContext      pls_integer;

begin
 -- Set up the tree as we want to see it.
 -- 1. Show the Expansion Indicators and Lines and Text
 COMCTL_ITREEVIEW10.Style(:item('B1.TREE').interface,

ComctlLib_CONSTANTS.tvwTreelinesPlusMinusText);

 -- 2. Show in the same style as the Forms navigator
 COMCTL_ITREEVIEW10.LineStyle(:item('B1.TREE').interface,
                    ComctlLib_CONSTANTS.tvwRootLines);

 -- 3. Get the Handle to the Nodes Collection
 --    the ADD method that we need to use to create nodes
 --    is exposed by the NODES collection, so we need the
 --    Handle to that first and then we can invoke ADD
 hNodes := COMCTL_ITREEVIEW10.nodes(:item('B1.TREE').interface);

 -- 4. Now LOOP through all the departments and add a node for
each
 --    We will generate a "key" for each department based on the
string
 --    "DEPT" concatinated with the Deptno.
 --    This is just a restriction of this control that we need
to
 --    have Keys that begin with a Alpha Char not a numeric
 open get_dept;
 LOOP
   fetch get_dept into iDeptno, vcDname;
   if get_Dept%notfound then EXIT; end if;

      -- Add the Parent Department Node.
      hNode := COMCTL_INodes10.ole_add(
                      interface            => hNodes,
              Relative             => olevar_empty,
              Relationship      => to_variant(

ComctlLib_CONSTANTS.tvwLAST),
              Key         =>
to_variant('DEPT'||to_char(iDeptno)),
              Text         => to_variant(vcDname));
      release_obj(hNode);

      -- 5. Now loop through the child rows (employees) for this
dept
      --    We add each Employee as type tvwChild. retalive to
the
      --    Key that we generated for the current deptno
      --    We also generate a key for each of the employee rows
using
      --    "EMP" concatinated with the Empno.
      open get_dept_employees (iDeptno);
      LOOP
    fetch get_dept_employees into iEmpno, vcEname;
    if get_dept_employees%notfound then
        close get_dept_employees;
        EXIT;
    end if;

    -- Add each child to this Dept
    hNode :=  COMCTL_INodes10.ole_add(
                     Interface            => hNodes,
                Relative             =>
to_variant('DEPT'||to_char(iDeptno)),
             Relationship    => to_variant(

ComctlLib_CONSTANTS.tvwChild),
             Key         => to_variant('EMP'||to_char(iEmpno)),
             Text         => to_variant(vcEname));
        release_obj(hNode);
      END LOOP;
  END LOOP;
  release_obj(hNodes);

exception
  when form_ole_failure then
        errCode := last_ole_exception( errSrc,
                                      errDescription,
                                      errHelpfile,
                                      errHelpContext);
        message('Error Raised by ->'||errSrc||':
'||errDescription);

end;
-----------------------------------------------------------------
---------------

NOTES
=====
You will notice that this control uses the Variant datatype
extensivly.  This means that you must convert all of your
arguments to the variant type, using the TO_VARIANT() built-in.

To define a "Null" value to send to the control (for instance,
where we are creating the DEPT nodes and need to set the
Relative as NULL), we use the built-in OLEVAR_EMPTY, not
OLEVAR_NULL.  OLEVAR_NULL indicates that this variant is invalid
rather than just empty.  Be careful regarding this distinction
of the meaning of NULL between PL/SQL NULL and and OLE NULL.
The error handling used in this sample is explained in the
following Note:

     Note:44805.1   Standard Error Handler for OLE Code

Review &
      Followup

      Rating:
      4
  March 12, 2001
Reviewer:  Ravi
      Jain  from Delhi, India

      Rating: 4
Thanks for
      question regarding Tree View in forms 5.0 but ??   March 12,
      2001
Reviewer:  Ravi Jain  from Delhi, India

Thanks Tom,

Ur response is a very great help for me to start my knowledge in ActiveX with
forms..
ur given example works very well... but the thing is i wanna do some insertion
and updation using this control. Basically if the managar wants to move a member
from one group or dept to other. or if the dept level has to be changed how will
i accomplish that please ??

      Rating:
      5
treeview/node/child", version 8  December 07, 2001

Reviewer:  meena  from singapore
I have create a form (forms 5, window 95 platform)  and created OCX control and
named it as temp1. and right_mouse_click choose import object and imported
'Microsoft Treeview control,ver5, and imported the OLE accessor code also. and i
created the procedure called 'populate detail' and paste your sample codes and
change where ever u have mentioned as
:item('B1.TREE'),interface  to :temp1('B1.TREE').interface
and system promts "Bad bind variable :item('B1.TREE')'
How to get rid of this problem. Tom could u pls send suggession on this? Thanks.

      Rating: 3
use of
      COMCTL32.OCX in Forms 5.0  March 14, 2003

Reviewer:  Zo A.  from Madagascar
Hi Tom,

Your response is OK but we need much more details on how to insert, delete,
update. Suppose, I have a field name test (BLOCK.test) outside the OCX item. I
would like to display on TEST each event occured such as When Collapse,
TEST:='Collapse', When Click Node TEST:='Node', etc. Perhaps a source code will
be helpful for us to achieve that.

Thanks

Zo

          Followup:

sorry -- don't do OCX's at all -- my operating system for some reason doesn't
recognize them....

      Rating: 4
Implementing
      the Treeview Control   March 17, 2003
Reviewer:
      Zo A.  from Madagascar
Sorry to bother u again Tom. My current project is to have a menu looks like a
treeview (such as Windows explorer or the one we found in forms designer, ...) I
tried the above method and it works fine (activeX), my little snag is that I
couldn't show up the folder icon on it. Now u recommend me not to use activeX. I
know doing it in forms6i is very easy, but we are still using FORMS 5.0 and I've
used the example demo using navigate.pll but there is no line, no icon or any
bitmap to present a folder, just a plus and minus sign. I know u won't let me
down, please give me your advice. Any source code is very appreciated.

Thanks

Zo

          Followup:

sorry -- i don't work with windoze at all -- don't even have it installed
anywhere.   I'll refer you to otn.oracle.com under discussion forums, maybe
someone there can help you.

      Rating:
      2
Images  October 09, 2003
Reviewer:
      Iker  from Slovakia
I would like images into treeview.
I can set
 COMCTL_ITREEVIEW10.Style(:item('B1.TREE').interface,
    ComctlLib_CONSTANTS.tvwTreelinesPlusMinusPict_7).
How I set property Image, SelectedImage in COMCTL_INodes10.ole_add ?

March 17, 2003 Sunil -- Thanks for the question regarding "Sort
      Area Size", version 8.0.5

      You Asked
Hi Tom ,

       We are oracle server 8.0.5. and the number of concurrent
users
are around 100. We are using siebel applications . when we query
there is
lot of sorting around 400,000 records . Is it possible that we
can have sort area
size more then 1 MB. say around 2 MB and can we have sort area
retained size also 2 MB
If you can send me the answer I will really appericiate that.
Thanks
Sunil

We are not running mts Server. The OS is Sun Solaries 2.6.
We want the query should be fast . Even we don't have that much
of space . If we
Increase the sort area size (Is it necessary that sort_area_size
and sort_area_retained_size should be equal)

Thanks for the fast reply

Sunil

      and we said...
Sure, you can set the sort_area_size to 2m.  The sort_area_size
will be dynamically allocated by the dedicated server at runtime
when a sort is invoked and this setting will allow it to GROW
upto 2m in size (it'll not go right there, this is more of an
upper bound).  After a sort is completed, we will shrink the ram
allocated by the process back down to the sort_area_retained
size.

It is common, and the reason we have the 2 settings, to have
sort_area_retained_size set smaller then sort_area_size.  Think
of these as upper and lower bounds.  We'll start your session
with no sort memory allocated.  As you do sorts -- we'll allow
you to allocate UPTO sort_area_size bytes to sort in memory.
When the individual sorts are complete -- we'll shrink that pool
of memory back down to the retained size (so we don't have to
allocate it again -- that memory can be used for other things
now).

Make sure you have enough physical ram for all of this work else
the system will start swapping and paging.  Consider how many
concurrent sorts you expect (multiply that by 2m and make sure
you can support that).  Consider also the retained size * 100
and make sure you can support that as well.

You might start with 2m sort area size and a retained that is
some fraction of that (say 0.5 meg) and see how that goes for
you.

Also, make sure you are currently doing LOTS of sorts to disk
first (see if this is going to have any effect or is just a shot
in the dark).  Simply:

select * from v$sysstat where name like '%sort%';

will tell you that.
Review & Followup

      Rating:
      2
  June 08, 2001
Reviewer:
      A reader

      Rating:
      4
Thanks  September 22, 2001
Reviewer:
      Olga  from Canada

      Rating:
      3
  December 10, 2001
Reviewer:  Kiro
       from Bulgaria

      Rating: 5
Sort Area Size
      and Order By  March 04, 2003
Reviewer:
      Chenna
Tom

One of my queries takes 30 seconds with out order by, but amazingly takes 2436
seconds with the order by.

A couple of times I got the 'Cannot extend temporary segment by 128M error'.

I want to understand whats going on here. When I include 'order by' in my query,
it is waiting for space in temporary segment , is that what is happening?

What type of a wait is it, if I want to go into the v$ tables and determine if
this kind of wait is occurring , how should I find out. Is there a way to find
out details about the waits created by my specific session.

When I trace my session, where should I look for , for this kind of waits in the
trace file?

Please discuss in detail.

          Followup:

does it really take 30 seconds (it seems every time I get this sort of question
is it really that -- well, it took 30 seconds to get the first pagefull in toad
-- they didn't wait for the LAST row to come back).

Sorting generally must get all rows -- then sort -- then return.  I will guess
you really are comparing the time to get the first row from each query -- and
that is not comparable.

Show us a TKPROF of the two queries....

It is not "waiting" for space in temp, it is using it.

      Rating: 3
retained
      size?  March 12, 2003
Reviewer:
      A reader
Hi

I am not sure what's the use of sort_area_retained_size since the memory are not
released back to the server, I mean once sort_area_size has been allocated to an
session the pga wont shrink will it? What's the point of using
sort_area_retained_size?

Also I have a question regarding very big sort_area_size, our senior DBAs set
the sort_area_size parameter to 256MB in one of our 7.3.4 database I was kind of
shocked but he argues that he had to set that to speed up direct sql loader
processes, the index rebuild phase needed to speed up. I remember when I was
wokring in another shop I did tests with setting sort_area_size from 16mb to
128mb (16, 32, 64 and 128) and the process took more or less the same so I told
him that setting sort area bigger than 32mb shouldnt yield extra benefits he
argues back that if that was the case why he see PGA uses up to 256MB (as
sort_area_size) which made me wonder too! If Oracle can use the memory we set
then why the process (was basically very big indexes rebuilds) I tested didnt
get faster?

          Followup:

well, you have to think about configurations as well.

shared server -- sort_area_retained in the UGA in the SGA.  sort area size - in
the PGA in the dedicated server.

But -- is the sort_area_size not returned to the OS in dedicated server mode?
Yes and no I say.  If you have allocated memory, and then are not using it --
the OS can and will page it out - releasing the real physical memory for use by
other processes.

I cannot comment on why your test did not show what you expected.  I would need
to see the test.  I can say there is a good chance that the larger sort area
would be useful.  I can say that if I was using parallel query to rebuild -- I
might want it smaller.

      Rating:
      4
SORT_AREA_SIZE  March 14, 2003
Reviewer:
      gene charles  from Jacksonville/FL
 Hi Tom - good practical info. I ran the select from  v$sysstat and got good
results:
 memory 4806255
 disk 2835
 rows 7055256499.

 How do I found out which sql is doing the sorts on disks?

          Followup:

it is not tracked at the statement level -- you can find the sessions...

      Rating: 4
my
      sort_area_size test  March 14, 2003
Reviewer:
      A reader
Hi

I carried following test

table with 28 columns
composite primary key, 4 columns
number of rows 3000000

table occupies 600MB
index 160MB

oracle 8.1.7.4, Locally managed tablespace (autoallocate), Sun Solaris 8

script (I change sort_area_size for each test, 16MB, 32MB, 64MB, 128MB):

alter session set sort_area_size=16777216;
drop index X_PK;
set timing on
CREATE UNIQUE INDEX X_PK ON X(X1, X2, X3, X4);
set timing off

results:
16MB 6 minutes 32 secodns to build the index
32MB 6 minutes 30 seconds to build the index
64MB 6 minutes 42 seconds to build the index
128MB 6 minutes 22 seconds to build the index

Checked with PMAP and corresponding amount of heap memory (showed in pmap) used

Number of temporary extents used for all tests, around 250 (1mb extent, LMT)

Absolutely no difference!

          Followup:

so that means that -- your "bottleneck" if any was not in any way related to
using temp.

that is all that shows.

you actually have shown two important things:

o tuning blindly and using conventional wisdom to tune is silly.
o that Oracle actually used 16, 32, 64, 128 meg of ram (but obviously that was
NOT the bottleneck)

      Rating: 3
hmm I dont
      understand then  March 15, 2003
Reviewer:
      A reader
Hi

I tried the whole test again today with 1MB ~ 300MB sort_area_size, 1MB took
like 9 minutes the others still take 6 minutes or so... Even the 300MB one takes
that time, I dont understand this at all, I thought I am making the sorts
occuring in the memory. I dont see what can be the bottleneck.

BTW My collegue told me he was told by one of his friends working in Oracle
Support that the sort algorithm used by Oracle doesnt get give you better
results even you set massive sort_area_size, true?!?!

PS: If I had enough RAM in my server I would have tried 800MB (table size +
index size) sort_area_size! Well I do have 2GB Memory but there are other
instances running so...

          Followup:

DISK, writing 600meg to DISK and if you are in archive log mode, writing another
X hundred meg there.  Just means you were not cpu/memory bound, you were IO
bound -- you had more then enough of those other resources.

At some point, all you are measuring anymore is "how long does it take to write
this stuff".

So, you've done what is always best to do -- benchmarch, find the sweet spot

      Rating: 5
sort area size
      for all session ?  March 16, 2003
Reviewer:
      A reader
Is the sort_area_size value valid for each session or is it the total for all
sessions ?

          Followup:

it is PER sort...

A single session may in fact have more than one sort going on at a time and in
doing so may have N "sort area size" chunks of memory all going.

      Rating: 5
Did I misread
      the docs?  March 17, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Tom:

Did I misread the docs on this one?  "What?!?!", everyone says, "the
documentation???"

Anyway, from the 9iR2 Reference Guide for sort_area_size:

Increasing SORT_AREA_SIZE size improves the efficiency of large sorts. Multiple
allocations never occur. Only one memory area of SORT_AREA_SIZE exists for each
user process at any time.

It was my understanding that multiple sort_area_retained_size areas may be
allocated, but only one sort_area_size would be created.  Is this correct?

Thanks,

Mark

          Followup:

ok, uncle.

sort area retained size would be the more appropriate number here, if you negate
the use of parallel query which does it by process or thread in child sessions.

      Rating: 5
RE: Did I
      misread the docs?  March 17, 2003
Reviewer:
      Mark A. Williams  from Indianapolis, IN USA
Tom:

select   standard_text
from     thanks
where    subject = this_one;

- Mark

      Rating:
      5
standard_text  June 03, 2003
Reviewer:
      A reader
Mark,

Just curious - what does your above lines(sql) mean to say ?

March 17, 2003 -- Thanks for the question regarding "Passing
      c_my_cursor%RowType as IN parameter of procedure ?", version 9iR2

      You Asked
Hi Tom,

My friend here wrote a package with global  cursor  defined in the Package Body
.

I am asking , are we allowed to define something like this :

create or replace package body   test as

--example
cursor  c_my_cursor
is select * from dual ;

procedure  pass_cursor_rec( p_cursor_var  IN  c_my_cursor%RowType
 ,  modified_CURsor_record   IN OUT varchar2)
is
begin
--do something with  p_cursor_var  which was assigned in
--another procedure which opened the GLOBAL Cursor as defined  above
--before we call this Procedure
null;

end  pass_cursor_rec ;

end  test  ;

Thanks in advance .

      and we said...
sure.

why didn't you just try it?

ops$tkyte@ORA920> create or replace package my_pkg
  2  as
  3          cursor c1 is select * from dual;
  4
  5          procedure p1( data in c1%rowtype );
  6  end;
  7  /

Package created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package body my_pkg
  2  as
  3          cursor c2 is select * from dual;
  4
  5          procedure p2( data in c2%rowtype )
  6          as
  7          begin
  8                  null;
  9          end;
 10
 11          procedure p1( data in c1%rowtype )
 12          as
 13          begin
 14                  null;
 15          end;
 16  end;
 17  /

Package body created.

Review &
      Followup

      Rating: 4
Any way around
      this in 8.1.7?  March 06, 2003
Reviewer:
      Jon  from Sydney
How can we return a ref cursor when using dynamic SQL?  This is necesary for a
problem we have where by we are calling one of many possible procedures based on
certain condititons.

create package my_pkg as
    type t_rc is ref cursor;

    procedure p1(rc out t_rc);
end;
/

create package body my_pkg as
    procedure p1(rc out t_rc) as
    begin
        open rc for select * from dual;
    end p1;
end;
/

1) OK.. this works fine:
declare
    rc my_pkg.t_rc;
begin
    my_pkg.p1(rc);
end;

2) But when using dynamic SQL:
declare
    rc my_pkg.t_rc;
begin
    execute immediate 'my_pkg.p1(:x)' using rc;
end;
/

ORA-06550: line 4, column 45:
PLS-00457: expressions have to be of SQL types
ORA-06550: line 4, column 5:
PL/SQL: Statement ignored

3) So try to use a global cursor variable to hold the result set:
create or replace package my_pkg as
    type t_rc is ref cursor;
    rc t_rc;
    procedure p1(rc out t_rc);
end;
/

PLS-00994: Cursor Variables cannot be declared as part of a package

And it won't even compile.

I know this is all "expected" behaviour, but is there anyway around this in
8.1.7?  Any suggestions?  What about in 9i?

Thanks
Jon

          Followup:

You'll have to use DBMS_SQL, not a ref cursor, for this...

In all releases.

(and probably don't even want my opinion on code that dynamically calls other
code to dynamically open a query to generically do this that and the other
thing...)

      Rating: 3
Binding to
      dynamic SQL  March 17, 2003
Reviewer:  Zsolt
       from Hungary
from
PL/SQL User's Guide and Reference
Release 8.1.6:
"...
Dynamic SQL supports all the SQL datatypes. So, for example, define variables
and bind arguments can be collections, LOBs, instances of an object type, and
refs. As a rule, dynamic SQL does not support PL/SQL-specific types. So, for
example, define variables and bind arguments cannot be Booleans or index-by
tables. The only exception is that a PL/SQL record can appear in the INTO
clause.
..."

You can bind object type to dynamic SQL.

          Followup:

sure you can -- too bad that was not their question.

they are trying to bind a REF CURSOR.

A REF CURSOR is not an object type, it is not a SQL type.

Hence you cannot bind a REF CURSOR as output using native dynamic sql.

Therefore, if they want to dynamically call a routine that returns a result set
- they will have to open the query using DBMS_SQL, not a ref cursor, since
DBMS_SQL returns a simple INTEGER cursor handle and that can in fact be bound.

March 17, 2003 Vinod -- Thanks for the question regarding "Error
      in using bulk collect with REF cursors", version 8.1.7

      You Asked
Hi Tom,

I tried to execute the following code, but it returned me an invalid cursor
error.

declare
  vcur package1.curs;
  vstr varchar2(1000);
  type m is table of number index by binary_integer;
  a m ;
begin
  vstr := 'select col1 from table1' ;
  open vcur for vstr ;
  fetch vcur bulk collect into a ;
  close vcur ;
end ;

If i go thru a normal loop it works but bulk collect does'nt work here. I'm not
sure whether bulk collect is supported for reference cursors.

Please let me know if there is any other workaround.

Regards
Vinod

      and we said...
not for dynamically opened ref cursors -- not until 9i.

In 9i:

ops$tkyte@ORA920.US.ORACLE.COM> declare
  2          type array is table of varchar2(30) index by binary_integer;
  3          l_data array;
  4          l_cur sys_refcursor;
  5  begin
  6          open l_cur for 'select username from all_users';
  7          fetch l_cur bulk collect into l_data;
  8          close l_cur;
  9  end;
 10  /

PL/SQL procedure successfully completed.

works -- in 8i and before -- not.  A "work around" is:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package globals
  2  as
  3          type array is table of varchar2(30) index by binary_integer;
  4
  5          g_data array;
  6  end;
  7  /

Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2          execute immediate '
  3          begin
  4                  select username BULK COLLECT into globals.g_data
  5                    from all_users;
  6          end;';
  7  end;
  8  /

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating: 3
bulk collect in
      execute immediate with analytic function  March 17, 2003

Reviewer:  A reader
hi,
i need to use an analytic function and hence i wrote a dynamic sql  (inside a
proc). I need a way to return the result set into a nested table.
How do I do it?

I want something like :

create or replace package p is
procedure pr
is
t_var t_nested_table := t_nested_table(t_object_type('','','','',''));
begin
str:='select x,y,rank() over (partition by x order by x) as r from table1';
execute immediate str bulk collect into t_var ;

/*i need to use the result set in t_var for further processing. I need to use
that t_var as a table(by casting) for joins, finding max of a column etc
etc....*/

exception;
end;

          Followup:

ops$tkyte@ORA920> create or replace type MyScalarType as object
  2  ( a int, b date, c varchar2(25), rank number )
  3  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace type myTableType as
  2  table of myScalarType
  3  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          l_data myTableType;
  3  begin
  4
  5          execute immediate '
  6          select cast( multiset( select deptno, hiredate, job,
  7                                        rank() over (partition by deptno
order by job) r
  8                               from emp ) as myTableType )
  9        from dual' into l_data;
 10
 11          for i in 1 .. l_data.count
 12          loop
 13                  dbms_output.put_line( l_data(i).a || ' ' || l_data(i).b ||
' ' || l_data(i).c || ' ' || l_data(i).rank );
 14          end loop;
 15  end;
 16  /
10 23-JAN-82 CLERK 1
10 09-JUN-81 MANAGER 2
10 17-NOV-81 PRESIDENT 3
20 09-DEC-82 ANALYST 1
20 03-DEC-81 ANALYST 1
20 17-DEC-80 CLERK 3
20 12-JAN-83 CLERK 3
20 02-APR-81 MANAGER 5
30 03-DEC-81 CLERK 1
30 01-MAY-81 MANAGER 2
30 20-FEB-81 SALESMAN 3
30 28-SEP-81 SALESMAN 3
30 08-SEP-81 SALESMAN 3
30 22-FEB-81 SALESMAN 3

PL/SQL procedure successfully completed.

March 17, 2003 wallacel -- Thanks for the question regarding
      "performance manager in OEM", version 8.1.6

      You Asked
Tom:
I have no problem run OEM console.and from there i can run
tablespace map,sql analyze etc ,but each time i try to use
performance manager,it gives me message
VTM 0005:Error connection to server
java.net.ConnectException:Connection refused.

why? also some book mention file i/o monitor, but i never see
it.
where is it?

Regards

      and we said...
Problem Description:
====================

You are running Performance Manager (PM) 2.X trying to connect
to a remote database and you receive one of the following
errors:

    VTM-5: Error connecting to server

 - or -

    VTM-7: no collection cartridge of this service type

followed by the java.net.connect exception:

    connection refused

Solution Description:
=====================

There are several things to check:

1. Start the Oracle Data Gatherer if you have not yet done so:
--------------------------------------------------------------

On UNIX:

Start the Data Gatherer with the following command:
     $ vppcntl -start

As soon as everything is started, run the following command:
     $ vppcntl -ping

This command will report if the outside world will be able to
communicate with the Data Gatherer.

For errors look in the alert_dg.log in $ORACLE_HOME/odg/log
directory.

On NT:

Start the Data Gatherer service from the NT Control
Panel/Services icon.

For errors look in the alert_dg.log  in %ORACLE_HOME%\odg\log
directory.

Explanation:

ODG is needed for Performance Manager and Capacity Planner
applications to work.

2. Check the preferred credentials:
-----------------------------------

You need to change the Preferred Credentials for the target
database service to  a valid user with dba priveleges:

* Click on the 'System' menu:
   ->Preferences
      ->preferred Credentials:
           select the target service name and change the
           user name and password to a dba user.

* Relaunch Performance Manager.

Explanation:

The Data Gatherer must connect to the target database with a dba
role. If the specified user does not have dba access to the
database (or any  access at all), then Data Gatherer will exit
abnormally.

3. Check if the location of the Oracle Data Gatherer and the
connect string for the remote databases are correct:
---------------------------------

Performance Manager will assume that there is a Data Gatherer
running on the production boxes, which may not be the case at
hand.

If so, after you receive the VTM-5 or VTM-7 error:

1. Hit continue
2. Change the ODG Location field to the hostname of the
repository machine which is actually running the ODG
3. Reconnect
4. If, after that, your error changes to include errors:
       VTD-77: Error in Data Gatherer request string
       ORA-12154: TNS: Could not resolve service name
  then the ODG home does not contain the proper service
name/alias for the production database.

  Go to the <ODG_ORACLE_HOME>/network/admin/tnsnames.ora and add
the service name and its proper connect description for the
production database EXACTLY AS SHOWN IN THE OEM NAVIGATOR, under
the Databases folder (it is case sensitive).

  Do the same for all other remote databases you need to monitor
with  Performance Manager.

Explanation:
============

The performance manager is unable to retrieve the necessary
information from the Data Gatherer.

The Performance Manager sends the name of the service it is
checking to the Data Gatherer. This string is the name of the
database as it is displayed in the OEM navigator. The Data
Gatherer will consider this string as a TNS connect string, and
will try to make a connection to the database using that
string.

Review &
      Followup

      Rating: 5
Where is
      Performance Manager ???  March 17, 2003
Reviewer:
      Riaz Shahid  from PRAL, ISlamabad, Pakistan
Hello Tom !

From where i can download performance manager ?

Regards

Riaz

          Followup:

it is part of OEM, you do not download it - it is on your install cds as part of
that tool.

March 17, 2003 osama -- Thanks for the question regarding
      "database", version 8.1.1

      You Asked
what is the tablespace?
what is the segmenets?
can the procedures return two or higher records like view?
plzz i need this answer by full detials, and thank u very much.
if u have pdf file send it to my email.for full informations

      and we said...
Read this totally amazing document:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-CON
The Oracle Server Concepts Guide.  Read it, remember 10% of it and you'll
already know 90% more then most people.

But -- in there you will definitely find out what a tablespace and a segment is.

Yes, Procedures can return COLLECTIONS.  For that see the PLSQL programmers
guide -- same place as the concepts guide.

Since you used a version of 8.1.1 which never really existed, I sent you to the
most current documentation -- but OTN has it all for 8i as well -- you can just
goto otn's home page.
Review & Followup

      Rating: 5
URGENT!!! what
      is oracle?  March 17, 2003
Reviewer:  Remi
      Visser  from Netherlands (The Hague)
sir, this is very urgent:

what is oracle?
what is database?
what is server?

Please explain this asap ...

;-)

Hihi

Your answer about the concepts guide is the one and only
good answer Tom, helped me to get the OCP and more important made me understand
Orace!!

You have the hottest Oracle site on the net Tom!

Remi

      Rating:
      1
????????????????????????????????????????????????????  March
      17, 2003
Reviewer:  Riaz Shahid  from PRAL,
      Islamabad, Pakistan
hello remi

First, here r mu suggestions :

(1) If u have any question to be asked, search asktom.oracle.com first. Don't
directly post ur questions. Almost 60-70 % answers of ur questions may be found.
(2) Read Books for simpler questions (like this).

ok...hope u woulldn't mind that.

Tech Definition
---------------

Oracle is RDBMS.

Verbal Meaning
--------------

Oracle means mystery.
Oracle is a knowledge thru which u can predict fututre.

A database is collection of files(table).

A server is a machine which responds to the requests of clients/other servers.

Riaz

          Followup:

(talk about missing the point ;)

I think Remi was exercising a bit of sarcasm.

      Rating: 3
Oracle9i
      Release 2 - Developer's Releases  November 06, 2003

Reviewer:  juancarlosreyesp@yahoo.com
Hi Tom, can you explain please why there is other database release called
Oracle9i Release 2 - Developer's Releases

and specially why is so small.

here

http://otn.oracle.com/software/products/oracle9i/index.html
:) Thanks

March 17, 2003 YAN -- Thanks for the question regarding "Look up
      Table", version 8.0.4

      You Asked
Hello Tom,
I wondered if something i do since a while to optimise my code is good in all
case. I have personal experience that tell me that it is good.

big = millions of row
small = thousands of row

In many case this statement

SELECT t1.a,
       t2.b,
       util_database.get_info_small_table(t1.a),
       util_database.get_info_small_table(t2.b)
FROM t_big_table1 t1,
     t_big_table2 t2
WHERE t1.key = t2.key

seems faster than this statement

Select t1.a,
       t2.b , ts1.x, ts2.x
from t_big_table1 t1,
     t_big_table2 t1,
     t_small_table ts1,
     t_small_table ts1

where t1.key = t2.key
and   t_big_table.a = t_small_table.a

i know that there is many factor to consider, bu in the general case, my
experience tell me that look_up table query base are faster when packaged than
when join...

If you consider that a is indexed on t_small table.
i am wrong ? a sure thing is that is seems that in many case it is faster

thanks Tom

      and we said...
I cannot fathom a case where what you say is true.  never ever.  databases were
born to join (really, they are) and the overhead of calling PLSQL from SQL is
huge.

"seems" = "i haven't really tested this and my personal opinion is influencing
me, what I want to believe is true 'seems' to be true"

Until you have hard and fast *facts*, I will vehemently say "nope, not a
chance".

Ok, here goes my evidence (this is going to fry my machine, doing all of that
plsql, but here goes)

big_table@ORA920> create table small_one as select owner, rownum x from (select
distinct owner from big_table);

Table created.

big_table@ORA920> alter table small_one add constraint small_one_pk primary
key(owner);

Table altered.

big_table@ORA920> analyze table small_one compute statistics;

Table analyzed.

big_table@ORA920> create table small_two as select object_type, rownum x from
(select distinct object_type from big_table);

Table created.

big_table@ORA920> alter table small_two add constraint small_two_pk primary
key(object_type);

Table altered.

big_table@ORA920> analyze table small_two compute statistics;

Table analyzed.

big_table@ORA920> select table_name, num_rows, blocks from user_tables;

TABLE_NAME                       NUM_ROWS     BLOCKS
------------------------------ ---------- ----------
BIG_TABLE                         3816192      54669
SMALL_ONE                              28          1
SMALL_TWO                              27          1

big_table@ORA920> create or replace function f1( p_owner in varchar2 ) return
number
  2  as
  3          l_x number;
  4  begin
  5          select x into l_x from small_one where owner = p_owner;
  6          return l_x;
  7  end;
  8  /

Function created.

big_table@ORA920> create or replace function f2( p_object_type in varchar2 )
return number
  2  as
  3          l_x number;
  4  begin
  5          select x into l_x from small_two where object_type = p_object_type;
  6          return l_x;
  7  end;
  8  /

Function created.

select t1.status, t2.object_type, t3.x, t4.x
  from big_table t1,
       big_table t2,
       small_one t3,
       small_two t4
 where t1.id = t2.id
   and t1.owner = t3.owner
   and t2.object_type = t4.object_type

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch   254414    147.38     185.30     127019     109375          0     3816192
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   254416    147.38     185.30     127019     109375          0     3816192

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 60

Rows     Row Source Operation
-------  ---------------------------------------------------
3816192  HASH JOIN  (cr=109375 r=127019 w=23055 time=160907496 us)
     27   TABLE ACCESS FULL SMALL_TWO (cr=3 r=0 w=0 time=236 us)
3816192   HASH JOIN  (cr=109372 r=127019 w=23055 time=132087614 us)
     28    TABLE ACCESS FULL SMALL_ONE (cr=3 r=0 w=0 time=208 us)
3816192    HASH JOIN  (cr=109369 r=127019 w=23055 time=102827145 us)
3816192     TABLE ACCESS FULL BIG_TABLE (cr=54685 r=51982 w=0 time=12307230 us)
3816192     TABLE ACCESS FULL BIG_TABLE (cr=54684 r=51982 w=0 time=12030250 us)

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                  254414        0.05          1.06
  db file scattered read                       6518        0.08          6.90
  direct path write                            1537        0.00          0.02
  direct path read                             1537        0.00          0.03
  SQL*Net message from client                254414        0.48         80.60

versus (i got bored, hit ctl-c before it finished, that and I sort of needed
my CPU back :)

select t1.status, t2.object_type, f1(t1.owner), f2(t2.object_type)
  from big_table t1,
       big_table t2
 where t1.id = t2.id

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch   228802   1930.09    2123.88     125016     109380          0     3432016
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   228804   1930.10    2123.89     125016     109380          0     3432016

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 60

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                  228802        0.07          1.75
  db file scattered read                       6520        0.04          6.64
  direct path write                            3328        0.00          0.04
  direct path read                             3003        0.00          0.07
  SQL*Net message from client                228802        0.57         87.70

SELECT x
from
 small_one where owner = :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute 3432017    805.40     826.65          0          0          0
0
Fetch   3432016    260.65     254.68          0    6864032          0
3432016
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   6864034   1066.05    1081.33          0    6864032          0
3432016

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 60     (recursive depth: 1)

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  latch free                                      2        0.01          0.01
********************************************************************************

SELECT x
from
 small_two where object_type = :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute 3432016    788.53     805.21          0          0          0
0
Fetch   3432016    259.48     251.13          0    6864032          0
3432016
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   6864033   1048.02    1056.34          0    6864032          0
3432016

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 60     (recursive depth: 1)

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  latch free                                      1        0.01          0.01

so, start adding up those CPU times and the logical IO's performed, and you
tell me -- which query performs better???

SQL -- it isn't just for breakfast anymore...  The less procedural code YOU
write, the faster it all goes.

Review &
      Followup

      Rating: 4
Must find those
      special case of  March 17, 2003
Reviewer:  Yan
      Courtois  from Montreal Canada
your answer is fair and square Tom..
but i remember that i have worked on a datawarehouse where i was right..

there was join between huge table and many thing to look up in small table, and
i sware to god it was faster with a look_up function.. i meen faster enought for
me to talk about it. I will try to talk with my old partener on this project to
get the real exemple i am looking at.

but for what i get of your answer i will try to get my batch processing clean of
any funtion call.. that way i will avoid question from people who don't know
PL/SQL and avoid compilation problem..

but.... i know that in some case you might have to use a function to speed up
the stuff, for exemple when you have lots of param to get from a parameter table
 like
            get_param(key, 1)
            get_param(key, 2)
            get_param(key, 3), is faster than the where

big_table.key = param_table1.key
and param_table1.the_param = 1
and big_table.key = param_table2.key
and param_table2.the_param = 2
and big_table.key = param_table3.key
and param_table3.the_param = 3;

the stuff is foreign_key linked.

I will listen your advice and get rid of the PL/SQL code and see what come up in
performance.
I'll get you back on that.

          Followup:

Just set up a scientific example...

I disagree with your second premise -- so much, so very very much.

            get_param(key, 1)
            get_param(key, 2)
            get_param(key, 3)

is many times slower then a join....  Think about it -- *it is procedurally
doing a silly nested loops join*.  You are running a query in there -- a keyed
read, table access by rowid after an index range scan.  And you do it MILLIONS
of times (so you say).

There is *no way* that'll be faster.

      Rating:
      4
Thanks  March 17, 2003
Reviewer:
      Yan  from Canada
Thanks Tom, i forgive to talk about group by but i will get back here with more
detail..
For the kind of situation i was talking about you where real right.
Thanks

March 17, 2003 Antonio -- Thanks for the question regarding "How
      to retrieve values from bind variables on other user sessions.", version
      8.1.5

      You Asked
Hello Tom,

We have an application here that is updating a date field with invalid
values (we are always getting error ora-01801).

This application has been developed using Borland/inprise DELPHI and the
developer could not find the incorrect value being generated. We have already
identified the sql statments that change that field but all of them uses bind
variable. So, is it possible to see what
values are on an user bind variable (the user is still logged on) ?

Thanks for your answer.

A.Klauss

      and we said...
Hmm, in 815 -- no, not easily.  We added a function to dbms_system in a later
release (816 i believe, 817 definitely) that lets us set events in other
sessions.  Using this, we could turn on sql_trace with "bind variable" dumping.

What you could do is use a LOGON trigger:

ops$tkyte@ORA815.US.ORACLE.COM> grant alter session to ops$tkyte;
Grant succeeded.

the user NEEDS DIRECT grant alter session!!!  else the trigger will fail

ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM> create trigger logon_trigger
  2  after logon on schema
  3  begin
  4          execute immediate
  5   'alter session set events ''10046 trace name context forever, level 12''';
  6  end;
  7  /

Trigger created.

Now, the very next time the owner of the trigger logs in, a trace file will be
generated.  So, for example, I just:

Connected to:
Oracle8i Enterprise Edition Release 8.1.5.0.0 - Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.0.0 - Production

ops$tkyte@ORA815.US.ORACLE.COM> variable x number
ops$tkyte@ORA815.US.ORACLE.COM> select * from dual where dummy = to_char(:x);

no rows selected

After exiting, I can find the trace file (in the user_dump_destination -- an
init.ora parameter).  and in that trace file, we'll see:

====================
PARSING IN CURSOR #3 len=45 dep=0 uid=40 oct=3 lid=40 tim=0 hv=2047848565
ad='80e96110'
select * from dual where dummy = to_char(:x)
END OF STMT
PARSE #3:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=0,tim=0
BINDS #3:
 bind 0: dty=2 mxl=22(22) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=24
offset=0
   bfp=016b07bc bln=22 avl=00 flg=05
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=0

If you have my book -- I go through in detail what this all means in chapter 10.
 In particular, I say:

oacflg    Internal flags. If this number is odd, the bind variable might be null
(allows nulls).

so that shows us X was null this time.  If I:

ops$tkyte@ORA815.US.ORACLE.COM> variable y varchar2(20)
ops$tkyte@ORA815.US.ORACLE.COM> exec :y := 'abcdef'

PL/SQL procedure successfully completed.

ops$tkyte@ORA815.US.ORACLE.COM> select * from dual where dummy = :y;

no rows selected

I'll find in the trace file:

PARSING IN CURSOR #3 len=36 dep=0 uid=40 oct=3 lid=40 tim=0 hv=3408919760
ad='80dbbe58'
select * from dual where dummy = :y
END OF STMT
PARSE #3:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=0,tim=0
BINDS #3:
 bind 0: dty=1 mxl=32(20) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=32
offset=0
   bfp=016a0d90 bln=32 avl=06 flg=05
   value="abcdef"
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=0

the actual runtime value.

Hope this helps
Review & Followup

      Rating:
      4
  June 14, 2002
Reviewer:  ANTONIO
      KLAUSS  from Santos, Brazil
Very useful piece of code. Thank you!

      Rating: 5
Much simpler
      way exists  October 28, 2002
Reviewer:
      Oleksandr Alesinskyy  from Bad Soden am Tunus, Germany,

In case of Delphi (and exactly this was in the orginal question) much simpler
(while much less general) solutions exist. Delphi contain SQL*Monitor utility
that do all that is needed. All that is needed to do - start this utility, turn
on monitoring of bind variable values and specify logfile, then start
application. All statements with all bind variable values ( both passed to DB
and returned from it) would be in that file. This is true if application use
BDE, in other case, if it use e.g. DOA, Debug property of DOA component that
represent statement in question should be set to true (I'm not 100% sure about
the property name -it  may slightly differ, but it is very clear) and
application recompiled. Then during application run before statement execution
popup window with all bend variable values would appear.

Yours sincerely,

Oleksandr Alesinskyy

          Followup:

Excellent -- thanks!

      Rating: 4
Thanks for the
      insight  October 29, 2002
Reviewer:  MDF
       from Minneapolis, MN
The use of the Logon Trigger and "set events" was very
insightful.  I can add this to my toolbox in the future...
We also use DOA (Direct Oracle Access) within Delphi,
and have used (extensively) the Oracle Monitor program
to watch SQL and Bind Variables as they are sent to
our Oracle database.  It has saved us many hours of work
in tracking down problems...

      Rating: 5
Might be null
      -- any way to determine for certain?  March 17, 2003

Reviewer:  Glenn A. Santa Cruz  from Mobile, AL USA

Tom -

Is there any certain way to determine if the bind variable is null?  You'd
mentioned that an odd number in the oacflg value means the variable *might* be
null; I presume the  combination of odd oacflg and avl=00 would definitely mean
a null value, but can we be certain about this?  Could you show situations where
oacflg is odd and the bind variable is not null?

Thanks

          Followup:

when I said "might be" -- it should be read as "could be, allowed to be"

the avl does it for you.

      Rating: 4
This may
      work  March 17, 2003
Reviewer:  gs

Hope the following may work in 8.1.5

sql> connect / as sysdba ;
sql> oradebug setospid <SPID from v$process>
sql> oradebug event 10046 trace name context forever, level 4

I am not sure level 4 gives bind variables. But 12 definitely does

      Rating:
      4
Tracing  March 18, 2003
Reviewer:
      Mike  from St. Louis, MO
For what it's worth, set_ev() is another option that works well.  It works for
earlier version of 8 as well as 7.3.  Of course, Tom's example via login trigger
is ideal if you wish to capture all SQL vs. fighting the clock to find the
session sid/serial and then executing the set_ev() or oradebug.

-- 1   Standard SQL_TRACE functionality
-- 4   As level 1 plus tracing of bind variables
-- 8   As level 1 plus wait events
-- 12  As level 1 plus bind variables and wait events.

execute sys.dbms_system.set_ev( <sid>,<serial>, 10046, 4, '');

      Rating:
      5
fine  August 19, 2003
Reviewer:
      Vera  from Russia
Thanks. Very usefull.

March 17, 2003 Lon -- Thanks for the question regarding "PLSQL
      function returning object type causes full table scan", version 9.2.1

      You Asked
Tom,

I have a function that takes a comma delimited string of numbers and stuffs them
into an object table and then returns an object type. Whenever I use it in a
join against another table it causes a full table scan against that table even
if I have an index on the joining column. Here's the code for the function:

CREATE OR REPLACE TYPE TINLIST IS TABLE OF NUMBER;
/

CREATE OR REPLACE PACKAGE BUILDINLIST AS

/* PUBLIC PROCEDURES */

FUNCTION IN_LIST (pString IN VARCHAR2) RETURN TINLIST;

END BUILDINLIST;
/
show errors;

CREATE OR REPLACE PACKAGE BODY BUILDINLIST AS

FUNCTION IN_LIST (pString IN VARCHAR2)
return TINLIST
IS
    vData         TINLIST := TINLIST();
    vString       LONG DEFAULT pString || ',';
    vIndex        NUMBER;
BEGIN

    loop
        exit when vString is null;
        vData.EXTEND;
        vIndex := instr (vString, ',');
        vData(vData.COUNT) := substr (vString, 1, vIndex - 1);
        vString := substr (vString, vIndex + 1);
    end loop;
    return vData;
END;
END BUILDINLIST;
/
show errors;

And here is the query:
SELECT a.* FROM pendingedits a where a.job_name = 'Testing' and a.g3e_fid in
(select COLUMN_VALUE from table(cast
(buildinlist.in_list('1321635,1322087,1321572,1322088,1321635,1321572,1322088,132
2087,1321635,1322087') AS TINLIST)));

My question is:

Am I doing something stupid here or is that the way Oracle handles this
scenario?

Thanks,

      and we said...
well, it is a side effect of the lack of stats on that table function.  The
optimizer is defaulting it to return 8,166 rows:

ops$tkyte@ORA920> create table pendingedits
  2  ( job_name varchar2(25), g3e_fid number );

Table created.

ops$tkyte@ORA920> create index g3e_fid_idx on pendingedits(g3e_fid);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_stats.set_table_stats( user, 'PENDINGEDITS', numrows
=> 100000, numblks => 10000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> variable x varchar2(255)
ops$tkyte@ORA920> exec :x :=
'1321635,1322087,1321572,1322088,1321635,1321572,1322088,1322087,1321635,1322087'
;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ps$tkyte@ORA920> select /*+ first_rows */ COLUMN_VALUE
  2    from table(cast (buildinlist.in_list(:x) AS TINLIST));

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=11 Card=8168)
   1    0   COLLECTION ITERATOR (PICKLER FETCH) OF 'IN_LIST'

Here, I do two things

a) use first rows optimization (or optimizer_index_cost_adj).  It makes the CBO
happier to use an index

b) rownum in the subquery to materialize the result set into a temporary view.
This allows the distinct to happen AND makes it so the plsql function is called
once per query -- instead of once per row per query:

ops$tkyte@ORA920> SELECT        /*+ first_rows */ a.*
  2    FROM pendingedits a
  3   where a.job_name = 'Testing'
  4     and a.g3e_fid in ( select COLUMN_VALUE +rownum-rownum
  5                          from table(cast (buildinlist.in_list(:x) AS
TINLIST))
  6                                      )
  7  ;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=16374 Card=81680
Bytes=3267200)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'PENDINGEDITS' (Cost=2 Card=10
Bytes=270)
   2    1     NESTED LOOPS (Cost=16374 Card=81680 Bytes=3267200)
   3    2       VIEW OF 'VW_NSO_1' (Cost=11 Card=8168 Bytes=106184)
   4    3         SORT (UNIQUE)
   5    4           COUNT
   6    5             COLLECTION ITERATOR (PICKLER FETCH) OF 'IN_LIST'
   7    2       INDEX (RANGE SCAN) OF 'G3E_FID_IDX' (NON-UNIQUE) (Cost=1
Card=400)

ops$tkyte@ORA920> set autotrace off
ops$tkyte@ORA920>
Review & Followup

      Rating: 5
PLSQL function
      returning object type causes full table scan  March 17, 2003

Reviewer:  Lon  from Huntsville, AL USA
Thanks Tom!

That sort of explanation is exactly what I needed. We use this sort of function
a lot and it had been causing full table scans on some pretty big tables (>
1000000 rows). Which of course slows us down a lot! :)

      Rating: 5
Thank
      you  July 31, 2003
Reviewer:  Mark
       from Thetford, Norfolk UK
Many thanks, Tom.

We had a similar problem to Lon, but in our case the table function was being
used to return LIKE patterns that select from a small (half-a-million row) fact
table.

Our test queries now run between 2 and 4 times as fast.

March 18, 2003 sj -- Thanks for the question regarding "100
      partitions, their names, and there count(*)", version 8.1.7

      You Asked
Tom

Iam looking for a script , which will  list out for me the partition name and
the total number of records for a particular partition for a particular table.

Say for example if a table has 100 partitions how will you do the above, list
all the partitions and theier count(*).

thank you

      and we said...
Here is one method:

ops$tkyte@ORA817.US.ORACLE.COM> create or replace
  2  function get_rows( p_tname in varchar2 ) return number
  3  as
  4      l_columnValue    number default NULL;
  5  begin
  6      execute immediate
  7         'select count(*)
  8            from ' || p_tname INTO l_columnValue;
  9
 10      return l_columnValue;
 11  end;
 12  /

Function created.

ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> select table_name,
  2             partition_name,
  3             get_rows( table_name || ' partition(' || partition_name || ')' )

                                                                        row_cnt
  4    from user_tab_partitions
  5  /

TABLE_NAME                     PARTITION_NAME                    ROW_CNT
------------------------------ ------------------------------ ----------
PARTITIONED                    PART_VK_20010803                        0
PARTITIONED                    PART_VK_20010804                        0
PARTITIONED                    PART_VK_20010805                        0
PARTITIONED                    THE_REST                                0
PARTITIONED                    PART_VK_20010806                        0
PARTITIONED                    PART_VK_20010807                        0

6 rows selected.
Review & Followup

      Rating: 5
100 partitions,
      their names, and there count(*)  March 17, 2003

Reviewer:  shanmugam  from Singapore
I can able to access  the partitioned table via DBLINK.
Say, select count(1) from Partitioned_table@DLEZRATOCALEB;
But can I access the particular partition records through dblink. Say,
select count(1) from Partitioned_table partition(CY02MONTH9)@DLEZRATOCALEB;
Is it possible?

          Followup:

ps$tkyte@ORA815> select * from t@ora817dev partition(part_1);
select * from t@ora817dev partition(part_1)
              *
ERROR at line 1:
ORA-14100: partition extended table name cannot refer to a remote object

You would need to create a view on the remote site that hid the fact it is a
partition you are querying.

  1* create or replace view v as select * from t partition(part1)
ops$tkyte@ORA817DEV> /

View created.

ops$tkyte@ORA815> select * from v@ora817dev
  2  /

no rows selected

March 18, 2003 pasko -- Thanks for the question regarding "How
      to Handler Errors when Function returns only Ref Cursors", version 9iR2

      You Asked
Hi Tom,

1.
I am not sure  how to do this, but i am asking how to return error string and
error code to the PERL Client who is calling Oracle Database  Function which
returns Ref Cursors .

For example :

function test_return_refcursor( p_id IN  varchar2 )
return  sys_refCursor
is

c sys_refCursor ;

begin

open  c  for
select * from test_tab
where id =  p_id ;

--Will this use p_id as a BIND variable for  parameter p_id  ?
--or do i have to use the USING clause...?

return c ;

Exception when others then

--how to return here
--the client only wants to see the error
--no raise needed
-----****---------
-- is this right ??
----*****---------
/*
open c for 'select :some_err_string ,
                  :some_err_code
from dual '
using sqlcode , sqlerrm ;

return c ;

*/

end ;
/

2.
 Using the New Oracle 9i Feature of inserting record types :

for example :

  create table test_table ( id  varchar2(30) , val1 varchar2(30) ) ;

  declare

  l_row   test_table%rowType ;

  begin
  l_row.id := 'some date' ;
  l_row.val1 := 'some_value ' ;

  insert into test_tab
  values l_row ;

  --the above is OK..
  --But if i specify the table columns names ,
  --it doesn't seem to work..

  insert into test_tab ( id , val1 )
  values l_row ;

 --is it necessary to specify table column names ?

 end ;
/

Thanks in advance and

have a nice Weekend :)

      and we said...
you should let the client get the error message and code NATURALLY.

There is no reason on the planet that code should be any different from:

function test_return_refcursor( p_id IN  varchar2 ) return  sys_refCursor
is
   c sys_refCursor ;
begin
   open  c  for
   select * from test_tab
   where id =  p_id ;   /* yes, p_id is a bind */

   return c ;
end ;
/

anything else will be BUGGY -- the client will get data and not be able to
differentiate SUCCESS from FAILURE.

It is either the whole row in the record or not, so you would not need to
(cannot) specify them.

also note they are POSITIONAL -- not by name:

ops$tkyte@ORA920> create table test_table ( id  varchar2(30) , val1 varchar2(30)
) ;

Table created.

ops$tkyte@ORA920> create table test_table2 ( val1  varchar2(30) , id
varchar2(30) ) ;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2
  3    l_row   test_table%rowType ;
  4
  5  begin
  6    l_row.id := 'id' ;
  7    l_row.val1 := 'val';
  8
  9    --the above is OK..
 10    --But if i specify the table columns names ,
 11    --it doesn't seem to work..
 12
 13    insert into test_table2 values l_row;
 14    insert into test_table values l_row;
 15
 16   --is it necessary to specify table column names ?
 17
 18   end ;
 19  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from test_table;

ID                             VAL1
------------------------------ ------------------------------
id                             val

ops$tkyte@ORA920> select * from test_table2;

VAL1                           ID
------------------------------ ------------------------------
id                             val

Review &
      Followup

      Rating: 5
I think for a
      single Table Defined Record Type ....  March 17, 2003

Reviewer:  pasko  from HH,Germany
Hi Tom,

thanks very much for your Response...

but take a look at this ...i think  Named Notation is  used here ?

drop table test_table
/
create table test_table ( id  varchar2(30) ,  pos_val1_here  varchar2(30) ,
val1 varchar2(2000) )
/
declare

l_row   test_table%rowType ;

--  note :  l_ROW from Test_Table

begin

l_row.id   := ' ID ' ;
l_row.val1 := ' val1 : if  POSITIONAL , then this should''ve been inserted in
Table COLUMN:  pos_val1_here ,  right ?   ' ;

--to test for positional
--l_row.pos_val1_here := ' positional testing  ' ;

insert into test_table
values   l_row ;

--I think if the Record Type is from a Single Table   then ,
--  Named Notation still holds true ?

end ;
/
select * from test_table ;

SQL> select * from test_table ;

ID    POS_VAL1_HERE        VAL1
----- -------------------- --------------------------------------------------
ID                         val1 : if  POSITIONAL , then this should've been
                           inserted in  Table COLUMN:  pos_val1_here ,  right
                            ?

          Followup:

er -- take a look again.  You seem to be confusing something here.

You have a table with columns:

id, pos_val1_here, val1

You have a record with attributes:

id, pos_val1_here, val1

Those attributes in that order will be inserted into the table.  Do this:

create table t1 ( id, pos_val1_here, val1 )
create table t2 ( id, val1, pos_val1_here )

Then

declare
    l_rec    t2%rowtype;
begin
   ...
   insert into t1 values l_rec;
end;

and then see what happens.  You seem to be thinking the order of assignment to
the record is somehow relevant -- but it doesn't mean anything here.

      Rating:
      5
Follow-up  March 17, 2003
Reviewer:
      pasko
Hi Tom,

I will try to explain again.....

In your example above , you are using  TWO Tables ...but in mine i am using only
ONE table .

I do not want to declare a record Type from table1 and insert this into Table2 .

My argument is :

IF I  HAVE :

declare
    l_rec    t2%rowtype;
begin
   ...
   insert into T2  values l_rec;
end;

Then , i think  here NAMED Notation is used .
Not  POSITIONAL  as you said .
and  my earlier example demonstrates this.

Thanks .

          Followup:

arg -- you are not getting it.

You are experiencing what is known as "false causality".  I observe A and then I
observe B -- A must cause B right?  NOT.

you just happen to have a record T2%ROWTYPE which just happens to have the same
field names in the same order as the table you are inserting into.

The insert is done positionally (I proved that -- I changed the order of the
attributes in the record and lo and behold -- they went in POSITIONALLY -- not
by name).

Your insert is done positionally -- it is just serendipitous in your case that
the ORDER of the attributes in the record match the ORDER of the columns by name
-- giving you the false illusion that it is done by name.

Your earlier example does NOT demonstrate it is done by name -- not any more
then a single flip of a coin that lands on heads (or dozens of repeated flips)
"proves" that the coin always lands on heads.

I used a proof that shows conclusively -- it is NOT done by name.
You showed if the names are the same in the same order, it appears to be by
name.

      Rating:
      5
pasko  March 17, 2003
Reviewer:  Tony
      Andrews  from London, UK
Pasko,

You put the text string into l_rec.val1, which is the 3rd field of l_rec.  It
got inserted into val1, which is the 3rd column of the table.

Your example doesn't prove anything either way, since the column names and
positions matched up anyway.  You need to do a test where the names are the same
but the positions are different in the record compared to the table - which is
what Tom did.

      Rating: 5
All in all
      ...is there any danger of column interchange...  March 17,
      2003
Reviewer:  pasko  from HH.Germany
Hi Tom,
thanks again for your follow-up ....

is there any danger of inserting coumn values into wrong column names when using
this Notation ,

assuming i am  defining a separate record type for each Table   and i am not
mixing  record types of different tables like your example above , which
indicated interchange of column names and their values.

i.e :  of the form

declare
l_tab1_rec_type  tab1%rowType ;
begin

--assing values to  l_tab1_rec_type  here

insert into tab1  values
l_tab1_rec_type  ;

end ;

Thanks .

          Followup:

Yes, there is serious change of column interchange UNLESS you

a) always use a record type per table
b) use a record based on TABLE%ROWTYPE;

if you define your own record type, use record types across different tables --
watch out!

      Rating: 5
Thanks
      ....  March 18, 2003
Reviewer:  pasko
       from HH,Germany
Hi Tom ,

Thanks..

in  that case i have to :

 a) always use a record type per table

and never WORRY about column interchange.

...Do the same issues come into play when  we use the NEW 9i
update syntax :

  update table_name
  set  ROW  = record Type
  where  condition

 I know that in this case sub-query is not allowed in the SET clause...but what
are other side-effects we should watch out  for .

Thanks in advance .

          Followup:

Yes, it is also done positionally, not by name:

ops$tkyte@ORA920> create table t1 ( id int, x varchar2(5), y varchar2(5), z
varchar2(5) );

Table created.

ops$tkyte@ORA920> create table t2 ( id int, z varchar2(5), y varchar2(5), x
varchar2(5) );

Table created.

ops$tkyte@ORA920> create table t3 ( id int, y varchar2(5), x varchar2(5) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t1 (id) values ( 1 );

1 row created.

ops$tkyte@ORA920> insert into t1 (id) values ( 2 );

1 row created.

ops$tkyte@ORA920> insert into t1 (id) values ( 3 );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          l_t1_rec t1%rowtype;
  3          l_t2_rec t2%rowtype;
  4          l_t3_rec t3%rowtype;
  5  begin
  6          l_t1_rec.id := 1;
  7          l_t1_rec.x  := 'x';
  8          l_t1_rec.y  := 'y';
  9          l_t1_rec.z  := 'z';
 10
 11          l_t2_rec.id := 2;
 12          l_t2_rec.x  := 'x';
 13          l_t2_rec.y  := 'y';
 14          l_t2_rec.z  := 'z';
 15
 16          l_t3_rec.id := 3;
 17          l_t3_rec.x  := 'x';
 18          l_t3_rec.y  := 'y';
 19
 20          update t1 set row = l_t1_rec where id = l_t1_rec.id;
 21          update t1 set row = l_t2_rec where id = l_t2_rec.id;
 22          update (select id, x, y from t1 ) set row = l_t3_rec where id =
l_t3_rec.id;
 23  end;
 24  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from t1;

        ID X     Y     Z
---------- ----- ----- -----
         1 x     y     z
         2 z     y     x
         3 y     x

      Rating: 5
Thanks very
      much Tom...  March 19, 2003
Reviewer:  pasko

Hi Tom ,

thank you very much for your Response and follow-up..

i will be using only Queries of the form :
update t1 set row = l_t1_rec where id = l_t1_rec.id ;

to avoid  column name/column values interchange.

Have a nice Day  :)

March 18, 2003 Sriram -- Thanks for the question regarding
      "Comparison of Oracle DBA on Unix and Windows 2000", version 9.0.1.1.1

      You Asked
How do you Compare a Oracle DBA on Unix Platform and Win2000 Platform.
Is there any Difference in your Opinion.

Which Platform is preferred ( Win2000 or Unix ) under which conditions

What is the Suggested way to improve your skills in Unix Platform using Desktop
PC .

Regards

Sriram

      and we said...
To me -- they are the same.

Unless of course you expect your DBA to also be the system administrator, the
person who fixes the printer, and services the file server down the hall.  Then
they are different.

I remember the first time I touched windows way back when.  As soon as I got the
the command line -- all was the same.  I could do whatever I needed (because the
tools are the same).

Now, if you asked me to create a new domain or add a user to the OS -- forget
about it...

Ask me to do database stuff -- no worries.

As to which platform is preferred?  That is a matter of opinion.  Me -- I have
no computers that run windows software.  Not my servers, not my desktops, not my
laptops...  So -- that speaks to my preference.

The suggested way to improve your unix skills with a desktop?  Here are my 3
easy steps:

a) fdisk and create one big partition on your hard disk
b) install linux
c) never look back....
Review & Followup

      Rating:
      4
  March 17, 2003
Reviewer:  Robert
       from Memphis, USA

      Rating: 3
Tom, the
      Windows/Oracle DBA....  March 17, 2003
Reviewer:
      robert  from PA
I got the picture that, everything else remaining the same,
if Tom were to be asked to become DBA on Windoz, he be
kicking and screaming.... :)

          Followup:

yes, that would be an appropriate picture.

      Rating: 4
The
      difference...  March 17, 2003
Reviewer:  Jeremy
      Smith  from Salt Lake City, UT
Is that on UNIX it's hard to keep things simple, and on Windows it's hard to be
smart.  This applies to being a DBA as well as every other kind of application
development.

On Windows, the environment provides you lots of fancy tools to make your life
easy.  It's got GUI managers for things, simple interfaces...things are usually
changed through a control panel or by a command from a menu.  To do what 75% of
people want to do, it's easy.  All this is true for Oracle, too.  It runs in a
couple of processes, all the tools work on it (TOAD, Designer, all of those
fancy monitoring/tuning products), and the install _tends_ to be easier than on
UNIX.

However, if you want to go in-depth, you're in bad shape.  First of all you'll
have to tackle the incomprehensible beast called the "Registry."  You'll have to
buy or download products to do certain tasks that are standard issue in UNIX.
There's no good built-in programming language, so you're at least going to have
to install and configure Perl if you want to automate or script anything.  It's
not multi-user, so you're off configuring terminal services or VNC or ssh or
something uglier if you want to do anything os-wise remotely.  And when things
go wrong, you can't get at the code, or figure out what's going on.
Troubleshooting operating system problems often degenerates to voodoo.  To have
a great understanding of the platform you're running on is extremely difficult.

In UNIX it's the opposite.  Some simple things require quite a bit of work.
You're expected to know much more as a _user_ of the operating system.  You
sometimes have to edit cryptic files, and sometimes you have to experiment quite
a bit.  You don't have all the tools available that windows users do (although
it's getting better very quickly), and you'll have to roll your own sometimes.
Some trivial actions require knowledge of a half-dozen tools and concepts, and
sometimes you'll be overwhelmed by the possibilities of all the ways you can do
the same task.

However, when you need to do something complicated, there are thousands of tools
and scores of programming languages available for free for you to do whatever
you want with.  The operating system tells you everything it knows...and
although it might take some experimentation, you're pretty much able to do
anything you want.  If you go with an open-source platform, you can even alter
the operating system to work best with your particular hardware and software (as
RedHat did for 2.1AS and Oracle)

If you look at the descriptions of the two platforms, and compare them with how
most would describe Oracle, you can see why most Oracle DBA's (and there are
exceptions) prefer UNIX operating systems.

          Followup:

Well, not sure I agree with the "on unix it is hard to keep it simple" part, but
I love the one windows it's hard to be smart!

      Rating: 5
Middle of
      nowhere  March 18, 2003
Reviewer:  Vinit
       from UK
I was working on Oracle since Oracle 6, I've almost worked on all the versions
upto 9.0.1. I'm good at pl/sql , Unix shell scripting on developmen side.
Could you please suggest me , now where should I head, towards DBA activity or
towards development usinf pl/sql.
I dont know where to go from here, please help.

          Followup:

Which do you like better...

      Rating: 4
Unix
      Knowledge  August 26, 2003
Reviewer:
      Praveen  from India
Hi Tom,

      This has been a good discussion. I know, with windows its simple to
administer as everything can be done using "Ok" or "Cancel". But how much unix
must a DBA need to know in order to administer a database on Unix operating
system.

March 18, 2003 alan -- Thanks for the question regarding "How do
      I move a control file when using an spfile?", version 9.0.1

      You Asked
Hi Tom,

I have a new 9.0.1 database on Win2k.  The control files were all put in the
same directory and I wish to move them to different disks.  Normally, this is
very easy.  However, I am using a spfile for my initialization parameters.

How do I change the locations of the controlfiles in the spfile without having
the database open?  I tried opening in nomount and nomount but couldn't get to
the spfile.

The only thing I can think of is to switch to a pfile, make the change in the
old fashioned way, and switch back to an spfile.

Thanks,

Alan

      and we said...
You can get to the spfile in the NOMOUNT stage.  Consider:

...
Connected to:
Oracle9i Enterprise Edition Release 9.0.1.2.0 - Production
With the Partitioning option
JServer Release 9.0.1.2.0 - Production

sys@ORA9I> show parameter control_files;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
control_files                        string      /d01/oradata/ora9i/control01.c
                                                 tl, /d02/oradata/ora9i/control
                                                 02.ctl
sys@ORA9I> shutdown immediate
Database closed.
Database dismounted.
ORACLE instance shut down.

sys@ORA9I> !

$ msu - oracle9i
Sun Microsystems Inc.   SunOS 5.7       Generic October 1998

$ cd /d01/oradata/ora9i

$ mv control01.ctl control_01.ctl

$ exit

sys@ORA9I> startup nomount
ORACLE instance started.

Total System Global Area  174359528 bytes
Fixed Size                   279528 bytes
Variable Size             134217728 bytes
Database Buffers           33554432 bytes
Redo Buffers                6307840 bytes
sys@ORA9I> alter system set control_files =
'/d01/oradata/ora9i/control_01.ctl','/d02/oradata/ora9i/control02.ctl'
  2  scope=spfile;

System altered.

sys@ORA9I> shutdown
ORA-01507: database not mounted
ORACLE instance shut down.

sys@ORA9I> exit
Disconnected from Oracle9i Enterprise Edition Release 9.0.1.2.0 - Production
With the Partitioning option
JServer Release 9.0.1.2.0 - Production

$ sqlplus / as sysdba

SQL*Plus: Release 9.0.1.0.0 - Production on Tue Mar 18 09:38:40 2003
(c) Copyright 2001 Oracle Corporation.  All rights reserved.
Connected to an idle instance.

idle> startup
ORACLE instance started.

Total System Global Area  174359528 bytes
Fixed Size                   279528 bytes
Variable Size             134217728 bytes
Database Buffers           33554432 bytes
Redo Buffers                6307840 bytes
Database mounted.
Database opened.

idle> show parameter control_files

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
control_files                        string      /d01/oradata/ora9i/control_01.
                                                 ctl, /d02/oradata/ora9i/contro
                                                 l02.ctl

March 18, 2003 Gururaj -- Thanks for the question regarding "How
      to select a particular rollback segment ?", version 8.1.4

      You Asked
Tom,

I am importing a set of huge tables and facing performance problems.
Here is my parameter file for the Import.

userID=ops$reload/reload
file=DKB5:[INTEMP]RCR_20020303.DMP
ignore=Y
commit=Y
buffer=20000000
grants=N
indexes=N
rows=Y
log=reload3.log
fromuser=ops$nice
touser=ops$reload
tables=list_of_tables

record length is around 350.

Could you tell me hoe to select the rollback segment, so that I can avoid having
commit=Y ?

Could help in choosing parameters to tune my import ?

Thanks
-Gururuaj

      and we said...
No version 8.1.4 was ever released, but anyway...

Just make them all the SAME SIZE.  All of your RBS's should be exactly the same
size -- let them grow if they need to.

The best way to speed up import -- use as many dmp's as you can (export by
schema or by table) and import as many as you can at the same time --
parallelize the operation.

and to make it even faster, you would do the DDL like creating indexes and
constraints manually -- using nologging, parallel, enable/rely type of options.

Review &
      Followup

      Rating: 3
Thanks
      Tom  March 18, 2003
Reviewer:  A reader

I missed .7 in between, it is 8.1.7.4 :-)

March 18, 2003 ted -- Thanks for the question regarding "how to
      enable plan stability for window 2000 oracle 8.1.6", version 8.1.6

      You Asked
thomas,

oracle8.1.6 under win2000. i have plan stability = false in
v$option. how do i enable it, if possible.

thnx ted chyn

      and we said...
The "getting to know oracle8i" guide lists all of the
features/options by release and whether they are available or
not.

Query Plan Stability is part of Oracle8i EE and Personal
Edition.

It is not available with SE (standard edition).  I have to
assume you have SE.  In that release, this feature is not
available.  Sorry.
Review & Followup

      Rating: 1
Enabling Plan
      Stability  March 18, 2003
Reviewer:  Ross
      Mulder  from United Kingdom
This question was an almost exact match for a search that I did on Plan
Stability, and had the answer contained more detail, it would have sorted out my
problem within seconds. Even though the person who asked the question may not
have had the correct edition of Oracle, a simple line in the answer saying:
'If you have the correct edition, then use the following:
........'

That would hjave answered any further questions to arise.

          Followup:

huh?

you cannot enable it.  you have to have a totally different set of software.

don't know what you wanted me to say here at all.

      Rating: 5
What Ross is
      saying...  March 18, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Tom,

My interpretation of what Ross is saying is this:

Even though the Original Poster did not have the correct edition of Oracle to
use Plan Stability, you should have said so, and then proceeded to answer the
question as if he did.  i.e. "You don't have EE, but if you did, this is how you
would use Plan Stability..."

Funny thing is, it seems that some other people at Oracle outsmarted you on this
one!  They wrote this document called the "Database Performance Tuning Guide and
Reference", and guess what?  There's a whole chapter called "Using Plan
Stability".  Here it is:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/outlines.htm#13547
That may be of some help because there's a section called "Enabling Plan
Stability"...

Of course, I could be off-base here...

- Mark

          Followup:

no fair stealing my attitude there ;)

      Rating:
      5
Thanks  March 19, 2003
Reviewer:  Ross
      Mulder  from London, United Kingdom
Mark,
Spot on with your interpretation! I had already found the page you referred to,
and attempted to alter the parameters mentioned in the section on enabling Plan
Stability, with errors. I've managed to get this sorted out now.

Unfortunately, I have now been informed that the client site is actually on
Standard Edition and not EE as originally stated, meaning that my research was
only for personal benefit.

March 18, 2003 Swapnil -- Thanks for the question regarding
      "Queries againt the partitioned table", version 9.0.1

      You Asked
Tom,

"The value of QUERY_REWRITE_ENABLED must be set to TRUE to potentially take
advantage of queries againt the partitioned table."

What is your opinion for above mentioned quote.

My understanding is that if my table is partitioned by date range and if I'm
querring the data using where date=somedate_in_part1 , Oracle re-writes the
querry to use the partition part1.
Please explain, Thanks in advance as always.

      and we said...
it does not "rewrite" the query.  It uses its knowledge of the partitioning
scheme in order to avoid having to access certain partitions.

it is not a query rewrite, it is just an access plan.

I do not see the tie in with query_rewrite_enabled and partitioning.

materialized views

function based indexes

sure, but not simple partitioning.
Review &
      Followup

      Rating:
      4
Thanks...  March 18, 2003
Reviewer:
      Swapnil  from UK

March 18, 2003 Lucy -- Thanks for the question regarding
      "parallel query on win2000", version 8.1.7

      You Asked
Hi, Tom,
Your site is great. I learned so much here. Thanks.

Here is my problem:
I am testing the parallel query on my 8.1.7 win2k box, but just couldn't make it
work.  I created a test table as you did

create table big as select * from all_objects;

insert /*+ append */ into big select * from big;
commit;

Everytime I tested, the parallel query is slow than the full table scan, also
when I turned on sql_trace, I saw a high number in the  fetch-disk and
fetch-query in the full table scan, could you tell me why.  Thanks you very much

the following is what I did:
SQL>  select /*+ FULL(big) PARALLEL(big,4) */ count(*) from big;

  COUNT(*)
----------
   6836224

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=12272 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     SORT* (AGGREGATE)                                        :Q22000
   3    2       TABLE ACCESS* (FULL) OF 'BIG' (Cost=12272 Card=6836224 :Q22000
          )

   2 PARALLEL_TO_SERIAL            SELECT /*+ PIV_SSF */ SYS_OP_MSR(COUNT(*)) F
                                   ROM (SELECT /*+ NO_EXPAND ROWID(A2)

   3 PARALLEL_COMBINED_WITH_PARENT

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

SQL> select count(*) from big;

  COUNT(*)
----------
   6836224

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=12272 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'BIG' (Cost=12272 Card=6836224)

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

SQL> show parameter  parallel

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
fast_start_parallel_rollback         string  LOW
optimizer_percent_parallel           integer 0
parallel_adaptive_multi_user         boolean FALSE
parallel_automatic_tuning            boolean FALSE
parallel_broadcast_enabled           boolean FALSE
parallel_execution_message_size      integer 2148
parallel_instance_group              string
parallel_max_servers                 integer 5
parallel_min_percent                 integer 0
parallel_min_servers                 integer 0
parallel_server                      boolean FALSE

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
parallel_server_instances            integer 1
parallel_threads_per_cpu             integer 2
recovery_parallelism                 integer 0
SQL>

select /*+ FULL(big) PARALLEL(big,4) */ count(*)
from
 big

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.03          0          0          0           0
Execute      1      0.00       0.00          0          0          3           0
Fetch        2      0.00      16.97          0          0          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.03      17.00          0          0          3           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: SYS

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
      0   SORT AGGREGATE
      0    TABLE ACCESS FULL BIG

********************************************************************************

select count(*)
from
 big

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      1.58      13.05      63104      80852          6           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      1.60      13.06      63104      80852          6           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: SYS

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
6836224   TABLE ACCESS FULL BIG

********************************************************************************

      and we said...
are you running things as SYS????  stop that right now - sys is special, sys is
magic, sys is ours.  Don't use it -- even to test.  (same with system and the
other accounts).  Create your own!

Parallel query is a tool - it is not "fast=true".  I would say in this case --
you have a reletively low end box, probably one disk.  You set 4 processes
against this disk on I don't know how many cpus.  They contend with eachother.
They have to be coordinated (PQ involves more work then serial).  Your problem
just isn't "big enough" to benefit from parallel processing.

Review &
      Followup

      Rating: 4
how about the
      high number in the fetch-disk and fetch-query in the full table
      scan  March 18, 2003
Reviewer:  lucy
       from new york, usa
Thanks, Tom.

I noticed a high number in the  fetch-disk and
fetch-query in the full table scan, and they are 0 for parallel query, could
you explain this?

Thanks again

          Followup:

you did things as sys, everything is suspect.  don't do things as sys.

Besides the PQ stuff is done by other processes, it doesn't rollup in tkprof.
you got the stats from the query co-ordinator.  It didn't do any of the work.

now, if you used a real user -- autotrace would become much more useful...

big_table@ORA817DEV> set autotrace on
big_table@ORA817DEV> @count big_table 4
old   1: select /*+ FULL(&1) PARALLEL(&1,&2) */
new   1: select /*+ FULL(big_table) PARALLEL(big_table,4) */
old   3: from &1
new   3: from big_table

  COUNT(*)
----------
   1576192

Elapsed: 00:00:28.42

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     SORT* (AGGREGATE)
                              :Q348000
   3    2       TABLE ACCESS* (FULL) OF 'BIG_TABLE' (Cost=3 Card=1576192)
                              :Q348000

   2 PARALLEL_TO_SERIAL            SELECT /*+ PIV_SSF */ SYS_OP_MSR(COUNT(*)) F
                                   ROM (SELECT /*+ NO_EXPAND ROWID(A2) */ 0 FRO
                                   M "BIG_TABLE" PX_GRANULE(0, BLOCK_RANGE, DYN
                                   AMIC)  A2) A1

   3 PARALLEL_COMBINED_WITH_PARENT

Statistics
----------------------------------------------------------
        184  recursive calls
        479  db block gets
      22872  consistent gets
      22822  physical reads
        652  redo size
        370  bytes sent via SQL*Net to client
        425  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          7  sorts (memory)
          0  sorts (disk)
          1  rows processed

big_table@ORA817DEV> select /*+ FULL(b) */ count(*) from big_table b;

  COUNT(*)
----------
   1576192

Elapsed: 00:00:31.31

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=3 Card=1576192)

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

autotrace will roll it up

      Rating: 4
PQ
      tracing  March 18, 2003
Reviewer:  GunS
       from Russia
You've wrote: "Besides the PQ stuff is done by other processes, it doesn't
rollup in tkprof.
you got the stats from the query co-ordinator.  It didn't do any of the work."

  Do you know any way how to get traced these 'other processes'?

          Followup:

autotrace picked it up for you

March 18, 2003 Beth -- Thanks for the question regarding
      "Switching from Rule-based optimizer to Cost-based", version 8.1.6

      You Asked
In reference this discussion:

http://asktom.oracle.com/pls/ask/f?p=4950:8:275388::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:879828062602,%7Boptimizer%7D%20and%20%7Brule%7D%20and%20%7Bcost%7D
You mentioned that the result of moving from the RBO to CBO is easy enough to
evaluate.  How is this?

If it's possible that it won't increase performance, or stay the same--how can
you be sure that some query or app won't slow to an unacceptable response time,
when analyzing tables for the first time.  Does this indicate that you must test
every query, every application to make sure that something doesn't result in
horrible performance?  Is there a way to predict this?  Or is it a matter of
"try it and see if the clients complain?"  Or is it guaranteed that the
performance WON'T decrease?

Also, we are currently using the default CHOOSE.  So, I understand that queries
are using CBO anyway, but just gathering the statistics on the fly, not from the
data dictionary.  But that these on-the-fly statistics are less accurate.  Does
this mean if we analyze all of our tables and indexes, that we won't see hardly
ANY difference in performance?

Lastly, is there a way to discover any existing queries that are written
specifically as Rule-based queries, that might become a problem after analyzing?

Oh and is there a method to figure out how often to gather statistics, or only
when the data changes enough that you see a performance slow-down and realize
they are out of date?

Thanks.

      and we said...
I say its easy enough to test cause its trivial to put back to RBO.

You can

o set the init.ora parameter optimizer_goal to rule
o delete the statistics
o use an on logon trigger to set certain sessions (applications) to rule until
you figure out how to fix them.

As with ANY large change in a production environment I would hope you would
regression test -- just like you would when applying a patch or an upgrade.

when you use CHOOSE, Oracle will use RBO if there are no stats, CBO if there are
(unless you access an object that requires CBO such as an IOT, partitioned
table, table with parallel set, etc).  So, in choose mode you are using RBO
without stats.

If you analyze all of your tables, indexes etc -- expect changes -- both good
and bad. You will *need* to test.

There is no such thing as a "rule based query" (well, you can use the RULE hint
but thats really not the same).

There is a method to automate the gathering of stats only for objects that need
it.  See the "ALTER TABLE MONITORING" and search for

monitoring

on this site for details.
Review & Followup

      Rating: 3
Oracle still
      chosen CHOOSE even no stats  April 28, 2002

Reviewer:  Mike  from GA, USA
I creates a brand new table tt, no stats, But Oracle still chooses the CBO
(CHOOSE).
Why? Unless RULE hint applied.

sql> create table tt as select * from dba_objects;

Table created.

sql> create index tt_oi_idx on tt(object_id);

Index created.

sql> /* there is no statistics collected */
sql> show parameter optimizer_mode

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
optimizer_mode                       string  CHOOSE

sql> select count(*) from tt where object_id>12000;

 COUNT(*)
---------
     8886

sql> select /*+ RULE */ count(*) from tt where object_id>12000;

 COUNT(*)
---------
     8886

TKPROF output:

select count(*)
from
 tt where object_id>12000

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.02          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.01       0.10         21         22          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.02       0.12         21         22          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 22

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
   8886   INDEX RANGE SCAN (object id 21570)

********************************************************************************

select /*+ RULE */ count(*)
from
 tt where object_id>12000

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.01       0.01          0         22          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.02       0.02          0         22          0           1

Misses in library cache during parse: 1
Optimizer goal: RULE
Parsing user id: 22

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
   8886   INDEX RANGE SCAN (object id 21570)

********************************************************************************

          Followup:

CHOOSE means choose -- that means "i will use the RBO if no stats are present
and no constructs like partitioning are used -- some constructs force the use of
CBO regardless".

In the above -- CHOOSE *is in fact rule* since you have a simple table with no
statistics.  The second query with RULE shows RULE for the simple reason that
you "forced" rule to be used.

You can see this using AUTOTRACE better perhaps:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int primary key );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select rownum from all_users;
34 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain;

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where x < 5;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   INDEX (RANGE SCAN) OF 'SYS_C006757' (UNIQUE)

there I can tell RBO was used simply by the lack of CARD/COST/BYTES.. see
below for example of CBO with same query..

ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ RULE */ * from t where x < 5;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   INDEX (RANGE SCAN) OF 'SYS_C006757' (UNIQUE)

Again, RBO, not CBO.  Now a tkprof would say the first was run with choose
(and it WAS, that is not at all inaccurate) and the second with RBO -- that
again is correct, neither contradicts the other!

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;
Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where x < 5;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=5 Bytes=10)
   1    0   INDEX (RANGE SCAN) OF 'SYS_C006757' (UNIQUE) (Cost=1 Card=5
Bytes=10)

that additional info will appear ONLY when using the CBO.  That shows that in
this case, choose means CBO, not RBO.  The tkprof would still show choose and it
would be CORRECT.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ RULE */ * from t where x < 5;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   INDEX (RANGE SCAN) OF 'SYS_C006757' (UNIQUE)

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off

and back to rule again, nothing is amiss here, what you see is both normal and
correct

      Rating: 4
How to find out
      RBO or CBO  March 04, 2003
Reviewer:
      A reader
Hi Tom,
 Your explanation on this topic has impressed me a lot. Now I have a scenario
like this.
My database has the optimizer mode as CHOOSE.
Out of 700+ tables, only 10 major tables are analyzed. Now I would like to
analyze all the tables.
My queries are:

1. Now I want to know with the current setup, how many sqls are choosen the RBO
or CBO. In the V$sql view, we have a column optimizer_cost, can I assume that,
at any particular point of time, the number of records in this view where the
optimizer_cost=0 will give me the information that these many sqls are choosen
for RBO. The same exercise I would like to do after analyzing all the tables, so
that I will come to know that all my sqls are using CBO.

2.There are 3 application users in my database. First I would like to analyze
all the objects for one user. So When I have tried this procedure

dbms_stats.gather_schema_stats('username')

in my development server, I found that only the tables are analysed and not the
indexes( This info i have seen from the user_tables and user_indexes). So please
let me know which procedure I have to use inorder to analyze one schema.

Thanks in Advance
SUBBARAO.

          Followup:

1) well, you need look at optimizer_mode as well, a cost of zero is a valid cost
for a CBO query.  Need to make sure optimizer mode was rule or choose.  But it
seems like a useless statistic to have at hand.  Why does it matter if you have
5032 queries using RBO and 5254 using CBO -- what can you do based on that fact?

I would not rely on this count 100%, even under choose -- a cost of zero is
valid for a CBO query.  In 9i v$sql_plan will unambigously help us out here (the
cost/card/bytes are only present for cbo queries)

2) add cascade=>true  (you can read the supplied packages guide to discover all
of the options for dbms_stats)

      Rating: 4
Measuring the
      performance after analyze  March 06, 2003

Reviewer:  A reader
Hi Tom,

 My Intention is not to collect the count, My main intention is this, Now that i
didnt anlayze 99% of my tables in my database, Now I want to analyze all the
objects. There is some way to measure out the performance after analyzing the
table right?. My boss is not understanding the advantage of analyzing the
tables. So I should prove him that after anlayzing this is the status.
I am not asking this mainly to show it to my boss, whereas even I would like to
measure the performance difference.

Thanks in advance
SUBBARAO.

          Followup:

you are not doing this in production right -- this is a test system, where you
have scripts setup to benchmark your application, report back that things work
correctly, how fast or slow things went right?

cause if this is a production box and you are just going to do this -- expect to
have a very very very long week ahead of you.

But anyway -- on you TEST system, collect some statspacks during a normal test
load, analyze, collect statspack again and compare the outputs.

      Rating:
      3
Clarifications.  March 06, 2003
Reviewer:
      A reader
Hi Tom,
I am planning to do this in the productions system. Right now  I have already
collected the statspack for a weeks time, after anlayzing also I will collect
the statspack and compare the outputs.

If it is a production system  why you are telling this

"cause if this is a production box and you are just going to do this -- expect
to have a very very very long week ahead of you."

What kind of problem you are expecting in a general OLTP system. And mainly what
figures we need to look at in the statspack. What I thought is to see only the
expensive SQLS in the statspack. is it enough?

Thanks in Advance
SUBBARAO

          Followup:

the problems you would expect doing this to a production system are not a
concern if you do this to a test system

anytime you do something untested to production, anything at all, expect a very
very long week.

      Rating: 5
Is RBO the same
      as hint /*+RULE*/?  March 18, 2003
Reviewer:  A
      Reader  from USA
I learn that the RBO will be eliminated in future Oracle release and we are
trying to use CBO as much as possible.  The optimizer_mode set to choose in our
database.  We have statistics on every table and we update the statistics to
keep them update.  I assume that would lead to the use of CBO.  However, there
are some SQL queries, we cannot make them run faster unless we use the /*+RULE*/
hint (even I just analyzed all the tables involved).  Using other CBO hint like
/*+FIRST_ROWS*/ etc. will make the performance worse.  My question:  is the
/*+RULE*/ hint going to go away in future release?  Is the hint he same as using
RBO and should we use it?
Thank you!

          Followup:

You need to find out *why* the queries are not functioning well with CBO.  Using
the RULE hint uses the RBO and the RBO is *gone* in the next release.

read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6601251003901
and test out optimizer_index_cost_adj -- see what that does for you.

      Rating: 3
RBO *gone* ?
      Rumor?  March 18, 2003
Reviewer:  Jeff Evans
       from Salt Lake City, UT USA
You wrote:

You need to find out *why* the queries are not functioning well with CBO.  Using

the RULE hint uses the RBO and the RBO is *gone* in the next release.

Does this mean that the next major release of Oracle will not have the RBO?
I've heard rumors of this but I haven't ever got 100% confirmation.

          Followup:

RBO is gone.  Metalink announced this a while ago.

March 18, 2003 Robert -- Thanks for the question regarding "How
      to Monitor Object Usage", version 8.1.7

      You Asked
Hi Tom,

Sorry to hear about your publisher, hope everything works out well for you with
this situation...

I want to monitor object usage in my database so I can know which objects are
not being used (within 3, 6, 12 month, etc. span), so I can drop them.

I have seen a script on the internet (dbazine.com ?) which monitors v$bh and
v$db_object_cache to determine this.

Do you think using these views is a 'foolproof' method to determine object
usage, all things being equal... Or do you have any other suggestions.

Thanks,

Robert.

      and we said...
auditing - you want auditing enabled.  v$ stuff -- gets reset on restarts, ages
out, etc -- good for a "high level view".  100% dead on?  not a chance.

but -- this is a very suspicious way to approach this problem, drop things that
haven't been used in a while.  Hmmm.  Me thinks there are better, more
structured approaches to those unused objects.  I cannot imagine a circumstance
where I would be tempted to remove stuff cause no one used them.  I would want a
slight better handle on my database.  Start by asking "what the heck is this"
and getting an answer and recording it.  things you don't get answers for --
hey, rename them and see who gives you a call (only kidding -- sort of).

This is a documentation/system managment problem -- not a technical problem.
Review &
      Followup

      Rating: 3
Follow
      up...  March 18, 2003
Reviewer:  Robert
       from Memphis, USA
Tom,

Thanks for your insight into this, I am especially prone to the rename
suggestion, but please let me clarify and confirm..

Comment 1) I am attempting to get feedback from Developers on some of these
mystery tables, and we admittedly have a bit of a mess, in that there are tables
that *might* be being used by our production application, but which we are not
sure of.
My point on letting the computer figure it out is that we would know for sure...
no doubt at all.

Comment 2) The use of the system to automatically document object usage could be
a basis and validation of the 'human' sort of documentation... Not to mention
the interesting and perhaps useful statistics of *precice* usage.

Question 1) Does auditing show all tables and indexes that have been used? (as
well as views, procedures, etc.)?

Question 2) Does auditing incur much overhead?

Your verbose comments on all the above would be appriciated, but especially on
the 2 questions.

Thanks,

Robert

          Followup:

comment 1) let the computer figure it out?  fire the developers then, you don't
need them.  get a professional team on board, one that knows what they are
doing. (see -- I don't just say "fire the dba" do i!)

comment 2) that is what auditing is all about.

question 1) tables, views procedures yes, indexes no -- 9i has an index monitor
but before that -- impossible.

question 2) without it -- what do you have.  Nothing.  Therefore I would say it
incurrs reasonable processing time (not overhead) as overhead is something you
can do away with and not miss.  Here you would lose information.

      Rating: 3
Try the
      SYS.DBA_DEPENDENCIES View  March 18, 2003

Reviewer:  Cynthia LaPara  from Wilmington, DE USA

I've found the SYS.DBA_DEPENDENCIES View useful in teasing out dependencies when
the application developers have not been very helpful.  Check it out.

I agree with Tom, don't drop anything!  You never know. especially in
production.

      Rating:
      5
OUCH!  March 19, 2003
Reviewer:
      Robert  from Memphis, USA
A wise man loves those that rebuke him...
Thanks!

March 18, 2003 Lilian -- Thanks for the question regarding "sql
      loader with scheduler", version 8.1.6

      You Asked
Hi Tom:

thank you very much for your help.
what is the best way to automitic to use sql loader to loader data? every week i
will receive text file from mkt dept., and using sql loader to load those data
to one oracle table menully, I just wonder if there is a way to create pkg and
using dbms_job to load data automitic. we use unix OS.

thank you as always,
an

      and we said...
OEM can do this (oracle enterprise manager)

Unix CRON can do this

if you skip sqlldr (since you have 8i, not 9i) and use UTL_FILE + plsql, you can
use dbms_job to do this.

If the volume of data is smallish (hundreds/thousands of rows),
utl_file+dbms_job would be my choice.

If you have lots of systems to manage -- OEM would be good (centralized
repository of jobs).

If you just have a single database and tons of data to load -- cron would be
good.
Review
      & Followup

      Rating: 5
data load
      scripts  March 18, 2003
Reviewer:  an
       from de, usa
Thank you very much Tom,
actually, we are single db with heavy loader weekly, would you happen have some
scripts that i could be referenced,
thanks again.

          Followup:

it is simple -- just set your path, oracle home, oracle sid and invoke sqlldr.

No, I don't have a general purpose script

March 18, 2003 Sharkar -- Thanks for the question regarding
      "Interpretation of sql_trace", version 8.1.7.4

      You Asked
Hi tom,

my understanding goes with the below sql_trace output is that,

total time for the script to run = total time elapsed on non-recursive sqls +
total time elapsed on recursive sqls

Is that true ? The reason to ask this question is, we have a batch script
written in perl, runs from a client machine that takes 10 minutes to complete.
so as part of tuning,  i decided to run sql_trace first to figure out the bad
sqls and then to use 10046 to trace wait events. But didn't proceed further
after looking at sql_trace output. if my understanding is true then the trace
only accounts for ~2 1/2 minutes and rest don't know. Can i be certain that 7
plus minutes are spent on the data processing at the client side. Thanks for any
inputs on this.

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       34      0.15       0.24          3          3          1           0
Execute  22905     41.05     100.70      20041     656445     415420      131112
Fetch    13057     17.70      42.02      21513     182028         47      626630
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    35996     58.90     142.96      41557     838476     415468      757742

Misses in library cache during parse: 30
Misses in library cache during execute: 3

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       50      0.07       0.09          0          0          0           0
Execute     71      0.04       0.04          0         24         11           7
Fetch      242      0.03       0.36         20        311          0         186
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      363      0.14       0.49         20        335         11         193

Misses in library cache during parse: 18

   35  user  SQL statements in session.
   50  internal SQL statements in session.
   85  SQL statements in session.
********************************************************************************

Shankar

      and we said...
Yup -- 7+ minutes are on the client side.

This shows that we spent about 143 seconds in the database.  the rest of the
processing was by the client.

Perl is not the highest performing language, especially when it comes to working
with database data in a batch process..  I'd bet it would go slightly faster in
PLSQL.
Review
      & Followup

      Rating: 4
Network
      delays  March 18, 2003
Reviewer:  Shankar
       from CA, USA
thanks for your response tom. As usual, helpful.

"the rest of the processing was by the client." -- what tests can be done to
exclude network delays between client and database from the 7+ mins equation
other than running the batch script from db machine.

Regards,
Shankar

          Followup:

and even that won't cause we use networking stuff on a single machine.

I might be tempted to:

set echo on
variable n number
exec :n := dbms_utility.get_time;
set arraysize 100
alter session set sql_trace=true;
set termout off
select * from all_objects;
set termout on
exec dbms_output.put_line( round((dbms_utility.get_time-:n)/100,2)||' seconds');

in sqlplus (put it in a script) and bearing in mind that sqlplus will spend a
big of time processing itself, compare the elapsed time recieved above with the
sql_trace.  For example:

ops$tkyte@ORA920> @test
ops$tkyte@ORA920> variable n number
ops$tkyte@ORA920> exec :n := dbms_utility.get_time;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> set arraysize 100
ops$tkyte@ORA920> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA920> set termout off
ops$tkyte@ORA920> exec dbms_output.put_line(
round((dbms_utility.get_time-:n)/100,2) || ' seconds' );
4.21 seconds

PL/SQL procedure successfully completed.

select * from  all_objects

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
...
total      298      2.83       3.18          0     147947          0       29455

There I would attribute the 1 second of processing overhead to SQLPlus -- the
network is not the issue.  If there were LARGE differences, then you start
looking there.

      Rating:
      4
  March 18, 2003
Reviewer:  Shankar
       from CA, USA
Thanks tom, that is what i was looking for and it turned out that network is not
an issue.

Regards,
Shankar


Subject | Article | Last Updated (East coast time)
Temp Tables and CBO | article only | Mar 19, 2003 06:04 am
Estimate 100 percent VS compute statistics | article only | Mar 19, 2003 10:58 am
Crosstab query question | article only | Mar 20, 2003 07:52 am
date | article only | Mar 20, 2003 08:51 am
Database creation -- ORA-01990: error opening password file | article only | Mar 20, 2003 11:16 am
SQL-02122 when using EXEC SQL AT :hostvariable for multiple Pro*C DB connections | article only | Mar 20, 2003 12:59 pm
Hierarchical query needs to fill in the gaps | article only | Mar 20, 2003 03:18 pm
how to control exception | article only | Mar 21, 2003 08:44 am
Oracle Text | article only | Mar 21, 2003 09:23 am
query | article only | Mar 21, 2003 10:22 am
Why PK ... why not unique index + not null | article only | Mar 21, 2003 10:53 am
changing fonts in the owauti.tablePrint | article only | Mar 21, 2003 02:55 pm
How to get the reponse time | article only | Mar 21, 2003 05:00 pm
Moving Index only to new Tablespace. | article only | Mar 21, 2003 05:02 pm
Update query | article only | Mar 21, 2003 05:10 pm
Enterprise User Security | article only | Mar 21, 2003 05:22 pm
bringing up two listeners for two databases on the same machine | article only | Mar 22, 2003 09:42 am
Bad Oracle Installation | article only | Mar 24, 2003 07:42 am
Comparing Nulls? | article only | Mar 24, 2003 07:43 am
startup and shutdown at winnt | article only | Mar 24, 2003 07:46 am
Selection of invalid time | article only | Mar 24, 2003 09:14 am
striping | article only | Mar 24, 2003 10:16 am
computed pct subtotals | article only | Mar 24, 2003 11:51 am
Monitoring Processes/instances | article only | Mar 24, 2003 12:22 pm
escape characate in the password string | article only | Mar 24, 2003 03:52 pm
Horizontal and Vertical subsets in replication | article only | Mar 25, 2003 07:50 am
Could you briefly list the new SQL or PL/SQL features in 9.2.0 that you like most? | article only | Mar 25, 2003 08:13 am
Timed Statistics Overhead - per system vs session | article only | Mar 25, 2003 09:57 am
Using XML to transfer data from SQL Server to Oracle | article only | Mar 25, 2003 08:14 pm
row(s) 1 - 29 of 29
March 19, 2003 Anton -- Thanks for the question regarding "Temp Tables and CBO", version 8.1.7
You Asked
Hi Tom.

I understand that temporary tables cannot be analyzed.

How then does the CBO decide how to optimise a SQL query involving a non temp
table joined with a temp table (assuming the non temp table has been analyzed) ?
 I am faced with tuning an Oracle query joining a not temp table to a temp
table.  If I run the query in one environment, the temp table is used as the
driving table and a nested loop join is done.  In another environment if I run
the same query, the non temp table is the driving table with a hash merge.  What
could be the factors determining the different execution plan in the two
environments ?

Thanks
and we said...
Well -- you can in fact use DBMS_STATS.SET_TABLE_STATS to set representative
statistics and in 9i, you can even analyze temporary tables (put in
representative data -- run dbms_stats.gather...)

Even better in 9iR2 is optimizer_dynamic_sampling!

So, in 8i, you can use dbms_stats to tell Oracle what to expect.  Otherwise,
Oracle will believe that:

ops$tkyte@ORA817DEV> create global temporary table t ( x char(2000), y
char(2000), z char(2000) );
Table created.

ops$tkyte@ORA817DEV> set autotrace traceonly explain
ops$tkyte@ORA817DEV> select /*+ first_rows */ * from t;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=3 Card=8168
Bytes=49057008)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=3 Card=8168 Bytes=49057008)

there are 8,168 rows in T -- the bytes will change:

ops$tkyte@ORA817DEV> drop table t;

Table dropped.

ops$tkyte@ORA817DEV> create global temporary table t ( x int );

Table created.

ops$tkyte@ORA817DEV> select /*+ first_rows */ * from t;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=3 Card=8168
Bytes=106184)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=3 Card=8168 Bytes=106184)

ops$tkyte@ORA817DEV> set autotrace off

but the rows will remain at "8168"...

There are many factors that influence what plan will be used -- from
db_file_multiblock_read_count to sort_area_size to optimizer_goal to clustering
factors of indexes to <long list>...

If you have an AUTOTRACE TRACEONLY EXPLAIN report to add -- that would be very
useful (you can add it in the followup review if you like, I'll comment on it --
we would need one from BOTH systems of course).

In 9iR2 this temporary table issue is nicely solved with optimizer dynamic
sampling, see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3779680732446
Review
      & Followup

      Rating: 5
Hi
      Tom  March 18, 2003
Reviewer:  A reader

Thanks for the reply.  Yes - here is the explain output from both systems
running the same query (sorry, commercially sensitive query so cannot send)

Env1:

Execution Plan
----------------------------------------------------------
   0      INSERT STATEMENT Optimizer=CHOOSE (Cost=7281 Card=1960 Bytes
          =147000)

   1    0   SORT (UNIQUE) (Cost=7281 Card=1960 Bytes=147000)
   2    1     FILTER
   3    2       HASH JOIN (Cost=7238 Card=1960 Bytes=147000)
   4    3         TABLE ACCESS (FULL) OF 'PARTY_PROFILE_ADDRESS' (Cost
          =7217 Card=24 Bytes=1272)

   5    3         TABLE ACCESS (FULL) OF 'IOMS_IDENTITY_TEMP' (Cost=16
           Card=8168 Bytes=179696)

   6    2       SORT (AGGREGATE)
   7    6         TABLE ACCESS (BY INDEX ROWID) OF 'PARTY_PROFILE_ADDR
          ESS' (Cost=4 Card=1 Bytes=16)

   8    7           INDEX (RANGE SCAN) OF 'PPA_PP_IX' (NON-UNIQUE) (Co
          st=3 Card=1)

   9    2       TABLE ACCESS (BY INDEX ROWID) OF 'PARTY_PROFILE_ADDRES
          S' (Cost=4 Card=1 Bytes=14)

  10    9         INDEX (RANGE SCAN) OF 'PPA_PP_IX' (NON-UNIQUE) (Cost
          =3 Card=1)

Statistics
----------------------------------------------------------
          9  recursive calls
          8  db block gets
      47555  consistent gets
      47550  physical reads
          0  redo size
        649  bytes sent via SQL*Net to client
       2635  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
          0  rows processed

Env2:

Execution Plan
----------------------------------------------------------
   0      INSERT STATEMENT Optimizer=CHOOSE (Cost=857 Card=1797 Bytes=
          134775)

   1    0   SORT (UNIQUE) (Cost=857 Card=1797 Bytes=134775)
   2    1     FILTER
   3    2       NESTED LOOPS (Cost=833 Card=1797 Bytes=134775)
   4    3         TABLE ACCESS (FULL) OF 'IOMS_IDENTITY_TEMP' (Cost=16
           Card=8168 Bytes=179696)

   5    3         TABLE ACCESS (BY INDEX ROWID) OF 'PARTY_PROFILE_ADDR
          ESS' (Cost=1 Card=22 Bytes=1166)

   6    5           INDEX (RANGE SCAN) OF 'PPA_PP_IX' (NON-UNIQUE)
   7    2       SORT (AGGREGATE)
   8    7         TABLE ACCESS (BY INDEX ROWID) OF 'PARTY_PROFILE_ADDR
          ESS' (Cost=1 Card=1 Bytes=16)

   9    8           INDEX (RANGE SCAN) OF 'PPA_PP_IX' (NON-UNIQUE) (Co
          st=3 Card=1)

  10    2       TABLE ACCESS (BY INDEX ROWID) OF 'PARTY_PROFILE_ADDRES
          S' (Cost=1 Card=1 Bytes=14)

  11   10         INDEX (RANGE SCAN) OF 'PPA_PP_IX' (NON-UNIQUE) (Cost
          =3 Card=1)

Statistics
----------------------------------------------------------
         10  recursive calls
          5  db block gets
          7  consistent gets
          0  physical reads
          0  redo size
        434  bytes sent via SQL*Net to client
       2462  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
         23  sorts (memory)
          0  sorts (disk)
          0  rows processed

          Followup:

you do sort of realize that given the plan like that -- the query is fairly
straight forward... but any way

how about the parameters -- diff the parameters for the two systems -- looking
at optimizer related ones like hash_join_enabled, optimizer_index_cost_adj, etc

      Rating:
      5
Interesting  March 18, 2003
Reviewer:
      Anton  from NZ
Hi Tom.

I found that in Env1, the values of hash_area_size was 131072 and
hash_multiblock_io_count was 0 compared to Env 2 values of 2048000 and 2.  SO I
thought that Env2 would prefer a hash join (rather than NL it is using).
However, I also found that Env1 had optimizer_index_caching set to 0 and
optimizer_index_cost_adj of 100 (default) whereas Env2 had 95 and 10, which may
explain Env2 optimizer choosing the NL join over hash merge ?

          Followup:

i would guess it was the optimizer_index_cost_adj.  Makes index access paths
look more reasonable.

March 19, 2003 Ravi -- Thanks for the question regarding
      "Estimate 100 percent VS compute statistics", version 817

      You Asked
Dear Tom,

Is there any difference between

ANALYZE TABLE t_name compute statistics;   and
ANALYZE TABLE t_name estimate statistics sample 100 percent;

Oracle manual says that for percentages over 50, oracle always collects exact
statistics. Does it mean in the above case, that both are going to take the same
time
 even for large tables.

I would like to understand,

1) Are the statistics collected in both the cases same,
2) Are the way in which the data obtained and analyzed are same in both the
cases
3) Is there any adv. of using one over the other.

Thanks
Ravi

      and we said...
anything above "sample 49" (eg: sample 50 and above) is the same as

"compute"

it'll do the whole table.

1) yes.
2) yes.
3) no.
Review
      & Followup

      Rating:
      5
  March 30, 2001
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating: 4
sample size in
      analyze  November 30, 2001
Reviewer:  Ramesh
       from MA, USA
From ADministrative guide chapter 20
General Management of schema Objects
" In either case, if you specify a percentage greater than 50, or a number of
rows or index values that is greater than 50% of those in the object, Oracle
computes the exact statistics, rather than estimating."

If I specify
Analyze table table_name estimate statistics sample 50 percent;
It is analyzing like compute statistics.( I checked sample_size with both
compute and with sample 50 percent)
If I analyze table with 49 percent the sample size from user_Tables is in no of
rows which is not exactly as 49 percent,

09:59:07 kumarr@IDRDEV SQL>analyze table test estimate statistics sample 49
percent;

Table analyzed.

Elapsed: 00:00:00.79
10:04:54 kumarr@IDRDEV SQL> select table_name, sample_size,num_rows from
user_tables
10:05:10   2  where table_name='TEST';

TABLE_NAME                     SAMPLE_SIZE   NUM_ROWS
------------------------------ ----------- ----------
TEST                                 10644      29986

Elapsed: 00:00:00.20
10:05:13 kumarr@IDRDEV SQL> select 10644/29986*100 from dual;

10644/29986*100
---------------
     35.4965651

Please clarify

Thanks in advance

          Followup:

It is an estimate after all, it is never exact.  We scan the first couple of
blocks of the table to get an estimate of the number of rows in the table all
together (so that can skew this very much).  We then generate random rowids
based on the blocks in the table (from the extents) and remove duplicate rowids
from this set (further decreasing the number of rows).

If you want to analyze exactly 49%, you'll need to count the rows and analyze
based on rows -- not a sample pct.

      Rating: 4
anaylze with
      sample size  November 30, 2001
Reviewer:
      Ramesh  from MA, USA
Even though I calculate 49 % for sample rows and used the figure for estimating
, the sample size  in user_tables is differnt . Why ?

14:59:45 kumarr@IDRDEV SQL> select 29986/100*49 from dual;

29986/100*49
------------
    14693.14

Elapsed: 00:00:00.00
15:00:09 kumarr@IDRDEV SQL> analyze table test estimate statistics
15:00:31   2  sample 14694 rows;

Table analyzed.

Elapsed: 00:00:00.77
15:01:20 kumarr@IDRDEV SQL> select sample_size,num_rows from user_tables
15:01:34   2  where table_name='TEST';

SAMPLE_SIZE   NUM_ROWS
----------- ----------
      10694      29986

          Followup:

Ok, i mispoke on the previous response.

Using the sample N rows fixes issues with regards to the sample scan of the
first couple of blocks to get the est. number of rows -- you still have the
issue that 14,694 rowids were randomly generated - only 10,694 of which were
unique.

I doubt you would ever get exactly 49% analyzed for this reason, you would have
to over-estimate the number of rows you wanted.  its not exact (its an estimate)

      Rating:
      5
dbms_stats.gather_schema_stats:
      estimate_percent  November 30, 2001
Reviewer:
      jan van mourik  from Houston, Texas
Tom,

In your answer here:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1411208576534
you use this example:

BEGIN dbms_stats.gather_schema_stats
       (user, 99, method_opt =>null, cascade=> true );
end;
/

You set estimate_percent to 99.
Documentation:
  Percentage of rows to estimate (NULL means compute): The valid range is
[0.000001,100).

Do you have any particular reason to set it to 99 for gather_schema_stats, and
not 100 or NULL? Or does a percentage > 49 mean COMPUTE for this proc too?

Thanks, jan

          Followup:

pct > 49 is the same as 100%.

I used 99  because the value range is not inclusive of 100:

  1  BEGIN dbms_stats.gather_schema_stats
  2         (user, 100, method_opt =>null, cascade=> true );
  3* end;
scott@ORA817DEV.US.ORACLE.COM> /
BEGIN dbms_stats.gather_schema_stats
*
ERROR at line 1:
ORA-20001: Illegal sample percent 100: must be in the range [0.000001,100)
ORA-06512: at "SYS.DBMS_STATS", line 4479
ORA-06512: at "SYS.DBMS_STATS", line 4610
ORA-06512: at "SYS.DBMS_STATS", line 4720
ORA-06512: at "SYS.DBMS_STATS", line 4702
ORA-06512: at line 1

      Rating: 3
Copy
      Stats  October 26, 2002
Reviewer:  A reader
       from BG
Tom,
    Don't know if this sounds stupid enough, but this doubt is really bothering
me...

At the development environment we have only a fraction of the actual onsite
data. Its very hard to simulate performance problems due to lack of data. My
question is that can we simulate the onsite data by loading all the data
dictionary tables at the development environment with the same statistics as in
Onsite database?

Is this feasible? I do believe that data dictionary tables are non
updateable...but can we use dms_stats or someother package for maintaining the
tables with the details we provide?

If this is feasible, would this be a good idea.

Thanks.

          Followup:

You know -- this week (Nov 1st) I'm giving a presentation on some peformance
stuff and one of the slides reads exactly:

Don't Develop...

 - On an empty database
     -  Importing stats isn’t good enough
     -  Can you really look at a plan and say “that’s a good one”

 - With a single user (quote from asktom below)
      “Before the stress test, and when we ran application by itself, it worked
       fine.  That means SQL statements are committed properly.  But somehow
       when muti-process access the database resource, it broke.”

 - In a dust free lab….

   and expect it to work in production!

So, yes, you can import the stats from prod using dbms_stats but I question what
this will buy you in the end.

Tell me, can you look at a page of explain plan output and say "yes, that is
exactly what we want.  When this runs against 1,000,000 rows it will be as good
as when it ran against 1,000"

I strongly feel the developers need to develop against live sized data if
they want to have any chance of detecting and correcting issues BEFORE rolling
out the code.

      Rating: 3
Can you guide
      me through this please  October 27, 2002
Reviewer:
      A reader  from BG
Tom,

  Thanks for the response. Copying stats will never be a 100% substitute for
live data. But, in our case getting data is not going to happen and we would
have to develop against a fraction of the actual data.

We are plagued by performance problems for big queries on reports. Now this is
the last option we have on hand.

1. Can you please let me know to what percent of accuracy would i be getting by
copying stats.

2. Also, if i have 5 tables (say), should i copy column by column? Iam not
familiar with dbms_stats and got confused over the creation on a table which
holds the stats. Which functions should i be using and in what order.

Thanks

          Followup:

But I ask you to ask yourself -- of what use would you find getting the query
plans?

Suppose

o you have the stats
o you run a query
o it runs good against "test"
o you look at the plan

now what?  will it run good in production?  can you tell?  how?

You don't have to "get real data", you just have to "generate real enough data".

You will have your plague forever otherwise.  Tuning a query against a fraction
of the data is virtually worthless.  Knowing the query plan generated with stats
that are big but data that is small -- not very useful (in my opinion)

You would be getting 100% accuracy in the query plans if you:

o set the init.ora parameters the same on both databases (sort areas, hash
areas, sga size, multi-block read count, etc etc etc)

o had the same stats

But I ask you -- so what?

You'll need to read about dbms_stats (fortunately we document these packages!)

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76936/dbms_sta.htm#999107
and in particular:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76936/dbms_st2.htm#1010202
you can probably use export schema stats to get the stats for your entire
application schema.

      Rating: 4
Copy
      Stats  March 17, 2003
Reviewer:  kumar
       from india
Tom,
regarding the copy stats query--
I was trying to do exactly the same thing you have said to be useless - trying
to export the stats and import to my test server. I thought of getting your
opinion about this before start doing it. Hence I searched your site and found
your reply ( to my utter shock ). Can you tell me how else can I simulate my
production data so that atleast I can be sure of the execution plan ? Also can
you tell me why we have the export and import schema stats procedure ?

          Followup:

you can see the execution plan -- but tell me, can you tell me if it is a good
one or not??????

if you can look at a plan and say "thats a darn good plan, the best one in fact"
with accuracy over say 80% -- I'd like to talk to you about a job, I might have
one for you.

Tell me, of the two, which is better?

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T'
   2    1     NESTED LOOPS
   3    2       TABLE ACCESS (FULL) OF 'T'
   4    2       INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE

Row Source Operation
---------------------------------------------------
HASH JOIN
  TABLE ACCESS FULL T
  TABLE ACCESS FULL T

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6749454952894
so, yes, you can see the plans -- my point is -- so what?  unless you test
the plans against representative data -- so?

      Rating:
      5
Why?  March 17, 2003
Reviewer:  Chuck
      Jolley  from OKC, OK USA
Why do managers who will strip nekid, and who knows what else, for a doctors
physical balk at letting the developers of thier database see the data?
It seems to me that if they don't trust the developers they should get new ones.
On the other hand, we are a tiny shop going against data that is public by law,
so maybe I'm missing something.
We frequently fix reports where the problem would never show up at all if we did
not have access to production or a VERY recent copy.
What rights SHOULD a development team have to realalisticaly sized and
distributed data?
Thanks,
Chuck

      Rating: 3
stats
      query  March 18, 2003
Reviewer:  kumar
       from India
Tom,
Thanks for your response. I can understand when you say that " unless you test
the plans against representative data " . I am not going to decide which plan is
better for any particular query - I believe the optimizer fully. But Can you
tell what are the factors on which the cost calculation is done ? I am asking
this because my understanding is that for a particular expected result, the
query which gives a plan whose cost is lesser is better than another query whose
cost is higher. In that case, i can try tuning by re writing the queries. Please
correct me if I am wrong. Also Can you tell what is primary purpose of the
dbms_stats - export and import procedures ?

          Followup:

The cost of two queries cannot be compared.  In theory - the query with the
smallest cost would take less time, however, in practice this is many times not
the case.

You might use dbms_stats/export/import to move stats from a staging database to
production, instead of analyzing production, after a reload for example.

      Rating: 2
can you explain
      please  March 19, 2003
Reviewer:  kumar
       from India
Tom,

"In theory - the query with the smallest cost would take less time, however, in
practice this is many times not the case."

Can you explain a bit more on this please ? In that case, the only way to
compare queries is to run them against the actual data and test them . Is there
no other way to compare the performance of two queries ?

Thanks again for the response

          Followup:

umm, don't know what more to say.

A query with a cost of 1 could take longer then a query with a cost of 100.

The optimizer gets a query.
generates hundreds, maybe thousands of plans.  assigned each a cost.
sorts by cost, keeps the least costly.

Tell me:

big_table@ORA920> set echo on
big_table@ORA920> alter session set optimizer_index_cost_adj = 100;

Session altered.

big_table@ORA920> set autotrace traceonly explain
big_table@ORA920> select count(status) from big_table where owner = 'SCOTT';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=1 Bytes=10)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=6 Card=204
Bytes=2040)
   3    2       INDEX (RANGE SCAN) OF 'BIG_TABLE_OWNER_IDX' (NON-UNIQUE) (Cost=3
Card=204)

big_table@ORA920> alter session set optimizer_index_cost_adj = 1;

Session altered.

big_table@ORA920> select count(status) from big_table where owner = 'SCOTT';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=10)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=2 Card=204
Bytes=2040)
   3    2       INDEX (RANGE SCAN) OF 'BIG_TABLE_OWNER_IDX' (NON-UNIQUE) (Cost=3
Card=204)

big_table@ORA920> set autotrace off

So, is the second query "faster"????  No, we just fiddled with stuff to make it
have a lower cost.

I would say "you gotta run them"

      Rating: 4
thanks
        March 20, 2003
Reviewer:  kumar  from
      India
Tom,

Thanks for the nice illustration. Thats a typical Tom  answer, If I can say so..
I strongly feel that the reason behind Oracle as a very succesful product is the
knowledge sharing by people like you. Users are never short of knowledge
sources..

Keep up the good work.

March 20, 2003 Bhavesh -- Thanks for the question regarding
      "Crosstab query question", version 8.1.7

      You Asked
Hi Tom:

Crosstab Query question.

I have a table balance with this description

SQL> desc balance
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 RELATION_ID                               NOT NULL NUMBER(10)
 FDG                                                VARCHAR2(3)
 NET_BOOK_BALANCE                                   NUMBER
 COMMITMENT                                         NUMBER
 BAL_CHANGE_CODE                                    VARCHAR2(12)
 IN_AS_OF_FILE_DT                                   DATE

The sample data..
SQL> select * from balance where relation_id = 11164;

RELATION_ID FDG NET_BOOK_BALANCE COMMITMENT BAL_CHANGE_C IN_AS_OF_
----------- --- ---------------- ---------- ------------ ---------
      11164 75           2500000          0              31-MAR-02
      11164 85          60312499   62232603              31-MAR-02
      11164 85          57890624   62232603              30-APR-02

I have lots of rows there. Usually 1 row per relation_id.
But, It can have multiple rows per realtion_id per in_as_of_file_dt.

The distinct values of FDG are...

SQL> select distinct fdg from balance;

FDG
---
70
72
75
78
85
95

6 rows selected.

The distinct values for in_as_Of_file_dt will *always* be 2 dates.
The last two months' last_day!
SQL> select distinct in_as_of_file_dt from balance;

IN_AS_OF_
---------
31-MAR-02
30-APR-02

Now, I want the output in this way!
How do I write the SQL?
I don't want to write PL/SQL to do it.

relation_id  fdg   april_balance    march_balance
-----------   ---   ------------     -------------
11164         70               0                0
11164         72               0                0
11164         75               0          2500000
11164         78               0                0
11164         85        57890624         60312499
11164         95               0                0

where balance is the net_book_balance

Please help.
Thanks
Regards,
Bhavesh Tailor

---------
Answers
---------

I will be doing one relation_id at a time. However, if possible, I want to see
one id at a time as well as all relations..

Yes, set of FDG is fixed! Those are the only possible values.

Yes, date is always the last day for last two months.
e.g. in May it is last day of march and last day of April..
Thanks for your time and help!
Regards,
Bhavesh

      and we said...
Suggest you create a table "fdg_values" that has that list in it.  Then:

ops$tkyte@ORA817DEV.US.ORACLE.COM> variable bv number
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec :bv := 11164

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select :bv,  a.fdg,
  2                  sum(decode( to_char(in_as_of_file_dt,'mm'),
to_char(add_months(sysdate,-1),'mm'),NET_BOOK_BALANCE,0)) last_month,
  3                  sum(decode( to_char(in_as_of_file_dt,'mm'),
to_char(add_months(sysdate,-2),'mm'),NET_BOOK_BALANCE,0)) month_before_last
  4    from fdg_values a,
  5             (select * from t where relation_id = :bv) b
  6   where a.fdg = b.fdg(+)
  7   group by a.fdg
  8  /

       :BV        FDG LAST_MONTH MONTH_BEFORE_LAST
---------- ---------- ---------- -----------------
     11164         70          0                 0
     11164         72          0                 0
     11164         75          0           2500000
     11164         78          0                 0
     11164         85   57890624          60312499
     11164         95          0                 0

6 rows selected.

will be want you want.
Review & Followup

      Rating: 5
thanks a
      lot  May 08, 2002
Reviewer:  Bhavesh Tailor
       from charlotte, NC
I tried a similar query but I was using inline view instead of table fdg_views
and my outer join did not returen all values of FDG.

Thanks a lot

      Rating: 3
why my outer
      join is failing?`  May 08, 2002
Reviewer:
      Bhavesh Tailor  from charlotte, NC
Tom:

My query still does NOT return all the rows.
I don't know what I am doing wrong, but outer join is failing!

Here is my table t with all FDG.
SQL> select * from t;

FDG
---
70
72
75
78
85
95

6 rows selected.

My query
--------

SQL> edit
Wrote file afiedt.buf

  1   select  b.relation_id, t.fdg,
  2
sum(decode(in_as_of_file_dt,trunc(last_day(add_months(sysdate,-1))),net_book_bala
nce,0)) as aprilBalance,
  3
sum(decode(in_as_of_file_dt,trunc(last_day(add_months(sysdate,-2))),net_book_bala
nce,0)) as marchBalance
  4   from  t , (select * from balance where relation_id = 11164) b
  5   where t.fdg = b.fdg (+)
  6   and b.relation_id = 11164
  7*  group by  b.relation_id, t.fdg
SQL> /

RELATION_ID FDG APRILBALANCE MARCHBALANCE
----------- --- ------------ ------------
      11164 75             0      2500000
      11164 85      57890624     60312499
Instead of returning six rows, it's returning only two.
What am I missing here?
Thanks a lot.
Regards,

          Followup:

get rid of this:

 6   and b.relation_id = 11164

it wasn't in my query and doesn't belong in your (we put that in the INLINE
VIEW)

      Rating: 3
A different
      question on similar lines  March 19, 2003

Reviewer:  Sameer  from WI, USA
Tom,

First of all, thanks for all the knowledge you have been sharing with us. I
always wanted to ask you a question but always found the answer in the database
:)

Finally -- couldn't find solution for this problem.

SQL> desc itemlist_detail
 Name                  Null?    Type
 --------------------- -------- ------------
 ITEMLIST              NOT NULL NUMBER(8)
 ITEMNO                NOT NULL NUMBER(8)
 ITEM_CATEGORY         NOT NULL VARCHAR2(1)

The above table contains different items with different categories within an
itemlist. I want to find out what different categories are listed in a given
itemlist on a single row. Can I achieve this by writing a single sql ?

data :
758  1112166  s
758  1112165  s
758  1112167  r
758  1112168  r
758  1112169  k
758  1112170  k
758  1112171  k
758  1112172  k
215  1112173  i
215  1112174  i
215  1113043  i
215  1113050  i
215  1521562  i
660  1522247  s
660  1522261  s
660  1523261  h
660  6611001  h

output desired :
758  s r k
215  i
660  s h

Thanks again !

          Followup:

If you know a reasonable upper bound on the number of distinct item_category
values, then see:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6091131017063
and apply that technique to the inline view:

( select distinct itemlist, category from t )

If you do NOT have a reasonable upper bound and are on 9i, see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2196162600402
and apply that technique to that inline view.

      Rating: 5
Hats off
      !!!  March 20, 2003
Reviewer:  Sameer
       from WI, USA
Tom, that query worked just gr8 ! Thanks !

I wonder where you get all the enthusiasm & energy to keep answering all these
questions upto so fine details, with examples -- I guess, now you might be
obsessed with this board :-)

By the way, on the query of the first link, 'partition' is misspelt.

March 20, 2003 Rich -- Thanks for the question regarding "date",
      version oracle 8.1.5

      You Asked
Tom
   If i have a system date and i want to be able to check for
the day on the system day to be between 1 and 28 what should i
do?
Thanks

      and we said...
in PLSQL:

if ( to_char( sysdate, 'dd' ) between 1 and 28 ) then
   ... it is
else
   ... it is not
end if;

In SQL:

select decode( sign(to_char(sysdate,'dd')-28),
               1, 'It is NOT between 1 and 28',
                  'It IS between 1 and 28' )
from dual
/
Review &
      Followup

      Rating:
      3
date  March 20, 2003
Reviewer:  Rory
       from Philippines
HI Tom,

    How do I get this format for sysdate

YYYYMM01. I need to get the first of the current month. Thanks.

          Followup:

to_char( trunc(dt,'mm'),'yyyymmdd')

is one way, another is

to_char( dt, 'yyyymm' ) || '01'

of course.

      Rating:
      5
  March 20, 2003
Reviewer:  Rory
       from Philippines
Hi Tom,

    Thanks. Actually I've already thought about the second approach you gave me.
I concat the date to 01. But I didn't want to use it. But I'm going to use the
1st approach. Thanks again.

March 20, 2003 Murali -- Thanks for the question regarding
      "Database creation -- ORA-01990: error opening password file ", version
      8.0.5.1.0

      You Asked
Hi
I am using Oracle 8.0.5.1.0 on Solaris 2.6. i was trying to
create a database( create databse command from svrmgrl) and it
fails with follwing error

ORA-01501: CREATE DATABASE failed
ORA-01990: error opening password file
'/u01/app/oracle/product/8.0.5/dbs/orapw'

i found that this problem occur only if i set
remote_login_passwordfile = shared /exclusive. Once i set this
parameter to none  all work fine.

could you please explain me why this happend and hwo to set the
parameter to shared / exclusive.

The same i tried in NT and it wokrs fine with all the options

      and we said...
If there is no "orapw<sid>" file,set the parameter
"REMOTE_LOGIN_PASSWORDFILE" = none in the init<sid>.ora file
or create the "orapw<sid>" file and set the
"REMOTE_LOGIN_PASSWORDFILE" = exclusive|shared and then retry
the create database command.

Be aware:  Though error messages appeared, the datafiles are
created as well!!! So it needed to remove the datafiles,
controlfiles and so on, before attempting a new 'create
database'.

 ORA-1990 always occurs when the SID specific 'password' file
does not exist  and the init<sid>.ora parameter
"REMOTE_LOGIN_PASSWORDFILE" is set to "EXCLUSIVE"

REMOTE_LOGIN_PASSWORDFILE specifies whether Oracle checks for a
password file and how many databases can use the password file.
Setting the parameter to NONE signifies that Oracle should
ignore any password file (and, therefore, privileged users must
be authenticated by the operating system). Setting the
parameter to EXCLUSIVE signifies that the password file can be
used by only one database and the password file can contain
names other than SYS and INTERNAL.

Review &
      Followup

      Rating: 3
Database
      Creation Errors  October 18, 2002
Reviewer:
      Malo  from Canada
Hi Bob,

I am try to create the database manually. I have following Error :
ORA-01501: CREATE DATABASE failed
ORA-01101: database being created currently mounted by some other instance

I followed the method :
http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96521/create.htm#1656

Please tell what did I missed.

Thanks

          Followup:

"bob"?

anyway, you have a database that is already up and running with that name:

[tkyte@tkyte-pc-isdn tkyte]$ oerr ora 1101
01101, 00000, "database being created currently mounted by some other instance"
// *Cause:  Some other instance has the database of same name currently mounted
//         and you are trying to create it.
// *Action: Either change the database name or shutdown the other instance.

      Rating:
      3
dba(9i)  March 20, 2003
Reviewer:  abid
      dawood  from pakistan
when i exectue query of database it give the error of password file . am i have
to create password file . and how
i create password file. thanks

          Followup:

i wonder -- how many pages will you attach this question to?

March 20, 2003 Michael -- Thanks for the question regarding
      "SQL-02122 when using EXEC SQL AT :hostvariable for multiple Pro*C DB
      connections", version 8.1.6

      You Asked
I am trying to connect to multiple databases and run a simple
query using Pro*C's EXEC SQL AT syntax.  I have read the Database Concepts
documentation from oracle.com (part number A97269-01 for Version 9.2 since I
couldn't easily find the documentation for 8.1.6) and I've tried to replicate
the code as closely as possible from page 10 of that document, but I'm getting
error SQL-02122 if I try to two different dbnames (or handles in my code).

Previous to the table_count function calls, I have succssfully connected twice
to the same database with:
        EXEC SQL CONNECT :user
        IDENTIFIED BY :passwd
        AT :handle
        USING :connect_string;
where handle is first "DB000" and then "DB001"

table_count("DB000", "tablename"); /* works fine */
table_count("DB001", "tablename"); /* errors with SQL-02122 */

Here is the code:

int table_count(char *h, char *t)
{
    char handle[64];
    char table[64];
    char st[1024];
    int count = 0;

    strcpy(handle, h);
    sprintf(st, "SELECT COUNT(*) FROM %s", t);

    EXEC SQL WHENEVER SQLERROR GOTO errrpt;
    EXEC SQL WHENEVER NOT FOUND GOTO notfound;
    EXEC SQL AT :handle DECLARE statem STATEMENT;
    EXEC SQL PREPARE statem FROM :st;
    EXEC SQL DECLARE curs CURSOR FOR statem;
    EXEC SQL OPEN curs;
    EXEC SQL FETCH curs INTO :count;
    EXEC SQL CLOSE curs;

    return count;

notfound:
    fprintf(stderr,
        "table %s not found\n", table);
    return 0;
errrpt:
    fprintf(stderr,
        "%s\n", sqlca.sqlerrm.sqlerrmc);
    return 0;
}

I've tried adding AT :handle to each of the EXEC SQL lines after the
DECLARE STATEMENT line in turn with no change in behavior.

      and we said...
close,

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();
    EXEC SQL AT :handle DECLARE statem STATEMENT;
    EXEC SQL AT :handle PREPARE statem FROM :st;
    EXEC SQL AT :handle DECLARE curs CURSOR FOR statem;
    EXEC SQL OPEN curs;
    EXEC SQL FETCH curs INTO :count;

the 8i doc set is here:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/products.htm

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76942/pc_03dbc.htm#6143
specifically

Review &
      Followup

      Rating: 2
Still doesn't
      work.  March 17, 2003
Reviewer:  Michael
      Sterrett  from Vancouver, BC
Tom - Thanks for taking question.

Please be more explicit because maybe I'm missing something. I've been working
on this problem for many hours now so maybe I'm burned out on it.

Note that in my original question I said that I had "tried adding AT :handle to
each of the EXEC SQL lines after the DECLARE STATEMENT line in turn with no
change in behavior."

Simply adding AT :handle to the three other statements doesn't change the
behavior.  I still get the same error. (I'm assuming the problem isn't with my
SQLERROR handling).

My code looks like this now.  Very similar to yours:

    EXEC SQL WHENEVER SQLERROR GOTO errrpt;
    EXEC SQL WHENEVER NOT FOUND GOTO notfound;
    EXEC SQL AT :handle DECLARE statem STATEMENT;
    EXEC SQL AT :handle PREPARE statem FROM :st;
    EXEC SQL AT :handle DECLARE curs CURSOR FOR statem;
    EXEC SQL OPEN curs;
    EXEC SQL FETCH curs INTO :count;

I'm also concerned that there is no CLOSE for curs.

          Followup:

i only gave you a snippet -- showing the necessary AT's to add.

This is my program in full:

#include "unistd.h"
#include <stdio.h>

static char *   USERID = "scott/tiger";

#define SQLCA_INIT
EXEC SQL INCLUDE sqlca;

static void sqlerror_hard()
{
    EXEC SQL WHENEVER SQLERROR CONTINUE;
    printf("\nORACLE error detected:");
    printf("\n% .70s \n", sqlca.sqlerrm.sqlerrmc);
    EXEC SQL ROLLBACK WORK RELEASE;
    exit(1);
}

int table_count(char *h, char *t)
{
    char handle[64];
    char table[64];
    char st[1024];
    int count = 0;

    strcpy(handle, h);
    sprintf(st, "SELECT COUNT(*) FROM %s", t);

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();
    EXEC SQL AT :handle DECLARE statem STATEMENT;
    EXEC SQL AT :handle PREPARE statem FROM :st;
    EXEC SQL AT :handle DECLARE curs CURSOR FOR statem;
    EXEC SQL OPEN curs;
    EXEC SQL FETCH curs INTO :count;
    printf( "sqlca.sqlcode = %d, count = %d\n", sqlca.sqlcode, count );
    EXEC SQL CLOSE curs;

    return count;
}

main( argc, argv )
int argc;
char *  argv[];
{
varchar oracleid[50];
char    db1[] = "c1";
char    connect1[] = "ora817dev.us.oracle.com";
char    db2[] = "c2";
char    connect2[] = "ora815.us.oracle.com";

    strcpy( oracleid.arr, USERID );
    oracleid.len = strlen(oracleid.arr);

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();
    EXEC SQL CONNECT :oracleid AT :db1 USING :connect1;
    printf("\n(1) Connected to ORACLE as user: %s\n\n", oracleid.arr);

    EXEC SQL CONNECT :oracleid AT :db2 USING :connect2;
    printf("\n(2) Connected to ORACLE as user: %s\n\n", oracleid.arr);

    table_count( db1, "all_users" );
    table_count( db2, "all_users" );

    /* Disconnect from ORACLE. */
    /* EXEC SQL COMMIT WORK RELEASE; */
    exit(0);
}

and when executed:

$ ./test

(1) Connected to ORACLE as user: scott/tiger

(2) Connected to ORACLE as user: scott/tiger

sqlca.sqlcode = 0, count = 54
sqlca.sqlcode = 0, count = 14

showing it works.

      Rating: 4
I think I'm
      going to cry.  March 17, 2003
Reviewer:  Michael
      Sterrett  from Vancouver, BC
Thank you for posting your entire code.  I grabbed it,
changed the passwords and databases to ours and tried
it out.  It fails in exactly the same way my code is
failing.  Is this perhaps an Oracle version problem?

I have tried with 8.1.6.1.0, 8.1.7.1.0 and 8.1.7.3.0.

Does this method of AT :hostvariable only work in version 9?

If it's known to work in version 8 any idea what could be
causing this error for me, yet working for you?

          Followup:

I compiled on Sun Solaris using version 8174 of Oracle.

I also tested with 8150 on Solaris as well.

What C compiler are you using?
What is the exact sequence of commands you use to compile?
If you want to compile exactly like I do use

http://asktom.oracle.com/~tkyte/proc_makefile/

      Rating: 4
Tracked the
      problem down (I think)  March 18, 2003
Reviewer:
      Michael Sterrett  from Vancouver, BC
Tom, after duplicating the error with your code and your Makefile, I felt sure
that the problem was in our local configuration.

I noticed that we had HOLD_CURSOR=YES in our sytem default
file so I experimented with both hold_cursor and release_cursor on the
command-line.  Here are the results:

hold_cursor=yes release_cursor=yes: SUCCESS
hold_cursor=no  release_cursor=yes: SUCCESS
hold_cursor=yes release_cursor=no:  FAILURE
hold_cursor=no  release_cursor=no:  SUCCESS

Are these the expected results?  If so, can you explain
why I'm seeing the failure in the third case and why this
is the way it should work?  What are the implications of
removing HOLD_CURSOR=YES from our global config file?

To answer your questions:

> What C compiler are you using?

The system C compiler (/bin/cc) on DYNIX/ptx V4.4.8.  Not
the most common OS I admit.

> What is the exact sequence of commands you use to compile?

Now, the commands from your Makefile, make -n shows this:

proc hold_cursor=no release_cursor=yes ireclen=255 lines=yes
include=/oracle/rdbms/8.1.6.1.0/proc/lib iname=tom2
cc  -Wc,-O2 -Xa  -Xa -Wl,-Bexport -K PIC  -I.
-I/oracle/rdbms/8.1.6.1.0/precomp/public -I/oracle/rdbms/8.1.6.1.0/rdbms/public
-I/oracle/rdbms/8.1.6.1.0/rdbms/demo -I/oracle/rdbms/8.1.6.1.0/plsql/public
-I/oracle/rdbms/8.1.6.1.0/network/public -DORAALLOCA    -c tom2.c
cc -o tom2 -L/oracle/rdbms/8.1.6.1.0/precomp/lib/ -L/oracle/rdbms/8.1.6.1.0/lib/
-t -o tom2 \
tom2.o -L/oracle/rdbms/8.1.6.1.0/lib -lclntsh   `cat
/oracle/rdbms/8.1.6.1.0/lib/sysliblist`  -lm

          Followup:

Well -- it is happening because the release_cursor=no says to pro*c -- when we
open a cursor, keep it open - even when we close it -- we are probably going to
reuse it.

Hence, with release cursor = no, it fails -- the cursor is opened on the wrong
database when you go to execute it the second time around.  You are only seeing
this when hold_cursor=yes because of the interaction (documented in the
precompiler guide) between these two.  This table:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76942/pc_actun.htm#1387
shows their relationship.  Basically when hold_cursor=yes and release_cursor=no,
the link between your program and cursor in the server is maintained (in all
other cases it is marked either as reusuable or closed straight away).

March 20, 2003 --
      Thanks for the question regarding "Hierarchical query needs to fill in the
      gaps", version 9.2.0

      You Asked
Assume I have a hierarchy like the following and
would like to assign allocations of colors to each
level of the hierarchy:

NA
   US                10% RED   90% BLUE
     TEXAS
     GEORGIA         100% YELLOW
        ATLANTA
     CALIFORNIA
     VIRGINIA
     MINNESOTA
   MX                100% GREEN
     GUANAJUATO
   CA                50% RED  50% BLUE
     BC
     ONTARIO         100% RED
     SASKATCHEWAN

Here's some DDL and rows to represent the above situation:

CREATE TABLE location (
  location_name VARCHAR2(50),
  parent_location_name VARCHAR2(50),
  PRIMARY KEY (location_name ));

INSERT INTO location VALUES ('NA',NULL);
INSERT INTO location VALUES ('US','NA');
INSERT INTO location VALUES ('TEXAS','US');
INSERT INTO location VALUES ('GEORGIA','US');
INSERT INTO location VALUES ('CALIFORNIA','US');
INSERT INTO location VALUES ('VIRGINIA','US');
INSERT INTO location VALUES ('MINNESOTA','US');
INSERT INTO location VALUES ('MX','NA');
INSERT INTO location VALUES ('GUANAJUATO','MX');
INSERT INTO location VALUES ('CA','NA');
INSERT INTO location VALUES ('BC','CA');
INSERT INTO location VALUES ('ONTARIO','CA');
INSERT INTO location VALUES ('SAKSATCHEWAN','CA');
INSERT INTO location VALUES ('ATLANTA','GEORGIA');

CREATE TABLE location_pct (
    location_name VARCHAR2(50),
    color         VARCHAR2(10),
    pct           NUMBER,
    PRIMARY KEY (location_name, color, pct) );

INSERT INTO location_pct VALUES ('US', 'RED',10);
INSERT INTO location_pct VALUES ('US', 'BLUE',90);
INSERT INTO location_pct VALUES ('GEORGIA', 'YELLOW',100);
INSERT INTO location_pct VALUES ('MEXICO', 'GREEN',100);
INSERT INTO location_pct VALUES ('CA', 'RED',50);
INSERT INTO location_pct VALUES ('CA', 'BLUE',50);
INSERT INTO location_pct VALUES ('ONTARIO', 'RED',100);

COMMIT;

What I'd like to do is to query this information to
obtain the lowest level assignment in the hierarchy that applies.

For example, if I ask for the allocation for the following
locations, I'd get these results:
(for US)
US   RED 10
US   BLUE 90

(for GEORGIA)
GEORGIA YELLOW 100

(for TEXAS -- no direct assignment):
TEXAS RED 10
TEXAS BLUE 90

(for ATLANTA -- no direct assignment):
ATLANTA YELLOW 100

I can't think of a way to do this cleanly. Can you?
I'm not opposed to temp tables or the like, but in my
case, I have tens of thousands of entries in location_pct,
hundreds of thousands in location, and a depth of around
6 in location, so constructing every pairing seems like a bad
idea.

New info: Your proposal didn't give the ATLANTA value as YELLOW 100,
which is the lowest level parent that HAS data returns.
That is, for a given child, it should show the data for its lowest level parent
that actually has the data.

      and we said...
did you try to unravel what I was doing and see that the answer lies in there?

sigh, anyway, just look backwards instead of forwards in the pct2 string:

ops$tkyte@ORA920> select lname, pct2,
nvl(pct,'*'||substr(pct2,instr(pct2,'^',-1)+1) ) pct
  2    from (
  3  select rpad('*',2*level,'*') || location_name lname,  pct,
rtrim(sys_connect_by_path(pct,'^'),'^') pct2
  4    from (select location.*,
  5             rtrim((
  6               select max(decode(rownum,1, pct||'% '||color, null))||' '||
  7                      max(decode(rownum,2, pct||'% '||color, null))||' '||
  8                      max(decode(rownum,3, pct||'% '||color, null))||' '||
  9                      max(decode(rownum,4, pct||'% '||color, null))||' '||
 10                      max(decode(rownum,5, pct||'% '||color, null))||' '||
 11                      max(decode(rownum,6, pct||'% '||color, null))
 12                 from location_pct
 13                where location_name = location.location_name ))  pct
 14            from location )
 15   start with parent_location_name is null
 16   connect by prior location_name = parent_location_name
 17        )
 18  /

LNAME                PCT2                           PCT
-------------------- ------------------------------ --------------------
**NA                                                *
****US               ^^90% BLUE 10% RED             90% BLUE 10% RED
******TEXAS          ^^90% BLUE 10% RED             *90% BLUE 10% RED
******GEORGIA        ^^90% BLUE 10% RED^100% YELLOW 100% YELLOW
********ATLANTA      ^^90% BLUE 10% RED^100% YELLOW *100% YELLOW
******VIRGINIA       ^^90% BLUE 10% RED             *90% BLUE 10% RED
******MINNESOTA      ^^90% BLUE 10% RED             *90% BLUE 10% RED
******CALIFORNIA     ^^90% BLUE 10% RED             *90% BLUE 10% RED
****MX                                              *
******GUANAJUATO                                    *
****CA               ^^50% BLUE 50% RED             50% BLUE 50% RED
******BC             ^^50% BLUE 50% RED             *50% BLUE 50% RED
******ONTARIO        ^^50% BLUE 50% RED^100% RED    100% RED
******SAKSATCHEWAN   ^^50% BLUE 50% RED             *50% BLUE 50% RED

14 rows selected.

Review &
      Followup

      Rating: 5
Mea
      culpa  March 17, 2003
Reviewer:  Basil

Tom,

I was heading out the door to spend some long-promised time flying kites with
the kids, but I wanted to send a quick follow-up to your original response. I
did a quick glance, an the output wasn't quite what I needed, hence my original
response. Sorry to have discounted your proposal so soon.

Your solution, I think, will work for my needs. I'll start doing some testing
today. Thanks so much for coming up with a very novel solution to what has been
a difficult problem for me.

      Rating: 5
Is there a way
      to use something besides varchars for this?  March 19, 2003

Reviewer:  Basil
One problem I've run into now is that, for a given location, I'll have thousands
(20,000 in one case in my current data set) of location_pct entries. This makes
a string impractical here.

One additional complication is that I'd like to be able to
specify one additional entry in location_pct, that serves
as a member of the location_pct tuple on output. In my test
cases so far, I've just added it to the concatenated string,
but it would be nice if I could some how join to it on
the output, just as I can join to the location_name in our
example so far. To flesh it out with some DDL:

CREATE TABLE seller (
    seller_name VARCHAR2(50) NOT NULL PRIMARY KEY );

INSERT INTO seller VALUES ('GROCERY');
INSERT INTO seller VALUES ('BEAUTY SUPPLY');
INSERT INTO seller VALUES ('GAS STATION');

CREATE TABLE location_pct (
    location_name VARCHAR2(50),
     seller_name   VARCHAR2(50),
    color         VARCHAR2(10),
    pct           NUMBER,
    PRIMARY KEY (location_name, seller_name, color, pct) );

INSERT INTO location_pct VALUES ('US', 'GROCERY','RED',10);
INSERT INTO location_pct VALUES ('US', 'BEAUTY SUPPLY','BLUE',90);
INSERT INTO location_pct VALUES ('GEORGIA', 'BEAUTY SUPPLY','YELLOW',100);
INSERT INTO location_pct VALUES ('MEXICO', 'GAS STATION', 'GREEN',100);
INSERT INTO location_pct VALUES ('CA', 'GROCERY', 'RED',50);
INSERT INTO location_pct VALUES ('CA', 'GROCERY', 'BLUE',50);
INSERT INTO location_pct VALUES ('ONTARIO', 'GAS STATION','RED',100);

Any further thoughts on these issues?

          Followup:

which string is impractical?

PCT or PCT2?

      Rating: 5
More
      follow-up  March 19, 2003
Reviewer:  Basil

The innermost PCT seems to be the limitation:

(select location.*,
  5             rtrim((
  6               select max(decode(rownum,1, pct||'% '||color, null))||' '||
  7                      max(decode(rownum,2, pct||'% '||color, null))||' '||
  8                      max(decode(rownum,3, pct||'% '||color, null))||' '||
  9                      max(decode(rownum,4, pct||'% '||color, null))||' '||
 10                      max(decode(rownum,5, pct||'% '||color, null))||' '||
 11                      max(decode(rownum,6, pct||'% '||color, null))
 12                 from location_pct
 13                where location_name = location.location_name ))  pct
 14            from location )

Why? Well, first of all, the number of location_pct entries
for a given location can number in the thousands. (Even in the hundreds,
at 10 characters for each location_pct tuple in the string, you're
at 10 * 600 = 6000, longer than a SQL VARCHAR2.)

Secondly, I'd have to list all the possible row numbers, which
makes for a REALLY long query. (Even generating it dynamically, I'd
have a good chance of bumping the 32k VARCHAR2 limit for EXECUTE IMEMDIATE,
e.g., 50 chars for each max(decode()) * 600 = 30000.)

          Followup:

how can you have thousands of pcts?  and be meaningful?

what would the output be like???  don't see how you can get anything from that.

the other pct2 -- maybe can fix that.  this one -- I cannot imagine what the
resulting output to the end users would even be.

      Rating: 5
Still more
      followup  March 19, 2003
Reviewer:  Basil

I tried to simplify the situation in my original posting, expecting to extend
your proposal to fit my specific needs.

In my particular case, the issue is that of the SELLER table.
I have thousands of seller entries.

So, in each location, there can be a seller, and each seller can have several
location_pcts.  In practical terms, I need to be able to drill down at a
location level, finding the sellers and the sellers' location_pcts. If the
location has a known seller specifically, it's easy, The problem is when a
seller has "territory" rights. Thus, when I ask about a sub-territory (a child
location) that has no specific seller-location_pcts defined, I need to roll-up
to a higher level to get the applicable entries.

The fact that there are thousands of location_pct entries is due to the fact,
that for a given location, there might be (thousands of sellers * several dozen
color/pcts).

Ideally I'd like to answer the question: What are the color/pcts for this seller
in this location, where location might not have a direct entry in the
location_pct table?

          Followup:

Sorry -- don't think I'll be able to answer that as I don't know how to find a
"direct" entry if there are thousands of them.   I'm not grasping the entire
question here.

      Rating: 5
Yet more
      followuph  March 20, 2003
Reviewer:  Basil

If you'll oblige, I'll give this another shot at clarification.

My original question only had location and location_pct, which was
perhaps an oversimplification.

In reality, what I have is something like (realizing this may
not quite match data-wise with what I gave you in my previous response):
NA
   US
                     GROCERY: 10% RED   90% BLUE
                            GAS STATION: 50% GREEN 50% BLUE
     TEXAS
     GEORGIA
                      GROCERY: 100% YELLOW
        ATLANTA
     CALIFORNIA
     VIRGINIA
     MINNESOTA
   MX
                     GAS STATION: 100% GREEN
     GUANAJUATO
   CA
                      GROCERY: 50% RED  50% BLUE
                            GAS STATION: 80% YELLOW 20% ORANGE
     BC
     ONTARIO
                      GROCERY: 100% RED
     SASKATCHEWAN

Data-wise, I could do it this way:

-- (no change for location)
CREATE TABLE location (
  location_name VARCHAR2(50),
  parent_location_name VARCHAR2(50),
  PRIMARY KEY (location_name ));

INSERT INTO location VALUES ('NA',NULL);
INSERT INTO location VALUES ('US','NA');
INSERT INTO location VALUES ('TEXAS','US');
INSERT INTO location VALUES ('GEORGIA','US');
INSERT INTO location VALUES ('CALIFORNIA','US');
INSERT INTO location VALUES ('VIRGINIA','US');
INSERT INTO location VALUES ('MINNESOTA','US');
INSERT INTO location VALUES ('MX','NA');
INSERT INTO location VALUES ('GUANAJUATO','MX');
INSERT INTO location VALUES ('CA','NA');
INSERT INTO location VALUES ('BC','CA');
INSERT INTO location VALUES ('ONTARIO','CA');
INSERT INTO location VALUES ('SAKSATCHEWAN','CA');
INSERT INTO location VALUES ('ATLANTA','GEORGIA');

-- no change from seller given previously
CREATE TABLE seller (
    seller_name VARCHAR2(50) NOT NULL PRIMARY KEY );

INSERT INTO seller VALUES ('GROCERY');
INSERT INTO seller VALUES ('BEAUTY SUPPLY');
INSERT INTO seller VALUES ('GAS STATION');

-- location_pct: now includes seller
CREATE TABLE location_pct (
    location_name VARCHAR2(50),
     seller_name   VARCHAR2(50),
    color         VARCHAR2(10),
    pct           NUMBER,
    PRIMARY KEY (location_name, seller_name, color, pct) );

INSERT INTO location_pct VALUES ('US', 'GROCERY','RED',10);
INSERT INTO location_pct VALUES ('US', 'GROCERY','RED',90);
INSERT INTO location_pct VALUES ('US', 'GAS STATION','GREEN',50);
INSERT INTO location_pct VALUES ('US', 'GAS STATION','BLUE',50);
INSERT INTO location_pct VALUES ('GEORGIA', 'GROCERY','YELLOW',100);
INSERT INTO location_pct VALUES ('MX', 'GAS STATION', 'GREEN',100);
INSERT INTO location_pct VALUES ('CA', 'GROCERY', 'RED',50);
INSERT INTO location_pct VALUES ('CA', 'GROCERY', 'BLUE',50);
INSERT INTO location_pct VALUES ('CA', 'GAS STATION', 'YELLOW',80);
INSERT INTO location_pct VALUES ('CA', 'GAS STATION', 'ORANGE',20);
INSERT INTO location_pct VALUES ('ONTARIO', 'GROCERY','RED',100);

There are perhaps 30k locations, 100k sellers (not in every location),
and 400 or so colors.

The question (restated): Given a location and a seller, return the
colors and percentages that apply. The location specified, though,
might not have a "direct" seller/color/pct specification. In that case,
the color/pct specification of the lowest level parent above
the requested location with the requested seller should be returned.
It's basically the same as the original problem with the complication
that asking for the same organization with different sellers might
yield results from different nodes of the hierarchy.

For example a request for the location ATLANTA with seller GROCERY
should return (YELLOW, 100) (found at node GEORGIA).

A request for the location ATLANTA with seller GAS STATION
should return (GREEN,50; BLUE, 50) (found at node U.S., because
there is no GAS STATION seller lower in the branch).

In a way, this is a star schema, with LOCATION and SELLER as dimensions,
and LOCATION_PCT as fact table. LOCATION, though, is hierarchical.
SELLER is not.

          Followup:

ops$tkyte@ORA920> variable lname varchar2(25)
ops$tkyte@ORA920> variable seller varchar2(25)
ops$tkyte@ORA920> exec :lname := 'ATLANTA'

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec :seller := 'GAS STATION'

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select *
  2    from (
  3  select c.* , rank() over ( order by lvl ) rnk
  4    from (
  5  select lvl, a.location_name, b.color, b.pct
  6    from (
  7  select level lvl, location.location_name, location.parent_location_name
  8    from location
  9   start with location.location_name = :lname
 10  connect by prior location.parent_location_name = location.location_name
 11         ) a,
 12         location_pct b
 13   where a.location_name = b.location_name
 14     and b.seller_name = :seller
 15   order by lvl
 16        ) c
 17        )
 18  where rnk = 1
 19  /

       LVL LOCATION_NAME        COLOR             PCT        RNK
---------- -------------------- ---------- ---------- ----------
         3 US                   BLUE               50          1
         3 US                   GREEN              50          1

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec :seller := 'GROCERY'

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> /

       LVL LOCATION_NAME        COLOR             PCT        RNK
---------- -------------------- ---------- ---------- ----------
         2 GEORGIA              YELLOW            100          1

      Rating:
      5
Ahhhhh!  March 20, 2003
Reviewer:
      Basil
Simplicity at its finest. Yes, analytics DO rock.

So, do you have a beer/wine/coffee/vodka delivery address? I'd like to send a
case along.

      Rating: 5
Didnt Actually
      Read it  March 20, 2003
Reviewer:  Andy
      Gilfrin  from Dover, Kent, UK
I didnt actuall read most of this but it does show a good point. If you asking a
question make sure you explain exactly what your doing rather than trying to
simplify, you can simplify to the point where your not actually asking the right
king of question.

If the original post had been as detailed as the final one Tom would have had
spent less time and been able to spend more time answering other questions and
the other guy could have got more kite flying done.

Analytics Rock and Kites Rock, I got one last year with an Action Man (GI Joe)
on and he went up the kite and when he got to the top detached and parachuted
down.

          Followup:

yes, I have spent lots of time answering questions only to discover "well, sure,
your answer does that but what I really need is....." ;)

      Rating: 5
A
      suggestion!!!  March 20, 2003
Reviewer:  VJ
       from CA,USA
Hi Tom,

I am an every day/every hour visitor to your site and have been observing that
"you have a huge backlog of questions that you need to take care of and asking
to post the question again later" for almost 10 days or more in a row.

I was wondering if you setup an option (a tab) on your site to list the question
at hand to answer and ask "the others" like us to take a stab at it, that might
clear the backlog of questions you have.

You anyway right now allow people to post reviews on the questions asked,
rather, you can ask people to answer the questions and may review the answers
and post your own if you think that the given answers are not sufficient enough
or not correct.

My two cents.....what do you say?

          Followup:

well, I say.... I've been taking some questions:

ask_tom@ASKUS> select trunc(timestamp), count(*)
  2  from wwc_ask_submitted_questions$
  3  where timestamp > sysdate-11
  4  group by trunc(timestamp);

TRUNC(TIM   COUNT(*)
--------- ----------
09-MAR-03          9
10-MAR-03         24
11-MAR-03         18
12-MAR-03          9
14-MAR-03         10
15-MAR-03         24
16-MAR-03         22
17-MAR-03         14
18-MAR-03         15

9 rows selected.

But near average on the days I was around:

  1  select min(cnt), max(cnt), avg(cnt)
  2  from ( select count(*) cnt
  3           from wwc_ask_submitted_questions$
  4             where timestamp >= to_date( '01012002', 'ddmmyyyy' )
  5*         group by trunc(timestamp) )
ask_tom@ASKUS> /

  MIN(CNT)   MAX(CNT)   AVG(CNT)
---------- ---------- ----------
         1         70 20.2872928

for new questions actually.. but then again I have been traveling a ton as
well...

My 2cents -- when I'm not here:

otn.oracle.com -> discussion forums is a great place to go
groups.google.com -> comp.databases.oracle.* are awesome groups
metalink.oracle.com -> Jonathan Lewis has been known to hang there as well as
comp.databases.oracle.*, you don't get much better then that!

so, rather then re-inventing the wheel, I'd rather have things like that over
there.  Beside, I try to personally check everything that is posted here -- I
will correct a followup or pull a post from time to time.  If I had an offshoot
that had material from just anywhere that I did not do this too -- I think it
would "water down" whats here.

The way I got started on this was actually in an editorial fashion.  I hung out
in the comp.databases.* newsgroups.  There I found a posting that said "hey, to
rename columns just update this sys. table".  I slammed em, they pushed back
with "well, we got this from oracle.com" -- sure enough, it was the read tips
section, Oracle magazine put it up there -- they didn't have a technical editor.
 I made myself their technical editor and asktom followed.

groups.google.com (the usenet newsgroups) is one of the best places to go

March 21, 2003 --
      Thanks for the question regarding "how to control exception", version
8.1.7

      You Asked
Hi Tom.

I put a select/into/from/Where in a trigger and I need deal with
two cases: there is a row I do something, if no row I do someting else, and
finally I do something common.

Begin
 ...

  select ..
  into temp
  from..
  Where  ..

  if temp is null
  then
      Statements1;
  else
      Statements2;
  end if;

  Statements3;

Exception
  when ...
  ....

End;
/

But problem now is if no row found, it will throw execptions " ORA-01403: no
data found". I don't have have chance to run statement 3.
I can copy Statements1 and Statements3 to exception part. But I don't
think it is a good way? Does Oracle have something like
try/catch in JAVA? or What is the better way to do this?

Thanks!

Steve

      and we said...
begin
    begin
         select into
         code when select into succeeds
    exception
         when no_data_found then
              code for that case
    end;
    code in common
end;
/

Just float the exception block down the right level -- not for the whole trigger
but for the single statement.

Just like try/catch in java -- begin end (from waaaayyyy before java)
Review &
      Followup

      Rating: 4
Still
      UNCLEAR  March 21, 2003
Reviewer:  Sajid
      Anwar  from London
Hi Tom,
I am still not clear with your response. Here is what the original question was
about.

Begin
 ...
  select ..
  into temp
  from..
  Where  ..

  if temp is null
  then
      Statements1;
  else
      Statements2;
  end if;

  Statements3;

Exception
  when ...
  ....

End;

IF NOT DATA do statement1 ELSE do statement2. Do statement3 in either case.
I agree with you that we should have individual begin-exception-end block for
individal select-into statement. This is what you suggested. But i dont think
that answers the original question in total.

begin
         select into
         code when select into succeeds
    exception
         when no_data_found then
              code for that case
    end;
    code in common

Correct me, if i am wrong.Is that what you mean in your reply?
when no_data_found then
   statement1
end
statement3;

what happen to statement2, when will that be executed?

Regards,
Sajid Anwar
London

          Followup:

consider the following full block of code.  In the first block, I use where x =
1, finds a row.  in the second, where x = 2 -- doesn't find.

Hope this clears it up.

ops$tkyte@ORA920> create table t ( x int, y int );

Table created.

ops$tkyte@ORA920> insert into t values ( 1, 2 );

1 row created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      l_y   number;
  3  begin
  4      begin
  5          select y into l_y from t where x = 1;
  6          dbms_output.put_line
  7          ( 'Code when exists in T goes here' );
  8      exception
  9          when no_data_found then
 10              dbms_output.put_line
 11              ( 'Code when NOT exists in T goes here' );
 12      end;
 13
 14      dbms_output.put_line
 15      ( 'Code that is to execute in both cases goes here' );
 16  end;
 17  /
Code when exists in T goes here
Code that is to execute in both cases goes here

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      l_y   number;
  3  begin
  4      begin
  5          select y into l_y from t where x = 2;
  6          dbms_output.put_line
  7          ( 'Code when exists in T goes here' );
  8      exception
  9          when no_data_found then
 10              dbms_output.put_line
 11              ( 'Code when NOT exists in T goes here' );
 12      end;
 13
 14      dbms_output.put_line
 15      ( 'Code that is to execute in both cases goes here' );
 16  end;
 17  /
Code when NOT exists in T goes here
Code that is to execute in both cases goes here

PL/SQL procedure successfully completed.

      Rating: 3
This should be
      like this !!  March 21, 2003
Reviewer:  Vineet
      Jain  from India
begin
 begin
  Select col1, col2
    into var1, var2
    from some_table
   where some_condition;
  statement 2;
 exception
 when no_data_found
 then
  statement 1;
 end;
statement 3;
end;

      Rating: 3
Hi vineet
        March 21, 2003
Reviewer:  mk  from
      sing
This is manoj r u the one from WIT?? do reply here ..

Sometimes we  can use this website as chat room . Huh?

March 21, 2003 Marianne -- Thanks for the question regarding
      "Oracle Text", version 9.0.1

      You Asked
Hi Tom,

Is it possible to insert binary files in a CLOB and use Oracle Text to search
the field?  Could you please explain the difference between BLOB and CLOB?

Thanks
Marianne

      and we said...
binary files + clob = oxymoron.

binary files + blob = success.

A clob is a character field.  We do character set conversions on the data.  We
expect the data to be all character data.  We do not expect binary data in the
input stream.  Putting binary data into a clob would be like using vi to edit an
Oracle datafile -- it would not work -- all kinds of garbage on the screen and
you probably hit and end of file character that would truncate the file or a
line that is too long to fit.  Suppose you put binary data into a clob and the
database character set was WE8ISO8859P1 (western european 8 bit data).  Later a
client with US7ASCII fetches the data out.  We'll convienently strip the high
bit off for that 7bit client -- thusly damaging the binary file.

A blob is just raw data.  binary data.  we don't look at it, we don't touch it,
we don't make any assumptions about it -- we just store it as is.  We serve it
out as is.

You would use a blob to store binary documents like pdfs and such in Oracle and
Oracle Text can index those.
March
      21, 2003 Charlie -- Thanks for the
      question regarding "query", version 8.1.7

      You Asked
Hi Tom,

I have this parent child relationship.

parent child
------ ------
a1     b1
b1     c1
c1     d1
d1     e1
...

How can I write a query to return the following results since the grandparent is
"a1" in this tree?

parent child
------ ------
a1     b1
a1     c1
a1     d1
a1     e1

thanks,

      and we said...
ops$tkyte@ORA920> select substr(
  2         (select max(to_char(level,'fm00000') || p )
  3            from t t2
  4                   start with c = t.c
  5             connect by prior p = c ), 6 ) top_lvl, c
  6    from t
  7  /

TOP C
--- --
a1  b1
a1  c1
a1  d1
a1  e1

Review &
      Followup

      Rating:
      4
  March 20, 2003
Reviewer:  Charlie

Hi Tom,

Thanks for your answer!

I modified your query a little bit.  So here is the table m:

P   C
--- ---
a1  b1
b1  c1
c1  d1
d1  e1
a2  b2
b2  c2
a3  b3
b3  c1

Here is the query:

SQL> l
  1   SELECT substr(
  2            (SELECT MAX(TO_CHAR(LEVEL,'fm00000') || '/' || p )
  3                FROM m m2
  4                       START WITH c = a.c
  5                 CONNECT BY PRIOR p = c ), '7') top_lvl, c
  6        FROM
  7   (
  8      select p, c
  9      from m
 10      start with p in('a1', 'a2')
 11      connect by prior c = p
 12*  ) a
SQL> /

TOP_ C
---- ---
a1   b1
a3*   c1  <=====
a3*   d1
a3*   e1
a2   b2
a2   c2

6 rows selected.

The parent shuffled!!

The desired output should be as follows:

TOP_ C
---- ---
a1   b1
a1   c1
a1   d1
a1   e1
a2   b2
a2   c2

So how can we make it work?

thanks,

          Followup:

umm, you are apparently "making up stuff".

How did you get from C1 whose parent is B2 whose parent is A3 and thats the top
of the tree -- to A1?

I see *no* relationship between C1 and A1.  Explain.

      Rating:
      2
relationship  March 20, 2003
Reviewer:
      Charlie
I did not make that up.  I do have this kind of data, not 'a1', 'b1' but
similar.

 a3 -> b3 \
           \
a1 -> b1 -> c1 -> d1 -> e1

Different parents have the same child, like Dad and Mom having only one son,
which is reasonable.

The following result was generated from that query.

TOP_ C
---- ---
a1   b1
a3*   c1  <=====
a3*   d1
a3*   e1
a2   b2
a2   c2

          Followup:

Tell me then -- how in the world did YOU know (that is what I meant by "making
it up" -- there is some thought process you are going thru that is not known to
me as yet) that A1 is the correct parent?

Seems to me that A3 is as correct as A1.  You asked "find me the parent".  I
found Mom, you seem to want Dad.

So, what algorithmic process did you go through in order to decide "A1" is the
correct parent?

      Rating:
      2
relationship  March 21, 2003
Reviewer:
      Charlie
The query I used was:

SQL> l
  1   SELECT substr(
  2            (SELECT MAX(TO_CHAR(LEVEL,'fm00000') || '/' || p )
  3                FROM m m2
  4                       START WITH c = a.c
  5                 CONNECT BY PRIOR p = c ), '7') top_lvl, c
  6        FROM
  7   (
  8      select p, c
  9      from m
 10      start with p in('a1', 'a2')  <==============
 11      connect by prior c = p
 12*  ) a
SQL> /

Since I was passing in "start with p in('a1', 'a2')", I want to find mom(A1) not
dad(A3) that what I meant.

thanks,

          Followup:

just add where p in ('a1', 'a2' ) to the scalar subquery then.

 1   SELECT substr(
  2            (SELECT MAX(TO_CHAR(LEVEL,'fm00000') || '/' || p )
  3                FROM m m2
                 where p in ( 'a1', 'a2' )
  4                       START WITH c = a.c
  5                 CONNECT BY PRIOR p = c ), '7') top_lvl, c
  6        FROM
  7   (
  8      select p, c
  9      from m
 10      start with p in('a1', 'a2')  <==============
 11      connect by prior c = p
 12*  ) a

      Rating:
      4
THANKS!  March 21, 2003
Reviewer:
      Charlie

March 21, 2003 Bala -- Thanks for the question regarding "Why PK
      ... why not unique index + not null", version 8.1.6

      You Asked
Hi Tom,

I just got one question.

In one of the table design, developer came up with the question,

Why should one use Primary Key and why not Unique index + NOT NULL.

It does the same thing. He is not expecting any FKs to this table in the future.

The only thing i was able to explain about the importance of PK was :

1. In the future releases of Oracle , PK is the one going to stay and we should
use PK in our tables.

2. In case of replication, if the table does not have PK we cant replicate the
that table.

Is there any other reason why PK is better than Unique index or Unique
constraint (NOT NULL)

Thanks

      and we said...
My question to the developer

why use a unique index + not null when you could use a primary key?

A primary key has semantic meaning, it TELLS people things.
A primary key is used by the database for query rewrites and other purposes.
A primary key in an ad-hoc query tool *MEANS* something.

Why does the develop want to hide the fact you have a primary key from the
world?  You should be trying to stuff as much semantic meaning, use as MANY
integrity constraints in the database as you humanly can.  It is not only used
by the optimizer and tools -- it actually *DOCUMENTS* what is going on.

So, tell the developer to justify why they want to do it the hard way?  And lose
documentation?  And lose meaning?
Review &
      Followup

      Rating: 5
Also one more
      reason  March 21, 2003
Reviewer:  Sajid
      Anwar  from London
Hi Tom,
One last reason why PRIMARY KEY should be used.
Primary Key imposes UNIQUE CONSTRAINT as well. Thats not possible with UNIQUE
INDEX and NOT NULL constraint.

Am i right , TOM?

Regards,
Sajid Anwar
London

          Followup:

No, a primary key imposes a primary key constraint -- a primary key constraint
says "data must be unique and not null"

A unique constraint imposes a unique constriant.  That says "data must be
unique"

      Rating: 4
Not convinced
      !!  March 21, 2003
Reviewer:  Sajid Anwar
       from London
Hi Tom,

Here is my test case:

SQL> create table mytable ( a number primary key);

Table created.

SQL> insert into mytable values(1);

1 row created.

SQL> /
insert into mytable values(1)
*
ERROR at line 1:
ORA-00001: unique constraint (TUSKERDEV.SYS_C004635) violated

SQL> drop table mytable
  2  ;

Table dropped.

It doesnt say "PRIMARY KEY CONSTRAINT VIOLATED" rather it says UNIQUE CONSTRAINT
VIOLATED. I accept your point that it has UNIQUE CONSTRAINT behaviour, that
comes by default.

What do you think now?

Regards,
Sajid Anwar
London

          Followup:

you don't have to be convinced.

there are primary keys = unique + not null
there are unique constraints = unique.

if you want to split hairs, so be it.

ops$tkyte@ORA920> create table t ( x int );

Table created.

ops$tkyte@ORA920> create unique index t_idx on t(x);

Index created.

ops$tkyte@ORA920> insert into t values ( 1 );

1 row created.

ops$tkyte@ORA920> /
insert into t values ( 1 )
*
ERROR at line 1:
ORA-00001: unique constraint (OPS$TKYTE.T_IDX) violated

Now what?  that conflicts with your statement:

....
One last reason why PRIMARY KEY should be used.
Primary Key imposes UNIQUE CONSTRAINT as well. Thats not possible with UNIQUE
INDEX and NOT NULL constraint.
........

but it isn't a unique constraint really -- there is no constraint.  It is just
the general purpose error message returned when you violate uniqueness.

March
      21, 2003 Shruti -- Thanks for the
      question regarding "changing fonts in the owauti.tablePrint", version
      4.0.8.1

      You Asked
Hi Tom,

How can I change the font size in the following procedures?

owa_util.tableprint function

I have tried sevral things I have also tried changing fonts
using
htp.basefont,
htp.fontOpen and htp.fontclose. I hev also tried looking into
using htp.tableopen , htp.rowopen and close respectively instaed
of using the owa_util.tableprint function but my font does not
change,

Please help!!

Thanks,
Shruti

My program looks like this:
ate or replace procedure  printsummary (p_grp varchar2 default
null)
  IS
    CURSOR emp_cursor IS
      SELECT module, comments, code
      FROM   master980
      WHERE  grp = p_grp;

   ignore boolean;
    begin

    htp.htmlOpen;
    htp.bodyOpen;

    htp.tableOpen('BORDER ');
    htp.tableRowOpen;
     htp.tableHeader ('MODULE ');
     htp.tableHeader ('COMMENTS ');
     htp.tableHeader ('CODE');
    htp.tableRowClose;
    FOR i IN emp_cursor LOOP
      htp.tableRowOpen;

    htp.basefont(0);
       htp.tableData (cvalue=>i.module, cattributes=>'FONT SIZE
= "0"');
       htp.tableData (cvalue=>i.comments, cattributes=>'FONT
SIZE = "0"');
       htp.tableData (cvalue=>i.code, cattributes=>'FONT SIZE =
"0"');
      htp.tableRowClose;
   END LOOP;
    htp.tableclose;
    htp.bodyClose;
    htp.htmlClose;

end;
/

      and we said...
You could do it with cellsprint like this:

scott@ORA8I.WORLD> begin
  2  owa_util.cellsPrint(
  3  owa_util.bind_variables
  4    ( 'select ''<font size=0>'' || ename || ''</font>'',
  5              ''<font size=0>'' || empno || ''</font>'',
  6              ''<font size=0>'' || job || ''</font>''
  7         from emp
  8        where job = :x', ':x', 'CLERK' ) );
  9  end;
 10  /

PL/SQL procedure successfully completed.

scott@ORA8I.WORLD> exec owa_util.showpage
<TR>
<TD><font size=0>SMITH</font></TD>
<TD><font size=0>7370</font></TD>
<TD><font size=0>CLERK</font></TD>
</TR>
<TR>
<TD><font size=0>JAMES</font></TD>
<TD><font size=0>7900</font></TD>
<TD><font size=0>CLERK</font></TD>
</TR>
<TR>
<TD><font size=0>ADAMS</font></TD>
<TD><font size=0>7876</font></TD>
<TD><font size=0>CLERK</font></TD>
</TR>
<TR>
<TD><font size=0>MILLER</font></TD>
<TD><font size=0>7934</font></TD>
<TD><font size=0>CLERK</font></TD>
</TR>

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating: 3
pass class
      attribute  March 21, 2003
Reviewer:  Vikas
      Sharma  from Delhi India
Hi Tom,

How can i pass the class='myclass' to the <td> tag of tableprint. for eg. <td
class="myclass">

Thanks

Regards,

Vikas Sharma

          Followup:

you cannot -- you have the source code for cellsprint tho, just read it out of
the database or from $ORACLE_HOME/rdbms/admin, modify it and call it by your own
name.

      Rating:
      3
Thanks  March 21, 2003
Reviewer:  Vikas
      Sharma  from Delhi India
Hi Tom,

Thanks thanks for providing me this information. I have the source and will
modify it accordingly.

Regards,

Vikas Sharma

March 21, 2003 charlie -- Thanks for the question regarding "How
      to get the reponse time", version 9.2.0

      You Asked
Hi, Tom,
Thanks god I got the change to ask you a question.

My question is:
Is there any way we can get a response time of a sql without running it, by
tpprof, just by selecting v$sql?

Metalink's RDA(Remote Diagnostic Agent) seems give us a reponse time of sqls in
our database, by studying their code, they seem to use
buffer_gets/500/100 = response time in seconds, I could be wrong.

Is there any reason behind these? Is there other way we can get the response
time?

Thanks and have a good weekend

      and we said...
short of actually running a query there is no accurate, not even close to
accurate, method.

March 21, 2003 Boris -- Thanks for the question regarding
      "Moving Index only to new Tablespace.", version 8.1.7

      You Asked
Hi Tom,

I have a database where all the tables belong to a Tablespace PROD and the
indexes to INDX.

The application developers have requested that I move selected Tables and
indexes to new Tablespaces.

My question is this :

Some tables need to stay in their current Tablespace and the index moved to a
new Tablespace. Can this be safely done with :
alter INDEX ACC_PK rebuild tablespace NEWIDX storage(....

What about the old index ?...

Incidentally what would be the main reason for multiple Tablespaces in the first
place ?...

Thank you for the outstanding Forum...

Boris.

      and we said...
Yes, to move the tables -- just use alter table MOVE.  then you must rebuild all
indexes on that table.

To move the index, use alter index rebuild tablespace ....

USE LOCALLY MANAGED TABLESPACES and drop the storage clause!!!! save yourself
lots of grief and outlaw storage clauses all together!

the old index?  there isn't one.  the rebuild leaves you with one index at the
end of the process.

Multiple tablespaces is mostly for administrative ease.

you backup a tablespace,
you recover a tablespace,
you transport a tablespace,
you manage tablespaces....

March 21, 2003 -- Thanks for the question regarding "Update
      query", version 9.2.0

      You Asked
Thanks for you great service.

I have a problem with an update statement. Here is the example (I was trying to
simulate the problem)

Lets say I have employee table (emp) with following columns

Emp_id number,
Emp_name,
Dept_no – Here department number can have null
Dept_name.

I have a query like this

Update emp
Set dept_name = (select dept_name from dept where dept.dept_no = emp.dept_no)
Where exists (select 1 from dept where dept.dept_no = emp.dept_no)

The reason behind this query is the update statement can perform only the rows
that have dept_no, which saves full table scan.

Now the query is too slow. Is it because of the logic in the query or it depends
on environment and data?

      and we said...
do this

update ( select emp.dept_name emp_dept_name,
                dept.dept_name dept_dept_name
           from emp, dept
          where emp.dept_no = dept.dept_no )
   set emp_dept_name = dept_dept_name;

Your query will

o full scan emp
o for every row in emp, fire the where exists query
o for every row that exists in prior step, fire the set query

My query will probably

o full scan emp and dept and hash join them (brutally efficient)  or

o full scan the small dept table and nested loop join that to emp and update

This technique mandates that the DEPT table have a primary key or unique
constraint on DEPTNO to work.
March
      21, 2003 Joseph -- Thanks for the
      question regarding "Enterprise User Security", version 9.2.0.1.0

      You Asked
Hi Tom,

I have successful configure Oracle enterprise user security where all clients
(to be 10-30,000) is centralize manage within the Oracle OID. The web
application connect all our client to database via a share schema using JDBC
thin driver.

1) For enterprise user that connect through share schema, the
sys_context('userenv', 'session_user') does not show the true client user as
session_user. The only place that I see the enterprise user audit information is
in the comment_text column of the dba_audit_trail table. Also, our project
require make use of the Oracle VPD (fined grain access control). Can you please
show how to obtain the enterprise user (LDAP user) as session user???

2) As part of the enterprise user configuration, we have to use the Oracle
provide Enterprise Security Manager GUI to create LDAP Enterprise Global Role
then map the global role to database role then the whole bunch of other
steps.... Is it possible to duplicate  Enterprise Security Manager functionality
within our Web application??? what I means is to implement Enterprise Security
manager as part of our web application instead of run it seperately out side of
our application.

Thanks,

Joseph Nguyen

      and we said...
I asked David Knox, our Chief Engineer in the Oracle Information Assurance
Center to look at this and he says:

1) Correct, the session_user is the database schema that you mapped your
enterpise user to.   Enterprie Users can be seen in the 'external_name' value of
the USERENV i.e., sys_context('userenv','external_name') This gives you the
distinguished name of the user.  Base your VPD policies on this value.
You can easily write a function to format the name into something easier (such
as the common name attribute) if you so choose.

CREATE OR REPLACE function application_username (p_external_name in varchar2
default SYS_CONTEXT('USERENV','EXTERNAL_NAME'))  return varchar2
as
BEGIN
    return upper(substr(p_external_name,4,instr(p_external_name,',')-4));
end;
/

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

SQL> select sys_context('userenv','external_name') from dual;

SYS_CONTEXT('USERENV','EXTERNAL_NAME')
--------------------------------------------------------------

cn=Knox,ou=CONTRACTOR,ou=PKI,ou=DoD,o=U.S. Government, c=US

1 row selected.

SQL> select application_username from dual;

APPLICATION_USERNAME
--------------------------------------------------------------

KNOX

1 row selected.

SQL>

2) Not sure what you application is supposed to do. First for clarification,
Enterprise Security Manager (ESM) provides the capability to map enterprise
roles (stored in OiD)  to global roles (stored in the DB's).  ESM also assigns
enterprise roles to enterprise users. The users get their privileges from the
database global roles based on how those roles are mapped to the
enterprise roles assigned for them.  So, ESM is an administration tool, and its
functionality should probably not be incorporated into your application (Not
knowing what your application is trying to do,  I can only assume it is not for
provisioning new users or assigning authorizations to them.)  Your application
will just use privileges assigned to the global roles.  Please let me know if I
misunderstood what your application is supposed to do.

Review &
      Followup

      Rating: 5
Enterprise User
      Security  March 21, 2003
Reviewer:  Joseph
      Nguyen  from Vienna, VA USA
Thanks Tom and great solutions as usual. As for the second question, is it
possible for me to configure Enterprise User within my application? The scenerio
would be without using the ESM tool.
1) Our web application GUI create new LDAP user within oracle OID.
2) now instead of using the ESM admin to configure this new LDAP user. Is it
possible to write a PLSQL package to customize this new enterprise user??? The
idea is not to use the ESM but to develop our application so that every new
created LDAP user will get properly configure as if ESM is used to configure.

          Followup:

OID has a rich programming interface ( C, PL/SQL, Java) that allows you to
create a user in the directory - which can be leveraged for basic user creation
purposes. However, password management for database authentication and other
enterprise user security related administration must be done through our tools

March 22, 2003 Jayaraman -- Thanks for the question regarding
      "bringing up two listeners for two databases on the same machine", version
      8.1.6.0.0

      You Asked
Hi Tom,
I had already sent you a question regarding this. But i had
accidentally deleted the reply you had given me. Can you please
tell me how i have to configure listener.ora and tnsnames.ora on
the same server for two different databases. For example if we
have one database/instance as 'production' and the other
database/instance as 'test' how do we bring up the listeners for
these two databases individually .
Thanks in advance
regards,
Ashok

      and we said...
You just have multiple listener and sid_list_listener sections
in your listener.ora:

LISTENER_database =
  (DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS_LIST =(ADDRESS = (PROTOCOL = TCP)(HOST =
slackdog)(PORT = 1521))
      )
    )
  )

listener_extproc =
  (DESCRIPTION_LIST =
    (DESCRIPTION =
     (ADDRESS_LIST =
   (ADDRESS = (PROTOCOL = TCP)(HOST = slackdog)(PORT = 1522))
        (ADDRESS = (PROTOCOL = IPC)(KEY = extproc))
      )
    )
  )

SID_LIST_LISTENER_database =
  (SID_LIST =
    (SID_DESC =
      (GLOBAL_DBNAME = test)
      (ORACLE_HOME = /d01/home/oracle8i)
      (SID_NAME = test)
    )
  )

SID_LIST_LISTENER_extproc =
  (SID_LIST =
    (SID_DESC =
      (SID_NAME = extproc)
      (ORACLE_HOME = /d01/home/oracle8i)
      (PROGRAM = /d01/home/oracle8i/bin/extproc)
    )
  )

Now, I can

$ lsnrctl start listener_database
$ su - safeuser
$ lsnrctl start listener_extproc

to have my database listener running as "oracle" and my extproc
listener running as some "safe" user.

Review &
      Followup

      Rating: 3
Listener
      Behaviour  March 22, 2003
Reviewer:  vicky
       from INDIA
         i m New in DBA ..     i m using  Oracle  9.2.0  on Linux 7.3  Box
        *********************************************

My database name =  db1
sid                         =   siddb1

we have these parameters      Service_names  =    db1
                                            db_domain         =    vsnl.com
                                             instance_name   =    siddb1

i think is Normaly global Name is  ==>  db_name||'.'||db_domain ==>
service_names

but we can change Oracle global name through " Alter system rename Global_name
to db1.mtnl "
but it does not change the service name ....

is there any relation with actual global_name  or service_names parameter .. and
whenever i changed this parameter
runtime .. " Alter system set service_names = ABC "  my database got  crashed
... but not in 9.0.1 or 8.1.6
it done only in 9.2.0 on linux Box..

in static registration i tried to put in Listener.ora file .
.  Sid_list_listener in sid_desc  =>   Global_name =   not_global_name  << any
String  not actual global_name

but i can connect from client machine by providing service_name =
not_global_name

y oracle allow this ..

and when i set my parameter instance_name = TEST and in listener.ora same
SID_name = TEST
then i was unable to connect from client machine  ..

Pls clarify this .. what is relationship  between lister and
instance_name,service_names and global_name

Thanx ..

          Followup:

global name is not related to service is not related to sid.

A global name is used to UNIQUELY identify a database in a federation of
databases.  Using a global name, you can make it so the DBLINK from database
with global name A to database with global name B will only work if the database
link itself is named after "B".  This ensures when using dblinks, you KNOW what
database you are connecting to.  global names are for distributed databases.

A sid or site identifier, is used in conjunction with the oracle_home in order
to generate a unique key for a shared memory realm.  After that, it is
meaningless really.  You can use an oracle_home+oracle_sid to connect to an
oracle instance without using the network when you are on the machine that hosts
the database server.

A service is a name given to a COLLECTION (and a collection can be of size 1) of
instances.  You cannot to a SERVICE and using load balancing -- Oracle will pick
an instance to connect you to.  In most cases, there is a single instance in the
service and hence you always goto the same instance.  Using Oracle9i RAC in a
cluster however, there maybe 2, 3, .... N instances in a service and Oracle will
load balance connections over them.

Your database "did not get crashed" by simply renaming the global name.
Something might have happened but it wasn't the global name change.

[tkyte@tkyte-pc-isdn tkyte]$ plus

SQL*Plus: Release 9.2.0.1.0 - Production on Sat Mar 22 09:40:45 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

ops$tkyte@ORA920> alter database rename global_name to foobar;

Database altered.

So, not having any real information on what you mean by "crashed", I'll have to
pass on that comment.

You would not use a global name to connect clients to the server, you use global
names in a distributed database environment to enforce that a dblink must be
named after the global name so you are ensured you know what database you are
linking to.

      Rating:
      3
  March 26, 2003
Reviewer:
      A reader
.. Sorry Sir .. i meant when i issued this command

'Alter system set service_names = foobar ;'

i received this message system altered ..

and with in 1 minute when i tried to select any table ..

tns . packet failure ..

i tried this for 3 or four time ... every time my database got crashed ...

And Sir u said that not use Database global name for connectivity  from client
side ...  Listener in static registration Listener registerd with global_name

Sid_List_mylistener =
   (sid_list=
     (sid_desc = (Global_dbname=abc.vsnl.com)

and client sid Tnsnames.ora  required this global_name

Connect_data = (Service_name=abc.vsnl.com))

.. if Global_dbname in listener.ora and service_name in Tnsnames.ora is not same
i m unable to connect ...

pls explain ...

      Rating:
      3
ok  May 21, 2003
Reviewer:  liyirong
       from PRC CHINA
ok

March 24, 2003 Johan -- Thanks for the question regarding "Bad
      Oracle Installation", version 8.1.6

      You Asked
Tom,

We are in a situation where an inexperienced DBA installed
Oracle 8.1.6 and subsequently the 8.1.6.3.0 patch set on a
server, but with the TMPDIR environment variable pointing to a
non-existent directory. The install failed during the linking
stage and the DBA though it was safe to simply ignore the error
and continue.

We noticed the problem only when we tried to setup external
procedures, experienced problems and noticed that the
$ORACLE_HOME/bin/extproc executable had a size of 0 bytes. We
tried a re-installation and found the TMPDIR problem based on
the error message.

In order to resolve the problem, we stopped the Oracle database,
corrected the TMPDIR reference and re-installed Oracle and the
patch set.

My questions are:

(1) We didn't notice any other problems due to the linking
problems and imported and updated several million rows with this
incomplete installation. Is it safe to continue using this
database or can we run into problems at some stage (i.e. could
the non-relinked executables cause "invisible" problems like
database corruption, etc.) ? If potential for problems exist,
what action do you suggest ?

(We can start over and recreate the database and rerun the
imports and updates, should that be required, but it will
probably take a week - i.e. we don't want to waste a week if its
not absolutely necessary. On the other hand, if we know there
could be potential problems in the future, it may be worth the
time and effort, since the system is still pre-production and
correcting problems later will be more difficult).

(2) Why is it necessary for the Oracle Universal Installer to
relink to Oracle executables ?

(3) Why does the Oracle Universal Installer offer the option to
ignore this kind of error ? This makes installations appear to
work, even though they may not have completed successfully, with
the potential of causing many problems that initially go
unnoticed.

Regards
Johan

      and we said...
1) You will be fine in the current configuration.  Since you've
relinked, all is well.  I do not believe you will have any
issues.  For running the database, there is really only one
executable "oracle" -- lgrw, dbwr, arch, dedicated servers, etc
are all really just $ORACLE_HOME/bin/oracle.  It was either
relinked or not during the patch upgrade.  There isn't a chance
to have had a lgrw and dbwr at different release levels for
example.

2) because what gets linked into the database depends on the
selections you make when you install.  If you install Net8 for
tcp/ip and the Advanced Security option -- those object code
files are added.  If you just use tcp/ip, ASO is not linked in.
If you choose parallel server, that is linked in -- else not.
Depending on what you install -- you'll have a fairly unique
binary for your configuration and OS.

3) It does this because you can relink the kernel easily from
the command line using the make command yourself.  You might hit
an issue (such as insufficient space to link) that you know you
can fix easily afterwards and just let the installation finish.
If we just aborted the install, it would thwart many people.  We
give you the choice.

It should be clear to the installer (the person installing) that
there were issues -- they physically have to pick "ignore this
error".
Review
      & Followup

      Rating:
      5
Parameters  March 22, 2003
Reviewer:
      TH  from Manama, BAH
Can you please bief the parameter settings while installing Oracle (Cutom
database creation) in some of the practical situations?

Thanx in Advance.

          Followup:

just use DBCA and let it default -- as good as anything else.  Pick OLTP, DSS or
Mixed workload.

No point in doing a custom install unless you have specific settings already in
mind.

      Rating: 5
Pls see this
      too..  March 24, 2003
Reviewer:  TH
       from BAH
I.

Pls see this:

create tablespace acc_indx_ts
datafile 'D:\Oracle\oradata\ora9i\school\accindxts_01.dbf' size 200M
autoextend on next 5M
default storage (
    initial 128K
    next    128K
    minextents 1
    maxextents unlimited
    pctincrease 2)
/

Can you pls explain the various values set? Sorry for troubling you.

II.

And also this:

create user student identified by quartz
default tablespace sch_ts_l
temporary tablespace sch_temp_ts
quota unlimited on acc_ts_l
quota unlimited on acc_ts_m
quota unlimited on acc_ts_h
quota unlimited on sch_ts_l
quota unlimited on sch_ts_m
quota unlimited on sch_ts_h
quota unlimited on acc_indx_ts
quota unlimited on sch_indx_ts
/

Why so many table spaces to a particular user? What does it mean? Can we tell
Oracle what exactly is the table space for indexes while creating the schema? If
so, where in the above is that?

Thanx Tom for your kind attention.

          Followup:

We have this really cool concept.  I'm not sure if all software vendors do it -
but we do.

We actually document this stuff!  And -- its all online too!  You can get it
right now!

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a85397/statem4c.htm#2063129

When you create a segment (table, index, etc) you can specify a tablespace to
create them in on that statement.  That SQL reference guide I pointed you to has
that syntax.

March 24, 2003 Vincent -- Thanks for the question regarding
      "Comparing Nulls?", version 8.1.5

      You Asked
Hi Tom,

    I have a question about using nulls in where clauses.  I've seen code using
nulls in decode statements like short circuits, for instance ...

select ...
  from ...
 where ...
   and decode(cust_id, null, 1, 0) = 1      for  cust_id is null or

select ...
  from ...
 where ...
   and decode(cust_id, null, 0, 1) = 1      for  cust_id is not null

does this provide any performance gain or this just another cute way of handling
nulls?

Thanks,

Vincent

      and we said...
performance gain?  nope.

performance loss?  perhaps.  must evaluate decode potentially many times.

harder to read?  harder to understand?  definitely.

good idea?  not a chance.

right way to code it?  "and cust_id is null", "and cust_id is not null"

it says what it means....

Review &
      Followup

      Rating: 3
do the function
      based index will gain in the performance  February 16, 2003

Reviewer:  Mike  from TX

          Followup:

nope -- not really.

Not unless the CUST_ID column was the ONLY column in the index.  If cust_id is
just part of a concatenated index, it would not help -- only slow down
inserts/updates/deletes really.

      Rating: 5
what
      for?  February 17, 2003
Reviewer:  Max
       from Moscow, Russia
What for do you need function based index?

it's very clear, that the only right way to code this kind of queries is using
"is null" or "is not null" clause.

Could you, pls, give as all the example, where you cannot solve this kind of
problem without using decode.

      Rating: 5
What about
      this?  February 19, 2003
Reviewer:  CJ
       from Albany, NY USA
What if you're comparing 2 fields which may or may not be null to each other?
i.e.
if a=b or (a is null and b is null)
vs.
if decode(a,null,1,0)=decode(b,null,1,0)

          Followup:

why?

length(decode(a,null,1,0)=decode(b,null,1,0)) > length(a is null and b is null)

so it takes longer to type.

the meaning of the decode is not clear at all.  I read it and say "ok, what are
they doing there?"

the meaning of a is null and b is null is obvious.

I would have the same list here as above -- good idea?  no, i don't think so.

      Rating: 5
Would nvl be
      ok?  February 19, 2003
Reviewer:  Reader
       from Downtown, NE
nvl(a,0)=nvl(b,0)?

          Followup:

and if we started with:

a = 0
b is null

what then?  false positive = wrong answer = there are no good shortcuts here.
One thing and one thing only does it right:

 (a=b) or (a is null and b is null)

      Rating: 4
Always use
      simplest comparison  February 19, 2003
Reviewer:
      Norman  from Jamison, PA
We can simply consider any "extra" operations as performance loss.
Ideally where clause must be a simply comparison operation.
Something like this:
    where  a=b
So the best way the code in this situation:
    - CUST_ID is null
    - CUST_ID is not null

      Rating:
      4
  February 19, 2003
Reviewer:
      A reader

      Rating: 4
Usage of DECODE
      functions   March 24, 2003
Reviewer:
      Krishan  from India
Tom,
   Only very recently I came to know abt ur site. Simply Superb site for Oracle
related queries. Frankly put, Initially I didnt even think of this site to be
like this. Doing a great job Tom. Hats Off to ur job. From where did u get this
much of knowledge in Oracle ?

          Followup:

It is all I do - Oracle, the database.  Been working @Oracle for 10 years.  Been
working with Oracle for going on 15.

March 24, 2003 wallacel -- Thanks for the question regarding
      "startup and shutdown at winnt", version 8.1.6

      You Asked
Tom:

I installed ora8.1.6 in winnt.
one strange thing happened when i try to startup or shutdown
use different way.

1. i start database by going to control panel -> services->
oracleService<MyDB> and press start.
oracle startup.
and then i press stop, Oracle shutdown,

then i go to start->program->oracle-oraHome->Application
Development->SQLPLUS.
connect system/manager as sysdba
it gives me error:
ORA-12751: TNS:packet writer failure

or
ORA-12560: TNS:protocol adapter error.

but if i use control panel->service startup the database,
then use sqlplus shutdown it, i can use sqlplus startup the
database.

why?

2.
when i use sqlplus shutdown database, then go to control panel->
service , i found that the status for that service is still
"started"

Regards,

      and we said...
1) because there are two stages to staring Oracle on NT.  You
need to start it -- and then startup the database.  Until it is
"started", you cannot startup a database.

If you want to start from the command line (or stop) I find it
most easy to:

net start oracleservice%ORACLE_SID%
net stop oracleservice%ORACLE_SID%

the database processes must be started as a service to get them
to start as the correct user with the correct privileges and
all.

2) we are still started and running -- in a manner "similar" to
"start nomount" on unix.  Process is running but nothing is
happening.
Review & Followup

      Rating: 5
How to start
      service without opening DB?  October 07, 2002

Reviewer:  Jan  from Singapore
Is there any possibility in Windows to start Oracle Service without opening
database? To get in the status like if you start Oracle Service by 'net start'
and after Shutdown the database thru SQL PLUS 'shutdown'.

Thanks,

          Followup:

you can modify the startup characteristics of the database on NT using oradmin

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/win.817/a73008/ch5.htm#1013701
....

      Rating: 3
How to what
      time database has started   March 24, 2003

Reviewer:  Deepak Gupta  from India
Dear Mr. Tom,

         Hi,

Is Oracle store anywhere (Tables or view etc) what date & time it got  started
(restart) ?
As I know it stores only in alter log file if so how to  extract that time ( or
it is  possible search manually)

Thanks in advance.

          Followup:

v$instance.

March 24, 2003 Ronnie -- Thanks for the question regarding
      "Selection of invalid time", version 7.3.4.5

      You Asked
We have to import data from several hundred V7 databases. The imports complete
successfully. But we have discovered records that contain invalid time entries
in the date field e.g.

Typ=12 Len=7: 120,100,4,3,100,123,123

When we try to select this date we get the error ora-01801 date format is to
long for internal buffer.

What we need to be able to do is select out these bad records and update them
with a valid time.

Any ideas on how to do this.

      and we said...
yuck, that means you have some program out there that is putting dates in in
BINARY (the source databases are messed up as well).  You need to FIND that
application and have them STOP using that approach -- they need to stop using
the internal format (which they don't know how to do properly) and use TO_DATE
on the way in and TO_CHAR on the way out...

Now, I don't know exactly how to FIX your date.  The date component is OK, that
is

century = 20   (120-100)
year    = 00   (100-100)
month   = april (4)
day     = 3

thats april 3rd 2000.  Now the TIME component is really messed up.

the hours are only allowed to range from 1..24, you have 100
the minutes from 1..60, you have 123
the seconds from 1..60, you have 123

Looks like whatever developer did this to you just put in the DATE part and
didn't set the time component properly.

If the time component is not relevent to you -- you can:

update your_table set date_column = trunc(date_column);

I tried this -- it worked in 817 anyway without error and fixed the date.  I put
the bad date in there via:

static void process()
{

typedef char DATE_TYPE[7];

EXEC SQL TYPE DATE_TYPE is DATE;

typedef struct mystruct
{
    DATE_TYPE   x;
    int         y;
}
mystruct_type;

mystruct_type   data = { {120,100,4,3,100,123,123}, 1 };

    EXEC SQL WHENEVER SQLERROR CONTINUE;
    EXEC SQL DROP TABLE TEST_DATE_TBL;

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();
    EXEC SQL CREATE TABLE TEST_DATE_TBL ( X DATE, Y INT );

    EXEC SQL INSERT INTO TEST_DATE_TBL (x,y) VALUES ( :data );

    EXEC SQL COMMIT WORK;
}

and after I trunc'ed it -- the date was fine.

If you have some method of telling me what 100, 123, 123 represents in TIME, I
can help you write a pro*c app that will work much like the above -- treating
the dates as 7 byte arrays of data, we can fix up the times and put them back...

Review &
      Followup

      Rating: 4
Helpful but how
      do I select out the records with invalid times  November 29,
      2001
Reviewer:  Ronnie Doggart  from Belfast Northern
      Ireland.
Tom,

Many thanks, but could you give me an example of how to select the invalid rows
out and update them to a fixed time of say 08:00:00

          Followup:

Here is one method (but you better track down the program that is causing the
issue in the first place or the bad dates will just keep coming back!)

ops$ora734@ORA734.WORLD> select x, y, dumpx, to_char(x,'dd-mon-yyyy hh24:mi:ss'
) dt,
  2         substr( dumpx, first_comma+1, second_comma-first_comma-1 ) hour,
  3         substr( dumpx, second_comma+1, third_comma-second_comma-1 ) minute,
  4         substr( dumpx, third_comma+1 ) second
  5    from (select x, y, dump(x) dumpx,
  6                 instr(dump(x),',',1,4) first_comma,
  7                 instr(dump(x),',',1,5) second_comma,
  8                 instr(dump(x),',',1,6) third_comma
  9            from test_date_tbl )
 10  /
ERROR:
ORA-01801: date format is too long for internal buffer

no rows selected

ops$ora734@ORA734.WORLD> select x, y, dumpx,
  2         substr( dumpx, first_comma+1, second_comma-first_comma-1 ) hour,
  3         substr( dumpx, second_comma+1, third_comma-second_comma-1 ) minute,
  4         substr( dumpx, third_comma+1 ) second
  5    from (select x, y, dump(x) dumpx,
  6                 instr(dump(x),',',1,4) first_comma,
  7                 instr(dump(x),',',1,5) second_comma,
  8                 instr(dump(x),',',1,6) third_comma
  9            from test_date_tbl )
 10  /

X                  Y DUMPX                HOUR MINU SECO
--------- ---------- -------------------- ---- ---- ----
03-APR-00          1 Typ=12 Len=7: 120,10 100  123  123
                     0,4,3,100,123,123

29-NOV-01          2 Typ=12 Len=7: 120,10 10   6    5
                     1,11,29,10,6,5

we have one good date, one bad date (y=1 is bad)

ops$ora734@ORA734.WORLD> update
  2         (select x, y, dump(x) dumpx,
  3                 instr(dump(x),',',1,4) first_comma,
  4                 instr(dump(x),',',1,5) second_comma,
  5                 instr(dump(x),',',1,6) third_comma
  6            from test_date_tbl )
  7     set x = trunc(x) + 8/24
  8   where NOT substr( dumpx, first_comma+1, second_comma-first_comma-1 )
between 1 and 24 OR
  9         NOT substr( dumpx, second_comma+1, third_comma-second_comma-1 )
between 1 and 60 OR
 10         NOT substr( dumpx, third_comma+1 ) between 1 and 60
 11  /

1 row updated.

ops$ora734@ORA734.WORLD> select x, y, dumpx, to_char(x,'dd-mon-yyyy hh24:mi:ss'
) dt,
  2         substr( dumpx, first_comma+1, second_comma-first_comma-1 ) hour,
  3         substr( dumpx, second_comma+1, third_comma-second_comma-1 ) minute,
  4         substr( dumpx, third_comma+1 ) second
  5    from (select x, y, dump(x) dumpx,
  6                 instr(dump(x),',',1,4) first_comma,
  7                 instr(dump(x),',',1,5) second_comma,
  8                 instr(dump(x),',',1,6) third_comma
  9            from test_date_tbl )
 10  /

X                  Y DUMPX                DT                   HOUR MINU SECO
--------- ---------- -------------------- -------------------- ---- ---- ----
03-APR-00          1 Typ=12 Len=7: 120,10 03-apr-2000 08:00:00 9    1    1
                     0,4,3,9,1,1

29-NOV-01          2 Typ=12 Len=7: 120,10 29-nov-2001 09:05:04 10   6    5
                     1,11,29,10,6,5

ops$ora734@ORA734.WORLD> rollback;

Rollback complete.

Now they are all "good"

      Rating: 3
same problem
        February 14, 2003
Reviewer:  Dilip
       from NY, NY, USA
Oracle 8.1.7.4
I have a table C.U with public readonly rights (schema C)
When I log in as user D, do select * from C.U ; works fine.
also the sql in this trail select dump(x).... works fine.

I have another instance oracle 9i.
using database link, I select the table U@MY_LINK; gives ORA-01801

I tried using sql with dump, gives another error.
Can you please help?

**************************
oracle 8.1.7.4
**************************
select uid, DT from C.U

      UID DT
--------- ---------
        1 29-OCT-99
        2 29-OCT-99
       10 29-OCT-99
       11 29-OCT-99
       12 29-OCT-99
       13 29-OCT-99
       33 29-OCT-99
       35 29-OCT-99

8 rows selected.

select dumpx, to_char(DT,'dd-mon-yyyy hh24:mi:ss' ) dt,
substr( dumpx, first_comma+1, second_comma-first_comma-1 ) hour,
substr( dumpx, second_comma+1, third_comma-second_comma-1 ) minute,
substr( dumpx, third_comma+1 ) second
from (select DT,  dump(DT) dumpx,
instr(dump(DT),',',1,4) first_comma,
instr(dump(DT),',',1,5) second_comma,
instr(dump(DT),',',1,6) third_comma
from u@MY_LINK )
/

DT       DUMPX
         DT                   HOUR
                              MINUTE
                              SECOND

--------------------
--------------------------------------------------------------------------------
--------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
29-OCT-99            Typ=12 Len=7: 119,199,10,29,1,1,1
                     29-oct-1999 00:00:00 1
                                          1
                                          1

29-OCT-99            Typ=12 Len=7: 119,199,10,29,1,1,1
                     29-oct-1999 00:00:00 1
                                          1
                                          1

29-OCT-99            Typ=12 Len=7: 119,199,10,29,1,1,1
                     29-oct-1999 00:00:00 1
                                          1
                                          1

29-OCT-99            Typ=12 Len=7: 119,199,10,29,1,1,1
                     29-oct-1999 00:00:00 1
                                          1
                                          1

29-OCT-99            Typ=12 Len=7: 119,199,10,29,1,1,1
                     29-oct-1999 00:00:00 1
                                          1
                                          1

29-OCT-99            Typ=12 Len=7: 119,199,10,29,1,1,1
                     29-oct-1999 00:00:00 1
                                          1
                                          1

29-OCT-99            Typ=12 Len=7: 119,199,10,29,1,1,1
                     29-oct-1999 00:00:00 1
                                          1
                                          1

29-OCT-99            Typ=12 Len=7: 119,199,10,29,1,1,1
                     29-oct-1999 00:00:00 1
                                          1
                                          1

8 rows selected.

**************************
oracle 9i
**************************
If I do
select UID, DT from U@MY_LINK ;

gives ERROR:
ORA-03120: two-task conversion routine: integer overflow

If I do
select * from U@MY_LINK ;
gives
ERROR:
ORA-01801: date format is too long for internal buffer

If I run the sql dump(x)...
ERROR:
ORA-03120: two-task conversion routine: integer overflow

even this sql gives 03120
SQL> select dump(dt) dumpx from u@MY_LINK ;
ERROR:
ORA-03120: two-task conversion routine: integer overflow

          Followup:

uumm, does ANYTHING about that dblink work?  looks like a definite
setup/configuration issue here.

      Rating: 3
ORA-03120:
      two-task conversion routine: integer overflow  March 24,
      2003
Reviewer:  reader
Tom,

i encountered the following error :

ORA-03120: two-task conversion routine: integer overflow

when accessing a view through dabatase link. Its a simple select statement using
a dblink on the view. The following lines are from the error documentation.

Error: ORA 3120
Text: two-task conversion routine: integer overflow
-------------------------------------------------------------------
Cause: An integer value in an internal Oracle structure overflowed
when being sent or received over a heterogeneous connection.
This can happen when an invalid buffer length or too great
a row count is specified.
It usually indicates a bug in the user application.
Action: Check parameters to Oracle calls.
If the problem recurs, reduce all integer parameters,
column values <not> included, to less than 32767.

my question(s) for you are :

1.'heterogeneous connection' - yes i have the dblink to oracle 8i database and i
am connecting from oracle 9i. when i use the same select using a java program i
am not encountering the error but when i use sqlplus i do get this error. - why
is it?.

2.'This can happen when an invalid buffer length or too great a row count is
specified' - can you explain this lines to me - in my case the size of the
column which i am selecting is varchar2(25) only!

3.'If the problem recurs, reduce all integer parameters,
column values <not> included, to less than 32767.' - reduce the column size or
the value for that coulmn? and why is it less than 32767 ?

Thank you ..

          Followup:

contact support for this one.  they'll most likely ask you to set some events to
diagnose this.

      Rating: 2
please provide
      me the interpretation of the error descirption  March 24,
      2003
Reviewer:  reader
tom,

but for the solution can you please provide me the interpretation of the error
descirption.  i understand that, this is something to do internal to oracle but
wanted to understand the error description.

i did not understand 'buffer length' (of what?) and the lines of 3rd question.

thanks

          Followup:

nothing to explain from my end.  the fact that it happens in environment 1 and
not in 2 indicates "product issue or configuration issue".

It is saying the internal bind buffers are not set up right -- someones messed
up something somewhere.

contact support.  my interpretation of the error isn't going to help us here.

March 24, 2003 wallacel -- Thanks for the question regarding
      "striping", version 8.1.6

      You Asked
Tom:

When i read document about tuning i/o. I don't understand why
I should "put table extents slightly smaller than the datafile"
why not exectly? what kind of overhead are these document
talking?
below is the document.

Tuning I/O 20-23
To stripe disks manually, you need to relate an object’s storage

requirements to its I/O requirements.
1. Begin by evaluating an object’s disk storage requirements by
checking:
        The size of the object.
        The size of the disk.
For example, if an object requires 5GB in Oracle storage space,
then you need one 5GB disk or two 4GB disks to accommodate it.
On the other hand, if the system is configured with 1GB or 2GB
disks, then the object may require 5 or 3 disks, respectively.
2. Compare to this the application’s I/O requirements, as
described in "Analyzing I/O Requirements" on page 20-3. You must

take the larger of the storage requirement and the I/O
requirement.For example, if the storage requirement is 5 disks
(1GB each), and the I/O requirement is 2 disks, then your
application requires the higher value: 5 disks.
3. Create a tablespace with the CREATE TABLESPACE statement.
Specify the datafiles in the DATAFILE clause. Each of the files
should be on a different disk. For example:
CREATE TABLESPACE stripedtabspace
DATAFILE ’file_on_disk_1’ SIZE 1GB,
’file_on_disk_2’ SIZE 1GB,&#3338;Solving I/O Problems
’file_on_disk_3’ SIZE 1GB,
’file_on_disk_4’ SIZE 1GB,
’file_on_disk_5’ SIZE 1GB;
4. Then, create the table with the CREATE TABLE statement.
Specify the newly created tablespace in the TABLESPACE clause.
Also specify the size of the table extents in the STORAGE
clause. Store each extent in a separate datafile. The table
extents should be slightly smaller than the datafiles in the
************************************************************
tablespace to allow for overhead. For example, when
********************************
preparing for datafiles of 1GB (1024MB), you can set the table
extents to be 1023MB. For example:
CREATE TABLE stripedtab (
col_1 NUMBER(2),
col_2 VARCHAR2(10) )
TABLESPACE stripedtabspace
STORAGE ( INITIAL 1023MB NEXT 1023MB
MINEXTENTS 5 PCTINCREASE 0 );
(Alternatively, you can stripe a table by entering an ALTER
TABLE ALLOCATE EXTENT statement with DATAFILE 'datafile' SIZE
'size'.) These steps result in the creation of table STRIPEDTAB.
STRIPEDTAB has 5 initial extents, each of size 1023MB. Each
extent takes up one of the datafiles named in the
DATAFILE clause of the CREATE TABLESPACE statement. Each of
these files is on a separate disk. The 5 extents are all
allocated immediately, because MINEXTENTS is 5.

      and we said...
well, they went a tad overboard in their example.  1meg is
no slightly smaller to many people.

for a locally managed tablespace, you must allow for 64k/file of
overhead for the space management bitmap.  All other tablespaces
take <blocksize>k bytes from the each file added.  It would be
better to say "add 1 block or 64k depending on the type of
tablespace you have for overhead".

rather then making the extent smaller -- I would probably prefer
to make the file a tad bit larger....

Note that if you did not make them a tad smaller in the above
example (which is NOT locally managed) you would get an error
like this:

system@TKYTE816> create tablespace stripedtabspace
  2  datafile 'c:\temp\testing1.dbf' size 1m
  3  /

Tablespace created.

system@TKYTE816> alter tablespace stripedtabspace
  2  add datafile 'c:\temp\testing2.dbf' size 1m;

Tablespace altered.

system@TKYTE816> alter tablespace stripedtabspace
  2  add datafile 'c:\temp\testing3.dbf' size 1m;

Tablespace altered.

system@TKYTE816> alter tablespace stripedtabspace
  2  add datafile 'c:\temp\testing4.dbf' size 1m;

Tablespace altered.

system@TKYTE816> alter tablespace stripedtabspace
  2  add datafile 'c:\temp\testing5.dbf' size 1m;

Tablespace altered.

system@TKYTE816>
system@TKYTE816> create table stripedtab (
  2  col_1 NUMBER(2),
  3  col_2 VARCHAR2(10) )
  4  TABLESPACE stripedtabspace
  5  STORAGE ( INITIAL 1024k NEXT 1024k
  6            MINEXTENTS 5 PCTINCREASE 0 );
create table stripedtab (
*
ERROR at line 1:
ORA-01658: unable to create INITIAL extent for segment in
tablespace STRIPEDTABSPACE

  1  create table stripedtab (
  2  col_1 NUMBER(2),
  3  col_2 VARCHAR2(10) )
  4  TABLESPACE stripedtabspace
  5  STORAGE ( INITIAL 1016k NEXT 1016k
  6*           MINEXTENTS 5 PCTINCREASE 0 )
system@TKYTE816> /

Table created.

That works -- when we consider the 8k overhead for each file.
In a locally managed tablespace, it'll be a little different:

system@TKYTE816> create tablespace stripedtabspace
  2  datafile 'c:\temp\testing1.dbf' size 1m
  3  extent management local
  4  uniform size 1024k
  5  /
create tablespace stripedtabspace
*
ERROR at line 1:
ORA-03214: File Size specified is smaller than minimum required

that shows the overhead is "builtin".  It knows that with an
extent size of 1024k -- it cannot allocate any extents in that
file and fails the command.  We'll add 64k to each file and
it'll go for us (this is my preferred method -- make the FILE a
tad bigger -- not the extent smaller):

system@TKYTE816> create tablespace stripedtabspace
  2  datafile 'c:\temp\testing1.dbf' size 1088k
  3  extent management local
  4  uniform size 1024k
  5  /

Tablespace created.

system@TKYTE816> alter tablespace stripedtabspace
  2  add datafile 'c:\temp\testing2.dbf' size 1088k;

Tablespace altered.

system@TKYTE816> alter tablespace stripedtabspace
  2  add datafile 'c:\temp\testing3.dbf' size 1088k;

Tablespace altered.

system@TKYTE816> alter tablespace stripedtabspace
  2  add datafile 'c:\temp\testing4.dbf' size 1088k;

Tablespace altered.

system@TKYTE816> alter tablespace stripedtabspace
  2  add datafile 'c:\temp\testing5.dbf' size 1088k;

Tablespace altered.

system@TKYTE816>
system@TKYTE816> create table stripedtab (
  2  col_1 NUMBER(2),
  3  col_2 VARCHAR2(10) )
  4  TABLESPACE stripedtabspace
  5  STORAGE ( MINEXTENTS 5 PCTINCREASE 0 );

Table created.

Review &
      Followup

      Rating:
      5
  March 22, 2003
Reviewer:
      A reader
BEGIN DBMS_STATS.GATHER_SCHEMA_STATS('AK',10); END;

*
ERROR at line 1:
ORA-01658: unable to create INITIAL extent for segment in tablespace SYSTEM
ORA-06512: at "SYS.DBMS_STATS", line 4652
ORA-06512: at "SYS.DBMS_STATS", line 4720
ORA-06512: at "SYS.DBMS_STATS", line 4704
ORA-06512: at line 1

It failed as schema analyzing.
What shall I do?
TIA

          Followup:

no clue as to version or anything -- hmm...

I would guess it is trying to create a temp table or some other object, your
default tablespace is set to SYSTEM.  You have no ability to create anything in
system so it fails.

Try altering the account to have a default tablespace where it can actually do
something.

without a testcase, probably won't be able to help you.  you could:

alter session set sql_trace=true;
exec dbms_stats....

and then edit the trc file and look for the statement that failed -- that would
be very telling (not the tkprof report, the trc file)

      Rating:
      5
  March 22, 2003
Reviewer:
      A reader
follow up: The pct_increase for the SYSTEM is 50%. Is the setup problem? and
found all the 5GB SYSTEM full for the Oracle Apps (11.5.7 and 8173 db). Why does
Oracle set it this way 50%?

          Followup:

alter your user to not have system as its default and or temporary tablespace.

then ignore system exists, it is ours.

      Rating:
      4
  March 23, 2003
Reviewer:
      A reader
I used the APPS account to do the schema ANALYZE, which has the default
tablespace APPLSYSD. It's 8174 database.

apps@prod1m> exec DBMS_STATS.GATHER_SCHEMA_STATS('ABM',10);
BEGIN DBMS_STATS.GATHER_SCHEMA_STATS('ABM',10); END;

*
ERROR at line 1:
ORA-01658: unable to create INITIAL extent for segment in tablespace SYSTEM
ORA-06512: at "SYS.DBMS_STATS", line 4652
ORA-06512: at "SYS.DBMS_STATS", line 4720
ORA-06512: at "SYS.DBMS_STATS", line 4704
ORA-06512: at line 1

Trace file (after tkprof with sys=yes):

BEGIN DBMS_STATS.GATHER_SCHEMA_STATS('ABM',10); END;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.03       0.03          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.03       0.03          0          0          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 173
********************************************************************************

SELECT /*+ ordered full(t) full(o) use_hash(o) full(u) use_hash(u)  */'TABLE'
  OBJ_TYPE,O.NAME OBJ_NAME,U.NAME USER_NAME,O.OBJ# OBJ_NUM
FROM
 SYS.TAB$ T,SYS.OBJ$ O,SYS.USER$ U  WHERE BITAND(T.PROPERTY,1) != 1  AND
  BITAND(T.PROPERTY,512) != 512  AND BITAND(T.PROPERTY,4194304) != 4194304
  AND BITAND(T.PROPERTY,8388608) != 8388608  AND BITAND(T.PROPERTY,8192) !=
  8192  AND T.OBJ# = O.OBJ#  AND O.OWNER# = U.USER#  AND O.LINKNAME IS NULL
  UNION ALL SELECT /*+ ordered use_nl(c) use_nl(o) use_nl(u)  */'INDEX'
  OBJ_TYPE,O.NAME OBJ_NAME,U.NAME USER_NAME,O.OBJ# OBJ_NUM   FROM SYS.IND$ I,
  SYS.CLU$ C,SYS.OBJ$ O,SYS.USER$ U  WHERE I.BO# = C.OBJ#  AND I.OBJ# =
  O.OBJ#  AND O.OWNER# = U.USER#  AND O.LINKNAME IS NULL ORDER BY 3,1 DESC,2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.02          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      2.90      19.10      10282      10342         52           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      2.92      19.12      10282      10342         52           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 1)
********************************************************************************

select file#
from
 file$ where ts#=:1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        8      0.00       0.00          0         15          0           7
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       10      0.00       0.00          0         15          0           7

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 2)

Rows     Row Source Operation
-------  ---------------------------------------------------
      7  TABLE ACCESS BY INDEX ROWID FILE$
      8   INDEX RANGE SCAN (object id 39)

********************************************************************************

select  file#,block#,length
from
 fet$       where length>=:1 and ts#=:2 and file#=:3

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        7      0.01       0.01          0          0          0           0
Execute      7      0.00       0.00          0          0          0           0
Fetch        7      0.00       0.01          1         14          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       21      0.01       0.02          1         14          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 2)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS CLUSTER FET$
      2   INDEX UNIQUE SCAN (object id 7)

********************************************************************************

select file#, block#
from
 fet$ where ts#=:1 and file#=:2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        7      0.00       0.00          0          0          0           0
Execute      7      0.01       0.01          0          0          0           0
Fetch        7      0.00       0.00          0         14          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       21      0.01       0.01          0         14          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 2)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS CLUSTER FET$
      2   INDEX UNIQUE SCAN (object id 7)

********************************************************************************

BEGIN DBMS_OUTPUT.GET_LINES(:LINES, :NUMLINES); END;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          0          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 173
********************************************************************************

alter session set sql_trace=false

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          0          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 173

Questions:
Why Oracle setup SYSTEM as pct_increase = 50% for the Oracle APPS?
Why there any CREATE statement caputured in the trace, which result the failed
ORA-01658: unable to create INITIAL extent for segment in tablespace SYSTEM
Thanks

          Followup:

you know, I could have sworn I said:

alter session set sql_trace=true;
exec dbms_stats....

and then edit the trc file and look for the statement that failed -- that would
be very telling (not the tkprof report, the trc file)

Not that you should post it here - but rather look at the TRC file, find the
statement that FAILED and at most post that statement.

Ignore the storage characteristics of SYSTEM as I said.
We don't know that the create statement wasn't captured - do we?  Look through
the trace file for any possible create statements.  Look for 1658 and look
backwards in the trc file for the possible cause.

Also -- you have filed a TAR with support right?

      Rating:
      4
  March 24, 2003
Reviewer:
      A reader
Sorry for not showing the trace file, becuase I could not find any failure
messages in. I thought it was the space issue of SYSTEM, and wanted to ask a
couple of question shown above.
May I put some kind of trace levels order to capture the error related in the
trace file?
Here is the trace file, posted it because of the relative small size of the
file:

Dump file /d02/oracle/xxxx1db/8.1.7/admin/xxxx1/udump/xxxx1_ora_15933.trc
Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production
ORACLE_HOME = /d02/oracle/xxxx1db/8.1.7
System name:    SunOS
Node name:    yyyy
Release:    5.8
Version:    Generic_108528-16
Machine:    sun4u
Instance name: xxxx1
Redo thread mounted by this instance: 1
Oracle process number: 28
Unix process pid: 15933, image: oracle@yyyy (TNS V1-V3)

*** 2003-03-23 17:41:20.544
*** SESSION ID:(43.52641) 2003-03-23 17:41:20.523
APPNAME mod='01@ sqltracetrue.sql' mh=2121163651 act='' ah=4029777240
=====================
PARSING IN CURSOR #1 len=33 dep=0 uid=173 oct=42 lid=173 tim=1766712950
hv=3732290820 ad='838aef84'
alter session set sql_trace=true
END OF STMT
EXEC #1:c=0,e=2,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=4,tim=1766712950
=====================
PARSING IN CURSOR #1 len=45 dep=0 uid=173 oct=42 lid=173 tim=1766712963
hv=3937768788 ad='8bc4a96c'
alter session set max_dump_file_size=1000000
END OF STMT
PARSE #1:c=1,e=1,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=4,tim=1766712963
EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=1766712963
=====================
PARSING IN CURSOR #1 len=40 dep=0 uid=173 oct=42 lid=173 tim=1766712975
hv=3270856335 ad='812d8364'
alter session set timed_statistics=true
END OF STMT
PARSE #1:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=4,tim=1766712975
EXEC #1:c=1,e=1,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=1766712976
=====================
PARSING IN CURSOR #1 len=53 dep=0 uid=173 oct=47 lid=173 tim=1766713000
hv=3759542639 ad='8dda30a0'
BEGIN DBMS_APPLICATION_INFO.SET_MODULE(:1,NULL); END;
END OF STMT
PARSE #1:c=1,e=1,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=0,tim=1766713000
APPNAME mod='SQL*Plus' mh=3669949024 act='' ah=4029777240
EXEC #1:c=1,e=12,p=0,cr=0,cu=0,mis=0,r=1,dep=0,og=4,tim=1766713012
*** 2003-03-23 17:42:57.577
=====================
PARSING IN CURSOR #1 len=54 dep=0 uid=173 oct=47 lid=173 tim=1766722653
hv=1035406852 ad='801949d0'
BEGIN DBMS_STATS.GATHER_SCHEMA_STATS('ABM',10); END;
END OF STMT
PARSE #1:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=4,tim=1766722653
=====================
PARSING IN CURSOR #2 len=726 dep=1 uid=0 oct=3 lid=0 tim=1766722655
hv=3928717011 ad='8d611d3c'
SELECT /*+ ordered full(t) full(o) use_hash(o) full(u) use_hash(u)  */'TABLE'
OBJ_TYPE,O.NAME OBJ_NAME,U.NAME USER_NAME,O.OBJ# OBJ_NUM   FROM SYS.TAB$
T,SYS.OBJ$ O,SYS.USER$ U  WHERE BITAND(T.PROPERTY,1) != 1  AND
BITAND(T.PROPERTY,512) != 512  AND BITAND(T.PROPERTY,4194304) != 4194304  AND
BITAND(T.PROPERTY,8388608) != 8388608  AND BITAND(T.PROPERTY,8192) != 8192  AND
T.OBJ# = O.OBJ#  AND O.OWNER# = U.USER#  AND O.LINKNAME IS NULL  UNION ALL
SELECT /*+ ordered use_nl(c) use_nl(o) use_nl(u)  */'INDEX' OBJ_TYPE,O.NAME
OBJ_NAME,U.NAME USER_NAME,O.OBJ# OBJ_NUM   FROM SYS.IND$ I,SYS.CLU$ C,SYS.OBJ$
O,SYS.USER$ U  WHERE I.BO# = C.OBJ#  AND I.OBJ# = O.OBJ#  AND O.OWNER# = U.USER#
 AND O.LINKNAME IS NULL ORDER BY 3,1 DESC,2
END OF STMT
PARSE #2:c=2,e=2,p=0,cr=0,cu=0,mis=1,r=0,dep=1,og=4,tim=1766722655
EXEC #2:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=1766722655
*** 2003-03-23 17:43:16.705
=====================
PARSING IN CURSOR #3 len=36 dep=2 uid=0 oct=3 lid=0 tim=1766724566 hv=1705880752
ad='8d746fe8'
select file# from file$ where ts#=:1
END OF STMT
PARSE #3:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=2,og=0,tim=1766724566
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724566
FETCH #3:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=2,og=4,tim=1766724566
FETCH #3:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=2,og=4,tim=1766724566
FETCH #3:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=2,og=4,tim=1766724566
FETCH #3:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=2,og=4,tim=1766724566
FETCH #3:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=2,og=4,tim=1766724566
FETCH #3:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=2,og=4,tim=1766724566
FETCH #3:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=2,og=4,tim=1766724566
FETCH #3:c=0,e=0,p=0,cr=1,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724566
STAT #3 id=1 cnt=7 pid=0 pos=0 obj=17 op='TABLE ACCESS BY INDEX ROWID FILE$ '
STAT #3 id=2 cnt=8 pid=1 pos=1 obj=39 op='INDEX RANGE SCAN '
=====================
PARSING IN CURSOR #3 len=84 dep=2 uid=0 oct=3 lid=0 tim=1766724567 hv=904892542
ad='873ef0cc'
select  file#,block#,length from fet$       where length>=:1 and ts#=:2 and
file#=:3
END OF STMT
PARSE #3:c=1,e=1,p=0,cr=0,cu=0,mis=1,r=0,dep=2,og=0,tim=1766724567
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724567
FETCH #3:c=0,e=1,p=1,cr=2,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724568
STAT #3 id=1 cnt=0 pid=0 pos=0 obj=12 op='TABLE ACCESS CLUSTER FET$ '
STAT #3 id=2 cnt=2 pid=1 pos=1 obj=7 op='INDEX UNIQUE SCAN '
=====================
PARSING IN CURSOR #3 len=56 dep=2 uid=0 oct=3 lid=0 tim=1766724568 hv=2714675196
ad='84a49634'
select file#, block# from fet$ where ts#=:1 and file#=:2
END OF STMT
PARSE #3:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=2,og=0,tim=1766724568
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724568
FETCH #3:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724568
STAT #3 id=1 cnt=0 pid=0 pos=0 obj=12 op='TABLE ACCESS CLUSTER FET$ '
STAT #3 id=2 cnt=2 pid=1 pos=1 obj=7 op='INDEX UNIQUE SCAN '
=====================
PARSING IN CURSOR #3 len=84 dep=2 uid=0 oct=3 lid=0 tim=1766724568 hv=904892542
ad='873ef0cc'
select  file#,block#,length from fet$       where length>=:1 and ts#=:2 and
file#=:3
END OF STMT
PARSE #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724568
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724568
FETCH #3:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724568
STAT #3 id=1 cnt=0 pid=0 pos=0 obj=12 op='TABLE ACCESS CLUSTER FET$ '
STAT #3 id=2 cnt=2 pid=1 pos=1 obj=7 op='INDEX UNIQUE SCAN '
=====================
PARSING IN CURSOR #3 len=56 dep=2 uid=0 oct=3 lid=0 tim=1766724568 hv=2714675196
ad='84a49634'
select file#, block# from fet$ where ts#=:1 and file#=:2
END OF STMT
PARSE #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724568
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724568
FETCH #3:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724568
STAT #3 id=1 cnt=0 pid=0 pos=0 obj=12 op='TABLE ACCESS CLUSTER FET$ '
STAT #3 id=2 cnt=2 pid=1 pos=1 obj=7 op='INDEX UNIQUE SCAN '
=====================
PARSING IN CURSOR #3 len=84 dep=2 uid=0 oct=3 lid=0 tim=1766724568 hv=904892542
ad='873ef0cc'
select  file#,block#,length from fet$       where length>=:1 and ts#=:2 and
file#=:3
END OF STMT
PARSE #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724568
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724568
FETCH #3:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724568
STAT #3 id=1 cnt=0 pid=0 pos=0 obj=12 op='TABLE ACCESS CLUSTER FET$ '
STAT #3 id=2 cnt=2 pid=1 pos=1 obj=7 op='INDEX UNIQUE SCAN '
=====================
PARSING IN CURSOR #3 len=56 dep=2 uid=0 oct=3 lid=0 tim=1766724568 hv=2714675196
ad='84a49634'
select file#, block# from fet$ where ts#=:1 and file#=:2
END OF STMT
PARSE #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724568
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724568
FETCH #3:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724568
STAT #3 id=1 cnt=0 pid=0 pos=0 obj=12 op='TABLE ACCESS CLUSTER FET$ '
STAT #3 id=2 cnt=2 pid=1 pos=1 obj=7 op='INDEX UNIQUE SCAN '
=====================
PARSING IN CURSOR #3 len=84 dep=2 uid=0 oct=3 lid=0 tim=1766724568 hv=904892542
ad='873ef0cc'
select  file#,block#,length from fet$       where length>=:1 and ts#=:2 and
file#=:3
END OF STMT
PARSE #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724568
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724568
FETCH #3:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724568
STAT #3 id=1 cnt=0 pid=0 pos=0 obj=12 op='TABLE ACCESS CLUSTER FET$ '
STAT #3 id=2 cnt=2 pid=1 pos=1 obj=7 op='INDEX UNIQUE SCAN '
=====================
PARSING IN CURSOR #3 len=56 dep=2 uid=0 oct=3 lid=0 tim=1766724568 hv=2714675196
ad='84a49634'
select file#, block# from fet$ where ts#=:1 and file#=:2
END OF STMT
PARSE #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724568
EXEC #3:c=1,e=1,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724569
FETCH #3:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724569
STAT #3 id=1 cnt=0 pid=0 pos=0 obj=12 op='TABLE ACCESS CLUSTER FET$ '
STAT #3 id=2 cnt=2 pid=1 pos=1 obj=7 op='INDEX UNIQUE SCAN '
=====================
PARSING IN CURSOR #3 len=84 dep=2 uid=0 oct=3 lid=0 tim=1766724569 hv=904892542
ad='873ef0cc'
select  file#,block#,length from fet$       where length>=:1 and ts#=:2 and
file#=:3
END OF STMT
PARSE #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724569
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724569
FETCH #3:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724569
STAT #3 id=1 cnt=0 pid=0 pos=0 obj=12 op='TABLE ACCESS CLUSTER FET$ '
STAT #3 id=2 cnt=2 pid=1 pos=1 obj=7 op='INDEX UNIQUE SCAN '
=====================
PARSING IN CURSOR #3 len=56 dep=2 uid=0 oct=3 lid=0 tim=1766724569 hv=2714675196
ad='84a49634'
select file#, block# from fet$ where ts#=:1 and file#=:2
END OF STMT
PARSE #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724569
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724569
FETCH #3:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724569
STAT #3 id=1 cnt=0 pid=0 pos=0 obj=12 op='TABLE ACCESS CLUSTER FET$ '
STAT #3 id=2 cnt=2 pid=1 pos=1 obj=7 op='INDEX UNIQUE SCAN '
=====================
PARSING IN CURSOR #3 len=84 dep=2 uid=0 oct=3 lid=0 tim=1766724569 hv=904892542
ad='873ef0cc'
select  file#,block#,length from fet$       where length>=:1 and ts#=:2 and
file#=:3
END OF STMT
PARSE #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724569
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724569
FETCH #3:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724569
STAT #3 id=1 cnt=0 pid=0 pos=0 obj=12 op='TABLE ACCESS CLUSTER FET$ '
STAT #3 id=2 cnt=2 pid=1 pos=1 obj=7 op='INDEX UNIQUE SCAN '
=====================
PARSING IN CURSOR #3 len=56 dep=2 uid=0 oct=3 lid=0 tim=1766724569 hv=2714675196
ad='84a49634'
select file#, block# from fet$ where ts#=:1 and file#=:2
END OF STMT
PARSE #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724569
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724569
FETCH #3:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724569
STAT #3 id=1 cnt=0 pid=0 pos=0 obj=12 op='TABLE ACCESS CLUSTER FET$ '
STAT #3 id=2 cnt=2 pid=1 pos=1 obj=7 op='INDEX UNIQUE SCAN '
=====================
PARSING IN CURSOR #3 len=84 dep=2 uid=0 oct=3 lid=0 tim=1766724569 hv=904892542
ad='873ef0cc'
select  file#,block#,length from fet$       where length>=:1 and ts#=:2 and
file#=:3
END OF STMT
PARSE #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724569
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724569
FETCH #3:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724569
STAT #3 id=1 cnt=0 pid=0 pos=0 obj=12 op='TABLE ACCESS CLUSTER FET$ '
STAT #3 id=2 cnt=2 pid=1 pos=1 obj=7 op='INDEX UNIQUE SCAN '
=====================
PARSING IN CURSOR #3 len=56 dep=2 uid=0 oct=3 lid=0 tim=1766724569 hv=2714675196
ad='84a49634'
select file#, block# from fet$ where ts#=:1 and file#=:2
END OF STMT
PARSE #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724569
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724569
FETCH #3:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=0,dep=2,og=4,tim=1766724569
STAT #3 id=1 cnt=0 pid=0 pos=0 obj=12 op='TABLE ACCESS CLUSTER FET$ '
STAT #3 id=2 cnt=2 pid=1 pos=1 obj=7 op='INDEX UNIQUE SCAN '
FETCH #2:c=292,e=1913,p=10283,cr=10385,cu=52,mis=0,r=0,dep=1,og=4,tim=1766724569
EXEC #1:c=295,e=1916,p=10283,cr=10385,cu=52,mis=0,r=0,dep=0,og=4,tim=1766724569
ERROR #1:err=1658 tim=1766724569
=====================
PARSING IN CURSOR #1 len=52 dep=0 uid=173 oct=47 lid=173 tim=1766724621
hv=1697159799 ad='8639b790'
BEGIN DBMS_OUTPUT.GET_LINES(:LINES, :NUMLINES); END;
END OF STMT
PARSE #1:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=0,tim=1766724621
EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=0,og=4,tim=1766724621
*** 2003-03-23 17:43:35.374
=====================
PARSING IN CURSOR #1 len=53 dep=0 uid=173 oct=47 lid=173 tim=1766726433
hv=3759542639 ad='8dda30a0'
BEGIN DBMS_APPLICATION_INFO.SET_MODULE(:1,NULL); END;
END OF STMT
PARSE #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=1766726433
APPNAME mod='01@ sqltracefalse.sql' mh=1221103161 act='' ah=4029777240
EXEC #1:c=0,e=14,p=0,cr=0,cu=0,mis=0,r=1,dep=0,og=4,tim=1766726447
=====================
PARSING IN CURSOR #1 len=34 dep=0 uid=173 oct=42 lid=173 tim=1766726470
hv=1582735206 ad='82464318'
alter session set sql_trace=false
END OF STMT
PARSE #1:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=4,tim=1766726470
EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=1766726470

Thanks for your time

          Followup:

well, it looks big from my screen ;)

anyway -- your users TEMP tablespace is set to system, the analyze begins to
run, runs out of room and bombs trying to create a temp segment in system.

Alter user X temporary tablespace <SOMETHING ELSE>;

It is the initial allocation of a temporary segment that is failing here.

      Rating:
      3
  March 24, 2003
Reviewer:
      A reader
Qoute:
anyway -- your users TEMP tablespace is set to system, the analyze begins to
run, runs out of room and bombs trying to create a temp segment in system.

Alter user X temporary tablespace <SOMETHING ELSE>;

It is the initial allocation of a temporary segment that is failing here.

********

How could you be sure?

apps@xxx> select DEFAULT_TABLESPACE,TEMPORARY_TABLESPACE from user_users;

Mon Mar 24

                                                           Database Users

DEFAULT         TEMPORARY
--------------- ------------
APPLSYSD        TEMP

1 row selected.

          Followup:

because of the lack of CREATES in there.  tell you what -- if this is still
happening, with the user set to TEMP, followup the rest of this with your TAR
you must have filed by now with support.  Nothing else I can do.

March 24, 2003 robert -- Thanks for the question regarding
      "computed pct subtotals", version 8.1.7

      You Asked
Hi Tom,

Thanks in advance for this.
I have a query that looks something like this:

column region heading 'Region' format a18;
column DUMMY NOPRINT;
column CC heading 'Comp|Code' format a6;
column D heading 'Active|Date' format a9;
column E heading 'Pending|EEs' format 999999;
column R heading 'Enrolled|EEs' format 999999;
column PCT heading '   Pct%|Enrolled' format 999.99;
column T heading 'Total|EEs' format 999999;

ttitle 'Regionalized Mktg. Stats'
break on region skip 1 on report
compute sum label "Subtot:" of T on region
compute sum label "Subtot:" of R on region
compute sum label "Subtot:" of E on region
compute sum label "Total:" of T on report;
compute sum label "Total:" of R on report;
compute sum label "Total:" of E on report;
set pages 400
set lines 80
set underline '_'

SELECT
  DECODE(TB.regcode,
     72, 'Arcadia        72',
     30, 'Southeast      30',   --Atlanta
     60, 'Chicago        60',
     33, 'Cincinnati     33',
     34, 'Cleveland      34',
     120, 'Clifton NPC   120',
     35, 'Columbus       35',
     62, 'South Central  62',   --Dallas
     63, 'Denver         63',
     64, 'Detroit        64',
     12, 'Hartford       12',
     65, 'Houston        65',
     54, 'Indianapolis   54',
     66, 'Kansas City    66',
     70, 'Southern Calif 70',   --La Palma
     76, 'Pacific NW     76',   --Seattle
     69, 'St. Louis      69',
     57, 'Syracuse       57',
     37, 'Tampa          37',
     'UNKNOWN') region,
  TB.regcode DUMMY,
  TB.ccode CC,
  MAX(V.active_dt) D,
  COUNT(*) T,
  SUM( DECODE(TB.user_id,NULL,0,1) ) R,
  SUM( DECODE(TB.user_id,'-1',1,0) ) E,
  SUM( DECODE(TB.user_id,NULL,0,1) ) * 100.00 / COUNT(*) PCT
FROM
  tblssn_pin TB,
  v_voi_cc_active_union V
WHERE
  TB.regcode = V.regcode
  AND TB.ccode = V.ccode
GROUP BY
  TB.regcode, TB.ccode
HAVING
  SUM( DECODE(TB.user_id,NULL,0,1) ) > 0
ORDER BY
  1, 3
/

...with output looking something like this:

Mon Aug 27                                                             page    1
                         Regionalized Mktg. Stats

                   Comp   Active      Total Enrolled Pending     Pct%
Region             Code   Date          EEs      EEs     EEs Enrolled
__________________ ______ _________ _______ ________ _______ ________
Chesapeake     52  IBW    20-JUL-01       2        2       0   100.00
                   XXX    06-AUG-01     601        2       0      .33
******************                  ------- -------- -------
Subtot:                                 603        4       0

Chicago        60  FF     13-AUG-01       6        4       0    66.67
                   K29    27-JUL-01     102        2       0     1.96
                   UDV    14-AUG-01      27        1       0     3.70
******************                  ------- -------- -------
Subtot:                                 135        7       0

Cleveland      34  -KT    06-AUG-01       5        3       0    60.00
                   JSZ    08-AUG-01     402        1       0      .25
                   REC    10-AUG-01     364        2       0      .55
******************                  ------- -------- -------
Subtot:                                 771        6       0

Clifton NPC   120  -F     30-JUL-01      20        5       0    25.00
                   CE4    30-JUL-01    2030       25       1     1.23
                   EPS    30-JUL-01     361        5       0     1.39
******************                  ------- -------- -------
Subtot:                                2411       35       1

NY Metro       20  38M    02-AUG-01     190       20       0    10.53
                   LP0    01-AUG-01    1404        1       0      .07
******************                  ------- -------- -------
Subtot:                                1594       21       0

New England    10  9XH    03-AUG-01     170       20       0    11.76
                   BG4    03-AUG-01     598       48       0     8.03
                   BG5    03-AUG-01      50        8       0    16.00
                   DPC    03-AUG-01     136        4       0     2.94
                   FD8    03-AUG-01     369        7       0     1.90
                   GAW    03-AUG-01       2        1       0    50.00
                   HUK    03-AUG-01     749        6       0      .80
                   I7E    03-AUG-01      14       10       0    71.43
                   IB2    03-AUG-01       4        3       2    75.00
                   IUU    03-AUG-01      10        1       0    10.00
                   J6U    03-AUG-01       9        8       0    88.89
                   JVZ    03-AUG-01     172        4       0     2.33
                   LN1    03-AUG-01     190        5       0     2.63
                   PC4    03-AUG-01     164        4       0     2.44
                   PC5    03-AUG-01      76        5       0     6.58
                   PJT    03-AUG-01     785        2       0      .25
                   PRY    03-AUG-01      78        1       0     1.28
                   PTY    03-AUG-01     210        5       0     2.38
                   WZJ    03-AUG-01      83        8       0     9.64
                   ZMV    06-AUG-01     269        2       0      .74
******************                  ------- -------- -------
Subtot:                                4138      152       2

Pacific NW     76  96X    14-FEB-01      45        3       0     6.67
                   99A    03-JUL-01       3        3       0   100.00
                   99P    22-JAN-01      32       10       0    31.25
                   C6G    31-JUL-01      74       74       0   100.00
                   EAP    31-JUL-01      84       72       0    85.71
                   GEN    14-AUG-01     152        2       0     1.32
                   GPS    27-DEC-00    1478        1       0      .07
                   QCK    02-APR-01     148        7       0     4.73
******************                  ------- -------- -------
Subtot:                                2016      172       0

Philadelphia   55  E8L    30-JUL-01     165        2       0     1.21
                   GJQ    30-JUL-01     107        2       2     1.87
                   JJ2    30-JUL-01     233        2       0      .86
                   L87    30-JUL-01      53        2       0     3.77
                   NRD    30-JUL-01      41        1       0     2.44
                   QWJ    30-JUL-01      29        5       0    17.24
******************                  ------- -------- -------
Subtot:                                 628       14       2

Pittsburgh     56  AAL    01-AUG-01       9        2       0    22.22
******************                  ------- -------- -------
Subtot:                                   9        2       0

South Central  62  6DF    15-AUG-01     122        1       0      .82
                   DMO    10-AUG-01      29        3       0    10.34
******************                  ------- -------- -------
Subtot:                                 151        4       0

Southeast      30  8PH    30-JUL-01      42        5       5    11.90
                   912    30-JUL-01      45        4       0     8.89
                   BD4    30-JUL-01     198        5       0     2.53
                   DAH    30-JUL-01     140       61       0    43.57
                   F8J    30-JUL-01     519       45       0     8.67
                   FCP    08-AUG-01     220        2       0      .91
                   LJ3    30-JUL-01      54        1       1     1.85
                   M9J    30-JUL-01     233        4       0     1.72
                   NWK    30-JUL-01     150       11      11     7.33
                   TAW    30-JUL-01      26        1       1     3.85
******************                  ------- -------- -------
Subtot:                                1627      139      18

Southeast NPC 130  2J2    16-AUG-01     555        6       0     1.08
                   B6X    08-AUG-01      46        3       0     6.52
                   EQE    08-AUG-01    2443        1       0      .04
                   EQP    08-AUG-01     217       22       0    10.14
                   HRW    08-AUG-01      10        1       0    10.00
                   HRY    08-AUG-01    1915      144       0     7.52
                   HRZ    08-AUG-01    1656       99       0     5.98
                   LJR    31-JUL-01     224        1       0      .45
                   MNK    28-DEC-00     412      108       0    26.21
                   MNU    08-AUG-01    1194        2       0      .17
                   MTT    08-AUG-01    1614        6       0      .37
                   SCI    08-AUG-01    2756       16       0      .58
                   XYA    08-AUG-01     263       37       0    14.07
                   Z4T    08-AUG-01      13       11       0    84.62
******************                  ------- -------- -------
Subtot:                               13318      457       0

Southern Calif 70  -WK    15-AUG-01     156        4       0     2.56
                   3KK    24-AUG-01      13        4       0    30.77
                   LCV    24-AUG-01     212        1       0      .47
******************                  ------- -------- -------
Subtot:                                 381        9       0

St. Louis      69  -FH    30-JUL-01      18       12       0    66.67
******************                  ------- -------- -------
Subtot:                                  18       12       0

West NPC      170  6B-    30-JUL-01      70        8       0    11.43
                   883    30-JUL-01     490       62       0    12.65
                   A-A    30-JUL-01      16       13       0    81.25
                   AGI    30-JUL-01   11792       12       0      .10
                   M11    30-JUL-01    1243      137       0    11.02
                   MA3    30-JUL-01    2279       20       0      .88
                   MA4    30-JUL-01    1313        1       0      .08
                   TR1    30-JUL-01    9269      153       0     1.65
                   VS1    30-JUL-01   19786       18       0      .09
******************                  ------- -------- -------
Subtot:                               46258      424       0

                                    ------- -------- -------
Total:                                74058     1458      23

87 rows selected.

What I'm looking for is some way to get the subtotal percentages computed and
printed for each region break.  Some kind of break clause or compute option?
Can you help?  Thanks.

~Rob S.

      and we said...
You'll be able to wrap your existing query as an inline view and use the new (as
of 816) analytic functions to do this easily.  Consider this example:

ops$tkyte@ORA8I.WORLD> column pct format 999.99
ops$tkyte@ORA8I.WORLD> column sum_pct format 999.99
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> break on deptno skip 1
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select   a.*, sum(pct) over ( partition by deptno order
by ename range unbounded preceding ) sum_pct
  2    from ( select deptno, ename, sal,
  3      round( 100 * ratio_to_report(sal) over ( partition by deptno), 2 ) pct
  4             from emp ) a
  5   order by deptno, ename
  6  /

that bolded text is where YOUR existing query will go

    DEPTNO ENAME             SAL     PCT SUM_PCT
---------- ---------- ---------- ------- -------
        10 CLARK            2450   28.00   28.00
           KING             5000   57.14   85.14
           miller           1300   14.86  100.00

        20 ADAMS            1100   10.11   10.11
           FORD             3000   27.59   37.70
           JONES            2975   27.36   65.06
           SCOTT            3000   27.59   92.65
           SMITH             800    7.36  100.01

        30 ALLEN            1600   17.02   17.02
           BLAKE            2850   30.32   47.34
           JAMES             950   10.11   57.45
           MARTIN           1250   13.30   70.75
           TURNER           1500   15.96   86.71
           WARD             1250   13.30  100.01

14 rows selected.

It shows an alternate (more efficient) method to compute a percentage in a group
as well.

The data warehousing guide, sql reference have more info (and my book has a big
chapter on this topic as well)

Review &
      Followup

      Rating: 4
Compute all
      subtotals to get Grand Total  January 14, 2003

Reviewer:  TSSK  from USA
Hi Tom,

I 've a table with duplicate rows.
The table looks like :-
SQL> desc t1
 Name                           Null?       Type
 ---------------------------- -------- ------------------
 EMPNO                                     NUMBER(8)
 DNO                                       NUMBER(8)
 SERIAL_NO                                 NUMBER

values are :-
     EMPNO        DNO  SERIAL_NO
---------- ---------- ----------
       100          1          1
       100          2          2
       100          3          3
       100          4          4
       100          5          5
       100          1          6
       100          2          7
       100          3          8
       100          4          9
       100          5         10
       100          1         11
       100          2         12
       100          3         13
       100          4         14
       100          5         15
       101          3          1
       101          4          2
       101          3          3
       101          4          4
      1000         10          1
      1000         24          2
      1000         10          3
      1000         24          4

As you can see for each empno there are multiple deptno
which inturn is duplicated couple of times. Inorder
to get a count of duplicates for each empno,deptno combo I wrote a query like
this:

BREAK on empno
COMPUTE sum of count(*) "Total Duplicates" on empno
select a.empno,a.dno,count(*) "Total Duplicates" from t1 a
where a.rowid not in (select min(b.rowid) from t1 b
where a.empno=b.empno
and a.dno=b.dno)
group by empno,dno;

Output:-
======
   EMPNO        DNO Total Duplicates
---------- ---------- ---------------
       100          1               2
                    2               2
                    3               2
                    4               2
                    5               2
**********            ---------------
sum                                10
       101          3               1
                    4               1
**********            ---------------
sum                                 2
      1000         10               1
                   24               1
**********            ---------------
sum                                 2

9 rows selected.

What I need is to get the grand total of the "Total Duplicates" and in this case
its 14. I tried various options and didn't succeed. Can you help me pls?

Thanks,
Sai

          Followup:

ops$tkyte@ORA920> select decode(grouping(empno)+grouping(dno),
  2                0,null,
  3                1,'Sum for emp',
  4                2,'Grand Total' ) label,
  5         empno,
  6         dno,
  7         count(*)-count(distinct empno || ',' || dno) cnt
  8    from t
  9   group by rollup( empno, dno )
 10  having count(*) > 1
 11   order by empno, dno
 12
ops$tkyte@ORA920> /

LABEL            EMPNO        DNO        CNT
----------- ---------- ---------- ----------
                   100          1          2
                   100          2          2
                   100          3          2
                   100          4          2
                   100          5          2
Sum for emp        100                    10
                   101          3          1
                   101          4          1
Sum for emp        101                     2
                  1000         10          1
                  1000         24          1
Sum for emp       1000                     2
Grand Total                               14

13 rows selected.

it one approach....

      Rating: 5
Compute all
      subtotals to get Grand Total Displays values with
      "0"  January 16, 2003
Reviewer:  TSSK
       from USA
Tom,

Thanks for helping me. I executed the query:
select decode(grouping(empno)+grouping(dno),
              1,'Sum for emp',
              2,'Grand Total' ) label,
        empno,
        dno,
        count(*)-count(distinct empno || ',' || dno) cnt
     from t
    group by rollup( empno, dno )
   having count(*) > 1
   order by empno, dno;

Here's the output:-
=================
LABEL            EMPNO        DNO        CNT
----------- ---------- ---------- ----------
                   100          1          2
                   100          2          2
                   100          3          2
                   100          4          2
                   100          5          2
Sum for emp        100                    10
                   101          3          1
                   101          4          1
Sum for emp        101                     2
                  1000         10          1
                  1000         24          1
Sum for emp       1000                     2
Sum for emp       1333                     0
Grand Total                               14

works Great.But I 've a problem, the query shows rows
 that have no duplicates  ie. the column "cnt" displays it as "0"(In this case
EMPNO 1333), Is there a way to avoid displaying empno's that dont 've any
duplicates ?

Also, I didn't get an Email Notification once you replied to my previous thread
of the same Question. I just happened to check the status and saw that You had
replied to it. Just to let you know.

Thanks Again,
Sai

          Followup:

Add to the having clause.  Apparently, you don't want rows "having"
count(*)-count(distinct empno||','||dno) = 0

      Rating: 5
Way of finding
      subtotal on 8.1.6  March 18, 2003
Reviewer:
      Sai  from USA
Tom,

I ran the following query mentioned in this thread to get subtotal and grand
total:-
select decode(grouping(empno)+grouping(dno),
              1,'Sum for emp',
              2,'Grand Total' ) label,
        empno,
        dno,
        count(*)-count(distinct empno || ',' || dno) cnt
     from t
    group by rollup( empno, dno )
   having count(*) > 1
   order by empno, dno;

Works Great on 8.1.7, but if I run the same query on 8.1.6
it gives the following error :-

ERROR at line 6:
ORA-30480: DISTINCT option not allowed with GROUP BY CUBE or ROLLUP

Is there a way to write a simplified query like the one above for 8.1.6 to
achieve the same results ?

Asusual any help from you is greatly appreciated.

Thanks,
Sai

          Followup:

hiding it in an inline view appears to work:

ops$tkyte@ORA815> select decode(grouping(empno)+grouping(dno),
  2                1,'Sum for emp',
  3                2,'Grand Total' ) label,
  4             empno, dno, sum(cnt) cnt
  5    from
  6  ( select
  7          empno,
  8          dno,
  9                  count(*) cnt_star,
 10          count(*)-count(distinct empno || ',' || dno) cnt
 11       from t
 12           group by empno, dno )
 13           where cnt_star > 1
 14      group by rollup( empno, dno )
 15     order by empno, dno
 16  /

LABEL            EMPNO        DNO        CNT
----------- ---------- ---------- ----------
                   100          1          2
                   100          2          2
                   100          3          2
                   100          4          2
                   100          5          2
Sum for emp        100                    10
                   101          3          1
                   101          4          1
Sum for emp        101                     2
                  1000         10          1
                  1000         24          1
Sum for emp       1000                     2
Grand Total                               14

13 rows selected.

      Rating:
      4
  March 18, 2003
Reviewer:  Kishore
       from India
Hi tom,

select   a.*, sum(pct) over ( partition by deptno order
by ename range unbounded preceding ) sum_pct
  2    from ( select deptno, ename, sal,
  3      round( 100 * ratio_to_report(sal) over ( partition by deptno), 2 ) pct
  4             from emp ) a
  5   order by deptno, ename
  6  /

I tried the above query but it is showing error at partition and asking for FROM
keyword. We are using 8.1.5. Is the Partition clause not available in this ver?
If not pl. tell how we can the same o/p?

Thanx in advance

Kishore

          Followup:

it was a new feature way back with Oracle8i R2 (version 816).  it did not exist
in 815.

      Rating: 5
Highlighting
      the subtotals   March 24, 2003
Reviewer:
      Sai  from USA
Tom,

Thanks for helping me out on how to get subtotals
in Oracle 8.1.6 using the following query:-

ops$tkyte@ORA815> select decode(grouping(empno)+grouping(dno),
  2                1,'Sum for emp',
  3                2,'Grand Total' ) label,
  4             empno, dno, sum(cnt) cnt
  5    from
  6  ( select
  7          empno,
  8          dno,
  9                  count(*) cnt_star,
 10          count(*)-count(distinct empno || ',' || dno) cnt
 11       from t
 12           group by empno, dno )
 13           where cnt_star > 1
 14      group by rollup( empno, dno )
 15     order by empno, dno
 16  /

LABEL            EMPNO        DNO        CNT
----------- ---------- ---------- ----------
                   100          1          2
                   100          2          2
                   100          3          2
                   100          4          2
                   100          5          2
Sum for emp        100                    10
                   101          3          1
                   101          4          1
Sum for emp        101                     2
                  1000         10          1
                  1000         24          1
Sum for emp       1000                     2
Grand Total                               14

13 rows selected.

Is there any way to make the output look like :-
LABEL            EMPNO        DNO        CNT
----------- ---------- ---------- ----------
                   100          1          2
                   100          2          2
                   100          3          2
                   100          4          2
                   100          5          2
                                      ---------
Sum for emp        100                    10
                                      ---------
                   101          3          1
                   101          4          1
                                      ---------
Sum for emp        101                     2
                                      ---------
                  1000         10          1
                  1000         24          1
                                      ---------
Sum for emp       1000                     2
                                      ---------
Grand Total                               14

13 rows selected.

ie. underlining the subtotals(Sum for emp),I ran the query with about 2000
records and it keeps scrolling by and it was difficult to distinguish between
the regular count and the subtotals. Thanks for all the help.

-Sai

          Followup:

try a break on label skip 1

or, maybe:

select decode(grouping(empno)+grouping(dno),
              1,'Sum for emp',
              2,'Grand Total' ) label,
           empno, dno, to_char( sum(cnt), '999999' ) ||
       decode(grouping(empno)+grouping(dno),
              1,chr(10)||'-------',
              2,chr(10)||'-------' ) cnt
  from
( select
        empno,
        dno,
                count(*) cnt_star,
        count(*)-count(distinct empno || ',' || dno) cnt
     from t
         group by empno, dno )
         where cnt_star > 0
    group by rollup( empno, dno )
   order by empno, dno
/

      Rating:
      4
Rollup  December 27, 2003
Reviewer:
      A.R.Chary  from S'pore
Hi Tom,
Thanks for your excellent solutions.
I need your help to slove my problem.
I have a summary table which gives information for business and sales level
summaries.
In above I am facing problems in sales summaries.
In sales hierarchy I have levels from 0 to 4 (4 is last). While assigining
hierarchy to customers mostly will assign all 0-4 levels. But in rare cases will
assign upto 0-3 or 0-2 levels. For these cases when I am using rollup on summary
table I am getting multiple rows.
Example:
SF_TIER0_DSC    SF_TIER1_DSC    SF_TIER2_DSC    SF_TIER3_DSC    SF_TIER4_DSC
CY_AMT
GE FANUC    AMERICA SALES    WEST CANADA    N/A    N/A    0
GE FANUC    AMERICA SALES    WEST CANADA    N/A        0
GE FANUC    AMERICA SALES    WEST CANADA    TROGDON WILL    GRIFFITH DON    0
GE FANUC    AMERICA SALES    WEST CANADA    TROGDON WILL    N/A    186.91
GE FANUC    AMERICA SALES    WEST CANADA    TROGDON WILL        186.91
GE FANUC    AMERICA SALES    WEST CANADA            186.91
GE FANUC    AMERICA SALES                186.91
GE FANUC                    186.91
in this last level is "GRIFFITH DON" , "TROGDON WILL" is before level. for this
level to next level i am showing 'N/A' because I have null value over
there(directly coded to 3rd level no 4th level for this). The next record is
dynamically created to show breakup summary. so when i am assiging levels for
this data the above 2 records assiging for same level 3. it is wrong i need only
one.
Below are table structures and quires i am using.

Name                                Null?    Type
------------------------ ----------------------------
PL_TIER0_DSC                              VARCHAR2(30)
PL_TIER1_DSC                              VARCHAR2(30)
PL_TIER2_DSC                              VARCHAR2(30)
PL_TIER3_DSC                              VARCHAR2(30)
SF_TIER0_DSC                              VARCHAR2(30)
SF_TIER1_DSC                              VARCHAR2(30)
SF_TIER2_DSC                              VARCHAR2(30)
SF_TIER3_DSC                              VARCHAR2(30)
SF_TIER4_DSC                              VARCHAR2(30)
TIME_DWMQY_FLG                            VARCHAR2(1)
CY_AMT                                    NUMBER(38,2)

SELECT  PL_TIER0_DSC,
        PL_TIER1_DSC,
        PL_TIER2_DSC,
        PL_TIER3_DSC,
        SF_TIER0_DSC,
        SF_TIER1_DSC,
        SF_TIER2_DSC,
        SF_TIER3_DSC,
        SF_TIER4_DSC,
        SUM(CY_AMT) CY_AMT,
        GROUPING(PL_TIER0_DSC),
        GROUPING(PL_TIER1_DSC),
        GROUPING(PL_TIER2_DSC),
        GROUPING(PL_TIER3_DSC),
        GROUPING(SF_TIER0_DSC),
        GROUPING(SF_TIER1_DSC),
        GROUPING(SF_TIER2_DSC),
        GROUPING(SF_TIER3_DSC),
        GROUPING(SF_TIER4_DSC)
FROM    TB_EIP001_TEST2
WHERE     PL_TIER0_DSC != 'N/A'
AND        PL_TIER1_DSC != 'N/A'
AND        PL_TIER2_DSC != 'N/A'
AND        PL_TIER3_DSC != 'N/A'
AND     SF_TIER2_DSC  = 'WEST CANADA'
AND        PL_TIER3_DSC  = 'All Other OI'
GROUP BY ROLLUP(PL_TIER0_DSC,
                 PL_TIER1_DSC,
                PL_TIER2_DSC,
                PL_TIER3_DSC,
                   SF_TIER0_DSC,
                SF_TIER1_DSC,
                SF_TIER2_DSC,
                SF_TIER3_DSC,
                SF_TIER4_DSC)
HAVING    (
        GROUPING(PL_TIER0_DSC) = 0
        OR
        GROUPING(PL_TIER1_DSC) = 0
        OR
        GROUPING(PL_TIER2_DSC) = 0
        OR
        GROUPING(PL_TIER3_DSC) = 0
        OR
        GROUPING(SF_TIER0_DSC) = 0
        OR
        GROUPING(SF_TIER1_DSC) = 0
        OR
        GROUPING(SF_TIER2_DSC) = 0
        OR
        GROUPING(SF_TIER3_DSC) = 0
        OR
        GROUPING(SF_TIER4_DSC) = 0
        )

Help me to over come this.
Thanks in advance.

A.R.Chary

March 24, 2003 Darleen -- Thanks for the question regarding
      "Monitoring Processes/instances", version 8.05

      You Asked
What is the syntax for obtaining what processes/instancs are
currently 'running'. for example to check that listener is
active?

Thanks
Darleen

      and we said...
Well, Oracle Enterprise Manager (OEM) is the single tool to
watch them "all".  You might look into that.

Each particular tool has a way to do this.  To see that a Net8
listener is running for example, you can issue:

$ lsnrctl status

or

$ tnsping <connect string>

To see if the database is running, you can just sqlplus in.  And
so on.

If you want a single report of all thats up (and whats not) OEM
is the tool to use.

Review &
      Followup

      Rating: 5
How to know,
      quickly whether the target database instance/listener is
      running  March 24, 2003
Reviewer:  Bala
      Subramanian
Hi Tom

we have several distributed databases(8.1.7) connected to one master database
via db_links. Our application(jsp/servlet/beans) connects to this master db and
executes procedure which inturn uses the db_link to get information from remote
databases.

If the remote server is down or connectivity is down then it takes long time for
oracle to read tnsnames and look for the remote service and come back with an
error.

Is there any way to tnsping or ping from plsql to see the  target server is up
and running, quickly before we run some sql against them.

Thanks
Bala.

          Followup:

You'll need to tune your tcp settings at the server level (OS).

shutdown your database, connect to it -- if that takes more then a fraction of a
second to get a response, somethings wrong.

It should be (and has been in my experience) immediate.

March 24, 2003 jianhui -- Thanks for the question regarding
      "escape characate in the password string", version 9.2.0

      You Asked
Tom,

How to escape a special character in the password string? Take a look at follow
example,

SQL> l
  1  create database link tst
  2  connect  to scott identified by tiger!a
  3* using 'isgt1'
SQL> /
connect  to scott identified by tiger!a
                                     *
ERROR at line 2:
ORA-00933: SQL command not properly ended

......

SQL> create database link tst
  2  connect  to scott identified by tiger\@a
  3  using 'isgt1' escape '\'
  4  /
connect  to scott identified by tiger\@a
                                     *
ERROR at line 2:
ORA-00911: invalid character

I have EXECUTE IMMEDIATE dynamic sql in my stored procedure and it reports the
same error as i got from the sql*plus example above. How to escape the special
character like '!', '@', etc. in the password string.

Thanks

      and we said...
double quotes

identified by "tiger!a"

but bear in mind that is CASE SENSITIVE so that might be

"TIGER!A"

scott@ORA920> alter user scott identified by "TIGER!A";

User altered.

  1  create database link ora920@loopback
  2  connect to scott
  3  identified by "TIGER!A"
  4* using 'ora920'
scott@ORA920> /

Database link created.

scott@ORA920> select * from dual@ora920@loopback;

D
-
X

March 25, 2003 Mahesh -- Thanks for the question regarding
      "Horizontal and Vertical subsets in replication", version 8.1.7

      You Asked
Tom,
Could you please explain what are the Horizontal and Vertical subsets in
replication and also advanced techniques like Dynamic ownership, shared
ownership with update conflict detection and resolution and as well as
procedural replication .

Thanks In Advance
Mahesh Iyli

      and we said...
horizontal = a predicate was applied to replicate only SOME rows.

vertical = a select list was applied to replicate only SOME columns.

create snapshot emp_snap
as
select empno, hiredate
  from emp
 where deptno = 10;

I get a vertical subset (empno,hiredate) of all of the emp columns and only for
the horizontal subset of rows in deptno=10

Dynamic Ownership = one site at a time owns a row of data.  Say you have a table
T with a column "owner".  You have two sites A and B.  At noon, Site A inserts
the row with owner=A.  They (site A) owns the row.  There is a trigger or some
constraint on this table that prohits site B from updating this row (since
owner=A and site is B, the trigger will fail the update).

Now, at 12:05, site A issues "update t set owner=B" for that row.  Site A loses
the ability to update that row (owner=B, site=A, trigger fails further updates).
 Site B however gains ownership of this row and B now "owns it".  Ownership of
this row is dynamically changed in this fashion.

Shared ownership = both sites can update it.  This is harder as you have to
anticipate that both will update it at about the same time.  You have to then
code the resolution routine or tell us which one of the 7 or so builtin ones to
use.

Update conflict detection is us simply detecting the condition I refered to
above -- Site A and Site B both share ownership of a record and both update it
-- that is an update conflict.  We detect it -- you resolve it.

Procedural replication is the replication of a PROCESS instead of bits and
bytes.  Instead of running the "close the books end of year process" and
replicating row by row -- we can replicate the "close the books" call -- both
sites run the same process -- resulting in the same net effect but at a
significantly reduced code (lots less network traffic, more efficient
transactions at each site)

you should read:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76959/toc.htm
if you are really interested in this topic -- it covers all of this stuff.

Review &
      Followup

      Rating:
      5
Excellent  March 24, 2003
Reviewer:
      A reader

      Rating:
      4
Excellent!  March 24, 2003
Reviewer:  A
      reader
How do you compare Oracle replication vs other vendor's replication for example,
sybase or Db2 or sql server? Thanks.

          Followup:

ours is better of course.

do you have a specific problem you are trying to solve?  I can wax poetic for
hours about features functions -- but unless you tell me a real problem you have
that you are trying to use replication to solve -- I might not say anything
relevant to you.

You need to

a) have a problem
b) study the solution sets
c) decide which is best suited to your problem

Oracle has at least 3 methods

o snapshots -- aka materialized views.  simple to implement, ease of use is
great, over 11 years in maturity.

o peer to peer (master to master) replication.  More moving bits, more
complexity -- with corresponding flexibility.  Over 9 years of maturity.

o streams -- new with 9iR2, new architecture.  middleware, message based,
pub/sub oriented.

March 25, 2003 --
      Thanks for the question regarding "Could you briefly list the new SQL or
      PL/SQL features in 9.2.0 that you like most? ", version 9.2.0

      You Asked
Could you briefly list the new SQL or PL/SQL features in 9.2.0 that you like
most?

Thanks in advance

      and we said...
I like them all.  (well, except for natural joins)

Table compression tops the list....

I like fetch bulk collect into a record
I like insert from a record...

I like CASE in sql and plsql...

utl_file with directory objects instead of utl_file_dir...

databases with system installed as a LMT -- making it IMPOSSIBLE to use DMT's

Review &
      Followup

      Rating: 5
Thank
      You  March 24, 2003
Reviewer:  A reader

      Rating:
      5
UTL_FILE  March 25, 2003
Reviewer:
      Martin  from UK
I've just rewritten a 9iR1 application using UTL_FILE (and some JSPs which
archive files etc.) into a 9iR2 app and taking advantage of the new features of
UTL_FILE and it's reduced the code and manageability by over 70% (not least that
I don't need to modify UTL_FILE_DIR anymore!)

DIRECTORY objects rule!

Can you give a quick example of

"I like fetch bulk collect into a record"

Thanks in advance.

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:8784259916366
and ctl-f for

bulk collect

      Rating: 3
SQL/PLSQL
      features in Oracle 9i  March 25, 2003
Reviewer:
      Nathan  from Singapore
Hi Tom,
Any link to go through these features...
Pls guide me
Regards
Nathan

          Followup:

well, there is this:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-NEW
the new features guide.

If you go here:

http://asktom.oracle.com/~tkyte/
I have some online presentations as well.

      Rating: 4
No excuse for
      not bulking up now...  March 25, 2003
Reviewer:
      Adrian Billington  from UK
Martin from UK

Here's an example. There's no excuse for row-by-row processing any more because
even NDS supports this stuff...

920> declare
  2     type typ_aa_objects is table of user_objects%rowtype
  3        index by pls_integer;
  4     aa_objects typ_aa_objects;
  5  begin
  6     select *
  7     bulk collect into aa_objects
  8     from user_objects;
  9     dbms_output.put_line(aa_objects.count||' records fetched.');
 10  end;
 11  /
49 records fetched.

PL/SQL procedure successfully completed.

Regards
Adrian

March 25, 2003 Beth -- Thanks for the question regarding "Timed
      Statistics Overhead - per system vs session", version 8.1.6

      You Asked
I would like to alter system set timed statistics = TRUE for our production
database, for the tuning benefits with bstat and estat and tkprof.

I understand the overhead on the system is negligible, but wonder if it might be
better to just grant all developers the alter session privilege to set
timed_statistics = true for that session in question.

Can you comment on the pros and cons of both options?  Is there a reason to not
grant the "alter session" priv globally to all developers?  Would there be less
overhead with users altering their sessions, rather than altering the entire
system, or would that difference even matter?

When altering the system, I believe the operating system time is collected and
placed into 10 extra rows (CPU used by this session, parse time CPU, etc) in
v$sessstat and v$sysstat.  If multiple sessions, (not system) have timed
statistics = true, how are the sessions distinguished?  Through the sid in
v$sessstat?  Are the underlying tables in these dynamic views only updated and
never inserted into, so that there is really no "space issue" collecting all
these statistics?

Thanks

      and we said...
Don't use bstat/estat -- use STATSPACK!!!!  Infinitely better (if you have my
book -- chapter 10)

I run my systems with timed_stats = true.  Your developers can have alter
session (but on a production box -- maybe no, do you want them setting their own
sort_area_sizes and such on the fly without checking it out?)

For tools like statspack to work, timed stats needs to be on for the system.

There is not any space issue with regards to collecting these stats (statspack
does save them -- so you have a history -- something you really need, so
statspack has some minimal space needs).  The slots in the v$ tables (memory
data structures really) are always there -- it is just "are they 0 or some
number".

Review &
      Followup

      Rating: 2
Probelm with
      Timed Statistics=TRUE  March 25, 2003
Reviewer:
      Riaz Shahid  from PRAL, Islamabad, Pakistan
Hello Tom !

Is there any performance overhead with timed_statistics=true at instance level.
I set timed_statistics=true and all clients queries gone slower and slower. What
may the possible reason for that ? I set timed_statistics=FALSE and restarted
DB. Now the pefromance was ok.
Can u please comment on this ?

riaz

          Followup:

sigh, never anything useful in things like this - like, oh say the version, the
os, etc....

in 815 there was a known issue with timed stats causing excessive child cursors.
 found and fixed years ago.  maybe thats it.

otherwise you would have to quantify things a tad better -- eg: look at this,
query takes x, then y, then z, version is a.b.c, os is FOO.... and so on.

March 25, 2003 Elisa -- Thanks for the question regarding "Using
      XML to transfer data from SQL Server to Oracle", version 8.1.5

      You Asked
Hi Tom,
I have a table on SQL Server that I want to move to Oracle every five minutes or
so.  My thought was to use SQL Server to create a flat file and use sqlloader to
load the data in the Oracle table.  I was asked to look into using XML,  would
this be a better approach?  I haven't read anything that would indicate that so
I thought I would ask you.  What are your thoughts.
Thanks
Lisa

      and we said...
Ahh, the mystique of XML...

Using XML to do everything is like "everything you code must be written in java
because java is good".

Well, there are 100 ways to do everything (and there are more languages then
java out there).

You want to simply move that table over -- the method I would probably take is:

o load the sql server jdbc drivers into Oracle
o write a java stored procedure in Oracle that connects to sql server and opens
a result set.
o it'll delete from the existing oracle table
o it'll insert the rows it fetches
o you would use DBMS_JOB to schedule that procedure to run every N minutes

no muss, no fuss -- all done in the database.

In the event you don't have java in the database, I would write the java OUTSIDE
the database and use the OS tools to schedule it.

In the event you dont write java, I would use BCP to dump the data, sqlldr to
load it.

Why would you want to write a program to dump the data from sql server in XML
(makeing it perhaps 2 times BIGGER then it already is) and then write another
program to parse it and load it into Oracle???

Always look for the easiest way.  If you had to build a service that dozens of
external companies were going to use (eg: you had to make this table available
to 100's of people outside of your organization) XML would be a good choice.
Nice vendor independent data representation.

To simply move data from database A to database B inside your own company, it
would be a waste.
Review & Followup

      Rating:
      5
  July 27, 2001
Reviewer:
      A reader

      Rating:
      5
  July 27, 2001
Reviewer:
      A reader

      Rating: 5
Class 1
      teaching methodology  July 28, 2001
Reviewer:
      WL  from ROUND ROCK, TX USA
This is the best answer for this type of the question.

      Rating: 3
Using XML to
      transfer data from SQL Server to Oracle  July 30, 2001

Reviewer:  Satish  from Hyderabad, India
But i still prefer a generic answer to this question , it can be any database to
Oracle and as he mentioned some 100 companies are accessing this data. so how it
can be done using XML?

      Rating: 5
The most
      efficient way.  July 30, 2001
Reviewer:  AT
       from Sydney Australia
This is the most effecient way to do a data transfer,
very clean indeed.

I wonder if a table link could be another way too.

      Rating: 5
will appreciate
      if it comes out with XML example  July 30, 2001

Reviewer:  musa  from NY USA
will appreciate if it comes out with XML example

      Rating: 3
also SQL Server
      replication through ODBC  July 31, 2001
Reviewer:
      Paolo Bazzocchi  from Woking, United Kingdom
It is quite easy to set up and there is no coding involved. We move real-time
data from SQL server to Oracle and we never had a problem.

      Rating: 3
also SQL Server
      replication through ODBC  July 31, 2001
Reviewer:
      Paolo Bazzocchi  from Woking, United Kingdom
It is quite easy to set up and there is no coding involved. We move real-time
data from SQL server to Oracle and we never had a problem.

      Rating: 3
Nice
      Touch  August 02, 2001
Reviewer:  Anthony, Lai
      Cheuk Tung  from Ma On Shan,Hong Kong SAR
Pursue a simple way, the goal is how to get the job done, not how to apply tech.
everywhere without cautious consideration.

      Rating: 5
Pure
      think  August 03, 2001
Reviewer:
      A reader

      Rating: 5
a
      reader  August 03, 2001
Reviewer:  juan pedro
      lopez  from Spain

      Rating:
      5
Parag  March 31, 2002
Reviewer:  Parag
      Mehta  from India
Hi Tom :

As always , I am higly impressed byur ans. But it would be fairenough that , u
would have given some sort of example. I am also facing same Problem. While
seating at Oracle SQL Pormpt , I need to put join in MS SQL Server 2000 Table.

Can u please give me a simple example , ( with the hlp of CORE PL-SQL) if not
then with JAVA , where i can access the tables (data) of SQL Server while
seating at Oracle.

Secondly , One more requirement is to call SQL Server Stored procedure while
seating at Oracle SQL* Prompt. How do i go about for same ?

Waiting for u reply with example.

Regards,
- Parag

          Followup:

read

http://www.oracle.com/gateways/
...

      Rating:
      1
Parag  March 31, 2002
Reviewer:
      Parag  from India
Hi Tom :

I have gone through the link u have send to me. It's not usefull at all ( May be
not to me) . Can u please send me a small example of same.

Waiting for Example rather then Link.

          Followup:

Is your keyboard broken?  many of the Y's and O's seem to be missing.

An example of querying MS SQLServer data joined with Oracle data via a gateway:

select * from emp, dept where emp.deptno = dept.deptno;

(assumimg EMP is in MS Sqlserver, DEPT is in Oracle).  Using a gateway, we make
sql server look like Oracle, you can call stored procedures, do transactions,
query the data, whatever you like.

There is no "example" of a gateway, you buy the product, install it, use it.
Please read the links -- they do contain useful information.

since you don't like clicking on links and gathering data on your own, click on
this link which gets you a step closer then the other one does:

http://www.oracle.com/gateways/gateway_bundles/index.html?open_system_gateways.html
then follow the production information links to the FAQ, features, and getting
technical.

      Rating:
      5
Excellent  April 01, 2002
Reviewer:
      Chandra S. Reddy  from Hyd,India
I think the first approach is good.
That is, sitting a Java inside Oracle server, Read and Insert data.
The performance could be increased dramatically.

      Rating: 3
Synchronize
      Data  November 27, 2002
Reviewer:  Mohammed
      Osman  from KSA
I have been visiting this great site, since long and have learnt a lot from your
valuable Advise.

I have a similar scenerio where I need to retrieve Records from Sql Server into
Oracle9i Database, I have no knowledge of Java or Xml.  Besides loading from
Flat File using  SQL Loader is there any other option available.  I tried to
create a Database Link for the ODBC String but could not succeed.

CREATE DATABASE LINK SQLSRV CONNECT TO ADMIN IDENTIFIED BY ADMIN USING 'Test'

Select * From Branches@SQLSRV
                       *
ERROR at line 1:
ORA-12154: TNS:could not resolve service name

CONN ADMIN/ADMIN@ODBC:Test
ERROR:
ORA-03121: no interface driver connected - function not performed

Would appreciate your respose.

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4406709207206

      Rating: 3
Generating XML
      file to Interface from Oracle   March 25, 2003

Reviewer:  Bhavesh  from Boston, USA
Tom,

I've simillar requirement and wondering if you have already provided the
solution. I did not find it on the site though.

We are looking at generating XML file from Oracle to interface E-commerce
website. Our code is complex pl/sql (not a single SQL statement) generating a
flat file. Now we would like to generate XML instead of the flat file. I've
looked at dbms_xml API's and it takes SQL statement as parameter. Is it possible
to generate XML file from PL/SQL simillar to what utl_file does. What should be
our approach ?

I would greatly appreciate if you provide some sample code or give us the
direction. Thanks a lot for your help.

Bhavesh

          Followup:

Umm, you have complex code that results in a flat file.

flat files = rows and columns.

sounds like a query to me -- if you have to just put the rows and columns in a
global temporary table and use that as the input to the XML stuff in the
database.

      Rating: 4
XML Data Load
      performance Vs Text file  March 25, 2003
Reviewer:
      Sam  from Japan
Hi Tom,
Which one has better data load performance, XML using dbms_xml api or text file
uploaded through SQL Loader direct load?

Regards
Sam

          Followup:

apples and pears here.  how could you even begin to compare a complex process
with tons of benefits with a simple write?

      Rating:
      5
google  March 26, 2003
Reviewer:
      Max  from Moscow, Russia
hey, everybody
don`t you noticem that most of the answers were "go nad check out this or that
link..."
is it ok?

please, use Google.

do not bother Mr. Kyte


Subject | Article | Last Updated (East coast time)
Flushing Database Buffers | article only | Mar 26, 2003 07:47 am
Index usage | article only | Mar 26, 2003 03:57 pm
Invalid Objects X sys.order_object_by_dependency | article only | Mar 26, 2003 04:00 pm
Error handling using implicit cursors for insert | article only | Mar 26, 2003 06:41 pm
Set autotrace on | article only | Mar 26, 2003 07:07 pm
Backing up an Oracle db | article only | Mar 26, 2003 07:21 pm
9.2 associative arrays and forall frustration... | article only | Mar 27, 2003 07:31 am
General about Database | article only | Mar 27, 2003 08:20 am
String tokenize | article only | Mar 27, 2003 08:23 am
Recursion | article only | Mar 27, 2003 10:16 am
Does RAISE_APPLICATION_ERROR also mark the calling job as broken? | article only | Mar 27, 2003 10:17 am
Tru64 based Oracle | article only | Mar 27, 2003 12:45 pm
Saveing the Output from a Dynamic SQL | article only | Mar 27, 2003 02:15 pm
Bind variables & DB Links | article only | Mar 27, 2003 02:26 pm
What do commercial vendors user - OCI or PRO*C | article only | Mar 27, 2003 03:23 pm
sum on report | article only | Mar 27, 2003 07:21 pm
Performance | article only | Mar 28, 2003 07:09 am
De-composing Composite Indexes | article only | Mar 28, 2003 07:10 am
Strange result when trying to call Java Stored Procedure with no parameters. | article only | Mar 28, 2003 08:35 am
loading data from .txt to table takes half an hour for 25000 to 30000 records | article only | Mar 28, 2003 03:56 pm
SQL | article only | Mar 29, 2003 09:35 am
PL-SQL Server Pages | article only | Mar 29, 2003 09:40 am
having more then 1 listener process | article only | Mar 29, 2003 09:48 am
Is it possible to bring down the consistent get ?? | article only | Mar 29, 2003 10:59 am
Compare between two tables and other database objects | article only | Mar 29, 2003 11:03 am
Repost link to flashback query paper? | article only | Mar 29, 2003 08:16 pm
Backup | article only | Mar 30, 2003 08:20 am
Can I Pass a nested table to Java from a pl/sql procedure. | article only | Mar 30, 2003 09:05 am
LONG data type | article only | Mar 30, 2003 02:41 pm
Deletion of AQ queue table | article only | Mar 30, 2003 02:49 pm
Oracle 9i Datawarehousing features | article only | Mar 30, 2003 03:07 pm
ora-02004 | article only | Mar 30, 2003 05:59 pm
confusion about SCN | article only | Mar 31, 2003 08:16 am
dbms_jobs | article only | Mar 31, 2003 08:56 am
Workaround for "Select Any Table" privilege | article only | Mar 31, 2003 09:24 am
SQL relared queries ?? | article only | Mar 31, 2003 10:25 am
Serialization of selects | article only | Mar 31, 2003 10:36 am
Log Miner Clarification | article only | Mar 31, 2003 11:07 am
DB transfer | article only | Mar 31, 2003 11:08 am
session_cached_cursor | article only | Mar 31, 2003 11:21 am
How to Combine different columns to a field | article only | Mar 31, 2003 01:44 pm
SQL Query | article only | Mar 31, 2003 02:28 pm
servererror trigger | article only | Mar 31, 2003 03:30 pm
How to rename datafiles? | article only | Mar 31, 2003 03:41 pm
SQL*Loader-510: Physical record size limit | article only | Mar 31, 2003 05:02 pm
Primary Key VS Unique Index | article only | Mar 31, 2003 05:11 pm
Wanting to pass progress information to client layer process - DBMS_PIPE? | article only | Apr 01, 2003 07:28 am
customizing the SQLprompt | article only | Apr 01, 2003 07:36 am
SGA Used Size | article only | Apr 01, 2003 07:41 am
Analytic function | article only | Apr 01, 2003 08:49 am
re: list partitioning; update time | article only | Apr 01, 2003 09:01 am
How to update lob | article only | Apr 01, 2003 09:04 am
Columns In Select Clause Effecting Explain Plan | article only | Apr 01, 2003 04:13 pm
Using a environmental variable for system passwords in UNIX scripts | article only | Apr 01, 2003 04:22 pm
Partial Combination Index | article only | Apr 01, 2003 07:18 pm
Chained Row and Index | article only | Apr 01, 2003 07:23 pm
Moniroting suspicious activity | article only | Apr 01, 2003 08:19 pm
Unknown Oracle Processes | article only | Apr 01, 2003 08:46 pm
row(s) 1 - 58 of 58
March 26, 2003 Mahesh -- Thanks for the question regarding "Flushing Database Buffers", version 8.1.7
You Asked
I am trying out variants of a SQL statement in an attempt to tune it. Each
variant involves joins across a different combination of tables, although some
tables are common across all variants. In order to be able to do a valid
comparison of the TKPROF outputs for the variants, I believe I need to flush the
database buffer cache between variants so that the db block gets, consistent
gets and physical reads parameters are true for each variant. By doing this,
data retrieved for one variant is not already in the buffer cache for the next
variant, thus not influencing the above parameters for the next variant.

Is it possible to flush the buffer cache? The shared pool can be flushed with
the ALTER SYSTEM FLUSH SHARED_POOL command. I've searched but have not been able
to find an equivalent for the buffer cache. The NOCACHE option to the ALTER
TABLE command only pushes retrieved data to the LRU list in the buffer cache,
but does not remove it from the buffer cache.

I'm hoping to be able to do this without bouncing the database between variants.
It is a development instance, and I have it to myself after hours.
and we said...
db block gets and consistent will remain the same regardless of the flush (or
not).

physical reads will differ but hey -- in the real world they would differ
anyway.

There is no real way to flush the buffer cache short of a shutdown.

For testing, ignore the physical ios and concentrate on the logical ios -- or
run each query N times, through out the first observation and average.

I've never found the need to bounce an instance just to test a query.  Running
the query for the second time is more real world (you don't run a query just
ONCE and some of the data is naturally going to be cached).  Flushing the buffer
cache would actually be more artificial then NOT flushing it -- there is no way
the buffer cache would be emtpy when you ran your query.

followup to comment two

Doh -- of course.

Here is a test:

CREATE TABLE t
( x      int,
  data   char(2000)
)
tablespace users;

insert into t select rownum, object_name from all_objects where rownum < 101;

alter session set sql_trace=true;
select * from t t1;
alter tablespace users offline;
alter tablespace users online;
select * from t t2;
select * from t t3;

I used a char(2000) to make each row take up lots of space.  The TKPROF from
that shows:

********************************************************************************
select *
from
 t t1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        8      0.00       0.00          3         42          4         100
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       10      0.00       0.00          3         42          4         100

********************************************************************************

alter tablespace users offline
alter tablespace users online

********************************************************************************

select *
from
 t t2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        8      0.00       0.00         36         45          4         100
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       10      0.00       0.00         36         45          4         100

the 36 blocks are the 36 blocks the table takes in my instance -- using
dbms_space -- i verified my table is exactly 36 blocks in size

********************************************************************************

select *
from
 t t3

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        8      0.00       0.00          0         42          4         100
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       10      0.00       0.00          0         42          4         100

and now they are in the cache ...
Review & Followup
Rating: 4
Flushing database buffers July 11, 2001
Reviewer: Mahesh from Tucson, AZ
Thanks much for your prompt and detailed response. I will proceed with my tuning
efforts on the basis of your suggestions. I was tuning assuming a worst case
scenario: my SQL statement was not in the shared SQL area of the shared pool,
and the data was not already in the database buffers as a result of one or more
previous queries. One of the objectives was to determine that combination of
table joins that would minimize physical reads.

Rating: 4
A possibility... July 12, 2001
Reviewer: Connor from UK
I haven't confirmed this, but I think if you take a tablespace offline its
buffers are removed the cache.

Rating: 5
alter tablespace ... appears to work July 18, 2001
Reviewer: Mahesh from Tucson, AZ
Altering tablespace certainly seems to work. See following test:

Query:
SELECT NVL(SUM(mol.hours_loaded), 0) released
FROM  shop_ord_tab so,
      shop_order_operation_tab soo,
      mach_operation_load_tab mol
WHERE so.order_no      = soo.order_no
AND   so.release_no    = soo.release_no
AND   so.sequence_no   = soo.sequence_no
AND   soo.operation_no = mol.operation_no
AND   soo.order_no     = mol.order_no
AND   soo.release_no   = mol.release_no
AND   soo.sequence_no  = mol.sequence_no
AND   so.rowstate IN ('Released', 'Reserved', 'Started')
AND   so.contract         = 'HBG'
AND   soo.work_center_no  = '160'
AND   trunc(mol.work_day) in (trunc(to_date('10-JAN-01')));

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

alter tablespace xxx offline;
alter tablespace xx  online;

stats after query run again:
Statistics
---------------------------------------------------
          0  recursive calls
          4  db block gets
        543  consistent gets
         20  physical reads
          0  redo size
        368  bytes sent via SQL*Net to client
        425  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

stats without altering tablespace and running query again:
Statistics
---------------------------------------------------
          0  recursive calls
          4  db block gets
        543  consistent gets
          0  physical reads
          0  redo size
        368  bytes sent via SQL*Net to client
        425  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

Rating: 3
Does Renaming global_Name Flushs The Buffer March 26, 2003
Reviewer: vicky from INDIA
Hi Tom ..

Does Renaming  global_Name Flushs The Buffer. If does ..
whats the Reason behind this ...

Followup:
no

  1* select count(*) from t
ops$tkyte@ORA920> /

  COUNT(*)
----------
        14

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

ops$tkyte@ORA920> alter database rename global_name to foobar;

Database altered.

ops$tkyte@ORA920> select count(*) from t;

  COUNT(*)
----------
        14

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

March 26, 2003 Rui -- Thanks for the question regarding "Index usage", version 8.1.6
You Asked
Hi Tom
My question is only to confirm or not this:
I have an index with column A and Column B from this order.
If I select ... from table ... where a = 1 and b = 2 the server uses this index.
If I select ... from table ... where b = 1 and a = 2, can the server uses this
index too ?
It´s dependent of Optimizer_Mode or something else ?

Thanks
Rui Brito
and we said...
it can be used in both cases, regardless of the optimizer in use.

In fact, it can be used by

where a = :x

where b = :y (mostly in 9i with index skip scans but not at all near impossible
in 8iR2)

where a = :x and b = :y

where b = :y and a = :x
Review & Followup
Rating: 5
Bind Variables March 25, 2003
Reviewer: Rui Brito from Portugal
Hi Tom
Your answer opens a new question. When you use Bind Variables, the indexes are
always use ? The where clause "where a = :x and b = :y" uses the index ?
I had a discussion about the pros and cons to use Bind Variables vs Constants,
and the advantage or not, to determine the most correct Explain Plain if we use
Constants. All tools Oracle use Bind Variables, so there is no alternative to
them.
What you think about this ?
Is better use always Bind Variables or Constants ?
Can I use the term "always" or depends of data ?
Histograms helps the decision or not (only with Constants)?
Thanks
Rui Brito

Followup:
Unless you are a data warehouse where you parse a query per minute -- you had
better use bind variables everywhere unless you want to fail.

Use binds
Use binds
Just do it, use binds.

Rating: 3
Importance of bind variables March 26, 2003
Reviewer: Peter from Cumbria, England
Is somebody paying you to recommend bind variables?
I know that it's one of your pet things to (almost) always use bind variables,
but if I had the time I could show you numerous cases where the execution plan
for a query is not as good (or more importantly the query elapsed time is
longer) when using bind variables. As I'm sure you will agree there are more
important things than reducing parses whether you are running a data warehouse
or an OLTP system.
Bind variable vs. constants - it's horses for courses - depends on the
circumstances.

Followup:
Oracle pays me.

In a transactional system bind variables are a 100% must, no if and or butts
about it.  PERIOD.  If you use PLSQL for everything (as I do), you really have
no choice and I've never dropped down to dynamic sql in plsql to override a bind
and make it a literal.  Never.

data warehouse, as I've said, totally different.

March 26, 2003 Andre, from Brazil -- Thanks for the question regarding "Invalid Objects X sys.order_object_by_dependency ", version 817
You Asked
Hi Tom.
I have the following script that is very good to recompile invalid objects and I
can filter
just for INVALID objects.

SET PAGESIZE 1000
SET HEAD OFF PAGES 0 LINES 79 VERIFY OFF ECHO OFF
SELECT DECODE( object_type, 'PACKAGE BODY',
      'ALTER PACKAGE ' || owner||'.'||object_name || ' COMPILE BODY;',
      'ALTER ' || object_type || ' ' || owner||'.'||object_name || ' COMPILE;' )
FROM  sys.dba_objects a, sys.order_object_by_dependency b
WHERE a.object_id = b.object_id(+)
  AND status = 'INVALID'
  AND owner=upper('&OWNER')
  AND object_type IN ( 'PACKAGE BODY', 'PACKAGE', 'FUNCTION', 'PROCEDURE',
                      'TRIGGER', 'VIEW' )
ORDER BY dlevel desc, object_type, object_name;
set head on

In 8.1.7 the view sys.order_object_by_dependency does not exists. So, Oracle
recommends
to use scripts for recompiling all schema instead recompiling only INVALID
objects.

Do you have a script that :
- compile just INVALID objects in 8.1.7 and
- compile in order by dependency ?

TKS.
and we said...
Yes, actually in my book I have this.  The source code is available from:
http://www.wrox.com/Books/Book_Details.asp?ISBN=1861004826
under "download code".  The fact is -- you do not need to compile them in
order -- you just need to compile them in some order.

1) What I do is find an invalid object
2) remember in a temp table that i've processed it
3) compile it
4) Using a brand new query find the next invalid object to compile that i
haven't processed already.

So, if I have procedure A that calls procedure B and both procedure A and B are
invalid, it doesn't matter if I compile B and then A -- or just compile A
(compiling A will in fact compile B for me!)

If you want all of the details -- they are in the book.
Review & Followup
Rating: 5
Tks ! August 24, 2001
Reviewer: Andre, from Brazil

Rating: 4
November 07, 2002
Reviewer: Pushparaj Arulappan from Edison, NJ
Tom,

I have a situation where I need to compile the objects
in the order. Please consider the following example,

SQL > CREATE OR REPLACE TYPE object_type1
      AS OBJECT ( col1 NUMBER )
/
Type created.

SQL > CREATE OR REPLACE TYPE nested_table1
      AS TABLE OF ( col1 object_type1 )
/
Type created.

SQL > CREATE OR REPLACE TYPE object_type2
      AS OBJECT ( col1 nested_table )
/
Type created.

For example, if I want to do any modification to the
object_type1 object..

SQL > CREATE OR REPLACE TYPE object_type1 (col1 number(4))
/
*
ERROR at line 1:
ORA-02303: cannot drop or replace a type with type or table dependents

So I have to drop all the dependents and recreate all of them in the order.

Or

SQL > DROP TYPE object_type FORCE
/
Type Dropped.

SQL > CREATE OR REPLACE TYPE object_type1 (col1 number(4))
/
Type created.

If I drop the object using FORCE option, and re-creating that object alone gives
internal error problem when the dependent objects are accessed from the XML
application.

1) Is there any way to compile a object type which has   dependent objects
without droping.

2) Do you have a sample sql which can get all the nested dependents in the for a
given object. (ie object_type3 is dependent on nested_table1 which is dependent
on object_type1.)

Thanks
Pushparaj

Followup:
1) upgrade to 9i where types are modifiable (type evolution is fully supported).

2) you would use user_dependencies to find that information -- i don't not have
an off the shelf query for you in that case.

Rating: 5
Is there a bug in the compile script ? March 26, 2003
Reviewer: A Reader from UK
Tom,

When I download the script, run against Sys, then try and use from another
schema, I get:

SQL> exec sys.compile_schema('ABC');
alter PACKAGE BODY DW.ABC compile body
ORA-00922: missing or invalid option

(repeated for a whole series of packages)

I suspect its because the script selects the object type, when for packages it
should just be:

alter PACKAGE DW.ABC compile body ???

I modified the script to do a decode on object type up-front and if it is
package body replace it with package.

Did I miss something - that seems to work now ?

Followup:
yes, my script did not get 100% of the types available over time.  adding
support for it as you did is proper

March 26, 2003 Pratap -- Thanks for the question regarding "Error handling using implicit cursors for insert", version 9.0
You Asked
Hi Tom

Consider an insert statement -

insert into t1 select * from t2

Now say one record violates some constraint in t1, the entire operation stops.
Is there is any trick/workaround to continue execution of the DML and log the
errors as well?

I don't want to use an insert inside a cursor loop because there are many
records to be inserted.

Another questions on Materialized view - I read the chapter in your book about
MV's. But I have a question - If I have huge data in the MV, then I will we have
to tune the MV's as well. Like creating the indexes, partitioning etc. Is it
practical to create MV's with huge amount of data?

Thanks for the great book, the explainations given in all the chapters is just
great, I will wait for the second edition having 9i features.

Thanks and regards

Pratap
and we said...
You can use an instead of trigger on a view of T1 to accomplish this.  Be
prepared to take a performance hit!  anytime you add procedural code, it'll make
it slower.

It'll be very very much like doing it inside of a cursor for loop however --
you'll be inserting each row row by row at the end of the day.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t1
  2  ( x int primary key,
  3    y date check ( y > to_date('01012002','mmddyyyy') ),
  4    z varchar2(25)
  5  )
  6  /

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t2
  2  ( x int,
  3    y date,
  4    z varchar2(50)
  5  )
  6  /

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t2 values ( 1, sysdate, 'this
MIGHT work' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t2 values ( 1, sysdate, 'this
MIGHT work' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t2 values ( 2,
to_date('01012002','mmddyyyy'), 'this won''t work' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t2 values ( 3, sysdate, 'this is
too long, this will not work, too big' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace view v1
  2  as
  3  select rowid RID, x, y, z from t1;

View created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table error_table
  2  ( err_msg  varchar2(255),
  3    err_code number,
  4    rid      rowid
  5  )
  6  /

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace trigger v1_trigger
  2  instead of insert on v1
  3  declare
  4      l_msg error_table.err_msg%type;
  5      l_code number;
  6  begin
  7      insert into t1 ( x, y, z ) values ( :new.x, :new.y, :new.z );
  8  exception
  9      when others
 10      then
 11          l_msg := sqlerrm;
 12          l_code := sqlcode;
 13          insert into error_table
 14          ( err_msg, err_code, rid )
 15          values
 16          ( l_msg, l_code, :new.rid );
 17  end;
 18  /

Trigger created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into V1 (rid, x, y, z ) select rowid,
x, y, z from t2;

4 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t1;

         X Y         Z
---------- --------- -------------------------
         1 30-MAY-02 this MIGHT work

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from error_table;

ERR_MSG
---------------------------------------------------------------------------------
--------------------------------------------------
  ERR_CODE RID
---------- ------------------
ORA-00001: unique constraint (OPS$TKYTE.SYS_C007512) violated
        -1 AAAJqOAAHAAAHEKAAB

ORA-02290: check constraint (OPS$TKYTE.SYS_C007511) violated
     -2290 AAAJqOAAHAAAHEKAAC

ORA-01461: can bind a LONG value only for insert into a LONG column
     -1461 AAAJqOAAHAAAHEKAAD

It is practical to create MV's with large amounts of data if the data upon which
it is built is even larger.  If you are turning a 100 gigabyte table(s) into a
1gigabyte MV -- yes, that could be very very efficient.  And yes, you have to
index them and such as well.

The nice thing about MV's is that they are transparent to the application --
just like an INDEX would be.  You can add them and speed up (or slow down!)
applications without rewriting the application (just like an index!)
Review & Followup
Rating: 4
Useful "instead of trigger" trick May 30, 2002
Reviewer: Andrew from CA, USA

Followup:
You know what -- It needs an improvement though.  I thought about it a bit and
have decided that the view needs to be:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace view v1
  2  as
  3  select rowid RID, x, y, z from t2;

It needs to be on T2, not T1 as I had it.  The reason is due to datatype
conversions and such.

You can see one side effect of this in my prior example:

ORA-01461: can bind a LONG value only for insert into a LONG column
     -1461 AAAJqOAAHAAAHEKAAD

that error should be:
ORA-01401: inserted value too large for column

we get the other one cause of the initial datatype mismatch (we stuffed 50
characters in a 25 character "bind variable" -- it messed up with a different
error).

Also, if we do a more serious type conversion, from string to number, and it
fails -- the code above won't work.

So, the example is better as this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t1
  2  ( x int primary key,
  3    y date check ( y > to_date('01012002','mmddyyyy') ),
  4    z varchar2(25),
  5    a number
  6  )
  7  /

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t2
  2  ( x int,
  3    y date,
  4    z varchar2(50),
  5    a varchar2(10)
  6  )
  7  /

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t2 values ( 1, sysdate, 'this
might work', null );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t2 values ( 1, sysdate, 'this
might work', null );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t2 values ( 2,
to_date('01012002','mmddyyyy'), 'this won''t work', null );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t2 values ( 3, sysdate, 'this is
too long, this will not work, too big', null );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t2 values ( 4, sysdate, 'this
won''t work, number', 'ab' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace view v1
  2  as
  3  select rowid RID, x, y, z, a from t2;

View created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table error_table
  2  ( err_msg  varchar2(255),
  3    err_code number,
  4    rid      rowid
  5  )
  6  /

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace trigger v1_trigger
  2  instead of insert on v1
  3  declare
  4      l_msg error_table.err_msg%type;
  5      l_code number;
  6  begin
  7      insert into t1 ( x, y, z, a ) values ( :new.x, :new.y, :new.z, :new.a
);
  8  exception
  9      when others
 10      then
 11          l_msg := sqlerrm;
 12          l_code := sqlcode;
 13          insert into error_table
 14          ( err_msg, err_code, rid )
 15          values
 16          ( l_msg, l_code, :new.rid );
 17  end;
 18  /

Trigger created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into V1 (rid, x, y, z, a ) select
rowid, x, y, z, a from t2;

5 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from error_table;

ERR_MSG
---------------------------------------------------------------------------------
--------------------------------------------------
  ERR_CODE RID
---------- ------------------
ORA-00001: unique constraint (OPS$TKYTE.SYS_C007536) violated
        -1 AAAJrnAAHAAAHEKAAB

ORA-02290: check constraint (OPS$TKYTE.SYS_C007535) violated
     -2290 AAAJrnAAHAAAHEKAAC

ORA-01401: inserted value too large for column
     -1401 AAAJrnAAHAAAHEKAAD

ORA-01722: invalid number
     -1722 AAAJrnAAHAAAHEKAAE

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t1;

         X Y         Z                                  A
---------- --------- ------------------------- ----------
         1 30-MAY-02 this might work

by basing the view on the table we are selecting from -- we "know" the data
will "fit".  We'll convert it on the way into the other table T1 and catch the
errors there.

Rating: 5
ORA-01461: can bind a LONG value only for insert into a LONG column March 26, 2003
Reviewer: Pushparaj from NJ, USA
Tom,
Can you please explain on what conditions this ORA-01461 error will occur. I
have tested your test case and I see this error but I did not understand why
should it raise ORA-01461 instead of ORA-01401.

In our application also we get this error on a simple insert and the table does
not have any long column.
within a procedure an insert statement of similar to the
following is raising this error.

insert into tab1 (col1, col2, col3, col4, col5 )
values (scol1, scol2, ' ', ' ',scol5);
none of these varaibles declared as long datatype.

Can you please give us some more explanation on this error.

Followup:
it was an error in the bind to the trigger -- that is the kernel was binding the
:new stuff and that raised the error.

As I said in one of the followups:

...
You can see one side effect of this in my prior example:

ORA-01461: can bind a LONG value only for insert into a LONG column
     -1461 AAAJqOAAHAAAHEKAAD

that error should be:
ORA-01401: inserted value too large for column

we get the other one cause of the initial datatype mismatch (we stuffed 50
characters in a 25 character "bind variable" -- it messed up with a different
error).
......

My tables didn't have a long column either -- it was stuffing into a bind
variable something larger then the bind variable is supposed to be able to
handle.

You would have to give me a FULL test case.  Something I can run.  Then -- I can
explain (maybe ;)

March 26, 2003 khalid -- Thanks for the question regarding "Set autotrace on", version 8.1.7
You Asked
Hi Tom
When I enter the statement set autotrace on
i get the following error.
SP2-0618: Cannot find the Session Identifier.  Check PLUSTRACE role is enabled
SP2-0611: Error enabling STATISTICS report

How can solve it?
and we said...
set up autotrace...
http://asktom.oracle.com/~tkyte/article1/autotrace.html
has a howto.
Review & Followup
Rating: 5
Tom September 28, 2002
Reviewer: khalid from Yemen
Thank you

Rating: 4
correct your script February 08, 2003
Reviewer: A reader
cd $oracle_home/rdbms/admin
log into sqlplus as system
run SQL> @utlxplan
run SQL> create public public synonym plan_table for plan_table
run SQL> grant all on plan_table to public
exit sqlplus and cd $oracle_home/sqlplus/admin
log into sqlplus as SYS
run SQL> @plustrce
run SQL> grant plustrace to public

Tom
 Please correct create public public synonym plan_table for plan_table
Remove extra public

Secondly why cant I run plustrce connect as system, and why should I log out and
login again...

Lastly why cant I run all the above scripts connected as scott..

thanks

Followup:
removed the extra public....

you need to "log out and login" again because I want you to create the table as
system -- but the grants need to be run as "sys" or "sysdba"

because

a) scott cannot create a public synonym
b) scott cannot run plustrce.sql successfully.

Rating: 5
Why 'SYS' why not 'SYSTEM' February 09, 2003
Reviewer: Nasser
Tom

I understand that SCOTT does not have necessary privileges to create public
synonyms and create roles.

But system does have those abilities, so then why connect as SYS why not as
system and create the public synonym, and run the plustrce.sql script.

Followup:
Confusion on my part here.  system does the public synonym:

You ask " so then why connect as SYS why not as
system and create the public synonym,"

well, SYSTEM created the public synonym in my example.  system created the table
and hence created the public synonym and granted on it.

sys runs plustrce.sql because at the top of it (the script) it says:

-- DESCRIPTION
--   Creates a role with access to Dynamic Performance Tables
--   for the SQL*Plus SET AUTOTRACE ... STATISTICS command.
--   After this script has been run, each user requiring access to
--   the AUTOTRACE feature should be granted the PLUSTRACE role by
--   the DBA.
--
-- USAGE
--   sqlplus "/ as sysdba" @plustrce
--

in order to run this, you must have been granted select on v_$ views with the
ADMIN option or own the v_$ views.  SYS owns them, SYSTEM doesn't have the admin
option....  hence, SYSTEM cannot run this script.

Rating: 3
All the Statistics are not showing March 21, 2003
Reviewer: Arun from India
Tom,
  I Asked my DBA to give PLUSTRACE role to me. He also enabled that. But  i find
this.
SQL> select count(*) from dual;

  COUNT(*)
----------
         1

Statistics
----------------------------------------------------------
          0  recursive calls
          1  rows processed

SQL>

Only  a part of info is shown.  He said, on checking he got all the other
information also.
What would have gone wrong.

Thanks,
Arun

Followup:
I have never before seen that in my life.

if you trace autotrace, we run a query:

SELECT STATISTIC# S, NAME
FROM
 SYS.V_$STATNAME WHERE NAME IN ('recursive calls','db block gets','consistent
  gets','physical reads','redo size','bytes sent via SQL*Net to client',
  'bytes received via SQL*Net from client','SQL*Net roundtrips to/from
  client','sorts (memory)','sorts (disk)') ORDER BY S

that gets the statistic numbers for each statistic of interest.  That is used to
drive this report.

I cannot imagine what caused your output -- you'll have to contact support if it
is happening like that.

Rating: 3
I think this is the reason March 21, 2003
Reviewer: Arun from India
Tom,
    When i log into oracle, on sqlplus from my home directory only i get this
problem. When i tried from some other directory it works fine.
    My DBA would be cursing me for all the trouble i gave him.
    Still i donno whats the reason...

Regards,
Arun

Rating: 3
Similar behaviour for TKPROF March 25, 2003
Reviewer: Matt from Australia
I have been receiving some trace files from a customer site. My understanding
was that these trace files are standalone - there is sufficient information in
them to be tkprof'ed on any machine.

However, when I use for example 'tkprof   ora_40714.trc outputfile=out.prf' and
look at the resulting file, I do not see any 'Row Source Operation' output. eg:

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  UPDATE DAEMON
      5   TABLE ACCESS FULL DAEMON

Everything else in the file seemed fine - no errors.

So, can you please clarify whether I should see this output?

BTW, I just received a tkprof'ed file from the same customer. Same problem.

Does the tkprof utility have any reason to use the PLAN_TABLE (for the
parameters I've described)? I can't  find anything described in the doc to this
effect. I couldn't find a description of any required setup either.

Is this a known problem, or is this again, a good reason to have a chat with
support?

Thanks for any help.

Followup:
they did not close their session before grabbing the trace file.

The STAT records are recorded only when the cursor is closed, most cursors are
wisely not closed until the session disconnects.

You need to have them

o enable tkprof
o run
o END THE SESSION

then grab the trace file.

It is normal, expected behavior.  You can observe it yourself with sqlplus --
trace, run a query, host out and run tkprof. Then EXIT from sqlplus and run
tkprof on that same trace file.

Rating: 3
The session should have been closed. March 26, 2003
Reviewer: Matt from Australia
The trace was generated by a software module that, when started, runs in it's
own session. The first thing that the module does is an alter sesssion to switch
on SQL_TRACE. So, when the module is completed (this is viewable via the UI) the
trace file is then retrieved.

I have tested this (described above) many times in our development environments
and have never seen this before.

I will double check with the customer that the task had indeed completed before
the retreival of the trace, but as teh job was scheduled to run overnight and
the trace was retreived in the morning, I am not hopefull.

There is definately no interface between PLAN_TABLE and tkprof for the previous
description? It may be possible the there is an eariler pre-upgrade version of
the PLAN_TABLE in the site test environment.

Followup:
there is no connection to the PLAN table.

that information comes from the STAT records placed into the trace file.

March 26, 2003 Thomas -- Thanks for the question regarding "Backing up an Oracle db", version 8.1.7
You Asked
Hi Tom,

Could you please let me know of the different methods and maybe a simple code
example on how to backup a database to a local harddrive ?
I am completeky new to Oracle and have only heard of RMAN utiliy but that can
cause the backup to be fragmentated ?? Not up to date if transactions are been
processed....
I'd appreciate any help that can be volunteered...
and we said...
how important is this data to you?

if the loss of this data would cause you pain you need to

a) stop
b) read lots more
c) test alot

it would be highly recommended even to get a mentor -- have someone who has done
this work with you, teach you.

At the very least, if you don't have the time for that, have them on contract so
that they

a) setup the backup environment
b) are on call to restore when (not if, when) needed due to a failure.

RMAN does not "fragment" backups.  It can chunk them up into manageable pieces.

RMAN does not "lose transactions" -- as long as the database is in archive log
mode anyway.

Consider these books:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/index.htm
the backup and recovery section.  You need to read "Oracle8i Backup and Recovery
Guide" at least.

This is a big topic -- bigger then a breadbox.  There is no concise 500 word
answer to this.  You need to be in archive log mode.  you need to have sized
your system to accomodate your archives.  You need to have the jobs setup to get
the archives OFF your computer and onto safe storage.  You need to have your
last 3 backups available.  You need to have some off site.  You need to
understand the relationship between control files, redo logs, archived logs,
datafiles, tempfiles.  You cannot backup to a local hard drive (eggs, one basket
-- metaphor coming into mind)...
Review & Followup
Rating: 2
Steady on old chap March 26, 2003
Reviewer: Peter from Cumbria, England
The start of your answer makes perfect sense and then you go and get a bit
carried away with yourself stating things that may not apply.

Bigger than a breadbox indeed. So why go on to say that you need to be in
archive mode? Not necessarily. Last 3 backups available - why? Off site -
depends. Why not backup to a local hard drive if you are aware of the risks and
shortcomings of doing it and can live with the consequences. I agree - read lots
more, test until blue in the face, get help if you are at all unsure and the
data is important to you.

Followup:
steady on yourself.

If you make statements like:

<quote>
Not up to date if transactions are been
processed....
</quote>

and are NOT in archivelog mode -- you lose.  You will (not might, will) lsoe
committed transactions.  Period.  Transactional systems must be in archive log
mode.  There, I've said it -- I'll stand by it.

Last 3 backups available -- yes, 3 being sort of the minimum.  Or are you the
person that leaves a single tape in the machine and copies over it again and
again.  What happens when (not if, when) the disk fails during the backup?  Say
it never happens.  I've got people you need to talk to.  So, you need at least 2
due to that but -- I would say 3 just to be safe.  Data is expensive --
priceless.  Tape -- cheap.  Suppose the tape goes bad? Read Errors?  or you
backed up a physical corruption you did not detect right away.  The further back
you can go -- the better.

Off site -- absolutely.  You would be foolish not to.  Can you spell "fire".

If you are aware of the risks and shortcoming of backing up to the local hard
drive, well -- why are you doing backups at all??  Might as well just raid it
and mirror it and hope you are employeed elsewhere when it fails eh.

I don't think I overstated this at all.  Understated it maybe.

A DBA can do everything else in the world wrong -- EVERYTHING.  We can fix that,
we can resolve those issues.  But if they mess up backup and recovery -- forget
about it.  You are hosed.

Yes, I've seen the result of a fire.
Yes, I've seen both sides of a mirror fail within 3 minutes of eachother.
Yes, I've seen media failure occur in the middle of a backup -- rendering the
database toast.

Rating: 3
losing transactions? March 26, 2003
Reviewer: neil from salt lake city, ut
RMAN does not "lose transactions" -- as long as the database is in archive log
mode anyway.

I've been wondering, after I do a system backup with RMAN, i  then run " backup
archivelog all format $ARCHIVELOGDIR/%d_%t_%s_p.arc';"

Will this backup archivelogs generated DURING the backup archivelog command?  I
was always curious as to how this works.

Followup:
you would want to swith log files and make sure they were all archived -- then
back them up.  or, use the archivelog option with the backup database:
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96566/rcmconc1.htm#465613

Rating: 2
A vital job that's too hard March 26, 2003
Reviewer: David Penington from Melbourne, Australia
Oracle database backup is too complex and requires too much expertise - it makes
it difficult to sell and deploy turnkey solutions to customers who are not
already Oracle sites. The documentation is complex and confusing, and I cannot
find any examples of simple backups and archive log management of simple systems
with small databases. A simple site doesn't have a second machine running an
Oracle RMAN catalog. A simple backup probably does backup to a local disk, and
then includes the backup file(s) in a network or tape backup.  If Oracle
provided a guaranteed recipe for backing up a small enough database and managing
the archive logs, people like the original questioner would be able to get on
with using Oracle, and defer major backup learning until they have large amounts
of data, and have had time to understand Oracle better.  Currently Standard
Edition can be licensed and installed for about $US2500, but it costs about that
much again just to back it up, and much more if people follow your advice and
read, test etc..

Followup:
OEM -- push a button.  done.  People who like gui's can do that can't they?

why does the size of the data have anything to do with it?  either you care
about the data and will back it up properly or -- well -- you don't?

Rating: 5
Couple of FUPs to reviews above March 26, 2003
Reviewer: Jim from Australia
Followup to "Steady on"
I've worked all around the planet for 15 years using Oracle
and I can tell you the Tom is spot on... Getting the backup
and recovery right is the most important thing if you value
your data. So forget "steady on", "cut loose" Tom!

Follow to "vital but too hard"
There is PLENTY of info on how to setup backup and
recovery procedures. I'm sure theres a Dymocks or a McGills
Technical bookstore in Melbourne that will have
The Oracle Press backup and recovery guide between it and the Oracle doco you
should be able to design a robust
backup and recovery plan...
I find the the big thing Oracle offers is it's flexability
I can design a backup plan tailored for the database
I'm working on.

Rating: 5
Who said it is difficult. March 28, 2003
Reviewer: A reader
I`ve been playing with oracle and rman for a while and can say that it takes one
command to backup DB and Archived Log. If you just want to play with RMAN it is
so simple for simple stuff and so flexible for serious stuff.

March 27, 2003 Adrian -- Thanks for the question regarding "9.2 associative arrays and forall frustration...", version 9.2.0
You Asked
Tom

A couple of "when" questions for you, the first of them highly theoretical...

a) Associative Arrays
---------------------
It's good to have index-by PL/SQL tables indexed by varchar2 at last. They will
be of great application to lookup tables, as were the index-by binary_integer
for lookup tables with numeric PK columns. However, we must loop through these
lookup tables to load the arrays. Wouldn't it be nice if we could do something
like the following (pseudo-code)?

SELECT pk_col, description_col
BULK COLLECT INTO l_lookup
USING pk_col AS INDEX
FROM lookup_table;

...where l_lookup would be a table of description_col%type indexed by
pk_col%type...

I don't know if this is possible or if anything like it is being developed for
later release? It looks good in pseudo-code ;)

b) FORALL with table of records
-------------------------------
As of 9.2 we still can't reference individual elements of a table of records in
FORALL. I know we can insert an entire record which is good and pdate using an
entire record (but this is bad as we would update the primary keys and we need
to limit the updates to their respective target rows using extra arrays for the
FORALL WHERE clause). However, it would be nice to be able to do the following:-

FORALL i IN myCompositeArray.FIRST .. myCompositeArray.LAST
   UPDATE my_table
   SET    my_col = myCompositeArray(i).my_col
   WHERE  my_pk  = myCompositeArray(i).my_pk;

Do you have any idea if and when this will be available? I use the TABLE method
at the moment (and TABLE(CAST( on 8i)...

Many thanks

Adrian
and we said...
a) it would be syntatically nice, yes, but until then -- see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3239385496862
it shows how to do this in a two step

b) you would have to use a record of arrays instead of an array of records.  It
has to do with how the data is physically organized (row major or column major)
in the array.  Array operations work on contigous arrays.

For example:

ops$tkyte@ORA920.US.ORACLE.COM> create table t ( x int, y int );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> declare
  2      type foo is record
  3      ( x int,
  4        y int
  5      );
  6
  7      type foo_array is table of foo index by binary_integer;
  8
  9      l_data foo_array;
 10  begin
 11      for i in 1 .. 2
 12      loop
 13          l_data(i).x := i;
 14          l_data(i).y := i;
 15      end loop;
 16
 17      forall i in 1 .. 2
 18          insert into t values l_data(i);
 19  end;
 20  /

PL/SQL procedure successfully completed.

so, in some cases, we can use arrays of records -- when the interface is
expecting a record at a time.... (using new 9ir2 feature to insert/update using
RECORDS

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> declare
  2      type foo is record
  3      ( x int,
  4        y int
  5      );
  6
  7      type foo_array is table of foo index by binary_integer;
  8
  9      l_data foo_array;
 10  begin
 11      for i in 1 .. 2
 12      loop
 13          l_data(i).x := i;
 14          l_data(i).y := i;
 15      end loop;
 16
 17      forall i in 1 .. 2
 18          update t set y = l_data(i).y where x = l_data(i).x;
 19  end;
 20  /
        update t set y = l_data(i).y where x = l_data(i).x;
                         *
ERROR at line 18:
ORA-06550: line 18, column 26:
PLS-00436: implementation restriction: cannot reference fields of BULK In-BIND
table of records
ORA-06550: line 18, column 35:
PL/SQL: ORA-00904: : invalid identifier
ORA-06550: line 18, column 9:
PL/SQL: SQL Statement ignored

but because of the row major organization of the array here -- the elements of
y are not contigous, hence we cannot use the array on Y or X..

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> declare
  2      type intArray is table of int index by binary_integer;
  3      type foo is record
  4      ( x intArray,
  5        y intArray
  6      );
  7
  8      l_data foo;
  9  begin
 10      for i in 1 .. 2
 11      loop
 12          l_data.x(i) := i;
 13          l_data.y(i) := i;
 14      end loop;
 15
 16      forall i in 1 .. 2
 17          update t set y = l_data.y(i) where x = l_data.x(i);
 18  end;
 19  /

PL/SQL procedure successfully completed.

but here we can..
Review & Followup
Rating: 4
Some reservations on record updates August 14, 2002
Reviewer: Adrian Billington from UK
Tom

Thanks for workaround you pointed me to in a). Hadn't thought of that - I would
have just looped through the target table and assigned the associative array
directly row by row.

b) As far as using records of arrays goes, nothing has changed there. But as 9.2
now supports bulk collecting into an array of records it would have been useful
to be able to forall using them as well?

Being able to insert using records in 9.2 is very good also, but the update
needs a couple of major caveats mentioning.

First, you will update the PK if you have one (accepted as bad practice
generally, especially when you have foreign keys).

Second, unless I'm missing a feature, the update using an entire record will not
be targetted unless you maintain a separate array of the PK values to ensure
each update only hits its own record:-

For example:-

   FORALL i in array.FIRST .. array.LAST
      UPDATE my_table
      SET ROW = array(i);

   ...will update every row in the table with every array record.

Whereas:-

   FORALL i in array.FIRST .. array.LAST
      UPDATE my_table
      SET ROW = array(i)
      WHERE my_pk_column = array(i).pk_col;

...is not supported as identified in my original question.

So we have to:-

   FORALL i in array.FIRST .. array.LAST
      UPDATE my_table
      SET ROW = array(i)
      WHERE my_pk_column = separate_array_of_pk(i);

Am I missing something? Or is the update using record not as good as it could
be?

Regards

Adrian

Followup:
your observations on the UPDATE set row are mostly correct, yes.

Rating: 4
table of record containing a table November 19, 2002
Reviewer: Paul Ukken from USA
Hi Tom,

i have a table of pl/sql records that i need pass back to java . But The pl/sql
record itself needs to have a table of number.
i tried the following

declare
TYPE prev_membr_id IS TABLE of NUMBER INDEX BY BINARY_INTEGER;
TYPE prgm_rec_type IS RECORD
(
    program_id           NUMBER
   ,l_prev_membr_id      prev_membr_id
);
TYPE programDtails IS TABLE OF prgm_rec_type INDEX BY BINARY_INTEGER;
x_dtail_tbl programDtails;
begin
null;
end;

but i get the following error
PLS-00507: a PLSQL Table may not contain a table or a record with composite
fields

How can i achieve this in pl/sql?. i heard that theres some new feature in
Oracle Collections to achieve this. Also is there any specific way i need to
handle this in java, if i am using the collections

Followup:
You will never be passing a TABLE OF RECORDS back from PLSQL to any other
language.  Never.

If you use collection types (by defining a new SQL type) this becomes as easy as
returning a REF CURSOR (result set) back to the client.  See

see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:246014735810
for an example of what I mean and if you've never used a ref cursor see
http://asktom.oracle.com/~tkyte/ResultSets/index.html

Rating: 4
Bulk collect on record of arrays? March 26, 2003
Reviewer: Kashif from Houston, TX
Hi Tom,

Can a record of arrays be bulk collected into? I'm trying that in my procedure
but I keep getting a table is of wrong type error. Just need to reconfirm.
Thanks.

Kashif

Followup:
you can bulk collect into arrays.

You can bulk collect into arrays of records in 9iR2.

You can have a record that is filled with arrays and bulk collect into each
array.

not having a small, concise, simple example (and version, etc) -- cannot really
comment further.

Rating: 5
Actually... March 27, 2003
Reviewer: Kashif from Houston, TX
Thanks Tom, your response indicated that I had to populate the array elements of
the record of arrays individually, as opposed to all in one shot. For example:

"declare
cursor c1 is select col1, col2
from table1;
charArray is table of varchar2 (2000) index by binary_integer;
type recType is record
(array1 charArray,
 array2 charArray);
l_tabrec recType;

--instead of..
begin
open c1;
loop
fetch c1 bulk collect into l_tabrec;
end loop;

-- I am now doing...
open c1;
loop
fetch c1 bulk collect into l_tabrec.array1, l_tabrec.array2;
end loop;

end;"

I guess I hadn't read the docs close enough, bit of a rush. Thanks for the tip.

Kashif

March 27, 2003 Chacravarthi -- Thanks for the question regarding "General about Database", version 8.0
You Asked
Dear Sir,
Well and wish the same from you.I have some questions for you.They are
as follows
1)Are Null,User and sysdate  Pseudocolumns?
2)After tables makeup a cluster,is it possible to break their cluster-
ship and restore them as previous independent tables?
3)If a table has some 10 columns, how to retrieve the first 5 column
values without specifying their column names?Is this possible in sql or we have
to go for pl/sql?
4)What do you mean by hashing function?How is it used in Hash data
clusters and hash joins?
5)Is oracle an acronym?
6)How long you work daily?What are your motives and ambitions?
Thanks in advance
Yours sincerely,
R.Chacravarthi.
and we said...
1) no, no and no.
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a85397/sql_elem.htm#27457
NULL is null -- it is the absence of a value.
USER and SYSDATE are functions

2) No, the cluster is the segment, you operate on the cluster.  You would have
to use create table as select to copy the data, drop the clustered table, rename
the new table.

3) you have to specify their column names in ANY environment.  If you have 10
columns, and you want 5, you list them.

SELECT * is a hugely bad practice except for casual ad-hoc queries in sqlplus.
Sounds like you are starting out so get in the habit of selecting by name from
day one.

4)
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76992/optimops.htm#36362
hashing is a very common computer science operation.  A hash function takes
arbitrary inputs and maps it to a discrete set of values.  A hash table is
commonly used to store lookup data.  You would recieve an input (a key), you
hash it to a number between 0 and <size of hash table>.  The values associated
with the key are found in the hash table at that slot.

5) No, it was the project name of a project at the CIA in the late 70's.  That
was the genesis of Oracle

6) my motives -- that sounds sinister doesn't it...
Review & Followup
Rating: 4
One more general question March 25, 2003
Reviewer: Alla Gribov from Boston, MA USA
Hello Tom;

You say "SELECT * is a hugely bad practice except for casual ad-hoc queries in
sqlplus."

And I completely agree with you, except that I got puzzled couple of days ago,
trying to answer the following question.

Let's say that I have a following declaration in the package spec:

type gc_table is ref cursor return table_A%rowtype;

procedure get_data
(v_param    in   table_A.column_1%type,
 v_data_out out  gc_table);

and here is the procedure itself:

procedure get_data
(v_param    in     table_A.column_1%type,
 v_data_out out    gc_table)
as
begin
  open v_data_out for
     select * from table_A
     where column_1 = v_param;

end;

So, which is better in this case: "select *" or "select column_2, column_3 ...".
My concern is that if somebody adds a column to the table_A, then if I have a
select *, I would not have to change anything and my procedure would still work,
otherwise, every time there is a new column, somebody should remember to change
this particular procedure.

At the same time, may be it's not such a good idea to base a cursor on a
rowtype?

What do you say?

Thanks

Followup:
man, got bit by ROT myself.

PLSQL is the exception to the rule -- many times in PLSQL it is very safe to do
this.  This is a good case in point.  Here, unlike java, vb, C, etc -- if
table_a is modified -- this plsql code will "fix itself", here using select *
would be prefered.

But it is an unusual case.

You should not always code:

  for x in ( select * from t ... )
  loop

you should code

  for x in ( select c1, c3, c5, c7 from t ... )

you should select EXACTLY the columns you need for performance, best memory use,
and readability.

In your case -- you are returning the entire row.  Here, it would actually be
counter productive to list the columns -- for if a new column is added or a
column is removed - -the code as you have it would "correct itself".  If you
listed the columns out in the select, you would have to go back and fix it.

Rating: 3
March 25, 2003
Reviewer: A reader
It seems Mr.Chacravarthi is taking Toms interview :-)

This site is exclusively for Oracle questions and we should be thankful to Tom
for his wonderful help, rather than getting personal.

Rating: 5
cool March 26, 2003
Reviewer: hrishy from PA
Hi Tom

Thought that oracle stood for Oakridge Automated computing and logical engine ..

regards
Hrishy

Rating: 2
Satisfactory March 26, 2003
Reviewer: R.Chacravarthi from Chennai,India
Thanks for your response.For the first question I am in dilemma whom (author) to
follow.In Oracle9i-The complete
Reference by Kevin M.Loney,he says Null,User and sysdate arePseudocolumns.You
say as no.I don't know what to do
Yours sincerely,
R.Chacravarthi

Followup:
File errata with Oracle Press -- that is what I would do.

It is obviously *wrong* if that is what they have written in there.

psuedo columns are things that can only come from a sql query.

Rownum == psuedo column, gotta select it.  I cannot:

begin
   x := rownum;
end;

USER -- a function (it is after all listed in the function section).  I can:

begin
   x := USER;
end;

Rating: 5
Ah yes - the complete reference....... March 26, 2003
Reviewer: Steve from UK
Which I and my colleagues call the INCOMPLETE Complete reference because it
never seems to have the information we need!

Rating: 2
Hashing March 26, 2003
Reviewer: David from AL
"hashing is a very common computer science operation.  A hash function takes
arbitrary inputs and maps it to a discrete set of values.  A hash table is
commonly used to store lookup data.  You would recieve an input (a key), you
hash it to a number between 0 and <size of hash table>.  The values associated
with the key are found in the hash table at that slot."

Tom, could you please point me to something that elaborates on this?  I looked
at
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76992/optimops.htm#36362,
but it just talks about the process of a hash join.  I'm interested in
learning about the concept of 'hashing' because you mention that it's used
commonly in lookup functions.

Thanks!

Followup:
http://whatis.techtarget.com/definition/0,289893,sid9_gci212230,00.html
you'll really need to get a book on algorithms or something to go into more
depth.

      Rating: 3
For David -
      from AL  March 26, 2003
Reviewer:  A Reader
       from UK
A few starting points that seem reasonable - from a search or two of the web.
I'm no expert but they tied-up with my memory from computer-science lectures !

http://mathworld.wolfram.com/HashFunction.html
http://whatis.techtarget.com/headerWOTD/0,,sid9,00.html
http://www.cs.albany.edu/~sdc/CSI310/Main/hand12.pdf

      Rating: 4
Thanks to the
      Reader from UK  March 26, 2003
Reviewer:
      David  from David, Al
I read your links.

I'm going home now to get an aspirin.

      Rating: 3
My excuses
      Tom..  March 27, 2003
Reviewer:
      A reader
Didn't know that my reaction of yesterday, about the meaning of O.R.A.C.L.E.,
would be taken so serious as to remove my posting.

It was only a joke.
And one i found on an oracle-site, even.

          Followup:

I remove anything that has questionable language in it...

      Rating: 5
So
      ?  March 27, 2003
Reviewer:  Curious guy

So, what is the meaning of O.R.A.C.L.E. ?

What does Tom mean by "questionable language" ?

          Followup:

don't bother, if it comes up again, I'll just take it down again.

Questionable language includes words you would not use in gracious company, or
in front of your parents perhaps...

      Rating: 3
Nice to hear
      from you  March 27, 2003
Reviewer:
      R.Chacravarthi  from Chennai,India
You say as book error and Do you know how many people use it
as a Bible?I hope you will contact the necessary authority
to get that error corrected(atleast in subsequent editions).
Do you know the mail-id of Kevin Loney?
Thanks
Yours sincerely,
R.Chacravarthi

      Rating:
      5
So  March 27, 2003
Reviewer:
      A reader
Well, the only joke I know about the Oracle name is: read ORACLE backwards...

EL CARO (el=the and caro=expensive in spanish)

It does not seem offensive to me.

      Rating: 3
Ah, then again
      - excuses :)  March 27, 2003
Reviewer:  Phae
       from the Netherlands.
I wrote:
--------------
Didn't know that my reaction of yesterday, about the meaning of O.R.A.C.L.E.,
would be taken so serious as to remove my posting.

It was only a joke.
And one i found on an oracle-site, even.
--------------
Tom wrote:
--------------
Followup:
I remove anything that has questionable language in it...
--------------

Ofcourse, as being from the Netherlands, I pretty fast forget that a lot of
words we use in our daily conversations are in fact questionable language in the
native tongue. Just like the S- and F-words.

My excuses, did not think when I posted it.

March 27, 2003 -- Thanks for the question regarding "String
      tokenize", version 9i

      You Asked
I have a comma delimited string containing column names.

Ex: strTemp = 'col1,col2,col3,col4'

i want to break up strTemp and assign each column name to a variable as follows.
str1:=col1
str2:=col2
str3:=col3
str4:=col4

If i was using Java, i would use the StringTokenizer.
What would be the easiest way to do this in PL/SQL.

Thank you.
Ramesh

      and we said...
http://asktom.oracle.com/~tkyte/SkipCols/index.html
has a "sophisticated" delimited package (it obeys an enclosure, like SQLLDR
would if you need).

But very simply:

ops$tkyte@ORA815.US.ORACLE.COM> create or replace type myTableType as table of
number
  2  /

Type created.

ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM> create or replace function str2tbl( p_str in
varchar2 ) return myTableType
  2  as
  3      l_str   long default p_str || ',';
  4      l_n        number;
  5      l_data    myTableType := myTabletype();
  6  begin
  7      loop
  8          l_n := instr( l_str, ',' );
  9          exit when (nvl(l_n,0) = 0);
 10          l_data.extend;
 11          l_data( l_data.count ) := ltrim(rtrim(substr(l_str,1,l_n-1)));
 12          l_str := substr( l_str, l_n+1 );
 13      end loop;
 14      return l_data;
 15  end;
 16  /

takes a string delimited by commas and returns an array so str(1), str(2)

All it takes in instr and substr -- parsing is pretty easy in plsql...
March 27, 2003 --
      Thanks for the question regarding "Recursion", version 920

      You Asked
Hi Tom

Objective
Im trying to write a procedure for truncating table or matching tables that the
user pass and also truncate the child tables.

You cannot truncate a table if it has child tables referencing it. So you need
to disable their FKs & then truncate them & finally Truncate the parent table.
But the child tables can also be refrenced by other child tables & so on. SO we
have a recursive situation here.

My procedure results in an error as follows:

Create or Replace Procedure rec(tabname in user_tables.table_name%Type, tier
number:=1) is
-- Get the child constraint information
Cursor curPK(t in Varchar2) is select table_name ,constraint_name from
user_constraints
where table_name =t and constraint_type='P';

CURSOR curFK(pcPKName IN VARCHAR2) IS
SELECT constraint_name, table_name FROM user_constraints  WHERE
r_constraint_name = pcPKName;
STMT VARCHAR2(255);
Begin

For pk in CurPK(tabname)  loop
 For fk in CurFK(pk.constraint_name) loop
If tier =1 Then
      STMT:= 'Alter table ' || FK.table_name || ' Disable Constraint '||
FK.constraint_name ;
      Execute immediate stmt;
      STMT:='Truncate Table ' ||FK.table_name;
       Execute immediate stmt;
End if;

For ee in CurPk(FK.table_name) loop
    STMT:= 'Alter table ' || ee.table_name || ' Disable Constraint '||
ee.constraint_name ;
    Execute immediate stmt;
    STMT:='Truncate Table ' ||ee.table_name;
        Execute immediate stmt;

REC(ee.table_name, tier+1);

End Loop;
End Loop;
End loop;
End;
/

*
ERROR at line 1:
ORA-06511: PL/SQL: cursor already open
ORA-06512: at "TEST.P2", line 2
ORA-06512: at "TEST.P2", line 14
ORA-06512: at line 1

Could you please help me in figuring this out?
Thanks

      and we said...
Here is the procedure I have for doing this:

ops$tkyte@ORA920.US.ORACLE.COM> create table p1 ( x int primary key );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM> create table p2 ( x int primary key );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create table c1 ( x int primary key, y
references p1, z references p2 );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM> create table c2 ( x int primary key, a
references c1, b references p1 );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM> create table c3 ( x int primary key, m
references c2, n references p2, o references c3 );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create table c4 ( x int primary key, y
references p2 );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM> create table c5 ( x int primary key, z
references c4 );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM> create table c6 ( x int primary key, a
references c5, b references p1 );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace package util
  2  as
  3      procedure recursive_truncate( p_tname in varchar2, p_level in number
default 1 );
  4  end;
  5  /

Package created.

ops$tkyte@ORA920.US.ORACLE.COM> create or replace package body util
  2  as
  3
  4  type stack is table of varchar2(32000) index by binary_integer;
  5  g_stack stack;
  6  g_empty_stack stack;
  7
  8  procedure do_ddl( p_stmt in long, p_level in number )
  9  is
 10  begin
 11      dbms_output.put_line( rpad(chr(9), p_level-1, chr(9)) || p_stmt );
 12      execute immediate p_stmt;
 13  end do_ddl;
 14
 15  procedure recursive_truncate( p_tname in varchar2, p_level in number
default 1 )
 16  as
 17  begin
 18      if ( p_level = 1 )
 19      then
 20          g_stack := g_empty_stack;
 21      end if;
 22
 23      dbms_output.put_line( rpad(chr(9), p_level-1, chr(9)) || 'Processing
Table ' || p_tname );
 24      for x in ( select *
 25                   from user_constraints
 26                  where constraint_type = 'R'
 27                    and table_name <> p_tname
 28                    and r_constraint_name in ( select constraint_name
 29                                                 from user_constraints
 30                                                where table_name = p_tname
 31                                                  and constraint_type in (
'P', 'U' )
 32                                              )
 33               )
 34      loop
 35          do_ddl( 'alter table ' || x.table_name || ' disable constraint ' ||
x.constraint_name, p_level );
 36          g_stack( g_stack.count+1 ) := 'alter table ' || x.table_name || '
enable constraint ' || x.constraint_name;
 37          recursive_truncate( x.table_name, p_level+1 );
 38          do_ddl( 'truncate table ' || x.table_name, p_level );
 39      end loop;
 40      if ( p_level = 1 )
 41      then
 42          for i in 1 .. g_stack.count
 43          loop
 44              do_ddl( g_stack(i), p_level );
 45          end loop;
 46      end if;
 47  end recursive_truncate;
 48
 49  end util;
 50  /

Package body created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> exec util.recursive_truncate( 'P1' );
Processing Table P1
alter table C1 disable constraint SYS_C003158
        Processing Table C1
        alter table C2 disable constraint SYS_C003161
                Processing Table C2
                alter table C3 disable constraint SYS_C003164
                        Processing Table C3
                truncate table C3
        truncate table C2
truncate table C1
alter table C2 disable constraint SYS_C003162
        Processing Table C2
        alter table C3 disable constraint SYS_C003164
                Processing Table C3
        truncate table C3
truncate table C2
alter table C6 disable constraint SYS_C003173
        Processing Table C6
truncate table C6
alter table C1 enable constraint SYS_C003158
alter table C2 enable constraint SYS_C003161
alter table C3 enable constraint SYS_C003164
alter table C2 enable constraint SYS_C003162
alter table C3 enable constraint SYS_C003164
alter table C6 enable constraint SYS_C003173

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> exec util.recursive_truncate( 'P2' );
Processing Table P2
alter table C1 disable constraint SYS_C003159
        Processing Table C1
        alter table C2 disable constraint SYS_C003161
                Processing Table C2
                alter table C3 disable constraint SYS_C003164
                        Processing Table C3
                truncate table C3
        truncate table C2
truncate table C1
alter table C3 disable constraint SYS_C003165
        Processing Table C3
truncate table C3
alter table C4 disable constraint SYS_C003168
        Processing Table C4
        alter table C5 disable constraint SYS_C003170
                Processing Table C5
                alter table C6 disable constraint SYS_C003172
                        Processing Table C6
                truncate table C6
        truncate table C5
truncate table C4
alter table C1 enable constraint SYS_C003159
alter table C2 enable constraint SYS_C003161
alter table C3 enable constraint SYS_C003164
alter table C3 enable constraint SYS_C003165
alter table C4 enable constraint SYS_C003168
alter table C5 enable constraint SYS_C003170
alter table C6 enable constraint SYS_C003172

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM>
Review &
      Followup

      Rating: 5
Most useful -
      but...  October 12, 2002
Reviewer:  andrew
       from ca, usa
Merits of the following structure aside (although I have encountered one of
these) the code can be made to break.

create table x1 (col1 number, col_fk number);
alter table x1 add constraint x1_pk primary key (col1);

create table x2 (col1 number, col_fk number);
alter table x2 add constraint x2_pk primary key (col1);

alter table x1 add constraint x1_x2_fk foreign key (col_fk)references x2(col1);
alter table x2 add constraint x2_x1_fk foreign key (col_fk)references x1(col1);

insert into x1 values (1, null);
insert into x2 values (1, null);
insert into x1 values (2, 1);
insert into x2 values (2, 1);

begin
util.recursive_truncate( 'X1' );
end;

Processing Table X1
alter table X2 disable constraint X2_X1_FK
    Processing Table X2
    alter table X1 disable constraint X1_X2_FK
        Processing Table X1
        alter table X2 disable constraint X2_X1_FK
            Processing Table X2
            alter table X1 disable constraint X1_X2_FK
                Processing Table X1
                alter table X2 disable constraint X2_X1_FK
                    Processing Table X2
                    alter table X1 disable constraint X1_X2_FK
                        Processing Table X1
                        alter table X2 disable constraint X2_X1_FK
                            Processing Table X2
                            ...

ORA-20000: ORU-10028: line length overflow, limit of 255 bytes per line
ORA-06512: at "SYS.DBMS_OUTPUT", line 84
ORA-06512: at "SYS.DBMS_OUTPUT", line 58

          Followup:

cool -- now, whats the fix?

(and do you really use mutually referencing tables like that -- they are quite
painful to actually insert into!)

      Rating: 5
Add some code,
      maybe something like this?  October 12, 2002

Reviewer:  Jan van Mourik  from Houston, TX

package body util
  as

  type stack is table of varchar2(32000) index by binary_integer;
  g_stack       stack;
  g_empty_stack stack;
  type tabs is table of varchar2(30) index by binary_integer;
  g_tab_to_truncate   tabs;
  g_empty_tabs        tabs;

  procedure do_ddl( p_stmt in long, p_level in number )
  is
  begin
      dbms_output.put_line( rpad(chr(9), p_level-1, chr(9)) || p_stmt );
      execute immediate p_stmt;
  end do_ddl;

  procedure recursive_truncate( p_tname in varchar2, p_level in number default 1
)
  as
      e_already_truncated  exception;
  begin
      if ( p_level = 1 )
      then
          g_stack           := g_empty_stack;
          g_tab_to_truncate := g_empty_tabs;
      end if;

      dbms_output.put_line( rpad(chr(9), p_level-1, chr(9)) || 'Processing Table
' || p_tname );
      g_tab_to_truncate( g_tab_to_truncate.count+1 ) := p_tname;

      for x in ( select *
                   from user_constraints
                  where constraint_type = 'R'
                    and table_name <> p_tname
                    and r_constraint_name in ( select constraint_name
                                                 from user_constraints
                                                where table_name = p_tname
                                                  and constraint_type in ('P',
'U')
                                              )
               )
      loop
         begin
            do_ddl( 'alter table ' || x.table_name || ' disable constraint ' ||
x.constraint_name, p_level );
            g_stack( g_stack.count+1 ) := 'alter table ' || x.table_name || '
enable constraint ' || x.constraint_name;

            for i in reverse 1 .. g_tab_to_truncate.count
            loop
                if g_tab_to_truncate(i) = x.table_name
                then
                   raise e_already_truncated;
                end if;
            end loop;

            recursive_truncate( x.table_name, p_level+1 );
            do_ddl( 'truncate table ' || x.table_name, p_level );
         exception
         when e_already_truncated
         then
              dbms_output.put_line('Table '|| x.table_name ||' already
truncated, skipping...');
         end;
      end loop;
      if ( p_level = 1 )
      then
          do_ddl( 'truncate table ' || p_tname, p_level );
          for i in 1 .. g_stack.count
          loop
              do_ddl( g_stack(i), p_level );
          end loop;
      end if;
  end recursive_truncate;

end util;
/

exec util2.recursive_truncate( 'X1' );

Processing Table X1
alter table X2 disable constraint X2_X1_FK
    Processing Table X2
    alter table X1 disable constraint X1_X2_FK
Table X1 already truncated, skipping...
truncate table X2
truncate table X1
alter table X2 enable constraint X2_X1_FK
alter table X1 enable constraint X1_X2_FK

Also added the truncate of p_tname at the end...

          Followup:

that was my first thought -- look in the stack I had....

thanks

      Rating:
      5
  October 16, 2002
Reviewer:
      A reader
Hi Tom
Im in a situation where I cant seem to use packages. So Instead of using do_ddl
I locally used Execute Immediate statement . But this does not let the disabled
constraints to be enabled at one level deeper. i.e
If x1 <- x2
      <- x3
      <-x4
         <-x5
            <-x6
Running the following procedure would enable x2,x3,x4 but not x5 & x6. I dont
get it, even if I create a separate procedure do_ddl & call it from within the
following procedure it does not Enable the embedded constraints.

create table x1(id number primary key);
create table x2 (id number , constraint fk2 foreign key(id) references x1);
create table x3 (id number , constraint fk3 foreign key(id) references x1);
create table x4 (id number primary key , constraint fk4 foreign key(id)
references x1);
create table x5 (id number PRIMARY KEY, constraint fk5 foreign key(id)
references x4);
create table x6 (id number , constraint fk6 foreign key(id) references x5);

Create or Replace procedure recursive_truncate( p_tname in varchar2, p_level in
number default 1 )
AS
type stack is table of varchar2(32000) index by binary_integer;
  g_stack       stack;
  g_empty_stack stack;
  type tabs is table of varchar2(30) index by binary_integer;
  g_tab_to_truncate   tabs;
  g_empty_tabs        tabs;
      e_already_truncated  exception;
 stmt LONG;
  begin

      if ( p_level = 1 )
      then
          g_stack           := g_empty_stack;
          g_tab_to_truncate := g_empty_tabs;
      end if;
--Dbms_output.put_line( rpad(chr(9), p_level-1, chr(9)) || 'Processing Table '
|| p_tname );
g_tab_to_truncate( g_tab_to_truncate.count+1 ) := p_tname;
for x in ( select *
                   from user_constraints
                  where constraint_type = 'R'
                    and table_name <> p_tname
                    and r_constraint_name in ( select constraint_name
                                                 from user_constraints
                                                where table_name = p_tname
                                             and constraint_type in ('P', 'U'))
               )
 Loop
         Begin
--do_ddl( 'alter table ' || x.table_name || ' disable constraint ' ||
x.constraint_name, p_level );
stmt:='alter table ' || x.table_name || ' disable constraint ' ||
x.constraint_name ;
execute immediate stmt;
--Dbms_output.put_line( rpad(chr(9), p_level-1, chr(9)) || stmt );
g_stack( g_stack.count+1 ) := 'alter table ' || x.table_name || ' enable
constraint ' || x.constraint_name;
            for i in reverse 1 .. g_tab_to_truncate.count
            loop
                if g_tab_to_truncate(i) = x.table_name
                then
                   raise e_already_truncated;
                end if;
            end loop;
            recursive_truncate( x.table_name, p_level+1 );
         --   do_ddl( 'truncate table ' || x.table_name, p_level );
stmt:='truncate table ' || x.table_name;
execute immediate stmt;
Dbms_output.put_line( rpad(chr(9), p_level-1, chr(9)) ||stmt);
         exception
         when e_already_truncated
         then
 dbms_output.put_line('Table '|| x.table_name ||' already truncated,
skipping...');

         End;
      end loop;
if ( p_level = 1 )
      then
        --  do_ddl( 'truncate table ' || p_tname, p_level );
stmt:= 'truncate table ' || p_tname ;
execute immediate stmt;
Dbms_output.put_line( rpad(chr(9), p_level-1, chr(9)) ||stmt);
          for i in 1 .. g_stack.count
          loop
         --     do_ddl( g_stack(i), p_level );
stmt:= g_stack(i);
execute immediate stmt;
Dbms_output.put_line( g_stack(i));
--Dbms_output.put_line( rpad(chr(9), p_level-1, chr(9)) ||stmt);
          end loop;
      end if;
  end recursive_truncate;
/

Thanks

          Followup:

why cannot you use packages?

lets fix the PROBLEM here.

      Rating:
      5
  October 16, 2002
Reviewer:
      A reader
In our application due to some legacy code practices we can only have stored
procedures & not packages. Currently our application code that is dependent on
the database has to be in the form of SPs. Believe me no amt of arguing with my
bosses is of any use.

I am very curious of Oracle's behavior regarding  this issue . Is it related to
locally defined variable "stmt" that is dynamically executed?

Appreciate the response.

          Followup:

Well, sorry -- i cannot compete with what I will call "ignorance"

write a package -- create a standalone procedure that calls the package and
makes it look like a standalone procedure.  Dont' tell your managers you wrote a
"bad old package"

I'm not even going to look at making this code a single procedure. (ok, so I
peeked -- the problem is really quite simple -- you need a GLOBAL STACK, you
have a LOCAL STACK, the stuff you push in the recursive calls all "disappears"
as you unwind out of the recursion.  You'll have to totally redo the logical to
remove recursion.

FUNNY your managers let you do recursion, which many people out law, but won't
let you use packages which only make things *better* -- never worse.... sigh,
sigh, double sigh)

Because we did it in the past isn't any reason to keep propagating bad practices
into the future.  I firmly believe that ALL real production code MUST be in a
package -- there should be NO standalone procedures or functions beyond simple
utilities.

      Rating: 4
Why is not
      using bind variable faster sometimes?  March 27, 2003

Reviewer:  Jacques  from UK
Tom,
Dont know if I understand Bind variables correctly but I tried the following
test and it seems that not using bind variables works faster 2 out of 3 times;

Am I missing the point - or doing something wrong?

SQL>Create or replace Procedure P4
  2  as
  3  begin
  4  for i in 1 .. 1000
  5  loop
  6  execute immediate
  7  'insert into t (x,y,z) values (:x,:y,:z)'
  8  using i, sysdate+i, 'this is row ' || i;
  9  end loop;
 10* end;
 11  /

Procedure created.

Elapsed: 00:00:00.11
SQL> create or replace procedure NO_BINDS
  2  as
  3  begin
  4  for i in 1..1000
  5  loop
  6  insert into t(x,y,z) values(i, sysdate + i, 'test '||i);
  7  end loop;
  8  end;
  9  /

Procedure created.

Elapsed: 00:00:00.71
SQL> truncate table t;

Table truncated.

Elapsed: 00:00:00.50
SQL> exec p4;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.81
SQL> truncate table t;

Table truncated.

Elapsed: 00:00:00.00
SQL> exec NO_BINDS;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.90
SQL> truncate table t;

Table truncated.

Elapsed: 00:00:00.61
SQL> exec p4;

PL/SQL procedure successfully completed.

Elapsed: 00:00:01.82
SQL> truncate table t;

Table truncated.

Elapsed: 00:00:00.11
SQL> exec NO_BINDS;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.41
SQL> truncate table t;

Table truncated.

Elapsed: 00:00:00.50
SQL> exec p4;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.82
SQL> truncate table t;

Table truncated.

Elapsed: 00:00:00.51
SQL> exec NO_BINDS;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.81
SQL>

          Followup:

You were comparing

P4 -- dynamic sql using bind variables

with

NO_BINDS -- static sql That most certainly did use bind variables

static sql is more efficient then dynamic sql.  You would expect the second
routine to be more performant.

In this:

insert into t(x,y,z) values(i, sysdate + i, 'test '||i);

it was really:

insert into t(x,y,z) values( :i, sysdate + :i, 'test '|| :i );

All references to plsql variables in SQL in plsql are in fact bind
variables.

That is the sheer beauty of doing it in plsql -- it makes it so hard to NOT use
bind variables!!!

March 27, 2003 Paul -- Thanks for the question regarding "Does
      RAISE_APPLICATION_ERROR also mark the calling job as broken? ", version
      9.2.2

      You Asked
I am using RAISE_APPLICATION_ERROR to raise an exception within my code. Usually
I would make a call to dbms_job.broken in the exception handler each time an
exception is raised, but if I want to raise an exception within my code do I
still need to make the call to dbms_job.broken to mark the job as broken?

      and we said...
if you raise_application_error and DO NOT CATCH IT, the job will "fail".  If you
let the job processes get the error and log it to the alert, the job failes.

After 16 successive failures, the job will be marked broken.
Review &
      Followup

      Rating: 4
Just to
      clarify  March 27, 2003
Reviewer:  Paul
      Bevis  from Swindon, England
Thanks for the fast responce

So RAISE_APPLICATION_ERROR does not mark the job as broken in the job que.

For example, if I use RAISE_APPLICATION_ERROR within a job and I would like it
to fail and not be re-submited by Oracle another 16 times, I would need to call
dbms_job.broken before raising the error. Is this Correct?

Thanks

Paul

          Followup:

RAE fails the job

when a job fails 16 times in a row, it is marked as broken.

So yes, if you want the very first failure to cause the job to break, you would
have to break the job yourself.

March 27, 2003 Sandy -- Thanks for the question regarding "Tru64
      based Oracle", version 8.1.7

      You Asked
Hi Tom,

Greetings,

I’m not sure weather I should ask this question here or not, because may be this
question out of scope here. But finally I thought since this question is related
to Oracle, you might want to look into that. Here is my question:
How different is “Tru64 based Oracle” from “Solaris based Oracle”? Also, since
when (Which version) Oracle database started supporting Tru64?

Thanks a lot,
Sandy

      and we said...
Oracle is Oracle is Oracle is Oracle.  Whether you are running on Windows, Unix,
or an OS/390 mainframe.  If you know how to develop on Oracle for Solaris, you
know how to develop on Oracle for Tru64.

We've been on Tru64 for a while -- not sure exactly how long but a while.
Review &
      Followup

      Rating: 3
like
      it  May 06, 2002
Reviewer:  liz  from
      FL
hi tom
i like your tone
"Oracle is Oracle is Oracle is Oracle"

      Rating: 4
Tru64
      platform  March 27, 2003
Reviewer:  Abdal-Rahman
      Agha  from Yemen
Hi Tom,
  Oracle is Oracle is Oracle, I think developement wise. But what about
configuration wise? especially if there is clustering environment?
  I want to say, I have Oracle 9.2.0.1 working now alive in Alpha Server TRU64
UNIX platform. For some requirements, I need to install the last patch (9.2.0.3)
in this server, but I do not guarantee that the patch setup will be installed
successfully in the mid-night, because the system should be working the next day
morning.
  I want to say if all the steps required to install this patch have been done
perfectly, no unexpected problems will happen that stop my system for a days?

          Followup:

You will

a) try it on your test system first.

otherwise

b) you will be done for an unknown duration of time.

This is true for windows, for tru64, for z/os, for linux.  Rarely done, but
true.

March 27, 2003 Omar -- Thanks for the question regarding
      "Saveing the Output from a Dynamic SQL", version 8.1.7

      You Asked
Hi Tom,
I am trying to get a set of result from a Dynamic SQL but I have a problem, What
I am doing is

SQL> CREATE OR REPLACE procedure get_subfactor_values2 (acc_cat_code varchar2,
  2  P_CURRENCY varchar2,p_policy_no varchar2 ,result out number,v_sub_val_array
out  EG_ACC_NB_CALL2.sub_val_tbl)
  3  is
  4  tbl varchar2(10);
  5  str varchar2(5000);
  6  into_str varchar2(120);
  7  rows_processed integer;
  8  source_cursor integer;
  9  begin
 10
 11  RESULT := 0;
 12  tbl := acc_cat_code||'_EXT';
 13  dbms_output.put_line('Table Name is  '||tbl);
 14  str := 'SELECT
B.SUBFACTOR_1_VAL,B.SUBFACTOR_2_VAL,B.SUBFACTOR_3_VAL,B.SUBFACTOR_4_VA
 15  '
 16  ||' INTO
'||v_sub_val_array(1)||','||v_sub_val_array(2)||','||v_sub_val_array(3)||','
l_array(4)||'
 17  '
 18  ||' FROM ACC_POSTINGS A,AC_INTERNAL_ACCOUNTS B,
 19  '
 20  ||tbl||' WHERE A.BATCH_ID   = '||tbl||'.BATCH_ID'||' AND A.EVENT_NO =
 21  '
 22  ||tbl||'.EVENT_NO
 23  '
 24  ||'AND A.POSTING_NO= '||TBL||'.POSTING_NO
 25  '
 26  ||'AND A.INTERNAL_ACCOUNT_ID = B.INTERNAL_ACCOUNT_ID
 27  '
 28  ||'AND   A.POSTING_SWF = '||''''||P_CURRENCY||'''
 29  '
 30  ||'AND '||TBL||'.POLICY_NO = '||''''||P_POLICY_NO||'''
 31  '
 32  ||';'
 33  ||'end;'
 34  ;
 35  dbms_output.put_line(v_sub_val_array(1));
 36  dbms_output.put_line(v_sub_val_array(2));
 37  dbms_output.put_line(v_sub_val_array(3));
 38  dbms_output.put_line(v_sub_val_array(4));
 39  source_cursor := dbms_sql.open_cursor;
 40  dbms_sql.parse(source_cursor,str,dbms_sql.NATIVE );
 41  rows_processed := dbms_sql.execute(source_cursor);
 42  dbms_sql.close_cursor(source_cursor);
 43  exception
 44      when others then
 45    result := 1;
 46       if dbms_sql.is_open(source_cursor) then
 47          dbms_sql.close_cursor(source_cursor);
 48       end if;
 49     raise;
 50
 51  end;
 52  /

Procedure created.

SQL> SQL> set serveroutput on;

SQL> declare
  2  RESULT NUMBER;
  3  v_sub_val_array  EG_ACC_NB_CALL2.sub_val_tbl;
  4  begin
  5   get_subfactor_values2 ('ADP','EGP','06500003601' ,result
,v_sub_val_array);
  6   dbms_output.put_line(result);
  7  end;
  8  /
Table Name is  ADP_EXT
declare
*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at "CUSTOMER.GET_SUBFACTOR_VALUES2", line 49
ORA-06512: at line 5

As you see the table name is correct but always give me an errors,
I do not know where is the error? Is it wrong to try to get an output from a
Dynamic SQL ?, How to get the same result using NATIVE SQL?,
Thank you in advance Tom.

      and we said...
You do not use INTO in dynamic sql -- only in static SQL.  The error is coming
from line 35 really -- when you try to access the array (and it has NO DATA)

Here is the proper way to code this:

CREATE OR REPLACE procedure
get_subfactor_values2 (acc_cat_code varchar2,
                       P_CURRENCY varchar2,
                       p_policy_no varchar2 ,
                       result out number,
                       v_sub_val_array out  EG_ACC_NB_CALL2.sub_val_tbl )
is
begin
    result := 0;
    execute immediate
   'SELECT B.SUBFACTOR_1_VAL,
           B.SUBFACTOR_2_VAL,
           B.SUBFACTOR_3_VAL,
           B.SUBFACTOR_4_VAL
      FROM ACC_POSTINGS A,AC_INTERNAL_ACCOUNTS B, ' || acc_cat_code || '_EXT c
     WHERE A.BATCH_ID = C.BATCH_ID
       AND A.EVENT_NO = C.EVENT_NO
       AND A.POSTING_NO= C.POSTING_NO
       AND A.INTERNAL_ACCOUNT_ID = B.INTERNAL_ACCOUNT_ID
       AND A.POSTING_SWF = :currency
       AND C.POLICY_NO = :policy_no'
      into v_sub_val_array(1), v_sub_val_array(2),
           v_sub_val_array(3), v_sub_val_array(4)
     using p_currency, p_policy_no;
exception
    when no_data_found then
        result := 1;
        raise;
end;
/

Remember -- BIND VARIABLES -- use them!!!  Very very important (you should have
been using them with dbms_sql.bind_variable above!!)

But, you never use INTO inside of a dynamic SQL select like that.  Using
DBMS_SQL, you would have had to:

  0) open
  1) parse & define
  2) BIND!!!!
  3) execute
  4) fetch -- make sure you got one
  5) use dbms_sql.column_value 4 times to retrieve the 4 columns
  6) fetch again -- make sure you DON'T get one
  7) close

NDS (native dynamic sql) makes this a tad easier, so that is what I did.

Review &
      Followup

      Rating:
      5
Ridiculous  June 30, 2002
Reviewer:
      Reader
I don't understand why people never study and they just simply post the question
without undertstanding what is happening and why is it happening.....Sorry
Tom...i just could not control after reading the question...

      Rating: 4
Very useful new
      rule about INTO reserved word !  July 01, 2002

Reviewer:  Omar Foda  from Cairo Egypt
Thank u Tom for your kind answer,
It is the first time I know that I can not use INTO within the Dynamic SQL, I
will try your code and I hope it will work,
Thanks and best regards.

      Rating: 2
No data found
      using arrays with dbms_sql  March 26, 2003

Reviewer:  A reader
Hi Tom,

I am using dbms_sql to bin some variables because I don't know how many
variables I will need in the "in" list until I run the program.
I am using arrays in order to return the result and be able to manipulate the
data, but the procedures are reaching the error "no data found" when I tried to
retrieve the data.
Can you help to figure out what the problem is with these procedures.
Here you have the code:

create or replace procedure built_query_ret_result (p_colnames in
array,p_operator in array, p_values in array,
p_values_operin in array,p_selqntcol in number, p_query in varchar2,
l_ret_accid_tab out dbms_sql.Number_Table,l_ret_ordid_tab out
dbms_sql.Number_Table,
l_ret_ordstat_tab out dbms_sql.Number_Table,l_ret_orditid_tab out
dbms_sql.Number_Table,
l_ret_shipid_tab out  dbms_sql.Number_Table ) as
--
l_cursor int default dbms_sql.open_cursor;
l_query           long;
l_cond_oper       varchar2(20) default ' ';
l_comma           varchar2(1)  default '';
l_status          int;
l_columvalue      varchar2(4000);
l_in_flag         varchar2(1) default 'N';
l_nullval_flag    varchar2(1) default 'N';
l_accid_tab       dbms_sql.Number_Table;
l_ordid_tab       dbms_sql.Number_Table;
l_ordstat_tab     dbms_sql.Number_Table;
l_orditid_tab     dbms_sql.Number_Table;
l_shipid_tab      dbms_sql.Number_Table;
l_indx number := 10;
begin
 if p_values is null then
  l_nullval_flag:='Y';
 end if;
 if p_values_operin is not null then
  l_in_flag:='Y';
 end if;
 l_query:=p_query;
 for i in 1 ..p_colnames.count loop
  if upper(p_operator(i))='IN' then
     l_query:=l_query||l_cond_oper||p_colnames(i)||' '||p_operator(i)||' ( ';
     for x in 1 ..p_values_operin.count loop
      l_query:=l_query||l_comma||':bin_inval'||x;
      if x=1 then
        l_comma:=',';
      end if;
     end loop;
     l_query:=l_query||' ) ';
  else
    l_query:=l_query||l_cond_oper||p_colnames(i)||' '||p_operator(i)||'
'||':bin_var'||i;
  end if;
  l_cond_oper:=' and ';
 end loop;
 dbms_sql.parse(l_cursor, l_query,dbms_sql.native);
 if l_nullval_flag='N' then
   for i in 1..p_values.count loop
     dbms_sql.bind_variable(l_cursor,':bin_var'||i, p_values(i));
   end loop;
 end if;
 if l_in_flag='Y' then
    for i in 1..p_values_operin.count loop
     dbms_sql.bind_variable(l_cursor,':bin_inval'||i, p_values_operin(i));
    end loop;
 end if;
--
dbms_sql.define_array(l_cursor,1,l_ret_accid_tab,p_selqntcol,l_indx);
dbms_sql.define_array(l_cursor,2,l_ret_ordid_tab,p_selqntcol,l_indx);
dbms_sql.define_array(l_cursor,3,l_ret_ordstat_tab,p_selqntcol,l_indx);
dbms_sql.define_array(l_cursor,4,l_ret_orditid_tab,p_selqntcol,l_indx);
dbms_sql.define_array(l_cursor,5,l_ret_shipid_tab,p_selqntcol,l_indx);
--
 l_status:=dbms_sql.execute(l_cursor);
 while (dbms_sql.fetch_rows(l_cursor)>0)
   loop
      dbms_sql.column_value(l_cursor,1,l_ret_accid_tab);
      dbms_sql.column_value(l_cursor,2,l_ret_ordid_tab);
      dbms_sql.column_value(l_cursor,3,l_ret_ordstat_tab);
      dbms_sql.column_value(l_cursor,4,l_ret_orditid_tab);
      dbms_sql.column_value(l_cursor,5,l_ret_shipid_tab);
   end loop;
 for i in 1 .. l_ret_ordid_tab.count loop

dbms_output.put_line(to_char(l_ret_accid_tab(i))||to_char(l_ret_ordid_tab(i))||to
_char(l_ret_orditid_tab(i))||

to_char(l_ret_ordstat_tab(i))||to_char(l_ret_shipid_tab(i)));
 end loop;
   dbms_Sql.close_Cursor(l_cursor);
end;
/

Procedure created.

Procedure #2
------------
create or replace procedure show_result is
v_cursor types.rc;
v_ord_id number default 3007;
v_colnames array      :=array('a.order_id','b.order_item_id');
v_operator array      :=array('=','in');
v_values array        :=array(v_ord_id);
v_values_operin array :=array();
v_query varchar2(4000);
l_ret_accid_tab   dbms_sql.Number_Table;
l_ret_ordid_tab   dbms_sql.Number_Table;
l_ret_ordstat_tab dbms_sql.Number_Table;
l_ret_orditid_tab dbms_sql.Number_Table;
l_ret_shipid_tab  dbms_sql.Number_Table;
v_strsql varchar2(4000);
v_pos number default 0;
i number;
rowquery ssw.order_items%rowtype;
begin
 v_query:='select
a.account_id,a.order_id,a.order_status,b.order_item_id,b.SHIP_ID '||
          'from ssw.orders a , ssw.order_items b '||
          'where b.order_id=a.order_id and';
 i:=1;
 select count(*) into v_pos from ssw.order_items where order_id=v_ord_id;
 v_values_operin.extend(v_pos);
 v_strsql:='select * from ssw.order_items where order_id=:xord_id';
 open v_cursor for v_strsql using v_ord_id;
 loop
    fetch v_cursor into rowquery;
    exit when v_cursor%notFound;
    v_values_operin(i):=rowquery.order_item_id;
    i:=i+1;
 end loop;

built_query_ret_result(v_colnames,v_operator,v_values,v_values_operin,v_pos,v_que
ry,l_ret_accid_tab,l_ret_ordid_tab,l_ret_ordstat_tab,l_ret_orditid_tab,l_ret_ship
id_tab);
 dbms_output.put_line('l_ret_ordid_tab.count='||to_char(l_ret_ordid_tab.count));
 for i in 1 .. l_ret_ordid_tab.count loop

dbms_output.put_line(to_char(l_ret_accid_tab(i))||to_char(l_ret_ordid_tab(i))||to
_char(l_ret_orditid_tab(i))||

to_char(l_ret_ordstat_tab(i))||to_char(l_ret_shipid_tab(i)));
 end loop;
end;
/

Procedure created.

Error I receive when I try to run the procedures.
-----------------------------------------------------
SQL>set serveroutput on
SQL>exec show_result;
BEGIN show_result; END;

*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at "CTW.BUILT_QUERY_RET_RESULT", line 72
ORA-06512: at "CTW.SHOW_RESULT", line 33
ORA-06512: at line 1

Do you know why the dbms_sql.column_value is not loaded the data into the
arrays? What am i doing wrong here?
I can't not figure out where the problem is.

Thanks for any help you can give me.

          Followup:

unfortunately -- you give me only code -- not a test case.

I'm not a compiler (some people disagree with me on that but thats another
story)

Tell you what -- when I'm taking questions, feel free to post an entire working
test case with create tables, et. al.  Something anyone could take and run in
their database.

      Rating: 2
No data found
      using arrays with dbms_sql (cont.)   March 26, 2003

Reviewer:  A reader
Tom,
Sorry about the test case.
My database is an Enterprise edition release 8.1.7.3.0 on a Sun solaris sever.

Thanks for any help you can give me on this.

Here you have all you need in order to reproduce my test case.

prompt Here you have a test case......Thanks......
prompt There some parameters that you have to add like: default tablespace,
temporary tables..
prompt and it will ask you the users names (ctw and ssw) in order to assing
quota on the tablespace...
prompt ....

conn system

create user ctw identified by ctw
default tablespace &deftbsp
temporary tablespace &temptbsp
profile default;

create user ssw identified by ssw
default tablespace &deftbsp
temporary tablespace &temptbsp
profile default;

grant create session to ctw,ssw;

grant create table, select any table to ctw,ssw;

grant create procedure, create type to ctw;

alter user &user quota unlimited on &tbsp;

conn ssw

create table ssw.orders
(ORDER_ID      NUMBER(10)  NOT NULL,
ACCOUNT_ID     NUMBER(10)  NOT NULL,
ORDER_STATUS   NUMBER(3)  NOT NULL)
pctfree 0;

create table ssw.order_items
(ORDER_ID      NUMBER(10)  NOT NULL,
 ORDER_ITEM_ID  NUMBER(10)  NOT NULL,
 SHIP_ID        NUMBER(10))
pctfree 0;

insert into ssw.orders
(order_id,account_id,order_status)
values(3007,2376878,1)
/
insert into ssw.order_items
(order_id,order_item_id,ship_id)
values(3007,5113492,3254581)
/
insert into ssw.order_items
(order_id,order_item_id,ship_id)
values(3007,3004,null)
/
insert into ssw.order_items
(order_id,order_item_id,ship_id)
values(3007,692050,4534)
/
insert into ssw.order_items
(order_id,order_item_id,ship_id)
values(3007,1667964,896468)
/
insert into ssw.orders
(order_id,account_id,order_status)
values(3004,877686,1)
/
insert into ssw.order_items
(order_id,order_item_id,ship_id)
values(3004,5113293,3253170)
/
insert into ssw.order_items
(order_id,order_item_id,ship_id)
values(3004,3001,nul)
/
insert into ssw.order_items
(order_id,order_item_id,ship_id)
values(3004,690712,4105)
/
insert into ssw.order_items
(order_id,order_item_id,ship_id)
values(3004,1667848,896338)
/
commit
/

conn ctw

create or replace type array is table of varchar2(2000);
/

create or replace package types as type rc is ref cursor;
end;
/

create or replace procedure built_query_ret_result (p_colnames in
array,p_operator in array, p_values in array,
p_values_operin in array,p_selqntcol in number, p_query in varchar2,
l_ret_accid_tab out dbms_sql.Number_Table,l_ret_ordid_tab out
dbms_sql.Number_Table,
l_ret_ordstat_tab out dbms_sql.Number_Table,l_ret_orditid_tab out
dbms_sql.Number_Table,
l_ret_shipid_tab out  dbms_sql.Number_Table ) as
--
l_cursor int default dbms_sql.open_cursor;
l_query           long;
l_cond_oper       varchar2(20) default ' ';
l_comma           varchar2(1)  default '';
l_status          int;
l_columvalue      varchar2(4000);
l_in_flag         varchar2(1) default 'N';
l_nullval_flag    varchar2(1) default 'N';
l_accid_tab       dbms_sql.Number_Table;
l_ordid_tab       dbms_sql.Number_Table;
l_ordstat_tab     dbms_sql.Number_Table;
l_orditid_tab     dbms_sql.Number_Table;
l_shipid_tab      dbms_sql.Number_Table;
l_indx number := 10;
begin
 if p_values is null then
  l_nullval_flag:='Y';
 end if;
 if p_values_operin is not null then
  l_in_flag:='Y';
 end if;
 l_query:=p_query;
 for i in 1 ..p_colnames.count loop
  if upper(p_operator(i))='IN' then
     l_query:=l_query||l_cond_oper||p_colnames(i)||' '||p_operator(i)||' ( ';
     for x in 1 ..p_values_operin.count loop
      l_query:=l_query||l_comma||':bin_inval'||x;
      if x=1 then
        l_comma:=',';
      end if;
     end loop;
     l_query:=l_query||' ) ';
  else
    l_query:=l_query||l_cond_oper||p_colnames(i)||' '||p_operator(i)||'
'||':bin_var'||i;
  end if;
  l_cond_oper:=' and ';
 end loop;
 dbms_sql.parse(l_cursor, l_query,dbms_sql.native);
 if l_nullval_flag='N' then
   for i in 1..p_values.count loop
     dbms_sql.bind_variable(l_cursor,':bin_var'||i, p_values(i));
   end loop;
 end if;
 if l_in_flag='Y' then
    for i in 1..p_values_operin.count loop
     dbms_sql.bind_variable(l_cursor,':bin_inval'||i, p_values_operin(i));
    end loop;
 end if;
--
dbms_sql.define_array(l_cursor,1,l_ret_accid_tab,p_selqntcol,l_indx);
dbms_sql.define_array(l_cursor,2,l_ret_ordid_tab,p_selqntcol,l_indx);
dbms_sql.define_array(l_cursor,3,l_ret_ordstat_tab,p_selqntcol,l_indx);
dbms_sql.define_array(l_cursor,4,l_ret_orditid_tab,p_selqntcol,l_indx);
dbms_sql.define_array(l_cursor,5,l_ret_shipid_tab,p_selqntcol,l_indx);
--
 l_status:=dbms_sql.execute(l_cursor);
 while (dbms_sql.fetch_rows(l_cursor)>0)
   loop
      dbms_sql.column_value(l_cursor,1,l_ret_accid_tab);
      dbms_sql.column_value(l_cursor,2,l_ret_ordid_tab);
      dbms_sql.column_value(l_cursor,3,l_ret_ordstat_tab);
      dbms_sql.column_value(l_cursor,4,l_ret_orditid_tab);
      dbms_sql.column_value(l_cursor,5,l_ret_shipid_tab);
   end loop;
 for i in 1 .. l_ret_ordid_tab.count loop
dbms_output.put_line(to_char(l_ret_accid_tab(i))||to_char(l_ret_ordid_tab(i))||to
_char(l_ret_orditid_tab(i))||
to_char(l_ret_ordstat_tab(i))||to_char(l_ret_shipid_tab(i)));
 end loop;
   dbms_Sql.close_Cursor(l_cursor);
end;
/

create or replace procedure show_result is
v_cursor types.rc;
v_ord_id number default 3007;
v_colnames array      :=array('a.order_id','b.order_item_id');
v_operator array      :=array('=','in');
v_values array        :=array(v_ord_id);
v_values_operin array :=array();
v_query varchar2(4000);
l_ret_accid_tab   dbms_sql.Number_Table;
l_ret_ordid_tab   dbms_sql.Number_Table;
l_ret_ordstat_tab dbms_sql.Number_Table;
l_ret_orditid_tab dbms_sql.Number_Table;
l_ret_shipid_tab  dbms_sql.Number_Table;
v_strsql varchar2(4000);
v_pos number default 0;
i number;
rowquery ssw.order_items%rowtype;
begin
 v_query:='select
a.account_id,a.order_id,a.order_status,b.order_item_id,b.SHIP_ID '||
          'from ssw.orders a , ssw.order_items b '||
          'where b.order_id=a.order_id and';
 i:=1;
 select count(*) into v_pos from ssw.order_items where order_id=v_ord_id;
 v_values_operin.extend(v_pos);
 v_strsql:='select * from ssw.order_items where order_id=:xord_id';
 open v_cursor for v_strsql using v_ord_id;
 loop
    fetch v_cursor into rowquery;
    exit when v_cursor%notFound;
    v_values_operin(i):=rowquery.order_item_id;
    i:=i+1;
 end loop;
built_query_ret_result(v_colnames,v_operator,v_values,v_values_operin,v_pos,v_que
ry,l_ret_accid_tab,l_ret_ordid_tab,l_ret_ordstat_tab,l_ret_orditid_tab,l_ret_ship
id_tab);
 dbms_output.put_line('l_ret_ordid_tab.count='||to_char(l_ret_ordid_tab.count));
 for i in 1 .. l_ret_ordid_tab.count loop
dbms_output.put_line(to_char(l_ret_accid_tab(i))||to_char(l_ret_ordid_tab(i))||to
_char(l_ret_orditid_tab(i))||
to_char(l_ret_ordstat_tab(i))||to_char(l_ret_shipid_tab(i)));
 end loop;
end;
/

Here you have the results for the test:

SQL>set serveroutput on
SQL>exec show_result;
BEGIN show_result; END;

*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at "CTW.BUILT_QUERY_RET_RESULT", line 76
ORA-06512: at "CTW.SHOW_RESULT", line 34
ORA-06512: at line 1

          Followup:

I need to write a book on how to make a simple, small, concise, to the point
test case....

Anyway, add this DEBUG code:

 65   l_status:=dbms_sql.execute(l_cursor);
 66   while (dbms_sql.fetch_rows(l_cursor)>0)
 67     loop
 68        dbms_sql.column_value(l_cursor,1,l_ret_accid_tab);
 69        dbms_sql.column_value(l_cursor,2,l_ret_ordid_tab);
 70        dbms_sql.column_value(l_cursor,3,l_ret_ordstat_tab);
 71        dbms_sql.column_value(l_cursor,4,l_ret_orditid_tab);
 72        dbms_sql.column_value(l_cursor,5,l_ret_shipid_tab);
 73     end loop;
 74
 75   dbms_output.put_line( 'accid_tab.count = ' || l_ret_accid_tab.count );
 76   dbms_output.put_line( 'ordid_tab.count = ' || l_ret_ordid_tab.count );
 77   dbms_output.put_line( 'ordstat_tab.count = ' || l_ret_ordstat_tab.count );
 78   dbms_output.put_line( 'orditid_tab.count = ' || l_ret_orditid_tab.count );
 79   dbms_output.put_line( 'shipid_tab.count = ' || l_ret_shipid_tab.count );
 80
 81   dbms_output.put_line( 'accid_tab.first = ' || l_ret_accid_tab.first );
 82   dbms_output.put_line( 'ordid_tab.first = ' || l_ret_ordid_tab.first );
 83   dbms_output.put_line( 'ordstat_tab.first = ' || l_ret_ordstat_tab.first );
 84   dbms_output.put_line( 'orditid_tab.first = ' || l_ret_orditid_tab.first );
 85   dbms_output.put_line( 'shipid_tab.first = ' || l_ret_shipid_tab.first );
 86
 87
 88   dbms_output.put_line( 'accid_tab.last = ' || l_ret_accid_tab.last );
 89   dbms_output.put_line( 'ordid_tab.last = ' || l_ret_ordid_tab.last );
 90   dbms_output.put_line( 'ordstat_tab.last = ' || l_ret_ordstat_tab.last );
 91   dbms_output.put_line( 'orditid_tab.last = ' || l_ret_orditid_tab.last );
 92   dbms_output.put_line( 'shipid_tab.last = ' || l_ret_shipid_tab.last );
 93
 94   for i in 1 .. l_ret_ordid_tab.count loop
 95     dbms_output.put_line(to_char(l_ret_accid_tab(i))||
 96                          to_char(l_ret_ordid_tab(i))||
 97                                              to_char(l_ret_orditid_tab(i))||
 98                                              to_char(l_ret_ordstat_tab(i))||
 99                                              to_char(l_ret_shipid_tab(i)));
100   end loop;

and all will be revealed to you. you told us to start the array indexing at 10
(.first will show you that).  The array subscripts run from 10..265 in your
case, not 1..N....

A little debug code goes a long way...

The smaller a test case is -- the better.  99 times out of 100 in the
development of a test case to demonstrate a "product issue", I find the bug in
my code ;)

      Rating: 3
no data found,
      oracle 7.1.3, might be a bug  March 27, 2003

Reviewer:  rachru  from Hamburg
Well, I do remember we had a similar phaenomenon years ago using oracle 7.x.x.x
on HP-UX. It did go wrong occasionally, very strange.

I was able to produce a clear case and oracle was sending a patch. Never had any
trouble afterwards.

          Followup:

nope, this is "end user coding error"

      Rating: 5
No data found
      using arrays with dbms_sql (cont.)  March 27, 2003

Reviewer:  A reader
Tom,
Thank you very much for you help.
I did ran a debug on the code but I always tried to access to the first position
in the array. I never thought that the problem was connected with the way I had
defined the array. If I were used the .first and .last it would have be more
easy to see the error. My mistake for not having done a good debug.

One more thing, I never said that my problem was caused by a bug in the product.
I knew all the time that I was doing something wrong in my code, the problem was
I could not figure out where I had the error.

Thanks again for your help.

          Followup:

I was referring to the other persons comment, not yours, when I said "not a
product issue, coding issue"

March 27, 2003 Richer -- Thanks for the question regarding "Bind
      variables & DB Links", version 8.1.7.4

      You Asked
Hi Tom,

I am revisiting some of my packages in order to find out if the use of bind
variables would optimize code. In this particulare piece of code, I use database
links to get a specific value from all databases available from database links
(in order to test, I used ORADBA1 but in fact, this value will come from a
cursor since there are many database links to use).

My question is:  Is it possible to use database link in a bind variable?  If
not, how could we overcome this limitation?

Thanks,

Rich

---------------------------------------------------------------------

declare

v_dblink VARCHAR2(100) := 'ORADBA1';

cursor c_databases is
select dblink
from s_databases;

c0_val c_databases%ROWTYPE;

BEGIN

FOR C1_VAL IN c_databases LOOP
  EXECUTE IMMEDIATE 'INSERT INTO test SELECT x FROM dual@:v_dblink' USING
c0_val.dblink;
END LOOP;

END;
/

      and we said...
you cannot bind identifiers.

If you think about this -- it makes perfect sense.  If you change the dblink
name in the query -- the plan CHANGES, in order to change a plan, we must parse.
 Hence, we cannot bind dblinks since binds are used to bypass the need for a
parse and we MUST parse.

You have a very finite set of dblinks I would assume.  It is ok to have to parse
a statement for each one, in fact there is no avoiding it.
Review &
      Followup

      Rating:
      5
  March 27, 2003
Reviewer:
      A reader

March 27, 2003 Arjun -- Thanks for the question regarding "What
      do commercial vendors user - OCI or PRO*C", version 9.0.1

      You Asked
Hi Tom,

What do the commercial software products like TOAD, SQL Navigator etc use to
connect to different versions of the databases?

  Do they use OCI or PRO*C?

To be able to connect to different versions of database from a common front end
application, what would be better to program with: OCI or pro*c?

I would appreciate any information or pointers to any documents.

thanks
Arjun

      and we said...
OCI mostly.

But that is because they do pretty much 100% dynamic SQL.

If I was writing an application...
And I was forced to write it in C...
And it was a business application with much static SQL...

I would only use Pro*C as it is a much more productive environment for doing
that.
Review
      & Followup

      Rating: 5
OCI and
      JDBC?  March 26, 2003
Reviewer:
      A reader
Is PRO*C just a wrapper for the "native" OCI API?

Is there any relationship between JDBC Type 4 (thin) and OCI?

          Followup:

Now it is -- yes, Pro*C is a layer on top of OCI.  It didn't used to be in the
past but with 8i and up - it is.

the jdbc THICK driver is built on top of OCI.  it requires OCI be installed on
the client.

the type 4 driver is not.  it is pure java.

      Rating: 5
More OCI and
      JDBC  March 27, 2003
Reviewer:
      A reader
Is it correct to say that Oracle has two "lowest-level" API's: OCI and thin JDBC

Are the server processes (listener, dedicated server processes, pmon, smon, etc)
considered OCI applications.  It looks like the IFS application server processes
are written in Java.  Are there any other Oracle server products written in
JDBC?

          Followup:

No, it would be UPI.  OCI is a layer on UPI.  thin jdbc is an implementation of
our networking protocol that talks to UPI.

OCI is just a C api.  no, pmon et al would not be considered OCI applications.

SQLPlus - yes.

OEM, much of our applications, some packages in the database -- there is a
sprinkling of java throughout.

      Rating:
      5
OCI  March 27, 2003
Reviewer:
      A reader
"thin jdbc is an implementation of our networking protocol that talks to UPI."

Can you explain this a bit more?

Thank you.

          Followup:

the thin jdbc drivers just write/read on the network.  they are a pure java
implementation.  They cannot do everything the OCI drivers can due to this.

March 27, 2003 Florin -- Thanks for the question regarding "sum
      on report", version 8.0.6.

      You Asked
Hi Tom,

I have this query:

SQL>compute sum of Total on date
SQL>break on Date skip 2
SQL>select to_char(PROC_DATE,'Mon-YYYY') "Date",
  2  sum(nvl(SP_OUT,0))+sum(nvl(NO_OUT,0))  "Total"
  3  from AMC_MPS_CDR_OUTPUT where
  4  to_char(PROC_DATE,'Mon-YYYY') = to_char(sysdate,'Mon-YYYY')
  5  group by PROC_DATE;

Date          Total
-------- ----------
Mar-2003    4574751
            4380504
            7516785
            7965465
            8014700
            8087229
            8134093
            5854561
            5380846
            7612247
            8115514
            8338303
            8186109
            8346492
            5785274
            4001001
            7620584
            7746421
            8933036
            8214377
            8143332
            5870806
            4820025
            7417181
            7204319
******** ----------
sum       176263955

25 rows selected.

Do you have any idea how to get only one row of this? I don't want to see all
the 25 rows.

I need in the end something like this:

Date          Total
-------- ----------
Mar-2003    176263955

Thanks a lot in advance,

Florin

      and we said...
select to_char(trunc(PROC_DATE,'mm'),'Mon-YYYY') "Date",
       sum(nvl(SP_OUT,0))+sum(nvl(NO_OUT,0))  "Total"
  from (select hiredate proc_date, sal sp_out, comm no_out from emp)
AMC_MPS_CDR_OUTPUT
 where PROC_DATE between trunc(sysdate,'mm') and
add_months(trunc(sysdate,'mm'),1)-0.0001
 group by trunc(PROC_DATE,'mm');

avoid using functions on columns in where clauses when you can. instead of:

to_char(PROC_DATE,'Mon-YYYY') = to_char(sysdate,'Mon-YYYY')

use

PROC_DATE between trunc(sysdate,'mm') and
                  add_months(trunc(sysdate,'mm'),1)-0.0001

Also, use TRUNC when possible on dates to get them down to hours, days, weeks,
months, years, whatever -- faster then to_char.

Review &
      Followup

      Rating:
      5
  March 27, 2003
Reviewer:  Florin

Hi Tom,

Thanks a lot for your marvelous and quick solution !!!

Best Regards,

Florin

      Rating: 4
data
      manipulation in Where clause  March 27, 2003

Reviewer:  Eric D. Peterson  from Bellevue, WA

Oracle 8i/HPUX

   Could you expand on your statement about avoiding functions in the where
clause?  What pitfalls and limitations should I consider?  Do some functions
cause more of an impact to the performance of queries than other functions?
(i.e. RTRIM, NVL, DECODE, etc)

  Quite a bit I do this to find data that is currently valid.  Sometimes these
date fields are part of the primary key but most of the time they are not.

  SELECT
     < columns >
  FROM
     table T
  WHERE
     T.effective_dt < SYSDATE   AND
     NVL ( T.expiration_dt, SYSDATE + 1 ) > SYSDATE

  Is there another method I should use that would be "better"?

Thanks much
Eric

          Followup:

in this case, it would in all probability preclude the use of an index.  That is
why.  Unless they had a function based index on:

to_char(PROC_DATE,'Mon-YYYY')

that query could not use one to limit the row efficiently.  My query does.

In your case -- your NVL usage is probably OK.  An index on t(effective_dt) is
usable -- and if there were an index on t(effective_dt,expiration_dt), we would
range scan it for the effective_dt values -- apply the NVL function and filter
AND THEN access the table.  It would be fine.

You just need to look at the query and say "is there a way I can write that such
that I will not need a function?"

In your case -- with NVL -- the answer is "no, there is not".  But with TRUNC()
the answer is almost always "yes, I can"

March 28, 2003 --
      Thanks for the question regarding "Performance", version 9

      You Asked
We are converting an existing client server application to web. for this we are
using pl/sql cartridge with 9iAS and Oracle 8i. Each page is becoming very heavy
as the parent application has lots of functionality. So someone having a speed
of 28 KBPS or less will get time-out while loading only.
We are using external JavaScript file as some of the scripts are very large in
size. This also helps in caching. But still a lot of improvements are need to
get a descent performance.

Pl. suggest how to improve the speed. Event if the architecture needs to be
changes we are ready to consider that also.

      and we said...
cut out the junk.  make it lean.  make it clean.

Look at this very site.  Target pages to be 20k or less when possible.  cut down
on the javascript.  don't use images all over the place.  go lean, go mean.

don't try to emulate a client server application in html.  It is fruitless.
Look at yahoo.  Look at google.  Look at amazon.  Those are the sites to
emulate.

Review &
      Followup

      Rating:
      3
Performance  March 28, 2003
Reviewer:
      Sanjay
We have javascript ranging from 30KB to approx 50KB for each page which is on
the min. side. And we are not using any images only CSS is used to give it some
color to distinguish the sections in a page.

Another factor is HTTPS. Is cartrige slow on HTTPS.

I am using web cache to improve thing. And it has to some extent.

Is there any other option that will allow the web server to serve the client
request without going to the database complile the procedure then send the
output to the client?

          Followup:

30-50k of javascript.  cannot fathom the need for 30-50k of client side
javascript.

You have a misunderstanding of how the mod_plsql stuff works - it does not
"compile the procedure", the procedure is compiled.

Before you go further in trying to work around this database thing -- you need
to 100% identify that the database is even part of your performance issue.  What
have YOU done to isolate where the performance related issues are.

March 28, 2003 Tatiane -- Thanks for the question regarding
      "De-composing Composite Indexes", version 9.2.0.1

      You Asked
Tom,

I have read a lot about WHEN composite index are used -- that leading edge
stuff, etc. I know that in 9i there is skip-scanning.

Would it not be easier if I simply used a couple of single-column indexes
instead of one composite index ?

After all, what are the advantages of composite indexes over single-column
indexes -- except for space usage ?

Thanks

      and we said...
select *
  from t
 where x = :x
   and y > :y
 order by z;

Now, tell me -- if you have an index on

t(x,y,z)

versus INDEXES:

t1(x)
t2(y)
t3(z)

which would be better?

A concatenated b*tree index as they are known is very useful in situations like
this.  In the first case with t(x,y,z) -- Oracle can use the index for the
predicate x=:x and y>:y to quickly identify the rows to be returned and further
in this case, use the fact that z is last in the index to return the data sorted
without sorting it.

In the second case, Oracle would have to pick ONE of the three indexes and use
it.  Perhaps there are 5 MILLION rows where x=:x.  Perhaps there are 5 MILLION
records where y > :y.  Both are bad choices (so Oracle may full scan).  There
might be only 10 rows such that x = :x and y > :y -- unfortutnately, we would
not be able to make use of that fact.

Also, consider:

select count(x)
  from t
 where y = :y;

and an index on t(y,x).

Here, we can answer the query directly from the index, never bothering with the
table AT ALL.
Review & Followup

      Rating:
      3
  March 27, 2003
Reviewer:  Tatiane

Ok, but...

1) Don't you think the cases you mentioned are exceptions, and the ease of not
worrying about composite indexes and then indexing single columns might outweigh
that ?

2) Suppose another query comes up and uses the indexes some other way than that
the ordering in the index is proper for. Then I would have to create another
composite index for that, like first building t(a,b,c) and then t(a,c,b) -- then
I would have 2 composite indexes for the same columns -- wouldn't single column
indexes be better here ?

3) This is a bit off-topic. In your query:

>select *
>  from t
> where x = :x
>   and y > :y
> order by z;

Wouldn't the order by z be done in a second stage after you have retrieved the
rows with x and y ? From what you said, I understand this is all done in one
pass.

Thanks

          Followup:

1) nope, not at all -- I think the exceptions are the ones you are thinking are
the common ones.

concatenated indexes are necessary tuning and performance tools.  disregard them
as your own peril.

How many times I've fixed a system simply by adding a column to an index --
bypassing the TABLE ACCESS BY ROWID step.

Consider ALL_OBJECTS -- pretend it was a table T.  A common query is obviously:

select * from all_objects where owner = :x and object_type = :y and object_name
= :z;

A concatenated index is necessary there.

2) No, look at it.   Why would you create a,c,b when you have a,b,c?

In general, you'll get ONE index at a time to be used.  conspicous exceptions to
that are:

a) bitmap indexes -- these should probably never be concatenated but on single
columns only.

b) index joins -- but there ALL columns need for the query need to be in the
index.

Tell you what -- test it out.  create single column indexes and try to get them
to be used simultaneously.  Then think "hmm, how could Oracle use more then one
in this case"....

3) whoops -- missed something there --

ops$tkyte@ORA920> variable x number;
ops$tkyte@ORA920> variable y number;
ops$tkyte@ORA920> variable z number;
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select * from t where x = :x and y > :y order by y,z;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=50 Bytes=2600)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=50 Bytes=2600)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=4)

order by y,z will use the index only -- no extra step.  Alternatively:

ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select * from t where x = :x and y = :y order by z;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=10 Bytes=520)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=10 Bytes=520)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=2)

if we have equality on Y, we skip the sort.  You are correct as originally
written:

ops$tkyte@ORA920> select * from t where x = :x and y > :y order by z;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=50 Bytes=2600)
   1    0   SORT (ORDER BY) (Cost=6 Card=50 Bytes=2600)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=50 Bytes=2600)
   3    2       INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=4)

it would have sorted after the fact....

      Rating: 3
Errata -- one
      more thing I forgot to mention  March 27, 2003

Reviewer:  Tatiane
Sorry... in 2) above, suppose I'm not using 9i's skip scanning -- suppose I'm
using 8i. With skip-scanning, t(a,b,c) and t(a,c,b) would work the same -- not
considering performance.

          Followup:

you do not need to have skip scans for those to be very much almost the same.

But tell me -- you need to explain how

t(a)
t(b)
t(c)

would be superior to a query that would make use of t(a,b,c) in the first place!

      Rating: 5
What if
      composite index and individual column indexes
      co-exist...  March 28, 2003
Reviewer:
      A reader
What would Oracle use if the composite index on t(x,y,z) and the indexes on
individual columns t(x), t(y), t(z) co-exist? Is there any benefit to have them
all, or is it even worse than simple wasting of space? Thanks.

          Followup:

yes it would.

the index on t(x) might be considered redundant in that set of indexes.

March 28, 2003 Bob -- Thanks for the question regarding "Strange
      result when trying to call Java Stored Procedure with no parameters.",
      version 9.0.1

      You Asked
Tom,
I've gotten a strange result when trying to call a Java Stored proc with no
parameters. The procedure is part of a java class that needs to call truncate
table statements in other schemas. From Schema_A I can call
Schema_B.trucate_table_proc() and it will truncate the said table. However, when
doing as below, I get a boolean false from the execute() method, but no SQL
error and the query doesn't run. If I use a prepared statement and simply
"'delete from table..." it works. But I need to truncate, not delete.

create or replace and compile java source named my_class as
import java.sql.*;
import java.math.BigDecimal;
import java.util.StringTokenizer;
import java.util.ArrayList;
import java.text.SimpleDateFormat;
import oracle.jdbc.driver.*;
import com.sybase.jdbc2.jdbc.*;
import oracle.sql.*;
public class my_class {
  public static void doLoad()
  throws java.sql.SQLException {
    try {
      Connection sybaseConnection = null;
      Connection oracleConnection = null;
      Statement sybaseStatement = null;
      ResultSet rst = null;
      boolean xx =  true;
      PreparedStatement oracleStatement = null;
      String inUser = "username";
      String inPassword = "password";
      //String outUser = "username";
      //String outPassword = "password";
      String inDbUrl = "jdbc:sybase:Tds:IP_AND_PORT";

      Class.forName("com.sybase.jdbc2.jdbc.SybDriver"); //Load the Sybase JDBC
Drivers
      Class.forName("oracle.jdbc.driver.OracleDriver");
      sybaseConnection = DriverManager.getConnection(inDbUrl, inUser,
inPassword);

      /* If Inside the database do this */
      oracleConnection = new OracleDriver().defaultConnection();
  oracleConnection.setAutoCommit(true);
// this all works so I am seeing the DB
System.out.println(" DB " +
oracleConnection.getMetaData().getDatabaseProductName());
System.out.println(" DB " +
oracleConnection.getMetaData().getDatabaseProductVersion());
      //sybaseStatement = sybaseConnection.createStatement();
// below is the procedure I need to call
CallableStatement cstmt =
oracleConnection.prepareCall("{Schema_B.trucate_table_proc()}");
 xx = cstmt.execute();

System.out.println("After cstmt.execute -- shouldn't the statement have run?" +
xx );

cstmt.close();

      //oracleConnection.close();
    }
   catch(Exception ex) {
        String strMessage = ex.toString();
        System.out.println("Exception: " + strMessage);
        ex.printStackTrace();
        #sql {
           begin raise_application_error(-20000, 'Java Exception ' ||
:strMessage); end;
           begin dbms_output.put_line('Java Exception ' || :strMessage); end;
        };
      }
    }
  }
 Again, no error, but the boolean xx returned is false. There are no in params
or out params. I just want to issue the trunc tables at this point. WHat am I
missing in the CallableStatement?

Bob

      and we said...
not that this works:
    oracleConnection.setAutoCommit(true);
but why why why would you consider doing such a nasty thing?

But, according to:

http://java.sun.com/j2se/1.3/docs/api/java/sql/Statement.html#execute(java.lang.String
a preparedStatment.execute Returns:

    true if the next result is a ResultSet object; false if it is an update
count or there are no more results

So, I would expect false.  I set up a small test case.  It executes exactly as
expected.  Had to get rid of all of those nasty sybase words though:

ops$tkyte@ORA920> create user a identified by a;
User created.

ops$tkyte@ORA920> create user b identified by b;
User created.

ops$tkyte@ORA920> grant create session, create procedure to a;
Grant succeeded.

ops$tkyte@ORA920> grant create session, create procedure, create table to b;
Grant succeeded.

ops$tkyte@ORA920> alter user b default tablespace users quota unlimited on
users;
User altered.

ops$tkyte@ORA920> @connect b/b

b@ORA920> create table t ( x int );
Table created.

b@ORA920> insert into t select rownum from all_users;
40 rows created.

b@ORA920> create or replace procedure truncate_table_proc
  2  as
  3  begin
  4      execute immediate 'truncate table t';
  5  end;
  6  /
Procedure created.

b@ORA920> grant execute on truncate_table_proc to a;
Grant succeeded.

b@ORA920> @connect a/a

a@ORA920> create or replace and compile java source named my_class
  2  as
  3  import java.sql.*;
  4  import oracle.jdbc.driver.*;
  5  public class my_class
  6  {
  7    public static void doLoad() throws Exception
  8    {
  9      Connection oracleConnection = null;
 10      Class.forName("oracle.jdbc.driver.OracleDriver");
 11
 12      oracleConnection = new OracleDriver().defaultConnection();
 13      System.out.println
 14      (" DB " + oracleConnection.getMetaData().getDatabaseProductName());
 15      System.out.println
 16      (" DB " + oracleConnection.getMetaData().getDatabaseProductVersion());
 17
 18      CallableStatement cstmt =
 19          oracleConnection.prepareCall("begin b.truncate_table_proc; end;");
 20      boolean xx = cstmt.execute();
 21      System.out.println("After cstmt.execute " + xx );
 22
 23      cstmt.close();
 24    }
 25  }
 26  /

Java created.

a@ORA920> create or replace procedure my_class
  2  as
  3  language java name 'my_class.doLoad() ';
  4  /

Procedure created.

a@ORA920> set serveroutput on
a@ORA920> exec dbms_java.set_output(1000000)

PL/SQL procedure successfully completed.

a@ORA920> exec my_class
DB Oracle
DB Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
After cstmt.execute false

PL/SQL procedure successfully completed.

a@ORA920>
a@ORA920> @connect b/b

b@ORA920> select * from t;

no rows selected

March 28, 2003 Mamta -- Thanks for the question regarding
      "loading data from .txt to table takes half an hour for 25000 to 30000
      records", version oracle 8i

      You Asked
Dear Tom,

We are trying to upload data using utl_package from a .txt file
which has around 25,000 to 30,000 records , we are just
checking looking in one table for the matching field and
inserting the record in another table but the query is taking 30
minutes which is too long and performace is the major issue for
the site which I am developing . Can you throw some light on
this . In our query all unmatched records we are putting in the
error log .

I donot want the query to take more than a minute . Is it
possible in oracle or not (25000 to 30000 records in one shot)

Regards
mamta

      and we said...
Without actually seeing your PLSQL - I cannot optimize it,
however....  I would not do this in plsql myself.

I would use SQLLDR to load the 30k records into the table.  The
table would have a foreign key to the matching field.  We would
use a bad file to capture the records that do not have a match.
We would them load that bad file into the error table.

For example, I just "unloaded" my all_objects table -- about 25k
records.  I created a table that looks just like all_objects and
created a foreign key on the object_name field to another table.
 That other table was created via:

scott@DEV816> create table parent_table
  2  as
  3  select object_id from all_objects where rownum < 20000;
Table created.

scott@DEV816> alter table parent_table add constraint
  2  parent_pk primary key(object_id);
Table altered.

scott@DEV816> alter table t add constraint
  2  t_fk_to_parent foreign key (object_id)
  3  references parent_table;
Table altered.

Using the conventional path loader, it took much less then 1
minute to load that file using the command line options:

sqlldr userid=scott/tiger control=t.ctl bad=/dev1/t.bad \
       errors=99999999999999 rows=1000 bindsize=1000000 \
       log=/dev2/t.log

and a couple of seconds to load the 4k or so "bad" records.

Let the database do the work for you....

Review &
      Followup

      Rating:
      5
  November 25, 2002
Reviewer:
      A reader
Tom,

I want to capture the sqlldr run statistics, (like rows successfully loaded,
rows skipped due to logical errors, run time etc..) into a database table. (That
will help me keep track of the runtimes of all our sqlldr runs).I know that they
are available in the logfile and we can use shell+awk to capture the information
into the database. Is there any other method? May be it can be queried staight
out of some v$/x$ tables which you know?

We are on 8.1.7 still, so we can't use the external tables yet.

          Followup:

It is not in any table -- sqlldr is just a client program.

      Rating: 5
sqlldr
      views  November 25, 2002
Reviewer:
      A reader
I take it views like v$loadcstat and v$loadtstat are used by sqlldr to generate
its feedback information?  Any tips/tricks where these views could be used to
collect/record stats, etc.

          Followup:

yes -- and their names change over time and their contents ALWAYS appears empty
*except* to the sqlldr (or direct path writer -- using OCI for example) session.
 This information is stored in the PGA of the load.

      Rating:
      4
  November 25, 2002
Reviewer:
      A reader
OK. What about in 9i when we use external tables? How can we capture the
loaded/rejected rows info inside the db?

          Followup:

loaded rows = sql%rowcount

rejected rows = read the log file, or read the bad file after the load.

      Rating:
      5
  March 27, 2003
Reviewer:
      A reader
I wrote a program to grep the sqlldr logfiles and load it into a table. I am
facing difficulty in converting the elapsed time and cpu time to date. I am not
sure which date format conversion mask to be used here.

Eg: How do you convert these strings into date format?

Elapsed time was:     00:21:01.48
CPU time was:         00:00:26.74

Also, could you please show how these can be converted to timestamp format in
9i.

Thanks.

          Followup:

well, that is not really a 'date', it is a duration, an interval of time.  I
suppose you could store it as a time in a date field -- but it doesn't really
make "sense".

to_date( substr(:a,1,8),'HH24:MI:SS')+(to_char(sysdate,'dd')-1)

in 8i would store it as a DATE -- but then what?   Might as well just store the
string...

In 9i, you can store it as an interval type.  A duration

  1* select to_dsinterval( '0 ' || '00:21:01.48' ) from dual
ops$tkyte@ORA9I> /

TO_DSINTERVAL('0'||'00:21:01.48')
---------------------------------------------------------------------------
+000000000 00:21:01.480000000

that give you a day to second interval type.

      Rating:
      3
  March 28, 2003
Reviewer:
      A reader
I agree it does not mean much to convert it to a date. I am currently storing it
as a string. But, I would like to be able to perform calculations on that (eg, I
would like to sum up the elapsed times, or take difference of the timings of two
tables etc.). Can you suggest how I can do that? I was considering the option of
splitting up the string and storing HH, MI, SS and HS parts in seperate columns.
I am sure you can suggest a better way. As always, thanks verymuch for your
time.

          Followup:

well, you cannot do that with a date -- adding 2am on tuesday with 2pm on
tuesday does not give you 4pm on tuesday.

You can store it in a number in 8i as:

hh/24 + mi/24/60 + ss/24/60/60

Then, you can add those up and they'll be a multiple of a day.  So, a sum of 1.1
would be 1 day and 1/10th of a day.

In 9i this would be very natural as an interval type.

March 29, 2003 Sonu -- Thanks for the question regarding "SQL ",
      version 9.0.1

      You Asked
I have a table whos columns are
Table name = my_transactions
Columns:
Person_id ,Transaction_dt,Amount .

Problem: I want to find out payers in say March03 and how they paid in
March'02.I write an join query get an output like:

Person_id    Dt_march_03  Amount_03  Dt_March_02    Amount_02
123          20-March-03   1000       05-March-02    500
123          20-March-03   1000       10-March-02    1000

The problem is '123' paid once in March'03 but twice last in March'03,so
20-March-03 repeats which I want to prevent.
My desired output would be like :

Person_id   Dt_march_03  Amount_03  Dt_March_02    Amount_02
123         20-March-03   1000       05-March-02    500
                                     10-March-02    1000

If there is another payment date say 25-March-03 I would like to have
an output like

id    Dt_march_03  Amount_03  Dt_March_02    Amount_02
123   20-March-03   1000       05-March-02    500
123   25-March-03   2000       10-March-02    1000

Thanks

      and we said...
ops$tkyte@ORA9I> create table t ( person_id number, transaction_dt date, amount
number );

Table created.

ops$tkyte@ORA9I> insert into t values ( 123, '20-mar-2003', 1000 );
ops$tkyte@ORA9I> insert into t values ( 123, '05-mar-2002', 500 );
ops$tkyte@ORA9I> insert into t values ( 123, '10-mar-2002', 1000 );

ops$tkyte@ORA9I> insert into t values ( 456, '07-mar-2003', 500 );
ops$tkyte@ORA9I> insert into t values ( 456, '25-mar-2003', 1000 );
ops$tkyte@ORA9I> insert into t values ( 456, '15-mar-2002', 255 );

ops$tkyte@ORA9I> insert into t values ( 789, '17-mar-2003', 1243 );
ops$tkyte@ORA9I> insert into t values ( 789, '12-mar-2003', 5423 );

ops$tkyte@ORA9I> insert into t values ( 101, '17-mar-2002', 1243 );
ops$tkyte@ORA9I> insert into t values ( 101, '12-mar-2002', 5423 );

ops$tkyte@ORA9I> variable dt varchar2(20)
ops$tkyte@ORA9I> exec :dt := '01-mar-2003'
PL/SQL procedure successfully completed.

we will start with our "foundation" query.  The one that gets our data.
Rather then JOIN (for it will necessitate a full outer join and we can easily
avoid that), we'll UNION ALL the data.  We'll get this years data -- and for
year the person_id, transaction_dt and amt -- we'll get NULL values right now
for last year. Additionally -- we'll assign a row_number (1,2,3...) to each row
BY PERSON_ID after sorting by transaction_dt within a person_id.

Then, we get last years data -- doing the same thing with the nulls and
rownum:

ops$tkyte@ORA9I> select 1 year,
  2         person_id, transaction_dt td1, amount amt1,
  3         to_date(null) td2, to_number(null) amt2,
  4         row_number() over
  5           ( partition by person_id
  6             order by transaction_dt ) rn1,
  7         to_number(null) rn2
  8    from t
  9    where transaction_dt between to_date(:dt)
 10                             and last_day(to_date(:dt))-0.0001
 11  union all
 12  select 2 year,
 13         person_id, to_date(null), to_number(null),
 14         transaction_dt, amount,
 15         to_number(null),
 16         row_number() over
 17            ( partition by person_id
 18              order by transaction_dt )
 19    from t
 20   where transaction_dt between add_months(to_date(:dt) , -12 )
 21                        and last_day(add_months(to_date(:dt),-12))-0.0001
 22  /

YEAR  PERSON_ID TD1             AMT1 TD2             AMT2        RN1        RN2
---- ---------- --------- ---------- --------- ---------- ---------- ----------
   1        123 20-MAR-03       1000                               1
   1        456 07-MAR-03        500                               1
   1        456 25-MAR-03       1000                               2
   1        789 12-MAR-03       5423                               1
   1        789 17-MAR-03       1243                               2
   2        101                      12-MAR-02       5423                     1
   2        101                      17-MAR-02       1243                     2
   2        123                      05-MAR-02        500                     1
   2        123                      10-MAR-02       1000                     2
   2        456                      15-MAR-02        255                     1

10 rows selected.

Now, we need to "squash the rows" together.  We want to slide the td2/amt2
values up by PERSON_ID and RN1/RN2.  A simple MAX() with a group by person_id,
coalesce(rn1,rn2) does that.  coalesce returns the first NON-NULL value from a
list of columns - working nicely in this case:

ops$tkyte@ORA9I> select person_id, max(td1), max(amt1), max(td2), max(amt2)
  2    from (
  3  select 1 year,
  4         person_id, transaction_dt td1, amount amt1,
  5         to_date(null) td2, to_number(null) amt2,
  6         row_number() over
  7           ( partition by person_id
  8             order by transaction_dt ) rn1,
  9         to_number(null) rn2
 10    from t
 11    where transaction_dt between to_date(:dt)
 12                             and last_day(to_date(:dt))-0.0001
 13  union all
 14  select 2 year,
 15         person_id, to_date(null), to_number(null),
 16         transaction_dt, amount,
 17         to_number(null),
 18         row_number() over
 19            ( partition by person_id
 20              order by transaction_dt )
 21    from t
 22   where transaction_dt between add_months(to_date(:dt) , -12 )
 23                        and last_day(add_months(to_date(:dt),-12))-0.0001
 24         )
 25   group by person_id, coalesce(rn1,rn2)
 26  /

 PERSON_ID MAX(TD1)   MAX(AMT1) MAX(TD2)   MAX(AMT2)
---------- --------- ---------- --------- ----------
       101                      12-MAR-02       5423
       101                      17-MAR-02       1243

       123 20-MAR-03       1000 05-MAR-02        500
       123                      10-MAR-02       1000

       456 07-MAR-03        500 15-MAR-02        255
       456 25-MAR-03       1000

       789 12-MAR-03       5423
       789 17-MAR-03       1243

8 rows selected.

and that seems to be the result you wanted...
Review &
      Followup

      Rating:
      5
SQL  March 27, 2003
Reviewer:  Sonu
      Sehgal  from Boston
Exact Solution.

Thanks

      Rating:
      5
Awesome  March 28, 2003
Reviewer:
      Vladimir  from Albany, NY
As simple as ingeniously!
Tom, why didn't you use NVL function instead of Coalesce?
Thank you,
Vladimir

          Followup:

because in general there are N (rn's) to coalesce over -- I borrowed this idea
from similar problem -- a pivot -- with 3 rn's

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6923393629227
did point out there in 8i you would have to use

nvl(rn1,nvl(rn2,rn3))

March 29, 2003 Parag -- Thanks for the question regarding
      "PL-SQL Server Pages", version 8.1.6

      You Asked
Hi Tom :

   Right now we building all Application in Either ASP or JSP. But i am very
Strong beliver of Oralce. I came to know that , Oracle has come with PSP (Pl-Sql
Server Pages). Tom , please can u send some Technical Details on PSP.

   1. Can PSP Work only on Oralce App Server 9i or it can work on any
      Other Webserver like Resin , Java Web Server , IIS , Tomcat e.t

   2. What is main Diff between PSP & ASP/JSP

   3. How PSP can talk with Databse ie through JDBC/ODBC or Directly with some
Native Deriver , it can talk with Oracle.

   4. How can we benifited by using PSP (Performance)

   5. What kind of Hardware & Software setup we required for PSP.

   6. I am sure that PSP is going to work much more fater compare to
ASP/JSP/PHP. But what are the main Key points of PSP.

   7. Through PSP , can we do Uplaod & Download of File.  is any SmartUpload
features given by Oracle.

   8.  How complex will be the coding for PSP (I am sure that it wann't be
complex. It will be much more Simpler)

   9.  For which kind of Application , we can use PSP ?

   10. Can PSP work accross the Platform ?

   Tom ... please pass as much as u can Technical INfo on PSP. I am very much
igor to use PSP for my next App Development.

Thanks & Regards
Parag Mehta

      and we said...
1) psp's require the use of the mod-plsql module running under apache or IIS
(see

http://technet.oracle.com/docs/products/ias/doc_library/1021doc_otn/index.htm
for details on IIS integration)

2) the language.  psp is an asp that uses plsql instead of VB.

3) the mod_plsql module (an oci program) does this.  a psp is compiled into a
stored procedure, mod_plsql calls the stored procedure

4) the database is a very scalable thing.  you are generating data from the
database, less round trips and so on.

6) if you know asp/jsp -- they are functionaly equivalent.  The major difference
is that psp is hosted IN the database. asp and jsp are found outside of the
database (although you can put a jsp IN the database as well)

7) mod_plsql supports file upload and download into LOBS in the database.  I do
not know what you mean by "smartupload"

8) it is very similar to asp/jsp -- same level of complexity, different
language.

9) any

10) "across the platform?"  psp work where ever Oracle works.

Review &
      Followup

      Rating:
      3
PSP  May 22, 2001
Reviewer:  Zain
      Merchant  from Mumbai (INDIA)
Can u plz give some Site Listing which are using PSP ? Is there any Link/Page at
Oralce Site ?

Thanks
- ZainAli

      Rating: 4
Do we have
      mod_plsql module for iPlanet webserver?  March 28, 2003

Reviewer:  Saminathan Seerangan  from USA
I don't know whether this is to respond?

http://otn.oracle.com/pls/db92/db92.homepage
is an example of PSP site

          Followup:

is iplanet apache.

March 29, 2003 Tomas -- Thanks for the question regarding
      "having more then 1 listener process", version

      You Asked
It is possible to have multiple listener processes
listening for the same instance and how?
(Environment: Oracle 7.3.4.5; dedicated server connection
schema;HP-UX/Digital UNIX;tcp/ip)
setenv TNS_ADMIN `pwd`
lsnrctl start
cd ..
!set
!ls

      and we said...
Yes.  you just need 1 install and >1 listener.ora file.

You would use the TNS_ADMIN environment variable to tell each
listener where to get its configuration from.  The TNS_ADMIN
environment variable overrides the default directory for the
network configuration files.

This is a common requirement for users who run "extprocs"
(external procedures) in oracle8.0 and up for example.  Since an
extproc runs as the userid who started the listener, it is
common to setup a listener that runs as the user 'nobody' or
some other low priv'ed account to spawn the extprocs.  A
'regular' listener listens for all other inbound requests.

Here is a 7.3.4 example I just did.  I copied my listener.ora
from network/admin to network/admin/another.  I changed the PORT
and the KEY in the second one.  Then:

Thu Feb 10 19:32:18 EST 2000
(ora734@slackdog) /d01/home/ora734/network/admin/another

Thu Feb 10 19:32:18 EST 2000
(ora734@slackdog) /d01/home/ora734/network/admin/another

LSNRCTL for Solaris: Version 2.3.4.0.0 - Production on 10-FEB-00
19:32:43

Copyright (c) Oracle Corporation 1994.  All rights reserved.

Starting /d01/home/ora734/bin/tnslsnr: please wait...

TNSLSNR for Solaris: Version 2.3.4.0.0 - Production
System parameter file is
/d01/home/ora734/network/admin/another/listener.ora
Log messages written to
/d01/home/ora734/network/log/listener.log
Listening on: (ADDRESS=(PROTOCOL=ipc)(DEV=10)(KEY=ora734))
Listening on:
(ADDRESS=(PROTOCOL=tcp)(DEV=14)(HOST=138.2.5.206)(PORT=1566))

Connecting to (ADDRESS=(PROTOCOL=IPC)(KEY=ora734))
STATUS of the LISTENER
------------------------
Alias                     LISTENER
Version                   TNSLSNR for Solaris: Version 2.3.4.0.0
- Production
Start Date                10-FEB-00 19:32:46
Uptime                    0 days 0 hr. 0 min. 0 sec
Trace Level               off
Security                  OFF
SNMP                      OFF
Listener Parameter File
/d01/home/ora734/network/admin/another/listener.ora
Listener Log File
/d01/home/ora734/network/log/listener.log
Services Summary...
  ora734                has 1 service handler(s)
The command completed successfully

So, that shows I have a listener on port 1566 and the key
(listener name) is ora734.  Now I cd and do it over:

Thu Feb 10 19:32:18 EST 2000
(ora734@slackdog) /d01/home/ora734/network/admin/another

Thu Feb 10 19:32:49 EST 2000
(ora734@slackdog) /d01/home/ora734/network/admin
setenv TNS_ADMIN `pwd`

Thu Feb 10 19:32:49 EST 2000
(ora734@slackdog) /d01/home/ora734/network/admin
lsnrctl start

LSNRCTL for Solaris: Version 2.3.4.0.0 - Production on 10-FEB-00
19:32:56

Copyright (c) Oracle Corporation 1994.  All rights reserved.

Starting /d01/home/ora734/bin/tnslsnr: please wait...

TNSLSNR for Solaris: Version 2.3.4.0.0 - Production
System parameter file is
/d01/home/ora734/network/admin/listener.ora
Log messages written to
/d01/home/ora734/network/log/listener.log
Listening on: (ADDRESS=(PROTOCOL=ipc)(DEV=10)(KEY=ora734a))
Listening on:
(ADDRESS=(PROTOCOL=tcp)(DEV=14)(HOST=138.2.5.206)(PORT=1525))

Connecting to (ADDRESS=(PROTOCOL=IPC)(KEY=ora734a))
STATUS of the LISTENER
------------------------
Alias                     LISTENER
Version                   TNSLSNR for Solaris: Version 2.3.4.0.0
- Production
Start Date                10-FEB-00 19:32:59
Uptime                    0 days 0 hr. 0 min. 0 sec
Trace Level               off
Security                  OFF
SNMP                      OFF
Listener Parameter File
/d01/home/ora734/network/admin/listener.ora
Listener Log File
/d01/home/ora734/network/log/listener.log
Services Summary...
  ora734                has 1 service handler(s)
The command completed successfully

Now, i've got a second listener on port 1525 named ora734a.

Review &
      Followup

      Rating: 3
A useful
      answer  August 07, 2001
Reviewer:  LiBin Jia
       from China
It's a clever idea.
You help me get over a hard work,thank you
but I have another a question :
   " If my Listener have more than one service handler
working for just one port ,the server could not handle
 one request ,how to delete the more process"

      Rating: 5
please
      clarify  March 28, 2003
Reviewer:  Yogeeraj
       from Mauritius
Dear sir,

can you please ellaborate on this with an example if possible (or better guide
me to a link):
<quote>
is common to setup a listener that runs as the user
'nobody' or some other low priv'ed account to spawn the extprocs.
</quote>

How can i do a:
su - nobody -c "$ORACLE_HOME/bin/lsnrctl start"

if su - nobody gives me the following error:

su: Invalid ID

Also note that on my HP-UX, nobody exists:
SLX1:sbin/init.d>cat /etc/passwd|grep nobody
nobody:*:-2:-2::/:
SLX1:sbin/init.d>

thank you in advance for your precious guidance

Best Regards
Yogeeraj

          Followup:

talk to your system administrator and ask them to setup an appropriate account
for you.

Your OS admin will know how to accomplish OS specific tasks like this.

March 29, 2003 Ashiq -- Thanks for the question regarding "Is it
      possible to bring down the consistent get ??", version 8.1.7

      You Asked
Hi Tom,

  I've table which has 80000 records(even it can be more also) .This table
doesn't have primary key ,and cordinality is very less.I'll selecting all the
records from this tables.

  I can get uniqueness if we combine 7 - 9 columns.That too cordinality is less.

owner50rms@RMSREAL9> desc TEMP_T_H_TEMP_EXPORTPROFILE
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
------------------------------------
 PK_RMS_ID                                             NOT NULL NUMBER(11)
 SEX                                                            VARCHAR2(1)
 IX_LAST_NAME                                          NOT NULL VARCHAR2(25)
 IX_FIRST_NAME                                         NOT NULL VARCHAR2(25)
 ADDRESS_1                                                      VARCHAR2(50)
 ADDRESS_2                                                      VARCHAR2(50)
 POSTCODE                                                       VARCHAR2(15)
 FK_STATE                                                       VARCHAR2(8)
 ADDRESS_3                                                      VARCHAR2(50)
 PHONE_HOME                                                     VARCHAR2(17)
 PHONE_WORK                                                     VARCHAR2(17)
 FAX                                                            VARCHAR2(17)
 PERSON_TYPE                                           NOT NULL VARCHAR2(4)
 FK_CALL_ACCOUNTING_ID                                 NOT NULL NUMBER(11)
 TITLE                                                          VARCHAR2(5)
 E_MAIL                                                         VARCHAR2(50)
 IX_NATIONAL_ID                                                 VARCHAR2(15)
 IX_NEW_NATIONAL_ID                                             VARCHAR2(15)
 FK_MARITAL_STATUS                                     NOT NULL VARCHAR2(25)
 BIRTH_DATE                                                     DATE
 MEDICAL                                                        VARCHAR2(50)
 FK_RELIGION                                           NOT NULL VARCHAR2(20)
 FK_STUDENT_TYPE                                       NOT NULL VARCHAR2(5)
 IX_STUDENT_NUMBER                                              VARCHAR2(20)
 FK_SCHOOL                                             NOT NULL VARCHAR2(30)
 FK_MAJOR                                              NOT NULL VARCHAR2(30)
 STUDENT_PROFILE_COMMENT                                        VARCHAR2(250)
 INTERESTS                                                      VARCHAR2(50)
 PROGRAM1                                                       VARCHAR2(50)
 PROGRAM2                                                       VARCHAR2(50)
 FK_NATIONALITY                                        NOT NULL VARCHAR2(25)
 TER_SCORE                                             NOT NULL FLOAT(126)
 SCHOOL_ATTENDED                                                VARCHAR2(30)
 GPA                                                            VARCHAR2(15)
 SPORTS                                                         VARCHAR2(50)
 SCHOOL_GRAD_DATE                                               DATE
 COURSE_YEAR                                                    VARCHAR2(15)
 SCHOLARSHIP                                           NOT NULL NUMBER(18,5)
 BURSARY                                               NOT NULL NUMBER(18,5)
 ASSISTANCE_ACCEPTED                                            DATE
 FK_SCHOOL_CATEGORY                                    NOT NULL VARCHAR2(10)
 FK_LOCATION                                           NOT NULL VARCHAR2(10)
 FK_INSTITUTION                                        NOT NULL VARCHAR2(15)
 FK_CATERING                                           NOT NULL VARCHAR2(5)
 FK_ETHNICITY                                          NOT NULL VARCHAR2(25)
 APPLICATION_CANCEL_DATE                                        DATE
 PK_CONTACT_ID                                                  NUMBER(11)
 CONTACT_PROFILE_RELATION                                       VARCHAR2(15)
 OCCUPATION                                                     VARCHAR2(50)
 USER_DEF_TEXT1                                                 VARCHAR2(50)
 USER_DEF_TEXT2                                                 VARCHAR2(50)
 USER_DEF_TEXT3                                                 VARCHAR2(50)
 USER_DEF_TEXT4                                                 VARCHAR2(50)
 USER_DEF_TEXT5                                                 VARCHAR2(50)
 USER_DEF_TEXT6                                                 VARCHAR2(50)
 USER_DEF_TEXT7                                                 VARCHAR2(50)
 USER_DEF_TEXT8                                                 VARCHAR2(50)
 USER_DEF_TEXT9                                                 VARCHAR2(50)
 USER_DEF_TEXT10                                                VARCHAR2(50)
 USER_DEF_TEXT11                                                VARCHAR2(50)
 USER_DEF_TEXT12                                                VARCHAR2(50)
 USER_DEF_TEXT13                                                VARCHAR2(50)
 USER_DEF_TEXT14                                                VARCHAR2(50)
 USER_DEF_TEXT15                                                VARCHAR2(50)
 USER_DEF_INT1                                                  NUMBER(11)
 USER_DEF_INT2                                                  NUMBER(11)
 USER_DEF_INT3                                                  NUMBER(11)
 USER_DEF_INT4                                                  NUMBER(11)
 USER_DEF_INT5                                                  NUMBER(11)
 USER_DEF_DOUBLE1                                               NUMBER(8,2)
 USER_DEF_DOUBLE2                                               NUMBER(8,2)
 USER_DEF_DOUBLE3                                               NUMBER(8,2)
 USER_DEF_DOUBLE4                                               NUMBER(8,2)
 USER_DEF_DOUBLE5                                               NUMBER(8,2)
 USER_DEF_DATE1                                                 DATE
 USER_DEF_DATE2                                                 DATE
 USER_DEF_DATE3                                                 DATE
 USER_DEF_DATE4                                                 DATE
 USER_DEF_DATE5                                                 DATE
 USER_DEF_YES_NO1                                               NUMBER(1)
 USER_DEF_YES_NO2                                               NUMBER(1)
 USER_DEF_YES_NO3                                               NUMBER(1)
 USER_DEF_YES_NO4                                               NUMBER(1)
 USER_DEF_YES_NO5                                               NUMBER(1)
 REFERENCE_TEXT                                                 VARCHAR2(100)
 APPLICATION_TYPE                                               VARCHAR2(50)
 APPLICATION_PERIOD_NAME                                        VARCHAR2(50)
 CK_SHOW_NAME                                                   VARCHAR2(50)
 IX_MIDDLE_NAME                                                 VARCHAR2(25)
 ADDRESS_1B                                                     VARCHAR2(50)

owner50rms@RMSREAL9> select count(*) from temp_t_h_temp_ExportProfile;

  COUNT(*)
----------
     83207
owner50rms@RMSREAL9> set autot on

owner50rms@RMSREAL9> select count(*) from temp_t_h_temp_ExportProfile;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'TEMP_T_H_TEMP_EXPORTPROFILE'

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

   Is any way to bring down the consistent gets??

2. In sql server they have lock for a procedure.This is will make others to wait
to execute that procedure ,till he releases the lock.

    I know , to my knowledge as such there  lock like this.Is it possible to do
in oracle.Like make others wait till i finish execute the procedure. At any
point of time only user should execute that procedure.

thanx

A.Ashiq

      and we said...
1) what is the goal -- is it to simply bring down the consistent gets for a
count(*)?  If so, just index ANY not null column and analyze the table.
count(*) will do an index fast full scan instead.

2) see the DBMS_LOCK package.  You would aquire an exclusive lock at the
beginnging of the routine, run and then have it release on commit.  Everyone
will serialize on that lock.

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76936/dbms_loc.htm#998100
Review
      & Followup

      Rating: 5
more
      usefull  March 30, 2003
Reviewer:  Ashiq
      Shamsudeen  from Trichy, Tamil Nadu, India

March 29, 2003 Omar -- Thanks for the question regarding
      "Compare between two tables and other database objects", version 8.1.7

      You Asked
Dear Tom,
Does ORACLE has any function (like ADD_MONTHS,TTRUNC,...etc)
to compare between tables consider the name and the number of attributes?,
i.e I have three enviornments Development Test and Production and some times I
change the structure of a table in one of these enviornment to do a certain task
and after testing this change  I want to change the Production database tables
to add the same columns and the same changes but I have to compare between the
table in Development and Production enviornment column by column and some tables
are really huge (more than 128 columns) so I want to ask about an ORACLE table
that store the name and attributes of the objects in the database and what is
the SELECT should I use to do this comparison or about an ORACLE Function to
perform this task,
Is it possible?,
Regards,
Omar.

      and we said...
I would suggest that your method is haphazzard at best.

Are there tools?  Sure, OEM has the change management packs, there are dozens of
tools that will "diff and merge" however....

In a real environment, where people are counting on their data to be available
day to day, without issue -- you would:

a) make your changes in development, test them.

b) provide a testing environment with two scripts.  patch.sql and
backout_patch.sql.  patch.sql is the series of well documented, formal commands
to upgrade.  backout_patch is the reverse.  It'll restore the database back to
the way it was in the very real chance that the patch needs to be undone.

c) a tester, different from you, applies said patch.  applies backout_patch,
applies patch, tests patch.

d) tester, upon certifying patch actually works -- checks code into whatever
source code control system you are using and schedules an upgrad to production.

You now have a very good chance of meeting that desire to provide access to the
database in a stable, always up manner.

You need to get a bit more control on what you are doing.  simply "merge these
schemas together" isn't a really good idea.  "apply this documented formal
patch" -- that is the only way to do this properly.
Review &
      Followup

      Rating: 5
Schema
      comparison  March 31, 2003
Reviewer:  Logan
      Palanisamy  from Santa Clara, CA USA
Omar,

What Tom is saying is very valid and prudent. Anyway, here is a that might help
you.

http://asktom.oracle.com/pls/ask/f?p=4950:8:206956101770570530::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:941629680330,

      Rating: 5
TOAD - Compare
      Schema  March 31, 2003
Reviewer:
      A reader
TOAD also has a 'Compare Schema' feature which lets you find out all the
differences between two schemas withing the same database or across databases

March 29, 2003 Doug -- Thanks for the question regarding "Repost
      link to flashback query paper?", version 9.2.0.1

      You Asked
Tom - a couple months ago you had a link to a paper that had someone had put
together distilling all the information on flashback query he found on your
website.  Can you repost?

      and we said...
http://asktom.oracle.com/~tkyte/flashback.pdf
Review
      & Followup

      Rating:
      3
link  May 06, 2003
Reviewer:
      juancarlosreyesp@yahoo.com  from Bolivia
Hi Tom, I had been working in a page, where is the last version of the paper (as
it is), I work in.
As you are busy I'm putting there, maybe it could be useful to some one.
As you'll see I don't have expertise in html desing, and I hope you like the
toons :).

I did it the best I could. I copied the form to search in Oracle 9i release 2
online documentation too.

http://www.geocities.com/juancarlosreyesp
Any suggestion.

:) Thanks

March 30, 2003 Jamil -- Thanks for the question regarding
      "Backup ", version 8.0.4

      You Asked
Hi Tom,When I am Taking Backup The Users Can Not Use the Program Bacuse ,the
System Bacome very slow , and they have to wait tell the system is finished the
backup takes 70 minutes,our data is 700 MB the command that I am using for
backup as Follow :
Exp80 app/app file=satud1.dmp
and I am doing this work twice a day in the morining and evening .
Thanks For Your Help in advance
Best Regards
Jamil AlShaibani

      and we said...
EXP is NOT a backup tool.

You do not want to use export as a backup tool.

You NEED to run your database in archive log mode.

You can then do hot backups (simple datafile copies).

It will not affect performance (other then the addition IO you incurr during the
copy).

You can do it while people are using the database.  You can do it less often
(not 2 times a day) as the archives are part of your backup.

EXP is just a program that throughs a ton of SQL at the database.  If running a
single export session brings your system to its knees, you might consider adding
resources (memory, disk, cpu) as you are running at max capacity as it is.

In short -- stop using exp and do proper backups and make sure your database is
running in archivelog mode.
Review &
      Followup

      Rating: 1
Backup
        October 28, 2001
Reviewer:  Jamil
      Al_Shaibani  from SAUDI ARABIA
your answer is not clear to me , can you please give an example with your reply
,and explain how can I Run the database in archive log mode,because I don't have
any Idea about it.and what option do I have to use to ask you
a question,when ever I need your help.
Thank you very much for help
Jamil Alshaibani

          Followup:

Ok, if you have never heard of archive log mode and don't know anything about
it, yet you manage the Oracle databases - we have a problem.

There are two books you need to read straight away and both can be found at:

http://technet.oracle.com/doc/server.804/index.htm
they are the
Oracle8 Administrator's Guide, Release 8.0
Oracle8 Backup and Recovery, Release 8.0

You may also be interested in

http://www.amazon.com/exec/obidos/ASIN/0072127171
which is reputed to be a pretty good book on this topic.

(as to the asking of questions -- when I'm taking the, you can post them on
http://asktom.oracle.com/
When I'm not (cause I already have a bunch), I take away the link.  I like to
answer all questions in a timely fashion and that would not be possible --
either to answer all or do it in a timely fashion - if I did not control the
number of questions in the queue.

      Rating:
      2
backup  October 31, 2001
Reviewer:
      Ahmed  from Saudi Arabia
Tom,

I am not sure. But i have a gut feeling that the restore would be a problem. May
be out of sync.
The data in datafiles and Logfiles might be dups.

Ahmed

          Followup:

Your comment literally makes no sense to me.

Gut feelings are fine but you need to research a little.

Backup and restore is a science dictated by facts, it is not an art.  I suggest
you read through the documents suggested above to get a feeling for how it all
works (and believe me, it does in fact work).

      Rating: 1
Latest Methods
      & Documentation  March 29, 2003
Reviewer:
      Muhammad Ahmad  from pakistan
Tom where can I find latest methods for backup and recovery.
because I have oracle 8.1.7 on windows xp. Unfortunately windows is corrupted
how can i now able to retrieve data from the database. I have the command prompt
available so is this possible to run export utility from command prompt?
or tell me that is best for me

          Followup:

Umm, right about now is exactly when you don't want to be asking about how to do
backup and recovery.

Not knowing what you had in place.

Not knowing what you might mean by "windows is corrupted"..

Yes, export ONLY runs from a command line.

But, if you have access to a command line and the file system, one would just
shutdown the database and copy the files (a proper backup).

      Rating: 3
Hey
      Jamil  March 30, 2003
Reviewer:  Nee
       from Singapore
Jamil,

If your on Windows env. check your free space available. I suspect its very less
and that causes your apps to be slow when the backup is running.....

Not sure whether same is the case with you !

March 30, 2003 Ajeet -- Thanks for the question regarding "Can I
      Pass a nested table to Java from a pl/sql procedure.", version 8.1.7

      You Asked
Hi Tom,
Can I pass a nested table  from pl/sql procedure to java , I am using JDBC.If
yes can you please provide me a simple example or point me to a documentation on
this.My developers don't know how to do this.
(I have seen an example on this side about how to pass an array from function
(not from procedure) to java ).

Thanks so much .
Ajeet

      and we said...
If you have my book "Expert one on one Oracle", this will look familar -- it is
a subset of the example I have in the chapter on java stored procedures where I
demonstrate how to send back and forth all of the SQL types as well as
collections of them:

ops$tkyte@ORA920> create or replace type numArray as table of number;
  2  /

Type created.

ops$tkyte@ORA920> create or replace type dateArray as table of date;
  2  /

Type created.

ops$tkyte@ORA920> create or replace type strArray as table of varchar2(255);
  2  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package demo_passing_pkg
  2  as
  3      procedure pass( p_in in numArray, p_out out numArray )
  4      as language java
  5      name 'demo_passing_pkg.pass_num_array( oracle.sql.ARRAY,
  6                                             oracle.sql.ARRAY[] )';
  7
  8      procedure pass( p_in in dateArray, p_out out dateArray )
  9      as language java
 10      name 'demo_passing_pkg.pass_date_array( oracle.sql.ARRAY,
 11                                              oracle.sql.ARRAY[] )';
 12
 13
 14      procedure pass( p_in in strArray, p_out out strArray )
 15      as language java
 16      name 'demo_passing_pkg.pass_str_array( oracle.sql.ARRAY,
 17                                             oracle.sql.ARRAY[] )';
 18  end;
 19  /

Package created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set define off
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace and compile
  2  java source named "demo_passing_pkg"
  3  as
  4  import java.io.*;
  5  import java.sql.*;
  6  import java.math.*;
  7  import oracle.sql.*;
  8  import oracle.jdbc.driver.*;
  9
 10  public class demo_passing_pkg extends Object
 11  {
 12
 13  private static void show_array_info( oracle.sql.ARRAY p_in )
 14  throws SQLException
 15  {
 16      System.out.println( "Array is of type      " +
 17                           p_in.getSQLTypeName() );
 18      System.out.println( "Array is of type code " +
 19                           p_in.getBaseType() );
 20      System.out.println( "Array is of length    " +
 21                           p_in.length() );
 22  }
 23
 24  public static void pass_num_array( oracle.sql.ARRAY p_in,
 25                                     oracle.sql.ARRAY[] p_out )
 26  throws SQLException
 27  {
 28      show_array_info( p_in );
 29      java.math.BigDecimal[] values = (BigDecimal[])p_in.getArray();
 30
 31      for( int i = 0; i < p_in.length(); i++ )
 32          System.out.println( "p_in["+i+"] = " + values[i].toString() );
 33
 34      Connection conn = new OracleDriver().defaultConnection();
 35      ArrayDescriptor descriptor =
 36         ArrayDescriptor.createDescriptor( p_in.getSQLTypeName(), conn );
 37
 38      p_out[0] = new ARRAY( descriptor, conn, values );
 39
 40  }
 41
 42  public static void
 43  pass_date_array( oracle.sql.ARRAY p_in, oracle.sql.ARRAY[] p_out )
 44  throws SQLException
 45  {
 46      show_array_info( p_in );
 47      java.sql.Timestamp[] values = (Timestamp[])p_in.getArray();
 48
 49      for( int i = 0; i < p_in.length(); i++ )
 50          System.out.println( "p_in["+i+"] = " + values[i].toString() );
 51
 52      Connection conn = new OracleDriver().defaultConnection();
 53      ArrayDescriptor descriptor =
 54         ArrayDescriptor.createDescriptor( p_in.getSQLTypeName(), conn );
 55
 56      p_out[0] = new ARRAY( descriptor, conn, values );
 57
 58  }
 59
 60  public static void
 61  pass_str_array( oracle.sql.ARRAY p_in, oracle.sql.ARRAY[] p_out )
 62  throws java.sql.SQLException,IOException
 63  {
 64      show_array_info( p_in );
 65      String[] values = (String[])p_in.getArray();
 66
 67      for( int i = 0; i < p_in.length(); i++ )
 68          System.out.println( "p_in["+i+"] = " + values[i] );
 69
 70      Connection conn = new OracleDriver().defaultConnection();
 71      ArrayDescriptor descriptor =
 72         ArrayDescriptor.createDescriptor( p_in.getSQLTypeName(), conn );
 73
 74      p_out[0] = new ARRAY( descriptor, conn, values );
 75
 76  }
 77
 78  }
 79  /

Java created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set serveroutput on size 1000000
ops$tkyte@ORA920> exec dbms_java.set_output( 1000000 )

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      l_in strArray := strArray();
  3      l_out strArray := strArray();
  4  begin
  5      for i in 1 .. 5 loop
  6          l_in.extend;
  7          l_in(i) := 'Element ' || i;
  8      end loop;
  9
 10      demo_passing_pkg.pass( l_in, l_out );
 11      for i in 1 .. l_out.count loop
 12          dbms_output.put_line( 'l_out(' || i || ') = ' || l_out(i) );
 13      end loop;
 14  end;
 15  /
Array is of type      OPS$TKYTE.STRARRAY
Array is of type code 12
Array is of length    5
p_in[0] = Element 1
p_in[1] = Element 2
p_in[2] = Element 3
p_in[3] = Element 4
p_in[4] = Element 5
l_out(1) = Element 1
l_out(2) = Element 2
l_out(3) = Element 3
l_out(4) = Element 4
l_out(5) = Element 5

PL/SQL procedure successfully completed.

March 30, 2003 gaurang -- Thanks for the question regarding
      "LONG data type", version 8.1.6

      You Asked
hi tom,

why i am getting this message.
why can't i use below statement if there is LONG data type

CREATE TABLE EIM_ACCNT_DTL AS SELECT * FROM SIEBEL.EIM_ACCNT_DTL@GUI_RCR320
          *
ERROR at line 1:
ORA-00997: illegal use of LONG datatype

Thanks

      and we said...
In 8i you should not be using LONGS (long raws) they are deprecated types.

There are many restrictions with them -- this is one of them (not so with
CLOBS/BLOBS)

See

http://osi.oracle.com/~tkyte/Misc/MoveLongs.html
for how to copy a long from table to table.
Review &
      Followup

      Rating: 5
Use COPY
      COmmand  August 17, 2001
Reviewer:  Gopal
       from Nj
Baby USE COPY COMMAND
BUT BE VERY CAREFUL
BE COMPLETELY AWARE OF
COPY -Insert
REPLACE--DROPS AND RECREATES THE TABLE
TRUNCATE--TRUNCATES DATA.

      Rating: 5
Moving
      Longs  March 30, 2003
Reviewer:
      A reader  from Hiuston
In the url

http://osi.oracle.com/~tkyte/Misc/MoveLongs.html
for copying long datatypes

How can I find the sine of my longest long
set long N -- size of your longest long

Thank you

          Followup:

you would have to know it, unless the application stored it (like we do in
ALL_VIEWS).  Otherwise you have to write a program to read the long and see how
big it is.

      Rating: 4
Moving
      longs  March 30, 2003
Reviewer:
      A reader
We are not storing the length of the long datatypes.  Is there a Oracle package
that can give this kind of information.

Thank you

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:834827040715
it'll be slower then molasses in feburary in alaska if you have more then a
trivial amount of data.

Since you are moving them -- have you considered using a better datatype?  Like
CLOB?  Then

a) you can use CREATE TABLE NEW as select ...., to_lob(long) from old;
b) never have this problem again.

      Rating: 5
Long
      datatype  March 30, 2003
Reviewer:
      A reader
We have about 300000 + records in the table.

Thank you

March 30, 2003 Swapnil -- Thanks for the question regarding
      "Deletion of AQ queue table", version 8i

      You Asked
I've deleted a row from the queue table. After deletion when I executed my
application, which uses this queue, I got Ora-600:internal error.
In order to make it work I've recreated queue table and compiled all the invalid
objects.
Could you please explain me this, I havnt found any doc which says dont delete
anything fromthis table.
Thanks in advance.

      and we said...
did you find anything in the documentation that said "go ahead, take these
tables and muck around with them".

queue tables are like, well, not like -- they ARE generated objects.  They are
infrastucture.  You can no more modify them directly then you can rename a
column by updating a SYS table.

You should no more modify these objects then you would update a SYS table.

Review &
      Followup

      Rating:
      2
  March 27, 2003
Reviewer:
      A reader
hello tom,

I was told, that one would have to add a trigger on the queue table in order to
get an XML-type-payload validated against a schema. is that true? isn't that a
modification of an generated object too?

          Followup:

do you see it in the documentation?

      Rating:
      2
  March 28, 2003
Reviewer:
      A reader
no - I got it as an answer on an ORACLE workshop.

          Followup:

who ran this oracle workshop, and did they refer to documented practices?

      Rating:
      3
  March 30, 2003
Reviewer:
      A reader
it was ORACLE staff, and - you are right - they didn't mention any documentation
at all. they said: ORACLE only checks whether XML data is "well-formed" or not
(in order to avoid repetitive operations, since most clients would have checked
before submitting data). additional validation against a XML schema on database
side has to be manually initiated.

I haven't found to much about these topics in ORACLE documentation too. but I've
read there, that one could use XMLType payload on queues in 9iR2. would you
agree that one should use XML schema to validate messages of XMLType in queues?

could you give a simple example, how to create an user defined XML type WITHOUT
USING DBMS_AQ capabilities and assign a schema to this type.

          Followup:

well, just search for XML on this site for a couple of articles and some
examples or see

http://otn.oracle.com/tech/xml/content.html
tons of examples -- none of them really to do with AQ.

March 30, 2003 Scott -- Thanks for the question regarding
      "Oracle 9i Datawarehousing features", version 9.2.0

      You Asked
Hi Tom

I work on a multi terabyte data warehouse. Each night we are loading hundreds of
millions of records in to the database from a combination of sources including
flat text files, database links, database exports, and so on. These loads
include appending transactional data to fairly large fact tables, as well as
inserting new and modifying existing records in reference data such as customer
information.

We have recently upgraded the database from Oracle 8.0.5 to 9.2 running on
Solaris. We are now in the process of examining our existing ETL scripts and
processes to see if we can incorporate some of the newer Oracle features to
improve our load processes and performance, and also hopefully overcome some of
the bottlenecks we currently experience.

With this in mind I was wondering if you could recommend some "best practice"
guidelines using features available to us in version 9.2 for developing (or
reworking) load processes that have to handle large volumes in relatively short
load windows?

I have done some initial reading on this site and elsewhere, and have found a
few possible examples... external tables, the merge command, bulk exception
handling, etc.

Any thoughts or recommendations you might have would be greatly appreciated.
Thanks also for providing such great information on this site.

Cheers

      and we said...
for ETL, the three you just mentioned are most interesting.  See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6710307340210
but compressed tables can be huge in their savings (compressed indexes as well
-- but that was an 8i feature).

You have many more table partitioning options -- including a new LIST
capability.

Materialized views -- the indexes of your data warehouse...  something to give a
serious look see at.

Transportable tablespaces, rather then dump and load....

The difference from 8.0 to 9i -- wow, you have a lot of ground to cover.  That
is 5 versions of enhancements all at once.

You might want to sit and read:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96520/toc.htm
from cover to cover.  It goes into details on many of these topics...

Review &
      Followup

      Rating:
      5
and...  March 31, 2003
Reviewer:  Connor
      McDonald  from UK
pipeline functions
update global indexes

are also a couple of favourites of mine

March 30, 2003 Kenneth -- Thanks for the question regarding
      "ora-02004", version 8.1.6.3

      You Asked
ora-02004
  1) What does this error mean in easier words.

  02004, 00000, "security violation"
  // *Cause: This error code is never returned to a user. It is
used as a value for column, audit_trail.returncode, to signal
that a security violation occurred.
  // *Action: None.

  2) What causes this error - Can you give an example.

In my Audit Logs have entries like>

Mon Feb 5 13:58:22 2001
  SESSIONID: "590925" ENTRYID: "1" STATEMENT: "1" USERID:
"STATCO_EUL"  TERMINAL:   "HQWKS479" ACTION: "100" RETURNCODE:
"0" COMMENT$TEXT: "Authenticated by:   DATABASE; Client address:
(ADDRESS=(PROTO =tcp)(HOST=144.101.36.24)(PORT=1587))"
  OS$USERID: "AndersonG" PRIV$USED: 5  Mon Feb 5 13:58:23 2001
  SESSIONID: "590925" ENTRYID: "2" STATEMENT: "24" USERID:
"STATCO_EUL"  TERMINAL:  "HQWKS479" ACTION: "3" RETURNCODE:
"1031" OBJ$CREATOR: "STATCO_EUL"  OBJ$NAME:   "EUL_VERSIONS"

--> This One <--
  OS$USERID: "AndersonG"  Mon Feb 5 13:58:53 2001  SESSIONID:
"590925" ENTRYID: "3"STATEMENT: "39" USERID: "STATCO_EUL"
  TERMINAL:  "HQWKS479" ACTION: "3" RETURNCODE: "2004"
OBJ$CREATOR: "STATCO"  OBJ$NAME:   "FY00_TICKET" OS$USERID:

"AndersonG"  Mon Feb 5 14:02:03 2001  SESSIONID: "590925"
ENTRYID: "1" ACTION: "101" RETURNCODE: "0"  LOGOFF$PREAD:
  "0"  LOGOFF$LREAD: "303" LOGOFF$LWRITE: "0"LOGOFF$DEAD: "0"

      and we said...
It'll happen when you attempt to perform an operation but were
not allowed to due to insufficient privileges..

for example

SVRMGR> connect sys/manager
Connected.

SVRMGR> AUDIT ALL ON DBA_USERS BY ACCESS;
Statement processed.

SVRMGR> truncate table sys.aud$;
Statement processed.

SVRMGR> connect scott/tiger
Connected.

SVRMGR> select * from sys.dba_users;
select * from sys.dba_users
                  *
ORA-00942: table or view does not exist

SVRMGR> connect sys/manager
Connected.

SVRMGR> select username, obj_name, action_name, returncode
     2> from dba_audit_trail;
USERNAME                       OBJ_NAME
            ACTION_NAME                 RETURNCODE
------------------------------
----------------------------------------
----------- --------------------------- ----------
SCOTT                          DBA_USERS
            SELECT                            2004
1 row selected.
Review & Followup

      Rating: 5
is truncate on
      aud$ supported?  March 30, 2003
Reviewer:
      A reader
Hi

Can we truncate aud$ always? Is it supported by Oracle Support? Can we put it in
another tablespace?

          Followup:

Please contact support and open a tar -- to find out if something is supported
by support, that would be the way to go.

March 31, 2003 --
      Thanks for the question regarding "confusion about SCN", version 8.1.7

      You Asked
Hi Tom

I wonder if a query (SELECT statement) generates SCN because I was researching
about ORA-01555 error and 9i FlashBack feature. In the case of ORA-01555 it says
that when a query starts it compares its SCN with SCN in RBS, does this mean
that a query generates SCN? If not how does a query or a session obtain the
latest SCN? In case of FlashBack in order to read some deleted data we should
get the SCN of say a query before delete or update, how is the SCN of that query
determined?

cheers

      and we said...
It compares the SCN in place for the database at the point in time at which the
query begins.  The SCN is our "clock" if you will.

So, it is the SCN of the database, the queries SCN is the SCN of the database at
the point in time at which the query began.

DBMS_FLASHBACK -- new in 9i, supplies a procedure to get the SCN in place at a
specific point in time.
Review & Followup

      Rating: 5
so it is the
      LATEST scn?  March 19, 2002
Reviewer:
      A reader
Hi Tom

So when a query starst it obtains the latest SCN  (in that instance) from
database (wonder how) then take it and compare with the SCN in the corresponding
RBS (if the query is reading some uncommited data)?

Is a select considered as a transaction? Or a transaction is simply for example

insert ...
delete ...
commit first transaction

insert ...
insert ...
rollback second transaction

select ...

insert ...
commit 3rd transaction

select ....

all in same session (so do I have three transaction here?)

          Followup:

(wonder how) -- it is an internally maintained number that the server always has
access to.  In 9i, you have an easy interface to it as well via the
dbms_flashback package

You have three committed transactions.

You have started a new "transaction" although you won't have a transaction entry
in v$transaction (unless you select over a dblink, then you might...).

A transaction implicitly begins in Oracle with the first statement and ends when
you commit or rollback.

      Rating: 3
Can I get the
      SCN on 8i?  March 31, 2003
Reviewer:  Jon
       from Sydney
What about these two approaches:

select max(ktuxescnw * power(2, 32) + ktuxescnb) from x$ktuxe

or

alter system checkpoint;
select max(checkpoint_change#) from v$datafile;

Are they both OK?  Does the second approach mean there will be no redo recovery
required if I restore to that SCN?

Thanks
Jon

          Followup:

There are no documented methods in 8i for getting this.

Alter system checkpoint would be "close" but no cigar -- things happen "fast" in
the database... between the time the checkpoint returned to you and you issued
the select, hundreds of transactions may have occurred.  It would be like taking
an atom bomb to crack a walnut as well (and would have a correspondingly back
effect on over all system performance)....

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2591957673673#2592288690458

March
      31, 2003 -- Thanks for the question
      regarding "dbms_jobs", version 8.1.7

      You Asked
Tom

We have 5 jobs submitted through dbms_jobs , which get executed every hour.

Now sometimes some of the jobs dont get finished and get broken.

How can I automatically resubmit a job, without the user intervention.

Is there a routine which can for example check at every 20 minutes whether the
jobs are working fine.

What if this routine fails?

Can we in some way check from the operating system side, that the jobs are
running fine.

Thank you.

      and we said...
The jobs will resubmit themselves 16 times if they fail.

If you want to have them continue processing even if they fail, you could
schedule them like this:

dbms_job.submit( l_job,
                 'declare
                      msg varchar2(4000);
                  begin
                      procedure ....
                  exception
                      when others then
                          msg := sqlerrm;
                          rollback;
                          insert into log_table
                          (timestamp,error_msg)
                          values
                          (sysdate,msg);
                          commit;
                  end;',
                  ...... );

you now have a job that "won't fail" -- you catch the error, log it and continue
on....

As for checking at the OS level, that is pretty easy to do with a script -- how
to do that varies by OS but since you want to check from the OS (not some gui)
you must be on Unix ;)

Something like:

$ cat showjobs
#!/bin/csh -f

sqlplus -s / <<EOF
select job, what, broken, failures
from dba_jobs
/
exit
EOF

$ showjobs

       JOB WHAT                           B   FAILURES
---------- ------------------------------ - ----------
        61 dbms_refresh.refresh('"SCOTT". N
           "EMP_SNAP"');

       158 dbms_refresh.refresh('"SCHEDUL N
           ER"."WWV_CAL_USERS_MATERIALIZE
           D"');

       153 null;                          N
       152 dbms_refresh.refresh('"SCHEDUL N
           ER"."WWV_CAL_USERS_MATERIALIZE
           D2"');

    205972 tqm.tqm_utils.update_locations N
           _and_titles;

    205973 tqm.tqm_utils.nightly_job;     N

6 rows selected.

Review &
      Followup

      Rating: 3
Is there any
      way to prevent the system from resubmitting failed jobs 16
      times  August 22, 2001
Reviewer:  byheart wu
       from Singapore
Hi, Tom:

Sorry to disturb you.

I really don't how to ask question from the main page:

http://asktom.oracle.com/
My question is:

Is there any settings that control the retry times for
failed jobs? Sometimes when a job failed, we don't want system to rerun it, we
have to manually check the data
and solve the problems.

I'm very appreciate if you could help me on this issue.

Best regards

Byheart Wu

          Followup:

Well, when the main page has:

Sorry, I have a large backlog of questions right now. Please don't send email --
that will only delay me from accepting new questions here...

on it, i'm not accepting new ones (thats why you couldn't figure it out).

You can have the job "break" itself upon failure.  When we run the job, we do so
in a block like this:

DECLARE
   job BINARY_INTEGER := :job;
   next_date DATE := :mydate;
   broken BOOLEAN := FALSE;
BEGIN
    WHAT -- this is your code here
    :mydate := next_date;
    IF broken THEN :b := 1; ELSE :b := 0; END IF;
END;

Now, if the routine you schedule looks like:

create or replace procedure p( p_break_me in out BOOLEAN )
is
begin
     .... your code ....
EXCEPTION
   when others then
        p_break_me := TRUE; -- make me break
        ... do whatever else you want to notify ....
end;
/

And you schedule it like this:

exec dbms_job.submit( :job, 'p( BROKEN );', ..... )

it'll break itself right away.

      Rating: 4
Still
      Confuse  August 22, 2001
Reviewer:
      Confusion  from U.S.A
Hi,

Would you please explain more about :b := 1 else :b := 0?
Are they flags? I still can not catch the last portion of key point to break the
re-submit process.

Thanks

          Followup:

The only thing you really need to get out of the above example with BROKEN is
that you can in fact pass a boolean "broken" to your routine.  If you set that
to TRUE, your job will be marked broken.  thats it.  So, give a procedure like:

create or replace procedure p( p_break_me in out BOOLEAN )
is
begin
     .... your code ....
EXCEPTION
   when others then
        p_break_me := TRUE; -- make me break
        ... do whatever else you want to notify ....
end;
/

and a job submit like:

exec dbms_job.submit( :job, 'p( BROKEN );', ..... )

You will have scheduled a job such that upon hitting ANY error, it will "break
itself" silently.

The ":b := 1 else :b := 0"  Is just the way the job queue process gets back its
information -- its not really relevant to you (it just shows HOW it is done)

      Rating: 4
Is there any
      parameters that control the rerun times?  August 23, 2001

Reviewer:  byheart Wu  from Singapore
Tom:

Thank you very much.

Well, thank you for your response. But I want to make all
the failed jobs stop rerunning.
Is there any parameters in init file or else where that
control the rerun times? I just wat to switch off the rerun
machanism, is it possible?

Byheart Wu from Singapore

          Followup:

You can achieve that by scheduling:

begin p; exception when others then broken = TRUE; end;

instead of just:

begin p; end;

For example:

ops$tkyte@ORA8I.WORLD> create or replace procedure p
  2  as
  3  begin
  4          raise_application_error(-20001, 'I failed' );
  5  end;
  6  /

Procedure created.

ops$tkyte@ORA8I.WORLD> variable job number

ops$tkyte@ORA8I.WORLD> begin
  2     dbms_job.submit( :job,
  3               'begin p; exception when others then broken := TRUE; end;',
  4                      sysdate,
  5                      'sysdate+1/24' );
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD> print job

       JOB
----------
    210763

ops$tkyte@ORA8I.WORLD> select job, broken, what from user_jobs;

       JOB B WHAT
---------- - ------------------------------
    210763 N begin p; exception when others
             then broken := TRUE; end;

ops$tkyte@ORA8I.WORLD> exec dbms_job.run( :job );
PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD> select job, broken, what from user_jobs;

       JOB B WHAT
---------- - ------------------------------
    210763 Y begin p; exception when others
             then broken := TRUE; end;

That shows the job will be marked broken following ANY error.  the procedure
P need not be written to do this -- the submission of the job can handle it as
well.

      Rating: 4
Thank
      you  August 28, 2001
Reviewer:  baohe wu
       from Singapore
Thank you tom, I got it. This could be very helpful to me.

      Rating: 5
How to throw
      the error code & msg?  March 31, 2003

Reviewer:  Michael Leung  from hk
Hi Tom,

For you case of no-retry job, how could I throw the error and message to the
background trace file?

          Followup:

you either

o break the job
o have the job fail

It is an either or.  If you catch the error, the job hasn't failed.  If the job
fails, you cannot set broken.

      Rating:
      4
  January 08, 2004
Reviewer:
      A reader

March 31, 2003 Saibabu -- Thanks for the question regarding
      "Workaround for "Select Any Table" privilege", version 8.1.6

      You Asked
Hi,We have a requirement that one user called AMEXMART should be
able to select the all tables being added to another user NET
without granting eachtime the table is created.We don't want to
give "select any table" system privilege to AMEXMART user.Is
there any workaround for this.
 Thanks
 Saibabu

      and we said...
Since you have 8.1.6, you can use an ON CREATE trigger to do
this perhaps.

Something like:

create or replace trigger do_grant
after CREATE on schema
declare
    l_str varchar2(255);
    l_job number;
begin
    if ( ora_dict_obj_type = 'TABLE' )
    then
        l_str := 'execute immediate "grant select on ' ||
                                     ora_dict_obj_name ||
                                    ' to scott";';
        dbms_job.submit( l_job, replace(l_str,'"','''') );
    end if;
end;
/

That, when run in a given user account will schedule a job to
grant SELECT on any table created to SCOTT.  We use dbms_job to
run the grant in the background because this is executing as
part of a larger data dictionary transaction and we cannot
commit (nor can we use an autonomous transaction - the table is
not yet "fully" created)...

If you have job_queue_processes set to >0 and job_queue_interval
set to some reasonable number (I use 60 for 1 minute intervals),
then scott will have access to any table about 1 minute AFTER
its created....

Does that work for you?

Review &
      Followup

      Rating: 5
trigger to
      grant selects  August 29, 2001
Reviewer:
      Hubertus Krogmann  from looking to Rio de Janeiro I live at
      your left hand, about some thousand miles away (Germany :)
Hi

this was what I searched, even with a explanation why not grant directly.

Thanx!

      Rating:
      3
  March 27, 2003
Reviewer:  Anandhi
       from Philadelphia, PA
I used your code:

 create or replace trigger do_grant
  after CREATE on m_user.schema
  declare
  l_str varchar2(255);
  l_job number;
  begin
  if ( ora_dict_obj_type = 'TABLE' )
  then
  l_str := 'execute immediate "grant select on ' ||
  ora_dict_obj_name ||
  ' to g_user";';
  dbms_job.submit( l_job, replace(l_str,'"','''') );
  end if;
  end;
  /

I had to create this trigger as system user becos when I try to run it as
m_user, I get an insufficient privileges error.
The trigger compiles well, but when I try to create a table   as m_user, I get
this error:

  ERROR at line 1:
  ORA-00604: error occurred at recursive SQL level 1
  ORA-30511: invalid DDL operation in system triggers
  ORA-06512: at line 7

I am probably missing something here - what is that?

Thanks for your time.

Anandhi

          Followup:

well, you know -- you should not use SYS or SYSTEM for *anything* remotely
likely this.

They are our accounts.  You should however grant your account the privileges
necessary to perform its job.

But -- that aside, you'll have to be creative and debug this a little yourself
-- or at least give me a 100% test case with which to reproduce -- including
nice things like database version and OS....

Here is my test case showing "this works"

ops$tkyte@ORA817DEV> drop user m_user cascade;
User dropped.

ops$tkyte@ORA817DEV> drop user g_user cascade;
User dropped.

ops$tkyte@ORA817DEV> create user m_user identified by m_user;
User created.

ops$tkyte@ORA817DEV> create user g_user identified by g_user;
User created.

ops$tkyte@ORA817DEV> grant connect, resource to m_user;
Grant succeeded.

ops$tkyte@ORA817DEV> grant connect to m_user;
Grant succeeded.

ops$tkyte@ORA817DEV>  create or replace trigger M_USER.do_grant
  2    after CREATE on m_user.schema
  3    declare
  4    l_str varchar2(255);
  5    l_job number;
  6    begin
  7    if ( ora_dict_obj_type = 'TABLE' )
  8    then
  9    l_str := 'execute immediate "grant select on ' ||
 10    ora_dict_obj_name ||
 11    ' to g_user";';
 12    dbms_job.submit( l_job, replace(l_str,'"','''') );
 13    end if;
 14    end;
 15  /
Trigger created.

ops$tkyte@ORA817DEV> @connect m_user/m_user

m_user@ORA817DEV> create table t ( x int );
Table created.

m_user@ORA817DEV> select job, what from user_jobs;

       JOB WHAT
---------- ------------------------------
       289 execute immediate 'grant
           select on T to g_user';

wait a bit...

m_user@ORA817DEV> /
no rows selected

Job run, grant done.

      Rating: 3
Wouldn't the
      grant run under the schema from which the trigger
      fires  March 31, 2003
Reviewer:  NC
       from UK
Wouldn't in this case the job get submitted under the owner of the trigger ie
ops$tkyte and therefore the grant fail ?

          Followup:

good eye, I missed a schema name on there.  corrected...

March 31, 2003 Darshan -- Thanks for the question regarding "SQL
      relared queries ??", version 8.1.7

      You Asked
Hello Tom,
          I have some queries relating to SQL*Plus.

I)Is there any way to print count(1) as zero if there is no row present in the
table.For e.g. lets consider a table numbers which has a column called
num_value.There are totally 10 rows in this table.Out of this four rows are
1,three are 2 and the remaining three are 4.So when i run a query select
count(1),num_value from numbers group by num_value the output that i get is :

count(1) num_value
-------  ---------
4         1
3         2
3         4

So my query is can the following output be printed :

count(1) num_value
-------  ---------
4         1
3         2
0         3
3         4

We can also consider the fact that the master values i.e. 1,2,3 and 4 are
present in some other table.

II)I want to print a column for all the rows present in a table in a single
line.Say for e.g if emp table has four rows and the empno's are 10,20,30,40 the
output should be 10 20 30 40.Can u suggest any way to do so in pure sql ?

Thanking you in anticipation.

Darshan

      and we said...
count(1) is pedentic and misleading.  if you want to count - just count(*).  It
is proper.

select master_values.num_value,
      (select count(*)
         from t
        where t.num_value = master_values.num_value ) cnt
  from master_values;

or

select master_values.num_value, count(*)
  from master_values, t
 where master_values.num_value = t.num_value(+)
 group by master_values.num_value;

2) search this site for pivot -- many articles on that.
March 31, 2003 Bhagat Singh -- Thanks for the question regarding
      "Serialization of selects", version 8.1.6

      You Asked
Hello Tom,

Tom the situation is this that there is a dispatcher table in which the records
are there for sending them as SMS (short messaging service) now we want to run
several pro*c code which will select say for example 5 records lock them and
send as SMS other process should come and pickup other 5 records which have not
been locked and so on so the 5 running process will come and select 5 records
lock them send the SMS and update them so that there are not processed next time
and release the lock. I actually tried your M through N formula to get this done
but it does not allows to  write the for update statement in it, apart from that
any other better way, Please suggest.

Regards
Bhagat Singh

      and we said...
This is a prime candidate for AQ (advanced queues).  We have solved this problem
using message queues in the database for you.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:540822468136
for a very simple example -- dequeue_msg would be your pro*c routines.
March 31, 2003 andrew -- Thanks for the question regarding "Log
      Miner Clarification", version 8.0.6

      You Asked
I am running log miner in an 8.1 database against redo logs from and 8.0
database.
Perhaps I am missing it, but I cannot find in the documentation (Reference
guide, Administrator's guide):
1) What is the meaning/use of the ph#_name, ph#_redo, and ph#_undo columns of
v$logmnr_contents, where # is a number between 1 and 5.
2) Can you explain to me the circumstances when the username column of
v$logmnr_contents would be null.  I am finding this from time to time and do not
understand it.

      and we said...
Here is an excerpt from my book "Expert one on one Oracle" that describes there
(there are 1264 other pages in there with other stuff ;)

....
Now, we'll go one step further with this example. The V$ LOGMNR_CONTENTS view
has  'placeholder' columns.  These placeholder columns are useful for finding to
particular updates for up to 5 columns in you table. The placeholder columns can
tell us the name of the changed column and show us the before value of the
column and the after column value. Since these columns are broken out from the
SQL - it would become very easy to find the transaction such that the ENAME
column was updated (the name place holder column would have ENAME) from KING
(the before image placeholder column would have KING in it) to king. We'll do
another quick update example and setup the necessary column mapping file to
demonstrate this. The column mapping file (colmap for short) is used to tell Log
Miner which columns are of interest to you by table. We can map up to 5 columns
per table to be mapped into these placeholder columns. The format of a column
mapping file is simply:

colmap = TKYTE DEPT (1, DEPTNO, 2, DNAME, 3, LOC);
colmap = TKYTE EMP (1, EMPNO, 2, ENAME, 3, JOB, 4, MGR, 5, HIREDATE);

That will map the DEPT DEPTNO column to the first placeholder column when we are
looking at a row for the DEPT table, it will map the EMP EMPNO column to that
placeholder column when we are looking at an EMP row.

The column mapping file in general has lines that consist of the following
(items in BOLD are constants, <sp> represent a single, mandatory space)

colmap<sp>=<sp>OWNER<sp>TABLE_NAME<sp>(1,<sp>CNAME[,<sp>2,<sp>CNAME]&#65533;);

The case of everything is important - the OWNER must be upper case, the table
name must be the "correct" case (uppercase is usually the correct case unless
you've used quoted identifiers to create objects). The spaces are mandatory as
well. In order to make using the column mapping file a little easier - I use a
script such as:

set linesize 500
set trimspool on
set feedback off
set heading off
set embedded on
spool logmnr.opt
select
    'colmap = ' || user || ' ' || table_name || ' (' ||
   max( decode( column_id, 1,       column_id  , null ) ) ||
   max( decode( column_id, 1, ', '||column_name, null ) ) ||
   max( decode( column_id, 2, ', '||column_id  , null ) ) ||
   max( decode( column_id, 2, ', '||column_name, null ) ) ||
   max( decode( column_id, 3, ', '||column_id  , null ) ) ||
   max( decode( column_id, 3, ', '||column_name, null ) ) ||
   max( decode( column_id, 4, ', '||column_id  , null ) ) ||
   max( decode( column_id, 4, ', '||column_name, null ) ) ||
   max( decode( column_id, 5, ', '||column_id  , null ) ) ||
   max( decode( column_id, 5, ', '||column_name, null ) ) || ');' colmap
 from user_tab_columns
group by user, table_name
/
spool off

in SQL*PLUS to generate the logmnr.opt file for me. For example, if I execute
that script in a schema that contains only the EMP and DEPT tables from the
SCOTT/TIGER account - I will see:

tkyte@TKYTE816> @colmap
colmap = TKYTE DEPT (1, DEPTNO, 2, DNAME, 3, LOC);
colmap = TKYTE EMP (1, EMPNO, 2, ENAME, 3, JOB, 4, MGR, 5, HIREDATE);

It always picks the first 5 columns of the table - if you desire a different set
of 5 columns, just edit the resulting logmnr.opt file this creates and change
the column names.  For example, the EMP table has three more columns that are
not shown in the above colmap - SAL, COMM and DEPTNO.  If you wanted to see the
SAL column instead of the JOB column - the colmap would simply be:

tkyte@TKYTE816> @colmap
colmap = TKYTE DEPT (1, DEPTNO, 2, DNAME, 3, LOC);
colmap = TKYTE EMP (1, EMPNO, 2, ENAME, 3, SAL, 4, MGR, 5, HIREDATE);

Important considerations for the column mapping file, beyond its needs to have
the correct case and whitespace, are

?    The file must be named logmnr.opt. No other name may be used.
?    This file must be in the same directory as your dictionary file.
?    You must be using a dictionary file in order to use a column mapping file.

So, we will now modify all of the columns in the DEPT table.  I am using 4
different updates, each against a different row and set of columns.  This is so
we'll better see the effect of the placeholder columns:

tkyte@TKYTE816> alter system archive log current;

tkyte@TKYTE816> update dept set deptno = 11
  2   where deptno = 40
  3  /

tkyte@TKYTE816> update dept set dname = initcap(dname)
  2   where deptno = 10
  3  /

tkyte@TKYTE816> update dept set loc = initcap(loc)
  2   where deptno = 20
  3  /

tkyte@TKYTE816> update dept set dname = initcap(dname),
  2                    loc = initcap(loc)
  3   where deptno = 30
  4  /

tkyte@TKYTE816> commit;

tkyte@TKYTE816> alter system archive log current;

We can review the column by column changes now by loading the newly generated
archived redo log file and starting Log Miner with the option USE_COLMAP. Note
that I did generate the logmnr.opt file using the script above and I placed that
file in the same directory with my dictionary file:

tkyte@TKYTE816> declare
  2      l_name v$archived_log.name%type;
  3  begin
  4
  5      select name into l_name
  6        from v$archived_log
  7       where completion_time = ( select max(completion_time)
  8                                   from v$archived_log );
  9
 10      sys.dbms_logmnr.add_logfile( l_name, dbms_logmnr.NEW );
 11  end;
 12  /
PL/SQL procedure successfully completed.

tkyte@TKYTE816> begin
  2     sys.dbms_logmnr.start_logmnr
  3     ( dictFileName => 'c:\temp\miner_dictionary.dat',
  4       options => dbms_logmnr.USE_COLMAP );
  5  end;
  6  /
PL/SQL procedure successfully completed.

tkyte@TKYTE816> select scn, ph1_name, ph1_undo, ph1_redo,
  2              ph2_name, ph2_undo, ph2_redo,
  3              ph3_name, ph3_undo, ph3_redo
  4    from v$logmnr_contents
  5   where seg_name = 'DEPT'
  6  /

       SCN PH1_NA PH1 PH1 PH2_N PH2_UNDO   PH2_REDO   PH3 PH3_UNDO PH3_REDO
---------- ------ --- --- ----- ---------- ---------- --- -------- --------
6.4430E+12 DEPTNO 40  11
6.4430E+12                DNAME accounting Accounting
6.4430E+12                                            LOC DALLAS   Dallas
6.4430E+12                DNAME sales      Sales      LOC CHICAGO  Chicago

So, this output clearly shows us (from line 1 for example) that DEPTNO had a
before image value of 40 (PH1_UNDO) and became 11. That makes sense since we did
"set deptno = 11 where deptno = 40".  Notice that the remaining columns in that
first row of output are NULL - that is because Oracle logs changed bytes only,
there is no before/after image of the DNAME and LOC columns for that row. The
second row of shows the update of the DNAME column from "accounting" to
"Accounting" - and no changes for DEPTNO or LOC, as those columns where not
affected.  The last row shows that whn we modified two columns with our update
statement - they both show up in the place holder columns.

As you can see - using the placeholder columns can be very convenient if you are
trying to locate a specific transaction in a large set of redo. If you know that
the transaction updated the X table and changed the Y column from "a" to "b" -
finding that transaction will be a breeze.
................

2)
The username is the name of the user initiating the operation that generated the
redo record. The user name will always be NULL if the archive auditing option is
not enabled.  This auditing is enabled via the init.ora parameter
TRANSACTION_AUDITING

Review &
      Followup

      Rating:
      5
Reader  March 31, 2003
Reviewer:
      A reader
"options => dbms_logmnr.USE_COLMAP "

Is the option documented anywhere in the manual 9.0.1 or 9.2

          Followup:

Nope, superceded by the easier to use MINE_VALUE

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96521/logminer.htm#25689
has an example.  No more column mapping needed.

March 31, 2003 Alvin -- Thanks for the question regarding "DB
      transfer", version 9.2.0

      You Asked
Hi, Tom

I have a huge database about 300GB(O9i R2 in Sun solaris2.8),
and now I have to transfer it from one machine(A) to another(B)(same OS version
and Oracle).
If I cpoy all the data files from A to B and put them under same directories.
i.e, make same environment in both machines A and B.
Can my new database in B works ?
If can't, do you have any better idea to do this but export,import?

Thanks

      and we said...
Yes, you can certainly restore a BACKUP of your database to another machine.
This is what backups are all about.....

Just use your tried and tested backup and recovery mechanisms.  (they are tried
and tested right ;)  if they are not, this'll test your ability to actually use
your backups to recover with.
Review &
      Followup

      Rating: 4
Yes that will
      work  March 31, 2003
Reviewer:  A reader
       from Atlanta, GA
If you want to transfer the entire database, it's really not that difficult, but
will require downtime. If downtime isn't acceptible, then skip this post all
together. If it's ok, (like for Dev or QA) then you can try this.  This assumes
that your file system uses OFA, implying that the database SID is *somewhere* in
the full path name for any given database file.

1. From the top level Oracle directory (eg /oracle):

>find . -name "<SID>" -print >> filename

where filename will be the include file for the tar command.

2. Shutdown the database.

3. Tar the database files.

>tar -cvf <DBNAME.tar> -I filename

see above for filename.

4. Compress and ftp this .tar file to the new server.
5. Uncompress and extract from the same mount point on the new server.
> uncompress <DBNAME.tar.Z>
> tar -xvf DBNAME.tar

6. Startup database
7. If necessary, make changes to your local tnsnames.ora file.
8. You may need to "relink", but you should have done that when you installed
the new Oracle. Check Metalink.

Of course as Tom said, you probably should do a backup etc..but for
non-production systems, this may be a nice way to move entire db's, I know I've
used the technique numerous times.

          Followup:

That is a backup you just did.  A cold backup.

If they don't want downtime, just use their hot backups and do a full PITR..

      Rating: 2
Some thoughts
      about large files  March 31, 2003
Reviewer:
      Steve Meller  from Atlanta, GA
Some versions of tar limit the file size to 2gb.  If your datafiles are bigger
you may have an issue.  We found an individual file copy to be more robust, so
if one file copy fails, the recovery scenario is less painful.  For a database
that size in archivelog mode, alter each tablespace sequentially begin
backup/end backup and copy the files of that tablespace during the interval.
O/S must be the same of course.

March 31, 2003 Alex -- Thanks for the question regarding
      "session_cached_cursor", version 8.1.6.3

      You Asked
Hi Tom

I have a couple of questions regarding session_cached_cursor parameter

1).-
I did a small test with session_cached_cursor, I set it to 4 and bounced the
database. According to the paper on Metalink
http://metalink.oracle.com/metalink/plsql/ml2_documents.showDocument?p_database_id=NOT&p_id=32895.1
if session_cached_cursor is
set then no soft and hard parse would happen if the cursor is cached.

I executed a simple query

select * from dept;

then checked parse_calls in v$sqlarea it says 1, then I executed again it says 2
then again it increased to 3 does this means that this select is not cached?

I then tested another pl/sql block

declare
begin
for x in (select * from dept) loop
null;
end loop;
end;
/

The parse_calls still increases

2).-
If session_cached_cursor is set to zero (default) how many cursors would each
session cache. I talked to an Oracle Support guy in Spain and the guy told me
that Oracle would use what it needs, for example if there are 1000 cursors
opened once then it would try to cache all 1000. Which I refused to accept as an
answer. So I asked him if Oracle manage a cached cursor list, he told me that
MAYBE. Not very clear to me...

Also related to this parameter I asked him if setting 0 Oracle stops caching the
cursors, he said that as some parameters when they are set to zero it basically
tells Oracle dont use that parameter.
Then I brought him to

http://metalink.oracle.com/metalink/plsql/ml2_documents.showDocument?p_database_id=NOT&p_id=32895.1
Accordng t this note, Oracle only caches cursors only when
session_cached_cursor is set to a value???
and asked him what happens if SESSION_CACHED_CURSOR is set to ZERO and he just
said that Oracle caches the cursor if this is set to zero which contradicts the
paper

May you clarify

Cheers

      and we said...
1) you were measuring this one the wrong way.  It'll do a faster soft parse --
hows that.   The parse count will still go up tho...

Consider:

ops$tkyte@ORA817.US.ORACLE.COM> alter session set session_cached_cursors=0;
Session altered.

no cached cursors......

ops$tkyte@ORA817.US.ORACLE.COM> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%cursor ca%'
  5  /

NAME                                VALUE
------------------------------ ----------
session cursor cache hits               5     thats from logging in
session cursor cache count              0

ops$tkyte@ORA817.US.ORACLE.COM> declare
  2          l_cnt number;
  3  begin
  4          for i in 1 .. 100
  5          loop
  6              execute immediate 'select count(*) from dual d1' into l_cnt;
  7          end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817.US.ORACLE.COM> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%cursor ca%'
  5  /

NAME                                VALUE
------------------------------ ----------
session cursor cache hits               5   no change
session cursor cache count              0

now, lets cache upto 10 cursors

ops$tkyte@ORA817.US.ORACLE.COM> alter session set session_cached_cursors=10;
Session altered.

ops$tkyte@ORA817.US.ORACLE.COM> declare
  2          l_cnt number;
  3  begin
  4          for i in 1 .. 100
  5          loop
  6               execute immediate 'select count(*) from dual d2' into l_cnt;
  7          end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817.US.ORACLE.COM> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%cursor ca%'
  5  /

NAME                                VALUE
------------------------------ ----------
session cursor cache hits             104  99 more hits!
session cursor cache count              4

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

Our first query in that loop didn't get a hit (we hadn't cached it yet), the
subsequent 99 did.  It has to go through the mechanics of a pretending to do a
softparse (making sure things haven't been invalidated and such) but the code
path is much smaller.

2) simple answer, none.

Complex answer -- PLSQL, Pro*C, Forms, Reports, and many other environments
cache cursors as well.  PLSQL will cache all static SQL is executes, Pro*c will
hold cursors, a java program using a prepared statement will perform cursor
caching of its own.

So, if open_cursors was set to 1000 and you execute a plsql routine with 1000
statments -- peek at v$open_cursor -- you'll find its chock full of open
statements.  PLSQL caches them for us.  Session cached cursors is useful for
environments that don't (or can't) cache the cursors (eg: DYNAMIC SQL in plql
CANNOT cache the cursor).

So, it is a matter of semantics here.  You are both right.

Do I get the t-shirt now?
Review & Followup

      Rating: 5
How to do soft
      soft parse in java  August 26, 2002
Reviewer:
      Zoran Martic  from Dublin, Ireland
Hi Tom,

I have this situation. We are using java with JDBC connections to the database
(8i and 9i). We are managing a pool of connections to the database (we are not
using MTS), 8 connections are created at the startup and many more when the need
arise (if there are more then 8 active transactions at the moment).

How to have soft soft parse in this case, at least with these static 8
connections (99% percent of work is done in them).
Because we are using connections in some circular order I need to know for which
session I have prepared (cached) statement.

What is your way of solving this issue?
How to elegantly prepare a statement for every session I use?
How to know do I have a statement prepared for some particular session?

I know that this is java related issue but maybe you have seen something like
this.
Thanks in advance.

Regards,
Zoran

          Followup:

You know -- if you move 99% of your SQL into stored procedures, this'll be done
for you automagically.  PLSQL will do that -- then all you have to worry about
is the significantly FEWER plsql blocks you have to parse.

And those, I suppose you could attach them to your connection with a hash table
or something similar.  That is -- when you grab a connection, you get a hash
table with "named" prepared statements in them (instead of just a connection,
you get a connection and a set of statements waiting to be called -- if you
don't find a statement in the hash table, you'll prepare it and add it yourself.

      Rating: 5
How to do soft
      soft parse in java  August 26, 2002
Reviewer:
      Zoran Martic  from Dublin, Ireland
Hi Tom,

Thanks a lot.
As I wondered there is not any magic method there.

One question more if it is not the problem.
I am using weak ref cursors in PL/SQL without closing them (in Oracle
documentation is possible to find that you do not need to close weak ref cursors
because it is done automatically).
Also I am reusing these cursors in the code without close before or after.
I have seen many your examples with explicit close (as I always thinking is good
method).

What is the difference, if any, if you close or not close ref cursor in PL/SQL
and reuse it again many times (speed, latches, memory, ...)?

Thanks in advance.

Regards,
Zoran

          Followup:

Well, lets see -- if you close the cursor, you won't hit "max open cursors" at
some date in the future.

close em, close em all when they are done.  just close them.

      Rating: 5
Ref cursors -
      the last frontier  August 27, 2002
Reviewer:
      Zoran Martic  from Dublin, Ireland
Hi Tom,

Thanks a lot Tom.
I am dealing with four weak ref cursors.
Open many times (I am 99% sure more times then I have open_cursors) never close
(Oracle suggest that you do not need).

As you said that means I only need to close these four at the end because
probably when I open the same ref cursor again the Oracle code will
automatically reuse memory (and who knows what) that belongs to this cursor (is
it maybe faster in this case because I will have some of the infrastructure
already in place if I do not close, just open again). Maybe it is only 9i, who
knows.

Thanks for you patience. I am only curious because nobody from Oracle never
discuss in public this "recommended" way of dealing with ref cursors.

Thanks in advance.
"Sometimes I ask myself why do you need things like that!"

Regards,
Zoran

      Rating: 5
Ref cursors -
      the last frontier  August 29, 2002
Reviewer:
      Zoran Martic  from Dublin, Ireland
Hi Tom,

Finally I did some tests based on your runstats.sql script.
I used this:

declare
    l_start number;
--    add any other variables you need here for the test...
begin
    delete from run_stats;
    commit;
    -- start by getting a snapshot of the v$ tables
    insert into run_stats select 'before', stats.* from stats;
    -- and start timing...
    l_start := dbms_utility.get_time;

    -- for things that take a very small amount of time, I like to
    -- loop over it time and time again, to measure something "big"
    -- if what you are testing takes a long time, loop less or maybe
    -- not at all
    for i in 1 .. 1000
    loop
        -- your code here for approach #1
    essent_correlate.correlate_per_switch('HGL');
    end loop;

    dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );

    -- get another snapshot and start timing again...
    insert into run_stats select 'after 1', stats.* from stats;
    l_start := dbms_utility.get_time;

    for i in 1 .. 1000
    loop
    essent_correlate2.correlate_per_switch('HGL');
        -- your code here for approach #2
    end loop;

    dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
    insert into run_stats select 'after 2', stats.* from stats;
    commit;

end;
/

I used PL/SQL packages as you can see. The first one with this scenario:
loop
  open weak cursor; -- for different queries with binds
  ....
  close weak cursor;
end loop;

and the second:
loop
  open weak cursor; - for different queries with binds
end loop;
close weak cursor

The code is much more complex then that, with 3 weak cursors in loops, with some
processing without commit and rollback inside. But just to be consistent I did
this tests with running PL/SQL in loop 10, 100 and 1000 times in Oracle817 and
Oracle9iR2. And also change the position for what is going first as:

1. essent_correlate2.correlate_per_switch('HGL'); -- first
   essent_correlate.correlate_per_switch('HGL'); -- second
and
2. essent_correlate.correlate_per_switch('HGL');
   essent_correlate2.correlate_per_switch('HGL');

All statistics were pretty similar except:
STAT...opened cursors cumulative
STAT...recursive calls
and of course execution time.

The conclusion is:
If you want to reuse (weak) ref cursors each time, it is faster 2-5% to open the
cursor again without closing them
but you can see the difference only if you are reopening ref cursors so many
times as I need in my batch processing.
The test was on 1000 times less data rows in tables (2000 times less reopening).

********************************************************
It is not too much (1-5%) on big number of reopening.

What do you think Tom, is it worth it at all, because you can gain more
percentage on other things.

Regards,
Zoran

          Followup:

I would feel better closing the cursor (not seeing your code).

That way I know I don't have a leak.

Also, i code procedures NO LARGER then the height of my monitor, which
effectively limits me to 60-80 lines of code / procedure.  So, I would probably
have had many procedures where you did your stuff inline (eg: i would not have
been ABLE to reuse the cursor!  it wouldn't have fit, i would have had a loop
that called proc1, proc2, proc3, proc4, ... )

      Rating: 5
Ref cursors -
      the last frontier  August 30, 2002
Reviewer:
      Zoran Martic  from Dublin, Ireland
Hi Tom,

Thanks for the answer.
I agree with you 100%, why to risk a memory leak as probably the most common
thing in all softwares around the world for 2% gain.
"THE ONLY THING WHY I DID THAT IN MY FIRST RELEASE WAS
ORACLE RECOMMENDED - YOU DO NOT NEED TO CLOSE REF CURSOR IF YOU ARE OPENING
AGAIN"
(it was in Oracle docs or maybe Metalink note).

I am exactly doing what are you talking about (one of the basic programming
rules, short methods):
open c1
loop
  proc1(c2,bind1,bind2)
  proc2(processing....)
  proc3(c3,bind1,bind2)
  proc4(processing....)
  proc5(finishing)
  close c2
  close c3
end loop
close c1

but you need reopenings every time you do not know names of tables you need to
process as I do not know. The code is dynamic to correlate (process) as many
tables as somebody put in the routing table (because of that I am using weak
refs).

Thanks for all. Your book is great. I am happy to read a book as yours after 6
years of Oracle experience. Your book is my first Oracle book I bought with my
(non-company) money.

Regards,
Zoran

      Rating: 4
cursor
      parameter  December 20, 2002
Reviewer:  mo

Tom:

1. Can you explain briefly what the setting of this parameter does for the
system and if you did not set it =0 what happens?

Would it matter for a web app.  IS it when a store  procedure is run and you
open a cursor it checks if it is in shared pool or not?

          Followup:

search this site for

"session_cached_cursors"

lots of articles on this topic exist already....

it caches cursors for reuse in your session -- SESSION.  soft parsing -- what
you describe, happens automagically.

      Rating:
      5
cursors  December 21, 2002
Reviewer:
      mo
Tom:

I did of course before posting.  Got 28 hits two only applied directly.  My
understanding is that pl/sql will do it automatically, this parameter will only
helps if you use dynamic SQL. Correct?

          Followup:

native dynamic sql in plsql
sql in a poorly written java/vb application
help with recursive sql (sql oracle does on your behalf)

are all uses for this parameter.

      Rating:
      5
PreparedStatement and cursor sharing  January 08,
      2003
Reviewer:  Sam  from San Mateo, CA
Tom,

Suppose I have the following piece of Java code:
String mySQL = "SELECT * FROM MyTable WHERE id = :b1";

for (i=0;i<100;i++)
(
  PreparedStatement ps = myConnection.prepareStatement(mySQL);
  ps.setInt(1,i);
  ResultSet rs = ps.executeQuery();
  ...
  rs.close();
  ps.close();
}

Are the following correct:
1. If session_cached_cursor is set to say, 100, then the cursor will be cached,
even if we close the PreparedStatement explicitly inside the loop
2. Once the cursor is cached, subsequent PreparedStatement calls will result in
repeated "soft soft parse" instead of "soft parse"
3. Even though the cursor is cached, JDBC still has to issue a separate query to
find out the column types the query will return, as I did not use
defineColumnType to specify the datatypes.  And JDBC has to do this every time
in the loop.
4. We can avoid this extra JDBC operation as well as the repeated "soft soft
parse" if we prepare the statement outside the loop and just rebind every time
in the loop.

      Rating: 5
measuring the
      memory session_cached_cursors is using  March 31, 2003

Reviewer:  Juan Carlos Reyes  from Bolivia

Hi Tom
is possible to measure the memory session_cached_cursors is using for all the
clients connected to the database.

Thank you

          Followup:

it sets up a simple small array in the PGA/UGA memory of the session that is
dynamically grown.

You would have to measure PGA memory use with and without a cursor cache -- so
in effect "no, not really"

      Rating:
      5
Thanks  March 31, 2003
Reviewer:
      A reader

March 31, 2003 -- Thanks for the question regarding "How to
      Combine different columns to a field", version 9.0.1

      You Asked
Hi Tom,

I'm new to Oracle.  I'm trying to combine two parts of a fixed-length file to a
single field in my destination table.  That is, I'm trying to combine
POSITION(8:9) and POSITION(10:15) with a '-'.  For example, if POSITION(8:9) is
'AB' and POSITION(10:15) is '012345' I want the result to be 'AB-012345'.

In the control file, I tried to use the line below

FIELDNAME_01  "POSITION(8:9) || '-' || POSITION(10:15)"

but I can only get errors.  What should I do?  Can I do it using SQL*Loader
only?

Thank you very much.

      and we said...
fieldname_01 position(8:15)
            "substr(:fieldname_01,1,2)||'-'||substr(:fieldname_01,3)"

will do that -- you want sqlldr to bind in 8:15 and then use SQL functions to
"transform" the data.
Review & Followup

      Rating: 5
Thank
      you  March 31, 2003
Reviewer:  A reader

Tom,

Thank you very much.  Prompt and really very useful.  I really appreciate it.
:)

March 31, 2003 Samatha -- Thanks for the question regarding "SQL
      Query", version 8.1.7

      You Asked
Tom,

Here is the copy of a function I am working with.

create or replace
function SF_CHEM_transposed( pALLOY    in varchar2,
                             pCAST     in varchar2,
                             pFIELD_NAME     in varchar2 )
return VARCHAR2
is
l_str  varchar2(4000) default null;
l_sep  varchar2(3) default null;
l_sql  varchar2(2000) default null;
begin
  for x in (SELECT pFIELD_NAME VAL1 FROM QUANT_dATA
            WHERE ALLOY LIKE pALLOY||'%'
            AND CAST_NBR = pCAST
            ORDER BY SAMPLE_INFO )
  loop
   l_str := l_str || l_sep || x.val1;
   l_sep := ', ';
 end loop;
 return l_str;
end;
/

I want to pass the name of the field that I want to transpose dynamically using
the pFIELD_NAME parameter. But the above statement will only return the
pFIELD_NAME as a constant.

How can I dynamically change the field name to query?

Thanks,
Samatha

      and we said...
scott@ORA817DEV> create or replace package trans
  2  as
  3      type array is table of varchar2(4000) index by binary_integer;
  4
  5      g_array array;
  6
  7      function posed( p_cname in varchar2,
  8                      p_tname in varchar2,
  9                      p_where_col in varchar2,
 10                      p_where_val in varchar2 )
 11      return varchar2;
 12  end;
 13  /

Package created.

scott@ORA817DEV>
scott@ORA817DEV>
scott@ORA817DEV> create or replace package body trans
  2  as
  3
  4  function posed( p_cname in varchar2,
  5                  p_tname in varchar2,
  6                  p_where_col in varchar2,
  7                  p_where_val in varchar2 )
  8  return varchar2
  9  is
 10      l_return varchar2(4000);
 11  begin
 12      execute immediate '
 13      begin
 14          select ' || p_cname || ' bulk collect into trans.g_array
 15            from ' || p_tname || '
 16           where ' || p_where_col || ' = :x
 17           order by ' || p_cname || ' ;
 18      end;' using p_where_val;
 19
 20      for i in 1 .. g_array.count
 21      loop
 22          l_return := l_return || ', ' || g_array(i);
 23      end loop;
 24      return substr( l_return, 3 );
 25  end;
 26
 27
 28  end;
 29  /

Package body created.

scott@ORA817DEV> column data format a40 word_wrapped
scott@ORA817DEV>
scott@ORA817DEV> select deptno, trans.posed( 'ename', 'emp', 'deptno', deptno )
data
  2    from dept;

    DEPTNO DATA
---------- ----------------------------------------
        10 CLARK, KING, MILLER
        20 ADAMS, FORD, JONES, SCOTT, SMITH
        30 ALLEN, BLAKE, JAMES, MARTIN, TURNER,
           WARD

        40

scott@ORA817DEV>
scott@ORA817DEV> select deptno, trans.posed( 'hiredate', 'emp', 'deptno', deptno
) data
  2    from dept;

    DEPTNO DATA
---------- ----------------------------------------
        10 09-JUN-1981, 17-NOV-1981, 23-JAN-1982
        20 17-DEC-1980, 02-APR-1981, 03-DEC-1981,
           09-DEC-1982, 12-JAN-1983

        30 20-FEB-1981, 22-FEB-1981, 01-MAY-1981,
           08-SEP-1981, 28-SEP-1981, 03-DEC-1981

        40

March 31, 2003 Steve -- Thanks for the question regarding
      "servererror trigger", version 8.1.7

      You Asked
Tom,
I recently installed a trigger on servererror and log most varieties of error
messages.  I know that the servererror trigger executes in an autonomous
transaction so it can not cause harm to the thread that invoked it.  My question
is:
I observe a PL/SQL error in the log
ORA-06550: line 1, column 13:
PLS-00201: identifier 'SQLCQR_LOGINCHECK' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored
This error is apparently handled by the (vendor's) app since it does not cause
any known process to die.
What can I do to the trigger to learn more about the state of the calling app
other than using DBMS_UTILITY.FORMAT_ERROR_STACK?
The vendor (of course) does not acknowledge the error as it does not show up in
the app's log, so I would like to instrument the trigger to better illuminate
the problem.

      and we said...
search this site for DBMS_TRACE, that's what you want to turn on temporarily to
capture more details.
Review & Followup

      Rating:
      2
Follow-up  March 31, 2003
Reviewer:
      Steve Meller  from Atlanta, GA
Thanks for the reply.  Wouldn't it be too late to call DBMS_TRACE after the
error has already occurred?  I am looking for information at the time of the
call to the servererror trigger, if possible.  That way I don't need to turn on
tracing on all Production jobs to find the situation that invokes the problem.

          Followup:

Turn on trace, have the error happen, turn it off.  That'll be pretty much "it"

Personally -- if it is happening AND the application is working as documented I
would not be losing sleep over it.

March 31, 2003 Tony -- Thanks for the question regarding "How to
      rename datafiles?", version 8.1.5

      You Asked
Hi Tom,
I am having a test DB on NT. Not much in it. The DB was created just to test few
things, it is like a sand-pit DB.
Last week , I added a disk in the system, and by some reason by E drive
containing Oracle DB became F, and new drive became E. So, obviously my instance
did not start. I know the easiest way will be to reverse the mapping of drives.
But I want to go hard way, as I want to get some hands on with DB recovery.
Could you please suggest, what are the areas I look so that I am able to start
the Instance without changing the mapping of drives.
Thanks

      and we said...
edit the init file -- change the location of the control files in there.

"startup mount" the database.  If you attempted "alter database open" at this
point you would get messages like:

idle> alter database open;
alter database open
*
ERROR at line 1:
ORA-01157: cannot identify/lock data file 1 - see DBWR trace file
ORA-01110: data file 1:
'/usr/oracle/ora920/OraHome1/oradata/ora920/system01.dbf'

issue "alter database backup controlfile to trace"

shutdown the database

cd to your user dump destination -- where trace files go, "show parameter user"
before you shutdown will tell you where.

find that trace file (will be newest file in the directory). rename to ctl.sql.

edit it.  remove the junk at the top, upto the STARTUP MOUNT command.

fix the file names in the create control file statement.

run ctl.sql to recreate your control files, database will be up and running at
that point.

This is how you rename files "offline" or just recreate control files.  the
backup controlfile to trace should be part of your backup procedures as well.
Review &
      Followup

      Rating: 4
Or
      simply  March 31, 2003
Reviewer:  Alexandre
       from Rio de Janeiro - Brasil
database mounted...

alter database rename file 'file_old_dir_and_old_name' to
'file_new_dir_and_new_name';

database opened...

          Followup:

simply -- not if you have to mass rename all of the files which is what I
assumed they were doing.  then it would be easier in my opinion to create the
control files -- what about log files as well?

yes, that would work for the test case I provided.

      Rating: 4
No
      matter  March 31, 2003
Reviewer:  Alexandre
       from Rio de Janeiro - Brasil
According Tony´s problem...

Quotes
Last week , I added a disk in the system, and by some reason by E drive
containing Oracle DB became F, and new drive became E
Quotes

and even there were "a thousand" of files...

SIMPLY

database mounted...

select 'alter database rename file '||chr(39)||name||chr(39)||' to
'||chr(39)||'F'||substr(name,2)||chr(39)||';'
from v$datafile;

database opened...

for redologs, repeat the same query above just replacing name for member and
v$datafile for v$logfile...

Cheers,

Alexandre

          Followup:

to each their own -- me, I've always prefered the control file route and a
global change in vi...  sometimes it's easier then sql...

      Rating:
      4
Datafile  March 31, 2003
Reviewer:
      Tony
I think the guy's solution is more reasonable, simple, clean -- on the fly.
Thanks though.

      Rating:
      5
  March 31, 2003
Reviewer:  Alex
       from US
Tony, you missed the point.
1. "Backup controlfile to trace" must be your routine procedure.
2. Instead of generating several scripts, you have only one for simple
search/replace
3. You have ?text? copy of your control files, which you can use for tons of
situations.
4. As always in programming/troubleshooting there are many ways to do the thing,
but
   few to do that optimal way, with deep vision.

      Rating: 4
Reply to Alex -
      USA  April 01, 2003
Reviewer:  Alexandre
       from Rio de Janeiro - Brasil
Dear Alex,

Our friend Tony is not missing the point...

Quotes
1. "Backup controlfile to trace" must be your routine procedure.
Quotes

I do agree...

Quotes
2. Instead of generating several scripts, you have only one for simple
search/replace
Quotes

Several scripts ??? What several scripts ??? My solution was resumed to a simple
query...

Quotes
3. You have "text" copy of your control files, which you can use for tons of
situations.
Quotes

I agree too... but if the situation does not claim to rebuild controlfile, why
am I going to rebuild it, by the way, the simple query I´ve suggested,
internally works renaming the datafiles inside the controlfile without
rebuilding it manually... There are "tons" of information inside the controlfile
that Tony would not like very much to loose, for example, RMAN historical
data... if he uses it...

Quotes
4. As always in programming/troubleshooting there are many ways to do the thing,

but few to do that optimal way, with deep vision.
Quotes

Optimal way ???
Deep Vision ??? Sounds a bit "apple polisher" stuff...

I don´t know if this matches Tony´s case, but what if Tony were doing backups
with RMAN without catalog and his database crashes right after recreating the
controlfile ??? Hummmmmmm... Your several scripts to recover ... caused by
"recreating controlfile"...

Tom, thanks for all,

Alexandre

      Rating:
      4
  May 18, 2003
Reviewer:  A reader
       from Poland
It seems that if your have log files too on "new" volume, then you need to run
two "select" statements.
It is good practice to keep historical archive of control files, just for simple
journaling ( Oh! on that day I moved these files).

Without the recovery catalog in place however, these RMAN features are not
available:
 * Tablespace point-in-time recovery
 * Stored scripts
 * Recovery when the control file is lost or damaged
That is why Oracle strongly recommends that you use the recovery catalog (good
bloody practice too).

      Rating: 5
Rename
      Datafiles  October 18, 2003
Reviewer:  Evan
       from Altanta, Ga USA
I needed a one page description of how to do it. Tahiti had the syntax, but Tom
gave me the "warm and fuzzy". You always have an intelligent way and a good
reason to do it that way.

Thanks, Tom

March 31, 2003 Lei -- Thanks for the question regarding
      "SQL*Loader-510: Physical record size limit", version SQL*Loader 8.1.7

      You Asked
Dear Tom,

I tried to load a text file to Oracle db using SQL*Loader. Here is the table
definition:
create table mytable (
    key    varchar2(40),
    adate  date,
    atag   number(10),
    aclob  clob,
    version number(10)
);

Here is the SQL*Loader control file (actually it is generated by Oracle
Migration Workbench when I converted it from Informix):

load data
infile 'mydata.dat' "str '<endrec>'"
into table mytable
trailing nullcols
( key char(40),
adate date 'DD/MM/YYYY',
atag ,
aclob char(1024000) nullif aclob=BLANKS,
version )

Since some rows may be big (because of CLOB column), SQL*Loader failed with this
error:

SQL*Loader-510: Physical record in data file (mydata.dat) is longer than the
maximum(1048576)

I tried to increase aclob size in control file, and tried to use BINSIZE on
command line, but none of them worked.

My question is: does SQL*Loader have a limit on the size of physical record? How
can load a large record into Oracle DB?

I am using Oracle 8.1.7 on Solaris 2.7.

Thank you very much.

- Lei

      and we said...
Use:

readsize=3000000

or some large number to raise this limit. (i just did a 2meg clob using that)
Review &
      Followup

      Rating: 5
Thanks, it
      worked.  December 10, 2001
Reviewer:  Lei
       from New Jersey
Thank you very much.

The size limit on CLOB is 4GB. Is there a limit on readsize? May I set
readsize=4GB so that it may work on all CLOBs?

Thanks.

- Lei

          Followup:

looks like readsize is a signed integer, 2billion is the max HOWEVER, you'll
probably get:

$ sqlldr / t readsize=2000000000 bindsize=2000000000

SQL*Loader: Release 8.1.5.0.0 - Production on Mon Dec 10 13:54:54 2001

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

SQL*Loader-700: Out of memory while performing essential allocations [7]

so, no, maxing it out would not be feasible.

      Rating:
      5
  December 10, 2001
Reviewer:
      reader
What si sql*loader -524 error and how to solve it
I tried oerr utility but I don't know what prefix should i use with it ?

 like oerr ora -524 OR oerr ldr-524 ?

          Followup:

SQL*Loader-524: partial record found at end of datafile;

that means the last record in the input datafile did not have the end or record
marker.

search for

-524

on this site for details.

      Rating: 3
How to
      automatically generate the control file?  March 31, 2003

Reviewer:  Don  from TX, USA
Do you know of a oracle utility or have any script which can generate the
control file for the table in your example i.e mytable (with a clob column)

          Followup:

not with a clob per-say but see

http://asktom.oracle.com/~tkyte/flat/index.html
sqlldr_exp does it -- you can modify that to suit your needs.

      Rating: 4
Nicely done
      ...  April 01, 2003
Reviewer:  A reader

March 31, 2003 uma -- Thanks for the question regarding "Primary
      Key VS Unique Index", version 8.1.6

      You Asked
Hi Tom,

   Which is the better way(performance wise) to define the uniqueness in a table
for datawarehouse application where there are bulk inserts with no upadtes - Is
it with  table with primary keys defined on it or in the table with unique index
on the keys? For fact tables the primary keys are always  composite columns of
tables.

When should we go for unique index definition against the primary key
definition?

I find Most of the s/w packages (like SIEBEL) are now coming with unique index
on tables without primary keys.

Thanks,
Uma

      and we said...
a primary key is a unique constraint PLUS not null constraints.

all NOT NULL columns should be marked as NOT NULL -- hence, if it is a true
primary key -- use it.  And mark every possible column as NOT NULL -- that gives
the optimizer more information about the nature of the data and opens up access
plans that might not otherwise be possible.

If it is a primary key (unique+not null) -- use that.
April 01, 2003 Alex -- Thanks for the question regarding
      "Wanting to pass progress information to client layer process -
      DBMS_PIPE?", version 9

      You Asked
Hi Tom,
Thank you for your time and excellent resource.

My issue is that for long SQL processes we would like client to receive some
progress information so that the user is aware that the system has not gone to
sleep and that they have some idea of how much time is remaining for a process
to complete.

For example... a dynamic SQL process and looped cursor activity performing
operations on an aggregate function group by SQL on a table with 40,000,000
rows.

I believe that DBMS_PIPE would serve this purpose... correct?

Would you be able to provide or point me to a resource which will provide a
simple demonstration of this and how the client can communicate/listen to the
DBMS_PIPE monitoring mechanism?

Thank you.

      and we said...
If you have my book "Expert one on one Oracle" -- read the section in the
appendix on dbms_application_info.

If not, search this site for that term.  You are looking for set_session_longops
which populates the v$session_longops table which provides feedback not just to
"a session" but to any session that wants to see....

then it just becomes a matter of the client querying v$session_longops to see
how far it has gone and an estimate of how long it has to go.
Review &
      Followup

      Rating: 3
ok.. tried
      v$session_longops ... no result.  March 31, 2003

Reviewer:  Alex  from Australia
ok.. thanks Tom.

I had a look at:

http://asktom.oracle.com/pls/ask/f?p=4950:8:202029926061138788::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:7115955140126,
I could not seem to get v$session_longops to work on the test sample I gave
it...

Test:

DROP TABLE ztable;
CREATE TABLE ztable(x number, d date);

BEGIN
    for x in 1..1000000 LOOP
       INSERT INTO ztable VALUES (x, SYSDATE);
    END LOOP;
    COMMIT;
END;

While this was running... I queried the following...

select  substr(lo.sid||'-'||lo.serial#,1,10) "SID-Ser#",
        lo.last_update_time "Last Update",
        lo.elapsed_seconds secs,
        lo.sql_address||'-'||lo.sql_hash_value addhash
from    v$session_longops lo
where   lo.username not like 'SYS%'

Yet my query did not return anything.

BTW... I ordered your book from Amazon last Thursday!

          Followup:

well -- you didn't make a single call to
dbms_application_info.set_session_longops!  so, since you did no single long
running database operation (you did lots of little fast things), the database
said "hey, nothing long going on here"

try this perhaps:

declare
    l_nohint number default dbms_application_info.set_session_longops_nohint;
    l_rindex number default l_nohint;
    l_slno   number;
begin
    for i in 1 .. 1000000
    loop
        insert ....;

        dbms_application_info.set_session_longops
        ( rindex =>  l_rindex,
          slno   =>  l_slno,
          op_name => 'my long running operation',
          target  =>  1234,
          target_desc => '"msg description"',
          context     => 0,
          sofar       => i,
          totalwork   => 1000000,
          units       => 'loops'
        );
    end loop;
end;
/

and then in another session run:

select * from v$session_longops where time_remaining > 0;

      Rating:
      4
  March 31, 2003
Reviewer:  Alex
       from Oz.
hmmmm...

You mean dbms_application_info doesn't magically manifest the process progress
data itself!?!?!?

Best to be facetious in the face of my own stupidity! :)

Thank you for the sample code.

If I can ask one further base question on dbms_application_info's
capabilities...

The For.. Loop example works ok because there is a counter process the code can
loop through.

What if you just executed a single SQL statement on a very large table with
group by aggregate functions and nested selects etc etc?

In this scenario you would not get an opportuhnity to post
dbms_application_info.set_session_longops updates!

          Followup:

a long running sql statment that does bulk operations (full scans and such) is
automagically "long operation enabled".

if it uses "first rows" techniques however -- it will not be (but that is OK
cause you get the rows back and would be calling dbms_application_info yourself)

April 01, 2003 Lisa -- Thanks for the question regarding
      "customizing the SQLprompt", version 8.0.5.0.0

      You Asked
Hello Tom,

  I notice in the Oracle Magazine that you have your SQL prompt
displaying "user@instance".  Please tell me how you accomplish
this.  I can get it to display the username OR the instance name
but not both.  Thanks!

-Lisa

      and we said...
Sure, in my login.sql I have the following query:

column global_name new_value gname
set termout off
select lower(user) || '@' ||
decode(global_name, 'ORACLE8.WORLD', '8.0', 'ORA8I.WORLD',
'8i', 'ORA8IDEV.WORLD', 'dev8i', global_name ) global_name from
global_name;
set termout on
set sqlprompt '&gname> '

I just concatenate together whatever I want -- and that's my
prompt.

If you want the SID instead of the global_name, just use

select instance from v$thread

instead...
Review & Followup

      Rating:
      4
login.sql  March 21, 2001
Reviewer:
      Sudhakar  from India
Hi Tom,
just came to know this place for oracle doubts and clarifications, a few days
ago. its been very useful.

The login.sql way for sqlprompt was obvious but i woul like to kno can we have
any trigger which will give me sqlprompt when i connect to some other user or
database. Because login.sql will get accesed when we login to sqlplus first
time.

For instance

first login
system/manager@test
system@test>
'well this is fine'
system@test> connect scott/tiger@test1
system@test>
' the prompt doesnt change to scott@test1'

Hope u understood what i am trying to say

Thanks
Sudhakar

      Rating: 5
sql
      prompt  August 20, 2002
Reviewer:  mo
       from Reston, USA
Tom:

1.  Can you change the prompt if you connect internally to a second instance? do
you have to write ONCOnnect trigger?

thanks

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:928829365838
a trigger CANNOT affect a sqlplus (generic program) prompt.

      Rating: 3
2 prompts
      instead of One SQL> Prompt  September 06, 2002

Reviewer:  Vikas Khanna  from INDIA
Hi Tom,

I have been using this for quite some time, but everytime I work with it, found
that instead of one sql prompt we get 2 sql prompts if the glogin.sql or
login.sql has that text written for changing that SQL prompt.

Instead of regular SQL> prompt just one which we get by default,I always get
something like this :

SCOTT@Oracle8.1.7>
SCOTT@Oracle8.1.7>

Where I am wrong in my script? Please Clarify my doubt.
Thanks

          Followup:

Well, my super special eye goggles that let me see long distances (all of the
way to your monitor) are broken right now.  Hence, I cannot comment on what
might be wrong with your script.

I can only say that if you cut and paste what I have AS IS and use it to start -
it shouldn't happen (never has for me)

that and it is nothing more then a very tiny, minor "annoyance" at best....

      Rating: 5
using CASE
      instead of DECODE  September 06, 2002
Reviewer:
      Paul Druker  from Fairfax, VA USA
I've modified Tom's script a little bit -- to remove the second part (.WORLD) of
the global_name value. This is the modified select statement:

select lower(user) || '@' ||
case when instr(global_name,'.')>1
     then substr(global_name,1,instr(global_name,'.')-1)
     else global_name end
global_name from global_name;

      Rating: 4
Customising SQL
      prompt !  September 06, 2002
Reviewer:  Rama
       from CT USA
Tom,

I have Oracle 9 on Win 2000 on my personal comp. Now, I searched for login.sql,
instead I found glogin.sql and there's no connect.sql there. Is login.sql
replaced with glogin.sql in Oracle 9. And what about connect.sql ?? Your
response would be highly appreciated.

          Followup:

login.sql is a "personal" login.sql.  glogin.sql is a GLOBAL login.sql.  They
both have existed for as long as I can remember.

connect.sql is something I wrote that just does:

connect &1
@login

      Rating:
      3
  September 16, 2002
Reviewer:  A
      Reader  from INDIA
Tom,

If we do the changes in login.sql or glogin.sql to customise SQL Prompt but the
effects of it do appear in the SQL Worksheet when we do use it.

How could we eliminate that script not to run when we are executing SQL
Worksheet and to run when user is connecting to database via SQL*PLUS.

I am working with two Oracle Homes 8.0.5 and 8.1.7. In 8.0.5 for Sql Worksheet
we get the results of the execute via fetch in one line / record.

But in 8.1.7 even in SQL Worksheet it does behave like SQL*PLUS as the results
to appear are distorted and one record can consume many lines depending upon the
length of the record.

Please clarify.

          Followup:

I never use sqlworksheet.  Suggest you refer to the documentation for
sqlworksheet.

      Rating:
      5
  January 15, 2003
Reviewer:
      A reader
Hi Tom,

 I am new to unix.
  How can I customize sql prompt on UNIX.

 Thanks,

          Followup:

depends on your shell.  In the csh I use

set prompt = "\
`/bin/date`\
($USER@`/usr/ucb/hostname`) `pwd`\
> "

      Rating: 5
Other version
      of connect.sql  January 16, 2003
Reviewer:
      Chuck Jolley  from Oklahoma City, OK USA
The prompt part of my connect.sql looks like this:

connect &1
set termout off
col prom new_value prom
select lower(sys_context('USERENV','SESSION_USER'))||
       '@'||
       lower(sys_context('USERENV','DB_NAME'))||
       '.'||
       lower(machine) prom
  from SYS.V_$SESSION where sid = 1;
set sqlprompt "&prom> "
set termout on

The way things are set up here, this works fine.
But, are there any pitfalls in getting the server name the way I do?
For instance when we go from 8.1.7 to 9i?

thanks!

          Followup:

it'll work but I would use DUAL instead of v$session personally.

      Rating: 5
I didn't aks
      clearly  January 16, 2003
Reviewer:  Chuck
      Jolley  from Oklahoma City, OK USA
I guess my real question was:
Is there a better way to get the name of the DB server than

select machine
  from SYS.V_$SESSION where sid = 1;

and will sid = 1 always give the correct session?

Thanks,
Chuck

          Followup:

select host_name from v$instance

      Rating: 4
Next
      lines  February 07, 2003
Reviewer:  Wit
       from Kiev, Ukraine
Thanks to all.
Is there a way to form the prompt next to first one (with line numbers)? It's
not handy to use mouse for copying. Thanks.

      Rating: 5
Prompt get
      stuck  March 31, 2003
Reviewer:  Lucky lucky

Tom,

I have your script for glogin.sql and login.sql for customizing my sql prompt.
This script makes use of v$instance (or v$thread) to read the instance name, so
I have a prompt like:

myname@myinstance>

However, when I log in and the instance is not up, then the prompt gets "stuck"
and I have to ctl-c to go on -- since the v$ views are not accessible in
shutdown mode. That is when we usually get the "connected to an idle instance"
message.

I saw in a recent posting
http://asktom.oracle.com/pls/ask/f?p=4950:8:205987704748436160::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:8913476988509
,you wrote:

idle>

Is there any workaround so my prompt doesn't get stuck at this point ?

          Followup:

define gname=idle

at the top -- after the set termout off, before the query.

      Rating: 4
Prompt Does Not
      Change  April 01, 2003
Reviewer:  Hien Luong
       from Oz
Hi Tom,

I have noticed that in 8i(8.15-8.17) the prompt get changed correctly when you
first started sqlplus but once you have been connected to a database and then
switch to another the prompt remains the same.

For example,

$sqlplus scott/tiger@TEST
scott@TEST >

It works fine so far ..
then in sqlplus you switch to another database, say

scott@TEST > connect scott/tiger@PROD

the you would get

connected.
scott@TEST >

Note that the prompt has not been changed at all.

Can you please tell us how to change the prompt dynamically.

Thanks
Hien Luong

          Followup:

see the first two comments at the top.

      Rating: 4
sqlprompt that
      works  April 01, 2003
Reviewer:  Hien Luong
       from Oz
Tom,

Yeah, a little trick that works beautifully. Hahhah!

Thanks
Hien Luong

April 01, 2003 mohammed -- Thanks for the question regarding
      "SGA Used Size", version 8.1.6.3

      You Asked
Hi Tom

Thanks for all your good work to the DBA community.
Can you clarify this doubt for me. Is there any way to calculate the
Used size of the SGA in a instance. For e.g. i had allocated 100 MB for my
db_block_buffer then after running some series of test transactions i would like
to know how much of this db_block_buffers had been used in terms of bytes.
I would also like to find the same for Shared Pool Size is there any way out
there to determine the above sizes.

Thanks in Advance

Abu

      and we said...
See v$sgastat for the shared pool.

for the buffer pool, v$bh will help you:

sys@ORA817DEV.US.ORACLE.COM> select decode( status, 'free', 'free', 'used' ),
count(*)
  2  from v$bh
  3  group by decode( status, 'free', 'free', 'used' );

DECO   COUNT(*)
---- ----------
free       9007
used       1993

shows i'm using 1993 of my 11000 blocks...

sys@ORA817DEV.US.ORACLE.COM> alter table ops$tkyte.bigger_table cache;
Table altered.

I want to let all blocks from this huge table get cached..

sys@ORA817DEV.US.ORACLE.COM> set autotrace traceonly
sys@ORA817DEV.US.ORACLE.COM> select /*+ FULL */ count(*) from
ops$tkyte.bigger_table;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'BIGGER_TABLE'

sys@ORA817DEV.US.ORACLE.COM> select decode( status, 'free', 'free', 'used' ),
count(*)
  2  from v$bh
  3  group by decode( status, 'free', 'free', 'used' );

DECO   COUNT(*)
---- ----------
used      11000

as you can see, we've totally flooded the buffer pool....

sys@ORA817DEV.US.ORACLE.COM> alter tablespace users offline;
Tablespace altered.

sys@ORA817DEV.US.ORACLE.COM> alter tablespace users online;
Tablespace altered.

sys@ORA817DEV.US.ORACLE.COM> select decode( status, 'free', 'free', 'used' ),
count(*)
  2  from v$bh
  3  group by decode( status, 'free', 'free', 'used' );

DECO   COUNT(*)
---- ----------
free      10534
used        466

sys@ORA817DEV.US.ORACLE.COM>

and that flushed them right out... see the definition of v$bh, you can get these
counts by tablespace/file/whatever.
Review &
      Followup

      Rating: 4
Thanks a
      ton  December 09, 2001
Reviewer:  mohammed
      abubakker  from london
Hi Tom
Thanks a ton for your great answer &  quick reply.

Regards
Abu

      Rating:
      4
confusing  December 09, 2001
Reviewer:
      Rajeev  from NY, USA
can you explain this... the result was confusing.

SQL> desc v$bh;
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 FILE#                                              NUMBER
 BLOCK#                                             NUMBER
 CLASS#                                             NUMBER
 STATUS                                             VARCHAR2(5)
 XNC                                                NUMBER
 FORCED_READS                                       NUMBER
 FORCED_WRITES                                      NUMBER
 LOCK_ELEMENT_ADDR                                  RAW(4)
 LOCK_ELEMENT_NAME                                  NUMBER
 LOCK_ELEMENT_CLASS                                 NUMBER
 DIRTY                                              VARCHAR2(1)
 TEMP                                               VARCHAR2(1)
 PING                                               VARCHAR2(1)
 STALE                                              VARCHAR2(1)
 DIRECT                                             VARCHAR2(1)
 NEW                                                VARCHAR2(1)
 OBJD                                               NUMBER
 TS#                                                NUMBER

SQL> select decode( status, 'free', 'free', 'used' ),
  2  count(*)
  3    from v$bh
  4   group by decode( status, 'free', 'free', 'used' );

DECO   COUNT(*)
---- ----------
free         30
used       7834

SQL> alter system flush shared_pool;

System altered.

SQL> select decode( status, 'free', 'free', 'used' ),
  2  count(*)
  3    from v$bh
  4   group by decode( status, 'free', 'free', 'used' );

DECO   COUNT(*)
---- ----------
free         30
used       7834

SQL> alter tablespace users offline;

Tablespace altered.

SQL> alter tablespace users online;

Tablespace altered.

SQL> select decode( status, 'free', 'free', 'used' ),
  2  count(*)
  3    from v$bh
  4   group by decode( status, 'free', 'free', 'used' );

DECO   COUNT(*)
---- ----------
free          2
used       7862

          Followup:

One would not expect the flushing of the shared pool to affect the buffer cache.

as for the second, offline'ing users and online'ing it -- there could be many
causes for your observed behavior:

a) you are on a multi-user system (i was not).  USERS isn't really used, so it
was never in the buffer cache (hence offlining it did nothing to v$bh).  While
you offlined/onlined it -- 28 more blocks were used in the buffer cache by other
sessions.

b) you are on a single user system.  USERS isn't really used (in my example,
bigger table was in the USERS tablespace).  the recursive SQL you did to parse
the alters used 28 more blocks in the buffer cache.

Its all sensible.  What you need to do is find some object, put it IN the buffer
cache (like I did) offline/online the tablespace and then see the results.

Or add FILE# into the query so you can see free/used buffers by file.  Then
offline a tablespace that has a file with lots of used blocks.

      Rating:
      5
Reader  December 21, 2001
Reviewer:
      Reader  from USA
Tom,

Is there a flag in X$bh or any tables/views to show the
status of a BUFFER in buffer pool whether it is
checkpointed or waiting to be checkpointed

Thanks

          Followup:

look at V$BH.DIRTY

      Rating:
      4
  August 14, 2002
Reviewer:
      Jayakumar  from Dubai
Hi,
We are using Oracle 8.0.5 (Alpha) on NT. When I queried the V$BH, NO rows was
selected. At the same time I'm able to find rows in X$BH.
1.Tell me, how can I find the status of the Blocks in X$BH ??
2.Should I run any utility scripts to make the view (V$BH) select rows.

Thanks in advance
Jai.

          Followup:

8.0.5 is really old and non-supported/supportable.

But anyway, is this a database that was upgraded?  If so, you most likely didn't
run the upgrade scripts.  V$BH changed from being a "view" to being a "fixed
view" from 7.x to 8.0.  If you did not run the upgrade scripts, this change
didn't take place.

Contact support if you believe that is the case and they might be able to help
you figure out what scripts to run given your circumstances (hopefully, whomever
upgraded you remembers what they did/didn't do)

      Rating: 4
Some question
      on Buffer Pool Keep.  October 01, 2002
Reviewer:
      Shailandra  from NJ
Hi Tom,

How do I find out the segment_name and no of blocks used by them in the keep
Buffer Pool on Oracle 9i. I have assigned some tables to db_cache_keep using -

alter table t1 storage (buffer_pool keep);

But now I want to find out how much of the keep buffer pool has been used and by
how many blocks for each tables/indexes. Is there a way to find this out?

Thanks
Shailandra

          Followup:

v$bh joined to dba_objects to  dba_segments would be one way.

      Rating: 4
Getting
      Different Result from the query  April 01, 2003

Reviewer:  Sunil Gururaj  from Cyprus
Hi Tom,

I ran the query mentioned by you on v$bh table and here is what I get. I am
running Oracle 8173 (recently upgraded from 8063) on Solaris 8.

SQL> l
  1  select decode( status, 'free', 'free', 'used' ),
  2  count(*)
  3  from v$bh
  4*  group by decode( status, 'free', 'free', 'used' )
SQL> /

DECO   COUNT(*)
---- ----------
used     128000

What does this mean? Am I missing something here? Why am I not getting the
"free" part of the result set? BTW, I have not set the BUFFER_POOL_KEEP
parameter. Does it affect the result set?

We are planning to cache a few small tables (less than 64K each) in the SGA as
these are being frequently accessed and these tables are reference tables which
do not change often.

Please advice me about the advantages and disadvantages of caching
lookup/reference tables in the SGA.

Thanks as always,

Sunil Gururaj

          Followup:

re-read the answer above -- there I show "got some free".  Then, I ran a big
query -- then I "had no free".

All that is saying is "all of the blocks in your buffer cache are in a state
other then free right now".

You get no free cause you have no free.

reading further in the question -- you would find you might get free if you
offlined some tablespaces -- flushing the buffer cache of those blocks (not
recommended on a live system!)

The buffer cache is so named cause it caches things.  If these lookup tables
are popular enough to cause them to be cached -- they will.  At 64k they will be
cacheable for sure.  Let the buffer cache do its job.  If they are hot, they'll
be there.  If they are not, they won't.

The buffer cache will cache frequently used data automagically -- that is its
job.  For a table this size, you don't need to set the CACHE attribute which
doesn't really 'cache' the table, just changed the way it is handled in the
buffer cache.  short tables are 'cached' already.

      Rating: 3
More details
      about v$bg?  September 10, 2003
Reviewer:
      Christo Kutrovsky  from Ottawa, ON Canada
Tom,

Can you comment the TEMP, STALE and DIRECT flags in v$bh, possibly with examples
? When a block can be temp ? What does it mean and etc.

Thanks.

April 01, 2003 -- Thanks for the question regarding "Analytic
      function", version 817

      You Asked
Tom,

How can I compare dates from different column and different rows,
that is compare value of start date column with value of end date column of
previous row?

Many thanks,

      and we said...
LAG does that

ops$tkyte@ORA817DEV> select object_name, created, last_ddl_time,
  2             lag(last_ddl_time) over (order by created)
  3    from user_objects
  4   order by created
  5  /

OBJECT_NAME     CREATED              LAST_DDL_TIME        LAG(LAST_DDL_TIME)OV
--------------- -------------------- -------------------- --------------------
TRANS           31-mar-2003 14:24:24 31-mar-2003 14:27:29
TRANS           31-mar-2003 14:24:24 31-mar-2003 14:27:29 31-mar-2003 14:27:29
REF_DEPT_HIERAR 31-mar-2003 19:30:59 31-mar-2003 19:30:59 31-mar-2003 14:27:29
CHY

SYS_C0013024    31-mar-2003 19:30:59 31-mar-2003 19:30:59 31-mar-2003 19:30:59
UPDATE_T_LOTS   01-apr-2003 07:50:33 01-apr-2003 07:50:57 31-mar-2003 19:30:59
RUN_DEMO        01-apr-2003 07:50:34 01-apr-2003 07:50:57 01-apr-2003 07:50:57
T               01-apr-2003 07:50:57 01-apr-2003 07:50:57 01-apr-2003 07:50:57

7 rows selected.

April 01, 2003 cosmin -- Thanks for the question regarding "re:
      list partitioning; update time", version 9.2

      You Asked
hi Tom,
I have two quick questions:
1.  Is it possible to have a list or multi column range partition on a table
where the partitioning is segmented accross all columns, ie, let's say there we
have two Number(1) columns, Fld1, Fld2 and I need to have data partitioned such
that I have this scenario:

a) Fld1=1, Fld2=1
b) Fld1=1, Fld2=2
C) Fld1=2, Fld2=1
d) Fld1=2, Fld2=2

As far as I can tell, this is not possible; I have to make Fld1 unique.  Is that
so, are there any other workarounds?

2.  I have a partitioned table of 200 million recs, split into 4 partitions
(with each partition having a datafile on each of 3 disks, 12 datafiles) and an
exact replica of the table but not partitioned, again, with the datafiles spread
on each of 3 disks.  The tables are not indexed and I do an update on each table
in which the 'where' clause contains the 'key' for just one partition, ie,
updating just one partition, 50 million rows.  I can see that Oracle works on
all blocks for the unpartitioned table and only on 1/4th of the blocks for the
partitioned table however the execution time is exactly the same (i'm doing this
operation serially not both at the same time).  How could that be?

thanks v. much for any enlightenment,
Cosmin

      and we said...
1) ops$tkyte@ORA920> CREATE TABLE t
  2  (
  3    c1   int,
  4    c2   int
  5  )
  6  PARTITION BY RANGE (c1) subpartition by list (c2)
  7  subpartition template (subpartition s1 values ( 1 ),
  8                         subpartition s2 values ( 2 ))
  9  ( PARTITION p1 VALUES LESS THAN (2),
 10    PARTITION p2 VALUES LESS THAN (3)
 11  )
 12  ;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( 1, 1 );

1 row created.

ops$tkyte@ORA920> insert into t values ( 1, 2 );

1 row created.

ops$tkyte@ORA920> insert into t values ( 2, 1 );

1 row created.

ops$tkyte@ORA920> insert into t values ( 2, 2 );

1 row created.

ops$tkyte@ORA920> select * from t subpartition(p1_s1);

        C1         C2
---------- ----------
         1          1

ops$tkyte@ORA920> select * from t subpartition(p1_s2);

        C1         C2
---------- ----------
         1          2

ops$tkyte@ORA920> select * from t subpartition(p2_s1);

        C1         C2
---------- ----------
         2          1

ops$tkyte@ORA920> select * from t subpartition(p2_s2);

        C1         C2
---------- ----------
         2          2

ops$tkyte@ORA920>

2) all that means is that the time to full scan is trivial as compared to the
time to actually update 50 million records.  Even if the full scan took 4 times
as long (and if parallel query kicked in, this would be questionable) -- the
time to update 50 million records is such a large percentage of the actual run
time that the full scan time just became "noise".

If it takes N seconds to scan a quarter of the data and 4*N seconds to full scan
it all but it takes 1000*N seconds to update the 50 million records -- that 3N
seconds gets lost in the sheer size of the other number.
Review &
      Followup

      Rating: 5
thanks a
      bunch  April 01, 2003
Reviewer:  cosmin ioan
       from Deerfield Beach, FL
Tom, as always, your answers backed by practical examples are something we
should all strive for.  Thanks a big bunch for your help and dedication to the
Oracle community.

April 01, 2003 -- Thanks for the question regarding "How to
      update lob", version 9.2.0

      You Asked
I want to edit text in the lob column like this:

update statements
set sql_text = substr(sql_text,1,instr(sql_text, 'TABLESPACE', 1)-1)
            || substr(sql_text,instr(sql_text, 'work_space', 1)+10),
where instr(sql_text, 'TABLESPACE', 1)>1
and instr(sql_text, '&', 1)>1
and instr(sql_text, 'work_space', 1)=instr(sql_text, '&', 1)+1

(Basically, I want to get rid of "TABLESPACE &work_space" substrings). Am I
missing dbms_lob call somewhere?

      and we said...
why not just

update statements
   set sql_text = replace( sql_text, 'TABLESPACE &work_space', '' )

?

In 9i, you dont need to use dbms_lob if you don't want to.

ops$tkyte@ORA920> create table t ( sql_text clob );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( 'create table t ( x int ) TABLESPACE
&work_space' );

1 row created.

ops$tkyte@ORA920> select * from t;

SQL_TEXT
--------------------------------------------------------------------------------
create table t ( x int ) TABLESPACE &work_space

ops$tkyte@ORA920> update t set sql_text = replace(sql_text,'TABLESPACE
&work_space', '' );

1 row updated.

ops$tkyte@ORA920> select * from t;

SQL_TEXT
--------------------------------------------------------------------------------
create table t ( x int )

Review &
      Followup

      Rating:
      5
excellent  April 01, 2003
Reviewer:
      mikito harakiri
Minor addition involved SET DEF OFF: sql plus wanted to substitute & even if
it's a part of string literal. (And it doesn't substutute standalone '&').

April 01, 2003 Jay -- Thanks for the question regarding "Columns
      In Select Clause Effecting Explain Plan", version 8.1.6

      You Asked
Does the columns listed in the Select Clause ever effect the explain plan?  If
so, why.

      and we said...
sure....

select indexed_column from t;

maybe we index fast full scan the index on T, vs

select indexed_column, not_in_the_index from T...

which would full scan the TABLE t.

or we skip table access by rowid steps in the plan because the query can be
answered from the index...

you should select only those column that which you actually need.

Review &
      Followup

      Rating:
      4
Thanks  March 31, 2003
Reviewer:
      Jay

      Rating:
      5
  April 01, 2003
Reviewer:
      A reader
Can you give an example??

          Followup:

scott@ORA920> select * from emp;

14 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=14 Bytes=448)
   1    0   TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=14 Bytes=448)

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

scott@ORA920> select empno from emp;

14 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=14 Bytes=42)
   1    0   INDEX (FULL SCAN) OF 'EMP_PK' (UNIQUE) (Cost=1 Card=14 Bytes=42)

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

      Rating:
      5
Good  April 01, 2003
Reviewer:
      A reader

April 01, 2003 Chris -- Thanks for the question regarding "Using
      a environmental variable for system passwords in UNIX scripts", version
      generic

      You Asked
Tom,

What are the best methods you know for passing sensitive
database passwords to UNIX shell scripts.

Obviously, I don't want to hard-code them into the scripts. I
also don't want to depend on passing manually entered command
line arguments, so the scripts can be run in batch or executed
by non-DBA operators.

I will appreciate your help.

Chris

      and we said...
I don't use passwords.  If the password is available in some way
shape or form for the script to be run by a non-dba -- they'll
be able to get to it somehow.

o I would try to move all "scripts" into stored procedures
(anything you are doing in the scripts with SQL can be done with
SQL in PLSQL).  We can grant execute on these processes to
individuals.  So, instead of having something like:

sqlplus u/p <<EOF
alter tablespace foo begin backup;
exit
EOF

You would code:

sqlplus / <<EOF
execute begin_foo_backup;
exit
EOF

where begin_foo_backup is a procedure owned by someone who has
the privilege to put a tablespace into backup mode and uses
dynamic sql to issue the alter (either dbms_sql in 8.0 and
before or execute immediate 'alter tablespace foo begin backup';
in 8i and up)

o I would use OS authentication to log in (eg: if I am logged in
in Unix -- i can be logged into the database without using a
username or password).

o I would grant execute on the stored procedures to the
appropriate people.  This allows me to say "user1" can run a, b,
c and "user2" can run b, c, d.

Now, they can run the scripts -- the scripts log in as that user
-- we can find out who ran what (and when if need be) and so on.
 No passwords anywhere.

When I say above "I would" -- I really me "I do".
Review &
      Followup

      Rating: 4
Shell variable
      in Sql scripts  March 04, 2002
Reviewer:
      sam  from MA,USA
Hi Tom,
       Is there any way we can use a value in shell variable  in sql where
clause ?.
Thanks,
Sam

          Followup:

yup.

$ cat t.sh
#!/bin/csh -f

sqlplus / <<eof

select '$HOME' from dual;

eof

$ ./t.sh

SQL*Plus: Release 8.1.7.0.0 - Production on Mon Mar 4 20:03:41 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.2.0 - Production
With the Partitioning option
JServer Release 8.1.7.2.0 - Production

ops$tkyte@ORA817DEV.US.ORACLE.COM> ops$tkyte@ORA817DEV.US.ORACLE.COM>
'/EXPORT/HOME/TKYT
------------------
/export/home/tkyte

ops$tkyte@ORA817DEV.US.ORACLE.COM> ops$tkyte@ORA817DEV.US.ORACLE.COM>
Disconnected from Oracle8i Enterprise Edition Release 8.1.7.2.0 - Production
With the Partitioning option
JServer Release 8.1.7.2.0 - Production

      Rating: 4
Unable to use
      externally identified users  March 05, 2002

Reviewer:  Simon Holt  from Dublin, Eire
My current assignment has a number of scripts which I am trying to tidy up and
secure, so this was a very interesting question.

However, a recntly completed security review from the IT security dept.
highlighted the "risk" of using os authenticated oracle users, and have
recommended we remove any that exist! Given that this is the case, would you
have any suggestions for wrapping up passwords somehow?

For example, there is a batch job which is kicked off by a unix script in the
following style:

mybatchscript <password> <logfile_destination>

and this runs via cron - so anyone who can read this users crontab file can see
the password. Also, if anyone happens to log in whilst the job is running (they
would have to be pretty determined, since it runs at 4am ;-)  ) would be able to
grab the password from a ps listing.

I was considering putting the password in a file, making it read only for the
user, and then amending the script to obtain the password from the file - at the
moment I can't think of another way around this, which is a pity since someone
will have to maintain this when the password is forced to change...

          Followup:

Ask them WHY.

It is true if you use OS authenticated users over a network in a Unix
environment (forcing you to set "remote_os_authent=true") that is a security
hole.

The highest level of security you can have is using OS authenticated accounts on
a host based system (no network).

Your security folks are badly mistaken if they "outlaw" OS authenticated
accounts that use direct connections.  They are just about the most secure way
to do this.  It is ONLY true that there are problems if you set the init.ora
parameter remote_os_authent=true -- which is something you should do only in an
environment where you control ALL of the machines on the entire network.

Using OS authenticated accounts is safe, much much safer then what they propose.

As for your example, they would not have to be determined, they would just have
to write their own script to do a "ps >> mylog.txt" using cron themselves or
just write a script:

while(1)
  ps >> mylog.txt
  sleep 20
end

leave that running and go home.  Then use grep later to mine the data.

USE OS AUTHENTICATED ACCOUNTS.  They are 100% secure (more secure then even
identifiedy by accounts.  Do NOT use them over the network in Unix as they are
based on "trust" (just like the r* commands rsh, rexec)

      Rating: 4
script to
      prompt with the instances names ...  April 01, 2003

Reviewer:  reader
Tom,

i have a solaris server, where in i have 3 instances of oracle running. what i
need from you is ....

when i login to the server, i need to be prompted with the instances of oracle
that is running with numbering.when i choose to go into 3rd instance out of 1 or
2 or 3. i would type 3 and when i log in, i should be with the profile of the
3rd instance.

how should i set that shell script in the server. i have seen that functioality
in one of the server.

do your site has the link to that script.?

thanks for your time,

          Followup:

it is coraenv.

it should be installed already in /usr/local/bin or where ever the people
installing said to put it.

it reads the /etc/oratab to see the instances.

      Rating:
      4
coraenv  April 01, 2003
Reviewer:
      reader
tom,

its not running... sorry i dont know how to run it. it  says ./coraenv[33]:
syntax error at line 39 : `set' unexpected

i tried in different servers.. but same error. the files are exactly the same in
different servers.  is it like we need to run it with setting anything ?

Thanks ,.

          Followup:

$ csh coraenv

its a c-shell

      Rating:
      4
coraenv  April 01, 2003
Reviewer:
      reader
tom.

sorry, for my ignorance. i ran the script and it asks me this ...

$ csh coraenv
ORACLE_SID = [DEVEENGC] ? y
ORACLE_HOME = [/u01/app/oracle/product/8.1.5] ? y
$

if i say no also it says samething...

$ csh coraenv
ORACLE_SID = [DEVEENGC] ? n
ORACLE_HOME = [/u01/app/oracle/product/8.1.5] ? n

please let me know ... where/what i am missing.. should i have to add anything
in profile?

thanks a lot... tom.

          Followup:

well, coraenv (or oraenv) just set the environment.

they prompt you for an oracle sid and then use dbhome to find the oracle home
and set the oracle_home/oracle_sid for you.

read below, you can script something like that.

      Rating: 5
Choosing a
      instance when logging in  April 01, 2003
Reviewer:
      A reader
We have a script called oramenu which lets us choose the instance we want when
we log in. We put it under /var/opt/oracle on Solaris 8.

We have a line in /etc/profile to invoke oramenu at login.

. /var/opt/oracle/oramenu

Here is the oramenu script

OPTION=0
VALID=0
while [ $VALID -ne 1 ] ; do
    VALID=1
    echo "\n       Please choose an option\n"
    echo "          1. DVLP database (11.5.2)"
    echo "          2. TEST database (11.5.6)"
    echo "\n       Enter a number (E to exit): \c"
    read OPTION
    case $OPTION in
            1) . /u02/app/applmgr/dvlpappl/APPSORA.env;;
            2) . /u02/app/applmgr/testappl/APPSORA.env;;
            e|E) exit;;
            *) VALID=0 ;;
    esac;
done
export PS1="|`uname -n`|$LOGNAME|$TWO_TASK> "

      Rating: 5
hey ! ...Thats
      great, .. thank you  April 02, 2003
Reviewer:
      Reader

April 01, 2003 Vincent -- Thanks for the question regarding
      "Partial Combination Index", version 8.1.5

      You Asked
Hi Tom,

    This is my first time asking a question about anything on the web.  My
question is I've seen code here at where I work where a combination index is
used partially but using the first index column and then using the 2nd or 3rd
index column equaling itself.  For instance, an index define as

sku_items_idx1 (sku_no, store_no, wh_no)
...

the select clause has

select skt.sku_store_no ...
  from sku_table  skt, store_table stt, ...
 where skt.sku_no = 10001000
   and skt.store_no = skt.store_no
   and skt.wh_no = skt.wh_no
   ...

Does this improve performance by doing this if the sku_table has 20M> rows or
does it have no significance at all?

Thanks,

Vincent

      and we said...
well, it changes the answer:

ops$tkyte@ORA920> create table t ( x int, y int, z int, a char(255) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t select rownum, null, null, 'x' from all_users;

45 rows created.

ops$tkyte@ORA920> create index t_idx on t(x,y,z);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace on explain
ops$tkyte@ORA920>
ops$tkyte@ORA920> select count(*)
  2    from t
  3   where x = 5;

  COUNT(*)
----------
         1

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE)

ops$tkyte@ORA920>
ops$tkyte@ORA920> select count(*)
  2    from t
  3   where x = 5
  4     and y = y
  5     and z = y;

  COUNT(*)
----------
         0

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE)

ops$tkyte@ORA920> set autotrace off

See the count = 1 and count = 0.

It would be semantically more meaningful to code:

where sku_no = 111111
  and store_no is not null
  and wh_no is not null

performance wise -- they would not be any different (the predicate with where
y=y and where y is not null).

performance wise with:

where x = 5

vs

where x = 5 and y = y and z = z

the performance could be different but only because they are totally DIFFERENT
questions and cannot really be compared.
Review &
      Followup

      Rating:
      4
Reason  February 03, 2003
Reviewer:
      A reader
The reason that the two questions yield different answers, doesn't really have
anything to do with indexes. It's the fact that null doesn't equals null.

          Followup:

I was pointing out that comparing the two queries makes no sense -- they ask
totally different questions to begin with.

it is the fact that null is not know to equal or not equal null actually.  they
neither equal eachother, nor do they not equal eachother.

      Rating: 1
Question not
      answered  February 03, 2003
Reviewer:  David

Tom, I think the original question was not answered. He simply wants to know
about that leading edge stuff and such... which btw you answered a zillion times
before. I think you went fuuuuuuuuuuuuuuuuuuuurhter ahead with your answer.

Could you elaborate on it concerning 9i's skip-scaninng ?

          Followup:

the original question was answered.

original question:

Does this improve performance by doing this if the sku_table has 20M> rows or
does it have no significance at all?

my answer:

performance wise -- they would not be any different (the predicate with where
y=y and where y is not null).

performance wise with:

where x = 5

vs

where x = 5 and y = y and z = z

the performance could be different but only because they are totally DIFFERENT
questions and cannot really be compared.

The answer in short is "no"

This has nothing to do with skip scanning whatsoever -- the leading edge
(sku_no) is queried.  skip scanning would (might) come into play if store_no was
in the predicate with a value -- if the predicate were:

where store_no = 55 and sku_no = sku_no and wh_no = wh_no

which should be coded as

where store_no = 55 and sku_no IS NOT NULL and wh_no IS NOT NULL

So, I fail to see how i "failed to answer"....

      Rating: 4
z = y
      ?  February 03, 2003
Reviewer:  D

You wrote:
ops$tkyte@ORA920> select count(*)
  2    from t
  3   where x = 5
  4     and y = y
  5     and z = y;

Shoudn't the last line be: "z = z;" ?

          Followup:

yes, thanks -- same answer tho.

      Rating:
      4
Vincent  February 03, 2003
Reviewer:
      Vincent Henderson  from Irving, Tx
Thanks, Tom that's what I thought!

      Rating:
      5
X=X  February 03, 2003
Reviewer:
      Doug
What's the semantics here ? Shouldn't X=X evaluate to TRUE (or the like) in a
WHERE clause ? What's the meaning of that ? Why does it "invalidate" the query
(I thought only something like 1=2 would) ?

          Followup:

the semantics are:

where x=x

is really

where x is not null

if x is null -- x=x is neither TRUE nor FALSE, but "unknown"

It is not that it "invalidates" the query it is that:

where y = 5 and x=x

cannot be compared to

where y = 5

they return different answers due to nulls.

      Rating: 5
I ran your
      examples  February 03, 2003
Reviewer:
      A reader
But... what do the queries below mean ?

SQL> select count(*) from t where x=x;

  COUNT(*)
----------
        59

SQL> select count(*) from t where x=x and y=y;

  COUNT(*)
----------
         0

SQL> select count(*) from t where x=x and 1=1;

  COUNT(*)
----------
        59

          Followup:

SQL> select count(*) from t where x=x;

  COUNT(*)
----------
        59

that means there are 59 rows in T where is is not null

SQL> select count(*) from t where x=x and y=y;

  COUNT(*)
----------
         0

That means there are NO rows such that:

a) x is not null
b) AND y is not null

you could have 59 rows where (x,y) = (1,null) and another 1000 rows where (x,y)
= (null,1) -- the count

where x=x and y=y

would be ZERO on this 1059 row table

SQL> select count(*) from t where x=x and 1=1;

  COUNT(*)
----------
        59

that is the same as the first query -- you have 59 rows where X IS NOT NULL

It is all due to this:

ops$tkyte@ORA920> select * from dual where NULL=NULL;

no rows selected

ops$tkyte@ORA920> select * from dual where NULL<>NULL;

no rows selected

ops$tkyte@ORA920>

null is either EQUAL nor NOT-EQUAL to null (or anything for that matter)

      Rating: 5
not null or
      'Y'  April 01, 2003
Reviewer:  A reader

Hi tom,

  I have a table
t
------
t_code number,        (PK)
t_name varchar2(50),
t_islive char(1)

now, t_islive can be 'Y' or null
when it is live value of the filed = 'Y'
else null

q1.) so when I query it how shoul it be ?

select * from t
where t_islive is not null;

or

select * from t
where t_islive = 'Y';

q2.)if I create composit unique index on
    (t_code,t_islive) will it help the query ?

db is oracle 817 EE

TIA

          Followup:

well, if it is either Y or null -- it should be:

create table t ( ..., t_islive char(1) check (t_islive = 'Y') );

and then I would use "where = 'Y'"

the composite index would be useful if youre predicate contains both t_code and
t_islive.

April 01, 2003 Vipin -- Thanks for the question regarding
      "Chained Row and Index", version 8.1.7

      You Asked
Hi Tom,

Of course ,this is not a april fool Prank. Thanks for all your support in the
past.

I have two questions :-
1. I am using TOAD ,where under the tool option(main menu) there is one option
(Rebuiliding Multiple Indexes). After choosing this option I select the Schema
and Press the button for checking the Index state,
which gives me a report saying either 'No Rebuild Recommended' or 'Rebuild
Recommended (Pct Used). Does it mean that I should change the Pct User Feature
and then Rebuild the index.

Thanks
Regards

      and we said...
I think you should understand what toad is doing in order to recommend whether
an index should be rebuilt or not.  I've no idea what criteria it employs but
speaking as a guy who is somewhat opposed to blindly rebuilding objects -- I
would say "ignore that feature".

Unless you collect hard facts before and after that show the index rebuild
doesn't actually negatively impact performance and in fact has positive benefits
-- don't do it.

Review &
      Followup

      Rating: 5
toad and
      sqlplus  April 01, 2003
Reviewer:
      A reader
tom,

 In my company, everybody wants to use toad and not sqlplus
they say it is better and faster. I can't convince them other way around.. what
do you say ?

          Followup:

let em use toad and then watch what happens to them when asked to sit at generic
terminal X and do something -- telnet + sqlplus, ubiquitous.   toad -- not so.

toad is fine, many people here even use it (not me).  sqlplus is great -- I
think I personally am much more productive in it then anything else (and I can
even sit down at a windows machine and use it, or a mainframe, or a linux
box...)

      Rating:
      4
  April 01, 2003
Reviewer:  vipin
       from Singapore
Thanks Tom,
I would look how TOAD works.

Thanks once again.For your answer.
Vipin

April 01, 2003 Giuseppe -- Thanks for the question regarding
      "Moniroting suspicious activity", version 7.3.4.0.0

      You Asked
Hi Tom,

Are have a legacy application running on several machines (30 or so)
This application is the only one designed to make changes to certain Tables
All 30 machines logs to Oracle using the same username/password, and the
username/password is well known by everyone.

For political reasons I cannot change this.

Some users use Microsoft Access to browse the contents of the Tables (they link
to Oracle Tables) and they use the same username/password that my application
uses.
Is there a way to prevent users connecting to from another program (not my
application) to modify the data?
--or--
Is there a way to keep a log of what they have been doing (like
changing/deleting data, not only browsing)?

I need to know the date/time, machine name, and what they have changed

I've tried to use this script to monitor the SQL statements that each program is
executing
but it doesn't detect Deletes in Access, only Updates

SELECT SQL_TABLE.*, SESSION_TABLE.* FROM V$SQLTEXT_WITH_NEWLINES SQL_TABLE,
V$SESSION SESSION_TABLE
WHERE
SESSION_TABLE.SQL_HASH_VALUE = SQL_TABLE.HASH_VALUE AND
UPPER(SESSION_TABLE.Program) NOT LIKE 'P:\PHARM2\PHA%' AND --<-- my legacy
application
UPPER(SESSION_TABLE.Program) <> 'ORACLE73.EXE'
ORDER BY HASH_VALUE, PIECE

Thanks in advance.
Giuseppe

      and we said...
political reasons?  you have no chance of securing anything here.  This is the
worst kind of environment.

A single username
A single password
Everyone knows it.

If I want to bypass your "security" all I would do it:

copy sqlplus.exe P:\PHARM2\PHA_SQLPLUS.EXE

and then run that new copy!  no worries there.

You would have to create a trigger on every table.
You would have to log the rows modified into another table.
You would have to use v$session to capture the "machine name" (again, pretty
easy to change that).

It'll affect your performance and disk needs accordingly.

Alternatively, you can enable auditing, it'll capture the same -- but it will
only tell you they did a delete, they did an insert and so on -- it will not
(cannot) show you the data they deleted.
Review &
      Followup

      Rating: 2
Needs to know
      what everyone is doing on the database  March 31, 2003

Reviewer:  Giuseppe  from Japan
Auditing slows the performance down to about 50% and, like you said, it doesn't
show the data they have deleted.
For me, to log all the SQL statements that have been executed against the
database (also including my legacy application) would be good enough.

I would like to assign a unique username/pasword in Oracle that is known only by
my legacy application (not by the customer) and make their current one (the one
that everyone knows) read-only so that they can continue to browse on the
application's tables as they wish.

However, if I do that, they still wants to know what the new username/password
will be (rightfully and arguably so), and it's only a matter of time before
everyone knows it.

What do you recommend in this situation?

Thanks again
Giuseppe

          Followup:

where the heck did you pull that number from -- substantiate it.  You are off by
orders of magnitude.

ignoring you until you prove it.  show me the benchmark.  you are *wrong*.

      Rating: 3
To the original
      question......  April 01, 2003
Reviewer:
      Paul  from UK
I would look for an alternative that you CAN do within the political
constraints....maybe you have already thought of this and can't do it for some
reason, but here goes...

you say "...I cannot change this..." - what counts as a change ?  Can you change
the login/password used ?  It sounds like you could achieve what you want by:

i) Changing the login/password for the legacy app - maybe
   even give each user their own login !  (You'll have to
   do some stuff behind the scenes as you have probably got
   everyone connecting as the schema owner right now ?)

ii) Give the access users a different login to a user that
    only has SELECT on these tables.

This doesn't seem like a big 'change' to the app - but will need some
configuration and testing and I think from what you've said it will meet your
needs in terms of preventing changes - but obviously won't help you in tracking
changes.  It sounds like the access guys are changing stuff and you want to know
who/what - this would stop that as long as people didn't hand-out their
passwords !

How does that sound ?

      Rating: 1
Just looking
      for help  April 01, 2003
Reviewer:  Giuseppe

Tom,

Please understand, I am not pointing fingers here, I am the one in this factory
pushing for the last year to go to 9i, and here I am just looking for help.
50% degrade is the performance of my application. The application fires hundreds
of queries per seconds (depending on what the user does) and we just monitored
the screen response with a stopwatch.
I am not using Oracle for that long, and any correction (and help) is more than
welcomed.

Paul, would there be a way in 7.3.4 to automatically use the Win2K username for,
let's say, Access?

          Followup:

it needs be a little more scientific than that.

      Rating: 5
Giuseppe re:
      access  April 02, 2003
Reviewer:  Paul
       from UK
Guiseppe,

I doubt it - i'm not an Access expert by any means (rarely use it) or an expert
on Oracle Security and which versions it appears in - it might be worth looking
into OS authentication in Oracle, not sure how this works with ODBC though - you
probably need the Oracle ODBC driver or something - I'll stop now because i'm
guessing !

Presumably this is to avoid giving the users 'another' login and password - i.e.
to achieve single-signon ?

Regards,

Paul

April 01, 2003 Subhakar -- Thanks for the question regarding
      "Unknown Oracle Processes", version 8.1.6.2

      You Asked
Hi Tom,

When I issue the command "ps -ef" at the command prompt I see
the following additional processes. Iam not sure what these back
ground processes are... Any help is appreciated..

oracle 19267     1  0   Sep 20 ?    2:01 ora_i201_PROD15_M
oracle 19255     1  0   Sep 20 ?    1:23 ora_i101_PROD15_M
oracle 19261     1  0   Sep 20 ?    1:24 ora_i104_PROD15_M
oracle 19259     1  0   Sep 20 ?    1:29 ora_i103_PROD15_M
oracle 19257     1  0   Sep 20 ?    1:23 ora_i102_PROD15_M

Thankx in advance,
Subhakar

      and we said...
The are io slaves.

DBWR_IO_SLAVES specifies the number of I/O slaves used by the
DBWR process. The DBWR process and its slaves always write to
disk. By default, the value is 0 and I/O slaves are not used.

In Oracle8i if DBWR_IO_SLAVES is set to a non-zero value, the
numbers of I/O slaves used by the ARCH process LGWR process, and
Recovery Manager are set to 4.

Typically, I/O slaves are used to "simulate" asynchronous I/O on
platforms that do not support asynchronous I/O or implement it
inefficiently. However, I/O slaves can be used even when
asynchronous I/O is being used. In that case the I/O slaves will
use asynchronous I/O.

I/O slaves are also useful in database environments with very
large I/O throughput, even if asynchronous I/O is enabled.

Review &
      Followup

      Rating: 5
Process
      name  April 01, 2003
Reviewer:  GVN
       from Houston
Tom,
How can I find out the name of the process in the table v$process(spid)?
I would like to know if we can find out the name of the processes (SMON, PMON..)
for a given user or SID from the oracle DB?

Thanks a lot

          Followup:

look at the program column


Subject | Article | Last Updated (East coast time)
Query Tuning | article only | Apr 02, 2003 07:46 am
Proper SGA size on machines with very large SGA | article only | Apr 02, 2003 07:54 am
Currently running SQL statements | article only | Apr 02, 2003 08:17 am
How can I limit user connection time.. | article only | Apr 02, 2003 08:32 am
Regarding & and && | article only | Apr 02, 2003 09:01 am
VPD | article only | Apr 02, 2003 09:03 am
format | article only | Apr 02, 2003 10:43 am
Invokers Rights | article only | Apr 02, 2003 12:52 pm
Order of Journal Tables | article only | Apr 02, 2003 12:54 pm
Join Condition | article only | Apr 02, 2003 02:31 pm
Context variable | article only | Apr 02, 2003 09:05 pm
Inline Views | article only | Apr 02, 2003 09:09 pm
NOT NULL constraints. | article only | Apr 03, 2003 07:41 am
BInary operator like AND, XOR | article only | Apr 03, 2003 08:20 am
breaking the 4000-byte barrier | article only | Apr 03, 2003 08:33 am
Sizing of SGA | article only | Apr 03, 2003 02:00 pm
Build a hierarchy | article only | Apr 03, 2003 02:08 pm
Error in creating .dat with SQL | article only | Apr 03, 2003 03:02 pm
better exception handling, and more effective way to truncate | article only | Apr 03, 2003 03:04 pm
How to convert julian date into regular date | article only | Apr 03, 2003 09:07 pm
Tuning Process for like Operator ! | article only | Apr 03, 2003 09:19 pm
Features deprecated in oracle 9i | article only | Apr 03, 2003 09:20 pm
Limit Depth of Hierarchical Query | article only | Apr 04, 2003 11:30 am
Track buffer gets with 10046 trace | article only | Apr 05, 2003 12:08 pm
One table per day | article only | Apr 05, 2003 03:09 pm
PL/SQL for batch processing | article only | Apr 05, 2003 03:17 pm
denormalize | article only | Apr 06, 2003 10:28 am
Next Extent Size | article only | Apr 06, 2003 10:35 am
Number of tablespaces for rollback segments | article only | Apr 06, 2003 05:23 pm
Security in Developer forms | article only | Apr 07, 2003 08:02 am
lookups | article only | Apr 07, 2003 08:04 am
To find top three amount | article only | Apr 07, 2003 08:07 am
What are Fixed Size and Variable Size of SGA indicative of and how do we calculate them? | article only | Apr 07, 2003 02:13 pm
soft parse | article only | Apr 07, 2003 02:31 pm
IN vs. = | article only | Apr 07, 2003 03:10 pm
Store and Run a Wav & AVI file in Database | article only | Apr 07, 2003 06:46 pm
stats and truncate | article only | Apr 08, 2003 07:34 am
fragment issue | article only | Apr 08, 2003 07:35 am
Storing rowids in table columns | article only | Apr 08, 2003 07:39 am
Oracle version on Solaris x86 plateform | article only | Apr 08, 2003 12:32 pm
RMAN catalog maintenance | article only | Apr 08, 2003 12:55 pm
Connecting to Oracle from C application. | article only | Apr 08, 2003 01:13 pm
Tables sizing | article only | Apr 08, 2003 01:14 pm
problem in working with dbms_job.submit | article only | Apr 08, 2003 01:36 pm
Executing code across a database link | article only | Apr 08, 2003 05:45 pm
PL/SQL Parameterization | article only | Apr 08, 2003 08:45 pm
row(s) 1 - 46 of 46
April 02, 2003 Vipin -- Thanks for the question regarding "Query Tuning", version 8.1.7
You Asked
Hi Tom,
Sorry for troubling you again.
I have three tables and all the tables have indexes.The details are here.

Table Name               Primary key          Index Column

1.A                      Prod_Code            Prod Code
2.B                      Branch               Branch
3.C                      Branch+Mth           Branch+Mth+Prod_code

SELECT  C.BRANCH,C.MTH,SUBSTR(C.ACC_NBR,1,7) Ccust_code,C.ACC_NBR,
        C.PROD_CODE,   C.STP_FLAG,C.ITEM_COUNT,C.TXN_AMT,C.BIZ_CODE,
        C.CHRG_AMT,
        C.COST_MTH_DIRECT,C.COST_MTH_INDIRECT,
        C.COST_MTH_APHO,
        C.UNIT_COST_MTH,
        C.CCY_CODE,
        C.CHRG_CCY,
        C.UNIT_COST_CCY,
        A.PROD_CODE_DESC,
    B.NAME
FROM    B,
        A,
    C
WHERE   C.BRANCH   = B.BRANCH
AND     C.MTH      = &date
AND     C.PROD_CODE = A.PROD_CODE

Table A and B are master tables and have less than 100 records in each
table.Table C has 1500000 records.
How can I write this query to get the best performance.
My basic question is that what should be the table order in the from clause and
why.also what should be the order in where clause and why.

Thanks
Regads
and we said...
The objects should be analyzed.  (but please, make sure you understand the
implications of doing so if they are not already)

The order of anything in the query won't matter then as you'll be using the CBO.
 Also, it'll be much faster since it'll probably skip lots of nested loops joins
that the RBO is famous for.

Now, if count(*)/count(distinct mnt) from C is a reasonably small number (eg:
you would pull say less then 10-20% of the 1.5 million records on average), you
have the index on C built wrong for this query (at least until 9i when index
skip scans are implemented anyway).  You might consider putting MTH first in
that index.
Review & Followup
Rating: 4
Implications of ANALYZE April 01, 2003
Reviewer: Cefers.br from Brasil
Tom,

You said:

"...(but please, make sure you understand the implications of doing so if they
are not already)..."

I´d like to know more about these "implications". Can we get negative results
(low query performance) by analyzing a table that was not previously analyzed?

Where can I find a documentation about this?

Followup:
read
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76992/optimops.htm#721
if you are not using CBO, introducing it would be akin to upgrading to a new
release of the database.  That is -- you would need to TEST IT, make sure all
works as expected (or better), get used to it, learn how and when you need to
analyze, etc.

Read that document tho, (and the related info in the admin guide)

Rating: 4
April 01, 2003
Reviewer: vipin from Singapore
Tom,I do not understand that reason for putting MTH as the first column in the
Index.
Could you please elaborate a little more.
Thanks
Vipin

Followup:
you have this:

FROM    B,
        A,
    C
WHERE   C.BRANCH   = B.BRANCH
AND     C.MTH      = &date
AND     C.PROD_CODE = A.PROD_CODE

if you want C to drive the query -- c.mth = &date is the predicate you are
looking at.

you can test the different perf characteristics by creating an index
D(mth,prod_code) and using hints to get the various plans generated and compare
the results.

Rating: 1
Percent of rows returned? April 02, 2003
Reviewer: Mark J. Bobak from Belleville, MI
Tom,

I'm surprised to see you recommend a percentage of rows
returned as a reasonable metric for determining index
effectiveness.

I think this has been thoroughly debunked as an unreliable
measure of index effectiveness.  The best explanation I have
read is "When to Use an Index" by Cary Millsap, available
at
http://www.hotsos.com/
Clearly, there can be no arguement that block selectivity
is the only accurate prediction of index effectiveness.

-Mark

Followup:
it varies widely based on cluster factors and other things I've written about
dozens of times.  I've demonstrated that over and over (search for
disorganized).  Sometimes you just have a cut bait or fish -- here I cut bait.

I never said it was the only.  In fact to me 10-20% is higher then I normally
go.  It is "ROT".  Yes, I should not expose ROT (on record as not liking ROT
myself)

April 02, 2003 zhu -- Thanks for the question regarding "Proper SGA size on machines with very large SGA", version 8.1.7
You Asked
hi, tom:
  I have a machine with 12GB of physical memory, and i am thinking of what will
be the proper SGA size.
  For system with small physical memory, generally speaking, we give 1/3 -1/2 of
physical memory to SGA(50-200 to shared pool if not MTS), and the other to data
buffer(Is that the general rule?)
  But for machines with very large memory, Is that still true? I gathered the
following data from my database server:
  vmstat 2:
procs memory page disk faults cpu
r b w swap free re mf pi po fr de sr s6 s9 s1 sd in sy cs us sy id
0 0 0 8966720 380928 4 0 0 0 196 0 28 0 0 0 33 4048 14112 10178 12 4 84
0 0 0 8966720 380912 3 0 0 0 156 0 24 0 0 0 109 4325 15674 10952 16 5 79
0 0 0 8966720 380792 13 0 0 0 1080 0 205 0 0 0 116 3951 12751 9566 14 4 82
0 2 0 8966720 380928 1 0 0 0 72 0 8 0 0 0 88 3762 12588 9174 12 4 85
0 0 0 8966720 380824 2 0 0 0 260 0 38 0 0 0 56 3766 12634 9013 11 6 84
0 0 0 8966720 380904 1 0 0 0 204 0 30 0 0 0 52 3972 13474 9611 22 4 74
0 0 0 8966720 380896 6 0 0 0 328 0 47 0 0 0 81 3845 13669 9349 17 5 78

It seems that there is always that much free memory pages:
average: 380K Page * 8KB/Page = 3040 MB
top -s 1:
last pid: 20319; load averages: 2.96, 2.71, 2.59 20:55:38
453 processes: 448 sleeping, 1 running, 4 on cpu
CPU states: 78.3% idle, 15.3% user, 5.7% kernel, 0.6% iowait, 0.0% swap
Memory: 12G real, 372M free, 5308M swap in use, 8756M swap free

oracle@main-db1$prtmem
Total memory: 11904 Megabytes
Kernel Memory: 418 Megabytes
Application: 5072 Megabytes --sga 4.7GB+
Executable & libs: 83 Megabytes
File Cache: 5963 Megabytes
Free, file cache: 366 Megabytes
Free, free: 5 Megabytes

18:21:15 SQL> show sga
Total System Global Area 4711026364 bytes
Fixed Size 102076 bytes
Variable Size 415678464 bytes
Database Buffers 4294967296 bytes
Redo Buffers 278528 bytes
21:13:39 SQL> show parameter sort_area

NAME TYPE VALUE
------------------------------------ ------- ------------------------------
sort_area_retained_size integer 0
sort_area_size integer 1048576

oracle@main-db1$swap -l
swapfile dev swaplo blocks free
/dev/dsk/c0t10d0s1 32,73 16 8392048 8294992

add some more information:
21:39:27 SQL> select a.name, sum(b.value) from v$sesstat b,v$statname a
21:39:35 2 where a.statistic#=b.statistic#
21:39:42 3 and a.statistic# in (16,15,20,21,188)
21:39:45 4 group by a.name;

NAME SUM(B.VALUE)
---------------------------------------------------------------- ------------
session pga memory 238548664
session pga memory max 243857160
session uga memory 73514488
session uga memory max 144877192
sorts (memory) 26083641

5 rows selected.

21:42:43 SQL> select status,count(*) from v$session group by status;

STATUS COUNT(*)
-------- ----------
ACTIVE 22
INACTIVE 372

I think i can enlarge the data buffer to a larger value, for example 6G, but i
am not sure the optimal value, For this is a 7*24 System, we reboot only when
maintenance after several monthes. I cannot try.
Please share your valuable experience.
Add some important information, we are using Veritas Quick IO so filesystem
cache does not help much, because Quick IO behavior like Raw Device, and double
buffering just consumes more memory.
And if the machine have even large physical memory like 32GB, Can i give the
oracle sga about 25Gb? 1Gb leave for the OS and the other for oracle client
connection.
Thanks.
and we said...
The REAL QUESTION you must ask yourself is:

o How much ram would be best for me.

Now, if you were using Oracle9i, Oracle would help you size your buffer cache,
shared pool and large pool with the buffer cache advisors.

In 8i, you need to do this on your own. But the question you are faced with is
"given an infinite amount of RAM, what is the best size".  Then, if that fits on
your machine, you use it -- else you make it smaller.

It is wholly impossible for me to say what is the best for your system.  I've no
idea what you do -- how you use it -- what your current configuration is doing
for you and so on.

Suggestion:  start running statspack snaps every now and again, build a history
of how you have been performing.

Consider going to 9i in order to take advantage of the ability to be TOLD how
big things should be, to be able to resize these things without shutting down
(dynamically resize the caches online), and to get access to literally hundreds
of new features that make managing the database much easier.
Review & Followup
Rating: 4
very LARGE SGA side effects April 02, 2003
Reviewer: A reader
Hi

according to 9.2.0 doc
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/ch23_os.htm#4230
it states in a very big SGA:

Context Switching
Due to operating system specific characteristics, your system could be spending
a lot of time in context switches. Context switching can be expensive,
especially with a large SGA. Context switching is not an issue on NT, which has
only one process for each instance. All threads share the same page table.

Starting New Operating System Processes
There is a high cost in starting new operating system processes. Programmers
often create single-purpose processes, exit the process, and create a new one.
Doing this re-creates and destroys the process each time. Such logic uses
excessive amounts of CPU, especially with applications that have large SGAs.
This is because you need to build the page tables each time. The problem is
aggravated when you pin or lock shared memory, because you have to access every
page.

For example, if you have a 1 gigabyte SGA, then you might have page table
entries for every 4 KB, and a page table entry might be 8 bytes. You could end
up with (1G / 4 KB) * 8 byte entries. This becomes expensive, because you need
to continually make sure that the page table is loaded.
---------------

isnt 1G / 4 KB) * 8 byte

an incorrect formula.....? I understand m page table would have 1GB/4KB entries
but not this divided by 8! Is this really correct???

And why context switch would be expensive in large SGA?

Cheers

Followup:
(well -- they didn't divide by 8 ;)

maybe it would make more sense to read "1g/4kb * 8 bytes of entries".  they seem
to be discussing the aggregate size of the page table.  the number of entries
would be 1g/4kb, the size would be that *8 in bytes.

context switching by itself is expensive, with a large SGA, you have a large
page table to restore as part of that context itself.

Rating: 5
Private SQL Areas in SGA and in PGA. May 27, 2003
Reviewer: A reader
Private SQL Areas in SGA and in PGA.
In the Chapter 7 of the Memory Architecture of Oracle 9i Database Concepts, it
is talking about the Private SQL Area in two memory structures SGA and PGA:
1)    In the section Shared Pool:
"The library cache includes the shared SQL areas, private SQL areas (in the case
of a multiple transaction server),.."
2)    And the Private SQL Area in the PGA
Could elaborate the difference of the two Private SQL Areas and the correlation?
Thanks.

Followup:
shared sql areas are things like "query plans" -- the parsed, optimized
representation of the SQL.  Things that all sessions can shared.

private sql areas are things like "your bind variables", "your current place in
the execution of the plan", "your SCN (when the query is as of)".  Things that
are private to you.

multiple transaction server -- probably you mean MTS which is multi-threaded
server, now known as shared server.  There, the session specific information is
stored in the SGA instead of the PGA

April 02, 2003 Pradeep -- Thanks for the question regarding "Currently running SQL statements", version 8.1.7
You Asked
How do you interpret LAST_CALL_ET field from the v$session view? What is the
significance of this number field?

 Can I get the execution time by getting the difference between LOGON_TIME and
SYSDATE?

 How do I find the buffer_gets,logical reads for the individual sql statements?
Beacuse the number in V$sqlarea shows for the whole session.
and we said...
last_call_et shows you how long the active session has been active.   If you
search this site for

last_call_et v$session

you'll find my showsql script which uses this value.  the output might look
like:

--------------------
OPS$TKYTE(51,42794) ospid = 13111 command = 3 program = sqlplus@aria (TNS V1-V3)
dedicated server=13112
Tuesday   11:00  Tuesday   11:00 last et = 0
SELECT USERNAME || '('  || SID  || ','  || SERIAL#  || ') ospid
= '  || PROCESS  || ' command = '  || COMMAND  || ' program = '
|| PROGRAM  USERNAME,TO_CHAR(LOGON_TIME,' Day HH24:MI') LOGON_T
IME,TO_CHAR(SYSDATE,' Day HH24:MI') CURRENT_TIME,SQL_ADDRESS,LAS
T_CALL_ET,SID,PADDR   FROM V$SESSION  WHERE STATUS = 'ACTIVE'  A
ND RAWTOHEX(SQL_ADDRESS) != '00'  AND USERNAME IS NOT NULL  ORDE
R BY LAST_CALL_ET
--------------------
WEB$CBURGESS(23,5120) ospid = 13059 command = 0 program = httpd@aria (TNS V1-V3)
dedicated server=13083
Tuesday   11:00  Tuesday   11:00 last et = 14
BEGIN
owa.init_cgi_env(:n__,:nm__,:v__);
END;
--------------------
OPS$ORA817(26,18701) ospid = 12933 command = 0 program = sqlplus@aria (TNS
V1-V3) dedicated server=12947
Tuesday   11:00  Tuesday   11:00 last et = 38
SELECT DECODE(SA.GRANTEE#,1,'PUBLIC',U1.NAME) "GRANTEE",U2.NAME
"GRANTED_ROLE",DECODE(OPTION$,1,'YES','NO') "ADMIN_OPTION"   FRO
M SYSAUTH$@ORACLE8.WORLD SA,DEFROLE$@ORACLE8.WORLD UD,USER$@ORAC
LE8.WORLD U1,USER$@ORACLE8.WORLD U2  WHERE SA.GRANTEE# = UD.USER
# (+)    AND SA.PRIVILEGE# = UD.ROLE# (+)    AND U1.USER# = SA.G
RANTEE#  AND U2.USER# = SA.PRIVILEGE#  MINUS SELECT DECODE(SA.GR
ANTEE#,1,'PUBLIC',U1.NAME) "GRANTEE",U2.NAME "GRANTED_ROLE",DECO
DE(OPTION$,1,'YES','NO') "ADMIN_OPTION"   FROM SYSAUTH$ SA,DEFRO
LE$ UD,USER$ U1,USER$ U2  WHERE SA.GRANTEE# = UD.USER# (+)    AN
D SA.PRIVILEGE# = UD.ROLE# (+)    AND U1.USER# = SA.GRANTEE#  AN
D U2.USER# = SA.PRIVILEGE#
--------------------
OPS$ORA817(30,38969) ospid = 12876 command = 0 program = sqlplus@aria (TNS
V1-V3) dedicated server=12891
Tuesday   11:00  Tuesday   11:00 last et = 38
SELECT ID   FROM WWV_CORNERS  WHERE LEADER = :b1
ops$tkyte@ORA8I.WORLD>

so that shows that last query has been active for 38 seconds.

You can get the LOGGED IN time, which may or may not be the execution time, via
that method.  You can get CPU used by that session from v$sesstat.

You would use TKPROF and SQL_TRACE to get the buffer gets (which are logical
reads) for individual statements (or set autotrace on in sqlplus)
Review & Followup
Rating: 5
Additional possibility January 29, 2002
Reviewer: Connor McDonald from Battersea
LAST_CALL_ET appears to also reflect the last occurrence of when the STATUS
changed.  Thus is status is "INACTIVE", last_call_et will tell you approximately
how long that session has been inactive (as well as the aforementioned active
scenario)

Followup:
Yes, it is.

It is the time since the last call started and the time since the last call
finished.

Rating: 5
January 24, 2003
Reviewer: A reader
Hi Tom,

 Is Value in LAST_CALL_ET  in Second?

Thanks,

Followup:
yup

Rating: 3
Last_Call_et for Background Processes April 02, 2003
Reviewer: Vivek Sharma from Bombay, India
Hi Tom,

I was checking the last_call_et for all the active sessions when I noticed that
the last_call_et for all the background processes (sid 1 to 7) are continuosly
increasing and are very high. The output of the v$session for these processes is
as under :

LAST_CALL_ET SQL_ADDR        SID    SERIAL# OSUSER
------------ -------- ---------- ---------- ---------------
      787820 00                5          1 ora8
      787820 8F7FA548          6          1 ora8
      787820 8F7FBD30          7          1 ora8
      787821 00                1          1 ora8
      787821 00                2          1 ora8
      787821 00                3          1 ora8
      787821 00                4          1 ora8

If these are in seconds, does it mean that the processes (SMON, PMON) are not
doing their work on regular interval.
Secondly when I checked the sql_text of sid 6 from v$salarea it shows

select f.file#, f.block#, f.ts#, f.length from fet$ f, ts$ t where t.ts#=f.ts#
and t.dflextpct!=0

Can u pls explain this.

Thanks in advance.
Vivek Sharma

Followup:
backgrounds are always active.

ops$tkyte@ORA920> select (sysdate-startup_time)*24*60*60 from v$instance;

(SYSDATE-STARTUP_TIME)*24*60*60
-------------------------------
                         151888

time in seconds the instance is up...

ops$tkyte@ORA920> select last_call_et, program, status from v$session;

LAST_CALL_ET PROGRAM                                          STATUS
------------ ------------------------------------------------ --------
      151903 oracle@tkyte-pc (PMON)                           ACTIVE
      151903 oracle@tkyte-pc (DBW0)                           ACTIVE
      151903 oracle@tkyte-pc (LGWR)                           ACTIVE
      151903 oracle@tkyte-pc (CKPT)                           ACTIVE
      151903 oracle@tkyte-pc (SMON)                           ACTIVE
      151903 oracle@tkyte-pc (RECO)                           ACTIVE
      151903 oracle@tkyte-pc (CJQ0)                           ACTIVE
      151903 oracle@tkyte-pc (QMN0)                           ACTIVE
           0 sqlplus@tkyte-pc (TNS V1-V3)                     ACTIVE

9 rows selected.

last call et is time in seconds that the backgrounds have been work -- roughly
the same..

They do their work on regular intervals but like this:

  loop forever
      do work
      goto sleep for some interval or until someone wakes me up
  end loop

So, just like if you submit a plsql block:

loop
   do work
   dbms_lock.sleep(60);
end loop;

it would always be active.

explain what?  that is just the last query it ran.  SMON was looking for
dictionary managed tablespaces that might be in need of free space coalescing.

Rating: 3
Last_Call_et for Background Processes April 02, 2003
Reviewer: Vivek Sharma from Bombay, India
Hi Tom,

I was checking the last_call_et for all the active sessions when I noticed that
the last_call_et for all the background processes (sid 1 to 7) are continuosly
increasing and are very high. The output of the v$session for these processes is
as under :

LAST_CALL_ET SQL_ADDR        SID    SERIAL# OSUSER
------------ -------- ---------- ---------- ---------------
      787820 00                5          1 ora8
      787820 8F7FA548          6          1 ora8
      787820 8F7FBD30          7          1 ora8
      787821 00                1          1 ora8
      787821 00                2          1 ora8
      787821 00                3          1 ora8
      787821 00                4          1 ora8

If these are in seconds, does it mean that the processes (SMON, PMON) are not
doing their work on regular interval.
Secondly when I checked the sql_text of sid 6 from v$salarea it shows

select f.file#, f.block#, f.ts#, f.length from fet$ f, ts$ t where t.ts#=f.ts#
and t.dflextpct!=0

Can u pls explain this.

Thanks in advance.
Vivek Sharma

Rating: 5
April 02, 2003
Reviewer: Robert from Memphis, USA

April 02, 2003 Somrut -- Thanks for the question regarding "How can I limit user connection time..", version 8.1.7
You Asked
In my Database System.. I need user to connect only 15 minute per connect not
interest they are Idle or in work..
How can I dissconnect every session that connect more than 15 min
.. Thank you
and we said...
use profiles.  The session will be killed (status killed in v$session) and all
resources released.  The session will still appear in v$session but will not
consume any resources (locks and such).

See the Admin guide for details on resource profiles, you can limit idle time,
connect time, etc.
April 02, 2003 Farhad -- Thanks for the question regarding "Regarding & and &&", version 81.16
You Asked
Hi Tom,
 Thanks for the previous answer on updation of values in nested
table.Well this time, I want to know the difference between &
and && and @ and @@.
          Hoping for your prompt reply.

 Thanks,
 Farhad.
and we said...
The && makes it so that SQLPlus will not prompt you each time it
sees an &variable in a script.  Normally, if you have many
occurrences of &variable in a script, sqlplus will prompt you
each time for its value.  By using &&variable, you have told
sqlplus to use the same value for each subsequent occurence of
&variable until you would undefine &variable.  For example:

ops$tkyte@ORA8I.WORLD> select '&foo' from dual
  2  /
Enter value for foo: x
old   1: select '&foo' from dual
new   1: select 'x' from dual

'
-
x

ops$tkyte@ORA8I.WORLD> select '&foo' from dual
  2  /
Enter value for foo: y
old   1: select '&foo' from dual
new   1: select 'y' from dual

'
-
y

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select '&&bar' from dual
  2  /
Enter value for bar: a
old   1: select '&&bar' from dual
new   1: select 'a' from dual

'
-
a

ops$tkyte@ORA8I.WORLD> select '&bar' from dual
  2  /
old   1: select '&bar' from dual
new   1: select 'a' from dual

'
-
a

ops$tkyte@ORA8I.WORLD>

Notice how &foo caused us to be prompted 2 times but since I
used &&bar in the second example, we did not get prompted later
on -- it just reused the value.

The @ and @@ both run scripts however, the @@ runs the script
from the directory that the calling script is in.  For example,
you might be in a directory "/export/home/tom" and you run a
script "@/tmp/foo.sql".  If foo.sql has a line "@@bar" in it --
that will run the script @/tmp/bar.sql -- we will look for bar
in the same directory foo is in.  This is commonly used in
install scripts to make it so that the user does not have to cd
into the directory where the script is to run it.
Review & Followup
Rating: 5
How to CD to another Directory when installing with sqlplus scripts... April 02, 2003
Reviewer: pasko from HH, Germany
Hi Tom ,

How could i CD  to a Sub Directory  from my current  Directory while installing
some packages using sql script which has ' @@ '  constructs in it ..

Example :

my_install.sql  has :

@@file1.sql
@@file2.sql
rem  i want to move to sub directory now to get another file
@@..\temp_dir\file3.sql
rem  but the above apparently doesn't seem to work

is this possible or do i have to always specify a full path ?

Thanks in advance.

Followup:
the @@ only works on a simple file name -- no relative paths.

Rating: 3
Is there a way to make @@ to work with relative path? July 20, 2003
Reviewer: Chuan Sat from VA US
Tom

  Your answer on @@ is very informative.  In your last follow up however you
saying that @@ does not work on a relative path?

   Is there a way at all to make it work with such path?  This is because the
root of the relative path in this case may not always be the same, but the
subdirectory that is the relative path does.

For example
I am in c:\WorkDir calling

@@c:\InstallDir\Master.sql

Master.sql has..
@@Table.sql
@@View.sql
@@proc\proc1.sql
@@proc\proc2.sql

In this case, @@ will never find proc1.sql and proc2.sql because it is in proc
not in InstallDir.  I can make it

@@InstallDir\proc\proc1.sql
@@InstallDir\proc\proc1.sql

However the problem is..the root dir. is not always InstallDir, it can be
something else like InstallDir2.  If I move all the files to InstallDir2, the
@InstallDir\proc\proc1.sql becomes immediately invalid.

- is there a another command or a way to make the @@ to work with such relative
path?  Thanks

Rating: 3
pseudo-relative path July 29, 2003
Reviewer: finally_got_it
In unix, I set the SQLPATH environment variable to
include the path to my  master script and the paths to the subdirectories as
well.  I find that the @@./subdirectory syntax works when I do this.

I do something like this -

setenv SQLPATH /auto/bcf/edi/data:/auto/bcf/edi/data/ins

I then run sqlplus from some other directory

SQL> @crorder

This executes crorder.sql, which happens to live in /auto/bcf/edi/data.  The
script contains several calls like  this

@./ins/insbulklineitem
@./ins/prepacklineitem

which work fine.  If I remove the /auto/bcf/tst/edi/tst/data/ins subdirectory
from the SQLPATH, sqlplus reports that it is unable to find insbulklineitem and
insprepacklineitem.  It doesn't make a lot of sense to me, but it works which is
all I am looking for.  Perhaps you can do something similar in Windows.

Rating: 5
Regarding & and && December 08, 2003
Reviewer: Charlotte from Louisiana
Thanks, Tom. This is something I can use every day.

April 02, 2003 pushparaj -- Thanks for the question regarding "VPD", version 8.1.7
You Asked
Tom,

We have a requirement where in another group wants access some of
our tables to look into some tables data.
Our manager do want them to look into all the data and they should be only
looking into the particular that they have to see.
Hence I decided to add policy to the list of tables.

My question is by adding policy to the table, the policy function now is going
to be executed for all the users whoever query this table and which is an
un-necessary burden to the regular application users who have no restriction at
all.

Is there a way to have this policy set up and executed only for the particular
schema id ?

My other thought is to create view for the tables that the other group
want to see and set the policy on the view instead of on the tables.
So that the regular users who queries the tables will not be affected.

Add the policy to the DEPT table
BEGIN
    DBMS_RLS.ADD_POLICY (
     object_schema     =>     'APPLID',
     object_name        =>     'EMP',
     policy_name        =>     'DEPT_POLICY',
     function_schema =>     'APPLID',
     policy_function   =>     'DEPT_POLICY_FUNCTION',
     statement_types   =>     'SELECT'
    );
END;
/

Create the procedure that sets the context:

CREATE OR REPLACE PROCEDURE set_dept_ctx (pnDno IN NUMBER)
AS
BEGIN
    IF SYS_CONTEXT('USERENV','SESSION_USER')='SCOTT' THEN
        DBMS_SESSION.SET_CONTEXT('dept_ctx','DEPTNO',pnDno);
    END IF;
END;
/

Create a context:
CREATE OR REPLACE CONTEXT dept_ctx USING set_dept_ctx
/

CREATE OR REPLACE FUNCTION
DEPT_POLICY_FUNCTION (p_schema IN VARCHAR2,
                     p_object IN  VARCHAR2)
RETURN VARCHAR2
AS
nDno    NUMBER;
sCond   VARCHAR2(20);
BEGIN
    sDno := SYS_CONTEXT('DEPT_CTX’,'DEPTNO’);
    sCond := 'DEPTNO = '||nDno;
    IF (USER='SCOTT') THEN
        RETURN sCond;
    ELSE
        RETURN null;
    END IF;
END;
/

SQLPLUS > CONNECT scott/tiger

SQL > EXECUTE set_dept_ctx ( 10 );

SQL > SELECT * FROM dept;

Thanks
Pushparaj
and we said...
that is the wrong way to code the policy.  should be:

if ( user= 'SCOTT' ) then
   return 'deptno = sys_context( ''dept_ctx'', ''deptno'' )'
else
   return null;
end if;

Now, since this function
a) is very fast
b) is only called during a parse

do you really feel it'll be a burden on the users?  did you benchmark this?  do
you have a number eg: "this takes N times longer now"?

In any case, you could:

a) not grant them access to the table

b) create views EMP_VIEW as select * from emp where deptno = sys_context(
'dept_ctx', 'deptno' );

c) grant them access to the views

You have the tables, they got the views -- you don't bother eachother.

But never never never glue literals in like that -- use BINDS and sys_context is
a great way to bind.
Review & Followup
Rating: 3
Wanted to add something to it January 29, 2003
Reviewer: Jai Bathija from California, USA
We have been performing some tests on VPD and figured that there is a big
difference if we apply the policy as static when compared to dynamic. Default I
think is dynamic.
That causes the underlying function to be parsed as many times as the underlying
object is accessed.
This is an overhead and in our test cases, it results in 25% degradation. Hence
it might help to create the policy as static and not dynamic

Rating: 5
Policy and Refresh of Materialized view January 29, 2003
Reviewer: Ajeet from Cincinnati,OH
Tom -- I have a materialized who is using a table on which we have a security
policy using RLS.
When I tried to refresh(complete) this materialzed view using
dbms_refresh.snapshot --it gives me an error saying fine grain access control
voilated --probably it was ora-1531.But when I disabled the policy ,then
refreshed this view it worked. After refresh , I enabled the policy.
Is it the right way to do this refresh or I am doing something which I should
not.

Followup:
[tkyte@tkyte-pc-isdn tkyte]$ oerr ora 1531
01531, 00000, "a database already open by the instance"

doubt it was that.  when you know what it was and BETTER YET, when you have a
small example (like I do), we'll take a look at it.

Rating: 5
VPD January 30, 2003
Reviewer: Pushparaj Arulappan from NJ, USA
Tom,
Thanks for the solution.

I would like to setup this security for an application
which makes connection to the database from Visual Basic.
I would like to set the context by calling the context setup procedure from the
logon trigger itself. But my problem is getting context value from the user as
part of the connection.
Something like this...
connect scott/tiger 10
where 10 is the parameter value for the set_dept_ctx procedure which will be
called from the LOGON trigger.

The question is:
1. Is it possible or correct approach to set the context from the LOGON trigger.
If yes how do I pass a value as part of the connection to the database so that
the LOGON trigger can grap that value.

My main purpose is to make the context procedure transparent to the application.
If I can set this from the LOGON trigger then the application does not need to
know or call the context procedure to set the context value after making
connection to the database.

Thanks
Pushparaj

Followup:
you won't be doing it that way -- you cannot "pass a parameter upon startup"

and -- if the user gives you the value, I'm confused?  Why bother at all then?
I mean -- I'll just pass you the value of the dept's I want to see, so you
really won't be precluding me from seeing any at all???  but anyway -- in a
logon trigger, most people do a lookup into tables they own and control to find
the value that needs be set.

the only way to make it transparent to the application is to, well, make it
transparent -- to not need ANY inputs from the user whatsoever (table driven for
example)..

Rating: 5
vpd January 31, 2003
Reviewer: Pushparaj Arulappan from NJ, USA
and -- if the user gives you the value, I'm confused?  Why bother at all then?
I mean -- I'll just pass you the value of the dept's I want to see, so you
really won't be precluding me from seeing any at all???

Tom,

The user will be told with a value from his manager
on what he has to work on and other than that particular data he should not see
or know what are the other data resides in the table.

For example, Manager asks user A to work on DEPTNO 10
and so the user A should only see DEPTNO 10 data.
User B is asked to work on DEPTNO 20 and the USER B should
only see DEPTNO 20 data. So actually the Manager directs the person manually on
what to see and work on. The users
does not know about the data in the table.

The users also have permission to logon to the database by other method other
than through the application. like sqlplus.  That's why I would like find out
the best possible way to set the context value.

Thanks
Pushparaj

Followup:
so the user says "hah hah, mgr told me 10, I want to see 20.  I'll just tell the
application 20, now I'm in"

that is my point. why bother having a door if you are going to leave it open?

The manager needs to have a small application written for them.  This
application will allow the manager to insert/update records in a "user to
deptno" table.  Mgr wants employee "BOB" to work on deptno 10 -- fine, mgr goes
to application and updates BOB's record in this table to 10.

Your on-logon trigger trusts this table to have the proper values -- reads it by
username and sets the context.  There you go.  No matter what environment they
log in from -- BOB gets the right deptno.

Your approach, of the user telling you the deptno they are allowed to see, is
like giving someone the ability to tell the bank teller "how much money they
have in the bank".

Rating: 5
Global Contexts. April 02, 2003
Reviewer: Martin from UK
Hi Tom,

I'm very impressed with the whole FGAC / VPD features within 9i, and use them a
lot for our systems. They make implementing a security system very easy to
manage / maintain and you don't need to bother about whether an app references a
table or view (and what predicate to put on it of course!).

One of the things I was wondering though, it used to be the case that a good
mechanism for "securing" an app was to only give access to views / stored procs
instead of direct access to the tables. Is this kind of thing still recommended
with the advent of FGAC?

Also, a similar question, but, if I did give an app access to a view on a table
only rather than direct access to the table, and of course included the security
predicate in the view, presumably this would have a performance benefit as
opposed to a table policy (if only for the context switch to the PL/SQL)?

What's your recommended approach for apps where row-level
security is required?

Thanks in Advance

Followup:
It is cumulative!

PLSQL is just an application development language -- with lots of security
features to boot (really nice that users do not need insert/update/delete/select
on the base tables).  PLSQL lets you have the "super user" concept you get with
many Ntier applications -- but lets the end user log in as themself (so your
procedure KNOWS who is running the procedure).

So, just as I would say your security logic doesn't belong in the middle tier,
the security logic doesn't belong in your PLSQL either.  Your PLSQL | JAVA code
shouldn't be looking at "who is logged in, where are they logged in from, what
application are they user, what app role did they request, etc" to figure out
what data they have access to -- the data can take care of that.

Consider that you do not access table T in just one procedure, but perhaps
dozens.  You want to implement the security as few times as possible -- one
being optimal.  FGAC lets you do that.

Rating: 5
Thanks April 02, 2003
Reviewer: Martin from UK
Thanks Tom,

I suppose, like you say, it's a trade off. I almost consider FGAC (i.e. single
table policy) to be like the forms DEFAULT_WHERE clause... (stick with me on
this one!), in that you get : select * from t and then try and modify the where
clause.

The problem is just like you get in forms when the "security" information is
actually in another table and is better derived by a direct join (i.e. the from
clause) rather than a IN / EXISTS, you would base the block on a view rather
than the table itself. In this case, wouldn't a view mechanism be better from a
performance point of view with the security info in the predicate?

Also, a related question, I've done quite a bit of work now with app contexts,
and understand the concept of global app contexts (setting the session_id etc.)
but can't quite figure out under what circumstances this would actually be
beneficial? I understand that it can negate the need to pass loads of
information between web pages with disconnected sessions, say, and simply have
to pass a session ID around, but is this the only situation? What circumstances
have you used global app contexts?

Look forward to hearing your reply

Followup:
on the web -- when the app context takes measurable time to load. Suppose you
needed to do an LDAP lookup in order to instantiate the context with the proper
information.  Rather then hitting your ldap server once per page, you hit it
once - when they log in.  You would use it when the cost of setting the app
context is sufficiently high enough to mandate it.

April 02, 2003 Dmitry -- Thanks for the question regarding "format", version 9.2.0
You Asked
Hi Tom!
Two quesitons:
1) I have FLOAT field, usually we format it in application and now i want to
format it in query, format would looks like 8.0 -> 8, 8.1->8.1, 0.11 -> 0.11 and
so on... is it possible using TO_CHAR?

2) I have table with two number fields f1 and f2 and want to implement such
thing like unique constraint of 2 fields, but if f1 = 0 then several records
possible. How can i implement this by your opinion?
and we said...
1) no, you would have to use some other string functions to clean out the
characters you don't want.  eg:

ops$tkyte@ORA920>
ops$tkyte@ORA920> select rtrim(rtrim(to_char(x,'99999999990.99'),'0'),'.') from
t;

RTRIM(RTRIM(TO_
---------------
           8
           8.1
           0.11

2) Using the fact that there are function based indexes and that entirely NULL
entries are not made in b*tree indexes, we can:

ops$tkyte@ORA920> create table t ( f1 int, f2 int );

Table created.

ops$tkyte@ORA920> create unique index t_idx on
                                  t( decode(f1,0,to_number(null),f1),
  2                                  decode(f1,0,to_number(null),f2) );

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( 0, 1 );

1 row created.

ops$tkyte@ORA920> insert into t values ( 0, 1 );

1 row created.

ops$tkyte@ORA920> insert into t values ( 1, 1 );

1 row created.

ops$tkyte@ORA920> insert into t values ( 1, 1 );
insert into t values ( 1, 1 )
*
ERROR at line 1:
ORA-00001: unique constraint (OPS$TKYTE.T_IDX) violated
Review & Followup
Rating: 5
great thanks! April 02, 2003
Reviewer: DiZ
you save a lot of my time! especially with second question

Rating: 4
April 02, 2003
Reviewer: DiZ
I use rtrim(to_char(x,'fm9999999990.99'),'.') instead, couse
it doesn't left unnecessary spaces before value

April 02, 2003 Andre -- Thanks for the question regarding "Invokers Rights", version 8.1.6
You Asked
When I use invokers rights in my PL/SQL block, I use the
caller´s privileges and objects in the caller´s schema, instead
of the definer´s.

However Oracle says that "external references to other
statements are still resolved in the schema of the block owner".
What does it mean ? What do "statements" mean in this case ?

Thanks
and we said...
See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:961430030094
for some info on invokers rights.

What they mean by that is that in the following "invokers
rights" routine:

create or replace procedure p
authid current_user
as
begin
   for x in ( select * from T )
   loop
      pkg.proc( t.x );
   end loop;
end;

The table T will be resolved at run time using the identity of
the person running the procedure (eg: if user1 runs it and owns
a table T -- it'll query the table user1.t.  If user2 owns it
and has a view called T, it'll query that view user2.t).

The plsql statement call to pkg.proc however it fixed at
runtime.  No matter who calls this procedure p -- it'll ALWAYS
invoke the same exact pkg.proc call.  Pkg.proc will be
statically linked to this plsql routine.

Using invokers rights with PLSQL is similar to running a pro*c
application.  The function calls C makes are statically defined
at compile time -- the tables accessed by the pro*c program are
resolved using the identity of the person logged in.

If you wanted PLSQL procedures to be dynamically resolved at
runtime as well, you would simply use dynamic sql -- for
example:

create or replace procedure p
authid current_user
as
begin
   for x in ( select * from T )
   loop
      execute immediate 'begin pkg.proc( :x ); end;' using t.x;
   end loop;
end;

would use the same rules to find pkg.proc as it does for T in
this case.
Review & Followup
Rating: 4
what about packages August 30, 2001
Reviewer: Steve
For packages do you declare in the specification or the  body

Followup:
see
http://technet.oracle.com/doc/oracle8i_816/appdev.816/a77069/08_packs.htm#362
it is an attribute of the package specification.

Rating: 5
what is the difference January 13, 2002
Reviewer: Kulguru
Lets say we have two users

user1
user2

user 1 creates a procedure proc1, which accesses table t.

Now lets tryto understand the authid concept( which I guess facilitates invokers
and definers rights).

user 1 has privileges on proc1 as he created it, and on table t without which he
could not have compiled the procedure.

Now user2 wants to executes theprocedure. If the procedure is qualified with
authid current_user clause, are you trying to say that , user2 can execute the
procedure even if he doesnt have privileges to proc1.

In my opinion user2 needs

1.Execute privilege granted to him by user1 on proc1.
2.Necessary privileges on table t

only then can he execute the procedure successfully.

Further, if you are saying that , if user 2 had a table  tlocal to his schema,
then authid facilitates the use of that table t to execute the procedure.. then

1.How is this functionality different from previous versions.
2.Is this all invokers and definers rights about.

Thank you

Followup:
to get the entire story behind invokers/definers rights -- i can recommend my
book.  I spent a good 30 some odd pages on this with tons of examples.

In the above, if user1 created:

create or replace procedure proc1
authid current_user
as
begin
   for x in ( select * from t ) loop
     null;
   end loop;
end;
/

If user2 wants to execute this procedure, they need:

1) execute granted on proc1 to them or some role or public
2) an object (table or view) named T -- this T can be different from the T that
user1 has.  if user2 user has a table T -- this will do.  If there is a public
synonym T for USER500.T and user2 has select on user500.t -- this will do.  If
there is a view T owned by user2 -- this will do (and so on).

basically, a current_user (invoker rights) routine acts alot like an anonymous
block as far as references to tables/views go.  Every one that runs a
current_user procedure might have it execute against totally different objects.

Rating: 5
but how is this different January 13, 2002
Reviewer: A reader
How is this functionality different from previous versions.

Followup:
In prior releases, there was only DEFINERS rights procedures.

If user1 issues:

create package pkg
as
   procedure proc( p_x in number );
end;
/

create table t ( x int );

create or replace procedure p
as
begin
   for x in ( select * from T )
   loop
      pkg.proc( t.x );
   end loop;
end;

Then the table T that is referenced is forever fixed.  Regardless of WHO runs
this procedure P -- the same exact table T will be referenced.  It will be
user1.t

If user1 then issues:

grant execute on p to user2;

and we log in as user2, we can successfully:

begin user1.p; end;

which selects from user1.T and executes user1.pkg.proc.  user2 cannot
however:

select * from user1.t;
begin user1.pkg.proc( 55 ); end;

since they were not granted select on user1.t or execute on user1.pkg.  They can
only use these objects when they run user1.p.

It is a fairly complex topic -- it takes lots of examples to get the point
across (in my opinion).  If you want a full understanding of it -- i've got a
rather large chapter on nothing but in my book.

Rating: 5
November 05, 2002
Reviewer: Kalita
Hi Tom,
I have a procedure which would call other procedures dynamically based on the
name of the procedure passed.

customer@HL163DV1> create or replace procedure ktest1(p_proc_name in varchar2)
authid current_user i
s
  2  v_sql varchar2(2000) := 'begin ' || p_proc_name || '; end;';
  3  begin
  4  execute immediate v_sql;
  5  end ktest1;
  6  /

Procedure created.

Now I have another procedure which creates a table

customer@HL163DV1> create or replace procedure ktest2 authid current_user is
  2  v_sql varchar2(2000) := 'create table t1(c1 varchar2(10))';
  3  begin
  4  execute immediate v_sql;
  5  end ktest2;
  6  /

Procedure created.

If I execute ktest2 directly I can create the table without any problem. But if
I try the execute it dynamically using my first procedure it gives me
customer@HL163DV1> exec ktest1('ktest2');
BEGIN ktest1('ktest2'); END;

*
ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "CUSTOMER.KTEST2", line 4
ORA-06512: at line 1
ORA-06512: at "CUSTOMER.KTEST1", line 4
ORA-06512: at line 1

I think its because I am trying to execute it dynmically using the block. Is
there anyway it could be done.

Thanks,
Kalita

Followup:
I'll have to assume you are in 8i -- in 8i the Invoker rights chain is "broken"
by execute immediate.  The roles are turned "off" and the user must have the
ability to create that table via a role.  This shows in 9i this works.  HOWEVER,
I would say this looks like a shaky infrastructure you are building -- lots of
dynamic "this that and the other thing".  I would question what you are doing
before you finish doing it

ops$tkyte@ORA920.US.ORACLE.COM> drop user a cascade;

User dropped.

ops$tkyte@ORA920.US.ORACLE.COM> create user a identified by a default tablespace
users quota unlimited on users;

User created.

ops$tkyte@ORA920.US.ORACLE.COM> drop role a_role;

Role dropped.

ops$tkyte@ORA920.US.ORACLE.COM> create role a_role;

Role created.

ops$tkyte@ORA920.US.ORACLE.COM> grant create table to a_role;

Grant succeeded.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> grant create session, create procedure, a_role
to a;

Grant succeeded.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> @connect a/a
ops$tkyte@ORA920.US.ORACLE.COM> set termout off
a@ORA920.US.ORACLE.COM> REM GET afiedt.buf NOLIST
a@ORA920.US.ORACLE.COM> set termout on
a@ORA920.US.ORACLE.COM>
a@ORA920.US.ORACLE.COM> create or replace procedure p( tname in varchar2 )
  2  authid current_user
  3  as
  4  begin
  5          dbms_output.put_line( '------------' );
  6          for x in ( select * from session_roles )
  7          loop
  8                  dbms_output.put_line( x.role );
  9          end loop;
 10          execute immediate 'create table ' || tname || ' ( x int )';
 11          dbms_output.put_line( '------------' );
 12  end;
 13  /

Procedure created.

a@ORA920.US.ORACLE.COM>
a@ORA920.US.ORACLE.COM> create or replace procedure p2
  2  authid current_user
  3  as
  4  begin
  5          dbms_output.put_line( 'calling p statically' );
  6          p( 't1' );
  7          dbms_output.put_line( 'calling p dynamically' );
  8          execute immediate 'begin p(''t2''); end;';
  9  end;
 10  /

Procedure created.

a@ORA920.US.ORACLE.COM> set linesize 70
a@ORA920.US.ORACLE.COM> exec p2
calling p statically
------------
A_ROLE
------------
calling p dynamically
------------
A_ROLE
------------

PL/SQL procedure successfully completed.

a@ORA920.US.ORACLE.COM> desc t1
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 X                                            NUMBER(38)

a@ORA920.US.ORACLE.COM> desc t2
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 X                                            NUMBER(38)

a@ORA920.US.ORACLE.COM>

Rating: 5
November 05, 2002
Reviewer: Kalita
Thanks for your quick reply Tom. We were discussing this possibility of
executing some procedures dynamically and were checking if it would be possible
to handle procedures having dynamic sqls inside. It was something that was
supposed to happen very very rarely and so did not really give much importance
to the dynamic calls. Anyway good that now its out of question. Thanks a lot.

Rating: 4
I have a related question. April 01, 2003
Reviewer: John from New Jersey, USA
I have actually posted the question in another thread - "How to get current
schema".  Your answer was I cannot do it simply with "set current_schema".
Somebody suggested to me that AUTHID CURRENT_USER might be a feasible approach.

My task is how to use one set of stored procedures to access data from different
user schema.  All my stored procedures are in packages, so I create my package
header with "AUTHID CURRENT_USER" clause.

Under user A, I did:

CREATE OR REPLACE PACKAGE alert_batch_pkg
AUTHID CURRENT_USER
IS
   ......

END alert_batch_pkg;

I then grant execute privilege to another user B, and create synonym under user
B.  I can now execute packages by login as user B, and it accesses data from
user B.  All work well except that in two procedures I have object type as
parameter.  Whenever I called those procedures, I got error:

ERROR at line 32:
ORA-06550: line 32, column 16:
PLS-00306: wrong number or types of arguments in call to 'ADD_INVENTORY'
ORA-06550: line 32, column 16:
PLS-00306: wrong number or types of arguments in call to 'ADD_INVENTORY'
ORA-06550: line 32, column 4:
PL/SQL: Statement ignored

The same test script run fine under user A.

Here is the procedure interface:

   FUNCTION add_inventory (
      p_inventory              inv_input_data,
      p_inventory_item         table_of_inv_input_prod_data,
      p_status_msg       OUT   VARCHAR2
   )
      RETURN NUMBER;

Here is the script that creates the object type (were created in both user A and
B):

create or replace type dist_input_data
as object (
   form_id                varchar2(15),
   form_date              date,
   pract_id          number,
   pract_addr_id          number,
   form_comment           varchar2(1000),
   user_id                integer,
   rep_id          number)
/

create or replace type dist_input_prod_data
as object (
   product_id             number,
   lot                    varchar2(20),
   qty                    number(5),
   call_type              varchar2(10),
   call_pos               number(5))
/

create or replace type table_of_dist_input_prod_data as table of
dist_input_prod_data
/

I also tried to create the first 2 objects with "AUTHID CURRENT_USER" clause in
user A, but I got the same error message.  I would appreciate it if you can help
me on this.

Followup:
they have to be the SAME object type (not the same structure in 2 schemas -- the
SAME type).  You will want to prefix that object type with the single schema
name that it belongs to.  Just like us with ORDSYS or MDSYS or any of our object
types.

Rating: 5
Thanks for your prompt response. April 02, 2003
Reviewer: John from New Jersey, USA
Hope I understand you correctly.  What you said is that if I want to use invoker
rights in packages, object types used in those packages must come from one
schema, instead of the schema of the invokers.

Here is what I tried and it worked:

1) I created object types in user A as usual, without "AUTHID CURRENT_USER"
clause.
2) Grant execute privilege of those object to user B.
3) Create synonyms for those object types in user B, since my test script that
calls the package need to recognize the object types.
4) Execute my test script in user B and it worked!  Data were inserted into
B.table.

Is this the correct way to implement invoker rights?

Some more questions:

1) Should the object type be created with "AUTHID CURRENT_USER" clause?  In
cases the object need to access data, it should access data from invokers schema
(my test scenario is just using the object to pass data to stored procedures
which in turn add them to the database).  How about the collection TABLE created
based on object type?  It seems that I cannot use "AUTHID CURRENT_USER" clause
there.

2) How Oracle manage the situation when two or more invokers simultaneously
access the objects?  Do they interfere with each other?

3) My third question is somehow related to the previous one.  In the case of
global variables (declared in package header), how does Oracle manage the
threading issues when two or more users access them simultaneously?  I've always
been wonder about this.

BTW, after being at this website for so long, I realized that I need to get a
copy of you One-on-One book.  I got it today, and I'm very impressed with the
topics you covered in that book!

Followup:
that is one way -- but the use of synonyms could be (is to me) confusing.
rather have an "object_type schema" that holds them and just points to them.

1) if you want the queries (if any) in the type bodies to be executed as queries
in the calling packages are -- yes.

The table collection, having NO CODE associated with it - and hence no sql --
doesn't count.  You don't need authid current user, there is no privelege set to
change.

2) nope, it is as if each were in its own database

3) the code is shared -- the data (like any real OS) is private.  This is not a
DLL, this is just like real code in a real OS....

Rating: 5
Thanks a lot! April 02, 2003
Reviewer: John from New Jersey, USA
I've learnt a great deal from the discussions with you!

Sorry for the synonym part that was confusing.  Here is the interface of my
function (object types were created in "jw2"):

   FUNCTION add_inventory (
      p_inventory              jw2.inv_input_data,
      p_inventory_item         jw2.table_of_inv_input_prod_data,
      p_status_msg       OUT   VARCHAR2
   )
      RETURN NUMBER;

The synonyms were solely for my test script to run under JW000.  Without
synonyms, my test script would look like this:

declare
   v_inventory          jw2.inv_input_data := jw2.inv_input_data (null, null,
null, null, null, null, null);
   vo_inventory_item      jw2.inv_input_prod_data := jw2.inv_input_prod_data
(null, null, null);
   vot_inventory_item     jw2.table_of_inv_input_prod_data :=
jw2.table_of_inv_input_prod_data (vo_inventory_item);
   v_status               binary_integer := null;
   v_status_msg        varchar2(512);

begin
   v_inventory.rep_id             := 1;
   v_inventory.form_id            := 36;
   v_inventory.form_date          := to_date('07/28/2002', 'MM/DD/YYYY');
   v_inventory.inventory_type_id  := 1;
   v_inventory.inv_name_id        := 1;
   v_inventory.form_comment       := 'Testing alert AFDA009';
   v_inventory.user_id            := 1;

   vot_inventory_item.extend;

   vot_inventory_item(1).product_id  := 1;
   vot_inventory_item(1).prod_lot_no  := null;
   vot_inventory_item(1).quantity  := 110;

   vot_inventory_item(2) := jw2.inv_input_prod_data (null, null, null);

   vot_inventory_item(2).product_id  := 2;
   vot_inventory_item(2).prod_lot_no  := null;
   vot_inventory_item(2).quantity  := 210;

   v_status := inventory_pkg.add_inventory(v_inventory, vot_inventory_item,
v_status_msg);

  dbms_output.put_line('v_status ='||v_status);
  dbms_output.put_line('v_status_msg ='||v_status_msg);

end;
/

Rating: 5
Package privileges (9ir2) July 04, 2003
Reviewer: Ray from Ottawa,on,ca
I have a situation...

Package A declares

type varray is table of varchar2(32767)    index by binary_integer;

Package B references this declaration as

PROCEDURE Processform
(num_entries in number,
name_array in golk_utility.varray,
value_array in golk_utility.varray,
reserved in golk_utility.varray);

THE_USER has execute rights on Package B

No user has execute rights on Package A

Package B is valid and works to a point.

If the data returned in one of the value_array cells is > 1K the procedure fails
on entry from the webserver (Mod-Plsql) < 1k works like a charm.

I grant execute on Package A to THE_USER
and I can get up to 32K in a cell.

very wierd (to me anyway)

April 02, 2003 Carlos -- Thanks for the question regarding "Order of Journal Tables", version 8.1.1
You Asked
In the Journal Tables there is a field named JN_DATETIME that is Date datatype.
But this datatype has the restriction that it can only handle till seconds and
not miliseconds or so far.
Though, it is usual that I have many inserts on it in a second.
How can I view the inserts on the Journal ordered by time, even in the case they
were made in the same hour/minute/second?
A solution could be to put a sequence field in the journal, but this, I think,
may slow the perfomance.

Thanks,

Carlos
and we said...
Oracle9i supports fractional seconds -

ops$tkyte@ORA920> select systimestamp from dual;

SYSTIMESTAMP
---------------------------------------------------------------------------
02-APR-03 10.45.18.479437 AM -05:00

but in 9i, adding a sequence would give you something to sort on -- but the rows
won't be in order of "insertion" necessarily - in a multi-user environment,
sequences, timestamps, whatever -- you'll get the rows "almost in order".

Adding a sequence will add additional processing.
but if you have a requirement to do so -- so be it, it is unavoidable additional
processing.
Review & Followup
Rating: 1
Don't give me additional solution to my problem April 02, 2003
Reviewer: Carlos Rial from Montevideo, Uruguay
I don't have Oracle9i yet.
According to your answer, the "solution" I wrote is the only possible one

Followup:
I thought you wanted affirmation that what you proposed what the correct
proposal.

sorry -- I didn't think to make something up that sounded better..  Your
proposed solution is the only solution.  You said "may slow performance" and I
agreed -- but also stated that "hey, if its gotta be, its gotta be, not too much
you can do about it".

Unfortunately, I cannot provide functionality that does not exist.

April 02, 2003 Rama Krishna -- Thanks for the question regarding "Join Condition ", version 9i
You Asked
Hi Tom

I need your help in building a function which can create a join condition
between two tables on the basis of the referential constraint between them (i.e.
Foreign Key Constraint) by quering the data dictionary.
I would like to build this so that it can take in parent & child tables names &
their aliases as inputs and returns the join condition as the output of datatype
varchar2. This function should also be able to address the tables with the
composite keys too.
Also It is assumed that the parents column name is the childs
column name are the same.
The function looks as follows:

CREATE OR REPLACE function getjoincond_func (v_parenttable varchar2, v_ptalias
varchar2, v_childtable varchar2, v_ctalais varchar2) return varchar2 is
   cnt number := 0;
   returnvalue varchar2(500);
   v_col varchar2(30);
begin
   for crec in (
     select  parent_cols.column_name  v_col
 from user_constraints child, user_constraints parent,  user_cons_columns
child_cols, user_cons_columns parent_cols
 where  child.r_constraint_name = parent.constraint_name
 and    child.constraint_type = 'R'
 and    parent.constraint_type in ('P', 'U')
 and    child.r_owner=parent.owner
 and    child.constraint_name = child_cols.constraint_name
 and    parent.constraint_name=parent_cols.constraint_name
 and    child.owner = child_cols.owner
 and    child.table_name=child_cols.table_name
 and    parent.owner = parent_cols.owner
 and    parent.table_name = parent_cols.table_name
 and    child_cols.column_name = parent_cols.column_name
 and    parent.table_name = upper(v_parenttable)
 and    child.table_name = upper(v_childtable))
    loop
      if cnt = 0 then
      returnvalue := v_ptalias||'.'||crec.v_col||' =
'||v_ctalais||'.'||crec.v_col;
      elsif cnt > 0 then
      returnvalue := returnvalue || ' and ' ||v_ptalias||'.'||crec.v_col||' =
'||v_ctalais||'.'||crec.v_col;
      end if;
      cnt := cnt +1;
   end loop;
   return returnValue;
end getjoincond_func;
/

I request you to take a look at this and advice me if I am missing out on
anything in acheving the above outlined objectives.
I appreciate the utility of your forum. I would say that this is a boon to the
Developer Community for many reasons.
Thanks in Advance...

Rama Krishna Dronavalli
and we said...
Well, beware of constructs such as:

create table p ( x int primary key, y int unique );
create table c ( x references p, y int references p(y) );

your query will return x, y -- but that is unavoidable here, the end user
doesn't have to specify a relationship...

This query:

select column_name  v_col
  from user_cons_columns
 where table_name = 'P'
   and constraint_name in ( select r_constraint_name
                              from user_constraints
                             where table_name = 'C'
                               and constraint_type = 'R' )
/

will return the same results -- only it would be more efficient I would think.

You just need to find the set of r_constraint_name's on the child table that
relate to foreign keys -- then for the parent table 'P', find the names of the
columns that apply to it.

So, maybe something like this:

ops$tkyte@ORA920> create table p( x int, y int, z int, a date, primary
key(x,y,z) );

Table created.

ops$tkyte@ORA920> create table c ( x int, y int, z int, b date, foreign
key(x,y,z) references p );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace function  joins( p_parent in varchar2,
  2                                     p_palias in varchar2,
  3                                     p_child  in varchar2,
  4                                     p_calias in varchar2 ) return varchar2
  5  is
  6      l_cnames  dbms_sql.varchar2_table;
  7      l_return  long;
  8  begin
  9      select decode( rownum, 1, null, ' and ' ) ||
 10             p_palias || '.' || column_name || ' = ' ||
 11             p_calias || '.' || column_name
 12        bulk collect into l_cnames
 13        from user_cons_columns
 14       where table_name = upper(p_parent)
 15         and constraint_name in ( select r_constraint_name
 16                                    from user_constraints
 17                                   where table_name = upper(p_child)
 18                                     and constraint_type = 'R' );
 19
 20      for i in 1 .. l_cnames.count
 21      loop
 22          l_return := l_return || l_cnames(i);
 23      end loop;
 24
 25      return l_return;
 26  end;
 27  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_output.put_line( joins( 'p', 'parent', 'c', 'child'
) )
parent.X = child.X and parent.Y = child.Y and parent.Z = child.Z

PL/SQL procedure successfully completed.
Review & Followup
Rating: 5
Elegant Approach April 02, 2003
Reviewer: Rama Krishna Dronavalli from Washington DC
Thanks Tom...
The Prosposed approach of yours is definetly more elegant and uses better
programming constructs...
Once again thanks for your immediate response...

Rama krishna Dronavalli

April 02, 2003 Rajesh -- Thanks for the question regarding "Context variable", version 8.1.7
You Asked
Hi Tom,

I am using context variable in packages that contain dynamic SQL to build the
WHERE clause. Ours is a development environment. We have different schemas for
same product with different versions. As part of my build script I create
context (CREATE OR REPLACE ) variables to be used with various packages.

My problem is :

Say I have build schema for our product version 1.0 and couple of peoples are
working against this schema. Now I build 1.1 into another schema in the same
database it would over write the existing context information and the people
working against the earlier schema gets an insufficient privilege error, as the
context variable now belongs to the new schema.

Is there something that I am missing? Is there any way that instead of
recreating the contest variable I can say the second schema to use the context
variable created by the first schema (as the package name is same)?

What is the workaround for this?

Thanks and regards
Rajesh
and we said...
The namespace of a context is "global" -- it is a public object, no one owns it.

Hence, like a directory object, or a public synonym for example -- the context
is global.

Suggestion:  incorporate the schemaid into the context itself.  Since you
dynamically building the sys_context references, this is "easy".

In your package that references the context, use:

create package ....
as
   l_ctx_name varchar2(30)
              default 'my_ctx_' || sys_context( 'userenv', 'schemaid');

procedure set_val( p_name in varchar2, p_val in varchar2 )
is
begin
  dbms_session.set_context( l_ctx_name, p_name, p_val )
end;

function sys_ctx_string( p_name in varchar2 ) return varchar2
is
begin
   return 'sys_context( ''' || l_ctx_name || ''', ''' || p_name || ''' )';
end;

....
   l_where_clause :=
       ' and column = ' || sys_ctx_string( 'YOUR_VARIABLE_NAME' );
....

(schemaid will be the user id of the OWNER of the package or procedure that
makes this call -- so no matter who CALLS the package, the schemaid will be
constant)

Now, all you need to do is create your context using:

begin
  execute immediate
     'create context my_ctx_' || userid || ' using package my_pkg';
end;
/

in your install scripts instead of

create context my_ctx using my_pkg;
Review & Followup
Rating: 5
Thanks July 04, 2002
Reviewer: Rajesh A.G from Ireland
Thank you very much. This will solve my problem.

Rating: 5
This is EXACTLY what I want but ... March 21, 2003
Reviewer: Paul from UK
... but I have a problem (8.1.7):

SQL> select sys_context( 'userenv', 'schemaid') from dual;
ERROR:
ORA-02003: invalid USERENV parameter

no rows selected

I have tried other userenv parameters : current_schema, current_schemaid,
current_user, current_userid, but they all give back the data for the user
running the procedure rather than the owner of the code (well, I'm not
surprised, I guess thats what current means!).

I have checked the documentation, but only the following are valid parameters
for userenv:

terminal
language
lang
sessionid
instance
entryid
isdba
client_info
nls_territory
nls_currency
nls_calendar
nls_date_format
nls_date_language
nls_sort
current_user
current_userid
session_user
session_userid
current_schema
current_schemaid
proxy_user
proxy_userid
db_domain
db_name
host
os_user
external_name
ip_address
network_protocol
bg_job_id
fg_job_id
authentication_type
authentication_data

Followup:
sorry that should have been current_schemaid...  mistake #1 on my part.

mistake #2 -- i should have selected sys_context from dual.  This happens
everytime I try to save a second and just type code in without actually running
it.  going to stop doing that ;(   Consider:

(dr = definers rights, ir = invoker rights)

ops$tkyte@ORA817DEV> create or replace procedure DR
  2  as
  3  begin
  4          dbms_output.put_line
  5          ( 'dr ' || sys_context( 'userenv', 'current_schemaid' ) );
  6          for x in (select sys_context( 'userenv', 'current_schemaid' ) x
  7                                  from dual )
  8          loop
  9                  dbms_output.put_line( 'dr ' || x.x );
 10          end loop;
 11  end;
 12  /

Procedure created.

ops$tkyte@ORA817DEV> create or replace procedure IR
  2  authid current_user
  3  as
  4  begin
  5          dbms_output.put_line
  6          ( 'ir ' || sys_context( 'userenv', 'current_schemaid' ) );
  7          for x in (select sys_context( 'userenv', 'current_schemaid' ) x
  8                                  from dual )
  9          loop
 10                  dbms_output.put_line( 'ir ' || x.x );
 11          end loop;
 12  end;
 13  /

Procedure created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> grant execute on dr to public;

Grant succeeded.

ops$tkyte@ORA817DEV> grant execute on ir to public;

Grant succeeded.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select uid, sys_context( 'userenv', 'current_schemaid' ) cs
from dual;

       UID CS
---------- --------------------
       671 671

so, MY schemaid (uid) is 671

ops$tkyte@ORA817DEV> exec dr
dr 671
dr 671

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> exec ir
ir 671
ir 671

PL/SQL procedure successfully completed.

everything looks normal so far...

ops$tkyte@ORA817DEV> @connect scott/tiger

scott@ORA817DEV> select uid, sys_context( 'userenv', 'current_schemaid' ) cs
from dual;

       UID CS
---------- --------------------
       666 666

interesting -- 666, trying to tell me something I suppose ;)

scott@ORA817DEV> exec ops$tkyte.dr
dr 666
dr 671

PL/SQL procedure successfully completed.

see how the sys_context call that did not actually switch to the database is
SCOTTS uid, but the sys context selected from the database is.  That is a subtle
nuance of sys_context, you should select those from the database

scott@ORA817DEV> exec ops$tkyte.ir
ir 666
ir 666

PL/SQL procedure successfully completed.

 but bear in mind, in an IR routine, the current schemaid is the uid of the
INVOKER, not owner!

Rating: 5
Superbly explained! March 21, 2003
Reviewer: Paul from UK
I wish I could give you more than 5 stars!

Is the subtle difference in SYS_CONTEXT documented? - maybe I was in too much of
a rush and didn't spot it.

Nice to see DUAL still has its uses occasionally!

Followup:
Once I filed a bug on sys_context as when I discovered this initially, thats
what I thought it was -- however, it is the "designed manner of implementation"
(eg: it is supposed to be this way) so I turned it into a doc bug.  Not sure, if
this subtle nuance is fully documented or not.

Rating: 4
April 02, 2003
Reviewer: A reader
if I have a package p1

SQL> create or replace package p_common
  2  as
  3      procedure p(str varchar2);
  4      debug char(1) := 'F';
  5  end;
  6  /

Package created.

  1  create or replace package body p_common
  2  as
  3  procedure p(str varchar2) is
  4   begin
  5         if((debug) = 'T') then
  6              dbms_output.put_line(str);
  7         end if;
  8    end;
  9* end;
 10  /

Package body created.
/***********************************************/
another package
SQL> create or replace package p2
  2  as
  3
  4      debug char(1) := 'T';
  5  end;
  6  /

  1  create or replace package body p2
  2  as
  3  function add(n1 number, n2 number) return number is
  4    n3 number;
  5   begin
  6         n3 := n1 + n2;
  7         p_common.p(' output is: ' to_char(n3));
  8    end;
  9     return n3;
 10* end;
 11  /

now I want the contex or p2 when I call p_common and
if the debug of p2 == 'T'( **NOT P_COMMON.DEBUG)  then
print the line on consol

how can I reference that contex ? can I ?

TIA

Followup:
no, it is not the way the language (or any language i know of) works.

debug is a package variable -- when in p1, it is implicitly p1.debug. it'll
always be p1.debug.

if you goto
http://asktom.oracle.com/~tkyte/article2/index.html#Scripts
and download the scripts for that example, you'll get a little DEBUG package
however that can be used to achieve your goal.  you'll get my debug.sql

Rating: 4
Thanks, April 02, 2003
Reviewer: A reader
Thanks tom.

 also do you have any copy rights on it
can I use it in prod. ? or do  I have to buy it in case I want to  use ?

Followup:
public domain, go for it.

Rating: 5
Thanks, April 02, 2003
Reviewer: A reader
why is it like

SQL> select p2.debug from dual;
select p2.debug from dual
          *
ERROR at line 1:
ORA-00904: invalid column name

SQL> exec :v := p2.debug;

PL/SQL procedure successfully completed.

SQL>

/***************/
why I can't select  from dual ?
in the same session
I can assing but not select  ?
 I just want to read it !!

Followup:
because SQL cannot see plsql variables.  never could.  SQL can see plsql
functions, but not variables.

plsql can see plsql variables just fine.

When sql is hidden inside plsql -- it "appears" that sql can see the variables
-- eg:

begin
   select p2.debug into :v from dual;
end;

would in fact work -- but that is only because plsql rewrites that query
(provable with sql_trace) to be:

   select :bv1 from dual;

and binds bv1 in at runtime -- sql isn't seeing the plsql variable, rather PLSQL
is, factors it out and replaces it with a bind variable.

Rating: 4
April 02, 2003
Reviewer: A reader
plsql can see plsql variables just fine.

When sql is hidden inside plsql -- it "appears" that sql can see the variables
-- eg:

begin
   select p2.debug into :v from dual;
end;
/*********************************************************/
1:)
Wrote file afiedt.buf

  1   create or replace package body p_common
  2   as
  3   procedure p(str varchar2) is
  4    v_owner varchar2(2000) := null;
  5    v_obj_name varchar2(2000) := null;
  6    v_lineno number := null;
  7    v_caller_t varchar2(2000) := null;
  8     v_test  char(1) ;
  9   begin
 10     who_called_me( v_owner,v_obj_name,v_lineno,v_caller_t);
 11     dbms_output.put_line('v_obj_name: ' || v_obj_name || ' and v_owner : '||
v_owner);
 12     execute immediate 'select :obj.debug from dual'
 13                             into v_test using v_obj_name ;
 14          if(v_test = 'T') then
 15               dbms_output.put_line(str);
 16          end if;
 17     end;
 18*  end;
 19  /

Package body created.

SQL> set serveroutput on size 1000000
SQL> exec :n3 := p2.add(5,3);
v_obj_name: P2 and v_owner : SCOTT
BEGIN :n3 := p2.add(5,3); END;

*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SCOTT.P_COMMON", line 12
ORA-06512: at "SCOTT.P2", line 8
ORA-06512: at line 1
/****************************************************/

SQL> ed
Wrote file afiedt.buf

  1   create or replace package body p_common
  2   as
  3   procedure p(str varchar2) is
  4    v_owner varchar2(2000) := null;
  5    v_obj_name varchar2(2000) := null;
  6    v_lineno number := null;
  7    v_caller_t varchar2(2000) := null;
  8     v_test  char(1) ;
  9    v_flag   varchar2(2000);
 10   begin
 11     who_called_me( v_owner,v_obj_name,v_lineno,v_caller_t);
 12     v_flag := 'select '||v_obj_name||'.debug from dual';
 13     dbms_output.put_line('v_obj_name: ' || v_obj_name || ' and v_owner : '||
v_owner);
 14     dbms_output.put_line(v_flag);
 15     execute immediate  ' v_test := v_obj_name.debug';
 16  --   execute immediate v_flag  into v_test ;
 17          if(v_test = 'T') then
 18               dbms_output.put_line(str);
 19          end if;
 20     end;
 21*  end;
 22  /

Package body created.

SQL>  set serveroutput on size 1000000
SQL>  exec :n3 := p2.add(5,3);
v_obj_name: P2 and v_owner : SCOTT
select P2.debug from dual
BEGIN :n3 := p2.add(5,3); END;

*
ERROR at line 1:
ORA-00900: invalid SQL statement
ORA-06512: at "SCOTT.P_COMMON", line 15
ORA-06512: at "SCOTT.P2", line 8
ORA-06512: at line 1
/**********************************************/

why I can't do it in dynamic-sql ?
what is wrong that I am doing ?

Followup:
execute immediate runs sql or plsql.  as I said "sql" cannot "see" plsql
variables.

But plsql could.

 12     execute immediate 'select :obj.debug from dual'
 13                             into v_test using v_obj_name ;

is running SQL (and it is not well formed -- you cannot BIND and identifer!)

If you are trying to dyanmically dereference a plsql variable by name:

execute immediate 'begin :x := ' || l_object_name || '.debug; end;' into l_x;

BUT -- i would beg you not to do this as it appears you are building a dynamic
trace framework and doing that dynamic sql over and over and over will kill you
performance wise -- totally.

Rating: 4
thanks, April 02, 2003
Reviewer: A reader
Nope. I am not doing it that way !!

 I just wanted to do it. and there is a way out there though to get the p2.debug
from p_common with out passing
package_name as parameter

Thanks,

Followup:
execute immediate 'begin :x := ' || l_object_name || '.debug; end;' into l_x;

where l_object_name is the result of the who_called me call.

Rating: 4
does not work April 02, 2003
Reviewer: A reader
SQL> ED
Wrote file afiedt.buf

  1  create or replace package body p_common
  2   as
  3  procedure p(str varchar2) is
  4     v_owner varchar2(2000) := null;
  5     v_obj_name varchar2(2000) := null;
  6     v_lineno number := null;
  7     v_caller_t varchar2(2000) := null;
  8     v_test  char(1) ;
  9     v_flag   varchar2(2000);
 10   begin
 11        who_called_me( v_owner,v_obj_name,v_lineno,v_caller_t);
 12     dbms_output.put_line('v_obj_name: ' || v_obj_name || ' and v_owner : '||
v_owner);
 13     execute immediate
 14      'begin :x := '|| v_obj_name||'.debug; end;'
 15             into v_test; -- using ;
 16          if(v_test = 'T') then
 17               dbms_output.put_line(str);
 18          end if;
 19     end;
 20*  end;
SQL> /

Package body created.

SQL>  set serveroutput on size 1000000
SQL>  exec :n3 := p2.add(5,3);
v_obj_name: P2 and v_owner : SCOTT
BEGIN :n3 := p2.add(5,3); END;

*
ERROR at line 1:
ORA-01008: not all variables bound
ORA-06512: at "SCOTT.P_COMMON", line 13
ORA-06512: at "SCOTT.P2", line 8
ORA-06512: at line 1

Followup:
see, I swore I would stop just posting snippets of code and always run it.  I
didn't again and every time I get burned.... oh well here you go:

ops$tkyte@ORA920> create or replace package foo
  2  as
  3          g_var number default 55;
  4  end;
  5  /

Package created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          l_val number;
  3  begin
  4          execute immediate 'begin :x := foo.g_var; end;' using OUT l_val;
  5          dbms_output.put_line( l_val );
  6  end;
  7  /
55

PL/SQL procedure successfully completed.

Rating: 4
Determining the JOB_ID of process running on a Job Queue November 12, 2003
Reviewer: Robert Paterson from Perth, Western Australia
This is just an FYI.

We just had some problems using SYS_CONTEXT('USERENV','BG_JOB_ID') to determine
the job id of a background job. Investigation revealed that the job id of the
background job is actual returned by SYS_CONTEXT('USERENV','FG_JOB_ID') when
running as a background job. Hope this saves someone some time.

Environment :

Oracle9i Enterprise Edition Release 9.2.0.4.0 - 64bit Production
PL/SQL Release 9.2.0.4.0 - Production
CORE 9.2.0.3.0 Production
TNS for Solaris: Version 9.2.0.4.0 - Production
NLSRTL Version 9.2.0.4.0 - Production

April 02, 2003 Mike -- Thanks for the question regarding "Inline Views", version 8.1.5
You Asked
Tom,

I'm trying to get a handle on inline views.  When I was first learning SQL (many
years ago), inline views were not available.

Would inline views be an appropriate technology for, say, listing the
owners,their tables and rowcounts of their tables in DBA_TABLES?  I can't assume
that the statistics for a given table are very current, so I can't just pull
that value from DBA_TABLES.  I know I can spool a SQL file with the appropriate
criteria and then execute it, but it would be nice not to have to create
temporary files.

Thanks!
Mike Morgan
and we said...
No, an inline view would not be useful for doing that.  In order to query
DBA_TABLES and get a count of rows in a table for each row in DBA_TABLES -- the
inline view would have to query a different table each time.  Thats not what
they are for.  There is a way to do this in 8.1.5 and up easily and I'll
demonstrate that but first I'll show you what inline views are great at.

Lets say you have multiple tables in a query and they show information at
different levels of detail.  Table DBA_FREE_SPACE and DBA_DATA_FILES for
example.  DBA_FREE_SPACE tracks free space in a tablespace by extents.
DBA_DATA_FILES tracks available space in a tablespace by file.  Let's say you
wanted to write a query to print out the tablespace name, amount of space
allocated to it, and the amount of free space.  To get the amount of space
allocated to it, we need the SUM(bytes) from dba_data_files group by TABLEPSACE.
 To get the amount of space FREE in it, we need sum(bytes) from dba_free_space
group by tablespace.  So, if a tablespace had 2 one megabyte files allocated to
it and it had 4 free extents of 256k and we joined by tablespace name(resulting
in 8 rows) and then did the group by -- we would end up with the erroneous
result of 8 megabytes of allocated space (not 2) and 4 meg of free space (not
2).  Since the information is at different levels of detail -- we need to GROUP
BY first and then join.

A query to do this could be:

select nvl(b.tablespace_name,
             nvl(a.tablespace_name,'UNKOWN')) name,
       kbytes_alloc kbytes,
       kbytes_alloc-nvl(kbytes_free,0) used,
       nvl(kbytes_free,0) free,
       ((kbytes_alloc-nvl(kbytes_free,0))/
                          kbytes_alloc)*100 pct_used,
       nvl(largest,0) largest,
       nvl(kbytes_max,kbytes_alloc) Max_Size,
       decode( kbytes_max,
               0, 0,
               (kbytes_alloc/kbytes_max)*100) pct_max_used
from ( select sum(bytes)/1024 Kbytes_free,
              max(bytes)/1024 largest,
              tablespace_name
       from  sys.dba_free_space
       group by tablespace_name ) a,
     ( select sum(bytes)/1024 Kbytes_alloc,
              sum(maxbytes)/1024 Kbytes_max,
              tablespace_name
       from sys.dba_data_files
       group by tablespace_name )b
where a.tablespace_name (+) = b.tablespace_name
order by 1
/

That gets the free space from dba_free_space summed up by tablespace and the
allocated space from dba_data_files summed up by tablespace -- bringing the two
results to the same level of detail and THEN joins.

In a nutshell -- that is what inline views are great for.  Prior to the
introduction of inline views in Oracle7.1 -- we needed to create "real" views in
support of a query like this.

Now, as for how to run a query like you want -- to query DBA_TABLES and get a
row count for each table -- we can use dynamic sql for that.  We need to since
the count will be the count of rows in a table we do not know the name of until
runtime!  We can code this in Oracle8i release 1 and up:

ops$tkyte@ORA8I.WORLD> create or replace
  2  function get_rows( p_tname in varchar2 ) return number
  3  as
  4      l_columnValue    number default NULL;
  5  begin
  6      execute immediate
  7         'select count(*)
  8            from ' || p_tname INTO l_columnValue;
  9
 10      return l_columnValue;
 11  end;
 12  /

Function created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select user, table_name,
  2             get_rows( user||'.'||table_name) cnt
  3    from user_tables
  4  /

USER      TABLE_NAME             CNT
--------- --------------- ----------
OPS$TKYTE A                        0
OPS$TKYTE B                        0
OPS$TKYTE C1                       0
OPS$TKYTE C2                       0
OPS$TKYTE C3                       0
OPS$TKYTE C4                       0
OPS$TKYTE C5                       0
OPS$TKYTE CHAINED_ROWS             0
OPS$TKYTE EMP                      2
OPS$TKYTE IMAGE_LOAD               1
OPS$TKYTE LEAVE_DTL                6
OPS$TKYTE LST_INFO                 9
OPS$TKYTE MERGE                    3
OPS$TKYTE MV_1                     3
OPS$TKYTE P                        0
OPS$TKYTE RECIPE                   0
OPS$TKYTE RECIPEVERSION            0
OPS$TKYTE T                        1
OPS$TKYTE T1                       2
OPS$TKYTE T2                       1
OPS$TKYTE TEMP                     0
OPS$TKYTE TEMP1                    4
OPS$TKYTE TEMP2                   10
OPS$TKYTE TEST_BLOB                1
OPS$TKYTE TEST_DATE_TBL            2
OPS$TKYTE TMP_ROW_COUNT           27
OPS$TKYTE TREC_RPT_VARS            5

27 rows selected.

Be prepared to have that take a while to execute of course!

follow up to comment one below:

My use of USER_TABLES was intentional -- DBA_TABLES has quite a few tables in
it!  I just wanted to demonstrate the concept.  Against DBA_TABLES this of
course would be:

select owner, table_name, get_rows( owner || '.' || table_name )
from dba_tables;
Review & Followup
Rating: 4
Inline views, aka table rows count from dba_tables May 20, 2001
Reviewer: DC Law from Seattle WA USA
Tom,

Your response was, as usual, spot on.
With one little hitch:  from user_tables vs dba_tables.

Rating: 5
My brain being a sieve, yes March 23, 2002
Reviewer: Harrison Picot from Haymarket, VA USA "Arkansas without the glitter"
Hi Tom:

Like they say about chopping wood (warms you twice) this example (located in the
archives) saved me the embarrasement of having to ask you to repeat an answer
you provided a year or so ago (and I both lost and forgot) about inline views,
so the archives saved my asking, and your thinking, "Is this guy's brain a
sieve?".  Well, yes, but I'm saving room for Heather Locklear - also a
worthwhile endeavor. Despite my lack of an orderly archive, yours provided
another great answer.

Cheers,

Harrison

Rating: 5
Very helpful March 17, 2003
Reviewer: Hai from TX, USA
Tom,
Thanks for keeping this in the archives.  It saved me a lot of time try to
develop the same function.  I was right to go here to look for hints and
advices.
As usual, your web site is very helpful.

Rating: 5
inline or seperate !! March 28, 2003
Reviewer: A reader
hi tom,

  i have a table

emp
-------
emp_no number,
emp_name varchar2(20),
emp_inst_date date,
emp_is_current char(1)

dept
-------------
dept_no   number,
dept_name varchar2(20),
dept_inst_date date,
dept_is_courrent char(1)

we have our own audit trails.

so for the normal application we have views

emp_v
select * from emp where emp_is_current is not null;

dept_v
select * from dept where dept_is_current is not null;

so when I join the result set
should I go by

select e.*,d.* from emp_v e, dept_v d
where e.emp_dept_code = d.dept_code;

or

should I go like

 select e.* , d.*
from emp,
(select * from dept where dept_is_current is not null) d
where e.emp_dept_code = d.dept_code
and   e.emp_is_current is not null;

why ?

Followup:
they are all the same -- will result in the same plans.

use whichever is easier to type.

Rating: 5
or like March 28, 2003
Reviewer: A reader
select e.* , d.*
from emp e,
(select * from dept where dept_is_current is not null) d
where e.emp_dept_code = d.dept_code (+)
and   e.emp_is_current is not null;

Rating: 5
which is faster ? March 28, 2003
Reviewer: A reader
tom which of them is faster ?

Followup:
i guess the one that includes the least number of characters since you can type
it in faster -- but only if you include the time to type in the query -- since
as I said "same plan".  "same plan" means "they are the same"

Rating: 5
what about that outer join ? March 28, 2003
Reviewer: A reader
sorry for posting that query later.

but this outer join query will get more rows in some cases
does this make sance or is it impractical ?

Followup:
i didn't comment on the outer join -- that one is different, I was only talking
about the ones that were together.

the outer join is just a totally different result set.  cannot be compared for
performance -- they are apples and oranges.

Rating: 4
outer join March 31, 2003
Reviewer: A reader
I have to create  a view with this query to provide some data in the listbox or
some thing. I do need a join there.

 I am not 100% sure that
1.) should I go with the outer join query,
   ( which will return all the valid rows
     and dept where ever found.)

 or

2.) simple join (restrict emp rows where dept not found.)
     and create a view

NOte I have fk and not null constraints ( in trigger insert/update) to enforce
this fk/not null relationship.

Followup:
well, you know what -- I cannot answer that.

Only you -- with your knowledge of which result set is correct -- can.

Do you need to show all depts?  if so, outer join away.

Should you only show depts with emps?  If so, outer join would be wrong

so, back to you -- which is the right answer.

Rating: 5
thanks, March 31, 2003
Reviewer: A reader
I have to show all emp and dept names are secondary info..
  empcd and empname is the objective or the query
so I will go by outer joins

 ( I have trigger enforced constarint on emp table that
when ever we insert in emp dept should not be null and
deptcd  should be from department table.
)

 so always outer join and simple join, both,will return the same result.
I just still want to be sure that I get the full record set for emp..

Tanks again..

Followup:
then you do not need an outer join -- I pray you are not using a trigger to do
something as simple as:

..
deptcd REFERENCES dept(deptno) not null,
....

if you wrote code for that - erase it and just use the constraint.  faster,
safer, correct.

Rating: 5
March 31, 2003
Reviewer: A reader
I had to write the code because
the pk in dept is PK(deptcd,dept_entry_ts)

Followup:
then you have a problem with your data model.

It is pretty nigh impossible to correctly implement foreign keys with triggers
due to the way concurrency controls work.

You should fix the model - you are missing an object in your schema.  I'm
assuming that dept_entry_ts is a timestamp, and you have "versions" of
departments.  You are missing the top level schema object that you have
"versions of".  it would have the primary key on deptcd and that is what the
child should point to.

Rating: 5
March 31, 2003
Reviewer: A reader
what else I can do I can not reference a part of a primary key
can I ?

Rating: 5
what are you saying ? March 31, 2003
Reviewer: A reader
{
You are missing the top level schema object that you have
"versions of".  it would have the primary key on deptcd and that is what the..
}

oracle 817

table
emp

empcd           number
emp_entry_date  date
empname         varchar
emp_entry_empid  number
emp_deptcd      number

dept
deptcd          number
dept_entry_dt   date
deptname        varchar
dept_entry_empid number..

now what is that I am missing.

in this case If I want to
refer : deptcd
from  : emp table for (emp_deptcd)

and
refer : empcd
from  : dept table for ( dept_entry_empcd)

we have not introduce the surrogate keys !!

what is  (better ? )way ?

Followup:
what are the primary keys of anything there and WHY -- what is the meaning of
dept_entry_dt?

Rating: 5
March 31, 2003
Reviewer: A reader
all table contains this two audit trail fields

<tablename>_entry_dt     (is timestemp)
<tablename>_entry_empcd  (user who enterd/updated it)

primary key for emp table consists of
(empcd,emp_entry_dt)

  - also there is recursive relationship
    emp_entry_empcd ( references it self)
    (for the user who entered the record).

and

primary key for dept table consists of
(deptcd,dept_entry_dt)

-- also dept_entry_empcd referes to emp.empcd

Followup:
like I said -- you are missing the "master" or parent table to this VERSIONED
table.

you are missing a table in your model, the parent table of this versioned one..
IT would have a primary key on deptcd and other attributes that do not change
regardless of version.  It may even be a one column table.

Rating: 5
March 31, 2003
Reviewer: A reader
we have only one table and audit trail is enabled on it.
based on max(entry_date) we select the "eligible record".
( if there is something  i am missing, can you please
illustrate with an exaple. )

I think what you are refering as the master table is
another table which contains the all audit trail information ? and current table
should contain only valid records ?

 please let me know better design

Followup:
No, I'm saying if you have a fkey and no unique key -- you've made a mistake

create table dept_master( deptno primary key );

create table dept_version( deptno references dept_master, date_column, primary
key(deptno,date_column) );

create table emp( ...., deptno references dept_master NOT NULL );

I don't know how to say "you are missing a table" any other way -- sorry.

Rating: 5
ok March 31, 2003
Reviewer: A reader
so you are saying I  have to create this two tables for dept if  I have audit
trail enable on it.
and constraints are not as as efficiant ( or wrong use of
constraint) in this case.

 I have like 40/50 tables in this way because of the audit trail, where primary
key consists of two columns
(code and timestemp) and in our operational schema we are enforcing constraints
via insert/update row level triggers.

 you are suggesting that I should create 40/50 more master tables and take those
triggers/packages(to avoid mutating situations) based on the information I
provided you.

Thanks tom, I will try to take it to upper mgmt. cause I need approval and time
to do it, test it and move it to prod. and ofcourse I have to create a demo to
make my case there :)

Thanks,

Followup:
telling you -- you cannot reliably enforce contraints via triggers.

Yes, I'm suggesting that instead of gobs of procedural code (which doesn't work
100%), that runs for each and every insert/update delete -- you simply do it the
"relational way".

Or, if you are on 9i, consider this
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4529781014729
automated auditing that lets you have primary and foreign keys with no code.

Rating: 3
9i March 31, 2003
Reviewer: A reader
good evening !! Thanks for being there !! it's 6:06 pm already..

tom, 9iR2 is far for me. I mean my company is on 817 (stable ) deployment --
nobody wants to change it
( and 817 is very cool too).

there are 47 tables, at this point working fine
( passes all qa tests and all set.)
 when things are running fine nobody want to change it/touch it and update big
deal .. even if I push for 9i it will be only me in the IT dept !!

 also what about surrogate key ? what if I intoduce them in this case ? I think
sooner or later I do have to bring them in the picture because of the
complexity.

 what me be the implecation of that ?

so I go like ...

emp

emp_skey   number
empcd      number
emp_entry_dt date default sysdate,
emp_name  varchar2
emp_dept_skey number references dept(dept_skey) not null,
emp_entry_emp_skey number references emp(emp_skey) not null

dept

dept_skey number
dept_cd number,
dept_entry_dt date,
dept_entry_emp_skey number reference emp(emp_skey) not null,
dept_name

what implecations you see in that case ?

Followup:
I don't see how a surrogate key would help you -- you have multiple versions of
a record.  you have a composite key.  using a surrogate would not change
anything. it would not be any different than EMP have a composite fkey --
meaning you have to update EMP after each insert into DEPT -- a total change and
a total mess.

Rating: 5
thanks tom, March 31, 2003
Reviewer: A reader
I will try to create new master tables !!

Thanks !!

Rating: 5
demo time !! April 02, 2003
Reviewer: A reader
hi tom,

now in the same schema, I have two tables
say country and currency.
I want to create a table countrytocurrency
where audit trial should be enabled.

what is your design suggestion would be.

1.) the objective and the contax of the application
    will not influnce the design as it is solely going to be
    used for holding the mapping between country and
    currency
  eg. ( england can have pound and Euro)
    same for any europian country
        us only USD
       australia asd
  etc..

2.) I have country table
   country(country_code,country_name,timestemp)
  with pk(country_code)
3.) I have currency table
   currency(curr_code,curr_name,timestemp)
  with pk(curr_code)
4.) my new contrytocurrency table is
    depandent table (primary key cosists of fk s)
contrytocurrency(country_code,curr_code,timestamp,entry_usr,flag)
 pk(country_code (fk:country)),curr_code(fk:currency))

q)
what would be the "master" and child table in this case ?

Followup:
wow, did we get off track on this thread or what...

question is too vague to be answerable. "audit trail enabled".
"countryToCurrency" but no way to join country to currency...

no understanding of the columns involved.

tell you what, when I'm accepting questions -- fill in the blanks and make it a
question.

in short, a countrytocurrency association object would have just the primary key
of country and the primary key of currency -- period.  auditing -- audit on
countrytocurrency by select, insert,update, delete whatever you need.

April 03, 2003 Khan -- Thanks for the question regarding "NOT NULL constraints.", version 9.2.0.2
You Asked
Hi Tom,

Can you give us some examples of the benefits of using NOT NULL constraints on
columns? Thanks.

Kashif
and we said...
funny -- wonder why there always must be "benefits".  It seems obvious that if
you have a business rule that says "this column MUST not be null", a not null
constraint would be called for..

Well, benefits --

o it adds semantic meaning to your data model.  You KNOW that the column will
always contain a value.

o it removes ambiguity.  "find me all of the people who are not clerks".  Seems
obvious:

  select * from emp where job <> 'CLERK';

well, thats wrong

  select * from emp where job <> 'CLERK' or job is NULL;

is correct.  Many query tools will either
   a) get it wrong (wrong answer, that is a negative)
   b) get it right -- but therefore get it slower in many cases.

o it allows for access paths that did not otherwise exist.  Consider this simple
10,000 row example:

ops$tkyte@ORA920> create table t1 as select * from all_objects where rownum <=
10000;

Table created.

ops$tkyte@ORA920> create table t2 as select * from all_objects where rownum <=
9950;

Table created.

ops$tkyte@ORA920> create index t2_idx on t2(object_id);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table t2 modify object_id null;

Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table t1 compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table t2 compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select count(*)
  2    from t1  nullable
  3   where object_id not in ( select object_id from t2 );

  COUNT(*)
----------
        50

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table t2 modify object_id not null;

Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select count(*)
  2    from t1  notnull
  3   where object_id not in ( select object_id from t2 );

  COUNT(*)
----------
        50

tkprof says:

select count(*)
  from t1 nullable
 where object_id not in ( select object_id from t2 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.09          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2     16.67      17.68          0     735876          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4     16.68      17.78          0     735876          0           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
     50   FILTER
  10000    TABLE ACCESS FULL T1
   9950    TABLE ACCESS FULL T2
--------------------------------------------------------------------------------
select count(*)
  from t1 notnull
 where object_id not in ( select object_id from t2 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.03          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.02       0.06          0        161          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.04       0.09          0        161          0           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
     50   HASH JOIN ANTI
  10000    TABLE ACCESS FULL OBJ#(45593)
   9950    INDEX FULL SCAN OBJ#(45595) (object id 45595)

ahh, the difference a constraint makes!

In short -- the more information you give the database, the better the quality
of the plans.  Not null impacts the optimizer, query rewrite, even the queries
you must type in to get the correct answer.

Every column that is NOT NULLABLE -- should be marked as such.
Review & Followup
Rating: 5
Making 'NOT NULL' as the default March 31, 2003
Reviewer: Logan Palanisamy from Santa Clara, CA USA
Tom,

You have demonstrated very well the usefulness of the NOT NULL constraint.
Despite all its benefits, the default is still NULL when we create a table.
Wouldn't it be nice if the default is NOT NULL. One should intentionally set it
to NULL if there is a real need.

May be, Oracle should come up with a new init.ora which sets the database
default NULL/NOT NULL behavior for all newly created columns. The default value
of this new init.ora parameter can be set to NULL to simulate the current
behavior. Once you set this parameter to NOT NULL, all new columns created hence
forth will be NOT NULL by default which one can override with NULL where it is
needed.

Do you think of any drawbacks to this approach.

Followup:
well, it would be nice but a disaster.

far far far too much legacy code relies on the other way.  almost NOTHING would
work - even with that switch.  virtually every application written would "break"

Rating: 4
April 01, 2003
Reviewer: Dave from Colorado
Perhaps another disadvantage of automatically constraining new columns to not
null would be that it would encourage the idle among us towards not naming their
constraints. If there's one thing i hate, it's a system-named constraint.

create table my_table
(
my_column1 number constraint nn1my_table not null,
my_column2 number constraint nn2my_table not null,
my_column3 number constraint nn2my_table not null
)

vs

create table my_table
(
my_column1 number not null,
my_column2 number not null,
my_column3 number not null
)

It's not much to ask, people.

Rating: 5
April 01, 2003
Reviewer: Michael from CA USA
What is the big advantage for naming NN constraint versus not?

Followup:
clarity,
meaning,
documentation,
readability in the data dictionary,
more meaningful information from error messages...

Not only that -- but I've seen massive issues with unnamed constraints with
export import.

Here is the scenario:

o you export from db1
o you import into db2
o once a week you truncate and import into db2

over time, you notice that db2 is dog slow -- whats up?  well, someone had a
nasty un-named constraint in that and now you have it 500 times.

ops$tkyte@ORA920> create table t ( x int check ( x > 0 ) );

Table created.

ops$tkyte@ORA920> select count(*) from user_constraints where table_name = 'T';

  COUNT(*)
----------
         1

ops$tkyte@ORA920> !exp userid=/ tables=t

Export: Release 9.2.0.3.0 - Production on Tue Apr 1 19:32:23 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to: Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production
Export done in WE8ISO8859P1 character set and AL16UTF16 NCHAR character set

About to export specified tables via Conventional Path ...
. . exporting table                              T          0 rows exported
Export terminated successfully without warnings.

ops$tkyte@ORA920> !imp userid=/ full=y ignore=y

Import: Release 9.2.0.3.0 - Production on Tue Apr 1 19:32:32 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to: Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production

Export file created by EXPORT:V09.02.00 via conventional path
import done in WE8ISO8859P1 character set and AL16UTF16 NCHAR character set
. importing OPS$TKYTE's objects into OPS$TKYTE
. . importing table                            "T"          0 rows imported
Import terminated successfully without warnings.

ops$tkyte@ORA920> select count(*) from user_constraints where table_name = 'T';

  COUNT(*)
----------
         2

ops$tkyte@ORA920>

Every time I import -- i get that constraint over and over (we cannot tell they
are the same - -they have no name).  system gets REALLY slow.....

Name things.  You won't be sorry you did -- only sorry if you don't

Rating: 5
Excellent point!!! April 01, 2003
Reviewer: A reader

Rating: 5
How to get out of unnamed constraints trap? April 02, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom
I recently fell into the trap of unnamed constraints, lack of naming standards
and my own laziness to try and develop some. The situation is that some columns
have both a NOT NULL constraint and a check constraint which allows only 'Y' and
'N'. Now I wanted to drop only the check constraint only from columns. How do I
filter them out ? The column containing constraint text is a LONG and cannot be
used in where clause.

Thanks

Followup:
search this site for longsubstr and read all of the hits.

Rating: 5
Not nulls. April 03, 2003
Reviewer: Kashif from Houston, TX
Thanks Tom, I believe the word I had left out was 'performance' from the
question. I was looking for the performance benefits of using NOT NULL
constraints. Though I have tested a few things after your response and some of
the responses from queries using NOT NULL columns with indexes on them has gone
up, after I made changes to make them NOT NULL. Also got to use some fast full
scans as a result. Thanks as always.

Kashif

April 03, 2003 Ben Youssef -- Thanks for the question regarding "BInary operator like AND, XOR", version 8.0.5
You Asked
Hi

i have a simple question can we use the binary operator like AND
or XOR in a SQL statement. For example
"select 1 AND 1 from dual;" result = 1 or true
or
"select 1 XOR 1 from dual;"

if not, please can you tell me how can i do to have the same
result as the operator AND, XOR.

thanks in advance
and we said...
See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:375818206678
for more info on the bitand function.

ops$tkyte@ORA8I.WORLD> select bitand(1,1)+0 from dual;

BITAND(1,1)+0
-------------
            1

ops$tkyte@ORA8I.WORLD> c/,1/,2
  1* select bitand(1,2)+0 from dual
ops$tkyte@ORA8I.WORLD> /

BITAND(1,2)+0
-------------
            0

and given that bitor becomes:

  (x + y) - BitAND(x, y);

and bitXor becomes:

  (x + y) - BitAND(x, y) * 2;

(there are other ways to implement them as well I am sure)
Review & Followup
Rating: 1
March 11, 2002
Reviewer: A reader

Rating: 3
Still confused April 02, 2003
Reviewer: Paul from UK
I'm trying to get my head round this bitwise stuff.

I have this system (I inherited it, I didn't design it) where a user's
privileges are defined in a by their membership of a group.  The privileges data
was stored in an integer column in the groups table with the binary bits having
the following meaning:

Bit 1 - Query access
Bit 2 - Event creation
Bit 3 - Static data admin
Bit 4 - MI Reporting
Bit 5 - User administration

ANd the groups have the following privileges set
CLERK         - 00001 = 1
SUPERVISOR    - 00111 = 7
MANAGER       - 01001 = 9
ADMINISTRATOR - 10100 = 20

Now this was just fine and dandy when it was guaranteed that a user would only
have one group.  The routine would return the integer and the client (PHP)
program would work out the binary stuff.

The trouble is that now users can belong to more than one group.  So, for
example a user might be an administrator and a supervisor.  So, in this case I
want to do a binary or (I think, I'm not a mathematician) and return the
following:

ADMINISTRATOR - 10100 = 20
SUPERVISOR    - 00111 = 7

Binary Or     - 10111 = 23

So I'm trying to get a result of 23 from values of 20 and 7, and it's "doing my
head in" as we say in England.

Can you help at all, please?

Thanks

Paul

Followup:
big_table@ORA920> select (x + y) - BitAND(x, y)  bit_or
  2    from ( select 20 x, 7 y from dual );

    BIT_OR
----------
        23

1 row selected.

x+y-bitand(x,y) does a bitor

BitOR(x, y) - BitAND(x, y);

does a bitxor

Rating: 4
That's solved the logic but ... April 03, 2003
Reviewer: Paul from UK
My problem is that I need to do a binary OR on values in a column. Now, if I
know that there are exactly 2 values, I can get what I want as follows:

SQL> create table t (x number);

Table created.

SQL> insert into t values (20);

1 row created.

SQL> insert into t values (7);

1 row created.

SQL> select sum(x) - BitAnd(max(x),min(x)) from t;

SUM(X)-BITAND(MAX(X),MIN(X))
----------------------------
                          23

But what do I do if I've got more than (or less than) 2 values?

TIA

Paul

Followup:
what database version are you using.

Rating: 3
Version April 03, 2003
Reviewer: Paul
I'm on 8.1.7

(8.1.7.4, to be precise, my favourite ever version [so far])

Paul

Followup:
bummer, in 9i we could do a user defined aggregate to to a bitor.

In 8i, we have to "fake it"

ops$tkyte@ORA817DEV> create or replace function set_bitor( p_key in number )
return number
  2  as
  3          l_bitor number := 0;
  4  begin
  5          for x in ( select sal from emp where deptno = p_key )
  6          loop
  7                  l_bitor := (l_bitor + x.sal) - bitand( l_bitor, x.sal );
  8          end loop;
  9          return l_bitor;
 10  end;
 11  /

Function created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select set_bitor( 10 ) from dual;

SET_BITOR(10)
-------------
         8094

ops$tkyte@ORA817DEV> select lpad( to_bin( sal), 32, '0' ) from emp where deptno
= 10;

LPAD(TO_BIN(SAL),32,'0')
--------------------------------
00000000000000000000100110010010
00000000000000000001001110001000
00000000000000000000010100010100

ops$tkyte@ORA817DEV> select to_dec( '1111110011110', 2 ) from dual;

TO_DEC('1111110011110',2)
-------------------------
                     8094

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select deptno, set_bitor( deptno )
  2    from ( select distinct deptno from emp )
  3  /

    DEPTNO SET_BITOR(DEPTNO)
---------- -----------------
        10              8094
        20              4095
        30              4094

ops$tkyte@ORA817DEV>

Rating: 5
Brilliant!!! April 03, 2003
Reviewer: Paul from UK
I'll leave my question on user defined aggregates till we go to 9i! :-)

Many thanks

Paul

April 03, 2003 -- Thanks for the question regarding "breaking the 4000-byte barrier", version 9.2.0
You Asked
Hi Tom,

Problem Description:
  The flat script (sql*plus unloader) does not handle row sizes
  over 4000 bytes.

  While it is cool that there are pl/sql and pro*c solutions to
  this issue, we prefer the sql*plus approach to unloading, due to
  the simplicity and brevity of the flat script.

  So...how to break the 4000 byte barrier using sql*plus...

Test Table:
  SQL> create table t (c1 int, c2 varchar2(4000), c3 varchar2(4000));
  SQL> insert into t values (1, rpad('2', 4000, '2'), rpad('3', 4000, '3'));
  SQL> commit;

Proposed Workaround:
  1.  create a dummy table with one row and one column.  The column
      is of datatype CLOB, and must have a value which is one byte
      long.
    SQL> create table d (x clob);
    SQL> insert into d values ('X');
    SQL> commit;

  2. When flat selects the columns from t, it prepends the clob from
     table d to the column list, and uses substr to skip over
     the 1-byte value from d:
    SQL> select substr(d.x || t.c1 || ',' || t.c2 || ',' || t.c3, 2) from d, t;

  Apparently, the leading clob causes oracle to treat
  "d.x || t.c1 || ',' || t.c2 || ',' || t.c3"
  as a clob, and thus allows it to be greater than 4000 bytes.

Question:
  Are there any hidden gotchas with this method of concatenation?
  Performance implications due to the use of the CLOB?
  Or is this a reasonable way to get around the 4000 byte limit?

Thanks!!

btw, Enjoying the revamped web site, especially the "previous/next"
scrollability, and search on the main page.
and we said...
neat -- no answers for you -- but neat.  didn't think of that one myself.

I'll just have to say "give it a go" and let us know.  I'll publish this so
others can see the technique.  I'll be applying this to some other problems I
can see for sure.
Review & Followup
Rating: 4
sql*unloader? March 24, 2003
Reviewer: umesh from bangalore,india
tom,

what is this sql*unloader and why do i get this message while trying out the
above example

  1  insert into d
  2* select substr(d.x || t.c1 || ',' || t.c2 || ',' || t.c3, 2) from d, t
scott>/
select substr(d.x || t.c1 || ',' || t.c2 || ',' || t.c3, 2) from d, t
              *
ERROR at line 2:
ORA-00932: inconsistent datatypes

Followup:
see
http://asktom.oracle.com/~tkyte/flat/index.html
for the unloader and for the ora 932 -- you must have 8i or before.  when you
couldn't do that to a lob.

You need 9i to use a lob like that.

Rating: 5
Cool technique March 25, 2003
Reviewer: Georg from Germany
Ask Tom pages are always good to learn something new! Great idea!

Rating: 5
A further refinement March 25, 2003
Reviewer: A reader from Bay Area, CA USA
From my initial testing, it appears that the substr is not
necessary if the clob is initialized to "empty" rather than
a 1-byte string:

SQL> create table d (x clob);
SQL> insert into d values (EMPTY_CLOB());
SQL> commit;

SQL> select d.x || t.c1 || ',' || t.c2 || ',' || t.c3 from d, t;

Rating: 5
Cool! Even Better! March 25, 2003
Reviewer: A reader

Rating: 5
ORA-01652: unable to extend temp segment March 26, 2003
Reviewer: A reader from Bay Area, CA USA
Additional testing of this statement...

  SQL> select d.x || t.c1 || ',' || t.c2 || ',' || t.c3 from d, t;

...with table "t" populated with 20,000 rows is resulting in error:
  ORA-01652: unable to extend temp segment by 128 in tablespace TEMP

As far as I can tell, what's happening is:
  - Each concatenation is creating a new Temporary LOB in the temporary
tablespace.
  - The Temporary LOB's are not being released until the session is ended.

So, it's not too long before the select statement exceeds the space available in
the temporary tablespace.

The following are quotes from the LOB and Supplied Packages manuals:

Oracle9i Application Developer's Guide - Large Objects (LOBs)
  "System/Database Management: After this newly provided enhanced SQL semantics
functionality is used in your applications,
   there will be many more temporary LOBs created silently in SQL and PL/SQL
than before. Ensure that temporary tablespace
   for storing these temporary LOBs is large enough for your applications!"

Oracle9i Supplied PL/SQL Packages and Types Reference:DBMS_LOB
  "By default, all temporary LOBs are deleted at the end of the session in which
they were created."

Rating: 3
Unable to insert more than 4000k into clob column April 03, 2003
Reviewer: Balaji from India
Hi Tom
How do I insert a string value of more than 4000 k into a clob column.
I have a resume text that is 83 K.
Suppose I have a table emp
(emp_id number(10) , name varchar2(30),resume clob)
when i say
str1 := '<string data here>' ( which is more than 4000k length)
and then say
insert into emp values ( 1001,'balaji' , str1) then
i get an error string value too long.

How do i solve this problem??

Followup:
sigh, never ever a version -- oh well.  Not even a true error message or
environment.  I'll make lots of guesses then.

guess -- old version, pre817.
guess -- plsql
guess -- actual error:

ops$tkyte@ORA815> declare
  2      l_str varchar2(30000) := rpad('*',29999,'*') || 'x';
  3  begin
  4      insert into t values ( 1, l_str );
  5  end;
  6  /
declare
*
ERROR at line 1:
ORA-01461: can bind a LONG value only for insert into a LONG column
ORA-06512: at line 4

below is a 817 example that shows that this works upto 32k as a single
variable and then shows how to chunk this up in any release (255 was an
ARBITRARY NUMBER for the chunk, you can of course go bigger)

ops$tkyte@ORA817DEV> create table t ( x int , y clob );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> declare
  2      l_str varchar2(30000) := rpad('*',29999,'*') || 'x';
  3  begin
  4      insert into t values ( 1, l_str );
  5  end;
  6  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select x, dbms_lob.getlength(y), dbms_lob.substr(y,1,30000)
last from t;

         X DBMS_LOB.GETLENGTH(Y) LAST
---------- --------------------- ----------
         1                 30000 x

ops$tkyte@ORA817DEV> declare
  2      l_str varchar2(30000) := rpad('*',29999,'*') || 'x';
  3      l_clob clob;
  4      l_chunk number := 255;
  5      l_piece long;
  6  begin
  7      insert into t values ( 2, empty_clob() ) returning y into l_clob;
  8
  9      for i in 0 .. trunc(length(l_str)/l_chunk)
 10      loop
 11          l_piece := substr( l_str, 1 + i*l_chunk, l_chunk );
 12
 13          if ( l_piece is not null )
 14          then
 15              dbms_lob.writeAppend
 16              ( l_clob, length(l_piece), l_piece );
 17          end if;
 18      end loop;
 19  end;
 20  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select x, dbms_lob.getlength(y), dbms_lob.substr(y,1,30000)
last from t;

         X DBMS_LOB.GETLENGTH(Y) LAST
---------- --------------------- ----------
         1                 30000 x
         2                 30000 x

April 03, 2003 Nimesh -- Thanks for the question regarding "Sizing of SGA", version 9i
You Asked
Hi Tom!!
whats the method to estimate SGA size.
Considering 70GB DB OLTP on sun solaris enivronment whats should be the size of
SGA. Normally we go from Development / testing environment we get a fair
estimate ...else  start from 1/50th the size of DB aand then go with trial and
error till the stats approves it..
For a consultant to work in limited time Is there a precise method ...

Thanks for your time
and we said...
use statspack and the buffer cache advisor report that is in there.  Since you
can dynamically resize it on line -- you are set to go.

see
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96533/memory.htm#34133
Review & Followup
Rating: 3
Sizing of the SGA in an OLTP environment April 03, 2003
Reviewer: Reader
Tom..

I am immensly impressed by your great job. i have come to you to resolve one of
the problem i faced today ...

while installing oracle today ...already there was 6 instance of orace running
in the Sun Server with OS Solaris 2.8. ..the 7th instance which i was creating
could not connect to oracle. error meesage "Oracle not available " i thought it
was memory problem and so i stopped 2 of the running instance and proceeded ...i
got it installed this time after stopping two instances.

i am worried about what will happen if i start all the 7 intances.. will it
crash ??? - Pray GOD - It should not.

each instance has the Total System Global Area  of 41812128 bytes (~ 40 mb)

I wanted to know the guidelines on sizing the SGA. I have read different
messages that offer different guidelines about the size of the SGA.One says that
the size of all SGAs on a system should be maximum of half the physical memory.
Some say a Third. I have 1 GB of physical memory.

My machine has Websphere 5.0 and Oracle 8i and there is 7 instance of Oracle
running !!. There will be only one applications running against each instance
and the one instance of websphere will be catering to all 7 applications. I want
to size it for application having only 5 user connections to the database.

My DB_BLock_Size is 8M. Currently processes parameter is 150.

shall i go about with the current sga size of 40 mb?
What guideline should I use to size the total SGA ? 500 M what?
How should I breakdown the SGA between the Variable Size, Fixed Size , Database
Buffers and Redo Log Buffers. Are there any guidelines on these sizes.

Please advice me ...

Thanks in advance,

Followup:
so, you had 5 too many instances and you added the 6th....

firstly -- having more then one instance is just a waste of resources and limits
your ability to tune anything.  It is alot like herding cats trying to get 7
instances all to play fair and nice with eachother.

second -- you ran out of system resources apparently, be it shared memory,
semaphores, something.  time to dig out the solaris admin guide, see what might
need to be set higher in /etc/system, get the SA to help you out (reboot).

1 gig of memory -- that is barely enough for one database server machine, let
alone 7!

Oh -- and it is the app server as well?!?!?!?

You've gone about this all wrong.  You want ONE database, ONE instance -- no
more then that.

Rating: 5
Sizing of SGA April 03, 2003
Reviewer: Reader
tom,
sorry for my ignorance,.. please throw some light on my questions below ...

i want actually 6 database( 6 instances). so you mean to say that we should not
have many instances ?.. you are telling to  have ONE database, ONE instance --
no more then that. RIGHT?

else ..if i can have more than 1 instances ...for 1 Gb ram  how many instances
can i have ...? (please keep in mind that i will also have an app server running
in the same machine ). its just a development  server - used mainly for testing,
not much of load.

please let me know what parameter in etc/system i shud look for to change the
value to accomadate my instances.

Thanks for your patience in hearing me....

Followup:
You don't *want* 6 databases -- trust me.

You WANT one database.   you've gotten yourself into a bad situation where for
some reason you are forcing yourself to use 6.

for 1gig, i wouldn't go above 1.
for 10gig, i wouldn't go above 1.

the fewer the better -- the easier to manage, maintain, upgrade, and so on.

read
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:454420165038

Rating: 5
Sizing of SGA April 03, 2003
Reviewer: A reader
Thanks tom for your valuable input. The link was good enough,....

yes as you say i am forced to use the single server for ...

I have a thought, let me know if you appreciate that .
i have planned to run one instance at a time ..while stopping all the other.
when the testing is done on one instance i have planned to start the next,
stopping the first one.

Thanks a lot

Followup:
why have more then one instance then?

Rating: 4
Sizing of SGA April 03, 2003
Reviewer: reader
for i need to have different database for different applications.  in all we
have 6 different applications that need to be run in solaris environment.

does this make sense? or you have anyother to say.

thank you tom

Followup:
sounds like you need to have 6 schemas, one each for your 6 applications and a
single database and nothing you say will convince me elsewise.

That way, you don't have to bounce the instances to go from app1 to app2 -- they
can all run happily at the same time.

Rating: 4
Sizing of SGA April 03, 2003
Reviewer: reader
tom,
i am confused more now .. i mean i didnt get you..

i created database 6 times using dbca. so it means 6 database right?...schema ==
user right?

and i have one user for each database ...on the schema/user created  we apply
the framework ...

now tell me whether i have
6 database/6 instance or 1 database/6 instance.

i think its 6 database/6 instance and still can i run all the instances ..pls
clarify

sorry tom ..for bugging you

Followup:
what you have:

6 databases

what you want

1 database.

You *want* a single database and a single instance.  This single database will
have 6 schemas (users), one each for each of the applications.

April 03, 2003 Reji -- Thanks for the question regarding "Build a hierarchy", version 8.1.6
You Asked
Tom:
    I have a table:

    A(
         id             NUMBER,
         parent_id      NUMBER
     );

     sample data:
     (101, 100)
     (102, 101)
     (103, 102)
     (104, 103)

Given this data, How do I create a string which will
reflect the hierarchy like "100.101.102.103.104"

Reji
and we said...
If you know the MAX dept and will just be getting one parent and
all of its children, the following will do it:

ops$tkyte@DEV816> create table
  2      A(
  3           id             NUMBER,
  4           parent_id      NUMBER
  5       );

Table created.

ops$tkyte@DEV816>
ops$tkyte@DEV816> insert into a values(101, 100);

1 row created.

ops$tkyte@DEV816> insert into a values(102, 101);

1 row created.

ops$tkyte@DEV816> insert into a values(103, 102);

1 row created.

ops$tkyte@DEV816> insert into a values(104, 103);

1 row created.

ops$tkyte@DEV816>
ops$tkyte@DEV816> variable bv number
ops$tkyte@DEV816> exec :bv := 100

PL/SQL procedure successfully completed.

ops$tkyte@DEV816>
ops$tkyte@DEV816> select rtrim(
  2             :bv || '.' ||
  3             max( decode( rn, 1, id, null ) ) || '.' ||
  4         max( decode( rn, 2, id, null ) ) ||'.' ||
  5         max( decode( rn, 3, id, null ) ) ||'.' ||
  6         max( decode( rn, 4, id, null ) ) ||'.' ||
  7         max( decode( rn, 5, id, null ) ) ||'.' ||
  8         max( decode( rn, 6, id, null ) ) ||'.' ||
  9         max( decode( rn, 7, id, null ) ) ||'.' ||
 10         max( decode( rn, 8, id, null ) ), '.' )
 11  from (
 12  select id, rownum rn
 13    from a
 14   start with parent_id = :bv
 15  connect by prior id = parent_id
 16  )
 17  /

RTRIM(:BV||'.'
-----------------------------------------------------------------
-----------------------------------
100.101.102.103.104

If not, explain in more details -- there are other possible
methods but I would need to know what you are REALLY doing.
Review & Followup
Rating: 3
More of the same April 03, 2003
Reviewer: Steve from UL
From the same table above, if the data looks like:

ID        Parent_id
147        145
145        143
143        NULL
111        207
207        NULL

How could I get output like

ID        parent_id
147        143
145        143
111        207

In other words, I get each child with it's very top parent.  And if it helps I'm
using 7.3.4!

Thanks

Followup:
Using 7.3.4 does not help -- in fact, it hurts -- lots.

don't see anyway short of a function written in plsql and called from sql to do
this.  we didn't have scalar subqueries, no sys_connect_by_path, no analytics --
nothing to facilitate this.

Rating: 4
April 03, 2003
Reviewer: Dave from Colorado
Maybe a whole bunch of joins is required here ...

select a.id,
Nvl(g.id,Nvl(f.id,Nvl(e.id,Nvl(d.id,Nvl(c.id,Nvl(b.id)))))) highest_parent
from a,a b,a c,a d,a e,a f,a g
where
a.parent_id = b.id(+) and
b.parent_id = c.id(+) and
c.parent_id = d.id(+) and
d.parent_id = e.id(+) and
e.parent_id = f.id(+) and
f.parent_id = g.id(+) and
a.parent_id is not null;

Haven't tested it though.

Followup:
only if you know the max depth of the hierarchy....

Rating: 4
April 04, 2003
Reviewer: Marcel from Netherlands
This should do the job. Beware though, it's not exactly performance tuned and it
assumes that there actually always is an ID without a parent :) Just gives an
idea of a possible principle that might solve the problem.

declare
cursor c_a is
select id,parent_id
from   a
order by id;
cursor c_b (p_in number) is
select id,parent_id
from   a
where  id=p_in;
v_parent number;
v_high_parent number;
begin
  for r_a in c_a
  loop
    if r_a.parent_id is not null then
      v_parent:=r_a.parent_id;
      v_high_parent:=0;
      while v_high_parent=0
      loop
        for r_b in c_b(v_parent)
        loop
          if r_b.parent_id is null then
            v_high_parent:=r_b.id;
          else
            v_parent:=r_b.parent_id;
          end if;
        end loop;
      end loop;
      dbms_output.put_line(r_a.id||' : '||v_high_parent);
    end if;
  end loop;
end;
/

April 03, 2003 V sekhar -- Thanks for the question regarding "Error in creating .dat with SQL", version 8.0.6.0.0
You Asked
I'm sure, the data will NOT have any PIPE characters. Please
show the way to convert 13d into Pipe and back while loading.
Is there any other way to the same, since this method is a data
dependent.

Thanks

-----------------------
Hi Tom,

Additional information is here:

The data contains carriage return ascci value 13.

eg.
DIRSV       172 AMER  RESIN SLK CONV        4380550 REV 1
      1     18129       172 NORYL 14-AUG-00 P016878  NN1723
     0   UTIL-C
DIRSV       172 AMER  RESIN SLK CONV        4380526 ORIG
      1     18129       172 NORYL 11-AUG-00 P016878  NN1723
     0   UTIL-C

here after values NN1723, carriage return eists, which I
observed in the SQL plus. In this case what shall I do, while
creting the .dat file, or loading through SQL*Loader.

the lines 400 is enough. the max. lenth is les than 100.

Thanks,
Sekhar

---------------------
Hi tom,

I'm creating a .dat file from sql select statement. the total
rows aroung 160K. in the .dat file, some new line character
inserted. Due to this when I load a warehouse with this .dat
file using SQL Loader, such rows are not inserted. Please give
me the suggestion to avoid this new line character in the .dat
file.

the sql code is here:

set pause off
set verify off
set feedback off
set heading off
set termout off
set tab off
set trimout on
set timing off
set pages 8000
SET LINES 400
SPOOL /admin2/g034p/prod/scripts/qallocyear.dat
SELECT
 col1||'~'||
  COL2  ||'~'||
  COL3  ||'~'||
  COL4 ||'~'||
  nvl(center,'N/A')  ||'~'||
  COL6 ||'~'||
  COL7     ||'~'||
  COL8    ||'~'||
  COL9      ||'~'||
  COL10      ||'~'||
  COL11      ||'~'||
  COL12      ||'~'||
  site       ||'~'||
  col13||'~'||
  col14||'~'
  from
      TAB_A;
SPOOL OFF
SET HEAD ON
SET FEEDBACK ON
SET TERMOUT ON
SET VERIFY ON

Some sample rows which have newline character:

18129~REV 1~DIRSV~9130521~NN1723
~CONV     ~N/A~2000~0~0~172~0~P016878
~14-AUG-00~SLK~NORYL~4380550~

the .log file as follows:

Record 86315: Rejected - Error on table TAB_A, column CENTER.
Field in data file exceeded maximum specified length
Record 86316: Rejected - Error on table TAB_A, column SITE.
Column not found before end of logical record (use TRAILING
NULLCOLS)
Record 86402: Rejected - Error on table TAB_A, column CENTER.
Field in data file exceeded maximum specified length

The .bad file is as follows:

14805~ORIG ~DIRSV~9093304~TS1710^M

~CONV     ~N/A~2001~0~1~0~0~P5SR032
~1999-12-21~MGT~STABL~4375698~

the .ctl file is:

LOAD DATA
      CHARACTERSET 'US7ASCII'
      INFILE 'extract.dat'
      INTO TABLE wh_tab_a
      FIELDS TERMINATED BY "~" OPTIONALLY ENCLOSED BY '"'
      (  col1   INTEGER  EXTERNAL,
         col2      CHAR ,
         col3      CHAR ,
         col4     INTEGER  EXTERNAL,
         CENTER      CHAR,
         col5      CHAR,
         col6         CHAR,
         col7        INTEGER  EXTERNAL,
         col8          DECIMAL  EXTERNAL,
         col9          DECIMAL  EXTERNAL,
         col10          DECIMAL  EXTERNAL,
         col11          DECIMAL  EXTERNAL,
         col12  CHAR,
         col13  DATE(20) "YYYY-MM-DD",
         SITE        CHAR
         )

Thanks,
Sekhar
and we said...
In Oracle8i, we can do this in another way without converting
the characters.  In Oracle8.0 and before, we have a really hard
time with this.  We could use a FIXED length record (you would
rpad() each column on the way out and make sure each line was
400 bytes long with a newline at the end) in 8.0 but its a pain
and makes for really big input files.

What I suggest is: on the way out, you want to select

.... replace(colN,chr(13),'|') ....

instead of just

.... colN ....

that'll change the 13d's into |'s on the way out. you need do
this ONLY for columns that might have newlines.

In the ctl file, you would code:

colN   char "replace(:colN,'|',chr(13))",

to change them back into carriage returns.

In Oracle8i, release 8.1 and up, you'll be able to use something
like this:

load data
infile str.dat "str X'7c0a'"
into table T
TRUNCATE
fields terminated by ',' optionally enclosed by '"'
......

the str attribute says that a record is terminated by a pipe
character (7c) followed by a newline (0a).  So, I would just
concatenate a | onto the end of the query above at the end of
the row and we'd be done -- it would load right up without
converting the in-field carriage returns.
Review & Followup
Rating: 3
How to detect and remove Carriage returns March 19, 2002
Reviewer: pawan from USA
Tom,
   In our application the users sometimes use a carriage return or Line feed in
the addres field and thus while spooling this data and then loading into a
different database the record shows up as two records and fails to load. Here is
what I mean :
1  select acctid,territoryid,addr1,addr2
2  from voyager_org
3* where acctid='-1711537024'
CCW@STG> /

ACCTID           TERRITORYID  ADDR1    ADDR2
---------------- ------------ ------------------------------
-------------------
-----------
-1711537024      0110106A     300 East
                              efferson Street

-1711537024      01103A5A     300 East
                              efferson Street

-1711537024      06101111     300 East
                              efferson Street

ANd using yr query (dump)
CCW@STG> l
1  select acctid,addr1,instr(addr1,chr(10)), dump(addr1)  dumpaddr1,addr2,inst
r(addr2,chr(10)), dump(addr2)  dumpaddr2
2  from voyager_org
3* where acctid='-1711537024'
CCW@STG> /

ACCTID           ADDR1                          INSTR(ADDR1,CHR(10))
---------------- ------------------------------ --------------------
DUMPADDR1
--------------------------------------------------------------------------------
--------------------
ADDR2                          INSTR(ADDR2,CHR(10))
------------------------------ --------------------
DUMPADDR2
--------------------------------------------------------------------------------
--------------------
-1711537024      300 East                                         10
efferson Street
Typ=1 Len=25: 51,48,48,32,69,97,115,116,32,10,101,102,102,101,114,115,111,110,32
,83,116,114,101,101,
116

NULL

-1711537024      300 East                                         10
efferson Street
Typ=1 Len=25: 51,48,48,32,69,97,115,116,32,10,101,102,102,101,114,115,111,110,32
,83,116,114,101,101,
116

NULL

-1711537024      300 East                                         10
efferson Street
Typ=1 Len=25: 51,48,48,32,69,97,115,116,32,10,101,102,102,101,114,115,111,110,32
,83,116,114,101,101,
116

NULL

----------------------
My question is how to detect and remove this hard carriage return or line feed
and correct the record before sending it to other systems. Data is available on
both Oracle 7.3.4.5 and 8.1.7.0

Thanks

Followup:
use replace( field, chr(10), '' )  to replace the linefeed with nothing (or
blank or whatever you want)

Rating: 3
Update March 19, 2002
Reviewer: pawan from USA
Tom
   I ran the folllowing query and am able to correct the problem (remove
linefeed ) But my question is how can I run a query against the database and say
I want to replace these LINEFEEDS from two fields (addr1 and addr2) if they
Exist. I can replace linefeed when I know the record that contains it but how to
find and replace.
Thanks
---
select acctid,territoryid,replace(addr1,chr(10),',')addr1 ,addr2
2  from voyager_org  where acctid='-1711537024';

ACCTID           TERRITORYID  ADDR1                          ADDR2
---------------- ------------ ------------------------------ -------------------
-----------
-1711537024      0110106A     300 East ,efferson Street
-1711537024      01103A5A     300 East ,efferson Street
-1711537024      06101111     300 East ,efferson Street

Followup:
You'll have to call replace on EVERY column that you suspect might have a
linefeed in it.

Rating: 3
Thanks March 20, 2002
Reviewer: Pawan from USA

Rating: 5
Blank Line creating problem April 03, 2003
Reviewer: Sri from NJ
Dear Tom,

  I am using Oracle 9.0.1.  Why is UTL_File creating a blank line at the end?
For example, I am using the following procedure to write to a .dat file.  The
output I am expecting is a single line with 'abcd'.  But the output I am getting
is having two lines, the first with 'abcd' and the second a blank line.  How can
I avoid this blank line from occuring?

CREATE OR REPLACE PROCEDURE UtlTest
IS
   v_thefile UTL_FILE.FILE_TYPE;
BEGIN

   v_thefile := UTL_FILE.FOPEN( 'C:\CofAData\Out', 'Test.Dat', 'w' );

   UTL_FILE.PUT( v_thefile, 'abcd<NEXTLINE>' );

   UTL_FILE.FCLOSE( v_thefile );

END UtlTest;
/

  My problem is that I am using this .dat file as an external table where I am
giving "RECORDS DELIMITED BY '<NEXTLINE>'".  Because of the blank line at the
end, I am getting the ORA-30653: reject limit reached error.  I can't give
NEWLINE as record delimiters as some of the records in my table has got
multi-line data.  What is the work around?  Please advice.

  Thank you.

Followup:
rtrim newlines and use utl_file.put_line instead.

Rating: 4
How to RTrim in external table? April 03, 2003
Reviewer: Sri from NJ
Tom,

  As I have told, I am using an external table to read from this file.  The
"select * from <external table>" itself fails because of this newline.

  Did I understand you wrongly?

Followup:
utl_file is going to end each line with a newline (until 9ir2 when you can do
binary IO).  The close is going to put a newline out there when it flushes.  I
was suggesting you strip the newline off of the line you are putting out and use
PUT_LINE itself.

April 03, 2003 Sam -- Thanks for the question regarding "better exception handling, and more effective way to truncate", version 8.1.7
You Asked
Tom

I coded the following procedure to provide the developers with the ability to
truncate 3 tables and there partitions.
1.ONly the tables
2.only table partitions

I jus want to know if there is a better way of writing the same procedure, and
especially I want your opinion on my exception handling stratergy.

procedure prd_del_allrec(v_var1 varchar2 , v_var2
varchar2 default null)
is

user_exception exception;
v_var3 varchar2(25);

begin
    if  v_var1='AAA' then
        v_var3 := 'table_first';
    elsif v_var1='BBB' then
        v_var3 := 'TABLE_SECOND';
    elsif v_var1='CCC' then
        v_var3 :='table_third';
    else
        raise user_exception;
    end if;

    if v_var2 is null
    then
        EXECUTE immediate('Truncate table'||' '||v_var3);
    else
        EXECUTE immediate('Alter table'||' '||v_var3||' '||'truncate
partition'||' '||upper(v_var2) );
    end if;
    exception
    when user_exception then
    dbms_output.put_line('You are not supposed to  truncate tables other than'
||
                        ' table_first,'||
                        ' TABLE_SECOND,'||
                        ' table_third');
    when others then
    dbms_output.put_line('error while executing the truncate');

end;
and we said...
Well, I would have them pass in the name of the table -- not "AAA" and "BBB".  I
would simply do it like this:

create procedure t( p_tname in varchar2,
                    p_partname in varchar2 default null  )
as
begin
   if ( upper(p_tname) not in ( 'TABLE_FIRST', 'TABLE_SECOND', 'TABLE_THIRD' ) )
   then
        raise_application_error( -20001, 'Table ' || p_tname || ' not valid ' ||
                                 'only table_first, ..... ' );
   end if;
   if ( p_partname is NULL )
   then
       execute immediate 'truncate table ' || p_tname;
   else
       execute immediate 'alter table ' || p_tname ||
                         ' truncate partition ' || p_partname;
   end if;
end;
/

I will comment on your exception handling -- I don't really like it.  It "hides"
the error.  I believe strongly you should CATCH an exception IF and ONLY IF you
are going to handle it (fix it, make it work).  For example:

   begin
       select x into y from t where ...;
   exception
        when no_data_found then y := default_for_y;
   end;

is a good exception block -- we catch and handle the error.  Your approach would
tend to "hide" the error.  If the developer called this from another procedure
-- they would have no way to know it failed!  If they call it from plus without
having serveroutput on -- they would have no way of knowing it had failed.  It
silently "fails".

I believe the exception should bubble up to the guy who can handle it.  In this
case -- no one, so the error appears (regardless of the client -- the error is
there).

I firmly believe a WHEN OTHERS that is not followed by a RAISE to re-raise the
exception is a bug, no questions about it - you are silently ignoring all
errors.
Review & Followup
Rating: 5
I beg to differ November 10, 2001
Reviewer: Sam
"I would have them pass in the name of the table "

The chaces are typo are very great when developers try to type the full table
names.

I differ with you. Actually only 2 to 3 developers use this procedure , and they
very well know what the abbr's stand for. They find it very convenient to enter
the abbr's, not the full name.

I accept with you totally with regard to the exception section.

Followup:
If the developers cannot write a script -- get new developers.  Geez, if your
table names are so hard, you need new tablenames.  It would seem to me that I
would WANT to know exactly what table was getting truncated.   This abbrev thing
sounds like an accident just waiting to happen (Oh -- I though AAA was
table_two, not table_one -- can the DBA do a recovery for me).  Two or three
developers today -- two or three NEW developers tomorrow.  People come, people
go -- just seems that explicit names would be "safer" -- if they don't like
typing, they can create a script or even just create a procedure "trunc_aaa" so
they can type even less.

anyway, let them do the abbrev then if you really wan.  turn the validation
into:

select decode( p_tname, 'AAA', 'FIRST_TABLE',
                        'BBB', 'SECOND_TABLE',
                        'CCC', 'THIRD_TABLE',
                         NULL ) into l_tname
  from dual;

if ( l_tname is null )
then
    raise_application_error( -20001, 'sorry '||p_tname||' is not valid...' );
end if;

Rating: 3
November 11, 2001
Reviewer: Nirmal from Qatar
It's really good, usually i too many times "Hide" the errors while doing
programs, later i'd suffered a lot to debug it.

I'm sure, now onwards i'll handle the exceptions properly.

Thanks.

Rating: 5
Helpful discussion of exception handling January 11, 2002
Reviewer: a DBA from Denver, CO

Rating: 3
question about your example March 11, 2003
Reviewer: David from AL
You said that:

   begin
       select x into y from t where ...;
   exception
        when no_data_found then y := default_for_y;
   end;

is a good exception block because it handles the error.  I agree that it handles
the error.  y gets set to a default.  But doesn't processing stop at that point?

Suppose the proc looked like this:

   begin
       select x into y from t where ...;
       insert into t1 values (y);
   exception
        when no_data_found then y := default_for_y;
   end;

if the where clause is false, the default doesn't get inserted.  right?  in my
testing it does not.

if i execute:

create or replace procedure test_tom_proc as

p    integer;

begin

select 1 into p from dual where 1=0;

insert into test_tom values (p);

commit;

exception

when no_data_found then
    p:=-1;

end;
/

nothing happens.

What if you wanted this to insert a -1 into the table?  The only way I've found
to do it is like this:

create or replace procedure test_tom_proc as

p    integer;
f       integer;

begin

f := 0;

select count(*) into f from dual where <clause>;

if f <> 0 then
  select 1 into p from dual where <same_clause>;
else
 p := -1;
end if;

insert into test_tom values (p);

commit;

end;
/

There's nothing wrong with this last approach, as far as I can tell, but unless
I'm missing something (and that's likely) your block handles the exception, but
cannot do anything with the default value.

Followup:
processing doesn't "stop", it jumps.

If the code was:

   begin
       select x into y from t where ...;
       insert into t1 values (y);
   exception
        when no_data_found then y := default_for_y;
   end;

but you wanted the insert to happen regardless -- you made a coding error -- the
exception block covered too much ground and should have been:

   begin
       begin
          select x into y from t where ...;
       exception
          when no_data_found then y := default_for_y;
       end;
       insert into t1 values (y);
   end;

so you have a programming bug, not an issue with exceptions.  you just covered
too much ground is all.  move the exception block to cover the statement(s) you
are EXPECTING an exceptional condition, that is all.

stop counting rows -- that stinks, it is really really slow and doesn't even
assure you of success!

Rating: 5
Now I get it March 11, 2003
Reviewer: David from AL
I see what you mean.  I have to admit to a fundamental misunderstanding of the
options concerning placement of exception blocks.

Thanks for your help.

Rating: 5
More than 1 exception types April 03, 2003
Reviewer: Tatiane
In a PL/SQL block, I can have:

insert into tbl (id, descr, quote, ... ) values (10, 'Some description', 6354,
... );

id is my pk
descr is an uk
quote is an uk

How can I treat the exception clause in order to know if the violation was in
id, descr or quote, since the three of them yield the dup_val_on_index exception
?

Further, if there are other dml statements in the code, how do I know in the
exception clause from which one (which line) I get the dup_val_on_index
exception ?

Thank you for your services.

Followup:
you would parse the error message in the dup_val_on_index handler.

if you want to know the exact statment, you would wrap each statment in an
exception block.  Exceptions are raised at the block level, not a line, not a
statement.

begin
   begin
       insert into ..
   exception  when ....
   end;
   begin
       insert into ..
   exception  when ....
   end;
   begin
       insert into ..
   exception  when ....
   end;

April 03, 2003 Vipin -- Thanks for the question regarding "How to convert julian date into regular date", version 8.0.6
You Asked
Hi Tom ,
          We have to load some data in oracle from a flat file . The date in
flat file is displayed in Julian date format so 31st Mar 2003 comes as
2003090(number of days since Jan 1st,2003) and 20th Mar 2003 comes as
2003069(again number of days since Jan 1st,2003) .
I have to convert Julian date(2003090) into 20030331(yyyymmdd) format.
Now oracle consider julian date as the number of days since         Jan 1st,4712
bc  and not in the way i got my data. So is their any function which i can use
to convert a date given in 2003090 format into 20030331 format or do i have to
write a user function for this ?
Your help will be greatly appreciated as always.
Thanks
and we said...
Oracle julian dates are actual julian dates -- yours is a century, year and day
into the year.

You will not load into a yyyymmdd format (that is an output or input date
mask).. Internally ALL dates are 7 bytes and contain the century, year, month,
day, hour, minute, second.  You can then format this date as you choose on
output using TO_CHAR.

I assume you are using sqlldr, so, your control file will look like:

...
date_field
"trunc(to_date(substr(:date_field,1,4),'yyyy'),'yy') + substr(:date_field,5)-1",
...

will do it -- just take the year, create an Oracle date from it, trunc that to
the first day of the year and add the number of days into the year to that.
Review & Followup
Rating: 4
Specifying Date format April 03, 2003
Reviewer: Abdul Samed from Chennai, India
Hi Tom,

I think Vipin gets data in the format YYYYDDD i.e the year followed by number of
days since that year.

So he can straightaway specify the date format YYYYDDD. It'll convert that data
into a valid date.

HTH.

Regards,
Aby

Followup:
DOH, sometimes the obvious escapes us...  of course

...
field   date "YYYYDDDD",
....

does it.

Rating: 5
DOH?? April 03, 2003
Reviewer: Swapnil from UK
What is DOH??

Followup:
It is the sound Homer Simpson makes when he does something stupid...
http://www.rpi.edu/~markhn/sounds.simpsons.html
32dohs.wav

Rating: 4
Its working now........ April 03, 2003
Reviewer: Vipin from Toronto,Canada
 I put the following in my control file and it worked fine:
fieldname   POSITION(27:33) char
"to_char(to_date(:fieldname,'yyyyddd'),'yyyymmdd')"

Thanks......

Followup:
you are not putting a date in a number or a string are you????

dates go in dates.
numbers go in numbers.
strings go in strings.

never never never put a date in a number or a string -- someone will eventually
put garbage in there and someone else will always try to to_date it so they can
use the format they like for display -- or they'll want to add a day or a month
or something and the garbage data will defeat them.

use a DATE.

Rating: 4
column data type............ April 03, 2003
Reviewer: Vipin from Toronto,Canada
I also prefer the same but i have been given an existing target table which i am
not allowed to change so ......
I have one related query though.......the flat file has got a header record(
which says STARTnnnnn where n is a numeric digit) and a footer record which
says(ENDnnnnnnn). I have tried using a When condition to discard these 2 records
but i could discard only one. Does when support OR operator ?
if i use  when (1:5) != 'START'  , it works but if i use
when ((1:5) != 'START'  OR (1:3) != 'END') , it failed......
What am i missing ?
Thanks

Followup:
you can use skip=1 to skip the first and the 1:3 != END to get rid of the footer

Rating: 4
Working fine now......... April 03, 2003
Reviewer: Vipin from Toronto,Canada
Thanks for your help , Tom

April 03, 2003 OP -- Thanks for the question regarding "Tuning Process for like Operator !", version 8.1.6.0.0
You Asked
This Question is Related with the Tuning process.
Dear Tom,
Hi !

The LIKE operator if used do not make use of the INDEX
created on the table for that particular column.
Is there any way by which the LIKE condition can be
replaced in a fashion as to make sure the INDEX if
Exists can be utilized.

Is the same rule i.e whenever we make use of IN operator,
the INDEX is not utilized.If yes,Kindly suggest the
alternative for this also ?

Please answer taking this statement as an Example :=-

select empno,ename,deptno from emp
where name like 'a%';

Thanks & Regards
OP
and we said...
A like will use an index -- it depends on many other factors as
to whether an index will be used or not.  Look at this example.
I copy the EMP table and run your query.  It uses the index.  I
compute statistics on the table (small table) and it does not.
The table just isn't BIG enough to use an index using CBO.  I
change my optimier goal to be "first rows" which favors index
access more and it uses the index again....

So, LIKE definitely will (or will not) use an index.  It depends
on the circumstances and one would need more info to diagnose
why/why not an index is being used in a particular case.

ops$tkyte@DEV816> create table emp
  2  as
  3  select * from scott.emp;

Table created.

ops$tkyte@DEV816> create index name_idx on emp(ename);

Index created.

ops$tkyte@DEV816> set autotrace on explain
ops$tkyte@DEV816> select empno,ename,deptno
  2    from emp a
  3   where ename like 'a%'
  4  /

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=1 Card=5
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=1
   2    1     INDEX (RANGE SCAN) OF 'NAME_IDX' (NON-UNIQUE)

ops$tkyte@DEV816> analyze table emp  compute statistics;

Table analyzed.

ops$tkyte@DEV816> select empno,ename,deptno
  2    from emp b
  3   where ename like 'a%'
  4  /

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1
   1    0   TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=1
Bytes=10)

ops$tkyte@DEV816> alter session set optimizer_goal = first_rows;

Session altered.

ops$tkyte@DEV816> select empno,ename,deptno
  2    from emp c
  3   where ename like 'a%'
  4  /

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=2 Card=1
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=2
   2    1     INDEX (RANGE SCAN) OF 'NAME_IDX' (NON-UNIQUE)

ops$tkyte@DEV816> set autotrace off
Review & Followup
Rating: 5
full table scan October 01, 2001
Reviewer: A reader
Suppose Iam using the like operator in a sub query in the predicate,
and Iam querying one of the two tables in the from clause in the sub query in
the predicate.

Will we do a full table scan of the other table for each row returned by the
subquery, or will we do one full table scan for all the rows returned by the sub
query.

Followup:
impossible to say without seeing the query, understanding the indexes and
knowing the size/distribution of the data.

Rating: 3
FOLLOW UP TO ABOVE COMMENT October 01, 2001
Reviewer: A reader
     select COLUMNS
     from  T,T1
where T.id = T1.id
OTHER CONDITIONS AND
T.Y_cd not in (SELECT Y_CD FROM   T WHEREY_CD like '%GHIO%')
     group by COLUMNS

Table t contains 2 million rows, table t1 contains 1000 rows, and the optimizer
is RBO

Rating: 2
FOLLOW UP TO ABOVE COMMENT October 02, 2001
Reviewer: A reader
Tom

The query is given above.

Rating: 5
What about RBO July 26, 2002
Reviewer: atul from india
Sir,

Like operator uses index in CBO,but if we have RBO
how to deal with it??
we can't change to FIRST_ROWS in RBO...

Thanks.
atul

Followup:
did any say the RBO wouldn't use an index with like?  I don't see that here at
all.

scott@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain
scott@ORA817DEV.US.ORACLE.COM> select * from emp where ename like 'F%';

Execution Plan
----------------------------------------------------------
   0  SELECT STATEMENT Optimizer=CHOOSE
   1    0  TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
   2    1    INDEX (RANGE SCAN) OF 'EMP_IDX' (NON-UNIQUE)

that was the RBO (no cost, no card = RBO was used)

Rating: 4
Using Like % April 03, 2003
Reviewer: Dan from US
Tom,
      We have a table which holds loan data of 900,000 records.We have search
screen where most of the search is based on the loan id which is a number field.
  My question is what is best way to use it...I cannot use like with number as i
am forcing loan id to be character.

Dan

Followup:
don't understand what you are asking for....

you say "is a number field"
        "cannot use like with number"
        "forcing loan id to be character"

those seem to be contradictory.

Rating: 3
Dan could try this... April 03, 2003
Reviewer: A reader
You could do something as obscure as

select *
from loan
where (loan_id between :x*power(10,1) and (:x+1)*power(10,1)-1)
or (loan_id between :x*power(10,2) and (:x+1)*power(10,2)-1)
or (loan_id between :x*power(10,3) and (:x+1)*power(10,3)-1)
or (loan_id between :x*power(10,4) and (:x+1)*power(10,4)-1)
or (loan_id between :x*power(10,5) and (:x+1)*power(10,5)-1)
or (loan_id between :x*power(10,6) and (:x+1)*power(10,6)-1)

or a function-based index on to_char(loan_id)??

but... if it needs to be searched like a character string, maybe it should be a
charcter...

April 03, 2003 -- Thanks for the question regarding "Features deprecated in oracle 9i", version 8.1.7
You Asked
Are there a list of features that are present in Oracle 8i but have been
deprecated in Oracle 9i.
and we said...
suggest you goto:
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/index.htm
and search for deprecated, you'll see everything that is and will be at
sometime.

There is an appendix in the concepts guide that covers this -- and that search
will hit it as well.
Review & Followup
Rating: 3
Partition Views? April 02, 2003
Reviewer: Matt from Australia
I just searched for PARTITION_VIEW_ENABLED which switches on some functionality
that preceded the current partition table implementation in the RDBMS. I
understood that this functionality *was* to be deprecated, however I can't find
anything in the doco to this effect.

The reason I mention this is that we use some partition views at the core of our
application. These are partition views on a large number (200 or so) of
clustered index tables. This is in order to get the benfits of partition
pruning.

I know that Oracle recommend that users of partition views should move on to use
partitioned tables. However, I understand that at least up to release 9.0.1 that
partition tables do not support index clusters.

Are there any future plans for Oracle to support index clusters in partitioned
tables?

I am currently looking at how we store and report on this (index clustered) data
and it looks to me that at the moment we will lose some query performance if we
move in this direction (working towards a benchmark of this).

Are Partitioned Viees going to be deprecated and if so when?

Do you have any ideas whethere there are any recommended migration paths for our
situation?

THanks and Regards

Followup:
way way way back in 8.0.

partitioned views already ARE deprecated.  that is in the server tuning guide
for that release.

I do not see anything about partitioned clusters in the near future.

However, partitioned IOTS offer pretty much the same benefits as a B*Tree
cluster and they can be partitioned - have you investigated them?

Rating: 5
depricated features April 02, 2003
Reviewer: GVN from Houston
Thank you

Rating: 3
More on PARTITION_VIEW_ENABLED April 03, 2003
Reviewer: Matt from Australia
OK, we jumped passed 8.0 from 7.3.4 to 9.0.1.

IOT's are on the short list of things to consider, but were down the bottom of
that list, since I figured that the overhead of managing the index structure
would be slow for data loads.

We currently load into our clusters every night (say (very) roughly 100,000
rows), this data storage is core to the system and the business. Although, once
loaded there are no row updates. I have no feeling at the moment for what the
overhead is on IOT's compared to cluster/heap tables for for this type of data
loads (but I guess I shall be finding out soon).

I also then have to consider the migration strategy -> turning clustered index
tables into IOT's.

Can you compress IOTs?

Followup:
and how about the b*tree clusters -- forces order on the data, just like an
IOT....

IOTs can in fact be compressed -- yes.  Using index key compression.

April 04, 2003 Jerry -- Thanks for the question regarding "Limit Depth of Hierarchical Query", version 8.1.7
You Asked
Good morning.

I need to produce an org chart for a consultant to load an LDAP directory.  He
wants all the manager types, but doesn't want anyone with no direct reports.  Is
there a way to limit the depth of an hier. query to non-leaf nodes ( I believe
that's the right terminolog)?

Below is some data that I have been playing with.  Appreciate your time.

SET autotrace off
SET pagesize 40

drop table my_emp;

create table my_emp
(
 emp_id  varchar2(2),
 name    varchar2(15),
 mgr_id  varchar2(2)
);

insert into my_emp (emp_id,name,mgr_id) values ('1','KING',' ');
insert into my_emp (emp_id,name,mgr_id) values ('2','SMITH','1');
insert into my_emp (emp_id,name,mgr_id) values ('3','JONES','1');
insert into my_emp (emp_id,name,mgr_id) values ('4','BALL','1');
insert into my_emp (emp_id,name,mgr_id) values ('5','HOBBS','2');
insert into my_emp (emp_id,name,mgr_id) values ('6','BLACK','2');
insert into my_emp (emp_id,name,mgr_id) values ('7','WHITE','3');
insert into my_emp (emp_id,name,mgr_id) values ('8','MARSH','3');
insert into my_emp (emp_id,name,mgr_id) values ('9','CHANG','3');
insert into my_emp (emp_id,name,mgr_id) values ('10','STEWART','4');
insert into my_emp (emp_id,name,mgr_id) values ('11','DAILY','4');
insert into my_emp (emp_id,name,mgr_id) values ('12','GREEN','4');
insert into my_emp (emp_id,name,mgr_id) values ('13','BELL','10');
insert into my_emp (emp_id,name,mgr_id) values ('14','HARRIS','10');

commit;

select level,
       substr(lpad(' ',level*2)||name,1,20) name
from my_emp
start with name = 'KING'
connect by prior emp_id = mgr_id;

     LEVEL NAME
---------- --------------------
         1   KING
         2     SMITH
         3       HOBBS   <- worker bee - do not want
         3       BLACK      "
         2     JONES
         3       WHITE      "
         3       MARSH      "
         3       CHANG      "
         2     BALL
         3       STEWART
         4         BELL     "
         4         HARRIS   "
         3       DAILY      "
         3       GREEN      "
and we said...
ops$tkyte@ORA817DEV.US.ORACLE.COM> select level, rpad( '*', 2*level, '*' ) ||
ename ename
  2  from emp
  3  start with mgr is null
  4  connect by prior empno = mgr
  5  /

     LEVEL ENAME
---------- --------------------
         1 **KING
         2 ****JONES
         3 ******SCOTT
         4 ********ADAMS
         3 ******FORD
         4 ********aaa
         2 ****BLAKE
         3 ******ALLEN
         3 ******WARD
         3 ******MARTIN
         3 ******TURNER
         3 ******JAMES
         2 ****CLARK
         3 ******MILLER

14 rows selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select level, rpad( '*', 2*level, '*' ) ||
ename ename
  2  from emp
  3  where exists ( select null from emp e2 where mgr = emp.empno)
  4  start with mgr is null
  5  connect by prior empno = mgr
  6  /

     LEVEL ENAME
---------- --------------------
         1 **KING
         2 ****JONES
         3 ******SCOTT
         3 ******FORD
         2 ****BLAKE
         2 ****CLARK

6 rows selected.
Review & Followup
Rating: 4
Hierarchical Query from two tables December 15, 2002
Reviewer: Sri from India
Dear Tom,

  That was indeed very helpful.  But my problem is little different.

  I am developing an application in D2k and using the hierarchical tree control.
 It accepts a single query for populating the data.  My problem is that I have
two queries that has to be joined.  My table structures are as follows: -

Create Table T1
(Code Varchar2(10) Constraint pk_T1_Code Primary Key,
 T1_Name Varchar2(100),
 Parent_Code Varchar2(10) Constraint fk_T1_PCode References T1(Code)
)
/
Create Table T2
(Code Varchar2(10) Constraint pk_T2_Code Primary Key,
 T2_Name Varchar2(100),
 T1_Code Varchar2(10) Constraint fk_T2_T1Code References T1(Code)
)
/

  Please note that these are just sample tables.  My design is such that I
should have both these tables separate.  With only T1, my hierarchical query is
very simple.  But my problem is that the table T2 will have rows that refers to
one (and only one) of the rows in table T1, and when I show the tree it should
appear like that.

  Expecting your valuable help.

Regards,

Sri

Followup:
in 8i, you'll have to select (select) from....

select t1.*, (select t2.t2_name from t2 where t2.t1_code = t1.code )
  from t1
 start with...
connect by ...

in 9i, you can in fact join a connect by (and order it too)

Rating: 3
Hierarchical Query from two tables December 15, 2002
Reviewer: Sri from India
Hi Tom,

  I am afraid Tom, I think I didn't put my requirement properly.  I have the
tables t1 and t2 as above.  Now,

insert into t1 values ('1', 'First', Null);
insert into t1 values ('2', 'A', '1');
insert into t1 values ('3', 'B', '1');
insert into t1 values ('4', 'C', '1');
insert into t1 values ('5', 'AA', '2');
insert into t1 values ('6', 'AB', '2');
insert into t1 values ('7', 'CA', '4');
insert into t1 values ('8', 'CB', '4');
insert into t1 values ('9', 'CAA', '7');
insert into t1 values ('10', 'ABA', '6');

insert into t2 values ('1', 'Child of AA', '5');
insert into t2 values ('2', 'Child of CAA', '9');

I gave the select statement as you adviced: -

select level, substr(t1_name, 1, 15) Name,
   (select substr(t2_name, 1, 15) from t2 where t2.t1_code = t1.code) t2Name
  from t1
  start with parent_code is Null
  Connect by prior code = parent_code;

I am getting the following output: -

     LEVEL NAME       T2NAME
---------- ---------- ---------------
         1 First
         2 A
         3 AA         Child of AA
         3 AB
         4 ABA
         2 B
         2 C
         3 CA
         4 CAA        Child of CAA
         3 CB

Actually, my requirement is that I need the output as follows: -

     LEVEL NAME
---------- -----------
         1 First
         2 A
         3 AA
         4 Child of AA
         3 AB
         4 ABA
         2 B
         2 C
         3 CA
         4 CAA
         5 Child of CAA
         3 CB

  Hope you got me.  Waiting for your comments.

Sri

Followup:
I got you --

Your hierarchy tables don't appear to be modeled correctly -- at least not for
the questions you seem to want to ask.

I would fix that and try again.  T2 shouldn't be a separate table.  In 9i, while
we technically could write this query -- it would be painfully slow (requires a
connect by of a UNION ALL).

You will not be getting this output efficiently.

Rating: 4
Hierarchial query December 15, 2002
Reviewer: Sunil Kumar from Bangalore, India
The solution is very useful But i have slightly diffrent requirement. I want to
stop at the Highest level for a particular Manager. For ex: For a particular
hierarchy the output should stop at higest level. The sample output is given
below::

1 **KING
2 ****JONES
3 ******SCOTT
4 ********ADAMS

Becaase i will be processing the employee with Highest LEVEL. Can you please
suggest me a solution for this.

Sunil.

Followup:
you aren't making sense to me.  how is that the "highest level".

Rating: 4
Sorting in hierarchical tree December 27, 2002
Reviewer: Siba from Delhi
The Following is a TREE view of the EMP Table.

SELECT Empno,Ename,Sal,LEVEL
FROM EMP
START WITH Empno In (7839,1234)
CONNECT BY mgr = PRIOR empno;

     EMPNO ENAME             SAL      LEVEL
---------- ---------- ---------- ----------
      7839 KING             5000          1
      7566 JONES            2975          2
      7788 SCOTT            3000          3
      7876 ADAMS            1100          4
      7902 FORD             3000          3
      7369 SMITH             800          4
      7698 BLAKE            2850          2
      7499 ALLEN            1600          3
      7521 WARD             1250          3
      7654 MARTIN           1250          3
      7844 TURNER           1500          3

     EMPNO ENAME             SAL      LEVEL
---------- ---------- ---------- ----------
      7900 JAMES             950          3
      7782 CLARK            2450          2
      7934 MILLER           1300          3
      1234 SHAUN            8000          1
      1235 MARK             4000          2

Now I want to sort this TREE on the basis of SAL for
PARENT row only. If I sort by SAL DESC then I would like to have the SHAUN (with
empno 1234) and its tree to come first.
If I do sort by SAL ASC, the above should be the output.

I did it using a stored procedure using PLSQL table type. But I am unable to
return it to JAVA as a OUT Parameter.
The other way is to use TEMP TABLES, but I do not want to use it.

Please suggest me how this can be achieved. Is it possible using a single QUERY.

Regards
Siba

Followup:
In Oracle9i, you can do this with ORDER SIBLINGS BY.

I can see a way to do this but it'll be relatively inefficient for large
hierarchies.  Consider:

scott@ORA920> column empno format a15
scott@ORA920> column my_root format a5
scott@ORA920>
scott@ORA920> SELECT rpad( '*', 2*level, '*' ) || Empno empno,
  2         Ename,Sal,LEVEL lvl
  3    FROM EMP
  4   START WITH empno in ( 7698, 7566 )
  5   CONNECT BY mgr = PRIOR empno
  6   order siblings by SAL DESC
  7  /

EMPNO           ENAME             SAL        LVL
--------------- ---------- ---------- ----------
**7566          JONES            2975          1
****7788        SCOTT            3000          2
******7876      ADAMS            1100          3
****7902        FORD             3000          2
******7369      SMITH             800          3
**7698          BLAKE            2850          1
****7499        ALLEN            1600          2
****7844        TURNER           1500          2
****7521        WARD             1250          2
****7654        MARTIN           1250          2
****7900        JAMES             950          2

11 rows selected.

scott@ORA920>
scott@ORA920> select empno, ename, sal, lvl
  2    from ( SELECT rpad( '*', 2*level, '*' ) || Empno empno,
  3                  Ename,Sal,LEVEL lvl,
  4                  substr(
  5                     (select max( to_char(level,'fm0000') || sal)
  6                        from emp emp2
  7                       where empno in ( 7698, 7566 )
  8                       start with emp2.empno = emp.empno
  9                       connect by prior mgr = empno ), 5 ) my_root,
 10                  rownum rn
 11             FROM EMP
 12            START WITH empno in ( 7698, 7566 )
 13          CONNECT BY mgr = PRIOR empno
 14        )
 15   order by my_root ASC, rn
 16  /

EMPNO           ENAME             SAL        LVL
--------------- ---------- ---------- ----------
**7698          BLAKE            2850          1
****7499        ALLEN            1600          2
****7521        WARD             1250          2
****7654        MARTIN           1250          2
****7844        TURNER           1500          2
****7900        JAMES             950          2
**7566          JONES            2975          1
****7788        SCOTT            3000          2
******7876      ADAMS            1100          3
****7902        FORD             3000          2
******7369      SMITH             800          3

11 rows selected.

scott@ORA920> select empno, ename, sal, lvl
  2    from ( SELECT rpad( '*', 2*level, '*' ) || Empno empno,
  3                  Ename,Sal,LEVEL lvl,
  4                  substr(
  5                     (select max( to_char(level,'fm0000') || sal)
  6                        from emp emp2
  7                       where empno in ( 7698, 7566 )
  8                       start with emp2.empno = emp.empno
  9                       connect by prior mgr = empno ), 5 ) my_root,
 10                  rownum rn
 11             FROM EMP
 12            START WITH empno in ( 7698, 7566 )
 13          CONNECT BY mgr = PRIOR empno
 14        )
 15   order by my_root DESC, rn
 16  /

EMPNO           ENAME             SAL        LVL
--------------- ---------- ---------- ----------
**7566          JONES            2975          1
****7788        SCOTT            3000          2
******7876      ADAMS            1100          3
****7902        FORD             3000          2
******7369      SMITH             800          3
**7698          BLAKE            2850          1
****7499        ALLEN            1600          2
****7521        WARD             1250          2
****7654        MARTIN           1250          2
****7844        TURNER           1500          2
****7900        JAMES             950          2

11 rows selected.

scott@ORA920>
scott@ORA920>

Rating: 5
Very helpful April 04, 2003
Reviewer: phil winfield from UK
Tom,
What about a situation where I want to do a search in the hierarchy for a bit of
text. I'm going to use contains and index it using the ctxsys index. I want to
return this in the hierarchy  as above but Oracle won't allow me to place this
subquery in the the connect by condition.
Eg

Data returned as we have it now ....

1A
2AA
3AAA
4AAAA
5AAAAA
1A
2AB
3AB2
4AAA
5ABCD
1 C
2 CC

Perform a search for AAA, want the below returned...
1A
2AA
3AAA
1A
2AB
3AB2
4AAA

Is this possible in a statement?

Followup:
need an example....  tables, data, what you tried, etc.

April 05, 2003 Robert -- Thanks for the question regarding "Track buffer gets with 10046 trace", version 8.1.7
You Asked
Tom,

With the 10046 trace we can see the exact segment blocks Oracle accesses in a
*physical read*. Is there any way to see which blocks Oracle accesses in a
*logical read* ?

This is important when analyzing sql and explain plans.
In the trace file produced by 10046 tracing we can follow along with the
physical reads.... but not having the logical reads is leaving us with only half
the picture, which makes the physical read statistic somewhat worthless... since
we don't know which tables or indexes Oracle may be accessing via LIO.

(1) Can we track exact LIO as we can with PIO?
(2) If the answer is 'no', do you see this as a shortcoming, and why?

Thanks,

Robert.
and we said...
In 9iR2 (9202 <<-- 02, not 01) and up -- this is given to you.   consider:

select *
from
 all_users

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          3          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        4      0.00       0.00          1        154          0          45
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        6      0.01       0.02          1        157          0          45

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 104  (OPS$TKYTE)

Rows     Row Source Operation
-------  ---------------------------------------------------
     45  NESTED LOOPS  (cr=154 r=1 w=0 time=2827 us)
     45   NESTED LOOPS  (cr=102 r=1 w=0 time=1801 us)
     45    TABLE ACCESS FULL OBJ#(22) (cr=8 r=1 w=0 time=553 us)
     45    TABLE ACCESS CLUSTER OBJ#(16) (cr=94 r=0 w=0 time=933 us)
     45     INDEX UNIQUE SCAN OBJ#(7) (cr=4 r=0 w=0 time=216 us)(object id 7)
     45   TABLE ACCESS CLUSTER OBJ#(16) (cr=52 r=0 w=0 time=754 us)
     45    INDEX UNIQUE SCAN OBJ#(7) (cr=4 r=0 w=0 time=186 us)(object id 7)

this line:
     45    INDEX UNIQUE SCAN OBJ#(7) (cr=4 r=0 w=0 time=186 us)(object id 7)

shows 4 consistent reads, 0 physical reads and 0 physical writes (you would see
writes for sorts and such for example).  The time spent on this step was 186us
or 186/1,000,000th of a second

this is not there prior to 9202, so it is something to look forward to (and is
very useful indeed)
Review & Followup
Rating: 4
Row level details in 9iR2 April 06, 2003
Reviewer: Wolfgang Breitling from Calgary, Alberta
"In 9iR2 (9202 <<-- 02, not 01) and up -- this is given to you."
I get that in 9.2.0.1 as well. I tried it both in W2K and in Linux. Two things
though - which may have been lifted with 9202: you need to set statistics_level
to all and you need to make sure the statement is processed by the cbo:

select *
from
 all_users

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.37          0          2          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.01       0.00          0         51          0          13
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.02       0.37          0         53          0          13

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 23  (SCOTT)

Rows     Row Source Operation
-------  ---------------------------------------------------
     13  NESTED LOOPS
     13   NESTED LOOPS
     13    TABLE ACCESS FULL USER$
     13    TABLE ACCESS CLUSTER TS$
     13     INDEX UNIQUE SCAN I_TS# (object id 7)
     13   TABLE ACCESS CLUSTER TS$
     13    INDEX UNIQUE SCAN I_TS# (object id 7)

Adding a superfluous hint, just to force the CBO:
select /*+ all_rows */ *
from
 all_users

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.07          0          2          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.01       0.00          0         50          0          13
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.04       0.07          0         52          0          13

Misses in library cache during parse: 1
Optimizer goal: ALL_ROWS
Parsing user id: 23  (SCOTT)

Rows     Row Source Operation
-------  ---------------------------------------------------
     13  NESTED LOOPS  (cr=50 r=0 w=0 time=3978 us)
     13   HASH JOIN  (cr=34 r=0 w=0 time=3283 us)
     13    TABLE ACCESS FULL USER$ (cr=6 r=0 w=0 time=585 us)
     22    TABLE ACCESS FULL TS$ (cr=28 r=0 w=0 time=695 us)
     13   TABLE ACCESS CLUSTER TS$ (cr=16 r=0 w=0 time=513 us)
     13    INDEX UNIQUE SCAN I_TS# (cr=2 r=0 w=0 time=146 us)(object id 7)

Rating: 5
Thanks! April 07, 2003
Reviewer: Robert from Memphis, USA

April 05, 2003 -- Thanks for the question regarding "One table per day", version 9.2.0
You Asked
Hi Tom,

We have a 24x7 online system that write transaction records to our transaction
log table, call it tranlog. Average daily volume about 50000 records, each
record about 600 bytes. Our daily scheduled job will read those daily records to
further process and generate reports. We will archive some old data weekly.

My team member want to let the online system create a table for each day, naming
them tranlog_yyyymmdd. They will archive and drop some old tables weekly. I
prefer to put all records in a single table and add a column "businessday" of
date type. I will archive old records from the table.

My reasons are:

1. I do not want more table names, index names, contraint names etc.
2. I do not want online system to have create tables right.

I have a few questions:

1. Which way do you prefer and why (like the pros and cons)?
   If both ways are flawed, what will be a better way.

2. Will backup and recovery be more complicated if we have one table per day?

3. Will you change your advice if the daily records are 5000 or 500000?

Thanks in advanced and I will wait for your new book.
and we said...
I do not prefer either of your methods (yours is closest to being correct).

Doing a table per day is by far the worst approach you could take -- it would be
a mess.  You would have to use dynamic sql for each insert, you would be doing
ddl every day -- bad idea.

What you should be doing is using partitioning at either the week or month
level.  You would have a process that would age partions (taking out the oldest
-- for a purge, add a new one for next week/month/whatever)

In this fashion you have one table (logically) -- so you do not have many table
names, index names, etc.

In this fashion you have the ability to purge very easily -- using a single DDL
statement that can be done without generating gobs of undo, redo and slamming
your index structures to death.

The only thing that changes with respect to the number of daily records is the
granularity of the partition. 5k records -- maybe monthly.  500,000 records --
maybe weekly.
Review & Followup
Rating: 5
thanks, great advice April 05, 2003
Reviewer: Kim
Thanks Tom,
I actually wanted to do partition. Following the example "Range Partitions with
Dates" on chapter 13 of Jonathan Lewis book. But we do not have partition
options. Well, a good reason for me to request for it. Will make life easier.

Rating: 4
when do you prefer partitions ? April 05, 2003
Reviewer: reader
tom,

at what point of situation do you advice us to have partition in tables.?

i have a table for generation of report. the table has a column by name
expenditure_on_hardware, here we store five years data.the first year will have
12 entries and the previous 4 years will have the sum of all the months
consolidated and made to single entry. like the above columns we have around 6
more columns. do you prefer to use partition and if so where will i have my
consolidated column ... inside the partitioned column ? or where?

thank you

Followup:
answer to the question "at what point..." is "when it makes your life easier or
can massively increase performance".  It is not related to volume or size of
data really -- it is related to "can this tool make your job easier or the
results of your work better".

As for the rest of the question -- not really following you.  if entries means
"rows", doesn't sound like partitioning is going to make life easier or things
better in your case.

April 05, 2003 Frédéric -- Thanks for the question regarding "PL/SQL for batch processing", version 8.1.6
You Asked
Hi,
We’re moving from a VAX/mainframe architecture to Windows NT (our Oracle
database is on NT) and thinking of rewriting a couple of our VMS batch jobs to
unload the machines.  Should PL/SQL be used for batch processing, meaning
long-running, CPU-intensive programs that run for hours against our production
database?

The Oracle documentation isn’t clear about what types/sizes of programs should
be written in PL/SQL.  For batch processing and business logic, are there any
reasons not to use PL/SQL if it can be done in PL/SQL?  With 9i’s native
compilation, would you think of any reason to write 3GL code instead of PL/SQL?

Another example, a little more extreme:
If you had to rewrite a pay system of 25,000 lines of code from an obsolete 3GL
language to something else, would you consider PL/SQL?

Thanks.
and we said...
I would in a heartbeat write ANYTHING database related in a database language.

My answer is "yes", resoundingly so.

Oracle Replication -- plsql based.
Oracle Applications -- plsql based.
Oracle Workflow -- plsql based.

It is a real language capable (more then capable) of doing real things.
Review & Followup
Rating: 5
PL/SQL is really powerful, but... April 04, 2003
Reviewer: A reader
Some may fear to get "locked in" in a single vendor specific language. Then
again, one way or another we all get locked in, in some specific languages. The
only thing I don't like about PL/SQL is the name - maybe it should be called
Pro*SQL, with string manipulation and regexp thrown in!

Followup:
show me a backup script that is no vendor specific.

In fact, show me a useful shell script for database related stuff that is not
vendor specific.

Rating: 2
More precision April 05, 2003
Reviewer: flanglais from Montreal, Canada
Could you please be more precise on these points?

1. Can you give us a couple of reasons why you would choose PL/SQL over a
compiled language?

2. Is it really the job of the database to execute the business logic?  Is batch
processing considered OLTP?  For batch processing, why would I choose the
database instead of a mainframe where I can easily monitor the job, get
execution statistics, control the priority, memory/CPU usage, etc.

3. What about portability?  Is it safe to code everything in a language so close
to the database?

4. What about performance?  When a user executes a PL/SQL unit that does batch
processing, it uses all of the CPU for a long period of time.  Shouldn’t
long-running, CPU-bound programs be executed on another machine to be fair to
the other users?  What if hundreds of concurrent users execute big PL/SQL units?
 Was Oracle really meant for that?

Thanks.

Followup:
1) plsql is a compiled language.

plsql is a language specifically designed to work with data in the database.
the datatypes in PLSQL are in fact SQL datatypes - there is zero impendence
mismatch, there is no network movement of data.  It is quite simply THE most
productive language from a developers perspective with which to manipulate data
in the database.  I will not only write code many times faster then you would
(me using plsql you using <any other language>), my code will be more efficient.

2) yes.  why isn't it.  why do you believe it isn't.  Have you looked at the
40,000 pages plus of documentation talking about all of the features in the
database?  AQ for messaging.  XML type for XML processing.  workflow engine.
replication.  data integrity.

This web site you are using -- 1000% plsql based to the core.  It is a
transactional, OLTP style system.  It has batch attributes as well (reporting
and such that I do).

And why do you believe you cannot do

o easy monitoring of jobs
o execution stats
o control the priority of memory/cpu usage (and many other knobs)

inside the database?  I would prefer to do it in the database because then I can
do it on windows, on unix (any flavor) on the mainframe -- all without knowing
any of those operating systems and in fact picking up the code and moving it
from A to B to C without change or thought about the OS.  The solutions I
provide generally are 100% OS independent here...

3) only if you want it to be portable!  You see, if I do it in the database --
it is 100% portable to dozens of platforms -- from a laptop to a clustered
mainframe - instantly.

Only if you want to port your code should you do it outside the database.

4)
we have clustering if you need to scale out.
we have a resource manager to control the amount of resources a session uses.

Yes, Oracle was really meant to do big things.

Rating: 5
An analogy April 05, 2003
Reviewer: A reader
Just think about it:

Oracle is the JVM, and PL/SQL is ... JAVA!

Followup:
or, more reasonably -- the other way around is true if you look at the timeline
;)

Rating: 4
Monitoring April 05, 2003
Reviewer: A reader
Tom, you said:
"And why do you believe you cannot do
o easy monitoring of jobs
o execution stats
o control the priority of memory/cpu usage (and many other knobs)
inside the database?"

How do you monitor database jobs (see what's going on, log files) and gather
statistics on memory/cpu usage (what tools are you using)?

Followup:
I make sure long running things use dbms_application_info HEAVILY, so
v$session_longops tells me where they are at, what they are doing.

v$sysstat, v$sesstat, v$session_event tell you tons about whats going on (not to
mention all of the other 250+ v$ views)...

sql_trace+tkprof

statspack

dbms_profiler

April 06, 2003 -- Thanks for the question regarding "denormalize", version 8.1.6
You Asked
Tom:

can you give me an simple example at what situation
denormalize will help performance, or can you refer some
document on that subject.
and we said...
Denormalization is a technique typically used in a data warehouse/mart to speed
query response time.  It is typically not used in an OLTP system as it degrades
both concurrency and update response time greatly.

An example would be if you need to answer the question "how many employees are
in departements ( x, y, z )" frequently.  You could have a table of DEPT's and
EMP's and join them and count.  OR -- you could store in the DEPT table a count
of the number of employees in that department -- an EMP_COUNT column -- and
every time you added/removed an employee detail record, you would have to
maintain this column.  Oracle provides a feature called a materialized view for
just such an operation.

Denormalization TYPICALLY involves "pre-joining" frequently joined data to avoid
having to do that at runtime.  This typically results in redudant data they
either the system (via materialized views) or you must remember to maintain in
addition to the raw detail data.
Review & Followup
Rating: 4
Deormalization is not at all suitable in Oracle? April 06, 2003
Reviewer: Thaha Hussain from Manama, Bahrain
Are you saying that, demalization is not at all required in Oracle?

Thaha Hussain

Followup:
No, where did you read that?

I said --
In a data warehouse -- sure.
In a transactional system -- not.

Rating: 5
How much successful are you? April 10, 2003
Reviewer: Thaha Hussain from Manama, Bahrain
Dear Tom,

Seems you are a Roll-Model. I like you. At each stage, it is a curiosity to know
more about you.

I have a few personal queries. Sorry, if it is out of the scope.

1. How did you become the real Tom? How many years you took to reach this stage?

2. Is Oracle only your reign?

3. Is a successful Oracle expert successful in life too?

4. What do you think as the Point of success in personal life? Does money or
prestige fulfill the purpose of our being?

5. What is your final goal?

If you have time and mind, it will be helpful for people like us to come to a
conclusion.

Thanks

Thaha Hussain

April 06, 2003 Lindsay -- Thanks for the question regarding "Next Extent Size", version 8.1.6.1
You Asked
I have tables with various sizes in the same tablespace.

I have heard that the Next Extent needs to be the same for all
tables in a given tablespace to cut down on fragmentation.  Is
this correct?

If so, does the Initial Extent need to be the same also?

Thanks
and we said...
Its true.

I love a new feature in Oracle8i release 8.1 and that is LOCALLY
managed tablespace with UNIFORM sizing.  This feature is such
that storage options such as initial, next and pctincrease are
totally ignored on object creation statements and the
tablespace controls the space always.  There is also a "best
size" algorithm you can use as well where the tablespace manages
the extents given to an object -- the object doesn't dictate
what it wants beyond its initial extent.

Now, if you are using dictionary managed tablespaces -- then you
must do this yourself and it can be hard.  Yes, the initial
extent counts -- if you set up an initial extent of 5meg and you
use 2meg next extents with pctincrease=0 (i never used anything
other then pctincrease=0 myself, although I only use locally
managed tablespaces now...) and you use datafiles that are 6meg
-- then you have just created a situation with every file that
contains an initial extent will have a 1meg free extent that
never gets used (bad example -- would never have a 6meg datafile
really but it paints the picture).

So, if possible, use locally managed tablespaces (just add 64k
to the size of EVERY file you add to it!!  64k is needed for the
bitmap -- if you put a 5meg file in there and use uniform sizing
of 1m -- then you will only get 4 1meg extents, the 5'th one
will be too small -- you need to add a 5meg+64k datafile and
then you'll get 5 1meg extents)....
Review & Followup
Rating: 5
Same problem repeated. March 21, 2003
Reviewer: A reader
Does the same logic works for the partitioned tables where every partition table
have different initial_extent and next_extent for every partition ?

Regards

Followup:
each partition is individually sized:

ops$tkyte@ORA920> CREATE TABLE t
  2  (
  3    x int primary key,
  4    collection_year int,
  5    y int
  6  )
  7  PARTITION BY RANGE (COLLECTION_YEAR) (
  8    PARTITION PART_95 VALUES LESS THAN (1996) storage (initial 1m),
  9    PARTITION PART_96 VALUES LESS THAN (1997) storage (initial 5m),
 10    PARTITION PART_01 VALUES LESS THAN (MAXVALUE) storage (initial 1k)
 11  )
 12  ;

Table created.

ops$tkyte@ORA920> select segment_name, partition_name, max(extent_id),
sum(bytes)
  2  from user_extents
  3  where segment_name = 'T' group by segment_name, partition_name;

SEGMENT_NAME                   PARTITION_NAME                 MAX(EXTENT_ID)
SUM(BYTES)
--------------------- ------------------------------ -------------- ----------
T                     PART_01                                     0      65536
T                     PART_95                                    15    1048576
T                     PART_96                                     4    5242880

Rating: 4
Its Good March 24, 2003
Reviewer: A reader
I ran the same DDL and DML to create the table 'T'. But did not understood where
has it picked the values for next_extent, min_extent, max_extent. Can you please
explain me.

SQL> select tablespace_name, initial_extent, next_extent, min_extent, max_extent
  2  from dba_tab_partitions where table_name = 'T' ;

TABLESPACE_NAME                INITIAL_EXTENT NEXT_EXTENT MIN_EXTENT MAX_EXTENT
------------------------------ -------------- ----------- ---------- ----------
USERS                                 1048576      163840          1       2041
USERS                                 5242880      163840          1       2041
USERS                                   65536      163840          1       2041

Followup:
well,first -- don't know how you got 2041 for max_extent, or even what release
of Oracle you are on.  In 9iR2 I get:

ops$tkyte@ORA920> CREATE TABLE t
  2  (
  3    x int primary key,
  4    collection_year int,
  5    y int
  6  )
  7  PARTITION BY RANGE (COLLECTION_YEAR) (
  8    PARTITION PART_95 VALUES LESS THAN (1996) storage (initial 1m),
  9    PARTITION PART_96 VALUES LESS THAN (1997) storage (initial 5m),
 10    PARTITION PART_01 VALUES LESS THAN (MAXVALUE) storage (initial 1k)
 11  )
 12  ;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> column tablespace_name format a5
ops$tkyte@ORA920> select tablespace_name, initial_extent, next_extent,
min_extent, max_extent
  2    from dba_tab_partitions where table_name = 'T' and table_owner = USER;

TABLE INITIAL_EXTENT NEXT_EXTENT MIN_EXTENT MAX_EXTENT
----- -------------- ----------- ---------- ----------
USERS        1048576                      1 2147483645
USERS        5242880                      1 2147483645
USERS          16384                      1 2147483645

The initial = amount of space requested 1m, 5m, and smallest extent possible
(16k on this system)

In this case NEXT is null since I used SYSTEM allocated extents.  min extent is
of course 1 and max extents is (as it true for LMTS) is effectively unlimited
(RBS segments diverge from this rule, they have 32k max extents)

In 9iR1 I get:

TABLE INITIAL_EXTENT NEXT_EXTENT MIN_EXTENT MAX_EXTENT
----- -------------- ----------- ---------- ----------
USERS        1048576      524288          1 2147483645
USERS        5242880      524288          1 2147483645
USERS          16384      524288          1 2147483645

and 524,288 just happens to be my UNIFORM size.  But otherwise the same.

same results in 8i.

Rating: 5
Initial Extent April 06, 2003
Reviewer: Irfan from Kuwait
Hi Tom,

I have a table with the following

SVRMGR> select table_name,initial_extent,next_extent,min_extents,max_extents
from dba_tables;

TABLE_NAME                     INITIAL_EX NEXT_EXTEN MIN_EXTENT MAX_EXTENT
------------------------------ ---------- ---------- ---------- ----------
HCAN                            262215680  125849600          1        121

I would like to know from you, how this value is calculated for Initial Extent,
hence the table was created with oracle default storage clause. And from
dba_tablespaces I get the following information for the HCAN table in tablespace
HIST.

SVRMGR> select * from dba_tablespaces where tablespace_name='HIST';

TABLESPACE_NAME                INITIAL_EX NEXT_EXTEN MIN_EXTENT MAX_EXTENT
PCT_INCREA STATUS    CONTENTS
------------------------------ ---------- ---------- ---------- ----------
---------- --------- ---------
HIST                                10240      10240          1        121
  50 ONLINE    PERMANENT

The table inherits the properties of tablespace - In this case why the Initial
Extent Differs?

Many Thanks

Followup:
well, not having been there to watch all of the steps -- I can only say that one
of your assumptions is false.

One of these statements may be true:

o the default storage clause in fact was not used, the table in question had
"initial ...."

o the default storage for the tablespace in question was DIFFERENT at the point
in time the segment was created.

o HCAN is in fact not in the HIST tablespace and the tablespace it is in has
totally different storage clauses

o HCAN was IN a different tablespace and moved to this one and kept its old
storage clause.

In other words -- some other sequence of events other then what you believe
happened -- happened.

April 06, 2003 Randy -- Thanks for the question regarding "Number of tablespaces for rollback segments", version 9.2.0.1
You Asked
A) Is there any value to having your rollback segments split across multiple
tablespaces if they are on the same set of disks?

B) Does the answer depend on the version of Oracle?
and we said...
a) in 9iR2 you should use a single UNDO tablespace and be done with it.  In 9i,
you shouldn't be setting up rollback segments anymore.

In 8i, I always used a tablespace/rollback segment.  Makes it easier to shrink
them manualy (never use optimal, it is a waste) when I wanted to.  One
tablespace, one file, one rollback segment -- easy to shrink.

But again, in 9ir2 you are missing out on a great feature by not using undo
tablespaces.

b) see a
Review & Followup
Rating: 3
Why only one ? April 06, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Ottawa
Hello Tom,

Why only One rollback segment? Isn't having multiple rollback segments more
effective, especially in an OLTP system?

And when you say One rollback, is it one with many (for example ... 30-40 or
more) extents, or with fewer extents ?

When can One consider that one rollback segment is not sufficient (undo header
contention?) and aditional ones needs to be created (on pre-9i of course)?

Thanks.

Followup:
did I say one rollback segment?

I don't think I did...

I said

In 9iR2 -- use an undo tablespace -- Oracle will take care of how many and how
big the RBS's in this undo tablespace should be.

In 8i -- I used a single rbs per tablespace or a tablespace per rbs.  I was
talking of the relationship of a rbs to a tablespace -- not the number of rbs's
you needed here.

April 07, 2003 Pratap -- Thanks for the question regarding "Security in Developer forms", version 6i
You Asked
Hi Tom,
   How are you. I am a regular reader of your forum. I have a
question regarding Developer 6i.

   We want to use Developer 6i for deploying some forms in the
Web. So to access a form from a browser we give the URL
http://prachi.macmetit.com:8888/dev60cgi/ifcgi60.exe?form=test.fmx&userid=ibox/ibox@maci&otherparams=useSDI=yes&lookAndFeel=generic&colorScheme=teal
where
http://prachi.macmetit.com:8888/
is the servername:portno
test.fmx is the form name
ibox/ibox@maci is the user_id/password@connect string

It is working fine but the problems are

1. It displays the username and password in the URL. Is there
any way to supress it ?
2. We want only priviledged users to use it. But if somebody
copies the URL, he can access the forms by pasting it in the
browser bar without having the proper authorisation. Is there
any way to prevent it ?
3. Is it possible to have a logical layer of authorization i.e
by storing the username and password in a table and checking the
roles from it.

Hope to get a answer from you soon and thanks in advance.

Pratap
and we said...
The easiest way to do this is via a feature in Forms Server
called Specific Configurations.   Forms Server uses a
configuration file, formsweb.cfg (found in
$ORACLE_HOME\forms60\server on NT) where you can specify all of
the parameters you want, including things like the userid, form
and look and feel.   The CGI executable allows you to pass the
name of that configuration in as an argument, and it starts the
form for you.

Example:

Add this to the bottom of formsweb.cfg

[hardcode]
separateFrame=True
userid=scott/tiger@webdb
form=tom.fmx
lookandfeel=Generic

and call it in a browser with a URL like this:
http://yourmachinenamehere/dev60cgi/ifcgi60.exe?config=hardcode
Review
      & Followup

      Rating:
      1
Username/password are shown in HTML-source
      code  November 20, 2001
Reviewer:  Johann
      Kandlbauer and Christoph Wagner  from Austria
If we follow your advice to use a config section, the URL is clean from
username/password but username/password are still shown in the APPLET-tag of the
HTML source code.

We are _very_ interested in another (working) solution of this problem!!!

example HTML-code:
-----snip----
<!-- Forms applet definition (start) -->
<OBJECT classid="clsid:ff348b6e-fd21-11d4-a3f0-00c04fa32518"
        codebase="/jinitiator/jinit.exe#Version=1,1,8,7"
        WIDTH="1000"
        HEIGHT="600"
        HSPACE="0"
        VSPACE="0">
blah, blah ...
<PARAM NAME="serverArgs" VALUE="module=mymodule.fmx
userid=myuser/mypassword@mySQL ">
<PARAM NAME="separateFrame" VALUE="false">
... blah, blah
-----snap-----

regards,
Johann.

      Rating: 3
JInitiator
      creating problem  December 03, 2002
Reviewer:
      Nikunj  from Ahmedabad, India
Dear Tom,

I have Oracle EE 8.1.7 & Oracle Developer 6i/ Developer Server 6i which i had
installed on windows nt 4.0 with Service pack 6a.

After Installing JInitiator Control Panel 1.1.7.27 it will run nice on some
machine and on some machine i get the below error machine in all machine i have
windows 98 and internet explorer 5.

And also if i want to user ie 6 then which JInitiator i required & which setup i
have to done at server ?

Thanks,
Nikunj

JREW caused an invalid page fault in
module <unknown> at 0000:ca5ef984.
Registers:
EAX=00e64158 CS=0187 EIP=ca5ef984 EFLGS=00010206
EBX=00e64158 SS=018f ESP=0063f832 EBP=0063f840
ECX=00e60558 DS=018f ESI=00e951e8 FS=404f
EDX=006702d8 ES=018f EDI=00e60548 GS=0000
Bytes at CS:EIP:

Stack dump:
00010112 057000e6 00000570 77af0000 00a6dc8e 320001dd 02580068 054800e6 02d000e6
77af00e6 08a0dc8e 3acc0076 03986ee2 20d001d6 f8780065 dcd00063

          Followup:

time to contact support and open a tar....

      Rating: 4
Problem with
      Jinitiator  April 05, 2003
Reviewer:  Vishal
      Gupta  from INDIA
While loading applet to open forms(6i) in browser using IE(6.0.26) i am
encountering following error of IE and IE session is getting terminated.
" IE has encountered a problem and needs to close.We are sorry for the
inconvience
1.BEANS.OCX  at 016f:01e6368
2.JAVAI.DLL at 016f:6ee3895a "

Jinitiator(1.1.8.16) plug in used.No other plug in there in the system.
Forms version 6.0.8
Machine is Pentium IV

For this i have raised TAR also (TAR no 2886303.996), but no results so far.
Could u help or direct me on this.

          Followup:

not having any windows machines anywhere in my reach -- and not really being
support -- no, I cannot.

but in reading the tar -- it does seem that support has diagnosed this and told
you where the issue is, you just didn't like the answer?  You say here you use
no other plugin but the tar clearly says you do -- and in fact it is a known
incompatibility?

      Rating: 4
Problem with
      Jinitiator  April 07, 2003
Reviewer:  Vishal
      Gupta  from INDIA
Tom,
Futher to my previous question on jinitiator.
Though i have mentioned in the TAR 2886303.996 about some Sun Java Plug 1.4 used
but latter on i came to know that application is not using it at all.
Application is
using Jinitiator Plug in only. Could you throw some light on this and direct me
where is the actual problem.Is this with IE or with Jinitiator or some thing
else...

          Followup:

but it *is installed and is providing a known conflict*.

Support has told you what to do -- you'll need to work it through them.  I do
not use windows, I do not use jinitiator myself.

      Rating: 2
why we use
      JInitiator   August 12, 2003
Reviewer:  imran
      wani  from Pakistan
is there any alternate of JInitiator ?

April 07, 2003 Omar -- Thanks for the question regarding
      "lookups", version 8.1.7

      You Asked
Hi tom,
   I am developing  a personnel system which will have a master table for the
employee information and a lot of lookups that describe his relegion, gender,
qualification, degree, work unit, department and more ...
suppose we have the same structure for the lookups (i.e
code , desc)

please I would appreciate it very much if you provide the cons and pros for
having all the lookups in one table or seperate them more than one (performance
of the reports , data entry etc...).

thanks very much in advance.

      and we said...
Pros of separate tables:

- higher concurrency, at any point in time someone might be accessing "region"
and someone else accessing "gender" -- they won't contend for the blocks
simultaneously.

- better referential integrity.  You'll be assured that a code in the GENDER
column points to a GENDER table, if the code table has gender, religion, etc --
you are not assured of that.

Pros of a single table:

- none that I am aware of.  Some people might find it "easier", but I do not
necessarily include "easier" as being a pro.  You lose meaning with a single
table.

Consider using IOT's (index organized tables) for your code lookup tables.
Review &
      Followup

      Rating:
      3
  July 18, 2001
Reviewer:  Pichaimani
      Balasubramanian  from San Jose, CA USA
Hi Tom,
   Your answer has simply unnerved us for the common mistakes we make.

      Rating: 3
Single table
      might add flexibility  September 18, 2001

Reviewer:  Jan Eric Mortensen  from Oslo, Norway

A pro of using a single table might be that you gain flexibility: By having one
lookup table which can hold several kinds of lookup types (religion, sex, etc.)
you can add or remove lookup types easily as your requirements change, ie no
need to use several tables. Also, since this table is seldom updated (once every
version update), it's essentially a READ ONLY table, which means the point about
concurrency is not less important.

          Followup:

As I said, I don't regard that as a "pro".  You lose meaning, you lose data
integrity.  There is nothing (except maybe your application) stopping the
religion field from having a sex value.  You have moved more logic into the
application -- away from the data.  Someone sub

We'll just have to agree to disagree.

As for the concurrency -- you'll still hit it in a read only situation.  You get
high cache buffer chains latching on a table you frequently scan.  Splitting it
out can help that.

I guess I just like to be able to look at a database, see tables with MEANING,
something understandable.  Using things for a dual, generic usage leads to
ambiguity and makes the system harder to maintain over time.

Also, you lose the "data type" in a single table solution.  SEX is a char(1).
RELIGION is a varchar2(30).  ZIP CODE is a number.  FISCAL YEAR is a date. and
so on.  Using a single table, everything will be a "varchar2(some_max_size)".

I prefer to be specific when possible.  If I need to add a new lookup to my
application, I must have added new code somewhere (to make use of the lookup),
I'm upgrading my system -- adding a new table is not out of the question.

      Rating: 5
Here is a quick
      question  September 18, 2001
Reviewer:
      A reader  from USA
What is the difference between  a lookup table, parent table, master table.

In this context do we have any other kind of tables( mind you in the context of
the above question, not database tables.)

          Followup:

a lookup table is typically a parent table (a master table) in a parent child
relationship.

Say you have a zip code to state lookup.  the primary key is zip code.  you will
have foreign keys in other tables that point to this primary key.  These other
tables are the child tables in this parent/child relationship.

      Rating: 4
A Probable
      Pro  September 18, 2001
Reviewer:  Srimal
       from Trenton, USA
One of the main reasons why we need to use a single table as opposed to separate
lookups may be due to different tools (Front End) used to display data... While
having separate table is good on the database side, data access for an eCommerce
application, not using Oracle tools, may be at an advantage with single table
because of client side filter capability with the set of records.

Other than that separate tables are a way to go...

Please correct me otherwise

Thank you

      Rating: 5
if you need a
      single table, make it a view  September 19, 2001

Reviewer:  Mikito Harakiri  from SF, US
What is normalization for? Now with all the latest goodies like bitmap indexes,
MVs, etc, you never have to worry about access path performance.

      Rating: 5
Designer
      Disasters  September 19, 2001
Reviewer:  Connor
      McDonald  from UK
The most common cause of "single reference table" I've seen is any application
built (or even just conceived) in Designer.  If I had a dollar/pound (insert
currency of choice here) for every time CG_REF_CODES was a nasty hot spot in a
system....

      Rating: 4
I agree with
      you.  January 04, 2003
Reviewer:  eric
      givler  from Harrisburg, PA
I've seen this done with a master "code_types" table and a codes table, and even
a table that described what the code table columns had.  Eventually, it ends up
where the "description1" through "description10" columns, or whatever, have TOO
MUCH meaning in them and should be separate.

Even if they were in one table, you are saying that an invalid code could be put
in.  Couldn't we approach each code by including it's code type, and then
putting a check constraint on the code_type that forces a certain "type of
code), so:
child's (code_type, code_code) references codes
code_type  check ( code_type='SEX' )

Or is this simply to convoluted and to be avoided?

ALSO, would you be against using Triggers to validate the codes from a generic
codes table and fire a raise_application_error if they didn't match?  (again,
still more code required which could be done with an fk/pk relationship)

          Followup:

Well, that first question is hard for me to answer cause I would never go down
that path.  but adding yet another column that is CONSTANT for the entire table?
 Nah, I don't like that idea at all  (and code_type would have to be NOT NULL as
well)

I would never use a trigger (code) when I can use declaritive integrity (no
code)

Look at this example:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7125698844755
where I offer a way to accomplish the goal using declaritive integrity -- even
though it is subtly different from what was asked for.  If I can declare it -- I
declare it.  writing code -- fun but when it can be avoided...

      Rating: 4
Here is a nice
      article on this topic  January 06, 2003
Reviewer:
      ramakrishna  from india
Dear readers,
  A nice article discussing this very issue is here:
"Dealing With Denormalization of Lookup Tables"

http://www.tdan.com/i016ht03.htm
regards
ramakrishna

      Rating: 5
n-tier change
      anything?  April 07, 2003
Reviewer:  So

Our application may contain many dozens of lookups. The java guys want to cache
this low volatile info in a higher tier. Is it more beneficial in this sense to
have a single generic lookup rather than go through their mysterious java
caching process (I'm java illiterate) on many many small tables ?
(9iAS, 9iR2)

          Followup:

one would need to know more details.  In general I am categorically opposed to
mid tier caching.  It is a great way to mess everything up and never even know
it.

      Rating: 5
Here's two more
      multi-table benefits  April 07, 2003
Reviewer:
      Dave  from Colorado
If your lookup value has it's own logical parents -- say you have a table of
Zip-code+4, with parents of county, city, state etc., where are you going to put
those values? In the same single-lookup table? (tricky referential integrity AND
querying)

Also, how are you going to maintain meaningful column statistics that would let
the optimizer know that some states have few zip codes, and some have very many?
By keeping separate lookup tables, that's how.

I hate that single-table thing.

April 07, 2003 Pal -- Thanks for the question regarding "To find
      top three amount", version 7.3

      You Asked
How can I find top three AMOUNTS?

Suppose I have the following data

DATEX         AMOUNT   CUST_NO CUST_NAME
---------- --------- --------- ----------
12/31/99      100000    402563 COPPER
12/31/99      200000    205620 ROSENBAUM
12/31/99      200000    402563 COPPER
12/31/99      200000    205620 ROSENBAUM
12/31/99      300000    402563 COPPER
12/31/99      326000    208400 MLGP
12/31/99      180000    208400 MLGP
12/31/99      180000    219367 ALPERT
12/31/99      175000    415451 SUSAN

If i have custname again i would like to add up his AMOUNT

I would like the output to be something like this

COPPER    600000
ROSENBAUM 400000
MLGP      506000

ALSO I WOULD LIKE TO GIVE DATE IN THE WHERE CLAUSE

      and we said...
It is a trick question.  You need to be a little more specific.
What if the table contained:

datex    amount   cust_no cust_name
------   ------   ------- ----------
...      100      1       x
...      100      2       y
...      100      3       z
...      100      4       a

Now, all of a sudden there are not 3 top amounts -- there are 4.
 Who should get returned?  There are 2 SQL ways to interpret
this question:

o Find me the SET of people having the top 3 amounts (eg: in the
above example, the set of top three amounts is (100).  We would
return everyone in the table in this case...

o Find me the SET of people such that the number of people
having that amount or more is LESS then or EQUAL to 3.  (eg: in
the above example, the set returned would be the EMPTY set --
there exist no row in the table such that there are 3 people
having an amount that satisfies that)

And one "procedural" way

o Sum up the amounts, sort descending, give me the first random
three you find (eg: in our example, we would get some set of 3
rows -- if we run the query again, we might get a different
answer due to a different optimization path or something --
given the same exact data!)

I'll assume you want the "procedural" one -- you want 3
semi-random rows that represent upto 3 people having the largest
amounts...

It'll not be horribly efficient as you need to sum up the
amounts -- and then order by the amounts desc, and then get the
top three.  If the table is large, this'll take a while.  One
possible solution is (make sure to look at

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:247014750272
to see some important caveats about using group by as I
am):

scott@ORA734.WORLD> create table t
  2  ( datex date,
  3    amount number,
  4    cust_no number,
  5    cust_name varchar2(25)
  6  );
Table created.

scott@ORA734.WORLD> alter session set nls_date_format =
'mm/dd/rr';
Session altered.

scott@ORA734.WORLD> insert into t values
  2   ('12/31/99',100000,402563,'COPPER' );
scott@ORA734.WORLD> insert into t values
  2   ('12/31/99',200000,205620,'ROSENBAUM' );
scott@ORA734.WORLD> insert into t values
  2   ('12/31/99',200000,402563,'COPPER' );
scott@ORA734.WORLD> insert into t values
  2   ('12/31/99',200000,205620,'ROSENBAUM' );
scott@ORA734.WORLD> insert into t values
  2   ('12/31/99',300000,402563,'COPPER' );
scott@ORA734.WORLD> insert into t values
  2   ('12/31/99',326000,208400,'MLGP' );
scott@ORA734.WORLD> insert into t values
  2   ('12/31/99',180000,208400,'MLGP' );
scott@ORA734.WORLD> insert into t values
  2   ('12/31/99',180000,219367,'ALPERT' );
scott@ORA734.WORLD> insert into t values
  2   ('12/31/99',175000,415451,'SUSAN' );

scott@ORA734.WORLD> select cust_name, -amount amount
  2    from ( select -amount amount, max(cust_name) cust_name
  3             from ( select cust_name, sum(amount) amount
  4                      from t
  5                     group by cust_name )
  6            group by -amount )
  7   where rownum <= 3
  8  /

CUST_NAME                     AMOUNT
------------------------- ----------
COPPER                        600000
MLGP                          506000
ROSENBAUM                     400000

The inner most query performs the sum(amount) for us, summing up
the amounts by customer name (add your predicate on the DATE in
this inner most query if you whish).  The query wrapping that
one is used to "do a binary sort by the NEGATIVE amount" -- its
getting our AMOUNT DESC done for us.  Since we are working on a
temporary result set -- it will almost certainly sort for us (no
opportunity for a GROUP BY NO-SORT here).  We can then use
rownum to pick off the first three.

Please understand -- this is a *trick*.  In Oracle8i, you will
not have to resort to a trick to do this, you can use order by
in subqueries.  In Oracle8i, this would simply be:

ops$tkyte@8i> select *
  2    from ( select cust_name, sum(amount) amount
  3             from t
  4            group by cust_name
  5            order by 2 desc )
  6   where rownum <= 3
  7  /

CUST_NAME                     AMOUNT
------------------------- ----------
COPPER                        600000
MLGP                          506000
ROSENBAUM                     400000

Review &
      Followup

      Rating:
      5
  April 25, 2001
Reviewer:  lrc
       from gainesville, fl usa

      Rating: 3
A General Query
      for Selecting Top n Amount  November 01, 2001

Reviewer:  Samik Nath  from Tokyo
Selecting Top n Amount
Select a.EmpNo, ......, a.amount
from acc a
where n >=  (
select count(*)
from acc b
where b.amount <= a.amount );
Selecting Low/Bottom n Amount
Select a.EmpNo, ......, a.amount
from acc
where n >=  (
select count(*)
from acc b
where b.amount => a.amount );
This comes from Cartesian Product and stopping at count.

      Rating: 4
What if there
      is another column  February 06, 2002
Reviewer:
      Venkat K  from Minneapolis, MN USA
Hi Tom,

What if there is another column, say region, in the table and you want the same
output for custno, region, and amount. Will the same nested subquery you have
work? For ex. I have COPPER, MIDWEST, 100000 and COPPER, EASTCOAST, 500000 and
so on for other customers. Can I get the same results you had with the regions
in the output as follows:

COPPER MIDWEST   100000
COPPER EASTCOAST 500000
MLGP   MIDWEST   200000
MLGP   SOUTH     306000

Thanks

Venkat

          Followup:

yes, one column -- two columns -- N columns, makes no difference.

you would group by N columns.

Note that in 8i and up -- the analytic functions make this trivial (no group by
trick as in 7.3)

      Rating: 5
Top n queries -
      you need a good question to start with  May 21, 2002

Reviewer:  Bill Bunke  from Madison, WI USA

Tom,

You are batting a thousand (again). I've often encountered a top "n" query
questions where the person hadn't given much thought to what they were really
asking for!

Your list of possible interpretations is much more complete than I had ever
considered. I like the empty set scenario; it seems to resonate as the style of
question most often asked.

However, I am not sure myself that the procedural method, although you provide
the appropriate warning, answers any reasonable question that would be asked by
someone.

My answer has always been to follow the first scenario, and return rows matching
the values found in the first three occurrences after sorting (no matter how
many rows that results in).

Thanks again.

      Rating:
      5
  April 07, 2003
Reviewer:  VL
       from HK
QUOTE from ur example ...

select -amount amount, max(cust_name) cust_name
from ( select cust_name, sum(amount) amount
       from t
       group by cust_name )
group by -amount )

The wrapping query "do a binary sort by the NEGATIVE amount". Here, could u
kindly explain what binary sort is? I really wonder how this SQL can perform
such action.

And if the inner most result set contains the following top 5 cust_name in which
3 of them with the same amount.

CUST_NAME                     AMOUNT
------------------------- ----------
COPPER                        600000
BILLY                         600000
AMY                           600000
MLGP                          506000
ROSENBAUM                     400000
  :                             :

At final, could I return the TOP 3 cust_name with COPPER, BILLY, AMY instead of
COPPER, MLGP, ROSENBAUM ???

          Followup:

binary -- meaning bit by bit -- not using a character set to figure out if
string > string2.

I do not recommend this approach - very fragile -- it is a "trick".

Yes you can -- in 8i is it trivial

select *
  from ( select * from t order by amount desc )
where rownum <= 3

April 07, 2003 Chetan -- Thanks for the question regarding "What
      are Fixed Size and Variable Size of SGA indicative of and how do we
      calculate them?", version 7.3.4

      You Asked
When we SELECT from V$SGA, we get the values of Fixed Size,
Variable Size, Database Buffers and Redo Buffers. For e.g.

SQL> SELECT * FROM v$sga;

NAME                     VALUE
-------------------- ---------
Fixed Size               39816
Variable Size        151544820
Database Buffers     838860800
Redo Buffers          41943040

While the "Database Buffers" is a product of db_block_buffers
and
db_block_size or the "Redo Buffer" is the value of log_buffer in
INIT<SID>.ora, how do we compute Fixed Size and the Variable
Size of a SGA? What are they indicative of?

I have been looking for the answer to this for a long time. I
would appreciate if you could take some time out to reply to the
above question.

Best Regards,

(Chetan)

      and we said...
The fixed SGA is a component of the SGA that varies in size from
platform to platform and release to release.  It is “compiled”
into the database.  The fixed SGA contains a set of variables
that point to the other components of the SGA and variables that
contain the values of various parameters.  The size of the fixed
SGA is something over which we have no control and it is
generally very small.  Think of this area as a “bootstrap”
section of the SGA, something Oracle uses internally to find the
other bits and pieces of the SGA.

for consideration of the variable size, it is useful to look at
v$sgastat:

tkyte@TKYTE816> compute sum of bytes on pool
tkyte@TKYTE816> break on pool skip 1
tkyte@TKYTE816> select pool, name, bytes
  2  from v$sgastat
  3  order by pool, name;

POOL        NAME                                BYTES
----------- ------------------------------ ----------
java pool   free memory                      18366464
            memory in use                     2605056
***********                                ----------
sum                                          20971520

large pool  free memory                       6079520
            session heap                        64480
***********                                ----------
sum                                           6144000

shared pool Checkpoint queue                    73764
            KGFF heap                            5900
            KGK heap                            17556
            KQLS heap                          554560
            PL/SQL DIANA                       364292
            PL/SQL MPCODE                      138396
            PLS non-lib hp                       2096
            SYSTEM PARAMETERS                   61856
            State objects                      125464
            VIRTUAL CIRCUITS                    97752
            character set object                58936
            db_block_buffers                   408000
            db_block_hash_buckets              179128
            db_files                           370988
            dictionary cache                   319604
            distributed_transactions-          180152
            dlo fib struct                      40980
            enqueue_resources                   94176
            event statistics per sess          201600
            file # translation table            65572
            fixed allocation callback             320
            free memory                       9973964
            joxlod: in ehe                      52556
            joxlod: in phe                       4144
            joxs heap init                        356
            library cache                     1403012
            message pool freequeue             231152
            miscellaneous                      562744
            processes                           40000
            sessions                           127920
            sql area                          2115092
            table columns                       19812
            transaction_branches               368000
            transactions                        58872
            trigger defini                       2792
            trigger inform                        520
***********                                ----------
sum                                          18322028

            db_block_buffers                 24576000
            fixed_sga                           70924
            log_buffer                          66560
***********                                ----------
sum                                          24713484

43 rows selected.

the variable component of the SGA the sum of the "named" pools
-- large pool, java pool and shared pool.

The large pool is configured by the LARGE_POOL_SIZE init.ora
parameter.  It is used for allocation of "big" chunks of memory
such as used by MTS, Parallel Query, and RMAN.

The java pool is configured by the JAVA_POOL_SIZE init.ora
parameter.  The Java pool is a fixed amount of memory allocated
for the JVM running in the database.

the shared pool is *mostly* configured by the SHARED_POOL_SIZE
init.ora parameter but many other things contribute to it.  The
shared pool is where Oracle caches many bits of “program” data.
When we parse a query – the results of that are cached here.
Before we go through the job of parsing an entire query – Oracle
searches here to see if the work has already been done.  PLSQL
code that you run is cached here, so the next time you run it
Oracle doesn’t have to read it in from disk again.  PLSQL code
is not only cached here, it is shared here as well.  If you have
1,000 sessions all executing the same code, only one copy of the
code is loaded and shared amongst all sessions.  Oracle stores
the system parameters in the shared pool.  The data dictionary
cache, cached information about database objects, is stored
here.  In short, everything but the kitchen sink is stored in
the shared pool.

The shared pool is characterized by lots of small (4k or
thereabouts) chunks of memory.  The memory in the shared pool is
managed on a LRU basis.  It is similar the buffer cache in that
respect – if you don’t use it, you’ll lose it.  So, if over time
a cached parsed query plan is not reused, it will become subject
to aging out of the shared pool.  Even PLSQL code, which can be
rather large, is managed in a paging mechanism so that when you
execute code in a very large package, only the code that is
needed is loaded into the shared pool in small chunks.  If you
don’t use it for an extended period of time, it will be aged out
if the shared pool fills up and space is needed for other
objects.

So, in general -- the fixed size is computed for you when Oracle
is compiled.  You can do nothing to affect it.  The variable
size is MOST affected by java_pool_size + large_pool_size +
shared_pool_size but other parameters will contribute to it (eg:
every control_file will consume 256 bytes of variable size
memory.  If you have 4 control files, the SGA will have 1024
bytes set aside for them) in a small way.

Note that the large_pool_size was added in 8.0 and the
java_pool_size in 8.1.  Prior to that, shared_pool_size was the
largest (but not only) contributor to that size)

Review &
      Followup

      Rating:
      4
  February 14, 2002
Reviewer:
      Chetan
Appreciate the patience taken by Tom to answer the question

      Rating:
      5
  February 15, 2002
Reviewer:
      A reader

      Rating: 5
Very clear
      answer  November 24, 2002
Reviewer:  Frank
       from Canada

      Rating: 4
memory in use
      & session heap  March 12, 2003
Reviewer:
      Yakgna  from India
Tom,
 how did you get that 'memory in use' under java pool and 'session heap' under
large pool. i miss that in my output.. please let me know if i missed something
...how are they calculated from if they aren't there in the v$sgastat.

ge$kumar@devadw.world>select pool, name , bytes from v$sgastat order by pool;
 POOL       NAME                            BYTES
=========== ========================== ==========
java pool   free memory                  33554432
***********                            ----------
sum                                      33554432

large pool  free memory                   8388608
***********                            ----------
sum                                       8388608

shared pool enqueue                        171860
            KGK heap                         3756
            KQR S PO                        57600
            KQR M PO                       264212
            .
            .
            .
            log_buffer                     656384
***********                            ----------
sum                                      34664348

Thanks ...

          Followup:

you cannot see that which is not there.

run something "java".  you have all free memory, no one is using it.

run something using "shared server".  you have all free memory in the large
pool.

      Rating: 4
SGA - Fixed
      & Variable  April 04, 2003
Reviewer:
      Panjwani  from Canada
The best article I have seen so far on SGA components

      Rating: 4
Total RAM
      requirements for an oracle install  April 07, 2003

Reviewer:  Moh'd Saeed
Hi Tom:

If I am trying to calculate the total RAM requirements for running an Oracle
instance what else besides the size of the SGA and the b/g processes should I
account for ?
Am I correct that the free physical RAM available should at the very least
accomodate the SGA?
How do I accurately compute the RAM requirements of the b/g process and the user
sessions?
Does the equation change if I am using the dedicated server mode connection or
shared server mode ?

Thanks for helping me clarify this issue.

MSaeed

          Followup:

Well, the OS install guide gives you the bare minimum required on your OS.
Start there.

Yes, the SGA should fit in real memory -- and there better be stuff left over.
I don't like ROT (rules of thumb) but... in dedicated server mode, 50% of real
memory is a starting MAX for the SGA, in shared server, 80%.

You need to figure out how many ram YOUR application will consume per
connection.  Use only SQL? No PLSQL - you'll use less server memory.  Frequently
load 10,000 rows into a PLSQL index by table?  You'll need more.  Do you sort
tons of data and keep a large sort_area_retained_size?  etc etc etc (eg: your
mileage may vary).  ROT says say 1m of ram per connected session on "average".

In shared server -- the UGA is in the SGA -- so your SGA will be larger due to
this (you'll have a big large pool for hold the UGA).  In dedicated server --
the UGA is in the PGA, so your SGA is smaller but your dynamically allocated
process memory is larger (hence the 50/80 ROT above).

In short -- testing and benchmarking will get you right.  Everything else is a
huge "swag".

April 07, 2003 --
      Thanks for the question regarding "soft parse", version 8.1.7

      You Asked
Tom

How is it possible that we avoid a softparse?

My understanding is that when the first time a query is given it is hard parsed
i.e. it reads the disk. But after that every time you give that query it is read
from memory i.e. softparsed. Isnt it?
How can I avoid softparse?

Further, I observed that when I run a query from sqlplus, is being parsed each
time  i.e. the parse count is being equal to the execute count.
But when I put the same procedure and execute it again and again, then I find
that it is being parsed once and executed many times?
( I got the count info from v$sqlarea)

Can you please explain with examples please?

      and we said...
how you avoid a soft parse varies by language.

In PLSQL -- you get it for free.  If you have code that does:

  for i in 1 .. 100
  loop
        for x in ( selct * from t where zzz = i )
        loop
           .....

the query "select * from t where zzz = :BIND" is parsed ONCE per session -- not
per execution.

In Java -- you would use a prepared/callable statement and prepare it ONCE per
session -- not once per execution.

In VB -- same, don't be in such a hurry to close those statments, reuse them
over and over and over.

In SQLPlus -- being a very generic tool -- it canot cache open cursors for us
like that.  You can play with session_cached_cursors to achieve some caching by
the server.

PLSQL being very very specific -- can cache the (static) cursors for us. (but
not ref cursors)
Review & Followup

      Rating: 2
what if i am
      using servlet?  October 26, 2001
Reviewer:
      A reader
what if i am using servlet? each time user connect , do some query and
disconnect?
how those user share same statement ?
or i have no choice but soft parse each time?

          Followup:

If you are connecting/disconnecting -- then yes, each time will be a soft parse.
 Parsed statements are at the session level.  destroy the session, lose the
statement.

      Rating:
      2
But...  March 20, 2002
Reviewer:
      A reader
Hi

 When you execute multiple times BEGIN END block with insert in we get :

INSERT INTO SIEBEL.S_ACCNT_POSTN  (
      CREATED,
      ASGN_MANL_FLG,
      LAST_UPD_BY,
      OU_EXT_ID,
      MODIFICATION_NUM,
      ROW_ID,
      CONFLICT_ID,
      ASGN_DNRM_FLG,
      ROW_STATUS,
      LAST_UPD,
      CREATED_BY,
      ASGN_SYS_FLG,
      POSITION_ID)
VALUES ( :1, :2, :3, :4, :5, :6, :7, :8, :9, :10, :11, :12, :13)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       19      0.46       1.40          0          0          0           0
Execute     19      0.02       0.02          0         22        285          19
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       38      0.48       1.42          0         22        285          19

Misses in library cache during parse: 0
Optimizer goal: RULE

It means 19 soft parsing of insert INSIDE plsql block...

                                        Igor

          Followup:

Sorry -- should have been more specific.

Use a stored procedure, only stored procedures can do this caching.  If you
execute:

begin
   insert into t .....
end;

it'll parse the insert once for every execute.  OTOH, if you put the insert into
a procedure:

create or replace procedure p( ... )
as
begin
   insert into ....;
end;
/

then the insert will be parsed upon the first execution of the procedure in your
session and not again.

      Rating: 4
But it also
      makes me a bit confused on parsing  March 20, 2002

Reviewer:  Patricia Hu
So does this mean that even if the execution plan is in SGA, if we issue the
exactly same query again in sql*plus shortly afterwards(1st time in sql*plus
too), it would have another parse b/c there is no cursors cached? whereas only
stored procedure cache open cursors? SO basically does it mean query submitted
from sqlplus without using stored procedure doesn't store the execution plan at
all? If so is there any use of bind variable in sqlplus?

Clarification would be very appreciated. Thanks.

          Followup:

There are two types of parses (well, actually "three" these days).

They are

o HARD parse -- the query has never been seen before, isn't in the shared pool.
We must parse it, hash it, look in the shared pool for it, don't find it,
security check it, optimize it, etc (lots of work).

o SOFT parse -- the query has been seen before, is in the shared poo.  We have
to parse it, hash it, look in the shared pool for it and find it (less work then
a hard parse but work none the less)

o a kinder, softer SOFT parse -- you are using session_cached_cursors (search
this site for that word for info).  We take your query, look in the sessions
cursor cache -- find the cursor and reuse it.  Very very little work.

So, after "startup", you go into sqlplus and do this:

SQL> alter session set session_cached_cursors=0; -- just making sure this is off
SQL> select * from emp;  -- hard parse
SQL> select * from emp; -- soft parse
SQL> select * from emp; -- soft parse
SQL> alter session set session_cached_cursors=100; -- enable this feature
SQL> select * from emp;  -- soft parse
SQL> select * from emp; -- kinder, gentler, soft parse

      Rating: 4
How do you
      distinguish between the last two parses?  March 20, 2002

Reviewer:  Prince
Tom,

Does the sql_trace (or any other equivalance) show whether the parse is "a
kinder, softer SOFT parse" (CACHing) or just the "SOFT parse" (no CACHing)?

          Followup:

you would look into v$mystat/v$statname for "session cursor cache count" to see
it go up.

sql_trace won't show that tho.  it'll just show as a soft parse.  see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:904605517791
for an example.

      Rating: 3
SELECT
      STATEMENT TAKES TO LONG  March 21, 2002
Reviewer:
      A reader
I have following SELECT statement. Cost is low. But
query does not produce output after 1 hour and then it
fails with snapshot too old.
SELECT s.swlogin, DECODE (o.cust_id, NULL, 'Prospect', 'Existing Cust')
campaign,
       COUNT (resp_opportunity_id) total_number
  FROM wsi_opportunity o, sw_specialist s
 WHERE o.resp_date_created BETWEEN SYSDATE - 600 AND SYSDATE
   AND o.opp_quick_note LIKE '%' || TRUNC (SYSDATE)
   AND o.opp_assigned_to_id = s.swspecialistid
   AND s.whdistributionlist =
        (SELECT whdistributionlist
           FROM sw_specialist
          WHERE swlogin = 'Paul Slayer')
 GROUP BY s.swlogin, DECODE (o.cust_id, NULL, 'Prospect', 'Existing Cust')

TKPROF OUTPUT
--------------
TKPROF: Release 8.1.5.0.0 - Production on Wed Mar 20 22:47:32 2002

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Trace file: ././vant8i_ora_9218.trc
Sort options: default

********************************************************************************
count    = number of times OCI procedure was executed
cpu      = cpu time in seconds executing
elapsed  = elapsed time in seconds executing
disk     = number of physical reads of buffers from disk
query    = number of buffers gotten for consistent read
current  = number of buffers gotten in current mode (usually for update)
rows     = number of rows processed by the fetch or execute call
********************************************************************************

ALTER SESSION SET SQL_TRACE = TRUE

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      1      0.00       0.05          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        1      0.00       0.05          0          0          0           0

Misses in library cache during parse: 0
Misses in library cache during execute: 1
Optimizer goal: CHOOSE
Parsing user id: 35
********************************************************************************

SELECT s.swlogin, DECODE (o.cust_id, NULL, 'Prospect', 'Existing Cust')
campaign,
       COUNT (resp_opportunity_id) total_number
  FROM wsi_opportunity o, sw_specialist s
 WHERE o.resp_date_created BETWEEN SYSDATE - 600 AND SYSDATE
   AND o.opp_quick_note LIKE '%' || TRUNC (SYSDATE)
   AND o.opp_assigned_to_id = s.swspecialistid
   AND s.whdistributionlist =
        (SELECT whdistributionlist
           FROM sw_specialist
          WHERE swlogin = 'Paul Slayer')
 GROUP BY s.swlogin, DECODE (o.cust_id, NULL, 'Prospect', 'Existing Cust')

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.05       0.05          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1    256.78    2690.24     242504    2268378          4           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3    256.83    2690.29     242504    2268378          4           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 35

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  SORT GROUP BY
      0   FILTER
   6613    NESTED LOOPS
   6618     TABLE ACCESS FULL SW_SPECIALIST
  13229     PARTITION RANGE ITERATOR
  33080      TABLE ACCESS BY LOCAL INDEX ROWID WSI_OPPORTUNITY
2816877       INDEX RANGE SCAN (object id 47580)
      1    TABLE ACCESS BY INDEX ROWID SW_SPECIALIST
      1     INDEX RANGE SCAN (object id 45017)

********************************************************************************

BEGIN DBMS_APPLICATION_INFO.SET_MODULE(:1,NULL); END;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.04          0          0          0           0
Execute      1      0.01       0.05          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.01       0.09          0          0          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 35

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.05       0.09          0          0          0           0
Execute      3      0.01       0.10          0          0          0           1
Fetch        1    256.78    2690.24     242504    2268378          4           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        6    256.84    2690.43     242504    2268378          4           1

Misses in library cache during parse: 2
Misses in library cache during execute: 1

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      0      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        0      0.00       0.00          0          0          0           0

Misses in library cache during parse: 0

    3  user  SQL statements in session.
    0  internal SQL statements in session.
    3  SQL statements in session.
********************************************************************************
Trace file: ././vant8i_ora_9218.trc
Trace file compatibility: 7.03.02
Sort options: default

       1  session in tracefile.
       3  user  SQL statements in trace file.
       0  internal SQL statements in trace file.
       3  SQL statements in trace file.
       3  unique SQL statements in trace file.
      58  lines in trace file.

          Followup:

run the tkprof with explain= so I can see what indexes are really used and get
the stats information as well.

The tables/indexes/columns are fully analyzed right?

cost has NO BEARING or relationship to execution time whatsoever.

      Rating:
      3
  March 21, 2002
Reviewer:
      A reader
Tables and indexes are analyzed properly. We use a cost based optimizer.

          Followup:

great, so -- post the tkprofs with explain=user/password

      Rating: 2
Yes,
      but...  March 27, 2002
Reviewer:  Igor
       from France
Hi

 Siebel client sends anonymous block in loop
 and each time is soft parsed which takes 90% of total
 execution time.
 I tried to play with session_cached_cursors as you
 suggested.
 On isolated system I get a bit better parse time, but
 total elapsed time is much the same
 (and sometimes even worse).

 What mesurable improvements you get?
 (I compared values of 0, 50 and 100)

                                       Igor

          Followup:

You should get MUCH better parse time.  Session cached cursors will not affect
run time at all (only tuning the process will do that).

session cached cursors is a softer soft parse, as long as the siebel client
doesn't have more queries then you have set in this setting, it'll "softer" soft
parse it resulting in reduced parse times.

So for example:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table run_stats ( runid varchar2(15),
name varchar2(80), value int );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace view stats
  2  as select 'STAT...' || a.name name, b.value
  3        from v$statname a, v$mystat b
  4       where a.statistic# = b.statistic#
  5      union all
  6      select 'LATCH.' || name,  gets
  7        from v$latch;
View created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_start number;
  3          l_block varchar2(1000) :=
  4          'begin
  5                  null;
  6           end;';
  7  begin
  8      insert into run_stats select 'before', stats.* from stats;
  9      l_start := dbms_utility.get_time;
 10
 11          execute immediate 'alter session set session_cached_cursors=0';
 12      for i in 1 .. 1000
 13      loop
 14                  execute immediate l_block;
 15      end loop;
 16      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 17
 18
 19          insert into run_stats select 'after 1', stats.* from stats;
 20      l_start := dbms_utility.get_time;
 21
 22          execute immediate 'alter session set session_cached_cursors=10';
 23      for i in 1 .. 1000
 24      loop
 25                  execute immediate l_block;
 26      end loop;
 27      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 28
 29      insert into run_stats select 'after 2', stats.* from stats;
 30  end;
 31  /
24 hsecs
19 hsecs

PL/SQL procedure successfully completed.

you can see that by the "wall clock", it is faster however thats only part of
the story (especially in a SINGLE user configuration as you are)

ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value-a.value run1,
c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

NAME                                           RUN1       RUN2       DIFF
---------------------------------------- ---------- ---------- ----------
...
STAT...session cursor cache hits                  0       1002       1002
LATCH.shared pool                              1119        112      -1007
LATCH.library cache                           12306       4280      -8026

23 rows selected.

see those last two rows -- well, latches are LOCKS, locks are serialization
devices.  As the number of users executing in the database goes UP, concurrency
goes DOWN if you lock more.  Anything that uses less latches is more scalable
from that perspective.  If you run this in a database with lots of users using
the session cached cursors -- you'll see a larger overall improvement in parse
times SINCE the parse times in that very active database will be MUCH HIGHER
then in your single user database (you don't have anyone to wait on! )

Hence, your parse times will see more improvement in the "real" database then in
your single user database since the parse times are WORSE there then in your
testing instance.

Runtime, this'll not affect that at all.  Parse time, very much so.

      Rating: 4
What will
      happen if stored procedure is using ref cusrosr   March 27,
      2002
Reviewer:  A reader  from CA USA

If a stored procedure uses ref cursor to return result set to JDBC, We are
finding that same statements are parsed again and again. Do you thing that we
should avoid use of ref cursor ?

          Followup:

search for session_cached_cursors on this site.

with JDBC, you cannot avoid the ref cursor as that is the method to return
result sets from stored procedures.

      Rating:
      5
  March 27, 2002
Reviewer:  keef

so with jdbc + ref cursors, does that mean that since we do at least the soft
parse every time, it does not matter if we reuse the PreparedStatement or
re-prepare every execute (so long as we use the same connection/session, with
session_cached_cursors set)? in other words, does it not help to "prepare once,
execute many times" in this scenario?

tia.

          Followup:

You still want to prepare once, execute many whenever you can -- always.

      Rating: 5
A
      Reader  March 28, 2002
Reviewer:  Drew
       from Lansdale PA
I am using a third part tool - TOAD.  In the Server Stats window, there is a
stat called parse/execute ratio =170 and a warning sign, saying "high parse to
execute ratio".  In my init.ora file, "session_cached_cursors = 0".  None
complains about the performance, the database has been running fine with the
warning sign for years.  However, the warning sign makes me think of some tuning
work.  Where should I start with?  Do you think the "kinder and softer" parse
will help if I change the session_cached_cursors to a nonezero value?  I thought
the application programs can also do some tuning.  In the applications, there is
not a single procedure or function, just SQL.  Since none complaining, the
applcation prgrammers may not want to do any changes.
Thanks a lot.

          Followup:

session cached cursors will not reduce the parse/execute ratio (a ratio over
100% is horrible! it means people parse and never execute!!) but it CAN and will
reduce the load on the server.

The soft parse count will still increase, but so will the session cursor cache
hits  stat.  Everytime that one goes up -- its like you reduces the soft parse
count down.

So, yes, I would give serious consideration to setting that parameter with this
caveat "sometimes, if it ain't broke, don't fix it".  It could result in
increased memory consumption on the server (for the cached cursors) and that
could have an impact (negative) on performace.  So, as with any change -- be
watchful (and use statspack to measure performance BEFORE and AFTER the change
-- be ready to measure the change so you can tell if it is buying you anything)

      Rating: 5
How to set the
      session_cached_cursors parameter  April 02, 2003

Reviewer:  Juan Carlos Reyes P  from Bolivia

Hi tom based on what I read in the documentation, I did this two queries to
evaluate if this parameter is properly set.
Are they right or there is another way to evaluate this parameter, once this
parameter had been set.

Thanks :)

1)
SELECT SID, "cursorcachecount-parvalue"
FROM
(  SELECT C.SID,
                    'Increase the current value in session_cached_cursors if '
|| (C.VALUE) ||
                    ' is near to ' ||
                    D.VALUE "cursorcachecount-parvalue"
FROM V$SESSTAT C,V$PARAMETER d
WHERE C.statistic# = 207 -- = 'session cursor cache count'
              AND   D.NAME = 'session_cached_cursors'
ORDER BY C.VALUE DESC
)
WHERE ROWNUM < 10 -- First 10 cases

---------------------------------------------------------------------------------
----------
 009 Increase the current value in session_cached_cursors if 21 is near to 100
 028 Increase the current value in session_cached_cursors if 15 is near to 100

2)
SELECT 'Soft Parse%:' ||
    TO_CHAR(
     ROUND (1- ( (select value from V$SYSSTAT WHERE NAME = 'parse count (hard)')

      / (select value from V$SYSSTAT WHERE NAME = 'parse count (total)')),2)*100

        ,'990D00') || '; Always when this total is not a small number: ' ||
    (select value from V$SYSSTAT WHERE NAME = 'parse count (total)')
FROM DUAL;
 -----------------
Soft Parse%: 94.00; Always when this total is not a small number: 7820

          Followup:

1) never use numbers on statistics -- you'll get burned across os's /
versions...

all that is saying is that "at that point in time, looks good".

You might be able to better automate this with a logoff trigger.  during normal
peak time, have a logoff trigger that captures for the current session the
session cursor cache count statistic and saves it.  Then, just take the average
of those and compare to the set value and see if they make sense together.

2) i don't understand that one.  you need to look at the parse execute ratio,
not the soft parse % here.  soft parse %, good for cursor sharing consideration.
 poor parse execute ratio might indicate you need session cached cursors.

      Rating:
      5
  April 02, 2003
Reviewer:
      A reader
Can you please tellme if now they are correct.

1)
CREATE TABLE SYS.Stat_Session_Historic
(
 UUSER VARCHAR2(100),
 DDATE DATE,
 Sstatistic# NUMBER (6),
 VVALUE      NUMBER (6)
)
;

CREATE OR REPLACE TRIGGER SYS.TGR_LOGOFF_STATS
 BEFORE
 LOGOFF
 ON DATABASE

INSERT INTO SYS.Stat_Session_Historic
SELECT USER, SYSDATE, statistic# , VALUE
FROM V$SESSTAT C
WHERE C.statistic# = 207 -- = 'session cursor cache count'
      AND   C.SID = UID
/

-- And the select will be the first 10 of an
   average of their statics in a period of time for user

SELECT UUSER, AVGVAL
FROM
(  SELECT UUSER, AVG( VVALUE ) AVGVAL
FROM sys.Stat_Session_Historic
where TRUNC(DDATE) = TRUNC(SYSDATE)
GROUP BY UUSER
ORDER BY 2 DESC
)
WHERE ROWNUM < 10 -- First 10 cases

UUSER   AVGVAL
--------------
ADM    5
SAF    1

2)
SELECT 'Soft Parse%:' ||
    TO_CHAR(
     ROUND (1- ( (select value from V$SYSSTAT WHERE NAME = 'parse count (hard)')

      / (select value from V$SYSSTAT WHERE NAME = 'parse count (total)')),2)*100

        ,'990D00') || '; If is far to 100%, and this total count parse number is
not small ' ||
    (select value from V$SYSSTAT WHERE NAME = 'parse count (total)') || '
increase the parameter '
FROM DUAL;
 -----------------
Soft Parse%: 94.00; If is not near to 100%, and this total count parse number is
not small: 7820 increase the parameter

Thanks

          Followup:

Nope -- totally wrong.

You are creating tables owned by sys.  bad idea.  don't do that.

I did not read past:

1)
CREATE TABLE SYS.Stat_Session_Historic

that SYS. stopped me right away.

      Rating:
      5
But  April 03, 2003
Reviewer:  O
       from Europe Endless
Tom

You stated above that a query may be in the "shared POO"
Is there any way this can be flushed?

          Followup:

but what?

a query plan may be in the shared pool -- yes.

the shared pool may be flushed using the alter system flush shared_pool;

if the stored query plan is not being used and can be flushed, it will be
(meaning, it of course might NOT be as well)

don't do that on your live system, it'll impact performace somewhat.

      Rating: 4
What is the
      downsize of using session_cached_cursors   April 03, 2003

Reviewer:  Shailandra  from PA
Hi Tom,

What are the circumstances this parameter should not be used.

Thanks

          Followup:

don't know of any.

      Rating: 5
Queries to
      evaluate the value in session_cached_cursors  April 03, 2003

Reviewer:  Juan Carlos Reyes Pacheco  from Bolivia

Sorry, I used SYS, because this avoid granting privileges, and because I'm in a
test database,
:) Now there is no SYS.
Could you please tellme if now both query are right?
And if not, how they should be
thanks
1)
CREATE TABLE Stat_Session_Historic
(
 UUSER VARCHAR2(100),
 DDATE DATE,
 Sstatistic# NUMBER (6),
 VVALUE      NUMBER (6)
)
;

CREATE OR REPLACE TRIGGER TGR_LOGOFF_STATS
 BEFORE
 LOGOFF
 ON DATABASE

INSERT INTO Stat_Session_Historic
SELECT USER, SYSDATE, statistic# , VALUE
FROM V$SESSTAT C
WHERE C.statistic# = 207 -- = 'session cursor cache count'
      AND   C.SID = UID
/

-- And the select will be the first 10 of an
   average of their statics in a period of time for user

SELECT UUSER, AVGVAL
FROM
(  SELECT UUSER, AVG( VVALUE ) AVGVAL
FROM Stat_Session_Historic
where TRUNC(DDATE) = TRUNC(SYSDATE)
GROUP BY UUSER
ORDER BY 2 DESC
)
WHERE ROWNUM < 10 -- First 10 cases

UUSER   AVGVAL
--------------
ADM    5
SAF    1

2)
SELECT 'Soft Parse%:' ||
    TO_CHAR(
     ROUND (1- ( (select value from V$SYSSTAT WHERE NAME = 'parse count (hard)')

      / (select value from V$SYSSTAT WHERE NAME = 'parse count (total)')),2)*100

        ,'990D00') || '; If is far to 100%, and this total count parse number is

not small ' ||
    (select value from V$SYSSTAT WHERE NAME = 'parse count (total)') || '
increase the parameter '
FROM DUAL;
 -----------------
Soft Parse%: 94.00; If is not near to 100%, and this total count parse number is

not small: 7820 increase the parameter

Thanks

          Followup:

1) nope, you are using a statistic# = 207.

  1* select * from v$statname where statistic# = 207
ops$tkyte@ORA920> /

STATISTIC# NAME                                CLASS
---------- ------------------------------ ----------
       207 cursor authentications                128

ops$tkyte@ORA817DEV> select * from v$statname where statistic# = 207
  2  /

STATISTIC# NAME                                CLASS
---------- ------------------------------ ----------
       207 PX remote messages recv'd              32

ops$tkyte@ORA9I> select * from v$statname where statistic# = 207
  2  /

STATISTIC# NAME                                CLASS
---------- ------------------------------ ----------
       207 PX remote messages sent                32

ops$tkyte@ORA815> select * from v$statname where statistic# = 207
  2  /

STATISTIC# NAME                                CLASS
---------- ------------------------------ ----------
       207 OS Swaps                               16

      Rating: 5
Methinks there
      is a stink...  April 03, 2003
Reviewer:  Mr
      Kleen  from The Bleaches
Tom, I think Mr or Mrs O were having a joke at your mistyping of "Shared Poo"..a
Poo on this side of the world is another name for a Jobbie, or to put it another
way a number 2, or yet another way faeces!

Therefore I was quite relieved to see that it could be flushed!!!  ;)

          Followup:

ah hah, i didn't even get it.  now I see it, i left the l off and they want to
flush the poo.  got it ;)

      Rating: 5
back
      again  April 03, 2003
Reviewer:  Juan Carlos
      Reyes P  from Bolivia
Thanks good point I didn't know that stats# could change from release to
realease
It was 206 not 207 FIXED

Well we're going back again.

1)
CREATE TABLE Stat_Session_Historic
(
 UUSER VARCHAR2(100),
 DDATE DATE,
 SstatisticName VARCHAR2 (200),
 VVALUE      NUMBER   (6)
)
;

CREATE OR REPLACE TRIGGER TGR_LOGOFF_STATS
 BEFORE
 LOGOFF
 ON DATABASE

INSERT INTO Stat_Session_Historic
SELECT USER, SYSDATE, 'session cursor cache count', VALUE, C.SID
FROM V$SESSTAT C
WHERE C.statistic# = (select STATISTIC# from v$statname where name = 'session
cursor cache count')
      AND   C.SID = (SELECT SID FROM V$SESSION WHERE USER#= UID )
/

-- And the select will be the first 10 of an
   average of their statics in a period of time for user

SELECT UUSER, AVGVAL
FROM
(  SELECT UUSER, AVG( VVALUE ) AVGVAL
FROM Stat_Session_Historic
where TRUNC(DDATE) = TRUNC(SYSDATE)
GROUP BY UUSER
ORDER BY 2 DESC
)
WHERE ROWNUM < 10 -- First 10 cases

UUSER   AVGVAL
--------------
ADM    5
SAF    1

2)
SELECT 'Soft Parse%:' ||
    TO_CHAR(
     ROUND (1- ( (select value from V$SYSSTAT WHERE NAME = 'parse count (hard)')

      / (select value from V$SYSSTAT WHERE NAME = 'parse count (total)')),2)*100

        ,'990D00') || '; If is far to 100%, and this total count parse number is

not small ' ||
    (select value from V$SYSSTAT WHERE NAME = 'parse count (total)') || '
increase the parameter '
FROM DUAL;
 -----------------
Soft Parse%: 94.00; If is not near to 100%, and this total count parse number is

not small: 7820 increase the parameter

Thanks

          Followup:

well, v$mystat might be easier:

ps$tkyte@ORA817DEV> select * from v$mystat
  2  where statistic# = (select statistic# from v$statname where name =
  3  'session cursor cache count');

       SID STATISTIC#      VALUE
---------- ---------- ----------
        15        192          0

but yes, that would capture it.

2) I still don't get the relevance of this.  This tells me you have too many
hard parses, but they cannot be fixed with session cached cursors.

      Rating:
      5
  April 03, 2003
Reviewer:
      juancarlosreyesp@yahoo.com  from Bolivia
Thanks 1) OK
About number 2)
Soft parse ratio: This shows whether there are many hard parses on the system.
The ratio should be compared to the raw statistics to ensure accuracy. For
example, a soft parse ratio of 0.2 typically indicates a high hard parse rate.
However, if the total number of parses is low, then the ratio should be
disregarded.

1 - ( parse count (hard) / parse count (total) )

I agree, but as it is called soft parse ratio I thougth it could have some
relation with it.
When i sent my statspack report to www.oraperf.com they suggested me to increase
sessoin_cached_cursors, I supposed it was because this ratio.
Wich other ratio or method do you suggest , to evaluate this parameter?
Thanks :)

          Followup:

Think about it -- how could session cached cursors help a hard parse problem??

It cannot -- by definition you hard parse when?  when the sql never existed
before.

session cached cursors cannot help a hard parse problem.  cursor_sharing could,
but not cached cursors.

My question to you is -- of what relevance to session cached cursors could the
soft parse ratio possibly be?

      Rating: 5
other methods
      for tuning session cached cursors  April 03, 2003

Reviewer:  juancarlosreyesp@yahoo.com  from Bolivia

:) Thanks
My question to you is -- of what relevance to session cached cursors could the
soft parse ratio possibly be?

You are rigth, no relevance of softparse ratio to session cached cursors.

the question is now: is there a ratio or another method to measure the need of
tunning  session cached cursors.

Thanks

P.D. other than this that you explained in other question.

call     count       cpu    elapsed       disk     query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        3      0.80       1.72          0          0          0           0
Execute      3      0.00       0.00          0          0          0           0
Fetch        3      0.02       0.05          2        665          0          14
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        9      0.82       1.77          2        665          0          14
Misses in library cache during parse: 0
In this statistics we can see the is too soft parsing, parse = execute.
In a well tuned application
parse = 1,   execute = some number greater than or equal to 1
The best way to speed something up is to NOT do it.  Hence, don't parse 3 times,

just parse 1 time.

          Followup:

if you find yourself parsing the same statement more then 3 times and you really
cannot fix the code, session cached cursors can be of some assistance.  if you
do not, it will not help nor hurt.

      Rating:
      5
  April 03, 2003
Reviewer:
      A reader
thanks

      Rating: 5
Default value
      for session_cached_cursors  April 03, 2003

Reviewer:  me again ;) sorry
The default value is 0, but I read that is advisable to set it to 2.
Even if you think you don't need it.
Is this rigth.

          Followup:

where did you read it and what justification did they give.

      Rating:
      5
  April 03, 2003
Reviewer:
      A reader
:( Sorry, I forgot where I read but the reason was this

session cursor cache count
Total number of cursors cached. This statistic is incremented only if
SESSION_CACHED_CURSORS > 0

I was wondering how much memory it could take in an OLTP, but this could be
reasonable for a DSS

      Rating: 5
I got I got I
      got I got I got :)  April 04, 2003
Reviewer:
      Juan Carlos Reyes P.  from Bolivia
If you were thinking I was joking o I was not enough serious in my questions.
Here it is
the text and the link, as I had been reading a lot of it, I didn't remember
well. It tookme a time to find it again.

page

http://www.ixora.com.au/scripts/library.htm
Text
The session cursor cache is an important facility for reducing load on the
library cache. In our opinion, the session_cached_cursors parameter should
always be set to at least 2. However, a larger value is normally beneficial.

first) Question setting to 2 is not too much memory for a big OLTP. Which in
your opinion should be the default value for this parameter.
second)
Is this sql advisable to measure the value of this parameter

http://www.ixora.com.au/scripts/sql/session_cursor_cache.sql
Thanks

          Followup:

if steve adams said it, it is more then likely "true".

as they said -- a larger is normally beneficial.  I am partial (opinion, no true
science here) to 100.

the script looked reasonable to me

      Rating:
      5
  April 04, 2003
Reviewer:
      A reader
Thanks.

      Rating:
      5
CLOSE_CACHED_OPEN_CURSORS   April 07, 2003

Reviewer:  Juan Carlos Reyes Pacheco  from Bolivia

Hi Tom
Could you please explainme a usefull use to this parameter, specially for
developing in Oracle Forms.

executing the command
ALTER SESSION SET CLOSE_CACHED_OPEN_CURSORS ...

Or is this obsolete?

Thanks

          Followup:

very much obsolete.

      Rating:
      5
  April 07, 2003
Reviewer:
      A reader
Thanks :)

April 07, 2003 -- Thanks for the question regarding "IN vs. =",
      version 8.1.7

      You Asked
Suppose I have a unique index on a varchar2 column and queries like

1. column='value'
2. column in ('1')
3. column in ('1','2','3')
4. column like '1%'
5. column like '%abcd%'

The execution plan for (1) and (2) should be the same, right? i.e. a index
unqiue scan. But I find that (2) does a range scan even though it has only value

How is (3) treated by the CBO vs. RBO? CBO would use decompose it into 3 queries
using each of the 3 values in the IN and combine it. RBO would probably do a
range scan. Which is more efficient?

Same question (RBO vs. CBO) for (4)

Is there any way that (5) can be made to use the index? Even a index FFS would
be faster than FTS on the table since my table is really large.

      and we said...
in my testing 1 and 2 result in  the same plan.  consider this setup:

create table t
as
select a.*
  from all_objects a
/

alter table t add constraint t_pk primary key(object_id)
/
create index t_idx on t(object_name)
/

analyze table t compute statistics
for table
for all indexes
for all indexed columns
/

Now, tkprof shows me:

select /*+ RULE */ * from t  where object_id = 55

Misses in library cache during parse: 1
Optimizer goal: RULE
Parsing user id: 104

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID T (cr=3 r=0 w=0 time=40 us)
      1   INDEX UNIQUE SCAN T_PK (cr=2 r=0 w=0 time=21 us)(object id 36890)
********************************************************************************
select /*+ RULE */ * from t  where object_id in ( 55 )

Misses in library cache during parse: 1
Optimizer goal: RULE
Parsing user id: 104

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID T (cr=3 r=0 w=0 time=53 us)
      1   INDEX UNIQUE SCAN T_PK (cr=2 r=0 w=0 time=31 us)(object id 36890)

Instead of guessing what the RBO and CBO will do -- benchmark it and see.  You
got it backwards actually -- RBO does three queries, CBO one.  consider:

select /*+ RULE */ * from t  where object_id in ( 55, 56, 57 )

Misses in library cache during parse: 1
Optimizer goal: RULE
Parsing user id: 104

Rows     Row Source Operation
-------  ---------------------------------------------------
      3  CONCATENATION  (cr=9 r=0 w=0 time=136 us)
      1   TABLE ACCESS BY INDEX ROWID T (cr=3 r=0 w=0 time=57 us)
      1    INDEX UNIQUE SCAN T_PK (cr=2 r=0 w=0 time=31 us)(object id 36890)
      1   TABLE ACCESS BY INDEX ROWID T (cr=3 r=0 w=0 time=30 us)
      1    INDEX UNIQUE SCAN T_PK (cr=2 r=0 w=0 time=16 us)(object id 36890)
      1   TABLE ACCESS BY INDEX ROWID T (cr=3 r=0 w=0 time=18 us)
      1    INDEX UNIQUE SCAN T_PK (cr=2 r=0 w=0 time=9 us)(object id 36890)
********************************************************************************
select * from t  where object_id in ( 55, 56, 57 )

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 104

Rows     Row Source Operation
-------  ---------------------------------------------------
      3  INLIST ITERATOR  (cr=10 r=0 w=0 time=212 us)
      3   TABLE ACCESS BY INDEX ROWID T (cr=10 r=0 w=0 time=175 us)
      3    INDEX RANGE SCAN T_PK (cr=7 r=0 w=0 time=117 us)(object id 36890)

And the CBO is the better of the two at pretty much all queries....

As for the last one -- be careful what you wish for:

********************************************************************************
BEGIN :x := '%A%'; END;
********************************************************************************
select /*+ RULE */ * from t  where object_name like '%ABC%'
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      0.03       0.03         12        423          0          17

Rows     Row Source Operation
-------  ---------------------------------------------------
     17  TABLE ACCESS FULL T (cr=423 r=12 w=0 time=38263 us)
********************************************************************************
select * from t  where object_name like '%ABC%'
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      0.04       0.03         12        423          0          17

Rows     Row Source Operation
-------  ---------------------------------------------------
     17  TABLE ACCESS FULL T (cr=423 r=12 w=0 time=36812 us)
********************************************************************************
select /*+ RULE */ * from t  where object_name like :x
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      632      0.17       0.17         36       6785          0        9432

Rows     Row Source Operation
-------  ---------------------------------------------------
   9432  TABLE ACCESS BY INDEX ROWID T (cr=6785 r=36 w=0 time=124023 us)
   9432   INDEX RANGE SCAN T_IDX (cr=777 r=0 w=0 time=59660 us)(object id 36891)
********************************************************************************
select * from t  where object_name like :x
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      632      0.18       0.17         23       6785          0        9432

Rows     Row Source Operation
-------  ---------------------------------------------------
   9432  TABLE ACCESS BY INDEX ROWID OBJ#(36889) (cr=6785 r=23 w=0 time=123676
us)
   9432   INDEX RANGE SCAN OBJ#(36891) (cr=777 r=0 w=0 time=60363 us)(object id
36891)

but you see how to get your wish.
Review &
      Followup

      Rating: 4
Further on
      INLIST OPERATOR; LIKE predicate  April 07, 2003

Reviewer:  Alberto Dell'Era  from Milan, Italy

Tom,

the docs say (Performance Tuning Guide)

"The INLIST ITERATOR operation iterates over the operation below it for each
value in the IN-list predicate"

so,

1) isn't the plan chosen by the CBO above very
similar to the one with the CONCATENATION chosen
by the RBO ? Same access path to the data through
the index, three times - so may you please explain
why is the former more efficient than the latter ?

2) about the query with the LIKE predicate: do you
happen to know why the one with the bind variable chooses
the index ?

AFAIK, with the bind, since the optimizer doesn't know
the actual value of the like pattern it must generate
a plan good 'on average'.
It seems to me that the optimizer is betting on the fact that
the like pattern will be something like 'ABC%', and so
that it will be able to use the index efficiently (hence the
plan to use an INDEX RANGE SCAN).
Am i right ?

Thanks in advance
Alberto

          Followup:

1) very similar but not as "sophisticated". More plans are available to the CBO
using the inlist iterator then a simple "concatenation".  As queries get more
complex, this becomes obvious.  Consider:

ops$tkyte@ORA920> create table t as select * From all_objects;

Table created.

ops$tkyte@ORA920> alter table t add constraint t_pk primary key(object_id);

Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t2 as select * from all_users;

Table created.

ops$tkyte@ORA920> alter table t2 add constraint t2_pk primary key(username);

Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select *
  2    from ( select owner, object_id,
  3                row_number() over ( partition by owner order by object_id )
rn
  4           from t )
  5   where rn <= 2
  6  /

OWNER                           OBJECT_ID         RN
------------------------------ ---------- ----------
A                                   34005          1
...
SSW                                 32262          1
SSW                                 32263          2
SYS                                     3          1
SYS                                     4          2
SYSTEM                               1936          1
SYSTEM                               1938          2
WKSYS                               28392          1
WKSYS                               28393          2
WMSYS                                5987          1
WMSYS                                5988          2
...

64 rows selected.

ops$tkyte@ORA920> alter session set events '10046 trace name context forever,
level 12';

Session altered.

ops$tkyte@ORA920> select /* RBO */ t.object_name,  t2.created from t, t2
  2  where t.object_id in
  3  (32262, 32263, 3, 4, 1936, 1938,
  4   28392, 28393, 5987, 5988 )
  5  and t.owner = t2.username;

OBJECT_NAME                    CREATED
------------------------------ ---------
WM$LOCK_TABLE_TYPE             07-MAR-03
WM$LOCK_INFO_TYPE              07-MAR-03
/56e69815_InsoFilter           07-MAR-03
META-INF/MANIFEST.MF           07-MAR-03
CATALOG                        07-MAR-03
SYSCATALOG                     07-MAR-03
TAB$                           07-MAR-03
I_OBJ#                         07-MAR-03
ORDER_ITEMS                    27-MAR-03
ORDERS                         27-MAR-03

10 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table t compute statistics for table for all indexes
for all indexed columns;

Table analyzed.

ops$tkyte@ORA920> analyze table t2 compute statistics for table for all indexes
for all indexed columns;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select /* CBO */ t.object_name,  t2.created from t, t2
  2  where t.object_id in
  3  (32262, 32263, 3, 4, 1936, 1938,
  4   28392, 28393, 5987, 5988 )
  5  and t.owner = t2.username;

OBJECT_NAME                    CREATED
------------------------------ ---------
TAB$                           07-MAR-03
I_OBJ#                         07-MAR-03
CATALOG                        07-MAR-03
SYSCATALOG                     07-MAR-03
WM$LOCK_TABLE_TYPE             07-MAR-03
WM$LOCK_INFO_TYPE              07-MAR-03
/56e69815_InsoFilter           07-MAR-03
META-INF/MANIFEST.MF           07-MAR-03
ORDER_ITEMS                    27-MAR-03
ORDERS                         27-MAR-03

10 rows selected.

tkprof says:

select /* RBO */ t.object_name,  t2.created from t, t2
where t.object_id in
(32262, 32263, 3, 4, 1936, 1938,
 28392, 28393, 5987, 5988 )
and t.owner = t2.username

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          1          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00         11         50          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.01         11         51          0          10

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  CONCATENATION  (cr=50 r=11 w=0 time=2107 us)
      1   NESTED LOOPS  (cr=5 r=2 w=0 time=449 us)
      1    TABLE ACCESS BY INDEX ROWID T (cr=3 r=2 w=0 time=370 us)
      1     INDEX UNIQUE SCAN T_PK (cr=2 r=1 w=0 time=243 us)(object id 34133)
      1    TABLE ACCESS BY INDEX ROWID T2 (cr=2 r=0 w=0 time=41 us)
      1     INDEX UNIQUE SCAN T2_PK (cr=1 r=0 w=0 time=18 us)(object id 34135)
......
      1     INDEX UNIQUE SCAN T2_PK (cr=1 r=0 w=0 time=12 us)(object id 34135)
      1   NESTED LOOPS  (cr=5 r=0 w=0 time=40 us)
      1    TABLE ACCESS BY INDEX ROWID T (cr=3 r=0 w=0 time=18 us)
      1     INDEX UNIQUE SCAN T_PK (cr=2 r=0 w=0 time=9 us)(object id 34133)
      1    TABLE ACCESS BY INDEX ROWID T2 (cr=2 r=0 w=0 time=12 us)
      1     INDEX UNIQUE SCAN T2_PK (cr=1 r=0 w=0 time=6 us)(object id 34135)
********************************************************************************
select /* CBO */ t.object_name,  t2.created from t, t2
where t.object_id in
(32262, 32263, 3, 4, 1936, 1938,
 28392, 28393, 5987, 5988 )
and t.owner = t2.username

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.09          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0         34          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.09          0         34          0          10

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 83

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  HASH JOIN  (cr=34 r=0 w=0 time=2718 us)
     10   INLIST ITERATOR  (cr=30 r=0 w=0 time=569 us)
     10    TABLE ACCESS BY INDEX ROWID OBJ#(34132) (cr=30 r=0 w=0 time=446 us)
     10     INDEX RANGE SCAN OBJ#(34133) (cr=20 r=0 w=0 time=282 us)(object id
34133)
     36   TABLE ACCESS FULL OBJ#(34134) (cr=4 r=0 w=0 time=206 us)

so, large decrease in LIO's here -- also, see how the CBO plan lets the
optimizer treat the entire SET as just that -- a nice SET.  It is not a union
all of lots of queries -- it is a single set -- hash join and away we go.

2) the optimizer makes a reasonable "assumption" that at runtime -- :x will
possibly not be something that starts with '%'.  It generates generic plan that
works best in most cases.

      Rating: 3
What is
      LIO?  April 07, 2003
Reviewer:
      A reader
What is LIO?

Why do you say that the TKPROF for the CBO looks better? The elapsed time is
more for CBO (0.09) versus 0.01 for RBO.

          Followup:

LIO = logical io.

the elapsed time are ignorable here.  run it 100 times and they'll be totally
different each time.  Here, this is totally UNDOCTORED, just cut and pasted:

select /* RBO */ t.object_name,  t2.created from t, t2
where t.object_id in
(32262, 32263, 3, 4, 1936, 1938,
 28392, 28393, 5987, 5988 )
and t.owner = t2.username

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0         50          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0         50          0          10

select /* CBO */ t.object_name,  t2.created from t, t2
where t.object_id in
(32262, 32263, 3, 4, 1936, 1938,
 28392, 28393, 5987, 5988 )
and t.owner = t2.username

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0         34          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0         34          0          10

The elapsed time of 0.09 given a cpu time of 0.00 indicates there was some wait
going one -- eg: my machine was doing something at that instant.  Run these 100
times and you'll see that sometimes CPU exceeds elapsed (thats not possible but
it happens) and vice versa.  Sometimes RBO goes faster, sometimes slower.

The bottom line is:

each LIO takes some latching.
latches are a type of lock
locks imply serialization
serialization implies less scalability.

If I can get something to use 34/50'ths of the LIO -- thats a good thing.  good
for single user performance and even better as you scale up.

      Rating:
      5
Thanks  April 08, 2003
Reviewer:
      Alberto Dell'Era  from Milano, Italy
Thanks Tom - excellent as always!

April 07, 2003 TARIQ NOOR -- Thanks for the question regarding
      "Store and Run a Wav & AVI file in Database", version 8.1.6

      You Asked
I am designing an answring machine and message server to store phone class and
peoples audio and video messages.

Will you tell me that how i can do the following

1. Store my recorded WAV/AVI files in Database
2. Run the files with browser plugin on single button click

I am using ASP as front-end.

Thanks in Advance

      and we said...
See the intermedia Audio/Video documentation:

http://technet.oracle.com/doc/oracle8i_816/inter.816/index.htm
that is the technology we provide for this.

also see

http://technet.oracle.com/sample_code/products/intermedia/sample_code_index.htm
for examples
Review & Followup

      Rating: 3
It's
      Good.  January 23, 2002
Reviewer:  Rahul
      Sharma  from India
Your answer was helpful for me also.

But Tom I also want to know that how to create our own plug-ins in intermedia.
I want to create a new plug-in that supports an unsupported format of audio
(e.g. .rm files-real player files).

Pls. tell me how we can create as well as install it in database.
Pls. keep in attention that I'm using Oracle9i.

Thanks in advance.

          Followup:

We do not support that capability.

      Rating: 5
Audio
      Files  April 07, 2003
Reviewer:  MO

TOm:

1.  What is the best way in oracle to store audio files.  Would you use blob
data type.   Would that be a pointer to the file on the O/S or the file is
actually in the database.

2.  I have some XML fomratted files. What data type would you store these in?
Would you store the tags or just the data and then parse them back to XML to
pull it out of the database?

3.  Do I need INTERMEDIA for storing audio? or only for searching/indexing?

Thank you

          Followup:

1) I store everything in the database personally.  And a blob would be the only
type.  And I would actually use:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/inter.817/index.htm
those specialized types.

2) see

http://otn.oracle.com/tech/xml/content.html
for all things XML.

3) need, no, want, yes.

      Rating:
      5
audio  April 07, 2003
Reviewer:  mo

Tom:

1.  IS there any pros/cons to use BLOB versus BFILE other than you can
manipluate a BLOB and not a BFILE.

2.  I went to the link for specialized type but I did not understand where it
applies here. it is about intermedia, spatial, or time series?

3.  For the XML I am not trying to generate XML out of the database using XDK.
I already have an XML file and wodnering how best to store it and retrieve it.
SHall I store all of it with tags in a long or varchar2 column and then when I
retrieve it convert it to HTML using XSL.

Thank you

          Followup:

1) and you have backup and recovery, and you have security, and you have data
integrity, yadda yadda yadda.  Everything you buy a database for..

2)  Oracle interMedia Audio, Image, and Video User's Guide and Reference is what
you are interested in...

3) That is what that link talks about -- XML in the database.

      Rating:
      5
audio  April 07, 2003
Reviewer:  mo

Tom:

1.  Are you refering to the story : Dynamic News with XML DB.

I can not find a story "XML in the database".

          Followup:

read past the first headline -- look for things like "XML DB", XDK for <language
-- including plsql>, white paper on architecture, etc.

there are lots of links on that there page, all of them about XML -- in Oracle
-- in the database pretty much.  That is where XML belongs, in a database, after
all.

April 08, 2003 rekha -- Thanks for the question regarding "stats
      and truncate", version 8.1.7

      You Asked
what happens to the statistics of the tables after truncating
the
table.

      and we said...
they stay until you delete them or recompute them:

ops$tkyte@ORA8I.WORLD> select num_rows, last_analyzed
  2  from user_tables
  3  where table_name = 'T'
  4  /

  NUM_ROWS LAST_ANAL
---------- ---------

ops$tkyte@ORA8I.WORLD> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA8I.WORLD> select num_rows, last_analyzed
  2  from user_tables
  3  where table_name = 'T'
  4  /

  NUM_ROWS LAST_ANAL
---------- ---------
        25 05-FEB-01

ops$tkyte@ORA8I.WORLD> truncate table t;

Table truncated.

ops$tkyte@ORA8I.WORLD> select num_rows, last_analyzed
  2  from user_tables
  3  where table_name = 'T'
  4  /

  NUM_ROWS LAST_ANAL
---------- ---------
        25 05-FEB-01

ops$tkyte@ORA8I.WORLD>
Review & Followup

      Rating: 5
Reason behind
      not deleting statistics  April 07, 2003
Reviewer:
      Logan Palanisamy  from Santa Clara, CA USA
Tom,

What could be the reason for not deleting statistics when truncating a table?
Isn't it logical and meaningful to delete the statistics also?

Thanks.

          Followup:

no, you might just be reloading very similar data and want to skip the
analyze...

      Rating:
      4
  April 07, 2003
Reviewer:
      A reader

      Rating: 4
ANALYZE
        April 07, 2003
Reviewer:  umesh  from
      bangalore india
are the statistics used by rule based optimizer? when i contacted my dba he says
they are needed only wen you use rbo and not cbo ? what does oracle do with
statistics
thanks for your patience

          Followup:

thats backwards.

stats are used only by the CBO, the RBO will never use a single statistic

April 08, 2003 wallacel -- Thanks for the question regarding
      "fragment issue", version 8.1.6

      You Asked
Tom:

when i do query like below, i got the result that not look
good, how to improve or correct it?

select segment_name,extents from dba_segments where extents >30

SEGMENT_NAME                                EXTENTS
---------------------------------------- ----------
SOURCE$                                         213
IDL_UB1$                                        668
IDL_UB2$                                         56
DEPENDENCY$                                      36
I_SOURCE1                                       102
I_DEPENDENCY1                                    32
I_DEPENDENCY2                                    32
SYS_LOB0000021814C00002$$                        85

select count(0) from dba_segments where extents>5

  COUNT(0)
----------
        43

      and we said...
It is 'better' then mine where I have 90 objects with more then
30 and

ops$tkyte@ORA8I.WORLD> select count(0) from dba_segments where
extents>5
  2  /

  COUNT(0)
----------
       235

In fact, some of mine are nearing 1,000 extents:

SEGMENT_NAME                      EXTENTS
------------------------------ ----------
ARGUMENT$                              97
SOURCE$                               540
IDL_UB1$                              895
....

and I am perfectly happy.  If having hundreds of extents was a
problem -- I would not be happy.  Since having lots of extents
is not a problem, I have no problems with this situation.

This is not fragmentation.  Fragmentation is when you have lots
of irregular "holes" in a tablespace.  In your system tablespace
-- since space is never released, only allocated, you cannot
have fragmentation.  You do not have any HOLES in system -- just
lots of stuff.

It should NEVER be your goal to absolutely minimize extents.
That is a myth.  Your situation above looks fine.

Review &
      Followup

      Rating: 4
Fragmentation
      and performance  December 25, 2002
Reviewer:
      Reader  from Atlanta
"Fragmentation is when you have lots of irregular
"holes" in a tablespace"...

Does fragmentation affect performance? Or only space utilization?

Thanks

          Followup:

space utilization.

      Rating: 3
Fragmentation -
      Performance  December 26, 2002
Reviewer:
      Bala  from KL, Malaysia
Doesn't bringing data into one extent increase performance as all the data of
the segment is available at one place (sequentially). Supposing an operation
that needs to sort the whole data of the table (select * from all_objects order
by object_name). Please explain.

      Rating: 3
Sorry for the
      posting above, I got the details in other threads  December
      26, 2002
Reviewer:  Bala  from KL, Malaysia

Please go to

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1667293495336
for the above posting.
Sorry again. Would be doubly sure the next time

      Rating: 4
fragmentation
      and performance  April 08, 2003
Reviewer:  Anand
      Gopalan  from Chennai,India
"Does fragmentation affect performance? Or only space utilization?"

I expected a answer "BOTH". You said "only space utilization".

I thought it will impact the select queries  since the data now has to be
fetched across mutliple blocks.

In a similar way it would impact the insert/update/delete as well as these
operations span across fragmented rows.

Are my assumptions right?

Thanks for your time

          Followup:

need to reread:

<quote>
This is not fragmentation.  Fragmentation is when you have lots of irregular

"holes" in a tablespace.  In your system tablespace -- since space is never
released, only allocated, you cannot have fragmentation.  You do not have any
HOLES in system -- just lots of stuff.
</quote>

and no, having lots of extents does not affect DML performance.  But this was
NOT about fragmentation at all.

April 08, 2003 Bala -- Thanks for the question regarding
      "Storing rowids in table columns", version 9i R2

      You Asked
Tom,

Thanks for your reply to my prevoius question.
I have two questions to ask.

1. On one of the application they are storing rowids in
   the table itself. They compare this table rowid column with
   another table's rowid.
   The only reason i heard from the developer was , using rowid is
   for faster performance.

   One of the query is as shown below :

   select count(*) from table1 A, table2 B, table3 C
   where A.rowid = C.ot_rowid AND B.rowid = C.rm_rowid ;

   However, i do see lot of flaw with this approach

   1. Exp/Imp will always assign the different address (rowid) for the table
during import. In this case, the query will always fail.

   2. I can't do table move to different tablespace. So reorganisation is not
possible with this tables.
   3. Whenever we delete say table A's rows, then those deleted rowids will
always be reused . So again the query will fail.

   Can you give me some other reasons why we should not never store
   the rowids in a table.

   Thanks very much.

2. My second question is, i need to use a sequence no. for one of the
   columns which is varchar2(5). This is how it is.

   The first character of the string will be always "R" and i have to
   fillup with remaining with "sequence nos " say if i start with 1,
   then R0001 ,R0002 ...

   In this case, Should i use some table to store the sequence no.
   generated and pick it from there for any sequence reset etc.

   Otherwise, i have to parse out the last 4 characters and i have
   to do any sequence recreation or reset.

   Which one is the effecient way in terms of performance .

   Sorry for the lengthy statement.

      and we said...
1) rowids can change now.  new features can cause a rowid to change after it has
been inserted.  This was never true in 8.0 and before but in 8i and up -- a
rowid should never be persistently stored anywhere.  You can safely use
rowid in a session for locking purposes like forms does but thats about it.

But all of the reasons you give are 100% correct and valid.  That is a terrible
design they have come up with in the name of performance.  Databases were born
to join.  This is a very fragile environment they have created and is just
plain wrong

2) you only have 9999 things forever?  I hate these sorts of artificial
"sequences".  What happens when you get row 10,000?

No advice for you on this one other then to reconsider the "requirement".
Anything you do will be procedural and serialized here as you'll need to be real
careful not to "lose" a value since 9999 is such a tiny number.
Review &
      Followup

      Rating: 3
But Oracle uses
      rowid for snapshot/replication  December 06, 2002

Reviewer:  Tony  from India
I agree with your argument tom, but why oracle uses rowid for snapshot
replication. Snapshot tables created internally by oracle have rowid to identify
updated/inserted/deleted rows?

          Followup:

It USED to - it no longer does, it is all primary key based (since 8.0 --
1996/97).  Rowids for backwards compatbility only.

ops$tkyte@ORA817DEV.US.ORACLE.COM> desc mlog$_t
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 A                                            NUMBER
 B                                            NUMBER
 C                                            NUMBER
 SNAPTIME$$                                   DATE
 DMLTYPE$$                                    VARCHAR2(1)
 OLD_NEW$$                                    VARCHAR2(1)
 CHANGE_VECTOR$$                              RAW(255)

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

no rowids in there.

      Rating: 5
Can't join on
      rowids!?  December 06, 2002
Reviewer:
      Robert  from Memphis, USA
Tom,

Would you please clarify about rowids... if I am understanding what you are
saying, this is indeed bad news!

You mean we can't store rowids in a work table and then use them to retrieve the
actual table later?!

We have a situation where we are trying to identify and get rid of duplicate
keys from a 3rd party app's table. The table is very large and we are breaking
this up into several steps.
The first step is to use one of the classic queries to determine duplicate rows
(done by comparing min(rowid) by the way) and store these rowids in a table to
be used in the next step.... Are you saying we cannot trust these stored rowids?
...... If not how are we to do these kinds of tasks involving duplicate
checking/fixing ?

Thanks!

Robert

          Followup:

You can store rowids in a work table.

You might be able to retrieve the rows later.  depends on if the rows EXIST
still, depends on the rowids not having changed.

In your case, you are sounding pretty "safe". the rows should still be there
during your process (so the rowids will be valid still).  And, as long as you
are not using partitioning and index organized tables -- the rowids will be
constant.

From a long term storage perspective -- bad idea.  For quick and dirty things
like this -- great (heck, our own EXCEPTIONS INTO uses rowids for constraint
checking, you could id dups just by enabling a unique constraint on that table
and using the exceptions clause)

      Rating: 3
...But Oracle
      uses rowid for snapshot/replication  December 08, 2002

Reviewer:  harri  from Finland
I am going to use rowid based snapshots because there is tables without PK(3rd
party software). Is this safe and is there something i need to observe? Original
tables+snapshotlogs are in 8.1.7 db and snapshots are in 9.2 db.

          Followup:

As long as the rowid for a row never changes in the source database -- it will
be OK.

If they are not using partitioned tables with enable row movement.
If they are not using IOT's..
If you never reorg their data...

strongly suggest you get their PERMISSION before doing this (creating
snapshot logs on their tables).  If you want their support hotline to not hang
the phone up on you in any case.

      Rating:
      2
  April 08, 2003
Reviewer:
      A reader
but generally speaking (please correct me if I'm wrong): as long as contents of
rows can be changed by concurrent session(s) rows can be deleted and new rows
can be inserted at their "place". so a rowid can be used to address a row, but
it can NOT be used to IDENTIFY a certain database entry: the row addressed
through that rowid could have been deleted and a new one with the SAME rowid,
but DIFFERENT primary key values could have been inserted.

          Followup:

a rowid is safe to be used like this:

select rowid from table where <condition> FOR UPDATE;

That rowid will consistently point to that row for the duration of that
transaction.  This is, for example, how Oracle forms uses a rowid.

Oracle forms selects the row out "normally", without a lock.  It includes the
rowid in that query.

When you decide to update the row on screen -- before you type a character over
anything -- forms issues:

   select * from t where c1 = :block.c1 and c2 = :block.c2 and ... AND rowid =
:block.rowid FOR UPDATE NOWAIT;

if that gets a row - you have it locked.  we've matched the data on your screen
with the data in the database (so it didn't change) and we've done it fast (by
rowid).  if someone deleted and inserted a row reusing that rowid -- the c1 =
conditions would fix that.

if that gets zero rows - you get the message "sorry, data was changed TRY AGAIN"

if you thats ora-54 -- you get the message "sorry row locked, try later"

April 08, 2003 --
      Thanks for the question regarding "Oracle version on Solaris x86
      plateform", version 8.1.6

      You Asked
Hi,

I want to know what is the last release of ORACLE 8i on solaris
x86 plateform. Are all options available ?

Thanks,

Julien Nitot

      and we said...
On Solaris x86, it is 8.1.6.0

Operating System: Sun Solaris V2 Intel Version:2.7
Oracle Server - Enterprise Edition Version: 8.1.6 (8i)
N/A Version: N/A
Status: Certified

Certification Note:
Not certified for OPS.

OPS (parallel server) is an option not available on that
platform.

Review &
      Followup

      Rating:
      4
  April 05, 2003
Reviewer:
      A reader
Tom,

Has Oracle released 9i version for Solaris (intel).

Thanks

          Followup:

It is not to be...

      Rating:
      4
  April 06, 2003
Reviewer:
      A reader
Thanks Tom, if you dont mind can you tell us why??

          Followup:

Insufficient demand in order to offset the cost of maintaining and supporting
the platform.

      Rating: 3
8.1.7.4.0 is
      actually the last release...  April 06, 2003

Reviewer:  Eric Raskin  from Pleasantvill, NY USA

Tom:

I run 8.1.7.2.0 on Solaris x86 (2.7) right now, and I've been able to download
8.1.7.4.0 from Metalink.

However, as I understand it, this will be the last supported release, unless Sun
manages to convince Oracle differently.

          Followup:

correct, the original question was 2.5 years old at the time of the first
followup...

      Rating: 4
Downloading
      8.1.7.4 for Solaris Intel  April 08, 2003

Reviewer:  Dwayne Croteau  from Calgary, AB

Hi Tom,

Where might I aquire the 8.1.7.4 binaries for Solaris Intel?  I look around the
OTN for them, and the Solaris Intel binaries are no where to be found :(

          Followup:

you can order them.

goto store.oracle.com -> cd packs -> solaris intel

April 08, 2003 Paul -- Thanks for the question regarding "RMAN
      catalog maintenance", version 8.1.7

      You Asked
Hi,
Tom

We have recently implemeted an RMAN backup solution and are now starting to
think about RMAN maintance of the DB and catalog.

Is there an approach you would use for this
Does the database itself need to be maintained i.e. stats, indexes
How would you identify and delete old backup info

would you perform a crosscheck then delete any expired records using the RMAN
supplied package

would you delete obsolete records using the rman provided shell script

plus any other RMAN catelog maintance advice would be useful.

last but not least do these question get answered by the real Tom Kyte or are
you a team of people

cheers

Paul.

      and we said...
You don't really need to do anything to the database like that -- it is pretty
small.  Since indexes and tables don't really need to be reorged 99.9999% of the
times they are -- it is very self maintaining.

For maintanence purposes, yes, you can clean out old stuff.  It would take a
really active system with lots of databases to backup before the volume of
records got unbearable in there.

This book:

http://www.amazon.com/exec/obidos/tg/detail/-/0072226625
does have a chapter on RMAN "maintenance" as well as lots of other details on
RMAN itself.  I would recommend it if you are looking for some good information
on RMAN and what to do.

Yes, there is one real "me" and I answer the questions -- unless it starts with
"i ask so and so and they said...."

April 08, 2003 Brett -- Thanks for the question regarding
      "Connecting to Oracle from C application.", version 9.2.0.3

      You Asked
Tom,

My company has a business application that is currently written in C and is
deployed on a Macintosh OSX Server.  Macintosh OS9 and OSX clients connect to
this server to run the application.  To make the application more open and
scalable, we plan to now use Oracle 9iR2 as the backend on a Windows platform
(this is because we will have several other applications accessing the database
via Windows clients).  As one having to make sure that this is done as
efficiently as possible, I need to make sure that the application developers use
the correct access method to connect to Oracle.  I have read about JDBC, OCI,
Pro*C and others.  Can you give me a definitive on what should be used for the
OSX server to connect to Oracle and why?  Will I have to install any software on
the OSX Server?

Thanks,

Brett

      and we said...
You don't have to move from open systems to a proprietary as they get OS such as
windows in order to be "more open".  I would keep the server right where it is
-- windows can access it.

Anyway, if you are a C based application -- you'll be using OCI or PRO*C.  I
find Pro*C infinitely more productive as a programming tool than OCI is.  I can
write code -- good code -- many times faster using PRO*C.  You will find PRO*C
is as good as OCI since PRO*C is just a layer on top of OCE these days.  The
same pre-fetching et. al. that OCI did -- pro*c does.

If I had my choice -- the application would be written in PLSQL, stuff in the
database and the clients would just make simple stored procedure calls and the
client would be a middle tier (so it would be mod_plsql) and the clients would
be a browser -- talk about open.  That is how this site works.

Unless you use Java and a type 4 thin JDBC driver, you will need to install the
Oracle client on ANY client machine (all the more reason to think "web based",
"html")
April 08, 2003 -- Thanks for the question regarding "Tables
      sizing", version 8.1.7

      You Asked
Dear Tom,
i've a problem conserning import and export, yearly i do an export to a user
without data 'N_2002' and import it into another user'N_2003' each user have a
tablespace of its own'N_2002DATA, N_2003DATA', but i realize that after
importing into the new user 'N_2003'i see that it uses the old user tablespace
'N_2002DATA' am just asking how to prevent so? i use alter table move and
rebuild indexes to fix so.
also i want to size tables ( initial exetent, next extent)  are there any Table
Sizing Guidelines,hint procedures to follow....etc?
thanks in advance

      and we said...
read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:47812348053
it describes how to use exp/imp for this. you need to revoke unlimited
tablespace from the user that probably has RESOURCE (and hence has unlimited
tablespace)

consider using CREATE TABLE AS SELECT -- be tons faster then export import would
April 08, 2003 CSR -- Thanks for the question regarding "problem
      in working with dbms_job.submit", version 8.1.7

      You Asked
Tom,
Thanks for giving me chance to post a question.
please check the below code.

create table tab1(dt varchar2(100));

create or replace procedure sp1
is

begin
    insert into tab1 values(to_char(sysdate,'dd-mon-yyyy:hh24:mi:ss'));
    commit;
end ;
/

declare
jn binary_integer;
begin
dbms_job.SUBMIT(jn,'SP1;',sysdate,'sysdate + 1/86400');
dbms_output.put_line(jn);
end;
/

I wrote one stored procedure(SP1) and submitted this to job.I given interval as
1 sec.
But the SP1 is not invoked as per the given interval.
So, I could not see any rows inserted in the table 'tab1'.

I even tried by giving dbms_job.SUBMIT(jn,'SP1;',sysdate,'sysdate + 1');.
And changed the DB server date. This approach also not solved the problem.

Please let me know, what could be the problem.

Thanks in advance.

      and we said...
Well, I do not see a commit in there -- jobs only go into the queue for real
when you commit.  that could be it.

I do not know what your job_queue_interval is set to.  jobs are run every N
seconds depending on what this is set to.  In practicality -- every second is a
bit overboard.  Maybe every 10 seconds or 60 seconds...  that could be it
(job_queue_interval)

lastly, I don't know what your job_queue_processes is set to -- if that is zero,
jobs will NEVER auto-run.  it must be set to a number between 0 and 32 in 8i and
0 and 1000 in 9i.  If zero, no jobs run.  If set to one, one job at a time runs
and so on.  that could be it.
Review &
      Followup

      Rating:
      5
  April 09, 2003
Reviewer:  CSR
       from India
Fine, Tom it is working.

Thanks

April 08, 2003 David -- Thanks for the question regarding
      "Executing code across a database link", version 8.0.6

      You Asked
Morning, Tom.

Our production database is terminal at 8.0.6.  But we have a number of other
servers running 8i and 9i.  I'd like to take advantage of some of the 8i and 9i
functionality, but do it within the production database.

For example, I have a send_mail procedure on the 9i machine (using utl_smtp),
and I'd like to fire it off based on values in the production database, on the
8.0.6 machine.

If this is possible, what approach would you recommend?

As always, thanks.

David

      and we said...
it is possible and I used to do it myself in fact.

Here is what I did.  I set up a package on the "mail enabled database"

create package my_pkg
as
  procedure send_mail( p_to, p_from, p_subj, p_body );
  procedure background_send_mail( p_job in number );
end;
/

create table send_mail_queue ( job int primary key, to, from, subj, body );

Now, the send mail job just did this

as
   dbms_job.submit( l_job, 'background_send_mail(JOB);' );
   insert into send_mail_queue( l_job, .... );
end;

The send-mail-background was like this:

is
   select * into l_rec from send_mail_queue where job = p_job;
   ... send the mail;
   delete from send_mail_queue where job = p_job;
   commit;
end;

Now, you just need to call my_pkg.send_mail@remote_site (suggest creating a
synonym for my_pkg@remote_site for when you migrate this to 10i or whatever
later).  It'll queue a job that will run after you commit on the remote site.

Review &
      Followup

      Rating: 4
See what you
      think of this  April 08, 2003
Reviewer:
      David  from AL
CREATE OR REPLACE TRIGGER test_trigger after insert or update of dummy on t1
for each row WHEN (1=1)
declare

value varchar2(1);

begin

value := :new.dummy;

send('You inserted '|| value || ' into t1.dummy' );

end;
/

CREATE OR REPLACE procedure send (p_message in varchar2) as

begin

send_mail@omslab('user@xxx.com','user@xxx.com','sent from database',p_message);

end;
/

On the remote DB

CREATE OR REPLACE procedure send_mail
    (     p_sender      in varchar2,
          p_recipient   in varchar2,
          p_subject     in varchar2,
          p_message     in varchar2 ) as

l_mailhost         varchar2(255) := 'xx.xx.xx.xxx' ;
l_mail_conn     utl_smtp.connection ;
l_header        varchar2(1000);
crlf             varchar2(2):= utl_tcp.crlf;

begin

l_header:=     'Date: ' || to_char(sysdate -1,'dd Mon yy hh24:mi:ss')||crlf||
             'From: ' || p_sender || '' || crlf ||
              'Subject: ' || p_subject || crlf ||
               'To: ' || p_recipient;

l_mail_conn
    := utl_smtp.open_connection(l_mailhost, 25) ;

utl_smtp.helo(l_mail_conn, l_mailhost) ;

utl_smtp.mail(l_mail_conn, p_sender) ;

utl_smtp.rcpt(l_mail_conn, p_recipient ) ;

utl_smtp.open_data(l_mail_conn) ;

utl_smtp.write_data(l_mail_conn, l_header);

utl_smtp.write_data(l_mail_conn, crlf || p_message);

--utl_smtp.write_data(l_mail_conn, p_message ) ;

utl_smtp.close_data(l_mail_conn) ;

utl_smtp.quit(l_mail_conn );

end ;
/

*******

So when a value is inserted into the table, an email is sent with the value.

Pretty basic, but it's a prototype at the moment.  I'm working on getting the
distibution to be dynamic.

Thanks as always for your help.

David

          Followup:

Nope, I would use dbms_job to

a) make it appear blazingly fast.  (utl_smtp uses a blocking call -- it'll take
a second)
b) make it transactional (roll back that insert -- drat, got an email anyway)

if you have my book expert one on one Oracle -- i flesh this concept out fully.

      Rating: 4
I said it was a
      prototype!  April 08, 2003
Reviewer:  David
       from AL
;-)

With yours, I'm trying to figure out what drives the 'job' value.  Are these
different distributions and messages for specific applications?

          Followup:

see below

      Rating: 5
What is the
      meaning of JOB?  April 08, 2003
Reviewer:  Logan
      Palanisamy  from Santa Clara CA USA
Tom,

You have created the send_mail procedure as

"
as
   dbms_job.submit( l_job, 'background_send_mail(JOB);' );
   insert into send_mail_queue( l_job, .... );
end;
"

What is JOB in 'background_send_mail(JOB)'? Is it a variable or the value
itself? If variable where is declared?

It is a little confusing. It is an excellent technique otherwise.

Thanks

          Followup:

Ok, maybe it'll sell a book or two.  Here is a lengthly extract from my book
"Expert One on One Oracle" that describes this in painstaking detail:

....

The main routine you will use with DBMS_JOB is the SUBMIT routine. Its interface
is as follows:

PROCEDURE SUBMIT
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 JOB                            BINARY_INTEGER          OUT
 WHAT                           VARCHAR2                IN
 NEXT_DATE                      DATE                    IN     DEFAULT
 INTERVAL                       VARCHAR2                IN     DEFAULT
 NO_PARSE                       BOOLEAN                 IN     DEFAULT
 INSTANCE                       BINARY_INTEGER          IN     DEFAULT
 FORCE                          BOOLEAN                 IN     DEFAULT

Where the arguments to the SUBMIT routine have the following meanings:

o JOB: is a job identifier. It is system assigned (it is an OUT only parameter).
You can use this to query the USER_JOBS or DBA_JOBS views by job id to see
information about that job. Additionally some routines such as 'run' and
'remove' take the job id as their only input to uniquely identify the job to run
or remove.

o WHAT: is the SQL Text of what will be run. It must be a valid PL/SQL statement
or block of code. For example, to run a stored procedure P, you might pass the
string P; (with the semi-colon) to this routine. Whatever you submit in the WHAT
parameter will be wrapped in the following PL/SQL block:

DECLARE
   job BINARY_INTEGER := :job;
   next_date DATE := :mydate;
   broken BOOLEAN := FALSE;
BEGIN
         WHAT
    :mydate := next_date;
    IF broken THEN :b := 1; ELSE :b := 0; END IF;
END;

That is why you need to add the ';' to any statement - in order to just replace
the WHAT with your code, it will need a semi-colon

o NEXT_DATE: Is the next (or since we are just submitting the first) time to run
the job. The default is SYSDATE - run as soon as possible (after committing).

oINTERVAL: Is a string containing a date function that calculates the next time
to run the job.  You can consider this function to be 'selected from dual'. If
you pass in the string 'sysdate+1', the database will in effect execute 'select
sysdate+1 into :next_date from dual'. See below for some caveats on setting the
interval of a job to prevent 'sliding'.

o NO_PARSE: Determines whether the WHAT parameter is parsed upon submission. By
parsing the string - you can be reasonable sure the string is in fact
executable. In general, NO_PARSE should always be left with its default of
FALSE.  When set to true, the WHAT parameter is accepted 'as is' with no
validity checking.

o INSTANCE: Is only meaningful in Parallel Server mode, a mode Oracle can run in
on a loosely coupled cluster of machines. This would specify the instance upon
which this job should be executed. By default, this will have a value of
ANY_INSTANCE.

o FORCE: Again, this is only meaningful in Parallel Server mode. If set to TRUE
(the default) you may submit the job with any instance number - even if that
instance is not available at the time of job submission. If set to FALSE, submit
will fail the request if the associated instance is not available.

....

Now that we have a working knowledge of how DBMS_JOB works and what functions
are available to us, we'll look at how to run a job once, how to set up a
recurring job correctly, and how to monitor our jobs and find out what errors
they have encountered.

Running a job once

Many of the jobs I run are 'one off' jobs. I use DBMS_JOB much as one would use
the & in Unix or the start command in Windows to run a process in the
background. The example I gave above with regards to sending email is a good
example. I use DBMS_JOB to make the sending of email not only transactional but
appear to be fast. Here is one implementation of that to demonstrate how to run
a job once. We'll start with a small stored procedure to send mail using the
supplied UTL_SMTP package:

tkyte@TKYTE816> create or replace
  2  PROCEDURE send_mail (p_sender       IN VARCHAR2,
  3                       p_recipient IN VARCHAR2,
  4                       p_message   IN VARCHAR2)
  5  as
  6     -- NOTE that you have to use a host
  7     -- that supports SMTP and that you have access to.
  8     -- you do not have access to this host and must change it
  9     l_mailhost VARCHAR2(255) := 'aria.us.oracle.com';
 10     l_mail_conn utl_smtp.connection;
 11  BEGIN
 12     l_mail_conn := utl_smtp.open_connection(l_mailhost, 25);
 13     utl_smtp.helo(l_mail_conn, l_mailhost);
 14     utl_smtp.mail(l_mail_conn, p_sender);
 15     utl_smtp.rcpt(l_mail_conn, p_recipient);
 16     utl_smtp.open_data(l_mail_conn );
 17     utl_smtp.write_data(l_mail_conn, p_message);
 18     utl_smtp.close_data(l_mail_conn );
 19     utl_smtp.quit(l_mail_conn);
 20  end;
 21  /

Procedure created.

Now, to time how long that takes - I'll run it two times:

tkyte@TKYTE816> set serveroutput on
tkyte@TKYTE816> declare
  2          l_start number := dbms_utility.get_time;
  3  begin
  4          send_mail( 'tkyte@us.oracle.com',
  5                     'tkyte@us.oracle.com', 'hey there' );
  6          dbms_output.put_line
  7          ( round( (dbms_utility.get_time-l_start)/100, 2 ) ||
  8            ' seconds' );
  9  end;
 10  /
.81 seconds

PL/SQL procedure successfully completed.

tkyte@TKYTE816> /
.79 seconds

PL/SQL procedure successfully completed.

It looks like that will consistently take on the order of 8/10's of a second to
send a mail during the best of times. As far as I am concerned, that is far too
long. We can do much better - well, we can do much 'apparently' better. We'll
use jobs to give this the appearance of being much faster - and gain the benefit
of a 'transactional' email as well.

We will start by creating a table to store the email and a procedure we could
run against this table to send an email. This procedure will ultimately become
our background job. A question is - why am I using a table to store the emails?
Why not just pass parameters to the job?  The reason is bind variables and the
shared pool. Since all jobs are created using the WHAT parameter - and the
database will simply 'execute' that string at run time, we want to make sure
that the WHAT parameter we submit is something that will be in the shared pool.
We could easily just submit a job such as:

dbms_job.submit( x, 'send_mail(''tkyte@us.oracle.com'',
                                 ''tkyte@us.oracle.com'', ''hello'' );' );

but that would have the effect of flooding our shared pool with hundreds or
thousands of unique statements - killing our performance. Since we plan on
sending lots of emails (anything more than 1 is lots and would mandate the use
of bind variables) - we need to be able to submit something like:

dbms_job.submit( x, 'background_send_mail( constant );' );

Well, as it turns out there is an easy way to do this. We simply need to create
a table that contains a field for each parameter we really wanted to send to the
routine (sender, recipient, message in this case) plus an ID primary key field.
For example:

tkyte@TKYTE816> create table send_mail_data( id        number primary key,
  2                               sender    varchar2(255),
  3                               recipient varchar2(255),
  4                               message   varchar2(4000),
  5                               senton    date default NULL );

Table created.

Here I added an ID column as a primary key - and in this case a sent on senton
column. We'll use this table not only as a place to queue up outgoing emails but
to keep a persistent log of emails sent and when they were sent (very handy,
trust me, for when people say "but I didn't get the notification"). Now all we
need to do is figure out a way to generate a key for this table and get it to
our background process using a constant string. Fortunately DBMS_JOB already
does that for us. When we schedule a job, it automatically creates a job id for
it and returns that to us. Since the block of code it wraps around our WHAT
parameter includes this job id, we can simply pass it to ourselves! That means,
our fast_send_mail routine will look like this:

tkyte@TKYTE816> create or replace
  2  PROCEDURE fast_send_mail (p_sender    IN VARCHAR2,
  3                            p_recipient IN VARCHAR2,
  4                            p_message   IN VARCHAR2)
  5  as
  6          l_job   number;
  7  begin
  8          dbms_job.submit( l_job, 'background_send_mail( JOB );' );
  9          insert into send_mail_data
 10          ( id, sender, recipient, message )
 11          values
 12          ( l_job, p_sender, p_recipient, p_message );
 13  end;
 14  /

Procedure created.

This routine will submit a job background_send_mail and pass it the JOB
parameter. If you refer to the WHAT parameter description above - you'll see the
block of code includes 3 local variables we have access to - we are simply
passing ourselves one of them. The very next thing we do in this procedure is to
insert the email into our queue table - for delivery later. So, DBMS_JOB creates
the primary key, then we insert the primary key with the associated data into
this table. That's all we need to do. Now, we need to create the
background_send_mail routine and it is simply:

tkyte@TKYTE816> create or replace
  2  procedure background_send_mail( p_job in number )
  3  as
  4          l_rec   send_mail_data%rowtype;
  5  begin
  6          select * into l_rec
  7            from send_mail_data
  8           where id = p_job;
  9
 10          send_mail( l_rec.sender, l_rec.recipient, l_rec.message );
 11          update send_mail_data set senton = sysdate where id = p_job;
 12  end;
13  /

Procedure created.

It reads out the data we saved, calls the slow send_mail routine and then
updates the record to record the fact that we actually sent the mail. Now, we
can run fast_send_mail and see how fast it really is:

tkyte@TKYTE816> declare
  2          l_start number := dbms_utility.get_time;
  3  begin
  4          fast_send_mail( 'tkyte@us.oracle.com',
  5                          'tkyte@us.oracle.com', 'hey there' );
  6          dbms_output.put_line
  7          ( round( (dbms_utility.get_time-l_start)/100, 2 ) ||
  8            ' seconds' );
  9  end;
 10  /
.03 seconds

PL/SQL procedure successfully completed.

tkyte@TKYTE816>
tkyte@TKYTE816> declare
  2          l_start number := dbms_utility.get_time;
  3  begin
  4          fast_send_mail( 'tkyte@us.oracle.com',
  5                          'tkyte@us.oracle.com', 'hey there' );
  6          dbms_output.put_line
  7          ( round( (dbms_utility.get_time-l_start)/100, 2 ) ||
  8            ' seconds' );
  9  end;
 10  /
.02 seconds

PL/SQL procedure successfully completed.

As far as our end users are concerned - this fast_send_mail is 26 to 40 times
faster then the original send mail. It is not really, it just appears to be that
much faster (and that is what really counts). The actual sending of the mail
will happen in the background after they commit. That is an important note here:
if you run this example, make sure you commit when using the DBMS_JOB example
else the email will never get sent. The job will not be visible to the job queue
processes till you do (your session can see the job in the USER_JOBS view - but
the job queue processes won't see it until you commit).  Don't take this as a
limitation - it is actually a feature, we've just made email transactional.  If
you rollback, so does your send mail.  When you commit, it'll be delivered.

      Rating:
      5
Awesome  December 19, 2003
Reviewer:
      dharma  from CA USA
Cant beat you!!!!

April 08, 2003 Tom -- Thanks for the question regarding "PL/SQL
      Parameterization", version 9.2

      You Asked
Tom:

You are amazing.  Thanks for the answers.  MegaDittos.

2 questions:

1. What is a good practice regarding parameterization of procedures?  If we
adopt the "implement all db access for an app in packages", that's great.  We
are doing that.  Since DB access logic is now owned by the DB developers, the
app developers request certain functionality, and we give it to them.  So if
they need to retrieve widgets from the widget table, there are times they need
it to be filtered by other attributes, nullness, different ordering, etc.  You
can imagine.  And that all is necessary and reasonable.  But does it make sense
to try to parameterize all that into a few procedures as possible, or is the
more straightforward method of "write a proc for each request they give you" the
better way?

Example of heavy parameterization is something like:  if p_widget_id is null,
then return all all of them.  If it is not null, return just that widget.  In
same proc, if p_widget_type is not null, filter by that also (and p_widget_id
better be null or error).  Additionally, if p_widget_owner_is_null equals 'Y'
then only return those where widget_owner is null.

As opposed to having the following procs instead:  select_all_widget,
select_widget_by_id, select_unowned_widgets, select_widgets_by_type, etc... you
get the idea.

Which is a better practice?

2. For web apps, usually several rows will be selected for display and perusal
by the user, and then he will select one, update a field, which means the app
needs to call a proc to update that column for that row.  Typically, I will
write a proc that updates ALL the columns for the given PK value.  But, a web
app cannot feasibly store all the column values for hundreds of rows, so all the
values may not be available anymore when it comes time to request the DB update.
 Know any way around this problem?  I don't want to write an update proc for
every column, nor do I want to use dynamic SQL, if possible.

Thanks again.

      and we said...
1)

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279
for an approach.  If you are using ref cursors, probably easiest to dynamically
open a generically built one.  the KEY is to BIND.

2) well, here you have a trade off.

We can write one procedure with static sql to update the rows like this:

create procedure update_t( p_ts in ....,
                           p_pk in ...,
                           p_c1 in ..., p_c2 in ..., ... )
as
begin
   update t set c1 = nvl(p_c1,c1), c2 = nvl(p_c2,c2), ...., ts = ts+1
    where pk = p_pk
      AND ts = p_ts;

   /* i'll explain ts / p_ts in a minute */
   if ( sql%rowcount = 0 ) then
       raise_application_error(-20001, 'So sorry, you lose, someone else
          wanted that record and they already updated it' );
   end if;
end;

Or, you can do it dynamically like that query example above:

as
begin
   if ( p_c1 is not null )
   then
      dbms_session.set_context( 'my_ctx', 'c1', p_c1 );
      l_set_clause := l_set_clause || 'c1 = sys_context(''my_ctx'',''c1''),'
   end if;
   .....

   execute immediate 'update t set ' || l_set_clause || ' ts = ts+1 ' ||
   ' where pk = :pk and ts = :ts' using p_pk, p_ts;

   if .....
end;

The first -- easier to code, but generates gobs more UNDO.

The second -- harder to code, but updates only that which is necessary

A failing of both -- not possible to set a column to NULL, which we may have to
change the logic to permit if that is something you need.

The TS column is a "timestamp".  It is something you'll need to implement
optimistic concurrency controls.  When they get a row, they'll need the primary
AND the current "timestamp".  It just needs be a counter or something.  In order
to update a row, we have to make sure no one else did!  Else you suffer from
lost update problems big time.
Review &
      Followup

      Rating:
      5
Great  January 24, 2003
Reviewer:  Tom
      Best  from PA
Thanks.

      Rating:
      5
timestamp  April 08, 2003
Reviewer:
      sachin  from germany
"The TS column is a "timestamp".  It is something you'll need to implement
optimistic concurrency controls.  When they get a row, they'll need the primary
AND the current "timestamp".  It just needs be a counter or something.  In order

to update a row, we have to make sure no one else did!  Else you suffer from
lost update problems big time. "

Database: 8.1.7.4.0
Could you explain this please...
I do not understand this...
There is no timestamp column when we update
a table...where is this coming from..you are using it in both set and where
clause...
What is the lost update problem...

          Followup:

Lost update scenario:

time t1: you read out a row
time t2: your friend reads out same row
time t3: you update row, you updated the ADDRESS but write back the entire row
time t4: your friend updates the PHONE but writes back the entire row, hence
your
         update is totally lost.

It is a very classic database problem -- since the beginning of time.

Timestamp is a column YOU make up.  It is a number, it is a date, it is whatever
you want it to be.

time t1: you read out a row with timestamp "X"
time t2: your friend reads out same row with timestamp "X"
time t3: you update row -- making sure timestamp is still "X", you change
         timestamp to "Y"
time t4: your friend tries to update row --but since timestamp is "Y", not "X"
         they cannot -- they do not.  They lose.

If you have my book "Expert One On One Oracle" -- I cover both concepts in some
detail

      Rating: 5
lost
      updates....  April 08, 2003
Reviewer:
      Sachin  from Germany
"Timestamp is a column YOU make up.  It is a number, it is a date, it is
whatever
you want it to be."

O.K....You are saying that every table that we are thinking about updating
through the web apps..need to have a Timestamp column (date or number)...so when
we select the records...we always select this column also...and update it to the
next level...when a user does an update...
and this column will be passed to the procedure
We do have an existing modifieddt column on all our tables..
But we use that column to record sysdate when a column is updated or a new row
is inserted....so we have triggers
on all of our tables....that will change the modified date
only if atleast one column has been modified...
so if the web page is sending all the columns...regardless of whether they have
been modified or not....this trigger
willupdate the modifieddt

IF (INSERTING) THEN

     :NEW.MODIFIEDBY :=  NVL(:NEW.MODIFIEDBY,USER);
     :NEW.MODIFIEDDT :=  NVL(:NEW.MODIFIEDDT,SYSDATE);

  END IF;

   IF (UPDATING) THEN

      IF ((NVL(:NEW.col1,'-X')) != (NVL(:OLD.col1, '-X')))
        OR ((NVL(:NEW.col2, -1)) != (NVL(:OLD.col2, -1))) THEN

          IF (:NEW.MODIFIEDBY = :OLD.MODIFIEDBY) THEN
             :NEW.MODIFIEDBY :=  USER;
          END IF;
          IF (:NEW.MODIFIEDDT = :OLD.MODIFIEDDT) THEN
             :NEW.MODIFIEDDT :=  SYSDATE;
          END IF;

      END IF;

   END IF;

Question:
Can we use this modifieddt column and the PK column to select a row...and then
the trigger will update it to the current time...
So, all of our web selects will have modifieddt always selected...in the
'dd-mon-yyyy hh:mi:ss pm' format...
and will be passed to the update_table procedure in the same format...so that
exact comparison can be done....

seems a lot of work....wish there was an easier way to prevent these lost
updates

Thanks....

          Followup:

No, i didn't say that every table should...

It is one method -- others include:

o checksums
o saving the old values

In your case, your update would look like:

update T
  set .....
 where primary_key = :primary_key AND timestamp =
:timestamp_you_fetched_out_when_you_selected_the_row;

if sql%rowcount = 0 then message to user -- you lose.

client server had the easiest way to prevent lost updates -- you locked the row,
kept it locked -- when you fetched it out.  It was called pessimistic locking.
It happened for you naturally.  You want to go back ;)

One more thing -- it is work.  It is work to create a well designed, well
behaved application.  We have to think about things like this.  It takes time.
It takes energy.  It takes 15 extra keystrokes to make it happen.  But what you
get in the end is so much better then the "we'll just hope it never happens"
approach".  Databases are not black boxes.  Databases are not trivial.
Databases are not commodities.  Databases are tools -- I've got a nasty scar on
my leg from a chain saw.  I got a little careless with a fairly powerful tool
(that happens only once, you get a new appreciation for those things after a
close call)....

      Rating:
      4
??  April 08, 2003
Reviewer:
      A reader
You Said.
(Thank You Tom For answersing the Question)

The second -- harder to code, but updates only that which is necessary

A failing of both -- not possible to set a column to NULL, which we may have to
change the logic to permit if that is something you need.

AND We asked

Can u explain why it fails for null. What change will the context logic need to
have to handle null.

          Followup:

you would need to add more information to your screens -- check boxes, whatever
-- something to tell you "set to null" that is all.

It does not "fail" for null, this was a generic update routine - period.
designed to handle any update of any set of columns, using null to mean "ignore
me".  You would need to add a flag column to each input parameter to say "look
at me, use me"...

      Rating: 5
Excellent
      advice  April 09, 2003
Reviewer:  A Reader
       from UK
I bet there are millions (literally) of applications out there where no-one
thought to handle the 'lost update' problem.  I suspect it will be a lot of web
applications - things like Forms handle this for you I believe.

As always Tom, you do the Oracle community a great service.  I'd say "keep it
up" but if you do one day there'll be no bad developers left and you'd be out of
work (at least from this site's perspective) :)

      Rating: 5
Great advice to
      Sachin from Germany (April 8, 2003)  April 09, 2003

Reviewer:  Robert  from Memphis, USA
"One more thing -- it is work...", etc.

Excellent!

      Rating: 5
Point Taken
      !!!!!!!!!!!!!!!!!!!!!!! :)  April 09, 2003

Reviewer:  Sachin  from Germany

      Rating:
      5
  July 29, 2003
Reviewer:
      A reader


Subject | Article | Last Updated (East coast time)
about export/import indexes and constraints | article only | Apr 09, 2003 09:48 am
SELECT FOR UPDATE cursors | article only | Apr 09, 2003 10:27 am
download patch | article only | Apr 09, 2003 10:44 am
table design | article only | Apr 09, 2003 08:49 pm
Max number of rows in a table | article only | Apr 09, 2003 09:04 pm
ORA-00902: invalid datatype | article only | Apr 09, 2003 09:07 pm
oracle designer | article only | Apr 10, 2003 07:56 am
Is baseline statistic report important ? | article only | Apr 10, 2003 08:02 am
Read only tablespace mode not possible if there is an active tx in database | article only | Apr 10, 2003 09:13 am
Recommended system privileges | article only | Apr 10, 2003 09:30 am
usage of procedure/function in create view/derived table | article only | Apr 11, 2003 08:06 am
passing arrays into pl/sql stored procedures | article only | Apr 11, 2003 08:09 am
How not to ask a question. | article only | Apr 11, 2003 08:20 am
What limits the highwater mark for a rollback segment? | article only | Apr 11, 2003 08:44 am
intermedia type index on column varchar2(4000) | article only | Apr 11, 2003 08:54 am
Locks not getting released even after the session is killed | article only | Apr 13, 2003 07:09 pm
SQL Query | article only | Apr 13, 2003 07:31 pm
Internals of Roles and Privs. | article only | Apr 13, 2003 08:08 pm
Dynamic Columns selection | article only | Apr 13, 2003 08:13 pm
Database character set | article only | Apr 14, 2003 07:38 am
Moving Some Tables to a different Database !! | article only | Apr 14, 2003 01:36 pm
Missing Consecutives | article only | Apr 14, 2003 03:05 pm
timing a SQL | article only | Apr 14, 2003 03:07 pm
Foreign Key naming | article only | Apr 14, 2003 03:59 pm
Design question with SQL vs Pl/SQL in mind | article only | Apr 14, 2003 04:11 pm
SQL* Loader | article only | Apr 14, 2003 04:42 pm
Reading the numbers on the explain plan output | article only | Apr 15, 2003 08:08 am
Heterogeneous Services | article only | Apr 15, 2003 08:22 am
What is EMN0 ? | article only | Apr 15, 2003 10:48 am
row(s) 1 - 29 of 29
April 09, 2003 simone -- Thanks for the question regarding "about export/import indexes and constraints", version 8.1.6
You Asked
Hi Tom,
i have some questions about export and import of indexes and constraints.
To best explain my questions i make the following example:

edu816:test> create table t (a varchar2(10), b number);

Creata tavola.

edu816:test> alter table t add constraint t_pk primary key (a);

Modificata tavola.

edu816:test> create index t_idx on t(b);

Creato indice.

Then, I do an export of TEST's schema in this manner:

> exp test/test file=export.dmp

The file's content is the following:
----------------------------------------
...........
TABLE "T"
CREATE TABLE "T" ("A" VARCHAR2(10), "B" NUMBER)  PCTFREE 10 PCTUSED 40 INITRANS
1 MAXTRANS 255 LOGGING STORAGE(INITIAL 65536)
TABLESPACE "PROVA_LMT_AUTOALL"
INSERT INTO "T" ("A", "B") VALUES (:1, :2)
.......
CREATE UNIQUE INDEX "T_PK" ON "T" ("A" )  PCTFREE 10 INITRANS 2 MAXTRANS 255
STORAGE(INITIAL 65536)
TABLESPACE "PROVA_LMT_AUTOALL" LOGGING
CREATE INDEX "T_IDX" ON "T" ("B" )  PCTFREE 10 INITRANS 2 MAXTRANS 255
STORAGE(INITIAL 65536)
TABLESPACE "PROVA_LMT_AUTOALL" LOGGING
ALTER TABLE "T" ADD  CONSTRAINT "T_PK" PRIMARY KEY ("A") USING INDEX
PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 65536) TABLESPACE
"PROVA_LMT_AUTOALL" ENABLE
ENDTABLE
TABLE "T"
ALTER TABLE "T" ENABLE CONSTRAINT "T_PK"
ENDTABLE
EXIT
.......
------------------------------------------

When i run import utility the statement  "ALTER TABLE "T" ADD  CONSTRAINT" will
create the unique index on the column
without the statement "CREATE UNIQUE INDEX".
Hence, my questions is:
Is it really usefull "CREATE UNIQUE INDEX" before "ALTER TABLE "T" ADD
CONSTRAINT" as showed in the export file?
Can you give me the reason why export behave in this way???
This does not happen, in Oracle version 8.0.5.

In addition, if i want to import the file under another user without importing
the indexes, i run:
> imp dba/dba file=export.dmp fromuser=TEST touser=TEST1 indexes=N constraints=Y

then  i'll run import another time to create the index's file
> imp dba/dba file=export.dmp fromuser=TEST touser=TEST1indexfile=INDEX.sql

The INDEX.sql file's content is:

----------------------------
REM  CREATE TABLE "TEST1"."T" ("A" VARCHAR2(10), "B" NUMBER) PCTFREE 10
REM  PCTUSED 40 INITRANS 1 MAXTRANS 255 LOGGING STORAGE(INITIAL 65536)
REM  TABLESPACE "PROVA_LMT_AUTOALL" ;
REM  ... 0 rows
CONNECT TEST1;
CREATE UNIQUE INDEX "TEST1"."T_PK" ON "T" ("A" ) PCTFREE 10 INITRANS 2
MAXTRANS 255 STORAGE(INITIAL 65536) TABLESPACE "PROVA_LMT_AUTOALL" LOGGING ;
CREATE INDEX "TEST1"."T_IDX" ON "T" ("B" ) PCTFREE 10 INITRANS 2 MAXTRANS
255 STORAGE(INITIAL 65536) TABLESPACE "PROVA_LMT_AUTOALL" LOGGING ;
REM  ALTER TABLE "TEST1"."T" ADD CONSTRAINT "T_PK" PRIMARY KEY ("A") USING
REM  INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 65536)
REM  TABLESPACE "PROVA_LMT_AUTOALL" ENABLE ;
REM  ALTER TABLE "TEST1"."T" ENABLE CONSTRAINT "T_PK" ;
--------------------------------

If i run INDEX.sql file the statement ' CREATE UNIQUE INDEX "TEST1"."T_PK" ON
"T" ("A" ).....' will fail because
the object "TEST1"."T_PK" already exist. It has been already imported as a
constraint.
In my opinion, this seems an unnecessary repetition that create a lot of
confusion when i must import many tables, constraints and indexes.
Can you give me the reason for that????

This concept is from "Oracle8i SQL reference release2 (8.1.6) pag 596 create
index " (Part No. A76989-01):

"Oracle recommends that you do not explicitly define UNIQUE indexes on tables.
Uniqueness
is strictly a logical concept and should be associated with the definition of a
table. Therefore,
define UNIQUE integrity constraints on the desired columns."

thanks and regard
Simone
and we said...
It is to prevent the index on A from being "lost".  If you had a table:

create table t ( a int primary key, b int );
create index t_idx on t(a,b);

and we just exported:

create table t ( a int, b int );
create index t_idx on t(a,b);
alter table t add constraint primary key t(a);

you would end up with a different set of objects.  In the first case, you would
have the table T, the index T_IDX and the index to enforce the primary key
(SYS_.....)

In the second case, we end up with the table T and the index T_IDX -- NO unique
index on T(A) (not needed, we can in 8i use the index on (a,b) to enforce the
primary key).

That is why.

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( a int, b int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t add constraint t_pk primary
key(a);

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx on t(a,b);

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select object_name, object_type from
user_objects;

OBJECT_NAME                    OBJECT_TYPE
------------------------------ ------------
T                              TABLE

T_IDX                          INDEX
T_PK

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( a int, b int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx on t(a,b);

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t add constraint t_pk primary
key(a);

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select object_name, object_type from
user_objects;

OBJECT_NAME                    OBJECT_TYPE
------------------------------ ------------
T                              TABLE

T_IDX                          INDEX
Review & Followup
Rating: 3
Change Pctfree Val in Export File March 31, 2003
Reviewer: Abhi from India
hi ..

9.0.1   on   win2000

Sir i have a User with 120 tables In DMT with hight
PCTFREE 50.
and my Tablespace size is 3.5 GB.
Sir how can we reduce this Free space ....
   (i want reduce pctfree for 40% tables )

1. through export and import .. but i can't change in export file .. after
editing (make pctfree lower ) file becomes  unusable ...

2 if i change pctfree for a Table .. will it work for existing allocated blocks
Or it will work only For new Allocated Blocks.

Thanx .

Followup:
first things first -- change to LMT's.....

1) it is a binary file, not a simple text file.

You could:

imp userid=x/y ... tables=(t1,t2,t3,...) indexfile=foo.sql

edit foo.sql, uncomment and change the create tables -- delete the rest, run
foo.sql to precreate the tables and then:

imp userid=x/y ... full=y ignore=y

2) it affects only subsequent blocks, it does not go back and rewrite the table.

Rating: 3
Re. ( Question again PctFree and Pctused ) April 09, 2003
Reviewer: abhi from INDIA
hello Sir .. First Thanx For ur Answer ...
Sir u said
that changes pctfree or pctused for effect only new
comming blocks .. Oracle will not rewrite the exist table ... sir here the link
of referes...which confused me..
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/server.901/a90117/tables.htm#430
"
When altering the data block space usage parameters (PCTFREE and PCTUSED) of a
table, note that new settings apply to all data blocks used by the table,
including blocks already allocated and subsequently allocated for the table.
However, the blocks already allocated for the table are not immediately
reorganized when space usage parameters are altered, but as necessary after the
change. The data block storage parameters are described in "Managing Space in
Data Blocks".

When altering the transaction entry settings (INITRANS, MAXTRANS) of a table,
note that a new setting for INITRANS applies only to data blocks subsequently
allocated for the table, while a new setting for MAXTRANS applies to all blocks
(already and subsequently allocated blocks) of a table. To better understand
these transaction entry setting parameters, see "Specifying the Transaction
Entry Parameters: INITRANS and MAXTRANS".
"

Followup:
...
However, the blocks already allocated for the table are not immediately
reorganized when space usage parameters are altered, but as necessary after the
change.
......

if you set pctfree from 10 to 50 -- we do not reorg the blocks making 50% free.

if you set pctused from 50 to 10 -- we do not put onto the freelist any more
blocks (until we actually modify them)

Rating: 3
Re. ( Question again PctFree and Pctused ) April 09, 2003
Reviewer: abhi from INDIA
hello Sir .. First Thanx For ur Answer ...
Sir u said
that changes pctfree or pctused for effect only new
comming blocks .. Oracle will not rewrite the exist table ... sir here the link
of referes...which confused me..
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/server.901/a90117/tables.htm#430
"
When altering the data block space usage parameters (PCTFREE and PCTUSED) of a
table, note that new settings apply to all data blocks used by the table,
including blocks already allocated and subsequently allocated for the table.
However, the blocks already allocated for the table are not immediately
reorganized when space usage parameters are altered, but as necessary after the
change. The data block storage parameters are described in "Managing Space in
Data Blocks".

When altering the transaction entry settings (INITRANS, MAXTRANS) of a table,
note that a new setting for INITRANS applies only to data blocks subsequently
allocated for the table, while a new setting for MAXTRANS applies to all blocks
(already and subsequently allocated blocks) of a table. To better understand
these transaction entry setting parameters, see "Specifying the Transaction
Entry Parameters: INITRANS and MAXTRANS".
"

April 09, 2003 Gunjeet -- Thanks for the question regarding "SELECT FOR UPDATE cursors", version v 8.1.6
You Asked
Hi Tom,

I have a very basic question about the use of SELECT
FOR UPDATE cursors especially when used with DELETE
statements.

Could you please explain to me why someone would want
to get an an exclusive lock on the rows that are to be
deleted ? Do we care if someone tries to update them
at the same time ?

Also, if the result that was to be achieved with a
combination of SELECT FOR UPDATE and DELETE CURRENT OF
statement could be done in a single delete statement
(with a sub-query) can it be error-prone in a multi-user
env ? Won't a simple DELETE also lock the rows thus
achieving the same result ?

Is it absolutely essential to use SELECT FOR UPDATE for
doing updates in an environment where the same data can
be updated by multiple people ? What are the repercussions
if we don't use SELECT FOR UPDATE for updating/deleting
the data ?

Also, will the performance of stored procedures without
SELECT FOR UPDATEs be better than the ones with simple
update/delete statements as there is no additional
overhead of locking rows.

Thanks,

Gunjeet
and we said...
It depends on the logic.

Oracle forms for example will issue a SELECT for UPDATE on any
row you attempt to update or delete from the GUI.  It does this
since you read the row out at time T1 and you delete it at time
T2.  It wants to make sure that in the time between T1 and T2 --
no one UPDATED that row.  It wants to make sure you do not
delete the row without having at least looked at the modified
information (that would what is commonly known as a "lost
update").  It also does this to ensure that when the time comes,
the delete will proceed in a non-blocking fashion.  The select
for update forms puts on the row ensures

o the row was not changed
o the row will NOT be changed
o the row is locked by you and you will be able to delete it.

So, in forms the logic is something akin to :

1) read the data out with select t.*, rowid from T
2) let the user look at the data
3) when the user decides to delete the "5'th" row in the result
set, forms will issue:

     select t.*, rowid from T
      where t.c1 = :block.c1
        and t.c2 = :block.c2
        and ....
        and t.rowid = :block.rowid
     FOR UPDATE NOWAIT;

if that returns ORA-54 (resource busy), you are notified someone
else has that row locked.

if that returns 0 rows, you are told that the data has been
modified -- you need to requery

if that returns 1 row, you have the row locked and will be able
to delete it and no one else can touch it.

In a stored procedure - it will depend.  If you do not need to
inspect the data BEFORE doing the delete, you do not need this
logic in general.  If you need to inspect the data before the
delete -- YOU NEED this logic (to ensure the data does not
change under the covers).  So, if you have coded:

declare
   cursor c1 is select * from t where .... FOR UPDATE;
begin
   for x in c1 loop
        delete where current of c1;
   end loop;
end;

that would be better written as

begin
   delete from t where .....;
end;

OTOH, if you have coded:

declare
   cursor c1 is select * from t where .... FOR UPDATE;
begin
   for x in c1 loop
        some procedural code to look at X
        if ( some condition based on X ) then
            delete where current of c1;
        end if;
   end loop;
end;

you really do need the select for update to ensure that no one
else updates your data.

Select for update is a programming tool -- use it when you need
to.
Review & Followup
Rating: 3
SELECT FOR UPDATE cursors February 28, 2002
Reviewer: Julie from VA USA
The examples are wonderful they help me understand things much quicker. Keep up
the great work!

Rating: 4
update for update no wait March 04, 2002
Reviewer: nagaraju
i want to update a row with no wait.

can i use like update <tablename> set column1 = 'A' where <condition> for update
no wait

or
i have to write like
select rowid from <tablename> where <condtion> for update no wait;

then
update statement..

cant i do with a single stmt

Followup:
You do the select for update nowait and then the update.

Rating: 5
March 05, 2002
Reviewer: Steve from Blackburn
Tom,

why does a SELECT FOR UPDATE statement still lock a row in the table, even
though the select returned no rows?

e.g.
COLUMN Object_Name FORMAT A10
create table t1 (
a int ,
b int ,
c int
)
/
insert into t1
values (1,1,1);
insert into t1
values (1,2,2);
insert into t1
values (5,1,3);

commit;

update t1
set     a     =  3
where     c    =  2
/
commit;

SELECT     do.Object_Name,do.Object_Type,vlo.Locked_Mode
FROM    SYS.V_$Locked_Object    vlo,
    SYS.DBA_Objects        do
WHERE    vlo.Object_id        =    do.Object_Id
AND    do.Object_Name        =    'T1'
AND    vlo.Oracle_Username    =    'SCOTT'
/

SELECT     c
FROM    t1
WHERE     a     =     1
AND    b    =    2
AND    c    =    2
FOR UPDATE NOWAIT;

SELECT     do.Object_Name,do.Object_Type,vlo.Locked_Mode
FROM    SYS.V_$Locked_Object    vlo,
    SYS.DBA_Objects        do
WHERE    vlo.Object_id        =    do.Object_Id
AND    do.Object_Name        =    'T1'
AND    vlo.Oracle_Username    =    'SCOTT'
/
COMMIT;

Followup:
It didn't lock a row in the table.  It locked the TABLE itself.  A more clear
query might be:

ops$tkyte@ORA817DEV.US.ORACLE.COM> SELECT          c
  2  FROM       t1
  3  WHERE        a     =          1
  4  AND    b    =    2
  5  AND    c    =    2
  6  FOR UPDATE NOWAIT;

no rows selected

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select (select object_name from user_objects
where object_id = lock_id1) obj_name,
  2             lock_type, mode_held
  3    from dba_locks
  4   where session_id = (select sid from v$mystat where rownum=1)
  5  /

OBJ_NAME   LOCK_TYPE                  MODE_HELD
---------- -------------------------- ----------------------------------------
T1         DML                        Row-S (SS)

That shows the name of the mode -- going to the server concepts manual:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/c23cnsis.htm#2989
we can see that locks the table (due to the SELECT FOR UPDATE just being issued)
in Row share mode -- this prevents another session from locking that table in
exclusive mode but nothing else.  There are no TX (transaction) locks -- hence
no rows are locked in that table.

Rating: 5
Select for Update March 06, 2002
Reviewer: Steve from Blackburn
Thanks Tom.

Rating: 5
For update and Group By July 27, 2002
Reviewer: A reader
Dear Tom,
Please consider this requirement:
Field_Code   Value    Picked_up
10           10       No
20           15       No
10           20       No

All the records with "Picked_up=No" are to be selected and populated into
another table with "group by" on Field_Code, i.e.

Field_Code  Value
10          30
20          15

The Picked_up is to be updated to 'Yes'.
If we first do a Select for Update, we might have another record coming in when
we group by subsequently. For Update and Group By cannot be used simultaneously.
How do we do this then?
Thanks for your time.
Regards,

Followup:
I might use a 3rd value to indicate "Hey, I'm working on these"

ops$tkyte@ORA817DEV.US.ORACLE.COM> update t set picked_up = 'XXX' where
picked_up = 'NO';

3 rows updated.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t2
  2  select field_code, sum(value) from t where picked_up = 'XXX' group by
field_code;

2 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> update t set picked_up = 'YES' where
picked_up = 'XXX';

3 rows updated.

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

Commit complete.

Rating: 5
Thanks July 29, 2002
Reviewer: A reader
Thanks Tom, great help.
I think we just need to, reset picked_up=null, as well in the last update.

Rating: 5
what is the difference August 19, 2002
Reviewer: dada from LA
why do I get following error ???? what is the difference why I cannot use for
update?
thank you in advance for your help

SQL> ed
Wrote file afiedt.buf

  1  declare
  2  cursor c is
  3  select * from equipment_category_lookup for update nowait ;
  4  i number;
  5  begin
  6  for c1 in c loop
  7  update equipment_category_lookup
  8  set equipment_category_lookup_seq = main_seq.nextval
  9  where current of c;
 10  end loop;
 11* end;
SQL> /
where current of c;
                 *
ERROR at line 9:
ORA-06550: line 9, column 18:
PLS-00404: cursor 'C' must be declared with FOR UPDATE to use with CURRENT OF
ORA-06550: line 7, column 1:
PL/SQL: SQL Statement ignored

SQL> ed
Wrote file afiedt.buf

  1  declare
  2  cursor c is
  3  select * from equipment_category_lookup for update ;
  4  i number;
  5  begin
  6  for c1 in c loop
  7  update equipment_category_lookup
  8  set equipment_category_lookup_seq = main_seq.nextval
  9  where current of c;
 10  end loop;
 11* end;
SQL> /

PL/SQL procedure successfully completed.

Followup:
Looks like it wanted an "of something" for the nowait option there.

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2  cursor c is
  3  select * from emp for update of sal nowait ;
  4  i number;
  5  begin
  6  for c1 in c loop
  7  update emp
  8  set sal = main_seq.nextval
  9  where current of c;
 10  end loop;
 11  end;
 12  /

PL/SQL procedure successfully completed.

Rating: 4
Select For Update with multiple processes December 18, 2002
Reviewer: reader
Tom,
Just want to confirm the following :
I have 4 identical (same code) processes running. Assume, at a point in
execution all of them issue the following :

select c1,c2,c3
from t1
where c2=nvl(rtrim(somevalue))
for update

My understanding is that the 'lucky' process will lock the whole table for the
duration that it needs to do the update, while all other will wait. The reason
being use of function in the where predicate, since it will do the full scan and
hence lock the whole table.

Is this understanding correct?

Thanks.

Followup:
not the whole table, just the rows that meet the criteria.  regardless of the
access path.

but in this case you have:

where DATABASE_COLUMN = constant

(forget the functions - the value is a constant).  So, index range scan = OK.

It is when you have:

where f(DATABASE_COLUMN) = value

or

where DATABASE_COLUMN = variant(say another column in the table)

that the index range scan is not going to be used.

Rating: 5
FOR UPDATE December 19, 2002
Reviewer: reader from PAKISTAN
Tom,
The function in the query act upon 'bind variables', i am assuming this is also
treated as a constant.

In your explanation you refered to variant(database column), what do u mean by
variant?

Thanks.

Followup:
meaning the value varies from row to row.

select * from t where column1 = column2;

there -- column2 is "variable" when compared to column1.  The value for column2
must be evaluated for each row.

as opposed to

select * from t where column1 = 5;

Rating: 4
for update nowait April 09, 2003
Reviewer: G.V.N. from Houston
2 sessions are trying to update emp table as shown below

Session:1 - Time:T1
select empno, ename, job from emp where empno=7369 for update nowait;

     EMPNO ENAME      JOB
---------- ---------- ---------
      7369 SMITH      CLERK

Session:2 - Time:T2
SQL> update emp set ename='SMITH2' where empno=7369;
--nothing happens here since session 1 is holding the lock.

Session:1 - Time:T3
SQL> update emp set ename='SMITH1' where empno=7369;

1 row updated.

SQL> commit;

Commit complete.

Session:2 - Time:T4

1 row updated.
As soon as we issue a commit in session:1 The update from 'Session:2 - Time:T2'
gets done, after which 'Session:2 - Time:T4' issues a commit.

Now - either sessions would give the following result

SQL> select empno, ename, job from emp where empno=7369;

     EMPNO ENAME      JOB
---------- ---------- ---------
      7369 SMITH2     CLERK

Isn't this a case of lost update ?(not every transcation was using a for update
nowait)

Followup:
yes it is -- your application code is incorrect.  One of the guys did it right
-- they did a select for update, assuring them the row "is theirs".  The other
guy did it wrong, they blindly updated the data without checking to see if it
was modified since they read it out.

April 09, 2003 Richard -- Thanks for the question regarding "download patch", version 6.0.8.8
You Asked
Hi Tom:

Does Oracle provide a repository of all patches? If yes, please advise me the
web site.

Actually I’m looking for Forms 6i patch 12, but I can’t. Then I realized that I
couldn’t find patches of any oracle product.
and we said...
metalink.oracle.com

that is the support website, all patches available there.
Review & Followup
Rating: 3
December 30, 2002
Reviewer: Richard Jia from CANADA
Actually I searched Metalink before I asked my question. I could't find them in
Metalink.

Followup:
Hmm, I went to metalink.oracle.com, logged in.

clicked on patches (left hand side)

clicked on "New metalink patch search"

searched by product or family for "developer forms", release developer 6i,
patchtype = patchset/minipack

and the very first patch that popped up was:

  Patch Description Product Release Updated Type
2648892 RELEASE 6I: PATCH 12 Developer Forms 6i 27-NOV-2002 Patchset

Now that you have the patch number, 2648892, tis even easier

log in
patches
plug in the number.

Rating: 5
Great, as always December 30, 2002
Reviewer: Jay

Rating: 5
How to Know Database Patches Applied ? April 09, 2003
Reviewer: A reader
How do I know what patches have been applied on my database ?

Is there such thing as "v$patch" with the history of all patches applied ?

Followup:
regular standard patches are in the version.

8.1.7.4
      ^^^ patch level

One off patches, for a very specific bug fix, are rare and would be tracked by
you.  Support does have some tools to facilitate this if you "forget" -- but in
general, one off patches are not normal -- the patch sets are what we use.

April 09, 2003 sendi -- Thanks for the question regarding "table design", version 8.1.7
You Asked
Hi tom,

 I have a question of table design
I have 3 different types of users doing different tasks.
so they have different attributes ( behaviour)
but I want have a unique userid accorss all the applications
so that even if the user assumes different role I don't allocate
new userid for it and I will be able to trace him in the audit trail.

table type 1 has attributes :
userid number
username
contact#

table type 2 has attributes :
userid number
username
profession
sub_profession
specialty

table type 3 has attributes :
userid number
username
desice_desc
madication#
drug_id
on_madication_period
is_allagry

so
user type 1 is admin system users
user type 2 is
        our professionals who provide the service and get paid by us
user type 1 is our coustomer base who receive the service and pay us

so each type is the different attributs

I want to design it (IF POSSIBLE COMPLETELY RELATIONAL WAY)
 ie right now we have created no types/objects in our db.
    and if possible we don't want to use OO tech. in db at all.

to do that
  what we did is
    we careated a seq_user_id sequence for userid
  and created 4 tables
    1.) user1  represents type 1 users

table type 1 has attributes :
userid number
username
contact#
**cat_id

    2.) user2  represents type 2 users

table type 2 has attributes :
userid number
username
profession
sub_profession
specialty
**cat_id
    3.) user3 represents type  3 users

table type 3 has attributes :
userid number
username
desice_desc
madication#
drug_id
on_madication_period
is_allagry
**cat_id

  4.) cat   represents category
cat_id
cat_table_name
cat_table_pk_colid
cat_table_pk_col_name

and then
   created a function, that will take
   cat_id and user_id and based on cat_id
find and return the attributs requird.

as to tranform we need fuction in the queries
I can not create the matiralized views !!!!!

can you please guide me for design ?

Thanks,
and we said...
I don't see where MV's fit in here at all?

you need 4 tables - but not the above four -- in fact, I can succesfully argue
you need 1 table.

Your choices:

create table users ( userid int primary key, username varchar2 );
create table admin_users ( userid int primary key foreign key references users,
                           contact# ...);
create table type2_users ( userid int primary key foreign key ref.... );

and so on for type3 and type4.  You must insert (create) a user in users and
then insert into the subtypes.

Alternatively -- and equally as correct would be:

create table users ( userid int primary key,
                     username ..,
                     type1 attributes,
                     type2 attributes,
                     type3 attributes,
                     type4 attributes );

as long as the number of attributes stays under 100 or so -- me, i would do a
SINGLE table.
Review & Followup
Rating: 4
Thanks, April 09, 2003
Reviewer: A reader

now This tables are used all over the place.
to browsing data,to support other applications etc..

 so every body is geeting generic view and data is comming from which table is
transperent to foreign applications

 this tables are updated 10-100 times a day

I have a query

select userid,type,common.get_name(userid,type)
       spec_desc,prof_desc,....
from transaction_table t ,specialty s,profession p
where t.spec_id = s.spec_id
and  t.prof_id  = p.prof_id;

I can not put it in the " MV "

because of that fn

pls advise

Followup:
10 to 100 times a day is teeny.

I don't get the continued reference to a MV.

You asked my advice -- it is "looks like you have the need for a single table,
users.  If you do insist on more then one table, you are missing the parent
table for these subtypes"

I see no opportunity, need or desire for a materialized view here at all.

Rating: 4
Thanks, April 09, 2003
Reviewer: A reader
Thanks tom.

  I can not use OO feature and use type here.. we are not using even time series
only becaues of OO nature..

Thanks again for your adivce ..

Followup:
where do you see "OO"

I'm saying -- you need one table

create table users
( userid number primary key,
  username varchar2(255),
  attribute1_for_type1_people  varchar2(255),
  attribute1_for_type2_people  number,
  attribute2_for_type2_people  date,
  attribute3_for_type2_people  varchar2(5),
  .... and so on ....
)
/

a single table with columns for the attributes.  simple, nothing fancy -- as
easy as it gets.

Rating: 5
cool April 09, 2003
Reviewer: A reader
 I am cool with it. as you saind it is ok if I stay under
100 cols.
but isn't it denormalized ?
most of columns will be nulls for internal professionals
and system users.

and if I give to other groups and dba's
I don't know what will be there response to this
approach of denormalization.

Followup:
nope, denormalized would be storing DNAME in the EMP able in addition to the
DEPT table.

this is normalized -- it is optimal.

If they call this denormalization -- make fun of them.  Ask them "and where is
the redundant data?"

Rating: 5
Denormalized? April 09, 2003
Reviewer: Tony Andrews from London, UK
> but isn't it denormalized ?
> most of columns will be nulls for internal professionals
> and system users.

Denormalized =
1) No repeating groups
2) Data depends on the whole key, and nothing but the key

No, a table with NULL columns is not denormalized.

Rating: 5
ok April 09, 2003
Reviewer: A reader from NY,NY
Denormalized =
1) No repeating groups
2) Data depends on the whole key, and nothing but the key

No, a table with NULL columns is not denormalized.

 Thanks, for your feedbak tony,
the def. you have given above is Text Book def.

here I understand, but even if you see (of course from the)
"application contex",
** NOT all Attributes will be depandent on the
    primary key all the time.
   it is contex that will choose what attributs are
   to be selected.

** I will carry " all null" columns for those records
   which are internal system users and our professionals
  but I guess it will be no bigger deal as tom advised..

Thanks for sharing your ideas...

April 09, 2003 Aru -- Thanks for the question regarding "Max number of rows in a table", version 9iRel1
You Asked
Tom,
 We are designing a table which could possibly have a maximum of 800 million
rows.   The primary purpose of this table is for different applications to
exchange data  and also to store historical data for one year.

Our standard design for this a table is a "live table" which maintains the
latest 2 or 3 days worth of data and for the rest of the data to be in date
based partitions.

Our customer DBAs insist that there is no need for partitions.  They assure us
that performance will not be a problem if we design our indexes as they suggest
etc.

I had discussed a similar issue before and you suggested that we not go beyond a
few million rows per partition.  Any suggestions on how we can convince the
customer DBAs.  One question we could ask the DBAs is "why does Oracle provide
partitions"?  But they are the customer and we need to be cautious in how we
approach this.

Thanks much.
and we said...
performance is NOT what partitions are about.

Manageability
Availability
and maybe performance

in that order is what partitioning is about.

Tell me, how are you going to efficiently purge data from this table?
How are you going to move things around if you need to in the future?
How are you going to reorg things if you must later?

it is not about performance -- in fact, you have to many times work over time to
ensure that adding partitioning does not KILL performance.

The payoff -- bite size chunks that let you do your job using divide and
conquer.  Efficient purging of old data (just roll a partition off -- goodbye).
The ability to compress (in 9ir2) old static, read mostly data -- while still
working on live data.... and more
Review & Followup
Rating: 4
April 09, 2003
Reviewer: A reader

Rating: 4
Max number of rows in a table April 09, 2003
Reviewer: Rich from NJ
800 million row in a single table? Are there limits on the size of a table that
you would consider a "managable?"

Followup:
It's not the rows it is the size:

  1* select 800000000*100/1024/1024/1024 from dual
ops$tkyte@ORA920LAP> /

800000000*100/1024/1024/1024
----------------------------
                   74.505806

it is probably about a 75 gig table -- give a 100 byte avg rowsize.

I don't want a 75 gig table, I want maybe a 4-10gig table to manage.  Look at
the rest:

..
Our standard design for this a table is a "live table" which maintains the
latest 2 or 3 days worth of data and for the rest of the data to be in date
based partitions.
....

i see purging coming into play -- things by date.

manageable, available -- thats what this is about.  It is not the number of rows
in as much as it is the size -- 800,000,000 will probably put a table over the
edge -- yes.

April 09, 2003 Peter -- Thanks for the question regarding "ORA-00902: invalid datatype", version 8.1.7
You Asked
Hi Tom,

I have the following object types created in system schema.
I also have the same object types with the same names in apps
schema. Then I ran the PL/SQL script below as apps user.
If I use the objects in apps schema, the script ran fine.
However, when I use the objects in the system schema, I got
the following error:

ERROR at line 1:
ORA-00902: invalid datatype
ORA-06512: at line 11

Can you please help?

Thanks

- peter

create or replace type system.X_TYPE as object (
   party_id          NUMBER,
   attr_value        VARCHAR2(500),
   concat_value_str  VARCHAR2(4000)
);
/

create or replace type system.X_TBL as table of system.X_TYPE;
/

grant all on system.X_TBL to apps;
grant all on system.X_TYPE to apps;

create or replace type apps.X_TYPE as object (
   party_id          NUMBER,
   attr_value        VARCHAR2(500)
);
/

create or replace type apps.X_TBL as table of apps.X_TYPE;
/

DECLARE
   l_x system.X_TBL := system.X_TBL();
   l_dup_count NUMBER;

BEGIN
   l_x.EXTEND(2);
   l_x(1) := system.X_TYPE(100, 'xyz');
   l_x(1) := system.X_TYPE(200, 'xyz');

            SELECT *
            INTO   l_dup_count
            FROM (
               SELECT COUNT(*)
               FROM   THE (SELECT CAST(l_x AS system.X_TBL)
                           FROM   dual) a
               GROUP  BY attr_value
               HAVING COUNT(*) > 1) b;

   dbms_output.put_line(l_dup_count);

END;
/

ERROR at line 1:
ORA-00902: invalid datatype
ORA-06512: at line 11
and we said...
Well, a couple of things

first and foremost - SYS and SYSTEM, they belong to Oracle.  Don't under any
circumstances, use them for anything outside of what Oracle tells you to use
them for.

Don't use system.  Just don't do it.

Second -- your code:

 l_x.EXTEND(2);
   l_x(1) := system.X_TYPE(100, 'xyz');
   l_x(1) := system.X_TYPE(200, 'xyz');

doesn't fly -- system.x_type takes three values, not two and you are setting the
first value in the collection twice.  When I fixed that to be:

  6     l_x.EXTEND(2);
  7     l_x(1) := test.X_TYPE(100, 'xyz', 'a');
  8     l_x(1) := test.X_TYPE(200, 'xyz', 'a');

the query ran -- but failed with NO DATA FOUND (accessing (2) which wasn't set).
 So, I fixed that to be:

  6     l_x.EXTEND(2);
  7     l_x(1) := test.X_TYPE(100, 'xyz', 'a');
  8     l_x(2) := test.X_TYPE(200, 'xyz', 'a');

and it worked.  Here is my entire test case:

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop user test cascade;
User dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> grant dba to test identified by test;
Grant succeeded.

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop user apps cascade;
User dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> grant dba to apps identified by apps;
Grant succeeded.

ops$tkyte@ORA817DEV.US.ORACLE.COM> @connect test/test

test@ORA817DEV.US.ORACLE.COM> create or replace type test.X_TYPE as object (
  2     party_id          NUMBER,
  3     attr_value        VARCHAR2(500),
  4     concat_value_str  VARCHAR2(4000)
  5  );
  6  /

Type created.

test@ORA817DEV.US.ORACLE.COM> create or replace type test.X_TBL as table of
test.X_TYPE;
  2  /

Type created.

test@ORA817DEV.US.ORACLE.COM> grant all on test.X_TBL to apps;
Grant succeeded.

test@ORA817DEV.US.ORACLE.COM> grant all on test.X_TYPE to apps;
Grant succeeded.

test@ORA817DEV.US.ORACLE.COM> @connect apps/apps

apps@ORA817DEV.US.ORACLE.COM> create or replace type apps.X_TYPE as object (
  2     party_id          NUMBER,
  3     attr_value        VARCHAR2(500)
  4  );
  5  /

Type created.

apps@ORA817DEV.US.ORACLE.COM> DECLARE
  2     l_x test.X_TBL := test.X_TBL();
  3     l_dup_count NUMBER;
  4
  5  BEGIN
  6     l_x.EXTEND(2);
  7     l_x(1) := test.X_TYPE(100, 'xyz', 'a');
  8     l_x(2) := test.X_TYPE(200, 'xyz', 'a');
  9
 10              SELECT *
 11              INTO   l_dup_count
 12              FROM (
 13                 SELECT COUNT(*)
 14                 FROM   THE (SELECT CAST(l_x AS test.X_TBL)
 15                             FROM   dual) a
 16                 GROUP  BY attr_value
 17                 HAVING COUNT(*) > 1) b;
 18
 19     dbms_output.put_line(l_dup_count);
 20
 21  END;
 22  /
2

PL/SQL procedure successfully completed.

apps@ORA817DEV.US.ORACLE.COM>

So, in 8172 -- i don't see to be able to reproduce (although, I did not try
SYSTEM and won't)
Review & Followup
Rating: 2
I hit the same problem. April 09, 2003
Reviewer: Li Yang from CA USA
Hi, Tom:
I hit the same problem when trying to create a TYPE under SYSTEM schema and
refer it from APPS schema. Your demostration is good, but you still didn't
anwser why it doesn't work under SYSTEM schema.
I'm using  8.1.7.4.0.
Thanks,
Li

Followup:
because it doesn't -- sys, system -- special, magical, internal, not to be used
-- unless you work at 100-600 Oracle Parkway in Server Technologies.

Just don't go there.

sorry if I didn't make that clear in the original answer.  there is no reason,
there need be NO reason, it just is and and just doesn't matter.

April 10, 2003 parker -- Thanks for the question regarding "oracle designer", version designer 2000
You Asked
Tom

I know that when we install oracle designer, it will create a repository. It is
this repository which will contain all the tables which we are going to create
through our logical data model. I have the following questions..

1. Is the repository similar to a database.

2.We can also create forms and reports in designer2000, are the forms and
reports stored in the database as objects?

3.Are there two sides to the designer2000, i.e. the client side and the server
side. With the forms and reports stored on the client side and the database
objects like tables , indexes, procedures and packages stored in the repository.

4.Is it true that after creating the first cut forms in designer2000 for their
prototype, then the fmb's are copied over to regular forms tool, and developed.
If this is true why is it so ?

5.What is the current version of designer2000 and when was it released(if you
can give info on version previous to that , I will be really greatfull).

6.Apart from creating the Erd, forms, reports, what are the other things which
are possible using designer 2000?

7.Does ERD and logical data model mean the same?

8.What is the difference between a logical data model and a physical data model.

9.While working with data modelling tool erstudio , I have the option of
creating a identifying relationship and  a non identifying relationship. What is
the difference between the two

10.What does case tools actually mean?

11.Does reverse engineering mean , making changes to an already created database
using a data modelling tool.

12.Does oracle have a tutorial on designer2000?

Thank you
and we said...
1) a reposistory is a set of meta-data (data about data) that designer
maintains.  It is like a data DICTIONARY but is no more like a database then the
data dictionary is.  It needs a database, it is not a database

2) the META data about the forms/reports are.  forms and reports can be stored
in the database but typically you create them in the filesystem.

3) yes

4) sometimes.  You can.  You can use designer to get the forms, use developer to
"pretty them up" and then use designer to reverse engineer them so you can
regenerate them from the repository.

5) designer 6i release 3 releases April 2001

6) see
http://www.oracle.com/ip/develop/ids/index.html?designer.html
7) sort of.  a logical data model can have lots more info then an ERD does - an
ERD is typically part of a logical data model.

8) sometimes nothing.  other times, you physically implement the data structures
differently then the logical model would have you store it.  Denomalized for
example in a data warehouse -- for performance.

9) I'm not familar with erstudio and their terminology.  Sounds like a weak
entity (identifying) or strong entity (non-identifying) concept.  For exampe EMP
relates to DEPT but EMP is a strong entity -- it "stands alone".  Now, if DEPT
had multiple address objects related to it -- they might be WEAK entities, that
might be an identifying relationship.  Again tho -- you would really have to ask
erstudio what they meant.

10) CASE = compute aided software engineering.  It means I'm using a compute
instead of paper and pencil.

11) to read the definition of an existing application into a repository to study
how it works.

12) see #6
Review & Followup
Rating: 5
May 18, 2001
Reviewer: Helena Markova from Bratislava, Slovakia

Rating: 5
May 21, 2001
Reviewer: Tom Howze from Long Beach, CA

Rating: 4
Oracle designer January 10, 2002
Reviewer: Erhard Glueck from Austria

Rating: 4
More help April 10, 2003
Reviewer: Abdal-Rahman Agha from Yemen
Hi Tom,
  Your feedback is very nice to me. Now, if I have about more than 500 tables
and I want to reverse engineer them with their
relationships,triggers,procedures, i.e. all the schema. What is the best way so
that I can get very clear picture "in terms of the diagram" for my application.
I did that before but the diagram appears crowded. Can Designer for example
allow to divide my data model into groups, so that I can make many diagrams each
of which tell the details of the preceding one?

Followup:
500 objects with complex relationships is going to look crowded on anything
short of a billboard.

I'm not a big designer user, I'm not aware of its capabilities in this
particular area -- anyone out there want to comment?

Rating: 3
Server Model Diagram April 10, 2003
Reviewer: Tim from Germany
You can create several Server Model Diagrams for a
given shema where the tables show up.

You can delete tables from a populated diagram by selecting
them an pressing Strg-X. ( sorry Ctrl-X on an english Keyboard ;-) )

After that you can populate the diagram with the
tables you wish by dragging & dropping them to
the diagram.

Works for me. I am using Designer 6i.

Rating: 5
April 13, 2003
Reviewer: Abdal-Rahman Agha from Yemen
Thanks to Tim  from Germany, and of course, to Tom.

Rating: 4
Designer Problem: Char is converting to Varchar2 April 21, 2003
Reviewer: A reader
Hi,
I am using Designer9i and I am trying to generate DDL scripts thro' Database
Design Transformer (DDT). But all CHAR attribute-types of E-R Model is getting
converted to VARCHAR2.

What could be the problem.
Regards,
Moiz

Rating: 5
repos owner July 07, 2003
Reviewer: atul from India
Hi,

I want to know the name of user under which repository is been created..

as generally its repos_owner..But in our database i'm not able to find it out..

Kindly tell me how to find which database user is the owner of repository??

Thanks & Regards,
atul

April 10, 2003 Goh -- Thanks for the question regarding "Is baseline statistic report important ?", version 8.1.7
You Asked
Dear Tom,

   I understand that Baseline statistic report is very important in performance
monitoring and tuning process. Appreciate if you can briefly explain what is
baseline statistic report, why it is so important and how old the baseline
statistic report should be. Thanks in advance.

Rgds,
SHGoh
and we said...
a baseline is the "as is" picture of what is going on.

For example -- you want to improve the mileage your car gets.
So, you take it in for a tune up.
If you did not record the mileage before the tune up -- you won't know if it
works.

statspack during peak time is a good way to get a baseline image.
Review & Followup
Rating: 4
Thanks for reply. April 10, 2003
Reviewer: Goh Seong Hin from MA
Dear Tom,

   Thanks for the answer. As a DBA, do you recomend to keep a baseline (During
month-end and normal peak hour) of the system. Thanks.

Rgds,
SHGoh

P/S : I have just bought Expert One-to-One book but it doesn't talk much about
performance tuning (I think only one chapter). Do you have any other book to
recomend if I need to know is there any best pratice of to perform database
tuning and monitoring) ? Thanks.

Followup:
yes -- absolutely.  A month from now someone says "hey, the system -- its really
slow".

You can either say "nope, same as last month, six months ago, a year ago" or
"wow, you're right and here is what I see that is different"

If you follow the advice in that chapter 10 - you won't be tuning after the fact
-- you'll be sitting back relaxing.  I'm not big in the "emergency room doctor"
approach to tuning personally.

There are tons of books with exabytes of questionable ratios and advice you can
use.

Suggestion: save your money, get Jonathan Lewis's book "Pratical Oracle8i
Building Efficient Databases" (not a ton of tuning in there either -- sort of a
theme we got going -- why build junk and then make it go faster?), read that and
the Oracle Performance Guide and don't "tune" anymore.

Tuning has got to be the most awful boringest thing on the planet to do.

Rating: 3
Goh Seong Hin April 10, 2003
Reviewer: Freek from Belgium
why am I thinking at the words "wound" and "salt" now  ;)

Have a look at
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:8145042756282

April 10, 2003 -- Thanks for the question regarding "Read only
      tablespace mode not possible if there is an active tx in database",
      version 9.2.0.3

      You Asked
Since upgrading to 9i we do a lot of transport tablespace to move data between
databases .. One of the problem we face is that we can't put a tablespace in
read only mode when there is ANY transaction in rollback. This does not make
sense because it shouldn't be much of a problem to figure out if the tablespace
in question to be put into read only mode has any object(s) in rollback.
If no object in rollback for that tablespace let it move forward with read only
..
is it because of distributed transactions ?
thanks for your time and answer

      and we said...
You would have to scan all RBS's in order to determine if there were any
outstanding transactions against that tablespace.

It is trivial to know what transactions are in place -- the RBS's tell us that.

We would have to then inspect every block in every rbs in order to see if it
applied to the tablespace in question and the only way to do that would be to
FREEZE the system (cannot allow transactions to proceed -- it would be a never
ending vicious cycle).

So, you would freeze the instance -- we do that with the quiesce command:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96521/start.htm#9497

April 10, 2003 Gary -- Thanks for the question regarding
      "Recommended system privileges", version 8.17

      You Asked
After reading comments in other posts regarding 'dangerous' privileges ( ie most
of the ANY privileges), and about not liking public synonyms etc, I'd like to
know what system privileges you'd recommend for various types of account.

I would guess that for the basic user account, it would be CREATE SESSION at a
minimum. I'm not clear on whether the basic user needs QUERY REWRITE/GLOBAL
QUERY REWRITE and what, if any, the downsides of granting this would be. Is
ALTER SESSION 'safe' enough to be granted directly, or is it better to have the
desired functions of this in a package ?

For the 'application owner' user, all the CREATE privileges except those which
may affect other applications in the database. IE CREATE TABLESPACE, ROLE, USER,
PUBLIC SYNONYMS/DATABASE LINKS and LIBRARY and obviously not the CREATE ANY ...

Except for tablespace related privileges, I can't really come up with a similar
list for 'DBA' style users that matches job needs against security. For example,
someone needs ALTER USER to reset forgotten passwords, but it is also very
powerful/dangerous.

For these higher privileges, is it best to restrict access to them via a package
to build in extra controls ?

      and we said...
You should work with the concept of LEAST PRIVILEGES.  That says you grant the
bare minimum required and there is a reason for each and every grant.

CREATE SESSION is good for basic user accounts.

Through in a role or two that has some grant selects/inserts/executes on
specific objects.

Even something as *inoccent* as "ALTER SESSION" can have ramifications.  We were
having a discussion on the usenet newsgroups where someone said
"_trace_files_public" is totally safe (in regards to using undocumented init.ora
settings).  I politely pointed out that with public trace files (traces are
world readable) and ALTER SESSION -- which is unfortunately granted to CONNECT
-- a smart user could use google to find out how to dump some SGA structures,
read them and find information they should have (important stuff, things they
could use to get even more privs).

Using stored procedures to go very very granular is a good idea.  Suppose you
wanted people to be able to alter their session and change the NLS_DATE_FORMAT
(but not anything else).  Create a procedure:

procedure set_nls_date_format( p_format in varchar2 )
as
begin
   execute immediate
         'alter session set nls_date_format = ''' || p_format ||'''';
end;

and grant them execute on that.  Now, the owner of this package needs ALTER
SESSION but the owner of this package doesn't need CREATE SESSION (eg: owns
code, cannot login with it however).  Same could be true for QUERY REWRITE
ENABLED -- safe to set that on and off but do they *need* alter session?  (no)

Do you want the users to be able to

alter session set sort_area_size = 500gigawads of storage?  (if you do, give
them a set_sort_area_size procedure).

I would not let an application owner have create tablespace, user, public
synonyms, or library.  They are fairly powerful (remember, the library would run
as ORACLE, a malicious person with that.....)

As for the password thing -- use the same technique as above -- create a
procedure "reset_password", put into it serious auditing (make it an autonomous
transaction and audit its use).  Grant execute on that to specific
individuals/roles and the owner of the code needs ALTER USER, but not CREATE
SESSION again (in fact, you could lock the account)

The *fewer* privs you give out, the more securely you can sleep at night.

Review &
      Followup

      Rating: 3
How to revoke
      that ?  April 08, 2003
Reviewer:  Mark
       from Cambridge
I set up a user in an Oracle 9 database on Win2000, the user is called Newbie.
I only granted CREATE SESSION to newbie, but newbie can still alter the session.
Am I missing something here ?

>show user
USER is "NEWBIE"
>
>select * from session_privs

PRIVILEGE
----------------------------------------
CREATE SESSION

>select * from session_roles
no rows selected

>alter session set nls_date_format = 'DDMMYYYY'

Session altered.

          Followup:

There are very few "alter session" things that need the alter session priv.

Try "alter session set sql_trace=true" for example.

      Rating:
      4
  April 08, 2003
Reviewer:  Robert
       from Memphis, USA

      Rating: 4
Alter session
      flavours  April 08, 2003
Reviewer:  Mark
       from Cambridge, UK
Thanks for the quick follow up !

In that case - to prevent Newbie (see my example before) from doing naughty
things then, how can he prevented from pulling off 'alter session set
sort_area_size = some big number' ?

Because that's something you can do without the ALTER SESSION privilege too. You
just need CREATE SESSION !
I still must be missing something here..

          Followup:

you cannot -- setting the sort area size is totally permissible.

      Rating: 4
Alter Session
      Priv  April 09, 2003
Reviewer:  Steven
      Cooper  from England
Tom,

I'm in a situation where it appears that an sql of ours in report builder won't
run without Alter Session privs but I can't see any obvious reason why.  Also to
make things more confusing the Report Builder report calls a DB Function which
if we move from the DB to the report, the report will run OK.  It would suggest
that the function is the cause but if I grant alter session to the user then run
the report again with the function in the DB it will work, so it seems that
1) The report will work if the user is granted Alter Seesion and the function is
in either the DB or Report.
2) The report will work without Alter session only if the function is within the
report.

Heres the Function Code, its quite basic, nothing fancy.

FUNCTION PERCENT_A_B    (    I_Number_A        IN    NUMBER,
                I_Number_B        IN    NUMBER
            ) RETURN NUMBER
/*Function to Find A as a Percent of B, trapping the divide by zero error*/
IS
V_Number        NUMBER;
BEGIN
  BEGIN
    V_Number:=(I_Number_A/I_Number_B)*100;
  EXCEPTION
    WHEN ZERO_DIVIDE THEN
      V_Number:=0;
  END;
  RETURN(V_Number);
END PERCENT_A_B;

Tom can you please suggest a reason why this might happen.

Thanks
Steven.

          Followup:

I would suggest that using:

  decode( i_number_b, 0, 0, i_number_a/i_number_b*100 )

right in the SQL is the best approach all around.  It'll be quite a few times
FASTER and many times less cpu intensive.

Does this reproduce in sqlplus?
what is the report builder you are using?

I cannot reproduce in sqlplus:

ops$tkyte@ORA817DEV> drop user a cascade
  2  /

User dropped.

ops$tkyte@ORA817DEV> create user a identified by a;

User created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace
  2  FUNCTION PERCENT_A_B       (         I_Number_A        IN    NUMBER,
  3                  I_Number_B        IN    NUMBER
  4              ) RETURN NUMBER
  5  /*Function to Find A as a Percent of B, trapping the divide by zero error*/
  6  IS
  7  V_Number        NUMBER;
  8  BEGIN
  9    BEGIN
 10      V_Number:=(I_Number_A/I_Number_B)*100;
 11    EXCEPTION
 12      WHEN ZERO_DIVIDE THEN
 13        V_Number:=0;
 14    END;
 15    RETURN(V_Number);
 16  END PERCENT_A_B;
 17  /

Function created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> grant execute on percent_a_b to a;

Grant succeeded.

ops$tkyte@ORA817DEV> grant create session to a;

Grant succeeded.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> @connect a/a
ops$tkyte@ORA817DEV> set termout off
a@ORA817DEV> REM GET afiedt.buf NOLIST
a@ORA817DEV> set termout on
a@ORA817DEV>
a@ORA817DEV> select ops$tkyte.percent_a_b( 1, 0 ) from dual;

OPS$TKYTE.PERCENT_A_B(1,0)
--------------------------
                         0

      Rating: 5
Alter
      Session  April 09, 2003
Reviewer:  Steven
      Cooper  from England
Tom,

I will in future use the decode Thanks.

Report Builder 6.0.5.34.1

I can't run in sql since the report is set up such that its a report builder
summary column calling the function not the main body of the sql, it will work
for 1 call and between small ranges of data (2-300), but if we let the query go
without ranges it hangs.

We have a work around and have managed to hit the deadline, but I wanted to know
why this might be happening so as to avoid it in future.  What exactly does
Alter Session let you do....?

Our thoughts why this might be happening were not very good, we thought that it
might be something to do with temp space and the amount needed to call it over
and over, compared with calling it locally from the report were the PC's
resources will be used instead.  Its just weird that by granting Alter session
it will work for the full range of data and by revoking the priv it hangs on
page 1.

Thanks
Steven

          Followup:

I do not see the relationship between alter session and this function at all.

It sounds like a "product issue", you'll want to file a tar with support.

      Rating:
      5
Tar  April 10, 2003
Reviewer:  Steven
      Cooper  from England
Tom,

It appears this could be a know bug (2494243).

Thanks
Steven

          Followup:

that is with regards to using plsql record types... don't necessarily see the
connection with alter session.

      Rating: 4
Bug
      2494243  April 10, 2003
Reviewer:  Steven
      Cooper  from England
Tom,

While it isn't an exact match, it is very similar in resepcts to the problems we
are having i.e. won't run function on db but will run it locally, I've logged a
TAR anyway but this bug looks promising.

Steven

          Followup:

ok -- but it looks like  red herring to me.

The bug is all about "there is a hang when we use plsql records returned from
stored procedures to a report"

You have "we need to use alter session"

I don't see any possible connection personally.

      Rating: 4
Bug
      2494243  April 10, 2003
Reviewer:  Steven
      Cooper  from England
Tom,

I take your point, and I agree with you about alter session being a red herring,
Its just one of the workarounds that seems to work, but I can't explain why and
its bugging me :).  I do think their are similarities, but I suppose just
because the symptoms look the same doesn't necessarily mean its the same
problem.

Anyway thanks for all your feedback and I'll post the solution if I ever find
out from support.

April 11, 2003 Jimson -- Thanks for the question regarding
      "usage of procedure/function in create view/derived table ", version All

      You Asked
1. Does oracle support calling a procedure as a derived table,
   Eg
   select * from (call myprocedure) ....

2. Also, does it support creation of a view over a procedure ,

   Eg,

   Create view as (call myprocedure)

      and we said...
You need to use Oracle9i or up to do that (but I do show a way in 8i):

ops$tkyte@ORA9I.WORLD> create or replace function virtual_table( p_num_rows in
number )
  2  return virtual_table_type
  3  PIPELINED -- NOTE the pipelined keyword
  4  is
  5  begin
  6      for i in 1 .. p_num_rows
  7      loop
  8          pipe row( i );
  9      end loop;
 10
 11      return; -- note return with no value!
 12  end;
 13  /

Function created.

ops$tkyte@ORA9I.WORLD> ops$tkyte@ORA9I.WORLD> ops$tkyte@ORA9I.WORLD> set echo
off
Enter to continue

=================================================================

now we'll just see this in action

ops$tkyte@ORA9I.WORLD> ops$tkyte@ORA9I.WORLD> select * from TABLE(
virtual_table(5) );

COLUMN_VALUE
------------
           1
           2
           3
           4
           5

ops$tkyte@ORA9I.WORLD> select * from TABLE( virtual_table(10) );

COLUMN_VALUE
------------
           1
           2
           3
           4
           5
           6
           7
           8
           9
          10

10 rows selected.

We use "pipe" instead of suspend.

Prior to 9i, there is no pipe, it would look like this:

ops$tkyte@ORA9I.WORLD> create or replace type virtual_table_type as table of
number
  2  /

Type created.

ops$tkyte@ORA9I.WORLD> ops$tkyte@ORA9I.WORLD> create or replace function
virtual_table( p_num_rows in
number )
  2  return virtual_table_type
  3  is
  4          l_data virtual_table_type := virtual_table_type();
  5  begin
  6      for i in 1 .. p_num_rows
  7      loop
  8                  l_data.extend;
  9                  l_data(l_data.count) := i;
 10      end loop;
 11
 12      return l_data;
 13  end;
 14  /

Function created.

ops$tkyte@ORA9I.WORLD> ops$tkyte@ORA9I.WORLD> select * from TABLE(
virtual_table(5) );

COLUMN_VALUE
------------
           1
           2
           3
           4
           5

ops$tkyte@ORA9I.WORLD> select * from TABLE( virtual_table(10) );

COLUMN_VALUE
------------
           1
           2
           3
           4
           5
           6
           7
           8
           9
          10

10 rows selected.

you would fill up the collect and then return it (no "pipe", no suspend
functionality)

Review &
      Followup

      Rating: 5
Great
      answer!!  December 19, 2001
Reviewer:  Jimson K
      John  from Bangalore, India
This is what I was looking for! Thanks!

      Rating:
      3
Interesting  December 20, 2001
Reviewer:
      Randy  from CHicago,IL
It sounds very useful. But unfortunately when I try
select * from TABLE(virtual_table(5) ); after finishing previous steps in my
8.1.7 server, I am get
select * from TABLE(virtual_table(5) )
                    *
ERROR at line 1:
ORA-22905: cannot access rows from a non-nested table item

The type and the function was creted successfully.

          Followup:

try:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from TABLE( cast ( virtual_table(5)
as virtual_table_type ) );

COLUMN_VALUE
------------
           1
           2
           3
           4
           5

sorry about that -- forgot the CAST which is needed in 8i, but not 9i

      Rating: 4
What about
      REport and form  December 20, 2001
Reviewer:
      A reader
Hi, Tom,

Greate solution, but chanllenge is: it it possible we build query based on SQL
type in the ORACLE report/form?

Somthing like: ---- query in report
select *
from t1
where pk_col in (select * from TABLE(cast(PL_sql_tb as
         some_sql_type)));

it seems problem to me when i try it, please also clarify
if it is OK in ORACLE form?

Thanks

          Followup:

Either

o use dynamic sql
o hide the construct in a view and select * from the view

that is what I do if a particular tool won't accept some fancy SQL construct.

      Rating: 4
Dynamic sql in
      REPORT6.0  December 21, 2001
Reviewer:
      A reader
Hi, TOm,

Would you spend some valuable time instruct me of
how to construct a query to form a report by
"dynamic sql" using select * from TABLE(CAST....))
in ORACLE REPORT 6.0?

BTW, are there any inpact if i use a view to form a
report instead of a table if the view is fairly
complicated(lot of conditions in where clause, but still based on one or two
tables), is this view will be changed when some values in the based tables
changed?

Thanks

          Followup:

see chapter 6 of Oracle Reports Developer Release 6i: Building Reports,

http://technet.oracle.com/docs/products/reports/content.html
you would open the ref cursor in the database.

VIEWS do not negatively impact performance.  a view after all is nothing more
then a STORED QUERY.  The view text will be substituted in the query -- it is AS
IF the view text was written in your query.  Do not be afraid of views, they are
an excellent tool.

The view will not be changed when some values are in the base tables -- The view
is just a stored query.  The RESULTS returned by the view will be different as
the underlying data changes (since they are nothing more then a stored query)

      Rating: 5
Packaging
      everything...  March 13, 2002
Reviewer:
      Yogeeraj  from Mauritius
Hello,

Very interesting feature indeed.

however.....

Was trying to put everything in a package. Does not seem to work!! Thanks for
looking into it...

Best Regards
Yogeeraj
====================================================
SQL> @connect yd/yd@mydb
Connected.
yd@MYDB.CMT.MU> SQL> create or replace type virtual_table_type as table of
number;
  2  /

Type created.

yd@MYDB.CMT.MU> create or replace function virtual_table( p_num_rows in number )
return virtual_table_type
is
l_data virtual_table_type := virtual_table_type();
begin
 for i in 1 .. p_num_rows
  loop
  l_data.extend;
   l_data(l_data.count) := i;
  end loop;
 return l_data;
end;
/
Function created.

Elapsed: 00:00:00.24
yd@MYDB.CMT.MU>

yd@MYDB.CMT.MU> select * from TABLE( cast ( virtual_table(5) as
virtual_table_type ) );

COLUMN_VALUE
____________
           1
           2
           3
           4
           5

5 rows selected.

Elapsed: 00:00:00.05
yd@MYDB.CMT.MU>
---------------------------------------
NOW. I am packaging everything...
---------------------------------------
yd@MYDB.CMT.MU>
CREATE OR REPLACE PACKAGE test_pkg IS
type virtual_table_type is table of number;
function virtual_table( p_num_rows in number )
   return virtual_table_type;
END;
/
Package created.

Elapsed: 00:00:00.03

yd@MYDB.CMT.MU>
CREATE OR REPLACE PACKAGE BODY test_pkg IS
function virtual_table( p_num_rows in number )
   return virtual_table_type
   is
     l_data virtual_table_type := virtual_table_type();
 begin
      for i in 1 .. p_num_rows
      loop
                    l_data.extend;
                    l_data(l_data.count) := i;
       end loop;
       return l_data;
 end;
END;
/
Package body created.

Elapsed: 00:00:00.19
yd@MYDB.CMT.MU>
yd@MYDB.CMT.MU> select * from TABLE( cast ( test_pkg.virtual_table(5) as
test_pkg.virtual_table_type ) );
select * from TABLE( cast ( test_pkg.virtual_table(5) as
test_pkg.virtual_table_type ) )
                     *
ERROR at line 1:
ORA-22905: cannot access rows from a non-nested table item

Elapsed: 00:00:00.02
yd@MYDB.CMT.MU>

yd@MYDB.CMT.MU> select * from TABLE( cast ( test_pkg.virtual_table(5) as
virtual_table_type ) );
select * from TABLE( cast ( test_pkg.virtual_table(5) as virtual_table_type ) )
                                     *
ERROR at line 1:
ORA-00902: invalid datatype

Elapsed: 00:00:00.08
yd@MYDB.CMT.MU>

yd@MYDB.CMT.MU> select * from TABLE( cast ( virtual_table(5) as
test_pkg.virtual_table_type ) );
select * from TABLE( cast ( virtual_table(5) as test_pkg.virtual_table_type ) )
                     *
ERROR at line 1:
ORA-22905: cannot access rows from a non-nested table item

Elapsed: 00:00:00.01
yd@MYDB.CMT.MU>
====================================================

          Followup:

In order to use SQL on this, you need to use SQL types, not PLSQL types.  PLSQL
is a layer on top of SQL, hence, SQL does not "see" PLSQL types.

You need to use the SQL types with create or replace type.  There is no other
way if you want to use this feature.  Think of the type as you would a VIEW --
its meta data, a stored definition.  Just as you would not put a view in a
package, you will not put this type in a package.

      Rating: 5
Thank
      you....Crystal clear answer....as always..;)  March 13, 2002

Reviewer:  Yogeeraj  from Mauritius
Hello,

Thank you it works...But i dunno if it makes sense what our Project Leader wants
to implement...
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
..A PACKAGE to hold all the TYPE definitions per project..
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Thanks you again for your time and guidance.

Best Regards
Yogeeraj
======================================================
SQL> @connect yd/yd@mydb
Connected.
yd@MYDB.CMT.MU> CREATE OR REPLACE PACKAGE test_pkg IS
function virtual_table( p_num_rows in number )
   return virtual_table_type;
END;
  /

Package created.

Elapsed: 00:00:00.33
yd@MYDB.CMT.MU> CREATE OR REPLACE PACKAGE BODY test_pkg IS
function virtual_table( p_num_rows in number )
   return virtual_table_type
   is
     l_data virtual_table_type := virtual_table_type();
     begin
      for i in 1 .. p_num_rows
      loop
                    l_data.extend;
                    l_data(l_data.count) := i;
      end loop;
      return l_data;
 end;
END;
/

Package body created.

Elapsed: 00:00:00.18
yd@MYDB.CMT.MU> select * from TABLE( cast ( test_pkg.virtual_table(5) as virtua
l_table_type ) );

COLUMN_VALUE
____________
           1
           2
           3
           4
           5

5 rows selected.

Elapsed: 00:00:00.18
yd@MYDB.CMT.MU>

          Followup:

but you cannot -- so it isn't relevant what they *want*.  It is a simple matter
of what you can (and cannot) do.

      Rating: 4
Can I pass
      parameter to function in the view?  August 26, 2002

Reviewer:  Jay Shi  from Seminole, FL
This is very useful for what I am doing now, but how could I pass parameter to
function?

The following is the view I am using, and GlSales is  PL/SQL function with one
parameter runDate,
===
Create View GL_View AS
SELECT
    transactiondate
   ,postingdate
   ,networknumber
   ,location
   ,transactioncode
   ,transactiontype
   ,SUM(quantity)         quantity
   ,SUM(dollaramount)     dollaramount
   ,isdropship
   ,debitorcredit
   ,itemnumber
   ,itemvariant1
   ,itemvariant2
   ,itemvariant3
  FROM
    TABLE (CAST(GlSales(runDate)  AS GlSalesTableType))
  GROUP BY
    transactiondate
    ,postingdate
    ,networknumber
    ,location
    ,transactioncode
    ,transactiontype
    ,isdropship
    ,debitorcredit
    ,itemnumber
    ,itemvariant1
    ,itemvariant2
    ,itemvariant3;

If I use Func(parm) like above, it gets error
    TABLE (CAST(GlSales(runDate)  AS GlSalesTableType))
                        *
ERROR at line 64:
ORA-00904: invalid column name
Thanks.

          Followup:

It would have to be something more like:

...
  FROM
    TABLE (CAST(GlSales( to_date( userenv('client_info'), 'dd/mm/yyyy' ) )
                                            AS GlSalesTableType))
  GROUP BY
....

and you would then:

exec dbms_application_info.set_client_info( '01/01/2002' );
select * from gl_view;

      Rating: 5
Followup
      pipelined function questions.  April 09, 2003

Reviewer:  Kashif  from Houston, TX
Hi Tom,

Thanks for the explanation on pipelined functions. What do you mean when you
refer to 'suspend' in your response to the original poster? What does suspend
mean in this context? Also, can you give us examples of situations where a
pipelined function might come in handy? Finally, is there a context switch
involved when a pipelined function is called (it looks like there is), or any
other potential performance issues? Thanks in advance.

Kashif

          Followup:

the suspend means -- when you call pipe row, you "suspend" your procedure and
control can return to the client.

Do this

ops$tkyte@ORA920> create or replace type virtual_table_type as table of number
  2  /
Type created.

ops$tkyte@ORA920> create or replace function virtual_table( p_num_rows in number
)
  2  return virtual_table_type
  3  PIPELINED    -- NOTE the pipelined keyword
  4  is
  5  begin
  6      for i in 1 .. p_num_rows
  7      loop
  8          dbms_output.put_line( 'going to pipe' );
  9          pipe row( i );
 10          dbms_output.put_line( 'done pipeing' );
 11      end loop;
 12
 13      return; -- note return with no value!
 14  end;
 15  /
Function created.

ops$tkyte@ORA920> create or replace package my_pkg
  2  as
  3          cursor c is
  4      select * from TABLE( virtual_table(5) );
  5  end;
  6  /
Package created.

ops$tkyte@ORA920> begin
  2          dbms_output.put_line( 'opening' );
  3          open my_pkg.c;
  4          dbms_output.put_line( 'opened' );
  5  end;
  6  /
opening
opened

PL/SQL procedure successfully completed.

Ok, so we opened the cursor that references our function.  Our function did
nothing so far -- we've not seen any output from it

ops$tkyte@ORA920> declare
  2          l_n number;
  3  begin
  4          dbms_output.put_line( 'fetching' );
  5          fetch my_pkg.c into l_n;
  6          dbms_output.put_line( 'fetched ' || l_n );
  7  end;
  8  /
fetching
going to pipe
fetched 1

PL/SQL procedure successfully completed.

Ahh, but here our procedure started to run, it printed out going to pipe --
and then suspended itself.  It is in the middle of execution but control
obviously returned to the client!

ops$tkyte@ORA920> /
fetching
done pipeing
going to pipe
fetched 2

PL/SQL procedure successfully completed.

and here you can see it picked up again -- done piping AND THEN going to pipe.
 It is suspended on the pipe row call...

Where might a pipelined function come in handy?  Use your imagination.  A
complex transformation (ETL), getting feedback from a long running procedure as
it is running (select * from TABLE(
long_running_thing_that_pops_out_messages_as_it_processes) )

anywhere you can think of.

Yes, there is a context switch, nothing for free here.

      Rating: 4
Very
      Useful  April 09, 2003
Reviewer:  Randy
       from Chicago,IL
I wonder how do you use this method to return multiple columns. Is it possible
to return multiple columns (like empono and salary) using table type in Oracle8i
way ?

Thanks,

Randy

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4447489221109
for an example

      Rating: 2
Not in
      Oracle9i  April 10, 2003
Reviewer:  Randy
       from Chicago,USA
I asked if it is possible by Oracle8i way. The link show how to implement in
using PIPELINE which is was not there prior to 9i. Can you show how you can do
it in Oracle8i ?

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:110612348061
thats a single column -- but just merge the two examples together -- the
function just needs return a collection of (anything).

April 11, 2003 Abhijit -- Thanks for the question regarding
      "passing arrays into pl/sql stored procedures", version 8.1.5

      You Asked
Dear Sir,

I need to pass a Java String(or any other) array datatype into a
PL/SQL stored procedure. I am able to pass String (received as
VARCHAR2) and int (recd. as NUMBER) datatypes.

 How do I have an array IN parameter in such stored procedure?
Since no OAS is being used, I cannot declare a variable as
"owa_util.ident_arr". Is there a similar array structure?

rgds, Abhijit.

      and we said...
For all of the info -- see

http://technet.oracle.com/doc/oracle8i_816/java.816/a81354/oraarr3.htm#1056648
Here is a quick and dirty example showing PLSQL calling Java
calling PLSQL and passing an array from Java to PLSQL.  The
trick is to use a SQL table type -- not a PLSQL type (eg: create
the type OUTSIDE of plsql -- that way Java can in fact bind to
it).  Java cannot bind to PLSQL table types (eg: like
owa_util.ident_arr was -- that was a plsql table type -- not a
SQL type).

ops$tkyte@DEV816> create or replace
  2  and compile java source named "ArrayDemo"
  3  as
  4  import java.io.*;
  5  import java.sql.*;
  6  import oracle.sql.*;
  7  import oracle.jdbc.driver.*;
  8
  9  public class ArrayDemo
 10  {
 11  public static void passArray() throws SQLException
 12  {
 13      Connection conn =
 14          new OracleDriver().defaultConnection();
 15
 16      int intArray[] = { 1,2,3,4,5,6 };
 17
 18      ArrayDescriptor descriptor =
 19        ArrayDescriptor.createDescriptor( "NUM_ARRAY", conn
);
 20
 21      ARRAY array_to_pass =
 22         new ARRAY( descriptor, conn, intArray );
 23
 24      OraclePreparedStatement ps =
 25          (OraclePreparedStatement)conn.prepareStatement
 26          ( "begin give_me_an_array(:x); end;" );
 27
 28      ps.setARRAY( 1, array_to_pass );
 29
 30      ps.execute();
 31
 32  }
 33
 34  }
 35  /

Java created.

ops$tkyte@DEV816>
ops$tkyte@DEV816>
ops$tkyte@DEV816> create or replace type NUM_ARRAY as table
of number;
  2  /

Type created.

ops$tkyte@DEV816>
ops$tkyte@DEV816> create or replace
  2  procedure give_me_an_array( p_array in num_array )
  3  as
  4  begin
  5      for i in 1 .. p_array.count
  6      loop
  7          dbms_output.put_line( p_array(i) );
  8      end loop;
  9  end;
 10  /

Procedure created.

ops$tkyte@DEV816>
ops$tkyte@DEV816> create or replace
  2  procedure show_java_calling_plsql
  3  as language java
  4  name 'ArrayDemo.passArray()';
  5  /

Procedure created.

ops$tkyte@DEV816>
ops$tkyte@DEV816> set serveroutput on
ops$tkyte@DEV816> exec show_java_calling_plsql
1
2
3
4
5
6

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating:
      4
  March 18, 2001
Reviewer:
      A reader

      Rating:
      4
  July 06, 2001
Reviewer:  Karol
      Brejna  from Poland

      Rating: 5
Passing SQL
      arrays to stored procedures in JSP  August 14, 2001

Reviewer:  Dane Laverty  from Livermore, CA USA

This is the only useful information I found anywhere regarding passing SQL
arrays as parameters in stored procedures.

      Rating: 5
good
      resource  November 13, 2001
Reviewer:
      Rachel  from Berkeley, CA
Finally a straightforward solution.  Thanks!

      Rating: 5
Right on the
      money!  August 22, 2002
Reviewer:  Vadim
      Dolt  from VA USA

      Rating: 4
Great but is it
      possible to pass a Vector to a procedure?  January 20, 2003

Reviewer:  Paul  from UK
Tom, Is it possible to pass a Vector to a stored procedure in the same manner?
Or, do you have to read the Vector into an array first?

          Followup:

if by "vector" you mean the java class - you'll need to put it into a SQL
datatype first.

      Rating: 5
How to pass
      Array from SQL to java stored procedure?  March 26, 2003

Reviewer:  Steve  from NYC, NY
Hi Tom,

How about a reverse ?
i.e., how to pass Array from SQL or PL/SQL to java stored procedure?

Thanks!

Steve

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:986230504001

      Rating: 3
How to pass
      Array from SQL to java stored procedure?   March 26, 2003

Reviewer:  Steve  from NYC, USA
Hi Tom,

I am sorry I did not give you enough info
the following code is what I am trying to do.

create or replace package myTestPackage1
as
     Type STRING_ARRAY IS Table of varchar2(32);
     procedure test_1;

     procedure  testArray(description varchar2, a1 STRING_ARRAY, a2
STRING_ARRAY) as language java
     name ' testjava1.testArray(java.lang.String, java.lang.String[],
java.lang.String[])';

end;
/

create or replace package body myTestPackage1
as
  procedure test_1
  is
      testdata STRING_ARRAY := STRING_ARRAY();
      realdata STRING_ARRAY := STRING_ARRAY();
  begin
      testdata.extend(3);
      realdata.extend(3);

      select 'a', 'b', 'c'
      into testdata(1), testdata(2), testdata(3)
      from dual;

      select 'a', 'b', 'c1'
      into realdata(1), realdata(2), realdata(3)
      from dual;

      testArray('arrray', realdata, testdata);

  end test_1;
end;
/

CREATE OR REPLACE AND COMPILE java  source named "testjava1"
AS
import java.sql.*;
import oracle.jdbc.driver.*;
public class testjava1
{

   public static void testArray(String description, String[] expectedData,
String[] realData)
       throws SQLException
    {
        System.out.println("assertArray" );
        for(int i=0; i<expectedData.length; i++ )
           System.out.println("assertArray" + expectedData[i] + ", " +
realData[i]);
    }
}
/

and when I run
SQL> exec myTestPackage1.test_1
BEGIN myTestPackage1.test_1; END;

*
ERROR at line 1:
ORA-21700: object does not exist or is marked for delete
ORA-06512: at "SRS.MYTESTPACKAGE1", line 0
ORA-06512: at "SRS.MYTESTPACKAGE1", line 19
ORA-06512: at line 1

Thanks!

Steve

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3696816290928

      Rating: 5
Thank
      you!  March 26, 2003
Reviewer:  Steve

      Rating: 5
How to pass
      Array from SQL to java stored procedure?  March 27, 2003

Reviewer:  Steve
Hi Tom,

After runing the example on your book, I figured out
Why my code doesn't work. The difference is that your
code Defines the STRARRAY outside the package, Mine just
define it inside package. My question is Why I can't define String_array inside
a package, even though I reference it with Package prefix. Does Oracle design
that way?

Thanks!

Steve

create or replace package myTestPackage1
as
     Type STRING_ARRAY IS Table of varchar2(32); -- Have to define outside
package
     procedure test_1;

     procedure  testArray(description varchar2, a1 STRING_ARRAY ) as language
java
     name ' testjava1.testArray1(java.lang.String, oracle.sql.ARRAY)';

end;
/

create or replace package body myTestPackage1
as
  procedure test_1
  is
      testdata STRING_ARRAY := STRING_ARRAY();

  begin

      realdata.extend(3);

      select 'a', 'b', 'c1'
      into realdata(1), realdata(2), realdata(3)
      from dual;

      myTestPackage1.testArray('arrray', realdata);

  end test_1;
end;
/
show err;

CREATE OR REPLACE AND COMPILE java  source named "testjava1"
AS
import java.io.*;
import java.sql.*;
import java.math.*;
import oracle.sql.*;
import oracle.jdbc.driver.*;
public class testjava1
{

   public static void testArray1(String description, oracle.sql.ARRAY
expectedData) //,oracle.sql.ARRAY realData)
       throws SQLException
    {
        System.out.println("assertArray" );
        String[] ss = (String[])expectedData.getArray();
        for(int i=0; i<ss.length; i++ )
           System.out.println("assertArray" + ss[i] );
    }
}
/

          Followup:

PLSQL types are known to exactly PLSQL and PLSQL alone.

SQL types -- stored in the data dictionary -- are visible to all, usable by all.

you must use a SQL type via create type.

      Rating: 5
Very
      Helpful  April 02, 2003
Reviewer:  Rusty
      Bullerman  from Austin, TX
This answered the questions no one else is asking on this subject.  I perused
Oracle documentation as well as the highly-recommended PL/SQL books and didn't
find this info.

Thanks,
Rusty Bullerman

      Rating:
      4
Excellent...  April 04, 2003
Reviewer:
      So
I can't begin to describe how useful this website has been to our project. This
solution alone is worth its word count in grams of gold!

Our Java developers now want to pass all parameters in name-value arrays. Is
there a downside (or two) to this ?

e.g. instead of

  myPkg.myProc(resultSet,10,1,'Abc',1,10000,45......etc.)

now use:

      pNameArr = ['P1','P2','P3','P4','P4','P6'....etc]
      pValueArr= ['10','1','Abc','1','10000','45'....etc]

      myPkg.myProc(resultSet,pNameArr,pValueArr)

          Followup:

what version?

      Rating:
      5
  April 04, 2003
Reviewer:  So

Sorry  - it's 9.2EE

          Followup:

sure -- it'll work fine -- i would suggest the the plsql routine that gets the
two collections would then put them into a plsql index by array

create procedure foo( names in array, values in array )
as
   type associativeArray is table of varchar2(255) index by varchar2(30);

   data associativeArray;
begin
   for i in 1 .. names.count
   loop
       data(names(i)) := values(i);
   end loop;

then, plsql can quickly lookup data('fieldX').  this is 9iR2 and up only...
before that -- plsql will have to do a linear search for each field

      Rating: 5
WRT
      associativearray posting  April 04, 2003
Reviewer:
      A reader
How about handling an Array of Records in the similar fashion thru associative
arrays ? Any Example will help. Is it possible ? the example u gave was like
pivoting rows of parameters into columns which is feed to a input array.
  If we are dealing with an array of records how do we handle it best if
Associative array may not be the right way . version 9.2

          Followup:

ops$tkyte@ORA920> declare
  2          type array is table of emp%rowtype index by varchar2(25);
  3
  4          l_data array;
  5  begin
  6          for x in ( select * from emp )
  7          loop
  8                  l_data(x.ename) := x;
  9          end loop;
 10
 11          dbms_output.put_line( l_data('BLAKE').empno );
 12  end;
 13  /
7698

PL/SQL procedure successfully completed.

      Rating: 5
One more
      Carification  April 05, 2003
Reviewer:
      A reader
wrt associative example given above
Followup:
ops$tkyte@ORA920> declare
  2          type array is table of emp%rowtype index by varchar2(25);
  3
  4          l_data array;
  5  begin
  6          for x in ( select * from emp )
  7          loop
  8                  l_data(x.ename) := x;
  9          end loop;
 10
 11          dbms_output.put_line( l_data('BLAKE').empno );
 12  end;
 13  /

This question arises because I have to do something similar to name value pairs
to accept values from java.
But how to handle an array of records ? name value pair of associative arrays
wont work here.
The option i see from your site is each time define a new sql object type and
create a table based on that.
Is there any generic approach which will aviod me from having to create lots of
objects types for different procedure parameters.

example : emp%rowtype  I have to get values from java so obviously it will be
sqltype.
But this means for every procedure which gets array from java I will have to
create that object type as well that table type.
Can u suggest a better solution.

          Followup:

there is a necessary and unavoidable "impendance mismatch" between Java and
PLSQL...

I don't know what you are trying to do -- however, getting an associative array
from plsql to java just isn't "going to happen", nor would you want it to
really.  In java the natural way to do this will be via a hash table or
something similar.  In plsql -- a plsql table type.

You cannot have a SQL collection that is indexed by a varchar2 -- there is no
corresponding SQL type here.

You cannot bind any 3gl to a PLSQL record type (unless that 3gl just happens to
be plsql).

Java will interface to PLSQL via collections and if you want to be obscure,
collections of objects.  It'll be much more natural and easy for you to
interface via collections of simple scalars and in fact, I would propose you
actually interface via global temporary tables if you want the least amount of
code on both ends.  Java can insert easily -- PLSQL can select.  Batch the
insert and bulk collect and it'll be rather efficient as well.

      Rating: 5
One more
      Carification  April 05, 2003
Reviewer:
      A reader
wrt associative example given above
Followup:
ops$tkyte@ORA920> declare
  2          type array is table of emp%rowtype index by varchar2(25);
  3
  4          l_data array;
  5  begin
  6          for x in ( select * from emp )
  7          loop
  8                  l_data(x.ename) := x;
  9          end loop;
 10
 11          dbms_output.put_line( l_data('BLAKE').empno );
 12  end;
 13  /

This question arises because I have to do something similar to name value pairs
to accept values from java.
But how to handle an array of records ? name value pair of associative arrays
wont work here.
The option i see from your site is each time define a new sql object type and
create a table based on that.
Is there any generic approach which will aviod me from having to create lots of
objects types for different procedure parameters.

example : emp%rowtype  I have to get values from java so obviously it will be
sqltype.
But this means for every procedure which gets array from java I will have to
create that object type as well that table type.
Can u suggest a better solution.
"SORRY I MISSPELLED MY EMAIL, VERY VERY VERY SORRY"..JUST CORRECTED IT

      Rating: 5
Pls add these
      to the ratings
      ************************************************  April 05,
      2003
Reviewer:  A reader
Thanx much for your brilliant solns.
Will that also mean I user global temp table even for non array values to ne
passed to plsql from java .. in this case it will be just a single row in global
temp table for the name value pair solution.So we totally avoid using
associative arrays.

      Rating: 5
right on
      !!  April 07, 2003
Reviewer:  So
Tom

Thanks for validating the name-value approach in our java/plsql environment and
pointing out associative arrays.

When passing a ref cursor back to java should it be declared IN OUT or OUT (i.e
by value or by reference). Is it possible to summarize how all this works in
n-tier with regards to memory pointers and structures & table access?

p.s. What I've done and recommend is to buy 2 copies of your book. One for home
and one for work. Saves lugging it around! This is the only Oracle book that I
consider valuable enough to use a 'duplicate' approach on !!

          Followup:

 it'll work but it will be almost certainly slower then using formal named
parameters will be.

      Rating: 5
use OUT for
      self defined type   April 10, 2003
Reviewer:
      Steve  from NYC, USA
Hi Tom,

I have a quick question.
In your example, you can use OUT for  self defined type array, but Why I can't?
Thanks!

imm@IMMP> CREATE OR REPLACE TYPE STRING_ARRAY AS TABLE OF VARCHAR2 (64);
  2  /

Type created.

imm@IMMP> show err;
No errors.
imm@IMMP>
imm@IMMP> create or replace package sqlunit
  2  as
  3       procedure assertQuery(description varchar2, a1 STRING_ARRAY, a2
STRING_ARRAY) as language java
  4       name 'Assert.diffQuery(java.lang.String, java.lang.String,
java.lang.String)';
  5
  6       procedure fetchRefCursorInto(pname in varchar2, parameters in
STRING_ARRAY, fetchfields out STRING_ARRAY )
  7       as language java name 'Until.fetchRefCursorInto(java.lang.String,
oracle.sql.ARRAY , oracle.sql.ARRAY)';
  8
  9  end sqlunit;
 10  /

Warning: Package created with compilation errors.

imm@IMMP> show err;
Errors for PACKAGE SQLUNIT:

LINE/COL ERROR
-------- -----------------------------------------------------------------
6/6      PL/SQL: Declaration ignored
7/23     PLS-00235: the external type is not appropriate for the parameter
imm@IMMP>

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:8908169959941
shows how to pass an array as IN and an array as OUT

      Rating: 4
Very Helpful
      info. - How about passing 2 D array  May 19, 2003

Reviewer:  Baldeep  from Auburn Hills, MI USA

Hi Tom,

I am able to pass single Dimensional array to Oracle. I am trying to pass a
2-Dimensional Object array to Oracle from java using StructDescriptor and
ArrayDescriptor.

I get error "java.sql.SQLException: Inconsistent java and sql object types"
while creating the STRUCT.

Could you please suggest me a solution.

Here is the code I am using:

Java code:
/******************************************/
public void addUsers(List users) throws SQLException {
    OracleCallableStatement ocs = null;
    try {
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection dbConn = DriverManager.getConnection( URL, username, password);

 //*******Input parameters for the stored proc
StructDescriptor desc1 = StructDescriptor.createDescriptor("EFUSERARRVALUES",
dbConn);

ArrayDescriptor desc2 = ArrayDescriptor.createDescriptor("USERARRAYVALUES",
dbConn);
        //User data
        List lstUsers = users;
        int size = lstUsers.size();

        Object[] userArrObj = new Object[size];
        Object[][] userRecObj = new Object[size][4];

        //Struct obj and arrays
        for (int j = 0; j < size; j++) {

            User user = (User) lstUsers.get(j);
            userRecObj[j][0] = user.getLdapUserId();
            userRecObj[j][1] = user.getFirstName();
            userRecObj[j][2] = user.getLastName();

   userArrObj[j] = new STRUCT(desc1, dbConn, userRecObj[j]);

   }

    ARRAY userArr = new ARRAY(desc2, dbConn, userArrObj);

 //*****************Run the stored proc ****/
ocs = (OracleCallableStatement) dbConn.prepareCall("{call
eforms.ADD_USERS(?)}");

ocs.setArray(1, userArr);
ocs.registerOutParameter(1, OracleTypes.ARRAY, "USERARRAY");

        //Execute the proc
        ocs.execute();
        dbConn.commit();

    } catch (Exception e) {

        System.out.println("e>"+e);

    } finally {
        if (ocs != null)
            ocs.close();
    }
}
/****************************************************/

Procs:
/****************************************************/
CREATE TYPE USERARRAY AS OBJECT(user_id NUMBER,ldap_user_id varchar2(100),
first_name varchar2(100),last_name varchar2(100))
/

CREATE TYPE USERARRAYVALUES AS TABLE OF USERARRAY
/

PROCEDURE ADD_USERS(usrarr IN OUT USERARRAYVALUES) IS

l_user_id NUMBER;

BEGIN
FOR i IN 1..usrarr.COUNT
LOOP
BEGIN
SAVEPOINT start_transaction;

SELECT    DECODE(MAX(user_id)+1,NULL,1,MAX(user_id)+1)
INTO l_user_id FROM EF_USER_ADDR;

INSERT INTO EF_USER_ADDR (user_id,ldap_user_id,first_name,last_name)VALUES
(l_user_id,usrarr(i).ldap_user_id,usrarr(i).first_name,     usrarr(i).last_name)

END;
COMMIT;
END LOOP;

 EXCEPTION

    WHEN OTHERS THEN
    RAISE;

END Add_Users;

/****************************************************/

Thanks
Baldeep

      Rating:
      5
  August 18, 2003
Reviewer:
      A reader

      Rating: 3
Running as
      non-owner  August 19, 2003
Reviewer:  Carol
       from Canada
I created grants and synonyms so that I should be able to run the package from
an account other than the owner.  I haven't been able to get it to work.  Is it
me or is it possible?

My latest error is:

ORA-29532: Java call terminated by uncaught Java exception:
java.sql.SQLException: invalid name pattern: TESTUSER.TESTARRAY

Any help is greatly appreciated.

Thanks.

April 11, 2003 Nikunj -- Thanks for the question regarding "How
      not to ask a question.", version 6

      You Asked
hi,

I my form i have plsql procedures through those procedures i am inserting data
into one table x after completing all those procedure i am inserting data from
table x to table y through insert into table y as select * from table x;

successfully every rows are inserted in table x but insert into table y as
select * from table x; statement fails when i run the form first time.
then if i comment that statement & run the form it works fine.
after that i remove that comment & run the form it works fine.

why at the first time that statements fails ?

Thanks,
Nikunj

      and we said...
This is just a gentle reminder to all out there.  Here is how NOT to ask a
question here.  Been getting lots and lots of these recently.

gee, I don't know.

you give me nothing to work with -- you know, like error messages, error codes
-- nothing.

must be gremlins I guess.

The very first thing you had to read and say "i read that" by checking a box
was:

   1. Please include your definition of "it doesn't work". I will need all error
codes, messages and a complete description of the failure. When you drop your
car off to be fixed, you don't just say "it doesn't work". I cannot answer a "it
does not work" question without additional information.

Review &
      Followup

      Rating: 1
So why the heck
      you published this ?  December 25, 2002
Reviewer:
      Hersen  from CA
So why the heck you even published this "you-know-what" ?
I thought you publish selectively....
Because it's Christmas ?

          Followup:

bah humbug.

      Rating: 1
I want to ask
      Questions too !  December 26, 2002
Reviewer:
      Prashant  from Mumbai, India
I want to ask questions too, but the status on ur site is always 'Sorry I have a
large backlog right now, please ask a question later'. When will this backlog be
finally over.

When ??

          Followup:

well, when people stop putting things in my limited queue that violate these
rules that everyone submmitting a question has to READ and click a box saying
"yes, I read that"

   1. Please include your definition of "it doesn't work". I will need all error
codes, messages and a complete description of the failure. When you drop your
car off to be fixed, you don't just say "it doesn't work". I cannot answer a "it
does not work" question without additional information.

You know how many of these I get?  things like "it didn't work" -- wow, how
clear is that..

Every question asked like that prevents YOU from asking a real question.

   2. I do not handle installation questions. They are best dealt with by
support or by if you have trial software by emailing to web-sup@us.oracle.com. I
just have not installed every dot release on every platform personally, nor do I
have the time to do so. It would be best if you do not post installation
questions here (it will prevent others from asking questions I can answer)

How many questions I get like this -- must be at least 1 in every batch.  That
equates to 10%.  And my answer is always the same "sorry, I've never installed X
on Y myself -- please open a tar with support or try metalink or the discussion
forums on otn.oracle.com"

   3. Please try the SEARCH feature....it really works. About 1/3 of the
questions on AskTom are answered by ME searching AskTom and sending you a link
to an existing answer. If I can do it -- so can you!

probably 20% of my answers are:

and when you searched for <their SUBJECT here>, what did you find?  I found your
answer -- give it a try!

   4. Do not submit multi-part questions. If you ask more then 3 or 4 separate
questions in a question -- I will not answer it. The reviews on big multi-part
questions indicate others do not like them (too hard to follow) and they take
lots of my time to answer (preventing me from answering other questions). I will
not answer large multi-part questions.

   5. Do not submit 5 or 6 questions at once. Give others a chance. Be fair.

this is ignored lots....  sort of a corralary to #4 I guess - they obeyed #4
but ignored #5 or vice versa

   6. I am mostly a database guy. I do not, have not worked with every possible
Oracle tool. I have limited Forms experience (some), I have never used reports,
I've done Java, lots of Pro*C, some OCI. I never have (nor will) use VB, ODBC,
ASP's or any related technologies. I use open systems. Don't be surprised (or
mad) if I send you to
http://otn.oracle.com/
to the discussion groups for questions specific to a
certain development environment. I'll try to answer but if I can't -- its that
simple -- I can't.

that still gets me tons of forms questions -- 99% of which I have to answer
with

o i don't have forms installed
o i haven't written a form since 1995
o i don't have windows installed on any computers, so no I cannot tell you how
  to get forms to do something windows specific
o please goto otn.oracle.com (then, the invariably get mad...)

Also remember -- one of me, really.

Typical times best to try and ask:

8-9am EAST COAST (gmt-5) time.... I get in around 7am and clear the queue...
1pm   EAST COAST time.... after lunch...
4-5pm EAST COAST time.... before going home...
9-10pm EAST COOAST time.... after putting the kiddies to bed....

and i travel lots -- so no assurances that I'll take ANY new questions on a
given day...

      Rating: 5
PAY NO
      MIND  December 26, 2002
Reviewer:  JR
       from WALNUT, CA
They come asking for help and are completely ungrateful.
Pay no mind. Next time, don't even post their message, so
they can't fix & retry.

      Rating: 2
I want ask some
      Question too  December 26, 2002
Reviewer:
      sonwy  from China
I want ask some Question too!But every time you are not in State,what's time you
can help me?

      Rating: 3
HOW TO ASK TO
      TOM  December 26, 2002
Reviewer:
      cachitok@yahoo.com
If you want to ask to Tom Kyte, you must be very patiente and persist, for
example every 2 mn , in the period of time when he enables the questions.

Every one who asks him, have to press F5 several times in

http://asktom.oracle.com/
and several times we can't ask him.

There is no other way.

:) Merry Chrismas and Happy New year

      Rating:
      5
Amen!  December 26, 2002
Reviewer:
      Renee  from Nebraska, USA
We don't like it when our clients come to us without details - we just have to
go 'round and 'round.

So why an I.T. person would do that someone else is not logical because we
should all know how frustrating it is! :)

      Rating: 5
Totally Agree
      with JR from WALNUT, CA   December 26, 2002

Reviewer:  Disgusted with Childish Ingrates
Some people behave as if they are paying Tom to get their questions answered. In
fact even if they were paying him, their attitude is unacceptable.

These people need to grow up.

Please ignore such folks, Tom, and keep up with your great work. Most of us here
value you.

Thanks!

      Rating: 5
I shouldn't say
      this, but...  December 26, 2002
Reviewer:  Juan
      Guascarancas Pena  from Mexico
Ok ok, Tom asked me not to mention it on the Website, but if you want your
questions answered, you've got to pay him $50.00. Fifty bucks, mind you! The
only other way is to place comments all over the site with abreviations, like u
for you, r for are, ur for your, etc. Tom likes abreviations so much that he
feels compeled to let you ask a question.

Or you did you really believe he works for the pleasure of working?

Juan

      Rating: 5
If asking a
      question cost $50 that would be quite a bargin!  December
      26, 2002
Reviewer:  Jim Kennedy  from Portland, OR
      USA
Keep up the excellent work.  One suggestion.  Maybe on the refiew form put a
check box for no reply needed. (For this posting I don't expect a reply.)

      Rating:
      3
  December 26, 2002
Reviewer:
      Cachitok@yahoo.com
You must know what you get here.

You Pay 50U$ for any Oracle DBA, that solves your problem,

In this case, even if you would pay that 50$.

Having Tom answers,
you would have the privilege not to be answered
for any Oracle dba, else for an excellent Oracle expert.

I think many of us, at least me, I feel very culprit and bad, for asking to Tom
sometimes without paying him. (I never paid nothing)

And I must add that Tom takes the time to answer it with TOO PATIENTE, even the
most stupid questions I and other people had.

He is an excellent person.

      Rating: 5
Well
      put  December 26, 2002
Reviewer:  Scott
       from La Jolla, CA
Really, it was quite a gentle reminder, and the example about the mechanic is
quite appropos.

The technical expertise given in this forum is worth a lot. Tom explained in
Oramag that the "overbooking" message comes up to help keep the site from being
overloaded.

If you are a beginner, how 'bout trying a post on the forums at
technet.oracle.com? You may get your simpler questions answered more quickly. :)

      Rating:
      3
  December 26, 2002
Reviewer:
      cachitok@yahoo.com
I think that if you ask here.
You should buy his book.

It's 5 stars on Amazon.
There you'll find many dba questions answered.
The chapters I read are really good, you learn a lot.

      Rating: 5
Re: I want to
      ask Questions too !  December 26, 2002
Reviewer:
      CJ  from Albany, NY USA
If you "hang out" here a bit, you'll get a chance. I usually look at new
questions a couple of times a day and every few days I hit it when the "submit a
new question" button is there. So, usually, what bothers me more is not having a
question to ask. :-)

      Rating: 5
A different way
      to answer the question  December 27, 2002

Reviewer:  Joel  from Columbus, OH USA
Tom...if I were you, I think would have addressed the question a little
differently.  Next time, you should answer in the same form of complete
gibberish in which the question was asked.

For example, I would have said something like:

"Well, the problem is very clear.  Instead of using the select * from table x to
insert into table y, using PL/SQL which calls both table x and table y, I would
consider instead having it go through a new global temporary table, call it
Z....then you can simply go from X to Y to Z.  If it fails the second time but
not the first, try it a third time, it might work again."

      Rating: 3
A last
      quote  December 29, 2002
Reviewer:
      A reader
I must add now we are talking about Tom,
That he even ask question in Saturday and Sunday
In this moment I saw it. I didn't gave attention to this before.
( I suppose he's not marry ) and I really hope he don't. ;)
Because we are his DBA childrens.

Thank you Tom, congratulations for your work and dedication.

      Rating: 5
How to add new
      field in Report Editor-Layout Model?  February 14, 2003

Reviewer:  S.V.NEMADE  from INDIA
In existing report I am adding new field, it is giving error number "RE-1213
Field'<field name> reference column '<column name>' at a frequency below its
group."

I have tried all the way. Can you suggest valuable information to me in easy
step by step way.

I am new user in reports file making.
Please reply
Regards

          Followup:

try otn.oracle.com under discussion forums -> developer

I've never written a report in my life ;)

      Rating:
      5
Dr.Watson-application error  February 14, 2003

Reviewer:  S.V.NEMADE  from INDIA
When I am running report file thru menu and taking the various type reports I am
getting Dr.Watson - application error occurs. Every time I have to close all
menu and again I have start from the step one.
Is it a problem of WINDOW-NT?
Can suggest regarding this solution.
Regards

          Followup:

support is pretty good at tracking down support issues like this.  seems to be a
good place to start.

      Rating: 5
is this a
      joke?  February 14, 2003
Reviewer:  kingy
       from philadelphia, PA
did you (the idiot reports 'developer') read *any* of the above? what does tom
have to do for you to understand that

1. he is on reports guy
2. he is not a windows guuy
3. he is not a mind reader (no reports version, db version etc...not that in
this case that info would help solve the prob...just a reminder of how this site
works)

you better start getting used to dr waston gpfs in oracle developer tools...

please stop wasting everyone's time...

      Rating: 3
Auto saving for
      expdat.dmp  April 10, 2003
Reviewer:
      S.V.NEMADE  from India
Sir,
How to make autosaving at particular time give for expdat.dmp file?

          Followup:

huh?

autosave makes some sense in a word processor (type a bit, save what I've typed
so that when MS-WORD crashes, I've not lost everything.  As I person who
wrote a long book using that product -- I was grateful that they put the CYA
feature in there many dozens of times)...

autosave in export?  don't get it.

      Rating:
      5
Autosave  April 10, 2003
Reviewer:
      A reader
Tom, what he probably meant is scheduling export to execute from time to time,
like with dbms_job or unix's crontab.

By the way, can you illustrate that or throw us some links ?

Regards.

          Followup:

$ man cron

      Rating:
      5
Hilarious!  April 10, 2003
Reviewer:
      PhaE  from The Netherlands
This WHOLE thread is about how to ask a question, and still ppl reply on THIS
thread in the EXACT way you're not supossed to :)

Cracks me up, hehehe..

Even better: While typing this, I see this on the right side of the screen;
=======================
Instructions
---------------
This forum is not
intended for additional
questions. If you
would like to ask
additional questions,
please go to Ask Tom.
=======================

My guess is that most questions are asked in the one way they should not :P

What I do not understand though;

Why do so much people think it is their RIGHT to ask you a question, and get an
answer? you don't work for them, they don't pay your bills, they do not have to
pay to see the Q&A, so what gives them the right to demand an answer or get
angry when you don't?

*Sigh*

Life is complicated as a DBA.

Hmmm reminds me of a signature I saw on comp.databases.oracle.server:

=======================
SELECT * FROM USERS
WHERE CLUE > 0;

0 rows returned.
=======================

      Rating: 5
A fan of Oracle
      and Tom  April 10, 2003
Reviewer:
      A reader  from Memphis, USA
Tom is a good example of being patient with those who are not as smart or
knowledgable as oneself!
Tom is a good example of a patient teacher!
Tom is a good example of how to work diligently through a problem and not assume
anything... like a good detective.

Thanks, Tom!

      Rating:
      5
Volunteer  April 10, 2003
Reviewer:
      Christo Kutrovsky  from Ottawa, ON Canada
Hello Tom,

I am volunteering to filter your question queue, on a "whenever I can" basis.
This is, if you provide me a way of reading the questions, I could mark them as
"did not read rules" and "did read rules" or give them a simple priority tag.
You could have several ppl doing that, and average it out.

I am certain, that other people will be interested the same. Thus you can give
priority to questions which were set "did read rules" and after time, don't even
look at the "did not read rules".

Contact me via email if interested, I'll be happy to help you.

      Rating: 2
good
      idea!  April 10, 2003
Reviewer:  Richard Ji
       from NYC, NY USA
Counting on people to follow rules is hard, which is why
we put business rules in our database to enforce it.  So,
I like the idea of some people act as moderator to filter
out questions.  This can certainly take burden off and save
time for Tom.

          Followup:

I don't think so.  I travel lots -- sometimes I even want time to myself.  So, I
control the queue, when it is on, when it is off.  Coordinating that with
someone -- introducing an unknown entity into the loop, not into that.

      Rating: 5
Re:
      Volunteer  April 11, 2003
Reviewer:
      A reader
I'd prefer to have Tom himself decide whether my question is worthy or not,
thanks.

      Rating: 5
Remember the
      name of this site  April 11, 2003
Reviewer:  A
      Reader
Please remember the name of this site is called "AskTom" not others.  Let Tom
make the decision.
Thank you.

      Rating: 4
good insight of
      asktom.com  April 11, 2003
Reviewer:  Sravan
       from Boston, MA
Tom is a very patient, smart, knowledgable and sensible person.
People, probably lack of correct education dont know how to ask right questions.
Be more sensible and rational when in asktom.com
Personally, this article explained what Tom is NOT good at, which is very
valuable to know, so I know exactly what I can ask in the future.

He is a great resource, and I think Tom's explanation/solutions are way superior
to metalink iTar personnel.

Keep up the good work.
- a great supporter and fan of Tom

      Rating:
      4
well  May 05, 2003
Reviewer:
      Richard  from NYC, NY
Well, we are merely making suggestions to Tom.
It's his site and he makes the call.

      Rating: 5
Buy Tom's
      books  September 28, 2003
Reviewer:  Peter
      Tran  from Houston, TX USA
If you want to express your gratification to Tom, then buy his books.  He's
written two and co-authored 2 more.  See:

http://www.amazon.com/exec/obidos/search-handle-url/index=books&field-author=Kyte,%20Thomas/103-3594226-2870243
Personally, I have 3 of the 4 books.

1)  They make great resources.
2)  Almost 90% of the time they'll answer your questions.

However, there is a better way to figure out when Tom makes asking question
available rather refreshing the askTom.com page every hour of every day.  (And
NO I do not have any special insider knowledge).

Any guess on how?  :)

      Rating: 5
Use
      Search  December 31, 2003
Reviewer:  Sami
       from NJ,USA
Dear All,

Before we ask the question we should read Oracle Manuals( Concepts/Admin
Guide/Developers Guide) and Tom's TWO great books (Expert one-on-oen Oracle and
Effective Oracle by Design). Still if you do not find an answer then check
SEARCH functionality in this site. 99% of the times we will find an answer by
searching this site.

Do not blame Tom --if you are too lazy to read/refer manuals & books or search
this site. Also do not expect him to do your job and he can only provide
guidance/advice/help.

Tom likes practical approach, so please cut & paste your SQL/PLSQL/... code with
output/error. If you are asking performance related issue then post your Query
with EXPLAIN PLAN and TKPROF output.

We are all really lucky to have this kind of wonderful resource.

April 11, 2003 lawrence -- Thanks for the question regarding
      "What limits the highwater mark for a rollback segment?", version
8.1.7.3.0

      You Asked
   This database is mainly a reporting environment with data loading and updates
occurring anytime the data becomes available so that it can get to the end users
as soon as possible.

   From the init.ora file, the max_rollback_segments=122.
The transactions_per_rollback_segment=5  and transactions=610.
Initial rollback extent=15,350 or_blocks = next_extent
with max_extents = 1017.

  This instance has 4 rollback segments with available space for
rollback=3,014,610 oracle blocks. 1 oracle block=16,384 bytes. This is a 500
gigabyte database with the total rollback space being about 49 gigabyte. The
rollback optimal size is set to 30,700 oracle blocks.

  The available space  3,014,610 / 4 rollback  segments seems to
imply that each rollback segment could grow as large
as  753,653 oracle blocks.  In the past year we have had 24 ora-01555 errors in
version 8.1.7.3.0  and the rollback highwater mark never gets larger that
260,949 oracle blocks. The rollback segments have reached this highwater mark
36 times in the past 74 weeks but only 24 of the 36 times the highwater mark was
reached did we get the ora-01555 error.

Why doesn't the highwater mark ever hit the 753,653 oracle_blocks?
How many oracle blocks can a single rollback segment grow to with
this setup?

      and we said...
the RBS's grow only when a MODIFICATION makes them grow.  If you have lots of
short, small transactions - you need very small rollback segments (we just keep
wrapping around and around) -- however, if you have long running queries at
the same time - you will run into serious 1555 issues.

The solution there -- use initial=next and set initial*MIN_EXTENTS to a size
LARGE enought not to wrap around in the time it takes for your longest running
query to go.

that is, suppose you watch your wraps on the RBS right now and find they wrap
every 5 minutes.  If you have a query that takes 20 minutes to run, you'll need
to build your RBS's to be PERMANENTLY 4 to 5 times the size they are now.  You
would take their current size, multiply that by 4 or 5 and rebuld them with
initial*MIN_EXTENTS to exceed that.

Or, you could just upgrade to Oracle9i -- set aside sufficient disk space -- and
set the undo_retention init.ora parameter and let us worry about the rest (how
many rbs's, how big and so on)

Review &
      Followup

      Rating:
      4
  April 09, 2003
Reviewer:  Pauline
      Wan  from NY, U.S.A
Tom,
Could you give the detail about how to watch RBS wrap aroud?

Thanks.

          Followup:

use statspack -- review the rollback segment report section.  there you'll find
undo bytes written during the period of observation -- as well as the rollback
segment sizes.  you can divide.

      Rating:
      5
Reader  April 10, 2003
Reviewer:
      A reader
"
suppose you watch your wraps on the RBS right now and find they wrap
every 5 minutes.  If you have a query that takes 20 minutes to run, you'll need
to build your RBS's to be PERMANENTLY 4 to 5 times the size they are now.
"

It seems you are talking about the RBS go One circle to
wrap to the first extent and not the "WRAPS" from
v$rollstat. Is this a true statement

Thanks

          Followup:

doh, updated that one.  thanks

April 11, 2003 sushil -- Thanks for the question regarding
      "intermedia type index on column varchar2(4000)", version 81.7

      You Asked
Hi Tom,

I am currently working on intermedia text search and am new to it.
I have a table which has got n no of columns and my client wants to
have a keyword search on many columns.
Since the column length is 4000 I was not able to create an index.
As far my understanding is that index key length cannot be more than 3218.

Now as per the requirement i have to perform keyword search on say 10 columns
with 4 having length 4000 and are of datatype varchar2.

The client says it is possible by using user_datastore option of intermedia type
indexes but according to me the column length equal to 4000 would be the issue.

Pls tell me how do I go about creating a procedure to do keyword search on 10
columns and 4 of them with length 4000 each or else provide me workaround
solution for the same.

Tks.
sushil.

      and we said...
interMedia indexes are very different from b*trees, b*trees have the key length
limit, interMedia does not.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:440419921146
for an example of a user datastore.  Here your function will be called when
intermedia goes to index -- you'll be able to assemble upto 4gig of data to be
indexed for that row.  You'll just select that row out and dbms_lob.write_append
it to the temporary clob intermedia passes to you (make sure to put white space
between the words!)
Review & Followup

      Rating:
      3
Followup  December 31, 2001
Reviewer:
      Sushil Vyas  from India
Tom,

I tried going by your suggestion but if there are 5lakhs of rows then the
procedure would be requiring equal no of clob locators to be used in
dbms_lob.writeappend.

If this is the case than this is going to put a load on my memory as well as
disk processing.

Pls revert back.

Tks
sushil.

          Followup:

try it.  (i really don't know what a "lakh" is).

There will be but ONE lob locator.  interMedia will call you, passing you a lob
locator.  You'll fill it in.  intermedia will index that.  intermedia will free
it.  intermedia will then call you again with another lob locator to be filled
in.  it'll index it, free it, and so on.

Hey, -- i just discovered that there is a potential issue with the TEMPORARY
lobs (fixed in 9i).  There is a potential memory leak with them using this
technique.

We can instead use a VARCHAR2 of upto 32000 bytes (no leakage there at all).

To do this, you'll create the preference:

begin
ctx_ddl.create_preference( 'foo_user_datastore', 'user_datastore' );
ctx_ddl.set_attribute( 'foo_user_datastore', 'procedure', 'foo_proc' );
ctx_ddl.set_attribute( 'foo_user_datastore', 'output_type', 'varchar2' );
end;

and your procedure will be:

create or replace procedure foo_proc(
    p_id in rowid,
    p_lob IN OUT VARCHAR2
)

instead -- now you can stuff upto 32000 characters into p_lob as a string, not
as a LOB.

      Rating: 4
Lakh (or
      Lac)  January 02, 2002
Reviewer:
      A reader  from Trenton, USA
1 Lakh is equal to 100,000

      Rating: 3
Contain command
      ?  January 02, 2002
Reviewer:  William
       from Ontario Canada
Tom,

  Could you please provide more info about the contains command? It did not work
in my database (8.1.6). And I would also like to know if there is any difference
between these two queries.

Select * from T where contains(title,'Oracle') > 0

Select * from T where title like '%Oracle%'

Thanks a lot.

          Followup:

It is part of interMedia text.  See

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/inter.817/index.htm
it is used with a TEXT index.

      Rating: 2
Memory
      leakage  April 11, 2003
Reviewer:  Vikas
      Khanna  from INDIA
Hello Thomas,

We were working on some application which are using OCI calls to the database to
perform some of the DML statements. Our application is batch oriented and we are
processing a million of records in one go.

What happens is that if we run that tool on Windows 2000 Server as the client
then the memory leak is not there where as there is substantial memory leakage
(25K) per transaction (no of DML's) when the same tool is run on Windows NT
workstation.

For your info. we are using BLOBs to write the textual content inside them. Both
the clients are using the same Oracle software which is Oracle 8.0.5 Client
installation.

In OCI we are allocating a heap of 40 MB and have provided functions to Oracle
for alloc and dealloc of memory.

oci::OCIInitialize(OCI_DEFAULT, m_hHeap,
            OracleMemAlloc, OracleMemRealloc, OracleMemDealloc)

Please do suggest what needs to be done at the application end in order to
resolve this issue.

Regards,
Vikas

          Followup:

contact support and open a tar....

But then again, you have a really old unsupported version of the database
software -- running on operating systems invented long after their release (805
- early 1998, windows 2000 late 2000).  That could be a problem.

April 13, 2003 Sunil -- Thanks for the question regarding "Locks
      not getting released even after the session is killed", version 8.1.7

      You Asked
Hi Tom,

I executed a procedure which cleans up the data from one table (for duplicates,
consolidation) and moves the data to another table in another database. Data
count was 1.2 million. Usually the procedure was taking 2 hours to process but
yesterday it took more than 4 hours & not even one row was inserted to other
table. So i asked my DBA to kill the session in which i was executing the
procedure. After the session is killed i tried to execute the procedure for just
1 lakh records using rownum. Still the session was hanging. We checked the
V$locked_object. The problem was still the lock acquired by earlier process was
not released & the process which i executed now also was having the lock. We
could confirm this from V$locked_object with tow session ids & it was showing
two locks on the same table. So we again killed the currently running session.
But it took more than 4 hours for the locks to get released. V$session was
showing both the sessions as killed but even those two rows got cleared after 4
hours, soon after that locks were released.

My question is what may be the problem in above scenario. What is the other way
to release the locks. I tried with DBMS_LOCK.RELEASE package also. But with no
success.

Your suggestion will be highly appreciated.

Sunil.

      and we said...
You jumped the gun - you killed the process.  Now, it took that process 4 hours
to get to where it was (it had done alot of work).  It will generally take that
process a long time to UNDO that work as well.

When you killed the session, it was marked for kill.  It was rolling back
(undoing) the work it did.

You should instruement your code, your procedure, with calls to
dbms_application_info so you can monitor your progress in v$session or
v$session_longops (if you have my book -- i have extensive examples in there).
That way, you'll know how far you've gotten and won't be so quick to pull the
trigger.

Your locks will go away all by themselves -- after the process rolls back (which
can take significantly longer then it took to process in the first place)

dbms_lock is only useful on locks created by dbms_lock (user defined locks).
You cannot just "unlock" some object - the lock is there for an extremely
relevant reason (if you could just turn off that lock -- of what use would locks
be?)

Review &
      Followup

      Rating:
      3
  May 30, 2002
Reviewer:  Daniel
       from Toronto, Canada
I also hit this problem several times. My method is first kill the session, if
the session is only marked 'KILLED' and doesn't go away, I'll logon to the UNIX
( assuming oracle resides on UNIX ) as DBA to kill the session's dedicated sever
process. The lock will be released at once. It seems Tom doesn't recommend this
method, I don't know if there is any potential damage to data integrity. Thanks
for clarify

      Rating: 5
Locks not
      cleared after killing session  April 10, 2003

Reviewer:  Anil  from Malaysia
I am working in a datawarehouse environment which is on oracle 8.1.7 and os is
windows 2000 advanced server.

Our daily data loading takes around 4 hours but this time it took more than 28
hours and it is still not over. So i decided to kill the session. After killing
the session the locks are still there.

Can u suggest something to get out of this problem. Should i wait for locks to
get cleared on its own. At present the locks are on 37 tables. I had already
waited for more than 14 hours but lock is not cleared on even a single table. Is
there anything that i can do to clear the locks. I am having dba privileges

Regards,
Anil

          Followup:

if it took 28 hours to get there -- it might take 28 hours or more to GET BACK.

You are rolling back.  Until you roll back -- locks.

Use v$transaction and look at used_ublk to see how fast it is rolling back and
how far its got to go.

what you NEED TO DO immediately, while waiting, is get your load code and fill
it FULL of calls to dbms_application_info set session longops -- so you can
monitor in v$session_longops where you are and how far you have to go so you do
not get tempted to kill things again.

A little instrumentation goes a long long long way (and it gives you something
to watch)

      Rating: 2
Look at bug
      #1785969 on metalink  April 11, 2003
Reviewer:
      Chuck Jolley  from OKC, OK USA
We had this problem on an 8.1.7 database pair for updates over a DB Link.
It would hang and NEVER return.
Look at bug #1785969.
We never manualy mess with locks so we had to bounce both DBs to kill the locks
when it happened.
We have rewritten the app to use separate connections for the local query and
remote update.
Chuck

          Followup:

doesn't seem to apply a bit in this case.  totally different scenarios.

April 13, 2003 Munzer -- Thanks for the question regarding "SQL
      Query", version 8.1.7

      You Asked
Tom:

Can you explain this:

I have a column in a table named "PO_Flag" which is set to "Y" by a  user
function.

When a user creates a record this column is not set to anything and so it will
have default null value eve nthough I do not insert "null" there.

When I do:

SELECT * from table where PO_FLAG!='Y';

It does not give me the records that have null value.  It will give me
everything that has a value other than 'Y' but not one's that do not have a
value.

Do you know why?

THank you,

      and we said...
SQL has this thing called tri-valued logic.  The result of a
comparision is either:

o true
o false
o UNKNOWN

You can observe this via:

tkyte@TKYTE816> select * from dual where dummy = null;

no rows selected

tkyte@TKYTE816> select * from dual where dummy <> null;

no rows selected

tkyte@TKYTE816> select * from dual;

D
-
X

See -- "X" is neither equal to null nor is it NOT EQUAL to null.
 It is UNKNOWN wheter 'X' = NULL is true or false.  This is the
way SQL works.  Watch out for NULLS!

Review &
      Followup

      Rating: 5
SQL
      Query  May 26, 2002
Reviewer:  Munz
       from Reston, USA
Tom:

This is strange (never new that).

Should I always in comparing "not equal" values include the null constraint.

E.g. select * from table where po_flag!="Y" or po_flag is  null

          Followup:

depends on what data you need to retrieve -- doesn't it?

You are the only one that can answer that question -- you have to tell us what
predicate you need to use to get the right answer.

      Rating: 3
Your
      Comments  May 26, 2002
Reviewer:
      A reader
Was just trying this
  1* select * from dual where not(dummy = null  or  dummy <> null)
SQL> /

no rows selected

Then ..

  1*  select * from dual where not(dummy = null  and  dummy <> null)
SQL> /

no rows selected.

I got confused between using an "or / and ".Basically I was trying to combine
both your select statements in to one.
Still it returns no rows.It should have returned me a row.
The reason may be logical expressions involving NULL( = null) always
results in a NULL. Am I wrong. Please explain
ThanKs

          Followup:

but I showed that:

null = null   --- is neither TRUE or FALSE, it is UNKNOWN

null <> null --- is neither TRUE or FALSE, it is UNKNOWN

so,

unknown OR unknown is UNKNOWN
unknown AND unknown is UNKNOWN

and NOT( unknown ) is UNKNOWN -- not TRUE, not FALSE.

neither predicate should return a row.

See also

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3012348048

      Rating: 4
how to
      query  April 10, 2003
Reviewer:
      A reader
Hi tom,

 I have a table t( t1 number primary key, t_flag char(1));

now
t_flag can be null or 'Y' based on the t1 id is active or not.

 how should I query it ?

select * from t1
where t_flag  = 'Y' ;

or

select * from t1
where t_flag is not null ;

can you explain it to me ?

Thanks,

          Followup:

I would use "where flag = 'Y'"

it makes more sense.
it seems to use the index I'm assuming is on t(flag) more readily -- which will
be important if you are interested in getting the first non-processed item
(which you probably do)

      Rating:
      4
  April 11, 2003
Reviewer:
      A reader
I did not understand

"
it seems to use the index I'm assuming is on t(flag) more readily -- which will
be important if you are interested in getting the first non-processed item
(which you probably do)

"

 I have a table t with 2000 rows about 1800 active and 200 not active

          Followup:

if 1800 are active (flag=Y) -- won't matter.  Full scan rules.

But -- parsing my statement I said:

If you want to use an index
Use where flag = Y, not flag is null.

      Rating: 3
Very Useful to
      developers   April 14, 2003
Reviewer:
      Reader
 Tom, I think your reply  are very useful to developers.
 because the reason of most of performance's problems is
 due to 'BAD' sql statements.

 In additional , Index's efficiency is also important.
 obviously, the question:
   '2000 itmes ,1800 of which is 'Y' and 200 of which
    is'N'  will use table scan.

April 13, 2003 Saibabu -- Thanks for the question regarding
      "Internals of Roles and Privs.", version 8.1.6,9.0.0

      You Asked
Hi Tom,I have few questions unanswered since long time.
Database version : 8.1.5,8.1.6 and 8.1.7

1) I have granted a role "DBA" to user "ADM".Then I can select count(*) from
sys.obj$ and describe "SYS.OBJ$".But nowhere I can see that select on OBJ$ has
been granted to adm user.I have tried around with
user_tab_privs,user_tab_privs_recd,all_tab_privs_recd.This privilege has not
been granted to select_catalog_role also.

2) It looks so funny to me that "V$SESSION" is a public synonym for
"V_$SESSION",and "V_$SESSION" is a view defined on "V$SESSION".Same thing with
"GV$SESSION" and "GV_$SESSION".Why is this cyclic behaviour being maintained.I
know we can get full definition from v$fixed_view_definition.

Database version : 9.0.0

3) default settting of O7_DICTIONARY_ACCESSIBILITY=FALSE in 9i.Similar situation
is maintained as in questions no. 1.Now I can select count(*) from sys.obj$.But
desc "SYS.OBJ$" is not working.Please explain.

4) Instead of grating DBA role to adm user.I have granted roles
select_catalog_role,delete_catalog_role,execute_catalog_role,exp_full_database,im
p_full_database to adm user with admin option.Now adm user cannot able to select
from "SYS.OBJ$".How to explain this behaviour when Iam grating the same roles
DBA having.

Please take your own time to explain these issues.Your help is very much
appreciated.Have a nice weekend.

Thanks
Sai.

      and we said...
1) the DBA role has the very powerful "SELECT ANY TABLE" priv.  ADM can select
from ANY table in the database -- there is no direct grant on OBJ$ to ADM.

2) The v$ tables are magic -- they are in memory tables, they are special.  It
is just the way they are.

3) in 9i they changed how this works.  There is a new privelege

SELECT ANY DICTIONARY Query any data dictionary object in the SYS schema.

This privilege lets you selectively override the default FALSE setting of the
O7_DICTIONARY_ACCESSIBILITY initialization parameter. Note: This privilege must
be granted individually. It is not included in GRANT ALL PRIVILEGES, nor can it
be granted through a role.

And DBA has this priv.  It would appear to be a bug that DESC cannot describe
it.

It would also appear to be a bug that the Oracle9i reference still refers to the
ROLES, and not this new privelege.

4) See #3, the roles don't do it anymore.
Review &
      Followup

      Rating: 5
Excellent
      feedback,ofcourse you skipped internals of v$ views.  June
      29, 2001
Reviewer:  Saibabu Devabhaktuni  from Palo
      Alto,CA,USA.
Thank you so much.To me "SELECT ANY DICTIONARY" is working through roles in 9i.I
have grated it through roles and users can actuabble able to use it.

      Rating: 3
Privileg Grant
      Through Role  April 12, 2003
Reviewer:  Abhi
       from INDIA
Hello Sir .
Sir we can grant privlege (object/system) to a user through
role or Directly .. what is difference in these two ways.
and whats the Reason behind reason this.
when we should grant privilege directly or when Throught Role. I think Jobs only
can C directly Grnted Privs.
Is this Restriction is applicable for Both (System/Object) privileges.

          Followup:

see

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html
for the difference.

roles are for end users.

direct grants are generally needed for developers.

April 13, 2003 Bhavesh -- Thanks for the question regarding
      "Dynamic Columns selection", version 8.1.7`

      You Asked
Hi Tom,
     I have a table having columns columns tablename, username and columnname.
This table will contain the columns and tables from user schema,  which the user
is not privelaged to see.  So before generating the output (html format) using
Pl/SQL server pages, we have to determine which columns user can see and then
have to display only those columns. I have written a procedure something like
below.

-------------PL/SQL Procedure

create or replace procedure colselect is
cursor c1 is select * from (select column_name from user_tab_columns
where table_name ='EMP' and data_type in ('VARCHAR2', 'NUMBER') minus
select columnname from user_col_privs where userid='TEST' and
tablename='EMP');
x c1%rowtype;
sqlstmt  varchar2(2000);
begin
   open c1;
      loop
         fetch c1 into x;
         exit when c1%notfound;
         sqlstmt := sqlstmt ||x.column_name||',';
      end loop;
   close c1;
   sqlstmt := 'select '||substr(sqlstmt, 1, length(sqlstmt)-1)||' from EMP';
   dbms_output.put_line(sqlstmt);
end;
/

Now here if I take the sqlstmt in a ref cursor, I am not able to determine where
to fetch this into, because the number of columns in the sqlstmt varies. How can
I use this sqlstmt to create a ref cursor, fetch into variable and then use
htp.p column name, which also is a variable.
Also please let me know if I can use this procedure as a generic one where by I
pass the username and tablename as parameters and if there is a better way to
achieve above functionality.
Thanks a lot in Advance,
Regards

Bhavesh

      and we said...
Well, you won't be able to use a REF CURSOR for this, you'll have to use
DBMS_SQL.  dbms_sql will let you procedurally process the columns and has a
describe routine to get the column headings as well.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:235814350980
for an example.  (you might look at owa_util.cellsprint as well).

It would be quite impossible for a REF CURSOR to do this as you would have to
have a huge if then elsif block to handle every single possible permutation of
fetches you could ever have.  DBMS_SQL is the right approach in this case.
Review &
      Followup

      Rating: 4
Passing tables
      names as argument  April 09, 2003
Reviewer:
      A reader
Hi Tom,
I need to populate data warehouse (DW) tables from data staging (DS) tables. The
data is already cleaned in DS. The DW table definitions is exactly same as in DS
except that it contains an extra column to indicate that row is marked for
Insert, Delete or Update in DW. There are plenty of tables. Is it possible to
dynamically do through DB procedure like this?
1.  DB procedure excepts a table name
2.  The procedure dynamically get all the column names from DS table (parameter)
and insert, update or delete into DM tables (same parameter, as tables names are
same) without writing explicit column names for each table
Could you please show me with an example?

Thanks

          Followup:

sure, here is an example:

ops$tkyte@ORA817DEV> create table t
  2  as
  3  select username, user_id, created
  4    from all_users;

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> alter table t add constraint t_pk primary key(user_id);

Table altered.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create table stage
  2  as
  3  select lower(username) username, user_id, created, 'U' dml_type
  4    from all_users
  5   where mod(user_id,2) = 1
  6  /

Table created.

ops$tkyte@ORA817DEV> insert into stage
  2  select 'X'||username, -(user_id-1), created, 'I' dml_type
  3    from all_users
  4  /

56 rows created.

ops$tkyte@ORA817DEV> insert into stage
  2  select username, user_id, created, 'D' dml_type
  3    from all_users
  4   where mod(user_id,2) = 0
  5  /

27 rows created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> alter table stage add constraint stage_pk primary
key(user_id);

Table altered.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace procedure p ( p_str in varchar2 )
  2  is
  3     l_str   long := p_str;
  4  begin
  5     dbms_output.put_line( '---------------' );
  6     loop
  7        exit when l_str is null;
  8        dbms_output.put_line( substr( l_str, 1, 250 ) );
  9        l_str := substr( l_str, 251 );
 10     end loop;
 11     dbms_output.put_line( '---------------' );
 12  end;
 13  /

Procedure created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace procedure sync_em_up( p_tname in
varchar2, p_stage in varchar2 )
  2  as
  3      l_cnames      dbms_sql.varchar2_table;
  4      l_cnames_pk   dbms_sql.number_table;
  5      l_cnames_csv  long;
  6      l_pknames_csv long;
  7      l_stmt        long;
  8  begin
  9      select user_tab_columns.column_name,
 10             decode(user_cons_columns.column_name,NULL,0,1)
 11        BULK COLLECT into l_cnames, l_cnames_pk
 12        from user_tab_columns,
 13             (select column_name
 14                from user_cons_columns
 15               where constraint_name =
 16                     ( select constraint_name
 17                         from user_constraints
 18                        where table_name = upper(p_tname)
 19                          and constraint_type = 'P' )
 20             ) user_cons_columns
 21       where user_tab_columns.table_name = upper(p_tname)
 22         and user_tab_columns.column_name = user_cons_columns.column_name(+)
 23       order by user_tab_columns.column_id;
 24
 25      for i in 1 .. l_cnames.count
 26      loop
 27          l_cnames_csv := l_cnames_csv || l_cnames(i) || ', ';
 28          if ( l_cnames_pk(i) = 1 )
 29          then
 30              l_pknames_csv := l_pknames_csv || l_cnames(i) || ', ';
 31          end if;
 32      end loop;
 33      l_cnames_csv := rtrim(rtrim(l_cnames_csv),',');
 34      l_pknames_csv := rtrim(rtrim(l_pknames_csv),',');
 35
 36      l_stmt := 'delete from ' || p_tname ||
 37                ' where ( ' || l_pknames_csv ||
 38                ' ) in ( select ' || l_pknames_csv ||
 39                ' from ' || p_stage || ' where dml_type = ''D'' )';
 40
 41      p(l_stmt);
 42      execute immediate l_stmt;
 43
 44
 45      l_stmt := 'update ( select ';
 46      for i in 1 .. l_cnames.count
 47      loop
 48          l_stmt := l_stmt || ' a.' || l_cnames(i) || ' A' || i ||
 49                             ', b.' || l_cnames(i) || ' B' || i || ',';
 50      end loop;
 51      l_stmt := rtrim(l_stmt, ',' ) || ' from ' || p_tname ||
 52                ' a, ' || p_stage || ' b '  ||
 53                ' where b.dml_type = ''U'' ';
 54      for i in 1 .. l_cnames.count
 55      loop
 56          if ( l_cnames_pk(i) = 1 )
 57          then
 58              l_stmt := l_stmt || ' and a.' || l_cnames(i) ||
 59                                    ' = b.' || l_cnames(i);
 60          end if;
 61      end loop;
 62      l_stmt := l_stmt || ' ) set ';
 63      for i in 1 .. l_cnames.count
 64      loop
 65          if ( l_cnames_pk(i) = 0 )
 66          then
 67              l_stmt := l_stmt || ' A' || i || ' = ' ||
 68                                  ' B' || i || ',';
 69          end if;
 70      end loop;
 71      l_stmt := rtrim(l_stmt,',');
 72
 73      p(l_stmt);
 74      execute immediate l_stmt;
 75
 76      l_stmt := 'insert into ' || p_tname ||
 77                '( ' || l_cnames_csv || ' ) ' ||
 78                'select ' || l_cnames_csv ||
 79                ' from ' || p_stage ||
 80                ' where dml_type = ''I'' ';
 81
 82      p(l_stmt);
 83      execute immediate l_stmt;
 84  end;
 85  /

Procedure created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> exec sync_em_up( 't', 'stage' );
---------------
delete from t where ( USER_ID ) in ( select USER_ID from stage where dml_type =
'D' )
---------------
---------------
update ( select  a.USERNAME A1, b.USERNAME B1, a.USER_ID A2, b.USER_ID B2,
a.CREATED A3, b.CREATED B3 from t a, stage b  where
b.dml_type = 'U'  and a.USER_ID = b.USER_ID ) set  A1 =  B1, A3 =  B3
---------------
---------------
insert into t( USERNAME, USER_ID, CREATED ) select USERNAME, USER_ID, CREATED
from stage where dml_type = 'I'
---------------

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV>

      Rating: 5
Thanks a lot
      "GENIUS"  April 09, 2003
Reviewer:
      A reader

      Rating: 5
Privilege
      problem  April 09, 2003
Reviewer:
      A reader
Tom,
Once again thank for you invaluable help.
During update I get error
 1  update ( select  a.CLIENT_NUMBER A1, b.CLIENT_NUMBER B1,
  2  a.WC_EMPLOYER_DET A2,
  3  b.WC_EMPLOYER_DET B2,
  4  a.CLIENT_TYPE A3, b.CLIENT_TYPE B3
  5  from tb_nscr_wc_employer a, nscds.tb_nscr_wc_employer b
  6  where b.ddl_flag = 'U'
  7  and a.CLIENT_NUMBER = b.CLIENT_NUMBER
  8  and a.WC_EMPLOYER_DET = b.WC_EMPLOYER_DET )
  9* set  A3 =  B3
from tb_nscr_wc_employer a, nscds.tb_nscr_wc_employer b
                                  *
ERROR at line 5:
ORA-01031: insufficient privileges

The user running this statement (procedure) is nscdw. I am not able to
understand why this user needs update privilges for nscds table.
Thanks

          Followup:

they just do (it's in the update portion of the update stmt) -- you'll have to
use the where exists and correlated subquery update approach instead if you
cannot get update (slower in all likelyhood)

      Rating: 5
How do I handle
      the exception?  April 09, 2003
Reviewer:
      A reader
Hi Tom,
If I want to update the DS table column - processed timestamp, after loading the
data in DW, I can do it by putting an update statement for the data staging
table. But if execute immediate fails, can I handle the exception? At the same
time, after execute immediate success, if final update (DS table) fails, I need
to rollback execute immediate.
Can I do this?
Thanks

          Followup:

I don't know -- can you handle the exception?  I mean you can definitely CATCH
it but -- what are you going to do about it?  How would you handle it?  what
exception do you expect?  unexpected exceptions should not be caught (eg: don't
use when others) as you have no hope of handling them.

execute immediate is not any different transactionally then static sql.  You can
do the same thing you would do in any case.

      Rating:
      5
  April 10, 2003
Reviewer:
      A reader
Very well explained answer.

      Rating: 5
Excellent
      !  April 13, 2003
Reviewer:  A reader

Tom,
The code is excellent. Could you please provide help, if one wants to validate
data based upon database constraints (dynamically). The constraints to be
checked are:
PK, FK and NOT NULL constraints
i.e., the procedure takes table name as an argument, check for above mentioned
constraints, and if data does not meet the constraints, the records are
rejected, and put into corresponding rejects table. Obviously, corresponding
rejects table does not have any constraint, just an additional column
“reason_rejected”.

          Followup:

you insert it and if rejected, insert it elsewhere?

April 14, 2003 Walter -- Thanks for the question regarding
      "Database character set", version 7.3.4.4

      You Asked
How to determine the database character set?

      and we said...
ops$ora734@ORA734.WORLD> select * from nls_database_parameters;

PARAMETER                      VALUE
------------------------------ ------------------------------
NLS_LANGUAGE                   AMERICAN
NLS_TERRITORY                  AMERICA
NLS_CURRENCY                   $
NLS_ISO_CURRENCY               AMERICA
NLS_NUMERIC_CHARACTERS         .,
NLS_DATE_FORMAT                DD-MON-YY
NLS_DATE_LANGUAGE              AMERICAN
NLS_CHARACTERSET               US7ASCII
NLS_SORT                       BINARY
NLS_CALENDAR                   GREGORIAN
NLS_RDBMS_VERSION              7.3.4.0.1

11 rows selected.

Review &
      Followup

      Rating: 3
In 8.1.7
      ??  April 14, 2003
Reviewer:  Arul
       from United Kingdom
Tom,

Can you please tell me how to find the same in 8.1.7 as NLS_CHARACTERSET is
missing.

Thanks,
Arul.

          Followup:

Look closer

ops$tkyte@ORA817DEV> select * from nls_database_parameters;

PARAMETER                      VALUE
------------------------------ ----------------------------------------
NLS_LANGUAGE                   AMERICAN
NLS_TERRITORY                  AMERICA
NLS_CURRENCY                   $
NLS_ISO_CURRENCY               AMERICA
NLS_NUMERIC_CHARACTERS         .,
NLS_CHARACTERSET               WE8ISO8859P1
NLS_CALENDAR                   GREGORIAN
NLS_DATE_FORMAT                DD-MON-RR
NLS_DATE_LANGUAGE              AMERICAN
NLS_SORT                       BINARY
NLS_TIME_FORMAT                HH.MI.SSXFF AM
NLS_TIMESTAMP_FORMAT           DD-MON-RR HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT             HH.MI.SSXFF AM TZH:TZM
NLS_TIMESTAMP_TZ_FORMAT        DD-MON-RR HH.MI.SSXFF AM TZH:TZM
NLS_DUAL_CURRENCY              $
NLS_COMP                       BINARY
NLS_NCHAR_CHARACTERSET         WE8ISO8859P1
NLS_RDBMS_VERSION              8.1.7.4.0

18 rows selected.

      Rating: 4
Thank
      you!  April 17, 2003
Reviewer:  Arul.
       from United Kingdom
I was looking at V$Parameter ! hoping it should have all the parameters.

Thanks again!

April 14, 2003 Subbarao -- Thanks for the question regarding
      "Moving Some Tables to a different Database !!", version 8.1.7

      You Asked
Hi Tom :

Recently , we had a request from one of our internal customer . We have a bunch
of Tables in a Database .. They wanted to move some of those Tables ( I do not
know exact reason , why they wanted to do it ?) to a different Database ..

Our Application is designed to work with a single Database but we can still
support this case .. How best we can accomplish this need :

1) By creating a DB Link between the 2 Databases and by creating a Materialized
View ??

2) By creating a View ??

Some how this seems to be very trivial and at the same time looks like little
complicated ! I think , this way , we are compromsing on performance ? Am I
right ??

I would appreciate if you can suggest a better way of achieving this ??

Thanks
-Subbarao

      and we said...
I would ask them for the business reason why they want to do this.

Moving the tables into a distributed environment is the wrong way to do this.
People are realizing the very real benefits of CONSOLIDATION -- not spreading
data out.

It would be very hard for you -- it would be a major performance implication.
Trivial -- no, it would not be trivial. It would increase the cost of everything
-- development, maintenance, support, day to day management.

I see no benefits, only pain.  Get their reasons -- and then we can tell them
how to satisfy those concerns/reasons in a cost effective manner.
Review &
      Followup

      Rating: 4
Need more
      Details ..  December 03, 2002
Reviewer:
      Subbarao Punnamaraju  from Boston , MA USA
Tom :

Thanks for your quick response .. I fully agree with you and I was thinking same
thing and explained same to the requestor !

But , could you please let me know how can we accomplish this ?? Can you explain
, though it is painful !

Thanks
-Subbarao

          Followup:

Nope, i only give advice on things that are good to do.

You can read:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/index.htm
the distributed databases and replication guides.  Its all in there -- it takes
more then a web page to cover.

      Rating: 3
What about in
      9.2?  April 14, 2003
Reviewer:  Skp
       from Conway, AR USA
I have reviewed the documentation, tried the database links, worked with the sdu
parameter, but get really slow times.  My customer has decided that they only
want to pay for a process once so we have to process millions of records 6 days
a week and on the 7th day when I should be resting, I have to propagate those
changes (10s-20s gig). To multiple other databases.  I have tried parallelizing
the queries by using views on partitioned tables but it really didn't help the
runtimes (upwards of 12 hours on a gigabit network).  I have thought about
Oracle streams to accomplish this but it seems that streams are for low quantity
changes.  Do you have any other suggestions I can try?

          Followup:

insufficient data to say anything about anything.

Don't really know what it is you have to accomplish.  Why they would have to pay
you to run a program for them is beyond me -- so -- there must be something else
afoot here.

Streams is not for low quantity changes at all.  It would benefit from a
CONTINUAL process, but it is not mandatory.

When I'm taking questions again, feel free to post this as a question with tons
more detail -- more then "gotta run something, 10-20gig of data -- move it
about, I've identified the NETWORK as being the bottleneck as evidenced by
...."

April 14, 2003 Felipe A. -- Thanks for the question regarding
      "Missing Consecutives", version 8.0.5

      You Asked
Hi Mr. Kyte

My Question is:
Does Oracle DB (any version) have a way to calculate a set of Consecutive
Numbers and retrieve them as a
query result?

For example (something like that):

SQL> select consec_nbr
  2  from ORA_consecutives where consec_nbr between 6 and 15;

CONSEC_NBR
----------
6
7
8
9
10
11
12
13
14
15

In this case, "ORA_consecutives" ain't a common table and ain't a common view
either,
"ORA_consecutives" would be an object (as Oracle DUAL object) instead.

With that data set I can perform a Minus operation with a table that has
consecutives (and many of them
can be missing).

We need that in a very complex comparation with another tables in order to
calculate missing consecutives.

We know that it could be solved by a process, but it could be very large (in
time) depending on the
quantity of records (which will be very large in almost all ocassions).

We will really appreciate your answer ASAP!

Thanks!

      and we said...
consectutives would not be useful here unless you first knew the theoritical MIN
and MAX values (eg: a table with the numbers 100 and 999999999999999 would need
a consecutives number table with 999999999999999 rows.  A table with 100 and 105
would need 105 rows.  You need to know much about the table with "missing
numbers")

I think you will find a query such as:

select *
  from t
 where NOT EXISTS ( select null from t t2 where t2.x = t.x+1 )
/

accomplishes your goal nicely - it gives you the rows such that there isn't a
row one greater.

In 8i release 2 (not 805), you can go a step further.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select rownum from all_users;

43 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from t where x between 15 and 20;

6 rows deleted.

ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from t where x = 5;

1 row deleted.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select x, next_x, next_x-x-1 missing
  2    from (
  3  select x, lead(x) over ( order by x ) next_x
  4    from t
  5             )
  6   where x <> next_x-1
  7  /

         X     NEXT_X    MISSING
---------- ---------- ----------
         4          6          1
        14         21          6

shows you that there is a gap from 4 to 6 and 14 to 21 whereas:

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2    from t
  3   where NOT EXISTS ( select null from t t2 where t2.x = t.x+1 )
  4  /

         X
----------
         4
        14
        43

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

Just shows you there is a gap starting after 4 and after 14.
Review &
      Followup

      Rating: 5
This is a
      Better Alternative  April 11, 2003
Reviewer:
      Felipe Alzate V.  from Medellín, Colombia (South America)

In order to find gaps in a column of a table with consecutives values, you can
use the following query  (As a view)in a MINUS operation with your table:

select (a.d*1)+(b.d*10)+(c.d*100) continue_consecutive
from (select 0 d from dual union select 1 d from dual union select 2 d from dual
union
      select 3 d from dual union select 4 d from dual union select 5 d from dual
union
      select 6 d from dual union select 7 d from dual union select 8 d from dual
union
      select 9 d from dual) a,
     (select 0 d from dual union select 1 d from dual union select 2 d from dual
union
      select 3 d from dual union select 4 d from dual union select 5 d from dual
union
      select 6 d from dual union select 7 d from dual union select 8 d from dual
union
      select 9 d from dual) b,
     (select 0 d from dual union select 1 d from dual union select 2 d from dual
union
      select 3 d from dual union select 4 d from dual union select 5 d from dual
union
      select 6 d from dual union select 7 d from dual union select 8 d from dual
union
      select 9 d from dual) c

This query has just 1.000 rows, but you can easily increase the number of rows
(in power of 10, i.e.: 10^4=10.000, 10^5=100.000, ...) following the same used
structure.

          Followup:

so, why is it "better"....

Under what circumstances.

that "exists" for example, would be very fast at finding the FIRST one with a
gap.

lag and lead -- at finding all...

      Rating: 3
Use table
      function in 9i  April 14, 2003
Reviewer:
      fliu  from Vancouver
create type vlist_obj as object(element NUMBER);

create type vlist_tab as table of vlist_obj;

CREATE OR REPLACE FUNCTION get_vlist (pv_start NUMBER DEFAULT 1, pv_end NUMBER)
   RETURN vlist_tab
   PIPELINED
IS
BEGIN
   FOR i IN LEAST (pv_start, pv_end) .. GREATEST (pv_start, pv_end)
   LOOP
      PIPE ROW (vlist_obj(i));
   END LOOP;

   RETURN;
END;
/

Then you can simply get any numbers between two integers:

SQL> select * from table(get_vlist(100, 110));

   ELEMENT
----------
       100
       101
       102
       103
       104
       105
       106
       107
       108
       109
       110

          Followup:

and this does what exactly? (given the question above)

      Rating: 3
Table
      function  April 14, 2003
Reviewer:  fliu

The query result could then be joined with the actual table to get the missing
numbers...

For example:

select * from table(get_vlist(1000, 2000)) a
where a.element not in (select id from emp);

for employee IDs not currently in use.

Is this better than using all_objects etc., performance wise?

          Followup:

try it -- but you also need to know the MIN and the MAX.

I still am at a loss to understand why a single query against a single table
using either where exists or lag/lead is so untenable to so many...

      Rating: 5
and the CREATE
      Privileges...and the previous versions?????  April 14, 2003

Reviewer:  Felipe Alzate V.  from Medellín, Colombia,
      South America
Dear fliu (from Canada),

Your solution is useless if you don't have enuogh privileges to Create a
function, Instead My solution is useful even with previous versions of the
ORACLE Database.
ORACLE 9i Is not necessaryy !

I didn't talk about performance, thus, in those circunstnces (Previous versions
and restricted privileges) is better my solution.

It seems you don't know what is to work with a litle resources!

April 14, 2003 Steve -- Thanks for the question regarding
      "timing a SQL ", version 9.2.0

      You Asked
Hi Tom,

I try to measure the SQL response time using the following three ways:

1) SQL* plus (set timing on, set autotrace traceonly)
SQL> select /*+ LEADING */c.cust_last_name
  2  ,         s.time_id
  3  ,      s.prod_id
  4  from   customers c, sales s
  5  where  c.cust_id = s.cust_id (+);

1064702 rows selected.

Elapsed: 00:00:26.02

2) SQL>Alter session set sql_trace=true,
select /*+ LEADING */c.cust_last_name
,      s.time_id
,      s.prod_id
from   customers c, sales s
where  c.cust_id = s.cust_id (+)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch    70982      5.70      11.66       7675      31051          0     1064702
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    70984      5.70      11.67       7675      31051          0     1064702

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 49

3) JDBC:
        ...
        long t = System.currentTimeMillis();
        ResultSet rset = stmt.executeQuery (sqlString);
        long t1 = System.currentTimeMillis();
        System.out.println("time:" + (t1 - t));
        ...
   656  millisecond

I don't understand why I got three different results.
one is 26.02 sec, another is 11.67 (need add 5.70?)
and 656 millisec?
And does JDBC's executeQuery() just correspond to EXECUTE in TKPROF
and does not include FETCH time?

Thank in advance!

Steve

      and we said...
Well, SQLPLUS is a reporting tool -- it has tons of overhead.  So, as it fetches
data from the database (over the network), it is formatting, converting, doing
TONS of stuff.

It's elapsed time has the amount of time spent

a) in the database
b) over the wire (network)
c) formatting the data (even though you didn't print it)

SQL_TRACE -- well, that just times (a) in the database.  It does not include
over the wire time nor the time the client spent futzing with the data itself.

your JDBC program -- well, you would get the same result if you coded:

declare
   cursor c is select ....;
begin
   open c;
   close c;
end;

in sqlplus and traced that.  You never fetched the data so that result does not
include any time spent in the database really (you NEVER FETCHED ANY DATA after
all, the query did no work).  It did not count any "wire" time, you never
fetched any data.

I personally use tkprof output.  You tune that, you've tuned the query.  Now, in
your case, you have some waits obviously (else CPU would be the same as
elapsed).  Here you have 5.7 cpu seconds (that is the fastest this could run as
it exists).   It took 11.67 seconds by the wall clock -- the most likely reason
here is the 7,675 physical IO's you had to do (and had to wait for).  I don't
see the plan but hopefully it didn't use a single index for that query!!

So, use sqlplus set timing on only to show something goes really "fast".  It is
not good to time big things.

Don't use java at all -- unless you are benchmarking your java app -- but that
is a different benchmark!

sql_trace+timed_statistics+tkprof show you how fast the query ran on the server.
Review &
      Followup

      Rating:
      5
  December 10, 2002
Reviewer:  Steve
       from NJ, USA
Thanks for the answer, Tom!

I just don't understand your following comments,
"Don't use java at all -- unless you are benchmarking your java app -- but that
is a different benchmark! "

Could you please tell me why it is a different benchmark?
What happends under hood when I call JDBC executeQuery()?

What I am doing is to develop a java application to test
sql statements with different HINTs and find best hint. So I need an accurate
measurement inside Java App.

Thanks again, Tom!

          Followup:

Java isn't the fastest thing on the planet (i know, there are jit's there are
wat's -- way ahead of time compilers -- etc)....

but unless you are benchmarking a java app - don't use java to benchmark a
query.

As for  your "goal" - questionable at best.  HINTS are the path of totally LAST
RESORT.  With proper statistics and attention to optimizer_index_cost_adj /
optimizer_index_caching - hints are not necessary.

Read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6601251003901
and use that technique -- not hints.

(and even if your goal was commendable -- set autotrace traceonly + tkprof is
the way to do it.  no code needs/should be written)

      Rating: 4
Timing SQL
      and/or PL/SQL  December 11, 2002
Reviewer:
      Dan  from Pittsburgh, PA USA
Thanks, Tom. I did the similar things before with Java and Tcl. The basic idea
is to provide our developers a like tool like SQL*Plus's AUTOTRACE command. But,
most of our developers are using tools from third parties, like SQL Navigator,
TOAD, etc. Also, autotrace is not good for pl sql blocks and sotred procedures.
After several tests, I found that using plsql achieves the best results with
statistics collected. Here are what I did:

Step 1. create stats tables
drop table sqlperf_stats;
create global temporary table sqlperf_stats
(sid    number,
 state  char(1),
 statistic# number,
 name   varchar2(64),
 value  number
)
on commit preserve rows
/

grant select, delete, insert, update on sqlperf_stats to public;

Step 2. create stats package
create or replace
package sqlperf as
    procedure stats_begin;
    procedure stats_end;
    runTime  binary_integer;
end sqlperf;
/

create or replace
package body sqlperf as
    runState char(1);

    procedure stats_show is
        cursor curstat is select s.name, a.value - b.value value
                            from v$statname s, sqlperf_stats b, sqlperf_stats a
                           where a.statistic# = s.statistic#
                             and b.statistic# = a.statistic#
                             and (a.value - b.value) > 0
                             and b.state = 'B'
                             and a.state = 'A'
                             and s.name in ('CPU used by this session',
                                            'SQL*Net roundtrips to/from client',
                                            'SQL*Net roundtrips to/from dblink',
                                            'bytes received via SQL*Net from
client',
                                            'bytes received via SQL*Net from
dblink',
                                            'bytes sent via SQL*Net to client',
                                            'bytes sent via SQL*Net to dblink',
                                            'db block gets',
                                            'opened cursors current',
                                            'parse count (hard)',
                                            'parse count (total)',
                                            'parse time cpu',
                                            'parse time elapsed',
                                            'physical reads',
                                            'physical writes',
                                            'redo size',
                                            'session logical reads',
                                            'sorts (disk)',
                                            'sorts (memory)',
                                            'sorts (rows)',
                                            'table fetch by rowid',
                                            'table fetch continued row',
                                            'table scan rows gotten',
                                            'user commits',
                                            'user rollbacks',
                                            'no work - consistent read gets',
                                            'cleanouts only - consistent read
gets',
                                            'rollbacks only - consistent read
gets',
                                            'cleanouts and rollbacks -
consistent read gets'
                                           )
                               order by a.value - b.value desc;

    begin
        dbms_output.enable(1000000);
        dbms_output.put_line('---------------------------------');
        dbms_output.put_line('Statistics - ' || to_char(sysdate,'MM/DD/YYYY
HH24:MI:SS'));
        dbms_output.put_line('---------------------------------');
        for c in curstat loop
            dbms_output.put_line(rpad(c.name, 50) || lpad(to_char(c.value),
20));
        end loop;

    exception
        when others then
            raise_application_error(-20000, 'Failed to show stats.');
    end stats_show;

    procedure stats_begin is
        pragma autonomous_transaction;
    begin
        runState := 'B';
        delete from sqlperf_stats;
        insert into sqlperf_stats
        select m.sid, runState, s.statistic#, s.name, m.value
          from v$mystat m, v$statname s
         where m.statistic# = s.statistic#;
        commit;
        runTime := dbms_utility.get_time;

    exception
        when others then
            raise_application_error(-20000, 'Failed to create session stats.');
    end stats_begin;

    procedure stats_end is
        pragma autonomous_transaction;
    begin
        runTime := dbms_utility.get_time - runTime;
        runState := 'A';
        insert into sqlperf_stats
        select m.sid, runState, s.statistic#, s.name, m.value
          from v$mystat m, v$statname s
         where m.statistic# = s.statistic#;

        stats_show;
        dbms_output.put_line('---------------------------------');
        dbms_output.put_line('Elapsed Time - ' || to_char(round(runTime / 100,
4)) || ' sec.');
        dbms_output.put_line('---------------------------------');
        delete from sqlperf_stats;
        commit;

    exception
        when others then
            raise_application_error(-20000, 'Failed to get session stats.');
    end stats_end;

end sqlperf;
/

Step 3. grant execution privilege to public
grant execute on sqlperf to public;
create public synonym sqlperf_stats for sqlperf_stats;
create public synonym sqlperf for dchen.sqlperf;

To test your code, use some thing like

begin
  sqlperf.stats_begin;
  ...your code ...
  sqlperf.stats_end;
end;

It will add some extra bytes and one commit into the final statistics. I only
included the most intersted statistics here and did not include latch related
things. I learned most of the ideas from Tom's book and this site.
Thanks.

      Rating: 5
Query
      time  April 14, 2003
Reviewer:  Tony
       from Canada
Hi Tom,
      Thanks a lot for your valueable help and time,
My question is: I have a query running from an application, is there any way to
find out the time(from how long it's being executed) I tried it using
v$session_longops for the sid (session running that query) it gave me the
following output:
       SID START_TIM
---------- ---------
       112 06-APR-03
       112 06-APR-03
       112 06-APR-03
       112 06-APR-03
       112 06-APR-03
       112 06-APR-03
       112 06-APR-03
       112 06-APR-03
       112 06-APR-03
       112 06-APR-03
       112

       SID START_TIM
---------- ---------
       112 01-FEB-96

I don't understand why it shows FEB-96 and the rest of the values doesn't seem
valid as the session has a start time of '13-APR-03' (from instance manager),

Please help!!!!

Regards
Tony

          Followup:

That just means that slot hasn't been used -- it is "garbage".  Only look at
rows where TIME_REMAINING > 0

But -- it is easy to see how long a query that is running has been.

LAST_CALL_ET in v$session....

April 14, 2003 Sanjeevi -- Thanks for the question regarding
      "Foreign Key naming ", version 8.1.7

      You Asked
Hi!

Can you suggest any naming scheme/format for foreign keys so that it is unique
and includes as much of information as possible without exceeding the limit.

Regards
Jeeves..

      and we said...
I just usually use:

fk_t1_t2

for example, on the EMP table, I would add:

fk_emp_dept

on the DEPTNO column.  If the concatenated size exceeds 30 characters, just
start removing vowels.  If that doesn't work  fk2t2 should (eg: fk2dept instead
of fk_emp_dept)...

anyone else?
Review & Followup

      Rating: 3
a little bit
      more detail  April 14, 2003
Reviewer:
      jianhui  from DC
I use fk_t1_t2_pk format. For example,
fk_emp_dept_deptno

if the name of the table is too long, just use abbreviation. Same rule applies
to the name of the column.

      Rating: 5
Oracle Designer
      naming convention  April 15, 2003
Reviewer:
      Dushan  from Czech rep.
In my opinion Oracle Designer naming convention is the best, I'm using it for
years:
orders,      short name ordr
order_items, short name ordi

order_items.ordi_id <- FK column to orders.id (PK)
ORDI_PK        Primary key
ORDI_UK        Unique key (if any)
ORDI_CHK       Check constraint (if any)
ORDI_ORDR_FK   Foreign key, to orders
ORDI_ORDR_FK_I Foreign key index

April 14, 2003 Alla -- Thanks for the question regarding "Design
      question with SQL vs Pl/SQL in mind", version 8.1.7

      You Asked
Hi Tom;

You always say that it's not a good idea to switch between SQL and Pl/SQL in a
query, for example

select ...
from ...
where my_function() = :some_value

and that we should always try to come up with the query using only SQL.

I have a question then. When I design my databases I have a so-called look-up
tables. For example, I have an account_type table, that looks like this
account_type_id     number,   -- sequence generated
account_type_code   varchar(4),
account_type_name   varchar(30)

My account_type table will have only few entries, for example - Customer
account, General Journal account. I would identify them using some code - CUST,
GENL. I don't like hardcoding these values in my SQL statements, so I would
usually create a look_up_mgr package with couple of functions like
get_customer_acct_type_id() and get_genl_jnl_acct_type_id() to use in a SQL
statement.

select * from account
where account_type_id = look_up_mgr.get_customer_acct_type_id()

How would you go about this problem? I see only two solutions - use the values
in the SQL statement and improve performance, but decrease reliability OR use
the function, which might decrease the performance, but improve reliability and
readability

Thanks a lot in advance

Alla

      and we said...
how about this:

create view get_customer_acct_type_id
as
select ...

select * from account where account_type_id =
(select * from get_customer_acct_type_id);

is one method.
Review & Followup

      Rating: 5
Wow, this is a
      great idea  April 14, 2003
Reviewer:  Alla
       from Boston, MA USA
Thanks, Tom;

This is really good idea - instead of creating a look-up package, just create
several views and hard-code values there

Thanks a lot

Alla

April 14, 2003 Monica -- Thanks for the question regarding "SQL*
      Loader", version 8.0.5.0.0

      You Asked
I am new to using SQL Loader so I have a question that is
probably easy to answer.  I have a csv file that includes the
column headers that I do not want to import.  For example:

Column1,Column2,Column3

1,red,car
22,black,van
19837,blue, bus

How do I import the data minus the column headers?

Thanks

      and we said...
For this, you would use the SKIP parameter to sqlldr to tell it
to skip the first record (or two)...

sqlldr ... skip=1 ....

Review &
      Followup

      Rating: 4
can we skip
      tailers?  April 11, 2003
Reviewer:
      A reader
Tom,

can we skip last tailer records.

          Followup:

if you know the number of records that are not "trailer" records -- then load=

if the trailer records have some identifying string, use the WHEN clause to skip
them.

is there a way to skip the last "2" records -- no, not really.

      Rating: 4
skip tailer
      record  April 14, 2003
Reviewer:  RAJU
       from USA
yes my tailer record has an identifying string

but i am also using continueif clause as well to store the set of records as a
single row into the table.

in that case my "when" clause is NOT WORKING.

my control file is
SOMETHING LIKE BELOW

load data
infile temp.dat
continue if next(1) <> 'A'
WHEN NEXT(3) <> 'EOF'

sample data

A1
123
789
098
A2
123
ABC
DEF
A3
123
091
CDE
EOF

in the above data from A1 to A2 one record, A2 to A3 is another record, A3 to
EOF is another record.

how can i handles this?

          Followup:

in this case -- why do you care if the last record is glued on?  position or
delimited data wouldn't use it anyway right.

if you always have 3 -- use concatenate instead.

but no, i don't see a straight forward way to deal with this specific case

April 15, 2003 --
      Thanks for the question regarding "Reading the numbers on the explain plan
      output", version 8.1.7

      You Asked
Using CBO, I get a 'cost=N' value in my explain plan output. What are the units
of this number? Say the cost is 13. 13 what?

Is there any way to map this cost to the (elapsed) time to execute the query or
any other more tangible measure?

Say 1 query has a cost of 100 and I add some hints and the cost drops down to
50? Is this query going to be twice as fast?

There is also a 'card=N' in the output. Same question here. I know that
card=cardinality, but how do I make use of this information. Say, I get
'card=3630' in some explain plan. Now what?

Same for 'bytes=N'.

Thanks.

      and we said...
13 nillywillygigs of course.

In theory -- hypothetically speaking -- if everything in the world is perfect
and all of the planets lined up AND it was not raining on tuesday -- a query
with a cost of 50 would run in half the time a query with the cost of 100 would
HOWEVER, it never happens.

The cost is related to time, but not in a straight linear function we can use.

Proof -- alter the optimizer index cost adjust.  Now all of a sudden, the cost
of an index access will go up or down.  The plans change.  But does the data
come back any faster?  Any slower??  Nah -- consider:

big_table@ORA920> select * from big_table where object_id < 20000;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=39 Card=19999 Bytes=1679916)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=39 Card=19999
Bytes=1679916)
   2    1     INDEX (RANGE SCAN) OF 'FOO_IDX' (NON-UNIQUE) (Cost=140 Card=19999)

big_table@ORA920> alter session set optimizer_index_cost_adj = 50;

Session altered.

big_table@ORA920> select * from big_table where object_id < 20000;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=191 Card=19999 Bytes=1679916)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=191 Card=19999
Bytes=1679916)
   2    1     INDEX (RANGE SCAN) OF 'FOO_IDX' (NON-UNIQUE) (Cost=140 Card=19999)

is the second query (first was with 10, not 50) slower???

So, I would say -- no, cost is not excessively useful to you or me in
determining the runtime....

card -- that is the estimated number of rows expected to be output.  You make
use of this mostly be asking yourself "does that look right???" if not, we need
to figure out why -- most likely due to stale or insufficient statistics.

bytes = card * avg_row_width -- again, eyeball it.  does it look right?  if not,
fix it (stale or inappropriately gathered stats)

Review &
      Followup

      Rating:
      3
Thanks  April 14, 2003
Reviewer:
      A reader
1. OK, so the number on the cost= line is basically useless to me.

I am trying to migrate from RBO to CBO and I find myself asked by application
developers, how can I tell if I need to tune the SQL or look somewhere else?

Using RBO, in most cases, a FTS indicates something wrong somewhere.  A missing
index, wrong join, etc.

But CBO seems to choose a FTS more often than RBO and HASH JOINs stuff because,
I guess it considers this cheaper than the corresponding RBO plan (everything
else being the same).

That being the case, what do I tell my developers? Just trust the CBO to always
do the right thing, forget about tuning your SQL?

Also, everything else being the same, init.ora optimizer_mode=choose, one fine
day, I just did dbms_stats.gather_schema_stats(null,cascade=>true) and boom, I
start getting calls left and right. Most applications are slow as molasses.

This is surprising. Everything else being the same, shouldnt CBO choose a
execution plan that results in the same, if not better, performance than RBO?

2. You said "card -- that is the estimated number of rows expected to be output.
 You make use of this mostly be asking yourself "does that look right???" if
not, we need to figure out why -- most likely due to stale or insufficient
statistics."

In many cases, even though I have up-to-date stats, I find that the 'card'
returned on the first line of the plan is way off. ie.. the card is 30000 and my
query returns 200 rows. Why would this be?

I understand "stale", what do you mean by insufficient statistics. I do

dbms_stats.gather_schema_stats(null,cascade=>true)

This should be sufficient, right? It gathers stats for all the tables in my
schema, all indexes and all columns. What more do I need?

          Followup:

1) that is easy.

Look at the logical IO's the query does when executed with "good case" inputs.

For example, "select * from t where x = :x" -- if that returns N rows on average
and I think that x is selective enough to use an index -- I would expect AT MOST
2*N logical ios and optimally N+1 logical IO's  (one for the index block, N for
the table access by rowids).

A Full tablescan does not equate bad, does not equate query wrong, indexes are
as evil as FTS

You cannot "forget about tuning" during design.  You might want to give:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6601251003901
a read through.  Make sure that they ALL have access to and known how to use and
USE sql_trace+tkprof frequently (like all of the time).  They are looking for
reasonably small LIO counts.

Lets put it this way, if you have a query you know will be executed 100 times
per second and it does more then 10's of LIOs -- you better be buying some HUGE
hardware.  That is the query(s) you want to focus in on.  But all queries in a
transactional system should be low volume LIO generators.

what is surprising is that you would make a fundemental change to a system
without planning for testing it... Would you upgrade the OS one fine day
without a plan?  without testing?  why would you make a fundemental change to
the software without one?

2) You gathered stats at an insufficient level of detail perhaps.

try method_opt of 'for all indexed columns' for example, note the differences.
Also, read the above article, it may well be that you need to adjust the
optimizer_index_* parameter for your systems needs.

      Rating: 3
to the previous
      comment  April 14, 2003
Reviewer:  Christo
      Kutrovsky  from Ottawa, ON Canada
I think you need to see:

http://asktom.oracle.com/pls/ask/f?p=4950:8:36024804812412466::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:6601251003901,
Long story short:

consider (to get skewed data statistics):
EXECUTE DBMS_STATS.GATHER_SCHEMA_STATS(NULL, NULL, FALSE, 'FOR ALL COLUMNS SIZE
AUTO', cascade => true);

and in init.ora
optimizer_index_caching = 35
OPTIMIZER_INDEX_COST_ADJ = 50

and then play arround with those.

April 15, 2003 Mario -- Thanks for the question regarding
      "Heterogeneous Services ", version 8.1.7

      You Asked
Hi Tom,

I have set the access to a local dbase table using Heterogeneous Services.

This is Listener.ora
...
    (SID_DESC=
      (SID_NAME=hsodbc)
      (ORACLE_HOME=C:\Programmi\oracle\ora81)
      (PROGRAM=hsodbc)
    )
...

...and Tnsnames.ora:
...
HSODBC  =
  (DESCRIPTION=
            (ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))
            (CONNECT_DATA=(SID=hsodbc))
            (HS=)
  )
...
and the database link:
CREATE  DATABASE LINK "SIGLA"   USING 'HSODBC';

The inithsodbc.ora is:
HS_FDS_CONNECT_INFO = SIGLA
HS_FDS_TRACE_LEVEL = 0

When I run the query

SQL> select count(*)  from intabel@sigla;

  COUNT(*)
----------
      1813

it works fine. The odbc driver access to a local directory on PC1 (the oracle
server)

Now I change the odbc configuration to access a remote disk on a PC2 mapped as
G:
Test it using a query from excel and all is ok.
When I run the query this is the result:

SQL> select count(*)  from intabel@sigla;
select count(*)  from intabel@sigla
                      *
ERROR at line 1:
ORA-00942: table or view does not exist
[Generic Connectivity Using ODBC]393330(0,0,1[[]])
ORA-02063: preceding 2 lines from SIGLA

Does Heterogeneous Services work only if the dbf tables
are on oracle server disk?

Sorry for my poor english. Thanks for all.
Mario

      and we said...
Ahhh -- now I see -- you are trying to use a network disk, not a remote "dbase
server".

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:241814624807
it describes the issues and how you can possibly "solve" it.

the thing to remember is that when YOU log in there is a G: drive in your
login session -- but only in your login session.  The login Oracle is using
sees no such "g:" drive.

Review &
      Followup

      Rating: 4
Feedback
      Requested  April 15, 2003
Reviewer:  Saminathan
      Seerangan  from CA,USA
Dear Tom,

Thank U so much for your prominent support which you are providing to ORACLE
community.

The requirement is to connect DB2 DB which is running
on NT from oracle 9i Rel2 DB which is running on NT.

I went thru Heterogeneous DB Conn Admin Guide and
http://technet.oracle.com/products/gateways/content.html
and did the following

<DB2 info>
username/password= db2admin/db2admin
Machine name(NT)=venkatk
DB name=tpg01db
port #=50001
</DB2 info>

<Changes made in listener.ora>
    (SID_DESC =
      (GLOBAL_DBNAME = tpg01db)
      (ORACLE_HOME = D:\Oracle9i)
      (SID_NAME = tpg01db)
      (PROGRAM=
</Changes made in listener.ora>    )

<FROM Oracle Doc>
The value associated with PROGRAM keyword defines the name of the agent
executable.
</FROM Oracle Doc>

Question 1) What would be the PROGRAM value?

<FROM Oracle Doc>
HS agents are part of the Oracle9i Database, so they are installed by default
with these products. Generic Connectivity uses these agents to connect to the
non-Oracle systems.
</FROM Oracle Doc>

Question 2)How do i find out whether it is running or not?

Please guide me...

Thanks again

          Followup:

you don't say if you actually have the transparent gateway to db2, but you do
have generic connectivity -- all that is required is an ODBC driver.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4406709207206
for a pointer to the docs on doing that.  It has step by steps in it.

(the program would be hsodbc using generic connectivity)...

to see if it is running, create a dblink and query it.

      Rating: 4
Thanks and more
      info provided here  April 15, 2003
Reviewer:
      Saminathan Seerangan  from CA,USA
Tom,Thanks for your immediate response. Also i got the detailed info. from
metalink site DOC ID 114820.1

      Rating:
      5
  December 01, 2003
Reviewer:
      A reader

April 15, 2003 -- Thanks for the question regarding "What is
      EMN0 ?", version 8.1.6

      You Asked
I saw EMN0 in the alert.log while shutting an Oracle instance.
What is this 'dead background process' ?

Fri Jul 14 17:14:59 2000
Restarting dead background process EMN0
EMN0 started with pid=9
Fri Jul 14 17:15:19 2000
Shutting down instance (immediate)
License high water mark = 2
Fri Jul 14 17:15:20 2000
ALTER DATABASE CLOSE NORMAL
Fri Jul 14 17:15:20 2000
SMON: disabling tx recovery
SMON: disabling cache recovery
Fri Jul 14 17:15:23 2000
Thread 1 closed at log sequence 43
Fri Jul 14 17:15:23 2000
Completed: ALTER DATABASE CLOSE NORMAL
Fri Jul 14 17:15:23 2000
ALTER DATABASE DISMOUNT
Completed: ALTER DATABASE DISMOUNT
archiving is disabled

Thaanks

      and we said...
This is support <Note:105067.1>

What is the EMN0 process?
=========================

The EMNO process is new to Oracle8i, and has to do with
improvements made to Advanced Queuing, specifically:

A combination of features are introduced to allow a
publish/subscribe style of  messaging between applications.
These features include rule-based subscribers,
message propagation, the listen feature and notification
capabilities.

OCI functions are available to register a callback for message
notification.  This interface is only valid for the asynchronous
mode of message delivery. In this mode, a subscriber issues a
registration call which specifies a callback.  When messages are
received that match the subscription criteria, the callback
is invoked.  A new background process event monitor (EMON) is
used to notify the subscriber.  This process is started
automatically when the first notification is issued for the
instance.  The application may then issue an explicit
message_receive(dequeue) to retrieve the message.

This functionality is triggered with the OCI call
OCISubscriptionRegister. OCI clients can use the new call
OCISubscriptionRegister to register a callback for
message notification. The client issues a registration call
which specifies a subscription name and a callback. When
messages for the subscription are received, the callback is
invoked. The callback may then issue an explicit dequeue to
retrieve the message.

Note: This is not the same as the new QMON process, which
monitors the Queue tables.  Refer to <Note:72444.1> for further
information on this process.

If you are running release 8.1.x you may see the message "found
dead background process EMNO" in your alert log at startup. If
you are not using the EMNO Advanced Queuing feature you can
safely ignore this message.

Review &
      Followup

      Rating:
      4
  March 17, 2003
Reviewer:  j.

hi tom,

is there a way to get a certain piece of PL/SQL-code started in the event of an
incoming message? could you please give help/details on that?

          Followup:

yes you can.

see

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96587/qopface.htm#64124
and look for the plsql url for example (eg: ctl-f for plsql:

      Rating:
      5
j.  March 17, 2003
Reviewer:
      A reader
thank you tom.

      Rating:
      3
j.  March 18, 2003
Reviewer:
      A reader
I need your help, Tom: I've tried the example "PL/SQL (DBMS_AQ Package): Post of
Object-Type Messages" from
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96587/qopface.htm#64124
I replaced '<schema>.<queuename>:<recipient>' with
'PUBSUB1.ANONSTR:ADMIN' and specified 'plsql://<mypackage>.<myprocedure>'.

queue works fine (as before). but shouldn't <myprocedure> be invoked every time
a message is enqueued for <recipient>. did I do or get anything wrong? please
show me the way.

thanks in advance.

          Followup:

did you setup the aq processes?

SQL> show parameter aq

      Rating:
      3
  March 18, 2003
Reviewer:
      A reader
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

SQL> show parameter aq

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
aq_tm_processes                      integer     5

(I just installed AQ as recommended)

          Followup:

it should be going then, check your logs (alert and such)

      Rating:
      3
  March 18, 2003
Reviewer:
      A reader
I can't find anything special in alert.log. what else can I check?

          Followup:

sorry -- i'm on travel for the next day or two -- i won't be able to really
setup an example in the meanwhile.

what would be helpful (although I won't be able to look at it) is your entire
script to setup the example -- AS SMALL AS HUMANLY POSSIBLE, rip out anything
not relevant to getting it working.

Many times -- i find in the building of such a thing -- i discover the problem.

      Rating:
      5
  March 19, 2003
Reviewer:
      A reader
thank you very much. I'll strip it down to the minimum as soon as possible.

      Rating:
      3
  March 20, 2003
Reviewer:
      A reader
OK, here is, what I've achived so far:

create or replace type QueueEntryType as object(NumValue number);
/

begin
  DBMS_AQADM.create_queue_Table(queue_table        => 'QUEUETABLE',
                                queue_payload_type => 'QUEUEENTRYTYPE',
                                storage_clause     => null,
                                multiple_consumers => true,
                                message_grouping   => DBMS_AQADM.TRANSACTIONAL,
                                comment            => 'Comment');
  DBMS_AQADM.create_queue('QUEUENAME', 'QUEUETABLE');
  DBMS_AQADM.start_queue('QUEUENAME');
end;
/

declare
  v_EnqOpts    DBMS_AQ.Enqueue_Options_T;
  v_MsgProps   DBMS_AQ.Message_Properties_T;
  v_Recipients DBMS_AQ.AQ$_Recipient_List_T;
  v_MsgHandle  RAW(16);

begin
  v_Recipients(1) := SYS.AQ$_Agent('RECIPIENT', null, null);
  v_MsgProps.Recipient_List := v_Recipients;
  DBMS_AQ.Enqueue('QUEUENAME', v_EnqOpts, v_MsgProps, QueueEntryType(123),
v_MsgHandle);
end;
/

declare
  v_DeqOpts   DBMS_AQ.Dequeue_Options_T;
  v_MsgProps  DBMS_AQ.Message_Properties_T;
  v_MsgHandle raw(16);
  v_Msg       QueueEntryType;

begin
  v_DeqOpts.Wait := DBMS_AQ.NO_WAIT;
  v_DeqOpts.Consumer_Name := 'RECIPIENT';
  v_DeqOpts.Navigation :=  DBMS_AQ.FIRST_MESSAGE;

  DBMS_AQ.Dequeue('QUEUENAME', v_DeqOpts, v_MsgProps, v_Msg, v_MsgHandle);
  v_DeqOpts.Navigation := DBMS_AQ.NEXT_MESSAGE;
  DBMS_OutPut.Put_Line(v_Msg.NumValue);
end;
/

begin
  DBMS_AQADM.Stop_Queue('QUEUENAME');
  DBMS_AQADM.Drop_Queue('QUEUENAME');
  DBMS_AQADM.Drop_Queue_Table('QUEUETABLE');
end;
/

I'm sure, my first attempt to get a PL/SQL function notified was inappropriate.
In addititon to the information taken from the link you gave, I've found some
hints, how the interface to a PL/SQL callback funtion should look like

in ../oracle9i/doc/appdev.920/a96612/d_aq2.htm#1010248:

-- begin --
If the notification message is expected for an ADT payload enqueue, the PL/SQL
callback must have the following signature:

procedure plsqlcallback(
  context  IN RAW,
  reginfo  IN SYS.AQ$_REG_INFO,
  descr    IN SYS.AQ$_DESCRIPTOR,
  payload  IN VARCHAR2,
  payloadl IN NUMBER);
-- end --

that 's what I want. could you please make this meet my simple sample? what I'm
looking for is a tiny template, wich includes the most important details and
additional information, how the options could/should be used.
could you please give a more or less step by step explanation and give comments
on topics such as "Identification number of the message" or "context passed to
the PL/SQL function"?

that would be - once again - a  G R E A T  help from U.
thanks in advance, tom.

      Rating: 3
Asynchronous
      notifications in Java  April 14, 2003
Reviewer:
      Piotr Jarmuz  from Braunschweig Germany
Hello Tom,

I can't find any example of how I could achieve the same with AQ using just Java
API (even Oracle specific JDBC). In C using OCI API seems feasible and could be
made available even in Java using JNI but before I set out for writing C code
and JNI wrappers I'd like to know if it is already and directly possible in
Java. Could you, please, give an sample code in Java for registering a callback
in AQ I'd be grateful (so would be other developers I guess)

Meanwhile I have a workaround:

I wrote a small Java GUI Application that uses Oracle builtin package dbms_alert
in order to implement asynchronous notification of changes to the database
table. It works fine. If you change a record in a table from another application
(like sqlplus) the application gets an asynchronous kick and displays a dialog
box and refreshes the listbox. It is implemented using two database connections
and two Java threads. One thread calls indirectly dbms_alert.waitone and goes to
sleep upon this. Once a committed change happens to the database the trigger
sends a signal a wakes up this Java thread, which in turn places a notification
onto swing thread queue so that AWT dispatcher thread can do safely whatever
appropriate.

The HUGE disadvantage of this program is that it requires 2 connections. Sure I
could poll database, say, every second setting an 0 timeout in
dbms_alert.waitany(). But I like more the idea of AQ and async notifications
using just one connection. I think I could keep overall architecture of my
program with Listeners, Events and Notifiers but I need a "gluing" API in Java.
Really need one example...

Could you comment on that code, please?
Do you think it is acceptable to allocate 2 connections per one application
instance?
In MTS setup what combination would be the best:

main_thread    listen_thread
-----------------------------
dedicated     dedicated
dedicated    shared
shared        dedicated
shared        shared

Here I post the code for all of the parts of my solution:

------------------------------SQL----------------------------------------
create table alert(pk number, name varchar2(1000), primary key (pk));
insert into alert select rownum pk, 'name' || rownum name from all_objects where
rownum<=100

create or replace trigger alert_trg
 after insert or update or delete on alert
declare
l_message varchar2(1000);
begin
     if updating then
         l_message:='rows updated';
     elsif deleting then
         l_message:='rows deleted';
     else
         l_message:='rows inserted';
     end if;
     dbms_alert.signal('alert',l_message);
end alert_trg;

------------------------------PL/SQL----------------------------------------

create or replace package test_pkg is
type refcursor is ref cursor;
function get_all return refcursor;
function add_item(p_name varchar2) return number;
procedure delete_item(p_id number);
procedure edit_item(p_id number, p_name varchar2);
procedure wait(p_alert varchar2, p_message out varchar2, p_status out number,
p_timeout number default dbms_alert.maxwait);
end test_pkg;
/

create or replace package body test_pkg as

function get_all return refcursor is
rc refcursor;
begin
    open rc for select pk,name from alert;
    return rc;
end;

function add_item(p_name varchar2) return number is
l_tmp number;
begin
     select alert_seq.nextval into l_tmp from dual;
     insert into alert (pk,name) values (l_tmp, p_name);
     return l_tmp;
end;

procedure delete_item(p_id number) is
begin
     delete from alert where pk=p_id;
end;

procedure edit_item(p_id number, p_name varchar2) is
begin
     update alert set name=p_name where pk=p_id;
end;

procedure wait(p_alert varchar2, p_message out varchar2, p_status out number,
p_timeout number) is
begin
     dbms_alert.register(p_alert);

dbms_alert.waitone(name=>p_alert,message=>p_message,status=>p_status,timeout=>p_t
imeout);
     dbms_alert.remove(p_alert);
end;

end test_pkg;
/

------------------------------JAVA START---------------------------------
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.event.*;
import java.util.*;
import java.sql.*;
import oracle.jdbc.driver.*;

class DBEvent extends EventObject {
    private String message;
    public DBEvent(Object s, String m) {
        super(s);
        message=m;
    }
    public String getMessage() {
        return message;
    }
}

interface DBListener {
    public void objectChanged(DBEvent e);
}

class DBNotifier implements Runnable {
    private String name;
    private String message;
    private Connection con;
    private CallableStatement stat;
    private Vector listeners;

    public DBNotifier(Connection c, String n) throws SQLException {
        con=c;
        name=n;
        stat=con.prepareCall("{ call test_pkg.wait(?,?,?) }");
        listeners=new Vector();
    }

    public void run() {
        try {
            for (;;) {
                if (waitDB()==0) {
                    fireDBListeners(message);
                }
            }
        } catch(Exception e) {
            Util.handleError(e);
        }
        try {
            con.close();
        } catch(SQLException e) {
        }
    }
    public int waitDB() throws SQLException {
        stat.registerOutParameter(2,Types.VARCHAR);
        stat.registerOutParameter(3,Types.NUMERIC);
        stat.setString(1,name);
        stat.execute();
        message=stat.getString(2);
        return stat.getInt(3);
    }
    public void addDBListener(DBListener l) {
        listeners.add(l);
    }
    public void removeDBListener(DBListener l) {
        listeners.remove(l);
    }

    protected void fireDBListeners(final String m) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                DBEvent e=new DBEvent(DBNotifier.this,m);
                for (int i=0; i<listeners.size(); i++) {
                    DBListener l=(DBListener)listeners.get(i);
                    l.objectChanged(e);
                }
            }
        });
    }
}

class StatementCache {
    private Connection con;
    private Hashtable hash;
    public StatementCache(Connection c) {
        hash=new Hashtable();
        con=c;
    }

    public Connection getConnection() {
        return con;
    }

    public CallableStatement getCallableStatement(String s) throws SQLException
{
        CallableStatement stat=(CallableStatement)hash.get(s);
        if (stat==null) {
            stat=con.prepareCall(s);
            hash.put(s,stat);
        }
        return stat;
    }

    public void flush() {
        try {
            for (Enumeration en=hash.elements(); en.hasMoreElements(); ) {
                CallableStatement stat=(CallableStatement)en.nextElement();
                stat.close();
            }
        } catch (SQLException ex) {
        }
    }

    protected void finalize() {
        flush();
    }
}

class OracleCache {
    private static StatementCache cache;
    public static void init(Connection c) {
        cache=new StatementCache(c);
    }
    public static CallableStatement getCallableStatement(String s) throws
SQLException {
        return cache.getCallableStatement(s);
    }
    public static void commit() {
        try {
            cache.getConnection().commit();
        } catch (SQLException ex) {
        }
    }
    public static void rollback() {
        try {
            cache.getConnection().rollback();
        } catch (SQLException ex) {
        }
    }
}

class Entry implements Comparable {
    private int id;
    private String name;

    public Entry() {
    }

    public Entry(String name) throws SQLException {
        CallableStatement stat=OracleCache.getCallableStatement("{ ?=call
test_pkg.add_item(?) }");
        stat.registerOutParameter(1,Types.NUMERIC);
        stat.setString(2,name);
        stat.execute();
        setId(stat.getInt(1));
        setName(name);
    }

    public Entry(int id,String n) {
        setId(id);
        setName(n);
    }
    public String toString() {
        return name;
    }

    public int compareTo(Object o) {
        return name.compareTo(o.toString());
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id=id;
    }

    public String getName() {
        return name;
    }
    public void setName(String n) {
        name=n!=null ? n : "";
    }

    public static Vector getList() throws SQLException {
        Vector v=new Vector();
        CallableStatement stat=OracleCache.getCallableStatement("{ ?=call
test_pkg.get_all }");
        stat.registerOutParameter(1,OracleTypes.CURSOR);

        stat.execute();
        ResultSet rs=(ResultSet)stat.getObject(1);
        while (rs.next()) {
            Entry ent=new Entry(rs.getInt(1),rs.getString(2));
            v.add(ent);
        }
        rs.close();
        return v;
    }

    public void deleteItem() throws SQLException {
        CallableStatement stat=OracleCache.getCallableStatement("{ call
test_pkg.delete_item(?) }");
        stat.setInt(1,getId());
        stat.execute();
    }

    public void addItem(String name) throws SQLException {
        CallableStatement stat=OracleCache.getCallableStatement("{ ?=call
test_pkg.add_item(?) }");
        stat.registerOutParameter(1,Types.NUMERIC);
        stat.setString(2,name);
        stat.execute();
        setName(name);
        setId(stat.getInt(1));
    }

    public void editItem(String name) throws SQLException {
        CallableStatement stat=OracleCache.getCallableStatement("{ call
test_pkg.edit_item(?,?) }");
        stat.setInt(1,getId());
        stat.setString(2,name);
        stat.execute();
        setName(name);
    }
}

class SortedListModel extends DefaultListModel {
    public SortedListModel() {
    }

    public SortedListModel(Vector v) {
        setElements(v);
    }

    public void setElements(Vector v) {
        clear();
        Object arr[]=v.toArray();
        Arrays.sort(arr);
        for (int i=0; i<arr.length; i++)
            super.addElement(arr[i]);
    }

    public void add(int idx, Object o) {
        addElement(o);
    }

    public void setElementAt(Object o, int idx) {
        removeElementAt(idx);
        addElement(o);
    }

    public void addElement(Object o) {
        Comparable a=(Comparable)o;
        int i=0;
        for (i=0; i<getSize(); i++) {
            if (a.compareTo(getElementAt(i))<0) {
                break;
            }
        }
        super.add(i,o);
    }
}

class EntryDialog extends JDialog {
    private Entry ent;
    private JTextField name;
    private JButton okButton, cancelButton;
    private boolean retVal=false;

    public EntryDialog(Frame f, String t, Entry e) {
        super(f,t,true);
        getContentPane().setLayout(new BorderLayout());
        ent=e;

        Box vb=Box.createVerticalBox();
        vb.add(new JLabel("Name"));
        vb.add(name=new JTextField(ent.getName()));
        getContentPane().add(vb,BorderLayout.CENTER);

        Box hb=Box.createHorizontalBox();
        hb.add(okButton=new JButton("OK"));
        hb.add(cancelButton=new JButton("Cancel"));
        getContentPane().add(hb,BorderLayout.SOUTH);

        ActionListener al=new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    if (ent.getId()==0) {
                        ent.addItem(name.getText());
                    } else {
                        ent.editItem(name.getText());
                    }
                    OracleCache.commit();
                    retVal=true;
                    hide();
                } catch (SQLException ex) {
                    OracleCache.rollback();
                    Util.handleError(ex);
                }
            }
        };
        okButton.addActionListener(al);
        name.addActionListener(al);
        cancelButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                hide();
            }
        });
        pack();
        Util.centerWindow(this);
    }

    public Entry getEntry() {
        return retVal ? ent : null;
    }
}

class SQLPanel extends JPanel {
    private JList list;
    private JButton addButton, deleteButton, editButton, refreshButton;

    public SQLPanel() {
        setLayout(new BorderLayout());
        Vector v=new Vector();
        try {
            v=Entry.getList();
        } catch (SQLException e) {
            Util.handleError(e);
        }
        Box vb=Box.createVerticalBox();
        list=new JList(new SortedListModel(v));
        Box hb=Box.createHorizontalBox();
        hb.add(new JScrollPane(list));
        vb.add(hb);

        hb=Box.createHorizontalBox();
        hb.add(addButton=new JButton("Add"));
        hb.add(deleteButton=new JButton("Delete"));
        hb.add(editButton=new JButton("Edit"));
        hb.add(refreshButton=new JButton("Refresh"));
        vb.add(hb);
        add(vb,BorderLayout.CENTER);

        addButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                Entry ent=new Entry();
                EntryDialog dial=new EntryDialog(null,"Add item",ent);
                dial.show();
                if (dial.getEntry()!=null) {
                    SortedListModel slm=(SortedListModel)list.getModel();
                    slm.addElement(ent);
                }

            }
        });

        deleteButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                int idxs[]=list.getSelectedIndices();
                SortedListModel slm=(SortedListModel)list.getModel();
                try {
                    for (int i=idxs.length-1; i>=0; i--) {
                        Entry ent=(Entry)slm.remove(idxs[i]);
                        ent.deleteItem();
                    }
                    OracleCache.commit();
                } catch (SQLException ex) {
                    OracleCache.rollback();
                    Util.handleError(ex);
                }
            }
        });

        editButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                int idx=list.getSelectedIndex();
                if (idx<0)
                    return;
                Entry ent=(Entry)list.getModel().getElementAt(idx);
                EntryDialog dial=new EntryDialog(null,"Edit item",ent);
                dial.show();
                if (dial.getEntry()!=null) {
                    SortedListModel slm=(SortedListModel)list.getModel();
                    slm.set(idx,ent);
                }
            }
        });
        refreshButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                refresh();
            }
        });

    }
    public void refresh() {
        try {
            Vector v=Entry.getList();
            ((SortedListModel)list.getModel()).setElements(v);
        } catch (SQLException ex) {
            Util.handleError(ex);
        }
    }
}

public class AlertTest extends JFrame implements DBListener {
    private SQLPanel panel;

    public void objectChanged(DBEvent e) {
        panel.refresh();
        Util.message(null,"Database record: "+e.getMessage());
    }

    public AlertTest(String title) {
        super(title);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setContentPane(panel=new SQLPanel());
        pack();
        Util.centerWindow(this);
        setVisible(true);
    }
    public static void main(String s[]) {
        String user=null,password=null;
        String db="jdbc:oracle:thin:@localhost:1521:testdb";
        switch (s.length) {
        case 3:
            db=s[2];
            // fall through
        case 2:
            user=s[0];
            password=s[1];
            break;
        default:
            System.err.println("Usage AlertTest user password [db]");
            System.exit(1);
        }
        try {
            OracleCache.init(Util.getConnection(user,password,db));
            DBNotifier dbn=new
DBNotifier(Util.getConnection(user,password,db),"alert");
            dbn.addDBListener(new AlertTest("test"));
            new Thread(dbn).start();
        } catch(Exception e) {
            Util.handleError(e);
            System.exit(1);
        }
    }
}

class Util {
    public static void centerWindow(Window w) {
            Dimension ss=Toolkit.getDefaultToolkit().getScreenSize();
            Dimension ws=w.getSize();
            if (ws.height>ss.height)
                    ws.height=ss.height;
            if (ws.width>ss.width)
                    ws.width=ss.width;
            w.setLocation((ss.width-ws.width)/2,(ss.height-ws.height)/2);
    }
    public static boolean empty(String s) {
            return (s==null || s.length()==0);
    }
    public static void error(final Component p, final String s) {

JOptionPane.showMessageDialog(p,s,"Error!",JOptionPane.ERROR_MESSAGE);
    }
    public static void message(final Component p, final String s) {

JOptionPane.showMessageDialog(p,s,"Message!",JOptionPane.INFORMATION_MESSAGE);
    }
    public static void handleError(Exception e) {
        e.printStackTrace();
    }
    public static Connection getConnection(String user, String password, String
db) {
        try {
              Class.forName ("oracle.jdbc.driver.OracleDriver");
              Connection con=DriverManager.getConnection(db,user,password);
              con.setAutoCommit(false);
              return con;
        } catch (Exception e) {
              Util.handleError(e);
              System.exit(1);
        }
        return null;
    }

}
------------------------------JAVA END---------------------------------

Best regards.

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96587/qprogenv.htm#62580
use JMS.


Subject | Article | Last Updated (East coast time)
Ho to drop a datafile on oracle 8.1.7? | article only | Apr 16, 2003 10:09 am
running a report in d2k forms 5.0 from the menu | article only | Apr 16, 2003 10:11 am
Stuck in SVRMGRL? | article only | Apr 16, 2003 11:56 am
Database transfer from SQL Server to Oracle | article only | Apr 16, 2003 06:22 pm
Package Variable - is it refreshed everytime? | article only | Apr 17, 2003 10:27 am
alter table monitoring clause | article only | Apr 17, 2003 10:37 am
MINUS JOIN | article only | Apr 17, 2003 10:50 am
security and cookies | article only | Apr 17, 2003 03:05 pm
all_objects vs dba_objecs, describe | article only | Apr 17, 2003 07:57 pm
Can I change a range-partitioned table to a composite partitioned table? | article only | Apr 18, 2003 12:36 pm
Vacant Key Query | article only | Apr 18, 2003 04:31 pm
IMP-00020: long column too large for column buffer size | article only | Apr 19, 2003 11:58 am
analytic functions | article only | Apr 19, 2003 01:05 pm
UTL_FILE advantages | article only | Apr 19, 2003 01:16 pm
Specific about oracle | article only | Apr 20, 2003 10:35 am
Reading deadlock trace files | article only | Apr 20, 2003 02:57 pm
selecting every second record | article only | Apr 20, 2003 04:49 pm
How do I pass a CLOB value to a stored procedure? | article only | Apr 21, 2003 07:15 am
Space information | article only | Apr 21, 2003 07:22 am
oracle designer | article only | Apr 21, 2003 08:27 am
how to identify indexes created when defining primary key | article only | Apr 21, 2003 10:39 am
CBO: Comparing 2 plans; behaviour of CHOOSE | article only | Apr 21, 2003 10:55 am
Implicit cursors | article only | Apr 21, 2003 10:56 am
Displaying progress of a long-running job | article only | Apr 21, 2003 02:08 pm
Remote Tables insert | article only | Apr 21, 2003 02:14 pm
How should i promise our customers? | article only | Apr 21, 2003 03:34 pm
How to preventing front-end users accessing the db via sqlplus , etc | article only | Apr 21, 2003 10:30 pm
pl/sql for native execution | article only | Apr 21, 2003 10:39 pm
Bind variables and execution plan | article only | Apr 21, 2003 10:40 pm
How Oracle keep track of object size | article only | Apr 22, 2003 07:54 am
storage of numbers and a shared sql question | article only | Apr 22, 2003 09:36 pm
Automomous transactions and cursors over db links | article only | Apr 22, 2003 09:38 pm
row(s) 1 - 32 of 32
April 16, 2003 Tarek -- Thanks for the question regarding "Ho to drop a datafile on oracle 8.1.7?", version 8.1.7
You Asked
Hi Tom,
i need to drop a datafile which was created for error.
I have searched your website and already found a similar issue but
it was opened for oracle 7, so maybe with 8i things changed.
It seems to be that datafiles cannot be dropped, isn't it?
I have created a datafile in an existing tablespace which has another
datafile, so I want to drop only the new datafile (not all tablespace).
Does oracle write to the new datafile immediately or it will use it after
filling out all space of existing datafile?
From your article
http://asktom.oracle.com/pls/ask/f?p=4950:8:38345827218403780::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:515222144417,
you only say to resize the datafile to less than 5 oracle blocks so it will
never be used for extent allocation. But in this way information
in control file are still there.
At least, can i drop it or this is the only way?
Thanks
Tarek
and we said...
Once added, it is part of the database forever unless you drop the entire
tablespace.

So, either

a) live with it
b) shrink it and live with it
c) create a new tablespace and move everything over there and then drop this
one.

a or b being my suggested course of actions...
Review & Followup
Rating: 3
"Ho to drop a datafile on oracle 8.1.7?", April 16, 2003
Reviewer: Himnish from Chandigarh, India
Tom, we can take the export of the objects in that tablespace. Drop the
tablespace and recreate with required datafiles and then import back.

It should work.

Followup:
yes, very much like c)

still, i like A) and B)

every time you take the data OUT you risk not getting it back IN -- you
definitely suffer downtime.

April 16, 2003 tushar -- Thanks for the question regarding "running a report in d2k forms 5.0 from the menu", version
You Asked
Please tell me the procedure to run a
report ( .rep or .rdf) in D2k forms 5.00
from then menu.

when I use Run_product built-in the
error is:
'too many declarations mach this call.'

The syntax I use for running
x.rep report is :
run_product(reports,'c:\temp\x.rep',asynchronous,runtime,filesyst
em,null);
and we said...
You are missing the parameter list parameter.  There are 7
inputs into run_product and you have only 6.

declare
  pl_id paramList;
begin

  ...
  Run_Product(REPORTS, 'empreport', SYNCHRONOUS, RUNTIME,
           FILESYSTEM, pl_id, NULL);
  ....
end;

would be a valid call to run_product.

Here is the full example:

/*

** Built-in:  RUN_PRODUCT
** Example:   Call a Report Builder report, passing the
**            data in record group 'EMP_RECS' to substitute
**            for the report's query named 'EMP_QUERY'.
**            Presumes the Emp_Recs record group already
**            exists and has the same column/data type
**            structure as the report's Emp_Query query.
*/
PROCEDURE Run_Emp_Report IS
  pl_id ParamList;
BEGIN
  /*
  ** Check to see if the 'tmpdata' parameter list exists.
  */
  pl_id := Get_Parameter_List('tmpdata');
  /*
  ** If it does, then delete it before we create it again in
  ** case it contains parameters that are not useful for our
  ** purposes here.
  */
  IF NOT Id_Null(pl_id) THEN
    Destroy_Parameter_List( pl_id );
  END IF;
  /*
  ** Create the 'tmpdata' parameter list afresh.
  */
  pl_id := Create_Parameter_List('tmpdata');
  /*
  ** Add a data parameter to this parameter list that will
  ** establish the relationship between the named query
  ** 'EMP_QUERY' in the report, and the record group named
  ** 'EMP_RECS' in the form.
  */
  Add_Parameter(pl_id,'EMP_QUERY',DATA_PARAMETER,'EMP_RECS');
  /*
  **Pass a Parameter into PARAMFORM so that a parameter dialog
will not appear
  **for the parameters being passing in.
  */

  Add_Parameter(pl_id, 'PARAMFORM', TEXT_PARAMETER, 'NO');
  /*
  ** Run the report synchronously, passing the parameter list
  */
  Run_Product(REPORTS, 'empreport', SYNCHRONOUS, RUNTIME,
           FILESYSTEM, pl_id, NULL);
END;

Thanks
--

Thomas Kyte     tkyte@us.oracle.com
Reston VA
http://www.oracle.com/oramag
Review & Followup
Rating: 3
Generating to file loses param list March 28, 2002
Reviewer: Running in Circles from Calgary, AB Canada
Hi Tom,

I am using run_product with a number of parameters that restrict the data
returned in the report (v6).  Once the report displays in the previewer I choose
to generate it to a file (PDF) it then generates this report based on the
initial values for the parameters instead of the ones it was passed?

Is there some way, from the previewer to have it generate the file using the
received parameters, or to save to file the report in the previewer?

Thanks so much!

Rating: 2
parameter passing to reports.... April 16, 2003
Reviewer: Kiran Shah from India
How to pass comparatively large no. of inputs.(About 50-60 no. of inputs) to
report
using run_product ?
These parameters includes text fields, date fields,nested tables and lob fields.

what is the better way to implement this ?

Should I use forms for all these inputs and call report from form ?
But then how to pass these parameters ?(especially nested tables and lob fields)
?

Or should I use parameter form facility of Reports ?
(I don't know whether parameter form supports Nested tables, field validation,
LOV etc.)

Or any other solution ?(Like in HTML/Java/PHP)

We are using Oracle 8i, Report & Form 6.0

Thanks,
Kiran

Followup:
sorry -- never wrote a report in my life.

try otn.oracle.com -> discussion forums.   lots of people hanging out in the
developer discussion group.

Rating: 1
for Kiran April 16, 2003
Reviewer: Riaz Shahid from PRAL, Islamabad, Pakistan
Hi there !

When you run the report...you should also ask the user what kind of putput he is
wanting (e.g; screen, printer, PDF/HTML ect converted file). So that when u run
the report, it will be run as u wanted.

You will have to pass those parameters thru form(better way). Report has LOV but
no much more validatation can be done.

Regards

Regards

April 16, 2003 Doug -- Thanks for the question regarding "Stuck in SVRMGRL? ", version 8;1.7.4
You Asked
Tom - I tried this twice in svrmgrl..
I forgot to put a bang in front of an OS command and ended up stuck in svrmgrl..
why?

SVRMGR> ls -ltr /arch01/oradata/HRPRD/*
     2> ;
     3> .                    => At this point nothing works..
     4>                      => Not ctrl-c.. ".".. nothing..
     5> .
     6>
     7> ^C
     8> .
     9> quit
    10> ^C
    11> '
    12> .
    13> .
    14> .
    15> .
    16> .
    17> .
    18> qyutl
    19> quit;
    20>

Thanks.
and we said...
Cool -- but it makes "sense".

/*  <<<---- beginning of comment....

To escape from your personal hell, enter */

;)

SVRMGR> ls -ltr /tmp/*
     2> ;
     3> /
     4>
     5> quit
     6> */                   <<<=== closed comment
     7> /                    <<<=== run block
ls -ltr /tmp/*
*
ORA-01012: not logged on
SVRMGR>
Review & Followup
Rating: 5
Coooooooooool........... September 05, 2002
Reviewer: Subhro

Rating: 5
Thank you September 05, 2002
Reviewer: Doug from CT, USA
Clears that up.. thanks Tom.

Rating: 3
Ur Admirer September 05, 2002
Reviewer: Sagi from India
Very funny and cool.

It was very helpful.

Regards

Rating: 4
How come I can't get stuck ? September 05, 2002
Reviewer: Robert from PA
Not too cool for me....(AIX/8.1.7.2)
SVRMGR> ls -aFl
     2>
     3> .
SVRMGR> ls -aFl
     2> /
ls -aFl
*
ORA-01012: not logged on
SVRMGR> ls -aFl
     2> ;
ls -aFl
*
ORA-01012: not logged on
SVRMGR> ls -aFl
     2> ^C/
ls -aFl
*
ORA-01012: not logged on
SVRMGR>

Followup:
you did not start a comment

ls -aLF /tmp/*

"/*" is the beginning of a comment

Rating: 5
SVRMGR Hang September 06, 2002
Reviewer: shanmugam from Singapore
I am getting different Error. What is the difference?
SVRMGR> ls -ltr /tmp/*
     2> ;
     3> /
     4>
     5> */
     6> /
ls -ltr /tmp/*
*
ORA-00900: invalid SQL statement
SVRMGR>

Followup:
you are connected and

ls -ltr /tmp

is not a valid sql statement.  I was not connected, hence I got "not connected"
error instead.

Rating: 2
Reader September 08, 2002
Reviewer: A reader
The way to get of this which always works in UNIX is

<ctrl> Z
jobs
ps -ef |grep < the job > and identify the PID
kill <that PID>

Followup:
easier just to open a new window perhaps?  But that wasn't the question here.
the question was "why", not "what can I do"....

(and to say "always works in UNIX" is a tad strong.  Never know what stty
commands someone might have typed, what their telnet client might be doing, what
the client svrmgrl might do even....

$ svrmgrl

Oracle Server Manager Release 3.1.7.0.0 - Production

Copyright (c) 1997, 1999, Oracle Corporation.  All Rights Reserved.

Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production

SVRMGR> ^Z^Z^Z^Z^Z^Z
     2>
     3>
     4> /
??????
*
ORA-01012: not logged on

actual cut and paste on Solaris....).  Always, no.

Rating: 3
look cool but !!!!!!!!!! September 09, 2002
Reviewer: Radhakrishna from Mumbai , MH India
        We should mind all such thing like above. Though it looks simple but it
will raise n # of doubts.

Rating: 4
Reader September 09, 2002
Reviewer: A reader
Just for the benefit of those who may still be interested,
and to clear the doubts:

Find the ^ for "susp" signal
-----------------------------

$ stty -a
speed 19200 baud; line = 1; 28 rows; 135 columns
intr = ^C; quit = ^\; erase = ^H; kill = ^U; eof = ^D; eol = ^@; old-swtch = ^@;
susp = ^Z
lnext = ^V; werase = ^W; rprnt = ^R; flush = ^O; stop = ^S; start = ^Q; dsusp =
^@
-parenb -parodd cs8 -cstopb hupcl cread clocal -cnew_rtscts -loblk
-ignbrk brkint ignpar -parmrk -inpck istrip -inlcr -igncr icrnl -iuclc
ixon ixany -ixoff -imaxbel
isig icanon iexten -xcase echo echoe echok echoke echoctl -echoprt -echonl
-noflsh -flusho -pendin -tostop
opost -olcuc onlcr -ocrnl -onocr -onlret -ofill -ofdel tab3
$ svrmgrl

Oracle Server Manager Release 3.1.6.0.0 - Production

Copyright (c) 1997, 1999, Oracle Corporation.  All Rights Reserved.

Oracle8i Enterprise Edition Release 8.1.6.0.0 - Production
JServer Release 8.1.6.0.0 - Production

SVRMGR> ^Z ( in this case ^z is mapped to "susp")

Find the PID
------------

$ /etc/whodo
Mon Sep  9 14:12:13 2002

ttyq1        user1   6:46
    pts/1        58853751    0:01 tail

ttyq4        user2   6:07
    pts/4        55719325    0:00 csh
    pts/4        59015420    0:04 sqlplus

ttyq5        user3   6:09
    pts/5        58814384    0:00 csh
    pts/5        58953754    0:01 ssh2

ttyq6        oracle   8:58
    pts/6        57706848    0:00 csh
    pts/6        58932625    0:00 sh
    pts/6        58992539    0:01 tail

ttyq7        oracle   9:00
    pts/7        58919080    0:00 csh
    pts/7        59298087    0:00 sh
    pts/7        59354222    0:00 svrmgrl       <---- This is the pid
      ?          59320205    0:01 oracle
    pts/7        57795551    0:00 whodo

ttyq8        oracle   9:26
    pts/8        59042267    0:00 csh
    pts/8        59031287    0:00 sh

ttyq10       user1   9:26
    pts/10       58997698    0:00 csh
    pts/10       55557962    0:01 tail

$ ps -ef |grep svrmgrl|grep -v grep
 oracle   59354222   59298087  0 14:12:02 pts/7   0:00 svrmgrl
$ kill 59354222
$ ps -ef |grep svrmgrl|grep -v grep
[1] + Terminated               svrmgrl
$ ps -ef |grep svrmgrl|grep -v grep
$

Rating: 3
Oracle DBA April 16, 2003
Reviewer: Arash from Canada
Hi Tom,

  I have a really weird problem. The following is the story.

OS: Windows 2000
Oracle: 8.0.5

  I have 4 databases on this Server which work fine. It's been a while that
whenever I create a new database and new instance, at the beginning it is ok(I
mean I can connect as Internal and shutdown or startup database), but after a
while I can still connect as Internal, but I can't do anything, even show SGA !
and the error is
 ORA-01012 Not Logged On

I recreated password file, but to no avail.

I would appreciate it if you let me know, what might cause this and what should
I do.

Thanks for your time,
Arash

Followup:
you do have a wierd problem.

You are using the wrong operating system. 805 -- win2k = no go.  win2k =
operating system invented far after 805 was born (like well over 2 years).

I'm not really going to spend very much time on this -- it was never supposed to
work.

I have a really hard time understanding why people upgrade their OS at the drop
of a hat -- but refuse to keep up with the database.

Sorry -- if you go back to NT4.0 to go with your database software maybe that
would work.

But then again, that software is so far out of support...

Rating: 1
April 16, 2003
Reviewer: A reader
Thank you so much for your prompt answer Tom.

Regards,
Arash

April 16, 2003 Rajesh -- Thanks for the question regarding "Database transfer from SQL Server to Oracle", version 8i
You Asked
1. Before I go for Oracle database, I would like to know whether
migrating the database(all objects and data as well) from SQL
Server 7.0 to Oracle 8i is possible or not? If so how?

2. I would like to know how do I declare binary datatype? Like
in SQL Server 7.0 I can say create table temp(uid binary(8)).
and we said...
It is possible -- every Orace8i database ships with a SQLServer
migration kit to ease the process.

You can read about it at
http://technet.oracle.com//tech/migration/workbench/

For #2, you would use the RAW type

create table temp( uid raw(8) );
Review & Followup
Rating: 4
A related question April 16, 2003
Reviewer: John from New Jersey, USA
We need to transfer data from a SQL Server 7 database to an Oracle database on a
daily basis.  The solution I can think about is to generate text files from SQL
Server database, and then load the text file into Oracle using SQL*Loader.  I
wonder can Oracle query the SQL Server database directly, or are there other
ways of doing this?

We also need to take some data from Oracle database and update the SQL Server
database.  My plan again is to do it through text files.  Any other better ways?

Followup:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4406709207206
set up generic connectivity, you can select from sqlserver and insert into
sqlserver from Oracle -- giving sqlserver all of the power of Oracle
functionality ;)

See you can upgrade SS without touching their software...

      Rating: 5
Thank you for
      your prompt response.  April 16, 2003
Reviewer:
      John  from New Jersey, USA
It was eye opening.  I'll be very much pleased if it works in our environment.

      Rating: 4
Reverse exist
      or not ( from Oracle to SQL server  April 16, 2003

Reviewer:  Kumar  from San Diego
Tom,

If suppose any one wants to migrate the entire stuff from Oracle to SQLserver,
how this can be achieved? Do we have any such utilities exist. My concern is in
SQLSERVER no packages functionalities exist, if in such case of migrations, how
those utilities would be doing this kind of stuff without fail?

Thanks
Kumar

          Followup:

that would be a good question for askBill@microsoft.com don't you think...

I don't "go that way"

      Rating: 5
kumar please
      use askbill.com  April 16, 2003
Reviewer:
      reader  from usa
oracle to sqlserver?  why would oracle provide that?
why would anyone want to do that?  why would tom even consider looking into that
for you?

      Rating:
      3
Ha..Ha..Funny!  April 16, 2003
Reviewer:
      Sunny Chakraborty  from Stamford, CT
Yes go askbill.com [have to pay $$ thought?].. maybe buy a askbill support
licence from microsoft- :)

      Rating: 3
Agreed but
      looking for info for such .......  April 16, 2003

Reviewer:  kumar  from San Diego
Hi Folks,

I agree your point, why Oracle do that -- correct? In reality Oracle do a lot
like whenver a new windows OS then Oracle releases happens on them. So this is
the world of TECH. I am curious to ask this, what if a big Oracle shop ( like
the one I have) might need some small other databases interfaces with SQLSERVER.
In this kind of world there is no end boundaries for conversions, migrations
etc. Hence I just posted if any information exist with any clue like things.
However no way to think nor make the buisness down to Oracle.

Thnaks for suggestions and I am just investigating if any such gateways exist. I
am also sure Bill.com did not setup any gate ways till date however some useful
info published. I know this causes $$ to do this untill Bill.com emerges one day
with some key.

      Rating: 5
sqlserver's
      tool should do that  April 16, 2003
Reviewer:
      A reader
You need to look at the tools that come with sqlserver. Database system software
would generally provide "inbound" migrations (i.e. mirgate *to* my rdbms), but
not "outbount" migrations (i.e. migrate *from* my rdbms). Therefore you should
really explore what sqlserver (the target database you want to migrate to) has
to offer.

April 17, 2003 Jan -- Thanks for the question regarding "Package
      Variable - is it refreshed everytime?", version 8.1.7

      You Asked
CREATE OR REPLACE PACKAGE Pkg_Vars
IS

gv_date             DATE:=SYSDATE;
gv_value            VARCHAR2(10):=Get_Value_of_Sys_Param('BASE_COUNTRY');
..
..

------------

Function Get_Value_of_Sys_Param is just
SELECT value FROM Table WHERE code = p_parameter

What I found out is that:

1. variable GV_DATE is not synchronized with SYSDATE. It is initialised only
once (at the 1-st using) and then it keeps the same value as it was at the
begining.

2. GV_VALUE is synchronized. It has always the same value as it is in the table.
E.G. if at the begining the value in the table is SK, the GV_VALUE ='SK', if I
update the value to 'CZ' in the table, the variable will have the new value
'CZ'.

Why is this behaviour diferent? What is the rule?

Thank you,

      and we said...
1) correct.

2) false, you have made a mistake in your analysis.

Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x varchar2(30), y
varchar2(30) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 'BASE_COUNTRY',
'Hello' );

1 row created.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function get_value( p_str
in varchar2 ) return varchar2
  2  as
  3          l_return  t.y%type;
  4  begin
  5          select y into l_return from t where x = p_str;
  6          return l_return;
  7  end;
  8  /

Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package demo_pkg
  2  as
  3          global1  date := sysdate;
  4          global2  varchar2(30) := get_value( 'BASE_COUNTRY' );
  5  end;
  6  /

Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2          dbms_output.put_line( to_char( demo_pkg.global1, 'dd-mon-yyyy
hh24:mi:ss' ) );
  3          dbms_output.put_line( demo_pkg.global2 );
  4  end;
  5  /
31-may-2002 07:52:27
Hello

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> update t set y = 'World';

1 row updated.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_lock.sleep(4);

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select to_char(sysdate,'dd-mon-yyyy
hh24:mi:ss'), x, y from t;

TO_CHAR(SYSDATE,'DD- X                              Y
-------------------- ------------------------------
------------------------------
31-may-2002 07:52:32 BASE_COUNTRY                   World

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2          dbms_output.put_line( to_char( demo_pkg.global1, 'dd-mon-yyyy
hh24:mi:ss' ) );
  3          dbms_output.put_line( demo_pkg.global2 );
  4  end;
  5  /
31-may-2002 07:52:27
Hello

PL/SQL procedure successfully completed.

as you can see -- the global variable values are not changed.  The variables
are initialized ONCE per session.

Review &
      Followup

      Rating: 4
Great but
      please explain further...  April 17, 2003

Reviewer:  Michael  from Australia
Hi Tom

I normally have a package spec and a body.

If I declare gv_date on the spec, does it have the same behaviour as I declare
it on the package body?

Thanks in advance

          Followup:

Nope.

If you put it in the spec -- anyone with execute can READ or WRITE it's value.

If you put it in the body -- only code in the body can READ or WRITE it (you
would supply "getter" and "setter" routines to modify/access it)

of course, you can use "constant" in the spec to make a global variable READABLE
by all but not writeable

April 17, 2003 vrishali -- Thanks for the question regarding
      "alter table monitoring clause", version 8.1.6

      You Asked
Hi Tom,

 I want to know if I have altered the table with 'Alter Table
Monitoring ' will it create load on Database if lot of inserts
are occuring on that table duringg the day.

Thanks
Vrishali

      and we said...
It appears to have minimal impact (no measurable impact that I
could observe) and I see nothing in the problem database about
it being a performance issue.

However, if you are concerned about this, and since you know the
table is undergoing lots of inserts during the day, monitoring
is not really needed - you know for a fact the stats are going
stale frequently and need to be re-gathered.  Monitoring is all
about helping you find the tables that need stats to be
gathered.  This table would be known to need frequent statistics
collection.
Review & Followup

      Rating: 3
Doesn't
      MONITORING actually ANALYZE a table ?  October 02, 2001

Reviewer:  Andre Nasser  from Brazil
I didn't quite understand the use of MONITORING. From what I read in the Oracle
books, it enables automatic statistics collection in the SGA and from time to
time, they are flushed to the data dictionary.

Seems you're saying it only keeps track of which specific tables are being
modified.

Please clarify...

          Followup:

See

http://technet.oracle.com/doc/oracle8i_816/server.816/a76992/stats.htm#25754
for the details.  When you designate a table for monitoring, Oracle will watch
for DML activity on it.  It will capture the approx. number of
inserts/updates/deletes for the table.

Later, DBMS_STATS can be used to gather stats on "stale" tables.  Tables are
classified "stale" by the act of monitoring them.

It takes a combination of MONITORING and DBMS_STATS with a "gather stale" to
update the stats for stale tables -- monitoring does not analyze the tables
themselves, only counts (approximately) the DML hits on it.

      Rating:
      5
Reader  October 02, 2001
Reviewer:
      Reader  from USA
Tom,

Is there an algorithm Oracle uses to establish if the
statistics is fresh or not. Any combination of total
# of rows to total # of DMLS etc..

Thanks

          Followup:

The criteria for monitoring implying stales is that the total number of inserts,
deletes and updates for the table exceeds 10% of the number of rows in the table
as recorded by the last analyze.

      Rating: 5
Thanks for the
      followup !!!  October 03, 2001
Reviewer:  Andre
      Nasser  from Brazil
Tom,

Now I see what you mean. In fact, the Oracle material and online books are not
very clear about this feature. Even the 8i classes I attended did not cover this
subject.

Thanks a lot !!!

      Rating: 4
Table
      Monitoring  October 04, 2001
Reviewer:
      William  from Australia
Tom,

If a table is set to MONITORING and its statistics are stale, will Oracle then
ignore the current statistics until the tables are re-analyzed?

          Followup:

No.

All monitoring does is count about how many rows have been modified.  The stats
that are in place are used until you re-analyze.

The stats that are in place, even though they are stale, are probably much
better then "guessing" which is what it would do if it ignored them.

      Rating: 4
Stale
      statistics  November 30, 2001
Reviewer:  William
      Lee  from Australia
Tom,

Once a table has been tagged as having stale statistics, does the optimizer
still use the current statistics?

I was experementing with this feature and noticed performance problems with
queries until I re-analyzed the tables with stale statistics.

Thanks

          Followup:

yes, the "stale" stats are used.

the other even less desirable option would be to "guess" at the stats.

The reason you see poor performance with stale stats is that the stats are
INCORRECT, instead of their being 1,000 rows for example, there are now
1,000,000 and the plan of attack for 1,000 rows would be different then for 1
million.

      Rating: 4
Re: Stale
      statistics  December 05, 2001
Reviewer:
      William  from Australia
Tom,

I don't agree with what you mentioned about the stale statistics. Firstly,
without using MONITORING, I analyze all my tables once a week. When I set the
tables to be MONITORING, I had a script to analyze tables with stale statistics
every 6 hours.

With MONITORING set, some of the queries would run slow but would run ok if I
went and manually analyzed the table with stale statistics.

From this I would assumed that its not using stale statistics. I have given up
on this and have gone back to doing a full analyzed once a week.

Cheers

          Followup:

Sorry you don't "agree" but it is what happens.  You should have analyzed the
problem a little more thoroughly.  Your assumption is incorrect, something else
was the culprit.  Perhaps -- it was because you were running analyzes when
people where querying the data and the analyze was contending for the same
resources (disk) as the queries were.  You would have had to analyze the plans
of the queries (a) with the stats "fresh", (b) when they didn't work, and (c)
what they were right after making them fresh again.  If they didn't change
between (a) and (b) that means that the stats were in fact being used (since the
plans were the same).  If they changed between (b) and (c) that means the
collection of stats was needed -- NOT that the stats were ignored.

Sigh, thus begins another rumor/myth.  This is how they start you know.  Pretty
soon there will be an army of DBA's refusing to use another tool simply due to
heresay (same reason people don't use auditing, reorg tables into a single
extent for "performance" and so on)....

      Rating:
      4
SYS.DBA_TAB_MODIFICATIONS   April 17, 2003

Reviewer:  reader
tom,

i tried the following, i wonder why there are no rows in the
SYS.DBA_TAB_MODIFICATIONS ..please let me know where i went wrong.

ge$scott@workflow.us.oracle.com>ALTER TABLE EMP MONITORING;

Table altered.

ge$scott@workflow.us.oracle.com>analyze table SCOTT.EMP compute statistics;

Table analyzed.

ge$scott@workflow.us.oracle.com>select table_name, num_rows, blocks, avg_row_len
from dba_tables where table_name='EMP';

TABLE_NAME                       NUM_ROWS     BLOCKS AVG_ROW_LEN
============================== ========== ========== ===========
EMP                                    28          1          40

ge$scott@workflow.us.oracle.com>alter table SCOTT.EMP monitoring;

Table altered.

ge$scott@workflow.us.oracle.com>select table_name, monitoring from dba_tables
where owner='SCOTT' ORDER BY 2
  2  ;

TABLE_NAME                     MON
============================== ===
A                              NO
B                              NO
BIG                            NO
BONUS                          NO
PLAN_TABLE                     NO
SALGRADE                       NO
CHAINED_ROWS                   NO
DEPT                           NO
EMP                            YES

9 rows selected.

ge$scott@workflow.us.oracle.com>INSERT INTO EMP SELECT * FROM EMP;

28 rows created.

ge$scott@workflow.us.oracle.com>COMMIT;

Commit complete.

ge$scott@workflow.us.oracle.com>SELECT * FROM SYS.DBA_TAB_MODIFICATIONS;

no rows selected

i was expecting a record saying 28 inserts. please clarify

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1154434873552#8727886618633
you need to

o wait 3 hours or so with 8i
o use dbms_stats to flush modifications at will in 9i
o shutdown/startup cleanly in any release

      Rating: 5
Thanks Tom ...
        April 17, 2003
Reviewer:  Reader

April 17, 2003 Debbie -- Thanks for the question regarding
      "MINUS JOIN", version version 8i

      You Asked
How do you use a MINUS when joining tables? What is the purpose
of the MINUS.
Thank you.

      and we said...
MINUS works across queries -- like UNION does.

You can:

scott@ORA8I.WORLD> select ename, dname
  2  from emp, dept
  3  where emp.deptno = dept.deptno
  4  minus
  5  select ename, dname from emp, dept where
  6  emp.deptno = dept.deptno;

no rows selected

for example -- you can MINUS some joins...

MINUS simply takes away from the first query everything that is
in the second query.

Review &
      Followup

      Rating: 5
Substituting
      rows  April 17, 2003
Reviewer:  Sri
       from NJ
Dear Tom,

  I tried to do the following with MINUS, but I am not getting the result as I
needed.  So currently I am doing this in pl/sql.  Please go through and tell me
whether we can achieve the result with an sql statement and how to do it.

Create table CustomerMaster
(Cust_Code Varchar2(4) Primary Key,
 Cust_Name Varchar2(10)
)
/

create table ItemParameters
(Item_Code Varchar2(4),
 Lot_No Varchar2(4),
 Par Varchar2(10),
 Value Number(10),
 Primary Key (Item_Code, Lot_No, Par)
)
/

create table CustItemParameters
(Cust_Code Varchar2(4) References CustomerMaster(Cust_Code),
 Item_Code Varchar2(4),
 Par Varchar2(10),
 Value Number(10),
 Primary Key (Cust_Code, Item_Code, Par)
)
/

insert into customermaster values ('C1', 'Customer1')
/
insert into customermaster values ('C2', 'Customer2')
/
insert into customermaster values ('C3', 'Customer3')
/
insert into itemparameters Values ('KB', '01', 'Tax1', 10)
/
insert into itemparameters Values ('KB', '01', 'Freight', 5)
/
insert into itemparameters Values ('KB', '01', 'Discount', 2)
/
insert into itemparameters Values ('KB', '02', 'Tax1', 11)
/
insert into itemparameters Values ('KB', '02', 'Freight', 4)
/
insert into itemparameters Values ('KB', '02', 'Discount', 2)
/
insert into custitemparameters values ('C1', 'KB', 'Discount', 4)
/
insert into custitemparameters values ('C2', 'KB', 'Freight', 2)
/
insert into custitemparameters values ('C2', 'KB', 'Discount', 4)
/

My query condition would be:

Where Item_Code = 'KB' And Lot_No = '01' And Cust_Code = :CustCode

1. If CustCode = 'C1', output should be

ITEM LOT_ PAR             VALUE
---- ---- ---------- ----------
KB   01   Tax1               10
KB   01   Freight             5
KB   01   Discount            4 <= Taken from CustItemParameters

2. If CustCode = 'C2', output should be

ITEM LOT_ PAR             VALUE
---- ---- ---------- ----------
KB   01   Tax1               10
KB   01   Freight             2 <= Taken from CustItemParameters
KB   01   Discount            4 <= Taken from CustItemParameters

3. If CustCode = 'C3', output should be

ITEM LOT_ PAR             VALUE
---- ---- ---------- ----------
KB   01   Tax1               10
KB   01   Freight             5
KB   01   Discount            2

4. If CustCode = Null, output should be

ITEM LOT_ PAR             VALUE
---- ---- ---------- ----------
KB   01   Tax1               10
KB   01   Freight             5
KB   01   Discount            2

  Thank you for your help and support.

          Followup:

3 and 4 make no sense to me and 1 and 2 look like "really bad idea"

well, even #1 makes no sense -- wait, neither does #2.

not sure what you are trying to do but it Looks like "join to t1 or maybe t2 or
wait, maybe t3 or -- no, lets make up something"

I'd be looking at my data model and asking "how did I get myself in this
situation"

      Rating: 2
This can be
      done using SQL  April 17, 2003
Reviewer:
      Santosh Kumar  from NJ
Hi Tom,
    I found this site very recently and found very interesting.  Kudos to you
for great and sometimes funny answers.

Following query can be used to achive the result desired by Sri from NJ

select item_code, lot_no, par, value  from itemparameters
where item_code = 'KB' and lot_no = '01'
and (par) not in (select par from custitemparameters where cust_code = '&cust')
union
select item_code, '01', par, value
from custitemparameters where cust_code = '&cust'

April 17, 2003 Dave -- Thanks for the question regarding
      "security and cookies", version 9i

      You Asked
I'm working on a web application package in pl/sql that needs to have security
invoked. I have created a simple html login screen that validates a username and
password against encrypted values in a table using the dbms_obfuscation toolkit,
and it seems to work very well.

Although, I need to somehow carryover and validate this user and password for
each page to prevent unauthorized users from accesing incorrect pages, possibly
in a cookie. Can you recommend or share any ideas or code that shows how to do
this without having to invoke any Java, but straight within pl/sql?

Or..I would be open to other ideas of how to make this work out best.

I've attached below 2 pieces of the code I am using to login and validate.

Thanks in advance,
Dave
--------------------------------------------------

PROCEDURE LOGIN
IS
ampersand VARCHAR2(1) := CHR(38);
BEGIN

htp.print('<html><head>');
htp.print ('<link rel=stylesheet type="text/css"
href="'||servername||''||cssfile||'"></head>');
htp.print('</head><title></title>');
htp.p('<body>');
htp.print ('<table bgcolor="#6e7b8b" colspan="2"align="center" width="30%"
border="1" cellpadding="4" cellspacing="2">');
htp.p('<tr><td class="bigtitlestart"><font
size="3"><b>LOGIN</b></font></td></tr>');
-- htp.p('</table>');

htp.print ('<form name="login" method="post" action=viewer.crypt>');
htp.print ('<table bgcolor="#6e7b8b" align="center" width="30%" border="1"
cellpadding="4" cellspacing="2">');
htp.print ('<tr><td class="subtitle" nowrap> <b>username:</b></td><td><input
type=text name=username size="15"></td></tr>');
htp.print ('<tr><td class="subtitle" nowrap> <b>password:</b></td><td><input
type=password name=password size="15"></td></tr>');
htp.print ('</table>');
htp.p('<table align="center" width="30%" border="1" cellpadding="4"
cellspacing="2"><tr align="center"><td>');
htp.print ('<img src="VIEWER.DELIVER_MEDIA?media=button' ||
ampersand || 'num=submit_login.jpg"
onclick="document.login.submit()"></td></tr>');
htp.p('</form>');
htp.print ('</body></html>');
END LOGIN;
-------------------------------------------------------
procedure crypt (username IN VARCHAR2, password IN VARCHAR2)
IS
input_string VARCHAR2(8);
key_string VARCHAR2(8);
decrypted_string VARCHAR2(2048);

BEGIN

SELECT password INTO input_string FROM dpg_users WHERE username=username;
key_string:=rpad(username, 8);
dbms_obfuscation_toolkit.DESDecrypt(input_string,key_string,decrypted_string =>
decrypted_string );

IF password = rtrim(decrypted_string) THEN
viewer.frontpage_iap;
ELSE
htp.print('You have entered an incorrect username or password - Please try
again');
viewer.login;
END IF;

END CRYPT;

      and we said...
Well, don't take this the wrong way -- but I think you are using encryption in
the wrong place

If your table falls into the right (eg; the wrong) hands, they have every
password.  You've convienently put the key right next to the encrypted data.

(side point -- how is this query working in real life?
SELECT password INTO input_string FROM dpg_users WHERE username=username;
                                                       ^^^^^^^^^^^^^^^^^^ !!!
that'll fetch every row where username IS NOT NULL)

I don't believe in storing encrypted passwords.  See:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:95412348059
for a better, more secure method.  There you need not ever store their
passwords, no one can steal them from you but you can still AUTHENTICATE them
securely.

Does encryption fit in?  Sure, right after you authenticate that "bob" is really
"bob", you would take bob's identity (his username) plus any other information
about bob you wanted to remember and encrypt THAT with a key you change from
time to time.  that is your cookie.  If they look at the cookie, which you'll
have encrypted by

o taking bob's identity as a string
o using utl_raw.cast_to_raw convert to raw
o used the des3 encryption to encrypt the raw
o assigned the RAW to a varchar2 -- to convert it to HEX

all they see are a bunch of 0-9A-F's.  When you get the cookie back, you

o assign the varchar2 to a RAW
o decrypt using that key you change from time to time
o use utl_raw.cast_to_varchar2

and you know who they are. To make it even "more safe" you will use SSL to
encrypt this entire conversation.  Else, someone snooping out there can see that
string of 0-9A-F's, steal it, set their cookie and you would let them in.  if
the conversation is encrypted -- they cannot do that.
Review &
      Followup

      Rating:
      4
  February 14, 2003
Reviewer:  Dave
       from NH
Point taken...now I see the what you mean, it makes much more sense to set it up
the way you have illustrated.

can you point provide an example on how to setup the actual cookie? and how this
cookie will be used by each procedure in the package? I am a bit confuesd on how
it is actually working?

          Followup:

If you are confused on how cookies would work - you need to search about on the
web a bit and read up on them.

the OWA_COOKIE routine is what you are looking for - it is documented and a
search on this site will show an example or two

      Rating:
      5
SSL  April 17, 2003
Reviewer:  Ray
       from Ottawa,On,CA
Using 9i
What do I have to do to encrypt the conversation?

Can you explain the whole Server / certificate thing.

I would like to have secure conversation with internet users using Mod-PlSQL
html app.

          Followup:

you would use SSL between client and browser (nothing to do with Oracle -- you
goto a CA (certificate authority) like verisign or whatever, get a certificate,
follow the apache install directions and use https).

If you want to encrypt the conversation between the mod_plsql module and the db
-- read up on the Advanced Security Option (aso)

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/network.817/index.htm

      Rating: 5
Great
      stuff!  December 19, 2003
Reviewer:  Andrew
      Sims  from Reading, UK
Thanks again Tom,

All the problems I come up agaist seem to have been dealt with in this site.

Book is excellent by the way.

April 17, 2003 gs -- Thanks for the question regarding
      "all_objects vs dba_objecs, describe", version 9.2.0.3

      You Asked
Dear Tom,

1, Where can I find the source of DESCRIBE (ex DESC mytab). Is it a function,
procedure or just a sql script?

2, Why is it different when I used all_objects vs dba_objects in a procedure?
The owner of the proc is a DBA. (I assume it has to do with the roles). isn't
all_objects and dba_objects almost same when run by a DBA user?

thanks.

      and we said...
1) it is an internal function in sqlplus.  there is a package DBMS_DESCRIBE that
you can use to achieve the same.

2) read the view text out and compare them.  ALL_OBJECTS has security builtin.
DBA_OBJECTS -- shows everything.

ops$tkyte@ORA920> select 'all', count(*) from all_objects union all
  2  select 'dba', count(*) from dba_objects;

'AL   COUNT(*)
--- ----------
all      31622
dba      32121

they return different answers in all environments -- but the results are more
pronounced in a procedure.  Consider:

ops$tkyte@ORA920> begin
  2    for x in ( select 'all' a , count(*) b from all_objects union all
  3               select 'dba', count(*) from dba_objects )
  4    loop
  5           dbms_output.put_line( x.a || ' ' || x.b );
  6    end loop;
  7  end;
  8  /
all 31623
dba 32122

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> create or replace procedure p
  2  as
  3  begin
  4    for x in ( select 'all' a , count(*) b from all_objects union all
  5               select 'dba', count(*) from dba_objects )
  6    loop
  7           dbms_output.put_line( x.a || ' ' || x.b );
  8    end loop;
  9  end;
 10  /

Procedure created.

ops$tkyte@ORA920> exec p
all 29564
dba 32122

PL/SQL procedure successfully completed.

all_objects appears to have lots less rows in a procedure without roles -- but
you can see this in plus as well;

ops$tkyte@ORA920> set role none
  2  /

Role set.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select 'all' a , count(*) b from all_objects union all
  2  select 'dba', count(*) from dba_objects
  3  /

A            B
--- ----------
all      29564
dba      32122

Review &
      Followup

      Rating: 4
Thanks
      Tom.  April 17, 2003
Reviewer:  gs

      Rating:
      5
  April 18, 2003
Reviewer:
      A reader

April 18, 2003 -- Thanks for the question regarding "Can I
      change a range-partitioned table to a composite partitioned table?",
      version 8.1.7

      You Asked
I need to change a huge table of >60ml rows to a partitioned table. It has data
from 1998 to 2003, but data for 1998, 1999, 2000 are few so I want to combine
them with data of 2001 to become 1 partition. data for 2002 is the biggest, and
data for 2003 is obviously also going toward that direction. So ideally I would
want to subpartition 2002 and 2003 by hash. Here is what I did:

rename old_event to old_event_table;

CREATE TABLE old_event
    PCTFREE 10
    TABLESPACE dbridge_l
PARTITION BY RANGE (oe_ent_date)
    (PARTITION oe_all      VALUES LESS THAN (TO_DATE('01-Jan-2004',
'dd-Mon-yyyy')),
     PARTITION oe_since2004  VALUES LESS THAN (MAXVALUE))
AS (SELECT * FROM OLD_EVENT_TABLE WHERE 1 = 0);

ALTER TABLE old_event
EXCHANGE PARTITION oe_all
WITH TABLE old_event_table
WITHOUT VALIDATION;

ALTER TABLE old_event
    SPLIT PARTITION oe_all AT (TO_DATE('01-Jan-2002', 'dd-Mon-yyyy')) INTO
    (PARTITION oe_before2002, PARTITION oe_20022003);

ALTER TABLE old_event
    SPLIT PARTITION oe_20022003 AT (TO_DATE('01-Jan-2003', 'dd-Mon-yyyy')) INTO
    (PARTITION oe_2002, PARTITION oe_2003);

So by now the partition level is achieved. Can I create subpartition at this
stage, or this has to be done at create table stage, namely, the create table
statement should be:

CREATE TABLE old_event_file
    PCTFREE 10
    TABLESPACE dbridge_l
PARTITION BY RANGE (oe_ent_date)
--------------------------------------------
SUBPARTITION BY HASH (MODE) SUBPARTITIONS 8
--------------------------------------------
    (PARTITION oe_all      VALUES LESS THAN (TO_DATE('01-Jan-2004',
'dd-Mon-yyyy')),
     PARTITION oe_since2004  VALUES LESS THAN (MAXVALUE))
AS (SELECT * FROM OLD_EVENT_TABLE WHERE 1 = 0);

If I do this, what would happen when I later split each partition? What about if
I don't want subpartition in some of the smaller partitions? So basically, seems
we can't change a range-partitioned table into a composite one?

Also during the splitting, I noticed it required about the same number of
temporary segments with the original number of segments so in this case it is
big space requirement, it also generates lots of redo. Do you think this is the
best way of changing a big non-partitioned table into a partitioned one?

Thanks so much for your help, as always!

      and we said...
subpartitioning is something you need to do at table create time.  We can
achieve your goals, just not the way you tried.  Consider, we'll start with a
table with a single (low) partition and a single subpartition -- we need to
START with a composite partitioned table:

ops$tkyte@ORA920> create table new_table
  2  (OWNER                 VARCHAR2(30),
  3   OBJECT_NAME           VARCHAR2(30),
  4   SUBOBJECT_NAME        VARCHAR2(30),
  5   OBJECT_ID             NUMBER,
  6   DATA_OBJECT_ID        NUMBER,
  7   OBJECT_TYPE           VARCHAR2(18),
  8   CREATED               DATE,
  9   LAST_DDL_TIME         DATE,
 10   TIMESTAMP             VARCHAR2(19),
 11   STATUS                VARCHAR2(7),
 12   TEMPORARY             VARCHAR2(1),
 13   GENERATED             VARCHAR2(1),
 14   SECONDARY             VARCHAR2(1)
 15  )
 16  partition by range ( CREATED )
 17  subpartition by hash(object_id) subpartitions 1
 18  ( partition part1 values less than ( to_date( '01-jan-2003', 'dd-mon-yyyy'
) )
 19  )
 20  /

Table created.

Now, we can easily make it so additional partitions all have 8
subpartitions:

ops$tkyte@ORA920> alter table new_table set subpartition template 8;

Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table new_table add partition part2 values less than (
to_date('01-jan-2004', 'dd-mon-yyyy' ) );

Table altered.

ops$tkyte@ORA920> alter table new_table add partition part3 values less than (
to_date('01-jan-2005', 'dd-mon-yyyy' ) );

Table altered.

and if you decide part3 should have even MORE then part2, just add more
manually

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table new_table modify partition part3 add subpartition;

Table altered.

ops$tkyte@ORA920> alter table new_table modify partition part3 add subpartition;

Table altered.

ops$tkyte@ORA920> alter table new_table modify partition part3 add subpartition;

Table altered.

ops$tkyte@ORA920> alter table new_table modify partition part3 add subpartition;

Table altered.

ops$tkyte@ORA920> alter table new_table modify partition part3 add subpartition;

Table altered.

ops$tkyte@ORA920> alter table new_table modify partition part3 add subpartition;

Table altered.

ops$tkyte@ORA920> alter table new_table modify partition part3 add subpartition;

Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select partition_name, subpartition_count
  2    from user_tab_partitions
  3   where table_name = 'NEW_TABLE';

PARTITION_NAME                 SUBPARTITION_COUNT
------------------------------ ------------------
PART1                                           1
PART2                                           8
PART3                                          15

then, bulk load the table this way:

ops$tkyte@ORA920> alter table new_table NOLOGGING;
Table altered.

ops$tkyte@ORA920> set autotrace on statistics;
ops$tkyte@ORA920> insert /*+ append */ into new_table select * from all_objects;

31622 rows created.

Statistics
----------------------------------------------------------
       1248  recursive calls
        870  db block gets
     106096  consistent gets
         57  physical reads
      64692  redo size
        775  bytes sent via SQL*Net to client
        830  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
         13  sorts (memory)
          0  sorts (disk)
      31622  rows processed

ops$tkyte@ORA920> set autotrace off
ops$tkyte@ORA920> alter table new_table logging;
Table altered.

backup and you are done.

Review &
      Followup

      Rating: 5
Brilliant -
      rating is 6 stars  April 18, 2003
Reviewer:
      Marc  from Switzerland
Never seen set subpartition template 8 before.

Thanks Tom

      Rating: 5
add
      partitions  April 18, 2003
Reviewer:
      lakshmi  from bangalore india
excellent !!!!

      Rating:
      4
Great!  April 18, 2003
Reviewer:
      A reader  from MD
It also corrected a misconception I brought back from a training class: that you
always have to have >1 partition in a table or >1 subpartion in a partition. Of
course I didn't try to break that myself! I think conceptually it doesn't make
much sense (to have just 1 part in a table), but it can be a starting point,
just as in this case it is used to set up a structure for adding more.

          Followup:

conceptually it can be very COOL to have a single partition table.

Suppose....

every month, you get new data.
you load it into stage
you scrub it
you want to replace existing data in database with it.

you could:

rename old + rename new to old -- but that is two statements, chance of failure
between the two and they are not "transactional"

drop old + rename new to old -- same problem.

recreate a synonym (viable)
recreate a view (viable)

alter table exchange partition with table -- viable as well, just swap the data
from the stage table with the existing data and be done with it.

      Rating: 4
alter table xxx
      set subpartition template xx  April 18, 2003

Reviewer:  A reader  from MD USA
Seems this is only available in 9i!

          Followup:

sorry about that -- but the adding of subpartions will in fact work -- just a
little more "manual"

alter table new_table set subpartition template 8
                          *
ERROR at line 1:
ORA-02000: missing UNUSED keyword

Table altered.

Table altered.

Table altered.

Table altered.

PARTITION_NAME                 SUBPARTITION_COUNT
------------------------------ ------------------
PART1                                           1
PART2                                           1
PART3                                           3

      Rating: 5
Variable number
      of subpartitions  April 18, 2003
Reviewer:
      A reader
So far, I was under the impression that all the partitions should have the same
number of sub-partitions.

Well, Tom proved I was wrong. Every time I open up asktom.oracle.com, I learn
something new.

Great forum

      Rating: 4
subpartitions
      1  June 24, 2003
Reviewer:  Ernani
       from Rio de Janeiro, BRAZIL
Now, I know that a partition can have only 1 subpartition!

April 18, 2003 A -- Thanks for the question regarding "Vacant
      Key Query", version 8.1.6

      You Asked
How can I find the Vacant Key in the column?
Say I have table Customer{Key,Name}
Values{1,A},{2,B},{4,C},{6,D}
Now in the above list I should get the 3 as the Vacant Key(min).
So, If a new customer comes I can assign that key.

How can we do in the SQL Query.. Not using PL/SQL.

Extending my Question,

I.Suppose I have customers with Keys(System generated)
     Key Name
     1   Jones
     2   Smith
     3   Walton
     4   Jane
     5   Coddington
     6   Stone

II.Now The Customers 3,5 are deleted. Now the New Data would be.

     Key Name
     1   Jones
     2   Smith

     4   Jane

     6   Stone

III. If any new customer comes. Then Instead of assigning 7, I want to assign a
number 3 to that customer. So, To identify the vacant key, I have written a Code
in java already.  But I need it to be done in SQL Query.

Actually why I need it because keep holding the old data which never need to be
used, degrades the performance  enhancing the bulk data. To do avoid such things
we need to fill the vacant positions. Besides this, the column Key will be
defined to some Intergral value say
Key Number(8).   So, If keep on adding and deleting with out the technique of
Vacant key generation, increments the Key value and excceds the limit at some
point.

That is the main reason why I need the Vacant Key.

Hope u can understand my explanation.

      and we said...
That would be a truly terrible requirement in a relational database -- it would
really really be a TERRIBLE idea.

I hesitate to tell you how as it would be such a performance drain (eg: you must
LOCK the table in exclusive mode to generate a key -- only one person at a time,
also it is computationally expensive).

Gap free sequences just aren't a good idea.  Why do you want to assign the
number 3 to someone?

I don't see what " why I need it because keep holding the old data which never
need to be used, degrades the performance  enhancing the bulk data." means at
all.

You also realize that you would have to generate 114 new customers every hour of
every day for one hundred years in order to exceed your maximum....

Anyway -- It is a really BAD idea but here you go:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

         X
----------
         1
         2
         4
         6
         7
         8
         9
        10

8 rows selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select x+1
  2    from t
  3   where NOT EXISTS ( select null
  4                        from t t2
  5                       where t2.x = t.x+1 )
  6  /

       X+1
----------
         3
         5
        11

ops$tkyte@ORA817DEV.US.ORACLE.COM> select x+1
  2    from t
  3   where NOT EXISTS ( select null
  4                        from t t2
  5                       where t2.x = t.x+1 )
  6     and rownum = 1
  7     for update
  8  /

       X+1
----------
         3

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

*bad idea* all together for sure though.
Review &
      Followup

      Rating: 4
Another
      approach  April 01, 2002
Reviewer:  Dave
       from Ohio
Since one of the virtues of a good programmer is laziness, I ripped off some of
Tom's own code to produce a little routine which provides you with a list of
numbers ...

create type tab_Integer as table of Number(30);
/

create or replace function FromToIncr_2_Table
   (
   p_start in number,
   p_end in number,
   p_incr in number
   )
return tab_Integer
as
   l_element number;
   l_table   tab_Integer := tab_Integer();
begin
   l_element := p_start;
   loop
      exit when l_element > p_end;
      l_table.extend;
      l_table(l_table.count) := l_element;
      l_element := l_element+p_incr;
   end loop;
   return l_table;
end;
/

select * from table(cast(FromToIncr_2_Table(1,7,1) as tab_Integer));

COLUMN_VALUE
------------
           1
           2
           3
           4
           5
           6
           7

7 rows selected.

create table customer (key number,name varchar2(32));
insert into customer values (1,'Smith');
insert into customer values (3,'Jones');
insert into customer values (5,'Willy Wonka');

select * from customer;

       KEY NAME
---------- --------------------------------
         1 Smith
         3 Jones
         5 Willy Wonka

select t1.key
from customer c,
   (select column_value key
   from table(cast(FromToIncr_2_Table(1,5,1)  as tab_Integer))) t1
   where t1.key = c.key(+)
   and c.key is null
/

       KEY
----------
         2
         4

Hey, it's not perfect, and you'd have to do something about working out the
upper and lower limits (it could be done in the function itself) but it's an
approach that might be flexible enough to extend to other problem types.
Although it uses PL/SQL it does sort-of hide the fact.

Note the clever lack of any error handling in the function as well. You might
like to do something about that to avoid infinite result sets etc.

Oh, and thanks for the code Tom.

      Rating: 3
Dave's approach
      better...  April 01, 2002
Reviewer:  malcolm
       from new zealand
...because it will work for gaps in the key greater than one.

i.e.

SQL> select * from test;

         X
----------
         1
         2
         4
         5
         8

SQL> select x+1
  2   from test t
  3  where NOT EXISTS ( select null
  4                       from test t2
  5                      where t2.x = t.x+1 )
  6
SQL>
SQL> /

       X+1
----------
         3
         6
         9

No 7!

          Followup:

so -- you get a key at a time don't you.  If you get 6, you'll use 6 and the
very next time you ask -- you'll get 7.

Don't over complicate I say -- the query is what you want, the procedural code
-- while interesting -- is overdone (IMO).

Well, anyway, this whole discussion is overdone.  I'm pretty sure everyone can
tell my opinion on this "gap free" sequence concept.

(one of the constraints given also was "thou shalt not use plsql")...

      Rating:
      3
  April 01, 2002
Reviewer:  Dave
      again  from Still in Ohio.
I agree that a gapless sequence is a purposeless thing as far as relational
databases are concerned, but searching for gaps in sequences would be a routine
run by auditors to find missing invoice numbers etc., so there is often a
functional need for these.

The restriction on not using PL/SQL is pretty common when you find yourself
lumbered with a COTS reporting package, for example, but the prohibition is
generally based just on the need for a SQL method of accessing the data. I think
it's important to expose people to a technique like this just in case is drops
right into place for their purposes. Of course if it's just a foundless
prejudice against PL/SQL then "suck it up" buddy, and get coding.

Anyway, it seems like no schema is complete without a handy IOT of integers from
1 to a few million just for doing handy outer-joins in cases like this.

          Followup:

For auditors, I would just do this if they really really made me:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select rownum from all_users
where rownum <= 20;

20 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from t where x in ( 3, 4, 5, 15, 16 );

5 rows deleted.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select 'missing ' || (x+1) || '..' ||
(next_x-1)
  2    from ( select x, lead(x) over ( order by x ) next_x
  3             from t
  4             )
  5   where x+1 <> next_x
  6  /

'MISSING'||(X+1)||'..'||(NEXT_X-1)
---------------------------------------------------------------------------------
---------
missing 3..5
missing 15..16

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

Agreed, I keep a dummy table around with lots of numbers in it -- but tend to
use all_objects for most cases.

      Rating:
      5
  April 02, 2002
Reviewer:  Dave
       from Ohio
That's a very nice solution for a readable report. Imight just add an ORDER BY x
at the end.

      Rating: 4
Gap
      free  April 02, 2002
Reviewer:
      A reader
Hi,

Suppose those idoit "boss" or system want a gap free
invoice number, what is the solution beside keeping
a "unique" number table? Or what we should told
that "boss" without quit the job?

I know you can do anything you want in ORACLE.

Thanks

          Followup:

You can do this "gap free" thing -- it'll have these side effects:

o your system will serialize.  one person at a time.

o you will end up with gaps if you ever delete and will have to use ugly queries
or logic like the above to achieve your goal

Me -- I question the business logic behind the supposed "requirement".  But then
again, I question lots of stuff.

      Rating: 3
will this
      help  April 05, 2002
Reviewer:  Venkatesa
      Prasad. N  from CHennai, India
Hi Tom,
   To get a generalised query for missing numbers, we use the following query.

SELECT a.field + (b.val - 1)
FROM   (SELECT MIN (inv_no) inv_no
    FROM   tab
    WHERE  <condition>
    ) a,
    (SELECT ROWNUM val
     FROM   tab
     WHERE ROWNUM <= (SELECT MAX (field) - MIN (field) + 1
              FROM   tab
              WHERE  <condition>
)
    ) b
MINUS
SELECT  field
FROM    tab
WHERE   <condition>

      Rating: 5
update the
      missing numbers  April 18, 2003
Reviewer:
      Tanmoy Datta Choudhury  from India
now here goes another query..though the topic mention in this question finding
the missing number is unnecessary its a puzzle and a really really good one
....and thanks TOM once again for a great answer ...

i have another with me trying to crack it ..here it goes ..

table t having two column a and b and their values r like this ...

rows  a    b

 1     2
 2     3    3
 3          4
 4
 5     2    9
rows just to mention the row numbers in table t...
so we see 4 number rows having NULL for both a and b ...

now the requirment is to update the NULL columns of a and b and replace with
some number say 1 in a update statement without writting a pl/sql or replace
func ...possible????

tom any suggesstion ??

          Followup:

update t set a = decode( a, null, 1, a ), b = decode( b, null, 1, b );

      Rating: 5
update the
      missing numbers  April 18, 2003
Reviewer:
      Tanmoy Datta Choudhury  from India
now here goes another query..though the topic mention in this question finding
the missing number is unnecessary its a puzzle and a really really good one
....and thanks TOM once again for a great answer ...

i have another with me trying to crack it ..here it goes ..

table t having two column a and b and their values r like this ...

rows  a    b

 1     2
 2     3    3
 3          4
 4
 5     2    9
rows just to mention the row numbers in table t...
so we see 4 number rows having NULL for both a and b ...

now the requirment is to update the NULL columns of a and b and replace with
some number say 1 in a update statement without writting a pl/sql or replace
func ...possible????

tom any suggesstion ??

      Rating: 5
How about NVL
      instead of DECODE  April 18, 2003
Reviewer:
      A reader
Tom,

How about

update t set a = nvl(a, 1), b = nvl(b,1);

instead of

update t set a = decode( a, null, 1, a ), b = decode( b, null, 1, b );

Is there any reason you used DECODE instead of NVL?

          Followup:

sure, or case or <probably infinite number of ways> .....

don't know why but I rarely use NVL(), I think I find decode more "expressive"

      Rating: 5
missing
      mumbers  May 19, 2003
Reviewer:  bhavin

the reply was great. it solved my problem. thanks a lot.

April 19, 2003 Alison -- Thanks for the question regarding
      "IMP-00020: long column too large for column buffer size", version Oracle7
      Release 7.3.4

      You Asked
Hi There

I am having a problem when I'm trying to perform an import of a
successfully exported database.

I am getting the following error message in the import logs.

. . importing table                   "PSPNLFIELD"
IMP-00020: long column too large for column buffer size (29214)
IMP-00028: partial import of previous table rolled back: 98896
rows rolled back

I have increased the buffer size in increments of 1000 but still
get the same error message.

When I try the statement below I get the following error.
SQL> SELECT * FROM PSPNLFIELD;
ERROR:
ORA-00932: inconsistent datatypes.

I have attempted to export and import just this one table,
however I still get the same results.

Many thanks

Alison.

      and we said...
You should set the buffer size to some number larger then any of
the LONG RAWs you have in that file.

I typically use

buffer=10000000

(10million) or so.  If you long raws are bigger then 10m, adjust
acorrdingly.

The inconsistent datatypes is expected -- long raw columns are
not selectable in sqlplus.
Review & Followup

      Rating: 2
"long column
      too large..." but no long column  April 18, 2003

Reviewer:  Sri  from NJ
Dear Tom,

  I am getting the same error (as following) when I tried to import.  I am using
Oracle 9.0.1.1, I exported the data from the same server itself.  But when I am
trying to import it, it is giving the error.

IMP-00020: long column too large for column buffer size (10)

  As you had adviced to the original post, I tried increasing buffers while
importing, I gave values up to 10,000,000,000 but no use.  Still exactly the
same error with a 10 in brackets.  I have done the export from using Direct=Y.
The structure of the table for which the error is coming is as follows: -

Create Table CustomerMaster
(
 Code Varchar2(10)
    Constraint pk_CustM_Code Primary Key,
 CustomerID Varchar2(10)
    Constraint uq_CustM_CustID Unique
    Constraint nn_CustM_CustID Not Null,
 Customer_Name Varchar2(100)
    Constraint uq_CustM_CustName Unique
    Constraint nn_CustM_CustName Not Null,
 Alias_Name Varchar2(100)
    Constraint nn_CustM_AliasName Not Null,
 Record_Info Varchar2(100),
 Other_Details Varchar2(300),
 CustomerType_Code Varchar2(10)
    Constraint fk_CustM_CTypeCode References CustomerTypeMaster(Code),
 Address1 Varchar2(100),
 Address2 Varchar2(100),
 Location_Code Varchar2(10)
    Constraint fk_CustM_LocCode References LocationMaster(Code),
 State_Code Varchar2(10)
    Constraint fk_CustM_StaCode References StateMaster(Code),
 ZipCode Varchar2(50),
 Country_Code Varchar2(10)
    Constraint fk_CustM_CountryCode References CountryMaster(Code),
 Email_ID Varchar2(100),
 Phone_No Varchar2(30),
 Fax_No Varchar2(50),
 Referred_Emp Varchar2(10)
    Constraint fk_CustM_RefEmp References Employee(Code),
 Customer_Since Date,
 Customer_Status Varchar2(2)
    Constraint nn_CustM_CustStatus Not Null,
 Present_Status Varchar2(2)
    Constraint nn_CustM_PStatus Not Null,
 Remarks Varchar2(100) Constraint nn_CustM_CStat Not Null
)
/

  As you can see there are not too many columns.  Also there are no column with
type Long etc.  Why is this error coming still?  How can I successfully import
from this file?  Kindly help.

          Followup:

lets see the output from imp -- the startup stuff that tells me the
charactersets and all...

      Rating: 5
Here is the
      output...  April 18, 2003
Reviewer:  Sri
       from NJ
  Thank you for the response Tom.  Here is the output you asked.  Kindly go
through and advice on how to import the data successfully.

C:\>imp

Import: Release 9.0.1.1.1 - Production on Fri Apr 18 18:30:15 2003

(c) Copyright 2001 Oracle Corporation.  All rights reserved.

Username: sri
Password:

Connected to: Oracle9i Enterprise Edition Release 9.0.1.1.1 - Production
With the Partitioning option
JServer Release 9.0.1.1.1 - Production

Import file: EXPDAT.DMP > C:\SriData.Dmp

Enter insert buffer size (minimum is 8192) 30720> 100000000

Export file created by EXPORT:V09.00.01 via direct path
import done in WE8MSWIN1252 character set and AL16UTF16 NCHAR character set
List contents of import file only (yes/no): no >

Ignore create error due to object existence (yes/no): no >

Import grants (yes/no): yes >

Import table data (yes/no): yes >

Import entire export file (yes/no): no >
Username: sri

Enter table(T) or partition(T:P) names. Null list means all tables for user
Enter table(T) or partition(T:P) name or . if done: CustomerMaster

Enter table(T) or partition(T:P) name or . if done: .

. importing SRI's objects into SRI
. . importing table               "CUSTOMERMASTER"
IMP-00020: long column too large for column buffer size (10)

          Followup:

a) was there a function based index on this table that was dropped?
b) you have opened a tar with support right?

      Rating: 5
there was a
      function based index...  April 18, 2003
Reviewer:
      Sri  from NJ
Dear Tom,

  Yes, there was a function based index for this table.    (No, I haven't opened
a tar.)

  The index is as follows: -

CREATE UNIQUE INDEX custmaster_uppercustname_uqidx ON
customermaster(UPPER(customer_name))
/

  Why?  Has it got anything to do with this Tom?  Now, what is the way out?

          Followup:

Article-ID:         <Note:148740.1>
Circulation:        PUBLISHED (EXTERNAL)
Folder:             server.Utilities.ExportImport
Topic:              Alerts
Title:              ALERT: Export of table with dropped functional index may
                    cause IMP-20 on import

Import may fail with IMP-20 if direct-path export taken after functional index
was dropped
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~

Versions Affected
~~~~~~~~~~~~~~~~~
  This problem:
        Does not affect Oracle 8.0 releases
        Affects Oracle release 8.1.5, 8.1.6, 8.1.7, 9.0.1
        The issue is fixed in the 8.1.7.3 Patch Set.

Platforms Affected
~~~~~~~~~~~~~~~~~~
  GENERIC - affects all platforms

Description
~~~~~~~~~~~
  Direct export may create a corrupt export file if a table is exported which
  had it's functional index dropped.
  Subsequent attempts to import the affected table result in errors:
    IMP-00020: long column too large for column buffer size
  Increasing the import buffer size has no effect.

Likelihood of Occurrence
~~~~~~~~~~~~~~~~~~~~~~~~
  In order to hit this problem an export file must have been created under
  the following circumstances:

    a. The database was started with compatible set to 8.1.x or higher

    b. A table had a functional index created on it

    c. The functional index was dropped

    d. The table did NOT have unused columns dropped after the index was dropped

    e. The table was exported in direct-path mode

What are the chances of hitting this problem ?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  Connect as a SYSDBA user (or SYS) and run this statement:

      set echo off
      select 'ALTER TABLE '||u.name||'.'||o.name||' DROP UNUSED COLUMNS;'
        from user$ u, obj$ o, col$ c
      where o.type#=2
        and c.col#=0
        and bitand(32768,c.property)=32768
        and o.obj#=c.obj#
        and u.user#=o.owner#
        and u.user#!=0;

  If any rows are returned, drop the unused columns from the affected
  tables using the 'ALTER TABLE....' SQL statements returned from the
  above dictionary query.

Example Sequence of events leading to a Corrupt Export file
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  The steps below illustrate a common sequence of events which lead to
  a corrupt export file:

    a. Create and populate a table
       CREATE TABLE W21
         ( W21PROGRAMM CHAR(8) DEFAULT (' ')  NOT null,
           W21BEREICH  CHAR(2) DEFAULT (' ')  NOT null,
           OGRAMID     CHAR(8) DEFAULT (' ')  NOT null);
       INSERT INTO W21 VALUES ('SIBG43',0,'STAU');
       COMMIT;

    b. Create the functional index, then drop it
       CREATE INDEX W2102 ON W21 (W21PROGRAMM DESC) ;
       DROP INDEX W2102;

       Note: The DESC keyword causes a functional index to be created in
       Oracle8i onwards unless the init.ora parameter _IGNORE_DESC_IN_INDEX
       is set to TRUE. The same problem occurs for a normal functional index.
       For example:

          CREATE INDEX W2102 ON W21 (UPPER(W21PROGRAMM));

    c. Export the table (direct-path)
       exp user/pw buffer=100000 direct=yes tables=w21

    d. Drop the table
       DROP TABLE W21;

    e. Attempt to import the table
       imp user/pw fromuser=username
       IMP-00020: long column too large for column buffer size (2)

Workaround
~~~~~~~~~~

    a. If a functional index is dropped, immediately drop any unused
       columns from the table on which the index was created, using the
       syntax:

         SQL> ALTER TABLE <TABNAME> DROP UNUSED COLUMNS;

       Note that in some cases this command may fail with an ORA-600 [711]
       error. In such cases it should be possible to work around this
       ORA-600 by adding a dummy column to the table then dropping it.
       eg:
         SQL> ALTER TABLE <TABNAME> ADD ( dummy_col number );
         SQL> ALTER TABLE <TABNAME> DROP COLUMN dummy_col;
         SQL> ALTER TABLE <TABNAME> DROP UNUSED COLUMNS;

    b. Use export in conventional mode, rather than direct-path

    Note, it is possible to import other objects from the same export dump
    file, regardless of their ordering within the file (i.e. whether they
    were created before or after the affected table).

References
~~~~~~~~~~
  Base bug for this alert                             <Bug:1805146>
  IMP-20 with DIRECT=Y issue caused by this bug       <Bug:1754922>

Modification History
~~~~~~~~~~~~~~~~~~~~
  23-Oct-2001 This alert was modified to add steps to take if the
              DROP UNUSED COLUMNS command errors with ORA-600 [711].
  21-Jan-2002 Added fixed in 8.1.7.3 information

      Rating: 5
Don't use
      9.0.1  April 19, 2003
Reviewer:
      A reader
You dare to use 9.0.1... hope it's not an important database.

          Followup:

huh?

      Rating:
      4
FOLLOWUP  April 19, 2003
Reviewer:  Dana
      Day  from Phoenix, AZ
Ran across this error in 8.1.7.2.
If you export direct path from a table that "had" and then dropped a function
based index, you will recieve the error.
The workaround is to export conventional path.

Hope this helps.

April 19, 2003 Juan Carlos -- Thanks for the question regarding
      "analytic functions", version 9.0.2

      You Asked
Hi Tom I has a table that has the operations in a day, the client only can do a
operation per day.

OPERATIONS

DAY    DATE
CLIENT NUMBER
AMOUNT NUMBER
data1  VARCHAR2
data2  VARCHAR2
data3  VARCHAR2
data4  VARCHAR2

DATE        CLIENT  AMOUNT
01-01-2002   1  1,000
01-02-2002   1  500
*01-03-2003   1  50

01-01-2002   2  500
*01-05-2002   2  1,000

01-04-2002   3  1,000
01-05-2002   3  500
*01-06-2002   3  1,000
01-07-2002   3  500

I need to do a query and get the last operation and all its data.
For example to get data in date 01-06-2002, this is I has to got the last
operation that is less or equal to 01-06-2002

01-03-2003   1  50
01-05-2002   2  1,000
01-06-2002   3  1,000

I need to do a query
select date,codcli, amount,data...
from OPERATIONS
WHERE DATE = ( select max(date) from ... where date <= 01-06-2002 )

I was trying to use analytic functions to do this, because this query in some
cases tales 3 minutes. Is this possible?

:) Thanks.

      and we said...
http://otn.oracle.com/oramag/oracle/02-sep/o52asktom.html
and read

PLAYING WITH AGGREGATION

basically:

select *
  from (
select t.*, max(date) over (partition by client) max_dt
  from t
 where date <= to_date( ... )
       )
 where date = max_dt

is one approach -- but i have another there as well.
Review &
      Followup

      Rating:
      5
  March 07, 2003
Reviewer:
      A reader
Thanks

      Rating: 5
Can analytics
      help me ???  April 16, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Islamabad, Paksitan
Hi Tom !

Consider:

cr@STARR.LHR> desc custom_Sb_master

 Name                    Null?    Type
 ----------------------- -------- ------------
 COLLECTORATE_CODE       NOT NULL VARCHAR2(4)
 SHIPPINGBILL_NO         NOT NULL NUMBER(7)
 SHIPPINGBILL_DATE       NOT NULL DATE

  1  select count(1),collectorate_code
  2  from custom_sb_master
  3  where to_Char(shippingbill_date,'mmyyyy')='032003'
  4* group by collectorate_code
cr@STARR.LHR> /

  COUNT(1) COLL
========== ====
      1826 FEXP
      3544 KAFE
        13 KEPZ
     10793 KEXP
      7028 KPQE
      2949 LAFE
       815 LEXP
        13 LTEX
       352 MEXP
      1208 RAFE
        16 REXP
      3616 SDRE

Now i also want the average no. of records w.r.t
collectorate_code,to_char(shippingbill_date,'mmyyyy'). Can analytic function
help me here.
I am using oracle 8.1.7.2.

thankx

          Followup:

count(*) -- count(*) -- count(*)

count(1) -- what is that, a count of 1's?  count(*) means "count records for me
please" (sorry, pet peeve of mine)

anyway

ops$tkyte@ORA920LAP> create table t as select substr(upper(object_name),1,1) x
from all_objects;

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select x, count(*), avg(count(*)) over ()
  2    from t
  3   group by x;

X   COUNT(*) AVG(COUNT(*))OVER()
- ---------- -------------------
/      13096          1174.76923
A        987          1174.76923
B         51          1174.76923
C        773          1174.76923
D       1901          1174.76923
E        552          1174.76923
F         31          1174.76923
G        747          1174.76923
H        304          1174.76923
I        311          1174.76923
J       2751          1174.76923
K        334          1174.76923
L        676          1174.76923
M        193          1174.76923
N         37          1174.76923
O       2229          1174.76923
P        146          1174.76923
Q         31          1174.76923
R        243          1174.76923
S       3243          1174.76923
T        123          1174.76923
U        586          1174.76923
V        564          1174.76923
W        379          1174.76923
X        228          1174.76923
_         28          1174.76923

26 rows selected.

      Rating: 1
My question is
      still a question  April 19, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Islamabad, Pakistan
Hello tom !

Thanks for your response (and sorry for using count(1) instead of
count(*)...actually i read in DBA Certification guide that count(1) is faster
than count(*). Later i did a test and found no diff. in using count(*) or
count(1), but it became my habbit in the meantime. Would try to not use count(1)
later).

Unfortunately, your answer is not what i was saying. I needed avg of count w.r.t
2 fields. For example, consider:

cr@STARR.LHR> create table test1(coll char(1), a char(4), b number);

Table created.

cr@STARR.LHR> insert into test1 values('A','012003',5);

1 row created.

cr@STARR.LHR> ed
Wrote file afiedt.buf

  1* insert into test1 values('A','022003',6)
cr@STARR.LHR> /

1 row created.

Elapsed: 00:00:00.31
cr@STARR.LHR> ed
Wrote file afiedt.buf

  1* insert into test1 values('A','022003',7)
cr@STARR.LHR> ed
Wrote file afiedt.buf

  1* insert into test1 values('A','032003',7)
cr@STARR.LHR> /

1 row created.

Elapsed: 00:00:00.31
cr@STARR.LHR> ed
Wrote file afiedt.buf

  1* insert into test1 values('A','022003',7)
cr@STARR.LHR> /

1 row created.

Elapsed: 00:00:00.31
cr@STARR.LHR> ed
Wrote file afiedt.buf

  1* insert into test1 values('A','042003',9)
cr@STARR.LHR> /

1 row created.

Elapsed: 00:00:00.31
cr@STARR.LHR> ed
Wrote file afiedt.buf

  1* insert into test1 values('B','012003',9)
cr@STARR.LHR>
cr@STARR.LHR> /

1 row created.

Elapsed: 00:00:00.31
cr@STARR.LHR> ed
Wrote file afiedt.buf

  1* insert into test1 values('B','022003',29)
cr@STARR.LHR> /

1 row created.

Elapsed: 00:00:00.31
cr@STARR.LHR> ed
Wrote file afiedt.buf

  1* insert into test1 values('B','032003',29)
cr@STARR.LHR> /

1 row created.

Elapsed: 00:00:00.31
cr@STARR.LHR> insert into test1 values('A','022003',10);

1 row created.

Elapsed: 00:00:00.47
cr@STARR.LHR> /

1 row created.

Elapsed: 00:00:00.31
cr@STARR.LHR> commit;

Commit complete.
cr@STARR.LHR> select * from test1;

C A               B
= ====== ==========
A 012003          5
A 022003          6
A 032003          7
A 022003          7
A 042003          9
B 012003          9
B 022003         29
B 032003         29
A 022003         10
A 022003         10

10 rows selected.

Elapsed: 00:00:00.04
cr@STARR.LHR> select coll,a,count(*) from test1
  2  group by coll,a;

C A        COUNT(*)
= ====== ==========
A 012003          1
A 022003          4
A 032003          1
A 042003          1
B 012003          1
B 022003          1
B 032003          1

7 rows selected.

Elapsed: 00:00:00.41

Look...there are seven (1+4+1+1)records(counts) for A  and 3 for B.  I want
output like :

C A        COUNT(*)            AVG
= ====== ==========      ==========
A 012003          1            1.75 ==> (1+4+1+1)/4
A 022003          4            1.75
....
....
B 012003           1               1 ==> (1+1+1)/3

rEGARDS

          Followup:

it would be so cool -- if your examples worked out of the box ;)  how you put
032003 into a CHAR(4) is beyond me (cutting, pasting, right from sqlplus.... it
works)

anyway, the solution is pretty simple.  All you need to do is understand
analytic functions and you can answer this yourself simply.  All you need to do
is partion by the columns you need to then.

ops$tkyte@ORA920> select coll, a, count(*), avg(count(*)) over (partition by
coll)
  2    from test1
  3   group by coll, a
  4  /

C A        COUNT(*) AVG(COUNT(*))OVER(PARTITIONBYCOLL)
- ------ ---------- ----------------------------------
A 012003          1                               1.75
A 022003          4                               1.75
A 032003          1                               1.75
A 042003          1                               1.75
B 012003          1                                  1
B 022003          1                                  1
B 032003          1                                  1

7 rows selected.

      Rating: 5
Your Solution
      worked  April 19, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Islamabad, Pakistan
Hello Tom !

Your solution was great. Sorry for "putting 012003 in char(4)" :( . Altered
table but didn't copied the code here.

Your solution worked but here is a problem (sorry for "creating problems" for
you):

cr@STARR.LHR> select collectorate_code,to_char(shippingbill_date,'mmyyyy'),
count(*), avg(count(*)) over (partition by collectorate_code) "Average" from
custom_sb_master
group by  collectorate_code,to_char(shippingbill_date,'mmyyyy')
/

COLL TO_CHA   COUNT(*)    Average
==== ====== ========== ==========
FEXP 012003       1462     1620.5
FEXP 022003       1528     1620.5
FEXP 032003       1826     1620.5
FEXP 062002       1352     1620.5
FEXP 072002       1686     1620.5
FEXP 082002       1804     1620.5
FEXP 092002       1668     1620.5
FEXP 102002       1711     1620.5
FEXP 112002       1602     1620.5
FEXP 122002       1566     1620.5
KAFE 012001       5303 5090.54545
KAFE 012002       3965 5090.54545
KAFE 012003       5009 5090.54545
KAFE 022001       5442 5090.54545
KAFE 022002       3639 5090.54545

This is fine. But when i add a where clause to the query, it just gives me the
average of counts w.r.t those records while it should give me the averegae on
the whole table....no matter what i specify in where clause.

cr@STARR.LHR>  select collectorate_code,to_char(shippingbill_date,'mmyyyy'),
count(*), avg(count(*)) over (partition by collectorate_code) "Average" from
custom_sb_master
where to_char(shippingbill_date,'mmyyyy')='032003'
group by  collectorate_code,to_char(shippingbill_date,'mmyyyy')
/

COLL TO_CHA   COUNT(*)    Average
==== ====== ========== ==========
FEXP 032003       1826       1826
KAFE 032003       3544       3544
KEPZ 032003         13         13
KEXP 032003      10793      10793

Regards

          Followup:

select *
from (
select collectorate_code,
       to_char(shippingbill_date,'mmyyyy') sb,
       count(*),
       avg(count(*)) over (partition by collectorate_code) "Average" from
custom_sb_master
group by  collectorate_code,to_char(shippingbill_date,'mmyyyy')
)
where sb = '032003'
/

      Rating:
      5
Thanks  April 19, 2003
Reviewer:  riaz
      shahid  from PRAL, Islamabad, Pakistan
Tom !

Your solution worked.

Many many thanks

Riaz

April 19, 2003 Ashwin -- Thanks for the question regarding
      "UTL_FILE advantages ", version 8.1.7.4

      You Asked
Hi Tom,

Would you be able to provide some explanation on the reasons and advantages of
using UTL_FILE over any other tool available to create text output files?

Thanks in advance
Ashwin

      and we said...
say you are coding in PLSQL.

Well, then being able to create a text file from plsql would be very nice. So,
if your language is plsql, it is definitely an advantage.

Say you need to create a file on the SERVER -- from a client (that is not the
same machine as the server).  Well, UTL_FILE does that nicely.

Say you need to create the file every night at midnight.  DBMS_JOB to run a
stored procedure is very nice.

Say you want to create the file from a trigger....

It all depends on your needs and your environment.  If I need to create a
relatively small (couple of meg) file -- plsql is great.  If I want to unload
massive amounts of data -- PRO*C is perfect.  If I want to unload to a client --
sqlplus.  See

http://asktom.oracle.com/~tkyte/flat/index.html

April 20, 2003 Chacravarthi -- Thanks for the question regarding
      "Specific about oracle", version 8.0

      You Asked
Dear sir,
Well and wish the same from you.I have some questions for you.
1)What is the maximum number of users that an oracle database can
support?Does it differ on the type of editions(standard,Personal or
Enterprise) we use or the choice of an OS?
2)Throw a light on sys_refcursor?
Thanks.
Yours sincerely,
R.Chacravarthi.

      and we said...
1) 2 billion named accounts, number of concurrent users limited only by hardware
and your software design.  We've had hundreds of thousands of concurrent users
connected (see www.tpc.org for benchmarks)

2) it is just a builtin datatype -- so you don't have to code

type my_refcursor is ref cursor;

you can just use sys_refcursor, like you use number, date, varchar2
Review &
      Followup

      Rating:
      3
Nice  April 21, 2003
Reviewer:
      R.Chacravarthi  from Chennai,India
Thanks for your response

April 20, 2003 Andre -- Thanks for the question regarding
      "Reading deadlock trace files", version 8.1.5

      You Asked
Hi Tom !

How can I understand which two rows where involved in the following deadlock,
which appears in this excerpt of a tracefile ?

Especially in the last two lines, Oracle seems to convey this information, but I
was not able to locate the object or the rowids.

Would please elaborate on the whole output if possible ?

And how can prevent or at least avoid this situation ?

Thanks a lot !

DEADLOCK DETECTED
Current SQL statement for this session:
UPDATE RA_INTERFACE_LINES_ALL SET
LINE_GDF_ATTRIBUTE1=:b1,LINE_GDF_ATTRIBUTE2=DECODE(:b2, NULL ,DECODE(:b3, NULL
,:b4,:b3),LINE_GDF_ATTRIBUTE2),LINE_GDF_ATTRIBUTE3=DECODE(:b6, NULL
,:b7,:b6),LINE_GDF_ATTRIBUTE4=DECODE(:b9, NULL ,DECODE(:b10, NULL
,:b11,:b10),LINE_GDF_ATTRIBUTE4),LINE_GDF_ATTRIBUTE5=DECODE(:b13, NULL
,DECODE(:b14, NULL
,:b15,:b14),LINE_GDF_ATTRIBUTE5),LINE_GDF_ATTRIBUTE6=DECODE(:b17, NULL
,DECODE(:b18, NULL
,:b19,:b18),LINE_GDF_ATTRIBUTE6),LINE_GDF_ATTRIBUTE7=DECODE(:b21, NULL
,:b22,LINE_GDF_ATTRIBUTE7),HEADER_GDF_ATTR_CATEGORY=DECODE(:b23, NULL
,'JL.BR.ARXTWMAI.Additional
Info',HEADER_GDF_ATTR_CATEGORY),LINE_GDF_ATTR_CATEGORY=DECODE(:b24, NULL
,'JL.BR.ARXTWMAI.Additional Info',LINE_GDF_ATTR_CATEGORY) WHERE ROWID = :b25
----- PL/SQL Call Stack -----
  object      line  object
  handle    number  name
ab577a1c       125  anonymous block
The following deadlock is not an ORACLE error. It is a
deadlock due to user error in the design of an application
or from issuing incorrect ad-hoc SQL. The following
information may aid in determining the deadlock:
Deadlock graph:
                       ---------Blocker(s)--------  ---------Waiter(s)---------
Resource Name          process session holds waits  process session holds waits
TX-00080048-00023aa6        57     125     X             58      62           X
TX-0009002a-00024308        58      62     X             57     125           X
session 125: DID 0001-0039-00000002    session 62: DID 0001-003A-00000002
session 62: DID 0001-003A-00000002    session 125: DID 0001-0039-00000002
Rows waited on:
Session 62: obj - rowid = 00001435 - AAEUluADmAAAswlAAA
Session 125: obj - rowid = 00001435 - AAEUluACwAAAY83AAB

      and we said...
You use the dbms_rowid package to extract this info, for example:

ops$tkyte@ORA8I.WORLD> select rowid from dual;

ROWID
------------------
AAAADCAABAAAAHQAAA

ops$tkyte@ORA8I.WORLD> select dbms_rowid.rowid_object( 'AAAADCAABAAAAHQAAA' )
from dual;

DBMS_ROWID.ROWID_OBJECT('AAAADCAABAAAAHQAAA')
---------------------------------------------
                                          194

ops$tkyte@ORA8I.WORLD> select object_name from all_objects where object_id =
194;

OBJECT_NAME
------------------------------
DUAL

shows that given a rowid from dual, I can find the object number and hence the
object name.

There are various apis in the dbms_rowid pacakge to get all of the information
you need.

also:

ops$tkyte@ORA8I.WORLD> select to_number( '00001435', 'xxxxxxxxxxx' ) from dual;

TO_NUMBER('00001435','XXXXXXXXXXX')
-----------------------------------
                               5173

tells us this is object 5173 -- its in HEX.  You are updating more then one row
in RA_INTERFACE_LINES_ALL but must be getting them in a different order so one
session updated 1 row, another row 5 (for example) then the first session tried
to update row 5 (blocks) and the second session tried to update row 1 (deadlock)

Review &
      Followup

      Rating:
      4
Reader  October 02, 2001
Reviewer:
      Reader  from USA
The TX is structured <rbs><slot><wrap>
Could you tell us how DID is structured

Thanks

          Followup:

the middle number is the process number in hex.  The other two, i really don't
know what they are

      Rating: 5
DeadLock
      analysys  August 07, 2002
Reviewer:  atul
       from India
Sir,

In following case how to find out objects involved in deadlock..
as it's showing no rows...

Tue Aug  6 15:15:15 2002
*** SESSION ID:(21.5235) 2002.08.06.15.15.15.391
ksqded1:  deadlock detected via did
DEADLOCK DETECTED
Current SQL statement for this session:

UPDATE INV_MST SET PV_NO=:b1,PV_DT=SYSDATE,ACCT_YR_MTH=:b2,ACCT_DT=SYSDATE,PASS
DT=SYSDATE,INV_STATUS='03' WHERE BR_NO=:b3
The following deadlock is not an ORACLE error. It is a
deadlock due to user error in the design of an application
or from issuing incorrect ad-hoc SQL. The following
information may aid in determining the deadlock:
Deadlock graph:
                       ---------Blocker(s)--------  ---------Waiter(s)---------
Resource Name          process session holds waits  process session holds waits
TX-00100000-0000f243        15      21     X             12      19           S
TX-0012000b-0000ff4e        12      19     X             15      21           S
session 21: DID 0001-000F-00000001      session 19: DID 0001-000C-00000001
session 19: DID 0001-000C-00000001      session 21: DID 0001-000F-00000001
Rows waited on:
Session 19: no row
Session 21: no row

Thanks.
atul

          Followup:

I'm going to hypothesize that there is a unique index on some (at least one) of
the columns being updated.

The locks are NOT on a row -- they are due to unique conflicts.  Here is exactly
how to simulate this:

--------------------- test.sql -------------------------
drop table t;
create table t ( x int primary key );
insert into t values ( 1 );
insert into t values ( 2 );
insert into t values ( 3 );
insert into t values ( 4 );
commit;
update t set x = 5 where x = 1;
REM in another session, run test2.sql
pause
update t set x = 6 where x = 2;
---------------------------------------------------------

-------------------- test2.sql ---------------------------
variable x1 number
variable x2 number
exec :x1 := 6; :x2 := 3;
update t set x = :x1 where x = :x2;
exec :x1 := 5; :x2 := 4;
update t set x = :x1 where x = :x2;
---------------------------------------------------------

Here, session 1 will get the row updated from 1 to 5 -- 5 will be "locked" in
the index.

Session 2 will then update 3 to 6 (no conflict, but 6 is "locked" in the index)

Session 2 will then update 4 to 5 -- this'll be a conflict, unique key violation
POSSIBLE at this point.  Session 2 will block here.

Next, session 1 tries to update 2 to 6 -- that'll be another conflict with
session 2, unique key violation POSSIBLE at this point.  Session 1 will block
and then one of the sessions will get the dead lock.

That is when this trace file will be produced:

*** SESSION ID:(8.3883) 2002-08-07 11:09:23.816
DEADLOCK DETECTED
Current SQL statement for this session:
update t set x = :x1 where x = :x2
The following deadlock is not an ORACLE error. It is a
deadlock due to user error in the design of an application
or from issuing incorrect ad-hoc SQL. The following
information may aid in determining the deadlock:
Deadlock graph:
                       ---------Blocker(s)--------  ---------Waiter(s)---------
Resource Name          process session holds waits  process session holds waits
TX-0003003d-000011e2        14       8     X              8       7           S
TX-00020054-0000109e         8       7     X             14       8           S
session 8: DID 0001-000E-00000002   session 7: DID 0001-0008-00000002
session 7: DID 0001-0008-00000002   session 8: DID 0001-000E-00000002
Rows waited on:
Session 7: no row
Session 8: no row
===================================================

So, basically, you have two sessions doing this update (or a similar update) and
they are bumping into each other with a unique index.  Look for whats unique in
this table.

It isn't a row lock issue -- rather, an index key collision that is happening.

Further down in the trace file, you should be seeing something like:

...
Cursor frame allocation dump:
frm: -------- Comment --------  Size  Seg Off
 bind 0: dty=2 mxl=22(22) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=48
offset=0
   bfp=01a70280 bln=22 avl=02 flg=05
   value=5     <<<<<=== the bind variable values
 bind 1: dty=2 mxl=22(22) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=0
offset=24      <<<<<=== the bind variable values
   bfp=01a70298 bln=22 avl=02 flg=01
   value=4
End of cursor dump
***************** dump of cursor xsc=1a7681c **********************
........

And that should help you ID where the problem is (you'll see the BR_NO and be
able to identify the ROWS being modified)

      Rating: 4
No lock infor
      such as lock graph found in trace file  April 18, 2003

Reviewer:  Mike  from TX
Oracle 9i R1
I have tested a locking case:
session 1:
update emp set comm=100 where empno =7367
/

session 2
update emp set comm=10 where empno =7367
/

Then I went the dump, and found no lock infor. Why so? Do I need to set some
kind of event level order to dump the locking infor?
Thanks

          Followup:

why would there be a trace?  that is a blocking lock -- not a deadlock.  session
1 commits and all it well in the world.

you do not have a deadlock.

      Rating:
      4
  April 20, 2003
Reviewer:  Mike
       from TX
Could you please elaborate blocking-lock va deadlock ?

          Followup:

you didn't have a deadlock.  a deadlock happens when both transactions are stuck
blocking eachother.

Each

session 1 updates "bob's" record.
session 2 updates "mary's" record.
session 1 tries to update mary's record -- gets blocked by 2.

At this point, we have a simple "block".  session 2 is free to continue.  if
session 2 commits or rollsback -- session 1 continues on.

session 2 tries to update bob's record  -- gets blocked by 1.

Now we have a deadlock, neither session has a hope of completing naturally -
they would be stuck in this situation forever waiting on eachother.  So, we fail
the last statement with an error -- that session can now decide to

a) do something else, commit and finish
b) rollback

in either case, the other session will block until a) or b) occurs.

      Rating: 5
How to
      understand alert log file for dedlock.  April 20, 2003

Reviewer:  A reader
Tom,

Where can I find document to understand the output in alert log file.

          Followup:

metalink.oracle.com has some notes on it.

      Rating: 4
DEAD LOCK
      AMOUNG DDL  September 01, 2003
Reviewer:
      Nirmal  from Qatar
Hei,

We ran a stored procedure to process the payroll through a forms6i application.
After this execution, locking on a record has been occuring frequently, it may
be and index lock as you explained earlier. Still i could not realize the
scanario.

Here is my trace file details.

*** 2003-07-08 11:59:20.214
*** SESSION ID:(18.1659) 2003-07-08 11:59:20.214
A deadlock among DDL and parse locks is detected.
This deadlock is usually due to user errors in
the design of an application or from issuing a set
of concurrent statements which can cause a deadlock.
This should not be reported to Oracle Support.
The following information may aid in finding
the errors which cause the deadlock:
ORA-04020: deadlock detected while trying to lock object
HOSCDPAYTEST.NP_PAYROLL_PROCESS
--------------------------------------------------------
  object   waiting  waiting       blocking blocking
  handle   session     lock mode   session     lock mode
--------  -------- -------- ----  -------- -------- ----
 9cf90e8   a8bf2ec  bba1600    X   a8bf2ec  bbfca04    S
--------------------------------------------------------
---------- DUMP OF WAITING AND BLOCKING LOCKS ----------
--------------------------------------------------------
------------- WAITING LOCK -------------

Let me where i'm missing or doing wrong.

Nirmal

April 20, 2003 rajesh -- Thanks for the question regarding
      "selecting every second record", version 8.1.7

      You Asked
dear tom

i had thousands  records in a table. i like to  to select every second record.
ie 2nd 4th etc is that is possible

 exciting your valuable reply

sincerely

raj

      and we said...
not that there is another like a "second record" -- but you can do this:

select *
  from ( select a.*, rownum r
           from ( select * from table order by <whatever> )
       )
 where mod(r,2) = 0;

if you want a 50% random sample, by all means use the SAMPLE clause

select * from t sample(50);
Review &
      Followup

      Rating: 5
Any other
      efficient way to get ODD rownums?  April 21, 2003

Reviewer:  Saminathan Seerangan  from CA,USA

Dear Tom, Thanks for your great answer.

Any other efficient way to get ODD rownums like  1,3,5...??

select * from (
    select a.*, rownum r
        from ( select * from <table_name>  order by <nth_column> ) a
)
where mod(r,1) = 0
MINUS
select * from (
    select a.*, rownum r
        from ( select * from <table_name>  order by <nth_column> ) a
    )
where mod(r,2) = 0

      Rating:
      5
Grate  April 21, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Islamabad, Pakistan
Really Greate Tom ...

By the way, for select odd nos, the efficient way (as tom described) is:

select *
  from ( select a.*, rownum r
           from ( select * from table order by <whatever> )
       )
 where mod(r,2)!= 0;

      Rating: 5
Thanks Tom
      & Riaz  April 21, 2003
Reviewer:  Saminathan
      Seerangan  from CA,USA

April 21, 2003 Cleo -- Thanks for the question regarding "How do
      I pass a CLOB value to a stored procedure?", version

      You Asked
How do I pass a CLOB value to a stored procedure?
I am creating a stored procedure which appends a value to a CLOB

datatype.  The procedure has 2 in parameter (one CLOB and one
CLOB).  The procedure is compiled but I'm having problem
executing it.  Below is a simplified version of the procedure
and
the error given when the procedure is executed.

SQL> CREATE OR REPLACE PROCEDURE prUpdateContent (
2     p_contentId IN NUMBER,
3     p_body IN CLOB)
4     IS
5   v_id      NUMBER;
6   v_orig     CLOB;
7   v_add       CLOB;
8
9  BEGIN
10   v_id      := p_contentId;
11   v_add     := p_body;
12
13   SELECT body INTO v_orig FROM test WHERE id=v_id FOR UPDATE;
14
15   DBMS_LOB.APPEND(v_orig, v_add);
16   commit;
17  END;
18  /

Procedure created.

SQL> exec prUpdateContent (1, 'testing');
BEGIN prUpdateContent (1, 'testing'); END;

*
ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00306: wrong number or types of arguments in call to
'PRUPDATECONTENT'
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

      and we said...
Looks like you are attempting to append a TEXT (varchar2) to an
existing clob -- NOT appending a CLOB to a CLOB.

It'll just be:

ops$tkyte@dev8i> create table demo ( x int, y clob );

Table created.

ops$tkyte@dev8i>
ops$tkyte@dev8i> insert into demo values ( 1, 'Hello' );

1 row created.

ops$tkyte@dev8i>
ops$tkyte@dev8i> create or replace procedure lob_append( p_id in
number, p_text in varchar2 )
  2  as
  3          l_clob  clob;
  4  begin
  5          select y into l_clob from demo where x = p_id for
update;
  6
  7          dbms_lob.writeappend( l_clob, length(p_text),
p_text );
  8  end;
  9  /

Procedure created.

ops$tkyte@dev8i>
ops$tkyte@dev8i> select * from demo
  2  /

         X Y
----------
-----------------------------------------------------------------
---------------
         1 Hello

ops$tkyte@dev8i> exec lob_append( 1, ' World' );

PL/SQL procedure successfully completed.

ops$tkyte@dev8i> select * from demo
  2  /

         X Y
----------
-----------------------------------------------------------------
---------------
         1 Hello World

to do that.

Review &
      Followup

      Rating: 5
Succinct as
      usual, Mr. Kyte  October 25, 2001
Reviewer:
      Harrison  from Haymarket ("Venice without the water") VA, USA

These things always look a lot harder in my mind
than on your terminal. As Winston quoted another
old man, "I've seen a lot of troubles in my life
and most of them never happened" (except the recent
rash of people writing to the data dictionary, but
he never saw that.  Obviously some people are not
aware that every ringing phone is not announcing
a raise).

      Rating:
      3
  April 16, 2002
Reviewer:  Joshua
      Perry  from Las Vegas
When I do something similar in PLSQL (9i), I get an error!

Example:

BEGIN
  DECLARE l_clob             CLOB;
         vTEXT_PART    varchar2(20) := 'This is a test';
         vTEXT_ID       int          := 0;
  BEGIN
    IF( vTEXT_ID = 0 )
    THEN
    INSERT INTO TEXT_TMP VALUES
          ( SEQ_TEXT_ID.NextVal, vTEXT_PART )
       RETURNING TEXT_ID INTO vTEXT_ID;
    ELSE
    SELECT TEXT_DATA INTO l_clob
           FROM TEXT_TMP WHERE XTEXT_ID = vTEXT_ID
           FOR UPDATE ;

        dbms_lob.writeappend( l_clob,
           length(vTEXT_TEXT_PART), vTEXT_PART );
    END IF;
  END;
END;

First, I run with vTEXT_ID = 0, then after a row is inserted I take the returned
sequence and run again to try to append to that row and I get the error:

"The following error has occurred:

ORA-00600: internal error code, arguments: [ktsircinfo_num1], [2147483647], [0],
[0], [], [], [], []
ORA-06512: at "SYS.DBMS_LOB", line 722
ORA-06512: at line 13"

What am I doing wrong here?

Thanks

          Followup:

After changing your code significantly to make it even compile:

ops$tkyte@ORA9I.WORLD> create table text_tmp( text_id int, text_data clob );

Table created.

ops$tkyte@ORA9I.WORLD> create sequence seq_text_id;

Sequence created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> begin
  2    DECLARE l_clob             CLOB;
  3           vTEXT_PART    varchar2(20) := 'This is a test';
  4           vTEXT_ID       int          := 0;
  5    BEGIN
  6      IF( vTEXT_ID = 0 )
  7      THEN
  8      INSERT INTO TEXT_TMP VALUES
  9            ( SEQ_TEXT_ID.NextVal, vTEXT_PART )
 10         RETURNING TEXT_ID INTO vTEXT_ID;
 11      ELSE
 12      SELECT TEXT_DATA INTO l_clob
 13             FROM TEXT_TMP WHERE TEXT_ID = vTEXT_ID
 14             FOR UPDATE ;
 15
 16          dbms_lob.writeappend( l_clob,
 17             length(vTEXT_PART), vTEXT_PART );
 18      END IF;
 19    END;
 20  END;
 21  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> BEGIN
  2    DECLARE l_clob             CLOB;
  3           vTEXT_PART    varchar2(20) := 'This is a test';
  4           vTEXT_ID       int          := 1;
  5    BEGIN
  6      IF( vTEXT_ID = 0 )
  7      THEN
  8      INSERT INTO TEXT_TMP VALUES
  9            ( SEQ_TEXT_ID.NextVal, vTEXT_PART )
 10         RETURNING TEXT_ID INTO vTEXT_ID;
 11      ELSE
 12      SELECT TEXT_DATA INTO l_clob
 13             FROM TEXT_TMP WHERE TEXT_ID = vTEXT_ID
 14             FOR UPDATE ;
 15
 16          dbms_lob.writeappend( l_clob,
 17             length(vTEXT_PART), vTEXT_PART );
 18      END IF;
 19    END;
 20  END;
 21  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> select * from text_tmp;

   TEXT_ID TEXT_DATA
----------
--------------------------------------------------------------------------------
         1 This is a testThis is a test

I cannot reproduce.

You need to show an ENTIRE case, everything from start to finish.  don't make me
"guess"

      Rating:
      3
  April 17, 2002
Reviewer:  Sagi
       from India
I have a table LOGINFO. The Structure is:

LOGID           VARCHAR2(8)
CALLDESC        CLOB
CLOSEDESC       CLOB

When I tried the following:

declare
v_calldesc CLOB ;
v_closedesc CLOB ;
BEGIN
  SELECT calldesc, closedesc INTO v_calldesc, v_closedesc
  from calllog where callid='00078349' for update ;

  dbms_lob.writeappend(v_calldesc,LENGTH('APPENDING TO  CALLDESC'), 'APPENDING
TO CALLDESC' ) ;
  DBMS_LOB.writeappend(v_closedesc,LENGTH('APPENDING TO CLOSEDESC'), 'APPENDING
TO CLOSEDES') ;

  DBMS_OUTPUT.PUT_LINE('Before commit') ;
  COMMIT ;
  DBMS_OUTPUT.PUT_LINE('After commit') ;
END ;

Encounted the below Error:
==========================
  declare
*
ERROR at line 1:
ORA-21560: argument 2 is null, invalid, or out of range
ORA-06512: at "SYS.DBMS_LOB", line 722
ORA-06512: at line 9

It is working if I use one column but gives error if using two CLOB Columns. Why
is this?

Thanx in advance.

Regards,

          Followup:

because argument 2 is out of range.

You have DIFFERENT strings passed to length then to dbms_lob.

.. TO  CALLDESC ...
.. TO CALLDESC ...

bug in your code.

      Rating: 3
Better example
      and understanding  April 17, 2002
Reviewer:
      Joshua Perry  from Las Vegas
Sorry about the other example...

I have discovered that the problem lies in the temp table not the SQL.  What I
failed to mention in the example is that I am using a Global Temporary table
built off an Object Type,

Example:
   CREATE TYPE "USERNAME".TEXT_ROW_TYPE as object
     ("TEXT_ID" int, "TEXT_DATA" CLOB );
   CREATE Global Temporary TABLE TEXT_TMP
     of "USERNAME"."TEXT_ROW_TYPE"
     ON COMMIT preserve ROWS;

The code that you provided works for the example:

   CREATE Global Temporary TABLE TEXT_TMP
      ("TEXT_ID" int, "TEXT_DATA" CLOB)
      ON COMMIT preserve ROWS;

but not when I use the Temp. Table derived from the Object "TEXT_ROW_TYPE," why?

Thanks for the help!

          Followup:

How's about a full example -- from start to finish -- in the fashion I do them.

A simple, small, yet 100% complete example we can run to see the behaviour.

After a while I lose track of what problem exactly you are trying to solve.

      Rating: 3
Full
      Example  April 17, 2002
Reviewer:  Joshua
      Perry  from Las Vegas
I was responding the the earlier example/response on this question, and assumed
you had that information available to to so.  Here is an example in full:

This is how you created the temp table (from your earlier response):
create table text_tmp( text_id int, text_data clob );

This is where the problem lies:
   CREATE TYPE "USERNAME".TEXT_ROW_TYPE as object
     ("TEXT_ID" int, "TEXT_DATA" CLOB );
   CREATE Global Temporary TABLE TEXT_TMP
     of "USERNAME"."TEXT_ROW_TYPE"
     ON COMMIT preserve ROWS;
/
create sequence seq_text_id;
/
begin
      DECLARE l_clob       CLOB;
             vTEXT_PART    varchar2(50) := 'This is a test';
             vTEXT_ID      int          := 0;
     BEGIN
        INSERT INTO TEXT_TMP
            VALUES( SEQ_TEXT_ID.NextVal, vTEXT_PART )
            RETURNING TEXT_ID INTO vTEXT_ID;

     END;
END;
/
BEGIN
      DECLARE l_clob       CLOB;
             vTEXT_PART    varchar2(50) := 'This is a ANOTHER test';
             vTEXT_ID      int          := 1;
     BEGIN

       SELECT TEXT_DATA INTO l_clob
              FROM TEXT_TMP WHERE TEXT_ID = vTEXT_ID
              FOR UPDATE ;

          dbms_lob.writeappend( l_clob,
              length(vTEXT_PART), vTEXT_PART );
     END;
END;

It work fine with the first create statement, but fails on the second PLSQL
block with the Temp table based on the row object.

Thanks

          Followup:

Reproduced, looks like it might be related to an in progress bug #1310111.  My
suggestion is to contact support and provide this 100% reproducible and as small
as possible test case:

ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE or replace type TEXT_ROW_TYPE as
object("TEXT_DATA" CLOB );
  2  /
Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE Global Temporary TABLE TEXT_TMP of
"TEXT_ROW_TYPE" ON COMMIT preserve ROWS;
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> DECLARE
  2     l_clob       CLOB;
  3  BEGIN
  4     INSERT INTO TEXT_TMP VALUES( 'testing' );
  5     SELECT TEXT_DATA INTO l_clob FROM TEXT_TMP FOR UPDATE ;
  6     dbms_lob.writeappend( l_clob, length('x'), 'x' );
  7  END;
  8  /
DECLARE
*
ERROR at line 1:
ORA-00600: internal error code, arguments: [ktsircinfo_num1], [2147483647], [0],
[0], [], [], [], []
ORA-06512: at "SYS.DBMS_LOB", line 722
ORA-06512: at line 6

I could file it ( i believe it to be a dup of the above bug# ) but it'll be
better if you do it.

      Rating: 5
Nice
      one  April 18, 2002
Reviewer:  Nsabasi Etteh
       from England
I am new to this and i never knew that you could pass XML through a stored
procedure. I did it and it worked. Keep it up guys

      Rating:
      5
  June 12, 2002
Reviewer:
      A reader
Hi Tom,

 I am passing 1-LOB from one procedure to another.
 Second procedure already has 2-CLOB. I want to append 1-LOB passed from
procdedure to 2-LOB

 eg. 2-LOB := 2-LOB ||1-LOB;

How can i do that.

Thanks

          Followup:

read about the dbms_lob package.

PROCEDURE APPEND
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 DEST_LOB                       BLOB                    IN/OUT
 SRC_LOB                        BLOB                    IN
PROCEDURE APPEND
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 DEST_LOB                       CLOB                    IN/OUT
 SRC_LOB                        CLOB                    IN

will do that.

      Rating:
      5
  March 31, 2003
Reviewer:  SY
       from AVO
hi,Tom, I face the same question.

1. I can append text to CLOB now, or use "writeAppend" raw data to BLOB.
or use " loadfromfile " to insert external text file into BLOB,
but it seems it can't write/update/append text string
into BLOB like the following :

procedure SP_update_textBLOB( p_id in number, p_text
in varchar2 )
as
        l_blob  blob;
begin
        select blob into l_blob from report where id = p_id for update;

        dbms_lob.writeappend( l_blob, length(p_text), p_text );

        commit;
end;

Something wrong with my code (run in oracle 8.1.7)?

2. in fact, I want to convert a select statement result
into csv format, and insert this to a BLOB or CLOB column.
If I use dump_csv SP to output to OS, and load a real file from OS,
I can do it great. But if I direct write from select result
to BLOB, it seems I only can append one line once like
this:

    for i in 1 .. 255 loop
          begin

      dbms_sql.define_column( l_theCursor, i, l_columnValue, 2000 );

              l_colCnt := i;

              l_v2:=l_separator || l_descTbl(i).col_name;

              SP_updateBLOB(2, utl_raw.cast_to_raw(l_v2) );

              l_separator := p_separator;

     end;
       end loop;

Can I write it all once ? instead write line one by one?

3. How could write a line end charater to BLOB/CLOB?

Thank you very much!:)

          Followup:

1) blobs take raw.  varchar2 is not raw.  a varchar2 passed to a routine that
expects a raw expects that the varchar2 is in fact HEX codes that represent raw
data.

use utl_raw.cast_to_raw(p_text) instead of just p_text.

2)) well, just replace the || with dbms_lob.writeAppend

this routine would have the blob, you could:

    loop
        exit when ( dbms_sql.fetch_rows(l_theCursor) <= 0 );
        l_separator := '';
        for i in 1 .. l_colCnt loop
            dbms_sql.column_value( l_theCursor, i,
                                   l_columnValue );
            dbms_lob.writeAppend( l_blob, length(l_separator||l_columnValue),
                          utl_raw.cast_to_raw(l_separator||l_columnValue) );
            l_separator := p_separator;
        end loop;
        dbms_lob.writeAppend( l_blob, 1, '0A' );  /* newline */
    end loop;
    dbms_sql.close_cursor(l_theCursor);

but -- i have a feeling you would find that building a line and then appending
the line would be more efficient -- but DON'T select the blob out each time --
just do that once at the top of the loop.

3) see above, look for /* newline */

      Rating:
      5
  March 31, 2003
Reviewer:  SY
       from AVO
I checked the question "XML Generation/Manipulation using SQL", and found the
following question is exactly what I want, if change XML to CSV. :)

declare
  2    l_ctx  dbms_xmlquery.ctxHandle;
  3    l_clob clob;
  4  begin
5    l_ctx := dbms_xmlquery.newContext('select * from scott.emp');
  6    insert into xmldocs values ('EMP TABLE', empty_clob())
  7    returning xmldoc into l_clob;
  8    dbms_xmlquery.getXml(l_ctx, l_clob);
  9  end;
 10  /

But, it seems there's no dbms_csvquery exist. :(

      Rating:
      4
  April 01, 2003
Reviewer:  SY
       from AVO
when I keep digging asktom.oracle.com & google , I find many treasure.

I can answer my 3 question now, everybody who have better advice don't hesitate
to tell me.

1.As blob stores binary data, to update/append it must through raw , clob can
update/append with text .

2. insert chr(13)||chr(10) can end the line.

3. I update Tom's dump_csv code to a sp to do as this xml function:
xml :

  l_ctx := dbms_xmlquery.newContext('select * from scott.emp');
      insert into xmldocs values ('EMP TABLE', empty_clob())
      returning xmldoc into l_clob;
      dbms_xmlquery.getXml(l_ctx, l_clob);

-------------------------------
csv:

CREATE OR REPLACE procedure  dump_csv_clob
( p_id in number, p_query in varchar2, p_separator in varchar2 default ',' )
  is
      l_output        utl_file.file_type;
      l_theCursor     integer default dbms_sql.open_cursor;
      l_columnValue   varchar2(2000);
      l_status        integer;
      l_colCnt        number default 0;
      l_separator     varchar2(10) default '';
      l_cnt           number default 0;
      l_descTbl       dbms_sql.desc_tab;
      l_v2 varchar2(4000);

  begin
     --l_output := utl_file.fopen( p_dir, p_filename, 'w', 32000 );

      dbms_sql.parse(  l_theCursor,  p_query,
                                           dbms_sql.native );

      dbms_sql.describe_columns( l_theCursor, l_colCnt, l_descTbl );

      l_v2:=chr(13)||chr(10);
      clob_update(p_id, l_v2 );

      for i in 1 .. 255 loop
          begin

              dbms_sql.define_column( l_theCursor, i,
                                      l_columnValue, 2000 );
              l_colCnt := i;

              l_v2:=l_separator || l_descTbl(i).col_name;

              clob_append(p_id, l_v2 );

              l_separator := p_separator;

          exception
             when others then
                if ( sqlcode = -1007 ) then exit;
                 else
                      raise;
               end if;

          end;
      end loop;

               l_v2:=chr(13)||chr(10);
               clob_append(p_id, l_v2 );

      dbms_sql.define_column( l_theCursor, 1,
                              l_columnValue, 2000 );

      l_status := dbms_sql.execute(l_theCursor);

      loop
          exit when ( dbms_sql.fetch_rows(l_theCursor) <= 0 );
          l_separator := '';
          for i in 1 .. l_colCnt loop
              dbms_sql.column_value( l_theCursor, i,
                                     l_columnValue );

                l_v2:=l_separator || l_columnValue  ;

               clob_append(p_id, l_v2 );

              l_separator := p_separator;

               end loop;

               l_v2:=chr(13)||chr(10);
               clob_append(p_id, l_v2 );

          l_cnt := l_cnt+1;

      end loop;
      dbms_sql.close_cursor(l_theCursor);

      commit;

  end dump_csv_clob;
/

procedure clob_update( p_id in number, p_text
in varchar2 )
as
        l_clob  clob;
begin

        UPDATE report_doc SET docclob = EMPTY_CLOB()
     where sid=p_id
     RETURNING docclob into l_clob;

        dbms_lob.writeappend( l_clob, length(p_text), p_text );
end;

procedure clob_append( p_id in number, p_text
in varchar2 )
as
        l_clob  clob;
begin
        select docclob into l_clob from report_doc where sid = p_id for update;

        dbms_lob.writeappend( l_clob, length(p_text), p_text );
end;

e..... it's still much room to improve....
could anyone throw some light here? :) thanx in adv.

      Rating:
      5
  April 01, 2003
Reviewer:  SY

Wow, Tom's response much faster than I expected ! I even don't know you have
replied . Thank you, Tom.

With your suggestion, my code now look like :
...
l_v2:=l_v2 || chr(13)||chr(10);
dbms_lob.writeappend( l_clob, length(l_v2), l_v2 );
...

it's fast !

PS: Will it be faster if I write the lines to a temp CLOB in ram and in the end
write the temp CLOB to the real CLOB ?

          Followup:

no, it would not be (faster)...  but hey -- as Im fond of saying -- benchmark
it!

      Rating: 5
Thanks
      !  April 02, 2003
Reviewer:  SY
Thank tom, your suggestion is alway the best. :)
I benchmark it and got surprise result, one is ten times faster than the other,
so I think maybe I didn't make the best code, and maybe set temp clob 'cache'
parameter to 'true' is really helpful.... I paste my code here ... it would be a
bit lengthiness.

I use 'set timing on' in sqlplus to test.
the faster one, use temp clob ,  cost 1 second . :
/*--------------------------------------------------*/
CREATE OR REPLACE procedure dump_csv_clob3
( p_id in number, p_query in varchar2 )
  is
      l_theCursor     integer default dbms_sql.open_cursor;
      l_columnValue   varchar2(2000);
      l_status        integer;
      l_colCnt        number default 0;
      l_separator     varchar2(10) default '';
      l_cnt           number default 0;
      l_descTbl       dbms_sql.desc_tab;
        p_separator       varchar2(10) default ',';
      l_v2 varchar2(4000) default '';
      l_clob           clob;
      l4t_clob           clob;

  begin
      dbms_sql.parse(  l_theCursor,  p_query, dbms_sql.native );
       dbms_sql.describe_columns( l_theCursor, l_colCnt, l_descTbl );

      DBMS_LOB.CREATETEMPORARY(l_clob, TRUE);

      for i in 1 .. 255 loop
          begin
              dbms_sql.define_column( l_theCursor, i, l_columnValue, 2000 );
              l_colCnt := i;
              l_v2:=l_v2 || l_separator || l_descTbl(i).col_name;
                l_separator := p_separator;
            exception
             when others then
                if ( sqlcode = -1007 ) then exit;
                 else
                      raise;
               end if;
           end;
      end loop;
               l_v2:=l_v2 || chr(13)||chr(10);
               dbms_lob.writeappend (l_clob, length(l_v2), l_v2);
               l_v2:='';

      dbms_sql.define_column( l_theCursor, 1, l_columnValue, 2000 );

      l_status := dbms_sql.execute(l_theCursor);

      loop
          exit when ( dbms_sql.fetch_rows(l_theCursor) <= 0 );
          l_separator := '';
          for i in 1 .. l_colCnt loop
              dbms_sql.column_value( l_theCursor, i, l_columnValue );
                  l_v2:=l_v2 || l_separator || l_columnValue  ;
                    l_separator := p_separator;
          end loop;
                          l_v2:=l_v2 || chr(13)||chr(10);
                 dbms_lob.writeappend (l_clob, length(l_v2), l_v2);
                  l_v2:='';
                           l_cnt := l_cnt+1;

      end loop;
      dbms_sql.close_cursor(l_theCursor);

      UPDATE table1 SET clob_colume = EMPTY_CLOB()
                    where id=p_id
                 RETURNING clob_colume into l4t_clob;

        DBMS_LOB.APPEND(l4t_clob, l_clob);
      commit;
  end dump_csv_clob3;
/

2nd: direct write, slower
/*--------------------------------------------------*/
CREATE OR REPLACE procedure dump_csv_clob2
( p_id in number, p_query in varchar2 )
  is
      l_theCursor     integer default dbms_sql.open_cursor;
      l_columnValue   varchar2(2000);
      l_status        integer;
      l_colCnt        number default 0;
      l_separator     varchar2(10) default '';
      l_cnt           number default 0;
      l_descTbl       dbms_sql.desc_tab;
        p_separator       varchar2(10) default ',';
      l_v2 varchar2(4000) default '';
      l_clob           clob;

  begin
      dbms_sql.parse(  l_theCursor,  p_query, dbms_sql.native );
       dbms_sql.describe_columns( l_theCursor, l_colCnt, l_descTbl );

 UPDATE table1 SET clob_column = EMPTY_CLOB()
            where id=p_id
     RETURNING clob_column into l_clob;

      for i in 1 .. 255 loop
          begin
              dbms_sql.define_column( l_theCursor, i, l_columnValue, 2000 );
              l_colCnt := i;
              l_v2:=l_v2 || l_separator || l_descTbl(i).col_name;
                l_separator := p_separator;
            exception
             when others then
                if ( sqlcode = -1007 ) then exit;
                 else
                      raise;
               end if;
           end;
      end loop;
               l_v2:=l_v2 || chr(13)||chr(10);
               dbms_lob.writeappend (l_clob, length(l_v2), l_v2);
               l_v2:='';

      dbms_sql.define_column( l_theCursor, 1, l_columnValue, 2000 );

      l_status := dbms_sql.execute(l_theCursor);

      loop
          exit when ( dbms_sql.fetch_rows(l_theCursor) <= 0 );
          l_separator := '';
          for i in 1 .. l_colCnt loop
              dbms_sql.column_value( l_theCursor, i, l_columnValue );
                  l_v2:=l_v2 || l_separator || l_columnValue  ;
                    l_separator := p_separator;
          end loop;
                          l_v2:=l_v2 || chr(13)||chr(10);
                 dbms_lob.writeappend (l_clob, length(l_v2), l_v2);
                  l_v2:='';
                          l_cnt := l_cnt+1;

      end loop;
      dbms_sql.close_cursor(l_theCursor);

      commit;
  end dump_csv_clob2;
/

      Rating: 3
SY, I got
      different result  April 08, 2003
Reviewer:
      A reader
In my case, writing a real clob is slightly faster than
using temp clob , Oracle9iR2.
Maybe you should check the relative settings.

      Rating:
      4
  April 20, 2003
Reviewer:
      A reader
hi ,all , how to ' create a temp CLOB in ram ' ? It seems that temp CLOB is not
stored in ram .....

          Followup:

PROCEDURE CREATETEMPORARY
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 LOB_LOC                        CLOB                    IN/OUT
 CACHE                          BOOLEAN                 IN
 DUR                            BINARY_INTEGER          IN     DEFAULT

it'll cache what it can/should.

April 21, 2003 zaibi -- Thanks for the question regarding "Space
      information", version oracle 7.3

      You Asked
Hi,

I want to know  high water mark and allocated space above high
water mark(which can be deallocated) for table,tablespace and
datafile.

How can I do that.

Thanks

Zaibi

      and we said...
For a segment (table) see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:231414051079
for a datafile, you can modify a script I call "maxshrink".  It
is designed to generate "alter database datafile 'filename'
resize <smallest possible size>" statements.  It does this by
finding the HWM of the datafile:

------------------ maxshrink.sql -----------------------------
set verify off
column file_name format a50 word_wrapped
column smallest format 999,990 heading "Smallest|Size|Poss."
column currsize format 999,990 heading "Current|Size"
column savings  format 999,990 heading "Poss.|Savings"
break on report
compute sum of savings on report

column value new_val blksize
select value from v$parameter where name = 'db_block_size'
/

select file_name,
       ceil( (nvl(hwm,1)*&&blksize)/1024/1024 ) smallest,
       ceil( blocks*&&blksize/1024/1024) currsize,
       ceil( blocks*&&blksize/1024/1024) -
       ceil( (nvl(hwm,1)*&&blksize)/1024/1024 ) savings
from dba_data_files a,
     ( select file_id, max(block_id+blocks-1) hwm
         from dba_extents
        group by file_id ) b
where a.file_id = b.file_id(+)
/

column cmd format a75 word_wrapped

select 'alter database datafile ''' || file_name || ''' resize '
||
       ceil( (nvl(hwm,1)*&&blksize)/1024/1024 )  || 'm;' cmd
from dba_data_files a,
     ( select file_id, max(block_id+blocks-1) hwm
         from dba_extents
        group by file_id ) b
where a.file_id = b.file_id(+)
  and ceil( blocks*&&blksize/1024/1024) -
      ceil( (nvl(hwm,1)*&&blksize)/1024/1024 ) > 0
/
============================================================

Review &
      Followup

      Rating: 5
maxshrink
      script rocks.  November 14, 2002
Reviewer:
      Abhijit  from Mumbai

      Rating: 5
Still giving
      problem..  April 21, 2003
Reviewer:  Rahul
      Sharma  from India
Hi Tom

I am using LMT with uniform extent allocation for Temp tablespace. Right now my
tempfile is 11GB which is a problamatic sign for us.
I ran your script after changing it for temporary file (replace DBA_DATA_FILES
to DBA_TEMP_FILES) and get the Alter commands. But at the time of running that
command I am still getting the same error..
ORA-03297: file contains used data beyond requested RESIZE value

What is the solution of this problem? I have searched your site but didn't get
the exact response.

Thanks.

          Followup:

just

a) create a new temp of the right size
b) alter users to use this new one
c) drop this one

TEMP allocates the space once and holds it there ( to avoid the expensive
overhead of allocate/free/allocate/free).  temp always appears full -- so the
3297 is sort of unavoidable.

but you know, if it is 11gig today, it'll be 11gig again real soon.  sounds like
you might need 11gig of temp (about $100 of disk these days)

      Rating: 5
Still giving
      problem..  April 21, 2003
Reviewer:  Rahul
      Sharma  from India
Hi Tom

I am using LMT with uniform extent allocation for Temp tablespace. Right now my
tempfile is 11GB which is a problamatic sign for us.
I ran your script after changing it for temporary file (replace DBA_DATA_FILES
to DBA_TEMP_FILES) and get the Alter commands. But at the time of running that
command I am still getting the same error..
ORA-03297: file contains used data beyond requested RESIZE value

What is the solution of this problem? I have searched your site but didn't get
the exact response.

Thanks.

      Rating:
      5
  July 21, 2003
Reviewer:
      A reader

April 21, 2003 -- Thanks for the question regarding "oracle
      designer", version 8.1.6

      You Asked
Tom:
let's say we use oracle designer to design the oracle database.
and everything is fine.

later on, we may have the requirement to add one more table, and modify the
structure of another table, what should we do?

can we use oracle designer for that purpose? how?
i understand how to do it at first time, but if we need to modify something, i
don't know how to use oracle designer,
or what we should do is bypass oracle designer and modify the database directly
by using sqlplus?

      and we said...
Tim Taylor, our resident Designer guru says (rather emphatically)

USE DESIGNER! This is where the tool excels. Add your new column to your
existing table. Create your new table. Generate DDL. The generator will
compare the contents of the Designer Repository to the on-line database's
data dictionary. It will generate the correct ALTER TABLE statements for the
new column plus all the DDL to implement the new table and any foreign key
constraints, triggers, and other supporting objects.

USE DESIGNER! This is exactly the reason why you use a modeling tool rather
than just notepad and SQL*Plus.
Review &
      Followup

      Rating:
      3
  July 18, 2001
Reviewer:
      A reader
i want to use designer, but lack of document,

      Rating:
      3
  July 18, 2001
Reviewer:
      A reader
i want to use designer, but lack of update to date document,

      Rating: 4
Designer is the
      best  August 13, 2001
Reviewer:  Rui Brito
       from Portugal
Don´t lose time.
I use Designer 6.0 in all process of development.
I have 400 tables, 100 views, 360 Forms and 250 Reports all 100% generated with
templates and some imagination.
The goal is know the Preferences and how Designer generated the FMB. All the
rest is easy.

      Rating: 4
Designer
      Problem: Char is converting to Varchar2   April 21, 2003

Reviewer:  Moiz
Hi,
I am using Designer9i and I am trying to generate DDL scripts thro' Database
Design Transformer (DDT). But all CHAR attribute-types of E-R Model is getting
converted to VARCHAR2.

What could be the problem.
Regards,
Moiz

          Followup:

that is very nice of it actually as CHAR stink.  really -- they are the wrongest
datatype on the planet.

But other then that -- I don't know, I don't use that particular tool myself.
I'll refer you to otn.oracle.com

discussion forums -> products -> developer suite -> designer.

maybe someone there can help you out (but give varchar2 a chance, it is really
the type you want)

      Rating: 3
Compare to
      Rational Rose  April 21, 2003
Reviewer:
      Saminathan Seerangan  from CA,USA
Dear Tom,

I never used Designer but mainly Rational Rose for data modeling. What are the
advantages of Designer compare to Rational Rose EE?

          Followup:

presumes I've used RR which would be a "no"...

April 21, 2003 Dilip -- Thanks for the question regarding "how
      to identify indexes created when defining primary key", version 9.2

      You Asked
primary key & indexes used to maintained pk uniqueness were not explicitly named
by developer. it has system generated names.
primary key constraints can be identified  constraint type ='P'
constraints and indexes can be renamed easily in 9.2
how do I identify the index created when the table's primary key was defined?

Thanks
Dilip

      and we said...
check out INDEX_NAME in user_constraints.

(available in 9i and up ONLY, no, it is not in 8i)
Review &
      Followup

      Rating:
      3
thanks  April 21, 2003
Reviewer:
      Dilip
Yep, it matches,
only thing if constraints are renamed, then back to square one.
thanks it helped.

assuming it's highly unlikely to have more than 1 index with same set of columns
in same position -
can it work if no. of index columns & constraint columns matches and col.
position matches, we can safely assume that's that index for pk?

          Followup:

not really

primary key on (a,b,c)

indexes on (a,b,c), (b,a,c,d,e,f)

you don't know which is used just looking at columns -- both are candidates.

Why are we back to square one?

ops$tkyte@ORA920> select constraint_name, index_name from user_constraints;

CONSTRAINT_NAME                INDEX_NAME
------------------------------ ------------------------------
SYS_C003715                    SYS_C003715

ops$tkyte@ORA920> alter index SYS_C003715 rename to foobar;

Index altered.

ops$tkyte@ORA920> select constraint_name, index_name from user_constraints;

CONSTRAINT_NAME                INDEX_NAME
------------------------------ ------------------------------
SYS_C003715                    FOOBAR

ops$tkyte@ORA920> alter table t rename constraint SYS_C003715 to barfoo;

Table altered.

ops$tkyte@ORA920> select constraint_name, index_name from user_constraints;

CONSTRAINT_NAME                INDEX_NAME
------------------------------ ------------------------------
BARFOO                         FOOBAR

it keesp the linkage

      Rating:
      5
thanks  April 21, 2003
Reviewer:
      A reader
I was just matching index_name from user_indexes with constraint_name from
user_constraints... missed out on index_name from user_constraints..

thanks makes sense now!!

April 21, 2003 -- Thanks for the question regarding "CBO:
      Comparing 2 plans; behaviour of CHOOSE", version 8.1.7

      You Asked
Say I have a DEV, UAT and Prod environment containing largely varying amounts of
data but the schema/structure is the same.

How do I go about comparing the execution plan across environments? As per an
earlier question, you said that the 'cost=' number on the the explain plan is
basically meaningless.

So, if I have 2 plans from Dev and UAT, 2 screenfulls long, how exactly can I
compare them to see if they are the same? Just by eyeballing them? There has got
to be a better way.

Another question concerning CBO: The default behaviour of the
optimizer_goal=CHOOSE is to invoke CBO if at least one of the tables in the
query has statistics. This seems a little daft to me. It is well known (you have
said so yourself) that CBO works best if all the tables involved in the query
have stats. So why does CHOOSE work this way?

Thanks

      and we said...
If UAT and Prod differ significantly, UAT is almost useless -- except as a
staging ground to make sure your upgrade scripts work.  It cannot be used for
scalability testing or performance testing.

You cannot (i cannot, i know of no one who can) eyeball a plan and say "yes,
this is good" -- if we could, the CBO would be perfection as we would just
codify out thoughts...

You need to test the actual stuff.  The plan that works GREAT for 10,000 rows
stinks for 100,000 or 1,000,000.  The plan that works GREAT for 1,000,000 rows
might stink for 10,000.  Unless you are a SQL parser with infinite knowledge of
the data and understand exactly how the application uses this data and millions
of other pieces of information -- you'll not be eyeballing anything.

Especially two pages of something.

The thought is

o you analyzed some tables -- must be a reason
o some tables won't be analyzed (temporary tables come to mind?) sometimes --
for these default statistics (sort of like the RBO might be thought to be using)
would be used.

It would be daft IMO to analyze some but not all of a set of tables used by an
application.
Review & Followup

      Rating: 4
DBMS_STATS to
      tranfer statistics from Prod to Dev  April 21, 2003

Reviewer:  Saminathan Seerangan  from CA,USA

Hi Tom,

In case of 9i, shall we use the following built-in procedure to address this
issue?
a)DBMS_STATS.EXPORT_SCHEMA_STATS
b)DBMS_STATS.IMPORT_SCHEMA_STATS

Correct me if i'm wrong.

          Followup:

tell me -- which plan is better:

select t1.object_name, t2.object_name
  from t1,  t2
 where t1.object_id = t2.object_id
   and t1.owner = 'WMSYS'

Row Source Operation
---------------------------------------------------
HASH JOIN
  TABLE ACCESS FULL T2
  TABLE ACCESS FULL T1

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T2'
   2    1     NESTED LOOPS
   3    2       TABLE ACCESS (FULL) OF 'T1'
   4    2       INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE)

No matter what you answer, I'll prove you wrong.  If you pick the hash join, the
NL will be better.  If you pick NL, the hash will be better.

So what if you can see the plan -- can you - human as you are - tell us which is
better?  No, you actually need to run it, under your conditions, against the
real data to be certain.

      Rating:
      3
OK...  April 21, 2003
Reviewer:
      A reader
1. Comparing plans:

So what are you saying? Since the data in the 2 environments differ, the CBO
might be correct in generating different plans? So, why does Oracle offer the
plan stability/stored outlines feature to force a certain execution plan? That
plan might be horrible given the data in that environment, right?

Anyway, back to the original question, there is no way to compare plans?

2. Yes, I agree it is daft to analyze some tables and not others in a
application. But, temporary tables notwithstanding, I still dont understand,
from a design perspective, why Oracle's CHOOSE uses CBO if only 1 table has
stats? Why not use CBO only if ALL tables have stats? And provide a /*+ CBO /*
hint if the user knows better and wants to use CBO even if all tables DONT have
stats.

All I am saying is that the default behaviour of CHOOSE doesnt make sense to me.

          Followup:

1) no, I'm not saying that at all.

What I'm saying is the query with the BEST POSSIBLE PLAN on the planet with
10,000 rows in UAT might return in 0.50 seconds -- and this is deemed
acceptable.

That same query with the BEST POSSIBLE PLAN on the planet with 100,000 records
in production might return in 5.00 seconds -- and this is deemed UNACCEPTABLE
(the design and question are such that the query runtimes are linear or
exponential with respect to data volume -- more data = more run time)

So, in both environments you have the BEST POSSIBLE PLAN on the planet for the
query -- but given the physical design (it always comes back to that) -- you
cannot answer it in production due to the data volumes.

No problem with the query plans here -- none at all.  It is physics pure and
simple.  It happens all of the time -- it is why importing stats from production
into test is only useful for very limited sorts of things (like reproducing a
bad query plan and seeing what tweaks you might be able to use to fix it while
filing a bug if necessary).

So, the CBO does the exact right thing -- it is just that your design imposes
linear or worse response times (eg: think full scan of an unindexed table for a
really SIMPLE example).

So go ahead and compare the plans all you want.  What can you derive from it?

2) because if it was ALL -- temporary tables for example would in general not
work.  Sorry it doesn't make sense, sometimes things just "are" -- and are
documented as being "that way".  Guess their thinking was "this guy analyzed
this table for SOME reason -- lets use that information"

April 21, 2003 --
      Thanks for the question regarding "Implicit cursors", version 8.1.7

      You Asked
I have read your views on implicit vs. explicit cursors and that you prefer
implicit cursors, by far.

What do you think about parametrized implicit cursors? Say I have a cursor like

cursor c1(i_cust_no number := null) is
select .. from <tables> where <joins> and cust_no=NVL(i_cust_no,cust_no)

This way I can use this cursor to process all customers as

for rec in c1
loop
...
end loop;

or one specific customer as

for rec in c1(1234)
loop
end loop;

Is this usage of cursors ok?

Thanks

      and we said...
I still prefer:

for x in ( select ... from tables where .. and cust_no = nvl(i_cust_no,cust_no)
)

personally -- that way, when I'm reading the code, I can SEE the query without
having to page up again.

At this point in time, it is a matter of preference, coding style.

Now, as for your construct with the NVL(), make sure you are using CBO!  see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7555175291763
for why
Review
      & Followup

      Rating:
      4
Thanks  April 21, 2003
Reviewer:
      A reader
---x---
personally -- that way, when I'm reading the code, I can SEE the query without
having to page up again.
---x---

yes, I agree, but then if I am using this cursor in 10 places in my code, and if
i need to change it, i would need to change all the 10 places! isnt that the
very reason why the CURSOR construct was created so that the cursor is defined
just once, with parameters and used many places?

          Followup:

views are us.

that is when I would be using a view with a simple, single predicate...

but yes, that would be a valid argument for using a packaged cursor at that
point -- as I said, we are talking programming style at this point more then
anything else.

April 21, 2003 --
      Thanks for the question regarding "Displaying progress of a long-running
      job", version 8.1.7

      You Asked
Suppose I have a cursor returning a large number of rows and I do some
processing on each row that also takes some time.

I want to provide progress on these operations from within the procedure using
dbms_application_info.set_client_info()

Say

i := 0;
for rec in c1
loop
  i := i+1;
  dbms_application_info.set_client_info('Processing record '||i);
end loop;

Ideally, what I would like to do is show 'Processing record 1 of 10000'. This
way I have a estimate of how much time is remaining.

But, of course, I have no way to know the cursor is going to return 10000 rows
until I do a 'count(*)' before actually entering the cursor loop.

Is there a better way to do this? Would v$session_longops help here?

Thanks

      and we said...
well, doing the count(*) could easily double your run time - are you willing to
do that?

If you are using the CBO as you should be, you can explain the query -- get the
estimated cardinality from that

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4456921593765#5881975252271
in 9i, we'd suck that data right out of v$sql_plan to avoid the extra parse that
explain plan is going to introduce.  but since you don't run thousands of these
long running things.....

then you can use v$session_longops which expects you to tell it how many steps
you have (estimated number of rows in your case) and what step you are on.

Review &
      Followup

      Rating:
      5
Thanks  April 21, 2003
Reviewer:
      A reader
I didnt know I could do a explain plan right in my code and use the 'card'
number. Great idea.

      Rating: 3
Hm...doesnt
      quite work  April 21, 2003
Reviewer:
      A reader
select * from
accnt_emp_optn aeo,optn o,accnt a
where aeo.optn_id=o.optn_id
and aeo.accnt_id=a.accnt_id
and a.act_flg=1
and o.optn_cd_val='ABCD'

This gives me the right cardinality of 15000. The actual number of rows returned
are 15164. Quite a good estimate.

When I change this to

select * from
accnt_emp_optn aeo,optn o,accnt a
where aeo.optn_id=o.optn_id
and aeo.accnt_id=a.accnt_id
and decode(a.act_flg,1,'Y',0,'N')='Y'
and o.optn_cd_val='ABCD'

i.e. nothing has really changed, the 2 queries are identical, suddenly the card
drops down to 302.

Did the DECODE confuse the CBO? The reason I am doing this decode is that is the
way it is being exposed in a view to the users (converting 1/0 to Y/N).

Why did the card drop down so much when the query hasnt really changed? Thanks

          Followup:

it is an ESTIMATE -- a GUESSTIMATE

It is what I portrayed it as -- a pretty good estimate.  The query "really did"
change.  The decode doesn't confuse it - juts makes it much more "general".  It
does not expand the decode out.

      Rating: 2
More
      general?  April 21, 2003
Reviewer:
      A reader
---x---
It is what I portrayed it as -- a pretty good estimate.  The query "really did"
change.  The decode doesn't confuse it - juts makes it much more "general".  It
does not expand the decode out.
---x---

Right, but if it makes it more general, I would have expected the estimated no.
of rows to go UP! Not go down and drastically too!

In other words, if, as you say, it makes it more general because it does NOT
expand the DECODE, it is as if I have

select * from
accnt_emp_optn aeo,optn o,accnt a
where aeo.optn_id=o.optn_id
and aeo.accnt_id=a.accnt_id
and o.optn_cd_val='ABCD'

(just take the DECODE predicate out). But, this query, again, gives me a proper
estimate (22000 rows).

It is just when I add the DECODE that the estimate is thrown off completely.

How can I workaround this? I am ok with a estimate, but it is useless if it is
totally off like in this case.

          Followup:

it is what it is.

"workaround" is to count(*) which I find infinitely more distasteful.

      Rating: 5
But why do you
      need that decode?  April 21, 2003
Reviewer:  Jan
      van Mourik  from Houston, TX, USA
In this case it's quite unnecessary I would think, all it does is slow that
query down, or??? Interesting though that it totally throws off the estimate...

BTW. Having histograms on the table can make the estimate much more accurate.

      Rating:
      4
Decode  April 21, 2003
Reviewer:
      A reader
I need that decode because this query is wrapped in a view that uses this
'act_flg' column as
select ..
decode(act_flg,1,'Y',0,'N') as active,
...

so the user/app would say

select ..
from my_view
where active='Y'

April 21, 2003 -- Thanks for the question regarding "Remote
      Tables insert", version 9.2

      You Asked
Hi,
   I was going thru your postings on remote tables where you recommend instead
of using synonyms for remote tables use views like
create view vu_remote as select * from dept@remotedblink.
Q1) I also want to insert into these remote tables. So how can I insert into
these remote tables thru views.Any suggestions.These views are based on single
tables and not multi table joins.

Q2) Does it affect the performance to select from a view rather then directly
thru the linked table . The view is owned by the current schema. It will always
be select * from vu_remote is
like  select * from (select * from dept@remotelink).

Q3)What are the advantages of using views and not private synonyms.

      and we said...
a1) use the insert statement?  there is nothing preventing you from inserting in
a view

a2) a view is just a stored query -- just like a synonym must be looked up and
translated so shall a view.

a3) the procedures you create are dependent on the view, not the remote object,
directly.

Hence, you can compile your code even if the dblink is not available.

The view imports the metadata locally -- so you can query user_tab_columns for
example to see the number of columns, the datatypes, their names, etc

The view protects you from changes on the remote system -- when they decide to
rename a column -- you don't have to.  when they add a column, you don't have
to.  and so on.

Review &
      Followup

      Rating: 5
Great info. for
      developers who are working with distributed DB  April 21,
      2003
Reviewer:  Saminathan Seerangan  from CA,USA

      Rating: 5
Thanx a Million
      with a Q  April 21, 2003
Reviewer:
      A reader
What if the view is based on multiple table joins and we want to insert into
them. ? Sounds like a stupid Q as we can only insert in one table at a time. ?

          Followup:

that is a different question but --

if one of the tables is key preserved -- you can insert into that:

ops$tkyte@ORA817DEV> create table p ( x int primary key, a int );

Table created.

ops$tkyte@ORA817DEV> create table c ( x references p, y int primary key, z date
);

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace view v
  2  as
  3  select p.a, c.x, c.y, c.z
  4    from p, c
  5   where p.x = c.x;

View created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert into p values ( 1, 1 );

1 row created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert into v ( x,y,z ) values ( 1, 100, sysdate );

1 row created.

ops$tkyte@ORA817DEV> insert into v ( x,y,z ) values ( 1, 101, sysdate+1 );

1 row created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select * from v;

         A          X          Y Z
---------- ---------- ---------- -----------
         1          1        100 21-APR-2003
         1          1        101 22-APR-2003

and of course you can always use INSTEAD of triggers to make the view fully
insertable in all cases if you like.

April 21, 2003 lunar -- Thanks for the question regarding "How
      should i promise our customers?", version 8.1.7.0.0

      You Asked
hi, tom,

I am lunar :)
Thank you very much for you reply.

the following is rephrase the question
Our environment :
OS: Tru64 4.0f Unix  ,   RA3000 cluster
DB: 3 nodes OPS, Oracle 8.1.7.0.0 EE

I think my question will be:
b) customer wants you to take a backup of production and restore it to
production to see if it works

I want to know if you are me, could you promise this kind of requires?

sigh..
:(

Best Regards
Lunar.
2002.10.09

the following is my question that i asked you yesterday:
Our Customers insistently ask me to do once backup and recovery on the
Production machine to test the Availability  for the  backup.

I have told them it's a big risk to test a backup in this way, but i can not
persecute them up to now . I feel very dysphoric and anxious, Because they never
do any woke to test a backup , I worry about we perhaps lost datas if anything
happens  errors(e.g. the tape is bad, and so on) during we do this work on
Production machine .

I want to know what should I do and whether the Oracle Support or you should do
some works like this ? And should you promises your Customers?

Best Regards
Lunar
2002.10.08

      and we said...
It would be very unwise and unsafe to do that without comprimising the existing
database.

If you make a mistake, damage the EXISTING database and the backups aren't any
good -- what then???

The backup should be restored to a test/development machine.  It doesn't have to
be a rac cluster to test, just a single instance would be enough to test the
integrity of the backups.
Review & Followup

      Rating: 3
I really agree
      with you  October 09, 2002
Reviewer:  Lunar
       from China Beiing(but now i am Tainland on business)
I see.

Now, we have told them the result and suggest them that we can take a backup on
production and take a recovery on other macine, but we can't persecute them, so
we are going to asking oracle support to do recovery on production :).

Our Customer are asking us give them a promised for the Oracle Database can't
occur any mistakes.(no error)

How terrible :)

      Rating:
      4
  April 21, 2003
Reviewer:
      A reader
Hi Tom,

Assuming the environment is Sol8.0/RAC and db size is 500+GB, what do you
suggest for backup, should it be done through RMAN or Veritas or some other
suggestion.

Thanks.

          Followup:

why not veritas using rman?

http://www.veritas.com/nbugsp/nbu_2a.jhtml
if veritas is your storage solution..

      Rating:
      5
  April 21, 2003
Reviewer:
      A reader
Thanks Tom!!

April 21, 2003 Iain -- Thanks for the question regarding "How to
      preventing front-end users accessing the db via sqlplus , etc", version
      8.1.7

      You Asked
I have read the your comments on this issue. What do you think of this idea?
When the user logs on to the front-end the code assigns a password-protected
role to the user allowing access to the application tables. If the user logs on
via odbc or sqlplus this role is not assigned therefore preventing unauthorised
access.

Regards,

Iain Blacklaw

      and we said...
it would take me less then 5 seconds to get this password or figure out how to
get the role enabled.  It is trivial.  Security via obscurity.  A simple sqlnet
trace and I'm in.

You can achieve this degree of security using secure application roles and ntier
proxy authentication in 9i easily -- before that, it was all security via "i
hope they aren't savvy enough to be able to figure it out"
Review &
      Followup

      Rating: 2
Implementing
      this via trigger in 8.1.7  April 20, 2003

Reviewer:  ahmad imran  from pakistan
I am also looking for this that how it is possible that i restrict the user to
access the database through software which is in dev 6i. I put almost every
check to there in 6i. But I am still confusing that if log on through sql plus
he can damage the database.

Although I create a self password form for athentication, it works fine and i am
not giving the user the original db password but any user who have dba
privilage(like system) can access the user 's scheema through aqlplus or other
softwareeven he can delete it ...

Now how can I prevent it (triggers or not???)

          Followup:

you cannot.  If you are concerned about people with DBA privs doing bad things,
you have a waaayyy bigger fish to fry.  think about it -- if you cannot trust
the DBA account....

      Rating:
      3
  April 21, 2003
Reviewer:
      A reader
The true, at least in NT, if you have access to server, you have access to the
database,

To give some security using form I do the following
1) basic role, this role has the minimum to connect and enable a role.
2) applicatoin role, with password, it must be enabled.
3) schema users, that users are "locked", to connect between database I user
other users.

          Followup:

#2 is easy to subvert as I said.  so, beware.  knowledge being power here --
knowing that you do not have a 100% secure solution is better then not knowing.

NT, Unix, OS/390 -- doesn't matter.

      Rating: 3
How about
      product_user_profile table  April 21, 2003

Reviewer:  Arup Nanda  from Norwalk, CT, USA

Tom,

To prevent people from connecting via sql*plus, can't we use the
product_user_profile table to enforce that? Or I am missing something?

Arup

          Followup:

c/sqlplus/toad/
c/sqlplus/access/
c/sqlplus/odbc/

now what.

      Rating:
      5
  April 21, 2003
Reviewer:  Chuck
      Jolley  from OKC, OK 73107
What do you think of password mangling?
When a user logs into our app, before they are logged into the database, the app
logs into the database as a user with access to only one package.
The package is used to create a hash out of the username and password that the
user entered.
The app then logs the user in using their username and the hashed password.
The users therefore do no know their own passwords and can't log in using other
tools.
Of course, if they get hold of the app password and figure out the algorithm,
they can log in as the app, get the hash, then log in as themselves.
Not genius proof, but at least they can't just stumble in.
Chuck

          Followup:

it'll take me no time at all to get the hashed password.  the hashed password
comes back to the client app - a sqlnet trace will put that in a flat file for
me.  it is the same problem as above.

and you now have a hard coded password in the client application that you cannot
change easily -- even when it is compromised.

      Rating:
      5
  April 21, 2003
Reviewer:  Chuck
      Jolley  from OKC, OK USA
OK "i
hope they aren't savvy enough to be able to figure it out"
 it is untill we get on 9i. ;)

How does "secure application roles and ntier
proxy authentication" prevent a savvy front-end user from using sqltrace and a
debugger to open it up?

          Followup:

secure application roles allow a stored procedure to enable a role.

Ntier proxy authentication allows a middle tier to log on WITH ITS credentials
on your behalf....

so, now, a stored procedure can say:

if TOM_KYTE is the real user
   AND
   the PROXY USER IS "HR"
then
   enable role
else
   fail
end if

TOM_KYTE identifies himself to the middle tier using username/password, x509
PKI, whatever...

the middle tier logs in using ITS credentials on TOM_KYTE's behalf.

You know 2 things - real user = TOM_KYTE, proxy_user = HR.  The *only* way the
proxy user can be HR is if the middle tier HR application logged in.

Nothing is happening on the client pc.  the only way to log in as hr is via the
hr app in the middle tier.

April 21, 2003 jery -- Thanks for the question regarding "pl/sql
      for native execution", version 9

      You Asked
Good morning Tom,

Could you please give us a hint on the performance that can be reached compiling
a procedure into native code, c for example.

Thanks.

      and we said...
2x is a good maximum.
0x is a good minimum.

Somewhere in between is where you will generally fall.

It depends 100% on the code itself.  Lots of numeric computations -- no sql,
just plsql processing.  2x is possible (maybe even a little more).

Lots of SQL, most of the time is in SQL -- 0x.  The sql won't run any faster,
just the compute intensive PLSQL.
Review &
      Followup

      Rating:
      1
  June 13, 2002
Reviewer:
      A reader
Thanks.

      Rating: 3
clarification,
      please...  June 13, 2002
Reviewer:  George
       from Chester Springs, PA USA
are you saying that an SQL query that is encapsulated
in a C program will run at virtually the same speed,

but that if I convert a PLSQL program - say a cursor
loop that summarizes the data - to C, moving the loop
constructs to C, that the C program will run up to 2x?

Or are you saying something completely different?

          Followup:

I'm saying something totally different.

We are talking about ncomp'ed PLSQL (natively compiled plsql).  In 9i, when you
compile a procedure, you can "ncomp" it.

Normally, PLSQL is processed like this:

you write it -> we compile it into p-code -> you run it -> we interpret the
p-code

With ncomping it is like this:

you write it -> we turn it INTO c -> we compile that into a .obj or .o file ->
  we link that into the database kernel -> you run it -> we just call a
subroutine

It is like using a JIT compiler with java for example but this is a WAT compiler
(way ahead of time).

Here is a quick and dirty example I use:

ops$tkyte@ORA9I.WORLD> @demo021
=================================================================

make sure the settings are setup right for NCOMP'ed plsql

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> alter system set plsql_native_make_utility = 'make';

System altered.

ops$tkyte@ORA9I.WORLD> alter system set plsql_native_make_file_name =
'/export/home/oracle9i/plsql/spnc_makefile.mk';

System altered.

ops$tkyte@ORA9I.WORLD> alter session set plsql_compiler_flags = 'INTERPRETED';

Session altered.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> set echo off
Enter to continue

=================================================================

we'll compile an interpreted one (default) first

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> alter session set plsql_compiler_flags = 'INTERPRETED';

Session altered.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> create or replace
  2  function factorial_interpreted( p_n in number ) return number
  3  is
  4  begin
  5      if ( p_n = 1 )
  6      then
  7          return 1;
  8      else
  9          return factorial_interpreted( p_n-1 ) * p_n;
 10      end if;
 11  end;
 12  /

Function created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> set echo off
Enter to continue

=================================================================

and now NATIVE (too easy)

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> alter session set plsql_compiler_flags = 'NATIVE';

Session altered.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> create or replace
  2  function factorial_native( p_n in number ) return number
  3  is
  4  begin
  5      if ( p_n = 1 )
  6      then
  7          return 1;
  8      else
  9          return factorial_native( p_n-1 ) * p_n;
 10      end if;
 11  end;
 12  /

Function created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> set echo off
Enter to continue
=================================================================

and the race is on...

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> alter session set plsql_compiler_flags = 'INTERPRETED';

Session altered.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> declare
  2      l_start number;
  3      l_n     number;
  4  begin
  5      l_start := dbms_utility.get_time;
  6      for i in 1 .. 1000
  7      loop
  8          l_n := factorial_interpreted( 50 );
  9      end loop;
 10      dbms_output.put_line( (dbms_utility.get_time-l_start) ||
 11          ' hsecs...' || l_n );
 12
 13      l_start := dbms_utility.get_time;
 14      for i in 1 .. 1000
 15      loop
 16          l_n := factorial_native( 50 );
 17      end loop;
 18      dbms_output.put_line( (dbms_utility.get_time-l_start) ||
 19          ' hsecs...' || l_n );
 20  end;
 21  /
45 hsecs...30414093201713378043612608166064768844300000000000000000000000000
27 hsecs...30414093201713378043612608166064768844300000000000000000000000000

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> /
46 hsecs...30414093201713378043612608166064768844300000000000000000000000000
26 hsecs...30414093201713378043612608166064768844300000000000000000000000000

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD>

      Rating: 5
.obj , .o,
      .pcode  June 13, 2002
Reviewer:
      A reader
Tom kindly exlain about .obj , .o and pcode .. what are they

          Followup:

obj and o = object code.  What C, and many other languages get compiled into.
This is "machine code" -- the bits and bytes the OS will run.

pcode is like byte code (from java).  It is a psuedo compiled code -- we
interpret it and run it, instead of the OS

      Rating: 3
Inernal
      error  June 14, 2002
Reviewer:  siva
       from INDIA
Hello TOM,
  While creating the ncomp i'm geting the following error. Is there any setting
problems.

SQL>  alter session set plsql_compiler_flags = 'NATIVE';

Session altered.

SQL>  alter system set plsql_native_library_dir='/home';

System altered.

SQL>
SQL>  create or replace   function factorial_native( p_n in number )
  2     return number
  3       is
  4       begin
  5           if ( p_n = 1 )
  6           then
  7               return 1;
  8           else
  9               return factorial_native( p_n-1 ) * p_n;
 10          end if;
 11      end;
 12   /

Warning: Function created with compilation errors.

SQL> sho err
Errors for FUNCTION FACTORIAL_NATIVE:

LINE/COL ERROR
-------- -----------------------------------------------------------------
0/0      PLS-00801: internal error [79704]
SQL>

 one more doubt,
       Will it store any files in the operating system for this files.

Thanks and awaiting for your reply.

with regards,
siva

          Followup:

see

http://technet.oracle.com/docs/products/oracle9i/doc_library/901_doc/appdev.901/a89856/12_tune.htm#48419
for the setup involved.  There are one or two more init.ora type parameters you
may need to set.

and -- you do have an ANSI C compiler installed right?

      Rating: 3
Internal
      error  June 14, 2002
Reviewer:  siva
       from INDIA
Hello TOM,
  I forgot to enter in the previous mail. I set this parameters too.

 alter system set plsql_native_make_file_name =
 '/opt/oracle/oraHome1/plsql/spnc_makefile.mk'
/
alter system set plsql_native_make_utility = 'make';

Thanks

with regards,
siva

      Rating: 4
Thanks,
      +  June 14, 2002
Reviewer:  A reader

thanks for the clarification... I am trying to become aware of the latest
features, so this really helped.

btw, reading your message a second time leaves me with
another question - you say 0x to 2x, when I think you
mean 1x (no time improvement) to 2x (calculations take
about half the time.)  Perhaps its semantical, but its
relevant to ask if its possible to LOSE performance by
ncomping a routine?

thanks again, George

          Followup:

whoops -- I should have said 1x ... 2x

You will get somewhere between "no gain" and "2 times as fast" (or 1/2 as slow?)

      Rating: 5
What can I do
      under windows 2000 with MSVC 6.0  June 15, 2002

Reviewer:  GuoHui Chen  from China
Thanku Tom!
How to set the plsql_* parameters in initSID.ora

          Followup:

For this -- I'll have to refer you to support.  I don't have a windows
environment to play around with on this one.  I do know they didn't supply the
makefile with 9.0.1 on windows for some reason.  You'll need to check with
support to see if it is available.

      Rating:
      4
  December 19, 2002
Reviewer:
      A reader
we compile it into p-code.
I'm sorry for this simple question. What is p_code?

          Followup:

like "psuedo" code.

like "byte" code.

an intermediate representation that lends itself to be interpreted quickly at
runtime.

      Rating: 3
native
      compilation of sys/system packages  February 25, 2003

Reviewer:  A reader
Hi Tom,
This is on solaris 2.8/ oracle 9.2.0.1.
I read an article on OTN about native compilation of all oracle pl/sql packages
such as standard, dbms*, etc. I created a new db instance with all plsql native
compilation parameters set and sure enough it produced STANDARD__SYS__2.so etc
objects in $ORACLE_HOME/plsql/userlibs directory and the instance seems to be
OK. My questions:

1. would it help or hurt ncomp'ing all oracle packages?

2. i had to create a new db instance on this same machine. Again I set ncomp
parameters. Oracle went ahead and re-compiled all the same objects under
plsql/userlibs dir.. I think this is kind of redundant. I thought oracle would
either name these objects specific to every instance ,or sense that natively
compiled objects already exist and not overwrite them. can you please comment on
this.

          Followup:

1) benchmark it, let us know what you find.  You've got it all setup

2) how does oracle know that dbms_output is really the same dbms_output in both
instances?  to oracle -- it is just "code". each instance could have different
pieces of code.

you should NOT use the same directory for both instances -- that could be fatal!

      Rating: 4
error after all
      required settings   April 16, 2003
Reviewer:
      June  from MD, US
after I set up system as following:

alter system set plsql_native_make_file_name =
'/usr/local/oracle/product/9.2.0/plsql/spnc_makefile.mk' ;
alter system set plsql_native_make_utility = 'make';
alter system set
plsql_native_library_dir='/usr/local/oracle/product/9.2.0/plsql/plsql_libs';
alter system set plsql_native_library_subdir_count = 1000;

and I verified with SA that we have the Sun Workshop 6 C Compiler installed. but
still, I got following error after I submitted ' alter session set
plsql_compiler_flags=native;' and tried to compile the procedure:

Errors for PROCEDURE POP_EMP_APRVD_RGSTN_TMP:

LINE/COL ERROR
--------
0/0      PLS-00801: internal error [79704]

what else did I miss here? any other profile setting on OS level?

thanks!

          Followup:

metalink.oracle.com says (after simply cutting and pasting the error message in)

Problem Description
-------------------

You want to use the native compilation option of Oracle9i.
You have  set the plsql_compiler_flags to native.
You are trying to compile a pl/sql procedure from sql*plus.

You get the following error during compilation:

PLS-00801: internal error [79704]

You have checked that the relevant parameters are set

show parameter plsql%

  NAME                TYPE      VALUE
  -----------------------------------------------------------------
  plsql_compiler_flags string NATIVE
  plsql_native_c_compiler string /usr/local/bin/gcc
  plsql_native_library_dir string /oracle/locallib
  plsql_native_library_subdir_count integer 10000
  plsql_native_linker string /usr/ccs/bin/ld
  plsql_native_make_file_name string /oracle/plsql/spnc_makefile.mk
  plsql_native_make_utility string /usr/local/bin/make
  plsql_v2_compatibility boolean FALSE

Solution Description
--------------------

Check if the directories being referred while using NATIVE compilation are
present physically in the correct path. If not , create them manually before
attempting the compilation.

Explanation
-----------

The parameter plsql_native_library_dir decides the location of the native
libraries that are created during the pl/sql compilation.
If this directory is not physically present, then you might get this error.

      Rating: 3
error
      exists  April 21, 2003
Reviewer:  June
       from MD, US
Tom,

thanks for the information you provided. However after I changed the mode of
directory to 777 for plsql_native_library_dir, and changed the
plsql_native_c_compiler to the specific path where c compiler sits, I still get
the error message. all the settings are as following:
NAME                            TYPE    VALUE
------------------------------ ------- --------------
plsql_compiler_flags           string  INTERPRETED
plsql_native_c_compiler        string  /opt/SUNWspro/bin/cc
plsql_native_library_dir       string
/usr/local/oracle/product/9.2.0/plsql/plsql_libs
plsql_native_library_subdir_count integer 1000
plsql_native_linker            string  /usr/ccs/bin/ld
plsql_native_make_file_name    string
/usr/local/oracle/product/9.2.0/plsql/spnc_makefile.mk
plsql_native_make_utility      string  make

and before recompiling, I did:
alter session set plsql_compiler_flags=native;

I have verified that the make file and native_linker is right in place. Is there
any other trick to make it work?! thanks!

          Followup:

on the database server, do this:

ls -ld /usr
ls -ld /usr/local
ls -ld /usr/local/oracle
ls -ld /usr/local/oracle/product
ls -ld /usr/local/oracle/product/9.2.0
ls -ld /usr/local/oracle/product/9.2.0/plsql
ls -ld /usr/local/oracle/product/9.2.0/plsql/plsql_libs

lets see that output.

      Rating: 3
here is the
      output  April 21, 2003
Reviewer:  June
       from MD, US
here is the output for the requested info on the unix DB is on:

drwxr-xr-x  37 root     sys         1024 Oct 15  2002 /usr
drwxr-xr-x  18 root     root         512 Nov  1 12:52 /usr/local
drwxr-xr-x  23 oracle   dba         2048 Apr 21 10:21 /usr/local/oracle
drwxr-xr-x   8 oracle   dba         1024 Jun 21  2002 /usr/local/oracle/product
drwxr-xr-x  50 oracle   dba         1024 Jan 15 11:11
/usr/local/oracle/product/9.2.0
drwxrwxr-x   9 oracle   dba         1024 Apr  9 17:49
/usr/local/oracle/product/9.2.0/plsql
drwxrwxrwx   2 oracle dba        96 Apr 17 13:10
/usr/local/oracle/product/9.2.0/plsql/plsql_libs

the account I tried to compile pl/sql in native is non-dba account. As always,
thank you for looking into it.

          Followup:

I'll have to refer you to support at this time.  I cannot reproduce the issue
and all "looks" ok from here.

April 21, 2003 --
      Thanks for the question regarding "Bind variables and execution plan",
      version 9.2.0

      You Asked
Hi,

In the Oracle Concepts manual at the link

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/c16sqlpl.htm#1539
The steps for processing a SQL statement are

Stage 1) Create a cursor (implicit)

Stage 2) Parse the statement

    (2a) Verify it to be valid SQL

    (2b) Data dictionary lookups for table and column definations

    (2c) Aquire parse locks on required objects

    (2d) Check privilages

    (2e) Determine optimal execution plan

    (2f) Load it in the shared pool

<QUOTE>
Oracle parses a SQL statement only if a shared SQL area for an similar SQL
statement does not exist in the shared pool. In this case, a new shared SQL area
is allocated, and the statement is parsed.

The parse stage includes processing requirements that need to be done only once
no matter how many times the statement is run. Oracle translates each SQL
statement only once, reexecuting that parsed statement during subsequent
references to the statement
</QUOTE>
  :
  :

Stage 5) Bind the bind variables
  :
  :

How can Oracle develop an optimal execution plan (step 2e) if it does not know
the values of the bind variables?

For eg, suppose I have an employee table with an index on sex. I have a million
rows and only 1000 are F.

Query is: SELECT first_name FROM emp WHERE sex = v_sex

For 1st execution, v_sex = M

Since a majority of the rows are M, the optimizer determines (in step 2e) that a
full table scan is needed

2nd run of the query, v_sex = F

If we go according to the concepts manual, since a parsed version of the SQL
already exists, it skips Stage 2 (note that the 'optimal' execution plan is
generated in stage 2), therefore it does a full table scan???

That doesnt sound correct. What am I missing ???

Thanks

      and we said...
In a transactional system -- the most common -- you do not have queries as you
describe.  In these systems -- the VAST MAJORITY of systems -- you would not run
a query that either gets one row or one million rows.  In these systems -- you
use bind variables.  You are parsing dozens of statements per second and they
all return concise result sets.

In a data warehouse, you might not use bind variables in many cases -- in fact,
some data warehouse features such as star transformation are not even possible
if you use bind variables.

What you describe is correct, accurate and optimal.  You use the appropriate
techniques in the appropriate places.  Most systems -- bind variables.
Review &
      Followup

      Rating:
      5
  March 31, 2003
Reviewer:
      A reader
Thanks, that helps a lot

      Rating: 3
Execution Plan
      for SQL with Bind  April 21, 2003
Reviewer:
      Nitin
Hi,

How can I get the execution plan for a SQL which has a bind value (without
replacing the value)?
Example: In SQLPLUS, if I do
set autotrace traceonly explain
SQL> select sysdate from dual where dummy=:1;
SP2-0552: Bind variable "1" not declared.

Thanks and regards

Nitin

          Followup:

variable x varchar2(5)
set autotrace traceonly explain
select sysdate from dual where dummy=:x;

:1 won't work -- 1 is not a valid identifier in sqlplus

April 22, 2003 --
      Thanks for the question regarding "How Oracle keep track of object size",
      version 9.2.0

      You Asked
Hi Tom,
Just want to make myself clear on how Oracle keep track on table object size. I
probably misunderstood come crucial concepts.

I understand that any data changes are kept temporarily in buffer. DBWR will
writes them into datafiles later.

Consider following senario:

1. User is allocated quota 1MB on a tablespace.
2. The user creates a table on the tablespace.
3. The user inserts 1.5MB data and commits.

I think it is possible that at the point the user commits, less than 1MB of data
is being written into datafiles by DBWR. The rest are still in the buffer and
being flushed by LGWR into redo log files.

If the above is possible. Should the user receive an error because he had
actually exceeded his quota? How does Oracle know he had exceeded his quota when
most of the data are not yet written into datafiles?

Thanks in advanced.

-- Kim

      and we said...
In order to insert 1.5mb of data -- the table would need to have 1.5mb of
extents allocated to it so the 1.5mb of data in the buffer cache would KNOW
where it is to be written (they all need to be assigned to blocks -- blocks
belong to extents).

Hence, the users 1mb quota would prevent them from allocating 1.5mb of extents
and hence the hypothetical situation is impossible to get into.

Review &
      Followup

      Rating: 5
How Oracle keep
      track of object size  April 21, 2003
Reviewer:
      Kim
Thanks Tom,
That means blocks and extents are allocated at the moment we writes data, even
when the data are still in buffers. Is my understanding right?

          Followup:

you cannot put data into a block that is not part of an extent that belongs to
that segment.

So yes, you cannot write into a random block that is not part of the object
already -- so you cannot write above your quota.

      Rating:
      4
  April 22, 2003
Reviewer:
      A reader
why there is a need for blocks and hence extents?  why not the data directly
written into the database(tablespace)?  I accept that tablespaces are made up of
extents & blocks. do you think without having logical things like blocks &
extents, it is difficult to maintain an oracle database.  Please don't get
angry, I am not blaming the design, rather i want to have some basic info on the
need for blocks and extents.  I sincerely hope that it would help everyone.

          Followup:

umm, well -- we need to organize the data somehow, otherwise chaos reigns.

so, we use blocks (easy to manage, fixed sized entities)

and collections of blocks (extents)

so we can have segments (tables, indexes, partitions and so on)

without blocks, extents, segments et al -- it would be chaos and unmanageable.

      Rating: 3
you are always
      there to help great work  April 22, 2003
Reviewer:
      Vijay Sehgal  from India

April 22, 2003 ted -- Thanks for the question regarding "storage
      of numbers and a shared sql question", version

      You Asked
thomas, I have two questions,

1. numeric datatype
the storage is 1+precision/2.
if precision is 9
storage is 6 bytes.
if the field is nullable would 6 bytes allocated
?

2. after oracle idenfying two sqls are identical.
what determines whether oracle is going to reparse
the same sql(resulting in very high parse call/excutions
ratio ?

Thanks again
ted chyn

      and we said...
No -- a number of precision 9 will not take 6 bytes -- it'll
take some number of bytes upto 6.

If you

SQL> create table t ( x number(9) );

and then:

  1  declare
  2    y varchar2(10);
  3  begin
  4     for i in 1 .. 10 loop
  5             insert into t values (y);
  6     y := y||'1';
  7    end loop;
  8* end;
tkyte@ORACLE> /

PL/SQL procedure successfully completed.

tkyte@ORACLE> select x, vsize(x) from t;

         X   VSIZE(X)
---------- ----------

         1          2
        11          2
       111          3
      1111          3
     11111          4
    111111          4
   1111111          5
  11111111          5
 111111111          6

10 rows selected.

you'll see that the size of X varies depending on what the data
is in X.  the precision and scales are EDITS on the data and
have nothing to do with the storage (except for putting an upper
bound on the size).

A number(9) will consume:

o 0 bytes if it is a trailing NULL column (if no other column
after it has a non-null value in that row)

o 1 bytes if it is NULL and not a trailing NULL.

o n bytes according to the formula in the docs.  use vsize to
see what the number is for a given number.

When we find 2 sqls to be the same (based on a hash) it will be
upto the objects it references if we can reuse it.  Lets say the
SQL we are submitting is:

select * from emp;

lets say the first person to submit that is SCOTT.  That is
dependent on SCOTT.EMP.  Lets say I then run it but I have my
OWN emp table.  I find scotts hashed query and start to look at
it.  turns out that if I resolve EMP -- I get TKYTE.EMP, not
SCOTT.EMP so I cannot use that parsed query -- I must look
further on.  So we get the query, we get what the query goes
after and make sure that we "see" the same things.

Review &
      Followup

      Rating: 3
what am I
      missing??  April 22, 2003
Reviewer:  Tim
      Barton  from Cambridge, MA USA
Tom,

I'm trying to understand how numbers are stored in Oracle so I can accurately
estimate how much space I need to buy if we add a bunch of numeric columns to
a large table.

I have created the following test and it shows me something
I can't yet explain and it doesn't seem to follow the 1+precision/2 rule.

The get_data function I use here is copied  from your One on One book - page 204
-
it is based on dbms_space.unused_space.

I changed the output from your script to

dbms_output.put_line(l_total_bytes-l_unused_bytes || ' total BYTES USED by
table');

This is done in Oracle 8.1.7.4.1 with a block_size of 8192 in a
locally managed tablespace with 4m extents.

create table t  (d number(10)) pctfree 0;
create table t1 (d number(10)) pctfree 0;

begin
for i in 0 .. 10000
   loop
      insert into t  (d) values (1234);
      insert into t1 (d) values (1234567891);
    end loop;
    commit;
end;
/

PL/SQL procedure successfully completed.

TIM> EXEC get_data (t)
131072 total BYTES USED by table

TIM> EXEC get_data (t1)
131072 total BYTES USED by table

What I can't figure is why the tables use exactly the same number of bytes when
vsize against the values I inserted return

select vsize(1234) from dual;

VSIZE(1234)
-----------
          3

select vsize(1234567891) from dual;

VSIZE(1234567891)
-----------------
                6

Any insight???

Thanks,

Tim.

          Followup:

umm, you forgot about block overhead, row overhead, etc etc etc...

way to size:

o fill a table with representative data
o analyze
o see what it takes
o multiply (if you loaded 1% of the representative data, multiply by 100)

April 22, 2003 Dan -- Thanks for the question regarding
      "Automomous transactions and cursors over db links", version 8.1.7.4

      You Asked
Tom - Can you explain why I would get: 'ORA-00164: autonomous transaction
disallowed within distributed transaction' when I call a *local* procedure that
is defined as an autonomous transaction, when I'm iterating through a cursor
opened over a database link?  Here'a my test example (in scott):

CREATE TABLE audit_tab
(   msg         varchar2(4000),
    username    varchar2(30) default user,
    timestamp   date default sysdate
)
/

CREATE OR REPLACE PACKAGE audit_trail

AS

PROCEDURE log (
    p_msg         varchar2,
    p_username    varchar2 default user,
    p_timestamp   date default sysdate
);

END;
/

CREATE OR REPLACE PACKAGE BODY audit_trail

AS

PROCEDURE log (
    p_msg         varchar2,
    p_username    varchar2 default user,
    p_timestamp   date default sysdate
)

IS

PRAGMA AUTONOMOUS_TRANSACTION;

BEGIN

    INSERT INTO audit_tab
        (msg, username, timestamp)
    VALUES
        (p_msg, p_username, p_timestamp);

    COMMIT;

END;

END;
/

create database link other_scott connect to scott identified by tiger using
'CONNECTSTRING';
/

I get the error in this test scenerio:

DECLARE

CURSOR emp_cursor IS
    select * from scott.emp@other_scott;

emp_record  emp_cursor%ROWTYPE;

BEGIN

    OPEN emp_cursor;

    FETCH emp_cursor INTO emp_record;

    WHILE emp_cursor%FOUND LOOP

    --ORA-00164 here
        scott.audit_trail.log (emp_record.ename);

        FETCH emp_cursor INTO emp_record;

    END LOOP;

    CLOSE emp_cursor;

END;

I'm not committing in other_scott, so I'm baffled why Oracle would throw this
exception here.  It seems the db link cursor is considered a distributed
transaction, even though it's read-only.  If so, is there a standard workaround
I could use?

Thanks!

      and we said...
the dblink is considered a distributed transaction until you commit.

So, this

create or replace package test_pkg
as

CURSOR emp_cursor IS
    select * from scott.emp@ora920.us.oracle.com;

    procedure do_open;
end;
/
create or replace package body test_pkg
as

    procedure do_open
    is
        pragma autonomous_transaction;
    begin
        open emp_cursor;
        commit;
    end;

end;
/

DECLARE

emp_record  test_pkg.emp_cursor%ROWTYPE;

BEGIN

    test_pkg.do_open;

    FETCH test_pkg.emp_cursor INTO emp_record;

    WHILE test_pkg.emp_cursor%FOUND LOOP

        scott.audit_trail.log (emp_record.ename);

        FETCH test_pkg.emp_cursor INTO emp_record;

    END LOOP;

    CLOSE test_pkg.emp_cursor;

END;
/

appears to function as would:

cott@ORA817DEV> DECLARE
  2
  3  CURSOR emp_cursor IS
  4      select * from scott.emp@ora920.us.oracle.com;
  5  emp_record  emp_cursor%ROWTYPE;
  6
  7  BEGIN
  8
  9      open emp_cursor;
 10          commit;
 11
 12      FETCH emp_cursor INTO emp_record;
 13
 14      WHILE emp_cursor%FOUND LOOP
 15
 16          scott.audit_trail.log (emp_record.ename);
 17
 18          FETCH emp_cursor INTO emp_record;
 19
 20      END LOOP;
 21
 22      CLOSE emp_cursor;
 23
 24  END;
 25  /

PL/SQL procedure successfully completed.

(for the same reason)

Review &
      Followup

      Rating: 4
Simple answer
      to a not so obvious problem  April 21, 2003

Reviewer:  Dan  from Raleigh, NC
Issuing an immediate COMMIT or ROLLBACK after the cursor is opened seems easy
enough, but I'm still not sure why Oracle considers an open cursor over a db
link a distributed transaction.  This smells like a "feature" to me...I'd be
interested if it works the same in 9iR2.

          Followup:

that was to a 9iR2 database.

but touching a database link starts a distributed transaction "just in case".
the commit closes it -- no more distributed transaction.

scott@ORA920> DECLARE
  2
  3  CURSOR emp_cursor IS
  4      select * from scott.emp@ora9i;
  5  emp_record  emp_cursor%ROWTYPE;
  6
  7  BEGIN
  8
  9      open emp_cursor;
 10          commit;
 11
 12      FETCH emp_cursor INTO emp_record;
 13
 14      WHILE emp_cursor%FOUND LOOP
 15
 16          scott.audit_trail.log (emp_record.ename);
 17
 18          FETCH emp_cursor INTO emp_record;
 19
 20      END LOOP;
 21
 22      CLOSE emp_cursor;
 23
 24  END;
 25  /

PL/SQL procedure successfully completed.

that was 9iR2 to 9iR1

      Rating: 4
Could you use
      AQ to "solve" this issue?  April 22, 2003

Reviewer:  Dan Loomis  from Raleigh, NC
I was just thinking - if issuing the bogus COMMIT to close the distributed
transaction is a problem (already 3 separate developers pinged me on it), I
wondering if Advanced Queuing is a reasonable workaround.  Here's the proposed
process:

1. client calls logging procedure
2. logging procedure (logically, not physically autonomous now) pops a message
on a queue.
3. The message is immediately consumed by another procedure in my package, which
does the actual work and commit.

That way, the client isn't forced to workaround this error, and the transaction
is outside the parent transaction scope.  What do you think?

          Followup:

they are not "bogus", bogus implies "doesn't do anything", "not useful"

as long as you don't care of the message actually gets processed every time
(since you cannot be using a stable, table based queue -- you'd need a COMMIT)
-- sure.


Subject | Article | Last Updated (East coast time)
Triggers | article only | Apr 23, 2003 07:13 am
1=1 in the where clause | article only | Apr 23, 2003 07:19 am
sqlldr - skipping columns | article only | Apr 23, 2003 07:05 pm
How to use a if statement within where clause? | article only | Apr 23, 2003 08:13 pm
Case sensitive searches | article only | Apr 23, 2003 08:20 pm
Redundant Indexes | article only | Apr 24, 2003 08:02 am
cron job doesn't work | article only | Apr 24, 2003 08:05 am
Tablespace Type Change | article only | Apr 24, 2003 09:30 am
initializing a record | article only | Apr 24, 2003 09:39 am
Differences between two tables in a Replicated Environment | article only | Apr 24, 2003 09:42 am
oracle data to xml file | article only | Apr 24, 2003 12:22 pm
pctfree & pctused | article only | Apr 24, 2003 04:17 pm
Hot backup | article only | Apr 24, 2003 06:20 pm
Does the SGA swap or is it prevented? | article only | Apr 24, 2003 06:26 pm
lock and latch | article only | Apr 24, 2003 06:29 pm
Database links in PROD and TEST environments | article only | Apr 25, 2003 07:44 am
Backup using and old controlfile and online backup | article only | Apr 25, 2003 08:07 am
Generating XBRL/XML file | article only | Apr 25, 2003 01:59 pm
how to handle no data found in a complicated update | article only | Apr 25, 2003 03:30 pm
Closing Cursors | article only | Apr 25, 2003 03:32 pm
Exit From SQLPLUS | article only | Apr 25, 2003 03:53 pm
Trick for calling package.procedures dynamically | article only | Apr 25, 2003 04:46 pm
Database Security | article only | Apr 25, 2003 07:55 pm
Execute Procedure in Dbms_job | article only | Apr 26, 2003 08:22 am
Erorr ORA-1000, maximum open cursors exceeded. | article only | Apr 26, 2003 11:47 am
Is it possible to add a Partion on existing table | article only | Apr 27, 2003 08:32 am
Log_checkpoint_interval and timeout | article only | Apr 27, 2003 04:30 pm
Hierarchial query and NVL2 | article only | Apr 28, 2003 07:24 am
Natural Join and Index Monitoring (9iR2 Slide Show) | article only | Apr 28, 2003 07:26 am
enqueue processes and rescources | article only | Apr 28, 2003 07:52 am
Generate XML in desired format | article only | Apr 28, 2003 08:30 am
Unused Objects and Load Average | article only | Apr 28, 2003 10:56 am
implementing a data mart / warehouse | article only | Apr 28, 2003 01:09 pm
Recovery from bkup of open db with no activity during backup | article only | Apr 28, 2003 01:12 pm
ORA-00942: table or view does not exist (but they do!) | article only | Apr 28, 2003 02:09 pm
Timestamp appends "000" when formating. | article only | Apr 28, 2003 03:40 pm
Column Name , Command | article only | Apr 28, 2003 09:21 pm
Tablespace Coalesce ? | article only | Apr 29, 2003 07:48 am
No logon/connect in Forms 6 Application | article only | Apr 29, 2003 08:03 am
Exclusions from Hot backup. | article only | Apr 29, 2003 08:06 am
To determine SGA size for unix box | article only | Apr 29, 2003 08:46 am
Volumetric Analysis | article only | Apr 29, 2003 08:54 am
compare only time part of the date | article only | Apr 29, 2003 09:38 am
correct loop usage | article only | Apr 29, 2003 09:53 am
Commit after sequence.nextval | article only | Apr 29, 2003 12:21 pm
Multi-level replication | article only | Apr 29, 2003 12:50 pm
Lag functionality without using LAG | article only | Apr 29, 2003 01:19 pm
row(s) 1 - 47 of 47
April 23, 2003 Sandra -- Thanks for the question regarding "Triggers", version SQL*Plus: 8.0.5.0.0 Oracle8.0.5
You Asked
Hi
have have used Oracle Migration Wizard for MS Access to convert
a Database.  I am now trying to set a trigger that will insert a
new number for a primary key, replacing the Access
Counter/autonumber.  I have followed the trigger code given in
the documentation but keep getting an error about bind
variables.
The code given is:-
Create Trigger TRG_CNT_OTBLCOUNTERTEST
BEFORE
INSERT OR UPDATE on OTBLCOUNTERTEST
FOR EACH ROW
DECLARE
   iCounter SCOTT.OTBCOUNTERTEST.PRIMARYKEY%TYPE;
   cannot_change_counter EXCEPTION;
BEGIN
IF INSERTING THEN
      SELECT test.NEXTVAL into iCounter FROM dual;
      :new.PRIMARYKEY :=iCounter;
END IF;
END IF; -- End of Inserting Code
 IF UPDATING THEN       -- Do not allow the PK to be changed.
      IF NOT(:new.PRIMARYKEY = :old.PRIMARYKEY) THEN
         RAISE cannot_change_counter;
END IF;
   END IF; -- End of Updating Code
EXCEPTION
WHEN cannot_change_counter THEN
      raise_application_error(-20000,'Cannot Change Counter
Value');
END;
/
I am not sure where i am going wrong, i have set up the table
otbcountertest with PK Number,name varchar2 and a sequence
called test.
Any suggestions
Thank you
Sandra
and we said...
I see two errors:

iCounter SCOTT.OTBCOUNTERTEST.PRIMARYKEY%TYPE
                 ^^ missing L OTBL, not OTB

END IF;
END IF; -- End of Inserting Code

end if seems duplicated.

The trigger could simply be:

scott@8.0> Create or replace Trigger TRG_CNT_OTBLCOUNTERTEST
  2  BEFORE INSERT OR UPDATE of primarykey on OTBLCOUNTERTEST
  3  FOR EACH ROW
  4  BEGIN
  5     IF ( INSERTING ) THEN
  6        SELECT test.NEXTVAL into :new.primarykey
  7          FROM dual;
  8     elsIF (UPDATING and :new.primarykey <> :old.primarykey)
  9     then
 10        raise_application_error(-20000,
 11                         'Cannot Change Counter Value');
 12     end if;
 13  END;
 14  /

Trigger created.
Review & Followup
Rating: 4
December 31, 2001
Reviewer: A reader

Rating: 5
Auto Numbering Which is the best practice? April 22, 2003
Reviewer: TH from Manama, BAH
Dear Tom,

In our shool software(Delphi/Oracle) we have a provision to create the
RegistrationNumber automatically when the student is admitted.

I wrote a trigger to fill the Primary Key(RegistrationNumber) when the record is
coming with an empty RegistrationNumber.

The problem is,
A few of my collegues are saying, it is a bad practice. We should make one key
and display it along with the student's information. It will be his permanent
regn number when the record is posted.

So nobody else at the same time can register a student with the blocked
registration number.

I just want to know your opinion. (Even though it is a matter with the business
logic).

If you face such a situation what will be your solution?

Pls tell me how to solve the situation if my collegues are right.

Thanx for all your help.

Followup:
don't understand the question/problem as stated

Rating: 5
My trigger is this. April 22, 2003
Reviewer: TH from Manama, BAH
I forgot to show the trigger!

CREATE OR REPLACE TRIGGER TrigAutoRegNo  before insert on Students
          for each row
declare
         NextNumber Number(15);
         Padd_Count Number(20);
begin
         select  NVL(max(to_number(regno)),0) + 1 into NextNumber
            from  Students
           where  decode( replace( translate( regno,'0123456789.','00000000000'
), '0', '' ),
             null, to_number(regno), null ) is not null;

         select nvl(regnolength,6) into Padd_Count from settings where rownum=1;

         IF (:new.regno) IS NULL THEN
           :new.regno := lpad(to_char(NextNumber),Padd_Count,'0') ;
         END IF;
end;
/

Followup:
All -- now i see it, YUCK -- horrible.

worst of all possible implementations.

Use a sequence.

if ( ;new.regno ) is null
then
 select sequence.nextval into :new.regno from dual;
end if;

that is the entire trigger -- use NUMBERS, use sequences, never never never
select max()+1 -- don't store numbers in strings.

Rating: 5
The reason why we used strings... April 23, 2003
Reviewer: TH from Manama, BAH
Dear Tom,

The reason why we used string is, some users have RegistrationNumber as
AlphaNumeric in their old data. So we should consider that too in our system.

Sequences will miss the continuation sometimes. Right?

Followup:
ok on the string

so what on the "gap free" -- big deal -- they will not be gap free using your
technique the moment you delete.

especially BIG DEAL if some of your customers use a totally different technique,
no one has an expectation OR NEED for "gap free" numbers.

You have a choice, you can do it the wrong way (as you have) -- but be prepared
for concurrency issues and serialization (two people WILL generate the same
number if they run at the same time.  one of them WILL be made to wait.  the one
made to wait will get an error after waiting -- not a nice thing to do)

or you can use sequences and be able to scale, go fast and have lots less code

Your choice

Rating: 5
Thanks! April 23, 2003
Reviewer: TH from Manama, BAH
Dear Tom,

Thank you very much!!

Rating: 5
Thanks! April 23, 2003
Reviewer: TH from Manama, BAH
Dear Tom,

Thank you very much!!

April 23, 2003 rd -- Thanks for the question regarding "1=1 in the where clause", version 8.1.5
You Asked
Tom

We have a few queries written by previous delelopers, which
are like

select...
from ...
where
1=1

what does a 1=1 mean

thank you
and we said...
they probably were doing dynamic sql and had the ability to add more predicates
to it so that the query sometimes looked like:

select ...
 from ...
 where 1=1
   AND a=5
   AND b=2;

the "where 1=1" makes it so they can just add "AND <condition>" to the end of
the query easily.

Its a pretty common "trick"

followup to comments 1..6

Ora_Coder and some of the others are correct - thats what I was trying to say.
Perhaps an example will make it more clear.

If you look at:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279
I have a full example there where I myself used this 1=1 trick.

followup to comment 7

Thats non true.  "where 1=1" will in no way shape or form cause us to not use
(or use) an index.  It will have no bearing on the use or not of indexes --
never, not at all.

if you read the example I have pointed to above -- I use the 1=1 but the queries
generated can, will (and do) use indexes.  I use this method in the asktom
search -- it uses indexes when and where possible.

followup to the comment from Reviewer:  Sonali Kelkar  from usa

You'll have to ask the "they" you refer to as I have no idea how "1=1" could be
used for validation.  Does not make sense to me at all (I only know of its use
in dynamically constructing a where clause as demonstrated in the above link)
Review & Followup
Rating: 3
can you explain more about this trick August 09, 2001
Reviewer: Bob from Eng
You have put us in suspense about this trick

can you take an example of your own, and show the use of 1=1 , and what is it
providing.

and how does 1=1 help in adding an "AND <condition>" to the end of the query
easily.

Rating: 3
Still Confusing August 09, 2001
Reviewer: Sikandar Hayat Awan from Pakistan
Sorry, I have not got your point that someone is adding where 1=1 to help in
future that he/she will enter only AND. Now we will compere these two,

Select * from emp where 1=1 and idno =10;
Select * from emp where idno =10;

Here second query is short and developed with little efforts.
What r ur comments.
Thanks.

Rating: 3
useful trick "where 1=1" August 10, 2001
Reviewer: Ora_Coder from India
The trick is that the query already has a "select * from emp where 1=1". This
helps us in concatenating the query
dynamically without adding the "where" clause the first time & checking/avoiding
it for all additional clauses.
You just keep adding the clause " and x=y" for all additional clauses.

Rating: 3
respond August 10, 2001
Reviewer: A reader
can you please respond to comment one

Rating: 2
re: how this trick works? August 10, 2001
Reviewer: Bob from Russia
That's easy. Imagine you have a search engine that you want to be able to
perform a full search and a narrowed down search by a number of predicates. You
write a query like this:

stmt := 'select something from somewhere where 1=1';
begin
 if predicate1 then stmt := stmt||' and pred1='||pred1value; end if;
 ....
 execute immediate stmt using ....;
end;

you can add as much predicates as you want simply skipping those not used and if
none were used you will still have a valid query, so you don't have to check if
where has any arguments before executing it.

Rating: 3
Missing the point August 10, 2001
Reviewer: Mark J. Bobak from Ann Arbor, MI
I think you guys are missing the point.  The 'trick' is
that when you have a SQL w/ a dynamic 'where' clause,
it's lots easier if you can guarantee that there will
always be at least one where condition.  So, use '1=1' as
that one where condition.  Example:
Suppose 'select * from emp' is the base query, and the
user can dynamically plug in where conditions.
Suppose he adds two, 'deptno=5' and 'sal>5000'.  So,
here, you have to keep track of whether 'deptno=5' is
the first condition, in which case you need to append
'where ' or not the first condition, in which case
you need to append 'and '.  By starting w/ a base
SQL of 'select * from emp where 1=1', you can avoid that
logic and just add each user supplied where condition, and
tack a 'and ' in front of it, and it will work.

And the '1=1' will not have any effect on the data selected,
it's sole purpose in life is to make it simpler to tack on
dynamic where conditions.

Rating: 2
Several Meanings August 10, 2001
Reviewer: Holger Spangardt
We use this clause for forcing full table scan.
Usually no index is used with "where 1=1".
This easy helps finding corrupt indexes :-)

Rating: 4
Another use August 10, 2001
Reviewer: Still learning from USA
I think we can use that feature to create a table based
on another table, say:

create table temp as select * from all_objects
1)    where 1=1        ----- insert data as well from all_objects;
2)    where 1=0        ----- don't import data from all_objects, only create
table structure based on all_objects.

correct me if i'm wrong

Rating: 4
where 1=1 August 11, 2001
Reviewer: kannan from india
Even if i studied in material i can't understand this much.
Excellent TOM

Rating: 2
Can you explain this please August 13, 2001
Reviewer: Sonali Kelkar from usa
We do lot of HTML coding and we use this 1= 1 all the time, but they say that it
is for validation. How does it work any example.

thanks
Sonali
sonaliak@hotmail.com

Rating: 3
1=1 August 14, 2001
Reviewer: Badhri from India
Is it that a where clause 1=1 will be helpful to the developer to add his
additional where clauses without typing a keyword WHERE. This doesn't solve a
great problem. Do we have any performace change in doing the same... How does
the indexes are involved if we use the same...

The timing of execution of the SQL is faster compared to without 1=1.. I dont
understand the logic behind.. Can anyone help

Rating: 5
Not really that hard., January 14, 2003
Reviewer: Andrew Gilfrin from Dover Kent UK
It seems people are finding this hard to understand, but its really rather a
easy and neat trick.

Say your developing an application which accepts parameters from a user.  He
could enter 0, 1, 2 or n parameters and based on this you need to return
records.

You already know the main select something like

select * emp

You come to the part in your program where you need to build the where clause.
The problem is we need to know which parameter the user has entered is the first
as this needs a preceeding where and subsequent ones need an 'and'

To do this you could use code like this.

first_val varchar2(1) := 'Y';
select_code varchar2(1000) := 'select * emp';

If param1 <> NULL
   If first_val = 'Y'
     select_code := 'where '||param1
   Else
     select_code := 'and '||param1
   End If
End If

If param2 <> NULL
   If first_val = 'Y'
     select_code := 'where '||param2
   Else
     select_code := 'and '||param2
   End If
End If
.
.
.

And so on.

Using 1=1 we dont need to do the secondary checks to check if this is the first
time we have a parameter as by defualt we have the 1=1.

So it becomes

select_code varchar2(1000) := 'select * emp';

If param1 <> NULL
    select_code := 'and '||param1
End If

If param2 <> NULL
    select_code := 'and '||param2
End If
.
.
.

Followup:
well, just to be correct the code would use BIND VARIABLES.  We all know you
would *never* actually write the code as above.  You would do it like:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279
;)

Rating: 5
Doubt cleared up January 14, 2003
Reviewer: Robert Wood from Memphis, USA
Tom,

Thanks for verifying that the 'where 1=1' clause does in no shape, form,
fashion, manner, etc. affect the optimizer.. I will have no qualms in using this
from now on.

Thanks,

Robert

Rating: 5
Validation January 14, 2003
Reviewer: Lewis Cunningham from St Pete, FL, USA
I think instead of being used for validation, the developers said it was for
validation so they wouldn't have to spend a day explaining how and why they were
building dynamic queries.

Not only have I used the 1 = 1, I have also, depending on the skill level of the
person I was speaking to, said it was for validation (and occassionally for
better performance).  In general it's an answer that makes people's eyes glaze
over and they walk away.  Only useful on people who won't then ask why.

Rating: 5
Oops January 14, 2003
Reviewer: Andrew Gilfrin from Dover Kent UK
Yeah sorry I meant to say the code was sort of meant to be pseudo code rather
than actual code. It just looks a bit PL/SQL like so it was a bit more
meaningfull for Oracle people.

I wanted to keep it simple to get the core message across rather than confusing
people more by introducing bind variables and the like ;)

Rating: 4
why is this happening April 21, 2003
Reviewer: umesh kasturi from bangalore india
select ''''||decode( TRIM(dname) ,'ACCOUNTS',10)||'''' FROM DEPT

the query returns '10'
'10'

select * from dept
WHERE TO_CHAR(DEPTNO) IN (
select ''''||decode( TRIM(dname) ,'ACCOUNTS',10)||'''' FROM DEPT
)

no rows returned ??

thanks in advance

Followup:
what does

select * from dept where to_char(deptno) in ( '''10''' )

return -- that is what the second query is.  the first query is

select '''10''' from dept;

since deptno (a number) would never have quotes around it the second query
returns NOTHING.

perhaps you meant:

select * from dept where deptno in ( select decode( dname, 'ACCOUNTS', 10 ) from
dept )

Rating: 3
datatype returned by the decode April 22, 2003
Reviewer: andrew from ca, usa
I guess it's not obvious that the decode would return a number, but it's easy to
confirm:

SQL> create or replace view v1 as ( select decode( dname, 'ACCOUNTS', 10 )
deptno from dept);

View created.

SQL> descr v1
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 DEPTNO                                             NUMBER

SQL>

Followup:
it is obvious?

decode is documented to return the datatype of the FIRST returned datatype

decode( x, y, Z, .... )
              ^^ datatype of Z detmermines the return type.

Rating: 4
One more on that! April 23, 2003
Reviewer: DeeeBeee Crazeee from India
Hi Tom,

I'd used 'WHERE TRUE AND....' for that when I used to work in SQL Server...that
however doesn't work out in Oracle...I guess thats where 1=1 can be used.

I had another question in those lines. As a part of my filtering query, I need
to filter out records based on certain conditions if provided. Say for example,
if the user selects 'MANAGER' for designation, the the resultant should return
me managers, else all the records. My query goes something like...

SELECT ...
  FROM ....
 WHERE ....
   AND manager = NVL(l_mgr, manager)

This is returning me the correct results, but just wanted to check the overhead
it may have when l_mgr is NULL. The condition would be like 'manager =
manager'....is there any implications or is the condtion ignored?

thanks in advance

Followup:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279

      Rating:
      5
1=1  December 16, 2003
Reviewer:  Abdul
      Wahab Ahmed Shaikh  from India
The topic of 1=1 was extremely useful in terms that the concept was not covered
in any standard documentation and it really helps in maintability and ease of
adding predicates.

April 23, 2003 Tony -- Thanks for the question regarding "sqlldr
      - skipping columns", version 8.1.6

      You Asked
How do I import a comma delimited field (with variable length
fields) with 4 fields into a table with 3 fields - i.e. how do I
tell SQLLDR to ignore a field from each row in the import file?

Thanks!

      and we said...
see

http://osi.oracle.com/~tkyte/SkipCols/index.html
...
Review
      & Followup

      Rating: 3
Not much
      explanatory  July 19, 2002
Reviewer:  Ramesh
       from CA USA
My job is to load data using sqlldr, the flat file which is a pipe delimited
one.

As you mentioned I used the filler inthe contol file but it is not working. Do
you want me to add the filler in the table itself? I do not want to add any
columns to the existing table. How can I do it.

Can you please copy the same to me also. rmutyapu@yahoo.com

Thanks in advance for your reply.

          Followup:

"not working" -- not very descriptive there.

I can assure you -- filler works.  Maybe if you supply us a small example of

a) what you tried
b) what you expected
c) what actually happened

we can help you out.  I cannot "copy" you from this interface.

      Rating: 2
Ignore some
      fields in flat file by sqlldr  April 23, 2003

Reviewer:  Sujit Mondal  from USA
Suppose I have a flat file like
 a|b|c|d|e
 I want to load it table T from the above file in the following way:
 column1  a
 column2  c
 column3  e
that is I want to ignore 2nd and fourth field in the file , Is there any way to
do that?

          Followup:

Hmm, that does appear to be the same exact question that was asked originally.

Did you try reading my answer to it?  It'll answer your question as well.

April 23, 2003 Subhash -- Thanks for the question regarding "How
      to use a if statement within where clause?", version 0.0

      You Asked
I want to use if statement within where clause.

e.g

select * from mytable m

where col1 = {
        if col2 > sysdate then col2
         else
             sysdate
        end if
}

      and we said...
select *
  from m
 where col1 =
       case when col2 > sysdate then col2
            else sysdate
       end
/

or if case doesn't work -- as you gave me NO VERSION at all -- decode

select *
  from m
 where col1 = decode( sign( col2 - sysdate ), 1, col2, sysdate );

ops$tkyte@ORA920> create table t ( x date );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( sysdate-1 );

1 row created.

ops$tkyte@ORA920> insert into t values ( trunc(sysdate) );

1 row created.

ops$tkyte@ORA920> insert into t values ( sysdate+1 );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select case when x > trunc(sysdate) then x
  2              else trunc(sysdate)
  3             end,
  4             decode( sign(x-trunc(sysdate)), 1, x, trunc(sysdate) ),
  5             x
  6    from t
  7  /

CASEWHENX DECODE(SI X
--------- --------- ---------
18-MAR-03 18-MAR-03 17-MAR-03
18-MAR-03 18-MAR-03 18-MAR-03
19-MAR-03 19-MAR-03 19-MAR-03

Review &
      Followup

      Rating: 4
case
      operator  March 18, 2003
Reviewer:  Mikito
      Harakiri
Tom, why don't you encourage using case operator instead of decode in your
examples?

          Followup:

i didn't think I didn't??  I mean - case is "first" here with decode as a
fallback

I use decode when decode is easier.  I use case when it makes sense.

Not every release has case so I try to provide an equivalent decode when I
can...  Also, you have to hide case in PLSQL in 8i using dynamic sql or a view
and I heavily promote PLSQL -- so I want to make sure most people can use it.

      Rating: 4
Performance
      with CASE  March 18, 2003
Reviewer:
      A reader
Tom,

How and when a "CASE" is executed with in a statement? currently when using a
CASE i have serious performance issues even though the consistent gets are low?

Thanks,

          Followup:

that is like killing the messenger...

CASE is not at fault here -- lets see the actual example with a TKPROF.

      Rating: 5
Case in update
      statement  March 19, 2003
Reviewer:  Ram
       from India
i would like to extend this case operator usage in a subquery for the where
clause of an update statement but i'm not sure
1. if its possible.
2. if possible, how.

oracle version 8.1.5.

table users.
user_id
class
...<some other columns>

table profiles.
user_id
profile_id
...<a few other columns>

now based on the profile_id i would like to update the class column in the users
table. something like this is currently in mind:

update users
set class = 1
where user_id in ( select user_id
from profiles where profile_id IN ('x','y','z')
/

update users
set class = 2
where user_id in ( select user_id
from profiles where profile_id NOT IN ('x','y','z')
/

any ideas ?
Q3. is there any other way to do this in a single sql ?
thanks in advance !

          Followup:

case did not exist in 815, it was added in 816.

two ways -- if profiles has a primary key on user_id, the first works (else it
does not).  second works in any case:

ops$tkyte@ORA815> select * from users;

   USER_ID      CLASS
---------- ----------
         1
         2
         3

ops$tkyte@ORA815> select * from profiles;

   USER_ID P
---------- -
         1 x
         2 a

ops$tkyte@ORA815> update (select class, profile_id
  2            from users, profiles
  3           where users.user_id = profiles.user_id(+) )
  4    set class = decode( profile_id, 'x', 1, 'y', 1, 'z', 1, 2 )
  5  /

3 rows updated.

ops$tkyte@ORA815>
ops$tkyte@ORA815> select * from users;

   USER_ID      CLASS
---------- ----------
         1          1
         2          2
         3          2

ops$tkyte@ORA815> rollback;

Rollback complete.

ops$tkyte@ORA815> select * from users;

   USER_ID      CLASS
---------- ----------
         1
         2
         3

ops$tkyte@ORA815> update users
  2     set class = ( select nvl( max(1), 2 )
  3                     from profiles
  4                    where user_id = users.user_id
  5                      and profile_id in ( 'x', 'y', 'z' ) )
  6  /

3 rows updated.

ops$tkyte@ORA815> select * from users;

   USER_ID      CLASS
---------- ----------
         1          1
         2          2
         3          2

ops$tkyte@ORA815>

      Rating: 5
Great
      !  March 19, 2003
Reviewer:  ram  from
      india

      Rating: 5
Using Case in
      816  March 20, 2003
Reviewer:  Ram
       from India
How to do the same in 816 using CASE ? Just curious.

          Followup:

I answered that right at the beginning of my answer?

      Rating: 4
CASE and Remote
      DB and ORA-22804  April 23, 2003
Reviewer:
      Robert  from PA
Is it a fact that CASE does not work with db_link
in this fashion below ? thanks :

INSERT
INTO
emp@Remote_db
SELECT
EMPNO,
ENAME,
CASE
WHEN JOB = 'MANAGER'
THEN 'BULLY'
ELSE 'MINION' END JOB,
MGR,
HIREDATE,
SAL,
COMM,
DEPTNO
FROM emp
/

ORA-22804: remote operations not permitted on object tables or user-defined type
columns

          Followup:

it is a "no go" -- currently, fixed in the next release (10)

You can contact support and see if there is a fix that is backported to the
current releases.

April 23, 2003 David -- Thanks for the question regarding "Case
      sensitive searches", version 8.1.5

      You Asked
How can I perform queries against a varchar2 field that isn't
case sensitive?

If I have data like 'Dictionary' in the column, but I enter the
select like:

Select * from tab1 where col1 like 'dict%';

I get no rows found.

The SQL gets it's value from a web screen, so there is no way to
know how it will be entered.

      and we said...
Well, there are 2 things that pop into mind right away.

1) function based indexes.  You can create an index on the
UPPER(col1) so you can query:

   select * from tab1 where upper(col1) like upper('dict%');

and it'll use an index.  See

http://osi.oracle.com/~tkyte/article1/index.html
for some details on using that feature.

2) interMedia text.  I can create a text index which is case
insensitive on this varchar2 field.  I can then use the
contains() function on this text.  I can do word searches, stem
searches, wildcard searches, proximity searches and so on with
that.

see

http://technet.oracle.com/doc/inter.815/a67843/toc.htm
for details on that feature.
Review &
      Followup

      Rating: 5
Why Function
      Based Index doesn't work?  April 23, 2003

Reviewer:  Logan Palanisamy  from Santa Clara, CA USA

Tom,

I have a query joining four tables. On one of the columns I want to use
UPPER(column_name) condition using Function Based Indexes. But it doesn't seem
to work.

To show the progression, I run the same query three times with slight
modification.

1. WHERE address1 like '5200 Patrick%'
2. WHERE UPPER(address1) like '5200 PATRICK%'
3. WHERE UPPER(address1) like '5200 PATRICK%', with FBI enabled.

My questions are:
a)I was expecting query 3 to use FBI, but it is not. What am I missing?

b) In the USER_IND_COLUMNS, it is giving the system generated name(SYS_NC00028$)
as the column name for Function Based Indexes. Is there a way to get the actual
function used for defining the index (like upper(address1))?

c)Is there a way to increase the linesize of the explain plan output generated
by SQL*plus autotrace option? Currently the lines are folded making it kind of
difficult to read. Or Are TKPROF/'Explain plan for ..' the only other ways to
generate neatly formated output?

d) Is there anything else I can do to tune this query, with the constraint that
address1 column will be case insensitive in the where clause?

Thanks.

SQL> analyze table ADDRESS_DETAIL compute statistics for table for all indexes
for all indexed columns;

Table analyzed.

SQL> analyze table CC_AUTHORIZATION_DETAIL compute statistics for table for all
indexes for all indexed columns;

Table analyzed.

SQL> analyze table CONSUMER_PROFILE compute statistics for table for all indexes
for all indexed columns;

Table analyzed.

SQL> analyze table ORDER_HEADER compute statistics for table for all indexes for
all indexed columns;

Table analyzed.

SQL> select table_name, num_rows, last_analyzed  from user_tables
  2  where table_name in ('ADDRESS_DETAIL', 'CC_AUTHORIZATION_DETAIL',
'CONSUMER_PROFILE', 'ORDER_HEADER');

TABLE_NAME                       NUM_ROWS LAST_ANAL
------------------------------ ---------- ---------
ADDRESS_DETAIL                      95518 23-APR-03
CC_AUTHORIZATION_DETAIL             95951 23-APR-03
CONSUMER_PROFILE                    51576 23-APR-03
ORDER_HEADER                        85452 23-APR-03

QL> create index address_detail_address1 on address_detail(address1);

Index created.

SQL> create index address_detail_upper_address on
address_detail(upper(address1));

Index created.

SQL> select table_name, index_name, column_name, column_position cp
  2  from user_ind_columns
  3  where table_name in ('ADDRESS_DETAIL', 'CC_AUTHORIZATION_DETAIL',
'CONSUMER_PROFILE', 'ORDER_HEADER')
  4  order by table_name, index_name, column_position;

TABLE_NAME              INDEX_NAME                   COLUMN_NAME             CP
----------------------- ---------------------------- ---------------------- ---
ADDRESS_DETAIL          ADDRESS_DETAIL_ADDRESS1      ADDRESS1                 1
ADDRESS_DETAIL          ADDRESS_DETAIL_UPPER_ADDRESS SYS_NC00028$             1
ADDRESS_DETAIL          PK_ADDRESS_ID                ADDRESS_ID               1
CC_AUTHORIZATION_DETAIL NI_CAD_CC_ACCNUM             CC_ACCOUNT_NUMBER        1
CC_AUTHORIZATION_DETAIL NI_MOD_TRASID                MODULE_TRANSACTION_ID    1
CONSUMER_PROFILE        PK_CONSUMER_ID               CONSUMER_ID              1
ORDER_HEADER            FK_CONSUMER_ID               CONSUMER_ID              1
ORDER_HEADER            PK_CONSUMER_ORDER_ID         CONSUMER_ORDER_ID        1

8 rows selected.
SQL>
SQL> set autotrace traceonly
SQL>
SQL> SELECT
  2  oh.consumer_order_id order_id,
  3  oh.approval_status,
  4  ad1.LAST_NAME Bill_last_name,
  5  cp.EMAILID emailid,
  6  ad2.LAST_NAME Ship_last_Name,
  7  cad.PNREF
  8  FROM
  9  address_detail ad1,
 10  order_header oh,
 11  consumer_profile cp,
 12  cc_authorization_detail cad,
 13  address_Detail ad2
 14  where ad1.address1 like '5200 Patrick%'
 15  and oh.customer_type ='BTOC'
 16  and ad1.address_id = oh.BILL_ADDRESS_ID
 17  and oh.SHIP_ADDRESS_ID = ad2.Address_id
 18  and oh.consumer_id = cp.consumer_id
 19  and to_char(oh.consumer_order_id) = cad.MODULE_TRANSACTION_ID (+)
 20  and nvl(cad.INVOKING_MODULE,'B2C') not in ('SETTLEMENT') ;

235 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1064 Card=222557 Byt
          es=55194136)

   1    0   NESTED LOOPS (Cost=1064 Card=222557 Bytes=55194136)
   2    1     FILTER
   3    2       NESTED LOOPS (OUTER)
   4    3         NESTED LOOPS (Cost=528 Card=101 Bytes=16968)
   5    4           HASH JOIN (Cost=374 Card=154 Bytes=20020)
   6    5             TABLE ACCESS (BY INDEX ROWID) OF 'ADDRESS_DETAIL
          ' (Cost=3 Card=18 Bytes=1008)

   7    6               INDEX (RANGE SCAN) OF 'ADDRESS_DETAIL_ADDRESS1
          ' (NON-UNIQUE) (Cost=2 Card=18)

   8    5             TABLE ACCESS (FULL) OF 'ORDER_HEADER' (Cost=369
          Card=855 Bytes=63270)

   9    4           TABLE ACCESS (BY INDEX ROWID) OF 'CONSUMER_PROFILE
          ' (Cost=1 Card=51576 Bytes=1959888)

  10    9             INDEX (UNIQUE SCAN) OF 'PK_CONSUMER_ID' (UNIQUE)
  11    3         TABLE ACCESS (BY INDEX ROWID) OF 'CC_AUTHORIZATION_D
          ETAIL' (Cost=3 Card=95951 Bytes=4029942)

  12   11           INDEX (RANGE SCAN) OF 'NI_MOD_TRASID' (NON-UNIQUE)
           (Cost=1 Card=95951)

  13    1     TABLE ACCESS (BY INDEX ROWID) OF 'ADDRESS_DETAIL' (Cost=
          1 Card=95518 Bytes=3629684)

  14   13       INDEX (UNIQUE SCAN) OF 'PK_ADDRESS_ID' (UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          5  db block gets
       5076  consistent gets
          0  physical reads
          0  redo size
      17212  bytes sent via SQL*Net to client
       1322  bytes received via SQL*Net from client
         17  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        235  rows processed

SQL>
SQL> SELECT
  2  oh.consumer_order_id order_id,
  3  oh.approval_status,
  4  ad1.LAST_NAME Bill_last_name,
  5  cp.EMAILID emailid,
  6  ad2.LAST_NAME Ship_last_Name,
  7  cad.PNREF
  8  FROM
  9  address_detail ad1,
 10  order_header oh,
 11  consumer_profile cp,
 12  cc_authorization_detail cad,
 13  address_Detail ad2
 14  where upper(ad1.address1) like '5200 PATRICK%'
 15  and oh.customer_type ='BTOC'
 16  and ad1.address_id = oh.BILL_ADDRESS_ID
 17  and oh.SHIP_ADDRESS_ID = ad2.Address_id
 18  and oh.consumer_id = cp.consumer_id
 19  and to_char(oh.consumer_order_id) = cad.MODULE_TRANSACTION_ID (+)
 20  and nvl(cad.INVOKING_MODULE,'B2C') not in ('SETTLEMENT') ;

308 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=13763 Card=58894488
          Bytes=14605833024)

   1    0   MERGE JOIN (Cost=13763 Card=58894488 Bytes=14605833024)
   2    1     SORT (JOIN) (Cost=11190 Card=61658 Bytes=12948180)
   3    2       HASH JOIN (Cost=2999 Card=61658 Bytes=12948180)
   4    3         FILTER
   5    4           NESTED LOOPS (OUTER)
   6    5             NESTED LOOPS (Cost=1224 Card=559 Bytes=62608)
   7    6               TABLE ACCESS (FULL) OF 'ORDER_HEADER' (Cost=36
          9 Card=855 Bytes=63270)

   8    6               TABLE ACCESS (BY INDEX ROWID) OF 'CONSUMER_PRO
          FILE' (Cost=1 Card=51576 Bytes=1959888)

   9    8                 INDEX (UNIQUE SCAN) OF 'PK_CONSUMER_ID' (UNI
          QUE)

  10    5             TABLE ACCESS (BY INDEX ROWID) OF 'CC_AUTHORIZATI
          ON_DETAIL' (Cost=3 Card=95951 Bytes=4029942)

  11   10               INDEX (RANGE SCAN) OF 'NI_MOD_TRASID' (NON-UNI
          QUE) (Cost=1 Card=95951)

  12    3         TABLE ACCESS (BY INDEX ROWID) OF 'ADDRESS_DETAIL' (C
          ost=10 Card=4776 Bytes=267456)

  13   12           INDEX (RANGE SCAN) OF 'ADDRESS_DETAIL_UPPER_ADDRES
          S' (NON-UNIQUE) (Cost=2 Card=4776)

  14    1     SORT (JOIN) (Cost=2573 Card=95518 Bytes=3629684)
  15   14       TABLE ACCESS (FULL) OF 'ADDRESS_DETAIL' (Cost=238 Card
          =95518 Bytes=3629684)

Statistics
----------------------------------------------------------
       2711  recursive calls
        404  db block gets
     412589  consistent gets
       1045  physical reads
      51556  redo size
      18788  bytes sent via SQL*Net to client
       1657  bytes received via SQL*Net from client
         22  SQL*Net roundtrips to/from client
          0  sorts (memory)
          2  sorts (disk)
        308  rows processed

SQL>
SQL> alter session set query_rewrite_enabled=true;

Session altered.

SQL> alter session set query_rewrite_integrity=trusted;

Session altered.

SQL>
SQL> SELECT
  2  oh.consumer_order_id order_id,
  3  oh.approval_status,
  4  ad1.LAST_NAME Bill_last_name,
  5  cp.EMAILID emailid,
  6  ad2.LAST_NAME Ship_last_Name,
  7  cad.PNREF
  8  FROM
  9  address_detail ad1,
 10  order_header oh,
 11  consumer_profile cp,
 12  cc_authorization_detail cad,
 13  address_Detail ad2
 14  where upper(ad1.address1) like '5200 PATRICK%'
 15  and oh.customer_type ='BTOC'
 16  and ad1.address_id = oh.BILL_ADDRESS_ID
 17  and oh.SHIP_ADDRESS_ID = ad2.Address_id
 18  and oh.consumer_id = cp.consumer_id
 19  and to_char(oh.consumer_order_id) = cad.MODULE_TRANSACTION_ID (+)
 20  and nvl(cad.INVOKING_MODULE,'B2C') not in ('SETTLEMENT') ;

308 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=13763 Card=58894488
          Bytes=14605833024)

   1    0   MERGE JOIN (Cost=13763 Card=58894488 Bytes=14605833024)
   2    1     SORT (JOIN) (Cost=11190 Card=61658 Bytes=12948180)
   3    2       HASH JOIN (Cost=2999 Card=61658 Bytes=12948180)
   4    3         FILTER
   5    4           NESTED LOOPS (OUTER)
   6    5             NESTED LOOPS (Cost=1224 Card=559 Bytes=62608)
   7    6               TABLE ACCESS (FULL) OF 'ORDER_HEADER' (Cost=36
          9 Card=855 Bytes=63270)

   8    6               TABLE ACCESS (BY INDEX ROWID) OF 'CONSUMER_PRO
          FILE' (Cost=1 Card=51576 Bytes=1959888)

   9    8                 INDEX (UNIQUE SCAN) OF 'PK_CONSUMER_ID' (UNI
          QUE)

  10    5             TABLE ACCESS (BY INDEX ROWID) OF 'CC_AUTHORIZATI
          ON_DETAIL' (Cost=3 Card=95951 Bytes=4029942)

  11   10               INDEX (RANGE SCAN) OF 'NI_MOD_TRASID' (NON-UNI
          QUE) (Cost=1 Card=95951)

  12    3         TABLE ACCESS (BY INDEX ROWID) OF 'ADDRESS_DETAIL' (C
          ost=10 Card=4776 Bytes=267456)

  13   12           INDEX (RANGE SCAN) OF 'ADDRESS_DETAIL_UPPER_ADDRES
          S' (NON-UNIQUE) (Cost=2 Card=4776)

  14    1     SORT (JOIN) (Cost=2573 Card=95518 Bytes=3629684)
  15   14       TABLE ACCESS (FULL) OF 'ADDRESS_DETAIL' (Cost=238 Card
          =95518 Bytes=3629684)

Statistics
----------------------------------------------------------
       2728  recursive calls
        403  db block gets
     412591  consistent gets
       1045  physical reads
      51672  redo size
      18788  bytes sent via SQL*Net to client
       1657  bytes received via SQL*Net from client
         22  SQL*Net roundtrips to/from client
          0  sorts (memory)
          2  sorts (disk)
        308  rows processed

SQL>
SQL> spool off

          Followup:

read my referenced page above and work through those examples.  maybe you are
missing query_rewrite_enabled/integrity.

April 24, 2003 atul -- Thanks for the question regarding
      "Redundant Indexes", version 8.0.4

      You Asked
Sir,

I found from your redundant index script two indexes on a table which according
to script are redundant..(I am using RULE BASED OPTIMIZER)

TABLE:TEST

1)NONUNIQUE  INDEX_TEST                             INV_NO
                                                    INV_DATE

2)UNIQUE     PK_TEST                                 INV_NO

Is the index 1 IS REDUNDANT?
Can i safely drop it?

2)If i have a table name test with columns (A,B,C)
if i have a indexes
1)IND1 ON (A,B)
2)ind2 on (A,B,C)

and if i am using columns A,B in where clause for some jobs and A,B,C
for other jobs..
so can i drop IND1 ie ON (A,B)?

3)Do you have any definition on basis of which i can decide the redundant
indexes and drop them safely?

Thanks.
atul

      and we said...
It is impossible for me to say with 100% certainty.

First of all -- you will probably NOT be able to drop it straight away --
consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( inv_no number, inv_date
date, constraint pk_test primary key(inv_no) );

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index index_test on t(inv_no,
inv_date);

ops$tkyte@ORA817DEV.US.ORACLE.COM> set echo off
Datatypes for Table t

                               Data                 Data
Column Name                    Type                 Length      Nullable
------------------------------ -------------------- ----------- --------
INV_NO                         NUMBER                           not null
INV_DATE                       DATE                 7           null

Indexes on t

Index                          Is
Name                           Unique COLUMNS
------------------------------ ------ --------------------------------
INDEX_TEST                     No     INV_NO, INV_DATE
PK_TEST                        Yes    INV_NO

so, here I have your situation... two indexes.  If I try to drop the
PK_TEST:

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop index pk_test;
drop index pk_test
           *
ERROR at line 1:
ORA-02429: cannot drop index used for enforcement of unique/primary key

I cannot, so I drop the constraint instead:

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t drop constraint pk_test;
ops$tkyte@ORA817DEV.US.ORACLE.COM> set echo off
Datatypes for Table t

                               Data                 Data
Column Name                    Type                 Length      Nullable
------------------------------ -------------------- ----------- --------
INV_NO                         NUMBER                           null
INV_DATE                       DATE                 7           null

Indexes on t

Index                          Is
Name                           Unique COLUMNS
------------------------------ ------ --------------------------------
INDEX_TEST                     No     INV_NO, INV_DATE

and note the index goes away, now we add the constraint back:

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t add constraint pk_test primary
key(inv_no);

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1, sysdate) ;
ops$tkyte@ORA817DEV.US.ORACLE.COM> /
insert into t values ( 1, sysdate)
*
ERROR at line 1:
ORA-00001: unique constraint (OPS$TKYTE.PK_TEST) violated

we can see the primary key is there however:

ops$tkyte@ORA817DEV.US.ORACLE.COM> set echo off
Datatypes for Table t

                               Data                 Data
Column Name                    Type                 Length      Nullable
------------------------------ -------------------- ----------- --------
INV_NO                         NUMBER                           not null
INV_DATE                       DATE                 7           null

Indexes on t

Index                          Is
Name                           Unique COLUMNS
------------------------------ ------ --------------------------------
INDEX_TEST                     No     INV_NO, INV_DATE

the index isn't, the primary key kidnapped our other index as we can see with
this

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop index index_test;
drop index index_test
           *
ERROR at line 1:
ORA-02429: cannot drop index used for enforcement of unique/primary key

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

so, you MIGHT be able to safely drop this index but watch out for performance
changes in other queries downstream -- different mix of indexes, potentially
different mix of plans

I refuse to say "yes, you can drop those indexes without any negative impacts
whatsoever" cause the 1 in 100 rule will get me (you'll be the 1 out of 100
where a query plan is changed in a negative way).

As with ANY CHANGE, no matter how seemingly minor, do this in a test instance
and actually TEST everything.  If you do not, shame on you (and don't yell at me
later ;)

I would say offhand that if you have redunant non-unique indexes, they are
pretty safe.  Its the unique indexes I would take two or three looks at and test
before just getting rid of them (also if this is a read mostly, modify
infrequently system AND it's running just dandy - I would leave well enough
alone.  You won't make it run any faster and stand a chance of making it run
differently -- making people really mad at you)
Review &
      Followup

      Rating:
      4
Reader  April 07, 2002
Reviewer:
      A reader
Tom,

"
Index                          Is
Name                           Unique COLUMNS
------------------------------ ------ --------------------------------
INDEX_TEST                     No     INV_NO, INV_DATE

"

How can INDEX_TEST can still be non-unque. INV_NO is
unique because it is enforced by PK and hence
INV_NO,INV_DATE should be unique also. If INDEX_TEST *is*
non-unique, how will PK use this index

Thanks

          Followup:

We do not (and have not since 8.0) needed a UNIQUE index to enforce a primary /
unique constraint.

We only need an index with the primary/unique columns on the leading edge to
enforce uniqueness.

      Rating:
      4
redundant  April 08, 2002
Reviewer:
      gil  from UK
hello
        what is redundant index,do they have diff job than  regular indices?

          Followup:

Main Entry: re·dun·dant

Function: adjective
Etymology: Latin redundant-, redundans, present participle of redundare to
overflow -- more at REDOUND
Date: 1594

1 a : exceeding what is necessary or normal : SUPERFLUOUS b : characterized by
or containing an excess; specifically : using more words than necessary c :
characterized by similarity or repetition <a group of particularly redundant
brick buildings> d chiefly British : no longer needed for a job and hence laid
off

2 : PROFUSE, LAVISH

3 : serving as a duplicate for preventing failure of an entire system (as a
spacecraft) upon failure of a single component
- re·dun·dant·ly adverb

It is simply an index that is fully contained within some other index.  Eg: you
have a table t with columns A, b, C

you create indexes:

t(a)
t(a,b)
t(a,b,c)

the first two indexes are considered redundant as the third index contains them
fully.

      Rating:
      5
redundant  April 08, 2002
Reviewer:
      abeginner  from asia

      Rating: 5
used other
      index for enforcement of pk  September 02, 2002

Reviewer:  Jan
I want to use a new index for enforcement of pk on my table. Can I do that
without dropping the constraint? I have dependencies on that. Something like:
ALTER TABLE my_table USE INDEX my_new_index ON PRIMARY KEY

          Followup:

No, but then again, why do you CARE what index we use internally as it has no
bearing on your program or its performance?

      Rating: 5
Maybe
      because...  September 02, 2002
Reviewer:  Jan
      van Mourik  from Houston, TX, USA
Recently I had a similar problem, maybe it's because I'm a Jan too ;-)
I wanted to add an index consisting of the PK column and some additional
columns. So I'd end up with the PK index on column A, and the new index on
columns A, B, C. In that case, the PK index would be redundant, Oracle could use
my new index to enforce the PK constraint, and I could get rid of the current PK
index...

Another Jan

          Followup:

You would have to disable all foriegn keys that pointed to this, disable the
primary key, drop the index, reenable the primary key and then reenable the
fkeys.

You could do this disable/reenable with "rely novalidate" if you can ensure that
whilst you do this operation -- no one modifies the data.  then turning the
constraints on and off is very fast.  Caveat Emptor if someone slips in bad data
while you do it tho!

      Rating:
      3
  September 02, 2002
Reviewer:
      Andres  from Uruguay
Tom:

    Where Can i found your script to find redundant indexes?

Thanks

          Followup:

ops$tkyte@ORA920.US.ORACLE.COM> create table t ( x int, y int, z int );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM> create index t_idx1 on t(x);

Index created.

ops$tkyte@ORA920.US.ORACLE.COM> create index t_idx2 on t(x,y);

Index created.

ops$tkyte@ORA920.US.ORACLE.COM> create index t_idx3 on t(x,y,z);

Index created.

ops$tkyte@ORA920.US.ORACLE.COM> create index t_idx4 on t(x,z);

Index created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> column cols format a40
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select a.index_name || '(' || a.cols || ')'
cols,
  2         b.index_name || '(' || b.cols || ')' cols
  3    from (select index_name,  table_name,
  4                 rtrim(
  5                       max(decode(column_position,1,column_name,null))
||','||
  6                   max(decode(column_position,2,column_name,null)) ||','||
  7                   max(decode(column_position,3,column_name,null)) ||','||
  8                   max(decode(column_position,4,column_name,null)) ||','||
  9                   max(decode(column_position,5,column_name,null)) ||','||
 10                   max(decode(column_position,6,column_name,null)) ||','||
 11                   max(decode(column_position,7,column_name,null)) ||','||
 12                   max(decode(column_position,8,column_name,null)) ||','||
 13                   max(decode(column_position,9,column_name,null)) ||','||
 14                   max(decode(column_position,10,column_name,null)) , ',' )
cols
 15          from user_ind_columns
 16         group by table_name, index_name ) a,
 17         (select index_name,  table_name,
 18                 rtrim(
 19                       max(decode(column_position,1,column_name,null))
||','||
 20                   max(decode(column_position,2,column_name,null)) ||','||
 21                   max(decode(column_position,3,column_name,null)) ||','||
 22                   max(decode(column_position,4,column_name,null)) ||','||
 23                   max(decode(column_position,5,column_name,null)) ||','||
 24                   max(decode(column_position,6,column_name,null)) ||','||
 25                   max(decode(column_position,7,column_name,null)) ||','||
 26                   max(decode(column_position,8,column_name,null)) ||','||
 27                   max(decode(column_position,9,column_name,null)) ||','||
 28                   max(decode(column_position,10,column_name,null)) , ',' )
cols
 29          from user_ind_columns
 30         group by table_name, index_name ) b
 31   where a.table_name = b.table_name
 32     and a.index_name <> b.index_name
 33     and a.cols like b.cols || '%'
 34  /

COLS                                     COLS
----------------------------------------
----------------------------------------
T_IDX2(X,Y)                              T_IDX1(X)
T_IDX3(X,Y,Z)                            T_IDX1(X)
T_IDX4(X,Z)                              T_IDX1(X)
T_IDX3(X,Y,Z)                            T_IDX2(X,Y)

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

You still have to figure out for yourself that you only need t_idx3 here!

(if you have more then 10 columns per index, this won't find them - but then
again you have bigger fish to fry in that case!)

      Rating: 5
Possible bug in
      redundant indexes script???  September 02, 2002

Reviewer:  SB  from CA
Hi Tom,

I did the following test with a slight change in column names and here is the
result:

SQL> create table t ( my_new_column number(38), my_new_column_updated number)
tablespace tools;

Table created.

SQL> create index t_idx1 on t(my_new_column);

Index created.

SQL> create index t_idx2 on t(my_new_column_updated);

Index created.

SQL> select a.index_name || '(' || a.cols || ')'
cols,
           b.index_name || '(' || b.cols || ')' cols
      from (select index_name,  table_name,
                   rtrim(
                         max(decode(column_position,1,column_name,null))
||','||
                     max(decode(column_position,2,column_name,null)) ||','||
                     max(decode(column_position,3,column_name,null)) ||','||
                     max(decode(column_position,4,column_name,null)) ||','||
                     max(decode(column_position,5,column_name,null)) ||','||
                    max(decode(column_position,6,column_name,null)) ||','||
                    max(decode(column_position,7,column_name,null)) ||','||
                    max(decode(column_position,8,column_name,null)) ||','||
                    max(decode(column_position,9,column_name,null)) ||','||
                    max(decode(column_position,10,column_name,null)) , ',' )
cols
           from user_ind_columns
          group by table_name, index_name ) a,
          (select index_name,  table_name,
                  rtrim(
                        max(decode(column_position,1,column_name,null))
||','||
                    max(decode(column_position,2,column_name,null)) ||','||
                    max(decode(column_position,3,column_name,null)) ||','||
                    max(decode(column_position,4,column_name,null)) ||','||
                    max(decode(column_position,5,column_name,null)) ||','||
                    max(decode(column_position,6,column_name,null)) ||','||
                    max(decode(column_position,7,column_name,null)) ||','||
                    max(decode(column_position,8,column_name,null)) ||','||
                    max(decode(column_position,9,column_name,null)) ||','||
                    max(decode(column_position,10,column_name,null)) , ',' )
cols
           from user_ind_columns
          group by table_name, index_name ) b
    where a.table_name = b.table_name
      and a.index_name <> b.index_name
      and a.cols like b.cols || '%'
;

COLS
----------------------------------------
COLS
----------------------------------------
T_IDX2(MY_NEW_COLUMN_UPDATED)
T_IDX1(MY_NEW_COLUMN)

Which is not truly redundant index, right?? The indexes are on two different
columns. Pls clarify..

          Followup:

yup, never said it was "perfect".  It is a report you can look at AND AS A HUMAN
BEING decide what, if anything, needs be done.

      Rating: 3
Please look at
      the original question again.  April 24, 2003

Reviewer:  wayne  from Philadelphia, PA
Originally it asked:

"
1)NONUNIQU INDEX_TEST                          INV_NO
                                               INV_DATE
2)UNIQUE     PK_TEST                           INV_NO

Is the index 1 [INDEX_TEST] IS REDUNDANT?
Can i safely drop it?
"

Since the PK_TEST already enforces the uniquess,
do we really need the the INDEX_TEST index again.
Which index does the optimizer (either rule or cost) suppose to use? [I've run
few tests, and it appears
the Optimizer always uses the PK index]

Thanks.

          Followup:

run this query:

select inv_no, inv_date from t where inv_no = :x

now what index does it use.....  that index may be needed for performance
reasons, the index that might be redundant here would be PK_TEST

April 24, 2003 Margaret -- Thanks for the question regarding
      "cron job doesn't work", version 8.0.5

      You Asked
Dear Tom:
I have tried to put this script(test01.sh) to the crontab but it doesn't work.
crontab-e
16 19 * * * /tiadata/iaoracle/test01.sh

I am running this job as a dba user,
iaoracle(iaoracle:!:203:201::/tiadata/iaoracle:/usr/bin/ksh)- from /etc/passwd.

This is test01.sh:
sqlplus -s system/manager << EOF
start  /tiadata/iaoracle/test_back.sql
exit
EOF

Where test_back.sql is just:
alter tablespace  users begin backup;
 !cp /tiadata/iaoracle/8.0.5/oradata/IAORCL/users01.dbf /liadata/
alter tablespace users end backup;

But I got eorror message:
/tiadata/iaoracle/test01.sh: sqlplus:  not found

Here is some of my env settings:
$ echo $PATH
/tiadata/iaoracle/8.0.5/bin:.:/tiadata/iaoracle/8.0.5/lib:/usr/ccs/bin:/usr/bin:
/etc:/usr/sbin:/usr/ucb:/usr/bin/X11:/sbin:/usr/sbin/cluster

$ echo $ORACLE_HOME
/tiadata/iaoracle/8.0.5
$ echo $ORACLE_SID
IAORCL

Please help me in this matter
Thank you very much!

      and we said...
cron runs jobs in a rarified environment for security purposes.

You generally need to set the path, oracle home, oracle sid, etc in the script.

eg:

#!/bin/csh -vx

setenv ORACLE_SID ora92
setenv ORACLE_HOME /export/home/ora920
set path=( $path $ORACLE_HOME/bin )

sqlplus / <<EOF
set echo on
exec web\$sb.refreshdocs.do;
exit
EOF

Review &
      Followup

      Rating: 3
A standard korn
      shell solution  December 09, 2002
Reviewer:
      Fred
Sorry for breaking in...

I think Margaret would be better-off following this solution, which is a
standard practice in korn shell when running crontab tasks.

She is using ksh, not csh -- see /etc/passwd.

Just prepend to the beginning of your script a call to both /etc/profile and
~/.profile:

. /etc/profile
. ~/.profile

"dot" + "blank" + "filename" executes "filename" in your current shell, without
spawning a new process.

As Tom said, a crontab call to your script does not read your environment
settings, as a login does (PATH, ORACLE_SID, ORACLE_HOME, LD_LIBRARY_PATH, etc).

      Rating:
      5
sqlldr  December 10, 2002
Reviewer:
      A reader
Tom. can you tell me exactly what environment variables do I need to set

I want to run sqlldr
I have set $ORACLE_SID,$ORACLE_HOME,$LD_LIBRARY_PATH,$ORACLE_BASE
variable and $PATH
but it  says can not load the

error while loading shared libraries: libclntsh.so.8.0: cannot load shared
object file:

please help

TIA

          Followup:

then your ld_library_path wasn't set properly.

or you set it but then ran sqlldr in a sub-shell that didn't inherit that.

      Rating: 2
cron job
      doesn't work  April 23, 2003
Reviewer:
      Alvin  from Philippines
I made a similar unix script that takes hot backups per tablespaces.

I thought that CRON would do it in a serial manner eg.

   alter tablespace tbs_1 begin backup (ran via sqlplus tbs1_beg_bkup)
   cp files to intended directory
   alter tablespace tbs_1 end backup (ran via sqlplus tbs1_end_bkup)

   alter tablespace tbs_2 begin backup
   cp files to intended directory
   alter tablespace tbs_2 end backup

serial meaning wait for tbs_1 to be backed up before proceeding to tbs_2 .

expecting NO 2 tablespace in v$backup to be 'active' at the same time. To my
surprise, i saw 2 of the tbs being 'active' at the same time.... cron seems to
have run all the script serially but not waiting for the former line to finish
before it begins to run the next line.

how do i go about this ?

          Followup:

Umm, how about one script that serially does tablespace after tablespace.

      Rating: 4
cron job
      doesn't work  April 24, 2003
Reviewer:
      Alvin  from Philippines
say if i put them in a file like hotbk.scr and i want cron to run it every 8:01
am on sundays and wed

1 8 * * 0,3 . .profile;hotbk.scr

<<contents of hotbk.scr>>
# SYSTEM TABLESPACE
sqlplus /nolog @/scr_hotbk/beg_system.sql <-- containts alter tablespace system
begin backup...
echo ________________________________
echo .... archiving SYSTEM Tablespace
date
echo ________________________________
cp /oracle/oradata/system01.dbf /hotbk
date
sqlplus /nolog @/scr_hotbk/end_system.sql <-- end backup

# USER TABLESPACE
sqlplus /nolog @/scr_hotbk/beg_users.sql
echo ________________________________
echo .... archiving USER Tablespace
date
echo ________________________________
cp /oracle/oradata/users01.dbf /hotbk
date
sqlplus /nolog @/scr_hotbk/end_users.sql

<<end >>

would this backup the system tablespace first then the user tablespace in a
serial manner ? I've by told by one of our admin that CRON runs them serially
but it doesn't wait for the earlier command to finish eg.

the copy command might not have finish its job then the END BACKUP script will
run... is this true ?

          Followup:

if that is a single script and there are no "&" in it to run things in the
background, cron will run ONE script and it will execute sequentially.

the cp will not return control until it is done copying. (no ampersand, it is a
foreground command).

system will be backed up entirely before users is.

this cron job is "safe", someone either hasn't told the admin the entire story
or they are confused.

However, I can suggest you want to use a tool like RMAN to do this instead of
your own script.  Tell me -- what happens when you add "foobar" tablespace to
your database.  Or you add a file.. Or .... whatever.  You forget to update this
script and bamm -- you are dead.  no backups.

You need to drive your backups off of the data dictionary -- so you backup
everything.  RMAN is the tool you want to look at.  You don't even have to put
the database in backup mode for RMAN.

      Rating: 5
Thanks for the
      clarification  April 29, 2003
Reviewer:
      Alvin  from Philippines
Thanks for the clarification!

Great book by the way ! (it refuted some of my supposedly "great" ideas before)

April 24, 2003 -- Thanks for the question regarding "Tablespace
      Type Change", version 817

      You Asked
Hi,
  I am very Junior DBA.
  1)Is it possible to change a DMT to LMT. How ? Also vice versa.
  2)In DMT we can add multiple datafiles to a tablespace. Why do we do that if
max extent is unlimited. HOw/When does Oracle use the second Datafile.
  If automatic allocation is on for LMT will Oracle not keep increasing the
first datafile and eventually may run out of space. HOw to stop that.
/*
  Anxiously waiting for your NEW BOOK, Its like Star Wars movie is coming up and
we see huge Queue waiting outside the theatre to buy the tickets.
*/

Thanx

      and we said...
1) it is but it would be 100% advised against.  You do not get the major
benefits -- you get a sloppy LMT, instead of a nice clean one.

The best approach is:

a) create a new lmt
b) use alter table move and alter index rebuild to MOVE the objects from the DMT
to the LMT
c) drop the DMT

see the DBMS_SPACE_ADMIN package, it has procedures tablespace migrate from
local and tablespace migrate to local

2) datafiles and extents are really not totally related to eachother -- other
then an extent will be found in a single file.

Datafiles are how you add space to a tablespace.
Extents are how that space is doled out to segments that need it.

Why more then one datafile?

o to spread IO out across more then one device.  put a file on /dev1 /dev2 /dev3
and a table will be spread out over those three devices

o to exceed the max size of a file in your OS.  Maybe you can only use files
upto 2gig in size -- well, that would be the largest segment if we could not use
more then one file.

Oracle tends to allocate space in a tablespace using the datafiles in a round
robin fashion (file1, file2, file3).  The exception to this appears to be a
locally managed tablespace with system allocated (not uniform) extents.  Here
Oracle will use up the first file then move onto the second file.

If the first file cannot extend (hit its max size) Oracle will move onto the
second file naturally.

As for the new book -- check out the website accessible from my homepage to read
the sample chapters there.  Just like movie trailers.   That way you can see if
it is something you really want or not.
Review &
      Followup

      Rating: 5
Excellant as
      always  March 08, 2003
Reviewer:
      A reader

      Rating: 5
If you use
      dbms_space_admin...  March 08, 2003
Reviewer:
      Connor McDonald  from Perth, Australia
Just a quick note if you choose the quick option to migrate, you may want to set
the MINIMUM EXTENT clause to get the conversion you would expect.  Example:

SQL> create tablespace blah
  2  datafile 'G:\ORA9I\ORADATA\DB9\BLAH.DBF' size 10m reuse
  3  extent management dictionary;

Tablespace created.

SQL> create table t1 ( x number ) storage ( initial 400k) tablespace blah;

Table created.

SQL> create table t2 ( x number ) storage ( initial 800k) tablespace blah;

Table created.

SQL> create table t3 ( x number ) storage ( initial 1200k) tablespace blah;

Table created.

SQL> select bytes from dba_extents where tablespace_name = 'BLAH';

       BYTES
------------
     409,600
     819,200
   1,228,800

--
-- So at this stage, one would imagine that the unit size for a conversion
-- to a locally managed tablespace is 50 ( 50 * 8192 = 400k ).  But if we try...
--

SQL> exec dbms_space_admin.TABLESPACE_MIGRATE_TO_LOCAL('BLAH',50);
BEGIN dbms_space_admin.TABLESPACE_MIGRATE_TO_LOCAL('BLAH',50); END;

*
ERROR at line 1:
ORA-03241: Invalid unit size
ORA-06512: at "SYS.DBMS_SPACE_ADMIN", line 0
ORA-06512: at line 1

--
-- But if we set the minimum extent...
--

SQL> alter tablespace blah minimum extent 400k;

Tablespace altered.

SQL> exec dbms_space_admin.TABLESPACE_MIGRATE_TO_LOCAL('BLAH',50);

PL/SQL procedure successfully completed.

SQL>

      Rating: 5
Thanx Connor
      For your Example  March 09, 2003
Reviewer:
      A reader

      Rating: 3
What Oracle
      Does Internally ..  March 09, 2003
Reviewer:
      Jagjeet Singh
Dear Sir..

in DMT oracle maintain Freelists but in LMT Oracle Store in Bitmap Segments ...
Oracle provide this Package DBMS_SPACE_ADMIN..
Under which conditions u sugeest to use this package
migrate from and migrate to .

what oracle does internally .. will it migrate existing objects defination in
bitmaps or vice versa
...

          Followup:

DMT and LMT's both use freelists UNLESS You are using automatic segment space
management which is NOT a locally managed tablespace -- it is a brand new way of
managing storage.

Freelists are used in DMTs and LMTs by default.

I would not use dbms_space_admin to migrate tablespaces to/from DMT and LMT as I
stated.  I would move objects from a DMT to a LMT and never look back.

      Rating: 4
question
      regarding datafiles..  March 09, 2003
Reviewer:
      SV  from Virginia, USA
What do suggest regarding datafiles in a tablespace. I mean one big datafile(say
10Gb) or 5 datafiles each 2Gb in a tablespace. System having RAID 5.

          Followup:

depends...

what are your recovery requirements?  assumming 8i and no block level recovery
(9i feature), how long does it take to restore and recovery a 10 gig file vs 2
gig on your system?

do you have the tools to deal easily with 10 gig files?  or just 2 gig files?

do you have a need for a really big database (the bigger the db, the bigger the
files)

5 datafiles at (2gig - 1meg + 64k) wouldn't be terrible (think locally managed
tablespace, 64k for bitmap, rest for data, keep it under the magic 2gig number
instead of just over)

or

1 datafile at 10gig+64k

      Rating: 3
How to put a
      file across devices?  March 10, 2003
Reviewer:
      Jeff  from AZ, USA
Tom,

In Windows, I create a tablespace:
SQL> create tablespace blah (
  datafile1 'G:\ORA9I\ORADATA\DB9\BLAH.DBF' size 10m reuse,
  datafile2 'G:\ORA9I\ORADATA\DB9\BLA2.DBF' size 10m reuse)
  extent management dictionary;

How to accomplish what you said?
"o to spread IO out across more then one device.  put a file on /dev1 /dev2
/dev3 and a table will be spread out over those three devices"

Could you give more details?

Thanks a lot.

          Followup:

huge mistake "extent management dictionary" -- don't go there.

just create a table and allocate extents to it, watch where they go.  they'll
round robin between the disks if you use  a DMT or LMT with uniform extents.  If
you use a LMT with system managed extents -- it'll fill one file up and then the
next file.

      Rating: 4
Don't use
      Migrate to Local very disappointing.  March 28, 2003

Reviewer:  Sean  from Fresno, CA USA
I am in the process of writing converts to move our customers to 9i from 8i.  In
Oracle's conversion document, it mentions using DBMS_SPACE_ADMIN to migrate the
system tablespace to be locally managed.  I had planned to use this utility on
all of my tablespaces until I read this thread.  The process of doing a full
export, and re-import would be too costly for most of our customers, so this
news is very disappointing to me.  Any chance the functionality of this
procedure has improved in 9i, and we could consider using it?

Along the same lines I was interested in changing my objects to use automatic
segment-space management.  From what I have read so far, looks like I would have
to recreate each of them.

          Followup:

who said export/import?

alter table MOVE
alter index rebuild

The problem is -- YOUR EXISTING data isn't "uniform", you have wierd sized
extents.  The beauty of the LMT is -- this isn't the case!

If you take a DMT and just migrate it -- you get bitmaps but funky extents of
random sizes (since that is what you have).

So, my advice is -- if they are in dmts, leave em be OR move them to a nice LMT.

With ASSM -- you need to move, not recreate, them.

This can all be done ONLINE with dbms_redefinition as well.

      Rating: 5
Smart
      Sort  March 28, 2003
Reviewer:  Logan
      Palanisamy  from Santa Clara, CA USA
Here is further proof that, it is actually sorting only the the firtst N (e.g
10, 100, 150) records, replacing (1000 - N) as they are retrieved to get the top
N records

  1* select * from (select * from t order by object_name) where rownum < 10
SQL> /

9 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   COUNT (STOPKEY)
   2    1     VIEW
   3    2       SORT (ORDER BY STOPKEY)
   4    3         TABLE ACCESS (FULL) OF 'T'

Statistics
----------------------------------------------------------
          0  recursive calls
         18  db block gets
        340  consistent gets
        334  physical reads
          0  redo size
      18932  bytes sent via SQL*Net to client
        424  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          9  rows processed

SQL> c/10/100/
  1* select * from (select * from t order by object_name) where rownum < 100
SQL> /

99 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   COUNT (STOPKEY)
   2    1     VIEW
   3    2       SORT (ORDER BY STOPKEY)
   4    3         TABLE ACCESS (FULL) OF 'T'

Statistics
----------------------------------------------------------
          0  recursive calls
         18  db block gets
        340  consistent gets
        334  physical reads
          0  redo size
     204516  bytes sent via SQL*Net to client
       1090  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         99  rows processed

SQL> c/100/150/
  1* select * from (select * from t order by object_name) where rownum < 150
SQL> /

149 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   COUNT (STOPKEY)
   2    1     VIEW
   3    2       SORT (ORDER BY STOPKEY)
   4    3         TABLE ACCESS (FULL) OF 'T'

Statistics
----------------------------------------------------------
          0  recursive calls
         20  db block gets
        340  consistent gets
        372  physical reads
          0  redo size
     307579  bytes sent via SQL*Net to client
       1423  bytes received via SQL*Net from client
         11  SQL*Net roundtrips to/from client
          0  sorts (memory)
          1  sorts (disk)
        149  rows processed

If all the 1000 records were sorted, all the three cases should have been "disk"
sorts. In the examples above, only when I want the TOP150, it does disk sort.
When I want top 10 or 100, it does memory sort proving that it is sorting the
first 10 or 100, replacing the sorted list with the remaining only when they are
greater.

      Rating: 5
Sorry to post
      the "Smart Sort" review in the wrong thread  March 28, 2003

Reviewer:  Logan Palanisamy  from Santa Clara, CA USA

Sorry !!!

I meant to post the above review at

http://asktom.oracle.com/pls/ask/f?p=4950:8:197693925536939743::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:76812348057,

      Rating: 3
"Tablespace
      Type Change", version 817  April 03, 2003

Reviewer:  Mark Griffin  from Huntsville, AL

If an organization utilizes Raid 5 putting datafiles onto different devices is a
moot point ?

Unless of course regardless of Raid 5 you still may see performance increase if
you spread datafiles across different logical directories ?

BTW, thank you for this site. You sir in my opinion are the most if not the most
knowledgable Oracle person that I've seen..

          Followup:

"If an organization utilizes Raid 5 putting datafiles onto different devices is
a
moot point ? "

yes, no, maybe, sometimes....  It depends.  How was it laid out.  do you have 25
devices in 5 sets of 5?  so you have /d01, /d02, ..., /d05.  If so, putting
datafiles onto different logical devices would matter -- would not want
everything under /d01 would you?

Now, if you take the same 25 physical devices and get just one mount point --
/d/, then yes, it is moot from our perspective - the guys with the LVM software
would have to worry about making things hum nicely.

(and I've seen lots of people who know more about Oracle then I...  Or at least
we all go deep in different areas.  I'll never take on Jonathan Lewis with
regards to bitmapped index, or Steve Adams when it comes to latching and some of
the other internals...  And they probably wouldn't take me on in a tricky SQL
writing contest...)

      Rating:
      3
  April 16, 2003
Reviewer:
      A reader
"yes, no, maybe, sometimes....  It depends.  How was it laid out.  do you have
25
devices in 5 sets of 5?  so you have /d01, /d02, ..., /d05.  If so, putting
datafiles onto different logical devices would matter -- would not want
everything under /d01 would you?  "

Understood...I believe I knew this all along, just had to think about it for a
moment :)

(and I've seen lots of people who know more about Oracle then I...  Or at least
we all go deep in different areas.  I'll never take on Jonathan Lewis with
regards to bitmapped index, or Steve Adams when it comes to latching and some of

the other internals...  And they probably wouldn't take me on in a tricky SQL
writing contest...)

You seem to have most All-round Oracle knowledge...

P.S. Bought your book(not beginners the other one), Good book!...

At some point are/would you consider doing same type of book for Oracle9i ?

Again thanks for site.

          Followup:

I'm doing an Oracle "best practice" sort of book now (look for it in
august'ish).

If I get the rights back to expert 1/1 Oracle -- I will be very tempted to do a
10i version -- inclusive of 9i -- in the future.  right now the book is in limbo
with the demise of WROX

      Rating: 5
DMT to LMT
      using Export/Import - Need your comments  April 22, 2003

Reviewer:  Saminathan Seerangan  from CA,USA

Dear Tom, You said the best approach to convert DMT into LMT is

a) create a new lmt
b) use alter table move and alter index rebuild to MOVE the objects from the DMT

to the LMT
c) drop the DMT

I completely understand, but we have huge number of tables/indexes but in terms
of data size, it is less. Will it be okay if use
1. create LMT
2. export data from DMT
3. import data into LMT
4. drop DMT

instead of writing so many ALTER TABLE/INDEX command to move the data from one
TS to another?

Expecting your valuable comments?

          Followup:

test it on a test system first....

the steps would be:

1) exp
2) drop objects
3) drop tablespace
4) create tablespace(s)
5) imp

step #2 is the "tricky step" -- if step #5 fails -- you are going to backups to
recover.

I would much rather automate an alter table move using scripts generated right
from the data dictionary.

      Rating: 5
"Alter table
      move" with LONG datatype- Version 8.1.7  April 24, 2003

Reviewer:  Saminathan Seerangan  from CA,USA

Dear Tom, Thanks for your response..

Another reason for NOT using "ALTER TABLE MOVE" is
"ORA-00997: illegal use of LONG datatype"

I tried moving a table containing long data type into a different
tablespace, and I got this error

SQL> ALTER TABLE HR.HR_ALL_ORGANIZATION_UNITS
move STORAGE(INITIAL 512k next 1M) tablespace tab_move;

--*********************************************
ALTER TABLE HR.HR_ALL_ORGANIZATION_UNITS move STORAGE(INITIAL 512k next 1M)
*
ERROR at line 1:
ORA-00997: illegal use of LONG datatype
--********************************************

Expecting your comments Tom,

          Followup:

what more can I say -- you cannot do that.  you'll have to come up with some
other method (my option would be to stop using the long, use a CLOB and this
will not be an issue)

You can either

o take the "risk" of the exp/imp -- test it, make sure you have a backup first
o use sqlplus copy
http://asktom.oracle.com/~tkyte/Misc/MoveLongs.html
to copy the data to another table, drop old table,
rename new table

and don't be using storage parameters with LMTs at all.

      Rating: 5
This totally
      blows!!  April 24, 2003
Reviewer:  Terry
       from Miami, FL
<<I'm doing an Oracle "best practice" sort of book now (look for it in
august'ish).

If I get the rights back to expert 1/1 Oracle -- I will be very tempted to do a
10i version -- inclusive of 9i -- in the future.  right now the book is in limbo

with the demise of WROX>>

Who would have ever thought anything so good could have gone so wrong...  As a
Consulting Oracle DBA I "give away" copies of Expert One on One to Customers
because it makes my life so much easier.  I often wondered why this book did not
come out on Oracle Press?

          Followup:

It didn't come out from Oracle Press cause WROX approached me with the idea and
I liked the guy I was working with there.  It was a "first come, first serve"
sort of thing.

      Rating: 5
Most Oracle
      Press books are  April 24, 2003
Reviewer:
      A reader
Good for nothing , just high price trash.I dont know why ?
They will never discuss practical stuff as Tom does in his books.
  Its like reading the Oracle manuals it just a subset of it. Ment for book
worms.

April 24, 2003 Jack -- Thanks for the question regarding
      "initializing a record", version 8.0.5

      You Asked
I have a record type defined for a PL/SQL program. It contains
about 30 fields of different types - varchar2 and number.

I am using this record type to pass information to different
functions within the package - (add, update, delete) functions.

Is there a quick way to re-initialize the record without having
to specify each "rec.field = <initialize>"?

I suppose i could create an initialize function and invoke that
when needed, but if ORACLE has a built-in method of initialization,
that would be preferred.

Please advise. Thanks,

      and we said...
just declare a "init record" record variable and assign it:

ops$tkyte@ORA920> declare
  2      type rec is record
  3      (  a number  := 55,
  4         b date    := to_date('01-jan-2003','dd-mon-yyyy'),
  5         c varchar2(25) := 'Hello World'
  6      );
  7
  8      l_my_rec      rec;
  9      l_default_rec rec;
 10  begin
 11
 12      l_my_rec.a := 123;
 13      l_my_rec.b := sysdate;
 14      l_my_rec.c := 'bye now';
 15
 16      dbms_output.put_line
 17      ( l_my_rec.a || ',' || l_my_rec.b || ',' || l_my_rec.c );
 18
 19      l_my_rec := l_default_rec;
 20
 21      dbms_output.put_line
 22      ( l_my_rec.a || ',' || l_my_rec.b || ',' || l_my_rec.c );
 23  end;
 24  /
123,24-APR-03,bye now
55,01-JAN-03,Hello World

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating: 5
Simple and
      concise  April 24, 2003
Reviewer:  Jack

this is a simple and concise solution. It didn't occur to
me.
reminds me of what my college computer science professor used to say ... "When
you hear hoofbeats, think horses, and not zebras." .... the simplest solution is
usually the best.

thanks,

      Rating:
      5
  April 24, 2003
Reviewer:
      A reader

April 24, 2003 Sandy -- Thanks for the question regarding
      "Differences between two tables in a Replicated Environment", version
8.1.7

      You Asked
How I can rectify the differences between two tables in a Replicated
Environment.
I just simply wants to make sure if my two tables are in sync in a Replicated
Environment.

      and we said...
dbms_rectifier_diff does that:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76936/dbms_rec.htm#999107

April 24, 2003 Yakgna -- Thanks for the question regarding
      "oracle data to xml file", version 9.0.1

      You Asked
Tom,

can you please give me a sample pl/sql program probably using emp, dept and
generating an xml file.

in which version of oracle is it possible?.

i wanted it to be written to a new xml file

Thanks for all your help

      and we said...
8i and up

you can write it, spool it, whatever it however you like

scott@ORA920> select dbms_xmlgen.getxml
  2         ( 'select deptno, dname, cursor( select *
  3              from emp
  4             where emp.deptno = dept.deptno ) EMP_DATA
  5              from dept' )
  6    from dual
  7  /

DBMS_XMLGEN.GETXML('SELECTDEPTNO,DNAME,CURSOR(SELECT*FROMEMPWHEREEMP.DEPTNO=DEPT
--------------------------------------------------------------------------------
<?xml version="1.0"?>
<ROWSET>
 <ROW>
  <DEPTNO>10</DEPTNO>
  <DNAME>ACCOUNTING</DNAME>
  <EMP_DATA>
   <EMP_DATA_ROW>
    <EMPNO>7782</EMPNO>
    <ENAME>CLARK</ENAME>
    <JOB>MANAGER</JOB>
    <MGR>7839</MGR>
    <HIREDATE>09-JUN-81</HIREDATE>
    <SAL>2450</SAL>
    <DEPTNO>10</DEPTNO>
   </EMP_DATA_ROW>
   <EMP_DATA_ROW>
    <EMPNO>7839</EMPNO>
    <ENAME>KING</ENAME>
    <JOB>PRESIDENT</JOB>
    <HIREDATE>17-NOV-81</HIREDATE>
    <SAL>5000</SAL>
    <DEPTNO>10</DEPTNO>
   </EMP_DATA_ROW>
   <EMP_DATA_ROW>
    <EMPNO>7934</EMPNO>
    <ENAME>MILLER</ENAME>
    <JOB>CLERK</JOB>
    <MGR>7782</MGR>
    <HIREDATE>23-JAN-82</HIREDATE>
    <SAL>1300</SAL>
    <DEPTNO>10</DEPTNO>
   </EMP_DATA_ROW>
  </EMP_DATA>
 </ROW>
 <ROW>
  <DEPTNO>20</DEPTNO>
  <DNAME>RESEARCH</DNAME>
  <EMP_DATA>
   <EMP_DATA_ROW>
    <EMPNO>7369</EMPNO>
    <ENAME>SMITH</ENAME>
    <JOB>CLERK</JOB>
    <MGR>7902</MGR>
    <HIREDATE>17-DEC-80</HIREDATE>
    <SAL>800</SAL>
    <DEPTNO>20</DEPTNO>
   </EMP_DATA_ROW>
   <EMP_DATA_ROW>
    <EMPNO>7566</EMPNO>
    <ENAME>JONES</ENAME>
    <JOB>MANAGER</JOB>
    <MGR>7839</MGR>
    <HIREDATE>02-APR-81</HIREDATE>
    <SAL>2975</SAL>
    <DEPTNO>20</DEPTNO>
   </EMP_DATA_ROW>
   <EMP_DATA_ROW>
    <EMPNO>7788</EMPNO>
    <ENAME>SCOTT</ENAME>
    <JOB>ANALYST</JOB>
    <MGR>7566</MGR>
    <HIREDATE>09-DEC-82</HIREDATE>
    <SAL>3000</SAL>
    <DEPTNO>20</DEPTNO>
   </EMP_DATA_ROW>
   <EMP_DATA_ROW>
    <EMPNO>7876</EMPNO>
    <ENAME>ADAMS</ENAME>
    <JOB>CLERK</JOB>
    <MGR>7788</MGR>
    <HIREDATE>12-JAN-83</HIREDATE>
    <SAL>1100</SAL>
    <DEPTNO>20</DEPTNO>
   </EMP_DATA_ROW>
   <EMP_DATA_ROW>
    <EMPNO>7902</EMPNO>
    <ENAME>FORD</ENAME>
    <JOB>ANALYST</JOB>
    <MGR>7566</MGR>
    <HIREDATE>03-DEC-81</HIREDATE>
    <SAL>3000</SAL>
    <DEPTNO>20</DEPTNO>
   </EMP_DATA_ROW>
  </EMP_DATA>
 </ROW>
 <ROW>
  <DEPTNO>30</DEPTNO>
  <DNAME>SALES</DNAME>
  <EMP_DATA>
   <EMP_DATA_ROW>
    <EMPNO>7499</EMPNO>
    <ENAME>ALLEN</ENAME>
    <JOB>SALESMAN</JOB>
    <MGR>7698</MGR>
    <HIREDATE>20-FEB-81</HIREDATE>
    <SAL>1600</SAL>
    <COMM>300</COMM>
    <DEPTNO>30</DEPTNO>
   </EMP_DATA_ROW>
   <EMP_DATA_ROW>
    <EMPNO>7521</EMPNO>
    <ENAME>WARD</ENAME>
    <JOB>SALESMAN</JOB>
    <MGR>7698</MGR>
    <HIREDATE>22-FEB-81</HIREDATE>
    <SAL>1250</SAL>
    <COMM>500</COMM>
    <DEPTNO>30</DEPTNO>
   </EMP_DATA_ROW>
   <EMP_DATA_ROW>
    <EMPNO>7654</EMPNO>
    <ENAME>MARTIN</ENAME>
    <JOB>SALESMAN</JOB>
    <MGR>7698</MGR>
    <HIREDATE>28-SEP-81</HIREDATE>
    <SAL>1250</SAL>
    <COMM>1400</COMM>
    <DEPTNO>30</DEPTNO>
   </EMP_DATA_ROW>
   <EMP_DATA_ROW>
    <EMPNO>7698</EMPNO>
    <ENAME>BLAKE</ENAME>
    <JOB>MANAGER</JOB>
    <MGR>7839</MGR>
    <HIREDATE>01-MAY-81</HIREDATE>
    <SAL>2850</SAL>
    <DEPTNO>30</DEPTNO>
   </EMP_DATA_ROW>
   <EMP_DATA_ROW>
    <EMPNO>7844</EMPNO>
    <ENAME>TURNER</ENAME>
    <JOB>SALESMAN</JOB>
    <MGR>7698</MGR>
    <HIREDATE>08-SEP-81</HIREDATE>
    <SAL>1500</SAL>
    <COMM>0</COMM>
    <DEPTNO>30</DEPTNO>
   </EMP_DATA_ROW>
   <EMP_DATA_ROW>
    <EMPNO>7900</EMPNO>
    <ENAME>JAMES</ENAME>
    <JOB>CLERK</JOB>
    <MGR>7698</MGR>
    <HIREDATE>03-DEC-81</HIREDATE>
    <SAL>950</SAL>
    <DEPTNO>30</DEPTNO>
   </EMP_DATA_ROW>
  </EMP_DATA>
 </ROW>
 <ROW>
  <DEPTNO>40</DEPTNO>
  <DNAME>OPERATIONS</DNAME>
  <EMP_DATA>
  </EMP_DATA>
 </ROW>
</ROWSET>

scott@ORA920>

Review &
      Followup

      Rating: 3
oracle data to
      xml file - plsql  April 24, 2003
Reviewer:
      A reader
tom,

i want it in a plsql block. i might have to use file operation,Utl_File, to
write to a file, setting utl_file_dir parameter. i can't spool it. finally i
will have to call the generated xml file in a cron as this will be on daily
basis and my application will use the xml file

if you get chance please let me know, how to go about in plsql .

Thanks

          Followup:

umm

begin
  for x in ( that query i wrote )
  loop
     do what you will with the result
  end loop;
end;

If you need more details about "how to do it in plsql" then that -- you are
really asking "write my program for me since I don't code plsql".  UTL_FILE is
documented -- there are lots of examples on this site (search for dump_csv for
example)...

You don't want (need, desire) to use CRON, you want (need, desire) to use
DBMS_JOB to schedule this.

April
      24, 2003 Guru -- Thanks for the
      question regarding "pctfree & pctused", version 8.1.6

      You Asked
Dear Tom,

   I would like to know what are the factors that must be taken into
consideration for storage parameters in table definition.  i.e., initial, next,
pctfree, pctused, etc.  If any documentation is there kindly suggest me.  We
have a data block of size 8192 bytes.  Is it that if we have a for ex; columns
of number(3), varchar2(10) in a table, the number occupies 2 bytes, varchar
takes 1 byte per character.  Kindly suggest me a solution.

Thanking You,
Guru prasad

      and we said...
You do not have sufficient data to come to any conclusions.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:733825532206
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:240014552942
I can say that your NEXT should be whatever you decide your INITIAL is and you
should use locally managed tablespaces to ENFORCE that fact on all objects in
your database.

You need to figure out how fast this table will grow over time.  If the table
does not grow over time, then you need to figure out how big it will start at.
Lets say the table starts at 100meg and grows by 1meg per year.  I would
probably pick an extent size of about 1meg for this object.  for that
initial=next=1.  pctincrease (if you persist in using dictionary managed
tablespaces) would be ZERO as it should be always.  (before someone comments
"but smon won't coalesce free space unless it is non-zero" -- don't worry -- if
init=next for all objects and you put all objects into tablespaces based on
their extent sizes -- you don't need to coalesce free space).

pctfree and pctused is a function of how you use the data.

do you load it once and never update it?  then pctfree=0.  pctused=99.

do you load it with the varchar2 empty and update it to 10 bytes later?
pctfree=70 might be called for to avoid rows migrating.

and so on.  you need to figure out HOW you will be using the data in order to
come up with reasonable pctfree/pctused.  The defaults are typically OK for many
implementations.

Review &
      Followup

      Rating:
      4
  May 11, 2001
Reviewer:  cahillw
       from brevard, Florida USA

      Rating: 4
But can you
      give more details in deciding on pctfree?  February 04, 2003

Reviewer:  Pat  from md, usa
I can use this case as an example:

My block size is 8192, I have a table with all fields except those to be updated
adding up to about 115 bytes for a row. I set pctfree to be 20, so that means I
should have (8192 - 161) * 80% = 6424 bytes for initial data and around 1600 for
updates, and each block should hold 6400/115=55 rows. Now I update several
columns, which at most would add 23 bytes each row to the block, altogether it
would be 55rows * 23 bytes = 1280 bytes for each block. Now since I have set
aside about 1600 byptes for updates, I should not have any chained rows, right?
But I do see them from time to time.

Am I doing this wrong? In your response above you said 10 bytes of updates for
an empty varchar2 would warrant a pctfree of 70. How do you come to that, or it
is the experience?

Thanks.

          Followup:

well, you forgot to take the block header into consideration.

But in any case -- all that means is that sometimes your averages are OFF and on
that block, you are experiencing a row migration.  Sometime -- it is more then
155 bytes or more then 23.

ops$tkyte@ORA817DEV> create table t ( x char(115) primary key, y char(23) )
pctfree 20
  2  /

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> begin
  2          for i in 1 .. 1000
  3          loop
  4                  insert into t(x) values (i);
  5          end loop;
  6          commit;
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> select dbms_rowid.rowid_block_number(rowid), count(*)
  2    from t
  3   group by dbms_rowid.rowid_block_number(rowid)
  4  /

DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)   COUNT(*)
------------------------------------ ----------
                                  10         53
                                  11         53
                                  12         53
                                  13         53
                                  14         53
                                  15         53
                                  16         53
                                  17         53
                                  18         53
                                  19         53
                                  20         53
                                  21         53
                                  22         53
                                  23         53
                                  24         53
                                  25         53
                                  26         53
                                  27         53
                                  28         46

19 rows selected.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> update t set y = rownum;

1000 rows updated.

ops$tkyte@ORA817DEV> commit;

Commit complete.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA817DEV> select chain_cnt from user_tables where table_name = 'T';

 CHAIN_CNT
----------
         0

Shows that if it were exactly 115 and 23, they would fit.  If I use an AVERAGE
of 155 but let if vary from 30 to 200 -- we can see:

ops$tkyte@ORA817DEV> create table t ( x varchar2(200) primary key, y char(23) )
pctfree 20
  2  /

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> begin
  2          for i in 1 .. 1000
  3          loop
  4                  insert into t(x) values ( rpad(i,mod(i,170)+30) );
  5          end loop;
  6          commit;
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> select dbms_rowid.rowid_block_number(rowid), count(*),
  2             min(length(x)), max(length(x)), avg(length(x))
  3    from t
  4   group by dbms_rowid.rowid_block_number(rowid)
  5  /

WID_BLOCK_NUMBER(ROWID)   COUNT(*) MIN(LENGTH(X)) MAX(LENGTH(X)) AVG(LENGTH(X))
 ---------------------- ---------- -------------- -------------- --------------
                     10         83             31            113             72
                     11         45            114            158            136
                     12         35            159            193            176
                     13         79             30            199     75.9113924
                     14         48            103            150          126.5
                     15         37            151            187            169
                     16         73             30            199     81.9452055
                     17         52             91            142          116.5
                     18         38            143            180          161.5
                     19         65             30            199     92.6923077
                     20         58             76            133          104.5
                     21         40            134            173          153.5
                     22         55             30            199     111.363636
                     23         66             59            124           91.5
                     24         42            125            166          145.5
                     25         39             30            199     159.846154
                     26         79             36            114             75
                     27         45            115            159            137
                     28         21            160            180            170

19 rows selected.

ops$tkyte@ORA817DEV> select avg(length(x)) from t;

AVG(LENGTH(X))
--------------
        113.15

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> update t set y = rownum;

1000 rows updated.

ops$tkyte@ORA817DEV> commit;

Commit complete.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA817DEV> select chain_cnt from user_tables where table_name = 'T';

 CHAIN_CNT
----------
         9

some will migrate over time.

      Rating: 4
PCTFREE and
      FREELIST  April 24, 2003
Reviewer:  Vivek
      Sharma  from Bombay, India
Dear Tom,

I was going thru your book in which u have given one very good example regarding
measuring the impact of PCTUSED and PCTFREE parameters. I have a question :
The DB Block Size of my database is 4096. I have created a table as
create table t (x int, y char(1000) default 'x');
and inserted 10 rows into it as per the example given by you. The output of
measure_usage procedure shows :

exec measure_usage('VIVEK','T','TABLE');
Insert  00 0 on Freelists, 0 used by table
Insert  01 1 on Freelists, 1 used by table
Insert  02 1 on Freelists, 1 used by table
Insert  03 1 on Freelists, 1 used by table
Insert  04 1 on Freelists, 2 used by table
Insert  05 1 on Freelists, 2 used by table
Insert  06 1 on Freelists, 2 used by table
Insert  07 1 on Freelists, 3 used by table
Insert  08 1 on Freelists, 3 used by table
Insert  09 1 on Freelists, 3 used by table
Insert  10 1 on Freelists, 4 used by table

analyze table t compute statistics;

select PCT_FREE, PCT_USED, NUM_ROWS, BLOCKS, EMPTY_BLOCKS,  AVG_ROW_LEN
from dba_tables
where table_name='T';

  PCT_FREE   PCT_USED   NUM_ROWS     BLOCKS EMPTY_BLOCKS AVG_ROW_LEN
---------- ---------- ---------- ---------- ------------ -----------
        10         40         10          4            0        1009

Remember, I have not updated the column y yet and is default to 'x'. I have
analyzed the table and checked the avg_row_len which is 1009. The PCTFREE and
PCTUSED are defalut 10 and 40 respectively. Now 10% of 4096 is 410, so if the
block crosses 3686 (4096-410) bytes, it should be removed from the freelist. I
can only insert 3 rows ina block so 1009 * 3= 3027 so 3686-3027=659. Since 659
bytes are remaining on the block which is not sufficient to fit a new row this
block should remain on freelist and when the 4th row in inserted into another
block the no. of blocks in freelist go on 2 and so on. So why does it shows only
1 block on freelist ? If I am wrong please correct me.

Thanks
Vivek Sharma

          Followup:

the 4th insert actually took the block off of the freelist.  there was
insufficient space to put that row on there -- and keep it under %free so off it
went (we put the row on, compute free space, if under - kick it off the freelist
and put this row on the next block instead)

You can see that with this example (8k block):

ops$tkyte@ORA817DEV> create table t ( x int, y char(2000) default 'x' ) pctfree
90 pctused 10;
Table created.

ops$tkyte@ORA817DEV> insert into t (x,y) values ( 1,null );
ops$tkyte@ORA817DEV> insert into t (x,y) values ( 1,'x' );
ops$tkyte@ORA817DEV> insert into t (x,y) values ( 1,null );
ops$tkyte@ORA817DEV> insert into t (x,y) values ( 1,'x' );
ops$tkyte@ORA817DEV> insert into t (x,y) values ( 1,null );

ops$tkyte@ORA817DEV> analyze table t compute statistics;
Table analyzed.

ops$tkyte@ORA817DEV> select blocks, empty_blocks, num_freelist_blocks from
user_tables where table_name = 'T';

    BLOCKS EMPTY_BLOCKS NUM_FREELIST_BLOCKS
---------- ------------ -------------------
         5           58                   1

ops$tkyte@ORA817DEV> select dbms_rowid.rowid_block_number(rowid), count(*) from
t group by dbms_rowid.rowid_block_number(rowid);

DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)   COUNT(*)
------------------------------------ ----------
                               27786          1
                               27787          1
                               27788          1
                               27789          1
                               27790          1

every block has a row.  The first row with just a number went in, the second
row -- tried to go on block 1 but that put it over the 10% limit -- off it went
and onto row 2... and so on

ops$tkyte@ORA817DEV> truncate table t;
Table truncated.

ops$tkyte@ORA817DEV> insert into t (x,y) values ( 1,null );
ops$tkyte@ORA817DEV> insert into t (x,y) values ( 1,null );
ops$tkyte@ORA817DEV> insert into t (x,y) values ( 1,null );
ops$tkyte@ORA817DEV> insert into t (x,y) values ( 1,'x' );
ops$tkyte@ORA817DEV> insert into t (x,y) values ( 1,'x' );

ops$tkyte@ORA817DEV> analyze table t compute statistics;
Table analyzed.

ops$tkyte@ORA817DEV> select blocks, empty_blocks, num_freelist_blocks from
user_tables where table_name = 'T';

    BLOCKS EMPTY_BLOCKS NUM_FREELIST_BLOCKS
---------- ------------ -------------------
         3           60                   1

ops$tkyte@ORA817DEV> select dbms_rowid.rowid_block_number(rowid), count(*) from
t group by dbms_rowid.rowid_block_number(rowid);

DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)   COUNT(*)
------------------------------------ ----------
                               27786          3
                               27787          1
                               27788          1

there -- we have the first three small rows going together -- the last two by
themselves..

      Rating:
      5
great!  April 24, 2003
Reviewer:
      Alberto Dell'Era  from Milan, Italy
I had totally missed that a row bigger than [roughly]
(100-PCTFREE) * DB_BLOCK_SIZE "has" to violate the PCTFREE
requirement of the user.

I thought Oracle would have preferred to chain the row,
instead of giving less free space than requested; but that
is not the case.
Thanks for the Ray of Light!

April 24, 2003 venus -- Thanks for the question regarding "Hot
      backup", version 8.0.4

      You Asked
Why its not sensible to take hot backup at the busiest time of
the day?

      and we said...
Well, I can think of a couple of reasons why I might not want
to.  I don't know if that means "its not sensible".  It is
certainly "feasible".

o we generate extra redo information the first time we log a
block when a tablespace is in hot backup mode.  See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:271815712711for
why we
do that.

o you are doing extra IO on an already busy system.  While you
are reading the files for backup, we are reading and writing
them for normal database operations.

Review &
      Followup

      Rating: 3
got error when
      clicking on the link  August 14, 2001
Reviewer:
      A reader  from Canada
Has the refreneced page been deleted ?
Go error screen when clicking on the link.

      Rating: 2
Yes. The link
      is not available  April 24, 2003
Reviewer:
      A reader

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:271815712711

April 24, 2003 Doug -- Thanks for the question regarding "Does
      the SGA swap or is it prevented?", version 8.1.7

      You Asked
Tom - recently some high paging activity started occurring on one of our
database servers and we switched to MTS to conserve memory to stop the paging.
A question came up - can the SGA be paged out or is it locked?, perhaps by the
kernel extension?.  I am on AIX in case this is a platform independent
situation.  I imagine if shared memory segments in the SGA began to page out
that could cause an enormous performance problem.

Thanks,
D

      and we said...
SGA is just memory -- on virtually every system it can be swapped out.

there is a lock_sga init.ora that is supported on some platforms.

When you switched to MTS you did configure a LARGE_POOL big enough right?  I
mean you went through the right steps.  You are not using your shared pool for
MTS are you? MTS will not save you TONS of ram, it can save some of the repeated
PGA's but the UGA will still be there -- just moved into the SGA.

Paging is OK, normal even.  Paging is done LRU so if you are using it and its
hot, it won't get paged.

Swapping -- now that would be bad. Swapping is indiscriminate and just gets ram
free.
Review
      & Followup

      Rating:
      4
  April 10, 2002
Reviewer:
      A reader
Pardon my ignorance. Could you pls explain what is the differece between paging
and swapping.

          Followup:

see

http://www.princeton.edu/~unix/Solaris/troubleshoot/ram.html
for example

      Rating: 5
Good info as
      always  April 11, 2002
Reviewer:  Doug
       from CT, USA
Great info as always.  I think our MTS setup is ok.  We had about 200 dedicated
connections which are about 5-7 MB each.  We are expecting up to 600 within 2
months.  I have 2 dispatchers and 20 server processes and a 300 MB large pool.
No complaints so far.  200*6=1.2GB so with 300MB large pool and 20 server
processes*7MB=140MB it seems a pretty good memory savings. of 600MB.  Seems like
gobs to me on a 4GB box.

      Rating: 1
Thanks Tom for
      sending us back to college to learn some Swapping
      basics  April 11, 2002
Reviewer:
      A reader

      Rating: 5
about
      lock_sga  April 15, 2002
Reviewer:  Yogeeraj
       from Mauritius
Hello,

just a small question:

According to the Server Concepts manual:
Oracle 8 i
Concepts
Release 2 (8.1.6)
December 1999
Part No. A76965-01
<Quote>
Suggestion:
Usually, it is best to keep the entire SGA in real memory. On many platforms,
you can lock the SGA into real memory with the LOCK_SGA parameter.
</Quote>

LOCK_SGA parameter locks the SGA into physical memory.

however, (ref.
http://www.quest.com/whitepapers/orcl_db_mgmt.pdf
<Quote>
MLOCK_SGA/LOCK_SGA/_LOCK_SGA etc. are all parameters that facilitate locking of
the SGA in memory and preventing it from being paged/swapped. Proper use of this
parameter will prevent the paging algorithm from considering the memory pages
used by the SGA for page-outs. This provides a significant performance benefit
especially during periods of heavy activity. This parameter should not be used
if the system is not configured with an optimal amount of memory to begin with.
</Quote>

If my system (HP-UX) supports locking of SGA, how will i know if i have enough
PHYSICAL memory available and other applications running on my server will not
be affected?

Also, when i "lock" the SGA, is it exactly the amount of physical memory
comprising SGA (show sga) being dedicated?

Thank you for a reply.

Best Regards
Yogeeraj

          Followup:

It is called benchmarking.

The goal here, the point of this parameter is to lock the sga into memory AT THE
EXPENSE of everything else in a system where you have insufficient RAM to do
your work.

If this parameter is needed, by its VERY DEFINITION, other apps running on the
server will certainly be affected.  BY DEFINITION, you are saving the SGA at the
expense of all else.

The SGA locked will be the SGA allocated.

      Rating: 5
clear
      answer  April 15, 2002
Reviewer:  Yogeeraj
       from Mauritius
thank you for the reply.

make things clearer.

there definitely should be a compromise...
need to carry out some benchmarks

Regards
Yogeeraj

          Followup:

there is a compromise -- it is called PAGING, it is what it was invented for.

If your system is so undersized for your working as to cause the SGA to page or
even swap.... Hmmm..... seems like it might (just might) be time for some ram.

      Rating: 5
A question on
      swapping  November 08, 2002
Reviewer:  Rory
      Concepcion  from Philippines
Hi Tom,

      Just a question on swapping. Here are the informations.

      Database Version Oracle 8.1.7.0.0
      Operating System IBM AIX 4.3
      Total System Global Area 1968998524 bytes
      Fixed Size                   116860 bytes
      Variable Size             944349184 bytes
      Database Buffers         1024000000 bytes
      Redo Buffers                 532480 bytes
      sort_area_size               8388608
      sort_area_retained_size      0
      shared_pool_size             800000000

lsps -a output
      Page Space  Physical Volume   Volume Group    Size   %Used  Active  Auto
Type
paging00    hdisk2            swapvg        6288MB      12     yes   yes    lv
hd6         hdisk1            rootvg        1024MB      73     yes   yes    lv

vmstat output

kthr     memory             page              faults        cpu
----- ----------- ------------------------ ------------ -----------
 r  b   avm   fre  re  pi  po  fr   sr  cy  in   sy  cs us sy id wa
 0  0 426831   124   0   1   1  37  126   0 183  498 242  5  1 89  5

lsattr -E| mem0
size     3072 Total amount of physical memory in Mbytes  False
goodsize 3072 Amount of usable physical memory in Mbytes False

We are having some discussions on the swap of 12% as shown in the lsps command.
A sysadmin tells us DBA's that it is high and there should only be 5% ideally on
unix machines. Though there really isn't any problem on the database. Our
sort_area_size is big enough that there isn't much sorting done on disks
anymore. Please give me your opinion on these.
1. Is the 12% swap really too high? If yes, how could we lower it.
2. In connection to the above question, for an OS memory of 3gig, is an SGA of
1.9G acceptable?

Thank you Tom. Good day and God bless you!

          Followup:

I don't know what lsps is (don't work on AIX personally -- solaris, linux
mostly)

So... I'm not really sure what I'm looking at here.  If you are asking "is it OK
to have 12% of swap allocated" -- then my answer would be "yes, i don't see any
problem with that. processes allocate swap when they start -- even if they never
ever use it.  having swap allocated does not indicate swapping"

Is a 1.9 gig SGA on a 3gig machine acceptable?  depends -- is 1gig enough for
all of the dedicated/shared servers and the OS and anything else that is
running?  cannot tell from this,

      Rating:
      3
VM  November 08, 2002
Reviewer:  Juan G
      Pena  from Mexico
Tom, I know this is not asktom.solaris.com, but do you know how to see exactly
how much swap is being used on Solaris, and by which process?

Thanks,
Juan

          Followup:

$ swap -l
swapfile             dev  swaplo blocks   free
/dev/dsk/c0t0d0s3   32,3      16 615584 542464
/dev/dsk/c3t0d0s1   32,601     16 1667504 1597408
/dev/dsk/c2t1d0s1   32,489     16 1639424 1565328

$ swap -s
total: 746792k bytes allocated + 76912k reserved = 823704k used, 2407072k
available

never saw a way to get it by process, not really relevant actually.

      Rating:
      5
swapping  November 10, 2002
Reviewer:
      Rory  from Philippines
Thanks for the prompt response Tom.
Your response was helpful.
c",)

      Rating:
      5
PRE_PAGE_SGA  April 24, 2003
Reviewer:
      Juan Carlos Reyes  from Bolivia
lock_sga doesnt works in all platforms,

In documentation there is not too much about PRE_PAGE_SGA
 parameter, could you please give more your personal experience about its use.
And when you advise to use it.

Thanks

          Followup:

i've never actually used it myself.

      Rating:
      3
  April 24, 2003
Reviewer:
      A reader
thanks

      Rating:
      4
RAM  August 21, 2003
Reviewer:  Vera
       from Russia
Hello, Tom
I have a problem with performance on the bigger machine
(2 CPU and 3G RAM , WinNT2000) with oracle (SGA  ~1,5G)
than on the old one (1 CPU and 1G RAM,WinNT2000) with oracle (SGA~ 550M).
One the more power machine it runs slower.
Can it be swap. If yes? then why and what to do with it.

---------------------------------1 server (big)  -----------------------------
select count(*)
from
 hist_blanks

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.60       0.60        106      19646          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.60       0.60        106      19646          0           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 67  (DEVELOPER)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
2073310   TABLE ACCESS FULL HIST_BLANKS

----------------------------------2 server (old) ----------------------------
select count(*)
from
 hist_blanks

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.46       1.95      11242      19646          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.46       1.95      11242      19646          0           1

I see that cpu on the 1 pc = 0.60 and on the 2 = 0.46.
Could you comment it , please (indexes are absend).
Thanks in advance.

April 24, 2003 sam -- Thanks for the question regarding "lock
      and latch", version 8.1.5

      You Asked
Hi Tom

You are doing a great job.

Can you explain me the difference between a lock and latch. Actually what is a
latch.

and..if you permit me I will some more questions...o.k.

give me an example to explain a exclusive lock, a shared lock and a dead lock.
Iam not as smart as you are , so kindly give me a very very simple example.

Bye

      and we said...
A latch is a type of lock.

A latch is a light weight serialization device, typically used to serialize
access to a shared data structure.  It is normally implemented as a single "test
and set" instruction although this varies by OS.

A latch is a lock in that respect.

In Oracle the other major type of lock is called an enqueue.  They are
implemented differently from latches and are somewhat "heavier".  A session that
is waiting for a row that is being updated by another session is "enqueued" on
that other sessions transaction -- it is waiting for that session to commit.

An exclusive lock is a lock that only one session at a time can have.  If I lock
a table in exclusive mode -- no other session can.  Subordinate locks, like a
"share lock" on the table are prohibited as well.

A shared lock is a lock that many people can get at the same time.  Two sessions
can simultaneously lock a table in share mode.

A dead lock is when two sessions are blocked on eachother waiting for a resource
the other has.  For example:

Session 1                      Session 2
lock table t1
in exclusive mode;
                               lock table t2
                               in exclusive mode;
lock table t2
in exclusive mode;                                  <<<<<====== he is blocked
                               lock table t1
                               in exclusive mode;  <<<<<---- they are deadlocked

the system will detect that and fail one of the above last two statements.

In my book coming out -- I go into this in great detail (a whole chapter on this
stuff).
Review
      & Followup

      Rating:
      5
  May 25, 2001
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating: 5
lock
      table  April 02, 2003
Reviewer:
      A reader
How can I lock the table that only application can perform
the insert and update and delete is not allowed ever?

          Followup:

well, you'll have to tell me how you can tell in the database that it is the
application that is doing this.

Unless you are using ntier proxy authentication - you won't be able to tell me
that.  but -- if you tell me how to id the application in the database, i'll
tell you how to do this....

      Rating: 5
lock and
      latch  April 02, 2003
Reviewer:  Reader

Tom,

Great job tom ...

<quote>
A shared lock is a lock that many people can get at the same time.  Two sessions
can simultaneously lock a table in share mode.
<quote
can you please let us know, when a table needs shared lock and is there any row
level locking? (i suppose there is ) .. what are they?.

And is latches are internally maintained by oracle? .. if so how can we
recoganise that a latch is on a table.

Thanks,

          Followup:

You might use a shared lock just to make sure that table "doesn't change" for
the duration of some operation.  It would be "rare".

A latch is just a serialization device, sort of like a semaphore.  If you want
to scuba dive on them:

http://www.amazon.com/exec/obidos/tg/detail/-/156592598X

      Rating: 5
Latches and
      Semaphores  April 24, 2003
Reviewer:
      Cefers.br  from Brasil
Tom,

I´m little confuse here.

Do latches work the same way semaphore processes do (inside Oracle server)?

Could you please explain how does a semaphore work?

Thanks

          Followup:

latches are lightweight semaphores.  semaphores are heavy duty serialization
devices used by multiple processes generally at the operating system level to
coordinate access to shared resources.  It takes more cpu cycles to get and give
back semaphores generally then it does a latch.

They are all serialization devices, mutual exclusion devices, things to make
sure that shared resources are accessed "nicely", in a manner that won't corrupt
them.

April 25, 2003 Dan -- Thanks for the question regarding
      "Database links in PROD and TEST environments", version 8.1.7.4

      You Asked
Dear Tom...

Consider the following...

An application needs to retrieve data from both local and remote databases. The
application exists in both test and production environments. There are periodic
refreshes from the production database into the test database.

There are four database servers, two in each environment, and are "paired" in
the following fashion:

            Local   Remote
            ------  ------
Production  LOC1    REM1
TEst        LOC2    REM2

By paired, I mean that in a PL/SQL procedure in this application is such that in
the local test database, we need fetch data from the corresponding remote test
database.

My question is: how do we avoid the following construction,

<pseudocode>
   IF in_test_environment THEN
      SELECT  foo
      INTO    blah
      FROM    bar@REM2
   ELSE
      SELECT  foo
      INTO    blah
      FROM    bar@REM1
   END IF ;
</pseudocode>

... or avoid maintaining two codebases (hardcoding of the database link name):
one for test, one for production?

I suppose that we could use synonyms for the remote table-- the one in test
would be for REM2, the other for REM1-- and name them the same in both test and
prod. But then upon a refresh, the synonym from prod would clobber the one in
test. (Or perhaps not; correct me if I'm wrong.)

Many thanks in advance,

dan kefford

      and we said...
I would use views.  But why would a view in prod "clobber" a view in test?

Also -- they can both use "bar@dblink"

because you can it is really the TNS CONNECT STRING on the corresponding server
that will tell you what database is ultimately connects to.

If scott/tiger@connect_string gets me to REM1 on prod and
scott/tiger@connect_string gets me to REM2 on test, then both prod and test can
use "dblink" as the dblink name -- the tnsnames.ora file will direct them to the
proper location.
Review & Followup

      Rating: 3
What about
      sequences  April 24, 2003
Reviewer:
      A reader
1) For creating views do we still create private synonyms for the
remote tables or just use hard coded dblink names.
  Like create view vu_dept as select * from dept@remote_dblink or
     create vu_dept as select * from dept
 where dept is a private synonym dept@remote_db_link.
2)U suggest using views instead of synonyms for table dml operations. what to do
about sequences used for inserts into remote DBs. Do we not have to create
synonyms for them other wise it will be like table_seq@remote_db_link in the
insert statements.

          Followup:

sequences would be synonyms -- but as I said, the dblink names do not have to
be different at all

      Rating: 3
What about
      views ?  April 24, 2003
Reviewer:
      A reader
For creating views do we still create private synonyms for the
remote tables or just use hard coded dblink names.
  Like create view vu_dept as select * from dept@remote_dblink or
     create vu_dept as select * from dept
 where dept is a private synonym dept@remote_db_link.

          Followup:

you would be using views instead of synonyms -- put the dblink in the view.

      Rating: 3
what about
      global names  April 24, 2003
Reviewer:
      A reader  from Austin, TX
With global_names set to TRUE, the above (same dblink name, different database)
will not work.  I asked you about a scenario like this back in December to which
you replied:

"... by subverting global naming like that you stand the real risk of pointing
to the WRONG database by accident if someone mucks with your tnsnames.ora file,
or starts the database pointing to a different one."

"This is exactly what global names is designed to avoid.  There is NO ambiguity.
Rather then playing hokey pokey with a tnsnames.ora file -- I would drop and
recreate the database links using global names."

Am I missing something here?

          Followup:

for test vs prod, it is up to you.  I'm just laying the options out.

you have views
you have synonyms
you have tnsnames

      Rating: 2
Use synonyms
      and views  April 25, 2003
Reviewer:  W. M.
      Spears  from Fairborn, OH USA
Create views LOC_table_name, REM_table_name - table_name is the same for both
local and remote views.  Create a synonym that has the name that the application
code will refer to.  Create a PL/SQL "flipper" procedure to which you pass
parameters to specify table_name and local/remote.  The procedure can then
delete and recreate the procedure so it points to the view you want and your
application code doesn't have to know local vs. remote.  This is what we ended
up doing.  We also created a table that the "flipper" wrote to to record the
last action for table_name.  If you have a lot of people willy-nilly flipping
local to remote and back, this won't work.  This does need to be controlled.

April 25, 2003 Mudassar -- Thanks for the question regarding
      "Backup using and old controlfile and online backup", version 8.1.5

      You Asked
Hello,

Q.1. The"recover database using backup controlfile" command is
used to recover your database when the current controlfile is
not aware of the tablespace that is lost. This is what I did and
it worked.
At 8:00am I took a full database backup. At 9:00am I dropped a
tablespace named DATA01. At 9:30am I realized I needed to
recover that tablespace. So, using the controlfile available at
9:30 I did an incomplete recovery till 8:59am and the tablespace
was recovered.

In such a case what is the significance of using the clause,
USING BACKUP CONTROLFILE.

Q.2. My database was running in archivelog mode. I issued the
following command:
a) ALTER TABLESPACE DATA02 BEGIN BACKUP;
b) After this command I created a table on the tablespace data02
and inserted around 100 rows in this table. Then I issued the
following command 10 times:
c) ALTER SYSTEM SWITCH LOGFILE ; (My database has four log
groups)
d) After this I renamed the archive directory at the OS level to
a different name.
e) Then I issued the following command:
ALTER TABLESPACE DATA02 END BACKUP;
f) And as a result of this I was expecting Oracle to look for
the archives to apply the changes to the datafile, but no error
was returned and the table was accessible with all the rows.

Does this mean even after starting the online backup Oracle
continues to write to the Datafiles? (if this is true, why can't
we do online backups in NOARCHIVELOG mode)
OR the data in my above example was still in memory and in
actual case you would need to access it from the archive logs?

Thanks in advance,
Mudassar Shahzad

      and we said...
Generally, you would include "using backup control file" in your

recovery statement under two conditions:

1.  Anytime the controlfile is older than any of your
    datafiles.

2.  You do not have a valid current control file.
    (It is either missing or corrupt.)

As for the create table and such commands -- you must understand
that when a tablespace is in backup mode -- It is CONSTANTLY
being written to.  the changes are not "suspended" or anything
like that.  We checkpoint to that file, we read that file, we
write that file.

We need the archive log files to make that file consistent in
the event we use it to perform media recovery.  That data files
needs all of the redo generated from the time of the alter begin
backup until the current time if we want it to be consistent.
That is why you cannot do a hot backup in a noarchive log mode
database -- once the logs wrap, we cannot make that copy
consistent anymore and it is useless as a backup.

Review &
      Followup

      Rating: 3
Backup and
      Recovery  April 24, 2003
Reviewer:  Abhi
       from INDIA
Hello SIr .  ..
two or three time ,,
i am go  through oracle references  and ur book ...
but after all i m confused   ..(Thick Head)
abt backup and recover y ..
in which conditon we should create  new controgile
or when using  backup controlfile ..
i  know sir u   r too busy for answering  others importants question , i do no
waste  ur time  ...

sir can u  give me  any **** other link  **** other than oracle technet
references ..
for clearity  of  basic of backup and recovery.

thanx sir...

          Followup:

http://www.amazon.com/exec/obidos/search-handle-url/index=books&field-keywords=oracle%20backup%20recovery&search-type=ss&bq=1/104-2118941-5403913
(search for oracle backup recovery on amazon)

Rama Velpuri's book is well regarded but getting on in years.  The mechanics are
very much the same however.

The only times you create a new controlfile would be

a) you lost all online control files, so you use you "backup control to trace"
script to recreate them

b) you want to do an incomplete point in time recovery.

otherwise you always use the current online control files.

      Rating:
      4
Re:  April 25, 2003
Reviewer:  Abhi
       from India
Thanx Sir ... For ur Reply

Sir ONE thing more ..
For testing
I create a test database ....  noarchivelog
with 80.4 m system.dbf [ only catalog.sql and catproc.sql ]
with 1 control file
with 2 redo groups [ each 10 mb with one member ]
no temp and no undo tablespace

sir i shut the database cleanly ..
and take backup [ system.dbf and control file ]

again startup database
under sys schema i create a table "Test" with 1 column
and insert 10 records .. through  each insert command ..
mean no  nologging ... and commit ;
and note the scn . Assume 10000... using dbms_flashback.get_system_ch..
after commit i drop the table  ... [assume scn 10100 ];
and shutdown ..[cleanly ]
i m sure that there is no log switching i checkd this mean no log overwritten...
after that i replace the old system.dbf and controlfile ...
and up the database .. oracle says .. redo group is ahead
so i issued this command
Recover database until change 10000

Media recovery compelete ..
Alter database open resetlogs ..
sir i did not find my table ... where i m wrong  ..

i think if we are in noarchive mode after a cold backup
no redo is overwritten we can do incompelete / or full recover as like Archive
mode. ..

2.  sir in archivelog mode if i lost my controlfile
and had a old backuped control file .. . y should i using
backup controlfile for recovery .. i can create a new file if i know all files
locations ...

Thanx

          Followup:

1) if you want anything sensible -- and you want to do PITR like that -- you
will test proper methods.  You will be in archive log mode and use valid
processes.  Not seeing actual cut and pastes and not really desiring to play
with something that isn't truly useful in the real world -- I'll pass on this
one. (but next time, try not replacing the old control file)

2) you would use the old backup control file to generate the CREATE CONTROLFILE
script.  By all means, if you know the names of everything and want to do it
manually -- go for it.  I would have an alter database backup control file to
trace as part of my backup procedure in the first place so I don't ever really
even need the old control file.

You have a bigger problem though -- your keyboard is in dire straights.  It is
dropping extremely important letters (vowels) constantly -- making it harder
then necessary to read and understand your text.  I understand that english is
your second language -- but by mixing made up abbreviations with sometimes
broken english -- it gets hard to understand without reading and re-reading
sentences.

      Rating: 5
:)) :)) ... I
      pity you Tom  April 25, 2003
Reviewer:
      Anand  from Atlanta, US
:)) ...I think you need to have a team of translators from different dialects
(No offence to that guy!!)

I come from the same land this guy comes from. Even I found it hard to
understand , in the beginning. Still, your perseverence to answer questions like
these impresses me.

You are as cool as cucumber !

      Rating: 4
Impeccable
      sense of humour :))  April 25, 2003
Reviewer:
      admirer_of_tom  from somewhere in the deepest Amazon :))

April 25, 2003 Tony -- Thanks for the question regarding
      "Generating XBRL/XML file", version 8.1.6

      You Asked
Hi Tom,
There is a requirement to produce an XBRL (eXtensible Business Reporting
Language)file. Could you please advise:
1) Can I produce XBRL file from oracle DB? If yes, please show me the example
including the oracle products/tools I need to install on the server.
2) Could you please show me the sequence of step to be followed for creating an
XML file. Also let me know Oracle products/tools I need to install to achieve
this.
Thanks

      and we said...
I asked Sean Dillon, our local XML guru, to take a look at this and here's what
he had to say:
-------------------------------------------------

You can most certainly create XBRL files from the Oracle database.  Since you're
running Oracle8.1.6, your options aren't nearly as numerous were you to be
running on Oracle9i, but we'll touch on some of those capabilities in a minute.
For now, let's concentrate on the XML Developer's Kit (the "XDK") and how you
can use it to meet your requirements.

If you don't already have an Oracle Technology Network (OTN) account, go
register for one.  It's free, and you get access to a LARGE amount of
information on Oracle's website such as sample code, product downloads,
discussion forums and most importantly (for the purposes of our discussion here)
the FREE Oracle XDKs.

http://otn.oracle.com/
Click on the "Membership" icon at the upper-right side of the page, and from
there you can register for your account.  Once you have your account, you can go
to this page:

http://otn.oracle.com/tech/xml/content.html
To get access to news, papers, articles, downloads and all kinds of other
resources pertaining to Oracle's XML capabilities.  This is where you will find
the download for the XDKs.  On the left hand side of that page, you will see an
index of pages with a title of "XML".  Find the link to XML Developer's Kit and
click on that.

http://otn.oracle.com/tech/xml/xdkhome.html
This will take you to your resource to the XDK home.  From here, you can access
the installation files for the XDKs as well as documents written about the XDKs,
sample code, documentation, etc.

At this point, you should know how to get the XDKs installed into your database.
 One of the nice things about Oracle's XDKs is that they're FREE and they are
backwards compatible to Oracle8.1.6.  So even though the current production
release of the XDKs is 9.2.0.2, you can install them into your database.

The language you are most comfortable in will most likely determine how you use
the XDKs.  Regardless of language choice (well, it's gotta be PL/SQL, C, C++ or
Java), the XDK is the development kit you will use to generate (& store if need
be) your XML.  The XDKs support all manner of XML functionality, including (but
certainly not limited to):

- DOM & SAX parsers
- XSL Transformation
- XML Schema & DTD processing
- XML Class generator (in Java and C++)
- XML SQL Utility, to generate XML, Schemas & DTDs from SQL queries
- XSQL Servlet, a Java servlet that combines the XML, SQL and XSLT components of
the XDK to deliver dynamic content.

This isn't ALL the functionality, it just gives you a flavor.  As for generating
the XML from the database, there are a few different ways.  You can generate XML
by using the DOM APIs or by using SQL and the XML SQL Utility.  As an example,
you can use XMLGEN which is a wrapper package for the lower level XDK packages
as follows to generate some simple XML:

SQL> select xmlgen.getxml('select e.ename, e.empno,
  2    e.sal, d.dname from emp e, dept d where e.deptno = d.deptno') xml
  3    from dual
  4  /

XML
--------------------------------------------------------------------------------
<?xml version = '1.0'?>
<ROWSET>
   <ROW num="1">
      <ENAME>SMITH</ENAME>
      <EMPNO>7369</EMPNO>
      <SAL>800</SAL>
      <DNAME>RESEARCH</DNAME>
   </ROW>
   <ROW num="2">
      <ENAME>ALLEN</ENAME>
      <EMPNO>7499</EMPNO>
      <SAL>1600</SAL>
      <DNAME>SALES</DNAME>
   </ROW>
   <ROW num="3">
      <ENAME>WARD</ENAME>
      <EMPNO>7521</EMPNO>
      <SAL>1250</SAL>
      <DNAME>SALES</DNAME>
   </ROW>
...
   <ROW num="14">
      <ENAME>MILLER</ENAME>
      <EMPNO>7934</EMPNO>
      <SAL>1300</SAL>
      <DNAME>ACCOUNTING</DNAME>
   </ROW>
</ROWSET>

This, obviously, is not in the XBRL format but with XML-SQL generation and a
healthy mix of XSLT (which again, is fully supported in the database), you can
generate all manner of XBRL files!

I hope this helps!

_____________________________________________________________________
Sean Dillon - Senior Technologist, Oracle Corporation
Author "Beginning Oracle Programming"
http://www.amazon.com/exec/obidos/ASIN/186100690X

Review &
      Followup

      Rating: 4
XBRL/XML
      File  May 15, 2002
Reviewer:  A reader

Thanks for quick response.

      Rating:
      5
  May 15, 2002
Reviewer:  Helena
      Markova  from Bratislava, Slovakia
Thank you for explanation.

      Rating: 4
Great
      explanation with example  April 09, 2003
Reviewer:
      Vijay Sehgal  from India
Hi Tom & Sean,
             This is first time I am working on Xml with Oracle, it will be
great if you can help me out. We have to transfer data from tables in one
database to another database on client's request without user intervention after
the application is intiated, hence we can't opt for import/export or
sql*loader.We are thinking of getting the data out in Xml format from tables and
have it in a file, then using this file we will insert data into the respective
tables in second database.

What I want know is, is it possible in the first place to have data in xml file
from tables using pl/sql and xml parser for pl/sql, also the columns and table
name from which the data is to be extracted will be read from a table so this is
will be dynamic, could guide me how should I go about it, some documents or
books, or links anyway to do this is more than welcome.

Also it will be great if you comment on this way out or any other better options
to do the same. The database we are using is Oracle 8i 8.1.7 and on Unix
environment.

Thanks as always for all your help.

Best Regards,
Vijay

          Followup:

can you spell "database link"

that is it, that is what you want, need and desire.  don't over complicate it.
It'll be as simple as "insert into table@remote select * from local_table" or
vice versa.

("on clients request" "without user intervention" seem to be mutually exclusive
operations by the way -- if the client requested it -- you already have user
intervention).

      Rating:
      3
  April 10, 2003
Reviewer:  Vijay
      Sehgal  from India
Good day Tom,
             Well "Datbase Link" was the very first thing I suggested from my
side, I have not used it but read couple of your replies on that in here. What I
meant by "without user intervention" is once initiated the process will go on in
background sending the data, inserting  or updating records and all other
related processes.

Still it will be great if you help me out with my previous request.

          Followup:

dbms_job that runs a procedure that uses a dblink.

      Rating: 3
Thanks as
      always for all your help  April 14, 2003
Reviewer:
      Vijay Sehgal  from India
Hi Tom,
      I should have quoted my last question to you in a better way to avoid
asking you again sorry for this, but what I have to find out about is, Is it
possible in the first place to have data in xml file from tables using pl/sql
and xml parser for pl/sql, also the columns and table name from which the data
is to be extracted will be read from a table so this is will be dynamic, if you
can give some  example or references to some documents or
books, or links anyway to do this is more than welcome.

The database we are using is Oracle 8i 8.1.7 and on Unix
environment.

It will be again a great help of yours.

Thanks as always.

Best Regards,
Vijay

          Followup:

goto otn, look under technologies => xml.

Yes, you can load xml into the database.
Yes, you can create xml from structured tables.
Yes, you need to load the xdk into the database.

      Rating: 4
Feedback
      Requested  April 14, 2003
Reviewer:  Saminathan
      Seerangan  from CA,USA
Incomplete Result
-----------------
Dear Tom, How to get the complete result? Do i have to set any veriables like
linesize,pagesize? Please help me..

SQL> select xmlgen.getxml('select employee_id,first_name,salary from employees')
xml from dual;

XML
--------------------------------------------------------------------------------
<?xml version = '1.0'?>
<ROWSET>
   <ROW num="1">
      <EMPLOYEE_ID>100</EMPLOY

SQL>

          Followup:

set long 500000

      Rating: 5
Feedback
      requested  April 15, 2003
Reviewer:  Saminathan
      Seerangan  from CA,USA
Thanks Tom. Another question in XML..

1)The requirement is to create XML with 2 or more levels(like <FIRST_NAME>,
<LAST_NAME> will be child node of <NAME> tag.

2)Also SEX column should be attribute for <NAME> tag like <NAME SEX=M>

For example,

<?xml version = '1.0'?>
<ROWSET>
   <ROW num="1">
      <EMPLOYEE_ID>100</EMPLOYEE_ID>
      <NAME SEX=M>
              <FIRST_NAME>Steven</FIRST_NAME>
              <LAST_NAME>King</LAST_NAME>
      </NAME>
      <SALARY>24000</SALARY>
   </ROW>
</ROWSET>

How do i accomplish this? Your help is really appreciated.
If possible, kindly provide one small example.

SQL> desc employees
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 EMPLOYEE_ID                               NOT NULL NUMBER(6)
 FIRST_NAME                                         VARCHAR2(20)
 LAST_NAME                                 NOT NULL VARCHAR2(25)
 SALARY                                             NUMBER(8,2)
 SEX                            CHAR(1)
 DEPARTMENT_ID                                      NUMBER(4)

SQL> select xmlgen.getxml('select employee_id,first_name,last_name,salary from
employees')  xml from
 dual;

<?xml version = '1.0'?>
<ROWSET>
   <ROW num="1">
      <EMPLOYEE_ID>100</EMPLOYEE_ID>
      <FIRST_NAME>Steven</FIRST_NAME>
      <LAST_NAME>King</LAST_NAME>
      <SALARY>24000</SALARY>
   </ROW>
</ROWSET>

          Followup:

I asked Sean Dillon, our XML Technologist, to take a look at this and here's
what he had to say:
----------------

There are a number of ways to do what you're trying to do, most of which are
discussed here on ASKTOM.  Using Oracle9iR2, there is a new feature called SQLX
(or SQLXML), which is a standard being worked on by ANSI.  Oracle has released a
production version of most of the features of the standard in 9iR2.  SQLX is a
way to write a standard SQL query that results in XML.  For example:

scott@FUNK92> select xmlelement("Employees",
  2           xmlagg(
  3             xmlelement(
  4               "Employee",
  5               xmlforest(empno, ename, sal)))) as "result"
  6    from emp
  7  /

result
-------------------------------------------------------------------------------<E
mployees>
  <Employee>
    <EMPNO>7369</EMPNO>
    <ENAME>SMITH</ENAME>
    <SAL>800</SAL>
  </Employee>
  <Employee>
    <EMPNO>7499</EMPNO>
    <ENAME>ALLEN</ENAME>
    <SAL>1600</SAL>
  </Employee>
  . . .
</Employees>

Short of being on 9iR2, you can use the XML Developers kits (as you have done in
your example above w/ XMLGEN.GETXML) to generate XML as well.  To generate
complex nested XML, you can use Oracle object types to define the structure then
select the data into the structure:

scott@FUNK92> create type emp_t as object(
  2    "@empno" number,
  3    ename    varchar2(20)
  4  );
  5  /
Type created.

scott@FUNK92> create type emplist_t as table of emp_t
  2  /
Type created.

scott@FUNK92> create type dept_t as object(
  2    "@deptno" number,
  3    dname     varchar2(20),
  4    emps      emplist_t
  5  );
  6  /
Type created.

scott@FUNK92> var g_xml clob
scott@FUNK92> declare
  2    l_ctx   dbms_xmlgen.ctxhandle;
  3    l_query varchar2(400);
  4  begin
  5    l_query := 'select dept_t(d.deptno, d.dname,
  6                       cast(multiset(select e.empno, e.ename
  7                                       from emp e
  8                                      where e.deptno = d.deptno)
  9                         as emplist_t)) as deptxml
 10                  from dept d';
 11    l_ctx := dbms_xmlgen.newcontext(l_query);
 12    :g_xml := dbms_xmlgen.getxml(l_ctx);
 13  end;
 14  /

PL/SQL procedure successfully completed.

scott@FUNK92> print g_xml

G_XML
-------------------------------------------------------------------------------<?
xml version="1.0"?>
<ROWSET>
 <ROW>
  <DEPTXML deptno="10">
   <DNAME>ACCOUNTING</DNAME>
   <EMPS>
    <EMP_T empno="7782">
     <ENAME>CLARK</ENAME>
    </EMP_T>
    <EMP_T empno="7839">
     <ENAME>KING</ENAME>
    </EMP_T>
    <EMP_T empno="7934">
     <ENAME>MILLER</ENAME>
    </EMP_T>
   </EMPS>
  </DEPTXML>
 </ROW>
 <ROW>
  <DEPTXML deptno="20">
   <DNAME>RESEARCH</DNAME>
   <EMPS>
    <EMP_T empno="7369">
     <ENAME>SMITH</ENAME>
    </EMP_T>
    <EMP_T empno="7566">
     <ENAME>JONES</ENAME>
    </EMP_T>
    <EMP_T empno="7788">
     <ENAME>SCOTT</ENAME>
    </EMP_T>
    <EMP_T empno="7876">
     <ENAME>ADAMS</ENAME>
    </EMP_T>
    <EMP_T empno="7902">
     <ENAME>FORD</ENAME>
    </EMP_T>
   </EMPS>
  </DEPTXML>
 </ROW>
 . . .
</ROWSET>

Your next logical question is "that's not exactly what I want, how can I change
'X'".  You can take this result and use XSLT to transform it if need be.  There
are some things you can change using the xmlgen api (dbms_xmlgen), but it's not
everything.  Check out this for some more info:

asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1827993798549

Hope that helps!

      Rating: 4
XML- another
      way-  May 22, 2003
Reviewer:  Sunny
      Chakraborty  from Stamford, CT
I created a Weblogs Analytics system- using oracle generated XML- but here I
generated XML using SQL SELECT and stored them as CLOB. Then passed this CLOB to
JSP app which parsed and displayed in graphics objects. You can use
Oracle.Type.CLOB obj...getAsciiStream() etc to print result.
Try creating this XML, after defining a DTD.
Now, as Tom wrote, use DBMS_JOB to setup a cron job to process nightly etc. I
use this to compute statistics after nightly loads. Also think about using Java
Stored proc/JDBC to pull data from another database. Oracle has many ways out!

April 25, 2003 Nag -- Thanks for the question regarding "how to
      handle no data found in a complicated update", version 8.1.7

      You Asked
I have an update statement like this

for rec in c5

loop
Update t

set  t.col1=(select t.co1+t1.col1
              where t.key=t1.key
              and t.1.id=(select id from t3
                            where t3.dt= somedate and
                             t3.col6=rec.col6..........)),
     t.col2=select t.co1+t1.col1
              where t.key=t1.key
              and t.1.id=(select id from t3
                           where t3.dt= somedate....
                             t3.col6=rec.col6..........))
where t.col3 =something
      t.col4=something .

end loop;

Tom

My questions are

How should we handle a no data found returned by the inner most query.
Will the no data found in the inner most query , will result in the failing of
the whole update statement.

In the innermost query can use the rec.col6 value as I have used above.

thank you

      and we said...
There will be no "no data found" raised during this query.

ops$tkyte@ORA8I.WORLD> insert into t values ( 1 );

1 row created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> update t set x = ( select y from t2 where y in ( select y
from t2 ) );

1 row updated.

ops$tkyte@ORA8I.WORLD> select * from t;

         X
----------

ops$tkyte@ORA8I.WORLD>

It'll be set to NULL.  If this is not what you want -- you need to add a where
exists to your where clause:

ops$tkyte@ORA8I.WORLD> insert into t values ( 1 );

1 row created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> update t set x = ( select y from t2 where y in ( select y
from t2 ) )
  2  where exists        ( select y from t2 where y in ( select y from t2 ) );

0 rows updated.

ops$tkyte@ORA8I.WORLD> select * from t;

         X
----------
         1

to catch and ignore rows where the subquery returns 0 records.
Review &
      Followup

      Rating: 5
Let me rewrite
      it according to your guidelines ....  October 30, 2001

Reviewer:  Nag
I am updating the table t, to update the prices , so a null will not be
acceptable to me, so let me rewrite the update accoridng  to your advise.

Update t

set  t.col1=(select t.co1+t1.col1
              where t.key=t1.key
              and t.1.id=(select id from t3
                            where t3.dt= somedate and
                             t3.col6=rec.col6..........)),
     t.col2=select t.co1+t1.col1
              where t.key=t1.key
              and t.1.id=(select id from t3
                           where t3.dt= somedate....
                             t3.col6=rec.col6..........))
where t.col3 =something and
      t.col4=something and
      exists( select id from t3
                            where t3.dt= somedate and
                             t3.col6=rec.col6..........)

I added the exists to the end with the innermost subquery as you have suggested
in your example.

1.But please not that , Iam updating 2 columns, and the inner most subquery
differs for both of them to the effec taht the t3.dt= somedate will differ. Now
which one of these inner most subqueries should go into the exists clause.

2.Actually, how is the exists clause helping us in avoiding a null

(It could have been ideal had you given an example updating 2 columns, and
updating the columns  by adding to the updating columns in the update.)

          Followup:

You cannot just do the innermost subquery.  Your syntax is incomplete, the
example won't run. (and you shouldn't use somedate two TIMES if they are
different, use something like "somedate_1" "somedate_2" else they sure look the
same to me.

Whether you have 1 or 100 columns to update, it'll be the same:

update t
   set c1 = ( subquery1 ),
       ...
       cn = ( subqueryN )
 where <your conditions>
   and exists (subquery1)
   ...
   and exists (subqueryN)

Just copy the subqueries into the where clause in total.

      Rating: 5
You are
      awesome- you solved the mystery for me  October 30, 2001

Reviewer:  Nag
1.Actually, how is the exists clause helping us in avoiding a null.

2.Is this kind of an update given else where on your websidte , and in oracle
documentation

          Followup:

1) if the exists is not satisified, you won't update the row.  Hence, it won't
update it to null.

2) it is a natural thing, very common yes.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:288016031632
where exists is just a predicate -- you can use it in any where clause.  The doc
for UPDATE does show something very similar only they used an IN instead of
WHERE EXISTS.

      Rating: 5
update to zero
      when null  October 30, 2001
Reviewer:  Nag

my objective is to update the column x in table t to zero, if the inner most
query returns no data, but Iam finding that, even if I use nvl , it is updating
to null. Is this the expected  behaviour or Iam I missing something.

SQL> desc t
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 X                                                NUMBER(10)

SQL> desc t1
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 X                                         NOT NULL NUMBER(38)
 Y                                                  NUMBER(38)

SQL> select count(*) from t1;

  COUNT(*)
----------
         0

SQL>  select * from t;

       X
----------
     1

SQL> update t set X = ( select nvl(y,0) from t2 where y in ( select nvl(y,0)
  2  from t2 ) );

1 row updated.

SQL> commit;

Commit complete.

QL> select * from t
 2  /

      X
---------

          Followup:

You are missing the subtle point that the subquery returns NO rows -- there is
"nothing to nvl" as it produces no output.  Hence, what you really really what
is the NVL of the subquery!

ops$tkyte@ORA717DEV.US.ORACLE.COM> create table t ( x int );
Table created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> create table t2 ( y int );
Table created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> insert into t values ( 1 );
1 row created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> update t
                                   set x = nvl( ( select y from t2 ), 0 );
1 row updated.

ops$tkyte@ORA717DEV.US.ORACLE.COM> select * from t;

         X
----------
         0

nvl( subquery, 0 ), not subquery with NVL.

      Rating: 4
How about the
      same, but in PL/SQL  April 25, 2003
Reviewer:
      Alla  from Boston, MA USA
Hi Tom;

I have the same problem, when I need to update a table and
set columns to some value, if the subquery does not return
any data.

You suggested:

update t
set x = nvl( ( select y from t2 ), my_value );

and it works fine in SQLPlus, but not in the stored procedure.

I am running this on Oracle 8.1.7. Is it possible in 8.1.7 or do I have to wait
till we upgrade to 9.x?

Thanks in advance

          Followup:

execute immediate
'update t set x = nvl( (select y from t2 ), :my_value )'
using my_value;

April 25, 2003 John -- Thanks for the question regarding
      "Closing Cursors", version 8.1.7

      You Asked
I have often seen code like this in the exception handler.

  exception
    if my_cursor%isopen then
      close my_cursor;
    end if;

However it is my understanding that all open cursors are automatically closed
when an exception is raised initially and prior to the execution of code in the
exception handler.  Is this correct?  Is there any advantage in including such
code in the exception handler?

      and we said...
it is not correct.

ops$tkyte@ORA920> create or replace package demo_pkg
  2  as
  3          cursor c1 is select * from dual;
  4
  5          procedure p;
  6  end;
  7  /

Package created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package body demo_pkg
  2  as
  3
  4          procedure p
  5          is
  6                  cursor c2 is select * from dual;
  7          begin
  8                  open c2;
  9                  open c1;
 10                  raise program_error;
 11          end;
 12  end;
 13  /

Package body created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec demo_pkg.p
BEGIN demo_pkg.p; END;

*
ERROR at line 1:
ORA-06501: PL/SQL: program error
ORA-06512: at "OPS$TKYTE.DEMO_PKG", line 10
ORA-06512: at line 1

ops$tkyte@ORA920> exec demo_pkg.p
BEGIN demo_pkg.p; END;

*
ERROR at line 1:
ORA-06511: PL/SQL: cursor already open
ORA-06512: at "OPS$TKYTE.DEMO_PKG", line 3
ORA-06512: at "OPS$TKYTE.DEMO_PKG", line 9
ORA-06512: at line 1

That example shows that C2 -- the local cursor -- was implicitly closed when it
went out of scope, however, the other cursor C1 (on line 9), is not closed.

So, it depends on the SCOPE of the cursor itself as to whether or not it is
needed.

Review &
      Followup

      Rating: 5
Did not know
      Scope had any impact!  April 25, 2003
Reviewer:
      A reader

      Rating: 5
Closing
      Cursors  April 25, 2003
Reviewer:  Kumar
       from Dallas,TX
But is the Implicit cursor (C2) automatically closed before entering the
exception handler?

Can we assume that local cursors will be closed always or are there any other
considerations?  Does this mean that when I use a local cursor, I do not have to
add extra cursor checking code to my exception handler.

ex.
exception
when others then
  if c2%isopen then
    close c2;
  end if;
  raise;
end;

          Followup:

the local static cursors are closed for you.

you can see this via:

ops$tkyte@ORA920> show parameter open_cursor

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
open_cursors                         integer     300
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package demo_pkg
  2  as
  3          procedure p;
  4  end;
  5  /

Package created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package body demo_pkg
  2  as
  3          procedure p
  4          is
  5                  cursor c is select * from dual;
  6          begin
  7                  open c;
  8          end;
  9  end;
 10  /

Package body created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2          for i in 1 .. 10000
  3          loop
  4                  demo_pkg.p;
  5          end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

if it were not closed, it would have blown up.

April 25, 2003 Balaji -- Thanks for the question regarding "Exit
      From SQLPLUS ", version 8.0.5

      You Asked
I have series of sql queries written and called one after the
other in the same script.  I would like to quit the application
based on the success of the sql query.  Say for eg. If my
previous query has raised an error due to not enough tablespace,
snapshot too old or cannot extend the extents or any other error
for that matter.  the next sql should not be executed instead I
need to quit the application.

The application will be executed on Unix env by a shell script.
I would like to make minimal changes to the code.  This can be
achieved by coding PL/SQL block but is there any simplier way or
inbuilt function to trap the sql return code, if so can you
provide an example.

      and we said...
ops$tkyte@8i> whenever sqlerror EXIT;
ops$tkyte@8i> select * from non_existent_table;
select * from non_existent_table
              *
ERROR at line 1:
ORA-00942: table or view does not exist

Disconnected from Oracle8i Enterprise Edition Release 8.1.5.0.0
- Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.0.0 - Production

The whenever statment has the following usage:

Usage: WHENEVER SQLERROR
     { CONTINUE  [ COMMIT | ROLLBACK | NONE ]
     | EXIT  [ SUCCESS | FAILURE | WARNING | n | <variable> |
:<bindvariable> ]
             [ COMMIT | ROLLBACK ] }

so you can use that -- you can even use it to pass a return code
(make sure to keep it <= 255, the exit status in unix is a byte)
if you wanted to the shell script.

Review &
      Followup

      Rating: 3
Is there sucha
      thing as whenever 'sqlwarning' or equivalent??  April 16,
      2003
Reviewer:  A reader
Hi Tom, I would like to exit even a package or trigger is compiled with
compilation errors. Is there a setting that would make this happen? sort of a
'whenever sqlwarning', similar to whenever sqlerror.
Thanks.

          Followup:

nope, you could code:

declare x int; begin select 1 into x from user_objects where object_name =
'thing-you-compiled' and object-type = 'type' and status = 'VALID'; end;
/

(or a procedure that does the same that you call passing the name/type).
that'll fail with no data found and exit.

      Rating: 5
Amazing! Great
      solution...  April 16, 2003
Reviewer:
      A reader

      Rating: 5
Hide display of
      error on screen  April 23, 2003
Reviewer:
      A reader
Tom, that indeed was a good suggestion and I wrote a proc to do that. However,
is there a way to prevent the error output from showing up on the screen? set
termout off doesn't seem to do it. Thanks.

BEGIN chk_obj ('pkg_tes','package body'); END;
*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at "TES.CHK_OBJ", line 6

          Followup:

you must not be running it from a script?

set termout off only works for scripts -- are you typing it in by hand?

      Rating: 4
Whenever
      command in a sql statement  April 24, 2003

Reviewer:  Randy Casey  from USA
Thanks...  I knew there was a way to do this, but was not exactly sure how to.

      Rating: 5
Display errors
      from user_errors and then exit  April 24, 2003

Reviewer:  A reader
Tom, is it possible to take it a step further as follows: upon encountering an
invalid object, the chk_obj procedure  would display the errors from user_errors
and then exit.

          Followup:

yes, no, not really but sort of.

instead of:

declare x int; begin select 1 into x from user_objects where object_name =
'thing-you-compiled' and object-type = 'type' and status = 'VALID'; end;
/

code:

set serveroutput on
set feedback off
spool /tmp/temp.sql
begin
  for x in ( select * from user_objects
               where object_name = 'thing-you-compiled'
                 and object_type = 'type'
                 and status <> 'VALID' )
  loop
    dbms_output.put_line( 'show errors ' || 'type' || ' ' || 'thing' );
    dbms_output.put_line( 'exit' );
  end loop;
end;
/
spool off
set feedback on
@/tmp/temp.sql

temp.sql will either have something like:

show errors procedure p
exit

or "nothing" in it.

      Rating: 4
Similar
      question !!  April 25, 2003
Reviewer:  Anand
       from Atlanta
Tom,

I have a main SQL script that in-turn runs many other scripts. The process is
such that incase of ANY error in the script, it should exit imdtly without
proceeding further. So, I used "WHENEVER SQLERROR EXIT" command at the top of
the script. In one of the scripts, I am hitting ORA-1562 and ORA-1628. But, the
script wouldn't stop at this point. It just continues with the rest of the
script. That should NOT happen. What could be your solution to this scenario ?

This is the spooled output ...

***************************************

Executing Driving.SQL.......

628006 rows created.

Insert into cons.all_campaign_response
*
ERROR at line 1:
ORA-01562: failed to extend rollback segment number 10
ORA-01628: max # extents (121) reached for rollback segment R09

1 row created.

Table truncated.

Commit complete.
**************************************

Thanks a bunch for your help.

          Followup:

grep your scripts -- look to see who has stuck a "whenever sqlerror continue" in
there on you.

April 25, 2003 Cameron -- Thanks for the question regarding
      "Trick for calling package.procedures dynamically", version 9.0.1

      You Asked
Hi Tom.
--
You have so many nice little tricks up your sleeve I was wondering whether you
had one which would enable the dynamic calling of a packaged procedure/function.
(Preferably not C++ api, 'cause I can't even spell C++)
--
I'm trying to get away from case statements(some of which can be quite large) as
in
--
  idx := alias_tab.first;
  lidx := alias_tab.last;
  While idx <= lidx Loop
    --
    If Lower(alias_tab(idx)) = 'ipea' Then
      Ipea.get_using_fk_key_ipea(io_clob, rec.id, hierarchy_list);
    Elsif Lower(alias_tab(idx)) = 'ipgndr' Then
      Ipgndr.get_using_fk_key_ipgndr(io_clob, rec.id, hierarchy_list);
    End If;
    --
    idx := alias_tab.Next(idx);
    --
  End Loop;
--
and hoping to get towards that would equate to :-

  execute immediate Lower(alias_tab(idx)) || '.get_using_fk_key_' ||
Lower(alias_tab(idx)) || '(:io_clob, :recid, :list)'
                    into return_value
                    using io_clob, rec.id, :hierarchy_list;
--

      and we said...
execute immediate
'begin ' ||
  alias_tab(idx) || '.get_using_fk_key_' || alias_tab(idx) ||
 '( :a, :b, :c );
 end;'
using IN io_clod, IN OUT rec.id, OUT hierarchy_list;

using IN, IN OUT, OUT as appropriate (i don't know what is in/in out/out in your
case exactly -- but thats OK, you do)

ops$tkyte@ORA920> create or replace package demo_pkg
  2  as
  3          procedure test( x in number, y in out number, z out number );
  4          procedure p;
  5  end;
  6  /

Package created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package body demo_pkg
  2  as
  3
  4  procedure test( x in number, y in out number, z out number )
  5  is
  6  begin
  7          y := y*x*2;
  8          z := y/55;
  9  end;
 10
 11  procedure p
 12  is
 13          a   number := 3;
 14          b   number := 4;
 15          c   number;
 16  begin
 17          dbms_output.put_line
 18          ( a || ',' || b || ',' || c );
 19          execute immediate
 20          'begin demo_pkg.test( :x, :y, :z ); end;'
 21          using IN a, IN OUT b, OUT c;
 22          dbms_output.put_line
 23          ( a || ',' || b || ',' || c );
 24  end;
 25
 26  end;
 27  /

Package body created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec demo_pkg.p
3,4,
3,24,.4363636363636363636363636363636363636364

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating:
      5
  April 25, 2003
Reviewer:
      A reader

      Rating: 5
Trick for
      calling package.procedures dynamically  April 25, 2003

Reviewer:  Kevin Balfe  from Laurel, MD
Thanks.

      Rating:
      5
  April 25, 2003
Reviewer:  Robert
       from Memphis, USA

      Rating:
      5
Response  April 25, 2003
Reviewer:
      Cam  from Bristol, UK
Thanks Tom.

Works a treat but...

Giving it further thought, I'm not sure if I can use as I had originally
intended.

The declaration in the procedure being called is
(io_clob in out NOCOPY clob......

The 'using' clause suggested is

   using in out io_clob, in rec.id, in hierarchy_list;

Am I about to shoot myself in the foot by passing 'io_clob' by "value" rather
than by "reference" in making the call dynamically?

  (using in out NOCOPY io_clob. -- does not compile).

          Followup:

clobs are actually very very tiny.  they are lob locators -- we are not pushing
and popping the entire clob off off the stack.

CLOBS are pointers to begin with -- nocopy has virtually no effect on them.

ops$tkyte@ORA920> create or replace package demo_pkg
  2  as
  3          procedure p1( x in out nocopy clob );
  4          procedure p2( x in out clob );
  5  end;
  6  /

Package created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package body demo_pkg
  2  as
  3          procedure p1( x in out nocopy clob )
  4          is
  5                  y number;
  6          begin
  7                  y := 1;
  8          end;
  9          procedure p2( x in out clob )
 10          is
 11                  y number;
 12          begin
 13                  y := 1;
 14          end;
 15  end;
 16  /

Package body created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> drop table t;

Table dropped.

ops$tkyte@ORA920> create table t ( x clob );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          l_clob clob;
  3          l_n    number;
  4  begin
  5          insert into t values ( empty_clob() ) returning x into l_clob;
  6          dbms_lob.writeAppend( l_clob, 1, 'x' );
  7          dbms_output.put_line( dbms_lob.getlength(l_clob) );
  8
  9          l_n := dbms_utility.get_time;
 10          for i in 1 .. 10000
 11          loop
 12                  demo_pkg.p1( l_clob );
 13          end loop;
 14          dbms_output.put_line( round( (dbms_utility.get_time-l_n)/100, 2 )
|| ' hsecs' );
 15
 16          l_n := dbms_utility.get_time;
 17          for i in 1 .. 10000
 18          loop
 19                  demo_pkg.p2( l_clob );
 20          end loop;
 21          dbms_output.put_line( round( (dbms_utility.get_time-l_n)/100, 2 )
|| ' hsecs' );
 22
 23
 24          for i in 1 .. 100
 25          loop
 26                  dbms_lob.writeAppend( l_clob, 32000, rpad('*',32000,'*') );
 27          end loop;
 28          dbms_output.put_line( dbms_lob.getlength(l_clob) );
 29
 30          l_n := dbms_utility.get_time;
 31          for i in 1 .. 10000
 32          loop
 33                  demo_pkg.p1( l_clob );
 34          end loop;
 35          dbms_output.put_line( round( (dbms_utility.get_time-l_n)/100, 2 )
|| ' hsecs' );
 36
 37          l_n := dbms_utility.get_time;
 38          for i in 1 .. 10000
 39          loop
 40                  demo_pkg.p2( l_clob );
 41          end loop;
 42          dbms_output.put_line( round( (dbms_utility.get_time-l_n)/100, 2 )
|| ' hsecs' );
 43  end;
 44  /
1
.01 hsecs
.02 hsecs
3200001
.01 hsecs
.02 hsecs

PL/SQL procedure successfully completed.

      Rating: 4
Got a doubt
      !!  April 25, 2003
Reviewer:  Anand
       from Atlanta, US
Tom,

I see that this thread was first posted today (Apr 25, 2003). I wonder how this
person posted a new question. I am a regular visitor to this site and all I see
is a message "Sorry I have a large backlog right now, please ask a question
later" on the home page.

How did this person post a question today and what is the trick? Let me also
learn the trick :)

Thanx, Tom

          Followup:

it was put in at 8:17am east coast time -- which makes sense, I got in at 7am
this morning and opened the queues.  these are the times I took new questions
today:

TIMESTAMP
--------------------
25-apr-2003 07:08:28
25-apr-2003 07:20:49
25-apr-2003 07:21:31
25-apr-2003 07:30:39
25-apr-2003 07:34:26
25-apr-2003 07:34:45
25-apr-2003 07:38:18
25-apr-2003 07:41:50
25-apr-2003 07:43:22
25-apr-2003 07:45:13
25-apr-2003 08:17:05
25-apr-2003 09:32:55
25-apr-2003 09:35:32
25-apr-2003 09:38:32
25-apr-2003 09:39:07
25-apr-2003 09:40:36
25-apr-2003 09:46:30
25-apr-2003 09:48:55
25-apr-2003 09:49:50
25-apr-2003 09:52:40
25-apr-2003 09:53:04
25-apr-2003 09:53:19
25-apr-2003 09:53:49
25-apr-2003 10:03:06
25-apr-2003 10:25:03

25 rows selected.

      Rating: 5
Pretty Cool
      !!!!  April 25, 2003
Reviewer:
      A reader

      Rating: 5
My question
      is....  April 25, 2003
Reviewer:  Anand

What time of the day is the link enabled so as to enable us to start a new
thread, I mean.. which part of the day I won't see the message "Sorry, I have a
large backlog........"

Thanx Tom

          Followup:

it is whenever I get around to it.

I have a button that says "allow N more questions in"....

I hit it when I'm available

      Rating: 5
Read
      this!  April 25, 2003
Reviewer:
      A reader
If you go to this link and read it carefully, you'll know exactly when Tom takes
new questions

http://asktom.oracle.com/pls/ask/f?p=4950:40:
Good Luck!

      Rating:
      5
Thanks  April 25, 2003
Reviewer:
      Anand
Thanks for your quick turnaround time !

      Rating:
      5
Thanks  April 28, 2003
Reviewer:
      Cam  from England
Tom, thanks, you've been a great help.

April 25, 2003 Satyendu -- Thanks for the question regarding
      "Database Security", version 8.1.7

      You Asked
Hi Tom,

Followings are my requirement.

1)
We have 2 database A and B. Say for example Schema Owner of DB A is "Scott" and
for database B is "test". I want to give access SELECT access to all users in DB
A to some of the objects available ib DB B and the schema owner is "test".

My current situation is I have created a public DB link and I have given all
access to all users in DB A for those objects which are available in DB B.

Is there any easiest way where I can implement the same by considering the
DATABASED SECURITY features ?

2)What is the difference between Public RBS and Private RBS ? In what scenario
we will be using those 2 RBS ?

3) What should be the size of a RBS for a typical 30 GB OLTP DB ? What all
parameters we need to consider for deciding about the RBS size. Is there any
thumb-rules ?

4) What all security features we need to consider if we have multiple Database
in our Applications ? Our application is based on Power Builder 7.0 and Oracle
8i.

5) In one of the packages, we are creating a PUBLIC synonym. The owner of the
package is say e.g "scott". Suppose another user "aboot" in that DB is executing
that package and the owner of the package "scott" has given execute privileges
to a ROLE "X" which in turn has been assigned to user "aboot". The user "scott"
does not have create public synonym, rather the role "X" has. But the user
"aboot" is not able to execute that package since there is no explicit CREATE
PUBLIC SYNONYM privileges assigned to "scott". Is there any other way where I
can prevent this problem. I don't want to assign "CREATE PUBLIC SYNONYM"
explicitely to user "scott". Rather I would prefer through ROLE. Pl advise

Best Regards,
SP

      and we said...
1) You should do this:

a) in database B create a user "public_read" or something similar.
b) in database B grant select on test.table_name for all tables you want "global

   access" to.
c) in database A create public database link database_b connect to public_read
   identified by password using 'connect_string'

Now, everyone that uses database_b can "select" those tables. This is known as a
proxy database link.

2) only matters if you are using OPS (RAC in 9i).  A public rbs can be used by
many instances.  private RBS's are used by a single instance.  If you do not use
OPS / RAC, just make them all public.

3) I despise ROT -- rules of thumb.  They never apply to many cases, making them
useless (cause when people blindly follow them - and they use them in a case
where they don't apply -- all heck breaks lose).

Asking what the size of RBS for a 30gig OLTP system is like asking what shoe
size you need to order for 30 guys.  Makes no sense.  30gig might need more then
3000 gig.  30gig might need less then 3gig.  30gig might need less then 3000
gig.  and so on....  The easy answer is to use 9i and UNDO tablespaces and let
the system do it.  The harder answer is

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:233214228495
4) impossible to answer in anything short of a BOOK.  You would really need to
be a little more specific.

5) Scott needs create public synonym granted DIRECTLY

see

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html

Review &
      Followup

      Rating: 3
Oracle xpert !
      eh ?  December 09, 2002
Reviewer:
      A reader
Hey Tom,

I was just going thru ur answers to the questions in this site & looks like u r
really an xpert & a Guru in Oracle....

Tell me from where / how u learnt Oracle ? Even I need to learn from the same
place..... hahhah...

By the way, I don't think u got this much knowledge on reading ur "Concepts"
guide, rt ?!

          Followup:

Well -- I can offer you one piece of advice that will be very useful to you
forever:

Twying to wead text that is not in any weaw wanguage, but some made up wanguage
that you might use between fwiends wif SMS messages, is weawwy hawd. De pewson
weading it wiww find it vewy difficuwt to fowwow what you awe saying.

that paragraph brought to you by Elmer Fudd courtesy of:

http://www.rinkworks.com/dialect/
what I said was:

Trying to read text that is not in any real language, but some made up language
that you might use between friends with SMS messages, is really hard.  The
person reading it will find it very difficult to follow what you are saying.

I did learn Oracle from the concepts guide.

I did not learn Oracle from "inside magic information".

It was a mixture of reading lots about databases in general -- transaction
processing concepts, data management, data modeling, etc etc etc.  Just learning
about this thing called a database.  And then -- focusing in on my database of
choice after trying sybase, informix, db2, ingres, sqlbase, among others.

Oh -- yeah -- the most important point, it takes years to do this.

But I would blame most of what I know on that concepts guide, yes.

      Rating:
      5
Funnies  December 09, 2002
Reviewer:
      George Johnson  from Stevenage, UK
Thank you not only for your expert answers to the sensible questions, but also
for your biting wit and satire applied to the "oddball" questions. It brightened
up my usually dreary lunchtime! I especially enjoyed the answers to "Database
security" & "Some clarifications" articles.

Thanks again.
George J

      Rating: 5
I loved that
      punch!!!  December 09, 2002
Reviewer:
      A reader

      Rating:
      5
  December 10, 2002
Reviewer:
      Kalita  from UK
For once, I thought Tom's keyboard is also malfunctioning :;-)) Tom, why don't
you make the Name field mandatory so that people are discouraged to use 'A
reader'.

          Followup:

they would just type in "a reader" ;)

or what I've been know to put in on web sites that make you identify yourself:

name: barney fife
address: 1 main street, mayberry rfd
email bfife@mayberry.rfd

;)

      Rating: 5
Very
      funny!  April 25, 2003
Reviewer:
      A reader  from Ca, USA
:)

Hi Tom, are there any books on data modeling that you can recommend? thanks.

          Followup:

there are tons -- i got mine from a text book way back (forget what one it was,
probably out of print).  sorry  -- no current recommendation..

April 26, 2003 Bhaskar -- Thanks for the question regarding
      "Execute Procedure in Dbms_job ", version 8.1.7

      You Asked
Hi Tom,

I want to submit a job which would execute once a day . The problem is that the
procedure I am submitting contains OUT parameters therefore its giving error
while executing the job.How jobs having OUT parameter are handled while
submiting a job.The procedure gets compiled successfully but gives error while
executing.

PROCEDURE INSURANCE_DOWNLOAD_PROC
 Argument Name                  Type                    In/Out
---------- ----------------------- ------ --------
 P_CURRENTDATE                  DATE                    IN
 P_UID                          VARCHAR2                IN
 ERROR_NO                       NUMBER                  IN/OUT
 ERROR_TEXT                     VARCHAR2                IN/OUT

procedure test_dbms as
 l_job number;
 l_error number;
 l_msg varchar2(1000);
 l_uid varchar2(100):='BHASKAR';
 l_date date :='12-dec-2010';
 BEGIN
DBMS_JOB.SUBMIT(l_job,'INSURANCE_DOWNLOAD_PROC(
''12-dec-03'',''bhaskar'',l_error,l_msg);');
commit;
End;

Log Error :

Errors in file /export/home/oracle/app/oracle/admin/uat/bdump/uat_snp0_666.trc:
ORA-12012: error on auto execute of job 4
ORA-06550: line 1, column 121:
PLS-00330: invalid use of type name or subtype name
ORA-06550: line 1, column 93:
PL/SQL: Statement ignored
ORA-06512: at "SYS.DBMS_JOB", line 79
ORA-06512: at "SYS.DBMS_JOB", line 131
ORA-06512: at "LENDING.TEST_DBMS", line 8
ORA-06512: at line 1

I also want to know that how end user will come to know if the job fails.We can
see the log file but end user would'nt know about it.

Regards,

Bhaskar.

      and we said...
procedure test_dbms as
 l_error number;
 l_msg varchar2(1000);
 l_uid varchar2(100):='BHASKAR';
 l_date date := to_date( '12-dec-2010', 'dd-mon-yyyy');
BEGIN
   INSURANCE_DOWNLOAD_PROC( l_date, l_uid, l_error, l_msg );
   if ( l_error = 0 ) then
       commit;
   else
       rollback;
       raise_application_error( -20001, l_msg );
   end if;
End;

and then in sqlplus:

variable n number
exec dbms_job.submit( :n, 'test_dbms;', sysdate, 'trunc(sysdate)+1+8/24' );

(to run at 8am every day -- change 8 to be the hour you want to run at)

You need to wrap the procedure in yet another procedure that takes the OUT
parameters and does something about them.  In this case, I think you need to
look at the error code and raise an error which will put the error in the alert
log and increment the failures column in dba_jobs so you KNOW it is failing.

Review &
      Followup

      Rating:
      4
  January 06, 2003
Reviewer:
      Bhaskar  from TOKYO
Hi Tom,
Well i executed that thing and it worked fine.
Thanks for the reply !!
Bhaskar

      Rating: 5
How about no
      wrapping at all?  April 25, 2003
Reviewer:
      Doug  from CT, USA
Tom, are there any risks with just passing in a simple statement?

SQL> begin
  2  dbms_job.isubmit(40,'delete from ORDER_SUB where end_date <= sysdate;',
  3  trunc(sysdate),'TRUNC(SYSDATE+1)');
  4  end;
  5  /

It will commit?
It will break appropriately?

          Followup:

it will work fine.

April 26, 2003 Charanjiv -- Thanks for the question regarding
      "Erorr ORA-1000, maximum open cursors exceeded. ", version

      You Asked
Erorr ORA-1000, maximum open cursors exceeded.
We are running a VC++ application on a Oracle 7.3 server and are
gettting this erorr. We increased the parameter from its default to 200.
But we would like to know how can we find out the noumber and
text of the opem cursors. ?
If i am right open cursors would be uncommited statements ?

Thanx
Charanjiv

      and we said...
No, open cursors would be cursors you OPENed but never closed or they would be
cached cursors that plsql keeps open (if you are using lots of stored
procedures, you'll see the cached cursors from these).  Cursors stay open across
commits normally.

You can see the cursors you have open (all open cursors in the database) by
reviewing the dynamic performance view
v$open_cursors.  You can use this to track down cursors you have opened in your
application but have forgotten to close or to verify the cursors are being
cached by plsql for you.

I keep my open cursors set at about 1,000.  Cursor entries are aquired 64 at a
time, regardless of the open cursor setting, so setting to a high number will
not affect memory consumption for all clients -- just those that need it.

See also the documentation for "close_cached_open_cursors" in the server
reference guide if you really want plsql to close cached cursors upon commit
(this parameter is obsoleted in 8.1 -- a new algorithm is used and this is no
longer needed).  I don't recommend setting this but some people want to anyway.

followup to comment one below

well, it varies by language.

in jdbc, preparedStatement.close() does it.

In pro*c EXEC SQL CLOSE CURSOR_NAME;  does it.

In OCI -- there is an API call to close a statement

and so on.

followup to comment three

No, no it is not.

Open cursors are not uncommitted statements.  Open cursors are -- lo and
behold -- cursors you have not closed.

If you are in JDBC and you let a statement handle go out of scope (don't have
any idea what a loop might have to do with it except that I could use a loop to
exhaust open cursors rapidly), you will have open cursors that you can no longer
get to to close.

But the same would be true if you opened a file and didn't close it properly.
Just consider a statement a file, eventually, when you are done and will never
need that file again -- close it.  Same with the cursors (statements).  When you
are done (and not before, if you might reuse it, don't close it) with the
statement -- close it and you will not have this error.

This error is 100% caused by a programmer leaking cursors, if you replace the
database statements with file opens, you would be running out of file
descriptors -- same issue.

Do NOT close each query after it is executed, you should prepare a statement at
most ONCE per program execution and then rebind/execute it over and over.  If
there is a chance that you will execute the statement again, keep it open (don't
let it go out of scope!)

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1478004815829
for why the advice to close after each query is executed is potentially bad
advice.

Review &
      Followup

      Rating:
      5
  July 23, 2001
Reviewer:  Kavitha
       from Birmingham, UK
The explanation is very clear and to the point. But what I can't find here is
information on how to clear the open cursors. If that is added to this, then it
would be great!

      Rating: 3
still receive
      "max cursors open" error  July 23, 2001
Reviewer:
      Michael
I have a question about pro*c cursors under SUN OS 5.6 and Oracle 8.1.5.
      I write a pro*c program to implement authentication ,after every user
logon DB,immediate execute an sql select (EXEC SQL SELECT ....) to identify his
status.I have set OPEN_CURSORS=300 in DB ,but if I have more than 100 users
logon,I still receive  "max cursors open" error. And I have set HOLD_CURSOR=NO
and RELEASE_CURSOR=YES in pro*c config file,have no benefit.
     Can I only increase OPEN_CURSORS?

      Rating: 2
Yes it
      is  July 24, 2001
Reviewer:  ShyamKumar
      Reddy  from Chennai,India
In JDBC
If you run your query in Loops.
And not closing the Statement Object. This error will occur. So after each query
is completed Please close the Statement Object. This error is avoided

      Rating: 5
MAX open
      cursors  April 24, 2003
Reviewer:  G.V.N.
       from Houston
Tom,
We are getting the following error when connecting from a oracle 9i DB (Verson
9.2) to the oracle report server(Reports builder ver. 6.08).
Erorr ORA-1000, maximum open cursors exceeded

My questions
1)Does the value of open_cursors in the oracle ini file include implict cursors?
2)How can I find out he numbers of cursors used by a particular report?

          Followup:

1) it covers all cursors -- inside the kernel there is no such thing as implicit
or explicit -- that is purely a coding thing.  it counts all open cursors.

2) look in v$sesstat for open_cursors

      Rating: 4
How to find out
      # of open cursors?  April 25, 2003
Reviewer:
      Saminathan Seerangan  from CA,USA
Dear Tom,

How to find out the number of open cursors?

For example, i set open_cursors value to 10 but # of records in v$open_cursor
view is 51.

SQL> show parameter open_cursors
NAME                  TYPE        VALUE
--------------------- ----------- -------------------
open_cursors          integer     10

SQL> select count(*) from v$open_cursor;
  COUNT(*)
----------
        51

SQL> select count(distinct sql_text) from v$open_cursor;

COUNT(DISTINCTSQL_TEXT)
-----------------------
                     26

COUNT(DISTINCTSADDR)
--------------------
                   8

COUNT(DISTINCTHASH_VALUE)
-------------------------
                       26

Tom, Could you please explain little bit?

TIA

      Rating: 5
Thanks Tom, Got
      the answer from this forum itself  April 25, 2003

Reviewer:  Saminathan Seerangan  from CA,USA

open_cursor only for connected session
=======================================
select a.value, b.name
from v$mystat a, v$statname b
where a.statistic# = b.statistic#
and a.statistic#= 3;

all the open cursors for the database
=====================================
select sum(a.value), b.name
from v$sesstat a, v$statname b
where a.statistic# = b.statistic#
and b.name = 'opened cursors current'
group by b.name;

      Rating: 5
MAX open
      cursors  April 25, 2003
Reviewer:  G.V.N.
       from Houston
1) If the open_cursors in the oracle ini file is set to 50 that means a session
can use a max of 50 cursors.  The value of open_cursors in the ini file is for a
particular session - right?
2) Every select statement in a report or
every statements like
for i in (select ... from table_name) ....adds to the number of open cursors
Is that right?
3) If I have a procedure as shown below (copied from one of your other listings)

set timing on
declare
l_dummy dual.dummy%type;
begin
for i in 1 .. 10000
loop
select dummy into l_dummy from dual this_is_implicit;
end loop;
end;
/

How many cursors are opened?  Is it 1 or 10000
Since implict cursors are opened, fetched and closed automatically - We should
use no more than 1 cursor, Is that right?

Is there a way for a developer(not DBA) to find out the number of cursosrs used?
 If we have to query the tables v$sesstat, v$statname you would either need to
have DBA privileges or select privilege on these tables

Thank you

          Followup:

1) correct
2) yes and no.  that select in your example is only open inside the cursor for
loop. it is closed outside of it.
3) 2 -- one for the plsql block itself and one for select dummy from dual.

you need select privs on those objects if you want that information.

      Rating: 5
MAX open
      cursors   April 25, 2003
Reviewer:  G.V.N.
       from Houston
Thank you

      Rating: 4
v$sesstat
        April 26, 2003
Reviewer:  reader

tom,

> 2) look in v$sesstat for open_cursors

ge$cpmdev@deveccpm>sho parameters open_cursors

NAME                                 TYPE    VALUE
==================================== ======= ==============================
open_cursors                         integer 300
ge$cpmdev@deveccpm>select count(*)  from v$sesstat where SID=13;

  COUNT(*)
==========
       248

ge$cpmdev@deveccpm>select sum(VALUE) from v$sesstat where SID=13;

SUM(VALUE)
==========
2103335632

1) what is this value? ... why is it such a big number?

2) my session (SID=13) has used 248 cursors - right?

Thank you !

          Followup:

v$sesstat contains lots of statistics -- join it to v$statname to see them by
name.

The first query -- count(*) -- shows we are tracking 248 separate statistics for
you.

The seconds query adds up how much pga memory, max pga memory, uga memory, max
uga memory, sorts, sorts to disk, CURSORS, etc etc etc etc you did.  It is
meaningless.

this does not show sid 13 has 248 cursors.

select a.name, b.value
 from v$sesstat b, v$statname a where a.statistic# = b.statistic#

      Rating: 4
how many
      cursors are used by my session?  April 26, 2003

Reviewer:  reader
how to know, how many cursors are used by my session?

thanks

          Followup:

join v$mystat to v$statname and look at open cursors.

or add "and sid = ..." to the above query.

      Rating: 4
Use the
      following query  April 26, 2003
Reviewer:
      Saminathan Seerangan  from CA,USA
For your session,

select a.value, b.name
from v$mystat a, v$statname b
where a.statistic# = b.statistic#
and a.statistic#= 3;

all the open cursors for the instance

select sum(a.value), b.name
from v$sesstat a, v$statname b
where a.statistic# = b.statistic#
and b.name = 'opened cursors current'
group by b.name;

          Followup:

use statnames, not numbers -- we change them over time i've discovered.

April 27, 2003 shan -- Thanks for the question regarding "Is it
      possible to add a Partion on existing table", version 9.0.1

      You Asked
Hi Tom,
I have a table called SO, It is not partioned, I need to partion based on the
column trans_date. If trans_date's month Jan means partition m1, feb means m2 ..
upto 12 partition. I don't want to recreate the table. How can I alter the table
with the above 12 partion. I triled by using the following syntax, ALTER TABLE
pt ADD PARTITION p3 VALUES LESS THAN (somevalue); But I got ORA-14501: object is
not partitioned Error. Thanks in advance.

With best regards
shan

      and we said...
Well, one way or another, you'll have to recreate the table -- there is quite
simply NO WAY AROUND that.  Think about it -- you have an existing table full of
data.  Rows with jan are next to feb are next to mar.  The entire table needs to
be rewritten.

You can use online redefinition to accomplish this if you like (or just

a) export the table
b) create a new empty table that has the partition definition on it
c) import the table with IGNORE=Y

Here is an online redef example.

=================================================================

Lets build a table we'll want to redefine.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create table t NOLOGGING
  2  as
  3  select * from all_objects;

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

ops$tkyte@ORA920.US.ORACLE.COM> alter table t add constraint t_pk primary
key(object_id);

Table altered.

ops$tkyte@ORA920.US.ORACLE.COM> exec dbms_redefinition.can_redef_table( user,
'T' );

PL/SQL procedure successfully completed.

=================================================================

Here is the new table we want to reorg into , we are dropping some
NOT NULL constraints, creating a derived column flags from other columns
and getting rid of some columns -- renaming others (and partitioning it)

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create table t_interim
  2  (
  3    object_id     number primary key,
  4    object_type   varchar2(18),
  5    owner         varchar2(30),
  6    oname         varchar2(30),
  7    created       date,
  8    last_ddl_time date,
  9    status        varchar2(7),
 10    flags         varchar2(5)
 11  )
 12  partition by hash(object_id) partitions 8
 13  /

Table created.

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

=================================================================
This block of code  starts moving the data over for us.  changes
to the base table are being recorded off to the side while this happens..

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> declare
  2          l_colmap varchar(512);
  3  begin
  4          l_colmap :=
  5           'object_id,
  6                object_type,
  7                owner,
  8                object_name oname,
  9                created,
 10                last_ddl_time,
 11                status,
 12            temporary || ''/'' || generated || ''/'' || secondary flags ';
 13
 14          dbms_redefinition.start_redef_table
 15          ( uname => user,
 16            orig_table => 'T',
 17            int_table => 'T_INTERIM',
 18            col_mapping => l_colmap );
 19  end;
 20  /

PL/SQL procedure successfully completed.

=================================================================
here you would
- index t_interim
- grant on it
- add constraints
- add triggers
- add EVERYTHING you need to make t_interim look just like T,
  behave just like T
=================================================================

Now we'll sync them -- to speed up the "finish".  The finish will
be a non-online operation -- so we want it to be fast

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> begin
  2     dbms_redefinition.sync_interim_table( user, 'T', 'T_INTERIM' );
  3  end;
  4  /

PL/SQL procedure successfully completed.

=================================================================

Tables are synced, we are ready to rollover

ops$tkyte@ORA920.US.ORACLE.COM> begin
  2     dbms_redefinition.finish_redef_table( user, 'T', 'T_INTERIM' );
  3  end;
  4  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> set echo off
=================================================================

This finished the process, this is the ONLY time the table
is in fact locked. We can verify the tables are swapped now

Enter to continue

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> desc t
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
------------------------------------
 OBJECT_ID                                             NOT NULL NUMBER
 OBJECT_TYPE                                                    VARCHAR2(18)
 OWNER                                                          VARCHAR2(30)
 ONAME                                                          VARCHAR2(30)
 CREATED                                                        DATE
 LAST_DDL_TIME                                                  DATE
 STATUS                                                         VARCHAR2(7)
 FLAGS                                                          VARCHAR2(5)

ops$tkyte@ORA920.US.ORACLE.COM> desc t_interim
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
------------------------------------
 OWNER                                                 NOT NULL VARCHAR2(30)
 OBJECT_NAME                                           NOT NULL VARCHAR2(30)
 SUBOBJECT_NAME                                                 VARCHAR2(30)
 OBJECT_ID                                             NOT NULL NUMBER
 DATA_OBJECT_ID                                                 NUMBER
 OBJECT_TYPE                                                    VARCHAR2(18)
 CREATED                                               NOT NULL DATE
 LAST_DDL_TIME                                         NOT NULL DATE
 TIMESTAMP                                                      VARCHAR2(19)
 STATUS                                                         VARCHAR2(7)
 TEMPORARY                                                      VARCHAR2(1)
 GENERATED                                                      VARCHAR2(1)
 SECONDARY                                                      VARCHAR2(1)

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off

Table dropped.

Table dropped.

ops$tkyte@ORA920.US.ORACLE.COM> quit
Review &
      Followup

      Rating: 4
Partion on
      existing table  July 18, 2002
Reviewer:
      shan  from Singapore
Thanks Mr.Tom. The information is very useful to me.
Another clarification, I have table SO this is my master
table. Then I have following transaction table. SOI(Sales_order_items),
SOID(Sales_order_item_details), SOP(Sales_order_payments). If I do the partition
for master table, will it take  care of all transaction tables records ? That
is, Is any way of Group Partition based on master deatil relation ship.
Basically I need to do common logical partition for all the above four tables
based master detail relation ship.

          Followup:

You would partition each table independently of every other table.  Partitioning
is at the segment level only -- no cross segment.  A partitioned parent table
will not affect the child tables physical characteristics at all.

      Rating: 4
Partion on
      existing table  July 18, 2002
Reviewer:
      shan  from Singapore
Thank your very much for your information.  I have a table called customer.
It contains a column id it has the value like following
ID
-------------------
S0142877B
S0113547C
S2200626F
S1465471B
S2665992B
S1174972D
S2500848B
S7838243A
         The last character has some uniqeness. I want to createi partition
based on the last character.
RangeA  means last charter A,
RangeB  means last charter B,
RangeC  means last charter C,
RangeD  means last charter D,
RangeE  means last charter E,
RangeO means  last character may be other value. Can you pls give me some
solutions.

          Followup:

You'll need to create another column that is this character and partition based
on that.

      Rating: 4
Partion on
      existing table  July 18, 2002
Reviewer:
      shan  from Singapore
Thanks Mr.Tom. The table have no data now. I can drop and re-create a table with
additional column. Then how to do the Partition, can you give me a example ?

          Followup:

Sorry, for that (something so basic) I'll just refer you to the documentation.
You should learn about the feature before even attempting to use it.

      Rating: 5
Partitioning
      Example   July 18, 2002
Reviewer:  Jan van
      Mourik  from Houston, TX
Take a look here:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96524/c12parti.htm#464767

      Rating: 5
Table
      partition  September 18, 2002
Reviewer:
      Neena  from Malaysia
Very good.
I am having a  partitioned table which is partitioned on date. I had defined a
partition named cust_aug for which values is less than '20020831'. I want to
redefine the value as less than '20020901', the partition is having data in it.
how can i redefine the value for a partition.
Alter table modify partition command is not working

Regards,

neena

          Followup:

Need more info.  is there a partition "above" this one already -- you would
merge them.  if not, you could add one and merge them.

      Rating: 5
Will the
      foreign key relationships of the redefined table be
      maintained?  December 06, 2002
Reviewer:
      A reader
Hi Tom,

A quick question. Say table t in your example had two child tables, tc1 and tc2,

where t.object_id is the primary key and tc1.object_id and tc2.object_id are
foreign keys. Also, say we create the t_interim table with object_id as the
primary_key. Then after partition via dbms_redefinition, will the fk
relationship between t, tc1 and t, tc2 be maintained? (I can understand that if
I want to partition tc1 and tc2, I would have to do that separately).

Thanks so much, Tom.

          Followup:

You'll want to read:

http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96521/tables.htm#12458
which is "redefining tables online" for all of the details but basically it
works like this.  You create the fkeys to the interim table from the child
tables in a disabled mode.  when we finish the redef, we disable the old fkeys
and enable the news (without having to validate).  consider:

ops$tkyte@ORA920.US.ORACLE.COM> create table t NOLOGGING
  2  as
  3  select * from all_objects;

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> alter table t add constraint t_pk primary
key(object_id);

Table altered.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create table t1( x references t );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM> create table t2( y references t );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> insert into t1 select object_id from t where
rownum <= 100;

100 rows created.

ops$tkyte@ORA920.US.ORACLE.COM> insert into t2 select object_id from t where
rownum <= 100;

100 rows created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create table t_interim
  2  (
  3    object_id        number primary key,
  4    object_type      varchar2(18),
  5    owner         varchar2(30),
  6    oname         varchar2(30),
  7    created       date,
  8    last_ddl_time date,
  9    status        varchar2(7),
 10    flags         varchar2(5)
 11  )
 12  partition by hash(object_id) partitions 8
 13  /

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> declare
  2          l_colmap varchar(512);
  3  begin
  4          l_colmap :=
  5               'object_id,
  6                    object_type,
  7                    owner,
  8                    object_name oname,
  9                    created,
 10                    last_ddl_time,
 11                    status,
 12            temporary || ''/'' || generated || ''/'' || secondary flags ';
 13
 14          dbms_redefinition.start_redef_table
 15          ( uname => user,
 16            orig_table => 'T',
 17            int_table => 'T_INTERIM',
 18            col_mapping => l_colmap );
 19  end;
 20  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> alter table t1 add constraint t1_new_fk foreign
key(x) references t_interim disable;

Table altered.

ops$tkyte@ORA920.US.ORACLE.COM> alter table t2 add constraint t2_new_fk foreign
key(y) references t_interim disable;

Table altered.

ops$tkyte@ORA920.US.ORACLE.COM> select constraint_name, status from
user_constraints where constraint_type = 'R';

CONSTRAINT_NAME                STATUS
------------------------------ --------
SYS_C004733                    ENABLED     <<<== original constraint
T1_NEW_FK                      DISABLED
SYS_C004734                    ENABLED
T2_NEW_FK                      DISABLED

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> begin
  2     dbms_redefinition.sync_interim_table( user, 'T', 'T_INTERIM' );
  3  end;
  4  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> begin
  2     dbms_redefinition.finish_redef_table( user, 'T', 'T_INTERIM' );
  3  end;
  4  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select constraint_name, status from
user_constraints where constraint_type = 'R';

CONSTRAINT_NAME                STATUS
------------------------------ --------
SYS_C004733                    DISABLED       <<< flip flopped the status
T1_NEW_FK                      ENABLED
SYS_C004734                    DISABLED
T2_NEW_FK                      ENABLED

ops$tkyte@ORA920.US.ORACLE.COM> drop table t_interim cascade constraints;

Table dropped.

ops$tkyte@ORA920.US.ORACLE.COM> select constraint_name, status from
user_constraints where constraint_type = 'R';

CONSTRAINT_NAME                STATUS
------------------------------ --------
T1_NEW_FK                      ENABLED
T2_NEW_FK                      ENABLED

ops$tkyte@ORA920.US.ORACLE.COM> drop table t;
drop table t
           *
ERROR at line 1:
ORA-02449: unique/primary keys in table referenced by foreign keys

and that just shows that T is now the parent table

      Rating: 5
Wow, what a
      thorough reply! What about partitioning the
      indexes?  December 09, 2002
Reviewer:
      A reader
Tom, thanks so much for a such a thorough reply on the constraints. I have one
more question on indexes before I partition an existing table. Here is some
background:

I have a non-partitioned table t with 3 million rows as follows:

create table t (
pid number(10),
attrA varchar2(30),
attrB varchar2(30),
attrC varchar2(30)
constraint t_pk primary key (pid)
);

This table also has function based indexes as follows:
create index t_idx_a on t (upper(attrA));
create index t_idx_b on t (upper(attrB));

I would like to partition this table via dbms_redefinition, using pid as the
partition key.  pid is the foreign key in a lot of child tables and hence is
used a lot in joins. There are few other important queries which make use of the
t_idx_a and t_idx_b indexes.

All these indexes are stored in a separate tablespace from data.

My questions are

1) if pid is a sequence-generated number, bearing no significance to our
application as such, what kind of partition would be better - range or hash?

2) what kind of partition, if any, do I need to create on the 3 indexes
mentioned above? Could you kindly provide some guidance? Your help will be
greatly appreciated, as always.

Thanks,
Raj

          Followup:

1) sounds like HASH if you do equi-comparisions (equals).  A hash does not allow
for range scans with partition elimination (eg: where pid between X and Y would
have to look in all partitions -- where pid = X looks in one)....

2) you might not need ANY, again, it depends on how you use them -- how big they
are.  3 million rows isn't really that "large" these days.

If you query "select * from t where upper(attrA) = 'VALUE'" -- you won't want
that index to be locally partitioned (we would have to do 3 index range scans
now instead of just one).  You would want a single global index .

If you query "select * from t where pid = X and upper(attrA) = 'VALUE'" you can
locally partition.

What I do is envision how the data would be laid out PHYSICALLY.  Then, run some
sample queries through your head and ask yourself "can we elimination partitions
down to 1 or will we end up hitting them all".... That'll help you answer "whats
best"

      Rating: 5
Thanks for the
      previous reply. Hash vs. Range for
      equi-comparisons  December 10, 2002
Reviewer:
      A reader
Tom, your recommendation of hash in 1)is so helpful. Yes, equi-comparisons such
as 'where pid = 123' is exactly the kind of queries we do with pid. I am just
trying to understand why you recommended hash over range..wouldn't range also
eliminate partitions in equi-comparisons?

Thanks again, Tom.

          Followup:

but you have nothing by which to "range" on -- that is why.

tell me -- how would you propose to use RANGE on a sequence meaningfully in this
case?

      Rating: 4
I was thinking
      of pre-creating a few partitions..  December 11, 2002

Reviewer:  A reader
Tom,
Forgive my ignorance but I was thinking of pre-creating the a few partitons..
This is not as meaningful as Hash but is it sensible at all to do this?
Thanks.

create table t (
pid number (10)
...
...
partition by range (pid) (
    partition part1 values less than (1000001) tablespace users,
    partition part2 values less than (2000001) tablespace users,
    partition part3 values less than (3000001) tablespace users,
    partition part4 values less than (4000001) tablespace users,
    partition part5 values less than (5000001) tablespace users,
    partition part6 values less than (maxvalue) tablespace users
)
;

          Followup:

No, all of the data would be in part1 pretty much. And then part2. And then
part3 (after a really long time).  What would that do for you?

what is your goal, what are you trying to achieve via partitioning.
List that out on paper.

then ask yourself -- will range or hash help me get there.

      Rating: 4
Add range
      partition - 9iR2 compression feature  February 05, 2003

Reviewer:  Raj
Hi Tom,

I did use hash partition based on the sequence-generated primary key. See my DDL
below. My questions are follows:

Now,

1) Per this discussion on your site
"Table Compression -- in regards to Jan/Feb 2003 article"
I would like to take advantage of this compression feature so that all records
with ins_date > 6 months old would be compressed but still available in this
table rather than be moved to a 'historical' db. How do I add ins_date based
range partition on top of the existing hash partition?

2)The only index I have partitoned is the primary key. All other indexes are
not-partitioned.  Do you recommend any other indexes to be created local
nonprefixed? Here are the different where clauses that are used against this
table.

where pid=123
where devpart.pid = some_other_table.pid
where upper(serial_num) = 'ABC123'
where upper(pin) = 'ABC123'
where c_user_id = 123
where m_user_id = 234

=======================

create sequence seq_devpart;

CREATE TABLE DEVPART (
  PID                     NUMBER(10)    NOT NULL,
  SERIAL_NUM              VARCHAR2(50),
  PIN                     VARCHAR2(8),
  MON                     CHAR(1),
  LEVEL                   NUMBER(10),
  INS_DATE                DATE          NOT NULL,
  MOD_DATE                DATE,
  C_USER_ID               NUMBER(10)    NOT NULL,
  M_USER_ID               NUMBER(10),
  CONSTRAINT PK_DEVPART
  PRIMARY KEY ( PID )
    USING INDEX local
    (partition pk_devpart_idx1 tablespace bigindx,
     partition pk_devpart_idx2 tablespace bigindx,
     partition pk_devpart_idx3 tablespace bigindx,
     partition pk_devpart_idx4 tablespace bigindx,
     partition pk_devpart_idx5 tablespace bigindx,
     partition pk_devpart_idx6 tablespace bigindx,
     partition pk_devpart_idx7 tablespace bigindx,
     partition pk_devpart_idx8 tablespace bigindx)
     partition pk_devpart_idx7 tablespace bigindx,
     partition pk_devpart_idx8 tablespace bigindx)
     STORAGE (  PCTINCREASE 0 )
)
partition by hash(pid)
(
partition devpart1 tablespace bigusers,
partition devpart2 tablespace bigusers,
partition devpart3 tablespace bigusers,
partition devpart4 tablespace bigusers,
partition devpart5 tablespace bigusers,
partition devpart6 tablespace bigusers,
partition devpart7 tablespace bigusers,
partition devpart8 tablespace bigusers
)
 PCTFREE 10   PCTUSED 40
INITRANS 1   MAXTRANS 255
STORAGE ( PCTINCREASE 0)
;

create unique index un_devpart_1 on devpart (
    decode(serial_num,null,null,serial_num)
)
PCTFREE 10
TABLESPACE bigindx
STORAGE(PCTINCREASE 0
        ) ;

create unique index un_devpart_2 on devpart ( decode(pin,null,null,pin) )
PCTFREE 10
TABLESPACE bigindx
STORAGE(PCTINCREASE 0
        ) ;

create index devpart_upper_pin on devpart (UPPER(pin))
PCTFREE 10
TABLESPACE indx
STORAGE(PCTINCREASE 0
        ) ;

create index devpart_upper_serial on devpart (UPPER(serial_num))
PCTFREE 10
TABLESPACE indx
PCTFREE 10
TABLESPACE indx
STORAGE(PCTINCREASE 0
        ) ;
CREATE INDEX DUP_DEVPART_2 ON DEVPART(M_USER_ID)
PCTFREE 10
TABLESPACE bigindx
STORAGE(PCTINCREASE 0
        ) ;

CREATE INDEX DUP_DEVPART_3 ON DEVPART(C_USER_ID)
PCTFREE 10
TABLESPACE bigindx
STORAGE(PCTINCREASE 0
        ) ;

          Followup:

1) by creating a composite partitioned table -- partition by range subpartition
by hash...

BUT -- and this is a big BUT -- that could be deadly if your queries do not
include this date field in their predicate as well -- or you change you indexing
structure.  If your queries are all by this primary key -- all of a sudden you
might well find you are doing N index range scans instead of just one as
partition elimination may not be able to kick in.

Analyze how you query the data before doing something physical to the underlying
organization of it.

2) Only you can make that judgment call.  Understand what partitioning would do
to the index, imagine what the query would have to do in order to find the data
-- would index partition elimination still work?

Benchmark it -- test it, set up the empty tables and use dbms_stats to set table
stats and see what the query plans are (looking at the pstart/pstop's to make
sure what you want to be happening is)

      Rating: 4
easier said
      than done..??  March 18, 2003
Reviewer:
      anonymoys  from Ohio
Tom,

You said:
============================================================
here you would
- index t_interim
- grant on it
- add constraints
- add triggers
- add EVERYTHING you need to make t_interim look just like T,
  behave just like T
=================================================================

this seems to have some big consequences when your table has tons of triggers,
synonyms, contraints, and everything has be redefined with new names.
 correct?

if I had a synonym on table t, named t_syn, do I now have to create a new
synonym named t_syn_new? If so, this would than interfere with all the
applications that use the synonyms, or anything that referenced the name of
something that was previously defined with a different name.

Does the redef wipe out everything that was associated with t before?
If not, couldn't my synonyms, triggers, grants, etc.. be left in place?

I thought this might just be a temporary name that would be matched back up with
the original name based on definition, but from your index example with the
enabling/disabling of the indexes, it showed they are in fact renamed.

Am I missing something Tom?
Thanks in advance.

          Followup:

you can rename triggers contraints everything...

the synonym is unchanged.

the redef doesn't wipe out ANYTHING -- you do.  The redef just copies data and
lets you change names in the data dictionary.  You do the rest.  You end up with
TWO objects at the end.

You cannot just have the triggers/et al get transferred - the redef could

o invalidate them (did some transformation)
o change them
o render them obsolete
o have been done in order to change them

      Rating: 5
decode(pin,
      null, null, pin) ????  March 18, 2003
Reviewer:
      Logan Palanisamy  from Santa Clara, CA USA
Tom,

What does the decode in the statement below (posted on Feb 05 on this thread)
achieve?.

create unique index un_devpart_2 on devpart (decode(pin,null,null,pin) )
PCTFREE 10
TABLESPACE bigindx
STORAGE(PCTINCREASE 0);

I think the decode doesn't server any purpose. What is your opinion?

Thanks as always

          Followup:

well, in that particular case it is sort of like indexing to_char(pin) since the
decode is a defacto conversion from number to char in this case...

but yes, I don't see its rational purpose in life.

      Rating: 5
Getting error
      ORA-06512: at "SYS.DBMS_REDEFINITION", line 8  April 26,
      2003
Reviewer:  Sikandar Hayat  from Pindi Bhattian -
      Pakistan
Hi TOM I was trying to practice your example and can_redef_table has given no
error then why i am getting this error.

SCOTT > select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.0.1.1.1 - Production
PL/SQL Release 9.0.1.1.1 - Production
CORE    9.0.1.1.1       Production
TNS for 32-bit Windows: Version 9.0.1.1.0 - Production
NLSRTL Version 9.0.1.1.1 - Production

SCOTT > drop table t;

Table dropped.

SCOTT > create table t nologging as select * from all_objects;

Table created.

SCOTT > set echo off
SCOTT > alter table t add constraint t_pk primary key(object_id);

Table altered.

SCOTT > alter table t add constraint t_pk primary key(object_id);

SCOTT > exec dbms_redefinition.can_redef_table(user,'T');

PL/SQL procedure successfully completed.

SCOTT > create table t_interim(
  2  object_id number primary key,
  3  object_type varchar2(18),
  4  owner varchar2(30),
  5  oname varchar2(30),
  6  created date,
  7  last_ddl_time date,
  8  status varchar2(7),
  9  flags varchar2(5)) partition by hash(object_id) partition 8/
 10  /
Table created.

SCOTT >  declare
  2              l_colmap varchar(512);
  3      begin
  4              l_colmap :=
  5               'object_id,
  6                    object_type,
  7                    owner,
  8                    object_name oname,
  9                    created,
 10                   last_ddl_time,
 11                   status,
 12               temporary || ''/'' || generated || ''/'' || secondary flags ';
 13             dbms_redefinition.start_redef_table
 14             ( uname => user,
 15               orig_table => 'T',
 16               int_table => 'T_INTERIM',
 17               col_mapping => l_colmap );
 18*    end;
 19  /
declare
*
ERROR at line 1:
ORA-12091: cannot online redefine table "SCOTT"."T" with materialized views
ORA-06512: at "SYS.DBMS_REDEFINITION", line 8
ORA-06512: at "SYS.DBMS_REDEFINITION", line 138
ORA-06512: at line 13

          Followup:

12091, 00000, "cannot online redefine table \"%s\".\"%s\" with materialized
views"
// *Cause:  An attempt was made to online redefine a table that had
//          materialized views defined on it or had a materialized view log
//          defined on it or is a master.
// *Action: Drop all materialized views and materialized view logs before
//          attempting to online redefine the table.

that is not a cut and paste -- something else is happening here.  query your
data dictionary and see if there isn't a MV on T already.

      Rating: 5
You are right
        April 27, 2003
Reviewer:  Sikandar Hayat
       from Pindi Bhattian - Pakistan
This time no error. In the last copy, paste there were some errors due to
typeing mistakes in the procedures which I did not paste here but there was no
creation last time.
Anyway thanks for your nice response. Next time I will try atleast 3 times and
then will contact you.

SCOTT >  drop table t;

Table dropped.

SCOTT > create table t nologging
  2  as
  3  select * from all_objects;

Table created.

SCOTT > set echo off
SCOTT > alter table t add constraint t_pk primary key(object_id);

Table altered.

SCOTT > exec dbms_redefinition.can_redef_table(user,'T');

PL/SQL procedure successfully completed.

SCOTT > create table t_interim
  2  (
  3  object_id number primary key,
  4  object_type varchar2(18),
  5  owner varchar2(30),
  6  oname varchar2(30),
  7  created date,
  8  last_ddl_time date,
  9  status varchar2(7),
 10  flags varchar2(5)
 11  )
 12  partition by hash(object_id) partitions 8
 13  /
create table t_interim
             *
ERROR at line 1:
ORA-00955: name is already used by an existing object

SCOTT > drop table t_interim;

Table dropped.

SCOTT > ed
Wrote file afiedt.buf

  1  create table t_interim
  2   (
  3   object_id number primary key,
  4   object_type varchar2(18),
  5   owner varchar2(30),
  6   oname varchar2(30),
  7   created date,
  8   last_ddl_time date,
  9   status varchar2(7),
 10   flags varchar2(5)
 11   )
 12*  partition by hash(object_id) partitions 8
SCOTT > /

Table created.

SCOTT > declare
  2   l_colmap varchar(512);
  3  begin
  4  l_colmap :=
  5  'object_id,
  6  object_type,
  7  owner,
  8  object_name oname,
  9  created,
 10  last_ddl_time,
 11  status,
 12  temporary || ''/'' || generated || ''/'' || secondary flags';
 13
 14  dbms_redefinition.start_redef_table
 15  (uname => user,
 16  orig_table => 'T',
 17  int_table => 'T_INTERIM',
 18  col_mapping => l_colmap);
 19
 20  end;
 21  /

PL/SQL procedure successfully completed.
****************************************************
From another session to check the synch effect.

SCOTT > update t set owner = 'HR2' where owner = 'HR';

34 rows updated.

SCOTT > commit;
*****************************************************
SCOTT > begin
  2  dbms_redefinition.sync_interim_table(user,'T','T_INTERIM');
  3  end;
  4  /

PL/SQL procedure successfully completed.

SCOTT > begin
  2  dbms_redefinition.finish_redef_table(user,'T','T_INTERIM');
  3  end;
  4  /

PL/SQL procedure successfully completed.

SCOTT > set echo off
SCOTT > desc t
 Name

---------------------------------------------------------------------------------
-----------------
 OBJECT_ID

 OBJECT_TYPE

 OWNER

 ONAME

 CREATED

 LAST_DDL_TIME

 STATUS

 FLAGS

SCOTT > select distinct owner from t;

OWNER
------------------------------
AURORA$JIS$UTILITY$
CGLBYTE
CTXSYS
HR2
MDSYS
OE
OLAPDBA
OLAPSYS
ORDPLUGINS
ORDSYS
OSE$HTTP$ADMIN
OUTLN
PM
PUBLIC
QS
QS_ADM
QS_CBADM
QS_CS
QS_ES
QS_OS
QS_WS
RMAN
SCOTT
SH
SYS
SYSTEM
WKSYS

27 rows selected.

SCOTT >

--------------------------------
After rollover we have to recompile the procedures as well?

          Followup:

they will recompile themselves.

April 27, 2003 Saibabu -- Thanks for the question regarding
      "Log_checkpoint_interval and timeout", version Oracle 8i

      You Asked
Hi,
   It is rather confusing from documentation that the meaning of
Log_checkpoint_interval and log_checkpoint_timeout between
Oracle
8 and 8i versions.
  I believe that even though the definitions changed in 8i
meaning is same,if so why did they changed the definition.if iam
wrong please explain the tow parameters across 8 and 8i may be
small example.
  Thanks
  saibabu

      and we said...
The definition of log_checkpoint_interval did not change
significantly.  In v8.0 it was:

<quote>
LOG_CHECKPOINT_INTERVAL

LOG_CHECKPOINT_INTERVAL specifies the frequency of checkpoints
in terms of the number of redo log file blocks that are written
between consecutive checkpoints.
</quote>

In Oracle8i, it is:

<quote>
LOG_CHECKPOINT_INTERVAL

LOG_CHECKPOINT_INTERVAL specifies the frequency of checkpoints
in terms of the number of redo log file blocks that can exist
between an incremental checkpoint and the last block written to
the redo log. This number refers to physical operating system
blocks, not database blocks.
</quote>

What this means is that in prior releases, whenever Oracle
generated log_checkpoint_interval number of redo blocks, it
would start an interval checkpoint. Starting with Oracle 8.1,
log_checkpoint_interval will be interpreted to mean that the
incremental checkpoint should not lag the tail of the log by
more than log_checkpoint_interval number of redo blocks.  This
ensures that no more than log_checkpoint_interval number of redo
blocks will have to be read during recovery.  It helps in
bounded recovery times.

So, for example, assume log_checkpoint interval  is set to 1000.
 Oracle continually calculates the address of redo record that
was written 1000 records (OS blocks) ago.  In order to satisfy
this parameter, the checkpoint position must advance at least as
far as this redo record. Should the checkpoint position point to
a redo record written earlier than this target position (written
over 1000 records before the record at the end of the log),
Oracle will write dirty buffers and advance the checkpoint until
it points at a redo record written less than 1000 records ago.
Should the checkpoint position point to a redo record newer than
this target position (written less than 1000 records ago),
Oracle will do nothing to satisfy this target for it is already
satisfied.

Now for log_checkpoint_timeout the definition was:

LOG_CHECKPOINT_TIMEOUT

<quote>
LOG_CHECKPOINT_TIMEOUT specifies the maximum amount of time
before another checkpoint occurs. The value is specified in
seconds. The time begins at the start of the previous
checkpoint, then a checkpoint occurs after the amount of time
specified by this parameter.
</quote>

and is now:

LOG_CHECKPOINT_TIMEOUT

<quote>
LOG_CHECKPOINT_TIMEOUT specifies that the incremental checkpoint
is at the position where the last write to the redo log
(sometimes called the "tail of the log") was integer
seconds ago. This parameter also signifies that no buffer will
remain dirty (in the cache) for more than integer seconds. The
value is specified in seconds.
</quote>

What this means is that in prior releases, every
log_checkpoint_timeout seconds, Oracle started an interval
checkpoint.  Starting with Oracle 8.1, log_checkpoint_timeout
will be interpreted to mean  that the incremental checkpoint
should be at the log position where the tail of the log was
log_checkpoint_timeout seconds ago. In other words, the
incremental checkpoint should lag the tail of the log by no more
than log_checkpoint_timeout seconds worth of redo.

So for example:
Assume the log_checkpoint_timeout is set to 60.  Oracle
continually calculates the address of the redo record that was
written 60 seconds ago.  In order to satisfy this parameter, the
checkpoint position must advance at least as far as this redo
record.  Should the checkpoint position point to a redo record
older than this target position (written over 60 seconds ago),
Oracle will write dirty buffers and advance the checkpoint until
it points at a redo record written less than 60 seconds ago.
Should the checkpoint position point to a redo record newer than
this target position (written less than 60 seconds ago), Oracle
will do nothing to satisfy this target for it is already
satisfied.  In prior releases, this was more of a 'timer' and a
checkpoint would happen every N seconds.

Review &
      Followup

      Rating: 1
Not a specific
      reply (sorry)  May 08, 2002
Reviewer:  Sean
      Coughlin  from Bloomfield, Ct. USA
I have read these documents, the Oracle documentation etc. These are not
answering my question. Why, when I have 200m redo logs, Log_checkpoint_interval
set at 10000 blocks, an OS block size of 512 and log_checkpoint_timeout NOT set,
Log_checkpoints_to_alerts set to TRUE don't I see any increamental checkpoints
taking place in my alert log? I only see checkpoints occuring at log switches,
which is a good thing, don't get me wrong, but according to the doco and all the
articles, I should be seeing incremental checkpoint messages in my alert log
every 5m worth of changes..

          Followup:

Because INCREMENTAL checkpoints are not logged to the alert -- plain and simple.
 Period (sorry, I thought I said that in my response to you)

Imagine if they were.  Once the incrementals start (after getting 5m of redo
generated in your case -- from your question) the incrementals are almost
CONTINOUSLY GOING.  Your alert would exceed the size of your database in no time
flat.

If you see:

......
Starting with Oracle 8.1, log_checkpoint_interval will be
interpreted to mean that the
incremental checkpoint should not lag the tail of the log by more than
log_checkpoint_interval number of redo blocks.  This ensures that no more than
log_checkpoint_interval number of redo blocks will have to be read during
recovery.  It helps in bounded recovery times.
...........

that means once it gets going, if you are generating redo, it'll keep on
trickling the data out.

      Rating:
      4
  August 15, 2002
Reviewer:  Mike
       from SanAntonio, USA
Someone cliams you enable to disable the parameters by setting to 0, thus the
checkpoints only occurs as the log switchs. Is that ture? if so, what's the
disadvantages of having huge online redo logs,which implies the infrequently
checkpoints?

Another question in the mind:
look the parameter:
"
LOG_CHECKPOINT_INTERVAL

LOG_CHECKPOINT_INTERVAL specifies the frequency of checkpoints in terms of the
number of redo log file blocks that are written between consecutive checkpoints.

"

Because the checkpoint is the event as all the dirty db buffers are written to
the datafiles by
the db writers, why it has to do with "the number of redo log file blocks " in
the above definition?

          Followup:

These days, the database is virtually constantly in a checkpoint like mode,
trickleing blocks out in the background (you know, checkpoints are not evil,
they aren't something you really want to turn off -- especially if you would
like recovery to take less then a really long time).

There are many types of checkpoints, yes, you can make it so the official
checkpoint happens at log switch by playing with these parameters....

      Rating: 5
abut checkpoint
      from ILT guide and cos  September 07, 2002

Reviewer:  A reader
Hello Tom

From Oracle 8i backup & recovery instructor lead guide it states that checkpoint
is affected by several things, one of them is this:

==========================================================
90% of the size of the smallest log file: A redo log file cannot be reused until
the
checkpoint has advanced beyond the end of the log file. To minimize the chance
that update activity in the instance waits for a checkpoint to complete,
Oracle8i
makes sure that the target for checkpoint position does not lag the end of the
redo
log by more than 90% of the size of the smallest redo log.
The smaller the size of the smallest log, the more aggressively the Oracle
server
writes dirty buffers to disk to ensure the position of the checkpoint has
advanced to
the current log before that log completely fills.
========================================================

from Oracle 8i documents Tuning instance Recovery Performance it states

========================================================
Using LOG_CHECKPOINT_INTERVAL
Set the initialization parameter LOG_CHECKPOINT_INTERVAL to a value n (where n
is an integer) to require that the checkpoint position never follow the most
recent redo block by more than n blocks. In other words, at most n redo blocks
can exist between the checkpoint position and the last block written to the redo
log. In effect, you are limiting the amount of redo blocks that can exist
between the checkpoint and the end of the log.

Oracle limits the maximum value of LOG_CHECKPOINT_INTERVAL to 90% of the
smallest log to ensure that the checkpoint advances into the current log before
that log fills and a log switch is attempted.

LOG_CHECKPOINT_INTERVAL is specified in redo blocks. Redo blocks are the same
size as operating system blocks. Use the LOG_FILE_SIZE_REDO_BLKS column in
V$INSTANCE_RECOVERY to see the number of redo blocks corresponding to 90% of the
size of the smallest log file.
=========================================================

Since English is not my native language these statements look rather confusing
to me, are the two statements talking about the samething?
I dont understand what do they mean by saying

"checkpoint has advanced beyond the end of the log file" and "checkpoint
position"

How does a checkpoint advance? I thought checkpoint was simply a mechanism to
syncronize file headers?

          Followup:

Answering backwards.

A checkpoints primary job is to flush dirty blocks from the buffer cache to the
database files.  Updating file headers -- thats somewhat secondary. (important
but not the crux)

Yes, they basically say the same thing.

A checkpoint advances by making sure that the blocks protected by some area of
REDO log are safely flushed to disk.  If a redo log file protects the cached
versions of blocks 1 thru 100 in file 5 (eg: if the system crashed right now, we
would need that redo in order to reconstruct the changes to blocks 1..100 since
they are cached and not on disk) -- we can advance the checkpoint by writing
those blocks out.  Now, we no longer need that REDO log file for recovery and we
can reuse it.

      Rating: 4
90% of smallest
      log  September 07, 2002
Reviewer:
      A reader
quote:"Oracle limits the maximum value of LOG_CHECKPOINT_INTERVAL to 90% of the
smallest log"

does that means that if I have 3 redo logs of 5MB, 10MB and 15MB even I set
LOG_CHECKPOINT_INTERVAL to a very large value such as 100000 (what we used to do
in oracle 7 and 8, setting this high to ensure checkpoint occur only in log
switches) Oracle will limit it to 90% of 5MB which is 9216?

          Followup:

exactly, that is exactly what it is saying.  You should know that checkpoints
are almost continual in most systems today -- with fast_start_mttr_targets and
other mechanisms to bound recovery times.

(checkpoints are *not* evil, they are something you want to have happening on a
regular basis to avoid spiking and unduly long recovery times in the event of a
failure)

      Rating: 5
from your first
      reply  September 09, 2002
Reviewer:
      A reader
hi

you stated this in first reply

"So, for example, assume log_checkpoint interval  is set to 1000.  Oracle
continually calculates the address of redo record that was written 1000 records"

Should 1000 record be 1000 redo blocks? According to Oracle 8.1.7 Reference
guide, v$instance_recovery is based on blocks

For example LOG_FILE_SIZE_REDO_BLKS in v$instance recovery definition is:
Maximum number of redo blocks required to guarantee that a log switch does not
occur before the checkpoint completes.

          Followup:

I said:

was written 1000 records (OS blocks) ago

the clarification is in the answer....

      Rating: 5
regarding
      LOG_CHECKPOINT_TIMEOUT  September 09, 2002

Reviewer:  A reader
Hi

From new definition of LOG_CHECKPOINT_TIMEOUT isnt it quite hard to estimate how
aggresive checkpoint would occur? I mean 60 seconds worth of redo is relative,
we can generate lots of redo in 60 seconds or not as much as we might think

Am I right about LOG_CHECKPOINT_TIMEOUT new defintion?

          Followup:

It is pretty much happening all of the time (and you want it to).  You cannot
really predict when it will happen until you see what you are doing on your
system (load wise)

      Rating:
      3
Differentation  September 10, 2002

Reviewer:  Vikas Khanna  from INDIA
Hello Tom,

Need to understand Log_Checkpoint_timeout = 1800, Log_Checkpoint_interval =
10000 and Fast_start_io_target = 500. How would LGWR flush redo blocks from log
buffer to Online redo logs based on these parameter values.
Do any of these gets more priority over the other ones.?

Please explain in greater insights to understand it better?

          Followup:

LGWR flushes blocks to the redo logs not based on ANY OF THOSE.

every 3 seconds it'll flush the redo buffers
when 1/3 full it'll flush the redo buffers
when 1meg full it'll flush the redo buffers
when someone commits - it'll flush the redo buffers

      Rating: 2
My
      Mistake  September 11, 2002
Reviewer:  Vikas
      Khanna  from INDIA
I needed to ask regarding Checkpoint, and when does DBWR flushes the dirty
buffers from the dirty list to the respective datafiles. Is this some parameter
specific?

How fast these checkpoints gets initiated? In general please take all specific
cases to illustrate these parameters in combination with others?

Thanks in advance.

          Followup:

Almost continously these days.  Almost continously.  Too many parameters come to
mind -- fast_start_mttr_target (io_target in later releases) log checkpoint
interval, log checkpoint time out, log switches themselves, various SQL commands
(eg: a parallel query can do it), many DDL commands, etc etc etc.

Since DBWR is going in the background, you want this to happen.  checkpoints are
not evil, they are not something to totally defer.  Over time your ability to
control when they happen is going away (self managing).

      Rating:
      3
  September 13, 2002
Reviewer:  Vikas
      Khanna  from INDIA
In earlier releases of Oracle there used to be a parameter to be set named
checkpoint_process = TRUE/FALSE depending upon whether to initiate a checkpoint
or not, but with new releases it is going away from the hands of DBA. There are
numerous parametets which do often checkpoint, and feeling is that much of
checkpointing is calling DBWR to be called very frequently and the disk I/O is
initiated at regular intervals. This is just to maintain less MTTR as there
would be very less dirty blocks in the Dirty list for DBWR to write.

But isn't it too much on the system overhead.? There would be a time when at
every commit it would be calling DBWR to write the dirty buffers to datafiles.

Moreover, if I set LOG_CHECKPOINT_INTERVAL = 8,LOG_CHECK_TIMEOUT= 5 and
FAST_START_IO_TARGET = 10 then suppose we get a checkpoint when the dirty blocks
were exceeding 10 and at the same point we have 5 log blocks, and the time
elapsed is 3 sec, would at that point it would reinitialize the parameters to
start from fresh or it would continue with th old set values. If it is not
resetting the old values for these parameters then LOG_CHECKPOINT_INTERVAL just
need 2 more redo blocks before CKPT is initiated again.

Please do verify and give your valuable comments on this.

Thanks

          Followup:

No, that is not what checkpoint process did.  That parameter started the
optional CKPT process which is responsible for updating the data file headers --
it did not turn checkpoints on and off.  Never did.

If you define new releases as "all software made in the last 5 years", you would
be correct.

A commit won't be calling DBWR to write blocks out.  Remember -- in a well
running system -- DBWR just chugs in the background, you do NOT wait on him.  It
is only in a poorly tuned/overloaded system that you do (and it is NOT during a
commit, it is when you need a free buffer in the buffer cache and we have to
signal DBWR to make some for us -- that would be a side effect of NOT
checkpointing frequently enough!!!! so it is actually NOT checkpointing that
would cause you to wait on DBWR, not checkpointing -- you have it backwards)

      Rating:
      5
  December 31, 2002
Reviewer:
      A reader

      Rating: 5
Further
      reading  April 27, 2003
Reviewer:  Oren
       from Israel
Hi Tom.
Can you please recommend on a good and thorough document, explaining in detail
the various types of checkpoints?
Thanks,
Oren.

          Followup:

metalink may have some.  the only time i really care about a checkpoint -- is
when they stop my database -- and then I fix it and move on.

Checkpoints happen almost constantly and continously these days -- they are not
much of a "point" anymore, more of an ongoing thing.

      Rating: 5
Answers to
      Checkpoint process  April 28, 2003
Reviewer:
      Nikhil  from India
My search ends here while looking for docs/white papers on checkpoint and
related params...
Definitely a good page worth to read 10 times ...

April 28, 2003 omer -- Thanks for the question regarding
      "Hierarchial query and NVL2", version 8.1.7

      You Asked
Tom

From the deluge of questions you have answered in the last two years, I think if
you do some data mining, and come up with a list of the most often asked areas,
or areas where you find most of the developers and dba's weak. Do you have any
such list of areas.

I have not given enough attention to the new pl/sql features in the last 1 year.
What are the new pl/sql new pl/sql features which a oracle developer must know.

I have alwasys had this problem with hierarchial queries, i.e. to understand how
it works.
Can you give an example list the employess and managers in a hierarchial manner,
and  explain the start with and connect by clauses. Actually, what I want is
someone to explain the query in words, just like psuedo code.

and finally,

Why should use nvl2 when we already have nvl function.

Thank you

      and we said...
Get my book ;)  On the cover I have a quote to the effect "and is the tom behind
Asktom online at
http://asktom.oracle.com/
 Drawing on this unique expertise, he has procedure a book
..."

If you look at the table of contents -- you'll get the list (it's on amazon,
click through from my home page if you like)

As for "whats new in plsql in the last year" -- thats large.  9i is new and 9i
rewrites the story on PLSQL with object types.  You have inheritance,
polymorphism, tons of new functionality.  I'll have to refer you to the 9i new
features guide and the 9i plsql docs (which have a section on whats new)

Take this query for the hierarchical example:

cott@ORA817DEV.US.ORACLE.COM> select lpad('*',(level-1)*2, '*' ) || ename ename
  2  from emp
  3  start with mgr is NULL
  4  connect by prior empno = mgr;

ENAME
---------------------------------------------------------------------------------
--------------------------------------------------
KING
**JONES
****SCOTT
******ADAMS
****FORD
******SMITH
**BLAKE
****ALLEN
****WARD
****MARTIN
****TURNER
****JAMES
**CLARK
****MILLER

14 rows selected.

This builds a hierarchy of WHO works for WHO.

The query needed a set of rows to start with (rows that in this example won't
have '**' in front of them -- these are our "root" nodes in the hierarchy).  In
this case, i used "start with mgr is null" -- that is -- start with EVERYONE who
doesn't work for someone -- in this case KING.

So, that identified the rows to start working with -- the first row we hit was
king.  It then goes to the CONNECT BY statement.  Here I have connect by prior
EMPNO = MGR. That says to take the EMPNO of the record we just processed and
find all of the records that have that value in the MGR field.  In this case,we
took KINGS empno of 7839 and found JONES.  Then we did to this record what we
did for the last -- we found the people that work for JONES (scott)and so on and
so on.

In code it might be:

     create or replace procedure hierarchy( level in number,
                                            prior_empno in number )
  2  is
  3  begin
  4        for x in ( select * from emp where mgr = prior_empno )
  5        loop
  6            dbms_output.put_line( rpad( '*', (level-1)*2, '*' ) || x.ename );
  7            hierarchy(level+1,x.empno);
  8        end loop;
  9  end;
 10  /

Procedure created.

scott@ORA817DEV.US.ORACLE.COM> begin
  2          for c in (select * from emp where mgr is NULL)
  3          loop
  4              dbms_output.put_line( c.ename );
  5              hierarchy(2,c.empno);
  6          end loop;
  7  end;
  8  /

KING
**JONES
****SCOTT
******ADAMS
****FORD
******SMITH
**BLAKE
****ALLEN
****WARD
****MARTIN
****TURNER
****JAMES
**CLARK
****MILLER

PL/SQL procedure successfully completed.

As for nvl/nvl2 -- they provide different functionality.

nvl(a,b) is "if a is NULL then return b else return a"
nvl2(a,b,c) is "if a is NOT NULL then return b else return c"

Review &
      Followup

      Rating:
      5
nvl  August 29, 2001
Reviewer:  omer

Going by your explanation nvl2 is nothing but a different flavor of decode. Is
it so.

          Followup:

NVL2 is a shortcut for

  decode( a, NULL, c, b )

      Rating: 3
NVL2 and
      PLSQL  July 24, 2002
Reviewer:  Tony
      Johnston  from Reading, England
Can we use NVL2 in PLSQL, I seem to be having troubles using it in PLSQL?

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3027089372477
same issue.

      Rating: 4
any performnace
      issue  April 28, 2003
Reviewer:  umesh
      kasturi  from bangalore india
does replacing decode with nvl2 result in performance improvement?

          Followup:

did you benchmark it?

http://asktom.oracle.com/~tkyte/runstats.html

April 28, 2003 Jerry -- Thanks for the question regarding
      "Natural Join and Index Monitoring (9iR2 Slide Show)", version 9.2

      You Asked
I was able to go through the slide show on 9iR2 features today, and came away
with 2 questions:

(1)  I am not clear on why natural joins are a bad idea.  I guess I can imagine
some scenarios, but I would appreciate some details on the downsides of them.

(2) If there is one thing I've learned from this site (and there isn't only
one), that's to try things out.  I observe different behavior from the slide
that says that index monitoring data is wiped out after a restart.  It appears
to stick around until the index is dropped, and the doc indicates that, too.

Grateful as always.

P.S. Never been disappointed with an Addison-Wesley book.  They seem like a
class outfit.

      and we said...
1) they join by names of columns.

It is funny, there is a bug logged in the bug database against natural joins.
Says "they get the wrong answer".  sure enough, it would appear they do --
however, the bug is closed as "not a bug"

hr@ORA920> select employee_id, department_name
  2  from employees natural join departments;

EMPLOYEE_ID DEPARTMENT_NAME
----------- ------------------------------
        202 Marketing
        115 Purchasing
...
        113 Finance
        206 Accounting

32 rows selected.

hr@ORA920> select employee_id, department_name
  2  from employees, departments
  3  where employees.department_id = departments.department_id;

EMPLOYEE_ID DEPARTMENT_NAME
----------- ------------------------------
        200 Administration
        202 Marketing
...
        206 Accounting
        205 Accounting

106 rows selected.

See -- obviously the natural join is broken right?  (this is using the HR sample
schema provided with Oracle9i).  There are 32 rows using a natural join, yet 106
using a hand coded natural join.  Whats up with that?

Well:

hr@ORA920> desc employees;
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 EMPLOYEE_ID                         NOT NULL NUMBER(6)
 FIRST_NAME                                   VARCHAR2(20)
 LAST_NAME                           NOT NULL VARCHAR2(25)
 EMAIL                               NOT NULL VARCHAR2(25)
 PHONE_NUMBER                                 VARCHAR2(20)
 HIRE_DATE                           NOT NULL DATE
 JOB_ID                              NOT NULL VARCHAR2(10)
 SALARY                                       NUMBER(8,2)
 COMMISSION_PCT                               NUMBER(2,2)
 MANAGER_ID                                   NUMBER(6)
 DEPARTMENT_ID                                NUMBER(4)

hr@ORA920> desc departments
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 DEPARTMENT_ID                       NOT NULL NUMBER(4)
 DEPARTMENT_NAME                     NOT NULL VARCHAR2(30)
 MANAGER_ID                                   NUMBER(6)
 LOCATION_ID                                  NUMBER(4)

The problem is there is a manager id column in there as well.  The relationship
is really:

emp joins to dept by department_id to department_id
dept joins to emp by manager_id to employee_id

But a natural join just sees two columns with the same name.

It is a bug just WAITING to happen.  Someone adds a column to a table a year
from now and all of a sudden -- YOUR JOIN CONDITIONS CHANGE -- doh!  Or worse,
you didn't catch the 32 vs 106 condition and you have a bug from day 1.

Ahh, i see what I did wrong there:

ops$tkyte@ORA9I> select * from v$object_usage;

INDEX_NAME                     TABLE_NAME                     MON USE
START_MONITORING    END_MONITORING
------------------------------ ------------------------------ --- ---
------------------- -------------------
T_IDX                          T                              YES YES 03/22/2003
10:04:48

ops$tkyte@ORA9I> startup force
ORA-01031: insufficient privileges
ops$tkyte@ORA9I> connect / as sysdba
Connected.
ops$tkyte@ORA9I> startup force
ORACLE instance started.

Total System Global Area  174359528 bytes
Fixed Size                   279528 bytes
Variable Size             134217728 bytes
Database Buffers           33554432 bytes
Redo Buffers                6307840 bytes
Database mounted.
Database opened.
ops$tkyte@ORA9I> select * from v$object_usage;

no rows selected

appears wiped out... but it was my mistake

ops$tkyte@ORA9I> connect /
Connected.
ops$tkyte@ORA9I> select * from v$object_usage;

INDEX_NAME                     TABLE_NAME                     MON USE
START_MONITORING    END_MONITORING
------------------------------ ------------------------------ --- ---
------------------- -------------------
T_IDX                          T                              YES YES 03/22/2003
10:04:48

I tagged those slides to correct that.
Review &
      Followup

      Rating: 4
So, why was the
      syntax introduced?  March 23, 2003
Reviewer:
      Arup Nanda  from Norwalk, CT, USA
I had similar apprehensions about using the new syntax of Natural Joins. It's
just too easy to introduce a bug and certainly makes the current queries
ambiguous. So, why did ANSI introduce them as a standard, and why did Oracle
followed suit? They must have had some reasoning. Appreciate if you could share
your comments.

          Followup:

Answering in reverse

why did Oracle follow suit?  Because industry demands it.  We cannot pick and
choose syntactical features.  If we support the ANSI join syntax we either
support it or we don't.  If we leave out some -- we don't.

As to why ANSI did -- don't know, you would have to ask them.  I see no
advantage to it, only total disadvantage.  I cannot fathom their thought process
on this one.

      Rating: 3
Natural joins -
      bad ANSI standard  March 24, 2003
Reviewer:
      Jonathan Brain  from England
If natural joins looked for a foreign key constraint instead of matching column
names and raised an exception if there was none then we might have something
usefull.

How about inventing a KEY JOIN clause to use instead of NATURAL JOIN?

      Rating: 4
Natural Joins
      can produce cartesian products  April 07, 2003

Reviewer:  Chip  from Denver, CO USA
In Oracle 9i and 9i Release 2, a natural join becomes a cross join if the tables
do not have any common column names.

For example, trying to list the countries for all departments using the HR
example schema can produce a cartesian product (instead of an error):

hr@MSORA920>desc countries
Name             Null?    Type
---------------- -------- ------------
COUNTRY_ID       NOT NULL CHAR(2)
COUNTRY_NAME              VARCHAR2(40)
REGION_ID                 NUMBER

hr@MSORA920>desc departments
Name             Null?    Type
---------------- -------- ------------
DEPARTMENT_ID    NOT NULL NUMBER(4)
DEPARTMENT_NAME  NOT NULL VARCHAR2(30)
MANAGER_ID                NUMBER(6)
LOCATION_ID               NUMBER(4)

hr@MSORA920>SELECT country_name, department_name
  2  FROM countries NATURAL JOIN departments;

675 rows selected.  (25 countries * 27 departments).

Note: joining the locations table does produce the desired list of country names
for all departments.

hr@MSORA920>SELECT country_name, department_name
  2  FROM countries NATURAL JOIN locations NATURAL JOIN departments;

27 rows selected.

      Rating: 5
Natural
      Joins  April 28, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
In Docs,

"The NATURAL keyword indicates that a natural join is being performed. A natural
join is based on all columns in the two tables that have the same name. It
selects rows from the two tables that have equal values in the relevant columns.
When specifying columns that are involved in the natural join, do not qualify
the column name with a table name or table alias."

My queries is that with Natural we can't join more than 2 tables?

          Followup:

scott@ORA920> select *
  2   from ( emp natural join dept ) natural join bonus
  3  /

no rows selected

it works syntactically -- don't don't ever use a natural join in real life,
forget they exist.

April 28, 2003 Manuela -- Thanks for the question regarding
      "enqueue processes and rescources", version 8.1.7

      You Asked
Hello

we have troubles with our enqueue processes and resources. I don't know the
exact error-message, but something like "not enough enqueue resources".
I checked the init.ora-Parameters, but I don't find in documentations, which
parameter I could change.

processes                       = xxx
sessions                        = xxx
transactions                    = xxx

resource_limit                 = false

distributed_transactions        = xxx
open_links                      = xxx
job_queue_processes             = xxx
job_queue_interval              = xxx

Thank you for your help.

with friendly regards
M. Jessich

      and we said...
tkyte@TKYTE816> show parameter enqueue

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
enqueue_resources                    integer 1308

thats the parameter you want to look at.  Each table that your transaction
modifies will require an ENQUEUE (not each ROW you modify -- you only need one
enqueue per table).

Also, if you are using DBMS_LOCK, you'll need additional enqueue resources for
that as each named lock will require an enqueue.

Review &
      Followup

      Rating: 3
Are they
      "expensive" ?  April 25, 2003
Reviewer:
      Adrian  from UK
Tom

I am having trouble dropping a composite partitioned table due to the number of
enqueue_resources (1000) being exceeded. I have read several threads on Metalink
that suggest setting enqueue_resources to either number of tables + 40% or
number of objects that might be locked + number of sessions + number of
datafiles + number of PQ slaves + a 10% contingency. This is just test - the
production table will be huge in terms of the number of extents and on a
Metalink thread, Jonathan Lewis mentions that to create a local index on a
single partition requires one resource per extent for the index and one per
extent for the table.

My question therefore - are these enqueue_resources something that we can be
fairly free with or does upping the value have implications ? I have almost 1000
table/index partitions and 3000 table/index subpartitions, which suggests to me
that I need to increase the value by at least 200-500% just to get this table
dropped.

Thanks
Adrian

          Followup:

are you mixing up "extents" with "partitions" in this discussion?

      Rating: 3
Well I did
      wonder...  April 28, 2003
Reviewer:  Adrian
      Billington  from UK
Tom

I did wonder about the same thing but I am quoting Jonathan Lewis on this one so
I tend not to question too much ;)

Here's JL's quote from a posting on a Metalink thread on enqueues and
enqueue_resources:-

<quote>
2) Do you really mean enqueues here, or enqueue resources ?
The latter is usually somewhere around 'number of files'
+ number of users + number of PQ slaves + number of objects
which might be locked simultaneously. Bear in mind for the
latter that if you create a local index on a partitioned table of 100 extents,
then you need 100 enqueue resources for the table partitions and 100 for the
index partitions. You can reduce the requirement (and find some application
bugs, and protect the system somewhat) by disabling DML_LOCKs on objects or
globally.
<unquote>

Regards
Adrian

          Followup:

i'm almost certain he meant partitions.  the one "extents" that slipped in there
was a mistake.  All other references are partition related and the formula he
speaks of is refering to "objects" which are segments and a partition is a
segment.

The enqueue resources are allocated out of the shared pool (they are a data
structure basically - a queuing structure).  Each enqueue resource might take
about 100 bytes.

Now, enqueue resources will dynamically grow in 9i if you exceed the requested
value -- give that there is room to do so in the shared pool...

April 28, 2003 Geetha -- Thanks for the question regarding
      "Generate XML in desired format", version 8.0.6

      You Asked
Hi Tom,

  How do we generate XML in desired format using PL/SQL?

For example,

--This is my master table...

SQL> create table inv_master(inv_no number(10),
  2  inv_date date)
  3  /

Table created.

--This is my detail table...

SQL> create table inv_detail(inv_no number(10),item_no number(10),
  2  qty number)
  3  /

Table created.

SQL> insert into inv_master values (1, sysdate)
  2  /

1 row created.

SQL>
SQL> insert into inv_detail values (1, 1, 1)
  2  /

1 row created.

SQL>
SQL> insert into inv_detail values (1, 2, 1)
  2  /

1 row created.

SQL>
SQL> insert into inv_detail values (1, 3, 1)
  2  /

1 row created.

-- Am able to get this result...

SQL>  select xmlgen.getxml('select m.inv_no, m.inv_date, d.item_no, d.qty from
inv_master m, inv_detail d
  2   where m.inv_no = d.inv_no') from dual;

<?xml version = '1.0'?>
<ROWSET>
   <ROW num="1">
      <INV_NO>1</INV_NO>
      <INV_DATE>8/12/2002 14:36:25</INV_DATE>
      <ITEM_NO>1</ITEM_NO>
      <QTY>1</QTY>
   </ROW>
   <ROW num="2">
      <INV_NO>1</INV_NO>
      <INV_DATE>8/12/2002 14:36:25</INV_DATE>
      <ITEM_NO>2</ITEM_NO>
      <QTY>1</QTY>
   </ROW>
   <ROW num="3">
      <INV_NO>1</INV_NO>
      <INV_DATE>8/12/2002 14:36:25</INV_DATE>
      <ITEM_NO>3</ITEM_NO>
      <QTY>1</QTY>
   </ROW>
</ROWSET>

I have to eliminate date and inv_no that appears redundantly for every item.

I have to generate an XML using PL/SQL that should look like...

<?xml version = '1.0'?>
<ROWSET>
   <MASTER>
      <INV_NO>1</INV_NO>
      <INV_DATE>8/12/2002 14:36:25</INV_DATE>
   </MASTER>
   <DETAIL>
       <ROW num="1">
      <ITEM_NO>1</ITEM_NO>
      <QTY>1</QTY>
       </ROW>
       <ROW num="2">
          <ITEM_NO>2</ITEM_NO>
          <QTY>1</QTY>
       </ROW>
       <ROW num="3">
          <ITEM_NO>3</ITEM_NO>
          <QTY>1</QTY>
       </ROW>
   </DETAIL>
</ROWSET>

Also will have to read this XML and insert into my tables...
Please suggest ways of achieving this XML format using PL/SQL.

TIA

      and we said...
I asked Sean Dillon, our local XML technologist, to take a look at this and
here's what he had to say:
------------------------

Is the XML format you sent me dictated by you or by somebody else?  There are a
couple of reasons I ask.  First of all, you could use a very complex combination
of SYS_XMLGEN and SYS_XMLAGG to accomplish what you are looking for, but your
format doesn't seem to make sense.  Can you use a format such as:

<?xml version = '1.0'?>
<ROWSET>
   <MASTER>
      <INV_NO>1</INV_NO>
      <INV_DATE>8/12/2002 14:36:25</INV_DATE>
      <DETAILS>
         <DETAIL num="1">
            <ITEM_NO>1</ITEM_NO>
            <QTY>1</QTY>
         </DETAIL>
         <DETAIL num="2">
            <ITEM_NO>2</ITEM_NO>
            <QTY>1</QTY>
         </DETAIL>
         <DETAIL num="3">
            <ITEM_NO>3</ITEM_NO>
            <QTY>1</QTY>
         </DETAIL>
      <DETAILS>
   </MASTER>
</ROWSET>

It's close to what you had, but by enclosing the <DETAILS> node within the
<MASTER> node, you get a sense of heirarchy.  With your model, the data would
only ever make sense if there was one and only one <MASTER> node in a document,
and even then I would question whether or not the <DETAIL> node is really
associated to the <MASTER> node.

To make the format above using SYS_XMLGEN, we will use Oracle's object types.
Here's an example:

SQL> create table inv_master(
  2    inv_no   number(10),
  3    inv_date date)
  4  /
Table created.

SQL> create table inv_detail(
  2    inv_no  number(10),
  3    item_no number(10),
  4    qty        number)
  5  /
Table created.

SQL> insert into inv_master values (1,sysdate)
  2  /
1 row created.

SQL> insert into inv_detail values (1,1,1)
  2  /
1 row created.

SQL> insert into inv_detail values (1,2,1)
  2  /
1 row created.

SQL> insert into inv_detail values (1,2,2)
  2  /
1 row created.

SQL> create type DETAIL as object(
  2    item_no number(10),
  3    qty        number)
  4  /
Type created.

SQL> create type DETAILS as table of DETAIL
  2  /
Type created.

SQL> create type MASTER as object(
  2    inv_no     number(10),
  3    inv_date   date,
  4    inv_detail DETAILS)
  5  /
Type created.

SQL> select sys_xmlgen(
  2           MASTER(m.inv_no, m.inv_date,
  3             cast(multiset(select d.item_no, d.qty
  4                             from inv_detail d
  5                            where d.inv_no = m.inv_no) as DETAILS)
  6                 )).getClobVal() as XML_QUERY
  7    from inv_master m
  8  /

XML_QUERY
--------------------------------------------------------------------------------
<?xml version="1.0"?>
<ROW>
 <INV_NO>1</INV_NO>
 <INV_DATE>13-AUG-02</INV_DATE>
 <INV_DETAIL>
  <DETAIL>
   <ITEM_NO>1</ITEM_NO>
   <QTY>1</QTY>
  </DETAIL>
  <DETAIL>
   <ITEM_NO>2</ITEM_NO>
   <QTY>1</QTY>
  </DETAIL>
  <DETAIL>
   <ITEM_NO>2</ITEM_NO>
   <QTY>2</QTY>
  </DETAIL>
 </INV_DETAIL>
</ROW>

You can also use what is known as an XMLFORMAT object to change the XML document
that is being created by SYS_XMLGEN.  You can change things like the XML Schema,
namespace, processing instructions, and the enclosing tag (i.e., "MASTER"
instead of "ROW").  For example (note line 6):

SQL> select sys_xmlgen(
  2           MASTER(m.inv_no, m.inv_date,
  3             cast(multiset(select d.item_no, d.qty
  4                             from inv_detail d
  5                            where d.inv_no = m.inv_no) as DETAILS)),
  6                 xmlformat.createFormat('MASTER')).getClobVal() as XML_QUERY
  7    from inv_master m
  8  /

XML_QUERY
--------------------------------------------------------------------------------
<?xml version="1.0"?>
<MASTER>
 <INV_NO>1</INV_NO>
 <INV_DATE>13-AUG-02</INV_DATE>
 <INV_DETAIL>
  <DETAIL>
   <ITEM_NO>1</ITEM_NO>
   <QTY>1</QTY>
  </DETAIL>
  <DETAIL>
   <ITEM_NO>2</ITEM_NO>
   <QTY>1</QTY>
  </DETAIL>
  <DETAIL>
   <ITEM_NO>2</ITEM_NO>
   <QTY>2</QTY>
  </DETAIL>
 </INV_DETAIL>
</MASTER>

This pretty much sums up your capabilities using SYS_XMLGEN.  Not to fear
however, there are a couple of other ways to generate XML.  Keep in mind, the
documentation says SYS_XMLGEN takes a single argument and converts the result to
XML.  It operates at the row level, returning an XML document for each row
returned in your query.  So for example, if you had three master rows in the
preceding example, you'd get three XML documents in your result set.

There are a couple of other ways you can generate XML out of Oracle.  You can
use the DOM APIs that are available in PL/SQL or Java from inside the database
or Java, C or C++ outside of the database.  This is a programming job, though,
it's nothing that's automated inside the database.  You'd basically be querying
up the data and looping over result sets to create XML nodes, nodelists,
elements, attributes and putting it all together yourself.  This is what I like
to label "when all else fails..." :-).

In Oracle9i R2, there's a new feature Oracle has implemented known as SQL XML.
This is an emerging standard out of OASIS which defines how SQL should be
querying XML data stores (see links at the bottom).  We can generate your XML
using SQL XML, as well:

SQL> select xmlelement(
  2           "MASTER",
  3           xmlforest(
  4             m.inv_no, m.inv_date,
  5             (select sys_xmlagg(
  6                       xmlelement(
  7                         "DETAIL",
  8                         xmlforest(item_no, qty)
  9                       )
 10                     )
 11                from inv_detail d
 12               where d.inv_no = m.inv_no
 13             ) "DETAILS"
 14           )
 15         ) as "XML_QUERY"
 16    from inv_master m
 17  /

XML_QUERY
--------------------------------------------------------------
<MASTER>
  <INV_NO>1</INV_NO>
  <INV_DATE>13-AUG-02</INV_DATE>
  <DETAILS>
    <ROWSET>
      <DETAIL>
        <ITEM_NO>1</ITEM_NO>
        <QTY>1</QTY>
      </DETAIL>
      <DETAIL>
        <ITEM_NO>2</ITEM_NO>
        <QTY>1</QTY>
      </DETAIL>
      <DETAIL>
        <ITEM_NO>2</ITEM_NO>
        <QTY>2</QTY>
      </DETAIL>
    </ROWSET>
  </DETAILS>
</MASTER>

So you can see you have some options.  For more information, use the following
links:

SYS_XMLGEN:
http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96620/xdb12gen.htm#1026350
SYS_XMLGEN with objects:
http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96620/xdb12gen.htm#1026578
SYS_XMLAGG:
http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96620/xdb12gen.htm#1031117
Generating XML data from the database (The SQL XML stuff):
http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96620/xdb12gen.htm
To do DOM parsing, you'd use the XML Developer's Kits.  Here's the table of
contents to the XDK Dev Guide:
http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96621/toc.htm
Hope that helps!

_smd_
_____________________________________________________________________
Sean Dillon - Senior Technologist, Oracle Corporation
Author "Beginning Oracle Programming"
http://www.amazon.com/exec/obidos/ASIN/186100690X

Review
      & Followup

      Rating: 4
Generate XML in
      desired format  August 14, 2002
Reviewer:
      Geetha  from Chicago, IL
Amazing...  Thanks a lot for the help.

      Rating: 3
Generate XML in
      desired format  August 14, 2002
Reviewer:
      Nancy  from NJ
Tom,

  How to parse data in this format to retrieve back the canonical oracle form to
insert into tables?

          Followup:

Hi, Sean here again...

If I understand you correctly, you want to know how to use Oracle to take an XML
file and use it to insert rows into tables.  To do this, we use either the
DBMS_XMLSave package (PL/SQL) or the oracle.xml.sql.dml.OracleXMLSave class
(Java).  In these libraries, there are procedures, functions and methods to
insert, update and delete rows of relational and object-relational rows based on
an XML document.

There is a "canonical" format that must be used to insert data into a table
using the XML SQL Utility.  To find this canonical format, use a PL/SQL block
such as:

SQL> connect scott/tiger
Connected.
SQL> set long 5000
SQL> var g_xml clob
SQL> declare
  2    l_ctx   dbms_xmlquery.ctxtype;
  3  begin
  4    l_ctx := dbms_xmlquery.newcontext('select * from emp where rownum=1');
  5    :g_xml := dbms_xmlquery.getxml(l_ctx);
  6    dbms_xmlquery.closecontext(l_ctx);
  7  end;
  8  /

PL/SQL procedure successfully completed.

SQL> print g_xml

G_XML
--------------------------------------------------------------------------------
<?xml version = '1.0'?>
<ROWSET>
   <ROW num="1">
      <EMPNO>7369</EMPNO>
      <ENAME>SMITH</ENAME>
      <JOB>CLERK</JOB>
      <MGR>7902</MGR>
      <HIREDATE>12/17/1980 0:0:0</HIREDATE>
      <SAL>800</SAL>
      <DEPTNO>20</DEPTNO>
  </ROW>
</ROWSET>

Obviously, in line 4 above, you'd need to replace the query with a select from
the table you are interested in, not EMP.  So let's INSERT a row into EMP using
the XML SQL Utility:

SQL> select empno, ename
  2    from emp
  3   where empno > 9000
  4  /

no rows selected

SQL> declare
  2    l_xml  clob;
  3    l_rows pls_integer;
  4    l_ctx  dbms_xmlsave.ctxtype;
  5  begin
  6    l_xml :=
  7  '<?xml version="1.0"?>
  8  <ROWSET>
  9    <ROW num="1">
 10      <EMPNO>9999</EMPNO>
 11      <ENAME>DILLON</ENAME>
 12      <JOB>JANITOR</JOB>
 13      <MGR>9998</MGR>
 14      <HIREDATE>01/01/1990 0:0:0</HIREDATE>
 15      <SAL>1</SAL>
 16      <DEPTNO>20</DEPTNO>
 17    </ROW>
 18  </ROWSET>';
 19    -- identify the table were inserting into
 20    l_ctx := dbms_xmlsave.newcontext('EMP');
 21
 22    -- perform the insert, capture the rows inserted
 23    l_rows := dbms_xmlsave.insertxml(l_ctx, l_xml);
 24
 25    -- close the context, clean up resources
 26    dbms_xmlsave.closecontext(l_ctx);
 27
 28    dbms_output.put_line(l_rows || ' rows inserted...');
 29  end;
 30  /
1 rows inserted...

PL/SQL procedure successfully completed.

SQL> select empno, ename
  2    from emp
  3   where empno > 9000
  4  /

     EMPNO ENAME
---------- ----------
      9999 DILLON

For more information on how to do DML using XML and the XML SQL Utility, see:

XDK Developer's Guide Table of Contents:
http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96621/toc.htm
XDK Developer's Guide XML SQL Utility Chapter:
http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96621/adx08xsu.htm#1000433
Hope that helps!

_smd_
_____________________________________________________________________
Sean Dillon - Senior Technologist, Oracle Corporation
Author "Beginning Oracle Programming"
http://www.amazon.com/exec/obidos/ASIN/186100690X

      Rating: 3
how to insert
      if XML not in canonical form  August 14, 2002

Reviewer:  Nancy  from NJ
Sean,

  Am sorry for being unclear.How do I form a canonical XML?Say this is the XML I
have and I need to insert data in the two tables that you have created.

<?xml version=''1.0''?>
<MASTER>
  <INV_NO>1</INV_NO>
  <INV_DATE>13-AUG-02</INV_DATE>
  <DETAILS>
    <ROWSET>
      <DETAIL>
        <ITEM_NO>1</ITEM_NO>
        <QTY>1</QTY>
      </DETAIL>
      <DETAIL>
        <ITEM_NO>2</ITEM_NO>
        <QTY>1</QTY>
      </DETAIL>
      <DETAIL>
        <ITEM_NO>2</ITEM_NO>
        <QTY>2</QTY>
      </DETAIL>
    </ROWSET>
  </DETAILS>
</MASTER>

          Followup:

Nancy,

Typically, you will have to transform your XML data into Oracle's canonical XML
format.  The standard way of doing this is via XML Stylesheet Language
Transformations (XSLT).  Using XSLT, you can take one input XML document,
transform it using what's known as an XSL stylesheet, and make it look like some
other XML document.  For more information about XSLT, see:
http://www.xml.com/pub/a/2000/08/holman/index.html
That being said, the way we do multi-table inserts into XML is a bit tricky.  To
do it, we need to use Oracle's object-relational capabilities.  We'll have to
define an object view (which simulates data stored in a user-defined object,
such as our MASTER object we used in this questions answer).  Using this
object-view, we can both query XML data OUT of the database and insert XML data
INTO the database.  Let's take this a step at a time...

First, create the object view.  Using the types we created earlier (MASTER,
DETAIL, DETAILS), we can define an object view as such:

SQL> create or replace view inventory_view of master
 2    with object id (inv_no) as
 3    select MASTER(m.inv_no, m.inv_date,
 4             cast(multiset(select d.item_no, d.qty
 5                             from inv_detail d
 6                            where d.inv_no = m.inv_no) as DETAILS))
 7      from inv_master m
 8  /

View created.

This will let us query from this view (i.e., "select * from inventory_view") and
get OBJECTS in our result set instead of standard relational rows.  If we do
this query in SQL*Plus, it will actually show us the object the best way it
knows how:

sdillon@FUNK92.US.ORACLE.COM> select * from inventory_view;

   INV_NO INV_DATE  INV_DETAIL(ITEM_NO, QTY)
---------- --------- -------------------------------------------------
        1 14-AUG-02 DETAILS(DETAIL(1, 1), DETAIL(2, 1), DETAIL(2, 2))

...but let's not get wrapped up in that right now... this is simply a means to
an end, inserting complex XML into the database.  Next, let's create our
"INSTEAD-OF TRIGGER" that will be used for inserting relational rows into the
correct tables whenever an object is "inserted" into this object view:

SQL> create or replace trigger inventory_view_bifer
 2  instead of insert on inventory_view
 3  begin
 4    insert into inv_master (inv_no, inv_date)
 5    values (:new.inv_no, :new.inv_date);
 6
 7    for i in 1 .. :new.inv_detail.count loop
 8      insert into inv_detail(inv_no, item_no, qty)
 9      values (:new.inv_no, :new.inv_detail(i).item_no,
10              :new.inv_detail(i).qty);
11    end loop;
12  end;
13  /

Now, whenever we insert a row into this object view, the rows are inserted into
the underlying tables appropriately:

SQL> select m.inv_no, m.inv_date, d.item_no, d.qty
 2    from inv_master m, inv_detail d
 3   where m.inv_no = d.inv_no
 4  /

   INV_NO INV_DATE     ITEM_NO        QTY
---------- --------- ---------- ----------
        1 14-AUG-02          1          1
        1 14-AUG-02          2          1
        1 14-AUG-02          2          2

sdillon@FUNK92.US.ORACLE.COM> insert into inventory_view
 2  values (2,sysdate,details(detail(10,10),detail(20,20)));

1 row created.

SQL> select m.inv_no, m.inv_date, d.item_no, d.qty
 2    from inv_master m, inv_detail d
 3   where m.inv_no = d.inv_no
 4  /

   INV_NO INV_DATE     ITEM_NO        QTY
---------- --------- ---------- ----------
        1 14-AUG-02          1          1
        1 14-AUG-02          2          1
        1 14-AUG-02          2          2
        2 14-AUG-02         10         10
        2 14-AUG-02         20         20

So, using this concept, you can use the XML SQL Utility to insert an XML
document (that looks like an "INVENTORY OBJECT") into INVENTORY_VIEW.  This will
then insert the appropriate records into the underlying table as you saw above.
To figure out what an "INVENTORY OBJECT" looks like in XML, we use the test I
described above:

SQL> var g_xml clob
SQL> declare
 2    l_ctx   dbms_xmlquery.ctxtype;
 3  begin
 4    l_ctx := dbms_xmlquery.newcontext('select * from inventory_view where
rownum=1');
 5    :g_xml := dbms_xmlquery.getxml(l_ctx);
 6    dbms_xmlquery.closecontext(l_ctx);
 7  end;
 8  /

PL/SQL procedure successfully completed.

SQL> print g_xml

G_XML
----------------------------------------------------------
<?xml version = '1.0'?>
<ROWSET>
 <ROW num="1">
   <INV_NO>1</INV_NO>
   <INV_DATE>8/14/2002 20:41:33</INV_DATE>
   <INV_DETAIL>
     <INV_DETAIL_ITEM>
       <ITEM_NO>1</ITEM_NO>
       <QTY>1</QTY>
     </INV_DETAIL_ITEM>
     <INV_DETAIL_ITEM>
       <ITEM_NO>2</ITEM_NO>
       <QTY>1</QTY>
     </INV_DETAIL_ITEM>
     <INV_DETAIL_ITEM>
       <ITEM_NO>2</ITEM_NO>
       <QTY>2</QTY>
     </INV_DETAIL_ITEM>
   </INV_DETAIL>
 </ROW>
</ROWSET>

So let's use a PL/SQL block to simulate storing this XML document into the
database using the XML SQL Utility:

SQL> declare
  2    l_xml  clob;
  3    l_rows pls_integer;
  4    l_ctx  dbms_xmlsave.ctxtype;
  5  begin
  6    l_xml :=
  7  '<?xml version="1.0"?>
  8  <ROWSET>
  9    <ROW num="1">
 10      <INV_NO>2</INV_NO>
 11      <INV_DATE>8/30/2002 20:41:33</INV_DATE>
 12      <INV_DETAIL>
 13        <INV_DETAIL_ITEM>
 14          <ITEM_NO>10</ITEM_NO>
 15          <QTY>10</QTY>
 16        </INV_DETAIL_ITEM>
 17        <INV_DETAIL_ITEM>
 18          <ITEM_NO>20</ITEM_NO>
 19          <QTY>20</QTY>
 20        </INV_DETAIL_ITEM>
 21        <INV_DETAIL_ITEM>
 22          <ITEM_NO>30</ITEM_NO>
 23          <QTY>30</QTY>
 24        </INV_DETAIL_ITEM>
 25      </INV_DETAIL>
 26    </ROW>
 27  </ROWSET>';
 28
 29    -- identify the table were inserting into
 30    l_ctx := dbms_xmlsave.newcontext('INVENTORY_VIEW');
 31
 32    -- perform the insert, capture the rows inserted
 33    l_rows := dbms_xmlsave.insertxml(l_ctx, l_xml);
 34
 35    -- close the context, clean up resources
 36    dbms_xmlsave.closecontext(l_ctx);
 37
 38    dbms_output.put_line(l_rows || ' rows inserted...');
 39  end;
 40  /
1 rows inserted...

PL/SQL procedure successfully completed.

...resulting in:

SQL> select * from inventory_view;

    INV_NO INV_DATE  INV_DETAIL(ITEM_NO, QTY)
---------- --------- -------------------------------------------------
         1 15-AUG-02 DETAILS(DETAIL(1, 1), DETAIL(2, 1), DETAIL(2, 2))
         2 30-AUG-02 DETAILS(DETAIL(10, 10), DETAIL(20, 20), DETAIL(30, 30))

... or more directly:

SQL> select m.inv_no, m.inv_date, d.item_no, d.qty
  2    from inv_master m, inv_detail d
  3   where m.inv_no = d.inv_no
  4  /

    INV_NO INV_DATE     ITEM_NO        QTY
---------- --------- ---------- ----------
         1 15-AUG-02          1          1
         1 15-AUG-02          2          1
         1 15-AUG-02          2          2
         2 30-AUG-02         10         10
         2 30-AUG-02         20         20
         2 30-AUG-02         30         30

6 rows selected.

Hope that helps!

_smd_
_____________________________________________________________________
Sean Dillon - Senior Technologist, Oracle Corporation
Author "Beginning Oracle Programming"
http://www.amazon.com/exec/obidos/ASIN/186100690X

      Rating: 5
Generate XML in
      desired format in Oracle 8.1.7 DB  October 31, 2002

Reviewer:  Staline Xavier  from UK
Hi Tom,

The information that you provided for "generate XML in desired format using
PL/SQL" was helpful for me.

I am also having exactly similar DB structure in my Oracle 8.1.7 DB. When i was
tring your solution in this DB, i was getting the following error;

SQL> ed
Wrote file afiedt.buf

  1  select sys_xmlgen(
  2     MASTER(m.inv_no, m.inv_date,
  3       cast(multiset(select d.item_no, d.qty
  4                               from inv_detail d
  5                              where d.inv_no = m.inv_no) as DETAILS)
  6                   )).getClobVal() as XML_QUERY
  7*     from inv_master m
SQL> /
                 )).getClobVal() as XML_QUERY
                   *
ERROR at line 6:
ORA-00923: FROM keyword not found where expected

SQL>

The following website says that sys_xmlgen() is a new feature in Oracle 9i.

So, I tried the same select query using xmlgen.getxml(), as follows;

CREATE TABLE SAMPLE_XML (
  RID          NUMBER,
  XML_CONTENT  CLOB)
/

declare
      x varchar2(500);
begin
     x:='select MASTER(m.inv_no, m.inv_date,cast(multiset(select d.item_no,
d.qty from inv_detail d where d.inv_no = m.inv_no) as DETAILS)) from inv_master
m';
    INSERT INTO sample_xml VALUES(1,XMLGEN.GETXML(x,0));
EXCEPTION
    when others then
    dbms_output.put_line(sqlcode||' '||SQLERRM);
END;
/

The following content has been stored in "xml_content" column in sample_xml
table, instead of generating the xml document.

<?xml version = '1.0'?>
<ERROR>oracle.xml.sql.OracleXMLSQLException: Character ')' is not allowed in an
XML tag name.</ERROR>

Can you please tell me where i went wrong. And can you suggest me how i can
generate xml from the about query?

Staline X.

      Rating:
      5
  October 31, 2002
Reviewer:  Staline
      Xavier  from UK
Sorry Tom, i forgot to give the link where its specified that "sys_xmlgen()"
function is 9i feature. here is;

http://otn.oracle.com/products/oracle9i/daily/sept14.html
Thanks,
Staline.X

      Rating: 4
Error using
      Nested Cursor  November 15, 2002
Reviewer:
      Bharath  from USA
import java.sql.*;
import java.math.*;
import oracle.xml.sql.query.*;
import oracle.jdbc.*;
import oracle.jdbc.driver.*;

public class GenXMl
{
  public GenXMl()
  {
  }

  public static void main(String args[]) throws SQLException
  {
    String tabName = "USER_TBL";
    String user = "comps/comps";
    DriverManager.registerDriver(new   oracle.jdbc.driver.OracleDriver());
    Connection conn =
DriverManager.getConnection("jdbc:oracle:thin:@ds101.dev.global.chase.com:1521:gi
b_dev7","comps","comps");
    OracleXMLQuery qry = new OracleXMLQuery(conn,"select  user_id,CURSOR(SELECT
USER_ROLE_ID FROM USER_ROLE_XREF WHERE USER_ROLE_XREF.USER_ID =
USER_TBL.USER_ID) FROM  "
    +"USER_TBL  where rownum <  5" );

    String xmlString = qry.getXMLString();
   System.out.println(" OUTPUT IS XML DATA :\n"+xmlString);
// Close the JDBC connection
   conn.close();
   qry.close();
  }
  }

When i try to run this  i am getting
<ERROR>oracle.xml.sql.OracleXMLSQLException: Character ')' is not allowed in an
XML tag name.</ERROR>

Other Details:
Using :Oracle 9i Jdeveloper Version 9.0.3
Database:8.1.7
OS:win 2000

      Rating: 3
xml
      conversion  April 25, 2003
Reviewer:  Binu
       from Trichur,Kerala
i am using oracle production database 8.1.5 and development database 8.1.6 .will
i be able to convert the oracle data to xml and vice-versa(i.e.) data present in
xml to oracle database in any one of the database .if so please can u guide
me.Probably if i get the solution in 8.1.5 it will be of great use

Thank u

          Followup:

you are hopelessly lost here.

there is no way you can effectively, efficiently, or anyway develop on 816 and
deploy to 815.

You have no way to test a single thing.

This is beyond bad, way beyond bad.

You better get 815 installed in development and test (yes, you do need a test
system) and then you can figure out what is possible.  otn.oracle.com has the
xdk for xml, there are some things that will work in 815.

better yet, get your systems onto supported and supportable releases (eg: 8174
at least)

      Rating: 5
Getting Error:
      ORA-00904: "XMLGEN"."GETXML": invalid identifier  April 25,
      2003
Reviewer:  Dilip  from NY, USA
SQL> SELECT * FROM INV_MASTER;

    INV_NO INV_DATE
---------- ---------
         1 25-APR-03

SQL> SELECT * FROM INV_DETAIL;

    INV_NO    ITEM_NO        QTY
---------- ---------- ----------
         1          1          1
         1          2          1
         1          3          1

SQL> select xmlgen.getxml('select m.inv_no, m.inv_date, d.item_no, d.qty from
  2  inv_master m, inv_detail d
  3  where m.inv_no = d.inv_no') from dual;
select xmlgen.getxml('select m.inv_no, m.inv_date, d.item_no, d.qty from
       *
ERROR at line 1:
ORA-00904: "XMLGEN"."GETXML": invalid identifier

SQL>

          Followup:

load it in then, otn.oracle.com -> technology -> xml.

not knowing your version et. al.  makes things hard, but go there and try to get
it loaded up.

      Rating: 3
any limitation
      on using oracle's xml utility ?  April 26, 2003

Reviewer:  reader
tom,

Is there any limitations in creating xml file in oracle 9.0.2. I have three
tables each tables might have records of 15000 perday, once xml is generated i
shall be deleting the records, this happens at off peak hours, and again down 1
year the records might factor to 30000 perday.

Do you see any potential limitation on using oracle's xml utility and as well on
the above scenarion.

Thanks for your terrific job.

          Followup:

benchmark it.  it the only way to be sure it'll meet your needs regardless of
what anyone says

      Rating: 2
xml
      conversion  April 28, 2003
Reviewer:  Binu
       from Trichur,Kerala,India
suppose if i have to use oracle 8.1.5 how to go about?? in converting
the data from oracle to xml as well as xml to oracle
can i do the feature in JAVA i.e. can u provide a solution of getting the data
stored in XML file to oracle.

<?xml version ='1.0'?>

<EMPLOYEE>
    <EMPDATA>
        <EMPNO> 7369 </EMPNO>
        <ENAME> SMITH </ENAME>
    </EMPDATA>
    <EMPDATA>
        <EMPNO> 7499 </EMPNO>
        <ENAME> ALLEN </ENAME>
    </EMPDATA>
    <EMPDATA>
        <EMPNO> 7521 </EMPNO>
        <ENAME> WARD </ENAME>
    </EMPDATA>
    <EMPDATA>
        <EMPNO> 7566 </EMPNO>
        <ENAME> JONES </ENAME>
    </EMPDATA>
    <EMPDATA>
        <EMPNO> 7654 </EMPNO>
        <ENAME> MARTIN </ENAME>
    </EMPDATA>
    <EMPDATA>
        <EMPNO> 7698 </EMPNO>
        <ENAME> BLAKE </ENAME>
    </EMPDATA>
    <EMPDATA>
        <EMPNO> 7782 </EMPNO>
        <ENAME> CLARK </ENAME>
    </EMPDATA>
    <EMPDATA>
        <EMPNO> 7788 </EMPNO>
        <ENAME> SCOTT </ENAME>
    </EMPDATA>
    <EMPDATA>
        <EMPNO> 7839 </EMPNO>
        <ENAME> KING </ENAME>
    </EMPDATA>
    <EMPDATA>
        <EMPNO> 7844 </EMPNO>
        <ENAME> TURNER </ENAME>
    </EMPDATA>
    <EMPDATA>
        <EMPNO> 7876 </EMPNO>
        <ENAME> ADAMS </ENAME>
    </EMPDATA>
    <EMPDATA>
        <EMPNO> 7900 </EMPNO>
        <ENAME> JAMES </ENAME>
    </EMPDATA>
    <EMPDATA    >
        <EMPNO> 7902 </EMPNO>
        <ENAME> FORD </ENAME>
    </EMPDATA>
    <EMPDATA>
        <EMPNO> 7934 </EMPNO>
        <ENAME> MILLER </ENAME>
    </EMPDATA>
</EMPLOYEE>

          Followup:

goto the otn.oracle.com website -> technologies -> XML there are java sdks there
that you can use in 815 either inside or outside the database.

April 28, 2003 Bhavani -- Thanks for the question regarding
      "Unused Objects and Load Average", version 8.1.7

      You Asked
Hi Tom,

I have two questions

1)
I want to cleanup my database.  I want to know all the tables, Procedures,
Functions and Packages, which are not being used in the last x number of months.
 So that I can check them and drop them from the database.  Is there any way?

2)
Whenever load on the server is more than 5 (for Eg.) I don't want any developer
to run their queries from SQL Prompt directly on the database.  Is there any way
to prevent?

Thanks in advance
Venkat

      and we said...
1) if you had auditing enabled sure.

If not, after the fact, no.  Besides -- this is the single most haphazzard
approach possible.  "drop that which doesn't seem to have been used".  Well,
maybe something else is dependent on it being there, maybe it has a reason for
being.

My suggestion -- do an inventory, get an accounting of the "who what where why
and when" of your objects and maintain that.  You'll never have to "guess".

looking for "stuff that doesn't seem to have been used" is a fairly poor
approach to managing your infrastructure....

2) doesn't make sense.  are you saying basically that "i have but one machine
and all for one, one for all"?

Load is a measure the OS uses to report to you.  Inside the database, we do not
"see" load.

That and you cannot tell if the developers are using sqlplus, toad, odbc, your
application -- whatever.  they all look the same.

sounds like you need to get an environment going here -- development, test,
production -- and have each be managed....
Review &
      Followup

      Rating: 5
Neat answer
      !!  April 28, 2003
Reviewer:  Anand
       from Atlanta
To the point, precise !

Go Tom !! :)

April 28, 2003 giorgos -- Thanks for the question regarding
      "implementing a data mart / warehouse", version the newest existing

      You Asked
Which Oracle products do i need to fully develop a data mart or
a data warehouse?

      and we said...
See

http://www.oracle.com/datawarehouse/products/index.html
It really depends on your needs.

Every solution will involve Oracle8i the database.

You may need some tools such as Oracle Discoverer for data
mining, Express for OLAP analysis, DataMart Suite for a bundle
with ETT and other tools, and so on.

The options and descriptions of each are laid out at that URL...
Review &
      Followup

      Rating: 3
Tom, can you
      update the link? I think it's outdated.  April 28, 2003

Reviewer:  Kashif  from Houston, TX
Thanks,

Kashif

          Followup:

http://www.oracle.com/ip/analyze/warehouse/index.html

April 28, 2003 Ron -- Thanks for the question regarding
      "Recovery from bkup of open db with no activity during backup", version
      8.1.7

      You Asked
I'd like you opinion on the odds of recovery in the following situation.

I'm new (3 weeks) in this shop and have run into the classic problem with a db
breaking and inadequate Oracle backups / exports (the script hard coded the
datafile names and was never updated).

I am thinking about an alternative with a unix full system backup:
1. The whole box is backed up with the db open.
2. Assuming there were no updates for the whole time of the backup (middle of
the night / weekend, no user updates, cron jobs or dbms_jobs during that time).

If I were to restore that unix system backup and try to start the db, how would
that be different from a startup after a shutdown abort?

Thanks

      and we said...
that would be the single worst decision of your entire career -- for as you know
-- there is one thing and one thing only a DBA must be 100% correct on and that
is backup and recovery.

Every other mistake a DBA makes - well, we can recover from that, we can fix it.
 Mess up the backup and recovery stuff however -- well, you lose big time.

No, you cannot just do OS backups like that.

The database never sleeps -- never.  Your assumption there are no updates is
100% false.  The database is constantly going.

You need to employ hot backups.

You never want to use OS file sytem dumps (how many times have I seen a windows
machine kill the database cause someone did a network backup and we cannot open
our control files/datafiles/whatever read write...)

You need to do it *right*.
Review & Followup

      Rating:
      4
  April 28, 2003
Reviewer:  Ron
      Plank  from Raleigh, NC
Of course you are right.

I know this sounds lame, but backups are NOT my responsibility here.  I was just
trying to help with someone else's problem.

Thanks for the insight.
Ron

      Rating:
      4
  April 28, 2003
Reviewer:  Ron
      Plank  from raleigh, nc
It just occured to me, you may have missed the point of my question.

I was Not asking about the viability of network backups of an open db.  In this
situation, the data is lost and will be painful to recreate.  I was trying to be
imaginative about bailing out a new associate.

But now I'm CURIOUS:

What kinds of activities update the control files, datafiles, redo logs,
rollback segments or other backup related files when the db is open but idle
with respect to client activities?

I know checkpoints occur every few seconds, but when there is nothing to write
and the scn does not change, what gets updated?

Sorry for the mis-communication.

Thanks

          Followup:

I just used a network backup as a "for example", backing up an instance that is
running AND that you have not put into backup mode is a waste.

smon runs all of the time.
aq generates stuff in the background.
maybe that checkpoint you think happened hours ago doesn't happen until now.
control files are updated continously.

April 28, 2003 louise -- Thanks for the question regarding
      "ORA-00942: table or view does not exist (but they do!)", version 8.1.7

      You Asked
Hi Tom,

I am writing a v basic script to run a cold backup of a specific database.

All i need are the control files, data files and redo logs.  SQL returns results
for the table dba_data_files but not for the dynamic performance views V$logfile
and v$controlfile?  Why is this - the results are returned when I connect via
sql and select from them manually?

Thanks for any help

see script below:

set -e
ORACLE_BASE=/tpp/oracle
ORACLE_HOME=/tpp/oracle/general/8.1.7
###
ORACLE_SID=HOUSETUP
export ORACLE_BASE ORACLE_HOME ORACLE_SID
export LD_LIBRARY_PATH EDITOR TERM

# * Start sqlplus and make the backup file list.
sqlplus -s system/manager<<EOF1
SET ECHO ON
set pagesize 0
spool db_files.lst
select member from v$logfile;
select file_name from dba_data_files;
select name from v$controlfile;
spool off
exit
EOF1

# * Shut down the database.
svrmgrl << EOF2
connect internal
shutdown immediate
EOF2
# * Make a cold backup tar file.
cat db_files.lst |xargs tar -cvf /dev/rmt/0 $2
# * Restart the database.
svrmgrl << EOF3
connect internal
startup
EOF3

      and we said...
Well -- first and foremost

NEVER BACKUP REDO LOGS.  Period.  You never ever backup online redo logs.  you
can only do bad things with them accidently.  You never need to back them up,
you never want to back them up.

Second, you backup control files using "alter database backup controlfile to
trace" or just alter database backup controlfile to 'filename'.  YOU tell us
where to put them.  You should use the command backup control file to trace.

Third, svrmgrl goes away with your next version of Oracle -- use sqlplus..

Fourth, cold backups!  don't do it, do hot -- consider using RMAN instead of
scripting it yourself.

In answer to your question - ask yourself, what happens to $string in sh?  you
are selecting name from v and member from v -- since $LOGFILE and $CONTROLFILE
probably are not set in your environment.

select name from v\$controlfile;

will fix that -- but don't backup those log files.
Review &
      Followup

      Rating: 5
re: ORA-00942:
      table or view does not exist   April 29, 2003

Reviewer:  Louise Welch  from England
Thanks for your advice - it was very prompt!  You also went way beyond just
answering my original question and pointed out other mistakes I made too!

April 28, 2003 Prince -- Thanks for the question regarding
      "Timestamp appends "000" when formating.", version 9.0.1

      You Asked
I have the following query. I am wondering why, oracle sufixes "000" with the
given timestamp value when I format it.

SQL> alter session set NLS_TIMESTAMP_TZ_FORMAT='dd/mm/yyyy hh24:mi.ssxff' ;
SQL> alter session set nls_timestamp_format='dd/mm/yyyy hh24:mi.ssxff';

SQL> select systimestamp from dual ;
SYSTIMESTAMP
----------------------------------------------------------------------
27/02/2002 15:51.12.539880

SQL> select to_timestamp('27/02/2002 15:51.12.539880', 'dd/mm/yyyy
hh24:mi.ss.ff') from dual ;

TO_TIMESTAMP('27/02/200215:51.12.539880','DD/MM/YYYYHH24:MI.SS.FF')
---------------------------------------------------------------------
27/02/2002 15:51.12.539880000

Why do I get the last three zeroes? How do I supress them

Note: I return the "systimestamp" from a procedure and the returned value always
has "000" suffixed. I need to supppress the last three 0s. How do I do it?

Thanks.

      and we said...
It the default timestamp precision is 6.

The max is 9.

In the to_timestamp, it is assuming you have upto 9 digits in there. You can use
the CAST function to correct this:

  1  select
  2  cast( to_timestamp('27/02/2002 15:51.12.539880', 'dd/mm/yyyy
hh24:mi.ss.ff')
  3        as
  4       timestamp(6) )
  5*           from dual
tkyte@TKYTE9I.US.ORACLE.COM> /

CAST(TO_TIMESTAMP('27/02/200215:51.12.539880','DD/MM/YYYYHH24:MI.SS.FF')AST
---------------------------------------------------------------------------
27-FEB-02 03.51.12.539880 PM

Review &
      Followup

      Rating: 2
What version of
      Oracle does this work with?  February 28, 2002

Reviewer:  Mohit Dubey  from UK
Hi Tom!

What version of Oracle does the "systimestamp" call work with? Is it a 9i
feature or does it work on 8.1.7 too?

Mohit.

          Followup:

It is new with 9i and up.

Supports fractional seconds (upto 9 digits)
Supports timezones as well.

      Rating: 4
Great. But,
      will the last three digits be ever used?  February 28, 2002

Reviewer:  Prince
Thanks Tom.

It worked perfect.

Now, Will the last three digits of the last 9 digits be ever used?

I tried "select cast(systimestamp as timestamp(9)) from dual". This always gives
me the last three digits of the millisecond (or is it micro??) as "000"

ex.

28/02/2002 15:42:40:mmmmmm000

28/02/2002 15:42:40:nnnnnn000

          Followup:

you can STORE times that granular.

I haven't yet see a system clock that will generate a time that granular (and we
are reliant on that).

Oh I just know someones going to followup on this with a machine that does ;)

If you are getting time inputs from external systems/external devices and they
measure time more granularly -- you can store it.  the system clocks do not give
it to us as yet, they don't tick that fast.

      Rating: 5
Adding is not
      allowed  April 28, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
I was just trying the following to add and substract two timestamp fields but
adding is not allowed. What me be the reason that you can't add but can
substract?

------------------------------------------------------
SCOTT > create table tt (t1 timestamp, t2 timestamp);

Table created.

SCOTT > insert into tt values(systimestamp,systimestamp);

1 row created.

SCOTT > select * from tt;

T1                            T2
----------------------------- ------------------------------
28-APR-03 10.07.38.000000 PM   28-APR-03 10.07.38.000000 PM

SCOTT > select t1-t2 from tt;

T1-T2
--------------------------
+000000000 00:00:00.000000

SCOTT > select t1+t2 from tt;
select t1+t2 from tt
         *
ERROR at line 1:
ORA-30087: Adding two datetime values is not allowed

SCOTT >

          Followup:

tell me -- what could it possibly return?

It makes sense to subtract -- you get an interval, a difference between two
times.

"subtract Wednesday from Tuesday and you get one day" -- sensible.

"Add Wednesday to Tuesday - what should you get" -- not sensible....

April 28, 2003 Elamaran -- Thanks for the question regarding
      "Column Name , Command", version 8.1.6

      You Asked
Sir,

  1. One of my oracle DBA friens has told me that he has created one procedure
to change a column name without affecting the data.

Is it Possible?

  2. How to take a DDL query or command associated with the created objects?

Thanks in advance

      and we said...
1) prior to Oracle9iR2, when this command exists:

ops$tkyte@ORA920> alter table t rename column c1 to cx;

Table altered.

there is no way to rename a column.  You would use a view:

o rename T to T_TABLE
o create view T as select ..., c1 CX, ... from t_table
o grant on T
o revoke on T_TABLE

to rename a column.  If they have a script that purports to rename a column --
you should be afraid of them and their methods.  They must be updating the
Oracle data dictionary directly and in doing so totally break every rule.  Their
databases are unsupportable and unsupported.  They are among the worst type of
DBA's out there....

2) don't understand?  if you want the DDL for an object -- exp and then imp
indexfile=test.sql or imp show=y can get that.
Review &
      Followup

      Rating:
      5
  April 28, 2003
Reviewer:  Jim

Follow up to 1

Depending on the definition of "affecting the data"
I guess the procedure may add the new column to the table
insert the old column data into the new column and
drop the old column..

Jim

          Followup:

true -- yes, they could do that in 8i (when we added drop column)

April 29, 2003 Shah -- Thanks for the question regarding
      "Tablespace Coalesce ?", version 7.3.3.6

      You Asked
Tom,

I've one tablespace called MISC and the number of free space is
470 rows and a few of them having a same file_id and same bytes
size with different block id.

My question is, when I do tablespace coalesce on this tablespace
it's doesn't work. As far as I know, the coalesce command will
not work on different datafile but on this case I have only one
datafile.

Can you pleae elaborate more on "coalesce" command, how it's
work.

Thanks,

Shah

      and we said...
Coalesce will take ADJACENT free space and coalesce it into one
larger free chunk.  If your tablespace looks like this:

|t1|free|t2|free|i1|free|t1|free|t1|free|i2|free|free|t1|

(where Tx and Ix are extents allocated to tables and indexes and
"free" is a free extent)

after coalescing we would have:

|t1|free|t2|free|i1|free|t1|free|t1|free|i2|free+free|t1|
                                            ^^^^^^^^^

only 1 less free extent will be observed.  To make this
tablespace have all the free extents added together into one big
one involves MOVING every existing object in there.  You can
achieve this in 7.3 by exp/imp.

In order to avoid issues like this, I suggest you ALWAYS use:

o pctincrease =0
o initial=next = CONSTANT VALUE

in a tablespace.  In that way, you never need to coalesce
adjacent free extents (doing so would only decrease performance)
and every extent can always be used for some other object (hence
you are never left with wierd sized extents that no one can
use)...

Review &
      Followup

      Rating: 4
Why coalescing
      is not required when we use locally managed
      tablespace?  April 28, 2003
Reviewer:  A
      Reader  from Houston,TX
Why coalescing is not required when we use locally managed tablespace? I would
appreciate if you could explain the concept behind it. I could visuallize the
mechanism of coalescing in DMT but it is hard to do with LMT why it is not
required and how oracle does it for example, when I drop huge tables in LMT.

Thanks.

          Followup:

all coalescing ever did was to join contigous free extents into one larger
contigous extent.

Say you had a table -- it was laid out like this:

start block     end block
100             105
106             110
111             115

that is, it had 3 extents of 5 blocks each.  Now, when you drop that table --
using a DMT, we would end up with 3 FREE extents -- each of 5 blocks.  If
someone wanted 10 contigous blocks -- they might not use these free extents
because each of the 3 appears too small (smon would coalesce them later if
pctfree for the tablespace > 0).

Therefore, using a dmt, it was sometimes a good idea to manually run the
coalesce command to turn those 3 contigous yet separate free extents into 1
extent.

Now, say that is a LMT.  Well, firstly -- it would not really matter too much if
they were coalesced OR NOT if you were using a uniform sized extent allocation.
In that tablespace ALL extents would be 5 blocks -- coalecsing there is somewhat
"not meaningful".  However, since it is an LMT and the extents are managed in a
bitmap -- they will in fact appear to be one big free thing -- since they will
just be three zeroes in a row.

I think you are confusing coalescing with some sort of physical reorganization
process -- something of which is almost certainly a waste of time in dmt's as
much as in lmt's...

      Rating:
      5
Thanks!  April 29, 2003
Reviewer:  A
      reader  from Houston, TX

April 29, 2003 Sridevi -- Thanks for the question regarding "No
      logon/connect in Forms 6 Application", version 6

      You Asked
Hi Tom,
I have an Application created in Developer 6. I want the first
form (it is a welcome screen) to appear without the user having
to connect to any database. In other words the first screen of
the application should be accessible to all and sundry. On
clicking on a command button say "login" in this form, the logon
screen should pop up.

At the moment I have created a dummy user with only "create
session" privilige and I am opening my application with
logon_screen=no and with the dummy user's userid. On clicking
the "login" command button on the form, the logon screen is made
to appear using "logon_screen". And then user validations are
done.

To put it simply I want to know if in forms 6, is there an
equivalent to the "nolog" parameter that we use open SQL*plus
without connecting to any database?

Thanks in advance....

      and we said...
If you make an ON-LOGON trigger such as:

DECLARE
  un  VARCHAR2(80);
  pw  VARCHAR2(80);
  cn  VARCHAR2(80);
begin
  default_Value('NO','global.do_logon');

  if ( name_in( 'global.do_logon' ) = 'NO' )
  then
        null;
  else
    un := Get_Application_Property(USERNAME);
    pw := Get_Application_Property(PASSWORD);
    cn := Get_Application_Property(CONNECT_STRING);
    IF cn IS NOT NULL THEN
      LOGON(un,pw||'@'||cn);
    ELSE
      LOGON(un,pw);
    END IF;
  end if;
end;

and then, in the when-button-pressed trigger:

:global.do_logon := 'YES';
execute_trigger( 'on-logon' );

you'll achieve what you desire.
Review &
      Followup

      Rating: 2
BIG
      BUG  November 08, 2002
Reviewer:
      A reader
Hi, TOm,

Until today, I found a very BIG BUG in the oracle form.
THat is when you run the form, the default logon window
OR log_on ptocedure can not catch the blank padded after
the username:
Username:  "abcd    "
Password:  ....
connect string: "...."

The form let this logon info passed even there are lots
of spaces after the "abcd". This caused us lots of trouble
because we use the "GET_APPLICATION_PROPERTY(USERNAME)"
lots of times and store them in the DB---- with the blanks
padded after the real username. I mean we should not let the
logon username with the blanks padded passed, how can we do that?

Thanks

          Followup:

it is not a bug in forms -- it is a bug in your code.

You should have used rtrim( get_application_property(username) ) to trim it if
that is what you need.

      Rating: 2
Connect to
      DB  November 08, 2002
Reviewer:
      A reader
Hi, TOm,
I can not agree with you.
Say, id you connect to DB by "abcd    " instead of
"abcd", then you can not log into the DB.
How will you explain the difference?

Thanks and have a nie day

          Followup:

you don't have to agree with me -- however, unless and until you use rtrim --
you are hosed right?

ops$tkyte@ORA920.US.ORACLE.COM> connect scott    /tiger
Connected.
ops$tkyte@ORA920.US.ORACLE.COM>

hmm?  you could always use USER as well -- instead of get app property.

      Rating: 3
Learn another
      thing from you  November 08, 2002
Reviewer:
      A reader
HI, TOm,

How did you get:
ops$tkyte@ORA920.US.ORACLE.COM> connect scott    /tiger
Connected.
ops$tkyte@ORA920.US.ORACLE.COM>

mine is got error:

connect scott   /tiger
Invalid option.
Usage: CONNECT <username> [AS SYSDBA|SYSOPER]
is it 9i diffenrent from 8i?

Ok, would you tell me the fact behind the scene that why
we allow blank padded username passed validation--why
get_application_property(username) doesn't auto trim the right space? Is it
because we can create an user in DB with several blanks
padded? If so, why can't I log into DB either with
connect scott   /tiger OR
connect "scott   "/tiger?

My exsting system has lots of GET_APPLICATION_PROPERTY(USERNAME) both in forms
and packages in DB, I don't want to
mofified them one by one? Are you tell me that
GET_APPLICATION_PROPERTY(USERNAME) is not safe and we should always use USER? I
mean that
the HELP system in the file should address that issue at least to the developer?

Thanks and have a nice day

          Followup:

JServer Release 8.1.7.4.0 - Production

ops$tkyte@ORA817DEV.US.ORACLE.COM> connect scott   /tiger
Connected.
ops$tkyte@ORA817DEV.US.ORACLE.COM>

PL/SQL Release 8.0.6.0.0 - Production

ops$tkyte@ORA806.WORLD> connect scott   /tiger
Invalid option.
Usage: CONNECT <username> [AS SYSDBA|SYSOPER]

looks like 8i and up is OK with this.

You can use get app property -- you just have to be aware that it returns what
the user typed.  If trailing blanks are a problem -- you could add a trigger to
the table:

...
   :new.userid_field := rtrim(:new.userid_field);
.....

and that'll fix all of the forms.

      Rating: 3
Login Screen in
      the beginning  April 29, 2003
Reviewer:  Raj
       from India
Hi Tom,

I tried the sol. given above(Clicking the button and displaying the logon
screen) but I have a similar problem.
Actually I want the default logon_screen to appear all the time whenever I
access the application in the beginning itself.
I tried giving logon_screen built-in in preform I get only the logon screen but
when I click connect it goes to the application without giving any error message
even if I give the wrong password.

My requirement is
When I click the application the default screen should appear and I should
connect to the database based on the informations I enter in the default logon
screen or display an error if the data given is not correct.
If I click cancel it shoul exit the form

Pls. give me a solution.

          Followup:

i don't code forms, haven't done so since about 1995 when this web thing came
along.  I can suggest you try otn.oracle.com under discussion forums ->
developer.  there you will find many people who use forms every day.  they may
be able to help.

April 29, 2003 Mahomed -- Thanks for the question regarding
      "Exclusions from Hot backup.", version 7.3.4

      You Asked
Hi Tom,

My application has 2 very large tablespaces which hold historical
tables & their indexes. These tablespaces are written to only once a week and
the rest of the time the users can query them. A hot backup is done every night
and a full export is done once a week. Is it possible to exclude these
tablespaces from the backup and recovery ? My reason for asking this question is
that the user wants a quick backup and a very quick restore from any media or
system failure. The tables and indexes in these 2 tablespaces are not critical
for the operation of the application.

Thanks,
Mahomed

      and we said...
Well, its totally upto you .

Yes, you can skip them.

However, it'll complicate restore procedures -- you'll need to rebuild the
history, you'll need to recreate the indexes and you'll need to know how to
recover without having these tablespaces backed up.

Practice it, document exactly how to recover given that you don't have
everything.

Your restore will be somewhat SLOWER since rebuilding an index will probably
take longer then recovery would AND importing a large history table and indexing
it definitely will take much longer.

You can always just backup the history tablespace ONCE a week instead of every
night since its relatively static - just keep the archives for the entire week
handy.  Or -- you can:

alter tablespace history read only;
back it up.  don't need archives anymore.

when you goto modify it:

alter tablespace history read write;
modify it.
alter tablespace history read only;
back it up.  don't need the archivess to recover anymore.

You might give serious consideration to that last idea.  Use read only
tablespaces and back those up.  It'll be very fast to restore with.  You can
skip that export if you want, it won't be needed (and the copy of the datafiles
for the history stuff will be so much faster then an export)

Review &
      Followup

      Rating:
      3
  November 20, 2001
Reviewer:
      A reader  from U.K

      Rating: 5
Backing up fact
      table   August 16, 2002
Reviewer:  Steve
       from Montreal
Hi Tom,

We have a similar situation. We have a data warehouse with
1 main fact table that has the following properties:

  o fact table makes up about 90% of the storage
  o fact table stores 4 months worth of data
  o fact table is partitioned by day.
  o as a new day rolls in, the oldest day rolls out
  o in theory, once a day is loaded in the fact table, that
    partition doesn't change (there are exceptions).
  o there are 4 data tablespaces  (one per month) for the fact table.
  o there are 4 index tablespaces (one per month) for the fact table.
  o There are only 2 indexes (LOCAL) on the fact table.
  o unfortunately, none of the tablespace are "read only"

It looks soemthing like this:

   Fact Table

   +-----------+
   |    May    |
   |           |
   |Tablespace |
   | TS1_data  |
   | TS1_index |
   +-----------+
   |   June    |
   |           |
   |Tablespace |
   | TS2_data  |
   | TS2_index |
   +-----------+
   |   July    |
   |           |
   |Tablespace |
   | TS3_data  |
   | TS3_index |
   +-----------+
   |  August   |
   |           |
   |Tablespace |
   | TS4_data  |
   | TS4_index |
   +-----------+

We are trying to develop an efficient backup solution
leveraging on the fact that over 99% of the fact
table remains "static" for 120 days.

QUESTION 1:
===========

It seems a "no brainer" to go with RMAN because
of incremental backups.

But what I'm wondering is how often should we take
full and incremental backups? For instance, once we
move into September, we can take a full backup
of the tablespace holding August's data (TS4). From
then, we could take only incrementals of TS4 until
December's data rolls in.

We'd save a lot of space in the backups because the
incrementals would be mostly empty. But something
gives me a funny feeling in my stomach about having a
full backup that's about 90 days old and a bunch
of incrementals.

Maybe we should just "Keep It Simple" and take regular
full and incremental backups and not really care about
the fact that most of the data is static and the full
backup from one week will be the same as the full backup
from the previous week.

Any thoughts on this?

QUESTION 2:
===========

We have just 2 idexes on the fact table that are on
4 index tablespaces. But these tablespace are huge.

The index can be easily recreated. Would you think
it's acceptable to exclude these tablespaces from
the backups and, in the case of error, simply rebuild
the index? It would allow us shorten the backup window.

If yes, how would recovery of a datafile that is
not part of any backup proceed (from a high level)?

Would it go something like:

   drop the tablespace and datafile
   recreate the datafile
   recreate the tablespace
   recreate the indexes.

Thank you very much.

Steve

          Followup:

q1) incrementals are almost always slower, if speed is your need....

anyway, each tablespace is independent of every other tablespace.  I would
suggest as an easy "no brainer" way --

o at the end of each month, alter the last months tablespace to be read only.
o do a full backup of that tablespace (you never need to back it up again)

o during the month, backup SYSTEM, UNDO (rollback) and your data (eg: all of
your non-read only tablespaces) on a recurring basis.

o at the end of the month, goto step one again - make it read only and start
over

Now, all you backup when you backup is the current months data.  You'll never
need to take a FULL backup really.  If any of the read only tablespaces die, you
just restore their files (the read only copies you made need no recovery!)  If
any of the read write tablespaces dies -- you have the hot backups and archives
to restore them with.  Simple, small, easy.

q2) your backup window will be very small cause you are only backing up a
fraction of your database.  Whilst you could recreate the indexes, I would be
sorely tempted to back them up as it would reduce the downtime.  Using a hot
backup, the backup window is 24 hours a day.

      Rating:
      5
Thanks  August 16, 2002
Reviewer:
      steve  from Montreal
Thanks Tom.

I always hear people worried about "the backup window".
I totally agree with you that the backup window is
24 hrs a day with hot backups. So I always wondered why
everyone needs a short backup window.

What would you say is the "hit" on the database when
backing up the static fact table and its indexes? i.e.
if there is no insert/update activity on this table during
the backup, does backing it up have a huge impact on
the rest of the database performance? Would it be
unthinkable to backup parts of this fact table in the
middle of the day?

Thanks again

Steve

          Followup:

You'll just have "normal" IO contention.  A backup is an IO intensive thing.

It would not be unthinkable -- no more unthinkable then having two people do a
full scan at the same time on the same table.

      Rating: 3
Exclusions from
      Hot backup  April 29, 2003
Reviewer:
      A reader
Earlier you said.

alter tablespace history read only;
back it up.  don't need archives anymore

is this same as alter tablespace begin backup ?? is there any difference ?

          Followup:

begin backup lets you do a hotbackup.  it would be followed shortly by end
backup.

read only makes the tablespace "read only".  No more changes.  If you make it
read-write -- you'll need to back it up again.

they are two entirely different concepts.

April 29, 2003 Balaji -- Thanks for the question regarding "To
      determine SGA size for unix box", version 8.0.5

      You Asked
How to derive the size of the SGA at the time of installation.
Is there any thumb rule which needs to be followed for
specifying the SGA for large database.  Assume I have 16 Gig of
RAM. What will be the Optimum SGA Size for Oracle 8 and 8i ?

      and we said...
This is an answer that won't be an answer.

It depends on many factors.  There is no single "out of the box"
answer.  You'll start with something and tune it from there.

o Going to run lots of PLSQL? (eg: replication, Oracle apps,
your own stuff with tons of PLSQL) - need a large shared pool

o Going to run Multi-Threaded Server?  Need a large shared pool

o Going to run Java, lots of it, in the database?  Need a large
java shared pool

o Going to have 10 users doing lots of really big queries?  Need
larger settings for sort area size, hash area size and such --
these might not affect the SGA (but they might if you use MTS)

o Going to have 1000 users doing OLTP?  The mix will be
different.

o Going to have anything else on this box?  They will need
memory.

The settings that *most* affect the SGA size will be:

o db_block_buffers -- how many buffers to cache in the SGA.
o shared_pool_size -- if you do not have a large pool allocated
                      this will be used for that as well.
                      shared sql, loaded plsql and so on are in
                      here.
o java_pool_size   -- when you use java, this is used.
o large_pool_size  -- used by MTS, backup, and so on for large
                      chunks of memory that should be freed on
                      demand, not managed LRU like shared pool.

Thing that will affect it to a lesser degree:

o log_buffer       -- redo log buffer, measured in Kbytes.

Review &
      Followup

      Rating: 3
Really Helpfull
      to gain the basic  November 27, 2001
Reviewer:
      Siamak Thanawaal  from Mumbai, India
Really Helpfull to gain the basic.. & start off from some where. Though many
times your calculation go wrong but you at least know where to start from & this
normally  helps as your basic check list....

      Rating: 4
Sometimes
      Database is very slow  April 03, 2002
Reviewer:
      sivababu  from GERMANY
Hello TOM,
     Our one of the customer database is slow. But it was working fine past
days. Is there any query to check why it is slow now?. The database is running
oracle 8.1.5 under LINUX with our ERP application.

While starting the databse all the parameters showing 32 bytes. I didn't get why
it is.
svrmgrl>startup
sga             32 bytes
........        32 bytes
etc..

Thanks and expecting your reply.

regards,
sivababu

          Followup:

I've never heard of that -- 32bytes.

Ask the customer what they changed.  when they say "nothing" ask "no really --
what did you change"....

statspack can help you diagnose instance level performace issues.

      Rating: 4
32
      bytes  April 03, 2002
Reviewer:  Andrew
       from CA, USA
I've seen a similar thing when running svrmgrl where the the number of bytes are
all null - that turned out to be the wrong svrmgrl (different versions of Oracle
installed on the same machine). For unix do a "which svrmgrl".

      Rating: 3
Size of SGA vs
      RAM   April 04, 2002
Reviewer:  Charanjiv
       from Delhi, India
dont know if this belongs here, but hopefully you will answer. I have noticed on
my development server (8.1.6) win NT, the shared pool free memory remains a
stable 60-70 MB while my server memory (ram) decreases with usage (from 300 to
40/50MB free). What is contributing to this.

          Followup:

what is the size of the shared pool on the two boxes.  Should be the same for
prod as for development, probably isn't.

      Rating:
      5
  December 26, 2002
Reviewer:
      A reader
Hi TOM,

 In dedicated config. how much TOTAL memory each connection would use on the
server ?

          Followup:

don't know, somewhere between a couple hundred k to as much ram as the machine
will give it.

depends on what you do, what your settings are.

take a plsql table and put a million records in it -- lots more memory then if
you put 5 records in it..

have a sort_area_size of 1m vs 100m....

and so on.

I can say in 9i, if you use workarea_size_policy = auto and a
pga_aggregate_target, you can have some control over the SUM of the memory used
by each PROCESS....

      Rating:
      5
LOG_BUFFER  December 26, 2002
Reviewer:
      Jay Hart
Tom, sometimes its is recommended that LOG_BUFFER be changed (most times,
increased) to improve performance, and I've read much to that respect. HOWEVER,
wouldn't allocating more than 1M be largely unused, because LGWR is signaled to
write when R.L buffer has 1M of redo entries?? So wouldn't anything above 1M be
'wasteful'?

          Followup:

Well, it is

o every 3 seconds
o when 1/3 full
o upon commit
o (new) when 1meg full

Now, say you have a relatively large transaction that generates say 10meg of
redo.  When it hits 1meg of redo -- lgwr will start flushing in the background.
Whilst it is flushing - your transaction generated another 2meg of redo (3meg
taken by it + whatever else is going on in your system).  when lgwr is done
flushing the first 1m, it comes back and finds 2+meg more to flush and sets
about doing that.  Whilst it is flushing that 2meg, your transaction generates
another 3meg and so on... (5+ meg of redo in the buffer now).

If the redo log buffer was 1meg large -- what would have happened:

o your transaction generates 1meg of redo
o lgwr writes it out -- WHILE THIS IS TAKING PLACE, everyone else is waiting,
your transaction -- all transactions.
o you generate another 1m of redo -- goto #2 and loop over and over

So, redo log buffers of 1's to even 10's of megabytes do make sense in some
cases -- to avoid the waits for lgwr.

      Rating: 5
GREAT
      HELP  December 29, 2002
Reviewer:  Jay Hart

      Rating: 5
redolog buffer
      waits  February 04, 2003
Reviewer:  Moh'd
      Saeed  from Canada
Hi Tom:

This is a very interesting explanation about Tx waiting for LGWR.

How/what tells me that my transactions are waiting for LOGWR to complete writing
the log buffer to redo files ?

Thanks for your help.

          Followup:

use statspack -- take a snapshot, have your apps running at full steam, get a
cup of coffee, 15/30 minutes later take another snapshot.

Then, read the report.  Look at the top 5 timed events.  If you see events like
"log file sync" "log file ...." -- you are waiting on lgwr.

      Rating: 5
bytes or
      kbytes  April 29, 2003
Reviewer:  Irfan
       from Kuwait
Hi Tom

In your answer to the original question

/*
log_buffer       -- redo log buffer, measured in Kbytes.
*/

does log buffer in init.ora is stored in kbytes? but normally all other values
are in bytes - bit confusing me.
Thanks

          Followup:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76961/ch188.htm#55848
it is specified in bytes -- my kbytes reference was more to the overall size (it
is generally measured in kbytes, not mega/gigabytes - it is small relative to
the other sga components)

April 29, 2003 --
      Thanks for the question regarding "Volumetric Analysis", version 9.2.0.3.0

      You Asked
Hi Tom,

We need to do volumetric analysis for our Databases. We are still in the HLD
phase.The actual database has not been created yet. We are testing on Oracle9i
9.2.0.3.0 test databases.

We have got the list of tables and their datatypes and have approximate
estimates on the frequency of data inserts, updates and deletions.

We have few formulas (got them from the metalink) on calculating the average row
length of tables and indexes. I'm also using OEM and TOAD for the size
estimations. But results of each of them seems to varry.

So how do we proceed and what would be the best way to estimate the size of
tables and indexes ?

Thanks and Regards

      and we said...
There is one way and one way only to do this.

Load a representative sample of your data -- 10% for small tables, 1% or less
for really big tables.

Analyze them.

Ask the database "so, how big is it".

And then multiply.

All of the formulas are flawed -- they have to be.  They can either go for the
worst case (assume all numbers take 22 bytes, all varchar2s are "big") or they
can go for the best case or somewhere in between.  The result flucuate wildly.

You have to load it up with YOUR data -- measure it and multiply.
Review &
      Followup

      Rating: 5
Thanks and
      Regards  April 25, 2003
Reviewer:
      A reader
Hi Tom,

That solution looks promising. I'll do that.

Thanks and Regards

      Rating: 5
Max table
      size.  April 29, 2003
Reviewer:
      A reader
Hi Tom,

I got the method of finding the average table size (by analyzing). Now since we
do not have the actual data i would like like to know what the maximum size of a
row in a table would be.

Let me make it more clear, since the data is not there, what we are planning is
to find the row size and table size based on your suggestion and the maximum row
length based on the table structure and then take the size based on somewhere in
between the maximum and the average size (more nearer to the average side).

Is there any formula or procedure to calculate the max row length based on the
DB Block Size, KCBH, UB4, KTBBH, KTBIT, KDBH, SB2, UB1, INITVAR, PCTFREE,
INITTRANS, BLOCK HEADER, ROW HEADER.

Thanks and Regards

          Followup:

dates = 7 bytes
numbers = 22 bytes
varchar2(n) = N + 2 bytes
char(n) = N+2 bytes

add em up, multiply by, oh say 110% for good luck....

You don't know the domain values for your data?  good luck with that.

      Rating: 4
I know it
      hurts...  April 29, 2003
Reviewer:
      A reader
Ya i know how much you must have been p***ed off by that question. Doesn't make
sense at all. And we do have the domain data. We can fill up the tables with
dummy data.

But can't help it. Have to submit this so called "decided upon strategy" report
on both maximum row size and average row size to the people above me and hence
will have to support their decision. Any way thanks for the help.

Thanks and Regards

April 29, 2003 Will -- Thanks for the question regarding
      "compare only time part of the date", version 9.2.0.2

      You Asked
 Tom,

  I have a table
  t(fromdate date)

   I want to select rows where time part of the row is
   within a certain range. I don't want to look at the date part.

   For example

  select fromdate  from t where
  to_char(to_date(fromdate,'HH24:MI:SS),'HH24:MI:SS')
  >= to_date('06:10:05'.'HH24:MI:SS')  and
   to_char(to_date(fromdate,'HH24:MI:SS),'HH24:MI:SS')
  <= to_date('08:30:05'.'HH24:MI:SS')  ;

  Can you please suggest a better and efficient way of doing it
  using trunc.

 Regards

 Will

      and we said...
why are you to_date'ing a DATE???

that does an implicit to_char.  you most likely lost the time component doing
that.

anyway, just

where to_number(to_char(fromdate,'hh23miss')) between 061005 and 083005;

done.   You cannot use trunc as trunc can be used to remove seconds,
minutes+seconds, hours+minutes+seconds, days+hours+minutes+seconds and so on --
but not the other way (eg: it works from right to left, not left to right on the
date)

Review &
      Followup

      Rating: 4
Very useful
      tips  April 29, 2003
Reviewer:  Will
      Chakraborty  from Atlanta
Thanks

April 29, 2003 JoAn -- Thanks for the question regarding
      "correct loop usage", version 9i

      You Asked
Hi Tom, Need help!

I have a function where the loop does not seem to go to the next record.

The records I process is similar to:

Table:
Case Extract_date Type   Value   Date
 123 04/25/2003   test1   1000
 123 04/25/2003   test2   3000
 200 04/25/2003   test4          12/25/2001
 A record can only have either a value or a date - not both

create or replace function upd_fn
(P_case_in in varchar2(10),
P_Extract_dt in date)
Return Varchar2 is

Cursor cur_rec Is
select type, value, date
from table
where case = p_case_in
and extract_date = p_extract_dt;

result_rec     cur_rec%ROWTYPE;

IF NOT cur_rec%ISOPEN THEN
 OPEN cur_rec;
 END IF;
   FETCH cur_rec INTO result_rec;
  WHILE cur_rec%FOUND
    LOOP

If result_rec.type = 'test1' and result_rec.value is not null
  then
 update table2...
  else
 insert into table 2
end if;

If result_rec.type = 'test2' and result_rec.value is not null
  then
 update table2...
  else
 insert into table 2
end if;

If result_rec.type = 'test4' and result_rec.date is not null
  then
 update table2...
  else
 insert into table 2
end if;

END LOOP;
CLOSE cur_rec;

EXCEPTION
WHEN NO_DATA...
WHEN OTHERS ..
END;

What happens is that it never goes past the first record.. it only does the
update or insert to: result_rec.type = 'test1'  but not the remaining records...
nothing happened to test2 or test4

What is the correct way to handle this?

Appreciate your quick response!
JoAn

      and we said...
I only see one fetch -- it actually looks like an infinite loop to me.

Proper coding technique for this is simply:

create or replace
function upd_fn(P_case_in in varchar2(10),
                P_Extract_dt in date)  Return Varchar2
is
begin
   for x in ( select type, value, date
                from table
               where case = p_case_in
                 and extract_date = p_extract_dt )
   loop
    ....
   END LOOP;
EXCEPTION
   WHEN NO_DATA...  <=== thats OK, IF You expect no data found to be raised
                         and can recover from it (so I doubt it really)
   WHEN OTHERS ..   <<<<==== THATS A BUG waiting to happening.  I hate when
                             others.  unless that is followed by RAISE, it is
                             a bug.
END;

Actually in looking are your code - it looks like it should be 4 MERGE
statements -- no procedural code AT ALL.  But, I cannot fill in the blanks as
you've given me psuedo code, not the real mccoy.

April 29, 2003 Bala -- Thanks for the question regarding "Commit
      after sequence.nextval", version 8.1.7

      You Asked
Hi Tom,

Thanks for your help to Oracle community.

I see in one of the project, where they do commit everytime they select the
unique value from sequence as below :

select seq.nextval from dual ;
commit ;

I am sure we don't need this commit at all. In fact this will slow down the
database activities. Right ?

I guess the developers have coded this way to ensure the locking mechanism (no
other session should get the same sequence no. which they generated). I am sure
sequence already take care of this mechanism.

What is your suggestion on this.

Thanks

      and we said...
they do not need/want/desire that commit.

that commit can only slow them down.

sequences generate unique sequence numbers across all sessions.

(and even if they did not, committing would not help them do so!)
Review &
      Followup

      Rating:
      5
Thanks  April 29, 2003
Reviewer:  Bala
       from NJ,USA

April 29, 2003 Ronald -- Thanks for the question regarding
      "Multi-level replication", version 8.1.7

      You Asked
I am quite new with Oracle and its replication capability and would like to
understand some basic points. We have two DBs (DB1 and DB2). Currently, DB2 is a
replica of DB1. Later on, we want to add a third database (DB3) to replicate DB2
since we do not have direct access to DB1.

      DB1 --[replicate]--> DB2 --[replicate]--> DB3

Is this possible with Oracle 8i?

Thanks a lot.

      and we said...
with advanced replication, we support either peer to peer:

db1----------db2
  \          /
   \        /
    \      /
     \    /
      \  /
      db3

or hub and spoke:

    db1------------db2
                  /|\
                 / | \
                /  |  \
             db3  db4  db5

so yes.  Oracle streams in Oracle9ir2 and up support even more complex
configurations.
Review & Followup

      Rating: 5
Well
      said  April 29, 2003
Reviewer:
      A reader

April 29, 2003 Paul -- Thanks for the question regarding "Lag
      functionality without using LAG", version 817

      You Asked
Tom,

I've been scouring asktom for the answer to this, but to no avail.  We don't
have Oracle 8 EE, just standard....and I need to emulate the lag function.  It
seems to work, but doesn't have a way of LAGGING behind the first row... so it
starts with the first row that HAS a previous row if you see what I mean:

SELECT current_earned.cpd_policy_reference,
       current_earned.acc_month,
       current_earned.acc_year,
       current_earned.acc_first_day_of_period,
       current_earned.cal_month,
       current_earned.cal_year,
       current_earned.start_of_acc_cal_period,
       current_earned.earned,
           previous_earned.earned
  FROM DW_CUMULATIVE_INW_EARNED_TMP current_earned,
       DW_CUMULATIVE_INW_EARNED_TMP previous_earned
 WHERE current_earned.cpd_policy_reference =
                  previous_earned.cpd_policy_reference
   AND previous_earned.start_of_acc_cal_period = (SELECT
MAX(start_of_acc_cal_period)
                                  FROM   DW_CUMULATIVE_INW_EARNED_TMP
                                  WHERE  cpd_policy_reference =
current_earned.cpd_policy_reference
                                  AND    start_of_acc_cal_period <
current_earned.start_of_acc_cal_period)

I guess I need some kind of 'outer' on that SELECT max so that it just returns
the current earned if there is no previous.

Any ideas ?  This has been bugging me most of the day.

      and we said...
easy -- but not efficient (never will be, that is why we have lag/lead)

analytics are part of SE in 9i -- hope in the future...

we'll use a select of a select (in plsql, you'll need to use a VIEW or dynamic
sql to "hide" this construct -- plsql's parser in 8i won't recognize this
construct)

we'll select the max HIREDATE (formatted to sort nice) || sal from the emp table
for that "partition" (deptno is my partion).  Then we substr off that date and
make it a number again.

QED:

ops$tkyte@ORA817DEV> select deptno, empno, hiredate, sal,
  2            to_number( substr(
  3            (select max( to_char(hiredate,'yyyymmddhh24miss') || sal )
  4                   from emp e2
  5                  where e2.deptno = e1.deptno
  6                    and e2.hiredate < e1.hiredate), 11 ) ) prior_hires_sal,
  7        lag(sal) over (partition by deptno order by hiredate) lag_sal
  8    from emp e1
  9   order by deptno, hiredate
 10  /

    DEPTNO      EMPNO HIREDATE           SAL PRIOR_HIRES_SAL    LAG_SAL
---------- ---------- ----------- ---------- --------------- ----------
        10       7782 09-JUN-1981       2450
        10       7839 17-NOV-1981       5000            2450       2450
        10       7934 23-JAN-1982       1300            5000       5000
        20       7369 17-DEC-1980        800
        20       7566 02-APR-1981       2975             800        800
        20       7902 03-DEC-1981       3000            2975       2975
        20       7788 09-DEC-1982       3000            3000       3000
        20       7876 12-JAN-1983       1100            3000       3000
        30       7499 20-FEB-1981       1600
        30       7521 22-FEB-1981       1250            1600       1600
        30       7698 01-MAY-1981       2850            1250       1250
        30       7844 08-SEP-1981       1500            2850       2850
        30       7654 28-SEP-1981       1250            1500       1500
        30       7900 03-DEC-1981        950            1250       1250

14 rows selected.

Review &
      Followup

      Rating:
      5
Excellent....  April 29, 2003
Reviewer:
      A reader
Tom,

I eventually found a way....don't know if it is better I guess I should try
both:

SELECT current_earned.cpd_policy_reference,
       current_earned.acc_month,
       current_earned.acc_year,
       current_earned.acc_first_day_of_period,
       current_earned.cal_month,
       current_earned.cal_year,
       current_earned.start_of_acc_cal_period,

DECODE(current_earned.start_of_acc_cal_period,previous_earned.start_of_acc_cal_pe
riod,current_earned.earned,       current_earned.earned -
previous_earned.earned)
FROM   DW_CUMULATIVE_INW_EARNED_TMP current_earned, DW_CUMULATIVE_INW_EARNED_TMP
previous_earned
WHERE  current_earned.cpd_policy_reference =
previous_earned.cpd_policy_reference
AND    previous_earned.start_of_acc_cal_period = (SELECT
NVL(MAX(start_of_acc_cal_period),current_earned.start_of_acc_cal_period)
                                  FROM   DW_CUMULATIVE_INW_EARNED_TMP
                                  WHERE  cpd_policy_reference =
current_earned.cpd_policy_reference
                                  AND    start_of_acc_cal_period <
current_earned.start_of_acc_cal_period)

Basically, if it doesn't find a previous, do an NVL on it with the current, so
we'll match current against current.  Then in my select I decode to see if
current=previous date, and ignore it if it is....

Regards,

Paul

          Followup:

select the select.....

benchmark it -- i betcha it'll win (the select of a select) given the
appropriate indexes.


Subject | Article | Last Updated (East coast time)
High Parse Count | article only | Apr 30, 2003 08:04 am
How many times a package is called | article only | Apr 30, 2003 08:29 am
Oracle 8i or 9i? | article only | Apr 30, 2003 08:45 am
Finding Missing Nos. | article only | Apr 30, 2003 09:52 am
Offline drop | article only | Apr 30, 2003 09:57 am
Emailing after inserting into database tables. | article only | Apr 30, 2003 07:30 pm
plsql processes | article only | May 01, 2003 12:00 pm
Changing sqlprompt after connect | article only | May 01, 2003 12:15 pm
SQL*Loader and tab delimited input data | article only | May 01, 2003 12:21 pm
current session settings | article only | May 02, 2003 07:48 am
concurrency issues | article only | May 02, 2003 08:13 am
Analyzing tables and indexes | article only | May 02, 2003 09:27 am
PGA --UGA | article only | May 02, 2003 07:45 pm
Shrinking System | article only | May 03, 2003 12:03 pm
DB_Block_Buffer is zero in init file for 9.2.0. | article only | May 03, 2003 07:36 pm
clustering factor = consistent gets | article only | May 03, 2003 07:49 pm
DML locks serialization. | article only | May 04, 2003 11:00 am
A waste of space | article only | May 04, 2003 07:37 pm
Order By 2,3,29 Vs Order by 2,3,4 | article only | May 05, 2003 06:28 am
Table Difinition | article only | May 05, 2003 07:06 am
truncate vs delete with constraints on tables | article only | May 05, 2003 07:09 am
Sequence | article only | May 05, 2003 07:16 am
Sql order | article only | May 05, 2003 07:44 am
What is SORT(rows) ? | article only | May 05, 2003 07:46 am
IN & function | article only | May 05, 2003 08:06 am
Search space in a string | article only | May 05, 2003 08:17 am
How to use member functions and procedures? | article only | May 05, 2003 11:33 am
sql loader | article only | May 05, 2003 11:34 am
order by fn() | article only | May 05, 2003 01:51 pm
trunc (d,fmt) | article only | May 05, 2003 03:19 pm
randomly selecting 4 employees | article only | May 05, 2003 08:39 pm
Few questions with 'NOT NULL Constraints' and "number" datatype | article only | May 05, 2003 08:43 pm
ARC1: Beginning to archive log# 1 seq# 56947 | article only | May 06, 2003 07:44 am
export using QUERY | article only | May 06, 2003 07:50 am
check constraint | article only | May 06, 2003 08:32 am
Which one is the best index? | article only | May 06, 2003 10:02 am
mts config | article only | May 06, 2003 10:07 am
Tuning based on explain plan cost ??? | article only | May 06, 2003 12:31 pm
Importaince of coding in begin block - reg | article only | May 06, 2003 12:32 pm
SQL*Plus and ATAN2 | article only | May 06, 2003 02:28 pm
Max extents reached | article only | May 06, 2003 02:48 pm
One row to many? | article only | May 06, 2003 04:23 pm
changing size of a tablespace | article only | May 06, 2003 08:57 pm
row(s) 1 - 43 of 43
April 30, 2003 Richard -- Thanks for the question regarding "High Parse Count ", version 8.1.7.4
You Asked
Hi Tom,

Thanks for this great site.

I wrote a stored procedure which uses SQL Type, in the end it
will return it as a ref cursor:

open myrc for
   select *
      from TABLE ( cast ( routeData as routeTableType) );

During a trace, I saw a high parse count for this "open myrc ...".
See below, Why is there such a high parse rate?  I always use bind
variables.  Is this something I should worry about?

Thanks.

From sql trace:
=========================================

SELECT *
FROM
 TABLE(CAST(:b1 AS ROUTETABLETYPE))

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse     5000      2.21       2.18          0          0          0           0
Execute   5000      2.74       2.69          0          0          0           0
Fetch     5000      0.68       0.64          0          0          0        4958
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    15000      5.63       5.51          0          0          0        4958

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 133  (RJI3)   (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  COLLECTION ITERATOR PICKLER FETCH

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   COLLECTION ITERATOR (PICKLER FETCH)
and we said...
refcursors are parsed evertime they are opened.  They have to be -- they cannot
be cached like other statements -- since they are in fact "variables".

Now, that said - we can observe that 4999 of these parses were "soft" parses --
the misses in library cache during parse tells us that.  One hard parse, 4999
soft.

We can make these parses "softer soft parses".  Consider:

ops$tkyte@ORA920> create or replace package demo_pkg
  2  as
  3      type rc is ref cursor;
  4
  5      procedure parse1( p_output in out rc );
  6      procedure parse2( p_output in out rc );
  7  end;
  8  /

Package created.

ops$tkyte@ORA920> create or replace type array as table of number
  2  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package body demo_pkg
  2  as
  3
  4  g_table  array := array(1,2,3);
  5
  6  procedure parse1( p_output in out rc )
  7  as
  8  begin
  9      open p_output for
 10      select * from table( cast( g_table as array ) ) A;
 11  end;
 12
 13  procedure parse2( p_output in out rc )
 14  as
 15  begin
 16      open p_output for
 17      select * from table( cast( g_table as array ) ) B;
 18  end;
 19
 20  end;
 21  /

Package body created.

Now, those are arguably the "same" -- but we'll observe their differences
using runstats
http://asktom.oracle.com/~tkyte/runstats.html

ops$tkyte@ORA920> exec runstats_pkg.rs_start;
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> alter session set session_cached_cursors=100;
Session altered.

ops$tkyte@ORA920> declare
  2      l_cursor demo_pkg.rc;
  3      l_num    number;
  4  begin
  5      for i in 1 .. 1000
  6      loop
  7          demo_pkg.parse1( l_cursor );
  8          fetch l_cursor into l_num;
  9          close l_cursor;
 10      end loop;
 11  end;
 12  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runstats_pkg.rs_middle

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> alter session set session_cached_cursors=0;

Session altered.

ops$tkyte@ORA920> declare
  2      l_cursor demo_pkg.rc;
  3      l_num    number;
  4  begin
  5      for i in 1 .. 1000
  6      loop
  7          demo_pkg.parse2( l_cursor );
  8          fetch l_cursor into l_num;
  9          close l_cursor;
 10      end loop;
 11  end;
 12  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runstats_pkg.rs_stop(500)  /* only show those things
that differ by at least 500 */
Run1 ran in 73 hsecs
Run2 ran in 82 hsecs
run 1 ran in 89.02% of the time

Name                                Run1      Run2      Diff
STAT...session cursor cache hi       997         1      -996
LATCH.library cache pin            4,080     6,080     2,000
LATCH.shared pool                  4,144     6,153     2,009
LATCH.library cache pin alloca     2,046     6,036     3,990
LATCH.library cache                8,167    13,160     4,993

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
45,778    58,643    12,865  78.06%

PL/SQL procedure successfully completed.

as you can see -- it is "marginally" faster on the wall clock (do not expect
it to be many times faster) but the important thing is the reduction in latches.
 We had a softer soft parse (see the 997 session cursor cache hits there --
softer soft parses)

So, make sure your session cached cursors is set (100 is a reasonable number
I've found) and it'll reduce the impact of this.
Review & Followup
Rating: 5
Thanks Tom May 05, 2003
Reviewer: Richard
Well, thanks once again for a great answer.
Your suggestion did help out with some improvement.

April 30, 2003 Arun -- Thanks for the question regarding "How many times a package is called", version 8.1.7
You Asked
Tom
The client wants to find out how many times each package is called in a day to
make decisions on database server capacity.
a) Is there a way to find out how many times a package has been called ?
b) Is there any database server capacity metric associated with how many times a
package is called ?

Someone suggested to sum up the execution counts for each package from v$sqlarea
and divide by the number of days the database has been up. Is this the right way
?

Thanks
and we said...
a) you would need to enable auditing in order to do that accurately.  Using
executes in v$sql (not sqlarea, that is an aggregate of v$sql and slower) would
be a very good second choice.  You would want to "snapshot" v$sql every day at
the beginning of the day so you can subtract day from day to get a picture of
daily use.

b) NO, not even a teeny tiny little bit.  I cannot imagine how I would use this
information to begin "sizing"
Review & Followup
Rating: 5
Crystal clear answer as ever... November 07, 2002
Reviewer: Arun Gupta from Harrisburg, PA USA
 Thanks Tom...Can the information on how many times a package is called, be used
in any manner you can think of, for performance analysis, capacity planning or
anything like that? The only use I am aware of is for finding parse/execute
ratio.

Thanks again.

Followup:
Not really -- I mean -- if I tell you PACKAGE X was executed 1000 times does
that tell you anything?  What conclusion can you draw from that?

You would need lots more details.

Statspack can help you determine capacity planning.
OEM (enterprise manager) and the packs it has can really do that.

How many times you executed package X?  Not sure what I would do with that
particular number.

Rating: 5
One more problem November 09, 2002
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom
I tried to get the execute counts from v$sql over a period of 8 hours. The
difference of some counts is showing as negative. Does this mean that the
packages were aged out and reloaded?
Thanks

Followup:
could be -- yup.

Rating: 5
Statistics November 11, 2002
Reviewer: Mark J. Bobak from Ann Arbor, MI
This reminds me of a quote I read when the writer was
trying to convey the uselessness of these types of
statistics.  It was something like "If I drive home,
and I stop at 500 red lights, what does that tell you?"
(Either the writer was T.Kyte and the original source was
J.Lewis, or vice versa.  I don't remember now.)

Anyhow, the point is, the number of executions, even if
measured accurately, tells you NOTHING about the impact of
those executions on system performance.

PS  I just had a great week here w/ Jonathan.  He came in,
did the keynote at the local OUG and then did his seminar.
Wow, lots of good stuff!

Rating: 5
A follow up question December 10, 2002
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom
If the shared pool items are subject to deallocation, then how does statspack
give me a correct report? If a parsed sql statement is aged out of shared pool,
then it's dynamic performance statistics are also gone (my assumption). Since
the statspack reports are based on the difference between two point in time
snapshots, how does statspack maintain accuracy?
Thanks

Followup:
it won't be 100% accurrate 100% of the time.

It is however more then accurate enough.  Consider -- the TOP SQL is being used
heavily.  Odds are it won't age out.

Consider also that a statspack window should be 15 minutes or so.  Not hours.
So, if an important query is getting aged out in 15 minutes and it materially
affects your report -- it'll show up as a massively slow system with 100% of the
shared pool being used and tons of waits on the library cache!  that is, you'll
have so many other things wrong, that the omission of this information isn't
relevant.  When you fix the obviously wrong things, this will be self
correcting.

Rating: 4
what package does sql belong April 30, 2003
Reviewer: A reader
Hi

I always wondered how do we know which package or procedure or function does the
SQL statments we see in v$sql belong to.... I always have to get the sql and do
something like

select * from dba_source where text like '%SQL_STMT%'

not very efficient really :(

Followup:
sql statements do not "belong" to a package or procedure -- they can be shared
across a plsql package, a vb program and a java client.

fortunately, you should only need to do that infrequently so brute force is
ok...

April 30, 2003 Ronald -- Thanks for the question regarding "Oracle 8i or 9i?", version 8.1.7
You Asked
This is a follow-up to my previous question regarding multi-level replication.

Our client is using 8i on their production and backup environments. We need to
implement a new functionality within their current system which will be needing
a third database (8i or 9i?). However, I heard that 10i is going to be released
soon so I was wondering if 8i is still a wise decision due to support issues.
They are planning to roll this out in production later this year (~September). I
have two questions to ask you since they are both related.

1. When will Oracle stop supporting version 8i?
2. Is replication from 8i db to 9i db supported?

Thanks for your help.
and we said...
1) Oracle8i defect resolution support ends on Dec 31, 2003.  Meaning -- no new
fixes after that.  It'll still be supported -- existing patches will be
available, workarounds to issues supplied (if possible) but no new defect
resolutions will be performed.

2) yes.

My recommendation would be to start aggressively and actively planning your
migration to 9iR2 of your 8i instances, especially since you are still in
development and planning major changes.  The time to do that is before the
product becomes totally obsolete.
April 30, 2003 Riaz -- Thanks for the question regarding "Finding Missing Nos.", version 8.1.7
You Asked
Hello Tom !

I have a problem for you (i am always happy when i create problem(s) for you
since it increase my knowledge).

Consider the following: (I want to find missing shippingbill #s w.r.t
collectorate_code & Month and Year(to_char(shippingbill_date,'mmyyyy'))

cr@STARR.LHR> desc custom_sb_master
Name                    Null?    Type
----------------------- -------- ----------------
COLLECTORATE_CODE       NOT NULL VARCHAR2(4)
SHIPPINGBILL_NO         NOT NULL NUMBER(7)
SHIPPINGBILL_DATE       NOT NULL DATE

COLL SHIPPINGBILL_NO Month
==== =============== =======
KEXP           85070 022003
                          ==> here shippingbill_no 85071 is missing
KEXP           85072 022003
KEXP           85073 022003
KEXP           85074 022003
KEXP           85075 022003
KEXP           85076 032003
KEXP           85077 032003
                    ==> here shippingbill_no 85078 & 85079 are missing
KEXP           85080 022003
KEXP           85081 022003
LEXP            5774 022003
                    ==> here shippingbill_no 5775 is missing
LEXP            5776 022003
LEXP            5777 022003
LEXP            5778 022003
LEXP            5779 022003
LEXP            5780 022003
LEXP            5781 022003
LEXP            5782 022003
LEXP            5783 022003
LEXP            5784 022003
LEXP            5785 022003

So I want my answer look like this:

COLL SHIPPINGBILL_NO Month
==== =============== =======
KEXP           85071 022003
KEXP           85078 032003
KEXP           85079 032003
LEXP           5775  022003

I read your article about finding missing serial #s but couldn't understand
where to put the condition for collectorate & MonthYear wise.

Waiting for your expert advice.

Riaz
and we said...
ops$tkyte@ORA920> select code, sdate,
  2        sno+1 first_missing,
  3        decode( next_sno-1, sno+1, to_number(null), next_sno-1 ) last_missing
  4    from (
  5  select code, trunc(sdate,'mm') sdate, sno,
  6   lag(sno) over (partition by code,trunc(sdate,'mm') order by sno) last_sno,
  7   lead(sno) over (partition by code,trunc(sdate,'mm') order by sno) next_sno
  8    from t
  9         )
 10   where nvl(next_sno,sno+1) <> sno+1
 11  /

CODE SDATE  FIRST_MISSING LAST_MISSING
---- ------ ------------- ------------
KEXP 022003         85071
KEXP 022003         85078        85079
LEXP 022003          5775

ops$tkyte@ORA920>

gets you the necessary information.
Review & Followup
Rating: 5
great April 29, 2003
Reviewer: Riaz Shahid from PRAL, Islamabad, Pakistan
Thats simple wonderful and great.

Riaz

Rating: 5
Another Question April 29, 2003
Reviewer: Riaz Shahid from PRAL, Islamabad, Pakistan
Hello Tom !

Consider:

Note that the PK for this table is collectorate_code+SB_Date+SB_No

cr@STARR.LHR> select * from
(
select code, sdate,
          sno+1 first_missing,
          decode( next_sno-1, sno+1, to_number(null), next_sno-1 ) last_missing
      from (
    select collectorate_code code, shippingbill_date sdate, shippingbill_no sno,
     lag(shippingbill_no) over (partition by
collectorate_code,trunc(shippingbill_date,'mm') order by shippingbill_no)
last_sno,
     lead(shippingbill_no) over (partition by
collectorate_code,trunc(shippingbill_date,'mm') order by shippingbill_no)
next_sno
      from custom_Sb_master
           )
    where nvl(next_sno,sno+1) <> sno+1
)
where first_missing>last_missing
/

CODE SDATE     FIRST_MISSING LAST_MISSING
==== ========= ============= ============
MEXP 08-NOV-01          1864         1862
MEXP 10-NOV-01          1877         1875

Elapsed: 00:19:1174.32

Why first_missing is greate than last_missing. Shouldn't it be <=last_missing
???

Regards

Followup:
something I did not anticipate.  I assumed SNO would be unique -- yours must not
be:

ops$tkyte@ORA920> create table t ( code varchar2(4), sdate date, sno number );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( 'abcd', sysdate, 55 );

1 row created.

ops$tkyte@ORA920> insert into t values ( 'abcd', sysdate, 55 );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select code, sdate,
  2        sno+1 first_missing,
  3        decode( next_sno-1, sno+1, to_number(null), next_sno-1 ) last_missing
  4    from (
  5  select code, trunc(sdate,'mm') sdate, sno,
  6   lag(sno) over (partition by code,trunc(sdate,'mm') order by sno) last_sno,
  7   lead(sno) over (partition by code,trunc(sdate,'mm') order by sno) next_sno
  8    from t
  9         )
 10   where nvl(next_sno,sno+1) <> sno+1
 11  /

CODE SDATE     FIRST_MISSING LAST_MISSING
---- --------- ------------- ------------
abcd 01-APR-03            56           54

that'll happen when you have duplicate SNO's (the fact they are two apart led me
down that path -- SNO+1 NEXT_SNO-1 -- if SNO=NEXT_SNO, you would get the
above...)

You could add:

ops$tkyte@ORA920> select code, sdate,
  2        sno+1 first_missing,
  3        decode( next_sno-1, sno+1, to_number(null), next_sno-1 )
last_missing,
  4            cnt
  5    from (
  6  select code, trunc(sdate,'mm') sdate, sno,
  7   lag(sno) over (partition by code,trunc(sdate,'mm') order by sno )
last_sno,
  8   lead(sno) over (partition by code,trunc(sdate,'mm') order by sno )
next_sno,
  9   count(sno) over (partition by code,trunc(sdate,'mm'), sno ) cnt
 10    from t
 11         )
 12   where nvl(next_sno,sno+1) <> sno+1
 13  /

CODE SDATE  FIRST_MISSING LAST_MISSING        CNT
---- ------ ------------- ------------ ----------
KEXP 022003         85071                       1
KEXP 022003         85078        85079          1
LEXP 022003          5775                       1
abcd 042003            56           54          2

ops$tkyte@ORA920>

to find these DUPLICATES (which are maybe in error?)  or

ops$tkyte@ORA920> select code, sdate,
  2        sno+1 first_missing,
  3        decode( next_sno-1, sno+1, to_number(null), next_sno-1 ) last_missing
  4    from (
  5  select code, trunc(sdate,'mm') sdate, sno,
  6   lag(sno) over (partition by code,trunc(sdate,'mm') order by sno )
last_sno,
  7   lead(sno) over (partition by code,trunc(sdate,'mm') order by sno )
next_sno
  8    from t
  9         )
 10   where nvl(next_sno,sno+1) <> sno+1 and next_sno <> sno
 11  /

CODE SDATE  FIRST_MISSING LAST_MISSING
---- ------ ------------- ------------
KEXP 022003         85071
KEXP 022003         85078        85079
LEXP 022003          5775

ops$tkyte@ORA920>

to ignore them.

Rating: 5
True April 30, 2003
Reviewer: Riaz Shaihd from PRAL, Islamabad, Pakistan
True.....sb# is not unique w.r.t
collectorate_code,trunc(shippingbill_date,'mm').

But its unique w.r.t
collectorate_code,trunc shippingbill_date).

Consider:

cr@STARR.LHR> select
count(*),collectorate_code,trunc(shippingbill_date,'mm'),shippingbill_no
        from custom_sb_master group by
collectorate_code,trunc(shippingbill_date,'mm'),shippingbill_no
       having count(*)>1
    /

  COUNT(*) COLL TRUNC(SHI SHIPPINGBILL_NO
========== ==== ========= ===============
         2 MEXP 01-NOV-01            1863
         2 MEXP 01-NOV-01            1876

cr@STARR.LHR> select
count(*),collectorate_code,trunc(shippingbill_date),shippingbill_no
        from custom_sb_master group by
collectorate_code,trunc(shippingbill_date),shippingbill_no
       having count(*)>1

no rows selected

So what should i do in order to remove this anomoly. I want missing Sno w.r.t
collectorate_code,trunc(shippingbill_date,'mm')
BUT
Sbno is unique w.r.t
collectorate_code,trunc(shippingbill_date)
AND
i don't want to ignore those sbno (as u mentioned in previous post).

Please Advise

Riaz

Followup:

You tell ME what you want to do with this anomoly.....

Rating: 5
April 30, 2003
Reviewer: Riaz Shahid from PRAL, Islamabad, Pakistan
Hello Tom !

Thanks for your quick response.

Actually i am rececving this data from different sources. I Want to send them
the missing snos so that they can send me the data for those nos. They can't
send me the WHOLE data since its HUGE one.

Thankx

Riaz

Followup:
then -- ignore these duplicates??? why not?  they do not represent a gap -- so
trash them

April 30, 2003 Shivaswamy -- Thanks for the question regarding "Offline drop", version 817
You Asked
Hi Tom.

Test database on No Archivelog.(I know any one hates No-arch. But life is like
that..)

Media failure. Datafile not accessible. The datafile contains one of the many
partitions of a table.(and nothing else). If I 'offline drop' the datafile, I
can open the database. BUT, I can not drop the tablespace including contents,
(ORA-14404). And I can not drop the partition because I have already offline
dropped the datafile. As long as I do not need to use the same tablespace
name/partition name, it is OK. But, if I have to use the same tablespace
name(and partition name) how can I cirumvent this?

TIA,
Shivaswamy

TOM,

Sorry TWICE I preseed "update question" button twice and for the thitf time, I
entered the update. However, I am updating again.

1. I had ORA 110, 1157, 27031 errors and database down.
2. I dropped offline the datafile concerned and opened the database.
3. I gave drop table space including contents, I had ORA 14404.

Now after your reply, the spoolfile is thus:
============
10:09:07 SQL> ALTER TABLE DATAGYM.UIN_MASK_DETAILS_GOOD DROP PARTITION
UIN_MASK_DETAILS_GOOD_57 INCLUDING CONTENTS.
10:09:07   2  /
ALTER TABLE DATAGYM.UIN_MASK_DETAILS_GOOD DROP PARTITION
UIN_MASK_DETAILS_GOOD_57 INCLUDING CONTENTS.

 *
ERROR at line 1:
ORA-14048: a partition maintenance operation may not be combined with other
operations

10:09:07 SQL>
10:09:07 SQL> ALTER TABLE DATAGYM.UIN_MASK_DETAILS_GOOD DROP PARTITION
UIN_MASK_DETAILS_GOOD_57
10:09:07   2  /
ALTER TABLE DATAGYM.UIN_MASK_DETAILS_GOOD DROP PARTITION
UIN_MASK_DETAILS_GOOD_57
*
ERROR at line 1:
ORA-00376: file 301 cannot be read at this time
ORA-01110: data file 301:
'/u07/oracle/oradata/datagym/uin_mask_details_good_57.dbf'

10:09:07 SQL>

===========
Thanks in advance,
Shivaswamy
and we said...
Ok, that means you have some locally partitioned indexes.  Your choices:

1) drop locally partitioned indexed or
2) better: create a dummy table that can be exchanged with this broken
partition.

Here is a fullup example.  I'll offline drop a datafile and recover from it

ops$tkyte@ORA817DEV.US.ORACLE.COM> create tablespace test1
  2  datafile '/tmp/test1.dbf'
  3  size 512k reuse;

Tablespace created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create tablespace test2
  2  datafile '/tmp/test2.dbf'
  3  size 512k reuse;

Tablespace created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE TABLE t
  2  ( x int,
  3    y date,
  4    z varchar2(25),
  5    constraint t_pk PRIMARY KEY(x) using index local
  6  )
  7  PARTITION BY RANGE (x) (
  8    PARTITION t_p1 VALUES LESS THAN (5) tablespace test1,
  9    PARTITION t_p2 VALUES LESS THAN (10) tablespace test2
 10  );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1, sysdate, 'hello' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 7, sysdate, 'bye' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

         X Y         Z
---------- --------- -------------------------
         1 10-JAN-02 hello
         7 10-JAN-02 bye

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select table_name, partition_name,
tablespace_name
  2  from user_tab_partitions where table_name = 'T'
  3  /

TABLE_NAME                     PARTITION_NAME                 TABLESPACE_NAME
------------------------------ ------------------------------
------------------------------
T                              T_P2                           TEST2
T                              T_P1                           TEST1

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select index_name, partition_name,
tablespace_name
  2  from user_ind_partitions where index_name = 'T_PK'
  3  /

INDEX_NAME                     PARTITION_NAME                 TABLESPACE_NAME
------------------------------ ------------------------------
------------------------------
T_PK                           T_P1                           TEST1
T_PK                           T_P2                           TEST2

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter database datafile '/tmp/test2.dbf'
offline drop;

Database altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;
ERROR:
ORA-00376: file 19 cannot be read at this time
ORA-01110: data file 19: '/tmp/test2.dbf'

no rows selected

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t drop partition t_p2;
alter table t drop partition t_p2
*
ERROR at line 1:
ORA-00376: file 19 cannot be read at this time
ORA-01110: data file 19: '/tmp/test2.dbf'

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table dummy ( x int, y date, z
varchar2(25), constraint dummy_pk primary key(x) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t exchange partition t_p2 with
table dummy;

Table altered.

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t drop partition t_p2;

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

         X Y         Z
---------- --------- -------------------------
         1 10-JAN-02 hello

ops$tkyte@ORA817DEV.US.ORACLE.COM>
Review & Followup
Rating: 5
January 10, 2002
Reviewer: Shivaswamy from Dallas, TX, USA
Thanks, so much, TOM.
Perfect explanation!

Rating: 4
How about this..? May 08, 2002
Reviewer: Shivaswamy from Dallas, TX
Tom,

Again, I have very close problem..

Database had crashed.(ORA-1157, 1110,27037). Have a partitioned table with local
index. SInce database had problem accessing a datafile of a partition, I off
line dropped it. Mounted the database. But can not open it.(1157, 1110).
Database in NO ARCHIVELOG. How do I open it?(data loss no problem.)
Shivaswamy

Followup:
why why why

why did you offline drop it.

Anyway, if you offine dropped it -- I don't know why or how you would be getting
the

01157, 00000, "cannot identify/lock data file %s - see DBWR trace file"

error.  Insufficient data.  In any case, contact support -- open a tar.  They
are really good at getting downed databases up...

Rating: 5
May 08, 2002
Reviewer: Shivaswamy
Thanks Tom, Very much for the valued opinion. This was a test database and
proper controls to datafiles was lacking.. and there was a screw up. Thanks,
once again.

Rating: 4
Why can not? April 30, 2003
Reviewer: Cefers.br from Brasil
Tom,

In NOARCHIVELOG database, why can´t we OFFLINE a datafile in normal way (I mean,
without DROP)?

I know that there´s no checkpoint when the datafile goes offline, but what does
it have to do with de redo logs generated between the offline period?

In my understands, when in offline mode, there´s no action occuring on the
datafile, so there´s no information saved on redo logs...

Thanks.

Followup:
since there is no checkpoint -- that means there may be blocks in the buffer
cache that belong to that datafile but haven't been written yet.

so, tomorrow when you bring it back online -- and those blocks aren't in the
file -- we need to recreate them, that involves applying redo -- ARCHIVED redo
probably at that point in time.

It is the activity that took place BEFORE the offline that is of concern, not
after the offline.

You want to take it offline -- take the tablespace offline.

Rating: 5
Of course.. April 30, 2003
Reviewer: Cefers.br from Brasil
It seems so obvious...before your explanation.

Thanks.

April 30, 2003 Brett -- Thanks for the question regarding "Emailing after inserting into database tables.", version 8.1.7.3
You Asked
Tom,

I have a requirement to send an email of an order and order details
(master-detail) after an order is inserted into the database.  Can this be done
easily?  If so, how?  I know that I can email the contents of one table, but am
not sure if I can get the order detail or line items in an email also.

Thanks,

Brett
and we said...
You would use UTL_SMTP in order to send an mail (easy -- search this site for
utl_smpt).

I would recommend writing a stored procedure that takes a NUMBER as input.  It
would use this NUMBER to query a record that contains the necessary inputs to
generate and send the email.  So, you would create a table:

create table email_parameters
( id number primary key,
  <any data you needed to send email here>
)
/

And then put a trigger on the orders table:

create trigger send_email
after insert on orders for each row
declare
   l_job number;
begin
   dbms_job.submit( l_job, 'SEND_EMAIL( JOB );' );
   insert into email_parameters values ( l_job, :new.whatever-you-need );
end;
/

and then a procedure send_email( p_job in number )
is
   l_rec email_parameters%rowtype;
begin
   select * into l_rec from email_parameters where id = p_job;

   ... format an send email using utl_smtp;

   delete from email_parameters where id = p_job;
end;
/

That'll make it so that your email gets sent right after you commit (it is
transactional -- if you rollback the insert into orders -- no email gets sent,
if you commit, email gets sent)

remember to set job_queue_processes and job_queue_interval on the server!
Review & Followup
Rating: 5
learned sth great January 12, 2003
Reviewer: A reader

Rating: 5
Very good solution! January 12, 2003
Reviewer: Brett Gaston from Ft. Lauderdale, FL
That is the framework for what I am trying to do.  I think it will be easy for
me to make it work in my application now.  Thanks a lot.

Rating: 5
January 14, 2003
Reviewer: Mikhail Veramchuk from Poland
Hi, Thom!
I repeat your code in my 9.2 database, but my job doesn't started. In trace file
I have

*** SESSION ID:(15.3641) 2003-01-14 09:33:39.000
*** 2003-01-14 09:33:39.000
ORA-12012: error on auto execute of job 121
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at line 10

But dbms_job.RUN(121) is executed successfully ...

Thanks

Followup:
ok, so read line 10 of your code and figure out why you getting more than one
row!

you see - my example has no line 10.  you have a bug in your code -- a select
into that returns more than one row.

Rating: 5
January 14, 2003
Reviewer: Mikhail Veramchuk from Poland
create table email_parameters
(  id       number      primary key,
  address  varchar2(100) );

create or replace procedure send_email(p_job in number)
is
 l_rec email_parameters%rowtype;
 begin
  select * into l_rec from email_parameters where id = p_job;
  delete from email_parameters where id = p_job;
 end;
/

create or replace trigger send_email_trg
   after insert on employees
   for each row
declare
   l_job   number;
begin
   dbms_job.submit (job  => l_job,
                    what  => 'send_email(job);' );
   insert into email_parameters
        values (l_job, 'tru-la-la-la-la-la');
end send_email_trg;
/

Table email_parameters has only one record:

10 tru-la-la-la-la-la

In trace file:
*** SESSION ID:(30.207) 2003-01-14 14:26:34.000
*** 2003-01-14 14:26:34.000
ORA-12012: error on auto execute of job 10
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at line 10

But dbms_job.run(10) run and remove this job from queue

Followup:
I cannot reproduce that -- but -- do this, put an exception block in there --
catch the too_many_rows exception and use raise_application error to prove it is
where we think it might be

create or replace procedure send_email(p_job in number)
is
 l_rec email_parameters%rowtype;
 l_cnt number;
begin
   begin
      select * into l_rec from email_parameters where id = p_job;
   exception
      when too_many_rows then
          select count(*) into l_cnt from email_parameters where id= p_jod;
          raise_application_error( -20001, 'Too many rows, id = ' || p_job
                     || ' count = ' || l_cnt );
   end;
   delete from email_parameters where id = p_job;
 end;
/

Rating: 5
January 14, 2003
Reviewer: Mikhail Veramchuk from Poland
Thanks, Thom!
It seems, I have some problems with my 9.2.0.2 server.
Above code works fine on 8.1.7.
But I haven't any idea how to solve this problem.
It seems below message points to line 10 beyond my code

*** SESSION ID:(78.62) 2003-01-14 16:21:13.000
*** 2003-01-14 16:21:13.000
ORA-12012: error on auto execute of job 11
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at line 10

Followup:
alter system set sql_trace=true scope=spfile;
shutdown;
startup;

let it run and fail, as soon as it does

alter system set sql_trace=false scope=spfile;
shutdown;
startup;

and grep the trc files in udump/bdump for

$ grep 'err=1422' *.trc

edit the resulting trace file, search for err=1422, it'll look like this:

ERROR #3:err=1422 tim=1176756592

search backwards for (in this case, since it was #3)

PARSING IN CURSOR #3

that'll be the block of code that is causing the issue.  that might shed some
light on it.

i could not reproduce on 9202

Rating: 5
January 15, 2003
Reviewer: Mikhail Veramchuk from Poland
Thanx for your time and explanation. I found error in my logon trigger.

Rating: 5
email April 24, 2003
Reviewer: mo
Tom:

1.  If I have an after insert trigger that sends an email using mail_pkg.send
would not send the email before I commit? (not using the job scheduler).

2. If I want to have p_body be a table created by a sql query, would you assign
the query results to a string variable and pass it in as a parameter? Can you
control the formatting of the table here?

3. Is there a size limit on the P_body?

Thank you,

Followup:
1) not really a question?  unless you use dbms_job to send in the background --
the email goes right away.  DEFINITELY not recommended at all.

2) er?  are you asking how to send HTML?  as written, this sends ascii text,
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1739411218448
shows howto HTML

3) 32k as implemented, change implementation to be a clob or a blob and 4gig
would be the theoretical limit.

Rating: 5
email April 30, 2003
Reviewer: mo
Tom:

1.  I have email_parameters table with a colum email_type (long).  When I insert
into the table, I have a variable v_data varchar2(1000).

v_data:=chr(10)||'The subject Request created on
'||to_char(v_request_date,'mm/dd/yyyy')||' has been shipped.'
||chr(10)||chr(10)||'To view the the Shipment Packing List please click the link
below.'||chr(10)||chr(10);

        v_data:=v_data||'
http://xxx.xxx.xxx:yyyy/pls/devd/print_packing_list?
shipment_id='||:new.shipment_id||'&i_user_id='||:new.user_id;

When I get the email in MS Outlook it looks fine. However when the email gets
received at Novell groupWise client it cuts off the hyperlink at the 72th
character and the remaining text is show as regular text. Thus when you click
the link it gives a blank page.

Do you know whether this may be caused by the database and if there is a way to
force the whole text after "
http:///
as a hyperlink?

Thank you,

Followup:
it is not the database.

It is your email client.

The only solution -- use a smaller URL -- each client has a differnent limit.
It is like the email I send to you guys when I ask for more info -- I put right
in it this text:

Please add additional information to your question using the link:
http ://asktom.oracle.com/pls/ask/f?p=4950:15:::NO::F4950_P15_ID:nnnnnnnnn

It is important that you use the above URL (watch out for word wrapping by
your email client! That URL works).  We get many emails and the only way
we can keep track of things is to have the questions in the database.  If
you do not use the link but instead email us directly we cannot promise
you will receive an answer.

Rating: 5
email April 30, 2003
Reviewer: mo
Tom:

1.  How can I make my URL smaller if the procedure (for example) requires two
parameters?  It will not work otherwise.

2.  I tried a test where I sent the same email to a yahoo account and it showed
up fine.  Then I forwarded the yahoo email to a groupwise email and it showed up
fine.  As a second test, I copied the link and pasted it in groupwise and sent
it to another groupwise account and it showed up fine. Does that indicate it may
not be the client?

3.  Can these things be fixed in the email clients (configuration) or it can
not?

Thank you,

Followup:
look at how short we made the "url" to dbms_job -- all that is passed is "job"

you could do the same.  create a table, stuff the big parameters in there, give
them a sequence number and send a url like

http ://host/pls/x/y?p=12345456

y is your procedure.

as for the email stuff -- time to dig out a book or two or three and look
around.  I'm not an email expert (i keep saying "courier 8 is the only font I
need" don't i).  I got the email into your inbox -- you need to take it from
there.

Rating: 5
email April 30, 2003
Reviewer: mo
Tom:

To make sure  I understand what you are saying:

1.  I made a duplicate procedure (pl) instead of the long name that i had.

2.  My email_parameters table already has a unique identifier "ID" which is the
job id.  Do I still need to create a sequence number and store it there too.
Then I should create another procedure that only receives that sequence as an
input parameter and then pull out all other required data from the
email_parameters table?If this is correct can't I use the job ID instead.

Thank you,

Followup:
2) design decision.  something you can handle I'm sure.  use whatever -- you
have the "concept" -- you need nothing more then the concept to develop YOUR
implementation.

Rating: 4
pop up April 30, 2003
Reviewer: juancarlosreyesp@yahoo.com from Bolivia
Hi Tom is possible in some way that oracle read messages from email server?,
maybe using java,

thanks

Followup:
sure, learn the javamail API, load it into the database and read away.

Rating: 4
pop3 April 30, 2003
Reviewer: A reader
Tom,
I must say I read the article
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:741825581643
But I am asking a 9i specific answer please.

Thanks :)

Followup:
c/8\.1\.5/9i/g

if you know vi or sed, that'll make sense ;)

Rating: 5
May 01, 2003
Reviewer: A reader
Thanks Tom :)

May 01, 2003 -- Thanks for the question regarding "plsql processes", version version 7.3
You Asked
   How can I launch a pl/sql process in background mode in order
to launch more processes but avoiding a block out in the machine
(some like the nohup Unix command)?
   The Oracle engine is mounted over Solaris 7.3.4 and I want to
launch the processes in a host with Windows 95 by programming
the call for the procecess in the Developer 2000's
WHEN_BUTTON_PRESSED trigger.
and we said...
Well, if you see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:689424854669
I show how to start a process in "the background" -- making HOST
nonblocking.  I know it works on NT, not 100% sure about windows
95 though.  If that works for you, you can put the PLSQL block
to execute in a file (using text_io for example) and then run
SQLPlus via the host command.

Another option is to create a form that has this plsql block in
it (or to pass this plsql block to it as a parameter) and use
the RUN_PRODUCT builtin to run this form module asyncronously.

Another viable method is to use DBMS_JOB.  This will schedule
the PLSQL to run asyncronously on the SERVER (not on the client)
in the background soon after you commit.  See the docs for info
on dbms_job (you need to have 2 init.ora parameters setup for
this to work correctly).
Review & Followup
Rating: 4
nohup like May 01, 2003
Reviewer: A reader
Hi

Is there anyway to run procedures in sqlplus prompt without blocking the
host...? DBMS_JOB is the only way?

Followup:
you need another session -- so yes, dbms_job would be one way.

host sqlplus scott/tiger   @script &

would be another (on unix)

May 01, 2003 Jeff -- Thanks for the question regarding "Changing sqlprompt after connect", version 8.1.5.0
You Asked
I have a login.sql script that changes my sqlprompt whenever I
start sqlplus.  This script executes fine when I first go into
sqlplus, but if I connect to another database while already in
sqlplus, my prompt doesn't change.  (I know, all I have to do is
type @login again, but I'm a lazy DBA).

Is there any way to get this script (or something like it) to
execute automatically whenever I connect to a database?

jeffh@dev42$ cat login.sql
set termout off
col user_name new_value user_name
col db_name new_value db_name
select lower(user) user_name,
   ltrim(rtrim(lower(global_name))) db_name
from global_name;
set sqlprompt "&user_name@&db_name> "
set termout on
set trimspool on
set trimout on
set serveroutput on

jeffh@dev42$ sqlplus system@ibprod1.us
SQL*Plus: Release 8.1.5.0.0 - Production on Thu Oct 19 17:49:01
2000
(c) Copyright 1999 Oracle Corporation.  All rights reserved.
Enter password:

Connected to:

Oracle8i Enterprise Edition Release 8.1.5.0.0 - Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.0.0 - Production

system@thiprod.world> connect sys@ibprod1.us
Connected.

system@thiprod.world>
and we said...
Nope, I use a script  @connect myself that looks like:

connect &1
@login

It is then a matter of teaching yourself to

SQL> @connect scott/tiger

instead of

SQL> connect scott/tiger
Review & Followup
Rating: 3
Elegant workaround October 31, 2001
Reviewer: sanjay raj from USA
Thanks. I have been using the same technique for the last few years and always
thought that there was a better solution to this. But there U have it - IT WORKS
:)

Rating: 5
Brilliant, thanks November 01, 2001
Reviewer: A reader from Switzerland

Rating: 5
When I issue connect.. May 01, 2003
Reviewer: Chandra S.Reddy from India
Hi Tom,
This is a great solution.
for first time login it is working fine.
When I issue "connect" command from the existing SQL>, then it not changing to
new prompt.
As you said,if I create a script with the name @connect, it will not be invoked
if we are in some other directory.
So we need to copy the connect.sql file into current working directory.?
Or is there any other solution available for this.
Because, current working directory will be the project related directory and we
should not have any junk files in it.

Followup:
set your SQLPATH to a directory (environment variable)....

put all of your scripts in that directory.

now you can run them from anywhere.

Rating: 3
Thanks May 01, 2003
Reviewer: SS from Houston TX
Thanks Tom... I had the same problem.

May 01, 2003 Steve -- Thanks for the question regarding "SQL*Loader and tab delimited input data", version 8.1.5
You Asked
I have an input file that contains data that is tab delimited
containing info like firstname, lastname, city, state, zip.  The
fields are not enclosed in quotes or anything else.  The problem
arises when data for a field (like lastname) is missing.  The
appropriate tab delimiter is in the file, but SQL*Loader seems
to want to lump this all into one piece of whitespace and
therefore thinks it is missing some data.  Is there anyway to
specify a tab delimiter to SQL*Loader without it thinking it
should check for whitespace instead?
and we said...
If you use

LOAD DATA
INFILE *
INTO TABLE DEPT
replace
FIELDS TERMINATED BY X'9'
(DEPTNO, DNAME, LOC)
BEGINDATA
12      SARATOGA
10  ACCOUNTING  CLEVELAND
11  ART SALEM
13  FINANCE BOSTON
21  SALES   PHILA.
22  SALES   ROCHESTER
42  INT'L   SAN FRAN

It'll work (note: there are two tabs between 12 and SARATOGA in
the above -- it loads 12 into deptno, SARATOGA into LOC)
Review & Followup
Rating: 5
Very helpful.....was really great October 20, 2001
Reviewer: Riaz Shahid from Islamabad,Pakistan
Thats gre8

Rating: 5
August 13, 2002
Reviewer: laLocA from Austria
Thank you very much - I really missed this information about such a common
problem in the official reference.

Rating: 4
Works great, less filling December 18, 2002
Reviewer: Dave Archbell from La Jolla, CA USA
We used to put the control characters in the file.
This method works great.  Can you point to the
complete list of codes for character substitution?

Followup:
they are just ascii codes?

like carriage return = 0D (decimal 13), line feed = 0A (decimal 10) and so on...

Rating: 4
December 18, 2002
Reviewer: Sagi from India
Hi Tom,

As usual excellent.

In your solution what is ??
           X'9'

Thanx in advance.

Regards,
Sagi

Followup:
tab

Rating: 5
error December 19, 2002
Reviewer: ashraf from kuwait
hi tom when i go to search in your sit many times i get

ORA-06502: PL/SQL: numeric or value error: character string buffer too small

Followup:
so, would you like to share an example with me of a search string that causes
this because I myself search this site hundreds of times (because people asking
questions refuse to themselves for some reason) and I've never hit that.

Rating: 5
error December 19, 2002
Reviewer: A reader
this happen when i click on search link before i submit any query and i always
search in your site without any proplem but in the last week this happen round
7-10 times

Followup:
I cannot reproduce -- do you have exact steps?  is it still happening?

Rating: 5
Answer to Sagi from India December 19, 2002
Reviewer: Leonid Birman from Atlanta, GA USA
The X'9' is Hexadecimal Format -> X
and Hex 9 is the ASCII code of the TAB character.

Rating: 3
trim trailing blank space and other March 26, 2003
Reviewer: Vikas Sharma from Delhi India
Hi Tom,

I would like to load the data into a table using sql loader. My data file looks
likes this:

|br_cod|br_name                         |group_|open_dte |
|001001|A.D.A.S East                    |001   |11/11/98 |
|001004|A.D.A.S National                |001   |11/11/98 |
|001007|A.D.A.S South West              |001   |11/11/98 |

My problem is
1. I would like to remove trailing white space from all the columns. I have use
rtrim like
LOAD DATA
 INFILE branches.txt
 INTO TABLE v
 FIELDS TERMINATED BY '|' OPTIONALLY ENCLOSED BY '"'
 TRAILING NULLCOLS
 ( br_cod    CHAR(4000)   "RTRIM(:br_cod)"
,br_name     CHAR(4000)   "RTRIM(:br_name)"
,group_      CHAR(4000)   "RTRIM(:group_)"
,open_dte    CHAR(4000)   "RTRIM(:open_dte )"
)

Or there is some other way also.

2. When i insert data into the table the first column gets null values, second
col get the values of br_cod third get values of second col br_name...

This is because of the '|' before the first column. HOw can i solve this
problem. The one way i think is using filler for column one. Will it work fine.
Or kindly suggest me some way of doing it.

Thanks,

Regards,
Vikas sahrma

Followup:
1) If you used POSITION instead of delimited, it would trim trailing blanks.

2) using POSITION would solve that as well, or just add:

( X filler
,br_cod    CHAR(4000)   "RTRIM(:br_cod)"
,br_name

Rating: 4
Thanks March 26, 2003
Reviewer: Vikas Sharma from Delhi India
Hi Tom,

Thanks For the immediate reply this solves my problem.

Regards,

Vikas Sharma

Rating: 5
About the numeric or value error in the search field. March 27, 2003
Reviewer: Martin from UK
Hi Tom,

I've noticed a little "feature" with the search field as well every now and
then, but it's only if you supply a HUGE number of characters into the search
field, and you get the following error :

ERR-1004 Unable to Compute Function computation_point=BEFORE_HEADER
computation_type=FUNCTION_BODY
 Back

But, then again, as IF you'd ever hit this because we're talking about approx
2000 characters! ;-)

ORA-06502: PL/SQL: numeric or value error: character string buffer too small

Followup:
but it does catch it quite nicely doesn't it ;)

Rating: 3
Mark several Rows in Oracle Forms March 27, 2003
Reviewer: Mary
Hi Tom,

I would like to mark several rows in data block using Shift+ mouse click in
Oracle Form. How can I do this?
Please, help me!

Followup:
that is a list box feature -- it is not a feature of a default block.  Sorry.

Rating: 3
What about Null characters May 01, 2003
Reviewer: Chetan Kashikar from Auckland,New Zealand
Can I use X'0' for null delimitters?

Followup:
SQL*Loader-350: Syntax error at line 5.
Illegal hexadecimal literal specified for delimiter - not valid characters
FIELDS TERMINATED BY X'0'

doesn't appear so -- i didn't try "really hard" tho.  x'0' would be a strange
delimiter.

May 02, 2003 -- Thanks for the question regarding "current session settings", version 8.1.7
You Asked
Hi Tom,

I have the following question: I logon to the database by opening a new session.
 In the new session, I alter the sort area size and set the current schema to
someone else.  Then I connect to another user name in the current session.
After I switch to another, do I still keep the new settings (sort area size,
current schema)?  Also is there any general way to tell the current settings
(e.g. sort area size, current schema, sql_trace ...) for a session?

Thank you for your help.

-Denny
and we said...
When you connect -- everything goes back to the "default", you have a brand new
session and any and all settings you set are gone.

as for getting session settings -- it is somewhat "hit or miss".  Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select value from v$parameter where name =
'sort_area_size';

VALUE
--------------------------------------------------
1024000

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set sort_area_size = 60000;
Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select value from v$parameter where name =
'sort_area_size';

VALUE
--------------------------------------------------
60000

but
ops$tkyte@ORA817DEV.US.ORACLE.COM> select value from v$parameter where
  2  name = 'sql_trace';

VALUE
--------------------------------------------------
FALSE

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set sql_trace=TRUE;
Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select value from v$parameter where name =
'sql_trace';

VALUE
--------------------------------------------------
FALSE

but, if you knew that sql_trace is really just event 10046 - you could have:

ops$tkyte@ORA817DEV.US.ORACLE.COM> variable x number
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec sys.dbms_system.read_ev( 10046, :x );

PL/SQL procedure successfully completed.

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

         X
----------
         1

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set sql_trace=false;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec sys.dbms_system.read_ev( 10046, :x );

PL/SQL procedure successfully completed.

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

         X
----------
         0

which tells you (events are not in v$parameter....)

For current schema, it could be:
ops$tkyte@ORA817DEV.US.ORACLE.COM> select sys_context( 'userenv',
'current_schema' ) from dual;

SYS_CONTEXT('USERENV','CURRENT_SCHEMA')
---------------------------------------------------------------------------------
--------------------------------------------------
OPS$TKYTE

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set current_schema=scott;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select sys_context( 'userenv',
'current_schema' ) from dual;

SYS_CONTEXT('USERENV','CURRENT_SCHEMA')
---------------------------------------------------------------------------------
--------------------------------------------------
SCOTT
Review & Followup
Rating: 5
Thank you. December 11, 2002
Reviewer: Denny from NY, NY USA
Where I can find more information about sys_context and the event number? I
cannot find it in Oracle documentation.

Followup:
sys_context = sql reference, chapter on "functions"

the event stuff -- nowhere, they are not documented.  10046 is one of the "well
known ones", sql_trace....

Rating: 4
Alter Session set current_schema May 01, 2003
Reviewer: Robert from PA
Tom,
Why is it that "Alter Session Set Current_schema = ..."
does not work when "desc" ?
What other cases does it not work ?

Thanks

Followup:
it normally works...

ops$tkyte@ORA920> desc emp
ERROR:
ORA-04043: object emp does not exist

ops$tkyte@ORA920> alter session set current_schema=scott;

Session altered.

ops$tkyte@ORA920> set linesize 60
ops$tkyte@ORA920> desc emp
 Name                          Null?    Type
 ----------------------------- -------- --------------------
 EMPNO                         NOT NULL NUMBER(4)
 ENAME                                  VARCHAR2(10)
 JOB                                    VARCHAR2(9)
 MGR                                    NUMBER(4)
 HIREDATE                               DATE
 SAL                                    NUMBER(7,2)
 COMM                                   NUMBER(7,2)
 DEPTNO                                 NUMBER(2)

May 02, 2003 Bill -- Thanks for the question regarding "concurrency issues", version 8.1.7
You Asked
Hi Tom,
I guess this is more of an 'opinion' or 'advise' question.  I would like to know
your thoughts because I think i am going crazy.  I am working with a database
application that is having problems with concurrency and locking, in which the
application is throwing back errors that data has changed between commits etc
etc.
Here what i am dealing with:
We have a change management application.  The application "versions" objects and
stores them in the database.  The application was designed to have a "middle
tier server" that run jobs either manually or scheduled.  Never mind that they
think they need the middle tier to handle the "load" because the SQL does not
use bind variables and can commit 1 row at a time if a user so deems it. That is
a whole other issue.  These jobs on the middle-tier server handle the processing
of the "versioning/archiving".  So when user1 selects and "locks" objects to
version/archive, the middle tier reads a job from a table then kicks in ( as
user 2 ) and handles the transaction.
So what i found that happens is the following: User1 defines an object type (
ex: a .sh shell script ). User1 then selects those .sh files to version/archive
and waits for the middle tier server ( User2 to kick off the job ).  Well in
between the middle tier kicking off the job, the object type ( the .sh config )
has been changed by User3 to (.shs ).  User3 commits the change.  User1's job
now fails because the middle-tier (User2) cannot find that object type defined
in the database.

I tried to explain that it is a concurrency and transaction handling issue, but
I got so much push back from the architect and developer that i just dropped the
issue.  They contend that the configuration data will never change and that it
is impossible for the middle tier to know what user1s state is.  I told them the
transactions need to be handled properly.  They further contend that Oracle
Apps, Siebel and SAP all work this way....especially with these "jobs" running
as separate users.
Not sure what to do, as DBA, once again, its not the technology - its the
personalites...
I do have your book and when i read chapter one it was amazing because it
applies exactly to my situation.
Not sure how you can help me, other than confirming my thoughts regarding the
issue.
Seems that when i try to "educate" the developers/architect i just look like i'm
causing trouble - criticizing and undermining the design.
Best Regards
Bill
and we said...
You have a mess -- an utter mess -- on your hands.

"shell" scripts?  really, that is scary.

You are building a database application.  Ask the "designer" or "architect"
(tell me, did the guy get a piece of paper that says 'you passed such and such a
degree to be an architect?'???) how many large scale database applications they
wrote.

Let them borrow the book -- have them read just the beginning of it - how
transactions work, bind variables, what the database does, HOW the database does
it.

Oracle apps -- do not work like this.
Good apps -- do not work anything like this.

Basically, you have a mess, not sure what to tell you.  Unfortunately, they are
building a system that will lock the data up tighter then anything in the world.
 There  will be no adding onto this system in the years to come, there is no
ad-hoc queries here.

I feel for you -- other then letting them fail, which it sounds like they are
well on the way towards -- I don't know what to say.  I'd make them maintain
this system for at least the next 5 years as punishment -- don't let them move
onto the next system, make them live with the system they built for a while --
enhance it, live with it, support it.
Review & Followup
Rating: 5
a mess is right October 15, 2002
Reviewer: Bill
My feelings exactly.  Time will tell.
Thanks Tom - i learn more every from your site. Best site on the web.

Rating: 2
May 02, 2003
Reviewer: A reader
hello tom,

we have several long running java client processes extracting data from the
database and writing their results to CLOBs (one table entry per process).

what we want to do first is to isolate read and write issues from each other. we
are responsible for the extraction routines (PL/SQL and SQL - the possibly "low
hanging fruits"), but want to know, whether or not the concurrent transactions
against that CLOB-column cause the processes to be slow. how could we start
figuring out if THEY are the reason?

are there any database parameters and/or table properties, that could/have to be
checked first - before "instrumenting" the code (which unfortunately has not
been done so far as well)? what would your approach in such a scenario be?
where/how would you start (and go further on)?

Followup:
the code isn't finished yet -- no instrumentation -- you must still be in the
design phase ;)

do a statspack and see what the major waits are -- no use in hypothesizing, get
some metrics to look at.

do a tkprof on one of the apps as well -- see if it is spending the bulk of its
time in the database -- if not, you know you have an application problem.

what a shame that you take all of the data OUT of the database, only to put it
back in.  There is this other langage uniquely qualified to process data
efficiently and does not require actually taking it out....

May 02, 2003 Gunjeet -- Thanks for the question regarding "Analyzing tables and indexes", version V 8.1.6
You Asked
Hi Tom,

We currently use 8.1.6 (in MTS mode) and we plan to bundle
it with our product. Currently, the init.ora sets the optimizer
mode to CHOOSE. Based on my undestanding, it will default to
RULE
if we do not analyze the tables and indexes (which is currently
the case).

My questions are :

(i) Should we use COST based optimizer ? Will it result in
better
performance and is it suitable in our kind of configuration ?
At this point, none of our queries use any hints.

(ii) If yes, we will have to provide a script to the customers
to analyze tables and indexes on a routine basis.
How often should it be run ? It's hard to guess how often the
data will be changed and how much data will be changed.
Is there a rule of thumb for determining the optimal frequency
of gathering statistics ? Will weekly run be OK ?

(iii) I have read that using DBMS_STATS package is more
efficient
than "ANALYZE TABLE compute statistics for table for all
indexes". Is it true ? Can you do it for all the tables in
a schema without specifying individual table names ?

Thanks,

Gunjeet
and we said...
(i) in the best of cases, it'll go faster or the same.  In the
worst of cases, it will not for some queries.  Whether you use
it or not is a function of whether you want to use any of the
advanced features like function based indexes, descending
indexes, bitmapped indexes, parallel query and so on or not.
None of those are available with RBO -- only with CBO.

You can certainly try it -- it is easy enough to evaluate.

(ii) you could schedule a job for them instead.  The job will
run when you tell it to.  Without knowing the update activity,
it is difficult to say how long is OK to run without updating
the stats.  A weekly run will probably suffice to start but you
can give them directions on howto reschedule the jobs to run
more or less often.

(iii) I have not heard that.  It has to do the same work.
Review & Followup
Rating: 3
Analyzing tables and indexes -- Need some Tips May 02, 2003
Reviewer: JP from UK
Dear Tom

Thanks for your all time great suggestions and help.

I have also read the third point mentioned by Gunjeet from Oracle Manual
'Performance Tuning Guide and Reference'.

However, my question is as follows.

I have few tables that are frequently purged from the application point of view.
Basically one application inserts data in those tables and another application
reads data from those tables and after doing its work, it deletes all the
records from those tables and it happens very frequently.

I am using CBO. Analyse my database on a daily basis. But as CBO use dictionary
statistics, always the statistics would not be correct in this case. As for
example when I analyse those tables, Might be those tables contains around 10K
rows. And after deletion those will be empty. At that moment what Optimiser
actually consider? As I know Optimiser will consider analysed statistics i.e.
10K.

Will RBO beneficial for this case? Can I use RULE hint? Or any other alternative
way I can take care this type of scenario using CBO. Please give some
suggestion.

Thanks in advance.
Best Regards
JP

Followup:
As for RMAN -- it is because each block not only has to be read for the
incremental -- but it must be inspected to see if we need to copy it -- rather
then just bulk read/write -- it *probably* will be slower but as with all things
in life, there will be exceptions (backing up over a slow WAN for example --
that might reverse the slow to fast)

As for the empty/full table situation -- sounds like you *meant* to use advanced
queues which implements this functionality for you (and would even let you
magically add more or less back end processes to process the queue without
having to write lots of code yourself to mediate access to the data)....

But if the queries are only against this table (eg: you are not using this table
in multi-table statements like joins, subqueries, etc) -- using the RBO or
hinting it would be acceptable short term (AQ is the right answer).  In 9iR2 you
have optimizer_dynamic_sampling that can work wonders as well (search the 9iR2
docs on otn for that if you are interested)

May 02, 2003 Kurian -- Thanks for the question regarding "PGA --UGA", version 8.1.6
You Asked
hi Tom,
Below is the o/p of ps aux |more and the o/p of a query on v$sysstat
I am trying to figure out as to why the RSS for each client session is more than
500M.

What is the client session doing ?
How do i reduce the Same .

also, for your info the sort area size is 1M and sort area retained is 125K

USER        PID %CPU %MEM   VSZ  RSS TTY      S    STARTED         TIME COMMAND
oracle   232827 66.0  2.0 1.74G 502M ??       R    00:50:44    07:59:25
oracleBSCSPROD (DESCRIPTION=(LOCAL=no)(ADDRESS=(PROTOCOL=BEQ)))
oracle    82326 60.9  2.3 1.66G 554M ??       R    15:53:23     0:56.48
oracleBSCSPROD (DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))
oracle   118632 57.2  0.5 1.66G 113M ??       R    08:05:30    08:47:57
oracleBSCSPROD (DESCRIPTION=(LOCAL=no)(ADDRESS=(PROTOCOL=BEQ)))
oracle   164711 50.3  1.6 1.67G 399M ??       R    17:09:55     7:37.85
oracleBSCSPROD (DESCRIPTION=(LOCAL=no)(ADDRESS=(PROTOCOL=BEQ)))

STATISTIC# NAME                           CLASS          VALUE
---------- ------------------------- ---------- --------------
        15 session uga memory                 1   155270007032
        16 session uga memory max             1     8601632752
        20 session pga memory                 1    50429685152
        21 session pga memory max             1    50531824592
        91 calls to kcmgas                  128         911050

Regards
Kurian
and we said...
it (the RSS) includes the SGA that is attached to that process.

ps (top and the like) are useless with regards to seeing how much memory a
process owns.  If you are on solaris, the utility pmap will help you.

v$sysstat is cumulative from the time of instance startup -- you need to use
v$sesstat for these values.  the values in v$sysstat for these statistics are
"of great academic interest" only (meaning interesting but not very useful day
to day)
Review & Followup
Rating: 3
PGA --UGA May 02, 2003
Reviewer: Kurian from India
hi tom,

Our production system is running on Compaq Tru64.
I couldnt find pmap utility in here. I've used Pmap on Solaris. Is there a way
to find out How much memory the client session is using

Here is the O/p of v$sesstat
session pga memory             921133632
session pga memory max         922529848
session uga memory             240621168
session uga memory max         801843008

Followup:
that shows you how much memory the cumulative client sessions are using.

Right now, you are using about 878 meg of PGA memory (os memory) total across
all processes.  if you are using dedicated server (looks like it) the UGA is a
subset (contained in) the PGA so "ignore it"

if you don't aggregate it like you did above, you'll see it session by session.

If you want to do this at the OS level instead of the database for some reason,
you'll need to ask someone at compaq "how do i do that on your operating
system".

Rating: 5
User Resources May 02, 2003
Reviewer: A reader
How I can find the user, who is consuming most resources?

Followup:
define to me "resource"

v$sesstat shows you all of the statistics we collect by session -- order away,
pick the statistic you find to be the most "interesting" (based on your
definition of "resources") and order by against that.

Rating: 5
User Resources May 02, 2003
Reviewer: A reader
I mean by "resources", the resources that degrades the server performance.
Sometimes, our developers run thier bulk updates during working hours while
users connecting or may they do query that take long time. So, what is the
statistics in v$sessstat shall I look to it.

Followup:
tell me what resources "degrade performance"

You see -- that -- that is the $64,000 question.

If you could answer that -- you could save the world ;)

No resource "degrades" the server performance.

An unbalanced use of resource X on your system may well degrade YOUR performance
(but not mine).

May 03, 2003 Garry -- Thanks for the question regarding "Shrinking System", version
You Asked
Deframentation of the SYSTEM TABLESPACE
Presently:
Tspace   Status - Size(M)   - Used(M)
System - online - 2,622,627 - 58,734
Can the system tablespace be rebuilt with damage to the database
offline.
and we said...
Ouch -- looks like someone let people create some really big
things in system.

There is a chance you can shrink some of the datafiles (see
below -- it'll tell you if you can and will generate the alter
statements if they apply).  We can only shrink files back to
their high water mark -- if there is an extent way out at the
end of a file -- we'll not be able to shrink it.

If not, the only thing that will undo this is to recreate the
database (exp, imp).

----------- maxshrink.sql ----------------------------------

set verify off
column file_name format a50 word_wrapped
column smallest format 999,990 heading "Smallest|Size|Poss."
column currsize format 999,990 heading "Current|Size"
column savings  format 999,990 heading "Poss.|Savings"
break on report
compute sum of savings on report

column value new_val blksize
select value from v$parameter where name = 'db_block_size'
/

select file_name,
       ceil( (nvl(hwm,1)*&&blksize)/1024/1024 ) smallest,
       ceil( blocks*&&blksize/1024/1024) currsize,
       ceil( blocks*&&blksize/1024/1024) -
       ceil( (nvl(hwm,1)*&&blksize)/1024/1024 ) savings
from dba_data_files a,
     ( select file_id, max(block_id+blocks-1) hwm
         from dba_extents
        group by file_id ) b
where a.file_id = b.file_id(+)
/

column cmd format a75 word_wrapped

select 'alter database datafile '''||file_name||''' resize ' ||
       ceil( (nvl(hwm,1)*&&blksize)/1024/1024 )  || 'm;' cmd
from dba_data_files a,
     ( select file_id, max(block_id+blocks-1) hwm
         from dba_extents
        group by file_id ) b
where a.file_id = b.file_id(+)
  and ceil( blocks*&&blksize/1024/1024) -
      ceil( (nvl(hwm,1)*&&blksize)/1024/1024 ) > 0
/
Review & Followup
Rating: 5
The best trick I ever saw May 18, 2001
Reviewer: Stelios Karkazis from 30 STROFILIOU STR. 14561 KIFISIA - ATHENS - GREECE
This sql script saved my job. I hadn't enough disks, so I decided to run this
script and the miracle comes into my eyes.

Very nice !!!

Rating: 4
November 29, 2001
Reviewer: Jiten Padhiar from Englan (UK)
I'm not a DBA, but the results proved very useful.

Nice one.

Rating: 5
System tablespace grew... January 06, 2003
Reviewer: Rory B. Concepcion from Philippines
Hi Tom,

I have a similar problem. My SYSTEM tablespace grew to about 1G. Now I need to
resize it into about 200m (coz this is the space it really is occupying). here
is the full information.

I have about 15 rollback segments in the RBS tablespace. But the other 10 were
neglected to be written in the init.ora so when the database was bounced, only 5
were online and the other 10 offline. When transactions going on in the database
and additional rollback segments were needed, it made use of the rollback in the
SYSTEM tablespace. Now the SYSTEM tablespace is dictionary managed and it's
datafile has autoextend on. So the SYSTEM datafile grew to about 1G. When
transactions were finished, we learned about this coz the filesystem the SYSTEM
resided on grew and we needed the space. So now I was able to online the other
rolback segments and included them in the init.ora. But of course I can't resize
the SYSTEM datafile which generated an ora error 3297. When I looked at the
tablespace map os SYSTEM, I found out that the "way, way out segments" where the
clusters C_TS# and C_FILE#_BLOCK#. But just a few of their blocks where
scattered. So, these are the segment blocks preventing me from resizing the
SYSTEM tablespace.

Can you think of any workaround for this? I'm not sure if I can recreate those
two clusters. Any suggestions would be highly appreciated. Thanks and I'm
looking forward to your new book.

Followup:
that is not what happend. the special SYSTEM rollback is used when you offline
files that have active rollback.  the rollback is then moved from the normal RBS
to the system RBS so we know where it is -- so when the file comes back online
-- we can use that undo to fix the file.  That is how your system rbs grew.

1gig is trivial.  Given that a 36gig drive costs less than $200 usd -- it would
be far far cheaper to buy a new drive then spend the time to try and "fix" this.

Short of recreating the ENTIRE database from scratch using export import, you
are NOT going to resize that file.

Rating: 5
Shrinking system ... January 08, 2003
Reviewer: Rory B. COncepcion from Philippines
Thanks Tom for the very timely answer. You mean to say that when I have active
rollbacks in a rollback segment then it's taken offline, the active rollbacks
there would be put to the system rollback? sorry, just verifying on what you
meant by offlining file(datafile of what or rollback?).
But thanks again. Good day.

Followup:
when you offline a datafile, that has rollback, the rollback is copied into
system so when you online the DATAFILE, we can "fix it".
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96524/b_deprec.htm#634866

Rating: 5
HWM in datafile ? January 08, 2003
Reviewer: David
Tom, great article but... sorry, a slight emphasis on concepts:

>We can only shrink files back to their high water mark --
>if there is an extent way out at the end of a file --
>we'll not be able to shrink it.

By definition, the HWM in a SEGMENT is the "highest" location data has EVER
reached -- no matter if this piece of data is still there or not -- OK !

However, this is not the case with DATAFILES. If there has ever been an extent
at the end of the file, but I has been removed,  it doesn't matter -- unlike
with segments.

In fact we can shrink a datafile down to the highest extent that "is presently"
in it -- not "ever was" in it.

Rating: 4
Alternative Solution ? January 08, 2003
Reviewer: Johan Snyman from Johannesburg South Africa
Tom,

Would it not be possible (assuming Oracle 8.1.6 or later) to use the
transportable tablespace facility to "fix" this ?

I am thinking of something like:

(0) Make a backup.
(1) Perform the required actions to initiate tablespace transportation
(2) Create another database on the same system (preferrably keeping the
existing, large system tablespace in place, in case something goes wrong)
(3) Perform the required actions to transport the tablespaces to the new
database with the correctly sized system tablespace.
(4) Test
(5) Get rid of the old system tablespace
(6) Backup

Are there any potential problems with this approach ? One thing I am not certain
of, is the best way to transfer database user accounts to the "new" database ?

Followup:
yes, you can do that - but you'll need to carry over things like

users
roles
public synonyms
grants

yourself.

Rating: 4
A Very Useful Stuff February 18, 2003
Reviewer: Ashok Shinde from Pune, India
Tom,

    I know this is not the right place to ask the question but I waited for a
month so that you clear your backlog but luck was not on my side.

    The script is really very useful. But I've additional requirement. I do not
know where should I start to solve it myself. I wanted to shrink only those data
files whose tablespaces are not accessed by any DDL/DML for last 30 days ?

Please Help.

Followup:
You would need to enable auditing so you would actually KNOW what tablespaces
where accessed by DML in the last 30 days.

DDL would be trickier -- a create, easy to see, a drop -- not so easy to see.

But -- I would ask you this -- this would be a lot of pain for little (in my
opinion NO) gain.  what is the reasoning behind this?

Rating: 4
A Very Useful Stuf February 19, 2003
Reviewer: Ashok Shinde from Pune, India
Tom,
    We have a ver large data warehouse having 1000's of tablespaces and 30000
objects used by around 200 developers. Most of the development team is replaced
with a new team who does not have complete idea about the calls to all the
objects. Our development box is full of disk and each disk is almost full to its
capacity. The client is not ready to spend more on the box but wants to have
some development. Hence the only alternative to go ahead with new development is
to reduce the space occupied by current tablespaces. This has caused me to look
at tables/index which are not used for long time and shrink them.

Rating: 3
Useful but still confused March 25, 2003
Reviewer: Jay Earle from New Brunswick, Canada
Part way down in this thread the Reviewer David seems to contradict what Tom is
saying.  I am not sure if this is true or not.   If the data is removed can the
file be shrunk back?

------------------------------------------------

HWM in datafile ?  January 08, 2003
Reviewer:  David

Tom, great article but... sorry, a slight emphasis on concepts:

>We can only shrink files back to their high water mark --
>if there is an extent way out at the end of a file --
>we'll not be able to shrink it.

By definition, the HWM in a SEGMENT is the "highest" location data has EVER
reached -- no matter if this piece of data is still there or not -- OK !

However, this is not the case with DATAFILES. If there has ever been an extent
at the end of the file, but I has been removed,  it doesn't matter -- unlike
with segments.

In fact we can shrink a datafile down to the highest extent that "is presently"
in it -- not "ever was" in it.

Followup:
you you get rid of the extent at the end of a file, it (the file) can then be
shrunk back -- yes.

Rating: 3
maxshrink.sql for temp tablespace May 03, 2003
Reviewer: reader
tom,

i want to run maxshrink.sql for temporary tablespace. - if my temp segments are
not deallocated internally ..possible?

if so what changes i shud do in the script.

thanks

May 03, 2003 Nancy -- Thanks for the question regarding "DB_Block_Buffer is zero in init file for 9.2.0. ", version 9.2.0.
You Asked
Tom,  Thank you for all your hard work.  I find this site a wealth of
information.  I hope to see you in Dallas in Feb. for your conference on
performance.

I have installed Oracle 9.2.0 onto a test server with MS 2000 with 8192 block
size.
I noticed that the db_block_buffer in the init file is 0(zero).
Is this correct?  If yes, how is the best way to size the SGA?

I noticed that there are new parameters:  db_8k_cache_size, db_16k_cache_size,
etc,   How are they used in this release?

We currently are not using Java in our applications.  How small can I set the
java_pool_size and not effect Oracle.   The java_pool_size is currently set at
20,971,520.
and we said...
It is recommended that you use db_cache_size to specify the size of the cache in
bytes, kilobytes, megabytes or gigabytes.  It is easier then using db_block_size
* db_block_buffers.

The setting of 0 just means we are using the db_cache_size.

The others are only if you make use of multiple block sizes in your database.
You can setup separate caches for each block size you use and you can use upto 5
blocksizes (2,4,8,16,32k)

You can set to zero:

idle> show parameter java

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
java_max_sessionspace_size           integer     0
java_pool_size                       big integer 0
java_soft_sessionspace_limit         integer     0
idle>

if not using it.
Review & Followup
Rating: 5
I'm surprised December 03, 2002
Reviewer: CJ from Albany, NY, USA
I'm a little surprised that you didn't just point to the documentation.

Followup:
I've done so quite a few times but this keeps coming up so I answered it and
published it.

I've even had people tell me "you don't document this" -- even after being
pointed to the exact piece of documentation.

I don't get it -- I think people have been trained that "all documentation
stinks, don't even bother with it" or "Oracle documentation stinks, forget about
it".  How many questions begin with "don't point me to the documentation...."
that is for sure the fastest way to get me to point you to the documentation ;)

Rating: 2
ORA-00384: Insufficient memory to grow cache May 03, 2003
Reviewer: A reader
tom,
 i got this error when i tried to set 16k cache size.

SQL> alter system set db_16k_cache_size=11m;
alter system set db_16k_cache_size=11m
*
ERROR at line 1:
ORA-02097: parameter cannot be modified because specified value is invalid
ORA-00384: Insufficient memory to grow cache

should i have to increase my sga? - if so how?
or
should i have to increase my RAM? - (only)this i know :)

thanks for your terrific job

Followup:
you did not set an SGA_MAX_SIZE -- hence it will default to the size of the
allocated SGA -- meaning if you want to set the 16k cache size to X, you'll need
to decrease some other component by X first -- eg, shrink the db_CACHE_SIZE,
then you can increase the db_16k cach.

Rating: 4
SGA_MAX_SIZE May 03, 2003
Reviewer: reader
SQL> alter system  set db_cache_size=16m;

System altered.

SQL> alter system  set db_16k_cache_size=16m;

System altered.

SQL> select id,name,block_size, current_size,buffers from v$buffer_pool;

        ID NAME                 BLOCK_SIZE CURRENT_SIZE    BUFFERS
---------- -------------------- ---------- ------------ ----------
         3 DEFAULT                    4096           16       3916
         7 DEFAULT                   16384           16       1012

Now i could set it. - Thank you!

one more question, if i dont set sga_max_size what is the default size, is that
the sga size equals sga_max_size ?

SQL> sho parameters SGA_MAX_SIZE

NAME                                 TYPE        VALUE
------------------------------------ ----------- --------------------
sga_max_size                         big integer 122755896

SQL> sho sga

Total System Global Area  122755896 bytes
.
.

thanks

Followup:
sga max size defaults to the size of the sga.

May 03, 2003 Alberto -- Thanks for the question regarding "clustering factor = consistent gets", version 9.0.1
You Asked
Hi Tom,

May we say that
  "The clustering factor of an index is (exactly)
   the number of consistent gets we request on the table,
   when accessing the table from this index,
   visiting the index in key order,
   from the first leaf block down to the last leaf block" ?

The statement seems 100% "mathematically correct", as per the two test cases
below (results are for 9.0.1); but will it be valid in general ?

I would find this "physical meaning" (if confirmed by your wise eyes) very
useful and also easier to remember than the generic "measure of how well ordered
a table is in respect of this index".

Many Thanks
Alberto

-- test case 1 - table rows perfectly ordered
drop table d;
create table d (x number, filler char(1000) default 'x');
insert into d (x) select rownum from all_objects where rownum <= 40;
alter table d add constraint d_pk primary key (x);
exec dbms_stats.gather_table_stats (ownname=>user, tabname=>'D', cascade=>true);
col index_name format a10
set lines 123
select index_name,leaf_blocks, CLUSTERING_FACTOR, blevel from user_indexes where
table_name='D';
select num_rows, blocks from user_tables where table_name='D';
-- next stmt is to factor away #gets for parsing
select /*+INDEX (D D_PK) */ distinct filler from d;
set autotrace traceonly
select /*+INDEX (D D_PK) */ distinct filler from d;
set autotrace off
-- end of test case

Results:

SQL> select index_name,leaf_blocks, CLUSTERING_FACTOR, blevel from user_indexes
where table_name='D';

INDEX_NAME LEAF_BLOCKS CLUSTERING_FACTOR     BLEVEL
---------- ----------- ----------------- ----------
D_PK                 1                14          0

SQL> select num_rows, blocks from user_tables where table_name='D';

  NUM_ROWS     BLOCKS
---------- ----------
        40         15

SQL> select /*+INDEX (D D_PK) */ distinct filler from d;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=24 Card=1 Bytes=1001)
   1    0   SORT (UNIQUE) (Cost=24 Card=1 Bytes=1001)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'D' (Cost=15 Card=40 Bytes=40040)
   3    2       INDEX (FULL SCAN) OF 'D_PK' (UNIQUE) (Cost=1 Card=40)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         15  consistent gets
          0  physical reads

1 consistent get for the index + 14 (=Clustering factor) for the table = 15

Repeating the same steps, but inserting in the table 1000 rows instead of 40, in
order to have some branch blocks, we get:

INDEX_NAME LEAF_BLOCKS CLUSTERING_FACTOR     BLEVEL
---------- ----------- ----------------- ----------
D_PK                 4               334          1

SQL> select num_rows, blocks from user_tables where table_name='D';

  NUM_ROWS     BLOCKS
---------- ----------
      1000        335

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        339  consistent gets
          0  physical reads

1 for the root block + 4 for leaf blocks + 334 (=Clustering factor) for the
table = 339

-- test case 2 - table rows randomly ordered
drop table d;
create table d (x number, filler char(1000) default 'x');
insert into d (x) select dbms_random.normal from all_objects where rownum <= 40;
alter table d add constraint d_pk primary key (x);
exec dbms_stats.gather_table_stats (ownname=>user, tabname=>'D', cascade=>true);
col index_name format a10
set lines 123
select index_name,leaf_blocks, CLUSTERING_FACTOR, blevel from user_indexes where
table_name='D';
select num_rows, blocks from user_tables where table_name='D';
-- next stmt is to factor away #gets for parsing
select /*+INDEX (D D_PK) */ distinct filler from d;
set autotrace traceonly
select /*+INDEX (D D_PK) */ distinct filler from d;
set autotrace off
-- end of test case

Results:

SQL> select index_name,leaf_blocks, CLUSTERING_FACTOR, blevel from user_indexes
where table_name='D';

INDEX_NAME LEAF_BLOCKS CLUSTERING_FACTOR     BLEVEL
---------- ----------- ----------------- ----------
D_PK                 1                38          0

SQL> select num_rows, blocks from user_tables where table_name='D';

  NUM_ROWS     BLOCKS
---------- ----------
        40         15

SQL> select /*+INDEX (D D_PK) */ distinct filler from d;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=48 Card=1 Bytes=1001)
   1    0   SORT (UNIQUE) (Cost=48 Card=1 Bytes=1001)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'D' (Cost=39 Card=40 Bytes=40040)
   3    2       INDEX (FULL SCAN) OF 'D_PK' (UNIQUE) (Cost=1 Card=40)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         39  consistent gets
          0  physical reads

Again, 1 block for the index + 38 (=Clustering factor) for the table = 39

Inserting 1000 rows instead of 40:

INDEX_NAME LEAF_BLOCKS CLUSTERING_FACTOR     BLEVEL
---------- ----------- ----------------- ----------
D_PK                10               998          1

  NUM_ROWS     BLOCKS
---------- ----------
      1000        335

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       1009  consistent gets
          0  physical reads

Again, 1 for the root block + 10 for leaf blocks + 998 (=Clustering factor) for
the table = 1009

An interesting corollary is that the algorithm is not, as i foolishly thought:

for every entry in the index
    consistently get the rowid-pointed table block
    read the table block
    release the table block
(this would get at least 40 or 1000 gets (=number of keys) anytime)

but it is:

for every entry in the index
    if rowid-pointed table block is not current table block
        release current table block
        consistently get the rowid-pointed table block, make it the current
table block
    read the table block
and we said...
it is one way to think about it -- but since you never read an entire table via
an index -- not the most "useful" way in my opinion.

The way I remember it -- the close the cluster factor to the number of blocks in
the table, the more likely that index is to be used for larger range scans
because the rows in the table will be located in the same (small) set of blocks.

(but your last algorithm is conceptually correct, when range scanning)
Review & Followup
Rating: 5
"clustering factor = consistent gets" for range scan also May 03, 2003
Reviewer: Alberto Dell'Era from Milano, Italy
>The way I remember it -- the close the cluster factor to the number of blocks
>in the table, the more likely that index is to be used for larger range scans
>because the rows in the table will be located in the same (small) set of
blocks.

That's the way I've always thought about it, but it's the same. Let me explain
myself better.

For range scans [accordingly to Wolfgang Breitling's "A look under the Hood of
the CBO: the 10053 event", page 9 - i got the version available at hotsos.com],
the formula used by the 8.1.7 CBO to compute the cost is

blevel + FF * leaf_blocks + FF * clustering_factor

where FF (filtering factor) is the percentage of rows you are going to fetch
from the table, and so
1. "FF * leaf_blocks"       is the (approx) number of leaf  blocks Oracle
   consistently gets,
2. "FF * clustering_factor" is the (approx) number of table blocks Oracle
   consistently gets.

The latter is crystal clear now that I know the algorithm for range scanning
(that you have kindly confirmed as correct, albeit conceptually):

for every entry in the index
    if rowid-pointed table block is not current table block
        release current table block
        consistently get the rowid-pointed table block, make it the current
table block
    read the table block

If starting on the first leaf block and ending on the last (call it "full range
scan") Oracle:
1. reads and gets all the leaf blocks
2. reads all the table blocks, someone more than one time, for a total of
clustering_factor gets
[this seems 100% correct, as per the original experiment]

then, in a healthy range scan, starting somewhere in the index, and reading only
a percentage of FF index entries, Oracle:
1. reads FF * leaf_blocks once, for a total of FF * leaf_blocks gets
2. reads an *unknown* number of table blocks, someone more than one time,for a
total of FF * clustering_factor gets

I.e., FF if the width of the "slice" of the structure index+table that is
visited, as "seen" or "measured" though the consistent-gets-binoculars. The
clustering factor is the magnifying factor applied to the table part of this
index+table structure.

Thus, the clustering factor as a precise physical meaning, even when range
scanning. I would say, even when joining, but this starts to get complicated to
discuss.

That's why i consider "clustering factor = consistent gets" a very useful model;
and much more than simply a model.

Rating: 4
to Alberto May 03, 2003
Reviewer: A reader
Hi

How do you ensure that your table is perfectly ordered or not? Do you mean
physically in disk? Or some other meaning?

Followup:
b*tree clustered tables
hash clustered tables
index organized tables

OR

in a read mostly environment

data loaded sorted
create table as select ... order by....

Rating: 5
test case for: "clustering factor=consistent gets" for range scan also May 03, 2003
Reviewer: Alberto Dell'Era from Milan, Italy
We can't call it a post without a decent test case.

So let's have the consistent-gets-binoculars estimate the # of consistent gets
we will have by range-scanning the table. No fancy hints, we want a real
range-scan.

-- test case 1
drop table d;
create table d (x number, filler char(1000) default 'x');

-- prepare test rows
-- here goes the insert stmt used to populate table d (see below)

create index d_idx on d (x);
exec dbms_stats.gather_table_stats (ownname=>user, tabname=>'D', cascade=>true);

-- check the distribution
select x, 100 * count(*) / (select count(*) from d) ff from d group by x;

-- calc FF (this is the real FF, not an estimate)
variable ff number
exec select (select count(*) from d where x=10) / (select count(*) from d) into
:ff from dual;

-- activate consistent gets binoculars
col index_name format a10
set lines 123
select index_name, leaf_blocks, CLUSTERING_FACTOR, blevel, :ff ff,
round(blevel+:ff*leaf_blocks+:ff*clustering_factor) cons_get_binoculars
from user_indexes where table_name='D';

-- next stmt is to factor away #gets for parsing
select distinct filler from d where x=10;
set autotrace traceonly
select distinct filler from d where x=10;
set autotrace off
-- end of test case

-- scattered distribution : 1 2 3 4 5 6 .. 19 ... 0 1 2 3 4 5 6 .. 19
insert into d (x) select mod(rownum,20) from all_objects,all_objects where
rownum <= 10000;

INDEX_NAME LEAF_BLOCKS CLUSTERING_FACTOR BLEVEL         FF CONS_GET_BINOCULARS
---------- ----------- ----------------- ------ ---------- -------------------
D_IDX               41             10000      1        .05                 503

SQL> select distinct filler from d where x=10;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=585 Card=1 Bytes=1003)
   1    0   SORT (UNIQUE) (Cost=585 Card=1 Bytes=1003)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'D' (Cost=503 Card=500)
   3    2       INDEX (RANGE SCAN) OF 'D_IDX' (NON-UNIQUE) (Cost=3 Card=500)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        517  consistent gets
          0  physical reads

Error: (503 - 517) / 517 = 2.7%; very good

-- clustered distribution 0 (repeat for 10000/20 times) 1 (repeat for 10000/20
times) ...
insert into d (x) select trunc (rownum/ trunc(10000/20)) from
all_objects,all_objects where rownum <= 10000;

INDEX_NAME LEAF_BLOCKS CLUSTERING_FACTOR BLEVEL         FF CONS_GET_BINOCULARS
---------- ----------- ----------------- ------ ---------- -------------------
D_IDX               41              3334      1        .05                 170

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=240 Card=1 Bytes=1003)
   1    0   SORT (UNIQUE) (Cost=240 Card=1 Bytes=1003)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'D' (Cost=161 Card=476)
   3    2       INDEX (RANGE SCAN) OF 'D_IDX' (NON-UNIQUE) (Cost=2 Card=476)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        184  consistent gets
          0  physical reads

Error: (184-170)/184 = 7.6%; fairly good

-- completely random distribution
exec dbms_random.initialize (2323627);
insert into d (x) select mod (round(abs(dbms_random.random)),20)  from
all_objects,all_objects where rownum <= 100000;

INDEX_NAME LEAF_BLOCKS CLUSTERING_FACTOR BLEVEL         FF CONS_GET_BINOCULARS
---------- ----------- ----------------- ------ ---------- -------------------
D_IDX              401             95007      2     .05034                4805

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5579 Card=1 Bytes=1003)
   1    0   SORT (UNIQUE) (Cost=5579 Card=1 Bytes=1003)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'D' (Cost=4774 Card=5000)
   3    2       INDEX (RANGE SCAN) OF 'D_IDX' (NON-UNIQUE) (Cost=23 Card=5000)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       4930  consistent gets
          3  physical reads

Error: (4805 - 4930) / 4930 = 2.5%; very good

-- skewed random distribution - x=10 (50%), other values at random
exec dbms_random.initialize (2323627);
insert into d (x) select
case
  when dbms_random.random < 0 then 10
  else 100 + mod (round(abs(dbms_random.random)),20)
end
from all_objects,all_objects where rownum <= 100000;

INDEX_NAME LEAF_BLOCKS CLUSTERING_FACTOR BLEVEL         FF CONS_GET_BINOCULARS
---------- ----------- ----------------- ------ ---------- -------------------
D_IDX              415             78187      2      .4976               39114

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4511 Card=1 Bytes=1004)
   1    0   SORT (UNIQUE) (Cost=4511 Card=1 Bytes=1004)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'D' (Cost=3746 Card=4762)
   3    2       INDEX (RANGE SCAN) OF 'D_IDX' (NON-UNIQUE) (Cost=22 Card=4762)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      30598  consistent gets
      29152  physical reads

Error: (39114 - 30598) / 30598 = 27.8% KABOOOM ! Where's my Harakiri sword ?

Jonathan Lewis' book (pag. 80) to the rescue of my belly:
"To create this [2 column] index, oracle reads object_name and object_type from
the table, then appends the rowid to produce a THREE COLUMN list. Oracle then
sorts THIS LIST ...". So it means that the entries in the index are sorted in
rowid order too!
Here we have, following the leaf block chain:
(1) first 50000 entries with x = 10          => due the ordering of rowids, the
"local" clustering factor is "low"
(2) last  50000 entries with other values>10 => no ordering
Since the total clustering factor is (1)+(2) [additive, interesting], this is
much higher than (1); hence the superestimation made by the
consistent-get-binoculars.

To calculate (1), I've used this trick to 'truncate' the part(2) of the index:
update d set x = null where x <> 10;
alter index d_idx coalesce;
exec dbms_stats.gather_table_stats (ownname=>user, tabname=>'D', cascade=>true);

select CLUSTERING_FACTOR from user_indexes where table_name='D';

CLUSTERING_FACTOR
-----------------
            29152

So the binoculars pointed at (1) give: 2 + .4976*415 + 29152 =  29360
For an error of (29360 - 30598) / 30598 = 4%; very good!

So, I'm even more convinced that thinking to the clustering factor in terms of
consistent gets is the way to go, when one considers the "fluctuations" of the
"local" clustering factor when passing from one key to another. The consistent
gets are the true essence, the unit of measure, the physical meaning of it.

Now, given that the clustering factor is very often the largest contributor to
the consistent gets (LIO), as seen above,
wouldn't it beneficial to have an histogram on it ?

Sorry to bother you with a topic that you may find uninteresting ... but I'm
addicted, you know! ;-)
Alberto

Followup:
what i mean is -- i have a table and index with a cluster factor of 10,000 and
another table and index with a cluster factor of 100,000,000

which is better using the "cluster factor=consistent gets" methodology?

which ever you pick, I will successfully argue the opposite is true and present
the test case to prove my point.

Now, if I told you the first table had 500 blocks and the second table had
100,000,000 blocks -- you would be able to say something.

Or if the first and second tables had 1,000,000 blocks each...

and so on.  This is why I think the description or analogy is misleading.
cluster factor = consistent gets if and only if you get all of the rows from the
table via the index range scan which is something you would not really be
doing in real life.

Rating: 4
further clarification May 04, 2003
Reviewer: Alberto Dell'Era from Milan, Italy
>This is why I think the description or analogy is misleading.
>cluster factor = consistent gets if and only if you get all of the rows from
the
>table via the index range scan which is something you would not really be
>doing in real life.

But I 100% agree with your statement above! There's a misunderstanding here.

In the original post, i *forced* the CBO to use the index to get *all* the rows
of the table, but that was just an experiment, a probe, a border case, a
laboratory test in simplified conditions, not a real-life scenario; i absolutely
agree, IT IS NOT GOING TO HAPPEN IN REAL LIFE, never said it would happen. The
purpose of that experiment was to show that the clustering_factor is not just an
heuristic number - it's a physical measure. Of the consistent gets.

In the 2nd post I've refined (and provided test cases in the 3rd) :

"If you're going to fetch a percentage of FF rows from a table, passing through
the index,
the consistent gets (=LIO) that you are going to perform on the table are
(approx) FF * clustering_factor"

In retrospect, i would have being better rephrasing the 2nd title "cluster
factor * FF = consistent gets"; sorry for that, i've been misleading.

How good is this approximation ?

My test cases in the 3rd post "test case for : ... range scan also" have shown:
test case 1 - error: 2.7%
test case 2 - error: 7.6%
test case 3 - error: 2.5% (BTW case 3 is the most common case in real life,
IMHO)
test case 4 - error: 27.8%; once corrected to take into account the "local" cluf
for x=10 : 4%
(so caveat emptor if you get this case - a "skewed cluf distribution" on skewed
data)

Seems a very good approximation - that calls at least for closer investigation.

>i have a table and index with a cluster factor of 10,000 and
>another table and index with a cluster factor of 100,000,000
>which is better using the "cluster factor=consistent gets" methodology?

I'm not saying that the clustering_factor ALONE is of any use; never said that.

With the data provided, all i can say is:
first table] CGs performed *if using the index* = FF * 10,000
other table] CGs performed *if using the index* = FF * 100,000,000

Is the index going to be used ? Can't tell. What's the cost of the alternative
path, the full table scan ?

>Now, if I told you the first table had 500 blocks and the second table had
>100,000,000 blocks -- you would be able to say something.

Yes, now i can estimate the cost of the full scan. Assuming (for easy of
calculation) a MULTIBLOCK_IO_READ_COUNT=10, we have:

first table] multiblock reads : 500 / 10 = 50
other table] multiblock reads : 100,000,000 / 10 = 10,000,000

So, as a first approximation, it is cheaper to use the index if FF is less than
first table] FF * 10,000 < 50 => FF < 0.005 (0.5%)
other table] FF * 100,000,000 < 10,000,000 => FF < 0.1 (10%)

So i got the same qualitative conclusion you were suggesting me. But now we have
also some numbers to *start with* our analysis; we have the (approx) break even
for FF. Emphasys on "start with", i'm not pretending that this is a silver
bullet that sends any other methodology to /dev/null, perish the thought; it's
just, IMHO, another useful tool. Useful to leverage the true meaning of that
humble unappreciated number in the user_indexes view. Without defects ? Hardly,
see the test case 4 on skewed data; but 1,2,3 give much better results, an
average of 4.3% error on consistent gets forecasting. Can be it just by chance ?

Alberto

May 04, 2003 Saibabu -- Thanks for the question regarding "DML locks serialization.", version 9.2
You Asked
Hi Tom,

I am seeing strange behaviour with Oracle when it comes to serialization locks:

I have created a table called TEST3(a number, b varchar2(25)) and inserted
mutiple sets of all rownums from all_objects with column "b" value null. Now,
Index was created on TEST3(a). I have inserted a new row with values
(10000,'RADHA') and commit the transaction.

Now, I have opened two sessions:
Session 1> SQL> update test3 set b='ANURADHA' where b='RADHA'
  2  and 0<>(select count(*) from v$access) and
  3  0<> (select count(*) from all_objects) and
  4  0<> (select count(*) from v$access) and
  5  0 <> (select count(*) from v$access);

Session 2>  update test3 set b='ANU' where a=10000;

Session 2 sql statement was issues right after the sql statement in session 1,
but session 2 update statement got the row lock and updated the row, while the
session 1 update query is waiting for lock .

I thought Oracle will serialize locks for all DML's on a particular object, in
this particular case session 1 should have got the lock on TEST3 before session
2. Please explain.

Thanks
 Sai.
and we said...
session 1 took longer to get to the row a=10000 -- and lost the race.

I see nothing wrong or confusing here -- you did N count(*)'s (they take *time*)
and then started updating the table test3.  it had to take time to update N
(9,999 rows probably) before getting to row 10,000.

Session 2 got to its single row before session 1 did (it just zoomed over the
other rows, did not have to do the counts -- it won the race to get to row
10,000) and updated it.

This is normal and natural and totally expected.
May 04, 2003 Fredrik -- Thanks for the question regarding "A waste of space", version 9iR2
You Asked
Hi Tom,

Test case:

SQL> CREATE TABLE sales_list
  2  (
  3  salesman_name   VARCHAR2(30),
  4  sales_region     VARCHAR2(8)
  5  )
  6  PARTITION BY LIST(sales_region)
  7  (
  8  PARTITION sales_region_1 VALUES('R1'),
  9  PARTITION sales_region_2 VALUES ('REGION 2')
 10  );

Table created.

SQL>
SQL> insert into sales_list select 'Name1', 'R1' from all_objects where rownum <
1000;

999 rows created.

SQL>
SQL> analyze table sales_list compute statistics;

Table analyzed.

SQL>
SQL> select avg_row_len from dba_tables where table_name = 'SALES_LIST';

AVG_ROW_LEN
-----------
12

SQL>
SQL> truncate table sales_list;

Table truncated.

SQL>
SQL> insert into sales_list select 'Name1', 'REGION 2' from all_objects where
rownum < 1000;

999 rows created.

SQL>
SQL> analyze table sales_list compute statistics;

Table analyzed.

SQL>
SQL> select avg_row_len from dba_tables where table_name = 'SALES_LIST';

AVG_ROW_LEN
-----------
18

Why does the sales_region column consume space in the data blocks? It seems like
a waste of space...

Regards,
Fredrik
and we said...
Umm, it has to.

In general the list is ( 'a','b','c','d','e',..... )

it is not normally or in general a partition on a "single value"

Additionally -- you can start with partitions whereby the lists have a single
value but add partitions later that have more than 1.  you cannot say "there is
only one value"

suggestion -- use a numeric region number if space is a premium.
Review & Followup
Rating: 2
Less space, but May 02, 2003
Reviewer: Fredrik
No, in this specific case it is not necessary to store this information. It
could be stored at a "higher level", but I appreciate the effort to code for
this case.

I do not agree that it should be "not normal" to partition on a "single value".
Why should I create a partition list like ( 'a','b','c','d','e',..... ) when I
have an equal distribution of records and I can create a whole bunch of
partitions?

Even by using a numeric sales_region I can store fewer records per block and
thus cache fewer records than if it wasn&#8217;t stored in the first place. Even
a single extra byte counts if you have loads of them ;)

Thanks,
Fredrik

Followup:
it is the way it works -- it works that way.

You have a very specific, singular, not average case.

YOU have a specific case.
IN GENERAL that is not the case.

it is the way it works, nothing more, nothing less.

oh, and if this a data warehouse or read mostly data, use table compression if
space is your primary #1 concern.

Rating: 4
Table compression. May 02, 2003
Reviewer: Sai from Palo Alto, CA
Hi Tom,

What is Table compression?

Thanks

Followup:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7360081657285

      Rating: 3
"A waste of
      space" version 9iR2  May 02, 2003
Reviewer:
      Mohamed Elbasheer  from Seattle
I am not quite sure if I understood the question well but
if the issue is  about the Average Row length calculation here is the truth:

there is always some overhead associated with a row. Things like the row header
and bytes for column lenghts.
The  ANALYZE will include the row header (3 bytes) and the column overhead
(usually 1 bytes per column - 0 for a trailing null, 3 for a long column).
Therefore, in the example provided there is always going to be a 5 bytes
overhead for each row@

Regards,
mze

          Followup:

No, they are saying "Oracle -- you should not store sales_region AT ALL, it
obviously isn't needed"

In their special case, they would be correct, it technically is not necessary.

However, in general, it must be stored.  It would be incorrect not to store it
in general.

      Rating:
      3
Compression  May 04, 2003
Reviewer:
      Fredrik
OK, fair enough, it is the way it works and it works that way and I will use
compression.

However ;), I still do not agree that this case is so specific to me, consider
this general example:

Customer
=======
Primary Key (something)
…
…
Sex
MaritalStatus
IncomeGroup
NumberOfChildren
…
…

If I could partition and sub-partition again and again a few levels, using
partition by single value I could do away with these four columns completely – 0
bytes in the data blocks. Being restricted by the way it works today we would
probably create bitmap indexes on these columns (assuming we are not in a OLTP
environment).

Let’s assume I want to know how many married men with 2 children and earning a
high income. It would be quite fast using the bitmap indexes, but is it really
faster than the partition pruning we would have with the single value
partitioning and sub-partitioning concept?

Another advantage compared to the bitmap indexes is that it would be OK to use
in an OLTP environment with a lot of DML on the table.

One restriction of this concept would of course be: the columns used for this
should have a very low cardinality because otherwise we would obviously end up
with too many partitions, especially if it is deeply nested, but Oracle is full
of tools that should only be used under certain circumstances.

Anyway, this is not a question anymore and askTom is probably not the right
forum for an Oracle wish list discussion, general or otherwise...?

          Followup:

that is even more "rare" and superficial IMO.

There are official mechanisms via support to file enhancement requests with a
good business case.

      Rating: 5
Partition
      Nesting  May 05, 2003
Reviewer:  Loganathan
      Palanisamy  from Santa Clara, CA USA
Fredrick,

Even with Sub-partitions, you can go only two levels. I don't think Oracle
allows partitioning of sub-partitions. What you describe above needs four levels
which is not possible.

May 05, 2003 Ramasamy -- Thanks for the question regarding
      "Order By 2,3,29 Vs Order by 2,3,4", version 8.1.7

      You Asked
Tom ,

In the order by clause of a query instead of Order By 2,3,29
if i select the 29th column as 4th column and then order by 2,3,4.
Will it improve the performance of the query?.
(Note: Still i will be selecting 30+ columns).
Please explain your reasoning behind your answer YES OR NO.

Thanks in advance.

      and we said...
it will be marginal at best, the distance between the columns will not be very
relevant in a sort.

o case 1: index used to do the sort.  In that case, 2,3,29 are as "close" as
2,3,4 are.

o case 2: sort done after the fact -- we have to look at the entire row anyway
in order to sort (the "swap" part of a sort if you will).  Since we access the
entire result set row anyhow, it won't "matter"

select OWNER, OBJECT_NAME, SUBOBJECT_NAME, TIMESTAMP xxx, OBJECT_NAME x1,
OBJECT_NAME x2, OBJECT_NAME x3, OBJECT_NAME x4, OBJECT_NAME x5, OBJECT_NAME x6,
OBJECT_NAME x7, OBJECT_NAME x8, OBJECT_NAME x9, OBJECT_NAME x10, OBJECT_NAME y1,
OBJECT_NAME y2, OBJECT_NAME y3, OBJECT_NAME y4, OBJECT_NAME y5, OBJECT_NAME y6,
OBJECT_NAME y7, OBJECT_NAME y8, OBJECT_NAME y9, OBJECT_ID, DATA_OBJECT_ID,
OBJECT_TYPE, CREATED, LAST_DDL_TIME, TIMESTAMP, STATUS, TEMPORARY, GENERATED,
SECONDARY from big_table order by 2, 3, 29

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch   122254     96.09     655.28     321224      22002       1269     1833792
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   122256     96.09     655.28     321224      22002       1269     1833792

Rows     Row Source Operation
-------  ---------------------------------------------------
1833792  SORT ORDER BY
1833792   TABLE ACCESS FULL BIG_TABLE
********************************************************************************
select OWNER, OBJECT_NAME, SUBOBJECT_NAME, TIMESTAMP xxx, OBJECT_NAME x1,
OBJECT_NAME x2, OBJECT_NAME x3, OBJECT_NAME x4, OBJECT_NAME x5, OBJECT_NAME x6,
OBJECT_NAME x7, OBJECT_NAME x8, OBJECT_NAME x9, OBJECT_NAME x10, OBJECT_NAME y1,
OBJECT_NAME y2, OBJECT_NAME y3, OBJECT_NAME y4, OBJECT_NAME y5, OBJECT_NAME y6,
OBJECT_NAME y7, OBJECT_NAME y8, OBJECT_NAME y9, OBJECT_ID, DATA_OBJECT_ID,
OBJECT_TYPE, CREATED, LAST_DDL_TIME, TIMESTAMP, STATUS, TEMPORARY, GENERATED,
SECONDARY from big_table order by 2, 3, 4

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch   122254     96.46     651.36     321018      22002       1269     1833792
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   122256     96.46     651.36     321018      22002       1269     1833792

Rows     Row Source Operation
-------  ---------------------------------------------------
1833792  SORT ORDER BY
1833792   TABLE ACCESS FULL BIG_TABLE

for all intents and purposes -- those two queres are the "same"
May 05, 2003 khalid -- Thanks for the question regarding
      "Table Difinition", version 8.1.7

      You Asked
Hi Tom
I have a table how can i obtain the difinition of that table "create statment
that create it"

Thanks

      and we said...
exp userid=u/p tables=that_table rows=n
imp userid=u/p full=y show=y

it'll be on your screen.

in 9i, it'll be

scott@ORA9I.WORLD>  select dbms_metadata.get_ddl( 'TABLE', 'EMP' ) from dual
  2  /

DBMS_METADATA.GET_DDL('TABLE','EMP')
--------------------------------------------------------------------------------

  CREATE TABLE "SCOTT"."EMP"
   (    "EMPNO" NUMBER(4,0) NOT NULL ENABLE,
        "ENAME" VARCHAR2(10),
        "JOB" VARCHAR2(9),
        "MGR" NUMBER(4,0),
        "HIREDATE" DATE,
        "SAL" NUMBER(7,2),
        "COMM" NUMBER(7,2),
        "DEPTNO" NUMBER(2,0),
         CONSTRAINT "EMP_PK" PRIMARY KEY ("EMPNO")
  USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255
  STORAGE(INITIAL 524288 NEXT 524288 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCRE
ASE 0
  FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "USERS"  ENABLE,

         CONSTRAINT "EMP_FK_DEPT" FOREIGN KEY ("DEPTNO")
  REFERENCES "SCOTT"."DEPT" ("DEPTNO") ENABLE NOVALIDATE,
         CONSTRAINT "EMP_FK_EMP" FOREIGN KEY ("MGR")
  REFERENCES "SCOTT"."EMP" ("EMPNO") ENABLE NOVALIDATE
   ) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 LOGGING
  STORAGE(INITIAL 524288 NEXT 524288 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCRE
ASE 0
  FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "USERS"

Review &
      Followup

      Rating:
      4
dbms_metadata  September 16, 2002

Reviewer:  Andrew  from ca, usa
Now there's a long overdue package!

      Rating:
      5
  September 27, 2002
Reviewer:
      A reader
Tom,

check this

SQL> drop table BIG_TABLE;

Table dropped.

SQL> create table BIG_TABLE compress as select * from all_objects;
 --------

Table created.

--Extract defination by DBMS_METADATA

SQL> select replace(dbms_metadata.GET_DDL('TABLE','BIG_TABLE'),'"',' ') xx from
dual;

XX
--------------------------------------------------------------------------------

  CREATE TABLE  UPATEGV . BIG_TABLE
   (     OWNER  VARCHAR2(30) NOT NULL ENABLE,
         OBJECT_NAME  VARCHAR2(30) NOT NULL ENABLE,
         SUBOBJECT_NAME  VARCHAR2(30),
         OBJECT_ID  NUMBER NOT NULL ENABLE,
         DATA_OBJECT_ID  NUMBER,
         OBJECT_TYPE  VARCHAR2(18),
         CREATED  DATE NOT NULL ENABLE,
         LAST_DDL_TIME  DATE NOT NULL ENABLE,
         TIMESTAMP  VARCHAR2(19),
         STATUS  VARCHAR2(7),
         TEMPORARY  VARCHAR2(1),
         GENERATED  VARCHAR2(1),
         SECONDARY  VARCHAR2(1)
   ) PCTFREE 0 PCTUSED 40 INITRANS 1 MAXTRANS 255 COMPRESS LOGGING
  STORAGE(INITIAL 1048576 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
  TABLESPACE  USERS

--No COMPRESS word in Definition

Any comments.

Thanks,

          Followup:

really:

  ) PCTFREE 0 PCTUSED 40 INITRANS 1 MAXTRANS 255 COMPRESS LOGGING
  STORAGE(INITIAL 1048576 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
  TABLESPACE  USERS

      Rating: 5
Compressed
      table limitations?  May 02, 2003
Reviewer:
      Christo Kutrovsky  from Ottawa, ON Canada
Tom,

SQL> create table bsd (a number) compress;

Table created

SQL> alter table bsd add (b number);

alter table bsd add (b number)

ORA-22856: cannot add columns to object tables

I did not found such a limitation of compressed tables in the documentation. Is
this a BUG ? (Oracle 9.2.0.0 on linux)

Same thing if the table is partitioned:
SQL> create table bsd2 (a number)
  2  partition by range (a)
  3  (partition a values less than (10), partition b values less than (maxvalue)
compress)
  4  ;

Table created

SQL> alter table bsd2 add b number;

alter table bsd2 add b number

ORA-22856: cannot add columns to object tables

HOWEVER!!!

if you add the compression AFTER creation it's fine:
SQL> create table bsd2 (a number)
  2  partition by range (a)
  3  (partition a values less than (10), partition b values less than (maxvalue)
)
  4  ;

Table created

SQL> alter table bsd2 modify partition b compress;

Table altered

SQL> alter table bsd2 add b number;

Table altered

Sounds to me like a BUG ?

          Followup:

there is in progress work to remove this restriction

      Rating: 3
Just out of
      curiousity...  May 04, 2003
Reviewer:  Christo
      Kutrovsky  from Ottawa, ON Canada
Why is this limitation not present when it's only on a single partition?

Is this going to be treated as a bug or as extention?

          Followup:

if you feel it is a bug, please contact support and file one.  Not sure why it
works, it probably "shouldn't"

May 05, 2003 Kelly
      -- Thanks for the question regarding "truncate vs delete with constraints
      on tables", version 7.3.2

      You Asked
Tom,
I haven't used constraints as much as this new project that
I am on.
Usually I truncate tables to clear them out as
it frees up the space etc.
Yet when I tried to truncate the tables in RF order,
I received constraint errors and it took a long time
to come back with the error.
I then went through in the same order and used
delete from tableA. This went much quicker
and I didn't receive an error.
Do you know, why I received this behavior?

Thanks

      and we said...
You cannot truncate objects that have child tables, you would
have to first drop the foriegn key and then truncate the parent
table and then put the foriegn key back in place.

I have a script I use to list the create foriegn key statements
for a given parent.  You would run it as such (assuming you
wanted to truncate DEPT)

scott@8i> @cons dept

FKEY
----------------------------------------------------------
alter table "EMP"
add constraint "EMP_FK_TO_DEPT"
foreign key ( "DEPTNO" )
references "DEPT" ( "DEPTNO");

You would then DROP the constraint EMP_FK_TO_DEPT, truncate dept
and then run the above command to put the fkey back.  It should
be fast since the EMP table must be emptied before you can empty
the dept table anyway (so rebuilding the constraint is
instantaneous).

column fkey format a80 word_wrapped
select
'alter table "' || child_tname || '"' || chr(10) ||
'add constraint "' || child_cons_name || '"' || chr(10) ||
'foreign key ( ' || child_columns || ' ) ' || chr(10) ||
'references "' || parent_tname || '" ( ' || parent_columns || ')
;' fkey
from
( select a.table_name child_tname, a.constraint_name child_cons_
name,
         b.r_constraint_name parent_cons_name,
         max(decode(position, 1,     '"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position, 2,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position, 3,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position, 4,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position, 5,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position, 6,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position, 7,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position, 8,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position, 9,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position,10,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position,11,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position,12,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position,13,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position,14,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position,15,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position,16,', '||'"'||
                substr(column_name,1,30)||'"',NULL))
            child_columns
    from user_cons_columns a, user_constraints b
   where a.constraint_name = b.constraint_name
     and b.constraint_type = 'R'
   group by a.table_name, a.constraint_name, b.r_constraint_name
 ) child,
( select a.constraint_name parent_cons_name, a.table_name parent
_tname,
         max(decode(position, 1,     '"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position, 2,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position, 3,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position, 4,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position, 5,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position, 6,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position, 7,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position, 8,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position, 9,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position,10,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position,11,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position,12,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position,13,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position,14,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position,15,', '||'"'||
                substr(column_name,1,30)||'"',NULL)) ||
         max(decode(position,16,', '||'"'||
                substr(column_name,1,30)||'"',NULL))
            parent_columns
    from user_cons_columns a, user_constraints b
   where a.constraint_name = b.constraint_name
     and b.constraint_type in ( 'P', 'U' )
   group by a.table_name, a.constraint_name ) parent
where child.parent_cons_name = parent.parent_cons_name
  and parent.parent_tname = upper('&1')
/

Review &
      Followup

      Rating:
      5
  September 25, 2001
Reviewer:
      A reader
I save the script as dropandaddcons.sql and

SQL> @DROPANDADDCONS DEPT
old  87:   and parent.parent_tname = upper('&1')
new  87:   and parent.parent_tname = upper('DEPT')

no rows selected

SQL> /
old  87:   and parent.parent_tname = upper('&1')
new  87:   and parent.parent_tname = upper('DEPT')

no rows selected

SQL> /
old  87:   and parent.parent_tname = upper('&1')
new  87:   and parent.parent_tname = upper('DEPT')

no rows selected

SQL> /
old  87:   and parent.parent_tname = upper('&1')
new  87:   and parent.parent_tname = upper('DEPT')

no rows selected

Firstly, Iam not getting any output as you are getting,
and secondly

when I give @dropandaddcons at the sqlplus prompt, I expect it to prompt me to
enter a table name, but that is not happening, why

I dont want be prompted for a variable every time I execute the script , I dont
want to pass it like @scriptname <tablename>

          Followup:

Silly question for you -- does your emp and dept table have declaritive RI?  I
added it to mine, did you to yours?

It is not prompting you because you passed DEPT in on the command line there --
&1 is the first parameter.  You can add:

undefine 1

to the end of the script to make it prompt you each time.

      Rating:
      4
Reader  September 25, 2001
Reviewer:
      Reader  from USA
<quote>
alter table "EMP"
add constraint "EMP_FK_TO_DEPT"
foreign key ( "DEPTNO" )
references "DEPT" ( "DEPTNO");
<quote>

Is this not true that in this case, DEPT is the parent
table and EMP is the child table. If you truncate
DEPT first, will it not produce, orphan child rows
in EMP table.

Thanks.

          Followup:

It is true that DEPT is the parent key and EMP is the child.

If you truncate DEPT with this constraint in place -- the truncate will FAIL
(not proceed).  The whole purpose of this script was to generate all of the
ALTER commands to put the constraints BACK on th dept table -- so you CAN drop
them and truncate tables.  If upon putting the constraint back on you get an
error -- it means you missed a table to truncate and need to truncate it.

At no time will you have orphan rows.  You might not have a parent/child
relationship anymore if you truncate DEPT and have rows in EMP -- but the
constraint will not be in place (hence no orphans).

      Rating: 3
Q-why not just
      disable the FK's  May 04, 2003
Reviewer:
      A reader
Why do u have to drop them ?

          Followup:

you can disable them as well.

May 05, 2003 Keith
      -- Thanks for the question regarding "Sequence", version Oracle 8i 8.1.7

      You Asked
Tom,

There is a schema A used by user a and schema B used by user b. I am logged on
as user b. I have the following insert. The Contact table belongs to user b and
is in schema B. User b created the contact_sequence. User b has select
privileges on the Event_hdr and Ticket_hdr tables owned by user a and in schema
A. User a has select privileges on the contact sequence.

insert into Contact (contact_id,f_name,address,email)
   select contact_sequence.NEXTVAL,c_holder,c_add,emailid
   from A.Ticket_hdr
   where event_id in (
      select event_id
      from A.Event_hdr
      where venue_id = 21)
   order by ticket_id;

I get an ora- error saying I can't use the sequence. I don't understand why.
Sorry, I do not have the specific error but I am at home, error message is at
work. If I had kwown I'd actually get through to you (seems like months), I
would have brought it home with me. I know the solution in the Oracle docs
mentions incorrect syntax. Your help would be greatly appreciated. Thanks.

Keith Cutler

p.s.  It seems there may be a need for more than one of you.

      and we said...
It is the order by messing it up:

ops$tkyte@ORA8I.WORLD> create table t ( x int, y int );

Table created.

ops$tkyte@ORA8I.WORLD> create sequence my_seq;

Sequence created.

ops$tkyte@ORA8I.WORLD> insert into t ( x,y )
  2  select my_seq.nextval, rownum from dual;

1 row created.

ops$tkyte@ORA8I.WORLD> insert into t ( x,y )
  2  select my_seq.nextval, rownum from dual order by 2;
select my_seq.nextval, rownum from dual order by 2
              *
ERROR at line 2:
ORA-02287: sequence number not allowed here

Get rid of the order by.  Or hide it in an inline view:

ops$tkyte@ORA8I.WORLD> insert into t ( x,y )
  2  select my_seq.nextval, r
  3  from ( select rownum r from dual order by 1 );

1 row created.

Review &
      Followup

      Rating:
      4
  April 10, 2001
Reviewer:  Vipul
       from Boston, MA USA

      Rating: 4
group by as
      well?  May 05, 2003
Reviewer:  Scott Wesley
       from Perth, Western Australia
G'day

I presume this goes for group by statements as well?

The concept you presented for order by also corrected my 2287 problem.

Is there a particular reason why nextval/currval is not allowed in this context?

I also noticed in another post

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6915127515933
you could use both the values () clause as well as a select statement - ie
insert into w (a,b) values (x) select y from z
Is this a 9i specific feature? (I've got 8174)
Because this may also solve the problem.

Thanks

          Followup:

I presume it is to remove the amiguities that could arise from the indeterminate
nature of ORDER BY.

That syntax, a multi-table insert -- is new with 9i.

May 05, 2003 Krishna -- Thanks for the question regarding "Sql
      order", version 817

      You Asked
Tom,
SQL> select * from t where SUBSTR(SSN,6)=1234
  2  and to_char(birthdate,'yyyymm')='197505'
  3  and zip=40335
  4
SQL> /
and zip=48335
    *
ERROR at line 3:
ORA-01722: invalid number

But
select * from t where SUBSTR(SSN,6)=1234
and zip=40335 and to_char(birthdate,'yyyymm')='197505'

Works

Please explain.
Thanks
Krishna

      and we said...
you are relying on the order of evaluation of a predicate.

You have stored arbitrary character strings in ZIP.  ZIP is not a number field
in the database, it is a string.

Some of those strings are NOT NUMBERS.

if we do the predicate "zip = NUMBER" and then "birthdate = " -- you are hitting
a zip that is not convertable to a number -- this is happening for some record
where the birthdate is not may of 1975.

The proper way to write this query would be:

select * from t
  where substr( ssn, 6 ) = '1234'  /* compare STRINGS to STRINGS */
    and trunc(birthdate,'mm') = to_date( '05-1975','mm-yyyy')
                         /* DATES to DATES */
    and zip = '48335'  /* strings to strings */

Review &
      Followup

      Rating: 4
Predicates
      order.  May 04, 2003
Reviewer:  Sai
       from Palo Alto, CA
Hi Tom,

Does it mean that all predicates in where clause are evaluated from "Right" to
"Left", except when we have Index created on some of those predicate columns?

Does "Explain Plan" show the order of predicate evaluation?

Thanks

          Followup:

it means they are evaluated in any order we choose, they are purposely
"indeterminate" in their order of evaluation and you should NEVER rely on them
happening in any order.

The optimizer will reorder them at will -- day to day if it so chooses.

      Rating: 4
could you
      please elaborate on this   May 05, 2003
Reviewer:
      reader
Hi Tom,
       Could you please elaborate on this further I am unable to get it
"if we do the predicate "zip = NUMBER" and then "birthdate = " -- you are
hitting a zip that is not convertable to a number -- this is happening for some
record where the birthdate is not may of 1975."

I tried having two records in the table both with birthdate in may 1975 the
table structure is as follows
Name      Type
--------- ---------------
SSN      VARCHAR2(10)
BIRTH    DATE
ZIP      VARCHAR2(10)

and records are as follows

SSN        BIRTH     ZIP
---------- --------- ----------
1234567890 01-MAY-75 40335
1111111111 01-MAY-75 a20001

          Followup:

add SSN to the mix.  did substr(ssn,...) = get evaluated before or after the
implicit to_number(zip).

you have no control over it -- so do it the way I demonstrated -- where
regardless of the evaluation order, you

a) get the right answer
b) avoid implicit conversions
c) do not hit the ora error message.

May 05, 2003 --
      Thanks for the question regarding "What is SORT(rows) ?", version 7.3.3

      You Asked
What is SORT(rows) ?
We have large values of sort(rows) from v$sysstat.
Does that imply a lot of queries are sorting a large number of
rows?
STATISTIC#    NAME    CLASS    VALUE
----------       ----    -----    -----
139    sorts (memory)    64    144840
140    sorts (disk)    64    12
141    sorts (rows)    64    5444699

Thanks.

      and we said...
It is the cumulative number of rows sorted in your database
since it was started.

5,444,699 -- does that number by itself imply a lot of queries
are sorting a large number of rows?  No -- it does not.

5.5 million rows is not that large in the grand scheme of
things.

that is 137,500 queries sorting 40 rows each for example (you
have 144,852 queries doing your sorting -- they are sorting
small numbers of rows).  If my database has been up for a week,
that is less then 20k queries sorting 40 rows each day.  Or 40
queries per minute sorting 40 rows.

If my database was up 2 weeks, cut it in half.  and so on.

A number, taken in isolation, cannot tell you very much.  That
could be the result of a single query (doubtful but it could be)
or hundreds of thousands of queries.

Don't forget, UNION, DISTINCT, GROUP BY, and many operations
sort stuff.

Here is my number:

182 sorts (rows)        64 1,044,640,154

but if I look at the number of sorts performed:

ops$tkyte@ORA8I.WORLD> select 1044640154/32619117 from dual;

1044640154/32619117
-------------------
         32.0253965

I'm at about the same level as you are (my database was just up
longer probably).

Doesn't look like anything to worry about.

Review &
      Followup

      Rating: 5
SORT without
      any sort operation  May 05, 2003
Reviewer:
      Saminathan  from CA,USA
Dear Tom,

Could you please explain/provide the link to understand why the following SQL
statement(delete T2) have SORTS(4  sorts (memory)) in their statistics. I don't
see any
sort operations like
n SORT UNIQUE
n SORT AGGREGATE
n SORT GROUP BY
n SORT JOIN
n SORT ORDER BY

in the execution plan.

SQL> create table t2 as select * from sys.dba_objects;
Table Created.

SQL> delete t2;
38085 rows deleted.
Execution Plan
----------------------------------------------------------
   0      DELETE STATEMENT Optimizer=CHOOSE
   1    0   DELETE OF 'T2'
   2    1     TABLE ACCESS (FULL) OF 'T2'

Statistics
----------------------------------------------------------
         49  recursive calls
      78607  db block gets
      38168  consistent gets
      37804  physical reads
   15860656  redo size
        629  bytes sent via SQL*Net to client
        505  bytes received via SQL*Net from client
          6  SQL*Net roundtrips to/from client
          4  sorts (memory)
          0  sorts (disk)
      38085  rows processed

Tom, Expecting your valuable comments as always.

          Followup:

49 recursive calls -- a delete against a newly created table....

we had a hard parse here (the table didn't exist, the delete was hard parsed).
You had alot of recursive sql (queries run on your behalf in order to parse the
query in this case).  They "sorted" and since recursive sql is considered part
of your statement -- it counts against "you"

      Rating: 5
SQL*Net round
      trip  May 05, 2003
Reviewer:  Saminathan
       from CA,USA
Tom, Also how to reduce  SQL*Net round trip in JAVA APPLICATIONS like
JDBC(similar to arraysize in SQL*Plus)

          Followup:

set your prefetch attribute.

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/java.920/a96654/oraperf.htm#1002425

      Rating: 5
Tom, Thank you
      so much  May 05, 2003
Reviewer:  Sami
       from CA,USA

May 05, 2003 Yogesh -- Thanks for the question regarding "IN
      & function", version 8.1.7

      You Asked
I have written following function to  return a csv list like if I pass abcd it
will return 'a','ab','abc','abcd'.

CREATE OR REPLACE FUNCTION COMMA_SEP_STRING (I_STRING IN VARCHAR2)
RETURN VARCHAR2
IS
V_LENGTH   NUMBER:=0;
V_STRING   VARCHAR2(20);
O_STRING   VARCHAR2(4000);
BEGIN
V_LENGTH := LENGTH(I_STRING);
FOR I IN 1..V_LENGTH
LOOP
V_STRING := CHR(39)||SUBSTR(I_STRING, 1,I)||CHR(39);
IF I=1 THEN
O_STRING := V_STRING;
ELSE
O_STRING := O_STRING||','||V_STRING;
END IF;
END LOOP;
O_STRING := SUBSTR(O_STRING, 1, LENGTH(O_STRING));
--RETURN '('||O_STRING||')';
RETURN O_STRING;
EXCEPTION
WHEN OTHERS THEN
RETURN '';
END;
/

But when I use this function in a query like

select * from test where v1 in (select COMMA_SEP_STRING('10') from dual); it
does not retun me any thing ... can u please tell me where i'm going wrong ....

      and we said...
you need to return a COLLECTION -- a SET.

as it is, your query is no different then this:

select * from test
where v1 in ( '''1'',''10''' );

there '1','10' is a single string no different then 'Hello World' is a single
string.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:110612348061

May 05, 2003 vipin -- Thanks for the question regarding
      "Search space in a string", version 8.1.7

      You Asked
Hi Tom,
I have been struggling to write a sql to count the spaces in a string.
for example ,'this is not a good book'
i want to write a sql which can tell me the total no. of spaces in the string.
in this case 5 is total no. of spaces.

The string passed to the program would be dynamic.
also is it possible to break this string in words writing a sql.

Currently i am checking the maximum lenght of the string and then counting the
word in it and then using instr i am breaking the string into words. but even in
that i am facing a problem because the last word is not displayed as there is no
space after the last word and i search for space only.

p_addr is the string i pass.

SELECT Substr(P_Addr,1,INSTR(P_Addr,' ',1 ,1)-SELECT
Substr(P_Addr,1,INSTR(P_Addr,' ',1 ,1)-1) COL
FROM DUAL
UNION ALL
SELECT substr(P_Addr,INSTR(P_Addr,' ',1 ,1)+1,iNSTR(P_Addr,' ',1
,2)-(INSTR(P_Addr,' ',1 ,1)+1)) COL
FROM DUAL
UNION ALL
SELECT substr(P_Addr,INSTR(P_Addr,' ',1 ,2)+1,iNSTR(P_Addr,' ',1
,3)-(INSTR(P_Addr,' ',1 ,2)+1)) COL
FROM DUAL
UNION ALL
SELECT        substr(P_Addr,INSTR(P_Addr,' ',1 ,3)+1,iNSTR(P_Addr,' ',1
,4)-(INSTR(P_Addr,' ',1 ,3)+1)) COL
FROM DUAL
UNION ALL
SELECT        substr(P_Addr,INSTR(P_Addr,' ',1 ,4)+1,iNSTR(P_Addr,' ',1
,5)-(INSTR(P_Addr,' ',1 ,4)+1)) COL
FROM DUAL

Thanks and regards

      and we said...
ops$tkyte@ORA920> variable x varchar2(40)
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec :x := 'this is not a good book'

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select length(:x)-length(replace(:x,' ','')) from dual;

LENGTH(:X)-LENGTH(REPLACE(:X,'',''))
------------------------------------
                                   5

and see

http://asktom.oracle.com/~tkyte/SkipCols/index.html
for my delimited package to break strings up into "words"
Review &
      Followup

      Rating: 5
Useful
      concept  May 05, 2003
Reviewer:  Robert
       from Memphis, USA

      Rating:
      5
  May 05, 2003
Reviewer:
      A reader

      Rating: 5
Search
      Spaces  May 05, 2003
Reviewer:  vipin
       from singapore
Thanks tom.
The answer was very helpful.
Thanks for your support.
Rgds

      Rating: 4
simple things
      you do make us wonder  May 06, 2003
Reviewer:
      reader

      Rating:
      5
  September 10, 2003
Reviewer:
      Eugene  from USA

May 05, 2003 BALA -- Thanks for the question regarding "How to
      use member functions and procedures?", version 8.1.6

      You Asked
Dear sir,
    Give one small example about member functions and
procedures.
How to use,create and execute.

Thanking you sir,
balu

      and we said...
To create:

tkyte@ORACLE8I> create type foo as object
  2  ( x    number,
  3    y        number,
  4
  5    member function dist_from_orgin return number
  6  )
  7  /

Type created.

tkyte@ORACLE8I>
tkyte@ORACLE8I> create type body foo
  2  as
  3    member function dist_from_orgin return number
  4    is
  5    begin
  6       return sqrt( x*x+y*y );
  7    end;
  8  end;
  9  /

Type body created.

To use in PLSQL -- as a way to extend the set of datatypes
available in that language:

tkyte@ORACLE8I> declare
  2      my_variable    foo;
  3  begin
  4      my_variable := foo( 1, 2 );
  5
  6      dbms_output.put_line( my_variable.dist_from_orgin() );
  7  end;
  8  /
2.23606797749978969640917366873127623544

PL/SQL procedure successfully completed.

and as a way to use to extend the set of persistent SQL
types:

tkyte@ORACLE8I> create table t ( pt   foo );

Table created.

tkyte@ORACLE8I>
tkyte@ORACLE8I>
tkyte@ORACLE8I> insert into t values ( foo(2,5) );

1 row created.

tkyte@ORACLE8I>
tkyte@ORACLE8I> column data format a10
tkyte@ORACLE8I> select t.pt data, t.pt.dist_from_orgin() from t
t
  2  /

DATA(X, Y) T.PT.DIST_FROM_ORGIN()
---------- ----------------------
FOO(2, 5)              5.38516481
Review &
      Followup

      Rating: 3
Returns
      error  December 04, 2001
Reviewer:  vinci
       from USA
The last statement returns error for me:
Here it is:-

SQL> select t.pt data, t.pt.dist_from_orgin() from t ;
select t.pt data, t.pt.dist_from_orgin() from t
                       *
ERROR at line 1:
ORA-00904: invalid column name

Im running 8.1.7 in windows 2000

          Followup:

you didn't type in what I had:

tkyte@ORACLE8I> column data format a10
tkyte@ORACLE8I> select t.pt data, t.pt.dist_from_orgin() from t t
  2  /

you need to use a correlation name with objects -- "from t t" -- not "from t"

      Rating:
      4
  December 04, 2001
Reviewer:  vinci

Thanks tom..it worked.That was fast...u r not overloaded r u??(kidding)
I hav another query:
In the member function if I pass a parameter & write code to check a condition
and return 1 or 0, will this be faster than directly retreiving the column? Here
is an example-

create type mytype as object
( ind1  varchar2(2),
  ind2  varchar2(2)
  member function check1(myvar IN number) return number
)

create type body mytype as
member function check1(myvar IN number) return number
is
b varchar2(2);
begin
select t.col1.mytype.ind1 into b from t t where col2 = myvar;
if b='A' then
return 1;
else
return 0;
end if;
end;
end;

create table t ( col1  mytype, col2 number,col3..col4....etc);

insert into t (col1,col2) values (mytype('A','B'),5);
insert into t (col1,col2) values (mytype('A','B'),6);
insert into t (col1,col2) values (mytype('A','C'),7);

Now from the front end(JSP) if  link1  is clicked, i pass the value  'myvar'
(say 5) to my member function which will query my table and return 1 . If it is
1 i will query the same table to get more columns to be displayed on the
screen.If it is 0  i will  query with some lesser cols .

Now my question is - If this is possible then,
1. How do i call the member function from the front end (the syntax...)?
2. Will this approach be faster than directly querying the col1.ind1 column.
Meaning ..i just say this in the front end-
select t.col1.ind1 into variable from t t where col2 = myvar;
If variable = 'A' then
  select col4,col5.....
else
null;
end if;
3.Is there anyother way to do this to reduce query time.
  A direct sql statement will be faster than a function /stored proc right?
4.Also how do i estimate the table size if i use abstract datatypes? Is it more
or less?
In the above table if i had seperate cols for ind1 and ind2 instead of an object
then what wud be the difference in table size?

          Followup:

you would just get the attribute yourself, your front end should query:

select t.col1.mytype.ind1 from t t where col2 = :myvar;

the reason is twofold:

1) it'll be faster
2) you need an object instance upon which to invoke the method.  That means you
ALREADY retrieved the object in order to invoke the method!  You cannot invoke
the method until you have an instance.  Its a chicken and egg (your method would
not work unless it was a static method in which case it could just as well be a
plsql function)

Just use the query.

To reduce runtime use a ref cursor and execute the following block of code:

  declare
      type rc is ref cursor;
      l_data  varchar2(25);
      l_query long;
  begin
      select t.col1.ind1 into l_data from t t where col2 = ?;
      if ( l_data = 'A' )
      then
          l_query := 'select .......';
      else
          l_query := 'select * from dual where 1=0';
      end if;
      open ? for l_query;
   end;

(better yet, make that a stored procedure).  Bind and execute that from java and
you'll get your result set in one round trip....

If you estimate the tablesize in the same way I do -- you do it the same way as
for anything else.  I estimate by loading up some percentage (say from 1/10 to
10% depending on the number of estimated rows) of the table with good sample
data and using analyze to see how big it is.  Then, I multiply.  With the
'hidden' columns that come with object types -- this is the only way to go.

      Rating:
      5
  December 04, 2001
Reviewer:
      A reader
Hi Tom

In which circumstance we should use this functinality.
It is looking complacted.

          Followup:

what part looks complicated?  once you get over the syntax (all new syntax looks
complicated at first) its very straight forward.

I use objects in my code all of the time.  I tend not to use them as a
persistent storage mechanism (call me old fashioned).

      Rating: 2
And what about
      "setter" in OO : pb when updating obj attr !  March 06, 2003

Reviewer:  Yannick  from FRANCE
Tom i've got a BIG pb about OO with Oracle :
Check this out with Oracle 9i 9.0.2 :

Create or replace TYPE TYPE_A as object
(
value_ NUMBER,
MEMBER FUNCTION setValue(v_value NUMBER) RETURN TYPE_A
) INSTANTIABLE NOT FINAL;
/

create or replace TYPE BODY TYPE_A IS
member function setValue(v_value NUMBER) RETURN TYPE_A IS
v_inst TYPE_A := SELF;
v_old_inst TYPE_A := SELF;
begin
 if v_old_inst.value_ != v_value then
  v_inst.value_ := v_value;
dbms_output.put_line('setValue executed');
 end if;
 return v_inst;
end;

END;
/

create table TEST (ATTR_ TYPE_A);

insert into TEST values(TYPE_A(55));

// This line writes "setValue executed" 3 times, why !!??

update TEST a set ATTR_ = a.ATTR_.setValue(56);

the setValue method will be a great functionnality to keep data coherence, and i
would not just use SET ATTR_.value_ = xxx

Any idea please ?

          Followup:

you realize that'll always rewrite the ENTIRE row and be about the least
efficient method to accomplish this seemingly simple task?

Don't assume procedural principles when it comes to relational set operations.
I don't know why it calls it three times but -- in the end, it is something upon
which you have no control..

      Rating: 3
is there is
      diff. between procedure and function  May 05, 2003

Reviewer:  joachim mayer  from styria - austria -
      europe
hi tom,

thx for the nice example. i was playing around a little bit with your foo type
and i ran into a problem i do not understand and i cant find an explantaion ...

here is my ( your ) example...

SQL> create type foo as object(
  2     x    number,
  3     y        number,
  4
  5    member function dist_from_orgin return number,
  6
  7    member procedure set_x (  in_x in number ),
  8
  9    member function set_y ( in_y in number ) return number
 10
 11  )
 12  /

Type created.

SQL>
SQL> create type body foo
  2    as
  3      member function dist_from_orgin return number
  4     is
  5     begin
  6         return sqrt( x*x+y*y );
  7     end;
  8
  9     member procedure set_x ( in_x in number )
 10    is
 11    begin
 12         x := in_x;
 13    end;
 14
 15     member function set_y ( in_y in number)
 16     return number
 17     is
 18     begin
 19
 20       y := in_y;
 21
 22       return y;
 23     end;
 24
 25   end;
 26  /

Warning: Type Body created with compilation errors.

SQL> show error
Errors for TYPE BODY FOO:

LINE/COL ERROR
-------- -----------------------------------------------------------------
20/6     PLS-00363: expression 'SELF.Y' cannot be used as an assignment
         target

20/6     PL/SQL: Statement ignored

i added a new function and a procedure to set the x and
y values... but for some reason i can not assign a value to a attriptute in a
function. why does it not work ... and is
there a work around or do i have to move to procedures using out-parameters. ( i
tried this also and it resulted in no error ).
i do not understand the difference behavioure of a member function and a memeber
procedure. why is the assigment in
the procedure fine but an error in the function?

thx

joachim "hannibal" mayer

p.s.: when will your new book come out?

h.

          Followup:

to each non-static member procedure/function there is an implicit parameter
SELF.  By default for procedures, self it passed IN OUT.  For functions -- only
as IN -- this is to maximize your ability to call that function from SQL.  You
cannot call a function with out parameters from SQL so we default it to IN.

Here is how to do what you need:

ops$tkyte@ORA920> create type foo as object(
  2     x    number,
  3     y        number,
  4
  5    member function dist_from_orgin return number,
  6
  7    member procedure set_x (  in_x in number ),
  8
  9    member function set_y ( SELF in out foo, in_y in number ) return
number
 10
 11  )
 12  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create type body foo
  2    as
  3      member function dist_from_orgin return number
  4     is
  5     begin
  6         return sqrt( x*x+y*y );
  7     end;
  8
  9     member procedure set_x ( in_x in number )
 10    is
 11    begin
 12         x := in_x;
 13    end;
 14
 15     member function set_y ( SELF in out foo, in_y in number)
 16     return number
 17     is
 18     begin
 19
 20       y := in_y;
 21
 22       return y;
 23     end;
 24
 25   end;
 26  /

Type body created.

August'ish for the book

May 05, 2003 Venkatasubramanian -- Thanks for the question
      regarding "sql loader", version 8.1.7

      You Asked
Tom,

I find your book very useful. I tried many of your examples given in your book.
The examples are very much meaningful and educative. When I tried your example
(page no. 398.399) on sql loader, I faced a problem.

Table:

SQL> desc dept
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 DNO                                       NOT NULL NUMBER
 DNAME                                              VARCHAR2(20)
 LOC                                                VARCHAR2(20)
 DOM                                                DATE
 COMMENTS                                           VARCHAR2(4000)

Control file:

LOAD DATA
INFILE demo20.dat "str X'7C0D0A'"
INTO TABLE DEPT
REPLACE
FIELDS TERMINATED BY ','
TRAILING NULLCOLS
(DNO,
  DNAME        "upper(:dname)",
  LOC          "upper(:loc)",
  DOM "my_to_date(:DOM )",
  COMMENTS
)

Datafile:

10,Sales,Virginia,01-april-2001,This is the Sales
Office in Virginia|
20,Accounting,Virginia,13/04/2001,This is the Accounting
Office in Virginia|
30,Consulting,Virginia,14/04/2001 12:02:02,This is the Consulting
Office in Virginia|
40,Finance,Virginia,987268297,This is the Finance
Office in Virginia|

In windows platform, the data gets loaded perfectly whereas in unix platform,
only one record gets loaded as shown below:

/u01/admin/workarea>sqlldr sitadmin/cygnus loader.ctl

SQL*Loader: Release 8.1.7.0.0 - Production on Fri Jul 26 15:55:46 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Commit point reached - logical record count 1

Logfile:

SQL*Loader: Release 8.1.7.0.0 - Production on Fri Jul 26 15:55:46 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Control File:   loader.ctl
Data File:demo20.dat
  File processing option string: "str X'7C0D0A'"
  Bad File:     demo20.bad
  Discard File:  none specified

 (Allow all discards)

Number to load: ALL
Number to skip: 0
Errors allowed: 50
Bind array:     64 rows, maximum of 65536 bytes
Continuation:    none specified
Path used:Conventional

Table DEPT, loaded from every logical record.
Insert option in effect for this table: REPLACE
TRAILING NULLCOLS option in effect

   Column Name                  Position   Len  Term Encl Datatype
------------------------------ ---------- ----- ---- ---- ---------------------
DNO                                 FIRST     *   ,       CHARACTER
DNAME                                NEXT     *   ,       CHARACTER
    SQL string for column : "upper(:dname)"
LOC                                  NEXT     *   ,       CHARACTER
    SQL string for column : "upper(:loc)"
DOM                                  NEXT     *   ,       CHARACTER
    SQL string for column : "my_to_date(:DOM )"
COMMENTS                             NEXT     *   ,       CHARACTER

Table DEPT:
  1 Row successfully loaded.
  0 Rows not loaded due to data errors.
  0 Rows not loaded because all WHEN clauses were failed.
  0 Rows not loaded because all fields were null.

Space allocated for bind array:                  64500 bytes(50 rows)
Space allocated for memory besides bind array:        0 bytes
Total logical records skipped:          0
Total logical records read:             1
Total logical records rejected:         0
Total logical records discarded:        0

Run began on Fri Jul 26 15:55:46 2002
Run ended on Fri Jul 26 15:55:46 2002

Elapsed time was:     00:00:00.16
CPU time was:         00:00:00.04

At the backend, the query output is as follows:
SQL> select * from dept;

       DNO DNAME                LOC                  DOM
---------- -------------------- -------------------- ---------
COMMENTS
--------------------------------------------------------------------------------
        10 SALES                VIRGINIA             01-APR-01
This is the Sales
Office in Virginia|
20

Can you tell me where things went wrong? Give details.

Thanks and Regards,
Venkat

      and we said...
end of line on windoze = CARRIAGE RETURN/LINEFEED ( decimal 13/10,   HEX 0D0A)

end of line on Unix is LINEFEED (decimal 10, HEX 0A)

You must have ftp'ed the file in TEXT mode from windows to Unix.  This changed
all 0D0A's to 0A's.  Your STR'xxxxxx' is wrong due to this.

read the second para in "embedded newlines wrap-up" page 399 which discusses
this exactly.
Review & Followup

      Rating: 4
What slows
      sqlldr down ?  January 23, 2003
Reviewer:
      robert  from PA
We sent many fixed length data files to clients.
A new bus. requirement is that we audit what is exactly sent. (the files are <
500 records).
Still on 8i, so I wrote a very generic pl/sql module that basically reads col
info (all char) and builds control at run time, sends sqlldr command to cron
queue and loads tables that are structured based on the data file layout.
I know you kind of advice against this. But it's fix-len string and this is the
best way I could think of to ensure encapsulation, easy use and correct load.
All user has to do is create a table based on the file layout and call the
module.
Basically I call dbms_sleep to slow down pl/sql a little and check for log file
existence to determine end of load.

It has been working just great until the other day this
loading of 268 records took 14 minutes (!)...as you can imagine the log file
showed up and my pl/sql code continues on and SELECT on partially loaded
table....

A couple of hours later I re-ran and load took .2 secs.

So my question is what could cause sqlldr to slow down ? ---
Can it be a bottleneck on the OS or Oracle or both ?
(it can't be a table lock because I'm still in beta with this module and no one
is reading those tables let alone updating/locking them)

Do you know if the log file shows up as soon as sqlldr is
started ?

Boss wants total auditability on data we sent so now I added code to put the
module into an infinite loop scanning the log file for the line "%CPU%TIME%" to
ensure end of load.

Your comment is, as always, most appreciated.

          Followup:

Sounds like

o archives filled up or
o checkpoint not complete (you can look at the alert log to check those, look
  for cannot allocate new log)
o there is a unique index on the table and something else was playing with
  the table -- blocking you (they inserted a row you wanted to)
o the table was locked (is is a child table in a foreign key relationship
without
  indexes on the fkey?)

those are the culprits that pop into my head first...

      Rating: 5
sql
      loader  January 23, 2003
Reviewer:  atul
       from India
Sir,

I have got test.dat file with no further info...
ie i dont have table info etc...

so with only .dat file,can i use sql-loader to load data??

Thanks.
atul

          Followup:

umm, no.  if you don't have a table structure -- what would you be loading it
into.

you need a ctl file to tell sqlldr how to load it into what table.

      Rating: 4
Diff flat
      files  March 21, 2003
Reviewer:  Anirudh
       from N.Delhi, INDIA
Hi Tom,
Please suggest what to do in the following scenerio:
I have a flat file, lets say containing the data for the emp table. There is a
description file(.doc) which comes along with that flat file describing the
positions and lengths of the fields. Its fine that I can use a SQLLDR to load
that data in to the emp table but it nay be the case when some of the fields are
swapped/reshuffled so....in the first flat file I may get
empno,ename,mgr,sal...and in second it may be empno,sal,ename....(ofcourse this
will be stated in the description file everytime the flat file comes). In this
case do I have another alternative except from reading the description file then
dynamically making the ctl file for the loader and then doing the stuff.
I hope I am clear.

          Followup:

you would be creating a control file to load the data -- each time.

seems like "data standards" would be really useful to you

      Rating: 5
.ctl file
        March 21, 2003
Reviewer:  Tom  from
      NJ USA
(Original post) There is a description file(.doc) which comes along with that
flat file describing the
positions and lengths of the fields. ...

(Followup:)You would be creating a control file to load the data -- each time.

If the .doc file is in the SAME format each time, you can parse it with
sophisticated filters (awk/sed, or perl) to produce a control file.  I have seen
this done, it is not pretty, but sometimes you cannot control your data sources.
 I second the part about "data standards", if possible.  Otherwise, you will be
manually creating the .ctl files each time you get a .doc file.

      Rating: 3
Carriage Return
      or Linefeed in data  May 02, 2003
Reviewer:
      Abdul Samed  from Chennai, India
Hi Tom,
We have a datafile in which one the field has carriage return say Address1
(sometime it has 2 lines of data), so while loading through SQL*Loader, it
considers single record as 2 record.
first records whatever before carriage return and
second record whatever after carriage return.

Please let me know how to solve this issue.

Many thanks.

Regards, Aby

          Followup:

well, any reasonable piece of software would consider 2 records to be 2 records
(a newline is the convention for saying "new record coming" after all)

I don't know what issue I'm to solve here.  No example input datafile.  No
example table.  No example result set.

Tell me -- if you gave the file to someone who didn't read english (or whatever
language the input data is in) and had never seen a number before in their life
(so it would look like a random string of characters) would you be able to
describe to them to algorith by which you determine conclusively "this is the
end of the record".  If you can -- add that as well.  If you cannot, I'll
probably not be able to either.

      Rating: 5
LRM-00112
      Error  May 05, 2003
Reviewer:  ramks
       from India
Hi Tom

I have the following Sql Loader Call in my shell script
which is running in sun os 5.8

 sqlldr  userid=${FCP_LOGIN} \
          data="${lc_inboundpath}/${lc_filename} \
          control="${lc_controlfile}" \
          log="${lc_cust_top}/log/${lc_filename}".log
          bad="${lc_cust_top}/log/${lc_filename}".bad

all the variables are set before this call but when i run this, it is giving
error "LRM-00112 Multiple Values Not allowed for Parameter bad"

Can u help in this regard.

thanks in adv

          Followup:

echo that command out

echo sqlldr userid=.......

and see what is it resolving to.  maybe a space in a file name or something like
that.

May 05, 2003 --
      Thanks for the question regarding "order by fn()", version 8.1.7

      You Asked
What do you think about putting a 'order by fn()' on a query? The 'fn()' is a
simple PL/SQL function returning a INTEGER that does a decode and returns a
value to order, sort of like a rank.

Yes, I could just as well do 'order by decode(....)', but that way every
query/app that needs to do this has to repeat the lengthy DECODE and if it needs
to change, all the queries need to change. By putting it in the database and
centralizing it, everyone can benefit.

Any performance considerations with this approach?

Thanks

      and we said...
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1493455952998
i would stick with DECODE if at all possible.

You can hide it in a VIEW

create view v as select ...., decode( .... ) ORDER_BY_ME_PLEASE from ......

and then they select * from v where ... order by order_by_me_please
Review &
      Followup

      Rating:
      5
  May 05, 2003
Reviewer:
      A reader

May 05, 2003 Sanjai -- Thanks for the question regarding
      "trunc (d,fmt)", version 8.1.7

      You Asked
Hi Tom,

Can you please explain the difference between trunc(d,fmt) vs trunc (d). Also
when shoule we use 'DD' vs 'D' format.

scott> select trunc(sysdate,'DD') from dual;

TRUNC(SYS
---------
05-MAY-03

scott> select trunc(sysdate,'D') from dual;

TRUNC(SYS
---------
04-MAY-03

scott>  select trunc(sysdate) from dual;

TRUNC(SYS
---------
05-MAY-03

scott> select trunc(sysdate,'D') from dual;

TRUNC(SYS
---------
04-MAY-03

Thanks for your time.

      and we said...
the default format for trunc is DD

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a85397/function.htm#79763
the formats:

DD = truncate to the DAY
 D = truncate to the first day of the week

fortunately, we document all of the formats:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a85397/function.htm#80101
specifically:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a85397/function.htm#80165

Review &
      Followup

      Rating:
      5
Excellent  May 05, 2003
Reviewer:
      sanjai  from NY USA
Thanks a lot, Tom.

May 05, 2003 John -- Thanks for the question regarding
      "randomly selecting 4 employees ", version 9.2

      You Asked
tom,
i need to randomely select 4 employees from one of our core tables

SQL> desc people
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 TYPE_ID                                   NOT NULL VARCHAR2(1)
 EMP_ID                                    NOT NULL VARCHAR2(10)
 ID                                                 NUMBER(38)
 IMPS_OFFICE_ID                                     VARCHAR2(6)
 REGION                                             VARCHAR2(10)

from the above table i need to randomely select 4 employees(i.e values of the
column "id")
based on some where conditions

where conditions are like we have to choose four employees who belong to a
department and who are active
and who have agreed to be picked up randomely, for this the above table is
joined with some other tables.

1.i see like based on the conditions i get the row count with all the join
condtions

2.then use the same above conditions and use row_number() analytical
function,dbms_random and count found
  in step 1 to get the ids.

is the above correct, or any suggestions and better ways.

would really appreciate your reply

      and we said...
scott@ORA920.US.ORACLE.COM> select *
  2    from ( select empno, ename
  3             from emp
  4                    where ename like '%'
  5                    order by dbms_random.value )
  6   where rownum <= 4
  7  /

     EMPNO ENAME
---------- ----------
      7934 miller
      7902 ford
      7900 james
      7839 king

scott@ORA920.US.ORACLE.COM> /

     EMPNO ENAME
---------- ----------
      7521 ward
      7844 turner
      7788 scott
      7698 blake

scott@ORA920.US.ORACLE.COM> /

     EMPNO ENAME
---------- ----------
      7900 james
      7844 turner
      7566 jones
      7876 adams

scott@ORA920.US.ORACLE.COM>
Review &
      Followup

      Rating: 3
thanks, but it
      is slow  October 21, 2002
Reviewer:  John

thanks for that.

in a department we can have at an average 1000 to 5000 employees.

but i run the query in my db, it takes around 8 seconds
this is the autotrace output

1)

SQL> SELECT * FROM ( SELECT * FROM people ORDER BY dbms_random.VALUE ) WHERE
ROWNUM <= 4

Execution Plan
----------------------------------------------------------
   0|    |SELECT STATEMENT Optimizer=CHOOSE (Cost=1780 Card=4 Bytes=28|
   1|   0|  COUNT (STOPKEY)                                           |
   2|   1|    VIEW (Cost=1780 Card=88663 Bytes=28904138)              |
   3|   2|      SORT (ORDER BY STOPKEY) (Cost=1780 Card=88663 Bytes=10|
   4|   3|        TABLE ACCESS (FULL) OF 'SID_EM_REF' (Cost=157 Card=8|

Statistics
----------------------------------------------------------
         26  recursive calls
          0  db block gets
       1626  consistent gets
       1351  physical reads
          0  redo size
       1978  bytes sent via SQL*Net to client
        311  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
          4  rows processed

but when i do,

2)

SQL> SELECT * FROM (SELECT a.*, ROWNUM rn FROM SID_EM_REF a WHERE ROWNUM <= 562)
WHERE rn = 562

Execution Plan
----------------------------------------------------------
   0|    |SELECT STATEMENT Optimizer=CHOOSE (Cost=157 Card=562 Bytes=1
   1|   0|  VIEW (Cost=157 Card=562 Bytes=190518)
   2|   1|    COUNT (STOPKEY)
   3|   2|      TABLE ACCESS (FULL) OF 'SID_EM_REF' (Cost=157 Card=886

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

so, i think i write a proc and store dbms_random.value in 4 variables and union
all the queries simular to 2 replacing 562 with the 4 variables.

any feedback.

because we are going to put this in home page of site.i think 8 seconds is slow.

          Followup:

generating 5000 random numbers is somewhat CPU intensive.

I've no idea where you were going with that 2cnd query there.  Full scanning but
stopping at row 562 (not completing the scan) versus full scanning an entirely
differently table -- why do you think they would be even remotely the same.

I cannot imagine the usefulness of "showing 4 random records" on a home page.
Perhaps if you explain your actual business requirements and constraints -- we
could come up with something reasonable.

I don't see what storing 4 values of random numbers and using a "union" would do
for you -- I cannot imagine.

      Rating:
      4
  October 21, 2002
Reviewer:  Bhagat
      Singh  from Delhi,India
Tom is there a way around for oracle version 8.1.6 for this please update us on
the same

Bhagat Singh

          Followup:

if dbms_random.value isn't working well for you -- see

http://asktom.oracle.com/~tkyte/Misc/Random.html
there I have a random number generator in PLSQL (works identically to the C
rand() function)

same technique works.

      Rating: 5
Works on
      8i  October 22, 2002
Reviewer:  Dave

That also works on 8i, try it

      Rating: 4
Unique Random
      Values  October 22, 2002
Reviewer:
      A reader
John, ur gonna have to write some code to generate the 4 unique random numbers
that are < count(*) of ur table. Why take the pain... Follow Tom...

      Rating: 5
For original
      poster  October 22, 2002
Reviewer:  Connor
      McDonald  from UK
You might have some joy with the SAMPLE clause to give a
smaller/quicker/"semi-random" set and then apply the dbms_random to that.

hth
connor

          Followup:

good point -- if there are no joins, just a simple single table query, using
sample to get a couple of dozen random observations and then ordering that by
random would be speedier.

The trick would be specifying the right "pct" or "block" value in order to get a
sample *big* enough.  For example in the following, sample(1) gets 1% of 5000 or
about 50.  It might get 0, it might get 100 (very very doubtful BUT the chance
exists, it is a "sample" not a precise "get me 50 rows").  So,  you need to make
sure the sample size is large enough to get at least 4 rows!

ops$tkyte@ORA817DEV.US.ORACLE.COM> /*
DOC>
DOC>drop table t;
DOC>
DOC>create table t as select * from all_objects where rownum <= 5000;
DOC>*/
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set timing on
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select object_name, object_id
  2    from ( select object_name, object_id
  3                     from t
  4                    where object_name like '%%'
  5                    order by dbms_random.random )
  6   where rownum <= 4;

OBJECT_NAME                     OBJECT_ID
------------------------------ ----------
/65ddadec__TimeoutImplBase          31405
/a2efb14b_Manager                   30951
/782c5acd_XSDFactory                44004
/b7b4a7fc_BorderUIResourceBeve       7980

Elapsed: 00:00:00.57
ops$tkyte@ORA817DEV.US.ORACLE.COM> /

OBJECT_NAME                     OBJECT_ID
------------------------------ ----------
/8d664d2f_ObjectBuilder             43951
/ae48c769_DynaHashTest2             14703
/860c1d1e_HandlerRegistryClien      30343
/6fbd66b5_BeanContextServicesS       8840

Elapsed: 00:00:00.56
ops$tkyte@ORA817DEV.US.ORACLE.COM> select object_name, object_id
  2    from ( select object_name, object_id
  3                     from t sample ( 1 )
  4                    where object_name like '%%'
  5                    order by dbms_random.random )
  6   where rownum <= 4;

OBJECT_NAME                     OBJECT_ID
------------------------------ ----------
/4ff59575_MetalComboBoxUIMetal       7336
/65fabb7f_ServerInterceptorFac      30355
/7ad01403_schemasHolder             31442
/68f6d056_HTMLDocumentHTMLRead       9498

Elapsed: 00:00:00.01
ops$tkyte@ORA817DEV.US.ORACLE.COM> /

OBJECT_NAME                     OBJECT_ID
------------------------------ ----------
/7e51c6b4_JspBeanFieldInfo          35358
/858cce89_WrappedPlainViewWrap       9450
/19c0b889_NotEmptyHelper            32240
/a995fc24_DropTargetContextPee      10144

Elapsed: 00:00:00.01
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set timing off

Using sample made this much faster (but the poster must be on a teeny tiny
machine if it took them 8 seconds to do 5000 rows)

      Rating:
      4
  October 22, 2002
Reviewer:
      A reader
Dear Tom,
  Can you please explain the working of dbms_random.value in the order by
clause?

          Followup:

umm, well, we order the results by a random number and pick off the first 4 --
getting in effect 4 random rows from the entire result set.

      Rating: 4
why randomly
      selecting numbers? -- answer from john  October 22, 2002

Reviewer:  John
tom,

i am sorry for the confusion.

in the 2nd query i put sid_em_ref is same as people table.

the intention of selecting the 4 random ids from people table is:

when i login to our site, we have to display 4 employee photos who belong my
division and who said ok for their photos being viewed by others.

i ran the query you gave in another followup with sample(1) clause. it runs
reasonably fast.

but can we use the query with sample caluse for my requirement with no hassles?
and i don't know anthing about this sample clause

again sorry for any confusion caused.

          Followup:

if your predicate always returns more then 1000 rows or so, a 1% sample via

  sample(1)

should return about 10 rows give or take.  Getting 4 from that should be "safe"
enough -- especially since it is something as inoccent as "display 4 random
pictures".  If you get less then 4 -- big deal (and if someone logs a bug on it
just say "hit reload and tell me if it happens again -- and then it won't ;)

      Rating: 3
sample
      cluase  October 22, 2002
Reviewer:  john

sample clause doesn't work for joins.
any work around for that??

          Followup:

join to the sample.  Since:

scott@ORA920.US.ORACLE.COM> select *
  2    from ( select ename, dname
  3             from emp sample(10), dept
  4                    where emp.deptno = dept.deptno
  5                    order by dbms_random.value )
  6   where rownum <= 4;
                  where emp.deptno = dept.deptno
                  *
ERROR at line 4:
ORA-30561: SAMPLE option not allowed in statement with multiple table references

doesn't fly, use the equivalent:

scott@ORA920.US.ORACLE.COM>
scott@ORA920.US.ORACLE.COM>
scott@ORA920.US.ORACLE.COM> select ename, dname
  2    from ( select *
  3             from ( select ename, deptno
  4                      from emp sample(50)
  5                             order by dbms_random.value )
  6            where rownum <= 4 ) EMP, dept
  7   where emp.deptno = dept.deptno
  8  /

ENAME      DNAME
---------- --------------
MILLER     ACCOUNTING
WARD       SALES
KING       ACCOUNTING

scott@ORA920.US.ORACLE.COM> /

ENAME      DNAME
---------- --------------
ADAMS      RESEARCH
TURNER     SALES
SCOTT      RESEARCH
ALLEN      SALES

scott@ORA920.US.ORACLE.COM>

      Rating: 5
Still not
      clear...  October 22, 2002
Reviewer:  Kashif

Hi Tom,

I'm still a little hazy about how dbms_random works in the order by clause. My
understanding was that a number in the order by clause signified the number of
the column in the select clause, so 'select * from t order by 6' would order by
the 6th column in t. How would the order by work for a number such as 633248193
(last number generated by dbms_random in our database) and so forth?

Secondly, isn't it true that the dbms_random package needs to be initialized
with a seed number? I'm assuming you've already initialized it when you call the
random function in the order by clause.

Thanks in advance.

Kashif

          Followup:

select * from emp order by sal;

so, when sal = 1300 does that order by the 1300'th column??? (no)

select * from emp order by dbms_random.random;

is just like ordering by SAL.

dbms_random hasn't needed to be initialized for a couple of releases now.

      Rating: 3
sample
      caluse  October 22, 2002
Reviewer:  John

tom,
thanks for the alternatives given.

will the sample(10) caluse return
1.10 random rows and get the random values or
2.it gives first 10 rows.

if 2 is the case, i get 4 random photos of first 10 employees of that department
all the time, which we can't tell random.

and for using sample caluse on joins,
i saw your workaround, but as i mentioned, i am not joining to get the data, i
have to join to filter out some rows and get 4 random rows from that filtered
rows.

your query is :

select ename, dname
  2    from ( select *
  3             from ( select ename, deptno
  4                      from emp sample(50)
  5                             order by dbms_random.value )
  6            where rownum <= 4 ) EMP, dept
  7   where emp.deptno = dept.deptno

i want to give something like this:
scott@ORA920.US.ORACLE.COM> select *
  2    from ( select ename, dname
  3             from emp sample(10), dept
  4                    where emp.deptno = dept.deptno
                       and   dept.deptno = '123'
  5                    order by dbms_random.value )
  6   where rownum <= 4
                  where emp.deptno = dept.deptno

thanks

          Followup:

sample(10) is 10%

See the SQL Reference for information on SQL statements!

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a85397/state21b.htm#2065954
sample(10) will return about 10% of the rows randomly.

If what you say is true -- that you join to filter -- then you violate what i
said in the first place:

....
good point -- if there are no joins, just a simple single table query, using
sample to get a couple of dozen random observations and then ordering that by
random would be speedier.
...........

You know what -- if you want my opinion -- this seems like a lot of work for a
"silly" function.  To show four grinning heads "at random".  Not sure its what I
want my database to spend half of its time doing if you know what I mean...

If you didn't want my opinion, well, guess it is too late for that!

      Rating:
      3
clarification  October 22, 2002
Reviewer:
      Mark Wooldridge  from Vienna, VA.
Tom,

Interesting solution.  I am responding to a reply by Kashif of why the
dbms_random works.

Doesn't the order by dbms_random do the same as:
select *
  from ( select empno, ename, dbms_random.value
           from emp
          where ename like '%'
          order by 3 )
 where rownum <= 4

It is confusing that you can specify a number as the order by column or a column
name, or function, if the order by is a function, it is applied to each record
returned and then ordered by that value.  Similar to order by substr(ename, 3).

          Followup:

correct, well said.

      Rating: 3
sample clause
      -- joins  October 22, 2002
Reviewer:
      A reader
to your statement

"If what you say is true -- that you join to filter -- then you violate what i
said in the first place:"

the point here is not whether John is true or you are true.
point here is to find good solution for a requirement(in your opinion which is
silly)

well, people have their own requirements which you can never tell silly. because
you are being paid to solve their requirements.

          Followup:

I clearly said waaaayyy back when the sample idea was first proposed:

"if you join, no joy for you"

Sorry to have wasted time (his and mine) pursuing something that was not to be
(faster via sample)...  I thought I was laying it all out from the get go.

I'm not being paid to solve their requirments by the way -- I actually sort of
take exception to that.

      Rating:
      3
  October 22, 2002
Reviewer:  John

i said only:
"
i want to give something like this:
scott@ORA920.US.ORACLE.COM> select *
  2    from ( select ename, dname
  3             from emp sample(10), dept
  4                    where emp.deptno = dept.deptno
                       and   dept.deptno = '123'
  5                    order by dbms_random.value )
  6   where rownum <= 4
                  where emp.deptno = dept.deptno
"

i never said i am true, i only said that i want something like that which i
don't know how to do that.

please leave it as it is,
my mistake to ask a silly question.

btw, thanks for answering the sample clause -- i don't know that.

      Rating: 3
sample clause
      -- join -- correction  October 22, 2002
Reviewer:
      A reader
correction to :

"because
you are being paid to solve their requirements. "

"we are being paid to answer business owner's requirements."

          Followup:

And - many times -- if you speak up and say

"well, you know that little thing about displaying 4 random pictures.  Well, its
cute and all but let me explain to you the resources it would take to
accomplish.  Now, are you sure you still want it?"

That -- that is our job as well.  Perhaps even more important then doing
whatever you are told.  What sounds trivial to them "4 random pictures based on
this complex query" when translated into "it'll take a bigger machine to host"
-- might persuade them that its perhaps not a requirement.

It is like disaster recovery -- everyone wants it, but they don't want to pay
for it.  Except DR isn't something you should skip-- but maybe -- just maybe --
this is.

      Rating: 1
speak up and
      say  October 22, 2002
Reviewer:
      A reader
you mean "speak up and say" to the business owner that your requirement is
silly?

that too without knowing why do they really want it.

it may be simple to you to tell us like that, as after all we are asking you the
questions, but we can't do that to our clients.

          Followup:

then you are doing your clients a huge, great, large dis-service.

You are not helping them, you are hurting them.

Do you read dilbert (a comic strip)?

The pointy haired guy is the business owner when it comes to technlogy.
Rotating 3D heads might look really cool and they'll call it a business
requirement but unless you tell them the COST of the 3D rotating head on their
display -- they'll just want it.

Tell them the cost of what they are asking for and you might really be surprised
at how quickly they become accomidating.  Really.

I've seen 99.9999% uptime (business requirement) get rapidly reduced, once they
find out the cost and offset that against the cost of being down for an hour.
This is NOT ANY DIFFERENT.

      Rating: 4
randomly
      generating unique integers in 9iR2  November 11, 2002

Reviewer:  John
tom,

is there way to generate unique random integers.

like below

declare
  v_random1 integer;
  v_random2 integer;
  v_random3 integer;
  v_random4 integer;
begin
  v_random1 := dbms_random.value(1,75);
  v_random2 := dbms_random.value(1,75);
  v_random3 := dbms_random.value(1,75);
  v_random4 := dbms_random.value(1,75);
end;

sometimes i get duplicates, how can i avoid duplicates

          Followup:

Well, "unique random values" is an oxymoron....

You cannot really do it -- maybe something like this tho:

  select to_char( dbms_random.value(1,75), '99' )) ||
         to_char(seq.nextval,'fm00000000009' ) into v_r
    from dual;

use a sequence to make it unique

      Rating:
      4
Alternative  November 12, 2002
Reviewer:
      Paul  from UK
Tom,

As a mathematician, I totally agree with you!

If the user means "Can I randomly order the integers 1 .. 75 and select the
first 4?", then something like this would do (copying your trick of ordering by
DBMS_RANDOM.RANDOM):

SELECT * FROM
       (SELECT rownum my_integer
          FROM all_objects
         WHERE rownum < 76
      ORDER BY dbms_random.random)
WHERE ROWNUM < 5
/

          Followup:

excellent -- that would do the trick of getting 4 unique random numbers in the
range of 1..75 yes.

      Rating: 4
Using sample
      with a where clause  November 12, 2002
Reviewer:
      Sam To  from San Mateo, CA
Tom,

I am writing an application that involves randomly selecting a small sample of
items in a certain category from a large shopping catalog and then feeding them
to an AI engine to automatically generate rules on why these items are
classified into that category.  All the items are in a single table.

If I use the SAMPLE clause, something like:
SELECT rowid, item_desc FROM my_catalog_items sample(10)
WHERE category_name = :b1
then for categories that have a small number of items this statement often
return no rows.  I think the sample is taken before applying the where clause,
am I correct?

Is there a better way to do this?  I want to avoid scanning the entire table
since it could be very large.

          Followup:

with a small number of rows -- a 10% sample could EASILY return 0 rows.
  1* select dname from dept sample(10)
scott@ORA920.LOCALHOST> /

DNAME
--------------
ACCOUNTING
SALES

scott@ORA920.LOCALHOST> /

no rows selected

with or without a where clause.  If the sample IS TOO SMALL -- you stand a very
very good chance of randomly getting nothing.

sample does scan the full table the way you are using it - sample BLOCK wouldn't
(but the same problem would arise -- maybe even worse!)

Sorry -- but I don't have a "good" solution for you short of "rerun the query if
the sample size is deemed too small"

      Rating: 3
Randomly
      selecting with 7.3  December 11, 2002
Reviewer:
      Brijesh
Hi Tom,

I have created your random package with 7.3. (we are in the process of upgrading
to 902, but it

will take another 6 months as we have a huge number of users).

We have a requirement :

    We call companies to offer tenders.
    The companies information is stored in a table called "fatwa" which has over
a million

rows.

    In order to give fair chance to companies and to involve no user
intervention in

selecting companies we decided to randomly select a given set of companies and
call them to offer

tender.

    After selecting the companies I am entering the company id of the selected
companies in a

table called "old_fatwa" so that later I can exclude them.
    And when ever I select new companies I query the "fatwa" table like this :

First a count(*) from the table.

SQL> select count(*) from fatwa;

  COUNT(*)
----------
   1179651

Elapsed: 00:00:02.05

And now selecting companies randomly.

SQL> ed
Wrote file afiedt.buf

  1  select fatwaid from
  2     (select * from fatwa where fatwaid not in
  3             (select fatwaid from old_fatwa)
  4     order by random.rand)
  5* where rownum <=5
SQL> /

   FATWAID
----------
     10407
     22342
     50031
     71660
     54781

Elapsed: 00:02:51.03
SQL> /

   FATWAID
----------
     42989
     66700
    215773
    249553
    228372

Elapsed: 00:02:55.00

I am able to get the companies randomly but it is taking too long.....
Can you please help me reduce that.

Thanks Brijesh.

          Followup:

for a million rows, surprised it is going that fast!

Especially in 73.  1,000,000 calls to random.rand is taking a good portion of
that -- just do a loop and time it.

In 7.3 -- not using CBO, the not in is probably an issue as well.  did you test
how long just:

  1  select fatwaid from
  2     (select * from fatwa where fatwaid not in
  3             (select fatwaid from old_fatwa)
  4     )
  5*

takes?  try:

select a.*
  from fatwa a, old_fatwa b
 where a.fatwaid = b.fatwaid(+)
   and b.fatwaid is null;

getting a random sample from 1million rows is going to take a while -- in 8i and
up with dbms_random and the top-n query optimization it will take LESS time, but
it'll still take a while.

sorry -- no good answer for you on this one.

      Rating: 3
Same values
      using dbms_random.value  May 02, 2003
Reviewer:
      Max
Tom,

What are the chances that dbms_random.value returns same values in a multiuser
environment ? For one of my process, I insert some rows into a table (this is an
existing table which is no longer used -- so I use it as a temp table). But
since the table has a pk constraint on the column, I simply generate a random
value, insert data and delete data when I am done. So technically, at any given
time, the table is always empty.

So would it any time happen that 2 users running the same process from different
sessions might get the same random value ? What are the chances ?

I know, I can use sequences and get rid of the matter but I just do not want to
create an additional object. But if there is a high chance, I will use
sequences.

Thanks in advance !

          Followup:

100% at some point.

you want to use a global temporary table -- each session can see only ITS data.

I've never understood the "i don't want to create that which is necessary for my
application" thought process.  seems counter productive to me.

      Rating: 4
random numbers
      !=non repeating numbers  May 03, 2003
Reviewer:
      Jim  from Portland, OR USA
Just because you are generating a random number does not mean you won't get
repeats (the same number).  The random number generator in Oracle is generates
numbers via an algorithium that allows replacement.  To generate a list of
random numbers with out the possibility of  a repeat you would need a random
number generator that generates numbers without replacement.

Think of it this way.  You have a large bucket of marbles.  Each marble has a
different number on it.  The Oracle Random number generator (and most random
number generators) work by picking a marble and telling you the number on it and
then replacing the marble back in the bucket. (with replacement)

To pick numbers without replacement you have to pick a marbel and not put it
back into the bucket.

The bucket analogy is just an analogy.  The random number generator is just a
mathmatical formula - there are a lot of them out of there.

So don't use random numbers to generate a primary key.  I worked with some
developers who tried this and couldn't understand why it failed every so often.
(They were getting repeated keys)

      Rating: 5
creating a view
      out of sample (8.1.7.4)  May 05, 2003
Reviewer:
      Scott  from Perth, Western Australia
G'day

The sample feature is perfect for our problem. We also want to get random
employee records - but just to create random names as dummy data for our
customer records in development - due to privacy laws.
I tried to create a view, so I could also use the lag and lead functions (as
they don't work in pl/sql), but I receive this error:
      FROM employees SAMPLE (1)
                            *
ERROR at line 6:
ORA-00907: missing right parenthesis

While the query by itself in SQL*Plus works...
SELECT first_name, last_name, addr_1, NULL locality
FROM (SELECT first_name
     ,NVL(LEAD(last_name) OVER (ORDER BY
DBMS_RANDOM.value(1,1000)),LAG(last_name) OVER (ORDER BY
DBMS_RANDOM.value(1,1000))) last_name
     ,NVL(LEAD(addr_1,2) OVER (ORDER BY DBMS_RANDOM.value(1,1000)),LAG(addr_1,1)
OVER (ORDER BY DBMS_RANDOM.value(1,1000))) addr_1
      FROM employees SAMPLE (1)
      ORDER BY DBMS_RANDOM.value(1,1000))
/
FIRST_NAME                     LAST_NAME                      ADDR_1

------------------------------ ------------------------------
----------------------
EVELYN                         CRAWFORD                       1 BATHGATE LOOP

Why can't sample () be used in a view?

Thanks.

          Followup:

Sample is not support in views.

May 05, 2003 Bala
      -- Thanks for the question regarding "Few questions with 'NOT NULL
      Constraints' and "number" datatype ", version 9i R2

      You Asked
Hi Tom,

I have two questions.

1. I need to add a column to a table which is a "number" datatype
   and which a default value set to "0".

When i come across "Steve Adams" article -- He is suggesting a tip Why to
declare not null constraints if you know data will be always supplied for the
column". Here is the excerpt.

<<
People sometimes do not bother to define columns as NOT NULL in the data
dictionary, even though these columns should not contain nulls, and indeed never
do contain nulls because the application ensures that a value is always
supplied. You may think that this is a matter of indifference, but it is not.
The optimizer sometimes needs to know that a column is not nullable, and without
that knowledge it is constrained to choose a less than optimal execution plan.
>>

Can you provide me your view point on this from performance perspective ? How
this "NOT NULL" influences the optimizer choosing the query plan.   Thanks

You also menioned in your book that "Even if there is any index on NULLABLE
column, index won't be used if "not null" is declared" .

2. Please refer question (1), In this case , Can i just define this number
datatype as "integer". I know i will store only integer values
with precision 2. No floating point values.

Is there any performance improvement, if i define this column as
number(2).

Could you please provide your suggestions on this.

Please advise.

Thanks

      and we said...
1) Steve Admans is a pretty smart guy -- and he is dead on here.  If the column
is NOT NULLABLE -- say so.  If not for performance -- just because *it is so*

I did not write that which you attribute to me.  Give me a page reference -- but
that is not anything I would have written.

Here is a simple example, consider the plans before and after:

ops$tkyte@ORA920> create table t ( x int, y char(2000) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index t_idx on t(x);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_stats.set_table_stats( user, 'T', numrows =>
1000000000, numblks => 1000000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select x from t order by x;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=8403861 Card=1000000000
Bytes=13000000000)
   1    0   SORT (ORDER BY) (Cost=8403861 Card=1000000000 Bytes=13000000000)
   2    1     TABLE ACCESS (FULL) OF 'T' (Cost=96176 Card=1000000000
Bytes=13000000000)

ops$tkyte@ORA920> select * from t where x not in ( select x from t );

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4808800096176 Card=50000000
Bytes=5000000000)
   1    0   FILTER
   2    1     TABLE ACCESS (FULL) OF 'T' (Cost=96176 Card=50000000
Bytes=5000000000)
   3    1     TABLE ACCESS (FULL) OF 'T' (Cost=96176 Card=50000000
Bytes=650000000)

ops$tkyte@ORA920> alter table t modify x not null;

Table altered.

ops$tkyte@ORA920> select x from t order by x;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=26 Card=1000000000
Bytes=13000000000)
   1    0   INDEX (FULL SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=26 Card=1000000000
Bytes=13000000000)

ops$tkyte@ORA920> select * from t where x not in ( select x from t );

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=8308515 Card=1 Bytes=113)
   1    0   MERGE JOIN (ANTI) (Cost=8308515 Card=1 Bytes=113)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=826 Card=1000000000
Bytes=100000000000)
   3    2       INDEX (FULL SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=26
Card=1000000000)
   4    1     SORT (UNIQUE) (Cost=8307689 Card=1000000000 Bytes=13000000000)
   5    4       INDEX (FAST FULL SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=4
Card=1000000000 Bytes=13000000000)

ops$tkyte@ORA920> set autotrace off
ops$tkyte@ORA920>

2) consider number(scale,precision) to be an EDIT -- like a check constraint or
not null.  If the number is to be a 2 digit number -- by all means use a
number(2) and the database won't let 999 in there!

It is all about data integrity.  Use the most specific type that meets your
needs.

Can you use integer?  Sure, but if your business rule states "this is a 2 digit
number and is an error if it it more than that" -- use a number(2)!

Review &
      Followup

      Rating: 5
Thanks
      Tom  January 18, 2003
Reviewer:  Bala
      Murugan  from NJ, USA
Tom,

Thanks for your timely reply.

1. When i look at before and after statistics of your sample example,

With out 'not null' constraint, the optimizer chooses full table scan

With 'NOT NULL' constraint, the optimizer chooses the index access path.

So, i won't get less optimal plan due to the absence of 'not null' constraints.

In my case, as i know that values are supplied, if not
default values are always set, i will be declaring "NOT NULL"  constraint.
Thanks.

Tom, sorry i am not attributing on you. My oversight. I was looking at the page
. 300 (at the bottom). I could be wrong.

2. I will confirm it with developers /SEs on this issue.

If "business rules" are not the issue here, how is the space management when i
use "integer" in stead of number(2).

Will it allocate more spaces even if i enter 2 digit nos.
with "integer" datatype.

Thanks for your suggestions.

          Followup:

1) on page 300, I'm talking about the fact that entirely null entries are not
made into b*tree indexes.  but that doesn't translate into:

 "Even if there is any index on NULLABLE
column, index won't be used if "not null" is declared" .

I said -- an index will not be used to answer a query like "select * from t
where x is null" if there is a b*tree index on X -- there won't be any entries
in there.

2) there are only numbers in Oracle -- they are all stored the same regardless
of how you declare them -- number, float, integer, they are all the same.

      Rating: 5
Thanks for your
      clarification  January 18, 2003
Reviewer:
      A reader

      Rating: 4
Number
      Datatype  May 04, 2003
Reviewer:  a reader
       from UK
Tom,

My question is related to a database environment. We get data feed from many
sources. Due to the source system (in this case Mainframe) migration to a new
release, field lengths are dramatically changed. As a result we have to take
that into account in our database tables.

The number fields are already defined in our tables with precsion and scale.
e.g. Let's balance is currently defined as number(9,2). Due to the migration,
the new  definition should be number(17,2). Now, out of the following two
options which one should be preferrable
1) re-define the column to Number(17,2)
2) re-define the column to number.

Could you please let me know what could possibly an issue if I choose option 2.

These changes have to undergo across many tables and in many columns. Do you
think it is good idea to re-create the tables or just re-define the coulmns.

Sorry for asking this question in the review.

Many thanks in advance

          Followup:

you lose the edit, a number(17,2) is like having a datatype with a check
constraint that validates the data.

can you risk losing this validation?

You should just modify the column via ALTER, no need to rewrite the data.

I would suggest number(17,2) -- for if you went to number, THEN realized -- hey,
we really need that edit -- going back from number to number(17,2) would
necessitate a rewrite of the data.

      Rating: 5
Why no
      error...  May 05, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Hi Tom,
I created a table with one column called "X" as a NUMBER and another column "Y"
as a INTEGER. When I describe the table it shows the second column as
NUMBER(38). Then It allows me to insert a 60 digit value into column "Y" with
not errors. Then after I alter column "X" to NUMBER(38) and try to insert the 60
digit value into column "X" it gives me the precision error. Why not give the
precision error for the first case too? Why column "Y" is shown as NUMBER(38)
and still a 60 digit value is accepted?

SQL> create table number_vs_integer(x number, y integer) ;

Table created.

SQL> desc number_vs_integer
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 X                                                  NUMBER
 Y                                                  NUMBER(38)

SQL> insert into number_vs_integer values (1,
123456789012345678901234567890123456789012345678901234567890) ;

1 row created.

SQL> delete from number_vs_integer ;

1 row deleted.

SQL> alter table number_vs_integer modify (x number(38)) ;

Table altered.

SQL> desc number_vs_integer ;
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 X                                                  NUMBER(38)
 Y                                                  NUMBER(38)

SQL> insert into number_vs_integer values
(123456789012345678901234567890123456789012345678901234567890, 1) ;
insert into number_vs_integer values
(123456789012345678901234567890123456789012345678901234567890, 1)
                                      *
ERROR at line 1:
ORA-01438: value larger than specified precision allows for this column

SQL>

          Followup:

the number is just unconstrained.  the DD returns 38 which is the maximum you
can expect to be supported.

when you said number(38), it became an edit, to be enforced.

May 06, 2003 rong
      -- Thanks for the question regarding "ARC1: Beginning to archive log# 1
      seq# 56947", version 8.1.6.1.3

      You Asked
We have the environment called data storage, from tuesday to saturday, large
amount of data inserted into database from 4:00am to 9:00am. we have got errors
from alert log off and on:"
Fri Nov 09 08:13:42 2001
ARC2: Beginning to archive log# 2 seq# 56943
ARC2: Failed to archive log# 2 seq# 56943
ARC2: Beginning to archive log# 5 seq# 56944
ARC2: Failed to archive log# 5 seq# 56944
ARC2: Beginning to archive log# 4 seq# 56945
Fri Nov 09 08:13:50 2001
ARC0: Completed archiving log# 2 seq# 56943
Fri Nov 09 08:13:50 2001
ARC3: Beginning to archive log# 5 seq# 56944
ARC3: Failed to archive log# 5 seq# 56944
ARC3: Beginning to archive log# 4 seq# 56945
ARC3: Failed to archive log# 4 seq# 56945
Fri Nov 09 08:13:57 2001
Thread 1 advanced to log sequence 56947
  Current log# 1 seq# 56947 mem# 0: I:\DATA\ORADATA\DFWP\LOGDFWP01A.DBF
  Current log# 1 seq# 56947 mem# 1: K:\DATA\ORADATA\DFWP\LOGDFWP01B.DBF
Fri Nov 09 08:13:57 2001
ARC4: Beginning to archive log# 5 seq# 56944
ARC4: Failed to archive log# 5 seq# 56944
ARC4: Beginning to archive log# 4 seq# 56945
ARC4: Failed to archive log# 4 seq# 56945
ARC4: Beginning to archive log# 3 seq# 56946
Fri Nov 09 08:14:10 2001
Thread 1 advanced to log sequence 56948
  Current log# 2 seq# 56948 mem# 0: I:\DATA\ORADATA\DFWP\LOGDFWP02A.DBF
  Current log# 2 seq# 56948 mem# 1: K:\DATA\ORADATA\DFWP\LOGDFWP02B.DBF
Fri Nov 09 08:14:10 2001
ARC0: Beginning to archive log# 5 seq# 56944
ARC0: Failed to archive log# 5 seq# 56944
ARC0: Beginning to archive log# 4 seq# 56945
ARC0: Failed to archive log# 4 seq# 56945
ARC0: Beginning to archive log# 3 seq# 56946
ARC0: Failed to archive log# 3 seq# 56946
ARC0: Beginning to archive log# 1 seq# 56947
Fri Nov 09 08:14:11 2001
ARC1: Completed archiving log# 5 seq# 56944
ARC1: Beginning to archive log# 4 seq# 56945
ARC1: Failed to archive log# 4 seq# 56945
ARC1: Beginning to archive log# 3 seq# 56946
ARC1: Failed to archive log# 3 seq# 56946
ARC1: Beginning to archive log# 1 seq# 56947
ARC1: Failed to archive log# 1 seq# 56947
Fri Nov 09 08:14:25 2001
Thread 1 advanced to log sequence 56949
  Current log# 5 seq# 56949 mem# 0: I:\DATA\ORADATA\DFWP\LOGDFWP05A.DBF
  Current log# 5 seq# 56949 mem# 1: K:\DATA\ORADATA\DFWP\LOGDFWP05B.DBF
Fri Nov 09 08:14:25 2001
ARC3: Beginning to archive log# 4 seq# 56945
ARC3: Failed to archive log# 4 seq# 56945
ARC3: Beginning to archive log# 3 seq# 56946
ARC3: Failed to archive log# 3 seq# 56946
ARC3: Beginning to archive log# 1 seq# 56947
ARC3: Failed to archive log# 1 seq# 56947
ARC3: Beginning to archive log# 2 seq# 56948
Fri Nov 09 08:14:26 2001
ARC2: Completed archiving log# 4 seq# 56945
Fri Nov 09 08:14:27 2001
ARC1: Beginning to archive log# 3 seq# 56946
ARC1: Failed to archive log# 3 seq# 56946
ARC1: Beginning to archive log# 1 seq# 56947
ARC1: Failed to archive log# 1 seq# 56947
ARC1: Beginning to archive log# 2 seq# 56948
ARC1: Failed to archive log# 2 seq# 56948
Fri Nov 09 08:14:41 2001
Thread 1 advanced to log sequence 56950
  Current log# 4 seq# 56950 mem# 0: I:\DATA\ORADATA\DFWP\LOGDFWP04A.DBF
  Current log# 4 seq# 56950 mem# 1: K:\DATA\ORADATA\DFWP\LOGDFWP04B.DBF
Fri Nov 09 08:14:42 2001
ARC2: Beginning to archive log# 3 seq# 56946
ARC2: Failed to archive log# 3 seq# 56946
ARC2: Beginning to archive log# 1 seq# 56947
ARC2: Failed to archive log# 1 seq# 56947
ARC2: Beginning to archive log# 2 seq# 56948
ARC2: Failed to archive log# 2 seq# 56948
ARC2: Beginning to archive log# 5 seq# 56949
Fri Nov 09 08:14:46 2001
ARC4: Completed archiving log# 3 seq# 56946
ARC4: Beginning to archive log# 1 seq# 56947
ARC4: Failed to archive log# 1 seq# 56947
ARC4: Beginning to archive log# 2 seq# 56948
ARC4: Failed to archive log# 2 seq# 56948
ARC4: Beginning to archive log# 5 seq# 56949
ARC4: Failed to archive log# 5 seq# 56949
Fri Nov 09 08:14:56 2001
Thread 1 advanced to log sequence 56951
  Current log# 3 seq# 56951 mem# 0: I:\DATA\ORADATA\DFWP\LOGDFWP03A.DBF
  Current log# 3 seq# 56951 mem# 1: K:\DATA\ORADATA\DFWP\LOGDFWP03B.DBF
Fri Nov 09 08:14:56 2001
ARC1: Beginning to archive log# 1 seq# 56947
ARC1: Failed to archive log# 1 seq# 56947
ARC1: Beginning to archive log# 2 seq# 56948
ARC1: Failed to archive log# 2 seq# 56948
ARC1: Beginning to archive log# 5 seq# 56949
ARC1: Failed to archive log# 5 seq# 56949
ARC1: Beginning to archive log# 4 seq# 56950
Fri Nov 09 08:15:05 2001
ARC0: Completed archiving log# 1 seq# 56947
Fri Nov 09 08:15:05 2001
ARC4: Beginning to archive log# 2 seq# 56948
ARC4: Failed to archive log# 2 seq# 56948
ARC4: Beginning to archive log# 5 seq# 56949
ARC4: Failed to archive log# 5 seq# 56949
ARC4: Beginning to archive log# 4 seq# 56950
ARC4: Failed to archive log# 4 seq# 56950
Fri Nov 09 08:15:11 2001
Thread 1 advanced to log sequence 56952
  Current log# 1 seq# 56952 mem# 0: I:\DATA\ORADATA\DFWP\LOGDFWP01A.DBF
  Current log# 1 seq# 56952 mem# 1: K:\DATA\ORADATA\DFWP\LOGDFWP01B.DBF
Fri Nov 09 08:15:11 2001
ARC0: Beginning to archive log# 2 seq# 56948
ARC0: Failed to archive log# 2 seq# 56948
ARC0: Beginning to archive log# 5 seq# 56949
ARC0: Failed to archive log# 5 seq# 56949
ARC0: Beginning to archive log# 4 seq# 56950
ARC0: Failed to archive log# 4 seq# 56950
ARC0: Beginning to archive log# 3 seq# 56951
Fri Nov 09 08:15:21 2001
ARC3: Completed archiving log# 2 seq# 56948
ARC3: Beginning to archive log# 5 seq# 56949
ARC3: Failed to archive log# 5 seq# 56949
ARC3: Beginning to archive log# 4 seq# 56950
ARC3: Failed to archive log# 4 seq# 56950
ARC3: Beginning to archive log# 3 seq# 56951
ARC3: Failed to archive log# 3 seq# 56951
Fri Nov 09 08:15:27 2001
Thread 1 advanced to log sequence 56953
  Current log# 2 seq# 56953 mem# 0: I:\DATA\ORADATA\DFWP\LOGDFWP02A.DBF
  Current log# 2 seq# 56953 mem# 1: K:\DATA\ORADATA\DFWP\LOGDFWP02B.DBF
Fri Nov 09 08:15:27 2001
ARC4: Beginning to archive log# 5 seq# 56949
ARC4: Failed to archive log# 5 seq# 56949
ARC4: Beginning to archive log# 4 seq# 56950
ARC4: Failed to archive log# 4 seq# 56950
ARC4: Beginning to archive log# 3 seq# 56951
ARC4: Failed to archive log# 3 seq# 56951
ARC4: Beginning to archive log# 1 seq# 56952
Fri Nov 09 08:15:37 2001
ARC2: Completed archiving log# 5 seq# 56949
ARC2: Beginning to archive log# 4 seq# 56950
ARC2: Failed to archive log# 4 seq# 56950
ARC2: Beginning to archive log# 3 seq# 56951
ARC2: Failed to archive log# 3 seq# 56951
ARC2: Beginning to archive log# 1 seq# 56952
ARC2: Failed to archive log# 1 seq# 56952
Fri Nov 09 08:15:43 2001
Thread 1 advanced to log sequence 56954
  Current log# 5 seq# 56954 mem# 0: I:\DATA\ORADATA\DFWP\LOGDFWP05A.DBF
  Current log# 5 seq# 56954 mem# 1: K:\DATA\ORADATA\DFWP\LOGDFWP05B.DBF
Fri Nov 09 08:15:43 2001
ARC3: Beginning to archive log# 4 seq# 56950
ARC3: Failed to archive log# 4 seq# 56950
ARC3: Beginning to archive log# 3 seq# 56951
ARC3: Failed to archive log# 3 seq# 56951
ARC3: Beginning to archive log# 1 seq# 56952
ARC3: Failed to archive log# 1 seq# 56952
ARC3: Beginning to archive log# 2 seq# 56953
Fri Nov 09 08:15:49 2001
ARC1: Completed archiving log# 4 seq# 56950
ARC1: Beginning to archive log# 3 seq# 56951
ARC1: Failed to archive log# 3 seq# 56951
ARC1: Beginning to archive log# 1 seq# 56952
ARC1: Failed to archive log# 1 seq# 56952
ARC1: Beginning to archive log# 2 seq# 56953
ARC1: Failed to archive log# 2 seq# 56953
Fri Nov 09 08:15:59 2001
Thread 1 advanced to log sequence 56955
  Current log# 4 seq# 56955 mem# 0: I:\DATA\ORADATA\DFWP\LOGDFWP04A.DBF
  Current log# 4 seq# 56955 mem# 1: K:\DATA\ORADATA\DFWP\LOGDFWP04B.DBF
Fri Nov 09 08:15:59 2001
ARC2: Beginning to archive log# 3 seq# 56951
ARC2: Failed to archive log# 3 seq# 56951
ARC2: Beginning to archive log# 1 seq# 56952
ARC2: Failed to archive log# 1 seq# 56952
ARC2: Beginning to archive log# 2 seq# 56953
ARC2: Failed to archive log# 2 seq# 56953
ARC2: Beginning to archive log# 5 seq# 56954
Fri Nov 09 08:16:14 2001
ARC1: Beginning to archive log# 3 seq# 56951
ARC1: Failed to archive log# 3 seq# 56951
ARC1: Beginning to archive log# 1 seq# 56952
ARC1: Failed to archive log# 1 seq# 56952
Fri Nov 09 08:16:14 2001
Thread 1 cannot allocate new log, sequence 56956
All online logs needed archiving
  Current log# 4 seq# 56955 mem# 0: I:\DATA\ORADATA\DFWP\LOGDFWP04A.DBF
  Current log# 4 seq# 56955 mem# 1: K:\DATA\ORADATA\DFWP\LOGDFWP04B.DBF
Fri Nov 09 08:16:14 2001
ARC1: Beginning to archive log# 2 seq# 56953
ARC1: Failed to archive log# 2 seq# 56953
ARC1: Beginning to archive log# 5 seq# 56954
ARC1: Failed to archive log# 5 seq# 56954
ARC1: Beginning to archive log# 3 seq# 56951
ARC1: Failed to archive log# 3 seq# 56951
ARC1: Beginning to archive log# 1 seq# 56952
ARC1: Failed to archive log# 1 seq# 56952
ARC1: Beginning to archive log# 2 seq# 56953
ARC1: Failed to archive log# 2 seq# 56953
ARC1: Beginning to archive log# 5 seq# 56954
ARC1: Failed to archive log# 5 seq# 56954
Fri Nov 09 08:16:15 2001
ARC0: Completed archiving log# 3 seq# 56951
ARC0: Beginning to archive log# 1 seq# 56952
ARC0: Failed to archive log# 1 seq# 56952
ARC0: Beginning to archive log# 2 seq# 56953
ARC0: Failed to archive log# 2 seq# 56953
ARC0: Beginning to archive log# 5 seq# 56954
ARC0: Failed to archive log# 5 seq# 56954
Fri Nov 09 08:16:16 2001
Thread 1 advanced to log sequence 56956
  Current log# 3 seq# 56956 mem# 0: I:\DATA\ORADATA\DFWP\LOGDFWP03A.DBF
  Current log# 3 seq# 56956 mem# 1: K:\DATA\ORADATA\DFWP\LOGDFWP03B.DBF
Fri Nov 09 08:16:16 2001
ARC1: Beginning to archive log# 1 seq# 56952
ARC1: Failed to archive log# 1 seq# 56952
ARC1: Beginning to archive log# 2 seq# 56953
ARC1: Failed to archive log# 2 seq# 56953
ARC1: Beginning to archive log# 5 seq# 56954
ARC1: Failed to archive log# 5 seq# 56954
ARC1: Beginning to archive log# 4 seq# 56955
Fri Nov 09 08:16:31 2001
Thread 1 cannot allocate new log, sequence 56957
All online logs needed archiving
  Current log# 3 seq# 56956 mem# 0: I:\DATA\ORADATA\DFWP\LOGDFWP03A.DBF
  Current log# 3 seq# 56956 mem# 1: K:\DATA\ORADATA\DFWP\LOGDFWP03B.DBF
Fri Nov 09 08:16:31 2001
ARC0: Beginning to archive log# 1 seq# 56952
ARC0: Failed to archive log# 1 seq# 56952
ARC0: Beginning to archive log# 2 seq# 56953
ARC0: Failed to archive log# 2 seq# 56953
ARC0: Beginning to archive log# 5 seq# 56954
ARC0: Failed to archive log# 5 seq# 56954
ARC0: Beginning to archive log# 4 seq# 56955
ARC0: Failed to archive log# 4 seq# 56955
ARC0: Beginning to archive log# 1 seq# 56952
ARC0: Failed to archive log# 1 seq# 56952
ARC0: Beginning to archive log# 2 seq# 56953
ARC0: Failed to archive log# 2 seq# 56953
ARC0: Beginning to archive log# 5 seq# 56954
ARC0: Failed to archive log# 5 seq# 56954
ARC0: Beginning to archive log# 4 seq# 56955
ARC0: Failed to archive log# 4 seq# 56955
Fri Nov 09 08:16:34 2001
ARC4: Completed archiving log# 1 seq# 56952
Fri Nov 09 08:16:34 2001
Thread 1 advanced to log sequence 56957
  Current log# 1 seq# 56957 mem# 0: I:\DATA\ORADATA\DFWP\LOGDFWP01A.DBF
  Current log# 1 seq# 56957 mem# 1: K:\DATA\ORADATA\DFWP\LOGDFWP01B.DBF
Fri Nov 09 08:16:34 2001
ARC0: Beginning to archive log# 2 seq# 56953
ARC0: Failed to archive log# 2 seq# 56953
Fri Nov 09 08:16:34 2001
ARC4: Beginning to archive log# 2 seq# 56953
ARC4: Failed to archive log# 2 seq# 56953
ARC4: Beginning to archive log# 5 seq# 56954
Fri Nov 09 08:16:34 2001
ARC0: Beginning to archive log# 5 seq# 56954
Fri Nov 09 08:16:34 2001
ARC0: Failed to archive log# 5 seq# 56954
ARC0: Beginning to archive log# 4 seq# 56955
ARC0: Failed to archive log# 4 seq# 56955
ARC0: Beginning to archive log# 3 seq# 56956
Fri Nov 09 08:16:35 2001
ARC4: Failed to archive log# 5 seq# 56954
Fri Nov 09 08:16:35 2001
ARC4: Beginning to archive log# 4 seq# 56955
ARC4: Failed to archive log# 4 seq# 56955
ARC4: Beginning to archive log# 3 seq# 56956
ARC4: Failed to archive log# 3 seq# 56956
Fri Nov 09 08:16:49 2001
Thread 1 cannot allocate new log, sequence 56958
All online logs needed archiving
  Current log# 1 seq# 56957 mem# 0: I:\DATA\ORADATA\DFWP\LOGDFWP01A.DBF
  Current log# 1 seq# 56957 mem# 1: K:\DATA\ORADATA\DFWP\LOGDFWP01B.DBF
Fri Nov 09 08:16:49 2001
ARC4: Beginning to archive log# 2 seq# 56953
ARC4: Failed to archive log# 2 seq# 56953
ARC4: Beginning to archive log# 5 seq# 56954
ARC4: Failed to archive log# 5 seq# 56954
ARC4: Beginning to archive log# 4 seq# 56955
ARC4: Failed to archive log# 4 seq# 56955
ARC4: Beginning to archive log# 3 seq# 56956
ARC4: Failed to archive log# 3 seq# 56956
Fri Nov 09 08:16:50 2001
ARC3: Completed archiving log# 2 seq# 56953
ARC3: Beginning to archive log# 5 seq# 56954
ARC3: Failed to archive log# 5 seq# 56954
ARC3: Beginning to archive log# 4 seq# 56955
ARC3: Failed to archive log# 4 seq# 56955
ARC3: Beginning to archive log# 3 seq# 56956
ARC3: Failed to archive log# 3 seq# 56956
Fri Nov 09 08:16:50 2001
Thread 1 advanced to log sequence 56958
  Current log# 2 seq# 56958 mem# 0: I:\DATA\ORADATA\DFWP\LOGDFWP02A.DBF
  Current log# 2 seq# 56958 mem# 1: K:\DATA\ORADATA\DFWP\LOGDFWP02B.DBF
Fri Nov 09 08:16:50 2001
ARC4: Beginning to archive log# 5 seq# 56954
ARC4: Failed to archive log# 5 seq# 56954
ARC4: Beginning to archive log# 4 seq# 56955
ARC4: Failed to archive log# 4 seq# 56955
ARC4: Beginning to archive log# 3 seq# 56956
ARC4: Failed to archive log# 3 seq# 56956
ARC4: Beginning to archive log# 1 seq# 56957
Fri Nov 09 08:17:04 2001
ARC2: Completed archiving log# 5 seq# 56954
ARC2: Beginning to archive log# 4 seq# 56955
ARC2: Failed to archive log# 4 seq# 56955
ARC2: Beginning to archive log# 3 seq# 56956
ARC2: Failed to archive log# 3 seq# 56956
ARC2: Beginning to archive log# 1 seq# 56957
ARC2: Failed to archive log# 1 seq# 56957
Fri Nov 09 08:17:09 2001
Thread 1 advanced to log sequence 56959
  Current log# 5 seq# 56959 mem# 0: I:\DATA\ORADATA\DFWP\LOGDFWP05A.DBF
  Current log# 5 seq# 56959 mem# 1: K:\DATA\ORADATA\DFWP\LOGDFWP05B.DBF
Fri Nov 09 08:17:09 2001
ARC3: Beginning to archive log# 4 seq# 56955
ARC3: Failed to archive log# 4 seq# 56955
ARC3: Beginning to archive log# 3 seq# 56956
ARC3: Failed to archive log# 3 seq# 56956
ARC3: Beginning to archive log# 1 seq# 56957
ARC3: Failed to archive log# 1 seq# 56957
ARC3: Beginning to archive log# 2 seq# 56958
Fri Nov 09 08:17:11 2001
ARC1: Completed archiving log# 4 seq# 56955
ARC1: Beginning to archive log# 3 seq# 56956
ARC1: Failed to archive log# 3 seq# 56956
ARC1: Beginning to archive log# 1 seq# 56957
ARC1: Failed to archive log# 1 seq# 56957
ARC1: Beginning to archive log# 2 seq# 56958
ARC1: Failed to archive log# 2 seq# 56958
Fri Nov 09 08:17:24 2001
ARC0: Completed archiving log# 3 seq# 56956
Fri Nov 09 08:17:24 2001
ARC2: Beginning to archive log# 1 seq# 56957
ARC2: Failed to archive log# 1 seq# 56957
ARC2: Beginning to archive log# 2 seq# 56958
ARC2: Failed to archive log# 2 seq# 56958
Fri Nov 09 08:17:31 2001
ARC4: Completed archiving log# 1 seq# 56957
ARC4: Beginning to archive log# 2 seq# 56958
ARC4: Failed to archive log# 2 seq# 56958"
====================================================================
THERE ARE FOLLLOWING PARAMETERS SET IN INIT.ORA FILE:
log_archive_dest = J:\DATA\ORADATA\DFWP\ARCHLOGS
log_archive_max_processes = 5
log_buffer = 9437184
log_archive_format = log_dfwp_%S.arc

online log files are located @ i:\data\oradata\dfwp\, and k:\data\oradata\dfwp\
====================================================================
HERE IS THE INITDFWP.ORA FILE:
#####################################################
# Initdfwp.ora Revised 12/01/98 S.M. Lawrence
#              Revised 07/14/99 P. Cappabianca Data Warehouse changes
#                      05/21/01 P. Cappabianca RBS changes
#               06/22/01
#               increased log buffer from  65536 to 9437184
#               06/22/01 P. Cappabianca Timed_statistics to true
#               06/29/01 P. Cappabianca added log_archive_max_processes
#####################################################

# audit_trail = true                            # if you want auditing
background_dump_dest=D:\DATA\ORADBA\ADMIN\DFWP\BDUMP
control_files = D:\APPS\ORA8i\DATABASE\ctl1DFWP.ORA
control_files = J:\DATA\ORADATA\DFWP\ctl2DFWP.ORA
control_files = K:\DATA\ORADATA\DFWP\ctl3DFWP.ORA
cursor_space_for_time = TRUE
db_block_buffers = 231000
# do NOT change the below value
db_block_size = 8192
# do NOT change the above value
db_file_multiblock_read_count = 8    # pjc 7/20/00 parallel tuning, was 32
#DB_FILE_SIMULTANEOUS_WRITES = 16
db_files = 1024                      # pjc 3/6/00 set to maxdatafiles
db_name = dfwp
dml_locks =  500                                # INITIAL
# sml added 12 01 98 to override the default value 73XX
enqueue_resources = 9000
# event="471 trace name errorstack level 10"
# event="10235 trace name context forever, level 1"
hash_area_size = 16384000              # pjc 7/20/00 increased from 4096000
hash_join_enabled = TRUE               # pjc 7/20/00 parallel tuning, was FALSE
hash_multiblock_io_count = 4           # pjc 7/20/00 parallel tuning, was 2
log_archive_start = true      # if you want automatic archiving
log_archive_dest = J:\DATA\ORADATA\DFWP\ARCHLOGS
log_archive_max_processes = 5
log_archive_format = log_dfwp_%S.arc
log_buffer = 9437184
# write 400 blocks then do a checkpoint
# 4096 block size x 400 div 2 plus 1 equals 819201
log_checkpoint_interval = 819201
#LOG_SIMULTANEOUS_COPIES = 8
max_dump_file_size = 10240      # limit trace file size to 5 Meg each
max_rollback_segments = 100
# nls_data_format added 11/21/99
nls_date_format = "DD-MON-RR"
open_cursors = 1000
#partition_view_enabled = TRUE
pre_page_sga = TRUE
# processes =  3500             # see parallel_automatic_tuning 40+6+5(4*4)=816
#  Pat O changed remote_login_passwordfile = EXCLUSIVE 11/21/00
remote_login_passwordfile = EXCLUSIVE
#rollback_segments =
(rseg01_01,rseg01_02,rseg01_03,rseg01_04,rseg01_05,rseg01_06,rseg01_07,rseg01_08,
rseg01_09,rseg01_10)
shared_pool_reserved_size = 22500000
shared_pool_size =  45000000
sort_area_size=8192000
sort_area_retained_size=8192000
timed_statistics = true       # if you want timed statistics
user_dump_dest=D:\DATA\ORADBA\ADMIN\DFWP\UDUMP
#JOB_QUEUE_PROCESSES = 3
#Parameter added by Data Migration Assistant
compatible = 8.1.6           # updated 6/13/2000 PJC enable transportable TS
db_domain = world
# Parallel Tuning pjc 7/20/00
parallel_automatic_tuning = TRUE
optimizer_percent_parallel = 10     # 100/# concurrent_users
transactions =  942      # default 1.1 * PROCESSES + 5 = 902
transactions_per_rollback_segment=98     #5/22/01 # rbs
started=trans/trans_per_rbs (98 is max value)

=====================================================================
MY QUESTION IS WHAT WE CAN DO TO ELIMINATE ABOVE "ARC2: Failed to archive log# 2
seq# XXXX" to happen?

I have set LOG_ARCHIVE_TRACE =95, if you want, I can email you the trace file it
generated for the last 2 days.

      and we said...
It is just a side effect of having more then one archiver.

If automatic archiving is turned on then all of the archiver process will try
to archive this logfile, however they will not be able to aquire the lock
'kcrrlt',
the lock to protect multiple arch processes from archiving the same logfile.

The failing process process will write to the trace/alert message that it was
unable to archive the logfile.

The file is successfully archived by some other process.

In 9i, this message is a little more informative:

 ARC1: Beginning to archive log# 2 thrd# 1 seq# 24
 ARC1: Unable to archive log# 2 thrd# 1 seq# 24
       Log actively being archived by another process

Review &
      Followup

      Rating:
      1
  January 04, 2002
Reviewer:  Bhadresh
      Patel  from Gujarat,India
It's totally a different case you have answered. This is tmporarily solution.
I'm also facing a same problem. Give me suggession for permanent solution.

1) I have added one more redo log file with mirror image but still problem is
coming. So If I'll increase the size of all redo log files by creating a new
redo log files and dropping the existing files. will it solve my problem? If
not, please suggest other permanent solution.

Please give me the answer as soon as possibe.
Thanks & regards
bhadresh

          Followup:

I don't believe you read the answer so I'll repeat it:

It is just a side effect of having more then one archiver.

If automatic archiving is turned on then all of the archiver process will try
to archive this logfile, however they will not be able to aquire the lock
'kcrrlt',
the lock to protect multiple arch processes from archiving the same logfile.

The failing process process will write to the trace/alert message that it was
unable to archive the logfile.

The file is successfully archived by some other process.

In 9i, this message is a little more informative:

 ARC1: Beginning to archive log# 2 thrd# 1 seq# 24
 ARC1: Unable to archive log# 2 thrd# 1 seq# 24
       Log actively being archived by another process

-------------------------------------------------------

You see, this is a benign, harmless warning.  I offered no "temporary" (or
permanent or in fact ANY) solution here -- its just a harmless warning.

adding more redo's, having them mirrored -- that won't make it "go away".  You
want it to go away -- use one archiver process only.

      Rating: 4
Similar
      problem  January 04, 2002
Reviewer:  Mikel
      Jurisich  from London UK
Hi Tom,

I too have noticed this problem. I'd determined that it wasn't a problem as you
say, but thought I'd try to prevent it being generated by limiting each db to
one archiver process via the log_archive_max_processes init.ora parameter - as
my understanding of this was that it controls the max no. allowed. But setting
this is one was - during heavy periods - still triggering the automatic startup
of another archiver process leaving me with two running despite the parameter
saying only one should be allowed.

Any thoughts on how to ensure only one archiver process is running - and why
this parameter doesn't do what I thought it should be doing?

Cheers,

Mikel

          Followup:

This is fixed in 9.0.1, it appears to have been back ported to 817.x as well.
Contact support, reference bug1921532

      Rating:
      5
  January 04, 2002
Reviewer:  Jeff
      Hunter  from Greenwich, CT, USA
I love it! You should re-title this to "How to impact the performance of your
database so your alert.logs look cleaner."

      Rating: 4
Why only
      sometimes?  December 18, 2002
Reviewer:  Venky
      Dev  from Seattle, USA
I have 5 arc processes running.  I see the "failed to archive" only sometimes
and not all the time a redo log is archived.  I would expect based on your
answer to happen every time.  Why does it happen only sometimes?

          Followup:

because only sometimes do they go for the same file at the same time.

      Rating: 4
Automatic
      archiving not restarting  April 21, 2003
Reviewer:
      kumar  from India
Tom,

We have an oracle 8.1.7 database on Win NT. When there is no diskspace
available for automatic archiving, the archiver process records an error in the
alert log file and the db stops responding. Even after I make the space
available by clearing the files, the archiver continues to give the same error
message. Accoring the Oracle Server Admin Guide, it has to restart archiving
automatically once the space is made available. Why is this not happening ?
Also REOPEN_SECS from v$archive_dest shows 0. But the still the archiver tries
every 5 mins - which according to the manual is default. Why is this not
displayed in the V$archive_dest ? Is there some other view to see this info?

          Followup:

I'll let you contact support for this one.  It gets "unstuck" for me, always
has.

I can only guess that not enough space was cleared perhaps.

did you try kickstarting it with alter database archivelog all after clearing
space.

      Rating: 3
Manual
      Archiving also gives the error  April 21, 2003

Reviewer:  kumar  from India
Tom,

I failed to mention one thing in my last posting. That, when I shutdown the
instance and re-started it the archiver started archiving without further
errors. When I got the problem and after clearing the space, I tried manual
archiving also. It did not help - gave the same error. The STATUS column of
v$archive_dest showed the status as 'ERROR' once the problem occured and
remained so until I re-started the instance.

Thanks for the response.

          Followup:

they i would suspect your "clearing" process -- how did you do it, did it really
work -- did you verify the OS thought "there is free space" (and you HAVE filed
a TAR right)

      Rating: 3
Got the
      solution  April 23, 2003
Reviewer:  kumar
       from India
Tom,

Thanks for the response. It took some time for me to get the access details for
the metalink. I could find some threads over there on the same problem. They
have suggested various solutions. I recreated the problem and tried them. One of
the solution worked for me. I am giving that one here in case some one else
needs it. After making enough space available, I gave the following commands
ALTER SYSTEM ARCHIVE LOG STOP;
ALTER SYSTEM ARCHIVE LOG START;
Now it started the archiving process.

Now i have one small doubt - will the above steps affect the recovery process in
case i need to do media recovery in future ?

          Followup:

No they will not.

      Rating: 5
i thought there
      was something wrong with the archiving process!  May 05,
      2003
Reviewer:  Alvin  from Philippines
Tnx for the clarification !

Btw, how would i know how many archiving proccess running and how do i increase
/decrease them ?

ARC4: Failed to archive....

does this mean that i have around 4 ??

          Followup:

select program from v$process where program like '%(ARC_)%'

it'll start them as it needs them.  you can set log_archive_max_processes to
some degree as well.

May 06, 2003 David
      -- Thanks for the question regarding "export using QUERY", version 8.1.7

      You Asked
Dear Tom,

I find that Oracle8i can export table data selectively by using QUERY. Below is
the exp that I wrote for it. This is the first time I used it and was not
successful. The error message is:

table_export[2]: CUA_TRANS_DTS:  not found.

Is there anything wrong in the syntax?

exp ddd/ddd file=/dbf/u11/customer.dmp tables=AASC.AST_CUSTOMER_KEEP
query=\'where CUA_TRANS_DTS \< add_months\(sysdate, -6\)\'

Best Regards

      and we said...
The method by which you specify the QUERY= parameter depends on the operating
system.  A where clause generally has many special characters in it such as =, >
,<, and spaces.  The shell command prompt in Unix and Windows is not going to
like those characters very much.  They will have to be escaped and how to do
that is OS dependent.  What I prefer to do is to always use a PARFILE with the
QUERY option.  In this way, I can use the same exact methods regardless of
platform.

As an example of this, I have created a table T as select * from all_objects.  I
want to export all rows such that the object_id is less then 5000.  On Windows I
would have to execute:

C:\exp>exp userid=tkyte/tkyte tables=t query="""where object_id < 5000"""

Note that we need 3 double quotes on each size of the where clause in Windows.
Now on Unix I must use:

$ exp userid=/ tables=t query=\"where object_id \< 5000\"

But, if I simply use a parameter file with this in it:

query="where object_id < 5000"

I can now use the single command:

exp userid=/ tables=t parfile=exp.par

on both systems without change.  I find that to be much easier then trying to
properly escape the QUERY strings on each platform.

Review &
      Followup

      Rating:
      5
  April 10, 2001
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating:
      4
  April 10, 2001
Reviewer:  Vijay
       from Canada

      Rating: 5
Export Query
      Option  April 11, 2001
Reviewer:
      A reader
As always, Tom answered my question and helped me to find out a way that Oracle
Expert used.

Best Regards Tom

      Rating:
      5
  April 11, 2001
Reviewer:  Chuck
       from VA USA

      Rating:
      5
Branka  January 08, 2002
Reviewer:
      Branka Todorovic  from USA

      Rating: 4
Can I join the
      table, here?  October 10, 2002
Reviewer:
      Prince
Tom,
I want to extract (export) the data from a child table, only knowing the parent
table's where clause and then joing with the child table.

Ex,

CREATE TABLE P (ID         number(8) primary key,
                TSTAMP     date
                );

create table c (id      number(8),
                id2     number(8),
           constraint fk_c foreign key (id) references p) ;

I want to export the data for the following query. (dump all the records from c,
where its parent's tstampo is more than two months old)

select c.* from c, p where p.id = c.id and p.tstamp < add_months(sysdate, -2)

Can I do it using exp? What would be the other options?

Thank You!

          Followup:

the where clause would be:

where id in ( select id from p where tstamp < add_months(sysdate-2) )

That, when applied against C, gives you the rows you want.

      Rating: 4
Please ignore
      the prev posting. I got it working  October 10, 2002

Reviewer:  Prince
Tom,

Please ignore the previous posting. I got it working now.

      Rating:
      3
export..  May 06, 2003
Reviewer:  vj
       from in
the export with query...

1. first of all can i do 3 tables with 3 different queries in a single export..

guess no...if no,

then i wanted to have a single dmp file for these 3..

in the sense, is there any way to append into the dmp file..otherwise i have a
problem of sending too many dump files as quite a few tables requires the query
option while export..

rgds

          Followup:

there is no syntax to support three different where clauses and somehow
associate them with the right tables -- so no. you would need three exp commands
to separate dmp files.

there are tools like

$ tar cf - f1.dmp f2.dmp f3.dmp | gzip > all.tar.gz

in unixland and I'm sure you could find something like 'zip' in windows if that
is your platform to solve the multi-file issue as well as descrease considerably
the size of the files you need to send.

May 06, 2003 muhammad ahmad -- Thanks for the question
      regarding "check constraint", version 8.1.7

      You Asked
I want a check constraint that allow to enter an even number in a numeric column
and return error otherwise.

      and we said...
check( mod(column,2) = 0 )

ops$tkyte@ORA920> create table t ( x int
constraint x_must_be_even check ( mod(x,2) = 0 ) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( 0 );

1 row created.

ops$tkyte@ORA920> insert into t values ( 1 );
insert into t values ( 1 )
*
ERROR at line 1:
ORA-02290: check constraint (OPS$TKYTE.X_MUST_BE_EVEN) violated

ops$tkyte@ORA920> insert into t values ( 2 );

1 row created.

May 06, 2003 lou -- Thanks for the question regarding "Which
      one is the best index?", version 9.0.2

      You Asked
Hi tom,

If you have the chance to create indexes on a applications. Would go
with oracle text kind of indexes or regular indexes. This are pretty
big tables and the users do a lot searches...The code is primarily
dynamic SQL like the one below...it is throghtout the whole applications.

str :=
                str
             || ' and upper(rtrim(ltrim(batch_apis.batch))) like ''%'
             || UPPER (RTRIM (LTRIM (p_batch)))
             || '%''';

Lou

      and we said...
I PRAY that it is not that way.

It MUST be:

  str := str || ' and uper(rtrim(ltrim(batch_apis.batch))) like :bv'

...

and later you would

  open ref_cursor for str using '%' || UPPER (RTRIM (LTRIM (p_batch))) || '%';

that is really how you do it -- right?  that would be with bind variables.
If not, you have definitely done this really wrong.

There is insufficient data to answer your question.

For example, if batch is a string like:

ABCDEFGHIJKLMNOP

and you pass in 'H' -- Oracle text would be useless.  It indexes WORDS -- so the
WORD here would be ABCDEFGHIJKLMNOP.

Now, if batch is a string like:

How Now Brown Cow

and you pass in 'Brown', Oracle text would be the correct and appropriate and
PERFORMANT approach and the query would become:

  'and contains( batch, :bv ) > 0'

and you would

 open refcursor for str using p_batch;

Review &
      Followup

      Rating: 4
The whole
      code  May 06, 2003
Reviewer:  Lou  from
      new york
But how about what kind of index if I am using your approach?? I have submit the
whole code, would you please show me how can I re-write it using bind variable.

thank you....very much

FUNCTION view_log (
      p_batch                IN   VARCHAR2 DEFAULT NULL,
      p_name                 IN   VARCHAR2 DEFAULT NULL,
      p_coa                  IN   VARCHAR2 DEFAULT NULL,
      p_batch_size           IN   VARCHAR2 DEFAULT NULL,
      p_batch_size_units     IN   VARCHAR2 DEFAULT NULL,
      p_manufacturer         IN   VARCHAR2 DEFAULT NULL,
      p_date_of_manuf_from   IN   VARCHAR2 DEFAULT NULL,
      p_date_of_manuf_to     IN   VARCHAR2 DEFAULT NULL,
      p_manuf_batch_no       IN   VARCHAR2 DEFAULT NULL,
      p_description          IN   VARCHAR2 DEFAULT NULL,
      p_comments             IN   VARCHAR2 DEFAULT NULL,
      p_received_date_from   IN   VARCHAR2 DEFAULT NULL,
      p_received_date_to     IN   VARCHAR2 DEFAULT NULL,
      p_received_location    IN   VARCHAR2 DEFAULT NULL,
      p_order                IN   VARCHAR2 DEFAULT NULL
   )
      RETURN cursortype_api
   IS
      v_log                  cursortype_api;
      str                    VARCHAR2 (3000);
      v_order                VARCHAR2 (50);
      v_date_of_manuf_from   DATE
                              := TO_DATE (p_date_of_manuf_from, 'mm/dd/yyyy');
      v_date_of_manuf_to     DATE
                                := TO_DATE (p_date_of_manuf_to, 'mm/dd/yyyy');

      v_received_date_from   DATE
                              := TO_DATE (p_received_date_from, 'mm/dd/yyyy');
      v_received_date_to     DATE
                                := TO_DATE (p_received_date_to, 'mm/dd/yyyy');
   BEGIN
      ----building select statement--------------
      str :=
         'Select
                  batch_apis.ID,
                  batch_apis.batch BATCH,
                  COMPONENTS_LKUP.component_name NAME,
                  nvl(batch_apis.coa,''.'') COA,
                  nvl(batch_apis.batch_size,''.'') BATCH_SIZE,
                  nvl(batch_apis.batch_size_units,''.'') BATCH_SIZE_UNITS,
                  manufacturer_lkup.name MANUF,
                  nvl(to_char(batch_apis.date_of_manuf, ''mm/dd/yyyy''),''.'')
DATE_OF_MANUF,
                  nvl(batch_apis.manuf_batch_no,''.'') MANUF_BATCH_NO,
                  nvl(batch_apis.description,''.'') DESCRIPTION,
                  nvl(to_char(batch_apis.received_date, ''mm/dd/yyyy''),''.'')
RECEIVED_DATE,
                  nvl(locations_lkup.location,''.'') RECEIVED_LOCATION,
                  nvl(batch_apis.comments,''.'') COMMENTS';
      ---------define table(s) to select from
------------------------------------
      str :=
             str
          || ' from batch_apis, manufacturer_lkup, products ,
                    locations_lkup, COMPONENTS_LKUP';
      ----------define where
conditions-------------------------------------------
      str :=
             str
          || ' where upper(rtrim(ltrim(batch_apis.record_status))) =
''CURRENT''';
      str := str || '   and product_fk_id      = products.id(+)';
      str := str || '   and manufacturer_fk_id = manufacturer_lkup.id(+)';
      str := str || '   and received_location_fk_id    = locations_lkup.id(+)';
      str := str || '   and COMPONENTS_LKUP.id =PRODUCTS.component_fk_id';

      IF p_batch <> ' '
      THEN
          str :=
                str
             || ' and upper(rtrim(ltrim(batch_apis.batch))) like ''%'
             || UPPER (RTRIM (LTRIM (p_batch)))
             || '%''';

      END IF;

      IF p_name <> ' '
      THEN
         str :=
                str
             || ' and upper(rtrim(ltrim(COMPONENTS_LKUP.component_name))) like
''%'
             || UPPER (RTRIM (LTRIM (p_name)))
             || '%''';

      END IF;

      IF p_coa <> ' '
      THEN
         str :=
                str
             || ' and upper(rtrim(ltrim(batch_apis.coa))) like ''%'
             || UPPER (RTRIM (LTRIM (p_coa)))
             || '%''';
      END IF;

      IF p_batch_size <> ' '
      THEN
         str :=
                str
             || ' and upper(rtrim(ltrim(batch_apis.batch_size))) like ''%'
             || UPPER (RTRIM (LTRIM (p_batch_size)))
             || '%''';
      END IF;

      IF p_batch_size_units <> ' '
      THEN
         str :=
                str
             || ' and upper(rtrim(ltrim(batch_apis.batch_size_units))) like ''%'
             || UPPER (RTRIM (LTRIM (p_batch_size_units)))
             || '%''';
      END IF;

      IF p_manufacturer <> ' '
      THEN
         str :=
                str
             || ' and upper(rtrim(ltrim(manufacturer_lkup.name))) like ''%'
             || UPPER (RTRIM (LTRIM (p_manufacturer)))
             || '%''';
      END IF;

      IF p_date_of_manuf_from <> ' ' AND p_date_of_manuf_to <> ' '
      THEN
         str :=
                str
             || ' and batch_apis.date_of_manuf between '''
             || v_date_of_manuf_from
             || '''';
         str :=
              str || '                    and ''' || v_date_of_manuf_to
              || '''';
      END IF;

      IF p_manuf_batch_no <> ' '
      THEN
         str :=
                str
             || ' and upper(rtrim(ltrim(batch_apis.manuf_batch_no))) like ''%'
             || UPPER (RTRIM (LTRIM (p_manuf_batch_no)))
             || '%''';
      END IF;

      IF p_description <> ' '
      THEN
         str :=
                str
             || ' and upper(rtrim(ltrim(batch_apis.description))) like ''%'
             || UPPER (RTRIM (LTRIM (p_description)))
             || '%''';
      END IF;

      IF p_received_date_from <> ' ' AND p_received_date_to <> ' '
      THEN
         str :=
                str
             || ' and batch_apis.received_date between '''
             || v_received_date_from
             || '''';
         str :=
              str || '                    and ''' || v_received_date_to
              || '''';
      END IF;

      IF p_received_location <> ' '
      THEN
        str :=
                str
             || ' and upper(rtrim(ltrim(locations_lkup.location))) like ''%'
             || UPPER (RTRIM (LTRIM (p_received_location)))
             || '%''';

       END IF;

      IF p_comments <> ' '
      THEN
         str :=
                str
             || ' and upper(rtrim(ltrim(batch_apis.comments))) like ''%'
             || UPPER (RTRIM (LTRIM (p_comments)))
             || '%''';
      END IF;

      ----------define order by
conditions----------------------------------------
      IF p_order = 'Batch Number'
      THEN
         v_order := 'batch_apis.batch';
      ELSIF p_order = 'API'
      THEN
         v_order := 'COMPONENTS_LKUP.component_name';
      ELSIF p_order = 'COA'
      THEN
         v_order := 'batch_apis.coa';
      ELSIF p_order = 'Batch Size'
      THEN
         v_order := 'batch_apis.batch_size';
      ELSIF p_order = 'Batch Size Units'
      THEN
         v_order := 'batch_apis.batch_size_units';
      ELSIF p_order = 'Manufacturer'
      THEN
         v_order := 'manufacturer_lkup.name';
      ELSIF p_order = 'Manufactured Date'
      THEN
         v_order := 'batch_apis.date_of_manuf';
      ELSIF p_order = 'Manuf. Batch No.'
      THEN
         v_order := 'batch_apis.manuf_batch_no';
      ELSIF p_order = 'Description'
      THEN
         v_order := 'batch_apis.description';
      ELSIF p_order = 'Received Date'
      THEN
         v_order := 'batch_apis.received_date';
      ELSIF p_order = 'Received Location'
      THEN
         v_order := 'locations_lkup.location';
      ELSIF p_order = 'Comments'
      THEN
         v_order := 'batch_apis.comments';
      END IF;

      IF v_order IS NOT NULL
      THEN
         str := str || ' order by ' || v_order;
      END IF;

      --return str;
      OPEN v_log FOR str;
      RETURN v_log;
      CLOSE v_log;
   END view_log;

          Followup:

there would not be any indexes -- they would be useless for leading an trailing
percents like that with a like clause

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279
to see how I would 'bind' that using an application context.

May 06, 2003 wallacel -- Thanks for the question regarding
      "mts config", version 8.1.6

      You Asked
Tom:

I shutdown the database, stop listener, and then listener start,

startup database, then connect scott/tiger from a client
machine, when i query from client machine, it shows to me
that scott use "shared" server, but when i query it from
server , it gives me "none", what "none" means?
also i found that at server, all client session is "none"
at client,other client shows "none" and itself shows "shared"
can you give me an exaplain why this inconsistent situation
exists, and what "none" means?

===============================================================
at server machine
connect system/manager
sql>select username,server from v$session;

USERNAME                       SERVER
------------------------------ ---------
                               DEDICATED
                               DEDICATED
                               DEDICATED
                               DEDICATED
                               DEDICATED
                               DEDICATED
                               DEDICATED
                               DEDICATED
                               DEDICATED
                               DEDICATED
                               DEDICATED
SCOTT                          NONE
SYSTEM                         DEDICATED

at client machine
connect scott/tiger
sql>select username,server from v$session;
USERNAME   SERVER
---------- ----------
           DEDICATED
           DEDICATED
           DEDICATED
           DEDICATED
           DEDICATED
           DEDICATED
           DEDICATED
           DEDICATED
           DEDICATED
           DEDICATED
           DEDICATED
SCOTT      SHARED
SYSTEM     DEDICATED

as you can see, one of them for scott is shared, which make
sense to me, the other for scott is none, which i don't
understand.

      and we said...
Ok, it is clear with this followup what is happening...

When you connected on the server -- you did so as
SYSTEM/MANAGER.  The Scott/tiger session was just "hanging out",
not doing anything.  When SYSTEM ran the query and scott was
idle, scotts connection is "NONE" -- he is not doing anything.

In the second case, you ran the query as scott.  Scott is in
fact doing something at that point in time -- hence he is using
a shared server connection (by definition he is doing something
-- he is running the query).

Only when an MTS connection is actually DOING something will the
server column be shared.

You should query STATUS along with SERVER.  When STATUS =
INACTIVE, server = NONE.  When status = ACTIVE, server = SHARED.

Dedicated is of course dedicated and will be set that way
regardless of activity.
Review & Followup

      Rating:
      4
review  October 29, 2002
Reviewer:
      Irfan  from Kuwait
While creating database(oracle 9i) I opted for dedicated server mode.(we are
using 9iAS as middle tier and front end is java based)

But when i started database it was running in shared server mode.All the
performance ratios are good. Init.ora is showing following.

dispatchers="(PROTOCOL=TCP)(SER=MODOSE)",
"(PROTOCOL=TCP)(PRE=oracle.aurora.server.G
iopServer)", "(PROTOCOL=TCP)(PRE=oracle.aurora.server.SGiopServer)"

1. I want to run the database in dedicated server mode.
What I have to do to run the database in dedicated server mode?
2. For running database in deicated server mode should I remove the
large_pool_size from the init.ora. What other parameters should i
increase/decrease from init.ora

3. I read that if you are running dedicated server mode you must have enough
resorces. What are those resources ?

          Followup:

so, how do you know that it is in MTS mode?  what does

select server, count(*) from v$session group by server;

return.

2) any parameter starting with MTS_ can be looked at for removal.

3) you need resources for MTS as well -- if you have my book "expert one on one
oracle" i go into the architecture and how these things work.  You really need
to understand that in order to understand a comment like that (and how "weak"
that comment by itself is!)

      Rating:
      4
Review  October 29, 2002
Reviewer:
      Irfan  from Kuwait
I have your book and going through it. The query
select server, count(*) from v$session group by server;
returns
SQL> select server, count(*) from v$session group by server;

SERVER      COUNT(*)
--------- ----------
DEDICATED          8

BUT, if I issue query
select username, status, server from v$session;

I found some that some INACTIVE session having NONE in the SERVER column. And
Alert.log also showing following

starting up 1 shared server(s) ...
starting up 1 dispatcher(s) for network address
'(ADDRESS=(PARTIAL=YES)(PROTOCOL=TCP)
)'...
starting up 1 dispatcher(s) for network address
'(ADDRESS=(PARTIAL=YES)(PROTOCOL=TCP)
)'...
starting up 1 dispatcher(s) for network address
'(ADDRESS=(PARTIAL=YES)(PROTOCOL=TCP)
:

That made me to belelive that DB is running in shared server mode.

Please comment.

thanks

          Followup:

You are running in shared server mode -- but I'm confused.

The "select server, count(*)" would have returned NONE with a count as well -- I
don't see how "select username, status, server" could have returned NONE without
the aggregate query returning none.

Anyway -- you have MTS configured (i knew that from your init.ora).  lsnrctl
status (which I've sort of asked for a couple of times) would tell you if they
were connecting using that.  They are -- according to that query with NONE.

You are using MTS, you need to set the large_pool_size big enough for all of the
UGAs OR you need to stop using MTS.

      Rating:
      5
Review  October 31, 2002
Reviewer:
      Irfan  from Kuwait.
Sorry to confuse you regarding MTS.
select server, count(*) from v$session group by server;
I had taken the output just after restarting the database. that is why I got
only dedicated connections information. I normal working hours above query
returns NONE status.

I must thanks you for your valuable advice and support.
You Book expert one-on-one came to my rescue.

From your book, page 87 (dedicated server versus shared server. You mentioned to
make sure that transaction should be short. Though ours is OLTP application but
most of our transaction were taking 3-4 seconds(which involves validation from
lots of tables) to complete and these were monopolising the resources. When I
switched to dedicated server mode. Our system become very stable and fast.

Thanks a lot for the excellent book.

      Rating: 5
MTS_DISPATCHERS
      retired in 9i ?  January 07, 2003
Reviewer:
      David
I found this very strange siutuation using MTS (now called simply Shared
Server).

I'm using 9.0.2.1. The reference manual for this version doesn't mention
MTS_DISPATCHERS any longer -- only DISPATCHERS:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96536/ch14.htm#1014425
SQL> show parameter dispatchers

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
dispatchers                          string      (PROTOCOL=TCP) (SERVICE=orades
                                                 XDB)
max_dispatchers                      integer     5
mts_dispatchers                      string      (PROTOCOL=TCP) (SERVICE=orades
                                                 XDB)
mts_max_dispatchers                  integer     5

* I just want to set up 4 tcp dispatchers for my web application. But Oracle
already has this default disptacher for XDB.

SQL> alter system set dispatchers='(pro=tcp)(disp=4)';

System altered.

SQL> show parameter dispatchers

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
dispatchers                          string      (pro=tcp)(disp=4)
max_dispatchers                      integer     5
mts_dispatchers                      string      (pro=tcp)(disp=4)
mts_max_dispatchers                  integer     5

* It seems DISPATCHERS and MTS_DISPATCHERS are kind of synonyms now.

* However, for some reason, I cannot save the new MTS configuration to my
spfile:

SQL> alter system set dispatchers='(pro=tcp)(disp=4)' scope=spfile;
alter system set dispatchers='(pro=tcp)(disp=4)' scope=spfile
                                                 *
ERROR at line 1:
ORA-02065: illegal option for ALTER SYSTEM

SQL> alter system set dispatchers='(pro=tcp)(disp=4)' scope=both;
alter system set dispatchers='(pro=tcp)(disp=4)' scope=both
                                                 *
ERROR at line 1:
ORA-02065: illegal option for ALTER SYSTEM

SQL> alter system set dispatchers='(pro=tcp)(disp=4)' scope=memory;
alter system set dispatchers='(pro=tcp)(disp=4)' scope=memory
                                                 *
ERROR at line 1:
ORA-02065: illegal option for ALTER SYSTEM

* To make sure I'm using my SPFILE:

SQL> select name from v$spparameter where rownum=1;

NAME
--------------------
tracefile_identifier

* For example, when I try to save some other parameter, it works:

SQL> alter system set cursor_sharing=exact scope=both;

System altered.

* My alter system is only saving to memory, strangely, and it doesn't take the
SCOPE keyword (look at my first ALTER SYSTEM). Shouldn't the default be BOTH ?

* I thought of creating a pfile from spfile, changing dispatchers and then
creating spfile from pfile. But it wouldn't work online. I'd have to shutdown,
since my spfile is in use by the instance.

Questions:

1) How can I save my mts config to the spfile online ?
2) Why doesn't my alter system work for specific parameters with the the scope
keyword ?
3) Shouldn't BOTH be the default ?
4) Oracle already has a dispatcher for XBD. It seems that changing to my new
configuration disables this previous setting. How does one affect the other ?
Should they co-exist ? What should I do here ?
5) After all, are MTS_DISPATCHERS and the other MTS_ parameters made obsolete ?

          Followup:

1, 2, 3) being worked on as bug2080569, fixby 9203 currently.
4) the alter is broken for this right now, normally you would use a comma
delimited list

5) yes. they are.

....
Note:

This parameter is deprecated in favor of the DISPATCHERS parameter. Oracle
recommends that you use DISPATCHERS instead. MTS_DISPATCHERSis retained for
backward compatibility only.
.......

      Rating: 5
More than one
      dispatcher def in alter system  January 09, 2003

Reviewer:  David
Continuing my last review, about question 4):

1) You mean I could do:

SQL> alter system set
dispatchers='(pro=tcp)(service=oradesXDB),(pro=tcp)(disp=4)';

?

2) At a glance, I couldn't find any reference to this new syntax for multiple
dispatcher defs (comma-saparation). Where can I find it ?

          Followup:

1) did you try it?

2)
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96536/ch13.htm
all parameters that have multi-values (utl_file dir for example) can be done
either on multiple lines or in a list.

      Rating: 3
Role of
      dispatcher ans server process  April 28, 2003

Reviewer:  Reader  from USA
Hi Tom

This might be a silly and very basic question but pls. let me ask.
In your book you have written that when user request for a connection, TNS
service sends the request to Listener and Listener connects it to the dispatcher
and then dispatcher sends the request to any free shared server process.
Suppose If we have 10 dispatcher and 10 shared server and 10 users are connected
to 10 dispatchers and processing is going on for all the connections then what
will happen with 11th user. Would it get connection or not?

Pls explain me the roll and functionality of dispatchers and shared server
processs?

Thanks

      Rating: 3
MTS related
      question  May 06, 2003
Reviewer:  Habibulla
       from Chennai, India
Hi Tom, Pl answer my question:
We have  oracle 8i in our company running in Novel netware operating system.
(H/w config --> 40 GB 4 hdd, 2 GB RAM, Dual processor etc. basically good
configuration but "No Raid" mechanism).

Oracle installed in normal way and running fine.

Is it possible for us to convert our oracle server to MTS. If possible, what are
the steps to be followed. If it is complicated, atleast tell me "brief steps",
so that I can refer further details in book.

Thanks a lot.

          Followup:

yes it is possible -- but make sure it is necessary.  MTS (shared server) has a
longer code path then dedicated server.  you would use it when you have maxed
out the server due to lots of processes.

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/network.817/a76933/mts.htm#450520

May
      06, 2003 Arun -- Thanks for the
      question regarding "Tuning based on explain plan cost ???", version
9.2.0.3

      You Asked
Tom
Recently, a project has decided to tune only those SQL statements which have a
cost of greater than x, where x is some number. They asked me what my
recommendation would be about the value of x. In my opinion, this is the most
unreasonable (for the want of a better word) question. I mean, how can anyone
say that tune only those SQL statements which have a cost greater than, say 100
or 75? I think using this kind of tuning methodology is fundamentally wrong. At
least this is what I have learnt from your forum over the years. Please let me
know if this is wrong.
Thanks

      and we said...
you better set x = 1

and so -- what would the goal be here?  to get the cost down?  if that is the
case, we can just set some init.ora parameters to make them happy with the plans
they have.  Consider:

ops$tkyte@ORA920> create table t as select * from all_objects;

Table created.

ops$tkyte@ORA920> alter table t add constraint t_pk primary key(object_id);

Table altered.

ops$tkyte@ORA920> analyze table t compute statistics for table for all indexes
for all indexed columns;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> alter session set optimizer_index_cost_adj = 100;

Session altered.

ops$tkyte@ORA920> select * from t where object_id between 1 and 50;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=38 Card=43 Bytes=4128)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=38 Card=43 Bytes=4128)
   2    1     INDEX (RANGE SCAN) OF 'T_PK' (UNIQUE) (Cost=2 Card=43)

ops$tkyte@ORA920> alter session set optimizer_index_cost_adj = 1;

Session altered.

ops$tkyte@ORA920> select * from t where object_id between 1 and 50;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=43 Bytes=4128)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=43 Bytes=4128)
   2    1     INDEX (RANGE SCAN) OF 'T_PK' (UNIQUE) (Cost=2 Card=43)

ops$tkyte@ORA920> set autotrace off

now tell me -- honestly -- is that second plan "better"?  It is certainly
"cheaper".  is it going to run faster?  It is 19 times LESS COSTLY -- oh but
wait, it is after all the same plan isn't it....

Hmm

They would be kidding themselves going down this path.

You tune queries by logical IO's -- your goal -- reduce them to a minimum.  That
means you actually have to load up and test against a real life sized database
with real life sized (and realistic) datasets.

Review &
      Followup

      Rating:
      5
  May 06, 2003
Reviewer:
      A reader
Hi Tom,

Very good exapmle..

Could cost affects execution plan ?

          Followup:

it is the other way around -- the execution plan is determined by the cost, the
lowest cost plan -- wins.

May 06, 2003 patibandla -- Thanks for the question regarding
      "Importaince of coding in begin block - reg", version 8.1.6.0

      You Asked
Hi tom

I have two procedures like this

CREATE OR REPLACE procedure proc1(x pls_integer,y pls_integer)
is
z pls_integer DEFAULT GREATEST(X,Y);
begin
dbms_output.put_line(z);
end;
/

CREATE OR REPLACE procedure proc2(x pls_integer,y pls_integer)
is
z pls_integer ;
begin
z:=greatest(x,y);
dbms_output.put_line(z);
end;
/

In most of the examples u have coded  procedure in proc1 style.
i.e. assigning the value in declare section itself wherever
it is possible.

I feel coding in proc1 style increases the performance.
I don't know how internally it is processed.

pl. explain me why,where are the situations we have to code in
proc1  style.

Regards

Koshal

      and we said...
They are the same code wise - they MUST do the same exact work -- it would be
impossible for one to be faster then then other.

Using the profiler:

tkyte@TKYTE816> set serveroutput off
tkyte@TKYTE816> exec dbms_profiler.start_profiler('proc1')

PL/SQL procedure successfully completed.

tkyte@TKYTE816> begin
  2          for i in 1 .. 10000 loop
  3                  proc1(i,i-1);
  4          end loop;
  5  end;
  6  /

PL/SQL procedure successfully completed.

tkyte@TKYTE816> exec dbms_profiler.stop_profiler;

PL/SQL procedure successfully completed.

tkyte@TKYTE816>
tkyte@TKYTE816>
tkyte@TKYTE816> exec dbms_profiler.start_profiler('proc1')

PL/SQL procedure successfully completed.

tkyte@TKYTE816> begin
  2          for i in 1 .. 10000 loop
  3                  proc2(i,i-1);
  4          end loop;
  5  end;
  6  /

PL/SQL procedure successfully completed.

tkyte@TKYTE816> exec dbms_profiler.stop_profiler;

PL/SQL procedure successfully completed.

I find:

GRAND_TOTAL
-----------
        .64

=
=
====================
Total time spent on each run

RUNID RUN_COMMENT    SECS
----- ----------- -------
    3 proc1           .32
    4 proc1           .32

=
=
====================
Percentage of time in each module, for each run separately

RUNID RUN_COMMENT UNIT_OWNER  UNIT_NAME         SECS PERCEN
----- ----------- ----------- -------------- ------- ------
    3 proc1       OPS$TKYTE   PROC1              .12   37.9
    3 proc1       <anonymous> <anonymous>        .11   35.2
    3 proc1       SYS         DBMS_OUTPUT        .08   26.1
    4 proc1       OPS$TKYTE   PROC2              .12   37.3
    4 proc1       <anonymous> <anonymous>        .11   34.6
    4 proc1       SYS         DBMS_OUTPUT        .09   28.1

6 rows selected.

They are pretty much exactly the same.

I just find it more convienent to code:

declare
   x number := something;
begin

rather then

declare
   x number;
begin
   x := something;
   ....

Review &
      Followup

      Rating: 2
Careful when
      using functions and expressions in the declaration
      section  October 30, 2002
Reviewer:  B.
      Robinson  from Hartford, CT
Just a 'gotcha' I'd like to add:
If a variable is assigned with an expression or function call that results in an
error, the exception will not be caught in the exception section of the same
block but will be directly thrown up to the caller.  This may or may not matter
to you, but it is important to be aware of the differing behavior.

In the first example below, z is assigned after the BEGIN, and the division by
zero exception is trapped for the (4,0) parameters:

SQL> DECLARE
  2     PROCEDURE foo(x NUMBER, y NUMBER) IS
  3        z NUMBER;
  4     BEGIN
  5        z := x/y;
  6        DBMS_OUTPUT.PUT_LINE('z= '||z);
  7     EXCEPTION
  8        WHEN OTHERS THEN
  9           DBMS_OUTPUT.PUT_LINE('EXCEPTION: '||SQLERRM);
 10     END foo;
 11  BEGIN
 12     foo(1,2);
 13     foo(4,0);
 14  END;
 15  /
z= .5
EXCEPTION: ORA-01476: divisor is equal to zero

PL/SQL procedure successfully completed.

But if z is assigned *before* the BEGIN, the exception is raised directly up to
the outer block, bypassing the local exception section:

SQL> DECLARE
  2     PROCEDURE foo(x NUMBER, y NUMBER) IS
  3       z NUMBER := x/y;
  4     BEGIN
  5        DBMS_OUTPUT.PUT_LINE('z= '||z);
  6     EXCEPTION
  7        WHEN OTHERS THEN
  8           DBMS_OUTPUT.PUT_LINE('EXCEPTION: '||SQLERRM);
  9     END foo;
 10  BEGIN
 11     foo(1,2);
 12     foo(4,0);
 13  END;
 14  /
z= .5
DECLARE
*
ERROR at line 1:
ORA-01476: divisor is equal to zero
ORA-06512: at line 3
ORA-06512: at line 12

This bypassing of the local exception section can also occur when assigning a
literal value in the declaration section, if the literal is too large or has the
wrong format for the variable.

      Rating:
      4
ORA-01476  April 28, 2003
Reviewer:
      A reader
Hi

I have two sql stmts to check for buffer gets as follows:

select sql_text, buffer_gets/executions as buffer
        from v$sql
       where buffer_gets/executions > 0
         and executions > 0
      order by 2

it works, but if I use it as a inline view

select x.sql_text, x.buffer
from (select sql_text, buffer_gets/executions as buffer
        from v$sql
       where buffer_gets/executions > 0
         and executions > 0
      order by 2) x
where x.buffer > 1000

I get ORA-01476 divisor is equal to zero, WHY!!!! :(

          Followup:

you are relying on the order of evaluation of predicates and you should never do
so as we can, will and frequently do change them.

select sql_text, decode( executions, 0, 0, buffer_gets/executions )
  from v$sql
 where decode( executions, 0, 0, buffer_gets/executions ) > 0
 order by 2

is the "safe" way to accomplish your goal.

also -- consider saving yourself hours of "do it yourself" and just install
statspack.  It'll give you this sort of information -- only better.  It'll tell
you how queries performed in a "window of time".  It is almost meaningless to
look at your output -- it is from the 'beginning of time'.  You really want to
look at performance over a controlled window of time -- not forever.

      Rating: 5
err arent
      inline views resolved first?  April 28, 2003

Reviewer:  A reader
Hi

I thought inline views are resolved first.... so my outer query is actually
reading the inline view results? I mean if not what´s the use :-(((((

          Followup:

SQL is 100% assured to be totally unpredicable.

It is a set based language.
It is NOT procedural code.

inline views are a syntactic nicety.  They do not, cannot imply "order of
operation".  We are free (and do) to totally rewrite the query to our liking.

inline views are great for making a query more readable for us humans -- i use
them all of the time to "build" on prior work.

For example, isn't this easier and more clear to read:

select *
  from (select sql_text,
           decode( executions, 0, 0, buffer_gets/executions ) gets_over_execs
          from v$sql )
 where gets_over_execs > 0
 order by gets_over_execs;

and even so -- even IF inline views were resolved first:

select sql_text, buffer_gets/executions as buffer
        from v$sql
       where buffer_gets/executions > 0
         and executions > 0
      order by 2

is a *buggy query*.

You have made the 100% erroneous assumption that the predicate would be done
bottom up.  It does not have to be.  Consider:

ops$tkyte@ORA817DEV> create table t ( x int );

Table created.

ops$tkyte@ORA817DEV> insert into t values ( 0 );

1 row created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select 1/x from t where x > 0 and 1/x > 0;
select 1/x from t where x > 0 and 1/x > 0
                                   *
ERROR at line 1:
ORA-01476: divisor is equal to zero

ops$tkyte@ORA817DEV> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA817DEV> select 1/x from t where x > 0 and 1/x > 0;

no rows selected

simple stats gathering and the plan is totally different.

Moral to the story: don't make any assumption about how the query will be
executed.  Do not assume any order of evaluation of predicates.  Make sure you
know what each thing actually does -- what I mean by that is -- make sure you
know things like "group by doesn't sort -- if you want the data ordered, use
ORDER BY".

I cannot count how many 'bugs' i've seen people file because "hey, all of a
sudden, my data isn't ordered".  turns out they assumed group by would sort --
and it doesn't.  worked on version x.y, do an update to a.b and it stops (cause
the plans changed).  Or it worked yesterday but someone analyzed | added an
index | <host of other things here> and now it doesn't-- must be a database
bug... (not)

      Rating: 5
just a question
      regarding inline views  May 02, 2003
Reviewer:
      A reader
Hi

If inline views are not recolved first why the following works...?

select a.*
  from (select rownum numrow, emp.*
          from emp
         where rownum < 501) a
 where numrow between 100 and 110;

Also when we use analytic functions we always query the result sets of inline
views.... (some queries we have had like 3 or 4 levels of inline views using
analytic functions!) does that mean that the same query if now works in 8i it
probably wont work in 9i?! I paste a small working example below :-(

SELECT  SUM(CASE WHEN X.ESTADO = 'Exito' AND X.ACCION = 'Crear Intercambio' AND
X.ABIERTO = 0 AND X.ID_CAU IS NOT NULL
                 THEN 1
                 ELSE 0
            END) ABIERTO,
        SUM(CASE WHEN X.ESTADO = 'Error' AND X.ACCION = 'Crear Intercambio' AND
X.ABIERTO = 0 AND X.ID_CAU IS NULL
                 THEN 1
                 ELSE 0
            END) MAL_ABIERTO,
        SUM(CASE WHEN X.ESTADO = 'Exito' AND X.ACCION = 'Cerrar Intercambio' AND
X.ABIERTO = 1
                 THEN 1
                 ELSE 0
            END) CERRADO,
        SUM(CASE WHEN X.ESTADO = 'Error' AND X.ACCION = 'Cerrar Intercambio' AND
X.ABIERTO = 1
                 THEN 1
                 ELSE 0
            END) MAL_CERRADO,
        SUM(CASE WHEN X.ESTADO = 'Exito' AND X.ACCION = 'Crear Intercambio' AND
X.ABIERTO = 1 AND X.ID_CAU IS NULL
                 THEN 1
                 ELSE 0
            END) CERRADO_MANUAL,
        SUM(CASE WHEN X.ESTADO = 'Exito' AND X.ACCION = 'Crear Intercambio' AND
X.ABIERTO = 1 AND X.ID_CAU IS NOT NULL
                 THEN 1
                 ELSE 0
            END) DEVOLVER,
        SUM(CASE WHEN  X.ESTADO = 'Pendiente'
                 THEN 1
                 ELSE 0
            END) PENDIENTES,
        SUM(CASE WHEN X.OBJID_CASE IS NULL
                 THEN X.SIN_INTERCAMBIO
                 ELSE 0
            END) SIN_INTERCAMBIO,
        MAX(TOTAL) TOTAL
   FROM (SELECT
T.OBJID_CASE,T.IOBJID,T.ID_NUMBER,T.ID_CAU,T.ACCION,T.ESTADO,T.OBJID_INTERCAMBIO,

                T.X_USUARIO,T.ABIERTO,T.TOTAL,T.SIN_INTERCAMBIO
           FROM (SELECT
R.OBJID_CASE,R.IOBJID,R.ID_NUMBER,R.ID_CAU,R.ACCION,R.ESTADO,R.OBJID_INTERCAMBIO,

                        R.X_USUARIO,R.ABIERTO,R.TOTAL,R.SIN_INTERCAMBIO,
                        ROW_NUMBER () OVER (PARTITION BY R.OBJID_CASE ORDER BY
R.IOBJID DESC) NUM1
                   FROM (SELECT Z.X_INTERCAMBIO2CASE OBJID_CASE,MAX(Z.IOBJID)
IOBJID,Z.ID_NUMBER,Z.X_ID_NUMBER ID_CAU,
                                A.X_ACCION ACCION,A.X_ESTADO
ESTADO,A.X_ACT2INTERCAMBIO OBJID_INTERCAMBIO,
                                A.X_USUARIO,Z.X_ABIERTO_CERRADO ABIERTO,NUMERO
TOTAL,
                                COUNT(Z.SIN_INTERCAMBIO) OVER (PARTITION BY
Z.SIN_INTERCAMBIO) SIN_INTERCAMBIO
                           FROM TABLE_X_ACT_LOG A,
                                (SELECT V.OBJID, V.ID_NUMBER,
V.NUMERO,I.X_INTERCAMBIO2CASE,I.OBJID IOBJID,
                                        I.X_ID_NUMBER,I.X_ABIERTO_CERRADO,
NVL(I.X_INTERCAMBIO2CASE,-1) SIN_INTERCAMBIO
                                   FROM TABLE_X_INTERCAMBIO I,
                                        (SELECT K.OBJID,  K.ID_NUMBER,
MAX(NUMERO) OVER () NUMERO
                                           FROM (SELECT C.OBJID,C.ID_NUMBER,
                                                        ROW_NUMBER() OVER (ORDER
BY C.OBJID,C.ID_NUMBER DESC) NUMERO
                                                   FROM TABLE_CASE C,
TABLE_QUEUE Q
                                                  WHERE C.CASE_CURRQ2QUEUE =
Q.OBJID
                                                    AND Q.OBJID = '268436260')
K) V
                                  WHERE I.X_INTERCAMBIO2CASE (+) = V.OBJID) Z
                          WHERE A.X_ACT2INTERCAMBIO(+) = Z.IOBJID
                       GROUP BY
Z.X_INTERCAMBIO2CASE,Z.ID_NUMBER,Z.X_ID_NUMBER,A.X_ACCION,A.X_ESTADO,

A.X_ACT2INTERCAMBIO,A.X_USUARIO,Z.X_ABIERTO_CERRADO,NUMERO,Z.SIN_INTERCAMBIO) R
                  WHERE R.ESTADO IN ('Error','Exito','Pendiente')
                    AND R.ACCION IN ('Crear Intercambio', 'Cerrar Intercambio')
                     OR R.ESTADO IS NULL
                    AND R.ACCION IS NULL) T
          WHERE T.NUM1 = 1) X;

          Followup:

I did not say they were never, just that in general -- they do not have to be.

ROWNUM is the culprit here.  ROWNUM (like aggregates, distincts, intersect,
minus, union, .... ) causes the inner view to be materialized.

      Rating: 5
sorry realized
      it;'s almost unreadble  May 02, 2003
Reviewer:
      A reader
Sorry I look the sql and I recogn it's unreadble, I will paste a small resume

select * f
  from (select * -- analytic functions here
          from (select * -- analytic functions here
                  from (select * -- analytic functions here
                          from

etc

      Rating:
      5
finally  May 06, 2003
Reviewer:
      A reader
Hi

I finally did this query, I guess it works because I used dense_rank function
:-0

select bufexec, executions, sql_text
  from
        (select dense_rank() over (order by buffer_gets/executions desc)
ranking,
               buffer_gets/executions bufexec, sql_text, executions
          from v$sql
         where executions > 10)
 where ranking <= 10

          Followup:

gee, I don't get it.

i tell you what you need to do to be safe - but you like to play with fire.
whatever.

decode( executions, 0, 0, buffer_gets/executions )

that is "safe"

buffer_gets/executions

that is "not safe"

You are once again inferring some order of operation - and assuming it'll stay
that way forever.  good luck with that.

      Rating: 5
how about
      this?  May 06, 2003
Reviewer:  A reader

Hi again

I changed the query again, using the safe way i hope....

select diskexec, bufexec, executions, sql_text
  from
        (select dense_rank() over (order by decode(executions, 0, 0,
buffer_gets/executions) desc) rank,
                decode(executions, 0, 0, buffer_gets/executions) bufexec,
                sql_text,
                executions,
                decode(executions, 0, 0, disk_reads/executions) diskexec
          from v$sql
         where executions > 10)
 where rank <= 10;

better I hope... :?

          Followup:

yes, you are protecting yourself from any possible zero divides that way.

      Rating: 5
Doctor
      Doctor... it hurts when I do this!  December 01, 2003

Reviewer:  Kevin  from Connecticut USA
So don't do that.

May 06, 2003 Yousef -- Thanks for the question regarding
      "SQL*Plus and ATAN2", version 9i

      You Asked
Dear Tom:
I hope that you have enjoyed your vication.
I have a few small questions.
I am using Oracle 9i, When I use /* */ from comment I don't get the expected
result.

Assume that I have the following stored in file called Report.sql

/* Commissions report
to be run monthly. */
COLUMN ENAME HEADING SALESMAN
COLUMN SAL HEADING SALARY FORMAT $99,999
COLUMN COMM HEADING COMMISSION FORMAT $99,990
SELECT EMPNO, ENAME, SAL, COMM
FROM EMP
WHERE JOB = 'SALESMAN' /* Includes only salesmen. */
/

When I strat Report.sql I get
DOC>to be run monthly. */

     EMPNO  SALESMAN   SALARY COMMISSION
     ----------    ----------            --------     ----------
      7499      ALLEN          $1,600      $300
      7521      WARD          $1,250       $500
      7654      MARTIN        $1,250       $1,400
      7844      TURNER       $1,500        $0

Why Don't I get it  like this
DOC>Commissions report
DOC>to be run monthly.

My second question is What is the different between ATAN and ATAN2?
I read in Oracle doc that ATAN2(n,m)  Returns the arc tangent of n and m (What
does  this mean?)
outputs are in the range of -pi to pi , depending on the signs of n and m, but
the output of ATAN(n) is in the range of -pi/2 to pi/2 .
Where can I use ATAN2

      and we said...
I went back to every version of SQLPlus to 716 -- they all behave the same.  the
first line of the comment was never printed out.  It appears to be the "way it
works".

If you

set doc off

you'll avoid getting the DOC lines at all -- otherwise, just put the /* on a
line by itself to see the "full text" of the comment.

Arc Tangents are mathematical things that -- if you don't know what they are,
you almost certainly don't have a need to use them ;)

Technically:

atan computes the inverse tangent (arc tangent) of the input value

atan2() computes the angle in the interval [-pi,pi] whose arc tangent is y/x
radians for x and y in the interval [-infinity, +infinity]. The sign of atan2()
is the same as the sign of y.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select atan( .3/.2 ), atan2( .3, .2 ) from
dual;

ATAN(.3/.2) ATAN2(.3,.2)
----------- ------------
 .982793723   .982793723

ATAN can be used to do anthing ATAN2 does -- expect it handles ZERO nicely:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select atan( .3/0 ) from dual
  2  /
select atan( .3/0 ) from dual
               *
ERROR at line 1:
ORA-01476: divisor is equal to zero

ops$tkyte@ORA817DEV.US.ORACLE.COM> select atan2( .3, 0 ) from dual
  2  /

ATAN2(.3,0)
-----------
 1.57079633

Review &
      Followup

      Rating:
      5
  August 06, 2002
Reviewer:  Yousef
      Al-Awak
Thank you so much for your help

      Rating: 4
on the similar
      note...  May 06, 2003
Reviewer:
      A reader
Hi Tom,

Is there a similar function like ATAN2 that i can use to get results when the
divisor is equal to zero.
My requirement is : I have data like this

var1       x           y        z=x/y
-------------------------------------
ABC        1          95         1/95=0.0105
DEF        2          41         2/41=0.0487
GHI        1           0         1/0 --This gives divisor equal to zero error.

I want to capture that and return as 0 rather than an error. I want it in SQL
not in PL/SQL

Thanks for your time
Venkat.

          Followup:

decode( y, 0, 0, x/y )

      Rating: 5
That was quick
      and thank you so much...  May 06, 2003
Reviewer:
      A reader

      Rating: 4
All you wanted
      to know about arctangent  May 06, 2003
Reviewer:
      Dan Kefford  from New Haven, CT
Check out this page:

http://mathworld.wolfram.com/InverseTangent.html
Looks like Oracle's ATAN2 is just like the FORTRAN implementation mentioned
about one-fourth of the way into the article.

May 06, 2003 -- Thanks for the question regarding "Max extents
      reached", version 8.1.6

      You Asked
Hi Tom,

I have encountered this error when I ran a prog to insert new records into the
building table:
ORA-01632: max # extents (505) reached in index BOSS.UNQ_BUILDING

The DDL of building is:

create table BUILDING
(
    BLDG_CODE                VARCHAR2(30)           not null,
    BLDG_NAME                VARCHAR2(128)          null    ,
    constraint PK_BUILDING primary key (BLDG_CODE)
        using index
           tablespace CC_ADDRESS_IDX
           storage
           (
              initial 29M
              next 29M
              pctincrease 0
        )
)
pctfree 10
pctused 40
tablespace CC_ADDRESS
storage
(
   initial 32M
   next 32M
   pctincrease 0
)
/

create unique index UNQ_BUILDING on BUILDING (BLDG_NAME asc)
tablespace CC_ADDRESS_IDX
/

How to resolve this? I have read the manual but could not figure out what is
wrong with the ddl and the solution to resolve this? Could you pls explain to me
the cause and the solution to this problem?

Thanks.

      and we said...
There is nothing wrong with the ddl.

you hit MAX EXTENTS which defaulted to 505 (depends on the blocksize what the
default is).  You tried to put more data into the INDEX then it can hold.

You used the default tablespace definition for the index (and in this case, the
index on BLDG_NAME is most LIKELY going to be BIGGER then the table BUILDING
itself!!).

you need to look at the default storage parameters for CC_ADDRESS_IDX and figure
out how much space 505 extents would be and either:

o use locally managed tablespaces with uniform extents (this is the BEST answer
for all objects).  extents are unlimited in these

o alter this index to have unlimited extents

o fix the storage parameters on the index itself to be more specific, so you
allocate the space you really want

Review &
      Followup

      Rating:
      4
  April 01, 2002
Reviewer:
      A reader
For solution 1, what is the max extents should I allocate for UNQ_BUILDING if
the DDL of CC_ADDRESS_IDX is:

CREATE TABLESPACE cc_address_idx
   DATAFILE '/hds150GB/boss_data/oradata/boss/cc_address_idx_01.dbf' SIZE 6000M
REUSE,
      '/hds150GB/boss_data/oradata/boss/cc_address_idx_02.dbf' SIZE 6000M REUSE,
      '/hds150GB/boss_data/oradata/boss/cc_address_idx_03.dbf' SIZE 6000M REUSE,
        DEFAULT STORAGE
                (
                INITIAL         10K
                NEXT            10K
                PCTINCREASE     0
                MINEXTENTS      1
                )

Could you pls give an example how to allocate an appropriate amount space for
maxextents of a table/index? Is it not a good idea to have bigger index than the
table itself?
Thanks.

          Followup:

you do understand that your INDEX on this field is going to be as big if not
more likely BIGGER then your table?

If your primary key index (on a varchar2(30)) gets 29m/extent and your table
gets  32m/extent -- why would you use 10k/extent for your LARGEST object??

if you want to use 10k, you best go with "unlimited"

      Rating:
      4
  April 02, 2002
Reviewer:  Jesse

Hi Tom,
Again regarding the solution 1 to the problem, if i have DDL for cc_address_idx
as below:

CREATE TABLESPACE cc_address_idx
        DATAFILE '/hds150GB/boss_data/oradata/boss/cc_address_idx_01.dbf' SIZE
600
0M REUSE,
      '/hds150GB/boss_data/oradata/boss/cc_address_idx_02.dbf' SIZE 6000M REUSE,
      '/hds150GB/boss_data/oradata/boss/cc_address_idx_03.dbf' SIZE 6000M REUSE,
        DEFAULT STORAGE
                (
                INITIAL         10K
                NEXT            10K
                PCTINCREASE     0
                MINEXTENTS      1
                )
/

Could you pls give me an example to calculate the maxextents for index
unq_building?

If I use solution 2, would it be any effect to the table/tablespace? What do I
have to monitor?

Is it not a good sign if I have a bigger index than the table?

Thanks.

      Rating:
      5
  April 02, 2002
Reviewer:  Jesse

Sorry. Pls ignore the above questions. Thks.

      Rating: 5
same error --
      but don't know why  May 06, 2003
Reviewer:
      John
i get the same error when i am trying to populate a table in development.

ORA-01631: max # extents (505)
       reached in table HRDM.MLOG$_SID_PS_JOB_HIST

We want to load production simular data into the above development table.

INTIAL_EXTENT, NEXT_EXTENT FOR THE above development TABLE RESPECTIVELY ARE
65536,974848 and
PCT_INCREASE is 1

the details of the above table in Production which got from all_tables:

NUM_ROWS : 698016
AVG_ROW_LEN : 102

it looks to me like space of the 505 extents is more than the total space 12 *
698016. am i wrong? if yes, can you tell me why?

          Followup:

alter table .... maxextents unlimited;

May
      06, 2003 -- Thanks for the question
      regarding "One row to many?", version 8.1.7

      You Asked
I have a table like
(pk, sum_a, sum_b, sum_c)

How would I create another table that has rows like

(pk, 'A', sum_a)
(pk, 'B', sum_b)
(pk, 'C', sum_c)

i.e. convert 1 row from the first table into 3 rows.

Of course, I can do this by scanning the source table 3 times, one for each
'sum' column, but it is pretty large table (~50 million rows), so I want to do
it in one FTS if possible.

Can I use analytics to do this? How?

Thanks for your help.

      and we said...
select pk, code, decode( code, 'A', sum_a, 'B', sum_b, 'C', sum_c ) data
  from t, ( select 'A' code from dual UNION ALL
            select 'B' code from dual UNION ALL
            select 'C' code from dual )
/

cartesian join.
Review & Followup

      Rating:
      5
Brilliant!!!  May 06, 2003
Reviewer:
      A reader

      Rating: 5
Superb
      solution.. Tom you are a magician!!!  May 06, 2003

Reviewer:  A reader

      Rating:
      4
Splendid  May 07, 2003
Reviewer:
      reader
Hey Tom,
       Good day I have been reading the posting on this site quite frequently,
most of the things you tell are just superb is it all by practice and do you
recommend some book or document for tricks like these. Again you are simply
GREAT.....

      Rating:
      5
  May 07, 2003
Reviewer:  Sami
       from Banalore,India

      Rating:
      5
Beautiful!!  May 07, 2003
Reviewer:
      A reader

May 06, 2003 vuppala -- Thanks for the question regarding
      "changing size of a tablespace", version

      You Asked
what is the way of changinng max size of a tablespace.
1.with out shut down of the database.
2.with shutdown of database.
waht is the same way.?

      and we said...
sure -- in fact you cannot really change the size of a
tablespace without the database being UP!

1) alter tablespace table_space_name add datafile
'/path/to/some/file.dbf' size Xm;

that will add a new file to the existing tablespace.   This file
will be immediately available for use.

2) alter database datafile '/path/to/some/existing/file.dbf'
resize Xm;

that will resize an existing file (can be used to shrink or grow
files).  You would select file_name from dba_data_files where
tablespace_name = 'YOUR_TABLESPACE' to find the files associated
with your tablespace and 'grow' one of them.  I recommend never
growing a file over 2gig.

3) alter database datafile '/path/to/some/existing/file.dbf'
autoextend on next Xm maxsize Ym;

this lets the tablespace grow dynamically as it needs it.  make
sure you understand this one if you use it.  Make sure to set a
maxsize and try not to go over 2gig.  Make the Xm be some
reasonable number that fits in with the size of the extents this
tablespace generates (eg: if you extents are 5m, don't use a
next size of 4m as we will have to do 2 extends to get an extent
and then have 3m left over).

Review &
      Followup

      Rating:
      4
  May 09, 2002
Reviewer:
      A reader

      Rating: 4
adding new
      datafile after 2 gb  December 23, 2002
Reviewer:
      OCT
Hi Tom
Thanks ,I have a further add on to this
we have two Tablespace Data and Index in LMT mode.
both have one datafile size 1.6 gb data and 1.8gb index on different HDD.
Now as you mentioned that "never growing a file over 2gig."
1. why?please brief.
2.what is the best choice when we add a new datafile to above tablespace:-1.on
the same HDD as the old one
or on different HDD.
Our DBase growing at the rate of 5-6 mb /week.
Thanks

          Followup:

1) i never say never - except to say that I never say never.

I said in most cases i PREFER to keep the file at 2gig or less as all OS
utilities will work with 2gig and less files, some will not and not all file
systems support files over 2gig.

However, if I'm working on a really big database -- hundreds of gigabytes --
large files will be something to consider.

2) you want to add the datafile to the drive that is currently experiencing the
least amount of IO.

      Rating:
      4
thanks  December 24, 2002
Reviewer:
      A reader

      Rating:
      4
  May 06, 2003
Reviewer:
      A reader
Currently I have only one datafile created in the tablespace. And created 1m
size with the autoextend and maxsize 10G. I want to change the maxsize to be 5G.
Is it possible?

Thanks

Mike

          Followup:

sure alter database datafile 'foo' autoextend on maxsize ...;


Subject | Article | Last Updated (East coast time)
Dyamic Quarter Dates | article only | May 07, 2003 07:23 am
Create/Replace procedure command | article only | May 07, 2003 07:30 am
Get Index value from Associative Array (Index-by table) | article only | May 07, 2003 10:09 am
Regarding public Synonym | article only | May 07, 2003 01:46 pm
delete set null | article only | May 08, 2003 08:58 am
Query to return the most recent row | article only | May 08, 2003 09:25 am
Transport tablespace | article only | May 08, 2003 09:26 am
Import of the entire database with Objects !! | article only | May 08, 2003 09:36 am
Dynamic Function | article only | May 08, 2003 09:42 am
How To Count N Records & Stop | article only | May 08, 2003 06:12 pm
Transaction Isolation Level | article only | May 09, 2003 01:19 pm
Recovery using export dump file | article only | May 09, 2003 01:23 pm
Shared Pool (from your book) | article only | May 09, 2003 03:08 pm
Partitioning - Especially the PARTITION by RANGE Clause | article only | May 09, 2003 07:11 pm
Rebuilding | article only | May 10, 2003 07:30 am
Does oracle support bi-direction(either ascending or descending) index? | article only | May 10, 2003 07:31 am
rownum question | article only | May 10, 2003 07:34 am
no of return by a query,Paging | article only | May 11, 2003 09:55 am
form/report server | article only | May 11, 2003 09:56 am
Order of predicate evaluation in queries | article only | May 11, 2003 10:01 am
How to increase the datafile size or not to specify Maxsize / Record set as in Parameter | article only | May 11, 2003 10:06 am
What is a driving table | article only | May 12, 2003 07:44 am
Using the DB that comes with IAS-Best Practice | article only | May 12, 2003 07:53 am
Invoker rights question & privileges in dynamic sql | article only | May 12, 2003 08:02 am
CLOB 8-bit version? | article only | May 12, 2003 08:18 am
AQ Newbie | article only | May 12, 2003 08:21 am
WHy Some Database Envirenment I must user .world?? | article only | May 12, 2003 08:37 am
Cursor | article only | May 12, 2003 08:49 am
query and current | article only | May 12, 2003 09:15 am
Next_Day question | article only | May 12, 2003 03:50 pm
loading millions of records - speed/scalability | article only | May 12, 2003 07:02 pm
Local Nest Table Usage and Performance | article only | May 13, 2003 07:09 am
authentication and connection pooling at a Java middle tier | article only | May 13, 2003 07:15 am
DB Blocks | article only | May 13, 2003 07:17 am
Index - Scattered, Random Reads | article only | May 13, 2003 07:35 am
grand total | article only | May 13, 2003 10:27 am
How to issue DDL (for example drop index) on a busy table. | article only | May 13, 2003 10:28 am
Fetch across commit | article only | May 13, 2003 04:46 pm
dbms_application problem | article only | May 13, 2003 05:10 pm
OLTP Rollback segment planning | article only | May 13, 2003 05:16 pm
Invoker rights and Before Insert Trigger | article only | May 13, 2003 05:18 pm
I'm having a problem with Control File (.ctl). | article only | May 13, 2003 05:55 pm
Sybase / Oracle sequences | article only | May 13, 2003 09:29 pm
row(s) 1 - 43 of 43
May 07, 2003 Peter -- Thanks for the question regarding "Dyamic Quarter Dates", version 8.1.6.0.0
You Asked
I have been tasked with designing a dynamic select to return 3
rows based on a date which are the 3 end of month dates
corresponding to the quarter in which the first date resides.
For example is the first date is 12/18/2000 then the select
should return 10/31/2000, 11/30/2000, and 12/31/2000.  If the
date is 02/01/2000 then the dates returned should be 1/31/2000,
2/29/2000, 3/31/2000.  My co-workers and I have devised a
lengthy select using decode, add_months and union.  I tried to
use an answer you gave that calculated last and curr quarter but
the result only returned rows going forward for one year.
Thanks for reading my question.
and we said...
You can get this with a fairly simple subquery.  The query below
returns the three dates you want given an input date.  It would
be suitable for use in a "where date_field in ( subquery )" type
of statement.  The last query here shows how you can get them in
one row -- so you could say

 "where date_field = f(x) or date_field = f2(x) or date_field =
f3(x)"

if you didn't want to use a subquery for whatever reason.

It works using this function:

  5                last_day(
  6                    add_months( trunc(dt,'y'),
  7                                trunc(to_char(dt,'mm')/4)*3+0
  8                              )
  9                         )

dt is your date.  we truncate that date to the first day of the
year (trunc(dt,'y')).  We then add some months to that.  The
trunc(to_char(dt,'mm')/4)*3 part adds either 0, 3, 6, or 9
months to the date -- to bring us to the first month of the
quarter we are interested in.  The +0 in this case gives us the
first month in that query, +1 the second, +2 the third.  Lastly,
by applying last_day to this date -- we get the last day of that
month.

Here are the examples:

ops$tkyte@ORA8I.WORLD> variable rc refcursor
ops$tkyte@ORA8I.WORLD> set autoprint on
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> declare
  2      dt date default to_date( '12/18/2000', 'mm/dd/yyyy');
  3  begin
  4      open :rc for
  5          select last_day(
  6                    add_months( trunc(dt,'y'),
  7                                trunc(to_char(dt,'mm')/4)*3+r
  8                              )
  9                         ) dates
 10            from ( select 0 r from dual
 11                   union all
 12                   select 1 from dual
 13                   union all
 14                   select 2 from dual );
 15  end;
 16  /

PL/SQL procedure successfully completed.

DATES
---------
31-OCT-00
30-NOV-00
31-DEC-00

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> declare
  2      dt date default to_date( '02/01/2000', 'mm/dd/yyyy');
  3  begin
  4      open :rc for
  5          select last_day(
  6                    add_months( trunc(dt,'y'),
  7                                trunc(to_char(dt,'mm')/4)*3+r
  8                              )
  9                         ) dates
 10            from ( select 0 r from dual
 11                   union all
 12                   select 1 from dual
 13                   union all
 14                   select 2 from dual );
 15  end;
 16  /

PL/SQL procedure successfully completed.

DATES
---------
31-JAN-00
29-FEB-00
31-MAR-00

ops$tkyte@ORA8I.WORLD> declare
  2      dt date default to_date( '02/01/2000', 'mm/dd/yyyy');
  3  begin
  4      open :rc for
  5          select last_day(
  6                    add_months( trunc(dt,'y'),
  7                                trunc(to_char(dt,'mm')/4)*3+0
  8                              )
  9                         ) date1,
 10                 last_day(
 11                    add_months( trunc(dt,'y'),
 12                                trunc(to_char(dt,'mm')/4)*3+1
 13                              )
 14                         ) date2,
 15                 last_day(
 16                    add_months( trunc(dt,'y'),
 17                                trunc(to_char(dt,'mm')/4)*3+2
 18                              )
 19                         ) date3
 20            from dual;
 21  end;
 22  /

PL/SQL procedure successfully completed.

DATE1     DATE2     DATE3
--------- --------- ---------
31-JAN-00 29-FEB-00 31-MAR-00
Review & Followup
Rating: 4
Dyamic Quarter Dates January 31, 2002
Reviewer: M.Srinivas from INDIA
Thanks for giving valuable info...

Rating: 1
Does not work all the times --- ERROR IN CODE -------- November 21, 2002
Reviewer: saleh from france
the output you get is not always valid ???
when you change your default  date to 07/07/2002, your answer should be :
31-JUL-02
31-AUG-02
30-SEP-02
what you get is :
30-APR-02
31-MAY-02
30-JUN-02

the same answer  change you default  date  10/10/2002

please Tom can you give us more info on that

thank
karo

Followup:
well, thats what happens when I don't test boundary value conditions ;)

Ok, here is one that "works".  first query shows how I'm computing the quarter
and that it works for every month.  next shows how you would use it -- to
incorporate into the query (to test for each month).  the last is the finished
"answer"

ops$tkyte@ORA920.US.ORACLE.COM> @test
ops$tkyte@ORA920.US.ORACLE.COM> select dt, to_char(dt,'mm'),
to_char(dt,'mm')/3-0.1, trunc(to_char(dt,'mm')/3-0.1) qtr
  2    from ( select add_months( trunc(sysdate,'y')-1, rownum ) dt from
all_objects where rownum <= 12 )
  3  /

DT        TO TO_CHAR(DT,'MM')/3-0.1        QTR
--------- -- ---------------------- ----------
31-JAN-02 01             .233333333          0
28-FEB-02 02             .566666667          0
31-MAR-02 03                     .9          0
30-APR-02 04             1.23333333          1
31-MAY-02 05             1.56666667          1
30-JUN-02 06                    1.9          1
31-JUL-02 07             2.23333333          2
31-AUG-02 08             2.56666667          2
30-SEP-02 09                    2.9          2
31-OCT-02 10             3.23333333          3
30-NOV-02 11             3.56666667          3
31-DEC-02 12                    3.9          3

12 rows selected.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select dt,
  2         add_months( trunc(dt,'y'), trunc(to_char(dt,'mm')/3-0.1)*3 ),
  3         add_months( trunc(dt,'y'), trunc(to_char(dt,'mm')/3-0.1)*3+1 ),
  4         add_months( trunc(dt,'y'), trunc(to_char(dt,'mm')/3-0.1)*3+2 )
  5    from ( select add_months( trunc(sysdate,'y')-1, rownum ) dt from
all_objects where rownum <= 12 )
  6  /

DT        ADD_MONTH ADD_MONTH ADD_MONTH
--------- --------- --------- ---------
31-JAN-02 01-JAN-02 01-FEB-02 01-MAR-02
28-FEB-02 01-JAN-02 01-FEB-02 01-MAR-02
31-MAR-02 01-JAN-02 01-FEB-02 01-MAR-02
30-APR-02 01-APR-02 01-MAY-02 01-JUN-02
31-MAY-02 01-APR-02 01-MAY-02 01-JUN-02
30-JUN-02 01-APR-02 01-MAY-02 01-JUN-02
31-JUL-02 01-JUL-02 01-AUG-02 01-SEP-02
31-AUG-02 01-JUL-02 01-AUG-02 01-SEP-02
30-SEP-02 01-JUL-02 01-AUG-02 01-SEP-02
31-OCT-02 01-OCT-02 01-NOV-02 01-DEC-02
30-NOV-02 01-OCT-02 01-NOV-02 01-DEC-02
31-DEC-02 01-OCT-02 01-NOV-02 01-DEC-02

12 rows selected.

ops$tkyte@ORA920.US.ORACLE.COM> select add_months( trunc( SYSDATE, 'y' ),
trunc(to_char(SYSDATE,'mm')/3-0.1)*3+r )
  2    from (select rownum-1 r from all_objects where rownum<=3)
  3  /

ADD_MONTH
---------
01-OCT-02
01-NOV-02
01-DEC-02

Rating: 3
How About TRUNC(Date, 'Q') November 22, 2002
Reviewer: Steve from New York
SELECT
    ADD_MONTHS(LAST_DAY(TRUNC(SYSDATE, 'Q')), rn) dates
FROM
    (SELECT ROWNUM -1 rn FROM all_objects WHERE ROWNUM <= 3)

DATES
---------
31-OCT-02
30-NOV-02
31-DEC-02

Followup:
that'll work as well

Rating: 3
how to get 12/30 instead of 12/31 March 12, 2003
Reviewer: Ravi Kumar from Chicago, IL
Hi,

When I run the following statement,

SELECT ADD_MONTHS(TO_DATE('09/30/2003', 'mm/dd/yyyy'), 3) FROM dual;

I got 12/31/2003. I expected the result to be 12/30/2003?

Why is this behaviour?

Thanks,
Ravi.

Followup:
because when you add a month to the last day of the month, you get the last day
of the month.

consider what happens if you started with 31-dec and added -3 months.  31-sep?

the math for the last day is the last day of the corresponding month

Rating: 3
What if I need to do this May 06, 2003
Reviewer: Pawan from USA
We need a Calendar which is something like as shown below. I have the two
columns separated by || and two spaces for readability.
Field||  Description

Run Date||  Date the Process was run
Quarter Start Date||  First day of the first month of the quarter
Quarter End Date||  Last day of the last month of the quarter
Beginning of Month||  First day of the last month of the quarter
End of Month||  Last day of the last month of the quarter
Quarter Code||  Q1,Q2,Q3,Q4
Quarter Description||  First Quarter, Second Quarter, Third Quarter, Fourth
Quarter
Current Year Number||  2003,2004,2005 etc.
Previous Year Number||  2002,2003,2004 etc.
Quarter First Month Code||  Jan=01 Apr=01, July=01and Oct=01
Quarter First Month Short Name||  Jan,Apr,Jul and Oct
Quarter First Month Long Name||  January,April,July and October
Quarter Second Month Code||  Feb=02,May=02,Aug=02 and Nov=02
Quarter Second Month Short Name||  Feb,May,Aug and Nov
Quarter Second Month Long Name||  February,May,August and November
Quarter Third Month Code||  Mar=03,Jun=03,Sep=03 and Dec=04
Quarter Third Month Short Name||  Mar,Jun,Sep and Dec
Quarter Third Month Long Name||  March,June,September and December
Last Update Date Time||  Sysdate with time

Thanks

Followup:
i've not any idea what this is about.

Rating: 1
This is what I meant May 06, 2003
Reviewer: pawan from USA
Tom,
What the requirement is to generate a calendar(table) which will have the fileds
as described in the last post. I tried to generate the fields for 36 months (3
yrs) by..
select trunc(add_months(EOQ_DT,-2),'mm') BOQ_DT,
EOQ_DT ,
add_months( trunc(EOQ_DT,'y'), trunc(to_char(EOQ_DT,'mm')/3-0.1)*3+2 ) BOM_DT,
add_months( last_Day(trunc(EOQ_DT,'y')), trunc(to_char(EOQ_DT,'mm')/3-0.1)*3+2 )
EOM_DT,
EOQ_DT ,
to_char( EOQ_DT, 'yyyy - q' ) qtr
from (
select add_months(to_date('31-dec-2002','dd-mon-yyyy'), rownum*3) EOQ_DT
from all_objects
where rownum <= 37)

I could not figure out how to populate the other fields like
Quarter Code,
Quarter Description,
Current Year Number,
Previous Year Number,
Quarter First Month Code
etc
with the values as in the Description field.

Hope I was able to clarify.

Followup:
tell you what -- when I'm taking new questions, feel free to put a more
descriptive question in there.

I don't know what your "quarter description" is, what your year number means

etc....  (it does seem you should be able to firgure that out yourself tho, i
mean -- heck -- you have the dates.  you cannot figure out how to turn a date
into whatever you mean by "quarter code" and so on?)

Rating: 2
I will try and explain May 06, 2003
Reviewer: Pawan from USA
Tom,
Sorry for not being clear with the question. What we are trying to do is create
a calendar lookup table. The Description of the table and the permitted values
in each field is as under:

Tom,
Sorry I was not clear. I will try and explain my request. We need to create a
table in our database. The Descrition of the table is(quarter calendar Lookup)
SQL> Desc QTR_CALD_LKP;
Column    Datatype                                        Explanation/Values
Permitted
------------------------------------
RUN_DT    DATE    Date when the process was run
RUN_TYP   VARCHAR2(6)  Can have values of ‘QTD’ or ‘Quarter’
PROC_IND  CHAR(1)      ‘Y’ or ‘N’ Default is null
BOQ_DT      DATE         First day of the first month
                       of the quarter. Like 01 Jan 2003,
                       01 Apr 2003, 01 Jul 2003 and
                       01 Oct 2003

EOQ_DT      DATE      Last day of the last month of
                    the quarter. Valid values are
            31 Mar 2003, 30 Jun 2003,
                    30 Sep 2003, 31 Dec 2003

BOM_DT      DATE        First day of the last month of
                    the quarter. Valid values are
            01 Mar 2003, 01 Jun 2003,
                    01 Sep 2003, 01 Dec 2003

EOM_DT      DATE        Last day of the last month of
                    the quarter. Valid values are
            31 Mar 2003, 30 Jun 2003,30
                    Sep 2003, 31 Dec 2003

QTR_CD     VARCHAR2(2)    Valid values are
                         Q1,Q2,Q3,Q4
QTR_DS    VARCHAR2(20)    Valid values are
                        First Quarter, Second Quarter,
                        Third Quarter,Fourth  Quarter
CURR_YR_NUM NUMBER(4)    4 digit year
                        Example: 2003,2004,2005 etc.
PREV_YR_NUM NUMBER(4)    4 digit last year
                        Example:2002,2003,2004 etc.
QTR_MTH_CD_1  VARCHAR2(2)   Jan=01 Apr=01, July=01and
                            Oct=01
QTR_MTH_SHORT_DS_1 VARCHAR2(3)   Jan,Apr,Jul and Oct
QTR_MTH_LONG_DS_1   VARCHAR2(10) January,April,July
                                 and October
QTR_MTH_CD_2  VARCHAR2(2)        Feb=02 May=02, Aug=02
                                 and Nov=02
QTR_MTH_SHORT_DS_2 VARCHAR2(3)   Feb,May,Aug,Nov
QTR_MTH_LONG_DS_2  VARCHAR2(10)  February,May,
                                 August,November
QTR_MTH_CD_3      VARCHAR2(2)    Mar=03 Jun=03, Sep=03
                                 and Dec=03
QTR_MTH_SHORT_DS_3 VARCHAR2(3)   Mar,Jun,Sep,Dec
QTR_MTH_LONG_DS_3   VARCHAR2(10) March,June,
                                 September,December

I wrote part of the query to populate this table which is my post above. I am
confused as to how to populate the other fields QTR_CD,QTR_DS etc. I feel I will
need to use DECODE but can't seem to figure how.
Will really appreciate your help.
Thanks

Followup:
tell you what -- when I'm taking new questions, feel free to put a more
descriptive question in there.

May 07, 2003 Gunjeet -- Thanks for the question regarding "Create/Replace procedure command", version v8.1.6
You Asked
Hi Tom,

Are there any issues with issuing a "create or replace
procedure"
command when the application that uses the database
is up and running in a production environment ?

What happens when you fire this script when this
same procedure is acutually getting executed at the same time.

Will it generate any errors ?

I vaguely remember coming across some "resource
unavailable/busy" kind of situation (the creation of stored
procedure probably hung for a while) while doing
a similar thing with an older version of Oracle
but can't remember the details.

Please let me know if a copy of the procedure is cached when
it's getting executed and if it's OK to recreate
it while it's getting executed.

Thanks,

Gunjeet
and we said...
YES -- big time.  You can really get yourself in a crunch.  You
(doing the create or replace) will have to wait for the current
executions to finish -- new executions have to wait for you to
finish.  If your procedure can take a long time -- it can make
it look like a database hang.

I never replace code like that during "up hours".  It is like
replacing a binary in the file system -- you have to wait until
people are not running your program to replace it (else they
will all get page faults).  If you use packages with stateful
variables, there can be other side effects from the apparent
hang - errors such as "existing state of packages discarded" --
causing them to "restart".

You might get the resource busy/unavailable after a while if you
were not able to get the lock -- but remember other people have
been waiting with you as well.

I would avoid it if at all possible.
Review & Followup
Rating: 5
How to identify objects that are being executed April 18, 2003
Reviewer: Logan Palanisamy from Santa Clara CA USA
Tom,

Is there an easy way to find out if the stored procedure that we are trying to
re-create is in fact being used or not at that moment?

Is V$ACCESS the right table to get this information? or are there better ways?

Thanks

Followup:
v$access would be appropriate.  dba_ddl_locks can also be useful to see who is
"blocking" a create or replace.

(hopefully you use PACKAGES and not stored procedures!)

Rating: 5
Stored Procedures April 18, 2003
Reviewer: Logan Palanisamy from Santa Clara, CA USA
Tom,

Thanks for your quick and great answer.

Yes. We always use packages. Needless to say, I am a regular reader of this
forum.

I used the term 'stored procedures' to collectively mean functions, procedures
and packages. That's all.

Followup:
just checking ;)

Rating: 5
"adding" new procedures/functions to existing packages possible? April 19, 2003
Reviewer: A reader
Is it possible to just "add" new pocedures and functions to a package, without
creating or replacing the whole package/package body all over again?

Followup:
you can add new procedures and functions to a body -- and not change the spec.

But, in order to add new externally exposed functions/procedures, you need to
recompile the spec.

And you either compile a body -- or not (same with a spec).  You cannot "alter"
it and add some code, you have to submit the code.

Sort of like "you either compile a source code file -- or not" in a 3gl.

Rating: 5
would be nice to have May 06, 2003
Reviewer: A reader
Not necessary an enhancement request, but would it be nice to have something
like

alter package this_pkg add procedure proc
or
alter package this_pkg add function  func

as a mechanism to "recruit" standalone procedures and functions into existing
packages? I think it can be done code-wise by oracle (i.e. procedure/function
spec goes to package spec; body goes to package body) behind the scenes. Is
there a logic reason not to have this?

Followup:
why would it be "nice to have"

package must be invalidated
dependent code would have to be changed

what would be the "perceived benefit"

Rating: 4
May 06, 2003
Reviewer: A reader
Hi Tom,

Couple of questions:

-Is there any advantage of using packages as against stand-alone procedures in
this context? (I konw about the modularity and performance advantages as you
have mentioned here earlier. But how does it help recompiling in a production
environment?)

--Secondly, I did not understand the following:

"you can add new procedures and functions to a body -- and not change the spec.

But, in order to add new externally exposed functions/procedures, you need to
recompile the spec."

Can you please elaborate with an example?

If what I understand correctly, adding a procedure/function to an existing and
widely used package in a live environment is a big issue. It involves
recompiling the entire package and invalidates all the existing code for a
shortwhile. It sounds that this is a big arguement for NOT using packages. For,
if we use stand-alone procs/functions then a new proc/function can be compiled
without touching any of the existing code. Very curious to know what is your
counter arguement to this.

Thanks verymuch for your time.

Followup:
o this is the MAIN reason for using packages -- to break the dependency chain.
You can implement and re-implement the body over and over and over and not
affect any dependent objects.

You cannot do that with a procedure

o just add "procedure I_am_only_in_the_body is begin null; end;"  to any package
body, compile.  There you go.

My 2 cents -- if you are adding code, you are adding new functionality -- you
are doing an upgrade, you are adding new functionality -- you are doing
something major league.  You are doing this off hours.

Your argument only applies the first time, marginally.  When you fix a bug in
that procedure -- it's all over.

There is no reason to use a stored procedure by itself for real production code.
 simple utilities, standalone things that you call from sqlplus directly (no
other code calls it) -- they can be procedures but that is about it.

Even if you take it to the illogical extreme -- a procedure per package -- that
is better then a standalone procedure.  You can zap the stored procedure in
(replace the body) as a bug fix without affecting anything else.

May 07, 2003 Jan -- Thanks for the question regarding "Get Index value from Associative Array (Index-by table) ", version 9.2.
You Asked
Dear Tom,

I have this example:
-------------------

TYPE my_type IS TABLE OF INTEGER INDEX BY VARCHAR2(10);
t    my_type;

BEGIN

t('A'):=1;
t('B'):=2;
t('X'):=3;

END;
-------------------

My question is if is it possible to get the list of all index values used in t?
The output will be:

'A'
'B'
'X'

Thank you.
and we said...
scott@ORA920> declare
  2          type myType is table of integer index by varchar2(10);
  3          t myType;
  4
  5          l_index varchar2(10);
  6  begin
  7          t('a') := 100;
  8          t('b') := 200;
  9          t('x') := 300;
 10
 11          l_index := t.first;
 12          for i in 1 .. t.count
 13          loop
 14                  dbms_output.put_line( i || ' = ' || l_index );
 15                  l_index := t.next(l_index);
 16          end loop;
 17  end;
 18  /
1 = a
2 = b
3 = x

PL/SQL procedure successfully completed.
Review & Followup
Rating: 5
May 07, 2003
Reviewer: A reader

May 07, 2003 Ashiq -- Thanks for the question regarding "Regarding public Synonym", version 8.1.7
You Asked
Hello Tom,

  I got a problem regarding the public synonym.I've 4 users like,
RMSREAL, RMSBASE, BILL & YUN .In this , RMSREAL and RMSBASE are ditto(like
they're 2 oranges) .Only difference is RMSREAL has more records where as RMSBASE
have less records,but other than that everything is same like number of table ,
packages etc... .

   Where as BILL and YUN doesn't own any objects but have   access to all
objects of RMSREAL as well RMSBASE .So i've created public synonym for all the
objects(like procedure and packages) for RMSREAL objects.

  But some part of time i want BILL to access the objects the RMSBASE and YUN
will continue to access the objects of RMSREAL .If i drop the public synonym
created for objects of RMSREAL and create for RMSBASE,then BILL will start
accessing the RMSBASE objects.But here my problem is still i want YUN to access
the objects of RMSREAL.I've drop the public synonym everything will go for toss.
    Then we thought of granting the objects directly to users.Like if BILL is
connected  then prefix RMSREAL in all the objects and if YUN is connected
,prefix with RMSBASE.But its need a lot of code changing in front End (front end
is VB , ASP )and i strongly believe its not  good idea.

    And we're running our application in Oracle8i as SQL server2k
Front end guys saying we're able to do this in SQL server without any any code
changes ,why not in Oracle. I strongly believe there is way to over come this.
     From Front End they'll access procedures , packages and in thru XML they'll
directly refer the tables.
      Plz explaing me in detail how to go abt with detail explanation.

thanx
and we said...
drop the synonyms -- get rid of them.  they are a bad idea anyway.

Either via an on-logon trigger in the database OR (better yet) in the
application -- issue:

alter session set current_schema = RMSBASE

for Bill and

alter session set current_schema = RMSREAL

for Yun.  You could create a table:

create table default_schemas
( username   varchar2(30) primary key,
  schema     varchar2(30)
)
/

and query:

select nvl( max(schema), user ) from default_schemas where username = user;

upon logon and set the current_schema to whatever that returns...  That'll
achieve your goal.
Review & Followup
Rating: 5
Why Synonms are Bad January 22, 2003
Reviewer: Ashiq Shamsudeen from Trichy , Tamil Nadu , India
Thanx for explaing .But why synonyms are very bad idea?? So u mean to say to
grant direct access to tables itself rather than synonyms.Then want for synonmns
are useful if its bad idea ??

Followup:
Your question above points out the failing of synonyms.  There can only be one
of them.  Your namespace gets heavily polluted.

You still have to do the same amount of granting.  synonyms do not bypass
security -- they just change the name of something.

Rating: 5
Public synonym is bad January 22, 2003
Reviewer: Robert Xuequn Xu
I'd say that using *public* synonym is very bad. They should be reserved for
data dictionary use only. If I have to use synonyms I would use private ones.

Rating: 4
make it generic January 23, 2003
Reviewer: Charlie
Hi Tom,

If I want everybody who logged in the database use schema 'SCOTT', how can I do
that?
"after logon on database" trigger seems not working.  My DB version is
8.1.7.0.0.  Any thoughts?

Thanks,

Followup:
"seems not working" -- seems not descriptive.  I just:

create trigger make_me_query_scott
after logon on database
begin
    execute immediate 'alter session set current_schema=scott';
end;
/

and

ops$tkyte@ORA817DEV> connect /
Connected.
ops$tkyte@ORA817DEV> show user
USER is "OPS$TKYTE"
ops$tkyte@ORA817DEV> select * from user_objects where object_name = 'EMP';

no rows selected

ops$tkyte@ORA817DEV> select * from dba_synonyms where synonym_name = 'EMP';

no rows selected

ops$tkyte@ORA817DEV> set linesize 50
ops$tkyte@ORA817DEV> desc emp
 Name                    Null?    Type
 ----------------------- -------- ----------------
 EMPNO                   NOT NULL NUMBER(4)
 ENAME                            VARCHAR2(10)
 MGR                              NUMBER(4)
 HIREDATE                         DATE
 SAL                              NUMBER(7,2)
 COMM                             NUMBER(7,2)
 DEPTNO                           NUMBER(2)
 JOB                              VARCHAR2(10)

ops$tkyte@ORA817DEV> alter session set current_schema=ops$tkyte;

Session altered.

ops$tkyte@ORA817DEV> desc emp;
ERROR:
ORA-04043: object emp does not exist

works for me.

Rating: 4
Which schema gets the trigger? January 23, 2003
Reviewer: David Gibbs from Burnaby, BC Canada
Tom

You showed the creation of the trigger.  Which schema should own it?  System,
(in this case) Scott or someone else.  Would this trigger affect all users
logging into the database?

Thanks

Followup:
I owned it (ops$tkyte)

As written -- it would affect all users - you would use an if statement to
restrict it to whomever you wanted...

Rating: 3
dba January 23, 2003
Reviewer: Charlie
create trigger make_me_query_scott
after logon on database
begin
    execute immediate 'alter session set current_schema=scott';
end;
/

and

ops$tkyte@ORA817DEV> connect /
Connected.
ops$tkyte@ORA817DEV> show user
USER is "OPS$TKYTE"
ops$tkyte@ORA817DEV> select * from user_objects where object_name = 'EMP';

no rows selected

ops$tkyte@ORA817DEV> select * from dba_synonyms where synonym_name = 'EMP';

no rows selected

ops$tkyte@ORA817DEV> set linesize 50
ops$tkyte@ORA817DEV> desc emp
 Name                    Null?    Type
 ----------------------- -------- ----------------
 EMPNO                   NOT NULL NUMBER(4)
 ENAME                            VARCHAR2(10)
 MGR                              NUMBER(4)
 HIREDATE                         DATE
 SAL                              NUMBER(7,2)
 COMM                             NUMBER(7,2)
 DEPTNO                           NUMBER(2)
 JOB                              VARCHAR2(10)

ops$tkyte@ORA817DEV> alter session set current_schema=ops$tkyte;

Session altered.

ops$tkyte@ORA817DEV> desc emp;
ERROR:
ORA-04043: object emp does not exist

works for me.

>>>>>>
I think I found the problem --> "ops$tkyte" is a DBA.  Let's try this:
- login as a dba to create trigger "make_me_query_scott"
- logout
- login as a normal user who is not a dba or does not have "select any table"
system privilege
- "desc emp" will barf

Also, I found that if user b is a dba and he created a logon trigger which uses
"alter session set current_schema=b" <-- his own schema.  Then This will fail
all users.  Nobody can describe his table.

Followup:
Umm -- did David "answer your question" below?  Cause I'm not really sure what
if any question you have here?

Rating: 3
January 23, 2003
Reviewer: Tianhua Wu from Atlanta, GA
I would say synonym has its own advantages.  It is not bad all te time.  For
example, if you have a package that use certain objects many many times, and you
want to change that to different objects (tables at different database, eg.),
and you know you will change them from time to time, I would use synonyms.  (Of
course there are other ways doing it)

Rating: 4
Follow up to Charlie's observation January 23, 2003
Reviewer: David Gibbs from Burnaby BC, Canada
Charlie

You stated:

"I think I found the problem --> "ops$tkyte" is a DBA.  Let's try this:
- login as a dba to create trigger "make_me_query_scott"
- logout
- login as a normal user who is not a dba or does not have "select any table"
system privilege
- "desc emp" will barf

Also, I found that if user b is a dba and he created a logon trigger which uses
"alter session set current_schema=b" <-- his own schema.  Then This will fail
all users.  Nobody can describe his table."

Your problem is because you set current_schema but did not grant any rights to
other users.  As it states in the SQL Reference guide:

"CURRENT_SCHEMA = schema
The CURRENT_SCHEMA parameter changes the current schema of the session to the
specified schema. Subsequent unqualified references to schema objects during the
session will resolve to objects in the specified schema. The setting persists
for the duration of the session or until you issue another ALTER SESSION SET
CURRENT_SCHEMA statement. CURRENT_SCHEMA is a session parameter only, not an
initialization parameter.

This setting offers a convenient way to perform operations on objects in a
schema other than that of the current user without having to qualify the objects
with the schema name. This setting changes the current schema, but it does not
change the session user or the current user, nor does it give you any additional
system or object privileges for the session."

If you granted select on emp to all users your query would not "barf".  The
reason OPS$TKYTE couldn't see the table is because he didn't qualify EMP as
SCOTT.EMP.

Rating: 5
How to set dynamically January 24, 2003
Reviewer: Ashiq Shamsudeen from Trichy, Tamil Nadu , India
Hello Tom,
  From  your answer  u asked to create trigger ,but for me still i've some
problem .Sometimes Bill will connect to RMSREAL
and sometimes RMSBASE.If u hard coding ('alter session set
current_schema=scott') the current_schema value to RMSREAL ,then how can i
change it for current_value to RMSBASE?

thanx

Followup:
your keyboard is seriously broken.  It is dropping really important letters from
time to time making it really hard to read. but anyway...

well, given that you have no way of reading BILL's mind when he logs in (thats
scheduled for version 20 of Oracle I think ;)  you'll need to give Bill a push
button or something in your application that lets Bill pick the schema Bill
wants to use -- or something like that.

You know what to do know -- use alter session to switch the current_schema.  You
can use it anywhere in your application you want to.

Rating: 3
Information Hiding January 24, 2003
Reviewer: Sankar from Malaysia
We are developing a Application Using Public Synonyms. We have created all
objects in a User A, And We have created Public Synonyms In the user B. And
Granted to B to all users.From our point of view there are 2 advantages.

1) No need to prefix the Username everywhere while programming.

2) As we create synonym we hide the owner, So we thought it is a additional
security, Is it so?....

Followup:
You do not create public synonyms in B, you created public synonyms -- IN
PUBLIC.

There is no such thing as a public synonym owned by "b".

1) alter session set current_schema accomplishes the same without polluting the
namespace

2) security through obscurity?  hardly.  You either HAVE ACCESS (via grant) or
not.  Besides, all_synonyms will show, well, it'll show you the owner.

If you believe this is a "security" measure -- well, hope you don't work on a
publicly accessible internet site and .....

Here, read this too:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7483411948917
shows the problems someone who used synonyms is faced with today.

Rating: 5
Followup on ALTER SESSION SET CURRENT_SCHEMA command January 24, 2003
Reviewer: Dan Kefford from New Haven, CT
Dear Tom...

Indeed, we also use public synonyms to avoid prefixing object references with
their respective schema owners.

How would you handle a situation in which one schema needed to reference objects
belonging to more than one other schemas? It appears that the ALTER SESSION
command can only specify a single schema. Is this wrong? If not, would you then
consider this a design issue in that perhaps the objects in question ought to be
owned by the same schema?

Many thanks in advance,

dan

Followup:
perhaps an integrating schema that had views to the necessary objects...

You see -- I do 99.99999% of everything in PLSQL where this becomes basically
moot.  you execute a schema.package.procedure and there you go.  it does the
rest.

Rating: 5
Good discussion January 24, 2003
Reviewer: Mike from St. Louis, MO
ALTER SESSION SET CURRENT_SCHEMA vs. public synonyms.  Eye-opening
debate/discussion.  I've always used public synonyms but I'm seeing Tom's point
about using SET CURRENT_SCHEMA instead.  Interesting...

Rating: 4
good explanation February 05, 2003
Reviewer: G.Natarajan from Mumbai, India.
Tom It was a very good explanation. But I still didnt get the solution for a
question asked by some one. My problem is If my application( or a single
program) need to access the objects of two different schema's how can i use it
in my application, th way you mentioned by adding trigger called 'after logon '.
Now, we are using public synonyms so i need not to bother which schema i have to
refer for a particular object, and it will be a costly option to change the code
to replace the synonyms with "schema.object_name" format.. thanx

Followup:
ack.  I give up.

Here is a story for you.  App installed itself.  Created a public synonym IND
via drop/create.  Later, an upgrade occurs to the database.  I'll let you guess
what happens.

Any.  You have an application that uses public synonyms.  You need add ONE LINE
of code:

   alter session set current_schema=FOO

and then you can drop all public synonyms that point to foo.....

The answer to the question of "if my app needs to access the objects of two
different schemas" can be answered via views AND/OR private synonyms.

but do what you will -- at your own risk.

Rating: 5
February 06, 2003
Reviewer: A reader
If proper grants are given, then synonyms can be used for querying from the
database without altering data and by hiding the table from the user using the
synonym, can't they be?

Followup:
of course they can -- but "hiding the table" is strong, hiding the schema would
be more appropriate.

but the problem with PUBLIC synonyms is namespace pollution.  How many public
synonyms EMP can there be in a database?

what happens when new application "foo" comes along and wants EMP as its own
synonym?

Rating: 4
To avoid Creating SYNONYMS May 07, 2003
Reviewer: Riyaz H. from Chennai, South India
Hi Tom, I have a question for you.

MY INTENTION: Avoid Creating SYNONYMS (because it leads to lot of confusion &
difficult to maintain as you also stressed in many places)

MY OBJECTIVE IN NUTSHELL:
------------------------
1. I need to write a SINGLE PROCEDURE
2. Inside that, tables of both the users ( a & b) will be accessed. (sepeartely)
3. Grant permission will be given
4. I need to switch the user using 'alter sesion' command
5. I don't want to use synonyms.

I want to write a procedure similar to below: (a & b are schemas). This is
basically I am doing TO AVOID CREATING SYNONYMS

create or replace procedure p_1 authid current_user
as
n1,n2 number;
begin
alter session set current_schema=a;
select count(1) into n1 from t1;
alter session set current_schema=b;
select count(1) into n1 from t2;
end;

But the above procedure is giving error. (inspite of giving 'grant all'
permission from t2 to t1)

WHAT IS THE WAY OUT?

Thanks in advance.

Followup:
please -- ask once, you already asked this elsewhere and I addressed it there,
you'll have to go find it again.

Rating: 5
Is this related to synonyms May 14, 2003
Reviewer: Kulguru
Tom I can describe this object , but cannot select from it .. why is this
so...please give your input

SQL> connect test/test@itest
Connected.
SQL> desc test.t
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
------------------------------------
 column1                                                NOT NULL VARCHAR2(16)
 column2                                                NOT NULL NUMBER(16)
 column3                                                NOT NULL NUMBER(6)
 CREATE_USER                                           NOT NULL VARCHAR2(8)
 CREATE_DATE                                           NOT NULL DATE
 MODIFY_USER                                           NOT NULL VARCHAR2(8)
 MODIFY_DATE                                           NOT NULL DATE

SQL> select 'X' from test.t;
select 'X' from test.t
                        *
ERROR at line 1:
ORA-00942: table or view does not exist

SQL> set role none
  2  /

Role set.

SQL> desc test.t
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
------------------------------------
 column1                                                NOT NULL VARCHAR2(16)
 column2                                                NOT NULL NUMBER(16)
 column3                                                NOT NULL NUMBER(6)
 CREATE_USER                                           NOT NULL VARCHAR2(8)
 CREATE_DATE                                           NOT NULL DATE
 MODIFY_USER                                           NOT NULL VARCHAR2(8)
 MODIFY_DATE                                           NOT NULL DATE

SQL> select 'X' from test.t;
select 'X' from test.t
                        *
ERROR at line 1:
ORA-00942: table or view does not exist

May 08, 2003 wallacel -- Thanks for the question regarding "delete set null", version 8.1.6
You Asked
Tom:

what is that?

Regards,
and we said...
It is an option on an foreign key.  It will set a foreign key to
NULL when the parent record is deleted:

ops$tkyte@DEV816> create table p
  2  ( x int primary key );

Table created.

ops$tkyte@DEV816> create table c
  2  ( a int,
  3    b int references p
  4          on delete set null );

Table created.

ops$tkyte@DEV816> insert into p values ( 1 );

1 row created.

ops$tkyte@DEV816> insert into c values ( 1, 1 );

1 row created.

ops$tkyte@DEV816> delete from p;

1 row deleted.

ops$tkyte@DEV816> select * from c;

         A          B
---------- ----------
         1                  <<==== note how this is NULL
Review & Followup
Rating: 5
May 07, 2003
Reviewer: A reader
Very interesting

Rating: 5
May 07, 2003
Reviewer: A reader
Hi Tom, I'm short of ideas, could you give an example when to use it or what for
this options whas implemented for.

:) Thanks

Followup:
In the real world?  Not really.

Rating: 5
:) Thanks May 09, 2003
Reviewer: A reader

May 08, 2003 Brett -- Thanks for the question regarding "Query to return the most recent row", version 9.2.0.3
You Asked
Hi Tom,

I have a table in a proprietary app that looks like this:

BOM
   sku    parent_sku    effective_date
   91000
   91101   91100        05/01/2003
   91101   91000        05/05/2003
   91102   91000
   91102   91000        05/05/2003

I need to write a query that will return the parent sku with the most recent
child sku's based on today's date.  Some will have an effective_date and others
will not.  So based on the above data, the answer I expect should be:

   sku    parent_sku    effective_date
   91000
   91101   91000        05/05/2003
   91102   91000        05/05/2003

Is there a straight-forward way to do this in a single query?

Thanks,

Brett

Followup:

Yes, there could conceivably be an effective date that is 5/6/2003.  Other than
that, there is no reason for the requirement of today's date.  I just need to
get the latest record.
and we said...
ops$tkyte@ORA920> select * from t;

       SKU PARENT_SKU EDATE
---------- ---------- ---------
     91000
     91001      91100 01-MAY-03
     91001      91000 05-MAY-03
     91002      91000
     91002      91000 05-MAY-03
     91002      91542 25-MAY-03

6 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select sku, parent_sku, maxedate
  2    from (
  3  select sku, parent_sku, edate, max(edate) over (partition by sku) maxedate
  4    from t
  5   where edate <= sysdate OR edate is null
  6         )
  7   where maxedate is null OR maxedate = edate
  8  /

       SKU PARENT_SKU MAXEDATE
---------- ---------- ---------
     91000
     91001      91000 05-MAY-03
     91002      91000 05-MAY-03

ops$tkyte@ORA920>
Review & Followup
Rating: 4
Amazing May 07, 2003
Reviewer: Aamir Abbas from Karachi, Pakistan
It's amazing, Tom. How do you remember this much techniques and features??? How
do you keep yourself current to the recent Oracle features?

Can you give us some tips that may help us being current, or at least we know
and remember how to do things.

Thanks.
Another fan!!!

Followup:
well, analytics are not really "new" anymore.

been there since 816.

but -- i do get to specialize.  so I work with pretty much "this"

May 08, 2003 pauline -- Thanks for the question regarding "Transport tablespace", version 8.1.5
You Asked
Hi Tom,
I am trying to alter the tablespace to the transportation
tablspace. First I do:
 alter tablespace users read only;
Then do :
exp sys/change_on_install file=02072001.dmp
transport_tablespace=y tablespaces=users triggers=n
constraints=n
But I got the error as:
Export: Release 8.1.5.0.0 - Production on Fri Feb 9 13:19:08
2001

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.5.0.0 -
Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.0.0 - Production
Export done in US7ASCII character set and US7ASCII NCHAR
character set
Note: table data (rows) will not be exported
Note: constraints on tables will not be exported
About to export transportable tablespace metadata...
EXP-00008: ORACLE error 29341 encountered
ORA-29341: The transportable set is not self-contained
ORA-06512: at "SYS.DBMS_PLUGTS", line 1067
ORA-06512: at line 1
EXP-00000: Export terminated unsuccessfully

I checked packages under sys account and see the package named
'DBMS_PLUGTS' is there.
Could you point out how to solve the problem?
Thanks.

Pauline
and we said...
Your tablespace is NOT self contained (eg: it has an index but
not the table, or the table and not the index).

Run:

exec sys.dbms_tts.transport_set_check( 'users', TRUE );
select * from sys.transport_set_violations;

in sqlplus to see what the errors are.
Review & Followup
Rating: 4
IMP-00003: ORACLE error 19722 encountered September 23, 2002
Reviewer: Brigitte from Germany
Hallo Tom,

I exported 3 Tablespaces (read only) with Oracle 1.7.2 and copied the files

Then I try to import with Oracle 1.7.0

/oracle/k04/HK2>imp parfile=pari.dat

Import: Release 8.1.7.0.0 - Production on Mo Sep 23 13:41:01 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Username: sys as sysdba
Password:

Connected to: Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production
JServer Release 8.1.7.0.0 - Production

Export file created by EXPORT:V08.01.07 via conventional path
About to import transportable tablespace(s) metadata...
import done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set
. importing SYS's objects into SYS
IMP-00017: following statement failed with ORACLE error 19722:
 "BEGIN   sys.dbms_plugts.checkDatafile(NULL,432251738,13,1280,14,13,4194302,"
 "256,4609788,4789221,1,54525954,NULL,NULL,NULL); END;"
IMP-00003: ORACLE error 19722 encountered
ORA-19722: datafile $ORACLE_HOME/HK2/database/forever_tg2.dbf is an incorrect
version
ORA-06512: at "SYS.DBMS_PLUGTS", line 1577
ORA-06512: at line 1
IMP-00000: Import terminated unsuccessfully

with pari.dat:
transport_tablespace=y
datafiles=('$ORACLE_HOME/HK2/database/forever_tg2.dbf',
'$ORACLE_HOME/HK2/database/forever_tk.dbf',
'$ORACLE_HOME/HK2/database/forever_I.dbf')
tablespaces=(forever_tg2,forever_tk.forever_i)
file=exphk.dmp

Is it impossible to transport tablespaces from Oracle 8.1.7.2 to Oracle 8.1.7.0?

Thanks

Brigitte

Followup:
1.7.2 isn't a version by the way.  8.1.7.2 is and I guess that is what you
meant.

anyway, we are upwards compatible, not backwards.  file formats change.  You
would be able to take a 8170 file and attach it to 8172 but not the other way in
general.

You can go up, but not down.

Rating: 5
December 13, 2002
Reviewer: A reader
Hi Tom

I am getting errors.

C:\>exp userid='gaurang/gaurang@ora92 as sysdba' transport_tablespace=y
tablespaces=users fil
e=c:\users.dmp

Export: Release 8.1.6.0.0 - Production on Fri Dec 13 10:07:23 2002

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to: Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
Export done in WE8ISO8859P1 character set and UTF8 NCHAR character set
server uses WE8MSWIN1252 character set (possible charset conversion)
Note: table data (rows) will not be exported
About to export transportable tablespace metadata...
EXP-00008: ORACLE error 6550 encountered
ORA-06550: line 1, column 10:
PLS-00306: wrong number or types of arguments in call to 'CHECKPLUGGABLE'
ORA-06550: line 1, column 10:
PL/SQL: Statement ignored
EXP-00000: Export terminated unsuccessfully

Thanks

Followup:
you cannot unplug a 9i datafile to be plugged into an 816 database.

this is 100% to be expected.  You cannot unplug <HIGHER VERSION> datafiles and
plug them into <LOWER VERSION> databases.....

that'll never work!  Good thing it failed at this step, else you would have
wasted that time copying the datafiles for nothing.

Rating: 3
TTS Question February 14, 2003
Reviewer: reader from USA
As per my understanding, the target database should not have the TS with the
same name as the one in TTS set. Hence my question :

I have a schema who should have the default TS as the one in TTS set. How do I
get around this?

Thanks.

Followup:
the target database MAY NOT (not should not, it is that it CANNOT)

the default TS is used for newly created objects without a tablespace clause.
beyond that it is meaningless.  By definition -- the TTS will be named the same
it was and the objects will be in there (in that tablespace).

Rating: 5
Production data copy using transport tablespace February 15, 2003
Reviewer: Amitabha Bhaumik from Indianapolis, IN
We need to have a copy of our production data in another database to test out
the regular backend datafixes that we currently have to do directly in
production ( Its an Intercative Voice Response system and many a times users
press the wrong button on the phone!!).
What are the advantages /disadvantages of creating transport tablespace for the
existing production database to replicate the production database in a test
environment.
Our oracle database is 8.1.7. on Unix.

thanks,
Amitabha

Followup:
well, in order to transport, the tablespaces will have to be READ ONLY in
production for a bit of time.  This time can be very short (eg: if you are using
mirrors and can split the mirrors briefly to make the copy).  This time can be
longer (eg: the time it takes to actually copy the files and do the metadata
export)

Another avenue to consider would be this -- use your HOT backups and restore
them to test.  It'll kill two birds:

o you get a fast copy
o you test your backups, your ability to restore

Rating: 4
Using transportable tablespaces May 07, 2003
Reviewer: Devanshi Parikh from NJ USA
If I have an 8.1.7.0 database(source) with character set USASCII7 and an 9.2.0.2
database(target) with character set
UTF8, can I unplug tablespace from the source database and plug them in the
target database ? Other then the version difference, the character sets are also
different.

Followup:
charactersets must match.

May 08, 2003 Srinivasan -- Thanks for the question regarding "Import of the entire database with Objects !!", version 8.1.7
You Asked
Hi Tom,

   I have used an Object to represent ADDRESS. I am able to do a complete export
of the schema. But when i try to import it into another schema, tables that
contain address as datatype for their column(s) are not getting created. I get
the following error. "Table Employee Master was skipped as OBJECT ADDRESS does
not exist or has an invalid identifier". I tried creating the object first and
then did an import, but the same errors repeated. What could be the problem?

Thanks in Advance
and we said...
This is not an export of an entire database (that would work).

This is trying to copy objects from schema to schema.

Here is a short excerpt from my book "Expert one on one Oracle" that covers this
exact topic (in short, you cannot do what you are trying to do, below tells how
to do what you need to do)

.....

A similar, but slightly different issue occurs with Oracle's object types.
Oracle allows you to create new datatypes in the database. Just like they
provide you with the NUMBER, DATE, VARCHAR2, and so on, types, you can add types
to the database. You can then create tables of that TYPE or create tables with
columns, which are of that TYPE. A common course of action is therefore to
create a schema and set about creating types in the schema and objects that rely
on those types – everything in one user account. If you ever want to 'clone'
that schema, you'll run into a serious problem. I'll demonstrate the problem
first and then describe why it happens and how to partially solve it.

First, we'll start with a schema like this:

tkyte@TKYTE816> create type my_type
  2  as object
  3  ( x int,
  4    y date,
  5    z varchar2(20)
  6  )
  7  /
Type created.

tkyte@TKYTE816> create table t1 of my_type
  2  /
Table created.

tkyte@TKYTE816> create table t2 ( a int, b my_type );
Table created.

tkyte@TKYTE816> insert into t1 values ( 1, sysdate, 'hello' );
1 row created.

tkyte@TKYTE816> insert into t2 values ( 55, my_type( 1, sysdate, 'hello') );
1 row created.

tkyte@TKYTE816> commit;
Commit complete.

tkyte@TKYTE816> host exp userid=tkyte/tkyte owner=tkyte

This gives us a copy of this schema. When we try to use the FROMUSER/TOUSER
option however, we quickly discover this:

tkyte@TKYTE816> host imp userid=sys/manager fromuser=tkyte touser=a;

Import: Release 8.1.6.0.0 - Production on Tue Mar 20 12:44:26 2001

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.6.0.0 - Production
With the Partitioning option
JServer Release 8.1.6.0.0 - Production

Export file created by EXPORT:V08.01.06 via conventional path

Warning: the objects were exported by TKYTE, not by you

import done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set
. importing TKYTE's objects into A
IMP-00017: following statement failed with ORACLE error 2304:
 "CREATE TYPE "MY_TYPE" TIMESTAMP '2001-03-20:12:44:21' OID '4A301F5AABF04A46"
 "88552E4AF5793176'  "
 "as object"
 "( x int,"
 "  y date,"
 "  z varchar2(20)"
 ")"
IMP-00003: ORACLE error 2304 encountered
ORA-02304: invalid object identifier literal
IMP-00063: Warning: Skipping table "A"."T1" because object type "A"."MY_TYPE"
cannot be created or has different identifier
IMP-00063: Warning: Skipping table "A"."T2" because object type "A"."MY_TYPE"
cannot be created or has different identifier
Import terminated successfully with warnings.

We are basically stuck at this point. We cannot create the type in A's schema,
and even if we could, it would be a different type and the import would still
not be happy. It would be like having two different NUMBER types in the database
– they would be different from each other. In the above, we were trying to
create two different MY_TYPE types, but treat them as one.

The issue (and this is not clear in the documentation) is that we should not
create a schema that has both TYPES and objects, especially if we want to
import/export the schema in this fashion. Similar to the way in which CTXSYS and
ORDSYS are setup for Oracle's interMedia, we should setup a schema to contain
our types. If we wish to use interMedia Text, we use the types available from
CTXSYS. If we wish to use interMedia Image capabilities, we use ORDSYS types. We
should do the same. I'll redo the above example in the proper way here:

our_types@TKYTE816> connect OUR_TYPES

our_types@TKYTE816> create type my_type
  2  as object
  3  ( x int,
  4    y date,
  5    z varchar2(20)
  6  )
  7  /
Type created.

our_types@TKYTE816> grant all on my_type to public;
Grant succeeded.

Here, we set up a schema that will hold our types – any and all of our types.
All schemas will use these types, never their own personal types, which will
become strongly tied to their account. Now, we will redo our example from above:

tkyte@TKYTE816> connect tkyte

tkyte@TKYTE816> create table t1 of our_types.my_type
  2  /
Table created.

tkyte@TKYTE816> create table t2 ( a int, b our_types.my_type );
Table created.

tkyte@TKYTE816> insert into t1 values ( 1, sysdate, 'hello' );
1 row created.

tkyte@TKYTE816> insert into t2 values ( 55,
  2  our_types.my_type( 1, sysdate, 'hello') );
1 row created.

tkyte@TKYTE816> commit;
Commit complete.

tkyte@TKYTE816> host exp userid=tkyte/tkyte owner=tkyte

So, the only difference here is that we are using OUR_TYPES.MY_TYPE, not just
MY_TYPE. Since we can never create synonyms for TYPES, this schema qualification
is mandatory, we must fully qualify the object name with the schema name that
owns them. This is the way we must do it when using interMedia types (CTXSYS,
ORDSYS objects for example) and indexes as well – we always fully qualify them.
Pick the schema name of your TYPE holder with care for this reason, you will be
living with it for a while!

Now, let's see how the import goes this time:

tkyte@TKYTE816> host imp userid=sys/manager fromuser=tkyte touser=a;

Import: Release 8.1.6.0.0 - Production on Tue Mar 20 12:49:33 2001
(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.6.0.0 - Production
With the Partitioning option
JServer Release 8.1.6.0.0 - Production

Export file created by EXPORT:V08.01.06 via conventional path

Warning: the objects were exported by TKYTE, not by you

import done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set
. importing TKYTE's objects into A
IMP-00017: following statement failed with ORACLE error 2304:
 "CREATE TABLE "T1" OF "OUR_TYPES"."MY_TYPE" OID 'AC60D4D90ED1428B84D245357AD"
 "F2DF3' OIDINDEX (PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 524288)"
 " TABLESPACE "DATA") PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 LOGGING S"
 "TORAGE(INITIAL 524288) TABLESPACE "DATA""
IMP-00003: ORACLE error 2304 encountered
ORA-02304: invalid object identifier literal
. . importing table                           "T2"          1 rows imported
Import terminated successfully with warnings.

We fared better, but it is still not perfect yet. The pure OBJECT table failed
but the relational table with the object type succeeded. This is to be expected.
The object table is not the same object table technically, and objects are very
picky about that. We can work around this particular issue however, since these
two tables are in fact to be constructed on the same type. What we must do is
pre-create the object table in A's schema. We can use the IMP INDEXFILE= option
to get the DDL:

a@TKYTE816> host imp userid=a/a tables=t1 indexfile=t1.sql

If we edit the resulting T1.SQL we'll find:

REM  CREATE TABLE "A"."T1" OF "OUR_TYPES"."MY_TYPE" OID
REM  'AC60D4D90ED1428B84D245357ADF2DF3' OIDINDEX (PCTFREE 10 INITRANS 2
REM  MAXTRANS 255 STORAGE(INITIAL 524288) TABLESPACE "DATA") PCTFREE 10
REM  PCTUSED 40 INITRANS 1 MAXTRANS 255 LOGGING STORAGE(INITIAL 524288)
REM  TABLESPACE "DATA" ;
REM  ALTER TABLE "A"."T1" MODIFY ("SYS_NC_OID$" DEFAULT SYS_OP_GUID()) ;
REM  ... 1 rows

We need to remove the REM characters as well as the OID xxxxx clause and then
execute:

a@TKYTE816> CREATE TABLE "A"."T1" OF "OUR_TYPES"."MY_TYPE"
  2  OIDINDEX (PCTFREE 10 INITRANS 2
  3  MAXTRANS 255 STORAGE(INITIAL 524288) TABLESPACE "DATA") PCTFREE 10
  4  PCTUSED 40 INITRANS 1 MAXTRANS 255 LOGGING STORAGE(INITIAL 524288)
  5  TABLESPACE "DATA" ;

Table created.

a@TKYTE816> ALTER TABLE "A"."T1" MODIFY ("SYS_NC_OID$" DEFAULT SYS_OP_GUID()) ;

Table altered.

Now we can execute:

a@TKYTE816> host imp userid=a/a tables=t1 ignore=y

Import: Release 8.1.6.0.0 - Production on Tue Mar 20 13:01:24 2001

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.6.0.0 - Production
With the Partitioning option
JServer Release 8.1.6.0.0 - Production

Export file created by EXPORT:V08.01.06 via conventional path

Warning: the objects were exported by TKYTE, not by you

import done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set
. importing TKYTE's objects into A
. . importing table                           "T1"          1 rows imported
Import terminated successfully without warnings.

a@TKYTE816> select * from t1;

         X Y         Z
---------- --------- --------------------
         1 20-MAR-01 hello

and we have our data back.
Review & Followup
Rating: 4
copying objects July 09, 2002
Reviewer: Venkatasubramanian from Chennai,India
Tom,

Excellent illustration. But, you have mentioned that "we can never create
synonyms for TYPES, this schema qualification is mandatory,...."

I tried to create a public synonym for a type and could do the following way. If
I am wrong/did not understand what you meant, kindly correct.

sitadmin@SITOASIS817>create type mytype as object(x int, y varchar2(20))
  2  /

Type created.

sitadmin@SITOASIS817>create public synonym p_mytype for mytype;

Synonym created.

sitadmin@SITOASIS817>desc mytype
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
 X                                                              NUMBER(38)
 Y                                                              VARCHAR2(20)

sitadmin@SITOASIS817>desc p_mytype
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
 X                                                              NUMBER(38)
 Y                                                              VARCHAR2(20)

sitadmin@SITOASIS817>@connect sitoasis
Connected.

GLOBAL_NAME
---------------------------------------------------------------------------------
-------------------
sitoasis@SITOASIS817

sitoasis@SITOASIS817>desc p_mytype
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
------------------------------------
 X                                                              NUMBER(38)
 Y                                                              VARCHAR2(20)

sitoasis@SITOASIS817>spool off

Followup:
Ok, what I meant was "you cannot use a public synonym that points to a type"

ops$tkyte@ORA817DEV.US.ORACLE.COM> create type foo as object ( x int );
  2  /

Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create synonym foo_syn for foo;

Synonym created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x foo_syn );
create table t ( x foo_syn )
                   *
ERROR at line 1:
ORA-00902: invalid datatype

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x foo );

Table created.

Note that in Oracle9i Release 2, this restriction has been lifted:

ops$tkyte@ORA920> create or replace type foo as object ( x int );
  2  /
Type created.

ops$tkyte@ORA920> create public synonym foo_syn for foo;
Synonym created.

ops$tkyte@ORA920> create table t ( x foo_syn );
Table created.

Rating: 4
Useful stuff....but.... November 28, 2002
Reviewer: Robert Boyle from Ireland
Hi Tom,

Product info first, I am running Oracle 9.2 on Win2000.

Now to the meaty part! I have created the address_type as per your book and
created a public synonym and used the address_type in another schema as a type
for a column in a table.

However, when I go to grant select on this table for another user/role, I get
the following error:

ORA-01720: grant option does not exist for 'ROBERT.ADDRESS_TYPE'

I am guessing that I need some sort of admin grant to be able to pass on the
grant but I thought the only grant would be EXECUTE for a type?  I have granted
PUBLIC access on address_type for the moment and both users do have rights.

The user I am trying to grant the select to can also see another table that had
the rights granted to it before I added a field with address_type in it and they
can see that field no problem.

I even tried the AUTHID clause too incase it was invokers right issue but still
no luck.  Am I missing something totally obvious?

Cheers
Robert

Followup:
grant execute on that with WITH GRANT OPTION.

the type can contain executable code.  In order for the second user to pass on
the ability to use this code -- they will need the grant with the grant option.

Rating: 4
Apologies November 29, 2002
Reviewer: Robert from Eire
For my misreading of the documentation, I have now used:
grant execute on address_type to public with grant option
which of course has worked!

Cheers
Robert.

Rating: 4
a funny import requirement May 08, 2003
Reviewer: umesh from bangalore india
Tom
Good Day!
I have export of Master-Detail tables DEPT, EMP
Is it possible to import them into a user . But while they get imported they
should be NEW_DEPT, NEW_EMP (i.e tables with different names) I know I can
import in a different schema and use "CREATE TABLE NEW_DEPT AS SELECT * FROM
DEPT"
and "CREATE TABLE NEW_EMP AS SELECT * FROM EMP"

may sound foolish! Thanks for your patience

Followup:
you could rename them AFTER importing.

You could precreate them, create views EMP as select * from NEW_EMP etc and then
import with "ignore=y constraints=n (lots of other nos to avoid triggers,
indexes and such)". then drop the views (that works in most releases)

May 08, 2003 muhammad ahmad -- Thanks for the question regarding "Dynamic Function ", version 8.1.7
You Asked
I am wondering how it is possible....

I enter a string in the query gives the results according to criteria but
independant of columns searching the whole table.

I mean to say that the value may be in any column or in more than one column
must display from a table.

The function should accept an argument and result all the rows that contains
that value again remember that value may be of any datatype and may be in any of
the column(simply stated" Search the whole table".
and we said...
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6262416768516
for various implementations of this.
Review & Followup
Rating: 2
suggest a solution May 08, 2003
Reviewer: muhammad ahmad from pakistan
Is this a better solution or not for a performance reason:
or suggest some other one
select * from test
where (col1 || col2 || col3|| col4 || col5) like '%string%'

Followup:
say col1 and col2 are varchar2(4000)

and they each have 2001 characters in them.

now what?

and no, it would not be.  an OR will short circut.  You have to process all
columns (build a big string) and then compare.

I compare column by columns.

May 08, 2003 Syed -- Thanks for the question regarding "How To Count N Records & Stop", version 8.1.7
You Asked
Hi Tom,

I am implementing a search engine which searches the database for records.  It
performs the following 2 steps:

1. Count(*) the number of records that will be fetched by the search SQL. i.e.
depending on what fields are populated in the search page, a dynamic search SQL
is generated by the Java code to be executed.  This is required to check if the
number of records are not exceeding the 1000 records (as set by the business
rules).  Such as

SQL> SELECT COUNT(*) FROM emp WHERE deptno=10;

2. The actual search SQL is now executed to return the records like:

SQL> SELECT * FROM emp WHERE deptno=10;

What I want to ask you is if its possible that the COUNT(*) SQL will execute for
the first 1000 records and then stop like:

SQL> SELECT COUNT(*) FROM emp WHERE deptno=10;

And I want this statement to stop and return if it exceeds more then 5 records
without counting all the records with deptno=10.

This has been a problem because the tables that are involved in the search have
around more then 4 million records and even counting on the index won't help
much given that the user can enter any silly search criteria that might result
in million of rows.  We have a tight performance benchmark to achieve which is 4
secs. and if the COUNT(*) goes for counting a search criteria thats hitting 1
million records it take more then 4 secs.

Your expert advice on this will be highly appreciated.

Thanks :)
and we said...
Here is my advice:

a) do not count, never count -- that is a total waste of resources

b) run the query like this:

   select * from emp where deptno = 10 AND ROWNUM <= 1000;

c) if you get 1000 rows back, put up a message saying "sorry, your query
returned more then 1000 records.  We are only going to show you the first 1000,
please refine your search  criteria"

Much better then counting to see if you should run the query at all.  Also, they
get to see some results even if they exceed this threshold.  The count isn't
relevant here at all since simply running the query and getting the first 1k
records will give you the same exact information!
Review & Followup
Rating: 3
This is Useful But Its Bit Tricky August 14, 2002
Reviewer: Syed Abid from New Zealand
Tom, Thanks very much for your valuable advice.
But it gets bit tricky when the end-user is using a low spec machine connecting
remotely with a router(128k) in between, logging to database via an application
server.1000 records (in my case) could be upto 55KB of data that may not at all
required by the user.Secondly the rows returned could be duplicate and we want
distinct rows.For e.g. a user can search for ename='SMITH' which will have more
then 1 record in table with same empno.So 1000 records as well doesn't guarantee
that they will be 1000 distinct records. If you weight the resource consumption
in doing a COUNT DISTINCT first and then running the query OR just running the
query with ROWNUM<1000 (in most of the search scenarios) the first one will be
better.Thirdly if we do implement query with ROWNUM<1000 then still JAVA has to
handle all 1000 records before knowing that it hits the 1k mark.What we are
doing currently is processing 10 records at a time only. Please remember we have
to do all this under 4 seconds.

Followup:
Well, the time to count the distinct records and see if there are 1000 will be
close in time to actually getting the 1000.

You see, say you have the query "select ename from emp"

You want distinct, so that needs to be  "select distinct ename from emp"

You want to know if there are more then 1000 records coming back right -- so,
you would "select count(*) from ( select distinct ename from emp ) where rownum
<= 1000"

Well, the entire distinct'ed query must be executed more or less to completion
(some shortcuts we can take but it'll still be a ton of work)

I would rather:

select * from (  select distinct ename from emp ) where rownum <= 1000;

and have my java on the app server (which ain't connecting to the database over
a 128k line i would suspect) and let the java in the middle say "oh my, I got
55k of data -- 1000 rows, thats too much, lets send back and error message"
rather then java saying -- hey, count these records and if it's less then 1000
get the records you just counted.

Java fetching 1000 records should be fairly fast.

I'm still dead set against doing a count, it is just a waste.  You'll be harder
pressed to meet your response time requirements if you effectively DOUBLE YOUR
WORKLOAD (yes, that is what you would be doing by counting and then running the
query again).

Rating: 5
count May 07, 2003
Reviewer: mo
Tom:

1.  Can I get a count of how many null columns in one record in a table in one
SQL statement or I have to write a pl/sql and use a counter and IF statements to
check if each column is null?

2.  Can you count the number of columns in a table?

THank you,

Followup:
select decode( c1, null, 1, 0 ) + decode( c2, null, 1, 0 ) + ....
  from t;

select count(*) from all_tables where table_name = 'X' and owner = 'Y'

Rating: 5
Number of columns in table May 08, 2003
Reviewer: Mark A. Williams from Indianapolis, IN USA
To get num columns in table, what about:

select count(*) from all_tab_columns where owner = '<OWNER>' and  table_name =
'<TABLE>';

Instead of:

select count(*) from all_tables where table_name = 'X' and owner = 'Y'

(which should only return 0 or 1, i hope)

- Mark

Followup:
doh , that was supposed to be all_tab_columns, lets hope he was able to figure
that out on his own...

May 09, 2003 Kailash -- Thanks for the question regarding "Transaction Isolation Level", version 8.1.7
You Asked
Tom,

I tried the following 2 scenarios :

Case #1 : serializable

Session 1
=========
Set transaction isolation level serializable;
select * from a;
(displayed 2 rows)

Session 2
=========
delete from a;
commit;

Session 1
=========
select * from a;
(displayed the 2 rows again)
commit;
select * from a;
(no rows selected)

Can you explain me what happened actually?

2. Restored the table again

Case#2 : read committed

Session 1
=========
set transaction isolation level read committed
select * from a;
(displayed 2 rows)

Session 2
=========
delete from a;
(2 rows deleted)
commit

session 1
=========
select * from a;
(no rows selected)
commit
select * from a;
(no rows selected)

What happened in this case?

Case#2 is the usual way Oracle works, right? Can you explain me what is
important about case #2 and what is difference between case#1 and case #2

I read through your book "Expert One On One", but not able to understand. I
would appreciate if you could provide me an explanation with an example to make
things more clear. Awaiting your response on this.

3. Also please explain me under what situations these might be useful
and we said...
1) umm -- well, you asked for serializable in session 1 which means that the
database will appear to you to be frozen in time...

It will ensure that every single thing you see is "as of" the point in time you
set "serializable please".

So, when you did the first select *, it checked to make sure the data didn't
change since you set the transaction isolation (it hadn't, so it just read from
the blocks).  The second time, it checked the blocks again - found they HAD
changes and went to the rollback segments to "undo" those changes -- to restore
the block to the way it looked when you said "serializable"

2) well, the same thing as one actually EXCEPT that the queries were to return
the data at the point in time they were submitted.

So, when you did the first select *, it checked to make sure the data didn't
change since you submitted the query (it hadn't, so it just read from the
blocks).  The second time, it checked the blocks again - to make sure the data
didn't change since you submitted the query (it hadn't, so it just read from the
blocks).

Please reread some of the Expert one on one stuff -- redo, rollback, concurrency
controls.  I have more information in there, along with examples, then I could
put here.  Just read pages 123-132 a couple more times.  If you have a concrete
question -- be glad to try and address it.

3) for that -- read the whole book.  The answer to this comes from
understanding.  When you understand what it does -- you will be able to apply it
to your problem at hand.
Review & Followup
Rating: 5
ORA-08177: can't serialize access for this transaction May 08, 2003
Reviewer: Sami from Bangalore,India
Dear Tom,

Thanks for your wonderful service. I need your help again..

How come value "2" has been inserted into SESSION2 but  value "5" is giving
error in the same session?

                19:12:14 SESSION2>set transaction isolation level serializable;

                Transaction set.

                19:12:19 SESSION2>select * from t;
                            no rows selected

19:12:54 SESSION1>set transaction isolation level read committed;
Transaction set.

19:12:58 SESSION1>select * from t;

no rows selected

19:13:04 SESSION1>insert into t values(1,1,1,1);

1 row created.

            19:13:09 SESSION2>insert into t values(2,2,2,2);

            1 row created.

19:13:24 SESSION1>commit;

Commit complete.
            19:13:44 SESSION2>insert into t values(3,3,3,3);

            1 row created.

19:13:53 SESSION1>insert into t values(4,4,4,4);

1 row created.

            19:14:00 SESSION2>insert into t values(5,5,5,5);
            insert into t values(5,5,5,5)
                        *
            ERROR at line 1:
            ORA-08177: can't serialize access for this transaction

19:14:24 SESSION1>commit;

Commit complete.

19:14:26 SESSION1>

              19:14:36 SESSION2>insert into t values(5,5,5,5);
              insert into t values(5,5,5,5)
                          *
              ERROR at line 1:
              ORA-08177: can't serialize access for this transaction

How come value "2" has been inserted into SESSION2 but  value "5" is giving
error in the same session?
I went thru all the docs related to isolation but not able to understand.

Kindly provide your comments.
Thanks in adavance.

Followup:
an incomplete test that a simple dry run on my system cannot reproduce....

start with "drop table t"
           "create table t ...."
           "alter session....."

and show the entire progression.

Rating: 5
May 08, 2003
Reviewer: Dave from Colorado
Can you just clarify one thing for me?

Is the point-in-time for consistency as-of the time that the isolation level was
set to serializable, or at the time that the first serializable query started?

Followup:
easy enough to see:

ops$tkyte@ORA920> create table t ( x int );

Table created.

ops$tkyte@ORA920> insert into t values ( 1 );

1 row created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set transaction isolation level serializable;

Transaction set.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2    pragma autonomous_transaction;
  3  begin
  4          update t set x = 555;
  5          commit;
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from t;

         X
----------
         1

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920> select * from t;

         X
----------
       555

ops$tkyte@ORA920>
ops$tkyte@ORA920>

from the time it was issued.

Rating: 5
ORA-08177: can't serialize access for this transaction May 08, 2003
Reviewer: Sami from Bangalore,India
Dear Tom, Sorry for my previous incomplete test case.
Hope this will explain the problem clearly.

In short, Is it possible to manipulate data on a table from both "read
committed" and "serializable" transaction?

C:\>sqlplus demo/demo@tamizh1

SQL*Plus: Release 9.2.0.1.0 - Production on Fri May 9 08:50:51 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, Oracle Label Security, OLAP and Oracle Data Mining
options
JServer Release 9.2.0.1.0 - Production

SQL> set sqlprompt SESSION1>
SESSION1>set time on
08:51:07 SESSION1>drop table t;

Table dropped.

08:51:16 SESSION1>create table t(a number,b number,c number,d number);

Table created.

08:52:05 SESSION1>alter table t add constraint t_pk primary key(a);

Table altered.

08:52:18 SESSION1>set transaction isolation level READ COMMITTED;

Transaction set.

08:53:09 SESSION1>insert into t values(1,1,1,1);

1 row created.

08:53:37 SESSION1>commit;

Commit complete.

                    08:54:41 SESSION2>show user
                    USER is "DEMO"
                    08:54:43 SESSION2>set transaction isolation level
SERIALIZABLE;

                    Transaction set.

                    08:54:52 SESSION2>select * from t;

                    no rows selected

                    08:55:06 SESSION2>insert into t values(2,2,2,2);

                    1 row created.

                    08:55:17 SESSION2>insert into t values(3,3,3,3);

                    1 row created.

08:55:31 SESSION1>insert into t values(4,4,4,4);

1 row created.

                    08:55:43 SESSION2>insert into t values(5,5,5,5);
                    insert into t values(5,5,5,5)
                                *
                    ERROR at line 1:
                    ORA-08177: can't serialize access for this transaction

08:56:03 SESSION1>commit;

Commit complete.
                    08:56:09 SESSION2>insert into t values(6,6,6,6);
                    insert into t values(6,6,6,6)
                                *
                    ERROR at line 1:
                    ORA-08177: can't serialize access for this transaction

08:56:28 SESSION1>

Followup:
your test doesn't compute.

At time 8:54:43, there should be a row in there (in T).

That and I quite simply cannot reproduce it as you have it.  I tried -- really,
but no go.

Rating: 5
transaction isolation Vs Index May 09, 2003
Reviewer: Sami from Bangalore,India
Dear Tom,

Kindly ignore my previous test case and look at this one.
Does isolation depent on indexes?

/***************************
TEST CASE 1: WITH NO INDEX
****************************/
14:53:15 SESSION2>create table r1(a number,b number,c number,d number);

Table created.
                14:53:20 SESSION1>set transaction isolation level read
committed;

                Transaction set.

14:53:42 SESSION2>set transaction isolation level serializable;

Transaction set.

14:53:48 SESSION2>select * from r1;

no rows selected

                14:53:58 SESSION1>select * from r1;

                no rows selected

                14:54:06 SESSION1>insert into r1 values(1,1,1,1);

                1 row created.

14:54:14 SESSION2>select * from r1;

no rows selected
                14:54:34 SESSION1>commit;

                Commit complete.

                14:54:38 SESSION1>insert into r1 values(3,3,3,3);

                1 row created.

14:54:48 SESSION2>insert into r1 values(2,2,2,2);

1 row created.

14:55:02 SESSION2>insert into r1 values(4,4,4,4);

1 row created.

                14:55:12 SESSION1>insert into r1 values(5,5,5,5);

                1 row created.

14:55:21 SESSION2>insert into r1 values(6,6,6,6);

1 row created.

                14:55:31 SESSION1>commit;

                Commit complete.

14:55:38 SESSION2>insert into r1 values(7,7,7,7);

1 row created.

14:55:42 SESSION2>commit;

Commit complete.

/***************************
TEST CASE 2: WITH INDEX(Primary Key)
****************************/

14:55:48 SESSION2>create table r2(a number,b number,c number,d number);

Table created.
                14:55:51 SESSION1>set transaction isolation level read
committed;

                Transaction set.

14:56:06 SESSION2>alter table r2 add constraint r2_pk primary key(a);

Table altered.

14:56:34 SESSION2>set transaction isolation level serializable;

Transaction set.

14:56:43 SESSION2>select * from r2;

no rows selected

                14:56:47 SESSION1>select * from r2;

                no rows selected

                14:57:07 SESSION1>insert into r2 values(1,1,1,1);

                1 row created.

14:57:12 SESSION2>select * from r2;

no rows selected

                14:57:26 SESSION1>commit;

                Commit complete.

                14:57:29 SESSION1>

14:57:32 SESSION2>insert into r2 values(2,2,2,2);
insert into r2 values(2,2,2,2)
            *
ERROR at line 1:
ORA-08177: can't serialize access for this transaction

14:57:38 SESSION2>

May 09, 2003 vishal -- Thanks for the question regarding "Recovery using export dump file", version oracle8i version 8.1.5.0.0
You Asked
hello Tom,

1) I have Oracle 8.1.5.0.0 running on solaris 2.6. I lost one of
my datafile and my database is running in NOARCHIVELOG mode. I
have full database export taken in the morning. How can i
recover my database using export backup ? recreating the
database and import everything is the only option ? what are the
steps ?

2) one of my colleague accidentally dropped a datafile which
belongs to RBS tablespace. This file had some active rollback
segments. we recover the database by initializing
_corrupted_rollback_segments variable in the init.ora file. is
this the correct approach ? what else we could have done ?

thanks

vishal
and we said...
1) You cannot really recover from a DMP file -- you can retrieve
objects from a point in time from the DMP file but its not a
"backup" in the true sense.

What I would do is make a note of the tables in the affected
tablespace (you can get this from the data dictionary).

You can then offline drop the affected datafile (alter database
datafile '/path/to/file/you/deleted.dbf offline drop) and then
drop the tablespace with the missing file INCLUDING contents
(drop tablespace affected_tablespace including contents).  You
would then recreate this tablespace with new datafiles and then

  $ imp userid=u/p 'tables=(t1,t2,t3,...,tn)'

where t1,t2...,tn are the tables that were in the tablespace.
They will be recovered to the point in time of the export.

Please take a cold full backup before doing such an operation to
protect yourself against further loss of data.

You have to rebuild this entire tablespace -- not just what was
in that one datafile.

2) after doing that (_corrupted_rollback_segments) the ONLY
valid thing to do is a full database export, shutdown, copy that
database elsewhere and recreate a new instance.  If you have not
done so, you must do that now.  You may have logical
inconsistencies in that instance that will appear over time as
ORA-600's and other serious side effects.  That parameter is a
last ditch effort to save data.  That instance is suspect.
Review & Followup
Rating: 2
Recover from file with rollback segments December 05, 2001
Reviewer: Bernd Boecker from Sankt Augustin, near Bonn (GERMANY)
Hello Tom,

I appreciate your comments much.  Here a further possibility for recovery a file
with rollback segments:

Error scenario:

1. set transaction use rollback segment rb1;
2. INSERTS into's...
3. SHUTDOWN ABORT;  (simulate Media errors)
4. Delete file rb1.ora (Tablespace RB1 with segment rb1 );
5. Backup of the file copy

Recover:

1. comment out INIT.ORA ROLLBACK_SEGMENT parameter , so ORACLE does not try to
find the incorrect segment rb1
2. STARTUP MOUNT
3. ALTER DATABASE DATAFILE  ‚rb1.ora' OFFLINE;
4. ALTER DATABASE OPEN  # now we are in business
5. CREATE ROLLBACK SEGMENT rbtemp TABLESPACE SYSTEM; # We need Temporary RBS for
further steps;
6. ALTER ROLLBACK SEGMENT rbtemp ONLINE;
7. RECOVER TABLESPACE RB1;
8. ALTER TABLESPACE RB1 ONLINE;
9. ALTER ROLLBACK SEGMENT rb1 ONLINE;
10. ALTER  ROLLBACK SEGMENT rbtemp OFFLINE;
11. DROP ROLLBACK SEGMENT rbtemp;

Result:  Successfully rollback uncommitted Transactions, no suspect instance.

Best regards

Bernd

Followup:
yes, that is standard archivelog mode backup recovery procedures -- if "backup
of the file copy" means "restore rb1.ora from the last backup".

You simply recovered the datafile.  In the question here, they start with "we
are in noarchivelog mode" meaning there is no opportunity for recovery from
media failure.

Rating: 5
Your Help Needed ! ! ! May 09, 2003
Reviewer: Riaz Shahid from PRAL, Islamabad, Pakistan
Dear Tom !

I am running my database in NonArchivelog mode. One of datafiles of tablespace
RBS got corrupted (as shown below). So i want to drop the rbs tablespace and
re-create it but i am unable to do so as shown below:

C:\>svrmgrl

Oracle Server Manager Release 3.1.7.0.0 - Production

Copyright (c) 1997, 1999, Oracle Corporation.  All Rights Reserved.

Oracle8i Enterprise Edition Release 8.1.7.2.1 - Production
With the Partitioning option
JServer Release 8.1.7.2.1 - Production

SVRMGR> connect / as sysdba
Connected.
SVRMGR> shutdown abort;
ORACLE instance shut down.
SVRMGR> startup
ORACLE instance started.
Total System Global Area                 178436124 bytes
Fixed Size                               75804 bytes
Variable Size                            104882176 bytes
Database Buffers                         73400320 bytes
Redo Buffers                             77824 bytes
Database mounted.
Database opened.
SVRMGR> select count(*) from cr.gstt05;
COUNT(*)
----------
ORA-00376: file 2 cannot be read at this time
ORA-01110: data file 2: 'E:\ORACLE\ORADATA\STARR\RBS01.DBF'
SVRMGR>  alter database datafile  'E:\ORACLE\ORADATA\STARR\RBS01.DBF' offline;
Statement processed.
SVRMGR> alter tablespace rbs offline;
alter tablespace rbs offline
*
ORA-01539: tablespace 'RBS' is not online
SVRMGR> drop tablespace rbs including contents;
drop tablespace rbs including contents
*
ORA-01548: active rollback segment 'RBS0' found, terminate dropping tablespace
SVRMGR> alter tablespace rbs online
     2> ;
alter tablespace rbs online
*
ORA-01113: file 2 needs media recovery
ORA-01110: data file 2: 'E:\ORACLE\ORADATA\STARR\RBS01.DBF'
SVRMGR> alter rollback segment rbs0 offline;
alter rollback segment rbs0 offline
*
ORA-01598: rollback segment 'RBS0' is not online

Note that i tried the media recovery but it asks for archived logs (that are not
available since database was running in Noarchivelog mode). Please advise what
should o do in order to re-create the tablespace RBS (and its datafiles). I have
no physical backup for this database. However, i've logical backup for this
database.

Moreover, how can i find the tables that are affected from this tablespace,
using the data dictionary ?

(I am using 8.1.7.2.1 on Windows 2000 Server)

Waiting for your expert advice...

Riaz

Followup:
you are, as they say, in a really bad place.

I hope you have cold backups.

You made a decision a long time ago that data loss is OK, when it happens (not
if, but when) we are OK with that.

RBS is a vital integral, needed and necessary component of the database.  You
cannot simply "slice it out".  We need it.  It is part of our "inner being" if
you will.

You cannot drop that rbs because it contains active rollback data (it was being
used).

Time to contact support and open a tar -- or just goto your last full cold
backup (hope you tested it)

Oh, I just read "and I've no backups" -- my expert advice -- contact support,
they'll go over your options which are somewhat limited as there were active
transactions in this rbs.  All options will lead to "rebuild database" in all
probability.

May 09, 2003 Robert -- Thanks for the question regarding "Shared Pool (from your book)", version 8.1.7.2
You Asked
Morning Tom, I hope you don't mind my asking question in this forum about what I
read in your book "Expert One-On_One"

Page 81, Line 7
"Even PL/SQL code...is managed in a paging mechanism....only the code
that is needed is loaded into the shared pool in small chunks"

I am quite confused about the "small chunks" reference....what exactly do you
mean ?

We know it's a fact that the entire package is loaded into memory (SGA) when
it's called.

So do you mean the whole pkg is NOT loaded directly into the shared pool  but
bits of code necessary to do the job is  put into the pool ?

Thanks for the clarification
Robert
and we said...
we use 4k chunks to manage memory so the plsql code is loaded into the shared
pool in little pieces -- so that all memory allocated in there is the same size
more or less and can be reused by other things easily.

Suppose we didn't and you have procedures of various sizes:

1k
10k
100k
500k
1m

Now, we load up a bunch of 1k/10k procedures -- we need to load a 1m procedure
but have run out of room.  So, we flush as many loaded procedures as we can in
the hopes of freeing up 1m of CONTIGOUS memory (this isn't the way we do it -
assuming we did tho...).  We probably cannot find 1m of contigous memory but we
have lots of 1k and 10k chunks here and there that if you added them all up
would be lots more then 1m.

So, by loading things into little pieces of say 4k -- when we goto load that 1m
procedure, we can flush some of the 10k routines and get enough 4k chunks to
hold our stuff.

So, instead of needing 1m of contigous memory for the package, we just need
enough 4k chunks out there to hold it.
Review & Followup
Rating: 4
Reader August 13, 2002
Reviewer: A reader
Tom,

Could you tell us the concept of Extents and Chunks in
shared pool. Is extent a contiguous space comprised of
multiple chunks by Forward coalescing mechanism of
adjacent free chunks.

Are the chunks connected by doubly linked list to previous
and next free chunk in the free list. Also if the memory is
managed in 4000 - 5000 bytes chunks, what sceanrio will
cause ORA 4031. According to the shared pool memory
management, Oracle can always allocate multiple chunks
if contiguous memory is not available

Thanks

Followup:
There is no concept of extents in the shared pool.  extents are a "disk" thing.

Just think of the shared pool as a really big "heap".  Like the heap managed by
C using malloc, calloc, realloc and free.  We just allocate memory in about 4k
chunks and everyone that uses memory is used to having things managed that way.
It is a method to decrease fragmentation (similar to the concept behind locally
managed tablespaces -- all extents (eg: chunks) are either the same size or some
very nice multiple of eachother and are hence very reusable -- no fragmentation)

4031 -- caused when we run out of shared pool memory, when all of the memory is
being used by things that cannot be flushed right now.

Rating: 5
November 12, 2002
Reviewer: Mirjana from Barcelona, Catalunya
Hi Tom,
I would like your comment on this. It's a metalink note DocId: 146559.1
"Diagnosing and Resolving Error ORA-04031"

"...
Shared Pool Fragmentation:
Every time a SQL or PL/SQL statement needs to be executed the parse
representation is loaded in the shared pool requiring a specific amount of free
contiguous space. The first resource where the database scans is the free memory
available in the shared pool. Once the free memory is exhausted, the database
looks for reusing an already allocated piece not in use. If a chunk with the
exact size is not available, the scan continues looking for space based on the
following criteria:
- The chuck size is larger than the required size
- The space is contiguous
- The chuck is available (not in use)
Then that chunk is split and the remaining free space is added to the
appropriate free space list. When the database is operating in this way for a
certain period of time the shared pool structure will be fragmented.
When the shared pool is suffering fragmentation, the allocation of a piece of
free space takes much more time generating slow performance in the database (the
"chunk allocation" is protected by a single latch called "shared pool latch"
which is held during the whole operation) or ORA-04031 errors (when the database
cannot find a contiguous piece of free memory).
...
The ORA-04031 is a result of lack of contiguous space in the shared pool
reserved space...
..."

Doesn't this say that specific amount of FREE CONTIGUOUS space is needed to load
an object into memory (?), which differs from "4k chunks" from the post above...

??

Thanks...

Followup:
Article-ID:         <Note:146559.1>
Circulation:        PUBLISHED (EXTERNAL)
Folder:             industries.clinical
Topic:              RXCUPGRADE:Upgrade Scripts
Title:              Oracle Clinical Release 3.2 Patchset 3 (1796644), Windows
                    README

I think your note number is wrong?  but anyway -- plsql is loaded in small
chunks.  A specific amount of free contigous space is needed - about 4k -- for
plsql.  (maybe lots of about 4k chunks perhaps but...)

Rating: 5
November 13, 2002
Reviewer: Mirjana from Barcelona
Ok!
Sorry, it was DocId 146599.1 with the title "Diagnosing and Resolving Error
ORA-04031".
There, it says nothing about 4k chunks to manage memory (?), or am I getting
this wrong?

Thanks!

Followup:
It just doesn't say it -- what it says is accurate but it doesn't say everything
(it covers 7.0 to 8.1 -- a hugely broad set of versions indeed! things change)

If you click through to related note:

Article-ID:         <Note:62143.1>
Circulation:        PUBLISHED (EXTERNAL)
Folder:             server.Performance.Database
Topic:              Tuning Shared Pool
Title:              Understanding and Tuning the Shared Pool in Oracle7,
                    Oracle8, and Oracle8i

You'll find:

...

Historic Notes
The notes here relate to pre-Oracle7.3 releases of Oracle and are included for
completeness only:

* In 7.3 the PLSQL was enhanced to used paged executable code reducing the
number of large allocations in the shared pool and reducing the need for
KEEPing.

......

Rating: 5
Oracle Clinical? May 09, 2003
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
Dear TOM,
I have heard about Oracle Clinical and want to know about it but unable to find
any info about it. Please give some info about it or any link if you have.
Thanks

Followup:
if you search for clinical on oracle.com, you'll get some hits on it.  It is a
fairly specific application that is typical sold through a sales rep, you might
ask yours about it.

May 09, 2003 Daniel -- Thanks for the question regarding "Partitioning - Especially the PARTITION by RANGE Clause", version 8.1.7
You Asked
Hello,
I have a question concerning partitioning.  We have a field called "PERIOD" in
the table which is a value of YYMM which is VARCHAR2.  I want the table
partitioned on this column, but the issue is the 2 digit year.  For example,
9902 is greater than 0012.  Thus, the rows will not fall into the correct
partitions as defined by the less than (partition boundry) clause.  So my
thought was to make the PARTITION BY RANGE clause as follows:

partition by range ( to_char(to_date(period,'RRMM'),'YYYYMM') )

which would solve the problem by including the century.  However, I don't think
you can use to_char and to_date in this clause, plus the fact that the partition
bounds is now expecting a 6 digit value.  Any help would be greatly appreciated.

To summarize, I need a way to partition by the column PERIOD (YYMM) without the
year 2000 through 2003 and beyond going into the wrong partition (i.e. some 199x
year).  In fact the partition bounds which if segmented by quarter would be
something like 9903 could not preceed, say 0312.  Also, if I start with say the
year 2000 and work my way to Quarter 1 of 2003, then once I enter the partion
for 1998 (i.e. 9804) all of the new 2003 data will go into the wrong partition.

Below is the code I am using for testing.

Thanks,
Dan.

TRUNCATE TABLE xxafp.AFP_TITSALDET;
DROP     TABLE xxafp.AFP_TITSALDET CASCADE CONSTRAINTS ;
CREATE   TABLE xxafp.AFP_TITSALDET (
  PERIOD               VARCHAR2 (4),
  DES1                 VARCHAR2 (3))
   TABLESPACE XXAFP_DATA NOLOGGING
   PCTFREE 10
   PCTUSED 90
   INITRANS 1
   MAXTRANS 255
  STORAGE (
   INITIAL 33554432
   NEXT 33554432
   MINEXTENTS 1
   MAXEXTENTS 100
   FREELISTS 1 FREELIST GROUPS 1 )
   NOCACHE
partition by range ( to_char(to_date(period,'RRMM'),'YYYYMM') )
(
partition sales_q1_99 values less than ('199904')
  pctfree 5
  pctused 95
  tablespace xxafp_data
  storage (initial 128M next 8M maxextents 1024),
partition sales_q2_99 values less than ('199907')
  pctfree 5
  pctused 95
  tablespace xxafp_data
  storage (initial 128M next 8M maxextents 1024),
partition sales_q4_02 values less than (maxvalue)
  pctfree 5
  pctused 95
  tablespace xxafp_data
  storage (initial 128M next 8M maxextents 1024)
);
and we said...
wow, you would think that this close to y2k -- we would still remember.

it takes 4 characters to store a year, period, plain and simple.  I don't get
it.

Anyway, you can do what you need:

ops$tkyte@ORA817DEV> CREATE TABLE t
  2  (
  3    year_month  varchar2(4)
  4  )
  5  PARTITION BY RANGE (year_month) (
  6    PARTITION part_jan_2000 VALUES LESS THAN ('0002') ,
  7    PARTITION part_feb_2000 VALUES LESS THAN ('0003') ,
  8    PARTITION part_mar_2000 VALUES LESS THAN ('0004') ,
  9    PARTITION part_jan_2001 VALUES LESS THAN ('0102') ,
 10    PARTITION part_feb_2001 VALUES LESS THAN ('0103') ,
 11    PARTITION part_mar_2001 VALUES LESS THAN ('0104') ,
 12    PARTITION part_jan_2002 VALUES LESS THAN ('0202') ,
 13    PARTITION part_feb_2002 VALUES LESS THAN ('0203') ,
 14    PARTITION part_mar_2002 VALUES LESS THAN ('0204') ,
 15    PARTITION part_jan_1998 VALUES LESS THAN ('9802') ,
 16    PARTITION part_feb_1998 VALUES LESS THAN ('9803') ,
 17    PARTITION part_mar_1998 VALUES LESS THAN ('9804') ,
 18    PARTITION part_jan_1999 VALUES LESS THAN ('9902') ,
 19    PARTITION part_feb_1999 VALUES LESS THAN ('9903') ,
 20    PARTITION part_mar_1999 VALUES LESS THAN ('9904') ,
 21    PARTITION junk VALUES LESS THAN (MAXVALUE)
 22  )
 23  ;

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert into t values ( '9801' );

1 row created.

ops$tkyte@ORA817DEV> insert into t values ( '9802' );

1 row created.

ops$tkyte@ORA817DEV> insert into t values ( '9803' );

1 row created.

ops$tkyte@ORA817DEV> insert into t values ( '9901' );

1 row created.

ops$tkyte@ORA817DEV> insert into t values ( '9902' );

1 row created.

ops$tkyte@ORA817DEV> insert into t values ( '9903' );

1 row created.

ops$tkyte@ORA817DEV> insert into t values ( '0001' );

1 row created.

ops$tkyte@ORA817DEV> insert into t values ( '0002' );

1 row created.

ops$tkyte@ORA817DEV> insert into t values ( '0003' );

1 row created.

ops$tkyte@ORA817DEV> insert into t values ( '0101' );

1 row created.

ops$tkyte@ORA817DEV> insert into t values ( '0102' );

1 row created.

ops$tkyte@ORA817DEV> insert into t values ( '0103' );

1 row created.

ops$tkyte@ORA817DEV> insert into t values ( '0201' );

1 row created.

ops$tkyte@ORA817DEV> insert into t values ( '0202' );

1 row created.

ops$tkyte@ORA817DEV> insert into t values ( '0203' );

1 row created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select year_month, 'part_jan_1998' from t partition(
part_jan_1998 ) union all
  2  select year_month, 'part_feb_1998' from t partition( part_feb_1998 ) union
all
  3  select year_month, 'part_mar_1998' from t partition( part_mar_1998 ) union
all
  4  select year_month, 'part_jan_1999' from t partition( part_jan_1999 ) union
all
  5  select year_month, 'part_feb_1999' from t partition( part_feb_1999 ) union
all
  6  select year_month, 'part_mar_1999' from t partition( part_mar_1999 ) union
all
  7  select year_month, 'part_jan_2000' from t partition( part_jan_2000 ) union
all
  8  select year_month, 'part_feb_2000' from t partition( part_feb_2000 ) union
all
  9  select year_month, 'part_mar_2000' from t partition( part_mar_2000 ) union
all
 10  select year_month, 'part_jan_2001' from t partition( part_jan_2001 ) union
all
 11  select year_month, 'part_feb_2001' from t partition( part_feb_2001 ) union
all
 12  select year_month, 'part_mar_2001' from t partition( part_mar_2001 ) union
all
 13  select year_month, 'part_jan_2002' from t partition( part_jan_2002 ) union
all
 14  select year_month, 'part_feb_2002' from t partition( part_feb_2002 ) union
all
 15  select year_month, 'part_mar_2002' from t partition( part_mar_2002 );

YEAR 'PART_JAN_199
---- -------------
9801 part_jan_1998
9802 part_feb_1998
9803 part_mar_1998
9901 part_jan_1999
9902 part_feb_1999
9903 part_mar_1999
0001 part_jan_2000
0002 part_feb_2000
0003 part_mar_2000
0101 part_jan_2001
0102 part_feb_2001
0103 part_mar_2001
0201 part_jan_2002
0202 part_feb_2002
0203 part_mar_2002

15 rows selected.
Review & Followup
Rating: 1
January 10, 2003
Reviewer: A reader

Rating: 3
Now comes the tricky part. January 10, 2003
Reviewer: Daniel Nelson from Minneapolis, MN
Thanks for the response Tom.  However, now comes the part I don't understand and
I am sorry for not explaining it very well the first time.  If you take your
example and now add 0303
insert into t values ( '0303' );

that row now is added to the partition (part_jan_1998) and not the maxvalues
partition as I would like.  I want all of the dates past 0204 in your example to
go into the MaxValues partition. Then I can split it at a later time.  This kind
of provides a hands off maintenance instead of creating the "new" partition
before any data would be inserted into the table.  For instance, in your
example, in order for the value "0303" to go into the correct partition, I would
have to create:

PARTITION part_mar_2003 VALUES LESS THAN ('0304')

ahead of time otherwise it will go into the
part_jan_1998 partition, which is wrong.  Also, this does not allow me to take
the partitions off-line without fear of "removing, for lack of a better word"
valid 2003 data.  Is there another option by altering the partition key with the
to_char and to_date functions as noted in my first e-mail?  I know the 2 digit
year is completely wrong, but this is not a simple fix as it is our sales detail
data and was designed by a consultant no longer here.

Thanks for your time.
Dan.

Followup:
well -- for that, you'll need to fix your attribute to be a proper year/month.

0303 is NOT greater then 9912 obviously, hence, it'll by definition not go into
the max values.

Either

a) do the right thing and fix the attribute
b) use a real date field
c) set up the partitions in advance.

Rating: 4
Partition Question May 09, 2003
Reviewer: Venkat
Hi Tom,

I wanted to know if a situation like this is possible or not. I want to create a
table with partitions and subpartitions. Here's the requirement.

create table t7 (flag char(1), fiscal_id varchar2(6), partition_key number)
-------------------------------------
Now, i want two primary partitions on flag whose values will be 'T' or 'R'. So,
i can create partition like this

partition by range (flag)
(partition p_1 values less than ('S'),
 partition p_2 values less than ('U'))
---------------------------------------------
Now,for partition p_1, i want to set up a subpartition on (partition_key) values
less than 3, 6
and
for partition p_2, i want to set up a subpartition on (fiscal_id) values less
than '199512','199612'
-----------------------------------------------

Is this situation possible? Kindly comment

Thanks

Followup:
you can first partition by range and then subpartition either by HASH or LIST
(in 9i).

so, in effect, 'no'

May 10, 2003 atul -- Thanks for the question regarding "Rebuilding", version 8.0.4
You Asked
Sir,

I know we can easily rebuild nonunique indexes but i want to know can we rebuild
primary key indexes or we have to drop and recreate those
indexes?

Thanks.
atul
and we said...
You can rebuild them as well -- BUT YOU RARELY, IF EVER NEED TO.

rebuilding indexes is 99% a waste of time.  You should work to find the 1% that
might sometime need it and ignore the rest.
Review & Followup
Rating: 4
why it is a waste of time May 05, 2002
Reviewer: A reader
Hi

Why rebulding index is a waste of time? If we have a index with level 6 instead
of 3 dont we have to perform more I/O? I from Oracle Documentation that
rebulding index is recommended but I see the opposite here :o

Followup:
When is the last time you saw one with a level of 6? (and did it go down to 3..)
 Rebuilding an index is only recommended when it NEEDS IT.  My point is, I see
people BLINDLY rebuidling ALL indexes every day, week, month -- whatever.  Just
an utter waste of time for most (might even be counter productive!).

Find the 1% that you need to rebuild and do them when they need to.  Not as a
part of a standard operating procedure (time to rebuld all of the indexes..
what a waste)

Rating: 5
agree May 05, 2002
Reviewer: A reader
I agree, i thought you mean rebuilding index is a waste of time, i dont do it
everyday anyway just when I see the level becomes 4  upwards

Rating: 3
Is it true for Bit map indexes too May 05, 2002
Reviewer: pawan from USA
From the various answers in this forum and also after testing it myself i agree
that rebuilding indexes is "mostly" a waste of time. However, recently one of
our production jobs which used to take approximately 40 minutes to complete did
not complete in 10 hours and after lot of playing around and finally rebuilding
the BIT map index we had on the Table key the process finally started completing
in reasonable amount of time. So my question is - Does the condition in
rebuilding Bit map and B tree indexes differ

Followup:
Bitmaps are a little different -- they are seriously affected by incremental
loads and in general stand to gain from a rebuild after some incremental loads.

Rating: 3
When to rebuild December 23, 2002
Reviewer: sam from NJ, USA
Tom,

In one of your response you said.

 You will rebuild an index in response to IDENTIFIED
and DEGRADED performance -- period.  You will not rebulid indexes based on a
schedule.  Period.

Is there any statistical way to know the response is bad, since response is very
relative term. What I mean is, is there a script which can tell me potential
problem related to Indexes??

Thanks

Followup:
watch statspack - look at the top sql, especially by buffer gets.  If one starts
creeping up there over time, could be an index that needs looking at.

Rating: 3
dbms_stats.gather_table_stats after rebuild a table -- 9.2.0.3 May 09, 2003
Reviewer: Baqir Hussain from emeryville, CA USA
Tom,
I plan to rebuild a table as follow in 9.2.0.3:

* rename table
* drop constraint
* create table
* insert from rnamed table
* drop indexes
* recreate indexes
* put back constraint

When running the following script, show no entries in either of the columns:

SELECT INDEX_NAME "NAME", NUM_ROWS, DISTINCT_KEYS "DISTINCT",
   LEAF_BLOCKS, CLUSTERING_FACTOR "CF", BLEVEL "LEVEL",
   AVG_LEAF_BLOCKS_PER_KEY "ALFBPKEY"
   FROM DBA_INDEXES
   WHERE owner = 'ORACLE' and table_name = 'FOO'
  ORDER BY INDEX_NAME

My questions are :

1. After rebuilding a table do I have to run dbms_stats.gather_table_stats on
this table??
-- (even though the sql is executing very well without running dbms_stats on
this table in a test environment)
2. Do I have to run dbms_stats.gather_table_stats every time when a new index is
created for a table?

Apprecaite your response in advance

Thanks

Followup:
1) if you need statistics, of course.  is the original table having stats?  then
this one should.

2) no, you can gather index stats.

Rating: 4
May 09, 2003
Reviewer: A reader
>>When is the last time you saw one with a level of 6? (and did it go down to
3..)

How to find out the Level? Any query or info?

Followup:
look in user_indexes after you analyze it.

May 10, 2003 Danielle -- Thanks for the question regarding "Does oracle support bi-direction(either ascending or descending) index?", version 8.1.7
You Asked
I understand we can specify asc or desc when creating index. And wonder if it is
possible to specify it in some way like BIDIR, with which oracle can identify
the best direction (asc or desc) based on the query.
Is there such a feature in oracle 8.1.7?

Thanks for your time,

Danielle
and we said...
Oracle will use indexes in ascending and desc mode as needed.  It can and will
read indexes "front to back" or "back to front".

the DESC option on the create index is applied at the column level so that an
index:

 ( a, b desc, c )

will sort by A ascending, B descending, C ascending -- something that cannot be
done just by reading the index backwards and forwards....
Review & Followup
Rating: 5
Indexes September 14, 2001
Reviewer: jm from USA
But why will we use such an index like ( a , b desc , c) .
Can you explain this with some example.

Followup:
create index on emp ( job, sal DESC, ename );

That will sort the data by job from low to high (A-Z) and salary HIGH to low and
within the same salaries by ename.

That would show you the highest paid people FIRST by job for example.

Rating: 4
artistic way to do it September 14, 2001
Reviewer: Mikito Harakiri from SF, CA
&lttitle, MINUS(sal), ...&gt should also do the trick

Rating: 3
need further clarification September 14, 2001
Reviewer: Danielle from USA
Tom,
Thanks for the quick feedback.
I was thinking about a parameter that can allow both directions (asc and desc).
For example, if having such a parameter BIDIR (like ASC/DESC). then I create the
index sal_idx on salary column wtih BIDIR. When a query does "selects min(sal)
from emp", ascending index is used. If a query does "selects max(sal) from emp",
descending indext is used for faster search.

I don't think oracle supports/has this, but want to get your confirmation.

Thanks,

Followup:
We already do this.  We read indexes backwards and forwards all of the time.

This happens naturally all of the time.

Rating: 5
ASC/DESC with single column index May 09, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
Is it correct to assume (from the discussion above) that specifying ASC or DESC
when creating an index on one single column is of no significance as Oracle will
be able to do this kind of reading forward/backward automatically, with or
without ASC/DESC specified (with equal efficiency).

Then, Does it matter (change the execution plan or otherwise) if this ASC/DESC
was specified (compared to not specifying when creating the index)?

Followup:
the discussion above points out that just reading the index backwards and
forwards is not the same as using ASC/DESC on each column -- when you mix ASC
and DESC up (when some columns are ASC and some are DESC)

so no, they are not equivalent.

May 10, 2003 Sujit -- Thanks for the question regarding "rownum question", version
You Asked
How does rownum work? suppose I issue a query where I
put the condition as "where rownum < 100". Does Oracle first
select all the row into the buffer and then return 99 rows?
and we said...
It depends.  If we can get 99 rows and then stop -- yes.  For
example, if T is a table:

select * from t where rownum < 99;

will get just the first 99 rows and stop -- it'll not expend the
engery to get all of the rows in T.

On the other hand, if it is something like:

select * from V where rownum < 99;

and V is a complex view involving an aggregate (eg: v = "select
deptno, count(*) from emp group by deptno") -- the entire result
set may be materialized into a temp result and then the first 99
rows returned.

So the answer is -- usually we just get the first N and stop but
sometimes we have to build the entire result set and then return
the first N rows of it.
Review & Followup
Rating: 5
How can we prove the above conclusively with statistics November 19, 2001
Reviewer: Nag
SQL> select deptno, avg(sal)
  2  from emp
  3  where rownum<5
  4  group by deptno  ;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (GROUP BY)
   2    1     COUNT (STOPKEY)
   3    2       TABLE ACCESS (FULL) OF 'EMP'

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

SQL> select deptno, avg(sal)
  2  from emp
  3  group by deptno  ;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (GROUP BY)
   2    1     TABLE ACCESS (FULL) OF 'EMP'

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

Can we consider bytes sent via SQL*Net to client as conclusive proof that less
rows are being read when where condition is given using rownum.

Followup:
You cannot prove conclusively from this output that less rows were actually
read, only that less rows were sent back to the client.

There is insufficient data in the table to see the effect of rownum in this
case.

Rating: 5
Conlusive proof - November 20, 2001
Reviewer: Nag
Can the increasing consistent gets, bytes sent via SQL*Net to client , below
when using rownum, be considered conlusive proof of lesser work being done when
requesting lesser number of rows via  <rownum.

SQL> select object_name from all_objects;

14610 rows selected.

Execution Plan
----------------------------------------------------------
ERROR:
ORA-01039: insufficient privileges on underlying objects of the view

SP2-0612: Error generating AUTOTRACE EXPLAIN report

Statistics
----------------------------------------------------------
          7  recursive calls
          4  db block gets
     234213  consistent gets
         16  physical reads
          0  redo size
     436887  bytes sent via SQL*Net to client
      38200  bytes received via SQL*Net from client
        975  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      14610  rows processed

SQL> select object_name from all_objects
  2  where rownum <100;

99 rows selected.

Execution Plan
----------------------------------------------------------
ERROR:
ORA-01039: insufficient privileges on underlying objects of the view

SP2-0612: Error generating AUTOTRACE EXPLAIN report

Statistics
----------------------------------------------------------
          7  recursive calls
          4  db block gets
        397  consistent gets
          5  physical reads
          0  redo size
       2778  bytes sent via SQL*Net to client
        487  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         99  rows processed

SQL> select object_name from all_objects
  2  where rownum <300 ;

299 rows selected.

Execution Plan
----------------------------------------------------------
ERROR:
ORA-01039: insufficient privileges on underlying objects of the view

SP2-0612: Error generating AUTOTRACE EXPLAIN report

Statistics
----------------------------------------------------------
          7  recursive calls
          4  db block gets
       3596  consistent gets
          4  physical reads
          0  redo size
       7601  bytes sent via SQL*Net to client
        994  bytes received via SQL*Net from client
         21  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        299  rows processed

SQL> select object_name from all_objects
  2  where rownum <600 ;

599 rows selected.

Execution Plan
----------------------------------------------------------
ERROR:
ORA-01039: insufficient privileges on underlying objects of the view

SP2-0612: Error generating AUTOTRACE EXPLAIN report

Statistics
----------------------------------------------------------
          7  recursive calls
          4  db block gets
      10953  consistent gets
          1  physical reads
          0  redo size
      13394  bytes sent via SQL*Net to client
       1774  bytes received via SQL*Net from client
         41  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        599  rows processed

Followup:
the consistent gets tell the story.  The bytes transmitted over the network tell
you nothing about the work done on the server.

Try:

select count(*) from all_objects where rownum < ....

they all have the same bytes sent (for the count) but the consistent gets will
vary based on the rows processed.

Rating: 5
ROWNUM in Top-N queries November 20, 2001
Reviewer: Andre Whittick Nasser from Rio de Janeiro, Brazil
Please correct me if I am wrong... (or confirm it if I am right):

Query a) select ename, sal
         from emp
         where rownum<=10
         order by sal desc;

Query b) select ename, sal
         from ( select ename, sal
                from emp
                order by sal desc)
         where rownum<=10;

Oracle8i introduced the possibility of using order bys in in-line views, and now
it has the capability of doing a "smart" sort.

Important, not that the the processing will flow like indicated in the query.
Oracle will identify the Top-N syntax and do this "smart" sort.

What I suppose the queries do:

Query a) will first sort all the rows in the emp table -- quicksort style, I
imagine -- and then extract the first 10 rows, that is, the top 10 salaries.

Query b) will do a scan on the table to simply get the top 10 values, like
storing 10 MAX()'s in some memory positions. The result: the same as query a)
except for the processing complexity.

We go from an O(n log n) to an O(1) algorithm, which is just fantastic if you
are considering huge, petabyte data farms and VLDB's. That might mean years as
opposed to minutes on large amounts of data.

>> Is this so deterministic ? <<

Another consideration:

>> Is it possible to use parallel query when sorting ? <<

That does not make much sense in coarse-grained computing -- in our general
case, SMP machines -- since sorting algorithms can only be really optimized in
fine-grained architectures. In SMP machine you would still be getting O[(n log
n)/m] -- m being the number of processors -- which ends up in O(n log n).

Followup:
Query a ) will find 10 records and then sort them.  Rownum is assigned before
the ORDER BY.

Query b ) will sort the entire emp table by SAL desc (unless an index exists, in
which case we'll do something different) and then take the first 10 rows.

If we have an index on sal, we can read it backwards -- avoiding the sort, get
the 10 rows and be done with it.

query a and query b return different answers -- almost certainly (a may
accidently return the same answer but it probably won't).

This is easy to see:

scott@ORA817DEV.US.ORACLE.COM> select ename, sal
  2           from emp
  3           where rownum<=10
  4           order by sal desc;

ENAME             SAL
---------- ----------
KING             5000
SCOTT            3000
JONES            2975
BLAKE            2850
CLARK            2450
ALLEN            1600
TURNER           1500
WARD             1250
MARTIN           1250
A                 800

10 rows selected.

scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM>  select ename, sal
  2           from ( select ename, sal
  3                  from emp
  4                  order by sal desc)
  5           where rownum<=10;

ENAME             SAL
---------- ----------

KING             5000
SCOTT            3000
FORD             3000
JONES            2975
BLAKE            2850
CLARK            2450
ALLEN            1600
TURNER           1500
MILLER           1300

10 rows selected.

You do not get your reduction at all.

If you have an index on SAL, the optimizer is smart enough to figure out that it
can read it to get the 10 rows it needs.

Yes, you can use PQ when sorting.  Scanners feed sorters, sorters feed a single
merger.

Rating: 4
PLAY OF THE DAY November 20, 2001
Reviewer: Andre Whittick Nasser from Rio de Janeiro, Brazil
Well Tom...

You will have to find a good interjection for this one... so will I...

This is TOTALLY confusing. I researched the on-line books and they are reticent
about this issue.

Excerpt from the SQL Reference:

----------------------------------------------------------

" For each row returned by a query, the ROWNUM pseudocolumn returns a number
indicating the order in which Oracle selects the row from a table or set of
joined rows. The first row selected has a ROWNUM of 1, the second has 2, and so
on.

You can use ROWNUM to limit the number of rows returned by a query, as in this
example:

SELECT * FROM emp WHERE ROWNUM < 10;

If an ORDER BY clause follows ROWNUM in the same query, the rows will be
reordered by the ORDER BY clause. The results can vary depending on the way the
rows are accessed. For example, if the ORDER BY clause causes Oracle to use an
index to access the data, Oracle may retrieve the rows in a different order than
without the index. Therefore, the following statement will not have the same
effect as the preceding example:

SELECT * FROM emp WHERE ROWNUM < 11 ORDER BY empno;

If you embed the ORDER BY clause in a subquery and place the ROWNUM condition in
the top-level query, you can force the ROWNUM condition to be applied after the
ordering of the rows. For example, the following query returns the 10 smallest
employee numbers. This is sometimes referred to as a "top-N query":

SELECT * FROM
   (SELECT empno FROM emp ORDER BY empno)
   WHERE ROWNUM < 11;

In the preceding example, the ROWNUM values are those of the top-level SELECT
statement, so they are generated after the rows have already been ordered by
EMPNO in the subquery. "

----------------------------------------------------------

OK, this is what you said.

BUT... Read this text from the Oracle University Book, which seems to be
undocumented in the on-line books: "Oracle8i: New Features for Administrators",
page 3-22

----------------------------------------------------------
"
(slide)

Top-N SQL

-Views and in-line views now allow ordering
-Sorts only the >>>required<<< number of rows

SELECT *
FROM ( SELECT class_id, crs_id, start_date
       FROM classes
       ORDER BY start_date DESC )
WHERE ROWNUM < 10

...

(text)

As shown in the slide, >>>when only the top 10 rows are requested, Oracle does
not sort all the rows in the table to produce the result<<<. Oracle stores a
maximum of 10 rows as data is retrieved from the table. When the 11th row is
read, it discards the row with the lowest margin. This operation continues until
all the data is read and the >>>sorting takes place only on the 10 rows at the
end of the operation<<<. Here is the execution plan for this operation:

Execution Plan
----------------------------------------------------------
0   SELECT STATEMENT Optimizer=CHOOSE
1  0   COUNT (STOPKEY)
2  1      VIEW
3  2         SORT (ORDER BY STOPKEY)
4  3            TABLE ACCESS (FULL) OF 'CLASSES'

"
----------------------------------------------------------

What now ?

---------> AskDavidAustin.oracle.com ...

Followup:
Well, we are NOT magic ;)

If there is no index on start_date, we obviously MUST sort the data -- otherwise
it would behave just like the original

  select * from t where rownum < 11 order by x;

So, it does in fact sort the entire result set and then returns the first 9.
Now, if there is an index, it can (and will) use it.  Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t as select * from all_objects;
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on
ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2   from ( select owner, object_name, object_id
  3                    from t
  4                   order by object_id desc )
  5   where rownum < 10;

OWNER                          OBJECT_NAME                     OBJECT_ID
------------------------------ ------------------------------ ----------
OPS$TKYTE                      T                                   23087
OPS$TKYTE                      MD5STRING                           23086
OPS$TKYTE                      MD5                                 23085
OPS$TKYTE                      THE_OLD_IN_OUT                      23084
OPS$TKYTE                      STRING_ARRAY                        23083
OPS$TKYTE                      ArrayDemo                           23082
OPS$TKYTE                      ArrayDemo                           23081
A                              T                                   23078
OPS$TKYTE                      PRINT_TABLE                         23048

9 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   COUNT (STOPKEY)
   2    1     VIEW
   3    2       SORT (ORDER BY STOPKEY)
   4    3         TABLE ACCESS (FULL) OF 'T'

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

note the blocks processed -- 238.  Thats the entire table, as we'll see below.
 We read and sorted everything

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx on t(object_id);
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2   from ( select owner, object_name, object_id
  3                    from t
  4                   order by object_id desc )
  5   where rownum < 10;

OWNER                          OBJECT_NAME                     OBJECT_ID
------------------------------ ------------------------------ ----------
OPS$TKYTE                      T                                   23087
OPS$TKYTE                      MD5STRING                           23086
OPS$TKYTE                      MD5                                 23085
OPS$TKYTE                      THE_OLD_IN_OUT                      23084
OPS$TKYTE                      STRING_ARRAY                        23083
OPS$TKYTE                      ArrayDemo                           23082
OPS$TKYTE                      ArrayDemo                           23081
A                              T                                   23078
OPS$TKYTE                      PRINT_TABLE                         23048

9 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   COUNT (STOPKEY)
   2    1     VIEW
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'T'
   4    3         INDEX (FULL SCAN DESCENDING) OF 'T_IDX' (NON-UNIQUE)

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

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off

Now, if we look at the size of the raw table:

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec show_space('T' )
Free Blocks.............................0
Total Blocks............................256
Total Bytes.............................2097152
Unused Blocks...........................21
Unused Bytes............................172032
Last Used Ext FileId....................7
Last Used Ext BlockId...................246537
Last Used Block.........................43

PL/SQL procedure successfully completed.

We can see the 238 blocks = 256     - 21     - 1           + 4
                              Total     Not    Overhead      full scan
                              Blocks    Used   for Table     read seg. hdr
                                                      four times

it read the entire table, sorted it then gave us the answer.

Rating: 3
Rownumis only Record Number Saved November 21, 2001
Reviewer: Mohammed Al-moayed from Yemen
Hi, As I noticed That rownum is the rownumber and the sequence of inserting the
Record to the Table.
If you use
 Select rownum,empno,ename
 from   emp
 order by empno ;

The rownum for each Record retrived will not be in sequence
1,2,3,4,....

you can insert into emp table any Record ,and select max(rownum ) from that
table .

and you will see ....

Followup:
NO ITS NOT!!!!!! (if you get Oracle Magazine, I just published an article on
this as many many people seemed to be totally confused by this).

Rownum is assigned to rows as they are retrieved from the table.  Data in a
table is NOT STORED in order of INSERTION.

quick and dirty example that hopefully puts this to rest:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t
  2  ( x int,
  3    a char(2000) default 'x',
  4    b char(2000) default 'x',
  5    c char(2000) default 'x');

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t (x) values ( 1 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t (x) values ( 2 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t (x) values ( 3 );

1 row created.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from t where x = 2;

1 row deleted.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t (x) values ( 4 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select x, rownum from t;

         X     ROWNUM
---------- ----------
         1          1
         4          2
         3          3

FACTS:

o rows are not stored in order of insertion.
o rownum is not the sequence of inserting the rows.

Rating: 5
Where can an 'order by in an inline view be useful' November 21, 2001
Reviewer: Nag
Tom

I read above

"Oracle8i introduced the possibility of using order bys in in-line views"

Can you demonstrate the utility of using an order by in an inline view.

What and in what cases this can make a difference.

Followup:
see my comment that starts with:

Well, we are NOT magic ;)

Rating: 4
Magic ??? November 21, 2001
Reviewer: Andre Whittick Nasser from Rio de Janeiro, Brazil
Sorry Tom, with all due respect...

Questions:

1) What do you mean by 'we are not magic' ? The algorithm described in the
course book storing the MAX's makes A LOT of sense if there is not an index in
place.

2) So Oracle charges nearly US$ 1000,00 for a course and the all excited
instructor explains a new internal feature and I in turn get across how
wonderful it is, and it does not simply work in fact ?

3) Is the Oracle University material 'bugged' ? So, there nothing SO great about
the Top-N query feature, only the possibility of putting an order by in the
in-line view ? The OU instructor explained that Oracle would identify the syntax
and arrange an special execution plan, blah, blah, blah... In sum, the "smart
sort" is a legend ?

Buddy, I regret to inform you that at this time there is an army of instructors
out there saying this... thing.

Followup:
Look at the paragraph -- I did not say he was wrong.  Lets digest this:

......
As shown in the slide, >>>when only the top 10 rows are requested, Oracle does
not sort all the rows in the table to produce the result<<<. Oracle stores a
maximum of 10 rows as data is retrieved from the table. When the 11th row is
read, it discards the row with the lowest margin. This operation continues until

all the data is read and the >>>sorting takes place only on the 10 rows at the
end of the operation<<<. Here is the execution plan for this operation:
....

Perhaps we are mincing terms here but.... we read the entire table and in fact
sorted it (keeping just the 10 you were interested in).  Lets be more clear
about this feature then.  We'll look at it in a little more detail. (i myself do
not see any conflicts between what you cut and pasted from OU and what we see
here).

We'll measure what happens when we use the "new way" with rownum versus the DIY
(do it yourself way) -- the results are huge.  My point was:

o we read the entire table, no getting around that.  we in fact sort it as well
-- just smarter then we would if we did not know you wanted the top n.

o if there is an index, we can do this really well.

Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table run_stats ( runid varchar2(15),
name varchar2(80), value int );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace view stats
  2  as select 'STAT...' || a.name name, b.value
  3        from v$statname a, v$mystat b
  4       where a.statistic# = b.statistic#
  5      union all
  6      select 'LATCH.' || name,  gets
  7        from v$latch;
View created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop table t;
Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( object_name char(2000),
object_id number, owner varchar2(30) );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t
  2  select object_name, object_id, owner
  3    from all_objects
  4   where rownum < 101;
100 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set sort_area_size = 65536;
Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_start number;
  3  begin
  4      insert into run_stats select 'before', stats.* from stats;
  5
  6      l_start := dbms_utility.get_time;
  7      for i in 1 .. 100
  8      loop
  9          for x in ( select *
 10                        from ( select * from t order by object_name )
 11                       where rownum < 10 )
 12          loop
 13              null;
 14          end loop;
 15      end loop;
 16      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 17
 18      insert into run_stats select 'after 1', stats.* from stats;
 19
 20      l_start := dbms_utility.get_time;
 21      for i in 1 .. 100
 22      loop
 23          for x in ( select a.*, rownum r
 24                        from ( select * from t order by object_name ) a )
 25          loop
 26              exit when ( x.r >= 10 );
 27          end loop;
 28      end loop;
 29      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 30
 31      insert into run_stats select 'after 2', stats.* from stats;
 32  end;
 33  /
22 hsecs
2306 hsecs

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value-a.value run1,
c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

NAME                                           RUN1       RUN2       DIFF
---------------------------------------- ---------- ---------- ----------
LATCH.done queue latch                            2          1         -1
LATCH.ksfv messages                               4          3         -1
STAT...calls to get snapshot scn: kcmgss        506        505         -1
STAT...consistent gets                         3812       3811         -1
LATCH.multiblock read objects                     2          0         -2
STAT...parse time cpu                             3          1         -2
STAT...parse time elapsed                         1          3          2
LATCH.library cache                             544        547          3
STAT...redo entries                              16         13         -3
STAT...db block changes                          27         22         -5
LATCH.active checkpoint queue latch               0          8          8
LATCH.session timer                               0          8          8
LATCH.shared pool                               159        147        -12
LATCH.redo allocation                            16         30         14
LATCH.redo writing                                3         29         26
STAT...prefetched blocks                         33          0        -33
LATCH.checkpoint queue latch                      2         36         34
LATCH.messages                                    3         44         41
STAT...redo size                              21484      21428        -56
STAT...free buffer requested                     38        103         65
STAT...enqueue requests                           5        103         98
STAT...enqueue releases                           4        103         99
LATCH.file number translation table               1        101        100
STAT...recursive calls                         1031       1131        100
STAT...sorts (memory)                           103          3       -100
STAT...sorts (disk)                               1        101        100
STAT...recursive cpu usage                       27        149        122
LATCH.cache buffers lru chain                    38        208        170
STAT...session logical reads                   5038       5230        192
STAT...db block gets                           1226       1419        193
LATCH.enqueue hash chains                         6        206        200
LATCH.loader state object freelist                2        202        200
LATCH.enqueues                                   13        413        400
LATCH.sort extent pool                            6        606        600
STAT...physical reads                            43        809        766
STAT...physical reads direct                      9        809        800
LATCH.row cache objects                          85        985        900
STAT...physical writes                            9       2509       2500
STAT...physical writes direct                     9       2509       2500
STAT...physical writes non checkpoint             9       2509       2500
LATCH.cache buffers chains                    10197      17102       6905

41 rows selected.

As you can see -- there is an ENORMOUS difference (and that guy was worth the
$1,000 bucks).  It does work -- never meant to say it didn't -- only that its
not magic, it still has to read the entire table, finding the top "N".  Perhaps
they should use some framework like I do above to help quantify this, the above
shows very clearly the effect.... However, compared to what you can do with a
well placed index on a top-n:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( object_name char(2000),
object_id number, owner varchar2(30) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t
  2  select object_name, object_id, owner
  3    from all_objects
  4   where rownum < 101;

100 rows created.

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

Commit complete.

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

Table dropped.

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

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t2_idx on t2(object_name);

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t2 modify object_name NOT NULL;

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set sort_area_size = 65536;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_start number;
  3  begin
  4      insert into run_stats select 'before', stats.* from stats;
  5
  6      l_start := dbms_utility.get_time;
  7      for i in 1 .. 100
  8      loop
  9          for x in ( select *
 10                        from ( select * from t order by object_name )
 11                       where rownum < 10 )
 12          loop
 13              null;
 14          end loop;
 15      end loop;
 16      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 17
 18      insert into run_stats select 'after 1', stats.* from stats;
 19
 20      l_start := dbms_utility.get_time;
 21      for i in 1 .. 100
 22      loop
 23          for x in ( select *
 24                        from ( select * from t2 order by object_name )
 25                       where rownum < 10 )
 26          loop
 27              null;
 28          end loop;
 29      end loop;
 30      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 31
 32      insert into run_stats select 'after 2', stats.* from stats;
 33  end;
 34  /
21 hsecs
14 hsecs

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value-a.value run1,
c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

NAME                                           RUN1       RUN2       DIFF
---------------------------------------- ---------- ---------- ----------
LATCH.active checkpoint queue latch               1          0         -1
LATCH.enqueues                                   13         14          1
LATCH.session idle bit                            1          2          1
STAT...free buffer requested                      5          6          1
STAT...total file opens                           1          0         -1
STAT...parse time elapsed                         1          2          1
STAT...enqueue releases                           4          3         -1
LATCH.direct msg latch                            2          0         -2
LATCH.library cache load lock                     0          2          2
STAT...parse time cpu                             1          3          2
STAT...enqueue requests                           5          3         -2
LATCH.session queue latch                         2          0         -2
LATCH.session allocation                          2          4          2
LATCH.redo writing                                4          2         -2
LATCH.done queue latch                            2          0         -2
LATCH.checkpoint queue latch                      5          8          3
STAT...redo entries                              16         13         -3
STAT...physical reads                             9         12          3
STAT...parse count (total)                        6          9          3
STAT...opened cursors cumulative                  6          9          3
STAT...execute count                            105        108          3
STAT...cluster key scans                          0          3          3
STAT...cluster key scan block gets                0          3          3
LATCH.messages                                    5          2         -3
LATCH.ksfv messages                               4          0         -4
LATCH.cache buffers lru chain                     5         11          6
STAT...db block changes                          28         22         -6
LATCH.row cache objects                          85         91          6
LATCH.redo allocation                            16         23          7
LATCH.shared pool                               165        155        -10
STAT...recursive cpu usage                       28         18        -10
STAT...recursive calls                         1031       1106         75
LATCH.library cache                             519        601         82
STAT...sorts (memory)                           103          7        -96
STAT...table scans (short tables)               100          0       -100
STAT...redo size                              21512      21404       -108
STAT...calls to get snapshot scn: kcmgss        506        108       -398
LATCH.undo global data                          401          0       -401
STAT...table fetch by rowid                       8        909        901
STAT...buffer is not pinned count              3416       2421       -995
STAT...db block gets                           1228         19      -1209
STAT...no work - consistent read gets          3408       1712      -1696
STAT...consistent gets                         3812       1922      -1890
STAT...session logical reads                   5040       1941      -3099
STAT...table scan blocks gotten                3400          0      -3400
STAT...session uga memory                         0       4128       4128
LATCH.cache buffers chains                    10168       3921      -6247
STAT...sorts (rows)                           12317       2321      -9996
STAT...table scan rows gotten                 10000          0     -10000
STAT...session uga memory max                 24808          0     -24808

50 rows selected.

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

Rating: 5
that is good November 21, 2001
Reviewer: mano from usa
tom thanks for that answer.
can you guide whether i can achieve the following in any better way or any
oracle 8i features to help the same.

I HAVE WRITTEN A PROC THAT ACCEPTS TWO PARAMS I_PAGE_NUM AND
I_ROWS_PER_PAGE.DEPENDING ON THIS I SHOULD RETURN ROWS USING A REF CURSOR.

CREATE OR REPlace procedure
myproc(i_page_num number,i_rows_per_page number,o_ref_cur t_ref_cur)
BEGIN

  OPEN O_REF_CUR for select * from (select rownum rno,a.* from emp a) where rno
between
  ((i_page_num - 1) * i_rows_per_page) + 1  and  ((i_page_num) *
i_rows_per_page);

end;

is there a better way using any oracle 8i new features or using existing
features.

rgds

Followup:
I generally code this as:

select *
  from ( select a.*, rownum r
           from ( YOUR_QUERY_HERE ) a
          where rownum <= :MAX_ROW )
 where r >= :MIN_ROW
/

For the reason that sometimes (NOT EVERYTIME) the optimizer can stop the inner
query after MAX_ROW rows and then just through away the first MIN_ROW rows.
Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( object_name char(2000),
object_id number, owner varchar2(30) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t
  2  select object_name, object_id, owner
  3    from all_objects
  4   where rownum < 101;

100 rows created.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_start number;
  3          l_min_row number default 50;
  4          l_max_row number default 60;
  5  begin
  6      insert into run_stats select 'before', stats.* from stats;
  7
  8      l_start := dbms_utility.get_time;
  9      for i in 1 .. 1000
 10      loop
 11          for x in ( select *
 12                        from ( select rownum r, t.* from t )
 13                       where r between l_min_row and l_max_row )
 14          loop
 15              null;
 16          end loop;
 17      end loop;
 18      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 19
 20      insert into run_stats select 'after 1', stats.* from stats;
 21
 22      l_start := dbms_utility.get_time;
 23      for i in 1 .. 1000
 24      loop
 25          for x in ( select *
 26                        from ( select rownum r, a.*
 27                                 from ( select * from t ) a
 28                                where rownum <= l_max_row )
 29                       where r >= l_min_row )
 30          loop
 31              null;
 32          end loop;
 33      end loop;
 34      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 35
 36      insert into run_stats select 'after 2', stats.* from stats;
 37  end;
 38  /
198 hsecs
170 hsecs

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value-a.value run1,
c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

NAME                                           RUN1       RUN2       DIFF
---------------------------------------- ---------- ---------- ----------
LATCH.active checkpoint queue latch               1          0         -1
LATCH.session timer                               0          1          1
STAT...enqueue releases                           4          3         -1
STAT...parse time elapsed                         1          2          1
STAT...calls to get snapshot scn: kcmgss       5006       5005         -1
LATCH.messages                                    4          2         -2
STAT...enqueue requests                           5          3         -2
LATCH.multiblock read objects                     2          0         -2
LATCH.library cache                            2288       2291          3
STAT...redo entries                              16         13         -3
LATCH.redo writing                                3          0         -3
STAT...db block changes                          28         22         -6
STAT...db block gets                          12026      12017         -9
LATCH.redo allocation                            13         23         10
LATCH.shared pool                               175        159        -16
STAT...recursive cpu usage                      177        150        -27
LATCH.cache buffers lru chain                    36          6        -30
STAT...prefetched blocks                         33          0        -33
STAT...physical reads                            34          0        -34
STAT...free buffer requested                     38          2        -36
STAT...redo size                              21532      21420       -112
STAT...buffer is not pinned count             41016      27016     -14000
STAT...consistent gets                        45011      31011     -14000
STAT...table scan blocks gotten               41000      27000     -14000
STAT...no work - consistent read gets         41008      27008     -14000
STAT...session logical reads                  57037      43028     -14009
LATCH.cache buffers chains                   114127      86132     -27995
STAT...table scan rows gotten                100000      60000     -40000

28 rows selected.

Quite a difference in the buffers processed....

Rating: 5
it is good November 21, 2001
Reviewer: mano from usa
sorry tom,

i understood the example but not the theory behind that.
can you please explain me more about your

"For the reason that sometimes (NOT EVERYTIME) the optimizer can stop the inner
query after MAX_ROW rows and then just through away the first MIN_ROW rows"

Rating: 2
I don't get it yet... November 21, 2001
Reviewer: Andre Whittick Nasser from Rio de Janeiro, Brazil
So, from what I gathered, what you first said:

"Query b ) will >>>sort<<< the entire emp table by SAL desc (unless an index
exists, in which case we'll do something different) and then take the first 10
rows."

That is:

1) Sorting (supposing there is no index)

2) Getting the top 10 values

>>>contradicts<<< what the OU book says:

"As shown in the slide, >>>when only the top 10 rows are requested, Oracle does
not sort all the rows in the table to produce the result<<<. Oracle stores a
maximum of 10 rows as data is retrieved from the table. When the 11th row is
read, it discards the row with the lowest margin. This operation continues until
all the data is read and the >>>sorting takes place only on the 10 rows at the
end of the operation<<<."

That is:

1) "Collecting" the top 10 values NOT in a sorted state hrough a FTS, grabbing
the greatest values along the way.

2) Sorting the resulting, >>>unordered<<< 10 values.

I understand that you might be mistakenly considering the last 1) a "sort". This
is where we are diverging. You WILL have to agree this is no kind of sorting, at
best some kind of MAXing.

I read your code carefully. Sorry I am sometimes a bit too slow, I saw the
performance difference between both queries. I am impressed.

But I did not see anyhthing accounting for the discrepancy of concepts. If there
was something else I missed, please show me more clearly.

Excuse me, but I am not convinced yet the 1000 bucks were worth it in the case
the teacher told half the story: yes, faster, but working differently.

Followup:
Ok, i retract everything.  You win.

Let me rephrase.

It get the 10 rows from EMP such that they have the 10 biggest salaries, it'll
do this in a super good way.

It sorts no data whatsoever.  It compares values to find out which is bigger
then then other.  It kept the 10 biggest ones.  It did a super efficient "kind
of sort that is not a sort".  I was too loose with my terminology.

Hopefully the exhaustive example clears it all up and removes all confusion.
The stats don't lie in this case, they show the difference.

Rating: 5
it is good November 21, 2001
Reviewer: mano from usa
sorry tom,

i understood the example but not the theory behind that.
can you please explain me more about your

"For the reason that sometimes (NOT EVERYTIME) the optimizer can stop the inner
query after MAX_ROW rows and then just through away the first MIN_ROW rows"

Followup:
The optimizer recognizes "where rownum < N" and knows -- Hey, if at all
possible, I should STOP processing.

The optimizer sees "where rno between min and max" and doesn't have that
ability.

Hence, by breaking it into two inline views, we can manytimes quickly build the
result set for "where rownum < max_row" (faster then building the ENTIRE result
set) and then applying the "where rno > min_row".

It is just a fact that when there is a rownum in the inline view/subquery,
Oracle will materialize that result set.  So, when you went:

select *
  from ( select emp.*, rownum rno from emp )
 where rno between A and B;

The query: "select emp.*, rownum rno from emp" was performed in FULL and then
the predicate was applied.

In my case, the query:

  select a.*, rownum r
    from ( select * from t ) a
   where rownum <= MAX_ROWS

was performed -- faster then yours since it only had to do MAX_ROWS rows -- and
then the final predicate "where r >= min_rows" was applied.

Rating: 4
SUN CHEN July 23, 2002
Reviewer: SUN CHEN from HK
HI Tom,
   Why the following procedure cann't work?

CREATE OR REPLACE procedure mi_clm_dscv is
begin

    execute immediate 'delete from te_mi_clm_mvnt';

    insert into te_mi_clm_mvnt
             (prcp_abbr,
              clm_no,
              ac_yr_mth,
              chnnl_nm,
              isrd_nm,
              grs_rsrv_bf,
              net_rsrv_bf,
              grs_paid,
              net_paid,
              grs_rsrv_cf,
              net_rsrv_cf,
              grs_incrrd,
              net_incrrd,
              grs_paid_ytd,
              net_paid_ytd,
              clm_ind,
              mgt_cls_desc,
              rn_l,
              rn_s)
      select  e.prcp_abbr,
              m.clm_no,
              m.ac_yr_mth,
              s.chnnl_nm,
              p.isrd_nm,
              m.grs_rsrv_bf,
              m.net_rsrv_bf,
              m.grs_paid,
              m.net_paid,
              m.grs_rsrv_cf,
              m.net_rsrv_cf,
              m.grs_incrrd,
              m.net_incrrd,
              m.grs_paid_ytd,
              m.net_paid_ytd,
              m.clm_ind,
              b.mgt_cls_desc,
              row_number() over (partition by m.ac_yr_mth, e.prcp_abbr,
m.clm_ind order by m.net_paid_ytd desc) rn_l,
              row_number() over (partition by m.ac_yr_mth, e.prcp_abbr,
m.clm_ind order by m.net_paid_ytd asc) rn_s
         from mi_clm_mvnt m,
              mi_clm_m c,
              mi_clm_pcy p,
              mi_sbu_main_cd_m s,
              mi_prcp_m e,
              mi_mgt_cls_m b,
              mi_mgt_cls_dtl a
        where m.clm_no = c.clm_no
          and c.pcy_no = p.pcy_no
          and c.endt_srl = p.endt_srl
          and c.endt_yr = p.endt_yr
          and c.itm_no = p.itm_no
          and m.sbu_main_cd = s.sbu_main_cd
          and m.prcp_cd = e.prcp_cd
          and c.bsc_typ_cd = a.bsc_typ_cd
          and m.pcy_sub_typ_cd = a.pcy_sub_typ_cd
          and a.mgt_cls_cd = b.mgt_cls_cd;

end;

Followup:
you make it *really* hard when you don't include any information -- like for
example -- versions of software, actual error message/code.....

Ok, so I go into "compiler" mode and say "hey, if I was an 8i database, running
8i plsql -- that analytic function row_number() would bug me and I would fail
the compliation"

See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3027089372477
for what is happening (if I guessed right)

btw: why do the delete dynamically?  use static sql whenever possible.

Rating: 4
SUN CHEN July 24, 2002
Reviewer: SUN CHEN from HK
Hi Tom,
   Thank you for your reponse immediately.
why the following store procedure get errors:
CREATE OR REPLACE procedure mi_clm_dscv is
begin

    execute immediate 'delete from te_mi_clm_mvnt';

    insert into te_mi_clm_mvnt
         (prcp_abbr,
          clm_no,
          ac_yr_mth,
          chnnl_nm,
          isrd_nm,
          grs_rsrv_bf,
          net_rsrv_bf,
          grs_paid,
          net_paid,
          grs_rsrv_cf,
          net_rsrv_cf,
          grs_incrrd,
          net_incrrd,
          grs_paid_ytd,
          net_paid_ytd,
          clm_ind,
          mgt_cls_desc,
          rn_l,
          rn_s)
  select  e.prcp_abbr,
          m.clm_no,
          m.ac_yr_mth,
          s.chnnl_nm,
          p.isrd_nm,
          m.grs_rsrv_bf,
          m.net_rsrv_bf,
          m.grs_paid,
          m.net_paid,
          m.grs_rsrv_cf,
          m.net_rsrv_cf,
          m.grs_incrrd,
          m.net_incrrd,
          m.grs_paid_ytd,
          m.net_paid_ytd,
          m.clm_ind,
          b.mgt_cls_desc,
          row_number() over (partition by m.ac_yr_mth, e.prcp_abbr, m.clm_ind
order by m.net_paid_ytd desc) rn_l,
          row_number() over (partition by m.ac_yr_mth, e.prcp_abbr, m.clm_ind
order by m.net_paid_ytd asc) rn_s
     from mi_clm_mvnt m,
          mi_clm_m c,
          mi_clm_pcy p,
          mi_sbu_main_cd_m s,
          mi_prcp_m e,
          mi_mgt_cls_m b,
          mi_mgt_cls_dtl a
    where m.clm_no = c.clm_no
      and c.pcy_no = p.pcy_no
      and c.endt_srl = p.endt_srl
      and c.endt_yr = p.endt_yr
      and c.itm_no = p.itm_no
      and m.sbu_main_cd = s.sbu_main_cd
      and m.prcp_cd = e.prcp_cd
      and c.bsc_typ_cd = a.bsc_typ_cd
      and m.pcy_sub_typ_cd = a.pcy_sub_typ_cd
      and a.mgt_cls_cd = b.mgt_cls_cd

    update te_mi_clm_mvnt
       set rank_ind = 'Largest'
     where rn_l <= 50;

    update te_mi_clm_mvnt
       set rank_ind = 'Smallest'
     where rn_s <= 50;

    update te_mi_clm_mvnt
       set rank_ind = 'Others'
     where rank_ind is null;
end;
/

The errors:
PLS-00103: Encountered the symbol "(" when expecting one of the following:

   , from

I run it in oracle 817 and the sql can work if I remark the analyst function.Is
that analyst function cann't be used in stored procedure? or others errors have
occured? thanks....

Rating: 4
order by rownum,x August 13, 2002
Reviewer: PN
Hi Tom
thanks for previous helps.
How to make use of Rownum in query to display Rownum,empno
in ASC order (without emp index ,with emp index)
Exapmle
SELECT ROWNUM,EMPNO FROM SCOTT.EMP
ORDER BY ROWNUM,EMPNO

output=
ROWNUM    EMPNO
1    7369
2    7499
3    7521
4    7566
5    7654
6    7698
7    7500
8    5
9    2
10    1

We want as
output=
ROWNUM    EMPNO
1    1
2    2
3    5
4    7369
5    7499
6    7500
7    7521
8    7566
9    7654
10    7698

Thanks
PN

Followup:
select rownum, empno
  from ( select empno
           from emp
          order by empno );

sort AND then assign rownum.

Rating: 3
Does rownum behave differently using RBO? October 31, 2002
Reviewer: Kashif
Hi Tom,

you mention that if rownum <= n is used, the server will not expend the energy
to get all rows and then return the 'n' rows as limited by rownum, but rather
just get the 'n' rows requested. Does this work any differently when RBO is
being used? TIA.

Kashif

P.S. I am unable to download from the link you have provided on your home page
to your presentation on searchdatabase.com. (the 'download the slides from here
(no sound)' link).

Followup:
It will work differently with different query plans (eg: you can get a different
N rows back with different plans -- add an index and you might get a different N
rows then without it).

So -- since the RBO might pick a different plan -- yes, the RBO and CBO might
well return different answers depending on HOW rownum was used.

As for the PS -- its been downloaded 548 times and I haven't heard from anyone
else having issues -- anyone else having problems?

Rating: 3
rownum vs. distinct March 12, 2003
Reviewer: VJ
Tom,

I was told that using rownum would be faster than using distinct in a query. Can
you provide an example if this statement is correct or direct me?

Followup:
rownum is not a replacement for distinct???

I don't know what you mean.

Rating: 2
rownum vs. distinct March 13, 2003
Reviewer: VJ
what i mean is that if a query is returning multiple rows  with same values
(duplicate)then use rownum =1 rather than use distinct in the query. But i got a
doubt is that if i use rownum=1 then it is going to return only one row and not
distinct values. So this question about rownum vs. distinct.
If it still doesnt drive a point...let just leave it!!!

Rating: 3
Error on your page today 03-14-2003 March 14, 2003
Reviewer: vj
Hi Tom,

I saw this error message on your site today..

report error:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small

I was gonna ask you this very same question today as i was getting this in one
of my packages yesterday and saw this on your site. Can you tell me why this
error?

Rating: 5
rownum May 09, 2003
Reviewer: mo
Tom:

I have a table of 900 stock numbers where stock number is PK. I am trying to get
the order of a given stock number in the table meaning the position. What I did
is :

 select rownum from (select rownum,stock_number from stock_item order by
stock_number)
 where stock_number='MS208'

and it is still giving me rownum=1.  How can I get the rownum for that stock
number which is 800.

Thank you

Followup:
there is no such thing as the "positition in a table".

rownum is a fleeting number assigned to a row in a result set.  It exists for
the brief moment in time that result set does.  There is no such thing as a row
having a positition or place in a table.

You do not want what you ask for -- trust me.

Rating: 5
rownum May 09, 2003
Reviewer: mo
Tom:

1. I am sure there has to be a way to get a corresponding number for a value in
a table. You say rownum would not work.  is there anything else like a random
number that can be assigned in incrementing order?  I am trying to pass this
number (x)  to a javascript array value
'document.form.field[0].option['||x||'].selected =   true

Basically I have a html for with a dropdown list for all stock numbers. The user
can do a lookup where a child window is open and he enters a keyword and oracle
does a search and pulls all matching records.  Then he clicks on select for one
stock number and I am assigning that number to the value in the dropdown on the
parent page. I can not find a way in javascript to access the different values
in the dropdown to find the location of the matching value so i am trying to get
it in oracle.

Followup:
if you say "i am sure", you would lose that bet.

rows having "order" is nonsense in a relational database.

If you assigned a sequence number to this (application data) -- you could use
that, but to ask the database to assign a "number to a row" isn't going to
happen.

Rating: 5
Position in table May 12, 2003
Reviewer: Jim Tran from NJ USA
I believe that reviewer "Mo" wants to rewrite the query:

select rownum from
  (select rownum,stock_number from stock_item
   order by stock_number)
where stock_number='MS208'

as:

select RN from
   (select rownum RN,stock_number from stock_item
    order by stock_number)
where stock_number='MS208'

I think the confusion is that "rownum" in the outer query is NOT the same
"rownum" in the inner query.  The "rownum" in the outer query is an entirely new
"rownum".

Rating: 5
query May 13, 2003
Reviewer: mo
Thanks

RN worked.  I thought rownum on the outer were coming from the inner query but
it was not.

Rating: 5
August 12, 2003
Reviewer: Helena Marková from Bratislava, Slovakia

May 11, 2003 Vikas -- Thanks for the question regarding "no of return by a query,Paging", version 8.1.7
You Asked
Hi tom,
I have two question to ask. Many thanks in advance. great site and yor book
ultimate i have ever come across.
Q.1 I have a cursor for example
  cursor c1 is select * from emp;
  I want to know that how many row will this cursor will return when opened. It
is   some thing like in oracle forms when you issue a query you can trap how
many row will be  that query return. by traping message 'Query will Reterive..'
on-message trigger. How does form do it.

I have read you book about the multi versioning and rollback segment. can i make
 use multi versioning and rollback segment logic to get this. If yes the how?

 Q.2 I am developing web application using mod_plsql. I which i have introduced
paging.  by using following logic
     select * from (select rownum rx,e.* from
                    ( select * from emp ) e where rownum <=:bv_endrow) where rx
>= :bv_startrow ;

Now in first page the bv_endrow = 5 and bv_startrow = 1. So i will get first
five rows. after getting 5 rows i want to know more row exists or not if exist
then i will show  next>> button or i will not show if no further record found.

The one way i can see is after execution of the first five rows set execute the
query  for the next 5 rows and collect the values into record type
collection(table of  record type) and store it on the current form in hidden
item . if the count of table type   return > 0 then show the next button and
pass the hidden values when next button is clicked, or if count is 0 then i will
not show the the next button.  to be more clear i want to achive following

o  i want to include paging of a resultset return from a query.
o  I want to show the total no of the rows return by the query.
o  I want my next button enable when if more rows are there.

It is somthing like your search which shows the total no of the record found
then shows
10 rows than if more records are there then next button img.

Can we do it in one sql statement of a cursor. Kindly let me know the best
possible way to achieve it.

Regards,
Vikas Sharma
India
and we said...
q1) forms does it the only way you can (and it is a horrible way).  It does
"select count(*) from ( YOUR_QUERY )".  either that, or it fetches all possible
rows (and then it knows).  Not only is is horrible (slow, wasteful, etc) -- it
can be wrong (since the count(*) and YOUR_QUERY are executed at two different
points in time, the results can differ.

We do not know the number of rows your query will fetch until it fetches the
very very last row.  Consider "select * from one_billion_row_table", we do not
make you wait till we fetch the last row to show you the first one!

q2) I always assume that if I fetched PAGESIZE rows -- then yes, there is a next
page so show the next button.  At worst, you'll show a blank page once in a
while.

alternatively, use "rownum <= :bv_endrow+1".  Don't print the last row, just use
it as a flag to see whether you should put the button or not.

Paging -- you already know how to do that (you've got the query right there)
Total number of rows -- not knowable unless you really want to make them wait
Either assume there is a next page, if current page is full or overfetch by one.

The "total" records on my site is a guesstimate supplied by interMedia text --
it is not "general" purpose and only works with a text index (and it is not
100%, its an good approximation).
Review & Followup
Rating: 4
June 30, 2002
Reviewer: ashraf from KUWAIT
hi tom

i get your book it is very intrested aren't you plan to
to make a small one for new future for 9i for tuning and cluster

Rating: 4
thanks July 02, 2002
Reviewer: Vikas Sharma from Delhi india
Hi

It is a surprized to know the way form does that.

Thanks lot,

Rating: 5
September 04, 2002
Reviewer: Juan Pena from Mexico
Hi there. I've been trying to do this for a long while, and only when I saw it
here I could figure it out! Thanks!!!

One little doubt, though:

You did this:

select * from (
  select rownum rx,e.* from (
    select * from emp
    ) e
  where rownum <=:bv_endrow
) where rx >= :bv_startrow

But this will give the same result:

select * from (
  select rownum rx, * from emp
)
where rx between :bv_startrow and :bv_endrow

It seems you didn't want to use the between operand. Am I right? Why didn't you
use it? Performance?

Thanks a lot again!!!!

Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064
for that answer to that (yes, performance is the reason)

Rating: 5
askTom.oracle.com RULES!!!!!!!!! September 05, 2002
Reviewer: Juan Pena from Mexico
Keep it up Tom! Your site rules!!!
I got the answers I was looking for.

Rating: 5
Order By October 07, 2002
Reviewer: Juan Guascarancas pena from Guadalajaras, Mexico
Hey Tom, I read somewhere else on your site that a subquery cannot contain an
ORDER BY clause before Oracle 8.1.
Is there a workaroung on Oracle 8.0 to get a "paged" resultset ordered?

Thanks.

Followup:
Nope, you'll have to fetch and throw away the first N rows to get rows N through
M.

Rating: 5
How will i know how many search results have come October 08, 2002
Reviewer: Lakshmana Swamy from Hyderabad, India
Hi Tom,
  this is the first time i came to this forum. searching on Paging problem i
came to here and i found very very valuable information.
  My question is How would we know about how many search results we got?
  Am i clear?

Followup:
It is clear -- but I thought my answer was clear:

We do not know the number of rows your query will fetch until it fetches the
very very last row.  Consider "select * from one_billion_row_table", we do not
make you wait till we fetch the last row to show you the first one!

In short, until you fetch the very last row, no one knows how many rows it'll
return.  See also
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3489618933902
and look for "google"

Rating: 5
October 09, 2002
Reviewer: Lakshmana Swamy from Hyderabad, India
Hi Tom,
  Thank you.
  I have one question. I don't know whether placing this question here is right
or wrong.
  we are implementing paging concept. In our implementation we should know the
total number of records. I need small help on this.
  In our implementation we are using ResultSet object to retrieve all the
records at once. using ResultSet.last() and ResultSet.getrow() we are getting
the total number of records. My collegus and superiors are saying that ResultSet
will not occupy much memory. so there is no problem with memory, if there are so
many records and so many users.
  Is it true?
 what i am arguing is we should not get the whole resultset at once and we
should get that page only i.e. the number of records we are displaying.
  Can you tell me which is the correct way?

and one more thing is i thought in another way. i don't know whether it is
possible to implement or not. My thinking is as folows.
  If we maintain the search result in CachedRowset and this CachedRowset is
stored in implicit application object so that every user can access that object.
If anyone updating or modifying the database has to reexecute the query and set
the CachedRowset to the latest value. Did you got my point?
Is it possible to implement and how good this approach is?

Thank you
Laxman

Followup:
Measure the memory used -- should be easy to do from the operating system

Ask them how much ram 1000, 10000 rows would take -- the result set is cached
somewhere.

My way, if you believe "my way" to be the correct way, would be the google way
or the way I do it here.  the number of hits is an APPROXIMATION (you could get
that by explaining the query or in 9i from the v$ tables).  If the approximation
is "wrong", so be it, we adjust.

Rating: 4
Approxmiation ?? October 09, 2002
Reviewer: Vikas Sharma from Delhi - India
Hi tom,
i shall be thaknful if you can tell how to get approxmation by query explain
plan and how do we get it from v$tables in 9i. Please explain both for 9i and
8i.

Regards,

Vikas Sharma

Followup:
You would use the documented EXPLAIN plan command in 8i and then a query similar
to the one autotrace uses.  It could be like this:

ops$tkyte@ORA920.LOCALHOST> create or replace function get_est_rows( p_query in
varchar2 ) return number
  2  as
  3     pragma AUTONOMOUS_TRANSACTION;
  4     l_card  number;
  5     l_stmtid varchar2(25) default 'ESTIMATED';
  6  begin
  7     execute immediate
  8     'EXPLAIN PLAN SET STATEMENT_ID= ''' || l_stmtid || ''' FOR  ' ||
p_query;
  9
 10     select cardinality into l_card
 11       from plan_table
 12          where statement_id = l_stmtid
 13            and id = 0;
 14
 15     delete from plan_table where statement_id = l_stmtid;
 16     commit;
 17
 18     return l_card;
 19  end;
 20  /

Function created.

So, now, just like autotrace:ops$tkyte@ORA920.LOCALHOST> set autotrace traceonly
explain
ops$tkyte@ORA920.LOCALHOST> select /*+ first_rows */ * from dual;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=11 Card=8168
Bytes=16336)
   1    0   TABLE ACCESS (FULL) OF 'DUAL' (Cost=11 Card=8168 Bytes=16336)

ops$tkyte@ORA920.LOCALHOST> exec dbms_output.put_line( get_est_rows('select /*+
first_rows */ * from dual') );
8168

PL/SQL procedure successfully completed.

you can get the estimated cardinality.

Note: this info is available ONLY with the CBO

note2: see the estimated cardinality from DUAL???  Make sure you have good stats
or this will basically be a random number.

Rating: 4
What about in 9i v$tables? October 10, 2002
Reviewer: Vikas Sahrma from Delhi India
Hi Tom,

Thanks a lot for approxmation review follow up.

Kindly explain how do we do it via v$table. are the same statistics of
plan_table are available in the v$tables. I am sorry i dont have 9i to see but
soon we will have.

Regards,
Vikas Sharma

Followup:
It is basically the same structure as the plan table itself.

You don't need to have 9i installed to see this stuff.  all of the docs are on
OTN.

Here:
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96536/ch3194.htm#1101574
is v$sql_plan specifically.

Rating: 5
Acuuracy of query estimation October 11, 2002
Reviewer: Tom from UK
Tom,

Assuming that you have up to date statistics on all tables used in the query and
statistics on all indexed columns [with histograms where necessary], how
accurate are estimated cardinalities likely to be?

Will this accuracy decrease as the query gets more complex?

Thanks

Followup:
As good as you could guess yourself.

Say I give you a query:

select * from emp where ename like '%A%';

we won't be very good -- it'll be a guesstimate.

Now, say we give you:

select * from emp where ename = :X;

Now, we'd be very good.

Both of those queries are "simple" -- neither "complex".  The complexity matters
not -- it is the specificity that matters.  Can you look at the question and say
"this will return N rows" and be confident.  If you can, we can -- if you
cannot, we cannot.

Rating: 5
October 11, 2002
Reviewer: Lakshmana Swamy from Hyderabad,India
Hi Tom,
  Thank you for your information.
  But, i did't understand whether ResultSet in JAVA occupies memory or not. I
have searched so many forums regarding this. Some people say the ResultSet will
not occupy memory, it only contains a reference. Some people like YOU are saying
that ResultSet will be cached somewhere. So, i was in confusion.
I am believing your's is the correct way. but i have to clarify my doubt.
  I decide to do a practical. I have created a table which contains username and
password. and this table contains 120,000 records.
  I took two Servlets. In First Servlet i retrieved whole Resultset and
displayed first 20 records. i retrieved total number of records by
ResulSet.last() and ResultSet.getRow() methods. In second servlet i used two
statements. in first statement i retrieved the count and in second statement i
retrieved the particular 20 records using your technique and displayed them.
  I have executed these two in Tomcat 4.0.5 Server and My System has 256MB RAM
and i am using Oracle9i. The first Servlet took around 17seconds to execute
where as the Second Servlet executed in 2 seconds.

ON This I would like to ask a question.
 What is the main reason per such a low performance by the First Servlet. Is it
Database related or JAVA ResultSet related.
Any clarification is most valuable to me.

Thanks & Best Regards
laxman

Followup:
It is going to depend on the jdbc driver, the implementation, the version, lots
of stuff.

The first one, by going resultset.last, had to fetch over 120,000 rows (it would
have been interesting for you to measure their MEMORY CONSUMPTION).

The other one had to fetch over 20 rows + 1 count (that was probably done via a
index).

Me, I would make it even faster.

Blow off the count, just get the 20 rows.  Instantaneous!

Rating: 5
Approxmiation October 12, 2002
Reviewer: A reader
Hi Tom,

when I use BC4J it gives me correct estimate of the number of rows returned,
although it is the estimate but "MOST" of the times it is accurate,
what technique is bc4j people using to get this ?..

can you put some light on this.

Followup:
They do it just like forms.  From a BC4J developer:

...
If they want to see all of the SQL that the BC4J framework
is issuing, they can start their application with the
Java VM command line flag:

   -Djbo.debugoutput=console

and they'll see everything.

For our getEstimatedRowCount() method, we execute
the following query:

   select count(*) from (............)

where the ........... is the query for the ViewObject in question,
with all it's where clause and bind variables set as they are
currently set.
........

That is why "most" of the times it is accurate -- it is the count of the rows
your query will return AT THAT point in time (which is different from the actual
point in time at which you run the query to get the results...)

My reply to them was:

yuck, hope no one uses it or at least they understand that this is like setting
"slow=true" in their application!

Rating: 5
Approximation October 22, 2002
Reviewer: A reader
It's disappointing to hear that "Experts at Oracle" are
using such not so good techniques.

Thanks for the insight on what BC4J was doing in getEstimatedRowCount() method.

Rating: 3
how about this technique May 11, 2003
Reviewer: Asim Naveed from Pakistan
I want to use the following technique to get
the total no. of rows in a resultset before fetching
the whole reslutset. Sir Tom and others please comment
on peformance or other issue in this technique.

What ever SELECT you write just add an item COUNT(*) OVER()
at the end of the select list.
e.g
To get the total no. of rows of the resultset of

SELECT deptno, job
FROM dept, emp
WHERE dept.deptno = emp.deptno
GROUP BY deptno, job

just convert the above select into

SELECT deptno, job, COUNT(*) OVER()
FROM dept, emp
WHERE dept.deptno = emp.deptno
GROUP BY deptno, job

the last column will give the total no. of rows
returned by the query. Then in your program
just fetch the first row and read the third column.

Followup:
that works -- it gets an accurate (but to me "so what") number of rows in that
result set however, it forces the entire result set to be gathered before the
first row can be returned.

If you like that "side effect" (hey, on my search page would you like me to
start counting exactly ever hit you have before I give you the first 10?  or, do
you just want me to get the first 10 asap?  I get you the first 10 asap which
means I can run asktom on 1/10th the hardware I would need otherwise.  No point
in my counting that there are 1,325 hits when you only care about the first 10)
go for it.

Me, when I work on a web project, that is the first "feature" I go after and
kill.  All of a sudden, lots of stuff runs many times faster, with many orders
of magnitude less logical IO, only a fraction of the hardware required before
the change.

May 11, 2003 Nikunj -- Thanks for the question regarding "form/report server ", version 8.1.6
You Asked
hi tom,

i have windows nt server 4.0, with oracle 8.1.6, forms & report
developer server.

i had deplyed my forms on web db listener & its work fine.

now in formsweb.cfg file i want to know more about below
paremeter.

lookAndFeel=Generic
colorscheme=titanium

now how much different value we can asign to this both & how can
i create a new ?

i had created snapshots how can i refresh it from sql plus
prompt.

thankx,
nikunj
and we said...
lookAndFeel       Optional, determines the applications
look-and-feel. Legal
                  values: Oracle or Generic (Windows 95
look-and-feel)

colorScheme       Optional, determines the application’s color
scheme. Legal
                  values: Teal, Titanium, Red, Khaki, Blue,
Olive, or Purple.
                  Note: colorScheme is ignored if lookAndFeel is
set to Generic

You cannot create your own, they are compiled into forms itself.

SQL> execute dbms_snapshot.refresh( 'snapshot name' );

would refresh a snapshot from the command line.
Review & Followup
Rating: 4
March 13, 2001
Reviewer: A reader

Rating: 4
March 13, 2001
Reviewer: A reader from Ahmedbad / Gujarat /India

Rating: 1
Forms are not running on forms server January 17, 2002
Reviewer: Tarun Sharma from India
Hi Tom,

I've installed 9iAS 1.0.2.2.1 on a machine. I installed and configure forms
server also. The Test.fmx is running properly.
But when I created a form using forms 6i "emp.fmx", which is accessing a
database, it gives me error like "ora-12514: TNS is not able to find service
name". While I'm able to connect to the database using SQL prompt.

Can u help me.

Thanks in advance
Tarun

Followup:
Make sure the tnsnames.ora used by webforms (in the $ORACLE_HOME/network/admin
directory where FORMS is installed on the server) contains that entry.

The fact that sqlplus on your client (which reads your tnsnames.ora file on your
machine) can log in doesn't mean much here.  You need to set up the
configuration files on the app server.

Rating: 3
Form server January 19, 2002
Reviewer: Sazzadur Rahman Tusar from Bangladesh
I face problem using forms and Report server.
When I run the a form from a browser( i.e. IE), it takes much time to appear on
screen.why?
It should be fast like client/server although it is come from web server.
Probably I have to do fine tuning.
But how I will do fine tuning of Forms server
so that it will come fast on screen?
can you help me ?

Rating: 5
Formserver with OAS May 11, 2003
Reviewer: atul from India
Hi,

Can i integrate ODS(form server) with OAS???
If yes how will it work??
Ie when user type USERNAME/PASSWORD..
How flow will happen??

Thanks.
atul

Followup:
what is "ods" and OAS is not really the platform we use these days -- it would
be iAS with apache.  the forms server for Oracle Forms runs on iAS.

May 11, 2003 Nick -- Thanks for the question regarding "Order of predicate evaluation in queries", version 8.1.5
You Asked
Hi Tom,
I was browsing the answer you've supplied to another friend about the order in
which Oracle evaluates the predicates in the WHERE section of a SQL statement
(by the way the example you provide in your answer is really frightening). I was
wondering, whether proper parenthesising would do any good in suggesting an
order of predicate evaluation.
That is, will

select * from emp, dept
where (emp.dept_no = dept.dept_no) and
       ((dept.dept_no = 1))

AND

select * from emp, dept
where emp.dept_no = dept.dept_no and
       dept.dept_no = 1

make any difference at all (for the optimizer) or not? I mean regardless of the
existence of various indexes and statistics
that will favour either order of predicate evaluation.
Thank's a lot and keep on the good work.
and we said...
No, the order of evaluation of a predicate in SQL is purposely "indeterminate".

You should not, cannot rely on a specific order or evaluation.  You cannot even
rely on short-circut evaluation.  The optimizer will evaluate the predicate in
the order it feels is best -- that is its job.

SQL is a wholly non-procedural language, you are trying to attribute procedural
language to it.  You should not have any need to have a predicate evaluated in
some order.

If anyone can come up with a good reason for needing a predicate evaluated in
some specific order -- put it in the comments and I'll address them as they come
in.
Review & Followup
Rating: 5
Nick - Order of predicate evaluation May 25, 2001
Reviewer: A reader
Thanks a lot Tom. It was very clarifying.

Rating: 5
What about this one? May 09, 2003
Reviewer: Basil
I don't understand why Oracle would bother scanning here, when the bind
variable, a constant, can be tested quickly.
Is this a case?

SQL> create table t as select object_name from all_objects;

Table created.

SQL> insert into t select * from t;

24754 rows created.

SQL> insert into t select * from t;

49508 rows created.

SQL> insert into t select * from t;

99016 rows created.

SQL> insert into t select * from t;

198032 rows created.

SQL> insert into t select * from t;

396064 rows created.

SQL> commit;

Commit complete.

SQL> create index t_idx on t(object_name);

Index created.

SQL> analyze index t_idx compute statistics;

Index analyzed.

SQL> variable p_name varchar2(50);
SQL> exec :p_name := NULL;

PL/SQL procedure successfully completed.

SQL>
SQL> set autotrace traceonly explain statistics
SQL>
SQL> select object_name from t where :p_name is null or object_name like :p_name
;

792128 rows selected.

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

Followup:
you are using the brain dead RBO.

analyze the *table*, use the CBO.

Rating: 5
Doh. Bad example May 09, 2003
Reviewer: Basil.
Will get another one together

Rating: 3
Why I need predicate evaluated in a particular order May 09, 2003
Reviewer: Robert from Memphis, USA
Tom,

This is something I ran into about a year ago.

select *
  from t1
 where no_index_column = 'xyz'
   and col2 = complicated_function(col3)
/

In the above case, I would want Oracle to do the inexpensive comparison on
'no_index_column' before testing col2 vs. the expensive function result.

Thanks,

Robert.

Followup:
select *
  from t1
 where decode( no_index_column, 'xyz', complicated_function(col3), null ) =
col2;

Rating: 3
ORDERED_PREDICATES May 11, 2003
Reviewer: Adrian from UK
Tom

I take it you don't like the ORDERED_PREDICATES hint then ?

Regards
Adrian

Followup:
nope.

hints are hints, relying on them to get a specific execution path is fragile at
best.

when you can easily do this in a 100% assured fashion using DECODE or CASE, why
you would bother with a hint.....

Rating: 5
May 12, 2003
Reviewer: Robert from Memphis, USA

May 11, 2003 sunil -- Thanks for the question regarding "How to increase the datafile size or not to specify Maxsize / Record set as in Parameter", version Ver 8.0.6 on solaris 7
You Asked
Hi
Thank you, for ur  pointing out the mistake in the package Now
it compiles without even changing ref cursor  to IN OUT as u
mentioned for 8.0.6 .Any it is ok

The second problem  which I had  asked u told it was not
possible as it is   given by the com level parameter so cannot
decided what type of  querys or tables it would be that is in
the in parameter of this package  Can u suggest me a work about
for oracle 8.0.6

The script was
CREATE OR REPLACE package spKT_ClassFind is
type recset is ref cursor;
procedure Recipes
(Criteria in Class.Description%TYPE,
rec1 out recset
);
end spKT_ClassFind;
/
CREATE OR REPLACE package body spKT_ClassFind
is
procedure Recipes
(Criteria in Class.Description%TYPE,
rec1 out recset
)
is
lc_criteria Class.Description%TYPE default null;
begin
lc_criteria := ' '||Criteria ;
open rec1 for lc_criteria;
EXCEPTION When Others Then
open rec1 for select * from dual where 1>2;
dbms_output.put_line('Error - ' || sqlerrm);
Return ;
end Recipes;
egin
null;
end spKT_ClassFind;
/
The error is
PLS-00103: Encountered the symbol "LC_CRITERIA" when expecting
one of the following:
   select
PLS-00103: Encountered the symbol "end-of-file" when expecting
one of the following:
   begin function package pragma procedure form external

One more issue I am facing some  problem of increasing the size
of the tablespace

See I have this tablespace sturt for my database

TABLESPACE                     INITIAL_EXT  NEXT_EXT   MIN_EXT
MAX_EXT PCT_INCREASE
------------------------------ ----------- --------- ---------
--------- ------------
SYSTEM                               10240     10240         1
    121           50
RBS                                 131072    131072         2
    121            0
TEMP                                262144    262144         1
    121            0
TOOLS                                10240     10240         1
    121           50
USERS                                10240     10240         1
    121           50
RECIPE                               10240     10240         1
    121           50

And the datafile size is
FILE_NAME                                  TABLESPACE_NAME
             BYTES
------------------------------------------
------------------------------ ---------
/export/home/u01/oradata/rms/users01.dbf   USERS
           1048576
/export/home/u01/oradata/rms/tools01.dbf   TOOLS
          26214400
/export/home/u01/oradata/rms/temp01.dbf    TEMP
           1048576
/export/home/u01/oradata/rms/rbs01.dbf     RBS
          15728640
/export/home/u01/oradata/rms/system01.dbf  SYSTEM
          83886080
/export/home/u01/oradata/rms/recipe01.dbf  RECIPE
         253952000

I created  the recipe01.dbf with the size as 350M  but it
doesn’t show some much size
Just 250M
Why is it like  this ?

The script was
CREATE TABLESPACE recipe datafile
‘/export/home/uo1/oradata/rms/recipe01.dbf’ size 500k reuse
autoextend on next 500k maxsize.
Now when I use  this database it gets filled up This is used for
testing  . Hence I need a larger space which I am not able to
determined . Can I have something which grows without giving a
maxsize value.
Now this datafile is already defined  I don’t want add one more
datafile I need to use the same datafile recipe01 what should  I
do this .It should be for the same tablespace RECIPE.
How should I go about this.
Should I make the database offline
 If I try to add  a new with making the tablespace offline andf
then I give this command
CREATE TABLESPACE recipe datafile
‘/export/home/uo1/oradata/rms/recipe02.dbf’ size 500k reuse
autoextend on next 500k maxsize.

It  shows error
Create tablespace recipe
*
ERROR at line 1:
ORA-01543 : tablespace ‘RECIPE’ already exists.

What is the problem
Can u help me
SUNIL
and we said...
In 8.0.6, a possible solution for you would be as follows.  If
the result set has a CONSISTENT set of columns in the output
list -- we can do this (else we cannot).  So, if you can create
a table like this:

scott@ORA806.WORLD> create table result_set_temp_tab
  2  as
  3  select empno,ename from emp where 1=0;

Table created.

that will represent the SELECT list of your ref cursor... Then
we can code something like:

scott@ORA806.WORLD> create or replace package demo_pkg
  2  as
  3      type rc is ref cursor;
  4
  5      procedure get_data( p_criteria in varchar2,
  6                          p_cursor   in out rc );
  7  end;
  8  /
Package created.

scott@ORA806.WORLD> create or replace package body demo_pkg
  2  as
  3
  4
  5  procedure execute_immediate( p_sql in varchar2 )
  6  is
  7      cursor_name   pls_integer default dbms_sql.open_cursor;
  8      ignore        pls_integer;
  9  BEGIN
 10      dbms_sql.parse(cursor_name, p_sql, dbms_sql.native);
 11      ignore := dbms_sql.execute(cursor_name);
 12      dbms_sql.close_cursor(cursor_name);
 13  END;
 14
 15
 16  procedure get_data( p_criteria in varchar2,
 17                      p_cursor   in out rc )
 18  is
 19  begin
 20      execute_immediate( 'insert into result_set_temp_tab '
||
 21                          p_criteria );
 22      open p_cursor for select * from result_set_temp_tab;
 23
 24      delete from result_set_temp_tab;
 25  end;
 26
 27
 28  end;
 29  /

Package body created.

that works by putting the result set dynamically into the
table and opening the cursor on it.  Due to our multi-versioning
and read consistent model -- even though we delete it right away
-- the cursor will still see that data.  As far as anyone is
concerned -- that table will ALWAYS appear empty, except to that
ref cursor.  The table "cleans itself out".

Here are 2 calls to that with different criteria:

scott@ORA806.WORLD> variable x refcursor
scott@ORA806.WORLD>
scott@ORA806.WORLD> begin
  2    demo_pkg.get_data( 'select empno,ename
  3                          from emp
  4                         where ename = ''KING'' ',
  5                        :x );
  6  end;
  7  /

PL/SQL procedure successfully completed.

scott@ORA806.WORLD> print x

     EMPNO ENAME
---------- ----------
      7839 KING

scott@ORA806.WORLD>
scott@ORA806.WORLD> begin
  2    demo_pkg.get_data( 'select empno,ename
  3                          from emp
  4                         where sal < 1000',
  5                        :x );
  6  end;
  7  /

PL/SQL procedure successfully completed.

scott@ORA806.WORLD> print x

     EMPNO ENAME
---------- ----------
      7369 SMITH
      7900 JAMES

scott@ORA806.WORLD>

the second Question i do not understand at all.  You say you
created it at 350m but show a create statement of:

CREATE TABLESPACE recipe datafile
‘/export/home/uo1/oradata/rms/recipe01.dbf’ size 500k reuse
autoextend on next 500k maxsize.

which is not valid syntax (maxsize needs to be followed by
something) and is not 350meg (500k).

If you want the file to be 350 meg, you can:

alter database
datafile '/export/home/uo1/oradata/rms/recipe01.dbf'
resize 350m;
Review & Followup
Rating: 5
"default" maxsize May 11, 2003
Reviewer: A reader
On 9i R1 on my win2000,I created a tablespace with autoextend on with specifing
MAXSIZE. ANd Oracle gave the default size of 3.436E+10, whcih is 34.36 GB. Is it
based on the OS 32bit. I have not figured the calculation.
Thanks a lot.

Followup:
your welcome, for what I don't know. doesn't seem to be a question in there,
more a statement of fact.

but if you do the division:

ops$tkyte@ORA920> select 3.436e+10/1024/1024/1024 from dual;

3.436E+10/1024/1024/1024
------------------------
              32.0002437

you find the number is more like 32gig and you'll probably find the number is
really:

34,359,721,984

which is actually:

ops$tkyte@ORA920> select 34359721984/1024/1024/1024 from dual;

34359721984/1024/1024/1024
--------------------------
                        32

exactly 32gig, just meaning that 32gig is probably the largest file we support
on that platform (the above numbers were taken from my linux box)

May 12, 2003 Stephen -- Thanks for the question regarding "What is a driving table", version
You Asked
Could you please explain the concept of 'driving table' and
When joining a number of tables of varying sizes in a 'Select'
statement which one is the driving table and does it matter
where
it goes in the 'from 'statement in the Select clause
Also is there any preferred order with the statements that join
the tables in the 'where' clause
and we said...
The 'driving' table is the table we will join FROM -- that is
JOIN TO other tables.  For example, lets say you have the query:

select * from emp, dept where emp.deptno = dept.deptno;

In this case the driving table might be DEPT, we would fetch
rows from DEPT in a full scan and then find the rows in EMP that
match.  DEPT is the driving table.

The choice of a driving table made using many factors.  For
example, in the above query if there was an index on
DEPT(DEPTNO) but not EMP(DEPTNO), we would probably use EMP as
the driving table -- we would fetch rows from EMP and then using
the DEPT(DEPTNO) index -- find their matches in the DEPT table.
Table sizes, cardinality of column values, and other things can
affect the choice of a driving table (eg: HINTS).

When using the Rule Based Optimizer -- the placement of tables
in the FROM clause is relevant.  We process the from clause from
the RIGHT to the LEFT -- we would tend to pick a driving table
from the end of the FROM list.  There is a hint in the Cost
Based Optimizer to have this happen as well.

When using CBO -- the order of tables is not relevant (unless
you hint it to be).  We use the statistics and data dictionary
to determine which table is best to be used as the driving
table.
Review & Followup
Rating: 4
What is a driving table April 03, 2001
Reviewer: c barr from camridge, ma usa
Good, helpful explanation - could say more.

Rating: 5
Driving table March 07, 2002
Reviewer: O. D from New Jerery, USA
Good morning:
Why there is no different in the order of two tables. It looks EMP is driving
talbe in either order.

Thank you

select /*+ rule */ename, dname from scott.emp,
scott.dept where
scott.emp.deptno = scott.dept.deptno;
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   NESTED LOOPS
   2    1     TABLE ACCESS (FULL) OF 'EMP'
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'DEPT'
   4    3       INDEX (UNIQUE SCAN) OF 'SYS_C0012143' (UNIQUE)
select /*+ rule */ename, dname from scott.dept,
scott.emp where
scott.emp.deptno = scott.dept.deptno;
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   NESTED LOOPS
   2    1     TABLE ACCESS (FULL) OF 'EMP'
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'DEPT'
   4    3       INDEX (UNIQUE SCAN) OF 'SYS_C0012143' (UNIQUE)

Followup:
As i said:

The choice of a driving table made using many factors.  For example, in the
above query if there was an index on DEPT(DEPTNO) but not EMP(DEPTNO), we
would
probably use EMP as the driving table -- we would fetch rows from EMP and then
using the DEPT(DEPTNO) index -- find their matches in the DEPT table.  Table
sizes, cardinality of column values, and other things can affect the choice of a

driving table (eg: HINTS).

So, you have a primary key constraint on dept(depno) -> index.  You do not have
an index on emp(deptno).  Hence, the RBO has a rule that says "indexes are good,
use them" and it chooses the driving table based on the existence of an index.

Rating: 5
About Driving Table April 28, 2003
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
I have created index on emp(deptno) and here is the result as you already
mentioned,

SCOTT >  select ename,dname
  2     from emp,dept
  3   where emp.deptno=dept.deptno;

14 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
   2    1     NESTED LOOPS
   3    2       TABLE ACCESS (FULL) OF 'DEPT'
   4    2       INDEX (RANGE SCAN) OF 'TEMP_IDX' (NON-UNIQUE)

>> From the execution Plan if we want to check that which is driving table then
it seems that the the table on the TOP will be driving table?

Needs your comments.

Followup:
comments about what?

I described what a driving table was above?

Here we will use DEPT as the driving table, the psuedo code for that query plan
is

for x in ( select * from dept ) /* table access (full) dept -- driving table */
loop
   do an index lookup to find matching rows in emp
   for each row found in that index lookup
         output data
   end loop
end loop

Rating: 3
Need some explanation May 12, 2003
Reviewer: Vikas from INDIA
Hi Tom,

If I have a query which has the involvement of 5 tables and the stats for these
tables have been recently updated using dbms_stats, would this query using /*+
RULE */ hint slow down.

What needs to be concluded, is that the optimizer when trying to come up with
the optimal plan look in for the stats even when the coder has used the RULE
hint with the query?

eg. Query A having tables t1,t2,t3,t4 and t4 without the RULE Hint.
Stats not analysed for all these tables
tkprof timings : x1 sec
&
eg. Query A having tables t1,t2,t3,t4 and t4 with the RULE Hint.
Stats analysed for all these tables
tkprof timings : x2 sec

Which one of them would be faster and why? My opinion is that x1 should be
faster as the tables are not analysed and RULE Hint works well enough with the
Syntax & the order of the tables instead of the statistics computed in the data
dictionary.

Your comments are appreciated.

Regards,
Vikas

Followup:
In theory, the query without the hint using the CBO will go as fast, if not
faster then the query with the hint.

the CBO has many more plans available to it.  the CBO is more sophisticated.

May 12, 2003 Meyer -- Thanks for the question regarding "Using the DB that comes with IAS-Best Practice", version 9.0.2
You Asked
Tom, I noticed that that IAS on NT installs a database...I have a main DB on
separate hardware then my web server...but I do have a second db on the same
server as my current IAS.

I am running the first version of IAS (I think 1.01 and DB 8.15 on that sever).
Please expand on the function of the DB that is installed with IAS 9.02?

Does IAS NEED that DB for anything? and if I decide to use it...is it ok if I
use it for a production DB and create lots of objects and get rid of my current
secondary DB?
and we said...
I asked Matt Piermarini, our resident iAS guru to answer this and he says:

...
When you install 9iAS 9.02, there are essentially 4 different install types:
1) J2EE, 2) J2EE & Portal, 3) J2EE & everything (forms,reports,wireless,
portal,etc) and finally, 4) The INFRASTRUCTURE.   The infrastructure
component is what manages and controls all of the other installs. The main
part of the infrastructure is the OID (LDAP) server and the SingleSign
Server.  Each of these requires an Oracle Database for the repository.  The
infrastructure is only needed if installing #2 (Portal) or #3 (Forms,etc),
and is optional for #1 (J2EE).

The database that the infrastructure installs is a 9.0.3 (when you applied
the 9ias Patchset).   You can use this DB for other things, but we would not
recommend doing so in a production environment, as portal, OID, and SSO will
the main applications for this DB.   For development/testing purposes, no
problem.

Your next question might be:  Can I install the infrastructure parts (OID,
PORTAL, SSO) into an existing DB, so I don't have to create another
instance.  This will happen in future releases, not in 9.0.2 ( I heard
9.0.4)

Matt

......
Review & Followup
Rating: 4
Thank You July 22, 2002
Reviewer: Meyer from Baltimore, MD
very helpful

Rating: 4
May 11, 2003
Reviewer: Sean from Atlanta
Why do Oracle recommend the Infrastructure  and customer data in seperate DBs?
Why is it not better put them in one DB, for better managing/maintaning?

Followup:
because most people run infrastructure (app server) on machine1 and database on
machine2 or even more likely databases on machine2, machine3, machine4, ....

the infrastructure database is tied to the ias version as well -- it is upgraded
as an entity, it is self contained.

Rating: 5
May 11, 2003
Reviewer: Sean from Altanta
Thanks.

Rating: 2
Using the DB that comes with IAS-Best Practice May 12, 2003
Reviewer: Leonard Anuka
Do one need to the INFRASTRUCTURE in other to install and use the iAS and in
what order can they be installed with 9i

Followup:
depends, if you do ias EE or SE, yes, you need infrastructure for oid, portal,
workflow, et.al.

if you do ias "java edition" (just j2ee support), you do not need the
infrastructure database

May 12, 2003 Sushant -- Thanks for the question regarding "Invoker rights question & privileges in dynamic sql", version 8.1.7
You Asked
Hi Tom

I have a user with 2 roles connect & resource

following is my code

create or replace procedure chkimm

as

begin

execute immediate('create table mytab (a number)');

end;

it compiles but when i execute  i get the following error

*
ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "PAYLOG.CHKIMM", line 10
ORA-06512: at line 1

now i alter the code with invoker rights
=========================================

create or replace procedure chkimm

authid current_user

as

begin

execute immediate('create table mytab (a number)');

end;

It compiles & executes fine without any errors.

In asktom forums I got some solutions but i am still a bit confused
about invoker rights

Roles are not enabled in plsql so any privileges i have received via
roles wont execute in plsql(correct me if i am wrong). I reckon the
same is happening in the 1st code stated above & hence the error.

I wont have encountered the above problem had i received the create
table privilege not via a role but directly as privilege.

In the second code i have just added a line authid current_user
(invoker
rights) , on the execution of which the error disappears.

Here is what i got  from the fourms ,  by using invoker rights
my procedure will run with the privileges of the user executing the
query plus any & all roles they have

but still my create table privilege is via role(connect , resource)
i havent specially granted create table privilege to scott.

So can i say by using invoker rights all privileges granted to me via
roles can behave as if they are granted individually as a privilege &
not via roles.Correct me if i am wrong.

Since roles are not enabled in plsql, Can i generalize that using
invoker rights in my procedures all privileges granted to me via roles
behave as if they are granted to me individually.

If not how can a privilege error disappear just by adding a line
authid current_user.

Regards
Sushant
and we said...
See
http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html
and if you have my book "Expert one on one Oracle" -- i have a very in depth
chapter on this topic.

when you run a procedure the right way (definer rights), it runs with the base
set of privs granted to the OWNER of the procedure.

When you do it using invokers rights -- it runs with all enabled privs of the
current schema.  Meaning, if you call the procedure from sqlplus -- then it will
run AS IF IT WERE AN ANONYMOUS plsql block almost -- all privs will be inherited
from your session, role based privs inclusive.  If you call that same invokers
rights routine from a definers rights routine, it will run with the BASE set of
privs of the owner of the definers rights routine.

In general, you can use invokers rights routine for simple utilities.  Do not
design a system around it however, it will surely defeat the entire concept of
shared sql, it'll be nightmarish to manage, it would be ugly.
Review & Followup
Rating: 3
Is this possible ?? May 11, 2003
Reviewer: Rama from Ct, USA
Tom,

Let me apologise sincerely for asking a question here. I have been trying
earnestly for 2 days and couldnt get chance. As this is critical and has to
implement a solution on Monday, I had to resort to this. Please....answer my
question.

I have few Tables that has the following structure: (tables have different
structures,
but all of them have ID, PARENTID and PROCESSDATE columns)

ID        NUMBER,
PROCESSDATE    DATE,
COL1        NUMBER,
COL2        VARCHAR2(20),
..
..
..
..
COLN        .....
PARENTID    NUMBER,
KEYCOL        NUMBER

The requirement is:

 Replicate COL1....COLN values for all the records that has NOT NULL KEYCOL
values to:
 IDs that has NULL KEYCOL values and has same PARENTID.

 The KEYCOL column for each table  is defined in a configuration table.

 KEYVALS

 SCHEMANAME    TABLENAME    COLUMNVALUE        START_DATE
END_DATE

 XYZ        TAB1        KEYCOL            07-MAY-2003    31-DEC-9999
 XYZ        TAB2        SOMEOTHERKEYCOL        08-MAY-2003    31-DEC-9999

 And, if a parentId has two Ids that has data for KEYCOL and a third ID that
donot have, then the
 third ID should get replicated with the least ID value of the two IDs that
has data.

 ID .....PARENTID.....KEYCOL
 101      111        0.005
 102      111        0.03
 103      111           NULL
 104      112        0.1
 105      112        NULL

 then, 103 should get 0.005 least of (101,102). and KEYCOL value for 105
should be 0.1(that of 104).

 The procedure should be called with TableName and Date parameters.
 With the passed TableName, we select the KeyCol from the KEYVALS table and
then replicate data.

 I am in a jinx as to how to solve this. Your help would be very valuable
and appreciated.

 thanks again.

 This is what i was trying.....

 create procedure replicate_data(inTable    IN    VARCHAR2,
                  inDate        IN    VARCHAR2
default TO_CHAR(SYSDATE-1,'YYYYMMDD')
                  )
 AS

     theSQL        VARCHAR2(2000);
     theData        NUMBER;
     theSchema    VARCHAR2(30);
     theTable    VARCHAR2(30);
     theColumn    VARCHAR2(30);

 BEGIN
     FOR i IN (SELECT schemaName, TABLENAME, COLUMNVALUE FROM KEYVALS
WHERE TABLENAME = UPPER(inTable) AND start_date <=
TO_DATE(inDate,'YYYYMMDD') AND end_date > TO_DATE(inDate,'YYYYMMDD') )
     LOOP
         /** Do you want me to use bind variables for the following
statement? I didnot use them as the table name itself is not known..so
probably no use to use them **/
         theSQL    :=    'SELECT 1 FROM
'||i.schemaName||'.'||i.tableName||' WHERE '||i.columnName||' IS NOT NULL
AND processdate = '||to_date(inDate,'YYYYMMDD') ||' AND ROWNUM = 1';
         /** storing the following values into variables for use of
them outside the loop **/
         theSchema    :=    i.schemaName;
         theTable    :=    i.tableName;
         theColumn    :=    i.columnName;
         execute immediate theSQL INTO theData;
    END LOOP;

    IF theData = 1 /** then the data table has data for the date **/
        Now, I have Table and KEYCol.....But as I donot know the
number of columns...how to select the records that
        has KEYCOL values and replicate them?
        I am stuck here....please help.

    END IF;

END;
/

Followup:
given:

TABLE t

 ID .....PARENTID.....KEYCOL
 101      111        0.005
 102      111        0.03
 103      111           NULL
 104      112        0.1
 105      112        NULL

you want to generate an update statement that looks like this:

update t
   set (c1,c2...) =
                 (select c1,c2....
                    from t t2
                   where t2.parentid = t.parentid
                     and id = ( select min(id)
                                  from t t3
                                 where t3.parentid = t2.parentid
                                   and keycol is not null ) )
 where keycol is not null
/

just generate that statement for each table you need using USER_TAB_COLUMNS to
find the column names you need to update.

Rating: 5
Thanks ! May 11, 2003
Reviewer: rama from Ct, US
Thanks tom....that works..just a small typo there..
last line to where keycol is null

..
again thanks much.

Followup:
right, you want to update the rows where keycol IS null, not is not null ;)

Rating: 5
Problem again ! May 11, 2003
Reviewer: Rama from Ct, US
Tom,

Sorry. I am encountering this error message from the following procedure.

ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "REPLICATE_FACTORS", line 41
ORA-06512: at line 1

Here is the procedure:

create or replace procedure replicate_factors
(    inTable        IN    VARCHAR2,
                        inDate        IN    VARCHAR2    default
TO_CHAR(SYSDATE-1,'YYYYMMDD')
                         )
AS

    theSQL        VARCHAR2(4000);
    theData        NUMBER;
    theSchema    VARCHAR2(30);
    theTable    VARCHAR2(30);
    theColumn    VARCHAR2(30);
    theColSQL    VARCHAR2(10000);
    theCount    NUMBER    :=    0;
BEGIN
    FOR i IN (SELECT schemaName, tableName, columnName FROM replicate_qa WHERE
tableName = UPPER(inTable) AND start_date <= TO_DATE(inDate,'YYYYMMDD') AND
end_date > TO_DATE(inDate,'YYYYMMDD') AND replicate = 'Y')
    LOOP
        theSQL    :=    'SELECT 1 FROM '||i.schemaName||'.'||i.tableName||'
WHERE '||i.columnName||' IS NOT NULL AND ROWNUM = 1';
        dbms_output.put_line(theSQL);
        theSchema    :=    i.schemaName;
        theTable    :=    i.tableName;
        theColumn    :=    i.columnName;
        execute immediate theSQL INTO theData;
    END LOOP;
        dbms_output.put_line('theData count is: '|| theData ||' - '|| theSchema
||' - '||theTable|| ' - ' || theColumn );
    IF theData = 1 THEN

        FOR i IN (SELECT column_name FROM ALL_TAB_COLUMNS WHERE owner =
theSchema AND table_name = theTable and Column_name NOT IN ( 'CAMO_ID',
'PARENT_CAMO_ID','COMPANY_NAME','FACTSETID','CUSIPSEDOL','EFFECTIVE_DATE','REPLIC
ATION_FG'))
        LOOP
            IF theCount = 0 THEN
                theCOLSQL    :=    i.column_name;
                theCount    :=    theCount + 1;
            ELSE
                theCOLSQL    :=    theColSQL || ',' ||i.column_name;
            END IF;
        END LOOP;

        theSQL    :=    'UPDATE '||theSchema||'.'||theTable ||' t ';
        theSQL    :=    theSQL || 'SET ('||theCOLSQL || ',REPLICATION_FG) =
(SELECT '|| theCOLSQL || ',''Y'' FROM ' || theSchema||'.'||theTable ||' t1 ';
        theSQL    :=    theSQL ||' WHERE t.parent_camo_id = t1.parent_camo_id
AND t.fs_date_downloading = t1.fs_date_downloading AND camo_id IN ';
        theSQL    :=    theSQL ||' (SELECT MIN(camo_id) FROM '||
theSchema||'.'||theTable ||' t2 WHERE ';
        theSQL    :=    theSQL ||'  t2.parent_camo_id = t1.parent_camo_id AND
'|| theColumn || ' IS NOT NULL AND t2.fs_date_downloading =
t1.fs_date_downloading ))';
        theSQL    :=    theSQL ||' WHERE fs_date_downloading = ''' || inDate
||''' AND '|| theColumn || ' IS  NULL';

        EXECUTE IMMEDIATE theSQL;

        commit;

    END IF;
END;
/

Now, if I execute the UPDATE SQL at command prompt, it runs fine.
Here is my table structure:

Name                                      Null?    Type
----------------------------------------- -------- ----------------------------
FACTSETID                                          VARCHAR2(100)
DATE_POPULATION                                    VARCHAR2(100)
CUSIPSEDOL                                         VARCHAR2(100)
CAMOID                                             VARCHAR2(100)
PARENT_CAMO_ID                                     VARCHAR2(100)
COMPANY_NAME                                       VARCHAR2(100)
FS_DATE_DOWNLOADING                                VARCHAR2(100)
CAM_COUNTRY_CD                                     VARCHAR2(100)
CAM_COUNTRY                                        VARCHAR2(100)
CAM_SECTOR                                         VARCHAR2(100)
CAM_INDUSTRY_GROUP                                 VARCHAR2(100)
CAM_INDUSTRY                                       VARCHAR2(100)
CAM_SUB_INDUSTRY                                   VARCHAR2(100)
GICS_SECTOR                                        VARCHAR2(100)
GICS_INDGRP                                        VARCHAR2(100)
GICS_INDUSTRY                                      VARCHAR2(100)
GICS_SUB_INDUSTRY                                  VARCHAR2(100)
MSCI_COUNTRY                                       VARCHAR2(100)
MSCI_REGION                                        VARCHAR2(100)
CW_CAP_LOCAL_CO_0                                  VARCHAR2(100)
CW_CAP_USD_CO_0                                    VARCHAR2(100)
CW_CAP_USD_SEC_0                                   VARCHAR2(100)
CW_CAP_LOCAL_SEC_0                                 VARCHAR2(100)
WS_CURRENCY                                        VARCHAR2(100)
IB_DATE_FY0_0                                      VARCHAR2(100)
IB_DATE_FY1_0                                      VARCHAR2(100)
IB_DATE_0                                          VARCHAR2(100)
IB_MEDIAN_FY1__0                                   VARCHAR2(100)
IB_MEDIAN_FY2__0                                   VARCHAR2(100)
IB_MEAN_FY1__0                                     VARCHAR2(100)
IB_MEAN_FY2__0                                     VARCHAR2(100)
IB_A_EPS_ACTUAL_0                                  VARCHAR2(100)
FS_PRICE_IBESCURR                                  VARCHAR2(100)
CP_PE_1Y_MAVG_0                                    VARCHAR2(100)
CP_PE_3Y_MAVG_0                                    VARCHAR2(100)
CP_PE_5Y_MAVG_0                                    VARCHAR2(100)
CP_PE_7Y_MAVG_0                                    VARCHAR2(100)
WS_PE_1Y_MAVG_0                                    VARCHAR2(100)
WS_PE_3Y_MAVG_0                                    VARCHAR2(100)
WS_PE_5Y_MAVG_0                                    VARCHAR2(100)
WS_PE_7Y_MAVG_0                                    VARCHAR2(100)
FILING_DAYS                                        NUMBER
MOD_ID                                             NUMBER
CAMO_ID                                            VARCHAR2(100)
EFFECTIVE_DATE_SAS                                 VARCHAR2(9)
CW_EPS_0                                           VARCHAR2(100)
CW_EPS_1                                           VARCHAR2(100)
CW_EPS_2                                           VARCHAR2(100)
CW_EPS_3                                           VARCHAR2(100)
CW_EPS_4                                           VARCHAR2(100)
CW_EPS_5                                           VARCHAR2(100)
EPSG_5TO4                                          NUMBER
EPSG_4TO3                                          NUMBER
EPSG_3TO2                                          NUMBER
EPSG_2TO1                                          NUMBER
EPSG_1TO0                                          NUMBER
EPSG_0TOFY1                                        NUMBER
EPSG_FY1TOFY2                                      NUMBER
EPSG_MEDIAN_5TOFY2                                 NUMBER
EPSG_MEAN_5TOFY2                                   NUMBER
EPSG_MEDIAN_5TO0                                   NUMBER
EPSG_MEAN_5TO0                                     NUMBER
EPSG_MEDIAN_0TOFY2                                 NUMBER
EPSG_ACCELERATION_5TOFY2                           NUMBER
FY0_ACTUAL_DATE                                    DATE
DAYS_TO_REPORT                                     NUMBER
TIME_WEIGHT                                        NUMBER
EPSG_LTM                                           NUMBER
EPSG_NTM                                           NUMBER
EPSG_ACCELERATION_NTM                              NUMBER
PE_ACTUAL                                          NUMBER
PE_MEDIAN_FY1                                      NUMBER
PE_MEDIAN_FY2                                      NUMBER
PE_MEDIAN_LTM                                      NUMBER
PE_MEDIAN_NTM                                      NUMBER
PE_MEAN_FY1                                        NUMBER
PE_MEAN_FY2                                        NUMBER
PE_MEAN_LTM                                        NUMBER
PE_MEAN_NTM                                        NUMBER
CW_PE_1Y_MAVG_0                                    VARCHAR2(100)
CW_PE_3Y_MAVG_0                                    VARCHAR2(100)
CW_PE_5Y_MAVG_0                                    VARCHAR2(100)
CW_PE_7Y_MAVG_0                                    VARCHAR2(100)
PE_ACTUAL_REL_1Y                                   NUMBER
PE_ACTUAL_REL_3Y                                   NUMBER
PE_ACTUAL_REL_5Y                                   NUMBER
PE_ACTUAL_REL_7Y                                   NUMBER
PE_MEDIAN_FY1_REL_1Y                               NUMBER
PE_MEDIAN_FY1_REL_3Y                               NUMBER
PE_MEDIAN_FY1_REL_5Y                               NUMBER
PE_MEDIAN_FY1_REL_7Y                               NUMBER
PE_MEDIAN_FY2_REL_1Y                               NUMBER
PE_MEDIAN_FY2_REL_3Y                               NUMBER
PE_MEDIAN_FY2_REL_5Y                               NUMBER
PE_MEDIAN_FY2_REL_7Y                               NUMBER
PE_MEDIAN_LTM_REL_1Y                               NUMBER
PE_MEDIAN_LTM_REL_3Y                               NUMBER
PE_MEDIAN_LTM_REL_5Y                               NUMBER
PE_MEDIAN_LTM_REL_7Y                               NUMBER
PE_MEDIAN_NTM_REL_1Y                               NUMBER
PE_MEDIAN_NTM_REL_3Y                               NUMBER
PE_MEDIAN_NTM_REL_5Y                               NUMBER
PE_MEDIAN_NTM_REL_7Y                               NUMBER
PE_MEAN_FY1_REL_1Y                                 NUMBER
PE_MEAN_FY1_REL_3Y                                 NUMBER
PE_MEAN_FY1_REL_5Y                                 NUMBER
PE_MEAN_FY1_REL_7Y                                 NUMBER
PE_MEAN_FY2_REL_1Y                                 NUMBER
PE_MEAN_FY2_REL_3Y                                 NUMBER
PE_MEAN_FY2_REL_5Y                                 NUMBER
PE_MEAN_FY2_REL_7Y                                 NUMBER
PE_MEAN_LTM_REL_1Y                                 NUMBER
PE_MEAN_LTM_REL_3Y                                 NUMBER
PE_MEAN_LTM_REL_5Y                                 NUMBER
PE_MEAN_LTM_REL_7Y                                 NUMBER
PE_MEAN_NTM_REL_1Y                                 NUMBER
PE_MEAN_NTM_REL_3Y                                 NUMBER
PE_MEAN_NTM_REL_5Y                                 NUMBER
PE_MEAN_NTM_REL_7Y                                 NUMBER
PEG_MEDIAN_ACTUAL                                  NUMBER
PEG_MEDIAN_FY1                                     NUMBER
PEG_MEDIAN_FY2                                     NUMBER
PEG_MEDIAN_LTM                                     NUMBER
PEG_MEDIAN_NTM                                     NUMBER
PEG_MEAN_ACTUAL                                    NUMBER
PEG_MEAN_FY1                                       NUMBER
PEG_MEAN_FY2                                       NUMBER
PEG_MEAN_LTM                                       NUMBER
PEG_MEAN_NTM                                       NUMBER
EFFECTIVE_DATE                                     DATE
REPLICATION_FG                                     CHAR(1)

Can you help me...please.

Followup:
print out the update statement -- you'll see your bug then.

i'll guess it is around this:

 theSQL    :=    theSQL ||' WHERE fs_date_downloading = ''' || inDate
||''' AND

but instead of building and executing the statment, just print it out -- you'll
see what you are building that way:

dbms_output.put_line( 'UPDATE '||theSchema||'.'||theTable ||' t ' );
dbms_output.put_line( 'SET ('||theCOLSQL || ',REPLICATION_FG) =
(SELECT '|| theCOLSQL || ',''Y'' FROM ' || theSchema||'.'||theTable ||' t1 ');
....

Rating: 3
Tried that ! May 11, 2003
Reviewer: Rama from ct, us
tom,

I am sorry I didnot mention it earlier. the update (copied from dbms output from
the procedure) statement when executed from SQL*PLUS works fine. But, when the
statement is executed dynamically, it gives this error.

Here is samle output:

-- LINE 1
UPDATE EQMODEL.TEST_PE_EPSG t
-- LINE 2
SET
(DATE_POPULATION,CAMOID,FS_DATE_DOWNLOADING,CAM_COUNTRY_CD,CAM_COUNTRY,CAM_SECTOR
,CAM_INDUSTRY_GROUP,CAM_INDUSTRY,CAM_SUB_INDUSTRY
,GICS_SECTOR,GICS_INDGRP,GICS_INDUSTRY,GICS_SUB_INDUSTRY,MSCI_COUNTRY,MSCI_REGION
,CW_CAP_LOCAL_CO_0,CW_CAP_USD_CO_0,
CW_CAP_USD_SEC_0,CW_CAP_LOCAL_SEC_0,WS_CURRENCY,IB_DATE_FY0_0,IB_DATE_FY1_0,IB_DA
TE_0,IB_MEDIAN_FY1__0,IB_MEDIAN_FY2__0,
IB_MEAN_FY1__0,IB_MEAN_FY2__0,IB_A_EPS_ACTUAL_0,FS_PRICE_IBESCURR,CP_PE_1Y_MAVG_0
,CP_PE_3Y_MAVG_0,CP_PE_5Y_MAVG_0,CP_PE_7Y_MAVG_0,
WS_PE_1Y_MAVG_0,WS_PE_3Y_MAVG_0,WS_PE_5Y_MAVG_0,WS_PE_7Y_MAVG_0,FILING_DAYS,MOD_I
D,EFFECTIVE_DATE_SAS,CW_EPS_0,CW_EPS_1,CW_EPS_2,
CW_EPS_3,CW_EPS_4,CW_EPS_5,EPSG_5TO4,EPSG_4TO3,EPSG_3TO2,EPSG_2TO1,EPSG_1TO0,EPSG
_0TOFY1,EPSG_FY1TOFY2,
EPSG_MEDIAN_5TOFY2,EPSG_MEAN_5TOFY2,EPSG_MEDIAN_5TO0,EPSG_MEAN_5TO0,EPSG_MEDIAN_0
TOFY2,EPSG_ACCELERATION_5TOFY2,FY0_ACTUAL_DATE,DAYS_TO_REPORT,
TIME_WEIGHT,EPSG_LTM,EPSG_NTM,EPSG_ACCELERATION_NTM,PE_ACTUAL,PE_MEDIAN_FY1,PE_ME
DIAN_FY2,PE_MEDIAN_LTM,PE_MEDIAN_NTM,
PE_MEAN_FY1,PE_MEAN_FY2,PE_MEAN_LTM,PE_MEAN_NTM,CW_PE_1Y_MAVG_0,CW_PE_3Y_MAVG_0,C
W_PE_5Y_MAVG_0,CW_PE_7Y_MAVG_0,PE_ACTUAL_REL_1Y,
PE_ACTUAL_REL_3Y,PE_ACTUAL_REL_5Y,PE_ACTUAL_REL_7Y,PE_MEDIAN_FY1_REL_1Y,PE_MEDIAN
_FY1_REL_3Y,PE_MEDIAN_FY1_REL_5Y,
PE_MEDIAN_FY1_REL_7Y,PE_MEDIAN_FY2_REL_1Y,PE_MEDIAN_FY2_REL_3Y,PE_MEDIAN_FY2_REL_
5Y,PE_MEDIAN_FY2_REL_7Y,PE_MEDIAN_LTM_REL_1Y,
PE_MEDIAN_LTM_REL_3Y,PE_MEDIAN_LTM_REL_5Y,PE_MEDIAN_LTM_REL_7Y,PE_MEDIAN_NTM_REL_
1Y,PE_MEDIAN_NTM_REL_3Y,PE_MEDIAN_NTM_REL_5Y,
PE_MEDIAN_NTM_REL_7Y,PE_MEAN_FY1_REL_1Y,PE_MEAN_FY1_REL_3Y,PE_MEAN_FY1_REL_5Y,PE_
MEAN_FY1_REL_7Y,PE_MEAN_FY2_REL_1Y,PE_MEAN_FY2_REL_3Y,
PE_MEAN_FY2_REL_5Y,PE_MEAN_FY2_REL_7Y,PE_MEAN_LTM_REL_1Y,PE_MEAN_LTM_REL_3Y,PE_ME
AN_LTM_REL_5Y,PE_MEAN_LTM_REL_7Y,
PE_MEAN_NTM_REL_1Y,PE_MEAN_NTM_REL_3Y,PE_MEAN_NTM_REL_5Y,PE_MEAN_NTM_REL_7Y,PEG_M
EDIAN_ACTUAL,PEG_MEDIAN_FY1,PEG_MEDIAN_FY2,
PEG_MEDIAN_LTM,PEG_MEDIAN_NTM,PEG_MEAN_ACTUAL,PEG_MEAN_FY1,PEG_MEAN_FY2,PEG_MEAN_
LTM,PEG_MEAN_NTM,REPLICATION_FG) =
(SELECT
DATE_POPULATION,CAMOID,FS_DATE_DOWNLOADING,CAM_COUNTRY_CD,CAM_COUNTRY,CAM_SECTOR,
CAM_INDUSTRY_GROUP,CAM_INDUSTRY,CAM_SUB_INDUSTRY,GICS_SECTOR,
GICS_INDGRP,GICS_INDUSTRY,GICS_SUB_INDUSTRY,MSCI_COUNTRY,MSCI_REGION,CW_CAP_LOCAL
_CO_0,CW_CAP_USD_CO_0,CW_CAP_USD_SEC_0,
CW_CAP_LOCAL_SEC_0,WS_CURRENCY,IB_DATE_FY0_0,IB_DATE_FY1_0,IB_DATE_0,IB_MEDIAN_FY
1__0,IB_MEDIAN_FY2__0,IB_MEAN_FY1__0,IB_MEAN_FY2__0,
IB_A_EPS_ACTUAL_0,FS_PRICE_IBESCURR,CP_PE_1Y_MAVG_0,CP_PE_3Y_MAVG_0,CP_PE_5Y_MAVG
_0,CP_PE_7Y_MAVG_0,WS_PE_1Y_MAVG_0,
WS_PE_3Y_MAVG_0,WS_PE_5Y_MAVG_0,WS_PE_7Y_MAVG_0,FILING_DAYS,MOD_ID,EFFECTIVE_DATE
_SAS,CW_EPS_0,CW_EPS_1,CW_EPS_2,CW_EPS_3,CW_EPS_4,
CW_EPS_5,EPSG_5TO4,EPSG_4TO3,EPSG_3TO2,EPSG_2TO1,EPSG_1TO0,EPSG_0TOFY1,EPSG_FY1TO
FY2,EPSG_MEDIAN_5TOFY2,EPSG_MEAN_5TOFY2,
EPSG_MEDIAN_5TO0,EPSG_MEAN_5TO0,EPSG_MEDIAN_0TOFY2,EPSG_ACCELERATION_5TOFY2,FY0_A
CTUAL_DATE,DAYS_TO_REPORT,TIME_WEIGHT,EPSG_LTM,
EPSG_NTM,EPSG_ACCELERATION_NTM,PE_ACTUAL,PE_MEDIAN_FY1,PE_MEDIAN_FY2,PE_MEDIAN_LT
M,PE_MEDIAN_NTM,PE_MEAN_FY1,PE_MEAN_FY2,
PE_MEAN_LTM,PE_MEAN_NTM,CW_PE_1Y_MAVG_0,CW_PE_3Y_MAVG_0,CW_PE_5Y_MAVG_0,CW_PE_7Y_
MAVG_0,PE_ACTUAL_REL_1Y,PE_ACTUAL_REL_3Y,
PE_ACTUAL_REL_5Y,PE_ACTUAL_REL_7Y,PE_MEDIAN_FY1_REL_1Y,PE_MEDIAN_FY1_REL_3Y,PE_ME
DIAN_FY1_REL_5Y,PE_MEDIAN_FY1_REL_7Y,
PE_MEDIAN_FY2_REL_1Y,PE_MEDIAN_FY2_REL_3Y,PE_MEDIAN_FY2_REL_5Y,PE_MEDIAN_FY2_REL_
7Y,PE_MEDIAN_LTM_REL_1Y,PE_MEDIAN_LTM_REL_3Y,
PE_MEDIAN_LTM_REL_5Y,PE_MEDIAN_LTM_REL_7Y,PE_MEDIAN_NTM_REL_1Y,PE_MEDIAN_NTM_REL_
3Y,PE_MEDIAN_NTM_REL_5Y,PE_MEDIAN_NTM_REL_7Y,
PE_MEAN_FY1_REL_1Y,PE_MEAN_FY1_REL_3Y,PE_MEAN_FY1_REL_5Y,PE_MEAN_FY1_REL_7Y,PE_ME
AN_FY2_REL_1Y,PE_MEAN_FY2_REL_3Y,PE_MEAN_FY2_REL_5Y,
PE_MEAN_FY2_REL_7Y,PE_MEAN_LTM_REL_1Y,PE_MEAN_LTM_REL_3Y,PE_MEAN_LTM_REL_5Y,PE_ME
AN_LTM_REL_7Y,PE_MEAN_NTM_REL_1Y,
PE_MEAN_NTM_REL_3Y,PE_MEAN_NTM_REL_5Y,PE_MEAN_NTM_REL_7Y,PEG_MEDIAN_ACTUAL,PEG_ME
DIAN_FY1,PEG_MEDIAN_FY2,PEG_MEDIAN_LTM,PEG_MEDIAN_NTM,
PEG_MEAN_ACTUAL,PEG_MEAN_FY1,PEG_MEAN_FY2,PEG_MEAN_LTM,PEG_MEAN_NTM,'Y' FROM
EQMODEL.TEST_PE_EPSG t1
-- LINE 3
WHERE t.parent_camo_id = t1.parent_camo_id AND t.fs_date_downloading =
t1.fs_date_downloading AND camo_id IN
-- LINE 4
(SELECT MIN(camo_id) FROM EQMODEL.TEST_PE_EPSG t2 WHERE
-- LINE 5
t2.parent_camo_id = t1.parent_camo_id AND EPSG_MEDIAN_0TOFY2 IS NOT NULL AND
t2.fs_date_downloading = t1.fs_date_downloading ))
-- LINE 6
WHERE fs_date_downloading = '20030507' AND EPSG_MEDIAN_0TOFY2 IS  NULL

But, the same statement with Execute Immediate fails. However, this is working
dynamically for this table structure.

Name                                      Null?    Type
----------------------------------------- -------- ---------------------
FACTSETID                                          VARCHAR2(100)
DATE_POPULATION                                    VARCHAR2(100)
CUSIPSEDOL                                         VARCHAR2(100)
CAMOID                                             VARCHAR2(100)
PARENT_CAMO_ID                                     VARCHAR2(100)
COMPANY_NAME                                       VARCHAR2(100)
FS_DATE_DOWNLOADING                                VARCHAR2(100)
CAM_COUNTRY_CD                                     VARCHAR2(100)
CAM_COUNTRY                                        VARCHAR2(100)
CAM_SECTOR                                         VARCHAR2(100)
CAM_INDUSTRY_GROUP                                 VARCHAR2(100)
CAM_INDUSTRY                                       VARCHAR2(100)
CAM_SUB_INDUSTRY                                   VARCHAR2(100)
GICS_SECTOR                                        VARCHAR2(100)
GICS_INDGRP                                        VARCHAR2(100)
GICS_INDUSTRY                                      VARCHAR2(100)
GICS_SUB_INDUSTRY                                  VARCHAR2(100)
MSCI_COUNTRY                                       VARCHAR2(100)
MSCI_REGION                                        VARCHAR2(100)
CW_CAP_LOCAL_CO_0                                  VARCHAR2(100)
CW_CAP_USD_CO_0                                    VARCHAR2(100)
CW_CAP_USD_SEC_0                                   VARCHAR2(100)
CW_CAP_LOCAL_SEC_0                                 VARCHAR2(100)
WS_CURRENCY                                        VARCHAR2(100)
IB_DATE_FY0_0                                      VARCHAR2(100)
IB_DATE_FY1_0                                      VARCHAR2(100)
IB_DATE_0                                          VARCHAR2(100)
IB_DATE_A_EPS_ACTUAL                               VARCHAR2(100)
IB_MEDIAN_FY1__0                                   VARCHAR2(100)
IB_MEDIAN_FY1__1                                   VARCHAR2(100)
IB_MEDIAN_FY1__3                                   VARCHAR2(100)
IB_MEDIAN_FY1__6                                   VARCHAR2(100)
IB_MEDIAN_FY2__0                                   VARCHAR2(100)
IB_MEDIAN_FY2__1                                   VARCHAR2(100)
IB_MEDIAN_FY2__3                                   VARCHAR2(100)
IB_MEDIAN_FY2__6                                   VARCHAR2(100)
IB_MEDIAN_FY3__0                                   VARCHAR2(100)
IB_MEDIAN_FY3__1                                   VARCHAR2(100)
IB_MEDIAN_FY3__3                                   VARCHAR2(100)
IB_MEDIAN_FY3__6                                   VARCHAR2(100)
IB_FY1_NUM__0                                      VARCHAR2(100)
IB_FY1_NUM__1M                                     VARCHAR2(100)
IB_FY1_NUM__3M                                     VARCHAR2(100)
IB_FY1_NUM__6M                                     VARCHAR2(100)
IB_FY1_UP__1M                                      VARCHAR2(100)
IB_FY1_UP__3M                                      VARCHAR2(100)
IB_FY1_UP__6M                                      VARCHAR2(100)
IB_FY1_DOWN__1M                                    VARCHAR2(100)
IB_FY1_DOWN__3M                                    VARCHAR2(100)
IB_FY1_DOWN__6M                                    VARCHAR2(100)
IB_FY2_NUM__0                                      VARCHAR2(100)
IB_FY2_NUM__1M                                     VARCHAR2(100)
IB_FY2_NUM__3M                                     VARCHAR2(100)
IB_FY2_NUM__6M                                     VARCHAR2(100)
IB_FY2_UP__1M                                      VARCHAR2(100)
IB_FY2_UP__3M                                      VARCHAR2(100)
IB_FY2_UP__6M                                      VARCHAR2(100)
IB_FY2_DOWN__1M                                    VARCHAR2(100)
IB_FY2_DOWN__3M                                    VARCHAR2(100)
IB_FY2_DOWN__6M                                    VARCHAR2(100)
IB_FY3_NUM__0                                      VARCHAR2(100)
IB_FY3_NUM__1M                                     VARCHAR2(100)
IB_FY3_NUM__3M                                     VARCHAR2(100)
IB_FY3_NUM__6M                                     VARCHAR2(100)
IB_FY3_UP__1M                                      VARCHAR2(100)
IB_FY3_UP__3M                                      VARCHAR2(100)
IB_FY3_DOWN__1M                                    VARCHAR2(100)
IB_FY3_DOWN__3M                                    VARCHAR2(100)
IB_FY3_DOWN__6M                                    VARCHAR2(100)
IB_SHARES_0M                                       VARCHAR2(100)
IB_SHARES_3M                                       VARCHAR2(100)
FILING_DAYS                                        NUMBER
MOD_ID                                             NUMBER
CAMO_ID                                            VARCHAR2(100)
EFFECTIVE_DATE_SAS                                 VARCHAR2(9)
DAYS_TO_REPORT                                     NUMBER
TIME_WEIGHT                                        NUMBER
DEPTH_FY1_1M                                       NUMBER
DEPTH_FY1_3M                                       NUMBER
DEPTH_FY1_6M                                       NUMBER
DEPTH_FY2_1M                                       NUMBER
DEPTH_FY2_3M                                       NUMBER
DEPTH_FY2_6M                                       NUMBER
DEPTH_FY3_1M                                       NUMBER
DEPTH_FY3_3M                                       NUMBER
DEPTH_FY3_6M                                       NUMBER
DEPTH_NTM_1M                                       NUMBER
DEPTH_NTM_3M                                       NUMBER
DEPTH_NTM_6M                                       NUMBER
BREADTH_FY1_1M                                     NUMBER
BREADTH_FY1_3M                                     NUMBER
BREADTH_FY1_6M                                     NUMBER
BREADTH_FY2_1M                                     NUMBER
BREADTH_FY2_3M                                     NUMBER
BREADTH_FY2_6M                                     NUMBER
BREADTH_FY3_1M                                     NUMBER
BREADTH_FY3_3M                                     NUMBER
BREADTH_FY3_6M                                     NUMBER
BREADTH_NTM_1M                                     NUMBER
BREADTH_NTM_3M                                     NUMBER
BREADTH_NTM_6M                                     NUMBER
DEPTH_NTM_1M_RANK                                  NUMBER
DEPTH_NTM_3M_RANK                                  NUMBER
BREADTH_NTM_1M_RANK                                NUMBER
BREADTH_NTM_3M_RANK                                NUMBER
ESTIMATE_REVISION                                  NUMBER
ESTIMATE_REVISION_RANK                             NUMBER
ESTIMATE_REVISION_PRCTL                            NUMBER
EFFECTIVE_DATE                                     DATE
REPLICATION_FG                                     CHAR(1)

Followup:
look at line 41 of your code, I'd bet it is NOT execute immediate.

I'd bet it is theSql := theSql || ......

I'll bet varchar2(4000) isn't big enough to hold that update which appears to be
about 4400 characters.

Rating: 3
External Table May 12, 2003
Reviewer: A reader
Hi Tom,
  I have problem when I want to query data from external table,
  From [Query3]. ,It shows me that no data found, but I sure for that join have
data.
  Then ,I try to findout ,It seem like that problem is some field in query
makes this query no data found
  { from this query is ATPT_MT_BATCH_DATE} .[Query2 ]

  In the past, I ever found this problem when I first to implement External
Table.
  By that case, It cause some filed in DataType Number(10,2) as usually in
SQL*Loader use statement
    ==> FIELD_A        position(M:N)    decimal external ":FIELD_A/100",
   I resolve  by use statement FIELD_A        position(M:N)    decimal external
, then devide to(10,2) in view (FIELD_A/100) .

   I think It should have better way to solve this problem , Pls suggest it to
me too.

   But, Now , This Problem is from Join query .While it can direct query from
External Table. [Query1]

  please correct me for this example..

  Thank a lot.

[Query1]

16:19:36 40DWH>select ATPT_MT_BATCH_DATE
16:19:54   2   from V_GAIN_SALE_MASTER
16:20:00   3   where ATPT_ACCT = '0002299119998261006';

ATPT_MT_BAT
-----------
28-APR-2003
01-MAY-2003
02-MAY-2003
02-MAY-2003
06-MAY-2003

[Query2]

16:32:57 40DWH>(SELECT

16:33:12   2             ATPT_ORG,
16:33:12   3             ATPT_ACCT,
16:33:12   4             ATPT_LOGO,
16:33:12   5             ATPT_MT_EFF_DATE,
16:33:12   6             ATPT_MT_TXN_CODE,
16:33:12   7             ATPT_MT_AMOUNT,
16:33:12   8        --     ATPT_MT_BATCH_DATE,
16:33:12   9             ATPT_MT_POSTING_DATE,
16:33:12  10             ATPT_MT_SI_NBR,
16:33:12  11             ATPT_MT_PMT_REFERENCE_NBR,
16:33:12  12             ATPT_MT_DEPT,
16:33:12  13             GAIN_PRI_NAME_LINE_1,
16:33:12  14             GAIN_PRI_NAME_LINE_2,
16:33:12  15             GAIN_CYCLE,

16:33:12  16             GAIN_FRAUD_IND,

16:33:12  17             GAIN_BLK_CODE_1_2
16:33:12  18          FROM

16:33:13  19            V_GAIN_SALE_MASTER, V_GAIN_MASTER
16:33:13  20          WHERE   ATPT_MT_TXN_CODE  in ('651','051','151','152') AND

16:33:13  21               (  ATPT_ORG =  GAIN_ORG (+) ) AND

16:33:13  22       ( ltrim (  ATPT_ACCT , '000' ) =  GAIN_ACCT_NMBR (+) ) AND
16:33:13  23               (  ATPT_LOGO =  GAIN_LOGO (+) )

16:33:13  24          and ATPT_ACCT =  '0002299119998261006'
16:33:13  25          );

ATP ATPT_ACCT           AT ATPT_MT_EFF ATPT_MT_TXN_CODE  ATPT_MT_AMOUNT
ATPT_MT_POS  ATPT_MT_SI_NBR AT
--- ------------------- -- ----------- ---------------- ---------------
----------- --------------- --
201 0002299119998261006 11 09-MAY-2003              651          106.15
09-MAY-2003         9024001

16:34:04 40DWH>ed
Wrote file editsql.buf

[Query3]

  1  (SELECT
  2             ATPT_ORG,
  3             ATPT_ACCT,
  4             ATPT_LOGO,
  5             ATPT_MT_EFF_DATE,
  6             ATPT_MT_TXN_CODE,
  7             ATPT_MT_AMOUNT,
  8             ATPT_MT_BATCH_DATE,
  9             ATPT_MT_POSTING_DATE,
 10             ATPT_MT_SI_NBR,
 11             ATPT_MT_PMT_REFERENCE_NBR,
 12             ATPT_MT_DEPT,
 13             GAIN_PRI_NAME_LINE_1,
 14             GAIN_PRI_NAME_LINE_2,
 15             GAIN_CYCLE,
 16             GAIN_FRAUD_IND,
 17             GAIN_BLK_CODE_1_2
 18          FROM
 19            V_GAIN_SALE_MASTER, V_GAIN_MASTER
 20          WHERE   ATPT_MT_TXN_CODE  in ('651','051','151','152') AND
 21               (  ATPT_ORG =  GAIN_ORG (+) ) AND
 22       ( ltrim (  ATPT_ACCT , '000' ) =  GAIN_ACCT_NMBR (+) ) AND
 23               (  ATPT_LOGO =  GAIN_LOGO (+) )
 24          and ATPT_ACCT =  '0002299119998261006'
 25*         )
16:40:04 40DWH>/

no rows selected

Followup:
you would really have to give me a nice concise testcase with which to
reproduce.  for example, something like:

I've got a file, it has data like this:

.......<actual data would be here>....

I'm using this external table definition:

.....<actual create table here>......

If I select on it using <query here>, I see this <data here>

but when I create this table < table create here> and insert this stuff in it
<inserts here -- actual inserts so I can run them> and join <actual query here>
I get nothing....

(many times, most times in fact, in the setup of this small test case -- people
find their problem!)

Rating: 3
Figured it out ! May 12, 2003
Reviewer: Rama from ct, US
Figured it out....thanks.

May 12, 2003 Xavier -- Thanks for the question regarding "CLOB 8-bit version?", version 9.0.0
You Asked
I am just starting to work with CLOB's (using dotnet but that's not what my
question is about).  The CLOB's are coming in as Unicode strings, which means
they are taking 2 bytes per character.  I don't really have a problem with that
inside my application but I want to make sure that the CLOB's are stored as
single-byte text within the database, if at all possible.  My application is on
an intranet so I know that I do not need multi-lingual support.

My question is, do CLOB's come in flavors such as ASCII or Unicode and if so how
do I pick one when I create a CLOB column?
and we said...
read:
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96529/toc.htm
the database has a character set.
the client has a character set.

when the client and database have DIFFERENT character sets, we convert from one
to the other using the standard as a guide to tell us "how to do that".

So, if your database uses an 8bit character set, your unicode CLOBS will be
converted -- as long as the client using unicode has their NLS_LANG environment
(registry) variable set correctly.

Give that document a read through, it will be well worth the time spent on it.
Review & Followup
Rating: 5
Thanks May 12, 2003
Reviewer: Xavier Irias from Oakland CA
Great answer, thanks.

May 12, 2003 Jansen -- Thanks for the question regarding "AQ Newbie", version 9.0.2
You Asked
Dear Tom,

I have a stored procedure who process some table into other table.
In that procedure have a loop. How to make an AQ to send a message to client,
where that message content a value of loop. With this message I want to inform
to client how many percent that process done.

Pls help me

Best Regards
Jansen Hutagalung
and we said...
you do not need AQ to do that.

you only need dbms_application_info.  Consider a loop like this:

declare
    l_nohint number default dbms_application_info.set_session_longops_nohint;
    l_rindex number default l_nohint;
    l_slno   number;
begin
    for i in 1 .. 10
    loop
        dbms_lock.sleep(2);  /* your "process" goes here */
        dbms_application_info.set_session_longops
        ( rindex =>  l_rindex,
          slno   =>  l_slno,
          op_name => 'my long running operation',
          target  =>  1234,
          target_desc => '"msg description"',
          context     => 0,
          sofar       => i,
          totalwork   => 10,
          units       => 'loops'
        );
    end loop;
end;
/

Now, in another session, run:

select * from v$session_longops where time_remaining > 0;

That lets your long running procedure tell the outside world how far along it is
-- your application would only have to query v$session_longops in order to see
whats up.
May 12, 2003 Somrut -- Thanks for the question regarding "WHy Some Database Envirenment I must user .world??", version 8.1.7
You Asked
I have some confuse about Database TNS.
Cause Some Database I must use .world after connection name to connect to
database. If I not enter .world it error "can't resolve service name"
and we said...
look in the sqlnet.ora on the client.

in the tnsnames.ora (another client file) they must have xxx.world.  In the
sqlnet.ora, they must NOT have a default domain setup.

for example, in my sqlnet.ora i have:

NAMES.DEFAULT_DOMAIN = us.oracle.com

In my tnsnames.ora I have entries like:

ORA920.US.ORACLE.COM =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = tkyte-pc)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SERVER = DEDICATED)
      (SERVICE_NAME = ora920)
    )
  )

Now, since my default domain is us.oracle.com, I can either

sqlplus scott/tiger@ora920

OR

sqlplus scott/tiger@ora920.us.oracle.com

the .us.oracle.com is supplied for me if I do not use it.
Review & Followup
Rating: 4
Good ! May 12, 2003
Reviewer: A reader
This has been bugging since I began working with Oracle. Thanks for the answer.

Rating: 5
May 12, 2003
Reviewer: A reader

Rating: 3
My SQLnet.ora Only contain .. this May 28, 2003
Reviewer: Somrut
SQLNET.AUTHENTICATION_SERVICES= (NTS)
NAMES.DIRECTORY_PATH= (TNSNAMES, ONAMES, HOSTNAME)

May 12, 2003 Raju -- Thanks for the question regarding "Cursor", version 8.1.7
You Asked
Dear Mr Tom,
First of all Thank you for this site. I have a very simple question. I need to
spool data selected from 7-8 tables into a flat file. I know about SPOOL and
have learnt about UTL_FILE last week. I am confused as to how to spool the data
from the tables into 3 parts per file. Like
The report if by Sales Rep.
part1
select * from a.col1, b.col2 ....
from A, B
where a.colx=b.colx

part2
select * from C.col3, D.col4 ....
from C,D
where C.colx=D.colx

Requirement is that each flat file have the <name of the rep>.txt. The name of
the rep is in a table called ALL_REPS. I know I cannot do it in simple SQL and
will have to write a PL/SQL. Do I need to open separate cursors for each
part(part1,part2 and part3) and if so how? I have never done it and really need
someone to help.

Thanks for your time
and we said...
I might use SQL to write SQL.  Here is an example that uses "tables" as if they
were "sales reps".  We'll print out a report by "table" (sales rep to you) and
each report will goto a file named after the table (rep).

We'll start with a parametrized script to do the report:

----------------------- report.sql -------------------------------------
set autotrace off
set verify off
set linesize 72
set pagesize 9999
set feedback off
set termout off

spool &1..txt

desc &1

Prompt Indexes on &1
column index_name heading "Index|Name"
column Uniqueness heading "Is|Unique" format a6
column columns format a32 word_wrapped

select substr(a.index_name,1,30) index_name,
       decode(a.uniqueness,'UNIQUE','Yes','No') uniqueness,
max(decode( b.column_position,  1, substr(b.column_name,1,30),
NULL )) ||
max(decode( b.column_position,  2, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position,  3, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position,  4, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position,  5, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position,  6, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position,  7, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position,  8, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position,  9, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position, 10, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position, 11, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position, 12, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position, 13, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position, 14, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position, 15, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position, 16, ', '||
substr(b.column_name,1,30), NULL )) columns
from all_indexes a, all_ind_columns b
where a.owner = user
and a.table_name = upper('&1')
and b.table_name = a.table_name
and b.table_owner = a.owner
and a.index_name = b.index_name
group by substr(a.index_name,1,30), a.uniqueness
/
spool off
set termout on
set feedback on
set verify on
------------------------------------- end of file -------------------

So, if we run

SQL> @report t

that will create a file T.TXT that might look like this:

 Name                                 Null?    Type
 ------------------------------------ -------- -------------------------
 X                                             DATE
 Y                                             CHAR(255)

Indexes on t

Index                          Is
Name                           Unique COLUMNS
------------------------------ ------ --------------------------------
T_IDX                          No     X

Now, what we want to do is have a script that will call report.sql for each
table (rep).  That would be:

------------------- runall.sql ---------------------------------
set heading off
set feedback off
set termout off
spool tmp.sql
select '@report ' || table_name from user_tables;
spool off
set termout on
set feedback on
set heading on
@tmp.sql
-------------------------------------------------------------------

you would replace table_name with "repname" and user_tables with ALL_REPS
(suggestion -- all_reps is confusing to an Oracle-ite like me, it looks like a
dictionary table, in fact, who knows the next release might have a table named
that!  avoid table names that start with ALL_, USER_, DBA_ -- that would be
confusing)

That would simply create a file tmp.sql in the current directory that would call
report for each row in user_tables -- creating a file per table (per rep).

That's it.  One script to create a report for a rep.  Another script to generate
a script that calls this report for each rep.  Done.
Review & Followup
Rating: 5
excellent, as usual !!! May 12, 2003
Reviewer: orafan from India from india

Rating: 3
Thanks and .. May 14, 2003
Reviewer: Raju
Thanks Tom. I started coding my query and I will surely let you about the
success or failure. I am HOOKED to this site and am always telling others about
this - to the point of being irritating. I bought your book - Is it possible
that I get your signature on this book.

Thanks a million Tom.

May 12, 2003 play -- Thanks for the question regarding "query and current", version 8.1.7
You Asked
Tom

the following is tkprof output for a delete statement

call     count       cpu    elapsed       disk      query    current
rows
------- ------  -------- ---------- ---------- ---------- ----------
----------
Parse        1      0.00       0.01          0          0          0
0
Execute      1     52.54     126.17      38033      33109    1056178
142117
Fetch        0      0.00       0.00          0          0          0
0

say , Iam the only the logged into the database, From what I understand the
number in current should be zero , as nothing will be changing in the table Iam
doing the deltes.

query should be where all the data should be read , as data is read as it
existed when I issued the delete statement , is this not true.

current should be more than zero only when other transactions do some dml and
commit, while Iam doing my delete , is my understanding right?

further, I seldom see disk reads zero, why is this so, when I execute a query
all the blocks are loaded into the memory, so when I execute it next time the
value under disk should be zero

is there a scenario where the value under query is zero and all the blocks are
read in current state
and we said...
every row you delete will need to get the block the row is on in CURRENT MODE as
well as the block the index entry is on for that row.  You are changing the
table -- any row/index/etc you change will be gotten in CURRENT mode.

The query mode gets is the "read" or select part of your delete.

Each index you have will add to the current mode gets.  Consider this exmaple:

create table t ( x int, y int, z int );
insert into t select rownum, rownum, rownum from all_objects where rownum
<=1000;
commit;

alter session set sql_trace=true;
delete from t where x+0 < 100;
commit;
create index t1_idx on t(x);
delete from t where x+0 < 200;
commit;
create index t2_idx on t(y);
delete from t where x+0 < 300;
commit;
create index t3_idx on t(z);
delete from t where x+0 < 400;
commit;

the tkprof shows me then:

delete from t
where
 x+0 < 100

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          3        109          99
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          3        109          99

delete from t
where
 x+0 < 200

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          1          3        312         100
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          1          3        312         100

delete from t
where
 x+0 < 300

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          1          3        513         100
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          1          3        513         100

delete from t
where
 x+0 < 400

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          1          3        716         100
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          1          3        716         100

Each index added about 2 CURRENT mode block gets in my example....

Query mode gets are blocks gotten in a mode to protect you from other sessions
(and yourself in fact - the read portion of your DELETE cannot see your own
deletions)...

As for the disk reads, a full table scan of a large table will not buffer the
blocks from that table.  These blocks will be flushed out faster then blocks
that are read individually - by design.  this is to avoid the trashing of the
buffer cache that would happen otherwise on a large full table scan (as you are
doing).

I cannot think of an example where query mode blocks are zero.  updates and
deletes always read the table in query mode and then do their stuff in current
mode.  selects of course read in query mode.  select for update does both.

followup to comments one and two

Query mode = blocks read in read consistent mode.
Current mode = blocks read as they exist, right now.

When we process the query "DELETE FROM T WHERE X = 5", Oracle will find the rows
to delete using QUERY mode gets.  That identifies the rows we will delete.

When we actually FIND the row to delete, we must get the block *as it exists
right now*.  This only makes sense -- we cannot change the PAST version of the
block (else we would miss other changes).  The block we physically modify must
be the current, last version of the block (someone else may have also modified
that block after our delete began but before we got there -- if they have used
that block, we need to see their changes).

query mode = consistent reads, find the rows
current mode = we want to modify this block right now, we need the last version
of it to do this safely.

followup to comment 3

Lets use this part to look at this question:

delete from t
where
 x+0 < 100

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          3        109          99
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          3        109          99

Here we had no indexes -- just rows.  Well, we did a full scan and that gets
some blocks in CURRENT mode (the extent info and such).  We also deleted 99 rows
-- each of the ROWS we deleted gets its block in CURRENT mode.  If we deleted 5
rows we might have read 1 block in query mode but we'll get 5 blocks (the same
block) in CURRENT MODE.  Each row deleted is a current mode get.  We get the
block in current mode, delete the row, and give it back.

So, the more rows you delete, the more current mode gets you'll do.

followup to comment 4

The " x+0 < 0 " was to force a full scan. I did not want it to use the INDEX to
find the rows to delete.  I only added indexes to show that the addition of each
additional index added a some amount of overhead to the delete.
Review & Followup
Rating: 4
question August 07, 2001
Reviewer: Jimmy from USA
Tom

for deletes and updates, Why do we have to get the blocks in current mode, why
can we get these blocks in query mode, as it is  they are read in query mode for
updates and deletes.

can you illustrace with an example?

Rating: 3
need more explanation August 08, 2001
Reviewer: Banjo from USA
I have the same questions a Jimmy. When you give the delete statement, I am
under the understanding that we access all data in a read consistent state, i.e.
data as it existed when we issued the sql statement.

Now in between what if another user logs in and makes somes changes, and some
rows should not be deleted ( or is this the reason why blocks are read in
current state, i.e. as they exist now)

Tom, so are you saying that data is read in query mode for selects , and current
mode for updates and deletes.

Does query mode mean consistent gets.

Rating: 4
response to comments August 08, 2001
Reviewer: Asim from USA
If that is so, then the qeury and current should have the same number of blocks
read , in teh tkprof output in the question, why do we have more blocks under
current than what we have under query

Rating: 5
Tom rules! August 09, 2001
Reviewer: A reader from ma
Excellent!
Missed you these last weeks.

  why delete from t where x+0 < 300 ?
  why not just x < 0 ??

Rating: 5
current mode in queries May 12, 2003
Reviewer: PINGU
Hi

sometimes when I run autotrace in queries (no matter how many times I run the
same query) I see db block gets whch from the documentation also means current
mode. How come? Isnt current mode only used in DML?

Followup:
full scans do db block gets on the segment header.

Rating: 5
Buffer gets May 12, 2003
Reviewer: A reader
Are the "buffer gets" numbers in the sql part of the statspack report the same
as logical io (consistent+db block gets) ?

Followup:
buffer gets are logical ios, yes.

May 12, 2003 Cesar -- Thanks for the question regarding "Next_Day question", version 8.1.6
You Asked
Hi Tom!, Happy new year!!!

How can I use ever the same value for [name_of_day] in [next_day]? Try this:

SQL> declare
  2    v_until date;
  3  begin
  4    v_until := next_day(sysdate, 'MONDAY');
  5  end;
  6  /
declare
*
ERROR at line 1:
ORA-01846: not a valid day of the week
ORA-06512: at line 4

SQL> alter session set nls_language=english;

Session altered.

SQL> declare
  2    v_date date;
  3  begin
  4    v_date := next_day(sysdate, 'MONDAY');
  5  end;
  6  /

PL/SQL procedure successfully completed.

I want an unique [name_of_day] for the next_day function independently of the
NLS_LANGUAGE parameter...

Thanks!!!
and we said...
Here is a trick I use:

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2          l_monday varchar2(255) default to_char( to_date( '20011231',
'yyyymmdd' ), 'day' );
  3          l_until  date;
  4  begin
  5          l_until := next_day( sysdate, l_monday );
  6          dbms_output.put_line( l_until  || ' ' || l_monday );
  7  end;
  8  /
14-JAN-02 monday

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2          l_until  date;
  3  begin
  4          l_until := next_day( sysdate, 'monday' );
  5          dbms_output.put_line( l_until );
  6  end;
  7  /
14-JAN-02

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set nls_language = french;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2          l_monday varchar2(255) default to_char( to_date( '20011231',
'yyyymmdd' ), 'day' );
  3          l_until  date;
  4  begin
  5          l_until := next_day( sysdate, l_monday );
  6          dbms_output.put_line( l_until  || ' ' || l_monday );
  7  end;
  8  /
14-JAN-02 lundi

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2          l_until  date;
  3  begin
  4          l_until := next_day( sysdate, 'monday' );
  5          dbms_output.put_line( l_until );
  6  end;
  7  /
declare
*
ERROR at line 1:
ORA-01846: not a valid day of the week
ORA-06512: at line 4

That works -- i just pick a date I KNOW is a monday and ask Oracle to "spell
monday" for me.
Review & Followup
Rating: 5
Neatest little trick I have ever seen! January 09, 2002
Reviewer: An ardent fan from USA
Wow! Tom, that was simply awesome! Kind of beating beast (sorry, Oracle) in its
own game.

Rating: 5
: ) January 09, 2002
Reviewer: Cesar Salas from Mexico
you are a genius!!!

1000 Thanks...

Rating: 5
VERY nicely done January 09, 2002
Reviewer: Jim from australia
Tom,
    Really liked this one !
You have really prompted me to start thinking more
global with your comments and solutions to the recent
trigger question and this one by highlighting the need
to cater for different languages.

Rating: 5
Russian problem January 16, 2002
Reviewer: Cesar Salas from Mexico
Hi tom,

I have a little problem with RUSSIAN language.

Example.

create or replace view Week
as
select
sysdate Today,
next_day(sysdate,
  to_char(to_date('01012001', 'ddmmyyyy'), 'day')) mon,
next_day(sysdate,
  to_char(to_date('02012001', 'ddmmyyyy'), 'day')) tue,
next_day(sysdate,
  to_char(to_date('03012001', 'ddmmyyyy'), 'day')) wed,
next_day(sysdate,
  to_char(to_date('04012001', 'ddmmyyyy'), 'day')) thu,
next_day(sysdate,
  to_char(to_date('05012001', 'ddmmyyyy'), 'day')) fri,
next_day(sysdate,
  to_char(to_date('06012001', 'ddmmyyyy'), 'day')) sat,
next_day(sysdate,
  to_char(to_date('07012001', 'ddmmyyyy'), 'day')) sun
from dual
/

* And then...

select * from week
/

TODAY     MON       TUE       WED       THU       FRI       SAT       SUN
--------- --------- --------- --------- --------- --------- --------- ---------
16-JAN-02 21-JAN-02 22-JAN-02 23-JAN-02 17-JAN-02 18-JAN-02 19-JAN-02 20-JAN-02

Works fine, but...

alter session set nls_language = russian
/

select * from week
/

TODAY     MON       TUE       WED       THU       FRI       SAT       SUN
--------- --------- --------- --------- --------- --------- --------- ---------
16-¿¿¿-02 20-¿¿¿-02 22-¿¿¿-02 23-¿¿¿-02 22-¿¿¿-02 22-¿¿¿-02 22-¿¿¿-02 20-¿¿¿-02

last give me wrong information...

Thanks!

Followup:
Yes, well, do you have russian language supported installed?  Are the required
NLS files for russian installed on your machine -- did you install them when you
installed Oracle?

Rating: 5
Next_day question April 23, 2003
Reviewer: Steve from nyc, usa
Hi Tom,

I use Next_day(dd, 'SAT') to get the coming saturday.
but I want to return the date if it is already Saturday?

e.g.

Next_day('4-apr-2003, 'SAT') = 4/5/2003
Next_day('5-apr-2003, 'SAT') = 4/5/2003

Thanks!

Steve

Followup:
just subtract 1 from the date

select next_day( DATE-1, 'SAT' )

that way -- on sat, the input value will be friday and you'll get that saturday
back again...

Rating: 3
May 11, 2003
Reviewer: A reader
I'm facing a strange effect with the following queries:

When I issue

select trunc(DateVal, 'YYYY') + RowNo WeekDate
from  (select to_date('01.01.2003', 'dd.mm.yyyy') + RowNo DateVal, RowNo from
table(GetNumTab))
where  to_char(trunc(DateVal, 'YYYY') + RowNo, 'DY') not in ('SA', 'SO')
  and  trunc(trunc(DateVal, 'YYYY') + RowNo, 'YYYY') = trunc(DateVal, 'YYYY')

I get the ORA-exceptions 01846, 01841 and 01002 after a while (one minute or so,
a few hundred records fetched).
if I remove condition "trunc(trunc(DateVal, 'YYYY') + RowNo, 'YYYY') =
trunc(DateVal, 'YYYY')" from the where clause the error disappears. GetNumTab is
a pipelined table function that simply returns 0, 1, 2, ...:

function GetNumTab return t_tabNum pipelined
is
  i        number   := 0;
  v_tabNum t_tabNum := t_tabNum(t_objNum(i));

begin
  loop
    pipe row(v_tabNum(v_tabNum.LAST));
    v_tabNum.Extend;
    i := i +1;
    v_tabNum(v_tabNum.LAST) := t_objNum(i);
  end loop;
end GetNumTab;

what 's going on, what 's wrong here?

Followup:
I would suspect that under the covers you are running out of RAM as you are in a
infinite loop allocating a collection to be larger and larger on each iteration.

Look at that code -- tell me, what is the point?  what is the goal here?  That
function plus the query on that function would run forever with no end on a
machine with infinite ram.

My guess, you don't have infinite ram.

Not knowing what t_objNum(i) is makes it sort of hard as well -- incomplete
examples are not excessively useful.

Rating: 2
May 11, 2003
Reviewer: A reader
this is t_objNum's definition:

create or replace type t_objnum as object(RowNo integer);

the function's purpose is just to produce numbers. although this could be
achived without extending the collection I decided to add new entries (so the
collection grows). but isn't it "funny" that I only run into problems, if I
restrict the numbers of executions with that above mentioned clause? ressource
consumption should be finite in that case?

Followup:
I could have guessed its structure, it is its content that would be interesting.

It is not "funny", you have an infinite loop.  You have a query that never
STOPS.  What did you expect?  You increase the amount of ram you gobble up with
every iteration.  All that means is on your machine it takes a couple of minutes
to gobble up all memory.

I don't know what you mean by if I restrict the number of executions" -- you
have NOTHING in your example that would ever restrict the number of executions
-- you have a classic infinite loop with no way out of it -- the query will
never break out (no "and rownum <= N" in it), the loop will never end.

Nothing surprising here.  infinite loops with no way out....

Rating: 3
May 12, 2003
Reviewer: A reader
you are absolutely RIGHT, it's my fault: "trunc(trunc(DateVal, 'YYYY') + RowNo,
'YYYY') = trunc(DateVal, 'YYYY')" does NOT reduce the number of rows returned
from the INLINE VIEW at all.

but nevertheless its EXISTENCE (which in fact does affect the number of rows
returned by the outermost query) seems to cause the error. I would expect this
to be true in the other scenario (as well). why doesn't the query without
"trunc(trunc(DateVal, 'YYYY') + RowNo, 'YYYY') = trunc(DateVal, 'YYYY')" run
into the same problem?

thank you very much for your patience.

Followup:
are you overflowing the date in your infinite loop (before running out of ram)

  1  select trunc(DateVal, 'YYYY') + RowNo WeekDate
  2     from  (select to_date('01.01.2003', 'dd.mm.yyyy') + RowNo DateVal, RowNo
  3              from  (select 9999999999 rowno from dual )
  4           )
  5    where to_char(trunc(DateVal, 'YYYY') + RowNo, 'DY') not in ('SA', 'SO')
  6*     and trunc(trunc(DateVal, 'YYYY') + RowNo, 'YYYY') = trunc(DateVal,
'YYYY')
ops$tkyte@ORA920> /
    and trunc(trunc(DateVal, 'YYYY') + RowNo, 'YYYY') = trunc(DateVal, 'YYYY')
                    *
ERROR at line 6:
ORA-01841: (full) year must be between -4713 and +9999, and not be 0

I just used dual on line 3 to show you but one of the inifinite values you are
returning that would overflow the valid range of dates.

Rating: 3
May 12, 2003
Reviewer: A reader
very good point: that 's one of the errors I got.
I'd better stop asking now why/how the presence of that "special" condition in
the where clause of the outermost query causes its execution to fail. I've to
assume that this additional condition forces ORACLE to loop until the overflow
occurs to generate the whole base set. in the absence of that condition it seems
that ORACLE does NOT try get ALL rows from the inline view first (in order to
apply the predicates to them afterwards).

would you agree? is this a valid conclusion?

thank you for all your help!

Followup:
there is nothing magic or surprising about it.

You have a routine that generates an infinite set of numbers.

These numbers get really big.

When you added that really big number to a date -- you overflowed the date, made
it invalid --> bamm, error.

The entire base set is fortunately never generated.  Lets say we generated 100
BILLION per second, it would take

ops$tkyte@ORA920> select (rpad('9',38,'9')/ (1000000000*100))/60/60/24/366 from
dual;

(RPAD('9',38,'9')/(1000000000*100))/60/60/24/366
------------------------------------------------
                                      3.1623E+19

that many YEARS to generate the inline view.

I do not agree.

The only thing we agree on is this:

a) you have an infinite loop
b) this is totally inappropriate
c) it is a bug in your code

but beyond that -- I don't seem to be translating very well here.  Everything
I've seen so far is

a) predicable (add a big number to a date, get an error)
b) infinite loops with no possible exit conditions -> bug

Rating: 3
May 14, 2003
Reviewer: A reader
... and so I'll never know WHAT prevents ORACLE from trying to reach infinity in
case I omit that additional condition ...

I'll now fix the bug.

but please let me ask two last questions related to table functions:

1st: is the order of rows returned predictable (when no order by clause is
specified)?

2nd: there seems to be no need to let the internal collection GROW. in my case
it works fine to NOT extend that collection but "reuse" the one and only entry
over and over again to pipe its content out. can one rely on such behaviour?

thanks for all your help.

May 12, 2003 -- Thanks for the question regarding "loading millions of records - speed/scalability", version 8.1.5
You Asked
hi tom:

I wanted to know whether there was any other way, faster than
SQLLOADER (Direct & Parallel) to load data into Oracle tables.
I need to come up with a strategy to load literally 100's of
millions of records into 8-10 field tables in batch as quickly
as possible. I am aware of strategies like splitting data files,
loading them with concurrent processes etc.

However, are there 3rd party tools that have
strategies/methodologies to load this data faster than the
native Oracle utility of SQLLOADER.

I realize this is a very generic question without much detail,
but if you could throw some suggestions I would greatly
appreciate it.

Thanks in advance !
and we said...
I would say -- no, there is nothing I am aware of that is faster
then SQLLDR in parallel direct path mode.  Since it writes
database blocks directly, without going through the database
engine, it will be faster then other 3rd party tools which do
not have the ability to write directly to the datafiles.

If you are concerned about downtime on a live system or
impacting a live system -- you could investigate transportable
tablespaces.  These would let you load onto a "non-live" system
and then detach the already formatted datafiles (including
indexes) and attach them to an existing instance in very little
time.
Review & Followup
Rating: 5
November 27, 2002
Reviewer: A reader
Would you prefer using parallel within the sqloader or running multiple parallel
streams of sqlldr on the o/s level? If we have a DOP set on the table level,
will sqlldr automatically invoke parallel load?

We are discussing on creating load scripts for our datawarehouse. Some of the
DBAs say, we should not use parallel caluse at all, instead run parallel streams
of sqlldr. We have 36 partitions to load and each of them will be loaded from a
seperate datafile. So what would be the best way to load them, we are planning
on doing 36 parallel streams of sqlldr (without using parallel on sqlldr command
line). Thanks for your valuable suggestions.

Followup:
External tables with CTAS or direct path inserts can (and do) use the DOP in 9i
and above.

SQLLDR, as a command line scripting tool, would have to be SCRIPTED in order to
do parallel loads.  You figure out how many, you figure out how to start them
all up, you split up the inputs.

if you are loading into 36 segments (partitions) and you don't want to parallel
load into each segment, don't use parallel.  sounds like you don't want parallel
in your case, you'll parallelize over segments instead of within a segment which
is just dandy.

Rating: 5
November 27, 2002
Reviewer: A reader
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76955/ch08.htm#1665
discusses just that.

      Rating: 5
Sqlldr during
      OLTP  December 17, 2002
Reviewer:  Sandra

Hi Tom,

I have a large table (around 3 GB) with OLTP users using its indexes for
adequate performance. Without these indexes, these queries take far too long.

However, at 1:00 am daily, I have to load (sqlldr) the records for that day -- a
total of 40 thousand rows.

Up to row 20.000 load time is ok, but after that it gets really slow. (Why does
it happen ?)

I thought of having the indexes dropped during the load, but then OLTP users
would time out via Web.

What would be a common solution for that concurrent load vs oltp situation ?

We are using 9i. I thought of list-partitioning the table into 12 months, so we
would have local indexes, which would be less complex (smaller) and load time
would be less affected. What do you think of that ? Is it a good workaround ?
Can you give me some reference or article on that ?

Thank you, Tom.

          Followup:

look in your alert log and see if you see any "cannot allocate new log"
messages.

40k rows is nothing - your logs are probably too small (checkpoint not complete,
cannot allocate new log) or you flooded arch with more then then he can do
(archival required, cannot allocate new log) and need more log files to get your
through this busy period.

a load is just like a really fast typist in this case -- 40k rows is nothing.  I
seriously doubt it is your indexes here - look at the alert.

      Rating: 3
So there is a
      BAD file....but what about records successfully loaded
      ?  May 12, 2003
Reviewer:  Robert  from
      PA
Tom,
So the BAD file tells me what records failed the load....
But is there a way I can retrieve/output the records that are just successfully
loaded ?
Thanks

          Followup:

unless you "tag them" somehow (say with sysdate or whatever) -- no, not really,
they are just rows in a table.

May 13, 2003 Matt
      -- Thanks for the question regarding "Local Nest Table Usage and
      Performance", version 8.1.6

      You Asked
Tom,

Your example on using nested tables to pass result sets
<
http://asktom.oracle.com/pls/ask/f?p=4950:8:25870::NO::F4950_P8_DISPLAYID,F4950_P8_B:246014735810,Y>
gave me an idea to use locally defined nested tables as result set staging
tables in PL/SQL routines(packages in out case).  We have processing in our
system that typically requires result set staging  tables that we use to
calculate values that we then apply to the transaction tables.  For staging
tables I have typically set up physical tables that the programs then reference.

I have a couple of questions reguarding there usage and performace.

Also .. one primary reason that I am considering using local nested tables is
that I can create individual copies of staging tables without have to create
staging tables in all users schemas .. or use somewhat complex dynamic SQL to
create individual staging tables on the fly.

1)I plan on creating local nested tables that range between 1MB - 150MB in size.

 Where does oracle get the space to store the nested table structure?(PGA)  Are
they stored in RAM or in TEMP space?

2)I have create an example package to compare performance between select queries

on a identically structured physical table and nested table.  I load each
structure with approx 90K rows and perform counts on the entire structure.  Its
takes aprox 3 times as long to fully scan the nested table(approx 9 secs for
full scan) as opposed to the physical tables(approx 3 secs).   The same is true
for individual record searches.   I'm using the syntax :
select * from TABLE ( CAST (MYTable as MYTABLEdef)) as A

Interesting enough Sqlplus's autotrace facility does not show any physical I/O
for the nested table so I am wondering how it can be so much slower?  Also  I
assume there is no way to index a local nested table.. only a nested table
stored in a database column right?

3)  Finally, With my tests so far I am seeing some inconsistint result sets when

I use a locally nested table in a query that has a correlated sub-query with  a
physical table. Do you know of any limitations with using the local nested
tables in correlated queries or any other type of queries.. I read something in
the docs that locally nested tables will negate the use of parallel query with
certain result sets

      and we said...
1) 150meg -- ouch.  not good.  It'll be stored in the PGA which is in the
process heap of a dedicated server of the LARGE_POOL under MTS in the SGA.  They
are always memory structures.

2) the database knows how to scan buffers.  It has to convert all of your nested
table variables into buffers.  There is no way to index a nested table variable,
correct.  The nested table trick isn't scalable to 1000's of items, I would deem
this an inappropriate use.

3) I know of no limits.  I'm not certain what you mean by inconsistencies
though.

I would suggest GLOBAL TEMPORARY tables with ON COMMIT PRESERVE ROWS.  They
offer many of the benefits you are after without the issues of above.  the will
be stored in the buffer cache like anything else.  They can be indexed.  they
will be swapped to disk.  If you mostly INSERT and READ them -- they generate
little to no ROLLBACK (and hence no redo).  If you update/delete them they will
generate rollback but only 1/2 as much REDO for the updates as a normal table
would.  You would only need to install them ONCE per database, not once per
schema as you can only see your data (you get your OWN extents and no one will
ever see your data)..
Review & Followup

      Rating: 5
Good Info on
      Local Nested Tables Internals  March 27, 2001

Reviewer:  Matt Perry  from Pa USA
Thanks for the info.. Global Temporary Tables are exactly what I was trying to
implement with Local Nested Tables..  Its very difficult to keep up with all the
new features of Oracle... Thanks fot the Tip

      Rating:
      5
  March 28, 2001
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating: 4
Nested Tables
      As A Way Of Reproducing "Realms" or "Areas" Concept In Oracle CODASYL
      DBMS.  May 12, 2003
Reviewer:  Fred Shimaya
       from Irvine, CA
We have several Oracle "CODASYL" databases that contain archival data.  We would
like to load the data into Oracle 8/9i databases.  Would Nested tables be a good
way to reproduce the data that are stored in "Realms" or "Areas" aside from
creating normalized structure?  For instance, each area in CODASYL can be turned
into a table.  Each "Record" in the CODASYL area can be turned into a nested
table.  I am hoping you are familiar with the archaic CODASYL to answer this
question.  Just curious.

          Followup:

I'm very familar with area variables in PL/I (which by the way are still what I
count among the coolest of all language features and something I've never seen
in any other language -- PL/I is still my favorite).

I do not like nested tables/varrays as persistent storage mechanisms.  You can
do better all by yourself with parent/child relationships.  If you can give me a
more concrete example, I'd be glad to take a look -- but I've not used CODASYL
myself so the terminology is not familar.

      Rating: 4
Nested Tables
      As A Way Of Reproducing "Realms" or "Areas" Concept In Oracle CODASYL
      DBMS  May 12, 2003
Reviewer:  Fred Shimaya
       from Irvine, CA
Well, in Oracle DBMS, you have data storage areas that are the data files.
These are called Areas.  Within each Area file, you can have what are called
Records.  These are like tables.  So, for example, an area called CUSTOMER can
contain three Records:  CUSTOMER_ACCOUNT, CUSTOMER_ADDRESS, and
CUSTOMER_HISTORY.  In relational databases, these can be considered tables in a
way.  There is another term called Realms.  You can break up an area file into
more than one storage file.  So, because the storage file itself is too large on
one disk, we break up CUSTOMER logically into two realms.  There are now two
area (storage) files, on two separate disks, for the logical name CUSTOMER.  But
the Area files within the metadata can be, for instance, CUSTOMER_1 and
CUSTOMER_2.

Sorry for the long-winded explanation, but aside from designing a totally
relational database to handle this (which is the most sensible thing to do), it
would seem nested tables can be used in this fashion.  Each record in an area
though is huge in size (one is over 7 gigs in our case).

I too miss some of the benefits of "older" languages.  VMS was one my favorite
operating system.

          Followup:

So an AREA is a SCHEMA in Oracle "create user CUSTOMER" and then "CREATE TABLE
CUSTOMER.CUSTOMER_ACCOUNT"....

Realms sound a little like partitions, alot like them actually.

I don't see nested tables as being a fit at all.  areas are schema, realms are
either

o ignored
o implemented as partitions

You might want to check out:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-CON
the concepts guide, it has a nice chapter on how we've divorced the concepts of
physical storage mechanisms from the segments (tables etc) themselves.  There is
no tie in between a table and a file.

We have tablespaces.
Tablespaces have 1 or more files, you can add files over time as you like.

You create segments in tablespaces.  Segments include for example:
o table segments
o index segments
o partition segments

A table created in a tablespace will span the 1 or more files that make it up.

      Rating: 4
Oh By The
      Way...  May 13, 2003
Reviewer:  Fred Shimaya
       from Irvine, CA
I was bummed out that your Beginning Oracle Programming book is out of print.
Still, I will enjoy your One-On-one...

      Rating: 5
Very
      Helpful  May 13, 2003
Reviewer:  Fred
      Shimaya  from Irvine, CA
Thanks!

May 13, 2003 Tom -- Thanks for the question regarding
      "authentication and connection pooling at a Java middle tier", version
9iAS

      You Asked
Hi Tom -- and thank you for your site and help with my previous questions.

We use fine-grained access control in our database.  Our security is based on
either the user or a role the user is assigned to, and the fine-grained access
control works perfectly in both SQL*PLUS and our client-server apps--both of
which log directly into the database so we know incontrovertibly who is using
the database.

Now, I'm trying to develop a middle-tier app that does the same thing.  I know
that I can do this with OCI and CONNECT THROUGH.  However, I don't know how to
do it with a browser-based app calling JSP, JSERV, OSE or PSP.  If I make a
connection with a static user, say USER_INTRANET, then my application context
belongs to USER_INTRANET.  If I make a connection with a username/password
entered from the browser, then I end up creating a connection for each
individual user and don't get the benefits of connection pooling.

I apologize if you've already written to this question, but I can't find
anything on doing this in JAVA.

Thank you,
Tom

      and we said...
What I do with this is to NOT use the USER psuedo column directly but rather use
an application context.  This is not java specific, this works with any middle
tier solution that logs in using a common username.

I would create a package and context:

create or replace context my_ctx using my_pkg;

create or replace package my_pkg
as
   procedure set_ctx_val( p_name in varchar2, p_val in varchar2 );
end;
/

create or replace package body my_pkg
as

  procedure set_ctx_val( p_name in varchar2, p_val in varchar2 )
  as
  begin
     dbms_session.set_context( 'my_ctx', p_name, p_val );
  end;
end;
/

Now, where I would normally use the USER psuedo column -- I will use:

  nvl(sys_context( 'my_ctx', 'username' ),USER)

That is -- if the sys_context value of USER is set -- I use it, else I use the
USER psuedo column.

Now, I can grant access on this pacakge MY_PKG to the finite set of "application
schemas" that need to log in  -- your middle tier user for example.  Your middle
tier must *know* who is talking to it (they have logged into your application in
some fashion) so your middle tier can then call:

  begin my_pkg.set_ctx_val( 'username', ? ); end;

It is doing a "set user" call if you will.  Your fine grained access control
will now see a value in sys_context( 'my_ctx', 'username' ) and will use that.
Since you control access to this package, allowing only priveleged users to
execute it -- this is "safe" (as long as you trust that account, which you have
to since everyone is logging in with it).

You should go one step further and make sure the predicate:

   if ( nvl(sys_context('my_ctx', 'username', user) = 'USER_INTRANET' ) then
        return '1=0';
   end;

is returned IF the user in the database is your middle tier user and the context
has not been set.

Review &
      Followup

      Rating: 4
Excellent
      response  July 09, 2001
Reviewer:  Tom
      Wurzbach  from Florida
Thanks for the info.  This is exactly what I was looking for.

      Rating:
      5
Excellent  May 24, 2002
Reviewer:
      Alessandro Giannitelli  from Italy
This is a good response.

      Rating: 4
opposite
      situation: identify individual from one APPUSER   September
      18, 2002
Reviewer:  June  from MD USA
while this was an good answer to original situation described, I have different
scenario here:

we have production in place, where all users will access Oracle database via one
application user account: APPUSER. now we would like to allow different real
user see different data based on some criterial - should be a good exercise of
FGA. all users information are stored in same database under METADATA schema,
while application data is under APPL schema accessed by APPUSER account.

once the user hit the production URL, persistent file will be created on web
server in XML format (using information in METADATA) instead of cookie,
including real user_id, which potentially, could be used to identify the real
user for security policy implementation.

I was thinking to have another application user account such as USERA, USERB
with different real user associated,  and assign security policy based on
session_user which should be easy and straightforward to implement in database.

however, since this has to change user registry on web server and may also have
some other technical issues involved, I have to explore the possibility to use
application context.

As I described, we did have real user information stored in XML format. How can
I extract information from there and apply to my application context?  any time
user hit SUBMIT button, there will be a call to Oracle database via written VB
component. will this application context be consistent during individual user's
web browsing?

any suggestion/recommendation will be appreciated.

          Followup:

an application context in 8i is specific to a session -- so as long as the VB
component maintains a persistent connection to a single session -- yes.

In 9i, we added "global application contexts" which can migrate from session to
session with a user and persist across sessions.  These might work better for
you.

      Rating:
      5
  December 16, 2002
Reviewer:  Igor
      Racic  from France
Hi, Tom

 I'm trying to see if it's possible to implement FGAC
on existing application on Oracle 8.1.7.

 In 2-tier mode, users log with their own account. It works with no problem.
 Using Tuxedo/Clarify on middle tier, Tuxedo holds certain number of connections
which he use to execute queries of all users against Oracle.

 Correct me if I'm wrong:
Because Tuxedo doesn't reconnect for new session, we could not use FGAC -
because connections are static. So returned predicate could not be reliable -
caching problem.

 Thanks for your answer and for your site.

                                           Igor

          Followup:

If tuxedo sent over the actual identity -- sure you could.  Eg: tuxedo calls a
stored procedure that ONLY the tuxedo account is allowed to call.  This stored
procedure is passed the identity of the user.  This stored procedure stuffs that
identity in to the sys_context environment.  Your fgac drives off of that...

      Rating: 4
Why don't we do
      like this?  December 17, 2002
Reviewer:
      Tony  from India
I'm planning to do the following for my web application:

I create a centralized schema for my application. Then I create as many
individual users for the application in the database itself. So, the end users
will login from browser using their own username (schemaname) and password. For
these end users, I'll write a pre-logon trigger to set current schema to the
centralized application schema. If required, I'll create & assign roles for the
users.
Is it a correct approach Tom?

          Followup:

why -- won't they just be running stored procedures?  why give them access to a
schema at all.

      Rating: 5
what about FGA?
        December 19, 2002
Reviewer:  Joe
       from OH
Tom,

Doesn't FGA depend on the fact that you have real database users accessing the
data?  The stuff I have read about proxy authentication in 9i and what is taught
in the 9i Security course says you have to use OCI or thick jdbc to implement
non - apps_user/application context solutions.  Does this mean web apps can't
use built in FGA capabilities? The original poster referred to that type of
solution with the "alter user bob grant connect through appsuser" syntax, but
you directed him/her toward the application context solution.  Would you than
roll your own FGA storing details somewhere based on attributes in USERENV
namespace?

Also can you put the link back to the j2ee asktom equivalent site?

          Followup:

thick jdbc = "i am a web application", virtually ALL thick jdbc apps are middle
tier apps.

the question was asking about FGAC -- not FGA.  FGAC -- we just need the
identity pushed down for us, and the policy function can make use of that.
using n-tier proxy authentication would be even BETTER.

      Rating:
      4
curious  December 19, 2002
Reviewer:
      Mike Yang  from Jersey City, NJ USA
Tom,

Since an application context in 8i is specific to a session, why isn't it called
"session context"?  I got confused when I first encountered this concept.  Just
wonder.  Thanks.

      Rating: 5
against about
      caching  January 20, 2003
Reviewer:  Igor
       from France
Hi

 What makes confusion in all this story
 is that returned pradicate
 should be constant for a session.
 Is your approach to
 avoid to put
         if sys_context( 'abc', 'username' ) != 'ABC' then
              return '1=0';
         else
              return '';

 but to do
  return 'sys_context( ''abc'', ''username'' ) != ''ABC'' '

 Is that idea to avoid caching problem, or
 did I miss something ?

  Thanks for your clarification.

                                              Igor

          Followup:

It no long holds true.  that was true in 815/816 but in 817 -- if a context
value is changes, the predicate policies will be re-evaluated during the next
execute.

      Rating: 5
Thanks for your
      answer  January 20, 2003
Reviewer:  Igor
       from France
Hi
 Is it documented somewhere that difference in 8.1.7 ?

                         Igor

          Followup:

it was never documented that it worked the other way -- so there is no anti
documentation, no.  Just a change in the behaviour.

      Rating: 4
Connection
      pooling   May 13, 2003
Reviewer:  Tony

Hi,
  We have created separate user name and password for each users in the oracle
database(8.1.7) for our JSP application, so there are hundreds of schemas
accessing one central schema that has the data. I've some doubts here.

1) What I heared is that connection pooling is meaningful when we have only one
user(schema) name and password used by all. Is it required to create connection
pooling in the middle tier as, in my case, each user is given a separate user
name?

2. As its known, in connection pooling, db connections are created and retained
by the middle tier for reuse. Will the session id be same or different when the
connections are reused?
If the session is reused, what will be the impact on session specific global
temporary tables being reused by different users with same sessionid?

          Followup:

1) not so, in Oracle we have ntier proxy authentication which fully supports
connection pooling AND allows you to have separate authorization schemas in the
database so you can still

o use grants instead of writing your own insecure security layer
o use auditing
o use fine grained access control
o and everything else...

http://tahiti.oracle.com/pls/db92/db92.drilldown?remark=&word=proxy+authentication&book=&preference=

2) depends on the implementation.  you should consider using "on commit delete
rows" tables in order to avoid issues and making each "page" a transaction.
Don't use the sessionid as a unique id, use a sequence to generate one or have
the middle tier assign a sessionid for you.

May 13, 2003 Jon
      -- Thanks for the question regarding "DB Blocks", version 8.1.7

      You Asked
Hi Tom,

Again, a question raised from your book.  Actually a few questions, all on the
same topic.

If the block is the lowest level of data segmentation in Oracle, how does Oracle
find a row within a block.  A rowid, as I understand it, refers to a block.  Is
there some offset to a row within a block that is stored, or is the block just
sequentially scanned in memory to find out where each row starts and ends.

And assuming there are many rows per block, how does Oracle know what row is the
correct one?  (Thinking of an index storing a rowid, goes to data block, then
what???)

Depending on the answer to the above, does this mean that there is a trade off
to consider when using larger block sizes. e.g. to actually find a row within a
block may take longer with a larger block size, and so adversely effects the
performance of nested loop type joins.

Also, when a row is locked, you mention that the block header will hold the
transaction id for the locked row - so again, how does the information in the
header refer to an individual row in this case.

In your book, you also made a comment that there could be rows from more that
one table in a block - is that only within a cluster?  Is there any other
situation that may happen?

Thanks!

      and we said...
A rowid has in it

o file
o block
o row (slot on block)

in it.  the file/block get us to the block.  The slot is an index into a table
directory on the block header.  That tells us where the row is on the block
itself. see

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96524/c03block.htm#2601
So, no, the larger blocks do not make it "harder" to find the row.

Only clusters support more then one table in a block.
Review &
      Followup

      Rating:
      5
Excellent!  May 12, 2003
Reviewer:
      A reader
What is limit on the number of rows that can fit on a block?  I assume the row
directory must have a max number of entries it can hold.

          Followup:

varies by block size.  730 some odd rows with 8k

to find out:

create table t ( x int ) pctfree 0;
insert into t select 1 from all_objects where rownum < 5000;
commit;
select max(count(*)) from t
group by dbms_rowid.rowid_block_number( rowid );

that'll be it.

      Rating: 4
Size of
      Table  May 13, 2003
Reviewer:  Praveen
       from Mumbai India
Hi Tom,
    I am regular reader of this site and always learn something new from here.
Here is one small doubt regarding blocks and tables.

I created a table with storage clause specifying initial, next as 1k and
pctincrease 0. The initial size allocated to the table should be 8K(as initial
and next will be allocated in same block of 8K). But the value is 16K which is
double. Does this mean that initially 8K is allocated to segment header?
So actual space = segment header(8K)+Initial extent(8K as 1K < 8K the lowest
level of allocation)

Here is my example :

SQL> create table t(x varchar2(10))
  2  tablespace users
  3  storage(initial 1k
  4          next 1k
  5          pctincrease 0);

Table created.

SQL> select extent_id, block_id, bytes
  2  from dba_extents
  3  where segment_name = 'T'
  4  and   tablespace_name = 'USERS';

 EXTENT_ID   BLOCK_ID      BYTES
---------- ---------- ----------
         0     162443      16384

Thanks once again,
Keep up the good work that you are doing for the oracle community.

Regards,

          Followup:

yes, one block is "borrowed" by us to manage the extent map for the segment.

I frown on using storage clauses like that -- initial, next, pctincrease.
Consider them obsolete and just use locally managed tablespaces.

May 13, 2003 Peter -- Thanks for the question regarding "Index
      - Scattered, Random Reads", version 8.1.7

      You Asked
Hi Tom,

I tried to reproduce what you did on p279 ~ 281 of your book
about the effect the scattered random reads of an index has
on the system performance.

The example below (from your book) shows that logical I/O's
is indeed much higher when the "disorganized" table is queried.
However, it also shows that there's really not much difference
in elapsed time!  In fact, I ran these queries many times and
sometimes the query on "disorganized" ran faster. How can this
be possible?

thanks

.......................................................................
CREATE TABLE colocated (x int, y varchar2(2000)) pctfree 0;

BEGIN
   FOR I IN 1..100000 LOOP
      INSERT INTO colocated VALUES
      (i, RPAD(dbms_random.random, 75, ‘*’));
   END LOOP;

   commit;
END;
/

ALTER TABLE colocated ADD CONSTRAINT colocated_pk PRIMARY KEY(x);

CREATE TABLE disorganized NOLOGGING PCTFREE 0 AS
SELECT x, y FROM colocated ORDER BY y;

ALTER TABLE disorganized ADD CONSTRAINT disorganized_pk PRIMARY KEY(x);
.......................................................................

set autotrace traceonly explain statistics
set timing on

SELECT * FROM colocated
WHERE  x BETWEEN 20000 AND 40000;

20001 rows selected.

Elapsed: 00:00:03.25

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'COLOCATED'
   2    1     INDEX (RANGE SCAN) OF 'COLOCATED_PK' (UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       2908  consistent gets
          0  physical reads
          0  redo size
    1800272  bytes sent via SQL*Net to client
      93622  bytes received via SQL*Net from client
       1335  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      20001  rows processed

SELECT * FROM disorganized
WHERE  x BETWEEN 20000 AND 40000;

20001 rows selected.

Elapsed: 00:00:03.45

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'DISORGANIZED'
   2    1     INDEX (RANGE SCAN) OF 'DISORGANIZED_PK' (UNIQUE)

      and we said...
Wall clock time like that is useful to a degree *in a single user system* with
no wait events (all buffered IO, you own the CPU, no waiting on disk to through
things off).  In your case, you probably have a large enough buffer cache, you
did not hit physical reads -- it was all about CPU.  If you look at my example,
the first query (colocated) did 258 physical reads, the second -- 1,684 (buffer
cache not big enough to hold it all)

What you have to take into consideration as well are the other hidden
scalability factors.  Each consistent get will UP the number of latches we get
to process this query.  A latch = lock, lock = serialization device,
serialization device = less scalable.

Using the technique outlined on:

http://asktom.oracle.com/~tkyte/runstats.html
I ran this test:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table colocated ( x int, y
varchar2(2000) ) pctfree 0;
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2      for i in 1 .. 100000
  3      loop
  4          insert into colocated values ( i, rpad(dbms_random.random,75,'*')
);
  5      end loop;
  6  end;
  7  /
PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table disorganized nologging pctfree 0
  2  as
  3  select x, y from colocated ORDER BY y
  4  /
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table colocated         add constraint
colocated_pk    primary key(x);

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table disorganized add constraint
disorganized_pk primary key(x);

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop table run_stats;
Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table run_stats ( runid varchar2(15),
name varchar2(80), value int );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace view stats
  2  as select 'STAT...' || a.name name, b.value
  3        from v$statname a, v$mystat b
  4       where a.statistic# = b.statistic#
  5      union all
  6      select 'LATCH.' || name,  gets
  7        from v$latch;

View created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_start number;
  3
  4      type    Tab is table of varchar2(255);
  5      l_x     Tab;
  6      l_y     Tab;
  7  begin
  8      insert into run_stats select 'before', stats.* from stats;
  9
 10      l_start := dbms_utility.get_time;
 11      select x, y bulk collect into l_x, l_y from COLOCATED where x between
20000 and 40000;
 12      dbms_output.put_line( l_x.count  || ' rows fetched from colocated' );
 13      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 14
 15
 16      insert into run_stats select 'after 1', stats.* from stats;
 17      l_start := dbms_utility.get_time;
 18      select x, y bulk collect into l_x, l_y from DISORGANIZED where x
between 20000 and 40000;
 19      dbms_output.put_line( l_x.count  || ' rows fetched from disorganized'
);
 20      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 21
 22      insert into run_stats select 'after 2', stats.* from stats;
 23  end;
 24  /
20001 rows fetched from colocated
45 hsecs
20001 rows fetched from disorganized
166 hsecs

PL/SQL procedure successfully completed.

showing that the wall clock is better for colocated (using RBO here, both used
index plans, that can be confirmed by tkprof)

Now, more importantly, lets look at the nitty gritty details:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value-a.value run1,
c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

NAME                                 RUN1       RUN2       DIFF
------------------------------ ---------- ---------- ----------
...
STAT...free buffer requested          261       8243       7982
STAT...physical reads                 257       8241       7984
LATCH.cache buffers lru chain         260       8302       8042
STAT...session pga memory               0       8512       8512
STAT...session logical reads          295      20055      19760
STAT...buffer is not pinned co        231      19999      19768
unt

STAT...buffer is pinned count       39787      20019     -19768
STAT...no work - consistent re        264      20032      19768
ad gets

STAT...consistent gets                269      20038      19769
LATCH.cache buffers chains            871      48482      47611

39 rows selected.
ops$tkyte@ORA817DEV.US.ORACLE.COM>

Lots more Latches.  Latches in a single user system are not worrisome.  Add a
user and they drop your speed like a falling brick.

Review
      & Followup

      Rating:
      5
  April 24, 2002
Reviewer:  Peter L
       from CA, USA
Hi Tom,

I'm the person who posted the question. I ran the following script in my
environment and got a very different result!?

thanks

declare
    type    Tab is table of varchar2(255);

    l_start number;
    l_x Tab;
    l_y Tab;

begin
    delete from run_stats;
    commit;

    -- start by getting a snapshot of the v$ tables
    insert into run_stats
    select 'before', stat.*
    from  (
    select 'STAT...' || a.name name, b.value
    from   v$statname a, v$mystat b
    where  a.statistic# = b.statistic#
    union  all
    select 'LATCH.' || name,  gets
    from   v$latch) stat;

    -- and start timing...
    l_start := dbms_utility.get_time;

    SELECT x, y BULK COLLECT
    INTO   l_x, l_y
    FROM   colocated
    WHERE  x BETWEEN 20000 AND 40000;

    dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );

    -- get another snapshot and start timing again...
    insert into run_stats
    select 'after 1', stat.*
    from  (
    select 'STAT...' || a.name name, b.value
    from   v$statname a, v$mystat b
    where  a.statistic# = b.statistic#
    union  all
    select 'LATCH.' || name,  gets
    from   v$latch) stat;

    l_start := dbms_utility.get_time;

    SELECT x, y BULK COLLECT
    INTO   l_x, l_y
    FROM   disorganized
    WHERE  x BETWEEN 20000 AND 40000;

    dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
    insert into run_stats
    select 'after 2', stat.*
    from  (
    select 'STAT...' || a.name name, b.value
    from   v$statname a, v$mystat b
    where  a.statistic# = b.statistic#
    union  all
    select 'LATCH.' || name,  gets
    from   v$latch) stat;
end;
/

col name for a40

select a.name, b.value-a.value run1, c.value-b.value run2,
       ( (c.value-b.value)-(b.value-a.value)) diff
from   run_stats a, run_stats b, run_stats c
where  a.name = b.name
   and b.name = c.name
   and a.runid = 'before'
   and b.runid = 'after 1'
   and c.runid = 'after 2'
   and (c.value-a.value) > 0
   and (c.value-b.value) <> (b.value-a.value)
order by abs( (c.value-b.value)-(b.value-a.value))
/

NAME                                RUN1      RUN2      DIFF
------------------------------ --------- --------- ---------
LATCH.redo allocation                  9        10         1
STAT...enqueue requests                1         0        -1
STAT...messages sent                   1         0        -1
STAT...prefetched blocks             922       923         1
STAT...recursive cpu usage            35        36         1
STAT...redo entries                    6         8         2
LATCH.active checkpoint queue          0         3         3
latch

STAT...db block changes               13        16         3
LATCH.redo writing                     3         6         3
STAT...calls to get snapshot s         3         7         4
cn: kcmgss

LATCH.messages                         4        10         6
STAT...consistent gets              1069      1075         6
STAT...hot buffers moved to he         7         0        -7
ad of LRU

LATCH.undo global data                 2        10         8
STAT...db block gets                  15        27        12
STAT...session logical reads        1084      1102        18
LATCH.cache buffers chains          3362      3391        29
LATCH.session idle bit                40         2       -38
LATCH.cache buffers lru chain       1064      1110        46
LATCH.shared pool                    104        20       -84
LATCH.checkpoint queue latch           2       120       118
STAT...redo size                   19720     20232       512
LATCH.library cache                  660        74      -586
LATCH.row cache objects             3276         0     -3276

          Followup:

What is your version and did you run this in ISOLATION (latches are cumulative
across ALL sessions, the last part only makes sense in isolation.  See

http://asktom.oracle.com/~tkyte/runstats.html
for details)

and always do a full cut and paste of the stuff from sqlplus, like I do.

      Rating:
      3
  May 10, 2002
Reviewer:
      A reader
Here's the result I have. The table structures and data are the same as the ones
above.

Ran in isolation on an Oracle instance on my PC.  The ran this many times just
made sure whatever should be in memory was already in memory.  The question is
why is result I produced so much different from that produced in your
environment given that we have the same table structures and data?

PL*SQL Version: 9.0.1.0.0

SQL> select * from v$version;

BANNER
----------------------------------------------------------
Oracle9i Enterprise Edition Release 9.0.1.1.1 - Production
PL/SQL Release 9.0.1.1.1 - Production
CORE    9.0.1.1.1       Production
TNS for 32-bit Windows: Version 9.0.1.1.0 - Production
NLSRTL Version 9.0.1.1.1 - Production

SQL> declare
  2      type    Tab is table of varchar2(255);
  3
  4      l_start number;
  5      l_x Tab;
  6      l_y Tab;
  7
  8  begin
  9      delete from run_stats;
 10      commit;
 11
 12      -- start by getting a snapshot of the v$ tables
 13      insert into run_stats
 14      select 'before', stat.*
 15      from  (
 16      select 'STAT...' || a.name name, b.value
 17      from   v$statname a, v$mystat b
 18      where  a.statistic# = b.statistic#
 19      union  all
 20      select 'LATCH.' || name,  gets
 21      from   v$latch) stat;
 22
 23      -- and start timing...
 24      l_start := dbms_utility.get_time;
 25
 26
 27      SELECT x, y BULK COLLECT
 28      INTO   l_x, l_y
 29      FROM   colocated
 30      WHERE  x BETWEEN 20000 AND 40000;
 31
 32      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 33
 34      -- get another snapshot and start timing again...
 35      insert into run_stats
 36      select 'after 1', stat.*
 37      from  (
 38      select 'STAT...' || a.name name, b.value
 39      from   v$statname a, v$mystat b
 40      where  a.statistic# = b.statistic#
 41      union  all
 42      select 'LATCH.' || name,  gets
 43      from   v$latch) stat;
 44
 45      l_start := dbms_utility.get_time;
 46
 47      SELECT x, y BULK COLLECT
 48      INTO   l_x, l_y
 49      FROM   disorganized
 50      WHERE  x BETWEEN 20000 AND 40000;
 51
 52      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 53      insert into run_stats
 54      select 'after 2', stat.*
 55      from  (
 56      select 'STAT...' || a.name name, b.value
 57      from   v$statname a, v$mystat b
 58      where  a.statistic# = b.statistic#
 59      union  all
 60      select 'LATCH.' || name,  gets
 61      from   v$latch) stat;
 62  end;
 63  /

PL/SQL procedure successfully completed.

SQL> select a.name, b.value-a.value run1, c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

NAME                                           RUN1       RUN2       DIFF
---------------------------------------- ---------- ---------- ----------
LATCH.channel operations parent latch             0          1          1
LATCH.redo writing                                3          4          1
LATCH.session timer                               0          1          1
STAT...enqueue requests                           1          0         -1
STAT...redo entries                              24         23         -1
STAT...change write time                          1          0         -1
STAT...calls to kcmgcs                           11         12          1
LATCH.enqueue hash chains                         0          2          2
LATCH.undo global data                            4          6          2
LATCH.shared pool                                23         20         -3
STAT...db block changes                          43         40         -3
STAT...recursive cpu usage                       30         33          3
LATCH.enqueues                                    0          4          4
LATCH.redo allocation                            23         27          4
STAT...calls to get snapshot scn: kcmgss          3          7          4
STAT...db block gets                             34         38          4
LATCH.library cache                              71         67         -4
LATCH.checkpoint queue latch                      1          8          7
LATCH.messages                                    1          9          8
STAT...buffer is not pinned count              2177       2186          9
STAT...no work - consistent read gets          2177       2186          9
STAT...table scan blocks gotten                2177       2186          9
STAT...consistent gets                         2185       2199         14
STAT...session logical reads                   2219       2237         18
STAT...redo size                              25396      25576        180
LATCH.multiblock read objects                     0        340        340
STAT...prefetched blocks                          0       1185       1185
STAT...free buffer requested                      1       1356       1355
STAT...physical reads                             0       1356       1356
LATCH.cache buffers lru chain                  2178       3542       1364
LATCH.cache buffers chains                     4500       5898       1398

31 rows selected.

          Followup:

run tkprof and make sure the queries are using the index (the purpose of the
test).

Based on your consistent gets -- I'm going to guess that either:

o you didn't add the constraints and there are no primary keys on X.
o you have analyzed the table and the optimizer is saying "index = bad idea",
which in general it is since we are getting 20,000 rows from 100,000 (20% of the
table)

When I run the example "as is" on 9i on Windows, I get:

...
STAT...no work - consistent re        524      20085      19561
ad gets

STAT...consistent gets                533      20100      19567
LATCH.cache buffers chains           1910      40485      38575

38 rows selected.

When I drop the primary keys I get:

...
STAT...consistent gets               2189       2204         15
STAT...db block changes                64         46        -18
LATCH.enqueues                         26          6        -20
STAT...hot buffers moved to he         36          0        -36
ad of LRU.....

32 rows selected.

which is more in tune with what you have

      Rating: 4
Why couldn't I
      create a view based on v$..  May 15, 2002

Reviewer:  Prince.
Tom,

I tried to create a view similar to your "STATS" view.

SQL> create or replace view stats as
  2    select a.name name, b.value
  3          from v$statname a, v$mystat b
  4          where a.statistic# = b.statistic#
  5  /
        from v$statname a, v$mystat b
                           *
ERROR at line 3:
ORA-00942: table or view does not exist

SQL> select * from v$mystat where rownum < 2 ;

       SID STATISTIC#      VALUE
---------- ---------- ----------
         8          0          1

Why am I getting this problem? What am I missing.

Note: I tried prefixing the view/synonym with sys. / sys.v_$ , but none of them
work.

I am conneted as a user with DBA privileges.

Thanks

          Followup:

see

http://asktom.oracle.com/~tkyte/runstats.html
you have access to v$mystat via a role.

      Rating: 5
colocated or
      disorganized  January 16, 2003
Reviewer:
      A reader
>>CREATE TABLE colocated (x int, y varchar2(2000)) pctfree 0;

>>CREATE TABLE disorganized

On what basis are you saying that the 2 tables created above are colocated or
disorganized?  How can you make out from the create table statement that the
data is going to be colocated or disorganized?

Thank you

          Followup:

in one, we:

CREATE TABLE colocated (x int, y varchar2(2000)) pctfree 0;

BEGIN
   FOR I IN 1..100000 LOOP
      INSERT INTO colocated VALUES
      (i, RPAD(dbms_random.random, 75, ‘*’));
   END LOOP;

   commit;
END;
/
ALTER TABLE colocated ADD CONSTRAINT colocated_pk PRIMARY KEY(x);

so in this table, if you look on the database blocks, you would TEND to find
that:

1,random******
2,random******
3,random*******
....
N,random*******

the data in the table is going to be more or less "sorted" by x.  When we:

CREATE TABLE disorganized NOLOGGING PCTFREE 0 AS
SELECT x, y FROM colocated ORDER BY y;

ALTER TABLE disorganized ADD CONSTRAINT disorganized_pk PRIMARY KEY(x);

we'll find that data is organized in the table like this:

1032,random*****
32,random*****
2323,random*****
522,random*****

That is - the data in the table is NOT sorted by X -- so 1 is not by 2 is not by
3 and so on.

      Rating: 5
colocated or
      disorganized   January 16, 2003
Reviewer:
      A reader

      Rating: 4
Colocated Table
      with CBO  May 13, 2003
Reviewer:  Vivek
      Sharma  from Bombay, India
Hi Tom,

I was also going thru this same example from your book. I have created the
colocated table as mentioned in your book and the primary key constraint was
added as well. When I first executed the query

set autot traceonly

SQL> select * from colocated where x between 20000 and 40000;

20001 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'COLOCATED'
   2    1     INDEX (RANGE SCAN) OF 'PK_COLOCATED' (UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       3156  consistent gets
          0  physical reads
          0  redo size
    1788256  bytes sent via SQL*Net to client
      93627  bytes received via SQL*Net from client
       1335  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      20001  rows processed

It used the Primary Key index with 3516 consisten gets.
When I queried it for

SQL> select * from colocated where x between 20000 and 80000;

60001 rows selected.

Elapsed: 00:01:83.70

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'COLOCATED'
   2    1     INDEX (RANGE SCAN) OF 'PK_COLOCATED' (UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       9462  consistent gets
          0  physical reads
          0  redo size
    5391872  bytes sent via SQL*Net to client
     280317  bytes received via SQL*Net from client
       4002  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      60001  rows processed

It again used the primary key index with 9462 consistent gets.
Then I analyzed the table so that it uses CBO

SQL> analyze table colocated compute statistics for table for all indexes for
all indexed columns;

SQL> select * from colocated where x between 20000 and 40000;

20001 rows selected.

Elapsed: 00:00:56.40

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=133 Card=19991 Bytes
          =1579289)

   1    0   TABLE ACCESS (FULL) OF 'COLOCATED' (Cost=133 Card=19991 By
          tes=1579289)

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

SQL> select * from colocated where x between 20000 and 80000;

60001 rows selected.

Elapsed: 00:01:108.30

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=133 Card=59971 Bytes
          =4737709)

   1    0   TABLE ACCESS (FULL) OF 'COLOCATED' (Cost=133 Card=59971 By
          tes=4737709)

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

The behaviour of both the queries changed and it went for full table scan with
3479 consistent gets against 3156 for 20001 rows and more elapsed time than
previous. For Second Query, 6088 consistent gets against 9462 for 60001 rows
again with more elapsed time.

When it was using index, it was running faster and after analyzing it runs slow.
This is when there is no load on the database but I am afraid when there are
many users connected to the DB, what would be the impact.

Please Explain this behavior.
Thanks and Regards
Vivek Sharma

          Followup:

elapsed timing in sqlplus is of great academic interest but pretty useless.
tell me, what is 1:108.30?  I don't know myself.

You are timing the amount of time it takes sqlplus to process the data, to paint
the screen, the network to transfer, the database to process other peoples
requests.  Most relevant, it times the time to query the v$ tables as well which
in MANY cases takes longer then the query itself.

Use TKPROF and TIMED_STATISTICS.

In the above, the CBO did the right things in all cases and if you tkprof it,
you'll find it is faster.

May 13, 2003 chirayu -- Thanks for the question regarding
      "grand total", version 8.1.6

      You Asked
Dear Tom,
   I have a query like this,

SELECT   vplcode, SUM(rated_flat_amount)/COUNT(*) amt
FROM     tap_rtx_009
WHERE    vplcode = 223
GROUP BY vplcode,  start_d_t, actual_volume,
           rated_flat_amount
HAVING   COUNT(*) > 1

---- result ----

vplcode amt
------- ---
223    2
223    3
223    9
223    1
223    5

what I want is a grand total at the end ie.

vplcode amt
------- ---
223    2
223    3
223    9
223    1
223    5
       20

Is it possible, can analytic functions be of use in this..

thanks in advance

      and we said...
group by rollup does this:

scott@ORA920> select grouping(deptno), grouping(job),
  2         deptno, sum(sal)/count(*) amt
  3  from emp
  4  group by ROLLUP(deptno, job)
  5  /

GROUPING(DEPTNO) GROUPING(JOB)     DEPTNO        AMT
---------------- ------------- ---------- ----------
               0             0         10       1300
               0             0         10       2450
               0             0         10       5000
               0             1         10 2916.66667
               0             0         20        950
               0             0         20       3000
               0             0         20       2975
               0             1         20       2175
               0             0         30        950
               0             0         30       2850
               0             0         30       1400
               0             1         30 1566.66667
               1             1            2073.21429

13 rows selected.

if you are not interested in the intermediate subtotals, a having clause will
filter them out:

scott@ORA920> select grouping(deptno), grouping(job),
  2         deptno, sum(sal)/count(*) amt
  3  from emp
  4  group by ROLLUP(deptno, job)
  5  having grouping(deptno) = 1 OR
  6        (grouping(deptno)+grouping(job) = 0)
  7  /

GROUPING(DEPTNO) GROUPING(JOB)     DEPTNO        AMT
---------------- ------------- ---------- ----------
               0             0         10       1300
               0             0         10       2450
               0             0         10       5000
               0             0         20        950
               0             0         20       3000
               0             0         20       2975
               0             0         30        950
               0             0         30       2850
               0             0         30       1400
               1             1            2073.21429

10 rows selected.

Review &
      Followup

      Rating:
      5
  May 07, 2003
Reviewer:
      A reader

      Rating: 4
but want
      something more  May 08, 2003
Reviewer:  chirayu
      sutaria  from ahmedabad, India
Dear Tom,
   Thanks for the prompt reply..
   but I think I was not able to articulate my question properly..
   moreover I am also learning the analytics, rollup, cube,
   so not well equipped to apply these advanced features appropriately.
   Please help me out.

  If you would look at my query again..

  SELECT   vplcode, SUM(rated_flat_amount)/COUNT(*) amt
  FROM     tap_rtx_009
  WHERE    vplcode = 223
  GROUP BY vplcode,  start_d_t, actual_volume,
           rated_flat_amount
  HAVING   COUNT(*) > 1

  I have four fields in my group by clause,
  so when I modified my query ..

 SELECT   grouping(vplcode), SUM(rated_flat_amount)/COUNT(*) amt
 FROM     tap_rtx_009
 WHERE    vplcode = 223
 GROUP BY rollup(vplcode,  start_d_t, actual_volume,
           rated_flat_amount)
 HAVING   COUNT(*) > 1   AND grouping(vplcode) = 1

 I got the result set as
  GROUPING(VPLCODE)    AMT
    1        7.48142587770495

 I think I had made some mistake somewhere..

 Please also see that I have four columns in the group by clause
 and I am filtering the query to return only those rows who have a count greater
 than one..

 Hope this clarifies my doubt...

          Followup:

add the other columns in (i've never understood why they wouldn't be there)

select out their grouping() values as well

use that output to clearly identify the rows you want.

use that in the having clause.  Believe it or not, that is what I do -- i always
forget if 0 is aggregates and 1's are details or vice versa -- so I run it,
isolate the rows I want -- add the predicate and away you go

      Rating: 5
What difference
      does it make when.....  May 08, 2003
Reviewer:
      pasko
Hi Tom ,
Thanks for a great trick above for filtering unwanted rows :
"having grouping(deptno) = 1 OR
          (grouping(deptno)+grouping(job) = 0)"

I have tried this Rollup stuff myself and i have a question related slightly  to
 this :

1.
Is the order of columns in the rollup statement related
in any way to the group by columns from the select list ?

2.I note that i get different results when i reverse the order of columns inside
the rollup Brackets ..is this expected behavior ?

For Example :
These queries don't seem to return same Results :

select grouping(deptno), grouping(job),
deptno, job ,
sum(sal) amt
from emp
group by ROLLUP(deptno, job)

select grouping(deptno), grouping(job),
deptno, job ,
sum(sal) amt
from emp
group by ROLLUP( job , deptno )  -- reversed order of columns

select grouping(deptno), grouping(job),
job , deptno ,  -- reversed order of columns

sum(sal) amt
from emp
group by ROLLUP( job , deptno )

sorry i don't have scott sample schema setup right here with me. :-(

Thanks in advance.

          Followup:

1) 100%

2) yes.  The first rollup(deptno,job) will sort (rollup sorts) by deptno, job --
giving you subtotals by job within deptno and by deptno and totals for the
entire set.

rollup(job,deptno) will sort by job, deptno -- giving you subtotals by deptno
within job and then by job and then totals

scott@ORA817DEV> select deptno, job, sum(sal),
  2             decode( grouping(job),1,'<===job'),
  3             decode( grouping(deptno),1,'<===deptno')
  4  from emp group by rollup(deptno,job)
  5  /

    DEPTNO JOB         SUM(SAL) DECODE( DECODE(GRO
---------- --------- ---------- ------- ----------
        10 CLERK           1300
        10 MANAGER         2450
        10 PRESIDENT       5000
        10                 8750 <===job
        20 ANALYST         6000
        20 CLERK           1900
        20 MANAGER         2975
        20                10875 <===job
        30 CLERK            950
        30 MANAGER         2850
        30 SALESMAN        5600
        30                 9400 <===job
                          29025 <===job <===deptno

13 rows selected.

scott@ORA817DEV> select deptno, job, sum(sal),
  2             decode( grouping(job),1,'<===job'),
  3             decode( grouping(deptno),1,'<===deptno')
  4  from emp group by rollup(job,deptno)
  5  /

    DEPTNO JOB         SUM(SAL) DECODE( DECODE(GRO
---------- --------- ---------- ------- ----------
        20 ANALYST         6000
           ANALYST         6000         <===deptno
        10 CLERK           1300
        20 CLERK           1900
        30 CLERK            950
           CLERK           4150         <===deptno
        10 MANAGER         2450
        20 MANAGER         2975
        30 MANAGER         2850
           MANAGER         8275         <===deptno
        10 PRESIDENT       5000
           PRESIDENT       5000         <===deptno
        30 SALESMAN        5600
           SALESMAN        5600         <===deptno
                          29025 <===job <===deptno

15 rows selected.

everybody that has sqlplus has emp and dept.
$ORACLE_HOME/sqlplus/demo/demobld.sql.  you can put it into your own schema,
doesn't have to be SCOTT

      Rating: 5
Thanks very
      much...mostly appreciated ....  May 09, 2003

Reviewer:  A reader

May 13, 2003 Christo -- Thanks for the question regarding "How
      to issue DDL (for example drop index) on a busy table.", version 9.2.0.1

      You Asked
Hello Tom,

If you have a very busy table, lot's of transactions at the same time, is there
anyway to perform a DDL on the table, by forcing everyone else to wait?

For example, to add a column or to drop an index, which takes like a second.

I don't want to lock the entire database, just make everyone on that table wait
(without any errors) until I do whatever I have to do.

I tried with "lock table TAB in exclusive mode" and wait until I have the lock,
but after I issue the DDL, the lock is released *before* the operation, and I am
getting again the "resource busy" error.

      and we said...
have you read about dbms_redefinition?  it allows you to reorg objects "online"

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4636779130376
many operations, create index for example, have an ONLINE option.

create index t_idx on t(x) ONLINE;

that will patiently wait for a lock that will be taken JUST BEFORE it begins
(and then released).  It will build the index and then patiently wait for a lock
JUST AS IT ENDS (and then released).  It is done online.

So, dbms_redefinition for those "structural" changes -- ONLINE for others.

Additionally, at the "database" level (not what you asked for but for
completeness) there is a quiesce (needs the resource manager to be in place)
that'll let you do larger scale upgrades by pausing everyone for a bit.
Review &
      Followup

      Rating:
      5
  May 12, 2003
Reviewer:
      A reader

      Rating:
      3
Followup  May 13, 2003
Reviewer:
      Christo Kutrovsky  from Ottawa, ON Canada
Thank you very much for the reply. I have read the example for an online table
redefinition in one of the Oracle magazines.

However, my questions was more oriented to "small" or "quick" changes, like
droping an index. There is no need to copy the entire table into a new one, just
to drop an index. The same for adding a column or making a certain column
larger.

Is there a way to do that "online" without copying the data?

          Followup:

I cannot imagine dropping an index in a production environment where people are
banging on the table..

I know of no way to "drop" an index online.

      Rating:
      2
Re-phrase  May 13, 2003
Reviewer:
      Christo Kutrovsky  from Ottawa, ON Canada
Maybe I did not express my self correctly.

The table is been modified by N transactions. We want to wait for all N
transactions to finish, perform that ddl (drop index), while other transactions
are waiting for us, and then let them go.

So basically not to do it "online" but to make other wait, while you do it. I
would imagine someting like "drop index BLABLA wait;" just like the select for
update works.

Why would you need that? - Well for example you have an index that is no longer
needed.

The only way to do this in a 24/7 production environment is to make an infite
loop with the drop command, until it eventually succeeds, but I don't consider
this to be an ellegant solution.

          Followup:

you were clear.

we don't do that for drop index.

May
      13, 2003 GAURANG -- Thanks for the
      question regarding "Fetch across commit", version 8.1.5

      You Asked
what do you mean by  'Fetch across commit'

      and we said...
  for x in ( select rowid rid, t.* from T ) loop
     update T set x = x+1 where rowid = x.rid;
     commit;
  end loop;

That implicit cursor is fetched from "across a commit".  It is the practice of
keeping a cursor open after committing.  It is a bad practice and is a common
cause of ORA-1555 (the above looping construct in particular)
Review &
      Followup

      Rating: 4
'Fetch across
      commit'  March 07, 2002
Reviewer:  Cheema
       from Los Angeles
I have found that this construct does not give the error in 8i and above.
However, the lock acquired by For Update clause are released by the Commit
statement.

Previous versions used to give the error mentioned.

Cheema

          Followup:

sure it does.  ALL versions of Oracle (even 9i if you use rollback segments or
set the undo_retention too small) will give you this.  100%.

You just ran your test in a database with enough rollback to NOT hit it.

Here is one that shows is definitely can and will happen:

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

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx on t(object_id);

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create rollback segment rbs_small
  2  storage (initial 1k next 1k maxextents 2) tablespace system;

Rollback segment created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter rollback segment rbs_small online;

Rollback segment altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2          for x in ( select * from t where object_id > 0 )
  3          loop
  4                  dbms_transaction.use_rollback_segment( 'rbs_small' );
  5                  update t set object_name = lower(object_name)
  6                    where object_id = x.object_id;
  7                  commit;
  8          end loop;
  9  end;
 10  /
begin
*
ERROR at line 1:
ORA-01555: snapshot too old: rollback segment number 7 with name "RBS_SMALL" too
small
ORA-06512: at line 2

      Rating: 4
Fetch out of
      sequence error  March 07, 2002
Reviewer:
      Mike  from Portland, OR
It appears that you can't fetch across commit on a "For update" cursor.  I tried
it with the following code (using your temp table) and got the fetch out of
sequence error:

SQL> begin
  2      for x in ( select * from t where object_id > 0 for update)
  3      loop
  4         update t set object_name = lower(object_name)
  5              where object_id = x.object_id;
  6              commit;
  7      end loop;
  8  end;
  9  /
begin
*
ERROR at line 1:
ORA-01002: fetch out of sequence
ORA-06512: at line 2

          Followup:

You've never been able to fetch across commit on a FOR UPDATE cursor.  The
commit releases the locks -- cursor gone.

      Rating: 5
RE: Fetch out
      of sequence error  March 07, 2002
Reviewer:
      Mark A. Williams  from Indianapolis, IN USA
From the Oracle documentation (Oracle8i Error Messages):

ORA-01002 fetch out of sequence

Cause: In a host language program, a FETCH call was issued out of sequence. A
successful parse-and-execute call must be issued before a fetch. This can occur
if an attempt was made to FETCH from an active set after all records have been
fetched.

***
This may be caused by fetching from a SELECT FOR UPDATE cursor after a commit.
***

A PL/SQL cursor loop implicitly does fetches and may also cause this error.

Action: Parse and execute a SQL statement before attempting to fetch the data.

HTH,

Mark

      Rating: 4
I dont
      understand  August 21, 2002
Reviewer:
      A reader
hi tom

I am a bit confused about fetch across commit and ora-01555

if I have anonymous pl/sql block such as

begin
  for i in (select rownum numrow from a where rownum < 10001)
  loop
    set transaction use rollback segment rbs1;
    update a set a = i.numrow;
    commit;
  end loop;
end;
/

I can reproduce ora-01555 easily but my question is, isnt the cursor stored in
memory? If it is stored in memory, then before the update starts there is no
need to read the rollback segment rbs1 right?

My understanding about ora-01555 is that the query is reading a consistent image
from rbs, if the query's starting scn is older than the scn in the rollback
segment then ora-01555 occurs.

I mean in the pl sql block I pasted doesnt it work this way?

1. read table a into memory from disk
2. set rbs
3. update the table with new values
4. set rbs again
5. update the table again

and so on till cursor finishes, if it works this way there is nowhere that the
cursor reads rbs then how can ora-01555 be arised?

or it works this way

1. read an array of rows of table a into memory from disk
2. set rbs
3. update the table with new values
4. set rbs again
5. update the table again
6. read next array of rows of table a into memory from disk and repeat 2 and 3
and so on until cursor finishes

          Followup:

No, a cursor is not stored in memory.  Consider this query:

   select * from one_billion_row_table_that_has_1000_varchar2(4000)_columns;

(forgetting for a moment that the syntax isn't quite right ;).  The results are
not precomputed in general, the query is not pre-answered.

So, your basic assumption (table is read into memory from disk) is flawed.  It
works more like the last way you describe.

      Rating: 5
array is random
      I guess  August 21, 2002
Reviewer:
      A reader
thanks for the reply

I guess the size of array reading into memory is random or it follows some
strict data block buffer availability algorithm?

          Followup:

it is based on the request from the client -- how many rows did they ask for in
that call?

      Rating:
      5
5000  August 21, 2002
Reviewer:
      A reader
I set arraysize in sqlplus to 5000, I tried to set to more but I got

SP2-0267: arraysize option 1569325056 out of range (1 through 5000)

what is option 1569325056?

if I was using a GUI Front-end such as forms/discoverer/reports whatever how do
we specify arraysize? setting the highest I guess I yield best performance :?

          Followup:

ops$tkyte@ORA817DEV.US.ORACLE.COM> set arraysize 5000
ops$tkyte@ORA817DEV.US.ORACLE.COM>

ops$tkyte@ORA806.WORLD> set arraysize 5000
ops$tkyte@ORA806.WORLD>

ops$tkyte@ORA734.WORLD> set arraysize 5000
ops$tkyte@ORA734.WORLD> set arraysize 50000
arraysize option 50000 out of range (1 through 5000)
ops$tkyte@ORA734.WORLD> set arraysize 500000000
arraysize option 500000000 out of range (1 through 5000)
ops$tkyte@ORA734.WORLD> set arraysize 500000000000000000000
arraysize option -279969792 out of range (1 through 5000)
ops$tkyte@ORA734.WORLD> set arraysize 500000000000000
arraysize option 1382236160 out of range (1 through 5000)
ops$tkyte@ORA734.WORLD>

what I believe you did was not 5000 but some number much bigger and we put it
into a 2 byte integer -- see the effect with 500000000000000000000 and
500000000000000....

so you are just seeing a wrapped number....

As for the comment "setting the highest I guess I yield best performance " is
like saying "if one aspirin makes me feel better, the whole bottle should fix me
up real good" (yeah, right into the hospital good)

I find 100 to be a "sweet spot", much more -- diminishing returns.  5000, way
too large in my experience.

As for setting arraysizes in various products -- it'll be product specific.

      Rating: 5
hmm why big
      arraysize is bad?  August 21, 2002
Reviewer:
      A reader
if I was doing a full table scan of 100000 rows wouldnt a bigger arraysize give
me better performance than 100....????

I dont see the theory of bigger the array the performance can get worse... :0

          Followup:

Benchmark it for me would you.  Present the results.  See what happens.

Make sure the client DOES something with the rows ok.  That's important (cause
clients do do things with the data).  You want to get an steady flow of "request
to server", "processing by client", "request to server", "processing by client"
and so on.

There are ram considerations as well.  Tell me, which would take less resources
to cache on a client -- 100 row or 100000 rows?  You want to deal with that java
result set?  I don't.  (or try throwing that at VB sometime)

So I run:

set echo on
spool test
set termout off
set timing on
set arraysize 10
select  * from all_objects;
set arraysize 100
select  * from all_objects;
set arraysize 1000
select  * from all_objects;
set arraysize 5000
select  * from all_objects;
spool off
exit

$ sqlplus scott/tiger@ora8i @test

$ grep Elapsed test.lst
Elapsed: 00:00:31.70
Elapsed: 00:00:27.90
Elapsed: 00:00:25.66
Elapsed: 00:00:26.14

No significant difference between 100 and 5000 (except in the amount of ram it
used on the client and server that is)

      Rating:
      5
  August 21, 2002
Reviewer:
      A reader
I forgot to ask.... arraysize sets the number of rows sqlplus fetches from the
database... but that hasnt got anything to do with reading from disk to memory
in server side... no?

disk ========> data block buffer

database ====> client

:-?

          Followup:

Yes it does ;)

Watch the consistent gets:

scott@ORA8I.WORLD> drop table t;

Table dropped.

scott@ORA8I.WORLD> create table t as select * from all_objects;

Table created.

scott@ORA8I.WORLD>
scott@ORA8I.WORLD> set autotrace traceonly statistics;
scott@ORA8I.WORLD>
scott@ORA8I.WORLD> set arraysize 10
scott@ORA8I.WORLD> select       * from t;

37001 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
       4153  consistent gets
        503  physical reads
          0  redo size
    4551787  bytes sent via SQL*Net to client
     411125  bytes received via SQL*Net from client
       3702  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      37001  rows processed

scott@ORA8I.WORLD> set arraysize 100
scott@ORA8I.WORLD> select       * from t;

37001 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
        875  consistent gets
        448  physical reads
          0  redo size
    4175497  bytes sent via SQL*Net to client
      41495  bytes received via SQL*Net from client
        372  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      37001  rows processed

scott@ORA8I.WORLD> set arraysize 1000
scott@ORA8I.WORLD> select       * from t;

37001 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
        544  consistent gets
        465  physical reads
          0  redo size
    4137868  bytes sent via SQL*Net to client
       4532  bytes received via SQL*Net from client
         39  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      37001  rows processed

scott@ORA8I.WORLD> set arraysize 5000
scott@ORA8I.WORLD> select       * from t;

37001 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
        515  consistent gets
        481  physical reads
          0  redo size
    4134478  bytes sent via SQL*Net to client
       1202  bytes received via SQL*Net from client
          9  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      37001  rows processed

scott@ORA8I.WORLD>

Might not neccessarily affect the physical IO in a lightly loaded system -- but
definitely affects the LIO's -- and they are just as important.

      Rating: 4
Where a large
      array size might not be performant  August 21, 2002

Reviewer:  Jim Kennedy  from Portland, OR USA

You can take an empirical approuch and test a variety of situations and find
which is best for your environment.  Thus we move from theory to actuality.
(theory could be wrong)

Reasons why 5,000 array might not be as performant as 100 array.  The memory
allocation algorithium might be more efficient at 100 elements vs 5,000
elements.  For example, to send that information over the wire the server
probably has to allocate a large enough buffer space to ready that data for the
network interface card to send.  It might be very efficient at 100 elements and
not very efficient at 5,000 elements.(larger size, memory allocation routines
might have a sweet spot)  For example, I remember seeing a performance test for
a Cray computer that tested the handling of arrays.  As the array got larger and
larger the test took on a monotonicly increasing saw tooth response time.  (got
longer and longer then all of a sudden would get better, then repeat the
pattern)  This was because the hardware of the machine had a certain memory
stride and as you got farther and farther away from the optimal memory stride
the calculation - adding the elements of the array in order - got less and less
effecient.

Also lets assume that each network packet holds 2 rows.  So for 100 element
array that would be about 50 packets.  For a 5,000 element array that would be
about 500 packets.  It is possible that you might start getting packet
collisions and thus actually generate more traffic and slow things down.

All these are pure theory and the best way is to test it and find where it works
best for your application.  (eg for a big wide table 100 might be too large, but
for a narrow skinny table or result set 500 might be best.)

          Followup:

*Thank you*

well said.

      Rating: 4
then what has
      reading disk to mem?  August 21, 2002
Reviewer:
      A reader
Jim

then what has arraysize to do with reading from disk to sga in the server???? if
it used to set a limit of fetching from server to client?

          Followup:

It is all about buffer gets.  forget physical IO -- thats noise here.  Logical
IOs are expensive (goto www.hotsos.com and read "Are you still using cache hit
ratios?", excellent paper, love it)

      Rating: 4
arraysize is
      set in the call from the client  August 21, 2002

Reviewer:  Jim Kennedy  from Portland, OR USA

I know you can set it from jdbc (prefetch I think) and OCI and probably proc C
and even some ODBC drivers.  So I assume it is telling the server how you are
going to retrieve data - X number of rows at a time.  So I assume the server is
doing something different from single row fetches.  sqlplus is a client program
not part of the server - sure it can run on the same machine, but it is not a
part of the Oracle database.  It is a client tool.

      Rating: 4
Fetch out of
      sequence  September 19, 2002
Reviewer:  Paul
       from UK
Tom,

I understand why this happens, but how would you suggest I get round my problem?
 I have to process a number of files loaded into the database using SQL*Loader
and want to commit after processing each file successfully, so my code
(simplified - I have exceptions etc, of course) looks something like this:

BEGIN
   FOR rec1 IN(SELECT file_id,
                      ROWID
                 FROM file_data
                WHERE Processed = 'N'
                  FOR UPDATE OF Processed) LOOP
      FOR rec2 IN(SELECT *
                    FROM file_records
                   WHERE file_id = rec1.file_id) LOOP
         <Process file records here>
      END LOOP;

      UPDATE file_data
         SET Processed = 'Y'
       WHERE ROWID = rec1.ROWID;
   -- Ideally would like to commit here, but would get "ORA-01002: fetch out of
sequence"
   END LOOP;
END;

          Followup:

loop
   begin
       select file_id, rowid into l_file_id, l_rowid
         from file_data
        where processed = 'N'
          and rownum = 1
          for update of processed;
   exception
        when no_data_found then exit;
   end;
   for rec2 in ( select ..... )
   loop
        process....
   end loop;
   update file_data set processed = 'Y' where rowid = l_rowid;
   commit;
end loop;

      Rating: 5
Thanks a
      million  September 19, 2002
Reviewer:  Paul
       from UK
So simple!
So beautiful!

      Rating: 4
Strang behavior
      ora 8.0.4  September 23, 2002
Reviewer:
      Martin  from Amsterdam, NL
>Followup:
>You've never been able to fetch across commit on a FOR >UPDATE cursor.  The
>commit releases the locks -- cursor gone.

Tom,

We recently upgraded from dbms 8.0.4. We had several packages with a fetch
across a commit, but these always seem to have worked fine. I totally agree
fetching across a commit is bad practice but I don't understand why the packages
have worked until now? Do you have any clue? A bug maybe?

          Followup:

It was a bug (and they didn't "work" before, the "for update" part got lost as
soon as you committed.

      Rating: 5
Difference
      between commit and no commit in terms of RBS usage  October
      16, 2002
Reviewer:  Sam To  from Redwood City, CA

What will the difference be in the following statement if we remove the commit
(assume id is not unique within T) in terms of the query having to read from the
RBS?  When is the system change number updated?  Won't the query still have to
go to the RBS in order to achieve read consistency?  In that case, will we still
potentially run into ORA-01555 even if we remove the commit?

for x in ( select rowid rid, t.id from T ) loop
     update T set x = x+1 where id = x.id;
     commit;
end loop;

If id is unique in T, then will it make any difference in the above statement?

          Followup:

the differences will be (without the commit)

o it will run faster
o it will protect the logical integrity of the data (eg: it will NOT tend to
  corrupt your data as the one with the commit would since it is NOT
  restartable!)
o it will run faster
o it will run without a HIGH probability of hitting ORA-1555

Leave the commit in and it will

o run slooooowwwwwer
o logically corrupt you data when you are forced to re-run it when you get the
  ora-1555
o run lots slower
o it will run with an EXTREMELY HUGELY HIGH probability of a self induced
  ora-1555

unique, none unique  -- matters not.

the commit does NOT belong there.

Search this site for ora-01555

or, if you have my book "expert one on one" i have a ton of examples and
detailed explanation in there.

      Rating: 4
Implied fetch
      across commit  April 17, 2003
Reviewer:
      Damon  from UK
I have come across a case where there seems to be an implied commit (i.e. I
haven't coded it, but it happens anyway)...

for r in (select * from all_snapshots) loop
 dbms_snapshot.refresh(r.name);
end loop;

I expect you'll have a better way of doing this anyway, but until I read about
the fetch across commit issue here I didn't know why I kept getting rollback
segment too old errors - I'm guessing that the dbms_snapshot call does a commit?
 I spent a long time trying to prevent the error by increasing the RBS and not
getting anywhere, but re-writing the loop to build the list of snapshots to
refresh then calling the function with the list seems to have worked.

Thanks

Damon

          Followup:

refresh commits, yes.

you can

declare
   l_names dbms_utility.uncl_array;
begin
   select name bulk collect into l_names from all_snapshots;
   dbms_snapshot.refresh
   ( tab => l_names,
     ...
     atomic_refrehs => TRUE (to do all and THEN commit, => FALSE to commit each
                             as it does it)

      Rating:
      5
Reader  April 17, 2003
Reviewer:
      A reader
"
declare
   l_names dbms_utility.uncl_array;
"

Where can I find information about dbms_utility.uncl_array

          Followup:

vi dbmsutil.sql in $ORACLE_HOME/rdbms/admin

it is the documented datatype you use to pass to this function.  it is just a
table of varchar2's

      Rating: 5
Fetch Across
      Commit  May 12, 2003
Reviewer:  ramks
       from India
Hi Tom,

I have a pl/sql block like following;

begin
for i in (select * from t)
loop
--Call to
procedure A
procedure B
if return_status = 'E' then
  rollback;
else
  commit;
end if;
end loop;
end;

the logic behind is, if any one of above procedure generates error the whole
transaction should be rolled back
otherwise commit. Here I am getting the ORA-01002 error. how to avoid that. If
the above method is bad practice what is the best practice. Here I don't need to
use 'For Update' clause.

Can you help me in this regard. thanks in advance

          Followup:

it means you don't have a block like the above, you actually have a for update
in there.

the best practice:

begin
  for x in ( select * from t )
  loop
      proca;
      procb;
  end loop;
end;

proca and procb would RAISE AN EXCEPTION upon failure (no status codes, that is
so 70's).  Since you do not catch the exception (you cannot possibly recover
from it), it'll propagate to the client and the work performed by that statement
will automagically roll back.

The client will then decide "should I rollback the entire transaction at this
point or should I commit"

      Rating: 5
Fetch Across
      Commit - contd  May 12, 2003
Reviewer:
      ramks  from India
one more thing tom in my above question;

if procedure B generates error, the previous transaction generated by A should
be rolled back and an error record must be created in a seperate table and that
should be commited. then the cursor should select next record.

          Followup:

your example is not complete, something is missing -- whole story is not there.
for update maybe?

      Rating: 5
Fetch Across
      Commit  May 13, 2003
Reviewer:  ramks
       from India
Hi Tom,
sorry for not explaining properly. here i am giving the code;

main procedure:
---------------
procedure main is
x_err_status varchar2(10);
my_exception exception;
begin
for i in (select * from t)
loop
begin
 --call to proc's
 procedure A(id,x_err_status);
 if x_err_status='ERROR' then
  RAISE my_exception;
 end if;
 procedure B(id,x_err_status);
 if x_err_status='ERROR' then
  RAISE my_exception;
 end if;
commit; --If above two proc's went on well
exception
 when my_exception then
 rollback;
end;
end loop;
end;

procedure A is(id in number,p_err_status in varchar2) is
begin
  <some sql stmts>
p_err_status := 'SUCCESS';
exception
  when no_data_found then
  p_err_status := 'ERROR';
   <call procedure Error_proc to log error record>
end;

procedure B is(id in number,p_err_status in varchar2) is
begin
  <some sql stmts>
p_err_status := 'SUCCESS';
exception
  when no_data_found then
  p_err_status := 'ERROR';
   <call procedure Error_proc to log error record>
end;

--Procedure to log Error record

procedure Error_proc is
begin
rollback; (all the previous txn's done by  procedures A & B
insert into <error table with valeus>
commit;
end;

I hope this will enough u to understand my problem. Please help me in this
regard.

          Followup:

use savepoints, it is the rollback, and only if it was the first call that
rolled back and only if I'm guessing right and you were already in a
transaction.

so, what is happening is this:

o before you call main, you did some other insert/update/delete
o you call main, it opens the cursor (which MAY -- or may NOT be dependent on
  the data you inserted/updated/deleted in the above bullet point)
o if you rollback -- you rollback that first bullet point, that INVALIDATES
  the cursor in the second bullet point
o when you next fetch -- error

If you

o before you call main, you did some other insert/update/delete
o you call main, it opens the cursor (which MAY -- or may NOT be dependent on
  the data you inserted/updated/deleted in the above bullet point)
o you commit -- that makes the changes from the first bullet point PERMANENT
o when you next fetch -- success
o any rollbacks won't FAIL this cursor

ops$tkyte@ORA920LAP> create table t ( x int );
Table created.

ops$tkyte@ORA920LAP> begin
  2      insert into t values ( 1 );
  3      for x in ( select * from emp where rownum <= 2 )
  4      loop
  5          dbms_output.put_line( x.ename );
  6          rollback;
  7      end loop;
  8  end;
  9  /
SMITH
begin
*
ERROR at line 1:
ORA-01002: fetch out of sequence
ORA-06512: at line 3

that simulates your error

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> begin
  2      for x in ( select * from emp where rownum <= 2 )
  3      loop
  4          dbms_output.put_line( x.ename );
  5          rollback;
  6      end loop;
  7  end;
  8  /
SMITH
ALLEN

PL/SQL procedure successfully completed.

that shows the insert is needed to cause it

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> begin
  2      insert into t values ( 1 );
  3      for x in ( select emp.*, rownum r from emp where rownum <= 3 )
  4      loop
  5          dbms_output.put_line( x.ename );
  6          if ( x.r = 1 )
  7          then
  8              commit;
  9          else
 10              rollback;
 11          end if;
 12      end loop;
 13  end;
 14  /
SMITH
ALLEN
WARD

PL/SQL procedure successfully completed.

that shows if you commit before you rollback, it won't happen

My advice - commiting in a cursor for loop like that is a heinous operation.
Consider the transactional "conundrum" you are obviously in too -- that first
transaction INCLUDES SOME OTHER OUTSTANDING WORK!!!  You are rolling back --
what?  do you even know?  there is some larger transaction going on here!

Perhaps what you need is a third procedure C that is an autonomous transaction.
it will either commit or rollback - it will be SEPARATE and DISTINCT from your
already established transaction.

      Rating: 5
Fetch across
      commit  May 14, 2003
Reviewer:  ramks
       from India
Hi Tom

thank you very much for your suggession. I have used savepoint's in my proc's.
after i am not receiving that error. anyway thanks for your timely help.

May 13, 2003 Jansen -- Thanks for the question regarding
      "dbms_application problem", version 9.0.2

      You Asked
Thx for your reply, Tom.

But I have some error when I run this script.
Would you explain to me, why?

Best Regards
Jansen

oracle:dev> declare
  2      l_nohint number default
dbms_application_info.set_session_longops_nohint;
  3      l_rindex number default l_nohint;
  4      l_slno   number;
  5  begin
  6      for i in 1 .. 10
  7      loop
  8          dbms_lock.sleep(2);  /* your "process" goes here */
  9          dbms_application_info.set_session_longops
 10          ( rindex =>  l_rindex,
 11            slno   =>  l_slno,
 12            op_name => 'my long running operation',
 13            target  =>  1234,
 14            target_desc => '"msg description"',
 15            context     => 0,
 16            sofar       => i,
 17            totalwork   => 10,
 18            units       => 'loops'
 19          );
 20      end loop;
 21  end;
 22  /
        dbms_lock.sleep(2);  /* your "process" goes here */
        *
ERROR at line 8:
ORA-06550: line 8, column 9:
PLS-00201: identifier 'DBMS_LOCK' must be declared
ORA-06550: line 8, column 9:
PL/SQL: Statement ignored

      and we said...
you need to have been granted execute on dbms_lock.

just put some "process" in there, eg: replace the dbms_lock call with

for i in 1 .. 100000 loop
   null;
end loop;

or something time consuming, say

select count(*) into l_cnt from all_objects;

or whatever.
Review & Followup

      Rating: 2
Getting access
      to dbms_lock would be better   May 13, 2003

Reviewer:  Dave  from Austin, TX USA
any coding of loops for the sake of taking up time will use system resources,
mainly CPU.  I'd recommend begging for access on dbms_lock since sleep doesn't
use much resources and you have direct control over the time delay.

I wish Oracle would have put that proc in dbms_util or something like that,
makes more sense since it is a useful procedure.

          Followup:

this is a DEMO to show the functionality

May
      13, 2003 Amit -- Thanks for the
      question regarding "OLTP Rollback segment planning", version 8.0.6.0.0

      You Asked
Hi Tom,

Could you please clarify the following question about rollback
segments.
I am working on oracle 8.0.6.0.0 (on NT).

Oracle Enterprise Manager help gives the following
recommendations for
creating rollback segments:

1. Allocate one rollback segment for each 10 concurrent OLTP
(online transaction processing)
   transactions and one rollback segment for each concurrent
batch job.

2. keep extents within a rollback the same size by specifying
STORAGE parameters
   where NEXT=INITIAL.

3. It is also recommended that you set the MINEXTENTS parameter
value to 20.
   Because rollback segments are logically regarded as a
circular queue of extents,
   they are required to have MINEXTENTS value of at least 2.
   The more extents the rollback segment has, the less likely it
that a rollback
   segment will require an extent allocation that could be
avoided.

My questions are:

1. Is there any method for calculating what is the "right"
NEXT=INITIAL storage
   parameters for an OLTP system , or for monitoring whether
existing values
   are appropriate ?
2. Assuming there is a lot of RAM in the system, is it
recommended to put
   the OLTP rollbacks in the KEEP bufferpool ?
3. If batch jobs are run against the OLTP database, larger
rollbacks need
   to be used (one per concurrent batch job?).
   In order to avoid getting "snapshot too old" errors, are
there any tips
   for the OLTP rollbacks ?

Thanks,
Amit.

      and we said...
1) Is there a method for calculating what is the right extent
size?

The following advice is from support note 62005.1, "Creating,
Optimizing, and Understanding Rollback Segments"

<quote>
To find out the size and number of rollback segments needed
to handle normal processing on the database you need to do
some testing.  A good test is to start with small rollback
segments and allow your application to force them to extend.
Here are the steps to run such a test:

o  Create a rollback segment tablespace.

o  Select a number of rollback segments to test and create
   them in the tablespace.

o  Create the rollback segments so that all extents are the
   same size.  Choose an extent size that you suspect will
   need between 10 to 30 extents when the segments grow to
   full size.

o  Each rollback segments should start with two extents
   before the test is run.  This is the minimum number of
   extents any rollback segment can have.

o  Only the rollback segments that you are testing and the
   SYSTEM rollback segment should be online.
   Run transactions and load typical of the application.

o  Watch for rollback segment contention.

o  Watch for the maximum size a rollback extends to.

The maximum size any one of the rollback segments reaches
during the test is the size you want to use when
configuring.  This size we will call the "minimum coverage
size".  If you see contention, adjust the number of segments
and rerun the test.  Also, if the largest size requires
fewer than 10 extents, or more than 30, it is a good idea to
lower or raise the extent size, respectively, and rerun the
test.  Otherwise, space may be getting wasted during the
test and this would throw the number off.

At the end of this test, you will have some good base
estimates for the size and number of rollback segments
needed for normal processing.  However, handling large
transactions has not been discussed yet.  Calculating the
segment size needed for these types of transactions as well
as how to meet the requests for the calculated space will be
addressed.

For sizing rollback segment extents, it is strongly
recommended that each extent be of the same size.  The minimum
and maximum number of extents for an individual segment should
be around twenty for best performance.
</quote>

I like to create my rollback segments with around 20-25
minextents and set maxextents to 255 or so.  I place each
rollback segment in its own tablespace and allow each tablespace
to have 1 autoextendable datafile allocated to it.  In this
fashion, the rollback segments will grow as needed, if space is
available on the system -- upto 255 extents.  Periodically
(typically before backups) I manually offline and then drop each
rollback segment, shrink the datafile associated with its
tablespace back to the right size for 20 extents and recreate
the rollback segment.  This allows for the infrequent "large"
transaction to dynamically grow the rollback segment without
having to interject a "set transaction use rollback segment
BIG_RBS" into it.

2) "is it recommended to put the OLTP rollbacks in the KEEP
bufferpool"

Well, the only DDL that accepts the buffer pool clause are:

o CREATE TABLE table name... STORAGE (buffer_pool_clause)
o CREATE INDEX index name... STORAGE (buffer_pool_clause)
o CREATE CLUSTER cluster_name...STORAGE (buffer_pool_clause)
o ALTER TABLE table_name... STORAGE (buffer_pool_clause)
o ALTER INDEX index_name... STORAGE (buffer_pool_clause)
o ALTER CLUSTER cluster_name... STORAGE (buffer_pool_clause)

so, the answer to this is no.  There is no syntax to support
this construct.

3) "if batch jobs are run against the OLTP database, larger
rollbacks need to be use.  In order to avoid getting snapshot
too old errors, are the any tips for the OLTP rollbacks."

This is a tricky one to answer.  There are 2 concepts here

A) you need a larger rollback segment for the big batch
transaction, larger then you need for the OLTP transactions in
any case, just to handle the volume of updates it performs.

B) you need larger rollback segments for the OLTP transactions
to use as well during the batch job to support the consistent
read mechanism employed by Oracle -- not only does the batch job
need to have a big rollback segment but you want to try and make
it so that your other rollback segments used by the OLTP job do
not wrap while the batch job is processing else it (the batch
job) will get the ORA-1555 when it tries to reconstruct some
data from the RBS's used by the OLTP jobs.

What that means is in general -- any of your rollback segments
should be large enough to accomidate your batch job (or at least
allow it to grow as described above) AND your rollback segments
should be large enough so that during the period of time the
batch job is executing, the OLTP transactions do not cause them
to "wrap" around -- overwriting data the system no longer needs
to perform a rollback with but that your batch job might need to
perform a consistent read.

Review &
      Followup

      Rating:
      3
  October 03, 2002
Reviewer:  Sagi
       from India
Hi Tom !

You Said
"Periodically (typically before backups) I
manually offline and then drop each rollback segment, shrink the datafile
associated with its tablespace back to the right size for 20 extents and
recreate the rollback segment."

q1) Normally the HOT Backups are scheduled automaticlly during some offpeak
time. Can we still follow this methind in HOT Backups.

q2) Secondly, if its a 24*7 and if we drop the rollback segments and say someone
is trying to work, would he not encounter error.

Regards,
Sagi

          Followup:

q1) sure

q2) the person trying to drop it would, yes.  they would have to wait or just
skip it and do it next time.  it is not vital to drop and recreate it.

      Rating: 5
A few
      clarifications (please) and guidelines  October 30, 2002

Reviewer:  Yogeeraj  from Mauritius
Hi,
a.
Can you please clarify the following?
<quote>
I like to create my rollback segments with around 20-25 minextents and set
maxextents to 255 or so.  I place each rollback segment in its own tablespace
and allow each tablespace to have 1 autoextendable datafile allocated to it.
</quote>
- Do you mean One Rollback segment per tablespace? (Each equi-size?)

b. I am running an OLTP and reviewing my RBS tablespace which is DMT. The
current size is 1.5 GB. Can you please guide me? I need to know if it has been
properly sized and how to plan for the new rollback segments using statistics
from the current operating environment.

c. My OLTP with a large number of users (~300). How will i know if any of my
users are hitting  ORA-01555?

Thank you in advance for your explanations.

Regards
Yogeeraj

          Followup:

a) yes, 1 RBS / tablespace (before 9i anyway -- in 9i, undo tablespaces are what
I've been using)

b) do you get more wraps then YOU want. do you get lots of waits on undo segment
headers.  do you frequently get ora-1555's.  those are questions you need to ask
yourself.

c) they will tell you.  if they don't tell you, they must not care....  Or the
developers can instrument the code to capture and handle errors and log them
somewhere.

      Rating: 5
rbs tablespace
      maxextents unlimited?  May 12, 2003
Reviewer:
      Jerry  from USA
Hi, Tom,

Follow up questions on ORA-01555.

I have a database with rbs tablespace init=next=256k, maxextents unlimited,
minextents 20.  I created 20 rollback segments in the rbs tablespace.

When I query dba_tablespaces,it has maxextents=2147483645 for the rbs
tablespace.  When I query dba_rollback_segs, for each rollback segments, it
shows the maxextents=32765.  32765 x 20 = 655300 !=2147483645 , why?

The database experiences sporadic ORA-01555 errors when running a procedure.
The code is like:

declare
cursor c1 is
select a, b from table1 order by a;
begin
open c1;
loop
update table1
where xxxx;
commit;
end loop;
end;
/

Is this a 'fetch across commit'?  I read from metalink that 'add an order by
statement to the query will create a temp segment to hold the results of the
order by and will not require consistent read'

I can't understand why I could not re-produce the ORA-01555 error.  I used the
same rbs segment and tested on a quite system.  If there were active
transactions, it should always extends because of the rbs tablespace setting.
How can I find out what caused the ORA-01555?

Thanks as always!

          Followup:

32k is the MAX for rollback segments.

that is the CLASSIC fetch across commit -- classic cause you are updating the
table you are reading and committing the changes.  the UNDO you are overwriting
(since you released it via commit) is the exact UNDO you yourself need.  You
have shot yourself in the foot.

Adding an order by *MIGHT*, MAY, can but does not have to do that. (and it does
require consistent reads, they just happen before the updates is all -- but they
are still all there).

commiting in a cursor for loop like that

o is slow
o generates more undo/redo
o leads to total loss of data integrity
o is the second worst practice I see all of the time (bind variables and not
  using them being the first)

In fact, I'd bet that procedural code should be a SINGLE update statement --
there should be no cursor for loop, no looping, no single row updates -- just

update table1 set..... where ....;

and the CLIENT that invoked this process should commit (wish plsql didn't
support the verbs commit and rollback personally)

And in your case -- the rbs will *NOT* extend.  only modifications cause an RBS
to extend.  You commit -- ending the transaction -- hence you will never cause
it to extend.

      Rating: 5
could you
      please clarify  May 13, 2003
Reviewer:
      Jerry  from USA
Thanks Tom for the explaination. That's very helpful!

Several more questions:

1. What's the definition of 'fetch across commit'?  If I have the code like
this, is that fetch across commit (cursor table opened being different from the
table updated):

declare
cursor c1 is
select a, b from TABLE1;
begin
open c1;
loop
update TABLE2
where xxxx;
commit;
end loop;
end;
/

2.  Could you please clarify:
'Adding an order by *MIGHT*, MAY, can but does not have to do that. (and it does

require consistent reads, they just happen before the updates is all -- but they

are still all there).'

3. If the application code caused the ORA-01555 error, why can't I re-produce
the error with the same data? I re-ran the procedure and can't re-produce the
error.

Thanks again!

          Followup:

1) you hae no fetches in there so i'll assume you really mean

open c
loop
   fetch c
   exit when c%notfound
   update
   commit
end loop
close c

that is a fetch across commits.  You open, fetch -- COMMIT -- fetch ....

2) what is to clarify?

a) an order by MIGHT cause us to materialize the result set before the first
fetch.  it might not.

b) an order by does not disable or affect the consistent read mechanism, we do
consistent reads.  It may affect WHEN they were done (all before the first fetch
or row by row)

3) your rollback segments just happened to be big enough and didn't wrap around.
 I can reproduce 1555's at will -- if you have my book "expert one on one
oracle", tons of examples in there.

      Rating:
      5
  May 13, 2003
Reviewer:  Mike
       from TX
Rollback Segment Stats for DB: xxxDB  Instance: xxxdb  Snaps: 226 -227
->A high value for "Pct Waits" suggests more rollback segments may be required
->RBS stats may not be accurate between begin and end snaps when using Auto Undo
  managment, as RBS may be dynamically created and dropped as needed

        Trans Table       Pct   Undo Bytes
RBS No      Gets        Waits     Written        Wraps  Shrinks  Extends
------ -------------- ------- --------------- -------- -------- --------
     0            7.0    0.00               0        0        0        0
     1        1,951.0    0.00       9,710,280       10        0       10
     2        2,467.0    0.32      13,078,576       15        1       12
     3        2,300.0    0.00      12,706,808       14        0       11
     4          769.0    0.00         430,534        1        0        0
     5          722.0    0.00         587,162        1        0        0
     6      390,504.0    0.00      19,390,538       21        1       18
     7          738.0    0.00         280,982        0        0        0
     8          797.0    0.00         387,670        0        0        0
     9          903.0    0.00       1,439,410        1        0        0
    10        2,220.0    0.00      12,171,202       11        0       10
          -------------------------------------------------------------
 Rollback Segment Storage for DB: xxxDB  Instance:xxxdb  Snaps: 226 -227
->Optimal Size should be larger than Avg Active

RBS No    Segment Size      Avg Active    Optimal Size    Maximum Size
------ --------------- --------------- --------------- ---------------
     0         401,408               0                         401,408
     1      12,705,792       4,504,095                      14,802,944
     2      15,851,520       3,974,749                      15,851,520
     3      13,754,368       4,166,232                      13,754,368
     4       2,220,032       2,729,481                      20,045,824
     5       2,220,032       1,885,905                      13,754,368
     6      21,094,400       5,934,144                      21,094,400
     7       3,268,608       3,362,346                      12,705,792
     8       5,365,760       2,217,066                      14,802,944
     9       2,220,032       3,267,504                      21,094,400
    10      14,802,944       3,994,321                      14,802,944
          -------------------------------------------------------------
 Undo Segment Summary for DB: xxxDB  Instance: xxxdb  Snaps: 226 -227
-> Undo segment block stats:
-> uS - unexpired Stolen,   uR - unexpired Released,   uU - unexpired reUsed
-> eS - expired   Stolen,   eR - expired   Released,   eU - expired   reUsed

Undo           Undo        Num  Max Qry     Max Tx Snapshot Out of uS/uR/uU/
 TS#         Blocks      Trans  Len (s)   Concurcy  Too Old  Space eS/eR/eU
---- -------------- ---------- -------- ---------- -------- ------ -------------
   1          8,192      1,911      226          4        0      0 0/0/0/0/0/0
          -------------------------------------------------------------

Undo Segment Stats for DB: xxxDB  Instance: xxxdb  Snaps: 226 -227
-> ordered by Time desc

                     Undo      Num Max Qry   Max Tx  Snap   Out of uS/uR/uU/
End Time           Blocks    Trans Len (s)    Concy Too Old  Space eS/eR/eU
------------ ------------ -------- ------- -------- ------- ------ -------------
13-May 10:56        8,192    1,911     226        4       0      0 0/0/0/0/0/0
          -------------------------------------------------------------
Hi Tom,
The above is from the statspack @15min for our 9ias infrastructure DB , web
application.
I would know how do you think the rollback segs performed? especially on these
have extends/shrinks/wraps? what changes  shall I make?

          Followup:

the extends might mean they are too small however, since there is fortunately no
optimal, they'll fix themselves over time (they'll extend and stay there)

I don't see major cause for concern, you had some big transactions running --
doing "big things" during this period.

May 13, 2003 Dusan -- Thanks for the question regarding
      "Invoker rights and Before Insert Trigger", version 8.1.6

      You Asked
Hi Tom,

I found out that if I call procedure with authid current_user hint from Before
Insert Trigger for each row, the invoker rights do not work there. Please, have
a look at following:

SQL> select user from dual
  2  ;

USER
------------------------------
DV

SQL>
SQL> PROMPT Creating Table 'DV_T2'
Creating Table 'DV_T2'
SQL> CREATE TABLE DV_T2
  2   (RGPT_ID NUMBER(10) NOT NULL
  3   ,SEQ_ID NUMBER(10) NOT NULL
  4   )
  5  /

Table created.

SQL> grant select,insert,update,delete on DV_T2 to v30;

Grant succeeded.

SQL> PROMPT Creating Primary Key on 'DV_T2'
Creating Primary Key on 'DV_T2'
SQL> ALTER TABLE DV_T2
  2   ADD (CONSTRAINT DVT2_PK PRIMARY KEY
  3    (RGPT_ID
  4    ,SEQ_ID))
  5  /

Table altered.

SQL>
SQL>
SQL> PROMPT Creating Sequence 'DDVT2_SEQ'
Creating Sequence 'DDVT2_SEQ'
SQL> CREATE SEQUENCE DDVT2_SEQ
  2   NOMAXVALUE
  3   NOMINVALUE
  4   NOCYCLE
  5   NOCACHE
  6  /

Sequence created.

SQL>
SQL> PROMPT Creating Sequence 'DVT2_SEQ'
Creating Sequence 'DVT2_SEQ'
SQL> CREATE SEQUENCE DVT2_SEQ
  2   NOMAXVALUE
  3   NOMINVALUE
  4   NOCYCLE
  5   NOCACHE
  6  /

Sequence created.

SQL> create synonym v30.dvt2_seq for ddvt2_seq;

Synonym created.

SQL> grant select on ddvt2_seq to v30
  2  /

Grant succeeded.

SQL>
SQL>
SQL> create or replace function f_return_sequence
  2  ( p_table_name in varchar2)
  3  return number
  4  authid current_user
  5  as
  6  v_return number(10);
  7  begin
  8  dbms_output.put_line('User in proc='||user);
  9  if p_table_name='DV_T2' then
 10  select dvt2_seq.nextval into v_return from dual;
 11  end if;
 12  return v_return;
 13  end;
 14  /

Function created.

SQL> grant execute on f_return_sequence to v30
  2  /

Grant succeeded.

SQL> create synonym v30.f_return_sequence for f_return_sequence
  2  /

Synonym created.

SQL> CREATE OR REPLACE TRIGGER BIT_DVT2
  2   BEFORE INSERT
  3   ON DV_T2
  4   FOR EACH ROW
  5  -- PL/SQL Block
  6  begin
  7  :new.seq_id :=f_return_sequence('DV_T2');
  8  end;
  9  /

Trigger created.

SQL> insert into DV_T2(rgpt_id) values(1);

1 row created.

SQL> insert into DV_T2(rgpt_id) values(1);

1 row created.

SQL> insert into DV_T2(rgpt_id) values(1);

1 row created.

SQL> connect v30/v30
Connected.

SQL> set serveroutput on
SQL> insert into DV.DV_T2(rgpt_id) values(1);
User in proc=V30

1 row created.
SQL> select * from DV2.DV_T2;

  RGPT_ID    SEQ_ID
--------- ---------
        1         1
        1         2
        1         3
        1         4

This surprised me. From BIR trigger I called function with invoker rights, my
synonym dvt2_seq was for dv.ddvt2_seq, as is demonstrated below:

SQL> select dvt2_seq.nextval from dual;

  NEXTVAL
---------
        1

SQL> begin
  2  dbms_output.put_line(f_return_sequence('DV_T2'));
  3  end;
  4  /
User in proc=V30
2

PL/SQL procedure successfully completed.

SQL>

Why trigger did not respect function with invoker rights? How can we outsmart
trigger?

Thanks,

Dusan

      and we said...
It is working exactly as it should.

An invokers rights routine takes on the identity of the invoker.  In this case,
the invoker is the trigger and triggers ALWAYS run in "definer rights" mode.

It would be exactly the same as if you called that procedure from a definer
rights routine.  As soon as you enter the definer rights routine the invoker IS
the definer of that procedure -- not the user who is logged in.

You cannot "outsmart" the trigger. The trigger quite simply runs as the OWNER of
the trigger as it must.  Anything it calls will consider that OWNER the invoker.

Btw: the way to see "whats in effect schema wise" would be:

  4      for x in
  5      ( select sys_context( 'userenv', 'current_user' ) current_user,
  6               sys_context( 'userenv', 'session_user' ) session_user,
  7               sys_context( 'userenv', 'current_schema' ) current_schema
  8          from dual )
  9      loop
 10          dbms_output.put_line( 'Current User:   ' || x.current_user );
 11          dbms_output.put_line( 'Session User:   ' || x.session_user );
 12          dbms_output.put_line( 'Current Schema: ' || x.current_schema );
 13      end loop;

USER is constant in a session -- it'll never change.  The above will show you

o CURRENT_USER: The name of the user whose privilege the session is currently
executing under.
o SESSION_USER: The name of the user who originally created this session ? who
is logged in.  This is constant for a session.
o CURRENT_SCHEMA: The name of the default schema that will be used to resolve
references to unqualified objects.
which is more relevant then user.

Review &
      Followup

      Rating: 5
More clear
      now  September 30, 2001
Reviewer:  Dusan
       from Lexington, MA, 01801
At least, it diverted me from wrong direction.

      Rating:
      4
Reader  September 30, 2001
Reviewer:
      Reader  from USA
Tom,

Can you tell us what is the rational in
PL/SQL Procedures having Definer's rights as default and
Java Stored Procedures ( extrenal routines written in Java)
having Invoker's rights as the default

Thsnks

          Followup:

When you load java into the database, the java itself is compiled with invokers
rights.  However -- since all java is called via a call spec written in SQL and
this call spec will compile by default with DEFINER rights -- most people don't
even notice this nuance (it appears to be definer rights).

The reasons why this is so -- there are a couple:

1) you typically use java in the database as a "utility" as a way to extend
plsql to let it do what plsql cannot.  By loading with IR (invoker rights) --
this is facilitated.  Instead of loading the java per schema to get the rights
privs -- we load it once and when you use it -- it'll take on "your" identity
(you being another stored procedure).  So, if both user A and user B create a
call spec to the java AND call the java from their procedures, it will be as if
they put the java into their schema.

2) the sql in java is typically in jdbc and is invisible to the compiler.
Unlike plsql where the sql is right there and visible typically.  When you
compile java, we do not "rip the sql" out of it like with do with plsql.  With
plsql there are great efficiencies to be gained by using definer rights and
doing the security checks and such right then and there at compile time.   DR
(definer rights) are very efficient with plsql -- they would add nothing to
java.

So, in short -- since the call spec is a DR routine -- the fact that the java
was loaded with IR isn't typically relevant at all (or even noticed).

      Rating: 5
But why
      triggers "Always" have definers rights...  September 13,
      2002
Reviewer:  Subhrajyoti Paul  from Mumbai, India

Tom, what is the philosphy behind triggers always having definers rights...?

          Followup:

Because they are considered part of the table itself.

Tell me -- can you give a logical, real world, practical case where it should
run with invokers rights?

      Rating: 3
Thanks, Tom,
      excellent explanation, but....  May 13, 2003

Reviewer:  Olga  from Canada
... My goal is to track WHO updated certain column of some table and to store
username in rec_upd_username column of the said table, for each row. I tried
various solutions, including the one described in this topic, but came to the
same conclusion that you just described.

I believe, there must be the way to do this in ORACLE, would you be so kind to
give me a hint? :)

I run Oracle 8.1.7 database.
Thank you!

          Followup:

WHO = user?

create trigger before update on t for each row
begin
   :new.updated_by := user;
end;

May
      13, 2003 Suyog -- Thanks for the
      question regarding "I'm having a problem with Control File (.ctl). ",
      version 8

      You Asked
I'm trying to load few record in Data Base with sqlldr using .ctl file. I'm
having a DATE column in the records. Sometime DATE column is null/blank. I want
to insert DATE coulumn with SYSDATE for record taht comes in with DATE column as
NULL.
How Can I do that ?

EFF_DT                  POSITION(16:25)    DATE "mm/dd/yyyy" NULLIF
EFF_DT=BLANKS,

Can I write some thing like
 EFF_DT                  POSITION(16:25)    DATE "mm/dd/yyyy" SYSDATE IF
EFF_DT=BLANKS,

I'm getting error as shown below.

SQL*Loader-350: Syntax error at line 20.
Expecting valid column specification, "," or ")", found keyword sysdate.
 EFF_DT                  POSITION(16:25)    DATE "mm/dd/yyyy" SYSDATE

I'm very very new to ORacle. Please do advise me.

Thanks a lot for you time.

      and we said...
ops$tkyte@ORA920LAP> create table t ( x int, y date, z int );
Table created.

ops$tkyte@ORA920LAP> !sqlldr / t

SQL*Loader: Release 9.2.0.3.0 - Production on Tue May 13 17:55:01 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Commit point reached - logical record count 2

ops$tkyte@ORA920LAP> select * from t;

         X Y                  Z
---------- --------- ----------
         1 01-JAN-03          2
         3 13-MAY-03          4    <<<=== that is today

ops$tkyte@ORA920LAP> !cat t.ctl
LOAD DATA
INFILE *
INTO TABLE T
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
(x,
 y "nvl(to_date(:y,'mm/dd/yyyy'),sysdate)",
 z )
BEGINDATA
1,01/01/2003,2
3,,4

ops$tkyte@ORA920LAP>
Review & Followup

      Rating:
      5
  May 13, 2003
Reviewer:
      A reader

      Rating:
      4
  May 19, 2003
Reviewer:
      A reader

May 13, 2003 Jeff -- Thanks for the question regarding "Sybase
      / Oracle sequences", version 8.1.7

      You Asked
Tom,

First off, thanks for your site, it is truly awesome. On to the meat.

We have a huge customer databse in Sybase, and for reasons beyond my control,
use Oracle to generate the primary key for some of the tables.  After a refresh
from production of the Sybase boxes, I have to go through and make sure that the
LAST_NUMBER from dba_sequences is greater in the Oracle DB than in
Sybase...quite a tedious task. I'm in the middle of writing a proc to help
myself out, and was wondering if you had an elegant way of handling this. I
wasn't born to code, so right now I'm doing something like:

-- Pass in "select(max)primary_key from sybase_table" eg:
-- select max(addressid) from adddress
-- v_ variables are the sybase column vals

procedure update_seq (v_addressid in number, v_billingid in number, v_contactid
in number, v_svcplanid in number ...)
is
     l_number number;
     v_placeholder varchar2;
     v_cmd varchar2;
begin
select ADDRESS_SEQ.nextval into l_number from dual;
if (l_number < v_addressid) then
 v_placeholder := (v_addressid-l_number) + 20
 v_cmd := 'alter sequence address_seq increment by ' || v_placeholder;
 execute immediate v_cmd;
 select ADDRESS_SEQ.nextval into l_number from dual;
 v_cmd := 'alter sequence address_seq increment by 1';
 execute immediate v_cmd;
end if;
 select BILLING_SEQ.nextval into l_number from dual;
...

Like I said... I'm not the worlds greatest coder.

Here are the current vals from each of the DBs.
-- Sybase max(column vals)

AddressId, 1007602217
BillingId, 1006028076
ContactId, 1008730144
CustId, 1007925073
Environment, 3324258
Notification, 6709008
LevelId, 19627
PhoneId, 1013580204
ResponseId, 1001987528
ServiceTaxRuleId, 1004003070
ServiceId, 1003071073
SvcCreditId, 1000083025
SvcPlanHistoryId, 1002792048
SvcPlan, 1060646044
SvcPromoHistoryId, 1008520551
SvcPromoId, 1005886059
TransactId, 1006893334

-- Oracle
SEQUENCE_NAME                  LAST_NUMBER
------------------------------ -----------
ADDRESS_SEQ                     1004603129
BILLING_SEQ                     1006029043
CONTACT_SEQ                     1006731138
CUSTOMER_SEQ                    1007926070
ENVIRONMENT_SEQ                    4831000
NOTIFICATIONTABLE_SEQ              6710005
ORGLEVEL_SEQ                        128000
PHONE_SEQ                       1013580056
RESPONSE_SEQ                    1002330000

SEQUENCE_NAME                  LAST_NUMBER
------------------------------ -----------
SERVICETAXRULE_SEQ              1004004066
SERVICE_SEQ                     1003072070
SVCCREDIT_SEQ                   1001593041
SVCPLANHISTORY_SEQ              1001793017
SVCPLAN_SEQ                     1003830794
SVCPROMOHISTORY_SEQ             1008521517
SVCPROMO_SEQ                    1005887035
TRANSACT_SEQ                    1006894128

Thanks Tom, if you have time to answer great, if not I totally understand, I was
just looking for a smoother way of doing this kind of thing. Thanks again, and
keep up the good work!!

Jeff

      and we said...
well, this is a little more generic, is that what you were looking for?

ops$tkyte@ORA920LAP> create or replace procedure set_seq_to( p_name in varchar2,
p_val in number )
  2  as
  3      l_num number;
  4  begin
  5      execute immediate 'select ' || p_name ||
  6                        '.nextval from dual' INTO l_num;
  7      execute immediate 'alter sequence ' || p_name ||
  8                        ' increment by ' || (p_val-l_num-1) ||
  9                        ' minvalue 0';
 10      execute immediate 'select ' || p_name ||
 11                        '.nextval from dual' INTO l_num;
 12      execute immediate 'alter sequence ' || p_name ||
 13                        ' increment by 1 ';
 14      dbms_output.put_line
 15      ( 'Sequence ' || p_name || ' is now at ' || p_val );
 16  end;
 17  /

Procedure created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> drop sequence s;

Sequence dropped.

ops$tkyte@ORA920LAP> create sequence s;

Sequence created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> exec set_seq_to( 's', 10032 );
Sequence s is now at 10032

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> select s.nextval from dual;

   NEXTVAL
----------
     10032

ops$tkyte@ORA920LAP> exec set_seq_to( 's', 1 );
Sequence s is now at 1

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> select s.nextval from dual;

   NEXTVAL
----------
         1

Review &
      Followup

      Rating: 4
where do i find
      starts with value for a sequence  May 14, 2003

Reviewer:  reader  from USA
Hi tom,
Where do I find starts with value for a sequence in a data dictionary?

When I execute alter sequence test increment by 10000;
Then it also changes starts with value for test to 10000(per dbartisan)
I tried searching dba_sequences but its not there


Subject | Article | Last Updated (East coast time)
SET SERVEROUT ON SIZE considerations | article only | May 14, 2003 07:02 am
The Liblary vs DB package | article only | May 14, 2003 07:31 am
Can logging turned off in PL/SQL programs | article only | May 15, 2003 09:37 am
clustering_factor | article only | May 15, 2003 09:39 am
Drop / recreate local indexes while loading data in partitioned tables | article only | May 15, 2003 09:44 am
List of all database roles | article only | May 15, 2003 07:48 pm
Materialized View Log - snaptime$$ | article only | May 16, 2003 09:53 am
synonyms | article only | May 16, 2003 10:05 am
Why these queries have so different plans? | article only | May 16, 2003 04:43 pm
Using OWA_UTIL.LISTPRINT | article only | May 16, 2003 04:45 pm
Table Reorg | article only | May 16, 2003 05:03 pm
Numeric Or Value Error | article only | May 16, 2003 05:07 pm
Are There Ramifications In Using "EXECUTE IMMEDIATE" In A Repeated Fashion? | article only | May 16, 2003 05:30 pm
Analyze and DBMS_STATS | article only | May 17, 2003 10:02 am
User Function from SQL statement ! | article only | May 17, 2003 10:03 am
Oracle Database and NT Services | article only | May 17, 2003 10:10 am
Invoking sql procedure in J.S.P | article only | May 17, 2003 10:21 am
Question on check constraints | article only | May 17, 2003 10:53 am
Parallel Execution and the Large Pool | article only | May 17, 2003 03:18 pm
Clarification and Question about UGA/PGA and sort_area_sze | article only | May 17, 2003 06:50 pm
Need your comments on schema design | article only | May 17, 2003 06:54 pm
pga | article only | May 18, 2003 10:41 am
searching name value pairs | article only | May 18, 2003 02:57 pm
re-parse and recursive calls | article only | May 18, 2003 09:02 pm
Size of one I/O in unix | article only | May 19, 2003 07:52 am
Buffer cache size is too large? | article only | May 19, 2003 10:37 am
Update Table using pl/sql | article only | May 19, 2003 10:42 am
backups | article only | May 20, 2003 07:28 am
Predicate pushing in views containing analytic functions | article only | May 20, 2003 12:50 pm
tablespace point in time recovery -- what is it? | article only | May 20, 2003 12:54 pm
sql loader questions | article only | May 20, 2003 01:12 pm
Shared Pool | article only | May 20, 2003 01:36 pm
row(s) 1 - 32 of 32
May 14, 2003 Andre -- Thanks for the question regarding "SET SERVEROUT ON SIZE considerations", version 8
You Asked
Hi.
Congratulations for your very useful work !
I have a little question for you.
When i create a PLSQL block, i always type in the first line the
statement SET SERVEROUT ON SIZE 1000000 ( with the maximum value , 1000000)
because if I set the value to a very little number, for example 10000,
the error "buffer overflow can happen". I know that this error occurs
just because i have dbms_output.put_line inside the code.

The question is: is there any consideration to do when setting this value,
because if not, i always will set the value to 1000000, trying
to avoid the "buffer overflow" error.
TIA,
Andre Schaeffer
and we said...
No, it just sets an upper limit, it allocates no additional storage.

I have that in my login.sql so I never have to set it myself.  Mine is at
1000000 as well.
Review & Followup
Rating: 5
April 18, 2001
Reviewer: Helena Markova from Bratislava, Slovakia

Rating: 4
buffer size overflow May 09, 2003
Reviewer: venkat from India
Tom,

ours is a web application and frequently, developers complain of buffer size
overflow error. can the procedure,
dbms_output.enable(800000) be executed to sort out this?

Followup:
why are they enabling dbms_output at all in a web application?  it goes
*nowhere* unless you are in sqlplus or svrmgrl

dbms_output.disable is what they are looking for.

Rating: 4
buffer size overflow May 14, 2003
Reviewer: Venkat from India
Tom,

I understand from the application team that they are using dbms_output in many
stored procedures towards debugging related stuff.During testing of various
modules, this error comes up. The connectivity to oracle is thru' the app server
weblogic.
I have set 'set serveroutput on size 1000000' in glogin.sql script. In spite of
this, the team complains of the error ORA-20000: ORU-10027: Can u suggest some
solution for this?

Thanks
Venkat

Followup:
glogin.sql only happens during sqlplus access.

in weblogic is it not run, glogin.sql is a sqlplus'ism.

have them set dbms_output.disable;  in weblogic, since the dbms_output stuff
goes into the big bucket anyway (weblogic doesn't dump it anywhere), it is
meaningless to have it on.

May 14, 2003 chen -- Thanks for the question regarding "The Liblary vs DB package", version 8.17
You Asked
Tom,
   I have created a procedure for all the developers, but I have some questions
where should I store the procedure, library or db packages? as all the
developers will use it when they create forms using d2k. If I store it in the
library, they need to attach the library in the forms, but If I store it in db
packages, they just need to call it.
Which one is best? any other suggestions? thanks...
and we said...
What does this code do?

If this code is a utility that doesn't do anything in the database (eg: it does
some unique string manipulation), then I would use a .pll file (library).

If this code is a utility that does lots of database stuff, then I would create
it in the database.

Regardless of library or database -- all production (real) code should be in a
package -- period.
Review & Followup
Rating: 3
still clearly needed for package May 14, 2003
Reviewer: A reader
hi tom i want to know still what what are all the advantage & disadvantage of
having  pll's in front end(developer 2000).
     what are all the advantage & disadvantage of having stored packages in back
end.
      which one is better

Followup:
it is just about where the code is.

tell me, can you put a procedure in the database that makes a show-window call?
(no)

tell me, should you put a procedure in the client (pll) that retrieves 100,000
records and processes them (no)

You put the procedure where it makes the most sense.  Doing something GUI --
that is a PLL.  Doing something data intensive -- that is a database stored
procedure.

May 15, 2003 Sundara -- Thanks for the question regarding "Can logging turned off in PL/SQL programs", version 8.1.6
You Asked
Hi,

I have created few tables with nologging option, but when I do
DML operations through a PL/SQL
procedure, it generates log. Is there anyway I can turnoff
logging while
PL/SQL execution?

Thank you,

Murthy. S
and we said...
The NOLOGGING option on the CREATE table only affects the actual
operation of the CREATE TABLE itself. All subsequent
operations are logged as normal.  The "nologging" option is
used with statements like:

   create table T nologging as select * from T2;

The initial population of T is done in an unrecoverable fashion.
 All subsequent operations are done in a manner that can be
rolled back and recovered from.  They have to be -- the
assumption is that multiple people are accessing the table (need
to be able to rollback and such) and that the instance might
fail - making it so the table needs recovery (if the instance
failed during the create table -- we recover simply by rolling
back the insertions into the data dictionary).

There is an operation known as a direct path insert (done via
the INSERT /*+ APPEND */ into ....) that may be used if you
are doing a massive insert (its not for a single row insert).
Beyond that -- all changes are logged.
Review & Followup
Rating: 5
tablespace vs INSERT /*+ (oracle 92) August 27, 2002
Reviewer: Juan Carlos Reyes from Bolivia
Hi tom
I do a big amount of insert to a table, only for an archive generation, if it
fails it can be rerun, there is no need for logging, but it takes a lot of time.

In this insert  what is better to use INSERT /*+
APPEND */ into ....)
or to set the tablespace to NOLOGGING

Which is the difference

Thank again you Tom

Followup:
If you do not care to be able to do media recovery on this object, alter the
table to nologging (not the whole tablespace) and then do the insert append,
then alter the object BACK and do a BACKUP of at least that tablespace.

Rating: 5
August 27, 2002
Reviewer: A reader
Thank you

Rating: 4
don't want to get logged January 12, 2003
Reviewer: John
Hi Tom,

I am using an oracle 9.0.1 SE. I have background process which has following
code:
loop
   begin
      delete from table_a where type='type_1';
      insert into table_a
         select * from table_a@remote where type='type_1';
      commit;
   end;
   dbms_lock.sleep(10);
end loop;

I don't care the log of the table_a because I can get full copy from remote
database at any time. Is it possible to un-log the change in the redo-log since
it is big amount of the log.

Thanks.

Followup:
nope.  You do want to get logged -- you just don't know it.  corruption is a
terrible thing.

Seems like a wasteful process here -- why not just do distributed queries for
the things that need type_1 data in the first place.  Every 10 seconds -- if
that is generating lots of redo -- I cannot imagine why you would do it that
way.

Rating: 4
What about Materialized views? May 15, 2003
Reviewer: Damon Jebb from England
This may well explain why we are having problems, but I'm not sure.  We have a
large number of materialised views which were generating significant amounts of
redo log when refreshing.  Sometime ago we turned off the logging option on most
of the MV's and thought we had solved the problem.  Until a couple of days ago,
when we found the database not started after the over-night backup because the
disk space for redo logs had run out (a fairly common problem for us).

Looking at the times when the log switches occurred it is clear that it is when
the database is running the MV refreshes as it is outside normal working hours.

Is it possible to prevent the MV refreshes from generating redo logs?  It seems
pointless generating redo on MV's, as all the supporting data to generate the
views is available and logged elsewhere anyway.

Thanks

Damon

Followup:
so, you have not sized your system properly -- and you know it -- but you keep
on living with it anyway.

full refreshes of MV's that are not in MV's groups will be done via truncate and
INSERT /*+ APPEND */.  If the MV's were set to nologging, they would generate
redo ONLY for the indexes they had on them.

May 15, 2003 wallacel -- Thanks for the question regarding "clustering_factor", version 8.1.6
You Asked
Tom:

the clustering_facotr column in the user_indexes view
is a measure of how organized the
data is compared to the indexed column, is there any way i can
imporve clustering factor of a index. or how to improve it?

Regards
and we said...
This defines how ordered the rows are in the index.  If CLUSTERING_FACTOR
approaches the number of blocks in the table, the rows are ordered.  If it
approaches the number of rows in the table, the rows are randomly ordered.  In
such a case (clustering factor near the number of rows), it is unlikely that
index entries in the same leaf block will point to rows in the same data blocks.

Note that typically only 1 index per table will be heavily clustered (if any).
It would be extremely unlikely for 2 indexes to be very clustered.

If you want an index to be very clustered -- consider using index organized
tables.  They force the rows into a specific physical location based on their
index entry.

Otherwise, a rebuild of the table is the only way to get it clustered (but you
really don't want to get into that habit for what will typically be of marginal
overall improvement).

Followup to the comment below

I though that was somewhat self explainatory based on the notes associated with
this column (found in the Oracle8i Reference Manual):

The clustering factor is:
<quote>
Indicates the amount of order of the rows in the table based on the values of
the index.

n If the value is near the number of blocks, then the table
is very well ordered. In this case, the index entries in a
single leaf block tend to point to rows in the same data
blocks.

n If the value is near the number of rows, then the table
is very randomly ordered. In this case, it is unlikely
that index entries in the same leaf block point to rows
in the same data blocks.
</quote>

In general, if all of the index entries in a given leaf block point to the same
block -- the table is well ordered with regards to this index.

If all of the index entries in a given leaf block point to different blocks in
the tabe -- the table is not well ordered with respect to this index.

Thats all this is.
Review & Followup
Rating: 1
clustering factor April 23, 2001
Reviewer: A reader from UK
the article will be more useful if it talks about how the clustering factor is
determined

Rating: 4
My understanding of how clustering factor is determined October 20, 2001
Reviewer: Doug Cowles from CT, USA
Correct me if I'm wrong -
I thought that during an analyze.  A scan of the index was done in order,
starting with a clustering factor of zero.  Every time we end up switching
blocks during the fast scan of the index, we increase the clustering factor by
one. Therefore, if we had to switch blocks for every row, we ended up with a
clustering factor close to the number of rows, and if we didn't have to switch
we ended up with a clustering factor near the number of blocks.  No?  Now, what
"order" we are strarting with I would assume means staring with the root block
etc.,  I also go the impression that this "block switch" was really a datablock
switch, not jumping from leaf to leaf of the index for example.

Followup:
the leaf blocks are stored in a doubly link list, once we get to the "first"
entry -- scanning becomes easy, we don't have to walk a tree, we go from leaf
node to leaf node.

As we scan the elements in the leaf nodes (and we really only read the leaf
nodes pretty much during this process as we get to the first row in 1 or 2
blocks), we can simply look at the rowid in the index entry to see if the block
is the same as or different from the others rows in the index right around it.

So, the block switch is a data block switch and its based on the rowid found in
the index entry on the leaf node.

Rating: 5
number of blocks May 09, 2003
Reviewer: PINGU
Hi

when the reference guide says number of blocks, what blocks? High watermark?
Used blocks under hig waterark? Or the total blocks?

I guesssss it´s BLOCKS in user_tables, i.e HWM... but I am not sure

Followup:
the reference guide probably says that term in hundreds of places.

Out of context like that -- like saying "the ref guide says 'it is blue'" -- i
cannot comment on what blocks they were refering to.

Rating: 5
sorry to get back late! May 15, 2003
Reviewer: PINGU
Hi

If we dont know what blocks is the documentation referring how do we compare how
good or bad is the data ordered? How do we take the clustering_factor and
compare it with what values...?

Followup:
if the documentation was talking about cluster factors and blocks, it would be
the blocks in the table that could contain data -- the blocks under the HWM.

May 15, 2003 ivan -- Thanks for the question regarding "Drop / recreate local indexes while loading data in partitioned tables", version 8.1.6.1.2
You Asked
Hello Tom,

Currently I'm working on a large warehouse project.
One of the tables is :
- partitioned by week
- has 8 local indexes
Every morning millions of records are added to the last
partition of that huge table.
The source of our warehouse consists of flat files and oracle
tables.
Currently we are using a tool (ETI) that reads the flat files /
oracle source tables into a staging area (oracle database).
After this phase the tool generates pl/sql-scripts, that convert
the data and load it into the warehouse (conventional load -
cursor loop).

While loading we want to avoid index maintenance. Local indexes
cannot be dropped via : ALTER INDEX DROP PARTITION

How can we drop (before loading) and recreate (after loading)
these indexes ?

Thanks in advance,
Ivan Verpoort.
and we said...
Bummer, that has to be pretty slow.  Ok, hopefully -- you do not
NEED the indexes to do lookups and such during the load -- what
you can do is one of:

option 1:  if you can inject an ALTER SESSION statement, you can
do this:

ops$tkyte@DEV816> CREATE TABLE table2000
  2  ( x int,
  3    y int,
  4    z DATE
  5  )
  6  PARTITION BY RANGE (z)
  7  (
  8  PARTITION tab_1999_h1 VALUES LESS
  9  THAN(to_date('30-jun-1999','dd-mon-yyyy')),
 10  PARTITION tab_1999_h2 VALUES LESS
 11  THAN(to_date('31-dec-1999','dd-mon-yyyy')),
 12  PARTITION tab_2000_h1 VALUES LESS
 13  THAN(to_date('30-jun-2000','dd-mon-yyyy')),
 14  PARTITION tab_2000_h2 VALUES LESS
 15  THAN(to_date('31-dec-2000','dd-mon-yyyy'))
 16  )
 17  /

Table created.

ops$tkyte@DEV816> create index foo on table2000(y) local;
Index created.

ops$tkyte@DEV816> alter index foo modify partition tab_1999_h1
unusable;
Index altered.

ops$tkyte@DEV816> insert into table2000 values ( 1, 1,
add_months(sysdate,-100) );
insert into table2000 values ( 1, 1, add_months(sysdate,-100) )
*
ERROR at line 1:
ORA-01502: index 'OPS$TKYTE.FOO' or partition of such index is
in unusable state

ops$tkyte@DEV816> ALTER SESSION SET SKIP_UNUSABLE_INDEXES=TRUE;

Session altered.

ops$tkyte@DEV816> insert into table2000 values ( 1, 1,
add_months(sysdate,-100) );

1 row created.

ops$tkyte@DEV816> alter index foo rebuild partition tab_1999_h1;

Index altered.

Option 2:

exchange the partition with an empty table that is structurally
identical down to the indexes.

drop the indexes on the partition which is now a table.

load the table.

index it.

exchange it back...

see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:728425384831
for the syntax of an exchange.
Review & Followup
Rating: 5
what about deferred status? May 15, 2003
Reviewer: Joe from PA
The alter session advice was key.
that leads me to two other followups that I would like to hear your thoughts on.

is deferred any different than unusable (ie will query still bomb?) or just
perform slowly?

can sdo_relate queries that normally perform a primary filter with the index go
straight to the computationally intensive secondary filter passing all rows onto
that stage, or will the query die if the index is deferred or unusable in the
primary filter stage?

Followup:
deferred means the constraint is enforced -- you cannot save changes that would
violate the constraint.  It is as different from "unusable" (an index status) as
you can get.

indexes are NOT deferred.  constraints are.  A deferred constraint may implement
itself with a NON-UNIQUE index -- that is how it would differ from a
non-deferrable constraint.

It will not affect SDO, unless you have a requirement somewhere for a UNIQUE
index (then deferred won't be an option on those columns)

Rating: 2
deferred index status - is what I was referring to. May 15, 2003
Reviewer: Joe from PA
Tom,

Sorry I wasn't very clear, I was referring to an index status of deferred which
is available for spatial indexes.  My apologies for not reading far enough into
the otn docs, where my question was ultimately answered.

index_status Specifies that index modifications are to be deferred
('index_status=deferred') or that deferred index modifications are to be
synchronized with the data in the spatial table ('index_status=synchronize').

the answer I was looking for was further down in the docs (covered here)

The index_status keyword lets you defer modifications to the spatial index when
geometries are inserted, updated, or deleted in a spatial table. Deferring the
index modifications allows the geometry insert, update, and delete operations to
be completed sooner, and it can reduce concurrency issues with R-tree indexes if
multiple sessions are inserting rows into the spatial table. While index
modifications are being deferred, spatial functions and procedures will work
correctly with the current table data; however, spatial operator-based queries
might perform more slowly, will not include the results of new insert
operations, and might not include the results of new update operations.
Therefore, you are advised not to use spatial operators while index
modifications are being deferred.

Thanks for all your assistance. I love your site and go to it first to search
for all my developer/dba type questions.

May 15, 2003 Oleg -- Thanks for the question regarding "List of all database roles", version 8.1.6
You Asked
Can I get a list of all database roles being connected as an
ordinary user? DBA_ROLES is inaccessible.
I see two methods: SELECT privilege on DBA_ROLES and procedure
stored in DBA schema (definer rights). But is there any data
dictionary view to get the list?
and we said...
If you want to know ALL of the roles in the database, you need
to have been granted SELECT on DBA_ROLES.  That is the data
dictionary view to see the roles.

To see the roles YOU have access to -- USER_ROLE_PRIVS.

To see the roles YOU currently have enabled -- SESSION_ROLES.
Review & Followup
Rating: 4
privs May 15, 2003
Reviewer: reader from Houston, TX
I queried the privileges associated with DBA role, following two are not clear.

UNDER ANY TYPE
UNDER ANY VIEW

could you explain what are these privileges for? Thanks.

Followup:
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/wnsql.htm#970127

May
      16, 2003 peter -- Thanks for the
      question regarding "Materialized View Log - snaptime$$", version 8.1.7

      You Asked
What does snaptime$$ column in a materialized view log represent? Isn't it the
time the DML operation is performed on the table to which the log is defined on?
I created the following example and query the log table. It seems that
snaptime$$ always has the same time. Is that what it's supposed to be?

thanks

SQL> CREATE TABLE base_table (
  2  pk_col NUMBER PRIMARY KEY,
  3  col2   NUMBER
  4  );

Table created.

SQL> CREATE MATERIALIZED VIEW LOG ON base_table;

Materialized view log created.

SQL> INSERT INTO base_table VALUES(1, 1);

1 row created.

SQL> INSERT INTO base_table VALUES(2, 1);

1 row created.

SQL> commit;

Commit complete.

SQL> select pk_col, to_char(snaptime$$, 'mmddyyyy hh24:mi:ss'), dmltype$$,
old_new$$
  2  from   mlog$_base_table
  3  /

    PK_COL TO_CHAR(SNAPTIME$ D O
---------- ----------------- - -
         1 01014000 00:00:00 I N
         2 01014000 00:00:00 I N

2 rows selected.

      and we said...
snaptime$$ is modified upon a refresh and is only used when there are multiple
snapshots defined on a single master.

When a snapshot refreshes -- it sets this column to the time it did its refresh.
 That way, when it refreshes later -- it can skip rows it already did.

Say you have two snapshots S1 and S1.  You have a master table M.

You have a snapshot log with:

1   01014000 00:00:00 I N

in it.  Say it is now noon on April 4th 2003.  S1 was last refreshed at 11am on
the same day.  S2 was last refreshed at 9am that morning. S1 refreshes by
pulling every change from the snapshot log where snaptime$$ is greater then 11am
April 4th 2003.  That record from the year 4000 is so it gets applied.  When S1
is done, the snapshot log would have:

1   04042003 12:00:00 I N

Now at 1pm, S1 refreshes again.  Nothing happens -- there are no newer records
to process.  At 1:05pm, S2 refreshes.  The record in there happened after 9am
(S2's last refresh) so it pulls it.  Since S2 is the last snapshot in need of
refresh from this record -- it is removed from the log.

So, snaptime$$ is used to coordinate multiple snapshots using the same log.

Review &
      Followup

      Rating: 5
Using MV Log
      for Incremental Refresh  April 05, 2003
Reviewer:
      A reader  from ca, us
I was thinking of using MV log to do incremental refresh. Say that I have a
base_table and a refresh_table, which gets its data from base_table. I'll create
a MV log on base_table so that all the DML operations on the table are captured.
A scheduled job will process these DML operations to perform incremental refresh
on refresh_table. Can this be done? Is this even a good approach?

thanks

          Followup:

are you saying you would refresh the table?  why -- why wouldn't you just let it
refresh all by itself?

      Rating: 5
Follow Up on
      Using MV Log for Incremental Refresh   April 05, 2003

Reviewer:  A reader
Only a subset of refresh_table comes from base_table. base_table is only one of
the sources.

          Followup:

well, you'll be hard pressed to successfully use our snapshot logs.  it'll be
tricky (and most importantly unsupported).

What you would need to do is:

update snaptime$$ to a constant -- it'll be 01-jan-4000, you could set it to
SYSDATE.

Using that same SYSDATE, you can pull the primary keys of the modified rows and
apply your changes.

Then, you can remove the rows you processed.

Otherwise, new rows will be added into the log as you are processing and you'll
get "doubles" if you are not careful

      Rating: 5
Follow Up on
      Using MV Log for Incremental Refresh  April 05, 2003

Reviewer:  A reader
So I'd update snaptime$$ to, say, SYSDATE to "freeze" the log. However, let's
say if the same record has been inserted, updated, and updated again, how do I
make sure that they are processed in that particular order since snaptime$$ for
the three records recorded in the log would have the same time. Also in this
case, don't I just need to process the last update statement by inserting a new
record in refresh_table. Can this be done with snaptime$$?

          Followup:

oh, one you "freeze" the records -- then it is easy to refresh.

Just

delete from copy where pk in (select pk from log where dml_type in ('U','D') and
snaptime$$= whatever);

insert into copy select * from base where pk in (select pk from log where
dml_type in ('I','U') and snaptime$$ = whatever )

just get rid of all records that were modified or deleted and then add all that
where modified or inserted.  don't do anything in any order -- it wouldn't do
anything for you...

      Rating: 5
drop
      materialized view log  April 29, 2003
Reviewer:
      shyampaliyath  from TN,India
1)how to drop a materialized view log?
2)when i create a materialized view log it had created two tables MLOG$_EMP and
RUPD$_EMP. what is the use of the RUPD$_EMP?

          Followup:

1) drop materialized view log on table (did you look up drop materialized in the
sql reference -- it is pretty obvious)

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a85397/state24b.htm#2067174
2) it is an internal table.  It is in support of the Java RepAPI updatable
snapshots.  It may well be empty and stay that way.

      Rating: 4
Materialized
      View log  April 29, 2003
Reviewer:
      shyampaliyath  from TN,India
also what is the value of CHANGE_VECTOR$$   ??

          Followup:

nothing you can use.  internal, undocumented.

if you are curious about it -- search for

change_vector$$

on this site. (for that matter, searching for rupd$ would have works too... it
would find

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1300401907681

      Rating: 4
MV
      Log  April 29, 2003
Reviewer:  Rob
       from Pittsburgh, PA
Tom:

I am trying to use an MV log to propagate deltas in a work table to a dw table.
The work table has a mix of regular dml and direct load (insert append). In my
initial tests it seems that the data from the direct path load is not being
transferred to the dw table when I do a fast refresh. Is fast refresh supported
with direct load (the docs indicate that it is), can I mix direct load with
conventional dml and still fast refresh. Anything different in the create log or
create MV that I should be aware of to support the mix of direct load and
standard dml.

Rob

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7540691732165

      Rating:
      4
review  May 16, 2003
Reviewer:
      Shyampaliyath  from T.N. India
this materialized view log keeps track of the DML operation done to the parent
table . But how to keep track of the DML operation performed in the child
tables??

          Followup:

what child table??

each table would have its own log.

May 16, 2003 J --
      Thanks for the question regarding "synonyms", version 8.16

      You Asked
Tom,

I have only been using your site for just over a month, but already it has been
invaluable.

Thanks.

I need some advice:

I have a view that is made up of columns from several base tables.
It is taking a substantial amount of time it is taking to search e.g. when a
user enters search criteria from the site, due the large number of joins it
contains.

I thought of creating 2 identical tables (created as SELECT * FROM view) and
then set it up so that the application just points to the table1 while table 2
is being updated and vice versa using a private synonym so that the table can be
dropped and created from the view at several intervals throughout the day to
ensure up to date info and quicker searching for the user.

eg morning

User being pointed at t1 (via the synonym)

DROP table t2;

CREATE TABLE t2
AS SELECT * FROM view;

DROP SYNONYM;

CREATE PRIVATE SYNONYM FOR t2;

eg afternoon

User being pointed at t2 (via the synonym)

DROP table t1;

CREATE TABLE t1
AS SELECT * FROM view;

DROP SYNONYM;

CREATE PRIVATE SYNONYM FOR t1;

What I want is for their to be no 'down time' for the user while these tables
are being dropped and recreated behind the scenes throughout the day (set up as
a cron job).

Am I thinking along the right lines.........

Thanks.

      and we said...
Well, if all efforts to tune have been exhausted (joins are not evil, joins are
not bad -- we are sort of built to do them -- many

Why not use a materialized view that rebuilds itself on a timed basis.  It'll
just take care of itself, no synonyms.

the problem with the synonym flip is that anything dependent on the synonym will
go invalid (procedures, triggers, views, etc).  All shared sql will be flushed
that relied on this synonym.  people will be blocked during the drop synonym
(and will get an error immediately after -- during the brief moment of time it
does not exist) and the drop synonym will be blocked until everyone that was
using the synonym finish.

Doing DDL in a production system with lots of activity is an EXTREMELY bad idea,
extremely bad.

Review &
      Followup

      Rating: 5
SQL in
      Schools  March 24, 2003
Reviewer:  Thaha
      Hussain  from Manama, BAH
SQL lessons and practices should be included from the primary school onwards. It
should be a subject like Maths. Right? After all, it will be the only essential
tool required to do any manipulation on a DB based software. Let students having
interst in maths find it very funny. Whats your opinion?

Thaha Hussain

          Followup:

well, everyone needs to be able to do math.

less people need to be able to do SQL.

I don't see SQL being a general purpose skill - it is not required to do
manipulation of the data -- in fact, I would guess that most people using the
database could not write a query to save their lives.  They rely on
applications.  So, I think SQL, like C, would be something that many people
indirectly make use of but would never do themselves.

      Rating:
      3
  May 15, 2003
Reviewer:
      A reader
hello tom,

one of our development teams wants us to "wrap" all of their database objects:
we are forced to hide their views behind views made by us and code behind code
made by us. thus they want to reduce the number of cross schema dependencies.
there 's only ONE "link" left between the schemas for every object. in order to
get the schemas more indenpendent from each other (minimize the impact of
invalidation of their objects) it is furthermore recommended to access these
"wrappers" through (private) SYNONYMS only.

I'm not sure about the pros & cons of such a scenario (if any) and if it 's
worth to be done that way. what would YOU suggest?

          Followup:

I don't know what you mean by being forced to hide views behind views and code
behind code?

      Rating:
      3
  May 16, 2003
Reviewer:
      A reader
the want us to do:

create view <our_view> as select ... from <their_view>

and to use <our_view> instead of <their_view>. thus only ONE cross schema
dependency (between <their_view> and <our_view>) is left.

furthermore they recommend to:

create synonym <our_synonym> for <our_view>

in order to avoid "unnecessary invalidations" of our objects if their objects
get invalidated (for some reason).

so finally we have to issue:

select * from <our_synonym>

instead of:

select * from <their_view>

... same procedure with all the stored code. "some work" to do - but is it worth
it?

          Followup:

so what if there is only "one cross schema dependency" -- what have you achieved
besides YAL (yet another layer)?

Nothing...

check out what really gets invalidated

ops$tkyte@ORA920LAP> create user a identified by a;

User created.

ops$tkyte@ORA920LAP> create user b identified by b;

User created.

ops$tkyte@ORA920LAP> grant dba to a;

Grant succeeded.

ops$tkyte@ORA920LAP> grant dba to b;

Grant succeeded.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> @connect a/a
ops$tkyte@ORA920LAP> set termout off
a@ORA920LAP> REM GET afiedt.buf NOLIST
a@ORA920LAP> set termout on
a@ORA920LAP>
a@ORA920LAP> create table t ( x int );

Table created.

a@ORA920LAP> create view v as select * from t;

View created.

a@ORA920LAP> grant all on v to b;

Grant succeeded.

a@ORA920LAP>
a@ORA920LAP> @connect b/b
a@ORA920LAP> set termout off
b@ORA920LAP> REM GET afiedt.buf NOLIST
b@ORA920LAP> set termout on
b@ORA920LAP>
b@ORA920LAP> create view v as select * from a.v;

View created.

b@ORA920LAP>
b@ORA920LAP> create synonym s for v;

Synonym created.

b@ORA920LAP>
b@ORA920LAP>
b@ORA920LAP> create or replace procedure p1
  2  as
  3  begin
  4          for x in ( select * from v )
  5          loop
  6                  null;
  7          end loop;
  8  end;
  9  /

Procedure created.

b@ORA920LAP> create or replace procedure p2
  2  as
  3  begin
  4          for x in ( select * from v )
  5          loop
  6                  null;
  7          end loop;
  8  end;
  9  /

Procedure created.

b@ORA920LAP> @invalid;
b@ORA920LAP> break on object_type skip 1
b@ORA920LAP> column status format a10
b@ORA920LAP> select object_type, object_name, status
  2  from user_objects
  3  where status = 'INVALID'
  4  order by object_type, object_name
  5  /

no rows selected

b@ORA920LAP>
b@ORA920LAP> @connect a/a
b@ORA920LAP> set termout off
a@ORA920LAP> REM GET afiedt.buf NOLIST
a@ORA920LAP> set termout on
a@ORA920LAP> create or replace view v as select t.x, t.x y from t;

View created.

a@ORA920LAP>
a@ORA920LAP> @connect b/b
a@ORA920LAP> set termout off
b@ORA920LAP> REM GET afiedt.buf NOLIST
b@ORA920LAP> set termout on
b@ORA920LAP>
b@ORA920LAP> @invalid
b@ORA920LAP> break on object_type skip 1
b@ORA920LAP> column status format a10
b@ORA920LAP> select object_type, object_name, status
  2  from user_objects
  3  where status = 'INVALID'
  4  order by object_type, object_name
  5  /

OBJECT_TYPE        OBJECT_NAME                    STATUS
------------------ ------------------------------ ----------
PROCEDURE          P1                             INVALID
                   P2                             INVALID

VIEW               V                              INVALID

I have that "layered view", that "layered synonym", but so what?

May 16, 2003 Prince -- Thanks for the question regarding "Why
      these queries have so different plans?", version 8.1.7.2

      You Asked
Tom,

I have the following queries. I believe they are equivalent (logicaly).

At line -->1 and -->2 I have ,

WHERE ENTRY.cny#= 6939        -->1
AND BATCH.cny# = ENTRY.cny#    -->2

If I change the above to  (as in CASE2) there is vast difference in the
execution plan (note in the first case there is a FTS on BATCH)

WHERE BATCH.cny#= 6939        -->1
AND BATCH.cny# = ENTRY.cny#    -->2

Note: BATCH is the parent of ENTRY.

If I remove the OUTER JOIN both queries are having the same execution path.

Can you, please explain why the OUTER JOIN causes so much difference in the
execution plan?

Very many thanks for all your help!

Case1:
=========
SELECT ENTRY.*, BATCH.modulekey as MODULEKEY, BATCH.BATCH_title as TITLE,
BATCH.childentity as CHILDENTITY, JOURNAL.symbol as SYMBOL
FROM ENTRY, BATCH, JOURNAL
WHERE ENTRY.cny#= 6939        -->1
AND BATCH.cny# = ENTRY.cny#    -->2
AND JOURNAL.cny# = ENTRY.cny#
AND BATCH.record# = ENTRY.BATCH#
AND JOURNAL.record# = BATCH.journal# (+)
order by location#, dept#, entry_date
/

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1583 Card=7223 Bytes
          =816199)

   1    0   SORT (ORDER BY) (Cost=1583 Card=7223 Bytes=816199)
   2    1     HASH JOIN (Cost=1153 Card=7223 Bytes=816199)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'ENTRY' (Cost=40 Ca
          rd=921 Bytes=51576)

   4    3         INDEX (RANGE SCAN) OF 'IX_ENTRY_USER#' (NON-UNIQUE
          ) (Cost=9 Card=921)

   5    2       FILTER
   6    5         HASH JOIN (OUTER)
   7    6           TABLE ACCESS (BY INDEX ROWID) OF 'JOURNAL' (Cost
          =3 Card=6 Bytes=48)

   8    7             INDEX (RANGE SCAN) OF 'IX_JOURNAL_MSTRACCT#' (
          NON-UNIQUE) (Cost=2 Card=6)

   9    6           TABLE ACCESS (FULL) OF 'BATCH' (Cost=671 Card=21
          6206 Bytes=10594094)

Case2:
=========
SELECT ENTRY.*, BATCH.modulekey as MODULEKEY, BATCH.BATCH_title as TITLE,
BATCH.childentity as CHILDENTITY, JOURNAL.symbol as SYMBOL
FROM ENTRY, BATCH,JOURNAL
WHERE BATCH.cny#= 6939        -->1
AND BATCH.cny# = ENTRY.cny#    -->2
AND JOURNAL.cny# = ENTRY.cny#
AND BATCH.record# = ENTRY.BATCH#
AND JOURNAL.record# = BATCH.journal# (+)
order by location#, dept#, entry_date
/

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=62 Card=51 Bytes=576
          3)

   1    0   SORT (ORDER BY) (Cost=62 Card=51 Bytes=5763)
   2    1     HASH JOIN (Cost=59 Card=51 Bytes=5763)
   3    2       HASH JOIN (Cost=18 Card=47 Bytes=2679)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'JOURNAL' (Cost=3
           Card=6 Bytes=48)

   5    4           INDEX (RANGE SCAN) OF 'IX_JOURNAL_MSTRACCT#' (NO
          N-UNIQUE) (Cost=2 Card=6)

   6    3         TABLE ACCESS (BY INDEX ROWID) OF 'BATCH' (Cost=14
          Card=226 Bytes=11074)

   7    6           INDEX (RANGE SCAN) OF 'IX_BATCH_CHILDENTITY' (NO
          N-UNIQUE) (Cost=4 Card=226)

   8    2       TABLE ACCESS (BY INDEX ROWID) OF 'ENTRY' (Cost=40 Ca
          rd=921 Bytes=51576)

   9    8         INDEX (RANGE SCAN) OF 'IX_ENTRY_USER#' (NON-UNIQUE
          ) (Cost=9 Card=921)

      and we said...
Do you understand what an outer join implies?

Using this predicate:

WHERE BATCH.cny#= 6939        -->1
AND BATCH.cny# = ENTRY.cny#    -->2

with an outer join to BATCH as you have -- using a predicate like "batch.cny# =
6939" is "bad".  It is very non-performant.  The entire purpose of the outer
join is "lost".

Consider -- if we actually outer join to BATCH and "make up" a row -- all of the
columns in BATCH will be NULL.  BATCH.cny# will be NULL, hence that predicate
would NEVER be satisfied when we actually make up a row in BATCH -- hence the
outer join just makes us waste time.

Think about what and outer join implies.  Take the simple EMP, DEPT example:

select *
  from emp, dept
 where emp.deptno(+) = dept.deptno;

Now, that will return EVERY row from DEPT and if a row in EMP exists, we get
that as well (else we'll make up a row in EMP and set all of the columns to
NULL).  Now, there are 4 dept rows -- 10, 20, 30, and 40.  There are NO emps in
40.  Lets see what happens with each query:

scott@ORA817DEV.US.ORACLE.COM> select ename, dname
  2  from emp, dept
  3  where emp.deptno(+) = dept.deptno
  4  and dept.deptno = 40;

ENAME      DNAME
---------- --------------
           OPERATIONS

scott@ORA817DEV.US.ORACLE.COM> 4
  4* and dept.deptno = 40
scott@ORA817DEV.US.ORACLE.COM> c/dept/emp
  4* and emp.deptno = 40
scott@ORA817DEV.US.ORACLE.COM> l
  1  select ename, dname
  2  from emp, dept
  3  where emp.deptno(+) = dept.deptno
  4* and emp.deptno = 40
scott@ORA817DEV.US.ORACLE.COM> /

no rows selected

As you can see if I put the predicate on "EMP.DEPTNO=40" -- we got nothing --
the query is very SEMANTICALLY (logically) different.  We go through the painful
and sometimes very expensive operation of doing the outer join only to have your
predicate preclude any of the outer joined rows from surviving the query filter.

Review &
      Followup

      Rating: 4
Excellent!.
        April 11, 2002
Reviewer:  Prince

Thank you verymuch Tom,

This query was snipped from the v$sql. I understand the OUTER join.

I was under the impression, since the constant defeat the purpose of OUTER JOIN
at the last line and the first two lines are logically equivalent, oracle treats
them same. but obviously not.

Is there any situation, this query gives different result because of the
presenece of OUTER JOIN?

Thanks,

      Rating: 5
Please
      clarify  May 22, 2002
Reviewer:  AM
       from Pgh,PA
Hi Tom,
I got this example from a book and believe it works
" Will bring you all the employee from emp table who does not belong to 'Acct'
department".
but not been able to clarify myself how it actually works.
Can you please explain what each line does.

SELECT a.ename,b.dname
  FROM emp a
  ,dept b
  WHERE  b.deptno(+) = a.deptno
  AND b.dname(+) = 'Acct'
  AND b.deptno IS NULL

Thanks a lot for your great support to the ORACLE User/developer/administrator
community.

          Followup:

it is a really silly way to do it.

They are outer joining from EMP to DEPT.  The condition is join by deptno and
dname = 'Acct'.  If there is a row in EMP such that there is not any row in DEPT
that matches that DEPTNO/'Acct' -- make up a row in DEPT and set all of the
columns to NULL.

Then, they keep only the rows that are null.

      Rating: 5
How do you
      explain this ?  May 16, 2003
Reviewer:  Igor
       from France
Hi
    Thanks for your previous answers.

I've got today two queries which give different results.
I don't get why he does not materlize line in subquery
(I would expect before looking at the plan that exists takes line from upper
query
and then go inside...)

        Thanks for your clarification.

                                                  Igor

SQL> select * from BOADM6.tbo_cdr_aggr a
where exists ( select 1 from ARBOR6.RATE_USAGE r, ARBOR6.RATE_USAGE_BANDS rb
                                           WHERE a.SEQNUM_RATE_USAGE = r.seqnum
                                           AND a.SEQNUM_RATE_USAGE =
rb.seqnum(+)
  2                                         )
  3    4    5    6
SQL> /

no rows selected

SELECT STATEMENT   [CHOOSE] Cost=1
  FILTER
    TABLE ACCESS FULL TBO_CDR_AGGR [ANALYZED]  Cost=1 Card=1 Bytes=117
    NESTED LOOPS    Cost=3 Card=2 Bytes=24
      INDEX UNIQUE SCAN RATE_USAGE_PK [ANALYZED]  Cost=1 Card=1 Bytes=6
      INDEX RANGE SCAN RATE_USAGE_BANDS_PK [ANALYZED]  Cost=2 Card=2 Bytes=12

SQL>
SQL>
SQL>
SQL>
SQL> select a.* from BOADM6.tbo_cdr_aggr a, ARBOR6.RATE_USAGE r,
ARBOR6.RATE_USAGE_BANDS rb
                                           WHERE a.SEQNUM_RATE_USAGE = r.seqnum
                                           AND a.SEQNUM_RATE_USAGE =
rb.seqnum(+)   2    3
  4  /

TYPE_ID_USG ACCOUNT_NO  SUBSCR_NO SUBSCR_NO_RESETS JURISDICTION PRIMARY_UNITS
SECOND_UNITS THIRD_UNITS NUM_RECORDS TRANS_DT  SECOND_DT AMOUNT_REDUCTION_ID
AMOUNT AMOUNT_REDUCTION SEQNUM_RATE_USAGE SEQNUM_RATE_USAGE_OVERRIDES  STATE_TAX
COUNTY_TAX   ID_BATCH STATUT_AGREGAT NOM_FICHIER

                ADD_IMPLIED_DECIMAL
----------- ---------- ---------- ---------------- ------------ -------------
------------ ----------- ----------- --------- --------- -------------------
---------- ---------------- ----------------- ---------------------------
---------- ---------- ---------- --------------
---------------------------------------------------------------------------------
---------------------------------------------------------------
-------------------
         -9        168        188                0          201         28000
     3760           0           4 13-AUG-02 01-JAN-03    0     105280
     0           1756525                          -1      28000        903
  21              0 aoll20030516_113954_21_-2.data

        3

SELECT STATEMENT   [CHOOSE] Cost=3
  NESTED LOOPS    Cost=3 Card=1 Bytes=129
    NESTED LOOPS OUTER   Cost=3 Card=1 Bytes=123
      TABLE ACCESS FULL TBO_CDR_AGGR [ANALYZED]  Cost=1 Card=1 Bytes=117
      INDEX RANGE SCAN RATE_USAGE_BANDS_PK [ANALYZED]  Cost=2 Card=29003
Bytes=174018
    INDEX UNIQUE SCAN RATE_USAGE_PK [ANALYZED]

          Followup:

they are totally DIFFERENT queries (hint to all -- best if you put up the
smallest possible test cases -- complete ones -- with create tables, inserts,
etc... You know, sort of like I do ;)

The first one cartesian products t2 and t3 together.
The second one joins t2 to t1 and t3 to t1.

semanitically NIGHT and DAY different.

An outer join really only works when there is a table to drive it.  Consider:

ops$tkyte@ORA920> select *
  2    from dual
  3   where dummy(+) = 'A';

no rows selected

This is what is happening in your "where exists" subquery.  Consider this
complete, yet small, test case:

ops$tkyte@ORA920> create table t1 ( x int );
Table created.

ops$tkyte@ORA920> create table t2 ( y int );
Table created.

ops$tkyte@ORA920> create table t3 ( z int );
Table created.

ops$tkyte@ORA920> insert into t1 values ( 1 );
1 row created.

ops$tkyte@ORA920> insert into t2 values ( 1 );
1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select *
  2    from t1
  3   where exists ( select 1
  4                    from t2, t3
  5                                   where t2.y = t1.x
  6                                     and t3.z(+) = t1.x )
  7  /

no rows selected

This is what you are seeing, why -- well because the subquery really DOESN'T
Exist!

ops$tkyte@ORA920>
ops$tkyte@ORA920> select 1
  2    from t2, t3
  3   where t2.y = 1
  4     and t3.z(+) = 1
  5  /

no rows selected

See, just like with dual, there is NO driving table to "outer join" t3 to --
so the caresian product is 1 row times 0 rows = 0 rows, This is different --
much different then this query:

ops$tkyte@ORA920>
ops$tkyte@ORA920> select *
  2    from t1, t2, t3
  3   where t1.x = t2.y
  4     and t1.x = t3.z(+)
  5  /

         X          Y          Z
---------- ---------- ----------
         1          1

you get different results because the queries ask different questions

      Rating:
      5
  May 16, 2003
Reviewer:
      A reader

May 16, 2003 Munzer -- Thanks for the question regarding
      "Using OWA_UTIL.LISTPRINT", version 8i

      You Asked
Tom:

1.  Is there a way to control the size of column displayed when using
owa_util.listprint like defining it in <input size=20>.

Problem is if I define a SQL query using owa_util.listprint to create a dropdown
list, it will shrink the size of the column displayed on the screen (too small)
if no records exist as a result of query.

2.  Is there a way to create a null entry or default the selection to blank when
using the owa_util.listprint.

Problem is that it is confusing to user when he is trying to fill an order when
he tries to select "stock number" since the first stock number in the table is
always displayed.  It would be much nice if a blank value or "SELECT -->" shows
rather than a value.

3.  Is there a way in oracle to reset a sequence.  For eample, I want to use a
sequence for "Item_no" on an purchase order form.  It will increment for each
additional item user wants to order on the same PO.

The next time a user creates a new PO , I want item number to start with 1
again.

4.  I created procedure that runs fine and displays web page fine.  However
someteimes I get a message 'Number of cursors open exceeded".
Any ideas?

5.  Is there a way to control the size of a button displayed on an HTML form.

Thank you greatly,

      and we said...
1) the html <SELECT> tag does not support a "width", the width of the list is
defined based on the width of the widest element in the list itself.

There may be "fancy", browser dependent methods to do this - but that would be
outside the realm of standard HTML.  You would have to research that yourself.

2) select a NULL row from dual and union all it with your list.

begin
    owa_util.listPrint( 'select null, ''no selection'', ''x'' from dual
                         union all
                         select to_char(user_id), username, null from
all_users',
                         'pick_a_username',
                         5 );
end;
/

does a list where the USER gets to see the word "no selection" by default
selected, you'll get NULL as the input if they select it.  The rest of the list
would be USERNAMES (as the end user sees it) and you would get the user_id when
they select one.

3) No, you would have to do that procedurally.  Sequences are:

o never gap free, never, never, never
o are used to produce HIGHLY CONCURRENT unique identifiers for rows.

Having rows with "1,2,3,4,...." is really NOT any more meaningful then have rows
with "101, 200, 3000, 4000, 5003, ...."

If you need it for reporting purposes, you can always have it generated easily:

select empno, row_number() over ( order by sal ) from emp order by sal;

gives you that nice sequential number for displaying to the end user.  The
actually number used to order the data (sal in this case) is simply not
relevant.

4) you probably have the default of 50 cursors open, i run with a minimum of
1,000.  Up the value.

5) you control that by setting the text value.  See what:

<input type=submit value="    foo      ">
<input type=submit value="foo">

does on your browser (on mine, the button widths are different with "    foo   "
being wider.
Review & Followup

      Rating: 5
sorting
      list  August 05, 2002
Reviewer:  Munz
       from Reston, USA
TOm:

I have a dropdown list like this:

 state_cursor:=owa_util.bind_variables('select null,'' Pick One -->'',''x'',1
     from dual
       union all
        select state_id,state_name||''(''||cr_date||'')'',null,2 from state
        order by 4,2');
     owa_util.listprint(state_cursor,'i_state',null,null);

I am trying to sort list order by cr_date not name.  When I do order by
4,cr_date I get an error that column does not exist.
Mon, 5 Aug 2002 21:43:32 GMT

ORA-00904: invalid column name
ORA-06512: at "SYS.DBMS_SYS_SQL", line 1474
ORA-06512: at "SYS.OWA_UTIL", line 1663
ORA-06512: at "IMSADMIN.PROCESS_REC", line 34
ORA-06512: at line 7

Do you know how to do it?

          Followup:

There is no column CR_DATE in this union all view.  Only one of the two queries
has a CR_DATE in it.

Try this:

state_cursor:=owa_util.bind_variables
('select null,'' Pick One -->'',''x'',1,  sysdate
    from dual
   union all
   select state_id,state_name||''(''||cr_date||'')'',null,2, cr_date
     from state
    order by 4,5');

      Rating:
      5
listrpint  August 21, 2002
Reviewer:
      mo  from Reston, USA
Tom:

Do you know why I get this error. I tried different ways but nothing is working.
The problem occurs after I add a filter on obl_date_retro data.

   po_cursor:=owa_util.bind_variables('select null,'' Pick One
-->'',''x'',1,sysdate
     from dual
      union all
      select
obl_no,obl_no||''---(''||obl_date_retro||'')'',null,2,obl_date_retro from
obl@pba_link
        where obl_date_retro > ''10-AUG-2002''
      order by 4,5 desc ');
     owa_util.listprint(po_cursor,'i_po_number',null,null);

ORA-01858: a non-numeric character was found where a numeric was expected
ORA-02063: preceding line from PBA_LINK
ORA-06512: at "SYS.DBMS_SYS_SQL", line 1123
ORA-06512: at "SYS.DBMS_SQL", line 321
ORA-06512: at "SYS.OWA_UTIL", line 1890
ORA-06512: at "IMSADMIN.PROCESS_RECEIPT", line 39
ORA-06512: at line 7

          Followup:

Toms rule #1

"Never never never

compare dates to strings
strings to numbers
number to dates

Always Always Always

compare dates to dates
strings to string
number to numbers

and never do it any other way."

ops$tkyte@ORA817DEV.US.ORACLE.COM> select to_date( 'ab' ) from dual;
select to_date( 'ab' ) from dual
                *
ERROR at line 1:
ORA-01858: a non-numeric character was found where a numeric was expected

> oerr ora 1858
01858, 00000, "a non-numeric character was found where a numeric was expected"
// *Cause: The input data to be converted using a date format model was
//  incorrect.  The input data did not contain a number where a number was
//  required by the format model.
// *Action:  Fix the input data or the date format model to make sure the
//  elements match in number and type.  Then retry the operation.

your remote database or your local database isn't using the format you are
expecting.

This leads to Toms rule #2

Always use an explicit date format mask.
Never rely on implicit conversions -- you'll get bitten every time

where obl_date_retro > to_date( ''10-AUG-2002'', ''dd-mon-yyyy'' )

should fix that right up.

      Rating:
      5
listrprint  August 21, 2002
Reviewer:
      Mo  from Reston, USA
TOm:

Thanks it works. But this is cinfuing.

the query worked fine in sql*plus

and

obl_date_retro is column of type date
and '01-AUG-02' is a valid internal data format.

so i was comparing a date column to a date value.

Where am I wrong?

          Followup:

sigh,

follow toms rules number 1 and 2.  always, every time, never ceasing -- and
you'll never ever be sorry you did.

Don't follow them and you'll be slapping your forehead lots (tell me, if the dba
or even the CLIENT running your code changes something, will MY code break?  --
rhetorical question, answer = no.  Will your code be affected adversely?  --
another rhetorical question, answer = YES)

Use to_date.
Use an explicit format.
Never do it any other way.

You were comparing a DATE (olb_date_retro) to a STRING '01-AUG-02'.  The server
is applying IMPLICIT conversion to either turn the date into a string or the
string into a date.  You never ever want to have IMPLICIT conversions happen --
they are BUGS just waiting to happen.  You hit one -- your IMPLICIT conversion
of the string '01-aug-2002' failed with an ora-1858 because apparently the
default date mask on the remote system is *not* what you were expecting for
whatever reason.

In order to compare a date to a date you MUST use:

obl_date_retro = to_date( '01-aug-2002', 'dd-mon-yyyy' )

Now, that is a date compared to a date.  It'll never fail.

      Rating:
      5
listrprint  November 28, 2002
Reviewer:
      mo
Tom:

I am trying to sort names by lowercase and I got an error when I did "order by
lower(name)"

so I did select lower(name) in select and sort by it.  Is not this inefficient?

state_cursor:=owa_util.bind_variables('select null,'' Pick One
-->'',''x'',1,null
     from dual
       union all
        select person_id,name,null,2,lower(name) from state
        order by 4,5');

2.  did oracle do any changes to the listprint in 9i so you allow to use more
parameters?

          Followup:

Of course you cannot order by lower(name) -- in your union all query there is NO
column "name" to "lower".

ineffecient?  no.  it is just fine

listprint does what it does and hasn't been added to.

      Rating:
      5
listrpint  November 29, 2002
Reviewer:
      mo
Tom:

yes i had one it was like this

state_cursor:=owa_util.bind_variables('select null,'' Pick One
-->'' name,''x'',1
     from dual
       union all
        select person_id,name,null,2 from state
        order by 4,lower(name)');

          Followup:

 thats the way SQL works.

'select *
   from ( select null, '' Pick One -->'' name, ''x'', 1 oc
            from dual
           union all
          select person_id, name, null, 2 oc
            from state
        )
  order by oc, lower(name)'

now, that would work.

      Rating:
      5
listprint  May 16, 2003
Reviewer:
      mo
Tom:

1.  IS there a way to add like a separator (------) between the values in the
dropdown list created by the listprint.  I have a long list of stock numbers,
and I want to put ----- after AA001, AB001 etc.

AA001
AA002
AA003
-----
AB001
AB002
-----
AC004

etc.

          Followup:

tell me, can you write a query that does that?

If so, sure you can.  The trick will be writing that query ;)

you will most likely find it most efficient to just do this yourself -- in a
loop.

      Rating:
      5
listprint  May 17, 2003
Reviewer:
      mo
Well I thought there might be something in Analytical functions you can do or a
group by.

If you do it using a loop, do you always have to compare the first two letters
of stock number with the previous value before you decide to put the dashed line
or not?

May 16, 2003 Kailash -- Thanks for the question regarding
      "Table Reorg", version 8.1.7

      You Asked
Most of the DBA books on Oracle discuss that if a table has lot of empty blocks
below the HWM, the best way to reorg is to do the following steps :

1. CREATE TABLE N AS select * from O
2. Drop O
3. Create Owith appropriate INITIAL_EXTENT
4. CREATE TABLE O AS SELECT * FROM N

My question is :

Qn 1.Whenever you do a FTS, the table reads all of the blocks below the HWM. So
in this case all of the empty blocks below HWM in A will go into B as well. Is
this right or is my understanding wrong?

Qn 2.If a FTS is performed, Oracle ignores any empty blocks it finds on it's way
and considers only blocks with meaningful data.

Which one of the above questions is true? Qn 1 or Qn 2

Suppose Qn 1 is TRUE, then how table reorg is achieved by following steps 1 thru
4 given above? Please explain

      and we said...
I would prefer

alter table T move;

But if your books really have those four steps -- it is time for a new book.  At
worst it would be:

create table N as select * from O;
drop O;
rename N to O;

and you would NEVER ever use initial/next or pctincrease since we all know that
locally managed tablespace are the way to go and storage parameters should
not be used....

IF I felt the serious burning desire to reorg it at all (which i almost never
ever do since it is a fact that the table will just go back to the way it is
all over again)...

if I full scan lots, maybe, index read -- nah.

q1) that would be wrong - the RESULTS of the full scan (the rows) are fed into
the insert -- not the raw blocks

q2) Oracle does not ignore them, it reads them, processes them and returns any
rows from them.  No rows -- no output -- it is not ignored however.

Review &
      Followup

      Rating: 4
Storage
      Parameter  March 15, 2003
Reviewer:
      Lakshminarasimhan R  from Dubai
Hi Tom

You have mentioned above that

<
and you would NEVER ever use initial/next or pctincrease since we all know that
locally managed tablespace are the way to go and storage parameters should
not be used....
>

If we are not giving any storage parameter, table will automatically inherit
Tablespace parameters isnt it.  that means we need to give some general storage
parameter (like initial, next) to Tablespace to propagate the same to tables?

          Followup:

read about LOCALLY MANAGED TABLESPACES

and never use storage parameters again, be free from this arduous task.

You will either use

LMTs with uniform extent sizes -- initial=next and pctincrease=0 and the DBA
decides what the extent size will be.

LMTs with system managed extent sizes -- the system decides automagically what
the extent sizes will be.

both prevent fragmentation and since it matters not if a segment is in 1, 100,
or 1000 extents -- you need not worry about that either (maxextents is always
UNLIMITED in LMTs).

      Rating: 5
Any downside to
      LMT?  March 17, 2003
Reviewer:  Chandru
       from Harlingen, TX
I always hear great things about LMT. Is there any downside with regard to LMT?
Any side effects that you know? Is LMT the perfect architecture that Oracle has
evolved with respect to space management? Thanks.

          Followup:

I have found no downsides.

      Rating: 5
More insight
      needed  April 30, 2003
Reviewer:  Arun Gupta
       from Harrisburg, PA USA
Tom
I need more on when to use uniform extent size and when to use auto allocate,
particulary for web based OLTP application.

What is the best method for converting from uniform allocation to auto allocate
in 9i.

Thanks

          Followup:

do you know the size of the object (what it will be forever?)
do you know how fast the object should grow (for objects that grow over time)

if so, uniform is for you -- you would pick an extent size for the "static"
object (that which you know the size of) that results in a number of extents
that pleases you (no magic number there -- somewhere between 1 and 2 billion)

Or you would pick an extent size that caused the object to extend about once a
month -- so you can watch and monitor it's growth over time.

Otherwise, you may as well use system allocated extents.

ALTER TABLE move is the "best" way to convert.

      Rating: 3
RECREATE
      INDEXES  May 15, 2003
Reviewer:  Jack
       from Columbus, OH USA
Tom,
We bought a product and that was put into production longback. When we brought
the product we have one ORDER_ITEMS table which was having composite indexes
(one index with multiple columns). Right now we have about 15 million rows in
this table. We have 7 indexes similar to this. We want to drop all these indexes
and and want to create new indexes (as the existing composite indexes are
becoming useless).
We would like to keep the downtime as min. as possible. This is a online
production system. Is there a way we can approach this.
(This is a 8.17.4 oracle db and no near plans of moving to 9i). Highly
appreciate your suggestion.

          Followup:

why are the existing composite indexes "becoming useless"

      Rating: 2
INDEX
      REBUID  May 16, 2003
Reviewer:  jack
       from Columbus, OH USA
Business requirements changed over the time and hence the where clauses are
looking at different columns now (instead of the first column in the composite
index).
pl.suggest a good practical way to do this

          Followup:

that is not an index rebuild.

that is a change in the application.

You can build indexes "online"

ops$tkyte@ORA817DEV> create index t_idx on t(x) online;

Index created.

      Rating: 4
Well,
      sometimes...  May 16, 2003
Reviewer:  Brent
       from Salt Lake City, UT
---
and you would NEVER ever use initial/next or pctincrease since we all know that
locally managed tablespace are the way to go and storage parameters should
not be used....
---

Well, that's not strictly true.  I completely agree the LMT's are the way to go.

One case that comes to mind, though, is an application we use that creates the
tables with large initial extents sizes.  A lot of the these are lookup tables
that are created with 2mb or larger initial extents, but will never grow beyond
64k.  In this case an:

 alter table my_table move tablespace my_tablespace;

... will retain the 2mb initial extent. You would need to specify an initial
extent size in this case.

          Followup:

ahh but you broke the FIRST RULE.

You used a storage clause -- just like a lie leads to another lie, a storage
clause leads to another storage clause.

so, if you NEVER use them, you, well, never use them ;)

      Rating: 5
Agreed,
      but...  May 16, 2003
Reviewer:  Brent
       from Salt Lake City, UT
The app was originally written for Oracle 7, and the scripts the vendor supplied
creates the tables (a few dozen).

Usually I just edit them where I can, then run them... sometimes it's easier the
other way around.

May 16, 2003 Stanley -- Thanks for the question regarding
      "Numeric Or Value Error", version 9i

      You Asked
Hi Tom,

   Need you help.

   We're on the process of moving our application from Oracle 7.3 to 9i and we
encounter an error in runtime(Numeric or Value Error). But the same works fine
in 7.3

   I Have a html form which is generated using the htp and htf packages. When I
do a submit I call a packaged procedure.

We tried to simulate the error by writing a small code. Here it goes

18:00:44 Stanley-Intereq>ed test

Create Or Replace Package TestPackage
Is

Procedure TestProcedure1;

Procedure TestProcedure2(Field1 In Out Varchar2);

End;
/
Show Errors

Create Or Replace Package Body TestPackage
Is

Procedure TestProcedure1
Is
Begin
   htp.htmlopen;
   htp.bodyopen('');
   htp.formopen(Owa_Util.Get_Owa_Service_Path||
                'SNSDIA.TestPackage.TestProcedure2', 'Post');
   htp.Tableopen;
   htp.Tablerowopen;
   htp.Tabledata('This is test package for Numeric or Value Error');
   htp.tablerowclose;

   htp.Tablerowopen;
   htp.Tabledata(htf.formtext('Field1', 2, 2));
   htp.tablerowclose;
   htp.tableclose;

   htp.br;
   htp.br;

   htp.Tableopen;
   htp.Tablerowopen;
   htp.Tabledata(htf.formsubmit('', 'Submit'));
   htp.tablerowclose;
   htp.tableclose;

   htp.formclose;
   htp.bodyclose;
   htp.htmlclose;
End;

Procedure TestProcedure2(Field1 In Out Varchar2)
Is
Begin
   Field1 := '0'||Trim(Field1);
   htp.htmlopen;
   htp.bodyopen('');
   htp.Tableopen;
   htp.Tablerowopen;
   htp.Tabledata('The value passed is '||Field1);
   htp.tablerowclose;
   htp.tableclose;

   htp.br;
   htp.br;

   htp.bodyclose;
   htp.htmlclose;
Exception
When Others
Then
   htp.htmlopen;
   htp.bodyopen('');
   htp.Tableopen;
   htp.Tablerowopen;
   htp.Tabledata(Sqlerrm);
   htp.tablerowclose;
   htp.tableclose;

   htp.br;
   htp.br;
   htp.bodyclose;
   htp.htmlclose;
End;

End;
/
Show Errors

18:02:52 Stanley-Intereq>sta test
Package created.

No errors.

Package body created.

No errors.
18:03:06 Stanley-Intereq>Grant Execute On TestPackage To Public;

Grant succeeded.

18:03:56 Stanley-Intereq>

When we try to call the procdure(TestPackage.TestProcedure1) it displays the
text field and the submit button. After entering 1 in the textfield and press
the Submit button its giving a Numeric or Value error at ( Field1 :=
'0'||Trim(Field1); ).

Here Field1 is an IN OUT parameter of Varchar2 datatype.

But in the command prompt when i wrote a small pl/sql block it works fine

18:14:49 Stanley-Intereq>Set Serveroutput On
18:14:54 Stanley-Intereq>Declare
18:14:56   2     Field1 Varchar2(2) := 1;
18:15:07   3  Begin
18:15:09   4     Field1 := '0'||Trim(Field1);
18:15:31   5     Dbms_Output.Put_Line(Field1);
18:15:48   6  End;
18:15:50   7  /
01

PL/SQL procedure successfully completed.

18:15:52 Stanley-Intereq>

Would appreciate if you can explain us why this errors occurs in the previous
case using htp and htf packages.

Thanks in advance

Regards
Stanley

      and we said...
Ouch -- I see the problem (and I'm EXTREMELY surprised this worked in 8i and
before -- there should be issues with it as well).  The problem lies with this:

Procedure TestProcedure2(Field1 In Out Varchar2)
Is
Begin
....

The PLSQL cartridge/module is sending to you a 1 character input field as a BIND
input.  It is responsible for setting the length of this field.  When you enter
"1" into the form and submit it to the plsql component - the app server sets up
a 1 character field and binds it.  You should NEVER use IN OUT or OUT with
procedures called from the web -- the app server is not expecting you to modify
the data!!  If you modify it the changes go nowhere.

This is what is happening:

ops$tkyte@ORA8I.WORLD> variable x varchar2(1)
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> begin
  2          :x := 'xx';
  3  end;
  4  /
begin
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at line 2

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> variable y varchar2(2)
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> begin
  2          :y := 'xx';
  3  end;
  4  /

PL/SQL procedure successfully completed.

See how the first one, since we bind a varchar2(1) blows up -- the second does
not.

You should never use an OUT parameter in these procedures.

You should code this as:

Procedure TestProcedure2(Field1 In Out Varchar2)
Is
   l_field1 varchar2(25) default field1;
Begin
   l_Field1 := '0'||Trim(l_Field1);
 ....

and reference l_field1 in the code.

---------------------- old stuff ------------------------------------------
Please give me a test case, for example take this code:

ops$tkyte@ORA9I.WORLD> declare
  2          vcp_unit_no varchar2(2) default '1';
  3  begin
  4      If Length(Ltrim(Rtrim(vcP_Unit_No))) = 1 And
  5         Ascii(Ltrim(Rtrim(vcP_Unit_No))) Between 48 and 57
  6      Then
  7         vcP_Unit_No := '0'||Ltrim(Rtrim(vcP_Unit_No));
  8      End if;
  9          dbms_output.put_line( vcp_unit_no );
 10  end;
 11  /
01

PL/SQL procedure successfully completed.

and show me what I need to do in order to make it "break".

I can say that if vcp_unit_no is a number hiding in a string, then:

ops$tkyte@ORA9I.WORLD> declare
  2          vcp_unit_no varchar2(2) default '1';
  3  begin
  4          vcp_unit_no := to_char( to_number( vcp_unit_no ), 'fm00' );
  5          dbms_output.put_line( vcp_unit_no );
  6  end;
  7  /
01

PL/SQL procedure successfully completed.

that one line of code would be a better way to convert it.
Review &
      Followup

      Rating: 5
Numeric Or
      Value Error  December 31, 2001
Reviewer:
      Stanley Daniel  from Singapore
Thank you so much Tom. It was really helpful and helped us to understand how the
processing takes place. Thanks once again.

      Rating:
      3
owa_util.ident_Arr  May 01, 2002
Reviewer:
      Bhavani  from India
Hi Tom,

In a Procedure I am taking Bunch of values as owa_util.ident_Arr.

create or replace procedure procedure_name
(question_id owa_util.ident_Arr,
 in_question owa_util.ident_Arr)

I am getting this Following error if length of the any of the input values, are
more than 30 characters.

ORA-06502: PL/SQL: numeric or value error: character string buffer too small

If all the values i passed thru this array are less than 30 characters length,
its executing fine.

can you please suggest us a solution?

Thank you Tom.
Bhavani

          Followup:

use owa.vc_arr instead.

Or BETTER YET, create your own type:

create package types
as
   type myArray is table of varchar2(32000) index by binary_integer;
   empty myArray;
end;
/

Now you can:

create procedure p ( x in types.myArray default types.empty )
....

      Rating: 5
why
      ?  March 26, 2003
Reviewer:  A reader

SQL>exec p_pkg.get_prc_all(50,:rc,:out_num,:msg_cd,:msg_str);
BEGIN p_pkg.get_prc_all(50,:rc,:out_num,:msg_cd,:msg_str); END;

*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SCOTT.P_PKG", line 49
ORA-06512: at line 1

SQL>exec p_pkg.get_prc_all(50,:rc,:out_num,:msg_cd,:msg_str);

PL/SQL procedure successfully completed.

SQL>print :msg_cd

MSG_CD
----------------
SUCCESS

SQL*>print :str

STR
------------------------------------

/**********************************************************/

 1st time it is an error but 2nd , 3rd,4th ... it works fine
can you please tell me why ?

 in this stored proc. I don't have any temp variables or buffers that I hold
data in it just opens a ref cursor and sends it back

Thanks,

          Followup:

I would actually need to see the code.  I can reproduce this -- but i've no idea
if it is the same cause

ops$tkyte@ORA920> exec demo_pkg.p
BEGIN demo_pkg.p; END;

*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at "OPS$TKYTE.DEMO_PKG", line 49
ORA-06512: at line 1

ops$tkyte@ORA920> exec demo_pkg.p
hello world

PL/SQL procedure successfully completed.

...
create or replace package body demo_pkg
as
    g_num number;

    procedure p
    is
    begin
        dbms_output.put_line( 'hello world' );
    end;

.....

begin
    g_num := 'a';
end;
/

      Rating: 5
the code is
      very big ...  March 26, 2003
Reviewer:
      A reader
Hi tom, thx for your reply.

   but as i said, I do not have any variables
I am *NOT* moving any field into any variables
so there is no type conversion or not any kind of buffer overflow !!

          Followup:

but as I said...

I would actually need to see the code.... I would actually need to reproduce
this.

      Rating: 3
Why am I
      getting this  May 16, 2003
Reviewer:  Raju
       from USA
Can you please help.I am trying to run the following code and get

DECLARE
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at line 75
----
set pages 10000;
set lines 600;
set serveroutput on size 1000000;
DECLARE
l_file                       UTL_FILE.file_type;
l_last_filename              varchar2(255) := '0000000';
l_cust                       varchar2(45) := '00000Q.XLS';
l_custid                     varchar2(6) :=' ';
l_custnm                     varchar2(45) := ' ';
l_custid_prev                varchar2(6) := ' ';
l_fcst_mth1                  number(13,2) := 0.00;
l_fcst_mth2                  number(13,2) := 0.00;
l_fcst_mth3                  number(13,2) := 0.00;
l_fcst_qtr                   number(13,2) := 0.00;
CURSOR forecast_cur is
select v.cust_corp_id c1, substr(v.CUST_CORP_NM,1,5) c2,
SUM(decode(to_char(b.FCST_DT,'MON'),'JAN',b.FCST_AMT,null)) c3,
SUM(decode(to_char(b.FCST_DT,'MON'),'FEB',b.FCST_AMT,null)) c4,
SUM(decode(to_char(b.FCST_DT,'MON'),'MAR',b.FCST_AMT,null)) c5,
SUM(decode(to_char(b.FCST_DT,'MON'),'APR',B.FCST_AMT,null)) c6,
SUM(decode(to_char(b.FCST_DT,'MON'),'MAY',B.FCST_AMT,null)) c7,
SUM(decode(to_char(b.FCST_DT,'MON'),'JUN',B.FCST_AMT,null)) c8,
SUM(decode(to_char(b.FCST_DT,'MON'),'JUL',B.FCST_AMT,null)) c9,
SUM(decode(to_char(b.FCST_DT,'MON'),'AUG',B.FCST_AMT,null)) c10,
SUM(decode(to_char(b.FCST_DT,'MON'),'SEP',B.FCST_AMT,null)) c11,
SUM(decode(to_char(b.FCST_DT,'MON'),'OCT',B.FCST_AMT,null)) c12,
SUM(decode(to_char(b.FCST_DT,'MON'),'NOV',B.FCST_AMT,null)) c13,
SUM(decode(to_char(b.FCST_DT,'MON'),'DEC',B.FCST_AMT,null)) c14
from cso.FCST b,
cso.cust_corp_ppl_vw v
where b.cust_corp_id= v.cust_corp_id
and
to_char(b.FCST_DT, 'YYYY/MM') in ( '2003/01', '2003/02', '2003/03') and
v.cust_corp_id in ( '040116','027245','027278','034018','068970','036324',
'000549')
group by v.cust_corp_id, v.CUST_CORP_NM;
BEGIN
l_cust := '0000001Q.XLS';
l_last_filename := '9999999Q.XLS';
IF (UTL_FILE.IS_OPEN(l_file)) THEN
UTL_FILE.FCLOSE(l_file);
END IF;
l_file := UTL_FILE.FOPEN('/tmp',l_cust,'w',32767);
l_custid                     := ' ';
l_custid_prev                := ' ';
l_fcst_mth1                  := 0.00;
l_fcst_mth2                  := 0.00;
l_fcst_mth3                  := 0.00;
l_fcst_qtr                   := 0.00;
FOR i in forecast_cur LOOP
IF forecast_cur%ROWCOUNT = 1 THEN
l_custid_prev := i.c1;
END IF;
END LOOP;
FOR i in forecast_cur LOOP
IF l_cust <> l_last_filename
THEN
UTL_FILE.PUT_LINE(l_file,'Jan|Feb|Mar|Q1');
END IF;
l_last_filename := l_cust;
l_custid := i.c1;
IF l_custid_prev = l_custid THEN
UTL_FILE.PUT_LINE(l_file, nvl(i.c2,0) ||'|'|| nvl(i.c3,0)
||'|'||nvl(i.c4,0)||'|');
l_fcst_mth1                  := 0.00;
l_fcst_mth2                  := 0.00;
l_fcst_mth3                  := 0.00;
l_fcst_qtr                   := 0.00;

l_custid_prev := i.c1;
END IF;
END LOOP;
FOR i in forecast_cur LOOP
IF forecast_cur%ROWCOUNT = 1 THEN
l_custid_prev := i.c1;
END IF;
END LOOP;
FOR i in forecast_cur LOOP
l_cust := i.c2 || 'Q.XLS';
l_custid := i.c3;
IF l_cust <> l_last_filename THEN
IF (UTL_FILE.IS_OPEN(l_file)) THEN
IF forecast_cur%ROWCOUNT <> 1 AND l_custid <> l_custid_prev THEN
UTL_FILE.PUT_LINE(l_file,'Jan|Feb|Mar|Q1');
END IF;
l_custid_prev := i.c1;
UTL_FILE.FCLOSE(l_file);
END IF;
l_file := UTL_FILE.FOPEN('/tmp',l_cust,'w',32767);
UTL_FILE.PUT_LINE(l_file,'Jan|Feb|Mar|Q1');
END IF;
l_last_filename := l_cust;
END LOOP;
IF (UTL_FILE.IS_OPEN(l_file)) THEN
UTL_FILE.FCLOSE(l_file);
END IF;
END;
/
-----

          Followup:

apparently, on line 75 -- whatever that line is, impossible for me to say -- you
have a character string buffer that is too small.

run this in sqlplus.

sqlplus will show you the line number.

It should be very obvious as to the mistake you made.

      Rating:
      3
Sorry  May 16, 2003
Reviewer:  Raju
       from US
Please forget my last post. Fixed the error.

Thanks

May 16, 2003 Fred -- Thanks for the question regarding "Are
      There Ramifications In Using "EXECUTE IMMEDIATE" In A Repeated Fashion?",
      version 8.1.7

      You Asked
Hi Tom,

I remember reading somewhere that if you invoke "EXECUTE IMMEDIATE" repeatedly
(like in a loop), you will waste system resources since it is meant to be used
only once.  My questions therefore are:

1.  Is my memory correct?  I am not a young man. ;)
2.  If it is correct, what can I do to basically perform a dynamic DML/DQL
action within a loop?  I am providing the column names dynamically in the WHERE
clause by storing the column_name from ALL_TABS_COLUMN into a variable.  This
and other variables are concatenated into one SQL statement, which is then 'fed'
into the EXECUTE IMMEDIATE during each loop iteration.
3.  Can I get around this restriction by creating a procedure for EXECUTE
IMMEDIATE and passing the parameters to this procedure from within the LOOP of
the calling program?  Or am I still faced with the same issue because it's all
happening within the same session?

Thanks.

Fred

      and we said...
1) it is correct

2) use DBMS_SQL.
   use bulk operations.
   use static sql ;)

3) no.

The problem with execute immediate is that every execute immediate is a
PARSE/BIND/EXECUTE/CLOSE.  so you end up with:

  loop
     PARSE/BIND/EXECUTE/CLOSE.
  end loop

when what you want to code is:

   parse
   loop
       bind/execute
   end loop
   close

in that case, you can use DBMS_SQL.  If you have my book "Expert one on one
Oracle" -- i spend quite a bit of time on this (proving the point) and explain
why the supposed death of DBMS_SQL is "premature"

Review &
      Followup

      Rating: 5
Procedure with
      DDL excecute immediate statements fails as dbms_job  April
      29, 2003
Reviewer:  pascal  from HH,Germany

Hi Tom,

thanks for a the Response above...

i have written a procedure with some execute immediate statements in it..and
these have some DDL/Alter statements such as :

execute immediate 'alter session set sort_area_size=100080' ;

execute immediate 'create or replace force view vw_test as
select c1,c2,.. from some_table' ;

execute immediate 'create table tbl_test as subquery here ' ;

execute immediate 'drop table tbl_test ' ;

Those are just example statements from this procedure .

This procedure works and runs just fine in sql Plus,
but when i tried to submit this procedure as a dbms_job , i get insufficient
privileges error .

Why is this happening..i am doing everything under the schema who owns the
objects .

Version 9iR2

Thanks in advance.

          Followup:

do you run it for real as a Procedure in sqlplus -- or as an anonymous block.

also, why in the world would you even dream of scheduling a recurring operation
that created and dropped tables and views -- that would be a slight "no no" --
something you don't want to consider doing.

      Rating: 5
Appericated,
      But...  April 30, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Hi Tom,
Now I understand more clearly the places where to use EXECUTE IMMEDIATE and
where to choose the good old DBMS_SQL package.
But, when you say this:

"loop
     PARSE/BIND/EXECUTE/CLOSE.
 end loop"

Does this apply even if we use the

EXECUTE IMMEDIATE stmt USING bind_variable_values ;

Will it still do the PARSE/BIND/EXECUTE/CLOSE for every iteration? Will it be
able to find the parse tree during the subsequent iterations of the loop?

          Followup:

execute immediate is ALWAYS

parse/bind/execte/close

the parse might be soft, might be hard -- but a parse is a parse is a parse and
is something you want to limit.

      Rating: 5
insufficient
      Privileges continued....Pascal from above...  April 30, 2003

Reviewer:  pascal  from Hamburg,Germany
Hi Tom,

Thanks very much for your follow-up above...

Yes, i ran it once as a procedure in sql plus and it worked fine i.e

sql_prompt> exec  exec_immed_as_cron_dbms  ;

Also, i need to run these statements only once at the end of the Month ,so it's
OK in my case
and
also my procedure has only 1 create table , drop table if the table is already
there and then create view statements for about 4 views based on the Table
created above ; plus the alter session clause .

so as you can see it's not too much parse activity going on there if i run it
once per Month.

But all in all , i was just surprized how come it doesn't run when submitted as
dbms_job .

Thanks in advance.

          Followup:

something else happened.  can you prove to me that it didn't work -- something
changed (you lost the grants).

do this:

exec exec_immed_as_cron_dbms;
variable n number
exec dbms_job.submit( :n, 'exec_immed_as_cron_dbms;' );
exec dbms_job.run(:n)

cut and paste the results.

I don't care if this is a once a month thing, you have a requirement for a table
-- table and views should exist from day 1.  they should NOT be in a procedure.
that is just asking for heartburn every month (insufficient privs, out of space,
error doing something, view cannot create and we didn't realize it, etc etc etc)

      Rating: 4
insuficien
      privileges  May 16, 2003
Reviewer:  Adrian
       from Lima, Peru
Hi Tom,
I'm conn as dba in a standar edition oracle 8i, i made an stored procedure like
this:
create or replace procedure dynamic ( query varchar2) is
begin
   execute immediate query;
exception when others then dbms_output.put_line(sqlerrm);
end;
/
when i run this i got:

SQL> exec dynamic ( 'create table T0006 as select * form b where 1 = 2 ');

ORA-01031: insufficient privileges

but if i run the script:

SQL> create table T0006 as select * from b where 1 = 2;

Table created.

can u explain this behavior? wich privilege i nedd to do this?

          Followup:

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html

May 17, 2003 Suresh -- Thanks for the question regarding
      "Analyze and DBMS_STATS", version 8.1.7

      You Asked
Tom,
   Could you please tell me if there are any other important differences,
advantages with DBMS_STATS over ANALYZE other than the points listed below.

1. DBMS_STATS can be done in parallel
2. Monitoring can be done and stale statistics can be collected for
   changed rows using DBMS_STATS.

Thanks,
Suresh

      and we said...
you can import/export/set statistics directly with dbms_stats

it is easier to automate with dbms_stats (it is procedural, analyze is just a
command)

dbms_stats is the stated, preferred method of collecting statisttics.

dbms_stats can analyze external tables, analyze cannot.

DBMS_STATS gathers statistics only for cost-based optimization; it does not
gather other statistics. For example, the table statistics gathered by
DBMS_STATS include the number of rows, number of blocks currently containing
data, and average row length but not the number of chained rows, average free
space, or number of unused data blocks.

dbms_stats (in 9i) can gather system stats (new)

ANALYZE calculates global statistics for partitioned tables and indexes instead
of gathering them directly. This can lead to inaccuracies for some statistics,
such as the number of distinct values.  DBMS_Stats won't do that.

Most importantly, in the future, ANALYZE will not collect statistics needed by
the cost-based optimizer.
Review & Followup

      Rating: 3
Incomplete
      statistics?  June 23, 2002
Reviewer:  Jayaraman
      Ashok  from India
Hi Tom
You have replied that dbms_stats does not gather information on chained
rows,unused data blocks and average free space available in blocks. Does it mean
that to get the above mentioned information we have to run analyze command
again? Also you mention that dbms_stats gathers statistics required by the CBO.
Are there any special statistics that are gathered apart from number of rows,
number of blocks occupied? If it is only the above two, then i believe that
analyze command already accomplishes it. The only feature that is of use may be
the set_stats procedure for tuning queries before using them in production.

Thanks & regards,
Ashok

          Followup:

dbms_stats only gathers things the CBO cares about.  The CBO does not care about
that information.

In the future, analyze will not gather CBO info, only these other things CBO
doesn't care about.  DBMS_STATS currently and will only gather CBO info.

I think you need to reread the answer.  dbms_stats gathers everything that the
analyze command does but.... can do it in parallel, can do it for stale tables,
can export/import stats, can analyze things analyze cannot (the system for
example) and so on.  There are lots more stats then just num rows and num blocks
-- and both commands collect them.  There are things dbms_stats will do that
analyze does not (especially on a partitioned table).

Recommendation: use dbms_stats.

      Rating:
      5
  June 24, 2002
Reviewer:  Dave
       from Dayton, OH
When I "CREATE INDEX my_index ... COMPUTE STATISTICS" is the analyze being
performed by the DBMS_STATS method, or the ANALYZE INDEX method?

If by the latter method, am I likely to gain by running DBMS_STATS post-CREATE
or post-REBUILD mostly for partitioned segments (ref. your comments on gathering
global statistics), with "ANALYZE ..." still being a reasonable choice at the
moment for non-partitioned segments?

          Followup:

It is closer to the analyze method -- only because it is DDL (same results from
analyze, dbms_stats, create index ultimately).

But since you only create an index once, it is good only for the initial build,
after that -- you are back to dbms_stats.

It is a general recommendation to use dbms_stats, period.  There will come a
time when dbms_stats is the only way.

      Rating: 5
external table,
      index and dbms_stats  June 24, 2002
Reviewer:
      Suresh  from New York
Tom,
   You have mentioned that DBMS_STATS can analyze external tables. I tried to
create an index on an external table and it came up with an error "not supported
on external table".
My question is if dbms_stats could analyze external tables, why not allow an
index on external table as well and analyze it with dbms_stats.

          Followup:

you cannot index something that we have no control over.

Consider if we did.  say the file started with:

1,a,hello
2,b,goodbye

and we indexed that (the first column).  Now, you come along and replace that
file with:

2,b,goodbye
1,a,hello

and issue: select * from external_table where column1 = 1;

what do you think would happen if

a) we used an index
b) we full scanned

I think we would get two answers.  We cannot index a file that is changeable
(and changeable by forces outside of our control)

      Rating: 5
Queries on
      Analyze  June 24, 2002
Reviewer:  Sanjay Jha
       from Toronto, Canada
Tom,
The ongoing discussion has generated few more related queries in me. I am
listing them below, for you crisp and clear answers :
1. What are different packages for running analyze (eg. dbms_utulity,
dbms_stats, db_maintenance etc.) and what are the advantages of each?
2. What is the best way to analyze one particular table from CBO's
perspective(BTW, what are all possible ways)?
3. If someone has to automate the analyze, what do you recommend and why -
scheduling through cron or dba_jobs? (For that matter what is your opinion on
scheduling any job for database- OS level or through Oracle jobs?)
4. This question is not related to analyze but to the Oracle supplied packages-
Where in a databse one can get info about these packages (like v$ views etc.) or
give me an url for reference please.

Thanks.

          Followup:

1) dbms_stats is the one you should use. dbms_utility has some very very
primitive functions left in there for backwards compatibility only.

2) for that, please read the ADMIN guide, it covers the methods and when to use
what.

You know -- if there was a "best way", it would be the only way...

3) If you have my book -- you know my opinion on this.  Do it in the database.

Cron -- can cron tell if the database is up and running?  cron will try to
analyze even when the database is not ready.  DBMS_JOBS is the right tool for
this in my opinion (barring that, OEM would be the right place).  I want all of
my database related stuff going on in the database -- one place to look for
issues (my alert log or OEM logs) and so on.

4) how about the supplied packages guide.  Instead of a direct link, I'll teach
you to fish.

goto otn.oracle.com
click on documentation
pick what database version you have

go after the supplied packages guide (in 8i and before, this was with the
"database" set of docs.  In 9i -- there is just one big list)

      Rating: 5
DBMS_STATS and
      statistics  May 02, 2003
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
Tom
I did the following:
a) Using dbms_stats, analyzed all the tables in schema X of database A and using
dbms_stats.export_schema_stats, exported the statistics into stattab.
b) Extracted the table/index creation scripts for schema X, excluding the
stattab table.
c) Using Oracle export (exp), exported the stattab table.
d) Using the scripts in b), I created all the tables and indexes in schema Y of
database B. These tables do not contain even a single row of data.
e) Using Oracle import(imp), imported the stattab into schema Y of database B.
f) Using dbms_stats.import_schema_stats, I imported all the statistics from
stattab.

I cannot see the statistics like num_rows, avg_row_len etc. in user_tables. How
does the CBO read these statistics?

Thanks

          Followup:

dbms_stats was designed to move the stats for user A from db1 to user A in db2.

It is the X to Y that is messing you up.  This demonstrates what you are seeing,
but shows how to adapt it to work anyway:

ops$tkyte@ORA920LAP> drop user a cascade;
User dropped.

ops$tkyte@ORA920LAP> drop user b cascade;
User dropped.

ops$tkyte@ORA920LAP> grant dba to a identified by a;
Grant succeeded.

ops$tkyte@ORA920LAP> grant dba to b identified by b;
Grant succeeded.

ops$tkyte@ORA920LAP> @connect a/a

a@ORA920LAP> create table t as select * from all_objects;
Table created.

a@ORA920LAP> create index t_idx on t(object_id);
Index created.

a@ORA920LAP> analyze table t compute statistics
  2  for table for all indexes for all indexed columns;
Table analyzed.

a@ORA920LAP> exec dbms_stats.create_stat_table( user, 'st' );
PL/SQL procedure successfully completed.

a@ORA920LAP> exec dbms_stats.export_schema_stats( user, 'st' );
PL/SQL procedure successfully completed.

a@ORA920LAP> !exp userid=a/a tables=st

Export: Release 9.2.0.3.0 - Production on Fri May 2 20:08:56 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to: Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production
Export done in WE8ISO8859P1 character set and AL16UTF16 NCHAR character set

About to export specified tables via Conventional Path ...
. . exporting table                             ST         78 rows exported
Export terminated successfully without warnings.

a@ORA920LAP> @connect b/b

b@ORA920LAP> create table t as select * from all_objects where 1=0;
Table created.

b@ORA920LAP> create index t_idx on t(object_id);
Index created.

b@ORA920LAP> !imp userid=b/b fromuser=a touser=b

Import: Release 9.2.0.3.0 - Production on Fri May 2 20:08:56 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to: Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production

Export file created by EXPORT:V09.02.00 via conventional path

Warning: the objects were exported by A, not by you

import done in WE8ISO8859P1 character set and AL16UTF16 NCHAR character set
. . importing table                           "ST"         78 rows imported
Import terminated successfully without warnings.

b@ORA920LAP> exec dbms_stats.import_schema_stats( user, 'st' );

PL/SQL procedure successfully completed.

b@ORA920LAP> select table_name, num_rows from user_tables;

TABLE_NAME                       NUM_ROWS
------------------------------ ----------
ST
T

that is what you currently see but:

b@ORA920LAP> update st
  2  set c5 = USER
  3  /

78 rows updated.

b@ORA920LAP> exec dbms_stats.import_schema_stats( user, 'st' );

PL/SQL procedure successfully completed.

b@ORA920LAP> select table_name, num_rows from user_tables;

TABLE_NAME                       NUM_ROWS
------------------------------ ----------
ST
T                                   30557

b@ORA920LAP>

that'll "rename the user" in the stats tab so it'll be looking for B.T, not
A.T

      Rating: 5
It
      works  May 02, 2003
Reviewer:  Arun Gupta
       from Harrisburg, PA USA
After updating c5 in stattab to new schema name, it works.
Thanks...!!

      Rating: 5
What about
      execution statistics  May 06, 2003
Reviewer:
      Arun Gupta  from Harrisburg, PA USA
Tom
I am a bit unclear about the purpose of importing statistics from one database
to another. My questions are:

a) If I set a table with 0 rows with the statistics of a 1 million row table,
can I run a query against this 0 row table and actually see the same buffer gets
as I would see against a 1 million row table?
b) When the query actually hits a 1 million row table, can Oracle change the
plan?

          Followup:

a) no
b) not sure what you mean.  if you mean "will the plan for a table with 900,000
rows be different then a plan for 1,000,000" -- the answer is "yes, it could
be".  If you mean "if the statistics say there are N rows but there are really M
rows -- will Oracle change the plan after finding there are not N rows?" -- the
answer is "no, once formulated -- the plan is the plan"

One use of the export/import stats trick is

a) you have a logical standby or some reporting instance that is a close copy of
production (using replication, whatever)

b) you run stats on the copy.  move them into production.

      Rating: 4
Problems in
      anlayze  May 07, 2003
Reviewer:  SUBBARAO.

There is much useful information on ANALYZE in this article. It is helpful. My
scenario is like this, looks strange but it is.

  In my production system, we have some problems in getting some reports from
the application.
For one report query has 8 tables, out of which 2 tables have huge number of
records, both these tables are not analyzed, rest all the tables are analyzed.
This report is not launching.
We have now analyzed the entire schema. Then the report started working. The
cost of the query before and after analyze has drastically come down. Some
tables will be getting different kinds of data, and some tables will be getting
same type of data.  I am not sure how long this report will work and when I
should analyze tables. I have read about the table monitoring feature and
document says that Oracle will automatically analyze the table if it requires
analyze.

My questions:
1) So enabling this feature, what will be the side effects in performance. I
know that if we want some thing we should scarifice few things, but I would like
to highlight these side effects to my boss.

2) If a query is having 5 tables and out of which 3 tables are not analyzed and
2 tables are analyzed, will the cost calculation be accurate? I mean will the
optimizer gives a better plan.
Is it required that all the tables in the query are to be analyzed or all not to
be analyzed.

          Followup:

1) monitoring is extremely low impact, not even measurable.  YOU still have to
analyze, you just use dbms_stats with the gather stale option.  it'll collect
stats only on tables you need.

2) of course the costing won't be accurate.  you should either have all or no
objects refered to by a query analyzed.

      Rating: 4
Excellent and
      Timely discussion....  May 07, 2003
Reviewer:
      A reader  from Mohsin/Dar-es-salaam/Tanzania
Hi Tom:

As usual, your answers allows us (DBA's) to have a good night's sleep.

On the issue of monitoring...

What criteria does oracle use to determine that the stats are stale ?
Can these thresholds be changed/set by the user ?
Are the stats in the user_tab_modifications cumulative ?
If I need to preserve the stats in the above view, can you please suggest a
procedure/script as to how I go about saving these. I am interested in
preserving the amount of dml activity by segment.

Thanks
Mohsin

          Followup:

o when about 10% of the table is changed

o no

o yes

o you would have to "insert into some_other_table select sysdate, a.* from the
modifications table A" before gathering stats.  Make that part of your
'process'.

      Rating: 4
Excessive
      Recursive calls after stats  May 07, 2003

Reviewer:  A reader  from
      Mohsin/Dar-es-salaam/Tanzania
Hi Tom:

I have observed that the statistics section of the sql+ trace reports excessive
recursive call count for queries agains the tables just after the tables have
been analyzed.

What does this mean and what are the side effects of stats gathering ? Has the
stats gathering invalidated the parsed sql in the shared pool ?

Would this be a good way of implementing my own thresholds:
I'd turn on monitoring for the segments.
Collect the dml activity from the user_tab_modifications to my own table and
then have my procedure re-compute the stats after say x% of change to the
segment and not use the gather stale option of the dbms_stats.
What is the timestamp column for ? If the values are cummulative then how does
it determine the incremental from the last stale stats run ?

Thanks.

          Followup:

hows about an example ?

      Rating: 5
Here it
      is.  May 07, 2003
Reviewer:  A reader
       from Mohsin/Dar-es-salaam/Tanzania
09:11:53 SQL> connect scott/tiger
Connected.
09:16:14 SQL>
09:16:14 SQL>
09:16:14 SQL> analyze table junk compute statistics for table for all indexed
columns for all indexes;

Table analyzed.

09:17:06 SQL> show user
USER is "SCOTT"
14:36:01 SQL> set autotrace traceonly explain statistics;
14:36:21 SQL>
14:36:23 SQL> select * from junk where owner='MMJ';

1 row selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=86)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'JUNK' (Cost=4 Card=1 Byt
          es=86)

   2    1     INDEX (RANGE SCAN) OF 'IDX_JUNK_OWNER' (NON-UNIQUE) (Cos
          t=3 Card=1)

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

14:36:37 SQL> /

1 row selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=86)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'JUNK' (Cost=4 Card=1 Byt
          es=86)

   2    1     INDEX (RANGE SCAN) OF 'IDX_JUNK_OWNER' (NON-UNIQUE) (Cos
          t=3 Card=1)

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

14:36:58 SQL>

          Followup:

analyzing the table will invalidate open cursors on that object in the shared
pool.  the next parse against that object will be "hard".  hard does lots more
recursive sql then "soft"

It is not the analyze so much as "you did something to cause hard parsing to
have to take place"

here is an example using the rbo and EMP:

ops$tkyte@ORA817DEV> select * from emp;

14 rows selected.

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

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

ops$tkyte@ORA817DEV> alter table emp add x number;

Table altered.

ops$tkyte@ORA817DEV> select * from emp;

14 rows selected.

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

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

Of course, SHUTDOWN/STARTUP would have the same effect.

normal, natural, expected and one time -- you hard parse and soft after that.

      Rating: 5
Some more
      info....  May 07, 2003
Reviewer:
      A reader
Hi Tom.

Here is some more info that might help you.

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
PL/SQL Release 9.2.0.1.0 - Production
CORE    9.2.0.1.0       Production
TNS for 32-bit Windows: Version 9.2.0.1.0 - Production
NLSRTL Version 9.2.0.1.0 - Production

The junk table is a mirror of dba_objects view.

And there was no activity in the d/b between the time 9:16  and 14:36 in my sql+
session results I posted earlier.

Please let me know if you need more info.

Thanks
Mohsin

      Rating: 3
effect of
      partial analyze  May 08, 2003
Reviewer:
      kumar  from India
Tom,

You have mentioned in one of your replies that we should not analyze certain
objects used in a query and leave the rest un-analyzed. I have read similar
statements in lots of places, but i could not really understand why. Can you
please explain why is it so dangerous to do so?

          Followup:

analogy time.

You are a manager now.

I give you a team of 4 people.

I tell you all about 2 of them -- their capabilities, experience, level of
comprehension, lots of stuff.

I tell you NOTHING about the other two.

I give you a problem to solve using these 4 people.  You need to make
assignments based on your knowledge of them.  You have good knowledge of 2, no
knowledge of the other 2.  You look at them and size them up.  Maybe you don't
like the way the 4th guy dressed -- he "looks dumb" to you.

You dole out responsibilities -- not giving much hard stuff to #4.
Unfortunately for you he was the smartest in the group and if he had been given
99% of the work -- it would have been a success.  As it was, due to inefficient
resource allocation -- you never finish your project!

So it is with statistics.  You give the optimizer only a little information, it
has to "make up the rest" and can come to some very bad decisions as to the
correct approach.

      Rating: 5
Hat's off to
      you...  May 08, 2003
Reviewer:
      A reader  from Mohsin/Dar/Tanzania
Hi Tom:

Excellent analogy about the CBO....

I will confirm your point, but I am positive nothing was happening on my d/b to
have caused invalidation of my cursors resulting in hard parse. I conducted the
test on my laptop, so I know what was happening.

Could the Oracle version have made the difference in this case?

I was using Oracle 9i Rel 2, and from your SQL+ prompt it seems you were using
8i.

Thanks
Mohsin

      Rating: 5
Ahhh the
      language barrier strikes again...  May 08, 2003

Reviewer:  A reader  from Mohsin/Dar/Tanzania.

Sorry Tom:

I read and re-read your response. I get it.

Once in a while in between Swahili, Gujarati, Katchi, Urdu, Hindi and a
sprinkling of Arabic I forget my English...

As they say in Swahili - Hakuna Matata (No Problem)

Kwaheri.

Thanks again.

      Rating: 5
Analogy worth
      more than 5 stars!!!!  May 08, 2003
Reviewer:
      A reader

      Rating: 3
Both Analyze
      and DBMS_STATS?  May 16, 2003
Reviewer:  Dan
      Jatnieks  from Redwood City, CA USA
In your original response you stated there are some statistics that DBMS_STATS
does not collect:

"...but not the number of chained rows, average free
space, or number of unused data blocks"

I would like to switch to using DBMS_STATS for CBO statistics on my 9i database,
but I also want to collect the statistics that DBMS_STATS does not; I have
monitoring software that collects and uses chained rows, avg free space and
unused data blocks.

Can I use Analyze first and then DBMS_STATS (in that order) to accomplish this?

The reasoning would be that first using Analyze will collect the data that
DBMS_STATS does not; then DBMS_STATS will overwrite the CBO statistics that
Analyze collected, but leave alone the other Analyze data.

Will that work?

          Followup:

it would work but it would be like using two cars to goto work -- it would be
double the work in most cases (partitioned tables being the exception).

can the monitoring software use "list chained rows" instead and can it use
dbms_space to get the other info.

May 17, 2003 Rama
      -- Thanks for the question regarding "User Function from SQL statement !",
      version 8.1.7

      You Asked
tom,

Say I have a  function "x".

Now, which of the following would be good:

1.select col, col2, x(col2) from table;

2. select col, col2, (select x(col2) from dual) from table;

similarly, what would  be preferred if I use it in where condition:

select col1, col2 from table where col2 = SomeFunction

select col1, col2 from table where col2 = (select SomeFunction from dual)

Thanks

      and we said...
#1 would be preferable to #2

select col1, col2 from table where col2 = SomeFunction
is sometimes worse then
select col1, col2 from table where col2 = (select SomeFunction from dual)
is sometimes worse then
select col1, col2 from table where col2 = (select SomeFunction from dual where
rownum > 0)

consider:

ops$tkyte@ORA920LAP> create table t as select * from scott.emp;

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create or replace function f( x in number ) return number
  2  as
  3  begin
  4          dbms_application_info.set_client_info( userenv('client_info')+1 );
  5          return 0;
  6  end;
  7  /

Function created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> exec dbms_application_info.set_client_info(0);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> select * from emp where empno = f(5);

no rows selected

ops$tkyte@ORA920LAP> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
14

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> exec dbms_application_info.set_client_info(0);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> select * from emp where empno = (select f(5) from dual);

no rows selected

ops$tkyte@ORA920LAP> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
1

ops$tkyte@ORA920LAP>

see the number of times it was called?  sometimes the second trick doesn't
"work" and you need to add "rownum > 0" to have it materialize the subquery in
temp and use it instead of evaluating the function over and over.

Review &
      Followup

      Rating: 5
Should we use
      PL/SQL function in a CURSOR declaration  May 15, 2003

Reviewer:  Kamal Kishore  from New Jersey, USA

Hi Tom,
We have a packaged function that takes a DESCRIPTION stored in the database
column and does some transformation to its value before returning to the client.
Typically, we have a CURSOR loop to fetch rows to be returned to the client and
we need to apply this PL/SQL packaged function to each DESCRIPTION of the row
fetched.

In this scenario, What would be more efficient:
1). Call the PL/SQL packaged function when declaring the cursor so that the
value returned by the SQL query is already transformed.
2). FETCH the row in the CURSOR loop, and for each row fetched, apply the
packaged function to the DESCRIPTION column and then return the converted values
to the client.

MY concern is, if the CURSOR returned, say 100 rows, will there be 100 context
switches from SQL to PL/SQL back and forth each time the DESCRIPTION needs to be
transformed (assuming we use option number one). Or that I'm worring for
nothing?

What would be preferred way? Or is there a hidden third choice I missed?
Thanks,

SQL> set serveroutput on size 100000
SQL> create or replace package Transform is
  2    function conv(p_input IN VARCHAR2) RETURN VARCHAR2 ;
  3  end ;
  4  /

Package created.

SQL> show errors
No errors.
SQL> create or replace package body Transform is
  2    function conv(p_input IN VARCHAR2) RETURN VARCHAR2 is
  3    begin
  4      --
  5      -- just return the value for now
  6      --
  7      return(p_input) ;
  8    end ;
  9  end ;
 10  /

Package body created.

SQL> show errors
No errors.
SQL>

================================
This is using option number one:
================================

SQL> declare
  2    CURSOR get_rows is
  3      select empno, Transform.conv(ename) name from scott.emp ;
  4    output VARCHAR2(32000) ;
  5  begin
  6    output := '<ROWS>' ;
  7    for rec in get_rows
  8    loop
  9      output := output || '<EMPNO>'||rec.empno ||
'<NAME_CONVERTED>'||rec.name ||'</NAME_CONVERTED>' ;
 10    end loop ;
 11    output := output || '</ROWS>' ;
 12    dbms_output.put_line('Length='||length(output)) ;
 13  end ;
 14  /
Length=699

PL/SQL procedure successfully completed.

================================
This is using option number two:
================================

SQL>
SQL> declare
  2    CURSOR get_rows is
  3      select empno, ename from scott.emp ;
  4    output VARCHAR2(32000) ;
  5  begin
  6    output := '<ROWS>' ;
  7    for rec in get_rows
  8    loop
  9      output := output || '<EMPNO>'||rec.empno ||
 10                '<NAME_CONVERTED>'||Transform.conv(rec.ename)
||'</NAME_CONVERTED>' ;
 11    end loop ;
 12    output := output || '</ROWS>' ;
 13    dbms_output.put_line('Length='||length(output)) ;
 14  end ;
 15  /
Length=699

PL/SQL procedure successfully completed.

SQL>
SQL>

          Followup:

choices 3, 4 and 5 to consider

3) array fetch instead of single row fetch with bulk collect (you have N context
switches this way as well ;)

4) use the XML capabilities of the database to return a clob that is XML without
doing it yourself

5) with case -- can transform.conv be made in SQL pure and simple

      Rating: 4
Thanks
      !  May 15, 2003
Reviewer:  rama  from
      ct, us

      Rating: 5
Example
        May 16, 2003
Reviewer:  Tony  from
      INDIA
You have said " sometimes the second trick doesn't
"work" and you need to add "rownum > 0" to have it materialize the subquery in
temp and use it instead of evaluating the function over and over".
Could you please give us an example why it won't work sometimes?

          Followup:

because the optimizer does what it does -- it can turn

select * from t where x = ( select y from dual )

into

select t.* from t, (select y from dual) y where t.x = a.y

which is really

select t.* from t where t.x = y;

and you are back to the beginning again.  I've shown you how to evaulate how
many times it is called -- I would suggest you just use

where x = ( select function from dual where rownum >= 0 );

and then you have less to worry about.

      Rating: 5
Choice
      3?  May 16, 2003
Reviewer:  Kamal Kishore
       from New Jersey, USA
Hi Tom,
In the previous reply you said about additional choices to consider. At this
time XML in the database does not seem to be an option since we don't have it
installed/enabled in the database (we are running Oracle 8.1.7 and higher).
The best option at this time then seems to be option 3 to bulk fetch into array.
Few questions though:
1). Will it make any difference in performance if the Transform.Conv function
was declared as DETERMINISTIC?
2). Is it better to make it a part of the CURSOR QUERY or better if we do it in
the FETCH loop?
3). Should we worry about the size of the array when doing bulk fetch? We do not
know how many rows we might get from the cursor. Shoud we get all the rows in
one single bulk fetch or is there a clause we could use to limit number of rows
returned in each bulk fetch (like in DBMS_SQL). IF there is a clause like this
(to limit rows in bulk fetch), is it available on 8.1.7?
Thanks,

          Followup:

1) no

2) probably inside the loop since you are going procedural

3) I like 100, you can fetch bulk collect with LIMIT in 817.

      Rating: 4
2 follow-up
      questions.  May 16, 2003
Reviewer:  Kashif
       from Houston, TX
Hi Tom,

Firstly, the 'select f(5) from dual' trick would work only if the value passed
in to the function 'f' was not a database column. Otherwise, if we passed a db
column in, e.g. 'select f(col_of_table) from dual', then function 'f' would need
to be executed as many times as the number of rows in the result set, regardless
of whether it was selected from dual, or called directly, correct?

Secondly, you mentioned above:

"use the XML capabilities of the database to return a clob that is XML without
doing it yourself"

How would that be done? What if the tag names are custom tag names belonging to
a specific XML vocabulary? Thanks in advance!

Kashif

          Followup:

it would not have made sense to call it once per query, correct.

I modelled the example after the question --

select col1, col2 from table where col2 = SomeFunction

select col1, col2 from table where col2 = (select SomeFunction from dual)

you have 100% control over the generated tag names -- read the xml developers
guide, it'll tell you eveyrthing you need to know

      Rating: 5
A
      clarification  May 16, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Hi Tom,
A question that keeps coming up at our site is that:
1). "do we need to enable JAVA in the database to reap the benefits of XML
support in database?"
2). "The above statement is true only for Oracle8i, For Oracle9i and above java
need not be configured to have XML capabilities."

This is what I'm told and asked to confirm.

I think statement one is correct that JAVA is required to be configured for
Oracle8i in order to use XML features.

Is this still true for Oracle9i? I heard that most of the internal java stuff
was being converted into C compiled code in Oracle9i and I'm told that this is
the reason that for Oracle9i we do not need JAVA configured to have XML support.
Is this correct?

At our site, people are hesistant to do all this setup that they say is required
to give us XML capabilities and are hoping that this required setup will not be
"required" once we upgrade to Oracle9i.

Appreciate your clarifications on this and all other topics.
Thanks,

          Followup:

what is "all of this setup"?  beyond either clicking a radio button OFF during
the default install (eg: it is installed by default) or running initjvm.sql
after the fast -- what is "all of this setup"?

      Rating: 5
A question for
      our DBAs  May 17, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Hi Tom,
We (the developer community) are being told that in order to give us the XML
capabilities JAVA support needs to be installed and configured (it was not
installed when the database was created) and JAVA_POOL_SIZE needs to be set
appropriately.
I understand that there is not much needed to do in order to support XML (we
will not be doing any java programming, we only are asking it for the XML
features). But we are having a hard time explaining this.
The moment we talk about configuring java to get XML features, we start to get a
cold response.
It looks like we may have to wait a little while longer for this support to be
available to us to make good use of it.
Thanks for explaning how to structure our programs in the absence of this
support.
Thanks,

May 17, 2003 Rajesh -- Thanks for the question regarding
      "Oracle Database and NT Services", version 8.1.6.3

      You Asked
Hi Tom,I have been asking regular questions from you. I hope that does not
bothers you too much. I have made a recent trasition from Unix to NT so I am
little confused about services in NT for
the Oracle DB. In NT there is something called 'SERVICE'. How is that connected
to DB ? If that service is stopped then what does that mean  - the database is
up or not ?

If we have to shutdown that database normally then if we stop the service, will
the database be shutdown ? or we have to stutdown the database then dtop the
service. What should be the sequence for shutting down the database and bringing
it up again so that database
keeps running smoothly? I hope my question is clear.

Basically I want to know the relationship between the service and database
up/down status/procedure.

      and we said...
Sorry to hear of your predicament.  I hope you can get back to unix soon ;)
(hey, we are all entitled to our opinions)

The SERVICE is how the database "bootstrap" process gets going.  If the service
is running that means the database may or may not be up.  If the service is NOT
running, the service will NOT be up.

When the service is running, normally it indicates the database is up and
running (but does not imply that it is).  For example, I have my 9i instance
setup so that it does NOT startup with the service starting up.  So, I have to
start the instance and then I use sqlplus to startup the database.

You can use sqlplus/svrmgrl to shutdown normal the database, typically, the
stopping of the service will actually "crash" the instance (NT can be impatient
and can kill the service on shutdown instead of waiting for it to end).

The relationship between the OracleService%ORACLE_SID% and the datebase "up
down" state is this:

o if the service is not running, the database is definitely not up

o if the service is running, the database might be up and if it is not, you can
start it up.

follow up to comment three

I find that time spent in books and manuals is not wasted.

where do you think I get many of my answers from?  I read the manuals and books.
 I used the software.

This was not one of them, but I frequently get questions that I answer with a
simple URL pointing the questioner to the documentation.  I would urge and
encourage everyone to read the manuals.  There is some truly useful
information in there.

Start with the server concepts manual (which I reread with every release).

it is DEFINITELY not a waste of time.

followup to comment 5

Thats just part of the story...  there are two other parameters you need to be
aware of as well:

ORA_SID_SHUTDOWN
Possible values: TRUE or FALSE
Set this to TRUE to enable the proper shutdown of the database

ORA_SID_SHUTDOWN_TIMEOUT
Possible values: timeout in seconds (30 for example)
Sets the maximum time (in seconds) to wait for the
shutdown to complete before the service for a particular SID stops.
This timeout should be set high enough to give the database time
to complete the shutdown.
If this is lower then the time needed, you won't have a clean shutdown!
You can check this in the alert file.

ORA_SID_SHUTDOWNTYPE
Possible Values: i , t , a.
where n = shutdown normal
      i = shutdown immediate
  and a = shutdown abort

If the entry's are not set, stopping an service will do WORSE then an shutdown
abort. Windows NT / 2000 will just "clean" the memory. This could be compared
by a kill -9 on unix.

So, NT will still crash your instance if the database takes longer then it likes
to shutdown.  NT has ( i believe ) a 20 second default in addition upon which
it'll kill the service.  There is a registry WaitToKillServiceTimeout that
controls this.
Review & Followup

      Rating: 5
What is the
      feature of oracle  July 13, 2001
Reviewer:  Anna
      Lee  from Malaysia(Asia)
Hello .
I am confuse when I am studying the oracle web site .There are many information
about the oracle .Can you show me briefly about the feature of Oracle and what
is the different between oracle and dBASE2 only .
Thank you .

      Rating:
      5
  July 14, 2001
Reviewer:
      A reader

      Rating: 5
Excellent
      explanation  July 16, 2001
Reviewer:  Rajesh
       from NJ
The explaination was very mushc concise and upto the point. That's the reason I
alway look forward to asking you instead of wasting time in books and manuals.

      Rating: 5
Got you
      !!  July 16, 2001
Reviewer:  A reader

I understand the utility of manuals and books but most of the documentation is
targetted to a broad audience. They explain most of the things with example but
at times they overlook certain things so at times the specific answer u r
looking for a particular issue is not clearly explained....Just like the
explanation that u have provided for services and Database status is not there
in any of the books and only by experience one can understand these things and
share it with others.

      Rating: 4
Autostopping
      the instance  July 16, 2001
Reviewer:
      A reader
There is a registry value you can use to have the service shutdown the instance
for you to avoid instance recovery.  With v8.0.5 on NT, I used
ORA_{SID}_SHUTDOWN = TRUE.  (as always, check the details out for yourself
first)

      Rating: 4
Oracle Database
      and NT Services  October 29, 2002
Reviewer:
      Umesh Patel  from Mumbai, India
Got some very useful information regarding automatic startup and shutdown of
Oracle database.
For Windows 2000 and oracle 9i, the registry key is
HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE\HOME0
the String is  ORA_DEPLOY_SHUTDOWN

Don't know if it is the same for all other versions of Windows and Oracle.

      Rating: 4
Win200 Services
      and Patch  December 11, 2002
Reviewer:
      Teddy
OK, this is clear.
What I am not sure about is this:
Which services do I have to shutdown before installing a patch or upgrading? I
have to install the Server Patchset 4 for 9.0.1.3.x (=> 9.0.1.4) and have
multiple installations of OraServ (8.1.7,9.0.1.3,9.2),iAS (1.0.2.2.1) AND tools
(iSuite) on that server. So I have following services (* indicates, that these
are started):

    OracleOraHome81xxx
       (xxx:Agent,ClientCache,CMAdmin,CMan,DataGatherer
        HTTPServ,MgmtServ,PagServ, TNSListener)
(*) OracleOraHome901xxx (Agent*,ClientCache*,HTTP,PagServ,
        SNMPyyy,TNSListener*)
    OracleOraHome92xxx  (Agent,ClientCache*,HTTP,MgmtServ,
        PagServ,SNMPyyy,TNSListener*)
    OracleServiceDB81
*   OracleServiceDB901
*   OracleServiceDB92
(*) OracleiSuitesyyy
      (yyy: Agent,CLientCache,DataGatherer,
       HTTPServer(*),TNSListener, Webzzz)

*   Oracle Forms Server [Forms60Server]
*   Oracle Reports Server [Rep60_<node>]
    OracleDiscoverer4i

    OracleClientCache80

    Oracle OLAP 9.0.1.0.1
    Oracle OLAP Agent
    Oracle Web Integration Server
    OracleMTSRecoveryService
    OracleSMNPPeerEncapsulator
    OracleSMNPPeerMasterAgent

some of them not having a version, so I don't know, whether
-they will crash on installation of the patch, or whether
-I won't be able to start/restart them after the installation.

I think, the patchset installation affects all 901 services and DBs, so I will
have to shutdown/stop them.
I am not sure about the last 6 (probably will have to stop the OLAP services
too), which causes headache to me ;-)

What do you say, Tom ?

Thanks in advance

Teddy

          Followup:

shut them all down.  windows loves to be rebooted, they'll restart after that.

better safe then sorry - you'll end up in DLL hell with some dll being locked in
some fashion otherwise.

      Rating:
      4
  May 17, 2003
Reviewer:
      A reader
Tom,
i see the following services installed in my system - windows2000. can you
please provide me with information of each of these services. few of this i
know.

i searched your site, i didnt get.. if you get time please answer or a link
would do.

OracleMTSRecoveryService
OracleOraHome90bAgent
OracleOraHome90bClientCache
OracleOraHome90bHTTPServer
OracleOraHome90bPagingServer
OracleOraHome90bSNMPPeerEncapsulator
OracleOraHome90bSNMPPeerMasterAgent
OracleOraHome90bTNSListener
OracleOraHome90ClientCache
OracleServiceORACLEB

Thanks for you valuable time and effort.

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/win.920/a95490/services.htm#1004903

      Rating: 5
thanks
      tom  May 17, 2003
Reviewer:  A reader

May 17, 2003 -- Thanks for the question regarding "Invoking
      sql procedure in J.S.P", version 8i

      You Asked
Is it possible to invoke a sql procedure in J.S.P Page
If Yes Please let me know how?

      and we said...
Yes, you just connect to the database using JDBC or SQLJ (which is a layer over
jdbc).

See

http://technet.oracle.com/doc/oracle8i_816/java.816/a81354/basic5.htm#1002489
for examples.

followup to comment

The above referenced link is all you need.  It matters NOT if the Java code is
in a JSP, or standalong program, an applet, a java stored procedure, <any where
else you can stick java> - it is exactly the same.  You use JDBC and call a
procedure.  The above link shows you how to call a procedure using Java and
JDBC.

You can also look at:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/java.817/a83726/sampapp2.htm#1007998
for sample JDBC JSP pages.  These particular examples run a SQL query but the
difference between running a query and a procedure is trivial.

Review &
      Followup

      Rating:
      2
ok  March 26, 2001
Reviewer:  parin
      bhatt  from india
i wanted the exact syntax explaining me with examples how to use oracle
procedure in jsp
please if can do it is well and good
my email id is parin48@hotmail.com

      Rating:
      2
  March 21, 2002
Reviewer:  victor
      nkonga  from Milwaukee, WI USA
Could you give me an example of how java servers pages program call a java
stored procedure load on oracle database. I wrote a java stored procedure
program and loaded and published it on oracle database. I tested the java stored
preocedure with SQL Plus and the call program was completed. That means the java
stored procedure works fine. I am not able to call java stored procedure in the
java server pages's application. I read many documentations about call java
stored procedure but they didn't help.
Please help me here.
If you have can direct me to an example that match my problem that will be
helpful.
Thanks.

          Followup:

It is NO different then calling a "stored procedure" since you create a stored
procedure wrapper on top of it.

Here is an example of calling a stored procedure from java:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:499097979114
no different for java stored procedure.

      Rating: 3
JSP, ASP and
      the like   May 17, 2003
Reviewer:  Fred
      Shimaya  from irvine, california usa
You stated above,

"These particular examples run a SQL query but the
difference between running a query and a procedure is trivial."

I know I read in one of my many books (I'm starting to learn the performance
aspects of PL/SQL, as a beginning PL/SQL programmer) that it is generally not
good to use SQL code in JSP, ASP and the like, because the code is not fully
captured in the shared pool.  Hence, as you mentioned, the hard parsing.  It
recommends calling a stored procedure to perform the query.  It would seem based
on this that there would be differences, major depending on the query.  In the
link you provided below (I do click all your links), you do mention some good
reasons (one being security) for calling stored procedures from within JSP.  But
you didn't mention this issue.  What is your opinion?

          Followup:

the code is fully captured.  100%.  you cannot avoid it.

the problem is jsp/asp coders for whatever reason seem to hate bind variables,
destroying the functionality of the shared pool and introducing security bugs
big enough to fly a 777 through.

They must BIND to the stored procedure call just as they MUST BIND to a SQL
statement.

I don't know what issue you mean there that I didn't mention.  Code should

a) bind

period.  bind is necessary for performance.  bind is good for debugging (you
know what the statement is going to be - you are not gluing junk in there).
bind is necessary for security.  what more need be said?

May 17, 2003 --
      Thanks for the question regarding "Question on check constraints", version
      9.2.0.1

      You Asked
I’m investigating my schema to define some standards. Currently check
constraints (99% them are not null constraints) doesn’t have user defined names
and they grabbed system defined name.

Is it good idea to change them and create our own names? If yes, can you guide
me any automated script that can create these alter scripts?

      and we said...
yes, it would be good to name them.

No, i know of no "automated" script but it would be easy for you to write one
customized for your site.  for example, a first cut for NOT NULL constraints
might be:

ops$tkyte@ORA920> select constraint_name, constraint_type, generated from
user_constraints
  2  where table_name = 'XXX';

CONSTRAINT_NAME                C GENERATED
------------------------------ - --------------
SYS_C0010303                   C GENERATED NAME
SYS_C0010304                   C GENERATED NAME
SYS_C0010305                   C GENERATED NAME

ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2          for x in ( select * from user_constraints where generated =
'GENERATED NAME' and constraint_type = 'C' )
  3          loop
  4                  if (x.search_condition like '"%" IS NOT NULL')
  5                  then
  6                          execute immediate
  7                          'alter table ' || x.table_name || ' rename
constraint "' || x.constraint_name ||
  8                          '" to "' || x.table_name || '.' || replace(
x.search_condition, '"', '' ) || '"';
  9                  end if;
 10          end loop;
 11  end;
 12  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select constraint_name, constraint_type, generated from
user_constraints
  2  where table_name = 'XXX';

CONSTRAINT_NAME                C GENERATED
------------------------------ - --------------
XXX.USERNAME IS NOT NULL       C GENERATED NAME
XXX.USER_ID IS NOT NULL        C GENERATED NAME
XXX.CREATED IS NOT NULL        C GENERATED NAME

you'll have to watch out for max lengths (30 characters) of course and you might
not like spaces in the name -- but you see how "easy" this can be automated
using your rules...
Review & Followup

      Rating: 5
Excellent
      !!  May 17, 2003
Reviewer:  Fan
I have been doing a similar thing on my present assignment.

      Rating: 5
Thanks a bunch
      for your excellent comments!  May 17, 2003

Reviewer:  A reader

May 17, 2003 Brian -- Thanks for the question regarding
      "Parallel Execution and the Large Pool", version 8.1.6

      You Asked
My understanding is that if parallel execution is enabled
(PARALLEL-AUTOMATIC_TUNING = TRUE) and we actually set a DOP for the
tables/indexes we want parallelized, then Oracle will use the Large Pool is used
instead of the Shared Pool when processing these objects (or any time we force
parallelism with hints).  Is this true?

      and we said...
The large pool is used for the PQ messages -- not for anything else.  Yes, it is
true.

the shared pool is for memory that "ages" (age this sql plan out after no one
has used it)

the large pool is for memory that has a lifetime -- allocate it, use it, free
it.  PQ messages, UGA's in shared server mode, rman IO -- they all use the large
pool.
Review
      & Followup

      Rating: 4
a bit more
      clarification required  January 11, 2002
Reviewer:
      Brian Davies  from Edmonton, Alberta, Canada
At our sight, we're not using MTS or RMAN, nor are we using Oracle Parallel
Server .  We have several independent servers, each with varying numbers of CPUs
(some have 1; some have 3; some have 6) and varying numbers of disks for
storage.

Our objective is to enable and use parallel execution.  We're trying to figure
out whether Oracle will use the large pool, and how big we should make the pool.

In your book (pg 83), you state that the large pool is used by:

MTS;
Parallel Execution to coordinate parallel query servers;
RMAN Disk I/O buffers.

I interpret this to mean the Oracle will use the large pool for parallel
execution query servers, even though we are not using MTS, RMAN, or parallel
server (i.e. multiple machines).

Is my interpretation correct?

What views/tables should we look at to monitor Large Pool usage?

          Followup:

Correct.

v$sgastat is the view that shows you large pool utilization.

      Rating: 5
Large Pool
      Sizing  June 18, 2002
Reviewer:  Andre
       from Brazil
I've read as a quirk that setting PARALLEL_AUTOMATIC_TUNING=TRUE has a very
useful side-effect of automatically and dynamically sizing the large pool, not
only for parallel operations, but for the other cases as well (rman, mts, etc),
preventing it from getting crowded -- so we don't have to worry about sizing it
manually. What do you think of it ?

          Followup:

Well, as for having it be set for MTS (shared server), I'm not sure I'd let it
do that.  It'll look at the mts_dispatchers to figure out what to allocate for
that -- so unless your sessions is set dead on and you use the "right" amount of
uga memory in your sessions, you'll be wanting to set this yourself (as the
parameter definition suggests you might).

      Rating: 5
Where are
      parallel query messages buffered  March 06, 2003

Reviewer:  Dale Ware  from St. Louis Mo.
Tom,

I am curious about where the PQ messages are buffered when the parameters
LARGE_POOL, DBWn_IO_SLAVES and PARALLEL_AUTOMATIC_TUNING are not set.

Would the shared pool be used under these circumstances?

          Followup:

large_pool_size will be set for you if you have pq configured.

....
Otherwise, derived from the values of PARALLEL_MAX_ SERVERS,
PARALLEL_THREADS_PER_CPU, CLUSTER_ DATABASE_INSTANCES, DISPATCHERS, and DBWR_IO_
SLAVES.
.........

and each of those has defaults as well.

answer = large pool

      Rating: 4
Parallel
      Query/execution  May 17, 2003
Reviewer:
      A reader
Tom,
The production system we have is a busy OLTP, a few hundred concurrent users and
we will have more users coming. Someone has been recommending to set:
PARALLEL-AUTOMATIC_TUNING = TRUE for the Database. Do you think it right?

Thanks so much

          Followup:

nope.

not at all.

OLTP queries are characterized by

o small
o fast

parallel query is used to execute

o big, huge
o long running

queries.

If you have big huge long running queries, you do not have an oltp system.

May 17, 2003 --
      Thanks for the question regarding "Clarification and Question about
      UGA/PGA and sort_area_sze", version 9.2.0.3

      You Asked
Tom, I know a lot has been written about this on your website ..
I need to clarify couple of things -
a) UGA and PGA are synonymous in dedicated mode and all the memory is coming
from the OS memory via the shadow process
b) In MTS mode PGA is small and is OS memory but the UGA is either in the large
pool if defined else in shared pool
c) Sort area size and hash area size are always OS memory irrespective of the
dedicated or MTS mode ..

And now for the simple question - IN both dedicated and MTS when a session is
doing sorting or hashing it pulls the data from db cache into its own OS meemory
space to do what needs to be done. Once done with the sorting or hashing is this
sorted/hashed data put back into db cache for it to be available to other
sessions ?  IF yes how does this happen in terms of pointers or is the memory of
the sorting process mapped to the SGA?
Thanks for your time ..

      and we said...
a) not synonymous but inside of eachother.  The UGA is in the PGA in dedicated
server mode.

b) in MTS mode, the PGA is not "small" (eg: sort_area_size=100m -
sort_area_retained_size = 64k - do a big sort.   Now, the PGA will be about
100m, the UGA about 64k).

In MTS mode, there are LESS pgas then in dedicated server mode.

The UGA is in the large pool and if you are using MTS and don't have a large
pool -- you are doing it wrong.

c) yes and no.  If I set sort_area_size = sort_area_retained_size, then given
that we use sort_area_retained_size bytes in the UGA first and then spill over
to sort_area_size -- it is VERY possible for sort_area to be in the UGA.

So, it depends.

In 9i though, you should be using workarea_size_policy = auto and
pga_aggregate_target -- then sort_area_size/hash_area_size is not even relevant.

Sorted data is session specific..  No, you cannot use my sort output.

Review &
      Followup

      Rating: 5
Clarification
      and Question about UGA/PGA and sort_area_sze  April 14, 2003

Reviewer:  A reader
Thanks Tom. Great answer..
now ..
You said - "In MTS mode, there are LESS pgas then in dedicated server mode"

 You mean in terms of memory size. Correct ? Beacause each session (dedicated or
MTS) will have a PGA .. In MTS how would the PGA process show when doing a ps
-ef .. Dedicated shows as LOCAL=NO or LOCAL=yes ..

As for the PGA_AGGREGATE - we have been looking at it .. sooner or later we will
come around to it ..
Thanks for your help ..

          Followup:

No, I do *not* mean in terms of memory size.

I mean in terms of CARDINALITY.

In dedicated server, if you have 100 dedicated servers for 100 sessions, you
have 100 PGA's.

In shared server, if you have 10 shared servers for the same 100 sessions, you
have 10 PGA's.

Each of the 10 PGA's may (or may not) be smaller in shared server then in
dedicated server (they can be, they don't have to be).

      Rating: 5
sort_area_size,
      hash_area_size, bitmap etc  May 16, 2003
Reviewer:
      PINGU SAN
Hi

In MTS or Oracle Shared Servers whatever they are called, from your previous
replies I understand that sort_area_size, hash_area_size are part of PGA and not
UGA (located in shared pool or large pool). My question is, if we make a big
hash join and a big sort using 100MB for each operation then the shared server
PGA would becme 200MB, is there anyway to free this? In dedicated server
processes the memory is freed when we quit our session but in MTS since server
processes are shared I guess there is no way to free the 200MB I mentioned
without killing the process. By the way what can go wrong if we kill a shared
server process (assumming that noone has been assigned to use it!)

          Followup:

no, it would be 100mb.  the sort would use it - and then give it back and then
the hash would use it and then the sort and then the hash.

the OS takes care of it. if you are not using it, it gets paged out.  In 9i,
using the pga_aggregate_target, the memory is returned to the OS directly. (so
the answer would be -- 9i does that, but in 8i, the OS does it effectively
enough)

      Rating:
      4
pga  May 16, 2003
Reviewer:  reader
       from Houston, TX
Does it mean that prior to 9i, can PGA grow depending upon the sort operation?
My understanding is that if I set my sort_area_size = sort_area_retained_size =
1M, whenever a session starts, the corresponding PGA of the server process will
be allocated 1M of memory for sort operation whether it needs it or not. If the
session performs sorting that requires, say 5M of memory, what Oracle would do
is, it will sort 1M in memory and retain the sorted result of 1M in the temp
segment and then proceed to sort the next batch of 1M and will keep on retaining
the sorted results in temp segment until it finishes sorting. After completing
sorting of 5M data, it will merge the sorted results in PGA (?) and gives the
output to the session. When it merges the results, it may need 5M of memory in
PGA and at that time the PGA memory grows from 1M to 5M. As my
sort_area_retained_size is set to 1M, after the sort is done, it will give back
the memory by keeping 1M memory in the PGA. Could you please clarify, is this
how it works prior to 9i.

Thanks as always.

          Followup:

your understanding is wrong.

sort area size is an upper bound, it is dynamically allocated from the pga at
runtime to the size needed, if you only needed 64k, that is all that would be
allocated.  After the sort is done, the sort area size is "freed" back to the
pga for reuse by any other pga allocation.

it does not work that way.

      Rating: 5
Please confirm
      this  May 16, 2003
Reviewer:  David

1) PGA_AGGREGATE_TARGET is the MAXIMUM size this area can take,  so that when
more memory is needed and it is not available in this area, then TEMP ts will be
used ?

2) Is it called "target" because it's an upper bound (maximum size allowed) or
because it's the size Oracle "tries" to reach (maybe it can stay a bit smaller
or a bit greater than the specified value.

3) if sum(pga_max_mem) from v$process is greater than my pga_aggregate_target
(as in fact it is in my system now), it doesn't mean that my
pga_aggregate_target is small. pga_max_mem is the maximum size the process has
even taken, so I think I does not mean much, right ?

          Followup:

1) no, it is the suggest bound on the PGA memory that will be used by all
concurrent sessions.  Oracle will look at the PGA aggregate target (say it is
500meg) and based on the number of active sessions and other factors it might
decide "your query shall use 25meg of sort area", later, cause the load went up,
it might decide "your query shall use 512K of sort area" and so on.

It dynamically adjusts these settings all day long based on the current
workload.

2) it is a target, it may overshoot it.

3) no, that is the max mem reached at a point in time, doesn't mean all sessions
reached that point at the same time. read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:8759826405304

      Rating: 5
PGA
      Contents  May 17, 2003
Reviewer:  Ashish
       from NJ
Is there any way to see the contents of the PGA allocated
out of PGA_AGGREGATE_TARGET?

          Followup:

read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:8759826405304
it shows some of the v$ views.

      Rating: 4
pga grows
      beyond my sort_area_size  May 17, 2003
Reviewer:
      reader  from Houston, TX
Tom, I tried the example from your book. Please see below. It looks like PGA
grows depending upon how many rows I see after sorting.
Could you clarify what is happening? I thought sort_area_size is the upper bound
as you say but my pga has grown beyond the sort_area_size.
I appreciate your time.

scott@oracle> show parameter sort_area

NAME                                 TYPE    VALUE
------------------------------------ ------- --------------------
sort_area_retained_size              integer 1000000
sort_area_size                       integer 1000000

scott@oracle> select a.name,b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and a.name like '%ga %';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
session uga memory                                                    44780
session uga memory max                                                57844
session pga memory                                                   599588
session pga memory max                                               599588

scott@oracle> set pause on
scott@oracle> set pagesize 10
scott@oracle> select * from all_objects order by 1,2,3,4;

..................

OWNER                          OBJECT_NAME                    SUBOBJECT_NAME
------------------------------ ------------------------------
------------------------------
 OBJECT_ID DATA_OBJECT_ID OBJECT_TYPE        CREATED   LAST_DDL_ TIMESTAMP
    STATUS  T G S
---------- -------------- ------------------ --------- ---------
------------------- ------- - - -

DEMO                           ITEM
     10931          10931 TABLE              23-JUN-99 23-JUN-99
1999-06-23:12:47:08 VALID   N N N

DEMO                           I_CUSTOMER$CUSTOMER_ID

     10940          10940 INDEX              23-JUN-99 23-JUN-99
1999-06-23:12:47:19 VALID   N N N

10 rows selected.

scott@oracle> select a.name,b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and a.name like '%ga %';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
session uga memory                                                    53252
session uga memory max                                              1106660
session pga memory                                                  1603492
session pga memory max                                              1603492

scott@oracle> select * from all_objects order by 1,2,3,4;

............

OWNER                          OBJECT_NAME                    SUBOBJECT_NAME
------------------------------ ------------------------------
------------------------------
 OBJECT_ID DATA_OBJECT_ID OBJECT_TYPE        CREATED   LAST_DDL_ TIMESTAMP
    STATUS  T G S
---------- -------------- ------------------ --------- ---------
------------------- ------- - - -
      5554                SYNONYM            23-JUN-99 23-JUN-99
1999-06-23:12:02:31 VALID   N N N

PUBLIC                         /34ecef2c_ImageConsumerQueue
      3898                SYNONYM            23-JUN-99 23-JUN-99
1999-06-23:11:59:28 VALID   N N N

PUBLIC                         /34f3aa56_DeploymentDescriptor

377 rows selected.

scott@oracle> select a.name,b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and a.name like '%ga %';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
session uga memory                                                    53244
session uga memory max                                              1106660
session pga memory                                                  2333620
session pga memory max                                              2333620

          Followup:

no version, perhaps 9i with workarea_size_policy is thankfully ignoring your
sort area size.

perhaps the method of testing -- which appears to be ctl-c??  is contributing to
it.  maybe the cursors are not closed, hence the sort areas never go away (until
the cursor is closed).  that would be a side effect of sqlplus.

run the entire query with

connect user/password
set autotrace traceonly
run the query
set autotrace off
query up the pga usage.

perhaps it only takes 600k to sort the entire thing (you see, the entire result
was sorted before you got row 1).  And you just kept getting 600k sort areas.
You can have many sort areas going -- many many sort area retained areas. that
is what this sort of looks like.

      Rating: 4
pga
      grows  May 17, 2003
Reviewer:  reader
       from Houston, TX
Please see below. still I could see pga grow if i repeat the steps as suggested.
It is 8i personaledition on windows. Thanks for your time.

SQL*Plus: Release 8.1.5.0.0 - Production on Sat May 17 16:29:21 2003

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Personal Edition Release 8.1.5.0.0 - Production
With the Java option
PL/SQL Release 8.1.5.0.0 - Production

scott@oracle> show parameter sort_area;

NAME                                 TYPE    VALUE
------------------------------------ ------- --------------------
sort_area_retained_size              integer 1000000
sort_area_size                       integer 1000000
scott@oracle> select a.name,b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and a.name like '%ga %';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
session uga memory                                                    41628
session uga memory max                                                54344
session pga memory                                                   110640
session pga memory max                                               110640

scott@oracle> set autotrace traceonly
scott@oracle> select * from all_objects order by 1,2,3,4;

11007 rows selected.

Execution Plan
----------------------------------------------------------
ERROR:
ORA-01039: insufficient privileges on underlying objects of the view

SP2-0612: Error generating AUTOTRACE EXPLAIN report

Statistics
----------------------------------------------------------
       1222  recursive calls
        197  db block gets
      31036  consistent gets
        626  physical reads
      23452  redo size
    1297149  bytes sent via SQL*Net to client
      82041  bytes received via SQL*Net from client
        737  SQL*Net roundtrips to/from client
          1  sorts (memory)
          1  sorts (disk)
      11007  rows processed

scott@oracle> set autotrace off
scott@oracle> select a.name,b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and a.name like '%ga %';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
session uga memory                                                    42700
session uga memory max                                              1093636
session pga memory                                                  1300036
session pga memory max                                              1300036

scott@oracle> set autotrace traceonly;
scott@oracle> select * from all_objects order by 1,2,3,4;

11007 rows selected.

Execution Plan
----------------------------------------------------------
ERROR:
ORA-01039: insufficient privileges on underlying objects of the view

SP2-0612: Error generating AUTOTRACE EXPLAIN report

Statistics
----------------------------------------------------------
       1222  recursive calls
        195  db block gets
      31036  consistent gets
        625  physical reads
      23372  redo size
    1297161  bytes sent via SQL*Net to client
      82041  bytes received via SQL*Net from client
        737  SQL*Net roundtrips to/from client
          1  sorts (memory)
          1  sorts (disk)
      11007  rows processed

scott@oracle> set autotrace off
scott@oracle> select a.name,b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and a.name like '%ga %';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
session uga memory                                                    41628
session uga memory max                                              1096588
session pga memory                                                  2216856
session pga memory max                                              2216856

scott@oracle>

          Followup:

it is like I said -- a side effect of the way sqlplus manages cursors.  do it
like this:

ops$tkyte@ORA815> alter session set sort_area_size = 1000000;

Session altered.

ops$tkyte@ORA815> alter session set sort_area_retained_size = 1;

Session altered.

ops$tkyte@ORA815>
ops$tkyte@ORA815> set autotrace traceonly statistics
ops$tkyte@ORA815> select * from all_objects order by 1,2,3,4;

10845 rows selected.

Statistics
----------------------------------------------------------
          8  recursive calls
          9  db block gets
      27936  consistent gets
        463  physical reads
          0  redo size
    1246609  bytes sent via SQL*Net to client
      80820  bytes received via SQL*Net from client
        726  SQL*Net roundtrips to/from client
          1  sorts (memory)
          1  sorts (disk)
      10845  rows processed

ops$tkyte@ORA815> set autotrace off
ops$tkyte@ORA815> select a.name,b.value
  2    from v$statname a, v$mystat b
  3   where a.statistic# = b.statistic#
  4     and a.name like '%ga %';

NAME                                VALUE
------------------------------ ----------
session uga memory                  44364
session uga memory max              94320
session pga memory                1202948
session pga memory max            1202948

ops$tkyte@ORA815> set autotrace traceonly statistics
ops$tkyte@ORA815> select * from all_objects order by 1,2,3,4;

10845 rows selected.

Statistics
----------------------------------------------------------
          8  recursive calls
          9  db block gets
      27936  consistent gets
        457  physical reads
          0  redo size
    1246631  bytes sent via SQL*Net to client
      80820  bytes received via SQL*Net from client
        726  SQL*Net roundtrips to/from client
          1  sorts (memory)
          1  sorts (disk)
      10845  rows processed

ops$tkyte@ORA815> set autotrace off
ops$tkyte@ORA815> select a.name,b.value
  2    from v$statname a, v$mystat b
  3   where a.statistic# = b.statistic#
  4     and a.name like '%ga %';

NAME                                VALUE
------------------------------ ----------
session uga memory                  44360
session uga memory max              94320
session pga memory                1914768
session pga memory max            1914768

ops$tkyte@ORA815> set autotrace traceonly statistics
ops$tkyte@ORA815> select * from all_objects order by 1,2,3,4;

10845 rows selected.

Statistics
----------------------------------------------------------
          8  recursive calls
          9  db block gets
      27936  consistent gets
        452  physical reads
          0  redo size
    1246631  bytes sent via SQL*Net to client
      80820  bytes received via SQL*Net from client
        726  SQL*Net roundtrips to/from client
          1  sorts (memory)
          1  sorts (disk)
      10845  rows processed

ops$tkyte@ORA815> set autotrace off
ops$tkyte@ORA815> select a.name,b.value
  2    from v$statname a, v$mystat b
  3   where a.statistic# = b.statistic#
  4     and a.name like '%ga %';

NAME                                VALUE
------------------------------ ----------
session uga memory                  44364
session uga memory max              94320
session pga memory                1914768
session pga memory max            1914768

ops$tkyte@ORA815> set autotrace traceonly statistics
ops$tkyte@ORA815> select * from all_objects order by 1,2,3,4;

10845 rows selected.

Statistics
----------------------------------------------------------
          8  recursive calls
          9  db block gets
      27936  consistent gets
        479  physical reads
          0  redo size
    1246631  bytes sent via SQL*Net to client
      80820  bytes received via SQL*Net from client
        726  SQL*Net roundtrips to/from client
          1  sorts (memory)
          1  sorts (disk)
      10845  rows processed

ops$tkyte@ORA815> set autotrace off
ops$tkyte@ORA815> select a.name,b.value
  2    from v$statname a, v$mystat b
  3   where a.statistic# = b.statistic#
  4     and a.name like '%ga %';

NAME                                VALUE
------------------------------ ----------
session uga memory                  44364
session uga memory max              94320
session pga memory                1914768
session pga memory max            1914768

ops$tkyte@ORA815> set autotrace traceonly statistics
ops$tkyte@ORA815> select * from all_objects order by 1,2,3,4;

10845 rows selected.

Statistics
----------------------------------------------------------
          8  recursive calls
          9  db block gets
      27936  consistent gets
        467  physical reads
          0  redo size
    1246631  bytes sent via SQL*Net to client
      80820  bytes received via SQL*Net from client
        726  SQL*Net roundtrips to/from client
          1  sorts (memory)
          1  sorts (disk)
      10845  rows processed

ops$tkyte@ORA815> set autotrace off
ops$tkyte@ORA815> select a.name,b.value
  2    from v$statname a, v$mystat b
  3   where a.statistic# = b.statistic#
  4     and a.name like '%ga %';

NAME                                VALUE
------------------------------ ----------
session uga memory                  44364
session uga memory max              94320
session pga memory                1914768
session pga memory max            1914768

ops$tkyte@ORA815>

      Rating: 4
pga
      grows  May 17, 2003
Reviewer:  Reader
       from Houston, TX
Tome, continuation from above results. If i did that one more time, pga seems to
be the same size. please refer above to see this. However, it has grown beyond
my sort_area_size. Thanks.

scott@oracle> set autotrace off
scott@oracle> select a.name,b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and a.name like '%ga %';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
session uga memory                                                    38460
session uga memory max                                              1096588
session pga memory                                                  2216856
session pga memory max                                              2216856

scott@oracle> set autotrace traceonly;
scott@oracle> select * from all_objects order by 1,2,3,4;

11007 rows selected.

Execution Plan
----------------------------------------------------------
ERROR:
ORA-01039: insufficient privileges on underlying objects of the view

SP2-0612: Error generating AUTOTRACE EXPLAIN report

Statistics
----------------------------------------------------------
       1222  recursive calls
        195  db block gets
      31036  consistent gets
        625  physical reads
      23412  redo size
    1297161  bytes sent via SQL*Net to client
      82041  bytes received via SQL*Net from client
        737  SQL*Net roundtrips to/from client
          1  sorts (memory)
          1  sorts (disk)
      11007  rows processed

scott@oracle> set autotrace off
scott@oracle> select a.name,b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and a.name like '%ga %';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
session uga memory                                                    38464
session uga memory max                                              1096588
session pga memory                                                  2216856
session pga memory max                                              2216856

      Rating:
      5
Thanks!  May 17, 2003
Reviewer:
      Reader  from Houston, TX

May 17, 2003 -- Thanks for the question regarding "Need your
      comments on schema design", version 9.2.0.1

      You Asked
Currently we are storing data in demoralized format for our datamart reports.
Everything works fine except at one place. We have a table on application
database, which stores custom fields as name/value pairs. Here is the table
format in application database.

Table A
Id    number(18) PK,
Param_name varchar2(30),
String_Value Varchar2(4000),
Number_value number(28,10),
Date_value date

Lets say the data is

ID    Param_name    String_value    number_value    Datevalue
1    Col1        Str1
2    Col2        Str2
3    Col3                100
4    Col4                        5/16/2003

The datamart was de-normalized to the following format

Table a_datamart
Id    number(18),
Col1    varchar2(4000),
Col2    varchar2(4000),
Col3    number,
Col4    Date

The current design is capable of using 10 datamart tables for holding these
custom columns (1000 columns in each table). Currently the custom fields got
increased to 2000 (right now its using 2 tables) and the data become 100,000
rows. The performance was too slow.

The reason for storing as columns is to improve performance at run time. Adhoc
query manager is using these tables and users can create the report dynamically
by using order by clause, aggregate functions and group by clause etc.,

We wanted to redesign this approach. Each set of these custom fields grouped by
one custom type. We wanted to use a generic 1 table with

500 number columns (num1, num2 … as column names)
400 String columns (str1, str2 … as column names)
100 date columns

and we wanted to store the data related to that custom type in that row. In the
GUI we still wanted to show the real names (Col1, Col2 etc) and then get the
actual db column names (Str1, Str2, num1, date1 etc) using the query. We want to
use transformations between real names and temporary columns using metadata.

Could you please comment on this approach? I will appreciate if you can give
your comments or if you can guide me to pick any better approach.

      and we said...
my comments -- generic solutions to specific problems are always going to be dog
slow as compared to the specific solution to the problem.

A data mart should be a specific solution to a specific problem.  There should
be no need for "generics" there, there should be N columns representing the N
known attributes with specific names and types for these columns.

I would lose the "generic" junk straight off.  It is a fancy "trick" loved by
developers but despised by end users and tuners alike.
Review &
      Followup

      Rating: 3
demoralized
      format.  May 17, 2003
Reviewer:  Keith
      Jamieson  from ARklow, Ireland
Currently we are storing data in demoralized format

Is this a freudian slip?

          Followup:

LOL, ROTFL actually...  very good and very appropriate.

      Rating: 3
What is LOL :
      Lots of Love and ROTFL ?  May 17, 2003
Reviewer:
      A reader

          Followup:

laughing out load
rolling on the floor laughing.

      Rating:
      4
Thanks!  May 17, 2003
Reviewer:
      A reader
Thanks Tom for your comments.

I was not fully confident on my design, I couldn't fine any better solutions,
and wanted to share my thought with you to get your valuable comments.

Sorry for my typo. Thanks Keith Jamieson!!! For your great catch, Keep it up.

      Rating: 3
What is
      freudian slip?  May 17, 2003
Reviewer:
      A reader

          Followup:

http://www.google.com/search?hl=en&lr=&ie=ISO-8859-1&safe=off&q=%22what+is+a+freudian+slip%22&btnG=Google+Search
it is good for a laugh or two as well ;)

May 18, 2003 --
      Thanks for the question regarding "pga", version 8.1.7.2

      You Asked
What is the relationship between pga, sort_area_size, and temp segments? I have
read the docs but not so clear about how pga is used during sorting? I would
appreciate if you could clarify. Thanks for your time.

      and we said...
do you have my book "expert one on one"?  in there I go into great detail.

SORT_AREA_SIZE is the amount of ram we'll use in memory to sort.

If your sort takes more space then that, we'll swap to temp and only use
sort_area_size bytes of ram.   So, say your sort area size is 1m, we'll use 1m
of ram when it fills up, swap that to temp and fill up that 1m again and swap as
many times as needed in order to finish the sort.

There is the sort_area_retained_size as well.  this is how much of the sorted
data will remain in RAM for fetching after the sort is completed.  If you have a
1m sort area and a 512k sort area retained and you do a sort that takes 1m --
what will happen is the sort will take place in RAM and at the end of the sort,
512k of it will be written to temp and 512k will remain in RAM for you to fetch
from.

the sort_area_retained_size RAM is allocated in the UGA.
the sort_area_size that exceeds the sort_area_retained_size is allocated in the
PGA.

In dedicated server mode, the UGA is in the PGA so they are synonymous really.
What that means is normally the sort area is totally in the PGA, only in shared
server would this distinction become noticable.

Review &
      Followup

      Rating:
      5
Thanks  May 19, 2003
Reviewer:
      Reader
I have bought your book and I am enjoying it. Thanks for explaining.

May 18, 2003 Badri -- Thanks for the question regarding
      "searching name value pairs", version 9.2.0

      You Asked
Have an application that keeps track of name value pairs of
of various "things"

a simplified example of a table called things

 thing varchar2(12)
 attribute varchar2(3)
 value varchar2(256)

Needless to say the same "thing" can occur many times in this
table with different attributes

The application needs to provide users ways to search for things
that have more than one attribute in common,

What is the best way to construct a sql to do this?

ie find me all things where attribute1 = value1 AND
attribute 2 = value2 AND so on..

Also is there an easy way to do OR queries also ie replace
AND with OR?

Thanks very much..

      and we said...
In my book I'm working on -- I actually use this model as the classic "bad
example of what not to do".

In there, I use the model I see over and over

table of objects  (objectid, name, etc )
table of attributes  (your things with a thingid)
table of object_attributes (objectid, thingid, value)
table of object_links (objectid1, objectid2)

It is impossible to query this model in anything remotely approaching "top
speed" or "easy"

you'll have to do something like:

select thing
  from table
 where (attribute = :bv1 and value = :bv2 )
    or (attribute = :bv3 and value = :bv4 )
    or .....
 having count(*) = :n  <<<=== n = number of or'ed predicates

good luck with this model, you'll be cursing it for as long as you are working
on this project -- I can almost guarantee you that!

If it is not too late -- CHANGE it, don't go there. In 16 years, I've never seen
this work well.

Review &
      Followup

      Rating: 5
How about this
      design  May 18, 2003
Reviewer:  Arun Gupta
       from Harrisburg, PA USA
Tom
We use a slightly different approach. We have one parent table, t_parent(name,
desc). This table has entries like

'R1', 'States in US'
'R2', 'Counties in PA'
'R3', 'School districts in PA'
and so on.

The child table, t_child(name,code,text) has data like:

'R1','PA','Pennsylvania'
'R1','NY','New York'
'R2','01','Adams'
'R2','02','Berks'
'R3','01','Cumberland Valley School District'

and so on. Thus, we have created multiple "logical tables" using two physical
tables. R1 is the table for states, R2 for counties etc. Note that code '01' in
t_child can have  different text description depending whether name='R1' or
name='R2'. The t_parent would fit in a couple of Oracle blocks and t_child table
is less than 2MB. The data in t_parent and t_child is mostly static.

The main application tables store the code but display the text from t_child.
The task of translating code to text and text to code is handled by the
application. There are no foreign key relationship between main application
table and t_child. The application ensures data integrity while translating text
to code and vice versa.

There are many queries which need multiple logical table values from t_child. A
query which needs to display text for state and text for county, needs to join
to t_child twice.

What do you think of this approach?
Thanks

          Followup:

that is a lookup, that is "normal", that is done all of the time.  I don't like
the way you have it entirely though.

what they are trying to do above is get away without naming, defining any
columns in tables....

Their 'emp' table would look like this:

create table emp ( empno );

and then they would put a row per attribute in this other table

insert into things( 1234, 'hiredate', sysdate );
insert into things( 1234, 'sal', 222 );
insert into things( 1234, 'comm', 123 );
insert into things( 1234, 'deptno', 10 );
......

You have a single lookup table for the entire system the key is 'R1', 'PA' and
'R1', 'NY' -- to me, there should be a table R1 with keys 'PA', 'NY' and so on.

So, you are much better off, but not quite there in my opinion.

      Rating: 4
seaching name
      value pairs  May 18, 2003
Reviewer:  Badri
      Subramaniam  from San Jose, CA
Thank you. I will have the developers stay away from
this implementation. BTW, nice talk at the NOCOUG
conference in San Jose.. priceless..

May 18, 2003 Mario -- Thanks for the question regarding
      "re-parse and recursive calls", version 9.2.0

      You Asked
Hi Tom,

at:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3479923945167
in the last follow-up why the recursive calls
are zero for the third and fourth queries in
the example given by Vivek Sharma? in the last
two queries the environment it is changed, the
table now it is analyzed, therefore Oracle has
to re-parse the two queries and in fact it does
reparse them: we can see this by the fact that
the plans are different: Oracle doesn't need
to execute recursive calls on behalf of these
re-parses?

 If you allow it, I would have an explanation:
the check for the existence of the objects
referenced in the queries and the check for
the privileges of the user that submitted
the queries are already stored in the
library cache, the newly-made statistics
of the objects (table and index) are in
the dictionary cache.

Could you kindly explain the reason of this
(obviously expected) behaviour?

Thank you very much Tom, especially
for your unvaluable daily help.

Mario from Rome, Europe.

      and we said...
that just means everything it needed was already in the dictionary cache.  We
didn't need to run any SQL to get the dictionary cache loaded up - it was there.
 No recursive SQL was needed.

Consider the following:

ops$tkyte@ORA920LAP> create table t ( x int );
Table created.

that actually loaded up the dictionary cache, everything we need to know T is
there, we can see that because:

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> set autotrace traceonly statistics;
ops$tkyte@ORA920LAP> select * from t;

no rows selected

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          3  consistent gets
...

but if we flush out that shared pool, empty the dictionary cache:

ops$tkyte@ORA920LAP> alter system flush shared_pool;
System altered.

ops$tkyte@ORA920LAP> select * from t;
no rows selected

Statistics
----------------------------------------------------------
        184  recursive calls
          0  db block gets
         24  consistent gets
...

we needed to run lots of sql to get it filled up again, next query:

ops$tkyte@ORA920LAP> select * from t;\
no rows selected

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          3  consistent gets
          0  physical reads...

has everything it needs, even dropping the table and recreating it won't do
recursive sql on the parse, since everything we need to know is there already:

ops$tkyte@ORA920LAP> drop table t;\
Table dropped.

ops$tkyte@ORA920LAP> create table t ( y int );
Table created.

ops$tkyte@ORA920LAP> select * from t;

no rows selected

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

ops$tkyte@ORA920LAP> set autotrace off

Review &
      Followup

      Rating: 4
Many Thanks
      Tom  May 19, 2003
Reviewer:  Mario
       from Rome, Europe

May 19, 2003 Ravi -- Thanks for the question regarding "Size
      of one I/O in unix", version 805

      You Asked
Tom,

Could you please clarify the following. I would like to optimize
the size of one I/O in unix(solaris)

DB_BLOCK_SIZE=8k
db_file_multiblock_read_count=8k
Database : OLTP type

I would like to read a row which is well contained in a block(no
chaining) through an index.

Let us assume that this row is read through with an index of
height 3. So there will be a total of 4 I/Os.

I would like to know, how much size of the data will be read

1) Will it be 4*8k = 32k , since it has to read only one block
from the datafile

2) Will it be 3*8k for indexes and
1*DB_BLOCK_SIZE*db_file_multiblock_read_count for data

3) will it be 3*DB_BLOCK_SIZE*db_file_multiblock_read_count for
index and 1*DB_BLOCK_SIZE*db_file_multiblock_read_count for data

Will oracle always read 64k in one I/O(with
db_file_multiblock_read_count=8k), though it needs only 8K of
block.

Ours is an OLTP database and the ratio of full table scans to
indexed scan is very less(indexed scan is more). If it is going
to read 64K with the above setting even when a single block is
required, is it advisable to lower the
db_file_multiblock_read_count.

Thanks
Ravi

      and we said...
it'll be 4 block reads, 32k.

we use db_file_multiblock_read_count for things like full scans
only.  for indexed reads, we do single block reads.
Review &
      Followup

      Rating:
      3
always?  February 04, 2002
Reviewer:
      Dean Reynolds  from Nashville, TN
Isn't it possible for the CBO to choose to do an FTS if the
multiblock_read_count is set high enough that it believes an FTS to be cheaper?

I'm not saying index reads are not single block reads, but, even if you use
indexes, FTS's are still possible and can be affected by the size of the
multiblock_read_count, right?

Thanks.

          Followup:

yes, but i didn't say anything different then that.  we are in concurrence.

      Rating: 4
In Unix is in
      it we always do I/O in multiple of 64k  February 04, 2002

Reviewer:  a reader  from Singapore
In Unix is in it we always do I/O in multiple of 64k?

          Followup:

no, ask your OS vendor what they do, it varies.

      Rating: 5
is this cae
      64k?  May 19, 2003
Reviewer:  PINGU SAN

Hi

I came across to this I/O configuration topic in Oracle 9.2 documentation where
it says

On low-concurrency (sequential) systems, ensure that no single I/O visits the
same disk twice. For example, assume that the stripe width is four disks, and
the stripe depth is 32k. If a single 1MB I/O request (for example, for a full
table scan) is issued by an Oracle server process, then each disk in the stripe
must perform four I/Os to return the requested data. To avoid this situation,
the size of the average I/O should be smaller than the stripe width multiplied
by the stripe depth. If this is not the case, then a single I/O request made by
Oracle to the operating system results in multiple physical I/O requests to the
same disk.

I am not sure whats the I/O size here but it seems 64K because it says there are
4 I/O per disk and 4 disks so if I take 1MB and divide by 16 I get 64K. Am I
right till now? Next thing is I dont understand the last paragraph where it says
the size of average I/O should be smaller than

stripe depth * stripe width = 32k * 4

why?

          Followup:

thats a question for EMC, Veritas, etc -- or someone who does hardware.  Tuning
physical disk stuff like this isn't something I do myself.  I rely on people who
know how to best set that stuff up -- to do so.

May 19, 2003 Terry -- Thanks for the question regarding
      "Buffer cache size is too large? ", version 734

      You Asked
Hi Tom,

We have done a database re-organisation last month but users complaint the speed
become slower. However, I do believe the speed should increase after database
re-org.

From DBA point of view, all figures look excellent. Even, we have asked Oracle
Support on-site three days in order to work out any possibility of why the speed
goes down. But, the Oracle support concluded that it is no problem at all from
database side. Then, we have ask HP consultant to work out this issue.

This is what they concluded:

"One thing that could be changed is the size of the Buffers. I think they may
be too large. If they are too large sometimes the system has to search too
much memory and that uses CPU cycles to do this. It is less work for the
system to do the IO from disk to retrieve the record.
I know it sounds strange but sometimes there can be too much memory, I would
be interested to know the effect of reducing the size of the Buffers
parameter in the init.ora file to a smaller size."

what they suggested is to reduce the buffer cache size. However, I don't think
the larger size of buffer cache will cuase adverse effort only the possibility
of wasting memory.  Am I right? Please help me. Thank you so much.

      and we said...
Ahh, the good old reorg.

I'll betcha a dollar you LOST an index, I see it happen every time (part of the
reason I am of the personal belief that re-orgs are an utter waste of perfectly
good time in 99.9% of the cases).

A reorg could actually slow down an OLTP system, all of those freshly rebuilt,
compact indexes must now get broken down again, split the blocks -- get back to
where they were -- plump but comfortable.

If you did not change the init.ora, the size of the buffer cache could not be
the cause.  If the only thing you changed was the organization of the data --
and all else was held constant - you would be barking up the wrong tree.

I would look for a missing index personally.  Since you are 734, consider using
UTLESTAT/BSTAT and (hopefully you have some) compare it to prior reports from
before the reorg to see if something (disk/whatever) is getting hit really hard.

Review &
      Followup

      Rating: 3
Large DB Block
      Buffers  January 21, 2002
Reviewer:  Krish
       from Nashville, USA
The question of large db block buffers is indeed intriguing.

I would like to mention here a case where I am stuck at:

I have a database with 45000 db blocks (8k each).

The 2 tables have 5M rows each.

SELECT /*+ RULE */ count(*)
    from  kentity e, kbenefit_entity be
    WHERE  last_name like :l and  first_name like :f
    and  be.entity_id = e.entity_id ;

SELECT STATEMENT Optimizer=HINT: RULE
  SORT (AGGREGATE)
    NESTED LOOPS
      INDEX (RANGE SCAN) OF KENTITY_FULLNAME_IDX (NON-UNIQUE)
      INDEX (RANGE SCAN) OF KBENENT_ENTID_ROLEID_BENID_IDX (NON-UNIQUE)

The entity table returns 1100 rows (<20 index blocks) for the criteria and the
query (in almost single user mode) as a whole returns 1200 (<20 index blocks)
rows. But it takes 40 seconds to return the result every time (except when
cached). Each index segment has <10 extents and data (if relevant) is
"colocated". And we are using one of the fastest disk technologies, per our
experts.

Session_event shows no waits.

The delay is inexplicable and I wonder if it has to do anything with the large
db block buffers value.

Could you pl throw light on this issue?

Thanks

KU

          Followup:

tkprof it.  see what that says.  Since it runs "fast" when cached -- i would
have to say that the large buffer cache is not having a negative impact but
rather that it must be scanning a TON of entries in the index (doesn't matter
how many rows it RETURNS -- it matters how many rows it had to INSPECT for the
like).

I don't know what the indexes are one, but say its on last_name, first_name and
you supply '%x%', it'll read EVERY index entry and if it has an x in it -- it'll
then do the index probe into the other table.  That could be a TON of singleton
(slow) IO's.

      Rating: 4
More,
      Please  January 21, 2002
Reviewer:  Harrison
      Picot  from Haymarket, VA USA
Hi Tom:

"Plump but comfortable" sounds good, but I didn't understand the part before
that:

You said: " reorg could actually slow down an OLTP system, all of those freshly
rebuilt, compact indexes must now get broken down again, split the blocks -- get
back to where they were -- plump but comfortable."

Why must the indexes get broken down? ("in the book" is an ok answer, if true).

          Followup:

An index is a data structure (a pretty complex one).  Many times -- depending on
what you do, they end up with "extra space" in them.  If you rebuild them (as
you did) they get smaller -- but lo and behold over time -- they grow again.
This "extra space" is really needed -- sometimes its used, sometimes not used.

When you rebuild (compact) the index, all of this space goes away.  The deleted
entries (caused by deletes or updates) are removed.  Well, we NEED those slots
-- we are going to use them tommorow!  The next day we won't but the day after
we need them again.

Well, we'll have to split blocks - move data around -- grow the structure to get
this "extra space" back again.  In a couple of days or weeks -- the index will
be exactly the same size it was before -- with all of that extra space.  Getting
that space back added overhead.

So, any index on a column that is updated frequently could cause this to happen.
   As you update it -- you'll leave a "deleted" entry where the old value was
and use or make space for an entry.  So, say you

  update t set x = 'M' where id = 55;  (x was 'C' before the update)

well, that leaves an empty slot on the block where that 'C' was before the
update and gets space where the 'M' was.  In a freshly compacted index -- we
might have had to split the block where the update ended up (the index block the
M goes on).

Now, the next guy that does:

  update t set x = 'C' where id = 66;

will be able to use the space we left behind from the first 'C' that is not
there anymore.

The problem after a compact is there are no more "deleted" entries, no place to
go and we'll have to split the index until there is.

Many indexes have a steady state they'll get into over time, regardless of how
often you "reorg" them and you pay the price of getting into this steady state
over and over.

      Rating: 3
Large buffer
      cache  January 22, 2002
Reviewer:  terry
       from Taiwan
Hi,

Thank you for helping on this issue. I would like to say that we have lost some
indexes after reorg, but we have made a list to compare and we built them
straight the way.

Firstly, please confirm whether it is not harm on larger buffer cache as long as
we are not 'Page in and page out' on unix memory.

Regarding to the index, you said that 'the indexes get broken down...then, go
back to the original steady state'.
This does confuse me...as many oralce books show the necessary of index rebuild.

If it is not necessary to rebuild index in most case, what kind of situation we
can gain a signicant benefit from  rebuilding index? Or, if not necessary in any
case, I am wondering why Oracle provide such function and keep improve its
functionality from, say, oracle 7 to oracle 8i online index rebuild.

Thank you in advance.

          Followup:

A large buffer cache is in the eye of the beholder.  Yes, an excessively large
one can cause issues -- but you were happy with the prior performance....

Many oracle books are not 100% accurate or only tell half of the story.  Me,
I've never actually rebuilt an index due to "fragmentation".  Get Jonathan
Lewis's book -- practical Oracle8i, building efficient databases.  Lots of good
common sense in there.

We provide that which customers demand.  The rebuild online is GREAT for moving
indexes from tablespace to tablespace, changing storage parameters, or after a
mass load (eg: set the indexes UNUSABLE, load, REBUILD the indexes -- that way
you NEVER EVER lose them ;)

So you see -- rebuild has many other alternate uses.

I would ask you -- now that you have spent UNTOLD hours, incurred serious
performance issues, tore your hair out fixing the problem -- was it worth
it?

Did you get measurable performance increases?
Was it an efficient use of your time?

Think about those two questions before you do your next reorg.

      Rating: 4
One more thing
      to say  January 22, 2002
Reviewer:  terry
       from Taiwan
Hi Tom,

Regarding to your exmaple above "

   As you update it -- you'll leave a "deleted" entry where the old value was
and use or make space for an entry.  So, say you

  update t set x = 'M' where id = 55;  (x was 'C' before the update)

well, that leaves an empty slot on the block where that 'C' was before the
update and gets space where the 'M' was.  In a freshly compacted index -- we
might have had to split the block where the update ended up (the index block the

M goes on).

Now, the next guy that does:

  update t set x = 'C' where id = 66;

will be able to use the space we left behind from the first 'C' that is not
there anymore.

The problem after a compact is there are no more "deleted" entries, no place to
go and we'll have to split the index until there is.
"

I agree with you but it seems that you are based on the assumption that the
deleted data will update back again in the future. I think that it is not
necessary to reuild index in this circumstance.

However, it is not the case, at least in my site, the deleted index entry will
not come back. As time goes, more and more delelted slots come out and index is
less and less balancing. At this case, the unbalance index will require more
i/o, so whether we need to rebuild index or not? Please explain for me. Thank
you so much.

          Followup:

Well, it depends on how you delete the data.  Do your indexes grow infinitely
large over time?  If not -- we must be doing some space reuse... (the indexes
tend to stay pretty balanced).

If the indexes grow infinitely larger over time -- they are a candidate for
rebuilds.  If not, -- make sure you MEASURE response time BEFORE and AFTER a
rebuild to see if you GAINED anything.  Write a program that randomly selectes
via the index a couple of thousand/million times, use sql_trace and tkprof.
rebuild the index and do it again.  compare.  If all you got back was a little
space (which will get allocated AGAIN very soon) don't rebuild.  If the
performance goes up measurably, consider rebuilding at some point in the future.

      Rating: 5
Yes, Sir ! I
      will study "index" further   January 22, 2002

Reviewer:  Terry  from Taiwan
Hi Tom,

Thank you so much. You do give me a lot of clues...I will pay more attention on
it, since "index" is extremely important part of the RDBMS. Great!

      Rating: 5
Performance
      gains (revisited)  January 24, 2002
Reviewer:
      Harrison Picot  from Haymarket, VA USA
Hi Tom:
When I saw your comments on the lack of performance gains from rebuilding
indexes, I had the feeling that in the dim past some smart Oracle people had
differed, and finally found the paper, one by Cary Millsap in 1992.  Actually I
remembered his saying the performance gains that come from raw disks are no
greater (and no more permanent) than those from moving a database, which
"reduces row chaining to its theoretical minimum and also nicely repacks and
balances every index."  I remembered the indexes, but what I should have
remembered was the next sentence: "The same five- to twenty-percent performance
improvement can be gained by moving a badly chained table from raw disks to a
Unix file system".  The present point being that sometimes we pick one thing
that has changed and associate better performance to to it, but have not held
other things constant. Your plan for testing the indexes makes a lot of sense
(and my going back and reading what I think I remember doesn't hurt either).  I
am always impressed by how throughly you test and document things, and how
little of that some of the rest of us do.  Maybe that is something to work on.

thanks for the help

Harrison

      Rating: 3
Buffer cache
      size is too large?  August 09, 2002
Reviewer:
      A reader
I agree with you table re-org is utter waste of time but what if I run a select
statement against an index which has too many fragements ?  Don't the "selects"
take more time?

          Followup:

define "fragments"

If you mean "over time, due to updates and deletes, my index is 50% 'whitespace'
and I think I should rebuild it" -- you might be very wrong (or you might be
right).

Indexes, like people, have a certain weight they like to be at.  No matter how
many times I go on a diet, lose weight -- I end up back at the same weight
(well, maybe a little heavier even).  Same with an index.

This quote:
A reorg could actually slow down an OLTP system, all of those freshly rebuilt,
compact indexes must now get broken down again, split the blocks -- get back to
where they were -- plump but comfortable.

from above typifies what I mean.  We put the index on a diet (rebuilt it) but it
goes right back. Why?  because your updates and deletes CONTINUE on this data
and the index moves all around.  An index like this might just want to be 50%
whitespace forever and no amount of rebuilding will change that.  You can put it
on a diet (rebuild) as often as you like -- it'll just consume resources getting
"fat" again.

      Rating:
      5
  May 19, 2003
Reviewer:
      A reader
Hi Tom,

 We are using siebel crm application.Siebel DBA visited out site last week and
give one suggestion about buffer cache that surprise me.

   He(DBA) came here to tune our bad sql statement generated by siebel.
 We have 150 Concurrent users and our block buffer size is 22000 and out block
size is 8k.
 Siebel DBA recommanded to dubble block buffer(ie 44000).
 According to him we need to allocate 5 to 10 MB  buffer cache per user.

 I never heard buffer cache allocation like this.

 He also suggested to make init.ora parameter pre_page_sga    to TRUE

 Please advide.
 Thanks,

          Followup:

"Siebel DBA" is what is known as an oxymoron.

Ask them why they propose such things.  You do not size a buffer cache by
numbers of users, that is silly.

Pre_Page_Sga is equally as doubtful.

In order to tune a system, you must diagnose what people are waiting on.  do you
have high IO waits?  Is it likely that you are aging blocks out frequently
causing to you read and re-read the same blocks?  if not, increasing the buffer
cache will just consume more ram (could make you slower) and pre-paging will
just make you take longer to startup.

May 19, 2003 Reazul -- Thanks for the question regarding
      "Update Table using pl/sql", version 8.1.7

      You Asked
Hello Tom,

We wanted to update a table with the plsql below, but it was taking too long and
as I was trying to see the stat from V$SQLAREA it shows lots of disk reads.

TARIFF lookup table has 7000+ rows with code & Description, it is indexed over
hscode column and VALUATION table is a transaction table with appx 800,000+ rows
with indexes.

SET SERVEROUTPUT ON

declare

CURSOR VAL_CUR
IS
select V.HSCODE,t.hs_desc
from tariff t, valuation v
where t.hscode=v.hscode;

begin

FOR VAL_REC IN VAL_CUR LOOP

UPDATE  VALUATION
SET HS_DESC=VAL_REC.HS_DESC
WHERE HSCODE=VAL_REC.HSCODE;

END LOOP;
end;
/

How can I make this update run faster. We are using 8i & Windows.

      and we said...
assuming that HSCODE is unique in TARIFF

update ( select t.hs_desc t_hs_desc, v.hs_desc v_hs_desc
           from tariff t, valuation v
          where t.hscode = v.hscode )
  set v_hs_desc = t_hs_desc;

never write code when a single SQL statement will do!

ops$tkyte@ORA920> create table tariff ( hscode int , hs_desc varchar2(30) );

Table created.

ops$tkyte@ORA920> create table valuation ( hscode int , hs_desc varchar2(30) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert /*+ APPEND */ into tariff
  2  select rownum, object_name from all_objects where rownum <= 8000;

8000 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table tariff add constraint tariff_pk primary
key(hscode);

Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table tariff compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert /*+ APPEND */ into valuation
  2  select rownum, object_name from big_table.big_table where rownum < 900000;

899999 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table valuation add constraint valuation_pk primary
key(hscode);

Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table valuation compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set timing on
ops$tkyte@ORA920> update ( select t.hs_desc t_hs_desc, v.hs_desc v_hs_desc
  2             from tariff t, valuation v
  3            where t.hscode = v.hscode )
  4              set v_hs_desc = t_hs_desc;

8000 rows updated.

Elapsed: 00:00:00.82
ops$tkyte@ORA920>
ops$tkyte@ORA920>

Thats not too slow...
and even if you do this for MOST of the rows (or all of them):

...
ops$tkyte@ORA920> insert /*+ APPEND */ into valuation
  2  select mod(rownum,8000)+1, object_name from big_table.big_table where
rownum < 900000;

899999 rows created.

Elapsed: 00:00:06.46
ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table valuation compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;

Table analyzed.

Elapsed: 00:00:10.91
ops$tkyte@ORA920>
ops$tkyte@ORA920> set timing on
ops$tkyte@ORA920> update ( select t.hs_desc t_hs_desc, v.hs_desc v_hs_desc
  2             from tariff t, valuation v
  3            where t.hscode = v.hscode )
  4              set v_hs_desc = t_hs_desc;

899999 rows updated.

Elapsed: 00:00:58.90
ops$tkyte@ORA920>

it is not so slow (well, this is redhat running on a little compaq desktop,
windows might not be so fast -- but you might be able to compensate with
hardware ;)

Review &
      Followup

      Rating:
      4
Excellent  May 19, 2003
Reviewer:
      A reader
Am I correct in saying that this would only work if the tables each have a
primary key?

          Followup:

second example, updating 899,999 rows, has no primary key on valuation

tariff must have a primary key or unique constraint (else the update is ambigous
-- that would mean tariff could have hscode two times for the number "5" --
which hs_desc would be used???)

valuation need not.

      Rating:
      4
Alternatively...  May 19, 2003
Reviewer:
      Kashif  from Houston, TX
Hi Tom,

Unfortunately I've never been able to use the update a join syntax in our
database, owing to the severe lack of primary and foreign keys. If the original
poster is faced with a simlar situation, then he could also use BULK COLLECT and
FORALL commands. Any other workarounds for us primary-key-deficient database
programmers? Thanks.

Kashif

      Rating: 5
Update Using
      plsql  May 19, 2003
Reviewer:  Reaz/dhaka
       from Dhaka, Bangladesh
Thanks Tom, it is of great help.

The best thing about it is, your judgement of what to use for best result,
frankly how do you get into it when using subquery in update not even in our
thoughts.

While I did this in our Win o/s it took 00:09:558.97. I am happy atleast it is
not there forever like used to.

Thanks again.
Reaz/dhaka

May 20, 2003 mohan -- Thanks for the question regarding
      "backups", version 8.1.6

      You Asked
Hai tom
I want to know  internal functionality while taking  cold backup and hot backup
1) cold backup:  shutdown the database and take the backup of database,  Media
falures not protected ,there is no pont in recovery,

2)Hot backup is you can take backup of database without shuting down the
database,media recovery is prottected ,supports point in time recovery

Thanks in advance
mohan

      and we said...
wrong.

cold backup, shutdown and copy database.  media failure 100% covered, point in
time recovery supported

It is not that cold and hot are different -- it is that ARCHIVELOG and
NOARCHIVELOG are different.

hot vs cold -- no difference there.  same procedures for recovery.

it is archivelog vs noarchivelog.

Suggestion -- read:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76993/toc.htm
a couple of times -- the one thing a DBA is not allowed to ever mess up is
backup and recovery.  understanding this is key.
Review &
      Followup

      Rating: 5
rman backup
      directly to tape  May 19, 2003
Reviewer:
      A reader
Regardless "cold" or "hot", is there an easy way to configure rman backup
directly to tape (in 9i)? Why do we need to care about "media management layer"?
Why do we need to relink oracle executable? Why can't we simply use /dev/rmt/0n
(and the like) as backup device in rman?

          Followup:

because we do not talk directly to tape -- you need media mgmt software.

if you want to use tape, you would be using dd or tar and just registering the
backups with rman.

May 20, 2003 Heath -- Thanks for the question regarding
      "Predicate pushing in views containing analytic functions", version 8.1.7

      You Asked
Tom,

I have a view in my database that contains an Analytic function.  The analytic
function windows on a set of rows in the table and returns a subset of them.
The initial testing using this view was quite promising, as the work performed
by the database to get the answer was  MUCH less using the analytic function
than joining the table back to itself (the approach used previously).  My
initial tests were queries against the view based upon the same column that was
used to partition the table in the window.  However, when I changed the query
against the view to use another column in the WHERE clause, the performance
dropped significantly.  My investigation showed that the predicate was not being
pushed into the view, resulting in the whole table being read, partitioned, and
sorted before the predicate was applied.

Is there no way to force the predicate all the way into the view?  I tried using
hints like PUSH_PRED, but couldn't get it to work.  Or is there another way to
construct the view to get the answers I want?

Many thanks for your help,

Heath

Here is a long snippet from an SQL*Plus session detailing the investigation.

SQL>drop table t
  2  /

Table dropped.

Elapsed: 00:00:00.41
SQL>
SQL>create table t( Group_id    VARCHAR2(32),
  2                  Group_Key   NUMBER,
  3                  col2        NUMBER )
  4  /

Table created.

Elapsed: 00:00:00.40
SQL>insert into t
  2      select object_name,
  3             ROW_NUMBER() OVER( partition by object_name
  4                                order by object_id ),
  5             object_id
  6      from all_objects
  7      where rownum<=25000
  8  /

24862 rows created.

Elapsed: 00:00:10.05
SQL>
SQL>insert into t
  2      select Group_id || '1',
  3             Group_Key,
  4             col2 * -1
  5      from t
  6  /

24862 rows created.

Elapsed: 00:00:02.03
SQL>
SQL>commit
  2  /

Commit complete.

Elapsed: 00:00:00.90
SQL>
SQL>-- Create indexes on 2 of the columns.
SQL>-- One on the column used for the analytic function partition
SQL>-- One on another column in the table
SQL>create index IDX_T_GRPID on T(GROUP_ID)
  2  /

Index created.

Elapsed: 00:00:08.63
SQL>
SQL>create index IDX_T_COL2 on T(COL2)
  2  /

Index created.

Elapsed: 00:00:09.05
SQL>
SQL>-- Compute statistics for the CBO
SQL>analyze table t compute statistics
  2  /

Table analyzed.

Elapsed: 00:00:14.51
SQL>-- Create a package that will be used to keep track of the
SQL>--   number of rows processed by the analytic function.
SQL>-- It also will be used to reject some of the rows
SQL>--   processed by the analytic function.
SQL>create or replace package test_anafunc is
  2      function test_func( x NUMBER ) return number;
  3
  4      function get_Count return number;
  5      procedure reset_count;
  6  end;
  7  /

Package created.

Elapsed: 00:00:00.70
SQL>
SQL>create or replace package body test_anafunc is
  2
  3      p_Count     NUMBER(38);
  4
  5      function test_func( x NUMBER ) return number is
  6      begin
  7          p_Count := p_Count + 1;
  8
  9          if( x > 1 ) then
 10              return 1;
 11          end if;
 12
 13          return 0;
 14      end test_func;
 15
 16      function get_Count return number is
 17      begin
 18          return p_Count;
 19      end get_Count;
 20
 21      procedure reset_count is
 22      begin
 23          p_Count := 0;
 24      end reset_Count;
 25
 26  begin
 27      p_Count := 0;
 28  end;
 29  /

Package body created.

Elapsed: 00:00:00.70
SQL>
SQL>-- Create a view containing the analytic function.
SQL>-- This simulates the view that would be exposed by an application
SQL>--   to the user.
SQL>create or replace view test_view as
  2  select a.group_id, a.group_key, a.col2
  3  from (select t.group_id, t.group_key, t.col2,
  4               ROW_NUMBER() OVER( PARTITION BY GROUP_ID
  5                                  ORDER BY GROUP_KEY ASC NULLS LAST ) RNUM
  6        from t
  7        where test_anafunc.test_func(GROUP_KEY) = 1 ) a
  8  where a.RNUM = 1
  9  /

View created.

Elapsed: 00:00:00.90
SQL>-- Let's see how queries against this view get executed.
SQL>
SQL>-- Query the view based upon a group_id.
SQL>--   Pick one from table t that has more than one row for it.
SQL>-- You should see the second row for the group (Group_key=2).
SQL>-- The execution plan should show the use of the index.
SQL>-- The test_anafunc.get_Count should return the number of
SQL>--   rows for that group_id value.
SQL>-- This shows that the predicate is pushed into the inline view.
SQL>execute test_anafunc.reset_Count

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.10
SQL>/

View created.

Elapsed: 00:00:00.50
SQL>set autotrace on explain statistics
SQL>select * from test_View where group_id = 'TRACE'
  2  /

GROUP_ID                          GROUP_KEY       COL2
-------------------------------- ---------- ----------
TRACE                                     2       7942

Elapsed: 00:00:00.21

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=1 Bytes=57)
   1    0   VIEW (Cost=5 Card=1 Bytes=57)
   2    1     WINDOW (SORT)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=1 Bytes=27)
   4    3         INDEX (RANGE SCAN) OF 'IDX_T_GRPID' (NON-UNIQUE) (Cost=1
Card=1)

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

SQL>set autotrace off
SQL>select test_anafunc.get_Count from sys.dual
  2  /

 GET_COUNT
----------
         7

Elapsed: 00:00:00.20
SQL>-- Query based upon a column other than the one used for the partition.
SQL>--   Pick an object_id that exists in table t in the col2 column.
SQL>-- You should get one row back.
SQL>-- However, the execution plan will not show the use of the index.  You'll
SQL>--   see a full table scan instead.
SQL>-- The test_anafunc.get_Count will return a total equal to the total
SQL>--   number of rows.
SQL>-- This shows that the predicate is NOT pushed into the inline view
SQL>--   and that we are processing the entire table instead of just those
SQL>--   rows that meet the where clause criteria.
SQL>
SQL>execute test_anafunc.reset_Count

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.10
SQL>/

 GET_COUNT
----------
         0

Elapsed: 00:00:00.10
SQL>set autotrace on explain statistics
SQL>select * from test_view where col2 = 10816
  2  /

GROUP_ID                          GROUP_KEY       COL2
-------------------------------- ---------- ----------
ADDRESSLOCATION_SDOGEOM                   2      10816

Elapsed: 00:00:05.58

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=40 Card=498 Bytes=28386)
   1    0   VIEW (Cost=40 Card=498 Bytes=28386)
   2    1     WINDOW (SORT)
   3    2       TABLE ACCESS (FULL) OF 'T' (Cost=36 Card=498 Bytes=13446)

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

SQL>set autotrace off
SQL>select test_anafunc.get_Count from sys.dual
  2  /

 GET_COUNT
----------
     49724

Elapsed: 00:00:00.10
SQL>
SQL>-- Contrast this to what I really wanted for the col2 query.
SQL>execute test_anafunc.reset_Count

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.10
SQL>/

 GET_COUNT
----------
         0

Elapsed: 00:00:00.10
SQL>set autotrace on explain statistics
SQL>select a.group_id, a.group_key, a.col2
  2  from (select t.group_id, t.group_key, t.col2,
  3               ROW_NUMBER() OVER( PARTITION BY GROUP_ID
  4                                  ORDER BY GROUP_KEY ASC NULLS LAST ) RNUM
  5        from t
  6        where test_anafunc.test_func(GROUP_KEY) = 1
  7            and col2 = 10816 ) a
  8  where a.RNUM = 1
  9  /

GROUP_ID                          GROUP_KEY       COL2
-------------------------------- ---------- ----------
ADDRESSLOCATION_SDOGEOM                   2      10816

Elapsed: 00:00:00.11

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=57)
   1    0   VIEW (Cost=4 Card=1 Bytes=57)
   2    1     WINDOW (SORT)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=1 Bytes=27)
   4    3         INDEX (RANGE SCAN) OF 'IDX_T_COL2' (NON-UNIQUE) (Cost=1
Card=1)

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

SQL>set autotrace off
SQL>select test_anafunc.get_Count from sys.dual
  2  /

 GET_COUNT
----------
         1

Elapsed: 00:00:00.10

      and we said...
You cannot -- it CHANGES the answer.  You think your two queries are equivalent,
they are NOT.

There is a HUGE difference between

select analytic function
  from t
 where <condition>

and

select *
  from ( select analytic function
           from t )
 where <condition>

They are NOT even a tiny bit COMPARABLE.  Not at all.

This query says:

SQL>select a.group_id, a.group_key, a.col2
  2  from (select t.group_id, t.group_key, t.col2,
  3               ROW_NUMBER() OVER( PARTITION BY GROUP_ID
  4                                  ORDER BY GROUP_KEY ASC NULLS LAST ) RNUM
  5        from t
  6        where test_anafunc.test_func(GROUP_KEY) = 1
  7            and col2 = 10816 ) a
  8  where a.RNUM = 1
  9  /

Take the rows from T -- skipping group key 1 (test_func does that) where col23 =
10816.

Then, for all of those rows -- partition by group_id and sort by the group_key
-- assign row_number.

Then, keep the first one.

this query:

SQL>
select *
 from (select a.group_id, a.group_key, a.col2
    from (select t.group_id, t.group_key, t.col2,
                 ROW_NUMBER() OVER( PARTITION BY GROUP_ID
                                    ORDER BY GROUP_KEY ASC NULLS LAST ) RNUM
          from t
          where test_anafunc.test_func(GROUP_KEY) = 1 ) a
 where col2 = 10816
   and a.RNUM = 1
/

says:

find all of the rows after skipping group key 1.
partition that that by group id.
order by group_key and assign row_number.

Now, after that is done, keep only the first row and only keep that IF it has
col2 = 10816.

Here is proof they are different -- their logic is TOTALLY and completely and
UTTERLY different:

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop table t
  2  /

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t( Group_id    VARCHAR2(32),
  2                   Group_Key   NUMBER,
  3                   col2        NUMBER )
  4  /

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 'x', 2, 10815 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 'x', 3, 10816 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace view test_view as
  2   select a.group_id, a.group_key, a.col2
  3   from (select t.group_id, t.group_key, t.col2,
  4                ROW_NUMBER() OVER( PARTITION BY GROUP_ID
  5                                   ORDER BY GROUP_KEY ASC NULLS LAST ) RNUM
  6         from t
  7         where test_anafunc.test_func(GROUP_KEY) = 1 ) a
  8   where a.RNUM = 1
  9  /

View created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from test_view where col2 = 10816
  2  /

no rows selected

ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2    from (
  3   select a.group_id, a.group_key, a.col2
  4   from (select t.group_id, t.group_key, t.col2,
  5                ROW_NUMBER() OVER( PARTITION BY GROUP_ID
  6                                   ORDER BY GROUP_KEY ASC NULLS LAST ) RNUM
  7         from t
  8         where test_anafunc.test_func(GROUP_KEY) = 1 ) a
  9   where a.RNUM = 1
 10   )
 11   where col2 = 10816
 12  /

no rows selected

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.group_id, a.group_key, a.col2
  2   from (select t.group_id, t.group_key, t.col2,
  3                ROW_NUMBER() OVER( PARTITION BY GROUP_ID
  4                                   ORDER BY GROUP_KEY ASC NULLS LAST ) RNUM
  5         from t
  6         where test_anafunc.test_func(GROUP_KEY) = 1
  7             and col2 = 10816 ) a
  8   where a.RNUM = 1
  9  /

GROUP_ID                          GROUP_KEY       COL2
-------------------------------- ---------- ----------
x                                         3      10816

Whether I query the view or a query that has the view inlined -- no data (by
definition -- the query shouldn't get any)

Do it your way -- and you get a row.  But that is due to the fact that its a
wholly different question!
Review & Followup

      Rating: 4
Fair enough.
      :)  April 19, 2002
Reviewer:  Heath
       from USA
I suspected that the reason for this behavior would be along the lines of
changing the answer.  Thanks for the good example that proved this.

Can you suggest another approach to accomplish what I am trying to do?  The
requirement is that every query against this table have its results processed
through the analytic function logic.  That is, use whatever predicate the
user/application specifies to find a subset of the rows, run them through the
test_func function to reduce them further and then, for the set of remaining
rows, run them through the windowing logic and the analytic function to choose
one of those rows (RNUM=1).

Is my problem simply not being able to formulate a correct SQL for this
requirement?  Or is this something that can not be done using views containing
analytic functions?

I have to use a view because the Analytic function can not exist in a WHERE
clause.  Even if not for that reason, I would want the application to query a
view to only "see" a subset of the columns in the table.

I know of other ways to address the problem, but they are not as easy on the
database as the analytic function approach would be (assuming I can get it to
work). :)

Thanks again for the help,

Heath

          Followup:

Your requirements cannot be accomplished with a view then.

The predicate will always be applied AFTER the analytics buried in the view,
they must be.  This is because:

select analytic
 from t
 where condition

is VERY and ALWAYS different from

select *
from ( select analytic
         from t )
 where condition

The problem is, in a nutshell, you cannot get there from here.

Your reason for using a view is backwards from your requirement.  You CAN use an
analytic result in a predicate (you DO -- rnum = 1)!!!  You just have to do it
AFTER, you cannot do it DURING.

It is just like an aggregate.  You cannot say: where count(*) = 1, you have to
say HAVING count(*) = 1.  The predicate is done AFTER the result set is built.
Same with analytics.

I believe you can use analytics (i know you can)

I also know you cannot use a view given your above list of "i must do this".

      Rating: 4
I have a quick
      question about partitioning  December 04, 2002

Reviewer:  sam  from NJ USA
Tom,

Can you guide me to a white paper written on Table Partitioning & indexing ,
which is most useful & easy to understand.

          Followup:

(how many people saw this response coming ;)

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96524/c12parti.htm#464767
The concepts guide has a pretty good writeup on this...

      Rating: 5
how to make
      no_push_pred work  May 20, 2003
Reviewer:  PINGU
      SAN
Hi

I have following query

select ename, dname
from (select * from emp where deptno = 10) a,
     (select * from dept where deptno = 10) b
where a.empno = 7934;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=21)
   1    0   NESTED LOOPS (Cost=3 Card=1 Bytes=21)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=1 Card=1 Bytes=10)
   3    2       INDEX (UNIQUE SCAN) OF 'EMP_PK' (UNIQUE)
   4    1     TABLE ACCESS (FULL) OF 'DEPT' (Cost=2 Card=1 Bytes=11)

I think its pushing the predicate (where empno... ) into view, so I changed the
query to

select /*+ NO_PUSH_PRED(a) */ ename, dname
from (select * from emp where deptno = 10) a,
     (select * from dept where deptno = 10) b
where a.empno = 7934;

but got same execution plan when I was expecting two full table scan of emp and
dept...

How does this hint work?

          Followup:

it works with join conditions.  thats not a join.  You can use no_merge to get
an alternate (but junky) plan -- but basically, the CBO is going to be free to
move that predicate around.

You can get the plan you seem to be after using a ROWNUM trick:

scott@ORA920> explain plan for
  2  select ename, dname
  3  from (select * from emp where deptno = 10 and rownum > 0 ) a,
  4       (select * from dept where deptno = 10) b
  5           where a.empno = 7934
  6  /

Explained.

scott@ORA920> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
--------------------------------------------------

---------------------------------------------------------------------
| Id  | Operation             |  Name       | Rows  | Bytes | Cost  |
---------------------------------------------------------------------
|   0 | SELECT STATEMENT      |             |       |       |       |
|   1 |  NESTED LOOPS         |             |       |       |       |
|*  2 |   TABLE ACCESS FULL   | DEPT        |       |       |       |
|*  3 |   VIEW                |             |       |       |       |
|   4 |    COUNT              |             |       |       |       |
|*  5 |     FILTER            |             |       |       |       |
|*  6 |      TABLE ACCESS FULL| EMP         |       |       |       |
---------------------------------------------------------------------

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

   2 - filter("DEPT"."DEPTNO"=10)
   3 - filter("A"."EMPNO"=7934)
   5 - filter(ROWNUM>0)
   6 - filter("EMP"."DEPTNO"=10)

Note: rule based optimization

22 rows selected.

but the question would be "why" at that point.

May 20, 2003 --
      Thanks for the question regarding "tablespace point in time recovery --
      what is it?", version 8.1.5

      You Asked
What is  tablespace point in time recovery  that you often
mentioned
in your replies ?
Can you give a simple example how it could be of use?
Thanks.

      and we said...
Tablespace point in time recovery was introduced in Oracle8.0.
Prior to this ability, the only option was to recover an entire
instance to the same point in time.  Where this might be a
problem is if you had dropped an important table (by accident of
course!) and wanted to recover it.

Lets say its version 7.3 and you were operating in archivelog
mode so you have the ability to do a point in time recovery to
the point in time right before you dropped the table.  Your
options were:

o restore ALL of the backed up datafiles and rolled the entire
instance forward to the point in time immediately prior to the
drop table.  This implies that you would lose ALL changes to ALL
other data that occurred after you dropped the table.  In order
to get the table you had to lose the changes to everything else.

o restore system, rollback and the tablespace containing the
dropped table to another system (elsewhere). point in time
recover this mini instance to the correct point in time.  Export
the table.  Import it into the other instance.

With tablespace PITR (point in time recovery), available with
RMAN, we can instead just recover a given tablespace to that
certain point in time.  We do not have to do the entire
instance.  We don't have to do a traditional export to .dmp file
and import.  The process is much the same as the second method
above however the tablespace is "transported" from one database
to the other (so we don't have to export the table to a flat
file and reload it -- we just recover the datafile, detach it
from the temporary instance and attach it to the real instance).

see

http://technet.oracle.com/doc/oracle8i_816/server.816/a76993/ostspitr.htm#421341
for more detailed information.
Review &
      Followup

      Rating: 4
Recover dropped
      tablespace in no archive log mode  May 20, 2003

Reviewer:  Monica  from USA
Hi Tom,

I have a question regarding tablespace recovery.
I'm aware that if the database is in archive log mode one can recover the
dropped tablespace or any other object. But in case my database is not in
archive log mode still can I by any means restore a dropped tablespace files
since they exists at OS level?

          Followup:

if you have dropped the tablespace, and you are not in archive log mode, the
best you can do is:

a) restore the database elsewhere (system+rollback+TABLESPACE YOU WANT BACK)
b) using export either export the data OR transport the tablespace
c) using import either import the data OR attach the datafiles you transported
in b

      Rating: 4
Recover dropped
      tablespace in no archive log mode  May 20, 2003

Reviewer:  Monica  from USA
Hi Tom,

I have a question regarding tablespace recovery.
I'm aware that if the database is in archive log mode one can recover the
dropped tablespace or any other object. But in case my database is not in
archive log mode still can I by any means restore a dropped tablespace files
since they exists at OS level?

      Rating: 4
Recover dropped
      tablespace in no archive log mode  May 21, 2003

Reviewer:  Monica  from USA
Hi Tom,

I have a question regarding tablespace recovery.
I'm aware that if the database is in archive log mode one can recover the
dropped tablespace or any other object. But in case my database is not in
archive log mode still can I by any means restore a dropped tablespace files
since they exists at OS level?

      Rating: 4
Recover dropped
      tablespace in no archive log mode  May 21, 2003

Reviewer:  Monica  from USA
Hi Tom,

I have a question regarding tablespace recovery.
I'm aware that if the database is in archive log mode one can recover the
dropped tablespace or any other object. But in case my database is not in
archive log mode still can I by any means restore a dropped tablespace files
since they exists at OS level?

May 20, 2003 ade -- Thanks for the question regarding "sql
      loader questions", version 8.1.6.0.0

      You Asked
1.How do I load data from a datafile that refers to database
columns in multiple tables in a non sequential manner. i.e
example datafile:

t=table the field values will be inserted)
f=position of data in the data file (physical record)

t1.f1,t1.f2,t2.f3,t2.f4,t2.f5,t1.f6,t2.f7.

2. using the same example above as my datafile:

how do i solve the following:
if t2.f3 is not null, insert  a value (based on a query)
else if t2.f4 is not null, insert a value (based on a query)
esle if t2.f3 and t2.f4 = null then insert a value (based on
query). the logic here is not the problem. The problem is,
although we have three different fields in the data file,
which are mutually exclusive,the final value should be inserted
into the same single coulmn in the database?

3. is the "position" clause relative to the field in the
datafile or the actual column poition in the database?

Hope this makes some sense.

      and we said...
For #1, if you want to use:

INTO TABLE T1
REPLACE
FIELDS TERMINATED BY ','

type of syntax (not a fixed width, fixed position input file),
we must take 2 passes at this file with 2 control files OR load
into a view.

To use just sqlldr, we would have 2 control files:

LOAD DATA
INTO TABLE T1
REPLACE
FIELDS TERMINATED BY ','
(f1,
 f2,
 f3 filler,
 f4 filler,
 f5 filler,
 f6,
 f7 filler
)

-------------------------------------
LOAD DATA
INFILE *
INTO TABLE T2
REPLACE
FIELDS TERMINATED BY ','
(f1 filler,
 f2 filler,
 f3 ,
 f4 ,
 f5 ,
 f6 filler,
 f7
)
-------------------------------------

If you want to make one pass, you can use the "delimited"
package I have at

http://osi.oracle.com/~tkyte/SkipCols/index.html
then the control file could look like:

LOAD DATA
INFILE *
INTO TABLE T1
REPLACE
FIELDS TERMINATED BY ','
(f1 position(1:4096) "delimited.word(:f1,1,NULL,',')",
 f2 position(1:1)    "delimited.word(:f1,2,NULL,',')",
 f6 position(1:1)    "delimited.word(:f1,6,NULL,',')"
)
INTO TABLE T2
(
 f3 position (1:4096) "delimited.word(:f3,3,NULL,',')",
 f4 position (1:1)    "delimited.word(:f3,4,NULL,',')",
 f5 position (1:1)    "delimited.word(:f3,5,NULL,',')",
 f7 position (1:1)    "delimited.word(:f3,7,NULL,',')"
)
BEGINDATA
f1x,f2x,f3x,f4x,f5x,f6x,f7x
f1y,f2y,f3y,f4y,f5y,f6y,f7y
f1z,f2z,f3z,f4z,f5z,f6z,f7z

to load both.

For #2, Here is a small example.  DECODE (or the new CASE
statement) can be used.

OPTIONS(rows=1)
LOAD DATA
INFILE *
REPLACE
INTO TABLE T2
FIELDS TERMINATED BY ','
TRAILING NULLCOLS
(f1 filler,
 f2 filler,
 f3 ,
 f4 ,
 f5 ,
 f6 filler,
 f7 ,
 f8 "decode( :f3,
             null, decode(:f4,
                          null, (select '2NULL' from dual),
                                (select '1NULL' from dual)
                         ),
                   (select '0NULL' from dual)
           )"
)
BEGINDATA
f1x,f2x,f3x,f4x,f5x,f6x,f7x
f1y,f2y,,f4y,f5y,f6y,f7y
f1z,f2z,,,f5z,f6z,f7z

the decode says:

  if f3 is null
  then
     if f4 is null
     then
         insert (select 2null from dual)
     else
         insert (select 1null from dual)
     end if;
  else
    insert (select 0null from dual)
  end if;
ops$tkyte@8i> select* from t2;

F3    F4    F5    F7    F8
----- ----- ----- ----- -----
f3x   f4x   f5x   f7x   0NULL
      f4y   f5y   f7y   1NULL
            f5z   f7z   2NULL

As for #3, position is relative to a byte offset in the input
file -- its not field related or column related at all...
Review &
      Followup

      Rating: 3
Load columns
      into multiple tables !  May 20, 2003
Reviewer:
      rama  from ct, us
Table T1:

SQL> desc t1
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 COL1                                               NUMBER
 COL2                                               VARCHAR2(30)
 COL3                                               NUMBER
 COL4                                               NUMBER
 COL5                                               NUMBER
 COL6                                               NUMBER
TIMEDATE                                           VARCHAR2(20)

Table T2:

SQL> desc t2
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 COL4                                               NUMBER
 COL6                                               NUMBER
 TIMEDATE                                           VARCHAR2(20)

DataFile: c:\temp\test.txt

col1|col2|col3|col4|col5|col6
1|row1|12|13|14|15
2|second row2|21|22|23|24
3|row3 in source|31|32|33|34

Control File:

Load data
into table T1
replace
fields terminated by '|'
trailing nullcols
(
col1,
col2,
col3,
col4,
col5,
col6,
timedate "to_char(sysdate,'yyyymmdd')"
)
into table T2
trailing nullcols
(
filler1 FILLER,
filler2 FILLER,
filler3 FILLER,
col4,
filler4 FILLER,
col6,
timedate "to_char(sysdate,'yyyymmdd')"
)

 C:\temp\gics>sqlldr userid=test/test@testdb data=test.txt skip=1

 control = load.ctl

 SQL*Loader: Release 8.1.6.0.0 - Production on Tue May 20 09:42:09 2003

 (c) Copyright 1999 Oracle Corporation.  All rights reserved.

 Commit point reached - logical record count 2
 Commit point reached - logical record count 3

 SQL> select * from t1;

       COL1 COL2                                 COL3       COL4       COL5
 COL6 TIMEDATE
 ---------- ------------------------------ ---------- ---------- ----------
---------- --------------
          3 row3 in source                         31         32         33
   34 20030520
          1 row1                                   12         13         14
   15 20030520
          2 second row2                            21         22         23
   24 20030520

 SQL> select * from t2;

 no rows selected

Log File:

SQL*Loader: Release 8.1.6.0.0 - Production on Tue May 20 09:42:09 2003

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Control File:   load.ctl
Data File:      test.txt
  Bad File:     test.bad
  Discard File:  none specified

 (Allow all discards)

Number to load: ALL
Number to skip: 1
Errors allowed: 50
Bind array:     64 rows, maximum of 65536 bytes
Continuation:    none specified
Path used:      Conventional

Table T1, loaded from every logical record.
Insert option in effect for this table: REPLACE
TRAILING NULLCOLS option in effect

   Column Name                  Position   Len  Term Encl Datatype
------------------------------ ---------- ----- ---- ---- ---------------------
COL1                                FIRST     *   |       CHARACTER
COL2                                 NEXT     *   |       CHARACTER
COL3                                 NEXT     *   |       CHARACTER
COL4                                 NEXT     *   |       CHARACTER
COL5                                 NEXT     *   |       CHARACTER
COL6                                 NEXT     *   |       CHARACTER
TIMEDATE                             NEXT     *   |       CHARACTER
    SQL string for column : "to_char(sysdate,'yyyymmdd')"

Table T2, loaded from every logical record.
Insert option in effect for this table: INSERT
TRAILING NULLCOLS option in effect

   Column Name                  Position   Len  Term Encl Datatype
------------------------------ ---------- ----- ---- ---- ---------------------
FILLER1                              NEXT     1           CHARACTER
  (FILLER FIELD)
FILLER2                              NEXT     1           CHARACTER
  (FILLER FIELD)
FILLER3                              NEXT     1           CHARACTER
  (FILLER FIELD)
COL4                                 NEXT     1           CHARACTER
FILLER4                              NEXT     1           CHARACTER
  (FILLER FIELD)
COL6                                 NEXT     1           CHARACTER
TIMEDATE                             NEXT     1           CHARACTER
    SQL string for column : "to_char(sysdate,'yyyymmdd')"

Table T1:
  3 Rows successfully loaded.
  0 Rows not loaded due to data errors.
  0 Rows not loaded because all WHEN clauses were failed.
  0 Rows not loaded because all fields were null.

Table T2:
  0 Rows successfully loaded.
  0 Rows not loaded due to data errors.
  0 Rows not loaded because all WHEN clauses were failed.
  3 Rows not loaded because all fields were null.

Space allocated for bind array:                  65448 bytes(36 rows)
Space allocated for memory besides bind array:        0 bytes

Total logical records skipped:          1
Total logical records read:             3
Total logical records rejected:         0
Total logical records discarded:        0

Run began on Tue May 20 09:42:09 2003
Run ended on Tue May 20 09:42:12 2003

Elapsed time was:     00:00:02.61
CPU time was:         00:00:00.09

Is this possible to load data from single data file to multiple tables, if the
data is NOT in the control file
and not in FIXED format?

I am getting "3 Rows not loaded because all fields were null." message, and I
think the problem is with
the position field (specyfing NEXT -- meaning looking for next field and since
it is null, rejecting it?)

Your help would be much appreciated.

          Followup:

you'll have to make two passes on the file.  the first into clause "ate" the
entire row.  there was nothing to insert into the second table.  using delimited
data, there is no way to "reset" the pointer.

May
      20, 2003 Danilo -- Thanks for the
      question regarding "Shared Pool", version 8.1.5

      You Asked
Tom,

   I had a database with problems of buffer free and block size
was 4k, we did an upgrade in this Unix Box (Solaris 2.6), so I
recreated the database with a block size of 8k, increase the
db_block_buffers, the DB is working OK, I don´t have buffer free
problems, but I started to have problems with ORA-4031 in
shared_memory.

  Our application is based on Java/JDBC with lots of dynamic
SQL, I increased the size of shared_pool size, but the problem
is still going on (shared_pool - 120M), in about 3 to 4 minutes
all the shared pool is fragments in blocks of 76 ou 254 bytes,
the free SGA is about 100Mb, but the greatest block is 3k size.

  I put the alter system flush shared_pool in cron with a
interval of 5 minutes. I don´t know what is going on, it seems
that Oracle is taking to long to defragment the shared-pool,
with the 4k block size I didn't have the problem.

  Thanks

   Danilo Bonamini
   danilo_bonamini@yahoo.com

  I am at Home Know, so I don´t have the message, but I will get
it, I have hundreds of queries like:
select * from emp where empno = 1;
select * from emp where empno = 2;
select * from emp where empno = 3;
select * from emp where empno = 4;

  The message had something of sga heap, table_name, and I think
library cache.

      and we said...
Ok, this is an easy one to diagnose.

You are not using bind variables.  This is terrible.  Flushing
the shared pool is a bad solution -- you are literally killing
the performance on your box by not using them.

If I was to write a book on how to build “non scalable
applications in Oracle”, this would be the first and last
chapter.  This is a major cause of performance issues and a
major inhibitor of scalability in Oracle.  The way the Oracle
shared pool (a very important shared memory data structure)
operates is predicated on developers using bind variables.  If
you want to make Oracle run slowly, even grind to a total halt –
just refuse to use them.

For those that do not know, a bind variable is a placeholder in
a query.  For example, to retrieve the record for employee 1234,
I can either query:

SELECT * FROM EMP WHERE EMPNO = 1234;

Or I can query:

SELECT * FROM EMP WHERE EMPNO = :empno;

And supply the value for :empno at query execution time.  The
difference between the two is huge, dramatic even.  In a typical
system, you would query up employee 1234 maybe once and then
never again.  Later, you would query up employee 456, then 789
and so on.  If you use literal (constants) in the query – each
and every query is a brand new query, never before seen by the
database.  It will have to be parsed, qualified (names
resolved), security checked, optimized and so on.  In short, it
will be compiled.  Every unique statement you execute will have
to be compiled every time.  This would be like shipping your
customers Java source code and before calling a method in a
class – you would invoke the Java compiler, compile the class,
run the method and then throw away the byte code.  The next time
you wanted to execute the same exact method, you would do the
same thing – compile it, run it and throw it away.  Executing
SQL statements without bind variables is very much the same
thing as compiling a subroutine before each and every call.  You
would never consider doing that in your application – you should
never consider doing that to your database either.

Not only will parsing a statement like that (also called a HARD
parse) consume many more resources and time then reusing an
already parsed query plan found in the shared pool (called a
SOFT parse), it will limit your scalability.  We can see it will
obviously take longer, what is not obvious is that it will
reduce the amount of users your system can support.  This is due
in part to the increased resource consumption but mainly to the
latching mechanisms for the library cache where these plans are
stored after they are compiled.  When you hard parse a query, we
will spend more time holding certain low level serialization
devices called latches.  These latches protect the data
structures in the shared memory of Oracle from concurrent
modifications by two sessions (else Oracle would end up with
corrupt data structures) and from someone reading a data
structure while it is being modified.  The longer and more often
we have to latch these data structures, the longer the queue to
get these latches will become.  Similar to the MTS architecture
issue described above, we will start to monopolize scarce
resources.  Your machine may appear to be underutilized at times
– but yet everyone in the database is running very slowly.  This
will be because someone is holding one of these serialization
mechanisms and a line is forming.  You are not able to run at
top speed.

The second query above on the other hand, the one with :empno,
is compiled once and stored in the shared pool (the library
cache).  Everyone who submits the same exact query that
references the same object will use that compiled plan (the SOFT
parse).  You will compile your subroutine once and use it over
and over again.  This is very efficient and the way the database
is intending you will do your work.  Not only will you use less
resources (a SOFT parse is much less resource intensive), but
you will hold latches for less time and need them less
frequently.  This increases your performance and greatly
increases your scalability.

Just to give you a tiny idea of how huge of a difference this
can make performance wise, you only need to run a very small
test:

tkyte@TKYTE816> alter system flush shared_pool;
System altered.

tkyte@TKYTE816> declare
  2      type rc is ref cursor;
  3      l_rc rc;
  4      l_dummy all_objects.object_name%type;
  5      l_start number default dbms_utility.get_time;
  6  begin
  7      for i in 1 .. 1000
  8      loop
  9          open l_rc for
 10          'select object_name
 11             from all_objects
 12            where object_id = ' || i;
 13          fetch l_rc into l_dummy;
 14          close l_rc;
 15      end loop;
 16      dbms_output.put_line
 17      ( round( (dbms_utility.get_time-l_start)/100, 2 ) ||
 18        ' seconds...' );
 19  end;
 20  /
14.86 seconds...

PL/SQL procedure successfully completed.

tkyte@TKYTE816> declare
  2      type rc is ref cursor;
  3      l_rc rc;
  4      l_dummy all_objects.object_name%type;
  5      l_start number default dbms_utility.get_time;
  6  begin
  7      for i in 1 .. 1000
  8      loop
  9          open l_rc for
 10          'select object_name
 11             from all_objects
 12            where object_id = :x'
 13          using i;
 14          fetch l_rc into l_dummy;
 15          close l_rc;
 16      end loop;
 17      dbms_output.put_line
 18      ( round( (dbms_utility.get_time-l_start)/100, 2 ) ||
 19        ' seconds...' );
 20  end;
 21  /
1.27 seconds...

PL/SQL procedure successfully completed.

That is pretty dramatic.  The fact is that not only does this
execute much faster (we spent more time PARSING our queries then
actually EXECUTING them!) it will let more users use your system
simultaneously.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:992630543630
for a feature that might help you in 8.1.6 (not 8.1.5, new
feature) although it has the side effect of binding everything
-- some queries need a mixture of binds and constants and with
cursor_sharing -- you only get binds.

Review &
      Followup

      Rating: 4
ORA-04031 in
      snp background process....  August 29, 2001

Reviewer:  Mohan Naik  from Cyprus
Tom,

I'm getting a same kind of error but for image: ora_snp2_WW8 (where WW8 is my
instance name) in Oracle 8.0.6.0.0

Errors in file /oravl01/oracle/adm/WW8/bdump/snp2_1575.trc:
ORA-00604: error occurred at recursive SQL level 3
ORA-04031: unable to allocate 4184 bytes of shared memory ("shared
pool","PROCEDURE$","sga heap","state objects")

Please let me know what has to be done in such a case ?

      Rating: 4
shared
      pool  April 18, 2003
Reviewer:  yazid
       from France
Hi
I tested your example, but no big diffrences in the time of execution. I dont't
understand what happen :
First execution : 27,5
Second execution : 26,25 (diffrences : 1,25s : not much)

I work with a database V81700 on Win2000.

system@declt> declare
  2   type rc is ref cursor;
  3          l_rc  rc;
  4          l_dummy all_objects.object_name%type;
  5          l_start number default dbms_utility.get_time;
  6   i number := 0;
  7   date_jour date;
  8  begin
  9  -- tache 1
 10          for i in 1 .. 1000
 11          loop
 12              open l_rc for
 13             'select object_name
 14                from all_objects
 15               where object_id = ' || i;
 16             fetch l_rc into l_dummy;
 17             close l_rc;
 18         end loop;
 19         dbms_output.put_line('Tache1 -utilise all_objects- : '|| round(
(dbms_utility.get_time-l
_start)/100, 2 ) || ' seconds...' );
 20   end;
 21  /
Tache1 -utilise all_objects- : 27,5 seconds...

Procédure PL/SQL terminée avec succès.

system@declt> r
  1  declare
  2   type rc is ref cursor;
  3          l_rc  rc;
  4          l_dummy all_objects.object_name%type;
  5          l_start number default dbms_utility.get_time;
  6   i number := 0;
  7   date_jour date;
  8  begin
  9  -- tache 1
 10          for i in 1 .. 1000
 11          loop
 12              open l_rc for
 13             'select object_name
 14                from all_objects
 15               where object_id = ' || i;
 16             fetch l_rc into l_dummy;
 17             close l_rc;
 18         end loop;
 19         dbms_output.put_line('Tache1 -utilise all_objects- : '|| round(
(dbms_utility.get_time-l_sta
 20*  end;
Tache1 -utilise all_objects- : 26,25 seconds...

Procédure PL/SQL terminée avec succès.

I checked the v$sqlarea and the result was 1Load an 2 executions (the code was
ot unloaded):
system@declt> r
  1  select sql_text, executions, loads from v$sqlarea
  2  where sql_text like '%begin%'
  3* order by first_load_time desc

SQL_TEXT
--------------------------------------------------------------------------------
EXECUTIONS      LOADS
---------- ----------
select sql_text, executions, loads from v$sqlarea where sql_text like '%begin%'
order by first_load_time desc
         3          2

declare  type rc is ref cursor;         l_rc  rc;         l_dummy all_objects.ob
ject_name%type;         l_start number default dbms_utility.get_time;  i number
:= 0;  date_jour date; begin -- tache 1         for i in 1 .. 1000         loop
            open l_rc for            'select object_name               from all_
objects              where object_id = ' || i;            fetch l_rc into l_dumm
y;            close l_rc;        end loop;        dbms_output.put_line('Tache1 -
utilise all_objects- : '|| round( (dbms_utility.get_time-l_start)/100, 2 ) || '
seconds...' );  end;
         2          1

Any adea please
thank you

          Followup:

ALTER SYSTEM FLUSH SHARED_POOL;

you ran it more then once, you soft parsed the second time around.

tkyte@TKYTE816> alter system flush shared_pool;
System altered.

that simulates REALITY.  when you have an application that doesn't use binds --
it is generating UNIQUE sql that never existed before and will not be reused
after.

      Rating: 5
To find sql
      that is not using bind vars  May 20, 2003

Reviewer:  Sachin  from Germany
Tom,
I remember seeing a script on your site that finds sql that is not using bind
variables.
Could you please post it here...

Thanks.

          Followup:

search for

remove_constants


Subject | Article | Last Updated (East coast time)
design question & multicolumn index | article only | May 21, 2003 07:18 am
Timed_statistics & SQL_TRACE parameters | article only | May 21, 2003 07:23 am
ALL_CONSTRAINTS | article only | May 21, 2003 07:34 am
use of table aliases and reduction in recursive sql queries | article only | May 21, 2003 08:15 am
All about SCN. | article only | May 21, 2003 08:31 am
Oracle Fail Safe | article only | May 21, 2003 08:54 am
very big sga side effects | article only | May 21, 2003 09:00 am
Help changing PL/SQL to SQL | article only | May 21, 2003 09:04 am
using decode in report | article only | May 21, 2003 09:47 am
order of firing triggers in 8.1.6 | article only | May 21, 2003 03:07 pm
using functions in insert statements -- ORA-06571: Function does not guarantee not to update | article only | May 21, 2003 05:08 pm
Self-Join | article only | May 21, 2003 05:25 pm
Query | article only | May 21, 2003 07:18 pm
Frgamentation in System Data File. | article only | May 23, 2003 07:43 am
How to handle NULL values in JDBC while using datum | article only | May 23, 2003 08:11 am
ORA_KGRL7_DEPENDICIES | article only | May 23, 2003 08:14 am
assige global temporary tables content to specific temporary tablespace | article only | May 23, 2003 08:18 am
RBO and Optimizer hints | article only | May 23, 2003 08:20 am
ordering a connect by | article only | May 23, 2003 08:21 am
Pivot a report by date range | article only | May 23, 2003 08:26 am
basic decision on sql query performance | article only | May 23, 2003 08:36 am
materialized view problem while refreshing | article only | May 23, 2003 08:50 am
OCI or Precompiler | article only | May 23, 2003 09:10 am
Order by in connect by clause | article only | May 23, 2003 10:00 am
DDL related parse, execute, fetch | article only | May 23, 2003 10:01 am
date partitions | article only | May 23, 2003 10:03 am
grouping by CLOB | article only | May 23, 2003 12:07 pm
query to find all sqlstatements taking more then a certain amount of time | article only | May 24, 2003 09:37 am
Random Sample | article only | May 24, 2003 10:15 am
SGA Parameters | article only | May 24, 2003 11:39 am
Difference between "db file scatterd read" and "db file sequential read" | article only | May 25, 2003 09:42 am
empty result set and CASE statement | article only | May 25, 2003 10:41 am
OCP | article only | May 25, 2003 11:38 am
Number Joins | article only | May 25, 2003 12:49 pm
SQL Tuning | article only | May 26, 2003 09:20 am
Menus and Running Reports | article only | May 26, 2003 09:30 am
When does the space occupied in temporary tablespace get released | article only | May 26, 2003 09:40 am
Interviews: Knowledge of tools Vs. concepts? | article only | May 26, 2003 09:52 am
Create Constraints Script | article only | May 26, 2003 10:28 am
sga size | article only | May 26, 2003 12:38 pm
Doubt about shared sql | article only | May 26, 2003 12:39 pm
VPD and bitmap indexes | article only | May 26, 2003 08:41 pm
About Rollback segments | article only | May 27, 2003 07:37 am
Sizing the recordset for the application | article only | May 27, 2003 07:55 am
Few questions | article only | May 27, 2003 08:01 am
lob storage | article only | May 27, 2003 08:15 am
IP address | article only | May 27, 2003 11:10 am
Parse/Executions | article only | May 27, 2003 05:19 pm
Closing Ref Cursors | article only | May 27, 2003 05:24 pm
Creating dynamic stored procedure problem | article only | May 27, 2003 05:26 pm
Preparing a Demo Pack - Reg. | article only | May 27, 2003 05:45 pm
row(s) 1 - 51 of 51
May 21, 2003 -- Thanks for the question regarding "design question & multicolumn index", version 8.1.7
You Asked
Hi Tom,

I have a scenario like this: I have a database with about 40 million recores
that I will load into Oracle using sql*loader(direct mode, but no partition
option available). After loading I would like to create a primary key column --
let's say pid -- to identify each row. So I would:
ALTER TABLE A ADD PID NUMBER(9);
UPDATE A SET PID=SEQ_A.NEXTVAL;(assuming seq_a is set up correctly)

My question is: I can't update all 40 million rows together, that would be too
big a transaction. I can't use rownum. I thought of doing sth like: update a set
pid=seq_a.nextval where state='xx'; so that at least the whole update is broken
into 50 smaller pieces and then I can create a larger rollback segment specially
for this purpose and then use 'SET TRANSACTION USE ....', but data is not
dispersed evenly by state: some states have millions, others would have only
several dozen thousand, and using a huge rollback segment for all of them would
be wasteful. The best that I can think of is to be able to divide the table
evenly, e.g. in batches of 100,000 and update each slice, so the use of rollback
segment is more predictable. If there is no column in the table that satifies
this criteria, is there any other thing we can do to accomplish the goal?

A related question: in creating index for some of the columns, e.g. first name,
last name, city, state, street name, should I use multiple column index or
single column? I tend to use single column index, since otherwise a query
skipping 1 column or in another order may not be able to use the index. But I
read that in some instances
multicolumn indexes might be preferred over several single-column b-tree
indexes. Can you explain when?

Appreciate your help very much.
and we said...
Why not load the column with sqlldr using its sequence (not the db sequence, the
sqlldr sequence) and then creating a sequence that starts at 50,000,000 and
using that from then on in?

If you have my book -- i talk about indexing in depth.  Concatenated indexes are
extremely useful.  If you have queries like:

where a = 5
where a = 5 and b = 6
where a = 5 and b = 6 and c = 7

a concatenated index on (a,b,c) is what you want.

if you don't, you don't.
Review & Followup
Rating: 4
single/vs multicolumn May 08, 2002
Reviewer: Andrew from Calgary Canada
Tom, I see it as another myth, that single column artificial PK is better than
the multicolumn composite key. It is probably more widespread than fighting
fragmentation.
Some people just love to create a surrogate PK from the sequence. I see the
necessity of such only in the warehouses star schemas, or when the composite PK
becomes unwidely long. It is the logic of your schema (as per your example,
particular, what is sitting in the where clause) dictate the index structure.
What is the point of creating artificial column, if one still will have to
create a multicolumn index (either to spead up the query or to establish the
logical uniquiness)? Besides, there is another advantage: if you name the fields
in the parent/child tables the same and you have two composite PKs (in the child
table it will be one column longer) and you have appropriate FK, it is extremely
easy to see the relationship just by looking on the table creation script. With
single column PK it is sorta vague... Tom, what is your opinion in general on
this debate?

Followup:
Well, i don't put this into the myth category.  there isn't really a mythology
behind this one.  This is more of an "opinion" (so, what is the difference
between a myth and opinion..... hmmm )

Me, I'm sort of a fan of the surrogate key.

Never fails -- if you define a primary key based on the natural key -- someone
wants to UPDATE IT (meaning it was NOT the primary key in the first place).

I sort of use a number column named "id" as a primary key in most of my tables
(sheepish grin here).  Pretty much every table in asktom has a column "id" in
it.  Sometimes that is due to there not being a "natural key" (eg -- a question
being asked has an ID).  Other times it is because I just do it that way all of
the time.

Rating: 4
May 09, 2002
Reviewer: Patricia Hu from MD USA
Always the best solution from Tom! Had to admit I did not realize there is this
sequence option in sqlldr(did not read the book carefully enough and did not use
imagination hard enough), but that is exactly what I need.

About the index, suppose it justifies to create multiple single column indexes
and I did for column a, b, c, when querying like .... where a=xx and b=xx and
c=xx, the trace shows it tends to use only 1 index, even if I used hints like
index_join and the table was analyzed using "estimate statistics". What could be
the reason for this? Also if trace shows it is using only 1 index, do I still
need to go to v$filestat to check read and write count to know if the index is
being used?

BTW, is something going on with your email queue again? I did not receive your
email response.

Thanks a million.

Followup:
You want a single concatenated index -- not multiple indexes.  Doing an index
join -- if we have to goto the table anyway -- isn't something we are going to
do (not worth it).

If trace shows it is using an index, you are done, it is using the index.

Email is working - you should have recieved one.

I know why you didn't get it though.  The email this generated to you bounced:

The original message was received at Thu, 9 May 2002 19:30:02 -0400 (EDT)
from osi.oracle.com [148.87.130.147]

   ----- The following addresses had permanent fatal errors -----
pzh@wolpofflaw.com

----- Transcript of session follows -----
550 5.1.2 pzh@wolpofflaw.com... Host unknown (Name server: mx01.erols.com.: no
data known)

Rating: 5
How about select sys_guid() into " ID " from dual May 10, 2002
Reviewer: pascal from HH,Germany
Hi Tom

Thanks for a great Discussion on this...

I'm also a big fan of every Table having the surrogate "ID" primary key column
and then populating it from within a Before Insert row trigger....

But now i am planning to shift to Replication, so i would like to avoid using
Sequences as they might clash..

so i select  sys_guid()  into my RAW(16) surrogate key and use it as a primary
key.

But this is now  RAW(16) Type...Not a number ....

I read somewhere in Oracle Docs that the use of RAW is discouraged and is used
only for backaward compatibility.

Do you see any Danger/issues  for me to use  this strategy?

Your opinion on this would be greatly appreciated .

Regards

Pascal

Followup:
RAW is ok -- "LONG RAW" is what is discouraged.  Blobs should be used instead of
long raw.

For replication -- i would still stick with sequences.  I would pick some number
that represents the max number of sites I envision (say that number is 20).  I
would multiply by 10 (200) - just to be safe.

At site one, you will:
create sequence my_seq start with 1 increment by 200;

At site two, you will:
create sequence my_seq start with 2 increment by 200;

and so on.  Now, each site generates it's own set of numbers, it is smaller then
raw(16) and it is "just a number" -- something people are expecting.  Raw can be
unwieldy (try querying that table in sqlplus).

Rating: 4
May 10, 2002
Reviewer: Shivaswamy from Dallas, TX
Tom,

Do you use row level trigger to update the "id" or some other method..?

Shivaswamy

Followup:
insert into t values ( my_seq.nextval, ...... );

Rating: 4
Concatenated index May 20, 2003
Reviewer: Craig from NW Arkansas
What if you have queries like:

where b = 6
where c = 7
where b = 6 and c = 7

Will the concatenated index still be the way to go?  If I have a concatenated
index on t(a,b,c), will the above queries be able to use that index?

Followup:
maybe, maybe not.

search this site for

"index skip scan"

which is an optimization technique in 9i that would allow the index on (a,b,c)
to be used for that query.

May 21, 2003 Baljinder -- Thanks for the question regarding "Timed_statistics & SQL_TRACE parameters", version 8.0.5
You Asked
1. Is there any relation between Timed_statistics & SQL_TRACE
parameters . I mean to say suppose I set Timed_statistics
parameter as true but SQL_TRACE as false, Now my question is
whether timed statistics will still be collected or not.?

2. In V$parameter view what is the meaning of isses_modifiable &
issys_modifiable columns .

Because for SQL_TRACE & TIMED_STATISTICS parameters the values
are as under
                    isses_modifiable            issys_modifiable
SQL TRACE  :        FALSE                       FALSE
TIMED_STATISTICS    TRUE                        Immediate

Plese explain with the above values for SQL_TRACE &
TIMED_STATISTICS parameters

Thanks In Advance
Howie
and we said...
Timed stats is done independent of sql_trace and vice versa.  It
is somewhat safe to say that setting sql_trace without
timed_statistics is not very useful but setting timed_statistics
without sql_trace can be.  Sql_trace isn't very useful without
timed statistics.

As for v$parameter...

isses = Is Session
issys = Is System

says whether a parameter can be modified at the SESSION level
via alter session or the system level via alter system.

As you can see -- it is lying to you about SQL_TRACE in this
view.  It is a known issue and is being worked on.
Review & Followup
Rating: 4
i am not getting it March 27, 2002
Reviewer: lakshmi narasimhan R from Dubai, UAE
Hello Tom

Sorry i am not getting the word

"but setting timed_statistics without sql_trace can be"

do u mean if times_statistics is set and sql Trace is not set still the trace
file will be generated.

I thought only when sql_Trace is on then trace file will be generated.

Can u explain please.

Followup:
I said:

It is somewhat
safe to say that setting sql_trace without timed_statistics is not very useful
but setting timed_statistics without sql_trace can be.

which means:

1) using sql_trace without timed_statistics is useless.  Without the timings,
the tkprof report is virutally (not entirely) useless.

2) using timed_statistics does not imply you are using sql_trace.  timed stats
by itself it very useful (you might be using statspack or utle/bstat instead of
tkprof or you just are looking at major wait events)

Rating: 3
TIMED_STATISTICS and performance May 20, 2003
Reviewer: A reader
Does turning TIMED_STATISTICS on result in performance degradation? We are on
ver 8.1.7.4.

Followup:
not anything I've ever worried about.  the benefit of having it on has always,
without exception, outweighed any percieved performance hit.

It is neglible.

May 21, 2003 berto -- Thanks for the question regarding "ALL_CONSTRAINTS", version 8.1.7
You Asked
Tom,

When I was investigating a performance-issue for a customer, I got involved in a
access-problem for queries on ALL_ dictonary_views.

For example: a querie on ALL_TABLES with table_name as parameter will lead to a
execution plan with a full-table-scan on OBJ$.
There are indexes on OBJ$ with  only owner as leading column in the index. So
every query on an ALL_... view will force a full table_scan on OBJ$, even if the
object_name is included in de where-clause.

question: Why isn't there by default an index present with object_name as
leading column?

CREATE UNIQUE INDEX I_OBJ4 ON
  OBJ$(NAME, NAMESPACE, REMOTEOWNER, LINKNAME, SUBNAME) ;

This is the problem_query that is causing the performance_trouble

SELECT a.column_name
FROM
all_cons_columns a,
all_constraints b
WHERE b.constraint_type = 'P' AND
b.constraint_name = a.constraint_name
AND b.table_name = :2

Can it is recommended to add this index to OBJ$, I don't have any idea what the
impact of this will be.

Thank you for investigating this problem

Berto Evering
and we said...
you cannot even consider indexing SYS owned tables -- don't even consider it, it
is not something you can do.

The question is -- why would you query ALL_* tables like that without having an
OWNER as part of the predicate?  Basically -- your query as written is
*meaningless*

when using ALL_* views, you need to consider OWNER as part of the key pretty
much most of the time.  Consider (i have lots of EMP tables):

ops$tkyte@ORA920> column column_name format a10
ops$tkyte@ORA920> set echo on
ops$tkyte@ORA920> @test
ops$tkyte@ORA920> variable tname varchar2(30);
ops$tkyte@ORA920> variable oname varchar2(30);
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec :tname := 'EMP'

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec :oname := 'SCOTT'

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> @trace
ops$tkyte@ORA920> alter session set events '10046 trace name context forever,
level 12';

Session altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> SELECT a.owner, b.owner, a.column_name
  2  FROM
  3  all_cons_columns a,
  4  all_constraints b
  5  WHERE b.constraint_type = 'P' AND
  6  b.constraint_name = a.constraint_name
  7  AND b.table_name = :tname
  8  /

OWNER                          OWNER                          COLUMN_NAM
------------------------------ ------------------------------ ----------
SCOTT                          DEMO                           EMPNO
DEMO                           DEMO                           EMPNO
WSMGMT                         DEMO                           EMPNO
SCOTT                          WSMGMT                         EMPNO
DEMO                           WSMGMT                         EMPNO
WSMGMT                         WSMGMT                         EMPNO
SCOTT                          SCOTT                          EMPNO
DEMO                           SCOTT                          EMPNO
WSMGMT                         SCOTT                          EMPNO

9 rows selected.

so, is empno really the primary key of EMP 9 times over?  nah...  This query

ops$tkyte@ORA920>
ops$tkyte@ORA920> SELECT a.column_name
  2  FROM
  3  all_cons_columns a,
  4  all_constraints b
  5  WHERE b.constraint_type = 'P' AND
  6  b.constraint_name = a.constraint_name
  7  and a.owner = b.owner
  8  AND b.table_name = :tname
  9  and b.owner = :oname
 10  /

COLUMN_NAM
----------
EMPNO

gets the right answer -- and, tkprof says:

SELECT a.owner, b.owner, a.column_name
FROM
all_cons_columns a,
all_constraints b
WHERE b.constraint_type = 'P' AND
b.constraint_name = a.constraint_name
AND b.table_name = :tname

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.04       0.11          0       1654          0           9
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.04       0.11          0       1654          0           9

********************************************************************************

SELECT a.column_name
FROM
all_cons_columns a,
all_constraints b
WHERE b.constraint_type = 'P' AND
b.constraint_name = a.constraint_name
and a.owner = b.owner
AND b.table_name = :tname
and b.owner = :oname

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0         90          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0         90          0           1

that is does so in a fairly efficient manner (as opposed to the other -- what
I believe to be incorrect -- query)
Review & Followup
Rating: 3
How do you avoid ORA-01039? May 20, 2003
Reviewer: Matt from Australia
ORA-01039:insufficient provileges on underlying objects of view

I've been tuning a query that accesses the all_objects view and have been
receiving this error. I tried the following approaches:

1) select on obj$ and user$ to user (while writing this I have realised that I
have missed some of the underlying objects)
2) select any table to user

Neither worked.

I am now tuning connected as SYS, which I do not like.

What premissions are required to be able to EXPLAIN or tkprof queries that
access all_* views without receiving this error?

Thanks in advance

Followup:
you would need select on the underlying dictionary objects.  It is a security
feature controlled by the O7_DICTIONARY_ACCESSIBILITY init.ora parameter (search
this site for that init.ora parameter)

Rating: 4
thanks June 12, 2003
Reviewer: berto evering from The Netherlands
Tom,
thanks for the time you take, for investigating/answering my question.

May 21, 2003 rd -- Thanks for the question regarding "use of table aliases and reduction in recursive sql queries", version 8.1.7
You Asked
Tom

1.How far is the below statement true. Can you explain me the fundamentals
behind the below statement.

"
Table  aliases should be used in all  queries
that have more than one table in the FROM  clause.
The use of table aliases speeds up the parse phase
of  an  oracle query, by reducing  the  number  of
recursive sql queries.
"

Infact can you give me an example and explain how use of table aliases help in
query performance.

2.
From what I grasped, while choosing the execution plan for a query oracle
optimiser  works looks at each of  the  WHERE  clauses and assigns the tables
concerned  a number based on the type of predicate e.g. field = 'const'  or
field(+) = field. It then chooses  the
 table with the lowest score as the driving  table.

Tom suppose Iam joining 10 tables for my query, how can I determine, how many
rows are returned from each table.

can you give an example and explain how should we arrange our where clause so
that the query gets executed with the least cose. What Iam asking is  can you
show me a query involving more than 3 to 4 tables where by changing the order of
the predicates , there is improvement in the query performance.

3.Tom after going through all your answers on in and exists, I have come to the
following conclusion

if the subquery returns  few rows, but, the main query  returns  a
lot of rows for each row from the subquery use the IN operator.

But what in cases where an in clause includes constants like
where  where name in ( 'sdfs', 'sfsd', '234sdff'). Doesnt it result in 3 full
table scans.

what if I change it like
where name='sdfs' and
where name='sfsd' and
where name='234sdff'

4.

I read this stuff from internet, can you explain more about
bind variable and host variable and what is the relationship between them. If
possible use an example

"
                    The binding phase takes place after the parse
               phase. It is at this stage that the user variables
               are decided. For instance in the query select  'x'
               from  person where name = :1 the host  variable  1
               would have its value 'bound' into the query during
               binding  by  having the address of  :1  associated
               with the host variable.
"

 Thank you very much
and we said...
1) there is some truth to it.  What they are saying (incompletely) is that you
should use qualified identifiers.  Its not the table alias that as is important
as qualifying the identifiers in the query itself.

It could be easier to parse:

select emp.ename, dept.dname
  from emp, dept
 where emp.deptno = dept.deptno

then

select ename, dname
  from emp, dept
 where emp.deptno = dept.deptno

even though both work and return the same results.  In the first query , the
parser doesn't have to look up which table could contribute ENAME and which
could contribute DNAME - it only has to check that emp has an ename and dept has
a dname.

I fail to see how using a table alias could reduce the amount of rescursive sql
however.  It is easy to see how using table aliases (or the name itself) on all
identifiers makes it easier on the parser (shaves a tiny bit of CPU off the
runtime).  It also protects you from changes in the schema -- if someone adds a
DNAME or ENAME column to EMP/DEPT -- the first query will contain to function,
the second will fail.

2) Well, in Oracle8i I would strongly -- very strongly -- encourage you to use
the cost based optimizer (CBO).  There are many features (partitioned tables,
bitmapped indexes, parallel query, function based indexes, parallel query, etc)
that work only with the CBO.

If you use the CBO, the order of the predicate doesn't count, doesn't matter,
isn't taken into consideration.

Using the RBO, the driving table is actually taken primarily from the order of
the tables in the FROM clause.  Consider:

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

Table created.

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

Table created.

ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> create index emp_idx on emp(deptno);

Index created.

ops$tkyte@ORA817.US.ORACLE.COM> alter table dept add constraint dept_pk primary
key(deptno);

Table altered.

ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> select * from emp, dept
  2  where dept.deptno = emp.deptno
  3  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     TABLE ACCESS (FULL) OF 'DEPT'
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
   4    3       INDEX (RANGE SCAN) OF 'EMP_IDX' (NON-UNIQUE)

ops$tkyte@ORA817.US.ORACLE.COM> select * from dept, emp
  2  where dept.deptno = emp.deptno
  3  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     TABLE ACCESS (FULL) OF 'EMP'
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'DEPT'
   4    3       INDEX (UNIQUE SCAN) OF 'DEPT_PK' (UNIQUE)

ops$tkyte@ORA817.US.ORACLE.COM> set autotrace off

see, just flip the order of tables and the optimizer will change the plan.
Running the above two queries again using the CBO:

ops$tkyte@ORA817.US.ORACLE.COM> analyze table emp compute statistics;

Table analyzed.

ops$tkyte@ORA817.US.ORACLE.COM> analyze table dept compute statistics;

Table analyzed.

ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> select * from emp, dept
  2  where dept.deptno = emp.deptno
  3  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=14 Bytes=700)
   1    0   NESTED LOOPS (Cost=5 Card=14 Bytes=700)
   2    1     TABLE ACCESS (FULL) OF 'DEPT' (Cost=1 Card=4 Bytes=72)
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=1 Card=14 Bytes=448)
   4    3       INDEX (RANGE SCAN) OF 'EMP_IDX' (NON-UNIQUE)

ops$tkyte@ORA817.US.ORACLE.COM> select * from dept, emp
  2  where dept.deptno = emp.deptno
  3  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=14 Bytes=700)
   1    0   NESTED LOOPS (Cost=5 Card=14 Bytes=700)
   2    1     TABLE ACCESS (FULL) OF 'DEPT' (Cost=1 Card=4 Bytes=72)
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=1 Card=14 Bytes=448)
   4    3       INDEX (RANGE SCAN) OF 'EMP_IDX' (NON-UNIQUE)

the order of the tables/predicate -- it just doesn't matter.

3) an "x IN (a,b,c)" does not result in 3 full scans.  It might result in 1 full
scan if x is not indexed but if x is indexed -- it'll do three INDEX range scans
and concatenate the results together.

the change you propose would ALWAYS return zero rows.  I assume you mean OR --
in which case it would be processed just like the IN (a,b,c)

4) see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1063759517155
....
Review & Followup
Rating: 3
look at your statement August 09, 2001
Reviewer: Bibi Za from USA
--
In the first query , the
parser doesn't have to look up which table could contribute ENAME and which
could contribute DNAME - it only has to check that emp has an ename and dept has
a dname.
--

what is the difference between  looking up which table could contribute enamem,
and which could contribute dname, AND
CHECKING WHETHER EMP HAS AN ENAME AND DEPT HAS A DNAME.

in both the cases is oracle not doing the same amount of work, isnt cpu cost
both for the same

Rating: 4
recursive sql August 09, 2001
Reviewer: A reader from USA
What factors will lead to a lot of recursive sql.

Is a lot of recursive sql good or bad.

How can we reduce recursive sql.

Rating: 4
Responses August 10, 2001
Reviewer: Connor from UK
In response to post 1:
The parser must check BOTH tables for each of the column names if they are not
aliased - thats why there is more effort.
In response to post 2:
Probably the biggest (and worst) thing which causes lots of recursive sql is not
use bind variables.
Response for Tom:
Of course you can override the CBO using the ORDERED_PREDICATES hint

Rating: 5
Good example showing actions of CBO August 11, 2001
Reviewer: Harrison from Haymarket, VA USA
Hi Tom:

I liked the example showing the effect of the CBO; I am
always surprised at how easy you find it to demonstrate
something that I imagine it would take all to day to
prove (and would take me all day, anyway).  If I see
enough of this, I may learn something.  Thanks

Rating: 4
Please Explain this... May 21, 2003
Reviewer: Reader from India
Recently I read that if we use smaller aliases for table
like instead of emp.ename -> e.ename etc.
the execution speed will be improved.
is this true ?

Followup:
did they supply you the scientific proof to back this up?  if not, I would stop
reading that persons material.  every statement like that should be "proven".

is there a teeny nugget of almost truth in there?  sure, it takes neglibly less
cpu power to parse such a statement.

does it matter in real life?  not a bit.

when would it matter the most?  if you hard parse every query in your system.
But -- then you have a much much much larger problem then worrying about a
nanasecond of cpu spent parsing a bigger string.  You have a large bug in your
system in that you are not using bind variables.

when would it matter the second most?  when you soft parse a query before each
execute.  But -- then you have a much much much larger problem then worrying
about a nanasecond of cpu spent parsing a bigger string.  You have a large bug
in your system in that your developers don't know how to write good code.

When you write code properly, it matters not a twit.

Consider case 1, you hard parse lots:

ops$tkyte@ORA920> create table emp as select * from scott.emp;
Table created.

ops$tkyte@ORA920> exec runStats_pkg.rs_start;
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> declare
  2      type rc is ref cursor;
  3      l_cursor rc;
  4  begin
  5      for i in 1 .. 10000
  6      loop
  7          open l_cursor
  8          for
  9          'select e.empno from emp e where e.empno = ' || i;
 10          close l_cursor;
 11      end loop;
 12  end;
 13  /
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runStats_pkg.rs_middle;
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> declare
  2      type rc is ref cursor;
  3      l_cursor rc;
  4  begin
  5      for i in 1 .. 10000
  6      loop
  7          open l_cursor
  8          for
  9          'select e12345678901234567890123456789.empno
 10             from emp e12345678901234567890123456789
 11            where e12345678901234567890123456789.empno = ' || i;
 12          close l_cursor;
 13      end loop;
 14  end;
 15  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runStats_pkg.rs_stop(1000);
Run1 ran in 1467 hsecs
Run2 ran in 1622 hsecs
run 1 ran in 90.44% of the time

Name                                Run1      Run2      Diff
LATCH.library cache              200,131   190,895    -9,236
LATCH.child cursor hash table     79,999    70,191    -9,808
LATCH.library cache pin           90,062    70,418   -19,644
LATCH.library cache pin alloca    80,036    60,242   -19,794
STAT...session uga memory              0    65,464    65,464
LATCH.shared pool                155,169   230,454    75,285
STAT...session pga memory              0   131,072   131,072

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
690,196   707,917    17,721  97.50%

PL/SQL procedure successfully completed.

neither piece of code is acceptable on my system, so the fact one is
marginally faster is not relevant.  they would both be rejected out of hand.

Next, we softparse:

ops$tkyte@ORA920> exec runStats_pkg.rs_start;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      type rc is ref cursor;
  3      l_cursor rc;
  4  begin
  5      for i in 1 .. 10000
  6      loop
  7          open l_cursor
  8          for
  9          'select e.empno from emp e where e.empno = :n' using i;
 10          close l_cursor;
 11      end loop;
 12  end;
 13  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runStats_pkg.rs_middle;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> declare
  2      type rc is ref cursor;
  3      l_cursor rc;
  4  begin
  5      for i in 1 .. 10000
  6      loop
  7          open l_cursor
  8          for
  9          'select e12345678901234567890123456789.empno
 10             from emp e12345678901234567890123456789
 11            where e12345678901234567890123456789.empno = :n'
 12            using i;
 13          close l_cursor;
 14      end loop;
 15  end;
 16  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runStats_pkg.rs_stop(1000);
Run1 ran in 351 hsecs
Run2 ran in 334 hsecs
run 1 ran in 105.09% of the time

Name                                Run1      Run2      Diff

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
233,986   233,752      -234 100.10%

PL/SQL procedure successfully completed.

due to the variances in "load" on the system, this time, the first query is
actually slower parsing.  Note how the latching went way down, that is good but
this code is still "buggy" to me.  There is a soft parse for each execute --
nasty.  Lets fix that

ops$tkyte@ORA920> exec runStats_pkg.rs_start;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      cursor c(p_number in number)
  3      is
  4      select e.empno
  5        from emp e
  6       where e.empno = p_number;
  7  begin
  8      for i in 1 .. 10000
  9      loop
 10          open c(i);
 11          close c;
 12      end loop;
 13  end;
 14  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runStats_pkg.rs_middle;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> declare
  2      cursor c(p_number in number)
  3      is
  4      select e12345678901234567890123456789.empno
  5        from emp e12345678901234567890123456789
  6       where e12345678901234567890123456789.empno = p_number;
  7  begin
  8      for i in 1 .. 10000
  9      loop
 10          open c(i);
 11          close c;
 12      end loop;
 13  end;
 14  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runStats_pkg.rs_stop(1000);
Run1 ran in 61 hsecs
Run2 ran in 62 hsecs
run 1 ran in 98.39% of the time

Name                                Run1      Run2      Diff

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
53,368    53,473       105  99.80%

PL/SQL procedure successfully completed.

there you go, now there is in fact no measurable difference between them --
but look at the times here, 61 hsecs vs 1500 hsecs.  NOW you see where the
difference is, where the measurable, massive performance opportunities are...
They are not in the size of your variable names -- they are in the way you write
your code!

Rating: 5
use of table aliases and reduction in recursive sql queries November 10, 2003
Reviewer: Jean Antoine from St. Louis, MO
You have a way to write responses most of us can read. You are patient,
informative and accurate. Thanks for sharing the wealth!

May 21, 2003 Saibabu -- Thanks for the question regarding "All about SCN.", version 9.2
You Asked
Hi Tom,

1) What is difference between SCN base and SCN wrap. What for SCN wrap is used.
I always see SCN wrap zero in v$transaction.

2)I have a small table and all of its data fit in one data block. From one
session this table was modified without commit or rollback, from second session
after some time select query was issued on this table. How does Oracle find
block was modified but not commited especially when second session scn is
greater than first session scn?

3)How does Oracle find which block buffer image to use for Current mode/read
consistent mode when we have several images of same block in buffer cache? Was
there any change in this behaviour in Oracle 9.2 compared to 8.1.7.

Thanks
 Sai.
and we said...
1)  SCNs are just numbers.  Think of the base and the wrap as a way to store an
even bigger number --  The wrap is incremented whenever an increment to the base
would result in an overflow (when the SCN would roll over to zero again).

It will not always be zero, just depends on how many wraps you've done (roll
overs if you will)

ps$tkyte@ORA817DEV> select start_scnw, start_scnb from v$transaction;

START_SCNW START_SCNB
---------- ----------
      1683 4117418604

2) see
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/c23cnsis.htm#17882
we use undo data to provide consistent reads

3) we use the SCN and find the block that would be "most correct" -- the newest
block with the SCN greater than or equal to the SCN that was in place when your
transaction began.  The rollback segments give us this information.  It is
pretty much the same in 9iR2 as it was in 8i, 8.0, 7.x, 6.x...  tweaks and
optimizations but fundementally the same.
Review & Followup
Rating: 4
SCN base and wrap. April 29, 2003
Reviewer: Sai from Palo Alto, CA
Hi Tom,

I am wondering why we have only one column checkpoint_change# in v$database &
v$datafile, and two columns start_scnb & start_scnw in v$transaction(also in
block header).

Does it make sense to assume that last checkpoint SCN checkpoint_change# from
v$database is close to start_scnb plus some factor multiplied by
start_scnw(presuming that checkpoint occured right after current session DML
statement started). If I am wrong here, please explain how current start_scnb &
start_scnw will be used to arrive at complete SCN number.

Followup:
use dbms_flashback to get the only SCN you ever need to know.

Rating: 4
Segment header. May 21, 2003
Reviewer: Saibabu Devabhaktuni from Palo Alto, CA
Hi Tom,
 When we issue insert statement, Oracle will scan through freelist (intermediate
BMB blocks and leaves in automatic segment space management). How can we find
how many blocks are being used for segment extent maps(intermediate BMB and
leave blocks in automatic segment space management)?

Thanks

Followup:
you can derive this value.

Using a procedure like this, we can get details:

ops$tkyte@ORA920> create or replace procedure show_space
  2  ( p_segname in varchar2,
  3    p_owner   in varchar2 default user,
  4    p_type    in varchar2 default 'TABLE',
  5    p_partition in varchar2 default NULL )
  6  authid current_user
  7  as
  8      l_free_blks                 number;
  9
 10      l_total_blocks              number;
 11      l_total_bytes               number;
 12      l_unused_blocks             number;
 13      l_unused_bytes              number;
 14      l_LastUsedExtFileId         number;
 15      l_LastUsedExtBlockId        number;
 16      l_LAST_USED_BLOCK           number;
 17      l_segment_space_mgmt        varchar2(255);
 18      l_unformatted_blocks number;
 19      l_unformatted_bytes number;
 20      l_fs1_blocks number; l_fs1_bytes number;
 21      l_fs2_blocks number; l_fs2_bytes number;
 22      l_fs3_blocks number; l_fs3_bytes number;
 23      l_fs4_blocks number; l_fs4_bytes number;
 24      l_full_blocks number; l_full_bytes number;
 25
 26      procedure p( p_label in varchar2, p_num in number )
 27      is
 28      begin
 29          dbms_output.put_line( rpad(p_label,40,'.') ||
 30                                to_char(p_num,'999,999,999,999') );
 31      end;
 32  begin
 33    select ts.segment_space_management
 34      into l_segment_space_mgmt
 35      from dba_segments seg,  dba_tablespaces  ts
 36     where seg.segment_name    = p_segname
 37       and   seg.owner           = p_owner
 38       and   seg.tablespace_name = ts.tablespace_name;
 39
 40    if l_segment_space_mgmt = 'AUTO'
 41    then
 42       dbms_space.space_usage (
 43         p_owner,
 44         p_segname,
 45         p_type,
 46         l_unformatted_blocks,
 47         l_unformatted_bytes,
 48         l_fs1_blocks, l_fs1_bytes,
 49         l_fs2_blocks, l_fs2_bytes,
 50         l_fs3_blocks, l_fs3_bytes,
 51         l_fs4_blocks, l_fs4_bytes,
 52         l_full_blocks, l_full_bytes
 53       );
 54       --
 55       p( 'Unformatted Blocks ', l_unformatted_blocks );
 56       p( 'FS1 Blocks (0-25)  ', l_fs1_blocks );
 57       p( 'FS2 Blocks (25-50) ', l_fs2_blocks );
 58       p( 'FS3 Blocks (50-75) ', l_fs3_blocks );
 59       p( 'FS4 Blocks (75-100)', l_fs4_blocks );
 60       p( 'Full Blocks        ', l_full_blocks );
 61    else
 62       dbms_space.free_blocks(
 63         segment_owner     => p_owner,
 64         segment_name      => p_segname,
 65         segment_type      => p_type,
 66         freelist_group_id => 0,
 67         free_blks         => l_free_blks
 68       );
 69       --
 70       p( 'Free Blocks', l_free_blks );
 71    end if;
 72      dbms_space.unused_space
 73      ( segment_owner     => p_owner,
 74        segment_name      => p_segname,
 75        segment_type      => p_type,
 76            partition_name    => p_partition,
 77        total_blocks      => l_total_blocks,
 78        total_bytes       => l_total_bytes,
 79        unused_blocks     => l_unused_blocks,
 80        unused_bytes      => l_unused_bytes,
 81        LAST_USED_EXTENT_FILE_ID => l_LastUsedExtFileId,
 82        LAST_USED_EXTENT_BLOCK_ID => l_LastUsedExtBlockId,
 83        LAST_USED_BLOCK => l_LAST_USED_BLOCK );
 84
 85      p( 'Total Blocks', l_total_blocks );
 86      p( 'Total Bytes', l_total_bytes );
 87      p( 'Total MBytes', trunc(l_total_bytes/1024/1024) );
 88      p( 'Unused Blocks', l_unused_blocks );
 89      p( 'Unused Bytes', l_unused_bytes );
 90      p( 'Last Used Ext FileId', l_LastUsedExtFileId );
 91      p( 'Last Used Ext BlockId', l_LastUsedExtBlockId );
 92      p( 'Last Used Block', l_LAST_USED_BLOCK );
 93  end;
 94  /

Procedure created.

ops$tkyte@ORA920> set define on
ops$tkyte@ORA920> exec show_space( 'T' )
Unformatted Blocks .....................               0
FS1 Blocks (0-25)  .....................               0
FS2 Blocks (25-50) .....................               0
FS3 Blocks (50-75) .....................               0
FS4 Blocks (75-100).....................               0
Full Blocks        .....................             411
Total Blocks............................             512
Total Bytes.............................       4,194,304
Total MBytes............................               4
Unused Blocks...........................              85
Unused Bytes............................         696,320
Last Used Ext FileId....................               8
Last Used Ext BlockId...................             392
Last Used Block.........................              43

PL/SQL procedure successfully completed.

that shows there are 411 "data" blocks, blocks with data on them.  There are
512 total blocks in the segment -- 85 of which are totally unused (above the
real HWM, not touched).

Well, 512 - 411 - 85 = 16, there are 16 blocks "unaccounted for".  they are used
by Oracle to manage this segment.

May 21, 2003 Nicklas -- Thanks for the question regarding "Oracle Fail Safe", version 8.1.6
You Asked
Hello Tom !
Can you please get me tips
where i can find articels about
Oracle Fail Safe, im using version 3.0.4

Thanks

Nicklas Sundberg
and we said...
Have you seen
http://technet.oracle.com/tech/nt/failsafe/
...
Review & Followup
Rating: 1
the linked page does not exist May 21, 2003
Reviewer: N Taylor

Followup:
and when you clicked on it and it said "sorry page doesn't exist, please try the
search" and typed FAILSAFE in the search -- what happened?

when I did that, I found information on failsafe, how about you?

May 21, 2003 -- Thanks for the question regarding "very big sga side effects", version 8.1.7.0.1
You Asked
Hi Tom

What negative result can we get if we set the SGA very big, form example we have
13GB RAM in our production box, would we get negative results if we set our SGA
to 4GB?

data buffer to 4GB and shared pool to 1GB for example

cheers
and we said...
If you needed 10gig of ram for your dedicated servers, that would be bad.

Just having 13gig of ram says nothing to me.  13gig of ram, 1 connected user,
nothing else on the box -- go for it.  13gig of ram, 5,000 users using MTS,
running the app server on the same box -- my answer would be different.

a 1gig shared pool would be ludicrous.  75-80meg would be reasonable.  A large
shared pool can seriously impact performance in a negative fashion.  Don't go
crazy on that one.
Review & Followup
Rating: 4
hi April 19, 2002
Reviewer: A reader from spain
Hi

We have maximum 200 users connecting so maximum PGA/user is around 2MB since we
have 1MB sort_area_size configured and 128K hash_area_size

Why there would be side effects for shared pool and not data block buffer?

The server is dedicated to database only, application server is in another
machine

Followup:
The shared pool is managed in a different fashion.  If you have my book -- I
discuss the various pieces of memory and how they are used in some detail.

1gig for a shared pool -- tooooo big.

Rating: 5
Is bigger better? September 11, 2002
Reviewer: Steve from Montreal
Let's assume for argument's sake we have pleanty of
availble RAM on our server and that increasing
one setting is not "stealing" from another. Let's just
assume that.

I was told by an Oracle consultant that a bigger
SGA (in terms of db buffers, not shared_pool) is not
better and may be worse. His reason was that in order
for oracle to determine if the page is in memory or
on disk, the server had to perform a linear scan of
some list. The larger the SGA, the larger the list, the
longer it takes to determine if the page is in memory
or not.

Any truth to this?

If not, can we assume that increasing the DB buffers (by
adding additional RAM to the machine not by stealing from
another area) can only help and not hurt matters?

Bottom Line: Is there a penality to huge db buffers?

Steve

Followup:
yup, there are diminishing marginal returns to EVERYTHING (eg: if setting the
arraysize to 100 works good, 1000 should be better and 10000000 even better
right? - no)

Is there an INHERIT penalty -- no,  can it hurt, yes, can it help, yes, can it
maybe do nothing, yes.  The answer is 100% "it depends"

Rating: 3
how much size SGA in Windows NT February 07, 2003
Reviewer: R.V.Reddy from Bangalore, India
Hi Tom

I would like to in Windows NT and Windows 2000 O/S,How much SGA size I can give.
If I give more size say 2GB is there any problem. Thanks four earlier answers.

Reddy

Followup:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/win.817/a73008/ch9.htm#1007966

      Rating:
      5
  May 20, 2003
Reviewer:
      A reader
Negative impact as the shared pool oversized.

Tom,
I like yours approval of you point via real test examples. Do you have some
tests can show the negative impact as the shared pool getting “too large”

Thanks, I love your site

Mike

          Followup:

you need a big shared pool
you need people using non-shareable sql (no binds)
you need them hitting the database rather heavy

you will then observe the database falling over as the pool gets filled up and
we try to manage the billions of 4k chunks in there.

so a simple test?  no, need lots of "big stuff".  I've seen it over and over in
production though

      Rating:
      5
  May 21, 2003
Reviewer:
      A reader
"
you need a big shared pool
you need people using non-shareable sql (no binds)
you need them hitting the database rather heavy

you will then observe the database falling over as the pool gets filled up and
we try to manage the billions of 4k chunks in there.

so a simple test?  no, need lots of "big stuff".  I've seen it over and over in
production though
"

Could you point what are events should be looked into as the negative impact big
time?
Thanks tom.

          Followup:

huh?

      Rating:
      5
  May 21, 2003
Reviewer:
      A reader
I mean the wait events.

          Followup:

latch free

May 21, 2003 CJ
      -- Thanks for the question regarding "Help changing PL/SQL to SQL",
      version 8.1.7

      You Asked
Can you help with changing this PL/SQL to SQL? I inherited this system that
loads a table from another system and would like to fix the sql to be more
efficient. First thing I'll do is remove the commit after 250, but...

DECLARE
   TYPE charArray IS TABLE OF varchar2(70)
      INDEX BY BINARY_INTEGER;
   sub_category_array charArray;
   CURSOR csfsub_cursor IS
      SELECT *
      FROM csfsubtab
      WHERE sub_csf_cat IN ('1','2','3','4','5','6','7','8')
      ORDER BY sub_dept, sub_type, sub_csf_cat;
BEGIN
   FOR csfsub_rec IN csfsub_cursor LOOP
      sub_category_array(1) := csfsub_rec.sub_category_1;
      sub_category_array(2) := csfsub_rec.sub_category_2;
      sub_category_array(3) := csfsub_rec.sub_category_3;
      sub_category_array(4) := csfsub_rec.sub_category_4;
      sub_category_array(5) := csfsub_rec.sub_category_5;
      sub_category_array(6) := csfsub_rec.sub_category_6;
      sub_category_array(7) := csfsub_rec.sub_category_7;
      sub_category_array(8) := csfsub_rec.sub_category_8;
      FOR sub_category_ctr IN 1..8 LOOP
         INSERT INTO specific_category_desc
            VALUES (CONCAT(csfsub_rec.sub_dept,csfsub_rec.sub_type),
                    csfsub_rec.sub_csf_cat,
                    sub_category_ctr,
                    RTRIM(sub_category_array(sub_category_ctr)));
      END LOOP;
      IF MOD(csfsub_cursor%ROWCOUNT,250) = 0 THEN
         COMMIT;
      END IF;
   END LOOP;
   COMMIT;
END;
/

The tables look like this:
create table csfsubtab
(
 SUB_TYPE                                 VARCHAR2(3),
 SUB_DEPT                                 VARCHAR2(3),
 SUB_CSF_CAT                              VARCHAR2(1),
 SUB_CATEGORY_1                           VARCHAR2(70),
 SUB_CATEGORY_2                           VARCHAR2(70),
 SUB_CATEGORY_3                           VARCHAR2(70),
 SUB_CATEGORY_4                           VARCHAR2(70),
 SUB_CATEGORY_5                           VARCHAR2(70),
 SUB_CATEGORY_6                           VARCHAR2(70),
 SUB_CATEGORY_7                           VARCHAR2(70),
 SUB_CATEGORY_8                           VARCHAR2(70),
 SUB_CATEGORY_9                           VARCHAR2(70),
 SUB_CATEGORY_10                          VARCHAR2(70),
 SUB_CATEGORY_11                          VARCHAR2(70),
 SUB_CATEGORY_12                          VARCHAR2(70),
 SUB_CATEGORY_13                          VARCHAR2(70),
 SUB_CATEGORY_14                          VARCHAR2(70),
 SUB_CATEGORY_15                          VARCHAR2(70)
)
/

create table specific_category_desc
(
 FORM_ID                         CHAR(6) NOT NULL,
 BROAD_CAT_NO                    CHAR(1) NOT NULL,
 SPEC_CAT_NO                     CHAR(1) NOT NULL,
 SPEC_CAT_DESC                   VARCHAR2(70)
)
/

Thank you very much.

      and we said...
boy, will you like multi-table inserts in 9i.  but this will do for now:
insert into specific_category_desc
select /*+ ALL_ROWS */ sub_dept || sub_type,
       sub_csf_cat,
       r,
       rtrim(
       decode( r, 1, sub_category_1,
                  2, sub_category_2,
                  3, sub_category_3,
                  4, sub_category_4,
                  5, sub_category_5,
                  6, sub_category_6,
                  7, sub_category_7,
                  8, sub_category_8 ) )
   from csfsubtab, (select 1 r from dual union all
                    select 2 r from dual union all
                    select 3 r from dual union all
                    select 4 r from dual union all
                    select 5 r from dual union all
                    select 6 r from dual union all
                    select 7 r from dual union all
                    select 8 r from dual  )
 where sub_csf_cat between '1' and '8'
/

Review &
      Followup

      Rating:
      5
tom  May 20, 2003
Reviewer:  Bill
       from SoCal
never ceases to amaze me - brilliant

      Rating: 5
Awesome.
      Thanks!  May 21, 2003
Reviewer:  CJ
       from Albany, NY
Thanks! Since we will hopefully be moving to 9i within the next few months,
could you show the 9i solution? Thanks again.

          Followup:

it would be something like:

insert ALL
into specific_category_desc ( c1, c2, 1, rtrim(sub_category_1) )
into specific_category_desc ( c1, c2, 2, rtrim(sub_category_2) )
into specific_category_desc ( c1, c2, 3, rtrim(sub_category_3) )
into specific_category_desc ( c1, c2, 4, rtrim(sub_category_4) )
into specific_category_desc ( c1, c2, 5, rtrim(sub_category_5) )
into specific_category_desc ( c1, c2, 6, rtrim(sub_category_6) )
into specific_category_desc ( c1, c2, 7, rtrim(sub_category_7) )
into specific_category_desc ( c1, c2, 8, rtrim(sub_category_8) )
select sub_dept || sub_type c1,
       sub_csf_cat c2,
       sub_category_1,
       sub_category_2,
       sub_category_3,
       sub_category_4,
       sub_category_5,
       sub_category_6,
       sub_category_7,
       sub_category_8
  from csfsubtab
 where sub_csf_cat between '1' and '8'
/

May
      21, 2003 Sangita -- Thanks for the
      question regarding "using decode in report", version 9.0.1

      You Asked
Hi Tom
I have a query like
select  t2.category , count(*)
from v_aar_request t1, v_aar_resource t2
where t2.resource_type = t1.resource_type
and t2.owner = 'RMS'
and trunc(t1.update_date) >= to_date('01-feb-2003')
AND trunc(t1.update_date) <= to_date('28-feb-2003')
AND T1.wf_state = 'END'
group by t2.category

The output of this is
CATEGORY    COUNT(*)
Certificate    34
Mainframe    910
Remote Access    287
STARS            69
STRATUS            2
Termination    102
Trusted Partner    9
UNIX            314
UNIX Firewall    17
VMS/VAX            28

However I want the output for the last 3 months . The desired output is :

Completed Requests by Resource per Month
Resource Type    Feb-03    Mar-03    Apr-03
Certificate     34    69    112
Mainframe     910    941    843
Remote Access     287    342    345
STARS             69    72    87
STRATUS     2    10    4
Termination     102    255    114
Trusted Partner 9    29    20
UNIX        314    392    477
UNIX Firewall     17    29    23
VMS/VAX     28    76    68

I am aware that this can be done using decode . But I am having trouble with the
aggregate function in the decode .

sql > select  category , decode(to_char(update_date,'MM'),'02',cnt,0 )
 from (select  t2.category , count(*) cnt
       from v_aar_request t1, v_aar_resource t2
      where t2.resource_type = t1.resource_type
      and t2.owner = 'RMS'
      and trunc(t1.update_date) >= to_date('01-feb-2003')
      AND trunc(t1.update_date) <= to_date('30-apr-2003')
     AND InStr(T1.wf_state||',', 'END,') > 0
     group by t2.category
  );
select  category , decode(to_char(update_date,'MM'),'02',cnt,0 )
                                  *
ERROR at line 1:
ORA-00904: invalid column name

Please show me how to achieve the desired result .
Thanks
Sangita

      and we said...
just sum up 1's and NULL's

also, use a date format...

and use between -- not trunc() on the update_date....

select category,
       SUM(
       decode( months_between(
                 trunc(update_date), to_date(:bv,'dd-mon-yyyy') ),
               0, 1 ) ) m01,
       SUM(
       decode( months_between(
                 trunc(update_date), to_date(:bv,'dd-mon-yyyy') ),
               1, 1 ) ) m02,
       SUM(
       decode( months_between(
                 trunc(update_date), to_date(:bv,'dd-mon-yyyy') ),
               2, 1 ) ) m03
from v_aar_request t1, v_aar_resource t2
where t2.resource_type = t1.resource_type
and t2.owner = 'RMS'

  and t1.update_date BETWEEN to_date( :bv, 'dd-mon-yyyy' )
         and (add_months( to_date(:bv,'dd-mon-yyyy'), 3 ) - 1/24/60/60)

AND T1.wf_state = 'END'
group by t2.category

Review &
      Followup

      Rating: 4
Report
        May 02, 2003
Reviewer:  lakshmi  from
      Bangalore India
Excellent

      Rating: 2
please
      clarify  May 05, 2003
Reviewer:  sangita
      saxena  from Boston, MA USA
Hi Tom
I tried your suggestion and am not getting the desired output . please let me
know what I am doing wrong . I ran the query you gave and substituted
'01-feb-2003' for :bv . The query is
select category,
       SUM(
       decode( months_between(
                 trunc(update_date), to_date('01-feb-2003','dd-mon-yyyy') ),
               0, 1 ) ) m01,
       SUM(
       decode( months_between(
                 trunc(update_date), to_date('01-feb-2003','dd-mon-yyyy') ),
               1, 1 ) ) m02,
       SUM(
       decode( months_between(
                 trunc(update_date), to_date('01-feb-2003','dd-mon-yyyy') ),
               2, 1 ) ) m03
from v_aar_request t1, v_aar_resource t2
where t2.resource_type = t1.resource_type
and t2.owner = 'RMS'
  and t1.update_date BETWEEN to_date( '01-feb-2003', 'dd-mon-yyyy' )
         and (add_months( to_date('01-feb-2003','dd-mon-yyyy'), 3 ) -
1/24/60/60)
AND T1.wf_state = 'END'
group by t2.category
/

And the output I got was :
CATEGORY       M01    M02   M03
------------------------ --------Certificate                    3
Mainframe                     36
Remote Access                 24
STARS                          7
STRATUS                        1
Termination                    4
Trusted Partner
UNIX                           8
UNIX firewall
VMS/VAX                        18

I have tried various dates but get the output for only 1 column . Also for M01
why are you looking at months_between = 0 in the decode ? should we looking for
a difference of 1 month ?

          Followup:

missed a 'mm' in the trunc, trunc(update_date,'mm')

      Rating: 5
Excellent
      solution  May 05, 2003
Reviewer:  Sangita
      Saxena  from boston, ma USa
Thanks Tom for this great solution

      Rating:
      4
Trunc...  May 05, 2003
Reviewer:
      Kashif  from Houston, TX
Hi Tom,

You mentioned that trunc should be substituted with the between clause. Is there
a way the same logic be applied to the select trunc clause? i.e.

"select category,
       SUM(
       decode( months_between(
--->> here!                trunc(update_date), to_date(:bv,'dd-mon-yyyy') ),
               0, 1 ) ) m01,
       SUM(
       decode( months_between(
--->> here!                 trunc(update_date), to_date(:bv,'dd-mon-yyyy') ),
               1, 1 ) ) m02,
       SUM(
       decode( months_between(
--->> here!                 trunc(update_date), to_date(:bv,'dd-mon-yyyy') ),
               2, 1 ) ) m03"

this part... Thanks.

Kashif

          Followup:

no, it needs no between -- it needed the missing 'mm' to trunc the update_date
to the month so the months between would return 0, 1, and 2

      Rating: 5
East to West
      Tom is the Best !!!  May 06, 2003
Reviewer:
      praveen  from india
Superb Solution!!
By the way Tom, when is your book on Performance likely to hit the market?
-Praveen from Inida.

          Followup:

end of summer.

      Rating: 5
use of
      decode  May 20, 2003
Reviewer:  mo

Tom:

IF I have
<SELECT name="i_suffix">
 <OPTION VALUE=""  >Pick One --->
 <OPTION VALUE="Jr."  >Jr.'
 <OPTION VALUE="Sr."  >Sr.'
 <OPTION VALUE="II"   >II'
 <OPTION VALUE="III"  >III'
</SELECT>

I have a value from one record in database for suffix. Is it possible to use
decode is selecting the value for the above list which is hard-coded in the html
form? or the only way to do it is to declare 5 variables and build an IF
statement to check the table value against the list and set the variable to
'SELECTED';

          Followup:

no idea what you are trying to say.

      Rating:
      5
decode  May 21, 2003
Reviewer:  mo

Tom:

What I am saying is that i have the above select created using htp in an html
form.  Now I want to build an edit form.  so i retrieve the value from the table
for queried record and I want to set one of those values to "SELECTED" based on
the value retrieved from the table.  The way I am doing it is cumersome because
I am creating 5 variables (one for each value) and setting the value to
"SELECTED" if it matches. Then I do this:

htp.p('<SELECT name="i_suffix">');
htp.p('<OPTION VALUE=""  '||v_prefix1||' >Pick One --->');
htp.p('<OPTION VALUE="Jr." '||v_prefix2||' >Jr.' );
htp.p('<OPTION VALUE="Sr." '||v_prefix3||'  >Sr.');
htp.p('<OPTION VALUE="II"  '||v_prefix4||' >II');
htp.p(' <OPTION VALUE="III" '||v_prefix5||'  >III' );
htp.p('</SELECT>');

Is there an easier way to do this via DECODE?

          Followup:

only if you can write it in a query since decode can only be used in sql.

      Rating: 3
Use FormsSelect
      and SQL to build the Select list  May 21, 2003

Reviewer:  Paul  from Ottawa, ON Canada
From the HTP reference
If you use this
htp.formSelectOpen( 'i_suffix');
      for c in ( select <your sql here to select suffix> )
       loop htp.formSelectOption(c.suffix);
      end loop;
htp.formSelectClose;
then you don't need 5 variables, or even to hardcode the select, it becomes
dynamic which is what DB applications are all about isn't it?

The resultant HTML looks something like this.
<SELECT NAME="i_suffix">
<OPTION>JR.
<OPTION>SR.
<OPTION>II
<OPTION>III
</SELECT>

May 21, 2003 Christian -- Thanks for the question regarding
      "order of firing triggers in 8.1.6", version 8.1.6

      You Asked
Hi Tom
thanks for responding to my question "Oracle 8.1.6 and
triggers".
You mentioned that the firing order of delete triggers in 8.1 is
totally different from 7.3.4, especially when cascading deletes
occur.
Yes, I noticed that behavior sometimes.
My question is: where can get information about the order in
which triggers fire in Oracle 8.1?

Thanks again,

Chris

      and we said...
It is, and has always been, documented as being "undefined and
random".

Triggers that depend on the order of firing should be avoided at
all costs.  For example, if you have 2 BEFORE, FOR EACH ROW
triggers and one trigger DEPENDS on the other trigger firing
first -- to the point where the second trigger fails or does the
wrong thing if it gets fired first -- you need to make that
become 1 trigger.

For example, you would take the logic of trigger1 and make it
procedure1.  You would make procedure2 from the logic of
trigger2.  You would create ONE trigger that simply called:

begin
  procedure1;
  procedure2;
end;

That is the only way to ensure the "correct" order of execution.

With regards to on delete cascade.  The way it would have fired
in 7.3 would be:

before delete on p
before delete on p for each row
before delete on c
before delete on c for each row
after delete on c for each row
after delete on c
after delete on p for each row
after delete on p

(where P is the parent and C is the child).  In 8i, it goes like
this:

before delete on c
before delete on p
before delete on p for each row
before delete on c for each row
after delete on c for each row
after delete on p for each row
after delete on c
after delete on p

Here it fires the before delete triggers on the child tables
BEFORE it fires the before delete on the parent -- it found the
tables that would be deleted from via the on delete cascade and
fired their before delete triggers before the parent.

Review &
      Followup

      Rating: 4
What about
      8.0.6?  May 21, 2003
Reviewer:  Leonardo
      Zacché  from Rio de Janeiro/Brazil
Tom, what about Oracle 8.0.6? Could you tell us which versions this behavior has
changed? Because sometimes, we work on a version and design some solutions based
on such behavior. Some time after the customer decide do upgrade Oracle and the
solutions stop to work.

Thanks,
LZ

          Followup:

what about 806?

May
      21, 2003 sunil -- Thanks for the
      question regarding "using functions in insert statements -- ORA-06571:
      Function does not guarantee not to update ", version 8.0.5

      You Asked
hello tom,
   we are trying to insert into a table, the value of a
"agent-code",  we have written a function, which takes in
order-no and sends back agent for that order.  Now we have
written the function "get-agent" to do the same.
   The problem is when i write the following
"insert into <table>
 select <col1>,get_agent(<col1>)
 from <a_view>"

the error i get is
"ORA-06571: Function GET_AGENT does not guarantee not to update
database". the code is....

FUNCTION     Get_Agent
( so  mandb200.ma_sodrec.ma_sohno%TYPE, LN NUMBER) RETURN CHAR
    AS
CURSOR c1 ( s CHAR , l NUMBER ) IS SELECT
            MAX(ma_comodat),ma_samcod sl_agent_cd
           FROM COMO
           WHERE ma_comosono LIKE s AND ma_comolin = l GROUP BY
            ma_samcod;

sl_data c1%ROWTYPE;

sl_code CHAR(6);

BEGIN

    OPEN c1( so ,LN) ;
    FETCH c1 INTO sl_data;
    IF c1%NOTFOUND THEN
      RETURN 'no ' ;
    ELSE
       CLOSE c1;
       RETURN sl_data.sl_agent_cd;
            END IF;

END;
   what could be the solution?
yes get_agent is a function inside a package
datamart_populate_pkg.  and the mandb200 is a user, ma_sodrec is
a table. ma_sohno is a field of that table.
sunil.

      and we said...
You need to assert the purity level of GET_AGENT in the package
specification then.  This is unique to packaged functions and
procedures (and won't be needed in Oracle8i release 8.1 and up).
 The package part is the key to this problem -- this would not
happen with a standalone function -- keep it in a package
though, there are dozens of advantages of packages over
standalone functions.

It would look like this (first I will show it failing and then
how to fix it)

scott@ORA806.WORLD> create or replace package
                                        datamart_populate_pkg
  2  as
  3    FUNCTION Get_Agent
  4    ( so     SCOTT.ma_sodrec.ma_sohno%TYPE, LN NUMBER)
  5    RETURN CHAR;
  6  end;
  7  /

Package created.

scott@ORA806.WORLD> create or replace package body
                                datamart_populate_pkg
  2  as
  3
  4
  5  FUNCTION Get_Agent
  6  ( so  SCOTT.ma_sodrec.ma_sohno%TYPE, LN NUMBER) RETURN CHAR
  7  AS
  8    CURSOR c1 ( s CHAR , l NUMBER )
  9        IS SELECT MAX(ma_comodat),
 10                  ma_samcod sl_agent_cd
 11             FROM COMO
 12            WHERE ma_comosono LIKE s
 13              AND ma_comolin = l
 14            GROUP BY ma_samcod;
 15
 16    sl_data c1%ROWTYPE;
 17    sl_code CHAR(6);
 18  BEGIN
 19      OPEN c1( so ,LN) ;
 20      FETCH c1 INTO sl_data;
 21      IF c1%NOTFOUND THEN
 22        RETURN 'no ' ;
 23      ELSE
 24         CLOSE c1;
 25         RETURN sl_data.sl_agent_cd;
 26      END IF;
 27  END;
 28
 29  end;
 30  /

Package body created.

scott@ORA806.WORLD> create table t ( x char(6) );
Table created.

scott@ORA806.WORLD> insert into t
  2  select datamart_populate_pkg.get_agent( null, null ) from
dual;

select datamart_populate_pkg.get_agent( null, null ) from dual
       *
ERROR at line 2:
ORA-06571: Function GET_AGENT does not guarantee not to update
database

scott@ORA806.WORLD> create or replace package
                                   datamart_populate_pkg
  2  as
  3    FUNCTION Get_Agent
  4    ( so     SCOTT.ma_sodrec.ma_sohno%TYPE, LN NUMBER)
  5    RETURN CHAR;
  6
  7    pragma restrict_references( get_agent, wnds, wnps, rnps
);
  8
  9    pragma restrict_references( datamart_populate_pkg, wnds,
wnps, rnps );
 10  end;
 11  /

Package created.

Those pragmas are all you need -- they tell us the function
and package do NOT write to the database -- if they did, we
could not call them

scott@ORA806.WORLD> insert into t
  2  select datamart_populate_pkg.get_agent( null, null ) from
dual;

1 row created.

scott@ORA806.WORLD>

and now it works.

Review &
      Followup

      Rating: 3
still i am
      getting  December 28, 2001
Reviewer:  lakshmi
      narasimhan r  from Dubai, UAE
Tom,

I have the same problem.  my version is 7.2.2.3.0

but even after adding pragma in the package header i am getting the problem.

i am using dbms_sql for dynamically selecting some count of table.  could be
that is the cause of the problem.  could u reply to me. i am in production place
and i held up with the work.

          Followup:

you cannot do what you are trying to do.

DBMS_SQL most certainly does NOT guarantee to update the database -- it will do
an update as easily as select.

You cannot call dbms_sql from PLSQL that will be called from SQL in 7.2 (or 7.3
or 8.0, you can in 8i with an autonomous transaction but you have EXECUTE
IMMEDIATE which is consider "pure" when used with the INTO clause)

      Rating: 4
how does this
      function work on your machine?  May 21, 2003

Reviewer:  DH  from USA
Hi, Tom.

I'm curious about how you can re-create the users' packages and functions
without having their schema.  I don't recognize COMO for instance.  This doesn't
look generic.

Are you meta-physical?

          Followup:

I create objects in order to run examples -- it does really help me when the
question includes CREATES and INSERTS to populate examples -- speeds things up.

May 21, 2003 John
      -- Thanks for the question regarding "Self-Join", version 8.107

      You Asked
Hi Tom,
Which is more efficient,
query with self-join or the same query with subquery instead of
a self join ?

Thanks
John.

      and we said...
if one were inheritly faster -- there would be only one.

and you forgot choice #3 -- analytics, they may be faster as well.

One would need a working testing case.  I find analytics to rock and roll in
these situations.  I find select ..., (select ..) from t to be a second choice
and a self join useful when there are many many columns (making the second
choice way too complex to type in )..

if you have a for example case study, we can benchmark it and explain what
happens and why.
Review & Followup

      Rating: 3
Another self
      join  May 14, 2003
Reviewer:  Max

Tom,

I need to find the BOMs which use the same items. I have a self-join query as
follows :

SELECT a.bom_nbr, a.item_no, b.item_no
FROM
(
SELECT bom_nbr, item_no
FROM bom_dtls
WHERE bom_type = 'A'
) a,
(
SELECT bom_nbr, item_no
FROM bom_dtls
WHERE bom_type = 'A'
) b
WHERE a.item_no = b.item_no
  AND a.bom_nbr <> b.bom_nbr
/

This gives me the correct results. Just wanted to know if there was any other
efficient way ?

Thanks !

      Rating: 3
How can I get
      your attention here ?????  May 21, 2003
Reviewer:
      A reader

          Followup:

i see most, but not all, of these followups.  I never promised to fill them all
in.

If  you need a cartesian product -- what you have is correct and "efficient".

May 21, 2003 nbk
      -- Thanks for the question regarding "Query", version 8.1.4

      You Asked
Hi Tom,

Happy christmas.Here i have question.Is it possible to have a sql query(single
query) to list the rank with student number and marks in the table.

Example : i have student table with sno,marks as columns and i have data like
this
SNO    Marks
20      39
22      49
39      99
10      38
11      20
12      29

i want the o/p as

SNO    Marks  Rank
39      99     1
22      49     2
20      39     3
10      38     4
12      29     5
11      20     6

with a single sql query.
Thanks,
Basa's

      and we said...
Don't know what version 814 is but...

select a.*, rownum
  from ( select sno, marks
           from t
          order by marks )

is one way..... rank() and dense_rank() yet another:

  1* select ename, sal, dense_rank() over ( order by sal desc ) from emp
scott@ORA920> /

ENAME             SAL DENSE_RANK()OVER(ORDERBYSALDESC)
---------- ---------- --------------------------------
KING             5000                                1
SCOTT            3000                                2
FORD             3000                                2
JONES            2975                                3
BLAKE            2850                                4
CLARK            2450                                5
ALLEN            1600                                6
TURNER           1500                                7
MILLER           1300                                8
WARD             1250                                9
MARTIN           1250                                9
ADAMS            1100                               10
JAMES             950                               11
SMITH             800                               12

14 rows selected.

Review &
      Followup

      Rating:
      3
Thanks  February 05, 2003
Reviewer:
      A reader
Thank you very much tom.

      Rating: 3
rownum for
      rank  May 21, 2003
Reviewer:  John A
      Buchanan  from Scotland, U.K.
I can't use analytic functions in version 8.0.6  so I tried rownum

select a.*, rownum
  from ( select sno, marks
           from t
          order by marks )

I get error at line 4 - Missing right parenthesis

It's the order by which is causing the problem, but I don't know why.

I know we should all be moving on to later versions, but  some of us have no
option.
Is there a way to rank these results pre version 8.1

          Followup:

order by in a subquery is supported with 815 and up.

analytics with 816 and up

in 8.0, you would have to assign the "rownum" youself -- in whatever client code
fetched this result set.

May 23, 2003 Parag -- Thanks for the question regarding
      "Frgamentation in System Data File.", version 8.1.7

      You Asked
Hi Tom :

After long time I am asking question to U.
Through my Application ( ASP ) I am Creating Run Time View to get one Complex
Report. To get that Report , I am creating 6 View & Depending on that 6 View , I
am creating One Main View. That Main View wil give me final data. After fetching
those data into ASP Resut-Set I am dropping those View.

Now in My Application there are 500 Users. ( There is only One Backend User).
Why I am creating Different view for each user , Beca before running report , I
am accepting some 5 - 8 Different Parameter from user. Depending on those
Parameter , I am showing then final report.

Now Due to Frequently Creation & Dropping of View , I am worried that My System
Data file will suffer from Too much of FRGAMENTATAION. How to avoid this ?

Waiting for ur + reply.

Regards
- Parag

      and we said...
Thats a really bad design.

Doing DDL is really truly expensive.  You are trashing the shared pool
(performance and scalability is compromised).  You are trashing the the system
tablespace over time.

It will not fragment it -- it'll just make it larger.  System pretty much cannot
get fragmented cause you can only get fragmentation after you drop an object
that consumes storage and you are not.

Many people do what you are doing without creating a whole ton of views per
user.

You should not worry about fragmentation but you should be worried.  What you
have set up is a total performance killer.  DDL is serialized, it is expensive
and your approach obviates the shared sql area.  This system will not scale.
Review &
      Followup

      Rating: 4
What About the
      Space In Data Dictionary  November 06, 2001

Reviewer:  Ganesh Raja  from Dubai, UAE
Tom what u say is okay as far as DDl is concerned but since the user is
constatntly adding and droping Views wont the Data Dictionary Get Updated ???
And wont this result in fragmentation.

Regards,
Ganesh R

          Followup:

The data dictionary will get updated -- just like a table. Blocks will go onto
the free list, come off of the free list -- just like any other table.

You are never releasing (dropping SEGMENTS) space back to the free space of the
tablespace -- hence the SYSTEM tablespace cannot get fragmented.  It'll just get
larger then it would be if you didn't create these views but will eventually hit
a steady state size.

It'll NOT be fragmented.  You have to drop a segment or deallocate an extent in
order to get tablespace fragmentation and you are not doing that here.

      Rating: 3
Use Fine
      Grained Access  November 06, 2001
Reviewer:
      Naveed Amin Khan  from West Des Moines Iowa
It sounds to me that this application is a candidate for fine grained access
control. If your application requires views which change as the application
runs, you can build dynamic where clauses ( predicates ) to your query using
this method. The predicate clauses depend on procedural logic which you write.

      Rating: 3
Fragmentation
      in System Data File.  November 07, 2001
Reviewer:
      Parag  from INDIA
Tom :

I do agree with U . But try to undestand my Logic. I can't Put the Entire
Business Logic in my Applicatiopn Program (ASP). Initially I have put Entire
Busines Logic in my Friend-End App Only , But Performance was too bad , Because
we are not using any Reporting tool , u know which calculate Sub -Total ,
Grand-Total , e.t.c. So i can't reply on my ASP Code at all. The's a reason ,
Why I am creating Tons of views per user. Anyhow basically this system will be
useed only in MOnthEnds. HARDLY 10 Days in a Month.
Coming back to prob , What's solution uare sugeesting to me. Should I deallocate
the Extents. Anyhow it is not Internet Site , it's an Intranet Site.
Can u please give me Scriupt which i have to run periodically to achive good
performacen & not to disturbe SGA much.
Waiting for ur Script & Solution.

Regards
- Parag

          Followup:

In 15 years, I've never seen the absolute need to create 5 or 6 views per
report.

If your report tool can dynamically create a view, it should be able to
dynamically create a QUERY -- no need for a view.

Most tools can use ref cursors -- plsql can dynamically open them -- there are
ways to avoid using the view.

the only way to fix your problem is to fix the code -- change the way in which
it works.

      Rating: 1
Fragmentation
      in System Data File.  November 07, 2001
Reviewer:
      Parag  from India
Hi Tom :

Now system has gone Live. I am not creating Tons of Views for all Reports. There
is a only One Complex Report for which i am creating views during Runtime.
Comeback to Requirement , It's not possible ( MAY BE V.V.V.V.DIFF) to Fetch all
data within Query. If u want , I can also mail u the Output of report with it's
table structure.

U are my last hope for this Problem.

Can u give me some solution for this without changing the Architecture ?

Regards
- Parag

          Followup:

nope.  there is no hope for you.

Actually, your system tablespace is going to be JUST FINE.  On my development
instance, we complile PLSQL routine THOUSANDS OF TIMES.  Thats just like a
drop/create/drop/create/drop/create -- just like a view.  Your system tablespace
is OK, I've never been worried about that.  Its your method that is giving me
heartburn.

If you are happy with your method and your performance -- don't sweat the other
stuff.

      Rating: 4
what is the
      solution  May 21, 2003
Reviewer:  umesh
       from bangalore india
If changing the design is the solutions shud this be using Fine Grained access
control or some thing else, Tom can you  put a line of your idea here.. becoz i
am new to this DESIGN issues .. can learn something out of it
Thanks in advance

          Followup:

I don't understand the tie in between fgac and this question.

      Rating: 4
rephrase my
      question  May 22, 2003
Reviewer:  umesh
       from bangalore india
Tom
I will put his way
what do you think will be the solution for the problem above related to views (
by Parag) .. I also have an application which creates views on views almost
working in the same manner

          Followup:

don't create views -- there, done.

Lets say you had a process that for whatever reason did this:

create view temp_v1 as .....

create view temp_v2 as select ..  from temp_v1, .....

create view temp_v3 as select ... from temp_v2, .....

and then selected from temp_v3.

all you would do is code:

select ....
  from ( select .... from ( TEXT of temp v1 here ), ..... )

one query, using inline views (select from a SELECT).  No need for persistent
views whatsoever.

May
      23, 2003 Sarada -- Thanks for the
      question regarding "How to handle NULL values in JDBC while using datum",
      version 8.1.7

      You Asked
Hi:
I am using Oracle 8.1.7 and OCI driver for JDBC.
The stored object is returing NULL value for an out parameter of datatype
NUMBER.
I am using dataum to get the PL/SQL table back from the function.

Here is the error what I am getting when I run the test java program

java.lang.ArrayIndexOutOfBoundsException
    at
com.proquest.oracle.lha.TestPQDGetLhPqLinks.main(TestPQDGetLhPqLinks.java:41)

Here is the java code to call stored object:
The 8th out parameter return NULL values from the database.
    cstmt.registerIndexTableOutParameter(8,MaxRows, elemTypeNumber,
CMNdict.CMN_NUMBER);

I know one way I can make NVL in the sotred object and that would work.
But in certain cases I don't want to do that because of business rules...
Please send me an example which is using datum to handle NULL values.

Appreciate your help!!!
Thanks
Das

package com.p1.oracle.la;

import java.io.*;
import java.util.*;
import java.sql.*;
import oracle.sql.*;
import oracle.jdbc.driver.*;
import com.p1.oracle.include.*;

public class PQDGetLhPqLinks
{
  private long    LhId = 0;
  private int        MaxRows = 1;
  private int        KeepCursorOpen = 0;
  private int        DebugFlag = 0;
  private Vector     LhPqLinks = new Vector();
  private String     MessageGroupId = "";
  private int     ActualRows = 0;
  private int     More = 0;
  private int     ReturnVal = 0;
  private Connection connection;

  /**
   * Constructor
   */

  public GetLhPqLinks(Connection con, long LhId, int MaxRows, int
KeepCursorOpen, int DebugFlag)
  {
    this.connection = con;
    this.LhId = LhId;
    this.MaxRows = MaxRows;
    this.KeepCursorOpen = KeepCursorOpen;
    this.DebugFlag = DebugFlag;
  }

  public String getMessageGroupId()
  {
    return MessageGroupId;
  }

  public int getReturnVal()
  {
    return ReturnVal;
  }

  public int getMore()
  {
    return More;
  }

  public int getActualRows()
  {
    return ActualRows;
  }

  public void work() throws SQLException
  {
    LhPqLinks.removeAllElements();
    OracleCallableStatement cstmt = null;
    cstmt = (OracleCallableStatement)connection.prepareCall("BEGIN ? :=
PQDKGLPL.PQDKGLPL(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); END;");

    int elemTypeNumber        = OracleTypes.NUMBER;
    int elemTypeVarchar    = OracleTypes.VARCHAR;
    int LhlIdMaxLen    = PQdict.AO_LHL_ID + 1;
    int LhlNameMaxLen = PQdict.AO_LHL_NAME + 1;
    int LhlDestMaxLen = PQdict.AO_LHL_URL_DEST + 1;
    int LhlIconMaxLen = PQdict.AO_LHL_URL_ICON + 1;
    int LhlLabelMaxLen = PQdict.AO_LHL_URL_LABEL + 1;
    int LhlTlsPovIdMaxLen = CMNdict.CMN_NUMBER;
    int LhlPqFlagMaxLen = PQdict.AO_LHLU_PQ_FLAG + 1;
    int LhlTpFlagMaxLen = PQdict.AO_LHLU_TPD_FLAG + 1;
    int LhlPreDefFlagMaxLen = PQdict.AO_LHLU_PRE_DEFINED_FLAG + 1;

    int LhlGlobalFlagMaxLen = PQdict.AO_LHLU_GLOBAL_FLAG + 1;
    int LhlResultIconFlagMaxLen = PQdict.AO_LHLU_DISP_RESULT_ICON_FLAG + 1;

/*
   pi_lh_id      IN  library_holdings.lh_id%type
  ,po_lhl_id     OUT co_lhl_id_tab_type
  ,po_lhl_name   OUT co_lhl_name_tab_type
  ,po_lhl_url_dest   OUT co_lhl_url_dest_tab_type
  ,po_lhl_url_icon   OUT co_lhl_url_icon_tab_type
  ,po_lhl_url_label  OUT co_lhl_url_label_tab_type
  ,po_lhl_title_list_prov_id OUT co_lhl_title_list_id_tab_type
  ,po_lhlu_pq_flag  OUT co_lhlu_pq_flag_tab_type
  ,po_lhlu_tpd_flag OUT co_lhlu_tpd_flag_tab_type
  ,po_lhlu_pre_defined_flag OUT co_lhlu_pre_def_flag_tab_type
  ,po_lhlu_global_flag      OUT co_lhlu_global_flag_tab_type
  ,po_lhlu_disp_result_icon_flag OUT co_lhlu_icon_flag_tab_type
  ,pi_max_rows    IN  NUMBER
  ,po_actual_rows OUT NUMBER
  ,po_more        OUT NUMBER
  ,pi_keep_cursor_open IN  NUMBER
  ,pi_debug_flag       IN  NUMBER
  ,po_message_grp_id   OUT VARCHAR2

*/

    cstmt.registerOutParameter(1,java.sql.Types.INTEGER);
    cstmt.setLong(2, LhId);

    cstmt.registerIndexTableOutParameter(3,MaxRows, elemTypeVarchar,
LhlIdMaxLen);
    cstmt.registerIndexTableOutParameter(4,MaxRows, elemTypeVarchar,
LhlNameMaxLen);
    cstmt.registerIndexTableOutParameter(5,MaxRows, elemTypeVarchar,
LhlDestMaxLen);
    cstmt.registerIndexTableOutParameter(6,MaxRows, elemTypeVarchar,
LhlIconMaxLen);
    cstmt.registerIndexTableOutParameter(7,MaxRows, elemTypeVarchar,
LhlLabelMaxLen);
    cstmt.registerIndexTableOutParameter(8,MaxRows, elemTypeNumber,
CMNdict.CMN_NUMBER);
    cstmt.registerIndexTableOutParameter(9,MaxRows, elemTypeVarchar,
LhlPqFlagMaxLen);
    cstmt.registerIndexTableOutParameter(10,MaxRows, elemTypeVarchar,
LhlTpFlagMaxLen);
    cstmt.registerIndexTableOutParameter(11,MaxRows, elemTypeVarchar,
LhlPreDefFlagMaxLen);
    cstmt.registerIndexTableOutParameter(12,MaxRows, elemTypeVarchar,
LhlGlobalFlagMaxLen);
    cstmt.registerIndexTableOutParameter(13,MaxRows, elemTypeVarchar,
LhlResultIconFlagMaxLen);
    cstmt.setInt(14, MaxRows);
    cstmt.registerOutParameter(15, java.sql.Types.INTEGER);
    cstmt.registerOutParameter(16, java.sql.Types.INTEGER);
    cstmt.setInt(17, KeepCursorOpen);
    cstmt.setInt(18, DebugFlag);
    cstmt.registerOutParameter(19, java.sql.Types.VARCHAR);

    cstmt.execute();

    this.ReturnVal = cstmt.getInt(1);
    if (this.ReturnVal == 0)
    {

      Datum[] LhlIdTab            = cstmt.getOraclePlsqlIndexTable(3);
      Datum[] LhlNameTab        = cstmt.getOraclePlsqlIndexTable(4);
      Datum[] LhlDestTab        = cstmt.getOraclePlsqlIndexTable(5);
      Datum[] LhlIconTab        = cstmt.getOraclePlsqlIndexTable(6);
      Datum[] LhlLabelTab        = cstmt.getOraclePlsqlIndexTable(7);

      Datum[] LhlTlsProvIdTab    = cstmt.getOraclePlsqlIndexTable(8);

      Datum[] LhlPqFlagTab        = cstmt.getOraclePlsqlIndexTable(9);

      Datum[] LhlTpFlagTab        = cstmt.getOraclePlsqlIndexTable(10);

      Datum[] LhlPreDefFlagTab    = cstmt.getOraclePlsqlIndexTable(11);

      Datum[] LhlGlobalFlagTab    = cstmt.getOraclePlsqlIndexTable(12);

      Datum[] LhlResultIconFlagTab    = cstmt.getOraclePlsqlIndexTable(13);

      for (int loopcntr = 0; loopcntr < LhlIdTab.length; loopcntr++)
      {
           GetLhPqLinksBean LhPqLinksBean = new GetLhPqLinksBean();

          LhPqLinksBean.setLhlId(LhlIdTab[loopcntr].stringValue());
          LhPqLinksBean.setLhlName(LhlNameTab[loopcntr].stringValue());

          LhPqLinksBean.setLhlDest(LhlDestTab[loopcntr].stringValue());

          LhPqLinksBean.setLhlIcon(LhlIconTab[loopcntr].stringValue());

          LhPqLinksBean.setLhlLabel(LhlLabelTab[loopcntr].stringValue());

          LhPqLinksBean.setLhlTlsProvId(LhlTlsProvIdTab[loopcntr].longValue());

          LhPqLinksBean.setLhluPqFlag(LhlPqFlagTab[loopcntr].stringValue());
          LhPqLinksBean.setLhluTpFlag(LhlTpFlagTab[loopcntr].stringValue());

LhPqLinksBean.setLhluPreDefFlag(LhlPreDefFlagTab[loopcntr].stringValue());

LhPqLinksBean.setLhluGlobalFlag(LhlGlobalFlagTab[loopcntr].stringValue());

LhPqLinksBean.setLhluResultIconFlag(LhlResultIconFlagTab[loopcntr].stringValue())
;
          LhPqLinks.add(LhPqLinksBean);

      }
    }

    this.ActualRows = cstmt.getInt(15);
    this.More = cstmt.getInt(16);
    this.MessageGroupId = cstmt.getString(19);

    if (cstmt != null )
        cstmt.close();

  }

  /** Getter of LH Links list */
  public Vector getLhPqLinks()
  {
    return LhPqLinks;
  }
}

Test Case:

Package:

create or replace package demo_pkg
as
    type array is table of number index by binary_integer;

    procedure p( p_data OUT array , p_num in number );
end;

create or replace package body demo_pkg
as

procedure p( p_data OUT array, p_num in number )
is
begin
    if ( p_num > 0 )
    then
        for i in 1 .. p_num
        loop
            p_data(i) := NULL; -- Suppose Null is coming from database
        end loop;
    end if;
end;

end;

import java.sql.*;
import oracle.jdbc.driver.*;
import oracle.sql.*;

class test
{

public static void main(String args[])throws Exception
{
DriverManager.registerDriver (new oracle.jdbc.driver.OracleDriver());

    Connection conn=DriverManager.getConnection
    ("jdbc:oracle:oci8:@ora817dev","scott", "tiger");
    OracleCallableStatement cstmt = (OracleCallableStatement)conn.prepareCall(
"begin demo_pkg.p(?,?); end;" );

    int maxl = 10;
    int elemSqlType = OracleTypes.NUMBER;
    int elemMaxLen = 0;

    cstmt.registerIndexTableOutParameter( 1, maxl, elemSqlType, elemMaxLen );

    cstmt.setInt( 2, 0 );
    cstmt.execute();
    Datum[] array = cstmt.getOraclePlsqlIndexTable(1);
    System.out.println( "array length = " + array.length );

    cstmt.setInt( 2, 5 );
    cstmt.execute();
    array = cstmt.getOraclePlsqlIndexTable(1);
    System.out.println( "array length = " + array.length );
    for ( int i=0; i < array.length; i++)
    {
       System.out.println( "array value["+i+"]= " + array[i]);
           System.out.println( "array value["+i+"]= " + array[i].longValue());
// I am using this kind statement to get the value.

    }
}

}

If I comment the 2nd println statement
// System.out.println( "array value["+i+"]= " + array[i].longValue());
the I get the following result

array length = 0
array length = 5
array value[0]= oracle.sql.NUMBER@2aa14a
array value[1]= oracle.sql.NUMBER@430b5c
array value[2]= oracle.sql.NUMBER@1ed927
array value[3]= oracle.sql.NUMBER@42a132
array value[4]= oracle.sql.NUMBER@651060

If I comment  the 1st println statement
// System.out.println( "array value["+i+"]= " + array[i]);
the I get the following result

array length = 0
array length = 5
java.lang.ArrayIndexOutOfBoundsException
    at oracle.sql.NUMBER._isZero(NUMBER.java:3033)
    at oracle.sql.NUMBER.toLong(NUMBER.java:336)
    at oracle.sql.NUMBER.longValue(NUMBER.java:1935)
    at com.proquest.oracle.lha.test.main(test.java:39)
Exception in thread "main"

Thanks
Das

      and we said...
Here is an example -- suppose we have a package that sets every other table
element to NULL:

create or replace package demo_pkg
as
    type array is table of number index by binary_integer;

    procedure p( p_data OUT array , p_num in number );
end;
/

create or replace package body demo_pkg
as

procedure p( p_data OUT array, p_num in number )
is
begin
    if ( p_num > 0 )
    then
        for i in 1 .. p_num
        loop
            if ( mod(i,2) = 0 )
            then
                p_data(i) := NULL;
            else
                p_data(i) := i;
            end if;
        end loop;
    end if;
end;

end;
/

We can use getLength on the datum -- if length = 0, the element is in fact NULL.
 Consider:

import java.sql.*;
import oracle.jdbc.driver.*;
import oracle.sql.*;

class test
{

public static void main(String args[])throws Exception
{
DriverManager.registerDriver (new oracle.jdbc.driver.OracleDriver());

    Connection conn=DriverManager.getConnection
    ("jdbc:oracle:oci8:@ora817dev","scott", "tiger");

    OracleCallableStatement cstmt = (OracleCallableStatement)conn.prepareCall(
"begin demo_pkg.p(?,?); end;" );

    int maxl = 10;
    int elemSqlType = OracleTypes.NUMBER;
    int elemMaxLen = 0;

    cstmt.registerIndexTableOutParameter( 1, maxl, elemSqlType, elemMaxLen );

    cstmt.setInt( 2, 5 );
    cstmt.execute();

    Datum[] array = cstmt.getOraclePlsqlIndexTable(1);
    System.out.println( "array length = " + array.length );

    for ( int i=0; i < array.length; i++)
    {
        System.out.println( "array value["+i+"]= " +
                ((array[i].getLength() > 0) ? array[i].longValue()+"":"NULL") );
    }
}

}

when we run that -- the output is:

$ java test
array length = 5
array value[0]= 1
array value[1]= NULL
array value[2]= 3
array value[3]= NULL
array value[4]= 5

Review &
      Followup

      Rating: 4
Check for NULL
      for Long datatype  May 22, 2003
Reviewer:
      A reader
tom,

Is it possible to check for NULL value for Long datatype column, like NVL().
?
Please let me know

Thanks,

          Followup:

in sql -- no.

you can use "is null"/"is not null" but thats about it.

      Rating: 4
nvl on Long
      datatype - any other equivalent ?  May 22, 2003

Reviewer:  A reader
Tom,

i tried, but it failed

SQL>>create table longcheck (column1 varchar2(2),column2  long);

Table created.

SQL>>insert into longcheck values ('01','The database must be shutdown with
SHUTDOWN NORMAL or SHUTDOWN IMMEDIATE. It
  2  must not be shutdown abnormally using SHUTDOWN ABORT');

1 row created.

SQL>>select * from longcheck;

CO COLUMN2
==
================================================================================
01 The database must be shutdown with SHUTDOWN NORMAL or SHUTDOWN IMMEDIATE. It
   must not be shutdown abnormally using SHUTDOWN ABORT

SQL>>insert into longcheck values ('02',NULL);

1 row created.

SQL>>select * from longcheck;

CO COLUMN2
==
================================================================================
01 The database must be shutdown with SHUTDOWN NORMAL or SHUTDOWN IMMEDIATE. It
   must not be shutdown abnormally using SHUTDOWN ABORT

02

SQL>>select nvl(column2,'NA') from longcheck;
select nvl(column2,'NA') from longcheck
                   *
ERROR at line 1:
ORA-00932: inconsistent datatypes: expected LONG got CHAR

any help on this?

          Followup:

yes, use CLOB types -- not longs.

May 23, 2003 Kenneth -- Thanks for the question regarding
      "ORA_KGRL7_DEPENDICIES", version 8.0.5

      You Asked
Dear Tom,
I have two queries
1. Oracle 8.0.3 documention  says to enable the checkpoint
process insert CHECKPOINT_PROCESS = TRUE  in the init.ora . But
on insertion and restarting the database gives me an error . how
can the ckpt be enable or disable then

2. This is the problem pertaining to a link which was working
fine for many months . But recently gave me the following error
My script is given below , the  link name yadr  created in my
development database to point to production database.

DECLARE
    CURSOR C1 IS
        SELECT * FROM IP21TAG@yadr where tag_name like '3%';
BEGIN
    FOR REC IN C1 LOOP
begin
        INSERT INTO IP21TAG (
            TAG_NAME, TAG_TYPE_CODE, TAG_RECON)          VALUES
(
            rec.TAG_NAME, rec.TAG_TYPE_CODE, rec.TAG_RECON );

EXCEPTION
    WHEN DUP_VAL_ON_INDEX THEN
            UPDATE IP21TAG SET
        TAG_NAME=rec.TAG_NAME,
        TAG_TYPE_CODE=rec.TAG_TYPE_CODE,
        TAG_RECON=rec.TAG_RECON,
        WHERE TAG_NAME=REC.TAG_NAME;

end;
END LOOP;

--EXCEPTION
    --WHEN NO_DATA_FOUND THEN
        --NULL;
    --WHEN TOO_MANY_ROWS THEN

END;
/

ERROR at line 1:
ORA-04052: error occurred when looking up remote object
PDMASTERS.IP21TAG@YADR.WORLD
ORA-04029: error ORA-904 occurred when querying
ORA_KGLR7_DEPENDENCIES@YADR.WORLD
ORA-00604: error occurred at recursive SQL level 1
ORA-00904: invalid column name

Thanks
Kenneth

      and we said...
for #1, the parameter CHECKPOINT_PROCESS is obsoleted.  Starting
with Oracle8 - the ckpt process is no longer optional.  If you
do a "ps" and capture all of the Oracle server processes -- you
should see it running already.

for #2, i'll guess that you've upgraded one of the databases.
You mention both 8.0.3 and 8.0.5.  In between these releases,
the view ORA_KGLR7_DEPENDENCIES had a column PROPERTY added to
it.  I would suspect that the upgrade was not done totally --
You should follow the steps in the migration guide.  For
example, to upgrade from 803 to 805, there should be a script
named something like u0800030.sql in oracle_home\rdbms\admin
(please see your migration and/or installation guide for the
proper file for your platform/version).  Additionally, scripts
such as catalog.sql, catproc.sql, catrep.sql, catparr.sql should
be run as well (again -- refer to your migration guide please -
it has all of the info).

the view ORA_KGLR7_DEPENDENCIES is defined in catrpc.sql.

Review &
      Followup

      Rating: 1
It does not
      work.  July 03, 2002
Reviewer:  Enrique
      Lopez  from Tarragona, Spain
We have the some problem describing in query 2 with a database created in 8.0.5a
version.
We re-run the script that you indicate in your response and the result was the
same. Could you said us something more about this Error.

Thanks, Enrique

          Followup:

8.0.5"a"  -- whats that.

anyway, did you do this to both databases -- the only way this happens, you
upgraded and did not read the upgrade manual and didn't actually complete the
upgrade.

      Rating: 3
similar kind of
      problem  May 22, 2003
Reviewer:  Vijay
      Sehgal  from India
Hi Tom,
      Good day first of all.
I am getting similar kind of a problem, In one of the procedure on Database A I
am calling a procedure from Database B while compiling procedure on database A I
am getting following error messages
ORA-04052: error occurred when looking up remote object TAXI2.BRIDGE@DBLINKSUR
ORA-00604: error occurred at recursive SQL level 1
ORA-02019: connection description for remote database not found

I am able to do the select using this dblink can you please help me out by
suggesting what could be the problem I have compared the ORA_KGLR7_DEPENDENCIES
to be similar in both the databases.

I am using 8.1.7 databases.

Thanking you in anticipation.
Vijay Sehgal

          Followup:

lets see the entire example, something like:

select * from TAXI2.BRIDGE@DBLINKSUR where 1=0;
begin
for x in ( select * from TAXI2.BRIDGE@DBLINKSUR where 1=0 )
loop
null;
end loop;
end;
/
create procedure p
as
begin
for x in ( select * from TAXI2.BRIDGE@DBLINKSUR where 1=0 )
loop
null;
end loop;
end;
/

      Rating: 3
In continuation
      to above  May 22, 2003
Reviewer:  Vijay
      Sehgal  from India
Hi Tom,
      Sorry about this but in continuation to the above question, what I am
doing exactly is, I am calling a function of database B from database A passing
a record type from packaged procedure in Database A which is accepted in called
packaged function in Database B record type similar to the type declared in
package of database A over dblink e.g.
'(prm_record_eam IN database.bridge.eam_rec@dblinksource)', please suggest so as
to what could be the cause of error.

          Followup:

examples, examples, examples -- like I give.

May 23, 2003 Petr
      -- Thanks for the question regarding "assige global temporary tables
      content to specific temporary tablespace", version 8.1.7

      You Asked
Hi Tom,
We'd like to ask users to use global temporary tables defined once insted of
creating ordinary tables for temporary pourposes every time. But our reporting
system (8i) has defined huge sort_are_aize (100M) and we have the same extent
size in our default temporary tablespace. Therefore every extent of temporary
table occupies 100M.
Could you please advise some easy solution how to allocate extents of temporary
tables in another temporary tablespace with smaller extent size while sorting
will still happen in tablespace with extent size equal to sort_area_size?
Thanks a lot for your ideas.

      and we said...
Believe it or not -- you can do this...

It'll require a bit of willingness on your part to achieve.

A temporary table uses extents from the temporary tablespace of the current
authorization schema.  Therefore, if you set up a user

create user small_sort_extents ... temporary tablespace SMALL_EXTENT_SIZE;

and then have this user create "CRUD" (create/insert, update, delete) routines
for these temporary tables and all code used these CRUD routines -- you would
have the temporary extents coming out of this small extent size tablespace since
the procedures execute "as owner" -- they will execute with the authorization
schema of small_sort_extents

Like this:

ops$tkyte@ORA920LAP> drop user a cascade;

User dropped.

ops$tkyte@ORA920LAP> drop table temp;

Table dropped.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create user a identified by a
  2  temporary tablespace temp2;

User created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create global temporary table temp
  2  on commit delete rows
  3  as
  4  select *
  5    from all_users
  6    where 1=0;

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> grant all on temp to a;

Grant succeeded.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create procedure a.insert_into_temp
  2  as
  3  begin
  4          insert into ops$tkyte.temp
  5          select * from all_users;
  6  end;
  7  /

Procedure created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> exec a.insert_into_temp;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> select username, tablespace from v$sort_usage;

USERNAME                       TABLESPACE
------------------------------ -------------------------------
OPS$TKYTE                      TEMP2

ops$tkyte@ORA920LAP> commit;

Commit complete.

ops$tkyte@ORA920LAP> insert into temp select * from all_users;

48 rows created.

ops$tkyte@ORA920LAP> select username, tablespace from v$sort_usage;

USERNAME                       TABLESPACE
------------------------------ -------------------------------
OPS$TKYTE                      TEMP

ops$tkyte@ORA920LAP> commit;

Commit complete.

If I first access temp via A -- temp2 is used, else my default temporary
tablespace temp is used.

In fact, we just need to "touch" the table -- like this:

ops$tkyte@ORA920LAP> drop user a cascade;

User dropped.

ops$tkyte@ORA920LAP> drop table temp;

Table dropped.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create user a identified by a
  2  temporary tablespace temp2;

User created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create global temporary table temp
  2  on commit delete rows
  3  as
  4  select *
  5    from all_users
  6    where 1=0;

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> grant all on temp to a;

Grant succeeded.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create procedure a.touch_temp
  2  as
  3  begin
  4          insert into ops$tkyte.temp
  5          select *
  6            from all_users
  7           where 1=0;
  8  end;
  9  /

Procedure created.

ops$tkyte@ORA920LAP> insert into temp select * from all_users;

48 rows created.

ops$tkyte@ORA920LAP> select username, tablespace from v$sort_usage;

USERNAME                       TABLESPACE
------------------------------ -------------------------------
OPS$TKYTE                      TEMP

ops$tkyte@ORA920LAP> commit;

Commit complete.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> exec a.touch_temp;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> insert into temp select * from all_users;

48 rows created.

ops$tkyte@ORA920LAP> select username, tablespace from v$sort_usage;

USERNAME                       TABLESPACE
------------------------------ -------------------------------
OPS$TKYTE                      TEMP2

ops$tkyte@ORA920LAP> commit;

Commit complete.

You might find that easier -- as all you need to do is add "a.touch_temp" before
touching temp and then leave the rest of the DML alone.

Review &
      Followup

      Rating: 5
Excellent usage
      of touching temp tables with small extents  April 16, 2003

Reviewer:  Kumar  from San Diego
The explanation is excellent and is going to be very very useful for may who are
in such usage of TEMPORARY TABLES especially when their SORT_AREA_SIZES are
bigger.

Thanks TOM
kumar

      Rating: 5
Two
      questions  April 16, 2003
Reviewer:
      A reader
1) I don't understand this. The original question seems to assume that sort
extents occurring for the temp table are smaller than for his whole system. I
remember Oracle recommends sort_area_size sould match the temp extent size. Is
the reader assuming there could be two different sort_area_size's -- and Oracle
has only one setting for this ?

2) There is this guideline saying we should size our temp extent according to
sort_area_size. I am using 9iR2 now. Is there any relationship between the new
aggragate PGA settings and the temp extent size ?

3) How does LMT extent sizing relates to that ?

          Followup:

1) the original question wanted temp tables to use a significantly smaller
extent size since they didn't put that much into them is all.

every touch of a temp table allocated 100m on disk.  they wanted it smaller.

2) conventional wisdom says 1m extent sizes are good -- I've seen nothing
conclusive to date to contradict that.

3) you would use 1m uniform extents.

      Rating: 4
temporary table
      and SGA  May 22, 2003
Reviewer:  John
       from San Jose, CA
Hi Tom,

Is the buffer cache ever touched when you insert data into or update data in the
temporary table? Or does ORACLE bypass the SGA or PGA and go to the temp
tablespace directly?

- John

          Followup:

it is touched, they are just like regular tables in that regard

May 23, 2003 Nag
      -- Thanks for the question regarding "RBO and Optimizer hints", version
      8.1.7

      You Asked
Tom

We are all under the understanding that our production database is USING THE
RBO.

But, recently I happened to observe that some of the queries (joining 5 to 7
tables) are using the FULL hint.

Iam perplexed, because from the oracle sql tuning manual and from your
discussions

"The optimizer recognizes hints only when using the cost-based approach. If you
include a hint (except the RULE hint) in a statement block, then the optimizer
automatically uses the cost-based approach."

I got back to our DBA, and let him know the same , but they argue bluntly that
the optimizer is not CBO , as they are not analyzing the tables.( the
optimizer_mode is set to CHOOSE).

I need a convincing example to prove the contrary.

You assistance is needed very dearly. Iam very confident that the best execution
path is not being chosen, as the tables are not analyzed and teh cbo is being
used due to the use of the FULL hint.

Thank you

      and we said...
If there is a WELL FORMED HINT in the query -- that query IS using the cost
based optimizer -- UNLESS the hint is in fact a RULE hint (can you point me to
where I've said the above?  Its not accurate).

The parser recognizes hints and will use the cost based optimizer.  If you
include a hint (except a RULE hint) in a SQL statement, then the optimizer will
be the CBO.

The example is trivial:

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

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

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index emp_idx on emp(deptno);
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from emp, dept where emp.deptno =
dept.deptno;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     TABLE ACCESS (FULL) OF 'DEPT'
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
   4    3       INDEX (RANGE SCAN) OF 'EMP_IDX' (NON-UNIQUE)

Ok, this is RBO above -- we can TELL that since there is no "Card", "Cost"
information in the plan...

ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ full */ * from emp, dept where
emp.deptno = dept.deptno;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     TABLE ACCESS (FULL) OF 'DEPT'
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
   4    3       INDEX (RANGE SCAN) OF 'EMP_IDX' (NON-UNIQUE)

There we used an improperly formed hint (full MUST be followed by () in order
to be well formed).  We are still using RBO...

ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ full(emp) */ * from emp, dept
where emp.deptno = dept.deptno;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=67 Bytes=7839)
   1    0   MERGE JOIN (Cost=6 Card=67 Bytes=7839)
   2    1     SORT (JOIN) (Cost=4 Card=82 Bytes=7134)
   3    2       TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=82 Bytes=7134)
   4    1     SORT (JOIN) (Cost=3 Card=82 Bytes=2460)
   5    4       TABLE ACCESS (FULL) OF 'DEPT' (Cost=1 Card=82 Bytes=2460)

Now, we can see we are using CBO with a well formed hint, we see the cost and
card -- in fact, the plan is different, something that would not be possible
with  the RBO

Going further:

  1* select /*+ full(a) */ * from emp, dept where emp.deptno = dept.deptno
ops$tkyte@ORA817DEV.US.ORACLE.COM> /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=67 Bytes=7839)
   1    0   MERGE JOIN (Cost=6 Card=67 Bytes=7839)
   2    1     SORT (JOIN) (Cost=4 Card=82 Bytes=7134)
   3    2       TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=82 Bytes=7134)
   4    1     SORT (JOIN) (Cost=3 Card=82 Bytes=2460)
   5    4       TABLE ACCESS (FULL) OF 'DEPT' (Cost=1 Card=82 Bytes=2460)

we used a well formed but INVALID hint (no table A in there).  We still use
the CBO

So the quick litmus test is to use autotracae in plus -- it'll show you right
off what optimizer you are using.

I should say -- that with 817, you are missing out on alot by NOT using the CBO.
Review &
      Followup

      Rating:
      5
  November 13, 2001
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating:
      5
CBO  November 13, 2001
Reviewer:  Andre
      Whittick Nasser  from Rio de Janeiro, Brazil
Some questions:

1) When you use a correct hint, like FULL(emp), then you are forcing emp to
undergo a FTS, and, in undergoing a FTS, the join behavior will adapt to what
the hint says, for example, doing a sort-merge join. Right ?

2) Even if you have not analyzed any of the tables ? Where does the CBO have the
statistics from ? That reminds me of FIRST_ and ALL_ROWS. Even with no stats,
the CBO is able to take assumptions based on "heuritics" (nice word, but what
does it mean in Oracle) ?

3) To the best of my knowledge, I have never used RBO in production. Does it
ignore hints like FULL, etc ? When Oracle says RBO is "syntax-driven" are not
hints part of this "syntax", or only things like the "driving table" in the FROM
table order are considered, not hints ?

4) I know "cost" metrics are not very meaningful. But what sense can I make of
it ? What about "card" and "bytes" ? How useful can they be, REALLY ?

Thanks once more, from (on this morning) rainy Rio de Janeiro !

          Followup:

1) yes -- IF it accepts the hint, hints are just that -- hints.  They are NOT
directives, they are suggestions.  It took the suggestion in this case.

2) Correct -- CBO makes up generalized statistics if none exist.  Heuristics
mean "rules" basically -- the CBO has a set of rules it applies to derive stats
for objects that have none.

<quote design/tuning for performance guide>
If the optimizer uses the cost-based approach for a SQL statement, and if some
tables accessed by the statement have no statistics, then the optimizer uses
internal information (such as the number of data blocks allocated to these
tables) to estimate other statistics for these tables.
</quote>

3) if you have HINTS -- you are NOT using RBO (I thought that would be clear
from the example!!  thats what this entire example shows -- drop a well formed
hint in there and the RBO goes out the window -- unless the well formed hint is
/*+ RULE */).

If you have HINTS, you are using CBO and the syntax of the query (order of
tables, etc) is not relevant.

4) Card is cardinality -- an estimate of the number of rows that each step will
work on/produce.  Bytes is the estimated number of bytes each step will produce.
 The cost can be useful to decide why a certain plan was choosen (see Jonathan
Lewis's Practical Oracle8i book -- he has a good discussion of this, excellent
book all around).

      Rating: 5
To wrap it up
      !  November 13, 2001
Reviewer:  Andre Whittick
      Nasser  from Rio de Janeiro, Brazil
So,

In sum:

ANY HINT - /*+ RULE */ => CBO

Correct ? (yes/no/why)

          Followup:

Correct, yes.  Why -- becauses thats the way the server is programmed -- you
want to use a hint -- only the CBO understands hints, hence the CBO is used

      Rating: 5
Why a query
      performs better only when /*+ rule */ hint is
      used?  November 07, 2002
Reviewer:  A Reader
       from USA
Hi Tom,
I know we should use CBO but in some queries we have, we have to user a
/*+rule*/ hint to make them perform better.  The optimizer_mode=choose. We have
updated statistics for all the tables. We have to use the hint not only because
it forces the queries to use the existing indexes, but also because it really
returns results noticeably faster. We tried to rewite in different ways but none
of them as good as using the rule hint. Are there any side effects on using the
"rule" hints?  Should we continue trying utill we find the matching CBO?
Thank you for your time.

          Followup:

I would prefer to use the optimizer_index_cost_adj init.ora (or session)
parameter.  I like the number "35".  Give that a try.

      Rating:
      4
Optimizer_Goad=First_Rows  November 07, 2002

Reviewer:  Mike  from Portland, OR
I read somewhere that using the FIRST_ROWS optimizer goal or hint would tend to
use indexes more than the ALL_ROWS goal, so it would be more like using the RULE
based optimizer.  Is that true?

          Followup:

I would prefer to use the cost adj -- but yes, first rows makes the CBO "index
happy" or "index crazy"

      Rating:
      5
  November 07, 2002
Reviewer:  Dave
       from Ohio
Can you describe the way in which optimizer_index_cost_adj and
optimizer_index_caching relate? It seems to me that if you promote the use of
indexes through lowering the former parameter, then the latter would also need
adjustment to reflect the true state of the cache. I wonder whether you could
get stuck in a kind of feedback loop where promoting the use of indexes leads to
greater likelihood of index usage, hence more likelihood of finding the indexes
in cache, hence there is a lower cost associated with using them, hence you
promote the use of them more ... until you end up with
optimizer_index_caching=100 and optimizer_index_cost_adj=1!

Also, I have a view defined as ...

create or replace view advise_index_cost_adj
as
select 'Actual is '
       ||LTrim(to_char(seq_read_wait/scattered_wait*100,'9990.0'))
       ||', system is set to '||init_value message
from   (
       select average_wait seq_read_wait
       from v$system_event where event ='db file sequential read'),
       (
       select average_wait scattered_wait
       from v$system_event where event ='db file scattered read'),
       (
       select value init_value from v$parameter
       where name = 'optimizer_index_cost_adj');

... that gives me output such as ...

"Actual is 7.1, system is set to 12"

.. for a quick snapshot of index cost adjustment.

Is the advice that it's giving (OK, that I'm giving myself) reliable, or am I
fooling myself here?

          Followup:

Not at all - you don't need to touch caching to use cost adj.  In fact, i've
personally never used the caching one, only the cost adj one.

Never saw a "feedback" loop like that -- no.

If you have Jonathan Lewis's book "practical Oracle8i building efficient
databases" -- he has a discussion on this.  He sums it up somewhat by saying:

"in effect the parameter COST ADJ behaves as if it were dictating to the
optimizer the degree to which is can expect the TABLE to be cached, whereas the
parameter index caching tells the optimizer the degree to which it can expect
the index to be cached"

cost adjust is a more "aggressive" caching one.

(if you don't have his book -- you should get it, it's really good).

I'm not quite sure where you are going with that ration -- the one of sequential
(single block typically) IO wait to scattered (multi-block typically) IO wait?

      Rating:
      5
  November 08, 2002
Reviewer:  Dave
       from Ohio
Quote: I'm not quite sure where you are going with that ration -- the one of
sequential
(single block typically) IO wait to scattered (multi-block typically) IO wait?

Uh-oh.

Wellllllllll, supposing that sequential read waits are mostly associated with
indexes and (maybe?) access by rowid on a table, and scattered read waits are
mostly associated with tables (excepting perhaps index scans, "fast full" or
otherwise) then we might get an estimate of one element of the overhead
associated with reading tables via full scan versus reading them via index
access.

Or maybe not. This could be shaky ground.

          Followup:

I think "shaky".

Suppose I had one billion waits for full scans -- looks like alot of time.
I have one thousand for index reads -- looks small.

Your ratio would make full scans look evil however -- if we flipped it an
started using indexes it would be really bad (it could be, it might not be, it
all depends).  It could be that we avoid a billion waits on the full scans but
introduce 5 billion waits on seq IO.

We would have to compare the cost of doing the scattered IO for a working set of
queries VS the cost of doing seq IO for the same set -- something you cannot do
really unless you have both sets of numbers!

      Rating: 5
FIRST_ROWS(n)
      HINT  November 08, 2002
Reviewer:  pasko
       from Hamburg,Germany
Hi Tom,
whats the major difference performance-wise ,  between the hints  FIRST_ROWS and
the new one in 9iR2 : FIRST_ROWS(n) ?

I have tried :

select /*+ FIRST_ROWS(20)  */
count(*)
from  emp
where   rownum <=  100000  ;

but i still see the hint being used and also the STOP KEY in the Query plan..

I didn't see any performance improvements after i switched FIRST_ROWS with
FIRST_ROWS(n) .

How best can we use this new HINT ?

Best regards

          Followup:

it'll only affect things in a very fine grained manner.

Given your query -- there really is only TWO or THREE approaches to take
regardless of how the query is optimized:

o full scan emp for 100,000 rows
o index range scan on emp_idx if emp_idx is an index on a not-null column
o index fast full scan on emp_idx if emp_idx is an index on a not-null column

regardless of the optimization technique -- the plan will most likely stay
exactly the same.

      Rating: 5
Please help me
      to solve these puzzle  November 08, 2002
Reviewer:
      Bharath  from USA
>>If there is a WELL FORMED HINT in the query -- that query >>IS using the cost
based optimizer

Tom please help me from the puzzle

Question 1,we are using 8.1.7.4 (Optimizer = rule Both in Prod & Dev ) it means
that we cant give a hint in the query ,the reason why i am asking is  in your
response rule based optimizer doesnt understand the hint

Question2)If i give the well formed Hint in the query will the oracle optimizer
will switch to CBO even if the oracle optimizer mode is set to rule

Question3)Why Rule based optimizer dosent understand the hint.

          Followup:

question 1) your premise is FALSE.  You can have RBO set as the default but the
second you pop a valid hint in there -- you are using CBO. Consider (autotrace
shows which optimizer we are using -- if is has COST/CARD, you are using CBO,
else you are using RBO)

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

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table emp compute statistics;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set optimizer_goal=choose;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from emp;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=14 Bytes=560)
   1    0   TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=14 Bytes=560)

table is analyzed, in choose that implies CBO...

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set optimizer_goal=rule;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from emp;

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

even though stats are present -- we can see we are using RBO here BUT...

ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ FULL(EMP) */ * from emp;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=RULE (Cost=1 Card=14 Bytes=560)
   1    0   TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=14 Bytes=560)

you can see the hint puts us BACK into CBO!!!

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off

question 2) yes.

question 3) because HINTS are only valid with CBO.  RBO = RULE based
optimization.  It uses RULES to come up with a query plan.  CBO doesn't use a
hard, fast list of rules to come up with the plan.  It takes tons of variables
into consideration -- including the hints.

      Rating:
      5
  November 08, 2002
Reviewer:  Dave
       from Ohio
Quote: Suppose I had one billion waits for full scans -- looks like a lot of
time.  I have one thousand for index reads -- looks small.

What if we start by looking not at the quantity of reads, but at the average
wait time instead? From that perspective the view might be suggesting whether it
would be a worthwhile to gain 1,000,000,000 sequential read waits while reducing
scattered read waits by only 500,000,000 -- total db_file wait time would be
reduced even though the total db_file number of waits has increased.

  1  select TOTAL_WAITS,TIME_WAITED,AVERAGE_WAIT
  2*        from v$system_event where event ='db file scattered read'
SQL> /

TOTAL_WAITS TIME_WAITED AVERAGE_WAIT
----------- ----------- ------------
      19543      284113   14.5378396

SQL> select TOTAL_WAITS,TIME_WAITED,AVERAGE_WAIT
  2         from v$system_event where event ='db file sequential read'
  3  /

TOTAL_WAITS TIME_WAITED AVERAGE_WAIT
----------- ----------- ------------
     192772      246160   1.27694893

Given the ratio of (avg sequential read wait time)/(avg scattered read wait
time) of 1:11 that I see on my system, I might be willing to take on many more
sequential reads for each scattered read that I eliminate. All completely
theoretical of course, and it probably represents about 1/100th of the total
system performance from the user point of view.

Note that:

i) this is a small data warehouse db, heavy on bitmap indexes and range
partitions, if that makes a difference
ii) I don't have a performance problem, I'm just tuning for the sake of it ;)

Hey, it could benefit someone else though, right? I'm sure that
index_optimizer_cost_adj is a much neglected parameter -- when I took over this
database an (obvious) adjustment from "100" down to "15" reduced average report
time from 75 seconds down to 15 seconds. And speaking of the users point of
view, not a single one of them noticed this (literally) overnight improvement --
an example which you are quite free to mention when bashing people for their
never-ending tuning exercises.

Talking of which, I'm now off to implement composite partitioning, bitmap join
indexing, and data segment compression on a major fact table in order to get
that average below ten seconds. Like anyone cares.

      Rating: 5
Final
      Answer  November 08, 2002
Reviewer:  Bharath
       from USA
What is Difference Between  Well formed  and Not well formed hints?If we place
Not well formed hint then it means that parser will simply reject it.

          Followup:

select /*+ Hello World */ * from dual;

is not well formed -- it is not a valid hint.

select /*+ full */ * from dual;

is not well formed -- FULL needs to be in the format FULL(tablename) in order to
be well formed.

select /*+ full(dual) */ * from dual

is well formed.  It is not that it rejects it -- it ignores hints that are not
valid hints -- as if they were not in the text at all.

      Rating:
      5
  March 17, 2003
Reviewer:
      A reader
Hi Tom,

We are using siebel CRM. siebel recommands rule base optimizer. we have other
ssystem in Oracle that use cost based Optimizer.

While doing query we have to join tables from both Siebel crm(Rule base opt.)
and other systems(cost based optimizer).

--I have seen lots of performance problem
What is your suggestions on this matter?

2) Star hint

We have some bad queries that is taking more then five(5) minitues, if I use /*+
star */ hint it run very fast and return result with in 10 or less SECONDS !!!!

Could you explain why this is happening  ?
We have mix enviournment (Both rule based and cost bas)

For eg.

---------------------
Without Hint
---------------------

  1  select   'OPTY', op.row_id, sysdate, 'FOR_ASSIGNMENT', 'ASSIGNMENT CHANGE -
HOT PROS OPTY - ZIP
  2   from siebel.s_opty op, siebel.s_contact cn, siebel.s_addr_per ad,
  3    sblint.release_rep_assignment rra, siebel.s_stg stg,
mkt.listing_state@oraprod1@mkt_link st
  4   where op.pr_con_id = cn.row_id and cn.pr_per_addr_id = ad.row_id
  5    and op.opty_cd in ('Hot Prospect', 'Prospect')
  6    and ( op.x_sub_type <> 'Last Year Cancel' or op.x_sub_type is null )
  7              and ( nvl(op.x_source,' ') <> 'Last Year Cancel')
     -- dz 2/2003
  8    and rra.sales_level_code = 3 and stg.stage_status_cd = 'Open'
  9    and op.curr_stg_id = stg.row_id
 10    and substr(ad.zipcode, 0, 5) = rra.zip_code
 11    and rra.zip_code is not null
 12        --    add code to match on state, county, and city  - zeller
 13         and ad.city = rra.city and ad.county = rra.county and ad.state =
st.state
 14    and st.state_no = rra.listing_state_no
 15    and not exists (select * from sbl_assignment where object_id = op.row_id
and object = 'OPTY'
 16*       and status <> 'Archived')
SQL> /

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=RULE
   1    0   FILTER
   2    1     NESTED LOOPS
   3    2       NESTED LOOPS
   4    3         NESTED LOOPS
   5    4           NESTED LOOPS
   6    5             NESTED LOOPS
   7    6               REMOTE*                                        ORAPROD1
                                                                       .WORLD@M
                                                                       KT_LINK

   8    6               TABLE ACCESS (BY INDEX ROWID) OF 'S_ADDR_PER'
   9    8                 INDEX (RANGE SCAN) OF 'S_ADDR_PER_M2' (NON-U
          NIQUE)

  10    5             TABLE ACCESS (BY INDEX ROWID) OF 'RELEASE_REP_AS
          SIGNMENT'

  11   10               INDEX (RANGE SCAN) OF 'RELEASE_REP_IDX2' (NON-
          UNIQUE)

  12    4           TABLE ACCESS (BY INDEX ROWID) OF 'S_CONTACT'
  13   12             INDEX (RANGE SCAN) OF 'S_CONTACT_PR_PER_ADDR_ID_
          X' (NON-UNIQUE)

  14    3         TABLE ACCESS (BY INDEX ROWID) OF 'S_OPTY'
  15   14           INDEX (RANGE SCAN) OF 'S_OPTY_M9' (NON-UNIQUE)
  16    2       TABLE ACCESS (BY INDEX ROWID) OF 'S_STG'
  17   16         INDEX (UNIQUE SCAN) OF 'S_STG_P1' (UNIQUE)
  18    1     TABLE ACCESS (BY INDEX ROWID) OF 'SBL_ASSIGNMENT'
  19   18       INDEX (RANGE SCAN) OF 'SBL_ASSIGNMENT_IDX1' (NON-UNIQU
          E)

   7 SERIAL_FROM_REMOTE            SELECT "STATE","STATE_NO" FROM "MKT"."LISTIN
                                   G_STATE" "ST"

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
   49228973  consistent gets
     300521  physical reads
         68  redo size
        280  bytes sent via SQL*Net to client
        249  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

---------------------
With  /*+ star */ Hint
---------------------

SQL> ed
Wrote file afiedt.buf

  1  select  /*+ star */  'OPTY', op.row_id, sysdate, 'FOR_ASSIGNMENT',
'ASSIGNMENT CHANGE - HOT PRO
  2   from siebel.s_opty op, siebel.s_contact cn, siebel.s_addr_per ad,
  3    sblint.release_rep_assignment rra, siebel.s_stg stg,
mkt.listing_state@oraprod1@mkt_link st
  4   where op.pr_con_id = cn.row_id and cn.pr_per_addr_id = ad.row_id
  5    and op.opty_cd in ('Hot Prospect', 'Prospect')
  6    and ( op.x_sub_type <> 'Last Year Cancel' or op.x_sub_type is null )
  7              and ( nvl(op.x_source,' ') <> 'Last Year Cancel')
     -- dz 2/2003
  8    and rra.sales_level_code = 3 and stg.stage_status_cd = 'Open'
  9    and op.curr_stg_id = stg.row_id
 10    and substr(ad.zipcode, 0, 5) = rra.zip_code
 11    and rra.zip_code is not null
 12        --    add code to match on state, county, and city  - zeller
 13         and ad.city = rra.city and ad.county = rra.county and ad.state =
st.state
 14    and st.state_no = rra.listing_state_no
 15    and not exists (select * from sbl_assignment where object_id = op.row_id
and object = 'OPTY'
 16*       and status <> 'Archived')
SQL> /

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=RULE (Cost=886 Card=1 Bytes=318)
   1    0   FILTER
   2    1     NESTED LOOPS (Cost=886 Card=1 Bytes=318)
   3    2       NESTED LOOPS (Cost=60 Card=1 Bytes=300)
   4    3         NESTED LOOPS (Cost=58 Card=1 Bytes=213)
   5    4           MERGE JOIN (CARTESIAN) (Cost=57 Card=1 Bytes=196)
   6    5             NESTED LOOPS (Cost=41 Card=1 Bytes=124)
   7    6               TABLE ACCESS (FULL) OF 'S_STG' (Cost=1 Card=20
           Bytes=520)

   8    6               TABLE ACCESS (BY INDEX ROWID) OF 'S_OPTY' (Cos
          t=2 Card=1 Bytes=98)

   9    8                 INDEX (RANGE SCAN) OF 'S_OPTY_F5' (NON-UNIQU
          E) (Cost=1 Card=1)

  10    5             SORT (JOIN) (Cost=55 Card=1 Bytes=72)
  11   10               TABLE ACCESS (FULL) OF 'RELEASE_REP_ASSIGNMENT
          ' (Cost=16 Card=1 Bytes=72)

  12    4           REMOTE* (Cost=1 Card=74 Bytes=1258)                ORAPROD1
                                                                       .WORLD@M
                                                                       KT_LINK

  13    3         TABLE ACCESS (BY INDEX ROWID) OF 'S_ADDR_PER' (Cost=
          2 Card=2000 Bytes=174000)

  14   13           INDEX (RANGE SCAN) OF 'S_ADDR_PER_M1' (NON-UNIQUE)
           (Cost=1 Card=2000)

  15    2       TABLE ACCESS (BY INDEX ROWID) OF 'S_CONTACT' (Cost=826
           Card=2000 Bytes=36000)

  16   15         INDEX (FULL SCAN) OF 'S_CONTACT_F6' (NON-UNIQUE) (Co
          st=26 Card=2000)

  17    1     TABLE ACCESS (BY INDEX ROWID) OF 'SBL_ASSIGNMENT' (Cost=
          2 Card=1 Bytes=63)

  18   17       INDEX (RANGE SCAN) OF 'SBL_ASSIGNMENT_IDX1' (NON-UNIQU
          E) (Cost=1 Card=1)

  12 SERIAL_FROM_REMOTE            SELECT "STATE","STATE_NO" FROM "MKT"."LISTIN
                                   G_STATE" "ST" WHERE "STATE_NO"=:1

Statistics
----------------------------------------------------------
          0  recursive calls
          8  db block gets
      25797  consistent gets
       6508  physical reads
          0  redo size
        280  bytes sent via SQL*Net to client
        249  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
         32  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL>
SQL>

--LOTS of  difference between statistics!!!

Thanks,

          Followup:

1) simple -- get rid of sighbel ;)

seriously -- on queries that involve tables with stats and without stats -- you
are ASKING for big time trouble.  You'll either be hinting the query literally
to death (doing the work of the cbo) or you'll be using the RBO (and missing out
on all of the really cool things the cbo can do which the RBO hasn't a chance).
also, if you use objects such as IOT's or partitioned tables - you have no
choice, you'll be using the CBO and most likely using hints.

2) simple -- the RBO is very stupid.  It has a tiny set of rules (documented in
the tuning guide) by which is processes any query.  It doesn't use any
statistics, it doesn't do anything smart.  It just applies the rules and away it
goes.

Look at this one:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6749454952894

      Rating: 5
rbo switches to
      cbo with well formed hint  April 03, 2003

Reviewer:  Sujata  from Germany
For Question: (By Bharath)
"Question2)If i give the well formed Hint in the query will the oracle optimizer

switch to CBO even if the oracle optimizer mode is set to rule"

Your answer was yes...

My Question:
1.Will it switch to CBO regardless of whether the table
is analyzed or not at this point?
2.If it does switch regardless..and the table is not analyzed....no
statistics..then you mentioned..that
CBO will guess the statistics based on blocks etc...
So, will it better to have those tables that are using hints analyzed...

          Followup:

1) yes.

2) but -- if you are using hints, you are basically doing the optimizers job,
the optimizer doesn't need stats.  You'll sit there in sqlplus hinting the query
to do exactly what you want.  statistics won't be something you want really.
You'll take a query:

select * from t where upper(ename) = :x

and you'll hint it to be

select /*+ index( t my_fbi ) */ * from t where upper(ename) = :x

it won't need stats cause you already told it what to do.

      Rating: 5
Why is it not
      using the FBI?  May 19, 2003
Reviewer:
      Sachin  from Germany
8.1.7.4.0 (running RBO)
O.K. We have a table that has PROJID as PK and following indexes on it:
NON-UNIQUE INDEX I_PROJ_PROJECT_PREVPROJID (PREVIOUS_PROJID)
UNIQUE INDEX I_PROJ_PROJECT_PROJNAME (PROJNAME, PROJID)
UNIQUE INDEX I_PROJ_PROJECT_UPPER_PROJID (UPPER(PROJID))
NON-UNIQUE INDEX PROJ_PROJECT_UPPER_PROJNAME (UPPER(PROJNAME))

I tried the following query:
select COUNT(*) FROM PROJ_PROJECT
WHERE UPPER(PROJID) LIKE '97%';

It returned
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=RULE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'PROJ_PROJECT'

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

Then I did this:
select /*+ INDEX(I_PROJ_PROJECT_UPPER_PROJID) */ COUNT(*) FROM PROJ_PROJECT
WHERE UPPER(PROJID) LIKE '97%';

and it returned:
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=RULE (Cost=4 Card=1 Bytes=15)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FAST FULL SCAN) OF 'I_PROJ_PROJECT_PROJNAME' (UNI
          QUE) (Cost=4 Card=20017 Bytes=300255)

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

There were 41490 records involved.

Question:
Why did it ignore the index on UPPER(PROJID)?
This same behaviour was also there while trying to hint for
upper(projname) index.

      Rating: 5
Any idea about
      the above...  May 22, 2003
Reviewer:  Sachin
       from Germany

          Followup:

8.1.7.4.0 (running RBO) <<<<=== says it all.

function based indexes (and hundreds of other goodies) use the CBO only.

May 23,
      2003 Robert -- Thanks for the question
      regarding "ordering a connect by", version

      You Asked
Tom,

How do you use Oracle's "connect by" clause and order the
resulting output in alphabetical order without losing the
hierarchical structure?

The more I look at this, the more perplexing it becomes.  Please
HELP!

      and we said...
This is tricky since the Order by happens AFTER the CONNECT BY
and hence will destroy the result sets 'hierarchy'.  You
typically never use connect by AND order by.

One solution that always works (in Oracle8i and up) is
demonstrated at
http://osi.oracle.com/~tkyte/autonomous/index.html
in the section on "Writing to the database
state in a function called from SQL".  It does not use a connect
by but achieves the same hiearchy and is guaranteed to be
ordered.

Another method by should work in most cases (you'll have to
verify it works on your data, with your query and if you
'ad-hoc' the query by adding predicates dynamically at run time,
you'll need to be very careful with this solution) is to use an
index to help the sort order.  We'll also use an index HINT in
the event the columns we are connecting by are indexed in other
ways as well.

To demonstrate this technique -- we will use the scott/tiger
table EMP.

ops$tkyte@8i> create table emp as select * from scott.emp;
ops$tkyte@8i> alter table emp add constraint emp_pk primary
key(empno);
ops$tkyte@8i> create    index emp_idx1 on emp(mgr,job);

So, we have the emp table with 2 indexes now, one on empno and
one on (mgr,job).

The connect by query we will run will use "connect by prior
empno = mgr".  That means for each row we start with -- the
database will effectively do "select * from emp where mgr =
:PRIOR_EMPNO" to find the subordinate records.  It will be
looking for an index that has the MGR column on the leading
edge.  Since we have one on (mgr,job), and the database finds
that index -- the data will come out in a hirearchy ordered by
JOB in each level.  We can see this with:

ops$tkyte@8i> set autotrace on explain

ops$tkyte@8i>  select lpad( '*', level*2 ) || ename ename,
  2              job
  3     from emp
  4    start with mgr is null
  5  connect by prior empno = mgr
  6  /

ENAME                JOB
-------------------- ---------
 *KING               PRESIDENT
   *JONES            MANAGER
     *SCOTT          ANALYST
       *ADAMS        CLERK
     *FORD           ANALYST
       *SMITH        CLERK
   *BLAKE            MANAGER
     *JAMES          CLERK
     *ALLEN          SALESMAN
     *WARD           SALESMAN
     *MARTIN         SALESMAN
     *TURNER         SALESMAN
   *CLARK            MANAGER
     *MILLER         CLERK

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   CONNECT BY
   2    1     TABLE ACCESS (FULL) OF 'EMP'
   3    1     TABLE ACCESS (BY USER ROWID) OF 'EMP'
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
   5    4       INDEX (RANGE SCAN) OF 'EMP_IDX1' (NON-UNIQUE)

See how the optimizer found and used the EMP_IDX1 to retrieve
the data and the data is in fact sorted by JOB at each level.
Suppose we wanted to be sorted by ENAME at each level.  We
should create an index then on MGR,ENAME and use a hint to tell
Oracle we want to use that index in the connect by.  It would
look like this:

ops$tkyte@8i> create index emp_connect_by_idx on emp(mgr,ename)
  2  /

ops$tkyte@8i>  select /*+ INDEX(emp emp_connect_by_idx) */
  2                  lpad( '*', level*2 ) || ename ename,
  3                  job
  4     from emp
  5    start with mgr is null
  6  connect by prior empno = mgr
  7  /

ENAME                JOB
-------------------- ---------
 *KING               PRESIDENT
   *BLAKE            MANAGER
     *ALLEN          SALESMAN
     *JAMES          CLERK
     *MARTIN         SALESMAN
     *TURNER         SALESMAN
     *WARD           SALESMAN
   *CLARK            MANAGER
     *MILLER         CLERK
   *JONES            MANAGER
     *FORD           ANALYST
       *SMITH        CLERK
     *SCOTT          ANALYST
       *ADAMS        CLERK

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1
Bytes=39)
   1    0   CONNECT BY
   2    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=5
Bytes=65)
   3    1     TABLE ACCESS (BY USER ROWID) OF 'EMP'
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=1
Card=1 Bytes=39)
   5    4       INDEX (RANGE SCAN) OF 'EMP_CONNECT_BY_IDX'
(NON-UNIQUE) (Cost=2 Card=1)

We verified the query plan is using our index and in fact the
data is sorted now by ENAME at each level, not by job.  If you
need 2 different sorts -- you can still do that, we can get our
original "sort by job" by hinting the query to use that index:

ops$tkyte@8i>  select /*+ INDEX(emp emp_idx1) */
  2                  lpad( '*', level*2 ) || ename ename,
  3                  job
  4     from emp
  5    start with mgr is null
  6  connect by prior empno = mgr
  7  /

ENAME                JOB
-------------------- ---------
 *KING               PRESIDENT
   *JONES            MANAGER
     *SCOTT          ANALYST
       *ADAMS        CLERK
     *FORD           ANALYST
       *SMITH        CLERK
   *BLAKE            MANAGER
     *JAMES          CLERK
     *ALLEN          SALESMAN
     *WARD           SALESMAN
     *MARTIN         SALESMAN
     *TURNER         SALESMAN
   *CLARK            MANAGER
     *MILLER         CLERK

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1
Bytes=39)
   1    0   CONNECT BY
   2    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=5
Bytes=65)
   3    1     TABLE ACCESS (BY USER ROWID) OF 'EMP'
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=1
Card=1 Bytes=39)
   5    4       INDEX (RANGE SCAN) OF 'EMP_IDX1' (NON-UNIQUE)
(Cost=2 Card=1)

So, it is sorted by JOB once again.

You should consider using QUERY Plan stability with Oracle8i,
release 8.1 once you get the right plan in place.  This will
make it such that once you get the plan that uses the index you
want -- it will be stored in the database and you can request at
run time to use "plan X" or "plan Y" for a given query.  This
will protect you from changes in the plan over time as different
statistics are collected, versions are upgraded and so on.  You
must be careful to have the correct indexes in place, else the
data will *not* be ordered and the fact the data is not ordered
is not considered an error (you'll not be notified that the
index you want is not in place)

Review &
      Followup

      Rating: 4
More reading on
      the same problem.  August 13, 2001
Reviewer:
      Andrew  from CA, USA
http://128.121.241.221/Pipelines/PLSQL/archives/orderby_connectby.txt

      Rating:
      4
  October 17, 2001
Reviewer:
      A reader
Verygood explaination...

Thanks.

      Rating:
      5
  July 31, 2002
Reviewer:  Helena
      Markova  from Bratislava, Slovakia
Thank you for explanation.

      Rating: 3
"ordering a
      connect by"  May 22, 2003
Reviewer:  Raja

Hi Tom,
I have a table MENU_ITEMS with the following data for example.....

   ID ITEM_NAME                 PARENT_ID ORDER_NUM
----- ------------------------- --------- ---------
    1 Client                                      1
    2 Client Info                       1         1
    3 Client Profile Info               2         1
    4 Client Accounts Info              2         2
    5 Add New Client                    1         2
    6 Create Client Profile             5         1
    7 Create Client Accounts            5         2
    8 Edit Client                       1         3
    9 Edit Client Profile               8         1
   10 Edit Client Accounts              8         2
   11 Remove Client                     1         4
   12 Remove Client Profile            11         1
   13 Remove Client Accounts           11         2
   14 Reports                                     2

The front-end requires a record set in the parent-child heirarchy ordered by the
order_num column.  I tried to do it as below....

select id, item_name, parent_id, order_num
from menu_items
start with parent_id is null
connect by parent_id = prior id
order by order_num ;

However, this is what I get....

   ID ITEM_NAME                 PARENT_ID ORDER_NUM
----- ------------------------- --------- ---------
    1 Client                                      1
    2 Client Info                       1         1
    3 Client Profile Info               2         1
    6 Create Client Profile             5         1
    9 Edit Client Profile               8         1
   12 Remove Client Profile            11         1
    5 Add New Client                    1         2
    4 Client Accounts Info              2         2
    7 Create Client Accounts            5         2
   10 Edit Client Accounts              8         2
   13 Remove Client Accounts           11         2
   14 Reports                                     2
    8 Edit Client                       1         3
   11 Remove Client                     1         4

Could you please help me with any ideas to get around this problem. We use
Oracle9i database.

Thanks in advance!

          Followup:

search for

"order siblings by"

on this site.

      Rating: 5
"ordering a
      connect by"  May 23, 2003
Reviewer:  Raja

Thanks a lot! it works!!

May 23, 2003 Art -- Thanks for the question regarding "Pivot a
      report by date range", version 8.1.7

      You Asked
Hello, Tom.

I have a table of mutually exclusive date-range information
(fiscal_quarters):

quarter_num year start_date end_date
----------- ---- ---------- -----------
          1 2001 01-OCT-00  31-DEC-00
          2 2001 01-JAN-01  31-MAR-01
          3 2001 01-APR-01  30-JUN-01
          4 2001 01-JUL-01  30-SEP-01
          1 2002 01-OCT-01  31-DEC-01
          2 2002 01-JAN-02  31-MAR-02
          3 2002 01-APR-02  30-JUN-02
          4 2002 01-JUL-02  30-SEP-02

...and I have an existing report something like this:

cust_no to_date_revenue
------- ---------------
      1           11400
      2           31500
      3           18000
      4           39000

The revenue shown is a sum grouped by customer, and each
component of this revenue has a date--date_claimed--associated
with it.

I'd like to add columns to this report to break out this
summed revenue by the four most recently fully elapsed
quarters.  So as the report moves through time, the window of
quarters shown on the report also moves.  If I were to run
this report today (during 2Q02), I would like it to show
columns of revenue for 2Q01, 3Q01, 4Q01 and 1Q02 data.  If
I run the report one quarter in the future, I'd like the quarters
displayed to similarly have advance by one quarter.

So if customer #1 above has the following revenue "profile":

cust_no revenue date_claimed
------- ------- ------------
      1    4000 02-OCT-00
      1    3000 02-JAN-01
      1    2000 02-APR-01
      1    1000 02-JUL-01
      1     800 02-OCT-01
      1     400 02-JAN-02
      1     200 02-APR-02

...and if I run the report today, I'd like to see something like:

cust_no rev_4Q_ago rev_3Q_ago rev_2Q_ago rev_last_Q to_date_rev
------- ---------- ---------- ---------- ---------- -----------
      1       2000       1000        800        400       11400
      2      10000       5000      10000       5000       31500
      3          0          0       7000      11000       18000
      4          0      30000          0          0       39000

I've tried adapting the pivot template you describe
in Chapter 12 of your book to my date situation, but
I haven't been having much luck.  Can you please
lend some guidance?

Also, is there any way to dynamically assign column
header names, so I could have a column header by
the name of "1Q02" during 2Q02 and one by the name
of "2Q02" during 3Q02, instead of the static
"rev_last_qtr"?

Thank you, Tom.

      and we said...
You cannot dynamically assign column names -- they are part of the query (you
would have dynamically generate the query).

Here is one approach for your sliding window.  I use a view on the "quarter"
table to make the sql more readable but here goes:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t1( cust_no int, revenue int,
date_claimed date );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t2( quarter_num int, start_date
date, end_date date );
Table created.

...populate t2 with the above data for quarters....

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t1
  2  select mod(rownum,4)+1, trunc(dbms_random.value( 100, 10000 )),
  3         trunc(sysdate-dbms_random.value( 0, 365*3 ))
  4   from all_objects
  5  /
22904 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace view
  2  recent_qtrs
  3  as
  4  select max(decode( rownum, 4, start_date, null )) start_date1,
  5         max(decode( rownum, 4, end_date,   null )) end_date1,
  6         max(decode( rownum, 3, start_date, null )) start_date2,
  7         max(decode( rownum, 3, end_date,   null )) end_date2,
  8         max(decode( rownum, 2, start_date, null )) start_date3,
  9         max(decode( rownum, 2, end_date,   null )) end_date3,
 10         max(decode( rownum, 1, start_date, null )) start_date4,
 11         max(decode( rownum, 1, end_date,   null )) end_date4
 12    from ( select *
 13             from t2
 14            where start_date < sysdate
 15            order by start_date desc
 16         ) t2
 17   where rownum <= 4
 18  /

View created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select cust_no,
  2         sum(case when date_claimed between start_date1 and end_date1
  3         then revenue else 0 end) q4_ago,
  4         sum(case when date_claimed between start_date2 and end_date2
  5         then revenue else 0 end) q3_ago,
  6         sum(case when date_claimed between start_date3 and end_date3
  7         then revenue else 0 end) q2_ago,
  8         sum(case when date_claimed between start_date4 and end_date4
  9         then revenue else 0 end) last_q_ago,
 10         sum(case when date_claimed between start_date1 and end_date4
 11         then revenue else 0 end) todate
 12    from t1, recent_qtrs
 13   where t1.date_claimed between ( select start_date1 from recent_qtrs )
 14                             and ( select end_date4 from recent_qtrs )
 15   group by cust_no
 16  /

   CUST_NO     Q4_AGO     Q3_AGO     Q2_AGO LAST_Q_AGO     TODATE
---------- ---------- ---------- ---------- ---------- ----------
         1    2572833    2481882    2432996     767691    8255402
         2    2436729    2473464    2288742     775810    7974745
         3    2393907    2474900    2323798     845330    8037935
         4    2451633    2465050    2144229     710270    7771182

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

There you go, the view recent qtrs will change over time to reflect the last 4
q's.
Review
      & Followup

      Rating: 4
Very good, but
      one peccadillo....  May 01, 2002
Reviewer:
      Art
Tom, nicely done, and not even a need to dip into the analytic functions!  One
small thing though....

The total I need to show (the column you labeled TODATE) needs to be the total
revenue per customer irrespective of time.  In other words, this sum needs to be
_not_ the sum of Q4_AGO, Q3_AGO, Q2_AGO and LAST_Q_AGO; rather, all revenue ever
generated for that customer.  As such, I'm running into trouble, because then
wouldn't failing to restrict t1.date_claimed by data from recent_qtrs result in
a Cartesian join?

As always, Tom, your clear help is much appreciated.  Thank you.

Art

          Followup:

The the query would simply be:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select cust_no,
  2         sum(case when date_claimed between start_date1 and end_date1
  3         then revenue else 0 end) q4_ago,
  4         sum(case when date_claimed between start_date2 and end_date2
  5         then revenue else 0 end) q3_ago,
  6         sum(case when date_claimed between start_date3 and end_date3
  7         then revenue else 0 end) q2_ago,
  8         sum(case when date_claimed between start_date4 and end_date4
  9         then revenue else 0 end) last_q_ago,
 10         sum(revenue)
 11         todate
 12    from t1, recent_qtrs
 13
 14
 15   group by cust_no
 16  /

and that is is.  full table scan, add up the 4 qtrs -- add ALL revenue in the
last column and group by cust_no.

      Rating: 3
Dynamically
      assign column names in SQL*Plus  May 01, 2002

Reviewer:  Andrew  from ca, usa
SQL> set verify off
-- simple example of dynamically setting column name
-- grab sysdate into a variable called The_Date
SQL> column My_Date new_val The_Date noprint
SQL> select sysdate My_Date from dual;

-- Use this variable as a column heading in the next query
SQL> column revenue format 99999999 heading &The_Date
SQL> select revenue from t1 where rownum < 2;

01-MAY-02
---------
     2244

-- so extending this to the example...
SQL> column start_date1 new_val v_q4_ago noprint
SQL> column start_date2 new_val v_q3_ago noprint
SQL> column start_date3 new_val v_q2_ago noprint
SQL> column start_date4 new_val v_q1_ago noprint
SQL> select to_char(start_date1, 'Q')||'Q'||to_char(start_date1, 'YY')
start_date1,
  2  to_char(start_date2, 'Q')||'Q'||to_char(start_date2, 'YY') start_date2,
  3  to_char(start_date3, 'Q')||'Q'||to_char(start_date3, 'YY') start_date3,
  4  to_char(start_date4, 'Q')||'Q'||to_char(start_date4, 'YY') start_date4
  5  from recent_qtrs;

SQL> column q4_ago heading &v_q4_ago
SQL> column q3_ago heading &v_q3_ago
SQL> column q2_ago heading &v_q2_ago
SQL> column last_q_ago heading &v_q1_ago
SQL> select cust_no,
  2        sum(case when date_claimed between start_date1 and end_date1
  3        then revenue else 0 end) q4_ago,
  4        sum(case when date_claimed between start_date2 and end_date2
  5        then revenue else 0 end) q3_ago,
  6        sum(case when date_claimed between start_date3 and end_date3
  7        then revenue else 0 end) q2_ago,
  8        sum(case when date_claimed between start_date4 and end_date4
  9        then revenue else 0 end) last_q_ago,
 10        sum(revenue)
 11        todate
 12   from t1, recent_qtrs
 13  group by cust_no;

   CUST_NO       2Q01       3Q01       4Q01       1Q02     TODATE
---------- ---------- ---------- ---------- ---------- ----------
         1    2645215    2591672    2955139    2561960   32161036
         2    2523765    2683104    2554243    2748321   31985326
         3    2722713    2729052    2636726    2676797   32197917
         4    2636179    2591297    2767145    2379181   32139968

SQL>

      Rating: 5
Pivot query
      with a self join  January 07, 2003
Reviewer:
      Steve  from NYC, USA
Hi Tom,

I read your Expert 1-to-1 and understand the pivot template in the book. But I
have no idea how to use self join /or subquery as part of partition clause.

I have a table weekly_sale(dept_no, transact_date, sales)
all the  transact_date are the date of the saturday.
I'd like to get a report like
dept_no  transact_date  ly_transact_date sales ly_sales
------     -----------     -----------    ----   ------

where ly_transact_date is last year's same fiscal week (e.g. week 52) I have a
table call bn_period (transact_date,year,month,date_of_wk,fiscal_yr,fiscal_wk)
ly_transact_date is got by following self join or subquery

select p.transact_date
from bn_period p, bn_period p1
where p.fiscal_yr||p.fiscal_wk||p.dow = p1.fiscal_yr-1 ||p1.fiscal_wk||6
and trunc(p1.transact_date) = some_transAct_date

or sub-query:
select transact_date as ly_from_date
from   bn_period
where  fiscal_yr||fiscal_wk||dow =(
                                   select fiscal_yr-1||fiscal_wk||6
                                   from bn_period
                                   where transact_date = 'some_transact_date'
                                   );

By the way, what is the peformance preferense regarding the self join and
subquery?

Thanks!

Steve

          Followup:

time to move on to the chapter on analytic functions!  no self join, LAG() and
LEAD() rock and roll...

ops$tkyte@ORA920> create table weekly_sale( deptno int, transact_date date,
sales number );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into weekly_sale values( 10, to_date( '05-jan-2002' ),
1000 );

1 row created.

ops$tkyte@ORA920> insert into weekly_sale values( 10, to_date( '04-jan-2003' ),
1100 );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2      for x in ( select * from weekly_sale )
  3      loop
  4          for i in 1 .. 50
  5          loop
  6              insert into weekly_sale values( 10, x.transact_date+7*i,
x.sales+i );
  7          end loop;
  8      end loop;
  9  end;
 10  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select *
  2    from (
  3  select deptno,
  4         transact_date,
  5         lag(transact_date) over (partition by week order by transact_date)
ly_trans_date,
  6         sales,
  7         lag(sales) over (partition by week order by transact_date) ly_sales
  8    from ( select deptno,
  9                  transact_date,
 10                  to_char(transact_date,'IW') week,
 11                  sales
 12             from weekly_sale
 13            where transact_date >= add_months(trunc(sysdate,'Y'),-12)
 14              and transact_date < add_months(trunc(sysdate,'Y'), 12)
 15         )
 16  )
 17   where ly_trans_date is not null
 18  /

    DEPTNO TRANSACT_ LY_TRANS_      SALES   LY_SALES
---------- --------- --------- ---------- ----------
        10 04-JAN-03 05-JAN-02       1100       1000
        10 11-JAN-03 12-JAN-02       1101       1001
        10 18-JAN-03 19-JAN-02       1102       1002
        10 25-JAN-03 26-JAN-02       1103       1003
        10 01-FEB-03 02-FEB-02       1104       1004
        10 08-FEB-03 09-FEB-02       1105       1005

use whatever function you want to compute "fiscal_wk"

      Rating: 5
Pivot query
      with a self join   January 08, 2003
Reviewer:
      Steve  from NYC, USA
Hi tom,

Many thanks for your answer!
Before I got your solution. I came up with the following
sql. I also use the fiscal_wk as partition key. (in real world I need add more
fields and tables)

select merch_div_num,
              dept_num,
              dept_name,
              subject_code,
              subject_desc,
              transact_date,
                max(decode(rn,1, sale_qty,0)) ty_sale_qty,
                max(decode(rn,1, sale_amt,0)) ty_sale_amt,
                max(decode(rn,1, sale_ext_amt,0)) ty_sale_ext_amt,
                max(decode(rn,2, sale_qty,0)) ly_sale_qty,
                max(decode(rn,2, sale_amt,0)) ly_sale_amt,
                max(decode(rn,2, sale_ext_amt,0)) ly_sale_ext_amt
    From
      (select  w.merch_div_num merch_div_num,
              i.dept_num,
              d.dept_name,
              i.subject_code,
              s.subject_desc,
              w.transact_date,
              sum(w.sale_qty) as sale_qty,
              sum(w.sale_amt) as sale_amt,
              sum(w.sale_qty * i.retail_amt) as sale_ext_amt,
              p.fiscal_wk,
              row_number() over (partition by w.merch_div_num, i.dept_num,
d.dept_name, i.subject_code, s.subject_desc,p.fiscal_wk
                                             order by p.fiscal_yr desc  ) rn
       from
              bn_wkly_sales_item w,
              bn_item i,
              bn_period p,
              bn_department d,
              bn_subject s
       where  w.sysid = i.sysid
       and    i.dept_num = d.dept_num
       and    w.transact_date = p.transact_date
       and    i.subject_code = s.subject_code
       group by w.merch_div_num, i.dept_num, d.dept_name, i.subject_code,
s.subject_desc,w.transact_date, p.fiscal_wk,p.fiscal_yr
       ) t
         group by t.merch_div_num, t.dept_num, t.dept_name, t.subject_code,
t.subject_desc,t.transact_date

But the problem is that when ty_sale_qty, ty_sale_amt have data, the ly_sale_qty
and ly_sale_amt are 0 and vice verse
Could you tell me what is the problem?

I'll try your way because You solution is much simple than mine.

Thanks!

Steve

      Rating: 5
pivot query
      with a self join  January 20, 2003
Reviewer:
      steve  from nyc, usa
Hi Tom,

Thank you for your great solution. I was a little bit confuse about how analytic
function works. In the following sql, the clause 'partition by week order by
transact_date' will group data with the same week

transact_date   sales    ...
---------       ----
05-JAN-02       1000
04-JAN-03       1100
....

How does Lag() will pick up the the previous one in stead of next one? and if I
use 'by week order by transact_date desc', the result is wrong.
Can you explain to me?

Thanks

Steve

select *
    from (
          select deptno,
           transact_date,
           lag(transact_date) over (partition by week order by transact_date)
ly_trans_date,
           sales,
           lag(sales) over (partition by week order by transact_date) ly_sales
      from ( select deptno,
                    transact_date,
                   to_char(transact_date,'IW') week,
                   sales
              from weekly_sale
             where transact_date >= add_months(trunc(sysdate,'Y'),-12)
               and transact_date < add_months(trunc(sysdate,'Y'), 12)
          )
  )
    where ly_trans_date is not null

      Rating: 5
anyway to make
      it dynamic  May 22, 2003
Reviewer:
      A reader
Hi

the query in your answer is great, I just wonder if it's possible to make it
dynamic for example instead of 4 quarters I want of first 5 months :-?
Do we have to use a temporary table for that.. Or any user defined datatype?

          Followup:

you can dyanmically build any query you want with ref cursors and or dbms_sql.

the key is understanding what query to build (thats here).  building a dynamic
query after you have that is pretty easy.

May 23, 2003 Scott -- Thanks for the question regarding "basic
      decision on sql query performance", version 8.1.7

      You Asked
G'day Tom

To get to a really basic example of what I'm asking, consider this statement:
select ename from bonus
where sal = nvl(:v, sal)

Forgetting any reason why you'd want to do this, would it be preferable to have
(for performance reasons)
a) two statements, one used if :v is null, the other if not null
b) build a ref cursor
c) leave as is
d) something else...

For a more definitive example, consider a customer table with an acc_nbr as
primary key, which provides the following execution plan
select count(*) from customers
where acc_nbr = nvl(:v,acc_nbr)
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=137 Card=1 Bytes=7)
   1    0   SORT (AGGREGATE)
   2    1     CONCATENATION
   3    2       FILTER
   4    3         INDEX (FAST FULL SCAN) OF 'CUST_PK' (UNIQUE) (Cost
   5    2       FILTER
   6    5         INDEX (UNIQUE SCAN) OF 'CUST_PK' (UNIQUE) (Cost=2

In essence, I guess I'm also trying to understand exactly what the concatenation
(and filter) in the explain plan means.

Thanks.

      and we said...
It is an interesting example and shows how much smarter the CBO is than the RBO.
 Reason 2134 to use the CBO.

consider this example:

create table t ( acc_nbr number, data char(255), constraint t_pk primary
key(acc_nbr) );

insert into t
select rownum, 'x' from all_objects;

analyze table t compute statistics
for table
for all indexes
for all indexed columns;

alter session set sql_trace=true;
exec :n := null
select count(*)
  from t n_is_null_cbo
 where acc_nbr = nvl(:n,acc_nbr)
/
select /*+ RULE */ count(*)
  from t n_is_null_rbo
 where acc_nbr = nvl(:n,acc_nbr)
/
exec :n := 55
select count(*)
  from t n_is_55_cbo
 where acc_nbr = nvl(:n,acc_nbr)
/
select /*+ RULE */ count(*)
  from t n_is_55_rbo
 where acc_nbr = nvl(:n,acc_nbr)
/

Now, tkprof shows us:

select count(*) from t n_is_null_cbo where acc_nbr = nvl(:n,acc_nbr)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.05       0.04          0         45          4           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.05       0.04          0         45          4           1

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   SORT (AGGREGATE)
  24098    CONCATENATION
  24098     FILTER
  24098      INDEX   GOAL: ANALYZED (FAST FULL SCAN) OF 'T_PK' (UNIQUE)
      0     FILTER
      0      INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF 'T_PK' (UNIQUE)
********************************************************************************
select /*+ RULE */ count(*) from t n_is_null_rbo where acc_nbr = nvl(:n,acc_nbr)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.06       0.06          0        895          5           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.06       0.06          0        895          5           1

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: 605  (OPS$TKYTE)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: HINT: RULE
      0   SORT (AGGREGATE)
      0    TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'T'
********************************************************************************
select count(*) from t n_is_55_cbo where acc_nbr = nvl(:n,acc_nbr)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          2          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.00          0          2          0           1

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   SORT (AGGREGATE)
      1    CONCATENATION
      0     FILTER
      0      INDEX   GOAL: ANALYZED (FAST FULL SCAN) OF 'T_PK' (UNIQUE)
      1     FILTER
      1      INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF 'T_PK' (UNIQUE)
********************************************************************************
select /*+ RULE */ count(*) from t n_is_55_rbo where acc_nbr = nvl(:n,acc_nbr)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.05       0.05          0        895          5           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.05       0.05          0        895          5           1

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: 605  (OPS$TKYTE)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: HINT: RULE
      0   SORT (AGGREGATE)
      0    TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'T'

see -- the CBO did our work for us -- it rewrote the query effectively as:

select sum(cnt)
  from ( select count(*) cnt from t where acc_nbr = :n
         union all
         select count(*) from t where :n is null )

and the query execution engine -- upon detecting the nullness (or not) of :n
would execute only one or the other query in the union all.  It realized that if
:n is NULL -- acc_nbr = :n is never true -- don't even bother with that -- just
index fast full scan and count.  If :n is not null -- then the first query would
run and the second would never go.....

The RBO on the other hand, it doesn't even realize that it can use the index at
all for this query (or count(*)'s in general).

So, as long as you see a plan like that -- a single query is all you need.  the
optimizer has already in effect rewritten it as two queries for you.
Review &
      Followup

      Rating:
      5
  January 30, 2003
Reviewer:
      Mirjana  from Barcelona

      Rating: 4
what is cost
      based about it  January 30, 2003
Reviewer:
      Mikito Harakiri
select --+no_expand
* from hz_parties p
where party_id = nvl(:1, party_id)

cost 31

select --+use_concat
* from hz_parties p
where party_id = nvl(:1, party_id)

cost 33

The decision to use concatenated plan is not cost-based (even though it made by
CBO, while RBO apparently doesn't implement this heuristic).

          Followup:

what is your point?

can you get the RBO to use this optimization (no)
does the CBO use this plan of attack naturally (yes)

I don't get what you are "saying" here.  If you do not use CBO, no special
optimization that obviates the need for two cursors.  If you use the CBO --
special plan is generated.

That is what is "cost based about it".  use the CBO and you can get it.  don't
use the CBO and you won't.

hz_parties is a wicked VIEW -- there is alot more then meets the eye there.
If you look under the hood -- you might discover that the CBO is still doing the
right thing.

      Rating:
      4
correction  January 30, 2003
Reviewer:
      Mikito Harakiri
Let it be

alter session set OPTIMIZER_DYNAMIC_SAMPLING = 1;
select --+use_concat
       --no_expand
* from obj$
where obj# = nvl(:1, obj# )

then. Concatenated plan costs 19 while full table scan is 17. Since execution
doesn't really follow both branches (as I was happy to learn from your
explanantion, btw), optimizer should cost concatenation somewhere between 2
(table access by index) and 17 (full table scan), so that concatenated plan
would be indeed cheaper than FTS. I suggest filing a bug.

There are many similar cases of cost "ariphmetics" that make me uncomfortable.
Although, RBO was rudimentary, it was much easier to undestand.

          Followup:

I'm still not getting your point Mikito

Is it that you don't understand or fear the CBO

Or is it that you don't "what is cost based about it"

you set dynamic sampling to 1, you must have had an unindexed, unanalyzed table
in your query.  we sampled it.  That changed the cost and the plan -- AS IT IS
SUPPOSED TO (else -- why bother sampling eh?)

Sure, RBO -- easy to understand, 13 simple rules to live by.  It was simplier
before electricity, indoor plumbing, air conditioning as well -- I wouldn't go
back though.

I suggest if you think it is a bug -- you file it?  When you use hints, all bets
are off -- you don't *know* how they work internally.   You don't know what they
do to play with the costs (hey -- we are back to that "you cannot compare costs
across queries", "you cannot derive any sort of relationship between COST and
RUNTIME" discussion aren't we??)

      Rating: 5
How much can
      CBO handle?  January 30, 2003
Reviewer:
      Jerry  from Boston
I think the CBO has some issues in handling extreme complex queries.  I had a
query with several view joining together, some view with complicated table joins
and computations.  There are also union all in the query.  In total, about ten
tables are used.  Sometimes, a slight change in one of the underlying table data
could cause the execution plan to change from hash joins to nested loops.  The
query result changed from a couple of seconds to hours, sometime even hanging.
I could not use hint or outln to stabilize the execution plan.  The query is
used in a software product where data could be very different from user to user.
 I am not sure one execution plan will work well in all environments. (The
queries were all run immediately after stats were refreshed)

I looked at the execution plans.  Oracle gave a high cost to the faster plan.

Based on your experience, how much can CBO handle?  Should queries be restricted
to less than five or six table joins?  Should I use PL/SQL instead?

          Followup:

In my experience

o if you have correct statistics
o if you have checked your optimizer_index_* parameters

thats it, CBO hands down -- for the most complex (in fact, the more complex --
the better).  See

http://groups.google.com/groups?selm=a1g2kv02r69%40drn.newsguy.com&oe=UTF-8&output=gplain
for my classic example of that.  Some of the tables referenced in here have 16
million + rows.  There are outer joins, exists, unions, subqueries out the
whazoo, calls to custom PLSQL functions for timezones and other things, queries
on queries on queries and probably more than 16 tables (never counted them).

it was written for 815 (thats why the custom TZ handling)

      Rating:
      5
  January 31, 2003
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating: 5
But what's
      going on here?  May 12, 2003
Reviewer:
      Basil
In the following scenario, why isn't the CBO realizing that object_name =
NVL(:p_name, object_name) is really the dominant predicate in this query? For 64
rows out of 766,000, the scan seems out of line:

SQL> create table t as select object_name, object_type from all_objects;

Table created.

SQL> insert into t select * from t;

23949 rows created.

SQL> insert into t select * from t;

47898 rows created.

SQL> insert into t select * from t;

95796 rows created.

SQL> insert into t select * from t;

191592 rows created.

SQL> insert into t select * from t;

383184 rows created.

SQL> commit;

Commit complete.

SQL> create index t_name_idx on t(object_name);

Index created.

SQL> create index t_type_idx on t(object_type)
  2  ;

Index created.

SQL> analyze index t_name_idx  compute statistics;

Index analyzed.

SQL> analyze index t_type_idx compute statistics;

Index analyzed.

SQL>
SQL> set autotrace traceonly explain statistics
SQL> variable p_name varchar2(50);
SQL> exec :p_name := 'ALL_IND_COLUMNS';

PL/SQL procedure successfully completed.

SQL> variable p_type varchar2(50);
SQL> exec :p_type := NULL;

PL/SQL procedure successfully completed.

SQL> select object_name, object_type from t
  2  where object_name like NVL(:p_name, object_name)
  3  and object_type like NVL(:p_type, object_type);

64 rows selected.

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

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       4171  consistent gets
          0  physical reads
          0  redo size
       1723  bytes sent via SQL*Net to client
        547  bytes received via SQL*Net from client
          6  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         64  rows processed

          Followup:

You'd have to ask the RBO why -- you are not using the CBO.

*analyze the table*

      Rating: 5
Another
      doh  May 13, 2003
Reviewer:  Basil

My production system DOES analyze the table prior to manipulatng it. But here's
the results from this test:

SQL> exec dbms_stats.gather_table_stats(ownname=>USER,tabname=>'t',method_opt=>'
for all indexed columns size auto', cascade=>true);

PL/SQL procedure successfully completed.

SQL> set autotrace traceonly explain statistics
SQL> variable p_name varchar2(50);
SQL> exec :p_name := 'ALL_IND_COLUMNS';

PL/SQL procedure successfully completed.

SQL> variable p_type varchar2(50);
SQL>  exec :p_type := NULL;

PL/SQL procedure successfully completed.

SQL> select object_name, object_type from t
  2  where object_name like NVL(:p_name, object_name)
  3  and object_type like NVL(:p_type, object_type);

64 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=509 Card=3832 Bytes=
          137952)

   1    0   CONCATENATION
   2    1     FILTER
   3    2       TABLE ACCESS (FULL) OF 'T' (Cost=254 Card=1916 Bytes=6
          8976)

   4    1     FILTER
   5    4       TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=254 Card=19
          16 Bytes=68976)

   6    5         INDEX (RANGE SCAN) OF 'T_TYPE_IDX' (NON-UNIQUE) (Cos
          t=104 Card=36495)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        162  consistent gets
          1  physical reads
          0  redo size
       1723  bytes sent via SQL*Net to client
        547  bytes received via SQL*Net from client
          6  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         64  rows processed

SQL>

          Followup:

yes, so, did you read the example above?

When the bind variable is NULL -- Oracle will (has to) do the full table scan
portion of the query.

When the bind variable IS NOT NULL -- Oracle will (can) do the index range scan.

Your bind variable is null, I would expect it to read the entire table.

Use TKPROF as I did.
Run the query with NULL and with a value.
You'll see the results as I have them above.

      Rating:
      5
Surprised  May 21, 2003
Reviewer:
      Basil
I'm only surprised that Oracle is still doing the full scan, when one of the
predicates (the one with the nonnull input) serves as the driving limiter on the
query.

If I'm just being dense, I'll take my lumps.

          Followup:

when the input is NON-NULL, Oracle does not do the full scan.

when the input is NULL, Oracle does the full scan.

where is the problem with that?  Please be more clear.

      Rating: 5
Two values at
      work  May 21, 2003
Reviewer:  Basil

There are two tests in the predicate, one against object_name/p_name and one
against object_type/p_type.

The predicate is ANDing the two tests:
WHERE object_name = NVL(:p_name, object_name)
AND object_type = NVL(:p_type,object_type)

In the case I gave, p_type is NULL, p_name is NOT.

In an AND situation like this one, if the query can reduce to an index scan for
one of these (which it CAN for the object_name/p_name test), that serves as a
limiting condition for the query. There will NEVER be rows found by table
scanning to test object_type/p_type that are NOT returned by the index test for
object_name/p_name. Put another way, the rows from the index test for
object_name/p_name will always be a superset (or an exact set) of all the rows
the query could return.

Statistics on the table should reflect that a mere 64 out of 760,000 rows fit
the object_name/p_name restriction.

It seems to me that the total of (logical reads for object_name/p_name + logical
reads to test object_type/p_type on the rows from the index scan) is quite a bit
less than (full table scan logical reads) for a table with those sorts of
statistics.

Again, I'm just trying to understand what's happening here. I apologize if I'm
missing something completely obvious.

          Followup:

Oh i see -- the forest was hidden for the trees.

I guess it just isn't "smart enough" to come up with all 4 plans

p_name null and p_type null
p_name not null and p_type null
p_name null and p_type not null
p_name not null and p_type not null

you see, it quickly explodes as you add predicates.  it works for the single
predicate.

      Rating:
      1
???  May 22, 2003
Reviewer:
      A reader
sorry, tom, but I didn't get your answer at all ...

          Followup:

with 1 column -- the optimizer will develop "both" plans in a single plan as
demonstrated above.

with 2 columns -- it does NOT develop the 4 necessary plans (with 3 the 9 and so
on).

the plan is a static object that is built at hard parse time, it is invariant
from then on in -- regardless of the bind variable values at run time.  The
query plan would literally "explode" in size if it did this for 2, 3, 4, ... n
columns.

      Rating: 5
Just for the
      record  May 23, 2003
Reviewer:  Basil

...what I was interested in trying to do is something like:

PROCEDURE search_proc( p_name varchar2, p_type varchar2, p_search3 varchar2,
etc...)
RETURN REF CURSOR
IS
  mycur REF CURSOR;
BEGIN
  OPEN mycur FOR
  select object_name, object_type from t
  where object_name like NVL(p_name, object_name)
  and object_type like NVL(p_type, object_type)
  and something_else like NVL(p_search3, something_else)
  ...;
END search_proc;
/

The idea being a general search procedure for a web front-end that will specify
0 or more search conditions. (A NULL indicating that the condition doesn't
matter.) The problem I get is that the query plan is too ugly. It's always table
scanning, even when the limiting condition on the query (the non-null
conditions) could make use of an index (it's a big table, WITH statistics).

Right now I'm planning to switch to a dynamically-constructed query (that
includes only the non-null conditions) in conjunction with
dbms_session.set_context() and sys_context(). Kind of a pain, but it performs
significantly better in my tests.

May 23, 2003 anand -- Thanks for the question regarding
      "materialized view problem while refreshing ", version 8.1.7

      You Asked
Hi
 We have have an ORACLE 8.1.7 database on suse linux 7.2 and we  have a
materialized view with joins and created a primary  key constraint on the mview.

 The refresh mode and refresh type of the created mview is

  refresh fast on demand.

  I have not enabled the query rewrite and by default query rewrite integrity is
enforced in the parameter file.

 Some times while refreshing the materialized view it throws a

   error unique constraint violated.

Since this is in production database I have taken the following  action as
follows :

Dropped the constraint and then I refreshed the materialized view it went
successfully and

Then I recreated the primary key constraint but I was shocked that I didn't
throw any error.

Please let me know any parameter has to set in the parameter file for query
rewrite integrity.

Note: Suppose if you alter the constraint with enable novalidate then it solves
my problem or

Is there impact on the indexes and also with respect to refresh?

Thanks and Regards

anand ts

      and we said...
did you create the primary key constraint as DEFERABLE?

If not, then you caused this issue.

During a refresh, the data can go temporarily "out of constency".  When we are
done -- it'll be consistent.

A refresh will issue "set contraints all deferrable" before it begins --
allowing this temporary inconsistency to take place and upon commit the data
will be consistent.

Note that no other session other then the refresh session itself will ever see
"duplicate" data.

Review &
      Followup

      Rating: 4
meterialized
      view refresh  November 10, 2002
Reviewer:
      Sreeju  from Cincinnati OH USA
Thanks for your valuable comments again and again. I have a problem with
materialized view refresh. Have created a single table aggregate materialized
view as shown below. In temp detail there are around 10 million records. There
are cases where 100,000 records in the log(updates in detail table). When doing
a fast refresh, it takes so long (more than 12 hours). But when i recreate the
summary snapshot it takes only less than hour. There are indexes on all the
seq_id columns (both in the detail and summary). Is there any way out to reduce
the refresh time of fast refresh ?

Kindly request your expert advise ;

CREATE MATERIALIZED VIEW LOG ON TEMP_DETAIL
WITH ROWID
(  CALENDAR_SEQ_ID
  ,MODEL_SEQ_ID
  ,SHOP_SEQ_ID
  ,MODULE_SEQ_ID
  ,CUST_SEQ_ID
  ,ORDER_NUM
  ,WEEK_SEQ_ID
  ,ACTUAL_HOURS
  ) INCLUDING NEW VALUES;

CREATE MATERIALIZED VIEW TEMP_SUMMARY
BUILD IMMEDIATE
REFRESH FAST
AS SELECT
   CALENDAR_SEQ_ID
  ,MODEL_SEQ_ID
  ,SHOP_SEQ_ID
  ,MODULE_SEQ_ID
  ,CUST_SEQ_ID
  ,ORDER_NUM
  ,WEEK_SEQ_ID
  ,SUM(ACTUAL_HOURS) as ACTUAL_HOURS
  ,COUNT(*) as COUNT_GRP
  ,COUNT(ACTUAL_HOURS) as  count_ACT
   FROM TEST.TEMP_DETAIL
   GROUP BY
   CALENDAR_SEQ_ID
  ,MODEL_SEQ_ID
  ,SHOP_SEQ_ID
  ,MODULE_SEQ_ID
  ,CUST_SEQ_ID
  ,ORDER_NUM
  ,WEEK_SEQ_ID

          Followup:

then it sounds like doing a complete refresh in your case is better then a
changes only refresh.

If you are interested, you can update a couple of detail records, commit, enable
sql trace and do the changes only refresh.  You'll be able to use tkprof to
format the trace file and SEE the sql that is needed in order to maintain this.
Multiply that sql by 100,000 and you'll see why it takes that long.

Consider this -- suppose it takes a little under 0.5 seconds to process a single
row refresh (I see sum's and counts and such - you are aggregating many records
from the details into one for the MV).  Well, 100,000 row updates at 0.5 seconds
would be a little over 12 hours.

Maybe you need to

a) refresh more often so as to not build up such a large backlog
b) use complete refreshes (they are not evil, they are an option)

      Rating:
      5
  November 11, 2002
Reviewer:  Dave
       from Ohio
Would I be right in inferring from this "deferrable constraints" tip that we
should avoid the use of index-organized tables (where PK is non-deferrable) in
materialized views?

Can you give more information on the situations in which the PK might have to be
deferrable in order to avoid the unique contraint violation? I'm guessing that
it might be something that could occur on fast refreshes, maybe not so on
complete refreshes. Maybe I could stick with my IOT/MV's as long as I avoid
particular MV features?

          Followup:

in the case of a fast refresh (changes only) that might be the case -- yes.  a
complete refresh -- no problem.

It would be very rare for a primary key to cause the issue (in fact, I cannot
think of a case offhand where it would) -- a unique constraint, yes -- but not a
primary key, since the primary key is what we use to apply the changes by in the
first place.

      Rating: 2
Create
      deferrable PK contraint on materialized view?  May 23, 2003

Reviewer:  Dave  from Sydney Australia
Tom,

I've looked for syntax on this but have been unable to find it & needless to say
when I add a unique index on the materialized view, the corresponding constraint
created gets violated during refresh. Can you please show me an example of
creating a deferrable PK constraint on an mview?

Also, am considering fast or complete refresh for a materialized view with
approx 3 million rows in it. Due to not satisfying the oracle 'rules' for a fast
refresh, I  may be forced to use a complete refresh - have no problem with using
the complete refresh as it removes reliance on the these 'rules' allowing more
flexibility in the underlying sql statement - it just seems the complete refresh
is lot less elegant given the complete refresh essentially truncates then
rebuilds the whole mview & indexes (most of which are bitmap). Total rebuild
time for this mview is approx 2hr which I can do overnight in a batch job? Is it
acceptable practice to use the complete refresh?

          Followup:

you cannot put unique indexes on MVs, only "indexes".

the MV already has a primary key constraint and that won't be violated.

I would need to see a small (tiny) example of your issue unless I've missed the
point.

with bitmaps, a complete refresh would probably be preferrable.  bitmap indexes
despise row by row operations.

      Rating:
      4
  May 23, 2003
Reviewer:  Victor
       from MD USA

May 23, 2003 Sateesh -- Thanks for the question regarding "OCI
      or Precompiler", version 8.1.5

      You Asked
Hi Tom,
  I was just going through the OCI manual to know what exactly
is OCI. What i understood after going through the first chapter
is now we can use OCI instead of using Precompilers. If i have a
Pro*C program, would it be better to change to OCI. What are the
advantages of doing so?

Thanks
Sateesh

      and we said...
There is, in my opinion, nothing to be gained from going to OCI
over Pro*C - especially if you already know pro*c and do not
know OCI.

I find pro*c to be infinitely more productive then OCI. I can do
in one or two statements in Pro*c what takes many lines of code
in OCI.

There are very very few things you can do in OCI that you cannot
do in pro*c.  The only thing I've ever encountered that you can
do in OCI but not Pro*C is dynamically bind to a prepared stored
procedure call that takes PLSQL table types.  That is a rare
thing to need (and even then -- you can connect with pro*c and
use OCI when and if you need to rather then for everything)

There are some object features available in OCI that are not as
accessible in Pro*c but in general -- I use pro*c until I cannot
and then use OCI.

It depends on what you are comfortable with.  As of 8i, pro*c is
a layer on top of OCI.  The performance of the two is extremely
comparable.
Review & Followup

      Rating: 3
OCI or
      Precompiler  June 19, 2001
Reviewer:
      roseh.y.  from Brazil

      Rating: 3
OCIStmtPrepare
      actions  May 23, 2003
Reviewer:  Manish
       from India
Hi Tom,

We are an ardent user of OCI for our database application development. Till now
we were under the impression that the syntax and semantic checking of any
statement is done in OCIStmtPrepare call. But if there is a syntax error in the
statement for example:

SELECT t.name, t.sal
  FROM1 emp t;

note that we have written FROM1 instead of FROM in the above example,
OCIStmtPrepare does not give any error. We also observed that the entry of this
statement does not go to the v$sql. That means that the syntax parsing is not
done in the OCIStmtPrepare call.

Our questions are :

1. When does the syntax and semantics check happen in OCI context.

2. What exactly is done in the OCIStmtPrepare call.

Thanks
Manish

          Followup:

it (oci) is bundling as much stuff as it can to reduce (remove) roundtrips to
and from the server.

from chapter 4 of the OCI guide:

Preparing Statements

SQL and PL/SQL statements need to be prepared for execution by using the
statement prepare call and bind calls (if necessary). In this phase, the
application specifies a SQL or PL/SQL statement and binds associated
placeholders in the statement to data for execution. The client-side library
allocates storage to maintain the statement prepared for execution. An
application requests a SQL or PL/SQL statement to be prepared for execution
using the OCIStmtPrepare() call and passing it a previously allocated statement
handle. This is a completely local call, requiring no round trip to the server.
No association is made at this point between the statement and a particular
server.

May
      23, 2003 Subhash -- Thanks for the
      question regarding "Order by in connect by clause", version 8i

      You Asked
Hi Tom,
 My question is regarding the usage of order by  in connect by clause.

I have table of structure shown below (Table Name : tree_struct)

  parent   varchar2(10)
  child    varchar2(10)
  descrip  varchar2(100)
  serialno number;

Here serialno denotes the serial numbers of each levels

I'll be writing the query like

 Select Lpad(' ',2*level)||descrip from  tree_struct
 connect by prior child=parent
 start with level=1;

Now i want the results ordered by level,serialno

How can i achieve this ? Please suggest a solution.

Regards
Subhash

      and we said...
In Oracle9i, this is easy:

scott@TKYTE901.US.ORACLE.COM> select lpad('*', level, '*' ) || ename ename,
  2         sys_connect_by_path( ename, '/' ) path
  3  from emp
  4  start with mgr is null
  5  connect by prior empno = mgr
  6  order SIBLINGS by ename DESC
  7  /

ENAME                          PATH
------------------------------ ----------------------------------------
*KING                          /KING
**JONES                        /KING/JONES
***SCOTT                       /KING/JONES/SCOTT
****ADAMS                      /KING/JONES/SCOTT/ADAMS
***FORD                        /KING/JONES/FORD
****SMITH                      /KING/JONES/FORD/SMITH
**CLARK                        /KING/CLARK
***MILLER                      /KING/CLARK/MILLER
**BLAKE                        /KING/BLAKE
***WARD                        /KING/BLAKE/WARD
***TURNER                      /KING/BLAKE/TURNER
***MARTIN                      /KING/BLAKE/MARTIN
***JAMES                       /KING/BLAKE/JAMES
***ALLEN                       /KING/BLAKE/ALLEN

14 rows selected.

There is explicit syntax for it.

Prior to that, if you have total control over the indexing scheme -- a
concatenated index on (parent,older_sibling) -- without any other index with
parent on the leading edge -- will probably do it (by accident)

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:9212348049
...

followup to comment one below

Perhaps you did not read the SECOND half of the answer then?  Where I provided a
link that shows how you might be able to do this in 8i and before?

Review &
      Followup

      Rating:
      2
  August 13, 2001
Reviewer:  Sreeja
       from UAE
The answer is not useful becuase only for using order by clause in Select, we
cannot change the database from 8i to 9i

      Rating: 3
Does this work
      for sorting numeric data?  October 16, 2001

Reviewer:  Leo J. Hart IV  from Boston, MA USA

Does this example work for sorting numeric data?  It doesn't appear to.  If you
wanted to sort on a field called order_number which consisted of numbers:
1000
30
203
80
It would return it in this order:
1000
203
30
80
Which is not what we want, right?

I don't have a better solution.  I was trying to follow one of the examples that
used index, but I couldn't get any of those to work either

          Followup:

If you order by a number column, it'll return the data sorted in numeric sort
order.

If you have a STRING column with "numeric looking" data in it -- it'll return it
sorted as STRING data -- if you want it sorted as "numbers" (but then you should
have used a number column in the first place) you can always order by
to_number(that_column) to get it sorted as a number.

      Rating: 3
Does this work
      for sorting numeric data PT- 2?  October 17, 2001

Reviewer:  Leo J. Hart IV  from Boston, MA USA

I appreciate your response.  But in the example you are showing, aren't you
concatenating a bunch of strings together to create a column to set order upon?
My point it that this won't work if a person tries to use your concatenation
technique on values that should be interpretted as numbers.  Once you
concatenate, the numbers can only be interpretted as a string and thus the order
will be different than what's desired.  Am I wrong?  I certainly could be
missing something.

Thanks!

          Followup:

Just order by whatever you want.

ops$tkyte@ORA9I.WORLD> select lpad('*', level, '*' ) || ename ename, sal,
  2          sys_connect_by_path( ename, '/' ) path
  3    from emp
  4   start with mgr is null
  5  connect by prior empno = mgr
  6   order SIBLINGS by SAL DESC
  7  /

ENAME                  SAL PATH
--------------- ---------- -------------------------
*KING                 5000 /KING
**JONES               2975 /KING/JONES
***SCOTT              3000 /KING/JONES/SCOTT
****ADAMS             1100 /KING/JONES/SCOTT/ADAMS
***FORD               3000 /KING/JONES/FORD
****SMITH              800 /KING/JONES/FORD/SMITH
**BLAKE               2850 /KING/BLAKE
***ALLEN              1600 /KING/BLAKE/ALLEN
***TURNER             1500 /KING/BLAKE/TURNER
***WARD               1250 /KING/BLAKE/WARD
***MARTIN             1250 /KING/BLAKE/MARTIN
***JAMES               950 /KING/BLAKE/JAMES
**CLARK               2450 /KING/CLARK
***MILLER             1300 /KING/CLARK/MILLER

14 rows selected.

It'll do it "right" as I said.  in my example, I used the "rpad()" to empphasize
the HIERARCHY.  It is not relevant in the real world.

      Rating:
      3
Sorry!  October 17, 2001
Reviewer:  Leo
      J. Hart IV  from Bosotn, Ma USA
I'm sorry Tom, I was completely mixing this article up with a different article
posted that used concatenation.  Unfortunately, I can't use this method which
WILL work because I don't have 9i and the index method doesn't seem to work
either, I'm guessing because I have an index on the PK as well as the one we're
creating here.  Sorry for the trouble!  I'm not sure how I'll get around my
problem.

      Rating: 2
A
      bug  October 17, 2001
Reviewer:  Mikito
      Harakiri
Well, "order SIBLINGS by" works in trivial cases, but any attempt of doing
anything more sophisticated fails. In the example below the last 2 queries
differ in a line containing "order SIBLINGS by" only, but the result set in the
last case is completely wrong:

SQL> explain plan set statement_id='1'
for select 1 from dual;

Explained.

SQL> explain plan set statement_id='2' for
  2  SELECT lpad(' ',level-1)||operation||' '||options||' '||
  3          object_name "Plan", cardinality
  4     FROM plan_table
  5  CONNECT BY prior id = parent_id
  6          AND prior statement_id = statement_id
  7    START WITH id = 0
  8  ;

Explained.

SQL> SELECT statement_id, lpad(' ',level-1)||operation||' '||options||' '||
  2          object_name "Plan", cost
  3     FROM plan_table
  4  CONNECT BY prior id = parent_id
  5          AND prior statement_id = statement_id
  6    START WITH id = 0
  7  ;

SU Plan                                           COST
-- ---------------------------------------- ----------
1  SELECT STATEMENT
1   TABLE ACCESS FULL DUAL
2  SELECT STATEMENT                                  1
2   CONNECT BY WITH FILTERING
2    NESTED LOOPS
2     TABLE ACCESS FULL PLAN_TABLE                   1
2     TABLE ACCESS BY USER ROWID PLAN_TABLE
2    HASH JOIN
2     CONNECT BY PUMP
2     TABLE ACCESS FULL PLAN_TABLE                   1

10 rows selected.

SQL> SELECT statement_id, lpad(' ',level-1)||operation||' '||options||' '||
  2          object_name "Plan", cost
  3     FROM plan_table
  4  CONNECT BY prior id = parent_id
  5          AND prior statement_id = statement_id
  6    START WITH id = 0
  7    order SIBLINGS by COST DESC
  8  ;

SU Plan                                           COST
-- ---------------------------------------- ----------
1  SELECT STATEMENT
2  SELECT STATEMENT                                  1

          Followup:

I confirmed and reproduced with:

drop table t;

create table t ( id int, pid int, ppid int );

insert into t values ( 1, null, 1);
insert into t values ( 2,    1, 1);

select t.*, level
  from t
 connect by prior id = pid
     and prior ppid = ppid
 start with pid is null
/
select t.*, level
  from t
 connect by prior id = pid
     and prior ppid = ppid
 start with pid is null
 order siblings by pid desc
/

it is now bug 2059318. It requires

o more then one clause in the connect by
o the desc order by
to reproduce.

      Rating:
      5
  April 07, 2003
Reviewer:  Pooja
       from USA
Do not have to read tons of documentation to learn this. Instead, your website
explained that hierarchial orderering is not possible in 8i. May be, we could
achieve it with index or some thing.

I just tried using order by in connect by clause. There was no error when I was
exceuting the sql. I am not sure if order by is really ignored by db. In my
scenario, I happened to get the correct results. I am not sure if it was just
coincidence or if it really works?

          Followup:

it is not ignored, it just destroyed the hierarchy you worked to build is all.

the entire set is sorted, not level by level but the entire set was sorted.

      Rating:
      5
  May 16, 2003
Reviewer:
      A reader
I am running the following query using Oracle 8.1.7

SELECT LEVEL, empno, ename, mgr
  FROM emp   CONNECT BY PRIOR empno = mgr;

How to order by last name within the level?

          Followup:

this is achieved by upgrading to 9i which supports an order siblings by clause.

      Rating:
      4
  May 16, 2003
Reviewer:
      A reader
Thanks for your comments, it is little difficult to upgrade to 9i at this
moment.

Sorting the data at client side is going to be costly. Any dirty trick for
implementing at SQL level using Oracle 8.1.7?

          Followup:

see the link above - part of the original answer.

      Rating: 4
Requirement
        May 22, 2003
Reviewer:  Jignesh Karia
       from India
Please go throught the script :

CREATE TABLE MY_LEVEL1 (
  ORIG_RECP  VARCHAR2 (10),
  ORIG_AMT   NUMBER,
  REF_RECP   VARCHAR2 (10),
  REF_AMT    NUMBER,
  PARTY      VARCHAR2 (6) ) ;

INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'100', 10000, NULL, 500, 'A0001');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'110', 300, '100', 100, 'A0001');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'120', 200, '110', 50, 'A0001');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'130', 100, '120', 30, 'A0001');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'300', 10000, NULL, 500, 'A0001');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'310', 300, '300', 100, 'A0001');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'320', 200, '310', 50, 'A0001');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'330', 100, '320', 30, 'A0001');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'100', 10000, NULL, 500, 'A0003');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'110', 300, '100', 100, 'A0003');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'120', 200, '110', 50, 'A0003');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'130', 100, '120', 30, 'A0003');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'100', 10000, NULL, 500, 'A0004');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'110', 300, '100', 100, 'A0004');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'120', 200, '110', 50, 'A0004');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'130', 100, '120', 30, 'A0004');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'130', 100, '120', 30, 'A0004');

select  level x,
 party, orig_recp, ref_recp, orig_amt, ref_amt
from my_level1
where party between 'A0001' AND 'A0003'
connect by
prior orig_recp = ref_recp and
prior party = party
start with ref_recp is null

1    A0001    100        10000    500
2    A0001    110    100    300    100
3    A0001    120    110    200    50
4    A0001    130    120    100    30
1    A0001    300        10000    500
2    A0001    310    300    300    100
3    A0001    320    310    200    50
4    A0001    330    320    100    30
1    A0003    100        10000    500
2    A0003    110    100    300    100
3    A0003    120    110    200    50
4    A0003    130    120    100    30

Clients desired output :

1    A0001    100        10000    500
4    A0001    130    120    100    30
1    A0001    300        10000    500
4    A0001    330    320    100    30
1    A0003    100        10000    500
4    A0003    130    120    100    30

i.e. clients requirement is that they want first and last row per level.  We
have tried a lot but not got any appropriate solution for this.

Please provide some solution for above query asap.

          Followup:

well, if the hierarchy is always 4 levels deep, it is pretty simple -- where
level in (1,4)

      Rating: 2
Thanks for your
      reply but...  May 23, 2003
Reviewer:  Jignesh
      Karia  from India
Sorry for not giving proper explanation for my query.

In this query, it will all be dependent on user.  The business functionality is,
the user is booking one bill, against he gets payment in parts, every time of
part payment new receipt will be generated and the reference receipt will be
updated.  Now in this case the level are unknown.  I.e. the user can get the
payment in full first time or it may go to n number of times.

Now the requirement is that the user is looking for the first voucher and the
last voucher in which he will get the original receipt and the balance amount in
last voucher.

I hope you will help me out in this case

Thanks in advance.

          Followup:

ps$tkyte@ORA920> select party, orig_recp, ref_recp, orig_amt, ref_amt,
  2         rtrim(substr( data, 6, 10 )) orig_recp_2,
  3         rtrim(substr( data, 16, 10 )) ref_recp_2,
  4         to_number(substr( data, 26, 14 )) orig_amt_2,
  5         to_number(substr( data, 40, 14 )) ref_amt_2
  6    from (
  7  select party, orig_recp, ref_recp, orig_amt, ref_amt,
  8        (select max( to_char(level,'fm00000') ||
  9                     nvl(rpad(orig_recp,10),rpad(' ',10)) ||
 10                     nvl(rpad(ref_recp,10),rpad(' ',10))  ||
 11                     to_char(nvl(orig_amt,0),'0000000000.00') ||
 12                     to_char(nvl(ref_amt,0), '0000000000.00' ) )
 13           from my_level1 t2
 14          start with party = my_level1.party and ref_recp =
my_level1.orig_recp
 15        connect by prior orig_recp = ref_recp and prior party = party ) data
 16    from my_level1
 17   where party between 'A0001' and 'A0003'
 18     and ref_recp is null
 19         )
 20  /

PARTY  ORIG_RECP  REF_RECP     ORIG_AMT    REF_AMT ORIG_RECP_ REF_RECP_2
ORIG_AMT_2  REF_AMT_2
------ ---------- ---------- ---------- ---------- ---------- ----------
---------- ----------
A0001  100                        10000        500 130        120
100         30
A0001  300                        10000        500 330        320
100         30
A0003  100                        10000        500 130        120
100         30

      Rating: 5
Great
      Solution  May 25, 2003
Reviewer:  jignesh
      karia  from India
Thanks a Ton. . .

You are simply greate. .

waiting for your next book. . .

jignesh

May 23, 2003 Rupali -- Thanks for the question regarding "DDL
      related parse, execute, fetch", version 8.1.5

      You Asked
Dear Mr. Tom,

When we issue a DML it undergoes
PARSE
EXECUTE
FETCH   process and the things happen in Database Buffer Cache.

Kindly tell me when we issue a DDL then
PARSE
EXECUTE  is understandable but
what about FETCH?
What it will Fetch or the Fetch will not occur at all?

2nd thing is where that definition will be available or will it
be immediately commited with the implicit commit and sent to
database directly???

Rupali

      and we said...
DDL won't "fetch".  It may generate recursive SQL that does
perform fetches.

DDL is performed in the following way:

o a commit is issued
o the DDL is performed
o a commit is issued

Therefore, the outcome of a DDL command will be immediately
visible to all sessions.

Review &
      Followup

      Rating: 5
can we just
      parse ddl?  May 21, 2003
Reviewer:  Gabriel
       from Montreal, Canada
Hello Tom,
Can you please tell me if there is a way to only parse ddl without executing it.
My need comes from the fact that one of our prod envs has very outdated testing
envs and there is absolutely no time to bring these testing envs in syns with
prod.
I would like to parse long ddl scripts without executing them in the prod
environment. I know that dbms_sql's parse procedure also executes. Is there any
other way?

Thank you very much

          Followup:

parsing ddl executes it, no if and or buts about it.  true in all environments
(not just dbms_sql)

      Rating: 4
Then why the
      distinction  May 21, 2003
Reviewer:  Gabriel
       from Monteral, Canada
If one is the other then why do we have a distinction between the parsing step
and the execution step. True they don't do the same thing but they are the same
thing. Instead of spliting in all the books the ddl execution in two steps
parsing/execution we should have just one, I think.

          Followup:

what book?

      Rating: 4
Quick
      question  May 23, 2003
Reviewer:  Karl
       from Fiji
<quote>
DDL is performed in the following way:

o a commit is issued
o the DDL is performed
o a commit is issued
</quote>

I understand that if session 1 is querying a table, and session 2 tries to alter
it, session 2 will block on a data dictionary lock until session 1's query
completes.

What if session 1 starts a serializable transaction, queries the table, then
session 2 alters the table, then session 1 queries the table again. Will it see
the table alter or not altered, or get "can't serialize this transaction".

Thanks and apologies if you can't understand the question.

          Followup:

ops$tkyte@ORA817DEV> create table t ( x int );
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> set transaction isolation level serializable;
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert into t select rownum from all_users where rownum <
5;
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> variable x refcursor
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> begin
  2    open :x for select * from t;
  3  end;
  4  /
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> declare
  2          pragma autonomous_transaction;
  3  begin
  4          execute immediate 'alter table t add y number default 55';
  5  end;
  6  /
declare
*
ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified
ORA-06512: at line 4

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> print x

         X
----------
         1
         2
         3
         4
ops$tkyte@ORA817DEV>

      Rating: 4
Tried it out
      myself  May 23, 2003
Reviewer:  Karl
       from Fiji
I forgot I could answer my own question!

Check out these interesting results...

==== session 1 ====
SQL> set transaction isolation level serializable;

Transaction set.

SQL> select * from test;

         X          Y          Z
---------- ---------- ----------
         1          2          0
         2          4          0
         3          6          0
         4          8          0

==== session 2 ====
SQL>alter table test add (q number default 1);

Table altered.

==== session 1 ====
SQL> select * from test;

         X          Y          Z          Q
---------- ---------- ---------- ----------
         1          2          0
         2          4          0
         3          6          0
         4          8          0

SQL> commit;

Commit complete.

SQL> select * from test;

         X          Y          Z          Q
---------- ---------- ---------- ----------
         1          2          0          1
         2          4          0          1
         3          6          0          1
         4          8          0          1

So the changes to the table structure in the data dictionary appear in session
1, but the value of the column doesn't appear until after the transaction ends.
Could you explain how this is happening? Thanks Tom.

          Followup:

the above example shows what happens in 8i, in 9i, this is what happens:

ps$tkyte@ORA920> create table t ( x ) as select rownum from all_users where
rownum < 5;

Table created.

ops$tkyte@ORA920> set transaction isolation level serializable;

Transaction set.

ops$tkyte@ORA920> select * from t;

         X
----------
         1
         2
         3
         4

ops$tkyte@ORA920> declare
  2          pragma autonomous_transaction;
  3  begin
  4          execute immediate 'alter table t add y number default 55';
  5  end;
  6  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from t;

         X          Y
---------- ----------
         1
         2
         3
         4

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920> select * from t;

         X          Y
---------- ----------
         1         55
         2         55
         3         55
         4         55

the reason Y is "NULL" until we commit is because the alter table really does an
"update t set y = 55" (sql_trace will show you that).  We cannot see the effects
of updates in a serializable transaction that happened AFTER our transaction
begin -- we have to see dictionary updates (SYS -- recursive -- SQL is never
done in "flashback" style) so we see the column -- but not the values therein

      Rating:
      4
Clarification  May 23, 2003
Reviewer:
      Karl  from Fiji
Sorry Tom, did you make a typo? I am running 9i.

SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.2.0 - 64bit Production
PL/SQL Release 9.2.0.2.0 - Production
CORE    9.2.0.2.0       Production
TNS for HPUX: Version 9.2.0.2.0 - Production
NLSRTL Version 9.2.0.2.0 - Production

What happens in 8i?

          Followup:

what typo?

I have two examples -- one in 8i, one in 9i.

May 23, 2003 Suresh -- Thanks for the question regarding "date
      partitions", version 8.1.6

      You Asked
Hi Tom,

In our data warehousing environment we have a monthly partition table
partitioned on a date field. We use

front end tools where we don't have control of sql generation. My problem is
when user selects date range

oracle only scans that month partition, which is fine. But our users most of the
time they want to select

month, quarter or year then the queries goes for full table scans. Is there any
way we can create a view

which can force to read only that partition.

We have more partitions(around 60) then below example in production env.
exmaple:

Defination of the table.
CREATE TABLE SMART.MUTUAL_FUND_TXN
(
 ...........
 ...........
)
PARTITION BY RANGE(STTLD_DT)
(PARTITION MFTXN_DEC01 VALUES LESS THAN (TO_DATE(' 2002-01-01 00:00:00',
'SYYYY-MM-DD HH24:MI:SS',

'NLS_CALENDAR=GREGORIAN'))
    STORAGE(INITIAL 32M
            NEXT 65600K
            MINEXTENTS 5
            MAXEXTENTS 2000
            FREELISTS 1
            FREELIST GROUPS 1),
PARTITION MFTXN_JAN02 VALUES LESS THAN (TO_DATE(' 2002-02-01 00:00:00',
'SYYYY-MM-DD HH24:MI:SS',

'NLS_CALENDAR=GREGORIAN'))
    STORAGE(INITIAL 32M
            NEXT 65600K
            MINEXTENTS 5
            MAXEXTENTS 2000
            FREELISTS 1
            FREELIST GROUPS 1)
    TABLESPACE SMTMFTXND_P2,
PARTITION MFTXN_FEB02 VALUES LESS THAN (TO_DATE(' 2002-03-01 00:00:00',
'SYYYY-MM-DD HH24:MI:SS',

'NLS_CALENDAR=GREGORIAN'))
    STORAGE(INITIAL 32M
            NEXT 65600K
            MINEXTENTS 5
            MAXEXTENTS 2000
            FREELISTS 1
            FREELIST GROUPS 1)
    TABLESPACE SMTMFTXND_P3,
PARTITION MFTXN_MAR02 VALUES LESS THAN (TO_DATE(' 2002-04-01 00:00:00',
'SYYYY-MM-DD HH24:MI:SS',

'NLS_CALENDAR=GREGORIAN'))
    STORAGE(INITIAL 32K
            NEXT 64M
            MINEXTENTS 1
            MAXEXTENTS 2000
            FREELISTS 1
            FREELIST GROUPS 1)
    TABLESPACE SMTMFTXND_P3)
/

View defination
create view test_view as select to_char(sttld_dt,'yyyymm') mnth, a.* from
smart.mutual_fund_txn a
/

The below sql works fine as expected.
select count(*) from test_view
where sttld_dt between '15-mar-2002' and '31-mar-2002'
/

PLAN
--------------------------------------------------------
  SORT AGGREGATE  [,]
    PARTITION RANGE ITERATOR  [KEY,KEY]
      TABLE ACCESS FULL MUTUAL_FUND_TXN [KEY,KEY]

But users some times selects from date range but some times they selects only
for a month
like below sql

select count(*) from test_view
where mnth = '200203'

PLAN
--------------------------------------------------------
 SORT AGGREGATE  [,]
   PARTITION RANGE ALL  [1,4]
     TABLE ACCESS FULL MUTUAL_FUND_TXN [1,4]

Is there any way can I create test_view which can only read only those
partitions which are required.
Even I have consider parameters for year & quarter.

Please let me know if you need more information.

Thanks in advance,
Suresh

      and we said...
Well, it would basically be "impossible" for us to do that.  You are applying a
function to the column.  That function is not reversible.  You have converted a
DATE to an arbitrary (as far as we are concerned) string and are comparing that
string to some other string.  We cannot turn that string into a date and do
partition elimination.

Consider this query:

select count(*) from test_view where mnth = 'hello';

that is just as valid as your query -- there is NO WAY we can convert hello into
a date -- there is no way *in general* to convert an arbitrary string into a
date for comparision with the partition key. Hence, since the query does not
include the partition key, we must look at all partitions.

Consider if this were just a normal small table and there was an index on
sttld_dt.  select * from test where mnth = '200203' cannot use the index -- the
same reasoning applies to the partitions.

You would query:

select count(*) from test_view
where sttld_dt >= to_date('01-mar-2002','dd-mon-yyyy') and
      sttld_dt < add_months(to_date('01-mar-2002','dd-mon-yyyy'),1)

to get a month.

select count(*) from test_view
where sttld_dt >= to_date( '01-jan-2002', 'dd-mon-yyyy' ) and
      sttld_dt < add_months(to_date('01-mar-2002','dd-mon-yyyy'),12)

for a year.

I understand you don't have "control over the front end tools" -- so my
suggestion is you do not use the view, do not expose a "year" and a "month"
virtual column and use the sttld_dt as it exists in the above fashion.  The
front end tool, not having any choice, will generate the proper sql at that
point.
Review
      & Followup

      Rating: 3
Partitioning on
      date with time !  March 17, 2003
Reviewer:  Arul
      kumar  from United Kingdom
Hi Tom,

I have a query in similar lines.

We have the following table :

CREATE TABLE TEMP (  TEMP_DATE  DATE)
PARTITION BY RANGE (TEMP_DATE)
  (
PARTITION D1 VALUES LESS THAN
(TO_DATE('15-mar-2003','dd-mon-rrrr')),

PARTITION D2 VALUES LESS THAN
(TO_DATE('16-mar-2003','dd-mon-rrrr')),

   );

Now, it is my understanding that if a query is fired as follows :

SELECT * FROM TEMP WHERE temp_date = '14-mar-03'

..
...

Oracle (ver. 9.2.0) should scan only the D1 partition fetching all the data
available in that partition and not all other partitions.

As of now, it does scans D1 partition but because of the absence of the time
details, taking them as 00:00:00 (hh:mi:ss format!)I guess and  fetching only
those records with 0 time details!. Most of the records are with proper time
details!!

So, how to make the above query to scan only the D1 Partition (where 14th March
details are available!) and fetch all the data (the whole day data!) present
there.

Please note that the above query is fired through Business Objects software.

Hope I have represented the problem clearly.

Thanks & Regards,
Arul.

          Followup:

you get what you ask for don't you.  terrible how we do that!

First a couple of things

a) it takes no less then 4 characters to accurately represent a year.  No LESS.
b) compare dates to dates, strings to strings and numbers to numbers.

That query should be:

ops$tkyte@ORA817DEV> variable x varchar2(25)
ops$tkyte@ORA817DEV> exec :x := '14-mar-2003'

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> explain plan for
  2  select *
  3    from t
  4   where temp_date >= to_date( :x, 'dd-mon-yyyy' )
  5     and temp_date < to_date( :x, 'dd-mon-yyyy' ) + 1;

Explained.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> set echo off

Plan Table
-----------------------------------------------------------------------------
| Operation                 |  Name |  Rows | Bytes|  Cost  | Pstart| Pstop |
-----------------------------------------------------------------------------
| SELECT STATEMENT          |       |    25K|    2M|  28788 |       |       |
|  PARTITION RANGE ITERATOR |       |       |      |        |   KEY |   KEY |
|   TABLE ACCESS FULL       |T      |    25K|    2M|  28788 |   KEY |   KEY |
-----------------------------------------------------------------------------

6 rows selected.

that shows partition elimination kicks in at runtime when the optimizer can see
how many partitions it'll need to scan.

      Rating: 4
Thanks ! and
      Any other workaround ?  March 17, 2003
Reviewer:
      Arul kumar  from United Kingdom
Tom,

Thanks for the prompt response.

Since this query is being generated by Business Objects, Is there any other
workaround possible instead of using

Temp_Date >= ... AND Temp_Date < ... + 1
(or)
Temp_Date BETWEEN ... AND operations...

which needs major changes in the BO settings it seems!

I know its a tricky one but sure that Tom might have one! :)

Thanks & Regards,
Arul.

          Followup:

well, you need to have BO ask the question that actually retrieves the data you
want.

A predicate of:

WHERE temp_date = '14-mar-03'

*does not do that*.  it is asking the entirely wrong question.

      Rating: 5
EXEC PLAN on
      partitioned table  May 22, 2003
Reviewer:
      Steve  from Montreal
Hi Tom,

We have 2 partitioned tables

  1 for transactions of type V
  1 for transactions of type M

Each table is partitioned by day and there are 90 days online.
There are 145 Million rows of type V, 133 million of type S.

We have a query to look for transactions of type V for
customer X. Performance is good.

We have an "identical" query to look for transactions of
type M for customer X. Performance is horrible.

The table structure is similar (same partition key,
same indexes, etc). Here are the 2 queries with the exec plan:

*************************************************************
*************************************************************
*************************************************************

TYPE M Query
============

NOTE: partition key is th_th_id (e.g. value less than 2003050100)

  1  select pp.th_th_id
  2  from  dwadm.subs s,
  3        dwadm.ppsm pp
  4  where     s.sn = 523338421288
  5        and pp.sub_sub_id = s.sub_id
  6        and pp.th_th_id between 2003050100 and 2003050599
  7*       and pp.message_time >= to_date( '2003-05-01 11:11:11', 'YYYY-MM-DD
HH24:MI:SS')

  TH_TH_ID
----------
2003050115
2003050118
2003050120
2003050120
2003050123
2003050123
.
.
.

2003050520

64 rows selected.

Elapsed: 00:00:42.38

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=547 Card=2 Bytes=74)
   1    0   NESTED LOOPS (Cost=547 Card=2 Bytes=74)
   2    1     INDEX (FAST FULL SCAN) OF 'SUB_PK' (UNIQUE) (Cost=545 Ca
          rd=2 Bytes=34)

   3    1     PARTITION RANGE (ITERATOR)
   4    3       INDEX (FAST FULL SCAN) OF 'PPSMS_PK_SHADOW_I' (NON-UNI
          QUE) (Cost=1 Card=13369 Bytes=267380)

Statistics
----------------------------------------------------------
          0  recursive calls
        106  db block gets
      46221  consistent gets
      16320  physical reads
          0  redo size
       2228  bytes sent via SQL*Net to client
        682  bytes received via SQL*Net from client
          6  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         64  rows processed

definition of PPSMS_PK_SHADOW_I:
--------------------------------

It is a local partitioned index defined as follows:

    SUB_SUB_ID,TH_TH_ID,MESSAGE_TIME,CT_CT_ID

*************************************************************
*************************************************************
*************************************************************

TYPE V Query
============

SQL> l
  1  select pp.th_th_id
  2  from   dwadm.subs s,
  3         dwadm.ppcl pp
  4  where     S.sn = 523338421288
  5        and PP.sub_sub_id = S.sub_id
  6        and PP.th_th_id between 2003050100 and 2003050599
  7*       and pp.start_time >= to_date( '2003-05-01 11:11:11', 'YYYY-MM-DD
HH24:MI:SS' )

  TH_TH_ID
----------
2003050123
2003050322
2003050417
2003050418
2003050423
2003050500
2003050508
2003050511

8 rows selected.

Elapsed: 00:00:04.03

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=553 Card=2 Bytes=70)
   1    0   NESTED LOOPS (Cost=553 Card=2 Bytes=70)
   2    1     INDEX (FAST FULL SCAN) OF 'SUB_PK' (UNIQUE) (Cost=545 Ca
          rd=2 Bytes=34)

   3    1     PARTITION RANGE (ITERATOR)
   4    3       INDEX (RANGE SCAN) OF 'PPCA_PK_SHADOW_I' (NON-UNIQUE)
          (Cost=11 Card=14604 Bytes=262872)

Statistics
----------------------------------------------------------
          0  recursive calls
          6  db block gets
       3651  consistent gets
          0  physical reads
          0  redo size
        522  bytes sent via SQL*Net to client
        358  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          8  rows processed

definition of PPCA_PK_SHADOW_I:
-------------------------------

It is a local partitioned index defined as follows:

    SUB_SUB_ID,TH_TH_ID,START_TIME,CT_CT_ID

*************************************************************
*************************************************************
*************************************************************

Now, what I think is causing the problem is that one is
doing a RANGE SCAN and the other is FAST FULL SCAN.

Both tables are analyzed daily as follows:

analyze table <TABLE> estimate statistics sample 10 percent
     for table
     for all indexes
     for all indexed columns;

Q1: What could be causing the difference in execution plans.
    The data distribution, as far as I can tell, is similar.

Q2: Any suggestions?

Thanks Tom

          Followup:

one cannot tell from the above if partition elimination is kicking in or not.
you need to use explain plan and @?/rdbms/admin/utlxpls.sql to get a real plan.

also, one needs a tkprof report as well.

In short, one needs more information then I'd like to see you put here, so when
I'm taking questions -- feel free to put that sort of stuff into a well formed
question.

      Rating: 5
More
      info  May 23, 2003
Reviewer:  steve
       from Montreal
Here's some more info that may be helpful:

Plan table for type V
---------------------

Explained.

Plan Table
--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |     2 |   70 |    571 |       |       |
|  NESTED LOOPS             |          |     2 |   70 |    571 |       |       |
|   INDEX FAST FULL SCAN    |SUB_PK    |     2 |   34 |    549 |       |       |
|   PARTITION RANGE ITERATOR|          |       |      |        |    75 |    79 |
|    INDEX RANGE SCAN       |PPCA_PK_S |    15K|  270K|     11 |    75 |    79 |
--------------------------------------------------------------------------------

8 rows selected.

Plan table for type M
----------------------

Plan Table
--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |     2 |   74 |   2691 |       |       |
|  HASH JOIN                |          |     2 |   74 |   2691 |       |       |
|   INDEX FAST FULL SCAN    |SUB_PK    |     2 |   34 |    549 |       |       |
|   PARTITION RANGE ITERATOR|          |       |      |        |    81 |    85 |
|    INDEX FAST FULL SCAN   |PPSMS_PK_ |    14K|  276K|   2141 |    81 |    85 |
--------------------------------------------------------------------------------

8 rows selected.

      Rating: 5
Sorry about the
      lack of info  May 23, 2003
Reviewer:  Steve
       from Montreal
I thought about the explain plan and posted it
but forgot the tkprof. Sorry. Here it is:
********************************************************
********************************************************
********************************************************
********************************************************

Calls of type V
---------------

TKPROF: Release 8.1.7.0.0 - Production on Fri May 23 07:30:45 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Trace file: dds_ora_29297.trc
Sort options: execpu  fchcpu
********************************************************************************
count    = number of times OCI procedure was executed
cpu      = cpu time in seconds executing
elapsed  = elapsed time in seconds executing
disk     = number of physical reads of buffers from disk
query    = number of buffers gotten for consistent read
current  = number of buffers gotten in current mode (usually for update)
rows     = number of rows processed by the fetch or execute call
********************************************************************************

select pp.th_th_id
from   dwadm.subs s,
       dwadm.ppcl pp
where     S.sn = XXXXXXXXXXX  (I replaced this)
      and PP.sub_sub_id = S.sub_id
      and PP.th_th_id between 2003050100 and 2003050599
      and pp.start_time >= to_date( '2003-05-01 11:11:11', 'YYYY-MM-DD
HH24:MI:SS' )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      3.06       3.29         14       3672          6           8
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      3.07       3.30         14       3672          6           8

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 86  (DWADM)

Rows     Row Source Operation
-------  ---------------------------------------------------
      8  NESTED LOOPS
      3   INDEX FAST FULL SCAN (object id 12453)
      8   PARTITION RANGE ITERATOR PARTITION: START=75 STOP=79
      8    INDEX RANGE SCAN PARTITION: START=75 STOP=79 (object id 12222)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      8   NESTED LOOPS
      3    INDEX   GOAL: ANALYZED (FAST FULL SCAN) OF 'SUB_PK' (UNIQUE)
      8    PARTITION RANGE (ITERATOR) PARTITION: START=75 STOP=79
      8     INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'PPCA_PK_SHADOW_I'
                (NON-UNIQUE) PARTITION: START=75 STOP=79

********************************************************************************

BEGIN DBMS_APPLICATION_INFO.SET_MODULE(:1,NULL); END;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      2      0.03       0.05          0          0          0           2
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.03       0.05          0          0          0           2

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 86  (DWADM)
********************************************************************************

alter session set sql_trace=TRUE

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      1      0.01       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        1      0.01       0.00          0          0          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 86  (DWADM)
********************************************************************************

select grantee#,privilege#,nvl(col#,0),max(nvl(option$,0))
from
 objauth$ where obj#=:1 group by grantee#,privilege#,nvl(col#,0) order by
  grantee#

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.01       0.00          0          3          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.01          0          3          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 1)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   SORT (GROUP BY)
      0    TABLE ACCESS (BY INDEX ROWID) OF 'OBJAUTH$'
      0     INDEX (RANGE SCAN) OF 'I_OBJAUTH1' (UNIQUE)

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        3      0.01       0.01          0          0          0           0
Execute      4      0.04       0.05          0          0          0           2
Fetch        2      3.06       3.29         14       3672          6           8
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        9      3.11       3.35         14       3672          6          10

Misses in library cache during parse: 2

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.01       0.00          0          3          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.01          0          3          0           1

Misses in library cache during parse: 1

    4  user  SQL statements in session.
    1  internal SQL statements in session.
    5  SQL statements in session.
    2  statements EXPLAINed in this session.
********************************************************************************
Trace file: dds_ora_29297.trc
Trace file compatibility: 8.00.04
Sort options: execpu  fchcpu
       1  session in tracefile.
       4  user  SQL statements in trace file.
       1  internal SQL statements in trace file.
       5  SQL statements in trace file.
       4  unique SQL statements in trace file.
       2  SQL statements EXPLAINed using schema:
           SYS.prof$plan_table
             Default table was used.
             Table was created.
             Table was dropped.
      63  lines in trace file.

********************************************************
********************************************************
********************************************************
********************************************************

Calls of type M
---------------

TKPROF: Release 8.1.7.0.0 - Production on Fri May 23 07:31:01 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Trace file: dds_ora_29389.trc
Sort options: execpu  fchcpu
********************************************************************************
count    = number of times OCI procedure was executed
cpu      = cpu time in seconds executing
elapsed  = elapsed time in seconds executing
disk     = number of physical reads of buffers from disk
query    = number of buffers gotten for consistent read
current  = number of buffers gotten in current mode (usually for update)
rows     = number of rows processed by the fetch or execute call
********************************************************************************

select pp.th_th_id
from  dwadm.subs s,
      dwadm.ppsm pp
where     s.sn = XXXXXXXXXXXX (I replaced this)
      and pp.sub_sub_id = s.sub_id
      and pp.th_th_id between 2003050100 and 2003050599
      and pp.message_time >= to_date( '2003-05-01 11:11:11', 'YYYY-MM-DD
HH24:MI:SS')

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        6     26.34      60.19      21303      24942         56          64
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        8     26.35      60.20      21303      24942         56          64

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 86  (DWADM)

Rows     Row Source Operation
-------  ---------------------------------------------------
     64  HASH JOIN
      2   INDEX FAST FULL SCAN (object id 12453)
8598601   PARTITION RANGE ITERATOR PARTITION: START=81 STOP=85
8598601    INDEX FAST FULL SCAN PARTITION: START=81 STOP=85 (object id 12352)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
     64   HASH JOIN
      2    INDEX   GOAL: ANALYZED (FAST FULL SCAN) OF 'SUB_PK' (UNIQUE)
8598601    PARTITION RANGE (ITERATOR) PARTITION: START=81 STOP=85
8598601     INDEX   GOAL: ANALYZED (FAST FULL SCAN) OF
                'PPSMS_PK_SHADOW_I' (NON-UNIQUE) PARTITION: START=81 STOP=85

********************************************************************************

alter session set sql_trace=TRUE

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      1      0.01       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        1      0.01       0.00          0          0          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 86  (DWADM)
********************************************************************************

select owner#,name,namespace,remoteowner,linkname,p_timestamp,p_obj#,
  d_owner#, nvl(property,0)
from
 dependency$,obj$ where d_obj#=:1 and p_obj#=obj#(+) order by order#

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.01       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          2          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.01       0.00          0          2          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  SORT ORDER BY
      0   NESTED LOOPS OUTER
      1    TABLE ACCESS BY INDEX ROWID DEPENDENCY$
      1     INDEX RANGE SCAN (object id 116)
      0    TABLE ACCESS BY INDEX ROWID OBJ$
      0     INDEX UNIQUE SCAN (object id 33)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   SORT (ORDER BY)
      0    NESTED LOOPS (OUTER)
      1     TABLE ACCESS (BY INDEX ROWID) OF 'DEPENDENCY$'
      1      INDEX (RANGE SCAN) OF 'I_DEPENDENCY1' (UNIQUE)
      0     TABLE ACCESS (BY INDEX ROWID) OF 'OBJ$'
      0      INDEX (UNIQUE SCAN) OF 'I_OBJ1' (UNIQUE)

********************************************************************************

select /*+ index (indpart$ i_indpart$_bopart$) */ obj#, dataobj#, part#,
  hiboundlen, hiboundval, flags, ts#, file#, block#, pctfree$, initrans,
  maxtrans, analyzetime, samplesize, rowcnt, blevel, leafcnt, distkey,
  lblkkey, dblkkey, clufac, pctthres$
from
 indpart$ where bo# = :1 order by part#

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      1      0.01       0.00          0          0          0           0
Fetch        2      0.00       0.05          1        132          0         111
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.06          1        132          0         111

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
    111  TABLE ACCESS BY INDEX ROWID INDPART$
    112   INDEX RANGE SCAN (object id 208)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
    111   TABLE ACCESS (BY INDEX ROWID) OF 'INDPART$'
    112    INDEX (RANGE SCAN) OF 'I_INDPART$_BOPART$' (NON-UNIQUE)

********************************************************************************

select order#,columns,types
from
 access$ where d_obj#=:1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          2          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          2          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS BY INDEX ROWID ACCESS$
      1   INDEX RANGE SCAN (object id 118)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   TABLE ACCESS (BY INDEX ROWID) OF 'ACCESS$'
      1    INDEX (RANGE SCAN) OF 'I_ACCESS1' (NON-UNIQUE)

********************************************************************************

select parttype, partcnt, partkeycols, flags, defts#, defpctfree, defpctused,
  definitrans, defmaxtrans, deftiniexts, defextsize, defminexts, defmaxexts,
  defextpct, deflists, defgroups, deflogging, spare1, mod(spare2, 256)
  subparttype, mod(trunc(spare2/256), 256) subpartkeycols,
  mod(trunc(spare2/65536), 65536) defsubpartcnt
from
 partobj$ where obj# = :1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          2          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.01          0          2          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID PARTOBJ$
      2   INDEX UNIQUE SCAN (object id 200)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   TABLE ACCESS (BY INDEX ROWID) OF 'PARTOBJ$'
      2    INDEX (UNIQUE SCAN) OF 'I_PARTOBJ$' (UNIQUE)

********************************************************************************

BEGIN DBMS_APPLICATION_INFO.SET_MODULE(:1,NULL); END;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      2      0.00       0.00          0          0          0           2
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          0          0           2

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 86  (DWADM)
********************************************************************************

select intcol#, col# , type#, spare1, segcol#, charsetform
from
 partcol$  where obj# = :1 order by pos#

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          2          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.00          0          2          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT ORDER BY
      1   TABLE ACCESS BY INDEX ROWID PARTCOL$
      2    INDEX RANGE SCAN (object id 202)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   SORT (ORDER BY)
      1    TABLE ACCESS (BY INDEX ROWID) OF 'PARTCOL$'
      2     INDEX (RANGE SCAN) OF 'I_PARTCOL$' (NON-UNIQUE)

********************************************************************************

select i.obj#,i.ts#,i.file#,i.block#,i.intcols,i.type#,i.flags, i.property,
  i.pctfree$,i.initrans,i.maxtrans,i.blevel,i.leafcnt,i.distkey, i.lblkkey,
  i.dblkkey,i.clufac,i.cols,i.analyzetime,i.samplesize,i.dataobj#,
  nvl(i.degree,1),nvl(i.instances,1),i.rowcnt,mod(i.pctthres$,256),
  i.indmethod#,i.trunccnt,nvl(c.unicols,0),nvl(c.deferrable#+c.valid#,0),
  nvl(i.spare1,i.intcols),i.spare4,spare2,decode(i.pctthres$,null,null,
  mod(trunc(i.pctthres$/256),256))
from
 ind$ i, (select enabled, min(cols) unicols, min(to_number(bitand(defer,1)))
  deferrable#, min(to_number(bitand(defer,4))) valid# from cdef$ where obj#=
  :1 and enabled > 1 group by enabled) c where i.obj#=c.enabled(+) and i.bo#=
  :1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        4      0.00       0.00          0         10          0           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        7      0.00       0.01          0         10          0           2

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 2)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   MERGE JOIN (OUTER)
      0    SORT (JOIN)
      0     TABLE ACCESS (CLUSTER) OF 'IND$'
      0      INDEX (UNIQUE SCAN) OF 'I_OBJ#' (NON-UNIQUE)
      0    SORT (JOIN)
      0     VIEW
      0      SORT (GROUP BY)
      0       TABLE ACCESS (CLUSTER) OF 'CDEF$'
      0        INDEX (UNIQUE SCAN) OF 'I_COBJ#' (NON-UNIQUE)

********************************************************************************

select pos#,intcol#,col#,spare1
from
 icol$ where obj#=:1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        4      0.00       0.00          0          8          0           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        7      0.01       0.00          0          8          0           2

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 2)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   TABLE ACCESS (BY INDEX ROWID) OF 'ICOL$'
      0    INDEX (RANGE SCAN) OF 'I_ICOL1' (NON-UNIQUE)

********************************************************************************

select name,intcol#,segcol#,type#,length,nvl(precision#,0),decode(type#,2,
  nvl(scale,-127/*MAXSB1MINAL*/),178,scale,179,scale,180,scale,181,scale,182,
  scale,183,scale,231,scale,0),null$,fixedstorage,nvl(deflength,0),default$,
  rowid,col#,property, charsetid,charsetform,spare1,spare2
from
 col$ where obj#=:1 order by intcol#

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch       33      0.00       0.01          0          6          0          31
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       36      0.00       0.01          0          6          0          31

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 2)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   SORT (ORDER BY)
      0    TABLE ACCESS (CLUSTER) OF 'COL$'
      0     INDEX (UNIQUE SCAN) OF 'I_OBJ#' (NON-UNIQUE)

********************************************************************************

select o.owner#,o.name,o.namespace,o.remoteowner,o.linkname,o.subname,
  o.dataobj#,o.flags
from
 obj$ o where o.obj#=:1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          3          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          3          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 2)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   TABLE ACCESS (BY INDEX ROWID) OF 'OBJ$'
      0    INDEX (UNIQUE SCAN) OF 'I_OBJ1' (UNIQUE)

********************************************************************************

select i.obj#
from
 ind$ i, obj$ o    where i.obj# = o.obj# and i.bo# = :1 and o.name = :2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0         10          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0         10          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 2)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  NESTED LOOPS
      2   TABLE ACCESS CLUSTER IND$
      1    INDEX UNIQUE SCAN (object id 3)
      1   TABLE ACCESS BY INDEX ROWID OBJ$
      3    INDEX UNIQUE SCAN (object id 33)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   NESTED LOOPS
      2    TABLE ACCESS (CLUSTER) OF 'IND$'
      1     INDEX (UNIQUE SCAN) OF 'I_OBJ#' (NON-UNIQUE)
      1    TABLE ACCESS (BY INDEX ROWID) OF 'OBJ$'
      3     INDEX (UNIQUE SCAN) OF 'I_OBJ1' (UNIQUE)

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        3      0.01       0.01          0          0          0           0
Execute      4      0.01       0.00          0          0          0           2
Fetch        6     26.34      60.19      21303      24942         56          64
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       13     26.36      60.20      21303      24942         56          66

Misses in library cache during parse: 1

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       10      0.02       0.03          0          0          0           0
Execute     13      0.02       0.00          0          0          0           0
Fetch       50      0.00       0.06          1        177          0         150
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       73      0.04       0.09          1        177          0         150

Misses in library cache during parse: 10

    4  user  SQL statements in session.
   10  internal SQL statements in session.
   14  SQL statements in session.
   11  statements EXPLAINed in this session.
********************************************************************************
Trace file: dds_ora_29389.trc
Trace file compatibility: 8.00.04
Sort options: execpu  fchcpu
       1  session in tracefile.
       4  user  SQL statements in trace file.
      10  internal SQL statements in trace file.
      14  SQL statements in trace file.
      13  unique SQL statements in trace file.
      11  SQL statements EXPLAINed using schema:
           SYS.prof$plan_table
             Default table was used.
             Table was created.
             Table was dropped.
     196  lines in trace file.

          Followup:

really sort of prefer these as "new questions"  (and really wish you would have
posted just the relevant portion of the tkprof -- not all of the recursive SQL
it did)

      Rating:
      5
ok  May 23, 2003
Reviewer:  Steve
       from Montreal
Deal.  When your backlog is cleared and start accepting
new questions, I'll post it as a new question and tidy it up.

(I wasn't sure if you preferred similar questions in
 existing threads or not. Sorry about that.)

Thanks

Steve

          Followup:

one other thing to do -- check the stats against eachother -- verify they are
"look right"

May 23, 2003 Bobby -- Thanks for the question regarding
      "grouping by CLOB", version 8.0.6

      You Asked
Well Tom -
I have searched for days here and found nothing to help me with my problem.
Here's the situation - VERY simplified to its unfunctional parts....

I have 2 tables: items & findings.
1 - * relationship on items to findings.
Each table has a couple of CLOB columns.
The user of my web-app will need to see records from either table, based on
criteria from either table.

Ex.- I want to see all items (just its id in this case) where its corresponding
findings are still open (a date field is null).

----------------------------------------------------------------------
SQL> select i.item_id from items i, findings f where i.item_id=f.item_id and
f.closure_date is null;

   ITEM_ID
----------
       306
       307
       307
       308
       306
       365
       322
       401
       421
       421
       309

   ITEM_ID
----------
       461
       505
       505
       505
       531
       365

17 rows selected.
----------------------------------------------------------------------

The problem is that this query returns duplicate rows if each item has many open
findings.  This means that I have to use a SELECT DISTINCT or a GROUP BY clause
to get unique rows.

----------------------------------------------------------------------
SQL> select i.item_id from items i, findings f where i.item_id=f.item_id and
f.closure_date is null group by i.item_id;

   ITEM_ID
----------
       306
       307
       308
       309
       322
       365
       401
       421
       461
       505
       531

11 rows selected.

SQL> select distinct i.item_id from items i, findings f where
i.item_id=f.item_id and f.closure_date is null;

   ITEM_ID
----------
       306
       307
       308
       309
       322
       365
       401
       421
       461
       505
       531

11 rows selected.
----------------------------------------------------------------------

This works fine except when I need to return one or more of the CLOB columns
from the items table.  Since I cannot SELECT DISTINCT or GROUP BY a CLOB column,
I have no idea how to accomplish this.
I have a column called item_description (CLOB).  If I wanted to find all items
(their id and description) where its findings are still open
I get an ORACLE error:  ORA-00932: inconsistent datatypes.
This is because of the CLOBs and the grouping.

Any suggestions?

      and we said...
If i.item_id is unique in items -- then:

select distinct i.item_id
  from items i, findings f
 where i.item_id=f.item_id
   and f.closure_date is null;

is the same as

select <whatever you want, including clobs>
  from items i, ( select distinct item_id
                    from findings
                   where closure_date is null ) f
 where i.item_id = f.item_id;
Review &
      Followup

      Rating: 2
Maybe I over
      simplified it.....  May 23, 2003
Reviewer:
      Bobby  from Newport News, VA
Thanks for such a quick response Tom.
I may have over simplified my question.

I have a function set up so that the user can select whatever fields that they
want returned from either table.

If they want fields returned from the findings table, the query that you
responded with will not work because then you would get stuck in a situation
like:

SQL>select i.item_id, f.closure_date
    from items i, ( select distinct item_id
                    from findings
                   where closure_date is null ) f
    where i.item_id = f.item_id;

where I want to return something from the findings table.

          Followup:

tell me what you want from F then.

consider in your example, we would just replace f.closure_date with NULL!

anyway -- if you want a single row per item -- that means you need a single
value from F per item.  So you would:

select *
    from items i, ( select item_id , max(c1), avg(c2), min(c3), whatever....
                    from findings
                   where closure_date is null
                   group by item_id ) f
    where i.item_id = f.item_id;

May
      24, 2003 sunil -- Thanks for the
      question regarding "query to find all sqlstatements taking more then a
      certain amount of time", version 8.1.6.0

      You Asked
Hi,

I would like the following Information as output from the query

  SID,
  SERIAL#,
  USERNAME,
  STARTTIME,
  SQLSTATEMENT the client is executing

for all queries that are taking more then a certain amount of time
(say around 5 Minute) to execute.

For queries taking more then 5Minutes , i want the SqlStatement and other
details like who is executing this statement, and what time the query started .

Sunil Nettyam

      and we said...
try this:

ops$tkyte@ORA8I.WORLD> column dur format a20
ops$tkyte@ORA8I.WORLD> select sid, serial#, username,
to_char(sysdate-last_call_et/24/60/60,'hh24:mi:ss') started,
  2             trunc(last_call_et/60) || ' mins, ' || mod(last_call_et,60) ||
                                       ' secs' dur,
  3             (select sql_text from v$sql where address = sql_address )
sql_text
  4    from v$session
  5   where username is not null
  6     and last_call_et > 5*60
  7     and status = 'ACTIVE'
  8  /

Review &
      Followup

      Rating: 5
Time Consuming
      Queries  August 17, 2001
Reviewer:  Sunil B
      Nettyam  from PA,USA
Hi,

Some time in our database some of the querise just hang. They stay there even
after a long time. These consume lots of system Resources.
  With the help of the query you gave now we are able to identify the bad
performing queries and kill them and study and rewrite the queries.

          Followup:

Just fyi: to get the FULL text of the query (this gets the first 1000
characters) see the v$sql_text_with_newlines view.

      Rating: 5
most useful
      query to help us figure out the performance problem  August
      17, 2001
Reviewer:  Pauline  from NJ, U.S.A.

I could not find v$sql_text_with_newlines using 8.1.7
SQL> sho user
USER is "SYS"SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle8i Enterprise Edition Release 8.1.7.2.0 - Production
PL/SQL Release 8.1.7.2.0 - Production
CORE    8.1.7.0.0       Production
TNS for Solaris: Version 8.1.7.2.0 - Production
NLSRTL Version 3.4.1.0.0 - Production

5 rows selected.

SQL> desc v$sql_text_with_newlines
ERROR:
ORA-04043: object v$sql_text_with_newlines does not exist

Could you tell me why?

          Followup:

sorry about that

V$SQLTEXT_with_newlines

ops$tkyte@ORA817.US.ORACLE.COM> desc v$sqltext_with_newlines;
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 ADDRESS                                      RAW(4)
 HASH_VALUE                                   NUMBER
 COMMAND_TYPE                                 NUMBER
 PIECE                                        NUMBER
 SQL_TEXT                                     VARCHAR2(64)

I added an extra underscore

      Rating: 5
but not
      working  August 18, 2001
Reviewer:  Mohammed
      Ahmed  from Saudi Arabia
the script do not run under

Oracle8 Enterprise Edition Release 8.0.5.2.1

It gives error message:

ERROR at line 4:
ORA-00936: missing expression

May be due to the select in place of colum name

          Followup:

Yes, the select (select...) is new with 8.1.

This works in 8.0 and before

select a.sid, a.serial#, a.username,
       to_char(sysdate-a.last_call_et/24/60/60,'hh24:mi:ss') started,
              trunc(a.last_call_et/60) || ' mins, ' || mod(a.last_call_et,60) ||
                                       ' secs' dur,
              b.sql_text
     from v$sql b, v$session a
    where a.username is not null
      and a.last_call_et > 5*60
      and a.status = 'ACTIVE'
      and a.sql_address = b.address
/

      Rating:
      4
  May 23, 2003
Reviewer:  Sean
       from Atlanta
"
V$SQLTEXT_with_newlines

ops$tkyte@ORA817.US.ORACLE.COM> desc v$sqltext_with_newlines;
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 ADDRESS                                      RAW(4)
 HASH_VALUE                                   NUMBER
 COMMAND_TYPE                                 NUMBER
 PIECE                                        NUMBER
 SQL_TEXT                                     VARCHAR2(64)
"

can HASH_VALUE use to identified an unique queries statement? Becuase STATSPACK
only gives the Hash Value for a query.
Having that said, I have tried the following query, found the hash value and
then use the view to find whole query. The outcome was very confussion:

select (select
    decode(extent_management,'LOCAL','*','') from dba_tablespaces where
    tablespace_name =  b.tablespace_name)
        || nvl(b.tablespace_name, nvl(a.tablespace_name,'UNKOWN')) name,
             kbytes_alloc kbytes,
             kbytes_alloc-nvl(kbytes_free,0) used,
             nvl(kbytes_free,0) free,
             ((kbytes_alloc-nvl(kbytes_free,0))/ kbytes_alloc)*100  pct_used,
             nvl(largest,0) largest,
             nvl(kbytes_max,kbytes_alloc) Max_Size,
             decode( kbytes_max, 0, 0, (kbytes_alloc/kbytes_max)*100)
pct_max_used
  from ( select sum(bytes)/1024 Kbytes_free,
                            max(bytes)/1024 largest,
                            tablespace_name
             from  sys.dba_free_space
             group by tablespace_name ) a,
       ( select sum(bytes)/1024 Kbytes_alloc,
                            sum(maxbytes)/1024 Kbytes_max,
                            tablespace_name
             from sys.dba_data_files
             group by tablespace_name

        select sum(bytes)/1024 Kbytes_alloc,
                            sum(maxbytes)/1024 Kbytes_max,
                            tablespace_name
             from sys.dba_temp_files
             group by tablespace_name )b
  where a.tablespace_name (+) = b.tablespace_name
  order by &1
/

SQL> select *
  2        from (
  3      select piece,address, hash_value,
  4             lag(sql_text) over (partition by address, hash_value order by
piece) ||
  5             sql_text ||
  6             lead(sql_text) over (partition by address, hash_value order by
piece)
  7                sql_text
  8        from v$sqltext_with_newlines
  9             )
 10       where hash_value=4167100631
 11  /

     PIECE ADDRESS  HASH_VALUE
---------- -------- ----------
SQL_TEXT
---------------------------------------------------------------------------------
---------------------------------------------------
         0 81178600 4167100631
select (select
    decode(extent_management,'LOCAL','*','') from dba_tablespaces where
    tablespace_name =  b.tablespace_name)

         1 81178600 4167100631
select (select
    decode(extent_management,'LOCAL','*','') from dba_tablespaces where
    tablespace_name =  b.tablespace_name)
        || nvl(b.tablespace_name, nvl(a.tablespace_name,'UNKOWN')) nam

         2 81178600 4167100631
 dba_tablespaces where
    tablespace_name =  b.tablespace_name)
        || nvl(b.tablespace_name, nvl(a.tablespace_name,'UNKOWN')) name,
             kbytes_alloc kbytes,
             kbytes_alloc-n

         3 81178600 4167100631

        || nvl(b.tablespace_name, nvl(a.tablespace_name,'UNKOWN')) name,
             kbytes_alloc kbytes,
             kbytes_alloc-nvl(kbytes_free,0) used,
             nvl(kbytes_free,0) free,

         4 81178600 4167100631
e,
             kbytes_alloc kbytes,
             kbytes_alloc-nvl(kbytes_free,0) used,
             nvl(kbytes_free,0) free,
             ((kbytes_alloc-nvl(kbytes_free,0))/ kbytes_alloc)*100

         5 81178600 4167100631
vl(kbytes_free,0) used,
             nvl(kbytes_free,0) free,
             ((kbytes_alloc-nvl(kbytes_free,0))/ kbytes_alloc)*100  pct_used,
             nvl(largest,0) largest,
             nv

         6 81178600 4167100631
           ((kbytes_alloc-nvl(kbytes_free,0))/ kbytes_alloc)*100  pct_used,

     PIECE ADDRESS  HASH_VALUE
---------- -------- ----------
SQL_TEXT
---------------------------------------------------------------------------------
---------------------------------------------------
             nvl(largest,0) largest,
             nvl(kbytes_max,kbytes_alloc) Max_Size,
             decode( kbytes

         7 81178600 4167100631
  pct_used,
             nvl(largest,0) largest,
             nvl(kbytes_max,kbytes_alloc) Max_Size,
             decode( kbytes_max, 0, 0, (kbytes_alloc/kbytes_max)*100)
pct_max_used
  from

         8 81178600 4167100631
l(kbytes_max,kbytes_alloc) Max_Size,
             decode( kbytes_max, 0, 0, (kbytes_alloc/kbytes_max)*100)
pct_max_used
  from ( select sum(bytes)/1024 Kbytes_free,

         9 81178600 4167100631
_max, 0, 0, (kbytes_alloc/kbytes_max)*100)   pct_max_used
  from ( select sum(bytes)/1024 Kbytes_free,
                            max(bytes)/1024 largest,
                            tablespa

        10 81178600 4167100631
 ( select sum(bytes)/1024 Kbytes_free,
                            max(bytes)/1024 largest,
                            tablespace_name
             from  sys.dba_free_space
             group

        11 81178600 4167100631
   max(bytes)/1024 largest,
                            tablespace_name
             from  sys.dba_free_space
             group by tablespace_name ) a,
       ( select sum(bytes)/1024 Kbytes_

        12 81178600 4167100631
ce_name
             from  sys.dba_free_space
             group by tablespace_name ) a,
       ( select sum(bytes)/1024 Kbytes_alloc,

     PIECE ADDRESS  HASH_VALUE
---------- -------- ----------
SQL_TEXT
---------------------------------------------------------------------------------
---------------------------------------------------
                            sum(maxbytes)/1024 Kbytes_max

        13 81178600 4167100631
 by tablespace_name ) a,
       ( select sum(bytes)/1024 Kbytes_alloc,
                            sum(maxbytes)/1024 Kbytes_max,
                            tablespace_name
             from

        14 81178600 4167100631
alloc,
                            sum(maxbytes)/1024 Kbytes_max,
                            tablespace_name
             from sys.dba_data_files
             group by tablespace_name

        15 81178600 4167100631
,
                            tablespace_name
             from sys.dba_data_files
             group by tablespace_name
             union all
        select sum(bytes)/1024 Kbytes_alloc,

        16 81178600 4167100631
sys.dba_data_files
             group by tablespace_name
             union all
        select sum(bytes)/1024 Kbytes_alloc,
                            sum(maxbytes)/1024 Kbytes_max,

        17 81178600 4167100631
      union all
        select sum(bytes)/1024 Kbytes_alloc,
                            sum(maxbytes)/1024 Kbytes_max,
                            tablespace_name
             from sys.dba_te

        18 81178600 4167100631

     PIECE ADDRESS  HASH_VALUE
---------- -------- ----------
SQL_TEXT
---------------------------------------------------------------------------------
---------------------------------------------------
                         sum(maxbytes)/1024 Kbytes_max,
                            tablespace_name
             from sys.dba_temp_files
             group by tablespace_name )b
  where a.tabl

        19 81178600 4167100631
                    tablespace_name
             from sys.dba_temp_files
             group by tablespace_name )b
  where a.tablespace_name (+) = b.tablespace_name
  order by '%'

        20 81178600 4167100631
mp_files
             group by tablespace_name )b
  where a.tablespace_name (+) = b.tablespace_name
  order by '%'

21 rows selected.

SQL>
-------

If you put them together by PIECE, then it does not match the original.

          Followup:

hash is not enough.

a single hash value will point to all child cursors.

two queries CAN have the same hash value.

The hash value plus the leading edge of the query provided by statspack is
generally more then sufficient however to track down the entire query.

your query was in 20 pieces apparently.

Your query to retrieve the query retrieved 20 rows.  Each row had the prior and
next row in it.  You ended up with about 58 pieces therefore.

just

select sql_text from v$sqltext_with_newlines
where hash = ..... order by piece;

      Rating:
      4
  May 23, 2003
Reviewer:  Sean
       from Atlanta
Never mind, I figured it out:

SQL> select sql_text from v$sqltext_with_newlines where HASH_VALUE=4167100631
order by piece;

May 24, 2003 Keith -- Thanks for the question regarding
      "Random Sample", version 8.0.4.2.2

      You Asked
I need to select a random sample percentage from a table based
on a particular column value.  For example, if I had the
following table:

id(PK)      list_id      fname      lname
-----------------------------------------
1             10         John       Doe
2             30         Jane       Doe
3             10         .          .
4             20         .          .
5             30         .          .
6             10         .          .
7             10         .          .
8             30         .          .
9             10         .          .
10            20         .          .
.             .          .          .
.             .          .          .
.             .          .          .
.             .          .          .
1000          30         Ed         James

I would need to select a random sample of x% for any single
list.  I can come up with a way to do this in a procedure, but
need to do it with just SQL.  Any help you can provide would be
greatly appreciated.

Thanks,
Keith

      and we said...
In Oracle8i release 8.1 -- this is pretty trivial:

scott@ORA816.US.ORACLE.COM> select sal from emp sample(50);

       SAL
----------
       800
      1600
      1250
      2975
      1500
      3000

6 rows selected.

scott@ORA816.US.ORACLE.COM> /

       SAL
----------
      1250
      2975
      1250
      2850
      5000
      1500
      1100
      3000

8 rows selected.

that gets a 50% random sample.  In Oracle8.0 and before -- this
is a little harder and it is always pretty inefficient.

If you goto

http://osi.oracle.com/~tkyte/Misc/Random.html
and get my random package, you can do something like the
following:

  1  select *
  2  from ( select sal, random.rand_max(100) rand, rownum
              from emp )
  3* where rand <= 50
scott@8.0> /

       SAL       RAND     ROWNUM
---------- ---------- ----------
      2975         28          4
      2850         27          6
      3000          5          8
      1500         39         10
       950         35         12
      1300         44         14

6 rows selected.

scott@8.0> /

       SAL       RAND     ROWNUM
---------- ---------- ----------
      1600         14          2
      1250         18          3
      2975         25          4
      1500         39         10
      1100         12         11
       950          3         12
      3000         31         13
      1300         12         14

8 rows selected.

scott@8.0> /

(yes, the rownum is relevant -- it avoids the

from ( select sal, random.rand_max(100) rand from emp )
                   *
ERROR at line 2:
ORA-06573: Function RAND_MAX modifies package state, cannot be
used here

you would get otherwise).

Review &
      Followup

      Rating: 3
How does the
      percent work?  August 09, 2002
Reviewer:
      Jay  from Scotland
I'm confused. If I want a 5% random sample, I would expect to get 5% of the
available rows returned randomly each time I run the query. So, why then does
the number of rows returned vary? Here's what I mean:

SQL> insert into t(num) select rownum from all_objects where rownum < 101;

100 rows created.

SQL> select num from t sample(5);

      NUM
---------
       18
       45
       76
       99

SQL> /

      NUM
---------
       10
       15
       20
       22
       37
       78
       99

7 rows selected.

SQL> /

      NUM
---------
        2
       22
       66

Surely I should 5 rows back each time, since I want 5% of 100 rows. If
"sample(<percent>)" can't do this, what would be the best way to do it?

Cheers
Jay

          Followup:

Because it is based on RANDOMNESS.

Your set is way too small.  It'll never get EXACTLY 5% of the data (except by
accident).  On  a larger set of data, it'll be very close to 5% but it'll still
not be 5% exactly.

Think of it like this, we goto each row -- we generate a random number between
0.00 and 1.00.  If the number if 0.05 or less -- then the row is output.  If the
number is above 0.05 we don't output it.

It could very well turn out that due to randomness -- a sample 5% on a small set
will return 0 rows!!!  We could generate a random number > 0.05 100 out of 100
time (not probable but possible)

The only way to get X% exactly would be to

a) count the rows
b) figure out what N is
c) select * from ( select * from t order by random-number ) where rownum <= N

it would be really slow.

      Rating: 5
Does random
      sampling support db_link?  May 23, 2003
Reviewer:
      A Reader  from USA
Tom,
When I try to run the following:

select * from t@db_link sample(10);

I get the following error:

ERROR at line 1:
ORA-00933: SQL command not properly ended

Is it because of the db_link?  Is there a way we can work around?

Thank you for your help.

          Followup:

if you check out the train track diagram for SELECT, it shows

partition
subpartition
sample
@dblink

being mutually exclusive options after a TABLE.  I know of no workaround

      Rating: 3
Getting error
      when using random  May 24, 2003
Reviewer:
      Fahim  from India
  1   select *
  2       from ( select sal, random.rand_max(100) rand, rownum
  3                 from emp )
  4*     where rand <= 50
  5  /
     from ( select sal, random.rand_max(100) rand, rownum
                               *
ERROR at line 2:
ORA-00904: invalid column name

          Followup:

do you have my random package installed (no, you don't)

May 24, 2003 akthar -- Thanks for the question regarding "SGA
      Parameters", version 8.1.5

      You Asked
Hi Tom,

Thank you for your continuous support.. Thanks a lot.

1) We are running high transaction(e_commerce) application on
8.1.5 in solaris E3500 - box , With 1GB RAM . Database Block
size is 16K , Please help me by suggesting values to be set for
following , Also explain me the thumb rule for these parameters.

db_block_buffer,
Shared_pool_size,
Sort_area_size,
Log buffer - (Redo log is sized to 50MB - 3 groups)
db_files
etc.

Regards,

Akthar

      and we said...
There are never any rules of thumb for what to set these two.
The only "rules of thumb" are

o never make the SGA bigger then 50% of physical ram in
dedicated server mode.

o never make it bigger then 80/90% of physical ram in MTS mode.

db_block_buffer * 16k will be the amount of room you set aside
for caching database blocks.  You might consider starting this
at 150meg and watching your cache hit ratio over time.  Increase
within reason while the cache hit improves measurably.  Stop
increasing when it does not (or you start having too much memory
allocated to the SGA, starving other processes of needed RAM)

shared_pool_size, start at 50m and observe it.  make sure to
use bind variables in your program!!! or you'll never be able
to size this.  Watch your likbrary cache hit ratio to see if you
can improve it.  See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1516005546092
for more info on this topic.

sort_area_size, it TOTALLY application specific.  start at
64-128k and observe how many sort to disks you get.  Don't go
crazy with this one, keep it under 1meg over time.

Log buffer, since the log buffer is flushed every 3 seconds,
when 1/3 full and upon commit -- it is small.  1-2meg max is
typically sufficient.

db_files, you tell me.  how many do you need???

If you are using MTS, multi-threaded server, don't forget to
size and set the large_pool.

The java_pool will kick in at 20m, if you are not using java,
downsize that to 1m.
Review & Followup

      Rating: 5
Please help me
      to understand...  January 31, 2002
Reviewer:
      Senthil
Tom,

your comment...

>>The java_pool will kick in at 20m, if you are not using java, downsize that to
1m.

not using java...in the sense...means jdbc or Oracle's JVM. Sorry if i dont
understand correctly.

Thanks.

          Followup:

Not using Oracles JVM in the database, if you are not using java stored
procedures/EJBs in the database.

      Rating: 5
I got
      it  January 31, 2002
Reviewer:  Senthil

After reviewing your reply for other questions...i got solution for my previous
reply.

Thanks Tom.

      Rating: 5
Regrading the
      "sort to disks"  February 01, 2002
Reviewer:
      Pratap Deshmukh  from India
Hi Tom

Regrading your statement -
"sort_area_size, it TOTALLY application specific.  start at 64-128k and observe
how many sort to disks you get"

We can get the sort to disks by using autotrace, but is there any way we can
find it in the tkprof report ?

Thanks

Pratap

          Followup:

not really.  You might see it in physical IO's but you won't see it broken out
(you would have to guess based on a high physical IO rate that you are sorting
to disk).

You can look at v$sesstat for the session you are tracing as well - it'll have a
count of sorts to disk (thats where autotrace gets it from)

      Rating: 3
Regarding
      LOG  February 01, 2002
Reviewer:  JP
       from Calcutta
Hi Tom

Thanks for your suggestion.

Hard parsing is a very crucial factor for shared_pool management and using bind
variables we can make it under control.

In case of Oracle version 8.1.6 and above shared_pool fragmentation can be taken
care by fixing cursor_sharing=force.

Regarding Log Buffer if it is large in size, we can manage Log IO using
_log_io_size also.

Regards
JP
USHACOMM INDIA

      Rating: 4
Please explain
      it to us  February 02, 2002
Reviewer:  Edgar
       from Latvia
Hello Tom

Can you please explain why should we "never make the SGA bigger then 50% of
physical ram in dedicated server mode". And why do we have to keep
sort_area_size under 1mb? Maybe you can point me where can I read about it in
manuals?

          Followup:

In dedicated server mode, you will have a process / connection and the UGA (user
global area, your session state) is stored in that process (not in the SGA).
The other 50% of the machine not in use by the SGA is for the OS and dedicated
servers to have a bit of memory as well.

The 1m was directed at the "we are running high transaction (e_commerce)
application ....".  They are not DSS/Data warehouse, hopefully -- sorting would
NEVER need more then 1m in a high end OLTP.  In a data warehouse, the sort area
size might be in the 10's of megabytes.

      Rating:
      5
  May 24, 2003
Reviewer:
      A reader
In your 1 on 1 book, page 81:
".. The shared pool is characterized by lots of small (4 KB, or thereabout)
chunks of memory..."

Is the 4 k related to DB_BLOCK_SIZE somehow?

          Followup:

no it is not.

May 25, 2003 Ravi
      -- Thanks for the question regarding "Difference between "db file scatterd
      read" and "db file sequential read"", version 816

      You Asked
Dear Tom,

What exactly the Difference between "db file scatterd read" and
"db file sequential read" in v$sysstat. How to use these
parameters for tuning.

Are they associated with sort/fulltable scan/ index scan
operations.

Your comment on this will really useful.

Regards
Ravi

      and we said...
A db file sequential read is an event that shows a wait for a
foreground process while doing a sequential read from the
database.

This is an o/s operation, most commonly used for single block
reads. Single block reads are mostly commonly seen for index
block access or table block access by a rowid (Eg: to access a
table block after an index entry has been  seen)

This can also be a multiblock read. Then it will usually
be a read from a SORT (TEMPORARY) segment as multiblock
reads for table scans (and index fast full scans) usually
show up as waiting on "db file scattered read"

A db file scattered read is the same type of event as "db file
sequential read", except that Oracle will read multiple data
blocks. Multi-block reads are typically used on full table
scans. The name "scattered read" may seem misleading but it
refers to the fact that multiple blocks are read into DB block
buffers that are 'scattered' throughout memory.

Review &
      Followup

      Rating: 5
Wondering about
      multiblock and buffers  September 20, 2001

Reviewer:  MDz  from CA USA
Excellent clarification for the confusing names!  I was just wondering, when
doing a full table scan, how will blocks that are buffered (and potentially
changed) influence the multi-block read operation?  Particularly, say that
multi-block read count is 16, blocks 1 through 16 should be read, but block 10
is buffered - will it perhaps cause two multi-block reads, or several
single-block reads?   Will that be reflected in 'db file scattered read' or 'db
file sequential read'?

      Rating: 4
How can the
      sequential read be reduced?  October 09, 2001

Reviewer:  Rich  from Indy

      Rating:
      5
ANTON  November 30, 2002
Reviewer:
      Anton
How how we reduce this contention?

          Followup:

contention?  I see no contention here -- what do you mean?

If you mean "how can we reduce reads" -- you do that by tuning your physical
structures (storing stuff so it answers your questions efficiently) or tuning
your SQL or not being afraid of full scans -- in short, by reducing IO.

      Rating:
      5
  May 24, 2003
Reviewer:
      A reader
Hi Tom

WAIT #4: nam='db file sequential read' ela= 5 p1=1 p2=53903 p3=1

Is above wait mean Total time taken to read one index block(53903) is 0.05
second ?

Thanks

          Followup:

depends on the release.  In 8i and before, it'll look like this:

WAIT #4: nam='db file sequential read' ela= 2 p1=1 p2=26612 p3=1

and ela is in 1/100th of a second.  In 9i and up, it'll look more like this:

WAIT #1: nam='db file sequential read' ela= 12917 p1=1 p2=26866 p3=1

and ela is in 1/1,000,000 (so that was 0.012917 seconds of wait time)

May
      25, 2003 -- Thanks for the question
      regarding "empty result set and CASE statement", version 8.1.7.3

      You Asked
Hi Tom,

I tried the following in SQLPLUS connected to Oracle 8i

SQL> create table mytab (c1 int);

Table created.

SQL> insert into mytab values(1);

1 row created.

SQL> commit;

Commit complete.

SQL> SELECT CASE WHEN 1=1 THEN 99 ELSE COUNT(*) END AS x  FROM mytab WHERE 1=0;

         X
----------
        99

SQL> SELECT CASE WHEN 1 = 0 THEN 99 ELSE COUNT(*) END AS x FROM mytab WHERE 1 =
0;

         X
----------
         0

Can you please share with me that why we can get something from out of an empty
set?

Thank you in advance,
Winston

      and we said...
AGGREGATES always return

o at least one row
o at most one row

in the absence of a group by.

select count(*) from dual where 1=0;

that must return -- 0.  The count is zero.

select max( dummy ) from dual where 1=0;

that returns NULL - it must, there is no group by.

If you add a group by, then only rows for groups that exist are returned -- then
aggregation may result in an empty result set.

No group by + aggregate = at least and at most 1 row.

Review &
      Followup

      Rating:
      5
Thanks  May 26, 2003
Reviewer:
      A reader

May 25, 2003 Omar -- Thanks for the question regarding "OCP",
      version 8.1.5

      You Asked
Is it True That OCP Certificat has an expiration date or What.
please I need to know as soon as possible.

with respect

      and we said...
Well,

http://education.oracle.com/certification/cp.faqs.html#6
states that:

How do I maintain and upgrade my certification?
------------------------------------------------

Once you earn OCP status, your certification does not expire.
However, you may find it beneficial to keep your certification
current with the latest version of Oracle technology. You can do
this by taking an additional New Features exam to upgrade your
certification to the next technology version of your current
credential. For example, an Oracle7.3 DBA can upgrade to Oracle8
by passing one exam- Oracle8: New Features for Administrators.

If you have earned OCP status and Oracle announces that the next
version of the track will be retired, you will have until the
retirement date to upgrade to that version by passing the New
Features exam. For example, when Oracle announces the retirement
date for the Oracle8 track, Oracle7.3 DBA's will have to pass
the Oracle8 New Features exam by that date. Currently, Oracle
has no plans to announce the retirement of the Oracle8 track.

Review &
      Followup

      Rating: 4
Oracle 8i DBA
      Track exams are retiring??  May 24, 2003
Reviewer:
      A reader
Tom,
Is there any announcement on, that the Oracle 8i DBA Track exams are retiring,
If that has been announced or possibly there are chances of announcement in the
next 3/4 months please let me know.

I am planning to take Oracle 8i dba track exams this information would be
helpful to me.

Thanks,
Yakgna

          Followup:

I'm not affiliated with OCP at all -- you would want to see education.oracle.com
and use their contact links.

      Rating:
      5
  May 24, 2003
Reviewer:
      A reader
Tom, can you do this, can you do that, please can you take the test for me.

Come on guys!!

      Rating:
      5
  May 25, 2003
Reviewer:
      A reader
Hi Tom,

 If you need to hire DBA would you take OCP DBA With  1 year experience or 5
Year experience DBA with no OCP.

Thanks

          Followup:

I would take the better candidate -- the one who could communicate the best with
me, showed a really good attitude and could answer my questions.

Would you hire someone with a higher SAT score?  Better GMATS?  Or would you
interview them, consider their experience, their ability to actually translate
that experience into knowledge, their ability to communicate and so on?

      Rating:
      5
  May 25, 2003
Reviewer:
      A reader
Hi Tom,

In your response you told

"the one who could communicate the best with me, showed a really good attitude
and could answer my questions."

I am from india and my communication skill is not that good but i can under
stand all technical questions and really good dba knowledge (thanks to your
site).

Could you list your choice in ascending order like
1)answer your questions           50%
2)communicate the best with you   40%
2)good attitude                   10%   etc.......

The reason for this question is many time i know the answer but my comminication
skill is bad so i can't tell my boss...

Thanks

          Followup:

communication transcends spoken language sometimes.  A picture, a written
document -- they go along way as well.

if you cannot communicate well -- you cannot answer the questions (so, #1 is
predicated on #2).

I do not have a "percentage" ranking -- all three of those things sort of blur
together.

      Rating:
      4
good  May 26, 2003
Reviewer:  Pond
      Ka  from CHINA
So I won't care about my spoken english. I will spend more time on Oracle,
instead of spoken english. Thanks.

May 25, 2003 Krishna -- Thanks for the question regarding
      "Number Joins", version 8.1.7

      You Asked
Tom,
Let's say we have two tables with RI. Parent has a PK with datatype Number and
child wit FK with number datatype (10,0). As far as I know 10,0 is just an Edit
and oracle stores number in the same same way internally irrespective of edits.
Will there be a performance issue when you have the tables set in this way.
Thanks

      and we said...
if you are asking "is it OK for the pkey and fkeys to be at different levels of
scale/precision", see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7640334135875
It is OK.
May 26, 2003 Barry -- Thanks for the question regarding "SQL
      Tuning", version Oracle 8i

      You Asked
SELECT REC_ID,VENDOR,VENNUM,RECVDATE,TODOCDATE,IMPORTDATE
FROM TPENDING_IMPORT
WHERE TODOCDATE IS NULL

Here is the explain plan for the SELECT statement above:
ST_ID SELECT STATEMENT cost=4238
 2.1 TABLE ACCESS(FULL)-TPENDING_IMPORT

I have an index on todocdate in tpending_import table.
Why is the explain plan showing a full table scan on
tpending_import when I have an index defined on todocdate?

      and we said...
Nulls are not indexed in conventional btree indices.  There are
no entries for those NULL values.

In Oracle8i, you could index a function based index, such as

create index I on
tpending_import(nvl(todocdate,to_date('01010001','ddmmyyyy')))

And then a search:

select * form tpending_import
where nvl(todocdate,to_date('01010001','ddmmyyyy')) =
to_date('01010001','ddmmyyyy');

would be able to use the index.  See

http://osi.oracle.com/~tkyte/article1/index.html
for more info on that.

btw:  If I ever plan on doing "where X is null" on a column, I
make sure not to store NULLS in that column but rather some
value that means "NULL" to me to avoid this issue.  For dates I
always pick jan 1, year 1.  Its easy to remember and the odds of
my actually needing to store that date for real data is zero.
Review &
      Followup

      Rating: 5
still getting
      FTS  March 04, 2002
Reviewer:  Paul Druker
       from Fairfax, VA USA
Tom,

I'm trying to use your approach, however function-based index is still not being
used. What am I doing wrong?

Paul Druker

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.2.0 - Production
With the Partitioning option
JServer Release 8.1.7.2.0 - Production

pdruker@DEVL06> create table t (id number not null, dt date, owner
varchar2(30));

Table created.

pdruker@DEVL06> -- populate table with 5% of null dt values
pdruker@DEVL06> insert into t
  2  select rownum,decode(mod(rownum,20),0,null,created),owner from all_objects;

17730 rows created.

pdruker@DEVL06> commit;

Commit complete.

pdruker@DEVL06> create index t_idx on t(nvl(dt,to_date('01010001','ddmmyyyy')));

Index created.

pdruker@DEVL06> analyze table t compute statistics for table for all indexes for
all indexed columns;

Table analyzed.

pdruker@DEVL06> alter session set query_rewrite_enabled=TRUE;

Session altered.

pdruker@DEVL06> alter session set query_rewrite_integrity=TRUSTED;

Session altered.

pdruker@DEVL06> set autotrace traceonly explain
pdruker@DEVL06> select * from t where
nvl(dt,to_date('01010001','ddmmyyyy'))=to_date('01010001','ddmmyyyy');

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=7 Card=178 Bytes=3738)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=7 Card=178 Bytes=3738)

pdruker@DEVL06> set autotrace off
pdruker@DEVL06>
pdruker@DEVL06> select INDEX_NAME,INDEX_TYPE,LEAF_BLOCKS,DISTINCT_KEYS,NUM_ROWS
from user_indexes
  2  where table_name='T' and index_name='T_IDX';

INDEX_NAME                     INDEX_TYPE                  LEAF_BLOCKS
DISTINCT_KEYS   NUM_ROWS
------------------------------ --------------------------- -----------
------------- ----------
T_IDX                          FUNCTION-BASED NORMAL                48
1510      17730

pdruker@DEVL06>
pdruker@DEVL06> show parameter rewrite

NAME                                 TYPE                             VALUE
------------------------------------ --------------------------------
------------------------------
query_rewrite_enabled                boolean                          TRUE
query_rewrite_integrity              string                           TRUSTED

          Followup:

In this case, you don't want this index analyzed necessarily.  The optimizer
will see it as "non-selective" (it is a constant after all!)

So, if I:

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics for table;
Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where
  2  nvl(dt,to_date('01010001','ddmmyyyy'))=to_date('01010001','ddmmyyyy');

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=172 Bytes=3440)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=172 Bytes=3440)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=172)

it used it.  If I:

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

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where
  2  nvl(dt,to_date('01010001','ddmmyyyy'))=to_date('01010001','ddmmyyyy');

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1023 Bytes=20460)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=2 Card=1023 Bytes=20460)

it does not without a little prodding:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ first_rows */ * from t where
  2  nvl(dt,to_date('01010001','ddmmyyyy'))=to_date('01010001','ddmmyyyy');

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=15 Card=1023
Bytes=20460)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=15 Card=1023 Bytes=20460)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=3 Card=1023)

      Rating: 5
still getting
      FTS  March 05, 2002
Reviewer:  Paul Druker
       from Fairfax, VA
Thanks a lot for your followup. Unfortunately, I still can't reproduce your
results - and getting FTS while using nvl/to_date approach.

However, everything works fine if I use a function (like one you present on p.
296 of your great book).

pdruker@DEVL06> create index t_idx on t(nvl(dt,to_date('01010001','ddmmyyyy')));

Index created.

pdruker@DEVL06> analyze table t compute statistics for table;

Table analyzed.

pdruker@DEVL06>
pdruker@DEVL06> set autotrace traceonly explain
pdruker@DEVL06> select /*+ first_rows */ * from t where
nvl(dt,to_date('01010001','ddmmyyyy'))=to_date('01010001','ddmmyyyy');

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=7 Card=178
Bytes=3738)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=7 Card=178 Bytes=3738)

pdruker@DEVL06> set autotrace off
pdruker@DEVL06>
pdruker@DEVL06> create or replace function get_null (p_dt date) return number
deterministic
  2  is
  3  begin
  4    if p_dt is null then
  5      return 0;
  6    else
  7      return 1;
  8    end if;
  9  end;
 10  /

Function created.

pdruker@DEVL06>
pdruker@DEVL06> drop index t_idx;

Index dropped.

pdruker@DEVL06> create index t_idx on t(get_null(dt));

Index created.

pdruker@DEVL06> analyze table t compute statistics for table for all indexes for
all indexed columns;

Table analyzed.

pdruker@DEVL06> set autotrace traceonly explain
pdruker@DEVL06> select /*+ first_rows */ * from t where get_null(dt)=0;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=19 Card=8866
Bytes=186186)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=19 Card=8866
Bytes=186186)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=18 Card=8866)

          Followup:

what does select * from v$version return?

also, show parameter optimizer

I'm running 8172 and didn't have to do anything out of the ordinary.

      Rating: 5
v$version
      info  March 05, 2002
Reviewer:  Paul Druker
       from Fairfax, VA
I found that compatible parameter had a value of 8.1.6.0.0. Changed it to
8.1.7.0.0 - no difference

pdruker@DEVL06> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle8i Enterprise Edition Release 8.1.7.2.0 - Production
PL/SQL Release 8.1.7.2.0 - Production
CORE    8.1.7.0.0       Production
TNS for Solaris: Version 8.1.7.2.0 - Production
NLSRTL Version 3.4.1.0.0 - Production

pdruker@DEVL06> show parameter optimizer

NAME                                 TYPE                             VALUE
------------------------------------ --------------------------------
------------------------------
optimizer_features_enable            string                           8.1.7
optimizer_index_caching              integer                          50
optimizer_index_cost_adj             integer                          25
optimizer_max_permutations           integer                          80000
optimizer_mode                       string                           CHOOSE
optimizer_percent_parallel           integer                          20
pdruker@DEVL06> show parameter compatible

NAME                                 TYPE                             VALUE
------------------------------------ --------------------------------
------------------------------
compatible                           string                           8.1.7.0.0
pdruker@DEVL06>

          Followup:

and if you set the optimizer settings back to default...

optimizer_index_caching              integer     0
optimizer_index_cost_adj             integer     100

what happens then.

      Rating: 5
cursor_sharing
      needs to be EXACT  March 05, 2002
Reviewer:
      Paul Druker  from Fairfax, VA
Changing values of optimizer_index_caching and optimizer_index_cost_adj didn't
help, however I found the reason for this inconsistency. The value of
cursor_sharing parameter was equal to FORCE, changing it back to EXACT solved
this mystery.

Yet another reason for not using cursor_sharing=force (in addition to the side
effects already mentioned in your book).

          Followup:

page 447!!! I have that one there too ;)

didn't think about about that one in this case ;(

      Rating: 4
Core dumps from
      setting query_rewrite_enabled=true  October 10, 2002

Reviewer:  TS  from USA
Tom,

I set the following parameters for queries to pickup
function based index:-

SQL> alter session set query_rewrite_enabled=true;

Session altered.

SQL> alter session set query_rewrite_integrity=trusted;

Session altered.

I ran this query from sql prompt :-

SELECT DISTINCT B.OID, B.NAME TRANSIT_CITY, C.IMAGE_FILE TRANSIT_IMAGE_FILE
FROM BV_POI B, POI_IMAGES C, BV_CATEGORY D, BV_CONTENT_REF E, BV_ENUM_VALUES G
WHERE  C.OID=B.OID
AND E.OID=B.OID
AND E.PARENT_OID=D.OID
AND D.NAME='City-Filter'
AND G.TYPE_NAME='POI_IMAGE_TYPE_ENUM'
AND G.INT_CODE=C.RELATION_TYPE
AND G.VALUE='Transit'
AND UPPER(B.NAME)='Blah'
AND UPPER(B.CITY)='Blah'
AND B.STATE_ABBREV='AB'

and it gave me the following error :-
SELECT DISTINCT B.OID, B.NAME TRANSIT_CITY, C.IMAGE_FILE TRANSIT_IMAGE_FILE
*
ERROR at line 1:
ORA-03113: end-of-file on communication channel

Once I rollback the parameter changes, The query runs fine.
BTW, I run oracle 8.1.6.

Also, I set the above parameters for fun. based index and ran it on production,
The queries that need to pick up the function based index
did pick it up, but I got a lot of core dumps and eventually
had to rollback the parameter changes for function based index.

Could you please explain where the problem could be? Certainly setting up the
query_rewrite_enabled=true and query_rewrite_integrity=trusted helped us speed
up a lot of queries that used function based index, but it didn't last long.

Thanks,
TS

          Followup:

core dumps = we hit a bug houston = contact support (who will in this case most
certainly ask you to get to the supported release, 8174)

      Rating: 4
autotrace shows
      no stats!  January 20, 2003
Reviewer:
      A reader
Hi

working with Oracle 8.1.7.4 and Suse Linux Enterprise 7

Tying to tune some queries but I get no statistics when use autotrace!

SQL>select count(*) from used_cars;

  COUNT(*)
----------
   2713604

1 row selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FAST FULL SCAN) OF 'LUSA_CNIV_FK_I' (NON-UNIQUE)
          (Cost=4 Card=65352)

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

This happens when I am connecting locally (in the server). If I connect using
net8 I do get statistics. Is my Oracle broken? :-(

          Followup:

no, you are broken.

You are using SYS/Internal/SYSDBA to conduct your tests.

Never Never Never use Sys, Internal, Sysdba (or system, ctxsys, mdsys, etc etc
etc) to conduct tests with.  They are OURS.  Don't use them, leave them alone!

Log in as your own account.

      Rating: 5
something to
      clarify  May 25, 2003
Reviewer:  ZS
       from Australia
Tom,
You said:
"btw:  If I ever plan on doing "where X is null" on a column, I make sure not to

store NULLS in that column but rather some value that means "NULL" to me to
avoid this issue."

If that column is null most of the time (like 95%) would you still not use the
null? My understanding is that sometime it is better to leave the null value
even though you are planning to use "where X is null" because in this case you
actually sort of forcing the optimizer to do FULL SCAN which is more efficient.
I believe this is true for RBO, not sure for CBO.
Can you please clarify.

thanks,
ZS

          Followup:

if 95% of the data were NULL then yes, this would not be needed.

However, if 95% of the data were NULL and you did this, the CBO would do the
right thing and full scan (the RBO would be index crazy and use the index which
would not be the right thing to do)

May 26, 2003 Prashant -- Thanks for the question regarding
      "Menus and Running Reports", version Developer 2000

      You Asked
I am using forms 6 for win95, I am not able to run report from
menu. I used syntax

run_product(reports,'xyz.rep',synchronous,
runtime,filesystem,null,null);

It cannot compile it and says
too many declaration match this call unable to process request.

I have developed a simple menu and I want to run report from it
so that i can Integrate the whole system into one.

My reports do contain parameters but I want to run the parameter
for also that means wHen I choose the report from the menu I
should get the parameter form in which I would be able to put
the values and then run the report.

      and we said...
Because of the fact that the paramlist_name may be either a
VARCHAR2 or a PARAMLIST type -- this function is overloaded.
You are passing NULL to paramlist_name and it does not
understand which version of the procedure you want to call.  You
should invoke it via:

run_product(reports,
           'xyz.rep',
            synchronous,
            runtime,
            filesystem,
            '',
            null);

The help for run_product states:

....
Specifies the parameter list to be passed to the called product.
 Valid values for this parameter are the VARCHAR2 name of the
parameter list, the ID of the parameter list, or a null string
('').  To specify a parameter list ID, use a variable of type
PARAMLIST.
....

So, for this one, you need to either pass:

  ''
  to_char(NULL)

instead of just NULL so it knows to call the version of the
function that takes a string input.

Review &
      Followup

      Rating: 3
Similar
      question  October 23, 2002
Reviewer:
      mappiah@yahoo.com  from Ghana
Please help.
I want to create a report that will produce result in a master-detail format
which it was able to do correctly.
eg. Consider a table of Lecturer and that of students.
If we assume that the relationship between Lecturer(L) and student(S) is 1:M
then my report shld allow me to produce only students under a particular
Lecturer- This works perfectly.
But one more thing is that at the report parameter level I used one parameter
for Lecturer and another parameter for student.
Now what I want to do is that when the Lecturer parameter populates Lecturers
and one choose a particular Lecturer, it should pass a parameter to student
parameter query such that only students under that particular Lecturer are
populated.
Can anyone pls help!!!
Thank You.

      Rating: 4
REPORTS 6 (very
      urgent )  May 26, 2003
Reviewer:  umesh
       from bangalore india
i have to invoke reports 6i from outside ( i.e a jsp,java, oracle application) I
have a parameter to be passed to this reports 6i from a variable in jsp,java
code
the question i need help are
1. how do i invoke reports 6i from outside ( from java, jsp or c++ or what ever
i.e from outside developer 6i suite)
2. how do i pass parameters for it when doing question number 1
Thanks
( your help will help us clinching the deal )

          Followup:

1) it is a command line program, you would invoke it just like you would invoke
"ls"

2) see #2 -- just like you would invoke an "ls -lag" passing -lag in.  In c++
for example, this would just be:

   system( "ls -lag" );

May
      26, 2003 Ramana -- Thanks for the
      question regarding "When does the space occupied in temporary tablespace
      get released", version

      You Asked
When does the space occupied in temporary tablespace
gets released.  As soon as the transaction is over or later
some time it does internally by oracle
background processes?
If latter is true, how do I forcibly release the space from
temporary tablespace?

Thanks
Ramana Damarla
DBA

      and we said...
for true temporary tablespaces -- space is allocated the first
time it is used and then the temporary segments are managed
internally (eg: temp appears 'full' but its not, its alot like
rollback -- you might have 25meg of rollback allocated and
'full' but none of it is really being 'used', its all free).
this is to avoid the high cost of recursive SQL to manage temp
segments (which come and go) and the hit on the system
tablespace (uet$ and fet$ tables) this would cause.

If you really truely want to "free" the space (although the
space is in fact *free*, others will use it), you can alter the
temporary tablespace to be permanent and then alter it right
back to temporary.  you'll see it shrink in allocated space
right them (but you'll also notice the system getting pretty
slow about then as it happens and it'll take a while -- this is
*not* recommended).  Your database won't be any smaller, no one
will get more temp space then they could have before you did
this.

suggest you investigate the v$sort_segment view which maintains
the status of these allocated temporary extents.

for permanent tablespaces used as temporary tablespaces - the
space is freed automatically as soon as you are done using it
(but they will be slower overall then true temporary
tablespaces).

Review &
      Followup

      Rating: 4
Free UP Space
      in Temp Tablespace without shutting the instance  April 17,
      2003
Reviewer:  Vivek Sharma  from Bombay, India

Dear Tom,

I have a Temporary Tablespace which is of type Temporary. To free up the space
from this tablespace, I follow this simple steps and notice that the free space
shoots up. I change the pctincrease of the tablespace to 1 and then issue
coalesce command 2-3 times and again change the pctincrease to 0.
This knowledge was shared to me by one of my fellow DBA but I am unaware what
oracle does internally to free up the space.

Can u please share ur knowledge on this with some example and also please let me
know whether the steps are correct. Is there any performance issues. I do it
normally in the night when no one is connected to the database. Can it be done
in the day time when there are maximum users ?

Thanks in Advance.
Vivek Sharma

          Followup:

you have a PERMANENT tablespace whose type is TEMPORARY.

What you are doing is SELF DEFEATING, you have not "freed up" anything. what you
have done is "undone" the expensive work of allocating the extents.  Now you
just have to allocate them all over again!

You see, a temp segment is made up of many extents.  In order to remove the
large overhead of allocate/free/allocate/free/allocate/free extents -- we
allocate the extent ONCE and keep it.  We then manage the extents in
v$sort_usage/v$sort_segment.  The next time someone needs temp space, instead of
allocating an extent -- we simply find one we already allocated and give it to
them.

What you have done effectively and brutally defeats this optimization.

What you SHOULD do is

a) use true temporary tablespaces.  They will be locally managed, with uniform
extent sizes

b) stop doing the self defeating thing you are doing now.  It is just wasting
cycles on your machine, making things take longer in the morning, and providing
nothing beneficial in return!

      Rating: 4
alter the
      temporary tablespace to be permanent  May 26, 2003

Reviewer:  A reader
<quote>
If you really truely want to "free" the space (although the space is in fact
*free*, others will use it), you can alter the temporary tablespace to be
permanent and then alter it right back to temporary.  you'll see it shrink in
allocated space right them
</quote>

just curious,..
how to convert the temporary tablepsace to permanent and then back to temporary.
i tried but i could not.

ge$kumar@devadw.world>alter tablespace TEMP PERMANENT;
alter tablespace TEMP PERMANENT
*
ERROR at line 1:
ORA-03217: invalid option for alter of TEMPORARY TABLESPACE

          Followup:

that works only for temporary tablespaces that can be permanent.

eg: if you use create tablespace temp .... temporary; -- you can alter that to
be permanent.

if you use create TEMPORARY TABLESPACE,  you cannot.

May 26, 2003 Shrek -- Thanks for the question regarding
      "Interviews: Knowledge of tools Vs. concepts?", version 8.1.7

      You Asked
Hi Tom:

What is the priority you place while selecting candidates? is it knowledge of
tools such as Developer 2000, designer 2000, Powercenter? cognos? and if so,
what would be the weightage you give for knowledge of specific tools V.
knowledge of database concepts? For example: I am an experienced modeler in
ERWIN but when I went to an interview recently I was asked about CRUD (something
specific to Designer 2000) which I didn't know about at that time.

It is almost impossible to keep up with all these new tools because when we get
to a contract, the most important priority is to provide a solution to a problem
at hand, and the major effort has little or nothing to do with the use of a
tool.

Could you care to comment? what is the weightage you would provide during an
interview for Tools Vs. Concepts.

Thanks,

Shrek

      and we said...
depends on what I'm interviewing the person for doesn't it.

Cognos, that would be meaningless to me right now, in my position at Oracle
corporation developing internal systems.

Discoverer on the other hand -- that would be meaningful.

However, if I quit Oracle and worked for company X with a large investment in
Cognos as the tool of choice, discoverer experience might be irrelevant and that
cognos background becomes appealing.

CRUD is not a tool and certainly is not specific to D2k -- CRUD is

Create (insert)
Read   (select)
Update (update)
Delete (delete)

D2k can generate default CRUD packages for objects -- so you can inject
auditing/security checks/whatever and use the packages to insert/update/delete
records instead of straight SQL.

CRUD is as old as dirt -- it is an acronym.  search google for

crud database

You sort of answered your own question here though -- when I see:

"... because when we get to a contract,"

I have to assume you are not going for long term employment with a company.  You
do not plan on spending 10 years there working for them.  You are going to sign
up for a contract, get an assignment, give them your experience, turn out a
result and move on.  They are not investing in you long term, you are not
investing in them long term.  As a contractor/consultant  -- you are expected to
hit the ground running (that is the purpose of contractor/consultants -- they
already are experienced in the tools required to work on the job).

tell me, would you take your brand new BMW with all of its computers to the
mechanic who was "willing to learn" -- or would you take it to someone who was
trained and proficient in fixing brand new BMW's?

In the line of work you are in -- consulting (i'm guessing based on that
contract comment) -- you are expected to be 100% knowledgable in the tools in
place, if not -- I myself would keep looking for a consultant that was.  Why
would I pay you to learn the tools I'm using.  I want to pay you to USE the
tools I'm using, pay you for as short a period of time as possible and move on.

Now, if you were interviewing with company XYZ and planning on being part of
their IT infrastucture for N years -- it would be totally different.  Then I
would look at the entire package -- "do you know data modeling", great -- that
is what I need to know, we can teach you the tools we use and since you'll be
here N years, we'll get our investment back.

So, if you are going for contracts as a consultant -- you need to have the EXACT
skills I'm looking for -- today.  That is why I pay you the premium.  I might
even overlook some personality issues cause I know you are going to be there for
a couple of weeks/months.  I can (have) live with some things like that for a
short period of time.

If you are looking to work for me for the next 10 years, a totally different set
of criteria would be used to evaluate you.  Personality issues would be
infinitely more relevant -- I would look to your ability to communicate, to
participate, to learn and what you already know.

Review &
      Followup

      Rating: 5
Another
      priority  May 25, 2003
Reviewer:
      A reader
Great answer. Now there is another related question. Interviews, by their
nature, are heavy in "talks" but with little, if any, "walks". There are plenty
of cases that some candidates can talk the talk but can't walk, at least not
nearly as good as what they talk. What are your suggestions in identifying such
cases from interviews ONLY (if it's possible)? On the other side of the table,
how can job applicants show what they can do without really "showing"? By
showing I mean a real life test scenario (could even be hands-on), which is not
feasible in a "talk-only" interview.

          Followup:

Database people -- I always have them draw a picture of Oracle on the whiteboard
and explain how it works - show me the interactions of dbwr, lgwr, arch,
dedicated servers, dispatchers, shared servers, whats in the SGA, how does it
work.  That would be for anyone who says "I know Oracle as well as you can know
Oracle".  DBA types I might go onto what is the process of recovery -- what are
the rudimentary steps and so on.  Developer types -- I give them a simple SQL
quiz.

Actually, I find if you can talk the talk with someone who really knows -- you
can walk the walk.  It is when you are interviewing someone for a position --
and you yourself are not able to walk the walk that you get "snowed".  For
example, if you yourself cannot draw the picture of Oracle on the whiteboard,
you cannot have someone else do it for you -- they could be totally wrong and
you'd never know.

Case in point, recently interviewed a guy -- it was for a position in technology
"I know about" but am not "in depth in".  We always interview in groups (at
least I do, i like that better myself).  In our group was someone who knew the
technology really deep.

When we were done, I was modestly impressed -- the candidate appeared to know
the subject matter.  However the guy who really knew the technology was able to
show us that guy was very shallow -- knew the buzzwords, knew what to say  - but
didn't really know the technology.  In short, he knew more then I did but lots
less then he would need to.  Only by having a subject matter expert there can
you catch that.

      Rating: 5
Average
      consultant vs excellant consultant  May 25, 2003

Reviewer:  A reader
From what I have seen in the tristate.Money is the most important factor in this
capitalistic corporate world.
They usually go for cheap consultants who can do the job average instead of
highly qualified consultants who come at a high price.Even IBM does
that.Moreover Consultant is always under pressure to perform.Even when it comes
to laying off a consultant the average ones stays and the exceptional one is
laid off.
Technical abilities is considered later, an average is fine.Main criteria how
cheap you are.

      Rating: 5
Your opinion
      counts  May 25, 2003
Reviewer:  Shrek
       from DC
As most of us who are familiar with your web site would agree, your opinion
counts. Thanks.

I was specifically interested in your response to selection of candidates for
long term positions, but inadvertently mentioned contracting and got your
response to that as well.

For a long term position, you said that: "personality issues would be infinitely
more relevant -- I would look to your ability to communicate, to participate, to
learn and what you already know."

However, I wonder if some of these atributes are measurable, and if so, the
measurement seems somewhat subjective.

          Followup:

the measurement is by definition 100% subjective.

I like some people.
I don't like others.

The ones I like -- others don't.
The ones I don't like -- others do.

Then there is the culture of the company to consider as well -- again, totally
subjective there.  I really like the culture inside Oracle here, but I've met
many people who don't as well.

It is about fitting in.

      Rating: 5
Oracle Instance
      9i R2  May 26, 2003
Reviewer:  A reader

From the Oracle Concepts document:
“Each Oracle instance can use several background processes. The names of these
processes are DBWn, LGWR, CKPT, SMON, PMON, ARCn, RECO, Jnnn, Dnnn, LMS, and
QMNn.”
It states QMNn and Dnnn are the optional, but does not states Jnnn, ARCn, LMS.
If I do not need the archive mode, AQ and are not using RAC.  Are  Jnnn, ARCn,
LMS required to be present as the Instance up? I also have fund Oracle created
one shared-server as default DB creation for 9i R2. Can I disable it as set
parameter SHARED_SERVER=0, if I do not use MTS?
Thanks
Sean

          Followup:

many of those processes are optional, yes jnnn (job queues), arcn (archiver),
and lms are optional

you can turn off shared server (you were asked during DBCA creation whether you
wanted it or not) via the mts_* parameters, yes.

May 26, 2003 Raghu -- Thanks for the question regarding
      "Create Constraints Script", version 8.1.7

      You Asked
Tom

During the course of my job, I have to drop a couple of tables and build them
again.

I'm looking for a script which takes USER and TABLE NAME  as parameters and
gives Creat Constraint DDL for all the constraints on the table i.e. primary key
and foreign key.

Do you have any such utility script.

Thank you

      and we said...
why not just truncate them?

disable the fkey constraints that point to this table (simple query on
user_constraints)

truncate them

enable the constraints

don't drop and recreate them, you'll "lose" things like grants and the like.

exp rows=n table=table userid=owner
drop table
imp full=y userid=owner

is the script that does EXACTLY what you are trying to do already.  (eg: its is
already written)
May 26, 2003
      bhabani -- Thanks for the question
      regarding "sga size", version 8.1.7.0.0

      You Asked
Hi Tom

In one of our databases i found the following information. How that is possible?

-------------------------------------------------------------
SQL> sho sga

Total System Global Area  575160480 bytes
Fixed Size                    73888 bytes
Variable Size             155475968 bytes
Database Buffers          419430400 bytes
Redo Buffers                 180224 bytes

SQL> select 575160480/1024/1024 from dual;

575160480/1024/1024
-------------------
         548.515778

SQL> !/usr/sbin/prtconf | grep size
Memory size: 512 Megabytes

SQL>
-------------------------------------------------------------------

As such what is the optimal size of SGA for aa available RAM size?

Thanks and Regards

      and we said...
operating systems employ virtual memory.  You have swap configured.

As your SGA exceeds physical memory -- you are running in what I would term a
"suboptimal configuration"

You need to leave enough free memory for

a) the operating system (64-128meg at least)
b) your dedicated servers (depends on how many and what you do)

but your SGA probably should be under 256meg.
Review &
      Followup

      Rating:
      5
  May 26, 2003
Reviewer:
      A reader
Thanks Tom

      Rating: 5
Why
      256M?  May 26, 2003
Reviewer:  Kamal Kishore
       from New Jersey, USA
Hi Tom,
When you say:
"but your SGA probably should be under 256meg. "
Is this a recommendation for the original poster of question, since the system
memory is 512MB
OR
Is this a recommendation for even larger systems having more memory and power?

IS there something like TOO-MUCH when it comes to SGA allocation? Should you
stop allocating more memory to SGA even if your system has plenty of free ram
available? If we should, how to know when to stop?
Thanks,

          Followup:

I'm just thinking

512meg total ram...

say 128meg for the OS....
say another 128meg for the dedicated servers

leaves a max of 256 I might want to give to an SGA.

if you are on 32 bit code, somewhere between 1.75 and 4gig will be your max sga.
On 64 bit the sky is the limit.

can having too large an sga cause performance issues?  sure, you should see what
happens when you

a) have a transactional system
b) that doesn't use binds
c) and has a really large shared pool

"grinding to a halt" comes to mind....

      Rating:
      5
  May 30, 2003
Reviewer:  Alexey
       from Russia

May 26, 2003 Tony -- Thanks for the question regarding "Doubt
      about shared sql", version 8.1.6

      You Asked
Hi Tom,
If in one session I issue a query, which already exists in SGA, and in other
session I create an index, how does the optimizer knows about this new index
without doing the hard parse? , and why optimizer will do hard parse when it
alreday exists in SGA?
Thanks

      and we said...
if you

a) issue a query which already was parsed
b) create an index

then the query in a) will not use that index (it did not exist)

if you

a) create an index
b) issue a query which goes after the table you just indexed

then a) would invalidate the plans in the SGA, causing B) to hard parse the
query again (eg: a got the plan out of the sga).  The index may be used (or it
may not be)

Review &
      Followup

      Rating: 4
breakable parse
      lock  May 26, 2003
Reviewer:  Reader
       from Houston, TX
As you explain in your book, does breakable parse lock take care of this
invalidation of the plan in shared pool?

          Followup:

the DDL takes care of invalidating everything that needs be invalidated.  One of
the mechanisms will be locks like that yes

May 26, 2003 Ron
      -- Thanks for the question regarding "VPD and bitmap indexes", version
      8.1.6

      You Asked
Hi Tom,

We are moving towards using Oracle's VPD feature for a currently running
production (OLTP) system. The idea is that the functionality will be used by
approx. 40 - 50 different companies.
Every (application)table will have a new column, let's say COMP_ID.

My question now is (and I know your opinion on the use of bitmap indexes in OLTP
systems) wouldn't it be a good idea to actually use bitmap indexes on these
COMP_ID (VPD) columns, just because of the relatively low cardinality?

Thanks in advance for your time.

      and we said...
NO

bitmaps are never never appropriate in a transactional system, never, not in any
circumstance.

COMP_ID will just become an attribute ADDED to your existing indexes -- you
won't really be indexing COMP_ID by itself.

Lets say you have a table emp:

create table emp ( empno   number,
                   ename   varchar2(30),
                   job     varchar2(10) );

and you have:

alter table emp add constraint emp_pk primary key(empno);
creaet index emp_ename_idx on emp( ename );

Now, you add that comp_id, you'll end up with:

alter table emp add constraint emp_pk primary key(EMPNO,COMP_ID);
create index emp_ename_idx on emp( ename, COMP_ID);

because you query that was:

select * from emp where empno = :x;

will now be equivalent to:

select * from emp where empno = :x and comp_id = sys_context(...);

your query:

select * from emp where ename = :x

will be treated likewise.
Review & Followup

      Rating:
      4
  May 26, 2003
Reviewer:  Dave
       from Colorado
Would you consider partitioning the table by COMP_ID?

          Followup:

sure, you can. works best if you have a static number of comp_ids of course.
harder (more admin) if you have to add them on the fly over time.

      Rating:
      5
  May 26, 2003
Reviewer:
      A reader
Great !!!

      Rating: 4
What about
      compression  May 26, 2003
Reviewer:  Scott
      Watson  from Canada
Wouldn't it be better to create the index with compression in mind such as

comp_id, empno and compress 1?

          Followup:

yes, that would work dandy

      Rating: 4
FGAC-generated
      predicate/SQL  May 26, 2003
Reviewer:
      Robert
Hi Tom, re-read the chapter on FGAC in "EOOO"
this weekend...
Question:
1) Is there any way to get either the predicate generated by
the security policy functions (e.g those functions in
package hr_predicate_pkg on p931) or the full SQL ?

2) when are the security functions actually executed ?

I tried the following...
- placing dbms_output in the functions: nothing ever came up, so no idea when
they got executed.
- v$sqlarea/v$sqltext shows only the VPD SQL ("select * from emp")
- tkprof also shows only "select * from emp"

pls clarify this, as I have the "habit" of insisting on seeing the full
submitted sql in my programming.

Thanks

          Followup:

1) see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1969395750585#9841925884656
2) when you parse.  dbms_output in FGAC is *in another session really* you'll
never see it.

May
      27, 2003 Om -- Thanks for the question
      regarding "About Rollback segments", version 8.1.7

      You Asked
Hi Tom,

How are you doing !

It's nice to get another opportunity to ask a question from you.

This Quest is related with rollback segment.

The database size which I am dealing now a days is 30 Gb to
50 Gb in size, and generally whenever I perform any big DML
operation i.e delete (deletion of around 70-80000) records
at one go ,I usually get rollbak segment Error.

Is there any standard to make calculation keeping in mind the
Transaction and the size of the rollback segment to find out
whether the DML operation will be a success or a failure.

Can you please explain the extents and pctincrease concept in
Rollback segment and in what all cases I should go ahead and
make changes in the extents.

Warm Regards,
Om

      and we said...
Well, for an INSERT you need to have the least amount of room as the UNDO
generated is only the UNDO to delete a row.  The rowid is the UNDO (although if
you have indexes on this table -- you will generate potentially LOTS of UNDO for
it as it takes more to undo the inserts from the index)

For an update you need enough undo to hold the before images of the modified
columns plus the updates to the index structure.

For the delete, you need the most rollback space -- the entire row goes there as
does all of the changes to the index to delete those entries.

You might try deleting a couple of hundred rows and then query up v$transaction
for your session:

  1  select used_ublk
  2    from v$transaction
  3   where addr = ( select taddr
  4                    from  v$session
  5                   where sid = ( select sid
  6                                   from v$mystat
  7*                                 where rownum = 1 ) )
ops$tkyte@ORA817DEV.US.ORACLE.COM> /

 USED_UBLK
----------
       252

to see the number of blocks of rollback you generated.  You can extrapolate up
from there.

What I like to do -- use X number of rollback segments.  Each RBS is in its own
tablespace with its own datafile.  The datafiles are autoextensible to some max
size.  They all start with the same number of equi-sized extents.  I let them
grow as big as they need be (within the limit I set on the datafile).  Before
backups or when we are bored, we offline them, drop them, shrink the datafiles,
rebuild them -- IF they grew too large.

Review &
      Followup

      Rating: 4
About used
      blocks in the answer  October 12, 2001
Reviewer:
      Om Lalwani  from UK
I have read the answer and have got good idea as to what
cn be done to make prevent rollback segment error .

Can you please explain how should I interpret USED-BLKS
in the Query mentioned.What is the relation between the
no of extents ,used_blocks and the size of the rollback
segment.

Also you have mentioned that you make use of more than
one segment to achieve big results.
With

SET ROLLBAK SEGMENT R1 FOR LARGE_RBS

OPTION WE CAN SPCIFY ONLY 1 ROLLBACK SEGMENT.Therefore
how can I make sure that for my current BIG transaction
more than 1 Rollback segment should be used.

Warm Regards,
Om

          Followup:

An extent is made up of blocks.  Say you have 640k extents and an 8k blocksize.
You have 80 blocks / extent.  If your delete will need 8,000 blocks -- you need
10 extents at least in your RBS to handle it.

I mentioned I made use of more then one rollback segment -- period.  I didn't
say "to achieve big results".

You can only use 1 rbs/ transaction.  Period.... Well -- if you PARTITION the
data and do a parallel delete -- each thread/process doing the delete can
actually use a different RBS.

      Rating: 4
used_ublk is
      negative for some transactions  October 16, 2001

Reviewer:  Sameer Utrankar  from NJ
When I query v$transaction to see how current transactions are using Rollback
Segments, I sometimes transactions with used_ublk < 0. What does that mean ?

          Followup:

Rolling back to a savepoint in 8i and before can cause negative values here.

ops$tkyte@ORA717DEV.US.ORACLE.COM> create table t ( x int );

Table created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> savepoint a;

Savepoint created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> insert into t values ( 1 );

1 row created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> rollback to a;

Rollback complete.

ops$tkyte@ORA717DEV.US.ORACLE.COM> insert into t values ( 1 );

1 row created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> rollback to a;

Rollback complete.

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> select used_ublk
  2    from v$transaction
  3   where addr = ( select taddr
  4                    from  v$session
  5                   where sid = ( select sid
  6                                   from v$mystat
  7                                  where rownum = 1 ) )
  8  /

 USED_UBLK
----------
        -1

that behavior is corrected in 9i

      Rating:
      4
Reader  October 16, 2001
Reviewer:
      Reader  from USA
Tom,

"
An extent is made up of blocks.  Say you have 640k extents and an 8k blocksize.
You have 80 blocks / extent.  If your delete will need 8,000 blocks -- you need
10 extents at least in your RBS to handle it.
"
Is this the rule of thumb to have at least 1/10 the size of
undo?

"
You can only use 1 rbs/ transaction.
"
Is there a downside in configuring say 4 concurrent TXs
per RBS

Thanks

          Followup:

No -- its not the rule of thumb (and how i hate rules of thumb).

It was simply an example with numbers that were easy to divide/multiply with.

When I said "you can only use 1 rbs/transaction" I meant that YOUR transaction
can only use 1 rbs -- it cannot span an RBS.

It is very very very common to have many (dozens) of transactions all happily
using the same rbs concurrently.

      Rating: 5
create rollback
      segment statment  October 17, 2001
Reviewer:
      olaf  from germany
Tom, to create rollback segmemt you have recommended early:

create tablespace RBS_01 datafile '..' size 25064 K reuse autoextend on maxsize
100 MB extent management local uniform size 1M;

create rollback segment rbs_01 storage (minextents 20 maxextents 255) tablespace
RBS_01;

I don't undestand the use of maxextents clause hier. I think Oracle should
ignore this for LMT's.

          Followup:

It does (well, it appears to limit them to 32k max extents, not 2billion like
other segment types) - and in this case, the filesize is the gating factor (we
could never get 255 extents since the maxsize is 100m)

It is not relevant in this case because the file will stop it from exceeding 100
extents.

      Rating:
      5
  October 17, 2001
Reviewer:  Olaf
       from Germany
What is with minextents hier? Will Oracle create 20X1MB extents by creating
rollback segment?

          Followup:

Yes, it will create 20, 1 meg extents.

      Rating: 5
Rollback
      segments  May 26, 2003
Reviewer:  atul
       from India
Hi Tom,

We are going to upgrade database from 8.1.6 to 8.1.7
by running upgrade script...

Before running script i want to check is there sufficient rollback space ...,

What is the best way to do that??
Also what other space related precautions i have to take?

Thanks.
atul

          Followup:

You should read the upgrade readmes, see what they say as far as "minimums" go
and then query the data dictionary to see that you have that much space.

If you observe the documented limits -- in the OS specific docs for the upgrade,
that is what you need to do.

      Rating: 4
undo segs vs
      rollback segs?  May 27, 2003
Reviewer:
      trinh
Statspack for 9i, there are shown two catagories:
1)Rollback Segment Stats for DB: ....
2)Undo Segment Stats for DB: ....
Why are there the two? I thought they should be the same?

Thanks

          Followup:

undo segments have different metrics then rollback segments.  undo segments are
sized and grow based on a retention period which is just a suggestion.  So, an
undo segment might "expire" an extent before its time -- that is something you
want to be aware of (that you are expiring extents before their time -- you have
insufficient space for the retention period you specified).  A rollback segment
would have no such metric for example.

Same things, measured with different yardsticks -- to different metrics.

May 27, 2003 Andre -- Thanks for the question regarding
      "Sizing the recordset for the application", version 8.1.6

      You Asked
Tom,

If I'm not mistaken, every time a select statement does a fetch step, a number
of rows called recordset or array are brought in from the database engine. And
this chunks are temporarily stored in the PGA in the cursos state area. When I,
for example, explicitly do a FETCH command via a cursor, this statement gets the
current line from this array or recordset.

My questions are:

1) Is there some way to configure the array size or recoreset size in the
database ?

2)How ?

3)And what implications does this sizing have ?

Thanks a lot !

      and we said...
1) the arraysize is a client thing, its set by the client.  In pro*c you use the
prefetch option on the command line or use arrays.  In plsql you BULK COLLECT
with the LIMIT clause.  In java, the default prefetch is 10 and is settable at
the statement or connection level.  In sqlplus, set arraysize N.

2) depends on your language/environment

3) it simply tells us how many rows to process and fetch in the server every
time you FETCH data in the client.  Using an array fetch can speed up processing
many times.
Review & Followup

      Rating: 4
But...
        October 10, 2001
Reviewer:  Andre Whittick
      Nasser  from Rio de Janeiro. Brazil
Proceeding...

a) If the cursor state area is stored in the PGA, shouldn´t it be a server
process component, not an application element ?

b) Or does this cursor state area serve another purpose, not exactly what I
thought, i.e., bringing in result sets ?

c) Is there some difference between the terms record set and result set (this
last one is found in the PL/SQL literature as to cursors).

          Followup:

a) server has to batch them up in response to a client requesting it.  The
CLIENT initiates the batch size (the client must be prepared to recieve 1 row or
100 rows).  The server just responds.

So no, it should not be a server process setting -- its all in the client.  You
need to have the ability to set this statement by statement or connection by
connection.  One size does not fit all and not all clients are capable of
processing array fetches.

b) see a

c) record set, result set, ref cursor -- they are all fairly synonomous.  They
are the output of a query, the rows you get back.

      Rating: 5
what arraysize
      for a db link ?  May 27, 2003
Reviewer:  Alberto
      Dell'Era  from Milano, Italy
Hi Tom,

but how big is the arraysize used for a connection to another db through a
db-link ?

E.g. if I issue, in sqlplus

create table local_t as select * from remote_t@db_link;

Which arraysize will be specified to the instance pointed by "db_link" when our
local instance fetches the rows of remote_t on our behalf ?

Many Thanks
Alberto

          Followup:

there really "isn't one".  it is all done internally.

May 27, 2003 B --
      Thanks for the question regarding "Few questions", version 9.x

      You Asked
Hi Tom,

1). I have read that RMAN can operate without a recovery catalog and that in
this situation certain important things which can be done with receovery catalog
are disabled. Can you please highlight what all can be done by RMAN without a
recovery catalog and in what situations is that advisible .

2). Table A structure is
    A    number
    B    varchar2(10)
    ....
    ....
    ....
    X    number

Table B structure is
    A    number
    B    varchar2(10)
    ....
    ....
    ....
    X    number
    Y    number
    Z    varchar2(100)

Now my requirement is that frequently I require inserting into table B by
selecting records from table A. NOte that for last 2 fields I need to feed some
static session values (like sequence numbers etc). Can I have a generic way of
doing this, someting like ....

insert into B select *,10,'TEST' from A;

3).  While allocating a channel I receive the following error. Please tell me
the reason for this. (Is it that all such things can be done only in RUN
construct, as if I do it in that I do not get any error).

RMAN> allocate channel chan1 for delete type disk;

RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-00579: the following error occurred at 05/20/2003 06:49:36
RMAN-00558: error encountered while parsing input commands
RMAN-01005: syntax error: found "identifier": expecting one of: "for"
RMAN-01008: the bad identifier was: channel1
RMAN-01007: at line 2 column 18 file: standard input

Many Thanks for your help.
BS

      and we said...
1) check it out, a section entitled:

Deciding Whether to Use RMAN with a Recovery Catalog

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96566/rcmquick.htm#442214
in the RMAN

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96566/toc.htm
users guide!

2) insert into b select a.*, 10, 'test' from a;

however, if you are going to do this in real code, just take the time and DO IT
RIGHT

insert into b ( a, b, c, d, e, f, g, ..... )
select a, b, c, d, ....., 10, 'TEST' from a;

"generic" is great
"generic" will burn you in the end.
been there, done that.  just do it the right way.

3) well, i have a feeling we are looking at doctored text, not a straight cut
and paste.  I don't see channel1 in the command at all -- do you?

but it does seem to be missing a "device type".  not really sure what you were
trying to do there.  maybe you really mean to use allocate channel for
maintenance.

Review &
      Followup

      Rating: 5
questions
        May 26, 2003
Reviewer:  Tanmoy Datta
      Choudhury  from india
hi tom,
i have a table t_con having following data ...

SQL> select *From t_con;

CO IT      VALUE
-- -- ----------
c1 i1         10
c2 i2         15
c2 c1          2
c3 c1          1
c3 i2         20
c4 c3          2
c4 c2          3

now i should write a query to get the result set like this ...

for c1 result should be i1  ---------  10
for c2 result should be i2 ----------  15
                        i1 ----------  20
and like this for others .....

is it possible to do in sql ....

now i tried like this

SQL> select con,item,sum(value) v
  2          from T_CON
  3     where con in (select distinct con  from T_CON)
  4     and con='&v_con'
  5     group by item,con
  6  /
Enter value for v_con: c1
old   4:    and con='&v_con'
new   4:    and con='c1'

CO IT          V
-- -- ----------
c1 i1         10

SQL> /
Enter value for v_con: c2
old   4:    and con='&v_con'
new   4:    and con='c2'

CO IT          V
-- -- ----------
c2 c1          2
c2 i2         15

SQL> /
Enter value for v_con: c3
old   4:    and con='&v_con'
new   4:    and con='c3'

CO IT          V
-- -- ----------
c3 c1          1
c3 i2         20

SQL> /
Enter value for v_con: c4
old   4:    and con='&v_con'
new   4:    and con='c4'

CO IT          V
-- -- ----------
c4 c2          3
c4 c3          2

but when in item there is some container i have to recursively execute the same
query and sum the values ...

any suggesstion tom ...

waiting for your reply ...

          Followup:

insufficient data.  what are you trying to do?  is 20 for the second i2 the
product of the prior values (if so, why not say that).  two observations are
insufficient to conclude a pattern here

      Rating: 5
requirment
        May 26, 2003
Reviewer:  Tanmoy Datta
      Choudhury  from bangalore,india
sorry tom ....as u couldnt understand my reqs ..
please again have a look
SQL> select *From t_con;

CO IT      VALUE
-- -- ----------
c1 i1         10
c2 i2         15
c2 c1          2
c3 c1          1
c3 i2         20
c4 c3          2
c4 c2          3

now i should write a query to get the result set like this ...

for c1 result should be i1  ---------  10
for c2 result should be i2 ----------  15
                        i1 ----------  20
for c3 result should be c1+20I2 = 10I1+20I2 like this
becos c3 will show me -
SQL> select con,item,sum(value) v
  2          from T_CON
  3     where con in (select distinct con  from T_CON)
  4     and con='&v_con'
  5     group by item,con
  6  /
Enter value for v_con: c3
old   4:    and con='&v_con'
new   4:    and con='c3'

CO IT          V
-- -- ----------
c3 c1          1
c3 i2         20

if item is having any container then i have to find the item
for that particular container ...

so if we see the table for c3 we have values
c3 c1          1
c3 i2         20
from here c3= c1+20I2
so we have to again find the value of c1 from table
and display ....
now for c4
so we can say c4 = 2c3 = 2(c1+20I2)
again c1 value is i1 10
                     = 2(10I1+20I2)
                     = 20I1+40I2

now again for second 3c2 =
from table c2 having - i2         15
                       c1          2
so it can be c2=15I2+2c1
again for c1 values like that ..
i have to show the result in terms of item I1 and I2 and
have to sum the same items ..

hope u will undestood ..sorry for waste of your time again

          Followup:

how did you get 20 for

for c1 result should be i1  ---------  10
for c2 result should be i2 ----------  15
                        i1 ----------  20   <<<<========

      Rating: 5
hope this will
      help  May 27, 2003
Reviewer:  Tanmoy Datta
      Choudhury  from bangalore,india
tom
for c2 the result set is
c2 c1          2
c2 i2         15
so c2 = 2c1 = 2(10I1)  as c1  i1 10
            = 20 I1
the result set should come as per item not container ..
c ->container and I->item ...
so if item contains some container we have to find the item for that and we have
to mutiply the value of that container ...

      Rating: 3
Excuse me for
      Interrupt by External table   May 27, 2003

Reviewer:  Oracle'User
Hi Tom ,
   I hvae any problem about loader into External data.
 And I already serch information in yours old answer but not macth with my
prablem.

   I have Data type Date in Format "RRRRDDD" , and I have External Script like
this ..

.....
DTE_BIRTH        position(163:169) date "RRRRDDD" NULLIF DTE_BIRTH= '0000000' or
GAIN_DTE_BIRTH    = '1111315' or GAIN_DTE_BIRTH    = '1972370' ,
.....

  Problem is DTE_BIRTH can be wrong such as '1970370' .. then It cannot
transform to date .
That records will no data too. so total records will not complete.

  From Old process .This field will Loader into Char .. and check by script
  if it out of date will translate to NULL .

  How can I Set External Table scripts to check if DDD > 365 or evenif date
error should set to NULL too.
for keep this records into External data .

  Thank for yours kindness...

          Followup:

map the column as CHAR type in the external table -- so the external table has a
char(7).

Then, use

  create view V
  as
  select ....,
         case ( when dte_birth = '0000000' or gain_dte_birth = '1111315'
                     or gain_dte_birth = '1972370'
                then NULL
                when substr(dte_birth,1,4) not between '1900' and '2200' AND
                     substr(dte_birth,5,3) not between '001' and '366'
                then NULL
                else to_date(dte_birth,'rrrrddd') ) dte_birth,
          .....
     from external_table;

alternatively, you can create a small plsql routine:

create or replace procedure my2date( p_string in varchar2 ) return date
as
begin
    return to_date( p_string, 'rrrrddd' );
exception
    when others then return NULL;
end;
/

and use

  create view V
  as
  select ....,
         case ( when dte_birth = '0000000' or gain_dte_birth = '1111315'
                     or gain_dte_birth = '1972370'
                then NULL
                else my2date(dte_birth)  ) dte_birth,
          .....
     from external_table;

      Rating: 3
one solution
      for container-item problem  May 27, 2003
Reviewer:
      ANINDYA MITRA  from Mumbai, India
atest@MYDB92> desc T_CON
 Name                 Null?    Type
 -------------------- -------- ------------
 CON                           VARCHAR2(2)
 ITEM                          VARCHAR2(2)
 VALUE                         NUMBER
atest@MYDB92> select * from T_CON;

CO IT      VALUE
-- -- ----------
c1 i1         10
c2 i2         15
c2 c1          2
c3 c1          1
c3 i2         20
c4 c3          2
c4 c2          3
c5 c4          5
c5 c2          4
c5 c1         10
c5 i1         20
c5 i3         15
 now, break
  c5=5c4+4c2+10c1+15i3
    =5(2c3+3c2)+4(2c1+15i2)+10(10i1)+15i3
    =... /* c3=(c1+20i2) */
    =600i1+485i2+15i3     <== your result

You have to create objects to access the result from sql.

create or replace type ITEM_TOT_TY as object (l_item varchar2(2),l_cnt  number);
/
create or replace type ITEM_TOT_TAB as table of ITEM_TOT_TY;
/

function ITEM_CNT_FN ( p_con T_CON.CON%type )
                    return ITEM_TOT_TAB as

   type level_rec is record ( l_level number,
                              l_value T_CON.VALUE%type,
                              l_path_mult number
                            );
   type level_tab is table of level_rec index by binary_integer;

   l_ITEM_TOT_tab ITEM_TOT_TAB := ITEM_TOT_TAB();
   l_level_tab    level_tab;
   l_found_flg    pls_integer :=0 ;

 begin
    l_level_tab(1).l_level     := 1 ;
    l_level_tab(1).l_path_mult := 1 ;

  for i in ( select item, value, level from t_con
            start with con = p_con
            connect by con= prior item
           )
   loop
--dbms_output.put_line('cursor fetch-> '||i.item||' '||i.value||' '||i.level);
    l_level_tab(i.level).l_value := i.value ;
    if i.level ^= 1 then
      l_level_tab(i.level).l_level     := i.level ;
      l_level_tab(i.level).l_path_mult := l_level_tab(i.level-1).l_value *
                                          l_level_tab(i.level-1).l_path_mult ;
    end if;

    for j in 1..l_ITEM_TOT_tab.count
    loop
--dbms_output.put_line('j='||j||' i.item '||i.item||' itemtab
'||p_ITEM_TOT_tab(j).l_item);
   if i.item = l_ITEM_TOT_tab(j).l_item then
         l_ITEM_TOT_tab(j).l_cnt := l_ITEM_TOT_tab(j).l_cnt +
                                    i.value *
                                    l_level_tab(i.level).l_path_mult;
         l_found_flg := 1;
         exit;
      end if;
    end loop;
    if l_found_flg = 0 then
        l_ITEM_TOT_tab.extend;
        l_ITEM_TOT_tab(l_ITEM_TOT_tab.count) := ITEM_TOT_TY(i.item,i.value *

l_level_tab(i.level).l_path_mult);
    elsif l_found_flg = 1 then
      l_found_flg := 0 ;
    end if;

   end loop;

   return  l_ITEM_TOT_tab;
 end;
/
now you can get the result from sql.
atest@MYDB92> var x varchar2(2)
atest@MYDB92> exec :x := 'c5'

PL/SQL procedure successfully completed.

atest@MYDB92> select * from the(select cast( ITEM_CNT_FN (:x) as ITEM_TOT_TAB)
from dual)
  2            where l_item like 'i%';

L_      L_CNT
-- ----------
i1        600
i2        485
i3         15

      Rating: 2
correction->
      one solution for container-item problem   May 27, 2003

Reviewer:  ANINDYA MITRA  from Mumbai, India

actually the calculation is
c5=5c4+4c2+10c1+15i3+20i1
  =...
  =600i1+485i2+15i3 <== result

      Rating: 5
thanks
        May 28, 2003
Reviewer:  Tanmoy Datta
      Choudhury  from bangalore,india
thanks ...for your suggesstion ....

May 27, 2003 George -- Thanks for the question regarding "lob
      storage", version 8.1.7

      You Asked
Hi Tom,

i have a table containing a clob column. Only inserting, deleting and searching
with Oracle Text is planned, no updates.
I loaded the table with sample data.
The average length of the clobs is about 5 k,
but the length can get up to 600 k,
the majority of values being in the 1k - 2k range.
Now i am not sure how to set up "correct" storage parameters for the lob
segment:
enable/disable storage in row ?
chunksize ?

Our data files are on raw partitions. Shall we use file system files for the
clob tablespace?

What other points should we consider?

Thanks for your great site

      and we said...
if most are 1-2k in size, it would seem that "enable storage in row" would be
most appropriate (as anything under about 4,000 bytes would stay inline)

Then, you just size the chunksize for the "rest" -- what is the most popular
size after that.  the average size isn't too useful as you could have an inverse
bell curve for all I know (eg: documents are EITHER 1-2k in size OR 600k in
size).  So, look at what most of the rest are and use a chunksize that would be
appropriate for that.  If you have lots of 5k documents -- a 32k chunksize would
be wasteful as each 5k document will take AT LEAST 32k (once it goes out of
line, it will use an entire chunk)

Use of raw or cooked file systems is totally 100% upto you.
May 27, 2003 Thoi
      -- Thanks for the question regarding "IP address", version version 8.1.1

      You Asked
I have got an Oracle version 8.1.1
I'd like to know how you get the IP address from users when they
have already logged on the Database.
Would you, please, send me infomations about versions older than
8.i?

Thanhs beforehand.

      and we said...
In Oracle8i, release 8.1, the SYS_CONTEXT builtin may be used to
get an IP address:

$ sqlplus scott/tiger@ora8idev

SQL*Plus: Release 8.1.5.0.0 - Production on Wed Aug 9 08:17:55
2000

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Enterprise Edition Release 8.1.5.0.0 - Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.0.0 - Production

scott@DEV8I.WORLD> select sys_context('userenv','ip_address')
from dual;

SYS_CONTEXT('USERENV','IP_ADDRESS')
------------------------------------------
138.2.5.52

Prior to that, one method would be to enable auditing and
retrieve the information from sys.aud$...

Review &
      Followup

      Rating: 1
forms
      6i  October 12, 2001
Reviewer:  bipin
       from india
Sir u can not use select statement sys_context statement in forms 6i it gives
error while compling itself i want to use it in forms & run it on web browser

          Followup:

Well, if you run the forms from the web browser -- you will have to understand
that the IP address will be the IP address of the forms SERVER -- not the
client.  If that is what you want there are many ways to solve your issue:

1) create a view, hide the sys_context function from Forms in the first place

2) use dynamic sql, hide the sys_context function from forms.  You can do this
with exec_sql

3) use a stored procedure

4) use a record group:

DECLARE
  rg_id    RecordGroup;
  errcode  NUMBER;
BEGIN
  rg_id := Create_Group_From_Query('ip_address',
             'select sys_context(''userenv'',''ip_address'') ip from dual' );

  errcode := Populate_Group( rg_id );

  message( 'your ip address is ' || get_group_char_cell( 'ip_address.ip', 1 ) );
END;

      Rating: 4
ip
      address  October 12, 2001
Reviewer:
      Abdal-Rahman Agha  from Yemen
Hi Tom,

  very useful information which i need to display in my application.

  Tom, i am still waiting for you to ask my question, but there is no chance.
Look Tom, my question is very very important. Please, tell me when can I do
that?

          Followup:

Sorry, I cannot give specifics on that cause it is purely "catch as catch can".

I am one guy.  I travel and meet with customers.  I have people who work for me
and I work for.  I try to answer as many as I can.  In fact -- right as I'm
writing this now, I'm accepting 10 more questions.

When I get 10, i stop taking them until I answer them.  This allows me to answer
in a timely fashion.  If I know I'll be busy (for example, yesterday I was
giving a talk in Boston, I didn't accept any questions till I got off the plane
and got home) I turn it off.

      Rating: 4
ip
      address  October 12, 2001
Reviewer:
      Abdal-Rahman Agha  from Yemen
Hi Tom,

  very useful information which i need to display in my application.

  Tom, i am still waiting for you to ask my question, but there is no chance.
Look Tom, my question is very very important. Please, tell me when can I do
that?

      Rating: 4
IP from App.
      Server  July 02, 2002
Reviewer:  hrishab
       from India
Tom,
  The solution was very useful, but I have one question. If my Client Appication
connects to the database through Application server, then using userenv will
give the IP Address of the Application server. My question is how can I get the
IP Address of client connected.

Thanks and Regards,

          Followup:

You'll have to ask the client to send it over to you.  The database has no clue
that there is a third party involved in this transaction -- all it sees is the
app server.  The app server can see there is a client and the app server will
have to send the IP address over to the database (perhaps as an input to a
stored procedure or whatnot)

Firewalls, NAT, and many other things will also prevent you from "seeing" the
real client ip address.

      Rating: 4
Where can we
      get a complete list?  July 02, 2002
Reviewer:
      Robert  from Memphis
Hi Tom,

Does Oracle documentation have a complete list of all the information one can
retrieve using sys_context (e.g. 'userenv', 'ip_address')?

Thanks,

Robert.

          Followup:

yes it does.  Surprisingly we keep it with all of the other SQL functions in the
SQL reference manual:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/functions122a.htm#1038178

;)

      Rating: 3
OWA_UTIL
      packages  July 02, 2002
Reviewer:  andrew
       from ca, usa
If the OWA* packages are nstalled (part of the PL/SQL gateway) you'd use
owa_util.get_cgi_env( 'REMOTE_ADDR' )

To avoid "ORA-06502: PL/SQL: numeric or value error" when you call this code
from a non-Web connection - search for owa.init_cgi_env on this site - or try:

         IF OWA.num_cgi_vars IS NOT NULL
         THEN
            -- PL/SQL gateway connection (WEB client)
            v_ip := OWA_UTIL.get_cgi_env ('REMOTE_ADDR');
         ELSE
            -- Direct connection over tcp/ip network
            v_ip := SYS_CONTEXT ('USERENV', 'IP_ADDRESS');
         END IF;

          Followup:

if the owa packages are installed and you are using mod_plsql and not using NAT
and firewalls -- correct, you can use this.

The problem with this question is -- that answer totally hinges on what is in
between the end user and the database -- what technologies they are using, what
languages they are using and so on.

      Rating: 4
Perhaps
      Technet's search facilities could be better?  July 02, 2002

Reviewer:  Doug  from Danbury, CT USA
Tom - if I go to technet.oracle.com and search for sys_context.. I get many hits
on an article called Establishing Security Policies.  With some digging and
following through I can get to the link you posted in the SQL Reference manual.
I recently had a similar problem with Directory object.  It seems to me that
technet does not look for articles with TITLES of the term I may be searching
for documentation on but some other criteria.  Do you agree?

          Followup:

You could just search the documentation instead of the website.  Searching the
website you'll find tons of stuff, but you are looking for doc refs.

try

http://tahiti.oracle.com/
goto the 8i doc set, search for it -- you'll find it in 2 major docs, concepts
guide and sql ref.  click on the sql ref and it's right there.

      Rating: 3
a way to do
      this  July 03, 2002
Reviewer:  Ryan Gaffuri
       from Northern Virginia
There is a java bean in the forms demo called GETCLIENTINFO

I think you can use this to get the users IP barring firewall issues, etc...

      Rating:
      1
  July 12, 2002
Reviewer:  M.P.KIRAN
      KUMAR  from India
hi tom
for oracle version
PL/SQL Release 8.0.4.0.0 - Production
SYS_CONTEXT IS NOT WORKING

          Followup:

As I said:

In Oracle8i, release 8.1, the SYS_CONTEXT builtin may be used to get an IP
address:

$ sqlplus scott/tiger@ora8idev

....

it was added in that release.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:526822275627
for an 8.0 approach (well 7.3 and up)

      Rating: 5
no
      result  October 10, 2002
Reviewer:  Fenng
       from China
Hi ,Tom ,why I get no result ?
I do the following operation:

SQL> connect scott/tiger
ÒÑÁ¬½Ó¡£
SQL> select sys_context('userenv','ip_address') from dual;

SYS_CONTEXT('USERENV','IP_ADDRESS')
-----------------------------------------------------

SQL>

          Followup:

you did not connect over the network -- that is why.  This only works when you
actually "network" into the database.

Else you need not have an IP address or anything, we are using a protocol
(bequeath) that is not TCP/IP based.

      Rating:
      5
YES  October 12, 2002
Reviewer:
      Fenng  from China
Yes ,Tom, in the world of Oracle ,you are the one :-)

      Rating: 4
Where to locate
      the IP address ?  May 19, 2003
Reviewer:  John
      Murphy  from USA
The question sound more of unix to me than the oracle but it is not independent
of oracle. We have users who connect to the unix server using some X-Windows
software(Proterm,Reflection etc). Through that they are able to connect to
oracle using sqlplus.

One of the user is creating some nuisance by deleteing some data from a major
table. We cannot do auditing of the database for DML because the database come
to its knees for some other reason. Hence we wrote a trigger to track but found
that he uses a username which is known to everybody in the project (flaw in
design done in 1995) and also the machine name is shown as the server name.

Now my question: I wanted to know the ip address of his client machine through
which he has connected to the unix and then to oracle. Is it possible to track
him down and how ?

      Rating: 5
Answer Not
      Received  May 27, 2003
Reviewer:
      A reader
Tom,

     The above mentioned question is raising eye balls over our team and none of
us have any solution. You are our last hope. Please find sometime to answere the
above mentioned question.

Warm Regards,
John.

          Followup:

basically you are saying "the user is logged directly onto the server"

the rest of the stuff -- x windows, reflection X and such is not really
relevant.  The only thing that counts here is:

the user is logged directly onto the server.
since they are, they can log into the database using os authentication.

we cannot tell you ANYTHING more then that.  You should prevent them from
logging into the OS -- change that password.  You should lock down the server.

May 27, 2003 John -- Thanks for the question regarding
      "Parse/Executions", version 8.1.7

      You Asked
In a question/answer you stated that the following query:

select parsing_schema_id, sql_text
from v$sql where parse_calls >= executions and parsing_schema_id <> 0;

would be a good place to start if someone wanted to know if the state of their
database was high parse to execute.

Question 1)

When I view the above statement, are the parse_calls and executions fields
cumulative values.

For example if I have a process that parses a sql statment once and executes it
100 times, and then several days later this process runs again with the same
sql, will the parse and execution
values start over , or will the values go to parse =2 and execute = 200, given
that the sql remains in the shared pool, and is not flushed out.

Question 2)

Could you explain to me how the parse/execute from the v$sql above differs from
the parse/execute that you can get from the trace file.  I know that the trace
gives timing statistics, but purely from the count are they the same.

Question 3)
 I was reading an article on Indexing, hotsos.com, and they state that block
selectivity is just as important as row selectivty.  Could you explain further
on block selectivity and how important is it really?

Thanks.

      and we said...
1) they are cumulative from the time of instance startup (assuming the statement
stays in the shared pool of course).

So, they are usually, almost always, cumulative -- but shared pool aging can
affect that.

2) a tracefile is SESSION specific.  that v$ table is across all sessions.

3) see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6643159615303#6676257438755
Say you have two tables -- each with the SAME data.

The only difference is table1 is "sorted" by ID on disk.  So, the rows go id=1,
2, 3, 4, ..... N in order.  On block 1 you find id=1,2,3,4,...100 for example.

Table1 is randomly organized.  id=1 is on block 103, id=2 is on block 5532, id=3
is on block 1, and so on.

If you use an index to read "where id between 1 and 100", table1 will need to
access A SINGLE table block.  table2 will need to access 100 table blocks.

Here, the index on table1 is very selective row-wise (assume id is the primary
key).  The index on table1 is also very BLOCK selective -- a range scan will hit
the minimum number of blocks.

The index on table2 is selective row-wise.  It is NOT selective blockwise
however.
Review
      & Followup

      Rating: 4
Block
      Selectivity  May 27, 2003
Reviewer:  John
       from Detroit,MI
How concerned should I be about the block selectivity?  Is there a way of
determining the block selectivity for an index?

          Followup:

clustering_factor is that measure.  thats what that one link goes into.

but -- bear in mind, a table can only be sorted ONE way ;)

we have many structures

o b*tree clusters
o hash clusters
o IOT's

to help you get and keep a table "clustered" by some key -- but it can only be
sorted one way.

So, you should be very concerned -- in certain cases.  Say you have stock quotes
(or any time based data).  Everynight you load a file of new data.  The data
includes

stock symbol
date
value
(other stuff)

if you put this into a "normal" table -- the data will be such that all of the
data for a DATE is "clustered" together -- eg: 01-jan-2003 data is on a block,
02-jan-2003 data is on ANOTHER block (but all together) and so on.

problem is, how do people use this data?

o show me the stock price of ORCL for the last 50 days
o compute a moving average of ORCL vs the Nasdaq for the last two weeks

and so on.

that first question -- show me the price for ORCL for the last 50 days -- would
require 50 distinct table blocks (one each for each day).

If I used an IOT whose primary key was SYMBOL, DATE -- that first question would
require 2 to 4 distinct blocks (well, maybe 5 on a bad day).  Why?  because all
of the data for ORCL would be physically clumped together -- it would all be on
the same block(s).

So yes, you want to be concerned about the physics of your data, where it
exists, when you have really "popular" questions like that (optimize the
frequently executed queries to death)

      Rating: 4
v$sql
        May 28, 2003
Reviewer:  John  from
      Detroit, MI
In regards to question 1)

select executions, parse_calls, sql_text, address, child_number  from sys.v_$sql
where sql_text like '%select * from HCS.HistoryLog%';

I have noticed somthing that I don't understand. When I run the query above,
part of my output is

EXECUTIONS PARSE_CALLS   SQL_TEXT   ADDRESS     CHILD_NUMBER
0            898         select *   61C4E5AC    0
                         from
                         HCS.HistoryLog
                         where DateOfEntry <= :C1
                         order by tablename,dateofentry

757           0          select *   61C4E5AC    1
                         from
                         HCS.HistoryLog
                         where DateOfEntry <= :C1
                         order by tablename,dateofentry

Can you explain why there are two entries in the shared pool with the same
address.  One row seems to be a child.
Can I assume that they are the same and in this example
the parse calls is 898 and executions is 757.  Do I need to check for this all
the time.  Thanks

May 27, 2003 Dee -- Thanks for the question regarding "Closing
      Ref Cursors", version 8.0.5

      You Asked
I know from experience that you should always close cursors.  Does this apply to
ref cursors?  I find that if I close ref_cursors I loose my results set.  It
does not seem to matter where I put the close cursor statement.

Is there a standard for ref_cursors?

      and we said...
you close cursors AFTER you use them.  Hence you close a ref cursor after you
fetch from it.  If you lose your result set -- that indicates you are not done
with the ref cursor.

just treat a ref cursor as you would any other cursor, close it after you are
finished with it.
Review & Followup

      Rating:
      2
  October 12, 2001
Reviewer:
      A reader

      Rating:
      4
  October 12, 2001
Reviewer:
      A reader
How, would I close a ref cursor explicitly, after I fetch from it.

I thought this was implicit functionality.

          Followup:

It depends on the language.

Pro*c:     EXEC SQL CLOSE :ref_cursor_variable;

SQLPlus:   Implicit

PLSQL:     Close ref_cursor_variable;

Java:      rset.close();

and so on.

      Rating:
      5
example  October 12, 2001
Reviewer:
      A reader
Give us an example and show how we have to do it in

PLSQL:     Close ref_cursor_variable;

For example I have a function which returns a ref cursor, where do you want me
to close it.

          Followup:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package types
  2  as
  3      type rc is ref cursor;
  4  end;
  5  /

Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function foo return
types.rc
  2  as
  3      l_cursor types.rc;
  4  begin
  5      open l_cursor for select * from dual;
  6          return l_cursor;
  7  end;
  8  /

Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace  procedure bar
  2  as
  3      l_cursor types.rc;
  4      l_rec    dual%rowtype;
  5  begin
  6      l_cursor := foo;
  7      loop
  8          fetch l_cursor into l_rec;
  9          exit when l_cursor%notfound;
 10          dbms_output.put_line( l_rec.dummy );
 11      end loop;
 12      close l_cursor;
 13  end;
 14  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec bar
X

PL/SQL procedure successfully completed.

      Rating: 2
why cursor
      leak?  October 12, 2001
Reviewer:
      A reader
import java.sql.*;
import java.io.*;
import oracle.jdbc.driver.*;

class curvar
{
  public static void showOpenCnt( Connection conn, String msg)
  throws SQLException
  {
      Statement stmt = conn.createStatement();
      ResultSet rset =
          stmt.executeQuery
          ( "select a.value, b.name "+
              "from v$mystat a, v$statname b "+
             "where a.statistic# = b.statistic# "+
               "and a.statistic#= 3");
      System.out.println( msg );

      while( rset.next() )
        System.out.println( rset.getString(1)+ " " +
                            rset.getString(2) );
      System.out.println( "-----------------------" );
      rset.close();
      stmt.close();
  }

  public static void main (String args [])
  throws SQLException, ClassNotFoundException
  {
      String query  =
           "begin :1:=getemp( :2 ); end;";
      DriverManager.registerDriver
          (new oracle.jdbc.driver.OracleDriver());
      Connection conn=
        DriverManager.getConnection
        ("jdbc:oracle:thin:@harps:1521:wpdb",
          "scott", "tiger");
CallableStatement  cstmt=null;
showOpenCnt( conn, "Before Anything" );
cstmt = conn.prepareCall(query);
cstmt.registerOutParameter(1,OracleTypes.CURSOR);
ResultSet rset=null;

for (int j=1;j<10;j++)
{

      cstmt.setString(2,"USER1");
      cstmt.execute();
      showOpenCnt( conn, "After prepare and execute" );
      rset = (ResultSet)cstmt.getObject(1);
      showOpenCnt( conn,
             "After prepare and execute and getObject" );

      for(int i = 0;  rset.next(); i++ )
      {
        if ( i == 10 )
           showOpenCnt(conn,"After fetching 10 rows...");
      }

      showOpenCnt( conn, "After but before any close" );

      rset.close();
      showOpenCnt( conn, "After rset.close" );
}
      cstmt.close();
      showOpenCnt( conn, "After cstmt.close" );
  }
}

function getemp(a in varchar2)
return types.rc
is
l_cursor types.rc;
begin
open l_cursor for select * from emp where ename=a;
return l_cursor;
end;

Before Anything
1 opened cursors current
-----------------------
After prepare and execute
3 opened cursors current
-----------------------
After prepare and execute and getObject
3 opened cursors current
-----------------------
After but before any close
3 opened cursors current
-----------------------
After rset.close
3 opened cursors current
-----------------------
After prepare and execute
4 opened cursors current
-----------------------
After prepare and execute and getObject
4 opened cursors current
-----------------------
After but before any close
4 opened cursors current
-----------------------
After rset.close
4 opened cursors current
-----------------------
After prepare and execute
5 opened cursors current
-----------------------
After prepare and execute and getObject
5 opened cursors current
-----------------------
After but before any close
5 opened cursors current
-----------------------
After rset.close
5 opened cursors current
-----------------------
After prepare and execute
6 opened cursors current
-----------------------
After prepare and execute and getObject
6 opened cursors current
-----------------------
After but before any close
6 opened cursors current
-----------------------
After rset.close
6 opened cursors current
-----------------------
After prepare and execute
7 opened cursors current
-----------------------
After prepare and execute and getObject
7 opened cursors current
-----------------------
After but before any close
7 opened cursors current
-----------------------
After rset.close
7 opened cursors current
-----------------------
After prepare and execute
8 opened cursors current
-----------------------
After prepare and execute and getObject
8 opened cursors current
-----------------------
After but before any close
8 opened cursors current
-----------------------
After rset.close
8 opened cursors current
-----------------------
After prepare and execute
9 opened cursors current
-----------------------
After prepare and execute and getObject
9 opened cursors current
-----------------------
After but before any close
9 opened cursors current
-----------------------
After rset.close
9 opened cursors current
-----------------------
After prepare and execute
10 opened cursors current
-----------------------
After prepare and execute and getObject
10 opened cursors current
-----------------------
After but before any close
10 opened cursors current
-----------------------
After rset.close
10 opened cursors current
-----------------------
After prepare and execute
11 opened cursors current
-----------------------
After prepare and execute and getObject
11 opened cursors current
-----------------------
After but before any close
11 opened cursors current
-----------------------
After rset.close
11 opened cursors current
-----------------------
After cstmt.close
1 opened cursors current

          Followup:

whats the platform and versions and other salient information. Works as
advertised for me.

      Rating: 3
show i put
      cstmt.close inside the loop?  October 12, 2001

Reviewer:  A reader
in previous question
if i put preparecall and cstmt.close inside the loop
then the number of opened cursor don't increase.

but i think just like preparestatement
we should use preparecall only one time and use it again and agin to improve
performance. right

          Followup:

Like i said -- version and other info -- I don't get your results at all.

      Rating: 3
store procedure
      or prepared statement?  October 12, 2001
Reviewer:
      A reader
when use storeprocedure , can i parse once and execute multiple times? seems
like each time i execute it i need parse once. how can i aviod it?

like the example in previous quesion, getemp parsed 10 times and run 10 times,
how to decrease it to parse only 1
time?

      Rating:
      2
  October 15, 2001
Reviewer:
      A reader
my version is 8.1.6
os is solaris 2.7.
i tried several machine and get the same result.
however
when i try the same program at
oracle 8.1.6 and winnt4.0
i get different result.
it open up to 3 open cursors.
why the result is different?

      Rating: 4
Closing Ref
      Cursors  April 24, 2002
Reviewer:  SubbaRao
       from Detroit,MI
Tom,
I was facing the same problem right now where my Java code is giving ORA-01000
(Maximum open cursors exceeded).
The java code calls a stored procedure and closes all the resources. The stored
procedure simply executes a query and returns the result set using the ref
cursor.
Do I need to close the refcursor in PL/SQL.
My code looks like this

BEGIN
OPEN rc FOR SELECT * FROM EMPLOYEE WHERE EMPL_ID = 'ABC';
EXCEPTION;
END;
Here rc is the Out parameter to the stored procedure.
Do I need to close "rc" in my StoredProcedure.
Pl. shed some light on this.
Thanks,

          Followup:

No, you need to close that ref cursor in the JAVA.

      Rating: 4
What about
      Forms?  April 25, 2002
Reviewer:  Paul
      Sharples  from Exeter, UK
I base many of my Forms data blocks on stored procs and invariably use ref
cursors. I'm assuming that Forms takes care of closing the cursor - but when? Is
it after the execute_query completes, a post, commit or exit_form?

If the Forms runtime crashes (we're still using Win95), do ref cursors get left
hanging around, or does Oracle close them when the session terminates?

As a final aside, is there any way I could query the database to see if any ref
cursors have been left open?

I realise that this is a lot of questions (and I apologise if I'm breaking
etiquette), but I'm hoping that there's just a simple concept in there that will
make things click into focus.

FWIW, I work mainly in Forms 5 on Win95 against 8.1.6.0. I couldn't find these
answers in any of the documentation available to me.

Regards,

          Followup:

forms takes care of it -- when the block is cleared or otherwise "goes away"

cursors belong to a session, when the session goes - so go the cursors, all of
them.

You can look in v$open_cursor to see the cursors your session currently opened
and cached cursors (cached by PLSQL -- static sql in plsql gets cached).

      Rating:
      4
  April 25, 2002
Reviewer:  A reader

Ok, some of my packages are doing the same:

------------
BEGIN
OPEN rc FOR SELECT * FROM EMPLOYEE WHERE EMPL_ID = 'ABC';
EXCEPTION;
END;
Here rc is the Out parameter from the stored procedure.
-------------------

application is VB using Oracle Oledb 8.1.7.3/auto commit, connection pooling,
but not using transactions (No MTS)

connect string look like:
Provider=OraOLEDB.Oracle; User ID=scott; Password=tiger; Data Source=qdev;
DistribTX=0

and we set objCmd.Properties ("PLSQLRSet"=True) when getting a recordset back.

Should we make a additional call to close the cursor after we are done with it?
I thought that OleDB uses disconnected recordsets so you did not have to close
them.

Thank you for the help once again.

Followup:
No, you need to close that ref cursor in the JAVA

          Followup:

Don't know about VB, never used it -- don't have it -- cannot test it.

Perhaps you can setup a test, similar to the java ones supplied here, and test
it out?

      Rating:
      3
  May 14, 2003
Reviewer:
      A reader
Tom,

I have a query which takes 2 inputs and displays data. What I want to do is,
after taking the input, the query should tell me how many rows have been fetched
and if required I will print the refcursor.

Thanks !

      Rating:
      3
  May 14, 2003
Reviewer:
      A reader
Tom -- here is the script that I tried but get an error when I try to print. Is
there a way out ???

-- t1.sql
set verify off

set serveroutput on
variable rc refcursor
set autoprint off

declare
  type eList is table of number index by binary_integer;
  aList eList;
begin
  open :rc for
    select empno from emp;

  fetch :rc bulk collect into aList;
  dbms_output.put_line('Rows fetched : '||aList.count);
  dbms_output.put_line('Type ''print'' to view the data.');

end;
/

set verify on

SQL> @t1
Total rows fetched : 14
Type 'print' to view the data.

PL/SQL procedure successfully completed.

SQL> print
ERROR:
ORA-01002: fetch out of sequence

no rows selected

SQL>

Is the cursor lost after fetching all the values ???

Thanks !

      Rating: 3
Tom, can I
      please get a feedback on the above question ???  May 21,
      2003
Reviewer:  A reader
Thanks !!!

          Followup:

yes, the results are "lost" after you fetch them.

think of a cursor like a bucket of water.  how many times do you get to dump a
bucket of water out?  once.

      Rating:
      5
  May 21, 2003
Reviewer:  praveen
       from india

      Rating: 5
Why... on
      Oracle9i 9.2.0.3  May 21, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Hi Tom,
Why in the first case the CLOSE statement does not generate any error while in
the second case, it raises "invalid cursor"? Does that mean the refcursor was
closed when the PL/SQL block ended? Does this, then, mean that it may not be
session bound?

SQL> variable rc refcursor
SQL> begin
  2    open :rc for select * from emp ;
  3    close :rc ;
  4  end ;
  5  /

PL/SQL procedure successfully completed.

SQL>
SQL>
SQL> begin
  2    open :rc for select * from emp ;
  3  end ;
  4  /

PL/SQL procedure successfully completed.

SQL> begin
  2    close :rc ;
  3  end ;
  4  /
begin
*
ERROR at line 1:
ORA-01001: invalid cursor
ORA-06512: at line 2

SQL>

          Followup:

you cannot bind the ref cursor back into another block.

once the cursor goes to the client (SQLPLUS), SQLPlus owns it and cannot send it
back to the server.

      Rating:
      5
REFCURSOR  May 27, 2003
Reviewer:
      Reader - Dublin  from Ireland
What is the syntax for defining ref cursor in pro*c?

          Followup:

static void process()
{
EXEC SQL BEGIN DECLARE SECTION;
    typedef char asciiz;
    EXEC SQL TYPE asciiz IS STRING(100);
    SQL_CURSOR  my_cursor;
    asciiz      ename[40];
    int         mgr;
    short       mgr_i;
    int         deptno;
    asciiz      dname[50];
    asciiz      loc[50];
    int         i;
EXEC SQL END DECLARE SECTION;

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();

    EXEC SQL ALLOCATE :my_cursor;

    EXEC SQL EXECUTE BEGIN empCurType.open_cursor( :my_cursor, 10 ); end;
    END-EXEC;

    for( ;; )
    {
        EXEC SQL WHENEVER NOTFOUND DO BREAK;
        EXEC SQL FETCH :my_cursor
            INTO :ename, :mgr:mgr_i, :deptno, :dname, :loc;

        printf( "%s, %d, %d, %s, %s\n",
                ename, mgr_i?-1:mgr, deptno, dname, loc );
    }
    EXEC SQL CLOSE :my_cursor;

      Rating:
      5
REFCURSOR  May 27, 2003
Reviewer:
      Reader - Dublin  from Ireland
What is the syntax for defining ref cursor in pro*c?

      Rating:
      4
Excellent  July 09, 2003
Reviewer:
      kamran  from Pakistan
A <b>brief</b> discussion on topic and very <u>clear</u> reviews.

      Rating: 5
Great
      Tom!  December 28, 2003
Reviewer:
      A reader

May 27, 2003 Boris -- Thanks for the question regarding
      "Creating dynamic stored procedure problem", version 8.0

      You Asked
Here is my first dynamic stored procedure and I have some errors when trying to
compile  it. Please help.  I am new in ORACLE. Thanks a lot

Line 12     PLS-00103: Encountered the symbol "=" when expecting one of the
following:   constant exception <an identifier>   <a double-quoted
delimited-identifier> table LONG_ double ref   char binary national character
ncharThe symbol "<an identifier>" was substituted for "=" to continue.

Line 14   PLS-00103: Encountered the symbol "." when expecting one of the
following:   constant exception <an identifier>   <a double-quoted
delimited-identifier> table LONG_ double ref   char binary national character
ncharThe symbol "<an identifier>" was substituted for "." to continue.

Line 15   PLS-00103: Encountered the symbol "IF" when expecting one of the
following:   begin function package pragma procedure subtype type use   <an
identifier> <a double-quoted delimited-identifier> cursor   form currentThe
symbol "begin" was substituted for "IF" to continue.

Line 47  PLS-00103: Encountered the symbol "SQLQUERY" when expecting one of the
following:   select

Line 55  PLS-00103: Encountered the symbol "/" when expecting one of the
following:   begin function package pragma procedure form external

CREATE OR REPLACE PROCEDURE SMKT.Account_Search
(p_searchname in varchar2,
p_sname in varchar2,
p_searchnumber in varchar2,
p_snumber in varchar2)
 AS
BEGIN
DECLARE
 SQLQuery varchar2 (4000) :='';
 TYPE ACCOUNT_CUR IS REF CURSOR;
 CUR ACCOUNT_CUR;
 ACCOUNTREC ACCOUNT%ROWTYPE;
--Line 12
  SQLQuery := 'SELECT ACCOUNT_CODE FROM INTL_ACCOUNT ';

--Line 14
      DBMS_OUTPUT.PUT_LINE('HI IF' || ACCOUNT);
--Line 15
    IF p_sname <>'' THEN
        DBMS_OUTPUT.PUT_LINE('HI IF');
        IF p_searchname='1' THEN
            SQLQuery := SQLQuery || ' AND ACCOUNT_NAME LIKE '''|| p_sname ||
'%''';
        END IF;
        IF p_searchname='2' THEN
            SQLQuery := SQLQuery || ' AND ACCOUNT_NAME LIKE ''%'|| p_sname ||
'''';
        END IF;
        IF p_searchname='3' THEN
            SQLQuery := SQLQuery || ' AND ACCOUNT_NAME LIKE ''%'|| p_sname ||
'%''';
        END IF;
        IF p_searchname='4' THEN
            SQLQuery := SQLQuery || ' AND ACCOUNT_NAME = '''|| p_sname || '''';
        END IF;
    END IF;
    IF p_snumber <>'' THEN
        DBMS_OUTPUT.PUT_LINE('HI IF');
        IF p_searchnumber='1' THEN
            SQLQuery := SQLQuery || ' AND ACCOUNT_CODE LIKE '''|| p_snumber ||
'%''';
        END IF;
        IF p_searchnumber='2' THEN
            SQLQuery := SQLQuery || ' AND ACCOUNT_CODE LIKE ''%'|| p_snumber ||
'''';
        END IF;
        IF p_searchnumber='3' THEN
            SQLQuery := SQLQuery || ' AND ACCOUNT_CODE LIKE ''%'|| p_snumber ||
'%''';
        END IF;
        IF p_searchnumber='4' THEN
            SQLQuery := SQLQuery || ' AND ACCOUNT_CODE = '''|| p_snumber ||
'''';
        END IF;
    END IF;
    DBMS_OUTPUT.PUT_LINE('SQLQuery'||SQLQuery);

--Line 47
    OPEN CUR FOR SQLQuery;
    LOOP
        FETCH CUR INTO ACCOUNTREC;
        EXIT WHEN CUR%NOTFOUND;
    END LOOP;
    CLOSE CUR;

END;

--Line 55
/

      and we said...
line 12 is an assignment (executable code) but you have it in a declare block.

You should lose the declare block and just put the variable declares after the
AS, before the BEGIN:

...
p_snumber in varchar2)
AS

 SQLQuery varchar2 (4000) :='';
 TYPE ACCOUNT_CUR IS REF CURSOR;
 CUR ACCOUNT_CUR;
 ACCOUNTREC ACCOUNT%ROWTYPE;

BEGIN
--Line 12
  SQLQuery := 'SELECT ACCOUNT_CODE FROM INTL_ACCOUNT ';
....

the rest of the code is equally affected by being in a declare block, not in a
begin/end block.

And, since you are just learning -- I have the golden opportunity to make sure
you do not make the MOST BASIC BUT TERRIBLE MISTAKE you could ever make.  You
are not using bind variables and they are SOOOO important -- you will not
BELIEVE how important.

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279
shows you exactly how to accomplish your task from start to finish!

Review &
      Followup

      Rating: 4
Creating
      dynamic stored procedure problem  July 02, 2002

Reviewer:  Boris Cherkalin  from New York
It was very useful but I still have some minor problems and if you can may be
you could help me because I can not find an answer to this errors.

57  PLS-00103: Encountered the symbol "SQLQUERY" when expecting one of the
following:   select

70  PLS-00103: Encountered the symbol "/" when expecting one of the following:
begin function package pragma procedure form external

CREATE OR REPLACE PROCEDURE SMKT.Account_Search (p_searchname in varchar2 :='1',
p_sname in varchar2 :='',
p_searchnumber in varchar2 :='1',
p_snumber in varchar2 :='')
 AS

 TYPE rc IS REF CURSOR;
 l_cursor  rc;
 SQLQuery varchar2(4000) default 'SELECT ACCOUNT_CODE FROM INTL_ACCOUNT ';
 cursor l_template is SELECT ACCOUNT_CODE FROM INTL_ACCOUNT;
 l_rec  l_template%rowtype;

BEGIN
  SQLQuery := 'SELECT ACCOUNT_CODE FROM INTL_ACCOUNT ';
  --SQLQuery :='SELECT
ACCOUNT_CODE,ACCOUNT_NAME,AFFILIATE_NAME,ACCOUNT_NUMBER,EFFECTIVE_START_DT,EFFECT
IVE_END_DT,FORECAST_IND,ACTUAL_IND,REF_RLI_COUNTRY_CODE FROM INTL_ACCOUNT '
    DBMS_OUTPUT.PUT_LINE('HI IF' || ACCOUNT);
    IF p_sname <>'' THEN
        DBMS_OUTPUT.PUT_LINE('HI IF');
        IF p_searchname='1' THEN
            SQLQuery := SQLQuery || ' AND ACCOUNT_NAME LIKE '''|| p_sname ||
'%''';
        END IF;
        IF p_searchname='2' THEN
            SQLQuery := SQLQuery || ' AND ACCOUNT_NAME LIKE ''%'|| p_sname ||
'''';
        END IF;
        IF p_searchname='3' THEN
            SQLQuery := SQLQuery || ' AND ACCOUNT_NAME LIKE ''%'|| p_sname ||
'%''';
        END IF;
        IF p_searchname='4' THEN
            SQLQuery := SQLQuery || ' AND ACCOUNT_NAME = '''|| p_sname || '''';
        END IF;
    END IF;
    IF p_snumber <>'' THEN
        DBMS_OUTPUT.PUT_LINE('HI IF');
        IF p_searchnumber='1' THEN
            SQLQuery := SQLQuery || ' AND ACCOUNT_CODE LIKE '''|| p_snumber ||
'%''';
        END IF;
        IF p_searchnumber='2' THEN
            SQLQuery := SQLQuery || ' AND ACCOUNT_CODE LIKE ''%'|| p_snumber ||
'''';
        END IF;
        IF p_searchnumber='3' THEN
            SQLQuery := SQLQuery || ' AND ACCOUNT_CODE LIKE ''%'|| p_snumber ||
'%''';
        END IF;
        IF p_searchnumber='4' THEN
            SQLQuery := SQLQuery || ' AND ACCOUNT_CODE = '''|| p_snumber ||
'''';
        END IF;
    END IF;
    --DBMS_OUTPUT.PUT_LINE('SQLQuery'||SQLQuery);
    p(SQLQuery);
--line 57
    open l_cursor for SQLQuery;
    LOOP
        fetch l_cursor into l_rec;
        exit when l_cursor%notfound;
         DBMS_OUTPUT.PUT_LINE(SQLQuery);
    END LOOP;
    close l_cursor;
END;
--line 70
/

          Followup:

My suggestion:

a) erase this code
b) follow the link to the code that does this and does it using binds so that it
does it efficiently.

You have quite a few problems with this code:

1) DBMS_OUTPUT.PUT_LINE('HI IF' || ACCOUNT);

what is account?  it is not defined anywhere.  That bombs.

2)  IF p_sname <>'' THEN  is a bug.  You must use "if p_sname is not null then"

consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure p( p_data in
varchar2 := '' )
  2  is
  3  begin
  4          if ( p_data <> '' )
  5          then
  6                  dbms_output.put_line( 'not equal' );
  7          elsif ( p_data = '' )
  8          then
  9                  dbms_output.put_line( 'equal' );
 10          else
 11                  dbms_output.put_line( 'gee, I just don''t know' );
 12          end if;
 13  end;
 14  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec p
gee, I just don't know

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec p( null );
gee, I just don't know

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec p( 'some stuff' );
gee, I just don't know

PL/SQL procedure successfully completed.

Using trivalued logic and the fact that '' is a NULL, x <> '' is never TRUE (or
FALSE), it is UNKNOWN.

3) SQLQuery := SQLQuery || ' AND ACCOUNT_NAME LIKE '''|| p_sname || '%''';

does not use binds and that is SO important that I won't even look at fixing
this code.  It needs to be FIXED to use binds more then anything else in the
world.

      Rating: 4
Creating
      dynamic stored procedure problem   July 02, 2002

Reviewer:  Boris  from New York
Sorry Tom.
I re-created stored procedure based on example you gave me with a bind variables
but stil have same problems. I am running Oracle 8.0

Line 82   PLS-00103: Encountered the symbol "L_QUERY" when expecting one of the
following:   select

line 95   PLS-00103: Encountered the symbol "end-of-file" when expecting one of
the following:   begin function package pragma procedure form external

Please help
Thanks a lot

CREATE OR REPLACE PROCEDURE SMKT.Account_Search_By
( p_sname   in varchar2 default NULL,
  p_snumber  in varchar2 default NULL,
  p_searchname in varchar2 default '1',
  p_searchnumber in varchar2 default '1')

    as
        type rc is REF CURSOR;

        l_cursor rc;
        l_query  varchar2(512)
                default 'SELECT ACCOUNT_CODE FROM INTL_ACCOUNT WHERE
ACCOUNT_NUMBER is not null ';

       cursor l_template is SELECT ACCOUNT_CODE FROM INTL_ACCOUNT WHERE
ACCOUNT_NUMBER is not null;
       l_rec  l_template%rowtype;
  begin

       if ( p_sname is NOT NULL ) then
           IF p_searchname='1' THEN
               dbms_session.set_context( 'MY_CTX', 'ACCOUNT_NAME',
                                         ''||p_sname||'%');
               l_query := l_query ||
                    ' and ACCOUNT_NAME like
                      sys_context( ''MY_CTX'', ''ACCOUNT_NAME'' ) ';
         end if;
           IF p_searchname='2' THEN
               dbms_session.set_context( 'MY_CTX', 'ACCOUNT_NAME',
                                         '%'||p_sname||'');
               l_query := l_query ||
                    ' and ACCOUNT_NAME like
                      sys_context( ''MY_CTX'', ''ACCOUNT_NAME'' ) ';
         end if;
           IF p_searchname='3' THEN
               dbms_session.set_context( 'MY_CTX', 'ACCOUNT_NAME',
                                         '%'||p_sname||'%');
               l_query := l_query ||
                    ' and ACCOUNT_NAME like
                      sys_context( ''MY_CTX'', ''ACCOUNT_NAME'' ) ';
         end if;
           IF p_searchname='4' THEN
               dbms_session.set_context( 'MY_CTX', 'ACCOUNT_NAME',
                                         ''||p_sname||'');
               l_query := l_query ||
                    ' and ACCOUNT_NAME =
                      sys_context( ''MY_CTX'', ''ACCOUNT_NAME'' ) ';
         end if;
       end if;

       if ( p_snumber is NOT NULL ) then
           IF p_searchname='1' THEN
               dbms_session.set_context( 'MY_CTX', 'ACCOUNT_CODE',
                                         ''||p_snumber||'%');
               l_query := l_query ||
                    ' and ACCOUNT_CODE like
                      sys_context( ''MY_CTX'', ''ACCOUNT_CODE'' ) ';
         end if;
           IF p_searchname='2' THEN
               dbms_session.set_context( 'MY_CTX', 'ACCOUNT_CODE',
                                         '%'||p_snumber||'');
               l_query := l_query ||
                    ' and ACCOUNT_CODE like
                      sys_context( ''MY_CTX'', ''ACCOUNT_CODE'' ) ';
         end if;
           IF p_searchname='3' THEN
               dbms_session.set_context( 'MY_CTX', 'ACCOUNT_CODE',
                                         '%'||p_snumber||'%');
               l_query := l_query ||
                    ' and ACCOUNT_CODE like
                      sys_context( ''MY_CTX'', ''ACCOUNT_CODE'' ) ';
         end if;
           IF p_searchname='4' THEN
               dbms_session.set_context( 'MY_CTX', 'ACCOUNT_CODE',
                                         ''||p_snumber||'');
               l_query := l_query ||
                    ' and ACCOUNT_CODE =
                      sys_context( ''MY_CTX'', ''ACCOUNT_CODE'' ) ';
         end if;
       end if;

      --Print( l_query );

       open l_cursor for l_query;

       loop
           fetch l_cursor into l_rec;
           exit when l_cursor%notfound;

           dbms_output.put_line(l_rec.p_sname || ',' ||
                                l_rec.p_snumber || ',' ||
                                l_rec.p_searchname || ',' ||
                                l_rec.p_searchnumber );
       end loop;

       close l_cursor;
   end Account_Search_By;

          Followup:

Oh, now it is obvious.  Sorry I did not see this before ( i saw the bind
variables -- lack thereof -- and stopped reading, sorry about that)

You have 8.0.

OPEN L_CURSOR FOR <variable>

is a new 8i feature.  You cannot dynamically open a ref cursor in 8.0 and
before, you would use DBMS_SQL.  You will use dbms_sql with dbms_sql.open,
.parse, .define, .bind_variable and so on.

It is interesting to note that in 8i, when you do do this, you could replace:

if ( p_snumber is NOT NULL ) then
           IF p_searchname='1' THEN
               dbms_session.set_context( 'MY_CTX', 'ACCOUNT_CODE',
                                         ''||p_snumber||'%');
               l_query := l_query ||
                    ' and ACCOUNT_CODE like
                      sys_context( ''MY_CTX'', ''ACCOUNT_CODE'' ) ';
         end if;
           IF p_searchname='2' THEN
               dbms_session.set_context( 'MY_CTX', 'ACCOUNT_CODE',
                                         '%'||p_snumber||'');
               l_query := l_query ||
                    ' and ACCOUNT_CODE like
                      sys_context( ''MY_CTX'', ''ACCOUNT_CODE'' ) ';
         end if;
           IF p_searchname='3' THEN
               dbms_session.set_context( 'MY_CTX', 'ACCOUNT_CODE',
                                         '%'||p_snumber||'%');
               l_query := l_query ||
                    ' and ACCOUNT_CODE like
                      sys_context( ''MY_CTX'', ''ACCOUNT_CODE'' ) ';
         end if;
           IF p_searchname='4' THEN
               dbms_session.set_context( 'MY_CTX', 'ACCOUNT_CODE',
                                         ''||p_snumber||'');
               l_query := l_query ||
                    ' and ACCOUNT_CODE =
                      sys_context( ''MY_CTX'', ''ACCOUNT_CODE'' ) ';
         end if;

With

if ( p_snumber is NOT NULL ) then
   select decode( p_searchnumber,
                  '1', p_snumber || '%', '2', '%' || p_snumber,
                  '3', '%' || p_snumber || '%', '4', p_snumber )
     into l_snumber
     from dual;

   dbms_session.set_context( 'my_ctx', 'account_code', l_snumber );
   l_qiery := l_query ||
       ' and account_code = sys_context( ''my_ctx'',''account_code'' ) ';
end if;

      Rating: 4
Elementary
      Troubleshooting  July 02, 2002
Reviewer:  Cory
      D. Wittenborn  from Chicago, IL USA
Boris--when you do dynamic SQL or PL/SQL in Oracle ALWAYS collect the entire
string you are going to execute in a variable and either DBMS_OUTPUT it or
insert it into a debug table BEFORE executing so you can see what you are asking
Oracle to execute.  99% of the time it is something silly that causes the
problem--not escaping single quotes, forgetting a space (so that keywords
flowtogetherlikethis), etc.  Take the resulting output and then try to run it in
SQL*Plus and see if it executes and, if it doesn't, what the problems are.  The
same suggestion applies to any language--troubleshooting dynamic SQL strings in
VB should be done the same way, etc. ...

      Rating: 4
Creating
      dynamic stored procedure problem   July 02, 2002

Reviewer:  Boris  from NY
Hi Tom
I am really sorry for bothering you so many times but I did not get your last
answer. YOu said "You will use dbms_sql with dbms_sql.open, .parse, .define,
.bind_variable and so on." Where? How? My questions might sound stupid but today
is a first day when I work with ORACLE 8.0
Could you help a little bit more please. I change the "IF" part the way you said
but still have same problems as before.

Line 46   PLS-00103: Encountered the symbol "L_QUERY" when expecting one of the
following:   select

line 62   PLS-00103: Encountered the symbol "end-of-file" when expecting one of
the following:   begin function package pragma procedure form external

CREATE OR REPLACE PROCEDURE SMKT.Account_Search_By1
( p_sname   in varchar2 default NULL,
  p_snumber  in varchar2 default NULL,
  p_searchname in varchar2 default '1',
  p_searchnumber in varchar2 default '1')

    as
        type rc is REF CURSOR;

        l_cursor rc;
        l_query  varchar2(4000) default 'SELECT ACCOUNT_CODE FROM INTL_ACCOUNT
WHERE ACCOUNT_NUMBER is not null ';

       cursor l_template is SELECT ACCOUNT_CODE FROM INTL_ACCOUNT WHERE
ACCOUNT_NUMBER is not null;
       l_rec  l_template%rowtype;
  begin

        if ( p_sname is NOT NULL ) then
           select decode( p_searchname,
                          '1', p_sname || '%', '2', '%' || p_sname,
                          '3', '%' || p_sname || '%', '4', p_sname )
             into l_sname
             from dual;

           dbms_session.set_context( 'my_ctx', 'ACCOUNT_NAME', l_sname );
           l_qiery := l_query ||
               ' and ACCOUNT_NAME = sys_context( ''my_ctx'',''ACCOUNT_NAME'' )
';
        end if;

        if ( p_snumber is NOT NULL ) then
           select decode( p_searchnumber,
                          '1', p_snumber || '%', '2', '%' || p_snumber,
                          '3', '%' || p_snumber || '%', '4', p_snumber )
             into l_snumber
             from dual;

           dbms_session.set_context( 'my_ctx', 'ACCOUNT_CODE', l_snumber );
           l_qiery := l_query ||
               ' and ACCOUNT_CODE = sys_context( ''my_ctx'',''ACCOUNT_CODE'' )
';
        end if;

      --Print( l_query );
       dbms_output.put_line( l_query);

       open l_cursor for l_query;

       loop
           fetch l_cursor into l_rec;

           exit when l_cursor%notfound;

           dbms_output.put_line(l_rec.p_sname || ',' ||
                                l_rec.p_snumber || ',' ||
                                l_rec.p_searchname || ',' ||
                                l_rec.p_searchnumber );
       end loop;

       close l_cursor;
   end Account_Search_By1;

          Followup:

This:

Line 46   PLS-00103: Encountered the symbol "L_QUERY" when expecting one of the
following:   select

is because the syntax you are using:

open l_cursor for l_query;

which is "open cursor for <variable>" is NEW with 8i, you have 8.0 and you need
8.1 and up for that syntax.  You can ONLY open a static query in 8.0 and before.
 In looking at your procedure, your best bet is this:

CREATE OR REPLACE PROCEDURE SMKT.Account_Search_By1
( p_sname   in varchar2 default NULL,
  p_snumber  in varchar2 default NULL,
  p_searchname in varchar2 default '1',
  p_searchnumber in varchar2 default '1')

    as
        type rc is REF CURSOR;

        l_cursor rc;
        l_query  varchar2(4000) default 'SELECT ACCOUNT_CODE FROM INTL_ACCOUNT
WHERE ACCOUNT_NUMBER is not null ';

       cursor l_template is SELECT ACCOUNT_CODE FROM INTL_ACCOUNT WHERE
ACCOUNT_NUMBER is not null;
       l_rec  l_template%rowtype;
  begin

   if ( p_sname is NOT NULL ) then
           select decode( p_searchname,
                          '1', p_sname || '%', '2', '%' || p_sname,
                          '3', '%' || p_sname || '%', '4', p_sname )
             into l_sname
             from dual;
   end if;

   if ( p_snumber is NOT NULL ) then
           select decode( p_searchnumber,
                          '1', p_snumber || '%', '2', '%' || p_snumber,
                          '3', '%' || p_snumber || '%', '4', p_snumber )
             into l_snumber
             from dual;
   end if;

   if ( l_sname is not null and l_snumber is not null )
   then
       open l_cursor for SELECT ACCOUNT_CODE FROM INTL_ACCOUNT
         where account_name LIKE l_sname and account_code LIKE l_snumber;
   elsif ( l_sname is not null )
   then
       open l_cursor for SELECT ACCOUNT_CODE FROM INTL_ACCOUNT
         where account_name LIKE l_sname;
   elsif ( l_snumber is not null )
   then
       open l_cursor for SELECT ACCOUNT_CODE FROM INTL_ACCOUNT
         where account_code LIKE l_snumber;
   else
       open l_cursor for SELECT ACCOUNT_CODE FROM INTL_ACCOUNT;
   end if;

       loop
           fetch l_cursor into l_rec;

           exit when l_cursor%notfound;

           dbms_output.put_line(l_rec.p_sname || ',' ||
                                l_rec.p_snumber || ',' ||
                                l_rec.p_searchname || ',' ||
                                l_rec.p_searchnumber );
       end loop;

       close l_cursor;
   end Account_Search_By1;

      Rating: 4
Create dynamic
      stored procedure  July 08, 2002
Reviewer:  Boris
      Cherkalin  from NY
Hi Tom,
Thanks a lot for your help. Thanks, but I still have one small problem. Could
you help me a little bit more. Here is an error:

Line 43    PL/SQL: Statement ignored
Line 43    PLS-00302: component 'P_SNAME' must be declared
Error happened on line  “dbms_output.put_line(l_rec.p_sname || ',' ||
l_rec.p_snumber || ',' || l_rec.p_searchname || ',' || l_rec.p_searchnumber );”

Thanks a lot

CREATE OR REPLACE PROCEDURE SMKT.Account_Search_By2
( p_sname   in varchar2 default NULL,
  p_snumber  in varchar2 default NULL,
  p_searchname in varchar2 default '1',
  p_searchnumber in varchar2 default '1')

    as
       l_sname VARCHAR2(300);

       l_snumber VARCHAR2(300);
        type rc is REF CURSOR;
        l_cursor rc;
        l_query  varchar2(4000) default 'SELECT ACCOUNT_CODE FROM INTL_ACCOUNT
WHERE ACCOUNT_NUMBER is not null ';
        cursor l_template is SELECT ACCOUNT_CODE FROM INTL_ACCOUNT WHERE
ACCOUNT_NUMBER is not null;
        l_rec  l_template%rowtype;
begin

   if ( p_sname is NOT NULL ) then
           select decode( p_searchname, '1', p_sname || '%', '2', '%' ||
p_sname, '3', '%' || p_sname || '%', '4', p_sname )
           into l_sname
           from dual;
   end if;

   if ( p_snumber is NOT NULL ) then
           select decode( p_searchnumber, '1', p_snumber || '%', '2', '%' ||
p_snumber, '3', '%' || p_snumber || '%', '4', p_snumber )
           into l_snumber
           from dual;
   end if;

   if ( l_sname is not null and l_snumber is not null ) then
       open l_cursor for SELECT ACCOUNT_CODE FROM INTL_ACCOUNT where
ACCOUNT_NUMBER is not null and account_name LIKE l_sname and account_code LIKE
l_snumber;
   elsif ( l_sname is not null ) then
       open l_cursor for SELECT ACCOUNT_CODE FROM INTL_ACCOUNT where
ACCOUNT_NUMBER is not null and  account_name LIKE l_sname;
   elsif ( l_snumber is not null ) then
       open l_cursor for SELECT ACCOUNT_CODE FROM INTL_ACCOUNT where
ACCOUNT_NUMBER is not null and  account_code LIKE l_snumber;
   else
       open l_cursor for SELECT ACCOUNT_CODE FROM INTL_ACCOUNT WHERE
ACCOUNT_NUMBER is not null;
   end if;

   loop
     fetch l_cursor into l_rec;
     exit when l_cursor%notfound;
     dbms_output.put_line(l_rec.p_sname || ',' || l_rec.p_snumber || ',' ||
l_rec.p_searchname || ',' || l_rec.p_searchnumber );
   end loop;
   close l_cursor;
end Account_Search_By2;

          Followup:

well, p_sname and p_snumber and so on are not in the cursor -- the l_rec record
only has ACCOUNT_CODE in it.

p_sname and so on are simple "variables".   maybe:

  dbms_output.put_line(p_sname || ',' || p_snumber || ',' ||
p_searchname || ',' || p_searchnumber );

is what you meant.

      Rating: 5
Create dynamic
      stored procedure  July 08, 2002
Reviewer:  Boris
      Cherkalin  from NY
Thank a lot Tom
I've posted my question to 3 or 4 places and only you are the one who really is
helping. Thanks you very much.

      Rating: 4
Creating
      dynamic stored procedure problem   July 11, 2002

Reviewer:  Boris Cherkalin  from NY
Hi Tom,
Sorry but I have to bother you a little bit more. I am frustrated with ORACLE
(or mostly with myself). I need your help. I compiled stored procedure and there
was no mistakes but I can not retrieve anything.
Here is my stored procedure:
PROCEDURE ACCOUNT_SEARCH
( p_sname   in varchar2 default NULL,
  p_snumber  in varchar2 default NULL,
  p_searchname in varchar2 default '1',
  p_searchnumber in varchar2 default '1')
    as
       l_sname VARCHAR2(300);

       l_snumber VARCHAR2(300);
        type rc is REF CURSOR;
        l_cursor rc;
        l_query  varchar2(4000) default 'SELECT ACCOUNT_CODE FROM INTL_ACCOUNT
WHERE ACCOUNT_NUMBER is not null ';
        cursor l_template is SELECT ACCOUNT_CODE FROM INTL_ACCOUNT WHERE
ACCOUNT_NUMBER is not null;
        l_rec  l_template%rowtype;
begin
   if ( p_sname is NOT NULL ) then
           select decode( p_searchname, '1', p_sname || '%', '2', '%' ||
p_sname, '3', '%' || p_sname || '%', '4', p_sname )
           into l_sname
           from dual;
   end if;
   if ( p_snumber is NOT NULL ) then
           select decode( p_searchnumber, '1', p_snumber || '%', '2', '%' ||
p_snumber, '3', '%' || p_snumber || '%', '4', p_snumber )
           into l_snumber
           from dual;
   end if;
   if ( l_sname is not null and l_snumber is not null ) then
       open l_cursor for SELECT ACCOUNT_CODE FROM INTL_ACCOUNT where
ACCOUNT_NUMBER is not null and account_name LIKE l_sname and account_code LIKE
l_snumber;
   elsif ( l_sname is not null ) then
       open l_cursor for SELECT ACCOUNT_CODE FROM INTL_ACCOUNT where
ACCOUNT_NUMBER is not null and  account_name LIKE l_sname;
   elsif ( l_snumber is not null ) then
       open l_cursor for SELECT ACCOUNT_CODE FROM INTL_ACCOUNT where
ACCOUNT_NUMBER is not null and  account_code LIKE l_snumber;
   else
       open l_cursor for SELECT ACCOUNT_CODE FROM INTL_ACCOUNT WHERE
ACCOUNT_NUMBER is not null;
   end if;
   loop
     fetch l_cursor into l_rec;
     exit when l_cursor%notfound;
    begin
         dbms_output.put_line(l_sname || ',' || l_snumber );
    end;
   end loop;
   close l_cursor;
end ACCOUNT_SEARCH;
Here is table structure I am using for my select:
CREATE TABLE INTLFCST.INTL_ACCOUNT
(
    ACCOUNT_CODE         VARCHAR2(3)  NOT NULL,
    ACCOUNT_NAME         VARCHAR2(32) NOT NULL,
    AFFILIATE_NAME       VARCHAR2(32) NOT NULL,
    ACCOUNT_NUMBER       VARCHAR2(5)  NOT NULL,
    EFFECTIVE_START_DT   DATE         NOT NULL,
    EFFECTIVE_END_DT     DATE             NULL,
    FORECAST_IND         CHAR(1)      DEFAULT 'Y' NOT NULL,
    ACTUAL_IND           CHAR(1)      DEFAULT 'Y' NOT NULL,
    REF_RLI_COUNTRY_CODE VARCHAR2(2)  NOT NULL
)
and table data:
INSERT INTO INTL_ACCOUNT
(ACCOUNT_CODE,ACCOUNT_NAME,AFFILIATE_NAME,ACCOUNT_NUMBER,EFFECTIVE_START_DT,EFFEC
TIVE_END_DT,FORECAST_IND,ACTUAL_IND,REF_RLI_COUNTRY_CODE)
VALUES
('USA','United States','Big Company','11111','WEDNESDAY, JULY 10,
2002',NULL,'1','1','11')

INSERT INTO INTL_ACCOUNT
(ACCOUNT_CODE,ACCOUNT_NAME,AFFILIATE_NAME,ACCOUNT_NUMBER,EFFECTIVE_START_DT,EFFEC
TIVE_END_DT,FORECAST_IND,ACTUAL_IND,REF_RLI_COUNTRY_CODE)
VALUES
('RUS','Russia','RusCompany','22222','WEDNESDAY, JULY 10,
2002',NULL,'1','1','22')

INSERT INTO INTL_ACCOUNT
(ACCOUNT_CODE,ACCOUNT_NAME,AFFILIATE_NAME,ACCOUNT_NUMBER,EFFECTIVE_START_DT,EFFEC
TIVE_END_DT,FORECAST_IND,ACTUAL_IND,REF_RLI_COUNTRY_CODE)
VALUES
('CHI','China','China Cosmetic','33333','WEDNESDAY, JULY 10,
2002',NULL,'1','1','33')

INSERT INTO INTL_ACCOUNT
(ACCOUNT_CODE,ACCOUNT_NAME,AFFILIATE_NAME,ACCOUNT_NUMBER,EFFECTIVE_START_DT,EFFEC
TIVE_END_DT,FORECAST_IND,ACTUAL_IND,REF_RLI_COUNTRY_CODE)
VALUES
('FRA','France','France Cosmetic','44444','WEDNESDAY, JULY 10,
2002',NULL,'1','1','44')

INSERT INTO INTL_ACCOUNT
(ACCOUNT_CODE,ACCOUNT_NAME,AFFILIATE_NAME,ACCOUNT_NUMBER,EFFECTIVE_START_DT,EFFEC
TIVE_END_DT,FORECAST_IND,ACTUAL_IND,REF_RLI_COUNTRY_CODE)
VALUES
('ITA','Italy','Italy Cosmetic','55555','WEDNESDAY, JULY 10,
2002',NULL,'1','1','55')

If I pass following parameters (‘U’,’’,’1’,’1’)  I should get one record (“USA”)
but it doesn’t return anything.
Please help.

          Followup:

OH, so close

SQL> set serveroutput on size 1000000

you need to tell sqlplus to dump the dbms_output buffer.

I use a login.sql file to have this always be set in my sessions.....  But you
can just put it in the script.

It worked -- you just didn't see it!

      Rating: 4
Creating
      dynamic stored procedure problem   July 12, 2002

Reviewer:  Boris Cherkalin  from NY
Hi Tom,
Thanks for your help. As you said I added “set serveroutput on size 1000000”
line and it gives me an error: PLS-00103: Encountered the symbol "SERVEROUTPUT"
when expecting one of the following:   transaction
One more thing I did not get this part, You said:  “you need to tell sqlplus to
dump the dbms_output buffer.I use a login.sql file to have this always be set in
my sessions.....  But you can just put it in the script.” Do you have an example
of the code how should I dump the dbms_output buffer.

CREATE OR REPLACE PROCEDURE INTLFCST.ACCOUNT_SEARCH
( p_sname   in varchar2 default NULL,
  p_snumber  in varchar2 default NULL,
  p_searchname in varchar2 default '1',
  p_searchnumber in varchar2 default '1')
    as
       l_sname VARCHAR2(300);

       l_snumber VARCHAR2(300);
        type rc is REF CURSOR;
        l_cursor rc;
        l_query  varchar2(4000) default 'SELECT ACCOUNT_CODE FROM INTL_ACCOUNT
WHERE ACCOUNT_NUMBER is not null ';
        cursor l_template is SELECT ACCOUNT_CODE FROM INTL_ACCOUNT WHERE
ACCOUNT_NUMBER is not null;
        l_rec  l_template%rowtype;
begin

   if ( p_sname is NOT NULL ) then
           select decode( p_searchname, '1', p_sname || '%', '2', '%' ||
p_sname, '3', '%' || p_sname || '%', '4', p_sname )
           into l_sname
           from dual;
   end if;
   if ( p_snumber is NOT NULL ) then
           select decode( p_searchnumber, '1', p_snumber || '%', '2', '%' ||
p_snumber, '3', '%' || p_snumber || '%', '4', p_snumber )
           into l_snumber
           from dual;
   end if;
   if ( l_sname is not null and l_snumber is not null ) then
       open l_cursor for SELECT ACCOUNT_CODE FROM INTL_ACCOUNT where
ACCOUNT_NUMBER is not null and account_name LIKE l_sname and account_code LIKE
l_snumber;
   elsif ( l_sname is not null ) then
       open l_cursor for SELECT ACCOUNT_CODE FROM INTL_ACCOUNT where
ACCOUNT_NUMBER is not null and  account_name LIKE l_sname;
   elsif ( l_snumber is not null ) then
       open l_cursor for SELECT ACCOUNT_CODE FROM INTL_ACCOUNT where
ACCOUNT_NUMBER is not null and  account_code LIKE l_snumber;
   else
       open l_cursor for SELECT ACCOUNT_CODE FROM INTL_ACCOUNT WHERE
ACCOUNT_NUMBER is not null;
   end if;

set serveroutput on size 1000000

   loop
     fetch l_cursor into l_rec;
     exit when l_cursor%notfound;
    begin
         dbms_output.put_line(l_sname || ',' || l_snumber );
    end;
   end loop;
   close l_cursor;
end ACCOUNT_SEARCH;

          Followup:

SQL> set serveroutput on size 1000000

you do that at the SQL prompt.

scott@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( 'hello world' );

PL/SQL procedure successfully completed.

scott@ORA817DEV.US.ORACLE.COM> set serveroutput on
scott@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( 'hello world' );
hello world

PL/SQL procedure successfully completed.

not in the plsql -- it is a sqlplus thing

      Rating:
      5
  January 22, 2003
Reviewer:
      A reader
Is there anyway at all to set serveroutput on inside a procedure code so that
irrespective of the client setting, the output will be displayed. Is there a sql
command equivalent to this (like an alter session?). Thanks.

          Followup:

Nope - if you understand how it works, it would be clear why this is so. (if you
have my book "Expert one on one Oracle" -- i go into details)

dbms_output just fills up a buffer.

The CLIENT is responsible for pulling this buffer and actually printing it.

Hence, if the client isn't told (sqlplus via set serverouput on for example,
your own code via some flag) that they need to dump the output -- they don't
know to pull it and dump it.

Therefore, UNLESS you use sqlplus commands to tell SQPLUS "hey, after you do
something, call dbms_output.get_lines and actually print out anything you find"
-- it just won't be happening.

      Rating: 4
Need some quick
      help....  May 27, 2003
Reviewer:  venkat

Hi Tom,

Sorry to use this thread for my problem...I need help getting this working.... I
am creating a procedure like this:

TEST:SQL> create or replace procedure p1 as
  2  begin
  3  insert into t1 (id, name) (select 1,(select 'venkat' from dual) from dual);
  4  end;
  5  /

Warning: Procedure created with compilation errors.

TEST:SQL> show err
Errors for PROCEDURE P1:

LINE/COL ERROR
-------- -----------------------------------------------------------------
3/38     PLS-00103: Encountered the symbol "SELECT" when expecting one of
         the following:
         ( - + mod not null others <an identifier>
         <a double-quoted delimited-identifier> <a bind variable> avg
         count current exists max min prior sql stddev sum variance
         execute forall time timestamp interval date
         <a string literal with character set specification>
         <a number> <a single-quoted SQL string>

3/65     PLS-00103: Encountered the symbol "FROM" when expecting one of
         the following:
         ; return returning and or

TEST:SQL> select 1,(select 'venkat' from dual) from dual;

         1 (SELEC
---------- ------
         1 venkat

Can you tell me if this is possible to do or i need to think of a workaround?
Your help much appreciated!!!

Thanks in advance
Venkat.

          Followup:

in 8i and before certain SQL constructs were unknown to the plsql parser such as

o select (select )....
o order by in subquery
o CASE
o analytics
o group by extensions

the workarounds in 8i, short of getting on 9i, would be

o native dynamic sql
o dbms_sql dynamic sql
o views

so, in your case,

  execute immediate 'insert into .....';

      Rating: 4
Thanks Tom, for
      your suggestion!!!  May 27, 2003
Reviewer:
      A reader

May 27, 2003 Koshal -- Thanks for the question regarding
      "Preparing a Demo Pack - Reg. ", version 8.1.6.0

      You Asked
1. We have developed a web based application
   Using ASP 3.0 and Oracle 8i. The Business
   Logic has been written in Pl/SQL. Now we want
   give a presentation to a client and the client
   is not having oracle 8i.

   Now what is the alternative. We have tried with
   8i Lite.  But it  Procedures/Functions button
   is not available in 8i Lite Navigator Window.

   Please suggest us the alternative how we can
   install our product.

2. We have written a procedure where
   the current days transactions are recorded
   in a separate   user with same table structure.
   now i want to send the current transactions
   to my head office. For that daily i am executing
   the procedure and then running the exp command
   at /oracle/bin directory. Is this exp command
   followed by the sub commands can be handled
   in the procedure. If So please specify.

      and we said...
1) Personal Oracle8i is what you are looking for.  It has most
all of the features of Oracle8i EE but runs on your laptop (in
fact I just use win2k and run Oracle8i EE on my laptop).

Oracle Lite does not support plsql, it uses java only.

2) if you want to create an extract of data from a database
stored procedure -- you would be considering the use of
UTL_FILE.  The EXP command cannot be invoked from PLSQL directly
(well, you could use an extproc written in Java or C to run it)
but using PLSQL with UTL_FILE would be easier.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:68212348056
for an example.
Review & Followup

      Rating: 3
Oracle
      lite  May 27, 2003
Reviewer:  A reader
       from Canada
Hi Tom,

  What is the major differences between Oracle lite and the others like Oracle
personal , Oracle Enterprise,..
In general, when do you recommend using Oracle Lite database ?

I searched in Oracle 9i documents but I couldn't find a clear & concise
description !

Lake always, thanks for your time.

          Followup:

Oracle lite is a feature "de-intensive" single user database to run on a small
device that doesn't have sufficient resources to run the Personal Edition.

If you look at the Oracle9i New Features guide, there is a feature/option
section that tells you what the differences between Personal, Standard and
Enterprise are.

in Lite you basically have sql and java stored procedures.

      Rating:
      3
  May 28, 2003
Reviewer:
      A reader
Thanks Tom


Subject | Article | Last Updated (East coast time)
Query output to CSV and Schedule a job | article only | May 28, 2003 06:46 am
first_value vs last_value | article only | May 28, 2003 06:51 am
error while using DBMS_JOB package | article only | May 28, 2003 07:02 am
noresetlogs | article only | May 28, 2003 07:07 am
WHENEVER SQLERROR | article only | May 28, 2003 06:35 pm
tunning sql | article only | May 28, 2003 06:48 pm
procedure | article only | May 28, 2003 06:49 pm
SQL*Plus does not use SQL*Net? | article only | May 28, 2003 07:40 pm
Best method to insert/update a table | article only | May 28, 2003 07:46 pm
overwhelmed | article only | May 28, 2003 08:27 pm
length limitation of CLOB in pl/sql | article only | May 29, 2003 08:05 am
pga_aggregate_target | article only | May 29, 2003 08:15 am
analytical functions | article only | May 29, 2003 08:18 am
Execution PLan for Views | article only | May 29, 2003 08:57 am
XMLSchema and Database links question | article only | May 29, 2003 09:53 am
Cutting the end off a string | article only | May 29, 2003 01:44 pm
Trigger | article only | May 29, 2003 04:44 pm
Hierarchical Query | article only | May 29, 2003 07:03 pm
Daylight Savings Time | article only | May 30, 2003 07:14 am
oracle date | article only | May 30, 2003 07:39 am
UTL_FILE_DIR on multiple database instances | article only | May 30, 2003 07:41 am
Format the Number for display | article only | May 30, 2003 07:43 am
Difference between Sort Merge and Hash Joins | article only | May 30, 2003 08:08 am
How to kill a procedure running via DBMS_JOB queue... | article only | May 30, 2003 11:58 am
Import question : COMMIT=Y : How big is the array insert ? | article only | May 30, 2003 01:49 pm
Performance Tuning | article only | May 30, 2003 02:11 pm
pass an array to a cursor, possible? | article only | May 31, 2003 11:27 am
difference between our db_block_buffers and db_cache_size | article only | May 31, 2003 11:32 am
question on job queue.How does it works | article only | May 31, 2003 11:37 am
Architecture Solutions, -- high availability | article only | May 31, 2003 11:45 am
Several Basic Doubts | article only | May 31, 2003 11:47 am
Will there be performance difference by using bind variables in this? | article only | May 31, 2003 02:31 pm
Reading file Using UTL_FILE... | article only | May 31, 2003 04:33 pm
ORA-1031 in a stored procedure doing dynamic sql. | article only | Jun 02, 2003 07:21 am
Deterministic function | article only | Jun 02, 2003 07:31 am
global database name | article only | Jun 02, 2003 07:34 am
case sensitive password | article only | Jun 02, 2003 08:17 am
Views | article only | Jun 02, 2003 09:35 am
Fast refresh is taking longer than complete refresh of materialzed view | article only | Jun 02, 2003 12:35 pm
Moving PL/SQL records from a PL/SQL table to a cursor. | article only | Jun 02, 2003 12:48 pm
LogMiner | article only | Jun 02, 2003 04:16 pm
Table Locks | article only | Jun 02, 2003 04:36 pm
DBLINK CONNECTION | article only | Jun 02, 2003 06:42 pm
PL/SQL call stack performance | article only | Jun 02, 2003 07:01 pm
How to avoid full table scan | article only | Jun 03, 2003 07:24 am
question about auditing | article only | Jun 03, 2003 07:26 am
How to achive this | article only | Jun 03, 2003 07:33 am
clustering_factor | article only | Jun 03, 2003 07:34 am
Differences between intersect and self join. | article only | Jun 03, 2003 07:47 am
How to exclude some tables in export? | article only | Jun 03, 2003 08:28 am
Why a Merge Join Cartesian? | article only | Jun 03, 2003 12:36 pm
Anomaly with NOCOPY | article only | Jun 03, 2003 01:14 pm
How to show missing date between given range | article only | Jun 03, 2003 02:08 pm
Hitrate on Oracle obejcts | article only | Jun 03, 2003 03:39 pm
row(s) 1 - 54 of 54
May 28, 2003 -- Thanks for the question regarding "Query output to CSV and Schedule a job", version 8.1.7
You Asked
Hi Tom,
  This is my first assignment on Oracle.I have to retrive data from a table
based on a query and save it into a CSV file.I could see some articles related
to this in the archives.

My question is I need to make it as a job which should be automatically run at
9.00 PM .How can I do that.Also will the generated CSV file be stored in the
machicine where the oracle server is installed or will the file be saved in the
client.How can I save the csv file in another machine in the network (E.g)
'c:/temp' can I give a network path in this.

Thanks & Regards
C.Sathish
and we said...
search this site for dump_csv.

that'll take a query and create a comma separated values file from it.

search this site for dbms_job (or read the supplied packages guide).  It will be
used to schedule this procedure to run every night at 9pm

dbms_job.submit( l_job, 'dump_csv....',
                 trunc(sysdate)+21/24, 'trunc(sysdate)+1+9/24' );

will do that.

it will ALWAYS write to the servers directory (has to, it is running on the
server).  It can write to a network disk -- but that is "difficult" at best on
windows.  they make it really hard, not like unix where this would be trivial.
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:241814624807
for howto.
Review & Followup
Rating: 5
Thnak u Tom May 12, 2003
Reviewer: C.Sathish from Bangalore, India
That was really helpful for some one new to Oracle.

Rating: 5
handling error situatuion May 26, 2003
Reviewer: Sathish from Tokyo,Japan
Hi tom in continuation to the above question ,I have to save the csv file on a
remote folder (mapped).what happens in a situation where a network error
occurs.say the destination system is shutdown.
The batch will run as scheduled but to find only that the destination does not
exist.
How should I handle such a situation.

Followup:
if the file system "doesn't exist" when utl_file goes to write to it, you'll
recieve an error.

Rating: 5
handling error situatuion May 27, 2003
Reviewer: C.Sathish from Tokyo,Japan
Hi tom When the file system doesn't exist , we will get an error.
but my question is in a production environment those errors need to be handled.
Is there any way to ,may be ,write the error to a log file or is it possible to
email the occurance of the error.

How can I do it.

Thanks & Regards
Sathish

Followup:
you can do whatever you want.

you can write the error to a log file -- sure, you know how to do that --
utl-file

you can email yourself sure, utl_smtp

May 28, 2003 Jan -- Thanks for the question regarding "first_value vs last_value", version 8.16
You Asked
Tom,

You used 'first_value(ename) over ( partition by deptno order by hiredate desc
NULLS LAST )' in one of your example queries, which made me wonder why you don't
use last_value...

These two queries both get the first and last snap id for a day, one uses
'first_value .. sort by .. desc', the other 'last_value'. Which one would be
more efficient?

select snap_id
, snap_time
, FIRST_VALUE(snap_id) OVER (partition by trunc(snap_time) ORDER BY snap_time)
AS first_snap
, FIRST_VALUE(snap_id)  OVER (partition by trunc(snap_time) ORDER BY snap_time
desc) AS last_snap
from STATS$SNAPSHOT
;

select snap_id
, snap_time
, FIRST_VALUE(snap_id) OVER (partition by trunc(snap_time) ORDER BY snap_time )
AS first_snap
, LAST_VALUE(snap_id)  OVER (partition by trunc(snap_time) ORDER BY snap_time
    rows between unbounded preceding and unbounded following) AS last_snap
from STATS$SNAPSHOT
;

The first explain plan shows:
Operation
SELECT STATEMENT
  WINDOW SORT
    WINDOW SORT
      TABLE ACCESS FULL    STATS$SNAPSHOT

The second
Operation
SELECT STATEMENT
  WINDOW SORT
    TABLE ACCESS FULL    STATS$SNAPSHOT

Thanks a lot, jan
and we said...
I goofed.  I did it the inefficient way.  It works better if you can keep the
windows sorted by the same thing.

What I did to test this is:

REM drop table t;
REM
REM create table t as select deptno, hiredate from emp;
REM insert /*+ append */ into t select * from t;
REM commit;
REM insert /*+ append */ into t select * from t;
REM commit;
REM insert /*+ append */ into t select * from t;
REM commit;
.... (lots more, got over 200k rows in there)

and then TKPROF shows:

select deptno,
       first_value(hiredate) over ( partition by deptno order by hiredate ),
       last_value(hiredate) over ( partition by deptno order by hiredate rows
between
       unbounded preceding and unbounded following)
  from t

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch    15293     10.76      25.97       4282        519         34      229376
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    15296     10.76      25.98       4282        519         34      229376

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 61

Rows     Row Source Operation
-------  ---------------------------------------------------
 229376  WINDOW SORT
 229376   TABLE ACCESS FULL T

********************************************************************************

select deptno,
       first_value(hiredate) over ( partition by deptno order by hiredate ),
       first_value(hiredate) over ( partition by deptno order by hiredate desc
nulls last )
  from t

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch    15293     18.01      58.21       8919        519         51      229376
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    15296     18.01      58.21       8919        519         51      229376

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 61

Rows     Row Source Operation
-------  ---------------------------------------------------
 229376  WINDOW SORT
 229376   WINDOW SORT
 229376    TABLE ACCESS FULL T

keeping the windows the same, especially for LARGE windows as I had, is much
more efficient.

Thanks for the heads up.
Review & Followup
Rating: 4
Performance difference? October 29, 2001
Reviewer: jan van mourik from Houston, Texas
Thanks for the quick reply Tom!

Rating: 5
first_value performance September 24, 2002
Reviewer: steve from montreal
Hi Tom,

Platform: Oracle 8.1.7, Solaris 8.

I have 4 tables with the following rowcount:

ppw_customer_dim            96111
ppw_cust_msisdn_history     96111
ppw_service_class_history   178574
ppw_service_class_dim       184

The following query works well (note: all join
columns are indexed)

  1  select count(*)
  2  from    ppw_customer_dim          ppw
  3         ,ppw_cust_msisdn_history   pcmh
  4         ,ppw_service_class_history sch
  5         ,ppw_service_class_dim     scd
  6  where       ppw.cust_id                 = pcmh.cust_id
  7          and ppw.curr_msisdn             = pcmh.msisdn
  8          and pcmh.cust_msisdn_key        = sch.cust_msisdn_key
  9*         and sch.service_class_id        = scd.service_class_id
SQL> /

  COUNT(*)
----------
    178573

Elapsed: 00:00:03.29

Putting the whole thing as a sub-query, still ok:
=================================================

Elapsed: 00:00:03.34
  1  select count(*) from (
  2     select   ppw.curr_msisdn
  3     from    ppw_customer_dim          ppw
  4            ,ppw_cust_msisdn_history   pcmh
  5            ,ppw_service_class_history sch
  6            ,ppw_service_class_dim     scd
  7     where       ppw.cust_id                 = pcmh.cust_id
  8             and ppw.curr_msisdn             = pcmh.msisdn
  9             and pcmh.cust_msisdn_key        = sch.cust_msisdn_key
 10             and sch.service_class_id        = scd.service_class_id
 11*    )
SQL> /

  COUNT(*)
----------
    178573

Elapsed: 00:00:03.44

Now, I start introducing the first_value:
=========================================

  1  select count(*) from (
  2     select   ppw.curr_msisdn
  3             ,first_value(ppw.cust_id)
  4                  over (partition by curr_MSISDN
  5                        order     by ppw.gen_ymdhms desc nulls first
  6                       ) phone_account
  7     from    ppw_customer_dim          ppw
  8            ,ppw_cust_msisdn_history   pcmh
  9            ,ppw_service_class_history sch
 10            ,ppw_service_class_dim     scd
 11     where       ppw.cust_id                 = pcmh.cust_id
 12             and ppw.curr_msisdn             = pcmh.msisdn
 13             and pcmh.cust_msisdn_key        = sch.cust_msisdn_key
 14             and sch.service_class_id        = scd.service_class_id
 15*    )
SQL> /

  COUNT(*)
----------
    178573

Elapsed: 00:00:11.74

The increase in execution time surprised me a little, but
fine. I guess there's some overhead with the first_value.

Now, I introduced a second first_value:
=======================================

SQL>

  1  select count(*) from (
  2     select   ppw.curr_msisdn
  3              --
  4             ,first_value(ppw.cust_id)
  5                  over (partition by curr_MSISDN
  6                        order     by ppw.gen_ymdhms desc nulls first
  7                       ) phone_account
  8              --
  9             ,first_value(ppw.master_cust_id)
 10                 over (partition by curr_MSISDN
 11                       order     by ppw.gen_ymdhms desc nulls first
 12                      ) master_account
 13     from    ppw_customer_dim          ppw
 14            ,ppw_cust_msisdn_history   pcmh
 15            ,ppw_service_class_history sch
 16            ,ppw_service_class_dim     scd
 17     where       ppw.cust_id                 = pcmh.cust_id
 18             and ppw.curr_msisdn             = pcmh.msisdn
 19             and pcmh.cust_msisdn_key        = sch.cust_msisdn_key
 20             and sch.service_class_id        = scd.service_class_id
 21*    )
SQL> /

  COUNT(*)
----------
    178573

Elapsed: 00:00:16.80

Hmmm... Same partition and sorting. Surprised at the increase.

Now, I introduced a third first_value:
======================================

  1  select count(*) from (
  2     select   ppw.curr_msisdn
  3              --
  4             ,first_value(ppw.cust_id)
  5                  over (partition by curr_MSISDN
  6                        order     by ppw.gen_ymdhms desc nulls first
  7                       ) phone_account
  8              --
  9             ,first_value(ppw.master_cust_id)
 10                 over (partition by curr_MSISDN
 11                       order     by ppw.gen_ymdhms desc nulls first
 12                      ) master_account
 13              --
 14             ,first_value(ppw.cust_status)
 15                 over (partition by curr_MSISDN
 16                       order     by ppw.gen_ymdhms desc nulls first
 17                      ) cust_status
 18     from    ppw_customer_dim          ppw
 19            ,ppw_cust_msisdn_history   pcmh
 20            ,ppw_service_class_history sch
 21            ,ppw_service_class_dim     scd
 22     where       ppw.cust_id                 = pcmh.cust_id
 23             and ppw.curr_msisdn             = pcmh.msisdn
 24             and pcmh.cust_msisdn_key        = sch.cust_msisdn_key
 25             and sch.service_class_id        = scd.service_class_id
 26*    )
SQL> /

  COUNT(*)
----------
    178573

Elapsed: 00:00:21.55

Now, I introduced a fourth first_value:
=======================================

  1   select count(*) from (
  2      select   ppw.curr_msisdn
  3               --
  4              ,first_value(ppw.cust_id)
  5                   over (partition by curr_MSISDN
  6                         order     by ppw.gen_ymdhms desc nulls first
  7                        ) phone_account
  8               --
  9              ,first_value(ppw.master_cust_id)
 10                  over (partition by curr_MSISDN
 11                        order     by ppw.gen_ymdhms desc nulls first
 12                       ) master_account
 13               --
 14              ,first_value(ppw.cust_status)
 15                  over (partition by curr_MSISDN
 16                        order     by ppw.gen_ymdhms desc nulls first
 17                       ) cust_status
 18              ,first_value(ppw.serv_status)
 19                  over (partition by curr_MSISDN
 20                        order     by ppw.gen_ymdhms desc nulls first
 21                       ) serv_status
 22      from    ppw_customer_dim          ppw
 23             ,ppw_cust_msisdn_history   pcmh
 24             ,ppw_service_class_history sch
 25             ,ppw_service_class_dim     scd
 26      where       ppw.cust_id                 = pcmh.cust_id
 27              and ppw.curr_msisdn             = pcmh.msisdn
 28              and pcmh.cust_msisdn_key        = sch.cust_msisdn_key
 29              and sch.service_class_id        = scd.service_class_id
 30*     )
SQL> /

  COUNT(*)
----------
    178573

Elapsed: 00:00:27.38

BUT: If I sort a subquery first and then apply the first_value()
on the result set, I get much better results:

  1    select count(*) from (
  2       select   curr_msisdn
  3                --
  4               ,first_value(cust_id)
  5                    over (partition by curr_MSISDN
  6                          order     by gen_ymdhms desc nulls first
  7                         ) phone_account
  8                --
  9               ,first_value(master_cust_id)
 10                   over (partition by curr_MSISDN
 11                         order     by gen_ymdhms desc nulls first
 12                        ) master_account
 13                --
 14               ,first_value(cust_status)
 15                   over (partition by curr_MSISDN
 16                         order     by gen_ymdhms desc nulls first
 17                        ) cust_status
 18               ,first_value(serv_status)
 19                   over (partition by curr_MSISDN
 20                         order     by gen_ymdhms desc nulls first
 21                        ) serv_status
 22       from    (select
pcmh.gen_ymdhms,ppw.curr_msisdn,ppw.cust_id,ppw.master_cust_id,ppw.cust_status,pp
w.serv_status
 23              from  ppw_customer_dim          ppw
 24              ,ppw_cust_msisdn_history   pcmh
 25              ,ppw_service_class_history sch
 26              ,ppw_service_class_dim     scd
 27       where       ppw.cust_id                 = pcmh.cust_id
 28               and ppw.curr_msisdn             = pcmh.msisdn
 29               and pcmh.cust_msisdn_key        = sch.cust_msisdn_key
 30               and sch.service_class_id        = scd.service_class_id
 31       --order by curr_msisdn,gen_ymdhms)
 32       order by ppw.curr_msisdn,pcmh.gen_ymdhms)
 33*      )
SQL> /

  COUNT(*)
----------
    178573

Elapsed: 00:00:19.63

But this is still dependent on the number of first_value operators:
===================================================================

  1    select count(*) from (
  2       select   curr_msisdn
  3                --
  4               ,first_value(cust_status)
  5                   over (partition by curr_MSISDN
  6                         order     by gen_ymdhms desc nulls first
  7                        ) cust_status
  8               ,first_value(serv_status)
  9                   over (partition by curr_MSISDN
 10                         order     by gen_ymdhms desc nulls first
 11                        ) serv_status
 12       from    (select
pcmh.gen_ymdhms,ppw.curr_msisdn,ppw.cust_id,ppw.master_cust_id,ppw.cust_status,pp
w.serv_status
 13              from  ppw_customer_dim          ppw
 14              ,ppw_cust_msisdn_history   pcmh
 15              ,ppw_service_class_history sch
 16              ,ppw_service_class_dim     scd
 17       where       ppw.cust_id                 = pcmh.cust_id
 18               and ppw.curr_msisdn             = pcmh.msisdn
 19               and pcmh.cust_msisdn_key        = sch.cust_msisdn_key
 20               and sch.service_class_id        = scd.service_class_id
 21       --order by curr_msisdn,gen_ymdhms)
 22       order by ppw.curr_msisdn,pcmh.gen_ymdhms)
 23*      )
SQL> /

  COUNT(*)
----------
    178573

Elapsed: 00:00:11.90

Questions:
----------

1. Why does the number of first_value operations affect the performance?
   If each column is partitioned and sorted in the same way, I thought
   performance would be the same.

2. Is it normal that the second query approach (the one that has
   the first_value() working on a sorted result set) would
   be faster?

3. Any performance advice for this query. The real query has about
   16 first_value() calls and takes minutes to run.

Tom: If you would like the exec plans, please let me know

Thanks Tom

Steve

Followup:
1) its doing tons more work -- partitioning the result set, sorting it.

2) many times, when something is done has a large effect on the time -- so yes,
it is normal in both ways (faster and slower -- depending)

3) umm, get rid of the first_value calls since they don't do anything?

Rating: 2
I guess I didn't ask the right questions September 24, 2002
Reviewer: steve from montreal
Regarding Q1:

if we have one first_value() or 10 first_value(),
if they are all partitioning and sorting in the
same way, is the optimizer not able to detect
this and therefore not repeat the work for each column?

Regarding answer 2:

Sorry, but I don't understand the answer.

Regarding answer 3:

Why do you say they "don't do anything"? It's taking the most recent value for
the column over time.

Followup:
1) the addition of each column did not add linearly to the time

Elapsed: 00:00:03.29  no first value
Elapsed: 00:00:11.74  1 FV   +8
Elapsed: 00:00:16.80  2 FV   +5
Elapsed: 00:00:21.55  3 FV   +5
Elapsed: 00:00:27.38  4 FV   +5

The work *is in fact done once* and the optimizer recognized that.  If not, all
would be +8 perhaps -- consider, you proved that with:

Elapsed: 00:00:19.63  4 FV, but pre-sorted
Elapsed: 00:00:11.90  2 FV, but pre-sorted

But as for "why does each take longer" -- that is like asking "why does select
sum(sal), sum(comm) from emp take longer then select sum(sal) from emp"?

You are asking for more work to be done.

and don't forget, the simple select count(*) might have been skipping lots of
table access by rowids that the first values added back in (the first values are
not coming from the same tables either -- so more then one extra "table access
by rowid" could have been added.

2) Umm, sometims SORTING and then doing an operation is faster then doing a
little bit -- sorting -- doing a little bit -- sorting and so on.

For example, sometimes "select * from t where x > 0" should use an index (when
5-15% of the table is > 0) and sometimes it should blow off the index and just
do a full tablescan.  Even if I add "order by x" onto the query -- we should
avoid the index, full scan/sort -- instead of index range scan/table access by
rowid.

Sometimes doing a function later (like you did, sort and then function) rather
then sorting in the function will be better -- sometimes not.

3) did it change the result set?  the answer posted back to you?

Rating: 5
Much clearer!! Thanks! September 24, 2002
Reviewer: steve from Mtl
Hey Tom,

Much clearer. Thank you.

I'm still surprised though at the increase (5 seconds or so
per first_value()). But hey, life is full of surprises I guess!

I'll get back to you on #3. I sure as heck hope it changes
the result set or else.....

Thanks again, AS ALWAYS, Tom. You rock man!

Steve

Rating: 5
answer for Q3 September 24, 2002
Reviewer: steve from mtl
Here's the answer on the result set with/without
the first_value calls

CREATE OR REPLACE VIEW V ( CURR_MSISDN,
PHONE_ACCOUNT, MASTER_ACCOUNT, CUST_STATUS, SERV_STATUS
 ) AS (select   distinct ppw.curr_msisdn
               --
              ,first_value(ppw.cust_id)
                   over (partition by curr_MSISDN
                         order     by ppw.gen_ymdhms desc nulls first
                        ) phone_account
               --
              ,first_value(ppw.master_cust_id)
                  over (partition by curr_MSISDN
                        order     by ppw.gen_ymdhms desc nulls first
                       ) master_account
               --
              ,first_value(ppw.cust_status)
                  over (partition by curr_MSISDN
                        order     by ppw.gen_ymdhms desc nulls first
                       ) cust_status
              ,first_value(ppw.serv_status)
                  over (partition by curr_MSISDN
                        order     by ppw.gen_ymdhms desc nulls first
                       ) serv_status
      from    ppw_customer_dim          ppw
             ,ppw_cust_msisdn_history   pcmh
             ,ppw_service_class_history sch
             ,ppw_service_class_dim     scd
      where       ppw.cust_id                 = pcmh.cust_id
              and ppw.curr_msisdn             = pcmh.msisdn
              and pcmh.cust_msisdn_key        = sch.cust_msisdn_key
              and sch.service_class_id        = scd.service_class_id
      )

CREATE OR REPLACE VIEW V2 ( CURR_MSISDN,
PHONE_ACCOUNT, MASTER_ACCOUNT, CUST_STATUS, SERV_STATUS
 ) AS (select   distinct ppw.curr_msisdn,
               --
              ppw.cust_id,ppw.master_cust_id,ppw.cust_status,ppw.serv_status
      from    ppw_customer_dim          ppw
             ,ppw_cust_msisdn_history   pcmh
             ,ppw_service_class_history sch
             ,ppw_service_class_dim     scd
      where       ppw.cust_id                 = pcmh.cust_id
              and ppw.curr_msisdn             = pcmh.msisdn
              and pcmh.cust_msisdn_key        = sch.cust_msisdn_key
              and sch.service_class_id        = scd.service_class_id
      )

SQL> select * from v;

CURR_MSISDN                    PHONE_ACCOUNT MASTER_ACCOUNT C S
------------------------------ ------------- -------------- - -
525510022031                          119057         119057 q I

Elapsed: 00:00:00.01

SQL>  select * from v2;

CURR_MSISDN                    PHONE_ACCOUNT MASTER_ACCOUNT C S
------------------------------ ------------- -------------- - -
525510022031                          119057         119057 P I
525510022031                          119057         119057 q I

Elapsed: 00:00:00.00

Thanks again for your help Tom.

Steve

Followup:
well, those are not what we were looking at!  not a count(*) to be seen.

Rating: 5
right September 25, 2002
Reviewer: steve from mtl
You're right that with the count(*) there's no
difference. And in the original posting, I was using the
count(*).

I added the count(*) in the posting and during testing to stop the thousands of
lines from being spit out to the screen. But in the real application, there's no
count(*). So that's why I was confused by the original comment (I was thinking
about the real application - not the example I posted). My mistake.

Now we're on the same page!

Ciao

Steve

Followup:
don't do that.  you cannot compare count(*) to the REAL QUERY!!!!

count(*) will many many many times let us skip the "TABLE ACCESS by ROWID" after
an index lookup.  When you put your real columns back in -- we need to add
those.

Consider this trivial example:

big_table@ORA920.US.ORACLE.COM> select count(*) from ( select * from big_table
where owner = user );

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=211 Card=1 Bytes=5)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'BIG_INDEX_1' (NON-UNIQUE) (Cost=211
Card=33234 Bytes=166170)

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

big_table@ORA920.US.ORACLE.COM> select * from big_table where owner = user;

32 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1237 Card=33234 Bytes=3190464)
   1    0   TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1237 Card=33234
Bytes=3190464)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      12712  consistent gets
      12694  physical reads
        300  redo size
       1637  bytes sent via SQL*Net to client
        521  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         32  rows processed

or this one:

big_table@ORA920.US.ORACLE.COM> select count(*)
  2    from ( select * from big_Table where owner = 'OUTLN' )
  3  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=5)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'BIG_INDEX_1' (NON-UNIQUE) (Cost=4 Card=224
Bytes=1120)

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

big_table@ORA920.US.ORACLE.COM> select * from big_Table where owner = 'OUTLN'
  2  /

224 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=228 Card=224 Bytes=21504)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=228 Card=224
Bytes=21504)
   2    1     INDEX (RANGE SCAN) OF 'BIG_INDEX_1' (NON-UNIQUE) (Cost=4 Card=224)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        248  consistent gets
          0  physical reads
        300  redo size
       4433  bytes sent via SQL*Net to client
        653  bytes received via SQL*Net from client
         16  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        224  rows processed

4 consistent gets vs 248 consistent gets!  The resulting performance
difference makes it IMPOSSIBLE, totally 100% IMPOSSIBLE to use select count(*)
from ( YOUR_QUERY_HERE ) as a method of tuning or testing ideas!!!!

Use autotrace traceonly as I did and you'll find the query runs, but the output
is never printed

Rating: 5
Really Useful! September 25, 2002
Reviewer: Steve from Mtl
I feel like I've just been scolded by the principal  :-)
That's good cause I need a good slap behind the head now and again.

Thanks Tom. I will definitely use autotrace traceonly. This is exactly what I
needed.

Steve

Rating: 5
How to show "Elapsed time" September 29, 2002
Reviewer: Abubaker from K.S.A.
Oracle 8.1.6.3
NT 4

Hi all,
Can someone show me how to display the "Elapsed:00:00:00.00"

Thanks,

Followup:
SQL> set timing on

If you do that and see "real nnnnnn", then use the 8i or above version of
sqlplus.  It shows the time in that format.  older releases on windows did not.

Rating: 5
rows between unbounded preceding and unbounded following May 27, 2003
Reviewer: Kamal Kishore from New Jesey, USA
Hi Tom,
What does this clause do:
"rows between unbounded preceding and unbounded following"
when using last_value?

I get different results from these two queries:

SQL> select distinct deptno,
  2                   last_value(ename) over (partition by deptno order by sal
  3                                           rows between unbounded preceding
and unbounded following) as highest_sal
  4  from scott.emp
  5  /

    DEPTNO HIGHEST_SA
---------- ----------
        10 KING
        20 FORD
        30 BLAKE

3 rows selected.

SQL>
SQL> select distinct deptno,
  2                   last_value(ename) over (partition by deptno order by sal)
as highest_sal
  3  from scott.emp
  4  /

    DEPTNO HIGHEST_SA
---------- ----------
        10 CLARK
        10 KING
        10 MILLER
        20 ADAMS
        20 JONES
        20 SCOTT
        20 SMITH
        30 ALLEN
        30 BLAKE
        30 JAMES
        30 TURNER
        30 WARD

12 rows selected.

SQL>

Followup:
the default window is from the beginning of the partition TO THE CURRENT ROW
within the partition for many analytics (ones that use order by in them).

you changed the defaul window to the entire partition using the unbounded
preceding/following.  Consider:

ops$tkyte@ORA920LAP> select deptno, ename, sal,
  2         last_value(ename) over (partition by deptno order by sal
  3                                 rows between unbounded preceding
  4                                  and unbounded following) as highest_sal1,
  5         last_value(ename) over (partition by deptno order by sal)
highest_sal2
  6      from scott.emp
  7   order by deptno, sal
  8  /

    DEPTNO ENAME             SAL HIGHEST_SA HIGHEST_SA
---------- ---------- ---------- ---------- ----------
        10 MILLER           1300 KING       MILLER
           CLARK            2450 KING       CLARK
           KING             5000 KING       KING

        20 SMITH             800 SCOTT      SMITH
           ADAMS            1100 SCOTT      ADAMS
           JONES            2975 SCOTT      JONES
           FORD             3000 SCOTT      SCOTT
           SCOTT            3000 SCOTT      SCOTT

        30 JAMES             950 BLAKE      JAMES
           MARTIN           1250 BLAKE      WARD
           WARD             1250 BLAKE      WARD
           TURNER           1500 BLAKE      TURNER
           ALLEN            1600 BLAKE      ALLEN
           BLAKE            2850 BLAKE      BLAKE

14 rows selected.

ops$tkyte@ORA920LAP>

See how the default last_value() with default window (upto the current row in
the partition) is the same as the current row within the partition?  You were
using a sliding window

if you have my book expert one on one oracle -- i've got a pretty detailed
walkthru of this stuff in there with pictures and stuff.

Rating: 5
Excellent explanation May 28, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
That was an excellent explanation, more than several pages of any book or
documentation.
I have your book, but have yet to reach this chapter.
With all the things going on right now, I find very little time to read. But
whenever I do find time, Expert One-On-One is the only book I read.
Thanks,

May 28, 2003 V Siva -- Thanks for the question regarding "error while using DBMS_JOB package", version 7.3.2.3.0
You Asked
Hi Tom,

I'm using first time DBMS_JOB package. The following is the steps follwed.

SQL> VARIABLE jobno number;
SQL> variable a number
SQL> begin
  2  dbms_job.submit(:jobno, 'select count(*) into :a from qalloc;', sysdate,
'sysdate + 1/48');
  3  end;
  4  /

PL/SQL procedure successfully completed.
SQL> begin
  2  dbms_job.run(1);
  3  end;
  4  /
begin
*
ERROR at line 1:
ORA-12011: execution of 1 jobs failed
ORA-06512: at "SYS.DBMS_IJOB", line 242
ORA-06512: at "SYS.DBMS_JOB", line 218
ORA-06512: at line 2
SQL> begin
  2  dbms_job.run(1);
  3  end;
  4  /
begin
*
ERROR at line 1:
ORA-12011: execution of 1 jobs failed
ORA-06512: at "SYS.DBMS_IJOB", line 242
ORA-06512: at "SYS.DBMS_JOB", line 218
ORA-06512: at line 2
SQL> begin
  2  dbms_job.run(1);
  3  end;
  4  /
begin
*
ERROR at line 1:
ORA-12011: execution of 1 jobs failed
ORA-06512: at "SYS.DBMS_IJOB", line 242
ORA-06512: at "SYS.DBMS_JOB", line 218
ORA-06512: at line 2

SQL> SELECT job, next_date, next_sec, failures, broken
  2   FROM user_jobs;

      JOB NEXT_DATE NEXT_SEC  FAILURES B
--------- --------- -------- --------- -
        1 13-JUN-00 17:32:34         3 N

After each run, the FAILURES and NEXT_SEC columns value are increasing, but I'm
unable to run the job.

Pl. give the reason and solution for this.

Thanks,
Siva
and we said...
Well the problem is that Jobs run in another session from yours and that "bind
variable" you defined in SQLPlus is not meaningful to the job -- there is no
":a" when it runs it.

Jobs have to be totally self contained, they must be totally "pure" plsql.  We
simply wrap a "Begin" and "end;" around the block of code you submit and execute
it.  No binding is done (we have nothing to "bind" at job execution time, your
sqlplus session is somewhere else).

So, if I run your script:

ops$tkyte@DEV8I.WORLD> VARIABLE jobno number;
ops$tkyte@DEV8I.WORLD> variable a number
ops$tkyte@DEV8I.WORLD> begin
  2     dbms_job.submit(:jobno, 'select count(*) into :a from dual;',
  3                                          sysdate );
  4  end;
  5  /

PL/SQL procedure successfully completed.

ops$tkyte@DEV8I.WORLD>
ops$tkyte@DEV8I.WORLD> exec dbms_job.run( :jobno );
BEGIN dbms_job.run( :jobno ); END;

*
ERROR at line 1:
ORA-12011: execution of 1 jobs failed
ORA-06512: at "SYS.DBMS_IJOB", line 394
ORA-06512: at "SYS.DBMS_JOB", line 276
ORA-06512: at line 1

I get the same error.  Looking at the ALERT log (which is where all jobs log
their errors) we see the message:

Tue Jun 13 19:22:05 2000
Errors in file
/export/home/oracle8i/admin/ora8i-dev/udump/ora8idev_ora_27200.trc:
ORA-12012: error on auto execute of job 83056
ORA-01008: not all variables bound

which helps us to diagnose this.

what will run successfully will be something like this:
ops$tkyte@DEV8I.WORLD> VARIABLE jobno number;
ops$tkyte@DEV8I.WORLD> begin
  2     dbms_job.submit(:jobno,
  3                     'declare
  4                        x number;
  5                      begin
  6                        select count(*) into x from dual;
  7                      end;',
  8                      sysdate );
  9  end;
 10  /

PL/SQL procedure successfully completed.

ops$tkyte@DEV8I.WORLD>
ops$tkyte@DEV8I.WORLD> exec dbms_job.run( :jobno );

PL/SQL procedure successfully completed.

That block is a standalone block -- it is how you should submit jobs.
Review & Followup
Rating: 4
Thanks for the question regarding "error while using DBMS_JOB package", version 7.3.2.3.0 December 11, 2001
Reviewer: David Jeyathilak from Bangalore, India.
This message was very much useful for me when I was searching the net for a task
assigned to me - to write a cron job in solaris5.7 which checks a particular
table in oracle 8i
create table table1(
emailid varchar2(50),
tender_closing_date DATE
);

and If the tender_closing_date is the current date, then I have to send an alert
mail(emailid) that the tender is closing today. After reading this message I
came to know that dbms_job is similar to cron. So as a Step 1, I thought I will
write a stored procedure which sends mail and execute it and as a step 2, I will
create a dbms_job.
I wrote a procedure compiled it and executed it. It gave me an error message
"ORA-29540: class oracle/plsql/net/TCPConnection does not exist"
SQL> exec send2('david@i2pl.net','david@i2pl.net','test','test')
ORA-29540: class oracle/plsql/net/TCPConnection does not exist

PL/SQL procedure successfully completed.

what do I do to overcome this.

Thanxs and regards,
David

Followup:
search for ora-29540 on this site.

Rating: 4
Error 12012 during import April 16, 2002
Reviewer: Lisa
What is this error 12012 means after I started the oracle817 database and did a
full import of oracle734 database into the 817 database? I also received a
SIDsnp0.trc message from udump, the following are some cut off from Alrt.log and
then snp0.trc file.
What do I have to do to fix this problem?
 Hope it is not too messy -
As always thank you very much....

Dump file D:\oracle\admin\FOCUS91\bdump\focus91ALRT.LOG
Tue Apr 16 09:01:24 2002
ORACLE V8.1.7.0.0 - Production vsnsta=0
vsnsql=e vsnxtr=3
Windows 2000 Version 5.0 Service Pack 2, CPU type 586
Starting up ORACLE RDBMS Version: 8.1.7.0.0.
System parameters with non-default values:
  processes                = 100
  shared_pool_size         = 83886080
  large_pool_size          = 614400
  java_pool_size           = 52428800
  control_files            = D:\oracle\oradata\FOCUS91\control01.ctl,
D:\oracle\oradata\FOCUS91\control02.ctl, D:\oracle\oradata\FOCUS91\control03.ctl
  db_block_buffers         = 6550
  db_block_size            = 8192
  compatible               = 8.1.0
  log_buffer               = 32768
  log_checkpoint_interval  = 100000
  log_checkpoint_timeout   = 1800
  db_files                 = 1024
  db_file_multiblock_read_count= 8
  rollback_segments        = RBS0, RBS1, RBS2, RBS3, RBS4, RBS5, RBS6, RBS7,
RBS8, RBS9, RBS10, RBS11, RBS12, RBS13, RBS14
  max_enabled_roles        = 30
  remote_login_passwordfile= EXCLUSIVE
  db_domain                = WORLD
  global_names             = FALSE
  distributed_transactions = 10
  instance_name            = FOCUS91
  service_names            = FOCUS91.WORLD
  mts_dispatchers          =
(PROTOCOL=TCP)(PRE=oracle.aurora.server.SGiopServer)
  sort_area_size           = 65536
  sort_area_retained_size  = 65536
  db_name                  = FOCUS91
  open_cursors             = 500
  os_authent_prefix        =
  optimizer_mode           = rule
  remote_dependencies_mode = SIGNATURE
  job_queue_processes      = 7
  job_queue_interval       = 60
  parallel_max_servers     = 5
  background_dump_dest     = D:\oracle\admin\FOCUS91\bdump
  user_dump_dest           = D:\oracle\admin\FOCUS91\udump
  max_dump_file_size       = 10240
  oracle_trace_collection_name=
PMON started with pid=2
DBW0 started with pid=3
LGWR started with pid=4
CKPT started with pid=5
SMON started with pid=6
RECO started with pid=7
SNP0 started with pid=8
SNP1 started with pid=9
SNP2 started with pid=10
SNP3 started with pid=11
SNP4 started with pid=12
SNP5 started with pid=13
SNP6 started with pid=14
Tue Apr 16 09:01:28 2002
starting up 1 shared server(s) ...
starting up 1 dispatcher(s) for network address
'(ADDRESS=(PARTIAL=YES)(PROTOCOL=TCP))'...
Tue Apr 16 09:01:28 2002
alter database mount exclusive
Tue Apr 16 09:01:34 2002
Successful mount of redo thread 1, with mount id 844445230.
Tue Apr 16 09:01:34 2002
Database mounted in Exclusive Mode.
Completed: alter database mount exclusive
Tue Apr 16 09:01:34 2002
alter database open
Beginning crash recovery of 1 threads
Tue Apr 16 09:01:42 2002
Thread recovery: start rolling forward thread 1
Recovery of Online Redo Log: Thread 1 Group 1 Seq 181 Reading mem 0
  Mem# 0 errs 0: D:\ORACLE\ORADATA\FOCUS91\REDO01.LOG
Tue Apr 16 09:01:47 2002
Thread recovery: finish rolling forward thread 1
Thread recovery: 1741 data blocks read, 306 data blocks written, 3601 redo
blocks read
Crash recovery completed successfully
Tue Apr 16 09:01:53 2002
Thread 1 advanced to log sequence 182
Thread 1 opened at log sequence 182
  Current log# 2 seq# 182 mem# 0: D:\ORACLE\ORADATA\FOCUS91\REDO02.LOG
Successful open of redo thread 1.
Tue Apr 16 09:01:57 2002
SMON: enabling cache recovery
SMON: enabling tx recovery
Tue Apr 16 09:02:10 2002
Completed: alter database open
Tue Apr 16 09:04:05 2002
CREATE TABLESPACE "RBS" DATAFILE  '/u03/oradata/SCOPE/rbs01_1.dbf' SIZE
209715200       DEFAULT STORAGE (INITIAL 2097152 NEXT 2097152 MINEXTENTS 2
MAXEXTENTS 249 PCTINCREASE 0) ONLINE PERMANENT
ORA-1543 signalled during: CREATE TABLESPACE "RBS" DATAFILE  '/u03/oradata/SC...
Tue Apr 16 09:04:06 2002
CREATE TABLESPACE "TEMP" DATAFILE  '/u04/oradata/SCOPE/temp01_1.dbf' SIZE
419430400       DEFAULT STORAGE (INITIAL 262144 NEXT 262144 MINEXTENTS 1
MAXEXTENTS 249 PCTINCREASE 50) ONLINE PERMANENT
ORA-1543 signalled during: CREATE TABLESPACE "TEMP" DATAFILE  '/u04/oradata/S...
Tue Apr 16 09:04:06 2002
CREATE TABLESPACE "TOOLS" DATAFILE  '/u03/oradata/SCOPE/tool01_1.dbf' SIZE
41943040       DEFAULT STORAGE (INITIAL 20480 NEXT 20480 MINEXTENTS 1 MAXEXTENTS
249 PCTINCREASE 50) ONLINE PERMANENT
ORA-1543 signalled during: CREATE TABLESPACE "TOOLS" DATAFILE  '/u03/oradata/...
Tue Apr 16 09:04:06 2002
CREATE TABLESPACE "SCOPETOOLS" DATAFILE  '/u02/oradata/SCOPE/SCOPETOOLS_1.dbf'
SIZE 12582912       DEFAULT STORAGE (INITIAL 20480 NEXT 20480 MINEXTENTS 1
MAXEXTENTS 249 PCTINCREASE 50) ONLINE PERMANENT
ORA-1543 signalled during: CREATE TABLESPACE "SCOPETOOLS" DATAFILE  '/u02/ora...
Tue Apr 16 09:04:06 2002
CREATE TABLESPACE "SCOPE_3503" DATAFILE  '/u03/oradata/SCOPE/SCOPE_3503_1.dbf'
SIZE 209715200       DEFAULT STORAGE (INITIAL 20480 NEXT 20480 MINEXTENTS 1
MAXEXTENTS 249 PCTINCREASE 50) ONLINE PERMANENT
ORA-1543 signalled during: CREATE TABLESPACE "SCOPE_3503" DATAFILE  '/u03/ora...
Tue Apr 16 09:04:06 2002
CREATE TABLESPACE "SCOPE_3503_IDX" DATAFILE
'/u02/oradata/SCOPE/SCOPE_3503_IDX_1.dbf' SIZE 209715200       DEFAULT STORAGE
(INITIAL 20480 NEXT 20480 MINEXTENTS 1 MAXEXTENTS 249 PCTINCREASE 50) ONLINE
PERMANENT
ORA-1543 signalled during: CREATE TABLESPACE "SCOPE_3503_IDX" DATAFILE  '/u02...
Tue Apr 16 09:04:06 2002
CREATE TABLESPACE "SCOPE_9000" DATAFILE  '/u02/oradata/SCOPE/SCOPE_9000_1.dbf'
SIZE 10485760       DEFAULT STORAGE (INITIAL 20480 NEXT 20480 MINEXTENTS 1
MAXEXTENTS 249 PCTINCREASE 50) ONLINE PERMANENT
ORA-1543 signalled during: CREATE TABLESPACE "SCOPE_9000" DATAFILE  '/u02/ora...
Tue Apr 16 09:04:06 2002
.....
CREATE ROLLBACK SEGMENT R10 STORAGE (INITIAL 2097152 NEXT 2109440 MINEXTENTS 2
MAXEXTENTS 249) TABLESPACE "RBS"
Completed: CREATE ROLLBACK SEGMENT R10 STORAGE (INITIAL 20971
Tue Apr 16 09:04:24 2002
Thread 1 advanced to log sequence 183
  Current log# 3 seq# 183 mem# 0: D:\ORACLE\ORADATA\FOCUS91\REDO03.LOG
Tue Apr 16 09:04:35 2002
Thread 1 advanced to log sequence 184
  Current log# 1 seq# 184 mem# 0: D:\ORACLE\ORADATA\FOCUS91\REDO01.LOG
Tue Apr 16 09:04:49 2002
Thread 1 advanced to log sequence 185
  Current log# 2 seq# 185 mem# 0: D:\ORACLE\ORADATA\FOCUS91\REDO02.LOG
Tue Apr 16 09:05:04 2002
Thread 1 advanced to log sequence 186
  Current log# 3 seq# 186 mem# 0: D:\ORACLE\ORADATA\FOCUS91\REDO03.LOG
Tue Apr 16 09:05:57 2002
Thread 1 advanced to log sequence 187
  Current log# 1 seq# 187 mem# 0: D:\ORACLE\ORADATA\FOCUS91\REDO01.LOG
Tue Apr 16 09:06:12 2002
Thread 1 advanced to log sequence 188
  Current log# 2 seq# 188 mem# 0: D:\ORACLE\ORADATA\FOCUS91\REDO02.LOG
Tue Apr 16 09:06:17 2002
Errors in file D:\oracle\admin\FOCUS91\bdump\focus91SNP0.TRC:
ORA-12012: error on auto execute of job 7
ORA-12154: TNS:could not resolve service name
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 617
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 674
ORA-06512: at "SYS.DBMS_IREFRESH", line 577
ORA-06512: at "SYS.DBMS_REFRESH", line 211
ORA-06512: at line 1

Tue Apr 16 09:06:28 2002
Thread 1 advanced to log sequence 189
  Current log# 3 seq# 189 mem# 0: D:\ORACLE\ORADATA\FOCUS91\REDO03.LOG
Tue Apr 16 09:06:41 2002
Thread 1 advanced to log sequence 190
  Current log# 1 seq# 190 mem# 0: D:\ORACLE\ORADATA\FOCUS91\REDO01.LOG
Tue Apr 16 09:07:03 2002
Thread 1 advanced to log sequence 191
  Current log# 2 seq# 191 mem# 0: D:\ORACLE\ORADATA\FOCUS91\REDO02.LOG
Tue Apr 16 09:07:26 2002
.....

This one is from snp0.trc -

Dump file D:\oracle\admin\FOCUS91\bdump\focus91SNP0.TRC
Tue Apr 16 09:06:17 2002
ORACLE V8.1.7.0.0 - Production vsnsta=0
vsnsql=e vsnxtr=3
Windows 2000 Version 5.0 Service Pack 2, CPU type 586
Oracle8i Release 8.1.7.0.0 - Production
JServer Release 8.1.7.0.0 - Production
Windows 2000 Version 5.0 Service Pack 2, CPU type 586
Instance name: focus91

Redo thread mounted by this instance: 1

Oracle process number: 8

Windows thread id: 1420, image: ORACLE.EXE

*** SESSION ID:(7.19) 2002-04-16 09:06:17.296
*** 2002-04-16 09:06:17.296
ORA-12012: error on auto execute of job 7
ORA-12154: TNS:could not resolve service name
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 617
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 674
ORA-06512: at "SYS.DBMS_IREFRESH", line 577
ORA-06512: at "SYS.DBMS_REFRESH", line 211
ORA-06512: at line 1
*** SESSION ID:(7.27) 2002-04-16 09:08:33.859
*** 2002-04-16 09:08:33.859
ORA-12012: error on auto execute of job 7
ORA-12154: TNS:could not resolve service name
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 617
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 674
ORA-06512: at "SYS.DBMS_IREFRESH", line 577
ORA-06512: at "SYS.DBMS_REFRESH", line 211
ORA-06512: at line 1
*** SESSION ID:(7.43) 2002-04-16 09:12:53.343
*** 2002-04-16 09:12:53.343
ORA-12012: error on auto execute of job 7
ORA-12154: TNS:could not resolve service name
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 617
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 674
ORA-06512: at "SYS.DBMS_IREFRESH", line 577
ORA-06512: at "SYS.DBMS_REFRESH", line 211
ORA-06512: at line 1
*** SESSION ID:(7.75) 2002-04-16 09:21:18.656
*** 2002-04-16 09:21:18.656
ORA-12012: error on auto execute of job 7
ORA-12154: TNS:could not resolve service name
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 617
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 674
ORA-06512: at "SYS.DBMS_IREFRESH", line 577
ORA-06512: at "SYS.DBMS_REFRESH", line 211
ORA-06512: at line 1

Followup:
Errors in file D:\oracle\admin\FOCUS91\bdump\focus91SNP0.TRC:
ORA-12012: error on auto execute of job 7
ORA-12154: TNS:could not resolve service name
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 617
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 674
ORA-06512: at "SYS.DBMS_IREFRESH", line 577
ORA-06512: at "SYS.DBMS_REFRESH", line 211
ORA-06512: at line 1

seems self explanatory to me -- your tnsnames.ora file in the
oracle\network\admin directory isn't setup properly -- we cannot resolve your
database links.

Rating: 4
But .... April 16, 2002
Reviewer: Lisa from Tx
Here is my tnsnames.ora from d:\oracle\ora81\admin\network
I did the tnsping from server I could ping and login to sqlplus or import dump
file, but the above error message is keeping showing on my Alrt.log during the
import process. -

# TNSNAMES.ORA Network Configuration File:
D:\oracle\ora81\network\admin\tnsnames.ora
# Generated by Oracle configuration tools.
FOCUS91 =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = kprhou-foc000)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = FOCUS91.WORLD)
    )
  )

INST1_HTTP =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = kprhou-foc000)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SERVER = SHARED)
      (SERVICE_NAME = FOCUS91.WORLD)
      (PRESENTATION =
http://admin/
    )
  )

EXTPROC_CONNECTION_DATA =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC0))
    )
    (CONNECT_DATA =
      (SID = PLSExtProc)
      (PRESENTATION = RO)
    )
  )

Followup:
so?  if the snapshot needs "foobar" -- I don't see foobar up there...

I've no idea WHAT service name its looking for.  You'll need to look at your
snapshots, database links and figure out why they are not working -- you are
missing something there.

Rating: 2
Reviewing November 08, 2002
Reviewer: priyaramnan from India
i have a procedure which runs successfully in oracle.In does not have any "out"
parameter. if i tried to execute through DBMS_JOB i get an error as shown below

SQL> declare
  2  v_job_num  BINARY_INTEGER;
  3  v_period varchar2(6);
  4  begin
  5  v_period:='200207';
  6  DBMS_JOB.SUBMIT(v_job_num,'BEGIN month_qd(''200207''); END;', SYSDATE,
null, NULL, NULL,null);
  7  end;
  8  /
declare
*
ERROR at line 1:
ORA-23319: parameter value  is not appropriate
ORA-06512: at "SYS.DBMS_SYS_ERROR", line 86
ORA-06512: at "SYS.DBMS_JOB", line 125
ORA-06512: at line 6

Followup:
Well, why are you passing all of those NULL's and did you even try seeing what
happens when you let them default or you pass appropriate values to them?

In this case, it is the INSTANCE parameter -- to which you are passing null and
Oracle is saying "whoa -- thats not right"

ops$tkyte@ORA920.US.ORACLE.COM> declare
  2    l_job binary_integer;
  3  begin
  4          dbms_job.submit( l_job, 'null;', sysdate, instance => null );
  5  end;
  6  /
declare
*
ERROR at line 1:
ORA-23319: parameter value  is not appropriate
ORA-06512: at "SYS.DBMS_SYS_ERROR", line 86
ORA-06512: at "SYS.DBMS_JOB", line 130
ORA-06512: at line 4

to queue a one time job you should call dbms_job simply like this:

ops$tkyte@ORA920.US.ORACLE.COM> declare
  2    l_job binary_integer;
  3  begin
  4          dbms_job.submit( l_job, 'null;' );
  5          commit;
  6  end;
  7  /

PL/SQL procedure successfully completed.

I would go a step further, if you plan on scheduling this month_qd job over and
over and add "bind variable support" to it.  You should code:

ops$tkyte@ORA920.US.ORACLE.COM> create table month_qd_parameters(job int primary
key, period date );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace procedure month_qd( p_job in
number )
  2  as
  3          l_parameters month_qd_parameters%rowtype;
  4  begin
  5          select * into l_parameters from month_qd_parameters where job =
p_job;
  6
  7          /* code here.... */
  8          delete from month_qd_parameters where job = p_job;
  9          commit;
 10  end;
 11  /

Procedure created.

ops$tkyte@ORA920.US.ORACLE.COM> declare
  2          l_job  number;
  3  begin
  4          dbms_job.submit( l_job, 'month_qd( p_job => JOB );' );
  5          insert into month_qd_parameters values ( l_job, to_date( '200207',
'yyyymm' ) );
  6          commit;
  7
  8  end;
  9  /

PL/SQL procedure successfully completed.

Rating: 4
One More Question November 08, 2002
Reviewer: Baskar from USA
>>"bind variable support"
When a procedure is called from JOB do you think it will parsed each time when
it executes.
E.g
dbms_refresh.refresh('"COMPS"."DEAL_DTL_REFRESH"');

Followup:
My point is -- if you plan on submitting lots of calls to this

month_qd( 'value1' );
month_qd( 'value2' );
month_qd( 'value3' );
.... lots of others
month_qd( 'valueN' );

you should make is "bind variable friendly" so you really just queue up

month_qd( JOB );
month_qd( JOB );
month_qd( JOB );
....
month_qd( JOB );

to avoid messing up the shared pool...  For snapshots -- as your example is --
you won't have hundreds or thousands of UNIQUE, ONE TIME executions - you might
have hundreds -- but they'll be executed over and over and over again (reused)
so its OK.

Rating: 5
Snapshot refresh on background May 28, 2003
Reviewer: Zo A from Madagascar
Hi Tom,

Good explanation as usual. Tom I have a few question to ask on snapshot refresh
on Oracle 7.3 or 8.0.4. I've got a view (V) came from different tables
(T1,T2,T3,...). When I query this view, it takes me several minutes to execute,
so I thought creating a snapshot as soon as a value has been updated or changed
from the master table will be my solution, so that it can run on the background.
How to achieve that ? or any other solution ?
Please Could u provide me a simple code.

Thanks
Zo A.

Followup:
well, materialized views were added in 815 with on commit refresh.

in 73/80 -- they did not exist.

when you get to 9i, read the data warehousing guide for materialized views.

May 28, 2003 razvan -- Thanks for the question regarding "noresetlogs", version 7.3.2
You Asked
hello tom

I don't understand why after recovering the database using a
backup control file, the database must be open with resetlogs
options?
And in what other ocasion i must utilises this option?

thanks a lot,
razvan
and we said...
The resetlogs option must be chosen to resynchronize the
controlfile.  The problem with using the backup controlfile is
that it cannot tell the difference between an ARCHIVE redo log
and an ONLINE redo log.  At some point during the recovery
process, we must apply the online redo log file as if it was an
archived redo log file (since the controlfile doesn't know when
the archive chain of files ends -- its an old backup
controlfile).  Since we just use our online logs as if they were
archive logs -- they need to be "reset".

Oracle enables you to restore an older backup and apply only
some redo data, thereby recovering the database to a specified
time or SCN. This type of recovery is called incomplete
recovery. You must open your database with a RESETLOGS operation
after performing incomplete recovery in order to reset the
online redo logs.

Please backup your database immediately after opening with
resetlogs!
Review & Followup
Rating: 3
I still don't understand it... September 12, 2002
Reviewer: Juan Guascarancas Pena from Mexico
Tom,

I'm still a little bit confused about this.
Does SCN get printed on the control file? When?

Lets see this scenario:

monday:
  hot backup database and control file.
tuesday:
  a lot of data is inserted on the database.
wednesday:
  a crash involving both the control file and datafiles occur. The redo log and
archived redo files are intact, though.

Please correct me, but I think things go like this:

1) We shutdown the database (perhaps with ABORT option?)
2) We restore the control file and datafiles from our backup
3) We start SVRMGRL and ask for a RECOVER DATABASE
4) Oracle will see what the SCN on the datafiles is and roll them forward up to
the SCN on the redo log, using the archived redo and the online redo.

Questions:

Why should I reset the redo log files? What part does the control file play on
the recover process? Why can't Oracle update the control file to reflect the SCN
on the redo log?

I understand that when we make incomplete recovery, the logs must be reset
otherwise the data that we didn't apply from the log to the datafile could be
used on another future recovery, which is not what we want.

Another doubt regards the archived redo. When can I safely delete them?

Please help me with this!

Juan

Followup:
does it get into the control file -- yes, when -- almost continously.

Your scenario is wrong, it would go more like this (assuming you did 1,2,3):

1) shutdown abort
2) restore control file and datafiles
3) do a recover database
4) database is recovered to the point in time of the backup -- cause that is
what the control files say to do

You should

o restore control/data
o RECOVER DATABASE UNTIL CANCEL USING BACKUP CONTROLFILE
o hit enter whilst we suggest what archives we think we need
o tell us about the online redo
o cancel the recovery.

the control file tells us how to recover (has the archive history).  using a
backup control file wipes out our "memory" if you will.

We cannot use the "scn" from the redo since

a) we have no idea about the redo -- the control files tell us about them,
chicken and egg.
b) we have no idea where in the redo stream we were when we "died"

You need to read a document, from start to finish.  this is totally important
stuff.
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76993/toc.htm

Rating: 5
query September 13, 2002
Reviewer: atul from India
Sir,

as we lost control file and only 3 datafiles out of 20..
as your method said restore control+datafiles,

We have to restore all 20 datafiles + controlfile as we are doing incomplete
recover,

Is it correct?

Thanks.
atul

Followup:
well, if you lost only one control file, you should have the other 2 right?

(this is perhaps the worst time to be learning how to recover isn't it?  Its
pretty frightening)....

Before you do ANYTHING, read the manuals -- get a good backup of what you got.
Perhaps if you've never ever done this yourself (bad bad idea, you need to
PRACTICE THIS in good times so the bad times aren't so bad) you should open a
TAR and let them pilot you through the process.

Rating: 4
Controlfile backed up with no resetlogs option... September 13, 2002
Reviewer: Subhrajyoti Paul from Mumbai, India
Tom,
   There is an option to create a controlfile backup to trace file with
NORESETLOGS option. Using this controlfile backup to recreate the controlfile
and then recovering the database does not require the use of OPEN RESETLOGS. How
is that done, if what I am saying is right?

Followup:
When you do a complete recovery, you do not need to resetlogs.  You need to
reset logs when there is redo in the redo logs that could be applied --but
wasn't.  Most common with point in time recoveries.

Rating: 3
Read the manuals but still... September 13, 2002
Reviewer: Juan Guascarancas Pena from Mexico
Hey Tom, I read the document you suggested from start to finish today (is not
being a busy friday after all) but there I could only find WHEN to use resetlogs
not WHY to use it.

Let me try again:

1)You said:

RECOVER DATABASE UNTIL CANCEL USING BACKUP CONTROLFILE

How will Oracle do it, if the old control file doesn't have the archived log
info?

My guess (I may be very wrong) is that Oracle gets the SCN from the online logs,
and perhaps the name of the archived logs from the data dictionary (I can find
this info there, right?) and tries to find and apply the archived log contents
to data files. Why doesn't Oracle then register this SCN and the list of
archived logs that it has successfully applied to the control file, making it a
happy new updated and current control file? Why do I need the resetlogs?

Tom, I now you are very busy, and perhaps I'm just being stupid and not seeing
what is right in front of my nose with this resetlog stuff. I could very well
print a piece of paper saying "always do a resetlog when using a backup control
file or imcomplete recover", but I would very much prefer to understand the
mechanics behind Oracle. So don't be mad at me, huh?

I'll try creating a database on monday, and deleting its control file, then I'll
see how things go. I never run through this recovery procedure before, perhaps
it's self explanatory, who knows?

Thanks in advance
Juan Pena, from Mexico

Followup:
You need to resetlogs when doing a point in time recovery - when stuff is left
in them that wasn't applied.

You need to do a resetlogs when you lose (or have damaged) the online redo logs
(resetlogs will rebuild them).  Eg: if you are in noarchivelog mode, you just
backup your data and control files -- when you need to restore, restore the data
and control files and open with resetlogs.  you never backup online redo logs)

Oracle will look at the datafiles, see when they are current as of and will
"suggest" what the archive log file names would be and try to apply them....

You have the correct idea tho -- do it, practice it, get into all kinds of
situations.  Learn what to do and you'll never be sorry you did!

Rating: 5
Archivelog and noarchive November 13, 2002
Reviewer: Ernest from Hong Kong (China)
I think that a lot of people just don't understand the basic concept of Oracle
process (For example) the asynchronous nature of the DBWR.
How it works/why it decide like that?/why tom recommend to put redo log on
non-raid disk./why is it so powerful/why not made it as synchronous process/Why
Oracle need so much ram over sql server 2000?

A lot of books just taught you how but forget to tell you why.  Thus, a lot of
questions being asked in the same area.  If you still don't understand, I highly
recommended you all to set up a test db and try all the deadly cases   say lost
of control file/lost of all db/lost of archive log/try with the redo log in
archive mode and experience how fatal it is as quoted by tom..

Rating: 4
Effects of Resetlogs on Standby Database May 28, 2003
Reviewer: Mohammed Osman from Riyadh, KSA
Lukcy me, this is the exact situation I am into.

I have been testing the Backup and Recovery process using Rman on Oracle Server
8.1.7. on Windows 2000 Server before implementing on Production database.

Created Primary Database & Standby Database (Managed Recovery Mode)

1) Incremental Level 0 Backup of Standby (Weekend), Daily Level 2, and alternate
days Level 1 is taken with All Arhicve Logs and deleting 3 days logs using
DELETE INPUT

2) Backup of Control File from Primary Database is taken.

During the Testing of Disaster Recovery Process I have taken into consideration
of the following scenarios. Shutdown Immediate, and deleting the Datafiles &
Controlfile.

1) Missing all Datafiles (Mounted the Database and successfully Recoverd)

2) Missing Control File and all Datafiles (Just the Redo Logs are intact).
Recovered the Control File with Nomount Database. Recovered all the Datafiles
with Mount Database successfully.

However when Alter Database Open is issued it does not Open, it prompts to Open
database with RESETLOGS.
Once the database is opened with RESETLOGS. Backup from Standby database
generates errors.

RMAN> resync catalog;

RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-03009: failure of resync command on default channel at 05/27/2003 02:24:36
RMAN-20011: target database incarnation is not current in recovery catalog

RMAN> list backup;

RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-03002: failure of list command at 05/27/2003 02:24:45
RMAN-06004: ORACLE error from recovery catalog database: RMAN-20011: target
database incarnation is not current in recovery catalog

Standby database does not synchronize with the primary once Incarnation of the
Primary database is done.

It seems that I have to recreate the Standby Database again to take the Backup
or is there any other alternative which I am not aware of.

Expecting your valuable advise.

With best regards

Followup:
if you resetlogs, that is one of the things that will "break" the standby --
yes.

so you backup and restore and reinstantiate the standby at that point.

Rating: 5
Prompt Response May 28, 2003
Reviewer: Mohammed Osman from Riyadh, KSA
Thanks a lot to clarify my doubt and advise accordingly.

Appreciate your prompt response.

With best regards

May 28, 2003 Rajendran -- Thanks for the question regarding "WHENEVER SQLERROR ", version 3.3.4.0.0
You Asked
Hi,
   How do I return multiple error codes from sqlplus
using statement "whenever sqlerror exit <variable>."
The sqlplus command executes a sql file that contains
a pl/sql block only. In sqlplus I did define a variable
using define command, but I find that I cannot access
this from the pl/sql block. You could define a bind
variable using variable command and set this variable
to store the return code in the pl/sql block, but I
cannot use it in the whenever sqlerror exit statement.
So how do you get around this ?

In short I am trying to do something like this.

---------------------------------------------
#!/usr/bin/ksh

$ORACLE_HOME/bin/sqlplus <<!!

< define a variable s >

WHENEVER SQLERROR EXIT <variable s>

BEGIN
   ....
   ....
EXCEPTION
  <Here try to set <variable s> to a value
  RAISE;
END;

!!
---------------------------------------------

 Is something like above pusedo code possible ?

Thanks,
raj.
and we said...
So, what you are seeing is this:

tkyte@ORA734.US.ORACLE.COM> variable n number
tkyte@ORA734.US.ORACLE.COM> begin
  2     raise program_error;
  3  exception
  4     when others then
  5        :n := 5;
  6            raise;
  7  end;
  8  /
begin
*
ERROR at line 1:
ORA-06501: PL/SQL: program error
ORA-06512: at line 6

tkyte@ORA734.US.ORACLE.COM> print n

         N
----------

tkyte@ORA734.US.ORACLE.COM>

and the reason N is still NULL after the block is because the
block failed -- hence the output bind variables were never sent
OUT of the block to the client (sqlplus).  Only if the block
succeeds -- will this work.  Can something like this work for
you:

variable n number

set serveroutput on
begin
   raise program_error;
exception
   when others then
      :n := 5;
      dbms_output.put_line( SQLERRM );
end;
/

column n new_val n
select :n n from dual;
exit &n

That has a very similar effect -- we can get the error message
(dbms_output will print it for us).  We can get the exit code
you want and then just exit with it (regardless).

In my testing -- i can get "5" when it fails and when I make it
succeed I get 0 back...
Review & Followup
Rating: 5
Any clues on this please May 28, 2003
Reviewer: trveor welch
Tom

A very simple SQL script called from a shell script
which checks the return status.

Trouble is IF NO ROWS FOUND occurs it errors of course
but in my case I can live with that

Any ideas of another approach??
Regards
Trevor

WHENEVER SQLERROR EXIT 255

SPOOL &1

UPDATE F1 SET grp_name = '$UNKNOWN'
WHERE substr(grp_name,1,1) <> '$';

SPOOL OFF
EXIT

Followup:
why does it error?  it is never an error to update zero rows.

there will never never ever be "no rows found" thrown by an update (unless a
trigger or some side effect throws it).

update t set x = 5 where 1=0;

that will be successful (assuming t exists and all...)

Rating: 5
My mistake May 28, 2003
Reviewer: A reader
Tom,

Thanks for your reply

I'll look at it furthe and let you know
Regards
Trevor

May 28, 2003 Javier -- Thanks for the question regarding "tunning sql", version 8.1.7.3
You Asked
Hello Tom,

We have a batch process made in OCI who runs in solaris that has a performance
problem , I've collected 10046 trace file level 8 and the the querys that have
more elapsed time are the following (ordered fchela,exeela,prsela):

insert into CLL_LLAMADOS_DET_01(LLCD_NRO_CUENTA, LLCD_FECHA_HORA,
  LLCD_COD_SWITCH,LLCD_NRO_SECUENCIA, LLCD_TARIFA_APLIC, LLCD_COD_CONCEPTO,
  LLCD_BANDA_HORARIA, LLCD_ESCALA, LLCD_ESCALON, LLCD_SEG_FACT,
  LLCD_SEG_FACT_REDON, LLCD_UNID_FACT, LLCD_BYS_PRIM_UNID,
  LLCD_IMPORTE_PRIM_UNID, LLCD_BYS_SIG_UNID, LLCD_IMPORTE_SIG_UNID,
  LLCD_COD_CRED1, LLCD_BOLSA_CRED1,LLCD_COD_CREDITO, LLCD_UNID_DESC_CRED1,
  LLCD_COD_CRED2, LLCD_BOLSA_CRED2, LLCD_BYS_CRED2, LLCD_UNID_DESC_CRED2,
  LLCD_COD_CRED3, LLCD_BOLSA_CRED3, LLCD_BYS_CRED3, LLCD_UNID_DESC_CRED3,
  LLCD_UNID_DESC, LLCD_IMPORTE_DESC, LLCD_IMPORTE_DESC_TOTAL,
  LLCD_PROMOCION_DIN, LLCD_BYS_PROMOCION_DIN, LLCD_PROMOCION_UNID,
  LLCD_BYS_PROMOCION_UNID, LLCD_IMPORTE_DESC_PROM, LLCD_UNID_DESC_PROM,
  LLCD_IMPORTE_DESC_TOTAL_PROM, LLCD_IMPORTE_FINAL, LLCD_UNID_FACT_PRIM_UNID,
  LLCD_UNID_FACT_SIG_UNID, LLCD_BH_CREDITO, LLCD_PAR_TARIFARIO_APLIC,
  LLCD_TBL_AREA_ORIG, LLCD_AREA_ORIG, LLCD_TBL_AREA_DEST, LLCD_AREA_DEST,
  LLCD_TBL_ZONA_ORIG, LLCD_ZONA_ORIG, LLCD_TBL_ZONA_DEST, LLCD_ZONA_DEST,
  LLCD_TARIFA_REL_APLIC, LLCD_PAR_TARIFARIO_TAR_REL,
  LLCD_BANDA_HORARIA_TAR_REL, LLCD_TIPO_SVC_APLIC, LLCD_IMPORTE_DESC_PROM_PU,
  LLCD_IMPORTE_DESC_PROM_SU, LLCD_TIPO_SVC, LLCD_FECHA_GRABACION,
  LLCD_ENVIO_CTA_ORDEN )
values
 (:01, :02, :03, :04, :05, :06, :07, :08, :09, :10, :11, :12, :13, :14, :15,
  :16, decode(:17,-1,NULL,:18), decode(:19,-1, NULL,:20), :21, :22,
  decode(:23,-1,NULL,:24), decode(:25,-1,NULL,:26), :27, :28, decode(:29,-1,
  NULL,:30), decode(:31,-1,NULL,:32), :33, :34, :35, :36, :37, :38, :39, :40,
  :41, :42, :43, :44, :45, :46, :47, decode(:48,-1,NULL,:49), decode(:50,-1,
  NULL,:51), decode(:52,-1,NULL,:53), decode(:54,-1,NULL,:55), decode(:56,-1,
  NULL,:57), decode(:58,-1,NULL,:59), decode(:60,-1,NULL,:61), decode(:62,-1,
  NULL,:63), decode(:64,-1,NULL,:65), decode(:66,-1,NULL,:67), :68,
  decode(:69,-1,NULL,:70), decode(:71,-1,NULL,:72), :73, :74, :75, :76, :77,
  :78 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute  21687     20.29     127.40       7031       1443      98744       21687
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    21687     20.29     127.40       7031       1443      98744       21687

The table CLL_LLAMADOS_DET_01 is IOT and the  Primary key is(LLCD_NRO_CUENTA,
LLCD_FECHA_HORA, LLCD_COD_SWITCH,LLCD_NRO_SECUENCIA)

For each insert the value of  LLCD_NRO_CUENTA is not in order , for example :
insert 1 : LLCD_NRO_CUENTA = 1000
insert 2 : LLCD_NRO_CUENTA = 4500
insert 3 : LLCD_NRO_CUENTA = 500
insert 4 : LLCD_NRO_CUENTA = 8000
.
.
.
.
The waits for these sentense are :
                                       Seconds      Calls
db file sequential read                108.30s      7031
SQL*Net message from client             12.02s     21687
SQL*Net message to client                0.09s     21687
latch free                               0.01s        57
log file switch completion               0.29s        02
log file sync                            0.46s        09

Is there anything that you can say me to reduce db file sequential read waits
???

2) The result of waits for the session after collected 10046 level 8 trace file
, are the following :

Event                                Time waited    %        Calls
----------------------------------------------------    ----------
SQL*Net message from client          636.33s        60  196663
db file sequential read              248.66s        23       26339
CPU SERVICE                           88.72s        08      290112
log file sync                         77.13s        07        1162
SQL*Net message from dblink            2.81s        00         798
SQL*Net message to client              0.62s        00      196663
log file switch completion             0.29s        00           2
SQL*Net more data from dblink          0.20s        00           2
latch free                             0.05s        00         427
buffer busy waits                      0.00s        00           2
SQL*Net more data from client          0.00s        00          32
SQL*Net message to dblink              0.00s        00         798
-----------------------------------         ----------------

Total                                 1054.81s      00           0

In the trace file there are lines like this :

XCTEND rlbk=0, rd_only=0
WAIT #0: nam='log file sync' ela= 8 p1=2028 p2=0 p3=0
WAIT #0: nam='SQL*Net message to client' ela= 0 p1=1413697536 p2=1 p3=0
WAIT #0: nam='SQL*Net message from client' ela= 0 p1=1413697536 p2=1 p3=0
WAIT #0: nam='SQL*Net message from client' ela= 1000 p1=1413697536 p2=1 p3=0

The waits for #0 are the following :

SQL*Net message from client            341.01s      1069
log file sync                           76.67s      1153
SQL*Net message from dblink              1.47s        54
SQL*Net message to client                0.00s      1069
SQL*Net message to dblink                0.00s        54
SQL*Net more data from client            0.00s        01
latch free                               0.00s        01

Could you help me with the waits in #0 ???, I don´t know how to resolve this
problem.

Regards.
Javier.
and we said...
batch the inserts up.  for 21687, I would hope to see no more then 216 EXECUTES.

You are doing things a single row at a time which is about the slowest method
possible -- not to mentation that it generates tons more log then necessary.

If you look at your MAJOR waits -- they are actually in the application itself.
sqlnet message from client means "database was idle, just waiting to be told
what to do"

Consider this example:

DOC>drop table t;
DOC>
DOC>create table t as select * from all_objects where 1=0;
DOC>create or replace package demo_data
DOC>as
DOC>        type array is table of all_objects%rowtype index by binary_integer;
DOC>
DOC>        l_data array;
DOC>end;
DOC>/
DOC>
DOC>create or replace package body demo_data
DOC>as
DOC>begin
DOC>    select * bulk collect into l_data from all_objects where rownum < 10000;
DOC>end;
DOC>/
DOC>*/
ops$tkyte@ORA920> truncate table t;

Table truncated.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_output.put_line( 'There are ' ||
demo_data.l_data.count || ' rows to be processed' );
There are 9999 rows to be processed

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec runstats_pkg.rs_start;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2          for i in 1 .. demo_data.l_data.count
  3          loop
  4                  insert into T values demo_data.l_data(i);
  5          end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runstats_pkg.rs_middle;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          l_lobound number := 1;
  3          l_cnt     number := demo_data.l_data.count;
  4          l_rows    number := 100;
  5  begin
  6          while (l_lobound < l_cnt)
  7          loop
  8                  forall i in l_lobound .. least( l_lobound+l_rows, l_cnt )
  9                          insert into T values demo_data.l_data(i);
 10                  l_lobound := l_lobound + l_rows + 1;
 11          end loop;
 12  end;
 13  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec runstats_pkg.rs_stop(250);
Run1 ran in 298 hsecs
Run2 ran in 24 hsecs
run 1 ran in 1241.67% of the time

Name                                Run1      Run2      Diff
STAT...CPU used by this sessio       277        17      -260
STAT...CPU used when call star       277        17      -260
STAT...Elapsed Time                  308        34      -274
LATCH.row cache objects              374        26      -348
LATCH.simulator hash latch           786         8      -778
STAT...calls to get snapshot s    10,123       242    -9,881
STAT...execute count              10,036       106    -9,930
LATCH.shared pool                 10,374       295   -10,079
STAT...redo entries               11,514     1,352   -10,162
LATCH.redo allocation             11,562     1,363   -10,199
STAT...recursive calls            10,322       122   -10,200
STAT...db block gets              12,383     1,972   -10,411
STAT...session logical reads      12,665     2,228   -10,437
STAT...db block changes           22,303     2,217   -20,086
LATCH.library cache pin           20,402       304   -20,098
LATCH.library cache               20,683       436   -20,247
LATCH.cache buffers chains        59,090     7,769   -51,321
STAT...session pga memory              0    65,536    65,536
STAT...redo size               3,654,284 1,170,656-2,483,628

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
125,545    10,861  -114,684 1155.92%

PL/SQL procedure successfully completed.

Single row vs 100 rows at a time.

Could that decrease in redo help you out?
How about the decrease in CPU/elapsed time over all?
Review & Followup
Rating: 5
batch insert May 24, 2003
Reviewer: lakshmi
excellent

Rating: 5
Perfect, thanks May 24, 2003
Reviewer: Marc from Switzerland
Hi Tom

I am searching your site for the source code of package runstats_pkg and I
cannot find it. Could you please let me know where this can be find. Many thanks
and nice weekend.

Followup:
click on the tidbits link on my home page -- howtos and stuff are there,
including runstats.

Rating: 5
Test harness May 24, 2003
Reviewer: Peter Tran from Houston, TX USA
Marc,

You can find it here:
http://asktom.oracle.com/~tkyte/
Look at the "How to build a simple test harness" link.

Bookmark that page!  :)

-Peter

Rating: 5
Strike, thanks Peter and Tom May 25, 2003
Reviewer: A reader

Rating: 4
sql tuning May 26, 2003
Reviewer: Javier Groszek from Argentina.
Thanks Tom , very good example.
I run the test and I gave the results to the developers.
We 'll do the changes in the production system.

Any idea what is WAIT #0: nam='log file sync' ela= 0 p1=249 p2=0 p3=0  ??
I don´t have in the trace file lines like this :
PARSING IN CURSOR #0

Regards.
Javier.

Followup:
is this happening in a "normal" session or in a parallel query session -- or did
you even turn on tracing for the backgrounds?

Rating: 4
sql tuning May 26, 2003
Reviewer: Javier Groszek from Argentina
This is happening in a normal session , it's a batch process , I am not using
parallel query.
I didn´t turn on trace for backgrounds.

Thanks.

Followup:
don't know, if you are interested you can file a tar with support.  I've not
seen it but then I've never really gone looking for it either.

Rating: 5
example in 8i May 28, 2003
Reviewer: Javier from Argentina
Could you give the same example of batch inserts (Single row vs 100 rows at a
time) for oracle 8i version. ???

Thanks.
Javier.

Followup:
you just need to use individual scalar arrays, instead of arrays of records as I
did here.  You would have an array per column.  You can do the one with N arrays
if you like (runstats is available for you under asktom.oracle.com/~tkyte )

It will not be as "elapsed time incredible" (about 2-3x better) but the latching
will be well down which is the *most* important thing.

Rating: 5
sql tunning May 29, 2003
Reviewer: Javier Groszek from Argentina
Thanks Tom , I'll do it.

Regards,
Javier.

May 28, 2003 Srinivasacharyulu -- Thanks for the question regarding "procedure", version 8.1.6
You Asked
This procedure gets compiled with errors while the same as an anonymous
block gets compiled:
=======================================================================
Srinivas@Srinivas: SQL> create or replace procedure ins_emp_dept as
  2  begin
  3  for x in (select * from scott.dept) loop
  4  insert into dept values(x.deptno,x.dname,x.loc);
  5  insert into emp select * from scott.emp where deptno = x.deptno;
  6  end loop;
  7  end;
  8  /

Warning: Procedure created with compilation errors.

Srinivas@Srinivas: SQL> show errors
Errors for PROCEDURE INS_EMP_DEPT:

LINE/COL ERROR
-------- -----------------------------------------------------------------
3/11     PL/SQL: SQL Statement ignored
3/25     PLS-00201: identifier 'SCOTT.DEPT' must be declared
4/1      PL/SQL: SQL Statement ignored
4/25     PLS-00364: loop index variable 'X' use is invalid
5/1      PL/SQL: SQL Statement ignored
5/31     PLS-00201: identifier 'SCOTT.EMP' must be declared
Srinivas@Srinivas: SQL> select * from scott.dept;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING     NEW YORK
        20 RESEARCH       DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS     BOSTON

Srinivas@Srinivas: SQL>

=====================================================
WHILE THIS ANONYMOUS BLOCK GETS EXECUTED

Srinivas@Srinivas: SQL> ed
Wrote file afiedt.buf

  1  begin
  2  for x in (select * from scott.dept) loop
  3  insert into dept values(x.deptno,x.dname,x.loc);
  4  insert into emp select * from scott.emp where deptno = x.deptno;
  5  end loop;
  6* end;
Srinivas@Srinivas: SQL> /

PL/SQL procedure successfully completed.
and we said...
read
http://osi.oracle.com/~tkyte/Misc/RolesAndProcedures.html
....
Review & Followup
Rating: 3
this is what has happened when I set role none March 07, 2002
Reviewer: Srinivasacharyulu Maddali from Atlanta GA
Srinivas@Srinivas: SQL> set role none
  2  ;

Role set.

Srinivas@Srinivas: SQL> create or replace procedure ins_emp_dept as
  2  begin
  3  for x in (select * from scott.dept) loop
  4  insert into dept values(x.deptno,x.dname,x.loc);
  5  insert into emp select * from scott.emp where deptno = x.deptno;
  6  end loop;
  7  end;
  8  /

Warning: Procedure created with compilation errors.

Srinivas@Srinivas: SQL> show errors
Errors for PROCEDURE INS_EMP_DEPT:

LINE/COL ERROR
-------- -----------------------------------------------------------------
3/11     PL/SQL: SQL Statement ignored
3/25     PLS-00201: identifier 'SCOTT.DEPT' must be declared
4/1      PL/SQL: SQL Statement ignored
4/25     PLS-00364: loop index variable 'X' use is invalid
5/1      PL/SQL: SQL Statement ignored
5/31     PLS-00201: identifier 'SCOTT.EMP' must be declared
Srinivas@Srinivas: SQL>

Followup:
re-read the link.

I told you in that link:

Try this:

SQL> set role none;
SQL> "statement you want to test to see if it'll work in a procedure"

If you can do it in plus with no roles you can do it in a procedure.  If you
can't, you must have the privelege from a role and hence won't be able to do it
in a procedure (unless you are using Invokers rights in Oracle8i.  See the PLSQL
documentation for more information on this feature and make sure you understand
the ramifications).  To be able to perform that operation in a typical
procedure, you need to have that privelege granted directly to you.

setting role NONE is not the fix, it is a tool you can use to see if this is
the problem!

The fix, clearly documented there, is:

The owner of the procedure or package must have been explicitly
granted the necessary object privileges for all objects referenced within
the body of the code; the owner cannot have obtained required
privileges through roles.

You simply need to grant select on emp to srinivas; grant select on dept to
srinivas; when connected as SCOTT.

Rating: 4
Never crossed my 6 year of Oracle experience. March 07, 2002
Reviewer: Ravi from USA
Maybe because we always granted explicitly the privilege on the object(s) for
the procedures/packges.
Ravi.

Rating: 3
this is the result screen output March 07, 2002
Reviewer: Srinivasacharyulu Maddali from Atlanta GA
Srinivas@Srinivas: SQL> connect internal/oracle@srinivas
Connected.
Srinivas@Srinivas: SQL> grant create procedure to srinivas;

Grant succeeded.

Srinivas@Srinivas: SQL> connect srinivas/srinivas@srinivas
Connected.
Srinivas@Srinivas: SQL> create or replace procedure ins_emp_dept as
  2  begin
  3  for x in (select * from scott.dept) loop
  4  insert into dept values(x.deptno,x.dname,x.loc);
  5  insert into emp select * from scott.emp where deptno = x.deptno;
  6  end loop;
  7  end;
  8  /

Warning: Procedure created with compilation errors.

Srinivas@Srinivas: SQL> show errors
Errors for PROCEDURE INS_EMP_DEPT:

LINE/COL ERROR
-------- -----------------------------------------------------------------
3/11     PL/SQL: SQL Statement ignored
3/25     PLS-00201: identifier 'SCOTT.DEPT' must be declared
4/1      PL/SQL: SQL Statement ignored
4/25     PLS-00364: loop index variable 'X' use is invalid
5/1      PL/SQL: SQL Statement ignored
5/31     PLS-00201: identifier 'SCOTT.EMP' must be declared
Srinivas@Srinivas: SQL> set role none;

Role set.

Srinivas@Srinivas: SQL> create or replace procedure ins_emp_dept as
  2  begin
  3  for x in (select * from scott.dept) loop
  4  insert into dept values(x.deptno,x.dname,x.loc);
  5  insert into emp select * from scott.emp where deptno = x.deptno;
  6  end loop;
  7  end;
  8  /

Warning: Procedure created with compilation errors.

Srinivas@Srinivas: SQL> show errors
Errors for PROCEDURE INS_EMP_DEPT:

LINE/COL ERROR
-------- -----------------------------------------------------------------
3/11     PL/SQL: SQL Statement ignored
3/25     PLS-00201: identifier 'SCOTT.DEPT' must be declared
4/1      PL/SQL: SQL Statement ignored
4/25     PLS-00364: loop index variable 'X' use is invalid
5/1      PL/SQL: SQL Statement ignored
5/31     PLS-00201: identifier 'SCOTT.EMP' must be declared
Srinivas@Srinivas: SQL>
Srinivas@Srinivas: SQL> begin
  2  for x in (select * from scott.dept) loop
  3  insert into dept values(x.deptno,x.dname,x.loc);
  4  insert into emp select * from scott.emp where deptno = x.deptno;
  5  end loop;
  6  end;
  7  /
for x in (select * from scott.dept) loop
                        *
ERROR at line 2:
ORA-06550: line 2, column 25:
PLS-00201: identifier 'SCOTT.DEPT' must be declared
ORA-06550: line 2, column 11:
PL/SQL: SQL Statement ignored
ORA-06550: line 3, column 25:
PLS-00364: loop index variable 'X' use is invalid
ORA-06550: line 3, column 1:
PL/SQL: SQL Statement ignored
ORA-06550: line 4, column 31:
PLS-00201: identifier 'SCOTT.EMP' must be declared
ORA-06550: line 4, column 1:
PL/SQL: SQL Statement ignored

Srinivas@Srinivas: SQL> set role;
set role
       *
ERROR at line 1:
ORA-01937: missing or invalid role name

Srinivas@Srinivas: SQL> connect internal/oracle@srinivas
Connected.
Srinivas@Srinivas: SQL> grant dba to srinivas
  2  ;

Grant succeeded.

Srinivas@Srinivas: SQL> connect srinivas/srinivas@srinivas
Connected.
Srinivas@Srinivas: SQL> begin
  2  for x in (select * from scott.dept) loop
  3  insert into dept values(x.deptno,x.dname,x.loc);
  4  insert into emp select * from scott.emp where deptno = x.deptno;
  5  end loop;
  6  end;
  7  /
begin
*
ERROR at line 1:
ORA-00001: unique constraint (SRINIVAS.SYS_C002964) violated
ORA-06512: at line 3

Srinivas@Srinivas: SQL> truncate table srinivas.emp;

Table truncated.

Srinivas@Srinivas: SQL> truncate table srinivas.dept;
truncate table srinivas.dept
                        *
ERROR at line 1:
ORA-03292: Table to be truncated is part of a cluster

Srinivas@Srinivas: SQL> begin
  2  for x in (select * from scott.dept) loop
  3  insert into dept values(x.deptno,x.dname,x.loc);
  4  insert into emp select * from scott.emp where deptno = x.deptno;
  5  end loop;
  6  end;
  7  /
begin
*
ERROR at line 1:
ORA-00001: unique constraint (SRINIVAS.SYS_C002964) violated
ORA-06512: at line 3

Srinivas@Srinivas: SQL> delete from srinivas.dept;

4 rows deleted.

Srinivas@Srinivas: SQL> begin
  2  for x in (select * from scott.dept) loop
  3  insert into dept values(x.deptno,x.dname,x.loc);
  4  insert into emp select * from scott.emp where deptno = x.deptno;
  5  end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

Followup:
so? as I said -- simply GRANT SELECT on the objects to your schema.

If you read that above referenced link carefully -- this is stated there.

Rating: 5
I got it to work March 07, 2002
Reviewer: Srinivasacharyulu Maddali from Atlanta GA
Sys@Srinivas: SQL> grant select any table to srinivas;

Grant succeeded.

Sys@Srinivas: SQL> grant select,update,delete any table to srinivas;
grant select,update,delete any table to srinivas
      *
ERROR at line 1:
ORA-01919: role 'SELECT' does not exist

Sys@Srinivas: SQL> grant select any table to srinivas;

Grant succeeded.

Sys@Srinivas: SQL> grant update any table to srinivas;

Grant succeeded.

Sys@Srinivas: SQL> grant delete any table to srinivas;

Grant succeeded.

Sys@Srinivas: SQL> grant create procedure to srinivas;

Grant succeeded.

Sys@Srinivas: SQL> grant alter any procedure to srinivas;

Grant succeeded.

Sys@Srinivas: SQL> connect srinivas/srinivas@srinivas
Connected.
Sys@Srinivas: SQL> create or replace procedure SRINIVAS.ins_emp_dept as
  2  begin
  3  for x in (select * from scott.dept) loop
  4  insert into dept values(x.deptno,x.dname,x.loc);
  5  insert into emp select * from scott.emp where deptno = x.deptno;
  6  end loop;
  7  end;
  8  /

Procedure created.

Sys@Srinivas: SQL>
=====================================
Oracle is not giving proper error messages and they are leading, but misleading

Followup:
What message is misleading?

If you mean this one:

Sys@Srinivas: SQL> grant select,update,delete any table to srinivas;
grant select,update,delete any table to srinivas
      *
ERROR at line 1:
ORA-01919: role 'SELECT' does not exist

it is very clear.  You seem to assume that "any table" would be associative over
select, update, delete.  That command would be the same as:

grant select to srinivas;
grant update to srinivas;
grant delete any table to srinivas;

There is NO privilege or role called "select" -- hence the error.

I beg of you -- revoke these right now.  This is NOT THE WAY TO SOLVE YOUR
PROBLEM. Rather this is the way to have a totally 100% un-secure
implementation.

Slow down -- read the docs (carefully).

Rating: 5
grant privileges to columns in table April 11, 2003
Reviewer: Rafi
Hi Tom,

Can I give privileges to columns in table. Note says yes, But it does not
working for me. why?

Followup:
you can grant MODIFICATION privs (eg: user X can update column Y)

you don't give any examples, here is mine:

ops$tkyte@ORA920> grant update (x) on t to scott;

Grant succeeded.

Rating: 5
A good example April 11, 2003
Reviewer: A reviewer from Atlanta, GA
Srinivas,

I get very frustrated when I read posts like yours here.  Your first question
was fine, but your subsequent posts showed a lack of patience and also
demonstrated clearly that you did not even bother to carefully read Tom's answer
to your question. Instead, you quickly re-posted, demanding Tom answer your
question. Ugh! Too many times people simply expect for Tom to answer every
little detail of every problem they are having as if he is their own personal
consultant. Too many times, there is no independent thinking going on from the
"reviewers". I just think that by not doing YOUR share, you are making it more
difficult for all of us learn from Tom's valuable insight.

BTW - Not a personal attack there Srinivas, just putting in my 2 cents

Rating: 5
April 11, 2003
Reviewer: A reader
Very well said. I agree totally with the reviewer from Atlanta. I hope people
will learn to make best use of this site by putting in their fair share of work.
Thanks a million to Tom not only for his unmatched technical brilliance, but
also for his cool composure and professionalism. Please keep up the great work.

Regards,

--an ardent Tom fan from NewYork.

Rating: 3
April 11, 2003
Reviewer: michael
I will drink to that :)

Rating: 4
Good Point- April 11, 2003
Reviewer: S. Chakraborty from Stamford, CT
Tom is helping everyone of us understand Oracle so well- I doubt if theres
anybody for SQL Server or Sybase or even Java...or anything else.

Thanks a LOT TOM! Hope you continue you good work-

Hope the rest of the readers understand the value of this forum and post real
problems issues [only after a search on this site didn't return a answer]. That
way Tom can answer the new issues quicker and will help all of us have a bigger
library to learn from.

-thanks
S. Chakraborty

Rating: 5
Srinivas April 11, 2003
Reviewer: unknown from usa
Srinivas,
listen man listen .. read first ...please don't waist other's valuable time..

Rating: 3
I'm seeing this same behavior and I don't think it's permissions May 28, 2003
Reviewer: David from USA
(And I don't know what it means to 'waist' time (-; )

Tom, we're running 8.0.6 and I have a user that created a fairly lengthy stored
procedure that doesn't run very well.  I've taken a look at it, and based on
what I've learned from you, the first thing I noticed were explicit cursors.  I
re-coded them to make them implicit, and I get this same 'statement ignored'
error at the first reference to the loop index.

The relevant (psuedo-)code:

----------------------------

declare

<var> number(15,4);
org_u <tab>.<col>%type;

for org_u in
    (select distinct <col>
    from <tab>
    where <year_col> = to_char(yada yada)) loop

    select
        nvl(sum(decode(<list,default>)
    into
        <var>
    from
        <tab>
    where
        org_u  = <blah>;

end loop;

-------------------------
Line # = 58 Column # = 2 Error Text = PL/SQL: SQL Statement ignored
Line # = 70 Column # = 15 Error Text = PLS-00306: wrong number or types of
arguments in call to '='
-------------------------

Line 58 is the select into statement.  Line 70 is the line in the where clause
that refers to the loop index, 'org_u'.  There is no object in the database
called 'org_u'.

I've created a simpler version that looks like this:

CREATE OR REPLACE PROCEDURE dummy IS
tmpVar NUMBER;
BEGIN
   for tmp_var in (select 1 from all_objects) loop
       null;
   end loop;

END dummy;
/

And this simple version is valid.

As for permissions, the owner of all the objects is a userid that the 3rd party
app supplies and it has very broad and comprehensive grants on the entire
schema.  I had our DBA look at this thread and your link (which I've read! <g>),
and together we carefully looked at the objects in the procedure and the
account's permissions and we both agree that grants are not the problem.

It's certainly possible that we're overlooking something, but I'm wondering if
this error can occur on this statement for any other reason.  I looked at this
for hours yesterday and read a good deal of the documentation on implicit
cursors, roles and permissions, the error itself, etc, etc. and I'm stumped.  Is
there a grant I can do that covers everything in the schema and would prove that
this is a permissions problem?

Followup:
you need to give a real example, you have a bug in your code somewhere.

Rating: 3
SOLVED May 28, 2003
Reviewer: David Harmon from USA
Please ignore the previous post.  Within the loop, I was treating the cursor
index as if it were a loop index.  It was fixed by using
cursor_name.column_name.

Rating: 5
October 29, 2003
Reviewer: A reader

May 28, 2003 Graeme -- Thanks for the question regarding "SQL*Plus does not use SQL*Net?", version 81.16
You Asked
Hi Tom

Can you please get to the bottom of this...

from the TOAD online help...

NOTE:  SQL*Plus is NOT a good test of a correct SQL*Net install, as SQL*Plus
does NOT use SQL*Net.  Instead, try using TNS ping from a DOS prompt.

this has always troubled me...i can't imagine that SQL*Plus would not use
SQL*Net or Net8.

does this maybe relate to the windows GUI version? i remember you said that it
was a different beast from the command line version?

actually i don't believe it even though it is has been stated in their help docs
for years. the only thing is that i'm not sure how to prove it?

please clarify

thanks
graeme
and we said...
Don't rely on TOAD documentation for Oracle products.

Of course SQL*Plus uses Oracle Net.

gui, command line -- even the "web isqlplus" does.  sqlplus is just an OCI
application.

How to prove?  rename your tnsnames.ora and try to use sqlplus with a connect
string ;)
Review & Followup
Rating: 5
why on earth would such a reputable product make such a false claim? May 27, 2003
Reviewer: Graeme King from NYC
i thought as much...thanks

maybe someone from quest would like to comment? ;)

Rating: 3
partly true May 27, 2003
Reviewer: Harrison Picot
Hi:

The idea that SQL*Plus does not use Oracle Net
comes from the ability of a SQL*Plus session on
a Unix server to connect to the database even
if the network is messed up (a common enough
happening in the "good old" days, not common
now). Testing your "SQL*Net" install from any
box other than the server IS a good test (if
on the server, just telnet to another box and
open a SQL*Plus session there). Thanks to Mitch
Vibbert (Oracle) for teaching me this in 1995,
when messed up files were a real possibilty, and
IPC was a hot topic (Tom is going "D'oh" now, he
knows all this).

Followup:
Toad can do that.  Anything compiled with Oracle Link Libraries can do that.

It is not special to "toad" and it still uses "Net" -- it is a two task
configuration.

The only things that didn't use any "net" were the old single task linked
applications -- which haven't existed for years (much longer then toad has been
around anyway)

Rating: 4
two task configuration May 27, 2003
Reviewer: A reader
tom,

"Anything compiled with Oracle Link Libraries can do that."

" -- it is a two task configuration."

can you explain me about this.

Thanks

Followup:
when you connect to Oracle, your client process (sqlplus, your application,
whatever) "talks" to another process (a dedicated server, a dispatcher).

there are two tasks -- your client, the dedicated server.

they either use the "pure network" over tcp (or some protocol) or they use IPC
-- inner process communiciation.

In either case, the overriding protocol -- the data sent back and forth -- is
the same, it is all "net" regardless of the lower level protocol.

so, even when you are not using the network, you are using net.

Rating: 3
page 3 May 28, 2003
Reviewer: Harrison Picot from Haymarket "Rain today, rain tomorrow" VA USA
It seems to me that the original point that tnsping was better
at testing your network files than sql*plus, and not a
suggestion (or not) to use toad. As tnsping uses the
network automatically, that would seem to be correct. When
logged in ON THE server, with my oracle_sid "orcl" if I do:

sqlplus scott/

sqlplus will use IPC and not test the network files.

Using:

sqlplus scott/tiger@orcl

creates a network connection, and tests the files (at
least those on the server), just as tnsping  would have
done (I have no idea how toad decides which to use; how
it parses the command line). If the toad documentation
says that SQL*Plus does not use SQL*Net, it is wrong
but certainly it can avoid mis-configured network files
if it is run on the server, by using IPC; something that
tnsping will not do. SQL*Plus can connect when tns is not
running at all:

Ultra 1> tnsping sarah

TNS Ping Utility for Solaris: Version 8.1.6.0.0 - Production on 28-MAY-2003
10:06:08

(c) Copyright 1997 Oracle Corporation.  All rights reserved.

Attempting to contact
(ADDRESS=(PROTOCOL=TCP)(HOST=haymarket-sun.alcyontech.com)(PORT=1521))
TNS-12541: TNS:no listener

Ultra 1> sqlplus scott/tiger

SQL*Plus: Release 8.1.6.0.0 - Production on Wed May 28 10:06:30 2003

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Enterprise Edition Release 8.1.6.0.0 - Production
With the Partitioning option
JServer Release 8.1.6.0.0 - Production

>
SP2-0158: unknown SET option "-"
scott@SARAH> select table_name from user_tables;

TABLE_NAME
------------------------------
BONUS
CHILD
...
57 rows selected.

scott@SARAH>

Connecting from another box tests what you really want
to test, do all the parts (including a lot of non-oracle
bits) work to enable a client to connect? (I once got a
call that the database was down (it was not), which was
amended to "no data" (also wrong), when a client had
restored an old tnsnames.ora file or edited it. Lots
of wasted time.  Also a prime example of why you ask
for the symptom, and not let a customer tell you the
diagnosis).

Apparently both toad and sql*plus can do inter
process communication, so tnsping would certainly be
a better test than "sqlplus scott/tiger", but not so
good as either from another box. Note that you don't
have to figure any of this out if you test from a client
as its calling process (tnsping or sql*plus) does not
share a system with the server, so inter process
communication is not possible. Also, you may want to
remember that the server and the client may not be using
the same copy of the tnsnames.ora file or one application
on a client may work, but not another one; one of the
horrors of NT installations is tnsnames.ora files in
every corner. The toad advice (to use tnsping) is not
bad advice if you edit your tnsnames.ora file on the
server (something you are no longer likely to do), want
to test it, and don't want to remember any of this (but
the best test is from a client, from which toad, SQL*Plus,
and tnsping will all be fine).

Followup:
nah, tnsping is ok to see that you have a route to a listener.

sqlplus is good to see that you have a valid configuration that gets you to the
listener AND a database -- tnsping wont do that.  tnsping is useful for one
thing "is the listener up".  The rest of the stuff in my tnsnames.ora file could
be utter "junk"

I think toad just doesn't want people to use sqlplus, they might like it.

[ora920@tkyte-pc-isdn admin]$ tnsping junk.us.oracle.com

TNS Ping Utility for Linux: Version 9.2.0.3.0 - Production on 28-MAY-2003
18:59:20

Copyright (c) 1997 Oracle Corporation.  All rights reserved.

Used parameter files:
/usr/oracle/ora920/OraHome1/network/admin/sqlnet.ora

Used TNSNAMES adapter to resolve the alias
Attempting to contact (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL =
TCP)(HOST = tkyte-pc-isdn.us.oracle.com)(PORT = 1521))) (CONNECT_DATA = (SERVER
= DEDICATED) (SERVICE_NAME = junk)))
OK (10 msec)
[ora920@tkyte-pc-isdn admin]$ sqlplus scott/tiger@junk.us.oracle.com

SQL*Plus: Release 9.2.0.3.0 - Production on Wed May 28 18:59:27 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

ERROR:
ORA-12514: TNS:listener could not resolve SERVICE_NAME given in connect
descriptor

tnsping is insufficient

Rating: 5
compiled with Oracle Link Libraries May 28, 2003
Reviewer: A reader
Tom,

Your reply was really informative, thanks.

can you brief me on this too - "Anything compiled with Oracle Link Libraries can
do that."

Thanks

Followup:
what do you need to be "briefed about".

if you compile with oracle libraries (binary code), you can connect over the
net, using ipc, any of the protocols we support.  sqlplus has no magic special
oracle code in it that toad couldn't have as well

Rating: 5
May 28, 2003
Reviewer: A reader
Tom,

Can you elaborate on how does isqlplus works?How does it generate web page so
fast?

Thanks.

Followup:
it is just sqlplus running in the middle tier -- it generates html as fast as
sqlplus generates output on a terminal (well, faster even since it doesn't have
to actually print to a slow screen)

Rating: 4
How to set up iSQL*Plus ? June 18, 2003
Reviewer: Tony from India
1. My Oracle db is running in Windows. How to setup iSQL*Plus for the database
server as well as for client machines?. I find it difficult to understand Oracle
documentation regarding this. I'll be of great help if you could expalin.

2. Is it true that sqlplus GUI is going to be depricated in the next release of
Oracle?. If yes, users like me will find it very difficult.  Please don't do
that.

May 28, 2003 -- Thanks for the question regarding "Best method to insert/update a table", version 8.1.7
You Asked
Hi Tom,
       Thanks for taking my question and your help in the past  .
I have a fact tables which has 3 to 4 million records . I need to refresh this
table daily from flat file which has data for most of the columns . I need to
fill in other columns with data from two
lookup tables with 40,000 and 25,000 records respectively .
      I'm thinking about the following solutions ..
1) Load flat file into temporary table , loop through all records in a
procedure to fill in columns from lookup tables and insert into
target table .
2) Do sql*loader direct path load into target table , and upadte
   it with a procedure .

    I'm sure you know a better way to do it fast anf efficient .
I'd really appriciate your advice on this .
Thanks in advance  ...
and we said...
In 9i, this'll be:

merge into EMP e1
using EXTERNAL_TABLE e2
on ( e2.empno = e1.empno )
when matched then
    update set e1.sal = e2.sal
when not matched then
    insert ( empno, ename, job, mgr, hiredate, sal, comm, deptno )
    values ( e2.empno, e2.ename, e2.job, e2.mgr, e2.hiredate, e2.sal, e2.comm,
e2.deptno )
/

from start to finish -- no loading (just query the flat file), no update step,
no insert step.  Just a single statement.

In 8i, I would be tempted to:

a) load and analyze the file into a table with a primary key (the merge key).

b) update ( select <columns to be updated>, <columns to update with>
              from table_to_be_updated, table_with_updates
             where <join by that primary key> )
      set  <columns to be updated> = <columns to update with>

c) insert into table_to_be_updated
   select * from table_with_updates where <primary key> not in (select primary
key from table_to_be_updated )

making sure of course that table to be updated had good stats as well (cbo will
be mandatory if you want this to run above a snails pace - especially for that
not in where we want a big old hash anti join)
Review & Followup
Rating: 4
May 23, 2003
Reviewer: Sam
Hi Tom,
In 8i, why not just load all the records from the file into the main table. The
discarded records will be in a bad file. Load this bad file again into a staging
table and do update (select.....)
NOTE: This is the solution in expert one on one.

Followup:
yes, that is another viable option definitely.

Rating: 4
One more Question May 23, 2003
Reviewer: A reader
Tom,
   Thanks for your update . One question though , the primary key in target
table is not the primary key in lookup table . Our data looks something like
this ..

Target table "Orders"
ORDER_NO  ITEM_NO  ITEM_NAME   ITEM_PRICE
   1        101
   2        101
   3        111
   4        111

Lookup table "Items"
ITEM_NO  ITEM_NAME   ITEM_PRICE
 101      CAMERA       300.00
 111      DVD           12.00
   That's how table "Orders" looks like , after load from file . Ineed to fill
it from lookup tables . Same ITEM_NO appers many times in target table can I
take advantage of bulk update/insert . Apologize if that doesn't make any sense
, I never used bulk so far .
Thanks in advance

Followup:
it should not be that the primary key in target is the same -- in fact, TARGET
need not have a primary key at all -- only lookup does.

you join to the fkey in target and you join that fkey in target to the pk in
source (lookup)

you would:

update ( select a.item_name a_item_name, a.item_price a_item_price,
                b.item_name b_item_name, b.item_price b_item_price
           from orders A, items B
          where a.item_no = b.item_no )
  set a_item_name = b_item_name,
      a_item_price = b_item_price;

Rating: 5
Is this due to lack of keys on the table- Pls explain the fundamentals behind this error May 28, 2003
Reviewer: Raghu
What is a non key preserved table ?

CREATE TABLE ORDERS
(ORDER_NO NUMBER(1),
 ITEM_NO  NUMBER(3),
ITEM_NAME VARCHAR2(60),
ITEM_PRICE NUMBER(10));
/

CREATE TABLE ITEMS
(ITEM_NO NUMBER(3),
 ITEM_NAME VARCHAR2(60),
ITEM_PRICE NUMBER (10));
/

INSERT INTO ORDERS (ORDER_NO,ITEM_NO) VALUES (1,101);
INSERT INTO ORDERS (ORDER_NO,ITEM_NO) VALUES(2,101);
INSERT INTO ORDERS (ORDER_NO,ITEM_NO) VALUES(3,111);
INSERT INTO ORDERS (ORDER_NO,ITEM_NO) VALUES(4,101);
/

INSERT INTO ITEMS(ITEM_NO,ITEM_NAME,ITEM_PRICE) VALUES (101,'CAMERA',300);
INSERT INTO ITEMS(ITEM_NO,ITEM_NAME,ITEM_PRICE) VALUES (111,'DVD',12);
/

update ( select a.item_name a_item_name, a.item_price a_item_price,
                b.item_name b_item_name, b.item_price b_item_price
           from orders A, items B
          where a.item_no = b.item_no )
  set a_item_name = b_item_name,
      a_item_price = b_item_price;

SQL> update ( select a.item_name a_item_name, a.item_price a_item_price,
  2                  b.item_name b_item_name, b.item_price b_item_price
  3             from orders A, items B
  4            where a.item_no = b.item_no )
  5    set a_item_name = b_item_name,
  6        a_item_price = b_item_price
  7  /
  set a_item_name = b_item_name,
      *
ERROR at line 5:
ORA-01779: cannot modify a column which maps to a non key-preserved table

ORA-01779 cannot modify a column which maps to a non key-preserved table

Cause: An attempt was made to insert or update columns of a join view which map
to a non-key-preserved table.

Action: Modify the underlying base tables directly.

Followup:
that is saying

"look, you are trying to update ORDERS by joining to ITEMS -- but the problem is
a single row in ORDERS could possibly join to 0, 1 (those are OK) or more
(<<== thats the problem) rows in ITEMS.  Hence ORDERS is not "key preserved" --
a single row in orders could be updated 500 times in that single update
statement.  Since we cannot assure you of the order of rows being updated --
what that means is an update of a set of data on database 1 could come to a
different answer then an update of the SAME EXACT DATA on database 2 (or even
worse, running the same update on database 1, rolling back and doing the update
later against the same data could come to a different answer"

In order for that update to fly -- ITEM_NO must be known to be unique in ITEMS
so that it is 100% assured that each row in ORDERS joins to AT MOST one row in
ITEMS.

that is why your example below doesn't work -- the primary key of items must be
ITEM_NO by itself in order for this to work -- and if item_no ISN'T unique --
there is no way you can update orders from items, what work in items would
be/should be used???

Rating: 5
ORA-01779 May 28, 2003
Reviewer: Raghu
Additon of primary keys did not fix the problem ora-01779

CREATE TABLE ORDERS
(ORDER_NO NUMBER(1),
 ITEM_NO  NUMBER(3),
ITEM_NAME VARCHAR2(60),
ITEM_PRICE NUMBER(10));
/

CREATE TABLE ITEMS
(ITEM_NO NUMBER(3),
 ITEM_NAME VARCHAR2(60),
ITEM_PRICE NUMBER (10));
/

INSERT INTO ORDERS (ORDER_NO,ITEM_NO) VALUES (1,101);
INSERT INTO ORDERS (ORDER_NO,ITEM_NO) VALUES(2,101);
INSERT INTO ORDERS (ORDER_NO,ITEM_NO) VALUES(3,111);
INSERT INTO ORDERS (ORDER_NO,ITEM_NO) VALUES(4,101);
/

INSERT INTO ITEMS(ITEM_NO,ITEM_NAME,ITEM_PRICE) VALUES (101,'CAMERA',300);
INSERT INTO ITEMS(ITEM_NO,ITEM_NAME,ITEM_PRICE) VALUES (111,'DVD',12);
/

update ( select a.item_name a_item_name, a.item_price a_item_price,
                b.item_name b_item_name, b.item_price b_item_price
           from orders A, items B
          where a.item_no = b.item_no )
  set a_item_name = b_item_name,
      a_item_price = b_item_price;

SQL> update ( select a.item_name a_item_name, a.item_price a_item_price,
  2                  b.item_name b_item_name, b.item_price b_item_price
  3             from orders A, items B
  4            where a.item_no = b.item_no )
  5    set a_item_name = b_item_name,
  6        a_item_price = b_item_price
  7  /
  set a_item_name = b_item_name,
      *
ERROR at line 5:
ORA-01779: cannot modify a column which maps to a non key-preserved table

ORA-01779 cannot modify a column which maps to a non key-preserved table

Cause: An attempt was made to insert or update columns of a join view which map
to a non-key-preserved table.

Action: Modify the underlying base tables directly.

SQL>  alter table orders add  constraint o_pk primary key(Order_no);

Table altered.

SQL> update ( select a.item_name a_item_name, a.item_price a_item_price,
  2                  b.item_name b_item_name, b.item_price b_item_price
  3             from orders A, items B
  4            where a.item_no = b.item_no )
  5    set a_item_name = b_item_name,
  6        a_item_price = b_item_price  ;
  set a_item_name = b_item_name,
      *
ERROR at line 5:
ORA-01779: cannot modify a column which maps to a non key-preserved table

SQL> alter table items add constraint i_pk primary key(item_no,item_name);

Table altered.

  1  update ( select a.item_name a_item_name, a.item_price a_item_price,
  2                  b.item_name b_item_name, b.item_price b_item_price
  3             from orders A, items B
  4            where a.item_no = b.item_no )
  5    set a_item_name = b_item_name,
  6*       a_item_price = b_item_price
SQL> /
  set a_item_name = b_item_name,
      *
ERROR at line 5:
ORA-01779: cannot modify a column which maps to a non key-preserved table

Rating: 5
Additon of primary keys did not fix the problem ora-01779 - May 28, 2003
Reviewer: Reader from Ireland
Raghu,
You need to add primary key in items table and orders table on item_no to use
update(select....) set.. command

Rating: 5
Additon of primary keys did not fix the problem ora-01779 - May 28, 2003
Reviewer: Reader from Ireland
Raghu,
You need to add primary key in items table and orders table on item_no to use
update(select....) set.. command

May 28, 2003 Dave -- Thanks for the question regarding "overwhelmed", version 9.2.0
You Asked
Tom, I'm writing my first pl/sql application. As time goes by and as I read more
and more asktom and expert oracle 1-on-1 I think more and more "oh crap, I'm
making decisions on this app that will affect it for its entire life. job
security is nice but i don't want the security of fighting the fires that I've
started"

I'm losing momentum as I think of a million "what-ifs". I'm not really a
programmer by trade. I started as a sysadmin but quickly became an Oracle DBA on
the project i was hired for. Since I knew a few queries and could pronounce the
word "Oracle" they figured that I might be able to write this web based catalog
system for them. It's been really challenging but I know i'll be able to pull it
off.

However, I know that oracle is such a complex program that I don't want to make
some really bad decision that I will have to live with forever. I have a lot of
the basic functionality figured out, with a tentatively scheduled scope
creep...er...meeting with the end users in the next few weeks to get their
feedback on what i have so far.

My lack of momentum is to the point that the only thing I did today on this
project was rewrite a cursor to use "or" instead of "union" because it does a
lot fewer logical I/Os using or. The rest of my time was spent experimenting
with tkprof and autotrace, trying to find glaring errors in my sql queries. I
always have heard that that's the number one place where a database app is made
or broken.

I'm using bind variables (all pl/sql procedures using htp/owa stuff mostly), I
try to use bulk collect and forall whenever I can. On the possible negative side
I have some procedures that have to use ref cursors because they are for an
"advanced search page" that will need to allow anything from zero to about 10
search criteria combined with the search term. This should net me tons of unique
sql queries in my sga that can't be reused.

I'm sure most table storage clause type of stuff can be worked out in the wash
with some real dbas when I do some trial runs with the end users so I'm worried
less about architecture problems. Maybe I should worry more? I'm trying to not
overuse indexes and I've dropped some that aren't being used (but that sounded
like they would be when I first starting throwing things together).

How is this for a broad question: "What is the biggest problem that might be
lurking in the shadows that will be a big pain to fix when things go live and
might even be a show-stopper?"
and we said...
that is an easy question to answer, the biggest problem that will be lurking in
the shadows and is a pain to fix when things go live (and will be a show
stopper) is:

Lack of testing to scale.

many people test in single user mode.  thats good, that is important, has to be
done.  You need to get it fast and correct, then you need to test to some scale.

what happens when 10 people run your application simultaneously?  100?  N?
where N is your target number.  can your machine do it (eg: if you take 0.25 cpu
seconds per page to generate a page and you have 6 requests per second and you
have a single cpu machine...... woops -- I need 1.5 cpu seconds per cpu second I
have (actually more as the queues backup - that system won't run, it won't
crawl, it'll grind to a halt))

do you have updates?  do they all play well (eg: does everyone update the same
record -- > serialization?).  do you have multi-user concurrency issues?

so, do you have a test plan, do you plan on testing to scale?  do you have an
end user test plan at all?
Review & Followup
Rating: 5
test plan May 28, 2003
Reviewer: Dave from san diego, ca
I'm not quite sure how to test a web application without using really expensive
software that actually simulates a large group of end users clicking on things.

My usage will be something like:

2-5 internal users doing all the real work (creating orders, looking up orders,
etc). There will probably be a desire to allow non-employee users log in and
check their own orders, look up availability of items, etc...so let's bump that
up to 10 concurrent users and 100 in a day.

a handful of non-internal users doing searches on our media catalog...this will
probably be in the hundreds per day but not a lot of concurrent users.

 Now, this is on an ultra 80 with 4x450 mhz processors and 2gb of ram. It will
probably be plenty of hardware for the beginning of things, but being at a
public college I'm not holding my breath for clustered sunfire 3800s showing up
at my door. This hardware needs to last.

Do you have any pointers on simulating simultaneous web users?

Rating: 5
maybe you're worrying too much May 28, 2003
Reviewer: Bobcat from San Diego, CA, USA
Testing and tuning to scale PROACTIVELY before going live is extremely
important, but in reality, there is only so much you can do because of certain
constraints, such as the lack of testing hardware/sofware, etc. There maybe some
real life scenarios that cannot be fully tested. So what? you go live whenever
you should, and resort to REACTIVE measures. Be sure to setup statspack. Be
prepared to do sql trace. "Tuning is a constant thing", I believe Tom said that
in "Export one on one".

Followup:
tuning is a constant thing that starts way way back before the first line of
code is written is more in the spirit.

I agree - you cannot be paralyzed...  you have to do all you can do though..

Rating: 3
Load testing framework May 28, 2003
Reviewer: Glenn A. Santa Cruz from Mobile, AL USA
I hate to plug any particular software on a forum such as this, but this seems
appropriate: take a look at the OpenSTA testing framework -- it is an open
source project that will provide a load testing mechanism for your web
application (as well as others), and is pretty easy to use.
http://www.opensta.org/
We use this in our development phase to issue heavy workload, and have found
this type of testing to be crucial to success.

Followup:
no worries, plug away -- for free or for fee, if it works, and works well,
people should know about it.

testing is one of the most overlooked items in this whole development process we
call software engineering.

Rating: 3
I've had some experience with this, Dave May 28, 2003
Reviewer: david from USA
There are tools out there that bombard your web server with requests.  I don't
recall that they were all that expensive.  But what we've done from a practical
standpoint is to come in really late one night and use a classroom and a few
rolling chairs.  Most organizations have some sort of training room with two or
three rows of PCs.  If you really want it cheap, you can simulate 10-20 users or
more just by bringing all the machines up and walking around sending requests to
the server from different machines.

I've actually seen small teams of very well-respected consultants do small scale
stress testing this way.  If you're talking about 10 concurrent users, this is a
reasonable test.

HTH

Followup:
agreed, I've used humans as well -- can be very effective... (especially when we
couldn't get loadrunner doing what we needed in a pinch ;)

Rating: 5
I have had some experience with simulating multiple users May 28, 2003
Reviewer: Winston
If you have the same set-up as my project environment:  WebServer--Application
Server--Database server, and you use pkg.stored_procedure to return ref cursor
to the java DAO layer. Since application server could cache the web requests
coming from the browser, I found writing a small multi-thread java program to
call your stored-procedure via JDBC is a simple but efficient way to test your
stored procedure.

Rating: 5
It sounds like you know what you're doing... June 03, 2003
Reviewer: Mark from Long Island, NY USA
I agree with whoever said not to worry so much. You seem to know what you're
doing; you can do a lot worse. Here's what I walked into when I was hired at my
current job:

A bunch of Access front ends, each associated with a different Oracle schema,
connected via ODBC (most or all of the functionality is the same throughout
each), redundant tables in each of these schemas (identical instances of table A
in Schemas X, Y and Z), massive OLAP style queries (yes, done from Access), NO
INDEXES, SGA and PGA settings at default values, tables not even in first normal
form, 180 column tables with 50 not even used... I could go on...

May 29, 2003 -- Thanks for the question regarding "length limitation of CLOB in pl/sql", version 9.0.1.1.0
You Asked
Hi Tom,

I understand there is a length limitation (max 32000) for all the plsql
variable.  However, I have the following procedure.

CREATE OR REPLACE PROCEDURE CLOB_TAB_TEST IS
    NUM       CLOB_TEST.ID_COL%TYPE;  -- INT
    V_VALUE      CLOB_TEST.VALUE_COL%TYPE; -- CLOB
BEGIN
 NUM := 2;
 V_VALUE := rpad('*', 32999, '*');

 INSERT INTO CLOB_TEST VALUES (NUM, V_VALUE);
 COMMIT;
 DBMS_OUTPUT.PUT_LINE('Length = ' || to_char(length(v_value)));
END;
/

The V_VALUE variable is assigned a 32999 long string and the procedure can be
run without any error.

Am I just lucky here?  Or is there anything that i am not aware of?

Another question is:  If I use "||" to build a clob string instead of
dbms_log.write, will I get wrong data?

Thank You!

Junying
and we said...
9i has made vast improvements to the lob types.  You can treat a clob like it
was a string for most purposes.

So, lobs behave pretty much like strings under the covers in 9i and up.
Review & Followup
Rating: 4
So is there a max length Oracle can handle? May 31, 2002
Reviewer: Junying from Melbourne, Australia
Hi Tom,

Thank you for your answer and your help!  Great site!

We use your way to send out emails (dbms_job and utl_smtp); so I would like to
know the max length/size of email body that Oracle can handle (our application
will not fail).  In your book it is said the limitation is 32k.  However from my
test the body length can be 60000.  Are size in byte and length the same?

Thanks again!

June

Followup:
In my book -- I used a varchar2 implementation, that is limited to 32k.

If you use a lob, it is 4gig.

Rating: 4
Using more than 32k June 27, 2002
Reviewer: Vikas from Delhi India
Hi tom,
can you provide me a link which shows how  to use clob for more than 32k.
Thanks,

Followup:
declare
   l_clob clob;
begin
   insert into t values ( empty_clob() ) returning clob_col into l_clob;

   for i in 1 .. 10
   loop
      dbms_lob.writeAppend( l_clob, 32000, rpad( '*', 32000, '*' ) );
   end loop;
   commit;
end;
/

is an example?  don't know what you are looking for here really.  A clob is a
lot like a file -- you can "seek" in it (dbms_lob.substr, dbms_lob.read), you
can write to it (dbms_lob.write, writeappend) and so on.  pretend it is a file
and you might be better off understanding it.

Rating: 5
May 28, 2003
Reviewer: A reader
tom, for a test case I want to get some GIVEN XML content inserted into a table
column of type CLOB through an anonymous block

declare
  myclob clob;

begin
  myclob := to_clob('<my_real_large_xml_content');

  dbms_output.put_line(length(myclob));  -- returns 25071!
  dbms_output.put_line(substr(clo, length(clo) -30));  -- does NOT return the
LAST tag of the given XML content
end;

can you pls help?

Followup:
no idea what your goal is.  but if you just want to insert, insert it then.

works for me though

ops$tkyte@ORA920LAP> declare
  2     myclob clob;
  3  begin
  4          myclob := 'blah blah blah blah blah blah
  5                     blah blah blah blah blah blah
  6                     blah blah blah blah blah blah
  7                     blah blah blah blah blah blah
  8                     blah blah blah blah blah blah
  9                     blah blah blah blah blah blah
 10                     blah blah blah blah blah blah
 11                     blah blah blah blah blah blah
 12                     blah blah blah blah blah blah
 13                     blah blah blah blah blah blah
 14                     blah blah blah blah blah blah
 15                     blah blah blah blah blah blah
 16                     blah blah blah blah blah blah
 17                             <some tag>';
 18          dbms_output.put_line( length(myclob) );
 19          dbms_output.put_line( substr( myclob, length(myclob)-30) );
 20  end;
 21  /
550
blah blah blah
                           <some tag>

PL/SQL procedure successfully completed.

Rating: 2
May 28, 2003
Reviewer: A reader
no idea why some of my "blahs" get lost?

Followup:
without an example -- no, not really.  if you run mine, what happens for you.

Rating: 3
May 28, 2003
Reviewer: A reader
it makes ME very unhappy ;o) ... because I see '<some tag>' AND the number of
bytes is what I expected it to be.

Rating: 5
DBMS_LOB.OPEN May 28, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
I think I read somewhere (don't recall where) that when writing to a CLOB, its
advantageous to do a explicit DBMS_LOB.OPEN and then perform your writes and
then explicitly do a DBMS_LOB.CLOSE. It will give better performance compared to
not doing an explicit OPEN and CLOSE (it is good to do this if you write to a
CLOB in a loop which runs multiple iterations).
Do you agree? will it really make a difference in performance?
Thanks,

Followup:
benchmark it for us -- see runstats on asktom.oracle.com/~tkyte

I've never heard that myself, sounds like an interesting test to setup ;)

Rating: 5
Was able to locate it... May 28, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
Here is the section from the Oracle9i Supplied PL/SQL Packages and Types
Reference Release 2 (9.2):
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96612/d_lob2.htm#1009075
<quote>

Usage Notes

The form of the VARCHAR2 buffer must match the form of the CLOB parameter. In
other words, if the input LOB parameter is of type NCLOB, then the buffer must
contain NCHAR data. Conversely, if the input LOB parameter is of type CLOB, then
the buffer must contain CHAR data.

When calling DBMS_LOB.WRITE from the client (for example, in a BEGIN/END block
from within SQL*Plus), the buffer must contain data in the client's character
set. Oracle converts the client-side buffer to the server's character set before
it writes the buffer data to the LOB.

It is not mandatory that you wrap the LOB operation inside the Open/Close APIs.
If you did not open the LOB before performing the operation, the functional and
domain indexes on the LOB column are updated during the call. However, if you
opened the LOB before performing the operation, you must close it before you
commit or rollback the transaction. When an internal LOB is closed, it updates
the functional and domain indexes on the LOB column.

If you do not wrap the LOB operation inside the Open/Close API, the functional
and domain indexes are updated each time you write to the LOB. This can
adversely affect performance. Therefore, it is recommended that you enclose
write operations to the LOB within the OPEN or CLOSE statement.

</quote>

Here the documentation says that not performing a LOB operation wrapped within a
OPEN/CLOSE call will affect the performance and it recommends doing OPEN before
the lob operation and then CLOSE at the end of the LOB operation.

Followup:
ahh, if you call dbms_lob.write* operations many times in succession -- yes.
Here is my benchmark -- i use runstats (asktom.oracle.com/~tkyte) -- it measures
more things.  There is a definite and obvious difference on my test once you
start doing THOUSANDS of write calls (abnormal, most people do a handful -
usually one -- rarely thousands)

Here is my test case:

ops$tkyte@ORA920> create table t ( x clob );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace procedure p( p_open_close in boolean default
false,
  2                                 p_iters in number default 100 )
  3  as
  4      l_clob clob;
  5  begin
  6      insert into t (x) values ( empty_clob() )
  7      returning x into l_clob;
  8
  9      if ( p_open_close )
 10      then
 11          dbms_lob.open( l_clob, dbms_lob.lob_readwrite );
 12      end if;
 13
 14      for i in 1 .. p_iters
 15      loop
 16          dbms_lob.WriteAppend( l_clob, 5, 'abcde' );
 17      end loop;
 18
 19      if ( p_open_close )
 20      then
 21          dbms_lob.close( l_clob );
 22      end if;
 23      commit;
 24  end;
 25  /

Procedure created.

We can control whether the lob is opened/closed and how many writeAppends we do
to it.  The results for 100 writeAppends was marginal:

ops$tkyte@ORA920> exec runstats_pkg.rs_start;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec p(FALSE,100);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runstats_pkg.rs_middle;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec p(TRUE,100);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runstats_pkg.rs_stop(10000);
Run1 ran in 11 hsecs
Run2 ran in 9 hsecs
run 1 ran in 122.22% of the time

Name                                  Run1        Run2        Diff

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
9,323       9,024        -299    103.31%

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>

They more or less -- for all intents and purposes -- did the same amount of work
in about the same amount of time.

Moving onto 1,000 iterations however

ops$tkyte@ORA920> exec runstats_pkg.rs_start;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec p(FALSE,1000);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runstats_pkg.rs_middle;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec p(TRUE,1000);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runstats_pkg.rs_stop(10000);
Run1 ran in 111 hsecs
Run2 ran in 102 hsecs
run 1 ran in 108.82% of the time

Name                                  Run1        Run2        Diff
STAT...session logical reads        38,219      12,897     -25,322
STAT...consistent changes           31,182       4,376     -26,806
STAT...data blocks consistent       30,673       3,859     -26,814
STAT...consistent gets - exami      32,022       5,206     -26,816
STAT...consistent gets              32,870       5,942     -26,928
LATCH.cache buffers chains         115,595      41,229     -74,366
STAT...session pga memory          107,996       7,812    -100,184
STAT...redo size                 5,443,144   5,599,576     156,432

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
171,337      92,963     -78,374    184.31%

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>

we start to see something interesting -- first, the one without open/close is
taking longer by the wall clock BUT more interesting is the latching that is
going on.  We almost doubled the latching by not opening/closing here.  That
inhibits scalability massively -- in a multi-user test, the one without
open/close would not scale very well at all.

Moving upto 2,000 writeappends

ops$tkyte@ORA920> exec runstats_pkg.rs_start;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec p(FALSE,2000);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runstats_pkg.rs_middle;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec p(TRUE,2000);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runstats_pkg.rs_stop(10000);
Run1 ran in 1258 hsecs
Run2 ran in 706 hsecs
run 1 ran in 178.19% of the time

Name                                  Run1        Run2        Diff
STAT...db block gets                19,044      29,575      10,531
LATCH.undo global data              18,837       4,022     -14,815
LATCH.simulator hash latch          36,148       2,215     -33,933
STAT...redo size                15,471,664  15,910,000     438,336
STAT...consistent changes        1,741,844      11,075  -1,730,769
STAT...data blocks consistent    1,741,335      10,557  -1,730,778
STAT...session logical reads     1,783,277      46,937  -1,736,340
STAT...consistent gets - exami   1,753,600      13,920  -1,739,680
STAT...consistent gets           1,765,441      18,570  -1,746,871
LATCH.cache buffers chains       5,349,496     147,523  -5,201,973

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
5,541,018     277,802  -5,263,216  1,994.59%

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>

now the wall clock difference is very measurable -- but look at those latches!
 that is *huge*.

At 5,000

ops$tkyte@ORA920> exec runstats_pkg.rs_start;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec p(FALSE,5000);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runstats_pkg.rs_middle;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec p(TRUE,5000);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runstats_pkg.rs_stop(10000);
Run1 ran in 8925 hsecs
Run2 ran in 1956 hsecs
run 1 ran in 456.29% of the time

Name                                  Run1        Run2        Diff
STAT...no work - consistent re       2,478      18,398      15,920
STAT...db block gets                64,682      96,602      31,920
STAT...db block changes            136,403      84,374     -52,029
STAT...redo entries                114,133      44,234     -69,899
LATCH.redo allocation              114,690      44,735     -69,955
LATCH.cache buffers lru chain       86,428       5,496     -80,932
STAT...free buffer requested        85,405       4,074     -81,331
STAT...calls to kcmgas              86,058       4,712     -81,346
STAT...cleanout - number of kt      84,750         378     -84,372
STAT...active txn count during      84,743         351     -84,392
STAT...immediate (CR) block cl      84,729         312     -84,417
STAT...cleanouts and rollbacks      84,722         288     -84,434
STAT...CR blocks created            84,750         288     -84,462
LATCH.undo global data             173,491      13,620    -159,871
LATCH.simulator hash latch         697,188       4,117    -693,071
STAT...redo size                48,304,392  46,655,964  -1,648,428
STAT...consistent changes       17,086,252      37,143 -17,049,109
STAT...data blocks consistent   17,085,741      36,626 -17,049,115
STAT...consistent gets - exami  17,179,776      46,216 -17,133,560
STAT...session logical reads    17,337,689     169,999 -17,167,690
STAT...consistent gets          17,277,215      77,605 -17,199,610
LATCH.cache buffers chains      52,010,113     508,652 -51,501,461

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
53,376,618     865,837 -52,510,781  6,164.74%

PL/SQL procedure successfully completed.

now it is getting serious.

Rating: 5
And, here is some benchmark tests... May 28, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
These tests indicate no difference. Does this mean that what documentation says
is not totally correct?
OR
I'm missing something during the tests?
What's your opinion?

Using DBMS_LOB.WRITEAPPEND ...

SQL> create table lob_effect(id int primary key, loc clob)
  2  tablespace users
  3  lob(loc) store as lob_lob_effect_segment (tablespace users)
  4  /

Table created.

Elapsed: 00:00:00.00
SQL> set serveroutput on
SQL> set timing on
SQL> DECLARE
  2      l_cnt        PLS_INTEGER;
  3      l_start_time PLS_INTEGER;
  4      lob_loc      CLOB;
  5      data         VARCHAR2(32767) := RPad('*',
  6                                           32767,
  7                                           '*');
  8  BEGIN
  9      l_start_time := dbms_utility.get_time;
 10      FOR idx IN 1 .. 100
 11      LOOP
 12          INSERT INTO lob_effect
 13          VALUES
 14              (idx,
 15               empty_clob())
 16          RETURNING loc INTO lob_loc;
 17          l_cnt := 0;
 18          WHILE (l_cnt < 32767)
 19          LOOP
 20              dbms_lob.WRITEAPPEND(lob_loc,
 21                             length(SubStr(data,
 22                                           l_cnt + 1,
 23                                           10000)),
 24                             SubStr(data,
 25                                    l_cnt + 1,
 26                                    10000));
 27              l_cnt := l_cnt + 10000;
 28          END LOOP;
 29      END LOOP;
 30      dbms_output.put_line('Time:' || (dbms_utility.get_time -
l_start_time));
 31      COMMIT;
 32  END;
 33  /
Time:3934

PL/SQL procedure successfully completed.

Elapsed: 00:00:39.03
SQL> truncate table lob_effect
  2  /

Table truncated.

Elapsed: 00:00:00.08
SQL> DECLARE
  2      l_cnt        PLS_INTEGER;
  3      l_start_time PLS_INTEGER;
  4      lob_loc      CLOB;
  5      data         VARCHAR2(32767) := RPad('*',
  6                                           32767,
  7                                           '*');
  8  BEGIN
  9      l_start_time := dbms_utility.get_time;
 10      FOR idx IN 1 .. 100
 11      LOOP
 12          INSERT INTO lob_effect
 13          VALUES
 14              (idx,
 15               empty_clob())
 16          RETURNING loc INTO lob_loc;
 17          l_cnt := 0;
 18
 19          dbms_lob.OPEN(lob_loc,
 20                        DBMS_LOB.lob_readwrite);
 21
 22          WHILE (l_cnt < 32767)
 23          LOOP
 24              dbms_lob.WRITEAPPEND(lob_loc,
 25                             length(SubStr(data,
 26                                           l_cnt + 1,
 27                                           10000)),
 28                             SubStr(data,
 29                                    l_cnt + 1,
 30                                    10000));
 31              l_cnt := l_cnt + 10000;
 32          END LOOP;
 33
 34          dbms_lob.CLOSE(lob_loc);
 35
 36      END LOOP;
 37      dbms_output.put_line('Time:' || (dbms_utility.get_time -
l_start_time));
 38      COMMIT;
 39  END;
 40  /
Time:3924

PL/SQL procedure successfully completed.

Elapsed: 00:00:39.02
SQL>
SQL>

And, using DBMS_LOB.WRITE ....

SQL> set serveroutput on
SQL> set timing on
SQL> DECLARE
  2      l_cnt        PLS_INTEGER;
  3      l_start_time PLS_INTEGER;
  4      lob_loc      CLOB;
  5      data         VARCHAR2(32767) := RPad('*',
  6                                           32767,
  7                                           '*');
  8  BEGIN
  9      l_start_time := dbms_utility.get_time;
 10      FOR idx IN 1 .. 100
 11      LOOP
 12          INSERT INTO lob_effect
 13          VALUES
 14              (idx,
 15               empty_clob())
 16          RETURNING loc INTO lob_loc;
 17          l_cnt := 0;
 18          WHILE (l_cnt < 32767)
 19          LOOP
 20              dbms_lob.WRITE(lob_loc,
 21                             length(SubStr(data,
 22                                           l_cnt + 1,
 23                                           10000)),
 24                             l_cnt + 1,
 25                             SubStr(data,
 26                                    l_cnt + 1,
 27                                    10000));
 28              l_cnt := l_cnt + 10000;
 29          END LOOP;
 30      END LOOP;
 31      dbms_output.put_line('Time:' || (dbms_utility.get_time -
l_start_time));
 32      COMMIT;
 33  END;
 34  /
Time:3822

PL/SQL procedure successfully completed.

Elapsed: 00:00:38.02
SQL> truncate table lob_effect
  2  /

Table truncated.

Elapsed: 00:00:00.07
SQL> DECLARE
  2      l_cnt        PLS_INTEGER;
  3      l_start_time PLS_INTEGER;
  4      lob_loc      CLOB;
  5      data         VARCHAR2(32767) := RPad('*',
  6                                           32767,
  7                                           '*');
  8  BEGIN
  9      l_start_time := dbms_utility.get_time;
 10      FOR idx IN 1 .. 100
 11      LOOP
 12          INSERT INTO lob_effect
 13          VALUES
 14              (idx,
 15               empty_clob())
 16          RETURNING loc INTO lob_loc;
 17          l_cnt := 0;
 18
 19          dbms_lob.OPEN(lob_loc,
 20                        DBMS_LOB.lob_readwrite);
 21
 22          WHILE (l_cnt < 32767)
 23          LOOP
 24              dbms_lob.WRITE(lob_loc,
 25                             length(SubStr(data,
 26                                           l_cnt + 1,
 27                                           10000)),
 28                             l_cnt + 1,
 29                             SubStr(data,
 30                                    l_cnt + 1,
 31                                    10000));
 32              l_cnt := l_cnt + 10000;
 33          END LOOP;
 34
 35          dbms_lob.CLOSE(lob_loc);
 36
 37      END LOOP;
 38      dbms_output.put_line('Time:' || (dbms_utility.get_time -
l_start_time));
 39      COMMIT;
 40  END;
 41  /
Time:3815

PL/SQL procedure successfully completed.

Elapsed: 00:00:38.01
SQL>
SQL>

Followup:
Your test only wrote to the lob 4 times with 10,000 10,000 10,000 2,767 bytes in
succession.

You have to hit it lots more then that to see the performance issue -- see the
benchmark above.

May 29, 2003 Marcel -- Thanks for the question regarding "pga_aggregate_target", version 9.2.0.1
You Asked
Hi Tom

I would like to take advantage of the new pga_aggregate_target
that is available in 9.2 to allow dynamic automatic management of the sessions
pga-memory allocation.
My question is: how does the currently available pga-memory affect the execution
plan of newly parsed statements? And if it does: do the execution plans of
cached statements change when there is no longer the same amount of memory
available?
Concrete scenario:
Session 1 connects, parses and executes a join over three tables.
Since session one is the only session, it gets all pga memory allocated, and the
optimizer decides to do a hash join.
Then n other sessions connect, all of which execute the same join at the same
time.
Now each of these sessions has only 1/n of the pga-memory available, therefore
generating more hash partitions, which would make a nested loop the better
access path.
What will the optimizer do in such a scenario?

Thanks for any thoughtfull comments on this.
and we said...
I need to change my response to this one.... After researching a bit (as
prompted by the review), I mis-represented the setting initially.

The optimizer looks at the setting of PGA_AGGREGATE_TARGET when using auto
memory mgmt.  This value, along with the other init.ora settings that affect the
optimizer, are taken into consideration.  External events that are current
"happening" do not affect the optimizer (for all it knows, these events will not
be happening when it comes time to actually run the query).  You do have the
ability to gather statistics on the 'system' itself to have the actual system
use impact the generated query plans -- but then you must pick some
representative times at which to sample the system.
Review & Followup
Rating: 3
July 02, 2002
Reviewer: Chris from UK
Hi Tom,

I, like Marcel, was under the impression that the pga_aggregate_target was a
global setting for the instance, not per process. The 9iR2 concepts manual tends
to confirm a global setting.
http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96524/c08memor.htm#20461
Could you please confirm if this is a global setting for the entire instance of
a per session limit.

Thanks in advance.

Rating: 3
pga_aggregate_target not shared for all processes? July 02, 2002
Reviewer: Marcel Duerr from Switzerland
Hmm,
Is this really tha case?
I understand the manuals different.
For example in the database reference manual, page 1-127
it is stated that: "pga_aggregate_target spacifies the aggregate memory
available to all server processes.." and further down "When setting this
parameter you should examine the total amount of memory of your system that is
available to Oracle and subtract the SGA. You can assign the remaining memory to
PGA_AGGREGATE_TARGET."
This implies that all processes need to share pga_aggregate_target, since there
is no more memory left.
Or should I read "You can assign the remaining memory divided by the expected
number of processes to PGA_AGGREGATE_TARGET." ??

Rating: 4
pga_aggregate_target July 02, 2002
Reviewer: Marcel Durr from Switzerland
Ok, I understand that the actual number of sessions will not change any cached
execution plans.
If i understand you correct, you mean that execution plans get changed whenever
I compute new system stats using dbms_stats.gather_system_stats. This would lead
to a huge amount of reparsing, since ALL cached statements would have to be
discarded from the cache?
Also if I got it correctly, then gather_system_stats rates cpu power to i/o
power of the system (roughly spoken). What I miss here is the amount of
available memory.
I guess I just don't understand how the optimizer takes the memory into
consideration. Is it the amount of pga memory available at parsing time, or is
it a fixed fraction of pga_aggregate_target? And how does the avaliable memory
influence execution plans (if at all)?
Thanks for putting some light on this.

Followup:
Only *newly generated* plans will be affected by new system stats (eg: you would
have to flush the shared pool to get all subsequent parses to look at the new
system stats)

It is the pga_aggregate_target that it looks at -- not the amount available at
parse time (which is not representative of the system over time).

Rating: 4
sum(PGA_ALLOC_MEM) and sum(PGA_MAX_MEM) October 23, 2002
Reviewer: A reader
Hi

If I have pga_aggregate_target set to 20MB how come I can see sum(PGA_MAX_MEM)
from v$process around 30 or even more MB? In the other hand sum(PGA_ALLOC_MEM)
stays very similar to 20MB.....

Followup:
PGA_MAX_MEM is the max a process had allocated which is not the same as "i
currently have allocated"

it is the most they ever had, not necessarily what they currently have.  So, you
could have 30 processes -- each having had 1m at some point (not all at the same
time bear in mind).  Now, each has 0.5 meg currently.

Rating: 4
Can't oracle sense the statistics change? May 29, 2003
Reviewer: Tony from India
What I understand from your answer is that old quries and their plans become
incorrect once new statistics is collected. So, we need to flush the shared pool
after every run of statistics collection so that new plans will be generated for
quries. This is equalent to restarting the database. isn't it?. Can't Oracle
sense the change in statistics and generate new plans automatically?
DBAs don't restart the db as they don't want to loose the quries and their plans
in the shared pool. Tom, Correct me if I'm wrong.

Followup:
No, Oracle will automagically invalidate the plans for you.  you analyze, we
take care of the rest.

who said to flush the shared pool?

May 29, 2003 Nag -- Thanks for the question regarding "analytical functions", version 8.1.7
You Asked
/* I HAVE MADE CORRECTIONS, sorry for the confusion, it was a typo and
unintended */

Tom
Iam trying to write a challenging sql

The table has 2 columns

the first one is a date
the second one is a number

For each date there is a value.

For example there are values for dates from 01-dec-2000 to 15-dec-2000
and the rest of the days for the month have no values.

Now, the value for ded-15-2000 is 2225.
I want to select the average  like this

sum of all the values untill 14th dec + dec15th value multiplied by rest of the
days in the month(i.e. 2225*16)
divided by 31

(So we need to know what is the last date from which a value exists, and how
many days in that month are without any values etc)

Iam trying to do it in a single sql, can you me your input into how to write
this sql, do analytical functions help in any way here.

Thank you
and we said...
ops$tkyte@ORA717DEV.US.ORACLE.COM> create table t ( dt date, x number );

Table created.

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> begin
  2          for i in 0 .. 13 loop
  3                  insert into t values ( to_date( '01-dec-2000' ) + i, 2000+i
);
  4          end loop;
  5          insert into t values ( to_date( '15-dec-2000' ), 2225 );
  6          commit;
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> select avg(x)
  2    from (
  3  select nvl(x, default_x) x
  4    from ( select dt, x from t ) a,
  5             ( select to_date('01-dec-2000')+rownum-1 the_date
  6                     from all_objects
  7                    where rownum <=
to_number(to_char(last_day(to_date('01-dec-2000')),'dd'))
  8             ) b,
  9             ( select x default_x
 10                     from ( select x from t order by dt desc )
 11                    where rownum = 1 ) c
 12    where b.the_date = a.dt (+)
 13             )
 14  /

    AVG(X)
----------
2126.32258

is one way to do that and

ops$tkyte@ORA717DEV.US.ORACLE.COM> select sum( sum1*cnt ) / sum( cnt ) from (
  2  select avg(x) sum1, count(*) cnt
  3    from t
  4  union all
  5  select default_x, ( to_number(to_char(last_day(dt),'dd')) -
to_number(to_char(dt,'dd')) )
  6    from ( select x default_x, dt
  7             from ( select x , dt
  8                      from t
  9                     order by dt desc )
 10            where rownum = 1 )
 11  )
 12  /

SUM(SUM1*CNT)/SUM(CNT)
----------------------
            2126.32258

is another...
Review & Followup
Rating: 5
October 21, 2001
Reviewer: A reader
Tom

It is really fascinating to see, the way you provide solutions, for seemingly
impossible things.

Hats off,  to you.

But I think you should be breaking the query into parts and explain it to us.
That way, next time, we will be in a situation to write these queries on our
own.

Followup:
In the first query:

ops$tkyte@ORA717DEV.US.ORACLE.COM> select avg(x)
  2    from (
  3  select nvl(x, default_x) x
  4    from ( select dt, x from t ) a,
  5             ( select to_date('01-dec-2000')+rownum-1 the_date
  6                     from all_objects
  7                    where rownum <=
to_number(to_char(last_day(to_date('01-dec-2000')),'dd'))
  8             ) b,
  9             ( select x default_x
 10                     from ( select x from t order by dt desc )
 11                    where rownum = 1 ) c
 12    where b.the_date = a.dt (+)
 13             )
 14  /

The inline view A is the original data (dec 1 thru 15).

Inlinew view B is a set of data that has a row for every day in the month of
decemeber.

Inline view C is simply the value of X you want defaulted for the days in the
month of december that are not in A.  Its the last value for that month.  It has
exactly 1 row.

We outer join B to A -- so that now we have a result set that has a row for
every day in the month.  To this, we cartesian product inline view C -- since
there is only one row in C, what we've really done is just given every row in B
outer join A that row from C.  We use NVL to either get the value of X from the
original table -- or if we "made up" a row from the outer join, we use the value
from C.

Then we average.  QED.  It is quite easy to reverse engineer this your self.
Just run the B outer join A part in sqlplus (remove c, remove the sum). Then add
C and see what you get.  Then add the NVL to just get the relevant data, then
avg is trivial.

The second  query

ops$tkyte@ORA717DEV.US.ORACLE.COM> select sum( sum1*cnt ) / sum( cnt ) from (
  2  select avg(x) sum1, count(*) cnt
  3    from t
  4  union all
  5  select default_x, ( to_number(to_char(last_day(dt),'dd')) -
to_number(to_char(dt,'dd')) )
  6    from ( select x default_x, dt
  7             from ( select x , dt
  8                      from t
  9                     order by dt desc )
 10            where rownum = 1 )
 11  )
 12  /

was just an alternative approach I thought up as I was putting the finishing
touches on the first one.  All we do is get the avg(x) and count(*) from the
original data.
then we use the old inline view trick again to get the value we wanted for the
last day of data we observed in the table -- this time we get the DATE as well
so we can figure out how many days are missing.

After that, simple math gets us our average.

Rating: 5
October 21, 2001
Reviewer: A reader
Tom you are indoutedly the best. i wish i could become like you

Rating: 5
correlated subquery instead of "order by" in inner select October 22, 2001
Reviewer: Mikito Harakiri
select sum( sum1*cnt ) / sum( cnt ) from (
    select avg(x) sum1, count(*) cnt from t
  union all
    select x , to_number(to_char(last_day(dt),'dd')) -
    to_number(to_char(dt,'dd'))  from t where dt in (select max(dt) from t)
);

Rating: 5
Query Tuning May 29, 2003
Reviewer: A reader
I have a transaction table which above 10000000 rows.
When I run this query it takes very long time to execute. Is there any way I can
optimize this query. This query is perform on as_of_date window which contains
approx. 3600000 records

SQL> select ods.account_id,
        ods.constitution,
        ods.branch_code,
        ods.gl_sub_head_code,
            ods.scheme_code, ods.value_date, ods.maturity_date,
ods.interest_rate,
            ods.last_interest_posted_date, ods.next_interest_outflow_date,
            sum(deposit_amount) over (partition by ods.constitution,
                      ods.branch_code, ods.gl_sub_head_code,
                                      ods.scheme_code, ods.value_date,
                      ods.maturity_date, ods.interest_rate,
                                      ods.last_interest_posted_date,
                      ods.next_interest_outflow_date) deposit_amount,
            sum(maturity_amount) over (partition by ods.constitution,
                       ods.branch_code, ods.gl_sub_head_code,
                                       ods.scheme_code, ods.value_date,
                       ods.maturity_date, ods.interest_rate,
                                       ods.last_interest_posted_date,
                       ods.next_interest_outflow_date) maturity_amount,
            sum(balance) over (partition by ods.constitution, ods.branch_code,
                   ods.gl_sub_head_code, ods.scheme_code, ods.value_date,
                   ods.maturity_date, ods.interest_rate,
ods.last_interest_posted_date,
                   ods.next_interest_outflow_date) balance
                     from ods$term_deposits_source ods
                     where business_date = p_as_of_date
                     and   quantum_optima = 'Y'
                     and   constitution like 'R%'

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=77)
   1    0   WINDOW (SORT)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'ODS$TERM_DEPOSITS_SOUR
          CE' (Cost=2 Card=1 Bytes=77)

   3    2       INDEX (RANGE SCAN) OF 'IDX_SOURCE_3101' (NON-UNIQUE) (
          Cost=4 Card=1)

SQL>

I was not able to collect the statistics a query was not coming out.
any suggestions will be highly appreciated.

Followup:
one would need the tkprof output.

I would guess you have very poor statistics.  the optimizer thinks this is a 1
row result set.  According to the plan, this would take about 0.0001 seconds.

If it is taking longer, your stats are way way out of date (which based on the
last comment -- I think to be true).

garbage in, garbage out they say.  If I withold vital information from you --
you would make poor decisions.  The same is very true of the CBO.

Rating: 4
Stats for query above May 29, 2003
Reviewer: A reader
Hi Tom,

     Here are the statistics for the query.

odstest@ODS> set autotrace traceonly statistics
odstest@ODS> set autotrace traceonly explain statistics
odstest@ODS> ed
Wrote file afiedt.buf

  1  select ods.account_id, ods.constitution, ods.branch_code,
ods.gl_sub_head_code,
  2                       ods.scheme_code, ods.value_date, ods.maturity_date,
ods.interest_rate,
  3                       ods.last_interest_posted_date,
ods.next_interest_outflow_date,
  4                       sum(deposit_amount) over (partition by
ods.constitution, ods.branch_code, ods.gl_sub_head_code,
  5                                                 ods.scheme_code,
ods.value_date, ods.maturity_date, ods.interest_rate,
  6
ods.last_interest_posted_date, ods.next_interest_outflow_date) deposit_amount,
  7                       sum(maturity_amount) over (partition by
ods.constitution, ods.branch_code, ods.gl_sub_head_code,
  8                                                  ods.scheme_code,
ods.value_date, ods.maturity_date, ods.interest_rate,
  9
ods.last_interest_posted_date, ods.next_interest_outflow_date) maturity_amount,
 10                       sum(balance) over (partition by ods.constitution,
ods.branch_code, ods.gl_sub_head_code,
 11                                          ods.scheme_code, ods.value_date,
ods.maturity_date, ods.interest_rate,
 12                                          ods.last_interest_posted_date,
ods.next_interest_outflow_date) balance
 13                       from ods$term_deposits_source ods
 14                       where business_date = '31-MAR-2003'
 15                       and   quantum_optima = 'Y'
 16                       and   constitution like 'R%'
 17 /

1134036 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=46542 Card=80776 Byt
          es=6219752)

   1    0   WINDOW (SORT)
   2    1     TABLE ACCESS (FULL) OF 'ODS$TERM_DEPOSITS_SOURCE' (Cost=
          41842 Card=80776 Bytes=6219752)

Statistics
----------------------------------------------------------
          9  recursive calls
      23689  db block gets
     689630  consistent gets
     799195  physical reads
          0  redo size
  123459086  bytes sent via SQL*Net to client
    4990044  bytes received via SQL*Net from client
      75604  SQL*Net roundtrips to/from client
          1  sorts (memory)
          1  sorts (disk)
    1134036  rows processed

odstest@ODS>

Followup:
tell you what, someday when I'm taking questions -- feel free to followup there
(not here)

Look at this plan vs the other one.  come on, you moved the goal posts like 500
miles here.

But, look -- you are sifting 1.1 million rows -- it would appear your stats are
still out of date (the cards are NOT CLOSE to reality).

You are comparing a date to a string. ugg.  dates to dates, strings to strings,
numbers to numbers -- only.

May 29, 2003 Raghu -- Thanks for the question regarding "Execution PLan for Views", version 8.1.7
You Asked
Hi Tom ,

I wanted to know whether the execution plan for view is also stored and reused
as with its definition.

basically i wanted to know is if i have many tables in my query joined together
, is it a good idea to split it into views and query on the view definition.

Is there any advantage if i use views instead of querys other then the fact that
the query for views is already parsed .

your inputs will be appreciated.
and we said...
plans for queries are saved in the shared pool.

A view is something you can use in a query.

Hence, plans for queries that use views will be saved and reused in the shared
pool.

the query for views is NOT already parsed.  when you run a query that references
a view, we look up the text of the view, stuff it into the query and then
parse/optimize that.

Views are for the ease of use for humans -- to save us from typing stuff
(imagine if ALL_OBJECTS did not exist and you had to type in that query each
time!)
May 29, 2003 Lise -- Thanks for the question regarding "XMLSchema and Database links question", version 9.2
You Asked
Hi Tom,
I would be so pleased if you could give me some answers to my questions, which
are:

1/ Our system creates XML documents representing letters, which are sent to our
document production system, as flat files.
The XML documents are also stored in the database for future reference. I have
decided to use CLOB since they will only be retrieved as whole documents.
The thing is I would like to apply some validation to the contents, so I thought
I could use XMLSchema in creating the table which holds the XML documents. The
question is do I need to create one table for each letter template (approx 25),
i.e. do I need to create one XMLSchema per XML document type?

2/ SAP also produces letters, which are to be sent directly to us. Can they be
passed as XML documents across a database link? Again storing them as CLOB?

3/ Do you know if I could make a RFC from Oracle to SAP?

Three questions!!
Many Thanks in advance.
and we said...
I asked Sean Dillon, our XML Technologist, to take a look at this and here's
what he had to say:
------------------

Hi Lise,

You should know that XML Schema design is NOT an exact science, there are good
ways to do it and bad ways to do it.  Ultimately, it's a DESIGN process and
should be approached just like any other design requirement, with an open mind
and a healthy dose of 'ok-lets-try-a-different-way-now'.  As you learn more
about XML Schemas, you'll settle into a groove with regard to how you create
your schemas and what works best for you.

Personally, I would strive to create ONE XML Schema for all 25 documents.  You
most likely have a number of elements and attributes that are the same across
documents, so if you can include all the elements (those that are repeated
across the 25 different document types and those that are unique to the 25
documents), that would most likely be the most attractive option.  There can
certainly be cases where this isn't the best approach, i.e. all the elements and
attributes are unique across documents and there isn't any duplication really...
In that case it might be better to create separate schemas.  I would urge you to
try different approaches.

Once you know what's best with regard to your XML Schema design, THEN apply it
to Oracle.  If you have 1 XML Schema, you're golden.  You can create an XMLType
column and specify the XML Schema that validates the XML document and you're
golden.  If you have 25 XML Schemas, you can do it one of two ways.  You can
either create 25 different tables (XMLType columns w/ XML Schema declarations),
or you can create a single table with am XMLType column and a reference to the
XML Schema that should be used for validation.  This way, you could store the
XML Schemas either in the database or on the web, and validate them
programatically.  This would alleviate the overhead of 25 separate tables.

As for passing CLOBs over database links, this works w/ some exceptions.  Look
at these urls:

asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:696224943687
asktom.oracle.com/pls/ask/f?p=4950:8:90835616617931864::NO::F4950_P8_DISPLAYID,F4
950_P8_CRITERIA:950029833940,

For RFC's, sure.  We have a few different ways we can do this.  We have External
Procedures (call-outs to C), we can use Java in the database so you could call
RMI, and if you look on the Web Services Center on OTN
(otn.oracle.com/tech/webservices/content.html), you'll find there is the ability
to call Web Services from PL/SQL.  Those are a few of the ways you might
accomplish that.

Hope that helps!
Review & Followup
Rating: 3
XML document validation based on XSD document May 29, 2003
Reviewer: Arvind K Sharma from Ann Arbor, MI, USA
Hi Tom,
I have a question about Xml validation based on XSD schema definition:

Both documents are stored in different tables in XMLType fields.
I have a trigger on table valid_xml_schemas that fires whenever I update the
VXS_SCHEMA field in table. I validate all the XML documents in the
subset_family_details table which are having foreign key value pointing to the
schema that I am updating in table valid_xml_schemas.
My problem is that if I take the :new.vxs_schema value and insert it into a
temporary table and use the inserted value to validate the XML documents then it
works and validates the documents for a valid schema but if instead of inserting
into temporary table, I validate the XML documents against :new.vxs_schema then
it fails the validation.

Here in this case you might have seen that schema is same with the difference of
only populating from database and direct in coming value. Is it changing the
value somewhere in between or we can not use any value other than database XSD
value to validate the XML document ?

Codes:
-- stores XSD documents
PROMPT Creating Table 'VALID_XML_SCHEMAS'
CREATE TABLE VALID_XML_SCHEMAS
 (VXS_ID NUMBER(4) NOT NULL
 ,VXS_NAME VARCHAR2(30) NOT NULL
 ,VXS_DESCRIPTION VARCHAR2(255)
 ,VXS_SCHEMA XMLTYPE NOT NULL
 ,VXS_PROG_ALIAS VARCHAR2(30) NOT NULL
 ,VXS_DISPLAY_SEQUENCE NUMBER(3) NOT NULL
 ,VXS_STATUS VARCHAR2(1) DEFAULT 'Y' NOT NULL
 ,VXS_STATUS_DATE DATE NOT NULL
 ,VXS_USER_CREATED VARCHAR2(30) NOT NULL
 ,VXS_DATE_CREATED DATE NOT NULL
 ,VXS_USER_MODIFIED VARCHAR2(30)
 ,VXS_DATE_MODIFIED DATE
 )
/
-- stores XML documents
PROMPT Creating Table 'SUBSET_FAMILY_DETAILS'
CREATE TABLE SUBSET_FAMILY_DETAILS
 (SFD_ID NUMBER(9) NOT NULL
 ,VSF_ID NUMBER(4) NOT NULL
 ,VXS_ID NUMBER(4) NOT NULL
 ,SFD_XML_DOC XMLTYPE NOT NULL
 ,SFD_USER_CREATED VARCHAR2(30) NOT NULL
 ,SFD_DATE_CREATED DATE NOT NULL
 ,SFD_USER_MODIFIED VARCHAR2(30)
 ,SFD_DATE_MODIFIED DATE
 )
/

CREATE TABLE TEST( XSD XMLTYPE);

Java code:

package oracle.util;

import oracle.xml.parser.schema.XSDBuilder;
import oracle.xml.parser.schema.XMLSchema;
import oracle.xml.parser.v2.DOMParser;
import oracle.xml.parser.v2.XMLParser;
import oracle.xml.parser.v2.XMLParseException;
import java.io.ByteArrayInputStream;
import java.io.StringWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.sql.SQLException;
import java.io.IOException;
import oracle.sql.CLOB;

public class SchemaUtil{
    public static String validation( CLOB xmlClob, CLOB xsdClob)throws
Exception{
        StringWriter sw = new StringWriter();
        try
        {
            //Build Schema Object
            String xml = getStringForClob(xmlClob);
            String xsd = getStringForClob(xsdClob);
            XSDBuilder builder = new XSDBuilder();
            byte [] docbytes = xsd.getBytes();
            ByteArrayInputStream in = new ByteArrayInputStream(docbytes);
            XMLSchema schemadoc = (XMLSchema)builder.build(in,null);

            //Parse the input XML document with Schema Validation
            docbytes = xml.getBytes();

            in = new ByteArrayInputStream(docbytes);
            DOMParser dp = new DOMParser();

            // Set Schema Object for Validation
            dp.setXMLSchema(schemadoc);
            dp.setValidationMode(XMLParser.SCHEMA_VALIDATION);

            dp.setPreserveWhitespace (true);

            dp.setErrorStream (new PrintWriter(sw));
            dp.parse (in);

            sw.write("WELLFORMED");
        }
        catch (XMLParseException pe)
        {
            sw.write("Parser Exception: " + pe.getMessage());
        }
        catch (Exception e)
        {
            sw.write("NonParserException: " + e.getMessage());
        }
        return sw.toString();
    }

    public static String getStringForClob(CLOB xmlObj) throws SQLException,
IOException{
          Reader clobStream = xmlObj.getCharacterStream ();
          StringBuffer suggestions = new StringBuffer();
        int nchars = 0;
        char[] buffer = new char[1000];
          while((nchars = clobStream.read(buffer)) != -1)
            suggestions.append(buffer, 0, nchars);
          clobStream.close();
          return suggestions.toString();
  }
}

Note: please create a Jar file for above class and load in database using( I
know you know this but still I am wrinting :) ):
loadjava -resolve -verbose -user user/pwd@connectstring filename.jar

CREATE OR REPLACE PROCEDURE TEST_P(XSD XMLTYPE) IS
PRAGMA autonomous_transaction;
BEGIN
     DELETE FROM TEST;
     INSERT INTO TEST VALUES(XSD);
     COMMIT;
END TEST_P;

CREATE OR REPLACE function schemavalidation(xml in clob, xsd in clob)
return varchar2
as language java name
   'oracle.util.SchemaUtil.validation(oracle.sql.CLOB,oracle.sql.CLOB) returns
java.lang.String';

CREATE OR REPLACE TRIGGER PQDTVXS
 BEFORE UPDATE OF VXS_SCHEMA
 ON VALID_XML_SCHEMAS
 FOR EACH ROW
DECLARE
XML_not_wellformed  Exception;
newline             varchar2(1) := chr(10);
lv_out             varchar2(32000);
lv_vxs_schemas        Clob;
lv_vxs_id            valid_xml_schemas.vxs_id%type;
lv_sfd_xml_doc        Clob;

Type tp_clob is table of clob index by binary_integer;
sfd_xml_doc_tab tp_clob;

Cursor cur_xml_doc(in_vxs_id in valid_xml_schemas.vxs_id%type) is
     select sfd.SFD_XML_DOC.getClobVal()
     from   subset_family_details sfd
     where  vxs_id = in_vxs_id;
BEGIN
      -- case1: works:
     TEST_P(:new.VXS_SCHEMA);  -- works
     SELECT X.XSD.getClobVal() INTO lv_vxs_schemas FROM TEST X;

     -- case2 does not work...
     --lv_vxs_schemas := :new.VXS_SCHEMA.getClobVal();

      open cur_xml_doc(:new.vxs_id);
     fetch cur_xml_doc bulk collect into sfd_xml_doc_tab;
     if (sfd_xml_doc_tab.count > 0) then
       for indx in sfd_xml_doc_tab.first..sfd_xml_doc_tab.last loop
          lv_sfd_xml_doc := sfd_xml_doc_tab(indx);
          lv_out := SchemaValidation(lv_sfd_xml_doc, lv_vxs_schemas);
         lv_out := replace(lv_out,newline,'');
          if ( substr(lv_out,1,1) != newline ) then
             if ( substr(lv_out,1,10) != 'WELLFORMED') THEN
                  raise XML_not_wellformed;
            end if;
          else
             raise XML_not_wellformed;
          end if;
       end loop;
     end if;
     EXCEPTION
         WHEN XML_not_wellformed then
             RAISE_APPLICATION_ERROR(-20010,'New Xsd document does not validate
existing XML documents...'
                                           ||'delete XML documents first and
then insert/update XSD again');
         WHEN OTHERS THEN
                RAISE;
END pqdtvxs;

Please let me know if you need any other information. Any help is appreciated.

Thanks,
Arvind Kapil Sharma

Followup:
sometime, when I'm taking questions -- feel free to ask there.....

it gets a little confusing when we start new unrelated questions in a question
of a question.

May 29, 2003 sean -- Thanks for the question regarding "Cutting the end off a string", version 8.1.7
You Asked
I'm trying to get an ascii file into a table. Everything is working fine except
that I can't think of a way to get some of the data into my tables columns.

This shows the ascii files format. I've added the first 2 lines to indicate the
column positions.

I need to be able to populate a column in my table with the characters starting
at position 36 upto but not including the /

i.e. I want my column to contain NNS, QIP_NNS etc. This will never be > 20char

The bit I'm interested in is always followed by /ORACLE.

I also want to populate another column with the string that sometimes follows
the /ORACLE part e.g. master_2d. This will never be > 8 char

         1         2         3         4         5         6
123456789012345678901234567890123456789012345678901234567890
  aadummy        05222001 10:01:25 NNS/ORACLE
  abdummy        05222001 14:45:09 QIP_NNS/ORACLE
  acdummy        06132001 09:36:56 NORTHERN_2000/ORACLE
  ahl291b        06121997 14:28:25 EO_CENTRAL_GRABEN/ORACLE
  akerq20        07052002 00:00:00 EO_NSRT/ORACLE
  alba99         10092002 11:15:00 EO_NSRT/ORACLE
  ang_test       05222003 11:37:08 LOADSEIS/ORACLE master_2d
  arbroath       04171996 16:47:40 EO_NSRT/ORACLE
  auk97          03231998 14:44:05 CENTRAL97/ORACLE
  auk98          05201998 08:00:00 CENTRAL97/ORACLE
  auktemp        11282002 12:19:08 CENTRAL97/ORACLE
  auktr43d       04022003 09:54:30 EO_W_OF_SHETLANDS/ORACLE

I've tried using all sorts of function combinations but I just can't think of a
way of doing it.

Cheers
and we said...
ops$tkyte@ORA920> create table t ( a varchar2(30), b date, c varchar2(20) );

Table created.

ops$tkyte@ORA920> !cat t.ctl
load data
INFILE *
INTO TABLE t
replace
FIELDS TERMINATED BY ','
(a position(3:17),
 b position(18:34) date "mmddyyyy hh24:mi:ss",
 c position(36:60) "substr(:c,1,instr(:c,'/')-1)"
)
begindata
  aadummy        05222001 10:01:25 NNS/ORACLE
  abdummy        05222001 14:45:09 QIP_NNS/ORACLE
  acdummy        06132001 09:36:56 NORTHERN_2000/ORACLE
  ahl291b        06121997 14:28:25 EO_CENTRAL_GRABEN/ORACLE
  akerq20        07052002 00:00:00 EO_NSRT/ORACLE
  alba99         10092002 11:15:00 EO_NSRT/ORACLE
  ang_test       05222003 11:37:08 LOADSEIS/ORACLE master_2d
  arbroath       04171996 16:47:40 EO_NSRT/ORACLE
  auk97          03231998 14:44:05 CENTRAL97/ORACLE
  auk98          05201998 08:00:00 CENTRAL97/ORACLE
  auktemp        11282002 12:19:08 CENTRAL97/ORACLE
  auktr43d       04022003 09:54:30 EO_W_OF_SHETLANDS/ORACLE

ops$tkyte@ORA920> !sqlldr / t

SQL*Loader: Release 9.2.0.3.0 - Production on Thu May 29 08:53:37 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Commit point reached - logical record count 12

ops$tkyte@ORA920> select * from t;

A                              B         C
------------------------------ --------- --------------------
aadummy                        22-MAY-01 NNS
abdummy                        22-MAY-01 QIP_NNS
acdummy                        13-JUN-01 NORTHERN_2000
ahl291b                        12-JUN-97 EO_CENTRAL_GRABEN
akerq20                        05-JUL-02 EO_NSRT
alba99                         09-OCT-02 EO_NSRT
ang_test                       22-MAY-03 LOADSEIS
arbroath                       17-APR-96 EO_NSRT
auk97                          23-MAR-98 CENTRAL97
auk98                          20-MAY-98 CENTRAL97
auktemp                        28-NOV-02 CENTRAL97
auktr43d                       02-APR-03 EO_W_OF_SHETLANDS

12 rows selected.
Review & Followup
Rating: 4
Did Sean want something else May 29, 2003
Reviewer: Peter from UK
Tom, I always seem to learn something here.

Didn't Sean also have a second part to the question where he is trying to split
the end of the text string into another column?

I guess that we would need an extra column(d) in table T and an extra d
position(36:60) for the righthand end of the string - can you use nvl in the
control function to allow for the many instance of NULL in sean's input file?

Followup:
doh! you are right, missed that.  sorry:

ops$tkyte@ORA817DEV> create table t ( a varchar2(30), b date, c varchar2(20), d
varchar2(20) );

Table created.

ops$tkyte@ORA817DEV> !cat t.ctl
load data
INFILE *
INTO TABLE t
replace
FIELDS TERMINATED BY ','
(a position(3:17),
 b position(18:34) date "mmddyyyy hh24:mi:ss",
 c position(36:60) "substr(:c,1,instr(:c,'/')-1)",
 d position(1:1)   "decode(instr(:c,' '),0,'',substr(:c,instr(:c,' ')+1))"
)
begindata
  aadummy        05222001 10:01:25 NNS/ORACLE
  abdummy        05222001 14:45:09 QIP_NNS/ORACLE
  acdummy        06132001 09:36:56 NORTHERN_2000/ORACLE
  ahl291b        06121997 14:28:25 EO_CENTRAL_GRABEN/ORACLE
  akerq20        07052002 00:00:00 EO_NSRT/ORACLE
  alba99         10092002 11:15:00 EO_NSRT/ORACLE
  ang_test       05222003 11:37:08 LOADSEIS/ORACLE master_2d
  arbroath       04171996 16:47:40 EO_NSRT/ORACLE
  auk97          03231998 14:44:05 CENTRAL97/ORACLE
  auk98          05201998 08:00:00 CENTRAL97/ORACLE
  auktemp        11282002 12:19:08 CENTRAL97/ORACLE
  auktr43d       04022003 09:54:30 EO_W_OF_SHETLANDS/ORACLE

ops$tkyte@ORA817DEV> !sqlldr / t

SQL*Loader: Release 8.1.7.4.0 - Production on Thu May 29 09:49:27 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Commit point reached - logical record count 12

ops$tkyte@ORA817DEV> select * from t;

A                              B           C                    D
------------------------------ ----------- --------------------
--------------------
aadummy                        22-MAY-2001 NNS
abdummy                        22-MAY-2001 QIP_NNS
acdummy                        13-JUN-2001 NORTHERN_2000
ahl291b                        12-JUN-1997 EO_CENTRAL_GRABEN
akerq20                        05-JUL-2002 EO_NSRT
alba99                         09-OCT-2002 EO_NSRT
ang_test                       22-MAY-2003 LOADSEIS             master_2d
arbroath                       17-APR-1996 EO_NSRT
auk97                          23-MAR-1998 CENTRAL97
auk98                          20-MAY-1998 CENTRAL97
auktemp                        28-NOV-2002 CENTRAL97
auktr43d                       02-APR-2003 EO_W_OF_SHETLANDS

12 rows selected.

fixed that up....

Rating: 5
Cutting the end off a string May 29, 2003
Reviewer: Sean Hoyland from UK
Thanks Tom

Very helpful. I'm actually bringing the data in via the  UTL_FILE package but
everything I needed to know is in your response and I learnt a lot to boot.

Get website!

Sean

Rating: 5
Oh that's neat May 29, 2003
Reviewer: Peter from UK
I didn't think about using c: twice -- I would have read the field again!

Guess that's why you have your job and I have mine ;-)

Rating: 4
two :c meanings May 29, 2003
Reviewer: A reader
Hi, TOm,

The first :c i know, but where does the second :c starts from, and position(1:1)
means only one character?

Please clarify,

Thanks

Followup:
the second :c is the first :c -- it is just a bind variable.

position (1:1) is just a placeholder -- I'm deriving the value of D from :c
entirely so it didn't matter if I used postition(1:1000) or position(1:1)

in sqlldr control files, you are just defining bind variables -- I have bind
variables :A, :B, :C, and :D

In the absence of a "sqlfunction" in the ctl file, oracle would have generated:

insert into t ( a,b, c, d ) values (:a,:b,:c,:d);

since I had sql functions in there it generated:

insert into t ( a,b,c,d ) values
(:a,
  to_date(:b,'mmddyyyy hh24:mi:ss'),
  substr(:c,1,instr(:c,'/')-1),
  decode(instr(:c,' '),0,'',substr(:c,instr(:c,' ')+1))
);

instead and bound the :A, :B ,and :C binds to it (ignoring :d, never used it)

Rating: 5
May 29, 2003
Reviewer: A reader
Cool!!!!!

Rating: 5
Excellent!!!!! May 29, 2003
Reviewer: gs
Great Answer Tom. Learned so much from your answer(s).

May 29, 2003 Robert -- Thanks for the question regarding "Trigger", version 8.1.7
You Asked
Hallo Tom

I have two row-triggers for INSERTS working on one table.
Is it possible to set the order these triggers will be fired ?

Greetings
Robert
and we said...
No, it is by definition "undefined".

you should write two procedures, p1 and p2 that have the logic you currently
have.  Then one trigger that calls p1 and p2.  That is the only way to get them
in a certain order.
Review & Followup
Rating: 4
Thanks June 26, 2002
Reviewer: Robert Hares from Luxemburg
It's not the best solution for me because one of these two triggers is generated
by a code generator but now I can stop searching for a possibility to set the
order of fireing.
I think I will take this trigger out of the generation list and put the two
triggers into one.

Thanks again.

Rating: 5
order May 29, 2003
Reviewer: mo
Tom:

I have 3 triggers on a table:
1 before-update
2 before-insert-or-update.

I want the "before-update" trigger to run first because it read two columns
values and delete the jobs scheduled for those values and then the other two
schedule two new jobs.

Everything is working fine.  However is it just a coincidence or it will happen
all the time?

Followup:
triggers are defined to have no defined order of firing.

I think you want two triggers.

1 before update
1 before insert

the before update will invoke the code in order for update processing.
the before insert will do likewise.

if the issue is "well they share lots of code" my response is and will be "that
is what packages are all about -- move it all there"

so your triggers should be nothing more then calls to a packaged procedure.

May 29, 2003 trinh -- Thanks for the question regarding "Hierarchical Query", version 817
You Asked
Tom,

The emp table:

scott@oradev> select * from emp;

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

14 rows selected.

Then the hieratchical:

scott@oradev> SELECT empno, ename, mgr
  2     FROM emp
  3     CONNECT BY PRIOR empno = mgr;

    EMPNO ENAME            MGR
--------- ---------- ---------
     7369 SMITH           7902
     7499 ALLEN           7698
     7521 WARD            7698
     7566 JONES           7839
     7788 SCOTT           7566
     7876 ADAMS           7788
     7902 FORD            7566
     7369 SMITH           7902
     7654 MARTIN          7698
     7698 BLAKE           7839
     7499 ALLEN           7698
     7521 WARD            7698
     7654 MARTIN          7698
     7844 TURNER          7698
     7900 JAMES           7698
     7782 CLARK           7839
     7934 MILLER          7782
     7788 SCOTT           7566
     7876 ADAMS           7788
     7839 KING
     7566 JONES           7839
     7788 SCOTT           7566
     7876 ADAMS           7788
     7902 FORD            7566
     7369 SMITH           7902
     7698 BLAKE           7839
     7499 ALLEN           7698
     7521 WARD            7698
     7654 MARTIN          7698
     7844 TURNER          7698
     7900 JAMES           7698
     7782 CLARK           7839
     7934 MILLER          7782
     7844 TURNER          7698
     7876 ADAMS           7788
     7900 JAMES           7698
     7902 FORD            7566
     7369 SMITH           7902
     7934 MILLER          7782

39 rows selected.

scott@oradev>

Why are there so many duplicated results?
and we said...
Because you asked for 14 trees -- we start with EACH record and explode the
hirearchy.

Maybe it makes more sense to look at it like this:

scott@ORA920.US.ORACLE.COM> SELECT
  2          decode( level, 1, 'top of tree =====>' ),
  3          empno, rpad( '*', 2*(level-1), '*' ) || ename ename, mgr
  4    FROM emp
  5  CONNECT BY PRIOR empno = mgr;

DECODE(LEVEL,1,'TO      EMPNO ENAME                       MGR
------------------ ---------- -------------------- ----------
top of tree =====>       7788 SCOTT                      7566
                         7876 **ADAMS                    7788
top of tree =====>       7902 FORD                       7566
                         7369 **SMITH                    7902
top of tree =====>       7499 ALLEN                      7698
top of tree =====>       7521 WARD                       7698
top of tree =====>       7654 MARTIN                     7698
top of tree =====>       7844 TURNER                     7698
top of tree =====>       7900 JAMES                      7698
top of tree =====>       7934 MILLER                     7782
top of tree =====>       7876 ADAMS                      7788
top of tree =====>       7566 JONES                      7839
                         7788 **SCOTT                    7566
                         7876 ****ADAMS                  7788
                         7902 **FORD                     7566
                         7369 ****SMITH                  7902
top of tree =====>       7698 BLAKE                      7839
                         7499 **ALLEN                    7698
                         7521 **WARD                     7698
                         7654 **MARTIN                   7698
                         7844 **TURNER                   7698
                         7900 **JAMES                    7698
top of tree =====>       7782 CLARK                      7839
                         7934 **MILLER                   7782
top of tree =====>       7369 SMITH                      7902
top of tree =====>       7839 KING
                         7566 **JONES                    7839
                         7788 ****SCOTT                  7566
                         7876 ******ADAMS                7788
                         7902 ****FORD                   7566
                         7369 ******SMITH                7902
                         7698 **BLAKE                    7839
                         7499 ****ALLEN                  7698
                         7521 ****WARD                   7698
                         7654 ****MARTIN                 7698
                         7844 ****TURNER                 7698
                         7900 ****JAMES                  7698
                         7782 **CLARK                    7839
                         7934 ****MILLER                 7782

39 rows selected.

scott@ORA920.US.ORACLE.COM>

If you want just the hierarchy starting from the top -- use this:

scott@ORA920.US.ORACLE.COM>
scott@ORA920.US.ORACLE.COM> SELECT
  2          decode( level, 1, 'top of tree =====>' ),
  3          empno, rpad( '*', 2*(level-1), '*' ) || ename ename, mgr
  4    FROM emp
  5   start with mgr is NULL
  6  CONNECT BY PRIOR empno = mgr;

DECODE(LEVEL,1,'TO      EMPNO ENAME                       MGR
------------------ ---------- -------------------- ----------
top of tree =====>       7839 KING
                         7566 **JONES                    7839
                         7788 ****SCOTT                  7566
                         7876 ******ADAMS                7788
                         7902 ****FORD                   7566
                         7369 ******SMITH                7902
                         7698 **BLAKE                    7839
                         7499 ****ALLEN                  7698
                         7521 ****WARD                   7698
                         7654 ****MARTIN                 7698
                         7844 ****TURNER                 7698
                         7900 ****JAMES                  7698
                         7782 **CLARK                    7839
                         7934 ****MILLER                 7782

14 rows selected.
Review & Followup
Rating: 5
Good Explanation July 22, 2002
Reviewer: Tom from Peoria, IL
As always

Rating: 4
May 29, 2003
Reviewer: A reader
scott@ORA920.US.ORACLE.COM>
scott@ORA920.US.ORACLE.COM> SELECT
  2          decode( level, 1, 'top of tree =====>' ),
  3          empno, rpad( '*', 2*(level-1), '*' ) || ename ename, mgr
  4    FROM emp
  5   start with mgr is NULL
  6  CONNECT BY PRIOR empno = mgr;

DECODE(LEVEL,1,'TO      EMPNO ENAME                       MGR
------------------ ---------- -------------------- ----------
top of tree =====>       7839 KING
                         7566 **JONES                    7839
                         7788 ****SCOTT                  7566
                         7876 ******ADAMS                7788
                         7902 ****FORD                   7566
                         7369 ******SMITH                7902
                         7698 **BLAKE                    7839
                         7499 ****ALLEN                  7698
                         7521 ****WARD                   7698
                         7654 ****MARTIN                 7698
                         7844 ****TURNER                 7698
                         7900 ****JAMES                  7698
                         7782 **CLARK                    7839
                         7934 ****MILLER                 7782

14 rows selected.

 /***************************************************/

tom this is cool..

 but I need to extand this question little further..

in our application /or any application
1.) if a change is made to the data
    specifically like this...

right now ( from above table)..

ADAMS has parent "scott" has parent "jones" has parent "king"  ..

if my user does some changes
and makes adams as "scott" parentkey I loose the
tree starting from Scott

Because it is circular Tree now...

Q: HOW CAN I PREVENT THIS ?

do we have any fn./proc. to check it
or do you have any proc. to do this easily to put it in trigger ? and generate
an exception ?

TIA

Followup:
you can do this with a trigger, but I don't like that implementation.  My
suggestion would be to use a procedure to parent records.  then we just need to
look "up" and "down" from that newly parented record.  eg:

ps$tkyte@ORA920> create or replace procedure set_parent( p_empno in number,
p_mgr in number )
  2  as
  3          l_cnt number;
  4  begin
  5          update emp
  6             set mgr = p_mgr
  7           where empno = p_empno;
  8
  9          select count(*) into l_cnt
 10            from emp
 11           start with empno = p_empno
 12     connect by prior empno = mgr;
 13
 14          select count(*) into l_cnt
 15            from emp
 16           start with empno = p_empno
 17     connect by prior mgr = empno;
 18  end;
 19  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec set_parent( 7788, 7876 );
BEGIN set_parent( 7788, 7876 ); END;

*
ERROR at line 1:
ORA-01436: CONNECT BY loop in user data
ORA-06512: at "OPS$TKYTE.SET_PARENT", line 9
ORA-06512: at line 1

ops$tkyte@ORA920>
ops$tkyte@ORA920> SELECT
  2    decode( level, 1, 'top of tree =====>' ) x,
  3    empno, rpad( '*', 2*(level-1), '*' ) || ename ename, mgr
  4    FROM emp
  5   start with mgr is NULL
  6   CONNECT BY PRIOR empno = mgr;

X                         EMPNO ENAME                  MGR
-------------------- ---------- --------------- ----------
top of tree =====>         7839 KING
                           7782 **CLARK               7839
                           7934 ****MILLER            7782
                           7698 **BLAKE               7839
                           7900 ****JAMES             7698
                           7521 ****WARD              7698
                           7654 ****MARTIN            7698
                           7844 ****TURNER            7698
                           7499 ****ALLEN             7698
                           7566 **JONES               7839
                           7788 ****SCOTT             7566
                           7876 ******ADAMS           7788
                           7902 ****FORD              7566
                           7369 ******SMITH           7902

14 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec set_parent( 7788, 7698 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> SELECT
  2    decode( level, 1, 'top of tree =====>' ) x,
  3    empno, rpad( '*', 2*(level-1), '*' ) || ename ename, mgr
  4    FROM emp
  5   start with mgr is NULL
  6   CONNECT BY PRIOR empno = mgr;

X                         EMPNO ENAME                  MGR
-------------------- ---------- --------------- ----------
top of tree =====>         7839 KING
                           7782 **CLARK               7839
                           7934 ****MILLER            7782
                           7698 **BLAKE               7839
                           7900 ****JAMES             7698
                           7521 ****WARD              7698
                           7654 ****MARTIN            7698
                           7844 ****TURNER            7698
                           7499 ****ALLEN             7698
                           7788 ****SCOTT             7698
                           7876 ******ADAMS           7788
                           7566 **JONES               7839
                           7902 ****FORD              7566
                           7369 ******SMITH           7902

14 rows selected.

Rating: 5
validation May 29, 2003
Reviewer: A reader
/*******************************************************/

you can do this with a trigger, but I don't like that implementation.  My
suggestion would be to use a procedure to parent records.  then we just need to
look "up" and "down" from that newly parented record.  eg:

/*******************************************************/

  Hi tom, Thanks for your prompt reply..
now I have only one question here,

could you please explain why would I check "DOWN"
why not just up ( leaf to root ?)

Followup:
yeah, you might be right -- i was thinking linear.  you would hit it going up as
well.

It was more the approach (don't update this column, use a procedure to
encapsulate this tricky update)....

Rating: 5
Thanks !! May 30, 2003
Reviewer: A reader

May 30, 2003 Dale -- Thanks for the question regarding "Daylight Savings Time", version 9.2.0
You Asked
We have customers with offices in different time zones around the country.  So,
we've decided that when a new user is created in our system (through our
application's UI), we'll ask the user for their time zone and we'll store that
with the user's other data.  The user's choices of time zones will be Atlantic,
Eastern,..., Pacific, etc.  There will be no indication of standard or daylight
savings time in the UI.  We’ll store the user’s time zone choice but also an
offset from UTC.  When the user logs in, a login trigger will read the user’s
chosen time zone and set the user's session's time zone.

Some concerns have come up and I cannot find definitive answers.

(1) Some parts of the country don't observe daylight savings time.  So, we could
have two users in the same time zone (say Central) but still one hour apart
because one is observing daylight savings time and the other is not.  Will we
have to handle all of this in our login trigger?  (We would have to add a
checkbox labeled "Automatically adjust clock for daylight savings changes" to
our UI.)
(2) When daylight savings time comes around, for customers that do observe it,
will our login trigger also have to calculate that change?  Or, if we set our
session time zone as an offset from UTC, will Oracle automatically get the
correct time?

Thanks very much for your help.  I enjoy your work and have learned a lot from
you over the years.
and we said...
1) you would have to ask for more then "eastern" or "pacific" then -- you need
to ask for timezones the way your OS does.  For exmaple, I'm "America/New York"
-- yes, I obey daylight savings, on the other hand, there is
America/Indiana/Indianapolis -- always on standard time.

We support both kinds -- maybe you just want a poplist populated from
v$timezone_names

2) if you just use the proper timezone from #1, it'll do it for you.

So, do NOT store an offset from UTC, store the timezone they want to use
Review & Followup
Rating: 4
How can you determine which time zones use daylight savings? March 03, 2003
Reviewer: Dale Johnson from Birmingham, AL
Your response clears up a great deal.

Using only V$TIMEZONE_NAMES, it does not seem possible to show the end user
which time zones do and do not observe daylight savings time.  I'd like to warn
any user that picks "America/Indiana/Indianapolis" that they've chosen a time
zone that does not use DST.  Where could I find such information so that I can
accurately show the user what Oracle will do with a particular time-zone
setting?

Thanks!

Followup:
You don't think the end user knows their time zone?

well, you could use a technique like:

ops$tkyte@ORA920> select timestamp'2003-04-06 01:00:00 America/New_York' +
interval '1' day,
  2         timestamp'2003-04-06 01:00:00 America/Indiana/Indianapolis' +
interval '1' day
  3    from dual
  4  /

TIMESTAMP'2003-04-0601:00:00AMERICA/NEW_YORK'+INTERVAL'1'DAY
---------------------------------------------------------------------------
TIMESTAMP'2003-04-0601:00:00AMERICA/INDIANA/INDIANAPOLIS'+INTERVAL'1'DAY
---------------------------------------------------------------------------
07-APR-03 02.00.00.000000000 AM AMERICA/NEW_YORK
07-APR-03 01.00.00.000000000 AM AMERICA/INDIANA/INDIANAPOLIS

see how america/new_york goes to 2am a day later -- but the other one goes to
1am a day later in indianapolis?  You should be able to use the database and a
known "daylight day" to figure it out.

Rating: 5
Thanks! March 03, 2003
Reviewer: Dale from Birmingham, AL
You don't know my users like I do! :)

Thanks very much.  Everything is perfectly clear now.

Rating: 5
Cannot find document about timestamp' May 29, 2003
Reviewer: Winston
This is very useful thread.

Tom, would you please share with us what the timestamp' is?
I know it's not a function because I couldn't find it on SQL reference.

And where we can find the complete list of valid timezone region?

And how can we take timezone into consideration in Oracle 8i?

Many thanks in advance

Followup:
if you are in 8i, timestamps, timezones, etc do not exist

they are 9i additions and documented in the 9i doc set.

Rating: 5
' operator May 30, 2003
Reviewer: Winston
I found it on SQL Reference.

Oracle 9i is really 'smarter'. it can accept both

  select timestamp'2003-04-06 01:00:00 America/New_York' from dual;
  select timestamp '2003-04-06 01:00:00 America/New_York' from dual;

I am confused with the 1st case, I thought ' is a operator

May 30, 2003 Munzer -- Thanks for the question regarding "oracle date", version 8.1.7
You Asked
Tom:

1.  A strange time stamp happens on the server when I do

select to_char(sysdate,'mm/dd/yyyy hh24/mm/ss') from dual;

WHen i keep doing this ig gives me time 17:04:05 and then I keep doing it again
and again

17:04:06
17:04:07
...

but then it goes back to 17:04:00 instead of going to 17:05:00.

Any ideas what is happening.

2.  when you insert or update in a sql statement and use sysdate does it use the
server time or the pc time.

3.  I work on an oracle instance that does lookup data from another instance.

Sometime every couple of weeks I get an error:

"maximum Number of processes 50 has been exceeded in 2nd instance"

do you know if this is from oracle or unix.

THank you,
and we said...
1) mm = MONTH, mi = MINUTES

After you smack your forehead, I will welcome you to the club.  I believe
virtually everyone has done that at one point or another!

2) server time

3) it is from oracle -- there is a processes init.ora parameter and you have
exceeded it.  You must have more then about 45 or so connections to the other
database (other sessions) and the cumulative number of processes on that system
exceeds what the DBA configured the maximum to be.
Review & Followup
Rating: 5
date format April 13, 2002
Reviewer: munz from Reston, USA
Toms:

A followup to answer #3,

1. Does oracle consider every "select statement" to the other instance a
process.

2. How  can I reset those every time I disconnect the browser or you really have
to disconnect from application server.

3. I mean is there a way as a developer to clean those processes up.

4. Do you recommend increasing this parameter, and what number would you
increase it to since you do not know how many times the user runs the form the
does the select statement.

Followup:
1) no, it is a session, a session typically (but not necessarily) has a process.

2) depends on your architecture I suppose.  For example, on asktom every page is
a session.  When you are not generating a page, you are not consuming a single
bit of resource on my machine (i couldn't survive on the size machine I have if
I maintained state).

I use a 100% stateless environment.  If you use an app server with a stated
environment, then yes, those dblinks will remain open for the duration of your
session.

You can use the alter session command to close them (see the docs for the syntax
and usage)

3) see #2

4) I cannot make such a wide ranging recommandation without intimate knowledge
of your environment, your needs, your system, your resources.  This is something
you and the DBA of the remote system will have to come to terms with.

Rating: 5
date format April 13, 2002
Reviewer: munz from Reston, USA
Tom:

Ok you are saying that the db links in a stateful environment will create too
many processes.

Is there a way for me to kill those processes during compilation of the code or
the session is not really know in advance.

I cannot use "Alter session" dynamically in the code itself.

2.  I am always confuse what a session is.  Is it a connection between a browser
and application server or connection between application server and database.
When a user logs in to the web system (stored procedure in database) is he
creating  so many sessions in a stateful environment. (one session/page in
stateless environemnt).

Thank you,

Followup:
No, that is NOT what I am saying.

"too many" -- no, "just enough to maintain the number of concurrent connections
you have requested to be open" -- yes.

You (at this point) do not know if

a) the dblinks are being held open
b) you will fix the problem by closing some of them
c) the other system is just under configured.

My advice is to sit back and figure out what is the best fix.  If you have ruled
out option b above from even existing, then you are left with A and C.  If A is
the case -- that implies C is true as well (the system is under configured for
the demands you are putting on it).  C may be true without A being true.

If A is true -- you can look up database link concentration in the network
guide, you may be able to use that to multi-plex all of your connections to the
remote database via one physical process.

2) from the database perspective a session is a row in V$session.  It is a
connected client. If that client is a "browser" -- the client is REALLY the app
server, not the browser.  The browser might have a session with the app server,
but the app server is the client of the database.

The life of that session -- the database session -- is wholly up to the people
who built the application running in the app server.

I cannot answer this question for you as I did not build your system.

Rating: 5
RE: I cannot answer this question for you as I did not build your system... April 13, 2002
Reviewer: Lester Burnham from Orlando, FL
>> I cannot answer this question for you as I did not build your system.

Are you sure?  Given the number of questions you have answered about this
system, I would say you have built at least half of it...

Rating: 5
date format May 29, 2003
Reviewer: mo
Tom:

In an html form I default a rquest date to today's date so that the user can
change it. When i insert that into the table it does not track time.  Hoe can i
take that date and time stamp to it?

Followup:
default the form to have dd-mon-yyyy hh24:mi:ss in the field insetad of just
dd-mon-yyyy.

YOU have to carry the time along if you want it.

Rating: 5
date May 29, 2003
Reviewer: mo
Tom:

I looked into that option. But can't you take the current time "13:05:22" and
add that to "01-jun-2003 00:00:00" to get that. do you have to convert the time
to a number here?

Followup:
huh?

what I mean is, when you create the value for the text item, that value should
be

01-jun-2003 13:05:22

the end user modifies that and when you save it you insert

to_date( :their_data, 'dd-mon-yyyy hh24:mi:ss' )

May 30, 2003 Sivadasan -- Thanks for the question regarding "UTL_FILE_DIR on multiple database instances", version 9i
You Asked
Tom:-The UTL_FILE worked fine with the utl_file_dir=e:\messages when there was
only one instance of the database (default logon is set correct). Now with
multiple database instances, the new databases return handling exceptions for
UTL_FILE, even though they are all set up the same way as the original instance.
I restarted all of the ORACLE instances from administrative tools/services. Do I
need to restart ORACLE ? how to do that with pfile option when there are
multiple database instances? Thanks.
and we said...
you need to bounce the instance after changing the init.ora OR pfile.

if you have 9iR2 -- you should use DIRECTORY objects instead of utl_file_dir,
easier, more secure and does not require a restart...
http://asktom.oracle.com/~tkyte/9iR2NewFeaturesForDevelopers/img28.html
Review
      & Followup

      Rating: 5
multiple
      directories  March 12, 2003
Reviewer:  Pushparaj
      Arulappan  from edison, nj
Tom,

We are in 9iR2 now and you have mentioned that we should
create directory for UTL_FILE_DIR instead of the init.ora
parameter.

My question is, we currently have multiple directories set
to the UTL_FILE_DIR parameter in the init.ora file.

utl_file_dir=/app/db1/logs
utl_file_dir=/app/db2/data

The syntax for creating a directory allows only one directory.

Can you please guide me how do I take care of this..

Thanks
Pushparaj

          Followup:

create directory directory1 as '/foo';
create directory directory2 as '/bar';

just create as many as you need.

      Rating: 1
UTL_FILE for
      multiple instances  March 12, 2003
Reviewer:
      Sivadasan A. Madhavan  from USA
Can't find any help on the use of DIRECTORY objects. The problem persists even
after carefully applying the init.ora changes, and instance bounces.

          Followup:

you are doing something wrong then.

I gave you a pointer to info on the directory objects -- even to the point of
having a sql script to demo it.

do a show parameter utl_file_dir in those instances.  you'll see.

      Rating: 3
UTL_FILE_DIR
      for multiple database instances  March 13, 2003

Reviewer:  Sivadasan A. Madhavan  from USA

Tom:- this worked for me. (Win2K and 9i2)
1. as sysdba, using EM/configuration, insert the directory path in initialized
parameters value for utl_file_dir
2. shut down instance in EM as sysoper
3. Win2k, add any local user/password in Log on/this account in Oracleservices
properties in computer management
4. start up instance in EM as sysoper
5. repeat for all instances
UTL_FILE will work in PLSQL

          Followup:

so it was a windoze security/permissions issue....  ok.  it makes sense that the
database would have to log in using a userid that allows read/write access to
the directories in question, doesn't it?

      Rating: 5
UTL_FILE_DIR ON
      WINDOWS  May 30, 2003
Reviewer:  umesh
       from bangalore india
when i  set utl_file_dir=c:\\ in init.ora i do not have any problems
but when i set utl_file_dir=c:\ i face problems saying that oracle  cannot
recognize the path set .. but on another server on windows it works fine

is that escape character really required or not .. i am confused as some of our
applications are running properly with a setting c:\
please clarify the same why is it so

thanks in advance
p.s: waiting eagerly  for your new book ( hope not getting delayed further)

          Followup:

suggest you would never ever set to c:\ personally.

I don't have any windows machines anywhere.  I cannot imagine setting it to c:\
however (there might - -just might be a file or two there that if they got
damaged things would be "bad")

May
      30, 2003 -- Thanks for the question
      regarding "Format the Number for display", version 816

      You Asked
Hello Guru,
Q1) I need to display numbers from a database in a specific format.
Table Tn (n number(6,3));

The format is 999.999

SQL> insert into tn values( 123.123) ;
1 row created.
SQL> insert into tn values(0) ;
1 row created.
SQL>insert into tn values(0.123)
1 row created.
So I do
SQL> select to_char(n,999.999) from tn ;

TO_CHAR(
--------
 123.123
    .000
    .123

I need the display output of value which is .000 to be 000.000 AND
.123 to be 000.123
as per the format mask.
No matter what mask i give zero is always missing from the left hand side of the
decimal.
Or atleast I should have the display as 0.000 and 0.123.
How to do this.
2)Can u send me the link for available format masks for numbers and dates in the
DOC Please.

Thanx

      and we said...
ops$tkyte@ORA817DEV.US.ORACLE.COM> select to_char(n,'000.000') from t;

TO_CHAR(
--------
 123.123
 000.000
 000.123

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a85397/sql_elem.htm#34512
right there in the "SQL Reference Manual" along with all of the functions and
such.
Review
      & Followup

      Rating: 5
So simple Yet
      so difficult  February 10, 2002
Reviewer:
      A reader
Thanx for your soln and the link.Sometimes we get puzzled even for the simple
problems and get overkilled trying to find complex answers to simple problems
where the soln is almost there, the horse is there at the lake and TOM makes him
drink the water.

      Rating: 4
Formating
      number in 8i  February 13, 2002
Reviewer:
      Sivababu  from GERMANY
Hello TOM,
    I need to have 6 digits after decimal place. But it is for some
calculations. I'm not able to get it using ROUND,TO_NUMBER and TO_CHAR.

For example
select    TO_CHAR(9999999.6765722378,'999999999D9999999999')   from dual;
   O/P
      9999999.6765722378
  It is fine. When I will make it to number..
select   TO_NUMBER( TO_CHAR(9999999.6765722378,'999999999D9999999999'))   from
dual;
    O/P
    9999999.676

Why it is comming only three decimal places. But i need 6 values after decimal.
How can i achive this?.
 I'm taking this value to insert into other database (Pervasive 2000).

Thanks and expecting ur reply.

with regards,
sivababu

          Followup:

SQLPlus has a default number format is uses to display data on the screen
"nicely".  You need to either

o set the numformat to something you like
o use a TO_CHAR on the number with a format that applies in your case.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select   TO_NUMBER(
TO_CHAR(9999999.6765722378,'999999999D9999999999'))   from
  2  dual;

TO_NUMBER(TO_CHAR(9999999.6765722378,'999999999D9999999999'))
-------------------------------------------------------------
                                                   9999999.68

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set numformat 9999999999999.9999999999999
ops$tkyte@ORA817DEV.US.ORACLE.COM> /

TO_NUMBER(TO_CHAR(9999999.6765722378,'999999999D9999999999'))
-------------------------------------------------------------
                                        9999999.6765722378000

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

See the difference?  I would recommand using a TO_CHAR with an explicit format
when selecting the data out or using numformat.

      Rating:
      5
to_char  October 16, 2002
Reviewer:
      mo
Tom:

Do you see anything wrong with this?

  IF ( strfmt.strip_money(i_unit_price(i)) !=
to_char(i_old_unit_price(i),'999,999.99')  ) THEN

PLS-00307  too many declarations of to_char match this call.

is it because of the array?

Thank you

          Followup:

what does i_old_unit_price's datatype.

      Rating: 3
dynamically
      defining scale via cast  October 16, 2002

Reviewer:  Jonas Edgeworth  from Berkeley, USA

All the examples you've shown provide fixed scale conversions to char. Im
curious how I can dynamically
specify the scale of a number (not converted to char)returned in a query. For
example, Im trying to do something similar to the following:

create table t1 (
  pk   number primary key,
  mycost number,
  precision number(2)
);

insert into t1 (pk, mycost, precision) values (1, 102.1234, 2);
insert into t1 (pk, mycost, precision) values (2, 102.1235, 3);

select cast ( t.cost
              AS NUMBER(30, precision))
 from ( select t.cost, t.precision from t);

Which is throwing a ORA-01728 scale out of range exception.

Any thoughts?

          Followup:

ops$tkyte@ORA920.LOCALHOST> select t.mycost, round(t.mycost,precision)
  2    from t1 t;

    MYCOST ROUND(T.MYCOST,PRECISION)
---------- -------------------------
  102.1234                    102.12
  102.1235                   102.124

cast does not work like that, cannot look work like that.

the TYPE of a column must be the same for all rows -- you would be trying to
change the datatype row by row.  that'll never fly.

      Rating:
      5
to_Char  October 16, 2002
Reviewer:
      mo
TOm:

i_old_unit_price is an IN parameter VARCHAR2.

What I am trying to do is compare a number entered via html form to another
number retrieved from table, however because the database stores 99 without
decimal, I am getting a difference if I compare
99 to 99.00 even though they should be the same.

so I want to format 99 to 99.00 and compare those.

Thank you,

          Followup:

no it is not, it is at LEAST and array:

to_char(i_old_unit_price(i),'999,999.99')  ) THEN

but anyway, I'll assume it is an array of varchar2's then -- to_char(
char_string ) isn't sensible.  is that char_string a DATE or a NUMBER?

to_char( to_number( char_string ), '999,999' )

now, that might make sense

      Rating: 2
How about
      negative number in brackets  May 30, 2003

Reviewer:  Charles Leung  from Hongkong
from SQL reference manual, I know that I can display the number in angluar
bracket "<>" using "PR"

SQL> select to_char(-12434,'99,999PR') as neg_num from dual
SQL> /

NEG_NUM
--------
<12,434>

SQL>

How can I display a number in "normal" bracket "(", i.e.

NEG_NUM
--------
(12,434)

          Followup:

translate( to_char(...), '<>','()' )

is one way.

May 30, 2003 Logan -- Thanks for the question regarding
      "Difference between Sort Merge and Hash Joins", version 8.1.6

      You Asked
Tom,

What is the difference between "Sort Merge" and "Hash" Joins. Don't they both do
a one FULL scan each on the joining tables and join them?

I know Sort Merge is used in the case of "ALL ROWS" and Nested Loops in the case
of "FIRST ROWS" hints. How about Has Join? When is it used?

Would really appreciate if you could explain it with a couple of examples.

Thanks in advance.

      and we said...
Well, a sort merge of A and B is sort of like this:

   read A and sort by join key to temp_a
   read B and sort by join key to temp_b

   read a record from temp_a
   read a record from temp_b
   while NOT eof on temp_a and temp_b
   loop
        if ( temp_a.key = temp_b.key ) then output joined record
        elsif ( temp_a.key <= temp_b.key ) read a record from temp_a
        elsif ( temp_a.key >= temp_b.key ) read a record from temp_b )
   end loop

(its more complex then that, the above logic assumed the join key was unique --
we really need to join every match in temp_a to every match in temp_b but you
get the picture)

The hash join is conceptually like:

   create a hash table on one of A or B (say A) on the join key creating temp_a.

   while NOT eof on B
      read a record in b
      hash the join key and look up into temp_a by that hash key for matching
      records
      output the matches
   end loop

So, a hash join can sometimes be much more efficient (one hash, not two sorts)

Hash joins are used any time a sort merge might be used in most cases.  If you
don't see hash joins going on, perhaps you have hash_join_enabled turned off...

Review &
      Followup

      Rating: 5
Re: Hash and
      Sort Merge joins  March 19, 2002
Reviewer:
      Logan Palanisamy  from Santa Clara, CA 95051
Very good.

      Rating: 4
are yu
      saying...  March 19, 2002
Reviewer:  Nag

That

1.Hash join is more beneficial in most cases than sort merge join. The way you
have expressed your opinion it appears that we should choose hash join over sort
merge join.

2.When should we use a sort merge join and hash join.(i.e. when we are using
cbo, WHERE IN  we can force tables to do a particular kind of join.

3.Which versions introduced both these joins.
Thanks

          Followup:

1) you should choose nothing, the optimizer should be making this choice for
you.

2) when the optimizer says so.

3) sort merge has been around forever.  hash joins were 7.2 or 7.3...

      Rating: 5
What is the
      hash function used?  March 20, 2002
Reviewer:
      Logan Palanisamy  from Santa Clara, CA USA
What is the Hash function that is normally used?

Also, I would assume it is the bigger of the two tables involved in the join
that is hashed. Am I correct?

          Followup:

It is an internal hash function, not documented.

It would seem more logical to me to hash the SMALLER table (less temp stuff to
store, might fit into memory and all)....

From the Designing/tuning for performance guide:

Hash Join

Oracle can only perform a hash join for an equijoin. Hash join is not available
with the RBO. You must enable hash join optimization, using the initialization
parameter HASH_JOIN_ENABLED (which can be set with the ALTER SESSION statement)
or
the USE_HASH hint.

To perform a hash join, Oracle performs the following steps:
1. Oracle performs a full table scan on each of the tables and splits each into
as many partitions as possible based on the available memory.

2. Oracle builds a hash table from one of the partitions (if possible, Oracle
selects a partition that fits into available memory). Oracle then uses the
corresponding partition in the other table to probe the hash table. All
partition pairs that do not fit into memory are placed onto disk.

3. For each pair of partitions (one from each table), Oracle uses the smaller
one to build a hash table and the larger one to probe the hash table.

      Rating: 2
Monitoring
      means  May 17, 2002
Reviewer:  john
       from ukraine
Tom,

How to monitor that the hash table couldn't fit
into hash_area_size?

          Followup:

You can use the stat physical writes to see this.  Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> /*
DOC>drop table t;
DOC>
DOC>create table t as select * from all_objects;
DOC>*/

ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and a.name = 'physical writes'
  5  /

NAME                                VALUE
------------------------------ ----------
physical writes                         0

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set hash_area_size = 2048000;
Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly
ops$tkyte@ORA817DEV.US.ORACLE.COM> select       * from t t1, t t2 where
t1.object_id = t2.object_id;

22962 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=129 Card=22962 Bytes=4041312)
   1    0   HASH JOIN (Cost=129 Card=22962 Bytes=4041312)
   2    1     TABLE ACCESS (FULL) OF 'T' (Cost=10 Card=22962 Bytes=2020656)
   3    1     TABLE ACCESS (FULL) OF 'T' (Cost=10 Card=22962 Bytes=2020656)

Statistics
----------------------------------------------------------
          0  recursive calls
         24  db block gets
        828  consistent gets
        714  physical reads
          0  redo size
    4733537  bytes sent via SQL*Net to client
     170255  bytes received via SQL*Net from client
       1532  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      22962  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and a.name = 'physical writes'
  5  /

NAME                                VALUE
------------------------------ ----------
physical writes                       714

that shows it took 714 physical writes to do this query -- they were due to
the 2m hash area size.  If we make that 20m instead:

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set hash_area_size = 20480000;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly
ops$tkyte@ORA817DEV.US.ORACLE.COM> select       * from t t1, t t2 where
t1.object_id = t2.object_id;

22962 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=43 Card=22962 Bytes=4041312)
   1    0   HASH JOIN (Cost=43 Card=22962 Bytes=4041312)
   2    1     TABLE ACCESS (FULL) OF 'T' (Cost=10 Card=22962 Bytes=2020656)
   3    1     TABLE ACCESS (FULL) OF 'T' (Cost=10 Card=22962 Bytes=2020656)

Statistics
----------------------------------------------------------
          0  recursive calls
         24  db block gets
       2149  consistent gets
          0  physical reads
          0  redo size
    4536381  bytes sent via SQL*Net to client
     170255  bytes received via SQL*Net from client
       1532  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      22962  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and a.name = 'physical writes'
  5  /

NAME                                VALUE
------------------------------ ----------
physical writes                       714

it didn't add any physical writes, no "swapping" if you will.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set hash_area_size = 2048000;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly
ops$tkyte@ORA817DEV.US.ORACLE.COM> select       * from t t1, t t2 where
t1.object_id = t2.object_id;

22962 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=129 Card=22962 Bytes=4041312)
   1    0   HASH JOIN (Cost=129 Card=22962 Bytes=4041312)
   2    1     TABLE ACCESS (FULL) OF 'T' (Cost=10 Card=22962 Bytes=2020656)
   3    1     TABLE ACCESS (FULL) OF 'T' (Cost=10 Card=22962 Bytes=2020656)

Statistics
----------------------------------------------------------
          0  recursive calls
         24  db block gets
        828  consistent gets
        714  physical reads
          0  redo size
    4733537  bytes sent via SQL*Net to client
     170255  bytes received via SQL*Net from client
       1532  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      22962  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and a.name = 'physical writes'
  5  /

NAME                                VALUE
------------------------------ ----------
physical writes                      1428

ops$tkyte@ORA817DEV.US.ORACLE.COM>
taking it back down to 2m, we see 714 writes once again...

      Rating: 5
Very
      Good  May 18, 2002
Reviewer:  kiro
       from Bulgaria

      Rating: 5
monitoring in
      parallel & which table was choosen as hash ...  August
      07, 2002
Reviewer:  Nemec  from Austria
Hi Tom,

a) could be the above monitoring (swapping of hash area and other resource
consumption) also be done in case of running the hash join in parallel?

b) can I see in a explain plan which of the two tables was choosen to be hashed
(is it the first table?)

c) In the "Oracle9i Database Performance Guide and Reference
Release 1 (9.0.1)"
is a sentence for OUTER hash join I don't understand:
The outer table (whose rows are being preserved) is used to build the hash
table, and the inner table is used to probe the hash table.

I would choose the opposite way (all rows from the outer table must be
processed, independent of matching or not matching the inner table). Can you
explain?

Thanks

Nemec

      Rating: 3
What if the
      query is more complicated?  January 22, 2003

Reviewer:  Matt  from Australia
What if the query is more complicated and required some sorts as well as HASH
JOINS?

Is this just a matter of identifying what space was used in the TEMPORORARY
tablespace for the sorts (using v$sort_usage and v$sort_segment etc) and then
subtracting this from the Physical Reads as you have described?

Are there any other ways that physical reads can occur when carrying out a
SELECT?

Is there a nice neat query for some general SELECT that might be able to display
how the physical reads are distributed amongst the various operations carried
out during the generation of the row sets?

Thanks and Regards

          Followup:

In 9iR2 (9.2.0.2) yes -- tkprof does that

big_table@ORA920> set autotrace traceonly
big_table@ORA920> alter session set sql_trace=true;

Session altered.

big_table@ORA920> select a.object_name, b.object_type from big_table a,
big_table b where a.object_id = b.object_id
  2  and a.object_name like '%Z%';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=13710 Card=91696
Bytes=3209360)
   1    0   MERGE JOIN (Cost=13710 Card=91696 Bytes=3209360)
   2    1     SORT (JOIN) (Cost=2552 Card=91693 Bytes=2017246)
   3    2       TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=2136 Card=91693
Bytes=2017246)
   4    1     SORT (JOIN) (Cost=11159 Card=1833857 Bytes=23840141)
   5    4       TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=2136 Card=1833857
Bytes=23840141)

select a.object_name, b.object_type from big_table a, big_table b where
a.object_id = b.object_id
and a.object_name like '%Z%'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.02          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch    21298     10.32      11.54      13526      35232          0      319452
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    21300     10.35      11.56      13526      35232          0      319452

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 64

Rows     Row Source Operation
-------  ---------------------------------------------------
 319452  HASH JOIN  (cr=35232 r=13526 w=0 time=10359215 us)
   9338   TABLE ACCESS FULL BIG_TABLE (cr=13021 r=13011 w=0 time=3035938 us)
1000000   TABLE ACCESS FULL BIG_TABLE (cr=22211 r=515 w=0 time=2070694 us)

cr=consistent reads
r =physical reads
w =writes

      Rating: 2
Any workaround
      for 9.0.1.4.0?  January 22, 2003
Reviewer:
      Matt  from Australia
Looks good. Is there some way of doing something like this in 9.0.1.4.0?

I can see JOIN operations in v$sql_plan but have no idea how any related info
might be pulled out of here.

In your execution plan I see a Bytes value:

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=13710 Card=91696
Bytes=3209360)
   1    0   MERGE JOIN (Cost=13710 Card=91696 Bytes=3209360)
   2    1     SORT (JOIN) (Cost=2552 Card=91693 Bytes=2017246)
   3    2       TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=2136 Card=91693
Bytes=2017246)
   4    1     SORT (JOIN) (Cost=11159 Card=1833857 Bytes=23840141)
   5    4       TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=2136 Card=1833857
Bytes=23840141)

What does this (Bytes Value) indicate?

Also in your HASH JOIN operation I see "time=10359215 us":

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 64

Rows     Row Source Operation
-------  ---------------------------------------------------
 319452  HASH JOIN  (cr=35232 r=13526 w=0 time=10359215 us)
   9338   TABLE ACCESS FULL BIG_TABLE (cr=13021 r=13011 w=0 time=3035938 us)
1000000   TABLE ACCESS FULL BIG_TABLE (cr=22211 r=515 w=0 time=2070694 us)

cr=consistent reads
r =physical reads
w =writes

What does this ("us") represent?

          Followup:

bytes = estimated number of bytes flowing out of that step of the execution
plan.  Consider (timestamp = varchar2(19), secondary = varchar2(1))

big_table@ORA920> select timestamp from big_table;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1253 Card=1001693
Bytes=19032167)
   1    0   TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1253 Card=1001693
Bytes=19032167)

big_table@ORA920> select secondary from big_table;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1253 Card=1001693
Bytes=1001693)
   1    0   TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1253 Card=1001693
Bytes=1001693)

as for the tkprof thingy -- 9.2.0.2 -- new feature.  Not even in 9.2.0.1.

us - microsecond - 1,000,000th of a second

      Rating: 5
What are
      Anti-Join and Semi-Joins?  March 17, 2003

Reviewer:  Logan Palanisamy  from Santa Clara CA USA

Tom,

I understand equi-joins, outer join (full, left and right), inner-join, natrual
join, hash join, sort-merge join, etc.

What are anti-join and semi-joins? They are being referenced in 9iR2 (HASH_AJ,
HASH_SJ, MERGE_AJ, MERGE_SJ), but I couldn't find anywhere about what they
basically are? Can you give some examples?

          Followup:

This sounds interesting - an "ANTI JOIN".  What could this be?  An ANTI join is
used to return rows from a table that are not present, not in some other row
source.  For example:

Select * from dept where deptno NOT IN ( select deptno from emp )

Might use an ANTI Join between DEPT and EMP and return only those rows in DEPT
that didn't join to anything in EMP.  Another way to write that query would be:

Select dept.*
  From dept, emp
 Where dept.deptno = emp.deptno(+)
   And emp.rowid is null;

There we outer join DEPT to EMP - and only keep the rows where the EMP row did
not exist. Another way you might find this query written could be:

Select *
   From dept
  Where NOT EXISTS
     ( select null from emp where emp.deptno = dept.deptno );

or

select *
  from dept
 where deptno NOT IN ( select deptno from emp );

An ANTI Join is generally the most efficient method of processing queries of
this nature.

Semi-Joins are joins like:

select * from dept where deptno in ( select deptno from emp );

or

select * from dept where exists ( select null from emp where dept.deptno =
dept.deptno )

we cannot turn either of the in or exists into a straight JOIN -- that would
return too many rows from DEPT (dept would be multiplied by EMP).  Hence, we do
what is known as a semi join.  We join DEPT to EMP but just stop after the first
hit on EMP for any DEPT record.  It is not a full join, just a partial or semi
join.

      Rating: 4
Optimizer
      choice – Hash vs. Nested loops.  May 08, 2003

Reviewer:  Hirisave Pratap  from Washington, DC

In order to analyze a performance problem, I recreated production database on
another machine and used the same initialization parameters. When the query was
run in the test bed, the optimizer choose nested loop where as the production
database uses hash cluster join.

To be sure, it was verified that the number of records in the underlying tables,
indexes, view definitions, etc. are identical in both databases. Also the name
and value from v$parameter in both databases matched. The hash join is enabled
in both database, SGAs are of same size, etc. (trust me :-)). The disk cluster
sizes (RAID5) are different for the two databases.

What is the logic behind the choices made (hash join vs. nested loop) by the two
optimizers?

#2. In analyzing further, it was observed that the select count(*) from one of
the underlying tables resulted INDEX (FAST FULL SCAN) in one database and INDEX
(RANGE SCAN) in the other database.

What could account for this difference?

Thanks in advance and appreciate your input!

Pratap

          Followup:

search this site for 10053

run that sort of trace (you only need to explain plan the query after setting
that event)

then, compare the trace files and see what is really different -- especially
some init.ora parameters.

things like cluster factor, stale stats, different settings will do this.  The
software, given the same inputs, will come to the same conclusion.  So, inputs
must not be the same (or the software isn't the same)

      Rating: 2
Few questions
      regarding Tuning  May 27, 2003
Reviewer:  Vikas
       from InDIA
Q.1 Under Scott schema, I executed a query :

Select * from emp,dept where emp.deptno = dept.deptno in which the result
expected is 14 rows. With such a small subset of rows returned it is preferred
to have a NESTED LOOP join access method instead of HASH JOIN which is preffered
in case of data set after join is more than 10,000 rows (in Server tuning
documentation).

Tables are analysed using DBMS_STATS.gather_schema_stats

Elapsed: 00:00:00.02

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=19 Bytes=1083)
   1    0   HASH JOIN (Cost=5 Card=19 Bytes=1083)
   2    1     TABLE ACCESS (FULL) OF 'DEPT' (Cost=2 Card=4 Bytes=80)
   3    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=19 Bytes=703)

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

where as

Select empno,ename,mgr,sal,comm,emp.deptno from emp,dept where emp.deptno =
dept.deptno; results into the explain plan

Elapsed: 00:00:00.02

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=19 Bytes=494)
   1    0   NESTED LOOPS (Cost=2 Card=19 Bytes=494)
   2    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=19 Bytes=437)
   3    1     INDEX (UNIQUE SCAN) OF 'PK_DEPT' (UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          8  consistent gets
          0  physical reads
          0  redo size
       1291  bytes sent via SQL*Net to client
        510  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         14  rows processed

which is using NESTED LOOPS. It is also observed that as soon as a column name
from the Second table is asked for retreival the HASH JOIN takes over from
NESTED LOOPS.

Select empno,ename,mgr,sal,comm,emp.deptno,dname from emp,dept where emp.deptno
= dept.deptno;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=19 Bytes=684)
   1    0   HASH JOIN (Cost=5 Card=19 Bytes=684)
   2    1     TABLE ACCESS (FULL) OF 'DEPT' (Cost=2 Card=4 Bytes=52)
   3    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=19 Bytes=437)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          8  consistent gets
          0  physical reads
          0  redo size
       1482  bytes sent via SQL*Net to client
        510  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         14  rows processed

Can you explain it why it is not concentrating on the join set for the no. of
rows retrieved? On what conditions HASH JOIN superceeds NESTED LOOPS join
access.

Q.2 From the Oracle documentation <Quote>

Cardinality represents the number of rows in a row set. Here, the row set can be
a base table, a view, or the result of a join or GROUP BY operator. <Quote>

Should it not be the distinct values of the row set and not just the number of
rows. Moreover can you please explain us the concept of Effective
cardinality,Join Cardinality using the example of EMP and DEPT tables, using
some where condition so that the Selectivity comes into picture.

Thanks in anticipation.
Vikas

          Followup:

hash join supercedes nested loops whenever the CBO decides to.  There is no cut
and dry formula here (well, there is but it is huge). There are dozens of
variables that affect this (init.ora settings).

As soon as you went for a plan that had to hit all of the table blocks in both
tables, nested loops becomes much less "appealing".  For example:

ops$tkyte@ORA920> create table emp as select * from scott.emp;

Table created.

ops$tkyte@ORA920> alter table emp add constraint emp_pk primary key(empno);

Table altered.

ops$tkyte@ORA920> create table dept as select * from scott.dept;

Table created.

ops$tkyte@ORA920> alter table dept add constraint dept_pk primary key(deptno);

Table altered.

ops$tkyte@ORA920> analyze table emp compute statistics;

Table analyzed.

ops$tkyte@ORA920> analyze table dept compute statistics;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> show parameter optimizer_index

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
optimizer_index_caching              integer     0
optimizer_index_cost_adj             integer     100
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from emp, dept where emp.deptno = dept.deptno;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=14 Bytes=700)
   1    0   HASH JOIN (Cost=5 Card=14 Bytes=700)
   2    1     TABLE ACCESS (FULL) OF 'DEPT' (Cost=2 Card=4 Bytes=72)
   3    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=14 Bytes=448)

ops$tkyte@ORA920> alter session set optimizer_index_cost_adj = 10;

Session altered.

ops$tkyte@ORA920> alter session set optimizer_index_caching  = 90;

Session altered.

ops$tkyte@ORA920> select * from emp, dept where emp.deptno = dept.deptno;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=14 Bytes=700)
   1    0   NESTED LOOPS (Cost=4 Card=14 Bytes=700)
   2    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=14 Bytes=448)
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'DEPT' (Cost=2 Card=1 Bytes=18)
   4    3       INDEX (UNIQUE SCAN) OF 'DEPT_PK' (UNIQUE)

ops$tkyte@ORA920> set autotrace off

q2) no, it should not be the number of distinct values.  It is the number of
rows -- period.

if I "select 1 from all_objects" should the cardinality of that be:

a) 1
b) 32,000 (on my system all_objects view has about 32k rows)

the only sensible answer is b)

      Rating:
      3
Cardinality  May 28, 2003
Reviewer:
      Vikas  from INDIA
Hi Tom,

Agreed. If the row set as mentioned by you gives you 32K rows then the base
cardinality for that row set has to be 32K & not the distinct values which is 1
in that case.

But if we have a column say "sex" and we have values M or F stored and the tbale
has 32K of rows then what is the base cardinality of that column? I think it is
2 and not 32K because this is the only way to identify the low cardinality
columns for creating the Bitmap indexes.

Please do correct me if I am wrong somewhere. Also please do mention the meaning
of :

Select emp.* from emp,dept where emp.deptno = dept.deptno;

1. Join Cardinality and how to calculate it? and how does optimizer know at the
compile time that what is the selectivity of that Join as the join cardinality
is the

Product of two cardinalities * Selectivity of the Join condition.

Thanks
Vikas
Please do illustriate it with a example demonstrating two tables.

          Followup:

no, the number of distinct values is 2.

the cardinality -- as defined by the definition of cardinality -- is 32k

search this site for 10053 to see some examples of how to get low level info
such as you appear to want to see from the optimizer (however, I won't really
help you parse a 10053 trace file too deeply, it is generally "more then we need
to know" and very version specific.  mostly useful for development to fix an
issue)

      Rating: 4
Nested Loop VS
      Hash Join  May 28, 2003
Reviewer:  vinodhps
       from India, Chennai
Hi Tom ,
As u said CBO choose the join depend on some of the parameter in the init.ora.

question

1) when , i execute query in test database(9.0.2.0) it comes very fast with in a
minute. where as in production database(8.0.4.0) it takes more than 4 hrs or
more.. why like that and plans are totally differnt.

2) like this some query take more time that i could not able to trace them even,
it take hrs so i aborptly terminate the process, is there any way to
trace(10046,10053) these kind of queries.(eg.some thing like explain plan set
statement_id for query), if i terminate the process iam getting only empty trace
files.. please suggest easy method to trace these queries.

3)Regarding you answer for Cardinality some times its showing total no of row
for the subset but some time i return distinct value.In the below plan it shows
card=3 where as the query returns 1294 rows, then what is that 3  can u please
explain me tom.

Thanks in advance and your Response are really great.

oracle version 8.0.4

Query Plan
---------------------------------------------------------------------------------
----------------
SELECT STATEMENT    [CHOOSE] Cost=4246 Rows=3 Bytes=496
  SORT UNIQUE
    UNION-ALL
      FILTER
        NESTED LOOPS
          NESTED LOOPS
            NESTED LOOPS
              HASH JOIN
                NESTED LOOPS
                  TABLE ACCESS BY INDEX ROWID T_ORD_PRD_DTLS  [ANALYZED]
                    INDEX RANGE SCAN PRD_IDX  [ANALYZED]
                  TABLE ACCESS BY INDEX ROWID T_ORD_ORDER_HDR  [ANALYZED]
                    INDEX UNIQUE SCAN CS_ORD_HDR_PK  [ANALYZED]
                VIEW
                  FILTER
                    SORT GROUP BY
                      HASH JOIN OUTER
                        FILTER
                          HASH JOIN OUTER
                            TABLE ACCESS FULL T_MKS_CONS_MST  [ANALYZED]
                            TABLE ACCESS FULL T_MKS_CONS_CRED_LMT_MST
[ANALYZED]
                        TABLE ACCESS FULL T_FAS_SALE_AWA  [ANALYZED]
              TABLE ACCESS BY INDEX ROWID T_MKS_CONS_MST  [ANALYZED]
                INDEX UNIQUE SCAN CS_CONS_MST_PK  [ANALYZED]
            TABLE ACCESS BY INDEX ROWID T_PIS_CITY_MST  [ANALYZED]
              INDEX UNIQUE SCAN CS_CITY_MST_PK  [ANALYZED]
          VIEW
            FILTER
              SORT GROUP BY
                HASH JOIN OUTER
                  FILTER
                    HASH JOIN OUTER
                      TABLE ACCESS FULL T_MKS_IND_MST  [ANALYZED]
                      TABLE ACCESS FULL T_MKS_IND_CRED_LMT_MST  [ANALYZED]
                  TABLE ACCESS FULL T_FAS_SALE_AWA  [ANALYZED]
        INDEX RANGE SCAN CS_MKS_STATE_HDAY_MST_PK  [ANALYZED]
      FILTER
        SORT GROUP BY
          FILTER
            HASH JOIN OUTER
              FILTER
                NESTED LOOPS OUTER
                  NESTED LOOPS
                    NESTED LOOPS
                      NESTED LOOPS
                        NESTED LOOPS
                          NESTED LOOPS
                            TABLE ACCESS FULL T_ORD_SCHDL_DTLS  [ANALYZED]
                            TABLE ACCESS BY INDEX ROWID T_ORD_PRD_DTLS
[ANALYZED]
                              INDEX RANGE SCAN PRD_IDX  [ANALYZED]
                          TABLE ACCESS BY INDEX ROWID T_ORD_ORDER_HDR
[ANALYZED]
                            INDEX UNIQUE SCAN CS_ORD_HDR_PK  [ANALYZED]
                        TABLE ACCESS BY INDEX ROWID T_MKS_CONS_MST  [ANALYZED]
                          INDEX UNIQUE SCAN CS_CONS_MST_PK  [ANALYZED]
                      TABLE ACCESS BY INDEX ROWID T_PIS_CITY_MST  [ANALYZED]
                        INDEX UNIQUE SCAN CS_CITY_MST_PK  [ANALYZED]
                    TABLE ACCESS BY INDEX ROWID T_MKS_CONS_MST  [ANALYZED]
                      INDEX UNIQUE SCAN CS_CONS_MST_PK  [ANALYZED]
                  TABLE ACCESS BY INDEX ROWID T_MKS_CONS_CRED_LMT_MST
[ANALYZED]
                    INDEX RANGE SCAN CS_MKS_CONS_CRED_LMT_MST_PK  [ANALYZED]
              TABLE ACCESS FULL T_FAS_SALE_AWA  [ANALYZED]
            INDEX RANGE SCAN CS_MKS_STATE_HDAY_MST_PK  [ANALYZED]
      FILTER
        NESTED LOOPS
          NESTED LOOPS
            NESTED LOOPS
              HASH JOIN
                NESTED LOOPS
                  TABLE ACCESS BY INDEX ROWID T_ORD_PRD_DTLS  [ANALYZED]
                    INDEX RANGE SCAN PRD_IDX  [ANALYZED]
                  TABLE ACCESS BY INDEX ROWID T_ORD_ORDER_HDR  [ANALYZED]
                    INDEX UNIQUE SCAN CS_ORD_HDR_PK  [ANALYZED]
                VIEW
                  FILTER
                    SORT GROUP BY
                      HASH JOIN OUTER
                        FILTER
                          HASH JOIN OUTER
                            TABLE ACCESS FULL T_MKS_CONS_MST  [ANALYZED]
                            TABLE ACCESS FULL T_MKS_CONS_CRED_LMT_MST
[ANALYZED]
                        TABLE ACCESS FULL T_FAS_SALE_AWA  [ANALYZED]
              TABLE ACCESS BY INDEX ROWID T_ORD_EXPORT_DTLS  [ANALYZED]
                INDEX UNIQUE SCAN CS_ORD_EXPORT_PK  [ANALYZED]
            TABLE ACCESS BY INDEX ROWID T_PIS_CITY_MST  [ANALYZED]
              INDEX UNIQUE SCAN CS_CITY_MST_PK  [ANALYZED]
          VIEW
            FILTER
              SORT GROUP BY
                HASH JOIN OUTER
                  FILTER
                    HASH JOIN OUTER
                      TABLE ACCESS FULL T_MKS_IND_MST  [ANALYZED]
                      TABLE ACCESS FULL T_MKS_IND_CRED_LMT_MST  [ANALYZED]
                  TABLE ACCESS FULL T_FAS_SALE_AWA  [ANALYZED]
        INDEX RANGE SCAN CS_MKS_STATE_HDAY_MST_PK  [ANALYZED]

95 rows selected.

oracle version 9.0.2

oiisdba@OIIS8.WORLD> @exp

Query Plan
---------------------------------------------------------------------------------
------------------
SELECT STATEMENT    [CHOOSE] Cost=3905 Rows=3 Bytes=897
  SORT UNIQUE
    UNION-ALL
      FILTER
        NESTED LOOPS
          NESTED LOOPS
            NESTED LOOPS
              NESTED LOOPS
                NESTED LOOPS
                  TABLE ACCESS BY INDEX ROWID T_ORD_PRD_DTLS  [ANALYZED]
                    INDEX RANGE SCAN PRD_IDX  [ANALYZED]
                  TABLE ACCESS BY INDEX ROWID T_ORD_ORDER_HDR  [ANALYZED]
                    INDEX UNIQUE SCAN CS_ORD_HDR_PK  [ANALYZED]
                VIEW
                  FILTER
                    SORT GROUP BY
                      HASH JOIN OUTER
                        VIEW
                          FILTER
                            NESTED LOOPS OUTER
                              TABLE ACCESS FULL T_MKS_CONS_MST  [ANALYZED]
                              TABLE ACCESS BY INDEX ROWID
T_MKS_CONS_CRED_LMT_MST  [ANALYZED]
                                INDEX RANGE SCAN CS_MKS_CONS_CRED_LMT_MST_PK
[ANALYZED]
                        TABLE ACCESS FULL T_FAS_SALE_AWA  [ANALYZED]
              TABLE ACCESS BY INDEX ROWID T_MKS_CONS_MST  [ANALYZED]
                INDEX UNIQUE SCAN CS_CONS_MST_PK  [ANALYZED]
            TABLE ACCESS BY INDEX ROWID T_PIS_CITY_MST  [ANALYZED]
              INDEX UNIQUE SCAN CS_CITY_MST_PK  [ANALYZED]
          VIEW
            FILTER
              SORT GROUP BY
                HASH JOIN OUTER
                  VIEW
                    FILTER
                      HASH JOIN OUTER
                        TABLE ACCESS FULL T_MKS_IND_MST  [ANALYZED]
                        TABLE ACCESS FULL T_MKS_IND_CRED_LMT_MST  [ANALYZED]
                  TABLE ACCESS FULL T_FAS_SALE_AWA  [ANALYZED]
        INDEX FAST FULL SCAN CS_MKS_STATE_HDAY_MST_PK  [ANALYZED]
      FILTER
        NESTED LOOPS
          NESTED LOOPS
            NESTED LOOPS
              NESTED LOOPS
                NESTED LOOPS
                  TABLE ACCESS FULL T_ORD_SCHDL_DTLS  [ANALYZED]
                  TABLE ACCESS BY INDEX ROWID T_ORD_PRD_DTLS  [ANALYZED]
                    INDEX UNIQUE SCAN CS_ORD_PRD_DTL_PK  [ANALYZED]
                TABLE ACCESS BY INDEX ROWID T_ORD_ORDER_HDR  [ANALYZED]
                  INDEX UNIQUE SCAN CS_ORD_HDR_PK  [ANALYZED]
              VIEW
                FILTER
                  SORT GROUP BY
                    HASH JOIN OUTER
                      VIEW
                        FILTER
                          NESTED LOOPS OUTER
                            TABLE ACCESS FULL T_MKS_CONS_MST  [ANALYZED]
                            TABLE ACCESS BY INDEX ROWID T_MKS_CONS_CRED_LMT_MST
[ANALYZED]
                              INDEX RANGE SCAN CS_MKS_CONS_CRED_LMT_MST_PK
[ANALYZED]
                      TABLE ACCESS FULL T_FAS_SALE_AWA  [ANALYZED]
            TABLE ACCESS BY INDEX ROWID T_MKS_CONS_MST  [ANALYZED]
              INDEX UNIQUE SCAN CS_CONS_MST_PK  [ANALYZED]
          TABLE ACCESS BY INDEX ROWID T_PIS_CITY_MST  [ANALYZED]
            INDEX UNIQUE SCAN CS_CITY_MST_PK  [ANALYZED]
        INDEX FAST FULL SCAN CS_MKS_STATE_HDAY_MST_PK  [ANALYZED]
      FILTER
        NESTED LOOPS
          NESTED LOOPS
            NESTED LOOPS
              NESTED LOOPS
                NESTED LOOPS
                  TABLE ACCESS BY INDEX ROWID T_ORD_PRD_DTLS  [ANALYZED]
                    INDEX RANGE SCAN PRD_IDX  [ANALYZED]
                  TABLE ACCESS BY INDEX ROWID T_ORD_ORDER_HDR  [ANALYZED]
                    INDEX UNIQUE SCAN CS_ORD_HDR_PK  [ANALYZED]
                VIEW
                  FILTER
                    SORT GROUP BY
                      HASH JOIN OUTER
                        VIEW
                          FILTER
                            NESTED LOOPS OUTER
                              TABLE ACCESS FULL T_MKS_CONS_MST  [ANALYZED]
                              TABLE ACCESS BY INDEX ROWID
T_MKS_CONS_CRED_LMT_MST  [ANALYZED]
                                INDEX RANGE SCAN CS_MKS_CONS_CRED_LMT_MST_PK
[ANALYZED]
                        TABLE ACCESS FULL T_FAS_SALE_AWA  [ANALYZED]
              TABLE ACCESS BY INDEX ROWID T_ORD_EXPORT_DTLS  [ANALYZED]
                INDEX UNIQUE SCAN CS_ORD_EXPORT_PK  [ANALYZED]
            TABLE ACCESS BY INDEX ROWID T_PIS_CITY_MST  [ANALYZED]
              INDEX UNIQUE SCAN CS_CITY_MST_PK  [ANALYZED]
          VIEW
            FILTER
              SORT GROUP BY
                HASH JOIN OUTER
                  VIEW
                    FILTER
                      HASH JOIN OUTER
                        TABLE ACCESS FULL T_MKS_IND_MST  [ANALYZED]
                        TABLE ACCESS FULL T_MKS_IND_CRED_LMT_MST  [ANALYZED]
                  TABLE ACCESS FULL T_FAS_SALE_AWA  [ANALYZED]
        INDEX FAST FULL SCAN CS_MKS_STATE_HDAY_MST_PK  [ANALYZED]

102 rows selected.

          Followup:

1) you confuse me

"test is 902"
"prod is 804"

that cannot be. that maybe a test instance -- but for some other application
entirely.

804 and 902, besides being 5 generations and as many years apart, will respond
entirely differently.

I pray you don't think testing on 902 can be done for 804.

      Rating: 4
Hash join Vs
      Nested Loop  May 28, 2003
Reviewer:
      Vinodhps  from India, Chennai
Hi Tom,
Sorry i have given wrong versions. first plan is from 9.0.2(Test database) and
second one is from 8.0.4(Live databse) have given vice versa..sorry.

i will be happy know the difference between the plans and how test database is
faster.

Thank you

      Rating: 4
Hi
      Tom  May 28, 2003
Reviewer:  vinodhps
       from India , Chennai.
Hi Tom,
Don't worry Due to server problem we have not upgraded and we already got server
for 2terabyte . End of June we will upgrade to 9.0.2.

please can you tell me what make the query to take 4 to 5 hrs
what is the exact problem over there in the plan.

so that it will be helpfull for me to learn. about the plan.

Thanks,

          Followup:

I cannot, insufficient data (and no, I don't want the extra data here -- it is
not relevant to the original question that was asked)

      Rating: 5
Hash join two
      large tables   May 29, 2003
Reviewer:  Tony
       from UK
Here is an excerpt of a statement from a dba when asked about poor performance
on a large load script, which was causing a hash_join of two tables.

"This is because a small set of data being joined to a large set of data is good
for Hash Join. but two large
data sets of a similar size is known to perform badly. A NL is better in that
circumstance."

Is this true ??? If it is true why does our optimizer choose to hash join. We
have a large hash area and have shown that the hash table has been stored in
memory.

          Followup:

that dba is smoking something.  BULK OPERATIONS like a hash join become even
more superior at the sizes go up.

Think about what a NL join involves:

  for every row in big table 1 (read using good multi-block io)
  loop
       for every index in an index range scan
       loop
           do a single block io on big table 2
       end loop
  end loop

that inner loop is executed for each row in big table 1.  that is hugely
expensive.

Consider -- big_table and big_table2 are two 3.8 million row tables. Tell me,
which query would you like to use?  which query do you think scales "better"?

big_table@ORA920> set echo on
big_table@ORA920>
big_table@ORA920> set timing on
big_table@ORA920> set autotrace traceonly
big_table@ORA920> select *
  2    from big_table2 t1, big_table t2
  3   where t1.id = t2.id
  4  /

3817191 rows selected.

Elapsed: 00:10:10.13

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=27627 Card=3817191
Bytes=778706964)
   1    0   HASH JOIN (Cost=27627 Card=3817191 Bytes=778706964)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE2' (Cost=5303 Card=3817191
Bytes=389353482)
   3    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=5261 Card=3817191
Bytes=389353482)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     109833  consistent gets
     218828  physical reads
          0  redo size
  609541217  bytes sent via SQL*Net to client
    2799768  bytes received via SQL*Net from client
     254481  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    3817191  rows processed

big_table@ORA920> select /*+ use_nl(t1,t2) index(t1 big_table2_pk) */ *
  2    from big_table2 t1, big_table t2
  3   where t1.id = t2.id
  4  /

3817191 rows selected.

Elapsed: 00:32:05.67

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=7639643 Card=3817191
Bytes=778706964)
   1    0   NESTED LOOPS (Cost=7639643 Card=3817191 Bytes=778706964)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=5261 Card=3817191
Bytes=389353482)
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE2' (Cost=2 Card=1
Bytes=102)
   4    3       INDEX (UNIQUE SCAN) OF 'BIG_TABLE2_PK' (UNIQUE) (Cost=1 Card=1)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
   12011606  consistent gets
    3857264  physical reads
          0  redo size
  540186137  bytes sent via SQL*Net to client
    2799768  bytes received via SQL*Net from client
     254481  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    3817191  rows processed

big_table@ORA920> set autotrace off

      Rating: 5
Choosing
      between Hash Join and Nested Loop or Merge Join  May 29,
      2003
Reviewer:  Vivek Sharma  from Bombay, India

Dear Tom,

Thanks for your support. This article was very helpful to understand the
differences in all of these joins.

What I want to know is, when writing a query on some big tables, suppose 4
tables and all of them containing around 1.5 Lakh rows. Assuming that the query
will return around 80000 to 90000 rows. Understanding from this article, Hash
Join are better than Nested Loop or Sort Joins. Shall we provide the hint to use
Hash Join or the optimizer will choose the best for us.

Thanks in advance.
Regards
Vivek Sharma

          Followup:

NO HINTS, let the optimizer do its job...

Maybe next year, a hash join isn't right and something else (that doesn't even
exist) is.  Hints preclude you from getting it.

      Rating: 4
Hash vs Nested
      join  May 30, 2003
Reviewer:  Vinodhps
       from India,Chennai
Hi... tom ,
thanks for your response..
1) some query take more time that i could not able to trace them ,it take hrs so
i aborptly terminate the process, is there any way to trace(10046,10053) these
kind of queries.
if i terminate the process iam getting only empty trace
files.. please suggest easy method to trace these queries.
In your book i could not able to find any regarding this.

2)Regarding your answer for Cardinality its showing total no of row for the
subset .when i expalin plan my query which shows me card=3 where as the query
returns 1294 rows,  what is that 3  can you please Explain the concept behind
that Mr.Tom.

Thanks

          Followup:

1) don't know what to tell you .  you kill them, they stop running, of course
the trace is empty.

10053 doesn't require running the query, just explain plan it.

2) i cannot explain anything without seeing it (NO do not put it here).  that is
an estimated cardinality.  It is based on statistics.  It is a mathematical
function.  It is rarely 100% accurate (think about it, every set of input to
that query would have to return exactly 3 rows -- thats not possible).

I would not be overly concerned with the difference between 3 and 1294 at the
topmost level.  There is some set of inputs (binds) to that query that would
probably come much closer or the stats are slightly out of date or the data is
skewed slightly and you don't have histograms...

      Rating: 4
do not believe
      everything CBO chooses  January 06, 2004
Reviewer:
      A reader
Hi

Do not believe everything CBO chooses, we have very bad experiences in all
versions from 8.0.6 to 9.2.0.4. It choose many times merge join when it should
use hash join, one example is one of the batch jobs running in 9.2.0.4 used to
take 8 hours, now it takes 45 minutes. All we did were forcing it to use hash
join instead of merge join, which CBO thought was the best

May 30, 2003 Kamal -- Thanks for the question regarding "How
      to kill a procedure running via DBMS_JOB queue...", version 817

      You Asked
Hi Tom,
We have a requirement where we want to schedule a procedure to run via DBMS_JOB
queue and a web interface to show the progress of the procedure.
We are planing to create the procedure such that it puts information at each
logical step completion using dbms_application_info.set_client_info.
The web interface may then read this info to find out how far in the code the
procedure is at this time.

Do you think this is optimal solution? Any ideas?

We also want to support a way to signal termination (or kill) of this procedure
via the web interface. The idea is that the web interface will place a request
to kill this running procedure (maybe because its taking longer than it should
to finish...) and we should go ahead and kill this job (the session its running
under) programmatically.

Any ideas how we could do that from within Oracle? (kill the procedure running
via dbms_job queue). We could remove the dbms_job entry for this job, but I
guess this will not affect the currently running copy of the procedure.
Thanks,

      and we said...
you can use dbms_application_info.set_session_longops instead, it supplies more
information about progress and even guestimates time to completion.  search for
that string on this site for examples....

you would have to kill the session behind the job or better yet -- you could do
this

o the job is going to call dbms_application_info anyway
o put that call into a "special procedure"
o have that special procedure look to see if an ALERT that it registered for
(read
  about dbms_alert) was fired
o if it did fire, this procedure should

     RAISE PROGRAM_ERROR;

o the top level "job" submitted would be:

  begin
      dbms_alert.register for the kill event
      do_the_procedure;
      dbms_alert.unregister yourself
  exception
      when program_error then
          rollback;
          dbms_alert.unregister yourself
          null; -- I was killed, just exit
      when others then
          dbms_alert.unregister yourself
          RAISE;
  end;

that would be "nicer".  It wouldn't be good to kill the job queue process.
Exiting normally as we did here will just remove the job normally from the
queue.
Review
      & Followup

      Rating: 5
Just what I was
      looking for..  May 29, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Thanks a lot.

      Rating: 5
Any overhead
      for setting TIMED_STATISTICS to TRUE?  May 30, 2003

Reviewer:  Kamal Kishore  from New Jersey, USA

Hi Tom,
The Oracle documentation says the following about the V$SESSION_LONGOPS stats
gathering:

<quote>
V$SESSION_LONGOPS
This view displays the status of various operations that run for longer than 6
seconds (in absolute time). These operations currently include many backup and
recovery functions, statistics gathering, and query execution, and more
operations are added for every Oracle release.

To monitor query execution progress, you must be using the cost-based optimizer
and you must:

Set the TIMED_STATISTICS or SQL_TRACE parameter to true
Gather statistics for your objects with the ANALYZE statement or the DBMS_STATS
package
You can add information to this view about application-specific long-running
operations by using the DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS procedure.
</quote>

It says that you must set TIMED_STATISTICS to TRUE to get meaningful information
in this view. Is there any overhead for setting this to TRUE permanently in a
production system?

Can it be set to TRUE always, or is it used only when there is a performance
problem (to evaluate the long running queries)?

Do you think it would be possible to convince the DBA's to set it to TRUE for a
production system?
Thanks,

          Followup:

I have it on all of the time in all of my systems.  Its on on our stuff.

as for whether it will be possible to convince your dba's, well, that is a
function of who your dba is more then another else.  some people don't believe
anything.

May 30, 2003 Patrick -- Thanks for the question regarding
      "Import question : COMMIT=Y : How big is the array insert ?", version
8.1.6

      You Asked
I would like a better understanding of the IMPORT parameter COMMIT=Y.

The Oracle manual says that if you use the parameter COMMIT=Y the import will
commit after each array insert.
What I want to know is ; How big is each array insert ?

I dug around on the net and I found a white paper by Michael Abbey in titled
'Export/Import - The DBA's Best Friend'.

http://www.dbatoolbox.com/WP2002_06/dba_imp_exp_friend.pdf
On the bottom of page 8 he explains :
COMMIT=Y will cause the import to commit after each buffer.  The buffer size is
controlled by the import BUFFER parameter.

I would like to confirm this.
Is the 'array insert size' that COMMIT=Y uses controlled by the IMPORT parameter
BUFFER ?

IE; If I use COMMIT=Y BUFFER=64000 - Will I get a commit after every 64K of data
?

      and we said...
it'll be roughly 64k -- there is overhead.

For example, a number will always consume 44 bytes in buffer.  But the numbers
you are inserting will most likely NOT consume 44 bytes.  44 bytes is the max

Think about this -- you have a table with 10 varchar2(4000) columns.  The maxed
out space will be 40,000 (plus overhead) bytes.  Hence, using a 64k buffer,
you'll get at most 1 record per insert.  Even if you insert a record with 9
nulls and the string "hello world" -- one record (since the buffer is a maxed
out representation of the row)

so, you'll get a commit after not each 64k of data -- but after you've inserted
the number of rows that, when you compute the WORST CASE rowsize -- fit into
that 64k buffer (eg: if your worst case rowsize is 32k - 1 row per insert.  20k
-- 3 rows, 10k -- 6rows and so on).

You can observe this behaviour fairly easily.  Consider a script like this:

create table t_1byte ( x char(1) );
create table t_1k ( x varchar2(1000) );
create table t_5k ( a varchar2(4000), b varchar2(1000) );
create table t_10k
( a varchar2(4000), b varchar2(4000), c varchar2(2000) );
create table t_20k
( a varchar2(4000), b varchar2(4000), c varchar2(4000),
  d varchar2(4000), e varchar2(4000) );
create table t_40k
( a varchar2(4000), b varchar2(4000), c varchar2(4000),
  d varchar2(4000), e varchar2(4000) ,
  f varchar2(4000), g varchar2(4000), h varchar2(4000),
  i varchar2(4000), j varchar2(4000) );

insert into t_1byte select 1 from all_objects;
insert into t_1k select 1 from all_objects;
insert into t_5k(a) select 1 from all_objects;
insert into t_10k(a) select 1 from all_objects;
insert into t_20k(a) select 1 from all_objects;
insert into t_40k(a) select 1 from all_objects;
commit;

!exp userid=/ 'tables=(t_1byte,t_1k,t_5k,t_10k,t_20k,t_40k)'

so, we have tables of various sizes -- various MAX sizes -- but they all have
the same *data* in them and then we

create or replace trigger tkyte_logon
after logon on schema
begin
    execute immediate 'alter session set sql_trace=true';
end;
/

drop table t_1byte;
drop table t_1k;
drop table t_5k;
drop table t_10k;
drop table t_20k;
drop table t_40k;

!imp userid=/ commit=y buffer=65367 full=y

the tkprof is very telling as to the array size used for each:

INSERT /*+NESTED_TABLE_SET_REFS+*/ INTO "T_1BYTE" ("X")
VALUES
 (:1)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      2      0.09       0.15          0         83        783       30563
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.09       0.16          0         83        783       30563

that shows we did two inserts here.  A CHAR(1) takes two bytes (leading length
plus the data) so that makes sense -- with the overhead, we got most of the rows
on the first insert and a couple of stragglers on the second.  The math is
easier with 1k:

INSERT /*+NESTED_TABLE_SET_REFS+*/ INTO "T_1K" ("X")
VALUES
 (:1)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute    478      0.29       0.27          0         90       1612       30563
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      479      0.30       0.27          0         90       1612       30563

ops$tkyte@ORA920> select 30563/478 from dual;

 30563/478
----------
63.9393305

about 64 rows/insert, using a 64k buffer -- that makes sense

INSERT /*+NESTED_TABLE_SET_REFS+*/ INTO "T_5K" ("A", "B")
VALUES
 (:1, :2)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute   2547      1.10       1.14          0         34       5628       30563
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2548      1.11       1.15          0         34       5628       30563

there, 5k MAX rowsize

ops$tkyte@ORA920> select 30563/2547 from dual;

30563/2547
----------
11.9996074

about 12 (12*5 = 60 with 4k leftover)....

INSERT /*+NESTED_TABLE_SET_REFS+*/ INTO "T_10K" ("A", "B", "C")
VALUES
 (:1, :2, :3)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute   5094      2.93       2.94          0         34      36500       30563
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     5095      2.94       2.95          0         34      36500       30563

ops$tkyte@ORA920> select 30563/5094 from dual;

30563/5094
----------
5.99980369

about 6 rows per insert (but see how the time is going UP UP UP -- same data,
shows the efficiency of array processing)

INSERT /*+NESTED_TABLE_SET_REFS+*/ INTO "T_20K" ("A", "B", "C", "D", "E")
VALUES
 (:1, :2, :3, :4, :5)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute  10188      5.17       5.25          0         34      41613       30563
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    10189      5.17       5.25          0         34      41613       30563

ops$tkyte@ORA920> select 30563/10188 from dual;

30563/10188
-----------
 2.99990185

3 rows per insert, slower still...

INSERT /*+NESTED_TABLE_SET_REFS+*/ INTO "T_40K" ("A", "B", "C", "D", "E", "F",
   "G", "H", "I", "J")
VALUES
 (:1, :2, :3, :4, :5, :6, :7, :8, :9, :10)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute  30563     12.69      14.31          0         34      62097       30563
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    30564     12.69      14.31          0         34      62097       30563

1 row per insert -- even though it was one byte big!

And believe me, that 40k insert took a heck of alot longer then 14 seconds, it
was 10's of minutes from IMP due to back and forth and back and forth and
waiting for commits to complete (lots of log file syncs going on there)

You want to set buffer really really really big on import to achieve
"faster=true".

Use commit=y at your own risk (tons of log file syncs will be in your future)
Review &
      Followup

      Rating:
      5
commit=no-no  May 30, 2003
Reviewer:
      Martin Guillen  from Buenos Aires, Argentina
Hi Tom:
Do COMMIT=N mean commiting at the end of imp?
Thank You,
Martin.

          Followup:

no, the commit happens after the table is loaded, table by table (they throw
create indexes and such in there that auto commit as well)

      Rating: 5
Import question
      : COMMIT=Y : How big is the array insert   May 30, 2003

Reviewer:  patrick howe  from olympia, wa
Very detailed explaination of my question!
Thanks for laying out a good explaination of the problem.

      Rating:
      4
tkprof  May 30, 2003
Reviewer:
      Reader  from Salt Lake City, UT
I tried your example but when i did my tkprof i got the following. What am i
doing wrong here? kindly help. thanks.

user19 ksh > tkprof u19_ora_3214.trc a.tkp explain=system/manager sys=no

TKPROF: Release 9.2.0.1.0 - Production on Fri May 30 08:55:46 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

user19 ksh > more a.tkp

TKPROF: Release 9.2.0.1.0 - Production on Fri May 30 08:55:46 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Trace file: u19_ora_3214.trc
Sort options: default

********************************************************************************
count    = number of times OCI procedure was executed
cpu      = cpu time in seconds executing
elapsed  = elapsed time in seconds executing
disk     = number of physical reads of buffers from disk
query    = number of buffers gotten for consistent read
current  = number of buffers gotten in current mode (usually for update)
rows     = number of rows processed by the fetch or execute call
    0  statements EXPLAINed in this session.
********************************************************************************
Trace file: u19_ora_3214.trc
Trace file compatibility: 9.00.01
Sort options: default

       1  session in tracefile.
       0  user  SQL statements in trace file.
       0  internal SQL statements in trace file.
       0  SQL statements in trace file.
       0  unique SQL statements in trace file.
      22  lines in trace file.

          Followup:

that trace file had nothing in it -- we'll, it had 22 lines and if I had to
guess the 22 lines included the message:

** SESSION ID:(9.3307) 2003-05-30 13:50:13.802
Skipped error 604 during the execution of OPS$TKYTE.TKYTE_LOGON
*** 2003-05-30 13:50:13.803
ksedmp: internal or fatal error
ORA-00604: error occurred at recursive SQL level 1
ORA-01031: insufficient privileges
ORA-06512: at line 2

grant yourself ALTER SESSION directly.

May 30, 2003 money -- Thanks for the question regarding
      "Performance Tuning", version 8.1.7

      You Asked
Tom,

In an application whenvever the concurrent users increase application
speed bogs down substanlially. This happens when we query or apply any DMl
statement

Concurrent users ranges from 25 to 50

Do I need to have Paralel server

      and we said...
no, you need to figure out what you bottlenecks are and then set about resolving
them.

if you have an IO problem, clusters will just make it so you can really slam the
IO system harder.

if you have a poorly tuning query, clusters would be like using an atom bomb to
crack a walnut -- you would just tune the queries.

diagnose
and then
correct

don't

correct
and then
hope it works
May 31, 2003 -- Thanks for the question regarding "pass an
      array to a cursor, possible?", version 8..1.7.2

      You Asked
Hi

I have following procedure to analyze my database schemas, I have a small
problem, the procedure works perfectly but if there is an inexistent user then
my exception does not work perfectly. Let me explain, what I want to do is
whenever there is a inexistent user it would insert the inexistent username in
my process control table. Obviously a cursor doesnt know if an user exists so I
cant user cursor.column_name as a variable to insert in my table. How can I
accmplish this?

Also what is the difference between a pl/sql table using index by binary integer
and one that does not (index by binary integer initailizes the array or any
other reason?)

CREATE OR REPLACE PROCEDURE rdbms_analyze
IS
  no_such_user exception;
  cursor username_cursor is
                   select username
                   from dba_users
                   where username in ('OPSC','PERFSTAT', 'SCOTT');
  errmsg ctl_error_proceso.des_error%type;
  l_username dba_users.username%type;
BEGIN
  execute immediate 'alter session set sort_area_size=10485760';
  OPEN username_cursor;
  LOOP
    FETCH username_cursor into l_username;
      IF username_cursor%notfound
      THEN
      raise NO_SUCH_USER;
      ELSE
      dbms_stats.gather_schema_stats(OWNNAME => l_username,
                                     ESTIMATE_PERCENT => 20,
                                     METHOD_OPT => 'FOR ALL COLUMNS
                                     SIZE1', DEGREE => 1,
                                     CASCADE => TRUE);
      END IF;
    EXIT WHEN username_cursor%notfound;
  END LOOP;
  CLOSE username_cursor;
EXCEPTION
  WHEN NO_SUCH_USER THEN
  errmsg := 'Inexiettn user';
  insert into ctl_error_proceso values(seq_cod_error_proceso.nextval, sysdate,
errmsg);
  commit;
  WHEN OTHERS THEN
  errmsg := 'Error during analyze: '||sqlerrm;
  insert into ctl_error_proceso values(seq_cod_error_proceso.nextval, sysdate,
errmsg);
  commit;
END;
/

      and we said...
well, yes, you will NEVER get an error when you don't have something in the in
list.  The inlist is not a list of values that MUST be returned.  Its a list of
values that MIGHT be returned.

Do this instead:

procedure rdbms_analyze
as
   type array is table of varchar2(30);
   l_users   array := array( 'OPSC', 'PERSTAT', 'SCOTT' );

   l_junk    varchar2(30);
   errmsg    ctl_error_proceso.des_error%type;
begin
    for i in 1 .. l_users.count
    loop
         begin
               select username into l_junk
                 from dba_users
                where username = l_users(i);

             dbms_stats.gather_stats( ownname => l_users(i), .... );
         exception
             when no_data_found then
                errmsg := 'Inexiettn user';
                insert into ctl_error_proceso values
                (seq_cod_error_proceso.nextval, sysdate, errmsg);
         end;
    end loop;
exception
   when others then
      errmsg := 'Error during analyze: '||sqlerrm;
     insert into ctl_error_proceso values
     (seq_cod_error_proceso.nextval, sysdate, errmsg);
     commit;
end;

The biggest differences between:

type array is table of varchar2(30) index by binary_integer;
type array is table of varchar2(30);

is

o the index by table doesn't have to be extended to add a new entry, the other
one does.  eg, with an index by table:

     array_variable( array_variable.count+1 ) := 'xxx';

works, you would code:

     array_variable.extend;
     array_variable(array_variable.count) := 'xxx';

with the other one.

o index by tables cannot be inited simply as above, you must assign each one.
non-index by tables can be inited as above.
Review &
      Followup

      Rating: 1
So many ways to
      do the same one you shown.  February 01, 2002

Reviewer:  anony  from usa
Why do you want to complicate the way if it can be done a lot of other simpler
ways. just to show the capability of asktom.oracle.com, I guess.

          Followup:

HUH?  what are you trying to say?

They want to analyze these three schemas.

In the event one of the three don't exist for whatever reason, they would like
to be notified in their error log.

tell me -- what is the easier way?

      Rating:
      5
excellent  February 01, 2002
Reviewer:
      A reader
I dont understand anony's comment but the code is good! But I am confused about
the pl/sql table part

May you be put a small example comparing index by bnary integer and one that
doesnt and how do we initialize each of them?

Also if index by binary integer gives us unlimited array size shouldnt we use
that most of the times? (I guess we have more flexibility)

Thx

          Followup:

Ok, here we are:

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2          type idx_by_array_type is table of varchar2(30) index by
binary_integer;
  3          type array_type is table of varchar2(30);
  4
  5          idx_by_array   idx_by_array_type;
  6          array          array_type := array_type( 'one', 'two', 'three' );
  7  begin
  8          idx_by_array(1) := 'one';
  9          idx_by_array(2) := 'two';
 10          idx_by_array(3) := 'three';

the above shows the differences in defaulting the values.  a collection type
(the non-index by array type) is easier to default...

 11
 12
 13          idx_by_array(4) := 'four';
 14          array.extend;
 15          array(4) := 'four';

that shows that adding to the "end" of the index by array is easier, its as if
it were already there.  We have to "extend" the other array type...

 16
 17
 18          idx_by_array(10000) := 'ten thousand';
 19          array.extend(10000-4);
 20          array(10000) := 'ten thousand';
 21  end;
 22  /

PL/SQL procedure successfully completed.

and that last bit shows that index by arrays are "sparse" in a way.  There are
5 elements in it -- 1,2,3,4,10000 are valid subscripts -- there are no values 5,
6, .... 9999.  The other one however is not sparse.  There are 10,000 elements
in that array now.

So, it depends on your needs.  I find myself using the non-index by arrays more
and more due to the ease of initialization like this example uses.

      Rating: 4
procedure
      declaration  February 01, 2002
Reviewer:
      raju
i see in the question, the procedure is declared
"create or replace procedure rdbms_analyze
IS"

in your answer you declared it as
"create or replace procedure rdbms_analyze
aS"

 is there any difference if yes what is that???

thanks

          Followup:

I like to read the code to myself.

create or replace procedure P as ..... code goes here ...

sounds better to me than

create or replace procedure P is .....

does.  The IS just isn't "right" sounding.  That is the only difference.  IS and
AS are interchangeable there.

I use IS in packages:

create package body my_pkg
as

procedure p
is
begin
 .....

there IS sounds right, AS does not....

      Rating: 3
what if we have
      1000 users?  February 04, 2002
Reviewer:
      A reader  from florida, usa
This procedure looks good but what happens if we have 1000 users...? How could
we define the array in a faster way?

thanx

          Followup:

put the users into a table and iterate over a cursor for loop instead of the
array.

 for x in ( select * from YOUR_USERS_TABLE )
    loop
         begin
               select username into l_junk
                 from dba_users
                where username = x.username;

             dbms_stats.gather_stats( ownname => x.username, .... );
         exception
             when no_data_found then
                errmsg := 'Inexiettn user';
                insert into ctl_error_proceso values
                (seq_cod_error_proceso.nextval, sysdate, errmsg);
         end;
    end loop;
exception

      Rating: 3
Having trouble
      getting this to work...  May 30, 2003
Reviewer:
      Paul  from Orlando, FL USA
Tom,

I feel really slow for having such difficulty in grasping this concept, when I
really need to.  We use subquery inlists VERY extensively to control our PL/SQL
DSS code, there may be 100s run in the course of an entire process.  The form is

INSERT INTO Target_table
( SELECT *
FROM Some_table
WHERE Some_column IN ( SELECT * FROM Another_table WHERE Another_column =
aBind_variable ) );

I have been searching your site, thinking there should be a way to accomplish
this with a collection, but I'm not having any luck at getting it to work.  When
I code a TYPE and a variable based on that type in a test PL/SQL procedure, I
get a complaint that says  'ORA-00902: invalid datatype' and it points to the
'AS HID_LIST' in the code, below.

   TYPE HID_List IS TABLE OF VARCHAR2(20) NOT NULL;
   sHID_List         HID_List;

BEGIN

SELECT CAST( MULTISET
    ( SELECT DISTINCT HG.Hospital_id
    FROM     T1 c,
             T2 hg
    WHERE    c.CLIENT_DESC = pClient
    AND      hg.CLIENT_CODE = c.CLIENT_CODE
    AND      hg.PRODUCT_ID = cProd_ID )
    AS HID_List )
INTO sHID_List
FROM DUAL ;

Next, I tried a specific example of yours from this discussion:

  sHID_LIST       HID_List;

BEGIN
INSERT_LOG( pRun_ID, cSP_Nm, 'Pass', 'BEGIN ' || cSP_Nm  );

FOR x IN ( SELECT DISTINCT HG.Hospital_id
    FROM     CORPORATE.Clients c,
             CORPORATE.Hospital_Groups hg
    WHERE    c.CLIENT_DESC = pClient
    AND      hg.CLIENT_CODE = c.CLIENT_CODE
    AND      hg.PRODUCT_ID = pProd_ID )
  LOOP
    SELECT x.Hospital_ID INTO sHID_LIST
    FROM   DUAL;
  END LOOP;

In this, I got an error that pointed to the x in x.Hospital_ID portion of the
code and said 'ORA-00932: inconsistent datatypes: expected UDT got CHAR'.

Also, most all of your examples and your inlist function I saw referenced, in
other discussions, work directly on known strings.  We want to use a dynamic
list through bind variables, like the example above, to drive the list for each
schema.  I think if I can get the second example to work this will solve that
problem, but I don't quite understand why the query in the FOR x and the query
inside the LOOP are both needed.

Finally, the last part of this question I have is relative to CREATE TYPE. When
I tried to do it in SQL*Plus, it was private to the schema where it was created.
Is there no way to make the type globally public, or will I have to create the
type in every schema that might reference it?   I see an AUTHID CURRENT_USER
feature in the syntax doc, but how would you use it.  Create a public synonym?
Seems odd for a data element, but maybe?

The reason behind this question is that we use AUTHID CURRENT_USER and run our
PL/SQL extensively across many schemas.

          Followup:

why do you want a collection?  that insert into with just sql looks perfect to
me?

anyway, when you use TYPES in sql, they must be SQL types -- NOT plsql types.

create type ....
/

in sqlplus, outside of plsql.

the types can be granted like anything else -- you just use schema references
(eg: just like if you create a spatial column, you'll use MDSYS)

      Rating:
      4
Closer  May 30, 2003
Reviewer:  Paul
       from Orlando FL USA
OK, Tom, that was a much faster response than I expected, thanks, the Schema
reference was painfully obvious when you pointed it out.

First, all that I am describing here, I want to do in PL/SQL.  We do very little
in SQL.

This will be my last question in this thread.  Yes, the subquery is OK.  The
underlying improvement I hoped to uncover was that it strikes me as quite
inefficient to keep going out to the buffer cache to retrieve the same small ( <
100, usually ) list of values, over and over and over.  We can hold individual
values in a PL/SQL program as variables, yet there is seemingly no way to hold
and use a list, easily and effectively.

I was thinking that collections could provide me with that kind or form of
constant list for the duration of the Stored Procedure, to use instead of the
subquery, but I seem to be missing some key point.

          Followup:

You *have* to define the types at the sql layer as you want to use them in SQL
(and that sql is in PLSQL).

there is an easy and effective way to hold a list, you just create a type (like
you just create a package a procedure a function a view anything).

collections do this.

they must be SQL Types if you desire to use them in SQL is all.  No big huge
fundemental change here, no more complex then using a view.

May 31, 2003 Solomon -- Thanks for the question regarding
      "difference between our db_block_buffers and db_cache_size", version 9i

      You Asked
Hi tom,

I know this would be a very novice question, but could you please tell me the
difference between db_block_buffers and db_cache_size. Thie question precisely
rises from the fact that the trace for simple select * from emp query in our
test database is as follows:-

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1363 Card=14 Bytes=5
          60)

   1    0   TABLE ACCESS (FULL) OF 'EMP' (Cost=1363 Card=14 Bytes=560)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      14168  consistent gets
      14157  physical reads
          0  redo size
       1313  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         14  rows processed

v$parameter shows that db_buffer_blocks is 0, is this the reason for 0 db block
gets.

And then came the confusing db_cache_size as 25165824

Please throw some ligth over this.

      and we said...
db block gets are 100% unrelated to these paramters.  db block gets are blocks
gotten in "CURRENT" (as of right now) mode.

do an update on emp and you'll see db block gets -- one at least for each row
updated.

you'll also see CONSISTENT gets, logical io's.  these are blocks gotten in
consistent read mode.  These are the results of a query portion of a statement
(so when you update EMP, you'll see consistent gets for the reads of EMP and
then db block gets for the updates)

the difference between db_block_buffers and db_cache_size is db_block_buffers is
the old 8i and before way of doing it.  You multiply db_block_buffers *
db_block_size and that is the size of your buffer cache.

db_cache_size is simply - the size of your default blocksize buffer cache
(easier).  It also permits you to set the other _cache_size settings for
different block sizes if you use that feature.
Review &
      Followup

      Rating:
      5
  May 25, 2003
Reviewer:  Solomon
      Brain
db_block_size parameter is 8192 and my db_cache_size is 25165824, does this mean
that I have 25165824/8192 = 3072 block buffers. And as far as I know database IO
will done in terms block size that is 8192 here, but then what is these
parameters:-

db_2k_cache_size
db_4k_cache_size
db_8k_cache_size
db_16k_cache_size

Presently there values are 0, but should there sum be equal to the db_cache_size
value?

Thanks for the response. This site is really helpful for novices like me.

Solomon

          Followup:

db_cache_size/db_block_size = number of buffers in the databases default block
size cache.

In your case, your default block size is 8k -- setting hte db_8k_cache_size is
not done.  You can set the 2k, 4k, 16k IF you intend on having tablespaces with
2k, 4k, and 16k block sizes -- else you just leave them at zero

      Rating:
      4
  May 30, 2003
Reviewer:  Vipin
       from New york
Hi Tom,

So what does that mean? If I am retreiving some data that is stored in the 2K
tablespace it will go to the 2K buffer cache and if I want 4K tablespace data
then Oracle will use 4K buffer cache?

If this is the case won't there be a size mismatch among the blocks in other
structures like redo log buffer, rollback segment etc.

          Followup:

yes.  2k data in 2k buffer cache, 4k data in 4k buffer cache.

redo is not "block" oriented at all.  changed bytes go into redo.

rollback (undo) is not "block" oriented at all.  It is a place we store stuff --
we don't pump full block images in there.

May 31, 2003 Lulekwa -- Thanks for the question regarding
      "question on job queue.How does it works", version 8.17

      You Asked
Hi Tom,
I want to schedule a job that runs from monday to friday, between 6a.m and 6pm
every 15 minutes time.How do I schedule it?
Yes I am using dbms_job.

I am checking

if to_number(to_char(sysdate,'hh24mi')) between 0600 and 1800 then
dbms_job.submit(job_no,job_name,
          next_day(sysdate + 15/1440,'MONDAY')
          ,null,false,nvl(job_instance),false);
elsif to_number(to_char(sysdate,'hh24mi')) between 1800 and 2359 then
 dbms_job.submit(job_no,job_name,
          next_day(sysdate + 15/1440,'TUESDAY')
          ,null,false,nvl(job_instance),false);
elsif to_number(to_char(sysdate,'hh24mi')) between 1800 and 2359 then
dbms_job.submit(job_no,job_name,
          next_day(sysdate + 15/1440,'WEDNSDAY')
          ,null,false,nvl(job_instance),false);
elsif to_number(to_char(sysdate,'hh24mi')) between 1800 and 2359 then
dbms_job.submit(job_no,job_name,
          next_day(sysdate + 15/1440,'THURSDAY')
          ,null,false,nvl(job_instance),false);
elsif to_number(to_char(sysdate,'hh24mi')) between 1800 and 2359 then
dbms_job.submit(job_no,job_name,
          next_day(sysdate + 15/1440,'FRIDAY')
          ,null,false,nvl(job_instance),false);
this code does not work.How do I schedule it?

      and we said...
ops$tkyte@ORA817DEV.US.ORACLE.COM> variable n number;
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace view next_date
  2  as
  3  select case
  4         when (to_char( sysdate, 'hh24' ) between 6 and 17
  5                   and to_char(sysdate,'dy') NOT IN ('sat','sun') )
  6              then
trunc(sysdate)+(trunc(to_char(sysdate,'sssss')/900)+1)*15/24/60
  7         when (to_char( sysdate, 'dy' ) not in ('fri','sat','sun') )
  8              then trunc(sysdate)+1+6/24
  9         else next_day( trunc(sysdate), 'Mon' )+6/24
 10         end interval_date
 11   from dual
 12  /

View created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2     dbms_job.submit( :n,
  3                      'null;',
  4                      sysdate,
  5                     '(select * from next_date)'
  6                    );
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_job.run( :n );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select to_char( sysdate, 'hh24:mi:ss' ),
to_char(next_date,'dd-mon-yyyy hh24:mi:ss' )
  2    from user_jobs
  3   where job = :n
  4  /

TO_CHAR( TO_CHAR(NEXT_DATE,'D
-------- --------------------
12:52:15 08-aug-2002 13:00:00

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

Will do that for you -- the view will return the proper next date any time you
select from it.
Review & Followup

      Rating: 5
extremely
      useful approach  August 08, 2002
Reviewer:
      A reader  from CA, USA

      Rating:
      5
  August 08, 2002
Reviewer:
      A reader
Extremely helpful!

Is there any advantage/disadvantage in creating next_date as a function instead
of a view?

          Followup:

In 817, CASE is not available in PLSQL unless you use dynamic sql.  So, you
would have to code:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function next_date return
date
  2  as
  3      l_date date;
  4  begin
  5      execute immediate
  6      'select case
  7              when (to_char( sysdate, ''hh24'' ) between 6 and 17
  8                        and to_char(sysdate,''dy'') NOT IN (''sat'',''sun'')
)
  9                   then
trunc(sysdate)+(trunc(to_char(sysdate,''sssss'')/900)+1)*15/24/60
 10              when (to_char( sysdate, ''dy'' ) not in
(''fri'',''sat'',''sun'') )
 11                   then trunc(sysdate)+1+6/24
 12              else next_day( trunc(sysdate), ''Mon'' )+6/24
 13              end interval_date
 14         from dual' INTO l_date;
 15
 16      return l_date;
 17  end;
 18  /

Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select to_char(sysdate,'dd-mon-yyyy
hh24:mi:ss' ),
  2         to_char(next_date(),'dd-mon-yyyy hh24:mi:ss' )
  3    from dual
  4  /

TO_CHAR(SYSDATE,'DD- TO_CHAR(NEXT_DATE(),
-------------------- --------------------
08-aug-2002 14:50:29 08-aug-2002 15:00:00

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

which works, just not as "pretty"

      Rating: 3
Hmmm - can't
      schedule a snapshot this way  August 08, 2002

Reviewer:  andrew  from ca, usa
Tom

I tried to use this method to schedule a snapshot, but it won't allow custom
functions or subqueries.
ORA-04044: procedure, function, package, or type is not allowed here
ORA-22818: subquery expressions not allowed here
If I try the whole case statement, I get "ORA-01401: inserted value too large
for column" (user_jobs.interval is varchar2(200)).

I guess the only remaining way is to create the snapshot with an arbitrary
INTERVAL and then edit the corresponding DBMS_JOB entry to use the select or the
function?

          Followup:

Yes that will work HOWEVER, beware exp/imp and other possible side effects.
Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create snapshot foo
  2  build immediate
  3  refresh complete
  4  next next_date
  5  as
  6  select * from scott.emp
  7  /
next next_date
     *
ERROR at line 4:
ORA-00984: column not allowed here

shows it really wants a DATE in that sql statement...

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create snapshot foo
  2  build immediate
  3  refresh complete
  4  next sysdate+10000
  5  as
  6  select * from scott.emp
  7  /

Materialized view created.

so use a bogus date and then

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> column job new_val J
ops$tkyte@ORA817DEV.US.ORACLE.COM> select job from user_jobs;

       JOB
----------
        97

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_job.interval( &J, 'next_date' );

PL/SQL procedure successfully completed.

change the job.  But, if you export that and imp show=y, you'll find:

...
 "ALTER FUNCTION "NEXT_DATE" COMPILE TIMESTAMP '2002-08-09:08:22:11'"
 "CREATE SNAPSHOT "FOO" USING ("FOO", (3, 'ORA817DEV.US.ORACLE.COM', 1, 0, "S"
 "COTT", "EMP", '2002-08-09:08:22:11', 0, 41340, '1950-01-01:12:00:00', '', 0"
 ", (1, "EMPNO", "EMPNO", 0, 321, 0)), 2097249, 4, ('1950-01-01:12:00:00', 31"
 "0, 0, 0)) REFRESH COMPLETE WITH PRIMARY KEY AS"
 "SELECT "EMP"."EMPNO" "EMPNO","EMP"."ENAME" "ENAME","EMP"."JOB" "JOB","EMP"."
 ""MGR" "MGR","EMP"."HIREDATE" "HIREDATE","EMP"."SAL" "SAL","EMP"."COMM" "COM"
 "M","EMP"."DEPTNO" "DEPTNO" FROM "SCOTT"."EMP" "EMP""
 "BEGIN   dbms_job.isubmit(job=>97,what=>'dbms_refresh.refresh(''"OPS$TKYTE"."
 ""FOO"'');',next_date=>to_date('2029-12-25:08:22:11','YYYY-MM-DD:HH24:MI:SS'"
 "),interval=>'sysdate+10000 ',no_parse=>true); END;"
 "BEGIN   dbms_refresh.make('"OPS$TKYTE"."FOO"',list=>null,next_date=>null,in"
 "terval=>null,implicit_destroy=>TRUE,lax=>FALSE,job=>97,rollback_seg=>NULL,p"
 "ush_deferred_rpc=>TRUE,refresh_after_errors=>FALSE,purge_option => 1,parall"
 "elism => 0,heap_size => 0); END;"
 "BEGIN   dbms_refresh.add(name=>'"OPS$TKYTE"."FOO"',list=>'"OPS$TKYTE"."FOO""
 "',siteid=>0,export_db=>'ORA817DEV.US.ORACLE.COM'); END;"
 "ALTER TABLE "FOO" ENABLE CONSTRAINT "EMP_PK""
Import terminated successfully without warnings.

It would be back to "normal".  You need to remember to fix the date again.

It might be better to

o create the snapshot WITHOUT an interval
o schedule the

begin
   dbms_job.submit( job => :n,
                    what => 'dbms_refresh.refresh( ''"OPS$TKYTE"."FOO"'' );',
                    next_date => next_date(),
                    interval => 'next_date' );
end;
/

call yourself.

      Rating: 5
Unexpected side
      effect  August 09, 2002
Reviewer:  andrew
       from ca, usa
Thanks for pointing the exp/imp issue out - I wouldn't have thought of that. I'm
glad I asked.

      Rating:
      5
dbms_job  August 19, 2002
Reviewer:
      Lulekwa  from South Africa
It is extremely useful!
It worked.

      Rating:
      3
Reader  September 26, 2002
Reviewer:
      A reader
Tom,
Why do I get the ORA-1422

Thanks

SQL> select * from next_date;

INTERVAL_
---------
27-SEP-02

SQL> begin
  2         dbms_job.submit( :n,
  3                          'null;',
  4                          sysdate,
  5                         '(select * from next_date)'
  6                        );
  7  end;
  8  /
begin
*
ERROR at line 1:
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at "SYS.STANDARD", line 586
ORA-06512: at line 2

          Followup:

what does select count(*) from next_date return?

You don't have 2 rows in DUAL do you?

You also neglect to mention a version.....

      Rating:
      4
Reader  September 26, 2002
Reviewer:
      A reader
Tom,

You are absolutely right. The count(*) from dual was > 1.
Although count(*) for x$dual remained at 1.

How do x$dual and dual inter-related

What causes the count(*) for dual to multiply. I have not
run sql.bsq more than, only one time during DB creation

what is the preferred method of fixing the count

Thanks

Microsoft Windows XP [Version 5.1.2600]

(C) Copyright 1985-2001 Microsoft Corp.

C:\Documents and Settings\josephn>cd c:\

C:\>sqlplus internal

SQL*Plus: Release 8.1.7.0.0 - Production on Thu Sep 26 08:44:38 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Enter password:

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production
With the Partitioning option
JServer Release 8.1.7.0.0 - Production

SQL> select * from dual;

D
-
X

SQL> select count(*) from dual;

  COUNT(*)
----------
         6

SQL> select * from x$dual;

ADDR           INDX    INST_ID D
-------- ---------- ---------- -
00F974C8          0          1 X

SQL> select count(*) from x$dual;

  COUNT(*)
----------
         1

SQL> truncate table sys.dual;

Table truncated.

SQL> insert into sys.dual values ('X');

1 row created.

SQL> commit;

Commit complete.

SQL> select count(*) from dual;

  COUNT(*)
----------
         1

          Followup:

dual and x$dual are not related.

x$dual IS dual before the database is opened.
dual is dual AFTER the database is opened.

what causes dual to have more then one row?  people do.  someone inserted a row
in there.  to fix:

delete from dual;
insert into dual values ('X');
commit;

and never never touch a SYS table directly again.

      Rating:
      4
  October 01, 2002
Reviewer:
      A reader
Hi tom,

I have two questions related to the original post:

1-I don’t understand very well the numbers that you used in your original
answer:
<quote>
   trunc(sysdate)+(trunc(to_char(sysdate,'sssss')/900)+1)*15/24/60
<quote>

I understand that 900=15*60, but why did you use that way to resolve that
issue?, what does exactly mean every term in your calculation ( [ trunc(sysdate)
] + [ [(trunc(to_char(sysdate,'sssss')/900)+1)]  *15/24/60 ] )?

2-I need to schedule a procedure that should ran every hour starting at 5:40 am
, so the run hours should be 5:40, 6:40,7:40 and so on. I tried to modify your
view replacing 900 by 3600 and 15 by 60, but it didn’t work and as I don’t
understand your calculation is difficult to me to figure out what I am doing
wrong. Would you mind helping me with this?

Thanks in advance.

          Followup:

for 5:40 on -- tis even easier.  we just need to trunc sysdate to the hour, add
an hour and 40 minutes:

trunc(sysdate,'hh') + 1/24 + 40*(1/24/60)

At 5:40, trunc(sysdate,'hh') will return 5am.  adding 1/24 (1/24th of a day, 1
hour) will give 6am.  Adding 40 * (1/24/60) will add 40 minutes... (adding N *
(1/24/60/60) will add N seconds)

It is all math -- add 1 to sysdate and you get tomorrow at the same time.  Add
1/2 -- you just added 1/2 day (12 hours) and so on.

So, in my:

   trunc(sysdate)+(trunc(to_char(sysdate,'sssss')/900)+1)*15/24/60

trunc(sysdate) is MIDNIGHT today (wipe out the time).

Then rest of it was figuring out how many 15 minute intervals had happened today
alert (the /900 stuff) and adding 1 more 15 minute interval to it -- so we get
nice 1:15, 1:30, 1:45, 2:00, 2:15 and so on.

If you have my book "Expert one on one Oracle" -- it goes into this in more
detail (not this particular example, but the whole "setting the intervals for
jobs" question.

      Rating: 5
DBMS_JOB KEEPS
      RESUBMITTING ITSELF  October 31, 2002
Reviewer:
      P  from LA
I have submitted a job via dbms_jobs. I want the job to run ONLY ONCE despite
the fact that it may fail.
Currently, dbms_jobs resubmits the job severaltimes.

I would like dbms_jobs to stop after the FIRST failure.

How to do this?

Also If I remove some job using
DBMS_JOB.REMOVE(1119);
while it is in queue and not running it doesnot go away from DBA_JOBS or doesnt
remove it.

please explain....

          Followup:

submit the job like this:

   dbms_job.submit( l_job,
                   'begin
                         procedure;
                    exceptions
                         when others then
                           null; /* blindly ignore the error */
                    end;' );

      Rating: 5
dbms_job.remove
      doesnt work  October 31, 2002
Reviewer:  P
       from LA
hey tom

why dbms_job.remove does not remove it from queue?

If I remove some job using
DBMS_JOB.REMOVE(1119);
while it is in queue and not running it doesnot go away from DBA_JOBS or doesnt
remove it.

          Followup:

give me an example like this:

ops$tkyte@ORA920.US.ORACLE.COM> variable n number
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> exec dbms_job.submit( :n, 'Null;' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> commit;

Commit complete.

ops$tkyte@ORA920.US.ORACLE.COM> print n

         N
----------
         4

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select job, what from user_jobs;

       JOB WHAT
---------- ------------------------------
         4 Null;

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> exec dbms_job.remove( :n );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> commit;

Commit complete.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select job, what from user_jobs;

no rows selected

that shows it does not work on your system.  works on all of mine.

      Rating: 5
why
      resubmit?  October 31, 2002
Reviewer:  P
       from LA
hi tom,
EXCEPTION
  WHEN OTHERS THEN
     NULL;

IS FINE

BUT COULD YOU PLEASE EXPLAIN WHY THIS IS HAPPENING?

WHY IT RESUBMITTS THE JOB AGAIN IF ERROR?

          Followup:

BECAUSE THAT IS THE DEFINED BEHAVIOR OF THE JOB QUEUES.

It is the way they are designed to work -- run the job, if it fails -- assume it
is a temporary error (network down, whatever).  Run the job again later.  try 16
times before marking the job as "broken".

It is the way they were built to operate.

All you need to do is make it so the queued job "succeeds", thats what this
does.

I don't see the purpose however in running a job, letting it fail and just
ignoring that.  Why bother even running the job?

      Rating:
      4
  November 01, 2002
Reviewer:  Sagi
       from India
Hi Tom,

Regarding DUAL table you said

"x$dual IS dual before the database is opened. dual is dual AFTER the database
is opened."

Q1) Please see the commands executed below.

SVRMGR> connect internal
Connected.
SVRMGR> shutdown immediate ;
Database closed.
Database dismounted.
ORACLE instance shut down.
SVRMGR> startup mount
ORACLE instance started.
Total System Global Area             47249436 byte
Fixed Size                              75804 byte
Variable Size                        30318592 byte
Database Buffers                     16777216 byte
Redo Buffers                            77824 byte
Database mounted.
SVRMGR>
SVRMGR>
SVRMGR> select * from dual ;
ADDR     INDX       INST_ID    D
-------- ---------- ---------- -
00F974C8          0          1 X
1 row selected.

SVRMGR> select * from x$dual ;
ADDR     INDX       INST_ID    D
-------- ---------- ---------- -
00F974C8          0          1 X
1 row selected.

SVRMGR> alter database open ;
Statement processed.
SVRMGR> select * from dual ;
D
-
X
1 row selected.
SVRMGR>

When the DB is mounted and I referr to either DUAL or X$DUAL both gives me same
output. Here my question is when i say DUAL how does it internally refer or
resolve to X$DUAL.

q2) What is the significance of having X$DUAL and DUAL before and after DB is
open. Any specific usage ...

q3) When the DB is "opened" the SYS.DUAL is with a single column, where as in
"mount" it was 4 columns.

Thanx in advance and waiting for your valuable reply.

Regards,
Sagi

          Followup:

q1) just take the word that "x$dual is dual before the database is opened".
thats all we (you and me both) need to know.  dual is special, dual is magic,
dual is handled differently then other tables under the covers.

q2) things that need dual (eg: rman) and that would fail without a dual like
table....

q3) statement of fact -- yes, that is true.

      Rating: 5
here is why run
      the jobs...  November 01, 2002
Reviewer:  P
       from LA
Hi Tom,
You said---------------
I don't see the purpose however in running a job, letting it fail and just
ignoring that.  Why bother even running the job?
-----------
Because these jobs create reports and store them in DB as a clob and we donot
care if they fail because this is done after 18 months so if failure then
correct error and startover again.

          Followup:

but how will you know they failed ;)

You can set the job to broken like this instead (now that I know what you WANT).
We'll make the job run once -- if it fails, it will break itself right off, but
stay IN the queue in a broken status with a run date way in the future.  so at
least you know it "didn't run".

Here is an example (first we'll see how to get the text used by oracle to
execute your job so you can see how to set broken and next_date!)

ops$tkyte@ORA920.US.ORACLE.COM> variable n number
ops$tkyte@ORA920.US.ORACLE.COM> exec dbms_job.submit( :n, ' /* tkyte */ null;'
);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> commit;

Commit complete.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> alter system flush shared_pool;

System altered.

ops$tkyte@ORA920.US.ORACLE.COM> exec dbms_job.run(:n);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> select sql_text from v$sql where sql_text like
'%tkyte%';

SQL_TEXT
---------------------------------------------------------------------------------
--------------------------------------------------
DECLARE job BINARY_INTEGER := :job; next_date DATE := :mydate;  broken BOOLEAN
:= FALSE; BEGIN  /* tkyte */ null; :mydate := next_d
ate; IF broken THEN :b := 1; ELSE :b := 0; END IF; END;

select sql_text from v$sql where sql_text like '%tkyte%'

there it is, we can just set the variables BROKEN and NEXT_DATE to whatever to
tell oracle the job is broken....

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace procedure big_time_failure
  2  as
  3      x int;
  4  begin
  5      x := 1/0;
  6  end;
  7  /

Procedure created.

ops$tkyte@ORA920.US.ORACLE.COM> exec big_time_failure;
BEGIN big_time_failure; END;

*
ERROR at line 1:
ORA-01476: divisor is equal to zero
ORA-06512: at "OPS$TKYTE.BIG_TIME_FAILURE", line 5
ORA-06512: at line 1

so, that'll never work...

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> begin
  2      dbms_job.submit( :n,
  3                      'begin  /* tkyte */
  4                          big_time_failure;
  5                       EXCEPTION
  6                          when others then
  7                             broken := TRUE;
  8                             next_date := to_date(''01019999'',
''ddmmyyyy'');
  9                       end;'
 10                     );
 11      commit;
 12  end;
 13  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> alter system flush shared_pool;

System altered.

ops$tkyte@ORA920.US.ORACLE.COM> exec dbms_job.run( :n );

PL/SQL procedure successfully completed.

"appears" to have run but....

ops$tkyte@ORA920.US.ORACLE.COM> select sql_text from v$sql where sql_text like
'%tkyte%';

SQL_TEXT
---------------------------------------------------------------------------------
--------------------------------------------------
DECLARE job BINARY_INTEGER := :job; next_date DATE := :mydate;  broken BOOLEAN
:= FALSE; BEGIN begin  /* tkyte */
       big_time_failure;                      EXCEPTION
when others then                           broken
:= TRUE;                            next_date := to_date('01019999',
'ddmmyyyy');                      end; :mydate := next_date; I
F broken THEN :b := 1; ELSE :b := 0; END IF; END;

select sql_text from v$sql where sql_text like '%tkyte%'

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select job, what, broken, failures from
user_jobs where job = :n;

       JOB WHAT                           B   FAILURES
---------- ------------------------------ - ----------
       223 begin  /* tkyte */             Y          0
           big_time_failure;
           EXCEPTION
           when others then
           broken := TRUE;
           next_date :=
           to_date('01019999',
           'ddmmyyyy');
           end;

we can see it is now just a "broken" job...

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> exec dbms_job.remove( :n );

PL/SQL procedure successfully completed.

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

      Rating: 5
what
      if  March 18, 2003
Reviewer:  miki
       from hungary
Hi Tom,

Could you give me an example about dbms_job.submit if i want to call set of
procedures in every last day of months.

thanks in advance,

miki

          Followup:

the interval would be

add_months(last_day(trunc(sysdate)),1)

      Rating: 3
Job
      Execution  March 19, 2003
Reviewer:  vicky
       from INDIA
hello sir ..

Sir i scheduled my job for execution after one hour...
when my database goes shutdown for greater than 1 hour
( interval time ) after that my does not execute ...
because system time is Greater then jobs Next_date ...

how i can schedule my job for executing permanently

          Followup:

that is not correct.  the job will execute shortly after startup.  The job
queues look for all jobs that SHOULD HAVE BEEN run and runs them.

if the job is NOT running after a restart -- perhaps your job queue processes in
your pfile is set to zero??? and someone needs to do an alter system and update
the pfile to fix that?

      Rating:
      5
Excellent  May 12, 2003
Reviewer:
      Ashish Somani  from Pune India
Nice tips given which r really helpful and helped me a lot in setting the jobs
and find out many wayouts.

Would like say I thing,

 TOM your r great would like to meet you once in my life.

      Rating: 4
Don't
      automaticlly run job after instance startup  May 30, 2003

Reviewer:  Minnie  from MA
Hi Tom,
Thank you for your excellent job.
I have a question about the automatic job running after startup.

as you said

......
that is not correct.  the job will execute shortly after startup.  The job
queues look for all jobs that SHOULD HAVE BEEN run and runs them.
.........

There is a job which cleans up a table every 4am. This table is critical to the
application during the daytime. I don't want this job automaticly runs when the
database startups after 8am if it's crashed before 4am.  How can I achive that?

Thanks again,

          Followup:

create procedure that_4am_job
as
begin
  if ( to_char(sysdate,'hh24') not in ( <list of acceptable hours to run ) )
  then
     maybe send an email to someone to let them know.
     return;
  end if;

  rest of code
end;

May
      31, 2003 shirley -- Thanks for the
      question regarding "Architecture Solutions, -- high availability", version
      8i

      You Asked
Hi Tom,

Currently, my company has 2 identical servers (in hardware
aspect) but serving different purposes:

1) slave server.

It is receiving replicated data (using Oracle replication tool
on HP-UX) from the master server. This server is protected under
the firewall with only 1 port opened to the master server.

2)  master server.

Connected to the slave server. It is replicating data to the
slave server.

My question here is how to make them of high availability.
Currently, we are using replication tools replicate data. Is it
possible to use a mixture of tools such as OPS (Oracle Parallel
Server), MC Service Guard (in Standby server), and Replication
Tool ( which we already have).

Could you advise on how we can come up with a high-availability
solution based on that?

Thanks in advance
Shirley

      and we said...
See
http://www.oracle.com/oramag/oracle/00-May/o30tom.html
for some more background information on this very
topic.

You can mix OPS and replication yes.  OPS in 8.1.6 has a new
"high availability" option.  It offers high available without
the normal OPS design issues you would have to typically
consider (that is, you can place this feature onto an existing
database that was not specifically designed for OPS).  See

http://technet.oracle.com/doc/oracle8i_816/paraserv.816/a76968/pshavdtl.htm#10876

and

http://technet.oracle.com/doc/oracle8i_816/paraserv.816/a76934/chap5.htm#1023930

for more info.

You can mix standby database with these features as well.  See

http://technet.oracle.com/doc/oracle8i_816/server.816/a76995/toc.htm
for more info on that.

As to which one is most appropriate for you -- only you can
truely answer that after evaluating the costs associated with
each (some of which the first link above address).
Review &
      Followup

      Rating: 3
high
      availability -- oracle 9.2.0.3  May 30, 2003

Reviewer:  Baqir Hussain  from Emeryville, CA USA

Tom,
We are looking to get a solution/advise how can we implement the following:

Production database is attached to a filer and web based application is running
in Java updating database in every second.  We also set up a physical standby.

Every 1 to 2 months new features were added -- that includes database change
(tables, columns add/delete/update/insert etc) and the java code as well.  That
requires 4 to 6 hours of production down time.  It's really impacting our
business.  We can not perform live because it will exert a whole lot of load.

We would like to achieve all the above with no down time as follows:
1. Production database should be up and updating.
2. Able to add new features (table,columns update/delete/insert etc)
3. Push new java code so that it can read new added feature
4. Add all new feature with new Java code to production databse.

Please advise is there any solution available to achieve it?
Thanks

          Followup:

you can design to accomidate it but in general application upgrades will involve
at least some window of downtime (you cannot for example have OLD code and NEW
code going against OLD structures and NEW structures at the same time)

You can use techniques like online redefinition to redefine the tables online
(so the adding/removal/conversion of columns/data will not be in your downtime)
but zapping your java code is going to require

a) stopping transactions
b) doing the last and final step in the dbms_redefinition process
c) starting back up.

One can see how you can do this with a very small window of downtime.  You
could:

a) stage another iAS instance with all of your new code off to the side
b) use the online redef to have your new structures waiting in the wings --
being syncronized with the old structures

c) shutdown old code, let transactions complete, finish redef

d) bring up new iAS instance with new code

now all you need to do is clean up the old junk. your downtime would be (c)
only.

May
      31, 2003 Saibabu -- Thanks for the
      question regarding "Several Basic Doubts", version 8.1.5

      You Asked
Hi Tom,
   Please take your time to answer my doubts.
1)I read the meanings of db_block_gets,consistent_gets and
db_block_changes.I was confused with what I read and output of
a query on V$sysstat view.In one session I have updated 473 rows
of number column having few blocks only.In another session I saw
about 1900,1500 increment in db_block_changes and
db_block_gets.Iam sure that no other sessions are
running.Generally db_block_gets should be more than
db_block_changes,but why this descripency?Since I have updated
only 473 rows atmost about 500 blocks should be get in current
mode or 500 blocks should be changed,How to explain this?What
exactly is CURRENT mode means?Even with queries also
db_block_gets are changing but it should be for updates only?

 2)All the hash bucket chains list,lru sets list will be stored
in the some of db buffers or DBWR private memory out of sga?
 3)In OS level does the datafile will be in OS block or Oracle
Block size.If it is Oracle Blocks size how the I-Node and other
OS level tracking will be done?
 4)On Solaris 2.6 or AIX 4 box how to find whether We are using
synchronous or Synchronous I/O.
 5)One of our Test is taking indefinite time for Normal shutdown
   but we are not running any transactions and it is very small
database.What could be the possible reasons.
 6)Why after shutdown abort,mounting database also taking some
time.I understand that Instance starting and mounting will take
same time always,only opening database depends on the last
shutdown mode.Please Explain?
 Thanks a Lot for your time
  Saibabu

      and we said...
So, you have a situation like this:

ops$tkyte@DEV816> create table t
  2  ( x number,
  3    y char(2000) default 'x'
  4  );
Table created.

ops$tkyte@DEV816> insert into t ( x )
  2  select rownum
  3    from all_objects where rownum < 500
  4  /
499 rows created.

ops$tkyte@DEV816> alter table t add
  2  constraint x_pk primary key(x)
  3  /
Table altered.

ops$tkyte@DEV816> create table tmp
  2  as
  3  select b.name, a.value
  4    from v$mystat a, v$statname b
  5   where a.statistic# = b.statistic#
  6     and ( b.name like 'db_block%'
  7           or
  8           b.name = 'consistent gets' )
  9  /
Table created.

ops$tkyte@DEV816> update t set x = x+10000
  2  /
499 rows updated.

ops$tkyte@DEV816> select t1.name, t2.value - t1.value
  2    from tmp t1,
  3         ( select b.name, a.value
  4             from v$mystat a, v$statname b
  5            where a.statistic# = b.statistic#
  6              and ( b.name like 'db_block%'
  7                    or
  8                    b.name = 'consistent gets' )
  9         ) t2
 10   where t1.name = t2.name
 11  /

NAME                           T2.VALUE-T1.VALUE
------------------------------ -----------------
consistent gets                              300
db block changes                            3116
db block gets                               2629

ops$tkyte@DEV816>

"db block changes" counts modifications made to current blocks.
This statistic counts the total number of changes that were made
to all blocks in the SGA that were part of an insert, update or
delete operation. These are changes that are generating redo log
entries and hence will be permanent changes to the database if
the transaction is committed.

"db block gets" counts logical reads in current mode. Also
includes direct read blocks (sort-on-disk) blocks.

"consistent gets" counts the logical read in consistent mode.
These are blocks read in "multi-versioned" mode.

When doing an update -- we need the block in CURRENT (non-read
consistent) mode.  We need it as it exists right now -- not as
it existed when the query began (as we do for selects).  We need
to get the block ORIGINALLY in consistent mode (hence the 300
blocks in consistent mode) to see what rows we SHOULD see and
then we need to get the block in current mode to make the
changes to that row.

I am guessing the number column is indexed (if we drop the
primary key on x, the numbers become:

NAME                           T2.VALUE-T1.VALUE
------------------------------ -----------------
consistent gets                              390
db block changes                            1245
db block gets                                591
)

What we are seeing there is we get a block in consistent mode -
discover it has about 2 rows on it and then need to to 2 block
gets to make the updates (one time for each row).  We make lots
of changes to the blocks (I made the number BIGGER hence the
data on the block is moving around quite a bit).  When the data
is indexed -- we have a lot more INDEX blocks to read and reread
in current mode (and change) and hence the numbers go up.

So, consistent gets = our consistent read.  we use that to find
the rows we should be seeing and hence updatings.

db block gets = getting the blocks as they exist RIGHT NOW.  we
can only modify those blocks -- not the blocks as they existed
when we started...

db block changes = count of all of the internal changes we made
that generated redo on those blocks.  Many blocks are counted 2,
3, 4, 5 or more times depending on the amount to times we had to
visit the block and what we had to do to it.

We still do db_block_gets for queries as well -- those are
mostly the result of reading extent maps from the data
dictionary (we don't want to know what USED to exist -- we need
to know what the data looks like NOW) and such.

#2 above does not make syntatic sense to me.  DBWR would neither
store hash bucket chains or buffers -- they are all in the SGA.
Many processes need to access that.

#3) the OS will store the file as it sees fit -- using its
technique.  At the OS level -- it'll be OS blocks.  We will use
Oracle blocks to perform our operations -- the OS will use OS
blocks to perform its operations.   inodes and such are in the
realm of the OS and hence would be done via its metrics.  We
don't change the way they do anything -- we just use "read" and
"write".

#4)
ops$tkyte@DEV816> show parameter async

NAME                                 TYPE    VALUE
------------------------------------ -------
------------------------------
disk_asynch_io                       boolean TRUE
tape_asynch_io                       boolean TRUE

#5) normal shutdown prevents others from logging in.  If anyone
remains in v$session, it'll wait indefinitely.  I believe you
want to use shutdown immediate.  that not only prevents new
logins but logs out people as well (terminates their
transactions, rolls them back and kicks them out).  There is a
shutdown transactional as well -- it is like shutdown immediate
but it waits for transactions to complete and then kicks them
out.

#6) you are seeing the time it takes for crash recovery to take
place.  In another window -- do a tail -f on your alert log
before you do the startup.  You'll see what we are doing during
the startup.  You'll see something like:

Database mounted in Exclusive Mode.
Completed: alter database  mount
Sat Sep 30 09:17:06 2000
alter database open
Beginning crash recovery of 1 threads
Sat Sep 30 09:17:06 2000
Thread recovery: start rolling forward thread 1
Recovery of Online Redo Log: Thread 1 Group 1 Seq 802 Reading
mem 0
  Mem# 0 errs 0: /log1/oradata/ora816dev/redo01.log
  Mem# 1.....

Review &
      Followup

      Rating: 4
More question
      on crash recovery   March 18, 2003
Reviewer:
      Chi  from Buchanan, NY USA
In the alert log file, I have seen the repeated message like:

Recovery of Online Redo Log: Thread 1 Group 3 Seq 4821 Reading mem 0
  Mem# 0 errs 0: /app0/oradata/maxz/system/redo3.log
Recovery of Online Redo Log: Thread 1 Group 3 Seq 4821 Reading mem 0
  Mem# 0 errs 0: /app0/oradata/maxz/system/redo3.log
Recovery of Online Redo Log: Thread 1 Group 3 Seq 4821 Reading mem 0
  Mem# 0 errs 0: /app0/oradata/maxz/system/redo3.log
Recovery of Online Redo Log: Thread 1 Group 3 Seq 4821 Reading mem 0
  Mem# 0 errs 0: /app0/oradata/maxz/system/redo3.log

The file is growing at a rate of 2M/min because of these messages. My question
is what caused this and how do I fix the problem. Thanks.

          Followup:

that would mean that recovery isn't completing I assume?  You'll want to open a
tar with support on that one.

      Rating: 3
asynch
      i/o  May 30, 2003
Reviewer:  Raja  from
      Chennai, India
i was seraching for asynch i/o on asktom, i could get only a couple of hits. So
i am posting this here if it is ok. Kindly help.

In one of our databases (Oracle version 8.0.5), we encounter frequent block
corruptions (ORA

1578). Fortunately, the corrupted blocks are related to index segments so we
drop them and

recreate the indexes. To rectify this issue, we have been thinking to do the
following.

(1) As the os is IBM AIX 4.3.0 and it supports asynchronous I/O. Is it ok to
turn on the

asynchronous I/O parameter at the OS level and also set disk_asynch_io = false.

(2) Presently this instance uses one dbwr process and do you suggest to have
more number of

dbwr processes? Is number of DBWR processes matter in this case.

(3) To have a good understanding of asynchronous I/O, could you please explain
what it is

and what it actually does? In the normal situation, DBWR writes the dirty blocks
to

datafiles. If I have async i/o turned on at the os level, DBWR gathers the dirty
blocks and

hands over to OS to write them to the datafiles? could you clarify please?

Our sys admins are looking at the system level (ex corruption as a result of the
disk controller etc). As a DBA, what i should do in the database level to
alleviate the block corruptions. Please don't ask me to upgrade to higher
version because that sugestion has been putforth to our management but of no
results. We have been asked to manage the databases as they are but with better
productivity.

In summary, what do you think about block corruptions and what should be the
best method to

resolve this issue. Thanks for your time.

          Followup:

you really need to figure out WHY you are getting block corruptions.  is it
software  or hardware related.  if software is it oracle or the os.  etc etc
etc.

then fix it.

async io probably has nothing at all to do with this, unless there is a bug in
your version of AIX -- but then you would patch that bug.

turn on the init.ora parameters to catch corruptions proactively.

May 31, 2003 Raj
      -- Thanks for the question regarding "Will there be performance difference
      by using bind variables in this?", version 8.1.7

      You Asked
Hi Tom,
If I have two cursors like this :

EXEC SQL DECLARE curglmap cursor for Select V_LINE_NUMBER, V_LINE_TYPE,
V_GL_CODE, V_MAPPING_TYPE,substr(v_gl_code,1,1),nvl(v_related_product_IE,'00') ,
nvl(v_c345_ie,'0')  from
line_glmap where v_mapping_type = 'G' and substr(v_branch_code,3,1) = '5' and
v_gl_code not in (select v_gl_code from dim_gl where f_customer = 'C' and
fic_mis_date = to_date(:H_column4,'YYYYMMDD'));

EXEC SQL DECLARE curglmapc cursor for Select V_LINE_NUMBER, V_LINE_TYPE,
V_GL_CODE, V_MAPPING_TYPE,substr(v_gl_code,1,1),nvl(v_related_product_IE,'00') ,
nvl(v_c345_ie,'0')  from
line_glmap where v_mapping_type = 'G' and     substr(v_branch_code,3,1) = '5'
and v_gl_code in (select v_gl_code from dim_gl where f_customer = 'C' and
fic_mis_date = to_date(:H_column4,'YYYYMMDD'));

First the cursor curglmap finishes the processing then curglmapc cursor
processed.. My question is should I also put bind variables instead of
v_mapping_type = 'G' and substr(v_branch_code,3,1) = '5' to enhance the
performance.

Your response will be much appreciated.

Thanks.
Raj

      and we said...
in static sql, you only bind that which changes.

No matter how many times you open curglmap or curglmapc -- they will ALWAYS and
forever use 5 and G

it is when doing dynamic sql that care must be taken to BIND values that change
from run to run of the cursor.  So, dynamic sql will in general have a mixture
of binds and literals.
Review & Followup

      Rating: 4
Will there be
      performance difference by using bind variables in this?  May
      30, 2003
Reviewer:  Raj  from Ireland
Tom, Thanks for the prompt response. One more query in the same then will the
time for fetching records be massively different if the cursor was coded as
Select V_LINE_NUMBER, V_LINE_TYPE, V_GL_CODE,
V_MAPPING_TYPE,substr(a.v_gl_code,1,1),nvl(v_related_product_IE,'00') ,
nvl(v_c345_ie,'0')  from
line_glmap a, dim_gl b where a.v_mapping_type = 'G' and
substr(a.v_branch_code,3,1) = '5' and a.v_gl_code = b.v_gl_code and b.f_customer
!= 'C' and a.fic_mis_date = to_date(:H_column4,'YYYYMMDD');

rather than EXEC SQL DECLARE curglmap cursor for Select V_LINE_NUMBER,
V_LINE_TYPE, V_GL_CODE,
V_MAPPING_TYPE,substr(v_gl_code,1,1),nvl(v_related_product_IE,'00') ,
nvl(v_c345_ie,'0')  from
line_glmap where v_mapping_type = 'G' and     substr(v_branch_code,3,1) = '5'
and v_gl_code not in (select v_gl_code from dim_gl where f_customer = 'C' and
fic_mis_date = to_date(:H_column4,'YYYYMMDD'));

          Followup:

the sql parser in my head didn't feel like trying to figure out what subtle
difference there is here.

You see, the two queries are not remotely the same.  one queries two tables, the
other one.

you would need to be more clear

      Rating: 4
Will there be
      performance difference by using bind variables in this?
        May 31, 2003
Reviewer:  Raj  from
      Ireland
I need to select all the records from line_glmap where the f_customer field for
the gl code in dim_gl does not have value 'C' in first cursor. The DIM_GL has
all the tpye General Ledger account defined. The join between this two tables
are v_gl_code. The DIM_GL has more records compared to line_glmap.

Therefore will the select fast if I use the join between these two tables rather
than using where v_gl_code in (select ....) commands.

Hope I do make sense in above. Looking forward for your response.

          Followup:

if you are using the CBO, and v_gl_code is NOT NULL the not in can be very
efficient.

but you know what -- benchmarking (done right) never lies.  grab runstats from
asktom.oracle.com/~tkyte and evaluate the two approaches with a couple thousand
iterations.

May 31, 2003 Dani
      -- Thanks for the question regarding "Reading file Using UTL_FILE...",
      version 8.0.5

      You Asked
I am using Oracle 8.0 and trying to read a ASCII file using
UTL_FILE package. I could find only one function to read
contents of a file i.e GET_LINE(). But this function has a
limitation. It can only read lines with maximum byte size of
1022.

Is there other work around to read more than 1022 bytes. Or any
other packages similar to UTL_FILE?

Can you suggest any other function which can read byte by byte
instead of line. ?

Please suggest an answer.
thank you

      and we said...
With 8.0.5 the limit is 32k.

If you describe the utl_file package, you will see that the
fopen procedure is overloaded - the second version takes a
parameter MAX_LINESIZE.  Set this to a higher number (upto about
32k)....

Review &
      Followup

      Rating: 3
Reading File In
      Part( paging)  April 01, 2003
Reviewer:  Vikas
      Sharma  from Delhi India
Hi Tom,

I have a problem. I am running a procedure which execute the sql commands and
add log into a file using utl_file. The procedure run for very long and create a
text file of about 400 to 500 mb. I want to read this file in part somthing like
paging method. For example i will first read 100 line then next 100 then next
100 and so on.

Kindly tell me the best way to achive. I display the the lines on browser. every
thing work fine for small file size about 1 mb and more.

What i would like to achive is paging when reading file. I have alredy adopted
your paging method when reading from tables. Kindly suggest.

Thanks,

Regards

Vikas Sharma

          Followup:

load the file into a table with a sequence per line and forget the file exists?

that would be easiest.

But you could use a BFILE instead in 8i as it supports "seeking", you would have
to use dbms_lob.instr and dbms_lob.substr and utl_raw.cast_to_varchar2 -- but it
would be doable.  Your web page would have to remember the offset to start
reading at again when it picked up.  see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1352202934074
for an example of what I mean.

In 9i, utl_file supports utl_file.fseek - so you could use that as well.  Just
remember where you were (fgetpos) and fseek there to resume paging.

      Rating:
      4
Thanks  April 03, 2003
Reviewer:  Vikas
      Sharma  from Delhi India
Hi Tom,

Thanks.

Regards,
Vikas Sharma

      Rating: 4
going back to
      previous rec wile reading a file  May 31, 2003

Reviewer:  srinivasa rao bachina  from bangalore

Hi tom ,
 i am having file with a set of recs like
  aa,fdds.................
  bb,.................
  bb,.................
  bb,.................
  aa,.............
  bb,...........
  bb,...........
where aa is the indication for master rec and bb for dtl rec
each master rec and recs followig after that but before another master belonfs
to one group.while processing the recs if any validation goes wrong i have to
put that group of recs(master rec+ detl recs) in a separate file,that is i have
to go back and read again the contents and again write to some other file,in
utl_file package there is no procedure/function to go back in file,how can we
solve this situation.
i am using oracle 8i releae 8.1.7.4.0
Please give your solution

Thanks
C nu

          Followup:

you will be writing a program to procedurally process this file in whatever
language you like.

      Rating: 5
Limit of opened
      files using utl_file  May 31, 2003
Reviewer:
      Reader  from Henry, Illinois
Tom,
Is there any limit on number of files can be opened for a given session?

          Followup:

yes, as determined by the operating system (and given that oracle itself has
files opened)

June 02, 2003 Aleksey -- Thanks for the question regarding
      "ORA-1031 in a stored procedure doing dynamic sql.", version 8.0.5

      You Asked
Access administration problem:
I created a package
..
Package Body        PKG_AXT_FILES
Is
…..
Procedure  create_file_info_table is
Begin
  begin
 dbms_utility.exec_ddl_statement ('truncate table server_file_info');
  exception
   when others then
    dbms_utility.exec_ddl_statement
                  ('create table server_file_info (file_name
varchar2(50),time_stamp date)');
  end;
Exception
    When OTHERS then
        dbms_output.put_line(sqlerrm);
End; -- Procedure
…
.. and ran it..

10:52:03 HUMQA>exec pkg_axt_files.create_file_info_table;
ORA-01031: insufficient privileges
ORA-00942: table or view does not exist

PL/SQL procedure successfully completed.

10:54:00 HUMQA>

Then  - in the same session:

10:54:00 HUMQA>exec  dbms_utility.exec_ddl_statement('create table
server_file_info (file_name  varchar2(50),time_stamp date)');

PL/SQL procedure successfully completed.

11:00:16 HUMQA>
…………..
11:01:50 HUMQA>exec dbms_utility.exec_ddl_statement('truncate table
server_file_info');

PL/SQL procedure successfully completed.

11:02:25 HUMQA>

What privileges do I need to make my package work?

Thanks.
With sincere admiration, Alex Titiyevskiy

      and we said...
see

http://osi.oracle.com/~tkyte/Misc/RolesAndProcedures.html
I cannot tell which line is causing the error, if it is the create table, you
need to be granted the CREATE TABLE priv directly, not via a role.

Review &
      Followup

      Rating: 4
RESOURCE
      role  June 20, 2001
Reviewer:  Jim B
       from Philly, PA USA
Good reply, even tho this subject get beaten to death almost every day.
Many times a user can create a table in sqlplus becausethey have been granted
the RESOURCE role.  As the link identifies, privs thru a role cannot be used in
PL/SQL

      Rating: 2
Even select
      access..??  May 31, 2003
Reviewer:
      A reader
Hi Tom,
I am using dynamic sql to access a view form some other users schema, i am
unable to do so unless i have select access on the table,
for eg user bis owner of table a & view a1

user A has select access on view A1 of user B schema, 'not' given via role.

Dynamic sql in a procedure invoked by users A gives insufficient
privilige.unless user a is given select access on table a

Is this expected behavaviour or something wrong.

cheers

          Followup:

expected, documented behavior

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html

      Rating:
      1
  June 01, 2003
Reviewer:
      A reader
Hi Tom,
I did read that, but as mentioned i hv given the select access directly to the
user and not via a role,
why does the user need a select access on the table too when accessing a view as
dynamic sql in a procedure,
dont u think it beats one of the very basic purposes of view.

cheers

          Followup:

Here is my test case that shows if A had been granted select on B.V by B and V
is a view, A can dynamically access it.  Where is yours that shows otherwise?

(try

SQL> set role none

and see if that view is still accessible, if it isn't - its a role)

ops$tkyte@ORA817DEV> select * from dba_sys_privs where grantee='PUBLIC';

no rows selected

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> drop user a cascade;

User dropped.

ops$tkyte@ORA817DEV> drop user b cascade;

User dropped.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create user a identified by a;

User created.

ops$tkyte@ORA817DEV> create user b identified by b;

User created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> grant create session, create procedure to a;

Grant succeeded.

ops$tkyte@ORA817DEV> grant create session, create table, create view to b;

Grant succeeded.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> alter user b default tablespace users quota unlimited on
users;

User altered.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> @connect b/b
ops$tkyte@ORA817DEV> set termout off
b@ORA817DEV> REM GET afiedt.buf NOLIST
b@ORA817DEV> set termout on
b@ORA817DEV>
b@ORA817DEV> create table t ( x int );

Table created.

b@ORA817DEV> insert into t values ( 1 );

1 row created.

b@ORA817DEV> create view v as select * from t;

View created.

b@ORA817DEV>
b@ORA817DEV> grant select on v to a;

Grant succeeded.

b@ORA817DEV>
b@ORA817DEV> @connect a/a
b@ORA817DEV> set termout off
a@ORA817DEV> REM GET afiedt.buf NOLIST
a@ORA817DEV> set termout on
a@ORA817DEV>
a@ORA817DEV> create procedure p
  2  as
  3          type rc is ref cursor;
  4          l_cursor rc;
  5          l_x      number;
  6  begin
  7          open l_cursor for 'select * from b.v';
  8          loop
  9                  fetch l_cursor into l_x;
 10                  exit when l_cursor%notfound;
 11                  dbms_output.put_line( l_x );
 12          end loop;
 13          close l_cursor;
 14  end;
 15  /

Procedure created.

a@ORA817DEV>
a@ORA817DEV> exec p
1

PL/SQL procedure successfully completed.

June
      02, 2003 Isaac -- Thanks for the
      question regarding "Deterministic function", version 8.1.7.3

      You Asked
Hi Tom,

Recently I created a function with DETERMINISTIC definition to get performance
on my query, but I check that the function is called every
time even that it receive the same input.

Here the script that I used to check this function:

CREATE OR REPLACE PACKAGE T2 AS
IND INTEGER;
END T2;
/
CREATE OR REPLACE
        function  Component_type
                ( i_blood_code          in varchar2 )   return varchar2
                DETERMINISTIC  is
        begin
          T2.IND := T2.IND + 1;
          if ( i_blood_code in ( 'WB', 'PC', 'PC BCP', 'GRAN' ) )  then
              return( 'RED_CELLS' );
            elsif( i_blood_code in ( 'PLT', 'SDP' ) )  then
              return( 'PLT' );
            elsif( i_blood_code in ( 'FFP', 'FFP CP', '2FFP', '2FFP CP', 'FP',
                                     '2FP' ) )  then
              return( 'PLASMA' );
            elsif( i_blood_code in ( 'CRYO', '2CRYO' ) )  then
              return( 'CRYO' );
            else  return( '' );
           end if;
        end;
/
create table ARCHIV_BLOOD_UNITS
( code varchar2(20))
/
BEGIN
  for i in 1..50 loop
    insert
    into archiv_blood_units
    values( 'PC' );
   end loop;
  commit;
  T2.IND := 1;
END;
/
SELECT COMPONENT_TYPE(CODE) FROM ARCHIV_BLOOD_UNITS WHERE ROWNUM<50
/
BEGIN
  dbms_output.put_line(T2.IND);
END;
/

The output that i received is: 50

Do you can help me, if I need a distinct definition or anything?

Thanks on advance

      and we said...
it may well be deterministic however -- CODE is changing for each and every row!
 We don't know that you put 50 of the same one in there, we evaluate the
function for each row..

deterministic is used for function based indexes.  I can make it so your query
calls component_type 0 times during the query using that feature.

http://asktom.oracle.com/~tkyte/article1/index.html

But, in your case -- I would NOT use a plsql function at all.  I would:

create view v
as
select archiv_blood_units.*,
       case when ( code in ( 'WB', 'PC', 'PC BCP', 'GRAN' )  )
            then 'RED_CELLS'
            when ( code in ( 'PLT', 'SDP' ) )
            then 'PLT'
            ....
            else NULL
       end COMPONENT_TYPE
 from archiv_blood_units
/

Review &
      Followup

      Rating:
      5
Determinism  December 19, 2002
Reviewer:
      Connor McDonald  from UK
I still don't see any particular benefit to the deterministic keyword. Yes,
you're telling Oracle some information that it needs to know to make it
candidate for FBI's, but we are still executing the function 'n' times when we
create the index.  All that the determinism keyword gives us is that if the
function is in fact not deterministic, then its our fault not Oracle's for
possibly getting a wrong answer.

I've not seen any example where Oracle has avoided having to run a deterministic
function based on previously calculated values except in the FBI case, where
we're effectively pre-calculating every possible return value in advance.

Maybe its just the semantics I don't like - the plsql manual implies that using
"deterministic" could give a function a performance "edge" over a
non-deterministic one.  It infers that if I call my function twice with the same
parameter value, it may avoid the second execution - something I've never
observed.

      Rating: 3
Understanding
      your response  December 22, 2002
Reviewer:
      Isaac Chocron  from Israel
Sorry,
I can't understand your response.
I insert the same value ('PC') 50 times, on the table
ARCHIV_BLOOD_UNITS (see the pl/sql block before the query statment), so the
function on the query receive 50 times the same CODE.
Also, it is a general function that I use on all my system on
differents SQL and PL/SQL uses.

          Followup:

And how does the database now that the data in row two is the same as the data
in row 1.  It does not and it does not know to call the function once.

The only thing deterministic is used for right now is:

a) to mark a function as a candidate to be used in a function based index
b) to make a function as a candidate to be used in a materialized view with
Query Rewrite

that is all.  It will NOT prevent it from being called twice on a two row table
that has the same value in both rows.

Here is a nice trick to reduce the number of times it is called.

select object_name, f(owner)
from
 all_objects

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.03          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     1962     21.21      20.79          0     145428          0       29404
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1964     21.23      20.83          0     145428          0       29404

select object_name, f_owner
  from all_objects, ( select owner, max(f(owner)) f_owner
                        from (select owner from all_objects group by owner)
                       group by owner ) b
 where all_objects.owner = b.owner

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     1962      4.03       4.06          0     287953          0       29404
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1964      4.03       4.06          0     287953          0       29404

that'll run the function once per value of owner instead of once per row.  In my
case, it was 29,000+ times in the first one and 34 in the second one.

But again -- use CASE, hide it in a view, it'll be many many times faster.

      Rating: 5
The
      doco  December 22, 2002
Reviewer:  Connor
      McDonald  from UK
Its this bit in the doco I don't like...

"This hint helps the optimizer avoid redundant function calls. If a stored
function was called previously with the same arguments, the optimizer can elect
to use the previous result"

          Followup:

Hey - it has an easy out "can elect", it just hasn't yet ;)

But actually it does.

select * from t where f(column) = 55;

if you index f(column), the optimizer may elect to use the previously computed
resut -- stored in the index.

Or, if you have a materialized view, it can elect to use the previously computed
result from that as well.

      Rating:
      3
-  December 23, 2002
Reviewer:  A
      reader  from BG
Tom,

This query ("select object_name, f_owner from all_objects, ( select owner,
max(f(owner)) f_owner from (select owner from all_objects group by owner)group
by owner ) b
 where all_objects.owner = b.owner") seems to have much higher Logical Reads
than the other one. The query with the function call has less Logical reads but
more response time.
Why do you prefer the query with higher Logical reads?

regards

          Followup:

because of this:

total     1964     21.23      20.83          0     145428          0       29404

total     1964      4.03       4.06          0     287953          0       29404

4 cpu seconds, versus 21.  The overhead of calling PLSQL from SQL 29,404 times
vs 34 times outweighed the LIO's.

And, if there were a more efficient way to get the answer -- we should use it.
For example, knowing that all_users has all possible owners if we add PUBLIC....
and takes lots less LIO's --

ops$tkyte@ORA920.US.ORACLE.COM> set autotrace traceonly statistics
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select object_name, f_owner
  2    from all_objects, ( select owner, max(f(owner)) f_owner
  3                          from (select owner from all_objects group by owner)
  4                         group by owner ) b
  5   where all_objects.owner = b.owner
  6  /

29417 rows selected.

Statistics
----------------------------------------------------------
         34  recursive calls
          0  db block gets
     288061  consistent gets
          0  physical reads
          0  redo size
    1070651  bytes sent via SQL*Net to client
      22070  bytes received via SQL*Net from client
       1963  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      29417  rows processed

ops$tkyte@ORA920.US.ORACLE.COM> select object_name, f_owner
  2    from all_objects, ( select username owner, max(f(username)) f_owner
  3                          from (select username from all_users
  4                                union all
  5                                select 'PUBLIC' from dual)
  6                         group by username ) b
  7   where all_objects.owner = b.owner
  8  /

29417 rows selected.

Statistics
----------------------------------------------------------
         53  recursive calls
          0  db block gets
     145665  consistent gets
          0  physical reads
          0  redo size
    1070651  bytes sent via SQL*Net to client
      22070  bytes received via SQL*Net from client
       1963  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      29417  rows processed

Remember -- I'm showing a technique here, apply it to your unique problem, with
your data!

      Rating: 3
what 's going
      on here?  May 28, 2003
Reviewer:
      A reader
why is this function executed over and over again:

function RunOnlyOnce return number deterministic
is
begin
  dbms_output.put_line('It''s me: ' || to_char(sysdate, 'dd.mm.yyyy
hh24:mi:ss'));
  return 1;
end RunOnlyOnce;

(or is it just the wrong way to verify the impact of the "deterministic" via
dbms_output?)

          Followup:

deterministic only counts in a function based index.  it is necessary to tell
the index that "given the number "5" as input, this function will consistently
return the same output"

it does not reduce the number of times it'll be called.

      Rating: 5
Taken from
      "Performance Tuning Guide and Reference"  May 31, 2003

Reviewer:  j.
as for "How the CBO Evaluates DETERMINISTIC Functions":

<quote>
Calls to a DETERMINISTIC function might be replaced by the use of an already
calculated value when the function is called multiple times within the same
query, or if there is a function-based index or a materialized view defined that
includes a relevant call to the function.
</quote>

      Rating:
      3
  June 02, 2003
Reviewer:  j.

tom, did I misunderstand the documentation or did I just do something that
prevented ORACLE from caching the return value? but what?
could you pls elaborate in which cases "might be replaced by the use of an
already calculated value" would take place?

          Followup:

they are used for function based indexes and materialized views if and when it
so deems it wants to.

You had neither.

June 02, 2003 rajesh -- Thanks for the question regarding
      "global database name", version 8.16

      You Asked
hi tom

what is the global database name  and what is it used for?
how can i change it? Would changing  it have any impact on the   database?

sincerely

rajesh

      and we said...
select global_name from global_name;

is the name.  It is used in a distributed database to enforce uniqueness across
all databases (so there is NO confusion as to which database is what).  You set
global_names = true and that makes it so the database link name must incorporate
the global_database name of the database you connect to.  In that fashion, all
databases with links must have UNIQUE names -- removes the ambiguity.

see the SQL language ref for the details but it is the:

alter database rename global_name ...

command.

Will it have an impact?  Sure, it could break database links that point to this
instance if you are using global naming.
Review &
      Followup

      Rating: 5
global_names in
      test  December 02, 2002
Reviewer:
      A reader  from Austin, TX
Sometimes I have found it convenient to setup test/development servers such that
when a database is refreshed from production to the test server, everything
already points to the right place.

For example the production database (DBX) may have a db link pointing to another
production database (DBY) defined as follows:

CREATE DATABASE LINK DBX2DBY.WORLD
CONNECT TO <user in DBY> IDENTIFIED BY <password of user in DBY> USING 'DBY' /

On my production server, call it DBX_PROD, is a TNS entry for DBY:

DBY =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = DBY_PROD)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SID = DBY)
    )
  )

When the test database is refreshed from production, say to server DBX_TEST, I
keep the same db link defined above and it still works fine since there is a TNS
entry for DBY like:

DBY =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = DBY_TEST)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SID = DBYT)
    )
  )

Basically, I have used the TNSNAMES file to alias my DBY test database.  I like
to refresh to a different SID because I feel safer that way.

If I set global_names = true, then the db link naming rule goes into effect and
I cannot use the above approach:

Connected.  (to DBXT)
SQL> select * from dual@dby.world;

D
-
X

1 row selected.

SQL> alter session set global_names = true;

Session altered.

SQL> select * from dual@dby.world;
select * from dual@dby.world
                   *
ERROR at line 1:
ORA-02085: database link DBY.WORLD connects to DBYT.WORLD

In your opinion, is global_names = true something I should be trying to use or
is it not that big of a deal?

Should I just set it true in production init.ora and false in test?

P.S.:  You'll give us all a heads up before the hefty (and just) AskTom
subscription fee goes into effect, won't you??

          Followup:

"Basically, I have used the TNSNAMES file to alias my DBY test database.  I like

to refresh to a different SID because I feel safer that way."

but -- by subverting global naming like that you stand the real risk of pointing
to the WRONG database by accident if someone mucks with your tnsnames.ora file,
or starts the database pointing to a different one.

This is exactly what global names is designed to avoid.  There is NO ambiguity.
Rather then playing hokey pokey with a tnsnames.ora file -- I would drop and
recreate the database links using global names.

Zero percent chance of ambiguity there.  Using global names would be safer then
relying on the tnsnames.ora file.  Relying on the tnsnames.ora file seems too
shaky -- too easy to mess up when you havent had quite enough sleep some night.
global names will just stop you.

In the end it is up to you -- but global names is there to make it un-ambigous.

Quick: tell me:

SQL> select * from dual@dby.world;

D
-
X

1 row selected.

what database was that?  What database did the data come from?  Even if you KNOW
the sid you connected to -- you cannot really tell unless you select * from
v$instance@dby.world or you edit the tnsnames.ora file that your server just
happens to be using.

      Rating: 3
Select
      DB-Name  December 03, 2002
Reviewer:  Marc
       from Switzerland
Hi

I use this statement for query db name:

select sys_context('userenv','db_name') db_name from dual;

It's nice.

          Followup:

great -- but that is not the global_name.

ops$tkyte@ORA920> select sys_context( 'userenv', 'db_name' ) sc, global_name
from global_name
  2  /

SC                   GLOBAL_NAME
-------------------- ------------------------------
ora920               FOOBAR.US.ORACLE.COM

and isn't useful for removing ambiguities in a distributed environment.

      Rating: 4
How to send
      global database name as parameter  March 10, 2003

Reviewer:  Mubarak Mnsour Elnour  from Yeman

Hi Tom

Can i send global database name as parameter

Example:

Create procedure x(p_g_name IN CHAR)
BEGIN
   SELECT * FROM SABANK.BRANCH_CODE@P_G_NAME;
END;

I execute this statement but i get error

          Followup:

that is not a global database name, that is a dblink and you would have to use
dynamic SQL as the query plan changes each and every time and you are able to
parameterize (bind) ONLY non-identifiers.  (avoid CHAR like the plague, just
pretend it doesn't exist, you'll never be sorry that way)

create procedure x( p_dbLink in VARCHAR2 )
as
   <variables>
begin
   execute immediate
   'select c1, c2, c3, ... from sabank.branch_code@' || p_dbLink
    into <variables>;

   .....

      Rating: 5
Direct
      connection - SQL PLUS  March 11, 2003
Reviewer:
      Thaha Hussain  from Manama, BAH
How can I connect directly to the schema and get the SQL> prompt -- if we know
username, password, machine name(ip) and global database name -- through SQL
*Plus?

All those things should be typed in through Host String Text Box.

(Suppose I don't have a Local Net Service Name and I don't want to add it all
the time)

          Followup:

you don't have enough information.

the global_name isn't used to connect, you need either

o the oracle_sid and oracle_home
o the service name under which this instance registered itself.

then, you can set up a tnsnames entry.

      Rating: 5
Direct
      connection - SQL PLUS  March 11, 2003
Reviewer:
      Thaha Hussain  from Manama, BAH
How can I connect directly to the schema and get the SQL> prompt -- if we know
username, password, machine name(ip) and global database name -- through SQL
*Plus?

All those things should be typed in through Host String Text Box.

(Suppose I don't have a Local Net Service Name and I don't want to add it all
the time)

      Rating: 5
Without a
      tnsnames entry...  March 15, 2003
Reviewer:
      Thaha Hussain  from Manama, BAH
Without a tnsname entry, is it possible? (I mean direct typing)

          Followup:

$ sqlplus
'scott/tiger@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=aria)(PORT=1
521)))(CONNECT_DATA=(SERVICE_NAME=ora9i)))'

all on one line

      Rating: 5
I actually
      tried without parenthesis  March 16, 2003

Reviewer:  Thaha Hussain  from Manama, BAH

Dear Tom,

I actually tried the host string without parenthesis and didn't work. Now it
works! Thanks!

Thaha Hussain

      Rating: 3
can't figure
      why is it using a diffrent global_name  June 02, 2003

Reviewer:  Alvin  from Philippines
i cant seem to login as other users except for internal.

This server is named luther.

my init.ora file
---
db_name = "rtbe"
db_domain =standby1
instance_name =standby1
service_names = rtbe.standby1
lock_name_space = standby1
---

my tnsnames.ora
---
RTBE=
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = luther )(PORT = 1521))
    )
    (CONNECT_DATA =
(SERVICE_NAME = rtbe.standby1)
    )
  )

RTBE.CRONOS =
 (DESCRIPTION =
   (ADDRESS_LIST =
     (ADDRESS = (PROTOCOL = TCP)(HOST = cronos)(PORT = 1521))
   )
   (CONNECT_DATA =
     (SERVICE_NAME = rtbe.cronos)
   )
 )

---

my listener.ora
---
LISTENER =
  (DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS_LIST =
        (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC))
      )
      (ADDRESS_LIST =
        (ADDRESS = (PROTOCOL = TCP)(HOST = luther )(PORT = 1521))
      )
    )
    (DESCRIPTION =
      (PROTOCOL_STACK =
        (PRESENTATION = GIOP)
        (SESSION = RAW)
      )
      (ADDRESS = (PROTOCOL = TCP)(HOST =  luther )(PORT = 2481))
    )
  )

SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (SID_NAME = PLSExtProc)
      (ORACLE_HOME = /export/home/ORACLE_HOME/rtbe/app/oracle/product/8.1.7)
      (PROGRAM = extproc)
    )
    (SID_DESC =
      (GLOBAL_DBNAME = rtbe.standby1)
      (ORACLE_HOME = /export/home/ORACLE_HOME/rtbe/app/oracle/product/8.1.7)
      (SID_NAME = standby1)
    )
  )

---
SVRMGR> select * from global_name;
GLOBAL_NAME
----------------------
RTBE.CRONOS

why is it using the other db's global name ? and how come the alter database
rename global_name to <new global_name> taking too long (hang is more like it)?

i did alter something in the tnsnames.ora and forgot to comment out the original
string now i can't connect via sqlplus or sqlplus sys/pass on my server console.

i can however connect via internal.

          Followup:

you've no example with the error code for us to look at.

      Rating: 5
got
      fixed.  June 02, 2003
Reviewer:  Alvin
       from Philippines
before leaving the office yesterday did tinker with the settings again.. and now
it works. Still don't what i did though. Will pay much much more attention in
the future.

what boggles me is below...

listener.ora
---

(SID_DESC =
      (GLOBAL_DBNAME = rtbe.standby1)
      (ORACLE_HOME = /export/home/ORACLE_HOME/rtbe/app/oracle/product/8.1.7)
      (SID_NAME = standby1)
    )
  )

---
SVRMGR> select * from global_name;
GLOBAL_NAME
----------------------
RTBE.CRONOS
---

global_dbname is it the same as global_name ??

if i issue the 'alter database rename global_name = rtbe.luther' do i need to
change the listener.ora file and issue a reload on lsnrctl ?

i have another server that uses rtbe.cronos as its global_name and i don't want
to get confused and drop something. I have to 'select name from v$datafile' to
check the directory structure to see which db i'm logged in to and its getting a
bit annoying.

Who has higher precedence service_name or global_name ?

June 02, 2003 Kamal -- Thanks for the question regarding "case
      sensitive password", version 817

      You Asked
Hi Tom,
Is Oracle database login password case sensitive?
If not, can it be made case sensitive?
Thanks,

      and we said...
Yes you can.

No, I would definitely NOT recommend it -- many tools would not "support" it.

You simply use quoted identifiers:

alter user scott identified by "tiger"

but now, you must use "tiger" in order to log in, not just tiger, "tiger".  And
therein lies the rub, many 3rd party tools will not work well with that concept.
June 02, 2003 Steve -- Thanks for the question regarding
      "Views", version 9.0.1.4

      You Asked
Hi Tom

How do views work as far as the optimiser is concerned?  For example, I have
test_view that joins about 5 tables it looks like this:

ltop_owner@EU0084P> create or replace view test_view as
  2  select o.CLLOCN,
  3  mp.trcod,
  4  o.CONAME,
  5  o.ADDLINE1,
  6  o.ADDLINE2,
  7  o.ADDLINE3,
  8  o.ADDLINE4,
  9  o.TOWN,
 10  o.COUNTY,
 11  o.POSTCODE,
 12  o.TEL,
 13  o.FAX,
 14  o.EMAIL,
 15  o.SITESIZE,
 16  o.DMSALUTE,
 17  o.DMFIRSTN,
 18  o.DMLASTN,
 19  o.DMTITLE,
 20  o.VENDINDIC,
 21  (case when o.archive_flag='Y' then 'Archive'
 22             when c.cllocn IS NOT NULL then 'Customer'
 23        else 'Prospect'
 24        end)  STATUS,
 25  (case
 26           when c.trcods IN ('H03','H05') then
 27          'Field Health Account'
 28    when ch.cllocn IS NOT NULL then 'Central Health Account'
 29       when c.cllocn IS NOT NULL then 'Field Sales Direct'
 30       else o.rtm
 31       end)  RTM,
 32  o.HEADOFFICE,
 33  case when o.cllocn=o.dmcllocn then  count(*) over (partition by o.dmcllocn
) else 0 end sites,
 34  o.MAILSTOP,
 35  o.SIC1992,
 36  o.AREACODE,
 37  o.LASTACT,
 38  o.LASTMAIL,
 39  o.TCONFLAG,
 40  o.TCONFLAG2,
 41  o.TCONFLAG3,
 42  o.TCONFLAG4,
 43  o.RINGBACKS,
 44  n.nacod,
 45  o.EBYEAR,
 46  o.EBPERIOD,
 47  o.KLIXMACH,
 48  o.FLAVBREW,
 49  o.DMTEL,
 50  o.DMCLLOCN,
 51  o.DMDATE,
 52  o.DMCONTROL,
 53  o.BUSTYPE,
 54  o.BUSSUBT,
 55  o.DMDEPT,
 56  o.DUNNSNR,
 57  o.DMCHECK,
 58  o.CCSDATE,
 59  c.csorgc ORGCODE,
 60  o.SUBCODE,
 61  o.EQPFLAG,
 62  o.FACTFLAG,
 63  o.FNOTEFLAG,
 64  o.CONTFLAG,
 65  o.MOBILE_NO,
 66  o.CATERER,
 67  o.OPERATOR,
 68  o.COMPETITOR,
 69  o.COMPMACH,
 70  o.CURRVEND,
 71  o.LBMSIC,
 72  o.LBMEMPBAND,
 73  o.LBMCOMPDUPEKEY,
 74  o.LBMCONTDUPEKEY,
 75  o.LBMSUPP,
 76  o.LBMCHANGE,
 77  o.LBM_LM_SCORE
 78  from opportunity o,
 79       client c, master_postcode mp, national n, central_health ch
 80  where   mp.areacode=o.areacode
 81  AND     c.csorgc=n.csorgc(+)
 82  AND     c.cllocn(+)=o.cllocn
 83* AND     ch.cllocn(+)=o.cllocn
ltop_owner@EU0084P> /

View created.

ltop_owner@EU0084P> analyze table opportunity compute statistics for table for
all indexes for all indexed columns;

Table analyzed.

ltop_owner@EU0084P> ed
Wrote file afiedt.buf

  1* analyze table client compute statistics for table for all indexes for all
indexed columns
ltop_owner@EU0084P> /

Table analyzed.

ltop_owner@EU0084P> ed
Wrote file afiedt.buf

  1* analyze table master_postcode compute statistics for table for all indexes
for all indexed columns
ltop_owner@EU0084P> /

Table analyzed.

ltop_owner@EU0084P> ed
Wrote file afiedt.buf

  1* analyze table national compute statistics for table for all indexes for all
indexed columns
ltop_owner@EU0084P> /

Table analyzed.

ltop_owner@EU0084P> ed
Wrote file afiedt.buf

  1* analyze table central_health compute statistics for table for all indexes
for all indexed columns
ltop_owner@EU0084P> /

Table analyzed.

ltop_owner@EU0084P> set autotrace on
ltop_owner@EU0084P> select * from test_view where cllocn=1;

    CLLOCN TRC CONAME                                             ADDLINE1
                                    A
---------- --- --------------------------------------------------
-------------------------------------------------- -
  COMPMACH CURRVEND                       LBMS LBMEMPBAND           LBMCOMPD
LBMCONTD LBMSU L LBM_LM_SCORE
---------- ------------------------------ ---- -------------------- --------
-------- ----- - ------------
         1 A23

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=38280 Card=162842 By
          tes=119037502)

   1    0   VIEW OF 'TEST_VIEW' (Cost=38280 Card=162842 Bytes=11903750
          2)

   2    1     WINDOW (SORT) (Cost=38280 Card=162842 Bytes=42176078)
   3    2       NESTED LOOPS (OUTER) (Cost=19769 Card=162842 Bytes=421
          76078)

   4    3         HASH JOIN (OUTER) (Cost=19769 Card=162842 Bytes=4136
          1868)

   5    4           HASH JOIN (OUTER) (Cost=13600 Card=162842 Bytes=40
          221974)

   6    5             HASH JOIN (Cost=1406 Card=162842 Bytes=38267870)
   7    6               INDEX (FAST FULL SCAN) OF 'MP_IDX1' (UNIQUE) (
          Cost=1 Card=2987 Bytes=20909)

   8    6               TABLE ACCESS (FULL) OF 'OPPORTUNITY' (Cost=432
           Card=168064 Bytes=38318592)

   9    5             TABLE ACCESS (FULL) OF 'CLIENT' (Cost=156 Card=4
          4170 Bytes=530040)

  10    4           TABLE ACCESS (FULL) OF 'NATIONAL' (Cost=1 Card=161
           Bytes=1127)

  11    3         INDEX (UNIQUE SCAN) OF 'CENTRAL_HEALTH_PK' (UNIQUE)

Statistics
----------------------------------------------------------
          7  recursive calls
         50  db block gets
     178455  consistent gets
      68484  physical reads
          0  redo size
       4125  bytes sent via SQL*Net to client
        425  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          0  sorts (memory)
          1  sorts (disk)
          1  rows processed

ltop_owner@EU0084P> select o.CLLOCN,
  2  mp.trcod,
  3  o.CONAME,
  4  o.ADDLINE1,
  5  o.ADDLINE2,
  6  o.ADDLINE3,
  7  o.ADDLINE4,
  8  o.TOWN,
  9  o.COUNTY,
 10  o.POSTCODE,
 11  o.TEL,
 12  o.FAX,
 13  o.EMAIL,
 14  o.SITESIZE,
 15  o.DMSALUTE,
 16  o.DMFIRSTN,
 17  o.DMLASTN,
 18  o.DMTITLE,
 19  o.VENDINDIC,
 20  (case when o.archive_flag='Y' then 'Archive'
 21             when c.cllocn IS NOT NULL then 'Customer'
 22        else 'Prospect'
 23        end)  STATUS,
 24  (case
 25           when c.trcods IN ('H03','H05') then
 26          'Field Health Account'
 27    when ch.cllocn IS NOT NULL then 'Central Health Account'
 28       when c.cllocn IS NOT NULL then 'Field Sales Direct'
 29       else o.rtm
 30       end)  RTM,
 31  o.HEADOFFICE,
 32  case when o.cllocn=o.dmcllocn then  count(*) over (partition by o.dmcllocn
) else 0 end sites,
 33  o.MAILSTOP,
 34  o.SIC1992,
 35  o.AREACODE,
 36  o.LASTACT,
 37  o.LASTMAIL,
 38  o.TCONFLAG,
 39  o.TCONFLAG2,
 40  o.TCONFLAG3,
 41  o.TCONFLAG4,
 42  o.RINGBACKS,
 43  n.nacod,
 44  o.EBYEAR,
 45  o.EBPERIOD,
 46  o.KLIXMACH,
 47  o.FLAVBREW,
 48  o.DMTEL,
 49  o.DMCLLOCN,
 50  o.DMDATE,
 51  o.DMCONTROL,
 52  o.BUSTYPE,
 53  o.BUSSUBT,
 54  o.DMDEPT,
 55  o.DUNNSNR,
 56  o.DMCHECK,
 57  o.CCSDATE,
 58  c.csorgc ORGCODE,
 59  o.SUBCODE,
 60  o.EQPFLAG,
 61  o.FACTFLAG,
 62  o.FNOTEFLAG,
 63  o.CONTFLAG,
 64  o.MOBILE_NO,
 65  o.CATERER,
 66  o.OPERATOR,
 67  o.COMPETITOR,
 68  o.COMPMACH,
 69  o.CURRVEND,
 70  o.LBMSIC,
 71  o.LBMEMPBAND,
 72  o.LBMCOMPDUPEKEY,
 73  o.LBMCONTDUPEKEY,
 74  o.LBMSUPP,
 75  o.LBMCHANGE,
 76  o.LBM_LM_SCORE
 77  from opportunity o,
 78       client c, master_postcode mp, national n, central_health ch
 79  where   mp.areacode=o.areacode
 80  AND     c.csorgc=n.csorgc(+)
 81  AND     c.cllocn(+)=o.cllocn
 82  AND     ch.cllocn(+)=o.cllocn
 83  and o.cllocn=1;

    CLLOCN TRC CONAME                                             ADDLINE1

---------- --- --------------------------------------------------
------------------------------------------
  COMPMACH CURRVEND                       LBMS LBMEMPBAND           LBMCOMPD
LBMCONTD LBMSU L LBM_LM_SCORE
---------- ------------------------------ ---- -------------------- --------
-------- ----- - ------------
         1 A23

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=8 Card=1 Bytes=259)
   1    0   WINDOW (SORT) (Cost=8 Card=1 Bytes=259)
   2    1     NESTED LOOPS (OUTER) (Cost=6 Card=1 Bytes=259)
   3    2       NESTED LOOPS (OUTER) (Cost=5 Card=1 Bytes=252)
   4    3         NESTED LOOPS (Cost=4 Card=1 Bytes=240)
   5    4           NESTED LOOPS (OUTER) (Cost=3 Card=1 Bytes=233)
   6    5             TABLE ACCESS (BY INDEX ROWID) OF 'OPPORTUNITY' (
          Cost=3 Card=1 Bytes=228)

   7    6               INDEX (UNIQUE SCAN) OF 'OPP_PK' (UNIQUE) (Cost
          =2 Card=168064)

   8    5             INDEX (UNIQUE SCAN) OF 'CENTRAL_HEALTH_PK' (UNIQ
          UE)

   9    4           TABLE ACCESS (BY INDEX ROWID) OF 'MASTER_POSTCODE'
           (Cost=1 Card=1 Bytes=7)

  10    9             INDEX (UNIQUE SCAN) OF 'PCM_PK' (UNIQUE)
  11    3         TABLE ACCESS (BY INDEX ROWID) OF 'CLIENT' (Cost=1 Ca
          rd=1 Bytes=12)

  12   11           INDEX (UNIQUE SCAN) OF 'CLI_PK' (UNIQUE)
  13    2       TABLE ACCESS (BY INDEX ROWID) OF 'NATIONAL' (Cost=1 Ca
          rd=1 Bytes=7)

  14   13         INDEX (RANGE SCAN) OF 'NATIONAL_CSORGC_IDX' (NON-UNI
          QUE)

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

Cheers

Steve

      and we said...
Ahh, that is because the two queries you have above are as different as night
and day.  They return distinctly different ANSWERS!

A query such as:

select ... ANALYTICS ...
  from ...
 where <joins>
   AND PREDICATES TO REDUCE ROWS

is very very very different from

select *
  from ( select ... ANALYTICS ....
           from ....
          where <joins> )
 where PREDICATES TO REDUCE ROWS

You see, analytics happen AFTER the where clause.  By pushing the predicate down
as you did in your second query, you materially changed the result set produced.

Here is a small example you can use to see the difference:

create these objects:

create table a ( pk int primary key, othercol int );
create table b ( pk int primary key, col int );
create table c ( pk int primary key, somecol int, col int );
create table d ( somecol int, othercol int, col int );
create index d_idx on d(somecol);
create table e ( othercol int, col int );
create index e_idx on e(othercol);

create or replace view v as
select a.pk a, b.col b, c.col c, d.col d, e.col e,
       case when a.pk = a.othercol then count(*) over (partition by a.othercol)
else 0 end sites
from a, b, c ,d, e
where a.pk=b.pk(+)
and   a.pk=c.pk
and   c.somecol=d.somecol
and   d.othercol=e.othercol(+);

column min_oid new_val ID
select min(object_id) min_oid from all_objects;

insert into a select object_id, &ID from all_objects;
insert into b select object_id, object_id from all_objects;
insert into c select object_id, object_id, object_id from all_objects;
insert into d select object_id, object_id, object_id from all_objects;
insert into e select object_id, object_id from all_objects;
analyze table a compute statistics for table for all indexes for all indexed
columns;
analyze table b compute statistics for table for all indexes for all indexed
columns;
analyze table c compute statistics for table for all indexes for all indexed
columns;
analyze table d compute statistics for table for all indexes for all indexed
columns;
analyze table e compute statistics for table for all indexes for all indexed
columns;

Now, run the following queries:

ops$tkyte@ORA920> select * from v where a = &ID;

         A          B          C          D          E      SITES
---------- ---------- ---------- ---------- ---------- ----------
         3          3          3          3          3      30579

ops$tkyte@ORA920>
ops$tkyte@ORA920> select *
  2    from (
  3  select a.pk a, b.col b, c.col c, d.col d, e.col e,
  4         case when a.pk = a.othercol then count(*) over (partition by
a.othercol) else 0 end sites
  5  from a, b, c ,d, e
  6  where a.pk=b.pk(+)
  7  and   a.pk=c.pk
  8  and   c.somecol=d.somecol
  9  and   d.othercol=e.othercol(+)
 10        )
 11  where a = &ID;

         A          B          C          D          E      SITES
---------- ---------- ---------- ---------- ---------- ----------
         3          3          3          3          3      30579

ops$tkyte@ORA920>
ops$tkyte@ORA920> select a.pk a, b.col b, c.col c, d.col d, e.col e,
  2         case when a.pk = a.othercol then count(*) over (partition by
a.othercol) else 0 end sites
  3  from a, b, c ,d, e
  4  where a.pk=b.pk(+)
  5  and   a.pk=c.pk
  6  and   c.somecol=d.somecol
  7  and   d.othercol=e.othercol(+)
  8  and   a.pk = &ID;

         A          B          C          D          E      SITES
---------- ---------- ---------- ---------- ---------- ----------
         3          3          3          3          3          1

See the difference?  if a.pk is a primary key -- that count(*) analytic you are
using will either return 0 or 1  and never any other number under the sun with
the last query.

So, the question is -- which answer is correct?
Review &
      Followup

      Rating:
      5
Thanks  June 02, 2003
Reviewer:
      Steve  from UK
Ah, of course!  I started to think it must be the analytics when I tried to
return rows without the analytic part and the response was the same whether from
the view or query!  The issue I now have is that the first answer is the correct
one not the second but the users will query and get the second result.  Argghh!!
 Should I lose the analytics and replace it with-
(select count(*) from opportunity op where o.cllocn=op.dmcllocn) sites....  ?

          Followup:

in this case, that may well be very appropriate (should have suggested it).
You'll find the plan in that case to be much more aggreeable I believe.

June 02, 2003 Murali -- Thanks for the question regarding "Fast
      refresh is taking longer than complete refresh of materialzed view",
      version 8.1.7.4

      You Asked
Fast refresh is taking longer than complete refresh of materialzed view, could
you tell me why? I checked metallink, it was a bug in 8.1.6 and fixed in 8.1.7.

      and we said...
fast refresh should be called "changes only" refresh.  The "fast" is a misnomer
if ever there were one.

A complete refresh works by

o truncate
o insert /*+ append */ the query

A 'fast' refresh works by

o row by row by row by row by row -- redo the operation, do it logged, do it
using just conventional SQL.

So, if you change a small % of a big table, "fast" might be faster -- but there
are just as many times when "complete" is faster then "fast".  Hence my vote to
rename "fast" to "changes only" (which would make it mean the same thing as
"complete" -- think about it, we have "complete" and "fast", why not "fast and
slow"?  -- because they aren't that way.  "complete and changes only" -- that
makes sense)
Review & Followup

      Rating: 4
refresh
      interval  June 01, 2003
Reviewer:  Reader
       from Houston, TX
also, it depends on the refresh interval. is it not?

          Followup:

what depends?  the definition?

The bottom line is how they are done:

o truncate + insert append

vs

o row by row

you can make a "changes only" take less time by decreasing the interval between
refreshes maybe - but the cumulative time....  six one way half dozen the other.

      Rating: 3
Rate of data
      change  June 02, 2003
Reviewer:  Reader
       from UK
Refresh interval is perhaps not as important as "how quickly the data changes"
and how much changes in one hit. For some data sets 'fast' never is quicker than
complete.

Question: For a partitioned MV are there any differences in mechanism for MV
refresh? - is there a route like "truncate partition / insert (append) into" for
just the changed partition. This would be useful in a DW context

          Followup:

see

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96520/mv.htm#45519
for whats available with regards to that.

      Rating: 5
Ohhhhh.
      !  June 02, 2003
Reviewer:  Reader
       from UK
Thanks Tom, a timely reminder to "Just read the Documentation"

      Rating:
      5
  June 02, 2003
Reviewer:
      A reader
Tom,

Do you help in writing Oracle doc's or is it some other group..

Thanks..

          Followup:

that is a different group

June 02, 2003 --
      Thanks for the question regarding "Moving PL/SQL records from a PL/SQL
      table to a cursor.", version 8.1.7

      You Asked
How do I move PL/SQL records from a PL/SQL (or Index by) table to a cursor. The
user is using VB6 and wants to receive the data as he has been in all other
cases, as a cursor. Both a VARRAY and a nested table seem like a possible route,
but the VARRAY is for small amounts of data, and the nested table does not
maintain sequence.

Here is the incomplete function from the package body I have so far. As you can
see there is simply no code written to facilitate the return of the data to the
user.

The process that is taking place is finding ranges in a non-sequential list of
item numbers. A certain item type can be from 40-100, then 95-145, and also
150-160. I do not have to group all the item types together, I only have to show
the starting and ending record in each individual range.

Thank you for your attention in this matter.

    FUNCTION get_ranges
      (ranges_out OUT item_numbers_cur)
    RETURN INTEGER

    IS

      local_numbers_cur item_number_methods.item_numbers_cur;
      local_hold_tab item_number_methods.hold_table;
      hold_rec item_numbers%ROWTYPE;
      current_id NUMBER;
      current_used NUMBER;
      current_type NUMBER;
      counter NUMBER;

    BEGIN

        counter := 1;

        OPEN local_numbers_cur FOR
        SELECT item_number_id, used, item_type
        FROM eqdv.item_numbers;

        FETCH local_numbers_cur INTO hold_rec;

        current_id := hold_rec.item_number_id;

                local_hold_tab(counter) := hold_rec;

    WHILE local_numbers_cur%FOUND
    LOOP

         IF current_id != hold_rec.item_number_id THEN

             counter := counter + 1;

            local_hold_tab(counter).item_number_id := current_id;
            local_hold_tab(counter).used := current_used;
            local_hold_tab(counter).item_type := current_type;

             counter := counter + 1;

                local_hold_tab(counter) := hold_rec;

           FETCH local_numbers_cur INTO hold_rec;

        ELSE

            current_id := hold_rec.item_number_id;
            current_used := hold_rec.used;
            current_type := hold_rec.item_type;

         FETCH local_numbers_cur INTO hold_rec;

        END IF;

    END LOOP;

    CLOSE local_numbers_cur;

      and we said...
You are applying the rules:

o varrays for small numbers of ordered things
o nested tables for infinite numbers of unordered things

which are for PERSISTENTLY stored varrays and nested tables to a variable
instance of a varray and nested table.  If you are using a plsql variable -- the
only real difference between a varray and nested table is that one is bounded
(varrays) and the other is not.

Here is how you would do this:

ops$tkyte@ORA8I.WORLD> create type myRecordType as object
  2  ( seq int,
  3    a int,
  4    b varchar2(10),
  5    c date
  6  )
  7  /

Type created.

ops$tkyte@ORA8I.WORLD> create table t ( x int, y varchar2(10), z date );

Table created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> create or replace type myTableType
  2  as table of myRecordType
  3  /

Type created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> create or replace function my_function return myTableType
  2  is
  3      l_data myTableType;
  4  begin
  5      l_data := myTableType();
  6
  7      for i in 1..50
  8      loop
  9          l_data.extend;
 10          l_data(i) := myRecordType( i, i, 'row ' || i, sysdate+i );
 11      end loop;
 12      return l_data;
 13  end;
 14  /

Function created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select *
  2    from TABLE ( cast( my_function() as mytableType ) )
  3   order by seq
  4  /

       SEQ          A B          C
---------- ---------- ---------- ---------
         1          1 row 1      23-MAR-01
         2          2 row 2      24-MAR-01
         3          3 row 3      25-MAR-01
         4          4 row 4      26-MAR-01
         5          5 row 5      27-MAR-01
         6          6 row 6      28-MAR-01
         7          7 row 7      29-MAR-01
         8          8 row 8      30-MAR-01
         9          9 row 9      31-MAR-01
        10         10 row 10     01-APR-01
        11         11 row 11     02-APR-01
        12         12 row 12     03-APR-01
        13         13 row 13     04-APR-01
        14         14 row 14     05-APR-01
        15         15 row 15     06-APR-01
        16         16 row 16     07-APR-01
        17         17 row 17     08-APR-01
        18         18 row 18     09-APR-01
        19         19 row 19     10-APR-01
        20         20 row 20     11-APR-01
        21         21 row 21     12-APR-01
        22         22 row 22     13-APR-01
        23         23 row 23     14-APR-01
        24         24 row 24     15-APR-01
        25         25 row 25     16-APR-01
        26         26 row 26     17-APR-01
        27         27 row 27     18-APR-01
        28         28 row 28     19-APR-01
        29         29 row 29     20-APR-01
        30         30 row 30     21-APR-01
        31         31 row 31     22-APR-01
        32         32 row 32     23-APR-01
        33         33 row 33     24-APR-01
        34         34 row 34     25-APR-01
        35         35 row 35     26-APR-01
        36         36 row 36     27-APR-01
        37         37 row 37     28-APR-01
        38         38 row 38     29-APR-01
        39         39 row 39     30-APR-01
        40         40 row 40     01-MAY-01
        41         41 row 41     02-MAY-01
        42         42 row 42     03-MAY-01
        43         43 row 43     04-MAY-01
        44         44 row 44     05-MAY-01
        45         45 row 45     06-MAY-01
        46         46 row 46     07-MAY-01
        47         47 row 47     08-MAY-01
        48         48 row 48     09-MAY-01
        49         49 row 49     10-MAY-01
        50         50 row 50     11-MAY-01

50 rows selected.

ops$tkyte@ORA8I.WORLD>

the SEQ attribute and ORDER BY seq are probably redundant -- I've Never seen it
come back in any order other then the natural index order however, we do not
promise that that is the case so better safe then sorry.

Your solution is to use a SQL NESTED table type (do NOT put the types into a
plsql spec - that will not work).  This works upto the amount of RAM you have on
your machine.
Review & Followup

      Rating:
      5
  March 22, 2001
Reviewer:
      A reader

      Rating: 4
Excellent
      approach  March 22, 2001
Reviewer:  Raj
       from United States
I didn't know we could write a code like this. I'm always learning from Tom,The
Great Guy.

      Rating:
      5
  March 23, 2001
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating:
      5
  March 29, 2001
Reviewer:  John
       from CT, USA

      Rating: 5
Why the rable
      't'  April 30, 2003
Reviewer:  Chandra
      S.Reddy  from India
Hi Tom,
This approach is very nice.
But I did not understand one point in your explanation.
Why do we create a table t in the statement"create table t ( x int, y
varchar2(10), z date );"

I think we can run the sample( I did this), even without creating the above
table.

Pl. correct me if I am missing anything here.

          Followup:

yup, t was not used in this example.  I modified an earlier example where T had
data and we "bulk fetched" it into the collection.  not relevant for this test.

      Rating:
      5
shiflett  April 30, 2003
Reviewer:
      steve shiflett  from Petaluma, CA
very clever!  The cool thing about your answers is that I can always build upon
them to solve my own problems.

      Rating: 5
Collection
      Problems  June 02, 2003
Reviewer:  Matt T
       from UK
Tom, are you aware of any problems using database types and collections with
regard to scaleability and number of users - possibly to do with latches in the
database.

Kind of cryptic question, but we've been warned off using them in our
application development - we want to use it as a temporary store for around 10K
records, to allow SQL manipulation.

Thanks in advance.

          Followup:

ohh - for 10k records, thats way above where I'd go with an in memory
collection.

I'd be looking at global temporary tables -- even though they'll latch a bit
more.  Consider:

ops$tkyte@ORA920> create or replace type myScalarType as object
  2  ( x int,
  3    y date,
  4    z varchar2(80)
  5  )
  6  /
Type created.

ops$tkyte@ORA920> create or replace type myArrayType as table of myScalarType
  2  /
Type created.

ops$tkyte@ORA920> drop table gtt;
Table dropped.

ops$tkyte@ORA920> create global temporary table gtt ( x int, y date, z
varchar2(80) );
Table created.

ops$tkyte@ORA920> exec runstats_pkg.rs_start;
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> insert into gtt
  2  select object_id, created, object_name
  3    from all_objects
  4   where rownum <= 10000;
10000 rows created.

ops$tkyte@ORA920> begin
  2          for i in 1 .. 50
  3          loop
  4                  for x in ( select count(*) from gtt )
  5                  loop
  6                          null;
  7                  end loop;
  8                  for x in ( select * from gtt )
  9                  loop
 10                          null;
 11                  end loop;
 12          end loop;
 13  end;
 14  /
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runstats_pkg.rs_middle;
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> declare
  2          l_data myArrayType;
  3  begin
  4          select myScalarType( object_id, created, object_name ) bulk collect
into l_data
  5            from all_objects
  6           where rownum <= 10000;
  7
  8          for i in 1 .. 50
  9          loop
 10                  for x in ( select count(*) from TABLE( cast( l_data as
myArrayType ) ) )
 11                  loop
 12                          null;
 13                  end loop;
 14                  for x in ( select * from TABLE( cast( l_data as myArrayType
) ) )
 15                  loop
 16                          null;
 17                  end loop;
 18          end loop;
 19  end;
 20  /
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runstats_pkg.rs_stop(1000);
Run1 ran in 1935 hsecs
Run2 ran in 3369 hsecs
run 1 ran in 57.44% of the time

the gtt is a little better for queries -- can support indexes and all, but

Name                                  Run1        Run2        Diff
STAT...CPU used by this sessio       1,839       3,061       1,222
STAT...CPU used when call star       1,839       3,061       1,222
LATCH.row cache objects             68,191      69,418       1,227
LATCH.row cache enqueue latch       45,484      46,716       1,232
STAT...recursive cpu usage           1,309       2,733       1,424
STAT...Elapsed Time                  1,938       3,372       1,434
STAT...redo entries                 10,523         505     -10,018
LATCH.redo allocation               10,540         517     -10,023
STAT...consistent changes           10,792         527     -10,265
STAT...db block gets                10,954         531     -10,423
STAT...db block changes             21,421       1,036     -20,385
LATCH.simulator hash latch          39,060       1,795     -37,265
STAT...session uga memory max            0     261,856     261,856
STAT...session uga memory                0     392,784     392,784
STAT...buffer is not pinned co     537,364      34,249    -503,115
STAT...table scan blocks gotte     503,151           1    -503,150
STAT...no work - consistent re     528,658      25,506    -503,152
STAT...consistent gets             566,393      63,058    -503,335
STAT...session logical reads       577,347      63,589    -513,758
STAT...table scan rows gotten    1,000,001           1  -1,000,000
LATCH.cache buffers chains       1,186,173     127,988  -1,058,185
STAT...redo size                 1,354,856      64,856  -1,290,000
STAT...session pga memory max        8,524   3,061,216   3,052,692
STAT...session pga memory         -131,072   3,388,896   3,519,968

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
1,352,628     251,123  -1,101,505    538.63%

PL/SQL procedure successfully completed.

you can see it gets a CBC latch lots more.  But given that I can have
statistics associated with it (and at 10k rows you'll want that), that it doen't
eat RAM (some 3m of ram for that 10k row table) -- thats a key decision factor
here

In 8i, on a totally different machine, the results were similar:

ops$tkyte@ORA817DEV> exec runstats_pkg.rs_stop(1000);
Run1 ran in 4605 hsecs
Run2 ran in 7923 hsecs
run 1 ran in 58.12% of the time

Name                                  Run1        Run2        Diff
LATCH.row cache objects             56,994      58,681       1,687
STAT...Elapsed Time                  4,627       7,928       3,301
STAT...CPU used by this sessio       4,412       7,838       3,426
STAT...CPU used when call star       4,412       7,838       3,426
STAT...recursive cpu usage           3,106       6,770       3,664
STAT...redo size                    40,528       1,412     -39,116
STAT...session uga memory max       13,220     450,080     436,860
STAT...session uga memory            3,664     455,308     451,644
STAT...table scan blocks gotte     503,101           1    -503,100
STAT...no work - consistent re     530,364      27,102    -503,262
STAT...buffer is not pinned co     539,757      36,415    -503,342
STAT...consistent gets             555,054      51,544    -503,510
STAT...session logical reads       555,844      51,566    -504,278
STAT...table scan rows gotten    1,000,001           1  -1,000,000
LATCH.cache buffers chains       1,112,518     102,935  -1,009,583
STAT...session uga memory max        6,220   2,380,748   2,374,528
STAT...session uga memory            2,328   2,382,460   2,380,132
STAT...session pga memory                0   2,444,800   2,444,800
STAT...session pga memory max            0   2,444,800   2,444,800

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
1,177,253     167,870  -1,009,383    701.29%

PL/SQL procedure successfully completed.

      Rating: 5
Great
      Example  June 03, 2003
Reviewer:
      A reader
Thanks again Tom

June 02, 2003 Lavanya -- Thanks for the question regarding
      "LogMiner", version 81721

      You Asked
Greetings Tom,

I hava a few questions on LogMiner.

1) One of our developers want to use the Logminer utility. I have never worked
on this utility till date. Is it true that the Logminer needs to be run as
sys/internal only.

2) If "NO" are there any other security concerns(Like database passwords...etc)
which i need to look into before i grant execute privileges on all those
packages.

3) Can we also see the ddl scripts using the Lominer or only the DMLs??

Thanks in advance

      and we said...
Well, if you are concerned I would set him up a mini-instance somewhere to play
with.  Logminer does not need to run on the machine that generated the logs.

1) no, I never run logminer as sys/internal.

The schema the will execute the DBMS_LOGMNR_D package has been granted execute
on SYS.DBMS_LOGMNR_D or has a role that is able to execute this package. By
default, the EXECUTE_CATALOG_ROLE has the privilege to run this package.

You also need to have utl_file set up for them as well -- in order to export the
data dictionary.

2) not really -- I would suggest that you just use another database to do the
analysis in, that way, you don't have to worry about things like this.  You can
export the data dictionary for them and away they go.

3) in 8i -- only the DML, later releases, the DDL can be recorded into the redo
logs, permitting logminer to easily see it.
Review &
      Followup

      Rating: 2
What is a
      LogMiner  July 14, 2002
Reviewer:
      A reader
Is it an Oracle Tool. Whats it used for?

          Followup:

read

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76956/archredo.htm#12680
for info

      Rating:
      5
LogMiner  July 15, 2002
Reviewer:
      Lavanya  from NJ
Excellent !!!
Thankyou Very much Tom.

      Rating: 3
error when
      building dictionary  September 11, 2002
Reviewer:
      ram  from india
hello Tom.

I get the following error when i am building the dictionary.
I am using 8.1.7. and here is the cut/paste from sqlplus:

ram@ORCL 8.1.7.0.0> begin
  2  sys.dbms_logmnr_d.build ('miner_dictionary.dat','D:\RAM');
  3  end;
  4  /
LogMnr Dictionary Procedure started
LogMnr Dictionary File Opened
TABLE: OBJ$ recorded in LogMnr Dictionary File
TABLE: TAB$ recorded in LogMnr Dictionary File
TABLE: COL$ recorded in LogMnr Dictionary File
TABLE: SEG$ recorded in LogMnr Dictionary File
TABLE: UNDO$ recorded in LogMnr Dictionary File
TABLE: UGROUP$ recorded in LogMnr Dictionary File
TABLE: TS$ recorded in LogMnr Dictionary File
TABLE: CLU$ recorded in LogMnr Dictionary File
TABLE: IND$ recorded in LogMnr Dictionary File
TABLE: ICOL$ recorded in LogMnr Dictionary File
TABLE: LOB$ recorded in LogMnr Dictionary File
TABLE: USER$ recorded in LogMnr Dictionary File
TABLE: FILE$ recorded in LogMnr Dictionary File
TABLE: PARTOBJ$ recorded in LogMnr Dictionary File
TABLE: PARTCOL$ recorded in LogMnr Dictionary File
TABLE: TABPART$ recorded in LogMnr Dictionary File
TABLE: INDPART$ recorded in LogMnr Dictionary File
TABLE: SUBPARTCOL$ recorded in LogMnr Dictionary File
TABLE: TABSUBPART$ recorded in LogMnr Dictionary File
TABLE: INDSUBPART$ recorded in LogMnr Dictionary File
TABLE: TABCOMPART$ recorded in LogMnr Dictionary File
ERROR -6532  ORA-06532: Subscript outside of limit
begin
*
ERROR at line 1:
ORA-06532: Subscript outside of limit
ORA-06512: at "SYS.DBMS_LOGMNR_D", line 793
ORA-06512: at line 2

Elapsed: 00:02:171.57

The utl_file directory is set properly. anything else i need to setup to avoid
this error ?
Thanks in advance.

          Followup:

Article-ID:         <Note:124671.1>
Circulation:        PUBLISHED (EXTERNAL)
Folder:             server.Utilities.LogMiner
Topic:              Product Usage
Title:              Dictionary File Creation Fails with ORA-06532
Document-Type:      PROBLEM
Impact:             MEDIUM
Skill-Level:        NOVICE
Server-Version:     08.01.07.00.00 to 08.01.07.01.00
Updated-Date:       13-AUG-2002 11:25:32
Attachments:        NONE
Content-Type:       TEXT/PLAIN
Errors:             ORA-6532;
Products:           5/RDBMS (8.1.7.0.0 to 8.1.7.1.0);
Platforms:          GENERIC;

Problem Description:
====================

You attempt to create a LogMiner dictionary file in Oracle RDBMS version
8.1.7.0.0 or 8.1.7.1.0 using DBMS_LOGMNR_D.BUILD and it fails with an ORA-6532
"subscript outside of limit" error.

For example:

    SQL>execute sys.dbms_logmnr_d.build(dictionary_filename=>'xyz.txt',
        dictionary_location=>'/d03/oracle8i');

    ERROR at line 1:
    ORA-06532: Subscript outside of limit
    ORA-06512: at "SYS.DBMS_LOGMNR_D", line 777
    ORA-06512: at line 1

Solution Description:
=====================

Do one of the following:

A. Obtain the fix for <bug:1529107> by upgrading to Oracle9i or applying the
   8.1.7.2.0 (or greater) patchset.

- OR -

B. If you are unable to apply the bug fix, you can workaround the problem by
   modifying the package body of DBMS_LOGMNR_D.BUILD as follows:

   ** Please note: this workaround involves making a small change to an
      Oracle supplied script. You should only make changes to these scripts
      when directed to do so by Oracle Support

   1. Edit the file "dbmslmd.sql" located in the "$ORACLE_HOME/rdbms/admin"
      directory.

      Change the line:

          TYPE col_desc_array IS VARRAY(513) OF col_description;

      to

          TYPE col_desc_array IS VARRAY(700) OF col_description;

      Save the file.

   2. Run the newly modified script:

          SQLPLUS> Connect internal

          SQLPLUS> @$ORACLE_HOME/rdbms/admin/dbmslmd.sql

   3. Recompile the package body DBMS_LOGMNR_D:

          SQLPLUS> alter package DBMS_LOGMNR_D compile body;

Explanation:
============

The error occurs due to <bug:1529107>, which is fixed in Oracle9i and for which
a fix exists starting in the 8.1.7.2.0 patchset.

The number of objects handled by the DBMS_LOGMNR_D.BUILD procedure goes beyond
the currently defined limit for VARRAY.

      Rating:
      5
Reader  March 29, 2003
Reviewer:
      A reader
Oracle 9.0.1 Manual

LogMiner has also been enhanced in Oracle9i to provide comprehensive log
analysis for additional datatypes. LogMiner now supports the following:

Clustered tables

Chained and migrated rows

LOB and LONG datatypes

Direct loads

Scalar object types

Data definition statements (DDL)

How does LogMiner tracks direct loads. Are the direct
not bypass Database Cache and hence redo not generated

          Followup:

direct path loads always generated redo if the database is in archivelog mode.

bypassing the buffer cache doesn't affect redo generation at all.

      Rating:
      5
Reader  June 01, 2003
Reviewer:
      A reader
From 8i concepts Guide
"
Advantages of Direct-Load INSERT
A major benefit of direct-load INSERT is that you can load data without logging
redo or undo entries, which improves the insert performance significantly. Both
serial and parallel direct-load INSERT have this performance advantage over
conventional path INSERT.
"
Does this statement refer to no archive log mode only ?

Thanks

          Followup:

in archivelog mode, the table would have to be in "NOLOGGING" mode to avoid the
redo (but the undo is skipped regardless).

in 9iR2 the database would have to also not be in the "force redo" mode as well.

      Rating: 4
conventional
      path  June 01, 2003
Reviewer:  Reader
       from Houston, TX
If i put my table in nologging mode in archivelog database, conventional path
also does not generate any redo? right? In this situation the major difference
between direct and conventional path is that users will not be able to access
the table during the direct path load and also it is fast as direct path load
does not have to check for the free (free list) space availability. is it
correct? Thanks.

          Followup:

conventional path always generates redo and undo.

Only "bulk" operations can be done nologging, things like

o insert /*+ APPEND */
o create table as select
o create index
o ....

users can access the table during a direct path load.

There are major differences between a direct path and conventional path load.
check out the concepts guide and the Utilities (SQLLDR chapters) guide.

      Rating:
      1
Logminer  June 02, 2003
Reviewer:
      Erik  from Belgium
Hello,

I've done some test with logminer ... on a 8174 Compaq True 64, 4GB RAM, 4cpu
868Mhz, SAN cabinet with 48 disks...

We generate 200 archived redo's of 100MB each on one day.

Well I can tell you it's impossible to use logminer... it is so slow ....

So I should say, use it in an very exceptional situation ...

      Rating: 3
direct path
      load  June 02, 2003
Reviewer:  Reader
       from Houston, TX
Tom, you say
<quote>
users can access the table during a direct path load.
<quote>

But the oracle doc

http://downloadwest.oracle.com/docs/cd/B10501_01/server.920/a96652/ch09.htm#1008816
says,
<quote>
A direct path load calls on Oracle to lock tables and indexes at the start of
the load and releases them when the load is finished.
<quote>

When you say users can access the table, did you mean query is allowed not DML.

Any comments.
Thanks.

          Followup:

query, yes.  that is accessing the table.

      Rating: 4
conventional
      path loading  June 02, 2003
Reviewer:
      Reader  from Houston, TX
Please see below from oracle doc.
<quote>
Specify whether the creation of the table (and any indexes required because of
constraints), partition, or LOB storage characteristics will be logged in the
redo log file (LOGGING) or not (NOLOGGING).The logging attribute of the table is
independent of that of its indexes.

This attribute also specifies whether subsequent Direct Loader (SQL*Loader) and
direct-path INSERT operations against the table, partition, or LOB storage are
logged (LOGGING) or not logged (NOLOGGING).

<quote>

To make sure i understand the concepts, if i had specified my table as nologging
during creation itself in ARCHIVELOG database, does conventional path loading
generates redo/undo or not? Thanks.

          Followup:

(see ABOVE where you asked this already ;)

conventional path loads ALWAYS
in ALL CASES
in EVERY CIRCUMSTANCE
generates redo and undo.

      Rating: 5
Got it
      finally!  June 02, 2003
Reviewer:  Reader
       from Houston, TX

      Rating: 5
Some log miner
      questions  November 12, 2003
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
Tom,
I want to use log miner to try to debug some database issues. Since I have never
used it before, I have some questions. We have Oracle 9.2.0.3.
a) Is is not possible to use log miner without turning on supplemental
logging/minimal supplemental logging?

b) As I read from Oracle manual, minimal supplemental logging doesn't have a
performance overhead on the database. Is the following the right command to turn
on minimal supplemental logging?

SQL> ALTER DATABASE ADD SUPPLEMENTAL LOG DATA

Once turned on, would it be preserved after database restart?

c) How can I enquire the status whether minimal supplemental logging is turned
on in a database?

Thanks

          Followup:

1)
a) you don't need to, you get "more", but you get lots without it
b) yes, alter databases persist, they are not "init.ora" settings.
c) query v$database

June 02, 2003 Sharon -- Thanks for the question regarding
      "Table Locks", version 8.0

      You Asked
Hi Tom !
I have a user, holding (not yet commiting) an update to 3
records on 3 different tables. I know how to obtain the names of
the tables from v$lock and other views as such. I need to
identify the locked records and get their primary keys. I work a
long time with Oracle and always knew that it's not trivial, but
surly Oracle holds this information somewhere... Is there a way
to do that? Thanks for your attention, Sharon.

      and we said...
we actually do not keep this information in a separate v$/x$
table.  This is the reason we can handle unlimited row level
locking.

Row locks in Oracle are an attribute of the data.  The fact that
a row is locked is stored on the block that row is on.  The
header of the block has a transaction table showing what
transactions have what rows locked on that block.

If we did not do this and stored them in a list like some other
databases -- we would have serious scalability issues when it
came to managing hundreds of thousands of millions of locks.
You would have a shared data structure that people would have to
serialize around to maintain.  You would have long search times
to find the lock (or not) you were looking for.  You would have
a memory resource requirement to maintain these locks.  It would
be ugly.  Instead of us going to a serial lock manager to lock a
row -- we goto the row and see if it is in fact locked.  We wait
on the transaction that has that row blocked and when it commits
or rolls back -- we get to try again.

The only way to discover what rows they might have locked would
be to try and lock every rows yourself and when you got blocked
-- use another session to see if they were blocking you.  If so,
you found one of the rows they have locked.
Review &
      Followup

      Rating: 3
Easy to get
      half the answer  June 14, 2002
Reviewer:
      andrew  from ca, usa
Tom
I needed the same answer and wanted to extend it to ID the corresponding
sessions. Your comments save me the effort.

BEGIN
   FOR i IN  (SELECT ROWID, col1 FROM tab1)
   LOOP
      BEGIN
         FOR j IN  (SELECT 1 FROM t1 WHERE ROWID = i.ROWID FOR UPDATE NOWAIT)
         LOOP
            -- sorry about the commit/rollback here
            commit;
         END LOOP;
      EXCEPTION
         WHEN OTHERS
         THEN
            DBMS_OUTPUT.put_line ('Locked ==>  '|| i.col1);
      END;
   END LOOP;
END;

      Rating: 3
What About
      Inserts & Blobs  June 02, 2003
Reviewer:
      Vinnie  from Orlando
What happens when you INSERT a row with a BLOB?  How does ORACLE determine how
much memory to reserve for the BLOB?
And what happend when 10k users are trying to do the same insert at the same
time?

          Followup:

check out:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96591/toc.htm
but LOBS are either cached or not cached.

if cached, no different then if you have 10k users inserting tons of stuff.
if not cached, direct to disk.

it doesn't reserver "memory" for the lobs.  It would use blocks in the buffer
cache like anything else -- DBWR would checkpoint the blocks as needed if cached
to make room in the buffer cache.

June 02, 2003 Rob -- Thanks for the question regarding "DBLINK
      CONNECTION", version 9.2.0

      You Asked
Tom:

Is there any way to determine if a session in a database is connected via a
database link. I have been experimenting with a logon trigger that will enable
resumable but resumable does not work if you connect through a dblink.

Rob

      and we said...
you do not WANT to enable resumable in a logon trigger.

resumable is something that should be explicitly requested.  otherwise, you'll
have end users calling all over the place saying "database is hung, whats
wrong".

I'm not a fan of this approach.

But, you can just wrap it in an exception block and ignore any errors from
trying to enable it (or just ignore the error you get via a dblink attempt)
June 02, 2003 Dan -- Thanks for the question regarding "PL/SQL
      call stack performance", version 8.1.7

      You Asked
Hi Tom -

We are having a spirited debate around how many layers of package.procedure
calls you can design before it affects performance.  For example, our typical
design calls for a 6-level layered PL/SQL approach...here's the call stack:

client_package.create_order() ->
our_om_pub.create_order() ->
our_om_pvt.create_order() ->
our_om_facade.create_order() ->
oracle_apps_om_pub.create_order() ->
oracle_apps_om_pvt.create_order()

We have people on our team making statements like: "6 layers of package calls
will cause performance issues."  Many of these packages are simple pass-throughs
to underlying functionality, and will not maintain a lot of state.

So, is this an issue, and if so, what are the queries to determine how much
overhead a particular layer is adding?

Thanks!

      and we said...
Well, lets benchmark it.

Runstats is what I use (see asktom.oracle.com/~tkyte for that).  Say you have a
package like this:

create or replace package demo_pkg
as
    procedure layer1( x in out char );
    procedure layer2( x in out char );
    procedure layer3( x in out char );
    procedure layer4( x in out char );
    procedure layer5( x in out char );
    procedure layer6( x in out char );
end;
/

create or replace package body demo_pkg
as
    procedure layer1( x in out char )
    is
    begin
        layer2(x);
    end;
    procedure layer2( x in out char )
    is
    begin
        layer3(x);
    end;
    procedure layer3( x in out char )
    is
    begin
        layer4(x);
    end;
    procedure layer4( x in out char )
    is
    begin
        layer5(x);
    end;
    procedure layer5( x in out char )
    is
    begin
        layer6(x);
    end;
    procedure layer6( x in out char )
    is
    begin
        x := '55';
    end;
end;
/

So, we can either call layer1, .... layer6.  To test, I used CHAR (since CHAR is
fixed size -- we can control the amount of data being passed about very closely)
and a script like this:

exec runstats_pkg.rs_start
declare
   x char(&1) := 'x';
begin
    for i in 1 .. 10000
    loop
        demo_pkg.layer1(x);
    end loop;
end;
/
exec runstats_pkg.rs_middle;
declare
   x char(&1) := 'x';
begin
    for i in 1 .. 10000
    loop
        demo_pkg.layer6(x);
    end loop;
end;
/
exec runstats_pkg.rs_stop(100);

so I could pass in the length.  I tested:

@test2 2
@test2 255
@test2 512
@test2 1000
@test2 5000
@test2 10000

2 bytes to 10,000 bytes.  The results were:

2 bytes
Run1 ran in 10 hsecs
Run2 ran in 3 hsecs
run 1 ran in 333.33% of the time

255 bytes
Run1 ran in 11 hsecs
Run2 ran in 3 hsecs
run 1 ran in 366.67% of the time

512 bytes
Run1 ran in 12 hsecs
Run2 ran in 4 hsecs
run 1 ran in 300% of the time

1000 bytes
Run1 ran in 16 hsecs
Run2 ran in 5 hsecs
run 1 ran in 320% of the time

5000 bytes
Run1 ran in 43 hsecs
Run2 ran in 12 hsecs
run 1 ran in 358.33% of the time

10000 bytes
Run1 ran in 72 hsecs
Run2 ran in 21 hsecs
run 1 ran in 342.86% of the time

So, think about it -- while the raw percentage looks impressive -- "hey, it
takes 3x times the time" -- think about this -- that is about 1/2 of a second
for 10,000 calls of 10,000 bytes!!!

So, is this low hanging fruit?
Something to wrangle your hands over?
Something to cause you to write spaghetti code?

no, i don't think so.  Also, if you use only IN parameters, they are passed
by REFERENCE (no copying going on) -- and if you use IN OUT NOCOPY when
appropriate -- you'll find numbers like this (I just changed the parameters in
the above from IN OUT to IN OUT NOCOPY)

2 bytes
Run1 ran in 5 hsecs
Run2 ran in 2 hsecs
run 1 ran in 250% of the time

255 bytes
Run1 ran in 6 hsecs
Run2 ran in 3 hsecs
run 1 ran in 200% of the time

512 bytes
Run1 ran in 5 hsecs
Run2 ran in 3 hsecs
run 1 ran in 166.67% of the time

1000 bytes
Run1 ran in 6 hsecs
Run2 ran in 3 hsecs
run 1 ran in 200% of the time

5000 bytes
Run1 ran in 10 hsecs
Run2 ran in 7 hsecs
run 1 ran in 142.86% of the time

10000 bytes
Run1 ran in 16 hsecs
Run2 ran in 11 hsecs
run 1 ran in 145.45% of the time

now you are talking 5/100th of a second!

Trust me, you have INFINITELY larger fish to fry in your system somewhere.
Please - stick with good modular coding techniques, don't short circut a call
stack for 5/100th of a second every 10,000 iterations!

Review &
      Followup

      Rating: 4
What if each
      procedure resides in separate packages?  June 02, 2003

Reviewer:  Dan Loomis  from Raleigh, NC
Thanks for the very thorough response.  Your example below has every procedure
in one package, where more often than not each procecdure call will reside in
separate packages in our system.   Again, probably still nothing to get worked
up about, but I'll use your example below to benchmark.

Thanks again for your help and the valuable service you provide.

June 03, 2003 Lucy -- Thanks for the question regarding "How to
      avoid full table scan", version 8.1.6.0.0

      You Asked
Hi Tom,
Could you help me with this, thanks
I have a project table with some columns as
project_id number primary key,
projectstatus_id number

I did

SQL> select project_id
  2  from project
  3  where project_id in (1,2,3);

PROJECT_ID
----------
         1
         3

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=3 Bytes=9)
   1    0   INLIST ITERATOR
   2    1     INDEX (RANGE SCAN) OF 'XPKPROJECT' (UNIQUE) (Cost=1 Card
          =3 Bytes=9)

from the execution plan I saw I will use index,

but when I added another where clause, it turned into full table scan

SQL> select project_id
  2  from project
  3  where project_id in (1,2,3) and
  4     projectstatus_ID in (1,2);

PROJECT_ID
----------
         1
         3

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=3 Bytes=18)
   1    0   TABLE ACCESS (FULL) OF 'PROJECT' (Cost=1 Card=3 Bytes=18)

Even I created an index on projectstatus_id, I still got full table scan, how
can I solve this, thanks

are you using cbo/rbo
CBO

how many rows in the table

124
how many rows returned by project_id in (1,2,3)
2
if you use cbo, what command are you using to analyze the table with
analyze table project compute statistics.

Thanks

      and we said...
with only 124 rows in the table, a full scan is the correct plan.

The first query:

SQL> select project_id
  2  from project
  3  where project_id in (1,2,3);

was answerable from the INDEX entirely -- the table never needed to be accessed.
 The second query:

SQL> select project_id
  2  from project
  3  where project_id in (1,2,3) and
  4     projectstatus_ID in (1,2);

must access the table to evaluate the predicate "projectstatus_id in (1,2)".
The cost of doing the index range scan+table access by rowid on such a tiny
table is higher then just scanning the table.

if you use /*+ FIRST_ROWS */ as a hint, it'll probably use the index HOWEVER it
won't be any faster and may actually be slower.

An index is not ALWAYS the best way.

Consider:

tkyte@TKYTE816> create table t ( project_id int, projectstatus_id int, data
char(50) );
Table created.

tkyte@TKYTE816> insert into t select rownum, rownum, 'x' from all_objects where
rownum <= 124;

124 rows created.

tkyte@TKYTE816> create index project_id_idx on t(project_id);
Index created.

tkyte@TKYTE816> analyze table t compute statistics;
Table analyzed.

tkyte@TKYTE816> set autotrace on

tkyte@TKYTE816> select project_id
  2    from t
  3   where project_id in (1,2,3);

PROJECT_ID
----------
         1
         2
         3

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=3 Bytes=9)
   1    0   INLIST ITERATOR
   2    1     INDEX (RANGE SCAN) OF 'PROJECT_ID_IDX' (NON-UNIQUE) (Cost=1 Card=3
Bytes=9)

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

tkyte@TKYTE816> select project_id
  2    from t
  3   where project_id in (1,2,3)
  4     and projectstatus_id in (1,2);

PROJECT_ID
----------
         1
         2

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=6)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=1 Card=1 Bytes=6)

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

tkyte@TKYTE816> select /*+ first_rows */ project_id
  2    from t
  3   where project_id in (1,2,3)
  4     and projectstatus_id in (1,2);

PROJECT_ID
----------
         1
         2

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=2 Card=1 Bytes=6)
   1    0   INLIST ITERATOR
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=1 Bytes=6)
   3    2       INDEX (RANGE SCAN) OF 'PROJECT_ID_IDX' (NON-UNIQUE) (Cost=1
Card=1)

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

tkyte@TKYTE816>
tkyte@TKYTE816> set autotrace off
tkyte@TKYTE816>

Here it is pretty much equivalent -- full scan vs index + table access by rowid.
Review &
      Followup

      Rating: 5
how if we use
      bind variable?  June 01, 2003
Reviewer:  eka
       from Indonesia
hi Tom,
okay, i've proved that fts sometimes better than index.
first, i create a table, and populate it with 100.000 rows.

scott@SD.JCISDA> create table test (tkey number, tcode number, tdesc
varchar2(200));
Table created.

scott@SD.JCISDA> alter table test add constraint pktest primary key (tkey);
Table altered.

scott@SD.JCISDA> create index idx1test on test (tcode);
Index created.

scott@SD.JCISDA> declare
2    ldesc varchar2(200) := rpad('x',200,'x');
3  begin
4  for x in 1..100000
5  loop
6    insert into test values(x,mod(x,100),ldesc);
7  end loop;
8  end;
9  /
PL/SQL procedure successfully completed.

scott@SD.JCISDA> commit;
Commit complete.

scott@SD.JCISDA> analyze table test compute statistics;
Table analyzed.

scott@SD.JCISDA> set autotrace traceonly;
scott@SD.JCISDA> select * from test where tkey > 30;
99970 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=447 Card=99971 Bytes=20594026)
   1    0   TABLE ACCESS (FULL) OF 'TEST' (Cost=447 Card=99971 Bytes=20594026)

Statistics
----------------------------------------------------------
          0  recursive calls
         22  db block gets
       9411  consistent gets
          0  physical reads
          0  redo size
    5540171  bytes sent via SQL*Net to client
     740129  bytes received via SQL*Net from client
       6666  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      99970  rows processed

scott@SD.JCISDA> select /*+rule*/ * from test where tkey > 30;
99970 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'TEST'
   2    1     INDEX (RANGE SCAN) OF 'PKTEST' (UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      16252  consistent gets
          0  physical reads
          0  redo size
    5540171  bytes sent via SQL*Net to client
     740129  bytes received via SQL*Net from client
       6666  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      99970  rows processed

okay, it proves that full table scan is better than index, in some cases. if i
change the value, select * from test where tkey > 90000, optimizer will choose
table access by index. great.
but, what if i use bind variable? most of our application use bind variable
(i've read your book ;) ) oracle parse the sql before doing any bind variable.
can optimizer choose the right path?

scott@SD.JCISDA> var n number;
scott@SD.JCISDA> exec :n:=30;
PL/SQL procedure successfully completed.

scott@SD.JCISDA> select * from test where tkey > :n;
99970 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=30 Card=5000 Bytes=1030000)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'TEST' (Cost=30 Card=5000
Bytes=1030000)
   2    1     INDEX (RANGE SCAN) OF 'PKTEST' (UNIQUE) (Cost=3 Card=5000)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      16252  consistent gets
          0  physical reads
          0  redo size
    5540171  bytes sent via SQL*Net to client
     740129  bytes received via SQL*Net from client
       6666  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      99970  rows processed

that's it. it choose the 'wrong' path. it seems that because optimizer don't
know what the variable will be, it assumes that query will return only 5% of the
total rows.
my question : how can i use bind variable, while i still want the optimizer to
pick the best path for me? can u explain how oracle choose the path while it
doesn't know the value of the variable?

TIA,
eka

          Followup:

tell me -- does your application select 99,970 rows?

you see, transactional applications, those that must -- repeat must -- use bind
variables (the vast majority of applications out there) do not.  The fetch few
rows.

So, is your example really valid in light of what you really do?

      Rating: 3
bind
      variable  June 02, 2003
Reviewer:  eka
       from indonesia
transaction : no. u're right. transaction only fetch a few rows only. but, for
reporting purpose : yes. it can fetch 90.000 rows or more.
Tom, if i'm not misunderstood, are you trying to say that, for transaction, i
should use bind variable, while in reporting, i should use hardcoded?

thanks for your help,
eka

          Followup:

for reporting, it totally depends.

Most of the times -- yes, you want to use binds.

June 03, 2003 deniz -- Thanks for the question regarding
      "question about auditing", version 9.2.0.3

      You Asked
Hello,

We would like to audit a specific user's action between
the time he is logged on and logged off.

As far as I looked at the documentation
you can audit with the command

audit insert table, update table, delete table by <username>;

but it does not give the details we want. We would like
to know which sql has been executed on the table.
How can we achieve this? Does a logon trigger can be used
for this? But if we use logon trigger , do the queries
in the v$sqlarea give us the correct results about
the queries the user executed?

Regards,

Deniz Ata

      and we said...
for selects, you use dbms_fga (fine grained auditing)

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96612/d_fga.htm#998101
for modifications, you would have to use a trigger and in the trigger, use
ora_sql_text to capture the sql in an AFTER INSERT OR UPDATE OR DELETE trigger:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96590/adg14evt.htm#1005274

Review &
      Followup

      Rating:
      4
  June 02, 2003
Reviewer:  Deniz Ata

As far as I understand from your answer,
we have to write AFTER INSERT OR UPDATE OR DELETE trigger
for all the tables in the DB. Am I right?

Regards

          Followup:

correct

      Rating:
      4
  June 02, 2003
Reviewer:
      A reader
One more question.

The trigger will fire for all the users..But we would like to take detailed info
for specific users. How can we achieve
this?

          Followup:

when user = 'SCOTT'
when user in ( 'a', 'b', 'c', ... )

use a when clause on the trigger.

      Rating: 2
Your Response
      to the Auditing Question  June 02, 2003
Reviewer:
      Gerry McGraw  from PA USA
deniz -- Thanks for the question regarding "question about auditing", version
9.2.0.3
originally submitted on 2-Jun-2003 11:40 Eastern US time, last updated
2-Jun-2003 14:50

Dear Tom,
I'm wondering why you do not mention the use of Oracle Streams to support Deniz
audit question. We have successfully implemented single datasource streams to
meet
our projects auditing requirements. We capture DML Insert, update and Delete
changes using streams. We are not using
propagation, our audit schema resides in the same instance.

Thanks,
Gerry

          Followup:

while that is one way -- it will not capture selects, nor will it capture the
SQL text of the insert/update/delete (eg: an update emp set ename = lower(ename)
would manifest itself at N logical change records).

that is what they asked for -- the text of the queries.

      Rating: 4
Is there any
      global trigger kind of thing?  June 03, 2003

Reviewer:  Tony  from India
I don't want to write a trigger for each table to capture DMLs. Is there any
gloal trigger kind of stuff that fires for each DML (like DDL triggers)?

          Followup:

No, write a procedure that writes a trigger - automate it.  You've already
figured out that the trigger would be more or less "the same".  So, write a
single packaged procedure so that your trigger is all of one line long.  Add a
procedure to that package that you can pass a tablename to and it'll actually
create that trigger.

June 03, 2003 sendi -- Thanks for the question regarding "How
      to achive this ", version oracle 8.1.7

      You Asked
Hi tom,

   I want to create a "tab delimited" flat file from pl/sql stored procedure how
can I do that ? I did try it with UTL_FILE/(TEXT_IO) package but I think it
supports only "\n" and not "\t" .
can you please give me a solution for this. ( I have never worked with
oracle web server or (OAS) !!)

  I don't know if your owa_sylk utility is for oracle web server
 but I did see your owa_sylk utility and I didnot understand it ?
/*************************************************************

 procedure p( p_str in varchar2 )
  is
  begin
    utl_file.put_line( g_file, p_str );
  exception
    when others then null;
  end;

/**************************************************
  procedure print_heading( font in varchar2,
                           grid in varchar2,
                           col_heading in varchar2,
                           titles in owaSylkArray )
  is
    l_title varchar2(2000);
  begin
    p( 'ID;ORACLE' );
    print_comment( 'Fonts' );
    p( 'P;F' || font || ';M200' );   ---> how it works ?
    p( 'P;F' || font || ';M200;SB' );
    p( 'P;F' || font || ';M200;SUB' );
    --
    print_comment( 'Global Formatting' );
    p( 'F;C1;FG0R;SM1' ||
           ite( upper(grid)='YES', '', ';G' ) ||
           ite( upper(col_heading)='YES', '', ';H' )  );
    for i in 1 .. g_desc_t.count loop
      p( 'F;C' || to_char(i+1) || ';FG0R;SM0' );
    end loop;
/******************************************************/

  Also I want the explanation of how do you make the "bold"
fonts with is utility.

 The owa_sylk utility does work in my computer but I dont know how
it bolds the column headings ?

  Can you please tell me this ?

Thanks,
Sendi

      and we said...
the function call:

   chr(9)

is a tab.  So

   c := a || chr(9) || b

will put into C the value A <tab> B

As for the owa_sylk -- we have versions that do utl_file, versions that do htp.p
calls.  Search this site of owa_sylk and you'll find both.

Search www.google.com for SYLK to discover what this file format is and how to
use it.  Its a very standard file format used by spreadsheets to exchange data.
Review &
      Followup

      Rating:
      4
m200;sub  June 03, 2003
Reviewer:  Scott
      Wesley  from Perth, WA
G'day

I've searched high and low via google to find documentation on sylk file format.
This was the best I could find:

http://www.faqs.org/faqs/spreadsheets/faq/
It has doco from microsoft on all the types of records etc.

What I can't find information on is what the
P;FArial;M200;SUB
commands actually do.
I've played around with the letters, because I presume it relates directly to
using commands such as
F;R1;FG0C;SD
which is <supposed> to BOLD the first row.
but I can't quite grasp it.

Do you have any information about this?

Thanks.

          Followup:

don't know, I think what we did was to create an excel spreadsheet and then
saved as SYLK and reverse engineered some of the "fancier" parts.

June 03, 2003 Ramasamy -- Thanks for the question regarding
      "clustering_factor", version 8.1.7

      You Asked
SQL> select clustering_factor , blevel from dba_indexes
  2  where index_name = 'PK_T1'
  3  ;

CLUSTERING_FACTOR     BLEVEL
----------------- ----------
         65381317          2

SQL> select blocks , empty_blocks from dba_tables
  2  where table_name = 'T1'
  3  ;

    BLOCKS EMPTY_BLOCKS
---------- ------------
   1182635        12865

SQL>

The table t1(renamed here) has got a primary key (PK_t1)
of store , sku columns.  This table T1 is partitioned by store column.
In the above result i am seeing the CLUSTERING_FACTOR factor is
way up from the number of blocks. What i was expecting is since PK_T1
is primary key the clustering_factor should be <= the no of blocks.

Could you explain why i see such a huge Clustering factor compared
to the number of blocks even though i am looking at primary key index.

Thanks,
Ramasamy T

      and we said...
because clustering factor is about "how sorted the table is with respect to the
index"

it has nothing to do with what sort of index we are looking at.

When clustering factor is near the number of blocks in the table, the table is
stored "sorted" by that index (eg: if the primary key were populated by a
sequence and the table was a heap table, one would expect the table to be
"sorted" more or less by key as data is added to the end of the table)

When the cluster factor is near the number of ROWS in the table, the table is
NOT sorted by the index key.

Remember -- a table can be sorted how many ways? (one)  So in general, one index
will have a good cluster factor, others might not.

consider this example, both are primary keys:

ops$tkyte@ORA920> create table t1 as select all_objects.*, rpad('*',255,'*')
xxxx from all_objects order by object_id;

Table created.

ops$tkyte@ORA920> create table t2 as select * from t1 order by
REVERSE(object_id);

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table t1 add constraint t1_pk primary key(object_id);

Table altered.

ops$tkyte@ORA920> alter table t2 add constraint t2_pk primary key(object_id);

Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table t1 compute statistics for table for all indexes;

Table analyzed.

ops$tkyte@ORA920> analyze table t2 compute statistics for table for all indexes;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select a.table_name, a.blocks, a.num_rows, b.clustering_factor
  2    from user_tables a, user_indexes b
  3   where a.table_name in ( 'T1', 'T2' )
  4     and a.table_name = b.table_name
  5  /

TABLE_NAME                         BLOCKS   NUM_ROWS CLUSTERING_FACTOR
------------------------------ ---------- ---------- -----------------
T1                                   1623      31593              1589
T2                                   1614      31593             31588

but only one has a "good" clustering factor!

(since we sorted the data that way before inserting)

Review &
      Followup

      Rating:
      4
  June 02, 2003
Reviewer:  Vipin
       from New york
Hi Tom,

As you described earlier it is clear how clustering factor is high.

If I am right the clustering factor must be less for better performance. How to
bring this down, is it just dba's job or can developer also do something in this
regard

          Followup:

remember -- ask yourself, a table can be sorted how many ways?

pretty much at most one way.

You can use structures such as

IOTs
Hash Clusters
Single Table Hash Clusters
B*Tree Clusters

to force physical "locale" of data -- to keep related key values together.
Otherwise, if you use a HEAP table -- the data goes wherever it wants.

For low cardinality reads via an index (which many are, single rows for
example), the clustering factor doesn't come into play.

It would be for large range scans.

So, look at how you use the data and decide if a structure that makes access
much more efficient (one of the above) is something you should consider.

      Rating: 5
range scan and
      clustering factor  June 03, 2003
Reviewer:
      pingu_san
Hi

Can we say that a good clustering factor only favours index range scans? Because
only range scans have more possibility of finding similar rows in blocks. For
example if table employees is sorted by employee_no, if we are looking for
employees from 1000 to 2000 then most probably they are packed in same blocks
(good clustering factor). Is that assumption correct?

          Followup:

it is all about range scans.

June 03, 2003 Laurent -- Thanks for the question regarding
      "Differences between intersect and self join.", version 9.2.0.3

      You Asked
Hello,

I want to count and list the people who match some criteria.
Each person can have multiple response for each criteria (ex: for the criteria
LOVE they can love TENNIS,SOCCER,...) .
So I create the table below, and have one row per criteria/value .
I think i will have about 100 000 differents name and about 1.000.000 rows in
the table (about 10 criteria/values per people).
Can you tell me if my design is ok and which query is better self-join or
intersect.
Thanks.

create table people (
name varchar2(8),
criteria varchar2(20) ,
value varchar2(20) ) ;

select count(*) from
people p1,
people p2,
people p3
where (p1.criteria='LIKE' and p1.value='TENNIS')
and (p2.criteria='SEX' and p2.value='WOMEN')
and (p3.criteria='STATUS' and r3.valeur='MARRIED')
and p1.name=p2.name
and p1.name=p3.name ;

select count(*) from
(
select name from people where criteria='LIKE' and value='TENNIS'
intersect
select name from people where criteria='SEX' and value='WOMEN'
intersect
select name from people where criteria='STATUS' and value='MARRIED'
);

      and we said...
or the best option:

create global temporary table gtt( criteria, value );

then you insert the values in there and query:

select count(*)
  from ( select name
           from people
          where criteria in ( select * from gtt )
          group by name
         having count(*) = (select count(*) from gtt) )

a single pass.

Review &
      Followup

      Rating:
      2
Question  June 03, 2003
Reviewer:
      Ralf  from Dresden, Germany
Hallo Tom,

may be you would code

... where (criteria,value) in (select * from gtt)... ?

best regards

Ralf

          Followup:

hopefully, they would have figured that concept out -- yes.

      Rating: 3
One or twho
      column.  June 03, 2003
Reviewer:  Laurent
       from Paris, France
Hi,

I am not sure to understand the way you use.
The table gtt has one column or two column ?
If it's two , i think that the clause where criteria in (select * from gtt)
generate an error code .
Thanks

          Followup:

select count(*)
  from ( select name
           from people
          where (criteria,value) in ( select criteria,value from gtt )
          group by name
         having count(*) = (select count(*) from gtt) )

June 03, 2003 --
      Thanks for the question regarding "How to exclude some tables in export?",
      version 9.2.0.3

      You Asked
I have a database with 300+ tables and some views, synonyms, packages and
triggers etc. I want to export everything except 3 tables. How to do that using
export utility?

Thanks in advance

      and we said...
you cannot really -- but you can make it so that the 3 tables export 0 rows.

You would create a user, say "TMP".
Using fine grained access control, set up a policy that says:

   if ( user = 'TMP' ) then return '1=0'; else return null; end if;

you would associate that policy with those three tables.

Now, when the user TMP does an export -- it'll not see any data in that table.

See

http://asktom.oracle.com/~tkyte/article2/index.html
for how to set up a fine grained access control policy
Review &
      Followup

      Rating:
      5
Thanks  June 03, 2003
Reviewer:
      A reader
Thanks for your comments. Your solution looks promising. I will test it as soon
as I can.

June 03, 2003 ZS -- Thanks for the question regarding "Why a
      Merge Join Cartesian?", version 8.1.7

      You Asked
Why cartesian merge join?

I have the following SQL and I keep getting a merge join cartesian and dont
understand why?

SELECT   vacctbgr.NO_ACCOUNT
        ,vacctbgr.cd_company_system
FROM
         TVP298SCHEDBILLCYL        schbllcyc
        ,TVP313SYSSHEDDEFLT        schdfl
        ,TVP719VACCTBILLGRP        vacctbgr
WHERE
       schdfl.ST_PRODUCTION = 'R'
  AND schbllcyc.no_version = schdfl.no_version
  AND schbllcyc.yr_calender = schdfl.yr_calender
  AND schbllcyc.cd_company_system = schdfl.cd_company_system

  AND schbllcyc.no_billing_cycle = vacctbgr.no_vol_bill_cycle
  AND schbllcyc.cd_company_system = vacctbgr.cd_company_system

  AND schbllcyc.cd_company_system='RRRR'
  AND schbllcyc.dt_schedule = to_date('20-apr-02','DD-MON-YY')
/

Execution Plan
----------------------------------------------------------
0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=41)
1    0   NESTED LOOPS (Cost=2 Card=1 Bytes=41)
2    1     MERGE JOIN (CARTESIAN) (Cost=2 Card=1 Bytes=21)
3    2       TABLE ACCESS (FULL) OF 'TVP313SYSSHEDDEFLT' (Cost=1
Card=1 Bytes=12)
4    2       SORT (JOIN) (Cost=1 Card=1 Bytes=9)
5    4         TABLE ACCESS (FULL) OF 'TVP719VACCTBILLGRP' (Cost=1
Card=1 Bytes=9)
6    1     INDEX (UNIQUE SCAN) OF 'PVP298' (UNIQUE)

------------------------------------------------------------
However the join in the SQL is:
schbllcyc --> schdfl
   |
   |
 vacctbgr
why Oracle is joining schdfl --> vacctbgr ?

Thanks for your help,
ZS

      and we said...
Good gosh -- those table names are indistinguishable from gibberish.  Amazing
how hard that makes it to actually follow the example.  Thankfully it is a small
query with a small plan.

Look at the plan.  It is assuming that the full scan on 'TVP313SYSSHEDDEFLT'
(schdfl) will return one row.  It is also assuming that the full scan on
'TVP719VACCTBILLGRP' (vacctbgr) will return 1 row as well.  It just puts those
two rows together (cartesian product) and joins that result to an index on
TVP298SCHEDBILLCYL.

It is because of the low cardinality expected back from the two tables that it
went this way.  It is a perfectly valid approach and can be very fast.
Review &
      Followup

      Rating:
      4
  June 05, 2002
Reviewer:
      A reader
Thanks Tom this was very useful. regarding the names, unfortunatly the table
names are coming from a package the company bought and we have to live with them
:(

      Rating: 4
Merge Join
      Cartesian  May 26, 2003
Reviewer:  Vivek
      Sharma  from Bombay, India
Dear Tom,

I have a query which is running out of temp tablespace. The explain plan shows
merge join cartesian. I have analyzed the tables which had further reduced teh
cost of the plan but the cost even now is too high and ther merge join cartesian
does not disappers.
The Query is

select
trades.user_code as user_code,
null as amc_code,
trades.tran_id as trxn_number,
null as folio_no,
null as check_digit_number,
trades.tran_type as trxn_type,
trandet.appln_no as application_number,
trades.rb_sch_code as scheme_code,
cusmashead.app_first_name as Inv_first_name,
cusmashead.joint_name1 ,
cusmashead.joint_name2,
cusmasdet.app_add1,
cusmasdet.app_add2,
cusmasdet.app_add3,
cusmasdet.app_city,
cusmasdet.app_pin,
cusmasdet.app_tel_o,
to_char(trandet.tran_date,'dd-mon-yyyy') as trxn_date,
to_char(trandet.tran_date,'hh24-mm-ss') as trxn_time,
'Y' as units,
trades.amount,
trandet.pay_mode,
trades.brok_code,
trades.sub_brok_code,
null,
trandet.issuing_bank,
trandet.issuing_branch,
trandet.instr_issue_date,
trandet.instr_no,
null,
null,
null,
cusmasdet.app_dob,
cusmasdet.gaur_name,
null as guardian_father_name,
null as guardian_mother_name,
cusmasdet.gaur_birth,
cusmasdet.APP_PAN_GIR_NO,
cusmasdet.app_tel_r,
null,
null,
cusmasdet.app_email,
null,
null,
null,
trades.bnk_acc_no,
trades.bnk_acc_type,
trades.bnk_name,
trades.bnk_branch,
null,
null,
cusmasdet.app_hold_mode,
null,
null,
cusmasdet.j1d_birth,
null,
null,
cusmasdet.j2d_birth,
cusmasdet.app_occup,
bnkrbstatusmast.cons_name,
null as remarks,
cusmasdet.app_state as state,
null as sub_tran_type,
trades.value_date as value_date,
trades.payment_mandate as pay_mechanism,
trades.payment_mandate as div_pay_mechanism,
null as ecs_number,
null as alt_broker,
null as alt_folio_number,
null as redeem_dividend,
null as payin_slip_no,
null as bank_charges,
trandet.pay_mode as instrm_type,
cusnommas.nom_name ,
cusnommas.nom_relation,
cusnommas.nom_add1,
cusnommas.nom_add2,
cusnommas.nom_add3,
null as nom_city,
null as nom_pincode ,
null as nom_state,
null as nom_country,
cusnommas.nom_guar_name,
cusnommas.nom_dob,
null as currency,
null as bank_code,
null as sign_verified,
trades.inv_acc_no,
trades.opt_code,
cusmasdet.a_detail,
cusmasdet.o_detail
from
bnkrbtrandet trandet,
bnkrbtrades trades,
bnkrbcusmashead cusmashead,
bnkrbcusmasdet cusmasdet,
bnkrbcusnommas cusnommas,
bnkrbstatusmast
where trades.tran_id = trandet.tran_id
and trades.bla_no = cusmashead.bla_no
and trades.inv_acc_no = cusmasdet.inv_acc_no
and trades.nom_no (+) = cusnommas.nom_no
and cusmasdet.app_tax_status = bnkrbstatusmast.cons_name
and rownum < 3
order by
trades.tran_id, trades.bla_no, trades.inv_acc_no
/

and the Explain Plan for this query is

SELECT STATEMENT   [CHOOSE] Cost = 1087275803324200000
  SORT ORDER BY
    COUNT STOPKEY
      NESTED LOOPS
        NESTED LOOPS
          FILTER
            MERGE JOIN OUTER
              SORT JOIN
                MERGE JOIN CARTESIAN
                  HASH JOIN
                    TABLE ACCESS FULL BNKRBSTATUSMAST

                    TABLE ACCESS FULL BNKRBCUSMASDET [ANALYZED]
                  SORT JOIN
                    TABLE ACCESS FULL BNKRBCUSNOMMAS [ANALYZED]
              SORT JOIN
                TABLE ACCESS FULL BNKRBTRADES [ANALYZED]
          TABLE ACCESS BY INDEX ROWID BNKRBTRANDET [ANALYZED]
            INDEX UNIQUE SCAN RBTRANDET_TRAN_ID_PK [ANALYZED]
        TABLE ACCESS BY INDEX ROWID BNKRBCUSMASHEAD [ANALYZED]
          INDEX UNIQUE SCAN CUSMAST_BLA_NO_PK [ANALYZED]

Just check the cost it is too high.
The records in all of the above tables are
BNKRBTRANDET - 152418 Records
BNKRBTRADES - 226598 Records
BNKRCUSMASHEAD - 131028 Records
BNKRBCUSMASHEAD - 137309 Records
BNKRBCUSNOMMAS - 57118 Records
BNKRSTATUSMAST - 44 Records.

How do I elimate Merge Join Cartesian. Please help me out.
Regards
Vivek Sharma

          Followup:

first step, lose the (+).

You have:

trades.nom_no (+) = cusnommas.nom_no

but also you have:

trades.tran_id = trandet.tran_id

Will, if the row in trades is "made up" from the (+), then tran_id will be null
and NULL is never equal (nor not equal) to anything else. So the outer join is
there simply to "remove access paths from the optimizer" in this case.  It is
not semantically relevant.

Secondly -- you do realize that an order by on a query block using rownum like
that is almost always a "bug".  Where rownum <= 3 is done BEFORE the order.  so,
it gets 3 random rows -- and then sorts them.

So, suggestions:

a) get rid of the outer join, and begin a review of all queries in your system
that use outerjoins and keep them IF and ONLY IF you actually need them.  Here,
I KNOW we can safely remove it -- it is meaningless.

b) query like this:

select *
  from ( select /*+ FIRST_ROWS */ ........ order by ..... )
 where rownum <= 3;

and if you have an index on (trades.tran_id, trades.bla_no, trades.inv_acc_no),
you should find this query returns instantly or faster (assuming one of those
three columns is defined as NOT NULL anyway)

      Rating:
      5
  May 27, 2003
Reviewer:  Helena
      Marková  from Bratislava, Slovakia

      Rating: 4
Why Merge Join
      Cartesian  June 03, 2003
Reviewer:  Vivek
      Sharma  from Bombay, India
Dear Tom,

I have created 2 tables as

create table t as
select object_id, object_name, 0 user_id
from all_objects;

create table t1 as
select 0 user_id, object_name name
from all_objects
where rownum<=200;

Then I updated user_id from both the tables with values as 1,2 and 3 and the
proportion were

SQL> select user_id, count(*) from t
  2  group by user_id;

   USER_ID   COUNT(*)   -- Total 28298
---------- ----------
         1      10000    -- 35%
         2      15000    -- 53%
         3       3298    -- 12%

  1  select user_id, count(*) from t1
  2* group by user_id
SQL> /

   USER_ID   COUNT(*)   -- Total 200
---------- ----------
         1         99    -- 50%
         2         49    -- 25%
         3         52    -- 25%
I have created indexes on both the tables on user_id column. But when I join
these 2 tables

SQL> set autot traceonly
SQL> select t.user_id, object_id, object_name, name
  2  from t, t1
  3  where t.user_id=t1.user_id
  4  and   t.user_id=1;

990000 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1783 Card=990000
Bytes=50490000)
   1    0   MERGE JOIN (CARTESIAN) (Cost=1783 Card=990000 Bytes=50490000)
   2    1     TABLE ACCESS (FULL) OF 'T1' (Cost=1 Card=99 Bytes=1881)
   3    1     SORT (JOIN) (Cost=1782 Card=10000 Bytes=320000)
   4    3       TABLE ACCESS (FULL) OF 'T' (Cost=18 Card=10000 Bytes=320000)

Why is it going for Cartesian Join and also the number of rows are multiplied ?

Also please let me know from where can I get details of difference in Nested
Loops, Sort Joins and Hash Joins. I need to know, in which scenario each of
these are better than others.

Tom your answer will be very helpful for clearing my doubts as I need to improve
my skills in SQL Tuning.

Thanks in advance
Regards
Vivek Sharma

          Followup:

why not?  it sees you want to join every row in T where user_id = 1 with every
row in T1 where user_id = 1.

You have a cartesian product that looks like a join.  It did the most efficient
thing for you.

your 99 rows in T are joined to EACH of the 10,000 rows in T2 -- you got exactly
what you asked for.  The optimizer developed the best plan possible for this
query and gave you what you wanted (or, at least, what you asked for)

read the performance guide.  it goes into the ins and outs of each type.

June 03, 2003 Jon -- Thanks for the question regarding "Anomaly
      with NOCOPY", version 8.1.7

      You Asked
Can you explain the different behavior of NOCOPY in the following two cases?
The only difference seems to be putting a precision on the variable "a" in the
second case, and yet they appear to function quite differently.

SQL> DECLARE
  2      a NUMBER;
  3      b NUMBER;
  4      PROCEDURE dummyProcedure (x IN OUT NOCOPY NUMBER) IS
  5          e EXCEPTION;
  6      BEGIN
  7          DBMS_OUTPUT.PUT_LINE(x);
  8          x := 2;
  9          RAISE e;
 10      EXCEPTION
 11          WHEN e THEN
 12              x := 1;
 13              RAISE;
 14      END;
 15  BEGIN
 16      a := 0;
 17      dummyProcedure(a);
 18  EXCEPTION
 19      WHEN OTHERS THEN
 20          DBMS_OUTPUT.PUT_LINE('A :'||a);
 21  END;
 22  /
0
A :1

PL/SQL procedure successfully completed.

SQL>
SQL> DECLARE
  2      a NUMBER(4);
  3      b NUMBER(4);
  4      PROCEDURE dummyProcedure (x IN OUT NOCOPY NUMBER) IS
  5          e EXCEPTION;
  6      BEGIN
  7          DBMS_OUTPUT.PUT_LINE(x);
  8          x := 2;
  9          RAISE e;
 10      EXCEPTION
 11          WHEN e THEN
 12              x := 1;
 13              RAISE;
 14      END;
 15  BEGIN
 16      a := 0;
 17      dummyProcedure(a);
 18  EXCEPTION
 19      WHEN OTHERS THEN
 20          DBMS_OUTPUT.PUT_LINE('A :'||a);
 21  END;
 22  /
0
A :0

PL/SQL procedure successfully completed.

      and we said...
NOCOPY is a hint.

When the types did not match as it wanted -- the hint was IGNORED. (as it is
documented that it might be)

The second example was using "copy semantics" and behaved exactly as a procedure
would behave without NOCOPY.

The first one shows the insidous side effect of NOCOPY - that the out parameter
values may be modified -- even in the event of an ERROR.

June 03, 2003 zam -- Thanks for the question regarding "How to
      show missing date between given range", version 7.3 or 8.0.5

      You Asked
Hi
How to show missing date from given date range except Friday
Suppose I have some columns like this

Empno   attend_date
------  --------------
101     5-jan-2000
101     9-jan-2000
101     10-jan-2000
101     11-jan-2000
101     12-jan-2000
101     13-jan-2000
101     16-jan-2000

I wanna see missing date from 5-jan-2000 to 17-jan-2000 except
Friday the result must be
Like this
Empno     attend_date
--------- --------------
101       6-jan-2000
101       8-jan-2000
101       15-jan-2000
and so on…..

thank u
regards,
zam

      and we said...
Yes, we can do that.  You'll need to tweak this for performance
and for your needs.  I've made it totally generic.  If you know
the date ranges -- it can go faster.  If you do this for one
employee at a time -- it can go faster.  I've set it up to
dynamically discover the date range and be applied to all
employees.  I'll build the example bit by bit:

drop table t2;
create table t2 ( r primary key ) as select rownum r from
all_objects where rownum < 500;

we need a table with lots of rows with just a number.  We need
this to synthesize the "missing" rows

here is your table, i've put 2 emps in there...
drop table t;
create table t ( empno int, attend_date date );

insert into t values ( 101,    '5-jan-2000' );
insert into t values ( 101,    '9-jan-2000' );
insert into t values ( 101,    '10-jan-2000' );
insert into t values ( 101,    '11-jan-2000' );
insert into t values ( 101,    '12-jan-2000' );
insert into t values ( 101,    '13-jan-2000' );
insert into t values ( 101,    '16-jan-2000' );

insert into t values ( 102,    '6-jan-2000' );
insert into t values ( 102,    '9-jan-2000' );
insert into t values ( 102,    '10-jan-2000' );
insert into t values ( 102,    '11-jan-2000' );
insert into t values ( 102,    '12-jan-2000' );
insert into t values ( 102,    '13-jan-2000' );
insert into t values ( 102,    '16-jan-2000' );

First, we figure out how to get the date ranges by employee.
What I need to know for an emp is the start date of the range
and the number of days...  You might supply these as BIND
VARIABLES instead of a query as I am:

select empno, min(attend_date) start_date,
       max(attend_date)-min(attend_date)+1 num_days
  from t
group by empno
/

     EMPNO START_DAT   NUM_DAYS
---------- --------- ----------
       101 05-JAN-00         12
       102 06-JAN-00         11

Now I make up all of the dates in that range for example
employee:

select empno, start_date+r-1
  from t2,
       ( select empno, min(attend_date) start_date,
                max(attend_date)-min(attend_date)+1 num_days
           from t
          group by empno  ) t1
 where t2.r <= t1.num_days
/

     EMPNO START_DAT
---------- ---------
       101 05-JAN-00
       101 06-JAN-00
       101 07-JAN-00
       101 08-JAN-00
       101 09-JAN-00
       101 10-JAN-00
       101 11-JAN-00
       101 12-JAN-00
       101 13-JAN-00
       101 14-JAN-00
       101 15-JAN-00
       101 16-JAN-00
       102 06-JAN-00
       102 07-JAN-00
       102 08-JAN-00
       102 09-JAN-00
       102 10-JAN-00
       102 11-JAN-00
       102 12-JAN-00
       102 13-JAN-00
       102 14-JAN-00
       102 15-JAN-00
       102 16-JAN-00

23 rows selected.

Now, I just need to outer join that query with the original
table.  the rows we want to keep are rows such that the original
table had no "mate" and the date is not friday:

select *
  from ( select empno, start_date+r-1 attend_date
           from t2,
                ( select empno, min(attend_date) start_date,
                    max(attend_date)-min(attend_date)+1 num_days
                    from t
                   group by empno  ) t1
          where t2.r <= t1.num_days ) A,
       t B
 where a.empno = b.empno(+)
   and a.attend_date = b.attend_date (+)
   and b.attend_date is NULL
   and to_char( a.attend_date, 'dy' ) <> 'fri'
/

     EMPNO ATTEND_DA      EMPNO ATTEND_DA
---------- --------- ---------- ---------
       101 06-JAN-00
       101 08-JAN-00
       101 15-JAN-00
       102 08-JAN-00
       102 15-JAN-00

And that is it...

Review &
      Followup

      Rating:
      4
unbelievable  February 13, 2003
Reviewer:
      Umesh  from India

      Rating: 4
What
      if...?  June 03, 2003
Reviewer:  Tom
       from Florida
What if you wanted the dates that were missed to be aggregated into ranges?  For
instance, say an employee missed May 2, 3, 4, 5 and May 18,19,20.  How would you
write a query to return:

   May 2, May 5
   May 18, May 20

?

          Followup:

ops$tkyte@ORA817DEV> select empno, last_attend_date, attend_date
  2    from (
  3  select empno, attend_date,
  4         lag(attend_date) over (partition by empno order by attend_date)
last_attend_date
  5    from t
  6         )
  7   where attend_date <> last_attend_date+1
  8  /

     EMPNO LAST_ATTEND ATTEND_DATE
---------- ----------- -----------
       101 05-JAN-2000 09-JAN-2000
       101 13-JAN-2000 16-JAN-2000
       102 06-JAN-2000 09-JAN-2000
       102 13-JAN-2000 16-JAN-2000

      Rating: 5
MISSING
      DATES  June 04, 2003
Reviewer:
      A reader
Good Example

June 03, 2003 Soeren -- Thanks for the question regarding
      "Hitrate on Oracle obejcts", version 8.1.7

      You Asked
Is there an easy why to find out the 'hitrate' on the indexes and tables in the
database? I have an application with lots of objects put into different
tablespaces (depends on the size of the object). It would be nice if I some how
could locate the object which generating most io and not just a filename.
/Soeren

      and we said...
not in 8i, in 9i -- yes.

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96536/ch3166.htm#1098972


Subject | Article | Last Updated (East coast time)
ora-03297 when resizing a datafile on a temporary tablespace | article only | Jun 04, 2003 07:39 am
Advanced queuing | article only | Jun 04, 2003 07:43 am
Number Of Extents | article only | Jun 04, 2003 07:52 am
Global variable across different sessions | article only | Jun 04, 2003 10:07 am
Array Processing | article only | Jun 04, 2003 12:43 pm
2GB Limit | article only | Jun 04, 2003 12:49 pm
Analytical functions - lag sales | article only | Jun 04, 2003 01:37 pm
write contents of a query to a text file | article only | Jun 04, 2003 02:28 pm
Java File class cannot see UNC path | article only | Jun 05, 2003 07:27 am
cursor passing | article only | Jun 05, 2003 07:36 am
Parse CPU to Parse Elapsed | article only | Jun 05, 2003 07:53 am
Some questions about SQL tuning(tkprof) | article only | Jun 05, 2003 07:54 am
Inserting a raw column. | article only | Jun 05, 2003 07:55 am
SQL Query | article only | Jun 05, 2003 08:04 am
parallel nologging not faster | article only | Jun 05, 2003 08:37 am
About BLOB and BFILE | article only | Jun 05, 2003 08:39 am
exact length for ID field | article only | Jun 05, 2003 08:43 am
Dropping a column in a table (Version Before 8.1.x) | article only | Jun 05, 2003 09:23 am
Repoerting Using Range bucket | article only | Jun 05, 2003 01:16 pm
SQL Sharing and Master Details Information. | article only | Jun 05, 2003 01:42 pm
PL/SQL Array processing .. | article only | Jun 05, 2003 03:31 pm
what is the difference between dynamic and static sql | article only | Jun 05, 2003 03:54 pm
Changing Column Headings | article only | Jun 05, 2003 06:28 pm
Insert rows to master table. | article only | Jun 06, 2003 08:41 am
Home made softwares x Migration | article only | Jun 06, 2003 08:57 am
PL/SQL Table Performance. | article only | Jun 06, 2003 09:30 am
some Interview Questions. | article only | Jun 06, 2003 09:51 am
LMT and extend management | article only | Jun 06, 2003 10:34 am
Inserting clob performance issue: enable vs disable strorage in row | article only | Jun 06, 2003 11:06 am
pl/sql table in report | article only | Jun 06, 2003 11:32 am
Multiple rows from table into one row in refcursor | article only | Jun 06, 2003 12:00 pm
Inserting into Varrays | article only | Jun 06, 2003 12:15 pm
DBMS_APPLICATION_INFO and V$SQL/AREA | article only | Jun 06, 2003 12:30 pm
will FGAC reduce performance | article only | Jun 06, 2003 02:50 pm
existence of record before delete | article only | Jun 06, 2003 03:25 pm
Valid/unusable indexes | article only | Jun 06, 2003 04:07 pm
SQL*NET; Net8; Drivers; Versions Used | article only | Jun 06, 2003 08:18 pm
Security -- Basic Authentication on the web over HTTP | article only | Jun 07, 2003 09:11 am
Compare UNION and Subquery | article only | Jun 07, 2003 02:48 pm
Why do we need 'Extents'? | article only | Jun 07, 2003 02:55 pm
oracle shuts down with set pause on in 9i | article only | Jun 07, 2003 02:58 pm
Resource locks from a select query | article only | Jun 07, 2003 03:01 pm
Assing value from Javascript variable into plsql variable in PSP | article only | Jun 07, 2003 03:14 pm
recovery after losing UNDO tablespace | article only | Jun 07, 2003 03:46 pm
Killing Current Session | article only | Jun 08, 2003 09:41 am
Sqlloader and passing passwords from scripts | article only | Jun 08, 2003 09:43 am
storing dynamic HTML pages to files | article only | Jun 08, 2003 04:04 pm
constraint state | article only | Jun 09, 2003 07:06 am
Number showing up in sqlplus as ########## | article only | Jun 09, 2003 07:07 am
Monitoring of programs in Oracle | article only | Jun 09, 2003 07:20 am
Retrieving data from blobs into a file | article only | Jun 09, 2003 08:15 am
Proxy Authentication vs. Application Context | article only | Jun 09, 2003 08:50 am
sysoper? | article only | Jun 09, 2003 10:13 am
One Oracle home or multiple Oracle homes? | article only | Jun 09, 2003 10:44 am
powerful GUI for adminstration | article only | Jun 09, 2003 01:03 pm
Data Migration Process | article only | Jun 09, 2003 03:33 pm
Migration (or is it an upgrade?) | article only | Jun 09, 2003 07:13 pm
INDEX RANGE SCAN vs. INDEX FAST FULL SCAN for _cached_ blocks | article only | Jun 09, 2003 07:55 pm
How to test the impact of dropping a column ... before hand... | article only | Jun 09, 2003 08:52 pm
dump.sql file grows and grows too large | article only | Jun 10, 2003 07:54 am
Why is Index Not Being Used? | article only | Jun 10, 2003 08:16 am
Sqltype Table of Records - Operations and Java | article only | Jun 10, 2003 08:19 am
couple of questions regarding PCTFREE | article only | Jun 10, 2003 08:46 am
Segment management clause | article only | Jun 10, 2003 08:47 am
Converting CLOBS TO VARCHAR | article only | Jun 10, 2003 08:55 am
Parametrised view ! | article only | Jun 10, 2003 09:16 am
SCNs and serializable | article only | Jun 10, 2003 09:23 am
Table Accessed Date | article only | Jun 10, 2003 09:50 am
Trigger/Revoke create session to prevent user logon | article only | Jun 10, 2003 10:44 am
Using LAG to populate a status history | article only | Jun 10, 2003 12:41 pm
Hierarchical query | article only | Jun 10, 2003 12:56 pm
Remote Listener Registration | article only | Jun 10, 2003 08:05 pm
flashback using timestamp | article only | Jun 10, 2003 08:14 pm
Cursor Memory | article only | Jun 10, 2003 08:34 pm
row(s) 1 - 74 of 74
June 04, 2003 Vittorio -- Thanks for the question regarding "ora-03297 when resizing a datafile on a temporary tablespace", version 806
You Asked
I try to resize a datafile abnormally grown, on which there is
only a temporary tablespace.

I have a ora-03297 error. Have i to reaasign another temporary
tablespace to all users that use this tablespace ? Note that sys
and system use this temporary tablespace.

Thanks
and we said...
Well -- you might be best off creating a NEW temporary
tablespace and selecting:

select 'alter user ' || username || ' temporary tablespace
        NEW_TABLESPACE;'
from dba_users
where temporary_tablespace = 'OLD_TABLESPACE'
/

and running the resulting commands and then dropping the old
tablespace and rm'ing the associated files.

The reason this is is because temporary segments in a true
TEMPORARY tablespace are never deallocated until you bounce the
database.  You must stop and restart to get the system to give
up those extents.  This is by design -- we allocate the segment
once and then manage it internally as needed.  In order to
shrink those files, you need to get us to release the segments.

Another way to get us to release the segments is to

alter tablespace temporary_tablespace PERMANENT;
alter tablespace temporary_tablespace TEMPORARY;

that'll get us to give them back as well but it'll take a long
time if there are lots of allocated segments (a really long
time).

I think option number 1 is easiest, create new, alter everyone,
and drop the old one.
Review & Followup
Rating: 3
What do we do in 8.1.6 ? April 17, 2002
Reviewer: Johan from JHB SA
Tom,

I have a situation in Oracle 8.1.6.2 where the temporary tablespace grew larger
than is normally required, due to the creation of a large index.

When I tried the methods used in this post, neither succeeded, since:

SQLWKS> alter tablespace temp permanent
     2>
alter tablespace temp permanent
*
ORA-03217: invalid option for alter of TEMPORARY TABLESPACE

and if I try to create another temporary tablespace, change all the users to use
that tablespace, and try to drop the original temp tablespace, the session that
attempts the drop is blocked by the session used by SMON.

What can I do to resize the tablespace without downtime ?

PS: Would it be possible to display the DB version for which the post is valid
more prominently ? I know some people don't even post the version, but maybe it
could be a mandatory field ?
It would also be useful if the date of the original post is displayed - at the
moment the original date is only apparent when one get to the followup  and see
the date of the followup.

Followup:
The DB version is always displayed right in the subject:

Vittorio -- Thanks for the question regarding "ora-03297 when resizing a
datafile on a temporary tablespace", version 806
                            ^^^^^^^^^^^

You cannot alter a true temporary tablespace to permanent -- that'll never work
(that only works for permanent tablespaces that are being used as "temporary".

It should just be a matter of time for SMON to give up the tablespace -- how
long have you waited?

Rating: 4
cant we resize temporary tablespace ? May 03, 2003
Reviewer: Reader
- oracle 9iR2 -
<quote>
You must stop and restart to get the system to give up those extents.
<quote>

i tried restarting the server and issued ..
SQL> ALTER DATABASE TEMPFILE
  2      'C:\ORACLE\ORA81B\ORADATA\ORACLEB\TEMP01.DBF' RESIZE  1600M;
ALTER DATABASE TEMPFILE
*
ERROR at line 1:
ORA-03297: file contains used data beyond requested RESIZE value

SQL>  select TABLESPACE_NAME,ALLOCATION_TYPE,CONTENTS  from  dba_tablespaces;

TABLESPACE_NAME                ALLOCATIO CONTENTS
------------------------------ --------- ---------
SYSTEM                         SYSTEM    PERMANENT
UNDOTBS1                       SYSTEM    UNDO
TEMP                           UNIFORM   TEMPORARY
.
.
XDB                            SYSTEM    PERMANENT

its true temporary tablespace only.  cant we resize temporary tablespace?

Thanks

Followup:
just drop and recreate it -- tempfiles are instantaneous to do that to.

It is pointless to shrink these files, they will after all just grow again (and
that is expensive to do)

Rating: 4
Thankyou ! May 03, 2003
Reviewer: A reader

Rating: 5
Any Reason to Include Locally Mgd Temporary Tablespaces? June 03, 2003
Reviewer: mary
Tom, sorry if this is an obvious question, but is there any reason for backing
up datafiles associated with locally managed temporary tablespaces (tempfiles)?
(i.e. alter tablespace ... begin backup no longer works).

Followup:
nope. TEMPFILES should not be backed up.

Rating: 5
disregard my last question June 03, 2003
Reviewer: mary
Sorry to waste your time.  I found the answer on metalink--

fact: Oracle Server - Enterprise Edition 8.1
fact: Oracle Server - Enterprise Edition 9
fact: Locally Managed Temporary Tablespace
symptom: ALTER TABLESPACE <tablespacename> BEGIN BACKUP fails
symptom: ORA-03217: invalid option for alter of TEMPORARY TABLESPACE
cause: Alter database and alter tablespace will not work on locally managed
tempfiles as they have no reference in the data dictionary.  There is no need
to back up the tempfiles as they are used and destroyed every time you start up
and shut down the database.

fix:

There is no need to backup the temporary locally manged tablespaces because:

1.  Locally managed tempfiles are always set to NOLOGGING mode.  So thus will
have no undo.
2.  Extents are managed by bitmap in each datafile to keep track of free or
used status of blocks in that datafile.
3.  The data dictionary does not manage the tablespace.
4.  Rollback information is not generated because there is no update on the
data dictionary.
5.  Media recovery does not recognize tempfiles.

June 04, 2003 Murali -- Thanks for the question regarding "Advanced queuing", version 8.0.5.0.0
You Asked
Hi

I am using Oracle 8.0.5.0.0 on NT

When i run the following query, feature not enabled error is
returned.

 execute
dbms_aqadm.create_queue_table(queue_table=>'RawMsgs_qtab',queue_p
ayload_type=>'RAW');

ERROR
------
ERROR at line 1:
ORA-00439: feature not enabled:
ORA-06512: at "SYS.DBMS_AQADM", line 1477
ORA-06512: at line 1

When i check V$OPTIONS Database queuing is set to FALSE

Is this is the problem..? Will AQ will work with Workgroup
editions.?

Please help

Thanks & Regards

Murali
and we said...
In 8.0, AQ was part of enterprise and not standard.

See
http://technet.oracle.com/doc/server.804/a58228/ch3.htm#5014
for the feature/function breakout between the two for 8.0.  For
8.1, see
http://technet.oracle.com/doc/oracle8i_816/server.816/a76962/ch4.htm#73594
in that AQ is no longer broken out -- its in both.
Review & Followup
Rating: 2
enterprise edition with typical installation and feature still not enabled February 24, 2002
Reviewer: coskan from turkey
i have already installed the 8.1.7 enterprise edition which i have downloaded
from otn and without any product licence i am using it for development
environment.
the aq support is not enabled in v$option table also when i ran the script
dbmsaqad.sql from rdmbs/admin it is still false in the v$option but the error
message has been changed
BEGIN dbms_aqadm.create_queue_table (queue_table => 'AppOne_QTab',
multiple_consumers => true, queue_payload_type => 'RAW'); END;

*
ERROR at line 1:
ORA-04068: existing state of packages has been discarded
ORA-04061: existing state of package "SYS.DBMS_AQADM_SYS" has been invalidated
ORA-04065: not executed, altered or dropped package "SYS.DBMS_AQADM_SYS"
ORA-06508: PL/SQL: could not find program unit being called
ORA-06512: at "SYS.DBMS_AQADM", line 55
ORA-06512: at line 1

why this happens then does it impossible to use aq without a metalink account?
ps: and i think no one in oracle corporation knows this because no one could
answer my question

Followup:
I think you just installed something wrong.

You give no operating system but I would guess (since you don't give me a
history of how you got from here to there) that you tried to upgrade a SE
database to EE and didn't follow all of the steps.

It looks like you might not have relinked the database executables (else
v$option would reflect Database queueing as true, that gets done at LINK time).

Also, necessary catalog scripts (eg: catqueue.sql) were not executed.

Typically the reason people cannot answer questions is due to insufficient
information.  Everything can be answered, given sufficient input data.

Suggest you either supply a HISTORY of what you've done exactly to get to where
you are OR you start over with a clean install and a fresh database, I can
assure you AQ will be there.

Rating: 3
typical installation or manual with cat scripts February 24, 2002
Reviewer: coskan from turkey
if i ask again like this way i think it would be more open.
operating system is win2000 pro sp2
installation is typical with database creation included
no any upgrade clean install
and the installation guides says that it is included with typical installation.
also advanced replication is set to false too.
do i have to run the cat scripts manualy?
doesn't  the universal installer do it for me ?
the only thing i am trying to do is to run the scripts in the "step by step b2b
application" which is in the "xml applications"  guide.
i am new with oracle and take your time but my graduation project is about b2b
application and advanced queueing is needed.

Followup:
show me the output of:

c:\> sqlplus system/manager

(i want to see the cut and paste of the SQLPlus banner that shows up)

as well as:

select * from v$version;
select * from v$option;

Rating: 3
May 15, 2003
Reviewer: A reader
hi tom,

how can one get the transaction id of a message?

Followup:
messages don't have a transaction id?  what are you looking for?

Rating: 3
May 16, 2003
Reviewer: A reader
I expected the value of column "ENQ_TXN_ID" of table <AQuser>.<queue_table_name>
to be a "marker" for transaction boundaries (value changes for every commit).

I want to have "piecewise enqueued" messages grouped by the transactions they
were commited with.

what else can one do to achieve this goal? what is ENQ_TXN_ID for (haven't found
a queue table description so far)?

Followup:
define "piecewise enqueued", I have to admit, I'm not familiar with it.

Rating: 3
May 16, 2003
Reviewer: A reader
-- message A
dbms_aq.enqueue(<message A - piece_1>);
     .
     .
dbms_aq.enqueue(<message A - piece_n>);
COMMIT;

-- message B
dbms_aq.enqueue(<message B - piece_1>);
     .
     .
dbms_aq.enqueue(<message B - piece_m>);
COMMIT;

all "pieces" should be recognized as commited by the SAME transaction (to
"reassemble" their content).

Followup:
why are you doing that?  if it is a single message, it is a single message, why
pieces.

Rating: 2
May 17, 2003
Reviewer: A reader
well - lets imagine the following scenario: your application recieves messages
from another application and you can't change message generation. the other
application enqueues messages for single DML statements and a transaction there
usually includes several statements - belonging to the same "business object".
that way you get some messages at STATEMENT level instead of one message at
TRANSACTION (or "business object") level. this leads to the question: how can
one get all "messages pieces" originated by the same transaction to be "grouped"
for further processing? what about that "ENQ_TXN_ID" within the queue table to
achieve this (can't find it in the DQ interface)?

Followup:
the other application has a bug.  it is doing it entirely wrong.

It could have used the replication API's which are specially layered to support
this mechanism.

It could have built its own layer to support it (eg: put the "messages" into a
table/set of tables all keyed by the same key and then MESSAGE JUST THE KEY, the
dequeuer gets the KEY to the related data)

I don't see a bridge from here to there without fixing the application that is
messaging you.

You can try the discussion forums on otn.oracle.com -- there is one for AQ.
Perhaps someone there can give you a different idea to persue.  My idea will be
"fix the messenger, it is broken".

Rating: 3
May 17, 2003
Reviewer: A reader
thanks for the (really bad) news. although there 's really no opportunity to get
message generation changed I would appreciate to know some more details about
what you 've suggested. could you PLS give just a brief overview of HOW TO use
replication for that (in general)? is it the way to go if we don't want to "copy
data" between the applications (at least not without transforming it) but get
notified about its modification only - at a HIGHER level than the current
solution provides?

I've already searched the web and some other sources for hints/advices. that 's
why I now want to know if that ENQ_TXN_ID in the queue table can be used to
identify the transaction. the only comment I've found so far about is "enqueue
transaction id" (see
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96587/qadfacev.htm#54313
.
you are the most valueable source of information I've ever found. please help.

Followup:
read about the deferred queues in the replication and supplied packages guide.

Rating: 5
Preventing trigger failures May 17, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
Currently we have several triggers that perform insert/updates to the remote
servers. At times we get timeout errors or the remote server is not available.
We are looking into using AQ to solve the problem of triggers failing because of
these issues.

The DML statements that we issue from the triggers can be applied
asynchronously.

What we are thinking is that we will setup the AQ environment and enable
propagation and then the triggers will just ENQUEUE the statements into the AQ
queue and Oracle will handle the propagation of this message to the remote
server (if remote site was down, AQ propagation will be able to handle and
recover from it), and upon receipt, the remote server will apply the changes
that trigger initiated. We would design a standard way of constructing the AQ
message so that the sender and recepient both know what is being sent and what
is being received.

This will free the triggers from making remote DML statement(s) and avoid
several potential issues.

What do you think of this approach?

Is this a viable solution for this kind of problem?
Are there other alternatives that you can suggest?

Followup:
sounds like you are re-writing replication which we already wrote?

Rating: 5
Can you elaborate a bit... May 17, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
Can you elaborate a bit as to how we could use replcation to our advantage in
this situation?
Currently, we are using replcation in some of our applications heavily, but
somehow, I'm not able to comprehend how would we be able to make use of it in
this particular situation?

To elaborate more on the requirement, We have a Oracle server that has
Insert/Update/Delete activity going on throughout the day. This schema is
actually a central repository at our site. These set of tables are used by
almost all the applications in our system for mainly query purposes(common set
of data strctures). At times, many of our applications need to be notified of
certain specific changes to this common set of data. When they detect the
specific change they are interested in, they want to update some columns in the
tables of their own schema (which many times is a remote site).

Currently, we do that by creating triggers on the tables of the common schema
and these triggers then fire the appropriate update to the application schema in
the remote server using DB-Links.

Our idea was to push these remote application specific updates to the AQ queue
and then apply them at the remote site later. This way, the common schema tables
will contain the latest updates (since the triggers do not fail anymore, the
transactions done on this common schema are now successful).

By doing this, we will be able to keep this common set of tables as current as
possible, since almost all other applications depend on the up-to-date data in
these tables.

Now, you have given another twist to the discussion by suggesting Replication. I
would be very much interested in knowing how we could make use of replication in
this scenario?
Thanks,

Followup:
you are replicating modifications to tables over a dblink.

you could copy the tables the triggers update 'remotely' to this central site.

they triggers update a local table, which in turn updates asyncronously its
copies at the remote site.

so instead of updating a remote table using two phase commit, you update a local
table which replicates its changes in the background to the tables that are
being updated in real time now.

Rating: 3
May 18, 2003
Reviewer: j.
isn't this something ORACLE streams could do?

Followup:
facilitate, sure.

do, only if you use the "replication" part of streams and do it as I described
above.

Else, it would just be a tool to facilitate it, sort of like AQ.

Rating: 3
May 21, 2003
Reviewer: A reader
hi tom,

I was lucky! further investigating my "how can one dequeue messages enqueued by
the SAME transaction?" issue (above) I've found out the following two facts:

from ORACLE's docs:

<quote>
Messages enqueued in the same transaction into a queue that has been enabled for
message grouping will form a group. If only one message is enqueued in the
transaction, then this will effectively form a group of one message. There is no
upper limit to the number of messages that can be grouped in a single
transaction.

In queues that have not been enabled for message grouping, a dequeue in LOCKED
or REMOVE mode locks only a single message. By contrast, a dequeue operation
that seeks to dequeue a message that is part of a group will lock the entire
group. This is useful when all the messages in a group need to be processed as
an atomic unit.

When all the messages in a group have been dequeued, the dequeue returns an
error indicating that all messages in the g
roup have been processed. The application can then use the NEXT_TRANSACTION to
start dequeuing messages from the next available group. In the event that no
groups are available, the dequeue will time-out after the specified WAIT period.
</quote>

the other thing is: I've got confirmed by ORACLE that the attribute ENQ_TXN_ID
in queue table CAN BE USED to identify the eunqueing transaction (although it's
NOT accessible through DQ interface provided by supplied packages).

I thought this would be something one might be interested in (here). so I'll no
dig into this "message grouping" ...

Rating: 3
what if dequeuing through "listeners" only succeeds once after every database start up? June 03, 2003
Reviewer: j.
hi tom, I've registered 4 PL/SQL procedures on 1 queue and set AQ specific
database parameters to the recommended values. if I enqueue more then one
message only the first one is caught by every "listener". subsequent messages
get caught by (consistently) 3 out of the 4 "listeners" only.

I've absolutely no idea anymore how I could investigate this issue and the
possible reasons could be. can you pls help?

Followup:
well, i'd need a test case -- but please -- don't put it here.

sometime when I'm taking new questions, I can look at it there.

bear in mind, 999 times out of 1,000 -- during the development of my test case
-- I find my mistake.  it would have to be a script I can actually run myself...

Rating: 3
Capturing updates from TCA model June 03, 2003
Reviewer: BShah from USA
Tom,

Your reply to above made me think about my approach to solve my current problem.
I was wondering if you could spend few minutes in answering my query.

I need to capture updates to Oracle Applications TCA data and send it to the
third party as XML message or send any sort of reliable message like queue. I
need to capture updates happening to a set of 5 to 7 tables and create a
customer record consisting of 250 attributes to be sent to the third party
software.

Could you please tell me what's the approach I should take. AQ or replication or
XML messaging(HTTP post) or is there any other option I have ? Where can I find
more information on this.

Thanks a lot for your time.

In your new book are you going to talk about any of this topic especially XML. I
didn't find anything in your current book regarding XML.

Followup:
Hmm, XML without a stable datastore is what?  Nothing.

AQ is a messaging mechanism.
XML is an encoding scheme.

You want to queue a message, message payload will be XML, someone else will
dequeue XML and process it, removing it from the queue when it was processed.

Rating: 5
How many queues December 09, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
We have a requirement to create queues to exchange messages from source server
to destination server.

There will be several applications that would want to utilize AQ queue to send
message across servers.

We could create one single queue which could handle incoming messages and
forward them to destination queues based on the queue agents (message
receipients) or we could create one queue per application to forward that
application specific messages.

Which might be better in this case?
One single queue at source handling message transfers for all the interested
applications or one queue for each applicaiton that is interested?

Will there be any contention issues (or others) if we use one single queue as
opposed to one queue for each application?
Thanks,

June 04, 2003 Ruben -- Thanks for the question regarding "Number Of Extents", version 8.1.7
You Asked
Hi, Tom

There is a lot debate concerning to number extents to be created for
segments. Some people think different about this myth.
I had read all of your links
http://asktom.oracle.com/pls/ask/f?p=4950:8:196471::NO::F4950_P8_DISPLAYID:730289259844
that disscus this point, and I had find that
no matter the number of extents for segments we could have for it.

I'm agree with you, because I have did tests that shows there's not difference
having 1,10,100,1000 extents per table, table scanning make same time, with
insignificant difference. Following is test I did.
Both tables have same structure and same data in different LMT.

SEGMENT_NAME              EXTENTS      BYTES     BLOCKS INITIAL_EXTENT
---------------------- ---------- ---------- ---------- --------------
F                               1    5242880       1280        5242880
M                             167    4788224       1169          28672

tkprof for table F (Full Table Scan)
--------------------------------------

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          1          0          1           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     3943      0.84       0.90       1170       5038          4       59129
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     3945      0.85       0.91       1171       5038          5       59129

tkprof for table M (Full Table Scan)
------------------------------------

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.01          1          0          1           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     3943      0.95       0.89       1166       5038         20       59129
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     3945      0.97       0.90       1167       5038         21       59129

But I have found next paragraph in book "Oracle Performance Tuning" page 429
from Mark Gurry and Peter Corrigan.

"Your goal is to minimize the number extents on disk. Access to contiguous
areas of disk is much faster than access to noncontiguous area. In one test
that we did on a 4,000-row table; we found that when the entire table fit
on one extent, it took 0.76 second to scan it; when the table was spread over
10 extents, it took 3.62 seconds.
We have found that the existence of a small number of such extents (fewer than
five) doesn't seem to affect performance very much, but it is a good practice
to store your objects in a single extent."

Regarding to contiguous areas of disk mentioned above, you say :

"As for the "seek time" -- you assume that an extent is contigous on disk.  They

are not.  When I ask a file system for 100meg of space -- it does not give me
100meg of contigous space, it gives me space from here, there, everything (its
been a loooonngggg time since you had to use CCF (create contigous file) to
create a database file!).  Even if everything was in a single extent -- its
still ALL OVER the disk.   Add in volume managers, raid arrays, etc etc etc --
your data is no where NEAR contigous."

"PHYSICALLY, on the disk, an extent is not contigous.  File systems dole out
space in tiny chunks.  They give out space as they find it.  They do not
allocate contigous space."

Another Question :

I had make some space estimation for 10 tables, I had find its proper initial
extents
for each one, 8 of them with 60MB of Initial extent and 2 of them with 100MB.
This give me an average of 30 extents per table yearly.
But in "HOW TO STOP DEFRAGMENTING AND START LIVING: THE DEFINITIVE WORD ON
FRAGMENTATION". in 2.1.3, authors says that :

2.1.3 The Extents Size for all Data Tablespaces Should be one of 160K, 5120K, or
160M

"These three extent sizes are all that you will ever need for tables and
indexes.
Using more than these three extent sizes will
not significantly improve performance or space utilization but it will
complicate administration and potentially induce
fragmentation."

Do I should not use an extent size of 60MB and 100MB for my own tables, and
should I use  one of 160K, 5120K, or 160M.?
Is  this a rule of thumb ?
Am I wrong with my own extents size ?

Can you explain this please, thanx.

Thanks in advance for your comments.

Regards.
and we said...
Man, I don't get it.  "facts is facts", the evidence is right in front of our
noses.  "Opinions, rumors" -- they don't count when you have "facts".  To all of
the people described by "Some people think different about this myth." -- they
must prove it (if it is true, they should be able to do that right??)

As for:

But I have found next paragraph in book "Oracle Performance Tuning" page 429
from Mark Gurry and Peter Corrigan. ....

Well -- those guys are "wrong".  You showed it to youself.  I can show it to
you.  They made a mistake, they were incorrect.  They did the test wrong in some
way (did they give you the test to reproduce??? if not, question the test.  In
my book -- if I say something, i give you the code to try and see for yourself
every time).

Here, here is a simple test:

ops$tkyte@ORA717DEV.US.ORACLE.COM> create table t1 storage ( initial 528k )
tablespace dict_managed
  2  as
  3  select * from all_objects where rownum <= 4000;

Table created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> create table t2 storage ( initial 1k next 1k
pctincrease 0 ) tablespace dict_managed
  2  as
  3  select * from all_objects where rownum <= 4000;

Table created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> select segment_name, extent_id, blocks
  2    from user_extents
  3   where segment_name like 'T_'
  4   order by 1, 2;

SEGMENT_NAME                    EXTENT_ID     BLOCKS
------------------------------ ---------- ----------
T1                                      0         70
T2                                      0          2
T2                                      1          1
T2                                      2          1
T2                                      3          1
T2                                      4          1
T2                                      5          1
T2                                      6          1
T2                                      7          1
T2                                      8          1
T2                                      9          1
T2                                     10          1
T2                                     11          1
T2                                     12          1
T2                                     13          1
T2                                     14          1
T2                                     15          1
T2                                     16          1
T2                                     17          1
T2                                     18          1
T2                                     19          1
T2                                     20          1
T2                                     21          1
T2                                     22          1
T2                                     23          1
T2                                     24          1
T2                                     25          1
T2                                     26          1
T2                                     27          1
T2                                     28          1
T2                                     29          1
T2                                     30          1
T2                                     31          1
T2                                     32          1
T2                                     33          1
T2                                     34          1
T2                                     35          1
T2                                     36          1
T2                                     37          1
T2                                     38          1
T2                                     39          1
T2                                     40          1
T2                                     41          1
T2                                     42          1
T2                                     43          1
T2                                     44          1
T2                                     45          1
T2                                     46          1
T2                                     47          1
T2                                     48          1
T2                                     49          1
T2                                     50          1
T2                                     51          1
T2                                     52          1
T2                                     53          1
T2                                     54          1
T2                                     55          1
T2                                     56          1

58 rows selected.

alter tablespace dict_managed offline;

alter tablespace dict_managed online;

alter session set sql_trace=true;
set termout off

select  * from t1;
select  * from t1;
select  * from t1;
select  * from t1;
select  * from t1;

select  * from t2;
select  * from t2;
select  * from t2;
select  * from t2;
select  * from t2;
set termout on

So, the results are:

select  *
from
 t1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        5      0.00       0.01          0          0          0           0
Execute      5      0.00       0.00          0          0          0           0
Fetch     1340      0.32       0.33         58       1615         60       20000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1350      0.32       0.34         58       1615         60       20000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 48

Rows     Row Source Operation
-------  ---------------------------------------------------
   4000  TABLE ACCESS FULL T1

********************************************************************************

select  *
from
 t2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        5      0.00       0.00          0          0          0           0
Execute      5      0.00       0.00          0          0          0           0
Fetch     1340      0.34       0.37         58       1640        135       20000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1350      0.34       0.37         58       1640        135       20000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 48

Rows     Row Source Operation
-------  ---------------------------------------------------
   4000  TABLE ACCESS FULL T2

Hmm, makes me suspect of their results -- how about you?  (btw: if you run this
over and over, sometimes T2 beats T1 and sometimes not -- meaning that any
differnce you see above is "noise", ignore it -- those results are the SAME)

Your goal is to NOT worry about tables with less then say about 1,000 or so
extents.  It is NOT worth losing sleep over.

It is a BAD practice to store your sizable objects in a single extent (and no, i
am not recommening you use 57 extents like I did above for such a trivial table
-- 4,000 rows is TINY, thats too small to even test with in my opinion!)

(and as I said above, extents are LOGICALLY contigous and physically ALL OVER
the place.  The odds of the blocks being right next to eachother is about ZERO
in the real world -- doesn't matter anyway since we do have these "multi-user"
systems and anyone who even dreams the head will stay where they left it is
wrong)

As for that paper, they make a compelling argument however, it predates locally
managed tablespaces with the ability to FORCE an extent size.  I would use the
extent size that makes you happy.  30 extents/year sounds great to me.  Should
give you a good 30 or 40 years before you would be tempted to reorg it.
Review & Followup
Rating: 5
Number of Extents October 19, 2001
Reviewer: Ruben
thanks for you worthwhile opinion, you always give us
very good examples. Thanks again

Rating: 5
elapsed less than cpu October 19, 2001
Reviewer: A loyal reader from USA
Tom:

I see this every now and again, and have wondered about it:

tkprof for table M (Full Table Scan)
------------------------------------

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.01          1          0          1           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     3943      0.95       0.89       1166       5038         20       59129
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     3945      0.97       0.90       1167       5038         21       59129

Why is the elapsed time less than the cpu time?

Thanks!

Followup:
Because we are measuring things using a clock that measures 1/100's of a second
but the things we are measuring take place in LESS time then that interval.

Each time we measure an event therefore, we could be off by +/- 1/100.  Over
many observations, this error can add up.  Basically, when measuring something
that takes SO LITTLE time, the timings we see are +/- some value.  It is like
trying to measure something that takes less then a second using a wrist watch.
Sometimes the event will span a second (from 12:01:01 to 12:01:02).  Other
times, it will appear to be instantaneous from 12:01:03 to 12:01:03 -- the
seconds didn't advance.

If you re-run, the elapsed will exceed CPU, run again, it'll be exactly the
same, do it again and it'll be less

(thats why I said in my answer that even though the numbers differ slitghtly,
that is just "noise" -- its this cumulative error creeping in)

Rating: 5
re: elapsed less than cpu October 20, 2001
Reviewer: A loyal reader from USA
Senior Moment! Senior Moment!

Totally missed ..."even though the numbers differ slightly,
that is just "noise" -- its this cumulative error creeping in)" the 1st time
through.  Sorry for the bother!  That's what I get for reading 'AskTom' late at
night.

Rating: 5
Serial Reads and Writes June 03, 2003
Reviewer: Bala from Kuala Lumpur
Tom,
What do you mean by
"(and as I said above, extents are LOGICALLY contigous and physically ALL OVER
the place.  The odds of the blocks being right next to eachother is about ZERO
in the real world -- doesn't matter anyway since we do have these "multi-user"
systems and anyone who even dreams the head will stay where they left it is
wrong)"

In any given extent the blocks are contiguous isnt it (I mean PHYSICALLY - how
could they be contiguous LOGICALLY). If they are physically contiguous then
isn't reading contiguous blocks faster than reading scattered blocks.
I understand your point about Multi user systems and the head not stying the
place where we left etc, but one of the main reasons Oracle has a Redo log is
this isn't it (Serial writes...)
All the statistics above prove your point...
I am still not convinced.
Correct me if my understanding is wrong

Followup:
if they were physically contigous, why do you have to defragment your disks?

files are not contigous on disk.  they are data structures managed by a file
system.  As files are used, space is allocated and the space is never 100%
contigous (unless you have a brand new disk, never erase any data, allocate
single files at a time, never grow or shrink files -- eg: you work in a dust
free lab environment ;)

Files themselves, provided by the OS, are NOT contigous things -- they are
spread all over the place.

Rating: 5
How about Redo logs June 04, 2003
Reviewer: Bala from Kuala Lumpur
Tom,
On page 94 you say
"The logs are written to with Sequential I/O as compared to the scattered I/O
DBWn must perform....This is one of the main reasons for having a LGWR and redo
logs in the first place"
The same problems hold there also, issues like fragmentation, O/S managing the
files etc.... making the writes not sequential..

Followup:
You do a 4 row update.

LGWR says to OS -- please write these 1000bytes of redo data out in this file.

dbwr would have to say "please write this block here, this block there, this
block over there, this other block over here" (for the data), "this block over
yonder, that block back there, this one over here, that other one down there"
(for the index) and so on.....

one sequential IO vs tons and tons of scattered block writes.

Rating: 5
Understand June 08, 2003
Reviewer: Bala from Kuala Lumpur
Clear. Period.

June 04, 2003 Arvind -- Thanks for the question regarding "Global variable across different sessions", version 8.1.7
You Asked
Hi Tom,
I have a very small problem. Can we store any value using plsql package in the
manner that it should be same for all sessions using that package and if any
session changes that value then it should become the new value for that variable
for all sessions.? Or in other words same like Static variable in Java for a
class's different objects.
Thanks in advance
Arvind Kapil
and we said...
Well, thats not like a static variable in a java program -- the analogy would be
that if I changed a static variabe in java -- every program running out there
would see the same value!

PLSQL, just like java, runs in a VM.  Every user runs their own copy.  A PLSQL
global is just like a Java global in this regards.  Every package (class) that
access it in the same session ( process) will see the same value.

So, plsql and java are alike in this case.

Your solution will be to use a database table:

create table global_value ( x int );
insert into global_value values ( 0 );

create or replace package get_global
as
   function val return number;
   procedure set_val( p_x in number );
end;
/

create or replace package body get_global
as

function val return number
as
   l_x number;
begin
   select x into l_x from global;
   return l_x;
end;

procedure set_val( p_x in number )
as
  pragma autonomous_transaction;
begin
   update global set x = p_x;
   commit;
end;

end;
/

You need the locking and concucrrency controls afforded by the database, you
need to use the database to share the information.
Review & Followup
Rating: 5
October 09, 2001
Reviewer: A reader
Kinldy elucidate the following

"PLSQL, just like java, runs in a VM.  "

Thank you

Followup:
Do you understand how java runs?  In a VM (virtual machine).  Well, PLSQL does
exactly the same.  there is a virtual machine that runs the code.

PLSQL is compiled to pcode (byte code) and run by an interpreter that pretends
to be an operating system.

Java is compiled to byte code (pcode) and .....

Its the same thing.  for the same reason that java is portable -- PLSQL is
portable.  The same plsql runs on an OS/390 mainframe as does on my windows
laptop as does on my Unix system.  Oracle ported the plsql VM so you don't have
to port your code.

Rating: 3
Only solution? October 09, 2001
Reviewer: Arvind from USA
Is this the only solution of my problem ? No other way

Followup:
In order to get a "super global variable" as you would like -- you would use a
database table.

Think about it -- its a pretty hard problem.  What would your solution in Java
be if all of your java processes needed to access the same variable -- you'd
have to write some OS dependent stuff and use shared memory, a memory mapped
file or an OS file.

The database table is actually a very elegant solution as it does all of the
concurrency control and such for you.  The amount of code you have to write for
such a functionality is very small.

Rating: 4
is it possible... June 03, 2003
Reviewer: Alvin from Philippines
if i create a package....

---
CREATE OR REPLACE PACKAGE BODY DUMMY
AS
G_VAR NUMBER := 10 ;
BEGIN
NULL;
END;
---

would i be able to pry out the value (10 or any current value) of G_VAR on
regular sql?

I'm asking this because of the procedure crypt_pkg.setkey('xxxx1111') in the
dbms_obfuscation_toolkit section of your book stores the data on a global
variable.

Followup:
as you have it, yes of course -- it is hard coded.

as i have it, no, it is not hard coded.  It is a global package body variable
(accessible ONLY to the code in the package body) that is set via a "setter"
function.

Rating: 4
what about a global context (if 9i) June 03, 2003
Reviewer: j.
would this be something to think of?

Followup:
to share across all sessions -- yes, that would work.  They could all share a
single global application context.

Rating: 5
An example... June 03, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
We have a similar requirement to have global values accesible across multiple
sessions.
We are currently in the process of upgrading to 9i, and was thinking of using
the global context feature to do that.
Can you provide us with a simple example to do this on 9i using global context?
Appreciate your help,
Thanks,

Followup:
ops$tkyte@ORA920> create or replace context App_Ctx using My_pkg
  2  ACCESSED GLOBALLY
  3  /

Context created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package my_pkg
  2  as
  3          procedure set_ctx( p_name       in varchar2,
  4                                             p_value      in varchar2 );
  5
  6          procedure init;
  7  end;
  8  /

Package created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package body my_pkg
  2  as
  3          g_session_id number := 1234;
  4
  5          procedure init
  6          is
  7          begin
  8                  null;  -- elaboration code does it all
  9          end;
 10
 11          procedure set_ctx( p_name       in varchar2,
 12                                             p_value      in varchar2 )
 13          as
 14          begin
 15                  dbms_session.set_context
 16                  ( 'App_Ctx', p_name, p_value, NULL, g_session_id );
 17          end;
 18  begin
 19                  dbms_session.set_identifier( g_session_id );
 20  end;
 21  /

Package body created.

ops$tkyte@ORA920> pause

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec my_pkg.set_ctx( 'Var1', 'Val1' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec my_pkg.set_ctx( 'Var2', 'Val2' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select sys_context( 'app_ctx', 'var1' ) var1,
  2         sys_context( 'app_ctx', 'var2' ) var2
  3    from dual
  4  /

VAR1       VAR2
---------- ----------
Val1       Val2

ops$tkyte@ORA920>
ops$tkyte@ORA920> connect /
Connected.
ops$tkyte@ORA920>
ops$tkyte@ORA920> select sys_context( 'app_ctx', 'var1' ) var1,
  2         sys_context( 'app_ctx', 'var2' ) var2
  3    from dual
  4  /

VAR1       VAR2
---------- ----------

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec my_pkg.init

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select sys_context( 'app_ctx', 'var1' ) var1,
  2         sys_context( 'app_ctx', 'var2' ) var2
  3    from dual
  4  /

VAR1       VAR2
---------- ----------
Val1       Val2

Rating: 4
dbms_session.set_identifier June 03, 2003
Reviewer: Robert from PA
>>ops$tkyte@ORA920> create or replace package body my_pkg
>>2  as
>>3          g_session_id number := 1234;
.......snip
>>18  begin
>>19   dbms_session.set_identifier( g_session_id );
>>20  end;

Is g_session_id  just any arbitrary number you can set ?
What if another program do the same (set_identifier) using the same value ?
Thanks

Followup:
g_session_id is just some arbitrary session id that all sessions that wish to
shared this context must use.

I showed "another program" using the same number -- i logged out / back in --
that is "another program"

if you want to share the context, the key is 1234 in this example.

Rating: 5
What privilege? June 04, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
It seems that in order to create/drop a context a user needs the "CREATE ANY
CONTEXT" and "DROP ANY CONTEXT" system privilege.
Is this the only privilege required?
Will it be considered as a security risk by our DBA's? (since the privilege says
"CREATE ANY...").
Or you recommend that we ask the DBA's to create it one time for us, and then we
keep using it?

Once created, can we access this context without the "CREATE ANY CONTEXT"
privilege?
Thanks,

Followup:
I would have the DBA's or an appropriately priveleged user create it one time.

You do not need create any context to access it.

June 04, 2003 Yogesh -- Thanks for the question regarding "Array Processing ", version 8.1.7
You Asked
I want to take your remarks on the code / approach which I'm using  in the
following code

I'll be getting the name, value pair as a input from the user .. the pair will
include  name of the field its value ... I need to take those columns and values
and update a table .. I don't know who many values I'll be getting .. so I have
created a type and created a table of the same .. after creating the array ... I
i'll read that array and then build my update statement ... following is the
code

create or replace type PosTypeRecord
as object
(
FeatureName VARCHAR2(200),
FeatureValue VARCHAR2(200)
);

create or replace type PosTypeTable as table of PosTypeRecord;

create or replace PROCEDURE InsertPosTypeTable
(
p_Tid VARCHAR2,
p_FeatureName VARCHAR2,
p_FeatureValue VARCHAR2,
p_Status NUMBER,
p_Message VARCHAR2
)
AS

v_PosTable PosTypeRecord;
cursor c1 is select * from poptest;

/* here to simulate the name and the value pair I'm selecting the data from one
of the table*/

x Varchar2(200);
y Varchar2(200);
i Number :=1;
v_String VARCHAR2(32000);

BEGIN
open c1;

loop
Fetch c1 into x,y;
exit when c1%notfound;
v_PosTable.extend;
v_PosTable(i) := PosTypeRecord(x,y);
i:=i + 1;
end loop;
close c1;

i=1;

FOR i in 1 .. v_PosTable.count
LOOP
v_String := v_String || v_PosTable(i).FeatureName
||'='||v_PosTable(i).FeatureValue||',';
END LOOP;
EXECUTE IMMEDIATE ' UPDATE posupdatetest SET '||v_String || ' WHERE Tid ='||
CHR(39)|| p_Tid || CHR(39);
COMMIT;
p_Status =0;
p_Message = 'Success';
END;

Please suggest, shall I continue with this approach or should use some other way
... can we build a cursor here by using something ?

One more question in the same regard
instead of using v_PosTable.extend; ... I guess I need to use
v_PosTable.extend(n,i);  here n=1, but as type is composit ... how can I specify
the value of "i" ...
and we said...
that must be

for i in 1 .. v_posTable.count
loop
execute immediate 'update posupdatetest set ' ||
                v_posTable(i).featureName || ' = :x where tid = :y'
        using v_PosTable(i).FeatureValue, p_tid;
end loop;

You MUST use bind variables -- MUST.

Also, return codes are so 70's -- exceptions -- just let the expecption
propagate to the client.

you just use v_posTable.extend( N );  that'll add N elements to the table.
Review & Followup
Rating: 4
DB calls June 03, 2003
Reviewer: Yogesh from Pune India
I got ur point of bind variables but, if u see the loop I'm first of all
generating the updation string and finally executing the update query .. so in
that case there will be only one DB call ... But in if I use update in loop it
is going to be N nos of DB calls ... what approach should we adopt

thanx for the reply

Followup:
wow, your keyboard is breaking up -- it is losing really important characters.
Seems to be centered around vowels, key characters in this language.  better
look into that.

You need to use bind variables.  Use DBMS_SQL and bind or use an application
context and bind.

Me, I would just as soon:

  for x in 1 .. v_posTable.count
  loop
      if ( v_posTable(i).featureName = 'C1' )
           then l_c1 := v_posTable(i).featureValue(i);
      elsif ( v_posTable(i).featureName = 'C2' )
           then l_c2 := v_posTable(i).featureValue(i);
      ...
      elsif ( v_posTable(i).featureName = 'CN' )
           then l_cN := v_posTable(i).featureValue(i);
      end if;
   end loop;

   update posupdateTest
          set c1 = nvl(l_c1,c1), c2 = nvl(l_c2,c2), .....
    where ....;

and not use dynamic sql at all.

Rating: 5
Test it June 03, 2003
Reviewer: Trevor Welch
Yogesh

Why don't YOU create a couple of test case and
bench mark it for yourself?

Rating: 4
Thanks June 04, 2003
Reviewer: Yogesh from Pune India
yes, That is one more good way of doing it .. But in this case I need to define
'N' number of variables for my feature names .. and if number of features
increases, I need to change the code ...

Followup:
then use dbms_sql or application contexts

you MUST -- repeat -- MUST bind.

Rating: 5
Thanks once again June 04, 2003
Reviewer: Yogesh from Pune India
got it ..

June 04, 2003 Sagi -- Thanks for the question regarding "2GB Limit", version 8.1.6
You Asked
Hi Tom!

You are very help full for a lot of guys like me. We all thank you for this.

I have been waiting from a long time to post my question. Since I was not able
to, I am posting 2 questions together. Plz. bear with me.

Q1) In Unix can'nt we use the "ulimit" to increase the upper limit of a file
over 2gb and then use the export utility to create a dump file thats greater
than 2GB. Why should we use the pipe in the bg process and compress it.
Increasing the file limit size using "ulimit' works. I don't have unix O/S.
Therefore I am writing this to u.

Q2) Tell me if this case is using bind variable or not.

CREATE OR REPLACE emp_ins (iempno number, iename varchar2, ideptno NUMBER)
AS
BEGIN
  INSERT INTO EMP (EMPNO, ENAME, DEPTNO)
  VALUES
  (iempno, iename, ideptno) ;
END ;

Thanx in advance.

Sagi.
and we said...
q1) export uses 32 file system apis.  These apis do not support files >2gig.
Its not a ulimit issue, its a file system api issue.

If you use 64bit Oracle -- this limit disappears.

q2) yes it is.  plsql variables in SQL are in fact bind variables.  You can
prove this to yourself using tkprof
http://asktom.oracle.com/~tkyte/tkprof.html
simply run sql_trace, run your procedure, use tkprof to format the trace file
and you'll see your insert is rewritten with binds.

Its almost IMPOSSIBLE to not use bind variables when you have static SQL in
PLSQL (thats a very good reason to use PLSQL!)
Review & Followup
Rating: 4
December 06, 2001
Reviewer: Tianhua Wu from Atlanta, Ga
Hi Tom,

I am using Oracle 8.0.5.2.1 on Solaris 2.6 32 bit.  The OS support large file
system.  I had tested this about two years ago:

The export generated a dump file size around 3GB and I sucessfully used this
dump file to import to another database.  So I do not think 2GB limit is there
anymore.

Thanks,

T Wu

Followup:
If you had 64bit Oracle -- no limit.
If you have 32bit Oracle -- 2gig limit, unless you use a pipe.  the DATABASE
itself supported larger files, but not export.

Rating: 3
Sagi December 07, 2001
Reviewer: Sagi from India
Thank you for the explanation.

How do we know whether Oracle is 64 or 32 bit. Which versions of Oracle are 64
bit.

Thanx.

Sagi.

Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:364418037604
...

Rating: 4
May 23, 2002
Reviewer: Tianhua Wu from Atlanta, Ga
Hi Tom,

I have a chance to test again today.  I am using 8.1.7.2  32-bit Oracle.  I
generate an 8GB dump file without using a pipe.  And I can use this dump file to
import sucessfully.  However I do not have chances to repeat it on an 8.0.5.2
databases.
I believe the limitation of 2GB is gone.

Rating: 4
2 GB limit June 04, 2003
Reviewer: Nikhil Mishra from INDIA
hi TOM,

  u stated in one of ur followups that its impossible not to use bind variables
in PL/SQL.

  I have a package that  generates flat files using utl_file package. It
processed some couple of thousands records. it was like :

.........

   for cursor in (select 1,2,3,4 from header) loop

     select 1,2,3 bulk collect into collection
      from detail where detail.1=cursor.1 and
       detail.2=cursor.2.....;

    for i in 1..collection.count loop
       select 1,2,3 from detail2
          where detail2.1=collection.1(i) and
                detail2.1=collection.2(i)
    end loop;
    end loop;

.........

   i know u hate selects in loops but i got this package after it was already
developed and assigned the task to improve its performance.

   To process data ...hardly around  50,000 , the above procedure used to take
around one hour.

   i then transferred the above code as follows.

       for cursor in (select 1,2,3,4 from header) loop
    execute immediate
     'select 1,2,3 bulk collect into collection
      from detail where detail.1=:1 and
       detail.2=:2.....'
     using
      cursor.1,
      cursor.2;

    for i in 1..collection.count loop
   execute immediate
       'select 1,2,3 from detail2
          where detail2.1=:1 and
                detail2.1=:2'
       using
          collection.1(i),
          collection.2(i)
    end loop;
    end loop;

 The result was that now the procedure took hardly 1 minute to process the same
amount of data. i got the feeling that this was bc'oz of the use of bind
variables in case of execute immediate. Correct me if i am wrong.

   If the variables in pl/sql are infact bind variables, how come the
performance improved so drastically when i used the 'Execute immediate .....'
instead of the simple select.

thanx
 Nikhil

Followup:

it is impossible to not properly use bind variables with STATIC sql in plsql.

it is entirely possible to not use them correctly with dynamic sql.

Your dynamic sql is not real code.  it does not work that way.  there is no such
thing as bulk collect into collection inside of a string like that.  the other
execute immediate is never fetched from.  that code does nothing.

I cannot fathom the case where the same code in dynamic sql would be faster then
static sql.

Rating: 4
June 04, 2003
Reviewer: Nikhil from India
HI tom

  sorry for not putting the real code. actually its too huge and garbled up. so
dint want to put it.

  however i would like to draw ur attention to one thing.

 In the where clauses we use some predefined constants. was that causing
excessive parse of the query and was later solved by using the execute
immediate.

FOR v_hdr IN(

    SELECT
        *
    FROM
        t_custtrans_hdr_uhh
    WHERE
    cth_nnm_status_f=VALID AND
    cth_vpk_transtype=TRANSTYPE

)    LOOP

         EXECUTE IMMEDIATE '
            SELECT
                    SUM(NVL(units_sold        ,0)),
          SUM(NVL(units_returned,0)),
          SUM(NVL(free_samples    ,0)),
          SUM(NVL(Gross_Amt    ,0)) ,
          SUM(NVL(sales_amt_for_tax,0))
            FROM(
                        SELECT

DECODE(ctd_vpk_transtype,''01'',ctd_vnm_item_qty)    units_sold,

DECODE(ctd_vpk_transtype,''02'',ctd_vnm_item_qty,''03'',ctd_vnm_item_qty,''09'',c
td_vnm_item_qty)  units_returned,

DECODE(ctd_vpk_transtype,''05'',ctd_vnm_item_qty) free_samples,

DECODE(ctd_vpk_transtype,''01'',DECODE(ctd_vpk_event_nbr,0, ctd_vnm_item_qty *
ctd_vnm_price),0) Gross_Amt,

DECODE(ctd_vpk_transtype,''01'',ctd_vnm_item_qty * ctd_vnm_price,0)
sales_amt_for_tax
                        FROM
                                    t_custtrans_dtl_uhh
            WHERE
                        ctd_vpk_country_code = :1 AND
                        ctd_vpk_company_code = :2 AND
                        ctd_vpk_docseq_nbr   = :3 /*AND
                        ctd_vpk_visitkey     = :4 AND
                        ctd_npk_transkey     = :5 */
                    )'
                INTO
                        total_units_sold        ,
                        total_units_returned,
                        total_free_samples,
                        Total_Gross_Amt,
                        total_sales_amt_for_tax
                USING
                 v_hdr.cth_vpk_country_code,
         v_hdr.cth_vpk_company_code,
         v_hdr.cth_vpk_docseq_nbr,
         v_hdr.cth_vpk_visitkey  ,
         v_hdr.cth_npk_transkey;

                     EXECUTE IMMEDIATE
                    'SELECT
                                SUBSTR(lvl2_node_nbr,2,2),
                                SUBSTR(lvl3_node_nbr,2,3),
                                SUBSTR(lvl4_node_nbr,2,3)
                    FROM
                        (SELECT
                            * FROM
                                (
                                    SELECT
                                                 lvl1_node_nbr,
                                                 lvl2_node_nbr,
                                                  lvl3_node_nbr,
                                                 lvl4_node_nbr,
                                                 rt_ctry_code,
                                                 rt_co_code,
                                                 rt_nbr,
                                                 rt_eff_dt,
                                                 rt_end_dt,
                                                 procs_revis_nbr,
                                                 MAX(procs_revis_nbr) OVER
(PARTITION BY rt_ctry_code,rt_co_code,rt_nbr) rt_max_procs
                                    FROM
                                        rt_perf
                                    WHERE
                                    procs_revis_nbr <= (SELECT procs_revis_nbr
FROM actv_revis_tbl WHERE type_nbr=''000'')
                                )
                        WHERE
                            procs_revis_nbr=rt_max_procs
                    ) rtp
                            WHERE
                                rtp.rt_ctry_code = :1 AND
                                rtp.rt_co_code   = :2 AND
                                rtp.rt_nbr         = :3 AND
                                :4 BETWEEN rtp.rt_eff_dt AND rtp.rt_end_dt'
                  INTO
                                lvl1,
                                lvl2,
                                lvl3
                    USING
                          v_hdr.cth_vpk_country_code      ,
                v_hdr.cth_vpk_company_code      ,
                TO_NUMBER(v_hdr.cth_vnm_routeid),
                TO_DATE(v_hdr.cth_vnm_document_dt,'dd/mm/yyyy');

                  EXECUTE IMMEDIATE
                            'SELECT

DECODE(tax_exemp_flg,''0'',''N'',''1'',''Y''),
                                    tax_exemp_nbr,
                                    tax_id_code
                            FROM
                            (
                                SELECT *
                                   FROM
                                (
                                    SELECT
                                        ctry_code,
                                        co_code  ,
                                        rt_nbr,
                                        tax_exemp_flg,
                                        tax_exemp_nbr,
                                        tax_id_code,
                                        cust_nbr ,
                                        eff_dt   ,
                                        end_dt   ,
                                        procs_revis_nbr,
                                        MAX(procs_revis_nbr) OVER (PARTITION BY
cust_nbr,rt_nbr,ctry_code,co_code) cust_max_procs
                                        FROM
                                            cust_perf
                                        WHERE
                                              procs_revis_nbr <= (SELECT
procs_revis_nbr FROM actv_revis_tbl WHERE type_nbr=''000'')
                                )
                                    WHERE
                                              procs_revis_nbr=cust_max_procs
                            ) cust
                            WHERE
                                    cust.ctry_code = :1 AND
                                    cust.co_code   = :2 AND
                                    cust.cust_nbr     = :3 AND
                                    cust.rt_nbr    = :4 AND
                                    :5 BETWEEN cust.eff_dt AND cust.end_dt'
                        INTO
                                    v_tax_exemp_flg,
                                    v_tax_exemp_nbr,
                                    v_tax_id_code
                        USING
                           v_hdr.cth_vpk_country_code           ,
               v_hdr.cth_vpk_company_code           ,
               TO_NUMBER(v_hdr.cth_vnm_customer_nbr),
               TO_NUMBER(v_hdr.cth_vnm_routeid)     ,
               TO_DATE(v_hdr.cth_vnm_document_dt,'DD/MM/YYYY');

               FOR v_productcur IN(
                SELECT
                            DECODE(ctd_vpk_transtype,
                                                     FRESHRETURNS    , '+0230',
                                                     DAMAGERETURNS ,'+0232',
                                                     STALERETURNS  ,'+0231',
                                                     '+0000') TRANSCODE,
                            DECODE(ctd_vpk_transtype,
                                                     SALES        ,'S',
                                                     FRESHRETURNS    ,'R',
                                                     DAMAGERETURNS,'R',
                                                     STALERETURNS ,'R',
                                                     FREEPRODUCT  ,'L',
                                                     '+0000') TRANS_IDENTIFIER,
                            ctd_vpk_transtype,
                            ctd_vpk_selling_code,
                            ctd_vpk_product_code,
                            DECODE(ctd_vnm_item_qty,0,1,ctd_vnm_item_qty)
ctd_vnm_item_qty,
                            ctd_vnm_price,
                            ctd_vnm_item_tot_promo,
                            ctd_vnm_contractual_discount,
                            ctd_vnm_vat_amt,
                            ctd_vnm_re_amt,
                            tax_key1      ,
                            ctd_vnm_green_tax
                    FROM
                             t_custtrans_dtl_uhh JOIN
                         (
               SELECT * FROM
               (
                SELECT
                  ctry_code   ,
                  co_code     ,
                  srce_loc_nbr,
                  srce_loc_type_code,
                  rt_nbr      ,
                  item_id     ,
                  tax_key1    ,
                  tax_key2    ,
                  rt_auth_eff_dt,
                  rt_auth_end_dt,
                  procs_revis_nbr,
                  MAX(procs_revis_nbr) OVER (PARTITION BY
ctry_code,co_code,rt_nbr,item_id) max_procs_revis
                FROM
                  rt_auth_perf
                WHERE
                  procs_revis_nbr <= (SELECT procs_revis_nbr FROM actv_revis_tbl
WHERE type_nbr = '000')
               )
               WHERE
                procs_revis_nbr = max_procs_revis
               )
               ON
               ctry_code          = ctd_vpk_country_code  AND
               co_code            = ctd_vpk_company_code  AND
               rt_nbr             = ctd_vnm_routeid       AND
               TO_NUMBER(item_id) = TO_NUMBER(ctd_vpk_product_code) AND
               TO_DATE(ctd_vnm_document_dt,'DD/MM/YYYY') BETWEEN rt_auth_eff_dt
AND rt_auth_end_dt
            WHERE
                    ctd_vpk_country_code = v_hdr.cth_vpk_country_code AND
                ctd_vpk_company_code = v_hdr.cth_vpk_company_code AND
                ctd_vpk_docseq_nbr     = v_hdr.cth_vpk_docseq_nbr   AND
                ctd_vpk_event_nbr = 0
                        )
                    LOOP

                    EXECUTE IMMEDIATE
'SELECT
        DECODE(prm_vnm_promo_type_code,
                                                ''01'',''0'',
                                                ''02'',''0'',
                                                ''03'',''0'',
                                                ''04'',''0'',
                                                ''05'',''0'',
                                                ''06'',''0'',
                                                ''07'',''0'',
                                                ''08'',''0'',
                                                ''09'',''0'',
                                                ''10'',''0'',
                                                ''17'',''0'',
                                                ''11'',''1'',
                                                ''12'',''1'',
                                                ''13'',''1'',
                                                ''14'',''1'',
                                                ''15'',''1'',
                                                ''16'',''1''
                                        ) PROMO_FLAG,
            prm_vnm_promo_amt,
            prm_vnm_event_nbr
FROM
    t_promotion_dtl_uhh
WHERE
  prm_vpk_country_code = :1 AND
  prm_vpk_company_code = :2 AND
  prm_vpk_docseq_nbr   = :3 AND
    prm_vpk_product_code = :4 AND
    prm_vpk_transtype   =  :5 '
BULK COLLECT INTO
    v_promo_flag,
    v_prm_vnm_promo_amt,
    v_prm_vnm_event_nbr
USING
 v_hdr.cth_vpk_country_code,
 v_hdr.cth_vpk_company_code,
 v_hdr.cth_vpk_docseq_nbr  ,
 v_productcur.ctd_vpk_product_code,
 v_productcur.ctd_vpk_transtype;

END LOOP;

 EXECUTE IMMEDIATE
'SELECT
        :DOCUMENT_DETAIL||
        :SALES_HEADER   ||
        LPAD(NVL(:cth_vpk_country_code,0),3, ''0'')||
      LPAD(NVL(:cth_vpk_company_code,0),3, ''0'')||
      LPAD(NVL(:cth_vpk_document_nbr,0),12,''0'')||
      ''+''||
      TO_CHAR(TO_DATE(:cth_vnm_document_dt,''DD/MM/YYYY''),''YYYYMMDD'')||
      ''+''||

NVL(TO_CHAR(TO_DATE(:cth_vnm_document_tm,''HH24:MI''),''HH24MISS''),''000000'')||

      ''00''||
      LPAD(NVL(:cth_vnm_routeid,0),8,''0'')||
      ''+''||
      LPAD(NVL(:cth_vnm_empid,''0''),6,''0'')||
      LPAD(NVL(SUBSTR(:lvl1,1,2),''0''),2,''0'')||
      LPAD(NVL(SUBSTR(:lvl2,1,3),''0''),3,''0'')||
      LPAD(NVL(SUBSTR(:lvl3,1,3),''0''),3,''0'')||
      LPAD(''0'',4,'' '')||--3 spaces and zero
      ''P''||
        ''+''||
        LPAD(NVL(prm_vnm_pkg_nbr  ,''0''),9,''0'')||
        ''+''||
        LPAD(NVL(prm_vnm_group_nbr,''0''),9,''0'')||
        ''+''||
        LPAD(NVL(prm_vnm_event_nbr,''0''),9,''0'')||
        TO_CHAR(nvl(SUM(DECODE(prm_vpk_transtype,
                                             :SALES
,prm_vnm_promo_amt*ctd_vnm_item_qty*100,

:DAMAGERETURNS,-prm_vnm_promo_amt*ctd_vnm_item_qty*100,
                                             :FRESHRETURNS
,-prm_vnm_promo_amt*ctd_vnm_item_qty*100,
                                             :STALERETURNS
,-prm_vnm_promo_amt*ctd_vnm_item_qty*100
                                             )
                                ),0)
                        ,''S0000000000000''
                     )
FROM
        t_promotion_dtl_uhh JOIN t_custtrans_dtl_uhh ON
        prm_vpk_country_code = ctd_vpk_country_code AND
        prm_vpk_company_code = ctd_vpk_company_code AND
        prm_vpk_docseq_nbr   = ctd_vpk_docseq_nbr   AND
    prm_vpk_product_code = ctd_vpk_product_code and
    prm_vpk_transtype     = ctd_vpk_transtype
    WHERE
        prm_vpk_country_code = :1 AND
        prm_vpk_company_code = :2 AND
        prm_vpk_docseq_nbr   = :3 AND
    ctd_vpk_event_nbr    = 0                              AND
    prm_vnm_promo_type_code IN (:4,:5,:6,:7,:8,:9)
    GROUP BY
        prm_vnm_group_nbr,
        prm_vnm_pkg_nbr  ,
        prm_vnm_event_nbr'
BULK COLLECT INTO
        test_buffer
USING
   DOCUMENT_DETAIL           ,
   SALES_HEADER              ,
   v_hdr.cth_vpk_country_code,
   v_hdr.cth_vpk_company_code,
   v_hdr.cth_vpk_document_nbr,
   v_hdr.cth_vnm_document_dt ,
   v_hdr.cth_vnm_document_tm ,
   v_hdr.cth_vnm_routeid     ,
   v_hdr.cth_vnm_empid       ,
   lvl1                      ,
   lvl2                      ,
   lvl3                      ,
   SALES                     ,
   DAMAGERETURNS             ,
   FRESHRETURNS              ,
   STALERETURNS              ,
   v_hdr.cth_vpk_country_code,
   v_hdr.cth_vpk_company_code,
   v_hdr.cth_vpk_docseq_nbr  ,
   GPM_AMOUNTOFF_PROMO              ,
   GPM_PERCENTOFF_PROMO           ,
   GPM_BUYXGETYFREE_PROMO      ,
   GPM_BUYXPAYFORY_PROMO     ,
   GPM_TIEREDCENTSOFF_PROMO  ,
   GPM_TIEREDPERCENTOFF_PROMO;

END LOOP

thanx in advance
 Nikhil

Followup:
you are right, it is too big to even consider looking at here.

but - i cannot believe it is faster -- I don't see proof of that anywhere.  I
can only imagine it being SLOWER.

June 04, 2003 Scott -- Thanks for the question regarding "Analytical functions - lag sales", version 8.1.7
You Asked
G'day

I'm trying to master the use of the lag function to get last years sales within
the same query.
Our data is partitioned by two time attributes, fin_year and fin_month.
CREATE TABLE ths_sales_det (state_code VARCHAR2(3) NOT NULL ,fin_year NUMBER NOT
NULL,fin_month NUMBER NOT NULL,acc_nbr NUMBER  NOT NULL
,locn_code NUMBER NOT NULL,subd_code  NUMBER NOT NULL,prod_type_id NUMBER NOT
NULL ,sales NUMBER,costs NUMBER,qty_bought NUMBER);
And the typical report needs to be sales grouped by month.
Month   Sales   Last Year Sales
Jul02   100     50
Aug02    90     30
...
INSERT INTO ths_sales_det VALUES ('WA',2003,1,332,2010,123,  0,100, 70,1,1);
INSERT INTO ths_sales_det VALUES ('WA',2003,2,332,2010,123,  0,90, 70,1,1);
INSERT INTO ths_sales_det VALUES ('WA',2002,1,332,2010,123,  0,50, 70,1,1);
INSERT INTO ths_sales_det VALUES ('WA',2002,2,332,2010,123,  0,30, 70,1,1);
However I am unable to get the query to distinguish between previous row and
previous year.
Keep in mind that some months may not necessarily have sales, so you can't
offset by 12.

This is obviously incorrect...
select sum(sales), fin_year, fin_month,lag(sum(sales)) over ( order by
fin_year,fin_month) last_year_sale
from ths_sales_det
group by fin_year,fin_month
SUM(SALES)   FIN_YEAR  FIN_MONTH LAST_YEAR_SALES
---------- ---------- ---------- ---------------
        50       2002          1
        30       2002          2              50
       100       2003          1              30
        90       2003          2             100

Would you please assist?
Thankyou in advance.
and we said...
I think a simple pivot is what you want -- just get the sum of sales by mon/year
for the years in question and pivot:

ops$tkyte@ORA920> select fin_month, max(decode( fin_year, 2002, sales, null ))
fy_2002,
  2                    max(decode( fin_year, 2003, sales, null )) fy_2003
  3    from (
  4  select sum(sales) sales, fin_year, fin_month
  5    from ths_sales_det
  6   group by fin_year, fin_month
  7         )
  8   group by fin_month
  9  /

 FIN_MONTH    FY_2002    FY_2003
---------- ---------- ----------
         1         50        100
         2         30         90
Review & Followup
Rating: 1
ongoing application March 30, 2003
Reviewer: Scott from Perth, WA
G'day Tom

This will be an ongoing application, so hardcoding the financial years will be
of no use.
The parameter set by the user will be financial year.
The data I supplied was only a sample to generate the test case.

I was trying to extend the example found in the documentation and in
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2403541386543
Thankyou for the quick reply.

Followup:
can you spell "bind variable"

any literal in any query is "replaceable"

ops$tkyte@ORA920> variable x number
ops$tkyte@ORA920> variable y number
ops$tkyte@ORA920> exec :x := 2002

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec :y := 2003

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select fin_month, max(decode( fin_year, :x, sales, null ))
fy_last_year,
  2                    max(decode( fin_year, :y, sales, null )) fy_this_one
  3    from (
  4  select sum(sales) sales, fin_year, fin_month
  5    from ths_sales_det
  6   group by fin_year, fin_month
  7         )
  8   group by fin_month
  9  /

 FIN_MONTH FY_LAST_YEAR FY_THIS_ONE
---------- ------------ -----------
         1           50         100
         2           30          90

or, if you want to key off of sysdate:

ops$tkyte@ORA920> select fin_month, max(decode( fin_year,
to_char(add_months(sysdate,-12),'yyyy'), sales, null )) fy_last_year,
  2                    max(decode( fin_year, to_char(sysdate,'yyyy'), sales,
null )) fy_this_one
  3    from (
  4  select sum(sales) sales, fin_year, fin_month
  5    from ths_sales_det
  6   group by fin_year, fin_month
  7         )
  8   group by fin_month
  9  /

 FIN_MONTH FY_LAST_YEAR FY_THIS_ONE
---------- ------------ -----------
         1           50         100
         2           30          90

or whatever -- use whatever technique you need -- it is trivial...  You will be
feeding those values into a predicate somewhere I would presume.

But, using lag and lead -- you need to order NOT BY YEAR, by partition by month
and then order by year

ops$tkyte@ORA920> select *
  2    from (
  3  select fin_month, sales, lag(sales) over (partition by fin_month order by
fin_year) last_year
  4    from (
  5  select sum(sales) sales, fin_year, fin_month
  6    from ths_sales_det
  7   group by fin_year, fin_month
  8         )
  9         )
 10   where last_year is not null
 11  /

 FIN_MONTH      SALES  LAST_YEAR
---------- ---------- ----------
         1        100         50
         2         90         30

just think about how lag/lead/etc work and partitions/windows and ranges...

Rating: 5
Thanks. March 30, 2003
Reviewer: Scott from Perth, WA
It's been that sort of Monday morning...

B-Y-N-D V-A-R-E-E-A-B-E-L, no, no, that's not it...

Thanks Tom.

Rating: 5
lag is not perfect than pivot June 04, 2003
Reviewer: Steve from nyc, usa
Hi Tom,

In the following answer you gave, what if remove one record ( e.g. fin_year is
2002, fin_month is 2)
then the result is

FIN_MONTH    FY_2002    FY_2003
---------- ---------- ----------
         1         50        100

instead of
FIN_MONTH    FY_2002    FY_2003
---------- ---------- ----------
         1         50        100
         2                    90

Thanks!
Steve

select *
      from (
    select fin_month, sales, lag(sales) over (partition by fin_month order by
fin_year) last_year
      from (
    select sum(sales) sales, fin_year, fin_month
      from steve_test_sales
     group by fin_year, fin_month
           )
           )
    where last_year is not null

Followup:
use where fin_year = 2003 (bind it) then instead to get rows for records with no
"last year"

Rating: 3
lag is not perfect than pivot June 04, 2003
Reviewer: Steve from NYC, USA
Hi Tom,

I am sorry I didn't follow you.
How do I put Zero in the column where there is no data?
e.g.

FIN_MONTH    FY_2002    FY_2003
---------- ---------- ----------
         1         50        100
         2          0         90

Thanks!

Steve

Followup:
select fin_month, sale, nvl(last_year,0)
      from (
    select fin_year, fin_month, sales, lag(sales) over (partition by fin_month
order by
fin_year) last_year
      from (
    select sum(sales) sales, fin_year, fin_month
      from steve_test_sales
     group by fin_year, fin_month
           )
           )
    where fin_year = 2003

Rating: 4
future years June 11, 2003
Reviewer: Scott from Perth, WA
Using
select *
    from (
 select fin_year,fin_month, sales, lag(sales) over (partition by fin_month order
by
fin_year) last_year
    from (
  select sum(sales) sales, fin_year, fin_month
    from ths_sales_det
   group by fin_year, fin_month
         )
        )
Is useful for selecting 2003/2002 data for a clause of fin_year=2003
  FIN_YEAR  FIN_MONTH      SALES  LAST_YEAR
---------- ---------- ---------- ----------
      2003          1        100         50
      2003          2         90         30

And good for selecting 2002/2001 data for clause of fin_year = 2002 (obviously,
with our test data, 2001 column will be null.
  FIN_YEAR  FIN_MONTH      SALES  LAST_YEAR
---------- ---------- ---------- ----------
      2002          1         50
      2002          2         30

But if we have a clause of fin_year=2004, even though there is data for 2003
(for the last_year column), the query will return no rows.
Ideally, it would return
  FIN_YEAR  FIN_MONTH      SALES  LAST_YEAR
---------- ---------- ---------- ----------
      2004          1                   100
      2004          2                    90
How can we combat this?

June 04, 2003 Suhen -- Thanks for the question regarding "write contents of a query to a text file", version 8.1.6
You Asked
Tom,

I enjoy reading through your site.
I have just bought your expert One on One book which I thoroughly enjoy. Still
at page 200 though. Will get to the end soon.
Keep up the great work.

Just a question.  I am currently doing some tuning on our database, mostly
application (SQL) tuning.

I have a procedure that runs for a long time every night (50 minutes)
and essentially what it does is runs a sql statement and dumps it to a text file
using utl_file package.
I ran the statement through sqlplus and spooled it to a file and it took just 90
secs.

Could you verify my procedure and give me a simpler way to do this.

Thanks and Regards
Suhen

CREATE OR REPLACE  PROCEDURE "JDAPROD"."HBI_STKTKE_PRODUCT_DOWNLOAD"
    (file_directory IN varchar2,
                                                               override_flag
varchar2 default 'N')
AS

/*

    Stocktake Product Download
    --------------------

    This PL/SQL procedure reads prd_lvl_number of products
    from the PRDMSTEE table    and outputs the data to an ASCII text file for
import into
    into the stocktake guns.

    Author: Emma Wright

    Modified     By  Reason for Modification
    --------     --  ------------------------------------------
    19-Dec-2001  EEW  Initial version
    20-Feb-2002  DJM  Added to only run on a Thursday
*/

BEGIN
DECLARE
-- storage variables for retrieval of data to be written to ASCII file
    v_sku                char(7);

-- variable to check that procedure executed OK
    procedure_ok            BOOLEAN;

-- UTL File Package parameters
    file_handle            UTL_FILE.FILE_TYPE;
    file_name                char(12) := 'PRODSKU.DAT';
    file_record             char(7);
    file_error            EXCEPTION;

-- SQL Error Handling storage variables
    procedure_name            sqlerree.procedure_name%TYPE    :=
'HBI_STKTKE_PRODUCT_DOWNLOAD';
    error_message            sqlerree.sql_text%TYPE;
    sql_error                sqlerree.sql_error%TYPE;

--this field tests runtime parameter aznd allows override of Thursday-only
processing

    override            BOOLEAN;
    v_current_day        caldtlee.CALCDW%TYPE;

    CURSOR prdmst_cur IS
    SELECT     LPAD(rtrim(ltrim(prd_lvl_number)),7,'0') as prd
      FROM     prdmstee
     WHERE  prd_lvl_id = 1
     ORDER BY LPAD(rtrim(ltrim(prd_lvl_number)),7,'0');

/* --------------------------------------------------------------------------
       The following procedure will format the output record then write the
contents
    of the buffer to the ASCII disk file.
*/

PROCEDURE write_output_record IS

BEGIN

-- format the output record buffer with retrieved data

    file_record := v_sku;

-- write the output record buffer to file

    UTL_FILE.PUT_LINE(file_handle, file_record);

END;

/* --------------------------------------------------------------------------
   Main processing section of program
*/

BEGIN

--added 20-feb-2002 to only run on Thursday

-- test for Thursday-only processing override

    override    := UPPER(override_flag) = 'Y';

-- determine the current day no. to check if it is a Thursday (Day = 4)

    FOR cal_rec    IN (    SELECT    c.calcdw
                FROM    caldayee d,
                    caldtlee c
                WHERE    d.cal_type    = 'M'
                AND    d.caldat    = c.calcur)
    LOOP <<cal_loop>>
        v_current_day        := cal_rec.calcdw;
    END LOOP cal_loop;

-- only run on Sundays (i.e. day = 4)

    IF     v_current_day     <> 4
    AND NOT override     THEN
        RETURN;
    END IF;

  IF UTL_FILE.IS_OPEN(file_handle) THEN -- close the current ASCII text output
file
    UTL_FILE.FCLOSE(file_handle);
  END IF;

  file_handle := UTL_FILE.FOPEN(file_directory,file_name, 'w');

-- open the cursor to fetch all product records

  FOR rec IN prdmst_cur LOOP
    v_sku := rec.prd;
    dbms_output.PUT_LINE(v_sku);
    write_output_record;            -- format the output buffer and write the
record
  END LOOP;

      IF UTL_FILE.IS_OPEN(file_handle) THEN
      UTL_FILE.FCLOSE(file_handle);
    END IF;

EXCEPTION
    WHEN UTL_FILE.INVALID_PATH THEN
        error_message    := 'Invalid path for file name '
                       || RTRIM(file_directory,' ') || '\' || RTRIM(file_name,'
');
        hbi_write_sqlerree(procedure_name,error_message);

    WHEN UTL_FILE.INVALID_MODE THEN
        error_message    := 'Invalid open mode for file name '
                       || RTRIM(file_directory,' ') || '\' || RTRIM(file_name,'
');
        hbi_write_sqlerree(procedure_name,error_message);

    WHEN UTL_FILE.INVALID_OPERATION THEN
        error_message    := 'Invalid operation for file name '
                       || RTRIM(file_directory,' ') || '\' || RTRIM(file_name,'
');
        hbi_write_sqlerree(procedure_name,error_message);

    WHEN UTL_FILE.INVALID_FILEHANDLE THEN
        error_message    := 'Invalid file handle for file name '
                       || RTRIM(file_directory,' ') || '\' || RTRIM(file_name,'
');
        hbi_write_sqlerree(procedure_name,error_message);

    WHEN UTL_FILE.WRITE_ERROR THEN
        error_message    := 'Invalid write for file name '
                       || RTRIM(file_directory,' ') || '\' || RTRIM(file_name,'
');
        hbi_write_sqlerree(procedure_name,error_message);

    WHEN UTL_FILE.READ_ERROR THEN
        error_message    := 'Invalid read for file name '
                       || RTRIM(file_directory,' ') || '\' || RTRIM(file_name,'
');
        hbi_write_sqlerree(procedure_name,error_message);

    WHEN UTL_FILE.INTERNAL_ERROR THEN
        error_message    := 'Internal error processing file name '
                       || RTRIM(file_directory,' ') || '\' || RTRIM(file_name,'
');
        hbi_write_sqlerree(procedure_name,error_message);

    WHEN FILE_ERROR THEN
        hbi_write_sqlerree(procedure_name,error_message);

-- SQL Error Logging

    WHEN OTHERS THEN
        sql_error        := sqlcode;
        error_message    := SUBSTR(sqlerrm,1,100);
        hbi_write_sqlerree(procedure_name,error_message,sql_error);

END;

END;
and we said...
Read chapter 10 and the appendix on dbms_profiler.  That'll help you in tuning
things.

I would run sql_trace to see if the query is taking the time in the plsql.  You
do not say how many records this generates or anything.  I can say a procedure
like this:

CREATE OR REPLACE  PROCEDURE p2(file_directory varchar2,
                                override_flag  varchar2 default 'N')
AS
    file_handle            UTL_FILE.FILE_TYPE;
    file_name              varchar2(13) := 'PRODSKU2.DAT';

    type array is table of char(7) index by binary_integer;

    l_data  array;
    l_empty array;
    l_str   long;

    CURSOR prdmst_cur IS
    SELECT     substr(object_name,1,7) prd
      FROM     all_objects;
BEGIN
    file_handle := UTL_FILE.FOPEN(file_directory,file_name, 'w', 32000);

    open prdmst_cur;
    LOOP
        fetch prdmst_cur BULK COLLECT into l_data LIMIT 100;
        l_str := null;
        for i in 1 .. l_data.count
        loop
            l_str := l_str || l_data(i) || chr(10);
        end loop;
        UTL_FILE.PUT_LINE(file_handle, rtrim(l_str,chr(10)) );
        exit when (prdmst_cur%notfound);
        l_data := l_empty;
    END LOOP;
    UTL_FILE.FCLOSE(file_handle);
END;
/

On my system runs more then 2x faster then your implementation.  You move lots
of data about into temporary variables -- introduce a procedure "write record"
that doesn't do much (other then slow down the process) and do single row
processing.
Review & Followup
Rating: 4
May 01, 2002
Reviewer: A reader

Rating: 1
An error encountered during compilation... May 06, 2002
Reviewer: Riaz Shahid,PRAL, from Islamabad, Pakistan
Hello Tom!

I have encountered the following error while compiling the above procedure:

LINE/COL ERROR
-------- -----------------------------------------------------------------
17/51    PLS-00103: Encountered the symbol "LIMIT" when expecting one of
         the following:
         . ( , % ;

Can u plz tell me what is the cause for that?

Thanks in anticipation

Followup:
Then you have 815 and LIMIT was introduced in 816 and up.  You cannot limit the
number of rows fetched in a bulk collect in your release.

Rating: 5
What is your opinion? April 17, 2003
Reviewer: TH from Manama, BAH
Dear Tom,

In our company developers are sharing a common text file and appending the text
file with modifications they are doing to a particular schema.

Usually this file (VERY BIG INDEED!) is taken to the customer site and we are
executing the commands in the file ONE BY ONE. This is because of the fear that
we cannot identify the errors correctly when we run the full scripts at a
stretch.

What is your opinion?

You have some better options?

Or should I write an application to go through the text file and read the text
until I find one '/'- show the portion of the SQL to be executed to the
user(developer)- ask him to execute the script - show the errors like that?

Followup:
not knowing what you put into this file beyond "commands" -- i have no opinion.
No idea what this file is for, what it does, whether it can be parallelized,
broken down, modularized.

That is really a "software design and implementation" issue.

Rating: 4
Would Native Compilation make your proc faster April 17, 2003
Reviewer: Rob from PIttsburgh, PA
Tom:

Would natively compiling procedure p2 above offer any additional speed in
writing the file. We have used pro*c in the past for programs where we fetch
rows from a cursor and write them to a flat file since it seemed a lot faster
than using utl_file in pl/sql.

Followup:
pro*c, using array fetching, is going to be the fastest method.

see
http://asktom.oracle.com/~tkyte/flat/index.html
for a generic utility i have that takes a query and unloads it using pro*c with
array fetching.  It'll be faster then ncomped plsql

Rating: 3
Continuation of the discussion April 19, 2003
Reviewer: TH from Manama, BAH
We have in the text file, SQL's like

- Create Table
- Create Function
- Create procedure
- Alter Table
etc.

During our customer support, we have to support a single product with a lot of
customers. Usually our developers identify the user's additional requirements
whenever they needed, and a few customer support engineers runs the script they
wrote at the customer site.

The same product is used by our different customers. All the customers coming to
our contract will have the latest version of our software.

Usually support people may find problem while running the scripts at the
customer locations. I have been pointing to that situation.

Followup:
still "no comment from me".  I don't really know even what you are looking for
-- BUT - rather then drag it out here, if you put it all together in a concise
question and when I'm taking questions -- post it there, I'll be glad to take a
look.

It sounds like you want to write an "installer".  Really -- it does. so you can
execute the statements one by one and check programatically for errors.

For me, a simple GREP of a lst file as the output of a spool command usually
does it.

Rating: 5
May 22, 2003
Reviewer: Tommy W from USA
If all you want to do is "see" the commands in the output
wouldn't putting "set echo on" at the start of the script help?

Rating: 5
Utl_file and its mystery... June 03, 2003
Reviewer: Kashif from Houston, TX
Hi Tom,

When you transfer the array data (which was populated via the bulk collect and
limit features) to an OS level File via utl_file, is there a context switch
involved? I'm a little unclear on whether a call to utl_file is resolved via the
pl/sql engine or not. Also, why is utl_file named utl_file and not dbms_file,
for example? What's the difference between the two prefixes? That also
contributes to my confusion, since the dbms packages that I know of are
implemented via pl/sql, and I'm not sure whether utl_file is or not. Thanks in
advance.

Kashif

Followup:
dbms_* packages are plsql wrappers in many cases.  much of the code is really
"C"

same with UTL_* functions.  they are all either plsql or plsql on top of C or
plsql on top of Java.

You can optimize your UTL_FILE interface by reducing the number of calls to it.

eg: instead of writing a COLUMN at a time, write a line at a time.

Rating: 5
June 03, 2003
Reviewer: A reader

Rating: 4
Interesting... June 04, 2003
Reviewer: Kashif from Houston, TX
Tom,

a) So, is there no context switch involved when calling utl_file from pl/sql?

b) From what I understand of what you're saying, it's more performant to have a
utl_file.put_line call write a record once versus call the utl_file.put command
to write a piece of the record everytime. It makes sense, fewer calls, less
trips to the OS level.

Thanks again.

Kashif

Followup:
a) utl_file is plsql.

b) correct.

June 05, 2003 Damien -- Thanks for the question regarding "Java File class cannot see UNC path", version 8.1.7
You Asked
Tom,

I have been unable to access Windows NT UNC file paths through Oracle's JVM. At
first I was not able to write to mapped drives. The oracle service was running
on the local system account, I changed it to run on a network domain account and
then the java stored procedure could write to these mapped drives. I wrote a
program that dynamically maps drives using Runtime.exec system calls and then
deletes these mappings when the use of the mapping is no longer needed.

Another issue I had in writing the test main() method of the included source
below is the java.net.InetAddress.getLocalHost() resolve to "localhost" instead
of the machine name. On the Sun JVM that is running locally on the machine is
resolves to the machine name, Do you know why that this is happenning?

--------------------------------------------------------------------
import java.io.*;
import java.sql.*;
import java.util.*;

class NTShareFile {

    private String UNC;
    private File mappedFile;
    private String driveLetter;

    public NTShareFile (String UNCshareName) {
        UNC = UNCshareName;
        mappedFile = mapDrive();
    }

    private File mapDrive() {
        try {
            StringTokenizer ST = new StringTokenizer(UNC, "\\");
            String servername = ST.nextToken();
            String sharename = ST.nextToken();

            String directoryAfterShareName = "";

            while (ST.hasMoreTokens()) {
                directoryAfterShareName += ST.nextToken() + "\\";
            }
            driveLetter = getFirstAvailableDrive();
            String cmd = "net use " + driveLetter + " \\\\" + servername + "\\"
+ sharename;
            doCmd(cmd);
            String completePath = driveLetter + "\\" + directoryAfterShareName;
            return new File(completePath);
        }
        catch (Exception e) {
            System.out.println(e);
        }
        return null;
    }

    public void close() throws Exception {
        String cmd = "net use " + driveLetter + " /delete";
        doCmd(cmd);
    }

    public File getFile() {
        return mappedFile;
    }

    public String getUNCString() {
            return UNC;
    }

    //Do a system command and return the output
    private static String doCmd(String cmd) throws Exception {
         String returnString = null;
         try {
            Process p = Runtime.getRuntime().exec(cmd);
            int bufSize = 4096;
            BufferedInputStream bis =
new BufferedInputStream(p.getInputStream(), bufSize);
            int len;
            byte buffer[] = new byte[bufSize];
             String bufferString = "";
             while ((len = bis.read(buffer, 0, bufSize)) != -1) {
                 bufferString += new String(buffer, 0, len).trim();
            }
             p.waitFor();
             returnString = bufferString.trim();
         }

         catch (Exception e) {
            e.printStackTrace();
             returnString = "An error occured " + e;
         }
        return returnString.trim();
     }

    //Loop through mapped drives, get the first available drive not mapped
    private static String getFirstAvailableDrive() throws Exception {
         char firstDrive = 'A';
        char lastDrive = 'Z';
        String localhostname =
java.net.InetAddress.getLocalHost().getHostName().toString();
        System.out.println("localhost name = " + localhostname);
        StringTokenizer ST = new StringTokenizer(localhostname, "/");
        localhostname = ST.nextToken();

        //since Oracle's JVM only resolves to localhost
        localhostname = "sprodop1nt";
        System.out.println(localhostname);
        for (char i = firstDrive; i <= lastDrive; i++) {
            String cmd = "net use " + i + ": \\\\" + localhostname + "\\c$";
            String results = doCmd(cmd);
            if (results.endsWith("completed successfully.")) {
                cmd = "net use " + i + ": /delete";
                doCmd(cmd);
                return new String(i + ":");
            }
            else {
                System.out.println("Continuing : " + i + ": drive in use");
                continue;
            }
        }
        return null;
    }

    //These 2 methods are for testing purposes only
    public static void main(String args []) throws Exception {
        NTShareFile.doIt();
    }
    public static void doIt() throws Exception {
        NTShareFile sharefile = new
NTShareFile("\\\\wmichd2k\\Documentation\\test1\\sharefile.txt");
        File file = sharefile.getFile();
        BufferedWriter bw = new BufferedWriter(new FileWriter(file));
        String line = "NTShareFile.main() is testing writing to a file on a NT
shared drive";
        bw.write(line, 0, line.length());
        bw.newLine();
        bw.newLine();
        for (int i=0; i<10; i++) {
            line = "This is test line #" + i;
            bw.write(line, 0, line.length());
            bw.newLine();
        }
        bw.close();
        sharefile.close();
    }
}
/*
DOS>loadjava -user user/pass@doc NTShareFile.class
*/
/*

SQL>create or replace procedure testNTShare
SQL>as
SQL>language java
SQL>name 'MapDrive.main()';
/

*/
/*
SQL> set serveroutput on
SQL> CALL dbms_java.set_output(9000000);
*/
/*
create or replace procedure testNTShare
as
language java
name 'NTShareFile.doIt()';
*/

/*
begin
  dbms_java.grant_permission
            ('ACTDOCDEV',
            'java.io.FilePermission',
            '<<ALL FILES>>',
            'execute');
end;

begin
  dbms_java.grant_permission
            ('ACTDOCDEV',
            'java.lang.RuntimePermission',
            '*',
            'writeFileDescriptor');
end;

*/

/*
begin
  dbms_java.grant_permission
            ('ACTDOCDEV',
            'java.io.FilePermission',
            '<<ALL FILES>>',
            'execute');
end;

begin
  dbms_java.grant_permission
            ('ACTDOCDEV',
            'java.lang.RuntimePermission',
            '*',
            'writeFileDescriptor');
end;

begin
  dbms_java.grant_permission
            ('ACTDOCDEV',
            'java.io.FilePermission',
            '<<ALL FILES>>',
            'write');
end;

*/
and we said...
for the localhost issue, see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1414403947647

for the UNC naming, there was an issue (#2235221) fixed in patchset 8.1.7.4.5.
I can say that in 901 (9i release 1):

SQL> connect system/manager
Connected.
SQL> exec  dbms_java.grant_permission( 'SCOTT',
'SYS:java.io.FilePermission','\\oaxaca\mbpierma\unc-file','write')

PL/SQL procedure successfully completed.

C:\>sqlplus scott/tiger

SQL*Plus: Release 9.0.1.0.1 - Production on Tue Aug 20 12:20:18 2002

(c) Copyright 2001 Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.0.1.1.1 - Production
With the Partitioning option
JServer Release 9.0.1.1.1 - Production

SQL> create or replace java source named UNC as
  2  import java.io.*;
  3  import java.sql.*;
  4  import java.util.*;
  5
  6  public class UNC {
  7    public static String UNCFile = "\\\\oaxaca\\mbpierma\\unc-file";
  8
  9    public static void write() throws Exception {
 10      BufferedOutputStream bos = null;
 11      int ctr = 0;
 12
 13      try {
 14        bos = new BufferedOutputStream(new FileOutputStream(UNCFile));
 15        for (ctr = 0; ctr < 1024; ctr++) {
 16          bos.write(64);
 17        }
 18
 19        bos.close();
 20        System.out.println("Wrote " + ctr + " bytes to " + UNCFile);
 21      } catch (Exception e) {
 22        e.printStackTrace();
 23        if (bos != null) { bos.close(); bos = null; }
 24      }
 25    }
 26
 27    public static void main(String[] args) throws Exception {
 28      write();
 29    }
 30  }
 31  /

Java created.

SQL> create or replace procedure UNC_WRITE
  2  as language java name 'UNC.write()';
  3  /

Procedure created.

SQL> set serveroutput on
SQL> exec dbms_java.set_output(100000)

PL/SQL procedure successfully completed.

SQL> exec unc_write
Wrote 1024 bytes to \\oaxaca\mbpierma\unc-file

PL/SQL procedure successfully completed.

It works as well.
Review & Followup
Rating: 5
Alternative for Running Oracle as Domain User June 04, 2003
Reviewer: mary
Tom, per Damien's first comment at the top:

-----"I have been unable to access Windows NT UNC file paths through Oracle's
JVM. At
first I was not able to write to mapped drives. The oracle service was running
on the local system account, I changed it to run on a network domain account and

then the java stored procedure could write to these mapped drives."---------

My question is:
Is there a way around having to run the Oracle service as a domain user in order
to read / write to network drives through a java stored procedure?  (i.e. if
domain user password changes, someone has to remember to change the password
through NT svc mgr)

Followup:
if you use Unix, sure -- using NFS disks is trivial.

Otherwise, you sort of need to bend to the operating system and do it the way it
wants you to.  When the OS complicates it -- there isn't much we can personally
do.

June 05, 2003 mathew -- Thanks for the question regarding "cursor passing ", version 9i
You Asked
hi,
  Untill 9i one had to use a package variable to pass ref cursors . Can you
provide an example of ref cursors in 9i using SYS_REFCURSOR? and how will this
prove better than the previous versions?

thanks,
mathew
and we said...
Ok, I see -- misuse of a term.

Before 8i, you had to define a TYPE (not a package variable) like this:

create package types
as
   type refcur is ref cursor;
end;
/

and then in your code, you used types.refcur.  That is not a package variable
and in fact, its quite impossible to define a package variable that is a
refcursor (hence my confusion).

In 9i, you need not use that package (you can if you want) instead you can just
use SYS_REFCURSOR -- they would be basically the same.

I do not see it proving "better", maybe more convienent.  It will not be faster
or anything.  As all real code should be in packages anyway (standalone
procedures and functions being something to avoid at all costs), having to
define this type in the past simply added a single line of code to some package.

No example needed -- just take any piece of code that used
"package_name.type_name" and replace it with SYS_REFCURSOR.  You are done...
they are the same.
Review & Followup
Rating: 3
simple explanation March 13, 2002
Reviewer: A reader

Rating: 5
Question :Why in out and not just out parameter June 04, 2003
Reviewer: A reader
Why do we need to use in out for sys_refcursor parameter for returning record
set to java and not just use out param.

Followup:
and can be just out.

June 05, 2003 Trevor -- Thanks for the question regarding "Parse CPU to Parse Elapsed", version 8.1.7
You Asked
Hi Tom,

I got given a statspack report to check out.

The Parse CPU to Parse Elapsed seems terrible.
In you book Expert 1 on 1 page 481
your example was 87.88% and you say for every CPU second
it spent about 1.13 seconds of wall clock time. How did you
work out 1.13 seconds please?

Regards
Trevor

STATSPACK report for

DB Name DB Id       Instance     Inst Num Release     OPS Host
------- ----------- ------------ -------- ----------- --- ------------
PRD     1921956436  XPS_PRD             1 8.1.7.4.0   NO  bd01

                Snap Id     Snap Time      Sessions
                ------- ------------------ --------
 Begin Snap:         26 31-May-03 10:58:42       93
   End Snap:         31 31-May-03 16:46:07       93
    Elapsed:                 347.42 (mins)

Cache Sizes
~~~~~~~~~~~
         db_block_buffers:     230400          log_buffer:    1048576
            db_block_size:       8192    shared_pool_size:  524288000

Load Profile
~~~~~~~~~~~~                      Per Second       Per Transaction
                               ---------------       ---------------
              Redo size:            146,489.66             29,758.19
          Logical reads:            111,987.73             22,749.40
          Block changes:                353.31                 71.77
         Physical reads:                446.84                 90.77
        Physical writes:                132.29                 26.87
             User calls:              1,161.46                235.94
                 Parses:                107.85                 21.91
            Hard parses:                  1.78                  0.36
                  Sorts:                 71.24                 14.47
                 Logons:                  0.03                  0.01
               Executes:                122.73                 24.93
           Transactions:                  4.92

% Blocks changed per Read:  0.32    Recursive Call %:   5.19
 Rollback per transaction %:0.00    Rows per Sort:   45.56
Instance Efficiency Percentages (Target 100%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     Buffer Nowait %:  100.00       Redo NoWait %:  100.00
     Buffer  Hit   %:   99.60    In-memory Sort %:  100.00
     Library Hit   %:   99.28        Soft Parse %:   98.35
     Execute to Parse %:   12.12         Latch Hit %:   99.85
Parse CPU to Parse Elapsd %:    2.38     % Non-Parse CPU:   98.98

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   95.29   95.25
    % SQL with executions>1:   36.44   70.67
  % Memory for SQL w/exec>1:   29.17   41.40

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                    Wait     % Total
Event                                   Waits      Time (cs)   Wt Time
------------------------------------ ------------ ------------ -------
latch free                             1,335,053    3,161,609   74.83
db file sequential read                1,367,706      438,661   10.38
db file scattered read                   697,792      310,837    7.36
log file sync                            105,273      128,159    3.03
log file parallel write                  106,085      108,835    2.58
          -------------------------------------------------------------
-> cs - centisecond -  100th of a second
-> ms - millisecond - 1000th of a second
-> ordered by wait time desc, waits desc (idle events last)
and we said...
It is just percentages.  if you have 87.88% (.8788) you just divide:

ops$tkyte@ORA920> select 1/.8788 from dual;

   1/.8788
----------
1.13791534

If parse cpu to parse elapsed was 87.88%, then for 1.13 seconds must be elapsed
for each 1 cpu second in order to get the ratio 87.88%

Looking at your latch frees, I would say your system doesn't use bind variables
(thats in the book as well, I think almost every page says "use them or you
lose")

347 minutes is 330 minutes TOO LONG for statspack.  Use a 15 minute window!

Use this script:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1163635055580
to find your problem queries.  I've got a feeling however, many of your queries
are going to pop out in that report.  The developers MUST use binds.  You'll
need to get your soft parse % up well over 99%
Review & Followup
Rating: 5
Developers sent me a link... AGAINST BINDING! June 05, 2003
Reviewer: Alvin from Philippines
http://www.oreilly.com/catalog/jorajdbc/chapter/ch19.html

I have a difficult time convincing them to use prepared statements (PS) in JAVA.
And for that i get below.....

================================
STATSPACK report for

DB Name         DB Id    Instance     Inst Num Release     OPS Host
------------ ----------- ------------ -------- ----------- --- ------------
xxxx          xxxxxxxxxxx                    1 8.1.7.0.0   NO  dbname

                Snap Id     Snap Time      Sessions
                ------- ------------------ --------
 Begin Snap:         31 05-Jun-03 03:02:19      139
   End Snap:         32 05-Jun-03 04:09:33      139
    Elapsed:                  67.23 (mins)

Cache Sizes
~~~~~~~~~~~
           db_block_buffers:      87500          log_buffer:     163840
              db_block_size:       8192    shared_pool_size:  512000000

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:              8,970.49              2,589.41
              Logical reads:              6,458.52              1,864.31
              Block changes:                 47.08                 13.59
             Physical reads:                 68.60                 19.80
            Physical writes:                 27.95                  8.07
                 User calls:                301.12                 86.92
                     Parses:                 52.53                 15.16
                Hard parses:                 10.26                  2.96
                      Sorts:                 21.27                  6.14
                     Logons:                  0.14                  0.04
                   Executes:                 52.37                 15.12
               Transactions:                  3.46

  % Blocks changed per Read:    0.73    Recursive Call %:   17.58
 Rollback per transaction %:    0.00       Rows per Sort:    5.01

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:   99.98
            Buffer  Hit   %:   98.94    In-memory Sort %:   99.94
            Library Hit   %:   87.04        Soft Parse %:   80.47
         Execute to Parse %:   -0.30         Latch Hit %:   99.98
Parse CPU to Parse Elapsd %:   83.51     % Non-Parse CPU:   99.99

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   84.24   86.20
    % SQL with executions>1:   51.80   65.49
  % Memory for SQL w/exec>1:   22.83   42.57

================================

How come i have a high Non parse CPU ? shouldn't the cpu be parsing all the time
based from the stats ?

=================================
                               Get                                  Spin &
Latch Name                    Requests         Misses      Sleeps Sleeps 1->4
-------------------------- -------------- ----------- ----------- ------------
library cache                   3,310,698       8,945      11,174 3036/1648/33
                                                                  56/905/0
shared pool                     1,556,136       1,570         365 1329/134/95/
                                                                  12/0
redo writing                       77,126       1,018           2 1016/2/0/0/0
row cache objects               1,527,365         488           7 482/5/1/0/0
cache buffers chains           51,607,754         231          30 211/17/1/2/0
session idle bit                2,631,371         129          46 86/41/1/1/0
session allocation                408,936          34           7 27/7/0/0/0
cache buffers lru chain           198,693          10           4 6/4/0/0/0
enqueues                          275,358           8           2 6/2/0/0/0
latch wait list                     6,111           7           2 5/2/0/0/0
          -------------------------------------------------------------

It's bad i know... and i get 4031 errors !! and i'm not a bit surprised !!

1. my question is why does exec statspack.snap take around 10 mins++ to finish ?
2. what does this mean 'library cache            kglhdgn: child:
 0     10,531   4,082
"

The head java leader is getting annoyed of me asking them to use PS. I was
surprise that they found the above link.

          Followup:

1) because your database is a mess and the java guys are KILLING YOU.  Literally
KILLING YOU.

It is funny you know.  I use that exact example in my next book -- to prove that
the java guy in that chapter has got not a single clue about what he is
talking about -- not at all

The test is 100% flawed.  Here is a short excerpt from my forthcoming book on
this topic:

Bind Variables and Java

One of the recent questions on the website asktom.Oracle.com recently was this
interesting one. It was about programming in Java with JDBC.  This discussion
applies equally to Visual Basic programmers using VB with ODBC as the concept of
"Statements" and "PreparedStatements" exists in ODBC in more or less the same
fashion.  It questioned the use of Statements versus a PreparedStatement. When
using Statements in JDBC - you must use the "string concatenation approach".
Using PreparedStatements allows you to use bind variables. The question was:
Tom -- Please briefly skim this link (link omitted for obvious reasons) which
gives an excerpt for JDBC performance.  It says always use statements (no bind
variables allowed) instead of preparedstatements because they perform better
without discussing the impact on the database, only in terms of a single apps
metrics.  Is this accurate or is this information just extremely short sighted
with regards to overall db impact?

Well, that was easy - I gave them the proof from above - case closed.
PreparedStatements with bind variables are absolutely without question the only
way to do it. Of course, later on, I got a followup:

For a moment keep aside shared pool, hard parse and soft parse and talk about
PreparedStatement and Statement as they are the only way to execute statements
from java.  I wrote this benchmark code that shows a Statement performs better
than a PreparedStatement unless you execute the same statement a whole lot of
times.  So, I reproduced the findings of the above link and prove that
Statements are better than PreparedStatements.

I had some issues with this one - they missed the point. They start with "for a
moment keep aside shared pool, hard parse and soft parse". Well, if we were to
ignore those - we totally miss the boat on this topic as they are the only
things to consider. The facts are:

o Hard Parsing incurs many latches
o Latches are serialization devices
o Serialization is not a scalable thing
o Therefore as you add users, the system that uses Statements instead of
PreparedStatements with bind variables will fail.

I quite simply could not observe their request to put aside the shared pool and
hard/soft parse. They are the relevant topics - they must be considered. That
was my initial response - but you know, this bothered me so much, I had to
explore it further. So, starting with their benchmark code which simply inserted
into a database table, I made it a multi-user benchmark to demonstrate the fact
that if you expand this simple, single user benchmark out to a real world
example with multiple users - you will see clearly what the issue is and why you
need to avoid statements.

But, an interesting thing happened. I could not reproduce their findings that a
Statement in JDBC without bind variables versus a PreparedStatement using bind
variables. When I ran their code - I could, using my code - I found that a
single statement executed using either of a Statement or PreparedStatement took
the same amount of time initially and if we executed the SQL over and over - the
PreparedStatement was always much faster. This conflicted with their
observations totally.

So, I set out to find out why. We'll walk through this process here because it
does cover two interesting things:

o If for some reason your test isn't meeting your hypothesis - either your
hypothesis is wrong or your test is flawed
o The seemingly simple, convincing test can be the most misleading thing in the
world

We'll walk through this simple benchmark now, starting with their original test
case and working up to the "real thing". It used a single table TESTXXXPERF
which was created using the script perftest.sql as follows:

scott@ORA920> drop table testxxxperf;
Table dropped.

scott@ORA920> create table testxxxperf
  2  ( id number,
  3    code varchar2(25),
  4    descr varchar2(25),
  5    insert_user varchar2(30),
  6    insert_date date );
Table created.

scott@ORA920> exit

Then, the main java code was supplied. It consisted of three subroutines
basically - a main that connected to the database and then called a routine to
insert into that table using statement and then called a routine to do the same
with prepared statements. The code piece by piece is:

import java.sql.*;
import oracle.jdbc.OracleDriver;
import java.util.Date;
public class perftest
{
  public static void main (String arr[]) throws Exception
  {
    Connection con = null;
    DriverManager.registerDriver(new oracle.jdbc.OracleDriver());
    con = DriverManager.getConnection
    ("jdbc:oracle:thin:@aria-dev:1521:ora920", "scott", "tiger");
    con.setAutoCommit(false);
    Integer iters = new Integer(arr[0]);
    doStatement (con, iters.intValue() );
    doPreparedStatement(con, iters.intValue() );
    con.commit();
    con.close();
  }

That is the main routine which simply connects to my Oracle 9iR2 instance as
scott/tiger - disables the autocommit JDBC uses by default and then invokes the
subroutine to execute a Statement N times and then a PreparedStatement N times.
I set it up to allow us to pass "N" into the Java routine so we can run multiple
simulations. Next, we'll look at the doStatement routine:

  static void doStatement(Connection con, int count)
  throws Exception
  {
    long start = new Date().getTime();
    Statement st = con.createStatement();

    for (int i = 0; i < count; i++)
    {
      st.executeUpdate
      ("insert into testxxxperf " +
       "(id, code, descr, insert_user, insert_date)" +
       " values (" + i  + ", 'ST - code" + i + "'" +
       ", 'St - descr" + i + "'" + ", user, sysdate ) ");
    }
    long end = new Date().getTime();
    st.close();
    con.commit();
    System.out.println
    ("statement " + count + " times in " +
      (end - start) + " milli seconds");
  }

Very straight forward - it simply creates a statement object and then loops
"count" times and builds a unique - never before seen INSERT statement and
executes it. It is somewhat scaled back from reality in that it is not checking
for quotes in strings and fixing them up - but we'll let that go for now. Also
note that it retrieves the time before and after executing the statement and
prints out the results.  Next, we look at the prepared statement:

  static void doPreparedStatement (Connection con, int count)
  throws Exception
  {
    long start = new Date().getTime();
    PreparedStatement ps =
       con.prepareStatement
       ("insert into testxxxperf " +
        "(id, code, descr, insert_user, insert_date)"
        + " values (?,?,?, user, sysdate)");

    for (int i = 0; i < count; i++)
    {
      ps.setInt(1,i);
      ps.setString(2,"PS - code" + i);
      ps.setString(3,"PS - desc" + i);
      ps.executeUpdate();
    }
    long end = new Date().getTime();
    con.commit();
    System.out.println
    ("pstatement " + count + " times in " +
     (end - start) + " milli seconds");
  }
}

Basically the same code but this uses a PreparedStatement to insert "count"
rows. It accomplishes the same exact task as the doStatement routine - just
using a PreparedStatement. Lastly, I set up a shell script to execute this:

!#/bin/csh -f
sqlplus scott/tiger @perftest
java perftest $1

A CMD file for Windows might look like:

sqlplus scott/tiger @perftest
java perftest %1

Now, I ran this with inputs of 1 (do one statement/prepared statement), 10, 100
and 1,000 and the results were:

Rows to Insert    Statement    PrepareStatement
1            0.05 seconds    0.92 seconds
10            0.34 seconds    1.03 seconds
100            2.69 seconds    2.35 seconds
1000            26.68 seconds   15.74 seconds

So, at first glance - it looks like they might have something here. If you were
to ignore the database (which I'm not inclined to do personally). If I just look
at this test - I might conclude that if I'm not going to execute the same
statement over and over - about 100 times - I would best be served by using a
Statement. The problem is there is a FLAW in our test! I discovered this flaw
when I rewrote the code a little to go "multi-user". I knew in a multi-user
test, using System.out.println would not be a very "scalable" testing tool. It
would be hard to collect and analyze the results. So, I did what I always do
when benchmarking and setup a database table to hold the timing results. The
slightly modified Java code had an extra subroutine "saveTimes" to save the
timing information into the database. That routine you can add to the test
program above is:

  static PreparedStatement saveTimesPs;
  static void saveTimes( Connection con,
                         String which,
                         long elap ) throws Exception
  {
    if ( saveTimesPs == null )
        saveTimesPs = con.prepareStatement
                      ("insert into timings " +
                       "( which, elap ) values "+
                       "( ?, ? )" );

    saveTimesPs.setString(1,which);
    saveTimesPs.setLong(2,elap);
    saveTimesPs.executeUpdate();
  }

Then, I modified the doStatement and doPreparedStatement routines like this:

  static void doStatement (Connection con,
                           int count) throws Exception
  {
    long start = new Date().getTime();
    Statement st = con.createStatement();
    for (int i = 0; i < count; i++)
    {
      st.executeUpdate
      ("insert into testxxxperf " +
       "(id, code, descr, insert_user, insert_date)" +
       " values (" + i  +
       ", 'ST - code" + i + "'" +
       ", 'St - descr" + i + "'" +
       ", user, sysdate ) ");
    }
    st.close();
    con.commit();
    long end = new Date().getTime();
    //System.out.println( "STMT" + " (" + (end-start) + ")" );
    saveTimes( con, "STMT", end-start );
  }

And I did likewise for the PreparedStatement routine. This would simply save the

times in a database table:

create table timings ( which varchar2(10), elap number );

so we could run a query to get average/min/max timings from multiple users. So,
remembering that the only thing I changed was to comment out the
System.out.printlns and add a routine to record the time - I ran this in single
user mode to test. I found:

Rows to Insert    Statement    PrepareStatement
1               0.05 seconds    0.05 seconds
10              0.30 seconds    0.18 seconds
100             2.69 seconds    1.44 seconds
1000           28.25 seconds    15.25 seconds

That's different - very different. Surprisingly different. All of a sudden -
there is not only no penalty ever for using a PreparedStatement - but it quickly
benefits us in single user mode even to use it. What could be the cause.
The code being timed was no different. Not a single byte of code was changed.
Sure, we commented out a System.out.println and added a call to saveTimes - but
that code was never timed before. So, what did change then? Well, it turns out
the saveTimes routine was the culprit here. If you look at that code - it uses a
PreparedStatement. It "warmed up" the PreparedStatement class. It paid a one
time penalty to load that class - java dynamically loads classes as you use
them. The simple act of connecting did that for the Statement class (it is used
during the connection to Oracle). Once the timing of the initial load of the
PreparedStatement class was factored out - it turns out that a PreparedStatement
is no more expensive to execute than a Statement is in JDBC. The entire premise
of a Statement being "lighter weight", "more efficient" for small numbers of
statements was flawed - wrong. If you used a single PreparedStatement anywhere
in your code - you would have paid this "load" penalty (which is pretty small
when you look at it over all) for ALL PreparedStatements.

That was the interesting part of this example - that the basic test itself was
flawed, we were timing an unrelated "thing". Since most non-trivial Java JDBC
programs are going to have to use a PreparedStatement somewhere - they all pay
this "load" penalty. Not only that but this "load penalty" isn't a penalty at
all - but simply the price of admission to building a scalable application on
Oracle. If you don't use Prepared statements - if you insist on using Statements
and "gluing the values in" - opening yourself up to the SQL Injection security
flaw and buggy code - your application will not scale as you add users. There is
no "maybe" here, there is no "might not scale", your application will not scale
- period.

      Rating:
      5
Amazing!  June 05, 2003
Reviewer:
      Christo Kutrovsky  from Ottawa, ON Canada
That's a very important piece of code to know.

I am not yet involved in JAVA, but I sure will be some day, and I'll need to
know how to prove the BIND variables point.

Thank you Tom.

June 05, 2003 zhu -- Thanks for the question regarding "Some
      questions about SQL tuning(tkprof)", version 8.1.7

      You Asked
hi, tom:
  nice to get this chance to ask you again, i have some question about sql
tuning :
1. look at the following tkprof result:
update TX_Transaction  set TX_TransactionSettlementDate=SYSDATE,TX_Status=:b0,
  TX_TransactionSettlementAmount=TX_TransactionAmount,TX_ModificationDate=
  SYSDATE,TX_Modifier_UL_LoginName_fk=:b1
where
 TX_TransactionID=:b2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse      200      0.04       0.04          0          0          0           0
Execute    200      0.35       0.30          7        608       7507         200
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      400      0.39       0.34          7        608       7507         200

Misses in library cache during parse: 0
Optimizer goal: FIRST_ROWS
Parsing user id: 37  (EACHPAY)

This seems to be using bind variable, a good SQL. But from the tkprof, i see
that the sql is pased every time it is executed.Most of the SQL in this schema
behavier like this.This is bad, isn't it?What is the possible reason? And how
can i avoid this?
2. In my oracle817 for solaris, some tracefile cannot be tkprofed.(i capture the
process id from top, select the sid,serial# from v$session+v$process, and
dbms_system.set_sql_trace_in_session to trace this file). The file is about 6m
in size and i can vi this file, see a lot of sql statement parsing, executing,
but tkprof said no sql in this trace file.But more and cat cannot see anything
in this trace file. I am the dba and logged in as oracle account.What is the
possible reason? how can i make it?
3. the last trivial question: i am using oracle 817 and tkprof 817, but the
trace file always said:Trace file compatibility: 8.00.04 what is the reason?
anything not good about this?
Great thanks.

      and we said...
1) the PROGRAM is at fault here.

The developer of this application has coded:

  parse update statement
  bind update statement
  execute update statement
  close update statement

when they should have coded:

  if ( first_time )
     parse update statement
     first_time = false
  end if

  bind update
  execute update

2) I've never heard of such a situation personally.  Constact support and you
can send them the trace file for inspection.

3) That is OK, that is normal.  It is just the way it is. (it hasn't changed
since then).  In 9i, it'll be:

********************************************************************************
Trace file: /export/home/oracle9i/admin/ora9i/udump/ora9i_ora_15331.trc
Trace file compatibility: 9.00.01
Sort options: default

as the trace file did change (the granularity of the timings is different)

Review &
      Followup

      Rating:
      4
chaos  April 30, 2002
Reviewer:  zhu
      chao  from china. shanghai
Thanks tom.
But i am still confused with the first question.
I asked the developers(using texudo/proc), and reviewed their pro*C programs.
These SQL are in functions,like:
    Update_Payee_Pending_Payment_Transaction_Record:
        {
            iTX_Status = TRANSACTION_SETTLED_PAYMENT;
            EXEC SQL
                UPDATE TX_Transaction
                SET
                    TX_TransactionSettlementDate = SYSDATE,
                    TX_TransactionSettlementAmount = TX_TransactionAmount,
                    TX_ModificationDate = SYSDATE,
                    TX_Status = :iTX_Status,
                    TX_Modifier_UL_LoginName_fk = :sCaller_LoginName,
                    ST_StatementID = :iPayeeStatementID
                WHERE TX_TransactionID =
:iPendingPayment_TX_Party_TX_TransactionID_fk;

            if (sqlca.sqlcode < 0)
            {
                sprintf(sTmpStr, "<UPDATE TX_Transaction> %s!!\n",
sqlca.sqlerrm.sqlerrmc);
                logError("Eachpay", "SettlePendingPaymentTransaction", sTmpStr);
                if (iTransactionInProgress == 0) tpabort(0);
                sprintf(retstr, "1%d\t%s\t\n", DATABASE_ERROR, "database
operation error!");
                tpreturn(TPSUCCESS, 0, retstr, 0L, 0);
            }

        }

And the corresponding Trace file is:
update TX_Transaction  set TX_TransactionSettlementDate=SYSDATE,TX_Status=:b0,
  TX_TransactionSettlementAmount=TX_TransactionAmount,TX_ModificationDate=
  SYSDATE,TX_Modifier_UL_LoginName_fk=:b1
where
 TX_TransactionID=:b2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse      200      0.04       0.04          0          0          0           0
Execute    200      0.35       0.30          7        608       7507         200
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      400      0.39       0.34          7        608       7507         200
From the trace file,this is bad SQL.
This seems that everytime it is executed, it is already with some specific
value, so it is parsed again and again, is it right?
So, how can i improve it?
Can you give me some simple example? I am not familier with proc/tuxedu and have
to communicate with the developers.
Thanks for your help:)

          Followup:

what are the pro*c options -- such as hold_cursor, release_cursor they are
using?

      Rating: 4
parse=prepare
      ?  May 01, 2002
Reviewer:  Ringo Li
       from Hong Kong
Tom,

Do you mean the developer should

prepare statement
if statement prepared {
    bind the variable
    execute the statement
}else
    {prepare the statment
    bind the variable
    execute
}

But not each time
  prepare statment
  bind variable
  execute the statemen

Ringo

          Followup:

I don't know why you start with prepare statement each time.

The "logic" is:

  if statement is not prepared
  then
     prepare it (parse it)
  end if;
  bind it
  execute it

      Rating: 4
what about the
      two option?  May 08, 2002
Reviewer:  zhu
      chao  from shanghai, china.
We are using the default(from 8.1.7 proc command, it seems to be no in both
case).
Which value do you use in your pro*c applications? Shall i change it to
hold_cursor=yes?
Thanks.

          Followup:

It is behaving as if release_cursor=yes hold_cursor=no.

try it with

release_cursor=NO hold_cursor=YES

      Rating:
      4
  September 09, 2002
Reviewer:
      chao_ping  from china
a little feedback:
  As for the second question, later i found out it was because i already traced
that sesssion and it generated a big trace file.
  And i truncated that trace file by" echo ''>ora_xxx.trc" like what i do to the
alert log file. Maybe this is why that file cannot be tkprofed.
Thanks.

      Rating: 5
prepare
        June 04, 2003
Reviewer:  Ivan  from
      HK
You said

  if statement is not prepared
  then
     prepare it (parse it)
  end if;
  bind it
  execute it

But I saw many Java examples with the following

cstmt = conn.prepareCall(....);
cstmt.setString...
cstmt.execute();

It will prepare every time.

Does this coding stle has problems ?

Thanks in advance

          Followup:

yes, it is exactly the bad code I wail about everyday here.

      Rating:
      5
prepare  June 04, 2003
Reviewer:  Ivan
       from HK
But it seems that the connection API does not have
isPrepare function.

So how to solve this problem ?

          Followup:

what connection api?

      Rating: 5
Maybe You need
      to Make it Your Self  June 04, 2003
Reviewer:
      Jose Cleto  from Santo Domingo, Dominican Republic
int Prepare;
...
prepapre = 0;
...
if Prepare = 0 then
  cstmt = conn.prepareCall(....);
  Prepare = Prepare + 1;
end;
...
cstmt.setString...
cstmt.execute();

      Rating: 5
prepare
        June 05, 2003
Reviewer:  Ivan  from
      HK
what connection api?

I mean the connection class does not have a
fucntion to check whether the store procedure is
prepared or not.

          Followup:

if you mean "whether the prepared statement is prepared or not", a simple:

  if ( pstmt == null )
  {
     pstmt = Prepare that thing...
  }

your statement is null until you prepare it.

June 05, 2003 gauri -- Thanks for the question regarding
      "Inserting a raw column.", version oracle 7.3

      You Asked
Hello Tom,
I have a table with a long raw column in it. I want to store the
raw data (like graphics, image, text etc) from files in the
column. Can I use the util_file package with the binary data as
we use it for text files ? Is there any other way to do the same
? With examples please.
Or if I can use a 3gl ( say C) then how can I do it ?

Thanks,
Gauri.

      and we said...
You will not be able to do this in PLSQL in version 7.3 (in
Oracle 8.0 and up, you could use BLOBS and dbms_lob.loadfromfile
to do this in plsql).

In C, you can use Pro*C or Oci (jdbc, odbc, and other APIs work
as well).

If you are interested in the OCI code for 7.3, goto:

http://osi.oracle.com/~tkyte/owarepl/doc/dnld.html
and download the source code for "owarepl".  In that
distribution, there are directories /owarepl/src/load and
/owarepl/src/unload.  They show how to piecewise read/write LONG
RAWs in the database using OCI.  they are simple command line
tools to extract a long raw from a table via a query Or to load
a long raw into a table.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:682224675732
for a java/jdbc example...

Here is a pro*c example:

static void process()
{

/*
 * This is the data structure we will use with our LONG RAWs.
   We will
 * always use pointers to this structure as we don't know how
   big we need
 * until runtime.  Will use malloc to allocate storage on the
   fly.
 */

typedef struct TAGmy_raw
{
    long            len;
    unsigned char   arr[1];
}
    my_raw;

/*
 * Use type equivalencing to tell Oracle that the C
   type "my_raw" is
 * equivalent to the Oracle type LONG VARRAW and can hold upto
   100k
 * bytes of data (we will never allocate that much here, just an

   upper
 * bound, could be MUCH higher (eg: 10,000,000,000)
 */

EXEC SQL BEGIN DECLARE SECTION;
EXEC SQL TYPE my_raw IS LONG VARRAW(100000) REFERENCE;
my_raw  * buffer;
EXEC SQL END DECLARE SECTION;

long    size = 100000;
int     i;

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();

    printf( "EXEC SQL CREATE TABLE TEST_BLOB ( X LONG RAW );\n"
);
    EXEC SQL CREATE TABLE TEST_BLOB ( X LONG RAW );

    /*
     * we will allocate a little over 99k of space
     */
    buffer = (my_raw *)malloc( size+sizeof(my_raw) );

    /*
     * fill it up with every possible byte value from 0..254
     */
    for( i = 0; i < size; i++ )
        buffer->arr[i] = i % 255;

    /*
     * Just like a varchar, set the length field
     */
    buffer->len = size;

    printf( "INSERT INTO TEST_BLOB\n" );
    EXEC SQL INSERT INTO TEST_BLOB ( X ) values ( :buffer );

    /*
     * commit the changes
     */
    printf("EXEC SQL COMMIT WORK;\n" );
    EXEC SQL COMMIT WORK;

    /*
     * Now, ZERO out the buffer so when we fetch, we know that
       the buffer
     * was NOT equal to what it was on the insert.  Just to
       *prove* that
     * the fetch got back the data we inserted
     */
    memset( buffer->arr, 0, size );

    /*
     * Select out the data
     */
    printf( " SELECT X INTO :buffer FROM TEST_BLOB;\n" );
    EXEC SQL SELECT X
             INTO :buffer
             FROM TEST_BLOB;

    /*
     * show what size we got
     */
    printf( "The Length = %ld\n", buffer->len );

    /*
     * Check each character to make sure it is what we inserted
     */
    for( i = 0; i < size; i++ )
        if ( buffer->arr[i] != i % 255 )
        {
            printf( "Error! %d != %d\n", buffer->arr[i], i%255
);
            break;
        }

    /*
     * drop that table
     */
    printf( "EXEC SQL DROP TABLE TEST_BLOB;\n" );
    EXEC SQL DROP TABLE TEST_BLOB;
}
Review &
      Followup

      Rating: 4
inserting
      raw/long raw values from other table  June 05, 2003

Reviewer:  Imran Wani  from Pakistan
I have a table with a long raw column (Photo)in it. I am storing the raw data
(like graphics and  image) using Developer 6i. Tha data is stored and displayed
properly.
The problem is that when I want to take backup of the table, I got ora-06502
error. When I empty the column (Photo), then there is no error.
Can you help me to copy the Column (photo) to another table.

          Followup:

export/import

"real backups"

or best yet -- USE BLOB, infinitely more useful then long raw.

June 05, 2003 --
      Thanks for the question regarding "SQL Query", version 8.1.7

      You Asked
Hello Tom,

I have a question regarding this SQL query:

I have 3 tables as:

Regions:

Region_id   number(10);
State_id    number(10);
Country_id  number(10);
region_name varchar2(40);

States:

State_id   number(10);
State_name varchar2(40);
Country_id number(10);

countries:

Country_id
Country_name

The Data in the regions table is

The Relationship can be defined as

Country ---< State ------< Region

Country_id         State_id      Region_id
1                      1             1
1                      1             2
1                      2             3
1                      2             4
1                      3             5
2                      4             6

I have a sales table as

Sales:

Entity_id    number(10);
Entity_type  varchar(2);
Amount       number(8,2);
product_id   number(6);
qty          number(4); and other columns.

The Entity_type can be 'C','S','R' for 'Country','State','Region'
with the corresponding Entity_id.

The Data in the Sales Table can be:
Entity_id     Entity-Type     Amt
1             C               323
3             R               434
4             S               1000

The query is to find out the sum of Amount Country wise.

Writing
3 different queries  one for state, one for region put them in to a temp table
along with country_id  and put the Country records also in the temp table and
then get The total-amt country wise: -This is a too tedious process. Is there a
way to achive it in a single query?

Thanks and Regards

      and we said...
select country_id, sum(amt)
  from (
select decode( entity_type,
          'C', ENTITY_ID,
          'S', (select country_id
                  from state
                 where state_id = ENTITY_ID),
          'R', (select country_id
                  from region
                 where region_id = ENTITY_ID) ) country_id,
       amt
  from sales
       )
 group by country_id

(you are probably realizing by now that a data model such as yours -- will
apparently "flexible" is increasingly difficult to query!  Also, it seems funky
to have country_id in REGION, it doesn't seem to belong there.)

Review &
      Followup

      Rating:
      5
decode  October 12, 2002
Reviewer:
      A reader
Tom,

If i have a product table which identifies region wise
products:

product table:

product_id,
region_id
qty

and the user can give input either 'C','S','R' and the corresponding entity id

To get sum(qty) of products on country-wise, state-wise or region-wise based on
input given

if i use the same logic in where clause

 region_id in (select decode( v_entity_type,
              'R', v_entity_ID,
              'S', (select region_id
                      from regions
                     where state_id = v_ENTITY_ID),
              'C', (select region_id
                      from region
                     where company_id = v_ENTITY_ID) )

will give an error as each select in decode will return
more than one row. Is there a way to do this in a single SQL statement rather
than using ref-cursors, contexts, if-then-else?

Thanks a Ton,

          Followup:

where decode( v_entity_type,
             'R', v_entity_id,
             'S', (select region_id
                     from regions
                    where state_id = v_entity_id
                      and SALES.region_id = regions.region_id
                      and rownum = 1 ),
             'C', (select region_id
                     from region
                    where company_id = v_entity_id
                      and SALES.region_id = region.region_id
                      and rownum = 1 )
            ) = region_id

would be one way,

where region_id in
  ( select v_entity_id
      from dual
     where v_entity_type = 'R'
     UNION ALL
    select region_id
      from regions
     where v_entity_type = 'S' AND state_id = v_entity_id
     UNION ALL
     ......

would be another.

(still and again, data model doesn't seem to be developed to answer your
questions very easily -- hint hint.  I forecast massive performance and
maintainence issues in your future)

      Rating:
      5
De-normalization  October 14, 2002

Reviewer:  A reader
Tom,

In a datawarehouse don't you think the de-normalization of tables would help in
performance?

          Followup:

sure, sometimes (bitmapped join indexes = neat way to denormalize into your
INDEXES instead of tables) but....

what does that have to do with this thread -- this model above is not
denormalized, I'm not sure what I would call it actually.  It certainly doesn't
appear to be setup to ANSWER the questions they are asking

      Rating:
      5
  October 15, 2002
Reviewer:
      A reader
Tom,

Can you please help me with my data model? what's wrong in my tables?Initially
to start off with i just segregated regions, states and countries. Agreed the
country_id is not required at regions table. How should i design them to suit my
query needs? 80% of my queries are in this format only.

The above relation ships between

regions, states and country

country --< states ----< regions and other tables such as
products, sales etc  have entity_type, entity_id

the data is stored either at country level, state level and region level in some
tables

and sometimes with all the three(levels) with indicators such as entity_type,
entity_id.

Can you please help me regarding this.

Regards,

          Followup:

not knowing all of your questions -- NO, i cannot do your data model.

My comment is: you seem to be having a very hard time asking your most basic
questions of this model.  Indicates to me that model is not good for your
system.

      Rating: 3
Datawarehouse
      query, maybe join/outer join or else  June 05, 2003

Reviewer:  Asim Naveed  from Pakistan

Hi,

Please consider the following structure.

CREATE TABLE product
(
company VARCHAR2(100),
division VARCHAR2(100),
productline VARCHAR2(100),
productid NUMBER(10)
);

CREATE TABLE geography
(
country VARCHAR2(100),
region    VARCHAR2(100),
area    VARCHAR2(100),
geographyid    NUMBER(10)
);

CREATE TABLE time
(
year varchar2(4),
quarter varchar2(4),
month    VARCHAR2(4),
timeid    NUMBER(10)
);
CREATE TABLE sales
(
timeid NUMBER(10),
productid NUMBER(10),
geographyid NUMBER(10),
sales        NUMBER(10)
);

INSERT INTO product (productid, company, division, productline) values
(1,'company1', 'division1', 'productline1');
INSERT INTO product (productid, company, division, productline) values
(2,'company1', 'division1', 'productline2');
INSERT INTO product (productid, company, division, productline) values
(3,'company1', 'division1', 'productline3');
INSERT INTO product (productid, company, division, productline) values
(4,'company1', 'division2', 'productline1');
INSERT INTO product (productid, company, division, productline) values
(5,'company1', 'division2', 'productline2');
INSERT INTO product (productid, company, division, productline) values
(6,'company1', 'division2', 'productline3');
INSERT INTO product (productid, company, division, productline) values
(7,'company1', 'division3', 'productline1');
INSERT INTO product (productid, company, division, productline) values
(8,'company1', 'division3', 'productline2');
INSERT INTO product (productid, company, division, productline) values
(9,'company1', 'division3', 'productline3');
INSERT INTO product (productid, company, division, productline) values
(10,'company1', 'division4', 'productline1');
INSERT INTO product (productid, company, division, productline) values
(11,'company2', 'division1', 'productline1');
INSERT INTO product (productid, company, division, productline) values
(12,'company2', 'division2', 'productline1');
INSERT INTO product (productid, company, division, productline) values
(13,'company2', 'division3', 'productline1');
INSERT INTO product (productid, company, division, productline) values
(14,'company2', 'division4', 'productline1');
INSERT INTO product (productid, company, division, productline) values
(15,'company2', 'division5', 'productline1');

INSERT INTO geography (geographyid, country, region, area) values  (1,
'country1', 'region1', 'area1');
INSERT INTO geography (geographyid, country, region, area) values  (2,
'country1', 'region1', 'area2');
INSERT INTO geography (geographyid, country, region, area) values  (3,
'country1', 'region1', 'area3');
INSERT INTO geography (geographyid, country, region, area) values  (4,
'country1', 'region2', 'area1');
INSERT INTO geography (geographyid, country, region, area) values  (5,
'country1', 'region2', 'area2');
INSERT INTO geography (geographyid, country, region, area) values  (6,
'country1', 'region2', 'area3');
INSERT INTO geography (geographyid, country, region, area) values  (7,
'country1', 'region3', 'area1');
INSERT INTO geography (geographyid, country, region, area) values  (8,
'country1', 'region4', 'area1');
INSERT INTO geography (geographyid, country, region, area) values  (9,
'country2', 'region1', 'area1');
INSERT INTO geography (geographyid, country, region, area) values
(10,'country2', 'region2', 'area1');
INSERT INTO geography (geographyid, country, region, area) values
(11,'country2', 'region3', 'area1');
INSERT INTO geography (geographyid, country, region, area) values
(12,'country2', 'region4', 'area1');

INSERT INTO time    (timeid, year, quarter, month) values  (1,  '1998', 'QTR1',
'JAN');
INSERT INTO time    (timeid, year, quarter, month) values  (2,  '1998', 'QTR1',
'FEB');
INSERT INTO time    (timeid, year, quarter, month) values  (3,  '1998', 'QTR1',
'MAR');
INSERT INTO time    (timeid, year, quarter, month) values  (4,  '1998', 'QTR2',
'APR');
INSERT INTO time    (timeid, year, quarter, month) values  (5,  '1998', 'QTR2',
'MAY');
INSERT INTO time    (timeid, year, quarter, month) values  (6,  '1998', 'QTR2',
'JUN');
INSERT INTO time    (timeid, year, quarter, month) values  (7,  '1998', 'QTR3',
'JUL');
INSERT INTO time    (timeid, year, quarter, month) values  (8,  '1998', 'QTR3',
'AUG');
INSERT INTO time    (timeid, year, quarter, month) values  (9,  '1998', 'QTR3',
'SEP');
INSERT INTO time    (timeid, year, quarter, month) values  (10, '1998', 'QTR4',
'OCT');
INSERT INTO time    (timeid, year, quarter, month) values  (11, '1998', 'QTR4',
'NOV');
INSERT INTO time    (timeid, year, quarter, month) values  (12, '1998', 'QTR4',
'DEC');

INSERT INTO time    (timeid, year, quarter, month) values  (13,  '1999', 'QTR1',
'JAN');
INSERT INTO time    (timeid, year, quarter, month) values  (14,  '1999', 'QTR1',
'FEB');
INSERT INTO time    (timeid, year, quarter, month) values  (15,  '1999', 'QTR1',
'MAR');
INSERT INTO time    (timeid, year, quarter, month) values  (16,  '1999', 'QTR2',
'APR');
INSERT INTO time    (timeid, year, quarter, month) values  (17,  '1999', 'QTR2',
'MAY');
INSERT INTO time    (timeid, year, quarter, month) values  (18,  '1999', 'QTR2',
'JUN');
INSERT INTO time    (timeid, year, quarter, month) values  (19,  '1999', 'QTR3',
'JUL');
INSERT INTO time    (timeid, year, quarter, month) values  (20,  '1999', 'QTR3',
'AUG');
INSERT INTO time    (timeid, year, quarter, month) values  (21,  '1999', 'QTR3',
'SEP');
INSERT INTO time    (timeid, year, quarter, month) values  (22,  '1999', 'QTR4',
'OCT');
INSERT INTO time    (timeid, year, quarter, month) values  (23,  '1999', 'QTR4',
'NOV');
INSERT INTO time    (timeid, year, quarter, month) values  (24,  '1999', 'QTR4',
'DEC');

INSERT INTO time    (timeid, year, quarter, month) values  (25,  '2000', 'QTR1',
'JAN');
INSERT INTO time    (timeid, year, quarter, month) values  (26,  '2000', 'QTR1',
'FEB');
INSERT INTO time    (timeid, year, quarter, month) values  (27,  '2000', 'QTR1',
'MAR');
INSERT INTO time    (timeid, year, quarter, month) values  (28,  '2000', 'QTR2',
'APR');
INSERT INTO time    (timeid, year, quarter, month) values  (29,  '2000', 'QTR2',
'MAY');
INSERT INTO time    (timeid, year, quarter, month) values  (30,  '2000', 'QTR2',
'JUN');
INSERT INTO time    (timeid, year, quarter, month) values  (31,  '2000', 'QTR3',
'JUL');
INSERT INTO time    (timeid, year, quarter, month) values  (32,  '2000', 'QTR3',
'AUG');
INSERT INTO time    (timeid, year, quarter, month) values  (33,  '2000', 'QTR3',
'SEP');
INSERT INTO time    (timeid, year, quarter, month) values  (34,  '2000', 'QTR4',
'OCT');
INSERT INTO time    (timeid, year, quarter, month) values  (35,  '2000', 'QTR4',
'NOV');
INSERT INTO time    (timeid, year, quarter, month) values  (36,  '2000', 'QTR4',
'DEC');

INSERT INTO sales    (timeid, productid, geographyid, sales)
SELECT timeid, productid, geographyid, rownum
FROM   product, geography, time;

DELETE FROM sales WHERE geographyid = 12;
DELETE FROM sales WHERE productid IN (6, 7);

Note that the dimension tables create statement has the columns ordered
as the dimension's hierarchy.

my required report is like this.

The user will input 3 parameters as follows

1- A company name
2- A division of the above company
3  Another division of the same company

e.g company = 'company1'
    first division = 'division1'
    second division = 'division3'

The question is :-
-------------------
Show all regions along with their sales in division1 and division3.
If there is no sales for a region (in division1 or division3 or both divisions),
still show that region along with division1 and division3 with sales = 0.

The output should be like this.
(This is just format, the data maybe not according
to the above INSERT statements. The blank lines are
just for clarity, they are not required in output).

Country     Region     Company    Division  Sales
---------------------------------------------------------------------

country1 region1 company1    division1    70
country1 region1 company1    division3    90

country1 region2 company1    division1    50
country1 region2 company1    division3    30

country1 region3 company1    division1    0
country1 region3 company1    division3    20

country1 region4 company1    division1    10
country1 region4 company1    division3    0

country2 region1 company1    division1    15
country2 region1 company1    division3    15

country2 region2 company1    division1    0
country2 region2 company1    division3    0

.
.
.
.
.
.

Notes:-

1-    There may be some regions whome dont have any
        sales at all i.e no records in sales table.

2-    There may be some regions whome dont have any sales
        in division1 of company1 but does have sales in
        division3 of company1.

3-    There may be some regions whome dont have any sales
        in division3 of company1 but does have sales in
        division1 of company1.

4-    There may be some regions whome dont have any sales
        in division3 or division1 of company1 but does have
        sales in division4 of company1.

5-    The query needs to be executed over a large
        datawarehouse, therefore it must be very fast. Use
        of Materialized views are allowed but not preferred
        because there are other dimensions also. Any two
        dimensions can be in place of region and division
    dimension. For example, question can also come like
        this "Show all "QUARTERS" along with their sales in
        division1 and division3. If there is no sales for a
        quarter (in division1 or division3 or both visions),
        still show that quarter along with division1 and
        division3 with sales = 0."

    or the question can be like this.

    "Show all "QUARTERS" along with their sales
         in "REGION1" and "REGION3". If there is no sales
         for a quarter (in region1 or region3 or both ions),
     still show that quarter along with region1 and
         region3 with sales = 0."

    So, if your solution requires that materialized
        views to be created, then I will have to create
        many Materialized views i.e for each combination of
        dimensions.

    However, if it is necessary to use Materialized
        views, I am ready to create as many Materialized
        view as needed.

          Followup:

looks alot like a really big, new question to me

      Rating: 3
SORRY FORGOT
      THE version  June 05, 2003
Reviewer:  Asim
      Naveed  from Pakistan
I am using ORACLE version 9i Release 2

Thanks in advance for any help.

      Rating: 5
How about this
      one  June 05, 2003
Reviewer:  DD  from
      Rochester, NY USA
select X.country_id,C.country_name,sum(X.AMT) from (
select r.country_id COUNTRY_ID,s.entity_id ENTITY, s.entity_type TYP, s.amount
AMT
from sales s, regions r
where s.entity_type = 'R'
and s.entity_id = r.region_id
UNION ALL
select st.country_id,s.entity_id, s.entity_type, s.amount
from sales s, states st
where s.entity_type = 'S'
and s.entity_id = st.state_id
UNION ALL
select entity_id, entity_id, entity_type, amount
from sales
where entity_type = 'C'
) X, countries C
where X.country_id = C.country_id
group by X.country_id,C.country_name

      Rating:
      5
requirments  June 07, 2003
Reviewer:
      Tanmoy Datta choudhury  from Bangalore,India
Tom ,
  Can i update a table reversly .. means if for table t1

t1
---

 3
 6
 1
 2
i want to update it like this

 t1
 --
 2
 1
 6
 3

i can do this in a pl/sql ..but in sql hw to do that ..

i fast tried to reverse it like this
  select *from t1 order by rowid desc  ..
but update is giving error..it is becos i am using order by clause in an inline
view ...
any suggesstion tom ..

          Followup:

that makes literraly no sense to me.  rows have no "order"

result sets can be ordered using an order by, but rows in a table?  no.

      Rating: 2
How about
      this?  July 23, 2003
Reviewer:  Ranga
      Chakravarthi  from Orlando, FL USA
Like Tom said, rows have no order, however,
if you are trying to update a column in a multi column
table in reverse, i.e, swap the last one to first etc.,
you can try this:

drop table t1

Table dropped

create table t1 (c1 number(1), c2 varchar2(5))

Table created

insert into t1 values ( 3, 'Three' )

1 row inserted

insert into t1 values ( 6, 'Six' )

1 row inserted

insert into t1 values ( 1, 'One' )

1 row inserted

insert into t1 values ( 2, 'Two' )

1 row inserted

select *
from t1
order by c1

C1 C2
-- -----
 1 One
 2 Two
 3 Three
 6 Six

4 rows selected

UPDATE t1
SET c1 = (SELECT new_value
          FROM (SELECT a.c1 old_value, b.c1 new_value
                FROM (SELECT ROWNUM rno, c1
                      FROM (SELECT   c1
                            FROM     t1
                            ORDER BY c1 ASC)) a,
                     (SELECT ROWNUM rno, c1
                      FROM (SELECT   c1
                            FROM     t1
                            ORDER BY c1 DESC)) b
                WHERE  a.rno = b.rno)
          WHERE  old_value = t1.c1)

4 rows updated

select *
from t1
order by c1

C1 C2
-- -----
 1 Six
 2 Three
 3 Two
 6 One

4 rows selected

June 05, 2003 -- Thanks for the question regarding "parallel
      nologging not faster", version 8.1.6

      You Asked
Tom:

i have 8 cpu. i am the only user.
create table p ( p int);
begin
for i in 1..1000000 loop
insert into p values (i);
end loop;
end;
/

set timing on
create index myindex on p(p);

Elapsed: 00:00:43.55

drop index myindex;
create index myindex on p(p) parallel(degree 4) nologging;

Elapsed: 00:00:53.19

why?

      and we said...
once again, its in scaling UP with parallel stuff.

1,000,000 integers is tiny, small, nothing, not relevant.

It is NOT the number of rows, it is the volume of data.

There is overhead to parallel operations, in coordination, in distributing the
work, in setting up the plan.

So, I loaded up your table p and discover that:

ops$tkyte@ORA817.US.ORACLE.COM> select count(*) from p;

  COUNT(*)
----------
   1000000

ops$tkyte@ORA817.US.ORACLE.COM> exec show_space( 'P' );
Free Blocks.............................1
Total Blocks............................1536
Total Bytes.............................12582912
Unused Blocks...........................24
Unused Bytes............................196608
Last Used Ext FileId....................7
Last Used Ext BlockId...................130761
Last Used Block.........................40

PL/SQL procedure successfully completed.

ops$tkyte@ORA817.US.ORACLE.COM> select (1536-24)*8/1024 from dual;

(1536-24)*8/1024
----------------
         11.8125

It is all of 11meg in size.  It took longer for us to setup the query plan for
parallel four, get the PQ slaves going, getting everyone to work together --
then it would have just to "get the data, sort it and be done with it".

You want to test this?  Load up 5 - 10 gig of data.  then try it out.  Parallel
is for big, to test big -- you need "big".

Think of it this way.  Suppose someone asked you to do something.  You have two
choices:

o do it yourself, just sit down and do it yourself from start to finish.
o get a bunch of people together, split up the work, assign the work, coordinate
(merge) the results and be done with it.

Well, If I asked to you send an email to someone telling them how to drive to
your company -- option 1 would be faster, better, easier.

If I asked you to build an HR system, option two would be faster, better,
easier.

Review &
      Followup

      Rating:
      5
  August 22, 2001
Reviewer:
      A reader

      Rating:
      5
perfect  August 22, 2001
Reviewer:
      Still learning  from UAS
Tom,

What a perfect example and accurate words picking.

Thanks

      Rating: 5
How big is
      big  June 05, 2003
Reviewer:  hazim
       from VN
Hi Tom,
I see your point since you have master ability in explaining things.
But I wonder that How big is considered big?
I have tested with some tables and find out that parallelism would be slower on
small tables due to overhead operations.
I want to know how many records when a table reaches would be considered to set
up parallelism.
I think "set autotrace" or "tkprof" or something else should have ability to do
that, but I don't know how?
Please help.
Thanks vey much for this great site.

          Followup:

if you have a query that is taking minutes

and it has lots of parallelizable operations

and you've made sure you didn't miss something obvious (oh -- hey -- if we index
this, we could get the answer instantly)

parallelism is something to consider

IF you have the resources to do so.

It is not "how many records".  It is "I've got a long running process, it is
parallelizable in nature, and I've got tons of free resources"

June 05, 2003 Ali
      -- Thanks for the question regarding "About BLOB and BFILE", version 9.0.1

      You Asked
Hi Tom.
We are designing an office automation application and we want to store image of
letters and documents.Our organization is very large (it has 100 departments)
and We have to use replications.
Now we want to know is BLOB a proper type for storing these images?
What about BFile? How does oracle handle bfiles replication, backup, access
restriction and security? What about data consistency?
Thank's for your reply.

      and we said...
If you want

o replication
o backup
o consistency
o access restriction
o security

you'll be using a BLOB and only a BLOB.  Blobs are in the database and all of
the features of the database are available.

A bfile is a file in the OS, outside the database.  Use a bfile and you are on
your own for all of the above.

Me, I use blobs exclusively for document storage.
Review &
      Followup

      Rating:
      4
  September 30, 2002
Reviewer:
      A reader

      Rating: 4
My
      question  April 10, 2003
Reviewer:  Bill
       from Norman, OK
We are working on an application that users will be uploading about 250 10MB per
day into our database.  We are reading the first few lines of the file when it
is upload, but other than that we don't use them. All we are doing is storing
these files and distributing them to other users. Then after 45 days the files
are archived.  We had originally deicded to use BLOBs but we got some advice to
use BFILEs.  Here is that advice making the case for BLOBs and BFILEs acorrding
to the circumstances:

> It is always better (if you can) to use BLOBs because of
> following reasons:
>
> 1. Faster access for smaller files (few hundred KBs).
>
> 2. Database security. For BFILEs, OS admin has to maintain
>    the security of files. BFILEs are stored outside oracle
>    database and it becomes OS admin's responsibility to make
>    sure that the files are not overwritten or modifed by someone else.
>    For BLOBs, this is not a problem as files are bound by
>    database user access security.
>
> 3. Backup and Recovery is easy in case of BLOBs. BFILEs are not
>    automatically backed up when you backup the database. It is
>    an extra step of backup and admins have to make sure that
>    location (pointer) integrity is maintained while restoring
>    such an application with BFILEs.
>
> 4. There are more features which can be used in case of BLOBs.
>    You can easliy modify the content of file in BLOB. I do not
>    think this applies to you - if you are looking for store and
>    retrieve kind of application.
>
> So, when do we use BFILEs?? - if you have following considerations:
>
> 1. BFILEs keep the transaction size smaller - hence smaller redo logs
>    hence better overall database performance.
>    So, for files larger in size (I think this applies to you) it is
>    advisable to use BFILEs. A BLOB with large size will cause lot of
>    log activity - slowing the transaction performance.
>
> 2. Continuing on the first point - since the database size is smaller
>    (only the file pointer is stored), it helps in faster backup and
>    recovery of the database. Please do not confuse this point with
>    point#3 mentioned above. That is about "easier" backup and recovery
>    for admins, this one is about "faster" backup and recovery.
>
> If you plan to use BLOBs in your case, I would recommend that you increase >
the "log_buffer" size to few MBs.

If we use BLOBs, it sounds like the issue with the redo log would create a
performance problem for us.  What do you think?

          Followup:

it depends on your needs.

It is day 1.  You load up your files.  One day 2, your disk crashes.

What do you do?  do you care you lost the files? if not, file system may be ok
for you.  If the lost data causes you a problem -- sounds like you need a
database.

o security
o backup, recovery
o single point of control (no chance someone will "accidently" erase the files)

      Rating: 4
More on
      uploading to Blobs  June 04, 2003
Reviewer:  Ray
      White  from Ottawa,ont
Tom we are using blobs vs Bfiles as per your advise. I agree with the
manageability aspects of this decision.

Can you shed some light on how to manage a site where you need to upload files
like resumes and spreadsheets that could potentially contain viruses. IE how to
detect, remove, prevent the spread of...

How could this be effectively managed using mod/plsql, oracle 9.2 and blobs?

          Followup:

if you use "ifs" (part of collaboration suite) -- it has this sort of software
builtin.

otherwise, you would need to get virus checking software from somewhere and use
a java stored procedure/external procedure in C to do it yourself. No personal
experience myself in that area.

      Rating: 3
Virus scan a
      Blob  June 05, 2003
Reviewer:  Ray
       from Ottawa,Canada
Ok Tom

Understanding that you have no previous expertise in this area. I would really
appreciate your opinion or stab or shot in the dark at how you would go about
this.

My 2cents
Most readily available and possibly approved by an organization virus software
is aimed at file servers. IE not BLOBs. Could a BFILE be advantageous here.
or
a possible process.
Receive the Blob
Flag the Blob as not tested
write the Blob out to a testme directory
launch external virus scanner
determine result
flag blob as clean or dirty
remove file
...

          Followup:

that would work (the second thing)

June 05, 2003 --
      Thanks for the question regarding "exact length for ID field", version
      9.2.0

      You Asked
Hi Tom,
I have this question about field length of IDs. For example our table has 12
digits customer_id. I would like to store them as varchar2(12) since those IDs
are assigned by us and we know the exact length.

However, some members of my team want to make the storage bigger like
varchar2(15) or varchar2(20). They argue that we should reserve more space for
future use. Since the ID is program generated, errors such as writing more
digits will not happen.

I against those idea. I think we can store fields like address, remarks with a
bigger fields. But for ID, we must make it exact. If years in the future and we
need to increase the length, a simple alter table will do it. Otherwise it is
very confusing for users who look at  the table structure. They can't tell how
long is the ID.

What is your take on this issue. Are there problems with assigning IDs to a
bigger fields?

Thanks.

-- Kim

      and we said...
if they are 12 DIGITS, number seems to be more appropriate.  it would take 1/2
the space as well.

You can always alter the table and make the column LARGER in the future.  do it
right, do it at 12, and if need be later, a simple ALTER will increase it.
Review &
      Followup

      Rating:
      4
  June 03, 2003
Reviewer:  Kim

Hi Tom,
But number will not allow leading 0s right? May be can take postal code as
example.

          Followup:

that is formatting, to_char deals with formatting numbers.

      Rating: 4
EXACT LENGTH OF
      NUMBER FIELD   June 03, 2003
Reviewer:  umesh
       from bangalore india
SQL>
SQL>
SQL> CREATE TABLE T
  2  ( ID NUMBER);

Table created.

SQL>
SQL> INSERT INTO T VALUES ( 12345678901234567890123456789012345678);

1 row created.

SQL> INSERT INTO T VALUES ( 123456789012345678901234567890123456789);

1 row created.

SQL> INSERT INTO T VALUES ( 1234567890123456789012345678901234567890);

1 row created.

SQL> INSERT INTO T VALUES ( 12345678901234567890123456789012345678901234567890);

1 row created.

SQL> INSERT INTO T VALUES (
1234567890123456789012345678901234567890123456789012345678901234567);

1 row created.

SQL> SELECT DATA_LENGTH FROM USER_TAB_COLUMNS
  2  WHERE TABLE_NAME='T';

DATA_LENGTH
-----------
         22

SQL> DESC USER_TAB_COLUMNS
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 TABLE_NAME                                NOT NULL VARCHAR2(30)
 COLUMN_NAME                               NOT NULL VARCHAR2(30)
 DATA_TYPE                                          VARCHAR2(106)
 DATA_TYPE_MOD                                      VARCHAR2(3)
 DATA_TYPE_OWNER                                    VARCHAR2(30)
 DATA_LENGTH                               NOT NULL NUMBER
 DATA_PRECISION                                     NUMBER
 DATA_SCALE                                         NUMBER
 NULLABLE                                           VARCHAR2(1)
 COLUMN_ID                                 NOT NULL NUMBER
 DEFAULT_LENGTH                                     NUMBER
 DATA_DEFAULT                                       LONG
 NUM_DISTINCT                                       NUMBER
 LOW_VALUE                                          RAW(32)
 HIGH_VALUE                                         RAW(32)
 DENSITY                                            NUMBER
 NUM_NULLS                                          NUMBER
 NUM_BUCKETS                                        NUMBER
 LAST_ANALYZED                                      DATE
 SAMPLE_SIZE                                        NUMBER
 CHARACTER_SET_NAME                                 VARCHAR2(44)
 CHAR_COL_DECL_LENGTH                               NUMBER
 GLOBAL_STATS                                       VARCHAR2(3)
 USER_STATS                                         VARCHAR2(3)
 AVG_COL_LEN                                        NUMBER

SQL> ED
Wrote file afiedt.buf

  1  SELECT DATA_PRECISION,DATA_SCALE, DATA_LENGTH  FROM USER_TAB_COLUMNS
  2* WHERE TABLE_NAME='T'
SQL> /

DATA_PRECISION DATA_SCALE DATA_LENGTH
-------------- ---------- -----------
                                   22

SQL> ED
Wrote file afiedt.buf

  1  SELECT DEFAULT_LENGTH, DATA_PRECISION,DATA_SCALE, DATA_LENGTH  FROM
USER_TAB_COLUMNS
  2* WHERE TABLE_NAME='T'
SQL> /

DEFAULT_LENGTH DATA_PRECISION DATA_SCALE DATA_LENGTH
-------------- -------------- ---------- -----------
                                                  22

SQL> SPOOL OFF

WHAT EXACTLY IS DATA_LENGTH IN IT ? I AM ABLE TO INSERT MORE THAT 22 DIGITS IN
IT ?
TOM , FROM DICTIONARY TABLES  I WANT TO KNOW THE WIDTH OF THE COLUMN
THANKS IN ADVANCE

          Followup:

YOUR CAPSLOCK KEY IS STUCK.

22 is the number of bytes it might take at most to store a number.  A number in
Oracle is stored in a format similar to a packed decimal with 2 digits per byte.

If you want to know the actual width of a value of a number (it is a varying
length thing really) you would use VSIZE on an actual number:

ops$tkyte@ORA920> create table t ( x number );

Table created.

ops$tkyte@ORA920> begin
  2     for i in 14 .. 38
  3     loop
  4      insert into t values ( rpad('9',i,'9') );
  5      insert into t values ( rpad('9',i,'0') );
  6     end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> set numformat 9999999999999999999999999999999999999999
ops$tkyte@ORA920> column vs format 999
ops$tkyte@ORA920> select x, vsize(x) vs
  2    from t
  3   order by x
  4  /

                                        X   VS
----------------------------------------- ----
                           90000000000000    2
                           99999999999999    8
                          900000000000000    2
                          999999999999999    9
                         9000000000000000    2
                         9999999999999999    9
                        90000000000000000    2
                        99999999999999999   10
                       900000000000000000    2
                       999999999999999999   10
                      9000000000000000000    2
                      9999999999999999999   11
                     90000000000000000000    2
                     99999999999999999999   11
                    900000000000000000000    2
                    999999999999999999999   12
                   9000000000000000000000    2
                   9999999999999999999999   12
                  90000000000000000000000    2
                  99999999999999999999999   13
                 900000000000000000000000    2
                 999999999999999999999999   13
                9000000000000000000000000    2
                9999999999999999999999999   14
               90000000000000000000000000    2
               99999999999999999999999999   14
              900000000000000000000000000    2
              999999999999999999999999999   15
             9000000000000000000000000000    2
             9999999999999999999999999999   15
            90000000000000000000000000000    2
            99999999999999999999999999999   16
           900000000000000000000000000000    2
           999999999999999999999999999999   16
          9000000000000000000000000000000    2
          9999999999999999999999999999999   17
         90000000000000000000000000000000    2
         99999999999999999999999999999999   17
        900000000000000000000000000000000    2
        999999999999999999999999999999999   18
       9000000000000000000000000000000000    2
       9999999999999999999999999999999999   18
      90000000000000000000000000000000000    2
      99999999999999999999999999999999999   19
     900000000000000000000000000000000000    2
     999999999999999999999999999999999999   19
    9000000000000000000000000000000000000    2
    9999999999999999999999999999999999999   20
   90000000000000000000000000000000000000    2
   99999999999999999999999999999999999999   20

50 rows selected.

      Rating: 4
can we get the
      length from dictionary tables  June 04, 2003

Reviewer:  umesh  from bangalore india
Tom,
I did get the VSIZE to work. Is it possible to query a dictionary table to get
the maximum width associated with a  column
i.e
1) In EMP table if ENAME is VARCHAR2(12)
i need to get 12
2)If EMPNO is NUMBER
I need to get it maximum width
becoz
desc EMP  will give the structure of table
against all varchar2 columns i get associated width
 but will not display any width against number if you have not specified any
,while creating the table .

For number I think the default width is 38 digits

Thanks for your patience

          Followup:

look at data_length in user_tab_columns.

It'll show 22 (max bytes needed to store) for a number(38) and
data_precision/data_scale will be null.

You could always NVL(data_precision,38) as well from that table

      Rating: 5
Length of key
      fields  June 05, 2003
Reviewer:  A Reader
       from UK
Tom,

I've recently heard people debating about whether (if you have a choice) it is
slower/'worse'/bad practice to say store:

TOM KYTE
JOHN DOE
JANE SMITH

as primary keys when you can store say:

TK
JD
JS

(ignoring for the moment the fact that maintaining uniqueness may be an issue).

Is it true that it is less efficient when querying if they key field is 'wider'
?

Regards,

Paul

          Followup:

keys should be keys.

I use surrogates frequently -- because many objects don't have natural keys, but
when a natural key exists, use it.

Forget the length for a moment, the difference between a 10 and 15 byte compare
isn't going to be significant in the grand scheme of things.  Its all about the
data.  Store the data as the data should be stored.

June 05, 2003 balasubramanian -- Thanks for the question
      regarding "Dropping a column in a table (Version Before 8.1.x)", version
      8.0.6/8.1.5

      You Asked
Hi Tom, In oracle 8i we can drop a column. But I want to drop a
column in a table in oracle version 8.0.6. To do that,can I
proceed in the following ways:

1.I will get the object_id of the table by sql:
  select object_id from dba_objects where object_name ='X';
2.I will delete the second column of the above object by sql:
 Delete from col$ where obj#=above_id and col#=2;
 Update tab$ set cols = (old_cols - 1);
 Commit;

3.Shutdown and restart the oracle instance.

Is it illegal to do like that? I think, Oracle Corp. will not be
responsible for any future crashes of my database, is'nt it?

      and we said...
That is the fastest way to terminate your support, yes.

Do not in any circumstances do that.  It'll totally mess up your
database.

The method to "drop a column" in 8.0 and before would be:

o rename X to X_OLD;
o create view X as select <all but column to drop> from X_OLD;
o revoke everything from X_OLD
o grant everything you need on X
o alter X_OLD modify column_to_be_dropped NULL; (if it is
  currently NOT NULL)

The column will appear to be gone as far as anyone is concerned.

You can of course do this physically by moving lots of bytes
around (create table as select, drop old table, recreate it,
reload it) but the view works great.
Review &
      Followup

      Rating: 2
What About
      Update  June 05, 2003
Reviewer:  Hatem

Dear Tom
your solution of creating view on the old table will be usefull only if am going
to select
but in case of updating or insert?
I want to drop columns from tables on oracle db version 8.0.5 or mark it as
unused (as in 8i) i'm afraid my application may refer to.

          Followup:

views work just dandy for this.  what problem do you forsee with DML on a view
that simply hides colums?

      Rating: 2
Drop columns in
      versions prior 8i  June 05, 2003
Reviewer:
      Hatem
Dear Tom
my problem is that my application refers to columns i no longer user, i've
readjust my application to no longer user such columns and i want be 100%
certain that these columns no longer used.
i've tried to recreate tables and reload it but it will take too much time as
many columns i need to drop it.
any hints?

          Followup:

rename the table T to T_TABLE;

create view T as select <columns you want> from T_TABLE;

columns are "dropped".  Now, if your application still references them - I
really don't know what you want from me.   You obviously cannot "drop" them can
you?  they are still used.

      Rating: 1
drop columns in
      8.0.5  June 05, 2003
Reviewer:  hatem

what about updating and inserting data in this view?
how to update and insert in views????
my problem is not adjusting my application my problem is how to drop such
columns or mark them unused?

          Followup:

just do it.

Ok, we have the EMP table, we want to "drop" HIREDATE:

ops$tkyte@ORA806> create table emp as select * from scott.emp;

Table created.

ops$tkyte@ORA806>
ops$tkyte@ORA806> desc emp
 Name                            Null?    Type
 ------------------------------- -------- ----
 EMPNO                           NOT NULL NUMBER(4)
 ENAME                                    VARCHAR2(10)
 JOB                                      VARCHAR2(9)
 MGR                                      NUMBER(4)
 HIREDATE                                 DATE
 SAL                                      NUMBER(7,2)
 COMM                                     NUMBER(7,2)
 DEPTNO                                   NUMBER(2)

ops$tkyte@ORA806> rename emp to emp_table;

Table renamed.

ops$tkyte@ORA806> create or replace view emp
  2  as
  3  select empno, ename, job, mgr, sal, comm, deptno
  4    from emp_table
  5  /

View created.

ops$tkyte@ORA806> desc emp;
 Name                            Null?    Type
 ------------------------------- -------- ----
 EMPNO                           NOT NULL NUMBER(4)
 ENAME                                    VARCHAR2(10)
 JOB                                      VARCHAR2(9)
 MGR                                      NUMBER(4)
 SAL                                      NUMBER(7,2)
 COMM                                     NUMBER(7,2)
 DEPTNO                                   NUMBER(2)

ops$tkyte@ORA806>
ops$tkyte@ORA806> insert into emp
  2  ( empno, ename, job, mgr, sal, comm, deptno )
  3  values
  4  ( 1234, 'blah', 'x', null, 100, 200, 10 );

1 row created.

ops$tkyte@ORA806>
ops$tkyte@ORA806> update emp
  2  set ename = 'halb' where empno = 1234;

1 row updated.

ops$tkyte@ORA806>
ops$tkyte@ORA806> delete from emp where empno = 1234;

1 row deleted.

so, I ask you again -- what is the problem you anticipate here -- I've told
you exactly what you need to do.  Just do it?  Column "gone"

June 05, 2003 Chandra -- Thanks for the question regarding
      "Repoerting Using Range bucket", version 8.1.7

      You Asked
We log all the hotel bookings by users into our database. I am working on a
report to provide the following kind of output.The count is the number of
bookings per user.We want to see how many users registered with us have 0,1,2,3
to 4, 5 to 9, and 10 or more bookings over a certain period of time.

Bookings    0    1    2    3-4    5-9    10+     Total
Count        0    953    474    448    432    407    2714
Percentage    0    35.1%    17.5%    16.5%    15.9%    15.0%   100%

Can you tell me how this can be accomplished using Analytic functions availabe
in Oracle 8.1.7.

Thank You,
chandra

      and we said...
ops$tkyte@ORA817DEV> /*
DOC>drop table t;
DOC>
DOC>create table t ( cust_id int );
DOC>
DOC>insert into t
DOC>select object_id
DOC>  from ( select object_id, rownum r1 from all_objects where rownum <= 2714
),
DOC>           ( select rownum r2 from all_objects where rownum <= 11 )
DOC> where (r1 <= 953 and r2 = 1)
DOC>        or (r1 between 954 and 1428 and r2 <= 2)
DOC>        or (r1 between 1429 and 1900 and r2 <= 3)
DOC>        or (r1 between 1901 and 1917 and r2 <= 4)
DOC>        or (r1 between 1918 and 2350 and r2 <= 5)
DOC>        or (r1 > 2350)
DOC>/
DOC>
DOC>*/
ops$tkyte@ORA817DEV> select decode( grp, 1, 1, 2, 2, 3, 3, 4, 3, 5, 5, 6, 5, 7,
5, 8, 5, 9, 5, 10 ) grp,
  2             count(*) ,
  3             round( ratio_to_report(count(*)) over() * 100, 2 )
  4    from ( select count(*) grp from t group by cust_id )
  5   group by decode( grp, 1, 1, 2, 2, 3, 3, 4, 3, 5, 5, 6, 5, 7, 5, 8, 5, 9,
5, 10 )
  6  /

       GRP   COUNT(*) ROUND(RATIO_TO_REPORT(COUNT(*))OVER()*100,2)
---------- ---------- --------------------------------------------
         1        953                                        35.11
         2        475                                         17.5
         3        489                                        18.02
         5        433                                        15.95
        10        364                                        13.41

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select decode( xx, 1, 'Count', 'Percentage' ) "Bookings",
  2             0 "0",
  3             decode( xx, 1, cnt1, pct1 ) "1",
  4             decode( xx, 1, cnt2, pct2 ) "2",
  5             decode( xx, 1, cnt3, pct3 ) "3-4",
  6             decode( xx, 1, cnt5, pct5 ) "5-9",
  7             decode( xx, 1, cnt10, pct10 ) "10+",
  8             decode( xx, 1, cnt1+cnt2+cnt3+cnt5+cnt10, 100 ) "Total"
  9    from (
 10  select max( decode( grp, 1, cnt ) ) cnt1,
 11         max( decode( grp, 1, pct ) ) pct1,
 12         max( decode( grp, 2, cnt ) ) cnt2,
 13         max( decode( grp, 2, pct ) ) pct2,
 14         max( decode( grp, 3, cnt ) ) cnt3,
 15         max( decode( grp, 3, pct ) ) pct3,
 16         max( decode( grp, 5, cnt ) ) cnt5,
 17         max( decode( grp, 5, pct ) ) pct5,
 18         max( decode( grp, 10, cnt ) ) cnt10,
 19         max( decode( grp, 10, pct ) ) pct10
 20    from (
 21  select decode( grp, 1, 1, 2, 2, 3, 3, 4, 3, 5, 5, 6, 5, 7, 5, 8, 5, 9, 5,
10 ) grp,
 22             count(*) cnt,
 23             round( ratio_to_report(count(*)) over() * 100, 1 ) pct
 24    from ( select count(*) grp from t group by cust_id )
 25   group by decode( grp, 1, 1, 2, 2, 3, 3, 4, 3, 5, 5, 6, 5, 7, 5, 8, 5, 9,
5, 10 )
 26             )
 27             ), (select 1 xx from dual union all select 2 xx from dual)
 28   order by xx
 29  /

Bookings     0       1          2        3-4        5-9        10+      Total
---------- --- ------- ---------- ---------- ---------- ---------- ----------
Count        0     953        475        489        433        364       2714
Percentage   0    35.1       17.5         18         16       13.4        100

ops$tkyte@ORA817DEV>
Review & Followup

      Rating:
      5
  June 04, 2003
Reviewer:
      A reader
Excellent !!!

      Rating: 5
Very
      good  June 04, 2003
Reviewer:  A reader

      Rating: 5
And why not
      CASE too?  June 05, 2003
Reviewer:  Angus
      McTavish aged 75  from Cumbria, England
Angus here.
Very Good. If we're going for this new-fangled stuff, why not go the whole hog
and use CASE instead of decode. It's aesthetically rather more pleasing, I
think.

SQL> l
  1  select
  2  case
  3     when grp=1 then 1
  4     when grp=2 then 2
  5     when grp=3 then 3
  6     when grp=4 then 3
  7     when grp=5 then 5
  8     when grp=6 then 5
  9     when grp=7 then 5
 10     when grp=8 then 5
 11     when grp=9 then 5
 12     else 10
 13  end grp,
 14  count(*) ,
 15  round( ratio_to_report(count(*)) over() * 100, 2 )
 16  from ( select count(*) grp from ttt group by cust_id )
 17  group by case
 18     when grp=1 then 1
 19     when grp=2 then 2
 20     when grp=3 then 3
 21     when grp=4 then 3
 22     when grp=5 then 5
 23     when grp=6 then 5
 24     when grp=7 then 5
 25     when grp=8 then 5
 26     when grp=9 then 5
 27     else 10
 28* end
SQL> /

       GRP   COUNT(*) ROUND(RATIO_TO_REPORT(COUNT(*))OVER()*100,2)
---------- ---------- --------------------------------------------
         1        953                                        35.11
         2        475                                         17.5
         3        489                                        18.02
         5        433                                        15.95
        10        364                                        13.41

SQL>

          Followup:

cause the version was 817 and the first thing they might try to do is stuff that
in plsql which leads to the inevitable question "why did it work in sqlplus but
not plsql..."

case isn't "plsql friendly" until 9i

      Rating: 5
Counts in range
      buckets  December 09, 2003
Reviewer:
      A reader
Thanks, I tried to apply this to my problem, but couldnt figure out how to do
it.

I have a table with a start_date column.

I have a parametrized report where the user can enter 4 dates i.e. a start range
(from/to date) and a end range (from/to date).

I want to get something like this in addition to the other columns in the table

count(*) of records that fall in the first range
count(*) of records that fall in the second range
difference and % difference between the above 2

Ideally, since this is a report where the users can enter any values at
run-time, this would be best exposed as a view into my base table with the
appropriate 4 date columns exposed and my report can just plug in the 4 date
parameters.

I was thinking a 4-way self-join of the table using analytic functions to get my
count(*)s but cant quite write the SQL.

Help? Thanks

June 05, 2003 M. -- Thanks for the question regarding "SQL
      Sharing and Master Details Information.", version 8.1.6

      You Asked
Hi Tom,

How can I display information from a Mster and Details Tables in a single Block
without repeting the Master records.

Like Dept: 10 have 250 Employee records, I need to show Dept Id: 10 only once
and all the Details recrds in one Block. How can I implement this.

Like this
10      Scott    $4500    Clerk
        David    $5000    Manager
        Robert   $4200    Salesman

2) In Libraray cache it is said that the Identical SQL statements will not be
reparsed. Is this True for Muti user environment or for just Single user SQL
Statements.

Suppose  User A Issues Select * from emp;
         User B Issues Select * from emp;
Will this statement be reparsed for User B even user b's Emp table has different
records or may be diffeent structure.

3) How can we Configure OPS (Oracle Parallele Server) and even Parallele
Queries, any Link to documentation is appreciated.

4) What are the Pros and Cons of a Dedicated Server and MTS.

Thanks...

      and we said...
1) you would use a master/detail block where the DEPTNO was in the MASTER block
and the remaining columns came from the detail block.  Set the records displayed
to 1 in the masterand 3 in the detail.  Move the deptno to be next to the ename
in the layout editor and you are done.

2) SQL statements that are identical not only in Syntax (as they are typed in)
but also in ACCESS (same emp table, same access rights, some optimizer goals
set, and so on) will be shared by any number of users.

If user A sees a different EMP table then user B -- there will necessarily be
two different plans in the shared pool.

3)
OPS

http://technet.oracle.com/doc/oracle8i_816/paraserv.816/index.htm

PQ

http://technet.oracle.com/doc/oracle8i_816/server.816/a76956/manproc.htm#1870
4) You would use MTS on a machine where you have basically maxed it out -- you
cannot connect additional users yet you need to.  If you have an environment
where you have lots of concurrently connected users that have some idle time
(eg: a client server application is many times like this) and need to get more
users connected, you would use MTS.  MTS will allow you to use a smaller set of
processes (less OS resources) to have the same number of users logged in.

So, unless your machine is maxed out in dedicated server mode, you would most
likely use that.

followup to comment one

Excellent point -- that is true.

Another case is if you are hosting EJB's in the database -- the architecture
demands that you use MTS at that point for the beans.

Review &
      Followup

      Rating: 4
How in
      SQL  July 09, 2001
Reviewer:  Reader
       from USA
Regarding question 1, how we implement in SQL not in FORM?

Thanks

      Rating: 4
MTS HElpful in
      OPS  July 09, 2001
Reviewer:  Arup Nanda
       from Norwalk, Connecticut, USA
With respect to the last question, whether MTS is more useful as opposed to
dedicated server mode, I would like to add that under OPS (Oracle Parallel
Server), or now Oracle Real Application Cluster, the MTS is required if Load
Balancing is to be setup among all the nodes of the cluster. In fact to acheive
load balancing, where the workload is uniformly distibuted across all the
dispatchers, MTS is the only solution.

      Rating: 3
use analistic
      function  July 10, 2001
Reviewer:  hahaer
       from zhejiang China
Maybe this statement can help

SELECT decode((row_number() over (partition by deptno order by
empno)),1,deptno,null) as dept ,ename
  FROM emp;

      Rating: 3
Master Detail
      with only 1 row from detail  June 05, 2003

Reviewer:  Paul  from Columbus, OH
Tom,

How to write this query if I want only 1 row from the detail after joining the
master-detail tables ? That is the detail will contain many rows for the master
table key but I only want 1 row from the detail, doesnt matter which row from
the detail table. But if I can get the desired row from the detail table, that
would be a bonus :)

order_mst:
order_no  order_date  supplier
1001      01/04/1999    775989
1002      04/06/1999    365890
1003      08/12/2000    566582
1004      11/01/2001    369874
1005      12/15/2001    963214
1006      12/31/2001    775989
1007      12/15/2001    654987

order_dtl:
order_no  item_no    order_qty
1001      1122001         1000
1001      1122002          650
1001      1122003          200
1002      5678901         3000
1002      5678905          300
1003      3213210          900
1004      3213210           50
1004      3213211           25
1004      3213212          785
1004      3213213          325
1004      3213214         1000
1005      7756541           90
1005      7756542          100
1005      7756543          200
1005      7756544          300
1005      7756545          400

Desired Output (1):
order_no  order_date  supplier  item_no
1001      01/04/1999    775989  1122001
1002      04/06/1999    365890  5678901
1003      08/12/2000    566582  3213210
1004      11/01/2001    369874  3213211
1005      12/15/2001    963214  7756545

Desired Output (2):
order_no  order_date  supplier  item_no
1001      01/04/1999    775989  1122001
1002      04/06/1999    365890  5678901
1003      08/12/2000    566582  3213210
1004      11/01/2001    369874  3213211
1005      12/15/2001    963214  7756545
1006      12/31/2001    775989
1007      12/15/2001    654987

Thanks in advance !!

          Followup:

select order_mst.*,
       (select item_no
          from order_dtl
         where order_dtl.order_no = order_mst.order_no
           and rownum = 1 ) item_no
  from order_mst
/

      Rating: 4
Forgot to
      mention more than 1 column  June 05, 2003

Reviewer:  Paul
Thanks Tom ! But I forgot to mention how would I write the sql for more than 1
column ?

Thanks again !

          Followup:

here are some other techniques:

scott@ORA920> select deptno, dname,
  2         to_number(substr(data,1,5)) empno,
  3             substr(data,6,15) ename,
  4             substr(data,16) job
  5    from (
  6  select deptno,
  7         dname,
  8        (select to_char(empno,'fm00000') || rpad(nvl(ename,' '),10) || job
  9               from emp
 10                  where emp.deptno = dept.deptno
 11                    and rownum = 1) data
 12    from dept
 13         )
 14  /

    DEPTNO DNAME               EMPNO ENAME           JOB
---------- -------------- ---------- --------------- ----------
        10 ACCOUNTING           7782 CLARK     MANAG MANAGER
        20 RESEARCH             7369 SMITH     CLERK CLERK
        30 SALES                7499 ALLEN     SALES SALESMAN
        40 OPERATIONS

scott@ORA920>
scott@ORA920>
scott@ORA920> create or replace type myScalarType as object
  2  ( empno number, ename varchar2(10), job varchar2(9) )
  3  /

Type created.

scott@ORA920>
scott@ORA920> select deptno, dname, d.data.empno, d.data.ename, d.data.job
  2    from (
  3  select deptno,
  4         dname,
  5             (select myScalarType(empno,ename,job)
  6                from emp
  7                   where emp.deptno = dept.deptno
  8                     and rownum = 1) data
  9    from dept
 10         ) d
 11  /

    DEPTNO DNAME          DATA.EMPNO DATA.ENAME DATA.JOB
---------- -------------- ---------- ---------- ---------
        10 ACCOUNTING           7782 CLARK      MANAGER
        20 RESEARCH             7369 SMITH      CLERK
        30 SALES                7499 ALLEN      SALESMAN
        40 OPERATIONS

scott@ORA920>
scott@ORA920> select *
  2    from (
  3  select d.deptno,
  4         d.dname,
  5             e.empno,
  6             e.ename,
  7             e.job,
  8             row_number() over ( partition by d.deptno order by ename ) rn
  9    from dept d, emp e
 10   where e.deptno(+) = d.deptno
 11         )
 12   where rn = 1
 13  /

    DEPTNO DNAME               EMPNO ENAME      JOB               RN
---------- -------------- ---------- ---------- --------- ----------
        10 ACCOUNTING           7782 CLARK      MANAGER            1
        20 RESEARCH             7876 ADAMS      CLERK              1
        30 SALES                7499 ALLEN      SALESMAN           1
        40 OPERATIONS                                              1

scott@ORA920>

      Rating: 5
You are simply
      great !!  June 05, 2003
Reviewer:  Paul

WOW !! The learning never ends at this site !!

Thanks Tom !!!

June 05, 2003 Sudip -- Thanks for the question regarding
      "PL/SQL Array processing ..", version 8.1.6

      You Asked
I have a declaration like:

Type emplist is TABLE OF  emp.empname%type;
v_emp    emplist;

how can i accept a list of Employee Names as a single string
with "comma-separated" values (empname) as a formal parameter to
a procedure and then put them into the collection variably v_emp
?

      and we said...
by parsing.  something like this:

create or replace function to_list
                     ( p_string in varchar2 ) return emplist
as
     l_data      emplist;
     l_string           long default p_string || ',';
     l_n                number;
begin
  loop
    exit when l_string is null;
    l_n := instr( l_string, ',' );
    l_data( l_data.count ) := substr( l_string, 1, l_n-1 );
    l_string := substr( l_string, l_n+1 );
  end loop;
  return l_data;
end;
/

Review &
      Followup

      Rating: 4
Infinite
      Loop  November 13, 2002
Reviewer:
      A reader
Hi Tom,
I made the following changes to the code.
When I used the exit when l_empnames IS NULL it resulted in an infinite loop.

l_iPos := 1;
begin
  while l_ipos != 0
  loop
    l_ipos := instr( trim(l_empnames), '$' );
    l_emp2 := substr(trim(l_empnames), 1, l_ipos-1 );
    l_empnames := substr( trim(l_empnames), l_ipos+1 );
    dbms_output.put_line(trim(l_emp2));
  end loop;
end;

However, now things are fine.

Cheers!
Abhijit

      Rating: 4
Infinite
      Loop  November 13, 2002
Reviewer:  Abhijit
       from Mumbai
Hi Tom,
I made the following changes to the code.
When I used the exit when l_empnames IS NULL it resulted in an infinite loop.

l_iPos := 1;
begin
  while l_ipos != 0
  loop
    l_ipos := instr( trim(l_empnames), '$' );
    l_emp2 := substr(trim(l_empnames), 1, l_ipos-1 );
    l_empnames := substr( trim(l_empnames), l_ipos+1 );
    dbms_output.put_line(trim(l_emp2));
  end loop;
end;

However, now things are fine.

Cheers!
Abhijit

      Rating: 4
Weird result
      using this function  June 05, 2003
Reviewer:
      Dianna  from Florida
Tom,

I am trying to use your original to_list function and I don't understand the
results that I am getting.

The code:

CREATE OR REPLACE package types as
type emplist is TABLE OF emp.ename%type index by binary_integer;
v_emp emplist;
end;
/

CREATE OR REPLACE function to_list
                     ( p_string in varchar2 ) return types.emplist
as
     l_data      types.emplist;
     l_string           long default p_string || ',';
     l_n                number;
begin
  loop
    exit when l_string is null;
    l_n := instr( l_string, ',' );

    l_data( l_data.count ) := substr( l_string, 1, l_n-1 );
    dbms_output.put_line('FUNCTION
RESULT:'||l_data.count||':'||l_data(l_data.count-1));
    l_string := substr( l_string, l_n+1 );
  end loop;
  return l_data;
end;
/
create or replace procedure containerlist
as

   l_containers types.emplist;
   x            number;
begin

   l_containers := to_list('AAA,BBB,CCC,DDD');

  for x in 1..l_containers.count loop
     dbms_output.put_line('FINAL RESULT:'||x||':'||l_containers(x));
  end loop;
end;
/
Here is my result:
Oracle9i Enterprise Edition Release 9.2.0.2.1 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.2.0 - Production

SQL> set serveroutput on;
SQL> exec containerlist;
FUNCTION RESULT:1:AAA
FUNCTION RESULT:2:BBB
FUNCTION RESULT:3:CCC
FUNCTION RESULT:4:DDD
FINAL RESULT:1:BBB
FINAL RESULT:2:CCC
FINAL RESULT:3:DDD
BEGIN containerlist; END;

*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at "DGW.CONTAINERLIST", line 12
ORA-06512: at line 1
------------------------------------------
Any idea why I have different results when I print the contents of the table in
the calling procedure?

Thanks,
Dianna

          Followup:

should have been:

create or replace function to_list
                     ( p_string in varchar2 ) return emplist
as
     l_data      emplist;
     l_string           long default p_string || ',';
     l_n                number;
begin
  loop
    exit when l_string is null;
    l_n := instr( l_string, ',' );
    l_data( l_data.count+1 ) := substr( l_string, 1, l_n-1 );
    l_string := substr( l_string, l_n+1 );
  end loop;
  return l_data;
end;
/

June
      05, 2003 razvan -- Thanks for the
      question regarding "what is the difference between dynamic and static
      sql", version 7.3.2

      You Asked
hello tom,

please can you have a time to explain me what is the difference
between sql statement static ( e.q. select a,b,c from t where
a=... etc) and sql statement dinamic (type dbms_sql....)
and what is the use of the last sql statement?

thank a lot

razvan

      and we said...
Static SQL is SQL that is known at compile time -- whether it be
"static" sql in a pro*c program or static sql in a plsql
application.

With static sql, the compiler (or precompiler) is able to "see"
the sql and do much more with it.  It knows what data types it
uses, what inputs it has, what the outputs look like.  It knows
the tables it is accessing and can make the security checks at
compile time (especially with respect to PLSQL this is true) and
so on.  In short, alot of work can be done at compile time.

With dynamic sql, we have SQL that is not known until run time.
the PLSQL engine does not know how many columns might be
fetched, what tables will be accessed, what inputs are expected.
 You, the programmer, must do more work to deal with this.

Lets consider a small example.  We want to get the COUNT of rows
in a table -- the table is EMP.  We'll write 2 functions, one
static and one dynamic to see the differences:

scott@ORA734.WORLD> create or replace
  2  function get_rows_dynamic( p_tname in varchar2 )
                                                return number
  3  as
  4      l_theCursor     integer default dbms_sql.open_cursor;
  5      l_columnValue   number default NULL;
  6      l_status        integer;
  7  begin
  8      dbms_sql.parse(  l_theCursor,
  9                      'select count(*) from ' || p_tname,
 10                       dbms_sql.native );
 11
 11      dbms_sql.define_column( l_theCursor, 1, l_columnValue
);
 12      l_status := dbms_sql.execute(l_theCursor);
 13      if ( dbms_sql.fetch_rows(l_theCursor) > 0 )
 14      then
 15         dbms_sql.column_value(l_theCursor, 1,
l_columnValue);
 16      end if;
 17      dbms_sql.close_cursor( l_theCursor );
 18      return l_columnValue;
 19  end ;
 20  /

Function created.

scott@ORA734.WORLD>
scott@ORA734.WORLD>
scott@ORA734.WORLD> create or replace function get_rows_static
                                                   return number
  2  as
  3      l_cnt number;
  4  begin
  5      select count(*) into l_cnt from EMP;
  6      return l_cnt;
  7  end;
  8  /

Function created.

scott@ORA734.WORLD>
scott@ORA734.WORLD> set serveroutput on
scott@ORA734.WORLD>
scott@ORA734.WORLD> exec dbms_output.put_line(
                              get_rows_dynamic( 'EMP' ) );
14

PL/SQL procedure successfully completed.

scott@ORA734.WORLD> exec dbms_output.put_line(get_rows_static);
14

PL/SQL procedure successfully completed.

As you can see -- the static routine is much smaller, easier to
code and definitely more understandable.  That is not to say the
dynamic routine doesn't have benefits -- it does.  We can use it
more generically.  For example I can simply:

scott@ORA734.WORLD> exec dbms_output.put_line(
                               get_rows_dynamic('DEPT' ) )
4

PL/SQL procedure successfully completed.

showing I can use that routine now to count the rows in ANY
table, not just the emp table.  That is both a bonus and a
downside.  I did not include a lot of error checking in my
routine and as you'll find, its really easy to make this routine
fail, for example:

scott@ORA734.WORLD> exec dbms_output.put_line(
               get_rows_dynamic('A_table_that_does_not_exit' ) )
begin dbms_output.put_line(
get_rows_dynamic('A_table_that_does_not_exit' ) ); end;

*
ERROR at line 1:
ORA-00942: table or view does not exist
ORA-06512: at "SYS.DBMS_SYS_SQL", line 239
ORA-06512: at "SYS.DBMS_SQL", line 32
ORA-06512: at "SCOTT.GET_ROWS_DYNAMIC", line 7
ORA-06512: at line 1

Since we are using dynamic sql -- an error that the plsql
compiler would have caught had this been static sql -- it
something we have to catch ourself at runtime.

So, dynamic sql is very flexible -- adds some amount of overhead
(we have to do the security check and extra code to supply the
functionality plsql normally provides for us) but is harder to
code.  You should probably use static sql until you cannot,
until you run into a case where only dynamic sql will work and
then use dynamic sql.

Some languages and apis (eg: JDBC, ODBC, OCI) only understand
and do "dynamic sql"

Review &
      Followup

      Rating: 5
Is there a
      better way to use dynamic sql  June 05, 2003

Reviewer:  Jerry
Tom, I've been thinking...

I'm an Oracle DBA and I see my people doing dynamic sql in their web
applications (asp, php, python -- whatever). They build their queries at run
time according to what they select on their web pages.

Look at the example of a school system -- if they choose a building on the form,
then that results in their adding something like "where building_id=3". If they
choose a room they add "and room_id=8" and so on. (Please, I am not raising the
question of binds here -- I will later on)

For every combination of choices on the form, there's a whole new statement.

Questions:

1. My view is that it it somewhat hampers *sql reuse*, since each new statement
will be different based on what the user selects. Do you agree ?

2. I see the code in those cgi's is difficult both to read and to write -- full
of "ifs" and "cases". Is there some way to define a "generalist" query in the
application that serves all combinations of choices some user selects of the
form ? I would then improve shared pool utilization -- I guess.

3. I've read something on prepared statements. Do they have something to do with
this ?

Well, fellow... Could you point me to some article(s) on this ?

          Followup:

1) there are a finite number of predicates there -- GIVEN the use of bind
variables.

As long as they build the query in a consistent fashion -- it'll be ok.  Eg:
columns A, B, C can result in

where a <op> ? and b <op> ? and c <op> ?
where a <op> ? and b <op> ?
where a <op> ? and c <op> ?
where b <op> ? and c <op> ?
where a <op> ?
where b <op> ?
where c <op> ?

and that is it.

2) at the huge expense of overall query performance, yes it could be done.
Problem is the optimizer would have a heck of a time optimizing it -- and it
would not be optimal for the specific cases

3) a prepared statement is necessary for BINDS so, yes, they must be using that
in order to get shared sql.

June
      05, 2003 Chad -- Thanks for the
      question regarding "Changing Column Headings", version 8.1.7

      You Asked
Hey Tom,

Quick question - Is it possible to use a function for the column heading in a
query.  For example; select id, name, date_hired as sysdate from employee; And
have the output look similar to this:

ID    NAME        09-JUL-02
------- --------------- ---------
1    BOB        06-MAY-02
2    MARY        26-SEP-00

Thanks!

      and we said...
You would have to use dynamic sql like this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> variable x refcursor
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2          open :x for 'select user_id, username, sysdate "' ||
to_char(sysdate) || '" from all_users';
  3  end;
  4  /

PL/SQL procedure successfully completed.

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

   USER_ID USERNAME                       09-JUL-02
---------- ------------------------------ ---------
         0 SYS                            09-JUL-02
         5 SYSTEM                         09-JUL-02
        11 OUTLN                          09-JUL-02
....

OR you can use this method as well:

set termout off
column dt new_val dt
select sysdate dt from dual;
set termout on

column X heading "&dt"
select user_id, username, sysdate X from all_users;

Review &
      Followup

      Rating: 5
Thanks -
      Excellent as always!  July 09, 2002
Reviewer:
      chad  from SLC, UT

      Rating: 5
Changing Column
      Headings", version 8.1.7  October 10, 2002

Reviewer:  Paul Goodwin  from England
This is just what I was needing for a report sql I was writing. Users enter the
date as part of the search criteria, and one of the output columns uses that
date as it's heading.

I used the second method of your reply.

      Rating:
      3
  June 05, 2003
Reviewer:
      A reader
Tom,

Is it possible to display the full heading without truncating in SQL*Plus ? The
char/varchar columns seem to get truncated depending on the data but the numeric
columns are displayed completely. How to overcome this problem ?

Well, is there any other way than saying
column <column_name> format A20

This does display but what if there are multiple columns ?

          Followup:

the columns just format to the width of the data.

column cname format aN

is the way to overcome this default format.

June 06, 2003 Santhanam -- Thanks for the question regarding
      "Insert rows to master table. ", version 8.1.7.3

      You Asked
Hi
   I got batch job which inserts data to a table. The table has a foreign key.
If a record fails due to referential integrity violation I want to insert the
row to the parent table.

Example

Desc emp
-----------------
id    number
name  varchar2(100)
sex   char(1)
dept  number foreign key references dept(id)
----------------

Desc  Dept

id number
desc varchar2(100)

-------------------

When my batch job tries to insert a record which gets rejected to a referential
integrity violation the record should be inserted to the parent table.

Example

select * from emp
id  name  sex dept
--------------------
1   john    m  1
2   jack    m  1
3   tom     m  2

select * from dept
id    desc
-------------
1     consulting
2     management

If my batch job tries to insert a record like  this
insert into emp values (10,tim,m,10 ); it will fail due to ref integriry.
I want to insert the value to the parent as follows

On exception
Insert into dept values(10,null);

Please could you let me know if this is possible.

Many thanks for helping DBA'a and Developers thsough your forum.

Regards
Santhanam

      and we said...
your code would have to do that and then retry the child table insert.

if your code cannot do that, you can:

create view emp_view
as
select * from emp;

create or replace trigger instead of insert on emp_view
begin
   insert into emp .....
exception
   when primary key violated then
       insert into dept
       insert into emp
end;
/

and insert into the view.
Review & Followup

      Rating: 4
Not too sure I
      like that approach  June 06, 2003
Reviewer:  A
      reader  from UK
This is batch, to me this implies big (oh, what is big anyway?)loads. So if the
odd record fails it just due missing parent data or is it bad source data? what
if every record fails with a RI fault - do fill up your parent table with
garbage?

I would favour setting up an exceptions table and a staging table for the batch,
this should have the same RI constraints as target table. Disable constraints on
the stage table, load the data, enable constraints with an "exceptions into"
clause. Then check the exceptions, and if reasonable fix-up the parent table. If
you can define the business rules for inserting into the parent table then you
could automate the whole process.

I think it always best to keep the data clean - fixing up data in situ is always
bad news so avoid it

      Rating: 4
insert master
      rows  June 09, 2003
Reviewer:  A reader

Good example

June 06, 2003 Luis -- Thanks for the question regarding "Home
      made softwares x Migration", version 8.1.7

      You Asked
Hi, Mr. Kyte.

What are the major concerns developers (and myself) must have before migrating
databases version 8.1.7 to 9iR2? What problems should we expect to occur after
the migration?

Thanks in advance.
Have a nice weekend.

Luis C L Nobre
Brasilia - Brazil

      and we said...
You should expect a test cycle to occur after migration/upgrading to a new
release of software.

You will probably find at least one "issue" to resolve.  You can choose to find
and resolve the issue in private (by testing) or in public by just upgrading
production -- if you do it publically, be prepared to

o be humiliated
o have all confidence in you wiped out for a really long time

I suggest doing the test thing personally ;)

Just like if you upgraded your OS
Or your development tool
Or your network
Or anything
Review & Followup

      Rating:
      4
OK!  June 06, 2003
Reviewer:  Luis C L
      Nobre
Thanks.

June 06, 2003 satish -- Thanks for the question regarding
      "PL/SQL Table Performance.", version 8.1.6

      You Asked
Hi Tom,
    I need u r suggestion for PL/SQL Tables. I am writing Procedures for Purging
and deletion of data. My database is very huge in TB.
to avoid network traffic, i used PL/SQL Table to hold temporary data.
Their are around 15 pl/sql tables holding different type of data.

I am using Dynamic sql, and using pl/sql table i am fetching data in Arrays. I
think this will improve performace.

    M I thinking in right way ?

    Looking forward for your opinion.

regards
satish sawant.

      and we said...
purging data in a large data warehouse should be handled via intelligent
partitioning designed and implemented to allow you to purge

o without delete
o without undo
o without redo

a simple "alter table" should do the purge.  there would be no plsql tables, no
dynamic sql involved.

That's about as far as I can go with this question since I do not know the
nuances of your system and why you would be using DELETE to purge.

but yes, in general, for large fetches -- bulk collect with the LIMIT clause (so
you don't get slammed with 100,000 rows at once) is faster then single row
fetches

HOWEVER

doing it in a single sql statement would be faster

HOWEVER

doing it via ddl would be instantaneous.
Review &
      Followup

      Rating: 2
PL/SQL
      Table  June 06, 2003
Reviewer:  satish
      sawnat  from INDIA,mumbai
Yes Tom for purging i am using Partitioning and droping partitons.

I have another option for user that is Deleting. I am writing procedures for
Purging as well as deleting. during procedures User will specify his/her input
for deletion or purging. In this process when ever i need to stored some
temporary data, I am using PL/SQL Table.

As Deletion has so many domain constraints and checks which i need to do it. for
that one way i always go to database and check it. one way is, i can keep
necessary data in memory and do it. so i am following 2 method.
Pl. Comment.

One more thing In DBMS_SQL.BIND_ARRAY(cur_handler,position,
Pl/SqlTable,cntl,lower no.). what is the use of cntl. If i specify cntl = 100,
is it means that 100 records will fetched at one network traffic.

Thanx for your response and waiting for reply.

          Followup:

I let the database do my work and strive to write as little code as humanly
possible.

The less code I write, the faster it goes.

see

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76936/dbms_sq2.htm#998630

June 06, 2003 Amarnath -- Thanks for the question regarding
      "some Interview Questions.", version 8.1.7

      You Asked
Hi Tom

Recently i moved into Oracle DBA field.In a recent interview i had,i had a few
questions like this

1) what do u do when PMON process died? how do u interpret the reasons for
failure?

(i know when any of the background processes died,automatically the database
will shuts down.and alert log file will contain the pmon failure
information...)they expect some thing other than what i said?

2)u r working on a client-server environment?there is a problem that one client
can't communicate the server?where is the problem?

i said
check for os level connection using ping, then check for the listner and check
for the net service name...

all of these are fine...then also not working?then what u do is the question
they fired at me?

waiting for ur reply.
Thanks
Amar

      and we said...
1) you start the database back up first ;)

then you review the alert log

then gather the trace file(s) for uploading to metalink

then open a tar with support and search metalink if the reason is not obvious,
which it won't be -- it'll be something like "pmon died with 489"

2) the problem would be that I would be in a dream state, flashing back to
client server....

but seriously -- as I like to say "totally insufficient data to say anything
about anything".  I would respond with "yeah, well, my car wouldn't start
yesterday, why not?"

was there an ORA-xxxxx error? what are the symptoms.  start by easking end user
"is it plugged in" i guess.

If you checked ping, checked the listener, checked the service name -- yet you
didn't have an ORA-xxxxx error code in hand, I would say you missed the obvious
"get the ORA-xxxxx error code and use oerr to see what it says to do"

Review &
      Followup

      Rating: 4
My
      Inputs  June 06, 2003
Reviewer:
      A reader  from NJ
As for the second question, after checking ping to server, checking listener, I
will do tnsping to the instance. It will give me some information whether client
is able to see the instance at the server. If not, then resolve the issue by
modifying the tnsnames.ora at the client.

Am I correct?

          Followup:

i want the ora-xxxx message still.

June 06, 2003 kiril -- Thanks for the question regarding "LMT
      and extend management", version 9.2.0.1 EE

      You Asked
Dear Tom

Could you comment following case:

Tablespace TS1 is LMT with uniform allocation unit 10Mb. In TS1 there was
created table T1(something) STORAGE(INITIAL 5M NEXT 5M MAXEXTENTS UNLIMITED).

Q1)When table have been created we will have 10Mb allocated from TS1, but
initial extend of T1 is 5Mb. Do we have 5Mb lost because of difference between
TS1 and T1 size of extends?

Q2) When first 5Mb of table is full with data Oracle will try to allocate next
5Mb, but uniform of TS1 is 10Mb.What Oracle will do in this situation - will use
5Mb left from initial extend or allocate new 10 Mb extend ?

Do we have any exception for indexes or the above is valid for them?
Please, make a short comment.
Thanks
Kiro

      and we said...
q1) we take the initial, next, pctincrease and minextents to compute how much
would have been allocated in a DMT (5m in your case)

we allocate AT LEAST that amount in the LTM (10m in your case is the smallest we
could allocate)

so, you'll get a single 10m extent, all of which will be usable -- no "wastage"

q2) the storage parameters in an LMT are used only to figure out "at least how
much to allocate initially".  After that -- they are ignored.  So, we'll fill up
10m, then get 10m more.

indexes are just a segment, like tables are.  nothing different about how they
allocate and use extents really.
Review &
      Followup

      Rating:
      4
  June 06, 2003
Reviewer:  kiro

June 06, 2003 Yves -- Thanks for the question regarding
      "Inserting clob performance issue: enable vs disable strorage in row",
      version 8.1.7

      You Asked
Storage in row DISABLE vs ENABLE: why such a performance difference ?
With ENABLE, inserts is about 30 times faster than with DISABLE !

I thought using DISABLE would be a better option because the clob values (text
to be filled) would be probably updated with a larger size and disable option
would make easier storage management.

Would you suggest other option ?

Thank you.

drop table toto;
create table toto (
  A  VARCHAR2 (30)  NOT NULL,
  B  VARCHAR2 (30)  NOT NULL,
  C  clob default empty_clob()
)
    lob(c) store as toto_name_lo(disable storage in row)
;

set feedback off
set timing on;
prompt
prompt Insert lob with enabled storage in row:
insert into toto (a, b, c)
select owner a, object_name b, owner || ' ' || object_name c from all_objects
where rownum < 1000
;
set timing off;
rollback;

drop table toto;
create table toto (
  A  VARCHAR2 (30)  NOT NULL,
  B  VARCHAR2 (30)  NOT NULL,
  C  clob default empty_clob()
)
    lob(c) store as toto_name_lo(enable storage in row)
;
set timing on;
prompt
prompt Insert lob with disabled storage in row:
insert into toto (a, b, c)
select owner a, object_name b, owner || ' ' || object_name c from all_objects
where rownum < 1000
;
set timing off;
rollback;

-- This produces:

Connecté à :
Oracle8i Release 8.1.7.1.0 - Production
JServer Release 8.1.7.1.0 - Production

Table supprimée.

Table créée.

Insert lob with enabled storage in row:
Ecoulé : 00 :00 :15.59

Insert lob with disabled storage in row:
Ecoulé : 00 :00 :00.62
SQL>

      and we said...
you are storing a trivial amount of data in the clob.

A clob stored out of line requires three structures:

o the table itself will have a lob locator (the size of this lob locator is
actually LARGER then you test data above!)

o a lob index.  The lob locator points into this index, this index then points
to the pages (chunks) of the lob

o a lob segment.  This is where the data finally goes to.

A clob stored inline requires 1 structure -- the table itself.

Storing clob data out of line (which will happen automatically once the clob
exceeds about 4,000 bytes) makes it so that the structured table data is more
efficiently stored and accessed.  Consider a table with 100 rows and each row
has a 4meg LOB.  If the LOB were stored "inline", that would be a 400meg table
-- full scanning that table would take 400meg of IO.  Now, consider that the lob
will be stored OUT of line -- a full scan of that table might read a single
block (very few blocks would be needed to store 100 rows of data normally).

Do the test with REAL sized data.  If most of your clobs are less then 4000
bytes, enabling storage inline makes sense (cuts out work).  If most are greater
then 4000 disabling inline storage might make sense.

Review &
      Followup

      Rating: 4
But why such a
      big difference in response time ?  February 22, 2002

Reviewer:  Yves Debizet  from Grenoble, France

Length of text to be filled will be between 0 and some hundreds of (UTF8)
characters (rarely more than 4000 characters).

Due to the facts that, in the first phase of our project, we have a lot of
insertion and slow response time when OUT of LINE storage, I will privilege IN
LINE storage.

I will examine the possibility of OUT OF LINE in a later phase of the project.

What you answer does not tell about is why such a big difference between IN LINE
and OUT OF LINE storage of clob ?

The reason might be that instead of inserting in one data block when IN LINE
storage, three data blocks (data + index + lob blmocks) are inserted when OUT OF
LINE storage.

But when inserting in a table with 2 indexes (so 1 data block + 2 index blocks)
versus inserting in a table with no index, I do not expect such a big difference
in time insertion !

Thank you for your answer.

          Followup:

I don't see whats so hard to see here.

When we store data inline, its very very straight forward.  If you have less
then say 4000 bytes -- just put it there, done, return to client.

The code to do the out of line storage has a MUCH longer code path.  Much much
longer.  It does a ton more work.  It does more IO, it creates more data, it
executes alot of code.  Heck, in your example -- the lob locator you make us
generate is LARGER in fact then the data itself.  You are doing many TIMES the
IO (the work) using this approach (out of line storage).

      Rating: 4
enable In row
      and chain count , Huge table distinct on a LOB
      table  September 20, 2002
Reviewer:
      A reader
I have this situation:
Unix , Oracle 8.1.7.4
Block size is 4k.
Chunk size is 4k
Enable storage in row for LOB.
Locally managed uniform tablespace both for table and LOB
Seperate tablespaces for LOB,table.
Tablespace1 extent size 4Mb
TablespaceLOB extentsize 128M
The table was initially allocated some space.(also initial was
specified for the lob column storage clause).

Table1 has 35 columns out of which one is a blob  column(table1blob).

SQL>Analyze table1 compute statistics;

SQL>select segment_name "Table Name" ,tablespace_name "Table space" ,
sum(bytes)/1024 "Allocated Kb" ,  sum(bytes)/1024/1024 "Allocated Mb",
sum(blocks) "Allocated Blocks" from user_extents where
segment_name='TABLE1' group by segment_name,tablespace_name ;

Table Name    Table space   Allocated Kb   Allocated Mb    Alloc Blocks
------------- ------------  ----------     ------------    ----------
TABLE1        TABLESPACE1   258048         252             64512

SQL>select segment_name from user_lobs where table_name='TABLE1';

SEGMENT_NAME
------------------------------
SYS_LOB0000007813C00015$$

SQL>select segment_name "Table Name" ,tablespace_name "Table space"
,sum(bytes)/1024 "Allocated Kb" ,  sum(bytes)/1024/1024 "Allocated Mb"
,sum(blocks) "Allocated Blocks" from user_extents where segment_name=
'SYS_LOB0000007813C00015$$' group by segment_name,tablespace_name ;

Table Name                 Table space      AllocKb   AllocMb  AllocBlks
-------------------------- ------------------------------ ------------
SYS_LOB0000007813C00015$$  TABLESPACE_LOB   1572864   1536      393216

SQL>select Table_name "Table Name" ,tablespace_name "Tablespace name" ,
avg_space "Avg free space", blocks "Blocks used" ,
empty_blocks "Empty blocks" ,
((blocks*4) - (avg_space*blocks/1024))/1024 "Used space Mb",
(empty_blocks*4/1024)-(((blocks*4)-(avg_space*blocks/1024))/1024)
"Space left Mb",
num_rows "Rows" , avg_row_len "Row length",CHAIN_CNT from
user_tables where table_name='TABLE1';

Table Name  Tablespace    Avgfreespace Blocksused Emptyblocks
----------------------------------------------------------------
TABLE1      TABLESPACE1   1613         5974       58537

Used space Mb   Space left Mb  Rows  Rowlength
 -------------- -----------    ------------  ----------
14.1462727      214.513884     6306  2253

CHAIN_CNT  FREELISTBLOCKS
---------  --------------
     5997  3

SQL>select count(*)  from TABLE1 where dbms_lob.getlength(TABLE1blob)
<2000;

  COUNT(*)
----------
      2479

SQL>select count(*)  from TABLE1 where dbms_lob.getlength(TABLE1blob)>2000
and dbms_lob.getlength(TABLE1blob)<4000;

  COUNT(*)
----------
      3577

SQL>select count(*)  from TABLE1 where dbms_lob.getlength(TABLE1blob)>4000;

  COUNT(*)
----------
       242

SQL>select count(*)  from TABLE1 where dbms_lob.getlength(TABLE1blob)<4000;

  COUNT(*)
----------
      6064

SQL>select count(*)  from TABLE1 where dbms_lob.getlength(TABLE1blob)<=4000;

  COUNT(*)
----------
      6064

SQL>select count(*)  from TABLE1 where dbms_lob.getlength(TABLE1blob)>8000;

  COUNT(*)
----------
        47

SQL>select count(*)  from TABLE1 where dbms_lob.getlength(TABLE1blob)>16000;

  COUNT(*)
----------
         5

SQL>select count(*)  from TABLE1 where dbms_lob.getlength(TABLE1blob)>32000;

  COUNT(*)
----------
         0

SQL>select avg(dbms_lob.getlength(TABLE1BLOB))  from TABLE1 ;

AVG(DBMS_LOB.GETLENGTH(TABLE1BLOB))
--------------------------------------
                            2367.24215

SQL>select max(dbms_lob.getlength(TABLE1BLOB))  from TABLE1 ;

MAX(DBMS_LOB.GETLENGTH(TABLE1BLOB))
--------------------------------------
                                 22780

SQL>select min(dbms_lob.getlength(TABLE1BLOB))  from TABLE1 ;

MIN(DBMS_LOB.GETLENGTH(TABLE1BLOB))
--------------------------------------
                                   743

SQL>exec show_space('TABLE1');
Free Blocks.............................3
Total Blocks............................64512
Total Bytes.............................264241152
Unused Blocks...........................58537
Unused Bytes............................239767552
Last Used Ext FileId....................8
Last Used Ext BlockId...................13329
Last Used Block.........................855

SQL>column segment_name new_val lob
SQL>select segment_name from user_lobs
  2  where table_name = 'TABLE1';

SEGMENT_NAME
------------------------------
SYS_LOB0000007813C00015$$

SQL>exec show_space('&lob',user,'LOB');
Free Blocks.............................2
Total Blocks............................393216
Total Bytes.............................1610612736
Unused Blocks...........................392663
Unused Bytes............................1608347648
Last Used Ext FileId....................18
Last Used Ext BlockId...................229393
Last Used Block.........................553

I have the following questions:

1. The table above has sample loading(of actual data). Actual number of
rows will be in millions(max 20,000,000). Increases by 1,000,000
monthly to the max specified. Most of the blob is less than 3k.
Why is there a chaint_cnt almost equal to the number of rows?
Is it getting displayed because of the ENABLE_IN_ROW ? Is it ok for it
to be this way then? Enable in row was chosen because most of the
data is less than 4k. I do not know how to get the space the
lob locater will hold. Out of the 1,000,000 rows per month abt 10% blobs
will have size above 4k...to any max (inside limit).hence enable in row was
chosen.

2. I need to do a 'select distinct datecolumn' on the above table1 to get last5
days
dates (exclude sat/sun) if soem conditions are satisfied .

  SQL>desc table2 (small table , abt 100 rows , wont increase much..max 300 )
Name                                      Null?
Type----------------------------------------- -------- ------------------
table2ref                                   NOT NULL RAW(16)
namecol                                      VARCHAR2(50)

SQL>desc table1 (huge table abt 20,000,000)
Name                                      Null?    Type
---------------------------------------- -------- ----------table1ref
                      NOT NULL RAW(16)
col1                                         VARCHAR2(58)
col2                                         VARCHAR2(60)
col3                                         VARCHAR2(80)
col5                                          VARCHAR2(80)TABLE1BLOB
                         BLOB
table2ref                              NOT NULL RAW(16)
DATEcol                                NOT NULL NUMBER(10)
     ....and so on

1 Indexe on table2 - > on table2ref column
6 Indexes on table1-> on table2ref column , on col1 column, col2 column, col3
column,
on datecol column,col5 columncol1,col2,col3,col5 are nullable.....(will index
not be
used though present because of these nullable columns?). I cannot make it not
null
(a 3rd party db).

select
      DISTINCT to_char(to_date(t1.DATEcol,'j'),DD/MON/YYYY')
   FROM
       table1 t1,
       table2 t2
   WHERE
       t1.table2ref = t2.table2ref AND
       TO_DATE(t1.DATEcol,'j') <= (sysdate-1) AND
       TO_DATE(t1.DATEcol,'j') >= (sysdate-7) AND
       UPPER(t2.namecol) LIKE  '%XXXX%' AND
         UPPER(t1.col1) = 'P' AND
         UPPER(t1.col5) IN ('T','O') AND
     (t1.col3= 'L' OR UPPER(t1.col2) IN ('R,I,U'));

Elapsed: 00:00:26.76

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=1 Bytes=38)
   1    0   SORT (UNIQUE) (Cost=6 Card=1 Bytes=38)
   2    1     NESTED LOOPS (Cost=2 Card=1 Bytes=38)
   3    2       TABLE ACCESS (FULL) OF 'TABLE2' (Cost=1 Card=1 Byt
          es=22)

   4    2       TABLE ACCESS (BY INDEX ROWID) OF 'TABLE1' (Cost=1 C
          ard=1 Bytes=16)

   5    4         INDEX (RANGE SCAN) OF 'TABLE1_TABLE2REF_IDX'
          (NON-UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
      12091  consistent gets
       3837  physical reads
          0  redo size
        132  bytes sent via SQL*Net to client
        246  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          0  rows processed

Later i pumped in more data ..upto 100,000 rows into table1 (with last 7 days
dates
and others too).
Execution time is more than this and the plan is the same. I can have about
1,000,000/22= abt 500,000
records per date.
How do i reduce response time? any other way to get the dates if this is not a
right approach?

3. When made another table table3 , same as table1 , copied all the columns but
the
blob into it, and executed the same
   query above on the new table, it gets executed fast...less than a second.
why?
   Similar execution plan:.
   Avg rwo length of the new table is 435. thats why?

   Elapsed: 00:00:00.31

   Execution Plan
   ----------------------------------------------------------
      0      SELECT STATEMENT Optimizer=CHOOSE (Cost=38 Card=1 Bytes=35)
      1    0   SORT (UNIQUE) (Cost=38 Card=1 Bytes=35)
      2    1     NESTED LOOPS (Cost=36 Card=1 Bytes=35)
      3    2       TABLE ACCESS (FULL) OF 'TABLE2' (Cost=1 Card=1 Byt
             es=21)

      4    2       TABLE ACCESS (BY INDEX ROWID) OF 'TABLE3' (Cost=35
             Card=1 Bytes=14)

      5    4         INDEX (RANGE SCAN) OF 'TABLE3_TABLE2REF_IDX'
             (NON-UNIQUE) (Cost=6 Card=1)
Statistics
---------------------------------------------------------
          0  recursive calls
          4  db block gets
        116  consistent gets
          0  physical reads
          0  redo size
        220  bytes sent via SQL*Net to client
        314  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          7  sorts (memory)
          0  sorts (disk)
          0  rows processed

          Followup:

1) it is because of the in row storage.  4k block = less then 4k per row.  You
don't show the other columns but if your row exceeds about 3900/4000 bytes --
it'll be chained.  This could be bad if you full scan this lots of times.  If
everything is a single row index read, probably OK (we'd have to do 2 IO's to
put the row together, using an OUT OF LINE lob -- 3/4/5 at leas -- the row, the
lob index, the lob segment)

You would be better in this case with an 8k block probably if this is the
"major" table.  8k is a nice round number for a block size (4k is a bit
"strange", don't see that too much)

2) indexes are precluded because you perform functions on all of the columns:

   WHERE
       t1.table2ref = t2.table2ref AND
       TO_DATE(t1.DATEcol,'j') <= (sysdate-1) AND   <<== no index path
       TO_DATE(t1.DATEcol,'j') >= (sysdate-7) AND   <<== no index path
       UPPER(t2.namecol) LIKE  '%XXXX%' AND         <<== no index path
         UPPER(t1.col1) = 'P' AND                   <<== no index path
         UPPER(t1.col5) IN ('T','O') AND            <<== no index path
     (t1.col3= 'L' OR UPPER(t1.col2) IN ('R,I,U'));

do this instead (assuming your DATEcol is badly stored as a NUMBER type, lose
the to_number if it is in fact a string -- why why why they didn't use a DATE
type!!!!)

   and t1.datecol between to_number( to_char(sysdate-1,'j') )
                      and to_number( to_char(sysdate-7,'j') )
   and upper(t2.namecol) like '%XXX%'
   and t1.col1 in ( 'P', 'p' )
   and t1.col5 in ( 'T', 't', 'O', 'o' )
   and (t1.col3= 'L' OR UPPER(t1.col2) IN ('R,I,U'))

that'll maximize the opportunities for index paths when appropriate.

3) yes, you did 1% of the logical ios and no physical io.  it'll take alot
less..

try to make it however so the index on date can actually be used!

      Rating: 4
row length less
      after making table as 'insert into' having LOBs  September
      25, 2002
Reviewer:  A reader
Hi Tom,
Thanks for the above info.
I created another table - table_new specifying disble in row for the LOB column.

The new table was created using the same storage and tablespace clauses as
table1
except for disable in row for lob in the new table (as opposed to enable in row
for lob in table1).
table1 data is in tablespace1
table1 lob is  in TABLESPACE_LOB
table_new data is in tablespace1
table1_new lob is in TABLESPACE_LOB

Then I did a
insert into table_new select * from table1;

Then i analyzed the new table and found the avg row length lesser . how? No
chained rows because of that.
How can the avg row length decrease in the new table?
I checked the length of teh lobs in both the tables, they are same.

here are the details:

TABLE1 has 30 columns - one blob column.
The remaining 29 columns as follows : 10 varchar2(30) , 12 number (38) , 6
raw(8), 1 char(1)

select segment_name from user_lobs where table_name='TABLE_NEW';

SEGMENT_NAME
------------------------------
SYS_LOB0000008509C00015$$

select segment_name from user_lobs where table_name='TABLE';

SEGMENT_NAME
------------------------------
SYS_LOB0000007813C00015$$

select Table_name "Table Name" ,tablespace_name "Tablespace name" ,avg_space
"Avg free space", blocks "Blocks used" , empty_blocks "Empty blocks" ,
((blocks*4) - (avg_space*blocks/1024)) "Used space Kb",   num_rows "Rows" ,
avg_row_len "Row length" ,chain_cnt from user_tables;

Table Name                     Tablespace name                Avg free space
Blocks used Empty blocks Used space Kb       Rows Row length  CHAIN_CNT
------------------------------ ------------------------------ --------------
----------- ------------ ------------- ---------- ---------- ----------
TABLE1                         TABLESPACE1                        1613
5974        58537    14485.7832       6306       2253       5997

Table Name                     Tablespace name                Avg free space
Blocks used Empty blocks Used space Kb       Rows Row length  CHAIN_CNT
------------------------------ ------------------------------ --------------
----------- ------------ ------------- ---------- ---------- ----------
TABLE_NEW                      TABLESPACE1                        472
205          818    725.507813       6306        113          0

The above queries with distinct etc work much faster because of the lesser row
length.

          Followup:

umm, the average row length of course went down cause you are no longer storing
upto 4000 bytes of text INLINE, you are storing it out of line and the "pointer"
(lob locator) is small -- not upto 4000 bytes.

Hence, the over all row width -- which NEVER includes out of line data like a
lob -- is smaller.

      Rating:
      4
  September 25, 2002
Reviewer:
      A reader
continuing from above,
if the row length decreased because of only the lob locators getting inserted in
the new table (table_new)..then how do i make a ne wtable with disable in row
instead of enable in row
using insert into table_new select * from table1?
thanks.

          Followup:

the row length decreased for that reason, yes.

I do not understand the second part of the question??  You would create the
table with DISABLE on the lob and then just insert?

      Rating: 4
can this be a
      way to inmprove performance?  September 25, 2002

Reviewer:  A reader
can this be a way to increase performance?
I mean, in my queries (as above using distinct etc etc)
i dont use the LOB column at all. I just need to update /retreive certain other
fields .
So if i create a view  on the base table (with lob) as
'select everything except blob column' '  and use that view in my queries, it
will be faster?
Is this a good approach for retreiving /updating/inserting
non blob columns?

          Followup:

You moved upto 4k of data OUT OF THE table into another segment.

Suppose you have a table t ( x int, y date, z varchar2(20), a clob )

and a is stored inline.  You could find your average rowsize to be 4k now.  So,
if you have 100,000 rows, you have 390meg of data.  If you full scan T, you'll
read 390meg of data

Now, you disable storage in row.  You might find your average rowsize to be
40bytes.  Now, if you full scan, you'll hit 4meg of data.

400meg, 4meg -- one will scan about 100times faster.

You need not use the view to "hide" the blob -- the blob when stored out of line
is very small.

      Rating:
      5
  September 25, 2002
Reviewer:
      A reader
Thanks Tom,
One more point that I didnt understand:

1)If i have a table t ( x int, y date, z varchar2(20), a clob ).
Two cases:
A)I store lob out of line.(disbale in row)
B)I store lob inline.(enable in row)

Now i have indexes on x and y...so when i write a query to say ...

select  distinct x,y,z from t where x= somethign and y=something.

I write it such that i dont do a full table scan of t but make it use indexes on
x and y.

So a) Vs b) should not make any difference right? I am not selecting LOB column
at all and am not doing a full table scan either.

But this is not the case with me. If i store lob out of line
performance is much much faster(for the reasons you have already explained ).
But i didnt understand how it is making a difference if i am not doing a full t
scan in both cases.
The queries run very very fast now!! with disable in row.

2)When i do a count (*) on a table, does all the rows (withe all columns) get
read before counting?

3)when i do a 'select distinct x,y,z from t where..'  or 'select x,y,z from t
where...' does all columns get read(x,y,z and a) or only x,y,z ? (assuming no
full table scan).

thanks.

          Followup:

Wrong -- a) vs b) could make a huge difference.

Suppose for x = something and y = something there are 100 rows.

Suppose you have a 4k blocksize.

Suppose you store the data inline and it is about 3k of data/row.

a) will do about 100 logical IO's on the table and 1 (one) physical IO
b) will do about 100 logical IO's on the table and upto 100 physical IOs

that is, a could store the answer on a block, b cannot.

so, that could be your case as well. you are processing a WHOLE LOT less data on
far fewer blocks when it is stored out of line.

2) well, we are counting as we go along -- they happen "at the same time"?
reading ahead to q3 -- do you mean "do all of the columns get read"?  No, just
the head rowpiece gets read.

3) just xyz, unless A is in front of xyz that is (then we have to read over A of
course to find xyz

      Rating: 4
Inserting clob
      performance issue: enable vs disable strorage in row  June
      05, 2003
Reviewer:  A reader  from London, UK

I did a test with real sized data and found, as you said that, Storing clob data
out of line  is as efficient as in line, however when changing some of the
storage parameters like (NOCAHCE etc) I see this: (run on 8.1.7.3)

set timing on
drop table t;
create table t (
  A  VARCHAR2 (30)  NOT NULL,
  B  VARCHAR2 (30)  NOT NULL,
  C  clob default empty_clob()
)
NOCACHE
LOGGING
lob(c) store as t_name_lo(enable storage in row);

begin
for i in 1..1000 loop
insert into t (a, b, c)
select 'a','b',rpad('*',32000,'*') from dual;
end loop;
end;
/
rollback
/
Elapsed:  00:00:42.05

--FIine an these are the other results:
noCACHE,noLOGGING:
Elapsed: 00:00:42.07
--
CACHE,LOGGING
Elapsed: 00:00:42.01
--
CACHE,NOLOGGING
Elapsed: 00:00:43.01

All results are about the same, that's OK.

But when I changed the CLOB for I LONG and rerun the insert (from sqlplus):

--col c is a LONG
Elapsed: 00:00:09.08

Over 4 times Faster????

Please explain?  I realise LONGs are deprecated so I want to use CLOBS, but if I
have , as I do , a scenario where I just want to write once no read of the
long/LOB col.  Then is LONG better?

Thanks in advance

          Followup:

the longs be in the buffer cache.

nocache = you waited for a direct write to disk.

you asked to bypass the buffer cache.

      Rating: 3
LONGs faster
      for writes than LOBS  June 05, 2003
Reviewer:
      A reader
Hi Tom,

You said that the LONGs are in the buffer cache
and using nocache means you have to wait for a direct write to disk. which is
fine

However I've tried the insert with all combinations:

NOCACHE,NOLOGGING: Elapsed: 00:00:42.07
CACHE,LOGGING:     Elapsed: 00:00:42.01
CACHE,NOLOGGING:   Elapsed: 00:00:43.01

But when I changed the CLOB for a LONG and rerun the insert
LONG:              Elapsed: 00:00:09.08

LONG is still faster even when the CLOB is ion the buffer cache.

Does this mean that despite the fact that LONGS are deprecated we should still
use them for write once (no update) type operations?

Thanks again

          Followup:

well, your test is a tad "flawed".  if you check it out -- you'll discover you
inserted 4000 bytes -- rpad returned 4000 bytes when used in SQL.  But in any
event, I cannot reproduce.  This was run in noarchivelog mode (so nocache is
nologging and cache is logging).  I cannot reproduce findings similar to yours
at all.

First just with "wall clock" timings:

ops$tkyte@ORA920> drop table t_nocache;

Table dropped.

Elapsed: 00:00:00.34
ops$tkyte@ORA920> create table t_nocache (a number, b clob)
  2  lob (b) store as (enable storage in row nocache )
  3  /

Table created.

Elapsed: 00:00:00.15
ops$tkyte@ORA920>
ops$tkyte@ORA920> drop table t_cache;

Table dropped.

Elapsed: 00:00:00.26
ops$tkyte@ORA920> create table t_cache (a number, b clob)
  2  lob (b) store as (enable storage in row cache )
  3  /

Table created.

Elapsed: 00:00:00.10
ops$tkyte@ORA920>
ops$tkyte@ORA920> drop table t_long;

Table dropped.

Elapsed: 00:00:01.99
ops$tkyte@ORA920> create table t_long(a number, b long);

Table created.

Elapsed: 00:00:00.09
ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> set timing on
ops$tkyte@ORA920> declare
  2          l_data long := rpad('*',32000,'*');
  3  begin
  4          for i in 1 .. 1000
  5          loop
  6                  insert into t_nocache ( a, b ) values ( i, l_data );
  7          end loop;
  8          commit;
  9  end;
 10  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:14.27
ops$tkyte@ORA920> declare
  2          l_data long := rpad('*',32000,'*');
  3  begin
  4          for i in 1 .. 1000
  5          loop
  6                  insert into t_cache ( a, b ) values ( i, l_data );
  7          end loop;
  8          commit;
  9  end;
 10  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:02.57
ops$tkyte@ORA920> declare
  2          l_data long := rpad('*',32000,'*');
  3  begin
  4          for i in 1 .. 1000
  5          loop
  6                  insert into t_long ( a, b ) values ( i, l_data );
  7          end loop;
  8          commit;
  9  end;
 10  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:14.70
ops$tkyte@ORA920>

14, 3, 14 seconds....

Now using runstats and comparing the cached lob with the long

ps$tkyte@ORA920> exec runstats_pkg.rs_start;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.11
ops$tkyte@ORA920> declare
  2          l_data long := rpad('*',32000,'*');
  3  begin
  4          for i in 1 .. 1000
  5          loop
  6                  insert into t_cache ( a, b ) values ( i, l_data );
  7          end loop;
  8          commit;
  9  end;
 10  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:02.53
ops$tkyte@ORA920> exec runstats_pkg.rs_middle;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.13
ops$tkyte@ORA920> declare
  2          l_data long := rpad('*',32000,'*');
  3  begin
  4          for i in 1 .. 1000
  5          loop
  6                  insert into t_long ( a, b ) values ( i, l_data );
  7          end loop;
  8          commit;
  9  end;
 10  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:15.40
ops$tkyte@ORA920> exec runstats_pkg.rs_stop(1000);
Run1 ran in 259 hsecs
Run2 ran in 1541 hsecs
run 1 ran in 16.81% of the time

Name                                  Run1        Run2        Diff
STAT...free buffer requested         4,158       5,224       1,066
STAT...Elapsed Time                    271       1,545       1,274
STAT...consistent gets               2,460       6,617       4,157
LATCH.cache buffer handles               4       6,930       6,926
STAT...dirty buffers inspected           3      10,297      10,294
STAT...free buffer inspected             3      10,506      10,503
LATCH.redo allocation               10,387      21,551      11,164
STAT...redo entries                 10,324      21,506      11,182
STAT...db block changes             16,245      28,364      12,119
STAT...db block gets                22,816      37,066      14,250
LATCH.checkpoint queue latch            16      17,988      17,972
STAT...session logical reads        25,276      43,683      18,407
LATCH.cache buffers lru chain        4,159      22,959      18,800
LATCH.cache buffers chains          77,235     162,816      85,581
STAT...redo size                33,552,760  34,459,324     906,564

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
112,677     253,011     140,334     44.53%

PL/SQL procedure successfully completed.

it was faster, it was infinitely more efficient latch wise

June
      06, 2003 Gladiola -- Thanks for the
      question regarding "pl/sql table in report", version report6

      You Asked
Hi Tom!
I will appeciate very much if you give me a idea  to build a report
base on  pl/sql table.I have seen all the documentation but I have found
nothing.
Let say I have pl/sql table creating in forms and I want that the records of the
table will be showing in reports.I dont want to create
a temporaly table and base the query on it because I will deal with multiuser
problem.Hope this helps.
Let say you pass a parameter to report from the form(could be record group).So I
want a pl/sql table to be pass to the report and base the query in that pl/sql
table.

thanks in advance

      and we said...
create a record group, put the values of the plsql table into the record group
and then pass the record group from forms to reports:

Article-ID:         <Note:110495.1>
Circulation:        REVIEWED (EXTERNAL)
Folder:             client.Dev2000.Forms.V6i
Topic:              6i - Implementation, Deployment, Runtime issues
Title:              PASSING A RECORD GROUP TO REPORTS USING RUN_REPORT_OBJECT
Document-Type:      BULLETIN
Impact:             MEDIUM
Skill-Level:        CASUAL
Updated-Date:       05-SEP-2000 19:32:18
References:
Attachments:        NONE
Content-Type:       TEXT/PLAIN
Keywords:           GROUP; RECORD; RUN_REPORT_OBJECT;
Products:           45/SF40;
Platforms:          GENERIC;

*************************************************************
This article is being delivered in Draft form and may contain
errors.  Please use the MetaLink "Feedback" button to advise
Oracle of any issues related to this article.
*************************************************************

PURPOSE
-------
To demonstrate how to pass a Record Group from Forms to Reports using
Run_Report_Object.

SCOPE & APPLICATION
-------------------

Forms Developers

PASSING A RECORD GROUP TO REPORTS USING RUN_REPORT_OBJECT
-----------------------------

Using RUN_PRODUCT it was relatively easy to pass a Record Group from Forms to
Reports using a Parameter List.  You could do this with the following code:

DECLARE
  v_rep  varchar2(50);
  param_list_id  paramlist;
  groupname varchar2(30) :='RG1';
  QNAME   VARCHAR2(30)  :='Q_1';
  QRY VARCHAR2(2000)  :='select empno, ename, sal, deptno from emp where deptno
  =20';
  RGID           RECORDGROUP;
  N              NUMBER;

begin
  RGID:=FIND_GROUP(GROUPNAME);
  IF NOT ID_NULL(RGID) THEN
    DELETE_GROUP(RGID);--IF IT EXISTS ALREADY, DELETE IT
  END IF;
 RGID:=CREATE_GROUP_FROM_QUERY(GROUPNAME,QRY);--THIS CREATES THE RG
  --MAKE SURE RG IS EMPTY of rows
  DELETE_GROUP_ROW(RGID,ALL_ROWS);
--  RETRIEVE THE DATA INTO THE RG
 N:=POPULATE_GROUP_WITH_QUERY(RGID,QRY);
  param_list_id:=get_parameter_list('param_data');
  /*test if the parameter list already exists.  If it does, delete
   it.  If it doesn't, create it*/
  if not id_null(param_list_id) then
    destroy_parameter_list('param_data');
  end if;
  param_list_id:=create_parameter_list('param_data');
  ADD_PARAMETER(param_list_id,QNAME,DATA_PARAMETER,GROUPNAME); --SEND NAME OF
-- QUERY (THERE IS A CORRESPONDING NAME IN THE REPORT) AND THE NAME OF THE RG
--CREATED FROM THE QUERY TO REPORTS.
run_product(reports,'CALLED_REP_1',synchronous,runtime,filesystem,param_list_id);

--CALLED_REP_1 is a report based on the same select statement as the Record
 --Group.  It's query is entitled 'Q_1'
end;

You can almost use this exact code to pass the Record Group to the same
Report using RUN_REPORT_OBJECT.In fact the only change you would need to make
is to replace the RUN_PRODUCT command with the following command:

 v_rep:=run_report_object('DRILL',param_list_id);

'DRILL' is a Report Object in the form based upon the Report entitled:

CALLED_REP_1

The limitation to this method is that it ONLY works when run_report_object is
NOT using the Reports Server.  If not using the Reports Server, it will call the
same background engine as run_product does, RWRBE60.EXE which allows this to
function similar to RUN_PRODUCT.

If you are using the Reports Server this method will not work.  You would have
to create a temporary table in the form to store the information and then
read it in a Report trigger.

Review &
      Followup

      Rating: 5
PL/SQL table to
      Forms  June 06, 2003
Reviewer:
      A reader
Hi Tom,
We are on Oracle 8.1.5 and wish to pass PL/SQL table from database to Forms 6i.
It says user defined types not supported in Forms 6i. Do we have a workaround
for this, please?
Thanks

          Followup:

sorry, I haven't used forms since about 1995 when this HTML stuff caught on...

otn.oracle.com -> discussion forums has a group for developer related topics
about forms.  I'd try over there

      Rating: 5
For "A
      Reader"  June 06, 2003
Reviewer:  Mac

In a similar situation, I have previously used this technique:

1. Create a database package, and store the PL/SQL table in a package variable.
2. In the form, reference the database package variable.

Hope this help!

June 06, 2003 Kasthuri -- Thanks for the question regarding
      "Multiple rows from table into one row in refcursor", version 8.1.5.1

      You Asked
Hi Tom,

 I have a table lst_info (id char(9), amt number(11,2),
 dt date, type char(3), plan_id number(6))
 some values:
   1, 1000,10-15-00,'CON', 111111
   1, 200,10-16-00,'LRP', 111111
   1, 3000,10-15-00,'CON', 111231
   1, 400,10-16-00,'LRP', 111231
   2, 5000,10-15-00,'CON', 111111
   2, 600,10-16-00,'LRP', 111111
   3, 7000,10-15-00,'CON', 111112
   3, 800,10-16-00,'LRP', 111112
   1, 800,10-16-00,'LRP', 111113

 i have a procedure that expects the id as input and has to send
a cursor . the output cursor should contain
 plan_id, amt (for type CON), dt (for type CON), amt(for type
LRP), dt ( for type LRP)
 for the given id. ( as 1 row based on the plan_id - from the
multiple rows in the table)
basically the OUTPUT CURSOR should contain ( for id 1 as input)
   111111, 1000,10-15-00,200,10-16-00
   111231, 3000,10-15-00,400,10-16-00
   111113, NULL, NULL, 800, 10-16-00

i can achieve this using two type tables, having separate query
and handling the no-data-found error, and populate the cursor.
Is there a more efficient way to do it?

I have a procedure which does this :
DROP TYPE lst_amt_dt_tab
/
DROP TYPE lst_amt_dt_rec
/
CREATE OR REPLACE TYPE lst_amt_dt_rec
AS OBJECT
(
        plan_id number(6),
    lst_contrib_dt date,
    lst_contrib_amt number(11,2),
    lst_loan_repmt_dt date,
    lst_loan_repmt_amt number(11,2)
)
/

CREATE TYPE lst_amt_dt_tab
AS TABLE OF lst_amt_dt_rec
/

CREATE OR REPLACE PROCEDURE  sel_lst_contrib_lr (
     p_id_in in  t_oacl_lst_contrib_lr.id%type,
    p_cb_lr_cursor_out out types.cursorType)
IS

l_cb_lr_cursor types.cursorType;
l_lst_amt_dt_tab lst_amt_dt_tab := lst_amt_dt_tab();
l_plan_id number(6);
l_contrib_amt number(11,2);
l_contrib_dt date;
l_loan_repmt_amt number(11,2);
l_loan_repmt_dt date;
l_cnt  number := 0;

BEGIN

         FOR x in (select distinct plan_id from
t_oacl_lst_contrib_lr
             where id = p_id_in)
         LOOP
         l_lst_amt_dt_tab.extend;
         l_cnt := l_cnt +1;
         BEGIN
         SELECT tran_dt, tran_amt
                INTO
        l_contrib_dt,
        l_contrib_amt
         FROM t_oacl_lst_contrib_lr
        WHERE plan_id = x.plan_id AND
        id = p_id_in AND tran_typ='LRP';

      EXCEPTION
     WHEN NO_DATA_FOUND THEN

     l_contrib_amt := NULL;
     l_contrib_dt := NULL;

      END;

         BEGIN
         SELECT tran_dt, tran_amt INTO
        l_loan_repmt_dt, l_loan_repmt_amt
        FROM t_oacl_lst_contrib_lr
        WHERE plan_id = x.plan_id AND
        id = p_id_in AND tran_typ = 'CON';

      EXCEPTION
     WHEN NO_DATA_FOUND THEN

     l_loan_repmt_amt := NULL;
     l_loan_repmt_dt := NULL;

      END;

/*
         dbms_output.put_line(x.plan_id || 'cd' || l_contrib_dt
||
                        'cm' || l_contrib_amt || 'ld' ||
                        l_loan_repmt_dt || 'lm' ||
l_loan_repmt_amt);
*/

         l_lst_amt_dt_tab(l_cnt) := lst_amt_dt_rec(x.plan_id,
            l_contrib_dt, l_contrib_amt,
            l_loan_repmt_dt, l_loan_repmt_amt);
         END LOOP;

         OPEN l_cb_lr_cursor FOR SELECT  *
      from table (cast (l_lst_amt_dt_tab as lst_amt_dt_tab));

         p_cb_lr_cursor_out := l_cb_lr_cursor;

END sel_lst_contrib_lr;
/

- It works fine. Is there a more efficient way instead of
looping and doing a select into individually?

Thanks & Regards
Kasthuri

      and we said...
I think you can do this in a query.  I put your procedure in and
ran it and my query -- they do the same thing.  You do not need
the table trick...

ops$tkyte@ORA8I.WORLD> variable x refcursor
ops$tkyte@ORA8I.WORLD> set autoprint on
ops$tkyte@ORA8I.WORLD> exec sel_lst_contrib_lr( 1, :x );

PL/SQL procedure successfully completed.

   PLAN_ID LST_CONT LST_CONTRIB_AMT LST_LOAN LST_LOAN_REPMT_AMT
---------- -------- --------------- -------- ------------------
    111111 10-16-00             200 10-15-00               1000
    111113 10-16-00             800
    111231 10-16-00             400 10-15-00               3000

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select plan_id,
  2  max( decode( tran_typ, 'LRP', tran_dt, null ) ) lrp_dt,
  3  max( decode( tran_typ, 'LRP', tran_amt, null ) ) lrp_amt,
  4  max( decode( tran_typ, 'CON', tran_dt, null ) ) con_dt,
  5  max( decode( tran_typ, 'CON', tran_amt, null ) ) con_amt
  6  from t_oacl_lst_contrib_lr
  7  where id = '1'
  8  group by plan_id
  9  /

   PLAN_ID LRP_DT      LRP_AMT CON_DT      CON_AMT
---------- -------- ---------- -------- ----------
    111111 10-16-00        200 10-15-00       1000
    111113 10-16-00        800
    111231 10-16-00        400 10-15-00       3000

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> exec sel_lst_contrib_lr( 2, :x );

PL/SQL procedure successfully completed.

   PLAN_ID LST_CONT LST_CONTRIB_AMT LST_LOAN LST_LOAN_REPMT_AMT
---------- -------- --------------- -------- ------------------
    111111 10-16-00             600 10-15-00               5000

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select plan_id,
  2  max( decode( tran_typ, 'LRP', tran_dt, null ) ) lrp_dt,
  3  max( decode( tran_typ, 'LRP', tran_amt, null ) ) lrp_amt,
  4  max( decode( tran_typ, 'CON', tran_dt, null ) ) con_dt,
  5  max( decode( tran_typ, 'CON', tran_amt, null ) ) con_amt
  6  from t_oacl_lst_contrib_lr
  7  where id = '2'
  8  group by plan_id
  9  /

   PLAN_ID LRP_DT      LRP_AMT CON_DT      CON_AMT
---------- -------- ---------- -------- ----------
    111111 10-16-00        600 10-15-00       5000

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> exec sel_lst_contrib_lr( 3, :x );

PL/SQL procedure successfully completed.

   PLAN_ID LST_CONT LST_CONTRIB_AMT LST_LOAN LST_LOAN_REPMT_AMT
---------- -------- --------------- -------- ------------------
    111112 10-16-00             800 10-15-00               7000

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select plan_id,
  2  max( decode( tran_typ, 'LRP', tran_dt, null ) ) lrp_dt,
  3  max( decode( tran_typ, 'LRP', tran_amt, null ) ) lrp_amt,
  4  max( decode( tran_typ, 'CON', tran_dt, null ) ) con_dt,
  5  max( decode( tran_typ, 'CON', tran_amt, null ) ) con_amt
  6  from t_oacl_lst_contrib_lr
  7  where id = '3'
  8  group by plan_id
  9  /

   PLAN_ID LRP_DT      LRP_AMT CON_DT      CON_AMT
---------- -------- ---------- -------- ----------
    111112 10-16-00        800 10-15-00       7000

Review &
      Followup

      Rating: 4
Quick &
      Easy  June 05, 2003
Reviewer:  A reader
       from OH
Quick and easy way

      Rating: 3
Follow up
      Question - CROSS TAB with Multiple Joins  June 06, 2003

Reviewer:  JK  from MA USA
Hi Tom

 In my case, i need something like this:
 Leads,Acct, Prsn, addr, Form, AcctAddr, AcctPrsn
 are the tables.

 Leads may have many forms for 1 person. we need all this in a single resultset.
 For eg.
 Open RC for
 select
   lead.ID,
   lead.srcI,
   form.ID,
   addr.addr_zipcd,
   prsn.prsn_lname,
   prsn.prsn_fname,
   acct.acct_acctnum,
   decode(form.TYPECD,29,'1','0'),
   decode(form.TYPECD,30,'1','0'),
   decode(form.TYPECD,31,'1','0')
 FROM
   lead,addr,prsn,form,prsnaddr
 WHERE
   lead.FM IS NULL     AND
   lead.CS IS NOT NULL AND
   lead.PS = '0'       AND
   lead.prsnID = prsn.prsnID AND
   prsn.prsnId = prsnaddr.prsnID AND
   addr.addr_seqid = prsnaddr.addr_seqid AND
   lead.prsn_seqid = acct.prsn_seqid AND
   form.leadId = lead.ID;

 This returns all 3 rows if there are three forms for the Lead.

 I could crosstab with Forms and Lead as
  Open RC for select
   lead.ID,
   decode(form.TYPECD,29,'1','0'),
   decode(form.TYPECD,30,'1','0'),
   decode(form.TYPECD,31,'1','0')
 FROM
   lead,form
 WHERE
   lead.FM IS NULL     AND
   lead.CS IS NOT NULL AND
   lead.PS = '0'       AND
   form.leadId = lead.ID;

 But i need the rest of the data too & also as a single resultset. Further, this
is only a partial set - There are many more tables to be joined and this query
is supposed to return around 100 columns as a single resultset.

 Is my only option to use Temporary Tables? Further more, this account which
will contain the package does NOT have a 'create any table' privilege. I want to
avoid granting this priv.

Please suggest
Thanks & Regards
JK

          Followup:

you would NEVER create a table in a package anyway.  You would create a global
temporary table before you even install the package.  But anyway -- not getting
your problem -- a transpose is basically a decode/max trick.  all you need is
the single query that retrieves all of your data from all of your tables and
then "pivots" just like I did above on a group by key, using max(decode()) on
the columns to place in record.

      Rating:
      3
Thanks  June 06, 2003
Reviewer:  JK
       from MA
Thanks - I got it

I used max(decode()) on the 'Forms Table.Columns' and 'group by' on the rest of
the join result columns.

I appreciate your immediate response
Regards
JK

June 06, 2003 Deepak -- Thanks for the question regarding
      "Inserting into Varrays", version 8.1.7

      You Asked
I am trying to select a coulmn value from a table into a Varray of varchars.
How would I do that?

Another question is How do i accept the values of a IN OUT weak Ref Cursor
coming back from a procedure and use the coming back values to do something

      and we said...
look up BULK COLLECT in the plsql guide -- or search for it on this site for
some example.

You just "use it" and fetch from it like you would any other cursor.  Even
though it is "weak", it really better be "well known".  There are no facilities
in PLSQL to "describe" and then dynamically fetch with a ref cursor. So, while
the cursor might be weakly defined -- it had better ALWAYS return the same
number and type of columns.

If the number/type of columns changes -- you must use DBMS_SQL in PLSQL which
does permit a describe and dynamic access to the result set.
June 06, 2003 Alan -- Thanks for the question regarding
      "DBMS_APPLICATION_INFO and V$SQL/AREA", version 8.1.7.4.1

      You Asked
Hi Tom,

Does setting the module and action of DBMS_APPLICATION_INFO via the SET_MODULE
procedure cause different versions of the same sql statement to be stored in
V$SQL?

I've set up a test with two sql*plus sessions and I am executing a simple
statement like:

var x number;

exec :x := 3907;  -- arbitrary seq #

select * from ag_client where client_id = :x;

Now when I issue different versions of the following statement from the two
sql*plus sessions:

-- session 1
exec dbms_application_info.set_module('sql_plus','test1');

-- session 2
exec dbms_application_info.set_module('testing','test2');

and then execute the above sql statement on each, I get two versions in v$sql.

Is this expected???  I can't find any documentation on your site or in the Fine
Manual.

BTW, we have cursor_sharing set to force, because we have some old code that
wasn't using bind vars.  We are trying to identify the worst offenders and
correct those statements.  We thought by setting module and action we could more
easily find those statements.  However, if setting those, causes even more
versions to appear in the library cache, then we probably shouldn't be setting
it.

I would appreciate any insight that you could give on this behavior.

Thanks,

Alan Davey

      and we said...
as ACTION and MODULE are part of the v$sql view, yes, they are.

ACTION and MODULE should be static strings so in general, this isn't going to
matter in the real world.  CLIENT_INFO -- that changes, is flexible -- but
action/module should be consistently represented.

setting the MODULE and ACTION to the same string like:

module=MY_PROGRAM
action=SUBROUTINE1

isn't going to affect anything since every time the statement is parsed by
someone, in the same place, it'll have the SAME action and module tags by
definition!

Review &
      Followup

      Rating:
      5
Thanks  June 06, 2003
Reviewer:  Alan
      Davey
Tom,

Thanks for confirming my suspicions!

At least now when I see multiple versions, I can more easily identify those
candidates that need to be wrapped into a single, common module.

Alan

June 06, 2003 Vipin -- Thanks for the question regarding "will
      FGAC reduce performance", version 8.1.7

      You Asked
Hi Tom,

While using FGAC, isn't it a performance overhead that the pridates we form will
always be excuted.

What my doubts is if I have a complex predicate which screens away some of the
data from a table, based on some conditions, every time I issue a Select * from
this table the predicate is going to execute which might be time consuming. If I
handle these conditions programatically instead of FGAC perhaps the query would
use a different execution path and would be perfromance wise good.

So what should be my criteria for going ahead with FGAC, without compromising
performance hits.

Thanks in advance

      and we said...
Hah, your kidding right?

You think that sucking all of the data out of the database (lots more then you
need) and doing it in code will be faster?

Hah, actually, sadness, dismay.

The database was born to process data, it is what it does best.  You will not be
faster at doing it outside the database NOR will you be more open, changeable,
flexible.

In fact, you will have a super closed system (no, you cannot ad hoc the data
using your fancy report tool you silly end user).

You will have a system that is super resistant to change (what, you mean I have
to figure out how to implement and reimplement this security over and over and
over again everywhere I access this stuff)

You will have a system that is very hard to be flexible with (what, you mean the
security changed, no way, i have 100 places to look and fix now)

You would have to present a case whereby doing it outside the database is must
faster -- so much faster that all of the other considerations become moot (and
give me a chance to implement the same logic inside the database by modifying
your database implementation to be more efficient).  I feel very confident that
-- I would win.

Review &
      Followup

      Rating:
      3
  June 06, 2003
Reviewer:  Vipin
       from New york
Ok,

Let's start then. The requirement would be something like what follows:-

A user can play different kind of administrator roles say:-
ADMIN1
ADMIN2
ADMIN3 etc.

All of these roles will be stored in role_master table.

Each of these roles can perform different functions which would be stored in
role_func_mapping:-
ADMIN1 FUNC1
ADMIN1 FUNC2
ADMIN2 FUNC3
ADMIN3 FUNC2 etc. over whatever population of employees he can view.

User to role mapping is done through yet another table say user_role_mapping
table where in there are additional complex restriction involved:-
Structure of this table would be something like this

USER_ID
ROLE_ID
DEPT_ID
GROUP_ID
SECTION_ID
CORP_ID
LOCATION_ID

Above table says that a user_id say "TKYTE" is of role "SUPER_ADMIN" for dept_id
ALL(which means for all departments, for group_id say "R&D" , for section_id
"AVIATION" , for corp_id ALL (means for all corp_ids) for location_ids USA.

Assuming there are masters for all the above details like DEPT, GROUP, SECTION,
LOCATION, CORP etc.

What my doubt is , can you form a policy for each user logged in and apply this
policy over EMPLOYEE table to provide him with only those employee details which
he is supposed to view without any performance impact. Now the functionality of
modules would not be limited to this retreival(means there might be a lot
operation that needs to be performed over the population of employees
retreived.so i don't want this retrieval to be the bottleneck for the entire
task.)

"I feel very confident that
-- I would win."

And BTW We want you to win always, because if you start losing developers like
myself can't take the blow :)

Thanks Tom

          Followup:

I want YOU to set up the benchmark -- both ends of it -- the "application
implementation" AND the "database one"

...
can you form a policy for each user logged in and apply this
policy over EMPLOYEE table to provide him with only those employee details which

he is supposed to view without any performance impact.
......

If you can do it in the application, of course we can do it in the database.

:)

      Rating:
      3
  June 06, 2003
Reviewer:  Vipin
       from New york
Hi tom,

What my doubt is suppose we have a predicate for this security feature.

Suppose a,b,c,d,e etc are tables which are needed to implement my functionality
after I get the required population over which the user can act.

Now when i give my query as

select ......
from   employee_details, /* table after the policy has been applied over it*/
       a ...,
       b ...,
       c ....,
       d ...,
       e ...,
       f ....
Where  ..........

First the policy would be executed and I would get a set of rows on which I can
action for employee details and then the rest of conditions will be executed.
Would this have better or reduced performance over writing the query in
following way :-

select .....
from   employee_details /* actual table without screening*/
       USER,
       ROLES,
       GROUPS,
       SECTIONS,
       ........
       A,
       B,
       .....
Where  .......

Wouldn't I be able to decide alternatives based on costs and execution paths,
indexes etc in the second query.

And one more doubt is would the first query execute as something like this:-

select ......
from   ( Select ...
         from employee_details
         Where PREDICATE FORMED BY THE POLICY),
       a ...,
       b ...,
       c ....,
       d ...,
       e ...,
       f ....
Where  ..........

Cheers

          Followup:

the CBO takes care of it -- the query would really be:

select ...
 from ( select .... from emp_dtls, user,roles,....),
       a,b,c,d,.....

and as long as they were semantically the same -- that would be the same as:

select ...
   from emp_dtls, user, role, ...., a, b,c, d, .....

they are the *same* with view merging.  Unless of course you use some technique
in the implied inline view that precluded it from merging (like aggregation,
analytics, set operations like UNION, rownum, etc) but at that point we would be
comparing apples and toaster ovens since the two queries would in fact *be
different*

      Rating:
      4
  June 06, 2003
Reviewer:  Vipin
       from New york
Thanks Tom

June 06, 2003 Jack -- Thanks for the question regarding
      "existence of record before delete", version 8.0.5.1

      You Asked
Tom,

I have the following scenario:

Table A is parent with 1 or more child records in table B.

I have a request to delete a child record in B:

    if Table A has only 1 record in B, then delete the record in B and the
corresponding parent in A

    if Table A has more than 1 child record in B, the just delete the requested
record in B.

I know you are not a fan of select count(*) for checking number of records -
what is a better way to check for the existence of 1 or more records in Table B
for a parent record in Table A.

please advise.

thanks

      and we said...
And what happens when you have:

parent_table, pk = 1

child_table, pk = 55, fk = 1
child_table, pk = 56, fk = 1

and at about the same time, two sessions issue:

delete from child_table where pk = 55;    delete from child_table where pk = 56;

no count(*) will help you there, they'll both see only committed changes, they
won't block.

And, one wonders, what about a single row insert into parent and not any inserts
into child?  Only rows that were parents should be magically "disappeared"?

Suggestion:

o add a column to parent called "child_cnt" (you can hide this with a view if
you like, rename parent to parent_table, add the column, create a view called
"parent" that selects everything but this cound and default it to ZERO)

o use a trigger on child to maintain count

o in 8.0 -- IGNORE rows with child_cnt = 0, you could even put that on the view
that hides the column.  They will appear to be deleted

o when you get onto a supported release of Oracle (8i and above), you can use a
trigger to physically delete the row.

something like this:

ops$tkyte@ORA806> create table p ( id int primary key, child_cnt number );

Table created.

ops$tkyte@ORA806> create table c ( id int primary key, fk references p );

Table created.

ops$tkyte@ORA806> create or replace view v
  2  as
  3  select ID from p where child_cnt <> 0;

View created.

ops$tkyte@ORA806>
ops$tkyte@ORA806>
ops$tkyte@ORA806> create trigger c_aiud_fer
  2  after insert or update of fk or delete on c
  3  for each row
  4  declare
  5          function not_equal( a in int, b in int ) return boolean
  6          is
  7          begin
  8                  return a <> b or (a is null and b is not null) or (a is not
null and b is null);
  9          end;
 10  begin
 11          if ( inserting or (updating and not_equal(:new.fk,:old.fk) ) )
 12          then
 13             update p set child_cnt = child_cnt+1 where id = :new.fk;
 14          end if;
 15          if ( deleting or (updating and not_equal(:new.fk,:old.fk) ) )
 16          then
 17              update p set child_cnt = child_cnt-1 where id = :old.fk;
 18          end if;
 19  end;
 20  /

Trigger created.

ops$tkyte@ORA806>
ops$tkyte@ORA806> insert into p values ( 1, 0 );

1 row created.

ops$tkyte@ORA806> insert into p values ( 2, 0 );

1 row created.

ops$tkyte@ORA806> insert into c values ( 100, 1 );

1 row created.

ops$tkyte@ORA806> insert into c values ( 101, 1 );

1 row created.

ops$tkyte@ORA806> select * from p;

        ID  CHILD_CNT
---------- ----------
         1          2
         2          0

ops$tkyte@ORA806> select * from v;

        ID
----------
         1

ops$tkyte@ORA806> update c set fk = 2 where id = 100;

1 row updated.

ops$tkyte@ORA806> select * from p;

        ID  CHILD_CNT
---------- ----------
         1          1
         2          1

ops$tkyte@ORA806> select * from v;

        ID
----------
         1
         2

ops$tkyte@ORA806> update c set fk = 2 where id = 101;

1 row updated.

ops$tkyte@ORA806> select * from p;

        ID  CHILD_CNT
---------- ----------
         1          0
         2          2

ops$tkyte@ORA806> select * from v;

        ID
----------
         2

ops$tkyte@ORA806> delete from c;

2 rows deleted.

ops$tkyte@ORA806> select * from p;

        ID  CHILD_CNT
---------- ----------
         1          0
         2          0

ops$tkyte@ORA806> select * from v;

no rows selected

and in 8i, you can

ops$tkyte@ORA806>
ops$tkyte@ORA806>
ops$tkyte@ORA806> create trigger c_aiud_fer
  2  after insert or update of fk or delete on c
  3  for each row
  4  declare
  5          l_cnt number;
  6          function not_equal( a in int, b in int ) return boolean
  7          is
  8          begin
  9                  return a <> b or (a is null and b is not null) or (a is not
null and b is null);
 10          end;
 11  begin
 12          if ( inserting or (updating and not_equal(:new.fk,:old.fk) ) )
 13          then
 14             update p set child_cnt = child_cnt+1 where id = :new.fk;
 15          end if;
 16          if ( deleting or (updating and not_equal(:new.fk,:old.fk) ) )
 17          then
 18              update p
 19                 set child_cnt = child_cnt-1
 20                   where id = :old.fk
 21                  returning child_cnt into l_cnt;
 22                  if ( l_cnt = 0 )
 23                  then
 24                          delete from p where id = :old.fk;
 25                  end if;
 26          end if;
 27  end;
 28  /

which will physically delete the row....

Review &
      Followup

      Rating:
      5
  June 06, 2003
Reviewer:
      A reader

June 06, 2003 -- Thanks for the question regarding
      "Valid/unusable indexes", version 8.1.6

      You Asked
Why is an index listed as 'VALID' in dba_objects, but can be listed as
'UNUSABLE' in dba_indexes (i.e. if the table was just moved)

Doesn't it seem like these status should be in sync?

      and we said...
dba_objects just has:

      decode(o.status, 0, 'N/A', 1, 'VALID', 'INVALID'),

whereas dba_indexes has:

       decode(bitand(i.property, 2), 2, 'N/A',
                 decode(bitand(i.flags, 1), 1, 'UNUSABLE',
                            decode(bitand(i.flags, 8), 8, 'INPROGRS',
                                                            'VALID'))),

dba_objects is a generic view, having status in there today is probably
"obsolete" as so much stuff has been added since this view was originally
created.  UNUSABLE is a relatively new attribute of an index.

DBA_OBJECTS must be for every object -- very generic.
DBA_INDEXES -- only for indexes, very specific.
Review &
      Followup

      Rating:
      4
  April 05, 2002
Reviewer:
      A reader  from USA
Thanks for your answer.

You mentioned the status in dba_objects is probably 'obsolete'.

Can you recommend a better way to verify the validity of the objects in the
database?  It seems you can really only get a true status from dba_objects for
things like triggers and procedures...

Also, for trivia's sake, what tables are you querying with your decode
statements below?

Thanks again!

          Followup:

goto each object type table (dba_tables, dba_indexes) and look there.

The decodes are from the view definitions - select text from all_views where
view_name = 'xxxxx';

make sure the set long 50000 in sqlplus first

      Rating:
      4
skip_unusable_indexes=true  February 19, 2003

Reviewer:  A reader
Shouldn't skip_unusable_indexes prevent this error ?

SQL> analyze table ism_lat estimate statistics

analyze table ism_lat estimate statistics
*
ERROR at line 1:
ORA-01502: index 'REL.SYS_C001218' or partition of such index is in unusable
state

SQL> alter session set skip_unusable_indexes=true;

Session altered.

SQL> analyze table ism_lat estimate statistics;
analyze table ism_lat estimate statistics
*
ERROR at line 1:
ORA-01502: index 'REL.SYS_C001218' or partition of such index is in unusable
state

          Followup:

<quote>
TRUE disables error reporting of indexes and index partitions marked UNUSABLE.
This setting allows all operations (inserts, deletes, updates, and selects)
on tables with unusable indexes or index partitions.
</quote>

so no, because you are doing an analyze and your analyze is equivalent to

analyze table t estimate statistics
for table
for all indexes
for all columns

you should use dbms_stats (won't fix this issue, but you should) and you should
look into whether you want/need/desire the histograms (they are costly to
compute) and if not, maybe you can turn that estimate into COMPUTE and get
better stats.

      Rating: 2
Can you give me
      an example to make an index Invalid ?  June 05, 2003

Reviewer:  David Jiang  from Houston, Texas, USA

Tom,

Thank you for the web site, which relly give me a lot of help.

I see that you have a column Status in User_indexes. But it seems to me all
indexes is either created valid or not created at all. The index created on the
table x is gone if the table x is dropped. It seems that the Status columna is
useless here in user_indexes. Is this true?

Please help me to verify it.

Thanks a lot.

          Followup:

what about unusable?
or inprogrs?
or N/A?

      Rating:
      5
Excellent  June 06, 2003
Reviewer:
      David Jiang  from Houston, Texas, USA
Thanks a lot, Tom.

OK, I see it now. Can you provide examples of valid/inprogress/ n/a / usable or
unusable regarding index status. I want to know how to make the status showed in
all these different condition.

Thanks again for your prompt answer.

          Followup:

valid -- just create one

unusable -- alter index iname unusable;

If you do "shutdown abort" while a CREATE INDEX ... indextype ctxsys.ConText job
is running (which otherwise would've completed without error) then you can
get... STATUS = 'INPROGRS'

June
      06, 2003 Beth -- Thanks for the
      question regarding "SQL*NET; Net8; Drivers; Versions Used", version 8.1.6

      You Asked
Hi Tom,
Our former dba upgraded our 7.3.4 database to 8.1.6.0.0 on the server-side, but
did not upgrade the client/SQL*Net.

I would like to break down and define each piece, so when I finish the upgrade,
I know EXACTLY what is changing.

Here is our enviroment:
SQL Plus executable:
Plus33w.exe 10/14/97  11:56am

V$version:
Oracle8i Enterprise Edition Release 8.1.6.0.0 - Production
PL/SQL Release 8.1.6.0.0 - Production
CORE    8.1.6.0.0       Production
TNS for Solaris: Version 8.1.6.0.0 - Production
NLSRTL Version 3.4.0.0.0 – Production

ODBC Driver
Oracle 73 Ver 2.5

compatible = 8.1.6

So, it seems we need to move the ODBC driver, and the SQL Plus executable to
Oracle 8.1.6/8.1.7.

I also obviously want to move to Net8, but it seems we may already be using it,
as I see TNS for Solaris: Version 8.1.6.0.0 not TNS for Solaris: Version
2.3.4.0.0  So are we using Net8 with a really old SQL Plus executable and ODBC
driver?

Can you explain which components make up the “Client”, and “Net8/SQLNet”, as
referenced above, and which are coming from the server side and which are coming
from the client side?

Thanks as always for your great answers!

      and we said...
The client (v7.3.4 in your case) is using the old sqlplus.

Oracle prides itself on being backwards compatible as much as possible - it is
something we definitely try for.  The older version client should be able to
connect and use the database as before -- it'll only be when you start to use
new features like LOBS and such that they will notice a problem.

On the server, you are using net8.

On the client you are still using 2.3.4.

the sqlplus executable -- that is client side
v$version - server side
odbc driver -- client side
compatible -- server side

You need to test if you can move the client to 8i -- or if you need to fix
something first....

Review &
      Followup

      Rating: 5
ODBC
      Driver  April 30, 2003
Reviewer:  Tony
       from Canada
Hi Tom,
       Thanks a lot for your help,
I have oracle 817 client and application running on an AIX box and database is
on win2000 box(test env.) , we are getting some connection issues and Oracle is
recommending to install 9.2x ODBC driver. My question is can I install 9.2 ODBC
driver on my existing 8.1.7 env. if yes, do i need to do it on both database and
client machines? and how can we just install ODBC driver without impacting
anything else.

          Followup:

do you use thin?  if so, it is just a jar file, you can plop it down anywhere
you want and just change the classpath.

if it is thick, you need to install the 920 client software.

      Rating: 5
JDBC
      Driver  April 30, 2003
Reviewer:  tony
       from Canada
Tom,
    sorry my mistake it's not ODBC driver but JDBC that I wanted to know about

Regards
Tony

      Rating: 3
SQL*Net
      Version  June 06, 2003
Reviewer:  Reader
       from pa
Tom,
How do I find out SQL*Net/Net8 version info on Windoz ?

Thanks

          Followup:

fire up the installer.

it'll list what is on there.

June
      07, 2003 Bjørn Erik -- Thanks for the
      question regarding "Security -- Basic Authentication on the web over
      HTTP", version 8i

      You Asked
I've studied the HTTP-headers going back and forth from
client/server during Basic Oracle authentication.

I register that once the user is correctly logged on, a
encrypted version of username/pw is sent as a response from the
client to every call to the server.

So my question is this:

1. Are there any way to protect this security risk? As I see it
one HTTP-header is all that is needed to break into a users
account.

2. Is there a way to remove this header, so to speak, after use?
Like a log-off-button. Or maybe say: If user/PW has not been
sent from a HTTP-header the last 30 minutes we prompt a new
logon-screen?

And then one more question:

3. Do you recommend to use the spare-fields (spare1-spare6) in
the user-tab for custom user data? Like storing the users
language.

Thanks in advance.

      and we said...
The HTTP headers and how basic authentication works are well
documented features of that protocol.

To use basic authentication what happens is this:

1) you enter
http://site1/foo/hello.html
in the browser. This will cause a GET request
to be submitted to the webserver at site1. This will request
will look something like:
-----------------------------------------
GET /foo/hello.html  HTTP/1.0

-----------------------------------------

2) The webserver gets the request. It determines that it needs a
userid and password for this document. It does not see one in
the request above (just a GET so far). It will return a document
such as:

-------------------------------------------
Content-Type: text/html
Status: 401 Unauthorized
WWW-Authenticate: Basic realm="SomeRealmName"

This document is protected. You must send the proper
authorization information
to access it.
-------------------------------------------

3) The browser gets back that document and views the header. it
sees the 401 Unauthorized and looks for the WWW-Authenticate
line to get the realm. It looks in its little list of
REALM=user/passwords it caches in memory (empty right now, we
just started the browser). Therefore, it prompts you for the
username/password for the realm "SomeRealmName". If you hit
cancel right now, it just displays the document (which is "This
document is protected. You must send the proper authorization
information to access it. ", you've probably seen that before).
If you put in a username/pass the browser (MOST browsers anyway)
will save the username/password in a list that contains the

  o realm name
  o host name requesting the user/pass
  o port the web server was running on on that host

The browser then submits a request that looks something like:
-------------------------------------
GET /foo/hello.html HTTP/1.0
Authorization Basic dGt5dGU6dGlnZXI

-------------------------------------------------

4) the webserver recieves this new request, sees foo needs basic
auth, sees the user/pass in the header, verifies it. If the
username/password don't match, goto 2 and start over. If it does
match the page /foo/hello.html is returned. Assumed it matched.

5) I now goto
http://site2/something/else.html
6) I now goto
http://site1/foo/another_page.html
The GET request goes up, the webserver
rejects with:
-------------------------------------------

Content-Type: text/html

Status: 401 Unauthorized

WWW-Authenticate: Basic realm="SomeRealmName"

This document is protected. You must send the proper
authorization information
to access it.

-------------------------------------------

the browser will take the hostname/portno/someRealmName and look
in its little in memory cache. this time it will find it since
we just did it a couple of pages ago. It will resend the request
with the username and password and we get our page.....

In short, the browser remembers your password for you. It
associates a username/passowrd with a HOST, PORT, and REALM. The
webserver asks the browser for this information by rejecting the
request.....

The username and password are encoded in the HTTP header using a
base-64 encoding scheme (a very lightweight encoding scheme,
trivial to decode -- email uses this to send binary data in 7
bit ascii format).

If you use SSL (recommended with basic authentication) the
entire converstation is encrypted -- including the HTTP headers.
 That is the answer to question #1 above.

As for number 2 above -- the answer is "no".  The browser would
have to implement this feature as IT is the one doing the
caching of the username/password.  There are simply no
mechanisms for timing this out in the protocol at all.  You
would have to ask your browser vendor to offer this feature.
There is nothing we can do at the server side to fix this.

as for #3, feel free to follow up with another question but I
really don't understand the context of that question.  I'm not
sure what you are asking about there.
Review &
      Followup

      Rating: 5
Follow on Basic
      Authentication  October 04, 2002
Reviewer:
      Quinn P. Sinnott  from Maryland
Tom,

As usual your advice is 100% worth following and I set up our 9iAS Rel 1
modpl/sql using SSL since we use basic authentication. I also set up a dedicated
listener for modpl/sql and have connection pooling enabled.

Ran across a curious event that makes me wonder what is going on in terms of
basic authentication and the userid/password cached by the browser as you
indicated in this message. Most of the time it seems to work fine. When a user
connects and I look at the Oracle processes I see that if this user is the only
one then they grab 5 (ias setting) httpd sessions from the pool. As other users
come in these sessions flip flop around. After 15 minutes idle the sessions die.

Enough boring background, the curious event is when a user changes their
password in Oracle something weird happens and they can still log in with old
password - AS LONG as at least ONE of the httpd sessions is still assigned to
them? If they get bumped by other users or time out then no problem, but if they
change Oracle password (verified by trying sqlplus - it is definitely changed,
sqlplus notices) close the browser, go back in and at the basic authentication
prompt use the OLD password or the NEW password they get in. In one browser
(Netscape 7) I think any password works as long as at least one httpd session is
active for that user.

This seems to indicate that the connections don't always validate by
userid/password but through some other magic connect id or something like that?
I don't think it's a huge problem since an active system would swap connections
in the pool often but I am very curious about the mechanism in effect here?

          Followup:

They are caching the user/password in the middle tier for that cached
connection.  So, when you are rejoined with your in progress session -- it just
checks to make sure the user/password used to establish the existing db
connection matches what the browser sent up.

      Rating: 5
I know what
      Bj?rn Erik meant (last question)  October 28, 2002

Reviewer:  MT  from MD
Tom, for Bjørn's question you wanted clarified I believe he is talking about the
table: sys.user$

There are 6 fields named: spare(N)

N = 1,2,3,4,5,6

My guess is we are not supposed to touch a SYS table...but the names are
misleading. What are they for?

          Followup:

ahh -- yes, that is probably what he meant.

you shouldn't touch the sys table.  the names are meaningless outside of redwood
shores CA.

      Rating: 5
A
      clarification  June 06, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Hi Tom,
Recently I had a discussion with one of my co-worker about how the browser
handles the cached loginname/password after initially prompting the user.
He said that once you enter your loginname and password browser caches it (so
far agreed), and for subsequent requests to the web server, the browser
automatically sends the cached loginname/password (without the web server first
rejecting it as un-authorized).
My question then is,

After the loginname/password is cached by the browser:
1). For subsequent GET requests to the same server, will the browser sent the
cached loginname/password without waiting for the web server to return an
un-authorized response?
OR
2). Even if the loginname/password has been cached by the browser, every time
the user wants to access a page from the same web site, the browser first sends
the header without the password, gets the un-authorized response and then sends
the request again with the encrypted username/password? What I mean is, does the
browser wait for the web server to specifically reject the request every time
before sending the encryted password?

Which one of the above two actually happen?
Thanks,

          Followup:

1) it uses the exact HOSTNAME, PORT NUMBER an REALM (so asktom.oracle.com is
different then asktom.oracle.com:80 is different then asktom.oracle.com realm
"foo")

it'll send the u/p

Lets say at site1, the url /foo/ is protected via basic authentication.
Then......

   1. you enter
http://site1/foo/hello.html
in the browser. This will cause a GET request to be
submitted to the webserver at site1. This will request will look something like:

-----------------------------------------
GET /foo/hello.html  HTTP/1.0

-----------------------------------------

   2. The webserver gets the request. It determines that it needs a userid and
password for this document. It does not see one in the request above (just a GET
so far). It will return a document such as:

-------------------------------------------
Content-Type: text/html
Status: 401 Unauthorized
WWW-Authenticate: Basic realm="SomeRealmName"

This document is protected. You must send the proper authorization information
to access it.
-------------------------------------------

   3. The browser gets back that document and views the header. it sees the 401
Unauthorized and looks for the WWW-Authenticate line to get the realm. It looks
in its little list of REALM=user/passwords it caches in memory (empty right now,
we just started the browser). Therefore, it prompts you for the
username/password for the realm "SomeRealmName". If you hit cancel right now, it
just displays the document (which is "This document is protected. You must send
the proper authorization information to access it. ", you've probably seen that
before). If you put in a username/pass the browser (MOST browsers anyway) will
save the username/password in a list that contains the
          * realm name
          * host name requesting the user/pass
          * port the web server was running on on that host
      The browser then submits a request that looks something like:

-------------------------------------
GET /foo/hello.html HTTP/1.0
Username: scott:tiger

-------------------------------------------------

      (the username: part is wrong, it would be base64 encoded in the real
world)

   4. the webserver recieves this new request, sees foo needs basic auth, sees
the user/pass in the header, verifies it. If the username/password don't match,
goto 2 and start over. If it does match the page /foo/hello.html is returned.
Assumed it matched.

   5. I now goto
http://site2/something/else.html
   6. I now goto
http://site1/foo/another_page.html
The GET request goes up, the webserver rejects with:

-------------------------------------------
Content-Type: text/html
Status: 401 Unauthorized
WWW-Authenticate: Basic realm="SomeRealmName"

This document is protected. You must send the proper authorization information
to access it.
-------------------------------------------

      the browser will take the hostname/portno/someRealmName and look in its
little in memory cache. this time it will find it since we just did it a couple
of pages ago. It will resend the request with the username and password and we
get our page.....

In short, the browser remembers your password for you. It associates a
username/passowrd with a HOST, PORT, and REALM. The webserver asks the browser
for this information by rejecting the request.....

that is the way it is supposed to happen, so that your u/p is not sent to a
routine that is not to have access to it (you can have dozens of u/p for the
same site, not every page should have access to your u/p but if the browser
sends it along with each request, every page would have access to it since it is
just information in the header)

June 07, 2003 Tarek -- Thanks for the question regarding
      "Compare UNION and Subquery", version 8.1.7

      You Asked
Hi Tom,

In some cases we use UNION or MINUS operators instead of subqueries. Which one
is less expensive and why?

Have a nice time,
Tarek

      and we said...
I fail to see how you would use UNION or MINUS in place of subqueries "in
general".  UNION and MINUS both involve sort/distinct steps that materially
affect the result set in a way that a subquery never would.

Neither is less expensive or more expensive -- if one were inheritly superior to
the other in all cases -- well -- we would not have coded both of them, that
would be counter productive.

I cannot even see when you could use UNION in place of a subquery personally --
and minus only in the rarest of rare cases.

Maybe you can give us an example and we'll comment on that.
June 07, 2003 Angus -- Thanks for the question regarding "Why
      do we need 'Extents'?", version 9.2.0

      You Asked
Hello Tom,

Why doesn't Oracle do away with the whole concept of 'extents'?
Why are they needed other than to create work for DBAs and to generate arguments
about whether it matters how many extents a segment is made up of? I know that
they're kind of fundamental to the way Oracle manages its space allocation but I
think that the time has come for Oracle to bite the bullet and be done with
them. Obviously it would take a wee bit of work on the part of Oracle, but
Oracle has done a lot to improve the whole space allocation business in recent
years so why not remove this unnecessary tier in the space management hierarchy?
Most Oracle stuff is done at block level anyway (broadly speaking). It's not
even as though extents are physically contiguous on disk, and even if they are
logically contiguous, so what?

What do you think of this idea?

      and we said...
umm, what exactly would you replace them with eh?

imagine what happens to multi-block reads without contigous collections of
blocks.

imagine the abymsal performance of inserts when every block is gotten one at a
time.

And imagine the arguments you would get from DBA's with new feature X.  That
dba's argue is nothing new or surprising.  removing extents would do nothing to
cut down on the chatter, they would just talk about anything else.

We have removed the issues with extent management -- did so in 815 with locally
managed tablespaces.  You want to hear arguments "should we use them or not".
It kills me.

If you use LMTs the whole "extent thingy" is a thingy of the past.
June 07, 2003 Bhanu -- Thanks for the question regarding
      "oracle shuts down with set pause on in 9i ", version 9.0.1.1.1

      You Asked
Hi Tom

I am  giving the folowing statement in Oracle 9i version 9.0.1.1.

SQL> set pause on
SQL>select * from cat;

My oracle window closes.The oracle application closes I would liek to know why
and whats the solution for that What  has to be done
I have never come across such a problem in 8i or previous versions of
Oracle So please let me know

Thanks

Bhanu Prakash

      and we said...
you forgot two pieces of information:

o i am using windows
o i am using that horrible sqlplusw.exe program.

in early releases of 9ir1 sqlplusW.exe for windows, it would falsely see the
pause as "exit" and exit.  Any pause would cause sqlplus to exit at that point.

you can:

o use sqlplus.exe (that is what I used to use on windows, more functional,
better, more portable -- it is the same sqlplus as on unix and elsewhere -- not
only that but in dos you can hit f7 and get a history or up arrow through it.
since sqlplusw.exe is going away in a future release of Oracle, might be
something to consider)

o get 9iR2 sqlplusw.exe, install the 9iR2 client

o contact support and see if there is a patch for 9iR1
June 07, 2003 Getachew -- Thanks for the question regarding
      "Resource locks from a select query", version 8.1.7.2

      You Asked
Hi Tom.
Your site is one of my resources I check on a daily basis.
My question is:
We have a reporting application which contains simply aggregiated
select statements.  It doesn't contain any "select for update.."
queries.  We run into what seems to be a contention between these select
statements and a batch process that runs at night. The batch
process waits for rows to be released. As soon as we kill sessions
running the select statements, the batch procesess continues.
Under what conditions do select statements lock rows?

Thank you!

      and we said...
you would have to look in v$lock to prove that there are any locks in the first
place.

a simple select without select for update, will not do what you describe.

Next time, use a script like utllockt.sql to see if there are blockers and
waiters (in $ORACLE_HOME/rdbms/admin, designed to run in svrmgrl as SYS, you'll
need to install @catblock.sql in same directory/tool first - one time install
for catblock)

If not, you just have normal "contention" and the batch process appears blocked,
but is probably just waiting for resources like everyone else (v$session_event
can be used to see if it is waiting and waiting and waiting for stuff)
Review &
      Followup

      Rating:
      4
  June 16, 2003
Reviewer:
      A reader

June 07, 2003 Srinivas -- Thanks for the question regarding
      "Assing value from Javascript variable into plsql variable in PSP",
      version 8.1.7

      You Asked
Hi Tom ,
I heard great about you.
I have a question about Oracle PSP (Plsql Server Pages).
How can I assing a value into Plsql variable from Javascript variable in Oracle
PSP.

Thanks in Advance
Srinivas

      and we said...
It is a trick question of sorts.

PLSQL runs on the server, javascript on the client.

Javascript can invoke a URL which in turn runs PLSQL of course. PLSQL can
generate a webpage that includes javascript.

Hence -- for plsql to "pass" a variable to javascript, plsql would write
javascript code that does an assignement:

htp.p( 'function doSubmit( r )
        { document.formName.p_request.value = ' || plsql_variable || ';
          document.formName.p_from_javascript.value = 55;
          document.wwv_flow.submit();
        } ' );

for javascript to assign a value to plsql -- you put it in a URL or a form field
-- for example, above, p_from_javascript is set to 55 by javascript and when the
plsql runs, it'll have 55 in it.
Review &
      Followup

      Rating:
      5
javascript  February 17, 2003
Reviewer:
      mo
Tom:

excellent examples. However I found that you can never access a javascript
variable value (value of an object on page) by the plsql code that generates the
same page.  You always have to submit the html page to another pl/sql procedure
which read the value of the javascript variable. Am i wrong?

Thanks

          Followup:

think about WHERE AND WHEN javascript runs.

think about WHERE AND WHEN plsql runs.

You do know that they NEVER run at the same time right?  How could a plsql
routine that is "writing" a page reference a javascript variable from "that
page" since the javascript is just text until the BROWSER gets it and actually
runs it.  There is "no javascript variable" to access whilst plsql is writing
it.

      Rating:
      5
lookup  June 06, 2003
Reviewer:  mo

Tom:

I have a web page generated by pl/sql.  The page builds a table of stock numbers
and descriptions so that when user selects a stock number in the dropdown list
the description automatically pops up.

However, if someone adds a "hard return" to the description the whole thing does
not work. Do you know why? do i have to escape something here in the browser?

<SCRIPT LANGUAGE="JavaScript 1.2" TYPE="Text/JavaScript">
 var desc = new Array()
 var unit = new Array()
   desc[0] = " ";
   unit[0] = " ";
 desc[1] = "ENTER DESCRIPTION "
 unit[1] = ""
 desc[2] = "PILLOWS."
 unit[2] = "EACH"
 desc[3] = "HEADPHONES"
 unit[3] = "EACH"

          Followup:

javascript doesn't accept code in the form

desc[n] = "foobar
"

that is not valid syntax AFAIK, it would have to be

desc[n] = "foobar" +
""

it is because the CODE you produce is not valid javascript code.  can escapes
help?  probably -- try it (i don't write javascript myself, don't know if
"foobar%0A" is valid -- probably not, guessing "foobar\n" it what would be
expected)

      Rating:
      5
javascript  June 07, 2003
Reviewer:
      mo
TOm:

Thanks, but how do you convert the hard returns in the oracle text to /n in
javascript.  Do you use replace function in oracle to replace chr(10) to /n.

          Followup:

replace pops into mind, yes.

June 07, 2003 Frank -- Thanks for the question regarding
      "recovery after losing UNDO tablespace", version 9

      You Asked
Hi Tom,
After normal shutdown Oracle9i DB, if i lose all UNDO files and dont have
backup, could i just create a new UNDO tablespace & files and drop the old to
recover the DB? Since the DB was shut down normally, it rolled back all
uncommited transactions, so all datafiles dont contain uncommitted data. Then I
think undo is not needed during recovery. The only thing i will not be able to
recover is I cant get flashback, which i dont care, am I right? But I was not
allow to create the UNDO tbs without opening the DB, how can I do to recover the
DB? Any difference between version 8.1.7 and 9?
Thanks!

      and we said...
Ok, here we go (don't try this at home, well, maybe - do try this at home cause
you probably are not running a "real" system at home ;)

sys@ORA920.US.ORACLE.COM> shutdown
Database closed.
Database dismounted.
ORACLE instance shut down.

sys@ORA920.US.ORACLE.COM> exit
Disconnected from Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

[ora920@tkyte-pc-isdn ora920]$ ls
control01.ctl  cwmlite01.dbf  indx01.dbf  redo02.log  redo0B.log    temp01.dbf
  users01.dbf
control02.ctl  drsys01.dbf    odm01.dbf   redo03.log  redo0C.log    tools01.dbf
  xdb01.dbf
control03.ctl  example01.dbf  redo01.log  redo0A.log  system01.dbf
undotbs01.dbf

[ora920@tkyte-pc-isdn ora920]$ mv undotbs01.dbf undotbs01.dbf.xxx

[ora920@tkyte-pc-isdn ora920]$ sqlplus /nolog

SQL*Plus: Release 9.2.0.1.0 - Production on Sat Sep 28 08:33:29 2002

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

idle> connect / as sysdba
Connected to an idle instance.
idle> startup
ORACLE instance started.

Total System Global Area  143725064 bytes
Fixed Size                   451080 bytes
Variable Size             109051904 bytes
Database Buffers           33554432 bytes
Redo Buffers                 667648 bytes
Database mounted.
ORA-01157: cannot identify/lock data file 2 - see DBWR trace file
ORA-01110: data file 2:
'/usr/oracle/ora920/OraHome1/oradata/ora920/undotbs01.dbf'

idle> show parameter undo

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
undo_management                      string      AUTO
undo_retention                       integer     10800
undo_suppress_errors                 boolean     FALSE
undo_tablespace                      string      UNDOTBS1

idle> alter system set undo_management = manual scope=spfile;

System altered.

Note: disabling the undo tablespace here, this'll let us go back to "rollback
segments" and we'll use the system RBS to get back on our feet

idle> shutdown
ORA-01109: database not open

Database dismounted.
ORACLE instance shut down.

idle> exit
Disconnected from Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

[ora920@tkyte-pc-isdn ora920]$ !sql
sqlplus /nolog

SQL*Plus: Release 9.2.0.1.0 - Production on Sat Sep 28 08:35:34 2002

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

idle> connect / as sysdba;
Connected to an idle instance.
idle> startup
ORACLE instance started.

Total System Global Area  143725064 bytes
Fixed Size                   451080 bytes
Variable Size             109051904 bytes
Database Buffers           33554432 bytes
Redo Buffers                 667648 bytes
Database mounted.
ORA-01157: cannot identify/lock data file 2 - see DBWR trace file
ORA-01110: data file 2:
'/usr/oracle/ora920/OraHome1/oradata/ora920/undotbs01.dbf'

Note: we still have to get rid of that thing..

idle> alter database datafile
'/usr/oracle/ora920/OraHome1/oradata/ora920/undotbs01.dbf'
  2  offline drop;

Database altered.

idle> alter database open;

Database altered.

idle> drop tablespace undotbs1;

Tablespace dropped.

idle> create UNDO tablespace undotbs1
  2  datafile '/usr/oracle/ora920/OraHome1/oradata/ora920/undotbs01.dbf' size
25m
  3  autoextend on next 1m maxsize 1024m;

Tablespace created.

idle> alter system set undo_management = auto scope=spfile;

System altered.

Now, we've put the undo tablespace "back" by creating a new one.  Just
bounce...

idle> shutdown
Database closed.
Database dismounted.
ORACLE instance shut down.
idle> exit
Disconnected from Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
[ora920@tkyte-pc-isdn ora920]$ !sql
sqlplus /nolog

SQL*Plus: Release 9.2.0.1.0 - Production on Sat Sep 28 08:40:51 2002

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

idle> connect / as sysdba;
Connected to an idle instance.
idle> startup
ORACLE instance started.

Total System Global Area  143725064 bytes
Fixed Size                   451080 bytes
Variable Size             109051904 bytes
Database Buffers           33554432 bytes
Redo Buffers                 667648 bytes
Database mounted.
Database opened.
idle> show parameter undo

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
undo_management                      string      AUTO
undo_retention                       integer     10800
undo_suppress_errors                 boolean     FALSE
undo_tablespace                      string      UNDOTBS1
idle>

and we are right back where we started from

Review &
      Followup

      Rating: 5
When need we
      UNDO & Need we backup online Redo?  September 28, 2002

Reviewer:  Frank Zhang  from FL, USA
Thanks Tom! I like your answer always with the real example!!!
Another related question:
1)Are 'shutdown abnormal' or system failure the ONLY cases that may leave the
uncommitted data in datafiles therefore the rollback/Undo is needed while
recovering?
2)I believe that : Latest DATA = Backup DATA + Archlogs + Current Redo.
Is 'backup online redo' a good thing, i prefer multiplexing it than making
backup since it's easy to mistakenly restore the redo from the old backup and
overwrite the current redo, am i right?

          Followup:

1) You ALWAYS want to have backups of UNDO information.  The procedure here is a
"whoops, I've done something really bad, how can i get out of it".

do not, repeat, do not take this as a way to get out of backing up UNDO, just
consider UNDO as important as your own tables.  Always back it up.  ALWAYS.

2) never never never backup online redo logs.  They only thing that could result
from doing that is you accidently restore last weeks redo over this weeks
current redo -- losing committed work during a restore.  NEVER back them up
(online redo logs) in any case, no matter what.

      Rating: 5
about UNDO
      again  September 28, 2002
Reviewer:  Frank Zhang
       from FL, USA
Tom, I didnt mean to omit the backup of UNDO. I just wanted to know how oracle
does and what is the case that we have no way out without UNDO data in my
question1, could you explain it. Of course, in the real world , i will back up
the UNDO as well as DB files, control files, etc. Answer to question2 was clear,
i disagree with other DBAs approach to backup the online redo and even in some
Oracle published books, author suggests to back up online redo, now i'm
confident with my oppinion, thanks.

      Rating: 5
is it possible
      on 8.1.7.0.0  June 03, 2003
Reviewer:  Alvin
       from Philippines
I've "show parameter undo" my 8i and there was no rows selected.

1. Can the same thing be done on 8i ?
2. Why the need to "bounce" instead of just issuing "alter database open" ?

          Followup:

UNDO tablespaces are a new 9i feature, did not exist in 8i.

why not bounce?  6 one way, 1/2 dozen the other.  I didn't check to see if an
alter database open would suffice, but I know a clean startup would so I used
that.

      Rating: 5
Undo tablespace
      missing...  June 03, 2003
Reviewer:  Alvin
       from Philippines
hmm... can an 8i db recover the way 9i recovered with the missing rbs tablespace
?

          Followup:

if you had a NORMAL shutdown, yes.

      Rating: 5
log seq
      number  June 07, 2003
Reviewer:  Reader

My understanding is that each datafile header contains the most recent log
sequence number (LSN) that is written at the time the datafile was being
written. Oracle uses this LSN info on datafile headers to know from which log
file to start to perform recovery incase i have restored the datafile from
backup.

(1) I was wondering which dynamic view could give me the LSN for each datafile
in my database. I was querying v$datafile_header and LSN info is missing from
that view although it gives me checkpoint_change# for each datafile.

(2) also, control file keeps all of these info. Is there a view that DBA can use
to see how these values match with control file info? Thanks.

          Followup:

in concept what you say it "sort of true".  it is perfectly ok to conceptualize
it that way.

v$datafile_header is the dynamic view that is the datafile header.  it works by
checkpoint change numbers.  v$archived_log has the change numbers covered by a
logfile.

      Rating: 5
It makes
      sense!  June 07, 2003
Reviewer:  Reader

Thanks a bunch. After seeing your answer, I refered v$archive_log definition and
the doc says, "This view displays archived log information from the control
file" and it makes sense now that controlfile has all of the info about archived
log files including SEQUENCE#, FIRST_CHANGE#, and NEXT_CHANGE#.
So, Is this how controlfile guides oracle to determine which log file needs to
be applied with respect to checkpoint_change# recorded in the datafile header?

Now, i have another question, if i may.

The number of entries the controlfile can keep is related to MAXLOGHISTORY and
for my database it is set to 226. Assuming that the database exceeded log seq
226, and say the present log seq number is 300, now the entries are overwritten
in the controlfile. Next time if i restored the file for recovery assuming that
file header has a checkpoint number corresponding to the FIRST_CHANGE# in the
archivelog file (say seq 5), how my current controlfile knows as the entry is
overwritten. Thanks in advance. I am learning so much from your answers.

          Followup:

it knows it is overwritten cause it is not there?

it'll "suggest" what the file name probably is for the log if it needs it during
recovery.

      Rating: 4
control file
      record keep time  June 07, 2003
Reviewer:
      Jeeva  from Austin, TX
Is there any relation between control_file_record_keep_time and how long
controlfile keeps records of archived log history?

June 08, 2003 Manoj -- Thanks for the question regarding
      "Killing Current Session", version 8.1.6

      You Asked
Hi Tom,
  in my application, i am calling a procedure which checks the rights of the
user. if user don't have rights then i have to kill the session after displaying
message.

      and we said...
The caller (client) of the procedure is the only one that can do that.  You
cannot commit suicide in the database.

So, the code that calls the database would do this - the code that logged in
would be responsible for this.
Review &
      Followup

      Rating: 4
Killed sessions
      does not disappear  August 16, 2002
Reviewer:
      Alexandre Matthes  from Brasil
Hi Tom,

First of all, thanks for all help you give us, your site is very very very
helpfull, and I´d like to know when your new book is coming ?

Ok, here is the problem...
I have a Intel machine with 8 Xeon 550Mhz, 4G memory and Windows 2000 Advanced
Server based running

 Oracle database EE version 8.1.7.3.0 (MTS configured)...

I have a very weird situation standing four days on database, I´ve killed some
sessions and they seems still connected and consuming resources...

SYS@DBSISBE:DADOS101> select sid,serial#,paddr,username,status,server
  from v$session where server = 'PSEUDO';

       SID    SERIAL# PADDR    USERNAME                       STATUS   SERVER
---------- ---------- -------- ------------------------------ -------- ---------
        37      20530 256115B0 SYSTEM                         KILLED   PSEUDO
        40      27366 256115B0 SYSTEM                         KILLED   PSEUDO
        63      12159 256115B0 SYSTEM                         KILLED   PSEUDO
        64       1683 256115B0 SYS                            KILLED   PSEUDO
        95       2550 256115B0 SYS                            KILLED   PSEUDO

SYS@DBSISBE:DADOS101> select spid
  2  from v$process p, v$session s
  3  where p.addr=s.paddr
  4  and s.server = 'PSEUDO';

no rows selected

SYS@DBSISBE:DADOS101> select * from v$process where PROGRAM = 'PSEUDO';

ADDR        PID SPID   USERNAME      SERIAL# TERMINAL   PROGRAM    TRACEID   B
LATCHWAI LATCHSPI
-------- ------ ------ ---------- ---------- ---------- ---------- --------- -
-------- --------
25237384      1                            0            PSEUDO

SYS@DBSISBE:DADOS101> SELECT name, AVG(value)
  2  FROM v$session ses, v$sesstat sst, v$statname stn
  3  WHERE sst.sid=ses.sid
  4  AND stn.statistic# = sst.statistic#
  5  AND stn.name = 'session pga memory'
  6  AND ses.server = 'PSEUDO'
  7  GROUP BY name;

NAME                                                             AVG(VALUE)
---------------------------------------------------------------- ----------
session pga memory                                                1824960,8

So, it seems that Oracle is missing and did not remove above sessions(still
consuming  memory resources)...
I can not use "orakill" because I DO NOT have SPID in v$process...
How can I eliminate these SESSIONS...(without restarting database) ???

Thanks a lot,

Alexandre Matthes

          Followup:

Comment -- do you have other psuedo sessions, if so, that 1.8 meg is misleading,
why didn't you just sum up value where sid in ( 37, 40, 63, 64, 95 )

contact support -- I don't really work with windows at all (waste of a perfectly
good server).  In this case, not sure what to tell you (although they might
remind you that 8173 isn't supported)

      Rating: 4
Killed sessions
      does not disappear  August 16, 2002
Reviewer:
      Alexandre Matthes  from Brasil
Comment -- do you have other psuedo sessions, if so, that 1.8 meg is misleading,

why didn't you just sum up value where sid in ( 37, 40, 63, 64, 95 )

No, I do not have other pseudo columns, and I get the same result running that
query using "where sid in ...." (obviously), thanks for the idea...

contact support -- I don't really work with windows at all (waste of a perfectly

good server).  In this case, not sure what to tell you (although they might
remind you that 8173 isn't supported)

Oh...that´s why I´ve submited this question to you, cause support DID NOT
SUPPORT my problem, they just told me "Reinitialize your database..."

Ok Tom,

Thanks a lot, I´ll try to convince my superiors to upgrade the database version,
even thow Oracle does not seems wanting to practice good prices (as the
concorrency is doing and well doing here in Brasil) and Oracle is loosing some
very good clients...

      Rating: 2
Killing the
      current session  August 19, 2002
Reviewer:
      Manoj Mistry  from India
Thanx Tom,
   But the problem remains becos i cannot kill the current session even if i
check at the client machine.

          Followup:

I am not saying that the client kills the session.

All the client need do is logoff.

      Rating: 3
Same Problem
      with 9i Release 2 on Windows NT  August 30, 2002

Reviewer:  Michael  from Germany
Hello Tom!

I have the problem with the pseudo sessions, too.
I use Oracle 9i R2 on NT. It's a test sytem. The following java causes the
problem.

import java.sql.*;
import oracle.sql.*;
import oracle.jdbc.*;
import oracle.jdbc.oci.*;
import oracle.jdbc.pool.*;
import java.util.*;

public class SimpleProxyConnectionTest {
 public static void main(String[] args)
  throws Exception {
   OracleOCIConnectionPool cpool =
    new OracleOCIConnectionPool
     ("mike","mike","jdbc:oracle:oci:@");
    Properties prop = new Properties();
    prop.put
     (OracleOCIConnectionPool.PROXY_USER_NAME, "scott");
    OracleOCIConnection cconn1 = (OracleOCIConnection)
     cpool.getProxyConnection
      (OracleOCIConnectionPool.PROXYTYPE_USER_NAME, prop);
    cconn1.setAutoCommit(false);
    OracleOCIConnection cconn2 = (OracleOCIConnection)
     cpool.getProxyConnection
      (OracleOCIConnectionPool.PROXYTYPE_USER_NAME, prop);
    cconn2.setAutoCommit(false);
    System.in.read(); // Here i terminate the java
    cconn1.close(); cconn2.close();
    cpool.close();
  }
}

The user "mike" can be proxy for scott.

After the termination of this java (at the read(), i simply closed the DOS box),
i have the two pseudo sessions of the database. I have tried to kill them with
OEM and SQL*Plus and waited about an hour, but the pseudo sessions were still
there.

Perhaps you are able to reproduce this thing.

          Followup:

Have you tried enabling dead client detection -- read the network Admin guide,
look for EXPIRE_TIME

      Rating:
      3
EXPIRE_TIME  September 02, 2002
Reviewer:
      Michael  from Germany
Yes, it's enabled. The SQLNET.ORA is as follows:

# SQLNET.ORA Network Configuration File:
E:\Apps\oracle\ora92\network\admin\sqlnet.ora
# Generated by Oracle configuration tools.

NAMES.DEFAULT_DOMAIN = hitachi-landshut.de

SQLNET.AUTHENTICATION_SERVICES= (NTS)
SQLNET.EXPIRE_TIME= 1

NAMES.DIRECTORY_PATH= (TNSNAMES, ONAMES, HOSTNAME)

Any idea?

      Rating: 4
Killing the
      procedure in remote server  December 30, 2002

Reviewer:  Praveen KV  from Bangalore
Hi Tom,

I ran a procedure in a remote server (from SQL *Plus) which inserts some log
information from with in a loop (and commits after each insertion). I killed the
client (not logged off) and found that the procedure went on execution in the
server. i.e, new log records were inserted into the log-table. Total time of
execution is around 10 min. I thought the procedure execution must also gets
killed automatically since the client may be expecting some return values.

1) Is there any init.ora parameter that can be set to halt the execution of the
server side procedure which was triggered from a remote client when the client
get unexpectedly killed?

2) How does such situations can be properly handled using an exception handler ?

Thanks and warm regards

Praveen KV

          Followup:

1) no, not really -- TCP doesn't send anyone messages or anything.  If the
client "goes away", the server doesn't know until it tries to send the
answer/result back.  Here, since you already committed, the data would be
modified -- then the server would realize "client is gone" but there is no
chance to rollback.

2) the server has NO CLUE the client "disappeared".  And in fact, it makes no
difference that the client did logically.  In fact, in your case since you
"commit after every insert" (terrible, heinous, horrible practice) - you would
WANT it to continue.

You should let the client control the transaction ALWAYS.

      Rating: 3
Re: Killing the
      procedure in the remote server  December 31, 2002

Reviewer:  Praveen KV  from Bangalore
Hi Tom,

Thankyou very much for the quick answer.

Tom, I am well aware of the fact that committing from within a loop/or after
each insertion is an horrible practice (I have your book), but the reason I did
that is I wanted to keep track of the procedure execution (by monitoring the log
records). There is no other way but to commit after each insertion of the log
record so that I can query the log table from another session.

Also when I killed the client, there were only 9 log records and the procedure
went on to add some 200 more. But had the procedure completed successfully there
would be exactly 540 log records. So even though I killed the client immediately
after the execution of procedure, according to you there should be atleast 539
log records. Right ? But there were only 209.

Please tell me where I went wrong.

Thanks and warm regards

Praveen KV

          Followup:

Read forward into the appendix -- learn about dbms_application_info --
especially the v$session_longops stuff.  No need to commit just to display
progress!!!

(also there are autonomous transactions as well!)

I cannot tell you what "went wrong" as I wasn't there.  Maybe you hit the
infamous ora-01555 since you are committing in the for loop.  You hit some error
- it raised an exception, it broke your procedure out of the loop and attempted
to return an error to the client only to find the client dies and so it kills
itself.

      Rating: 5
Predicate
      Merging  December 31, 2002
Reviewer:  Shree 420
       from from Like a mouse in the cage, I am trapped inside the
      cubicle
Hi Tom!
What a wonderful job you are doing! There are just not enough words to describe
the kind of positive imapct you have on people's career. I have been finally
able to read your book and like a good novel, I am unable to put it down. My
question to you is in the Appendix A, DBMS_APPLICATION_INFO topic you mention
that,

       "One of the issues with views can be in the area of predicate merging. If
the optimizer were able to 'merge' the predicate into the view definition, it
would run really fast. If not, it runs really slow. This feature, using the
client info, allows us to 'merge' the predicate ahead of time when the optimizer
cannot. The application developer must set the value and just SELECT * from the
view. Then, the 'right' data will come out."

       I do not understand what you mean by predicate merging. Can you please
elaborate on that? Thank you Tom! Have HåþÞ¥ New Ýeâr!

          Followup:

what i mean is that many times views contain "aggregates", "psuedo columns",
"outer joins" that preclude or prevent us from "pushing a predicate" down into
them. That is -- the entire view will be built into temp space AND THEN the
predicate will be applied.

We can push the predicate down ourselves and this little trick is what I was
showing.  Then, the view is run with the predicate pushed all the way down --
evaluated AS the view is materialized -- not after.

      Rating: 5
predicate
      merging   June 07, 2003
Reviewer:
      A reader  from CA
I had the same question as the earlier reader - could
not understand perdicate merging - Although your
explanation above makes sense, if you could give an
example, that would be really great!

thanx!:)

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96533/opt_ops.htm#1009807

June 08, 2003 Todd -- Thanks for the question regarding
      "Sqlloader and passing passwords from scripts", version 8.17

      You Asked
Hi Tom,

I have several shell scripts that call sql loader to process data from flat
files.  The general process works like this: The shell script will prompt for a
password which will be read into a variable and subsequently passed to the sql
loader commands(s) like "sqlldr userid=myuser/$passwd cnt=control.ctl
log=logfile.log

My question is, is there a way to hide the password from the ps -ef | grep
userid command?  Something similar to /nolog?

Thanks a lot,

Todd

      and we said...
see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:142212348066
....
Review & Followup

      Rating: 3
re- Sqlloader
      and passing passwords from scripts  May 30, 2002

Reviewer:  Todd Graham  from Santa Cruz, CA

Thanks for your answer Tom, It was very helpful to see the "recommended"
approach. I've approached it from another angle and used the following and it
appears to effectivly hide the password.

sqlldr userid=myuser control=mycontrol.clt log=mylog <<SQLEOF > LOGFILE 2>&1
$PASSWORD
SQLEOF

Can you tell me if there is anything wrong with this method?

Thanks in advance

          Followup:

Well, on Solaris I just go:

/usr/ucb/ps -auxwwe | grep PASSWORD

and I'll get it ;)

"e" dumps the environment.
"ww" is "weally wide" (to quote elmer fudd)

      Rating: 3
How to load
      data from saved in excel   May 31, 2002
Reviewer:
      Godwin  from Ghana
Please Tom i have many records save in excel and i want to use sql loader to
insert them into an oracle table.
How do i go about it?
It is difficult these days to post my questions directly because your status is
always busy.
Best Regards.

          Followup:

I'll respond to part two of this first.

I'm one person.  I have taken an average of 18 questions a day, every day since
Jan 1, 2000.  Not just business days -- every day.  That is well over 15,000
questions so far.  I'm just one person -- there are LOTS of resources out there.
 I might suggest that:

a) the USENET newsgroups, freely available from www.google.com, are a great
resource.  I can be found there, as well as many other extremely knowledgable
people -- all willing to try and help you.

b)
http://otn.oracle.com/
has discussion groups where other Oracle developers hang out.  Great
place to post a question and get an answer (and maybe even answer a question or
two yourself)

c) search works really well.  A search for excel on this site returned subjects
like:

"Automate loading of data from Excel spreadsheets into database"
"Storing & Retrieving Excel Spreadsheet in DB"
"How to import data from more than one (.csv ) [Excel] files into the related
tables !"

for example.  In your case, I might

1) save the excel data as CSV (comma separated values)
2) use sqlldr to load it (the csv file)

      Rating: 4
how to convert
      xls to csv  June 08, 2003
Reviewer:
      A reader
tom,

i tried

$ ls -l
total 28
-rw-r--r--   1 towercv  staff      13824 Jun  8 05:06 book.xls
$ mv * "book.csv"
$ ls -l
total 28
-rw-r--r--   1 towercv  staff      13824 Jun  8 05:06 book.csv
$ head *
&#9576;&#9575;&#945;í&#9618;ß#,##0.00_);[Red]\("$"#,##0.00\)&#9650;&#9830;7$

please let me know how to convert the xls to csv in solaris, i tried in windows
i came properly but in unix i dont know how to do it.

thanks

          Followup:

oh, that is funny.

you have to

a) open it in excel, they know how to read their file format.
b) file save as
c) choose file type "csv"

would it not be cool if simply renaming a file changed its format...

June 08, 2003 Gediminas -- Thanks for the question regarding
      "storing dynamic HTML pages to files", version 4.0.8 and 8.1.5

      You Asked
We are building dynamic HTML pages using PLSQL.
Is any easy way to capture resulting pages to files
(UTL_HTTP doesn't work, because we are using  SSL).

      and we said...
Sure, you can run the pages in SQLPlus and then use UTL_FILE to
write the result to disk.

So, lets say you have setup UTL_FILE (see the supplied packages
guide
http://technet.oracle.com/doc/server.815/a68001/utl_file.htm#998101
for setup steps -- you need an init.ora parameter set
up).  You can write a procedure:

create or replace procedure dump_page( p_dir     in varchar2,
                                       p_fname   in varchar2 )
is
    l_thePage       htp.htbuf_arr;
    l_output        utl_file.file_type;
    l_lines         number default 99999999;
begin
    l_output := utl_file.fopen( p_dir, p_fname, 'w' );

    owa.get_page( l_thePage, l_lines );

    for i in 1 .. l_lines loop
        utl_file.put( l_output, l_thePage(i) );
    end loop;

    utl_file.fclose( l_output );
end dump_page;
/

Now, you log into sqlplus (or some other command line sql
environment) and run the procedure you normally run from the
WEB.  For example, say you have a procedure:

create or replace procedure pretend_this_is_a_webdb_proc
as
begin
    htp.p( 'This is a webdb procedure, trust me' );
    htp.p( 'The end' );
end;
/

You would just run that and then run:

begin
    pretend_this_is_a_webdb_proc;
    dump_page( '/tmp', 'page.html' );
end;
/

the page would now be stored in the file page.html in /tmp.  If
your procedures make use of the Owa_Util.get_cgi_env() call, you
may have to setup an appropriate environment (if you get a
numeric or value error with respect to the owa_util package --
this is the cause).  To set up an environment you would simply
run a block like:

declare
    nm  owa.vc_arr;
    vl  owa.vc_arr;
begin
    nm(1) := 'SERVER_PORT';
    vl(1) := '80';
    owa.init_cgi_env( 1, nm, vl );
end;
/

first.  To cause this page to be extracted on a recurring basis,
you can take all of the above, put it into a stored procedure
that sets up the environment, runs the procedure and dumps the
page -- then use dbms_job to schedule that procedure to run
every X minutes/hours/days whatever.

Review &
      Followup

      Rating:
      4
  April 11, 2002
Reviewer:
      A reader

      Rating: 5
query server
      and dump results to another server  February 21, 2003

Reviewer:  mo
Tom:

1.  I have two unix servers "R7" and "R20".  There is a static homepage on "R7"
that visitors access using web server only.. THe page will have search button
for things to search.  Now the database to search is on "R20" which is accessed
using MOD_PLSQL.

I want visitors to get a result page off "R7". I do not want them to see the
link "procedure" running on the R20.

Can I do this with your above procedure that you also cover in your book page
1203.  Can I have the static page on R7 submit data to a procedure on R20 which
in tuns uses utl_file to dump results on r7 and then open that page for the
user?

THanks,

          Followup:

why why why?  what is the goal here -- security via obscurity again?

or -- is your goal to make this website as slow as you possibly can - there are
easier ways to do that.

So -- tell me -- what is the goal here.

      Rating: 5
dump
      page  February 21, 2003
Reviewer:  mo

Tom:

1. Yes to make the server 20 link invisible. It is not that big of a system to
create a problem here. we are only looking up addresses. I do understand it does
not implement security in any way, but it is a physcological thing that makes
client happy. more like making your phone unlisted that is all.  If it is
possible please tell me so?

2.  You say there are easier ways to  do this? How.

Thanks

          Followup:

But the link is there -- it is security via obscurity -- the two systems are
totally 100% linked network wise.  You are kidding yourself if this is a
"security thing".

it is a dis-service, false sense of security...

there are easier ways to make a system dog slow, that was my point:

...
or -- is your goal to make this website as slow as you possibly can - there are
easier ways to do that......

that was tongue in cheek, sarcasm :)

You would set up a program on the r7 server (cgi-bin or module or whatever) that
runs a url on r20 and sends the data back there should be no files anywhere,
just something that can run a URL and return the results

but.... this is sort of beyond "how do I do this in the database" so -- you'll
need to find a webserver guy to go further.

      Rating:
      5
search  February 21, 2003
Reviewer:
      mo
Tom:

Just to let you know I searched on he exact title of this link and your search
will not find anything:

try it search for

storing dynamic HTML pages to files

Thanks

          Followup:

yup, we are workig on it

      Rating:
      5
link  February 21, 2003
Reviewer:
      mo
Tom:

Well let us say people wants to bug you by making phone calls. They call 411,
you phone is unlisted. they open the white pages and your phone is still not
found.  Do not you think you enhanced your privacy/security more by having your
phone unlisted.

anyway, what would be the problem if i had the static page on the first server
submit to a procedure on the second server.  The second procedure creates the
results dynamic  page using dump_page and saves it into the first server
directory. then the procedure would change HREF value to the open the file on
the first server.  Is not this a feasible solution before i jump into it?

Thanks,

          Followup:

nope cause the autodialers that the pesky telemarketers use still find me.
believe me -- I know, my number IS unlisted and i STILL get calls.

the people that want to get into the system -- will use tools to do so.  they
will find this easily.

The ONLY thing this gives (and -- you and I have had this discussion before mo)
is a false sense of "my system is secure because I've obscured things"....

you tell me -- on that last paragraph.  You tell me -- how does a "static page"
do anything??? it is (after all) static.

do what you will.

      Rating:
      5
re-direct  February 21, 2003
Reviewer:
      mo
Tom:

Static page is like a homepage. It is always the same - static in nature. I can
still have a dropdown list on a static page and click a button "search" to do
something. It does not affect the page in any way.  It result in another page
that is dynamic. second page depends on what is in the database and what user
selected in first page.

Client has a static page on server that simply looks up addresses, emails from a
database on the second server.

Since the second server has other databases on it, he does not want people to
see the link name "www.hotserver.com" which enables them to find the ip address
and try to hack or get through unsecured oracle applications using the browser.
He says that hackers can not find out about ip addresses unless they were give
the address or have a DNS name?

Anyway, think of the static page as a page that lists all 51 states. User opens
up the page, selects a state from a dropdown list, presses "Search" then he sees
all info related to state as size, population, number of counties etc.

Can the "search" button submit data to a pl/sql procedure on a second server
which uses you dump_page routine which writes an html file "page.html" to the
first server, and then I re-direct the URL to open up the file.  So end result
user will see a URL to same public server with data results.

Thank you,

          Followup:

a static page cannot "lookup addresses, emails" -- a static page may send you to
a dynamic page that does that.

I'll say this one last time and then will just stop responding:

security through obscurity is worse then having no security at all.  got it?  it
does one thing and one thing only -- it makes the pointy haired managers feel
"better".  You haven't done a single thing to secure ANYTHING, in fact, since
people have this false sense of security -- you've actually made the problem
worse.  Got it?  Make sense?  If not, I sort of "give up"....

i don't know how else to say it -- people who want to find that server WILL FIND
it.  they are not stupid, there are tools to do it.  Just like a call center
finds my phone number -- which is unlisted.

You can do whatever you want.  The search button can certainly do that --
HOWEVER guess what URL will appear in the browser???  The BROWSER is the guy
that submits the page (not this "static page" -- it isn't even a teeny tiny bit
relevant in this entire discussion. Ignore it, pretend IT DOES NOT EXIST).

So, your static page submits to server r20, r20 appears in the URL -- r20 dumps
the page, r20 sends a redirect back to the client (browser) the browser gets the
static page from r7.

big deal -- so what -- r20 appeared, has to appear, will appear.

This is like a bad joke and you are not getting the punchline.

      Rating: 5
dump
      page  April 27, 2003
Reviewer:  mo

Tom:

If you want to dump HTML (htp type procedure output) file into a long variable
instead of a file Would you still be using UTL_FILE functions for that or not?

          Followup:

think about it...

      Rating: 5
Why not use
      mod_rewrite from apache to hide the url or maybe reverse
      proxy.  April 28, 2003
Reviewer:
      A reader

          Followup:

how does that enhance or add to the security of the system?

      Rating: 4
Handling
      Cookies  June 08, 2003
Reviewer:  Jack Wells
       from Fort Lauderdale, FL
Tom,

I would like to use the "dump_page" procedure to call a mod_plsql procedure
which sets a cookie before producing HTML:

--
-- Write the cookie in the http header BEFORE any HTML is produced
--
owa_util.mime_header ('text/html', FALSE);
owa_cookie.send ('APPID', TO_CHAR (c_wgn_rec.application_id), SYSDATE + 1);
owa_util.http_header_close;

However, when I call the dump_page procedure:

begin
    procedure_that_sets_cookie;
    dump_page;
end;

I get the following error:

ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SYS.OWA_UTIL", line 323
ORA-06512: at "SYS.OWA_UTIL", line 364
ORA-06512: at "PROCEDURE_THAT_SETS_COOKIE", line 179
ORA-06512: at "PROCEDURE_THAT_SETS_COOKIE", line 147
ORA-06512: at line 2

Line 179 is the "--" line before the "owa_util.mime_header..." line above.  Is
there a work-around?

          Followup:

declare
    nm  owa.vc_arr;
    vl  owa.vc_arr;
begin
    nm(1) := 'DUMMY_JUST_TO_SET_UP_OWA_UTIL';
    vl(1) := 'WHATEVER';
    owa.init_cgi_env( nm.count, nm, vl );
end;
/

you need to initialize the "cgi-environment".  if your procedure uses an
OWA_UTIL calls, you need to do this.

June 09, 2003 --
      Thanks for the question regarding "constraint state", version 9.20

      You Asked
I have hard time understanding the usage of 'disable validate'. Could you help
me understand its usefulness? Thanks.

      and we said...
it lets a constraint be "disabled" but will prevent the modification of data
while it is still in the validated mode:

ops$tkyte@ORA920> insert into t values ( 0 );
insert into t values ( 0 )
*
ERROR at line 1:
ORA-25128: No insert/update/delete on table with constraint (OPS$TKYTE.X_CHECK)
disabled and validated

It's "usefulness" is limited to the case where

a) you have a read only set of data, like in a data warehouse
b) you are using partitioning, you load data by partition (eg: no dml in the
other partitions)
c) you load a table with new data and use exchange partition to "load" the real
table
d) some column(s) must be unique in the data, but the need or desire for an
index on such a column(s) is not there.  You would like to save the space of the
index, yet be assured uniqueness.

In this case, the contraint will be "there", the exchange partition will scan
the data and ensure the data is unique, but there won't be any persistent index.
 Hence the DW data is "clean", known to be "clean" but you don't have an index
to enforce it.

Review &
      Followup

      Rating:
      5
Thanks  June 08, 2003
Reviewer:
      Reader

      Rating: 4
How does oracle
      do it?  June 08, 2003
Reviewer:  Reader

--> it lets a constraint be "disabled" but will prevent the modification of data
while it is still in the validated mode.

Tom, could you explain how oracle does it when the constraint is in disabled
state and also index is not there to enforce the validity of data? Which
mechanism prevents DML? Thanks.

          Followup:

ops$tkyte@ORA920> insert into t values ( 0 );
insert into t values ( 0 )
*
ERROR at line 1:
ORA-25128: No insert/update/delete on table with constraint (OPS$TKYTE.X_CHECK)
disabled and validated

the database just says "no, you cannot do that"

that is how.  NO data, not even conforming data, can be added.

      Rating:
      5
  June 09, 2003
Reviewer:
      A reader

June 09, 2003 Yves -- Thanks for the question regarding "Number
      showing up in sqlplus as ##########", version 8.1.7

      You Asked
Hi Tom.

I am querying the number column from a table in sqlplus and numbers are
appearing as ##########.  When I do a to_number on this field, I am able to see
the actual number.  This is only appearing with one particular integer field.
Do you know why this happens?  Workaround?

I have tried dropping that column and recreating it, and then insert new number
into it, it still happens.  Note that when I create a new table with an integer
field, it does not happen there.

Thanks.

      and we said...
Sounds like your SCRIPT is setting an inappropriate format for that column.
Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 12345 );
1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

         X
----------
     12345

no format in place, no problem...

ops$tkyte@ORA817DEV.US.ORACLE.COM> column x format a2
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

         X
----------
##########

attempt to use a character A format for a number -- problem...

ops$tkyte@ORA817DEV.US.ORACLE.COM> column x format 999
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

   X
----
####

attempt to use a format that is too small for the number -- problem...

ops$tkyte@ORA817DEV.US.ORACLE.COM> column x format 99999
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

     X
------
 12345

use a format that is appropriate, no problem (or use NO format whatsoever)

If you didn't set a format, look for a login.sql that is.

To see that this is (or is not) the problem issue:

column COLUMN_NAME clear
select COLUMN_NAME from TABLE where rownum <= 5;

that'll show the first 5 values of the number without ######'s

Review &
      Followup

      Rating: 5
Right
      on  March 18, 2002
Reviewer:  Yves

The glogin.sql that comes with the software had an incorrect format set.

Thanks.

      Rating: 5
is there a way
      to set the column alias with the column format command  June
      08, 2003
Reviewer:  A reader  from ca

something like
column column_name format a10 alias sc

so that all columns with column_name will be displayed as "sc" in all select
stmts?

          Followup:

ops$tkyte@ORA920> column empno heading sc
ops$tkyte@ORA920> select empno, ename from emp;

        sc ENAME
---------- ----------
      7369 SMITH
      7499 ALLEN
      7521 WARD
      7566 JONES
      7654 MARTIN
      7698 BLAKE
      7782 CLARK
      7788 SCOTT
      7839 KING
      7844 TURNER
      7876 ADAMS
      7900 JAMES
      7902 FORD
      7934 MILLER

14 rows selected.

      Rating:
      4
  June 09, 2003
Reviewer:  Bo
       from Washington, DC
How about
select empno sc, ename from emp;

      Rating: 5
exactly what i
      was looking for  June 09, 2003
Reviewer:
      A reader
i know about the  table alias in select- i wanted to find out
if there was a way to do it without that..

      Rating: 5
Good
      Info  June 09, 2003
Reviewer:  Riaz
       from Pakistan
You also do that by
SQL> set numwidth (possible max length of your column)

June 09, 2003 TRIPTI -- Thanks for the question regarding
      "Monitoring of programs in Oracle", version Oracle 8.0.6

      You Asked
Hi Tom,

We at time face response problem in our System. My requirement
is to find out list of programs (meaning
procedure/function/forms name) not just Fgen50 etc...

Is there anyway by which I can find out list of all programs
running at any point of time.

Best regards

Tripti Roy

      and we said...
Not unless the programs have been instruemented to provide such
information.

Your applications can utilize the
dbms_application_info.set_client_info() stored procedure to
place into the v$session table identifying information about
themselves.  This does not (cannot) happen automatically --
rather the code must call this procedure to provide this
information.

We can see the SQL that is executing at any given point in time
using a script in SQLPlus such as:

column status format a10
set feedback off
set serveroutput on

select username, sid, serial#, process, status
from v$session
where username is not null
/

column username format a20
column sql_text format a55 word_wrapped

set serveroutput on size 1000000
declare
    x number;
procedure p ( p_str in varchar2 )
is
   l_str   long := p_str;
begin
   loop
      exit when l_str is null;
      dbms_output.put_line( substr( l_str, 1, 250 ) );
      l_str := substr( l_str, 251 );
   end loop;
end;
begin
    for x in
    ( select username||'('||sid||','||serial#||
                ') ospid = ' ||  process ||
                ' program = ' || program username,
             to_char(LOGON_TIME,' Day HH24:MI') logon_time,
             to_char(sysdate,' Day HH24:MI') current_time,
             sql_address, LAST_CALL_ET
        from v$session
       where status = 'ACTIVE'
         and rawtohex(sql_address) <> '00'
         and username is not null order by last_call_et )
    loop
        dbms_output.put_line( '--------------------' );
        dbms_output.put_line( x.username );
        dbms_output.put_line( x.logon_time || ' ' ||
                          x.current_time||
                          ' last et = ' ||
                          x.LAST_CALL_ET);
        for y in ( select sql_text
                     from v$sqltext_with_newlines
                    where address = x.sql_address
                      order by piece )
        loop
            p( y.sql_text );
        end loop;
    end loop;
end;
/

column username format a15 word_wrapped
column module format a15 word_wrapped
column action format a15 word_wrapped
column client_info format a30 word_wrapped

select username||'('||sid||','||serial#||')' username,
       module,
       action,
       client_info
from v$session
where module||action||client_info is not null;
Review &
      Followup

      Rating: 5
Is it possible
      to modify client_info from another session?  August 21, 2002

Reviewer:  Paul Druker  from Fairfax, VA USA

Supplied PL/SQL Packages Reference 9.2 says in the description of
DBMS_APPLICATION_INFO: "CLIENT_INFO is readable and writable by any user. For
storing secured application attributes, you can use the application
context feature."
I understand that anybody with access to V$SESSION can read this info, how could
someone modify it for another session? Can it be done via
DBMS_SESSION.SET_CONTEXT procedure?

          Followup:

You have to be in the session to set it for that session.

You cannot set it from afar.  When they say "is readable and writeable by any
user" -- they implied "the row in v$session for your session is readable and
writeable by the currently logged in user"

      Rating: 5
An example
      needed  June 09, 2003
Reviewer:  robert
       from SIN
Can you provide an example to set the client_info using the
dbms_application_info.set_client_info() ?.

We have an MIS application that authenticates using end user via application
account (store in app table) and then connect to the DB using only one Oracle
account (e.g APP), we would like to put the end user id into this client_info
for the ease of tracing end user's session. Do you think this method is workable
?

Thank You

          Followup:

you mean

begin dbms_application_info.set_client_info( ? ); end;

?????  it is after all just a simple stored procedure call with one input?

June 09, 2003 Hoan -- Thanks for the question regarding
      "Retrieving data from blobs into a file", version 8.1.7

      You Asked
I don't have "expert one on one performance by design" book. so you can sent to
me the code sample: Pro*c - writes blobs to files in an external procedure.
Thank you very much.
Dong Van Hoan

      and we said...
without the book "Expert One on One Oracle", you are on your own with the source
code

have at it:

http://asktom.oracle.com/~tkyte/lobtofile

"as is", no warranty implied -- sort of on your own with it.

Review &
      Followup

      Rating: 4
Retrieving data
      from blobs into a file  June 11, 2003
Reviewer:
      Hoan.Dong  from VietNam
Thank you for your reply.
i had downloaded 3 file:lobtofile.pc, lobtofile.sql, makefile.NT. I run it but
still have err.
I have some question:
1) you can show me to run 3 scripts.
2)  i want to retrieve the lob image from DB and write to external file (exp:
*.jpg, *.tif ...) using stored procedure.
3) i don't know the analyzed table with lob columns.
   I have table TEMP1(id, A, B, C).  A, B, C is lob columns. The image size is
40Kb. i insert 800.000 records into table. The database size is 16G.
   This is a way to increase performance tuning?
Thank you very much!
Hoan

June 09, 2003 Dianna -- Thanks for the question regarding
      "Proxy Authentication vs. Application Context", version 9.2.0

      You Asked
Tom,
After review of AskTom, I determined that we trying to do the same thing as a
lot of others out there. We need to connect via a common userid/password but our
applications need to know who the user really is so that our fine grained access
control will work. I was going to write a common JSP routine that a performed
proxy authentication connection. For our purposes, this routine needs to be
callable from both PL/SQL and other JSPs. Not really being a Java programmer, I
was looking for a detailed example for all what was required. Does the JSP
actually perform the connection and pass back a pointer to the connection to
other JSPs? For the PL/SQL, don't we really just need the connection to be
performed and from then on all database accesses will be as if the real user was
logged in?

Then, I discovered an example on AskTom when you created an application context
and did what you called a 'set user' like in UNIX. This seems to be exactly what
we need. Can you explain why you would use one approach over the other and also
if the application context approach would allow the complete FGAC processing
that I discussed previously? I would really rather write the PL/SQL over the
Java if it performs the same job.

Thanks,
Dianna

      and we said...
it doesn't make sense to talk about this in context of PLSQL since PLSQL is in
the database and if you are running PLSQL you are already connected, hence a
common 'connection' routine for plsql just doesn't make sense (you are already
connected after all).

connecting is a client thing, an outside the database thing, only relevant to
things outside the database.  to plsql -- there is no such thing as "connecting"

You would use ntier proxy authetication so you can use native features like

o auditing
o roles
o grants
o FGAC (easier)

and other database features that require "select user from dual" to return
something meaningful.

The choice is NOT plsql vs java here, it is "how shall my java connect to the
database".
Review & Followup

      Rating: 1
Proxy
      Authentication vs. Application Context  June 09, 2003

Reviewer:  Dianna  from Florida
Tom,
You are missing the point. I know that I am connected using PL/SQL but I am
connected with a common identity that will not allow me to uniquely identify my
user to the database in order to utilize the security policies. I must either 1)
become the unique user without knowing their password [Can I do this with
application context? If so, will FGAC work without any special code in the
application?] or 2) perform a proxy connection in order to get a new connection.

Thanks,
Dianna

          Followup:

You missed the point maybe?

the routine doesn't need to be callable from PLSQL -- you see, PLSQL knows NOT
the identity.  The routine to "set the identity" needs to be called by the
client.

The client can use an APPLICATION context to set the identity.  Anything can
read the context and do what it will.

If you use NTIER proxy authentication -- then other database features like
grants, roles, auditing, et.al.  will all work "as is"

      Rating: 5
dianna follow
      the link  June 09, 2003
Reviewer:  bob
       from tx
for a sample of oci connection pooling and proxy authentication using the oci
jdbc driver see:

Middle-tier authentication allows a user(proxy) to act as proxy for other
user(client). Now the client can connect to the database using the already
authenticated credentials of proxy user. The proxy user can also restrict the
access of client by specifying the roles with which the client can connect. By
this way the client doesn't have to divulge its password to the middle-tier.

http://otn.oracle.com/sample_code/tech/java/sqlj_jdbc/files/9i_jdbc/OCIMidAuthSample/Readme.html

This is proxy authentication.. not application contexts, but should potentially
be a starting point for you.

June 09, 2003 -- Thanks for the question regarding "sysoper?",
      version 9.2.0

      You Asked
Why does oracle have sysoper priv? I have never seen it used in production
environment. What is it used for? Is there anything special with sysoper priv?
Thanks.

      and we said...
it allows you to have an account that can startup and shutdown the database but
that does not have all of the other privileges (like the ability to read all
data in every table).

A truly security concious site would not use SYSDBA
They would not use CONNECT, RESOURCE or DBA either for that matter.

They would grant SYSOPER only to those accounts that truly needed it.

They would reserve SYSDBA only for the "oracle account" (eg: you have to log in
as Oracle to the OS in order to get it)

They would create their own DBA roles with limited privs -- a daytime_dba,
nighttime_dba, jr_dba, sr_dba, etc -- with only the privs needed to perform the
job.

Many sites I see just grant DBA and SYSDBA to anyone with DBA in their job title
unnecessarily.
Review & Followup

      Rating:
      5
Thanks  June 08, 2003
Reviewer:
      Reader
So, if I want to restrict privs among the 'DBAs', then I can grant sysoper to a
'newly' joined dba until I get confidence. Because, connecting as sysoper, one
cannot view or drop any table in any schema. after connecting as sysoper, 'show
user' results, 'PUBLIC'. any specific reason?

SQL> connect a as sysoper;
Enter password: *
Connected.
SQL> select * from session_privs;

PRIVILEGE
----------------------------------------
CREATE SESSION
RESTRICTED SESSION
SYSOPER

SQL> select * from scott.emp;
select * from scott.emp
                    *
ERROR at line 1:
ORA-00942: table or view does not exist

SQL> drop table scott.x;
drop table scott.x
                 *
ERROR at line 1:
ORA-00942: table or view does not exist

SQL> show user
USER is "PUBLIC"  <------------------------

THANKS.

          Followup:

same reason that after you connect as SYSDBA it shows SYS.

you can only do things PUBLIC can do as sysoper....

      Rating:
      4
RemotelyConnecting as oper or dba  June 09, 2003

Reviewer:  Reaz  from Dhaka,Bangladesh
what would be the shortest possible config for connecting from remote client to
the server as sysdba or sysoper under Win & 8.1.7 using sqlplus.

Thanks.
Reaz/Dhaka

          Followup:

set up the remote_login_passwordfile

      Rating: 4
Why Oracle has
      SYS and SYSTEM users?  June 09, 2003
Reviewer:
      Tony  from India
Why Oracle has SYS and SYSTEM users?. Anyone of the users not enough?  Will
there be only one user(either SYS or SYSTEM) in future?

          Followup:

SYSTEM is a container used to hold extra stuff.
SYS is more powerful.

they are not in any way shape or form "equivalent" to eachother.

      Rating: 3
What about
      RMAN, which requires SYSDBA?  June 09, 2003

Reviewer:  A reader

          Followup:

how many people need that?  about 1 right?  could that be your oracle account?
yes.

      Rating: 3
Operators run
      backups using RMAN  June 09, 2003
Reviewer:
      A reader

          Followup:

so?

      Rating: 5
So we have the
      operators log in as oracle?  June 09, 2003

Reviewer:  A reader

          Followup:

you run the job as oracle.  operators, they push a button to run a job.  or
better yet, they sit there and watch the SCHEDULED job run from a console.

June 09, 2003 Bob -- Thanks for the question regarding "One
      Oracle home or multiple Oracle homes?", version 9.0.1

      You Asked
On one server we have 4 instances of oracle, all running the same versions
(9.0.1.4). 2 are used as regular oracle DBs and 2 are used as portal DBs. It's
been suggested that to simplify upgrades that we make all the instances use a
single Oracle Home, and share binaries etc. That way one upgrade affects all
DBs. I've not read this anywhere but is there any advantage or disadvantage to
doing this? Is there a recommended configuration?
Thanks
 Bob

      and we said...
if your goal is to keep all 4 at the same level (me, I would have but ONE
database -- tasted great, less filling -- consumes less resources, easier to
manage, easier to tune) then a single Oracle_home is the right way to go.

The only time I use >1 Oracle home is when I want more than 1 Oracle version --
else it is a single oracle home (and a single instance+database)
Review &
      Followup

      Rating: 5
all in one
      database  June 06, 2003
Reviewer:
      A reader
On our production system, we have a staging database, a warehouse, and an oltp
database. We may also be adding a data mart. Would you really roll all of those
databases into one instance?

          Followup:

I would most certainly not have them on the same machine if there were to be in
separate instances.

If they were to be on the same machine, absolutely they would be all in one.

      Rating: 5
Unfortunately
      there is only one machine  June 09, 2003
Reviewer:
      A reader
Since there is only one production machine, all instances at our shop must
reside on that one machine. We have a mix of oltp, staging, and dw instances.
Due to the differences in init.ora parameters, how could we justify combining
them all?

          Followup:

because performance will improve?

because it will be easier to manage?

because it is the only right thing?

      Rating: 5
How do you
      account for the differences in init.ora parameters?  June
      09, 2003
Reviewer:  A reader

          Followup:

give me a "for example".....

if it is like "sort_area_size", you should use workarea_size_policy = auto in 9i
-- especially in a mixed workload.

if it is like "buffer cache sizes", well -- you don't need to worry about that,
you just gave yourself a memory upgrade by shutting down the redundant
instances.

So, give me a concrete example and we'll see what we can do....

      Rating: 3
Version
      Restrictions  June 09, 2003
Reviewer:  Tim
      Hall  from UK
If you want to run portal then you need to have infrastructure. Infrastructure
currently runs against 9.0.X (not sure about the exact subversion). If you want
your client database to run at 9.2.X then you need to have multitple
installations due to the restriction on portal. I believe the 9.0.4 release of
9iAS will use 9.2.X which will solve this issue.

Easy solution is to buy more machines :)

          Followup:

somewhere else on this site i have a discussion on this type of topic.

There are two types of databases:

o those we build our (our being the universal "us" -- you and me, not "oracle")
  apps in
o those that come with packaged applications.

For those that we build our own applications in (like asktom for example, an
application built internally, custom development) -- I like to keep it down to
as few instances as humanly possible.  Like one.  In this single database that
houses asktom, we also house promedmail.org, the oai database and a couple of
other applications.  When we upgrade -- it is one for all, all for one (we all
go or none of us do)

For applications, of what I would argue the infrastructure database is part of
the portal install, much like there is an oracle database behind HR or CRM or
ERP, you will in general run an instance in support of that application.  That
would hold true for SAP, Siebel, whatever as well -- the database is sort of a
black box that goes hand in hand with those application infrastructures.

      Rating:
      5
Parameters  June 09, 2003
Reviewer:
      A reader
db_block_size
db_file_multiblock_read_count
optimizer_index_caching
optimizer_index_cost_adj
optimizer_percent_parallel

          Followup:

you do have multi-block size support if you feel that is relevant, however I
think you would find 8k to be an excellent all around block size.

db_file_multiblock_read_count is settable at the session level HOWEVER, since
your OLTP won't be full scanning and "that which is good for peter is good for
paul", one size really does fit all there.

the last one is obsolete.

the other two are settable at the session level, also in 9iR2, using SYSTEM
STATISTCS may more or less remove the need for them all together.

      Rating:
      5
Thanks!  June 09, 2003
Reviewer:
      A reader

      Rating: 5
So, Should we
      gather system stats on regular basis as done for other
      schemas?  June 09, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Should we call DBMS_STATS.gather_system_stats on a regular basis?
Thanks,

          Followup:

you should gather system stats during your "typical" processing times.

You could consider switching stats in and out during the day (you probably have
different load characteristics at night then during the day).

You should regather from time to time -- as your performance characteristics
will change over time as well.

June 09, 2003 --
      Thanks for the question regarding "powerful GUI for adminstration",
      version 9.2.0

      You Asked
Tom,

A non-technical question for you, sometime back there was conversation between
some of my friends,.... that oracle is bringing powerful GUI day-by-day for
database adminstration and so there may not be much work for DBA's in the
future, can you please share your thought's on this.

And Oracle 10i has been released?

Thanks for your consideration and Time.

      and we said...
I'll believe it the day after I see it.

Tell me -- where in the history of computers has a GUI put and entire suite of
people out of a job yet (I'd be interested in the followups on this one)?

I mean, Wordperfect and all put secretaries out of jobs right?  We don't have
them anymore (oopps -- we do, they just evolved over time to do tons of other
stuff).

The reason for the gui?  the job of the DBA is becoming so complex that without
something to help them get along -- many would not be able to function.  The job
is actually becoming more complex over time -- so many things must be made
easier, more manageable.

Think about it.  10 years ago, what was a big Oracle database?  1993...  It was
under a terabyte.

5 years ago, 1998, what was really big then?  It was single digit terabytes.

2003, really big is somewhere around 50-75 terabytes.

2008, what do you think "really big" will be?

10 years ago, how big was a big schema?
5 years ago?
today?  (betcha "small" schemas today are many times larger then a big schema
from 10 years ago)

10 years ago, how big was "a lot of users"
5 years ago (this is way out of wack really -- this internet thing came along
and we had orderS (with a capital S) magnitude increase in usage...
today?
tomorrow?

10 years ago, how much downtime was permitted?
5 years ago (same as the user base...)
today?
tomorrow?

I would not worry about the future of the DBA, as long as you are the DBA that
is willing to change, evolve, learn, adapt.  If you want to do things the way
you did with Oracle v6, you are toast.

Don't fear the gui too much.  Professionally managed data centers will be
staffed by professionals.  I was interview a Sybase DBA this week.  He described
how he became a DBA (the other guy quit -- they gave this guy his job, never
mind this guy was a software developer out of college in the software TESTING
position).  He relied on gui's for everything.

I asked him "so, is your company serious about data mgmt".  "oh yes, 100%, very
serious -- we have the <branch of military> as our customer, very serious".  I
ask him "why in the world would they take a testing guy, with no experience, and
call him DBA then", "well, it was pretty easy.  tools and all you know".

I ask him "so, how much data have you lost".  "hah hah, lots, yes, quite a few
times.  Stupid stuff wouldn't come back from backups yadda yadda yadda"

If a company is serious about their data, they'll be DBA's at that company.  If
they are not, it is OK you are not working for them cause 30% or more of them
will be out of business in a year after a catastrophic loss of data.

10i is in the future still.
Review &
      Followup

      Rating:
      5
  June 07, 2003
Reviewer:  JG

>>10i is in the future still.

Unless you are one of the lucky few who have got their grubby mits on a beta
version copy through the oracle partner network :-)

      Rating: 5
10? Beta
      Program  June 08, 2003
Reviewer:  Tyler D.
      Muth  from Reston, VA
If you are really interested, you might check out:

http://marvel.oracle.com/pls/otn/f?p=4550:20
You can read the other criteria, but you should at least be using or considering
Project Marvel:

http://marvel.oracle.com/
I'm not sure if there are still slots open, but it's worth a try.

      Rating: 3
DBA with GUI
        June 08, 2003
Reviewer:  Reaz  from
      Dhaka
Myself started database admin with OEM. One fine morning found without the idea
of playing with db in black screen I am nowhere.

My burning opinion is doesn't matter how good a GUI is coming in future, without
extensive DBA knowledge we can't do anything excepting some fancy features
maybe.

      Rating: 5
10i and
      tablespaces  June 08, 2003
Reviewer:
      A reader
someone from Nokia said tablespaces are disappearing in 10i... any beta tester
can confirm this?!

          Followup:

no, they are definitely still there.

      Rating: 3
9i/Linux GUI
      tools  June 09, 2003
Reviewer:  Fan
       from PA
Hi, Tom,
I just installed 9.2 on Linux at home...

I was "expecting/wishing" to see an Oracle entry in the menu
like on Windoz. (ha !). Surely not...

So far only found  netca (tns configuration) gui...
OEM kind of gives me the jitters...have yet to tackle it.
Can you point out to me name of any other gui's
(like "DBA Studio") .

Thanks

          Followup:

"dba studio" is just part of OEM.

just run

$ oemapp console

and connect standalone.

      Rating: 4
oemapp in
      solaris?  June 09, 2003
Reviewer:
      A reader
Tom,

where can i find this oemapp in solaris? i tried this ..

HYD280R:oracle:TSTETMCT: find ./ -name oem*
./product/8.1.5/bin/oemevent
./product/8.1.5/bin/oemeventO
./product/8.1.5/network/doc/oemtraps.v1
./product/8.1.5/network/agent/reports/web/oem.gif
./oraInventory/Actions/OEMRegistry/1.5.5/1/oemRegistryActions.jar

i could not find oemapp.

          Followup:

don't know what it was called in 815 so long ago...  could be that you do not
have it installed, or, it was called something else.  too long ago, I didn't
really use it back then either.

oemapp console is for 9i.  Maybe someone out there reading this using 815 can
say...

      Rating: 5
GUI
      SHMOOEY!  June 09, 2003
Reviewer:  Dana Day
       from Tempe, AZ USA
I have yet to find a gui that can do tasks faster or more precisely than a
knowledgeable command line process.
That being said, I don't have thousands of scripts available to re-duplicate
every single gui process.
I have a few dozen I use constantly.
If I need to do something quick, ad-hoc, and once only, I will use a gui and pay
attention to the sql it submits to the database. If I do the same task often
enough in the gui, I will create a script for the same purpose and stop relying
on the gui. What happens when you are away from your computer, and only have
access to telnet/cmdshell? What does a gui do for you then?

June 09, 2003 David -- Thanks for the question regarding "Data
      Migration Process", version 9.2

      You Asked
Tom -

I am working on a project to upgrade some SQLServer/VB legacy applications to a
new 3-tier J2EE/Oracle application. My specific responsibilities is the data
migration process.

Much of the talk surrounding ETL tools today is geared toward data warehousing.
A data migration process is a little different than this in that the target is
not a data warehouse but rather a new operational system. Some aspects are
similar (e.g., data tranformation, quality, source integration) while other
aspects differ (e.g., transaction ids and customer numbers can still be very
important in a new operational system whereas they might not be in the data
warehouse; Also, a cut-over strategy and running systems in parallel is complex
and important.) Anyway, to my question. Do you know of any books or
documentation on data migration methodologies? It isn't a new subject, but I'm
having trouble finding anything that specifically addresses this complex
process. Any info would be appreciated.

Also, I don't think Oracle's migration tool kit is a solution based on my
assumption that it is used to convert a database from SQLServer to Oracle. My
data migration process is a little different in that business process is
changing based on new gathered requirements (which will require data
transformation) and multiple data sources need to be combined.

So again, my only real question is about documented methodologies and/or
processes.

Thanks,
Dave

      and we said...
see, this doesn't sound like a "data migration" to me.

Forget for a moment that you are going from SQLServer to Oracle.

This sounds like "we were running version 1.0 on Solaris, we are going to run
version 2.0 on Windows"

You are moving the data from place A to place B

You are going to make fundemental changes (running in parallel doesn't sound
"wise", it sounds more like a cutover to me) -- resulting in version 2.0.

The same challenges anyone would have in moving from version 1 to version 2 of
in house developed software is what you are facing.

The only way I know to face that challenge is via change control, documentation,
testing.

I'm not familar with any titles that deal directly with this in your sense --
but I'll "publish" this question and we'll see what sort of followups we get
from others out there that have.
Review &
      Followup

      Rating:
      3
Thanks  June 09, 2003
Reviewer:
      David  from MA, USA
Yes, cutover is more appropriate (I would like to avoid any parallel activity.)

And yes, its not really a technology issue, its a data and process issue.

And yes, I agree change control, documentation, testing are imperative. I am
just looking for best practices to address specific issues in moving data from
old legacy systems to a new system.

Thank you for posting it.

Thanks,
Dave

June 09, 2003 Shrek -- Thanks for the question regarding
      "Migration (or is it an upgrade?)", version 8.1.7

      You Asked
I want to upgrade an 8.0 database to 8.1.7. on the same machine Linux redhat
7.x. Instead of export/import that I have done before, this is what I plan to
try.

However, I am not 100% sure if it will work, which is why I patiently waited for
asktom.oracle.com to let me in.

1) Cold backup the 8.0.x database
2) Create a new 8.1.7 database under a new $ORACLE_HOME but on the same machine
3) Alter database write control file to trace
3) Modify the datafile names in the control files to their new locations.
4) Do a restore after copying the old data files to their new locations.

a) Is the above strategy likely to work?
b) Is there a better strategy? if so, what is it?
c) Are Analytic Functions available in 8.1.7 Standard Edition? Please say yes!
because I am already addicted to them.
d) I was able to get a lot of info on complete migrations but not on upgrades.
Would you call this a migration or upgrade?

Thanks Tom

      and we said...
Instead of waiting, you should have read:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a86632/toc.htm
which is the document that comes with each release of the database and describes
exactly how to upgrade or migration from release X.Y to A.B.

a) read the doc, it'll tell you exactly what to do.  step #1 is the only one you
need to do in your list.

b) yes, read the doc ;)

c) Analytics are available on Enterprise only.

d)
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a86632/migupgra.htm#13277
tells you this is an upgrade.

Review &
      Followup

      Rating: 5
Once again,
      Thank you Tom.   July 21, 2002
Reviewer:
      Shrek

      Rating: 5
migration from
      existing 8i to 9i?  June 09, 2003
Reviewer:
      Reader
Tom, could you please show me a link which provides me guidelines and steps to
migrate my existing 8i production database to 9.2.0. Thanks.

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-MIG

      Rating:
      5
Thanks  June 09, 2003
Reviewer:
      Reader

June 09, 2003 vc -- Thanks for the question regarding "INDEX
      RANGE SCAN vs. INDEX FAST FULL SCAN for _cached_ blocks", version 9.2.0.1

      You Asked
Dear Tom,

I am curious as to why an index range scan is so much slower in comparison to
the index fast full scan when the index blocks are already in cache (no physical
IO).  I do realize that index blocks are a more complex entity to process but
still...

Please consider this:

Schema:

create table(
  employeeid int not null,
  skillid int,
  primary key(employeeid,  skillid) organization index;

Generate 33630 rows of data.

Case 1
======
SELECT count(*) from (
   select employeeid  from test1  /* The predicate to force an index range scan
*/
     where employeeid > 1
        group by employeeid
   having count(*) = 4
                and sum((case when skillid in (2,3,4,5) then 1 else 0 end)) = 4)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute     10      0.00       0.00          0          0          0           0
Fetch       10      7.00       7.01          0       5630          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       21      7.00       7.01          0       5630          0          10

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  SORT AGGREGATE
  75890   VIEW
  75890    FILTER
1000000     SORT GROUP BY NOSORT
3363020      INDEX RANGE SCAN SYS_IOT_TOP_40693 (object id 40694)

********************************************************************************

Case 2
======
SELECT count(*) from (
   select employeeid  from test1
        group by employeeid
   having count(*) = 4
                and sum((case when skillid in (2,3,4,5) then 1 else 0 end)) = 4)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute     10      0.01       0.00          0          0          0           0
Fetch       10      6.08       6.10          0       5690          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       21      6.09       6.10          0       5690          0          10

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  SORT AGGREGATE
  75890   VIEW
  75890    FILTER
1000000     SORT GROUP BY
3363020      INDEX FAST FULL SCAN SYS_IOT_TOP_40693 (object id 40694)

As you can see,  the number of LIOs are,  not surprisingly, the same in both
cases.  Also,  in Case 2 Oracle has to perform a sort and yet the result comes
faster,  610 ms vs 700 ms in Case 1.

Thank you.

      and we said...
to access each block one at a time, or to request them N at a time...

which requires a shorter code path?  You can ask for a book one page at a time,
or 16 pages at a time.  You get the same number of pages - but which one will
you get faster?

It is just a totally different code path for the two operations.  One is "brute
force, we are going to read them all in any old order", the other is "we'll read
the first leaf block, get a pointer, goto the next, get a pointer, goto the
next" and so on.
Review & Followup

      Rating:
      3
  June 09, 2003
Reviewer:  vc

Thank you,  Tom.

I understood as much but still was a little surprised at the difference.

June 09, 2003 Nag -- Thanks for the question regarding "How to
      test the impact of dropping a column ... before hand...", version 8.1.7

      You Asked
Tom

We are gonna change the name of a column.

Now this column is used in various packages.

Now how can I test before hand which packages, procedures, functions get
effected if I change the name of this column.

What is the best way to do the above.

Thanks

NAG

      and we said...
DBA_DEPENDENCIES

will tell you which objects have static references to this TABLE.  They will all
be affected by dropping this column.  Dynamic sql -- you'll find that as you hit
it.

I'm always curious about this -- who cares what the name is. If the end users
are exposed to this column -- you can simply give them a view that hides the
"bad name" with a "good name".  I've never felt compelled to rename a column --
never.  Just makes more work for you.
Review &
      Followup

      Rating: 1
Dropping a
      column  December 03, 2002
Reviewer:  Scott
      Berg  from Research Triangle Park, NC USA
I agree with you on identifying the dependencies.  By the nature of the question
I thought it was referring to "how long" will it take.  Earlier this year I did
the following to a table

alter table sess drop (
mrkt_res_id,
other_reason,
ani,
dns,
call_summary_code,
no_contact_reason_code,
hcp_id,
campaign_name,
outbound_start_time,
hangup_time,
record_id,
ivr_info_id,
master_call_id,
campaign_target_id,
cycle_number,
attempt_number,
sched_time
)
checkpoint 5000
/

The table had ~1200000 rows.  The process took over an hour to complete.  Is
there a way to know how much elapsed time a column drop will take before the
command is issued?

          Followup:

wow, how did you get from the straightforward question:

"Now how can I test before hand which packages, procedures, functions get
effected if I change the name of this column."

to "how long will it take".  Must be a mind reader, cause I sure didn't get that
from the question ;)

Anyway, a drop column is going to rewrite the entire table -- the entire table,
block by block.  It will go through and clean up every block -- putting blocks
onto the freelist as they fall under their pctused and such.

It will be a function of how big your table is, nothing more, nothing less.

If time was of the essence "set unused" would have been the way to do.  It
wouldn't reclaim space (but then again, do you REALLY want every block in your
table on the freelist).  to get rid of so much data I would have:

o set unused
o alter table move (nologging)
o alter index rebuild (nologging, in parallel if I wanted)

that would reclaim the space lots better (packed the blocks up -- nothing on the
freelist, just less blocks in the table) and would have been much faster.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6604243175081
where we just had a discussion on this not too long ago.

Let me ask you this -- when you did it in TEST, how long did it take there??
hmm.

      Rating: 4
Same for
      LOBS?  December 03, 2002
Reviewer:  Jeremy
      Smith  from Salt Lake City, UT
You said:
"Anyway, a drop column is going to rewrite the entire table -- the entire table,
block by block.  It will go through and clean up every block -- putting blocks
onto the freelist as they fall under their pctused and such."

Is this the same for LOBS not stored inline, or will the database just get rid
of the storage allocated for all the lobs in that column?  I'm curious because
dropping the column, and freeing up the space taken by large object might be
more "worth it" vs. setting it unused than if it were an varchar2 column or
something.

          Followup:

It would be infintely better to follow the advice of Connor on the above
referenced link.

Rather the blast freelists -- use alter MOVE to efficiently reclaim space.  set
unused -- alter move (the table, the lob, the whatever)

      Rating:
      4
Dependencies...  June 09, 2003
Reviewer:
      Kashif  from Houston, TX
Hi Tom,

Is there really no method of fixing dynamic SQL references to a column before
dropping that column? I guess we could search the shared pool for references to
that column and maybe try and go from there. Doth that maketh sense? Thanks.

Kashif

          Followup:

there is a method, it is called development and test -- coupled with a dash of
configuration management.

      Rating: 5
Dash of
      --  June 09, 2003
Reviewer:  Dana Day
       from Tempe, AZ USA
A huge amount of configuration management coupled with doses of development and
test in my opinion.
I have seen many cases where development and test were pro-forma pass-throughs.
Without the framework of a solid configuration management you are lost.
Development and test only provide the platform against which to validate test
cases generated by configuration/environment management.

          Followup:

(yes, a bit tongue in cheek i was ;)

      Rating: 3
Configuration
      management.  June 09, 2003
Reviewer:  Kashif
       from Houston, TX
Hi Tom,

I'm not clear on what you mean by configuration management in this context.
Thanks for any feedback.

Kashif

          Followup:

uh oh....

configuration management is the fine (lost?) art of keeping things under
control.  You would know what code would be possibly affected because you would
have a database that helps you manage the dependencies between who uses what --
where, when and why.  some people call it "change management" software.

you would typically be using a tool to help you keep track of stuff.

      Rating:
      5
  June 09, 2003
Reviewer:  Prasad
       from San Francisco,CA
Tom,
This following select stmt is run  very often .
looking at the tkprof . do you think that it can get better if we use bind
variable.

Thanks
Prasad

SELECT RV_MEANING, RV_LOW_VALUE
FROM
 CG_REF_CODES WHERE RV_DOMAIN = 'BLOCK BUSTER' ORDER BY 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0         17          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0         17          0          10

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 47

          Followup:

does 'BLOCK BUSTER' change it's value?  If not, no.  If it does, yes -- most
likely.

you will get much better performance by parsing ONE time and executing over and
over and over.

      Rating:
      5
  June 09, 2003
Reviewer:
      A reader
Yes , it does.

SQL> select count(distinct rv_domain) from cg_ref_codes;

COUNT(DISTINCTRV_DOMAIN)
------------------------
                     150

and why the parse shows as 2 . as it is supposed to be 1 hard parse.

Please advise.

          Followup:

the parse shows two because you parsed it twice.  probably using ODBC or some
layer that does that.

if the value passed to the query changes, it would behoove you to bind it, to
reduce the hard parses of this query down to 1 from 150

      Rating: 3
Column-level
      dependencies  June 10, 2003
Reviewer:  Ed
       from UK
I use this little script to look for strings in code (such as column names).  Of
course it isn't perfect, it only works for code (not views etc) and only for
code in the current schema.  Also, if you have parameters as the column name
prefixed by "p_", they'll turn up.

select distinct name,line,
substr(trim(text),1,100) text
from user_source
where UPPER(text) like UPPER('%'||replace('&string','_','\_')||'%') ESCAPE '\'
order by name,line
/

June 10, 2003 Patrick -- Thanks for the question regarding
      "dump.sql file grows and grows too large", version 4.2

      You Asked
In the client web there is a mysql database. It shows "Regisitration and user
name" information. There are times when a user leaves, and that information
about that user is "dumped." That information will never be used again. Even if
the person wants to "sign back up," a whole new entry would be started. But the
"dump.sql" file keeps growing larger and takes more and more of server space. Is
there a way to edit the dump.sql information for useless data without causing
harm to the database that is running? Or, without causing other problems?

      and we said...
mysql?  why would you come to asktom.ORACLE.com to ask about mysql?
Review &
      Followup

      Rating: 1
"And We
      Said....."  June 04, 2003
Reviewer:  Patrick
       from USA
mysql?  why would you come to asktom.ORACLE.com to ask about mysql?

Why? Because you guys are "touted" to be the absolute experts.
I was told by many that if there was an answer to this, that I could find it
through you.

Obviously, some portion of "expert" doesn't apply.

          Followup:

dude, use your head.

it is asktom.ORACLE.com -- I find this amusing that you would come to the author
of "Expert One on One ORACLE" and other ORACLE books -- who has ORACLE in their
domain, email address, business card and ask a mysql question.

If you ask me about fixing cars, I'll give you the same sort of answer.

      Rating:
      5
  June 05, 2003
Reviewer:  Kalita
       from Bromley,UK
Hee hee... Shall I ask a sqlserver question now ;-)

      Rating:
      5
  June 05, 2003
Reviewer:  Joel
       from Columbus, OH USA
Wow!  I think this one really takes the cake.

"Because you guys are touted to be the absolute experts", not sure who is doing
the touting.  Are "they" touting you as the expert of all things for all time?
Can I ask you questions about the Linux kernel, why my ColdFusion program
doesn't work, why my APPC program to DB2 on OS/390 fails every now and then,
why my bootleg copy of Tapper on the Atari 800 sometimes throws an exception?  I
mean....you are the expert, aren't you?

This simply validates what my father has often said....common sense isn't so
common.

          Followup:

:)

      Rating: 5
Hey, my
      dishwasher needs fixing, too...  June 05, 2003

Reviewer:  A reader

      Rating: 5
Perhaps he's
      confused you with "Ask Simon"?  June 05, 2003

Reviewer:  A reader

      Rating: 5
This is too
      funny  June 05, 2003
Reviewer:  Dennis
       from Missouri, USA
Wow.  Words escape me.  Hey, SQL is in the name, and you use SQL, so *poof*
solve my problem!!!

Congrats Tom, I never even realized I could come here with all that other stuff.
 Silly me, I limited myself to Oracle questions.  Hm...I wonder if you could
solve my Cognos problem.   It's on the same server as an Oracle database.  All
you gurus know everything!!  Those MCSE people who keep pretending to not know
anything about Oracle were really starting to annoy me.  I thought you'd at
least share!!  hehe

I bet you had a chuckle for the whole day, courtesy of Patrick!  His post
certainly put a smile on my face!!

          Followup:

I found it amusing, yes.

      Rating: 5
Dr. It hurts
      when I do this...  June 05, 2003
Reviewer:  Bob
      Maggio  from Troy NY
I thought you knew everything about Komputerz?

      Rating: 5
Thanks
      Patrick  June 05, 2003
Reviewer:  Jim

Patrick,

Just wanted to say thanks... been a bad week
and today's start wasn't any better. Then I read
this thread.. Very funny .. Really made my day

Jim

      Rating: 1
TEST
      FAILED  June 05, 2003
Reviewer:  Marion
       from All over the world, and looking at you now.
Those who come to ask and are not aware deserve a human and a kind response.
Those who respond with indifference are looked upon as ones who do not merit
growth potential.
In the next restructuring of workforce, remember that one who helps, stays. One
who jeers is not helpful.

We have no need or use to pay for those who jeer.

Bill

      Rating: 5
Test
      Failed?  June 06, 2003
Reviewer:  Trevor

Bill  or Marion whoever you are

Those who say things like
"Obviously, some portion of "expert" doesn't apply."
deserve to be jeered..

Those of who by their admission (as follows)
"Reviewer:  Marion  from All over the world, and looking at you now. "
looking at people all over the world now, obviously
aren't doing their work and will be the first to go in the
next restructure :P

      Rating:
      1
Trevor  June 09, 2003
Reviewer:  Oracle
      Stock Slumps  from Everywhere
Please connect to the one link that you should deserve.

http://v4.windowsupdate.microsoft.com/en/default.asp

And, Have a nice day.

          Followup:

gee, that links gives me:

<quote>
Thank you for your interest in Windows Update

Windows Update is the online extension of Windows that helps you get the most
out of your computer.

You must be running a Microsoft Windows operating system in order to use Windows
Update.
</quote>

Pretty exclusionary, ain't they.  :)

But anyway -- did you have a point?

      Rating: 5
Oracle Up -
      Microsoft slumps - Bill having garage sale  June 10, 2003

Reviewer:  Jim  from USA
Hi "Oracle Stock Slumps"...

Poor Trevor, I thought he deserved more then 1 star!

From the context of your review you seem to have assumed
he is anti microsoft .. he makes no mention of having
bias in anyway.

However I am very much in favour of open systems
and am grateful that I do not have to work with
windoze

The link in your review is just plain mean and nasty, nobody deserves to be
exposed to Windoze

I checked Oracle's share price and it was up.
The long term charts show the stock in a strong uptrend
pretty good for a depressed IT market.

Out of curiosity I also did a 1 year chart on Microsoft
and it looks like the titanic, it sinking!!
Billys probably planning a garage sale soon...

Guess what.. I'm still laughing... in fact
this is getting funnier...  :)

You have a real nice day too
Hope Santa brings you a Linux box for Christmas

God Bless You
Jim

      Rating: 5
Oracle Shares
      Still going up !!  June 15, 2003
Reviewer:
      A reader
================From Reuters ============================

Oracle Shares Rise as Results Cheered
Fri June 13, 2003 04:28 PM ET

NEW YORK (Reuters) - Shares of Oracle Corp. ORCL.O rose to a 15-month high on
Friday as Wall Street analysts cheered the business software maker's strong
quarterly results and positive guidance.

Several analysts raised their view on Oracle on Friday, a day after the company
posted a bigger-than-expected 30 percent rise in fourth-quarter profit and said
it was gaining ground in the tough corporate software market. It forecast
results for the August quarter roughly in line with Wall Street estimates.

June 10, 2003 -- Thanks for the question regarding "Why is
      Index Not Being Used?", version 9.2.0

      You Asked
Hi Tom,

It's late now so bear with me if this is a daft question.

I've just been playing around with some queries against a modified EMP table.
I've made the following changes:

1) Used INSERT INTO EMP SELECT * FROM EMP until there are 1024 rows.

2) Added a numeric column, ID, populated it and made this the primary key.

3) Added two non-unique indexes, one on job and one on empno.

Now, if I issue:

select empno, count (1) from emp group by empno

I, quite reasonably I think, get:

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=16 Bytes=64)
   1    0   SORT (GROUP BY NOSORT) (Cost=4 Card=16 Bytes=64)
   2    1     INDEX (FULL SCAN) OF 'EMP_I2' (NON-UNIQUE) (Cost=4 Card=
          1024 Bytes=4096)

EMP_I2 being the index on empno.

However, if I do:

select job, count (1) from emp group by job;

I get:

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=5 Bytes=40)
   1    0   SORT (GROUP BY) (Cost=6 Card=5 Bytes=40)
   2    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=3 Card=1024 Bytes=819
          2)

My question is, as indexes exist such that both queries should be answerable
without hitting the main table, why am I seeing otherwise?

Thanks in advance.

      and we said...
add "where job is NOT NULL" to the second query and see what happens.

entirely null entries are not made into b*tree indexes

JOB is nullable.

Hence, if it used the index, it could very well get the wrong answer as their
could be NULL entries for JOB in EMP that are not in the index!

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1249800833250
for how you can use this fact to your advantage.

Review &
      Followup

      Rating:
      5
  June 09, 2003
Reviewer:
      A reader

      Rating:
      2
INDEX  June 10, 2003
Reviewer:  mohammad
      taha  from Dubai
Dear Tom,

Please give your opinion on the following example:

SQL> select job,count(*) from emp group by job

JOB         COUNT(*)
--------- ----------
ANALYST            3
CLERK              4
MANAGER            3
PRESIDENT          1
SALESMAN           4
manager            1
                   3

7 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=5 Bytes=30)
   1    0   SORT (GROUP BY) (Cost=5 Card=5 Bytes=30)
   2    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=17 Bytes=102)

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

SQL> select job,count(*) from emp where job is NOT NULL group by job

JOB         COUNT(*)
--------- ----------
ANALYST            3
CLERK              4
MANAGER            3
PRESIDENT          1
SALESMAN           4
manager            1

6 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=5 Bytes=30)
   1    0   SORT (GROUP BY) (Cost=5 Card=5 Bytes=30)
   2    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=14 Bytes=84)

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

  -- The folowing query shows that NULL value exists.

SQL> select job,count(*) from emp where job IS  NULL group by job

JOB         COUNT(*)
--------- ----------
                   3

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=2 Bytes=12)
   1    0   SORT (GROUP BY) (Cost=5 Card=2 Bytes=12)
   2    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=3 Bytes=18)

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

          Followup:

the optimizer did a wonderful job.

Look -- how big is that table?  Do you have an index on job?

ops$tkyte@ORA920> drop table emp;

Table dropped.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table emp as select * from scott.emp;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index job_idx on emp(job);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> analyze table emp compute statistics;

Table analyzed.

ops$tkyte@ORA920> select job, count(*) from emp where job is not null group by
job;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=5 Bytes=35)
   1    0   SORT (GROUP BY NOSORT) (Cost=1 Card=5 Bytes=35)
   2    1     INDEX (FULL SCAN) OF 'JOB_IDX' (NON-UNIQUE) (Cost=1 Card=14
Bytes=98)

ops$tkyte@ORA920> exec dbms_stats.set_table_stats( user, 'EMP', numrows =>
1000000, numblks => 100000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select job, count(*) from emp where job is not null group by
job;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=5 Bytes=35)
   1    0   SORT (GROUP BY NOSORT) (Cost=1 Card=5 Bytes=35)
   2    1     INDEX (FULL SCAN) OF 'JOB_IDX' (NON-UNIQUE) (Cost=1 Card=1000000
Bytes=7000000)

ops$tkyte@ORA920> set autotrace off

      Rating:
      5
  June 10, 2003
Reviewer:
      A reader
In relation to the original question, tried it, worked as expected, thanks a
bunch.

June 10, 2003 -- Thanks for the question regarding "Sqltype
      Table of Records - Operations and Java", version 9.2

      You Asked
Hello Sir,
          I want to pass a Sqltype table of records to Java.
1)How to do populate values for such a record in plsql.
say from a select statement.
2) How to insert values in a table from such a table of records
3) How will java interpret that.

I see some examples on your site about passing a scalar sqltype like table of
numbers or table of varchar But no example on passing table of records to Java.
Can u pls give one

      and we said...
I've never done a collection of objects in java -- and I won't be :)

I would use result sets -- period.  Least amount of code on all parts.
Consider:

create or replace type myScalarType as object
( x int,
  y varchar2(20),
  z date
)
/
create or replace type myTableType as table of myScalarType
/

create or replace function non_pipelined( p_like in varchar2 )
return myTableType
as
    l_data myTableType;
begin

    select myScalarType( user_id, username, created  )
      BULK COLLECT into l_data
      from all_users
     where username like p_like;

    return l_data;
end;
/

create or replace function is_pipelined( p_like in varchar2 )
return myTableType
pipelined
as
begin
    for x in ( select myScalarType( user_id, username, created  ) y
                 from all_users
                where username like p_like )
    loop
        pipe row(x.y);
    end loop;
    return;
end;
/

select * from TABLE( non_pipelined( '%A%' ) );
select * from TABLE( is_pipelined( '%A%' ) );

Just will just run the QUERY to run the procedure and a ResultSet to manipulate
the output.
Review & Followup

      Rating: 4
New PL/SQL
      syntax  June 09, 2003
Reviewer:
      A reader
I haven't been keeping up to speed with the last PL/SQL stuff since 8.1.5 (early
1999).  I am amazed by all these new syntax, such as BULK COLLECT and PIPELINED
you used in your sample code.  I am still using the good old
myTableType(index).field := xxxx in a loop.  I hope I am not paying any
performance penalty.

BTW, here is my 2 cents on using collection of Java objects:  java.sql.ResultSet
IS fairly easy to use, but since it does not implement java.io.Serializable, it
is difficult to pass a ResultSet between tiers (web and business).  Plus
ResultSet is connection-based so you need to have a connection open - not a good
idea.  You may want to use the RowSet interface, but a more typical pattern is
to use a collection of DTO - data transfer objects.

      Rating: 5
perfect : Q
      about selective select  June 10, 2003
Reviewer:
      A reader
How about selecting only x and y sclar values from the  table of records for
populating the functions both piped and non piped.Just as we select few columns
from a table.
How to do that for table of records.
 How should we do that.
Wrote file afiedt.buf

  1  create or replace function selective_non_pipelined( p_like in varchar2 )
  2  return myTableType
  3  as
  4      l_data myTableType;
  5  begin
  6      select myScalarType( user_id, username  )
  7        BULK COLLECT into l_data.user_id ,l_data.username
  8        from all_users
  9       where username like p_like;
 10      return l_data;
 11* end;
 12  /

Warning: Function created with compilation errors.

SQL> show err
Errors for FUNCTION SELECTIVE_NON_PIPELINED:

LINE/COL ERROR
-------- -----------------------------------------------------------------
6/5      PL/SQL: SQL Statement ignored
6/12     PL/SQL: ORA-02315: incorrect number of arguments for default
         constructor

SQL> ed
Wrote file afiedt.buf

  1  create or replace function selective_is_pipelined( p_like in varchar2 )
  2  return myTableType
  3  pipelined
  4  as
  5  begin
  6      for x in ( select myScalarType( user_id, username  ) y
  7                   from all_users
  8                  where username like p_like )
  9      loop
 10          pipe row(x.y);
 11      end loop;
 12      return;
 13* end;
SQL> /

Warning: Function created with compilation errors.

SQL> show err
Errors for FUNCTION SELECTIVE_IS_PIPELINED:

LINE/COL ERROR
-------- -----------------------------------------------------------------
6/14     PL/SQL: SQL Statement ignored
6/21     PL/SQL: ORA-02315: incorrect number of arguments for default
         constructor

10/9     PL/SQL: Statement ignored
10/18    PLS-00364: loop index variable 'X' use is invalid

          Followup:

you have a record.

it has three components.

it'll have three components today, tomorrow, forever (unless you drop and
recreate it).

Just like a table -- if you have a table with three columns -- you have, well,
three columns.

So, you could:

  6      select myScalarType( user_id, username, NULL  )
  7        BULK COLLECT into l_data

and then the person querying that would query

select x, y from TABLE.....

or you could:

  6      for x in ( select myScalarType( user_id, username, NULL  ) y
  7                   from all_users
  8                  where username like p_like )
  9      loop
 10          pipe row(x.y);
 11      end loop;

or you could create yet another type that has your two attributes.

      Rating: 5
Thanx
      much  June 10, 2003
Reviewer:  A reader

You have made Oracle so simple to understand.

June 10, 2003 -- Thanks for the question regarding "couple of
      questions regarding PCTFREE", version 9i

      You Asked
Hi Tom,

Given: database block size is 8K

The average initial row size for table A is 180 bytes.  After updates, the
average final row size is 200 bytes.  For table A, leaving PCTFREE at the
default value of 10 should be fine.

The average initial row size for table B is 20 bytes.  After updates, the
average final row size is 200 bytes.  For table B, setting PCTFREE somewhere
around 90 will in general prevent row migration.

Table C has an initial row size that varies greatly.  The final row size after
updates will be 2000 bytes.  Some rows are inserted with an inital row size of
20 bytes.  Others with an initial row size of 2K bytes, others anywhere between
20 and 2K. Table C appears to be problematic when it comes to setting PCTFREE.
Even setting it very low will not stop row migration.

Our interim conclusions:
(a) As long as the initial row size is fairly consistent, PCTFREE provides a
simple and effective way to prevent row migration.

(b) In cases where initial row size varies widely, PCTFREE as a tool to prevent
row migration is not very useful.

Any suggestions?

Second Question on PCTFREE:
Is INITRANS expansion dependant on PCTFREE?  That is, can INITRANS expand even
after the PCTFREE threshold has been reached?

Example: PCTFREE is 10%.  92% of some block has data.  Further inserts on that
block are prevented by the PCTFREE setting.  INITRANS is at 1 for the block.
Two tx's then try to update rows in the block simultaneously.  Tx1 gets the
existing INITRANS slot.  Tx2 tries to allocate an additional INITRANS entry.
Can it, or is the INITRANS expansion prevented by the PCTFREE setting, in the
same way that a row insert would be?

As always, your insights are much appreciated.

      and we said...
In this case, you sort of know how many rows per block you want.  You want 4,
maybe three (4 2000 byte rows won't fit, 3 2000 byte rows would).

You could do this.  It is a "creative application of a setting designed for
something else".  There is a way to set the max number of rows/block -- it is
designed for bitmapped indexes.  We can use it here to limit the number of
rows/block right after you create (or recreate) the table.  Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x varchar2(2000) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 'a' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 'b' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 'c' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 'd' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t minimize records_per_block;

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select rownum from all_objects;

22870 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select avg( cnt), min(cnt), max(cnt)
  2    from (
  3  select dbms_rowid.ROWID_BLOCK_NUMBER( rowid ), count(*)  cnt
  4    from t
  5   group by dbms_rowid.ROWID_BLOCK_NUMBER( rowid )
  6   )
  7  /

  AVG(CNT)   MIN(CNT)   MAX(CNT)
---------- ---------- ----------
3.99965029          2          4

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

Here, I fed the table 4 rows -- small ones.  they went onto the same block.
Then, alter the table.  Now each block will contain at most 4 rows.  Note that
by using row number, we would normally fit hundreds of rows/block (eg: the last
query without the alter returned:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select avg( cnt), min(cnt), max(cnt)
  2    from (
  3  select dbms_rowid.ROWID_BLOCK_NUMBER( rowid ), count(*)  cnt
  4    from t
  5   group by dbms_rowid.ROWID_BLOCK_NUMBER( rowid )
  6   )
  7  /

  AVG(CNT)   MIN(CNT)   MAX(CNT)
---------- ---------- ----------
653.542857        366        662

on my system).

Now these rows can grow without worry about row migration.  A special trick for
a unique circumstance.

The answer to the second issue is the initrans will grow regardless of the
pctfree if there is room.

To demonstrate this, I set up this example.  The stuff in bold isn't part of the
script (if you want to run this yourself)

drop table t;
create table t ( a char(2000), b char(2000), c char(2000), d varchar2(4000) )
pctfree 0;

we need to find what the biggest row we can insert into there to have two
rows/block such that if the row was 1 byte bigger -- it would not fit (hence we
hit pctfree on the block).  If your block size is less then 8i, you'll need to
take away b and or c and start the loop at a lesser number!!!

declare
    l_blocks number;
begin
    for i in 2050..4000
    loop
        execute immediate 'truncate table t';
        insert into t values ( null, null, null, 'd' );
        insert into t values ( 'a', 'b', 'c', rpad('d',i,'d') );
        commit;
        select count( distinct( dbms_rowid.ROWID_BLOCK_NUMBER( rowid ) ) ) into
l_blocks from t;
        dbms_output.put_line( 'i = ' || i || ' l_blocks = ' || l_blocks );
        if ( l_blocks > 1 )
        then
            execute immediate 'truncate table t';
            insert into t values ( null, null, null, 'd' );
            insert into t values ( 'a', 'b', 'c', rpad('d',i-1,'d') );
            commit;
            exit;
        end if;
    end loop;
end;
/
select dbms_rowid.ROWID_BLOCK_NUMBER( rowid ), length(d) from t;

when I ran this, I get:

i = 2060 l_blocks = 1
i = 2061 l_blocks = 1
i = 2062 l_blocks = 1
i = 2063 l_blocks = 2

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select dbms_rowid.ROWID_BLOCK_NUMBER( rowid
), length(d) from t;

DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)  LENGTH(D)
------------------------------------ ----------
                                 394          1
                                 394       2062

showing the biggest D value I can insert it 2,062 bytes before we run out of
room... Now, we need two sessions and do the following;

update t set d = d where a is not null;

prompt IN ANOTHER WINDOW ISSUE:  update t set d = d where a is null;
prompt IT WILL BLOCK due to no space to grow initrans!
pause

commit;
prompt COMMIT IN THE OTHER WINDOW NOW PLEASE...
pause

That shows that if pctfree is 0, we use all of the space and initrans cannot
be exceeded.  Now, we'll set pctfree to 10 and do it all over again.  Same
caveats for blocks < 8k applies here:

drop table t;

create table t ( a char(2000), b char(2000), c char(2000), d varchar2(4000) )
pctfree 10;

select dbms_rowid.ROWID_BLOCK_NUMBER( rowid ) from t;

declare
    l_blocks number;
begin
    for i in 1260..4000
    loop
        execute immediate 'truncate table t';
        insert into t values ( null, null, null, 'd' );
        insert into t values ( 'a', 'b', 'c', rpad('d',i,'d') );
        commit;
        select count( distinct( dbms_rowid.ROWID_BLOCK_NUMBER( rowid ) ) ) into
l_blocks from t;
        dbms_output.put_line( 'i = ' || i || ' l_blocks = ' || l_blocks );
        if ( l_blocks > 1 )
        then
            execute immediate 'truncate table t';
            insert into t values ( null, null, null, 'd' );
            insert into t values ( 'a', 'b', 'c', rpad('d',i-1,'d') );
            commit;
            exit;
        end if;
    end loop;
end;
/
select dbms_rowid.ROWID_BLOCK_NUMBER( rowid ), length(d) from t;

on my system, I got:

i = 1263 l_blocks = 1
i = 1264 l_blocks = 1
i = 1265 l_blocks = 2

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select dbms_rowid.ROWID_BLOCK_NUMBER( rowid
), length(d) from t;

DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)  LENGTH(D)
------------------------------------ ----------
                                 394          1
                                 394       1264

update t set d = d where a is not null;

prompt IN ANOTHER WINDOW ISSUE:  update t set d = d where a is null;
prompt this time, it will NOT block....

That shows that initrans will "grow" down into the free space -- the other
update will not block this time, there is room to grow into the free space

Review &
      Followup

      Rating: 5
Usefull
        June 10, 2003
Reviewer:  Vasu  from
      Chennai, Tamil Nadu, India
Tom,  your explanations for this query were very usefull.

I have a Problem.  Im developing an application with Oracle 8i 8.1.7 and I have
fairly good idea about how the table growth would be for each table. ( I have
around 300+ tables).

But, Is there a generic way based on the maximum size of a record I could decide
the min_Trans, Max_Trans, pctfree, pctincrease, Initial extents, next extents,
max extents ( The entire storage clause)?

It would be great if you could suggest me some tips on this.

Vasu

          Followup:

lets see, your record will be 150 bytes maximum.

What can we deduce about initrans based on that?  well, nothing.  case 1) this
is a lightly used big table (lots of rows).  initrans at default is dandy.  case
2) this table has few rows and is updated FREQUENTLY.  initrans might need to be
set higher OR i'll minimize rows per block to get a row per block just to spread
them out

What can we deduce about pctfree based on that?  again, nothing

case 1) data is inserted with about 50 bytes filled in.  Rows grow to 150 bytes
almost always over time.  conclusion -- pctfree should be high

case 2) data is inserted with about 50 bytes filled in.  Rows RARELY grow.
pctfree should be low

case 3) data is inserted with about 150 bytes filled in.  pctfree should be low.

case 4) data is inserted with about 50 bytes filled in.  Many (say 1/2) grow to
150 bytes.  pctfree should be somewhere in the middle

Now, initial, next and max are the ONLY things we can say anything about with
100% certainty.  They should not be set to anything at all -- you will be
using locally managed tablespaces with either uniform or system allocated
extents.  You will never use initial, next, pctincrease, minextents, maxextents
again

June
      10, 2003 Tarek -- Thanks for the
      question regarding "Segment management clause", version 9.2.0

      You Asked
Hi Tom,

In Oracle9i we can use Free List or Bitmap to track free or used space in the
segment (segment management clause).

Which one is better for OLTP and which one for DSS?

I think (might be wrong) bitmap (automatic segment management) is better for
both.

Would you please explain the differences and benefits of these two modes?

Have a nice time,
Tarek

      and we said...
I've found them to be useful for preventing me from having to fine tune
freelists and such -- mostly in OLTP.

I think this thread:

http://asktom.oracle.com/~tkyte/assm.html

presents a really good balanced discussion on this topic.
Review &
      Followup

      Rating:
      4
  June 10, 2003
Reviewer:
      A reader
do you create all tablespaces in 9.2 in automatic segment space management mode

          Followup:

no, not all.  There are cases where space growth can be a real issue.

June 10, 2003 Surrinder -- Thanks for the question regarding
      "Converting CLOBS TO VARCHAR", version 8.1.7

      You Asked
Can you give me a solution for converting CLOBS datatype to
VARCHAR datatypes, all the documents I refer to talk about
converting BLOBS to VARCHAR and when I try and apply the
examples to CLOBS, get errors

      and we said...
dbms_lob.substr( clob_column, for_how_many_bytes,
from_which_byte );

for example:

  select dbms_lob.substr( x, 4000, 1 ) from T;

will get me the first 4000 bytes of the clob.  Note that when
using SQL as I did, the max length is 4000.  You can get 32k
using plsql:

declare
   my_var long;
begin
   for x in ( select X from t )
   loop
       my_var := dbms_lob.substr( x.X, 32000, 1 );
       ....

Review &
      Followup

      Rating: 5
Converting
      CLOBS TO VARCHAR  March 16, 2001
Reviewer:
      Surrinder Singh  from UK
Extremely useful all the documentation that refered to LOB conversions in the
ORACLE Docs , and that stated that the dbms_lob.substr should be returned into a
RWS(32767), not a LONG for CLOBS, thanks once again.

      Rating:
      4
  March 16, 2001
Reviewer:  Gururaj
       from Virginia USA

      Rating: 4
Converting
      CLOBS TO VARCHAR  March 16, 2001
Reviewer:
      Naveen  from Raleigh,NC
As usual, TOM makes it look easy.

      Rating: 4
Converting
      CLOBS TO VARCHAR  March 16, 2001
Reviewer:
      Naveen  from Raleigh,NC
As usual, TOM makes it look easy.

      Rating: 3
How to display
      long data after <xml> tag in text file via
      sql*plus?  June 05, 2001
Reviewer:  Teresa
      Felch  from Huntsville, Alabama 35824
Dear Tom,

How does one display a long datatype column variable of a table on the same line
after an xml tag?  For example, we are using sql*plus to write a file containing
xml tags at the beginning of each record via a  SELECT statement.  I get
"inconsistent datatypes" when I try to write  out the long variable.

COLUMN  newline_tag       newline;
COLUMN  date_submit       newline;
COLUMN  mmdd_date_submit  newline;
COLUMN  narrative_desc    format A1000 justify left;

SELECT '<DATE>' || TO_CHAR (synopsis.date_submit, 'MMDD') as
mmdd_date_submit,
       '<DESC>' || NULL newline_tag, synopsis.narrative_desc,
....
FROM synopsis
...
;

the variables are declared as follows:
date_submit      date   NOT NULL
mmdd_date_submit date   NOT NULL
narrative_desc   long   NOT NULL <--(18000 char max)

For now, I'm getting
<DESC>
narrative_desc text paragraph.

My goal is to get the long variable written on the same line as the <DESC> tag
with NO spaces between the <DESC> and the text data, as this output will serve
as a template to XML processing.

Any help you can provide me is greatly appreciated.  I just started using
sql*plus about a week ago, and I'm having fun with it.  I've looked at using the
ConText option, but my co-worker thinks there's an easier way of displaying this
information then using ConText.

Sincerely,
Teresa Felch
teresf@bellsouth.net

      Rating: 5
very
      good  June 25, 2001
Reviewer:  Satya K
       from San Francisco, CA USA
This means, Oracle cursor can not have any column more than 4000 chars width..

      Rating: 5
Converting
      CLOBS TO VARCHAR   March 18, 2002
Reviewer:
      Karl Bergerson  from Bellevue WA USA
It worked for me.  Quick efficient.  Thanks.

      Rating: 5
dbms_lob.substr
      problem  June 10, 2003
Reviewer:
      A reader
Hi

  How to get around with this . database is 817
We have a table lobsq(col clob).

select dbms_lob.getlength(col) from lobsq
gives me 4245.

my plsql program is likt this

declare
res   varchar2(32767);
begin
select dbms_lob.substr(col,dbms_lob.getlength(col),1) into res from lobsq;

dbms_output.put_line(res);
end;

gives me the following error
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SYS.DBMS_OUTPUT", line 57
ORA-06512: at line 10

          Followup:

dbms_output.put_line has a documented limit of 255 characters/line.  you have
exceeded that.

create or replace procedure p ( p_str in varchar2 )
is
   l_str   long := p_str;
begin
   loop
      exit when l_str is null;
      dbms_output.put_line( substr( l_str, 1, 250 ) );
      l_str := substr( l_str, 251 );
   end loop;
end;
/

is a little dump routine I use.  Furthermore, SQL is limited to returning 4000
characters from VARCHAR types.

ops$tkyte@ORA817DEV> create table t ( x clob );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> declare
  2          l_data long;
  3          l_clob clob;
  4  begin
  5          l_data := rpad( '*', 4245, '*' );
  6          insert into t values ( empty_clob() )
  7          returning x into l_clob;
  8          dbms_lob.writeAppend( l_clob, 4245, l_data );
  9  end;
 10  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select dbms_lob.getLength(x) from t;

DBMS_LOB.GETLENGTH(X)
---------------------
                 4245

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> declare
  2          l_data varchar2(10000);
  3  begin
  4          select dbms_lob.substr(x,4245,1) into l_data from t;
  5          dbms_output.put_line( 'length = ' || length(l_data) );
  6  end;
  7  /
declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at line 1
ORA-06512: at line 4

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> declare
  2          l_data varchar2(10000);
  3          l_clob clob;
  4  begin
  5          select x into l_clob from t;
  6          l_data := dbms_lob.substr( l_clob, 4245, 1 );
  7          dbms_output.put_line( 'length = ' || length(l_data) );
  8  end;
  9  /
length = 4245

PL/SQL procedure successfully completed.

would be the way to fetch that.

June 10, 2003 rama -- Thanks for the question regarding
      "Parametrised view !", version 8.1.7

      You Asked
tom,

thanks for the opprtunity to post a question.

My question may look stupid, but I would like you to shed some light on how to
achieve this.

I need to create a view. I have a working query. But, this view needs to be
parametrised so that, when called with parameters, it would use them in the
underlying where condition to get the results.

Like this.

Create view abc(dno number)
as
select * from emp where deptno = dno;

Now, I should be able to call this as

select * from abc(10).

To my knowledge, not possible with view. but, is there any work around where I
can select ALL the columns from the view, but can pass parameters, so that they
would be used for the query ?

thanks

      and we said...
you need to use a ref cursor and stored procedure to accomplish this.  there is
no such thing as "parameters to a view" or any sql query really.

see

http://asktom.oracle.com/~tkyte/ResultSets/index.html
for howto use ref cursors quickly.

Review &
      Followup

      Rating: 4
Parametrised
      view !  October 26, 2002
Reviewer:  Rama
       from ct, usa
Thanks ! I knew it is not possible with views.. I was looking for a work around.
I will try that and let you know.

      Rating: 3
What is the
      problem using Where condition  October 27, 2002

Reviewer:  Reviewer
If you are creating a view

Create view viewname as select * from table

always u can use select * From viewname where colname=your poarameter value.
for this why u want to go for ref cursor and all....

      Rating: 4
Tom from your
      book ...  October 27, 2002
Reviewer:  Sudhir
       from Louisville, KY USA
I have some code from your book which does this, doesn't it?

create or replace view emp_view as
select * from emp where deptno = sys_context('userenv','client_info');

exec :dno:=10;
exec dbms_application_info.set_client_info(:dno);
select * from emp_view;

would that not work?

          Followup:

Yes, that is what "i" call a parameterized view.  however, they question asker
wanted to:

select * from abc(10).

call the view like a procedure with parameters.  The closest thing to that is
"ref cursors" in a real procedure.

      Rating: 4
My
      Approach  November 04, 2002
Reviewer:
      Michael  from Germany
I tried the following to get a "Parametrised view":

create or replace type emp_type
as object (
empno number(4),
ename varchar2(10)
)
/

create or replace type emp_type_table as table of emp_type;
/

create or replace function emp_of_dept(p_deptno in number)
return emp_type_table
pipelined as
  l_emp_type emp_type;
begin
  for l_myrow in (select empno, ename from emp
                  where deptno = p_deptno) loop
    l_emp_type := emp_type(l_myrow.empno, l_myrow.ename);
    pipe row (l_emp_type);
  end loop;
  return;
end emp_of_dept;
/

select *
from table(emp_of_dept(10));

     EMPNO ENAME
---------- ----------
      7782 CLARK
      7839 KING
      7934 MILLER

select *
from table(emp_of_dept(30));

     EMPNO ENAME
---------- ----------
      7499 ALLEN
      7521 WARD
      7654 MARTIN
      7698 BLAKE
      7844 TURNER
      7900 JAMES

Tom - what do you think about this?

          Followup:

I think it is alot more code then:

create or replace
procedure foo( p_deptno in number, p_result_set out sys_refcursor )
as
begin
   open p_result_set for
    select empno, ename from emp where deptno = p_deptno;
end;

and having the client (which is doing Oracle stuff anyway) issue

begin foo( :n, :m ); end;

It'll be less performant as well.

Neat "trick" but not best practice.

      Rating:
      3
  November 04, 2002
Reviewer:
      A reader
Tom or Michael..

In your function you have used "pipelined" & "piperow"

can you please explain what is that..

Thanks.

/********
create or replace function emp_of_dept(p_deptno in number)
return emp_type_table
pipelined as
  l_emp_type emp_type;
begin
  for l_myrow in (select empno, ename from emp
                  where deptno = p_deptno) loop
    l_emp_type := emp_type(l_myrow.empno, l_myrow.ename);
    pipe row (l_emp_type);
  end loop;
  return;
end emp_of_dept;

***********/

          Followup:

search for

pipelined

on this site to see many examples and details of this new Oracle9i feature that
allows a plsql function to act just like a database table.

      Rating: 3
Read the
      documentation  November 05, 2002
Reviewer:
      Michael  from Germany
Start here:

http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96624/08_subs.htm#19677

      Rating: 3
Performance of
      Ref Cursor vs. Pipelined Function  November 05, 2002

Reviewer:  Michael  from Germany
Tom, you are right when you write:
"It'll be less performant as well."

I made a test with both things and the Ref Cursor is 8 - 10 times faster than
"my" pipelined function idea.

So, i will use the Ref Cursors where possible.

      Rating: 3
Calling
      parameterised views from Forms  June 10, 2003

Reviewer:  Carmen Burn  from NI
Hi,

I set about using a parameterised view and piped function, where the view is
called from Forms9i in a bast table block. However, how do I go about calling
the following :
exec dbms_application_info.set_client_info(:variabl)
from forms to pass a parameter to the view? I tried forms_ddl, but that had no
effect.

many thanks,
Carmen

          Followup:

in forms, you just naturally call plsql?

begin
  dbms_application_info.set_client_info( :block.item );
end;

for example.

      Rating:
      5
  June 11, 2003
Reviewer:  Carmen
       from NI
Hi, now why didn't I think to try that? It worked fine.
Thanks.

June 10, 2003 -- Thanks for the question regarding "SCNs and
      serializable", version 8.1.7

      You Asked
I have a rather theoretical question about SCNs and the serializable
transaction isolation level. Actually, it's an assumption and I just wanted
to run it by you.
If you're executing in read committed mode, every SELECT grabs the current
SCN from the database as follows

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT * FROM emp; -- gets SCN X
SELECT * FROM emp; -- gets SCN Y
COMMIT;

Now, X may not be the same as Y, since the results for these queries may not
be the same (non-repeatable reads). However, if your isolation level is
serializable,
the results must be the same by definition. So my assumption is, in the
serializable
mode, the SCN received by the first query of the transaction is used throughout
the
transaction until it commits (or rolls back) whereas in read committed mode each
query gets
the current SCN at the time the query executes. Is my assumption correct? Are
there any
other major differences between isolation levels when it comes to Oracle's
internal implementation
of them (e.g. differences in what is being locked (table vs. rows))?
On a related note, if you just do selects with or without 'FOR UPDATE' in your
transaction, does committing
reserve a new SCN for the transaction? If so, why?

Thanks,
Marko

      and we said...
Exactly - you got it.  The read consistent view of the database (as determined
by the SCN used to open the query) is fixed at the beginning of the transaction
in serializable (and read only).  It is fixed at the beginning of the STATEMENT
in read committed and in 9i -- you can TELL us what SCN to run the query "as of"

As for "differences" -- they lock data and everything else in exactly the same
fashion.

A commit advances the SCN yes (using a new 9i package to inspect the SCN)

ops$tkyte@ORA920.US.ORACLE.COM> select dbms_flashback.get_system_change_number
from dual;

GET_SYSTEM_CHANGE_NUMBER
------------------------
           6532315687389

ops$tkyte@ORA920.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA920.US.ORACLE.COM> select dbms_flashback.get_system_change_number
from dual;

GET_SYSTEM_CHANGE_NUMBER
------------------------
           6532315687390

ops$tkyte@ORA920.US.ORACLE.COM> commit;

Commit complete.

ops$tkyte@ORA920.US.ORACLE.COM> select dbms_flashback.get_system_change_number
from dual;

GET_SYSTEM_CHANGE_NUMBER
------------------------
           6532315687391

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

Why?  I guess cause it is easier just to do it rather then check to see if we
really truly need to do it.  No harm done.
Review &
      Followup

      Rating: 5
SCN of
      update/delete  May 13, 2003
Reviewer:  Alberto
      Dell'Era  from Milan, Italy
Hi Tom,

so, to check my understanding and complete what the original poster said, is it
correct to say that, in the context of a serializable transaction, when an
update/delete is issued against a row, Oracle will raise the "cannot serialize
transaction" exception if and only if the SCN of the row is higher than the SCN
of the transaction ?
I.e., a serializable transaction is nothing more than this, plus the
read-consistency "as of" the SCN of the transaction ?

          Followup:

if and only if is too strong.  it depends on whether we can tell -- if the ITL
entries are still there (why you might want to set initrans higher then the
default if you use this heavily)

a serializable transactions means

o you will see the database as it existed when your transaction began (sort of
like "flashing back"

o you will recieve an error if you try to modify a row that was modified since
your transaction began or we cannot make a read consistent view of that row you
are trying to read (like when you get an ora1555)

      Rating:
      5
  May 13, 2003
Reviewer:
      A reader

      Rating: 5
read
      committed  May 13, 2003
Reviewer:  A reader
       from Houston, TX
Is SET TRANSACTION ISOLATION LEVEL READ COMMITTED the default behavior of
Oracle?
Thanks.

          Followup:

yes

      Rating: 2
SCN
      increment  May 29, 2003
Reviewer:  Gaurav
       from Ipswich,UK
Dear Tom,

As addition to this discussion, I tried to perform the same exercise which you
did... get the scn number and commit.... here is the output of that---
SQL> select dbms_flashback.get_system_change_number
  2  from dual;

GET_SYSTEM_CHANGE_NUMBER
------------------------
                 3229757

SQL> commit;

SQL> select dbms_flashback.get_system_change_number
  2  from dual;

GET_SYSTEM_CHANGE_NUMBER
------------------------
                 3229761

SQL> commit;

SQL> select dbms_flashback.get_system_change_number
  2  from dual;

GET_SYSTEM_CHANGE_NUMBER
------------------------
                 3229765

SQL> commit;

SQL> select dbms_flashback.get_system_change_number
  2  from dual;

GET_SYSTEM_CHANGE_NUMBER
------------------------
                 3229842

you can notice that the increment is erratic. First it was incremented by 4 then
in the last it was incremented by some other value. May I ask you what is the
algorithim/mechanism/reason behind this?
One more doubt - please tell me whether SCN is unique to whole database or not
(I think it is unique) if it is unique than where this informations will stored
-in the control file or in all detafile header?

Regards,
Gaurav

          Followup:

there are things in the background going on -- AQ, SMON, Job queues -- maybe
even other users.  The "idle" database is actually very active.

These background things did it.

there is an SCN for a database -- it is like a clock, a ticker, always
incrementing as time marches on.  its a controlfile thing.

      Rating: 3
SCN
      Use  May 30, 2003
Reviewer:  gaurav
       from Ipswich, UK
Thanks Tom,
For clearing my doubts. However, In addition to this I would like to ask
something more, The use of SCN.
I have read the Oracle documentation and articles/discussion on sites but I
haven't got the clear answer. Please, clarify this to me with some precise
explanation. The scenario is-
X user has logged in to the system. Will oracle allocate some/current SCN to
him?
Y user has logged into the system and updated one table but haven't commited.
Suppose earlier table1 has 10 records now it has 11 records.
Now when X user execute select on table1 he will get only records 10. How oracle
will come to know that it has to ignore the 11th record is there any thing other
than SCN ?
I think there should be one more variable on the block level which should tell
that this block has been updated.
I am totally confused. It will be very helpfull for me as well as my other
collegues if you explain this whole procedure with proper example. I know you
are very busy, if it is not possible for you then please atleast give me some
reference (link) where I can get the required info.

Thanking you in advance.

Regards,
Gaurav

          Followup:

SCNs are allocated to transactions, not users and they are assigned upon COMMIT.

When Oracle starts user X's query -- it says "what is the current system SCN -
oh, it is 55, great".  Now every block it reads will be inspected.  If the block
is newer then scn 55, has any modifications made AFTER scn 55 (scn 55 is like a
time, like "12 oclock noon"), Oracle will roll back the block to scn 55 using
the RBS.

All blocks for user X's query will therefore be as of scn 55 -- either because
they were not modified after scn 55 or because we rolled them back to scn 55.

There is a transaction array (ITL) on the block header that tells us this.

      Rating: 5
"Transactions
      containing DML statements with subqueries should use serializable
      ..."  May 30, 2003
Reviewer:  Jim Dickson
       from UK, London
I am not sure how single statements that contain sub-queries work w.r.t scn and
serialization.

The Concept guides state
"Transactions containing DML statements with subqueries should use serializable
isolation to guarantee consistent read."

Does DML in this usage includes SELECTs ?

I don't believe the Concept guide explains the advice - to me, it implies each
query (parent/sub) within the statement compares blocks to different SCNs.

Can you enlighten?

Gratefully

          Followup:

the query is the query -- there is just a single query.

I've no idea what they might have meant by that sentence taken out of context.
How about a direct link into otn.oracle.com that gives it to us in context.

      Rating:
      4
Thanks  June 02, 2003
Reviewer:
      Gaurav  from Ipswich,UK
Thanks a lot tom for clearing my doubt. That is the informations for which I am
looking for.

Regards,
Gaurav

      Rating: 3
Transactions
      containing DML statements with subqueries should use serializable
        June 02, 2003
Reviewer:  Jim Dickson
       from London, UK
See Serializable Isolation
at

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/c21cnsis.htm#2414
for Note in context.

I'm still none the wiser as to why the advice needs to be followed, and how
Oracle implements serializable isolation level, for a statement with >1
subquery, in terms of SCNs.

          Followup:

That looks like a documentation bug if you ask me.

I'll file one on it.  A statement is processed read consistently -- with or
without subqueries -- in read committed or serializable.

serializable only affects the behavior of multiple statements run in a
transaction.

      Rating: 4
SCN change when
      COMMIT?  June 02, 2003
Reviewer:  steven
       from Beijing.China
hello tom,
 you have said that "SCNs are allocated to transactions, not users and they are
assigned upon COMMIT."
but then i try many times ,sqlplus reply me such:

SQL> select dbms_flashback.get_system_change_number
  2  from dual;

GET_SYSTEM_CHANGE_NUMBER
------------------------
                 4185290

SQL> commit;

commit success.

SQL> select dbms_flashback.get_system_change_number
  2  from dual;

GET_SYSTEM_CHANGE_NUMBER
------------------------
                 4185290

SQL> commit;

commit success.

SQL> select dbms_flashback.get_system_change_number
  2  from dual;

GET_SYSTEM_CHANGE_NUMBER
------------------------
                 4185290

SQL> commit;

commit success.

SQL> select dbms_flashback.get_system_change_number
  2  from dual;

GET_SYSTEM_CHANGE_NUMBER
------------------------
                 4185292

it seems that scn generated doesn't have to increase during user commit? can you
explain me more?
thank you very much.

      Rating: 3
"Transactions
      containing DML statements with subqueries should use serializable ..."
        June 04, 2003
Reviewer:  Jim Dickson
       from London, UK
I'd love for you to get a full explanation for this.

I have already raised issue on Metalink (DocID 285912.995)

Oracle Support did provide code examples, and results/errors with/without
serializable isolation level set.

But there was no real explanation of underlying architecture/mechanics and not
an SCN in sight.

Thanks for following this up.

Jim

      Rating: 3
Read Only
      Transactions  June 10, 2003
Reviewer:  Sagar
       from Pune, India
Hi Tom,
     I tried to test out Read Only transactions in a pl/sql block....but I get
the following error

**************************************************

SQL> declare
  2  begin
  3  set transaction isolation level read only;
  4  end;
  5  /
set transaction isolation level read only;
                                     *
ERROR at line 3:
ORA-06550: line 3, column 38:
PLS-00103: Encountered the symbol "ONLY" when expecting one of the following:
committed

**************************************************

Can you help me out here?

Thnx
Sagar

          Followup:

execute immediate 'set ....';

or

begin
   dbms_transaction.read_only;
end;
/

      Rating: 3
Transactions
      containing DML statements with subqueries should use
      serializable  September 29, 2003
Reviewer:  Jim
      Dickson  from London, UK
How can i follow up the documentation bug you filed on this (June 2003)?

thanks

June 10, 2003 Manish -- Thanks for the question regarding
      "Table Accessed Date", version 8.1.6

      You Asked
Hi Tom

I always access your site. It gives me greate help.
This time i needing help from you.

Question
========
How can i know the last accessed date and time of tables affected by any DML
(insert,update,delete) Operation.

Please answer , I desparately needing help;

Thanks in advance.

      and we said...
you can enable auditing on those tables, you can have an audit trail record
generated for each of those operations.

you can DIY via a trigger.

you can mine the redo logs (not appropriate for hour to hour use, appropriate if
you need this info once in a while).
Review &
      Followup

      Rating: 3
Table Accessed
      Date  June 08, 2003
Reviewer:  Manish Kumar
       from New Delhi (India)
Thanks Tom,

It is useful. But it is very lenghy procedure. Does Oracle provide any v$ view ,
so that One can easily know?

Any way as i think ORACLE prodvide incremental backup of tables. This is
possible when ORACLE store someware the last accessed date of tables.

          Followup:

it is called auditing and it is not a lengthly procedure at all.

      Rating: 1
Table Accessed
      Date   June 10, 2003
Reviewer:  saanchit
      Jindal  from INDIA
Somhow I am not convinced about the responce, as auditing is the only
alternative available.

Kindly also, explain in detail the algorithum followed by Oracle to take the
incremental backup. As we feel Mr Manish Kumar has a valid argument.

Thanks/saanchit

          Followup:

then you come up with the answer, ok?

not everything is documented or available to us.

      Rating:
      5
  June 10, 2003
Reviewer:  Ashiq
      Shamsudeen  from Trichy, Tamil Nadu, India
What's the expansion for 'DIY'?

          Followup:

Do
It
Yourself

      Rating:
      4
  June 10, 2003
Reviewer:  sam
       from NY USA
Tom

what is a procedure to do redo log mining??
Can you let me know a link ??

Thanks in advance

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96521/logminer.htm#17869

June 10, 2003 --
      Thanks for the question regarding "Trigger/Revoke create session to
      prevent user logon", version 9.x

      You Asked
Tom,
We have a 3 tier schema architecture - user executes plsql in a sql schema which
talks to ddl schema .. user schema cannot talk directly to ddl schema only thru
sql schema ..
We are trying to prevent developers/testers/others  to connect directly to the
sql owner and/or the ddl owner in production ..
changing password is one option (though we do it every once in a while )
Believe me we don;t want anyone in production env. but users/clients but at
times managers act like managers .. enough said ..

we have 2 ideas a) either remove the create session privs from both the ddl and
the sql schema .. this will prevent the users from getting it .. b) set up a
logon trigger  to prevent them from logging ..

I think the end result will be the same but not sure of the pros and cons of the
2 approaches ..
Your thoughts ..
Thanks

      and we said...
neither of the 2 ideas will prevent a DBA from accessing the schema's of course
-- there will really be no way to prevent that, all you can do there is AUDIT
that it happened (audit to the OS audit trail so that the DBA's cannot "remove"
the breadcrumbs they leave behind)...

I would LOCK the accounts. alter user useraname account lock;
Review &
      Followup

      Rating: 5
Big Problem
      Man!  June 09, 2003
Reviewer:  Abdul Wahab
       from Jeddah,KSA
Dear Tom,
         I know that by now you are really fed up of answering this 'security
issue' question.But Tom this is really a big problem. Last week i found that
some important reports were taking 1 day to generate. When i looked in my
database i found some important indexes were missing which were present
before!...when they were created again reports ran in matter of minutes.
          Therefore there has to be some way to prevent the developers from
getting into database and executing dml&ddl statements.
           I don't know but..Tom is there a way by which the database could
determine the type of connection being done-is the connection from application
or is it from sqlplus?.If this can be determined, can we put a condition to lock
the account if it's from sqlplus?

          Followup:

you cannot tell the difference (no one can) between sqlplus and your app.

"cp sqlplus foobar"

or "ln -s ...."

or in windoze "xcopy sqlplus.exe yourapp.exe"

now it looks like anything else.  And even if you "fix" sqlplus -- what about
toad or anything else (eg: you can use the product user profile)

It simply sounds like you have given too many privileges to too many people.
Why do the developers have the username and password for this account on your
production machine?  CHANGE IT.  If they have the "create any *" privs on this
production machine REVOKE THEM.

Enable auditing.
Revoke the privs.
Secure the accounts.

that is the only way.

      Rating:
      4
Re:Trigger/Revoke create session to prevent user
      logon  June 09, 2003
Reviewer:
      A reader
Tom,
thanks for the response ..
DBAs are not the issue here .. it is the developers knowing the
username/password of the sql and ddl schemas ..
I think both the trigger and the locking the accounts are great idea ..
Locking the account is the easiest and will do that .. thanks

          Followup:

where did DBA's come into play?  but actually the DBA's are the issue here.

They've improperly secured the production system.  They are partially to blame
here.  They never should have told the developers any of the passwords, they
never should have granted access to the developers.

(and -- pray tell -- how do you know it was the developers?  you didn't have
auditing on -- you have no idea who did it!)

      Rating: 5
Revoke which
      priveleges?  June 10, 2003
Reviewer:  Abdul
      Wahab  from Jeddah,Saudi Arabia
Dear tom,
       I felt good when you replied my question so fast.Thanks!
       Tom, you asked me to 'revoke' the priveleges.right? But these developers
have to add,delete data.So the problem is not with the 'create any *' priv.The
problem is with the delete privs.As i told you important indexes were
dropped.Therefore i was wondering if there is way to stop a person from
performing delete unless he does it from the application.
        But i came to know the bottom line, there seems that there is nothing
more that can be done except auditing.
        If you still think that i have made the system insecure can you mention
the minimum number of privs that i can give so that the developer can
add,delete,view the data but cannot drop db objects like indexes,views,tables?.
 Thanks again. You are the 'Oracle' from oracle.;-)

          Followup:

they should NOT be allowed in this schema.

they can be permitted to modify the data, without being able to modify the
STRUCTURES the data is in.

I would even question their need to directly modify the data, that -- that is
dangerous.

You let them log in AS THEMSELVES
Their schema would have just the basic privs necessary to access the data in the
fashion you see fit.

They would not be able to drop objects in that other schema.

      Rating:
      3
PRODUCT_USER_PROFILE table  June 10, 2003

Reviewer:  A reader  from Canada
Dear Tom,

  Is it possible by using PRODUCT_USER_PROFILE table, define some restrictions
for some users? I remember I did it once to avoid developers to change the
structure via SQL*PLUS! They had the permission to do that but via Designer 2000
NOT SQL*PLUS. It worked for me. Maybe it helps our friend as well.

Regards,
Arash

          Followup:

what about toad? or <any other tool of choice here>.

it is virtually useless and only gives you a false sense of security.  it worked
to some degree years and years ago when there were two ways to get to the
database

a) sqlplus
b) write code

now there are an infinite number of ways to get there.

      Rating:
      3
  June 10, 2003
Reviewer:
      A reader
Tom,

I thought because the table has a column named ' Product'
we can define other product and commands as well. That's right, if it just works
for SQLPLUS, it's not a big deal. By the way, I asked our network administrator
to avoid installing third party tools on clients but SQL*PLUS and Designer 2000.
So it worked for me.

Have a good day
Arash

          Followup:

i can download a java gui that uses thin jdbc to get into your database in a
second.  or download toad for eval.  or whatever.

Unless you have diskless workstations.....

      Rating: 5
Why let
      developers change production data?  June 10, 2003

Reviewer:  A reader
I don't understand why developers would be allowed to change ANY production
data.
I am a developer. I designed the table structure and wrote virtualy all the
PL/SQL in our database.
But the only table I have anything but select rights to in production is an app
version table used to force users to update to the latest version of the app.
Here is the problem:
Suppose I am asked to fix a report that looks at data that is too new to be in
the development copy of the database.
So I connect our programming tool (PowerBuilder in our case) to the production
DB and fix the report.
Then I do 4 hours of paperwork.
Then I fire up PB and do some experimenting with some new code.
1, With no write rights, I notice that I get an insufficient privalages error
and switch back to the development DB.
No big deal..

2, Suppose I have insert update or delete in production.
Oops! I forgot I was connected to production! I sure hope my experiment did not
do any damage!

I don't WANT insert, update or delete in production. It protects the data and
me.

For insertion of lookup values and things like that, my request to the DBAs to
insert or modify the data forms part of the documentation of the app. It's
actualy helpfull to track these types of changes.

chuck

PS This wasn't realy a question. So, is there any data a developer should be
able to change in production?

          Followup:

not really -- not under normal circumstances.

June 10, 2003 Geoff -- Thanks for the question regarding "Using
      LAG to populate a status history", version 8.1.7

      You Asked
Hi Tom,

We have a requirement to generate a sales pipeline history table from two
tables, status_details and status. This query will be used to generate a
materialized view.

SQL> desc status_details
STD_STA_ID                             NOT NULL VARCHAR2(50)
STD_SLO_ID                             NOT NULL VARCHAR2(50)
STD_START_DATE                    NOT NULL DATE
STD_END_DATE                       DATE
STD_MILESTONE_DATE           DATE
STD_NOTES                             VARCHAR2(300)

The Milestone_date is the date the status of the sale changed. Start_date and
End_dates are audit columns.

SQL> desc status

 STA_ID         NOT NULL VARCHAR2(50)
 STA_START_DATE     NOT NULL DATE
 STA_NAME        NOT NULL VARCHAR2(20)
 STA_END_DATE    DATE
 STA_PRIORITY        NOT NULL NUMBER(3)

For any particular sale, we have a history like
SQL> ed
Wrote file afiedt.buf

  1  select std_slo_id Sale_id, sta_name status,std_milestone_date Milesone
  2  from status_details,status
  3  where std_sta_id = sta_id
  4  and std_slo_id = 'SLO_079C0A6695CF4449923B8A772FECB001'
  5* order by std_milestone_date
SQL> /

SALE_ID                                            STATUS     MILESONE
-------------------------------------------------- ---------- ---------
SLO_079C0A6695CF4449923B8A772FECB001               Identified 01-FEB-02
SLO_079C0A6695CF4449923B8A772FECB001               Contacted  22-FEB-02
SLO_079C0A6695CF4449923B8A772FECB001               Outcome    01-JUL-02

What we want to do is expand this out on a month-by month basis, up to the
current date. Using your excellent article on showing missing dates as a
starting point, we now get

select date_structure.std_slo_id sales_id,status,
DATE_STRUCTURE.MILESTONE MILESTONE
from
(
-- date structure table construct
-- based on Tom Kytes column from askTom "How to show missing date given a
range"
    select a.std_slo_id std_slo_id,
    a.milestone_date MILESTONE
    from
        (select std_slo_id,start_date+r-1 milestone_date
        from t2,
        (select
std_slo_id,min(to_date(to_char(STD_MILESTONE_DATE,'mon-yyyy'),'mon-yyyy'))
start_date,
        max(to_date(to_char(SYSDATE,'mon-yyyy'),'mon-yyyy'))
        - min(to_date(to_char(STD_MILESTONE_DATE,'mon-yyyy'),'mon-yyyy'))+1
num_days
        from status_details
        where std_slo_id ='SLO_001E661354B6479BB34382EB7CA8C562'
        group by std_slo_id) t1
        where t2.r <=t1.num_days) a,
    status_details b
    where a.std_slo_id =b.std_slo_id(+)
    and a.milestone_date = b.std_milestone_date(+)
    and to_number(to_char(a.milestone_date,'dd')) =1 -- first day of the month
only
) DATE_STRUCTURE,
(
    -- this is the relational status history construct
    select STD_SLO_ID ,STA_NAME STATUS,sta_priority,
    --rownumber StatNum,
    to_date(to_char(STD_MILESTONE_DATE,'mon-yyyy'),'mon-yyyy') MILESTONE
    from status_details,status
    where sta_id = STD_STA_ID
    order by std_slo_id,std_milestone_date,sta_priority
) STATUS_STRUCTURE
where DATE_STRUCTURE.std_slo_id = STATUS_STRUCTURE.std_slo_id(+)
and DATE_STRUCTURE.milestone = status_structure.milestone(+)
order by
date_structure.std_slo_id,DATE_STRUCTURE.MILESTONE,status_structure.sta_priority
/
SALES_ID                                           STATUS     MILESTONE
-------------------------------------------------- ---------- ---------
SLO_001E661354B6479BB34382EB7CA8C562               Identified 01-JUL-02
SLO_001E661354B6479BB34382EB7CA8C562                          01-AUG-02
SLO_001E661354B6479BB34382EB7CA8C562                          01-SEP-02
SLO_001E661354B6479BB34382EB7CA8C562                          01-OCT-02
SLO_001E661354B6479BB34382EB7CA8C562                          01-NOV-02
SLO_001E661354B6479BB34382EB7CA8C562               Contacted  01-DEC-02
SLO_001E661354B6479BB34382EB7CA8C562                          01-JAN-03
SLO_001E661354B6479BB34382EB7CA8C562               Proposed   01-FEB-03
SLO_001E661354B6479BB34382EB7CA8C562               Outcome    01-FEB-03
SLO_001E661354B6479BB34382EB7CA8C562                          01-MAR-03
SLO_001E661354B6479BB34382EB7CA8C562                          01-APR-03
SLO_001E661354B6479BB34382EB7CA8C562                          01-MAY-03
SLO_001E661354B6479BB34382EB7CA8C562                          01-JUN-03

All well and good. Here's the question - I assume we'd use LAG to fill in the
status gaps, and use arg2 (the offset into the partition) to point to the
correct previous row to populate the history, so we end up with something like

SALES_ID                                           STATUS     MILESTONE
-------------------------------------------------- ---------- ---------
SLO_001E661354B6479BB34382EB7CA8C562               Identified 01-JUL-02
SLO_001E661354B6479BB34382EB7CA8C562               Identified 01-AUG-02
SLO_001E661354B6479BB34382EB7CA8C562               Identified 01-SEP-02
SLO_001E661354B6479BB34382EB7CA8C562               Identified 01-OCT-02
SLO_001E661354B6479BB34382EB7CA8C562               Identified 01-NOV-02
SLO_001E661354B6479BB34382EB7CA8C562               Contacted  01-DEC-02
SLO_001E661354B6479BB34382EB7CA8C562               Contacted  01-JAN-03
SLO_001E661354B6479BB34382EB7CA8C562               Proposed   01-FEB-03
SLO_001E661354B6479BB34382EB7CA8C562               Outcome    01-FEB-03
SLO_001E661354B6479BB34382EB7CA8C562               Outcome   01-MAR-03
SLO_001E661354B6479BB34382EB7CA8C562               Outcome   01-APR-03
SLO_001E661354B6479BB34382EB7CA8C562               Outcome   01-MAY-03
SLO_001E661354B6479BB34382EB7CA8C562               Outcome   01-JUN-03

but we haven't quite been able to make it work. We've tried using row_number()
in a nested query to generate the correct offset, i.e.

select sales_id,
nvl(status,lag(status,theOffset)
over(partition by opportunity_id
order by opportunity_id,milestone) ),
milestone
from
(
-- first nested query
select sales_id,status,milestone,
row_number() over(    partition by date_structure.std_slo_id
            order by DATE_STRUCTURE.MILESTONE) theOffset
from
(
  -- second nested query generating the expanded sales history, as above
)
)

Any help you could give us would be much appreciated; we're just getting into
analytical functions and yes, they do rock.

Thanks in advance,

Geoff.

      and we said...
Ok, have you ever had a question that people kept asking over and over and over
again?  I do, it is this one.  In general it is "how can I find the last
non-null value in a window after sorting and populate its value in this row?"
(eg: this question)

I've always said "I don't know, beats me with a stick"....

Then it hit me, it isn't any different then finding the row in a partition that
has the "max value" -- only we need to use a sliding window.

MAX can do this for us.

You want to find the MAX milestone
In your partition (sales_id)
That has a non-null status code
And return that.

Great, here we go:

ops$tkyte@ORA920> select t.*,
  2         substr( max( decode( status, null, null,
  3                 to_char(milestone,'yyyymmddhh24miss')||status) )
  4               over (partition by sales_id order by milestone ), 15 ) status
  5    from t
  6  /

  SALES_ID STATU MILESTONE STATU
---------- ----- --------- -----
         1 aaa   01-JUL-02 aaa
         1       01-AUG-02 aaa
         1       01-SEP-02 aaa
         1       01-OCT-02 aaa
         1       01-NOV-02 aaa
         1 bbb   01-DEC-02 bbb
         1       01-JAN-03 bbb
         1 ccc   01-FEB-03 ccc
         1 ddd   01-MAR-03 ddd
         1       01-APR-03 ddd
         1       01-MAY-03 ddd
         1       01-JUN-03 ddd
         2 AAA   01-JUL-02 AAA
         2       01-AUG-02 AAA
         2       01-SEP-02 AAA
         2       01-OCT-02 AAA
         2       01-NOV-02 AAA
         2 BBB   01-DEC-02 BBB
         2       01-JAN-03 BBB
         2 CCC   01-FEB-03 CCC
         2 DDD   01-MAR-03 DDD
         2       01-APR-03 DDD
         2       01-MAY-03 DDD
         2       01-JUN-03 DDD

24 rows selected.

If status is NULL, the decode returns NULL
Else the decode returns a STRING that is sortable (yyyymmddhh24miss)

We take the MAX of that string, by partition, after sorting, upto the current
row.

We just substring out what we want.

I'm happy -- sorry to the others to whom I said "i dont think so"
Review &
      Followup

      Rating:
      4
Excellent  June 09, 2003
Reviewer:
      Reader  from US
I really keep learning new stuff everyday. I have most of things that I learnt
here compiled into a document with my comments to understand it better.
Thanks for everything.

      Rating:
      5
  June 09, 2003
Reviewer:
      A reader

      Rating:
      2
  June 09, 2003
Reviewer:
      A reader
I have a query like

select cust_no,rank,min(rank) over (partition by cust_no)
from <complex view here>

I see that the plan has a 'window sort' as the last step i.e it seems to sort
the entire result set as if I had a 'order by cust_no' at the end of the query.
This is killing performance.

Shouldnt a min() over a partition order only the rows in that partition?

Thanks

          Followup:

tell me, how can it get the MIN(rank) in a cust_no unless all of the rows for a
cust_no are there?

and further, how can it know that all of the rows for that cust_no are there,
until it gets the last one -- since you have a complex view.

      Rating:
      2
  June 09, 2003
Reviewer:
      A reader
So how can I optimize this query? When I pass in a 'and cust_no=1234' to the
view, it is blazing fast. But some apps want to run this query for all customers
and it takes forever.

Any suggestions? Thanks

          Followup:

Umm, try to make "complex view faster" I suppose?

This is sort of like a question I got yesterday.  Found it funny enough to
share:

<quote>
I know it's partial information but due to confidentiality I can't
send you the SQL but will try to send you after making it generic.

In the meantime will it be possible for you to comment on this.

15233028 rows selected.

Elapsed: 18:19:02.52

Statistics
----------------------------------------------------------
   26790071  recursive calls
       5377  db block gets
   54149676  consistent gets
    1079284  physical reads
    692  redo size
 2032284077  bytes sent via SQL*Net to client
  112724810  bytes received via SQL*Net from client
    1015537  SQL*Net roundtrips to/from client
     70  sorts (memory)
      2  sorts (disk)
   15233028  rows processed
</quote>

Yeah, I can tune that.

NOT that I'm asking you to put all of the relevant information here, don't do
that.  If I'm taking questions -- feel free to post things like, oh, "the view",
"the tables", "an english description of what the query does", "tkprof of query
with and without analytics", "autotrace traceonly explains"

      Rating:
      5
Excellent!  June 10, 2003
Reviewer:
      Geoff Duniam  from London, United Kingdom
Thanks, Tom, very elegant - solves it for us. Great forum and site, BTW. Keep up
the good work.

      Rating: 5
Just need a
      link  June 10, 2003
Reviewer:  steve
       from NYC, USA
Hi Tom,

May I have the link for:
-- based on Tom Kytes column from askTom "How to show missing date given a
range"

Thanks!

Steve

          Followup:

try this, search for

How to show missing date given a range

from asktom.oracle.com.

June 10, 2003 Karma -- Thanks for the question regarding
      "Hierarchical query", version 8.1.7

      You Asked
Hi:
I have hierarchical query where i would like to know  all parents, grand
parents, their parents etc. i.e. as connect by value is changing i would like to
know. I know in 9i we have sys_connect_by_path but i need that functionality in
8.1.7.
Is this possible ?

One approach i was thinking is to use object types but i am not too sure.

Thanks

Karma

      and we said...
you would have to write a user defined function to do it.

ops$tkyte@ORA817DEV> create or replace function scbp( p_empno in number ) return
varchar2
  2  as
  3      l_data varchar2(4000);
  4  begin
  5      for x in ( select ename
  6                   from emp
  7                  start with empno = p_empno
  8                connect by prior mgr = empno )
  9      loop
 10          l_data := l_data || '/' || x.ename;
 11      end loop;
 12      return l_data;
 13  end;
 14  /

Function created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> column ename format a15
ops$tkyte@ORA817DEV> column scbp format a40
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select rpad( '*', 2*level, '*' ) || ename ename, scbp(
empno ) scbp
  2    from emp
  3   start with mgr is null
  4  connect by prior empno = mgr
  5  /

ENAME           SCBP
--------------- ----------------------------------------
**KING          /KING
****JONES       /JONES/KING
******SCOTT     /SCOTT/JONES/KING
********ADAMS   /ADAMS/SCOTT/JONES/KING
******FORD      /FORD/JONES/KING
********SMITH   /SMITH/FORD/JONES/KING
****BLAKE       /BLAKE/KING
******ALLEN     /ALLEN/BLAKE/KING
******WARD      /WARD/BLAKE/KING
******MARTIN    /MARTIN/BLAKE/KING
******TURNER    /TURNER/BLAKE/KING
******JAMES     /JAMES/BLAKE/KING
****CLARK       /CLARK/KING
******MILLER    /MILLER/CLARK/KING

14 rows selected.

Review &
      Followup

      Rating:
      5
  June 10, 2003
Reviewer:
      A reader

      Rating: 4
hierarchial
      query  June 10, 2003
Reviewer:
      A reader
Excellent

      Rating: 3
8.1.7
      sys_connect_by_path  June 10, 2003
Reviewer:
      A reader
sys_connect_by_path is available in 8.1.7 as an undocumented feature, which can
be enabled by

alter session set "_new_connect_by_enabled" = true;

or setting this parameter in init.ora

          Followup:

i would strongly discourage that.

not to "get you to upgrade", but because as with ALL _ parameters, there can be
serious and unintentional side effects..

"just say no"

      Rating:
      5
Excellent  June 10, 2003
Reviewer:
      A reader

June 10, 2003 Andre -- Thanks for the question regarding
      "Remote Listener Registration", version 8.1.6

      You Asked
In order to achieve connection load balancing, how does remote
connection registration take place ? Could you describe the
steps in the process in which PMON and a local listener become
aware of the remote nodes and intances ?

      and we said...
PMON is associated with an instance.  PMON contacts a listener
and registers itself with that listener, making the listener
aware of the existence and status of that instance.

You configure this by setting up the mts_dispatchers init.ora
parameter.  Information contained in this parameter tells PMON
how to contact a given listener.

Review &
      Followup

      Rating: 3
how can PMON
      register the instance to the listner  June 10, 2003

Reviewer:  Jianhui  from DC
Tom,

There are several ways for PMON to get the information of the listner process,
i'm just curious of how it works.

In case of no mts_dispatchers setting, how can PMON find listner and then
register the instance?
1) Does PMON read the listner.ora in order to find the PMON?
2) Does PMON use default wellknown port 1521 to find listner?
3) If the PMON and listner are not under the same oracle_home, do we need at
least one listener for each HOME?
4) If answer to 3) is no, how can PMON find listner then?

Thank you

          Followup:

see the server reference and read about the init.ora parameters

local_listener
remote_listener

by default, it uses the "well known" and attempts those.

June 10, 2003 --
      Thanks for the question regarding "flashback using timestamp", version
      9.2.0.1

      You Asked
Hi

I am using flashback feature but I cant get it to work properly, it sometimes
works sometimes dont.

In session A I do this

SQL> select sysdate from dual;
SYSDATE
--------------------
2002-OCT-14 16:33:47
1 row selected.
SQL> insert into x select * from emp;
14 rows created.
SQL> commit;
Commit complete.
SQL> select sysdate from dual;
SYSDATE
--------------------
2002-OCT-14 16:34:40
1 row selected.
SQL> delete x;
14 rows deleted.
SQL> commit;
Commit complete.
SQL> select * from x;
no rows selected

in session B I do flashback:

SQL> exec dbms_flashback.enable_at_time(to_date('2002-OCT-14 16:33:48',
'YYYY-MON-DD HH24:MI:SS'))
PL/SQL procedure successfully completed.
SQL> select * from x;
no rows selected
SQL> exec dbms_flashback.disable;
PL/SQL procedure successfully completed.
SQL> exec dbms_flashback.enable_at_time(to_date('2002-OCT-14 16:34:30',
'YYYY-MON-DD HH24:MI:SS'))
PL/SQL procedure successfully completed.
SQL> select * from x;
no rows selected
SQL>  exec dbms_flashback.disable;
PL/SQL procedure successfully completed.

none works! when I expect to see 14 rows

why?

      and we said...
flashback based on time works in +/- 5 minute windows.

If you look into SYS.SMON_SCN_TIME, you'll find a table with 1440 rows -- If you
look further, you'll find the timestamp in there is within about 5 minutes on
each row.

Every five minutes, SMON deletes the oldest and inserts the current information
(meaning you can use the time based flash back for 5 days at best --
(1440*5)/60/24 = 5)

Flashing back by time finds the SCN that was in effect "closest" to your time.
It could be off by +/- 5 minutes.

You need to use SCN based flashbacks for a really tight window like you have.
Consider:

scott@ORA920.US.ORACLE.COM> @connect "/ as sysdba"

sys@ORA920.US.ORACLE.COM> grant execute on dbms_flashback to scott;
Grant succeeded.

sys@ORA920.US.ORACLE.COM> @connect scott/tiger

Let's make a change to SCOTT.EMP and review it using
a flashback query

scott@ORA920.US.ORACLE.COM> column scn new_val SCN format 99999999999999999999
scott@ORA920.US.ORACLE.COM> select dbms_flashback.get_system_change_number SCN
from dual
  2  /

                  SCN
---------------------
              3505986

scott@ORA920.US.ORACLE.COM> update emp set sal = sal * 1.5;
14 rows updated.

scott@ORA920.US.ORACLE.COM> commit;
Commit complete.

scott@ORA920.US.ORACLE.COM> select a.ename, a.sal new_sal, b.sal old_sal
  2    from emp a, emp as of SCN &SCN b
  3   where a.empno = b.empno
  4  /
old   2:   from emp a, emp as of SCN &SCN b
new   2:   from emp a, emp as of SCN    3505986 b

ENAME         NEW_SAL    OLD_SAL
---------- ---------- ----------
SMITH            1200        800
ALLEN            2400       1600
WARD             1875       1250
JONES          4462.5       2975
MARTIN           1875       1250
BLAKE            4275       2850
CLARK            3675       2450
SCOTT            4500       3000
KING             7500       5000
TURNER           2250       1500
ADAMS            1650       1100
JAMES            1425        950
FORD             4500       3000
MILLER           1950       1300

14 rows selected.

See, we joined EMP with itself as of the point in time RIGHT BEFORE WE DID THE
UPDATE

scott@ORA920.US.ORACLE.COM>
scott@ORA920.US.ORACLE.COM> set echo off

Now, we'll fix that update -- undoing it in effect

scott@ORA920.US.ORACLE.COM> update emp
  2     set sal = ( select sal from emp as of SCN &SCN b where b.empno =
emp.empno )
  3  /
old   2:    set sal = ( select sal from emp as of SCN &SCN b where b.empno =
emp.empno )
new   2:    set sal = ( select sal from emp as of SCN    3505986 b where b.empno
= emp.empno )

14 rows updated.

scott@ORA920.US.ORACLE.COM> commit;

Commit complete.

scott@ORA920.US.ORACLE.COM>
scott@ORA920.US.ORACLE.COM> select ename, sal from emp;

ENAME             SAL
---------- ----------
SMITH             800
ALLEN            1600
WARD             1250
JONES            2975
MARTIN           1250
BLAKE            2850
CLARK            2450
SCOTT            3000
KING             5000
TURNER           1500
ADAMS            1100
JAMES             950
FORD             3000
MILLER           1300

14 rows selected.

scott@ORA920.US.ORACLE.COM>

and then put it back...

Review &
      Followup

      Rating: 5
I did notice
      SYS.SMON_SCN_TIME  October 14, 2002
Reviewer:
      A reader
Hi

I did see this sys table. Ok then the question is solved, we have to wait for
around 5 minutes. My other question is when I tried to use enable_at_time in
dbms_flahsback I always have to put the timestamp from SYS.SMON_SCN_TIME, if I
put it any other time then it does not work, is this expected behaviour?

I doubt we would use SCN for dbms_flashback, if ever anything happens user would
tell us the time not the SCN right?

          Followup:

I think you would almost ALWAYS use SCN for flashback.

Think about when a PROGRAM might want to flashback.... to join the data before
an update with the data after the update.... -- get the SCN, update, join.  No
time there.

Or maybe to UNDO an update AFTER the user said "ok" 5 times on the web...   You
want to update  the data back to what it was -- well, YOU would have saved the
SCN for them right before the update.

Think about when you might want to flashback....  After finding the egregious
statment in the logs (which will be accompanied by... SCN)

Timed based flashback is almost of academic or demo interest...  Yes, the use
might tell us to flashback but your program will remember points of interest to
them and capture the SCN.

This:

ops$tkyte@ORA920.LOCALHOST> exec dbms_flashback.enable_at_time( sysdate-1/24 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.LOCALHOST> exec dbms_flashback.disable;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.LOCALHOST> exec dbms_lock.sleep(15);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.LOCALHOST> exec dbms_flashback.enable_at_time( sysdate-1/24 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.LOCALHOST> exec dbms_flashback.disable;

PL/SQL procedure successfully completed.

shows that you need not pick an exact time -- I flashed an "hour" back but
neither of those times (at least ONE OF THEM anyway) were in the scn table.

      Rating: 4
just another
      thing  October 14, 2002
Reviewer:
      A reader
Hi again

I noticed you have not used dbms_flashback to enable or disable flashback,
instead you used

from .... , emp of SCN xxx

is that new in 9i too :-?

          Followup:

Yes, that is new in "9i release TWO" (play on words ;)

      Rating: 5
sorry to bother
      again but  October 15, 2002
Reviewer:
      A reader
Hi

I would like know if timestamp from SYS.SMON_SCN_TIME must be used...

          Followup:

I thought that I showed -- NO, it does not.

So, I'll just say it, NO, it does not.  We will find the time CLOSEST to the
time you asked for and use the SCN from there.

      Rating:
      5
flashback  June 10, 2003
Reviewer:
      Reader
Tom, I tried your example but i think i am making some mistake somewhere but
could not figure it out what ithe mistake is. This example you gave in another
thread but for some reason when i searched asktom i could not locate it so i am
posting it in this thread. Kindly help. Thanks. I am using 9.2.0.

SQL> connect system/manager;
Connected.

SQL> show parameter undo

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
undo_management                      string      AUTO
undo_retention                       integer     900
undo_suppress_errors                 boolean     FALSE
undo_tablespace                      string      UNDOTBS
SQL> connect hr/hr;
Connected.
SQL> create table employee_test as select * from employees
  2  ;

Table created.

SQL> update employee_test set salary = salary * 1.5;

107 rows updated.

SQL> commit;

Commit complete.

SQL> pause

SQL> select a.last_name, a.salary new_sal, b.salary old_sal
from employee_test a, employee_test as of timestamp sysdate-5/24 b
where a.employee_id = b.employee_id
/  2    3    4
from employee_test a, employee_test as of timestamp sysdate-5/24 b
     *
ERROR at line 2:
ORA-01466: unable to read data - table definition has changed

          Followup:

you have to wait a bit before flashing back on a newly created object.

create the table, get a cup of coffee (take about 5 minutes please) and then
come back.

      Rating:
      5
correction  June 10, 2003
Reviewer:
      Reader
In the above, i wanted to mention that in your example, you used emp table in
scott schema.

June 10, 2003 Muhammad -- Thanks for the question regarding
      "Cursor Memory", version 8.1.7

      You Asked
Please explain the handling of cursor in the memory by oracle in detail.
if i open a cursor of 10 million row
1)how much memory will be used and how to find the memory used.

2)how many row will be in the memory for the cursor at given time.

Thanks

      and we said...
none really, we read the data for that query from the buffer cache or disk as
needed.  If you query a 10 million row table with:

select * from ten_million_row_table;

you'll get data back immediately.  it is not like we stuff the result somewhere.

if you do something like:

select a, count(*) from ten-million_row_table group by a;

then

- if a has lots and lots of values and doesn't fit into sort_area_size space
along with count(*), we'll swap to temp

- if a has few values and fits in sort_area_size, we'll build the result in ram.
 then we look at sort_area_retained_size and figure out if we need to save some
of the results to disk or keep it all in ram.
Review &
      Followup

      Rating: 4
That is precise
      explanation  June 10, 2003
Reviewer:  oracle
      fan  from Canada
Now, I get much better understanding why we need sort_area_retained_size  ;-)

thanks TOM.


Subject | Article | Last Updated (East coast time)
size of initial extent of temporary tablespace | article only | Jun 11, 2003 06:27 am
configuring >1 listener on unix | article only | Jun 11, 2003 07:02 am
Order of rows returned by a simple select | article only | Jun 11, 2003 07:01 pm
Expert DBA | article only | Jun 12, 2003 08:46 am
How are varchar stored internally in a block or page. | article only | Jun 12, 2003 08:50 am
multi-table insert | article only | Jun 12, 2003 09:14 am
Oracle Fail Safe and Dataguard | article only | Jun 12, 2003 11:14 am
Default Directory | article only | Jun 12, 2003 01:01 pm
How do I clear the memory consumed by temporary lobs ? | article only | Jun 12, 2003 03:49 pm
Help - Inner Join | article only | Jun 12, 2003 04:17 pm
Refresh Group Architecture to minimise table locks | article only | Jun 12, 2003 04:22 pm
block level info | article only | Jun 12, 2003 04:27 pm
Returning cursor from stored procedure using dynamic sql | article only | Jun 12, 2003 08:02 pm
how to find my sessions record in v$session | article only | Jun 12, 2003 08:33 pm
ORA-01025: UPI parameter out of range | article only | Jun 12, 2003 08:34 pm
Index Not used on a null column | article only | Jun 13, 2003 08:08 am
How to compare two tables of data???? | article only | Jun 13, 2003 08:24 am
A method for replicating logging tables into reporting tables | article only | Jun 13, 2003 08:50 am
Working closely with Developersso | article only | Jun 13, 2003 08:58 am
Unauthorized DDL Audit | article only | Jun 13, 2003 09:56 am
Listener problem Oracle on Linux | article only | Jun 13, 2003 12:31 pm
De-duping customers | article only | Jun 13, 2003 02:23 pm
trigger logic | article only | Jun 13, 2003 03:46 pm
How do I execute an executable from PLSQL | article only | Jun 14, 2003 01:42 pm
Auditing -- where are the records visible and how to find out whats enabled. | article only | Jun 14, 2003 01:49 pm
Dynamic LDAP-Entries with OID dependent on table data? | article only | Jun 14, 2003 01:55 pm
sga size | article only | Jun 14, 2003 07:25 pm
criteria for selecting a partition key | article only | Jun 15, 2003 09:43 am
Migration steps from oralce 8i to 9i | article only | Jun 15, 2003 11:09 am
I am doing exp/imp without data. But still import is increasing the tablespace size | article only | Jun 15, 2003 12:51 pm
check constraint | article only | Jun 15, 2003 07:44 pm
Descending Indexes | article only | Jun 15, 2003 08:10 pm
two questions | article only | Jun 15, 2003 08:24 pm
Is there a better way to do this insert | article only | Jun 16, 2003 08:11 am
How to track select activity on a table without using database audit trails. | article only | Jun 16, 2003 09:02 am
How to register a new db with an new/existing listener? | article only | Jun 16, 2003 10:10 am
SSL | article only | Jun 16, 2003 10:52 am
Create a table hierarchy for an entire schema | article only | Jun 16, 2003 11:48 am
How to pass a '&' in url. | article only | Jun 17, 2003 06:38 am
Oracle 9i database performance tuning guid and reference | article only | Jun 17, 2003 06:43 am
Automate loading of data from Excel spreadsheets into database | article only | Jun 17, 2003 07:05 am
Required components for client-side access to DB.... | article only | Jun 17, 2003 07:09 am
Snapshot Log Column change_vector$$ | article only | Jun 17, 2003 07:22 am
Query Results in One row from Group of Rows | article only | Jun 17, 2003 07:23 am
Prevention and Detection of database corruption | article only | Jun 17, 2003 07:24 am
How to use Java to perform TO_LOB functionality over a link | article only | Jun 17, 2003 07:27 am
external table | article only | Jun 17, 2003 12:28 pm
Replicating records with where clause | article only | Jun 17, 2003 12:43 pm
Compressed Indexes & Tables | article only | Jun 17, 2003 12:47 pm
Value column in v$sesstat | article only | Jun 17, 2003 12:51 pm
dynamic sql in pl*sql in V8 | article only | Jun 17, 2003 12:52 pm
Kill a session from database procedure | article only | Jun 17, 2003 04:36 pm
row(s) 1 - 52 of 52
June 11, 2003 wallacel -- Thanks for the question regarding "size of initial extent of temporary tablespace", version 8.1.6
You Asked
Tom:

some document says that :
<quota>
since temporay segments were created to handle the sorts
that cannot be handled in memory,the initial
extent default for temporary segment should be at
least as large as the value of SORT_AREA_SIZE.
This will minimize extension of the segment.
</quota>

but since oracle suggest use local managed tablespace
for temporary tablespace, and i use automate allocated
tablespace, how should i set it up?
or i can just ignore the statement above?
and we said...
I would not use an auto allocate tablespace for temp.  I use
UNIFORM.

Make sure you use a uniform extent size that is a multiple of
your SORT_AREA_SIZE for the temporary tablspace.  Don't use
automated, use uniform and have it be a nice multiple of the
sort_area_size.
Review & Followup
Rating: 3
aligning temp uniform extents and sort_area_size November 19, 2001
Reviewer: Wolfgang Breitling from Calgary, Canada
Sorry if this is not the correct way to ask a follow-up question but I didn't
find another way, except asking a new question.

You say that uniform extent should be a multiple of sort area size. Making it
less (say 1/2, 1/4, 1/8) is inefficient since Oracle will need to allocate
multiple extents when sorting. For a permanent tablespace I would wholeheartedly
agree, but for a temporary tablespace does it really matter? The extents are
pre-allocated anyway (after the first big sort) and the nr of sort segments used
shouldn't matter (within reason). Having smaller extents can become useful once
you use temporary tables.

I only recently discovered this site - after buying your book. That's why I pose
this follow-up to such an old question.

Followup:
I still think you want your temp extents to be multiples of your sort area size
(and if you don't -- you can find yourself hitting issues with hash joins and
such, beware -- the algorithm for some reason doesn't look at the next extent
size in temp, assumes its big enough and you'll get an ORA error if its not).
Think of it in OS terms -- you want the "page size" of your temp segment to be
the same size of what you are swapping in and out (that after is really what
temp is all about -- swap).

In most systems, sort_area_size is smallish, 1/2 to 1 meg or LESS.  In large
data warehouses, it may be 10's of megs but then -- that may be totally
appropriate for your temporary tables if you use them at all (I for one find
them to be of very limited use, this isn't sqlserver here).

As your sort_area_size goes up -- I'm assuming so does the amount of data you
are sorting.  Here a small temporary extent size could be an issue as you would
get into 10's or even 100's of thousands of extents.

I would still stick with the make it a multiple - especially in your oltp system
were sorts should be small anyway in general (i frequently run with a 64k sort
area size still) and you don't even use temp very much.

Rating: 4
Plus block_size? May 30, 2003
Reviewer: Alex Daher from Brazil
Please, correct me if I am wrong: Every extent (permanent or temporary) has a
control block, with transaction, occupation information and stuff.

If so, the extents of temp tablespace should be a multiple of sort_area_size
plus block_size, right?

Like:
temp extent = n*sort_area_size + db_block_size

Followup:
the first extent of a segment does, not every one.

Rating: 4
How to set extents size if using pga_aggregate_target May 30, 2003
Reviewer: Rob from Piitsburgh, PA
Tom:

We are using the automated memory management feature of 9i  9.2.0.1) and setting
pga_aggregate_target rather than sort_area_size, hash_area_size etc. What do you
recommend for the uniform extent size of the temp tablespace in this case?

Followup:
1 megabyte

Rating: 3
what happens if the temp tbs fills up ? June 10, 2003
Reviewer: Alvin from Philippines
What happens if the temp tbs usage is around 100% ? we've been experiencing
slowness.

09:39:28 sys@RTBE.SUNFIRE01> select * from v$waitstat;

CLASS                   COUNT       TIME
------------------ ---------- ----------
data block           46809583   36751149
sort block                  0          0
save undo block             0          0
segment header             25         84
save undo header            0          0
free list                   0          0
extent map                  0          0
bitmap block                0          0
bitmap index block          0          0
unused                      0          0
system undo header          0          0
system undo block           0          0
undo header              4995     467693
undo block              18234      32452

Tablespace Name        KBytes         Used         Free   Used      Largest
---------------- ------------ ------------ ------------ ------ ------------
DRSYS                  20,480            8       20,472     .0       20,472
RBS                   564,224      118,792      445,432   21.1       28,672
TOOLS                 102,400       61,992       40,408   60.5       34,880
SYSTEM              4,096,000    2,722,560    1,373,440   66.5      327,744
INDX                  563,200      479,624       83,576   85.2        5,120
USERS               5,475,840    5,272,968      202,872   96.3      202,872
TEMP                1,429,120    1,428,936          184  100.0          184
                 ------------ ------------ ------------
sum                12,251,264   10,084,880    2,166,384

are those block contentions for the temp blocks ?

Followup:
temp should always be at about 100%, that does not mean it is "full"
(v$sort_usage and v$sort_segment will tell you what is actually USED versus
"just allocated")

Now, not having a single clue if that v$waitstat information represents 5
minutes, 5 days, 5 months, it is hard to say -- but it does look like you have
probable free list contention.

do you know how to run statspack?  if not, see the performance guide, use it.

Rating: 5
I did run statspack.... June 10, 2003
Reviewer: Alvin from Philippines
1. How would i know which table/s freelist i should increase ?will i based it on
the insert statements captured by statspack during the slowdown period ?

2. Do i have to increase the freelist of the indexes associated with the table ?

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
direct path write                                  23,556       43,731   55.22
direct path read                                   30,355       30,375   38.36
log file sync                                         410        1,390    1.76
latch free                                          1,961        1,211    1.53
log file parallel write                               458          768     .97
          -------------------------------------------------------------
 Wait Events for DB: xxxx  Instance: xxxx  Snaps: 41 -42
-> cs - centisecond -  100th of a second
-> ms - millisecond - 1000th of a second
-> ordered by wait time desc, waits desc (idle events last)

                                                                    Avg
                                                     Total Wait    wait  Waits
Event                               Waits   Timeouts  Time (cs)    (ms)   /txn
---------------------------- ------------ ---------- ----------- ------ ------
direct path write                  23,556          0      43,731     19   58.7
direct path read                   30,355          0      30,375     10   75.7
log file sync                         410          0       1,390     34    1.0
latch free                          1,961      1,245       1,211      6    4.9
log file parallel write               458          0         768     17    1.1
db file parallel write                 42          0         553    132    0.1
control file parallel write           272          0         543     20    0.7
db file sequential read               254          0         416     16    0.6
log file switch completion              2          0          66    330    0.0
log file sequential read              136          0          43      3    0.3
log buffer space                        3          0          35    117    0.0
log file single write                   8          0          27     34    0.0
SQL*Net more data to client         1,066          0          15      0    2.7
control file sequential read          120          0           9      1    0.3
file identify                          16          0           3      2    0.0
file open                              61          0           1      0    0.2
SQL*Net break/reset to clien            2          0           1      5    0.0
refresh controlfile command            14          0           0      0    0.0
LGWR wait for redo copy                 9          0           0      0    0.0
SQL*Net message from client       153,351          0   2,018,983    132  382.4
SQL*Net more data from clien           16          0          80     50    0.0
SQL*Net message to client         153,355          0          27      0  382.4
          -------------------------------------------------------------
 Background Wait Events for DB: xxxx  Instance: xxxx  Snaps: 41 -42

Followup:
1) those inserts will certainly help.  You should have a fairly good idea what
tables are being inserted into so heavily don't you?

2) looking at that statspack however, it is not the cause of your problems, if
you are having any.  not during the period of time you observed anyway.  Which
unfortunatly you removed from the report.  Now I do not know how long this
report is for.  If it is more then 15-30 minutes, it is too long.

based on the direct path read/write -- i would guess you sort to disk too much.
You might look there.

June 11, 2003 Ramana -- Thanks for the question regarding "configuring >1 listener on unix", version
You Asked
How do I configure multiple listeners
for my database running on unix. The
requirement is something like, configure
two different listeners one on port 1521
and the other on port 1526, so that users
can connect by using either of ports.
Ultimately this is supposed to be used for
application segregation.

Thanks
Ramana Damarla
DBA

setenv TNS_ADMIN `pwd`
lsnrctl start
cd ..
!set
!ls
and we said...
Yes.  you just need 1 install and >1 listener.ora file.

You would use the TNS_ADMIN environment variable to tell each
listener where to get its configuration from.  The TNS_ADMIN
environment variable overrides the default directory for the
network configuration files.

This is a common requirement for users who run "extprocs"
(external procedures) in oracle8.0 and up for example.  Since an
extproc runs as the userid who started the listener, it is
common to setup a listener that runs as the user 'nobody' or
some other low priv'ed account to spawn the extprocs.  A
'regular' listener listens for all other inbound requests.

Here is a 7.3.4 example I just did.  I copied my listener.ora
from network/admin to network/admin/another.  I changed the PORT
and the KEY in the second one.  Then:

Thu Feb 10 19:32:18 EST 2000
(ora734@slackdog) /d01/home/ora734/network/admin/another

Thu Feb 10 19:32:18 EST 2000
(ora734@slackdog) /d01/home/ora734/network/admin/another

LSNRCTL for Solaris: Version 2.3.4.0.0 - Production on 10-FEB-00
19:32:43

Copyright (c) Oracle Corporation 1994.  All rights reserved.

Starting /d01/home/ora734/bin/tnslsnr: please wait...

TNSLSNR for Solaris: Version 2.3.4.0.0 - Production
System parameter file is
/d01/home/ora734/network/admin/another/listener.ora
Log messages written to
/d01/home/ora734/network/log/listener.log
Listening on: (ADDRESS=(PROTOCOL=ipc)(DEV=10)(KEY=ora734))
Listening on:
(ADDRESS=(PROTOCOL=tcp)(DEV=14)(HOST=138.2.5.206)(PORT=1566))

Connecting to (ADDRESS=(PROTOCOL=IPC)(KEY=ora734))
STATUS of the LISTENER
------------------------
Alias                     LISTENER
Version                   TNSLSNR for Solaris: Version 2.3.4.0.0
- Production
Start Date                10-FEB-00 19:32:46
Uptime                    0 days 0 hr. 0 min. 0 sec
Trace Level               off
Security                  OFF
SNMP                      OFF
Listener Parameter File
/d01/home/ora734/network/admin/another/listener.ora
Listener Log File
/d01/home/ora734/network/log/listener.log
Services Summary...
  ora734                has 1 service handler(s)
The command completed successfully

So, that shows I have a listener on port 1566 and the key
(listener name) is ora734.  Now I cd and do it over:

Thu Feb 10 19:32:18 EST 2000
(ora734@slackdog) /d01/home/ora734/network/admin/another

Thu Feb 10 19:32:49 EST 2000
(ora734@slackdog) /d01/home/ora734/network/admin
setenv TNS_ADMIN `pwd`

Thu Feb 10 19:32:49 EST 2000
(ora734@slackdog) /d01/home/ora734/network/admin
lsnrctl start

LSNRCTL for Solaris: Version 2.3.4.0.0 - Production on 10-FEB-00
19:32:56

Copyright (c) Oracle Corporation 1994.  All rights reserved.

Starting /d01/home/ora734/bin/tnslsnr: please wait...

TNSLSNR for Solaris: Version 2.3.4.0.0 - Production
System parameter file is
/d01/home/ora734/network/admin/listener.ora
Log messages written to
/d01/home/ora734/network/log/listener.log
Listening on: (ADDRESS=(PROTOCOL=ipc)(DEV=10)(KEY=ora734a))
Listening on:
(ADDRESS=(PROTOCOL=tcp)(DEV=14)(HOST=138.2.5.206)(PORT=1525))

Connecting to (ADDRESS=(PROTOCOL=IPC)(KEY=ora734a))
STATUS of the LISTENER
------------------------
Alias                     LISTENER
Version                   TNSLSNR for Solaris: Version 2.3.4.0.0
- Production
Start Date                10-FEB-00 19:32:59
Uptime                    0 days 0 hr. 0 min. 0 sec
Trace Level               off
Security                  OFF
SNMP                      OFF
Listener Parameter File
/d01/home/ora734/network/admin/listener.ora
Listener Log File
/d01/home/ora734/network/log/listener.log
Services Summary...
  ora734                has 1 service handler(s)
The command completed successfully

Now, i've got a second listener on port 1525 named ora734a.
Review & Followup
Rating: 5
question November 18, 2002
Reviewer: atul from india
Sir,

How to set other listener as other user (nobody)..
and how to start it by logging it to other user??

Thanks.
atul

Followup:
You log into the other user and you start it.

su - nobody -c <some script>

as root for example.  You would create a script that sets up the proper
environment (oracle_home, path, etc) and run lsnrctl start

Rating: 4
Listening multiple ports on one listener June 11, 2003
Reviewer: Ogun from Turkey
Hi Tom,

We are using MTS, and we want to configure a listener that listens two ports.
Our aim is to configure one port to listen connections as dedicated, and the
other port to listen connections as shared. What is the way to do this ?

Followup:
see the net admin guide for details on configuring a listener.ora file OR use
the net assistant tool (gui) to do it.

June 11, 2003 -- Thanks for the question regarding "Order of rows returned by a simple select", version 9.x
You Asked
Tom, Man am I lucky to get 2 questions in 2 days .. Wow ..
A simple question:
It is generally said that one should not rely on the order of rows returned by a
select .. Ie. you may not always get the rows returned in the order they were
inputted/keyed in ..
Example: entered values - 1,2,3,4 in a table ..
doing a select on that table may not give me 1,2,3,4 .. the order is
unpredictable ..
Is that correct ? If yes, why ?
Or am I going bonkers here ?
Thanks
and we said...
It is not only "generally said", it is most EMPHATICALLY STATED -- never rely on
the order of rows in a result set unless you have an ORDER BY.

That is completely true.  Consider:

ops$tkyte@ORA920LAP> create table t ( x int primary key, y int ) organization
index;

Table created.

ops$tkyte@ORA920LAP> insert into t values ( 4, 4 );

1 row created.

ops$tkyte@ORA920LAP> insert into t values ( 3, 3 );

1 row created.

ops$tkyte@ORA920LAP> insert into t values ( 2, 2 );

1 row created.

ops$tkyte@ORA920LAP> insert into t values ( 1, 1 );

1 row created.

ops$tkyte@ORA920LAP> select * from t;

         X          Y
---------- ----------
         1          1
         2          2
         3          3
         4          4

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> drop table t;

Table dropped.

ops$tkyte@ORA920LAP> create table t ( x int primary key, a char(2000), b
char(2000), c char(2000) );

Table created.

ops$tkyte@ORA920LAP> insert into t values ( 1, 'x','x','x' );

1 row created.

ops$tkyte@ORA920LAP> insert into t values ( 2, 'x','x','x' );

1 row created.

ops$tkyte@ORA920LAP> commit;

Commit complete.

ops$tkyte@ORA920LAP> insert into t values ( 3, null, null, null );

1 row created.

ops$tkyte@ORA920LAP> select x from t;

         X
----------
         1
         3
         2

ops$tkyte@ORA920LAP>

databases return data as they see fit, only if made to return data in some
order will they
Review & Followup
Rating: 5
June 09, 2003
Reviewer: A reader

Rating: 4
Select w/group by June 11, 2003
Reviewer: Kurt Held from Vienna, Austria
Hi Tom

recentenly I had to debug code like the following:

declare
cursor c is
select dept.deptno dept_akt, job, sum(sal), min(sal),
       and some more expressions a b c d e
from   dept, emp
where  emp.deptno = dept.deptno
group by dept.deptno, job and and some more expressions
       a c b e d -- but the same effect when group by a b c d e
-- and no order by clause :-(
;
dept_b4 number := -1;
begin
  FOR r IN c LOOP
    if r.dept_akt <> dept_b4 then
      do something one time per dept
    end if;
    dept_b4:= r.dept_akt
    do some other calculation;
  end loop;
end;

Actually code joins 7 tables (1 outer joined) and selects 15 expressions

Guess what?

Because there is no order by clause in the cursor,
the code intented to be executed one time per dept
was executed several times for some dept´s....

I did some experiments with the query and discovered,
when I execute select w/o expresssions a .. e
output is strictly orderd by deptno but if the query gets
more complicated output is more or less random.

Execution plan is exactly the same for both query (SORT GROUP BY)

Tom, can you explain, why the output of the
query is orderd by deptno without the
expresssions a .. e, but if we select and group
by more expression, ordererd output gets lost.

Greetings
Kurt from Austria

Followup:
well, that is simple -- trivial actually.

unless there is an order on a query, you have no right, no ability to expect
the data to come back in any order.  It will come back to you in any order we
choose.

without an order by, you get the data as we feel like giving it to you.

June 12, 2003 Robert -- Thanks for the question regarding "Expert DBA", version 8.1.7
You Asked
Hi Tom,

If one were to become proficient with Personal Oracle (8i or 9i).
Would these database skills transfer to an Oracle database (Enterprise Edition)
on a Unix box ?

My question has at least 3 categories...
1. Is Personal Oracle *exactly* like 'regular' oracle EE (if not, is it
different in critical areas ?).
2. Are there many differences in administrating a personl Oracle database, vs. a
Unix database (besides the volumes and sizes) ?
3. Is Personal Oracle 9i a legitimate way to learn the 9i features (we don't
have 9i installed in our environment, yet).

Bonus question: Does Oracle really care if I download 9i onto my PC at home and
use it indefinitly ? (the 90 day agreement notwithstanding)... Isn't that
limitation just for corporate users? after all, by using their product at home,
I am helping deepen and possibly broaden thier base.

Please be as verbose as possible!

Thanks,

Robert.
and we said...
1) if you forget for a moment that you'll be mostly "single user", yes, PE is
the same.

But much of what a DBA/developer has to deal with is multi-user stuff.  So,
you'll be lacking in that area -- but the fundementals will be there.

It'll be like you were taught to drive a car in the country.
And now you are in New York City...

2) not really.

3) yes, sure, i learn most of the features myself on a single user machine and
then when I've gotten comfortable with them -- I apply that knowledge on the
"real thing"

Bonus Question:

With regard to licensing issues, the OTN License Agreement text that you read
(and agree to) on downloading software from Technet states ...

"We grant you a nonexclusive, nontransferable limited license to use the
programs only for purposes of developing and prototyping your applications, and
not for any other purpose.

...

You may not:
·use the programs for your own internal data processing or for any commercial or

production purposes, or use the programs for any purpose except the development
and prototyping of your applications;
·use the applications you develop with the programs for any internal data
processing or commercial or production purposes without securing an appropriate
license from us;
...

etc"

... which I read to mean that you can install on as many desktops that want,
as long as it is solely for the purpose of development. I think you are covered
for the purposes being discussed here.
Review & Followup
Rating: 5
February 05, 2003
Reviewer: Dave from Ex-Ohio. now Colorado
Tom, your answer to the bonus question was absolutely excellent. I couldn't have
said it better myself, if you know what I mean ;-)

Rating: 5
Thanks! February 06, 2003
Reviewer: Robert from Memphis, USA.
Tom,

As usual your valuable advice is most appriciated!

Tom, not to be ungrateful with your great response...But I've got a follow up
which veers of the original question by 20 degrees or so... but I'll give it a
shot...

With all the automation ya'll are putting into the database now... What do you
see as the role of the DBA 5 years from now... how will it differ from that of
today?

Thanks for your time and expertise,

Robert.

Followup:
well, look back 5 years -- it was 1998, version 8.0.5 was production....  What
did a DBA do back then?

o backup and recover <<<== most important job ever
o tune
o install/patch
o plan for future growth

and the databases were in the hundreds of gigabyte ranges at the high end,
hundreds of megabytes at the low end.

Today -- what do they do?  well, pretty much the same thing only

o for more users than ever before
o for larger databases than ever before
o for more uptime than ever before

The automation of some features is what permits them to do the same job on
bigger, more used, more frequently used databases and keep their heads above
water.

I'm pretty sure in 5 years, DBA will be doing:

o backup and recover <<<== most important job ever
o tune
o install/patch
o plan for future growth

just for much much much larger databases, with even more concurrent users, with
less ability to have planned downtime -- maybe no downtime ever.

Rating: 5
dont agree with ya tom February 07, 2003
Reviewer: hrishy from PA
Hi Tom

I dont think so..that DBA's now just do backups..if that was the case OCP would
have included only backup n recovery..as a DBA i feel you should now

1)How to tune
2)guide developers
3)Know a fair amount of java..how to load java into oracle database
4)what is stateless connection ?
5)fair amount of webserver..

i am not makin these things up..these were some of the things that were asked to
me when i was there for a interview..take this site for example..this site is
meant mostly for developers..but many DBAS also visit this place..i fall back o
your book many times..

maybe 5 years ago a dba was doin backup..well those dbas who still do only
backup..well i think these guys better watch out :-D

Followup:
did I say they only did backups?

No, I said the MOST IMPORTANT THING they do is backups.  then there was a list
of other stuff.

...
o tune
o install/patch
o plan for future growth
......

I agree with 1 and 2 (don't see them as different really)

#3 -- you need not know a bit of java to know how to use the tools to put java
in the database when needed.  Do the DBAs know C?  They don't need to -- yet
they configure interMedia which is C code.  Same thing, they need not know java
to manage a database.

#4 -- why?

#5 -- if they have to admin an app server sure, but now you are simply saying
"sorry, you are not just a DBA anymore, you are a DBA and App server Admin".

Could you add to your list

o configure disks
o set up the stripes
o configure the kernel
o all other things OS related

sure -- but then you are saying "you are DBA, SA and App server admin".

So, I never said they only do backups -- that is the most important thing they
do.  What you are describing however is the jack of all trades -- fair enough,
but that is not a DBA, it is DBA/SA/App Server Admin/etc etc/etc

Rating: 5
Development DBA February 07, 2003
Reviewer: A reader from Austin
I am one of two DBAs at my organization.  We each act as primary on a different
set of prod, dev and test databases.

Since I love DBA stuff, but I also love development stuff, I have toyed with the
idea of suggesting that I become the development DBA and the other DBA (who is
not as development-oriented) be the production DBA.

In your opinion, would a move like this be a "self-demotion" career-wise in any
way?

Followup:
you gotta do what you like to do.

No, i don't see it as a "demotion".

Rating: 5
It's funny, but... February 07, 2003
Reviewer: Jan van Mourik from Houston, TX
>>> sure -- but then you are saying "you are DBA, SA and App server admin".

It looks like a bunch of companies, smaller ones of course, are exactly looking
for that! One stop shopping. "We are looking for a dba who can do Unix stuff
too. Oh, SqlServer knowledge is a plus! And Oracle Apps knowledge is a plus".
With the jobmarket being as bad as it is, employers can pretty much ask for
anything...

Rating: 3
seeking career advice June 11, 2003
Reviewer: anurag from INDIA
Hi!, I am planing my career in database. Currently working as a MIS executive in
private firm in INDIA. Would like your advice in seeking expert guidelines, to
further my career as Oracle DBA. I am handling oracle since last 2 years in
developing application in D2k and managing tit-bit jobs of storage allocation,
mirroring, backup etc.

your advice in this regard will be very helpful.

thanks and regards,

Anurag

Followup:
the only way I know is to become one.  get an entry level job, have a good
mentor (that is more important then anything), and work for 3-5 years.  Get in
with a company that keeps up (you don't want to be doing version 7.3 5 years
from now) and does interesting, big things.

Rating: 5
Development DBA June 11, 2003
Reviewer: Hardworking DBA from SanJose,CA,USA
Tom

I am in the same boat as the reader from Austin. I love
being a DBA (I like this technical stuff). Currently i
support about 30 developers (down from about 60)
(with Java/PlSQL/Sometimes PowerBuilder/OracleMigration
WorkBench/Conversion to Sybase/Microsoft tools etc...)
besides being a DBA.  My question is , though i love being
a Dev.Support DBA, i want to see myself in the shoes of a
Prod.DBA , to my market value.
I agree with your feedback on may occasions that
the foremost thing for ANY dba is Backup Recovery. After
all, the DBAs are paid to "Protect" the data in the first
place. I would like to add Standby db, RAC(optional?)
,Replication in that order which a Prodn.DBA needs to be
aware of.  What are your suggestions for a Development
DBA to make a "move" to Prodn.DBA?  I also feel, it is
of importance for  a Dev.DBA to have allround knowledge of
Database, 3rd party tools, some amt of SQL/PlSQL/JDBC
connectivity + some tools like OMWB. As i told earlier
i also had to learn Sybase which i did, where as in the
case of a Prodn.DBA it seems to be more focussed on some
areas as mentioned above.

Can you give your suggestions on this.

Thanks in Advance

Followup:
I myself have always failed to see the difference between the two.  They operate
in the same buildings, using the same equipment and report to the same managers
generally.

I've never made a distinction between the two personally so cannot really say
how to "move from one to the other"

Rating: 5
Developent DBA June 11, 2003
Reviewer: Hardworking DBA from SanJose,CA,USA
Corrections to my earlier posting.
READ
<quote>
it is of importance for  a Dev.DBA to have allround knowledge of Database, 3rd
party tools, some amt of
SQL/PlSQL/JDBC
</quote>

AS
<quote>
it is of importance for  a Dev.DBA to have allround knowledge of Database, 3rd
party tools, **LOT OF**
SQL/PLSQL/UnixShellscripting
</quote> and some of VB/JDBC/ODBC etc

Rating: 5
DBA Career June 12, 2003
Reviewer: Fahad from Jeddah,KSA
Hi Tom,
    Thanks for giving us your valuable advices. I am very much interested in the
DBA stuff. To make a career in it i have also joined a firm as a dba.But our
company is a consultant.It dosn't have a production database for itself. We give
customer support who have the production DB.
    My request is can you advice me how to steer my career on a correct track?.
With reference to your quote    "Get in
with a company that keeps up (you don't want to be doing version 7.3 5 years
from now) and does interesting, big things."
 What big and interesting things are to be done in oracle at present?
 thank you

Followup:
interesting is in the eye of the beholder isn't it?

You might find data warehousing on a grand scale to be interesting and big.
Healthcare is doing a ton of that for example.

You might find high end oltp is cool.  Financial services is always doing that.

You might find running applications for a large install base (the proverbial
mixed workload environment -- oltp and lots of batch) to be the "cats meow"

(i like the oltp stuff myself, the others don't excite me personally as much)

Rating: 4
Thanks Tom!! June 12, 2003
Reviewer: anurag from INDIA
Highly thankful for perfect guidance. One should have a role model in their mind
while working for career and I would suggest U as a model for all future DBA's.

thanks once again.

June 12, 2003 Kelly -- Thanks for the question regarding "How are varchar stored internally in a block or page.", version 8.1.7
You Asked
Tom,
I work with a modelers group, who would like to define every varchar2 field with
the maximum length.  Which means that a table with 20 varchar2 fields will all
be defined with a maximum of 2000 or 4000 bytes.
I tied to talk to him about the reasons we identify data with correct lengths
and names inorder to understand what we have in our database.  He told me that
it doesn't matter as oracle just stores the length etc and there is no overhead.
 I don't believe this is true but have been jumping between so many databases
that I cannot find a document on the internals of oracle storage.
Can you help me out here with this question?
Thanks
and we said...
This is your "data modeler" -- my goodness.  They are the ones that are supposed
to be telling YOU that it is vital to use the appropriate length when defining
fields!  That is their JOB.

Lets just forget about things like storage for a minute...

Ask them

o whats going to happen when users pull this up in a query tool that formats
each field based on the width of the column in the database?  They'll see one
column and have to arrow way over to see the second and so on.

o Ok, so the code prepares a query that select 10 columns that are varchar2.
The developer, for performance would like to array fetch (very important).  They
would like to array fetch say 100 rows (very very typical).  So, we have 4000
times 10 times 100 = alsmost 4meg of RAM the developer must allocate!!

Now consider if that where 10 varchar2(80) fields (its probably much smaller
then that) -- thats about oh 78k.  Ask the "data modeler" how much RAM he is
willing to kick in for this system....

o ok so the developers go to build a data entry screen to put data into the
database.  Wow -- that code field can be 4000 characters long, that first name
can be 4000 characters long.  How the heck is anyone going to know what sort of
data can really go in there?

Tell them they need to consider the length as a constraint.  Just like they are
going to use primary and foreign keys, they will use the proper and correct
length on fields.  You can ALWAYS grow a field via "alter table t modify c
varchar2(bigger_number)" -- there is ZERO, none, no reason to use the max length
everywhere.  It'll kill the applications you develop - they'll mistakenly
allocate many megabytes of ram (just think of the array fetch example with an
app server -- now its not just 4 meg, its 4 meg * number of connections -- you
are talking some real memory here for a SINGLE QUERY (and you'll be doing lots
of them at the same time))

As for the storage.  A char(2000) will consume 2000 bytes of storage whether you
put the letter "a" in there or the string "hello world" or 2000 characters.  A
char is always blank padded.

As for the storage, consider this -- are you thinking about using an occasional
index in your system?

tkyte@ORA817.US.ORACLE.COM> create table t ( a varchar2(4000), b varchar2(4000)
);

Table created.

tkyte@ORA817.US.ORACLE.COM> create index t_idx on t(a);
create index t_idx on t(a)
                      *
ERROR at line 1:
ORA-01450: maximum key length (3218) exceeded

I have an 8k block size -- you would need to use at least a 16k block size to
index a SINGLE column but even then if you tried to create a concatenated index
on t(a,b) -- it would fail there!

This is one of the worst ideas I've personally heard of.  Get a new data
modeler!

(these are just the things that popped off the top of my head -- there are
undoubtably many many more bad things that would happen due to this)

followup to comment one

Thats an interesting point on the UTF8 issue.  In Oracle9i (9.0.1) and up what
we've done is to add a new syntax to the varchar2/char datatypes:

create table t
( some_data       varchar2( 20 char ),
  some_other_data char( 10 char )
)
/

This defines a table with 20 CHARACTERS -- not bytes -- of storage.  In a UTF8
instance (where a character can take many bytes - many more then 3 -- eg:

....
A typical situation is when character elements are combined to form a single
character. For example, o and an umlaut can be combined to form ö. In the Thai
language, up to three separate character elements can be combined to form one
character, and one Thai character would require up to 3 bytes when TH8TISASCII
or another single-byte Thai character set is used. One Thai character would
require up to 9 bytes when the UTF8 character set is used.
.....

That char( 10 char ) may consum many BYTES of storage -- many more then 10.

Now, this does NOT increase the max varchar2/char size.  If you define a:

varchar2( 4000 char )
char( 2000 char )

we are still limited physically to 4000 bytes and 2000 bytes.  Where this new
syntax comes in most useful is in "normal" fields like:

code    char( 1 char )
name    varchar2( 80 char )
description varchar2( 255 char )

for really long fields you'll still want to use CLOBS as the storage mechanism.

I stand by my comments above.  Having a blanket rule that everything should be a
varchar2(4000) is a really bad idea
Review & Followup
Rating: 3
How are varchar stored internally in a block or page. August 10, 2001
Reviewer: Svend Jensen from Denmark
Very interesting point on memory usage in clients/OAS.
The so called "modeler" is close to the point. Varchar2 are stored with minimum
overhead on disk. A column GREET varchar2(4000)='HELLO WORLD' would use ~11
bytes of disk storage.

Using variable multibyte charsets as UTF, each character will expand anywhere
from 1 to 3 bytes. Take that into account when you design your column sizes for
global deployment and charsets other than single byte charsets (US7ASCII,
WE8ISO8859...)

I miss info on overhead and or compression with repeating characters like 200
spaces or dot's

Rating: 5
Storage of Varchar2 fields August 10, 2001
Reviewer: Ilango Jv from INDIA
Its a good explanation and very useful.  Thank you very much Mr. Tom.

Rating: 4
How are varchar stored internally in a block or page August 10, 2001
Reviewer: Rob from Chicago, IL
Unfortunately I have seen this quite often.
The database modelers and are using row based architecture.
They create tables with a type identifier and a bunch of generic columns with
varchar2 (4000). I have tried to get them to move away from this approach. They
believe the flexibility out weighs the issues you bring up.
I’m sure you can imagine what a pain it is developing for these systems.

Rating: 5
August 13, 2001
Reviewer: AB from UK
I don't think these so called "modellers" are creating flexible environments at
all - they are just being extremely lazy and cannot be bothered to understand
the data that will be eventually be put into the database. Tom tells it as it
is.

Rating: 4
Storage of VARCHAR2 August 13, 2001
Reviewer: Randy from Chiacgo, USA
Personally I have seen data modeller of this kind. They seem to have no idea
about resource conservation and very surprisingly most of them has some kind of
Visual Basic programming background. Can you ask your data modeller how he would
estimate the size of the database/table ? I gurantee that would be very
challenging.

Rating: 1
varchar2 constraint September 19, 2001
Reviewer: Mikito Harakiri from SF, BA
I'm with data modeller. How come Oracle enters 21st century with that silly
limit on string length while any decent language has dynamic strings today? If
it's a constraint, then it must be _optional_ like any other constraint. In
short, one string datatype -- not dosen as today -- plus optional length
constraint would be an ideal implementation.

Followup:
If you don't want a limit, use a clob.  So, when you don't want a limit -- we
have a datatype for you.  Heck, in 9i you can use like, =, <, >, instr, substr,
whatever on a clob.  Clobs are for you.

There are practical concerns with regards to database development and design.
Imagine if people started putting gobs and gobs of data inline in the table (oh
wait -- thats called a LONG and the issues with that were so bad -- we've
totally replaced that datatype with a CLOB).

That is sort of what this thread was about -- there are practical considerations
to be thought about with a physical data model.  Can you answer some of the
questions I posed above??

SQL stands for something -- structured is part of the name.

Everything has constraints.  Even those languages that seemingly don't have
constraints. (C seems to be a decent language, PLSQL too -- all strings are
constrained in those languages as well).

Dates -- gotta be a date, there is no feb 30'th, there is nothing before 4712bc,
there is no year 10,000.

Numbers -- gotta be a number, there is no number 'z', gotta be in a certain
range of precision.

Strings -- gotta be of a certain size.  Go with 4000 if you want -- but you
might want to consider the practicality of doing so, answer the questions in
your own mind first (so when the developers ask them, you are ready with the
solutions to the real problems they will encounter).

Rating: 5
VARCHAR2 columns per table March 26, 2003
Reviewer: sachin from germany
Tom,
We have 8.1.7.4.
I am building a table for a survey...
Some of the columns involved needs lot of text...about 2000
characters...
So, couple of tables are coming up with about 11 columns
each with length varchar2(2000)...
The analyst is insisting that we will need this much length
for all these columns...may be more...
What is the best way to declare the types of these columns..
Will CLOBS be better than VARCHAR2
Also, We are using three tablespaces...small, medium and large (LMT)
I was going to put this in large:
DATAFILE SIZE 1000M
16384K
MAXSIZE  1000M
EXTENT MANAGEMENT LOCAL UNIFORM
SIZE 1024K

What are your suggestions about tablespaces for this kind of table.....may be a
separate tablespace??

Thanks...

Followup:
If they are 2000 characters -- varchar2(2000) is appropriate.

A table with 11 varchar2(2000) columns is not unusual, it need not be treated
differently from anything else.

Rating: 4
Is UTF8 not required? June 10, 2003
Reviewer: Tony from India
Tom,
   One of my friends says that if we use varchar2( 20 char )
syntax, no need to have UTF8 for multi-byte character sets. Is it true?

Followup:
no, either you misunderstood or your friend is confused.

You want to use varchar2(20 CHAR) when you think you might want to use UTF8 or
any multi-byte character set and don't want to have to change your schema.

a column of varchar2(20) reserves 20 bytes.
in US7ASCII for example, that just happens to be 20 characters.
in UTF8, that might be 20 characters, it might be 10, it might be 6 -- since
each character might take 1, 2, or 3 bytes (maybe even more, not sure, not
relevant -- only relevant that a CHARACTER may take more then ONE BYTE)

Now, if you had defined the column in 9i (only in 9i and up does this feature
exist) as varchar2(20 CHAR) then

in US7ASCII, that will permit upto 20 characters in that character set -- that
just happens to be 20 bytes.

in UTF8, that will permit upto 20 characters in that charcater set -- meaning
that might be 20, 40, 60, .... bytes

NOW, bear in mind, this does NOT increase the maximum varchar2/char byte width
which is still 4000/2000!  If you

varchar2(4000 CHAR)
char(2000 CHAR)

that will still be limited to 4000/2000 BYTES and may or may NOT be able to hold
4000/2000 utf8 characters!

This is something to consider start using in your applications as it will save
pain in the furture if you are installed on a multi-byte character set.  More
then one application has had to go back and fix their schema since their
"first_name" field at varchar2(40) -- couldn't hold 40 characters -- they had to
double to triple that

Rating: 4
What about size of number data type columns? June 11, 2003
Reviewer: A reader
Hi Tom,
Could you please also give your views (apart for using as a constraint)
regarding columns created with number data type of specific precision rather
that just number?
Thanks

Followup:
if the number is to be a number between 1 and 999 with no decimals, etc etc etc
-- using number(3) would be appropriate.

Just like using varchar2(10) for a 10 byte field would be.

If there is a constraint to be enforced, DO IT.

Rating: 4
Max of varchar2 is 2000 or 4000 bytes ? June 12, 2003
Reviewer: Tony from India
You have mentioned in the above follow-up for my question that varchar2 max byte
is 2000. Is it not 4000 bytes?

Followup:
touched that up -- was thinking "char", typed "varchar2", so I added both

Rating: 4
Max of varchar2 is 2000 or 4000 bytes ? Please Ignore June 12, 2003
Reviewer: Tony from India
Sorry Tom, I've misunderstood. You're right.

Followup:
no, you were, it is fixed.

June 12, 2003 Rob -- Thanks for the question regarding "multi-table insert", version 9.2.0
You Asked
Tom:

I was interested in using the multi-table insert feature in 9i but am stumped by
the restriction that the subquery can't contain a sequence. I use a sequence to
assign a Primary Key in one of the tables that is the target of the multi-table
insert. Can you suggest a workaround? Will calling a user-defined function that
returns the nextval of the sequence work in the subquery?

Thanks as always,

Rob
and we said...
ops$tkyte@ORA920> create table t1 ( x int primary key, y int );

Table created.

ops$tkyte@ORA920> create table t2 ( x int primary key, y date );

Table created.

ops$tkyte@ORA920> create sequence s;

Sequence created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert
  2   when mod( object_id, 2 ) = 1 then
  3      into t1 ( x, y ) values ( SS, object_id )
  4   when mod( object_id, 2 ) = 0 then
  5      into t2 ( x, y ) values ( SS, created )
  6  select s.nextval SS, object_id, created from all_objects
  7  /
select s.nextval SS, object_id, created from all_objects
         *
ERROR at line 6:
ORA-02287: sequence number not allowed here

ops$tkyte@ORA920> insert
  2   when mod( object_id, 2 ) = 1 then
  3      into t1 ( x, y ) values ( s.nextval, object_id )
  4   when mod( object_id, 2 ) = 0 then
  5      into t2 ( x, y ) values ( s.nextval, created )
  6  select object_id, created from all_objects
  7  /

29371 rows created.

put the nextval right in the values clause.
Review & Followup
Rating: 5
February 06, 2003
Reviewer: Daryl
What about the situtation where the targets of the multi-table insert have a
sequence value in common between them? In the following example, I'd like the
s_id column value to be identical for each row inserted into both tables:

create table s
    (s_id number not null primary key,
     s_name varchar2(500) not null );
create sequence s_seq;

create table s_sub
    (s_sub_id number not null primary key,
     s_id number not null references s(s_id),
     s_sub_name varchar2(500) not null );
create sequence s_sub_seq;

INSERT ALL
INTO s (s_id, s_name)
    VALUES (id, name)
INTO s_sub (s_sub_id, s_id, s_sub_name)
    VALUES(id2, id, name )
SELECT s_seq.NEXTVAL AS id,
    s_sub_seq.NEXTVAL AS id2,
    object_name AS name
FROM all_objects;

SQL> INSERT ALL
  2     INTO s (s_id, s_name)
  3             VALUES (id, name)
  4     INTO s_sub (s_sub_id, s_id, s_sub_name)
  5             VALUES(id2, id, name )
  6  SELECT s_seq.NEXTVAL AS id,
  7             s_sub_seq.NEXTVAL AS id2,
  8             object_name AS name
  9  FROM all_objects;
                s_sub_seq.NEXTVAL AS id2,
                          *
ERROR at line 7:
ORA-02287: sequence number not allowed here

Followup:
Not going to happen -- you'll have to put the subquery into a global temporary
table I suppose and then do the multi-table insert.

Rating: 4
February 09, 2003
Reviewer: Daryl
I put the SELECT seq.NEXTVAL FROM dual into two separate functions (outside of
packages), and it lets me use the functions in the subselect.

Followup:
careful with that -- i tried that as well.

ps$tkyte@ORA920> INSERT ALL
  2  INTO s (s_id, s_name)
  3      VALUES (id, name)
  4  INTO s_sub (s_sub_id, s_id, s_sub_name)
  5      VALUES(id2, id, name )
  6  SELECT s_seq_func AS id,
  7      s_sub_seq_func AS id2,
  8      object_name AS name
  9  FROM all_objects;
INSERT ALL
*
ERROR at line 1:
ORA-02291: integrity constraint (OPS$TKYTE.SYS_C003798) violated - parent key
not found

it is free to execute and re-execute the functions -- id and id2 may be called
UNPREDICABLY

Rating: 4
So I've discovered February 16, 2003
Reviewer: Daryl
In my case, the functions are being called more often than it seems they should.
Based on the documentation for the multi-table insert, the subquery is built and
then fed into the insert portions of the select. That is, the docs make it sound
as if the subquery that provides the source data is materialized before the
inserting starts. If that were true, my proposal would have no problems.
Oracle's just determined to make me incur the expense of multiple data passes
over millions of rows.

Followup:
we are not determined -- everything in sql is just "set oriented".  you can
never count on the order of evaluation in a predicate, how many times "foo" will
get called.  The language is 100% declarative, not procedural at all.

what about this tho:

ps$tkyte@ORA920> create table s
  2      (s_id number not null primary key,
  3       s_name varchar2(30) not null );

Table created.

ops$tkyte@ORA920> create sequence s_seq;

Sequence created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create table s_sub
  2      (s_sub_id number not null primary key,
  3       s_id number not null references s(s_id),
  4       s_sub_name varchar2(30) not null );

Table created.

ops$tkyte@ORA920> create sequence s_sub_seq;

Sequence created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> INSERT ALL
  2  INTO s (s_id, s_name) VALUES (s_seq.nextval, name)
  3  INTO s_sub (s_sub_id, s_id, s_sub_name) VALUES(s_sub_seq.nextval,
s_seq.currval, name )
  4  SELECT object_name AS name FROM all_objects where rownum <= 10;

20 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select *
  2    from s, s_sub
  3   where s.s_id = s_sub.s_id;

      S_ID S_NAME                           S_SUB_ID       S_ID S_SUB_NAME
---------- ------------------------------ ---------- ----------
------------------------------
         1 /1005bd30_LnkdConstant                  1          1
/1005bd30_LnkdConstant
         2 /10076b23_OraCustomDatumClosur          2          2
/10076b23_OraCustomDatumClosur
         3 /10297c91_SAXAttrList                   3          3
/10297c91_SAXAttrList
         4 /103a2e73_DefaultEditorKitEndP          4          4
/103a2e73_DefaultEditorKitEndP
         5 /1048734f_DefaultFolder                 5          5
/1048734f_DefaultFolder
         6 /10501902_BasicFileChooserUINe          6          6
/10501902_BasicFileChooserUINe
         7 /105072e7_HttpSessionBindingEv          7          7
/105072e7_HttpSessionBindingEv
         8 /106ba0a5_ArrayEnumeration              8          8
/106ba0a5_ArrayEnumeration
         9 /106faabc_BasicTreeUIKeyHandle          9          9
/106faabc_BasicTreeUIKeyHandle
        10 /10744837_ObjectStreamClass2           10         10
/10744837_ObjectStreamClass2

currval and nextval together should do it I think

Rating: 5
Awesome February 16, 2003
Reviewer: Daryl
The currval/nextval combo finally got it for us.

The behavior I was seeing with the functions was that the parent table's
sequence (s_seq) was getting hit twice, so the child table's references to the
parent table were off by 1 from what they should've been.

Rating: 4
Excellent Thread but I need to do this going from a 9i db to 8i db March 19, 2003
Reviewer: Ken from NY,NY
I have a scenario that is basicly identical to the one decribed above but the
insert has to go ver a db link and insert into two tables in an 8i db.

INSERT ALL
    INTO   content.items@cm_dev (ITEM_ID,Display_Name,Function_ID,Location)
    VALUES (content.seq_item_id.nextval,Function_ID,Location)
    INTO   content.item_meta_info (ITEM_META_INFO_ID, ITEM_ID,
Category_ID,Display_Name,Keywords,Start_Date,Can_Be_Called_out,Can_Be_Displayed,A
ttractor)
     VALUES
(content.seq_item_meta_info_id.nextval,content.seq_item_id.currval,Display_Name,K
eywords,Start_Date,Can_Be_Called_out,Can_Be_Displayed,Attractor)
    SELECT
Display_Name,Keywords,Start_Date,Can_Be_Called_out,Can_Be_Displayed,Attractor,Fun
ction_ID,Location FROM Content_items;

I recieve ORA-2021.

Any help would be appreciated.

On a side note I think this site is invaluable for any oracle proffesional and
your book is great.

Followup:
see
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/statements_913a.htm#2080119

You cannot perform a multitable insert into a remote table.

Rating: 5
Why duplicating April 27, 2003
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
SCOTT > drop table t1;

Table dropped.

SCOTT > drop table t2
  2  ;

Table dropped.

SCOTT > create table t1 as select * from emp where 1=1;

Table created.

SCOTT > create table t2 as select * from emp where 1=1;

Table created.

SCOTT > desc emp
 Name

---------------------------------------------------------------------------------
---------------------
 EMPNO

 ENAME

 JOB

 MGR

 HIREDATE

 SAL

 COMM

 DEPTNO

SCOTT > ed
Wrote file afiedt.buf

  1  insert all
  2    into t1 values(EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,DEPTNO)
  3    into t2 values(EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,DEPTNO)
  4* select * from emp
SCOTT > /

28 rows created.

SCOTT > select count(*) from emp;

    COUNT(*)
------------
          14

SCOTT > select count(*) from t1;

    COUNT(*)
------------
          28

SCOTT > select count(*) from t2;

    COUNT(*)
------------
          28

SCOTT > select empno from t1;
       EMPNO
------------
        7369
        7369
        7499
        7499
        7521
        7521
        7566
        7566
        7654
        7654
        7698
        7698
        7782
        7782
        7788
        7788
        7839
        7839
        7844
        7844
        7876
        7876
        7900
        7900
        7902
        7902
        7934
        7934

28 rows selected.

SCOTT >

I want to know that why the rows are duplicated in both target tables?
How many times select executed in the above code?
I was just expecting 14 rows in each table may be I was expecting wrong.

Your comments please.

Followup:
SCOTT > create table t1 as select * from emp where 1=1;

Table created.

SCOTT > create table t2 as select * from emp where 1=1;

Table created.

you created the table with 14 rows from emp.

and then

  1  insert all
  2    into t1 values(EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,DEPTNO)
  3    into t2 values(EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,DEPTNO)
  4* select * from emp
SCOTT > /

you inserted those same 14 rows all over again.

Rating: 5
Used 1=1 instead of 1=0 April 28, 2003
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
You are right TOM.

Rating: 4
How to handle exception in multi-table insert? June 12, 2003
Reviewer: Tony from India
1) How to handle exception in multitable insert? For example: Some Records are
inserted into 4 different tables. Some exception (ex: value_error or too many
rows) is thrown by one of those tables. How do I find the table for which the
exception is raised and the row forwhcih the exception is raised?

2) Can't I do multi-table insert without the SELECT statement as below?

 insert all into emp1 (empno,ename,deptno,sal) values (333,'anthony', 10, 50000)
           into emp2 (empno,ename,deptno,sal) values (333,'anthony', 10, 50000)

ERROR at line 2:
ORA-00928: missing SELECT keyword

Followup:
1) the same as you would with

insert into t select * from t2;

the constraint name will be given to you.  as for the "row", well, that is the
same as always.  you don't get the row

2) use "select :bv1, :bv2, :bv3, :bv4 from dual" for a single row insert.

June 12, 2003 Randy -- Thanks for the question regarding "Oracle Fail Safe and Dataguard", version 9.2.0.2
You Asked
If I want to use Oracle Fail Safe and Dataguard do the servers have to be
clustered?  Right now I have a primary database on one server and a separate
server for the logical standby database.  I want automatic failover, but it
looks like Oracle Fail Safe requires clustered servers.

The DATAGUARD manual mentions that you can use ORACLE FAIL SAFE on the windows
platform, but the ORACLE FAIL SAFE documentation doesn't say squat about
DATAGUARD or how to configure for it.   Is there any documentation of this
subject that you can refer me to?

Thanks
and we said...
Fail Safe is a clustering solution.

The two (data guard & failsafe) are complimentary but somewhat orthogonal here.

Failsafe is designed to keep the single database up and available -- in a single
data center.  As long as that room exists -- failsafe keeps the database up.

data guard is a disaster recovery solution.  It is for when the room the data
center is in "goes away" for whatever reason.

Data guard wants the machines to be independent (no clusters) of eachother and
separated by some geographic distance.

Failsafe, like 9i RAC, wants the machines to be tethered together - sitting
right next to eachother in a cluster.

Failsafe is HA (high availability)
Data guard is DR (disaster recovery)

Failsafe will give you automated failover.  As long as the data center exists,
that database is up.

With data guard -- you do not WANT automated failover (many *think* they do but
you don't).  Do you really want your DR solution to kick in due to a WAN
failure?  No, not really.  For DR to take over, you want a human to say "yup,
data center burnt to the ground, lets head for the mountains".  You do not want
the DR site to kick in because "it thinks the primary site is gone" -- you need
to tell it "the primary site is gone".  In a cluster -- the machines are very
aware of eachother and automated failover is "safe"

So, data guards reference to failsafe is incidental.
That failsafe doesn't talk about data guard is of no real consequence.

They are independent feature/functions.
Review & Followup
Rating: 5
data guard June 06, 2003
Reviewer: A reader
Please give me the link. I want to know more about data guard.

Followup:
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-DAT

      Rating: 5
Excellent
      Definitions of FailSafe and DataGuard   June 12, 2003

Reviewer:  A reader
Tom, Thanks for giving us a *very clear and easy* understanding of these
products.

      Rating: 5
Can you please
      fit 9i RAC into this picture?  June 12, 2003

Reviewer:  A reader
Tom,
You said:
"Failsafe is HA (high availability) Data guard is DR (disaster recovery)". How
would you define RAC - high availability + scalability?

If we want Scalibility and DR, we would need RAC + Dataguard? Can RAC and
DataGuard co-exist?

          Followup:

RAC is HA+scaling up.

RAC and DG can (and do) coexist.

June 12, 2003 Keith -- Thanks for the question regarding
      "Default Directory", version Oracle8i

      You Asked
What is the default directory for SQL*Plus?

      and we said...
the current working directory.  whatever directory you were in
when you started sqlplus.

scripts will be looked for in the current working directory and
the directory specified in the environment variable SQLPATH.
You can place your login.sql in a single directory for example,
set SQLPATH and we'll find it there.
Review &
      Followup

      Rating: 5
sql
      plus  August 15, 2002
Reviewer:  Mo
       from Reston, USA
Tom:

I usually click on the icon and edit the default directory there. is there a way
to do it in SQL*PLUS itself.

THanks

          Followup:

Your approach is sound.  It is the way to do it.  (or my way, which is to use
sqlplus.exe not sqlplusw.exe.  I cd to where I want to be and just type sqlplus
when forced to use windows)

      Rating: 3
Why not this
      way  August 16, 2002
Reviewer:  A reader
       from Atlanta , USA
I simply enter the list of directory in SQLPATH Resitry variable .eg c:\a ;c:\b
and so on
SQlplus then looks for the file in those particular directories

          Followup:

That does not change the current working directory.  I mentioned the sqlpath --
but that won't change the current working directory (you know, where files are
written to for example)

      Rating: 5
How to set it
      to a single directory  August 16, 2002
Reviewer:
      Nag
Tom

Can you show us or give us a script with which we can set the sqlplus default
directory back to the directory we want to.

Thanks

          Followup:

You cannot change the default directory WHILST you are in sqlplus.

So no, I cannot.

      Rating: 3
SQLPATH env
      variable and dbstart/dbshut  June 11, 2003

Reviewer:  fan  from pa
Tom,
I set SQLPATH to where login.sql (your script from book) is.

But now seems dbstart/dbshut also launches login.sql
and "interrupts" the start/shut processes.

So I had to go in dbstart/dbshut script to add at the top:
SQLPATH=""
export SQLPATH

and bottom...
SQLPATH=$ORACLE_HOME/admin/sql
export SQLPATH

This hack is not the right way to handle this, is it ?

Thanks

          Followup:

this is my current one, modified since we use sqlplus to startup and shutdown.
It'll not "hang" -- the trick was to add

define gname=idle

so when plus connects to a database that isn't up, it doesn't wait for you to
hit enter (if you termout on'ed the script, you would find that plus was waiting
for you to name the instance ;)

set termout off
define _editor="vi"

set serveroutput on size 1000000

column object_name format a30
column segment_name format a30
column file_name format a40
column name format a30
column file_name format a30
column what format a30 word_wrapped

set trimspool on
set long 5000
set linesize 131
set pagesize 9999

define gname=idle
column global_name new_value gname
select lower(user) || '@' || substr( global_name, 1, decode( dot, 0,
length(global_name), dot-1) ) global_name
  from (select global_name, instr(global_name,'.') dot from global_name );
set sqlprompt '&gname> '

column P_TO format a20 word_wrapped
column P_FROM format a20 word_wrapped
column P_SUBJ format a20 word_wrapped
column P_CC format a20 word_wrapped
column P_BCC format a20 word_wrapped
column P_BODY format a40 word_wrapped

column plan_plus_exp format a100
set termout on

      Rating: 5
That sure does
      the trick !  June 12, 2003
Reviewer:  fan
       from PA
Thanks Tom !
gname=idle ... these are SQL+ (undoc'ed ?) keywords, right ?

Thanks

          Followup:

No, I was using a substitution variable GNAME.

The problem was, if the query failed (database no open), &GNAME would not have a
value, so sqlplus would ask for one -- by using define gname=idle, I defaulted
gname -- so that when the query failed, gname would simply have the value idle
in it - that way:

set sqlprompt '&gname> '

would not prompt (appear to hang) in the login.sql

June 12, 2003 Sanjay -- Thanks for the question regarding "How
      do I clear the memory consumed by temporary lobs ?", version 8.1.7

      You Asked
Hi Tom,

We have a system where a pl/sql packaged function creates 2 clobs.
One is defined in package specs and returned back.  The other in pacakge body
for internal use.

I query the v$temporary_lobs view before and after execution of the function.
But
1) the CACHE parameter seems to be increasing all the time.
2) some times the same SID is used and hence the CACHE count is continued to be
increased even if new browser window hits the database from some other terminal.

Here is Package specgs & body for your kind reference -

CREATE OR REPLACE PACKAGE MANAGE_REPORT AS
gv_returned_xml_clob   CLOB;
FUNCTION Build_Report
(p_company_num VARCHAR2)
RETURN CLOB;
END MANAGE_REPORT;

and the package body (I have remoded un-necessary stuffs)-
CREATE OR REPLACE PACKAGE BODY MANAGE_REPORT
AS
FUNCTION build_report
(p_company_num VARCHAR2)
RETURN CLOB IS
  v_sql_stmt            CLOB;
BEGIN

   -- Clear all temporary tables
   COMMIT;

   xmlgen.setRowTag('');
   -- Create an temporary clob to hold select stmt
   -- note we can't use a string as the select statement will exceed 4000
characters

   DBMS_LOB.createtemporary (v_sql_stmt, true, 2);

   DBMS_LOB.open(v_sql_stmt,dbms_lob.lob_readwrite);

   v_sql_stmtn1:= '  select statemetn dynamically build ...';
   v_sql_stmtn2:= '  select statemetn dynamically build ...';
   v_sql_stmtn:= '   select stamtement dynamically build ....'; --This statement
is put into one internal clob.

    -- Now we've got all the sql statements we need to write it to the clob
    DBMS_LOB.writeappend(v_sql_stmt, length(v_sql_stmt1), v_sql_stmt1);      --
General info
    DBMS_LOB.writeappend(v_sql_stmt, length(v_sql_stmt2), v_sql_stmt2);      --
Financial/accounts info
    DBMS_LOB.writeappend(v_sql_stmt, length(v_sql_stmtn), v_sql_stmtn);

    xmlgen.clearBindValues;
    -- Bind Variables in the same order as they appear in the sql statement

    xmlgen.setBindValue('v_ayrenda', v_fourth_acc_yearend); -- bind values are
cleared

    xmlgen.setBindValue('v_naceqa', v_nace); --some bind variables are set

    dbms_lob.trim( MANAGE_REPORT.gv_returned_xml_clob, 0 );

    -- Generate the XML...

    MANAGE_REPORT.gv_returned_xml_clob := XMLGEN.getxml(v_sql_stmt);

    DBMS_LOB.close(v_sql_stmt);
    DBMS_LOB.freetemporary(v_sql_stmt);

    -- Clear Temporary Tables..

    COMMIT;
    RETURN MANAGE_REPORT.gv_returned_xml_clob;

   EXCEPTION
     WHEN OTHERS THEN
          raise applicatoin_error --some step

END build_report;

--Here the packaged specified CLOB is created
BEGIN
  DBMS_LOB.createtemporary(MANAGE_REPORT.gv_returned_xml_clob, TRUE, 1);
  DBMS_LOB.open(MANAGE_REPORT.gv_returned_xml_clob, DBMS_LOB.lob_readwrite);
END;
/

I also tried the following in the package body above - replacing the
CACHE (true) with NOCACHE (false).  However no joy.  It still eats memory.
DBMS_LOB.createtemporary (v_sql_stmt, false, DBMS_LOB.CALL);
DBMS_LOB.createtemporary(MANAGE_REPORT.gv_returned_xml_clob, false,
DBMS_LOB.SESSION);

Kindly guide.

Thanks & regards,
--Sanjay.

      and we said...
This shows that the lobs will be freed:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure allocate_lob
  2  as
  3          l_clob clob;
  4  begin
  5          dbms_lob.createTemporary( l_clob, true );
  6          dbms_lob.open( l_clob, dbms_lob.lob_readwrite );
  7
  8          for i in 1 .. 10
  9          loop
 10                  dbms_lob.writeAppend( l_clob, 32000, rpad('*',32000,'*') );
 11          end loop;
 12
 13          dbms_lob.close( l_clob );
 14          dbms_lob.freeTemporary( l_clob );
 15  end;
 16  /

Procedure created.

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

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from v$temporary_lobs;

no rows selected

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

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from v$temporary_lobs;

no rows selected

The problem I see is not with YOUR temporary lob -- it is with your handling of
your packge global variable MANAGE_REPORT.gv_returned_xml_clob.

A clob is a pointer -- when you execute the code:

   ...
   MANAGE_REPORT.gv_returned_xml_clob := XMLGEN.getxml(v_sql_stmt)
   .....

What you've done is "lost" the last clob you had there.  You have the clob
allocated still -- you just cannot get to it anymore, you lost the pointer to
it.  It is this clob that is "leaking".

You in fact are adding an extra clob to be "leaked" with this code:

...
BEGIN
  DBMS_LOB.createtemporary(MANAGE_REPORT.gv_returned_xml_clob, TRUE, 1);
  DBMS_LOB.open(MANAGE_REPORT.gv_returned_xml_clob, DBMS_LOB.lob_readwrite);
END;
/

Here, you allocate a lob upon package startup -- but you never use it -- you
overwrite it with the call to xmlgen.getxml.

You should get rid of that startup code (you never use that clob) and add code
such as:

  if ( MANAGE_REPORT.gv_returned_xml_clob is not null )
  then
     dbms_lob.close( MANAGE_REPORT.gv_returned_xml_clob );
     dbms_lob.freeTemporary( MANAGE_REPORT.gv_returned_xml_clob );
  end if;
  MANAGE_REPORT.gv_returned_xml_clob := XMLGEN.getxml(v_sql_stmt)

See what that does for you.

Review &
      Followup

      Rating: 5
CLOB fields:
      package scope, and lifetime  June 10, 2003

Reviewer:  T Cornwell  from Moravia, NY USA

This helps lots with a similar problem I'm having.  I still wonder, though,
about CLOBs in package methods:

. Are CLOBS declared within methods session-length unless explicitly released?

. Could I use a particular package-level CLOB as a NOCOPY parameter to access it
within a method?

A problem I have is that I must call a function more than once in one statement
(with different parameters) to build a single CLOB result field.  This makes
using a package-level CLOB field difficult.  Something like this:

Package:
...
  ---- Internal function -------------
  Function buildstring(param in Number)
  Return CLOB
  is
    tmpCLOB CLOB;
  Begin
    Select bigstring
    Into tmpCLOB
    From myTable
    where keyval = param

    Return tmpCLOB;

  End buildstring;

  ----- Public proc  -----------------
  Procedure Make_entry(param1,param2)
  is
  Begin
     Insert Into NewTable
       BigString
     Values(
       buildstring(param1)||
       ' - and - '||
       buildstring(param2));

  End Make_entry;

End;
----------------------------------------------------

In this scenario, when Make_Entry is called a number of times in a session, I
believe the v$temporary_lobs goes quite high.  I think because the tmpCLOB in
the function doesn't go away?

Is there a way to fix this, or to be sure to release a locally-scoped CLOB when
it is returned from a function like this?

Thanks for the help.
Tim

          Followup:

clobs in a function or procedure should be closed and cleaned up by you.  You
see, we have no way of knowing if you:

is
   l_clob clob;
begin
   l_clob := some_pkg_variable.clob;

   ....

or if you allocated a local temporary.  If you allocate it -- FREE it when you
are done.

You need to free the clobs you retrieve as well -- we have no idea when you are
done with them.

What would happen with a bulk collect for example?  There must be more then one
clob and you control them.

CLOBS are tiny.  What they POINT TO is large.  NOCOPY on a clob is funny (i wish
our docs wouldn't do that).  It is not like they are big.

      Rating: 2
Temporary CLOBS
      in a function  June 11, 2003
Reviewer:  Tim
      Cornwell  from Moravia, NY USA
Thanks,

How should I clean up a temporary CLOB created by a function like
'Internal_Function' in a situation like this:
Package:
------------------------------------
...
  pkglobTemp CLOB;

  Function Internal_Function(param)
  is
    tmpCLOB CLOB;
  begin
    Select clobField
    Into tmpCLOB
    From myTable
    Where keyField = param;

    Return tmpCLOB;

  End Internal_Function;
  ------------------------------------
  Procedure publicProc(param1)
  is
    For myRecs in
      (Select Field1, Field2
       From mySource_Tbl
       Where keyField = param1)
    Loop
      pkglobTemp := pkglobTemp ||
        Internal_Function(myRecs.Field1)||
        ' - and - '
        Internal_Function(myRecs.Field2);
    End Loop;

    INSERT INTO myLog_tbl FieldBig Values(pkglobTemp);

  End publicProc;

End;
------------------------------------------------------

Does the function get a new CLOB reference to a new CLOB value for each call?
If so, then how do I 'clean up' these temp CLOBS?

          Followup:

the clob in need of "cleansing" here is pkglobTemp.  TempClob is a pointer to a
"real" clob in a table -- it is either "cache" or "nocache" and lives on disk or
in the buffer cache.

I would really not code in the style you have there -- rather, I would insert
into myLob_Tbl and empty_clob -- returning that into a local variable -- and
then using  dbms_lob.copy / writeAppend to add to it.  There shouldn't be a
temporary clob here.

But, if there was, you would want to create temporary, and free it when you are
done.

      Rating: 5
Temporary
      CLOBs: space management  June 12, 2003
Reviewer:
      Tim Cornwell  from Ithaca, NY USA
The light goes on!  Maybe...

After some testing & verification of what happens I
now do this:

------------------------------------
...

  pkglobTemp1 CLOB;
  pkglobTemp2 CLOB;
  pkglobTemp3 CLOB;

  pkglobReturnValue CLOB

  Procedure Internal_Procedure(param)
  is
  begin
    Select clobField
    Into pkglobReturnValue
    From myTable
    Where keyField = param;

  End Internal_Procedure;
  ------------------------------------
  Procedure publicProc(param1)
  is
    lstrTemp varchar2(32000);
  Begin

    --  Empty contents
    dbms_lob.trim(pkglobTemp,0);

    For myRecs in
      (Select Field1, Field2
       From mySource_Tbl
       Where keyField = param1)
    Loop
      --  Get first gob
      Internal_procedure(myrecs.Field_1);

      --  Add it to total
      dbms_lob.append(pkglobTemp,pkglobReturnValue);

      --  Make string value to add
      lstrTemp := ' - and this value: ';

      --  Add it
      dbms_lob.WriteAppend(pkglobTemp,length(lstrTemp),lstrTemp);

      --  Get next gob
      Internal_procedure(myrecs.Field_2);

      --  Add to total
      dbms_lob.append(pkglobTemp,pkglobReturnValue);

    End Loop;

    -- Write it
    INSERT INTO myLog_tbl FieldBig Values(pkglobTemp);

  End publicProc;

Begin

  dbms_lob.Createtemporary(pkglobTemp1);
  dbms_lob.Createtemporary(pkglobTemp2);
  dbms_lob.Createtemporary(pkglobTemp3);
  dbms_lob.Createtemporary(pkglobReturnValue);

End;
------------------------------------------------------

This seems to *not* build mucho lost LOBs - like I was getting before.  I would
appreciate any comments on if this method really sucks or not.

I changed the internal function to a procedure because it was returning a
package-global variable anyhow.  I can't seem to get used to functions returning
these things - like I've seen in some examples.

Thanks again,
Tim

          Followup:

method doesn't "stink", not sure why you use globals, but you must have a reason
in the larger scheme of things.

June 12, 2003 Tadeu -- Thanks for the question regarding "Help
      - Inner Join", version 8.1.6

      You Asked
Hi,

I'm starting in Oracle. I wanna do a query using Inner join, but an error occur:

ORA-0993

I tryed to use the SQL-ANSI:

select emp.name, dpto.name from
emp inner join dpto on (dpto.dptono = emp.dptono)

how can I do to use inner join in Oracle?

thanks

Tadeu Camargo

      and we said...
That syntax is support in Oracle9i.

What you need to be aware of, and what no one says for some reason, is that
there are 4 levels of ANSI SQL 92.

This is a very short extract from my book on this topic:

...
If all databases are SQL92-compliant, then they must be the same. At least that
is the assumption made many times. In this section I would like to dispel that
myth.

SQL/92 is an ANSI/ISO standard for databases. It is the successor to the SQL/89
ANSI/ISO standard. It defines a language (SQL) and behavior (transactions,
isolation levels, and so on) that tell you how a database will behave. Did you
know that many commercially available databases are SQL/92 compliant? Did you
know that it means very little as far as query and application portability goes?

Starting with the standard, we will find that the SQL/92 standard has four
levels:

o    Entry-level ? This is the level to which most vendors have complied. This
level is a minor enhancement of the predecessor standard, SQL/89. No database
vendors have been certified higher and in fact the National Institute of
Standards and Technology (NIST), the agency that used to certify for
SQL-compliance, does not even certify anymore. I was part of the team that got
Oracle7.0 NIST-certified for SQL/92 entry-level compliance in 1993.  An entry
level compliant database has the feature set of Oracle7.0.

o    Transitional ? This is approximately 'halfway' between entry-level and
intermediate-level as far as a feature set goes.

o    Intermediate ? this adds many features including (not by any means an
exhaustive list):

Dynamic SQL
Cascade DELETE for referential integrity
DATE and TIME data types
Domains
Variable length character strings
A CASE expression
CAST functions between data types

o    Full ? Adds provisions for (again, not exhaustive):
Connection management
A BIT string data type
Deferrable integrity constraints
Derived tables in the FROM clause
Sub-queries in CHECK clauses
Temporary tables

The entry-level standard does not include features such as outer joins, the new
inner join syntax, and so on. Transitional does specify outer join syntax and
inner join syntax. Intermediate adds more, and Full is, of course all of SQL/92.
Most books on SQL/92 do not differentiate between the various levels leading to
confusion on the subject. They demonstrate what a theoretical database
implementing SQL/92 FULL would look like. It makes it impossible to pick up a
SQL/92 book, and apply what you see in the book to just any SQL/92 database. For
example, in SQL Server the 'inner join' syntax is supported in SQL statements,
whereas in Oracle (8i and before) it is not. But, they are both SQL/92-compliant
databases. You can do inner joins and outer joins in Oracle, you will just do it
differently than in SQL Server. The bottom line is that SQL/92 will not go very
far at the entry-level and, if you use any of the features of intermediate or
higher, you risk not being able to 'port' your application.

Review &
      Followup

      Rating: 4
Great
      explaination, but what can be done instead  June 12, 2003

Reviewer:  kmikottis  from Chicago, IL USA

The explaination is very good.  However I need to know what to do next.  My
senario is very similar. I am porting an application with Inner Joins from 9i to
8i.

Please direct me to a solution for 8.1.7 for example (alternative syntax)

          Followup:

inner joins are just basic joins

from t1 inner join t2 on ( t1.x = t2.y )
is:
from t1, t2 where t1.x = t2.y

t1 right outer join t2 on ( t1.x = t2.y )
is:
from t1, t2 where t1.x(+) = t2.y

t1 left outer join t2 on (t1.x = t2.y )
is
from t1, t2 where t1.x = t2.y(+)

t1 full outer join t2 on (t1.x = t2.y) is:

select *
  from t1, t2
 where t1.x = t2.y(+)
UNION ALL
select *
  from t1, t2
 where t1.x(+) = t2.y
   and t1.x IS NULL
/

      Rating: 5
Old
      fashioned  June 12, 2003
Reviewer:  Jim
       from Portland, OR
For inner joins I like the old fashioned way( select ... from a, b where
a.col1=b.col1 )

I know its just syntax, but I think in a more set oriented manner and it makes
more sense to me.  Your milage may vary.

June 12, 2003 Mike -- Thanks for the question regarding
      "Refresh Group Architecture to minimise table locks", version 8.1.7

      You Asked
Tom,

Our database architecture is two master sites with about 10 snapshot sites
served by each.

We have an application (currently 8.1.7 moving soon to 9.2) that contains 120
updatable snapshots in a refresh group.

The data volumes are low with perhaps 4 or 5 users at a typical snapshot site,
mostly running queries but also a few updates. A fast refresh of the group from
a local site to the master takes a couple of seconds.

However some of our snapshot sites access the master across a satellite link
which has a high latency - ping time about 0.6 seconds or more. The refresh time
of these sites is perhaps 3 or 4 minutes - during which time of course all
tables are locked. The refresh frequency is 10 minutes - which makes the
application difficult to use - high probability of having to wait to acquire a
row lock when commiting at the snapshot.

I've traced the traffic from snapshot to master and see that there are a couple
of messages per table - an overhead that exists even when there are no updates
in the MLOG$. It's the number of these messages in combination with the latency
that causes the large refresh time rather than the underlying updates.

The design options I'm currently considering that will improve this situation
are

1) Create multiple refresh groups. Much of the data doesn't change frequently.
We could analyse where changes are infrequent and split into a seperate group.
The problem we then need to deal with is what happens if there is an FK
violation ? Options are i) disable relavant FK's and get the application to work
or ii) Trap the FK violation on the refresh and force a refresh of the other
groups.

2) Create 2 levels of mv on the snapshot site. Level 1 refreshes against the
master. Level 2 mv then refreshes against the Level 1. The application would run
against the Level 2 mv. Since the Level 2 -> Level 1 refresh is on the same
server it will be quick so locks not for long. The downside of this approach is
the extra level of complexity, doubling of storage requirements, extra loading
on machine but given our light workloads and smallish data volumes we may be OK.

3) Reduce latency by moving satellite lower :)

What is your opinion on our options and do you have any alternatives ?

My preference would be for Oracle to only send messages from snapshot to master
for tables that needed updates i.e the first thing it does when refreshing is to
go through all MLOG$ and see which need to be applied and send this list back to
the snapshot. I'm sure you tell me that either this isn't possible or that this
is how it already works and I've messed up the implementation !

Regards,

Mike

      and we said...
Hey, I use a satellite at home, I can spell latency for you (this web thing
makes it great).  0.6 seconds turn around -- I'm jealous.  Over my VPN, I
experience 1-2 second turnarounds!  But, it is very workable.  (i work from home
frequently)

You know what my solution was?  I use a web browser for everything.  Why bother
with the distributed databases and complexity when you can just use a browser.
For a single message up, single message back, satellites are great.

Asktom.oracle.com -- I access it (central database) from my web browser.  I
wouldn't consider replicating it.  Email -- web.  HR applications -- web.
Purchasing -- web.

Since the central site obviously has the capacity for this (it has to, it is
processing all of the load NOW as it is -- in fact, by dropping off some of the
replication, it'll have excess capacity).

So, I offer to you option #4 -- a variation on the well known proverb that the
best way to speed something up is to not do it at all -- Move the goalposts.
Get rid of the remote databases.  Centralize your processing, your applications,
access them remotely.  especially in light of your 10 minute refresh cycles.
 That implies currency of data is important to you and nothing is more current
then "it is all the same database"....

#1 is workable but getting into the realm of "a bad idea" with disabling
constriants and such.

#2 isn't workable

#3 results in satellites crashing into the ground, which is bad.

#4 -- which I'm a huge fan of, easier, less work all around, manageable, the
right way to go.

#5 -- investigate messaging with streams:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96571/toc.htm
which is in accordance with "your preference"

Review &
      Followup

      Rating: 2
Refresh
      Architecture  June 12, 2003
Reviewer:  Mike
      Peacey  from England
That was my initial thought when I started the database design. Unfortunately
the customer has unreliable comms - they tend to deploy the snapshot sites
around the world in 'hostile' locations - many of them in the news recently.

The system has been live now for 18 months or more and generally fairly
successful - we just want to improve it. I agree that streams may give a much
better response however I was looking for minimal reconfiguration.

          Followup:

well, look at streams, option #5.  It can do what you think you want to do.  It
is going to be a rearchitecture.  There are sometimes no silverish bullets.

      Rating: 5
Good
      stuff.  June 12, 2003
Reviewer:  Scott
       from Great Southern Land

June 12, 2003 Ak -- Thanks for the question regarding "block
      level info", version 8.1.5

      You Asked
Hi tom,
   We have many database running in diff. places...and also we
have a script which will delete records from various tables...after
every two months or so..I delete records to CLAIM SPACES...
Here My question is how do I know (AFTER DELETION) how much of space
is freed.I know y'r answer will be it's not possible untill You
drop or truncate the table.Since i have to Give report to management,
is there any way with which i can tell them beore deletion it was
1 gb was free and after after deletion I got another half a gig.
I cann't do export and import also since it is remote site...(i cann't
be going site by site)
Or is there any way with which i can find out total number of
blocks allowcated and Used before deletion and after deletion.
With which it will be easier for me also to know..that how long
i can carry my buss. with available space.If i query dba_free_space
I will NOT get free space altered after deletion...(as HWM will NOT get reset).

Thanks,

Regards
   Ak

      and we said...
Well, you can see how many more blocks you have on the free list.  You can use
dbms_space before running the delete and then after.  If the delete causes any
block to fall within the range specified by pctused, it'll go back onto the
freelist for the table.

It maybe however, that if you delete 1 row/block -- NO blocks get on the
freelist as a result -- so, you've freed space for subsequent UPDATES but not
inserts.

In that case, an ANALYZE before and after would be helpful as you could look at
the AVG_SPACE and BLOCKS, EMPTY_BLOCKS before and after.  For example, looking
at a contrived example:

ops$tkyte@ORA9I.WORLD> create table t ( x int, y char(2000) );
Table created.

ops$tkyte@ORA9I.WORLD> begin
  2          for i in 1 .. 102
  3          loop
  4                  insert into t values ( i, 'x' );
  5          end loop;
  6  end;
  7  /
PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> select min(cnt), max(cnt), avg(cnt), count(*)
  2  from (select dbms_rowid.ROWID_BLOCK_NUMBER( rowid ), count(*) cnt
  3                  from t
  4         group by  dbms_rowid.ROWID_BLOCK_NUMBER( rowid )
  5           )
  6  /

  MIN(CNT)   MAX(CNT)   AVG(CNT)   COUNT(*)
---------- ---------- ---------- ----------
         3          3          3         34

that shows I have 3 rows/block (8k blocksize, rows are 2k apiece).
furthermore:

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> select dbms_rowid.rowid_block_number(rowid), x
  2    from t
  3   where x <= 7;

DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)          X
------------------------------------ ----------
                                2058          1
                                2058          2
                                2058          3
                                2059          4
                                2059          5
                                2059          6
                                2060          7

7 rows selected.

rows 1,2,3 are on block 1, 4,5,6 are on block 2 and 7 is on block 3...

ops$tkyte@ORA9I.WORLD> analyze table t compute statistics;
Table analyzed.

ops$tkyte@ORA9I.WORLD> select avg_space, blocks, empty_blocks from user_tables
where table_name = 'T';

 AVG_SPACE     BLOCKS EMPTY_BLOCKS
---------- ---------- ------------
      2217         35           28

ops$tkyte@ORA9I.WORLD> exec show_space( 'T' )
Free Blocks.............................2
Total Blocks............................64
Total Bytes.............................524288
Unused Blocks...........................28
Unused Bytes............................229376
Last Used Ext FileId....................7
Last Used Ext BlockId...................2057
Last Used Block.........................36

PL/SQL procedure successfully completed.

what this shows me is that before the deletes, the avg free space / block is
2,217 bytes.  We have 35 blocks in use (28 above the HWM).  Of the 35 blocks --
one is "overhead" for Oracle so we really have 34 blocks in use.  Therefore, we
have 34 * 2,217 bytes "free" for updates and such

ops$tkyte@ORA9I.WORLD> delete from t where x <= 6 -- totally clear blocks 1 and
2;

6 rows deleted.

ops$tkyte@ORA9I.WORLD> delete from t where x = 7  -- remove 1 row from block 3;

1 row deleted.

ops$tkyte@ORA9I.WORLD> analyze table t compute statistics;
Table analyzed.

ops$tkyte@ORA9I.WORLD> select avg_space, blocks, empty_blocks from user_tables
where table_name = 'T';

 AVG_SPACE     BLOCKS EMPTY_BLOCKS
---------- ---------- ------------
      2619         35           28

ops$tkyte@ORA9I.WORLD> exec show_space( 'T' )
Free Blocks.............................4
Total Blocks............................64
Total Bytes.............................524288
Unused Blocks...........................28
Unused Bytes............................229376
Last Used Ext FileId....................7
Last Used Ext BlockId...................2057
Last Used Block.........................36

PL/SQL procedure successfully completed.

now, what this shows is we now have 2,619 bytes free/block on average or 402
bytes more per block then before.  So, we have 34*402 more bytes "free" for
updates and since we added to the freelist, for inserts as well.

search this site for

dbms_space

to get the code for show_space.

Review &
      Followup

      Rating: 4
No. of rows per
      block  November 16, 2001
Reviewer:  Nirmal Kumar
      Muthukumaran  from Doha, Qatar
Tom,

i followed your answer and i got doubt in ur statement

--"that shows I have 3 rows/block (8k blocksize, rows are 2k apiece)." --

I'm using Oracle817 on WinNT, and oracle block size is 4k. In my case, i'm able
to store 327 records per block in table t (the one u used in demo). But in your
case ur able to store only 3 rows per block. Why this much differ?..

Can you please explain me about this.

Nirmal.

          Followup:

I used a char(2000) -- did you (no -- you couldn't have OR you put null into it
so it takes 0 bytes).

A char(2000) is blank padded -- so when I insert 'x', its really inserting
rpad('x',2000).

Make sure to use char(2000) (or in your case, char(1000) should get you 3
rows/block) and insert something into it.

      Rating:
      4
  November 16, 2001
Reviewer:  Lisa
       from TX
Show_space utility is nice to let you see one table info.
How about if I want to find out in my database what tables use a lots of blocks
(with variable parameter input for blocks), do you have some utilities like
that?
Thanks.

          Followup:

Just create little functions like this:

create or replace function get_total_blocks
( p_segname in varchar2,
  p_owner   in varchar2 default user,
  p_type    in varchar2 default 'TABLE' ) return number
as
    l_free_blks                 number;

    l_total_blocks              number;
    l_total_bytes               number;
    l_unused_blocks             number;
    l_unused_bytes              number;
    l_LastUsedExtFileId         number;
    l_LastUsedExtBlockId        number;
    l_LAST_USED_BLOCK           number;
begin
    dbms_space.unused_space
    ( segment_owner     => p_owner,
      segment_name      => p_segname,
      segment_type      => p_type,
      total_blocks      => l_total_blocks,
      total_bytes       => l_total_bytes,
      unused_blocks     => l_unused_blocks,
      unused_bytes      => l_unused_bytes,
      LAST_USED_EXTENT_FILE_ID => l_LastUsedExtFileId,
      LAST_USED_EXTENT_BLOCK_ID => l_LastUsedExtBlockId,
      LAST_USED_BLOCK => l_LAST_USED_BLOCK );

    return l_total_blocks;
exception
    when others then return null;
end;
/

create or replace function get_unused_blocks
( p_segname in varchar2,
  p_owner   in varchar2 default user,
  p_type    in varchar2 default 'TABLE' ) return number
as
    l_free_blks                 number;

    l_total_blocks              number;
    l_total_bytes               number;
    l_unused_blocks             number;
    l_unused_bytes              number;
    l_LastUsedExtFileId         number;
    l_LastUsedExtBlockId        number;
    l_LAST_USED_BLOCK           number;
begin
    dbms_space.unused_space
    ( segment_owner     => p_owner,
      segment_name      => p_segname,
      segment_type      => p_type,
      total_blocks      => l_total_blocks,
      total_bytes       => l_total_bytes,
      unused_blocks     => l_unused_blocks,
      unused_bytes      => l_unused_bytes,
      LAST_USED_EXTENT_FILE_ID => l_LastUsedExtFileId,
      LAST_USED_EXTENT_BLOCK_ID => l_LastUsedExtBlockId,
      LAST_USED_BLOCK => l_LAST_USED_BLOCK );

    return l_unused_blocks;
exception
    when others then return null;
end;
/

create or replace function get_free_blocks
( p_segname in varchar2,
  p_owner   in varchar2 default user,
  p_type    in varchar2 default 'TABLE' ) return number
as
    l_free_blks                 number;
begin
    dbms_space.free_blocks
    ( segment_owner     => p_owner,
      segment_name      => p_segname,
      segment_type      => p_type,
      freelist_group_id => 0,
      free_blks         => l_free_blks );

    return l_free_blks;
exception
    when others then return null;
end;
/

and then you can easily:

select table_name, get_total_blocks( table_name ),
                   get_unused_blocks(table_name),
                   get_free_blocks(table_name)
 from user_tables
/

      Rating:
      4
Reader  November 16, 2001
Reviewer:
      Reader  from USA
Tom,

Does dbms_space get these information (total blocks, used blocks, freelist
blocks) from segment file header.

Thanks

          Followup:

yes it does.

      Rating: 5
Ca You pls
      Explain  November 18, 2001
Reviewer:
      A reader
Hi Tom,
<quote>
ops$tkyte@ORA9I.WORLD> select avg_space, blocks, empty_blocks from user_tables
where table_name = 'T';

 AVG_SPACE     BLOCKS EMPTY_BLOCKS
---------- ---------- ------------
      2217         35           28

ops$tkyte@ORA9I.WORLD> exec show_space( 'T' )
Free Blocks.............................2
Total Blocks............................64
Total Bytes.............................524288
Unused Blocks...........................28
Unused Bytes............................229376
Last Used Ext FileId....................7
Last Used Ext BlockId...................2057
Last Used Block.........................36

PL/SQL procedure successfully completed.

what this shows me is that before the deletes, the avg free space / block is
2,217 bytes.  We have 35 blocks in use (28 above the HWM).  Of the 35 blocks --
one is "overhead" for Oracle so we really have 34 blocks in use.  Therefore, we
have 34 * 2,217 bytes "free" for updates and such
</quote>

Where did this 35 Come into pict.

if you see the output of Show_Space

The Total Blocks are 64 and the Block above HWM are 28

So The Used blocks are 36 and less overhead it should be 35..

You had taken The Used_Blocks -1 But this does not sync with what SHOW_SPACE
Gives.

Hope to hear your comment on this one.

Regards,
Ganesh R

          Followup:

"Where did this 35 Come into pict."

the report shows 64 total blocks.  28 of which are "unused" -- above the HWM.
That leaves 36.  1 for overhead -- we have 35 blocks with data on them.

I don't see any conflicts here?  We are using 35 blocks  - analyze shows that,
show_space shows that, I used that number consistently.

      Rating: 4
What About
      34*2217  November 18, 2001
Reviewer:
      A reader
Hi Tom,

But what about u multiplying 34*2217 Should it not be 35*2217 ????

Since the number of used blocks are 35 and not 34.

Your Comments Pls.

Regards,
Ganesh R

          Followup:

Ahh, now it is clear -- yes, that should be 35, I took the 1 overhead block out
twice.  sorry about that.

      Rating:
      5
Reader  November 19, 2001
Reviewer:
      Reader  from USA
Tom,

I used these functions (get-total_blocks, get_unused_blocks,
get-free_blocks) on a table 3+ millions row table which
is comprised of
4 datafiles 2000M each
File_id
15
18
19
20
From dba_segments, header_file = 18 header_block =2.

The functions took (15:37 to 15:49) 12 minutes to return
result. During this time, I found from v$session_wait,
it was waiting on "db file sequential read" on file#20
block# (various blocks by executing the query several times)

Could you explain why "db file sequential read" wait on the executing SID for
file#20, when the  header block is file#18 Block# 2

Thanks

          Followup:

Show me the query -- I cannot reproduce such an issue:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select table_name, get_total_blocks(
table_name ) totblocks,
  2                     get_unused_blocks(table_name) unusedblocks,
  3                     get_free_blocks(table_name) freeblocks
  4   from user_tables
  5  /

TABLE_NAME                      TOTBLOCKS UNUSEDBLOCKS FREEBLOCKS
------------------------------ ---------- ------------ ----------
ADDRESS_CHANGES                        64           62          1
BIGGER_TABLE                        76544           25          0
BIG_TABLE                           15366          490          1
BOM                                    64           62          1
DATA_TABLE                             64           62          1
DEPT                                   64           62          0
EMP                                    64           61          1
EMP_SNAP                               64           62          0
EMP_SNAP2                              64           62          1
HASHED
MLOG$_EMP                              64           62          1
MY_TABLE                               64           62          0
NUM_STR                                64           63          0
OT                                     64           62          0
PARTS                                  64           62          1
PLAN_TABLE                             64           62          1
RUN_STATS                              64           53          3
RUPD$_EMP
T                                      64           62          1
T1                                      2            0          1
T2                                      2            0          1
TT                                     10            0          4
T_STORE                                 2            0          1
T_TABLE                                 2            0          1
USLOG$_EMP_SNAP                        64           63          0

25 rows selected.

Elapsed: 00:00:00.08

bigger_table is a 600 meg table -- My system is no where NEAR fast enough to
read that much in 0.08 seconds!  It has 34,981,888 rows in it.

Post the CREATE TABLE as well.

      Rating:
      5
Reader  November 20, 2001
Reviewer:
      Reader  from USA
Tom,

Here is the query, table creation script and statistics:
========================================================
Database version 8.1.6 (Unix)
=============================
I executed the query logged on as myself and I have DBA
privs. I tried the query both, conversion of blocks to MBytes and printing
result in blocks. Got similar results

06:32:08 SQL> select sid from v$mystat where rownum = 1;

       SID
----------
       379

Elapsed: 00:00:00.06
06:32:30 SQL> get space-1.sql
  1  select table_name, get_total_blocks(table_name, 'SCM1')*8/1024 total,
get_unused_blocks(table_name, 'SCM1')*8/1024 unused,
  2*  get_free_blocks(table_name,'SCM1') free from dba_tables where owner =
'SCM1' and table_name = '&table_name';
06:32:42 SQL> @space-1.sql
Enter value for table_name: SCM1_RESP_123_P
old   2:  get_free_blocks(table_name,'SCM1') free from dba_tables where owner =
'SCM1' and table_name = '&table_name'
new   2:  get_free_blocks(table_name,'SCM1') free from dba_tables where owner =
'SCM1' and table_name = 'SCM1_RESP_123_P'

TABLE_NAME            TOTAL     UNUSED       FREE
------------------ ---------- ---------- ----------
SCM1_RESP_123_P          8000 1179.17969     315890

Elapsed: 00:11:40.49

The Table Creation Script:
==========================
Planning on conversion long to clob soon

CREATE TABLE scm1.scm1_resp_123_p
(
    seq                    VARCHAR2(9)  NOT NULL,
    seq_ext                NUMBER(2,0)  NOT NULL,
    hit_status             VARCHAR2(1)  NOT NULL,
    resp_text_len          NUMBER(7,0)  NOT NULL,
    resp_text                     LONG  NOT NULL
)
PARALLEL ( DEGREE          1 INSTANCES          1 )
TABLESPACE SCM1_PERM_P1
PCTFREE 20
PCTUSED 80
INITRANS 1
MAXTRANS 121
STORAGE
(
   INITIAL 2000M
   NEXT 2000M
   MINEXTENTS 1
   MAXEXTENTS 2147483645
   PCTINCREASE 0
   FREELISTS 1
   FREELIST GROUPS 1
)

v$session_wait statistics (subset):
===================================
06:37:25 SQL> /

SID  SEQ# EVENT    P1         P2         P3 STATE
---------- ---------- ------------- -------------------
379      20495 db file sequential read  20 161888 1 WAITING

06:37:26 SQL> /

SID  SEQ# EVENT     P1         P2         P3 STATE
---------- ---------- ----------- -------------------
379      26966 db file sequential read  20 150296 1 WAITING

06:42:02 SQL> /

SID  SEQ# EVENT     P1         P2         P3 STATE
---------- ---------- ------------ -------------------
379      17041 db file sequential read  18 132396 1 WAITING

06:44:20 SQL> /

SID  SEQ# EVENT      P1         P2         P3 STATE
---------- ---------- --- ---------- -------------------
379      50306 SQL*Net message from client 1650815232 1          0 WAITING

Statistics from x$bh and v$laarch_children on segment
=====================================================
header_file and header_blk (subset)
===================================

06:40:48 SQL> select a.hladdr, a.dbarfil, a.dbablk, b.gets, b.sleeps from
v$latch_children b, x$bh a
06:40:53   2  where a.hladdr = b.addr and dbarfil = 18 and dbablk = 2;

HLADDR              DBARFIL     DBABLK       GETS     SLEEPS
---------------- ---------- ---------- ---------- ----------
0000000833F9CB88         18          2     134454         27

06:44:32 SQL> /

HLADDR              DBARFIL     DBABLK       GETS     SLEEPS
---------------- ---------- ---------- ---------- ----------
0000000833F9CB88         18          2     135142         27

Thanks

          Followup:

Ok, NOW i see ;)

Tons of your blocks are on the freelist.  We walk the freelist from the segment
header to get the count.  Its a linked list.

Consider this example:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select 'BIG_TABLE',
  2             get_total_blocks('BIG_TABLE', user)*8/1024 total,
  3         get_unused_blocks('BIG_TABLE', user)*8/1024 unused,
  4         get_free_blocks('BIG_TABLE', user) free
  5    from dual
  6  /

'BIG_TABL      TOTAL     UNUSED       FREE
--------- ---------- ---------- ----------
BIG_TABLE 120.046875   3.828125          1

Elapsed: 00:00:00.04

very very fast....  Very very small freelists

ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from big_table;
1093184 rows deleted.
Elapsed: 00:27:18.06

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.
Elapsed: 00:00:00.80

ops$tkyte@ORA817DEV.US.ORACLE.COM> select 'BIG_TABLE',
  2             get_total_blocks('BIG_TABLE', user)*8/1024 total,
  3         get_unused_blocks('BIG_TABLE', user)*8/1024 unused,
  4         get_free_blocks('BIG_TABLE', user) free
  5    from dual
  6  /

'BIG_TABL      TOTAL     UNUSED       FREE
--------- ---------- ---------- ----------
BIG_TABLE 120.046875   3.828125      14875

Elapsed: 00:01:30.78

now, very very slow -- we had tons of free list blocks to "walk"

ops$tkyte@ORA817DEV.US.ORACLE.COM> select 'BIG_TABLE',
  2             get_total_blocks('BIG_TABLE', user)*8/1024 total,
  3         get_unused_blocks('BIG_TABLE', user)*8/1024 unused
  4    from dual
  5  /

'BIG_TABL      TOTAL     UNUSED
--------- ---------- ----------
BIG_TABLE 120.046875   3.828125

Elapsed: 00:00:00.02
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> spool off

that shows it was the overhead of walking the freelists, without the
get_free_blocks it runs very fast again.

      Rating:
      5
Reader  November 20, 2001
Reviewer:
      Reader  from USA
Tom,

Thanks very much
Amazingly quick resolution!

However could dbms_space read the # of free blocks from
the segment header:

Symbolic dump of header block (file 18 block 2)
===============================================
I know you do not encouage us doing these without
Oracle Support, just to have a look at the concept:

*** SESSION ID:(379.19) 2001-11-20 09:55:32.000
Start dump data blocks tsn: 12 file#: 18 minblk 2 maxblk 2
buffer tsn: 12 rdba: 0x04800002 (18/2)
scn: 0x0000.198b90bb seq: 0x01 flg: 0x00 tail: 0x90bb1001
frmt: 0x02 chkval: 0x0000 type: 0x10=DATA SEGMENT HEADER - UNLIMITED

  Extent Control Header
  -----------------------------------------------------------------
  Extent Header:: spare1: 0      space2: 0      #extents: 4      locks:
1023999
       ================
                  last map  0x00000000  #maps: 0      offset: 4128
      Highwater::  0x03c19a6b  ext#: 3      blk#: 105065 ext size: 256000
  locks in seg. hdr's freelists: 315883
                        =================
  locks below: 873064
  =====================
  mapblk  0x00000000  offset: 3
                   Unlocked
     Map Header:: next  0x00000000  #extents: 4    obj#: 18372  flag: 0x40000000

Thanks

          Followup:

They could, but it doesn't work in all cases... consider:

DOC>create table t storage ( freelists 4 freelist groups 2 )
DOC>as
DOC>select * from all_objects;
DOC>
DOC>delete from t;
DOC>commit;
DOC>*/
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> @get_free_blocks
ops$tkyte@ORA817DEV.US.ORACLE.COM> set define off
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function get_free_blocks
  2  ( p_segname in varchar2,
  3    p_owner   in varchar2 default user,
  4    p_type    in varchar2 default 'TABLE',
  5    p_groupid in number default 0 ) return number
  6  as
  7      l_free_blks                 number;
  8  begin
  9      dbms_space.free_blocks
 10      ( segment_owner     => p_owner,
 11        segment_name      => p_segname,
 12        segment_type      => p_type,
 13        freelist_group_id => p_groupid,
 14        free_blks         => l_free_blks );
 15
 16      return l_free_blks;
 17  exception
 18      when others then return null;
 19  end;
 20  /

Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set define on
ops$tkyte@ORA817DEV.US.ORACLE.COM> select get_free_blocks( 'T', user ) from
dual;

GET_FREE_BLOCKS('T',USER)
-------------------------
                        0

1 row selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select get_free_blocks( 'T', user, 'TABLE', 1
) from dual;

GET_FREE_BLOCKS('T',USER,'TABLE',1)
-----------------------------------
                                  0

1 row selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select get_free_blocks( 'T', user, 'TABLE', 2
) from dual;

GET_FREE_BLOCKS('T',USER,'TABLE',2)
-----------------------------------
                                234

1 row selected.

If you dump that segment header, it'll show 0 on the freelists.  Its only cause
we aren't using freelist groups that the number is there.

      Rating:
      5
Reader  November 20, 2001
Reviewer:
      Reader  from USA
Tom,

Thanks.

I did a test.
1. Created a table ( freelists 4 freelist groups 2).
2. The header_file = 2 and header_block = 19227.
3. Symbolic dump of blocks 19227 to 19232
4. Found the freelist blocks of group 2 listed on
   Block 19229 (= 45 freelist blocks)

Could we use header block as global free list group and
use the subsequent DBA to point one for each freelist
group consistently

Here are the results:
=====================

SQL> create table t storage ( freelists 4 freelist groups 2)
  2  tablespace tools
  3  as select * from all_objects;

Table created.

SQL> select count(*) from t;

  COUNT(*)
----------
      3681

SQL> delete from t;

3681 rows deleted.

SQL> commit;

Commit complete.

SQL> variable x number;
SQL> exec dbms_space.free_blocks('SYS','T','TABLE',2,:x);

PL/SQL procedure successfully completed.

SQL> print x

         X
----------
        45

SQL> select SEGMENT_NAME,OWNER,HEADER_FILE,HEADER_BLOCK from dba_segments where
     SEGMENT_NAME = 'T';

SEGMENT_NAME OWNER  HEADER_FILE HEADER_BLOCK
------------------------------ ----------- ------------
T SYS          7        19227

Start dump data blocks tsn: 6 file#: 7 minblk 19227 maxblk 19232
buffer tsn: 6 rdba: 0x01c04b1b (7/19227)
scn: 0x0000.00a943d4 seq: 0x02 flg: 0x04 tail: 0x43d41102
frmt: 0x02 chkval: 0x1093 type: 0x11=DATA SEGMENT HEADER WITH FREE LIST BLKS -
UNLIMITED

  Extent Control Header
  -----------------------------------------------------------------
  Extent Header:: spare1: 0      space2: 0      #extents: 1      locks: 6397
                  last map  0x00000000  #maps: 0      offset: 4128
      Highwater::  0x01c04b4b  ext#: 0      blk#: 45     ext size: 6397
  locks in seg. hdr's freelists: 0
  locks below: 45
  mapblk  0x00000000  offset: 0
      Disk Lock:: Locked by scn:  0x0006.02c.00000021
     Map Header:: next  0x00000000  #extents: 1    obj#: 4448   flag: 0x40000000
  Extent Map
  -----------------------------------------------------------------
   0x01c04b1e  length: 6397

  nfl = 4, nfb = 3 typ = 1 nxf = 0
  SEG LST:: flg: UNUSED lhd: 0x00000000 ltl: 0x00000000
buffer tsn: 6 rdba: 0x01c04b1c (7/19228)
scn: 0x0000.00a943d2 seq: 0x02 flg: 0x04 tail: 0x43d21602
frmt: 0x02 chkval: 0x4f15 type: 0x16=DATA SEGMENT FREE LIST BLOCK WITH FREE
BLOCK COUNT

  blocks in free list = 0
  SEG LST:: flg: UNUSED lhd: 0x00000000 ltl: 0x00000000
  SEG LST:: flg: UNUSED lhd: 0x00000000 ltl: 0x00000000
  SEG LST:: flg: UNUSED lhd: 0x00000000 ltl: 0x00000000
  SEG LST:: flg: UNUSED lhd: 0x00000000 ltl: 0x00000000
  SEG LST:: flg: UNUSED lhd: 0x00000000 ltl: 0x00000000
buffer tsn: 6 rdba: 0x01c04b1d (7/19229)
scn: 0x0000.00a943e4 seq: 0x01 flg: 0x00 tail: 0x43e41601
frmt: 0x02 chkval: 0x0000 type: 0x16=DATA SEGMENT FREE LIST BLOCK WITH FREE
BLOCK COUNT

  blocks in free list = 45
  ========================
  SEG LST:: flg: USED   lhd: 0x01c04b4a ltl: 0x01c04b1e
  SEG LST:: flg: UNUSED lhd: 0x00000000 ltl: 0x00000000
  SEG LST:: flg: UNUSED lhd: 0x00000000 ltl: 0x00000000
  SEG LST:: flg: UNUSED lhd: 0x00000000 ltl: 0x00000000
  SEG LST:: flg: UNUSED lhd: 0x00000000 ltl: 0x00000000
  XCT LST:: flg: UNUSED lhd: 0x00000000 ltl: 0x00000000 xid: 0x0000.000.00000000
buffer tsn: 6 rdba: 0x01c04b1e (7/19230)
scn: 0x0000.00a943e4 seq: 0x01 flg: 0x00 tail: 0x43e40601
frmt: 0x02 chkval: 0x0000 type: 0x06=trans data

Block header dump:  0x01c04b1e
 Object id on Block? Y
 seg/obj: 0x1160  csc: 0x00.a943d2  itc: 1  flg: O  typ: 1 - DATA
     fsl: 1  fnx: 0x0 ver: 0x01

 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   xid:  0x0002.02f.0000001c    uba: 0x00800720.0003.04  --U-   86  fsc
0x1b1e.00a943e2

data_block_dump
===============
tsiz: 0x1fb8
hsiz: 0xbc
pbl: 0x11cbfe44
bdba: 0x01c04b1e
flag=-----------
ntab=1
nrow=85
frre=-1
fsbo=0xbc

......

Thanks

          Followup:

On this one -- I'll have to pass.  "consistently" -- i don't know -- especially
in light of the fact that we are moving the automated segment space management
in 9i (doing to segment space mgmt what locally managed tablespaces did to
extent management).  Then all bets are off.  These things change over time.

      Rating:
      5
Reader  November 20, 2001
Reviewer:
      Reader  from USA
Tom,

Thanks for these discussions.

Every day I learn a lot of information from your web site.
I have found this site, to be the best, and full of
accurate and most current information, with reference to
9i enhancements as well.

Keep up your brilliant work

      Rating:
      5
  June 03, 2003
Reviewer:
      A reader
Hi Tom,

I am trying to analyze block dump of compressed table and regular table but
can't find any difference.. Could you explain below block dump...

Regular table
-----------------
Dump file e:\oracle\admin\zebra\udump\zebra_ora_996.trc
Tue Jun 03 20:01:18 2003
ORACLE V9.2.0.1.0 - Production vsnsta=0
vsnsql=12 vsnxtr=3
Windows 2000 Version 5.1 , CPU type 586
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
Windows 2000 Version 5.1 , CPU type 586
Instance name: zebra

Redo thread mounted by this instance: 1

Oracle process number: 19

Windows thread id: 996, image: ORACLE.EXE

*** 2003-06-03 20:01:18.000
*** SESSION ID:(18.8898) 2003-06-03 20:01:18.000
Start dump data blocks tsn: 9 file#: 9 minblk 350601 maxblk 350601
buffer tsn: 9 rdba: 0x02455989 (9/350601)
scn: 0x0001.9239ec95 seq: 0x01 flg: 0x04 tail: 0xec952001
frmt: 0x02 chkval: 0x7ce4 type: 0x20=FIRST LEVEL BITMAP BLOCK
Dump of First Level Bitmap Block
 --------------------------------
   nbits : 4 nranges: 1         parent dba:  0x0240caf2   poffset: 8
   unformatted: 0       total: 64        first useful block: 2
   owning instance : 1
   instance ownership changed at
   Last successful Search
   Freeness Status:  nf1 0      nf2 0      nf3 0      nf4 0

   Extent Map Block Offset: 4294967295
   First free datablock : 64
   Bitmap block lock opcode 0
   Locker xid:     :  0x0000.000.00000000
      Highwater::  0x00000000  ext#: 0      blk#: 0      ext size: 0
  locks in seg. hdr's freelists: 0
  locks below: 0
  mapblk  0x00000000  offset: 0
  HWM Flag: Not Set
  --------------------------------------------------------
  DBA Ranges :
  --------------------------------------------------------
   0x02455989  Length: 64     Offset: 0

   0:Metadata   1:Metadata   2:FULL   3:FULL
   4:FULL   5:FULL   6:FULL   7:FULL
   8:FULL   9:FULL   10:FULL   11:FULL
   12:FULL   13:FULL   14:FULL   15:FULL
   16:FULL   17:FULL   18:FULL   19:FULL
   20:FULL   21:FULL   22:FULL   23:FULL
   24:FULL   25:FULL   26:FULL   27:FULL
   28:FULL   29:FULL   30:FULL   31:FULL
   32:FULL   33:FULL   34:FULL   35:FULL
   36:FULL   37:FULL   38:FULL   39:FULL
   40:FULL   41:FULL   42:FULL   43:FULL
   44:FULL   45:FULL   46:FULL   47:FULL
   48:FULL   49:FULL   50:FULL   51:FULL
   52:FULL   53:FULL   54:FULL   55:FULL
   56:FULL   57:FULL   58:FULL   59:FULL
   60:FULL   61:FULL   62:FULL   63:FULL
  --------------------------------------------------------
End dump data blocks tsn: 9 file#: 9 minblk 350601 maxblk 350601

Compressed table
==================
Start dump data blocks tsn: 9 file#: 9 minblk 349449 maxblk 349449
buffer tsn: 9 rdba: 0x02455509 (9/349449)
scn: 0x0001.9239efe6 seq: 0x01 flg: 0x04 tail: 0xefe62001
frmt: 0x02 chkval: 0xe239 type: 0x20=FIRST LEVEL BITMAP BLOCK
Dump of First Level Bitmap Block
 --------------------------------
   nbits : 4 nranges: 1         parent dba:  0x0245542a   poffset: 8
   unformatted: 0       total: 64        first useful block: 2
   owning instance : 1
   instance ownership changed at
   Last successful Search
   Freeness Status:  nf1 0      nf2 0      nf3 0      nf4 0

   Extent Map Block Offset: 4294967295
   First free datablock : 64
   Bitmap block lock opcode 0
   Locker xid:     :  0x0000.000.00000000
      Highwater::  0x00000000  ext#: 0      blk#: 0      ext size: 0
  locks in seg. hdr's freelists: 0
  locks below: 0
  mapblk  0x00000000  offset: 0
  HWM Flag: Not Set
  --------------------------------------------------------
  DBA Ranges :
  --------------------------------------------------------
   0x02455509  Length: 64     Offset: 0

   0:Metadata   1:Metadata   2:FULL   3:FULL
   4:FULL   5:FULL   6:FULL   7:FULL
   8:FULL   9:FULL   10:FULL   11:FULL
   12:FULL   13:FULL   14:FULL   15:FULL
   16:FULL   17:FULL   18:FULL   19:FULL
   20:FULL   21:FULL   22:FULL   23:FULL
   24:FULL   25:FULL   26:FULL   27:FULL
   28:FULL   29:FULL   30:FULL   31:FULL
   32:FULL   33:FULL   34:FULL   35:FULL
   36:FULL   37:FULL   38:FULL   39:FULL
   40:FULL   41:FULL   42:FULL   43:FULL
   44:FULL   45:FULL   46:FULL   47:FULL
   48:FULL   49:FULL   50:FULL   51:FULL
   52:FULL   53:FULL   54:FULL   55:FULL
   56:FULL   57:FULL   58:FULL   59:FULL
   60:FULL   61:FULL   62:FULL   63:FULL
  --------------------------------------------------------
End dump data blocks tsn: 9 file#: 9 minblk 349449 maxblk 349449

============

I am trying to see how data is compressed in block for compressed table. Please
help

Thanks

          Followup:

Nope, I don't read block dumps except under duress.  (but it does appear you are
looking at a bitmap block used to manage space, not an actual data block)

      Rating: 4
Table Directory
      of a Block  June 11, 2003
Reviewer:  Vivek
      Sharma  from Bombay, India
Dear Tom,

In your book Expert one on one, on Page 63, you have mentioned that there is a
table directory whihc records the information about the tables that store rows
in a block, since data from more than one table can be stored in a block. I have
a query.
If I have 2 tables in a tablespace with different pctused and pctfree
parameters, how can these store data in a same block. i.e. when will the block
storing data from 2 different tables with different pctfree/pctused parameters
be removed from freelist and will be put to freelist ?

I think u got my point.
For Table A (pctfree is 10 and pctused is 40)
For Table B (pctfree is 20 and pctused is 20), if they store a row on a same
block, when will the block be removed from freelist, after filling 90% of the
block or 80% of the block.

Your explanation will be very helpful.

Thanks and Regards
Vivek Sharma

          Followup:

storage options for for the SEGMENT.

if you use a cluster, to store data from more then one table in a single block,
the segement is the CLUSTER, not the tables.

So, the conundrum you describe cannot happen.

      Rating: 4
Thanks
      Tom  June 12, 2003
Reviewer:  Vivek Sharma
       from Bombay, India
Thanks tom for your reply and patience. I am sorry actually this was a very
silly question.

Regards
Vivek

          Followup:

June 12, 2003 Suzanne -- Thanks for the question regarding
      "Returning cursor from stored procedure using dynamic sql", version Oracle
      8.0.5.2.1/PLSQL 8.0.5.2.0

      You Asked
Hello -

I have seen many different ways to return cursors from an Oracle
stored procedure using a static sql statement.  However, can you
return a cursor from a dynamic sql statement?  I am currently
using OPEN FOR, but this does not work with a dynamic sql
statement.  Thank you in advance.

      and we said...
Thats a feature added in Oracle8i release 8.1 and up...

In that release you can code:

ops$tkyte@8i> variable x refcursor
ops$tkyte@8i> set autoprint on
ops$tkyte@8i>
ops$tkyte@8i> declare
  2      some_query varchar2(255) default
  3                 'select ename, empno from emp where ename
like :x';
  4      some_bind_variable varchar2(255) default
  5                 '%A%';
  6  begin
  7      open :x for some_query USING some_bind_variable;
  8  end;
  9  /

PL/SQL procedure successfully completed.

ENAME           EMPNO
---------- ----------
ALLEN            7499
WARD             7521
MARTIN           7654
BLAKE            7698
CLARK            7782
ADAMS            7876
JAMES            7900

7 rows selected.

ops$tkyte@8i>

Prior to 8i, it is impossible to dynamically open a REF CURSOR.
Only DBMS_SQL can open any arbitrary query (but thats not really
a cursor then)...

Review &
      Followup

      Rating: 3
Yes this is
      this is useful but it is not all of the functionality that we
      need.  March 20, 2001
Reviewer:  Bradley
      Shockley  from KS USA
The functionality that we need is the following:

where_string_in := ' AND U.USER_PASSWORD = ''fishy''';

DBMS_SQL.PARSE(cursor_number_loc,
        'OPEN cursor_in_out FOR SELECT
            G.GROUP_NAME,
        FROM
            SW_GROUP G,
            SW_USER_GROUP UG,
            SW_USER U
        WHERE
            UG.SW_GROUP_GUID = G.SW_GROUP_GUID
        AND
            UG.SW_USER_GUID = U.SW_USER_GUID
:where_string_in
        ORDER BY
            GROUP_NAME',
            DBMS_SQL.NATIVE);

Only we need this for use with a REF CURSOR.  We are using Cold Fusion and need
to be able to have a dynamic where clause added to a select that we are creating
in a Stored Procedure.  Then passing back to the caller as a REF CURSOR.

Any help that could be given would be great.

Thank you

      Rating: 4
My related
      question  March 10, 2003
Reviewer:  John
       from New Jersey, USA
Tom: Can you return a REF CURSOR with the where clause built dynamically?

Something like this (use scott.emp as an example), but with one query and
dyanamically-built where clause, or perhaps DECODE/CASE in select list:

if salary < 3000
    select ename, job from emp where hiredate < to_date('mm/dd/yyyy',
'01/01/1982')

if commission > 1000
    select ename, mgr from emp where hiredate > to_date('mm/dd/yyyy',
'01/01/1982')

if salary >= 3000 and commission <= 1000
    select ename, sal, comm from emp where job = 'MANAGER' and sal >= 3000 and
comm <= 1000

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279

      Rating: 5
Thanks
      Tom.  March 18, 2003
Reviewer:  John
       from New Jersey, USA
The example is used with dbms_session.set_context.  I haven't got a chance to
try it out.  Bit I would think it should also work without using
dbms_session.set_context, isn't it?

          Followup:

huh? what example exactly -- if you mean the linked to article above - well the
answer to that would be "no, the entire example is predicated on
dbms_session.set_context"

      Rating: 5
Yes, the
      example in the linked article.  March 25, 2003

Reviewer:  John  from New Jersey, USA
So the only way to do dynamic where clause is to use dbms_session.set_context?

          Followup:

only way?  no.

there is cursor_sharing
there are other "where clause tricks"

but this is the one I prefer.

      Rating: 5
Yes, the
      example in the linked article.  March 25, 2003

Reviewer:  John  from New Jersey, USA
So the only way to do dynamic where clause is to use dbms_session.set_context?

      Rating: 5
Taking this a
      step further with a dynamic USING clause...  June 12, 2003

Reviewer:  Debbie Hamilton  from Tallahassee, FL USA

Super info!!  I'm needing to take this all one step further, though.  I'm having
to translate a ton of logic that is currently using the DBMS_SQL into native
dynamic sql since you can't transfer the data out of the DBMS_SQL cursor into a
REF CURSOR (that I know of... or can you?).  When you consider building a
dynamic sql statement with a dynamic WHERE clause using bind variables, one
would almost certainly need to build the USING clause dynamically as well.
Could you elaborate on the syntax for creating a dynamic USING clause?  All of
this is to be utilized by a REF CURSOR that is being returned by a packaged
procedure.

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279

      Rating: 3
Maybe I'm just
      blind....  June 12, 2003
Reviewer:  Debbie
       from FL USA
I read the whole page (twice) and didn't see an example of a dynamically
generated USING clause?  Were you trying to suggest something else?

          Followup:

there is no such thing as a dynamically generated USING clause.

that page shows you how to BIND with native dynamic sql when you don't know the
number of inputs to the query at compile time.

June 12, 2003 Rob
      -- Thanks for the question regarding "how to find my sessions record in
      v$session", version

      You Asked
How do I get the program name at
execution time? It is in v$session,
but that returns many rows, some of them
with null username. This is being used
in a trigger for auditing purposes.
I thought I had it by creating a procedure
in SYS using the following:
Create procedure get_prog_id
(prog_id OUT varchar2)
as
begin
select substr(program, 1, 8) INTO prog_id
from v$session
where user = username;
end;
SYS has to grant execute on this to PUBLIC.
However, I got tripped up today due to an
inactive session (so the select returned
two rows and failed).

Your assistance would be much appreciated.

Thanks

Rob Das

      and we said...
the query you are looking for is:

 select substr(program, 1, 8) INTO prog_id
 from v$session
 where audsid = userenv('sessionid');

that'll get the row from v$session for the current session.

Review &
      Followup

      Rating: 4
Confused on
      difference between SID, SERIAl#, SESSIONID, AUDSID  June 12,
      2003
Reviewer:  Joseph Chuan  from VA, USA

Tom

    Thanks for all your valuable insights.  I have a follow-up question.  After
reading many of your responses on SESSIONID, is it correct to state that

- SID combined (somehow) with SERIAL# make up SESSIONID
- SESSIONID = AUDSID (just different name when use for auditing)
- SID and SERIAL# are not unique each can be reused there SESSIONID is also not
UNIQUE i.e. two users at a different time can have the same SESSIONID.  It is
not unique across time in the same database.
- Is there a routine to get a (1) within the database ID that is always UNIQUE
(2) across the database, a globally unique number.

Thanks

          Followup:

sessionid comes from the sequence sys.audses$, it has no relation to sid/serial#

sid and serial# are unique, you won't see two sid/serials at the same time.
sessionid is unique but can wrap around over time.

sys_guid()?  does that meet your needs?

      Rating:
      5
  June 12, 2003
Reviewer:  Prasad
       from SF,CA
Tom,

you said

sessionid is unique but can wrap around over time.

But what  I see is  sid is getting generated on a max+1 basis . while sessionid
seems sequence generated unique ( I am not sure if it wraps around over time).

This is my session output

pk@fd>@connect pk@ed
Enter password: ***
Connected.
pk@ed>select distinct sid from v$mystat;

       SID
----------
        12

pk@ed>select sid,audsid from v$session;

       SID     AUDSID
---------- ----------
         1          0
         2          0
         3          0
         4          0
         5          0
         6          0
        10   10892773
        12   10892774

8 rows selected.

pk@ed>disco
Disconnected from Oracle9i Enterprise Edition Release 9.0.1.3.0 - 64bit
Production
With the Partitioning option
JServer Release 9.0.1.3.0 - Production
pk@ed>@connect pk@ed
Enter password: ***
Connected.
pk@ed>select distinct sid from v$mystat;

       SID
----------
        12

pk@ed>select sid,audsid from v$session;

       SID     AUDSID
---------- ----------
         1          0
         2          0
         3          0
         4          0
         5          0
         6          0
        10   10892773
        12(same as session before)   10892775(Changed)

8 rows selected.

your input please.

Thanks

          Followup:

in looking at v$session, sid serial are unique.  will they be reused?  sure.
are they unique at a point in time?  yes - definitely.

they are not good for a "primary key to a session", nor is "audsid" really cause
it can wrap.  you would have to generate your "own key" for this.

June 12, 2003 gaurang -- Thanks for the question regarding
      "ORA-01025: UPI parameter out of range", version 8.1.6

      You Asked
hi tom ,
i am getting this error message

ORA-01025: UPI parameter out of range

i have checked metlink for this and it is telling that if you use
 BULK COLLECT across a database link then you get this error

I am doing same thing

Is there any work around for this?

(My requirement is as follows

 We are using siebel CRM application

 I have customer number and i have to get last year ordered products
 from lagacy system by passing cust no. so i am using database link and to speed
up insert operation
 i am using BULK COLLECT caluse. i asked this question to you and u told me to
use BULK COLLECT)

 reply as soon as possible

      and we said...
bulk collect is not supported via a database link.  There is no way to use it
over a database link.

Here is an example that demonstrates the issue and shows one possible workaround
(use object types instead of a plsql table type)

scott@ORA815> declare
  2      type vcArray is table of varchar2(35) index by binary_integer;
  3
  4      l_data vcArray;
  5  begin
  6      select username BULK COLLECT into l_data from all_users where rownum <=
10;
  7      for i in 1 .. l_data.count
  8      loop
  9          dbms_output.put_line( l_data(i) );
 10      end loop;
 11  end;
 12  /
SYS
SYSTEM
OUTLN
DBSNMP
OPS$ORA815
AURORA$ORB$UNAUTHENTICATED
SCOTT
OPS$TKYTE

PL/SQL procedure successfully completed.

scott@ORA815> declare
  2      type vcArray is table of varchar2(35) index by binary_integer;
  3
  4      l_data vcArray;
  5  begin
  6      select username BULK COLLECT into l_data from all_users@ora8i.world
where rownum <= 10;
  7  end;
  8  /
declare
*
ERROR at line 1:
ORA-01025: UPI parameter out of range
ORA-02063: preceding line from ORA8I.WORLD
ORA-06512: at line 6

scott@ORA815>
scott@ORA815> create or replace type vcArray as table of varchar2(35)
  2  /

Type created.

scott@ORA815>
scott@ORA815> declare
  2      l_data vcArray;
  3  begin
  4
  5      select cast( multiset( select username
  6                               from all_users@ora8i.world
  7                              where rownum <= 10 ) as vcArray )
  8        into l_data
  9        from dual;
 10
 11      for i in 1 .. l_data.count
 12      loop
 13          dbms_output.put_line( l_data(i) );
 14      end loop;
 15  end;
 16  /
SYS
SYSTEM
OUTLN
TRACESVR
CTXSYS
OEM
ORDSYS
ORDPLUGINS
MDSYS
WEB$CDEJESUS

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating:
      5
Divine!  February 27, 2003
Reviewer:
      Urs  from Germany

      Rating: 4
driving_site
      hint  June 12, 2003
Reviewer:  Scott Wesley
       from Perth, WA
G'day

It's quite interesting actually, I found Metalink note 198842.1 relating to
this, because I have had some success with bulk selects over a database link
using version 8.1.7.4

However, when I add the DRIVING_SITE hint to my query, asking it to drive the
query remotely, then it raises the 01025 error.

Do you have any comment on this?

          Followup:

yes, bulk collect over a dblink isn't supported in 8i.

June
      13, 2003 Ashok -- Thanks for the
      question regarding "Index Not used on a null column", version 8.1.7

      You Asked
Hi tom,

Here is my problem.Ihave a table called sf_deal_link_xref which contains around
7500 rows (Currently)

CREATE TABLE sf_deal_link_xref
 (
  seqid                      NUMBER(10) NOT NULL,
  tr_comps_id                VARCHAR2(10),
  sfdb_id                    VARCHAR2(10),
  tr_sf_link_date            DATE,
  tr_sf_link_emp_id          VARCHAR2(6),
  tr_unlinked_flag           VARCHAR2(1),
  tr_unlinked_reason         VARCHAR2(2000),
  clnt_id                    VARCHAR2(12),
  clnt_id_mod_app            VARCHAR2(16),
  clnt_id_mod_eca            VARCHAR2(6),
  clnt_id_mod_date           DATE,
  mod_flag                   VARCHAR2(1),
  gsf_mod_flag               VARCHAR2(1),
  est_allocated              VARCHAR2(1),
  allocation_date            DATE
 )
 PCTFREE    10
 PCTUSED    40
 INITRANS   1
 MAXTRANS   255
/

-- Indexes for SF_DEAL_LINK_XREF

CREATE  UNIQUE INDEX sf_deal_link_xref_pk
 ON sf_deal_link_xref
  ( seqid  )
  PCTFREE    10
  INITRANS   2
  MAXTRANS   255
/

CREATE  UNIQUE INDEX sf_deal_link_xref_pk2
 ON sf_deal_link_xref
  ( tr_comps_id,
    sfdb_id  )
  PCTFREE    10
  INITRANS   2
  MAXTRANS   255
/

CREATE  INDEX sf_deal_link_xref_sf
 ON sf_deal_link_xref
  ( sfdb_id  )
  PCTFREE    10
  INITRANS   2
  MAXTRANS   255
/

CREATE  INDEX sf_deal_link_xref_tr
 ON sf_deal_link_xref
  ( tr_comps_id,
    clnt_id  )
  PCTFREE    0
  INITRANS   2
  MAXTRANS   255
/

ALTER TABLE sf_deal_link_xref
 ADD CONSTRAINT sf_deal_link_xref_pk PRIMARY KEY (seqid)
/

/

ALTER TABLE sf_deal_link_xref
 ADD CHECK ("SEQID" IS NOT NULL)
/

When i  try to run this simple query which is going for FTS why?
(we are running oracle version 8.1.7 Rule Based)

select tr_comps_id from sf_deal_link_xref where  clnt_id =650474 and tr_comps_id
= 107192

TIA
Ashok

      and we said...
One comment.  In 817 -- use the CBO.  You paid for lots of features you won't be
able to use without it.  RBO is dead.

Well, two comments -- if your columns contain NUMBERS, use that datatype.  Use
the correct datatypes.

Well, ok three comments -- primary key IMPLIES not null.  That not null
constraint is just adding extra CPU overhead to every insert.  We'll do that
check twice now.

In my book I have a chapter on indexes.  One of the sections in that chapter is
entitled "Why isn't my index getting used?"  You might find this book useful in
your working with Oracle

You are quite simply case number 4.

<quote>

Case 4

You have indexed a character column. This column contains only numeric data. You
query using the following syntax: 'select * from t where indexed_column = 5'.
Note that the number five in the query is a literal number five (not a character
string literal). The index on indexed_column is not used. This is because the
above query is the same as 'select * from t where to_number(indexed_column) = 5'
? you have implicitly applied a function to the column and as case 3 noted, this
will nullify the use of the index. This is very easy to see with a small
example:

ops$tkyte@ORA8I.WORLD> create table t ( x char(1) primary key );
Table created.

ops$tkyte@ORA8I.WORLD> insert into t values ( '5' );
1 row created.

ops$tkyte@ORA8I.WORLD> set autotrace on explain

ops$tkyte@ORA8I.WORLD> select * from t where x = 5;

X
-
5

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

ops$tkyte@ORA8I.WORLD> select * from t where x = '5';

X
-
5

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   INDEX (UNIQUE SCAN) OF 'SYS_C0038216' (UNIQUE)

You should always avoid implicit conversions in all cases anyway. Always compare
apples to apples and oranges to oranges. Another case where this comes up
frequently is with dates. You try to query:

-- find all records for today
select * from t where trunc(date_col) = trunc(sysdate);

You discover that the index on DATE_COL will not be used. You can either index
the TRUNC of DATE_COL or perhaps more easily query using BETWEEN.  The following
demonstrates the use of BETWEEN on a date.  Once you realize that
trunc(date_col) = trunc(sysdate) is the same as ?date_col between sysdate and
sysdate PLUS one day MINUS one second?, doing the between is straightforward:

select *
  from t
  where date_col between trunc(sysdate) and trunc(sysdate)+1/(1*24*60*60)

(Note: the expression 1/(1*24*60*60) is the way to subtract 1 second from a DATE
value.  Subtracting 1 would take away one day, 1/24 ? one hour, 1/(24*60) one
minute).

This moves all of the functions to the right hand side of the equation, allowing
us to use the index on DATE_COL (and has the same exact effect as 'where
trunc(date_col) = trunc(sysdate)'). You should always remove the functions from
database columns when they are in the predicate if possible. Not only will it
allow for more indexes to be considered for use, it will reduce the amount of
processing the database needs to do. In the above case, when we used:
 between trunc(sysdate) and trunc(sydate)+1/(1*24*60*60)
the values are computed once for the query  and then an index could be used to
find just the qualifying values. When we used trunc(date_col) = trunc(sysdate),
the trunc(date_col) had to be evaluated once PER ROW for every row in the entire
table (no indexes).
</quote>
Review & Followup

      Rating: 5
Perfect
      Answer  April 19, 2002
Reviewer:  ashok
       from USA

      Rating:
      3
  April 19, 2002
Reviewer:
      A reader
A query runs for 50minutes with the following as part of the WHERE clause:

CALL_NO='500'

and the same query runs for <10secs with

CALL_NO=500

The datatype of CALL_NO is varchar2(3). And there are three indexes on the
table, with two of them having CALL_NO as the leading column. And the execution
plan shows the following portion:
case 1->
INDEX (RANGE SCAN) OF 'TOTAPAY_CD' (NON-UNI
          QUE) (Cost=3 Card=4 Bytes=128)

case 2 ->
INDEX (FAST FULL SCAN) OF 'TOTAPAY_CD' (NON
          -UNIQUE) (Cost=23 Card=1 Bytes=32)

why this difference?

          Followup:

The reason (and this is SHOCKING for many people)

indexes do not mean "you will get your data really fast"

Sometimes, they mean quite the opposite!!!!

As shown above, call_no = 500 is the same as "to_number(call_no) = 500" which
means -- no index on call_no will be used in a range scan.  Instead, we used
more efficient means to get the data.

      Rating: 3
why mesurement
      does not support your comment  April 20, 2002

Reviewer:  Tanel Hiir  from Estonia
Tom,

regarding your comment:
<quote>
Well, ok three comments -- primary key IMPLIES not null.  That not null
constraint is just adding extra CPU overhead to every insert.  We'll do that
check twice now.
</quote>

I did mesure and found that NULL column uses less CPU but is *slower* and uses
*more* latches...

Is there something wrong with my mesurements?

NOT NULL time:995
NULL time:1058

recursive cpu usage
NOT NULL: 650
NULL: 641

------------------------------------------
SQL> create table run_stats ( runid varchar2(15), name varchar2(80), value int
);

Table created.

SQL>
SQL> create or replace view stats as
  2    select 'STAT...' || a.name name, b.value
  3    from v$statname a, v$mystat b
  4    where a.statistic# = b.statistic#
  5    union all
  6    select 'LATCH.' || name,  gets
  7    from v$latch;

View created.

SQL>
SQL> create table TAB_NOT_NULL
  2  ( ID NUMBER(10,0) NOT NULL
  3  , constraint TAB_NOT_NULL_PK primary key (ID)
  4  );

Table created.

SQL>
SQL> create table TAB_NULL
  2  ( ID NUMBER(10,0) NULL
  3  , constraint TAB_NULL_PK primary key (ID)
  4  );

Table created.

SQL>
SQL> set serveroutput ON
SQL>
SQL> declare
  2    iStart1 NUMBER;
  3    iEnd1 NUMBER;
  4    iStart2 NUMBER;
  5    iEnd2 NUMBER;
  6  begin
  7    -- insert into NOT NULL column...
  8    insert into run_stats select 'before', stats.* from stats;
  9    iStart1 := dbms_utility.GET_TIME;
 10    insert into TAB_NOT_NULL (select OBJECT_ID from ALL_OBJECTS);
 11    iEnd1 := dbms_utility.GET_TIME;
 12    -- insert into NULL column...
 13    insert into run_stats select 'after 1', stats.* from stats;
 14    iStart2 := dbms_utility.GET_TIME;
 15    insert into TAB_NULL (select OBJECT_ID from ALL_OBJECTS);
 16    iEnd2 := dbms_utility.GET_TIME;
 17    insert into run_stats select 'after 2', stats.* from stats;
 18    --
 19    dbms_output.put_line( 'NOT NULL time:' || to_char(iEnd1-iStart1) );
 20    dbms_output.put_line( 'NULL time:' || to_char(iEnd2-iStart2) );
 21  end;
 22  /
NOT NULL time:995
NULL time:1058

PL/SQL procedure successfully completed.

SQL>
SQL> select substr(a.name,1,35) as "Stat name"
  2       , b.value-a.value as "NOT NULL case"
  3       , c.value-b.value as "NULL case"
  4       , ((c.value-b.value)-(b.value-a.value)) as "Difference"
  5  from run_stats a, run_stats b, run_stats c
  6  where a.name = b.name
  7    and b.name = c.name
  8    and a.runid = 'before'
  9    and b.runid = 'after 1'
 10    and c.runid = 'after 2'
 11    and (c.value-a.value) > 0
 12    and (c.value-b.value) <> (b.value-a.value)
 13  order by abs( (c.value-b.value)-(b.value-a.value))
 14  /

Stat name                           NOT NULL case  NULL case Difference
----------------------------------- ------------- ---------- ----------
LATCH.active checkpoint queue latch            75         74         -1
LATCH.job_queue_processes parameter             0          1          1
LATCH.loader state object freelist             10         11          1
LATCH.ncodef allocation latch                   0          1          1
STAT...calls to kcmgas                         76         75         -1
STAT...redo log space requests                  5          6          1
LATCH.session switching                         0          1          1
LATCH.transaction branch allocation             0          1          1
STAT...calls to get snapshot scn: k           171        173          2
STAT...commit cleanouts successfull           204        202         -2
STAT...redo buffer allocation retri           109        111          2
STAT...parse time elapsed                       4          2         -2
STAT...commit cleanout failures: bu             0          2          2
STAT...execute count                           20         23          3
STAT...opened cursors cumulative               20         23          3
STAT...session cursor cache hits               13         16          3
STAT...table fetch by rowid                 56993      56996          3
STAT...parse count (total)                     20         23          3
STAT...parse time cpu                           5          2         -3
STAT...enqueue requests                       138        142          4
STAT...enqueue releases                       136        141          5

Stat name                           NOT NULL case  NULL case Difference
----------------------------------- ------------- ---------- ----------
STAT...messages sent                          119        125          6
STAT...no work - consistent read ge         42440      42446          6
STAT...redo entries                         18125      18132          7
LATCH.undo global data                        504        512          8
STAT...db block changes                     36120      36128          8
STAT...buffer is not pinned count           60536      60545          9
STAT...recursive cpu usage                    650        641         -9
LATCH.library cache                           590        600         10
LATCH.enqueue hash chains                     317        328         11
STAT...consistent gets                     207777     207789         12
LATCH.Token Manager                           348        363         15
STAT...write clones created in fore            78         93         15
LATCH.checkpoint queue latch                 1862       1878         16
LATCH.messages                                955        973         18
LATCH.shared pool                             283        260        -23
LATCH.redo allocation                       18537      18561         24
STAT...recursive calls                        161        185         24
STAT...db block gets                        35661      35689         28
LATCH.enqueues                                444        474         30
LATCH.row cache objects                     49146      49182         36
STAT...session logical reads               243438     243478         40

Stat name                           NOT NULL case  NULL case Difference
----------------------------------- ------------- ---------- ----------
STAT...redo log space wait time               233        291         58
LATCH.cache buffers lru chain                 903       1061        158
STAT...free buffer requested                  201        376        175
LATCH.cache buffers chains                 472061     472345        284
STAT...redo size                          5350220    5357292       7072

47 rows selected.

SQL>

          Followup:

I think you introduced an un-necessary "side effect" -- potential waits on the
all_objects query (or you might have had some wait on log file syncs or
something for the second one and not the first).

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t1 ( id number(10,0), constraint
t1_pk primary key(id) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t2 ( id number(10,0) check(id is
not null), constraint t2_pk primary key(id) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t1 values (0);

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t2 values (0);

1 row created.

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table run_stats ( runid varchar2(15),
name varchar2(80), value int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace view stats
  2  as select 'STAT...' || a.name name, b.value
  3        from v$statname a, v$mystat b
  4       where a.statistic# = b.statistic#
  5      union all
  6      select 'LATCH.' || name,  gets
  7        from v$latch;

View created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_start number;
  3  begin
  4      insert into run_stats select 'before', stats.* from stats;
  5
  6      l_start := dbms_utility.get_time;
  7      for i in 1 .. 10000
  8      loop
  9          insert into t1 values(i);
 10      end loop;
 11      dbms_output.put_line( 'create table t1 ( id number(10,0), constraint
t1_pk primary key(id) );' );
 12      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 13
 14
 15      insert into run_stats select 'after 1', stats.* from stats;
 16      l_start := dbms_utility.get_time;
 17      for i in 1 .. 10000
 18      loop
 19          insert into t2 values(i);
 20      end loop;
 21      dbms_output.put_line(
 22      'create table t2 ( id number(10,0) check(id is not null), constraint
t2_pk primary key(id) );' );
 23      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 24
 25      insert into run_stats select 'after 2', stats.* from stats;
 26  end;
 27  /
create table t1 ( id number(10,0), constraint t1_pk primary key(id) );
793 hsecs
create table t2 ( id number(10,0) check(id is not null), constraint t2_pk
primary key(id) );
834 hsecs

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value-a.value run1,
c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

NAME                                 RUN1       RUN2       DIFF
------------------------------ ---------- ---------- ----------
....
LATCH.session queue latch             180        623        443
LATCH.direct msg latch                180        631        451
LATCH.ksfv messages                   240        693        453
LATCH.cache buffers lru chain         257        854        597
LATCH.checkpoint queue latch          267        934        667
LATCH.cache buffers chains         131993     132664        671
STAT...session pga memory           33044      59584      26540
STAT...session pga memory max       85120       7508     -77612

53 rows selected.

And the latching differences can be very important as I've said many a time...
Latches = Locks, locks = serialization device.  Serialized = less scalable....

      Rating: 3
Semantic Error
        April 22, 2002
Reviewer:  Kurt  from
      Vienna, Austria
between trunc(sysdate)and trunc(sysdate)+1/(1*24*60*60)
means only then first second of sysdate,
should be
between trunc(sysdate)and trunc(sysdate+1)-1/(1*24*60*60)

          Followup:

correct -- thanks -- lost the +1 on the copy.

      Rating: 5
Why this is
      happening?  April 22, 2002
Reviewer:  Ashok
       from USA
I followed your advice by avoding the implicit conversion between number to
varchar.

here is interseting thing i came across...

i am using user defined function the select ..total no of seconds to retrieve
the 6512 rows is bout 31 sec.But if i remove the  user defined function from my
select it takes 41 secons why?It's suupose to take less <= 31 sec
(User Defined Function:check_user_IN_DEAL)
Oracle:8.1.7 Rule Based)

If necessary i can produce the statistics.

Here is the query:

select sysdate from dual;

 SELECT
         "DEAL"."NAME",
         "DEAL"."DEAL_ID",
         "DEAL_SOURCE"."DEAL_SIZE_AMT",
         "DEAL_SOURCE"."CMB_DEAL",
         "TR_DEAL_SOURCE"."READY_FOR_GSF_IND" ,
    "DEAL"."INFO_ONLY",
    check_user_IN_DEAL( "TR_DEAL_SOURCE"."DEAL_ID", 'ADMIN',
"TR_DEAL_SOURCE"."READY_FOR_GSF_IND",SEQID),
          1 "Rank",
          clnt_id,
          '                        '  ,
         IN_MARKET_FROM_DT
    FROM
            sf_deal_link_xref,
         "TR_DEAL_SOURCE",
           sf_clnts  ,
         "DEAL_SOURCE" ,
            "DEAL",
           GBIS.CURRENCY CURNCY,
         tr_issclnt_header

   WHERE ( "DEAL_SOURCE"."DEAL_ID" = "DEAL"."DEAL_ID" ) and
         ( "DEAL_SOURCE"."DEAL_ID" = "TR_DEAL_SOURCE"."DEAL_ID" ) and
         ( "DEAL_SOURCE"."DATA_SOURCE_ID" = "TR_DEAL_SOURCE"."DATA_SOURCE_ID" )
and
         ( "DEAL_SOURCE"."DATA_SOURCE_ID" = 13 )  and
         CURNCY.ID (+) = DEAL_SOURCE.DEAL_CURRENCY_ID and
           tr_comps_id = deal.deal_id  and
         tr_issclnt_header.client_id(+) = sf_deal_link_xref.clnt_id  and
            sf_deal_link_xref.clnt_id = sf_clnts.id
              and tr_comps_id is not null

union

SELECT '                      ' "NAME",
         '0',
          0,
         '',
         '',
            '',
            '',
            0 "Rank",
         clnt_id ,
         mktg_legal_name,
         SYSDATE

    FROM sf_deal_link_xref,
         sf_clnts ,
           tr_issclnt_header,
         tr_deal_source

   WHERE
           tr_comps_id = tr_deal_source.deal_id  and
          sf_deal_link_xref.clnt_id = sf_clnts.id and
         tr_issclnt_header.client_id(+) = sf_deal_link_xref.clnt_id
  and tr_comps_id is not null;

select sysdate from dual;

TIA
Ashok

          Followup:

You should consider UNION ALL instead of UNION when possible.

Yes, one would need to see a TKPROF report of both queries, their plans, and
timings with timed statistics enabled to make sense of anything.

      Rating: 5
It's very
      interesting.  April 23, 2002
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating: 5
Here is the
      statistics   April 23, 2002
Reviewer:  Ashok
       from USA
With Function in Select:
Statistics
----------------------------------------------------------
          9  recursive calls
         10  db block gets
     125820  consistent gets
         84  physical reads
          0  redo size
     736495  bytes sent via SQL*Net to client
      31225  bytes received via SQL*Net from client
        444  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
       6612  rows processed

Explain Plan:
------------
2.1 SORT(UNIQUE)
3.1 UNION-ALL
4.1 NESTED LOOPS
5.1 NESTED LOOPS(OUTER)
6.1 NESTED LOOPS
7.1 NESTED LOOPS
8.1 NESTED LOOPS(OUTER)
9.1 NESTED LOOPS
10.1 TABLE ACCESS(FULL) - DEAL
10.2 TABLE ACCESS(BY INDEX ROWID) - DEAL_SOURCE
11.1 INDEX(UNIQUE SCAN) - SYS_C00180065(UNIQUE)
9.2 TABLE ACCESS(BY INDEX ROWID) - CURRENCY
10.1 INDEX(UNIQUE SCAN) - CURRENCY_PK(UNIQUE)
8.2 TABLE ACCESS(BY INDEX ROWID) - TR_DEAL_SOURCE
9.1 INDEX(UNIQUE SCAN) - XAK_TR_DEAL_SOURCE(UNIQUE)
7.2 TABLE ACCESS(BY INDEX ROWID) - SF_DEAL_LINK_XREF
8.1 INDEX(RANGE SCAN) - SF_DEAL_LINK_XREF_TR(NON-UNIQUE)
6.2 INDEX(UNIQUE SCAN) - CLIENT_INDEX(UNIQUE)
5.2 TABLE ACCESS(BY INDEX ROWID) - SF_CLNTS
6.1 INDEX(UNIQUE SCAN) - SF_CLNTS_PK(UNIQUE)
4.2 NESTED LOOPS
5.1 NESTED LOOPS(OUTER)
6.1 NESTED LOOPS
7.1 TABLE ACCESS(FULL) - TR_DEAL_SOURCE
7.2 TABLE ACCESS(BY INDEX ROWID) - SF_DEAL_LINK_XREF
8.1 INDEX(RANGE SCAN) - SF_DEAL_LINK_XREF_TR(NON-UNIQUE)
6.2 INDEX(UNIQUE SCAN) - CLIENT_INDEX(UNIQUE)
5.2 TABLE ACCESS(BY INDEX ROWID) - SF_CLNTS
6.1 INDEX(UNIQUE SCAN) - SF_CLNTS_PK(UNIQUE)

Removed Function from Select:
Statistics
----------------------------------------------------------
          9  recursive calls
         10  db block gets
     125820  consistent gets
         79  physical reads
          0  redo size
     702209  bytes sent via SQL*Net to client
      31103  bytes received via SQL*Net from client
        444  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
       6612  rows processed
Explain Plan:
------------
2.1 SORT(UNIQUE)
3.1 UNION-ALL
4.1 NESTED LOOPS
5.1 NESTED LOOPS(OUTER)
6.1 NESTED LOOPS
7.1 NESTED LOOPS
8.1 NESTED LOOPS(OUTER)
9.1 NESTED LOOPS
10.1 TABLE ACCESS(FULL) - DEAL
10.2 TABLE ACCESS(BY INDEX ROWID) - DEAL_SOURCE
11.1 INDEX(UNIQUE SCAN) - SYS_C00180065(UNIQUE)
9.2 TABLE ACCESS(BY INDEX ROWID) - CURRENCY
10.1 INDEX(UNIQUE SCAN) - CURRENCY_PK(UNIQUE)
8.2 TABLE ACCESS(BY INDEX ROWID) - TR_DEAL_SOURCE
9.1 INDEX(UNIQUE SCAN) - XAK_TR_DEAL_SOURCE(UNIQUE)
7.2 TABLE ACCESS(BY INDEX ROWID) - SF_DEAL_LINK_XREF
8.1 INDEX(RANGE SCAN) - SF_DEAL_LINK_XREF_TR(NON-UNIQUE)
6.2 INDEX(UNIQUE SCAN) - CLIENT_INDEX(UNIQUE)
5.2 TABLE ACCESS(BY INDEX ROWID) - SF_CLNTS
6.1 INDEX(UNIQUE SCAN) - SF_CLNTS_PK(UNIQUE)
4.2 NESTED LOOPS
5.1 NESTED LOOPS(OUTER)
6.1 NESTED LOOPS
7.1 TABLE ACCESS(FULL) - TR_DEAL_SOURCE
7.2 TABLE ACCESS(BY INDEX ROWID) - SF_DEAL_LINK_XREF
8.1 INDEX(RANGE SCAN) - SF_DEAL_LINK_XREF_TR(NON-UNIQUE)
6.2 INDEX(UNIQUE SCAN) - CLIENT_INDEX(UNIQUE)
5.2 TABLE ACCESS(BY INDEX ROWID) - SF_CLNTS
6.1 INDEX(UNIQUE SCAN) - SF_CLNTS_PK(UNIQUE)

          Followup:

Keep running the queries -- both of them.  Average the times, you'll find them
to be almost the same or the query with the extra code taking longer.

The plans are identical.  The blocks processed as well.  You must be running on
a non-single user system (not a controlled environment) and your runtimes vary
from execution to execution.

Use a tool like TKPROF (as I said:

Yes, one would need to see a TKPROF report of both queries, their plans, and
timings with timed statistics enabled to make sense of anything.

)  in order to diagnose this.  Timing by the wallclock is useless in a
mutli-user environment.  You need to see CPU time, Elapsed time.  TKPROF +
TIMED_STATISTICS is the only thing that is useful here.

      Rating: 5
TKPROF
        April 26, 2002
Reviewer:  ashok  from
      USA
Here is the TKPROF

With Function on the select
call     count       cpu    elapsed       disk     query
------- ------  -------- ---------- ---------- ----------
Parse        1      0.02       0.49          0          0
Execute      2      0.00       0.02          0          0
Fetch        443    2.74       9.97          82      125927
------- ------  -------- ---------- ---------- ----------Total       446
0.00       0.00          0       125927

With Function on the select

call     count       cpu    elapsed       disk     query
------- ------  -------- ---------- ---------- ----------
Parse        1      0.00       0.01          0          0
Execute      2      0.00       0.00          0          0
Fetch        443    2.56       9.60          79      125927
------- ------  -------- ---------- ---------- ----------total       446
0.00       0.00          0       125927

Something i have to tune the sql?

TIA

          Followup:

huh?

both of these are "with function on select"

I have no idea if you "need to tune the sql" or what.  The above shows the two
queries are *basically the same*.  if in fact the above even represents two
different queries.

You should leave a TAD more detail in there -- like the query , the plans, etc.

      Rating: 5
wrong index
      used when we quote the literal  June 11, 2003

Reviewer:  A reader
Hi

We have a very strange problem in Oracle 7.3.4

desc tgcprt10.MSEFA_ELEM_FASE

 Name                       Null?   Type
 -------------------------  ------- -------------
 MSPED_ID_PEDIDO                 NOT NULL VARCHAR2(6)
 MSFAS_ID_FASE                   NOT NULL VARCHAR2(6)
 MSFAM_ID_FAMILIA                NOT NULL VARCHAR2(2)
 MSCOM_ID_COMPONENTE             NOT NULL VARCHAR2(2)
 MSEFA_SEC_COMPONENTE            NOT NULL VARCHAR2(15)
 MSPAR_ID_PARAMETRO              NOT NULL VARCHAR2(6)
 MSEFA_SEC_PARAMETRO             NOT NULL NUMBER
 MSVAL_ID_VALOR                  NOT NULL VARCHAR2(10)
 MSSER_ID_SERVICIO                        NUMBER
 MSEFA_INS_VALOR_GENERICO                 VARCHAR2(2000)
 MSMOV_ID_MOVIMIENTO                      VARCHAR2(1)
 MSPIN_ID_PIN_CLIENTE                     VARCHAR2(12)
 MSEFA_USUARIO_ALTA              NOT NULL VARCHAR2(8)
 MSEFA_ID_USUARIO_MODIF                   VARCHAR2(8)
 MSEFA_TS_TIMESTAMP              NOT NULL DATE
 MSDOM_ID_DOM_INSTAL                      NUMBER

  1  SELECT MSMOV_ID_MOVIMIENTO
  2   FROM tgcprt10.MSEFA_ELEM_FASE
  3  WHERE MSPED_ID_PEDIDO = 'CDR689'
  4    AND MSFAS_ID_FASE = '001'
  5    AND MSFAM_ID_FAMILIA = 'LI'
  6    AND MSCOM_ID_COMPONENTE = 'FR'
  7*   AND MSEFA_SEC_COMPONENTE = '900000008857229'

88 rows

Execution plan
----------------------------------------------------------
          0  SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=1 Bytes=27)
          1                  0  TABLE ACCESS (BY ROWID) OF 'MSEFA_ELEM_FASE'
(Cost=5 Card=1 Bytes=27)
          2                  1    INDEX (RANGE SCAN) OF 'MSEFA_ELEM_FASE_FK1'
(NON-UNIQUE)

 1  SELECT MSMOV_ID_MOVIMIENTO
  2   FROM tgcprt10.MSEFA_ELEM_FASE
  3  WHERE MSPED_ID_PEDIDO = 'CDR689'
  4    AND MSFAS_ID_FASE = '001'
  5    AND MSFAM_ID_FAMILIA = 'LI'
  6    AND MSCOM_ID_COMPONENTE = 'FR'
  7*   AND MSEFA_SEC_COMPONENTE = 900000008857229

88 rows

Execution plan
----------------------------------------------------------
          0  SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=1 Bytes=27)
          1                  0  TABLE ACCESS (BY ROWID) OF 'MSEFA_ELEM_FASE'
(Cost=5 Card=1 Bytes=27)
          2                  1    INDEX (RANGE SCAN) OF 'PK_MSEFA_ELEM_FASE'
(UNIQUE)

The first query which uses FK1 is so slow!!!! And it should be the proper query
because the MSEFA_SEC_COMPONENTE is varchar2 and we should quote it but by
quoting it optimizer uses wrong index....

INDEX_NAME                     COLUMN_NAME                    COLUMN_POSITION
------------------------------ ------------------------------ ---------------
PK_MSEFA_ELEM_FASE             MSPED_ID_PEDIDO                              1
PK_MSEFA_ELEM_FASE             MSFAS_ID_FASE                                2
PK_MSEFA_ELEM_FASE             MSFAM_ID_FAMILIA                             3
PK_MSEFA_ELEM_FASE             MSCOM_ID_COMPONENTE                          4
PK_MSEFA_ELEM_FASE             MSEFA_SEC_COMPONENTE                         5
PK_MSEFA_ELEM_FASE             MSPAR_ID_PARAMETRO                           6
PK_MSEFA_ELEM_FASE             MSEFA_SEC_PARAMETRO                          7
MSEFA_ELEM_FASE_FK1            MSFAM_ID_FAMILIA                             1
MSEFA_ELEM_FASE_FK1            MSCOM_ID_COMPONENTE                          2
MSEFA_ELEM_FASE_FK1            MSEFA_SEC_COMPONENTE                         3
MSEFA_ELEM_FASE_FK1            MSPAR_ID_PARAMETRO                           4
MSEFA_ELEM_FASE_FK1            MSEFA_SEC_PARAMETRO                          5

how so?

          Followup:

sorry, 734 is sooo old and the optimizer is sooo much better these days.

but basically -- it is saying the cost of either index is equal -- both are
"zero".  it is hitting a tie and if I recall in a tie, it sorts them
alphabetically.  as corny as it sounds, you might recreate that MSEFA...
constraint using a character that starts after P.

      Rating: 5
it was the
      order  June 12, 2003
Reviewer:
      A reader
Hi

It was the index name which was causing that!

However I have a further query, the query I pasted returns 88 rows which takes
like 1 minute. If I use a different predicate (same conditions but different
values) to return 2 rows only (must less than 88) it takes 2 hours! Same
execution plan (using the FK1 index) how´s that...? Puzzled because samething
happens in development database and production

Cheers

          Followup:

tkprof it.

      Rating: 5
just
      wondering  June 13, 2003
Reviewer:
      A reader
I wonder how a simple ' ' changes the optimizer to choose different indexes? Is
that a bug? The query without ' ' I guess Oracle does something like

column_a = to_char('VALUE_A')
what affected the optimizer?

The proper should be
column_a = 'VALUE_A'

column_a = to_char('VALUE_A') and column_a = 'VALUE_A' to me are the same....

          Followup:

Not when column_a is not a string.

cardinal rule of programming 101:

compare strings to strings.
dates to dates
numbers to numbers

<datatype> to <datatype>

if not -- guess what -- internally, deep down somewhere -- the software must
implicitly convert one or the other value into the SAME TYPE as the other one.
That is called an implicit conversion (the cause of billions of bugs a day).
That implicit conversion is (has to be, since it is) a function.  The use of a
function on an indexed column -- obviates the use of that index for range
scanning purposes.  You indexed "x", you did not index "f(x)", hence the index
is not useful.

June
      13, 2003 Priscillia -- Thanks for the
      question regarding "How to compare two tables of data????", version oracle
      8i

      You Asked
Hi Tom,

I have two tables of values(dept1 and dept2). How do I compare
all the data in these two tables??? It will return true if both
tables contain the same data & false for otherwise...
Another thing is that I do not know how to use CREATE OPERATOR
using Oracle 8i.. Can U explain more and give me specific
examples????

Thks.

Best Rgds,
Priscillia

      and we said...
select count(*)
  from
(
( select * from dept1
  minus
  select * from dept2 )
union all
( select * from dept2
  minus
  select * from dept1 )
)

will tell you how many rows are in dept1 not in dept2 + in dept2
not in dept1

as for the operators, unless you are going to build your own
datatype, they are not very relevant to you.

see

http://technet.oracle.com/doc/oracle8i_816/appdev.816/a76937/toc.htm
and specifically:

http://technet.oracle.com/doc/oracle8i_816/appdev.816/a76937/dci07id3.htm#620608
...

Review &
      Followup

      Rating: 5
dimantion
      update  October 18, 2002
Reviewer:
      A reader
Hi tom,

   I have a user table in NY. and another table user_d in london.

   the table structure of both the tables is different but
some fileds are
common ( userid,user_name,user_country,city..)

  now I db in london is in sql server 6.5 and db in NY is ora 8i.

 I have to append the rows from london to NY every night.
and also update any changes made in those rows that are changed (eg some user
changed his address )

 what is the best way.

  expected rows inthe dbny  is 4.2 mil
  expected rows in    dblondon is 4.8 mil

  expected new rows every day  10k format flat file

can you please suggest me the best way ?

 shuld I truncate the table in NY every nigth and reload ?
  but it has lots of references !!

  of check every night ? 4.1 mil rows ?

  any other best solution ?

TIA

          Followup:

whatever the heck "dimantion" means....

are you getting a flat file with updates -- rows to insert and rows to update?

      Rating: 5
sorry for that
      "dimention"  October 18, 2002
Reviewer:
      A reader
I get only one flat file with all data with their table structure and I load it
in temp table

TIA

          Followup:

is that what is in

  expected new rows every day  10k format flat file

so that is just CHANGED ROWS or WHAT?  please be more clear -- pretend I don't
know anything about your environment (hey -- I don't)

      Rating: 5
flat
      file  October 18, 2002
Reviewer:
      A reader
I get the file that conatains all the the data since
 begining. ( all records every time)

 all records = old not changed + old changed + new

          Followup:

how could that be 10k in size?

      Rating: 5
flat
      file  October 18, 2002
Reviewer:
      A reader
the only new rows are 10K

so all to gether all previous reocrds + 10K new records

          Followup:

Well, if you get a file with ALL of the records and no way to distiguish between
"new", "modified" and "unchanged" my suggestion would be:

o goto the sqlserver guys
o tell them "hey, give me the ADDS and MODS and nothing else"

otherwise, your processing is going to be heinously bad.  That is my suggestion.
 Get the CHANGES, not the entire database.  they should be able to do that
pretty easily (even I could tell them how to do that in sqlserver ;)

      Rating: 5
changed
      records  October 18, 2002
Reviewer:
      A reader

 If there is no timestamp or flag that indicates the modification or change, how
would one identify the changed or modified record ?

TIA

          Followup:

one would add such a detail in order to make ones processing somewhat efficient.

      Rating: 5
what do you say
      ?  October 18, 2002
Reviewer:  A reader

belive me or not...

  When we told them to give the modified and new record data only our london
group said ok but they can not 100%
garuntee that they are not " missing some data " or there is no " repeated data
"

  now if I take that data and put it in the db it will be my responsibility
because they already told me they will not 100% garuntee

  that's why I asked for all data. now there is no timestamp or flag that will
indicate that this is modified record

 please advice...

          Followup:

they need to change their process, or your choice is:

o dump and load every night.

and thats about all she wrote.

      Rating:
      3
  October 18, 2002
Reviewer:  Dave
       from Ohio
If you are stuck with getting the whole data set, and want to avoid statements
that look for differences in every one of multiple columns for a few million
rows, you might consider a different slant.

You can potentially speed up the identification of changed data by adding a new
column to your target table containing a hash value generated by applying the
DBMS_UTILITY.GET_HASH_VALUE() function to a concatanation of your columns
(populating and updating this value with a trigger I guess). Create an index on
your PK (or username, whatever you will use to do the comparison with the new
data) + this column. As you load the source data, calculate the hash value for
the potentially-new records. Then use a MERGE statement to do update when the
hash value is different, or insert where the record does not exist.

Does that make sense? It's a trade-off between CPU and I/O I suppose.

          Followup:

since you would have to load everything to do that -- sounds like:

o create a single partition table
o load the data into this
o when you get a new file, load it into a stage table, index it, etc
o alter table exchange partition <<<=== swap the newly loaded table with the
existing partition

minimum downtime (just the exchange)....

      Rating: 5
not
      possible  October 24, 2002
Reviewer:
      A reader
Hi tom,

  I can do swap but,

1.) The data is extracted and transformed in to other tables too. and there are
audit tails so still I have to update them.

2.) I am thinking to

   -> create new_data talble
   -> add one more column ( isused varchar2(1))
3.) when ever a row from that table is extracted,
     I chage the flag isused = 't'
4.) next day
    4.1--> append the new data in the new_data table
    4.2--> compare all rows withthe previous days data
           if the the row matches with the previous days row
            ( all fields except load_id)
            then
           change that row's isused column to 'T'
5.) after that process all rows with isused = 'f' or null ?

do u think it is ok ?

 but I don't know how to do setp 4.2 can you plase advise
how to compare like that ?

TIA

          Followup:

sorry -- sounds like a new question and insufficient data to answer anything.  I
don't know what you mean by "extracted" and how extracting would "update".  A
row by row audit trail is going to be the slow point here so you might as well
just brute force it procedurally anyway if you must do that.

Sounds like you need to sit down and come up with your procedural design.

      Rating: 4
Table data
      difference and make the statements like insert , update and
      delete  October 24, 2002
Reviewer:  kumar
       from san Diego
Tom,
This question is slightly differs from the above discussion however the reason I
am putting on the same thread is to find out is there any ready made script
available in the scripts bank?  If I have two identical tables from two
different databases and not much of data in those tables at the most maximum
15000 rows. I want to generate the DML statements like delete, update, insert
after comparing table1 with table2. Just let me know if any ready made procedure
such exist in these script bank. I am in the need of make one however if any
exist I can tweek that one easily. All my data comparision goes through Primary
key.

Thanks

          Followup:

bank is empty on that one. sorry.

      Rating: 5
"How to compare
      two tables of data????", version oracle 8i  April 03, 2003

Reviewer:  Karl Bergerson  from Bellevue, WA USA

Simply excellent as usual!

      Rating: 5
Which is the
      better execution plan  June 12, 2003
Reviewer:
      Angus McTavish aged 75  from Cumbria, England
Going back to your original answer, I'd like to pose this example and ask which
you think is the better method.

Two tables, same structure, 60000 rows, primary key on ID, analyzed. First
example is consistently slower by a long way despite the lower cost and much
fewer gets. It is doing a lot of recursive sql (why?) and more sorts however.
I've run both examples numerous times and the results are pretty consistent.

I have to do a lot of this kind of thing, comparing data from two tables, so I'd
appreciate your thoughts.

SQL> set timing on
SQL> set autot traceonly
SQL> select id,'In table 1, not in table 2'
  2  from accounts
  3  minus
  4  select id,'In table 1, not in table 2'
  5  from accounts2
  6  union
  7  select id,'In table 2 not in table 1'
  8  from accounts2
  9   minus
 10  select id,'In table 2 not in table 1'
 11  from accounts
 12  /

49 rows selected.

Elapsed: 00:00:17.00

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1208 Card=253738 Byt
   1    0   MINUS
   2    1     SORT (UNIQUE) (Cost=1208 Card=253738 Bytes=1014952)
   3    2       UNION-ALL
   4    3         MINUS
   5    4           SORT (UNIQUE) (Cost=302 Card=63450 Bytes=253800)
   6    5             INDEX (FAST FULL SCAN) OF 'PK_AC1' (UNIQUE) (Cos
   7    4           SORT (UNIQUE) (Cost=302 Card=63419 Bytes=253676)
   8    7             INDEX (FAST FULL SCAN) OF 'PK_AC2' (UNIQUE) (Cos
   9    3         INDEX (FAST FULL SCAN) OF 'PK_AC2' (UNIQUE) (Cost=9
  10    1     SORT (UNIQUE) (Cost=302 Card=63450 Bytes=253800)
  11   10       INDEX (FAST FULL SCAN) OF 'PK_AC1' (UNIQUE) (Cost=9 Ca

Statistics
----------------------------------------------------------
         32  recursive calls
         25  db block gets
        544  consistent gets
       1248  physical reads
          0  redo size
       1460  bytes sent via SQL*Net to client
        451  bytes received via SQL*Net from client
          6  SQL*Net roundtrips to/from client
          0  sorts (memory)
          4  sorts (disk)
         49  rows processed

SQL> select id,'In table 1, not in table 2'
  2  from accounts
  3  where id not in (select id from accounts2)
  4  union
  5   select id,'In table 2 not in table 1'
  6  from accounts2
  7  where id not in (select id from accounts)
  8  /

49 rows selected.

Elapsed: 00:00:09.05

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=32 Card=6344 Bytes=2
   1    0   SORT (UNIQUE) (Cost=32 Card=6344 Bytes=25376)
   2    1     UNION-ALL
   3    2       FILTER
   4    3         INDEX (FAST FULL SCAN) OF 'PK_AC1' (UNIQUE) (Cost=9
   5    3         INDEX (UNIQUE SCAN) OF 'PK_AC2' (UNIQUE) (Cost=1 Car
   6    2       FILTER
   7    6         INDEX (FAST FULL SCAN) OF 'PK_AC2' (UNIQUE) (Cost=9
   8    6         INDEX (UNIQUE SCAN) OF 'PK_AC1' (UNIQUE) (Cost=1 Car

Statistics
----------------------------------------------------------
          0  recursive calls
          8  db block gets
     253856  consistent gets
          0  physical reads
          0  redo size
       1460  bytes sent via SQL*Net to client
        418  bytes received via SQL*Net from client
          6  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         49  rows processed

          Followup:

Looks like a poorly sized SORT_AREA_SIZE.  In 9iR2 using workarea_size_policy =
auto and a pga_aggregate target set reasonably, the first plan always bests the
second.

You see -- in your output, lots of physical IO in the first one, the recursive
sql is due to the SORTS TO DISK.  Consider:

ops$tkyte@ORA920> create table t1 as select * from all_objects where owner <>
'AQ';
Table created.

ops$tkyte@ORA920> update t1 set object_id = -object_id;
30228 rows updated.

ops$tkyte@ORA920> insert into t1 select * from all_objects where owner <> 'AQ';
30228 rows created.

ops$tkyte@ORA920> alter table t1 add constraint t1_pk primary key(object_id);
Table altered.

ops$tkyte@ORA920> analyze table t1 compute statistics
  2  for table for all indexes for all indexed columns;
Table analyzed.

ops$tkyte@ORA920> create table t2 as select * from all_objects where owner <>
'HR';
Table created.

ops$tkyte@ORA920> update t2 set object_id = -object_id;
30219 rows updated.

ops$tkyte@ORA920> insert into t2 select * from all_objects where owner <> 'HR';
30219 rows created.

ops$tkyte@ORA920> alter table t2 add constraint t2_pk primary key(object_id);
Table altered.

ops$tkyte@ORA920> analyze table t2 compute statistics
  2  for table for all indexes for all indexed columns;
Table analyzed.

Now, we can observe:

ops$tkyte@ORA920> set timing on
ops$tkyte@ORA920> set autotrace traceonly
ops$tkyte@ORA920> select object_id,'In table 1, not in table 2'
  2  from t1
  3  where object_id not in (select object_id from t2)
  4  union
  5   select object_id,'In table 2 not in table 1'
  6  from t2
  7  where object_id not in (select object_id from t1)
  8  /

118 rows selected.

Elapsed: 00:00:00.67

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=34 Card=19 Bytes=190)
   1    0   SORT (UNIQUE) (Cost=34 Card=19 Bytes=190)
   2    1     UNION-ALL
   3    2       NESTED LOOPS (ANTI) (Cost=14 Card=18 Bytes=180)
   4    3         INDEX (FAST FULL SCAN) OF 'T1_PK' (UNIQUE) (Cost=14 Card=60456
Bytes=302280)
   5    3         INDEX (UNIQUE SCAN) OF 'T2_PK' (UNIQUE)
   6    2       NESTED LOOPS (ANTI) (Cost=14 Card=1 Bytes=10)
   7    6         INDEX (FAST FULL SCAN) OF 'T2_PK' (UNIQUE) (Cost=14 Card=60438
Bytes=302190)
   8    6         INDEX (UNIQUE SCAN) OF 'T1_PK' (UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     121168  consistent gets
          0  physical reads
          0  redo size
       2659  bytes sent via SQL*Net to client
        576  bytes received via SQL*Net from client
          9  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
        118  rows processed

ops$tkyte@ORA920> select object_id,'In table 1, not in table 2'
  2  from t1
  3  minus
  4  select object_id,'In table 1, not in table 2'
  5  from t2
  6  union
  7  select object_id,'In table 2 not in table 1'
  8  from t2
  9   minus
 10  select object_id,'In table 2 not in table 1'
 11  from t1
 12  /

118 rows selected.

Elapsed: 00:00:00.65

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=564 Card=120894 Bytes=1208940)
   1    0   MINUS
   2    1     SORT (UNIQUE) (Cost=564 Card=120894 Bytes=1208940)
   3    2       UNION-ALL
   4    3         MINUS
   5    4           SORT (UNIQUE) (Cost=141 Card=60456 Bytes=302280)
   6    5             INDEX (FAST FULL SCAN) OF 'T1_PK' (UNIQUE) (Cost=14
Card=60456 Bytes=302280)
   7    4           SORT (UNIQUE) (Cost=141 Card=60438 Bytes=302190)
   8    7             INDEX (FAST FULL SCAN) OF 'T2_PK' (UNIQUE) (Cost=14
Card=60438 Bytes=302190)
   9    3         INDEX (FAST FULL SCAN) OF 'T2_PK' (UNIQUE) (Cost=14 Card=60438
Bytes=302190)
  10    1     SORT (UNIQUE) (Cost=141 Card=60456 Bytes=302280)
  11   10       INDEX (FAST FULL SCAN) OF 'T1_PK' (UNIQUE) (Cost=14 Card=60456
Bytes=302280)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        540  consistent gets
          0  physical reads
          0  redo size
       2659  bytes sent via SQL*Net to client
        576  bytes received via SQL*Net from client
          9  SQL*Net roundtrips to/from client
          4  sorts (memory)
          0  sorts (disk)
        118  rows processed

but, if we force a bad thing:

ops$tkyte@ORA920> alter session set workarea_size_policy=manual;

Session altered.

ops$tkyte@ORA920> alter session set sort_area_size=65536;

Session altered.

we see something a tad different

118 rows selected.

Elapsed: 00:00:01.63

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1248 Card=120894
Bytes=1208940)
   1    0   MINUS
   2    1     SORT (UNIQUE) (Cost=1248 Card=120894 Bytes=1208940)
   3    2       UNION-ALL
   4    3         MINUS
   5    4           SORT (UNIQUE) (Cost=312 Card=60456 Bytes=302280)
   6    5             INDEX (FAST FULL SCAN) OF 'T1_PK' (UNIQUE) (Cost=14
Card=60456 Bytes=302280)
   7    4           SORT (UNIQUE) (Cost=312 Card=60438 Bytes=302190)
   8    7             INDEX (FAST FULL SCAN) OF 'T2_PK' (UNIQUE) (Cost=14
Card=60438 Bytes=302190)
   9    3         INDEX (FAST FULL SCAN) OF 'T2_PK' (UNIQUE) (Cost=14 Card=60438
Bytes=302190)
  10    1     SORT (UNIQUE) (Cost=312 Card=60456 Bytes=302280)
  11   10       INDEX (FAST FULL SCAN) OF 'T1_PK' (UNIQUE) (Cost=14 Card=60456
Bytes=302280)

Statistics
----------------------------------------------------------
          0  recursive calls
          9  db block gets
        540  consistent gets
       1184  physical reads
          0  redo size
       2659  bytes sent via SQL*Net to client
        576  bytes received via SQL*Net from client
          9  SQL*Net roundtrips to/from client
          0  sorts (memory)
          4  sorts (disk)
        118  rows processed

see, 4 sorts to disk, just like you -- with the corresponding PIO that goes
with that.  I'd guess that your sort area size is at the silly small default of
64k -- up it.

      Rating: 5
Which is the
      better execution plan  June 12, 2003
Reviewer:
      Angus McTavish aged 75  from Cumbria, England
Going back to your original answer, I'd like to pose this example and ask which
you think is the better method.

Two tables, same structure, 60000 rows, primary key on ID, analyzed. First
example is consistently slower by a long way despite the lower cost and much
fewer gets. It is doing a lot of recursive sql (why?) and more sorts however.
I've run both examples numerous times and the results are pretty consistent.

I have to do a lot of this kind of thing, comparing data from two tables, so I'd
appreciate your thoughts.

SQL> set timing on
SQL> set autot traceonly
SQL> select id,'In table 1, not in table 2'
  2  from accounts
  3  minus
  4  select id,'In table 1, not in table 2'
  5  from accounts2
  6  union
  7  select id,'In table 2 not in table 1'
  8  from accounts2
  9   minus
 10  select id,'In table 2 not in table 1'
 11  from accounts
 12  /

49 rows selected.

Elapsed: 00:00:17.00

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1208 Card=253738 Byt
   1    0   MINUS
   2    1     SORT (UNIQUE) (Cost=1208 Card=253738 Bytes=1014952)
   3    2       UNION-ALL
   4    3         MINUS
   5    4           SORT (UNIQUE) (Cost=302 Card=63450 Bytes=253800)
   6    5             INDEX (FAST FULL SCAN) OF 'PK_AC1' (UNIQUE) (Cos
   7    4           SORT (UNIQUE) (Cost=302 Card=63419 Bytes=253676)
   8    7             INDEX (FAST FULL SCAN) OF 'PK_AC2' (UNIQUE) (Cos
   9    3         INDEX (FAST FULL SCAN) OF 'PK_AC2' (UNIQUE) (Cost=9
  10    1     SORT (UNIQUE) (Cost=302 Card=63450 Bytes=253800)
  11   10       INDEX (FAST FULL SCAN) OF 'PK_AC1' (UNIQUE) (Cost=9 Ca

Statistics
----------------------------------------------------------
         32  recursive calls
         25  db block gets
        544  consistent gets
       1248  physical reads
          0  redo size
       1460  bytes sent via SQL*Net to client
        451  bytes received via SQL*Net from client
          6  SQL*Net roundtrips to/from client
          0  sorts (memory)
          4  sorts (disk)
         49  rows processed

SQL> select id,'In table 1, not in table 2'
  2  from accounts
  3  where id not in (select id from accounts2)
  4  union
  5   select id,'In table 2 not in table 1'
  6  from accounts2
  7  where id not in (select id from accounts)
  8  /

49 rows selected.

Elapsed: 00:00:09.05

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=32 Card=6344 Bytes=2
   1    0   SORT (UNIQUE) (Cost=32 Card=6344 Bytes=25376)
   2    1     UNION-ALL
   3    2       FILTER
   4    3         INDEX (FAST FULL SCAN) OF 'PK_AC1' (UNIQUE) (Cost=9
   5    3         INDEX (UNIQUE SCAN) OF 'PK_AC2' (UNIQUE) (Cost=1 Car
   6    2       FILTER
   7    6         INDEX (FAST FULL SCAN) OF 'PK_AC2' (UNIQUE) (Cost=9
   8    6         INDEX (UNIQUE SCAN) OF 'PK_AC1' (UNIQUE) (Cost=1 Car

Statistics
----------------------------------------------------------
          0  recursive calls
          8  db block gets
     253856  consistent gets
          0  physical reads
          0  redo size
       1460  bytes sent via SQL*Net to client
        418  bytes received via SQL*Net from client
          6  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         49  rows processed

      Rating: 5
It ain't the
      sorts to disk...  June 13, 2003
Reviewer:
      Angus  from Cumbria, England.
Tom,
Thanks for the suggestion - I'd overlooked the sort area size, and I thought
you'd hit the nail on the head because I was using a small sort area - 131072.
However, if I up the sort area size to 100 mb the sorts to disk go away as you
would expect, but the PIO remains the same as does the recursive sql and the
response time. For info, this is not on 9i but 8i (8.1.7.4). Something else is
going on here... Is it something to do with the way the index blocks are kept in
or flushed out of the SGA depending upon the execution plans? However many times
I run the two bits of sql the results are consistent.
SQL> set autot traceonly
SQL> set timing on
SQL> ed
Wrote file afiedt.buf

  1  select id,'In table 1, not in table 2'
  2    from accounts
  3    minus
  4    select id,'In table 1, not in table 2'
  5    from accounts2
  6    union
  7    select id,'In table 2 not in table 1'
  8    from accounts2
  9     minus
 10    select id,'In table 2 not in table 1'
 11*   from accounts
SQL> /

49 rows selected.

Elapsed: 00:00:07.06

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=32 Card=6344 Bytes=2
   1    0   SORT (UNIQUE) (Cost=32 Card=6344 Bytes=25376)
   2    1     UNION-ALL
   3    2       FILTER
   4    3         INDEX (FAST FULL SCAN) OF 'PK_AC1' (UNIQUE) (Cost=9
   5    3         INDEX (UNIQUE SCAN) OF 'PK_AC2' (UNIQUE) (Cost=1 Car
   6    2       FILTER
   7    6         INDEX (FAST FULL SCAN) OF 'PK_AC2' (UNIQUE) (Cost=9
   8    6         INDEX (UNIQUE SCAN) OF 'PK_AC1' (UNIQUE) (Cost=1 Car

Statistics
----------------------------------------------------------
          0  recursive calls
          8  db block gets
     253856  consistent gets
          0  physical reads
          0  redo size
       1432  bytes sent via SQL*Net to client
        442  bytes received via SQL*Net from client
          6  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         49  rows processed

SQL> /

49 rows selected.

Elapsed: 00:00:07.06

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=32 Card=6344 Bytes=2
   1    0   SORT (UNIQUE) (Cost=32 Card=6344 Bytes=25376)
   2    1     UNION-ALL
   3    2       FILTER
   4    3         INDEX (FAST FULL SCAN) OF 'PK_AC1' (UNIQUE) (Cost=9
   5    3         INDEX (UNIQUE SCAN) OF 'PK_AC2' (UNIQUE) (Cost=1 Car
   6    2       FILTER
   7    6         INDEX (FAST FULL SCAN) OF 'PK_AC2' (UNIQUE) (Cost=9
   8    6         INDEX (UNIQUE SCAN) OF 'PK_AC1' (UNIQUE) (Cost=1 Car

Statistics
----------------------------------------------------------
          0  recursive calls
          8  db block gets
     253856  consistent gets
          0  physical reads
          0  redo size
       1460  bytes sent via SQL*Net to client
        442  bytes received via SQL*Net from client
          6  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         49  rows processed

SQL> ed
Wrote file afiedt.buf

  1  select id,'In table 1, not in table 2'
  2    from accounts
  3    minus
  4    select id,'In table 1, not in table 2'
  5    from accounts2
  6    union
  7    select id,'In table 2 not in table 1'
  8    from accounts2
  9     minus
 10    select id,'In table 2 not in table 1'
 11*   from accounts
SQL> /

49 rows selected.

Elapsed: 00:00:14.09

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1208 Card=253738 Byt
   1    0   MINUS
   2    1     SORT (UNIQUE) (Cost=1208 Card=253738 Bytes=1014952)
   3    2       UNION-ALL
   4    3         MINUS
   5    4           SORT (UNIQUE) (Cost=302 Card=63450 Bytes=253800)
   6    5             INDEX (FAST FULL SCAN) OF 'PK_AC1' (UNIQUE) (Cos
   7    4           SORT (UNIQUE) (Cost=302 Card=63419 Bytes=253676)
   8    7             INDEX (FAST FULL SCAN) OF 'PK_AC2' (UNIQUE) (Cos
   9    3         INDEX (FAST FULL SCAN) OF 'PK_AC2' (UNIQUE) (Cost=9
  10    1     SORT (UNIQUE) (Cost=302 Card=63450 Bytes=253800)
  11   10       INDEX (FAST FULL SCAN) OF 'PK_AC1' (UNIQUE) (Cost=9 Ca

Statistics
----------------------------------------------------------
         32  recursive calls
         25  db block gets
        544  consistent gets
       1248  physical reads
          0  redo size
       1461  bytes sent via SQL*Net to client
        471  bytes received via SQL*Net from client
          6  SQL*Net roundtrips to/from client
          4  sorts (memory)
          4  sorts (disk)
         49  rows processed

SQL> alter session set sort_area_size=104857600;

Session altered.

Elapsed: 00:00:00.05

SQL> ed
Wrote file afiedt.buf

  1  select id,'In table 1, not in table 2'
  2    from accounts
  3    minus
  4    select id,'In table 1, not in table 2'
  5    from accounts2
  6    union
  7    select id,'In table 2 not in table 1'
  8    from accounts2
  9     minus
 10    select id,'In table 2 not in table 1'
 11*   from accounts
SQL> /

49 rows selected.

Elapsed: 00:00:14.07

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=528 Card=253738 Byte
   1    0   MINUS
   2    1     SORT (UNIQUE) (Cost=528 Card=253738 Bytes=1014952)
   3    2       UNION-ALL
   4    3         MINUS
   5    4           SORT (UNIQUE) (Cost=132 Card=63450 Bytes=253800)
   6    5             INDEX (FAST FULL SCAN) OF 'PK_AC1' (UNIQUE) (Cos
   7    4           SORT (UNIQUE) (Cost=132 Card=63419 Bytes=253676)
   8    7             INDEX (FAST FULL SCAN) OF 'PK_AC2' (UNIQUE) (Cos
   9    3         INDEX (FAST FULL SCAN) OF 'PK_AC2' (UNIQUE) (Cost=9
  10    1     SORT (UNIQUE) (Cost=132 Card=63450 Bytes=253800)
  11   10       INDEX (FAST FULL SCAN) OF 'PK_AC1' (UNIQUE) (Cost=9 Ca

Statistics
----------------------------------------------------------
         32  recursive calls
         24  db block gets
        544  consistent gets
       1248  physical reads
          0  redo size
       1461  bytes sent via SQL*Net to client
        471  bytes received via SQL*Net from client
          6  SQL*Net roundtrips to/from client
          8  sorts (memory)
          0  sorts (disk)
         49  rows processed

          Followup:

my testing shows that I think you have set the sort_area_retained_size too small
then and it is writing the results out to TEMP after the sort (sorted in memory
and then swapped it all out).

ops$tkyte@ORA817DEV> alter session set sort_area_size=104857600;

Session altered.

Elapsed: 00:00:00.00
ops$tkyte@ORA817DEV> alter session set sort_area_retained_size=0;

Session altered.

Elapsed: 00:00:00.00
ops$tkyte@ORA817DEV> select object_id,'In table 1, not in table 2'
  2  from t1
  3  minus
  4  select object_id,'In table 1, not in table 2'
  5  from t2
  6  union
  7  select object_id,'In table 2 not in table 1'
  8  from t2
  9   minus
 10  select object_id,'In table 2 not in table 1'
 11  from t1
 12  /

Elapsed: 00:00:01.96

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=476 Card=214328 Bytes=1071640)
   1    0   MINUS
   2    1     SORT (UNIQUE) (Cost=476 Card=214328 Bytes=1071640)
   3    2       UNION-ALL
   4    3         MINUS
   5    4           SORT (UNIQUE) (Cost=119 Card=53580 Bytes=267900)
   6    5             INDEX (FAST FULL SCAN) OF 'T1_PK' (UNIQUE) (Cost=4
Card=53580 Bytes=267900)
   7    4           SORT (UNIQUE) (Cost=119 Card=53584 Bytes=267920)
   8    7             INDEX (FAST FULL SCAN) OF 'T2_PK' (UNIQUE) (Cost=4
Card=53584 Bytes=267920)
   9    3         INDEX (FAST FULL SCAN) OF 'T2_PK' (UNIQUE) (Cost=4 Card=53584
Bytes=267920)
  10    1     SORT (UNIQUE) (Cost=119 Card=53580 Bytes=267900)
  11   10       INDEX (FAST FULL SCAN) OF 'T1_PK' (UNIQUE) (Cost=4 Card=53580
Bytes=267900)

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

ops$tkyte@ORA817DEV> alter session set sort_area_retained_size=65536;

Session altered.

Elapsed: 00:00:00.00
ops$tkyte@ORA817DEV> select object_id,'In table 1, not in table 2'
  2  from t1
  3  minus
  4  select object_id,'In table 1, not in table 2'
  5  from t2
  6  union
  7  select object_id,'In table 2 not in table 1'
  8  from t2
  9   minus
 10  select object_id,'In table 2 not in table 1'
 11  from t1
 12  /

Elapsed: 00:00:07.67

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=476 Card=214328 Bytes=1071640)
   1    0   MINUS
   2    1     SORT (UNIQUE) (Cost=476 Card=214328 Bytes=1071640)
   3    2       UNION-ALL
   4    3         MINUS
   5    4           SORT (UNIQUE) (Cost=119 Card=53580 Bytes=267900)
   6    5             INDEX (FAST FULL SCAN) OF 'T1_PK' (UNIQUE) (Cost=4
Card=53580 Bytes=267900)
   7    4           SORT (UNIQUE) (Cost=119 Card=53584 Bytes=267920)
   8    7             INDEX (FAST FULL SCAN) OF 'T2_PK' (UNIQUE) (Cost=4
Card=53584 Bytes=267920)
   9    3         INDEX (FAST FULL SCAN) OF 'T2_PK' (UNIQUE) (Cost=4 Card=53584
Bytes=267920)
  10    1     SORT (UNIQUE) (Cost=119 Card=53580 Bytes=267900)
  11   10       INDEX (FAST FULL SCAN) OF 'T1_PK' (UNIQUE) (Cost=4 Card=53580
Bytes=267900)

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

ops$tkyte@ORA817DEV> set autotrace off
ops$tkyte@ORA817DEV>

      Rating: 5
Strange
      behaviour of sort_area_retained_size  June 14, 2003

Reviewer:  Angus McTavish aged 75  from Cumbria,
      England
I think you've got the answer this time - thanks. But I've noticed a very
strange thing.
If I run my query with the small sort area, I get the disk sorts and all the
physical IO. Run it numerous times - same results. If I up the sort area to
100mb I don't get the disk sorts but I do still get the PIO. Again, run the
statement over and over again - same results.
But then, and this is the strange thing, if I 'alter session set
sort_area_retained_size=whatever', the PIO goes away and the response time
improves dramatically. But it doesn't matter what value I use. If I set it to
65536 the PIO goes away, if I set it to zero the PIO goes away. I've tried it
over and over again and always the same result - if I explicitly set the
retained size to anything, even zero, the PIO vanishes into thin air.
Oracle always does this to me, just when I think 'Oh good, now I understand
what's going on', something inexplicable happens and I end up with more
questions than answers.
Anyway, as always, thanks for your help on this.

June 13, 2003 Amit -- Thanks for the question regarding "A
      method for replicating logging tables into reporting tables", version
8.1.6

      You Asked
Hi Tom,
I am trying to find a robust/resource efficient method
for replicating data from logging tables into reporting
tables.

* Background:
  * OLTP database application with logging tables L1, L2, L3
    (massive insert activity).
  * All inserted rows have a timestamp field
    (of the insert itself).
  * logging rows are inserted only (never updated).
  * data in L1-L3 needs to be queried by complex queries
    involving joins.
  * queries do not have to return the most recent data
    (a few hours lag is acceptable).
  * Queries will initially be run on OLTP database, in
    the future will run a seperation reporting database REPDB.

* Objectives:
  * inserts into L1 - L3 should be as lightweight as possible.
  * replication to reporting tables should be as lightweight as
possible.
  * replicated data should be removed from the logging tables-
also needs
    to be as lightweight as possible.
  * queries should run as fast as possible.

Option#1:

Just create indexes on L1 - L3 to enable joins and complex
queries.

Drawbacks
* Inserts are more costly (due to indexes).
* Risk of snapshot too old (as blocks are queried and changed at
the same time).
* Deleting "old" data is costly (generates redo+rollback
activity).
* Cannot seperate between reporting and OLTP databases.

Option#2:

Use materialized views of L1-L3 that refresh every few hours:
Drawbacks
* Dramatic increase in activity in REDO, rollbacks, maintaining
  snapshot logs etc.
* Deleting "outdated" data is costly.

Option#3:

Manually simulation of snapshots, using partitions.

In OTLP database-
L1 - L3 are partitioned tables (on a locally managed tablespace)
in the following format:

  create table L1 (k number, t date, d varchar2(100), n number)
  partition by range (t)
  (partition p999999999999 values less than (maxvalue));

A daily job "INIT_PARTITIONED_DAY" will repeatably split the
maximum partition (an empty partition) into 6 parts for the
following day.
For example, when run at 04-JAN-2001 it will perform:

  -- partition #1 0000-0400
  alter table L1 split partition p999999999999
  at (to_date('200101050400','YYYYMMDDHH24MI'))
  into (partition p200101050400, partition p999999999999);

  -- partition #2 0400-0800
  alter table L1 split partition p999999999999
  at (to_date('200101050800','YYYYMMDDHH24MI'))
  into (partition p200101050800, partition p999999999999);
      :
      :
      :
  -- partition #5 1600-2000
  alter table L1 split partition p999999999999
  at (to_date('200101052000','YYYYMMDDHH24MI'))
  into (partition p200101052000, partition p999999999999);

  -- partition #6 2000-2400
  alter table L1 split partition p999999999999
  at (to_date('200101060000','YYYYMMDDHH24MI'))
  into (partition p200101060000, partition p999999999999);

As the OLTP system is working, the data inserted will be
placed in the pre-allocated paritions according to the
insert-timestamp.

Another dbms_job "MOVE_LOGS" runs periodically and moves data
from the filled partitions to the destination reporting
tables, and drops the partitions.

  function needs_archiving(oTname out varchar2, oPname out
varchar2)
  return boolean is
  begin
    -- Next table/partition that needs archiving ...
    ------------------------------------------------------
    select table_name, partition_name
    into oTname, oPname
    from user_Tab_partitions
    where table_name in ('L1','L2','L3')
    and partition_name = (
    select min(partition_name)
    from user_tab_partitions
    where table_name in ('L1','L2','L3')
    )
    and partition_name < 'P'||to_char(sysdate,'YYYYMMDDHH24MI')
    and rownum = 1;

    return(true);
  exception
    when others then
      return(false);
  end;

The following code iterates table/partition that
need replication, moves the data and removes the
partition:

set serverout on;
declare
  vTname varchar2(32);
  vPname varchar2(32);
  s1     varchar2(512);
  s2     varchar2(512);
begin
  loop
    exit when not needs_archiving(vTname,vPname);
    s1 := 'insert into '||vTname|| '@REPDB'||
          ' select * from '||vTname||
          ' partition ('||vPname||')';
    s2 := 'alter table '||vTname||
          ' drop partition '||vPname;
    begin
      execute immediate s1;
      execute immediate s2; -- implicit commit
    exception
      when others then
      begin
         rollback;
         raise;
      end;
    end;
  end loop;
end;

Benefits:

* Only "recent" data is kept in L1 - L3. Data
  which was sent to the reporting tables is dropped
  and not deleted, which saves REDO + Rollback activity.

* Data is replicated by partitions, meaning the number
  of data-blocks scanned by the select statement is
  minimal (a full scan till the partition's high
  water mark).

* Snapshot too old cannot occur, as selects are made
  on partitions on which there is no other activity.

Questions:

1. Are there any other efficient alternatives to
   accomplish this type of replication (inserts only).

2. Is there a way to configure materialized views
   to ignore deletes on the master table (delete
   on master table does not cause delete on
   snapshot) ?

3. In the above example, the OLTP database pushes
   data over a dblink into the reporting database
   REPDB.
   Alternatively, the MOVE_DATA dbms_job could be run
   on REPDB and pull data over a dblink.
   Which, from your experience is a better approach
   (more robust) ?

4. Can method #3 be made more robust ?

Thanks,
Amit.

      and we said...
For option number 1 -- i would just like to point out that the
risk of the ora-1555 is trivial and can be completely removed
simply by sizing rollback segments properly.  I treat ora-1555
as an implementation error, things were sized incorrectly based
on my needs.  The 1555 never has to happen.

Option number 1 isn't the best option for the reasons you
outlined.

For option #2, i would say "dramatic" is dramatic.  The overhead
of a snapshot log is pretty trivial as far as rollback/redo
generated.  you'll have a copy of the primary key of the log
table, a timestamp, two 1 byte fields and a couple of bits.  To
say that this would generate dramatic increases is dramatic
itself ;)  As the triggers to maintain the snapshot log are
internalized -- the overhead is very small.

Still, option #2, given you other constraints, is not optimal as
well.

Option #3 -- splitting the partition will involve rewriting the
entire partition (ugg).  Maybe a better way is to target each
days work into a partition using something like:

CREATE TABLE partitioned
( x int,
  a int default to_char(sysdate,'d')
)
PARTITION BY RANGE (a)
(
PARTITION part_1 VALUES LESS THAN(2),
PARTITION part_2 VALUES LESS THAN(3),
PARTITION part_3 VALUES LESS THAN(4),
PARTITION part_4 VALUES LESS THAN(5),
PARTITION part_5 VALUES LESS THAN(6),
PARTITION part_6 VALUES LESS THAN(7),
PARTITION part_7 VALUES LESS THAN(8)
)
/

X = you data fields.
A = new column to partition on.  It'll have the values 1..7.
Each day of the week into a partition.

Now, every day you goto the oldest partition and EXCHANGE that
with an empty table.  You'll keep the last 6 days on line and
the 7'th day will be moved into a table.  You'll now have a
partitioned table with 6 partitions with data and one empty one
(tomorrow that empty one will get data put into it).

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1238800184155
shows how to exchange partitions and tables. And now you
can take that table (which used to be a partition) and load it
up with the rest of the other data.

Review &
      Followup

      Rating: 3
Exchange
      partition for snapshot  June 06, 2003
Reviewer:
      Arun  from England
Hi Tom
We are using 8.1.7.4 enterprise edition. We are trying exchange partition
between a snapshot and a table with exactly same columns, but we get ORA-14097.
There seems to be a hidden column called M_ROW$$ on the snapshot. We tried
adding this column to the target table but we still get ORA-14097.

Is it possible to exchange a snapshot partition with a table? If not, is there
any other efficient alternative that insert/*+APPEND*/?
Thanks
Arun

          Followup:

Umm, what are you trying to do?  given that you don't even know how to populate
the m_row$$ column for example -- what do you think would happen to the snapshot
if you succeed.

      Rating: 1
I should have
      explained more  June 13, 2003
Reviewer:
      Arun  from UK
I am not trying to put data into the snapshot.

We have a table which contain records and we create summarised information of
this data using a snapshot. We remove data older than 30 days from the base
table but would like to keep the summaries for one year. So we thought of
creating a table that looks like the snapshot (same column, same data type) and
then exchange data from the snapshot to this archive table. Once the data is
moved we remove the data from the base table and after a refresh, it will go
away from the snapshot.
When we try to exchange data FROM snapshot TO the archive table, we get
ORA-14097. I hope this is more clear.
Thanks
Arun

          Followup:

if you are trying to exchange a partition with a snapshot -- well -- you are
putting data into the snapshot by definition (or taking it away as in this
case).  In any case, you are "breaking" the snapshot here.  You would be
performing an operation on it which Oracle would take to be "corrupting" it in
effect.

Options:

a)
insert /*+ append */ into archive_table select ... from snapshot;
purge base;
refresh snapshot

pros: trivial, easy, not much work
cons: copy data from snapshot to archive (not a big deal i don't think)

b) build the snapshot on a "prebuilt table".  Then, you can:

drop snapshot (leaves table)
purge data
rename table left over from drop
create new snapshot table
create new snapshot using prebuilt table option
refresh snapshot

pros: does not copy data from snapshot to table, just leaves the snapshots table
there
cons: more complicated, snapshot "disappears", have to regrant on it

I like the first option personally, it is easier.

June
      13, 2003 Maysam -- Thanks for the
      question regarding "Working closely with Developersso", version 8.1.7

      You Asked
This is more of an open-ended Question and a request for a healthy discussion
not only for Tom but for everyone that works in an environment that requires
interaction between the developers and the dba's in an organization.

Q) How to handle the developers lack of d/b knowledge ?

Q) How do you interact with them in terms of their mind set ? (For this I mean,
given a situation where a sorted results set is required from a d/b, a
programmer would most likely issue a sql query like select * from table and
store the results in a C++ array and then whip up a function to sort the results
instead of adding an order by clause in the query).

Q) How do we convince a programmer that a d/b is not a "dumping ground" for the
data. I phrase such use of a db as "JABORAC" Just A Bunch Of Rows And Columns.

I am always lacking proven defensive/convincing arguments/tools to demonstrate
the power of a d/b to a programmer. It seems it is a constant battle that
somehow they seem to win.

Does anyone work in these environments and how do they overcome these
differences in mind sets?

Tom has covered some of this in the begining of his wonderful book (One on One),
but I'd like to hear from both the developers and dba participating on this
site.

I'd greatly appreciate any and all feedback.

Thanks.
May$e.

      and we said...
speaking as a developer who's been a dba who's been a developer (and funny, i
just submitted my chapter on this very subject to Oracle Press for editing not
30 minutes ago...)

q1) through training, mentoring and hanging out together.

You'd be amazed -- developers really do want things to go really fast, really
easy, really smooth.  If you can show them how to make it so with some examples
every now and again -- you'll discover they catch onto this concept (that the
database can do things).

q2) Prove to them it is faster, cheaper, more efficient -- put down a challenge
-- tell them to beat SQLPlus.

q3) well, part of the problem is sometimes -- just sometimes -- DBA's make it so
hard to USE the database that they (developers) go into self preservation mode
and do it themselves.

Why might they go with a funky data model that is so generic as to be very poor
in real life performance wise?

Cause we make it really hard for them to add a table sometimes.

Why might they do security entirely in their application?

Cuase we make it really hard for them to turn it on in the database sometimes.

Why might they not use some database specific feature?

Cause someone in marketing thought it would be brilliant to stay database
independent -- whatever that means.

I've never found it hard to convince them.  SQLPlus is generally ALL it takes.
When I take a process that is taking them 5 hours and I turn it into a single
statement that takes seconds -- they are pretty convinced.

Me -- what I've come to think is we need 3 interacting groups of people

o UI developers -- let me have at it in java all of the way.  They never wanted
to touch this database thing anyway.

o DBA's as they are now.

o Data Developers -- the people who interface with both camps but are
responsible for the most valuable asset your organization has -- the data.

The applications, they aren't important. They are trivial to a large extent.
The data -- thats where the gold is -- wanna find out?  Just screw up a backup
and have it be needed -- you'll find out how important DATA is.

It takes a strong leader -- someone who wants to maximize their investment in
software -- to make this happen.  Someone who has seen that the cool generic but
painfully slow java data loader can be done using sqlldr in a fraction of the
time for example -- and then mandates the intelligent use of system resources...

I'll publish this for others to comment if they like.
Review &
      Followup

      Rating: 4
The other
      extreme...  April 20, 2003
Reviewer:
      A reader
I think I am in an environment which has the other extreme. Developers who want
_everything_ to be in the database. Everything. Application logic, rendering
application on the screen, UI display/ordering, you name it.

Is this healthy? I mean, what should be stored in the database and what should
be done on the app side?
I need to tell developers many times, guys, just do this in your app.

Database-neutral systems like CRM (Siebel, for example) store everything, from
pixel-position to app logic in the database. Of course, they probably do this
because they support every database under the sun. Heck, their database has
2000+ tables and not a single CHECK/FK even NOT NULL constraint. All they have
are PKs and non-unique indexes. They do all data integrity checks in their app
layer, but they _store_ all UI rendering details in the db.

          Followup:

hey, you just described my site!

that is exactly the way I operate here.

I have a slide I use alot that demonstrates my point of view.  This slide
builds.

It is a picture of a galaxy (i call it a universe -- its hard to get a good
universe picture)

In the center (arrow flies in to point to the center)....

And in the center is the database - everything revolves around it.

I do it all in the database, I consider Oracle my operating system -- PLSQL and
Java are my languages -- my platforms are infinite, windows, unix/linux,
mainframe, vms, macintosh, whatever -- I'm already ported.  I'm pretty scalable.
 I'm easy to manage (less moving pieces).  I can roll out an update to millions
of users with the push of a button.  Oracle is my infrastructure.

I'd hardly call Siebel et al "database neutral", more like "database
antagonistic (not agnostic, antagonistic)" -- they go out of their way to avoid
all things database.  So they store data in there -- that is IT, the beginning,
the end of it.  To them, it is just a bit bucket.

      Rating: 5
And the other
      side...  April 20, 2003
Reviewer:  Anonymous

We (the developer community at our company) are struggling with the DBA's to get
things tuned, installed, configured in the database so we could use the database
features instead of coding our own logic (waste of time and resources when such
thing is already inplemented and tested within the Database).

We had (and continue to have) very hard time trying to force our DBA's to give
us the facilities that database provides, for example, Advanced Queuing, XML
support, MTS configuration in the database to name a few.

We do and develop a lot many things that we know could benefit from such
features being available in the database but for some unknown reasons, we do not
have these features available to us.

It is surprising to see that, we, the developers, know exactly what features we
need and even know how to configure them in the database, but somehow the DBA's
are having a hard time implementing those features (For example, It's been years
we have been asking to get the MTS support configured, and still waiting...)

There have been so many instances of not getting the features that database is
able to support that we have developed many routines in the other layers to do
things what a database could do so easily.

To sum it up, In our case, we, the developers, are having trouble getting the
features that we want from our DBA's.

      Rating: 5
The other
      side??  April 20, 2003
Reviewer:
      A reader
I don't think that AQ, XML, etc are things that DBAs "give" you. They are
givings by certain versions/editions of Oracle. If you don't have these features
available, chances are that you are just having a very low version of the
software. Then DBAs cannot magically "enable" them. They just have to install
the correct version of Oracle RDBMS software and create databases with those
capabilities (by default, they will be there). Those features are not
"implemented", rather, they are _installed_. As for MTS, yes it is "implemented"
through configuratons by the DBAs, but funny thing is that it is completely
transparent to client programs. What does it have to do with developer's coding?
It is just a measure to handle thousands of connections! Do you need this
feature "enabled" just so that you can program correctly?? That would be
interesting :-). My advise: just do you job, and leave "people issues" to the
"strong leader" (hope you have one) - "it takes a strong leader to make this
happen" - very well said by Tom.

          Followup:

No, they have insufficient priveleges to use the features.

The DBA's hold the keys to the kingdom, no grants -- no use.

If the XMLtype wasn't installed -- no go.
If AQ wasn't configured -- no go as well.

You definitely do have to install and configure them.  Sure if the dba hit the
"make me a database" and didn't hit any other button -- they might be there (but
tell me -- how many do?)  But even if they are there, the appropriate grants
need to be in place as well.

MTS has nothing to do with developers coding (well it does, bad code with MTS
can be a disaster).  It has something to do with scaling up and using specific
features that require MTS -- if it is not configured, they cannot achieve their
goals.

The other side is accurate here -- I see it all of the time.  DBAs outlaw stored
procedures, views, application contexts, you name it -- reducing the database to
version 6.

It goes both ways here.

      Rating: 4
So let's build
      a list of issues for both sides.....  April 21, 2003

Reviewer:  Maysam  from Canada
Tom: I'd sure love to read what you submitted to Oracle Press. This was a pure
coincidence.

How about this:

The programmers point out the issues they would like the DBA's to understand and
like wise the DBA's will propose a list of issues that we want the programmers
to consider.

Let's also come to an agreement that both jobs (in person) are required for a
successful project/product completion/development.

So, I as you may have guessed am an aspiring DBA, will start with a few points
that I'd like the developpers to think about:

From a DBA to a Developper:

1) You will involve me in the design stages of the project. The stage before the
schema is even designed.

2) Stop telling me that this is just a proto-type, implying that the project
will be reviewed later and I'll get an opportunity to examine and critisize the
code later on. It is more expensive to fix the code later on. And there is never
time to re-write it resulting in band-aid fixing the code even before the
acceptance phase of the project.

3) Stop treating me as a person who is only responsible and good enough just for
installing oracle s/w, creating a started d/b and the project schema.

4) If there is a feature of the DB you'd like to exploit in the application then
we must first jointly learn about the applicability of the feature as
implemented by the d/b vendor and understand it's pros and cons, do extensive
research before proceeding with it.
(I was involved in a development project in which the lead programmer found out
that his calls to the d/b via the OAS/iAS layer was taking too long, so instead
of working with me in determining the source of his concerns, he actually worked
on developping a replacement of the OAS/iAS with his own version in C++, using
external procedures etc..). Needless to say, that blew more than my head off.

I also believe that we DBA put too much faith in the abilities of the product as
sold to us via "glossy brocures", "hyped" claims by DBA's of large instllations,
product spec sheets and release notes. So I'll also kick start the other side of
the coin.

From a Developper to a DBA:

1) Don't believe everything you read in the release notes and in the product
specs or what some sites (like Amazon) claim they do with their DB. These are
highly customized sites with the vendor providing specialized services to this
"flag ship" customer.

2)Don't claim that the DB can solve all programming problems. If you do, be
prepared to demonstrate the features with live examples.

How about that for a start.

Lets keep this ball rolling. The end result should be a comprehensive list of
items that we can then take to our own environments to be implemented in the
next project we may be asked to work on. Hopefully we can all learn from this
and have a happy/productive environment where the talents of all members will be
allowed to be expressed without any restrictions (within reason).

May$e

          Followup:

maybe we need that triumvirate i was talking about...

      Rating:
      5
  April 21, 2003
Reviewer:
      A reader
Story does not ends with developer and DBA. There are some architects who know
everything. But they are biased towards the thing which they know most. And for
them database is only table and few SQLs. Try to convinve them and look for
another job

      Rating: 5
Clarification
      on few things...  April 21, 2003
Reviewer:
      Anonymous
> I don't think that AQ, XML, etc are things that
> DBAs "give" you. They are
> givings by certain versions/editions of Oracle. If you
> don't have these features
> available, chances are that you are just having a very
> low version of the
> software.

You must not forget that even though these features are given by default in
certain versions/editions, the DBA's still need to configure/enable them. For
example, XML support does not happen magically. Java needs to be configured for
this to happen. AQ usage requires setting up cerain init.ora parameters to some
optimal values.

> Then DBAs cannot magically "enable" them. They just have
> to install
> the correct version of Oracle RDBMS software and create
> databases with those
> capabilities (by default, they will be there).

When you say "create databases with those capabilities", you already summed up
what my point is. Note that you can create database "without those capabilities"
and that is what our situation is.

> As for MTS, yes it is "implemented"
> through configuratons by the DBAs, but funny thing is
> that it is completely
> transparent to client programs. What does it have to do
> with developer's coding?
> It is just a measure to handle thousands of connections!
> Do you need this
> feature "enabled" just so that you can program
> correctly?? That would be interesting :-).

Yes, It has to do with the developer coding. There are cetain things that
require MTS to be configured (trying to call a remote procedure from the local
PL/SQL call when connected via Microsoft Transaction server and performing
updates). This not being available, we had to device alternate ways to do a
similar functionality, including opening two separate connections at the client.

      Rating: 5
Application
      systems with anti-relational approach  April 23, 2003

Reviewer:  Dushan
Siebel, Geneva, etc. Systems with no FK's, even no PK's (it is slow!), at most
unique/non-unique indexes. I can't believe, world is crazy or it is fully in
hands of sales persons and poor developers.
I was asked to look at slow Geneva billing system, ORACLE 8.1.7.4. Well, first
think - there were no statistics and optimizer_mode=RULE. I believed that I
discovered 'low hanging fruits', but answer from vendor - No way, optimizer mode
must be RULE.
Second think - one of the biggest problems - delete from table of size cca
160GB. Table where big inserts/deletes are required, designed as clustered
table. Pure candidate for partitioning - request not accepted from vendor.
'Access to data protected by application' - well, before running of billing,
they must spent certain time to cleanse the data.
What to say, what to do in such a case? To say - well, your system is and will
be really slow ...

      Rating: 5
My 2.5
      cents...  April 23, 2003
Reviewer:  Kashif
       from Houston, TX
I couldn't help but add my input to this discussion. The main problem I have
seen over the years is the lumping of the database developer's (also referred to
as the 'back-end' developer) job with that of the GUI/application developer. The
GUI developer has really no idea of how to utilize the database to its fullest
potential, and the people I would put in this category are the Java, VB and
C/C++ professionals. The database developer, on the other hand, has (or should
have) a strong grasp of the DB architecture, have strong SQL and PL/SQL
(assuming an Oracle environment) skills, and be aware of several different
database features to utilize them to their fullest. I think there's a
fundamental lack of understanding amongst IT managers of the distinction between
the GUI and the database developer. On a side note, I nearly let out a
blood-curdling scream when Tom suggested that DBAs and developers hang out
together. ;)

Kashif

          Followup:

Looks like another vote for a triumvirate.

if we can have three tiers -- why not three groups with different
responsibilities and roles at each level.  Take Xtreme programming concepts just
lump different programer types together working on the same problem.

      Rating:
      5
  April 25, 2003
Reviewer:
      A reader

      Rating: 5
Long live the
      triumvirate.  April 25, 2003
Reviewer:
      A reader

      Rating: 5
Tom, how about
      a list of interview questions for IT managers?  April 25,
      2003
Reviewer:  A reader
IT managers should be required to read your book and visit this site daily.

Tom, how about a list of interview questions for IT managers?

      Rating: 5
Should we hide
      the tables and views altogether?  April 28, 2003

Reviewer:  Ramakrishna. V  from Bangalore, India

Dear Tom,
  I have been going through your book and this site for info regarding where to
place the business logic, security, etc and what the UI should do.  Sometimes,
you seem to say that it is better to just expose a bunch of stored procs and
functions to the GUI developers rather than having them write inefficient
queries and so on.  Taken to the extreme, this could mean that a typical
database application can be viewed as having three concentric circles:
1.  The innermost one is the one with the entire schema -
    the tables, views, sequences, etc.  This could be
    owned by the data modeler and the DBA.
2.  The second circle is one with all the procedures and
    functions required to expose and manipulate all the
    underlying data.  This layer will be developed by
    db-savvy developers.  Effectively, this layer is like
    the complete API for the data model of the application.
    One thing to note here is that the GUI folks should
    specifically invoke all procs/functions using the
    named notation rather than positional because the proc's
    signature will probably change whenever an additional
    column is added to some underlying table.
3.  The outermost layer handles all the GUI and will be
    handled by the java, VB, etc. developers.
Is this a sensible approach or is too extreme?  Are there situations where it is
better to expose the table/view/sequence to the GUI folks?  Will this add too
much to the development effort?

Your views are invaluable as always.

regards
ramakrishna

          Followup:

you just described my triumvirate above exactly.

I think it works in all cases.

      Rating: 5
Caviat
      Developer  April 28, 2003
Reviewer:  Chuck
      Jolley  from OKC, OK USA
The triumvirate is pretty much how we do it here and it seems to work very well.
(We are slowly moving the entire app into the database, then we can go web based
for presentation.)
I have the role of Data Developer in our shop and I would like to point out a
safety issue:
The Data Developer is a developer, not a DBA, and should not have any write
rights in the production DB.
I can do any crazy thing I want in the development instance, but when it comes
time to implement anything in production then I sign the paperwork and the DBAs
we call "Storage DBAs" put it into production.
The Data Developer must realize that he/she is not a DBA in the traditional
sense and must not have the traditional DBA powers.
Chuck

      Rating: 4
How about 4GL
      tools?  April 29, 2003
Reviewer:  Ramakrishna.
      V  from Bangalore, India
Dear Tom,
  Thanks for your previous response.  While the basic approach to the three-part
separation looks ok, I am not clear how the following would be handled:
1.  How can we use 4GL tools like Oracle Forms & Reports
    if we 'hide' the schema behind API's?  Should we take
    the approach that 4GL tools can dip directly in while
    custom-built apps should go through the API?
2.  How do we build generic API's to solve all the needs
    of the GUI layers?  Different screens could require
    different attributes of the same underlying object and
    they might want to apply different filters.  If we
    build specific procs to meet each need, we could end up
    with a mess.  Even if we attempt a generic proc, we
    might need to compromise on performance.  E.g., if an
    object is physically stored in three underlying tables
    and a user asks for three attributes, all of which
    belong to one table itself, then the generic proc should
    avoid doing any extra joins to get at this data.

Could you please throw some light on when exactly we should use the triumvarate
approach?

Thanks in advance,
ramakrishna

          Followup:

1) forms -- a tad different.  The developers aren't doing SQL, they are building
a simple OLTP application (row insert/update/deletes).  It is a different
environment (not one that I actually use)

Reports -- it is all about views. (or ref cursors)...

2) why a mess?  what you end up with is a database full of objects that support
your needs.  You KNOW for a fact that if you alter table T drop column X that
package_X will be affected (user_dependencies will tell you).

the mess would be if you have this really important information (who uses what
and how) spread across 50 gazillion client applications and you have no clue
what is really what.  THAT -- well that would be a mess.

Having it all in the database, well, that, that would not be a mess.

You have a requirement....
Requirements are implemented via code...
You have code...  You either have code in the client application or you have
code in the database.  You have code EITHER WAY.

No generic procedures needed.  If you have the need for 50 queries today, you
have a need for 50 queries tomorrow.  I see no conflict here -- all I see is
that by putting in the database I can

o tell you what the queries are without digging through 500 source code files
o fix the performance of the queries
o consolidate queries (frank didn't know mary was doing basically the same
thing)
o audit the usage (i have a procedure wrapping it)
o fix the queries when the model changes a little -- without digging through 500
source code files and HOPING I got them all

You tell me -- what environment sounds better, more controlled, more manageable?

      Rating: 5
Working closely
      with Developers  April 29, 2003
Reviewer:
      juancarlosreyesp@yahoo.com  from Bolivia
Hi, maybe I can share something from my experience,

Oracle is too big, Tom Kyte, I see as a responsible DBA, don't know every issue
and parameter he has, even when he worked with Oracle from release 6 as I
understood.
It is not possible.
If you want developers optimally, and dba take advantage from all the features
oracle has, you must program periodic teaching courses, develop short manuals to
help developers to learn.
Dba's don't know every issue, an OCP certification, doesn't cover all topics, it
cover the "basic concepts", you must study advanced queues, workflow,
intermedia, etc.
Developer don't know about several MUST features, like analitic functions, trace
files, etc.

Is something you must accept, learning takes time, even a new brand OCP, need
time in the same way a seniour dba, when they are using a feature they never
used before.

What could you do
Ask a developer ( if he is not certified ) to give the izo007/izo009 (sql
fundamentales ) is reasonable, and helps too much, people who gave this test
(including me) learned a lot.

Establish bibliography, Tom kyte book "Expert one on one", Guy Garrison "High
performance Tuning", etc. Book that must be read for example in the first 6
months.

Establish procedures, Even now that I had done several forms, there some details
you lose, ( i am dba and developer ) do a procedure step by step with all the
step you don't have to forget.
In the same way do procedures to us trace files for example.
All that procedures shortcuts the time to learn.

Periodically work with them, is very important to work with them, for example
see you creating a table, or creating a form, because even whey you don't know
the experience had teached you several shortcuts that saves a lot of time, that
new developer neither knew that existed.

Buy third party software that could help you, for example Artisan, sql navigator
from quest software, etc. (you can find more in oracle magazine), can save you
hours of work, and avoid the need of experts.

Measure daily their advances to newbies and see their problems, avoid asking
after a week if he ended. You must help them.

---
Know you work with people with really big big differences.
Know when you are working with people that don't want to work, at this time I
had found something.

No worker including the boss is 100% responsible, every one, some times has a
hole.
If I should group in three group to employees I would say:
-There are people who make their work, all that you say, as you said, not
questions, very responsible. But don't do more, they avoid to be responsible of
anything.
They investigate in the way he must do.
And usually they become middlemanager
-There are people that likes to learn, very hyperactive, people that need to be
guided, because they uses to lost in their own ideas ( like me ), but if they
can relate their hyperactivy with the business goals, they grow and can be a
manager, because they like to take responsabilities.
They are investigators.
-There are people that works only in the essential, and tryes to work the less
they can, they work well in urgent matter, but after that they try to avoid
working the most they can.
They do anything other than working. ( you must know this kind of guys and fire
them from the beggining )

Every one must be helped and guided in the way they are.
You must see hidden problems, originated in the interrelation from distinct kind
of workers, sometimes responsaible persons seems to be a problematic, you can
judge based in how they talk to you neither in what other people says about him.
This is not simple, you must read something about sociology and psichology.
People are not machine where you put coins and they do something. (at least not
all the time)

:)

      Rating:
      5
Also...  April 29, 2003
Reviewer:
      A reader
I have also observed that seldom is it the case that applications are well
documented. Most of the times one would be hard-pressed to find a decent E-R
diagram. Which means that should we need to re-write the application, assuming
all the code is locked in the application, we're effectively hosed. The only ray
of hope one has is if all the code is stored neatly in the database. In such a
case, you can theoretically move from one front-end to another without spending
a chunk of time figuring out the business rules. Though on the flip side, it
gives us developers the chance to work on 6-month projects in which you swim
through thousands of lines of code to extract business rules, document them, and
re-implement them in another application. And in this economy I'd rather be
doing something silly than nothing at all... ;)

Kashif

      Rating: 5
ER
      Diagrammer?  April 29, 2003
Reviewer:
      A reader
Tom, do you use an ER diagrammer? If so which one. Oracle used to have a really
simple one, I can't remember the name.

I would just like something really simple, if anyone has any suggestions.

Thanks

          Followup:

i've heard "er win" is popular.

      Rating: 4
What if you
      move between DB Vendors  April 29, 2003
Reviewer:
      A reader
It was interesting to see how many ideas of achieving the best of all worlds was
discussed. But what if for some reason you need to move from one DB (let's say
Oracle) to a different DB (don't want to name any). What would happen to the
application code written neatly in the DB. What would you do then, write the
complete application just because the DB has changed?

          Followup:

you need to rewrite it all anyway.  IMHO (being a guy who did cross database
stuff way before he came to oracle -- my background was sql/ds, db2, ingress,
informix, sybase sqlserver, gupta sqlbase -- Oracle was my last database
actually -- quite appropriate, it is better then the others...) the only way to
achieve database independence it to be 100% into stored procedures so when you
have to for whatever reason go from A to B -- you can do it.

Are you really going to avoid decode and CASE?  connect by? analytics?
partitioning? bitmap indexes? (really super long list here)...

thats is (beware -- opinion, harsh opinion coming) stupid.  plain and simple --
stupid.  I have case after case after case of stupidity in this area.

concurrency models -- HEY -- we are different.  that transaction that works in
oracle -- fails in sqlserver and vice versa.

locking -- totally different -- deadlocks as rare as a blue moon in oracle -- as
frequent as red lights in new york city in others. blocking -- nope, don't got
it.

ways to properly ask a question -- everyone is different (commidity database --
give me a BREAK, it just isn't true)

Trivial read only simple "flat file" applications -- they can be database
independent -- but only because grep sed and awk would suite them just as well.

Unless you want to be like SAP or Peoplesoft (even they woke up and said -- "5
databases -- no way, sybase, informix -- gone, ibm, ms and oracle will be it)
and do virtually nothing in that piece of software you paid a couple of $$$'s
for....

My theory on why people move from db to db?  Because they wrote a db independent
app (or tried to) and performance stinks and they think the grass will be
greener elsewhere....

This stuff is infrastructure -- your platform -- more powerful then your
operating system can ever be.  Regardless of the database you choose -- you
would be foolish not to exploit the heck out of it.

      Rating: 4
Not sure why
      thats hard to figure out...  April 29, 2003

Reviewer:  A reader
Reader above,

If you're moving from one DB to another, life isn't going to be easy anyway,
you'll be needing to re-write a chunk of code, application and database. But
assuming you have all business rules code in the DB, you have one less step of
trying to consolidate and figuring out all business rules first. All your
business rules are in the DB, and it thus becomes an exercise in mapping one
Oracle procedure to one SQL Server/Sybase/whatever procedure, effectively. The
objective in consolidating business rules into the database is not necessarily
to prevent you from writing code ever again, but rather to make your life easier
if there is the need to make changes. Thats what my experience has been. And by
the way, are you suggesting that you want to take some code and plug it into
another database, without optimizing it for that database? I sure hope Tom isn't
reading this thread...

Kashif

          Followup:

too late ;)

      Rating: 5
OT - what does
      IMHO stand for ???  April 30, 2003
Reviewer:
      Tim

          Followup:

IMHO = In My Humble Opinion.

      Rating: 5
Data Developer
      - brilliant - needs visibility and traction  April 30, 2003

Reviewer:  Alberto Dell'Era  from Milan, Italy

What about Oracle Corporation starting promoting this definition, explaining it
to customers, giving some presentations (nice, colorful slides) at Oracle Events
such as AppsWorld, OpenWorld and so on ?
Perhaps going so far as inventing a new acronym that helps people identify and
distinguish their role to the public ("I'm a DDD - Database Data Developer; no,
I'm not a DBA, DataBase Administrator") ?

Not to make a favor to Tom, of course, but because having this "filling the gap"
professional specialty is VITAL for the future of Oracle. The above discussions
have pointed out how nearly always the features of the Oracle database that are
more advanced than 'select * from t' are simply not used. I may add tens of
similar 'business cases' myself. Now, the best selling point of Oracle is its
technical superiority, and Oracle is constantly investing millions of dollars to
stay ahead of the competition, but if this potential does not get expressed
because Developers don't know and use the features, and DBAs only care of
installing the software and taking backups, in the long run Oracle will lose,
since the customers will see no difference with the competition, but the cost.

And here it comes the DDD to the rescue - dramatically lowering the development
costs, time and risk by making the Developers use the features, and making the
managers happy, and selling more sw licenses, and making the ORCL Stock Price
skyrocket, and letting Larry spend more money for, and win, the next America's
Cup! This is the Paradise!

But, without "traction" from Oracle Corporation I'm afraid that the
market-at-large will never get it; they will constantly search for "Java Gurus,
SQL knowledge preferrable" and "10-year-experience DBAs, must know RMAN
perfectly, how to tune basic queries preferrable". Check any Job Opportunity;
i've been constantly searching the DDD for years on the market, got only two or
three times something similar. Really, it's the DDD that i want to be! But how
can i explain myself to the market, if they don't know, neither search for, what
the hell I'm speaking about ?

Tom, what about enabling your VP SuperPowers, and advocating the Database Data
Developer with Larry ?
Keep saying "America's Cup ... America's Cup ..." ;-)

Long Live the DDD!
Alberto Dell'Era

      Rating: 4
Dba vs
      Developer  May 01, 2003
Reviewer:  CA
       from Canada
Everybody's object should be, running the business better and shouldn't be only
keeping the database in shape. As a  developer, we are having very serious
problems with some DBAs because they act like database cops, they lack on
business and how we run the business. They think that the company runs hundreds
of million dollar business to have a fast database. NO! . Developer talks to
everyone and puts everything (business objective & requirements, system,
interfaces, data model, performance, security, risk, back-end, front-end, etc..
) together and comes up with a complete solution. Sometimes, overall best design
might not be the best for each one. Sometimes we give up from performance,
sometimes from accessability, sometimes we do ugly stuff to satisfy the client.
It is really hard to explain how we make decisions to people who thinks about
1's and 0's.
Also, other thing, DBA attitude. Some DBAs act like god. I find it very funny.

          Followup:

it is a two way street.  I've seen some developers .......

      Rating: 5
Note on
      "America's cup, VP superpowers and so on" above  May 01,
      2003
Reviewer:  Alberto Dell'Era  from Milan, Italy

Those were only a joke, of course.
Hope that nobody got offended by it - if yes, I apologize.

Alberto Dell'Era

      Rating: 5
Well
      Said....  May 07, 2003
Reviewer:
      A reader
Very well said Alberto:

"The above discussions have pointed out how nearly always the features of the
Oracle database that are more advanced than 'select * from t' are simply not
used."

That is so true...

DBA's acting like god !!!! (thank goodness it is a god with a lower case 'g').

Maysam

      Rating: 4
By the
      way...  May 12, 2003
Reviewer:  Kashif
       from Houston, TX
Hi Tom,

I was recently reading an article by Ken Jacobs in which he described the
evolution of the Oracle RDBMS and the relational database programming paradigm.
Very good stuff. Though he mentioned that the 'programmable' database (i.e. the
ability to program in PL/SQL) was not introduced till the 7.0 release. Is that
true? I wonder how all the data info was handle before that, and what about
triggers? I've only been using Oracle since 7.3.4., and assumed database
programming was there from day 1.

Kashif

P.S. Jokes aside, what do you think about Alberto's comment about promoting the
database developer? I think it's a role that has largely been ignored so far as
a distinct set of skills, and be should promoted actively.

          Followup:

PLSQL was introduced in version 6 of the database however, only anonymous blocks
were permitted -- no stored procedures, no triggers.  So, you could submit a
block of plsql code from a Pro*C or whatever client (no VB back then, ahh, life
was good) but you could not create a stored procedure.

Stored procedures, triggers, et al were introduced with version 7.0 of the
database in 1992, a relatively short 11 years ago....

I promote the role of the database developer all of the time -- dead serious.
Guess who owns the keys to the kingdom -- a gui programmer?

      Rating: 4
Users of the
      Data // Silos  May 23, 2003
Reviewer:  Brian
       from Phoenix, AZ
The one thing that I have noticed that is often overlooked is how the data will
be used.

I am neither a developer nor a database administrator, rather I am a user of the
data.  And what I need most is data that can quickly answer my questions
quickly.

Currently, I am in a situation, where I am working with developers as part of
large project to build working apps to extract information.  The current ones
were badly designed and the execution times are horrible.

Unfortunately, the application will not be built for another year.

This means that I have to go directly to the source -- the database.

I have to write my own SQL to extract out what I need to know.  And I am coming
from a finance background.  Thus, I have to have the know what CPAs know as well
as what database developers know to obtain my data.  This is a very problematic
environment.

Recently, I ran into an issue where I attempted to exclude a certain literal
string (e.g. 'xx') and pull everything else.  For some reason all my NULL values
would also be excluded.

I had to come here and spend an hour reading through all of these archives to
learn that the reason why was because of the constraint set on the field.  Even
the local database people couldn't answer that question for me.

I would like to suggest to Tom that a book that addresses the relationship
between the users of the data, the database folks, and application folks would
be very good!

Please don't get all stuck on the code and forget about how the data will be
used!

      Rating: 5
DBAs reqd
      little middle tier( JDBC or VB) knowledge.  June 10, 2003

Reviewer:  Chandra S.Reddy  from India
Hi All,
This thread is very interesting.
In my openion, If DBAs/Oracle Developers have some knowledge on middle tiers
like JDBC and VB, many problems can be resolved.
Let me put some of my live examples here.
1.When we are developing an application,
our requirement was, Java has to read data from Excel files, and load them into
Oracle database.
Solutions we thout for this,
#1. Convert all excel files into CSV files and dump them at the Oracle Server
location and use SQL Loader.
But, problem was our Application was running on NT and Database server is on
Solaris OS.
So FTP or some third party tools are reqd.
Due to security issues, Client was not happy with this solution.
#2. Convert Excel files into XMLs and use Oracle utilities to dump/insert XML
data into Oracle tables with the help of JDBC.
Since I had a bit of JDBC skills and support from JAVA guys, I run through all
the sites, and did R&D with Oracle/XML, We came up with this solution.
Client accepted this solution.
---

In another example,
They used 30 to 40 IN parameters from Java Application to PL/SQL procedure.
I asked them to send all IN variables in the form of Arrays, which are
compatible with both worlds(Java and Oracle).
So, that I can iterate the ARRAYs inside PL/SQL SPs and process them.
---

In another example,
Pagination should (the pages appear in Google site) be implemented in some other
situation.
First they did pagination at the Application level.
The problem is, when the data is very large(some lacks of rows), Application
Server gets hang up.
I personally observed the Application Server CPU usage and Architecture(Java
implementation) with the help of my own Java knowledge and by asking questions
to other Java Developers.
Finally,I decided and said pagination will be done at Stored Procedure level and
limited set of records(say 1000 rows) at a time. This was done with the help of
DENSE_RANK(analytic function)
This solution worked fine.

So, the bottom line is, it is always better to have JAVA/VB skills also to
analyze the problem from the flip side tooooo.! ;)

What do you say Tom ?

Have a fun.

          Followup:

1) sqlldr works over a network -- you didn't need to move anything
2) see #1

I don't see why you would need java/vb skills to solve any of the above?  The
first case was "should have used sqlldr anyway", second was a matter of choice
-- there is nothing wrong with 30/40 in parameters -- unless they should have
been a collection in which case you only really needed to understand plsql
still, third was "sql" (and 1000 is perhaps 100 times too many)

      Rating: 5
Once
      again  June 10, 2003
Reviewer:  in disguise
       from USA
Tom,

You hit the nail on the head.

Can you help me understand something?  Why would a developer want to create
public synonyms for every objects, tables, sequences, views ..., in a schema?

When I informed the developer that public synonyms can be bad for performance, I
was asked, "If public synonyms are so bad, them why does Oracle have them or use
them?"

          Followup:

we do not use them in our real applications.  look at the apps -- everything is
schema qualified.

We use them for the data dictionary, but -- you don't have applications built on
the data dictionary very often do you?

As for "why", you'd have to ask them "why" I guess.

      Rating:
      5
  June 10, 2003
Reviewer:
      A reader  from canada
This was/is an excellent thread. I serve my site primarily as a dba but also do
some development work so I get subjected to both areas. It's like tom said it's
a two way street. The developers at my site tend to be stuck in their little
development modes and no matter what hard evidence they are shown it is never a
good idea unless they come up with it first(and even then they will totally
reject it because it's in the database). You can't win, I have gone to great
lengths to help educate them in the database but they will always regard it as a
data dump. I don't think it's a matter of who's a dba or database developer or
even a UI developer. The problem lies with the some people having these fragile
little egos a tendency to want to be on a power trip all the time and an
inability to "communicate effectively" or have an "open mind".

      Rating: 5
Those backend
      developers  June 13, 2003
Reviewer:  Mike

As one of those backend developers I like to think that I'm pretty hot on my
Oracle stuff.  While I'm not a DBA, I do my utmost to understand how and why
Oracle works the way it does.  I like to think that this makes me more
employable, as it means I can talk to DBA's at a technical level, something
which I think is often a problem in organisations, and I can exploit my
knowledge to get the best out of the large price tag people pay for Oracle.

My question is, as the above posts seem to support this and most high quality
Oracle related publications seem to support this, why does Oracle's
certification program seem so unwilling to support this?  I would love to be
able to become OCP certified, but I'm not a DBA and I'm not a fan of Reports or
Forms (I still have nightmares about Reports 2.5) which means I don't have an
OCP track to go on.  Is there any reason why Oracle doesn't run a "PL/SQL /
Oracle developer Guru" OCP track?

          Followup:

I don't know, I'm not in any way affiliated with OCP (i'm not OCP'ed myself ;)

      Rating:
      5
OCP  June 13, 2003
Reviewer:
      A reader
While I believe that an OCP is of dubious value, many employers require it.
Oracle themselves promote it religiously. And, in the long run, it at least
shows that you know how to spell oracle.

      Rating: 4
What is OCP -
      my story  June 13, 2003
Reviewer:
      A reader
I am OCP but I do not know how much OCP(Developer track) helps. I went on first
project after I became OCP, there they wanted to develop Forms based on Store
procedure, believe me it was nightmare for me because I had never created forms
based on stored procedure, I had always created based on tables. I personally
feel that being OCP means you understand the subject, this is the starting point
and still you have a long way.

      Rating: 5
experience +
      OCP --> more experience  June 26, 2003

Reviewer:  Alberto Dell'Era  from Milano, Italy

Experience counts much, much more, but unless you're so lucky to work  in a shop
where you have plenty of occasions to explore all the features of Oracle
(=diversify your experience), you risk to re-invent the wheel just because you
simply don't know that they exist.

OCP at least gives you a good coverage of a lot of the main features of Oracle.
Theoretical of course. But if you explore the features sitting in front of
sqlplus, instead of trying just to memorize them to pass the exams, you get at
least some "experience". That what's i did, and since then i have re-used the
things learnt there many times, as a starter to investigate better whether they
can help me solve some particular real-life problem, as a complement to my real
experience. And then, introducing the "new" features in my shop practice (and in
my personal toolbox).

If there were a "data developer" track, i would find it very useful, since i am
mostly a DD. And since this would improve the world knowledge about this
critical specialization, i would say that Oracle too should be interested in
offering such OCP track.

Tom, this is my last post on this thread - i promise ;-)
Alberto

June 13, 2003 Sean -- Thanks for the question regarding
      "Unauthorized DDL Audit", version 8.1.7

      You Asked
Hi Tom,

I have setup DDL auditing on my database using database trigger examples on your
site.  All DDL changes cause a row to be inserted into my audit table

e.g. insert statement from trigger
insert into audit_ddl
  select user,sysdate,ora_sysevent,ora_dict_obj_owner,
        ora_dict_obj_name,ora_dict_obj_type,null
          from dual;

What I would like to do now is to be able to easily identify unauthorized DDL
operations more easily.

All authorized DDL changes are performed via a SQL script and at the moment I
have to manually go though the audit table to ensure that all DDL has originated
from one of the change scripts.

Can you think of a way I can flag that the change was applied via a script in
the audit table ?

Any ideas would be appreciated.

Regards,

Sean.

      and we said...
Well, there is one thing that will "help", but not SOLVE.

If you run a script in sqlplus, it is kind enough to call dbms_application_info
to register the name of the script in the v$session table:

ops$tkyte@ORA817DEV> @test
ops$tkyte@ORA817DEV> select module from v$session where sid = (select sid from
v$mystat where rownum=1);

MODULE
------------------------------------------------
01@ test.sql

ops$tkyte@ORA817DEV>

test.sql contained that query -- as you can see -- sqlplus told us "test.sql" is
the script running.  Further, if you create test1.sql that just has "@test.sql"
in it:

ops$tkyte@ORA817DEV> @test1
ops$tkyte@ORA817DEV> @test
ops$tkyte@ORA817DEV> select module from v$session where sid = (select sid from
v$mystat where rownum=1);

MODULE
------------------------------------------------
02@ test.sql

ops$tkyte@ORA817DEV>

you can see the number bumped -- showing the level of indentation.

Now -- how does this help you?  Well, you KNOW that any DDL that is executed
without this field is *bad* DDL.  It was not run from  a script.  So, you can
use that to immediately flag BAD DDL -- faster.

What it won't do however it prove that the other DDL is "OK".  There is nothing
stopping the "smart" user that knows your code from issueing:

exec dbms_application_info.set_module( '01@ test.sql' );
drop index t_idx;

So, you would have to validate still -- that the SQL was "OK".  Further, you
would have to validate that any script name was one of your scripts in truth.  I
mean, I can create "valid_file_name.sql" and run it in sqlplus -- but that does
not mean it is your valid script!

So, you can use this to id obviously "bogus" entries.

Review &
      Followup

      Rating: 5
Very Neat
      Solution  June 13, 2003
Reviewer:  Sean
       from UK
Thanks Tom.

Your solution fits in really well since my DDL script names incorporate the
change request number relating to the DDL.

This will make spotting unauthorized DDL very easy in my audit table.

I take on board your point about smart users trying to circumvent the system.
However, it should hopefully be difficult for them since I can check the audit
for suspicious entries e.g. scripts being run out of sequence, the same script
name being used at different times etc.

June 13, 2003 Md. Sazzadur Rahman -- Thanks for the question
      regarding "Listener problem Oracle on Linux", version 8.1.6

      You Asked
Hi Tom,
I have installed Oracle 8i(8.1.6) on Redhat Linux 6.2, but I am facing some
problems.
I want to start listener service and oracle SID during linux startup.
I have configured as per linux documentation provided by Oracle.
I have already set environment variable and export the PATH in the profile.

Here I give 2 problems that I am facing:
1)
For listener service I have included the following line in the /etc/services
file as per
instruction:
listener  1521/tcp
But Listener does not start automatically during linux booting.
I have to start it manually.
What is the problem? How it will be solved?
2)
I have updated the /etc/oratab file. My Oracle service name is project.
project:ORACLE_HOME:Y

I have created a file named dbora in the location of
/etc/rc.d/init.d/ and added the following lines:

#!/bin/sh
ORA_HOME=/p02/oracle
ORA_OWNER=oracle
if [! -f $ORA_HOME/bin/dbstart]
then
echo "Oracle startup: cannot start"
exit
fi
case "$1" in
'start')

su - $ORA_OWNER -c $ORA_HOME/bin/dbstart &
;;
'stop')

su - $ORA_OWNER -c $ORA_HOME/bin/dbshut &
;;
esac

Then I have Linked dbora file by entering:
$ln -s /etc/rc.d/init.d/dbora /etc/rc.d/rc0.d/K10dbora
$ ln -s /etc/rc.d/init.d/dbora /etc/rc.d/rc2.d/S99dbora

But I am facing the same problem. Database and Listener do not start
automatically.
I have to start the Listener by using the command '$lsnrctl start listener'
and database in the server manager 'svrmgr>startup'

I don't want this. I want to start both listener and database automatically.
What is the solution?

With best regards.

Sazzadur Rahman Tusar, Bangladesh.

      and we said...
that just documents that the listener will be using that port.  It doesn't start
a thing.  Its not supposed to start anything (heck, there isn't even enough
information contained in that line to know that the process is lsnrctl that
needs to run)

Now, I'm not a linux admin, never used linux -- but I use solaris all of the
time.  See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:387218751430
for how I do this on solaris.  Since you have init.d files -- it looks like it
will be exactly the same.  Use my method.
Review &
      Followup

      Rating: 2
I need a
      solution  January 13, 2002
Reviewer:  Sazzadur
      Rahman Tusar  from Bangladesh
I need a solution of Listener problem on Linux.

          Followup:

umm, yes.  I showed you how to autostart stuff.  did you read it?  follow the
link and its right there.....

      Rating:
      4
  January 14, 2002
Reviewer:  Tianhua
      Wu  from Atlanta, GA
I have used Linux for quite a while.  There is no major different between
Solaris and Linux.  just add two line in the /path/init.d/Oracle file:

...
su - oracle -c "ORACLE_HOME=/opt/oracle/product/8.0.5; export ORACLE_HOME;
/opt/oracle/product/8.0.5/bin/lsnrctl start"
...
su - oracle -c "ORACLE_HOME=/opt/oracle/product/8.0.5; export ORACLE_HOME ;
/opt/oracle/product/8.0.5/bin/lsnrctl stop"
...

Hope this help.

      Rating:
      4
  January 14, 2002
Reviewer:  Tianhua
      Wu  from Atlanta, GA
Oh, the su command is slightly different.

      Rating: 3
I have solved
      it!  January 15, 2002
Reviewer:  Sazzadur Rahman
      Tusar  from Bangladesh
I have solved this problem by writing script that is
the similar to SUN solaris.This is running well.
Thanks Tom.

      Rating:
      3
  June 13, 2003
Reviewer:  Mark

Hi Tom,

I have installed Oracle 8.1.7 EE on a standalone intel pc, running Red Hat Linux
8.0.

As an oracle user, I am able to start/stop oracle database, connect using
sqlplus and everything is working fine, as expected.

But when I try to connect as a different user, I get TNS error. I have done the
necessary environment settings for this user like ORACLE_HOME, ORACLE_SID, PATH
etc. I also tried copying the tnsnames.ora into this users home directory. So
what else is missing ?

Or is this a totally wrong way to try connecting as a different user (other than
oracle) on an Oracle server ???

Thanks in advance !!

          Followup:

well, no error message, no error code, no nothing.  there are more then one tns
error messages possible.

on the same machine, you should only have to set

o ORACLE_HOME
o ORACLE_SID

and then $ sqlplus scott/tiger

if you use sqlplus scott/tiger@tns_connect_string, then that would have to be
setup in the tnsnames.ora file and the listener would have to be running.

no messages, no codes, no help ;)

      Rating: 5
I have a couple
      of problems, myself  June 13, 2003
Reviewer:
      A reader
Could you get out your crystal ball and fix them, please? Seriously.

June 13, 2003 -- Thanks for the question regarding "De-duping
      customers", version 8.1.7

      You Asked
I am going thru a typical de-duping exercise for the customer table. i.e.
records having the same customer attributes but different customer id.

select a.cust_no,b.cust_no,a.last_name,a.first_name
from cust a, cust b
where a.last_name=b.last_name
and a.first_name=b.first_name
and a.cust_no!=b.cust_no

Of course, if there are 5 dupes for cust no C1, I will get

c1 c2
c1 c3
c1 c4
c1 c5
c1 c6

followed by
c2 c1
c2 c3
c2 c4
c2 c5
c2 c6

and so on.

i.e. each dupe is repeated :-(

Is there a way, using user-defined types and the CAST, multiset, magic to get
the following

c1 (c2,c3,c4,c5,c6) last name, first_name
i.e. all the dupes in one comma-separated column or something and not repeated
over and over.

Thanks

      and we said...
ops$tkyte@ORA817DEV> create or replace type myTableType as table of number
  2  /

Type created.

ops$tkyte@ORA817DEV> column empnos format a50
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select job, cast( multiset( select empno
  2                                from emp e2
  3                               where e2.job = e1.job ) as myTableType )
empnos
  4    from (select distinct job from emp) e1
  5  /

JOB       EMPNOS
--------- --------------------------------------------------
ANALYST   MYTABLETYPE(7788, 7902)
CLERK     MYTABLETYPE(7369, 7876, 7900, 7934)
MANAGER   MYTABLETYPE(7566, 7698, 7782)
PRESIDENT MYTABLETYPE(7839)
SALESMAN  MYTABLETYPE(7499, 7521, 7654, 7844)

Assume JOB = your first/last name
Review &
      Followup

      Rating: 5
use min / max
      for removal of duplication  June 09, 2003

Reviewer:  Yogesh Purabiya  from India
select a.cust_no,b.cust_no,a.last_name,a.first_name
from cust a, cust b
where a.last_name=b.last_name
and a.first_name=b.first_name
and a.cust_no!=b.cust_no

Just change the last line as

and nvl(a.cust_no,-1)  <   nvl(b.cust_no,-1)

          Followup:

and that'll answer his question?

      Rating: 1
missing
      !!!!  June 09, 2003
Reviewer:  Sajid Anwar
       from London
Where is the use of MIN/MAX in your example, Mr.Yogesh?

      Rating:
      3
  June 09, 2003
Reviewer:
      A reader
The 'job' in my case is about 6 fields (first_name, last_name, date_of_birth,
middle_initial, ssn, city, etc).

How can I modify your example to accomodate this?

select job, cast( multiset( select empno  from emp e2
where e2.job = e1.job ) as myTableType ) empnos
from (select distinct job from emp) e1

Replace job with first_name||last_name||...?

Thanks

          Followup:

select a,b,c,d,e, cast ( multiset ( select empno from emp e2
                                      where e2.a = e1.a and e2.b = e1.b .... )
from (select distinct .... )

beware of NULLS, they'll complicate matters.  make the predicate "harder"

      Rating:
      3
  June 10, 2003
Reviewer:
      A reader
create or replace type myTableType as table of number;

If I change this to

create or replace type myTableType as table of emp.emp_no%type;

and run your query, I get ORA-22907.

Why is this? They seem identical, just the latter doesnt hard-code the datatype
in the user-defined type and uses the emp.emp_no%type syntax. Thanks

          Followup:

because OBJECT TYPES, SQL TYPES do not understand PLSQL syntax.

You cannot create a type based on a table -- although you can create a table
based on a type.

      Rating:
      2
  June 13, 2003
Reviewer:
      A reader
Hm. This wont work. I want only the duplicates, not everything.

In your example, I get

PRESIDENT MYTABLETYPE(7839)

This is not a dupe, so it should not be returned.

i.e. your query doesnt have the 'a.cust_no != b.cust_no' that my original query
had.

Ideas? Thanks

          Followup:

ops$tkyte@ORA817DEV> select job, cast( multiset( select empno
  2                                from emp e2
  3                               where e2.job = e1.job) as myTableType ) empnos
  5    from (select job from emp group by job having count(*) > 1 ) e1
  6  /

JOB       EMPNOS
--------- --------------------------------------------------
ANALYST   MYTABLETYPE(7788, 7902)
CLERK     MYTABLETYPE(7369, 7876, 7900, 7934)
MANAGER   MYTABLETYPE(7566, 7698, 7782)
SALESMAN  MYTABLETYPE(7499, 7521, 7654, 7844)

June
      13, 2003 john -- Thanks for the
      question regarding "trigger logic", version 8.0.5

      You Asked
Tom:

I have the following trigger below that works. However I would like to
incorporate additional logic into the trigger to check some conditions in a
table and pass back a result set. What would be the most efficient way to do
this.

Current Trigger Body:

begin

 IF :new.AUDITCODE IN ( 'CRFRM', 'SCAND','PTCTL','FAX') THEN

        insert into FOCUS_LOG
( AUDITSEQUENCE,DATETIME,F_DOCNUMBER,FOLDERID,
 FOLDERTYPE,AUDITCODE,USERID,WORKSTATIONID,
 ACTUALLOCATION,PROCESSLOCATION,DOCTYPE,SUBQUEUECODE,
 TRANSACTIONDATETIME,TEXT,WORKITEMID,DOCNUMBER,
 BATCHIDENTIFIER,DOCIDENTIFIER,LAPSEDTIME,USERPOSITION,
 EFORMID,DIDNUMBER)
values
( :new.AUDITSEQUENCE,:new.DATETIME,:new.F_DOCNUMBER,:new.FOLDERID,
 :new.FOLDERTYPE,:new.AUDITCODE,:new.USERID,:new.WORKSTATIONID,
 :new.ACTUALLOCATION,:new.PROCESSLOCATION,:new.DOCTYPE,:new.SUBQUEUECODE,
 :new.TRANSACTIONDATETIME,:new.TEXT,:new.WORKITEMID,:new.DOCNUMBER,
 :new.BATCHIDENTIFIER,:new.DOCIDENTIFIER,:new.LAPSEDTIME,:new.USERPOSITION,
 :new.EFORMID,:new.DIDNUMBER);
 END IF;

END;

I would like to do something like this

IF :new.AUDITCODE IN (select auditcode from audit_code where
logtofocus=1) THEN

insert etc.....

How can I get the result sets back from this select and use it in the IF
CONDITION...

Thanks

John Nieblas

      and we said...
    for x in ( select *
                 from dual
                where :new.auditcode in ( select .... ) )
    loop
        .....

that loop will execute once if the IN is satisifed, zero times otherwise.

Review & Followup

      Rating:
      5
  June 15, 2001
Reviewer:  John
      Nieblas  from Torrance, Ca (Toyota Motor Sales Corp)
Tom:

Thank you very much, this saved me the time of creating a  sp or package to be
called from my trigger!

      Rating: 4
Trigger
      Question Similiar to this one  June 12, 2003

Reviewer:  Deanna  from SFO
What would happen in this scenario

Two tables: Table A and Table B
Users can insert, update or delete in Table A
If they update, or delete a row in Table A then that row has to be updated or
deleted in Table B based on the unique field (Field1)

If the user inserted a new row into Table A then that row appends to Table B.
(ie Field 1 was not in Table B)

I have very little experience writing triggers, I started by using static
cursors in the declare section and then opening these cursors in the begin
section... looping and comparing values... but I am not sure I am approaching
this correctly.

How would you suggest handling this situation?
Thank you for your time and effort
Deanna

          Followup:

i would just

create view B as select * from A;

unless I'm missing something?  sounds alot like "A=B"

      Rating: 3
Intermedia
      Table  June 13, 2003
Reviewer:  Deanna
       from SF
Thanks for the reply, its greatly appreciated

Table B is an intermedia table while table A is just an application table.
While I could replace all the data in Table B as A is updated, I would loose
whatever indexing was set up for the rest of the files.  So, I was thinking if I
just either appended to the table the new records and updated the old records I
could then just index the changed files?

          Followup:

what is an "intermedia table" ?

      Rating: 3
Intermedia
      Table  June 13, 2003
Reviewer:  Deanna
       from SF
The Intermedia table is for Oracle Intermedia Text... It contains all the url
links to files on another server so that I can use PLSQL and the contains
function to search for Keywords within those files. Table B contains both the
url links for intermedia as well as information from table A for the document
retrieval based on keywords.

          Followup:

why isn't that in table a?

I'm confused, show me the model.  why is there more then one table here?

      Rating:
      3
  June 13, 2003
Reviewer:  Deanna
       from SF
Table A is not the model because I had to store the URL for Intermedia.  The
developers of the application did not want additional columns made to the
application table for reporting purposes (they wanted to keep the two separate)
 In addition, the reports have version numbers... they start out with version 1,
and if the users publish another report with the same name a version 2 is
assigned in Table A with a new report_id (PK) and a new row.  Therefore the
actual number of records that would be indexed and searched would be much higher
in Table A as opposed to Table B

Table B retrieves the report name from table A (taking into account the max
version to avoid replication), builds the URL link and stores it, then pulls a
few pieces of information from 2 other tables such as peoples names.
An intermedia index is then created against the URL for report retrieval.

          Followup:

ok, well the trigger is very straightforward

create trigger ....
after insert or update or delete on A for each row
begin
  if ( updating or inserting )
     update b set ..... where field = :new.unique_field;
  else
     delete from b where field = :old.unique_field;
  end if;
end;

      Rating: 5
Thank
      you  June 14, 2003
Reviewer:  Deanna
       from SF
As always thanks for all your help!

June 14, 2003 Shabbir -- Thanks for the question regarding "How
      do I execute an executable from PLSQL", version

      You Asked
How do I execute an executable
from a PL/SQL code (Without using the
SQL*Plus "HOST" command)?

      and we said...
In Oracle7 -- you cannot execute another program from PLSQL.
You can 'talk' to an already running program (which in turn can
execute another process).  See
http://osi.oracle.com/~tkyte/plex/index.html
for an idea on how this
works -- we can use dbms_pipe to send messages to another
session and have it do something for us.

In Oracle8 -- this feature is called an external procedure and
is quite easy to implement.  You would code your stored
procedure in C, the C can use a system() command for example to
run another process.

In Oracle8i, release 8.1 -- a java stored procedure can run
external processes as well without having to code C.

In unix, you can do something like this as well (make sure you
understand the security implications here -- run host.csh as a
very Non-priveleged user!)

This is a quick and dirty daemon -- written in csh (the cool
shell)..

Here is a PL/SQL subroutine you can install in your schema:

create or replace procedure host( cmd in varchar2 )
as
    status number;
begin
    dbms_pipe.pack_message( cmd );
    status := dbms_pipe.send_message( 'HOST_PIPE' );
    if ( status <> 0 ) then raise_application_error( -20001,
'Pipe error' );
    end if;
end;
/

Here is a C-Shell script you can run in the background (use this
shell script make sure it is named host.csh)

-------------------- bof ----------------------------
#!/bin/csh -f

sqlplus tkyte/tkyte <<"EOF" | grep '^#' | sed 's/^.//' > tmp.csh

set serveroutput on

declare
        status  number;
        command varchar2(255);
begin
        status := dbms_pipe.receive_message( 'HOST_PIPE' );
        if ( status <> 0 ) then
                dbms_output.put_line( '#exit' );
        else
                dbms_pipe.unpack_message( command );
                dbms_output.put_line( '##!/bin/csh -f' );
                dbms_output.put_line( '#' || command );
                dbms_output.put_line( '#exec host.csh' );
        end if;
end;
/
spool off
"EOF"

chmod +x tmp.csh
exec tmp.csh
----------------------- EOF ---------------------------------

If you run this in the background (The script), you'll be able
to have it
execute any host command you want.  Run this in one window for
example and in
anther window go into sql*plus and try:

SQL> exec host( 'ls -l' );
SQL> exec host( 'uptime' );
SQL> exec host( 'echo Hello World' );
SQL> exec host( 'exit' );

You'll see the output of ls -l, uptime, and echo happen on the
other window
where the shell script is running (shows you a way to debug
pl/sql routines, use
"host( echo some string )" and you'll get real time feedback
from your pl/sql
procedure).....

Review &
      Followup

      Rating:
      4
  March 20, 2001
Reviewer:  alex
       from Moscow, Russia

      Rating:
      3
  March 25, 2001
Reviewer:
      A reader

      Rating:
      4
  May 11, 2001
Reviewer:  Shunan
      Xiang  from Rochester, NY

      Rating:
      4
  May 24, 2001
Reviewer:  Hossam Eldin
      Emam  from EGYPT
thank than I need

      Rating: 5
Bloody
      marvelous  June 07, 2001
Reviewer:  Andrew
      Fyfe  from Ottawa, Ontario, Canada
Solved my FTP problem, and has potential for hundreds of other uses!

      Rating: 5
you daaa
      maan!!!  June 20, 2001
Reviewer:  Prakash
       from Memphis, TN USA
This is cool and awesome and ....
Now I view my debug messages realtime!!!

      Rating:
      4
  June 22, 2001
Reviewer:  MaxU
       from Munich, Germany

      Rating: 5
Why didn't
      Oracle document this years ago?  August 23, 2001

Reviewer:  Colin Davies  from Los Angeles,
      California, USA
Tom delivers again!  This is SO useful!  Up until now, I have used a Perl daemon
listening to the pipe, but not all systems have Perl installed.  Now I just have
totranslate it into the Korn and Bourne shells...

      Rating:
      4
  September 17, 2001
Reviewer:
      A reader

      Rating:
      4
tom  September 21, 2001
Reviewer:
      ray
I want to use a similar kind of process .

I have to select from dbms_jobs make this an executable. And from another
session  I want to check peridically if there are any broken jobs.

How can I do it , kindly give the answeri n steps , I had been looking for this
functionality for a long time now, but ......

      Rating:
      5
  September 25, 2001
Reviewer:  Sue
       from Ottawa, Canada
Great example of exactly what I'm trying to do.  Very helpful.

      Rating:
      4
  November 20, 2001
Reviewer:  Richard
      Lee  from Toronto, ON, Canada
Could you please supply detail information on how to run under Windows NT +
Oracle8i(8.1.5), like how to execute ".exe" from PL/SQL? Or where I can find
related information?

Thanks
Richard

      Rating: 4
Totally off the
      point but worth a read  November 28, 2001

Reviewer:  A reader  from ca, usa
The famous csh bashing document...

http://www.perl.com/language/versus/csh.whynot

      Rating: 5
Excellent
      Tip  April 30, 2002
Reviewer:  kaustubh
       from Detroit, MI
The code in example works verbatim. Excellent tip that can be used for lot of
useful things...

      Rating:
      5
Excellent  June 03, 2002
Reviewer:
      Tom  from USA
Tom:

It's an excellent alternative for doing things.  However, I received an error
when I tried to compile the stored procedure to test out the process:

Processing ...
(1):PLS-00201: identifier 'SYS.DBMS_PIPE' must be declared
(2):PL/SQL: Statement ignored
(3):PLS-00201: identifier 'SYS.DBMS_PIPE' must be declared
(4):PL/SQL: Statement ignored

Procedure HOST compiled with errors; status INVALID

What do I have to do to fix the errors.  Please help.

Thanks,
Tom

          Followup:

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html

      Rating: 5
Great
      tip!  June 03, 2002
Reviewer:  Lm  from
      USA
Another follow-up:

If I want to write all output messages to a log file in the Unix directory where
the host.csh script is running, how would I modify the host.csh script?  Please
help since I am really new to Unix.

Thanks a lot.
Lm

          Followup:

well, "message" isn't known to the sh script -- it is only available in the
PLSQL.  So, I would use UTL_FILE to log that message to a known directory -- i
wouldn't mess with the sh script at all.

      Rating: 5
Great
      Tip  June 04, 2002
Reviewer:  Senthil
       from Qatar
Very Good tip can be used in many calls to call OS command

      Rating: 5
external
      procedures  January 15, 2003
Reviewer:
      seshadri  from India
hi
 Tom
 thanks for th wonderful support u have been extending, I have a similar qstn,
trying to run a .VBS files from a Procedure and not able to do can you please
guide me on this ?
 Oracle 8i on Win-nt is the Environment

          Followup:

i don't even know what a 'vbs' file is (beyond a carrier of viruses)...

but if it is an "executable", you can use

o java to run it.
o c to run it

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:952229840241

      Rating: 5
Really helpful
      -- why isn't this documented Larry??  January 15, 2003

Reviewer:  Jonathan
VBS - virus bearing script

      Rating: 5
need
      explaination  January 27, 2003
Reviewer:
      steve  from NYC, USA
Hi Tom,

I don't understand the following part
"| grep '^#' | sed 's/^.//' > tmp.csh "
 and
dbms_output.put_line( '##!/bin/csh -f' );
dbms_output.put_line( '#' || command );
dbms_output.put_line( '#exec host.csh' );

Could you please explain for me?
How do I port this to kshell ?
can I just replace dbms_output.put_line() in the following answer to  get the
same result?

http://asktom.oracle.com/pls/ask/f?p=4950:8:1965910::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:2003580269332,%7Bksh%7D
Thanks!

Steve

          Followup:

the grep keeps lines that begin with #
the sed gets rid of the first character of a line (a # sign in this case)

so the dbms_output puts out:

##!/bin/csh -f
#command
#exec host.csh

which the grep grabs and sed filters out the first character -- thats all..

as for porting to ksh -- c/csh/ksh/ and you are done.

      Rating: 5
try to run the
      scripts  January 28, 2003
Reviewer:  Steve
       from NYC, USA
Hi Tom,

I try to run the script you give to us.
1) host.ksh:  I just changed csh to ksh.
-------
#!/bin/ksh

sqlplus srs/srs <<"EOF" | grep '^#' | sed 's/^.//' > tmp.ksh

set serveroutput on

declare
        status  number;
        command varchar2(255);
begin
        status := dbms_pipe.receive_message( 'HOST_PIPE' );
        if ( status <> 0 ) then
                dbms_output.put_line( '#exit' );
        else
                dbms_pipe.unpack_message( command );
                dbms_output.put_line( '##!/bin/ksh' );
                dbms_output.put_line( '#' || command );
                dbms_output.put_line( '#exec host.ksh' );
        end if;
end;
/
spool off
"EOF"

chmod +x tmp.ksh
exec tmp.ksh

--------

2) creat host procedure.
3) I run host.ksh& and SQL>exec host('ls -l');
   Nothing shows up in the window where I run host.ksh&
   and I got tmp.ksh generated but with attributes
   -rw-rw-rw- (no x?, even though I modify 'chmod 777   tmp.ksh' in host.ksh )
4) the content of tmp.ksh:
   ls -l
   exec host.ksh
5) if I mannually run following command
   exec tmp.ksh
   I got files listed and xwindow closed!

Do I miss something ?

Thanks

Steve

          Followup:

put it back to csh i guess.  there is no logical reason on the planet to make
it a ksh script

besides, you didn't ask me to convert that to ksh - you asked about the other.

why not just leave it as is (or understand exactly what it does and code in in
ksh by hand yourself)

      Rating: 5
Pass SQL*Plus
      Variable Back  February 08, 2003
Reviewer:
      Jerry  from Washington, DC USA
Is there a way to pass the value of a plus variable back to the shell?

I know in this case that dbms_application_info is more appropriate, but just as
an example:

declare
        l_rec t%rowtype;
begin
     for i in 1 .. 200
     loop
        select * into l_rec from t;
        if mod(i,10) = 0 then
           host('echo 10 records ');  -- echo value of i
        end if;
     end loop;
end;

          Followup:

plsql is running on the server -- under Oracle.  that echo would go to the great
bit bucket in the sky.  there is no "terminal" for it to be echoed to.

If you want to have "real time" output from PLSQL -- you would

o use pipes, have a process you run on your terminal read from a pipe and print
out what it reads.  your plsql would write to the pipe

o use utl_file, have plsql write to a file on the server file filesystem and do
a tail -f on it

o use jdeveloper and run plsql in a debugger

o use dbms_application_info and the v$session_longops view

o write rows into a "message table" using an autononmous transaction

to name the first couple that come to mind.

      Rating: 4
SQL * PLUS
      HOST  June 14, 2003
Reviewer:  Reddy
       from Bangalore, India
Hi Tom
after a long time, I am asking this question.

You have an example how to call HOST command in UNIX environments. Is it
possible to run in NT environments? if so how?

Thanks in advance

Reddy

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:952229840241

      Rating:
      2
  June 14, 2003
Reviewer:
      A reader
Your window closed after running the script because you have

exec tmp.ksh

Anytime you run anything with 'exec' from the command-line, you are replacing
the current shell. So when tmp.ksh exits, you no longer have a shell so out you
go.

In other words, if you do

exec ls -l

from the command line, your window will be closed after the file listing
finishes.

June 14, 2003 balraj -- Thanks for the question regarding
      "Auditing -- where are the records visible and how to find out whats
      enabled.", version 8.1.6.3

      You Asked
Hi Tom,

When i have enabled the audting by the AUDIT Statements......

How/Where should I check the Audited information....?

Actually Oracle gives me these Tables/Views/Synonymns

1)Aud$    2)DBA_Audit_Objcet   3)Audit$.. and many more

I want to know once I had Given Auditing command Where should i go to check the
Auditd information..

Pl.Answer me in detail as how should I check the information and where should I
delete the information after the Information is checked

Also I need to understand that DBA_AUDIT_OBJECT has a field

STATEMENTID

Where will i find more information about the Statement issued by the user..

I want to get the information of all the Events Audited in the database ,where
will I find this

Thanks for The Answer in advance.
balraj

      and we said...
to review the contents of the audit trail:

DBA_AUDIT_EXISTS    lists audit trail entries produced by AUDIT NOT EXISTS.
DBA_AUDIT_OBJECT    contains audit trail records for all objects in the system.
DBA_AUDIT_SESSION   lists all audit trail records concerning CONNECT and
                    DISCONNECT.
DBA_AUDIT_STATEMENT lists audit trail records concerning GRANT, REVOKE,
                    AUDIT, NOAUDIT, and ALTER SYSTEM statements throughout the
                    database.
DBA_AUDIT_TRAIL     lists all audit trail entries.

DBA_OBJ_AUDIT_OPTS   - describes auditing options on all objects.
DBA_PRIV_AUDIT_OPTS  - describes current system privileges being audited across
                       the system and by user.
DBA_STMT_AUDIT_OPTS  - describes current system auditing options across the
                       system and by user.

followup to comment

the statementid doesn't tell you that.

the ACTION_NAME does.

If you are looking for the "actual statement" executed -- you can stop looking.
that is not recorded in the audit trail.

Review &
      Followup

      Rating: 3
Was bit
      incomplete  June 04, 2001
Reviewer:
      A reader
It was helpful

but the last part was missed

I asked How could i know the SQL Statement against the staementID mentioned in
the DBA_AUDIT_Object View

Pl.tell this also

      Rating: 3
Why Oracle cant
      audit DROP TABLE or DROP ANY TABLE etc DDL!  January 17,
      2002
Reviewer:  A reader  from spain
Hi Tom

I am trying to audit DDL in some schemas, for example create, drop, alter

I have read through the documentation in administration guide and sql reference

I tried this

1.- audit table
from sql ref it suppose to include auditing of
CREATE TABLE
DROP TABLE
TRUNCATE TABLE
and from Concepts, I am quoting here: DDL statements, regarding a particular
type of database structure or schema object, but not a specifically named
structure or schema object (for example, AUDIT TABLE audits all CREATE and DROP
TABLE statements), however it is auditing CREATE TABLE correctly but NOT DROP
TABLE (btw it is always auditing CREATE TABLE even I get rid of all audits, all
DBA_STMT_AUDIT_OPTS, DBA_PRIV_AUDIT_OPTS, DBA_AUDIT_OBJECT, DBA_AUDIT_SESSION,
DBA_AUDIT_STATEMENT are empty, I dont understand why!!!)

2.- audit alter table
This suppose to audit every alter table command as far as I can understand from
documentation since it's in Object Auditing Options (Table 8-3 from 8.1.7 SQL
ref) however it is NOT working, it doesnt audit anything at all

3.- I thought I should try privilege auditing since I am auditing people with
DBA Role as well so I did a small test after my desperation with statement
auditing:

audit drop any table

not working neither unless this works like PL/SQL, the guy needs this priv
directly instead through a role but I really doubt that?

basically what I am trying to accomplish is statement auditing rather than
privilege or object auditing but it getting frustrating, nothing works except
CREATE TABLE (I cant even get rid of it)! Metalink's helpless, been searching
the whole afternoon without success. The documentation is somehow confusing so I
wish you can give me a hand

Cheers

          Followup:

Not knowing exactly what you did -- I cannot point out what you did wrong.
However, I can show you how to do this correctly.

I simply

a) set audit_trail=true; and restarted.
b) ran the following:

ops$tkyte@ORA817DEV.US.ORACLE.COM> @connect "/ as sysdba"
ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout off
sys@ORA817DEV.US.ORACLE.COM> GET afiedt.buf NOLIST
sys@ORA817DEV.US.ORACLE.COM> set termout on
sys@ORA817DEV.US.ORACLE.COM>
sys@ORA817DEV.US.ORACLE.COM> truncate table aud$;

Table truncated.

sys@ORA817DEV.US.ORACLE.COM> audit table by access;

Audit succeeded.

sys@ORA817DEV.US.ORACLE.COM> audit alter table by access;

Audit succeeded.

sys@ORA817DEV.US.ORACLE.COM>
sys@ORA817DEV.US.ORACLE.COM> @connect /
sys@ORA817DEV.US.ORACLE.COM> set termout off
ops$tkyte@ORA817DEV.US.ORACLE.COM> GET afiedt.buf NOLIST
ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout on
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table audit_tab ( x int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select username, action_name from
dba_audit_trail;

USERNAME                       ACTION_NAME
------------------------------ ---------------------------
OPS$TKYTE                      CREATE TABLE

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table audit_tab add y date;

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select username, action_name from
dba_audit_trail;

USERNAME                       ACTION_NAME
------------------------------ ---------------------------
OPS$TKYTE                      CREATE TABLE
OPS$TKYTE                      ALTER TABLE

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select username, action_name from
dba_audit_trail;

USERNAME                       ACTION_NAME
------------------------------ ---------------------------
OPS$TKYTE                      CREATE TABLE
OPS$TKYTE                      ALTER TABLE
OPS$TKYTE                      DROP TABLE

As you can see -- the create and drop (covered by the AUDIT TABLE BY ACCESS) as
well as the ALTER (covered by the AUDIT ALTER TABLE BY ACCESS) were recorded.

I can only assume that in your attempts -- you entered many different audit
commands and that is why the create table audit is hanging about.  To see what
is in effect:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from dba_priv_audit_opts;

no rows selected

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from dba_stmt_audit_opts;

USER_NAME                      PROXY_NAME                     AUDIT_OPTION
                      SUCCESS    FAILURE
------------------------------ ------------------------------
---------------------------------------- ---------- ----------
                                                              ALTER TABLE
                      BY ACCESS  BY ACCESS
                                                              TABLE
                      BY ACCESS  BY ACCESS

that shows I have table and alter table and if I:

ops$tkyte@ORA817DEV.US.ORACLE.COM> @connect "/ as sysdba"
ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout off
sys@ORA817DEV.US.ORACLE.COM> GET afiedt.buf NOLIST
sys@ORA817DEV.US.ORACLE.COM> set termout on
sys@ORA817DEV.US.ORACLE.COM>
sys@ORA817DEV.US.ORACLE.COM> noaudit table;

Noaudit succeeded.

sys@ORA817DEV.US.ORACLE.COM> noaudit alter table;

Noaudit succeeded.

sys@ORA817DEV.US.ORACLE.COM> truncate table aud$;

Table truncated.

sys@ORA817DEV.US.ORACLE.COM>
sys@ORA817DEV.US.ORACLE.COM> @connect /
sys@ORA817DEV.US.ORACLE.COM> set termout off
ops$tkyte@ORA817DEV.US.ORACLE.COM> GET afiedt.buf NOLIST
ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout on
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table audit_tab ( x int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select username, action_name from
dba_audit_trail;

no rows selected

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table audit_tab add y date;

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select username, action_name from
dba_audit_trail;

no rows selected

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select username, action_name from
dba_audit_trail;

no rows selected

I'm no longer auditing.

      Rating: 5
A couple of
      questions more  January 18, 2002
Reviewer:
      A reader  from spain
Hi

I am doing this on Oracle 8.1.7 on Windows platform, it now works I dont know
why...

My first question is, what is the differewnce between audit_trail=db and true???
In the documentation it suggests use none, db or os for audit_trail but I see
your are using a boolean...

Second question is, in order to audit I must logon as sysdba?

The only differences between my tests and yours are these two, I used db instead
of true for audit_trail parameter and I did not logon as sysdba (I did login as
SYS but not with sysdba privs)

Finally, is it possible to audit a user like

audit table by user_name by session whenever successful

or we have to audit by access

Thanks a lot as usual

          Followup:

true = db, they are the same.

You need not logon as SYSDBA, you just need the appropriate privs.

Auditing by SESSION will only get you one audit per session per action ( alter 5
times, one audit record).  By access would get you 5 records -- thats the
difference.

Here is another example using circumstances like yours:

SQL*Plus: Release 8.1.7.0.0 - Production on Sat Jan 19 09:56:01 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.2.0 - Production
With the Partitioning option
JServer Release 8.1.7.2.0 - Production

ops$tkyte@ORA817DEV.US.ORACLE.COM> set echo on
ops$tkyte@ORA817DEV.US.ORACLE.COM> show parameter audit_trail

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
audit_trail                          string  DB

ops$tkyte@ORA817DEV.US.ORACLE.COM> @connect sys/manager

sys@ORA817DEV.US.ORACLE.COM> truncate table aud$;

Table truncated.

sys@ORA817DEV.US.ORACLE.COM>
sys@ORA817DEV.US.ORACLE.COM> audit table by access;

Audit succeeded.

sys@ORA817DEV.US.ORACLE.COM>
sys@ORA817DEV.US.ORACLE.COM> audit table by ops$tkyte by session whenever
successful;

Audit succeeded.

sys@ORA817DEV.US.ORACLE.COM>
sys@ORA817DEV.US.ORACLE.COM> @connect /

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table audit_tab ( x int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select username, action_name from
dba_audit_trail;

USERNAME                       ACTION_NAME
------------------------------ ---------------------------
OPS$TKYTE                      CREATE TABLE

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table audit_tab add y date;

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select username, action_name from
dba_audit_trail;

USERNAME                       ACTION_NAME
------------------------------ ---------------------------
OPS$TKYTE                      CREATE TABLE
OPS$TKYTE                      ALTER TABLE

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select username, action_name from
dba_audit_trail;

USERNAME                       ACTION_NAME
------------------------------ ---------------------------
OPS$TKYTE                      CREATE TABLE
OPS$TKYTE                      ALTER TABLE
OPS$TKYTE                      DROP TABLE

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

As you can see -- audit_trail = db, connected as SYS.

      Rating: 5
thank
      you  January 19, 2002
Reviewer:
      A reader
thank you very much, last problem... I copied ands pasted your statement as
follows

sys@DEV817>r
  1  audit table by lsc by session whenever
  2* successful

Audit succeeded.

sys@DEV817>r
  1* select * from dba_stmt_audit_opts

USER_NAME            PROXY_NAME AUDIT_OPTION   SUCCESS    FAILURE
-------------------- ---------- -------------- ---------- ----------
LSC                             TABLE          BY ACCESS  NOT SET

It says by access.. :-(

          Followup:

Sorry, should have pointed out (cut and pasted from the SQL ref manual):

BY ACCESS

causes Oracle to write one record for each audited statement.

If you specify statement options or system privileges that audit data definition
language (DDL) statements, Oracle automatically audits by access regardless of
whether you specify the BY SESSION clause or BY ACCESS clause.

For statement options and system privileges that audit SQL statements other than
DDL, you can specify either BY SESSION or BY ACCESS. BY SESSION is the default.

      Rating: 4
COMMANDS_TEXT
      Column  May 13, 2002
Reviewer:  Irfan
       from Kuwait
dba_audit_object and dba_audit_trial shows the actions if auditing is enabled.
Please correct me, the column COMMENT_TEXT, does it stores all the commands
typed by the user in SQL*Plus like for example, if a user inserts a row in a
table. If yes, this column is always blank and ACTION_NAME column is filled in.
Else can you please help me finding where can I get the full information of the
row inserted (including vlaues).
Thanks

          Followup:

We do not audit to that level -- we do not capture the actual statement or
values.

You would code a custom trigger to capture that information.

      Rating:
      5
comment_text  May 13, 2002
Reviewer:
      Irfan  from Kuwait
If I use trigger to write to that column, will the audit table show two entries
for the same statement. One from the application (trigger) and another from the
system directly.

          Followup:

You will NOT be writing to that column.

You will design your own audit trail tables.

      Rating: 4
From Admin
      Guide  May 13, 2002
Reviewer:  Irfan
       from Kuwait
Tom,
The Oracle Server Administrator's Guide in Appendix B has the details of Data
Dictionary. From that
For dba_audit_object, dba_audit_statement, dba_audit_trail

COMMENT_TEXT      Text comment on the audit trail entry
                  (inserted by an application program)

-- what does 'inserted by an application program' means?
because you say 'You will NOT be writing to that column'

Thanks.

          Followup:

Don't know what they were trying to say there.  I had to go back to 7.3 to even
find a server admin guide with an appendix and that one only has an appendix A,
no B.

Refer to:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76961/toc.htm
under static data dictionary views.  This text does not appear.

      Rating: 3
Same in
      8i  May 13, 2002
Reviewer:  Irfan  from
      Kuwait
Tom,
Thanks for the quick help. But when I refer to 8i docs for data dictionary, for
example dba_audit_statement has the column

COMMENT_TEXT   VARCHAR2(4000) - Text comment on the audit trail, inserted by the
application

How this column can be best utilized? Thanks again.

          Followup:

Sorry -- i read dba_audit_trail.

It is nothing you can influence.  ORACLE is the application.  You can reference
the data in this column, you cannot influence the contents of it.

      Rating: 4
Audit
      Trail  May 15, 2002
Reviewer:  Rukshan Soza
       from UK
Tom,

Once init.ora parameter audit_trail = true is set does it automatically start
auditing ?. Or do you have to then manually start auditing (eg: audit session) ?

          Followup:

audit_trail = true enables auditing

you have to then use the audit command to tell us what to audit.

      Rating: 4
Overhead of
      audit  May 23, 2002
Reviewer:  Jan van
      Mourik  from Houston, TX
Tom,

how much overhead does auditing put on the database? If I do "audit session",
would that have any measurable negative impact on my database performance?

jan

          Followup:

benchmark it -- you'll be pleasantly surprised.

Just use statspack, measure your performance now.

enable it, run with it, use statspack again

compare the results.

      Rating:
      4
Reader  July 26, 2002
Reviewer:
      A reader
Tom,

SQL> connect user1
Enter password:
Connected.
SQL> grant create any procedure to user2;

Grant succeeded.

SESSIONID                     : 4843963
ENTRYID                       : 2
STATEMENT                     : 8
TIMESTAMP#                    : 2002-07-25 16:02:08
USERID                        : USER1
USERHOST                      :
TERMINAL                      : ttyq5
ACTION#                       : 108
RETURNCODE                    : 0
OBJ$CREATOR                   :
OBJ$NAME                      :
AUTH$PRIVILEGES               : -
AUTH$GRANTEE                  : USER2
NEW$OWNER                     :
NEW$NAME                      :
SES$ACTIONS                   :
SES$TID                       :
LOGOFF$LREAD                  :
LOGOFF$PREAD                  :
LOGOFF$LWRITE                 :
LOGOFF$DEAD                   : 141
LOGOFF$TIME                   :
COMMENT$TEXT                  :
SPARE1                        : oracle8
SPARE2                        :
OBJ$LABEL                     :
SES$LABEL                     :
PRIV$USED                     :

ACTION#         => 108  =>  "SYSTEM GRANT" from audit_actions;

LOGOFF$DEAD     => 141  =>  "CREATE ANY PROCEDURE" from
                            system_privilege_map
Q1:
This is a bit strange that 141 is the correct code
for System Privelege Used but it is in LOGOFF$DEAD. Is this
a BUG .

Q2:
What is the significance of ENTRYID, STATEMENT.

Thanks

          Followup:

Looks like you are looking right as sys.aud$

Don't query the data dictionary tables like that.  Use the supplied DBA_ views.
The aud$ table is not documented for you or me, hence you can draw no
conclusions based on that.  What is important is -- do the views supplied to you
supply the correct information.  If so, no bug.

      Rating: 5
Action
      Name  November 17, 2002
Reviewer:  Frank
       from Canada
Hi Tom,

The action_name content in DBA_AUDIT_TRAILis mostly self explanatory. However, I
saw one entry "SESSION REC" in my audit trail I couldn't figure out what that
"REC" stands for, "RECEIVED", "RECREATED", ... Is there a place or view I could
go to find all of these details. Thanks.

          Followup:

Action 103 is an audit record inserted in AUD$ table each time a DDL or DML
operation is audited BY SESSION.  It is a session recORD

See support note:
 <Note:208855.1>
on metalink.oracle.com for the long winded and complete answer with examples.

      Rating:
      4
Prudent  May 30, 2003
Reviewer:
      prudent  from USA
I have set the audit_trail=db in init.ora and the database is bounced back.
When I try to audit the user by using AUDIT USER commant, I am not able to see
the auditing information for create user and alter user but I see the
information for drop user in dba_audit_trail.  Is there any other way in which I
can achieve the auditing of create user and alter user.

Thanks

          Followup:

give me a for example, like this:

ops$tkyte@ORA920> show parameter audit_trail

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
audit_trail                          string      DB
ops$tkyte@ORA920> column obj_name format a5
ops$tkyte@ORA920> select username, action_name, obj_name, priv_used from
dba_audit_trail;

no rows selected

ops$tkyte@ORA920> audit user;

Audit succeeded.

ops$tkyte@ORA920> disconnect
Disconnected from Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production
ops$tkyte@ORA920> connect /
Connected.
ops$tkyte@ORA920>
ops$tkyte@ORA920> create user a identified by a;

User created.

ops$tkyte@ORA920> alter user a identified by z;

User altered.

ops$tkyte@ORA920> drop user a;

User dropped.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select username, action_name, obj_name, priv_used from
dba_audit_trail;

USERNAME                       ACTION_NAME                 OBJ_N PRIV_USED
------------------------------ --------------------------- -----
----------------------------------------
OPS$TKYTE                      DROP USER                   A     DROP USER
OPS$TKYTE                      CREATE USER                 A     CREATE USER
OPS$TKYTE                      ALTER USER                  A     ALTER USER

ops$tkyte@ORA920> noaudit user;

Noaudit succeeded.

      Rating: 4
ip
      address  June 13, 2003
Reviewer:  Reader

Tom, is it possible to capture the ip address of the machine by turning audit
on. Say, for example, audit table by access or audit connect and then does
dba_audit_trail capture the ip address of the machine that connected to my
database in client-server configuration?

          Followup:

as long as they use tcp/ip -- the IP address of the client has been captured in
the audit trail since version 7.3 when using audit connect

      Rating: 4
Which
      view?  June 14, 2003
Reviewer:  Reader

Tom, could you please suggest which data dictionary view gives that? I was
looking at dba_audit_trail and could not find that information. May be I could
not see. Thanks as always.

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:526822275627

      Rating: 5
Thanks a
      bunch!  June 14, 2003
Reviewer:  Reader

June 14, 2003 Alfons -- Thanks for the question regarding
      "Dynamic LDAP-Entries with OID dependent on table data?", version 8.1.7

      You Asked
Hy Tom,

I am interested in LDAP with OID and I want to know, if it is possible to make
entries in the OID LDAP-tree dependent on table data. This mean that with
insert, update and delete on a specific table the LDAP-tree can adjust the
entries automatically. I found the following information on
http://www.oracle.com/database/oid/oid3.html
  :

“Another useful functionality is the ability to represent subsets of relational
data as part of an LDAP directory tree, making this information available to any
LDAP client application. This functionality is currently being designed, and
will be available with a future release of the Oracle Internet Directory.”

“Planned enhancements such as SQL-language LDAP interfaces and "projection
views" of relational data into the LDAP directory space will represent first
steps towards true integration of relational and directory data.”

OK - is this possible with the OID 2.1.1 in release 8.1.7, I found no
Information about it nowhere....

Regards
Alfons Chiarello

      and we said...
I went to a product manager of OID to answer this one and he says:

...
RDBMS 8.1.7 includes the new package DBMS_LDAP which enables trusted callout
from any PL/SQL environment (e.g., via triggers or packaged proc) to the OID C
API.  Thus any table of data can
be (loosely) synchronized with entries in OID or other LDAP repository today.

OID 2.1.1 (shipped with and built on top of 8.1.7) supports the IETF-recommended
changelog object specification, which enables controlled synchronization of the
data in OID with any outside
LDAP-enabled source (such as a metadirectory).  Changelog objects are stored
within OID as regular entries.

As it states in the whitepaper, we are continuing to work on tighter mechanisms
of relational/directory integration for future releases of both Oracle9i and
OID.
....

Review &
      Followup

      Rating: 4
Researching
      LDAP  March 21, 2002
Reviewer:  Robert D.
      Ware  from St Louis, Mo
Tom,

Your link does not appear to be working

http://www.oracle.com/database/oid/oid3.html

          Followup:

sorry -- it was from the original questioner - not me.  I don't know what the
link was even pointing to....

the url for ldap stuff would now be:

http://technet.oracle.com/docs/products/oid/content.html

      Rating: 5
What I'd really
      like to know is...  March 21, 2002
Reviewer:
      Connor  from UK
DBMS_LDAP works fine although last time I had a chance to play with it we
observed things like (paraphrased below):

SQL> insert into empty_table values (1);
SQL> "dbms_ldap.add_an_entry_to_hierarchy"
SQL> rollback;

and if memory serves, the insert would rollback but the ldap entry did not.  The
obvious question being "How?".  If I can find another ldap environment I'll
confirm the above.

      Rating: 5
...autonomous
      txn maybe  March 22, 2002
Reviewer:  Connor
       from UK
Just thinking a little more about my previous comment.. Could the dbms_ldap
routines be autonomous?  (Can't detect anything from the wrapped body)

          Followup:

DBMS_LDAP behaves exactly as an autonomous transaction would -- the LDAP
transaction (taking place outside of Oracle) is a single, one off transaction.

One method of supporting "transactions" with dbms_ldap is to schedule the call
to dbms_ldap as a job instead of running it real time (sort of like I propose in
my book with regards to sending email -- don't SEND the email -- schedula a job
to send an email after you commit)...

      Rating: 5
onames to
      OID  June 14, 2003
Reviewer:  Reader

Tom, in our organization, we use onames. Is there a migration path from onames
to OID? if so, could you please point to a link? It will be very helpful. Also,
on the outset, if we migrate from onames to OID, can OID support databases prior
to version 8i? Thanks Tom. You are always the best. Thanks.

          Followup:

It will be client specific as to whether the client code can resolve names via
ldap -- I'm not sure about pre-8i clients, you would want to check with support
as for the compatibility there.  It isn't the database that is the issue, its
the client trying to resolve the database names.

You would be interested in support <Note:158997.1> and

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/network.920/a96574/migratio.htm#1000002031

June 14, 2003 raja -- Thanks for the question regarding "sga
      size", version 8.1.5

      You Asked
I want to change sga size to more than 2gb in  datawarehouse database. shall i
add any specific parameters? apart from
block_buffers,shared_pool_size,log_buffers

Thanks
raja

      and we said...
Well, this is a trick question.

In order for YOU to be able to answer this YOU need to decide which components
of the SGA require the additional ram.

There are other parameters that affect the size of the SGA (virtually all of
them do actually).  Which resource do YOU want to increase.

Could be large_pool_size (used for parallel query, MTS, rman)
Could be java_pool_size (used by java obviously)
Could be shared_pool_size (but that would be really rare in a DW, they are
generally pretty small)
Could be log_buffer (but you are a DW, how much log do you generate?)
Most likely it is db_block_buffers

Now, as you get to the 2gig'ish limit, you'll be hitting certain OS constraints
if you are using 32bit Oracle -- eg: 1.75 gig max on HP/UX, right around 2gig on
Solaris, 2gig'ish on NT and so on.  That I cannot answer here.

I can say -- 815 is not supported or supportable at this point in time.  If you
hit an issue after making a major change like this, there might not be anything
we can do.  You might give serious consideration to upgrading to supported
software at the time you do this (after lots of testing of course)
Review &
      Followup

      Rating: 4
Its possible to
      increase the SGA size limit 1.75GB on 32 bit Solaris (SPARC)
        November 19, 2002
Reviewer:  A reader

I can across the same sitution where I need use more avaiable memory (4GB) on
dedicated database server for our DW. I did changed the attached address
(sgabeg) before installation...Check-out this two docs on metalink ...

Doc : Note:1028623.6
Doc : Note:61896.1
=====
MetaLink Doc Note:1028623.6
Subject: SUN SOLARIS: How to Relocate the SGA

PURPOSE:
This note describes how to relocate the SGA on the Sun SPARC Solaris
operating system in a 32-bit environment.
SCOPE & APPLICATION:
This applies to 32-bit database versions 7.x through 9.0.x on the Sun SPARC
Solaris operating system.
The Solutions described below are:
A. For RDBMS version 9.0.x - How to Change your Attach Address
B. For RDBMS version 8.1.x - How to Change your Attach Address
C. For RDBMS version 8.0.x - How to Change Your Attach Address
D. For RDBMS version 7.3.2.x - How to Change Your Attach Address
E. For RDBMS versions 7.3.3 or 7.3.4 - How to Change Your Attach Address
F. For RDBMS version 7.2.3 or older - How to Change Your Attach Address
If you experience difficulties during the attempt to change the attach
address, simply restore the file backed up as part of the procedure, and
use it to relink. You will then revert to your original attach address.
SUN SOLARIS: How to Relocate the SGA:
=====================================
You need to configure the SGA to a value greater than 256 Mbytes on your
Sun system. You are referring to the following sgabeg parameter chart:
sgabeg = 0xe0000000 | 256Mb SGA
sgabeg = 0xd0000000 | 512Mb SGA
sgabeg = 0xc0000000 | 768Mb SGA
sgabeg = 0xb0000000 | 1Gb SGA
sgabeg = 0xa0000000 | 1.25Gb SGA
sgabeg = 0x90000000 | 1.5Gb SGA
sgabeg = 0x80000000 | 1.75Gb SGA
sgabeg = 0x77000000 | just less than 2Gb SGA
sgabeg = 0x70000000 | 2Gb SGA
sgabeg = 0x60000000 | 2.25Gb SGA
sgabeg = 0x50000000 | 2.5Gb SGA
sgabeg = 0x40000000 | 2.75Gb SGA
sgabeg = 0x30000000 | 3Gb SGA
sgabeg = 0x20000000 | 3.25Gb SGA
sgabeg = 0x10000000 | 3.5Gb SGA
sgabeg = 0x01000000 | 16Mb less than 3.75Gb SGA
A. For RDBMS version 9.0.x - How to Change your Attach Address:
===============================================================
For RDBMS version 9.0.x, change your attach address by doing the following
steps to relocate the SGA:
1. Shutdown any databases using the current "ORACLE_HOME".
2. Change your location to the "/lib" directory
% cd $ORACLE_HOME/lib
3. Make a backup copy of 'libserver9.a'.
% cp libserver9.a libserver9.a.orig
4. Change your location to the "rdbms/lib" directory
% cd $ORACLE_HOME/rdbms/lib
5. Generate the "ksms.s" file
% $ORACLE_HOME/bin/genksms -b <new_location> > ksms.s
For example, changing the attached address starting at
0x80000000 to 0x60000000 would be:
% $ORACLE_HOME/bin/genksms -b 0x60000000 >ksms.s
6. Regenerate the 'ksms.o' object:
% make -f ins_rdbms.mk ksms.o
7. Archive 'ksms.o' into 'libserver9.a'
%ar r $ORACLE_HOME/lib/libserver9.a ksms.o
8. Relink
% make -f ins_rdbms.mk ioracle
B. For RDBMS version 8.1.x - How to Change Your Attach Address:
===============================================================
For RDBMS version 8.1.X , change your attach address by doing the following
steps to relocate the SGA:
1. Shutdown any databases using the current "ORACLE_HOME".
2. Change your location to the "/lib" directory
% cd $ORACLE_HOME/lib
3. Make a backup copy of 'libserver8.a'.
% cp libserver8.a libserver8.a.orig
4. Change your location to the "rdbms/lib" directory
% cd $ORACLE_HOME/rdbms/lib
5. Generate the "ksms.s" file
% $ORACLE_HOME/bin/genksms -b <new_location> > ksms.s
For example, changing the attached address starting at
0x80000000 to 0x60000000 would be:
% $ORACLE_HOME/bin/genksms -b 0x60000000 >ksms.s
6. Regenerate the 'ksms.o' object:
% make -f ins_rdbms.mk ksms.o
7. Archive 'ksms.o' into 'libserver8.a'
%ar r $ORACLE_HOME/lib/libserver8.a ksms.o
8. Relink
% make -f ins_rdbms.mk ioracle
Explanation:
============
Changing the 'sgabeg' in 'ksms.o' will allow you to increase SGA size and
allocate the memory in a contiguous segment.
NOTE: If the above process does not resolve the problem, you may be
using an older version of the Sun Solaris architecture (Sun4c).
If this is the case, repeat all the steps shown above and change
the value for Step 5 to be the same as the old value less one
zero.
For example:
Old Value for Step 5: 0x80000000
New Value for Step 5: 0x8000000
C. For RDBMS version 8.0.x - How to Change Your Attach Address:
===============================================================
For RDBMS version 8.0.x, change your attach address by doing the following
steps to relocate the SGA:
1. Shutdown the database.
2. Change your location to the "rdbms/lib" directory
% cd $ORACLE_HOME/rdbms/lib
3. Modify the "ksms.s" file
% $ORACLE_HOME/bin/genksms -b <new_location> >ksms.s
For example, changing the attached address starting at
0x80000000 to 0x60000000 would be:
% $ORACLE_HOME/bin/genksms -b 0x60000000 >ksms.s
4. Make a backup copy of the "ins_rdbms.mk" file
% cp ins_rdbms.mk ins_rdbms.mk.ksms
5. Modify the "ins_rdbms.mk" file
Change: ksms.s $(RDBMSLIB)ksms.s:
$(GENKSMS) > ksms.s
To: ksms.s $(RDBMSLIB)ksms.s:
$(GENKSMS) > ksms.s.default_sgabeg
6. Regenerate the 'ksms.o' object:
% make -f ins_rdbms.mk ksms.o
7. Relink
% make -f ins_rdbms.mk ioracle
NOTE: If the above process does not resolve the problem, you may be
using an older version of the Sun Solaris architecture (Sun4c).
If this is the case, repeat all the steps shown above and change
the value for Step 3 to be the same as the old value less one
zero.
For example:
Old Value for Step 3: 0x80000000
New Value for Step 3: 0x8000000
NOTE: If the above process does not resolve the problem, you may have to apply
the fix for [BUG:695468]
<ml2_documents.showDocument?p_id=695468&p_database_id=BUG>, fixed in 8.0.4.4.
D. For RDBMS version 7.3.2.x - How to Change Your Attach Address:
=================================================================
For RDBMS version 7.3.2.x, change your attach address by doing the following
steps to relocate the SGA:
1. Shutdown the database.
2. Change your location to the "rdbms/lib" directory
% cd $ORACLE_HOME/rdbms/lib
3. Modify the "ksms.s" file
Change this line:
sgabeg = 0xe0000000
To the new value:
sgabeg = 0x80000000
NOTE: This example moves the attached address from
"0xe0000000" to "0x80000000".
4. Make a backup copy of the "env_rdbms.mk" file
% cp env_rdbms.mk env_rdbms.mk.ksms
5. Modify the "env_rdbms.mk" file
Change: $(ORACLE_HOME)/bin/genksms > $(KSMSS)
To: $(ORACLE_HOME)/bin/genksms > $(KSMSS).sga
6. Regenerate the 'ksms.o' object:
% make -f ins_rdbms.mk ksms.o
7. Relink
% make -f ins_rdbms.mk ioracle
NOTE: If the above process does not resolve the problem, you may be
using an older version of the Sun Solaris architecture (Sun4c) .
If this is the case, repeat all the steps shown above and change
the value for Step 3 to be the same as the old value less one
zero.
For example:
Old Value for Step 3: 0x80000000
New Value for Step 3: 0x8000000
E. For RDBMS versions 7.3.3 or 7.3.4 - How to Change Your Attach Address:
=========================================================================
For RDBMS version 7.3.3 or newer, change your attach address by doing
the following steps to relocate the SGA:
1. Shutdown the database.
2. Change your location to the "rdbms/lib" directory
% cd $ORACLE_HOME/rdbms/lib
3. Modify the "ksms.s" file
% $ORACLE_HOME/bin/genksms -b <new_location> >ksms.s
For example, changing the attached address starting at
0x80000000 to 0x60000000 would be:
% $ORACLE_HOME/bin/genksms -b 0x60000000 >ksms.s
4. Make a backup copy of the "env_rdbms.mk" file
% cp env_rdbms.mk env_rdbms.mk.ksms
5. Modify the "env_rdbms.mk" file
Change: $(ORACLE_HOME)/bin/genksms > $(KSMSS)
To: $(ORACLE_HOME)/bin/genksms > $(KSMSS).sga
6. Make a backup copy of the "ins_rdbms.mk" file
% cp ins_rdbms.mk ins_rdbms.mk.ksms
7. Regenerate the 'ksms.o' object:
% make -f ins_rdbms.mk ksms.o
8. Modify the "ins_rdbms.mk" file
Change: -rm -f ksms.s ksms.o
To: -rm -f ksms.o
9. Relink
% make -f ins_rdbms.mk ioracle
NOTE: If the above process does not resolve the problem, you may be
using an older version of the Sun Solaris architecture (Sun4c).
If this is the case, repeat all the steps shown above and change
the value for Step 3 to be the same as the old value less one
zero.
For example:
Old Value for Step 3: 0x80000000
New Value for Step 3: 0x8000000
F. For RDBMS version 7.2.3 or older - How to Change Your Attach Address:
========================================================================
For RDBMS version 7.2.3 or older, change the attach address by doing
the following steps to relocate the SGA:
1. Shutdown the database.
2. Change location to the "rdbms/lib" directory
% cd $ORACLE_HOME/rdbms/lib
3. Modify the "ksms.s" file
Change this line:
sgabeg = 0xe0000000
To the new value:
sgabeg = 0x80000000
NOTE: This example moves the attached address from
"0xe0000000" to "0x80000000".
4. Edit the "oracle.mk" file by commenting out the following line:
FROM: ksms.s: $(ORACLE_HOME)/bin/genksms
$(ORACLE_HOME)/bin/genksms > ksms.s
TO: #ksms.s: $(ORACLE_HOME)/bin/genksms
# $(ORACLE_HOME)/bin/genksms > ksms.s
5. Relink
% make -f oracle.mk ioracle
6. Regenerate the 'ksms.o' object:
% make -f oracle.mk ksms.o
NOTE: If the above process does not resolve the problem, you may be
using an older version of the Sun Solaris architecture (Sun4c).
If this is the case, repeat all the steps shown above and change
the value for Step 3 to be the same as the old value less one
zero.
For example:
Old Value for Step 3: 0x80000000
New Value for Step 3: 0x80000
=====================================================

###########################################################

Doc Note:61896.1 Subject:
SOLARIS: SGA size, sgabeg attach address and Sun architectures

***************************************************************************
*** WARNING: Do not use this article to change the SGABEG attach address***
***         for Oracle8i (8.1.5 onwards) or you can have problems.     ***
***                                                                    ***
*** Note:1028623.6 for making these changes in 8i ***
***************************************************************************

SGA size, sgabeg attach address and Sun architectures
=====================================================

1. The SGA and shared memory
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The Oracle System Global Area (SGA) is implemented on UNIX using shared memory.

The successful use of shared memory on UNIX relies on two operations:

1) GET a shared memory segment of the required size - shmget()
2) ATTACH the segment to the processes virtual address space - shmat()

The success of shmget() relies on a number of factors, the most significant
being:

a) The segment's size is within the limits defined by the O/S kernel parameters
   SHMMIN & SHMMAX

b) SHMMNI (the O/S limit on the max. number of segments, system wide) is not
   exceeded

c) There is sufficient free virtual memory (i.e. swap space) to accomdate the
   segment

Problems involving shmget() are rare if the instructions in the Oracle
Installation and Configuration guide are followed (with respect to setting
SHMMAX, SHMMIN, SHMMNI and sizing swap space).

The success of shmat() also relies on a number of factors, the most significant
being:

a) There is sufficient free space in the processes virtual address map to
   accomodate the segment

2. sgabeg and the SPARC virtual memory map
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

On a UNIX operating system with a 32bit word size, each process has it's own 4Gb

virtual address map (0x0000000 - 0xffffffff), and ANY chunk of memory (shared
memory segments included) has to fit into free space in this map. The memory
management unit (MMU) is responsible for subsequent virtual to physical address
translation and has no further relevence here.

Locating sufficient free space within the virtual address map, in which to
attach the shared memory segment, is dictated by three things:

a) The starting address at which the segment will be mapped (sgabeg)
b) The size of the segment
c) Other memory segments already mapped

The SPARC ABI virtual memory map has several forms, but the most common (Solaris

2.5+ on sun4m/sun4u/sun4d) is:

 ------------
|            |
| 0xfb000000 | Top 80M not mapped
|------------|
|            |
|    /|\     |
|     |      |
|            |
| 0xf0000000 | Kernel stack (grows upwards)
|------------|
| 0xf0000000 | User stack (grows downwards)
|            |
|     |      |
|    \|/     |
|            |
|    /|\     |
|     |      |
|            |
| 0xef000000 | default start for dlopen()ed and mmap()ed files (grows upwards)
|- - - - - - |
|            |
|    /|\     |
|     |      |
|            |
| 0x80000000 | Default sgabeg (grows upwards)
|- - - - - - |
|            |
|    /|\     |
|     |      |
|            |
| 0x00010000 | Text & Data (grows upwards)
|------------|
|            |
| 0x00000000 | Bottom 64k not mapped
 ------------

As you can see from the diagram above, the default SGA attach address is
0x80000000. Given that dlopen()ed and mmap()ed files are attached at 0xef000000
and above, this allows for an SGA no bigger than about 1.7Gb.

3. Large SGA's
~~~~~~~~~~~~~~

You have the ability to reduce the value of sgabeg, thereby allowing a larger
SGA. The amount by which you can successfully reduce sgabeg is determined
by how far up the map text and data segments have already grown.

Also bear in mind that once the shared memory segment is successfuly mapped at
an address, other segments cannot use this space. Therefore, by lowering sgabeg
you reduce the amount of memory that can be allocated later for data segments,
which constitute the Oracle Private Global Area (PGA).

My advice is never to set sgebeg below 0x01000000. On an average system, this
leaves about 7Mb for data segments, out of which must come (at least) enough
memory for such things as SORT_AREA_SIZE etc. With a start address of 0x01000000

you can achieve an overall SGA size of about 3.6Gb.

Any problem attaching the shared memory segment due to it overlapping with
another segment (i.e. sgabeg too low, or sga too big) will produce the following

two errors at instance startup:

  V7:
  ORA-7307: sms1sg: shmat error, unable to attach sga
  SVR4 error: 22: Invalid argument

  V8:
  ORA-27123: unable to attach to shared memory segment
  SVR4 error: 22: Invalid argument

O/S error 22 (invalid argument) confirms that shmat() failed because the
requested attach address is illegal, mis-aligned, or the segment would overlap
an area of the virtual memory map that's already in-use. This error has also
been observed if the oracle user's stacksize is set to a large value. Use the
limit/ulimit command to ensure that the stacksize is set to a reasonable
value (8192 kbytes should be sufficient).

If you have successfully reduced the value of sgabeg, and subsequently receive
errors:

  ORA-4030: out of process memory when trying to allocate %s bytes (%s,%s)

or

  ORA-7324: smpall: malloc error while allocating pga.

then it's very likely that you've lowered the start address into an area which
the PGA needs to extend into. Raise sgabeg, and try again.

4. Different Sun architectures
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

There has always been some confusion with regards to the default (and valid)
values for sgabeg on differing Sun platforms.

In summary, the default values for sgabeg are:

Platform Group         Default sgabeg
--------------------   --------------
sun4m, sun4d & sun4u - 0x80000000
sun4c                - 0xe0000000

Section 6 (at the end of this note) shows systems versus platform groups.

During installation, the Oracle Installer is responsible for identifying the
platform & default attach address. It does this by using the
"$ORACLE_HOME/orainst/shmat_check" executable.

It is therefore worth noting that if you install Oracle RDBMS by copying an
ORACLE_HOME from one machine to another (rather than using the Installer), you
may need to change the sgabeg address (to either of the above two defaults) if
the platforms are different.

5. Useful commands
~~~~~~~~~~~~~~~~~~

a) Identify the architecture of a Solaris machine:

  # uname -m

b) Print the virtual address map of a process (Solaris 2.5+ only):

  # /usr/proc/bin/pmap <process id>

c) Identify the current value of sgabeg:

  # cd $ORACLE_HOME/bin
  # /usr/ccs/bin/nm oracle | grep sgabeg

  The second field shows the value of sgabeg (in decimal)

d) Identify current values of shared memory tunables:

  # /usr/sbin/sysdef | grep SHM

e) Print information about active shared memory segments:

  # ipcs -ma

f) Change the value of sgabeg:

  Ensure all instances using this $ORACLE_HOME are shutdown!

  For RDBMS versions 7.1.6.x.x and 7.2.x.x.x:

    % cd $ORACLE_HOME/rdbms/lib
    % genksms > ksms.s
    % vi ksms.s

    change:

    sgabeg = 0xe0000000

    to:

    sgabeg = <the value you require>

    e.g.

    sgabeg = 0x80000000

    % cp oracle.mk oracle.mk.orig
    % vi oracle.mk

    change:

    ksms.s: $(ORACLE_HOME)/bin/genksms
            $(ORACLE_HOME)/bin/genksms > ksms.s

    to:

    ksms.s: $(ORACLE_HOME)/bin/genksms
            $(ORACLE_HOME)/bin/genksms > ksms.s.default_sgabeg

    % rm ksms.o
    % make -f oracle.mk ioracle

  For RDBMS versions 7.3.2.x.x:

    % cd $ORACLE_HOME/rdbms/lib
    % genksms > ksms.s
    % vi ksms.s

    change:

    sgabeg = 0xe0000000

    to:

    sgabeg = <the value you require>

    e.g.

    sgabeg = 0x80000000

    % cp env_rdbms.mk env_rdbms.mk.orig
    % vi env_rdbms.mk

    change:

    $(KSMSS): $(ORACLE_HOME)/bin/genksms
            $(ORACLE_HOME)/bin/genksms > $(KSMSS)

    to:

    $(KSMSS): $(ORACLE_HOME)/bin/genksms
            $(ORACLE_HOME)/bin/genksms > $(KSMSS).default_sgabeg

    % rm ksms.o
    % make -f ins_rdbms.mk ioracle

  For RDBMS versions 7.3.3.x.x, 7.3.4.x.x:

    % cd $ORACLE_HOME/rdbms/lib
    % genksms -b <the value you require> >ksms.s

    e.g.

    genksms -b 0x80000000 >ksms.s

    % cp env_rdbms.mk env_rdbms.mk.orig
    % vi env_rdbms.mk

    change:

    $(KSMSS): $(ORACLE_HOME)/bin/genksms
            $(ORACLE_HOME)/bin/genksms > $(KSMSS)

    to:

    $(KSMSS): $(ORACLE_HOME)/bin/genksms
            $(ORACLE_HOME)/bin/genksms > $(KSMSS).default_sgabeg

    % rm ksms.o
    % make -f ins_rdbms.mk ioracle

  For RDBMS versions 8.0.x:

    % cd $ORACLE_HOME/rdbms/lib
    % genksms -b <the value you require> >ksms.s

    e.g.

    genksms -b 0x80000000 >ksms.s

    % cp ins_rdbms.mk ins_rdbms.mk.orig
    % vi ins_rdbms.mk

    change:

    ksms.s $(RDBMSLIB)ksms.s:
        $(GENKSMS) > ksms.s

    to:

    ksms.s $(RDBMSLIB)ksms.s:
        $(GENKSMS) > ksms.s.default_sgabeg

    % rm ksms.o
    % make -f ins_rdbms.mk ksms.o ioracle

6. System types versus platform groups
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

----------------------------------------------------------------------
System                         Platform Name            Platform Group
----------------------------------------------------------------------

SPARCserver 4/1XX              SUNW,Sun_4_100 Series    sun4
SPARCserver 4/2XX              SUNW,Sun_4_200 Series    sun4
SPARCserver 4/3XX              SUNW,Sun_4_300 Series    sun4
SPARCserver 4/4XX              SUNW,Sun_4_400 Series    sun4
SPARCstation SLC               SUNW,Sun_4_20            sun4c
SPARCstation ELC               SUNW,Sun_4_25            sun4c
SPARCstation IPC               SUNW,Sun_4_40            sun4c
SPARCstation IPX               SUNW,Sun_4_50            sun4c
SPARCstation 1                 SUNW,Sun_4_60            sun4c
SPARCstation 1+                SUNW,Sun_4_65            sun4c
SPARCstation 2                 SUNW,Sun_4_75            sun4c
SPARCserver 1000               SUNW,SPARCserver-1000    sun4d
SPARCcenter 2000               SUNW,SPARCcenter-2000    sun4d
SPARCclassic                   SUNW,SPARCclassic        sun4m
SPARCclassic X                 SUNW,SPARCclassic-X      sun4m
SPARCstation LX                SUNW,SPARCstation-LX     sun4m
SPARCstation LX+               SUNW,SPARCstation-LX+    sun4m
SPARCstation 4                 SUNW,SPARCstation-4      sun4m
SPARCstation 5                 SUNW,SPARCstation-5      sun4m
SPARCstation 10                SUNW,SPARCstation-10     sun4m
SPARCstation 10SX              SUNW,SPARCstation-10,SX  sun4m
SPARCstation 20                SUNW,SPARCstation-20     sun4m
SPARCstation Voyager           SUNW,S240                sun4m
SPARCserver 630                SUNW,SPARCsystem-600     sun4m
SPARCserver 670                SUNW,SPARCsystem-600     sun4m
SPARCserver 690                SUNW,SPARCsystem-600     sun4m
Ultra 1 Model 140              SUNW,Ultra-1             sun4u
Ultra 1 Model 170              SUNW,Ultra-1             sun4u
Ultra 1 Creator Model 170E     SUNW,Ultra-1             sun4u
Ultra 1 Creator 3D Model 170E  SUNW,Ultra-1             sun4u
Ultra Enterprise 1 Model 140   SUNW,Ultra-1             sun4u
Ultra Enterprise 1 Model 170   SUNW,Ultra-1             sun4u
Ultra Enterprise 1 Model 170E  SUNW,Ultra-1             sun4u
Ultra 2 Creator Model 1170     SUNW,Ultra-2             sun4u
Ultra 2 Creator3D Model 1170   SUNW,Ultra-2             sun4u
Ultra 2 Creator Model 2170     SUNW,Ultra-2             sun4u
Ultra 2 Creator3D Model 2170   SUNW,Ultra-2             sun4u
Ultra 2 Creator Model 1200     SUNW,Ultra-2             sun4u
Ultra 2 Creator3D Model 1200   SUNW,Ultra-2             sun4u
Ultra 2 Creator Model 2200     SUNW,Ultra-2             sun4u
Ultra 2 Creator 3D Model 2200  SUNW,Ultra-2             sun4u
Ultra Enterprise 2 Model 1170  SUNW,Ultra-2             sun4u
Ultra Enterprise 2 Model 2170  SUNW,Ultra-2             sun4u
Ultra Enterprise 2 Model 1200  SUNW,Ultra-2             sun4u
Ultra Enterprise 2 Model 2200  SUNW,Ultra-2             sun4u
Ultra Enterprise 150           SUNW,Ultra-1             sun4u
Ultra Enterprise 3000          SUNW,Ultra-Enterprise    sun4u
Ultra Enterprise 4000          SUNW,Ultra-Enterprise    sun4u
Ultra Enterprise 5000          SUNW,Ultra-Enterprise    sun4u
Ultra Enterprise 6000          SUNW,Ultra-Enterprise    sun4u
==========================================

      Rating: 4
command to see
      the disk path  June 14, 2003
Reviewer:
      Reader
As logical volume may have many disks invloved, is there a command the DBA can
use to see exactly the the (location) path of disks for a file system/mount
point? I am looking for a command(s) on sun solaris. Thanks. Any help would be
deeply appreciated.

          Followup:

depends on the LVM software.

June 15, 2003 --
      Thanks for the question regarding "criteria for selecting a partition
      key", version 8.1.7

      You Asked
Tom

1.Is it necessary that a partition key for a partitioned table should
mandatorily have column or columns which are part of a primary key. If this is
true , why is it so?

2.What is the criteria to select a partition key for a particular table. Are
there any set of rules or guidelines to be considered before we choose a column
or columns as the partition key.( especially when we dont have an appropriate
date or number column).

3.How many local and global indexes can we have on a partitioned table( I assume
any number of them can be created , but I want to be sure).
Before creating a local or global index on a partitioned table , what are the
things which need to be considered.

4.Is it necessary that a global index needs to be rebuilt , if we split or merge
a partition.

5.Suppose we are exchanging a partition with a table. Will the table have an
index created  if

1. the partitioned table had local index
2.the partitioned table had global index

Thank you

      and we said...
1) if the primay key is NOT in the partition key -- then the primary key must
use a GLOBAL index and n a data warehouse global indexes should be avoided.

Otherwise, it is perfectly OK to not have the primary key in the partition key
-- just bear in mind that in order to do uniqueness your index used to enforce
the primary key will be a globally partitiond index -- not local.

2) If there is no natural "ah-hah -- this is the partition key" set of columns,
you are probably a candidate for HASH partitioning.

Normally, the partition key falls out very naturally, by region, by time, by
code, etc.  If not -- hash partitioning might be for you.  You would have to
look at your table and how you use it to determine this.

3) any number

in a data warehouse -- local indexes should be used and global should be
avoided.  Otherwise, it depends on HOW you use the data.

4) yes. most any partition level operation will invalidate a global index
(split, merge, drop)

5) when you exchange a table with a partitionn, if the table has all of the
indexes that support the local indexes built, they can be swapped as well.  All
global indexes on the partitioned table will become invalid after the switch --
they need to have some data from this new partition merged into them and will
have to be rebuilt.
Review & Followup

      Rating: 3
What if
      partitions resides on same tablespace  October 09, 2001

Reviewer:  Shailandra  from USA
Tom

1. Will there be any performance improvement, in case table is partitioned
having all partition on same tablespace compared to non-partitioned table. How
does oracle parallelize fetch operation when all partitions exists on the same
tablespace? I mean when one person fetching records from one partitions and
another person from another partition.

2. Could updation of partition key be an important criteria? Suppose you choose
a partition a table on a partition key, which is a frequently updated in the
table. This may cause some overhead in terms of moving the row from one
partition to another partition. This may also cause invalidation of global
indexes.

Thanks

          Followup:

1) its not the TABLESPACE so much as the distribution of IO across your disks.

A tablespace may consist of many files -- each file can be on a separate disk.
You can spread IO out by using >1 file in a tablespace.

Whether you have a multi-partition table in a single tablespace with many disks
versus a non-partitioned table in a single tablespace with many disks won't
matter too much performance wise.

Using >1 tablespace with a partitioned table gives you better abilities to place
specific bits of data here and there, gives you higher availability, better
recovery and so on.  If you are using partitioning, there are good reasons to
use more then one tablespace but performance is not necessarily the driving
factor here.

2) it will NOT invalidate a global index (updating a partition key).  Partition
level operations like add, drop, split, merge -- those will invalidate the
global index.  A simple update -- not a chance.

Yes, a partition key that is modifiable and is in fact modified will add
overhead.  The update will be more like a delete/insert rather then an update as
the data must move from partition to partition sometimes.  It should be avoided
but it is supported when you need it.

      Rating: 5
criteria for
      selecting a partition key  June 15, 2003
Reviewer:
      jack  from Columbus, OH USA
Hi,
we are having an order entry system and key columns in this table are

order_id (PK)
customer_id,
order_date,
order_status .....and so on.

Typically our batch programs use order_date and order_status(90% order_status
column and status for an order can move to 3-4 status's based on the processing
steps) combination to process the orders.
But our UI programs (99%) use customer_id to display the orders inforamation.
Can you pl. suggest me in which way to partition this table and what columns I
need to include for better batch and UI response .
thanks

          Followup:

your UI programs will be doing keyed reads by customer_id.

your batch won't.

partition the table for batch.

create a globally partitioned index for UI based on customer_id.

It is hard to say more then that generalization as I don't know all about your
system, but it would look like you would want to benchmark and test partitioning
the table by order_date ranges and making sure the UI applications (using
indexed reads) have indexes partitioned best for them.

      Rating: 4
partition
        June 16, 2003
Reviewer:  santhanam
       from India
Excellent

June 15, 2003 Pavan -- Thanks for the question regarding
      "Migration steps from oralce 8i to 9i", version 8.1.7

      You Asked
Need the steps for migrating Oracle database 8i from UNIX-Aix to Oracle 9i
Database On UNIX -AIX

      and we said...
step 1)

read your handy dandy migration guide that is published with each release of
Oracle:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-MIG
step 2)

decide which approach you are going to take (upgrade or migrate via a dump/load
-- such as you would do with exp/imp)

step 3)

test it, test it, test it.

step 4)

do it.

June 15, 2003 syed -- Thanks for the question regarding "I am
      doing exp/imp without data. But still import is increasing the tablespace
      size", version 9.0.1.3

      You Asked
I am trying to export on a schema by schema basis. I am trying to import in
another schema on the same database. I want to import table structure only
without data. But when I import the schema from one user to another user
without data my tablespace size is also increasing in the same proportion as if
data is being imported. Butwhen I query the table there is no data in the
tables. I can see that
synonymns are also being importted with schema import.
My objective is to import only table strcuture without data.
How can it be done ?

i am going exp and imp by following command.

exp userid=scott/xxxxxx file=scott log=scott owner=scott rows=n

imp scott/xxxxxx file=scott.dmp log=scott fromuser=scott
touser=hadi rows=n ignore=y

      and we said...
exp exports everything a user has, triggers, tables, indexes, views, code, you
name it.

You can stop exporting SOME THINGS like constraints, grants -- using the
appropriate flags.

Some solutions:

o do table level exports instead of schema level.
o do a schema level export but imp ... full=y INDEXFILE=foo.sql   Foo.sql will
contain all table and index creates and you can edit them to fix any storage
clauses you don't like -- it will not have synonyms and other things.

By default EXP uses "compress=y" which says "goto the data dictionary and add up
all of the allocated space for the table as it exists now, then make that be the
new initial extent".  That is why your space reflects the "full size".
Solution: always use compress=n on every export you ever do from now on.
Review &
      Followup

      Rating:
      4
satisfactory  June 15, 2003
Reviewer:
      Syed Hadi  from Kuwait
As per your advice I did export with compress=n and did import. But still I find
the increase in tablespace size.
col owner format a15
SQL> col segment_name format a20
SQL> SELECT owner, segment_type, segment_name, bytes
FROM dba_segments WHERE tablespace_name = 'USERS' ORDER BY owner,segment_type,
segment_name;

WNER           SEGMENT_TYPE       SEGMENT_NAME              BYTES
--------------- ------------------ -------------------- ----------
SPI_GRPMED      INDEX              IDX_PBDH_POL_SYS_ID     8388608
SPI_GRPMED      INDEX              IDX_PBDI_PEMP_SYS_ID     131072
SPI_GRPMED      INDEX              IDX_PBDI_POCC_SYS_ID     131072
SPI_GRPMED      INDEX              IDX_PBD_PEMP_SYS_ID    12582912
SPI_GRPMED      INDEX              IDX_PBD_POCC_SYS_ID      262144
SPI_GRPMED      INDEX              IDX_PBD_POL_SYS_ID     12582912
SPI_GRPMED      INDEX              IDX_PBH_PEMP_SYS_ID    14680064
SPI_GRPMED      INDEX              IDX_PBH_POCC_SYS_ID    15728640
SPI_GRPMED      INDEX              IDX_PBS_PEMP_SYS_ID    40894464
SPI_GRPMED      INDEX              IDX_PBS_POCC_SYS_ID     1048576
SPI_GRPMED      INDEX              IDX_PBS_POL_SYS_ID     40894464

OWNER           SEGMENT_TYPE       SEGMENT_NAME              BYTES
--------------- ------------------ -------------------- ----------
SPI_GRPMED      INDEX              IDX_PB_POL_SYS_ID       9437184
SPI_GRPMED      INDEX              IDX_PEMPH               5242880
SPI_GRPMED      INDEX              IDX_PGA_PEMP_SYS_ID     4194304
SPI_GRPMED      INDEX              IDX_PGA_POCC_SYS_ID      131072
SPI_GRPMED      INDEX              IDX_PGA_POL_SYS_ID      4194304
SPI_GRPMED      INDEX              IDX_PGMS_DRCR_01          65536
SPI_GRPMED      INDEX              IDX_PGMTH_POL_BNF_SB   27262976
                                   NF

SPI_GRPMED      INDEX              IDX_PGMTH_POL_BNF_SB   23068672
                                   NF_02

OWNER           SEGMENT_TYPE       SEGMENT_NAME              BYTES
--------------- ------------------ -------------------- ----------

SPI_GRPMED      TABLE              CBK_2500                 131072
SPI_GRPMED      TABLE              CBK_5000                 131072
SPI_GRPMED      TABLE              FS_BAL                   131072
SPI_GRPMED      TABLE              GMENDORSE               4194304
SPI_GRPMED      TABLE              GMENDORSE_GL            8388608
SPI_GRPMED      TABLE              GPENDORSE                786432
SPI_GRPMED      TABLE              GPENDORSE_GL             917504
SPI_GRPMED      TABLE              GRPCLMEXP                131072
SPI_GRPMED      TABLE              GRPCLMSI                 131072
SPI_GRPMED      TABLE              GRPCVRDED                262144

OWNER           SEGMENT_TYPE       SEGMENT_NAME              BYTES
--------------- ------------------ -------------------- ----------
SPI_GRPMED      TABLE              GRPCVRDED_260301         262144
SPI_GRPMED      TABLE              GRPCVRDED_GL             131072
SPI_GRPMED      TABLE              GRPMEMBER               9437184
SPI_GRPMED      TABLE              GRPMEMBER_260301        8388608
SPI_GRPMED      TABLE              GRPMEMBER_GL           19922944
SPI_GRPMED      TABLE              GRPPOLICY                262144
SPI_GRPMED      TABLE              GRPPOLICY_260301         262144
SPI_GRPMED      TABLE              GRPPOLICY_GL             393216
SPI_GRPMED      TABLE              GRPRATE                  131072
SPI_GRPMED      TABLE              GRPRIRATE                131072
SPI_GRPMED      TABLE              GRPSMEMBER               131072

OWNER           SEGMENT_TYPE       SEGMENT_NAME              BYTES
--------------- ------------------ -------------------- ----------
SPI_GRPMED      TABLE              INSU                      65536
SPI_GRPMED      TABLE              INVESTMENT               131072
SPI_GRPMED      TABLE              KDL50024                 131072
SPI_GRPMED      TABLE              Kcci                     131072
SPI_GRPMED      TABLE              PCOM_CODES_RAJ           524288
SPI_GRPMED      TABLE              PGLT_POL_GEO_AREA_ME     131072
                                   D01050139

SPI_GRPMED      TABLE              PGLT_POL_GEO_AREA_TE     131072
                                   MP

OWNER           SEGMENT_TYPE       SEGMENT_NAME              BYTES
--------------- ------------------ -------------------- ----------
SPI_GRPMED      TABLE              PGMMA_APPL_SEGMENTS      917504
SPI_GRPMED      TABLE              PGMMA_APPL_USER_CATG     917504
                                   _REL

SPI_GRPMED      TABLE              PGMMA_APPR_SETUP         262144
SPI_GRPMED      TABLE              PGMMA_APPR_SETUP_DET     262144
SPI_GRPMED      TABLE              PGMMA_BENEFIT            917504
SPI_GRPMED      TABLE              PGMMA_CLM_ACNT_SETUP     917504
SPI_GRPMED      TABLE              PGMMA_COINS_FAC_POOL     917504
SPI_GRPMED      TABLE              PGMMA_COINS_FAC_POOL     917504
                                   _CUST

          Followup:

umm, it needs to allocate an initial extent regardless -- hence, it takes SPACE
to create a table/index -- even with no data in it (exception: global temporary
tables).

So, if you are unhappy with the "amount of space", use the index file option I
described and set up what you believe to be reasonable "initial extents" -- or
better yet, use DBMS_METADATA to extract the relevant DDL without any storage
clauses and just use LOCALLY MANAGED TABLESPACES (and never use storage clauses
again)

      Rating:
      4
review  June 15, 2003
Reviewer:  Syed
      Hadi  from Kuwait
I used compress=n in my export and imported. But still I find that intial is
bigger like before.

select bytes from dba_extents where owner='SPI_GRPMED' and
segment_name='GRPMEMBER'

Bytes
1048576
1048576
1048576
1048576
1048576
1048576
1048576
1048576
1048576
------------------------------------------------------------

Following are other table sizes.
--------------------------------
OWNER           SEGMENT_TYPE       SEGMENT_NAME              BYTES
--------------- ------------------ -------------------- ----------
SPI_GRPMED      INDEX              IDX_PBDH_POL_SYS_ID     8388608
SPI_GRPMED      INDEX              IDX_PBDI_PEMP_SYS_ID     131072
SPI_GRPMED      INDEX              IDX_PBDI_POCC_SYS_ID     131072
SPI_GRPMED      INDEX              IDX_PBD_PEMP_SYS_ID    12582912
SPI_GRPMED      INDEX              IDX_PBD_POCC_SYS_ID      262144
SPI_GRPMED      INDEX              IDX_PBD_POL_SYS_ID     12582912
SPI_GRPMED      INDEX              IDX_PBH_PEMP_SYS_ID    14680064
SPI_GRPMED      INDEX              IDX_PBH_POCC_SYS_ID    15728640
SPI_GRPMED      INDEX              IDX_PBS_PEMP_SYS_ID    40894464
SPI_GRPMED      INDEX              IDX_PBS_POCC_SYS_ID     1048576
SPI_GRPMED      INDEX              IDX_PBS_POL_SYS_ID     40894464
OWNER           SEGMENT_TYPE       SEGMENT_NAME              BYTES
--------------- ------------------ -------------------- ----------

SPI_GRPMED      TABLE              CBK_2500                 131072
SPI_GRPMED      TABLE              CBK_5000                 131072
SPI_GRPMED      TABLE              FS_BAL                   131072
SPI_GRPMED      TABLE              GMENDORSE               4194304
SPI_GRPMED      TABLE              GMENDORSE_GL            8388608
SPI_GRPMED      TABLE              GPENDORSE                786432
SPI_GRPMED      TABLE              GPENDORSE_GL             917504
SPI_GRPMED      TABLE              GRPCLMEXP                131072
SPI_GRPMED      TABLE              GRPCLMSI                 131072
SPI_GRPMED      TABLE              GRPCVRDED                262144

      Rating:
      4
Review  June 15, 2003
Reviewer:  Syed
      Hadi  from Kuwait
Sorry for duplicating my review.

I am already using Locally managed tablespace.

      Rating: 3
On 9i, why not
      use the dbms_metadata package  June 15, 2003

Reviewer:  Peter Tran  from Houston, TX USA

If all you want is the "table structure only without data", then using the
dbms_metadata package may satisfy your requirement.  NOTE only available in 9i.

See:

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96612/d_metada.htm#ARPLS026
With examples:

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96612/d_metad2.htm#1024762
-Peter

June 15, 2003 Eugene -- Thanks for the question regarding
      "check constraint", version 8.1.6

      You Asked
Hi Tom,
Is there a way to add check constraint to a column, which will check another
column on the same table?
For instance: I have a STYLE table. It has column division_id, which defaults to
the division that user is logged in to. I want to make sure that when user
creates new style, its name will be: first char of division plus the number
(that they typed in). Lets say that default division is JRS. User types in
style_id of B12345. I need to make sure that new style_id can only be J12345.

Thanks,
Eugene

      and we said...
you would not add it to COLUMN, no.

You would add that constraint to the TABLE itself.

something like this:

ops$tkyte@ORA920> create table t ( x varchar2(5), y varchar2(10) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table t add constraint y_check
  2  check ( substr(y,1,1) = substr(x,1,1) );

Table altered.

Review &
      Followup

      Rating:
      4
...  June 15, 2003
Reviewer:  Eugene
       from Seattle
Thanks Tom,
Now that constraint will be in place and it will not allow user to create
"wrong" style, what it the best way to let user know about the problem. In other
words, how can I present it to the user. As you know users almost never read
error messages. Is raise_application_error the only way to let them know about
the problem?

          Followup:

they'll get the "constraint NAME violated", use a meaningful name for the
constraint.

June 15, 2003 Kamal -- Thanks for the question regarding
      "Descending Indexes", version 9.2.0.3

      You Asked
Hi Tom,
In your book "Expert One-On-One Oracle" at page 270, you said:
<quote>
Descending Indexes - In the future, descending indexes will not be notable as a
special type of index.
</quote>

Can you elaborate on this please?

Are you saying that "Descending Indexes" feature will be removed in some future
database release?

At least in 9i, it allows me to create it:

SQL> desc emp
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 EMPNO                                              NUMBER
 ENAME                                              VARCHAR2(10)
 JOB                                                VARCHAR2(9)
 MGR                                                NUMBER(4)
 HIREDATE                                           DATE
 SAL                                                NUMBER(7,2)
 COMM                                               NUMBER(7,2)
 DEPTNO                                             NUMBER(2)

SQL> create index idx_emp_ename on emp(ename DESC) ;

Index created.

SQL>

      and we said...
What I was saying was

'hey guys, it is 8i, this is new -- you can use DESC in an index and it means
something.  In the future, when you are using 13i, this will be a big YAWN, but
right now they are "special"'

I noted them as a special kind of index in the book.

In a 9i or 10i book -- I would not as they would be "mainstream" by then.

It is not that they are going away, but rather that they would not be
"exceptional enough" in the future to mandate their own section in a book.
June 15, 2003 --
      Thanks for the question regarding "two questions", version 9.2.0.3.0

      You Asked
Hi Tom,

I would like to ask you two question which are quite weird to me:

Q1-One of the developers is doing the following on:

Oracle 8.1.7.3
==============
create table auxtab as select from dual;

create or replace procedure p_insert_auxtab as
begin
insert into auxtab using (select * from dual);
commit;
end;
/

Procedure created.

SQL>show errors
No errors.

Oracle 9.2.0.3.0
================
Now if you try to do the same thing on a release 9.2.0.3.0 the procedure gets an
error:

create table auxtab as select from dual;

create or replace procedure p_insert_auxtab as
begin
insert into auxtab using (select * from dual);
commit;
end;
/

Warning: Procedure created with compilation errors.

SQL>show errors
Errors for PROCEDURE P_INSERT_AUXTAB:

LINE/COL ERROR
-------- ----------------------------------------------------
3/1      PL/SQL: SQL Statement ignored
3/31     PL/SQL: ORA-00926: missing VALUES keyword

But if you do the following:

SQL>create or replace procedure p_insert_auxtab as
  2  begin
  3  insert into auxtab using select * from dual;
  4  commit;
  5* end;
SQL>/

Procedure created.

SQL>show errors
No errors.

I never thought that the "using" word could be used in this way. I have used the
using word in cases such as execute immediate or for example when you need to
open a cursor. My question is : is it correct to use the using word in the
"insert into.." statement? why did the developer was able to compile and execute
the procedure
in 8i using parenthesis but not in 9i?

2-I have a developer who did the following in a release 8.1.7.3

In 8i
=====
SQL>create table test2
  2  (colnum number)
  3  ;

Table created.

SQL>create or replace procedure p_test2 as
  2  x number:=0;
  3  begin
  4  select colnum into x from test2 where colnum mod 4 =0;
  5  dbms_output.put_line(to_char(x));
  6* end;
SQL_FRLS>/

Procedure created.

SQL>show error
No errors.

In 9i
======
SQL>create table test2
  2  (colnum number)
  3  ;

Table created.

SQL>create or replace procedure p_test2 as
  2  x number:=0;
  3  begin
  4  select colnum into x from test2 where colnum mod 4 =0;
  5  dbms_output.put_line(to_char(x));
  6* end;
  7  /

Warning: Procedure created with compilation errors.

SQL>show errors
Errors for PROCEDURE P_TEST2:

LINE/COL ERROR
-------- -----------------------------------------------------------------
4/1      PL/SQL: SQL Statement ignored
4/59     PL/SQL: ORA-00920: invalid relational operator

My question is why did 8i allow me to use the mod function as an operator?
Shouldn't it give me an error in 8i also?
I was looking into Oracle documentation and I wasn't able to find any example
where the mod is used as an operator.
Is it correct to use the mod as an operator? Why wasn't the developer able to
run the same thing on 9i?

Thanks in advance for your help.

      and we said...
q1)

insert into T
(select * from dual);

would not confuse you right?  so

insert into T T1
(select * from dual);

shouldn't either -- and further

insert into T "using"
(select * from dual);

is sort of like:

insert into T using
(select * from dual);

-- USING is just a 'correlation' name -- it was an ALIAS for T, not a keyword.

but it does seem to be some sort of issue in 9i with the USING PLSQL keyword
mucking things up:

ops$tkyte@ORA920> create table t as select * from dual where 1=0;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2          insert into t using (select * from dual);
  3  end;
  4  /
end;
   *
ERROR at line 3:
ORA-06550: line 2, column 16:
PL/SQL: ORA-00926: missing VALUES keyword
ORA-06550: line 2, column 2:
PL/SQL: SQL Statement ignored

ops$tkyte@ORA920> begin
  2          insert into t using select * from dual;
  3  end;
  4  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> begin
  2          insert into t "using" (select * from dual);
  3  end;
  4  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> begin
  2          insert into t "using" select * from dual;
  3  end;
  4  /

PL/SQL procedure successfully completed.

but in short -- USING was an alias, not a keyword.  It is not needed in the
first place.

q2) that is unique -- I haven't seen that one myself.  It has to do with the
fact that in 8i and before plsql had it's own sql parser.  PLSQL defined all
functions like =, < and so on as "functions", it rewrote them.  If you turn in
sql_trace and watch the generated SQL from PLSQL, that would be:

SELECT COLNUM
FROM
 T  WHERE MOD (COLNUM,4) = 0

even though the code was:

ops$tkyte@ORA817DEV> create table t ( colnum number );

Table created.

ops$tkyte@ORA817DEV> insert into t select rownum-1 from all_users where rownum
<=4;

4 rows created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace procedure p_test2 as
  2     x number:=0;
  3  begin
  4     select colnum into x from t where colnum mod 4 =0;
  5     dbms_output.put_line(to_char(x));
  6  end;
  7  /

Procedure created.

ops$tkyte@ORA817DEV> exec p_test2;
0

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> select colnum from t where colnum mod 4 = 0;
select colnum from t where colnum mod 4 = 0
                                  *
ERROR at line 1:
ORA-00920: invalid relational operator

definitely not documented that way at all.  it worked "by accident" in 8i.

Those are two definitely "strange but interesting" ones.

June 16, 2003 NC -- Thanks for the question regarding "Is there
      a better way to do this insert", version 8.1.7

      You Asked
Hi

I would like to know what would be the best way to perform the following
inserts.

I have tables  t1, t2, t3 and x1, x2 and x3

t1 has approx 400,000 rows
t2 has approx 1,000,000 rows
t3 has approx 200,000 rows

x1,x2,x3 will star empty and be appended to each month.

currently, I have 3 nested FOR LOOPS.
The main outer loop which selects all records from t1 and
inserts into x1.
The primary key on x1 is from a sequence via a pre-insert trigger.
This sequence is returned into a variable via a returning into clase

The next inner loop selects all records from t2 where the id (primary key) from
x1 matches column id in t2
and inserts into x2 (again generating a primary key via a trigger and returning
it into a variable)
Also the sequence from the previous loop is inserted into x2 so that the link
between x1 and x2 is created.

The last loop is much the same, insert into x3 all records from t3 where the id
(primary key) from x2 matches id in t3
and inserts into x3, and the sequence from the previous loop is inserted into x3
so that the link between
x2 and x3 is created.

So we  have

cursor c1 is select * from t1;
cursor c2(v_id1 in number) is select * from t2 where t2.id = v_id1;
cursor c3(v_id2 in number) is select * from t3 where t3.id = v_id2;

for rec1 in c1
loop
  insert into x1 (col1, col2, col3) values (null, rec1.a, rec1,b) returning col1
into v_new_id;

 for rec2 in c2(rec1.a)
  loop
   insert into x2 (col1, col2, col3) values (null, v_new_id, rec2.a) returning
col1 into v_new_id2;

   for rec3 in c3(rec2.a)
    loop
     insert into x3 (col1, col2, col3) values (null, v_new_id2, rec3.a);
    end loop;

   end loop;

end loop;

Would it be better to use arrays and bulk collect / for all insert ?
I would appreciate your advice.

Than

      and we said...
Oh, is there a better way.

Believe it or not, that process should be nothing more then 3 insert into
statements.  The speedup you'll see will amaze you.  The reduction in resources
will astound you.  I setup t1,t2,t3 like this:

ops$tkyte@ORA920> create table t1( id int, data char(20) );
Table created.

ops$tkyte@ORA920> create table t2( id int, fk_t1 int, data char(20) );
Table created.

ops$tkyte@ORA920> create table t3( id int, fk_t2 int, data char(20) );
Table created.

ops$tkyte@ORA920> insert into t1 select rownum, 'x' from big_table.big_table
where rownum <= 400000;

400000 rows created.

ops$tkyte@ORA920> insert into t2
  2  select rownum, id, 'x'
  3    from ( select id from t1
  4            union all
  5           select id from t1
  6            union all
  7           select id from t1 where mod(id,2) = 0 );
1000000 rows created.

ops$tkyte@ORA920> insert into t3
  2  select rownum, id, 'x'
  3    from (select id from t2 where mod(id,5) = 0);
200000 rows created.

ops$tkyte@ORA920> create index t2_fk_idx on t2(fk_t1);
Index created.

ops$tkyte@ORA920> create index t3_fk_idx on t3(fk_t2);
Index created.

ops$tkyte@ORA920> analyze table t1 compute statistics for table for all indexes
for all indexed columns;
Table analyzed.

ops$tkyte@ORA920> analyze table t2 compute statistics for table for all indexes
for all indexed columns;
Table analyzed.

ops$tkyte@ORA920> analyze table t3 compute statistics for table for all indexes
for all indexed columns;
Table analyzed.

then, I created your X1, x2, x3 tables

ops$tkyte@ORA920> create table x1a ( newid int, oldid int, data char(20) );
Table created.

ops$tkyte@ORA920> create table x2a ( newid int, oldid int, fk_t1 int, data
char(20) );
Table created.

ops$tkyte@ORA920> create table x3a ( newid int, oldid int, fk_t2 int, data
char(20) );
Table created.

ops$tkyte@ORA920> create index x1a_idx on x1a(newid,oldid);
Index created.

ops$tkyte@ORA920> create index x2a_idx on x2a(newid,oldid);
Index created.

I had x1 and x1a, x2, x2a and so on - only x1a...x2a had indexes, for my
approach

On x1, x2, x3, we created triggers:

ops$tkyte@ORA920> create or replace trigger x1_trigger
  2  before insert on x1 for each row
  3  begin
  4     select s.nextval into :new.newid from dual;
  5  end;
  6  /
Trigger created.

which is the worst way to approach this -- using a trigger.  anyway.  Here is my
approach to loading x1, x2, x3:

ops$tkyte@ORA920> exec runstats_pkg.rs_start

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> alter sequence S cache 1000000;
Sequence altered.

that is someting you want to do before using a sequence really hard and heavy,
you would be AMAZED at the benefits of that

ops$tkyte@ORA920>
ops$tkyte@ORA920> column S new_val S;
ops$tkyte@ORA920> select s.nextval S from dual;

         S
----------
         1

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert /*+ APPEND */ into x1a
  2  select s.nextval, id, data from t1;

400000 rows created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert /*+ APPEND */ into x2a
  2  select s.nextval, t2.id, x1.newid, t2.data
  3    from t2, x1a x1
  4   where x1.newid > &S
  5     and x1.oldid = t2.fk_t1;
old   4:  where x1.newid > &S
new   4:  where x1.newid >          1

1000000 rows created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert /*+ APPEND */ into x3a
  2  select s.nextval, t3.id, x2.newid, t3.data
  3    from t3, x2a x2
  4   where x2.newid > &S
old   4:  where x2.newid > &S
new   4:  where x2.newid >          1

200000 rows created.

ops$tkyte@ORA920> commit;
Commit complete.

ops$tkyte@ORA920> alter sequence S cache 20;
Sequence altered.

ops$tkyte@ORA920> exec runstats_pkg.rs_middle;
PL/SQL procedure successfully completed.

there, that is the entire load process -- it does *everything*.  Now your
way:

ops$tkyte@ORA920> declare
  2      l_newid_t1 int;
  3      l_newid_t2 int;
  4  begin
  5      for x in ( select * from t1 )
  6      loop
  7          insert into x1 ( oldid, data ) values ( x.id, x.data )
  8          returning newid into l_newid_t1;
  9          for y in ( select * from t2 where t2.fk_t1 = x.id )
 10          loop
 11          insert into x2 ( oldid, fk_t1, data ) values ( y.id, l_newid_t1,
y.data )
 12          return newid into l_newid_t2;
 13          for z in ( select * from t3 where t3.fk_t2 = y.id )
 14          loop
 15              insert into x3 ( oldid, fk_t2, data ) values ( z.id,
l_newid_t2, z.data );
 16          end loop;
 17          end loop;
 18      end loop;
 19  end;
 20  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec runstats_pkg.rs_stop(100000);
Run1 ran in 12183 hsecs
Run2 ran in 235576 hsecs
run 1 ran in 5.17% of the time

1/20th of the runtime, 20times faster (and it had two indexes extra to take
care of!). But more importantly:

Name                                  Run1        Run2        Diff
LATCH.checkpoint queue latch        36,430     141,104     104,674
LATCH.row cache enqueue latch        3,846     163,180     159,334
LATCH.dml lock allocation              370     160,329     159,959
LATCH.sequence cache             4,800,009   4,960,014     160,005
STAT...recursive cpu usage              63     199,915     199,852
STAT...CPU used by this sessio       5,509     217,812     212,303
STAT...CPU used when call star       5,509     217,812     212,303
STAT...Elapsed Time                 12,270     235,598     223,328
LATCH.undo global data              19,681     253,812     234,131
STAT...enqueue releases              5,151     242,495     237,344
STAT...enqueue requests              5,151     242,499     237,348
LATCH.library cache pin alloca       2,989     323,616     320,627
LATCH.enqueue hash chains           10,545     487,183     476,638
LATCH.row cache objects              5,286     484,151     478,865
STAT...sorts (rows)              1,402,631       2,618  -1,400,013
STAT...consistent gets - exami     407,293   1,890,245   1,482,952
STAT...table scan rows gotten      400,001   2,000,000   1,599,999
STAT...table scans (short tabl           1   1,600,000   1,599,999
STAT...table scan blocks gotte       1,757   2,000,048   1,998,291
STAT...redo entries                 52,903   2,215,021   2,162,118
LATCH.redo allocation               53,998   2,222,013   2,168,015
LATCH.session allocation                31   2,401,734   2,401,703
STAT...buffer is pinned count    2,600,058           0  -2,600,058
STAT...db block gets               102,375   3,161,828   3,059,453
STAT...no work - consistent re   1,207,558   4,402,253   3,194,695
STAT...db block changes             99,734   4,401,587   4,301,853
STAT...buffer is not pinned co   1,202,995   5,598,153   4,395,158
STAT...execute count                   502   4,680,368   4,679,866
STAT...recursive calls               5,033   7,323,453   7,318,420
STAT...consistent gets           3,022,880  10,894,618   7,871,738
STAT...calls to get snapshot s      12,743   8,043,676   8,030,933
LATCH.shared pool                1,604,008   9,805,723   8,201,715
STAT...session logical reads     3,125,255  14,056,446  10,931,191
LATCH.library cache pin          3,204,928  16,245,960  13,041,032
LATCH.library cache              4,807,752  19,774,939  14,967,187
LATCH.cache buffers chains       6,112,094  33,752,275  27,640,181
STAT...redo size               127,584,104 606,190,928 478,606,824

I'm just showing things that were more then 100,000 different here.  Look at
the REDO SIZE, look at the latching differences!  These are major league
different...

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
20,739,779  91,534,813  70,795,034     22.66%

PL/SQL procedure successfully completed.

just use 3 inserts and be done with it.  anytime you find yourself writing
code -- stop -- and ask "why, why am I doing that"

Review &
      Followup

      Rating: 5
Two
      questions  June 13, 2003
Reviewer:
      A reader
Tom,

I have two questions regarding your insert statements.

"ops$tkyte@ORA920> insert /*+ APPEND */ into x2a
  2  select s.nextval, t2.id, x1.newid, t2.data
  3    from t2, x1a x1
  4   where x1.newid > &S
  5     and x1.oldid = t2.fk_t1;
old   4:  where x1.newid > &S
new   4:  where x1.newid >          1

1000000 rows created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert /*+ APPEND */ into x3a
  2  select s.nextval, t3.id, x2.newid, t3.data
  3    from t3, x2a x2
  4   where x2.newid > &S
old   4:  where x2.newid > &S
new   4:  where x2.newid "

Q1: Why do you need "x1.newid > &S" in the where clause when inserting into x2a?

Q2: How come the where clause "x2.oldid = t3.fk_t2;" is missing when inserting
into x3a? But there is a similar condition "x1.oldid = t2.fk_t1;" when inserting
into x2a?

Thanks

          Followup:

q1) i assumed this table X1 was an aggregate over time and hence X1 would
contain last months data as well as this new month of data and that x1.oldid
would be duplicated over and over.

x1.newid limits our view of X1 to just the newly added data.

q2) excellent eyeballs.

it was a cut and paste error (boundary value condition :)

ops$tkyte@ORA920> insert /*+ APPEND */ into x3a
  2  select s.nextval, t3.id, x2.newid, t3.data
  3    from t3, x2a x2
  4   where x2.newid > &S
  5     and x2.oldid = t3.fk_t2;
old   4:  where x2.newid > &S
new   4:  where x2.newid >          1

200000 rows created.

is the correct one -- notice the ';' or '/' is missing in the original -- i lost
that one line.

      Rating: 5
Great
      example  June 13, 2003
Reviewer:  Peter Tran
       from Houston, TX USA
Tom,

Are you using a direct-path insert approach here?

You can do a little better if you disable logging uring the direct-path insert,
however you would need to run a backup after the job completes otherwise, you
run the risk of losing everything if the database crashes.  Am I correct?

Thanks,
-Peter

          Followup:

yes, yes and yes.

      Rating: 4
Thank
      you  June 15, 2003
Reviewer:  NC  from
      UK
I kinda thought I was missing something !
But if this needs to be called from a package, what
would need to change from your example, as I dont really understand the &S thing
in the where clause ?

Thanks for your help

          Followup:

&S was the value of the sequence before we began the mass inserts.

In a plsql routine, it would look like:

  select s.nextval into l_lo_seq from dual;

  insert /*+ APPEND */ into x2a
  select s.nextval, t2.id, x1.newid, t2.data
    from t2, x1a x1
   where x1.newid > L_LO_SEQ
     and x1.oldid = t2.fk_t1;

      Rating: 4
Package
      runstats_pkg  June 16, 2003
Reviewer:  Kurt
       from Austria
Hi Tom,
is it possible to get the sources of the package runstats_pkg?

          Followup:

http://asktom.oracle.com/~tkyte/
look for the simple test harness.

      Rating: 5
insert
      processing  June 16, 2003
Reviewer:
      Santhanam
Very useful

June 16, 2003 -- Thanks for the question regarding "How to
      track select activity on a table without using database audit trails.",
      version 8.1.7.2

      You Asked
Hi Tom ,

I am in a situation where there is no scope for database audit trails,because of
some weired security policies,in which we(as a primary dba of the database)
don't authorize or own Database audit trails.

I need to track all DML( Insert,update,delete) and select activity for four
tables.In which I need to find who(userid) access which tables.The purpose of
doing this audit ,is to obsolute all these four tables and I am making sure no
one is accessing them.

For update,insert and delete activity ,we plan to use simply database
triggers.But I don't have any good method to track select activity.

I wrote the below query and runs for every 5 minutes,to track selects on
tables.It don't looks good approve.

select /*+ RULE */
a.object,b.username,b.osuser,b.command
from
v$access a,
v$session b
where
a.sid=b.sid
and a.object in ('TABLE01','TABLE02','TABLE04','TABLE05')
and a.type='TABLE'
and b.command != 0
/

But my best bet,is since these tables are very big (12gb-45gb in size) and most
of usally queries ,which i known runs for 30minutes to more then 2hours,so 5
minutes probe time to track the selects seems to be ok.

I hope there should be better way to track the selects,please let me know,if you
know any better method.

Thanks for your great service...

      and we said...
This is terrible.  Think about it "hey, if you don't use it, you lose it".

You don't know if these 4 pretty huge tables are used?  why not?  that is the
problem you need to fix, not to watch these tables -- but to fix the "why we
have no idea what tables are actually used".

Why not do this

o rename them
o wait for the phone to ring

that'll tell you very quickly if anyone uses them.  brutally efficient.

sorry -- don't know what to tell you..  Seems totally backwards here.  You

a) don't know if these tables are used (why not?)
b) don't have the ability to enable auditing (that is just silly)

Review &
      Followup

      Rating:
      3
Silly?  June 16, 2003
Reviewer:  A
      Reader  from UK
I suppose at some point you have to trust someone - but if you were in charge of
information security at your organisation would you let the guys who control the
database also control the thing that tells you what was changed ?

I can see all sides of this, but for balance thought I would throw in that
point.

Imagine this was a bank account database - what is to stop a DBA putting through
a transaction, then removing it from the audit trail ?

Is there a way that the audit trail could be 'secured' so the DBA can enable it
but not touch it ?  THat might solve the problem ?

          Followup:

what I meant by silly was -- NOT having any capability to enable auditing for
these 4 tables is silly.

Ok, so they are not allowed to turn it on, but -- SOMEONE IS.  So, get them to
do it and report back to you whether someone is using the table.  problem
solved.  That they cannot do that is the "silly" part.

You use OS Audit Trails to separate the DBA from the audit trail data.

      Rating: 3
Answers to your
      questions  June 16, 2003
Reviewer:
      A reader
1.You don't know if these 4 pretty huge tables are used?  why not?

Well these tables were from "old" database model and no one supposed to be using
them any more.So before making them as absolute(archive and drop),as a DBA, I am
making sure no one is " REALLY " using these tables.Since this database is part
of pretty big complex environment with lots of dependencies and many
user/development groups of this database were not sure about these tables in
place( might be because this database is pretty old (7.3 to 8.1.7.4) with 4+TB
).I known best way do this kind of work is "Database Audit trails".

2.don't have the ability to enable auditing (that is just silly)?. <reviewer> :
Is there a way that the audit trail could be 'secured' so the DBA can enable it
but not touch it ?  THat might solve the problem ?

It looks silly.But its a "FACT".we (I mean security folks) can able to do
database audit trails on these tables,but for this kind of request it takes
"ATLEAST" 4-6weeks time,to just implemented the request(don't ask why so much
time?..it like this :).. ) and I have just hardly 4weeks to find the activity on
these tables. So,instead of wasting time in requesting security folks,If I start
with some good alternate method for audit trails and track activity for 4weeks
,I will be good shape.

I hate to say , this is my plan

1. Track activity with triggers for update/insert/delete and for selects uses
v$access/v$session for 3 first weeks.
2. rename the tables ,wait for any callers or pagers - as suggested by Tom for
last 1 week.
3. Final email notice to all posssible user groups,before making the tables
absolute.

Thanks Tom and all reviewers for taking time to share your knowledge.

If any one got good alternate to trace selects ,please let me know.

June 16, 2003 Reji -- Thanks for the question regarding "How to
      register a new db with an new/existing listener?", version 8.1.6

      You Asked
Tom:
    I have a tool to create an Oracle DB and how do I
    register that db with the listener?

    TIA
    Reji

      and we said...
Using the Net8 assistant found in the Network Administration
Group you can do this.

Assuming your oracle_home is c:\oracle and you are using 8.1.6,
the following command:

C:\oracle\BIN\launch.exe "C:\oracle\network\tools" netasst.cl
will start it up as well.

As well, you can modify the listner.ora file by hand as well.
Review &
      Followup

      Rating:
      5
  June 16, 2003
Reviewer:
      A reader
tom,

i am trying to create a database on my machine.. i dont see the oracle menu
under  start->program-> ...->

i tried this ...
C:\>C:\oracle\ora81b\bin\launch.exe "c:\oracle\ora81b\assistants\dbca" dbca.cl

but i am not getting the DBCA window coming up.. please help

thanks

          Followup:

well, if the start menu thingys aren't there either:

a) your install didn't go as planned and so all bets are off
b) you removed them

if a is true, you need to fix that.

if b is true, maybe someone that does have the innovation that is the start menu
on their machine can let us know the command that is being run -- i don't have
windows installed to take a look see.

      Rating: 3
try it without
      the double quotes  June 16, 2003
Reviewer:
      John  from Round Rock, TX
Try running the command without quotes.

e.g.

D:\oracle\ora92\bin\launch.exe D:\oracle\ora92\assistants\dbca dbca.cl

June 16, 2003 Kevin -- Thanks for the question regarding "SSL",
      version 8.1.6

      You Asked
Yes. I have Java Server Option.
When I access informations on Web site, they told about Cookie. But I know that
Cookie is a view of Client at Server side.
I want to know the solution that process as far as Client of Web
Browser.Regards.

Thanks for the reply about UTL_HTTP.
To use a PL/SQL program that is stored in Web Server, I make a another PL/SQL
Program.
Through UTL_HTTP.reguest('HTTP://abc.xxx.com/...'), I can contact the Web server
successfully. But after that, Web server did not provide any reply. I got NULL
response.
I guess this problem as follows:
Because when I issue the URL on Web browser, Web server asked Userid and
Password for authentification process. Therefore, when I issue UTL_HTTP, Web
server ask the same Authentification process.
I want to ask you that "How to pass the user-name and password on PL/SQL
program?"
Thanks.

      and we said...
grab

http://osi.oracle.com/~tkyte/utl_http2.tar
you would use install.sql to install it via sqlplus.  the OWNER of this code
MUST have been granted the javasyspriv in order to run this.

the file utlhttp2.pkh is commented.  As well - utlhttp_test.sql has lots of
examples.

This is a better utl_http that supports cookies, extended error handling, blobs,
clobs, and AUTHENTICATION.

Review &
      Followup

      Rating: 1
What does this
      have to do with SSL (the title)?  September 04, 2002

Reviewer:  Michael O'Neill
I am searching for examples of using utl_http with SSL (https) and this posting
didn't relate to SSL.  Even the utl_http2 package provided by link did not have
any SSL support either.

          Followup:

Well, I don't make up the titles -- you guys do.  (and given that a search for

 utl_http ssl

returns 2 hits -- i seriously doubt you wasted very much time eh?)

They obviously confused SSL with basic authentication....

However -- if you have my book "Expert one on one Oracle" -- I show how to do
SSL with UTL_HTTP from step 1 to step N...  There are some steps (and believe
me, it took a bit of "discovery" to figure it out).  I extracted the relevant
text here but the screen shots are of course "missing" (motivation to buy the
book!)

Using SSL with UTL_HTTP

UTL_HTTP also supports using SSL (Secure Sockets Layer).  If you are not
familiar with SSL and what it is used for, you can find a brief description at

http://www.rsasecurity.com/rsalabs/faq/5-1-2.html
Both the REQUEST and REQUEST_PIECES functions in UTL_HTTP support the
retrieval of URLS that are protected by SSL – however the documentation on doing
so is sparse at best.  SSL support is provided by using the last two parameters
to the UTL_HTTP.request and UTL_HTTP.request_pieces procedures.  These
parameters are the wallet_path and wallet_password.

Oracle uses the wallet as a metaphor for how a person stores their security
credentials – just like you would keep your driver’s license and credit cards in
your wallet for identification purposes, the Oracle wallet stores the
credentials needed by the SSL protocol. The wallet_path is the directory where
your wallet is stored on the database server machine. This wallet is password
protected to prevent someone from using your credentials. That is the purpose of
the wallet_password parameter, it is used to access the wallet. The password
prevents people from copying the wallet directory and trying to impersonate you,
as they will be unable to open and access the wallet. This is analogous to using
a PIN for using an ATM machine. If someone steals your bank card, they need to
have your PIN to get to your accounts.

The wallet, or the concept of a wallet is used not only by the Oracle database,
but also in Web browsers. The important aspect is that when you connect to a
site, e.g., www.amazon.com, how do you know that it is really Amazon.com?  You
have to get their certificate. Their certificate is digitally signed by someone
– that someone is called a Certificate Authority or CA. How does my browser or
database know to trust the CA that signed that certificate?  For example, I
could create a certificate for Amazon.com and sign it from hackerAttackers.com.
My browser and database should not accept this certificate even though it is a
legitimate X.509 certificate.

The answer to this trust issue is the wallet stores a set of trusted
certificates. A trusted certificate is a certificate from a CA that you trust.
The Oracle wallet comes with some common trusted certificates. You also have the
ability to add certificates as necessary. Your browser does the same thing. If
you ever connect to a site where your browser does not have the CA in its
wallet, you will get a pop-up window that notifies you of this as well as a
wizard that allows you to proceed or abort your connection.

Let’s see some examples of how to use SSL. First, we need to create a new
wallet. You can invoke the owm (Oracle Wallet Manager) program on UNIX, or
launch it from the Windows start menu on Windows. The screen you receive to do
this will look like this:

All you need to do is click on the green “plus” icon located on the left hand
side of the display.  It will prompt you for a password for this wallet – you
are making up the password at this point so enter whatever password you would
like.  You may get a warning about a directory not existing at this point – if
you do, you should simply ignore it.  It is the expected behavior if you have
never created a wallet before.  OWM at this point will ask you:

You do not need to create a certificate request. The certificate request is so
you can get a certificate for yourself. This would be used in SSL v.3 where the
server needs the identification of the client. Most Web sites do not
authenticate users via certificates, but rather username and password. This is
because an e-commerce site doesn’t care who is buying from them as long as they
get the money. But you care that you are sending the money (and credit card
information) to the correct entity, so we use SSL v.2 to identify the server
e.g., Amazon.com, and to provide all the encryption of data. So, click NO in
response to this and save the wallet by clicking on the yellow floppy disk icon
and we are ready to go.

Let’s go to Amazon.com first. Amazon’s certificate was signed by Secure Server
Certificate Authority, RSA Data Security, Inc. This is one of the defaults in
the Oracle wallet.

tkyte@TKYTE816> declare
  2     l_output long;
  3
  4     l_url varchar2(255) default
  5           'https://www.amazon.com/exec/obidos/flex-sign-in/';
  6
  7     l_wallet_path varchar2(255) default
  8           'file:C:\Documents and Settings\Thomas Kyte\ORACLE\WALLETS';
  9
 10
 11  begin
 12    l_output := utl_http.request
 13               ( url             => l_url,
 14                 proxy           => 'www-proxy.us.oracle.com',
 15                 wallet_path     => l_wallet_path,
 16                 wallet_password => 'oracle'
 17               );
 18    dbms_output.put_line(trim(substr(l_output,1,255)));
 19  end;
 20  /

<html>
<head>
<title>Amazon.com Error Page</title>
</head>
<body bgcolor="#FFFFFF"
link="#003399" alink="#FF9933" vlink="#996633" text="#000000">
<a name="top"><!--Top of
Page--></a>
<table border=0 width=100% cellspacing=0 cellpadding=0>
<tr

PL/SQL procedure successfully completed.

What?  An Error Page?  Don’t worry, this is accurate - try it in your browser.
The reason is that there is no session information being passed. The point is
that the connection worked, we just retrieved an SSL protected document.

Let’s try another site. How about E*Trade?

tkyte@TKYTE816> declare
  2     l_output long;
  3
  4     l_url varchar2(255) default
  5           'https://trading.etrade.com/cgi-bin/gx.cgi/AppLogic%2bHome';
  6
  7     l_wallet_path varchar2(255) default
  8           'file:C:\Documents and Settings\Thomas Kyte\ORACLE\WALLETS';
  9
 10
 11  begin
 12    l_output := utl_http.request
 13               ( url             => l_url,
 14                 proxy           => 'www-proxy.us.oracle.com',
 15                 wallet_path     => l_wallet_path,
 16                 wallet_password => 'oracle'
 17               );
 18    dbms_output.put_line(trim(substr(l_output,1,255)));
 19  end;
 20  /
declare
*
ERROR at line 1:
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at "SYS.UTL_HTTP", line 174
ORA-06512: at line 12

That apparently does not work. E*Trade has a certificate signed by
www.verisign.com/CPS Incorp.by Ref, which is not a default trusted certificate.
In order to access this page, we’ll have to add that certificate to our Oracle
wallet - assuming of course that we trust Verisign!  Here is the trick. Go to
the site (https://trading.etrade.com/cgi-bin/gx.cgi/AppLogic%2bHome) with
Microsoft Internet Explorer. Double-click the lock icon on the bottom right
corner of the window. This will pop-up a window that looks similar to this one:

Select the Certification Path tab on the top of this screen. This lists the
certificate you are viewing i.e., the one for E*Trade (trading.etrade.com), as
well as who issued the certificate. We need to add the person who signed the
certificate (the issuer) to our trusted certificates in the Oracle wallet. The
issuer is www.verisign.com/CPS Incorp.by Ref. LIABILITY LTD as depicted by the
tree-like hierarchy.

Click the View Certificate button while the www.verisign.com/CPS Incorp. by Ref.
is highlighted. This shows information for the issuer’s certificate. Click the
Details tab and you should see:

Now we need to Click the Copy to File… button. Save the file locally as a
Base-64 encoded X.509 (CER) file.  The following screen shows the selection you
should make, you can name the file anything you choose and save it anywhere.
We’ll be importing it in a moment, just remember where you save it to:

Now we can import this into our Oracle Wallet. Open the wallet in OWM and right
click on the Trusted Certificates – this will present you with a popup menu that
has “Import Trusted Certificate”:

You will select that option and in the next dialog that comes up, choose “Select
a file that contains the certificate”

Use the standard “file open” dialog that comes up to choose the certificate you
just saved.  Your screen should now look something like this:

Now, save the wallet using the yellow floppy disk icon and let’s try our example
again:

tkyte@TKYTE816> declare
  2     l_output long;
  3
  4     l_url varchar2(255) default
  5           'https://trading.etrade.com/cgi-bin/gx.cgi/AppLogic%2bHome';
  6
  7     l_wallet_path varchar2(255) default
  8           'file:C:\Documents and Settings\Thomas Kyte\ORACLE\WALLETS';
  9
 10
 11  begin
 12    l_output := utl_http.request
 13               ( url             => l_url,
 14                 proxy           => 'www-proxy.us.oracle.com',
 15                 wallet_path     => l_wallet_path,
 16                 wallet_password => 'oracle'
 17               );
 18    dbms_output.put_line(trim(substr(l_output,1,255)));
 19  end;
 20  /
<HTML>
<HEAD>
<META http-equiv="Content-Type" content="text/html;
charset=ISO-8859-1">
<TITLE>E*TRADE</TITLE>
<SCRIPT LANGUAGE="Javascript"
TYPE="text/javascript">
        <!--

        function mac_comment(){
                var
agt=navigator.userAgent.toLowerCase();
                var is_mac

PL/SQL procedure successfully completed.

This time we are successful. Now we know how to use and extend the Oracle wallet
to do secure HTTPS.

      Rating: 4
utl_http and
      Proxy authetication  September 12, 2002
Reviewer:
      Jeff Yuan  from AZ USA
Tom,

Thank you for the comments on this topic and the chapters on utl_http and
utl_tcp in your wonderful book. I get a question on how to use Proxy userid and
password to pass the authetication. When I called your packages, I get "Access
is Denied" error, same as utl_http.request does. I am using 9iR2. I searched
Oracle documents and Metalink, and did not get a solution. Could you please help
me on this?

Regards,

Jeff
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
jyuan@TMP9> select http_pkg.request('
http://www.oracle.com/',
'walbar-proxy:8080') from dual;

HTTP_PKG.REQUEST('HTTP://WWW.ORACLE.COM/','WALBAR-PROXY:8080')
------------------------------------------
Error: Access is Denied.

jyuan@TMP9> DECLARE
  2 c utl_tcp.connection; -- TCP/IP connection to the web server
  3 n number;
  4 buffer varchar2(255);
  5 BEGIN
  6 -- c := utl_tcp.open_connection('proxy-server', 80);
  7 c := utl_tcp.open_connection('walbar-proxy', 8080);
  8 n := utl_tcp.write_line(c, 'GET
http://www.oracle.com/
HTTP/1.0');
  9 n := utl_tcp.write_line(c);
 10    BEGIN
 11       LOOP
 12           n:=utl_tcp.read_text( c, buffer, 255 );
 13           dbms_output.put_line( buffer );
 14       END LOOP;
 15     EXCEPTION
 16       WHEN utl_tcp.end_of_input THEN
 17        NULL; -- end of input
 18    end;
 19     utl_tcp.close_connection(c);
 20  END;
 21  /
HTTP/1.0 407 Proxy authentication required Proxy-Authenticate: NTLM
Proxy-Authenticate: Basic
realm="170.777.115.5" Content-Length: 24 Content-Type: text/html
Error:
Access is Denied.

PL/SQL procedure successfully completed.

          Followup:

Your PROXY server requires a username and password.

I did not program that in my package (i didn't have a firewall that required
one, so I couldn't test it)

You'll have to dig out what the correct header would be and send it along.

      Rating: 4
Trying it with
      9iR2  April 11, 2003
Reviewer:  Jack Wells
       from Boca Raton, FL
I want to compare the performance of utl_http2 with the newly revamped one in
9iR2 (9.2.0.3).  Even though I'm granting all (i mean ALL) privileges to the
target user as SYS I'm getting java errors.  Any idea?

My grants:
BEGIN
  dbms_java.grant_permission ('WWW', 'java.net.SocketPermission', 'hostname',
'resolve');
  COMMIT;
  dbms_java.grant_permission ('WWW', 'java.util.PropertyPermission', '*',
'read,write');
  COMMIT;
  dbms_java.grant_permission ('WWW', 'java.io.FilePermission', '/tmp', 'read');
  COMMIT;
END;

Other (pre-9i, non-necessary but reaching attempt):

grant javasyspriv to user;
grant javauserpriv to user;
grant javadebugpriv to user;

Results when running @@jsock.java:
0/0      jsock:10: Class socket not found.
0/0      jsock:10: Incompatible type for new. Can't convert <error>[] to
         socket[].

0/0      jsock:10: Class socket not found.
0/0      jsock:12: Class string not found.
0/0      jsock:13: Class java.io.ioexception not found in throws.
0/0      jsock:20: Class socket not found.
0/0      jsock:28: Class java.io.ioexception not found in throws.
0/0      jsock:30: Class socket not found in void java_send_data(int,
         byte[]).

0/0      jsock:35: Class java.io.ioexception not found in throws.
0/0      jsock:38: Class socket not found in void java_recv_data(int,
         byte[][], int[]).

0/0      jsock:41: Class string not found.
0/0      jsock:42: Class java.io.ioexception not found in throws.
0/0      jsock:44: Class datainputstream not found.
0/0      jsock:44: Class datainputstream not found.
0/0      jsock:50: Class java.io.ioexception not found in throws.
0/0      jsock:54: Class socket not found in void java_disconnect(int).
0/0      jsock:55: Class socket not found in void java_disconnect(int).
0/0      jsock:60: Class java.io.ioexception not found in throws.
0/0      jsock:62: Class socket not found in int java_peek_sock(int).
0/0      jsock:65: Class string not found.
0/0      jsock:66: Class java.net.unknownhostexception not found in
         throws.

0/0      jsock:68: Undefined variable or class name: inetaddress
0/0      jsock:71: Class string not found.
0/0      jsock:73: Class date not found.
0/0      jsock:73: Class date not found.
0/0      jsock:74: Class dateformat not found.
0/0      jsock:74: Class simpledateformat not found.
0/0      jsock:81: Class string not found.
0/0      jsock:83: Class date not found.
0/0      jsock:83: Class date not found.
0/0      jsock:84: Class dateformat not found.
0/0      jsock:84: Class simpledateformat not found.
0/0      jsock:90: Class string not found.
0/0      jsock:92: Class base64encoder not found.
0/0      jsock:92: Class base64encoder not found.
0/0      Info: 35 errors

          Followup:

looks alot like java ain't installed in that database?

      Rating: 3
How can I work
      it out in HTTPS POST  June 16, 2003
Reviewer:
      Gordon Hou  from Mississauga, ON CA
I need to transmit an xml (in CLOB) via HTTPS protocol.  I can't find POST
function/procedure in UTL_HTTP.  I'm wondering if your utl_http2 works for me?

          Followup:

sorry, I don't do "https" (fairly complex protocol)...

you'll have to load some java packages in there.

http://asktom.oracle.com/~mbpierma/SSL_Java_DB.html
has a "for example"

      Rating: 3
UTL_HTTP2 not
      handling attachments  October 22, 2003
Reviewer:
      Dr. Luca Toldo  from Darmstadt, Germany, USA
The UTL_HTTP2 package that Tom povides is very nice however it is incomplete.
Infact, it does not handle the "file upload" case.
Namely, if one (like myself) needs to do a POST with
"Content-type=multipart/form-data" from an 8.1.7 Oracle machine, this package is
not capable of doing so
since does not handle the case appropriately.

      Rating:
      5
Great!  December 13, 2003
Reviewer:
      Andy  from Reading, UK
In the followup to this is the only place I have found on the Web that shows how
to use utl_http with https, wallets etc.

Tom, you mention your book here, I will buying it.

June 16, 2003 Yun-Ho -- Thanks for the question regarding
      "Create a table hierarchy for an entire schema", version 8.1.7

      You Asked
Tom,

I took the unloader package from your book, and changed it slightly, but I can't
quite get what I want.  I changed it so that it could unload all the tables and
data for an entire schema.  That works, but I want to be able to load the data
back into the tables in dependency order, and I can't quite get the logic.

I have not found any code that shows how to build a table hierarchy for an
entire schema.  I tried using the user_constraints data dictionary view, but I
get the error:

ORA-01437: cannot have join with CONNECT BY

So I created my own constraints table (without the LONG column,
search_condition):

CREATE TABLE my_constraints
AS
SELECT constraint_name, constraint_type, table_name,
       r_owner, r_constraint_name,
       delete_rule, status, deferrable, deferred,
       validated, generated, bad, rely, last_change
  FROM user_constraints
/

Then I tried to create the hierarchy by doing this:

SELECT RPAD('*', LEVEL*2, '*')|| table_name
  FROM my_constraints
 START WITH constraint_type = 'P'
CONNECT BY PRIOR constraint_name = r_constraint_name
/

My results don't nest but one deep:

RPAD('*',LEVEL*2,'*')||TABLE_NAME
----------------------------------------
**DEFECTS
****DEFECT_REV_MATERIALS
**DEFECT_CATEGORIES
****DEFECTS
****DEFECT_TYPES
**DEFECT_REV_MATERIALS
**DEFECT_TYPES
****DEFECTS
**ISSUES
****ISSUE_REV_MATERIALS
**ISSUE_REV_MATERIALS
**MEETINGS
**PARTICIPANTS
****PARTICIPANT_ROLES
**PARTICIPANT_ROLES
**PREFERENCES
****SEARCH_PREFERENCES
****TABLE_PREFERENCES
**PREFIXES
****PREFIX_COMPONENTS
**PREFIX_COMPONENTS
**REVIEWS
****DEFECTS
****ISSUES
****MEETINGS
****PARTICIPANTS
****REVIEWS
****REVIEW_MATERIALS
****SUPPORT_MATERIALS
**REVIEW_MATERIALS
****DEFECT_REV_MATERIALS
****ISSUE_REV_MATERIALS
**ROLES
****PARTICIPANT_ROLES
**SEARCH_PREFERENCES
**SUPPORT_MATERIALS
**TABLE_PREFERENCES

37 rows selected.

The results do not nest like I want them to.  I want to see a structure like
this (partial):

**REVIEWS
****DEFECTS
******DEFECT_REV_MATERIALS
****ISSUES
******ISSUE_REV_MATERIALS
****REVIEW_MATERIALS
******DEFECT_REV_MATERIALS
******ISSUE_REV_MATERIALS

Thanks in advance for your help.  You truly are the best!

      and we said...
my advice -- do it like EXP/IMP does, alphabetically (or just "in any order")

create tables
load tables
create constraints

Some tables -- with self referencing integrity for example, are impossible to
load otherwise!

If you cannot load without constraints (cause the constraints are "just there"),
just write a small procedure to disable fkeys, load the data, re-enable the
fkeys.
Review
      & Followup

      Rating: 4
What about a
      set of "golden" data  June 16, 2003
Reviewer:
      Yun-Ho Sikora  from St. Louis, MO USA
Tom,

We normally work in the manner you suggested, disable constraints, use exp/imp,
re-enable constraints.

However, the situation that caused us to investigate this strategy of loading
data using SQL*Loader came about in the following scenario:

1.  Developers run acceptance testing for Version 1.0 of their application using
a set of known data.  Export the "golden" data and put into Configuration
Management.
2.  Developers ready to release Version 2.0.  They want to run regression
testing with their "golden" data.  So we create their Version 2.0 database and
try to run import.

On step 2, we often get failures because:

a.  The schema has changed.
b.  The export file, which is saved by the developers, was somehow erroneously
saved, maybe they didn't save it in binary or some other user error.

At this point we're stuck, because we can't read the export file to make any
changes or corrections.  We thought the SQL*Loader route (using the unloader)
package was a better choice because it is possible to compensate for the schema
changes and the control file and the data file are both ASCII text files.

Or do you know of any other way to save off a set of "golden" data for
regression testing?

Thanks for your help.

          Followup:

I didn't say "use exp/imp", I said -- do it LIKE exp/imp would -- in any old
order.

but in any case, to address this comment:

a) won't be affected by doing a dump and load will it.  You'll have the same
incompatible data.  In any case, you can always create views that look like the
old schema and import into them (it'll just be insert intos after all).  You can
use the dmp file -- heck, you can import into the "golden" schema and then
perform the necessary ETL on it (you know, RUN YOUR APPLICATION V2.0 UPGRADE
SCRIPT that you must already have -- to test that as well)

b) that, well, that is a procedural mess up and saving to flat files isn't going
to help you if the developers break the files.

I would just backup the "golden" data (database backups)

I would just restore the "golden" data -- and if the schema changed, I would
take that opportunity to TEST the upgrade scripts they must have developed.

      Rating:
      5
  June 16, 2003
Reviewer:  Yun-Ho
      Sikora  from St. Louis, MO USA
Okay, I get your point that we probably have more procedural problems than
anything else -- we do try to create the old schema, import the data, then apply
the upgrade scripts -- it seems as if there is always some sort of gotcha.

Just one more thing, I don't know what ETL means.

          Followup:

extract transform load -- "make the data fit"

If you have a problem with "create the old schema, import the data, then apply
the upgrade scripts", then the upgrade scripts don't work and the developers
have a bug to fix.

June 17, 2003 Monish -- Thanks for the question regarding "How
      to pass a '&' in url.", version 9ias

      You Asked
Hi Tom,

In mod_plsql: I need to pass '&' as a part of data via a url to a stored
procedure.

Stored Procedure Name: show
Parameters: category1 (varchar2(120)), category2 (varchar2(120))

I am not able to send the following url:

http://xyz/pls/mydad/show?category1=toys%20&%20games&category2=abc
I have tried using: htf.escape_url and htf.escape_sc, but somehow not able to
fix it yet.

I shall appreciate if you could suggest a good solution.

Thanks,
Monish

      and we said...
well,  you already sort of knew the answer ;)

You knew how to url encode the blank as %20 -- that is 20 hex, which is 32 ascii
which is the ascii code for "space".

You could select to_char( ascii('&'),'xx') from dual;  to get the hexcode for &,
but there are other characters to worry about as well.  So, I use this function:

ops$tkyte@ORA920> create or replace function urlencode( p_str in varchar2 )
return varchar2
  2  as
  3          l_tmp   varchar2(12000);
  4          l_len   number default length(p_str);
  5          l_bad   varchar2(100) default ' >%}\~];?@&<#{|^[`/:=$+''"' ||
chr(10);
  6          l_char  char(1);
  7  begin
  8          if ( p_str is NULL ) then
  9                  return NULL;
 10          end if;
 11
 12          for i in 1 .. l_len loop
 13                  l_char :=  substr(p_str,i,1);
 14                  if ( instr( l_bad, l_char ) > 0 )
 15                  then
 16                          l_tmp := l_tmp || '%' || to_char(ascii(l_char),
'fm0X');
 17                  else
 18                          l_tmp := l_tmp || l_char;
 19                  end if;
 20          end loop;
 21
 22          return l_tmp;
 23  end;
 24  /

Function created.

which lets me code:

 variable := '
http://xyz/pls/mydata/show?category1='
||
              urlencode( 'toys & games' ) ||
             '&category2=' || urlencode('abc');

which gives me the string:

'HTTP://XYZ/PLS/MYDATA/SHOW?CATEGORY1='||URLENCODE('TOYS&GAMES')||'&CATEGORY2='||
URLENCODE('ABC')
---------------------------------------------------------------------------------
--------------------------------------------------

http://xyz/pls/mydata/show?category1=toys%20%26%20games&category2=abc

Review &
      Followup

      Rating: 5
I also noticed
      that ' ' is converted into a '+' by html forms.  June 15,
      2003
Reviewer:  Monish
Thanks Tom. The solution was very helpful.

Also I noticed that if we submit the data via html form (action method=get), it
coverts blank spaces into '+' instead of '%20'.

          Followup:

yes, that is valid in a URL encoded string (and why you have to URL encode a
plus that should be a plus!)

      Rating: 4
can you use
      utl_url.escape function?  June 16, 2003
Reviewer:
      Yong  from CA, USA
I am use utl_url.escape(string) for any escape.

          Followup:

that should work as well.

June 17, 2003 Winston -- Thanks for the question regarding
      "Oracle 9i database performance tuning guid and reference", version Oracle
      9i

      You Asked
Hi Tom,

I am reading Oracle9i database Performance Tuning Guide and REference and I
don't understand why in that document it says:
<quote>
If all keys are used in WHERE clauses equally often, then ordering these keys
from most selective to least selective in the CREATE INDEX statement best
improves query performance
</quote>

could you please refer to
http://otn.oracle.com/pls/db92/db92.to_URL?remark=drilldown&urlname=http:%2F%2Fdownload-west.oracle.com%2Fdocs%2Fcd%2FB10501_01%2Fserver.920%2Fa96533%2Fdata_acc.htm%2319964?
Do you think there is any merit there?

Thanks for clarification,
Winston

      and we said...
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/data_acc.htm#19964
is a doc bug, hopefully fixed in the next release.

the order of key entries when you query:

where a = :x and b = :y and c = :z

is not relevant -- an index on (a,b,c) will perform as well as an index on
(c,b,a) -- even if c has 2 unique values and a has 1 billion unique values.

However, if the predicate is

where a > :x and b = :y and c = :z

there is something to be said for putting A dead last.

consider:

ops$tkyte@ORA920> create table t ( a int, b char(20), c char(20), d int );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t
  2  select id, 'x', 'x', id
  3    from big_table.big_table
  4   where rownum <= 100000;

100000 rows created.

ops$tkyte@ORA920> insert into t values ( 0, 'Y', 'Z', 0 );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index t_idx1 on t(a,b,c);

Index created.

ops$tkyte@ORA920> create index t_idx2 on t(c,b,a);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select /*+ index( t t_idx1 ) */ *
  2    from t
  3   where a > -1 and b = 'Y' and c = 'Z';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=70)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=1 Card=1 Bytes=70)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX1' (NON-UNIQUE) (Cost=2 Card=1)

ops$tkyte@ORA920> select /*+ index( t t_idx2 ) */ *
  2    from t
  3   where a > -1 and b = 'Y' and c = 'Z';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=70)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=1 Card=1 Bytes=70)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX2' (NON-UNIQUE) (Cost=2 Card=1)

ops$tkyte@ORA920> set autotrace traceonly statistics
ops$tkyte@ORA920> select /*+ index( t t_idx1 ) */ *
  2    from t
  3   where a > -1 and b = 'Y' and c = 'Z';

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

ops$tkyte@ORA920> /

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

ops$tkyte@ORA920> select /*+ index( t t_idx2 ) */ *
  2    from t
  3   where a > -1 and b = 'Y' and c = 'Z';

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

ops$tkyte@ORA920> /

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

ops$tkyte@ORA920> set autotrace off
ops$tkyte@ORA920>

which index would you prefer in this case?  the order of the columns in a
index have NOTHING to do with the selectivity (thats a myth), they have
everything to do with the way in which you USE them.

Now just to finish the example, using strict equality across the board:

ops$tkyte@ORA920> declare
  2          l_rec t%rowtype;
  3  begin
  4          for x in ( select * from t )
  5          loop
  6                  select /*+ index( t t_idx1 ) */ * into l_rec from t
                      where a = x.a and b = x.b and c = x.c;
  7                  select /*+ index( t t_idx2 ) */ * into l_rec from t
                      where a = x.a and b = x.b and c = x.c;
  8          end loop;
  9  end;
 10  /

SELECT /*+ index( t t_idx1 ) */ * from t where a = :b3 and b = :b2 and c = :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute 100001     34.91      35.49          0          0          0           0
Fetch   100001     11.39      11.42          1     400815          0      100001
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   200003     46.31      46.92          1     400815          0      100001

Rows     Row Source Operation
-------  ---------------------------------------------------
 100001  TABLE ACCESS BY INDEX ROWID T (cr=400815 r=1 w=0 time=9546234 us)
 100001   INDEX RANGE SCAN T_IDX1 (cr=300814 r=1 w=0 time=6345565 us)(object id
52964)
--------------------------------------------------------------------------------
SELECT /*+ index( t t_idx2 ) */ * from t where a = :b3 and b = :b2 and c = :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute 100001     34.77      35.09          0          0          0           0
Fetch   100001     11.19      12.20        816     400815          0      100001
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   200003     45.97      47.29        816     400815          0      100001

Rows     Row Source Operation
-------  ---------------------------------------------------
 100001  TABLE ACCESS BY INDEX ROWID T (cr=400815 r=816 w=0 time=10302154 us)
 100001   INDEX RANGE SCAN T_IDX2 (cr=300814 r=816 w=0 time=7099472 us)(object
id 52965)

look pretty much to be "the same".... a,b,c or c,b,a....

Look to how you USE the index, not the values therein.

Review &
      Followup

      Rating: 5
Thanks for your
      clarification  June 15, 2003
Reviewer:
      Winston
I suspected it's a bug, it's a myth when I came across it because I remmeber you
have explained in your 1st book and you have included this in 'Qustion
Authority' section of your presentation. But I don't understand why Oracle
employees who drafted/reviewed this very important document hadn't read your
book, learned something from your web site/presentation.

      Rating:
      4
Why?  June 16, 2003
Reviewer:
      Kashif  from Houston, TX
Hi Tom,

Following up on your explanation, why does index t_idx2 work better than t_idx1?
Why does the order of columns matter in this case?

Kashif

          Followup:

think about what it must do:

  3   where a > -1 and b = 'Y' and c = 'Z';

if the index is on A, B, C and many rows in the table have A > -1, then we must
range scan ALL of those values in the index.  In this case, i set it up so that
the index keys were "large" (45'ish bytes apiece) and 100,001 of them were A >
-1.  So, you have to look at 100,001 keys to see if b = Y and c = Z (only 1 did)

then, if the index is on C, B, A -- we have to find the index leaf block with C
= Z and B = Y -- then scan for A > -1 -- all of one entry.  So, we processed 1
leaf row.

Think about a phone book.  You are told to find "all people who's name starts
with M and lives on Broad Street, House Number 5"

do you want a phone book sorted by

NAME, STREET, HOUSE NUMBER

or

HOUSE NUMBER, STREET, NAME

which would be easier for you to find the information -- same answer applies for
the database.

      Rating: 5
Excellent
      explanation  June 18, 2003
Reviewer:
      Michael  from Germany

June 17, 2003 AC -- Thanks for the question regarding "Automate
      loading of data from Excel spreadsheets into database", version 7.3

      You Asked
Hi Tom,

Is there a any way whereby I can automate the loading of data
from an excel spreadsheet into my database?
The only way we know is to manually export the data into an
ascii
file and then load it into the database using sqlload.

My database is sitting on a Netware server.

Thanking you in advance.

      and we said...
There are a couple of ways to approach this.

1) load the excel spreadsheet into the database "as is".  You
can use interMedia text to convert the .xls file into a .htm
file (HTML) or use iFS (see
http://technet.oracle.com/
for more info on that) to parse
it as well.  InterMedia text will convert your XLS spreadsheet
into a big HTML table (easy to parse out what you need at that
point)

2) Using OLE automation, a program you write can interact with
Excel, request data from a spreadsheet, and insert it.  Oracle
Forms is capable of doing this for example as is other languages
environments.  In this fashion, you can remove the "manual" and
"sqlldr" parts -- your program can automatically insert the
data.

3) You can write a VB script that uses ODBC or Oracle Objects
for OLE (OO4O) in Excel.  This VB script would be able to put
selected data from the spreadsheet into the database.  We would
recommend OO4O.  It provides an In-Process COM Automation Server
that provides a set of COM Automation interfaces/objects for
connecting to Oracle database servers, executing queries and
managing the results.  OO4O is available from

http://technet.oracle.com/
Review
      & Followup

      Rating: 2
Automating
      Excel to Form  July 03, 2001
Reviewer:  Sara
      Swartz  from Ft. Lauderdale Florida
I was directed here from another forum in order to find how to import the data
from an excel spread sheet to a form. There is no link included that will show
you how to do this. In fact, the only links were to the front page of the Oracle
Technology network. This is the same as a kid wanting to know about asteroids
and you drop her off in front of the city library. Thanks, but I already know
about the OTN, and I know that finding anything in it is simply a matter of
luck. The least you could have done was provided a link to the specific page
that held the information.

      Rating: 5
Access Database
      to Oracle  June 06, 2002
Reviewer:  Munz
       from Reston, USA
Tom:

I have an access table that I am trying to take to oracle 8.1.7.  I export the
table in access to ODBC database using a data source I create for my oracle
database and the DataDirect 4.1 Oracle Driver ( I also tried microsoft ODBC for
oracle).

The export happens and then When I go to oracle, I see the table when I do
"Select table_name from user_tables".

However when I do "desc table" or "select from table" it tells me that table
does not exist.

I cannot even delete the table.

Do you know what is happening?

          Followup:

is the table name in mixed case?  if so, make sure to use quoted identifiers

desc "tableName"
select * from "tableName"

      Rating: 5
Access
      database  June 06, 2002
Reviewer:  Munz
       from Reston, USA
Tom:

Yes I found that table name is in lowercase.  I re-did this from access and
named table uppercase.

this is weird.  I guess when I do  "desc table" it is always looking for
uppercase table.

1.  Can i change table name from lowercase to uppercase.

2. to delete it shall i do "drop table "emp" ";

3.  Can I use Oracle Migration Assistant to move data from all kind of
databases.

4.  I installed oracle ODBC driver for 8.1.7 database.  However when I try to
create a data source I can not see it listed in the list of drivers available in
the control panel.  Do you have to copy files to a certain directory in windows?

THank you

          Followup:

Well, in ANSI standard sql -- identifiers are case insensitive.

In sqlserver "standard" they are case sensitive.

We are not looking for "uppercase" in as much as they stored it in a particular
case.

1) one method:

create view tableName as select * from "tableName"

otherwise, create the table in UPPERCASE in their stuff....

2) to delete, use 'delete from "emp"'.  to DROP -- very different from delete,
use 'drop table "emp"'

3) there is the migration assistant with the "upgrade process" and there is the
migration workbench with a tool to migrate from sqlserver/informix/db2/etc to
Oracle.

The migration toolkit/workbench -- you get that for a database version/release.
We have many toolkits for you to use.  You need to get the toolkit that applies.

4) no idea, never used odbc in my life -- yet.  sorry.

      Rating: 4
Excel to
      Oracle  June 07, 2002
Reviewer:  Iain
       from Scotland, UK
For Question 4.

Ensure the  <Oracle Home Path>\bin is set in environment path.
Alternatively.....more a quick fix than a proper solution

Copy the SQORA32.DLL (not sure if same in 8.1.7) from <Oracle Home Path>\bin to
the Windows\system32 directory

      Rating: 5
Oracle
      Workbench  June 07, 2002
Reviewer:  Monz
       from Reston, USA
Tom:

1.  If you are converting an INFORMIX or SYBASE database, woudl the workbench
re-create all objects (i.e. tables, indexes, primary keys, foreign keys,
sequences, stored procedures.?

2. WOuld it convert the informix stored procedure to a PL/SQL stored procedure.

          Followup:

1) It would do "its best".  tables, indexes, primary key, foreign keys,
sequences - - pretty much dead on.  procedures, well.... code conversion is
always a tricky thing.  Say it follows the 80/20 rule -- it does the rote stuff,
leaving the interesting 20% for you.

2) it would "try".

      Rating: 5
informix
      schema  September 07, 2002
Reviewer:  ko
       from mclean, va
Tom:

When using the workbench to migrate informix database it takes all tables and
objects and create them in the oracle database under schema "informix" and not
the oracle dba schema.

Since application uses the developer acount to log in I have to refer to all
informix tables using schema.table.

Can you just export all objects out of informix schema and import it into
devadmin schema?

But then delete the informix schema or you have to do it though unix for this?

Is there a better way of doing this?

THank you,

          Followup:

Well, there is probably a setting somewhere in there (haven't used the workbench
personally) to set the schema -- i would have to imagine there would be but
suppose there isn't.

A simple:

exp userid=informix/password owner=informix
imp userid=devadmin/password fromuser=informix touser=devadmin

will get it for you.

      Rating: 5
Saving Access
      table as oracle table  January 08, 2003
Reviewer:
      mo
Tom:

I have a .dbf file that I opened up in Access and then saved it into oracle
table using save to ODBC database.

the oracle table is getting created fine. However the table has about 1000
records and I am only getting 900.  It gave me a message"Microsoft Access was
unable to append all data tothe table. The contents of fields in 100 records
were deleted. If data was deleted, the data you pasted or imported doesn't match
the field data types or the field size property in the destination table."

I do not have an oracle table to begin with. DO you think it is the ODBC driver
issue?

Any hints.

          Followup:

I have no clue -- I've never used ODBC in my life

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:711825134415
is an alternative, you'll be able to see what is in the dbf file (assuming it is
a dbase file)

or configure the generic connectivity that comes with the database and "select *
from it" in sqlplus and see what is in there (and insert into oracle_table
select * from dbf_file" if you like.

Or, open a problem report with bill.gates@microsoft.com -- he might now how to
get more information from access ;)

      Rating: 5
Using OLE
      automation  April 09, 2003
Reviewer:  Ali
       from Bahrain
I want to use your second option "Using OLE automation".
Can you give me a code example of how I can read the OLE data in my form to
insert it again in my db?

          Followup:

Nope -- never used OLE, don't have OLE...  Don't even have an operating system
capable of running OLE...

try otn.oracle.com under discussion forums -> developer

      Rating:
      5
parse  June 06, 2003
Reviewer:  mo

Tom:

1. If you covert the .xls file to .htm what tool do you use to parse the html
table?

2. i have a user spreadsheet that looks like a form. You have address info at
the top and items requested at the bottom in tabular format. When I saved .xls
to .csv file and I looked at the .csv there were a lot of commas in between
data. Would this format be hard to convert to oracle?

          Followup:

1) DIY (do it yourself).  I do not recommend this approach, it is "hard"

2) don't know, use your knowledge of Oracle and your application to see what it
would take to load it up and process it.

      Rating: 5
Excel file
      loading to Oracle Database  June 06, 2003

Reviewer:  Mark
Hi,

We had the same problem. The approach we took to solve this problem, we had
written a macro, which will generate insert and update statments from the excel
sheet and then cut and paste those insert or update statements. Now we have
migrated to Oracle 9i, so we are using external table and reading the text file
into database.

      Rating: 1
I asked
      gates....  June 06, 2003
Reviewer:  Microsoft
      Tester  from aha.... this is the question to be answered ! Right
      in the room next to u !
<Quoting tom>
"open a problem report with bill.gates@microsoft.com -- he might now how to
get more information from access ;)
<End quote>

Well, as per ur suggestion, I asked Gates about the problem, and he says it is
useless to insert the data into Oracle, why not use SQL Server ? Anyways Oracle
products are highly non-user friendly.....

Happy ?!

          Followup:

did he give you that broken, non functioning keyboard that drops vowels,
extremely important characters in this particular language as well?

probably a new "open standard" they are inventing for all of us to conform to
over over in redmond I guess.

but really -- do you honestly expect me to debug access?  a product that for
some reason just refused to actually install on the operating system I choose to
use?  sorry -- no can do.

(curious, how did you ask bill?  as far as I know, they don't actually do
anything similar over there.... what is the URL?  I do have some issues with
word on linux, maybe he can help?)

      Rating:
      4
  June 07, 2003
Reviewer:  A.WAHAB
       from Jeddah,KSA
Ha ha that was a good answer Tom. ;)

      Rating: 3
Reading .xls
      from Oracle Tools  June 08, 2003
Reviewer:  Maju
      Bellamin  from Bahrain
Dear Tom,

Can't we use heterogeneous connectivity to read data from a Microsoft Excel
File? Though it needs little bit of configuration, it works fine with Excel,
Access files.

          Followup:

if you have odbc access to excel, sure.

      Rating: 5
More thoughts
      on Excel  June 08, 2003
Reviewer:  Tyler D.
      Muth  from Reston, VA
This might be repeating what you did not want to do in the first place, but if
you can save the Excel files as CSV, then you can read them as external tables
using the technique described in:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6611962171229
As far as COM automation, I found a lot from google on this when writing a sync
between Oracle and Outlook tasks.  You could even write something using windows
scripting host to open all excel files in a directory, then save them as a CSV
file on a Network share on your db machine.  On the db side, a db job that runs
every x minutes and looks for files it hasn't already imported and reads them in
as an external table would work.

No matter what, I would shy away from ODBC.  It's a "least common denominator"
connection.  You can't leverage Oracle features such as packages or stored
procedures, return ID's of rows you just inserted, etc. like you can with OLE DB
or Objects for OLE (OO4O).  On the Microsoft Side, working with a recordset
returned as an object from an OLE DB connection is much easier and much more
powerful than what you can do with ODBC.  Just my 2 cents.

      Rating:
      5
excel  June 09, 2003
Reviewer:  mo

Tom:

1. On an HTML form can i have the user open the excel file, save it to .csv file
and then have him copy the ASCII text and paste it into text field on the form
then submit the form to a pl/sql procedure which saves the text to a long column
in a temp table.  Then I can search for data by counting the number of commas in
the record/column created and retrieve it and insert it into the oracle table I
want. IS this doeable in oracle?

2. or upload the .csv file from the browser into the unix file directory and
then use UTL_FILE to read lines and parse the data and insert into oracle table?

3. Why do you say parsing the HTML file is hard? Would an excel to XML
conversion and then parsing to oracle be much easier?

Thanks,

          Followup:

1) if they cut and paste it into a text field, it'll be going to a PLSQL
variable of type varchar2.

there will be no temp table, it'll just be a string of UPTO 32k in size.

If, you have them save as CSV, you can have the FILE UPLOAD it to a clob.
parsing the clob is very easy.

2) you can upload to a CLOB

3) hard as compared have a csv file.  save it as html, parse it.  tell us if the
code to do that was more difficult.

      Rating:
      5
excel  June 10, 2003
Reviewer:  mo

TOm:

1.  On Option 1, the max text I can pass using pl/sql variable is 32 K. what
about if there is more text. does oracle cut it and save the first 32 K or it
will not pass at all?

2.  Why do you use CLOB column type and not LONG. Is it because CLOB is more
like VARCHAR2 and LONG is hard to parse/manipulate?

3.  I tried to upload a file using an HTML form but the text was not saved in
the table but rather the path of the file? Do you know why?

PROCEDURE TEST_SCRIPT as
begin
htp.p('<HTML>');
htp.p('<HEAD>');
htp.p('<SCRIPT Language="JavaScript1.1">');
htp.p('   <!--');
htp.p('   function doSubmit()');
tp.p('   {');

htp.p('    document.my_form.submit();');
htp.p('   }');
htp.p('   //-->');
htp.p('   </script>');
htp.p('');
htp.p('   </HEAD>');
htp.p('');
htp.p('   <BODY>');
htp.p('   <a href="javascript:doSubmit()">Click me to submit the form</a>');
htp.p('   <form action="test_insert" name=my_form method=post>');

htp.p('<P>Files:<INPUT TYPE="file" ENCTYPE="multipart/form-data"  NAME="resume"
</P>');
htp.p('   </form>');
htp.p('   </BODY>');
htp.p('   </HTML>');

end;

PROCEDURE TEST_INSERT ( resume    in   varchar2 )
as
begin
    insert into test9(id,file_text) values (1,resume);
    commit;
end;

SQL> desc test9
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 ID                                           NUMBER(5)
 FILE_TEXT                                    LONG

SQL> select * from test9;

        ID
----------
FILE_TEXT
----------------------------------------------------------------------
         1
c:\ims\t1.txt

          Followup:

1) try it and see?  most browsers (used to) limit you to 30,000 characters in a
text field so it was moot.

I gotta feeling that it just gets truncated.  anything that could forseeably
even approach 32k should be a FILE UPLOAD.

2) (note: should have said BLOB, not CLOB) long/long raw as database column
types are deprecated and if you had more then 32k in a long raw (as long is not
a choice with mod_plsql and uploads) you would have NO WAY to parse it at all in
plsql.

a blob would be the only logical datatype to use.

3 did you read the documentation about how file upload works with mod_plsql?
you got passed a "key" if you will to a document table where the data actually
exists.

check out the docs.

      Rating: 5
file
      upload  June 16, 2003
Reviewer:  mo

Tom:

1.  In this link you say mod_plsql does not support uploading files?

http://asktom.oracle.com/pls/ask/f?p=4950:8:309875116771794298::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:3311092584033,
2. which documentation talks about how to do this: is it OTN?  Do you mean if I
have a text file with "Hello World" i can never get that file into oracle
column? it will always be a pointer.

Thank you,

          Followup:

1) no i didn't, read closer

question was:

...How can I upload a file from client machine to server file system (not
database)  .....

answer is:

file system upload is not supported via mod_plsql.

meaning to upload to a file system (not the database) is not supported.  You can
upload from your client machine INTO the database.

see

http://download-west.oracle.com/docs/cd/A97335_01/index.htm
chapter one, file upload download.

      Rating:
      5
upload  June 16, 2003
Reviewer:  mo

Tom:

Thanks,

1. Is there a way to find out the name of the document table?

2.  To parse the data you would parse it in the document table or you would copy
it to the original table or it does not matter really?

3.  When I save the .xls into .csv (text format) some data comes out between
double quotes while other does not. Would I still be able to pull the needed
info from this using PL/SQL by searching for commas or double quotes and then
using SUBTR/INSTR.

Company Code:,,DD11,,,Date:,5/8/03,
,,,,,,,
,,,,,,,
,,,,,,,
,,,,,,,
,,,,,,,
Agency:,,STATE LIBRARY,,,,,
,,"AGENCY, CENTER, EAST",,,,Center,
Address:,,222 LEE PLACE,,,,,
,,"DENVER,  CO  22215",,,,,East
,,,,,,,

Thanks,

          Followup:

1) read the documentation please.

http://download-west.oracle.com/docs/cd/A97335_01/apps.102/a90099/feature.htm#1006876
you actually create and name this table if you like.

2) do what is best, easiest and most efficient for you.  It is just "code" at
this point.  Use good algorithms, try different methods, write something
efficient and maintainable that performs well.

3) depends on how you write your parsing code!  Of course you *can* -- it just
makes it a little trickier then simply "search for commas", you have to make
sure you are NOT in quotes when you find a comman.

      Rating:
      2
OO4O  June 17, 2003
Reviewer:
      Alesinskyy  from Germany
Exporting data to CSV and then using it as external table or by SQL*Loader is a
good approach, but if it doesn't fit to the specific needs Oracle Objects for
OLE may be used. And they come with documentation and samples just for Excel.
And they are downloadable from OTN.

BTW, I usually use them in opposite direction - to make reports in Excel format
from data in Oracle.

      Rating: 3
Strange time
      format from Excel  July 11, 2003
Reviewer:
      Sameer  from Milwaukee, WI
Tom,

I am trying to load an Excel sheet data into Oracle. The column which has time,
is in HH:MI AM format. But when I pick the value from Excel, I get
0.445138888888889. Is this in some different format that I have to convert while
inserting into Oracle ?

I know, this is more like VBA question but I thought there might be Oracle way
to convert this number into time.

Thanks in advance !

      Rating: 3
Ignore my
      question, found the problem !!!  July 11, 2003

Reviewer:  A reader

      Rating: 3
How about Excel
      2003 XML interface for import/export  October 28, 2003

Reviewer:  Chowdary  from CA, USA
Did anyone tried using Excel 2003 XML iterface for importing/manipuating Oracle
9i Data? Read that even it has validation funcionality using smart tags.

Any feedback is welcome.

thanks,
Chowdary

June 17, 2003 Ken -- Thanks for the question regarding
      "Required components for client-side access to DB....", version 7.3.3

      You Asked
  We are running Financials 10.7 (Inventory, Order Entry, and
Purchasing) in 7.3.3 in HP-UX environment.  User access is thru
Web Server 3.  I and one other developer have client/server
access to SQL*Plus thru SQL*Net.
  I developed and am running numerous Crystal Reports for
distribution.  I want to enable user access to such reports, but
cannot do so over the web.  We do not want to load client
components on users' PCs that they won't use.  All they need is
to be able to hit the DB.
  Crystal has native Oracle drivers.  I am thinking that all I
need to load on PCs is sqlnet, listener, and tnsnames .ora
files.
Is my assumption correct?  Any online docs that address this
issue?

Thanks in advance.  Ken Banyas

      and we said...
You need to do a client install on those clients.  We put
various components into the registry and the are a variety of
.dll's needed for SQLNet support.  When Crystal reports says
they have a native driver -- what they mean is they use OCI
directly and not ODBC for performance.  Regardless of whether
they use ODBC or OCI, they still need SQLNet.

So you need to install sqlnet -- not just the sqlnet.ora file --
on the client machines. You'll do a minimal install of just
sqlnet (no sqlplus or anything they do not want) on the client
machines.
Review
      & Followup

      Rating: 5
error
      ..  January 07, 2003
Reviewer:  atul
       from India
Sir,

I'm using oracle 8.1.6...and users using crystal report 8
to get the report..

On one client we installed oracle client and crystal reports
we can connect to oracle from that client...but for getting crystal reports it
gives error like

"ERROR: ([Microsoft][ODBC Driver Manager] Data source name not  found and no
default driver"

it also shows some crora8.dll which gets installed when crystal reports
installation...

How to get solution??

Thanks.
atul

      Rating: 5
Oracle ODBC
      Driver  June 16, 2003
Reviewer:  Sujata
       from Germany
OS: Windows XP and 2000

Tom,

O.K. One of the client machine has Oracle 8 client (client needs it for lot of
other apps that they access) installed on it (XP Machine)...and we need to give
client access to Oracle 8i database through an Access database app.
ODBC driver that came with Oracle 8 as well as the "Microsoft ODBC driver for
Oracle"
are not working (giving ODBC failure error)..
We have succesfully made it work with Oracle 8i client (Oracle ODBC driver)..on
another machine.

Question:  Can we install the latest Oracle driver (Oracle ODBC Driver for Rdb ,
Release 3.0.2.6 for Windows)...not the client...just the driver...
Will this mess up the existing registery or dlls...
or do we need to install the whole Oracle 8i client (in a separate home
ofcourse)...and will that mess up the existing Oracle 8 dlls...please
recommend..

Thanks a bunch..

          Followup:

sorry -- you lost me back at "ODBC failure error" :)  odbc drivers do in fact
work, you've done something wrong.

ODBC is just an api, it requires the underlying client support.  If you put an
rdb odbc adapter on there but no rdb client code -- well, you won't get to rdb.

as for messing up the registry or dlls, as I recall, that happens every time you
install virtually anything on windows?

June
      17, 2003 Mike -- Thanks for the
      question regarding "Snapshot Log Column change_vector$$", version 8.1.7

      You Asked
There is a table named t_state, I created a snapshot log for it
(see below). I'd like to know what the column change_vector$$
is.

SQLWKS> desc MLOG$_T_STATE
Column Name                    Null?    Type
------------------------------ -------- ----
COUNTRY                                 VARCHAR2(3)
STATE                                   VARCHAR2(4)
SNAPTIME$$                              DATE
DMLTYPE$$                               VARCHAR2(1)
OLD_NEW$$                               VARCHAR2(1)
CHANGE_VECTOR$$                         RAW(255)

--------------
What I did here is insert a row and then update it.

SQLWKS> select * from MLOG$_T_STATE
     2>
COU STAT SNAPTIME$ D O CHANGE_VECTOR$$

tst MF   01-JAN-00 I N FE

tst MF   01-JAN-00 D O 00

tst MR   01-JAN-00 I N FF

3 rows selected.

Thanks

      and we said...
It is a change vector describing what columns have been
modified.  The size of the vector is a function of the number of
columns.  If you goto:

http://osi.oracle.com/~tkyte/hexdec/index.html
I have a base conversion routine there, usful for dumping the
binary format of the vector.

consider this example.  I update each column in turn and fetch
out the vector and display it.  It can be used to see which
columns were updated:

ops$tkyte@DEV816> create table t
  2  ( x int primary key,
  3    c0 int, c1 int, c2 int, c3 int, c4 int,
  4    c5 int, c6 int, c7 int, c8 int, c9 int,
  5    c10 int, c11 int, c12 int, c13 int, c14 int,
  6    c15 int, c16 int, c17 int, c18 int, c19 int );

Table created.

ops$tkyte@DEV816>
ops$tkyte@DEV816> insert into t(x) values ( 1);

1 row created.

ops$tkyte@DEV816>
ops$tkyte@DEV816> create snapshot log on t;

Materialized view log created.

ops$tkyte@DEV816> set serveroutput on format wrapped;
ops$tkyte@DEV816> declare
  2      l_str varchar2(255);
  3  begin
  4      for i in 0 .. 19
  5      loop
  6          execute immediate
  7          'update t set c' || i || ' = 1';
  8
  9          select change_vector$$ into l_str
 10            from mlog$_t;
 11
 12          dbms_output.put_line
 13          ( to_char(i,'99') || ' = ' || l_str  || ' ' ||
 14            lpad(to_bin( to_number(l_str,'xxxxxx') ),32,'0')
);
 15          delete from mlog$_t;
 16      end loop;
 17          update t set c1 = 2, c4 = 3, c10 = 12, c11 = 2,
c18=0;
 18      select change_vector$$ into l_str
 19        from mlog$_t;
 20
 21      dbms_output.put_line
 22      ( to_char(0,'99') || ' = ' || l_str  || ' ' ||
 23        lpad(to_bin( to_number(l_str,'xxxxxx') ),32,'0') );
 24  end;
 25  /
  0 = 040000 00000000000001000000000000000000
  1 = 080000 00000000000010000000000000000000
  2 = 100000 00000000000100000000000000000000
  3 = 200000 00000000001000000000000000000000
  4 = 400000 00000000010000000000000000000000
  5 = 800000 00000000100000000000000000000000
  6 = 000100 00000000000000000000000100000000
  7 = 000200 00000000000000000000001000000000
  8 = 000400 00000000000000000000010000000000
  9 = 000800 00000000000000000000100000000000
 10 = 001000 00000000000000000001000000000000
 11 = 002000 00000000000000000010000000000000
 12 = 004000 00000000000000000100000000000000
 13 = 008000 00000000000000001000000000000000
 14 = 000001 00000000000000000000000000000001
 15 = 000002 00000000000000000000000000000010
 16 = 000004 00000000000000000000000000000100
 17 = 000008 00000000000000000000000000001000
 18 = 000010 00000000000000000000000000010000
 19 = 000020 00000000000000000000000000100000
  0 = 483010 00000000010010000011000000010000

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating: 5
Problem on
      snapshot log  June 16, 2003
Reviewer:  Eka
       from Indonesia
Tom, thanks for answering my previous questions. You are very helpful.
I have some problem with snapshot log. I try to search on your site but i can't
found similar problem.
I experienced this a couple times : when i update a table, the snapshot logs for
that table is not updated.
for example, i update table invoice, but when i query mlog$_invoice, it shows no
rows. Not every table behave like that.
To solve the problem, i should drop snapshot log, recreate it again, and doing a
complete refresh on the snapshot site.
Tom, how can that happen? and how i prevent it so it won't happen again?
Thank you very much.

          Followup:

umm, have you noticed that the target table isn't getting the updates -- if so,
file a bug (that would be a bug).  If not -- if the changes always get made, let
it be.

You are looking at the logs, did you look at the real snapshots and see if they
got the changes or not.  if they did not, please file a bug.

      Rating: 4
did you
      commit?  June 17, 2003
Reviewer:  Reader

Please make sure that your transaction commits only then snapshot log will have
entries. I am sure you would have checked.

      Rating: 2
problem with
      snapshot log  June 18, 2003
Reviewer:  Eka
       from Indonesia
The real snapshot was not updated. Actually, i found this problem because my
report which running on snapshot site showed old data (not up to date). Then i
tried to do an update on master table and i found that snapshot log still showed
empty rows, even when i commit the update.
All tables in the same master replication group behave this way, but another
tables belong to another master replication group are fine.
Unfortunately, i can not reproduce this error, it happens eventually, and
rarely. I recall it happen three times since i setup snapshot replication, a
couple months ago.

June 17, 2003 Partha -- Thanks for the question regarding
      "Query Results in One row from Group of Rows", version 8.1.7

      You Asked
Tom,

I have Following layout of data

Load Date           S052SN   S052HV   S052WG   S052NN
-----------------------------------------------------------------
01-Oct-01 00:00:00  12.93    9.77     6.71     12.56
01-Oct-01 01:00:00  12.79    9.70     6.73     12.05
01-Oct-01 02:00:00  12.75    9.78     6.74     11.77
01-Oct-01 03:00:00  13.57    9.82     6.78     11.78 ---> Upto this
01-Oct-01 04:00:00  13.54    9.92     6.79     11.54 ---> Ignore
01-Oct-01 05:00:00  14.46    10.7     7.12     11.90 ---> Ignore
---Output Truncated

I need as follows...

Load Date           S052SN                   S052HV
-----------------------------------------------------------------
01-Oct-01 00:00:00  12.93,12.79,12.75,13.57  9.77,9.70,9.78,9.82

S052WG                 S052NN
-----------------------------------------------------------------
6.71,6.73,6.74,6.78    12.56,12.05,11.77,11.78

Note: Above is complete one record like that i need to diplay the output.

Note: Only first four Values of each column should display like above
Because it is one day calculation, the date should display only one for every
four values. i.e., Then goto next date and follow the same rule as above i.e.,
you ignore remaining two values(in above output i supplied 6 values for one
date, so we need only first four values.

Can you please give me the solution for above problem.

Thanks in Advance.

Partha

      and we said...
Analytic functions RULE....

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

LOAD_DATE              S052SN     S052HV     S052WG     S052NN
------------------ ---------- ---------- ---------- ----------
01-oct-01 00:00:00      12.93       9.77       6.71      12.56
01-oct-01 01:00:00      12.79        9.7       6.73      12.05
01-oct-01 02:00:00      12.75       9.78       6.74      11.77
01-oct-01 03:00:00      13.57       9.82       6.78      11.78
01-oct-01 04:00:00      13.54       9.92       6.79      11.54
01-oct-01 05:00:00      14.46       10.7       7.12       11.9
02-oct-01 00:00:00      12.93       9.77       6.71      12.56
02-oct-01 01:00:00      12.79        9.7       6.73      12.05
02-oct-01 02:00:00      12.75       9.78       6.74      11.77
02-oct-01 03:00:00      13.57       9.82       6.78      11.78
02-oct-01 04:00:00      13.54       9.92       6.79      11.54
02-oct-01 05:00:00      14.46       10.7       7.12       11.9

12 rows selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> column S052SN format a10
ops$tkyte@ORA817DEV.US.ORACLE.COM> column S052HV format a10
ops$tkyte@ORA817DEV.US.ORACLE.COM> column S052WG format a10
ops$tkyte@ORA817DEV.US.ORACLE.COM> column S052NN format a10
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select load_date,
  2         max( decode( rn, 1, S052SN, null ) ) || ',' ||
  3         max( decode( rn, 2, S052SN, null ) ) || ',' ||
  4         max( decode( rn, 3, S052SN, null ) ) || ',' ||
  5         max( decode( rn, 4, S052SN, null ) ) S052SN,
  6         max( decode( rn, 1, S052HV, null ) ) || ',' ||
  7         max( decode( rn, 2, S052HV, null ) ) || ',' ||
  8         max( decode( rn, 3, S052HV, null ) ) || ',' ||
  9         max( decode( rn, 4, S052HV, null ) ) S052HV,
 10         max( decode( rn, 1, S052WG, null ) ) || ',' ||
 11         max( decode( rn, 2, S052WG, null ) ) || ',' ||
 12         max( decode( rn, 3, S052WG, null ) ) || ',' ||
 13         max( decode( rn, 4, S052WG, null ) ) S052WG,
 14         max( decode( rn, 1, S052NN, null ) ) || ',' ||
 15         max( decode( rn, 2, S052NN, null ) ) || ',' ||
 16         max( decode( rn, 3, S052NN, null ) ) || ',' ||
 17         max( decode( rn, 4, S052NN, null ) ) S052NN
 18    from ( select trunc(load_date) load_date,
 19   row_number() over ( partition by trunc(load_date) order by load_date ) rn,
 20                  S052SN,  S052HV,  S052WG,  S052NN
 21             from t )
 22   where rn <= 4
 23   group by load_date
 24  /

LOAD_DATE          S052SN     S052HV     S052WG     S052NN
------------------ ---------- ---------- ---------- ----------
01-oct-01 00:00:00 12.93,12.7 9.77,9.7,9 6.71,6.73, 12.56,12.0
                   9,12.75,13 .78,9.82   6.74,6.78  5,11.77,11
                   .57                              .78

02-oct-01 00:00:00 12.93,12.7 9.77,9.7,9 6.71,6.73, 12.56,12.0
                   9,12.75,13 .78,9.82   6.74,6.78  5,11.77,11
                   .57                              .78

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

Review &
      Followup

      Rating: 5
Query to get
      one record from four records  June 07, 2002

Reviewer:  A reader

      Rating: 4
DATE IN WHERE
      CLAUSE  June 16, 2003
Reviewer:  vinodhps
       from India,Chennai
HI.. TOM..

I GOT QUERY LIKE THIS......

select NVL(SUM(decode(substr(pallet_code,5,2),'C1',pallet_wt)) , 0) CUTTER1_DAY,
NVL(SUM(decode(substr(pallet_code,5,2) , 'C2' , pallet_wt )) , 0 ) CUTTER2_day,

NVL(SUM(DECODE(substr(pallet_code,5,2),'ON',pallet_wt,'OU',pallet_wt,'OY',pallet_
wt,'OZ', pallet_wt))  , 0 ) OTHER_CUTTER_day
from t_pms_pallet_dtl
where PALLET_STATUS IN (5,20,25)
AND pallet_date between trunc(sysdate)-1 and trunc(sysdate)-(1/(24 * 60 * 60))

Iam using this query in MIS module where i have to insert in single table where
it has day , month , year.

Here in the above query iam getting output for DAY some thing like this

CUTTER1_DAY CUTTER2_DAY  OTHER_CUTTER_DAY
----------- ----------   ----------------
         0    30220.2               54040

SQL>DESC T_CUTTER
CUTTER1_DAY NUMBER
CUTTER2_DAY NUMBER
OTHER_CUTTER_MONTH NUMBER
CUTTER1_MONTH NUMBER
CUTTER2_MONTH NUMBER
OTHER_CUTTER_MONTH NUMBER
CUTTER1_YEAR NUMBER
CUTTER2_YEAR NUMBER
OTHER_CUTTER_YEAR NUMBER
DATESTAMP DATE

Here what i want is in single select statement, i want for the day , for the
month and for the year.

Production Database in oracle V8.
Can anlytical function will help here in V9i.

Thanks for your time tom,

          Followup:

something like this:

select sum( case when trunc(pallet_date)=trunc(sysdate-1) and
substr(pallet_code,5,2) = 'C1'
                 then pallet_wt
                 else 0
             end ) cutter1_day,
       sum( case when trunc(pallet_date)=trunc(sysdate-1) and
substr(pallet_code,5,2) = 'C2'
                 then pallet_wt
                 else 0
             end ) cutter2_day,
       sum( case when trunc(pallet_date)=trunc(sysdate-1) and
substr(pallet_code,5,2) in ('ON' ,'OU','OY', 'OZ')
                 then pallet_wt
                 else 0
             end ) other_cutter_day,
       sum( case when trunc(pallet_date,'mm')=trunc(sysdate,'mm') and
substr(pallet_code,5,2) = 'C1'
                 then pallet_wt
                 else 0
             end ) cutter1_month,
       sum( case when trunc(pallet_date,'mm')=trunc(sysdate,'mm') and
substr(pallet_code,5,2) = 'C2'
                 then pallet_wt
                 else 0
             end ) cutter2_month,
       sum( case when trunc(pallet_date,'mm')=trunc(sysdate,'mm') and
substr(pallet_code,5,2) in ('ON' ,'OU','OY', 'OZ')
                 then pallet_wt
                 else 0
             end ) other_cutter_month,
       sum( case when trunc(pallet_date,'y')=trunc(sysdate,'y') and
substr(pallet_code,5,2) = 'C1'
                 then pallet_wt
                 else 0
             end ) cutter1_year,
       sum( case when trunc(pallet_date,'y')=trunc(sysdate,'y') and
substr(pallet_code,5,2) = 'C2'
                 then pallet_wt
                 else 0
             end ) cutter2_year,
       sum( case when trunc(pallet_date,'y')=trunc(sysdate,'y') and
substr(pallet_code,5,2) in ('ON' ,'OU','OY', 'OZ')
                 then pallet_wt
                 else 0
             end ) other_cutter_year
  from t
 where pallet_date between trunc(sysdate,'y') and trunc(sysdate)+1/(24*60*60);

will get that in a single pass.

      Rating: 5
When
      CASE  June 17, 2003
Reviewer:  vinodhps
       from India, chennai
Hi Tom,
That was Great, Thanks a lot it works great in 9i.

Is there any method to use the same in oracle8.
thanks  a lot,

          Followup:

assume you mean "in plsql".

read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3027089372477
and then use a view or native dynamic sql

June 17, 2003 Stephen -- Thanks for the question regarding
      "Prevention and Detection of database corruption", version 8.1.5

      You Asked
Tom,

I run a 24x7 OLTP database that's just cannot afford ANY data
corruption. I am trying to proactively detect any possible data
integrity/corruption issues before it hits our clients. Besides
DBVerify and ANALYZE...VALIDATE STRUCTURE for data block checks
what else can I do and why? ANYTHING you can possible think to
help me do early detection of problems would be a tremendous
help.

      and we said...
a full database export/import show=y is another method.  it is
fairly low impact and exercises

o the logical integrity of the system tablespace
o as well as full scans all tables and reads them.

its what I do (exp/imp).  (i don't use analyze or dbv)

also, look at the init.ora parameters

db_block_checking
db_block_checksum

they do impact runtime performance so you'll need to evaluate
them first.

Review &
      Followup

      Rating: 4
Prevention and
      Detection of database corruption  January 29, 2003

Reviewer:  Mark
Metalink Note:214369.1 explains in detail what EXP does and does not check for
database corruption. The metalink note [NOTE:32969.1] explains the second option
of Block Checksumming.

      Rating: 4
block
      corruption  June 14, 2003
Reviewer:
      vinodhps  from India,chennai
Hi Tom,

We  are Using OLTP 24X7 system which recently in one month i faced 2 block
corruption problems in same tablespace .
We are runing on noarchive mode due to some technical problem. last time i lost
1day crusial data of production .but some how i managed to get the data.. but
again now iam having same problem but so far no problem for writing data or
reading from that block i face problem when i export the tables from the
tablespace. it avoids all the tables in that tablespace , iam using cron to
execute export in Direct path , and i saw the

"error message in export.err file."

ORA-01578: ORACLE data block corrupted (file # 29, block # 28264)
ORA-01110: data file 29: '/od/pms/pmsd03.dbf'
EXP-00008: ORACLE error 1578 encountered
EXP-00067: Direct path can not export T_OIIS_ATP_TEST_CASE_DTLS which contains
object or lob data.
EXP-00067: Direct path can not export T_OIIS_DOCUMENT_DTLS which contains object
or lob data.
EXP-00067: Direct path can not export T_OIIS_DOCUMENT_HIST_DTLS which contains
object or lob data.

----------------
"error messages in alertsid.log"
Corrupt block relative dba: 0x07406e68 file=29. blocknum=28264.
Fractured block found during user buffer read
Data in bad block - type:6. format:2. rdba:0x07406e68
last change scn:0x0000.06465372 seq:0x1 flg:0x00
consistancy value in tail 0x00000000
check value in block header: 0x0, check value not calculated
spare1:0x0, spare2:0x0, spare2:0x0
Sat Jun 14 08:33:02 2003
Thread 1 advanced to log sequence 225615

--------------

THAT particular tablespace DBF file is the important in our  Application which
contains.. all the data of production.

currently  there is no DBA .. iam just new to DBA job ..

Iam not sure what exactly should.. i do.. some of my friends.. asked me to
1)copy the particular pms03.dbf to some  other place and chage the path in
control file . But i don't think this will work.
2)copy the particular pms03.dbf to some other place, drop the exsisting one and
then rename the new file and move the particular path.. i suppose this too
does't work..

3) what i think is to export that particular tablespace tables in conventional
path.
a)drop tablespace
b)create tablespace
c)imp the dmp file back to the tablespace.

In the 3 above step which one is the correct one .Please help , if there is any
other method to solve the problem..

Thanks in advance.
i should thank you.. you have helped several times and you site helped a
lot..iam sure you will help to solve the problem.

          Followup:

you just said two things, both of which cannot possibly be true at the same
time:

thing 1) we are 24x7 OLTP
thing 2) we are in noarchivelog mode.

there are no technical problems with running archivelog mode.  You cannot be
24x7 in noarchivelog.  You can be in development, but you cannot be production.

thing 3) to back up the fact that thing 1 is the farthest thing from the truth
is "there is no DBA"

I think you need to get in touch with support and develop a list of things to
try.  You are between a rock and an incredibly hard place.  I am not going to
give you steps to try as you can easily done tons more harm then good right now.

      Rating: 4
Block
      corruption  June 16, 2003
Reviewer:
      vinodhps  from India,chennai
Hi Tom,
Yes , its true...

Its not 24x7 as our server hardware cannot support archiving.. so we use to take
full back up on every sunday (3hrs) down time..and every day  we are taking 3
full export and we got new server (HP RP7410 , HP UNIX ), just waiting.. to
migrate in 1 or 2 weeks we will migrate oracle 9i at that time we will be use
archive mode...

and the above problem is with one table ,it was identified and dropped it.. and
import from my old export fortunatly that table is not an important one  and
data flow is very low.

My doubt is, suppose this same problem come again before we migrate to new
server with couple of tables it is dangerous.. to lose data..

i can't do full export and import due to down time..

1)Why this block corruption happens?Any way to avoid this?
2)Is this block corruption is Os block or oracle data block corruption?

can you kindly explain this Tom.
Thanks

          Followup:

exports ain't backups.

this looks like a hardware related issue if you ask me.  you can turn on block
checking (performance impact) to proactively find these issues faster.  A
fractured block is when the head of the block is newer or older then the tail.
Like we asked the OS "please write this 4k of data" and the OS only wrote 2k of
it.

check your system logs, see if you have spurious IO errors reported.

      Rating: 5
Block
      corruption  June 16, 2003
Reviewer:
      vinodhps  from India, Chennai.
Hi Tom ,
Thanks for your immediate responce, Well its not hardware problem i checked with
Hardware Engineer.He checked for any bad block or any spurious IO errors , there
is nothing like that, what will the actual problem any where for this block
corruption.

Can any one corrupt the block,some thing like that..is it possible of so kind of
transaction will corrupt the block.

What will the main reason for the block corruption?

Thank you Tom,

          Followup:

I still believe it to be "hardware related" but you can enable block checking to
catch it at the point of failure.

did they do destructive low level read write tests?  if not, you have not yet
rules out anything.

      Rating: 5
Block
      corruption  June 17, 2003
Reviewer:
      vinodhps  from India,chennai
Hi Tom,
I once again checked with hardware Incharge about that problem he said.. server
does not have any problem.

Can you tell me how to enable block checking tom, is there any referece in you
book(Expert one-on-one) tom.

Thank you,

          Followup:

search for

db_block_checksum

on this site.

June 17, 2003 Chuck -- Thanks for the question regarding "How
      to use Java to perform TO_LOB functionality over a link", version Solaris
      v8.1.6

      You Asked
I am developing a document repository on a Solaris w/8.1.6.  The
initial load is a fixed set of documents which are currently in
a v7.3.4.5 as a LONG RAW.  Export, Import, TO_LOB and I have my
BLOBS.  The tricky part is moving new documents created in the
remote v7 LONG RAWs. I want the 8.1.6 to retrieve the LONG RAWs
via link(s) (up to 4 remote sites) and load into a BLOB.

The docs are 500k on average, but can get up to 8M. I've seen a
lot on streaming to and from OS files, but I prefer to cut out
the file and move it stream to stream (assuming a stream is
necessary - big server.)

Thoughts, opinions, and please some code!!!

      and we said...
We can do this but we'll need 2 connections -- one to read the
LONG RAW as a stream and one to write the BLOB as a stream.  The
reason we need 2 connections is:

<quote>
Streaming Data Precautions

This section describes some of the precautions you must take to
ensure that you do not accidentally discard or lose your stream
data. The drivers automatically discard stream data if you
perform any JDBC operation that communicates with the
database, other than reading the current stream. Two common
precautions are described in the following sections:
...
</quote>

see

http://technet.oracle.com/doc/oracle8i_816/java.816/a81354/basic4.htm#1007114
for more info.  Ok, so since we know we need 2 connections --
one to read and one to write, it can look like this.  I have a
table

IMAGE( id number, image long raw );

In the 7.3 instance.  I have a table

BLOB_DEMO( x number, y blob )

in the 8.1 instance.  I have a database link from 8.1 to 7.3 set
up.  The java code could look like this:

import java.io.*;
import java.lang.*;
import java.sql.*;
import java.util.*;
import oracle.jdbc.driver.*;

public class movelr
{
    public static void main(String[] args) throws Exception
    {
        int    length;

        DriverManager.registerDriver (new OracleDriver());

        Connection conn73 = DriverManager.getConnection
              ("jdbc:oracle:oci8:@ora8idev","scott","tiger");
        Connection conn81 = DriverManager.getConnection
              ("jdbc:oracle:oci8:@ora8idev","scott","tiger");

        conn73.setAutoCommit (false);
        conn81.setAutoCommit (false);
        System.out.println( "connected..." );

        // Create statements
        PreparedStatement pstmt = conn73.prepareStatement
                   ("select image from image@slack" );

        System.out.println( "prepared statement for v7.3..." );

        PreparedStatement insertStmt = conn81.prepareStatement
               ("insert into blob_demo ( x, y ) " +
                "values ( ?, empty_blob() )");
        System.out.println
           ( "prepared statement for v8.x BLOB insert..." );

        PreparedStatement getStmt = conn81.prepareStatement
             ("select y from blob_demo where x = ? for update");
        System.out.println
           ( "prepared statement for v8.x BLOB select..." );

        ResultSet rset73 = pstmt.executeQuery();
        System.out.println
           ( "executed statement for v7.3 select..." );

        for( int i = 0; rset73.next(); i++ )
        {
            System.out.println( "looping..." );
            InputStream is = rset73.getBinaryStream(1);
            System.out.println( "Got v7.3 LONG RAW stream...");

            // Insert a new row and get select
            // back the BLOB column
            insertStmt.setInt(1, i);
            insertStmt.executeUpdate();
            getStmt.setInt(1, i);
            OracleResultSet rset81 =
                 (OracleResultSet) getStmt.executeQuery();

            rset81.next();
            OutputStream os =
                rset81.getBLOB(1).getBinaryOutputStream();
            System.out.println( "Got v8.x BLOB stream..." );

            System.out.println( "Moving data from IS to OS...");
            byte buffer[] = new byte[1024];
            while ((length = is.read (buffer,0,1024)) != -1)
            {
                os.write( buffer, 0, length );
            }

            is.close();
            os.close();
            rset81.close();
        }
        rset73.close();
        insertStmt.close();
        getStmt.close();
        pstmt.close();
        conn81.commit();
        conn73.close();
        conn81.close();
    }
}

Review &
      Followup

      Rating: 5
How about the
      other way around  February 21, 2003
Reviewer:
      Tom Best  from PA
I'm using this with great success.  Thanks.

Is it possible to read in the blob and convert it/write into a long raw column?

          Followup:

most likely -- they are just streams both ways.  Never tried it as that would be
taking a huge step backwards.

      Rating: 4
Converting from
      clob to long.  June 17, 2003
Reviewer:
      Shailesh  from India
Hello,
   You said "most likely -- they are just streams both ways.  Never tried it as
that would be
taking a huge step backwards."
   Please consider our case.
   We are running Oracle 9.0.1.4 on HP-UX 11i. We have five tables in schema
"PROD" having datatype as LONG. Couple of days ago application team requested to
create new schema for storing historical data. They scheduled a job which
inserted the data into the new schema "CSSARCH" from the "PROD" schema and
deleted the same from "PROD" after successful completion. Everything was fine
except when we tried to apply the logs generated at the production server at the
disaster site. The recovery failed. We raised the TAR and got the reply that
this is a known bug 2322620. Also we found out that application team, withouit
informing us, had changed the datatype of one of the columns in all five tables
from LONG in schema "PROD" to CLOB in schema "CSSARCH".
   Now our question is how do we revert back? i.e how to get data from clob
column to long column because our historical data is in "CSSARCH" schema where
one of the datatype is CLOB and other data is in "PROD" schema. Our disaster
site is not available as logs are not getting applied. If we take cold database
backups, they are restored, however we cant take cold backups daily. we do not
want to loose data. Please help.
Also if we do manage to revert back i.e get data back to LONG column, our
concern is will long datatype will be supported in future versions? Should we
insist on application team to change the datatype to CLOB from LONG? Will it
effect the existing application?

Please advise.

Regards
Shailesh

          Followup:

why don't you backup and re-instantiate the standby?

      Rating: 4
Very good JAVA
      Example!  August 07, 2003
Reviewer:  Solfrid
       from Norway
This article(JAVA Example) helped us to have correct insert and update and
filling of a Blob. This was a a show stopper for us in our eKlima system, for
the user to order more than one report(blob) at a time.
Only thing I changed was that I dropped the class casting for ResultSet and
casted the java.sql.BLOB to Oracle.sql.Blob instead.

Thanx!

      Rating:
      5
  December 17, 2003
Reviewer:
      A reader

June 17, 2003 Anuja -- Thanks for the question regarding
      "external table", version 9.0.1

      You Asked
Hi Tom
Can you please show me with an example , how to use external table in a
procedure (to refresh the staging table). I would then like to call this
procedure in dbms_job to run daily .
Thanks
Sangita

      and we said...
No --

why?

the external table IS YOUR STAGING TABLE....

But even so -- think about it -- what is an external table?  A database table
that happens to be in a flat file.  How do you interact with it?  Using SQL.
So, if you know how to move data from tableA to tableB you already know the
answer.

an external table looks, feels, acts no differently to your queries then a HEAP
table or an INDEX table.  Just query it.
Review &
      Followup

      Rating: 4
Is it possible
      to access flat file from network ??  June 17, 2003

Reviewer:  sanjay joshi  from Pune,India
Tom,
I have mapped a network drive e.g. Z: of another PC on which
a file ( result.dat ) is getting continiously appended by a
spectro-meter analysis.
I have created a directory as
SQL> create directory data_dir as 'Z:\';
Then I have created external table.
But by using external table I am not getting the result.
Error as below.

ERROR at line 1:
ORA-29913: error in executing ODCIEXTTABLEOPEN callout
ORA-29400: data cartridge error
KUP-04063: unable to open log file EXT_TAB_1572_3780.log
OS error The system cannot find the file specified.
ORA-06512: at "SYS.ORACLE_LOADER", line 14
ORA-06512: at line 1

If I open that file through network(File->open->Notepad), showing data.

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:241814624807

June 17, 2003 Yves -- Thanks for the question regarding
      "Replicating records with where clause", version 9.2.0.2.0

      You Asked
Hi Tom.

I have a requirement to replicate from a very large OLTP database only a subset
of records (deemed critical) from three tables, to a smaller database on a
different server.

The records to be replicated will selected from several master tables as they
are inserted.  They will be selected based on some where clause criteria I have
been provided.  Once the records that match the replication criteria are flagged
upon insertion, only these records should be replicated to another database for
high availability.
The requirement is that if the primary database for whatever reason is not
available, the other database would (seemlessly) make these records available
for queries/updates.  Once the primary database is back online, these records
should be synched from the fail-over database before the primary database not
becomes again primary.

Any thoughts on how this could be setup?  Is this even possible?
Thanks,
Yves.

      and we said...
These are called Updatable snapshots in Oracle speak.

Setup?  documented in the replication guide:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96567/repmview.htm#51092

Possible?  Sure, we've been doing replication since the very early 90's.  Best
way to do this?  Depends -- it is a alot of work and maintenance for something
that could be solved via clustering today (with lots less work).  Being lazy, I
generally go for the easiest solution that solves the problem.

Consider what happens when

a) work done on prod but not yet replicated
b) system "fails"
c) you goto backup and work -- some changes did not make it over yet.  you
modify those rows OR make decisions based on their values
d) system comes back up

Now, you have to have coded all of the code to reconcile those differences.
Review &
      Followup

      Rating: 3
Please
      elaborate  February 01, 2003
Reviewer:
      A reader
When you say it could be solved via clustering, are you assuming that both
databases are identical in size?
In my case they are not.  The production server is a large Sun E4800 with
terabytes of space.
The server available for replication is a small Sun E280R.
I would love to cluster as well, but would I not need both server to be the same
size?
I'd love the simpler solution as well.  So, please elaborate!

          Followup:

with clustering, there is no second database.

You protect the database by having more then one computer.

RAID = disks stay up.
Redundant network = that stays up.
More than one computer = that stays up.

All three means it takes a data center catastrophy to bring it down.

You only need to talk to Sun about what can and cannot be clustered.  The
terabytes of "space" would be a shared resource.

Adding replication will cause you to add more capacity to the existing 4800 bear
that in mind.  If that machine doesn't have spare cycles and extra disk and more
network bandwidth then it needs -- you'll need to add more.

Having a 280 as a "failover" for a 4800 isn't the best choice if this system is
"mission critical".  (that and fault resistance is best designed into a system,
sounds like you are being asked to graft it in after the fact)

      Rating: 5
Very useful
      info. - Is it possible to replicate data subset 2
      way's  June 17, 2003
Reviewer:  Reddy KS
       from Sugar Land
I strongly feel it is possible, but I don’t know how to set this up.
I have eqpt_type table in master database (eqpmst) and in transaction databases
(I have about 5 of them, eqploc1, eqploc2, eqploc3, eqploc4, eqploc5).

eqpt_type_id Number Primary key
eqpt_locid Number Primary key
type_name varchar2(40)
type_desc varchar2(4000)

Data in eqpt_type table for eqpt_locid =1 will be provided as a initial setup
data to all sites. Data for eqpt_locid 1 will be maintained by eqpmst database.
This data is common and required by each location.

New rows will be added for eqpt_locid =1 in eqpmst database (master site). When
some one add data with eqpt_locid =1 into eqpmst database, I want to populate
all transaction databases with the data.

When some one add data at location5 with eqpt_locid =5 into eqploc5 database, I
want to replicate that data to eqpmst database (but not to other locations).

Could you please point me to code example or documentation on how to set this
up.

          Followup:

read

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96567/repoverview.htm#12703
about updateable materialized views or snapshots.

each site can create a MV that will select only the data relevant to it.  so,
that eqploc5 database would use the defining query:

select * from T where eqpt_locid in ( 1, 5 );

      Rating: 5
Thanks for the
      quick response  June 24, 2003
Reviewer:  Reddy K
      S  from Sugar Land, TX USA
Tom,

Thank you so much.
I was able to setup the environment for testing.
It is working great.

Have a nice day
Reddy

June 17, 2003 Riaz -- Thanks for the question regarding
      "Compressed Indexes & Tables", version 8.1.7

      You Asked
Hello Tom !

what are the pros and corns of compressed indexes/tables ? Is there any
performance overhead in this regard ? I heard that compressed indexes save space
but cause increased CPU time...is that true ?

Regards

Riaz Shahid

      and we said...
and reduce CPU time cause they take less space!

If you have to scan 100 blocks -- do 100 latches, 100 consistent gets that takes
a certain amount of CPU.

Now, if it is compressed -- you have to do many 60 blocks, 60% of the CPU as
before.  Sure, this is offset to some degree by the fact it takes longer to
process the block by most of the time, it is a total offset.

But - if you can only cache 70 blocks, one of them will do NO physical IO maybe
and the other will -- all of a sudden, you are doing better already.  It is like
getting more RAM for free as well.. Your buffer cache can hold X% more data --
without changing size.

There are pros and cons.  A con -- you store more index entries per leaf block,
increasing contention on heavily modified tables perhaps.  A pro -- you store
more index entries per leaf block, dramatically reducing consistent gets
(Logical IO), latching, and increasing buffer cache efficiency.

Benchmark, Benchmark, Benchmark.  Try it out -- see what it does for you.

If you have my book Expert one on one Oracle -- see the chapter on indexes, I
cover them there.

search for

compress

on this site and read the first couple of hits -- other articles about
compression are there.

Review &
      Followup

      Rating: 5
Some
      Confusion  March 25, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Islamabad, Pakistan
Hello Tom !

Thanks for your propmt reply. But i was confused with "and reduce CPU time cause
they take less space!"

Check this out and advise whats correct.

www.aaoug.org/pres/Index%20Compression.ppt

Regards

Riaz

          Followup:

the reduced cpu time comes from

if you have 100 uncompressed blocks to process, that'll be 100 logical IO.  Each
logical IO consumes CPU time.

Now, you compress, you have 60 blocks.  you have 40% less IO consumed by LIO
(and that is *SIGNIFICANT*). Less cache buffer chains latches, less contention,
less work.  so, you have to spend a tad bit more time processing the block --
that is typically offset by the rest.

The stuff in that presentation looks good -- however, it leaves out some things.
 Most importantly -- what happens as you SCALE UP.

So, you get to the end and see it takes a little more CPU.  thats bad -- but
wait, is it?  They reduced consistent gets (LIO's) by more then 50% in many
cases.  In a real system, with lots of people accessing the data -- this is
hugely significant.  What do we do for an LIO?  We latch -- lots.  Latches =
locks = serialization device = scalability inhibitor.

Oh, and the PIO's went way down as well.  supposing I was IO bound.

Compression -- it (like everything else) is a tool.  Used properly -- awesome.
Used on a unique single column index -- stinky (as they showed).

Overall -- I liked the presentation, well thought out (cept for the multi-user
case, they didn't play that up enough).  One big thing missing -- scripts to
reproduce the results with...

SLIDE 55 -- love it, everyone should do that.  Perfect.

Also -- if you read my book "Expert one on one Oracle", the chapter on indexes
-- I do put this to test myself in "Myth: It is Best to put the most
discriminating elements first?"  You will see you can also find 0% change in
CPU.  Here is a small test:

ops$tkyte@ORA920> create table t1
  2  as
  3  select * from all_objects ;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index t1_idx on
  2  t1(object_type,owner,object_name)
  3  compress 2;

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t2
  2  as
  3  select * from all_objects ;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index t2_idx on
  2  t2(object_type,owner,object_name);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> @trace
ops$tkyte@ORA920> alter session set events '10046 trace name context forever,
level 12';

Session altered.

ps$tkyte@ORA920> declare
  2      l_rec t1%rowtype;
  3  begin
  4      for x in ( select * from t1 )
  5      loop
  6          select * into l_rec from t1
  7          where object_type = x.object_type
  8            and owner = x.owner
  9            and object_name = x.object_name
 10                    and rownum = 1;
 11          select * into l_rec from t2
 12          where object_type = x.object_type
 13            and owner = x.owner
 14            and object_name = x.object_name
 15                    and rownum = 1;
 16      end loop;
 17  end;
 18  /

PL/SQL procedure successfully completed.

SELECT * from t1 where object_type = :b3 and owner = :b2 and object_name = :b1
          and rownum = 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute  30616     11.37      14.50          0          0          0           0
Fetch    30616      3.63       6.04         91      91848          0       30616
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    61233     15.00      20.55         91      91848          0       30616

Rows     Row Source Operation
-------  ---------------------------------------------------
  30616  COUNT STOPKEY
  30616   TABLE ACCESS BY INDEX ROWID T1
  30616    INDEX RANGE SCAN T1_IDX (object id 45535)
********************************************************************************
SELECT * from t2 where object_type = :b3 and owner = :b2 and object_name = :b1
          and rownum = 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute  30616     11.32      12.66          0          0          0           0
Fetch    30616      3.42      11.63        433      91848          0       30616
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    61233     14.74      24.30        433      91848          0       30616

Rows     Row Source Operation
-------  ---------------------------------------------------
  30616  COUNT STOPKEY
  30616   TABLE ACCESS BY INDEX ROWID T2
  30616    INDEX RANGE SCAN T2_IDX (object id 45537)

No significant difference in response times CPU wise -- but less PIO did give me
better elapsed times.  However,

ops$tkyte@ORA920> exec show_space( 'T1_IDX', user, 'INDEX' );
Free Blocks.............................
Total Blocks............................256
Total Bytes.............................2097152
Total MBytes............................2
Unused Blocks...........................95
Unused Bytes............................778240
Last Used Ext FileId....................9
Last Used Ext BlockId...................54920
Last Used Block.........................33

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec show_space( 'T2_IDX', user, 'INDEX' );
Free Blocks.............................
Total Blocks............................256
Total Bytes.............................2097152
Total MBytes............................2
Unused Blocks...........................34
Unused Bytes............................278528
Last Used Ext FileId....................9
Last Used Ext BlockId...................55816
Last Used Block.........................94

PL/SQL procedure successfully completed.

it is smaller....

(personally, i've yet to see the wide CPU disparities reported -- but, I can
believe it.  I would just need the "test case", like I provide)

      Rating: 5
Still One
      Question  March 28, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Islamabad, Pakistan
Hello Tom !

Thanks for your valuable advise. I read somewhere that only thise tables should
be compressed which are updated infrequently. Is that true ? If yes then whats
is the true defintion of "ifnrequently updated tables".

Thankx again

          Followup:

well, as with all ROT, you have to look at the intent.

I like to say -- the compressed data should be "read mostly" or "read only".

Now, suppose I have an audit trail.  Audit trails are write intensive.
Obviously they are not a candidate for compression.  But wait -- I think they
are perfect.  Here is how:

create audit trail partitioned by month.
current month is write intensive -- old months are read only.

At the end of the month you will:

add new partition for next month.

create table last_month_compressed as select * from audit partition(last_month)
order by <least selective columns to achieve maximum compression>;

alter table audit exchange last-months-partition with last_month_compressed;

drop oldest month

There -- it is perfect for a sliding window of data.  The old months are packed
really tight.  The current month is uncompressed.  Next month that uncompressed
data will be compressed.

So you see -- it is really at the segment level, not the "table" level and it is
a tool that you can apply in different circumstances, under different
conditions.

      Rating:
      5
Great  March 31, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Islamabad, Pakistan
Thats a fine example. I think the same holds for indexes (i.e; indexes that are
frequently updated shouldn't be compressed). Am i right ???

Riaz

          Followup:

wrong - totally different concept here -- indexes and tables.  Similar (both
compress, both compress via redundant data factoring) but different.

ops$tkyte@ORA920> declare
  2      l_rec t1%rowtype;
  3  begin
  4      for x in ( select * from t1 )
  5      loop
  6                  update t1
  7             set object_type = lower(object_type)
  8           where object_type = x.object_type
  9             and owner = x.owner
 10             and object_name = x.object_name;
 11                  update t2
 12             set object_type = lower(object_type)
 13           where object_type = x.object_type
 14             and owner = x.owner
 15             and object_name = x.object_name;
 16      end loop;
 17  end;
 18  /

PL/SQL procedure successfully completed.

and using same tables from before -- we see:

UPDATE t1
           set object_type = lower(object_type)
         where object_type = :b3
           and owner = :b2
           and object_name = :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          1          0           0
Execute  29967     26.50      29.79        307      81104     193278       29967
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    29968     26.51      29.79        307      81105     193278       29967

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  UPDATE  (cr=81158 r=307 w=0 time=15582063 us)
  29967   INDEX RANGE SCAN T1_IDX (cr=75002 r=167 w=0 time=3245296 us)(object id
32507)
--------------------------------------------------------------------------------
UPDATE t2
           set object_type = lower(object_type)
         where object_type = :b3
           and owner = :b2
           and object_name = :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          1          0           0
Execute  29967     26.08      29.07        772      92672     217154       29967
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    29968     26.08      29.07        772      92673     217154       29967

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  UPDATE  (cr=92747 r=772 w=0 time=16015230 us)
  29967   INDEX RANGE SCAN T2_IDX (cr=85919 r=231 w=0 time=4135654 us)(object id
32509)

      Rating: 5
Really
      Great  April 01, 2003
Reviewer:  Riaz Shahid
       from PRAL, Islamabad, Pakistan
Thats really great. Great help from your side.

Thanks

      Rating:
      5
Great  April 28, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Islamabad, Pakistan
Hello Tom !

thats great. But i've a little confusion. Consider:

-- Script:    uncompressed_indexes.sql
-- Purpose:    to identify indexes that would benefit from compression
-- For:        8.1 and above

column index_name format a54

select
  u.name ||'.'|| o.name  index_name,
  decode(
    sign(s.full_save - s.one_save),
    -1, 1,
    decode(s.cols, 1, 1, 2)
  )  min_compress,
  decode(
    sign(s.full_save - s.one_save),
    -1, greatest(1, least(s.max_ok, s.cols - 1)),
    s.cols
  )  max_compress
from
  (
    select
      x.obj#,
      x.cols,
      x.leaf_bytes,
      x.full_save,
      x.max_ok,
      h1.avgcln * (x.rowcnt - h1.null_cnt - h1.distcnt) - 4 * h1.distcnt
        one_save
    from
      ( select
      i.obj#,
      i.cols,
      i.rowcnt,
      (sum(h.avgcln) + 10) * i.rowcnt  leaf_bytes,
      sum(h.avgcln) * (i.rowcnt - i.distkey) - 4 * i.distkey  full_save,
          max(decode(sign(i.rowcnt - 2 * h.distcnt), -1, 0, ic.pos#)) max_ok
    from
      sys.ind$  i,
      sys.icol$  ic,
      sys.hist_head$  h
    where
      i.leafcnt > 1 and
      i.type# in (1,4,6) and        -- exclude special types
      bitand(i.property, 8) = 0 and        -- exclude compressed
      ic.obj# = i.obj# and
      h.obj# = i.bo# and
      h.intcol# = ic.intcol#
    group by
      i.obj#,
      i.cols,
      i.rowcnt,
      i.distkey
      )  x,
      sys.icol$  c1,
      sys.hist_head$  h1
    where
      c1.obj# = x.obj# and
      c1.pos# = 1 and
      h1.obj# = c1.bo# and
      h1.intcol# = c1.intcol#
  )  s,
  sys.obj$  o,
  sys.user$  u
where
  greatest(s.full_save, s.one_save)  > 0.05 * s.leaf_bytes and
  o.obj# = s.obj# and
  o.owner# != 0 and
  u.user# = o.owner#
order by
  greatest(s.full_save, s.one_save) desc
/

I ran it on my DB and found a few index names which may benefit from
compression. Is that right method to do this ? Please Advise

Regards

Riaz

          Followup:

it would be really cool just to say "yes" or "no", but -- i won't do that.

Here is what you need to do....

in your TEST environment,

o measure your current usage of space by these candidates
o recreate your indexes using index key compression.
o remeasure the space (but don't get excited, just measure and remember)
o run your test system to scale for a while.  find and fix any issues that might
  arise.  You might find you need more initrans (more rows/block), you might
  find you have a hot spot you didn't before.  You might even find a "product
  issue" or two that need patching.
o remeasure your index space again

Then

o make sure the savings is worth it to you (first measure vs LAST measure, after
the indexes had time to "grow" due to use)

o make sure everything works

o make sure performance is at least as good, if not better then before.

      Rating: 5
Very
      helpful  May 19, 2003
Reviewer:  Haiqing
       from NY USA
Hello Tom,
Thanks a lot for your detailed demonstration.  They are very helpful.
  It would be most appreciated if you could show us your pl/sql "show_space".
Thanks again Tom!
Best regards.

Haiqing

      Rating:
      5
  June 17, 2003
Reviewer:
      A reader
Hi Tom,
 Can we use compress table for CLOB and XMLTYPE?

 Thanks

          Followup:

LOBS are not compressable.

June 17, 2003 Om
      -- Thanks for the question regarding "Value column in v$sesstat", version
      8.1.7

      You Asked
Hi Tom,

Thanks for all the help.

Today my question is

1) How exactly a value is stored in the VALUE Column of v$sesstat
table.

Is it contained in milliseconds ?

Tom is there any site ot link where we can get documantation related
with each and every column or some of the most imp columns
in the Dict tables ?

Many thanks in advance.

Best regards,
Om

      and we said...
It depends.  See

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76961/apc.htm#289
It describes the statistics and what the VALUE is.

That same document

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76961/toc.htm
also contains the data dictionary description.

I'm always amused by requests for the "best setting", "most important views",
etc.  They are all relevant at different points in time.  What you need to do is
familarize (not memorize) yourself with what is available so that when the time
comes that you need it -- you are aware that it is there.  That is the same
reason we should all read the concepts guide with each and every release -- to
find what is new and refresh our memory as to what is available.....

Review &
      Followup

      Rating: 5
Datadictinary
      views  August 19, 2002
Reviewer:  Om
       from India
This is very useful link.Now I can easily figure out the
meaning of each and very column in the data dictionary tables.Thanks a lot for
this.

      Rating: 5
Why the Value
      for statistic# 12 are 0 for all the users?  June 17, 2003

Reviewer:  A Reader  from USA
Hi Tom,
For some reasons, the value for statistic# 12 (CPU used by this session) are 0
for the all the users, who currently login the database.  Can you please explain
why and how can I get the statistics back into the the v$sesstat view?
Thank you for you help.

          Followup:

do you have timed_statistics=true;

June 17, 2003 Ines
      -- Thanks for the question regarding "dynamic sql in pl*sql in V8",
      version version 8.0.5

      You Asked
Hi Tom,
is there an easier way to handle a 'select field1, ... from table' by
dynamic sql instead of using dbms_sql.bind_variable and these stuff?
The problem is that the number of fields can be different.

thanks for help
Ines

      and we said...
In all releases -- you'll have to use dbms_sql.* when you do not know the
number of fields.

What I typically do in a case like this, when I do not know the number of
fields/types of fields -- is just bind everything to 1 varchar2 field.  Since I
don't know their types at compile time it really does not matter if I fetch them
into a date or a string -- so I fetch into a string.  This makes it very easy to
use.

For example, here is a plsql snippet i have for 8.0.  It lets me execute
something like:

scott@8.0> @printtbl8 'select * from emp where ename = "KING" '
EMPNO                         : 7839
ENAME                         : KING
JOB                           : PRESIDENT
MGR                           :
HIREDATE                      : 17-nov-2081 00:00:00
SAL                           : 5000
COMM                          :
DEPTNO                        : 10
-----------------

PL/SQL procedure successfully completed.

(note my use of " instead of ' to pass in a CHARACTER STRING constant -- this is
just my convention, i use a replace in the code later on)....

The code to do this is simply:

scott@8.0> l
  1  declare
  2      l_theCursor     integer default dbms_sql.open_cursor;
  3      l_columnValue   varchar2(4000);
  4      l_status        integer;
  5      l_descTbl       dbms_sql.desc_tab;
  6      l_colCnt        number;
  7      procedure execute_immediate( p_sql in varchar2 )
  8      is
  9      BEGIN
 10          dbms_sql.parse(l_theCursor,p_sql,dbms_sql.native);
 11          l_status := dbms_sql.execute(l_theCursor);
 12      END;
 13  begin
 14      execute_immediate( 'alter session set nls_date_format=
 15                          ''dd-mon-yyyy hh24:mi:ss'' ');
 16      dbms_sql.parse(  l_theCursor,
 17                       replace( '&1', '"', ''''),
 18                       dbms_sql.native );
 19      dbms_sql.describe_columns( l_theCursor,
 20                                 l_colCnt, l_descTbl );
 21      for i in 1 .. l_colCnt loop
 22          dbms_sql.define_column( l_theCursor, i,
 23                                  l_columnValue, 4000 );
 24      end loop;
 25      l_status := dbms_sql.execute(l_theCursor);
 26      while ( dbms_sql.fetch_rows(l_theCursor) > 0 ) loop
 27          for i in 1 .. l_colCnt loop
 28              dbms_sql.column_value( l_theCursor, i,
 29                                     l_columnValue );
 30              dbms_output.put_line
 31                  ( rpad( l_descTbl(i).col_name,
 32                    30 ) || ': ' || l_columnValue );
 33          end loop;
 34          dbms_output.put_line( '-----------------' );
 35      end loop;
 36      execute_immediate( 'alter session set nls_date_format=
 37                             ''dd-MON-yy'' ');
 38  exception
 39      when others then
 40          execute_immediate( 'alter session set
 41                           nls_date_format=''dd-MON-yy'' ');
 42          raise;
 43* end;
scott@8.0>

See how I use a describe table to find the number of columns and then simply
loop to bind all of them to a varchar2(4000).  (very easy, very compact,
very safe).  Then I just fetch each of the columns into a single variable --
again very compact, very easy.

another alternative would be for you to code a "my_dbms_sql" helper package,
something like:

scott@8.0> create or replace package my_dbms_sql
  2  as
  3      procedure define_all( p_cursor in integer );
  4      type varchar2_table is table of varchar2(4000) index by
  5      binary_integer;
  6      function fetch_row( p_cursor in integer )
  7          return varchar2_table;
  8  end;
  9  /

Package created.

scott@8.0> create or replace package body my_dbms_sql
  2  as
  3  g_number_of_columns   dbms_sql.number_table;
  4  procedure define_all( p_cursor in integer )
  5  as
  6      l_columnValue   varchar2(4000);
  7      l_descTbl       dbms_sql.desc_tab;
  8      l_colCnt        number;
  9  begin
 10      dbms_sql.describe_columns( p_cursor,
 11                                 l_colCnt, l_descTbl );
 12      for i in 1 .. l_colCnt loop
 13          dbms_sql.define_column( p_cursor, i,
 14                                  l_columnValue, 2000 );
 15      end loop;
 16      g_number_of_columns(p_cursor) := l_colCnt;
 17  end;
 18  function fetch_row( p_cursor in integer )
 19       return varchar2_table
 20  is
 21      l_return        varchar2_table;
 22  begin
 23      for i in 1 .. g_number_of_columns(p_cursor) loop
 24          l_return(i) := NULL;
 25          dbms_sql.column_value( p_cursor, i,
 26                                 l_return(i) );
 27      end loop;
 28      return l_return;
 29  end;
 30  end;
 31  /

Package body created.

and now you could code more simply

scott@8.0> set verify off
scott@8.0> declare
  2      l_theCursor     integer default dbms_sql.open_cursor;
  3      l_status        integer;
  4      l_data          my_dbms_sql.varchar2_table;
  5
  5      procedure execute_immediate( p_sql in varchar2 )
  6      is
  7      BEGIN
  8          dbms_sql.parse(l_theCursor,p_sql,dbms_sql.native);
  9          l_status := dbms_sql.execute(l_theCursor);
 10      END;
 11  begin
 12      execute_immediate( 'alter session set nls_date_format=
 13                          ''dd-mon-yyyy hh24:mi:ss'' ');
 14      dbms_sql.parse(  l_theCursor,
 15         replace( 'select * from emp where ename = "KING"',
 16                      '"', ''''),
 17                       dbms_sql.native );
 18      my_dbms_sql.define_all( l_theCursor );
 19      l_status := dbms_sql.execute(l_theCursor);
 20      while ( dbms_sql.fetch_rows(l_theCursor) > 0 ) loop
 21          l_data := my_dbms_sql.fetch_row( l_theCursor );
 22          for i in 1 .. l_data.count loop
 23              dbms_output.put_line( l_data(i) );
 24          end loop;
 25      end loop;
 26      execute_immediate( 'alter session set nls_date_format=
 27                             ''dd-MON-yy'' ');
 28  exception
 29      when others then
 30          execute_immediate( 'alter session set
 31                           nls_date_format=''dd-MON-yy'' ');
 32          raise;
 33  end;
 34  /
7839
KING
PRESIDENT
17-nov-2081 00:00:00
5000
10

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating: 4
dynamic sql in
      pl*sql in V8  April 10, 2001
Reviewer:  chris
      barr  from Cambridge, MA USA
wonderful code examples, etc. ...
for DBMS_SQL, "execute immediate" as execute_immediate

      Rating: 4
Performance
      Consideration  January 21, 2002
Reviewer:
      Lakshmi Narasimhan R  from Dubai, UAE
Tom

What is the impact of Performance if we are using
dynamic sql (Execute immdiate in particular) in packages. particularly if i am
using in a loop of large number of iteration.  We are having 8.1.7 with RBO as
optimizer. I am forced to use execute immediate in a package which will loop
through 1000's of time to dynamically check few values.

          Followup:

Well, if the query is dynamic (not known at compile time) but is "static" for
the life of the session (eg: once you built it once in the session -- its built)
-- DBMS_SQL will be more performant.  See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:455220177497
for examples.  If you can PARSE once and execute 1,000's of times -- that is
better then parse/execute/close 1,000's of time (execute immediate will ALWAYS
parse/execute/close - no cursor reuse, with DBMS_SQL, you control it)

      Rating: 5
using Only
      Selects   January 23, 2002
Reviewer:  Lakshmi
      narasimhan R  from Dubai , UAE
Tom

Extremely thanks for the answer to my performance issue. As you said i am using
package Plsql tables only to construct the Select statement and parse it for
checking.  I am using it to avoid writting huge hierarchy of If statements.  I
am having around 9 columns in a big table each column having upto 4 values and
the combination is 9*4*4.   So instead of writting lot of if nested if i thought
of constructing a select statement and just parsing it into exec immediate like
below

Select 1 from dual where (my where clause constructed) and if it returns then my
condition satisfied otherwise condition failed. i am hoping this is the best way
for maintenance also and for performance also.

am i correct in my belief.

          Followup:

Static code is going to be faster then dynamically constructed code.

If you do this dynamic stuff - make sure you use BIND VARIABLES, if you build
unique statements and execute them without using bind varaibles, you will trash
your shared pool, kill performance and inhibit scalability.

      Rating: 5
Can you be
      truly dynamic in bulk?  February 25, 2002

Reviewer:  Adrian Billington  from UK
Tom

This dynamic cursor processing is excellent - but can we do it in array
processing in any way? I want to combine array fetching with UTL_FILE to dump
out csv data from tables unknown until runtime. So I thought I would take the
benefits of bulk fetching and then memory looping to write out the data, rather
than row-by-row looping.

I'm sure I could construct some dynamic PL/SQL using the DBMS_SQL.DESCRIBE
procedure but this would be long-winded. Do you think my approach is worth it or
should I stick to row-by-row?

Many thanks

Adrian

          Followup:

If you have my book -- I demonstrate fully array fetching with DBMS_SQL.  There
are array binds and array fetches available, read the docs on DBMS_SQL.

      Rating: 5
Using Native
      method.  February 25, 2002
Reviewer:
      Sandeep  from India, Bangalore
Very useful examples.

Is there a way to use native sql for "Method 4", where we are not sure about the
number of columns in the cursor.

          Followup:

Nope, not in plsql -- if you have my book I cover this in depth in a chapter on
dynamic sql.  I show you when dbms_sql is right and when NDS is right.  dbms_sql
is not dead!

      Rating: 3
Array fetching
      of unknown query  February 26, 2002
Reviewer:
      Adrian Billington  from UK
Tom

Thanks for the response. I do have your book and have recommended it to many
others as it is the best :)

As I have the book, I know that all your examples have EITHER pre-determined
queries for array fetching OR DBMS_SQL.DESCRIBE examples that fetch into a
V2(4000) variable.

I am trying to combine the two. I don't know the query until runtime and
therefore don't know how many PL/SQL tables to declare. So I'm constructing a
"v_plsql" variable which holds a dynamically built anonymous block based on the
DBMS_SQL.DESCRIBE of the query to dump the data.

Now I have completed an example and am about to benchmark it by comparing it to
something similar to your dump_table_to_csv row-by-row procedure. I'm pretty
sure that the bulk fetching will provide some benefit on large tables, so if you
wish, I can suplpy you with the details when done.

Thanks

Adrian

      Rating: 3
It's quicker in
      dynamic pl/sql and array fetching  February 26, 2002

Reviewer:  Adrian Billington  from UK
Tom

My benchmarks have shown that to dynamically construct a PL/SQL block to include
arrays for each column in the original query and use bulk fetching is quicker
than row-by-row fetching.

To dump 150K ALL_OBJECTS rows using row-by-row and concantenating the columns
into a V2(4000) variable took almost twice as long as building the PLSQL code
and dumping the data into arrays of 1000 and writing these out to file.

Just thought you might be interested ;)

Adrian

          Followup:

post the code!  share and share alike ;)

      Rating:
      5
print_table  February 27, 2002
Reviewer:
      Yogeeraj  from Mauritius
Hello,

I wanted to create the print_table procedure and demonstrate it all my
developers. Unfortunately, it is working eratically. is it that i did something
wrong?

First time, i created the procedure print_table, everything was fine:
-------------------------------------------------------
Connected to:
Oracle8i Enterprise Edition Release 8.1.7.0.0 - 64bit Production
With the Partitioning option
JServer Release 8.1.7.0.0 - 64bit Production

yd@cmtdb-SQL>@print_table

Procedure created.

Elapsed: 00:00:00.62
yd@cmtdb-SQL>

yd@cmtdb-SQL>exec print_table( 'select * from t where name = ''aaa''');
NAME                          : aaa
ADDRESS1                      : aaaa
ADDRESS2                      : aaaa
-----------------

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.09
yd@cmtdb-SQL>
yd@cmtdb-SQL>grant execute on print_table to public;

Grant succeeded.

Elapsed: 00:00:00.64
yd@cmtdb-SQL>grant select on t to cmtstore;

Grant succeeded.

Elapsed: 00:00:00.11

yd@cmtdb-SQL>create public synonym print_table for yd.print_table;

Synonym created.

Elapsed: 00:00:00.09
---------------------------------------------------------

Now, i connect as the user to whom i granted the procedure:

---------------------------------------------------------
yd@cmtdb-SQL>connect cmtstore/cmtstore@cmtdb
Connected.
cmtstore@cmtdb-SQL>exec print_table( 'select * from yd.t where name = ''aaa''');

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.02
----------------------------------------------------------
Oops! does not work... ;(

Now, i get back to my own account:
----------------------------------------------------------
cmtstore@cmtdb-SQL> connect yd/yd@cmtdb
Connected.
yd@cmtdb-SQL> exec print_table('select * from yd.t where name=''aaa''');

PL/SQL procedure successfully completed.

SQL>
----------------------------------------------------------
Oops! here too it does not work... ;(

Can you see something wrong? or is it that i did not do the steps right?

thanks
Yogeeraj

          Followup:

when you go "connect u/p" -- you lost the SET SERVEROUTPUT ON setting

I use a script "connect.sql" myself that is:

connect &1
@login

and have trained myself to always:

SQL> @connect scott/tiger

that way, the login.sql gets run and my default settings are always there.

      Rating: 3
How do I post
      code and results?  February 27, 2002
Reviewer:
      Adrian  from UK
Tom

How do I post my bulk fetch code as requested? Review says no more than 1,000
words...

Thanks

Adrian

          Followup:

send it to me, thomas.kyte@oracle.com, I'll add it.

Adrian Sent it and here it is:

Tom

Re-written to use bulk collect and limit and also declare the correct type of
arrays...

I've included the standalone procedure (that I'll now have re-engineer into my
package), the output code
and a summary of the benchmark results.

Note ABB_DUMP_TABLE used to benchmark is a copy and multiple-insert of
ALL_OBJECTS table up to about 150K rows...

----------------------
-- Procedure...
----------------------
CREATE OR REPLACE PROCEDURE dump_data_bulk_collect (
                            query_in       IN VARCHAR2,
                            file_prefix_in IN VARCHAR2,
                            directory_in   IN VARCHAR2,
                            array_size_in  IN PLS_INTEGER,
                            delimiter_in   IN VARCHAR2 DEFAULT NULL) IS

   v_fh          UTL_FILE.FILE_TYPE;
   v_ch          BINARY_INTEGER := DBMS_SQL.OPEN_CURSOR;
   v_sql         VARCHAR2(32767) := query_in;
   v_plsql       VARCHAR2(32767);
   v_dir         VARCHAR2(512) := directory_in;
   v_outfile     VARCHAR2(128) := file_prefix_in||'.dat';
   v_sqlfile     VARCHAR2(128) := file_prefix_in||'.sql';
   v_arr_size    PLS_INTEGER := array_size_in;
   v_col_cnt     PLS_INTEGER := 0;
   v_delimiter   VARCHAR2(1) := NULL;
   v_type        VARCHAR2(8);
   t_describe    DBMS_SQL.DESC_TAB;

   /* Procedure to output code for debug and assign plsql variable... */
   PROCEDURE put (
             fh_in     IN UTL_FILE.FILE_TYPE,
             string_in IN VARCHAR2
             ) IS
   BEGIN
      UTL_FILE.PUT_LINE(fh_in,string_in);
      v_plsql := v_plsql||string_in;
   EXCEPTION
      WHEN OTHERS THEN
         RAISE;
   END put;

BEGIN

   /* Open the file that the dynamic PL/SQL will be written to for debug... */
   v_fh := UTL_FILE.FOPEN(v_dir, v_sqlfile, 'W');

   /* Parse the query that will be used to fetch all the data to be written
out... */
   DBMS_SQL.PARSE(v_ch, v_sql, DBMS_SQL.NATIVE);

   /* Now describe the dynamic SQL to analyze the number of columns in the
query... */
   DBMS_SQL.DESCRIBE_COLUMNS(v_ch, v_col_cnt, t_describe);

   /* Now begin the dynamic PL/SQL... */
   put(v_fh,'DECLARE');
   put(v_fh,'   v_fh UTL_FILE.FILE_TYPE;');
   put(v_fh,'   CURSOR cur_sql IS');
   put(v_fh,'      '||REPLACE(v_sql,'''','''''')||';');

   /* Now loop through the describe table to declare arrays in the dynamic
PL/SQL... */
   FOR i IN t_describe.FIRST .. t_describe.LAST LOOP
      IF t_describe(i).col_type = 2 THEN
         v_type := 'NUMBER';
      ELSIF t_describe(i).col_type = 12 THEN
         v_type := 'DATE';
      ELSE
         v_type := 'VARCHAR2';
      END IF;
      put(v_fh,'   t_'||t_describe(i).col_name||'
DBMS_SQL.'||v_type||'_TABLE;');
   END LOOP;

   /* Syntax to set the date format to preserve time in the output, open the out
file and start to collect... */
   put(v_fh,'BEGIN');
   put(v_fh,'   EXECUTE IMMEDIATE ''ALTER SESSION SET NLS_DATE_FORMAT =
''''DD-MON-YYYY HH24:MI:SS'''''';');
   put(v_fh,'   v_fh :=
UTL_FILE.FOPEN('''||v_dir||''','''||v_outfile||''',''W'');');
   put(v_fh,'   OPEN cur_sql;');
   put(v_fh,'   LOOP');
   put(v_fh,'      FETCH cur_sql');
   put(v_fh,'      BULK COLLECT INTO
t_'||t_describe(t_describe.FIRST).col_name||',');

   /* Add all other arrays into the fetch list except the last... */
   FOR i IN t_describe.FIRST + 1 .. t_describe.LAST - 1 LOOP
      put(v_fh,'                        t_'||t_describe(i).col_name||',');
   END LOOP;

   /* Add in the last array and limit... */
   put(v_fh,'
t_'||t_describe(t_describe.LAST).col_name||' LIMIT '||v_arr_size||';');

   /* Now add syntax to loop though the fetched array and write out the values
to file... */
   put(v_fh,'      IF t_'||t_describe(t_describe.FIRST).col_name||'.COUNT > 0
THEN');
   put(v_fh,'         FOR i IN
t_'||t_describe(t_describe.FIRST).col_name||'.FIRST .. '||

't_'||t_describe(t_describe.FIRST).col_name||'.LAST LOOP');

   FOR i IN t_describe.FIRST .. t_describe.LAST LOOP
      put(v_fh,'
UTL_FILE.PUT(v_fh,'''||v_delimiter||'''||t_'||t_describe(i).col_name||'(i));');
      v_delimiter := NVL(delimiter_in,',');
   END LOOP;

   /* Add a new line marker into the file and move on to next record... */
   put(v_fh,'            UTL_FILE.NEW_LINE(v_fh);');
   put(v_fh,'         END LOOP;');

   /* Complete the IF statement... */
   put(v_fh,'      END IF;');

   /* Add in an EXIT condition and complete the loop syntax... */
   put(v_fh,'      EXIT WHEN cur_sql%NOTFOUND;');
   put(v_fh,'   END LOOP;');
   put(v_fh,'   CLOSE cur_sql;');
   put(v_fh,'   UTL_FILE.FCLOSE(v_fh);');

   /* Add in some exception handling... */
   put(v_fh,'EXCEPTION');
   put(v_fh,'   WHEN UTL_FILE.INVALID_PATH THEN');
   put(v_fh,'      DBMS_OUTPUT.PUT_LINE(''Error - invalid path.'');');
   put(v_fh,'      RAISE;');
   put(v_fh,'   WHEN UTL_FILE.INVALID_MODE THEN');
   put(v_fh,'      DBMS_OUTPUT.PUT_LINE(''Error - invalid mode.'');');
   put(v_fh,'      RAISE;');
   put(v_fh,'   WHEN UTL_FILE.INVALID_OPERATION THEN');
   put(v_fh,'      DBMS_OUTPUT.PUT_LINE(''Error - invalid operation.'');');
   put(v_fh,'      RAISE;');
   put(v_fh,'   WHEN UTL_FILE.INVALID_FILEHANDLE THEN');
   put(v_fh,'      DBMS_OUTPUT.PUT_LINE(''Error - invalid filehandle.'');');
   put(v_fh,'      RAISE;');
   put(v_fh,'   WHEN UTL_FILE.WRITE_ERROR THEN');
   put(v_fh,'      DBMS_OUTPUT.PUT_LINE(''Error - write error.'');');
   put(v_fh,'      RAISE;');
   put(v_fh,'   WHEN UTL_FILE.READ_ERROR THEN');
   put(v_fh,'      DBMS_OUTPUT.PUT_LINE(''Error - read error.'');');
   put(v_fh,'      RAISE;');
   put(v_fh,'   WHEN UTL_FILE.INTERNAL_ERROR THEN');
   put(v_fh,'      DBMS_OUTPUT.PUT_LINE(''Error - internal error.'');');
   put(v_fh,'      RAISE;');
   put(v_fh,'   WHEN OTHERS THEN');
   put(v_fh,'      DBMS_OUTPUT.PUT_LINE(SQLERRM);');
   put(v_fh,'      RAISE;');
   put(v_fh,'END;');

   /* Now close the cursor and sql file... */
   DBMS_SQL.CLOSE_CURSOR(v_ch);
   UTL_FILE.FCLOSE(v_fh);

   /* Execute the v_plsql to dump the data... */
   EXECUTE IMMEDIATE v_plsql;

EXCEPTION
   WHEN UTL_FILE.INVALID_PATH THEN
      DBMS_OUTPUT.PUT_LINE('Error - invalid path.');
      RAISE;
   WHEN UTL_FILE.INVALID_MODE THEN
      DBMS_OUTPUT.PUT_LINE('Error - invalid mode.');
      RAISE;
   WHEN UTL_FILE.INVALID_OPERATION THEN
      DBMS_OUTPUT.PUT_LINE('Error - invalid operation.');
      RAISE;
   WHEN UTL_FILE.INVALID_FILEHANDLE THEN
      DBMS_OUTPUT.PUT_LINE('Error - invalid filehandle.');
      RAISE;
   WHEN UTL_FILE.WRITE_ERROR THEN
      DBMS_OUTPUT.PUT_LINE('Error - write error.');
      RAISE;
   WHEN UTL_FILE.READ_ERROR THEN
      DBMS_OUTPUT.PUT_LINE('Error - read error.');
      RAISE;
   WHEN UTL_FILE.INTERNAL_ERROR THEN
      DBMS_OUTPUT.PUT_LINE('Error - internal error.');
      RAISE;
   WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE(SQLERRM);
      RAISE;
END dump_data_bulk_collect;
/

---------------------------------
-- Generated code...
---------------------------------
DECLARE
   v_fh UTL_FILE.FILE_TYPE;
   CURSOR cur_sql IS
      SELECT * FROM abb_dump_table;
   t_OWNER DBMS_SQL.VARCHAR2_TABLE;
   t_OBJECT_NAME DBMS_SQL.VARCHAR2_TABLE;
   t_SUBOBJECT_NAME DBMS_SQL.VARCHAR2_TABLE;
   t_OBJECT_ID DBMS_SQL.NUMBER_TABLE;
   t_DATA_OBJECT_ID DBMS_SQL.NUMBER_TABLE;
   t_OBJECT_TYPE DBMS_SQL.VARCHAR2_TABLE;
   t_CREATED DBMS_SQL.DATE_TABLE;
   t_LAST_DDL_TIME DBMS_SQL.DATE_TABLE;
   t_TIMESTAMP DBMS_SQL.VARCHAR2_TABLE;
   t_STATUS DBMS_SQL.VARCHAR2_TABLE;
   t_TEMPORARY DBMS_SQL.VARCHAR2_TABLE;
   t_GENERATED DBMS_SQL.VARCHAR2_TABLE;
   t_SECONDARY DBMS_SQL.VARCHAR2_TABLE;
BEGIN
   EXECUTE IMMEDIATE 'ALTER SESSION SET NLS_DATE_FORMAT = ''DD-MON-YYYY
HH24:MI:SS''';
   v_fh := UTL_FILE.FOPEN('/tmp','abb_dump_table.dat','W');
   OPEN cur_sql;
   LOOP
      FETCH cur_sql
      BULK COLLECT INTO t_OWNER,
                        t_OBJECT_NAME,
                        t_SUBOBJECT_NAME,
                        t_OBJECT_ID,
                        t_DATA_OBJECT_ID,
                        t_OBJECT_TYPE,
                        t_CREATED,
                        t_LAST_DDL_TIME,
                        t_TIMESTAMP,
                        t_STATUS,
                        t_TEMPORARY,
                        t_GENERATED,
                        t_SECONDARY LIMIT 1000;
      IF t_OWNER.COUNT > 0 THEN
         FOR i IN t_OWNER.FIRST .. t_OWNER.LAST LOOP
            UTL_FILE.PUT(v_fh,''||t_OWNER(i));
            UTL_FILE.PUT(v_fh,','||t_OBJECT_NAME(i));
            UTL_FILE.PUT(v_fh,','||t_SUBOBJECT_NAME(i));
            UTL_FILE.PUT(v_fh,','||t_OBJECT_ID(i));
            UTL_FILE.PUT(v_fh,','||t_DATA_OBJECT_ID(i));
            UTL_FILE.PUT(v_fh,','||t_OBJECT_TYPE(i));
            UTL_FILE.PUT(v_fh,','||t_CREATED(i));
            UTL_FILE.PUT(v_fh,','||t_LAST_DDL_TIME(i));
            UTL_FILE.PUT(v_fh,','||t_TIMESTAMP(i));
            UTL_FILE.PUT(v_fh,','||t_STATUS(i));
            UTL_FILE.PUT(v_fh,','||t_TEMPORARY(i));
            UTL_FILE.PUT(v_fh,','||t_GENERATED(i));
            UTL_FILE.PUT(v_fh,','||t_SECONDARY(i));
            UTL_FILE.NEW_LINE(v_fh);
         END LOOP;
      END IF;
      EXIT WHEN cur_sql%NOTFOUND;
   END LOOP;
   CLOSE cur_sql;
   UTL_FILE.FCLOSE(v_fh);
EXCEPTION
   WHEN UTL_FILE.INVALID_PATH THEN
      DBMS_OUTPUT.PUT_LINE('Error - invalid path.');
      RAISE;
   WHEN UTL_FILE.INVALID_MODE THEN
      DBMS_OUTPUT.PUT_LINE('Error - invalid mode.');
      RAISE;
   WHEN UTL_FILE.INVALID_OPERATION THEN
      DBMS_OUTPUT.PUT_LINE('Error - invalid operation.');
      RAISE;
   WHEN UTL_FILE.INVALID_FILEHANDLE THEN
      DBMS_OUTPUT.PUT_LINE('Error - invalid filehandle.');
      RAISE;
   WHEN UTL_FILE.WRITE_ERROR THEN
      DBMS_OUTPUT.PUT_LINE('Error - write error.');
      RAISE;
   WHEN UTL_FILE.READ_ERROR THEN
      DBMS_OUTPUT.PUT_LINE('Error - read error.');
      RAISE;
   WHEN UTL_FILE.INTERNAL_ERROR THEN
      DBMS_OUTPUT.PUT_LINE('Error - internal error.');
      RAISE;
   WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE(SQLERRM);
      RAISE;
END;

---------------------------------------
-- Results...
---------------------------------------
What arraysize to use? 1000
Row-by-row fetch on arraysize 1000..
Elapsed: 00:04:03.62

DBMS_SQL bulk fetch on arraysize 1000..
Elapsed: 00:02:17.38

Bulk collect on arraysize 1000..
Elapsed: 00:02:15.36

**********************************

What arraysize to use? 5000
Row-by-row fetch on arraysize 5000..
Elapsed: 00:03:56.28

DBMS_SQL bulk fetch on arraysize 5000..
Elapsed: 00:02:20.03

Bulk collect on arraysize 5000..
Elapsed: 00:02:15.86

**********************************

What arraysize to use? 10000
Row-by-row fetch on arraysize 10000..
Elapsed: 00:04:00.77

DBMS_SQL bulk fetch on arraysize 10000..
Elapsed: 00:02:20.42

Bulk collect on arraysize 10000..
Elapsed: 00:02:20.60

      Rating: 4
"cursor reuse"
      <> "parsed plan reuse" ?  October 14, 2002

Reviewer:  Robert C  from PA
Cool stuff ! thanks Adrian for sharing.

Tom in your followup, you said...
>>(execute immediate will ALWAYS
parse/execute/close - no cursor reuse, with DBMS_SQL, you control it) <<

"cursor reuse" is NOT the same,exact thing as "parsed plan reuse", is it ?

Also, I've been wondering why about this....
is it a fact that the search feature of this site
is not designed to look for article id like I searched for
"584023239495" & "ID 584023239495" for this one.

Thanks

          Followup:

correct, cursors are what cause you to parse.

You parse a statement and you have a cursor that points to it.  You can then
execute that cursor over and over and over without having to do a soft parse
again.

With EXECUTE IMMEDIATE, you have no cursor -- you do not explicitly control the
cursor -- hence it will soft parse each time.

I index the text - I do not have the id's (which are basically just random
numbers) in the body of the text I index.  Just the question, the answer and the
followups.

      Rating:
      4
Whoops...unnecessary loop ?  October 14, 2002

Reviewer:  Robert Chin  from PA
Tom, please confirm that in Adrian's "Generated Code"

>>OPEN cur_sql;
   LOOP
      FETCH cur_sql
      BULK COLLECT INTO t_OWNER,
<snip>
<<

THAT LOOP is not necessary but a bug.
I thought the idea of BULK COLLECT is so that we don't
"FETCH...LOOP" no more....
thanks

          Followup:

I didn't read his code, caveat emptor - use at your own risk (just like my code)

      Rating: 5
It's
      great!  October 14, 2002
Reviewer:  Rory
      Concepcion  from Philippines
Your solutions and scripts are great and very useful.
Just one question. I generate an error everytime I execute the @table/printtable
'select table_name from user_tables order by table_name';

TABLE_NAME                    : ACCOUNT
-----------------
TABLE_NAME                    : ACCOUNT_BILLING_INFO
-----------------
TABLE_NAME                    : ACCOUNT_PAYMENT_INFO
-----------------
TABLE_NAME                    : ACTIVITY
-----------------
TABLE_NAME                    : ADDRESS
-----------------
TABLE_NAME                    : APPCASE
-----------------
TABLE_NAME                    : AUDIT_LOG
-----------------
TABLE_NAME                    : BATCH_TEMP
-----------------
TABLE_NAME                    : BILLING
-----------------
TABLE_NAME                    : CALLCARDINFO
-----------------
TABLE_NAME                    : CAMPAIGN
-----------------
TABLE_NAME                    : CHAINED_ROWS
-----------------
TABLE_NAME                    : CONTACT
-----------------
TABLE_NAME                    : CURRENCY
-----------------
TABLE_NAME                    : CUSTACCTINFO
-----------------
TABLE_NAME                    : CUSTSERV
-----------------
TABLE_NAME                    : DATA_TEMP
-----------------
TABLE_NAME                    : DIGIFAULT
-----------------
TABLE_NAME                    : DISCOUNT
-----------------
TABLE_NAME                    : EP_ACC_REL
-----------------
TABLE_NAME                    : EP_CERTIFICATEREG
-----------------
TABLE_NAME                    : EP_CODESINTEMPLATES
-----------------
TABLE_NAME                    : EP_CON_REL
-----------------
TABLE_NAME                    : EP_CROSS_SALE
-----------------
TABLE_NAME                    : EP_MERGECODE
-----------------
TABLE_NAME                    : EP_PAR_REL
-----------------
TABLE_NAME                    : EP_TRANSACTION
-----------------
TABLE_NAME                    : EVENTS
-----------------
declare
*
ERROR at line 1:
ORA-20000: ORU-10027: buffer overflow, limit of 2000 bytes
ORA-06512: at line 42

Does the script have a limit in size?

Thanks

          Followup:

SQL> set serveroutput on size 1000000

1,000,000 bytes of output will be the max using this technique.

      Rating: 5
Robert, read my
      code properly  October 15, 2002
Reviewer:
      Adrian Billington  from UK
Tom

The reason I'm looping in my generated code is because I'm using LIMITs of 1000
rows to my BULK COLLECTs. Robert Chin completely failed to notice that when
"reading" the code...

Regards

Adrian

      Rating: 4
Dynamic PL/SQL
      blocks  December 12, 2002
Reviewer:  Praveen
      KV  from Bangalore
Dear Tom,

I came across the following problem.

How to dynamically change the 'conditional part' of an IF statement?

We are required to write a procedure to check the data in a table 'DataTab' such
that the data satisfy a set of criterias given in another table 'ConditionTab'.
For ex: if 'DataTab' contains a record with A=a1 then value of B must be b1. We
need to make note of those records that does not satisfies these conditions.

"DataTab"

A    B    C    D
--------------------------
a1    b1    c1    d1    correct record
a2    b2    c2    d2    correct record
a1    b0    c1    d1    incorrect record
a2    b2    c2    d0    incorrect record
--------------------------

"ConditionTab"

Field    Criteria
-------------------------
A    'A=a1 and B=b1'  The idea is to just pick the string in the
A    'A=a2 and B=b2'  criteria field and use this as the condition
C    'C=c1 and D=d1'  in an 'IF' clause
C    'C=c2 and D=d2'
-------------------------

One approch would be to directly hard code the logic, ignoring the existance of
'ConditionTab' table.

<for each loans loop>
  <for each conditions loop>

    if (loans.A =a1 and loans.b1) then --criteria from ConditionTab is hardcoded
here.
        <<do something>>
    end if;

  end loop;
end loop;

As you can guess this approch is tedious when conditions need to be changed
later.

In the following block I use the dynamic PL/SQL structure.

<<for each v_data in LoanTab loop>>

  <<for each v_conditions in ConditionTab loop>>
      v_sql :=   'BEGIN ' ||
                 'IF NOT (' || v_conditions.criteria || ' ) THEN ' ||
                <<do something>>
                       'END IF;' ||
           'END; ';
  end loop;
end loop;

EXECUTE IMMEDIATE v_sql;

Now 'v_conditions.criteria' is supposed to contain the string "if v_data.A=a1
and v_data.B=b1" (Assume 'v_data' is prefixed to A & B somehow)

I am getting the following error:

ERROR at line 1:
ORA-06550: line 1, column 11:
PLS-00201: identifier 'v_data.A' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored
ORA-06512: at "mydb.quality_check", line 51
ORA-06512: at line 1

Is there any other way to handle this problem? Or is it possible with dynamic
PL/SQL itself?

Kindly advise

Thanks and Regards

Praveen

          Followup:

tedious generally implies "most performant, assured to work at runtime".

I'm all for generic code, but if this is something that needs to be run a lot of
times, real code will massively outperf the dynamic code.

IF the criteria is really:

"ConditionTab"

Field    Criteria
-------------------------
A    A='a1' and B='b1'
A    A='a2' and B='b2'
C    C='c1' and D='d1'
C    C='c2' and D='d2'

Then, a simple

for x in ( select * from conditionTab )
loop
  execute immediate 'alter session set cursor_sharing=force';
  open ref_cursor for
  'select * from loanTab where not( ' || x.criteria || ' )';
  execute immediate 'alter session set cursor_sharing=exact';
  loop
      fetch ref_cursor into l_record;
      exit when ref_cursor%notfound;
      ..... process "bad" data ......
  end loop;
  close ref_cursor;
end loop;

      Rating: 5
Re:Dynamic
      PL/SQL blocks  December 16, 2002
Reviewer:
      Praveen KV  from Bangalore
Hi Tom,

Thankyou very much for the answer. But the answer did arise a few more doubts.

a) Why exactly we need to force a cursor sharing ?

b) Why we need to revert the cursor sharing property with in the loop itself ?

c) By using a different way of coding ie, using dynamic SQL instead of dynamic
PL/SQL, do you mean we cannot use the dynamic PL/slq structure to dynamically
change the condition-part of an 'IF' structure, as I had coded?

Please advise.

Thanks and regards

Praveen

          Followup:

a) because we want this to scale.  If you read this site on a periodic basis,
you must have hear me say at some point "use bind variables".  that is what
cursor_sharing is all about (search for

cursor_sharing

on this site to read more about it)

b) because we only want cursor sharing when we need it -- for that dynamically
opened cursor.  I would not blindly just set cursor sharing on for all other SQL
in there.  There are side effects from it that might not be good for your
existing code.

c) you don't need to -- so I didn't?  You asked a question and I showed you how
I would approach it.  I would do AS MUCH WORK AS POSSIBLE in SQL (which I did
and you were not) and as little as possible in PLSQL -- dynamic/static, doesn't
make a difference.  Let SQL do the work whenever possible.

      Rating: 3
How to use NVL
      in Refcursor.  January 25, 2003
Reviewer:
      Chandra S.Reddy  from Hyd, India
Hi Tom,
Your answers are very great and useful.

I have one issue related usage of NVL in dynamic query.
Please look into the below code.
Here I definately want to construct dynamic query and must be executed through
Refcursor.

PROCEDURE SP_SWIP_GetWires( --some more params--- ,out_cvGenric OUT
PKG_SWIP_CommDefi.Gencurtyp) IS
      v_cSelectQuery    VARCHAR2(1000) ;
BEGIN
    v_cSelectQuery := 'SELECT WR.WIRE_TRN_NUMBER,
                TO_CHAR(WR.WIRE_DATE,' || CHR(39)|| 'MM/DD/YY' || CHR(39)||'),
                  WR.SU,
                  '||CHR(39)||'Y'||CHR(39)||' POST_TO_LNCTR_STATUS_FLAG,
                    WR.PROCESS_STATUS_ID
            FROM    T_SWIP_WIRE WR
            WHERE    WR.REDIRECT_OPERATION_CENTER_ID = :OperationCenterId ' ;

       OPEN out_cvGenric FOR v_cSelectQuery     USING in_nOperationCenterId;

EXCEPTION
        -- more code here

END SP_SWIP_GetWires; --End of procedure

In the above code, SU column of T_SWIP_WIRE WR table will contain NULL
values.There I should apply NVL function here and it should be like
NVL(WR.SU,'').i.e empty string.
If I do so, code is throwing errors and did not get records.
And also NULL values should be displayed at top in order if I apply sort on SU
column.

Please suggest me.

          Followup:

Looks like financial stuff -- important data -- money involved.  Yet only 3
short years after y2k and you are using yy??????  bbbbbaaaaaadddddd idea don't
you think?

empty string is null as well.  that is like saying nvl(x,null).  pick something
else.

order by su NULLS FIRST

or

order by su NULLS LAST

you know, you can avoid all of those extra concats by:

v_cSelectQuery := 'select wr.wire_trn_number,
                          to_char( wr.wire_date, ''mm/dd/yyyy'' ),
                          wr.su,
                        ''Y'' post_to_lnctr_status_flag,
                          wr.process_status_id

Also -- if that code accurately reflects your code -- don't use dynamic sql at
all, use static sql, no need for dynamic.

      Rating: 5
DBMS_SQL Array
      fetching  April 14, 2003
Reviewer:  AndreyP
       from Germany
Hello,
what if we need to "array fetch" a Varchar2(4000) field using DBMS_SQL? Or we
can do this only with row-by-row fetching? You know what I mean, since the
definition in DBMS_SQL is:

type Varchar2_Table is table of varchar2(2000) index by binary_integer;

          Followup:

it is currently limited at 2000.

      Rating: 4
Related to
      original question  June 17, 2003
Reviewer:
      sam  from NY, USA
Tom can we do 'for update' in dynamic cursors??
I am trying to do something like this

TYPE reference_cur IS REF CURSOR;
c1 reference_cur;
open c1 for 'select cust_num from ' || table_name || ' for update';
-- table_name is passed as parameter into procedure
loop
        fetch c1 into custnum;
        exit when c1%notfound;

some calculations here

  execute immediate 'update ' || table_name || ' set
  deleted_ind=' || '''N''' || ' where current of c1';

end loop;

What I want to do is use ' for update / current of '
clause in ref cursor.

Thanks in advance

          Followup:

select out the rowid and just 'update .... where rowid = :x' using ROWID;

and make sure to use binds everywhere....

and try to avoid dynamic sql unless you totally 100% have no other way to do it.

      Rating: 5
dynamic sql in
      pl*sql in V8  November 10, 2003
Reviewer:
      Reader  from MA
Hi Tom:

Your solution to handling a cursor with variable number of columns is great.

June 17, 2003 Fabricio -- Thanks for the question regarding
      "Kill a session from database procedure", version 7.3.4

      You Asked
How i can kill a session from a stored database procedure. There
is some way to do this?

      and we said...
Sessions are killed via the command

alter system kill session 'sid,serial#'

where sid and serial# are obtained from the v$session dynamic
performance table.

Alter system is considered DDL and hence cannot be executed
directly from PLSQL.  We must use dynamic sql to accomplish this
feat.  In 7.3, we would use the dbms_sql package to accomplish
this.  It could look something like:

create or replace procedure kill_session( p_sid in varchar2,
                                          p_serial# in varchar2)
is
    cursor_name     pls_integer default dbms_sql.open_cursor;
    ignore          pls_integer;
BEGIN
    select count(*) into ignore
      from V$session
     where username = USER
       and sid = p_sid
       and serial# = p_serial# ;

    if ( ignore = 1 )
    then
        dbms_sql.parse(cursor_name,
                      'alter system kill session '''
                                ||p_sid||','||p_serial#||'''',
                       dbms_sql.native);
        ignore := dbms_sql.execute(cursor_name);
    else
        raise_application_error( -20001,
                               'You do not own session ''' ||
                                p_sid || ',' || p_serial# ||
                               '''' );
    end if;
END;
/

The owner of this procedure needs to have

o SELECT on v_$session granted to them by SYS.  This grant
must be directly to them, not via a role.

o ALTER SYSTEM granted directly to them -- not via a role.

To see why the caveat on granting directly to them is needed,
please see:

http://osi.oracle.com/~tkyte/Misc/RolesAndProcedures.html
You would then grant execute on this procedure to anyone you
want.  It would allow them to kill any session they own (running
under their username).  You would probably want to "grant select
on v_$session" when connected as SYS to these people as well so
they can 'see' the v$session dynamic performance view to get
their sid/serial# pairs.  Alternatively, you could:

create or replace view MySessions
as
select * from v$session where username = USER;

and grant them select on that view.  That view will show them
their sessions only.

Review &
      Followup

      Rating: 1
Kill a session
      from database procedure  October 10, 2002

Reviewer:  Scott Morin  from USA
Granting alter system to the user allows them to kill any session to the
procedure become redundant and offers no protection against the users killing
any other session. Dangerous!

          Followup:

You missed the point entirely.

One of the primary uses of PLSQL stored procedures is to encapsulate logic.

Suppose, you wanted BOB and MARY to be able to kill sessions but NOT do any
other ALTER SYSTEM commands?  What would you do?

I would

a) create a schema, granting it the needed privs
b) create this procedure in there
c) grant execute on this procedure to BOB and MARY
d) lock the account (or just revoke CREATE SESSION from it, munge the password,
whatever)

So, now BOB and MARY have the ability to kill sessions but not change ANY other
system settings.

Further, since it is procedural logic, you CONTROL exactly what is going on.
Maybe, you want to make it so that BOB can kill only HIS sessions and MARY only
hers -- how easy would that be?  Trivial.  Oh wait, it already does that.

      Rating: 5
Kill a session
      from database procedure  October 10, 2002

Reviewer:  Scott Morin  from USA
Granting alter system to the user allows them to kill any session to the
procedure become redundant and offers no protection against the users killing
any other session. Dangerous!

          Followup:

not when used as described!  Then it makes it very very safe (and is the way to
grant very very granular priveleges like just "kill session" or "kill your own
sessions" to people).

Maybe I didn't make the fact that the "owner" of this procedure is some
"utility" account like CTXSYS, MDSYS, ORDSYS, OUTLN and so on are in Oracle and
should be "locked" and protected from general use.

      Rating: 4
how to kill a
      stored proc by name  April 28, 2003
Reviewer:
      Jerry  from USA
Tom,

Want to follow up on this.  How do I kill a stored proc by name?  I have a web
user having multiple sessions open in the database.  I only want to kill the one
that's running stored proc.  How do I find out which session is the stored proc
since the sql_text is reflecting the queries within the stored proc.

          Followup:

have the developer put a call into their procedure using
dbms_application_info.set_client_info -- that way you can tell what procedure it
is.

In fact, have them put these calls all over their code -- setting the
client_info, action, and module columns in v$session with useful information for
you.

      Rating: 4
What is the
      datatype of SID / SERIAL#  June 17, 2003
Reviewer:
      Dilbert  from San Jose, Ca, USA
Hi Tom,

Your procedure is a VERY VERY helpful one.
I have a question though about the parameters. The Input parameters are defined
as VARCHAR2 while when I looked into the definition of v$session, both of them
are NUMBER datatype. Is there a particular reason you made the Input parameters
as Varchar2 ?

          Followup:

no reason in particular.  does violate my "compare number to numbers strings to
strings dates to dates" rule ;)

oversight, pure and simple


Subject | Article | Last Updated (East coast time)
Converting cumulative values to non-cumulative values | article only | Jun 18, 2003 01:29 pm
oracle 9i Data Warehousing features | article only | Jun 18, 2003 06:02 pm
Serializable Transaction | article only | Jun 18, 2003 06:09 pm
Index row overheat | article only | Jun 18, 2003 06:16 pm
commit in a trigger | article only | Jun 19, 2003 07:34 am
How to trap the table name in a system level trigger. | article only | Jun 19, 2003 07:43 am
Clarifications in SQL Plus | article only | Jun 19, 2003 07:55 am
VPD predicate executes trice | article only | Jun 19, 2003 07:59 am
Printing a report to file in .pdf format | article only | Jun 19, 2003 08:02 am
resuing the refcursor before passing it | article only | Jun 19, 2003 12:49 pm
Application security versus Database security | article only | Jun 19, 2003 01:03 pm
Lock/Grant | article only | Jun 19, 2003 01:05 pm
tnsnames.ora TCP vs. IPC for DB on local host | article only | Jun 19, 2003 01:20 pm
RMAN Error | article only | Jun 20, 2003 03:26 pm
Implications of dbms_stats.gather_schema_stats | article only | Jun 20, 2003 03:47 pm
Query | article only | Jun 20, 2003 04:05 pm
Locally Managed Tablespaces - LMT | article only | Jun 20, 2003 04:07 pm
High Water Mark | article only | Jun 20, 2003 04:37 pm
Import And Export | article only | Jun 20, 2003 04:39 pm
creating trigger for deleting rows(giving mutating error) | article only | Jun 20, 2003 05:00 pm
Combining rows | article only | Jun 20, 2003 05:29 pm
logon trigger | article only | Jun 20, 2003 05:29 pm
Append hint in archivelog mode. | article only | Jun 20, 2003 05:54 pm
Can the "analyze table" command cause major adverse database access problems | article only | Jun 20, 2003 05:56 pm
difference between cursors for loop and normal explicit cursors | article only | Jun 21, 2003 10:03 am
Soundex searches | article only | Jun 21, 2003 10:17 am
Calculating total size of tablespaces with autoextend on | article only | Jun 21, 2003 10:35 am
set timing on | article only | Jun 21, 2003 10:36 am
Closing/Reopening a cursor | article only | Jun 21, 2003 02:54 pm
Application logging and error handling | article only | Jun 21, 2003 09:05 pm
SPFILE backup | article only | Jun 22, 2003 01:28 pm
Resequence The Column Order in a Table | article only | Jun 22, 2003 04:29 pm
Chaining Occurs when Adding additional Columns with DEFAULT VALUE | article only | Jun 22, 2003 09:52 pm
STATSPACK Report.. | article only | Jun 23, 2003 08:20 am
Difference between tkprof and Explain plan | article only | Jun 23, 2003 08:57 am
Datafile Resize( Shrinking) | article only | Jun 23, 2003 11:17 am
Oracle Hot Backup and recovery | article only | Jun 23, 2003 11:45 am
Tricky Trigger Question | article only | Jun 23, 2003 01:32 pm
sql query results | article only | Jun 23, 2003 07:12 pm
Get client remote client IP address, | article only | Jun 24, 2003 07:22 am
Top Sql sessions | article only | Jun 24, 2003 07:24 am
Analytical Function Usage | article only | Jun 24, 2003 07:34 am
Implicit Commit | article only | Jun 24, 2003 07:34 am
Returning a named constant in a SQL query | article only | Jun 24, 2003 07:49 am
what is OLAP | article only | Jun 24, 2003 07:52 am
row(s) 1 - 45 of 45
June 18, 2003 Chandra -- Thanks for the question regarding "Converting cumulative values to non-cumulative values", version 8.1
You Asked
I have got the data as follows.

machine_id    cum_hours
1000          250
1001          550
1002          850

I want to get the data as follows

machine_id    hours
1000          250
1001          300
1002          300

Any ideas?
and we said...
ops$tkyte@ORA920LAP> select id, cum_hours, cum_hours - nvl(lag(cum_hours) over
(order by id),0)
  2    from t
  3   order by id;

        ID  CUM_HOURS CUM_HOURS-NVL(LAG(CUM_HOURS)OVER(ORDERBYID),0)
---------- ---------- ----------------------------------------------
         1        250                                            250
         1        550                                            300
         1        850                                            300
Review & Followup
Rating: 5
Converting cumulative values to non-cumulative values June 18, 2003
Reviewer: Chandra Rentala from Atlanta GA USA
Great..it works. Thanks

June 18, 2003 Subramanian -- Thanks for the question regarding "oracle 9i Data Warehousing features", version 9.2.0.1
You Asked
Hi Tom,

We have recently purchased licenses for oracle 9i EE, 9iAS and 9iDS.
And we want to build a datamart, to pull multi-dimensional reports.

For ETL we have Data Warehouse Builder and ETL functions build in the database.

For data mining, do we need the licenses for OLAP option in the database or can
we use the discoverer/BI beans?

We expect around 20 to 30 GB of data with around 300 users.
Can't we use star schema/materialized views/discoverer to generate
multi-dimensional reports. What extra benefits would we get with OLAP option?

Thanks for taking this question.
and we said...
Depends on your definition of data mining!

There is pure, true data mining ala:
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/datamine.920/a95961/toc.htm
this is technology we integrated over the years from Thinking Machines -- their
"Darwin" product.  This is true data mining, see
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/datamine.920/a95961/1concept.htm#918153
for a quick overview.  It is probably not what you are talking about (as people
who want to do "true data mining" are statisticians and are typically very
specific as to their needs)....  This Data mining is an extra cost option

Then there is OLAP:
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/olap.920/a95295/toc.htm
This is the ability to build multi-dimensional cubes and do some pretty cool
things that would either be really truly hard or impossible in straight SQL.
See
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/olap.920/a95295/basics2.htm#1005586
for a quick overview.

This OLAP capability is an extra cost option.

Then, there is conventional data warehousing:
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96520/toc.htm
this is what you bought.  Yes, you have the totally powerful analytic functions,
materialized views, star transformations, query rewrites -- a ton of stuff.  You
can use the tools in jdev such as the bi-beans to visualize or any number of
tools such as discoverer (you might find that discoverer is the tool for you --
less code)

20-30 gig is smallish, you should be able to go to town with what you have --
depending on your needs.  Generally, if you needed true OLAP of DM -- you would
"know it".
Review & Followup
Rating: 5
November 23, 2002
Reviewer: Subramanian

Rating: 3
I need some document for Information about error message on warehouse builder 9i June 17, 2003
Reviewer: Oracle User
ERROR MESSAGE:
"VLD-1108 Operatoe SPLIT is not properly connected. "
Operator SPLIT requires more input or output connections in order to generate
mapping code.

DETAIL:
I have 2 tables(A, B) and one function(C - Reconcile which returns either Y or
N).  Then Join all three and use "SPLIT" to separate into 3 groups of data set.

These are the assumptions in the SPLIT criteria
if Reconcile_result = 'Y' and Master.Product = 'A' and details.product = 'A'
then ...

if Reconcile_result = 'Y' and Master.Product = 'B' and details.product = 'B'
then ...

if Reconcile_result = 'Y' and Master.Product = 'C' and details.product = 'C'
then ...

After the execution, it returns error message as above.

Thank you

Rating: 3
Another Validate Error June 17, 2003
Reviewer: OWB'User
Can we search info about error message in Oracle.com web?

I have another error like this :
VLD-1111 - The mapping cannot be generate due to operators requiring different
code languages.
Detail :- For mapping code generation, the generated language needs to be the
same for every operator in the mapping.
Check the mapping and remove conflicting components.

Followup:
metalink.oracle.com -- the support website.

June 18, 2003 Peter -- Thanks for the question regarding "Serializable Transaction", version 8.1.7
You Asked
Hi Tom,

On page 130-131 of your book, "expert one-on-one Oracle," you gave an example of
two sessions querying and inserting into tables a and b. The result is that both
tables contain a row with the value zero in them.  My question is that wouldn't
the result be the same even if the isolation_level is not serializable?  Not
sure what you're demonstrating by this example.

Thank

- peter
and we said...
Allow me to clarify.  The text in question is as follows.  The text in BOLD is
what the example is trying to demonstrate.  There is a common misconception that
serializable MEANS serial execution -- that there is some serial ordering of
transactions.  If that were true, then the results we observe in my example
would be impossible -- there is NO serial (one after the other) ordering of
those two transactions that could result in both sessions inserting 0.

<quote>
Serializable

This is generally considered the most restrictive level of transaction
isolation, but provides the highest degree of isolation. A serializable
transaction operates in an environment that makes it appear as if there are no
other users modifying data in the database, the database will be "frozen" at the
point in time your query began. Your transaction sees the database consistently,
at a single point in time. Side effects (changes) made by other transactions are
not visible to it, regardless of how long it has been running.  Serializable
does not mean that all transactions executed by the users are the same as if
they were executed one right after another in a serial fashion.  It does not
imply that there is some serial ordering of the transactions that would result
in the same outcome.  This last point is a frequently misunderstood concept and
a small demonstration will clear it up.  The following table represents two
sessions performing work over time. The database tables A and B referenced in
the example start out empty and are created as such:

tkyte@TKYTE816> create table a ( x int );
Table created.

tkyte@TKYTE816> create table b ( x int );
Table created.

Time    Session 1 Executes    Session 2 Executes
0:00    Alter session set isolation_level=serializable;
0:01                            Alter session set isolation_level=serializable;
0:02    Insert into a select count(*) from B;
0:03                            Insert into b select count(*) from A;
0:04    Commit;
0:05                            Commit;

Now, when this is all said and done - A and B will each have a row with the
value of zero in it.  If there was some "serial" ordering of the transactions -
we could not possibly have both tables containing the value zero in them.  If
Session 1 executed before Session 2 ? then table B would have a count of 1.  If
Session 2 executed before Session 1 ? then table A would have a count of 1.
Both tables however will have a count of ZERO.  They just executed as if they
were the only transaction in the database at that point in time.  No matter how
many times Session 1 queried table B - the count would be the count that was
committed in the database at time 0:00.  Likewise, no matter how many times
Session 2 queries table A - it will be the same as it was at time 0:01.
</quote>

So, what I was trying to demonstrate is what serializable means --  in short:

A serializable transaction operates in an environment that makes it appear as if
there are no other users modifying data in the database, the database will be
"frozen" at the point in time your query began. Your transaction sees the
database consistently, at a single point in time. Side effects (changes) made by
other transactions are not visible to it, regardless of how long it has been
running.
Review & Followup
Rating: 5
Excellent explanation March 30, 2002
Reviewer: Reader from US
Excellent  explanation

Rating: 1
For isolation_level Serializable March 31, 2002
Reviewer: Sudhanshu Jain from India, Delhi
I completely agree with peter, in the experiment what you are trying to
demonstrate is just controlled by Commit time.

If i commit in 1st Session before 2nd session runs it scripts, i will see the
data else irrespective of Isolation_level it will be 0.

Followup:
Sigh,

here are the facts:

1) i am using serializable.

2) 99.99% of the world seems to believe "serializable means there is some serial
ordering of transactions that would achieve the same results".  That is, when in
serializable mode -- there must be some way to order the transactions
CONSECUITIVELY such that the same outcome is achieved"
3) The premise in #2 is wrong.

The example is not demonstrating any wild new crazy feature of serializable.
   It is not intended to show how serializable is different from read committed
in Oracle.  The example is showing that serializable DOES NOT imply *serial
ordering*.  That is all it is doing (and it does it quite simply -- perhaps TOO
simply)

I know, 100% know, that the same outcome in Oracle would be achieve in read
committed.  I know that -- it is not relevant to the concept being demonstrated
-- that serializable DOES NOT imply there is some serial ordering.

Rating: 3
Serializable March 31, 2002
Reviewer: Sudhanshu Jain from India, Delhi
I now understand your point of experiment.

But still i could not understand PURPOSE of Serializable. What additional i am
achieving by Serializable, other than, what is given by read consistent etc.

Followup:
(for the whole story, you can read the chapter in my book that contains pages
130-131 -- I go into this in detail as it is a very important topic to
understand).

It all stems from the isolation levels defined and how they are defined.  Ansi
defines them using specific "phenomena".

These phenomena are (excerpted from my book)

<quote>
The ANSI/ISO SQL92 standard adds to the concurrency control mix as well. In this
standard, they define four levels of transaction isolation with different
possible outcomes for the same transaction mixes. That is, the same work
performed in the same fashion with the same inputs, may result in different
answers given your isolation level. These isolation levels are defined in terms
of three 'phenomena' that are either permitted or not at a given level. These
phenomena are:

o Dirty read ? The meaning of this is as bad as it sounds. You are permitted to
read uncommitted 'dirty' data. This is the effect you would achieve by just
opening an OS file someone else is writing, and reading whatever data happened
to be there. Data integrity is compromised, foreign keys violated, unique
constraints ignored.

o Non-repeatable read ? This simply means that if you read a row at time T1, and
attempt to re-read that row at time T2, the row may have changed. It may have
disappeared, it may have been updated, and so on.

o Phantom read ? This means that if you executed a query at time T1, and
re-execute it at time T2, additional rows may have been added to the database,
which affects your results. This differs from the non-repeatable read in that in
this case, data you already read has not been changed but rather that more data
satisfies your query criteria than before.

SQL92 takes these three phenomena and creates four isolation levels based on the
existence, or lack thereof, of the above phenomena. They are:

Isolation Level            Dirty Read      Non-Repeatable Read    Phantom Read
Read Uncommitted        Permitted       Permitted              Permitted
Read Committed                          Permitted              Permitted
Repeatable Read                                                Permitted
Serializable

Oracle supports explicitly two of the above isolation levels as they are defined
? read committed and serializable. This doesn't tell the whole story however.
The SQL92 standard was attempting to set up isolation levels that would permit
various degrees of consistency for queries performed in each level. Repeatable
read is the isolation level they claim you must be at to get a read consistent
result from a query. Read committed does not give you consistent results. Read
uncommitted is the level to use to get non-blocking reads.
</quote>

So, serializable gives you the ability to prevent "phantom reads" -- a really
really repeatable read if you will.  To paraphrase:

A serializable
transaction operates in an environment that makes it appear as if there are no
other users modifying data in the database, the database will be "frozen" at the

point in time your query began. Your transaction sees the database consistently,

at a single point in time. Side effects (changes) made by other transactions are

not visible to it, regardless of how long it has been running.

that is what is given beyond read committed.  In a serializable transaction if
you do the following:

t1       select count(*) from b -- returns "0"
t2                                                   insert into b values(1);
t3                                                   commit;
t4                                                   select count(*) from b --
                                                         returns "1"
t5       select count(*) from b -- returns "0"
t6       commit;
t7       select count(*) from b -- returns "1"

Using read committed, the query at time t5 would return 1, not 0.  You get a
"phantom read"

Rating: 4
Serializable March 31, 2002
Reviewer: Sudhanshu Jain from India, Delhi
Fantastic Feature, and great explanation.

Thanks.

Rating: 5
Serializable vs. Read Committed April 01, 2002
Reviewer: Saravanakumar Natarajan from Hyderabad, India
In the follow-ups of Sudhanshu Jain, i feel the tabular column is wrong..
Phantom read and dirty reads are interchanged.... 'Dirty Read' is not permitted
except 'Read Uncommitted' Isolation level...

Followup:
darn cut and paste from word.  You are right the chart was like this:

         dirty      non-repeat        phantom
         x          x                 x
         x          x
         x

and should have been this:

         dirty      non-repeat        phantom
         x          x                 x
                    x                 x
                                      x

the tabs messed it up.  Sorry about that -- I corrected it (thanks!)

Rating: 4
Serializable April 01, 2002
Reviewer: sudhanshu jain from India, Delhi
What happens to Locking? What Happens to Redo Entry, does oracle reads the
previous data from rollback segment itself, which could lead to again snapshot
too old like error.

Followup:
Locking is done in exactly the same fashion as ever.

reads don't block writes
writes don't block reads
writes only block writes of the SAME exact data.

Oracle uses UNDO (not redo) to give each serializable transaction a "frozen"
picture of the database at the point in time the transaction began.

You can lead to snapshot too old or cannot serialize access errors, yes.
Serializable transactions are to be use for very short duration OLTP
transactions where a snapshot too old should not ever be an issue.

Rating: 3
serializable transaction April 02, 2002
Reviewer: kit from england
In your example why did t7 then return 1 as t3 also did  a commit. Whats the
difference between t4 and t5.

Are Serial transactions similar in creating a savepoint

Followup:
t3 was another session.

Until that first session commits -- it sees the database frozen at the point in
time the transaction began.  That is the entire purpose of "serializable"

see
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/c23cnsis.htm#2778
for a more in depth discussion.

Rating: 4
How does it use rollback segments? June 06, 2002
Reviewer: Colin Davies from Pasadena, California
I read your excellent book, but it was uncear to me how Oracle achieves
SERIALIZABLE, in that it was not explained how the rollback segments were
affected.  In normal operation Oracle uses the rollback information to maintain
a consistent read of the data (sometimes resulting in the dreaded "ORA-01555
Snapshot too old" error).  Is this how it does it for other isolation levels,
such as SERIALIZABLE?

Followup:
Exactly.

I alluded to this with this paragraph in chapter 3 when discussing serializable
transactions:

...
In Oracle, serializability is achieved by extending the read consistency we get
at the statement level to the transaction. Instead of results being consistent
with respect to the start of a statement, they are preordained at the time you
begin the transaction. Pretty deep thought there – the database already knows
the answer to any question you might ask it, before you ask it.
......

We use the rollback (UNDO) information in the same way we would for a single
statement -- it is just that the point in time your query is assumed to have
started is the point in time the transaction began.

Rating: 4
Starting point of a serializable transaction February 07, 2003
Reviewer: D. Robinson from Hartford, CT
Obviously, a COMMIT marks the end of a transaction, but what marks the start of
a transaction in serializable mode?  Is it the first SELECT since the last
commit, the first UPDATE/INSERT/DELETE since the last commit, the first SELECT
or DML of any kind since the last commit, or something else not mentioned?

Followup:
the first statement of the transaction.

A commit is the last statement -- so anything after that.

(next select would be enough)

Rating: 5
Set Transaction February 07, 2003
Reviewer: Mark J. Bobak from Ann Arbor, MI
Actually, Tom, I think the answer to his question:
"...but what marks the start of a transaction in serializable mode?" would be
"set transaction isolation level serializable".

If the transaction begins with anything else, the isolation
level is not serializable.

-Mark

PS  Looking forward to seeing you in Dallas next week.

Followup:
ops$tkyte@ORA920> alter session set isolation_level=serializable;

Session altered.

don't have to use set transaction.

Rating: 4
Good Explanation February 07, 2003
Reviewer: H S Anand from New Delhi, India
Even after working with Oracle Databases for about 2.5 years, I still had a
muddy concept of this "isolation level serializable." I had this
misunderstanding that there is some serial ordering of the transactions. I
really appreciate the explantion.

Rating: 5
How could this be solved ? April 03, 2003
Reviewer: David
Suppose something like:

procedure p (...)
is
...
begin
select count(*) into v_count from t1 where status=p_status
if v_count <> 0 then raise e_status_exists; end if;
insert into t2 values (p_id, p_status);
...
commit;
exception
   when e_status_exists then ...
end;

1) I would like to replace the "stinky" count(*) test with an exception test. I
was thinking of something like (as you said in another thread):

begin
       begin
          select x into y from t where ...;
       exception
          when no_data_found then y := default_for_y;
       end;
       (continue processing)
   end;

2) I would like to guarantee consistency along my code. Suppose someone else
comes in between my select and my insert and inserts a row where status=p_status
or updates a row to p_status. Then my database would be inconsistent because I
can only insert into t2 as long as there is no p_status in t1.

3) Would locking be a solution ?

4) Would serializable be a solution ?

Followup:
1)

begin
   for x in ( select 1 x from t1 where status = p_status )
   loop
       raise e_status_exists;
   end loop;
   .....

exception
   ....

2) an "anti foreign key".  Basically -- you'll need to lock the table T1 to
prevent modifications to it during this period of time.  And inserts into T1
will need to do the same to T2.  To force serialization.

It is a funny situation -- curious -- what is the business requirement that is
driving this?  how did you get here?

3) yes.

4) no.

Rating: 5
Business requirement April 03, 2003
Reviewer: David
Ok, thanks for the insight -- it was VERY useful.

The code I wrote above was an over-simplification of my application -- and I
think in that process I confused us both.

I don't have "t1" and "t2". They are both one table "t". I did it so a unique
key wouldn't be your answer.

Now, forget it all -- never mind. Start from zero here. What I really want to
know is...

In fact, the business rule that is bugging me is:

I have table t(id, name, status). Status can be 0 or 1.

I have to implement: "I cannot insert a new (or update an existing) record in t
if there's already a record with the same name as the new one, if status is 0.
Any other combination is valid."

That is, I can have:

name status
==== ======
abc  0
abc  1
def  1
def  1

But never:

name status
==== ======
abc  0
abc  0

I thought of something like a unique key, but only when status is 0. What would
be the best approach ? Maybe a trigger ?

Followup:
As pooh bear says "oh bother"....  wish you would have asked me that question,
that one is EASY...

create unique index t_idx on t( decode(status,0,name,null) );

done.  there you go.  just like in:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:8980833603650

you see - entirely null entries are not made into a b*tree index, hence, by
decoding on status, we in effect only index NAME when status = 0, therefore NAME
will be unique when status=0.

Rating: 5
April 03, 2003
Reviewer: Dave from Colorado
Although the explanation of the serializable transaction tends to be a little
complex, the implementation of it within Oracl is probably a doddle, I would
think.

In a non-serializable transaction each query looks for data consistant for the
SCN when the individual query started. In a serializable transaction all queries
look for data consistant for the SCN when the FIRST query of the transaction
started, no?

Followup:
yes

Rating: 5
April 03, 2003
Reviewer: A reader from Ca, USA
Can you give us a business scenario where serializable transaction would be
useful?
thanks

Followup:
when you need to run the benchmarks provided at www.tpc.org.

when you need a point in time view of the database (a report with dozens of
queries -- all of which must be consistent with regards to eachother)

Rating: 4
Point-in-time view April 04, 2003
Reviewer: Glenn A. Santa Cruz from Mobile, AL USA
With the new "Flashback query" in 9i, and the ability to flashback based on an
SCN, couldn't you accomplish the same thing for a given set of queries (provided
that each query were modified to include the "as of SCN" predicate)?

I can see the benefit of setting a serializable isolation level in that no
queries would need to be modified (and these individual queries could be reused
in other code that did not *need* flashback), but are there any performance
considerations between using a serialized isolation level versus a flashback ?

Followup:
not really -- you cannot update "as of"....

another side effect of serializable is that you cannot update data that was
updated by someone else during your transaction.  flashback won't provide that
protection

Rating: 5
COM+ and serializable transactions May 27, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom
We have a need where within a transaction, we need to update data in multiple
physical databases. The design team wants to use COM+ as only COM+ supports
connection to multiple databases (in Microsoft technology world). They also told
me that COM+ creates only serializable transactions. Since we do not have any
real business need for creating serializable transactions, is there a
workaround? Are serializable transactions less efficient in performance? My
thought would be that even though the transaction is serializable, the SQL
statements that make up the transaction would have the same execution
plan/statistics.
Thanks

Followup:
why would you not just use dblinks?  doing "database things" outside of the
database is just "not the right idea"

Rating: 5
May 28, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
My initial reaction was the same. Use dblink from one database to other and let
Oracle manage the transactions. That's what it is designed for. But the design
team doesn't want to do this. My other thought was to put a
execute immediate 'set transaction isolation level read committed'; in every
procedure. Not sure if it will work though...
Thanks

Followup:
why do they call themselves a design team?  maybe "redesign" or "as hard as we
can make it design".

set transaction statements like that must be the first statment in a
transaction, it'll be a really bad idea to stick it in each procedure.

Rating: 5
Do we know the reason... May 28, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Do you know (or rather, did they tell you) the convincing argument from your
"design" team as to why using DB-Links is such a bad idea?
Did you, and your design team, elaborate and discuss about pros and cons of
using/not using db-links?
I wounder what is it that is holding them from saying NO to db-links?
Lack of knowledge, maybe...
If that is the case, should they be part of the "design team"???

Followup:
that is actually a good point and shame on me really.

there could be a valid reason for using an external TPM to coordinate Oracle
resources -- there could be non-oracle data sources not covered by a gateway for
example.

there could be a valid reason.

Rating: 5
serializable and discrete transactions June 17, 2003
Reviewer: A reader from MN, USA
Tom,
  In a serilizable transaction the SCN is frozen at the first statement of the
transaction so that we get the same results till we commit in that transaction.
Also these same results are recreated from the rollback segments.. i wish to
know what happens when a discrete transaction makes changes to data which is
being queried by a serializable transaction.. a discrete transaction does not
create any undo so the serializable transaction would have no way to know what
the data would be.. however i m unable to reproduce this scenario.. i m using
the dbms_transaction.begin_discrete_transaction before i update a row in one
session and then try to query the same row from another session which has been
set in a islolation_level = serializable..
is the behavior i m expecting correct.. or have i completely misunderstood the
concept of serializable transactions..
hope to hear from u

Followup:
discrete transactions are deprecated and require the setting of an "_" parameter

discrete transactions "don't work" without it.

Rating: 5
is the behviour true for older versions .. 7. and before?? June 17, 2003
Reviewer: A reader

Followup:
well, serializable was not added until 7.3.3 so it was moot "before"

you had to enable discrete transactions in the init.ora then.  you could have
gotten:

ERROR:
ORA-08176: consistent read failure; rollback data not available

but it would not be assured, the blocks you need might be in the buffer cache -
we might not need the UNDO, there might be a read consistent block sitting there
for you already.

Rating: 4
isolation level parameter ? June 18, 2003
Reviewer: Yves Debizet from Grenoble France
Tom,

I think ALTER SESSION SET ISOLATION LEVEL = SERIALIZABLE very useful (specially
when doing reporting).

Question 1: why isn't there any init.ora parameter about isolation level which
would allow to change Oracle default isolation level value (which is READ
COMMITTED) ?

Question 2: in a moderately transactional application (few updates, deletes,
inserts) is it expansive to run serializable sessions (with an ALTER SESSION SET
ISOLATION LEVEL = SERIALIZABLE inside an ON LOGON trigger) ?

Thank you

Followup:
q1) you can certainly file an enhancement request regarding that.

q2) no

Rating: 4
Perhaps a better example would be: June 18, 2003
Reviewer: Matt from a tiny, lonely cubicle in an office building full of them
Perhaps a better example for book would have been (or NEXT book which I am very
much looking forward to):

Time    Session 1 Executes   Session 2 Executes
0:00    Alter session set
          isolation_level=serializable;
0:01                         Alter session set
                               isolation_level=serializable;
0:02    Insert into A select
          count(*) from B;
0:03                         Insert into B select
                                   count(*) from A;
0:04                         Commit;
0:05    Insert into A select
          count(*) from B;
0:06    Select * from A;

         A
----------
         0
         0

Thus you can show differences between read commited, serializable and the (ugh)
read uncommited.

June 18, 2003 Christo -- Thanks for the question regarding "Index row overheat", version 8.1.6.1.0
You Asked
Tom,

When I analyze-validate a index, and divide the number of LF_ROWS_LEN to LF_ROWS
I have 12 bytes of overheat. I would expect only 6 bytes for the rowid, where
are the other 6 bytes comming from? Am I doing to wrong calculations?

SQL> analyze index IDX_TBLACCOUNT validate structure;

Index analyzed

SQL> select * from index_stats;

HEIGHT                 3
BLOCKS                 18056
NAME                   IDX_TBLACCOUNT
PARTITION_NAME
LF_ROWS                5796880
LF_BLKS                16868
LF_ROWS_LEN            75359440
LF_BLK_LEN             7996
BR_ROWS                16867
BR_BLKS                57
BR_ROWS_LEN            237396
BR_BLK_LEN             8028
DEL_LF_ROWS            7655
DEL_LF_ROWS_LEN        99515
DISTINCT_KEYS          20
MOST_REPEATED_KEY      4228703
BTREE_SPACE            135334124
USED_SPACE             75596836
PCT_USED               56
ROWS_PER_KEY           289844
BLKS_GETS_PER_ACCESS   144925.5
PRE_ROWS               0
PRE_ROWS_LEN           0

LF_ROWS_LEN/LF_ROWS = 75359440/5796880 = 13

The lenght of the column is 1 byte (varchar2(1)).

SQL> select * from user_ind_columns where index_name = 'IDX_TBLACCOUNT';

INDEX_NAME       IDX_TBLACCOUNT
TABLE_NAME       TBLACCOUNT
COLUMN_NAME      AC_STATUS
COLUMN_POSITION  1
COLUMN_LENGTH    1
DESCEND          ASC

SQL> select * from user_tab_columns where table_name='TBLACCOUNT' and
column_name='AC_STATUS';

TABLE_NAME           TBLACCOUNT
COLUMN_NAME          AC_STATUS
DATA_TYPE            VARCHAR2
DATA_TYPE_MOD
DATA_TYPE_OWNER
DATA_LENGTH          1
DATA_PRECISION
DATA_SCALE
NULLABLE             Y
COLUMN_ID            67
DEFAULT_LENGTH
DATA_DEFAULT
NUM_DISTINCT         17
LOW_VALUE            30
HIGH_VALUE           74
DENSITY              0.05882352
NUM_NULLS            0
NUM_BUCKETS          1
LAST_ANALYZED        6/14/2003
SAMPLE_SIZE          235526
CHARACTER_SET_NAME   CHAR_CS
CHAR_COL_DECL_LENGTH 1
GLOBAL_STATS         NO
USER_STATS           NO
AVG_COL_LEN          1

Analyzing another index, on the same table again, 12 bytes of overheat:

SQL> analyze index idx_tblaccount_stssch validate structure;

Index analyzed

SQL> select * from index_stats;

HEIGHT               3
BLOCKS               18845
NAME                 IDX_TBLACCOUNT_STSSCH
PARTITION_NAME
LF_ROWS              5794493
LF_BLKS              17096
LF_ROWS_LEN          121620457
LF_BLK_LEN           7996
BR_ROWS              17095
BR_BLKS              53
BR_ROWS_LEN          390029
BR_BLK_LEN           8028
DEL_LF_ROWS          5268
DEL_LF_ROWS_LEN      110621
DISTINCT_KEYS        22291
MOST_REPEATED_KEY    553444
BTREE_SPACE          137125100
USED_SPACE           122010486
PCT_USED             89
ROWS_PER_KEY         259.94764703
BLKS_GETS_PER_ACCESS 133.473823516217
PRE_ROWS             0
PRE_ROWS_LEN         0

LF_ROWS_LEN/LF_ROWS = 121620457/5794493 = 20.99 (we have 12880 rows with NULL
for the 1st column)

Which is again DATE (7 bytes) + separator? - please comment (1 byte) +
varchar2(1) (1 byte) = 9 bytes.

12 bytes overheat.

SQL> select * from user_tab_columns where table_name='TBLACCOUNT' and
column_name='AC_SCHEDULETIME';

TABLE_NAME           TBLACCOUNT
COLUMN_NAME          AC_SCHEDULETIME
DATA_TYPE            DATE
DATA_TYPE_MOD
DATA_TYPE_OWNER
DATA_LENGTH          7
DATA_PRECISION
DATA_SCALE
NULLABLE             Y
COLUMN_ID            72
DEFAULT_LENGTH
DATA_DEFAULT
NUM_DISTINCT         4941
LOW_VALUE            78640714110101
HIGH_VALUE           C7C70C1F0E0201
DENSITY              0.00020238
NUM_NULLS            12880
NUM_BUCKETS          1
LAST_ANALYZED        6/14/2003
SAMPLE_SIZE          235526
CHARACTER_SET_NAME
CHAR_COL_DECL_LENGTH
GLOBAL_STATS         NO
USER_STATS           NO
AVG_COL_LEN          7

Thanks
and we said...
it is 4/5 bytes.

A varchar2 has a leading byte length, so a varchar2(1) takes at least 2 bytes.
There are null indicators to deal with.
The rowid is stored as a "string" as well (leading bytes).

So, that varchar2(1) has

1 "length" byte, 1 "data" byte, 1 "rowid length" byte, 6 bytes of rowid data = 9
bytes.

In my testing, I'm getting 12 bytes/ row on 9203 running on RedHat Linux on an
intel box.  It is very easy to measure this:

ops$tkyte@ORA920> create table t ( x varchar2(20) );

Table created.

ops$tkyte@ORA920> create index t_idx on t(x);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2      for i in 1 .. 20
  3      loop
  4          execute immediate 'truncate table t';
  5
  6          insert into t
  7          select rpad(chr(rownum),i,chr(rownum))
  8            from all_objects
  9           where rownum <= 255;
 10
 11          execute immediate 'analyze index t_idx validate structure';
 12          for x in ( select lf_rows_len, lf_rows, lf_rows_len/lf_rows
bytes_per_entry
 13                       from index_stats )
 14          loop
 15              dbms_output.put_line
 16              ( 'Width = ' || i || ' lf_rows_len = ' || x.lf_rows_len ||
 17                ' bytes/entry = ' || x.bytes_per_entry );
 18          end loop;
 19      end loop;
 20  end;
 21  /
Width = 1 lf_rows_len = 3315 bytes/entry = 13
Width = 2 lf_rows_len = 3570 bytes/entry = 14
Width = 3 lf_rows_len = 3825 bytes/entry = 15
Width = 4 lf_rows_len = 4080 bytes/entry = 16
Width = 5 lf_rows_len = 4335 bytes/entry = 17
Width = 6 lf_rows_len = 4590 bytes/entry = 18
Width = 7 lf_rows_len = 4845 bytes/entry = 19
Width = 8 lf_rows_len = 5100 bytes/entry = 20
Width = 9 lf_rows_len = 5355 bytes/entry = 21
Width = 10 lf_rows_len = 5610 bytes/entry = 22
Width = 11 lf_rows_len = 5865 bytes/entry = 23
Width = 12 lf_rows_len = 6120 bytes/entry = 24
Width = 13 lf_rows_len = 6375 bytes/entry = 25
Width = 14 lf_rows_len = 6630 bytes/entry = 26
Width = 15 lf_rows_len = 6885 bytes/entry = 27
Width = 16 lf_rows_len = 7140 bytes/entry = 28
Width = 17 lf_rows_len = 7395 bytes/entry = 29
Width = 18 lf_rows_len = 7650 bytes/entry = 30
Width = 19 lf_rows_len = 7905 bytes/entry = 31
Width = 20 lf_rows_len = 8160 bytes/entry = 32

PL/SQL procedure successfully completed.

Now, at a width of 20, we had:

length    1+
data     20+
length    1+
data      6
         --
      32-28 = 4

each row had 4 bytes "more", further, if we added a big column

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> drop table t;

Table dropped.

ops$tkyte@ORA920> create table t ( x varchar2(20), y char(100) default 'x' );

Table created.

ops$tkyte@ORA920> create index t_idx on t(x,y);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2      for i in 1 .. 20
  3      loop
  4          execute immediate 'truncate table t';
  5
  6          insert into t (x)
  7          select rpad(chr(rownum),i,chr(rownum))
  8            from all_objects
  9           where rownum <= 255;
 10
 11          execute immediate 'analyze index t_idx validate structure';
 12          for x in ( select lf_rows_len, lf_rows, lf_rows_len/lf_rows
bytes_per_entry
 13                       from index_stats )
 14          loop
 15              dbms_output.put_line
 16              ( 'Width = ' || i || ' lf_rows_len = ' || x.lf_rows_len ||
 17                ' bytes/entry = ' || x.bytes_per_entry );
 18          end loop;
 19      end loop;
 20  end;
 21  /
Width = 1 lf_rows_len = 29070 bytes/entry = 114
Width = 2 lf_rows_len = 29325 bytes/entry = 115
Width = 3 lf_rows_len = 29580 bytes/entry = 116
Width = 4 lf_rows_len = 29835 bytes/entry = 117
Width = 5 lf_rows_len = 30090 bytes/entry = 118
Width = 6 lf_rows_len = 30345 bytes/entry = 119
Width = 7 lf_rows_len = 30600 bytes/entry = 120
Width = 8 lf_rows_len = 30855 bytes/entry = 121
Width = 9 lf_rows_len = 31110 bytes/entry = 122
Width = 10 lf_rows_len = 31365 bytes/entry = 123
Width = 11 lf_rows_len = 31620 bytes/entry = 124
Width = 12 lf_rows_len = 31875 bytes/entry = 125
Width = 13 lf_rows_len = 32130 bytes/entry = 126
Width = 14 lf_rows_len = 32385 bytes/entry = 127
Width = 15 lf_rows_len = 32640 bytes/entry = 128
Width = 16 lf_rows_len = 32895 bytes/entry = 129
Width = 17 lf_rows_len = 33150 bytes/entry = 130
Width = 18 lf_rows_len = 33405 bytes/entry = 131
Width = 19 lf_rows_len = 33660 bytes/entry = 132
Width = 20 lf_rows_len = 33915 bytes/entry = 133

PL/SQL procedure successfully completed.

we expected

length    1
data     20
length    1  (yes, CHAR is just a VARCHAR2 that is blank padded, it uses length)
data    100
length    1
data      6
        ---
  133 - 129 = 4 bytes more
Review & Followup
Rating: 5
Good. June 15, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
Interesting. I wonder why the rowid needs a lenght, is it possible to have
variable lenght rowids ?

So where are those 4 bytes comming from ? Why are they needed?

Followup:
they just "are" (needed)

Rating: 5
Reader June 18, 2003
Reviewer: A reader
Here is a block dump:

row#0[536] flag: -----, lock: 0
col 0; len 20; (20):  01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
01
col 1; len 6; (6):  02 40 03 8d 00 00
row#1[566] flag: -----, lock: 0
col 0; len 20; (20):  02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
02
col 1; len 6; (6):  02 40 03 8d 00 01
row#2[596] flag: -----, lock: 0
col 0; len 20; (20):  03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03
03
col 1; len 6; (6):  02 40 03 8d 00 02
row#3[626] flag: -----, lock: 0
col 0; len 20; (20):  04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04
04
col 1; len 6; (6):  02 40 03 8d 00 03

According to this every row takes 30 bytes
the rows are started at 536, 566, 596, 626 ....

Could you explain the extra 2 bytes in your results

Thanks

Followup:
I don't do block dump interpretations.  You are seeing a "report" of a block,
not the block itself.  see flags, row#, etc etc etc.  Chalk it up to "data
oracle requires in order to manage your data"

it takes what it takes, plain and simple.

June 19, 2003 Kenneth -- Thanks for the question regarding "commit in a trigger", version 7.2.3
You Asked
i have trigger on a table A

in which i have statements like

if updating then
 update b
set col_var=:new.col_var,
col_var2=:new.col_var2
where prime_var=:new.prime_var

end if

similarly there are inserting and deleteing  predicates where
insertion and updation are
done on table B

The problem is i need to commit this transaction
but in a trigger you can't write commit/rollback

Is there any work around.

Thanks for all my previous queries answered.
Kenneth
and we said...
You cannot commit in a trigger (until Oracle8i release 8.1 with
autonomous transactions -- these will NOT commit the "parent"
statement but only allow you to create a small subtransaction
that is standalone and independently commited.  See
http://osi.oracle.com/~tkyte/autonomous/index.html
for more info on that)

Committing in a trigger is not allowed because a trigger is part

of a larger transaction.  Say you could have a ROW trigger that
could commit and you execute something like:

   update  T set x = x+1;

Now, that update will update every single row (or not) as an
atomic operation.  If we allowed you to commit in the row
trigger -- then you might commit HALF of the update, get an
error on the other HALF and end up with a HALF updated table.
That just goes against the entire concept of an atomic
transaction.  Identical cases arise for all 4 trigger types --
where the transactional consistency of the database would be
totally destroyed if you committed.

see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:482821541531
for more information on that subject.

If you have a specific requirement that you believe necessitates
a commit in the trigger -- I can suggest to you to post that
requirement (your desired end result -- what you need to do) and
perhaps I can offer ways to get there...
Review & Followup
Rating: 4
Autonomous Transactions November 26, 2001
Reviewer: Varma kalidindi from Melbourne,Australia
Your review has helped me in fixing the problem that i had when -- commiting in
a procedure which is called by a trigger.

Thanks for that.

Rating: 4
handle exceptions in triggers June 06, 2003
Reviewer: kumar from India
Tom,

I am not sure whether this is clear to you.
I have this particular situation:

We have a before update trigger on col1 of table 1.
This trigger calls a procedure.
The procedure inserts into another table  table2 ;
Now when there is no error, the trigger works fine. But when there is some
exception in the procedure called by the trigger ( no data found ), i want to
log the error in a log table.
I do not handle the exception in the procedure. I try to handle it in trigger by
making it an autonomous transaction . But its giving "autonomous transaction
detected " error. Also I want the initial update to fail when the procedure
throws the exception and the error to propagate to the front end application.

I have the code sample below :

CREATE OR REPLACE TRIGGER TRIGGER1
BEFORE UPDATE OF FLAG1 ON
TABLE1
REFERENCING OLD AS OLD NEW AS NEW
FOR EACH ROW
declare
pragma   autonomous_transaction;
errormsg varchar2(500);
BEGIN
PROC1_SP(:new.COL1,:new.COL2 ,:new.COL3 ,..);
EXCEPTION
WHEN OTHERS THEN
errormsg:=SQLERRM;
INSERT INTO MY_LOG(run_date,ERROR_MSG) VALUES ( SYSDATE,errormsg);
COMMIT;
RAISE;
END;
Can you suggest me some solution ?

Followup:
UGGG, what is up with that autonomous transaction?

why do you PURPOSELY want to make data integrity "unachievable".  do you know
what you've done?

The trigger should be:

for each row
begin
   proc1_sp( .... );
exception
   when others then
        CALL_AN_AUTONOMOUS_PROCEDURE_THAT_LOGS_MESSAGES( sqlerrm );
        RAISE;
end;

if proc1_sp has commits or roll backs in it -- proc1_sp has a BUG in the code
that needs to be fixed.

Rating: 5
Wonderful June 06, 2003
Reviewer: kumar from India
Tom,

That was really useful for me.I felt really stupid  when I realized that I was
compromising data integrity unknowingly...
Now one more question - Is it possible to return some value from the called
procedure (using out parameter doesn't work ) when an execption occurs ?

Thanks again for your swift response.

Followup:
Not in a reliable fashion (yes everyone -- NOCOPY *might* work but it also might
not work or it might work day1 but not day2 -- it is a hint)...

You would set a package global variable if you wanted the procedure to share
some data to the trigger.

create package state_pkg
as
   variable  datatype;
end;

the procedure can set state_pkg.variable := some_value and the trigger can
reference it.

Rating: 4
one more help... June 17, 2003
Reviewer: kumar from India
Tom,

I have this scenario
Two columns of a table have before update triggers. The triggers call a
procedure.
I handle the exception in the exception block of the trigger and send a mail on
error.
When both columns are modified, and when procedure called in the first trigger
completes without any exception,the second trigger is also fired. But when the
procedure called in the first trigger raises an exception which iam handling ,
the second trigger is not getting fired.

Can you please explain why this is happening ?

Followup:
nope -- but only cause I don't have a concrete test case from you with MINIMAL
CODE (make it small small small)

here is my simple (and small) example showing this working:

ops$tkyte@ORA920LAP> drop table t;

Table dropped.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create table t ( x int, y int );

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create or replace procedure p( p_x in number )
  2  as
  3  begin
  4      if ( mod(p_x,2) = 1 )
  5      then
  6          raise PROGRAM_ERROR;
  7      end if;
  8  end;
  9  /

Procedure created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create or replace trigger t_trigger_1
  2  before insert on t for each row
  3  begin
  4      dbms_output.put_line( 'in trigger 1' );
  5      p(:new.x);
  6  exception
  7      when program_error then
  8          dbms_output.put_line( 'bummer, I got this exception' );
  9  end;
 10  /

Trigger created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create or replace trigger t_trigger_2
  2  after insert on t for each row
  3  begin
  4      dbms_output.put_line( 'in trigger 2' );
  5  end;
  6  /

Trigger created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> insert into t values ( 0, 1 );
in trigger 1
in trigger 2

1 row created.

ops$tkyte@ORA920LAP> insert into t values ( 1, 1 );
in trigger 1
bummer, I got this exception
in trigger 2

1 row created.

Rating: 4
follow up to my previous question June 17, 2003
Reviewer: kumar from India
Tom,

I have put a raise statement in the last line of the exception block in each of
the triggers. Is that the reason why the second trigger is not fired when the
first one raises an exception though they are independent triggers on two
different columns ?

Please help me in resolving this .

Followup:
If you RAISE an exception and do not catch it, the entire STATEMENT that
intiated the work in the first place is undone.

It is "statement level atomicity", either ALL work is done or NO work is done.
no purpose in firing the other trigger as anything it does would be undone.

they are NOT independent -- they are as dependent as you can get.

Rating: 4
got it June 18, 2003
Reviewer: kumar from India
Tom,
Thanks for your swift response and the explanation..
I didnt realize that there is a single statement that was updating both the
columns, since i was new to the application..

Thanks very much..

Rating: 4
June 18, 2003
Reviewer: Keith Jamieson from Arklow, Ireland
 "Is it possible to return some value from the called
procedure (using out parameter doesn't work ) when an execption occurs ? "

It is possible. You need to set the value of the out parameter in the exception
block.  I typically set this to 0 for success, or the Oracle error code vale
(sqlcode) for failure.

Thus if value of out parameter p_error_out is 0, our code executed successfully.

Followup:
then the exception never happened, you caught it, handled it -- hid it.

(return codes are so "1980's" -- you should just let the exception propagate out
to the CLIENT.  Unless you are EXPECTING the exception, you should just let it
go)

June 19, 2003 Venkatesh -- Thanks for the question regarding "How to trap the table name in a system level trigger.", version 8.1.7
You Asked
Hi Tom-

I need to trace the name of the person, table name & timestamp  when
any body creates the object in our production instance(Oracle).

I could trace the username,timestamp. But i do not know, how to trace the object
name. Here is my trigger.

CREATE TRIGGER TRG_AUDIT_OBJECTS AFTER CREATE ON SCHEMA
DECLARE
v_username VARCHAR2(30);
BEGIN
SELECT  USERENV('TERMINAL') INTO v_username FROM DUAL;
INSERT INTO T_AUDIT_OBJECTS VALUES(v_username,SYSDATE);
END;

How do i trace the object name ?. I would really appreciate, if you could
respond for this.

Thanks. Have a good time

Sagaram.
and we said...
See
http://otn.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76939/adg14evt.htm#1000872
it shows how to use the Event Attribute functions to get a wealth of
information.

In this case "ora_dict_obj_name" is what you are looking for.
Review & Followup
Rating: 5
May 17, 2002
Reviewer: A reader

Rating: 4
Thanks Tom.. May 20, 2002
Reviewer: V Sagaram from Houston,TX
We can now track users who create unnecessary objects on the DB.

Rating: 4
How about row level triggers? June 18, 2003
Reviewer: Susan from Watertown, MA
Tom,

Looking for a way to trap and pass table name for our row level auditing
triggers.  Many thanks.

Followup:
the trigger is on the table?  you know by definition what table is going on
don't you?

but see
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76939/adg14evt.htm#1000872
for a list of functions that are available to you in triggers.

Rating: 4
Thanks Tom June 19, 2003
Reviewer: Susan from Watertown, MA
yeah, the problem was that our auditing procedure is passing one of our
applications meta data columns for the tablename instead of the just passing the
tablename as a parameter.  We have a type of form tables were this meta data
column is not the equivalent to the table name.  Not sure why, but they wanted
to keep the code the same but add conditional logic that checks for type of
table, if type where form_id <> tablename then use tablename.  I think the fix,
and logical approach, is to pass the tablename.

June 19, 2003 Guru -- Thanks for the question regarding "Clarifications in SQL Plus", version Oracle 7.3/Oracle 8
You Asked
Sir,

   I have several doubts in SQL Plus. They are as given below

1) How many indexes can be there for a table in Oracle 7 or
Oracle 8 database? Please tell me the logic behind arriving at
that number?

2) How many constraints can be there for a column in Oracle 7 or
Oracle 8 database?

3) If a row in inserted or updated in a table will the associted
indexes be updated immediately or at the next time database is
started?

4) What is the difference between TRUNCATE and DELETE command.
Will the database trigger "before delete or after delete"
trigger written for that table be fired if a truncate command is
executed?

5) What is the maximum date value that can be stored in a
Oracle7 and Oracle8 date column?

6) What is the minimum number of datafiles that must be there in
a Oracle8 database?

Thank You Sir
Guru Prasad M.R.
and we said...
1) there is no limit on the number of indexes other then
practical limitations (eg: every index will slow down an
insertion or deletion, having too many will make these
operations too slow).

2) No limits.

3) Conventional indexes such as b*tree and bitmap indexes are
maintained as part of your transaction (immediately).

4) TRUNCATE is DDL, DELETE is DML.

A truncate simply goes to the data dictionary and releases space
for the table.  It cannot be rolled back.  Truncate will commit
immediately BEFORE and AFTER executing.  Truncate generates no
rollback or redo (other then the rollback/redo needed to protect
the data dictionary updates).  A truncate will not fire any
triggers on the table whatsoever.

A delete will go row by row and delete the data in the table.
Every change it makes will be recorded in the rollback segments
and redo logs.  It can be rolled back, it does not perform any
commits.  A delete will fire any and all delete triggers on the
table.

5) Oracle can store dates in the Julian era, ranging from
January 1, 4712 BCE through December 31, 4712 CE (Common Era).
Unless BCE ('BC' in the format mask) is specifically used, CE
date entries are the default.

The upper limit is actually higher, it'll goto the year 9999:

ops$tkyte@DEV816> insert into t values ( to_date( '01-jan-9999'
) );
1 row created.

but 4712 is the supported end date.

6) 1 datafile -- for the SYSTEM tablespace (which could also be
your temp tablespace and rollback).  A reasonable minimum is 4
-- one for system, temp, rollback and user data.
Review & Followup
Rating: 4
Constraints on a Trigger April 22, 2002
Reviewer: Senthil Kumar Srinivasan from Chennai, India
Tom,
Is there any Constraints on Creating any Trigger ? If so, what are they?

Thanks in Advance

Rating: 4
Release space by truncate June 18, 2003
Reviewer: A reader
Tom,
Does the internal mechanism used by oracle for releasing space by truncate is
same as for dropping the table?

Followup:
no, yes, sort of.

they all can give space back (truncate will keep at least an extent, could keep
all of them).

June 19, 2003 Mark -- Thanks for the question regarding "VPD predicate executes trice", version 9i rel 2
You Asked
Tom,

I have a simple implementation of VPD which inserts a record into a log table
when a select is performed on the table.  I am seeing the log record inserted
twice.

As you can see from my example below my sql predicate does not limit any data in
the table, it just logs and returns a null predicate.

An interesting thing happens if I make my predicate function return a clause
which will make the statement fail, such as return 'invalidcolumnname = 200';
Only 1 log record gets inserted.

Why?

drop   table footab;
create table footab
(test        varchar2(30),
 flag        char)
tablespace users
storage(initial 4k next 4k pctincrease 0)
/

insert into footab values('test1', null);
insert into footab values('test2', 'y');
commit;

drop   table logtab;
create table logtab
(createdate    date)
tablespace users
storage (initial 4k next 2k pctincrease 0)
/

create or replace package tstctx as

procedure setid;

end tstctx;
/

show errors

drop   context tstcontext;
create context tstcontext using tstctx;

create or replace package body tstctx as

procedure setid is

begin
  dbms_session.set_context('tstcontext', 'MYID', uid);
end;

end tstctx;
/

show errors

create or replace package tstsp as

function sel(p_schema in varchar2,
            p_object in varchar2)
 return varchar2;

end tstsp;
/

show errors

create or replace package body tstsp as

function sel(p_schema in varchar2,
             p_object in varchar2)
 return varchar2 as
begin
 insert
   into logtab
 values (sysdate);
 commit;

 return null;
end;

end tstsp;
/

show errors

begin
 begin
  dbms_rls.drop_policy('msw', 'footab', 'tstpolicy');

  exception
   when others then null;
 end;
 dbms_rls.add_policy('msw',
                     'footab',
                     'tstpolicy',
                     'msw',
                     'tstsp.sel',
                     'select',
                     true,
                     true,
                     false);

end;
/

show errors

rem set context
exec tstctx.setid

rem select from the table which has a policy applied
select * from footab
/

*** 2 records are returned

rem check the log table
column dt format a20
select to_char(createdate, 'MM/DD/YYYY HH24:MI:SS') dt
  from logtab
/

****OUTPUT****

11/20/2002 23:21:20
11/20/2002 23:21:20
and we said...
but -- you cannot rely on a VPD function being called AT ALL during a select
(the cursor can and will be CACHED  -- subsequent executes of an already parsed
statement will simply reuse the cursor and NOT call your function)

The capability you are looking for is FGA -- fine grained auditing.  See
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96612/d_fga.htm#998101
So, use fine grained auditing -- it is the appropriate tool for this job (and
you'll get to capture the QUERY as well - very nice)
Review & Followup
Rating: 3
Not sure FG Auditing will do what we want November 21, 2002
Reviewer: Mark Wooldridge from vienna, va
Tom,

Thanks for the quick reply.  FG Auditing will not execute for each row returned.
 We are using a tip from your book to have functions defined with views
utilizing the functions with the view to trap what data was returned from a
select.

We found a work around and there seems to be a bug with VPD.  When VPD gets a
select statement it must execute the predicate function and then dynamically
modify the query.  This modified query contains the table that has the predicate
function applied to it but VPD must know not to reapply the predicate or an
endless loop would occur.  It seems to know not to apply the predicate but it
does execute the predicate function twice.  We solved the problem by putting a
boolean value in the body of the package and if false do the insert and set it
to true else set it back to false.  we have tested it in sql*plus and seems to
work great.  We can capture the action from the predicate and the data that was
selected.

We are planning on using open cursor for loops which you talked about in your
book to make sure we do not have the cached cursor problem.

Thanks

Followup:
Neither does VPD -- it executes only during PARSE time -- not for each row.  It
allows you to control the rows returned to the end user.

What you describe is what I call the chicken and egg -- it in fact does NOT know
to execute the predicate policy again and can easily get into an infinite loop.
See
http://asktom.oracle.com/~tkyte/article2/index.html
and search for chicken.  I describe it there (and how to avoid it)

As for that last paragraph -- you might as well do the auditing RIGHT IN THE
STORED procedure then.  You see -- VPD works as it works, you will have to
change your code in order to get it to behave the way you want.  Meaning -- it
is not a good general purpose solution for you at all.  All it will take is one
application that behaves differently to subvert your security, thats a really
bad thing.  Also -- the open cursor for '.....' is a performance inhibitor.

I would rethink your approach some more before you go down this path.

Rating: 4
Diff. November 21, 2002
Reviewer: Nikunj from Ahmedabad, India
What is diff. between VPD & FGAC ?

Followup:
Marketing came up with one name -- VPD (sounds really cool)
Development came up with another -- FGAC (sounds really technical)

Rating: 4
We are close... November 22, 2002
Reviewer: Mark Wooldridge from vienna, va
Tom,

I really appreciate your insight on this issue.  We wanted to utilized a single
point of control to apply the security policy and log this action.  We could use
the FG auditing but that would require setting up another policy on the tables
and keeping that sin sync with our security mechanism.  By putting the auditing
in the predicate functions we can let the security policy drive the auditing.

I agree with VPD not firing for each row, that is why we are using an excellant
tip from your book using functions and views.

I am not sure I understand or agree with '...All it will take is one
application that behaves differently to subvert your security.'.  The whole
point to VPD is that the application is not in control of security anymore and
cannot bypass security, unless it does not take into consideration how VPD works
(and you said '...VPD works as it works...').

The key is that we need VPD, however it works.  We must understand the tool and
work with it to accomplish our goals.  If cursor caching is an issue, we ensure
we code around this or utilize the new 8.1.7 and up feature and include a
context change whenever we make changes to underlying tables which support our
predicate generation.

There is STILL and interesting fact that the predicate function fires twice for
each execution of the statement! (IMHO - there is a BUG in VPD :) ).

P.S.  Anyhow, without this excellant site and your incredible book,
understanding ORACLE and it's behavior would have been extremely difficult.
From your book about 5.1.5c,  my first project was on an xt8088 with 5.1, 5.1a,
forms 2.3, BI files, sql*report and a bunch of floppies.  I share your passion
for Oracle, wish I had the same depth and breadth of knowledge regarding Oracle.

ALL - BUY TOM's BOOK and read it!

P.S.  Tell Wendy hi for me, saw her a few weeks ago.

Followup:
You yourself said "we will use the open for trick to make it parse every time"

Now what?  All it takes is ONE application to come in and not parse every time
(that would be the good performing application by the way) and your security
goals have been subverted in a trivial fashion.

Fine Grained auditing kicks in each time.

They never said they would call you once, twice or N times and you'll find it
varies from release to release.  There shouldn't be SIDE effects from your
policiy being called more then once.

I am concerned that you are building a solution that relies on undocumented
behaviours -- it is a slippery slope.  I still think that FGA is what you want.

Rating: 5
Interesting.... November 22, 2002
Reviewer: Mark Wooldridge from vienna, va.
Tom,

I Give :).  Once again your insights prove to be the best.

I never took into account that VPD is not supposed to execute the predicate
function just once.  Your concern about undocumented features is valid.  We will
have to look into this.  The good thing is we already are planning coding PL/SQL
to log, we just need to move the calls from VPD to FGA.  It would have been nice
to marry VPD policy with FGA policy and have a central point to drive security
and logging.  Although your comment about side effects of policy affecting
logging is interesting.

The FG Audit stuff does what we want also.  We were just trying to marry the
security and auditing in one place and thought we had a really cool idea
utilizing an existing technology.

If you really want logging to behave, it should not be tied to anything, it
should be completely unbiased to anything other piece of the system.

Maybe this is why FG Auditing was built?

Thanks,  we should be on the right track now with FGA.

Rating: 3
What about??? November 22, 2002
Reviewer: Mark Wooldridge from vienna, va.
Tom,

inserts, updates and deletes.  Does FGA support logging these actions?  We need
FGA for all statements, (sel, ins, upd, del)!

Mark

Followup:
You'll use regular old fashioned auditing for ins/upd/del (or triggers)

Rating: 4
Ok here we go yet another issue with FGAC June 02, 2003
Reviewer: bharath from USA
>>They never said they would call you once, twice or N >>times and you'll find
it varies from release to release.  >>There shouldn't be SIDE effects from your
>>policiy being called more then once.

we are calling a procedure inside function policy to find out current role for
the logged user.Based on your reply it looks like this procedure is going to
execute multiple times .hmmmmmm!

Please clarify this.

Followup:
inside PLSQL, the roles will be disabled.

so you must mean "the default roles assigned to the user"

which is something you should read ONCE, store in package global variables and
save for subsequent calls.

Rating: 5
Ok here we go yet another issue with FGAC June 03, 2003
Reviewer: Bharath from USA
>>inside PLSQL, the roles will be disabled.
>>so you must mean "the default roles assigned to the user"
>>which is something you should read ONCE, store in package >>global variables
and save for subsequent calls.

Roles is not oracle role ,role is something which has been set in
application(e.g Sales Analyst,Accountant) and can be changed by common security
module outside the application.The procedure which i am using inside the
function policy will determine this.SO if the function policy executes multiple
times this procedure will be called multiple times .

Followup:
so the role should be cached in an application is what you mean.

(don't know what you mean by your subject there??)

put the role into the context, that is what they are there for.

Rating: 5
Reason for the subject June 03, 2003
Reviewer: Bharath from USA
>>Ok here we go yet another issue with FGAC
Reason:
Issues with FGAC:
                  1,Cursor cache
                  2,Function which is used in policy   executing multiple times.
i am refering to this one
>>(They never said they would call you once, twice or N >>times and you'll find
it
>>varies from release to release.  There shouldn't be SIDE >>effects from your
policiy being called more then >>once.   )

Followup:
what is the issue with the cursor cache?  It is not any different then if you
did the predicate rewrite yourself outside the database (well it is, it is far
superior then doing it outside the database).

it is as efficient with the cursor cache as can be possible.

Rating: 5
Reason June 03, 2003
Reviewer: Bharath from USA
>>They never said they would call you once, twice or N >>times and you'll find
it varies from release to release.  >>There shouldn't be SIDE effects from your
>>policiy being called more then once.

What do you meant by that.
Reason i am  asking is ,if i refer procedure inside the policy i have to make
sure that procedure get executed only once ,when oracle  executes a sql and
table refered has got a policy.

for(e.g)

Select count(*) from loan_source;

loan_source has got policy something
if user is accountant then he can see all the data

if user is salesaccountant then he can see only see cmb_loan = 'Y'

so we have created a procedure which will give me back  the identification of
the user.we are calling that procedure inside function which is refered in the
policy definition.

so if i execute this
Select count(*) from loan_source;

policy function gets excuted multiple times and procedure gets executed
multiple times ,is that true.if it's true how we can avoid that.

Followup:
it is outside of your control.  That is why a predicate policy function should
just do minimal work (no lookups, no queries -- just return the predicate).  Use
the application context to cache that data which is expensive to retrieve.

Rating: 3
SYS_CONTEXT is slower than package variables June 04, 2003
Reviewer: Marc Blum from Aachen, Germany
Tom Kyte wrote: Use the application context to cache that data which is
expensive to retrieve.

We designed our VPD with regard of minimized additional SQL. Only the logon
trigger does some lookups. The predicate functions avoids any SQL. But in some
heavy data processing and computing procedures we found out, that using
SYS_CONTEXT to retrieve the session static values, we got a real slowdown. By
switching to package global variables to cache that values we gained 30%
performance.

In other applications we made the same experience, that SYS_CONTEXT is
significantly slower than packaged variables.

Followup:
use the right tools in the right places -- sys_context involves a call into the
kernel.  globals don't.  in your vpd functions, use the context, in normal plsql
-- use globals.

Rating: 2
Clarification June 04, 2003
Reviewer: Marc Blum from Aachen, Germany
sorry for not being specific enough.

We have a Package, with does awful much work. The data is retrieved from tables
which are completely FGACed. Inside the security functions we evaluated
SYS_CONTEXTed values. By switching to packaged variables, we gained 30%
performance.

Rating: 4
What is the data dictionary view for VPD June 19, 2003
Reviewer: A reader
Tom,

What is the data dictionary view to identify what policies exist for an object?
V$VPD gives only the current one in libraru cache.

Followup:
*_POLICIES

* = all, dba, user

June 19, 2003 Jeffrey -- Thanks for the question regarding "Printing a report to file in .pdf format", version Report Builder 6.0.8.8.3
You Asked
Hi Tom,

     I really do not have a lot of experience in regards to
using adobe products so when I got an assignment of making a
report and saving the output in .pdf format, I was totally
clueless.  I would appreciate it if you can give me some help in
this.

Jeff
and we said...
Use the desformat parameter to reports:

DESFORMAT (RWBLD60)

Description In bit-mapped environments, DESFORMAT specifies the
printer driver to be used when DESTYPE is FILE. In
character-mode environments, it specifies the characteristics of
the
printer named in DESNAME.

Syntax
[DESFORMAT=]desformat

Values

Any valid destination format not to exceed 1K in length.
Examples of valid values for this keyword are hpl, hplwide, dec,
decwide, decland, dec180, dflt, wide, etc. Ask your System
Administrator for a list of valid destination formats.

PDF Means that the report output will be sent to a file that can
be read by a PDF viewer. PDF output is based upon the
currently configured printer for your system. The drivers
for the currently selected printer is used to produce the
output; you must have a printer configured for the machine
on which you are running the report.

HTML Means that the report output will be sent to a file that
can be read by an HTML 3.0 compliant browser (e.g., Netscape
2.2).

HTMLCSS Means that the report output sent to a file will include
stylesheet extensions that can be read by an HTML 3.0 compliant
browser that supports cascading style sheets.

HTMLCSSIE Means that the report output sent to a file will
include style sheet extensions that can be read by Microsoft
Internet Explorer 3.x.

RTF Means that the report output will be sent to a file that can
be read by standard word processors (such as Microsoft Word).
Note that when you open the file in MS Word, you must
choose View Page Layout to view all the graphics and
objects in your report.

DELIMITED Means that the report output will be sent to a file
that can be read by standard spreadsheet utilities, such as
Microsoft Excel. Note that if you do not choose a delimiter, the
default delimiter is a TAB..
Review & Followup
Rating: 5
pdf reports March 13, 2002
Reviewer: Munz from Reston, USA
Tom:

1. AS a followup to this, is there any way to have PL/SQL create a PDF report
for you and then print the report via link.

I want to print a shipping address label from the database but I do not have
oracle reports running on AIX server.

2.  Would html print work for labels?

3.  would you convert html to pdf format but then user will have to run acrobat
and do that himself.  I amtrying to get this automate so that when a user
creates a shipping transcation it shoots the address to the printer.

4.  You said above that pdf file is created using the drived for your personal
printer.  But how can pdf files get distributed to the web since each user has a
different printer.

THank you,

Followup:
1) sure, if you know how to generate a PDF file that is.   I don't know the
format, but if you can discover it (the format), yes.

2) don't know.

3) don't know.

4) the above refers to the client server reports, its not the web version.  the
web version would download the pdf to the client browser, whence they can go
file/print where ever.

Rating: 3
Another possibility March 13, 2002
Reviewer: Tony from Philadelphia
Jeffrey,

I recently had the same type of request involving dynamic generation of stored
resumes into PDF.  I solved the problem by pulling out the data in XML format
(using the XDK) and using the rendering capabilities within Apache FOP.

You define the page with XSLT and have control over virtually every aspect of
the layout. This may not be the best solution for you, but probably worth
checking out...

xml.apache.org/fop/

Rating: 4
pdf output from Java, using PDFLib March 14, 2002
Reviewer: Harrison Picot from Haymarket, VA USA
Hi:

If you look on the Adobe site, there is a complete (5 meg pdf file) PDFreference
explaining how to create a pdf files that current versions of acrobat can read.
It is not that hard.  An easier way to create pdf files from Java (or other
language, but Java is likely where you will want to retreive your data from the
database, so I mention it) is the PDFLib from Thomas Merz which you can learn
more about at
http://www.pdflib.com/
 The java interface was largely the work of Paul Hazlett who
I mention because when you help out for free, you should get some recognition.

A web page returning a pdf file with data from an Oracle database, seems to me
anyway, an excellent way to save mailing a large detailed bill, and can keep it
on line where you can find it.  PDF, of course, can format data much better than
html. The files you will create will be text files and pl/sql can create them
just as easily as any other method, if you emit boilerplate, the results of your
query, and some more boilerplate.  I enclose an example of a small pdf file that
creates two pages and an index (thumbnails, all the good stuff) in 1.6k.  You
can flesh it out into a two page report easily, and, more to the point, see that
it is not that hard. Because it is all character, it is really compatible with
PL/SQL, Java, C or whatever you want to use.

Cheers.

%PDF-1.3//9
1 0 obj
<< /Type /Catalog
/Pages 6 0 R
/Outlines 2 0 R
>>
endobj//74
2 0 obj
<< /Type /Outlines
/First 3 0 R
/Last 5 0 R
/Count 3
>>
endobj//145
3 0 obj
<< /Title (Mon First Document)
/Parent 2 0 R
/First 4 0 R
/Last 4 0 R
>>
endobj//233
4 0 obj
<<  /Title (Mon Page Une)
/Parent 3 0 R
/Next 5 0 R
/Dest [7 0 R /Fit]
>>
endobj//322
5 0 obj
<<  /Title (Mon Page deux)
/Parent 3 0 R
/Prev 4 0 R
/Dest [9 0 R /Fit]
>>
endobj//412
6 0 obj
<<  /Type /Pages
/Kids [ 7 0 R
9 0 R]
/Count 2
>>
endobj//477
7 0 obj
<<  /Type /Page
/Parent 6 0 R
/MediaBox [ 0 0 612 792]
/Contents 8 0 R
/Resources << /ProcSet [/PDF /Text]
/Font << /Myfont 11 0 R >>
>>
>>
endobj//632
8 0 obj
<< /Length 80 >>
stream
BT
/Myfont 18 Tf
100 600 Td
(This is Page one.) Tj
ET
endstream
endobj//735
9 0 obj
<<  /Type /Page
/Parent 6 0 R
/MediaBox [ 0 0 612 792]
/Contents 10 0 R
/Resources << /ProcSet [/PDF /Text]
/Font << /Myfont 11 0 R >>
>>
>>
endobj//891
10 0 obj
<< /Length 80 >>
stream
BT
/Myfont 18 Tf
100 600 Td
(This is Page Two.) Tj
ET
endstream
endobj//995
11 0 obj
<< /Type /font
/Subtype /Type1
/Name /Myfont
/BaseFont /Arial
/Encoding /MacRomanEncoding
>>
endobj//1104
xref
0 12
0000000000 65535 f
0000000009 00000 n
0000000074 00000 n
0000000145 00000 n
0000000233 00000 n
0000000322 00000 n
0000000412 00000 n
0000000467 00000 n
0000000632 00000 n
0000000735 00000 n
0000000891 00000 n
0000000995 00000 n
trailer
<< /Size 12
/Root 1 0 R
>>
startxref
1094
%%EOF

Rating: 4
Doubt May 14, 2002
Reviewer: Aline from Salvador,Brazil
I've read that when generating a rtf file from Reports we have to configure the
MS Word page setup to see all the content of the report.
But a oracle support technician said to my client that is possible to see all
the content from a rtf file in Word, without have to configure this.
I'ts really possible? I haven't found how to do this.

Rating: 3
I am also in same problem June 09, 2002
Reviewer: Bhari from Asia
Still i am wondering how i can acheive this .My case is i want to run a report
in the BAckend without user interaction between particular intervals .I tried to
put this to AskTom but it looks like full .Since this is a urgent requirement
for me i want to get some help from this or atleast by the viewer's experience
if not Mr.Tom .

Followup:
read up on the reports server -- it has facilities that do exactly this (run
jobs in the background - on scheduled intervals)

Rating: 3
hope this helps! June 10, 2002
Reviewer: Yogeeraj from Mauritius
Hello,

On my HP-UX box, i have one script that is scheduled (cron) to run at specific
time which says:

$IAS_HOME/6iserver/bin/rwcli60 userid=query_user/query_user@mydb
server=Repserver Repo
rt=myreport.rdf destype=file desname=/deploy/myreport-`dat
e +%d%m%y-%H%M`.pdf

I can also do it by scheduling it using the Windows Based Reports Queue Manager
Utility.

Cheers
Yogeeraj

Rating: 4
You might also look into DocBook June 10, 2002
Reviewer: Daryl from Carrollton, TX USA
The original poster didn't indicate whether he was using Oracle Reports.
However, it's pretty easy to get Oracle 8i and 9i to generate XML. If you can
make XML, you can make DocBook input. DocBook is a markup language that makes it
possible to create documents that can ultimately generate final output in HTML
and lots of other formats, including PDF.

Take a look at:
http://www.docbook.org/
http://openjade.sourceforge.net/
http://www.netfolder.com/DSSSL/index.html
http://www.tldp.org/HOWTO/mini/DocBook-Install/

      Rating: 2
Cascading Style
      Sheets  June 19, 2003
Reviewer:  Matt
       from Australia
I searched for Cascading Style Sheets and this thread was all that came up.
Hopefully this is the best place for this question...

What support does PL/SQL Server Pages provide for Cascading Style Sheets?

I need to generate a number of web pages and want to manage the style and
formatting of these whilst using PSP.

THanks in advance,

          Followup:

since CSS is just a tag basically in the HTML document - psp 100% supports it,
just do a view source on this page, you'll see my callout for it.

it is just printing out of HTML

      Rating:
      4
  June 20, 2003
Reviewer:  Yong Wu
       from CA, USA
I used to use htmldoc to convert html to pdf.

http://www.easysw.com/htmldoc/
Here is a way to generage pdf

1) write html file in pl/sql on server
2) pass the file handle to htmldoc to pdf using perl cgi

June 19, 2003 Naga -- Thanks for the question regarding
      "resuing the refcursor before passing it", version 8.1.6

      You Asked
tom,

greetings, thanks for your useful answers.

i have a procedure p1

create or replace procedure p1(o_ref_cur t_ref_cur_typ)
as
begin
  open o_ref_cur for select '1' x,'2' y from dual;
end;

i have another proc that calls the above

create or replace procedure p2(o_ref_cur t_ref_cur_typ)
as
  v_ref_cur t_ref_cur_typ;
  v_x       varchar2(1);
  v_y       varchar2(1);
begin
  p1
  (o_ref_cur => v_ref_cur);
  --
  o_ref_cur := v_ref_cur;
  --
  FETCH v_ref_cur INTO v_x,v_y;
  -- use v_x and v_y in other process
end;

now the procedure p2 doesn't return any values for o_ref_cur, but i see values
in v_x,v_y.

As i assigned the v_ref_cur to o_ref_cur and only after that fetched v_ref_cur
values into variables, i don't understand why o_ref_cur is not returning the
record set.

because i want to return o_ref_cur and use its values in the rest of my code of
procedure p2. which is not happening.

thanks

      and we said...
Maybe I'm not understanding you here -- You don't think that we should "copy"
the result set on that assignment do you?  consider a cursor like a file, if you
assigned a file to a variable and wrote to it with V1 and then wrote to it with
V2, it would be the same file right?  If you read V1, it'll advance V2 as well
-- same with a cursor.

This works:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package types
  2  as
  3          type rc is ref cursor;
  4  end;
  5  /

Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure p1( p_cursor in
out types.rc )
  2  as
  3          l_cursor types.rc;
  4          l_ename  emp.ename%type;
  5  begin
  6          open p_cursor for select ename from emp;
  7
  8          l_cursor := p_cursor;
  9
 10          fetch l_cursor into l_ename;
 11          dbms_output.put_line( 'P1: ' || l_ename );
 12  end;
 13  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure p2
  2  as
  3          l_cursor types.rc;
  4          l_ename  emp.ename%type;
  5  begin
  6          p1( l_cursor );
  7
  8          loop
  9                  fetch l_cursor into l_ename;
 10                  exit when l_cursor%notfound;
 11                  dbms_output.put_line( 'P2: ' || l_ename );
 12          end loop;
 13          close l_cursor;
 14  end;
 15  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec p2
P1: SMITH
P2: ALLEN
P2: WARD
P2: JONES
P2: MARTIN
P2: BLAKE
P2: CLARK
P2: SCOTT
P2: KING
P2: TURNER
P2: ADAMS
P2: JAMES
P2: FORD
P2: MILLER

PL/SQL procedure successfully completed.

Is this not what you are seeing?  This is the expected (and only) behaviour
Review &
      Followup

      Rating: 4
New Schema
        June 12, 2003
Reviewer:  Yogesh  from
      Pune India
In the above example if procedure p2 exists in some different schema .. is there
any way we can access ref cursor created in proc p1

          Followup:

sure, it would just call p1?  no issues there.

      Rating: 4
how to refer
        June 12, 2003
Reviewer:  Yogesh  from
      Pune India
SO I can access it as schemaname.packagename.refcursor type ?  ... one sub
question .. if I don't know the name of the package in the source schema .. can
I still refer it in new schema ?

          Followup:

if you don't know the name of the package, you are "out of luck"

      Rating: 5
REF_CURSOR and
      distributed databases  June 13, 2003
Reviewer:
      AK  from Mumbai, India
Hi Tom,
I am not sure if this question is in the scope of the current thread. As per my
knowledge (correct me if I am wrong) ref_cursor cannot be used across databases
(i.e. across diff database instances).
So if one has to move P2 to another database, how does he/she get the same data
from P1, assuming P1 remains in the original database. Lets also say that data
coming from P1, must be available in P2 in the form of cursor only.
One way I can think of is -
Define a new type 'p1_tab_type' as pl/sql table type in P1 and returns a table
of that type (p1_tab_type)instead of returning a ref_cursor.
Then in P2
1.Create a table 'data_tab' of type P1.p1_tab_type
2.Call P1 to populate data_tab
3.Create cursor 'new_cur' from data_tab
This works, however it does not appear to me as a clean/proper solution because
of dependancy for TYPE. That is, pl/sql tables in P1 and P2 must be of same
TYPE.

Wondering if this issue can be addressed in a better way?
To make a long story short, what are the ways available to get non-scalar data
(like cursor) from a procedure in another database?
I can send you example/code if I above problem statement is not clear enough.
Thanks.

          Followup:

a ref cursor cannot be passed over a dblink.

a ref cursor can access remote data.

so, if one moves P2 to another database, one will not be retrieving a ref cursor
from P2 unless one is connected to that other database.

You would have to send plsql tables back and forth and at that point you would
be asking yourself "why", why don't i expose this data as a view instead of a
ref cursor.

Use a view.

      Rating: 5
View would not
      work in my case...  June 17, 2003
Reviewer:
      AK  from Mumbai, India
Thanks Tom.
Your reply arrived way earlier than I expected and I appreciate it.
Database views, as you suggested, might not be a good choice in my case.
Let me provide you with the application context in order to explain why.
We have suite of related application that is based on 4 different database
schemas.
One of the schema is a central repository and remaining 3 are sort of add-on of
satellite repositories. The application architecture is n-tier. The database
tier interacts with the next one using JDBC. The only way application can access
database is via packaged stored procedures. So stored procedure signatures must
remain intact as they form contract between the database and application. Most
of the core logic is implemented in those stored procedures. There exist more
than one application talking one or more databases.
Earlier the scheme was to deploy all databases (schemas) in one Oracle instance
(8.1.7).
Internally all databases accesses each other&#8217;s schema objects synonyms
(created for fully qualified schema object names).
Now it is required to deploy each schema in a separate Oracle instance (8.1.7)
without having any impact (i.e. code change including JDBC layer) on any
application.
So you are not allowed to modify signatures of interfacing stored procedures.
Given these constraints it was done rather quite easily. All we had to do was
recreate synonyms using database links.
However this created following problem.
There is a stored procedure in main_db called get_monthly_stats, which returns
ref_cursor. This procedure contains some core business logic (hence must not be
duplicated elsewhere).
Another stored procedure called get_full_analysis from a database called sub_db,
calls get_monthly_stats (from main_db) and returns the ref_cursor (returned by
get_monthly_stats) to an application (say report_app) as it is along with
additional data elements. Note that report_app is not allowed to access main_db
directly.
All this used to work fine earlier (in single database scheme).
However, now (distributed database scheme) ref_cursor is a showstopper.
Hence my earlier question to you.
I do not want to rewrite or implement get_monthly_stats in sub_db as it would
lead to code duplication resulting in maintenance related and other problems.
So I have written a wrapper procedure called get_monthly_stats_as tab in main_db
which calls get_monthly_stats and creates pl/sql table from ref_cursor and
return it.
Then I have changed implementation of get_full_analysis to call
get_monthly_stats_as_tab (instead of get_monthly_stats) from main_db, create a
new ref_cursor from returned pl/sql table and return it to the calling
application.

Please tell me:
1.    If approach taken by me was correct?
2.    Could I have done it in a better way given the described constrains?
3.    I do not use views to access remote objects or objects from other&#8217;s
schema. Rather, I like to use synonym. Any issues/pros/cons with that?
4.    If I use views instead of synonyms, will it affect performance? If so,
how?

Thanks for reading my long winding problem description.
P.S. - I like and follow your policy of doing &#8216;most possible work&#8217;
in one query. Same way provide max in information in one shot :-)

          Followup:

if the question is "will it work" -- sure, many things "will work"

if the question is "will it work well", I doubt it, sounds as slow as molasses
in feburary in Canada.

I would not do it that way personally.  But if it works fast enough for you and
scales well enough for you -- go for it.

      Rating: 2
Want to know
      the better way!  June 17, 2003
Reviewer:  AK
       from Mumbai, India
Tom,
I did not test is for performance yet. You are right "many things works" so will
it.
However, I doubt if it will perform well and scale.
And that is why I am seeking your advice.
I want to know the better way to handle it.
What do you mean by "I would not do it that way personally"?
What are you referring to? The architecture or use of synonym or use of ref
cursor in stored procedure signature or work-around implemented using pl/sql
table?
I am confused.
Please explain what did you find as inappropriate and the correct way to do the
same.
Now is the right time for me to correct mistakes if any.
Thanks in advance for your time.

          Followup:

I would not use this architecture.
I would not run more then one database.
I would not do it this way.

that is what I mean.

You are making what is a very easy thing -- very very hard.

the simple solutions are generally the correct ones.

      Rating: 3
A few more
      questions  June 18, 2003
Reviewer:  AK
       from Mumbai, India
Thanks for the clarification.
I did not architect this application. It is quite complex and huge.
What I described as architecture is what appears to me from a perspective of
developer/programmer.
May be I failed to describe it properly or may be it is not designed properly.

For my knowledge, please answer following questions:

Question # 1: Is best way to expose database consumer applications using stored
procedures (packaged) only?

Question # 2: You suggested making use of views in your first reply to review.
What I understood from that is if I want to refer/use a remote object say
employee table from scott&#8217;s schema then I should create a view as for
scott.employee and use it.
If my understanding is correct then is it the same if I create a synonym called
employee for scott.employee? Any pros/cons?
If my understanding is not correct then what did you mean? An example would
help.

Question # 3: In distributed database environment, how to pass non-scalar data
like cursor or collection from one database to another?

Question # 4: Do you think it is a better to rewrite procedure get_monthly_stats
in sub_db instead of calling get_monthly_stats from main_db and accept resulting
code/logic duplication as a trade off?

Question # 5: If answer to question # 4 is yes then what if someone changes
get_monthly_stats in main_db and forgets to make the same change in
get_monthly_stats in sub_db?

Question # 6: If answer to question # 4 is no, then what should I do given I am
not allowed to make architectural changes. As a developer, I am supposed to
provide a solution that will not cause any code change in applications.

Question # 7: Is earlier architecture (using single database) inappropriate? Or
earlier was correct, however splitting database in multiple databases (i.e.
later one) was inappropriate? Or the whole approach (i.e. JSP &#8211;> Java (No
EJB) &#8211;> Stored procedures &#8211;> database) was inappropriate?

Question # 8: Would I be able to get answer to question # 7 in your new book on
design? If no, please refer some good books / links on application design and
database design.

Question # 9: When will your new book on design be published?

Please try to answer all 9 questions.

Thanks for your feedback.

          Followup:

1) views are commonly used.  ad-hoc query tools understand views.  tools
understand views.  Not everything will work with a ref cursor.  It is good to be
flexible.

2) just use views, you have no need of a synonym.

3) ref cursors, as discussed and mentioned a couple of times, do not WORK in
this environment.  I do not pass non-scalar data about if I don't have to -- but
you can use plsql table types if you really wanted to. (but dont)

4) if you cannot use it as it is, you need to rewrite so you CAN.

5) change both to use the same VIEW?  I don't know, you'll have to work this
out.

6) good luck with that.

7) the earlier architecture is the only sensible one if you ask me.

8) there are tons out there.  I touch on it throughout the book, but there are
lots of books

9) august 2003

      Rating: 5
Will WROX
      publish it?  June 19, 2003
Reviewer:  AK

Hi Tom,
Thanks a lot for knowledge sharing.
I am little confused about making use of views.

Question # 1: In your replies did you refer to regular database 'view' or
'materialized view' when advised to make use of views?

Question # 2: Book seller told me (when purchased your book 'oracle one-on-one'
last week) that wrox is gonna close their shop soon. Wondering, who is the
publisher of your new book on desing?

Many thanks.

          Followup:

just regular views which work over dblinks, unlike a ref cursor

Oracle Press is doing the next book.

      Rating: 5
Ref_cursor from
      dynamic query...  June 20, 2003
Reviewer:
      AK  from Mumbai, India
Thanks a lot Tom.
I appreciate your feedback, time and patience.
In one of the stored procedure that used to return ref_cursor from a static
query, I created a view for that query and returned ref_cursor from that view
and now its moving so fast!! Thats really cool.
The only issue is convincing DBA to create so many views (there are so many
procedures that returns ref_cursor). I think its a compromise worth doing and
hope DBA will agree once I show him my test script using view.
Now the only question that I have is:

Question # 1: I can replace static queries returning ref_cursor with view to
return the same ref_cursor. However, in many stored procedures ref_cursors are
being returned from dynamic queries (using bind variables of course). I think it
not possible to create view for dynamic query, but I might be wrong. So how do I
get around with this situation?

Question # 2: Is it good idea to return ref_cursor from dynamic query in
non-distributed database environment? Or there is a better way to handle such
situation?

Question # 3: In distributed database environment if one has to pass non-scalar
data from one db to another, then we can use view if data is coming from a
static query. What if data is coming from a dynamic query?

Question # 4: If at all it possible to create view for dynamic query, please
provide example as to how to create it and query it.

Thanks again.

June 19, 2003 Sanjay -- Thanks for the question regarding
      "Application security versus Database security", version 8.1.7

      You Asked
Tom,
When does it make sense to have database level security versus
application level security ? Most ERP systems, which handle
sensitive data, have application level security using a custom
set of tables. Why don't we use database-level security,
preferring to create a security layer instead ? Does (or can)
the same logic apply to Portals ? In my case, the Portal will
serve as the front-end for a DSS (TB size, hundreds of users)
and the users can have quite a complex set of information that
they may or may not see. The same users will also use the Portal
for knowledge sharing. My gut feeling is to build a security
layer, but I would like a second opinion.
Thanks,
Sanjay
PS: Pls. excuse the inane question

      and we said...
Not an inane question, very reasonable.  Warning: opinions
ahead..

Ok, let's start with the idea of building a security layer in
the application itself.  That means to get at the data, you must
go through the application.  That means no or limited ad-hoc
access.  That means you can do less with the data then you
should be able to.

Now, build it in the database, using something like fine grained
access control.  Now, anything that can connect to the database
can use the data.

To see how useful this paradigm might be -- go back 5 or 6
years.  It is 1995, the web is just about to catch on.  client
server rules.  You put all of your application security logic
into your client application.  Now the internet explodes.
Short of ripping that logic out of the client and recoding it in
whatever environment you use for the web -- you are out of luck.
 If the security had been in the database all along -- you are
ready to roll.

Roll forward to 4 years from now.  what whiz-bang technology
will be there?  many say wireless.  Ok, do you want to code your
security logic all over again to run on a telephone -- or maybe
if it is already in the database you can just reuse it.

My approach, and this is one persons opinion, is if you can do
it in the database efficiently, go for it.  Everything can get
to the database.  any time you put something outside the
database, you limit the universe of people that can use it.  I
have yet to see an ad-hoc tool that can dynamically query an EJB
-- i know of thousands that can use SQL though.
Review &
      Followup

      Rating: 4
middle
      tier  March 28, 2002
Reviewer:  A reader

Hello Tom,

I agree that the application logic and security logic should be at the server
side rather than the client. But would you suggest to put it inside the database
or in the middle tier? Fine grained security is a great model for restricting
data access to "database users". But it may not be that flexible to restrict the
"application users". On ERP and E-commerce systems we tend to have large number
of application users and the middle tier with only data and index on the
database. Could you also kindly give your views on moving the business logic out
of the database.

Thank you.

          Followup:

If you read me book -- you'll know straight off where I stand on this - its all
part of the database.

Look at what changes have occurred over time.  Host based, client server, 3 tier
app servers, wap/wireless, ????  whats next.

If you put the security in the application, not with the data and the
application is a 3 tier app server setup -- how do you provide wireless access
(answer -- you repeat the security logic, hoping you get it right, in the wap
gateway.  don't change security logic ever again as you know have it in N
places).

If you put the security in the application, and one want to use some old
fashioned client server gui tool to query it what then?

Application logic -- thats a mixed bag.  J2EE style programming can help ensure
that is reusable -- but it's not SQL (hence security IMO needs to be in the db,
app logic could be -- i'd put it there -- but doesn't have to be)

      Rating: 5
Database
      Security  March 28, 2002
Reviewer:  munz
       from reston, usa
Tom:

I have a web application that has a table for menu items shown to the user.
Based on which group user belongs the main menu is built dynamically using
stored procedure.

I also restrict data access by puttin filter in SQL queries based on userids.

Is this considered to be database secure application or application secure.

          Followup:

It is application secure.  You have to write the proper QUERY to see the right
data for the current user.

You have to properly write that query everytime you need that menu and when you
change the security logic (not "if", but "when") you must hunt down all queries
and fix them.

      Rating: 5
Application
      SEcurity  May 28, 2002
Reviewer:  Munz
       from Reston, USA
Tom:

AS a followup, in my web application I have a main page for userid and password
where after user enters it I validate it against a user table to check if it is
valid.

My question, I pass userid from one page to another.  I do not pass password.
Do you think if a user bookmarked a page, he can get to it later without going
through the main login page.  Would this be a bad unsecure design.

How would you handle this?

Thank you,

          Followup:

Don't take this personally -- however -- This would be the single most INSECURE
approach.  It would take about 5 seconds to become ANY user I wanted to become
(literally - no kidding).

If you want to go down this path you should consider:

dbms_obfuscation_toolkit, to encrypt the identity

adding an "expire time" to the identity so the encrypted string changes with
each and every page. (eg: the identity is the string "TKYTE 27-may-2002
15:04:03".  You encrypt that.  When you recieve the encrypted string, you
decrypt it, figure out "it is tkyte", check the expiration date and send back a
newly encrypted string with an updated expiration time)

using ever changing keys -- so the key cannot be guessed over time.

using cookies (so you need not pass it from page to page - it also UPS the
"cracker level" a degree.  It is harder to mimick the cookie then a field value
passed from screen to screen.  Will deter less sophisticated users)

      Rating: 4
Application
      Security : Session Expire Time  May 29, 2002

Reviewer:  Tony  from India
Hi Tom,

We do have the same architecture for application security. One table for
application users and home page to accept userid and password and keeping the
userid in the session and using it for transactions. Will we face any security
issues with this, if yes, please let me know so that I can tell development team
to stop working on this. Is there any other approach to keep users in a table
and put some security checks in the database/application so that on one can
'become any user' as you said.

Regards,
Tony.

          Followup:

As I said -  you must

o encrypt the identity (else I can simply type in any name I want)
o use different keys (to ensure people cannot "guess" the key over time)
o change the string that is used to identify you with each request (i use an
expiration time for example -- every request will return a DIFFERENT encrypted
string)

and as a last piece -- use SSL so that no one can snoop the network and grab the
encrypted identity and use it.

      Rating: 5
Application
      Security  May 29, 2002
Reviewer:  Munz
       from Reston, USA
Tom:

1.  DOes not making a hidden password field on every page solve the problem.
There is no way a user can load a page without knowing the password. If I use
post method too the user can not see the parameters nor he can bookmark it?

2.  Do you mean encrypt the userid or password stored in the database?

3.  I did not quite understand what you mean by expiring user and your method.
Is there an example on your website or reference in oracle documentation?

Thank you,

          Followup:

1) no, i just "view source" and your scheme is right there.

Besides, what stops me from opening notepad and typing in:

<form METHOD=POST action=
http://yourserver/url/i/want/to/break/into>
<input type=hidden name=SECRET_HIDDEN_USERNAME_FIELD value=CEO_OF_YOUR_COMPANY>
<input type=text...... other fields here>
<input type=submit>
</form>

saving that file, and loading it into my browser.  Wah-lah -- 5 seconds later
"I'm in".

2) I mean to encrypt the value of the username in such a way that I cannot
possibly do #1 in any way (i don't have your key, I don't know how to encrypt
CEO_OF_YOUR_COMPANY)

3) the expiration should be very very straight forward.  You will take a string
such as:

CEO_OF_YOUR_COMPANY   28-may-2002 12:00:00

That string contains two things:

a) username (ceo_of_your_company)
b) the time this session is good till

You will encrypt this and send it back in a cookie over SSL (so it is encrypted
again -- no one can steal the encrypted identity).  Later, when they post the
form back, you will get this encrypted string -- decrypt it and see the username
and the time the session is good till.

If the session didn't expire -- continue.  If it did, fail and make them log in
again.

If the session didn't expire -- continue with your work -- do your process and
then set another cookie:

CEO_OF_YOUR_COMPANY   28-may-2002 12:01:20

(note, the time changed -- we used SYSDATE+1/24.  Everytime they post, their
identity will "change" as the time changes)

      Rating: 5
Application
      Security  May 29, 2002
Reviewer:  Munz
       from Reston, USA
Tom:

1.  I can not use cookies in this application due to management decisions.  I am
confused on how you actually do this.  Are you saying to encrypt/decrypt userid
when passing this parameter from one page to another after you select it from
the oracle table?  How would this protect the application  (just for the view
source)?

2. I am thinking of creating another column in addition to the username and
password called session_id.  Every web page will require this session_id for the
user.  User will never know what his session_id is.  I will make this a hidden
field and encrypt it in the browser and when I submit the page I will decrypt
it.  If the value of this parameter was null when a page is called, I tell user
to log in first.  If there is a value then i run the page.

What do you think of this approach?

3.  How do you handle the session expiration without using a cookie?

4.  If you use POST method, and every procedure requires unknown number of
parameters for the user, how would the user know how to run the procedure with a
different userid?  Also, if password is passed to every page and encrypted he
would not be able to pass the otehr person password?
Thank you,

          Followup:

1) why is management making technical decisions.

I am saying

a) the application, on the server, must encrypt the identity string.
b) you must be using SSL
c) a cookie is the *best* way to do this

a and b are mandatory.  Else, just give me about 5 seconds and I'm in as
whomever I want to be -- no kidding.

A) protects the application because I cannot just build a form and put your name
into it.  I must encrypt your name in that form and I cannot encrypt your name
since I do not know your encryption key.  Hence, I cannot "spoof" you easily.

B) protects the application because it will prevent me from sniffing the network
and discovering what your encrypted identity is.  All I need is your encrypted
identity and I can become you.  (a) makes it so I cannot generate your identity
string on my own.  (b) makes it so I cannot steal it from you on the network.

None of this has to do with "view source".  If you don't encrypt it - I've
already demonstrated that I just need to type it in (even with "post", I'll just
"post" it to you).  If you don't encrypt the document using SSL, I'll just grab
it off of the network.

2) If and only If

a) session id is the only thing passed (eg: username is NOT passed in the page)
b) you use SSL
c) session id is a primary key value you will use to get the users identity

Then, this solution is not any different then #1 above.  You will have encrypted
the identity string (your session id) making it not possible for me to just type
one in.  You will have encrypted the document using SSL so that I cannot just
pull yours off of the network and "kidnap" your session.

3) encode the expiration time into the identity string as I said.  Instead of
just USERNAME or just SESSIONID, it'll be username + expiration time or
sessionid + expiration time.

The cookie makes it trivial to pass from form to form.  Using hidden fields
makes it a big pain in the butt.

4) I'll save a page that posts to this procedure to disk.  I'll edit this file I
just saved.  I'll change my username in the hidden field to CEO_OF_YOUR_COMPANY.

It takes about 5 seconds.  Trivial.

If you are encrypting the password -- just encrypt the entire thing, I don't
understand what is so hard about all of this???  Don't keep moving the goalposts
-- I'm answering the original question

<quote>
My question, I pass userid from one page to another.  I do not pass password.
Do you think if a user bookmarked a page, he can get to it later without going
through the main login page.  Would this be a bad unsecure design.
</quote>

The solution for you (the only one)

(a) encrypt the identity in the application before sending it
(b) use SSL to avoid someone stealing the identity

      Rating:
      5
security  September 14, 2002
Reviewer:
      mo
TOm:

AS a followup to your quote:
<quote>
     If you want to go down this path you should consider:

dbms_obfuscation_toolkit, to encrypt the identity

adding an "expire time" to the identity so the encrypted string changes with
each and every page. (eg: the identity is the string "TKYTE 27-may-2002
15:04:03".  You encrypt that.  When you recieve the encrypted string, you
decrypt it, figure out "it is tkyte", check the expiration date and send back a
newly encrypted string with an updated expiration time)

using ever changing keys -- so the key cannot be guessed over time.
</quote>

1. I have about 50 procedures. Each one has a "p_user_id" as an input parameter
and I pass that as a hidden field from one to the other. I do not currently
encrypt it. I want o implement your suggestion. Let me confirm what to be done.
I do not use cookie now.

User "Tom" clicks on a URL that show a page to prompt him for userid/password.
I take these two parameters, hash the password and query the table for record
with userid="Tom" and check if the hashed password is identical to the hashed
password in the table. If it is, let him in otherwise not.

If authentication is OK, the user sees a main menu based on what he is allowed
to see. Now here, in order to run the main_menu procedure I am passing the
user_id as "Tom".

What you are saying is that I should take "Tom" and encrypt it as soon as user
is authenticated then pass it in encrypted mode from one procedure to another.
This way if a user does a "View Source" or even looks at the URL (get mode) he
sees a string that does not mean anything. DO he cannot take "Tom" and replace
it with "Mike" and run the procedure as "Mike".

2.  If I encode the user_id per step 1, and I have a lot of SQL statements in
each procedure that are based on that user_id, do I have to replace all with
select * from table where user_id=decrypt(user_id);

3.  If I want to add an expiration time for my system, I can write system time
to a cookie and run a function i nevery procedure that compares current time to
the time in the cookie and if it is for example larger than 60 minutes force him
to log in.

4.  I can still do this without a cookie by adding system time to every page as
a parameter and comapre it to the intial login time.  OR encode it with a userid
and decrypt it and pull time string out?

5. If one user wants to skip the login everytime and instead want to go to the
main menu. How do you implement this? DO you create a cookie on an initial login
and then when the runs the authentication procedure you check if a cookie is
there and the userid/password values. If it is valid you let him go to the main
menu.

However, if I want to do this only for one user do I have to hard code it in my
PL/SQL code for one userid. Create the cookie an check its values?

Thank you,

          Followup:

1) correct
2) or you can pass encrypted_user_id as a cookie (or formal parameter) and have
a local variable userid varchar2(30) default decrypt(encrypted_user_id); and
leave the body of the code alone
3) correct
4) correct
5) You question why you have a login routine at all if they can just skip it?

      Rating:
      5
security  September 14, 2002
Reviewer:
      mo
TOm:

As a follow up to (5):

1.  I still need a login routine because I am bascially here automating the
entry of the userid/password.  This user apparently wants something like
Microsoft IE feature where "It remembers userid/password" and windows will paste
for you.  Here it is the same thing.  I think I need t ocreate a permanenet
cookie and write the userid/password into that cookie and then when user calls
in the login routine it checks for the values in the cookie. if it is a valid
entry let him in. Can I do this in reality or not?
Would it jeproadize other things?

2.  AS a followup to your quote:
<Quote>
1) no, i just "view source" and your scheme is right there.

Besides, what stops me from opening notepad and typing in:

<form METHOD=POST action=

http://yourserver/url/i/want/to/break/into>
<input type=hidden name=SECRET_HIDDEN_USERNAME_FIELD value=CEO_OF_YOUR_COMPANY>
<input type=text...... other fields here>
<input type=submit>
</form>

saving that file, and loading it into my browser.  Wah-lah -- 5 seconds later
"I'm in".
</Quote>

In order to run a procedure, you have to know all input parameters to it. How
would you know by looking at the html file. this show you the output of the
procedure but how would you break it by guessing how many parameters the
procedure needs?

You also say that encrypted("Mike") would be much safer than "Mike:  Corect?

3.  Can you change the key in your encryption every session?would you recommend
doing this?

4.  Can you use PL/SQL RAND random number generator for assigning keys? How
would this be different than using a regular sequence or sysguid().

Thank you,

          Followup:

1) why doesn't the user use the weak MS feature (which in any real environment
should be totally and 100% outlawed.  Wonder how many windows machines are
sitting out there with this autocomplete turned on to your HR or Financial
systems -- how utterly lame)

My point was -- if you can afford to let this guy skip logging in, you don't
need a login at all -- security is non-existent

2) the HTML file would have all of the <INPUT TYPE= NAME=FOO> tags in there
telling me what the fields are.  Its in plain text.

I'm saying it is impossible for them to encrypt MIKE unless they

a) know your encrypt scheme  <<========== this they might know
b) HAVE YOUR KEY <<<===== this they won't have, ever

3) well, then you have a key managment issue, you surely don't want to ship the
key over the network -- so I think you need a fixed key (that you can change
from time to time)

4) well, dbms_random is a psuedo random number generator.  It has a period (show
me a sequence of numbers generated by it and I can guess what "keys" are coming
next).  Oracle9i added a truly random random number key generator.  dbms_random
is "probably" ok (maybe better if you seed it with a "random" number that is
generated from the "seed" of the number to_char( sysdate, 'jsssss') or something
like that)

A sequence or sysguid would be really bad as keys.  they are 100% predicable!

      Rating:
      5
security  September 14, 2002
Reviewer:
      mo
Tom:

1. This a special user and you know that you have to do what the client wants
sometimes without arguing him. Anyway, the "AutoComplete" feature does not skip
the main menu . It only remembers the userid/password and types it in for you.

Would creating a cookie with userid/password on his PC, and adding some code in
the login screen to read a cookie if it exists, and run the main_menu procedure
if userid="manager" work.

2. As a followup to 2, are you bascially saying that it is very easy for the
user to look at the html file and figure out how many parameters you are
submitting to the following procedure and then bascially replacing a userid with
something else.

3. When you talk about a "Key" IS it like a door key for encryption, and you
have to change the lock and get a new key once in a while?  When yo usay pseudo
random number generator you mean it is predictable while random random is not
predictable?

4. Since I use the parameter "p_user_id" in my SQL statments there is no way
really to leave code as is unless I can define  a default :
p_user_id      IN varchar2  default decrypt(p_user_id);

and just use the parameter instead of using a variable. Can i do that?

Thank you,

          Followup:

1) I'm telling you -- you don't need security at all apparently.  This special
user has given you the ULTIMATE "out".  You can just remove the login screen all
together.

Make darn sure this "special" guy knows that - if this "special guy" cannot be
bothered to click once, he must be very special indeed.

Even Larry Ellison logs in, *especially* him.

You could make this guys cookie have an expiration date that is 3 years in the
future and have your login screen look for the existence of the cookie and just
redirect him to the first page of the application.  BUT, this would be a truly
bad idea.  What if this guy isn't supposed to have access to the system anymore
(he ain't so special tomorrow for some reason).  Well, then you have to rewrite
your code to deny him access, not just remove him from the "user" table or
change his password.

2) yes, yes I am.  view the source of one of your pages some day -- the input
tags are all right there

3) correct and correct

4) sure, you can do that if you like, but then you have to change the name of
the parameter passed to you.

      Rating:
      5
security  September 15, 2002
Reviewer:
      mo
as a followup to #5:

why do i have to change the name of parameter defined:

here is how I will do it:

In login page I will do:

htp.p('i_user_id',decrypt(i_user_id);

and in main_menu page I will do:

create or replace procedure main_menu as
(i_user_id   IN VARCHAR2  default decrypt(i_user_id));
select * from table where col1=i_user_id;
htp.formHidden('i_user_id',encrypt(i_user_id));

and so on from one page to the next.

Thank you,

          Followup:

give it a try and see what happens.

then, you'll know why.

      Rating: 5
web
      security  January 06, 2003
Reviewer:  mo

Tom:

Is there a way to hide a port or server name in mod_plsql.

What i have is an index page on a main web server. then there is a search form
which runs a pl/sql procedure and shows the results.  However the result page
shows the hyperlink for the

http://xx50.was.yyy:7776/pls/xyz/search
This may open it up for hackers.  Any hints?

          Followup:

how could that "open it up for hackers?"

But the answer is "no", the browser sort of needs to know.

But how this could open it to hackers, who would just use a port scanner, ....

      Rating: 5
web
      security  January 06, 2003
Reviewer:  mo

Tom:

Are you saying knowing the server name and port address (7776) does not in any
way help a hacker to hack in? Is not that like giving a private phone number out
for all to see?

I do not know anything about hacking but this is what a manager is worried
about?

Thank you,

          Followup:

I have an unpublished phone number.  Guess what.  I still get telemarketers
calling me.   how?  they just try randomly.

Your ip address -- public knowledge, it is right there in the packet.  It is out
there for all to see.

port sniffers, they'll find whatever services you are running on all ports.

Your manager should let qualfied security experts worry about security.  He
needs to worry about development schedules, meeting them, performance reviews,
having staff meetings - you know, manager stuff ;)

Think about it - ask your manager "but mr. manager, if we don't tell the browser
the hostname/port -- how would the browser know where to get the data?"

Or here is a better analogy than your "private phone number".  Think of the
host/port as a address -- your house address.  Let's see, if you don't tell
anyone where you live -- your house will never be broken into right?   Wrong.

Security through obscurity is worse then no security at all.

      Rating: 5
How to avoid
      logins using ODBC  April 29, 2003
Reviewer:
      Ari  from Mumbai, India
Our applications  depand on  database security. Ie we use databse logins , roles
and object  privilages.
How ever we  would like to prevent users connecting to the databse from odbc
based tools like EXEL , ACCESS, VB progams. We would like to allow only oracle
forms, reports.
How do we achive this.

          Followup:

you cannot -- in client server (not reliably anyway)

you can in Ntier.  You don't say which you use....

say it is ntier and you are using the forms/reports servers.  Well, the forms
and reports can securely enable a password protected role -- this role has the
grants (users do not).  only when these roles are enabled can the users muck
with the data.  since your forms are the only ones that can enable the roles --
there you go.

In client server, this would include embedding the password in a form that is on
the client machine.  It would take me about 5 seconds to find out what that
password is.  In ntier, since I don't have access to the form and I cannot see
the sqlnet traffic between the forms/reports server - I cannot intercept this
information.

      Rating: 5
web
      security  May 05, 2003
Reviewer:  mo

Tom:

A DBA said this and I doubt its reliability.  Is it true?

<In Oracle Release 9, we have an option of installing the Internet Application
Server on a Windows computer, while leaving the Oracle database on an Unix
computer.  This means that you could use almost any URL for your application.
If it works, you could definitely get rid of the numeric portion of an URL.

E.g, A Dell Windows computer could have an address,
http://dell24.xxx.xxx/
 In the company
nameserver, this address could be translated to anything:
http://www.myfavoritename.com/
for example.
On this Windows computer, we would install Oracle 9iAS.  This software from
Oracle would redirect requests to your Oracle database on database server AB20>

1. Was not this always true with 8i too?
2. Does being able to run app server on windows has any effect on hiding URL or
re-routing it?

          Followup:

1) yes it was, it was true for all releases
2) no it does not

we are right smack back at that security via obscurity -- maybe thats why they
tie it into windows ;)

tell you what -- do a lookup on

asktom.oracle.com
osi.oracle.com
govt.oracle.com
www.promedmail.org
www.promedmail.net

they would all be the same box and believe me, there isn't a piece of windows
software anywhere even physically near them.

dns and name servers and multiple names resolving to a single ip address existed
slightly before windows.

      Rating: 4
Need your
      Advice  May 22, 2003
Reviewer:  Sanjay
       from US
Am in the process of developing an application, which will be rolled out to 5
users at present. The application will reside on the user's machine. The
application needs to access 10 tables in the database. The users will have the
privilege to Insert, Delete and Update these tables.  These tables are also
updated by a nightly batch cycle too. A user that is different from the
application user runs the batch cycle. From a Security point I need to stop the
application users from directly connecting to the database using an ODBC
connection (say Access) and Inserting, Deleting or Updating the 10 tables that
they have access to. These users have Oracle ID to connect to the database. How
can I make sure that the application users do not Insert, Delete and Update
these 10 tables that are connected to the application without first connecting
or going through the application. What in your opinion is the best way.

          Followup:

then the correct approach would be to put your applications data logic into the
database as stored procedures, grant execute on the procedures to a role, grant
the role to the end users.

Now, the only way to insert/update/delete your tables is via your application --
which is convienently located in the database.

      Rating:
      5
Security  June 19, 2003
Reviewer:
      Anirudh Sharma  from New Delhi, India
Hi Tom,
Talking about the database security, I intend to use FGAC and app contexts
ability. But I have a doubt.....our application is on the inetrnet(web-reporting
stuff)in order to use the FAGC feature, I'll have to log in each application
user as a database user. Suppose, I have millions of users....is it a viable and
advisable option to create million database users.
Thanks

          Followup:

viable - yes.

I've seen databases with 100's of thousands of users.  You would want to test
with millions (2billion is the theoretical max).

      Rating: 5
curious to
      know  June 19, 2003
Reviewer:  Anirudh
       from New Delhi, India
Thanks Tom for the followup.....However, I am just getting curious to know how
this works in sites like hotmail /yahoo where they have immeasurable users ...
do they really have a database user created for each account???????

          Followup:

If I wrote hotmail it might.

they probably do it themselves, differently.

June 19, 2003 Nilo -- Thanks for the question regarding
      "Lock/Grant", version 8.0.5

      You Asked
Hi Tom,

All the times I tried to issue a GRANT EXECUTE ON package_name
TO user_name and someone is executing that package
simultaneously, I've got notice that the package is locked,
forcing the GRANT to wait the end of the execution of the
package. Is there a shortcut to avoid the lock? Where could I
find the packages currently being executed on the server?

Thanks in advance and sorry my poor english.

Best regards.

Nilo Roberto da Cruz Paim
Analyst on Miracle Ltd.
Porto Alegre / Brazil

      and we said...
No, you cannot avoid this lock.  The packages set of privileges
cannot be modified whilst it is executing.

To see what is pinned in the SGA -- to see if the package you
want to grant on is being executed, you can use the query:

select namespace, owner, name, pins from v$db_object_cache where
pins > 0

Review &
      Followup

      Rating: 5
Still searching
      for solutions ...  June 19, 2003
Reviewer:
      Mihai  from BUCHAREST, Romania
Is there any other way of preventing this behaviour?

I would like to see the answers to these issues:
- which parameter controls the amount of time until I received the message
related to object locking?
- the attempt of acquiring the lock to the 'pinned' objects creates a specific
overhead on the system?
- if I issue an 'alter system flush shared_pool", would this statement release
the 'pins' and clear the way to my GRANT? If yes, what is the impact of
'flushing the shared pool' to the general performances of the system (OLTP)?

Thank you in advance,

Mihai

          Followup:

o you do not have access to that parameter
o no overhead, overhead is something "avoidable" that can be removed without
loss of functionality.

o no. (and it would be really bad to flush)

if you use ROLES (grant execute on package to role_name), you can grant the role
freely without pinning the package.

      Rating: 5
Thank
      you  June 20, 2003
Reviewer:  Mihai
       from BUCHAREST, Romania

June 19, 2003 Matt -- Thanks for the question regarding
      "tnsnames.ora TCP vs. IPC for DB on local host", version 8.1.5 and 8.1.7

      You Asked
We run an app server (Vantive) which makes OCI calls against a database located
on the same host.  The database administrators setup tnsnames.ora to connect to
that database with (PROTOCOL=TCP).  Isn't that much slower than (PROTOCOL=IPC)
because each request and response actually gets sent to the NIC?  They say it
would make only a miniscule difference in performance.  In my research I can't
find a conclusive answer whether this is correct.  Performance is just OK - I'm
just looking for the "low-hanging fruit" for improvements and I really should
test this but I thought I'd get your feedback also.

Configuration:
Oracle 8.1.5 and 8.1.7
HP-UX 11.00

Does the following describe the behavior that (PROTOCOL=TCP) would have for
connections initiated from the same host?

1. Application server makes request on Net8
2. Net8 sends request to interface
3. interface sends request to hub/switch
4. hub/switch sends request back to same interface
5. interface sends request to dedicated server proc
6. statement processed
7. dedicated server sends response to net8
8. net8 sends response to interface
9. interface sends response ot hub
10. hub sends response to same interface
11. interface sends response to application server

Thanks Tom...  Also: this is best technical site on the net.

Matt Butler

      and we said...
well, tuning TCP vs IPC would give you marginal improvements.

Overhauling data structures, re-architecting application, finding and fixing
poorly performing SQL -- massive payback.

(tuning tcp vs ipc isn't "low hanging fruit" material.  Its fine tuning after
all other avenues have been exhausted).

I've seen tcp be faster then ipc and vice versa.  varies by OS, port, settings
and so on.

I'm not a networking expert -- there is probably one out there -- but I do
believe that some OS's will recognize that the network data doesn't need to hit
the network.  I'm sure if I'm wrong -- someone will say something ;)  (i'm sort
of curious myself -- hoping someone follows up)

But -- you know -- it would be easy to benchmark.  Just setup a small test with
a tcp and ipc based listener.  open two sqlplus sessions, one tcp, one ipc and

set termout off
select * from all_objects;
set termout on
select to_char(sysdate,'dd-mon hh24:mi:ss' ) from dual;
set termout off
select * from all_objects;
/
/
/
/
set termout on
select to_char(sysdate,'dd-mon hh24:mi:ss' ) from dual;

and see which one appears to be "much faster"

Review &
      Followup

      Rating: 2
On most
      versions of UNIX, at least...  October 24, 2002

Reviewer:  Jeremy Smith  from Salt Lake City, UT

The network stack realizes when a packet needs to be routed to the
localhost/loopback and doesn't go so far as to actually send it to the network
interface.  Otherwise, you couldn't run any services on a machine with no
network card.  It does have to touch the tcp/ip code so there might be a tiny
bit of delay there...but otherwise, it's pretty much the equivalent of using
IPC's.

Easy to prove that it doesn't hit the network Solaris or Linux if you have root:
use tcpdump or snoop and see if you see any traffic there when you make a local
connection.

If you really want to see how it works, check out the linux source.

      Rating: 5
Sorry, didn't
      mean to give it two stars!  October 24, 2002

Reviewer:  Jeremy Smith  from Salt Lake City, UT

Nothing you ever write is only worth two stars, Tom. :)

      Rating: 4
Test
      results  October 24, 2002
Reviewer:  Matt
      Butler  from Roseville, CA
Here are the test results for your example:

USING TCP:

system@vwrld817>@asktom

TO_CHAR(SYSDATE,'DD-M
---------------------
24-oct 17:32:11

TO_CHAR(SYSDATE,'DD-M
---------------------
24-oct 17:32:23

system@vwrld817>@asktom

TO_CHAR(SYSDATE,'DD-M
---------------------
24-oct 17:33:33

TO_CHAR(SYSDATE,'DD-M
---------------------
24-oct 17:33:45

system@vwrld817>@asktom

TO_CHAR(SYSDATE,'DD-M
---------------------
24-oct 17:33:52

TO_CHAR(SYSDATE,'DD-M
---------------------
24-oct 17:34:05

A consistent 12 seconds!!!

Now for IPC:

connect system@vwrld817ipc
Enter password:
Connected.

system@vwrld817>@asktom

TO_CHAR(SYSDATE,'DD-M
---------------------
24-oct 17:36:35

TO_CHAR(SYSDATE,'DD-M
---------------------
24-oct 17:36:50
system@vwrld817>@asktom

TO_CHAR(SYSDATE,'DD-M
---------------------
24-oct 17:37:00

TO_CHAR(SYSDATE,'DD-M
---------------------
24-oct 17:37:15
system@vwrld817>@asktom

TO_CHAR(SYSDATE,'DD-M
---------------------
24-oct 17:37:26

TO_CHAR(SYSDATE,'DD-M
---------------------
24-oct 17:37:41
system@vwrld817>

A CONSISTENT 15 SECONDS!!!!

IPC is SLOWER!!!

I cannot believe it!

My listener setup:

LISTENER =
  (DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS_LIST =
        (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC))
      )
      (ADDRESS_LIST =
        (ADDRESS = (PROTOCOL = TCP)(HOST = foo.bar.agilent.com)(PORT = 1521))
      )
    )
    (DESCRIPTION =
      (PROTOCOL_STACK =
        (PRESENTATION = GIOP)
        (SESSION = RAW)
      )
      (ADDRESS = (PROTOCOL = TCP)(HOST = foo.bar.agilent.com)(PORT = 2481))
    )
    (DESCRIPTION =
      (ADDRESS = (PROTOCOL = IPC)(KEY = vwrld817))
      (PROTOCOL_STACK =
        (PRESENTATION = TTC)
        (SESSION = NS)
      )
    )
  )
[....]

My tnsnames.ora:

[....]
vwrld817 =
  (DESCRIPTION =
    (ADDRESS_LIST =
        (ADDRESS =
          (PROTOCOL = TCP)
          (Host = foo.bar.agilent.com)
          (Port = 1521)
        )
    )
    (CONNECT_DATA = (SERVICE_NAME = vwrld817)
    )
  )
vwrld817ipc =
  (DESCRIPTION =
    (ADDRESS_LIST =
        (ADDRESS =
          (PROTOCOL = IPC)
          (KEY=vwrld817)
        )
    )
    (CONNECT_DATA = (SERVICE_NAME = vwrld817)
    )
  )
[...]

This flys in the face of all logic - would expect IPC to be faster or at least
the same.

Well thanks Tom!!!!

      Rating: 3
How about the
      context switching from Pro*C / PLSQL  October 25, 2002

Reviewer:  Dev  from USA
We've a PRO*C program which calls a packaged function  around 1.2 mill times.
The driving cursor pulls 1.2 mill records and calls the packaged function.

The package has around 25 in/out parameter of number data type, Pro*C calls them
with double data type.

I guess the context switching happens lot

When we watch the program while it's running most of waits or SQL*Message to
Client.

It's 8.1.7 and the pro*C program and oracle Database resides on the same unix
box.

Please advice if this can be related to TCP protocol and there is work around to
reduce the waits.

Thanks.

          Followup:

why not do this.....

put the driving cursor into plsql and move the code entirely into the database
-- that'll make this take one round trip...

      Rating:
      5
Bequeath?  October 25, 2002
Reviewer:
      A reader
If the client and the database are on the same machine, why not use bequeath?

      Rating: 4
Very
      interesting  October 25, 2002
Reviewer:
      andrew  from ca, usa

      Rating: 5
Benchmarked
      Bequeath - no help  October 25, 2002
Reviewer:
      Matt Butler
FYI
I also benchamarked Bequeath and it averages 13 seconds

          Followup:

they (bequeath and IPC) both use IPC.  it varies from OS to OS and OS release to
OS release as to which will be faster.

Case in point (that things change).  I have a small demo I run.  It shows the
effects of using table compression in 9iR2.  It had been taking 31 seconds to
full scan the uncompressed table and 4 seconds to full scan the compressed on my
laptop running redhat 7.3 -- like clockwork.  In fact, I would kick off the demo
and had a little "talk" I would be giving while it ran.

I upgraded to redhat 8.0 and did the demo.  It took 7 seconds and 1 second.
(same ratio -- about 7ish to 1, just way faster).  Threw me off tho, it never
ran like that.  I attributed to "i must have run it before to test and it was in
the OS file cache).  So, next time I rebooted -- still 7:1 not 32:4 as it had
always been.

Turns out -- redhat 8.0 was just that much faster working with Oracle 9iR2 then
7.3 was.  Everything changed due to that OS upgrade!

      Rating: 4
8.0 not
      faster  December 30, 2002
Reviewer:
      A reader
I seriously doubt whether your quicker times had anything to do with your Red
Hat upgrade. Networking is no different and kernel is close to that in 7.2. The
OS was enhanced though the periferials.

          Followup:

Umm -- it was all about disk performance and YES, in fact it was due to the
upgrade thank you very much.

It had nothing to do with network performance -- it was all about a full scan
and the amount of data moving in from disk to buffer cache.

      Rating: 3
what're all the
      other options  December 31, 2002
Reviewer:
      A reader
>
>Followup:
>why not do this.....

>put the driving cursor into plsql and move the code >entirely into the database

>-- that'll make this take one round trip...

The program does so many calculation and it's not easy to migrate that logic to
PL/SQL. It's the vendor code and not the custom code. Our DBA saying to me that
changing to IPC is a global change (not even agreeing for doing it in test box)

So is there any other option?

          Followup:

what does IPC have to do with this?

anyway -- you have an algorithmic problem.  Look to your algorithm to fix it,
not to some magic switch.

My advice to them was -- get the code OFF the network, put it in the database.
Don't pull all of the data OUT just to put it back IN.

If you have vendor supplied code, you are stuck, you get what you got.  there is
not much you can do about it other then ask them "hey, why did you do it wrong?"

      Rating:
      3
  December 31, 2002
Reviewer:
      A reader
I guess I've to repeat my question

We've a PRO*C program which calls a packaged function  around 1.2 mill times.
The driving cursor pulls 1.2 mill records and calls the packaged function.

The package has around 25 in/out parameter of number data type, Pro*C calls them

with double data type.

I guess the context switching happens lot

When we watch the program while it's running most of waits or SQL*Message to
Client.

It's 8.1.7 and the pro*C program and oracle Database resides on the same unix
box.

Please advice if this can be related to TCP protocol and there is work around to

reduce the waits.

Thanks.

          Followup:

remember -- after all you are just "a reader" -- there is no way for me to see
what reader, just a reader

I told you what I would do.  Maybe I'll repeat myself (with a cut and paste)

put the driving cursor into plsql and move the code entirely into the database
-- that'll make this take one round trip...

but you already said "no can do"...

do you use array fetching?  if not, do so.

calling anything 1.2 million times is going to take quite a bit of time.
Parallelize your operation -- run 15 copies of your pro*c app, each working on a
different bit of the table.

"When we watch the program while it's running most of waits or SQL*Message to
Client."

does not make sense unless you meant "most waits are 'SQL*Net Message to
client'" -- but that is an idle wait (normal)...

if you are on the same unix box, why are you using net at all?  Just user/pass
-- no connect string.

      Rating: 5
Very
      informative response, Tom  December 31, 2002

Reviewer:  Jay Hart

      Rating: 4
That's what I
      guess I was missing  December 31, 2002
Reviewer:
      A reader
I'll run the program without the connect string and let you know.

      Rating: 4
No listener,
      got connect with the connect string  May 08, 2003

Reviewer:  A reader
On the the server, I stopped the listener as
lsnrctl stop

I verified it was indeed stopped.

Then on the same server I used:

conn scott/tiger@xxx

Got connected, Why? (The connect string and The listener was down.)

I recall there may be configured for no need the listener. Is it the case here?

Thanks.
Sean

          Followup:

you were using a different listener.  really.

      Rating: 4
TCP is fast
      enought.  May 08, 2003
Reviewer:  Leonardo
      Zacché  from RJ/Brazil
In fact, if you point to a host named localhost (or 127.0.0.1, if you prefer)
your packet doesn't reach the network. The NIC sends it back to you.

Try this:
set up a machine to use DHCP and boot it up, but with NO network cable attached.
This machine will not receive any IP number. If you ping any address (valid or
not) it'll fail. BUT if you ping localhost (or 127.0.0.1) you will get.
And even Oracle will listen to and respond to your application server.

Even if you use your ip address (fixed or given by a DHCP server) instead of
localhost your package will not "go for a ride" on the network. Your NIC is
smart enought to prevent it.

In fact, 127.0.0.1 and localhost are equivalent, and both "reserved TCP/IP
words". You cannot set a machine this IP and expect it to be found by anyone on
your network.

Hope this help.

LZ

ps: Tom, why the "spell checker" doesn't recognize over 90% of my words?

      Rating: 3
Bequeath
      Connection = IPC  June 18, 2003
Reviewer:
      Robert  from PA
Tom,
Is the the "old" Bequeath Connection now called IPC ?
Bequeath connection can be established without the listener running, right ?
I can't verify this right now, but is IPC also listener-independent ?
Thanks

          Followup:

$ sqlplus /

ops$tkyte@ORA920LAP> @getspid

DEDICATED_SE CLIENTPID
------------ ------------
3526         3525

ops$tkyte@ORA920LAP> !ps -auxww | grep 3526
ora920    3526  0.2  2.8 232876 14756 ?      S    07:51   0:00 oracleora920
(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))

bequeath is bequeath, IPC is instead of TCP (network)

bequeath uses ipc under the covers (that is just interprocess communication). we
can use it with or without using a listener to connect

      Rating:
      4
BEQUEATH  June 19, 2003
Reviewer:
      Robert  from PA
>ops$tkyte@ORA920LAP> !ps -auxww | grep 3526
>ora920    3526  0.2  2.8 232876 14756 ?      S    07:51   >0:00 oracleora920
>(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))

I could swear Bequeath was presented as one of the selections in net
configuration (Windoz GUI).

But i don't see it now on my 9iR2/Linux installation -- only TCP, TCPS, IPC.
Obviously it's still there.
Was it actually removed from the GUI list ? can you confirm this , Tom.
Thanks

          Followup:

nope, no windoze to look at, only thru

      Rating:
      5
Outstanding!  June 20, 2003
Reviewer:
      Wayne Zhu  from Philadelphia, PA
I had some question some times ago that I thought (IPC or BEQ) is "faster" than
TCP, but, after tests, it was found that there were basically no obvious
difference using IPC or BEQ vs TCP (Note: the tests were done for a whole
application, in which, for example, order processing measured by order lines per
hour, on different Unix flavors). Your anwser and follow-ups are surprisingly
accurate to me and more importantly very constructive. Well done, Tom, and
Thanks.

      Rating: 5
local vs
      TCP  November 03, 2003
Reviewer:  Zoran
      Martic  from Dublin, Ireland
Hi all,

Just to share a few things I tested in the past on Solaris and HP platforms.

Just to start first with the standard way of measuring the improvement here:

Overall time = Client time + network time + server time

When you are not using array processing (both bulk DML and prefetching in
SELECT) and your applcation is local to the database server you should consider
using BEQ or IPC.
If you cannot move your application to use array/bulk operations consider using
BEQ or IPC network connection
, of course only, if the network time here is the big percentage of overall time
or you want to increase the scalability of overall system.

Now the scenario to network time to be very big is always when you are doing
very fast SQL so many times per second in a loop like simple
insert/update/select from dual/....

We had the situation to use simple INSERT's a lot and not have the possibility
to change it to be bulk insert.
The application was on the same machine as Oracle server. We just switch the
application to use BEQ and we have got a very nice improvement in overall speed
of 30%.
We have also tested on other ocassions BEQ/IPC vs TCP and it was faster in many
cases.

It is very normal that BEQ/IPC to be faster then TCP because of the complexity
build arround TCP but not always true.
But at the end it is all about your platform and your test.
We are now using on all our sites BEQ if possible, if not IPC (usually when the
applciation is 32 bit and Oracle is 64 bit) and TCP when the application is on a
different server. At the end this is generic approach and should be tested.

Hope I help something.

June 20, 2003 Yogesh -- Thanks for the question regarding "RMAN
      Error ", version 8.1.7

      You Asked
I have two database servers. Both have 4 oracle instances. One database each for
RMAN specifically. I'm want to store details on cross servers i.e. details of
server 1 in RMAN instance on second and vice versa.

When I issue

rman target sys/change_on_install@mdb catalog rman/rman@rcat

I get following error ....

Recovery Manager: Release 8.1.7.0.0 - Production

RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-00554: initialization of internal recovery manager package failed
RMAN-04005: error from target database: ORA-01017: invalid username/password;
logon denied

but if I say sqlplus sys/change_on_install@mdb, I'm able to connect ... can you
please tell me where I'm going wrong

      and we said...
Article-ID:         <Note:1034291.6>
Circulation:        PUBLISHED (EXTERNAL)
Folder:             server.Recovery.RMAN
Topic:              Known Problems
Title:              RMAN-00569 -04005 ORA-01017: Connecting to Target Database
Document-Type:      PROBLEM
Impact:             LOW
Skill-Level:        CASUAL
Server-Version:     08.00.0X to 09.00.0X
Updated-Date:       21-FEB-2002 10:41:53
References:

Attachments:        NONE
Content-Type:       TEXT/PLAIN
Keywords:           ORA-01017; RMAN-04005; RMAN;
Errors:             ORA-01017;  RMAN-04005;
Products:           5/RMAN (08.00.03.XX to 09.00.0X);
Platforms:          GENERIC (2.5.1);

Problem Description:
====================

Recovery Manager fails with the following errors when attempting to connect
to the target database:

   % rman

Recovery Manager: Release 8.0.3.0.0 - Production

   RMAN> connect target sys/change_on_install@TARGETDB

   RMAN-00569: ================error message stack follows
   RMAN-04005: error from target database:
               ORA-01017: invalid username/password; logon denied

Problem Explanation:
====================

Recovery Manager automatically requests a connection to the target database
as SYSDBA.

Search Words:
=============

password file, orapwd, RMAN-569, RMAN-4005, ORA-1017, RMAN-554

Solution Description:
=====================

Recovery Manager automatically requests a connection to the target database as
SYSDBA.  In order to connect to the target database as SYSDBA, you must either:

1. Be part of the operating system DBA group with respect to the target
   database.  This means that you have the ability to CONNECT INTERNAL
   to the target database without a password.

    - or -

2. Have a password file setup.  This requires the use of the "orapwd" command
   and the initialization parameter "remote_login_passwordfile". See Chapter 1
   of the Oracle8(TM) Server Administrator's Guide, Release 8.0 for details.
   Note that changes to the password file will not take affect until after
   the database is shutdown and restarted.

For Unix, also ensure TWO_TASK is _not_ set.
e.g. % env | grep -i two
     If set, unset it.
     % unsetenv TWO_TASK

Solution Explanation:
=====================

Recovery Manager automatically requests a connection to the target database as
SYSDBA.  If the target database does not have a password file, the user you are
logged in as must be validated using Operating system authentication (similar
to svrmgrl, and connect internal).

NOTE:  SYSOPER privileges are not sufficient.  SYSDBA privileges are required
       for the target database.

Review &
      Followup

      Rating: 5
RMAN
      Error  October 09, 2002
Reviewer:  Ramu
       from India
Hi tom ,

I am getting the error using the RMAN while executing the following command

RMAN>   run {
2>        allocate channel disk1 type disk format
3>        'd:/rman/mydb/%d_fb_%t';
4>        sql 'ALTER SYSTEM ARCHIVE LOG CURRENT';
5>     }

The error is
-------------
RMAN-08522: input datafile fno=00005 name=C:\ORACLE\ORADATA\MOMDB\INDX01.DBF
RMAN-08522: input datafile fno=00006 name=C:\ORACLE\ORADATA\MOMDB\OEMREP01.DBF
RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-03007: retryable error occurred during execution of command: backup
RMAN-07004: unhandled exception during command execution on channel disk2
RMAN-10035: exception raised in RPC: ORA-19504: failed to create file "d:/rman/M
OMDB_fb_474824217"
ORA-27040: skgfrcre: create error, unable to create file
OSD-04002: unable to open file
O/S-Error: (OS 3) The system cannot find the path specified.
RMAN-10031: ORA-19624 occurred during call to DBMS_BACKUP_RESTORE.BACKUPPIECECRE
ATE
---------------------
can please tell me where is the problem  and to solve it .

Thank you !

          Followup:

does the directory exist, is it writeable by the OS account that is running
rman.

      Rating: 5
RMAN /
      Ramu  October 09, 2002
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Ramu:

The run block you have posted can not be the one generating the error.  The
channels are different (disk1 and disk2), there is no backup command in the run
block (but the error occurs during execution of a backup command), and the paths
are different ('d:/rman/mydb/' and 'd:/rman/')...  The run block you have posted
does nothing more than switch logfiles...

HTH,

Mark

          Followup:

Mark -- thanks, i didn't really even read it that closely.

99.9999% of the time -- its that the directory doesn't exist.  But you are
correct, this cut and pasted is "doctored" ;)

      Rating: 5
RE:
      doctored  October 09, 2002
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Yeah, and I'll bet this is the only one ever... :)

- Mark

      Rating:
      5
  October 10, 2002
Reviewer:  Ramu
       from India
I have just added  the  "backup database;" in the run command It was executed
successfully.
Thank you very much  for your cooperation

      Rating: 4
Different
      Error  January 10, 2003
Reviewer:  Rahul
       from India
Hi Tom

My target db and recovery catalog is on same system and when I tried to connect
to RMAN with catalog, it gave me following error.

C:\>RMAN CATALOG RECCAT/RECCAT

Recovery Manager: Release 8.1.6.0.0 - Production

RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-00554: initialization of internal recovery manager package failed
RMAN-04004: error from recovery catalog database: ORA-12560: TNS:protocol
adapter error

When I tried to connect with host string, it gave me different error. See
below...

C:\>RMAN CATALOG RECCAT/RECCAT@RND.COM

Recovery Manager: Release 8.1.6.0.0 - Production

RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-00554: initialization of internal recovery manager package failed
RMAN-04004: error from recovery catalog database: ORA-00604: error occurred at
recursive SQL level 1
ORA-02248: invalid option for ALTER SESSION

Thanks.

      Rating: 4
Different
      Error  January 10, 2003
Reviewer:  Rahul
       from India
Hi Tom

My target db and recovery catalog is on same system and when I tried to connect
to RMAN with catalog, it gave me following error.

C:\>RMAN CATALOG RECCAT/RECCAT

Recovery Manager: Release 8.1.6.0.0 - Production

RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-00554: initialization of internal recovery manager package failed
RMAN-04004: error from recovery catalog database: ORA-12560: TNS:protocol
adapter error

When I tried to connect with host string, it gave me different error. See
below...

C:\>RMAN CATALOG RECCAT/RECCAT@RND.COM

Recovery Manager: Release 8.1.6.0.0 - Production

RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-00554: initialization of internal recovery manager package failed
RMAN-04004: error from recovery catalog database: ORA-00604: error occurred at
recursive SQL level 1
ORA-02248: invalid option for ALTER SESSION

Thanks.

      Rating: 5
securing rman
      connections  June 03, 2003
Reviewer:
      A reader
Hi

in oracle 9.2.0.1 and RedHat Linux

in that note you posted, the first solution is

1. Be part of the operating system DBA group with respect to the target
database.  This means that you have the ability to CONNECT INTERNAL to the
target database without a password.

according to bug 644988 there is no way we can do connect internal remotely
because according to that bug most connections are not secure so I guess this
solution is discarded? Also how can we tell Oracle our connection is secure if
it always assumes it´s not secure?

I have done some tests in my test databases, I created a user called test at OS
level and created a ops$test in the database, my goal is to conect to the
database remotely using (connecting without providing password)

CONNECT /@net_service_name AS SYSDBA

as shown in
http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96521/dba.htm#13465
however I just noticed this would never work! In order to conenct using "/ as
sysdba" I must have a externally identified user, if I do

grant sysdba to ops$test;

I get ORA-01997: GRANT failed: user 'OPS$TEST' is identified externally

From Metalink this expected behaviour  because you cannot grant sysdba to ops$
users. So I guess the documentation is wrong suggesting we can connect remotely
using

CONNECT /@net_service_name AS SYSDBA
CONNECT /@net_service_name AS SYSOPER

???

The purpose of all this is I ant to backup around 20 databases from a server
where my RMAN Catalog is lcoated. I want to execute rman from there and backup
the database remotely because I dont want to store my rman catalog owner and
password in all servers.

          Followup:

umm, you've misread that "bug that is not a bug".

You use orapwd to setup remote connect internal privs in a secure fashion.

You cannot connect remotely "securely" using OS authentication.  Think about it.
Hmm, I'll just drop my machine in your network.  I'm running unix, I'll just
create an OS user test.  Hmmm, I'm in.

You cannot do what you are attempting to do securely.  That "bug that is not a
bug" tells you how you can do this securely and it'll require a password.

You will need an rman account using a password on your servers.  This rman
account will have sysdba.  Think about how INSECURE it would be if you just
trusted the remote OS to say "rman here, please let me in"

      Rating: 5
forogot to
      mention my parameters  June 03, 2003
Reviewer:
      A reader
hi

the parameters settings are

os_authent_prefix                    string      ops$
remote_login_passwordfile            string      NONE
remote_os_authent                    boolean     TRUE

      Rating: 5
I understand
      its not a bug  June 03, 2003
Reviewer:
      A reader
Hi

I understood it´s not a  bug, rather how things work. Then the documentation is
wrong suggesting we can use

CONNECT /@net_service_name AS SYSDBA
CONNECT /@net_service_name AS SYSOPER

That is never possible!

Also in administrator guide, there is flow chart suggestiing when to use
password file and when to use os authentification, if we cannot do remote os
authentification (with sysdba) then that flow chart is wrong too!

      Rating: 4
error
      info  June 19, 2003
Reviewer:  Reader

Tom, when I used OEM (RMAN interface) to backup my full database (9.2.0), I
received the following error.

RMAN:03009: Failure of controlfile and Spfile Autobackup Command on WB channel
at 06/19/2003 12:21:51

Could you please help me in resolving this error. Thanks.

          Followup:

try cutting and pasting FULL error stacks.  insufficient data.

      Rating: 3
Following error
      creating me full confusion  July 25, 2003

Reviewer:  Reddy  from Bangalore, India
Hi Tom

Thanks for your early reply's. Now I am practicing backup and recovery using
RMAN. I will give what scenarios are followed.

1. We have crated succefully RMAN and backed up my database succesfully.
2.Then I shutdown completely my target database and deleted one of the my
datafile.
3. Using RMAN I restored and recovered successfully.
4. I inserted some data in my db using insert into ... around 11.30 AM.
5.I tried to recover using incomplete recovery using UNTILE my db Using RMAN. I
got error so, I stopped recovery using RMAN.
6.I have givin in svrmgrl recover database until '2003-07-24:11:00:00'
I got confirmation that recovery completed.
7. Then I opened " alter database open resetlogs".
8. But my old data I din't get my old data.

the next day the 25-07-2003.

1.I started to RMAN successfully and I connected.
2.When I give  " resyc database ", I got the following error.
RMAN-03022: Compilling command: resync
RMAN-03023: Executing command : resync
RMAN-08029: Snapshot control file name set to default value:
%ORACLE_HOEM%\DATABASE\SNCF%ORACLE_SID%.ORA

3. When I checked my os level at E:\ORACLE816\DATABASE\SNCFREDDY.ORA file has
been created. But I donot know how this file has been created.

Can you tell me how I should resove the problem and how this file has been
created without my knowledge.

June 20, 2003 Cristian -- Thanks for the question regarding
      "Implications of dbms_stats.gather_schema_stats", version 8.1.7

      You Asked
Hi Tom:

My question it's Can a dbms_stats.gather_schema_stats(ownname=> 'A' ,
estimate_percent=> 10 , cascade=> TRUE), affect objects in other schema, making
them necessary to be analyzed. Le me give you a more detailed explanation:

I have an 8.1.7.3 EE database in Windows 2000. In this database we have various
schema, but particulay 2, lets call them A, and B are the ones that gives us the
problem. Schema A has a few tables, views, a package a 6 synonyms to schema B.
Schema A it's used to load using the tables, views and package and through the
synonyms 6 tables en schema B. But after we load the data into schema A, analyze
it with dbms_stats.gather_schema_stats(ownname=> 'A' , estimate_percent=> 10 ,
cascade=> TRUE) and then used the package to process it everything  was fine,
but when it finished and from schema B we launched a process that reads the 6
just inserted tables that process lasted about an hour (normally it takes about
a minute) after that we killed it. But later when we did the whole process again
but before we launched the process en schema B, first analyzed the 6 tables it
ran well. Is this an expected behavior ?

      and we said...
the gather schema stats on A would gather stats only on things owned by A, not
on the objects A has synonyms to.

Therefore, you loaded A, gathered stats on A's objects -- but not B's.  then you
ran something that used A's and B's tables together -- B's tables had no stats
so the optimizer "guessed they were small" and came up with a suboptimal plan

You corrected that by properly analyzing B's tables.
Review &
      Followup

      Rating:
      4
dbms_stats.gather_schema_stats  March 07, 2003

Reviewer:  Cristian Perez  from Dominican Republic

Just one clarification schema A loads the tables in schema B. Then Schema B runs
a process that works with the data in schema B only. But I guess the same
applies, table not analyzed that changed a lot because they were completely
loaded.

          Followup:

yes, if the tables in b were analyzed, then you loaded them with new stuff, and
then you ran stuff against them -- the optimizer was working off of the old --
totally not relevant and wrong statistics.

garbage in, garbage out.

      Rating:
      4
  March 07, 2003
Reviewer:
      A reader
UB from Domingo

      Rating: 5
Insufficient
      privileges to analyze an object in Schema  June 16, 2003

Reviewer:  Kamal Kishore  from New Jersey, USA

Hi Tom,
What could be the cause of following error?
IS there a way to find the object that is causing the error?
Thanks,

SQL> exec dbms_stats.gather_schema_stats(user, estimate_percent => 20,
method_opt => 'FOR ALL INDEXED COLUMNS', degree => 1, cascade => true) ;
BEGIN dbms_stats.gather_schema_stats(user, estimate_percent => 20, method_opt =>
'FOR ALL INDEXED COLUMNS', degree => 1, cascade => true) ; END;

*
ERROR at line 1:
ORA-20000: Insufficient privileges to analyze an object in Schema
ORA-06512: at "SYS.DBMS_STATS", line 9858
ORA-06512: at "SYS.DBMS_STATS", line 10023
ORA-06512: at "SYS.DBMS_STATS", line 10077
ORA-06512: at "SYS.DBMS_STATS", line 10054
ORA-06512: at line 1

SQL>
SQL> disconnect
Disconnected from Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production
SQL>

          Followup:

make sure you have the ANALYZE ANY priv.

      Rating: 3
What about any
      data locking issues?  June 16, 2003
Reviewer:
      Matt  from Australia
Obviously the best time to generate optimizer stats might be after a major data
load. Sometimes though it might be necessary to generate stats whilst the data
is changing (although at a quiet time).

What kind of approach does dbms_stats take to make sure that the stats generated
are accurate given the fact that data may be changing whilst the stats are being
generated. Is there any significant impact to processes that are concurrently
changing the data being analysed.

Thanks and Regards,

          Followup:

the stats will be current as of the time dbms_stats started reading the data.
If the data is changing, the stats will reflect only what dbms_stats sees (it
mostly uses SQL which gets a read consistent, non blocking read).

stats are never 100% accurate except for read only data.

      Rating: 4
privilege on
      GATHER_SCHEMA_STATS   June 17, 2003
Reviewer:
      VIVIAN  from USA
Hi Tom,

My database is 9.2.0.3.0 on HP UNIX.  With ANALYZE ANY privilege, I can run
stats for other schema, says SCOTT;

    BEGIN
    DBMS_STATS.GATHER_SCHEMA_STATS (
        ownname => 'SCOTT',
        estimate_percent => 10,
        degree     =>      4,
        cascade => TRUE);
    END;
/
PL/SQL procedure successfully completed

However, when I create a procedure in my schema to gather stats and run the
procedure, I got the error message, see the following:

create or replace procedure run_stat as

begin
     EXECUTE IMMEDIATE 'alter session SET SORT_AREA_SIZE = 10485760';
     EXECUTE IMMEDIATE 'alter session SET SORT_AREA_RETAINED_SIZE = 10485760';

    DBMS_STATS.GATHER_SCHEMA_STATS (
        ownname => 'RFRAM',
        estimate_percent => 10,
        degree     =>      4,
        cascade => TRUE);

EXCEPTION
   WHEN others THEN
      raise_application_error (-20000, 'stat failed' || SQLERRM);
end;

Procedure created

SQL> exec run_stat;

begin run_stat; end;

ORA-20000: stat failed ORA-01031: insufficient privileges
ORA-06512: at "TESTER.RUN_STAT", line 16
ORA-06512: at line 1

What else privilege needed for analyze other schema in 9.2.03?

The same procedure can run fine in oracle version 9.0.1 before.

          Followup:

see

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html
I will guess you have the analyze any priv via a ROLE and not directly granted.

      Rating: 4
privilege in
      DBMS_STATS.GATHER_SCHEMA_STATS   June 18, 2003

Reviewer:  VIVIAN  from USA
ANALYZE ANY priv is directly granted.

SQL> GRANT ANALYZE ANY TO TESTER;

Grant succeeded

-- lOGIIN AS TESTER;

SQL> EXECUTE RUN_STAT;

begin RUN_STAT; end;

ORA-20000: stat failed ORA-01031: insufficient privileges
ORA-06512: at "TESTER.RUN_STAT", line 16
ORA-06512: at line 1

          Followup:

so, compare the set of privs from the system where it works to the one that does
not.

You know what -- run a script like this and cut and paste the ACTUAL output:

ops$tkyte@ORA920LAP> drop user a cascade;

User dropped.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create user a identified by a;

User created.

ops$tkyte@ORA920LAP> grant create session to a;

Grant succeeded.

ops$tkyte@ORA920LAP> grant analyze any to a;

Grant succeeded.

ops$tkyte@ORA920LAP> grant create procedure to a;

Grant succeeded.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select * from dba_sys_privs where grantee in ( 'A',
'PUBLIC' );

GRANTEE                        PRIVILEGE                                ADM
------------------------------ ---------------------------------------- ---
A                              ANALYZE ANY                              NO
A                              CREATE SESSION                           NO
A                              CREATE PROCEDURE                         NO

ops$tkyte@ORA920LAP> @connect a/a
ops$tkyte@ORA920LAP> set termout off
a@ORA920LAP> REM GET afiedt.buf NOLIST
a@ORA920LAP> set termout on
a@ORA920LAP>
a@ORA920LAP>
a@ORA920LAP> create or replace procedure run_stat
  2  as
  3  begin
  4       EXECUTE IMMEDIATE 'alter session SET SORT_AREA_SIZE = 10485760';
  5       EXECUTE IMMEDIATE 'alter session SET SORT_AREA_RETAINED_SIZE =
10485760';
  6
  7      DBMS_STATS.GATHER_SCHEMA_STATS (
  8          ownname => 'SCOTT',
  9          estimate_percent => 10,
 10          degree     =>      4,
 11          cascade => TRUE);
 12  EXCEPTION
 13     WHEN others THEN
 14        raise_application_error (-20000, 'stat failed' || SQLERRM);
 15  end;
 16  /

Procedure created.

a@ORA920LAP>
a@ORA920LAP> exec run_stat

PL/SQL procedure successfully completed.

that was 9203

      Rating: 4
privilege in
      DBMS_STATS.GATHER_SCHEMA_STATS   June 19, 2003

Reviewer:  VIVIAN  from USA
Thanks Tom for your time. However I still got same error.
Before my database migrate to 9.2.0.3, the procedure works fine in 9.0.1.

Connected to Oracle9i Enterprise Edition Release 9.2.0.3.0
Connected as system

SQL> create user a identified by a;

User created

SQL>  grant create session to a;

Grant succeeded

SQL> grant analyze any to a;

Grant succeeded

SQL> grant create procedure to a;

Grant succeeded

SQL>
SQL> select * from dba_sys_privs where grantee in ( 'A',
  2  'PUBLIC' );

GRANTEE                        PRIVILEGE
ADMIN_OPTION
------------------------------ ----------------------------------------
------------
A                              ANALYZE ANY                              NO
A                              CREATE SESSION                           NO
A                              CREATE PROCEDURE                         NO

SQL> connect a/a;
Connected to Oracle9i Enterprise Edition Release 9.2.0.3.0
Connected as a

SQL> set termout on
SQL>
SQL> create or replace procedure run_stat as
  2
  3  begin
  4       EXECUTE IMMEDIATE 'alter session SET SORT_AREA_SIZE = 10485760';
  5       EXECUTE IMMEDIATE 'alter session SET SORT_AREA_RETAINED_SIZE =
10485760';
  6
  7
  8      DBMS_STATS.GATHER_SCHEMA_STATS (
  9          ownname => 'SCOTT',
 10          estimate_percent => 10,
 11          degree     =>      4,
 12          cascade => TRUE);
 13
 14  EXCEPTION
 15     WHEN others THEN
 16        raise_application_error (-20000, 'stat failed ' || SQLERRM);
 17  end;
 18  /

Procedure created

SQL> exec run_stat

begin run_stat; end;

ORA-20000: stat failed ORA-01031: insufficient privileges
ORA-06512: at "A.RUN_STAT", line 16
ORA-06512: at line 1

          Followup:

remove the exception block, lets see what the real call stack it. It is not
useful to catch an exception like that, only removes useful information for the
debug process.

      Rating: 2
Isn't that a
      BUG?  June 19, 2003
Reviewer:  Peter
       from Washington, DC. USA
Hi Tom,

I followed the same steps as you did and tested two Oracle9 database (one is
9.2.0.1 and the other is 9.2.0.3 ), both showed the same result. That is if you
run the run_stat to against any schema with no object, or only have tables, the
procedure will be executed sucessfully, however if you have INDEX in the
schedma, you will have "ORA-01031: insufficient privileges".  This rule applies
to all normal users (including users have dba privilege) except SYS (or users
have SYSBDA privilege and login as sysdba).

This problem only applies to Oracle9.2, the procedure run well both in
Oracle8.1.7 or Oracle9.0.

Please let us know if this is known bug in Oracle9.2. The following is the test
I result from my test against Oracle9.2.0.1.  Thanks.

SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
PL/SQL Release 9.2.0.1.0 - Production
CORE    9.2.0.1.0       Production
TNS for 32-bit Windows: Version 9.2.0.1.0 - Production
NLSRTL Version 9.2.0.1.0 - Production

SQL> create user a identified by a;

User created.

SQL>
SQL> grant create session to a;

Grant succeeded.

SQL> grant analyze any to a;

Grant succeeded.

SQL> grant create procedure to a;

Grant succeeded.

SQL> select * from dba_sys_privs where grantee in ( 'A',
  2  'PUBLIC' );

GRANTEE                        PRIVILEGE                                ADM
------------------------------ ---------------------------------------- ---
A                              ANALYZE ANY                              NO
A                              CREATE SESSION                           NO
A                              CREATE PROCEDURE                         NO

SQL> connect a/a@ora92
Connected.
SQL> create or replace procedure run_stat as
  2  begin
  3       EXECUTE IMMEDIATE 'alter session SET SORT_AREA_SIZE = 10485760';
  4       EXECUTE IMMEDIATE 'alter session SET SORT_AREA_RETAINED_SIZE =
10485760';
  5
  6
  7      DBMS_STATS.GATHER_SCHEMA_STATS (
  8          ownname => 'SCOTT',
  9          estimate_percent => 10,
 10          degree     =>      4,
 11          cascade => TRUE);
 12
 13 end;
 14  /

Procedure created.

SQL> exec run_stat

PL/SQL procedure successfully completed.

SQL> connect /@ora92 as sysdba
Connected.

SQL> select count(*) from dba_objects
  2  where owner='SCOTT';

  COUNT(*)
----------
         0

SQL>
SQL> connect scott/tiger@ora92
Connected.
SQL> create table t1 (col1 varchar2(10));

Table created.

SQL>
SQL> connect a/a@ora92
Connected.
SQL> exec run_stat;

PL/SQL procedure successfully completed.

SQL>
SQL> connect scott/tiger@ora92
Connected.
SQL> create index ind1 on t1 (col1);

Index created.

SQL>
SQL> connect a/a@ora92
Connected.
SQL> exec run_stat
BEGIN run_stat; END;

*
ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_STATS", line 9136
ORA-06512: at "SYS.DBMS_STATS", line 9616
ORA-06512: at "SYS.DBMS_STATS", line 9800
ORA-06512: at "SYS.DBMS_STATS", line 9854
ORA-06512: at "SYS.DBMS_STATS", line 9831
ORA-06512: at "A.RUN_STAT", line 7
ORA-06512: at line 1

SQL>

SQL> connect /@ora92 as sysdba
Connected.
SQL> create or replace procedure run_stat as
  2  begin
  3       EXECUTE IMMEDIATE 'alter session SET SORT_AREA_SIZE = 10485760';
  4       EXECUTE IMMEDIATE 'alter session SET SORT_AREA_RETAINED_SIZE =
10485760';
  5
  6
  7      DBMS_STATS.GATHER_SCHEMA_STATS (
  8          ownname => 'SCOTT',
  9          estimate_percent => 10,
 10          degree     =>      4,
 11          cascade => TRUE);
 12
 13  end;
 14  /

Procedure created.

SQL> exec run_stat;

PL/SQL procedure successfully completed.

SQL>

          Followup:

9.2.0.1??  you said 9.2.0.3 before so that is what I was using.  Anyway, does
not reproduce in 9.2.0.3, was an issue in 9.2.0.1, fixed in 9.2.0.2 and above

      Rating: 4
Roles for
      analyze  June 19, 2003
Reviewer:  Emmett
      Cleveland  from Houston, TX
Peter, their is an issue with 9.2 and the gather_system_statistics role, Try
granting that role to your executing user and run the procedure. If it does not
work the work around is to create a new gather_system_statistics role and grant
DBA to it.

      Rating: 5
The Real
      Answer  June 19, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Please review MetaLink Note: 203220.1

It has the answer to this issue...

Briefly:

SQL> create user b identified by b
  2  default tablespace users
  3  temporary tablespace temp;

User created.

SQL> grant create session, create table to b;

Grant succeeded.

SQL> connect b/b
Connected.
SQL> create table t
  2  (
  3  c1 number
  4  );

Table created.

SQL> connect a/a
Connected.
SQL> create or replace procedure run_stat as
  2  begin
  3       EXECUTE IMMEDIATE 'alter session SET SORT_AREA_SIZE = 10485760';
  4       EXECUTE IMMEDIATE 'alter session SET SORT_AREA_RETAINED_SIZE =
10485760';
  5       DBMS_STATS.GATHER_SCHEMA_STATS (
  6       ownname => 'B',
  7       estimate_percent => 10,
  8       degree     =>      4,
  9       cascade => TRUE);
 10  end;
 11  /

Procedure created.

SQL> exec run_stat

PL/SQL procedure successfully completed.

SQL> connect b/b
Connected.

SQL> create index ind1 on t(c1);

Index created.

SQL> connect a/a
Connected.

SQL> exec run_stat
BEGIN run_stat; END;

*
ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_STATS", line 9136
ORA-06512: at "SYS.DBMS_STATS", line 9616
ORA-06512: at "SYS.DBMS_STATS", line 9800
ORA-06512: at "SYS.DBMS_STATS", line 9854
ORA-06512: at "SYS.DBMS_STATS", line 9831
ORA-06512: at "A.RUN_STAT", line 5
ORA-06512: at line 1

SQL> connect b/b
Connected.

SQL> grant select on t to a;

Grant succeeded.

SQL> connect a/a
Connected.

SQL> exec run_stat

PL/SQL procedure successfully completed.

There secret is:  SQL> grant select on t to a;

HTH,

Mark

June 20, 2003 gaurang -- Thanks for the question regarding
      "Query", version 8.1.6

      You Asked
Hi Tom,

 I am running below query.Tkprof is showing lots of elapsed time on fetch. How
can reduce that?

 The query is using Indexes.

Thanks,

SELECT
      T6.POSITION_ID,
      T22.X_YELLOW_PG_FLG,
      T12.X_RATING,
      T23.PR_POSTN_ID,
      T23.LAST_UPD,
      T23.PR_REP_SYS_FLG,
      T10.ZIPCODE,
      T23.PAR_OPTY_ID,
      T2.STATE,
      T10.X_STREET_NUM,
      T12.X_COMP_INFO_LAST_UPD,
      T12.FST_NAME,
      T22.X_EARLY_CLOSE_STATE,
      T6.ROW_ID,
      T22.X_COMP_INFO_LAST_UPD,
      T3.NAME,
      T9.STATE,
      T23.X_CONFIRMATION_TEMPLATE,
      T5.LAST_NAME,
      T23.X_STREET_NUM,
      T22.X_FIND_LAW_AD_TYPE,
      T12.X_YELLOW_PG_FLG,
      T12.FST_NAME,
      T23.CREATED_BY,
      T1.NICK_NAME,
      T10.COUNTRY,
      T13.NAME,
      T19.ATTRIB_07,
      T12.ROW_ID,
      T22.X_PAST_DUE_AMT,
      T12.X_ADMIT_YR,
      T23.X_SIGNED_BY,
      T2.X_STREET_NAME,
      T17.ROW_ID,
      T22.X_SUB_STATUS,
      T23.PR_REP_DNRM_FLG,
      T22.PR_BL_ADDR_ID,
      T23.STG_START_DT,
      T5.FST_NAME,
      T12.X_PRACTICE_AREA,
      T18.LOGIN,
      T1.LOGIN,
      T22.X_MAIN_PT_FLG,
      T23.CONFLICT_ID,
      T22.X_CRIT_FLAG,
      T12.X_FIND_LAW_AD_TYPE,
      T23.PR_OU_ADDR_ID,
      T2.ROW_ID,
      T22.MAIN_PH_NUM,
      T23.PR_TERR_ID,
      T23.X_EDITION_YEAR,
      T23.PR_REP_MANL_FLG,
      T23.CURR_STG_ID,
      T14.ROW_ID,
      T23.CHANNEL_TYPE_CD,
      T10.ADDR,
      T4.NAME,
      T12.X_YEAR_BORN,
      T23.CREATED,
      T10.COUNTY,
      T8.CAMP_TYPE_CD,
      T22.PR_SHIP_ADDR_ID,
      T23.ROW_ID,
      T23.X_EXPLANATION,
      T9.ZIPCODE,
      T14.NAME,
      T8.X_LIT_ID,
      T12.FAX_PH_NUM,
      T23.X_CLOSED_BY_ID,
      T22.PR_POSTN_ID,
      T12.WORK_PH_NUM,
      T10.STATE,
      T9.COUNTY,
      T23.X_VERBAL_FLG,
      T12.X_ACCNT2_NAME,
      T12.JOB_TITLE,
      T1.X_SALES_REP_NUM,
      T10.X_STREET_NAME,
      T12.X_YELLOW_PG_AD_TYPE,
      T15.NICK_NAME,
      T22.NAME,
      T23.PROG_NAME,
      T12.EMAIL_ADDR,
      T23.X_TARGET_DATE,
      T6.FCST_REVN_CURCY_CD,
      T23.X_SUB_TYPE,
      T23.BU_ID,
      T23.DESC_TEXT,
      T6.FCST_REVN,
      T21.NICK_NAME,
      T23.PR_SRC_ID,
      T23.CURCY_CD,
      T22.LOC,
      T10.CITY,
      T23.X_ORDER_STATUS,
      T23.SALES_METHOD_ID,
      T23.X_LAST_CHANGES_DATE,
      T22.MAIN_FAX_PH_NUM,
      T6.ROW_STATUS,
      T23.X_COUNTY,
      T23.BDGT_AMT,
      T23.REASON_WON_LOST_CD,
      T23.NAME,
      T2.ZIPCODE,
      T12.X_OTHER_ADS,
      T9.CITY,
      T22.PR_BL_PER_ID,
      T22.X_YELLOW_PAGE_HEADERS,
      T22.X_FIND_LAW_FLG,
      T8.NAME,
      T23.X_ERLST_FIELD_CLOSING,
      T23.X_OPTY_STATE,
      T22.PR_ADDR_ID,
      T6.COMMITTED_FLG,
      T22.X_MAIN_EMAIL,
      T23.MODIFICATION_NUM,
      T12.X_PRACTICE_GROUP,
      T12.LAST_NAME,
      T23.X_COUNTRY,
      T23.X_ZIPCODE,
      T23.X_ORDER_STATUS_LAST_UPD,
      T23.X_CYCLE,
      T2.COUNTY,
      T23.X_STATE_CLOSING_DATE,
      T22.ROW_ID,
      T20.OU_ID,
      T22.INTEGRATION_ID,
      T23.X_STATUS_CHG_DATE,
      T12.PR_PER_ADDR_ID,
      T23.X_PREVIOUS_REVENUE,
      T14.OU_ID,
      T22.X_ACCNT_LEVEL,
      T23.ACTL_CLS_DT,
      T10.ROW_ID,
      T6.FCST_PROB,
      T23.X_OPTY_CITY,
      T12.X_FIND_LAW_FLG,
      T14.PR_EMP_ID,
      T2.CITY,
      T4.STG_ORDER,
      T6.PRIORITY_FLG,
      T15.PR_POSTN_ID,
      T9.COUNTRY,
      T6.ASGN_SYS_FLG,
      T22.X_YELLOW_PAGE_AMT,
      T9.ADDR,
      T23.PR_REP_ASGN_TYPE,
      T11.NAME,
      T2.X_STREET_NUM,
      T22.X_OTHER_ADS,
      T22.PR_SHIP_PER_ID,
      T12.X_YELLOW_PAGE_HEADERS,
      T14.ROW_ID,
      T23.LAST_UPD_BY,
      T2.ADDR,
      T23.PR_CON_ID,
      T23.PR_DEPT_OU_ID,
      T5.JOB_TITLE,
      T4.STAGE_STATUS_CD,
      T22.X_YELLOW_PG_AD_TYPE,
      T16.DESC_TEXT,
      T12.X_BIOG_DATA,
      T16.ROW_ID,
      T17.PR_EMP_ID,
      T12.X_YELLOW_PAGE_AMT,
      T23.X_SOURCE,
      T23.OPTY_CD,
      T6.OPTY_BU_ID,
      T12.X_ATTORNEY_FLG,
      T14.PR_TERR_ID,
      T23.X_ADDR,
      T22.X_CREDIT_STATUS,
      T2.COUNTRY,
      T23.X_SENT_DATE,
      T12.LAST_NAME,
      T6.FCST_CLS_DT,
      T12.X_MIDDLE_NAME,
      T8.ROW_ID,
      T23.ASGN_USR_EXCLD_FLG,
      T23.PR_OU_INDUST_ID,
      T6.ASGN_MANL_FLG,
      T23.X_AUTH_BY_ID,
      T23.X_ERLST_LAST_CHANGES,
      T23.X_STREET_NAME,
      T23.PR_CMPT_OU_ID
   FROM
       SIEBEL.S_EMPLOYEE@MHTRAIN1.WORLD T1,
       SIEBEL.S_ADDR_ORG@MHTRAIN1.WORLD T2,
       SIEBEL.S_ORG_INT@MHTRAIN1.WORLD T3,
       SIEBEL.S_STG@MHTRAIN1.WORLD T4,
       SIEBEL.S_CONTACT@MHTRAIN1.WORLD T5,
       SIEBEL.S_OPTY_POSTN@MHTRAIN1.WORLD T6,
       SIEBEL.S_POSTN_RPT_REL@MHTRAIN1.WORLD T7,
       SIEBEL.S_SRC@MHTRAIN1.WORLD T8,
       SIEBEL.S_ADDR_ORG@MHTRAIN1.WORLD T9,
       SIEBEL.S_ADDR_PER@MHTRAIN1.WORLD T10,
       SIEBEL.S_SALES_METHOD@MHTRAIN1.WORLD T11,
       SIEBEL.S_CONTACT@MHTRAIN1.WORLD T12,
       SIEBEL.S_ORG_INT@MHTRAIN1.WORLD T13,
       SIEBEL.S_POSTN@MHTRAIN1.WORLD T14,
       SIEBEL.S_EMPLOYEE@MHTRAIN1.WORLD T15,
       SIEBEL.S_LIT@MHTRAIN1.WORLD T16,
       SIEBEL.S_POSTN@MHTRAIN1.WORLD T17,
       SIEBEL.S_EMPLOYEE@MHTRAIN1.WORLD T18,
       SIEBEL.S_ORG_EXT_X@MHTRAIN1.WORLD T19,
       SIEBEL.S_POSTN@MHTRAIN1.WORLD T20,
       SIEBEL.S_EMPLOYEE@MHTRAIN1.WORLD T21,
       SIEBEL.S_ORG_EXT@MHTRAIN1.WORLD T22,
       SIEBEL.S_OPTY@MHTRAIN1.WORLD T23
   WHERE
      T23.X_AUTH_BY_ID = T5.ROW_ID (+) AND
      T15.PR_POSTN_ID = T20.ROW_ID (+) AND
      T23.X_CLOSED_BY_ID = T15.ROW_ID (+) AND
      T20.OU_ID = T13.ROW_ID (+) AND
      T22.PR_ADDR_ID = T9.ROW_ID (+) AND
      T23.PR_CON_ID = T12.ROW_ID (+) AND
      T23.PR_DEPT_OU_ID = T22.ROW_ID (+) AND
      T23.PR_DEPT_OU_ID = T19.ROW_ID (+) AND
      T23.SALES_METHOD_ID = T11.ROW_ID (+) AND
      T23.CURR_STG_ID = T4.ROW_ID AND
      T23.CREATED_BY = T18.ROW_ID (+) AND
      T23.PR_SRC_ID = T8.ROW_ID (+) AND
      T22.PR_ADDR_ID = T2.ROW_ID (+) AND
      T12.PR_PER_ADDR_ID = T10.ROW_ID (+) AND
      T22.PR_POSTN_ID = T17.ROW_ID (+) AND
      T17.PR_EMP_ID = T21.ROW_ID (+) AND
      T23.PR_POSTN_ID = T14.ROW_ID AND
      T23.PR_POSTN_ID = T6.POSITION_ID AND T23.ROW_ID = T6.OPTY_ID AND
      T14.OU_ID = T3.ROW_ID AND
      T14.PR_EMP_ID = T1.ROW_ID (+) AND
      T8.X_LIT_ID = T16.ROW_ID (+) AND
      T6.POSITION_ID = T7.SUB_POSTN_ID AND
      ((T4.NAME > '0-On Hold' AND(T23.OPTY_CD ='Renewal' OR T23.OPTY_CD =
'Upsell')) AND (T4.STAGE_STATUS_CD = 'Open')
        AND (T7.POSITION_ID ='1-ALR7'))
   ORDER BY
      T7.POSITION_ID

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.06       2.09          0          0          0           0
Execute      1      0.00       4.52          0          0          0           0
Fetch        3      0.04      36.36          0          0          0          18
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      0.10      42.98          0          0          0          18

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 64
********************************************************************************

      and we said...
read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6749454952894
and keep repeating:

"indexes are not goodness incarnate, full table scans are not evil personified"
"indexes can slow me way down, full table scans can make me go faster"

over and over and over. (and remember the difference between CAN and WILL, I
used CAN, as in might, not WILL, as in it is a sure thing)

I suspect it is becuase the tables are owned by SIEBEL (that's a joke).

Seriously, without supporting information like, oh, a full tkprof with the
query, current, filled in -- the query plan with row counts flowing through each
step -- one cannot say.

(but I hate it when questions have "the query is making full use of indexes, so
why is it slow?".... ugh -- ancient greek mythology at work full time)

But basically, when elapsed > cpu by alot, you have waits.  If you have my book,
I show how to use the 10046 event in chapter 10 to capture bind variables and
waits into the trace file, you can then review the trace file to see what the
waits are -- if you see db file sequential reads -- those are your indexes at
work and you are waiting on physical IO's from them and the table.

Review &
      Followup

      Rating:
      5
  June 19, 2003
Reviewer:
      A reader
Hi Tom,

 I Have this query and performace is horrible...

 s_camp_con_vw    has 1.6 Million rows
 contact_info_vw  has 1.5 Million Rows
 s_addr_per_vw    has 1.8 Million Rows

 campaign_info_vw  has only 400 Rows

 We are using siebel and we have created above views on siebel base tables(one
view referes to only one table).

 Siebel use RULE base optimizer..

 I would appricate any help.

  1  SELECT /*+ full(camp) */ COUNT(*)
  2    FROM s_camp_con_vw cc,contact_info_vw cont,s_addr_per_vw addr,
  3  campaign_info_vw camp
  4  WHERE camp.row_id = cc.src_id AND cont.row_id = cc.con_per_id
  5* AND addr.row_id = cont.pr_per_addr_id AND name = 'XSL3S223' AND state =
'CA'
SQL> /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=RULE (Cost=49 Card=1 Bytes=88)
   1    0   SORT (AGGREGATE)
   2    1     HASH JOIN (Cost=49 Card=1600 Bytes=140800)
   3    2       TABLE ACCESS (FULL) OF 'S_SRC' (Cost=2 Card=20 Bytes=7
          20)

   4    2       HASH JOIN (Cost=46 Card=8000 Bytes=416000)
   5    4         NESTED LOOPS (Cost=42 Card=400 Bytes=13600)
   6    5           TABLE ACCESS (BY INDEX ROWID) OF 'S_ADDR_PER' (Cos
          t=2 Card=20 Bytes=320)

   7    6             INDEX (RANGE SCAN) OF 'S_ADDR_PER_M2' (NON-UNIQU
          E) (Cost=1 Card=20)

   8    5           TABLE ACCESS (BY INDEX ROWID) OF 'S_CONTACT' (Cost
          =2 Card=2000 Bytes=36000)

   9    8             INDEX (RANGE SCAN) OF 'S_CONTACT_PR_PER_ADDR_ID_
          X' (NON-UNIQUE) (Cost=1 Card=2000)

  10    4         INDEX (FAST FULL SCAN) OF 'S_CAMP_CON_U1' (UNIQUE) (
          Cost=3 Card=2000 Bytes=36000)

Statistics
----------------------------------------------------------
         36  recursive calls
        222  db block gets
     599221  consistent gets
      89091  physical reads
          0  redo size
        206  bytes sent via SQL*Net to client
        311  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          4  sorts (memory)
          0  sorts (disk)
          1  rows processed

          Followup:

Hey, my car won't start -- why not?

NOT that I want you to put this information here (ask it as a question maybe...)
but.....

well, you obviously have some view action going on -- but boy, we don't even get
to see them....

why would you use a HINT with the query?  Without stats -- (assuming no stats
since you said siebel is using the rbo) -- you basically have to HINT the entire
query.  You do notice that all your hint did was invoke the CBO, it "ignored"
however.  perhaps you should drop that hint.

you say "its horrible" but give no timing information and no expectations of
what you believe is "reasonable"

you have no tkprofs....

NOT that I want you to put that here -- please don't -- but think about what one
would need to actually "lay hands on" and tune something....

June
      20, 2003 Tarun -- Thanks for the
      question regarding "Locally Managed Tablespaces - LMT", version 8.1.6

      You Asked
Hi,

OK you might say another one of those LMT questions, I have been thru most of
the questions and responses on LMT, just a  few more additional questions before
I go ahead to migrate all my tablespaces to LMT.

1) I can use dbms_space to migrate my tablespace to LMT's. Question is is there
an added advantage if I export the data and re-create all the tablespaces to
LMT's and then import the data compared to just migrating the existing ones to
LMT's using the package dbms_space.

Thanks

Tarun

      and we said...
1) I would not migrate as you won't have equisized extents.  It would be a good
first step but you would still have just as many fragments tomorrow as you did
yesterday.

I would not export and import either.  I would MOVE and REBUILD the data.  See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:47812348053
for what I mean by that.  You would want to customize that script to use storage
parameters that are meaningful for your objects and you'll want to move objects
to tablespaces based on their SIZE -- not their contents (have 3 or 4
tablespaces for tiny, small, med and large objects for example)

Review &
      Followup

      Rating: 4
Great
      stuff..  November 20, 2001
Reviewer:
      A reader
I will size the objects and can now proceed with moving the objects with greater
confidence..

Thanks

      Rating: 4
System
      tablespace  June 19, 2003
Reviewer:  Charlie

Hi Tom,

I just upgraded my database from 8.1.7 to 9.2.0.1.0.  Upon upgrade commpletion,
I noticed that the system tablespace is still DMT and I want to change it to
LMT.  Is there any CLEAN way to do that?  Do you have any scripts or any doc I
can refer to?

thanks!

          Followup:

you would want to rebulid the database if you want to do that.

do you have a really good technical reason for doing so?  In production, system
should not be growing much, if at all?  Me, I would leave well enough alone
personally.

June 20, 2003 Monica -- Thanks for the question regarding "High
      Water Mark", version 8.1.5

      You Asked
Hello Tom,

I have gone through with Oracle documentation and some of your article and
question/Answer about High Water Mark. I am kind of confuced with HWM.

According to my knowledge, "all Oracle segments have an upper boundary
containing the data within the segment. This upper boundary is called the "high
water mark" or HWM". That means table can not contain data beyond the HWM (If
not correct, please make me correct), if this is correct and we are determining
high water mark with help of this query:
SELECT blocks, empty_blocks, num_rows
FROM user_tables
WHERE table_name = <tablename>;
Where,
1. BLOCKS represents the number of blocks 'ever' used by the segment.
2. EMPTY_BLOCKS represents only the number of blocks above the 'HWM'.
Now if according to HWM defination we can not go beyond HWM, then:

Question: What is EMPTY_BLOCKS represents.
Question: How to determine the HWM, please give me an example.
Question: For what all object HWM is possible?
Question: How to determine real space used by a table? (Below the HWM)
and I was using:
SELECT COUNT(DISTINCT SUBSTR(rowid,15,4)||SUBSTR(rowid,1,8)) "Used"
FROM schema.table;
or
SELECT COUNT (DISTINCT
DBMS_ROWID.ROWID_BLOCK_NUMBER(rowid)||DBMS_ROWID.ROWID_RELATIVE_FNO(rowid))
"Used"
FROM schema.table;
or
SELECT COUNT (DISTINCT SUBSTR(rowid,1,15)) "Used"
FROM schema.table;
or
but all of three queries are giving different-2 results. Could you please also
through some focus on this.

THX
Monica

      and we said...
q1)empty_blocks are blocks allocated to the table but that have NEVER contained
any data.  When you create a table before putting any data into it -- all blocks
are empty.  Add 1 row and at least one block will no longer be empty.

q2)

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:231414051079

q3) makes no sense to me.

q4) see q2

YOU CANNOT count rowids.  that shows you the HEAD rowid but rows can span blocks
and rows can migrate.

follow up to comment one

Sandip -- the person asking the question did not ask for a defintion of HWM --
they PROVIDED IT.  I quote from the question:

all Oracle segments have an upper boundary containing the data within the
segment. This upper boundary is called the "high water mark" or HWM". That means
table can not contain data beyond the HWM

So, i would say that the defintion is in fact NOT lacking, it just wasn't part
of the answer and wasn't asked for in the question.

follow up to comment two

Monica -- I would have to say that I'm really confused.  You yourself defined
HWM in the question.  Ok, I'll give you a definition using different words.

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1445404373378
has one way of saying it.

Another is way is to consider a SEGMENT to be a set of blocks (these blocks are
collected together into extents).  Say you create a segment (initially totally
empty).  It might look like this:

Oeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee

where O = block used for Overhead (oracle used that block for space management)
      e = empty block, block has NEVER contained data.

Now, you insert 1,000 rows into the segment.  It now looks like this:

Oddddddddddddddddddddddddddddeeeeeeeeeeeeeeeeee

where d = block with data on it.  The set of blocks with d's on them are the set
of blocks under the HWM -- the HWM is the set of blocks that have contained
data.  Now I delete 1,000 rows from the table.  it will now look like this:

Oddddddddddddddddddddddddddddeeeeeeeeeeeeeeeeee

that is -- it is the same.  Just like in a flood -- a HWM can only be advanced.
A HWM never decreases for an object -- you have to in effect recreate the object
to decrease the HWM.  So a truncate table would do this:

Oeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee

The following example shows this "numerically".  see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:231414051079
for the show_space routine:

tkyte@TKYTE816> create table t ( x int, y varchar2(4000) );
Table created.

tkyte@TKYTE816> exec show_space( 'T' )
Free Blocks.............................0
Total Blocks............................64
Total Bytes.............................524288
Unused Blocks...........................63
Unused Bytes............................516096
Last Used Ext FileId....................6
Last Used Ext BlockId...................1609
Last Used Block.........................1

PL/SQL procedure successfully completed.

so the table has 64 blocks.  There are 63 unused blocks (blocks ABOVE the
HWM).  One block the the "overhead" block used by Oracle.  The remaining 63
blocks are above the HWM.  Oracle knows these blocks contain NO data whatsoever
(it will not scan them in a full scan for example)

Now, lets fill up the table:

tkyte@TKYTE816> insert into t select 1, rpad('*',4000,'*') from all_objects
  2  where rownum < 51;

50 rows created.

tkyte@TKYTE816> exec show_space( 'T' )
Free Blocks.............................1
Total Blocks............................64
Total Bytes.............................524288
Unused Blocks...........................13
Unused Bytes............................106496
Last Used Ext FileId....................6
Last Used Ext BlockId...................1609
Last Used Block.........................51

PL/SQL procedure successfully completed.

Now, there are only 13 blocks ABOVE the HWM.  There are 51 blocks BELOW the
HWM (blocks that will be scanned in the event of a full scan).

Now, what happens when we delete from T?

tkyte@TKYTE816> commit;
Commit complete.

tkyte@TKYTE816> delete from t;
50 rows deleted.

tkyte@TKYTE816> commit;
Commit complete.

tkyte@TKYTE816> exec show_space( 'T' )
Free Blocks.............................50
Total Blocks............................64
Total Bytes.............................524288
Unused Blocks...........................13
Unused Bytes............................106496
Last Used Ext FileId....................6
Last Used Ext BlockId...................1609
Last Used Block.........................51

PL/SQL procedure successfully completed.

Notice how the HWM does not move - we still have 51 blocks under the HWM.
However, notice how there are 50 blocks on the freelist (ever block that can
contain data is now on a freelist for the table).  The HWM does not retreat but
space is reclaimed.  If we did a full scan of this object -- 50 data blocks
would be scanned since they MIGHT contain data.

Now, we truncate:

tkyte@TKYTE816> truncate table t;

Table truncated.

tkyte@TKYTE816> exec show_space( 'T' )
Free Blocks.............................0
Total Blocks............................64
Total Bytes.............................524288
Unused Blocks...........................63
Unused Bytes............................516096
Last Used Ext FileId....................6
Last Used Ext BlockId...................1609
Last Used Block.........................1

PL/SQL procedure successfully completed.

and the storage is back to the way it was in the beginning.  the HWM has
retreated -- 63 blocks are ABOVE the HWM.

Review &
      Followup

      Rating: 2
What is HWM
      ....  May 18, 2001
Reviewer:  Sandip
       from Cincinnati, OH
Still the definition of HWM is lacking.

      Rating:
      2
HWM  May 20, 2001
Reviewer:  Monica,
      oraclequeries@hotmail.com  from Holand
High Water Mark defination itself is missing...and required, please provide if
you can.

      Rating:
      2
HWM  November 19, 2001
Reviewer:
      vivek  from NY, New York U.S.A.
What I understand about HWM (INFORMAL) is that it is a mark
to identify how may blocks had data at least once.

 This mark will be changed/refereshed only if

1.) Table is dropped
2.) Table is truncated

or
  More data is inserted ( HWM will Increase )

Thanks,
Vivek

      Rating: 4
I
      See  December 26, 2001
Reviewer:
      A reader
excellent explanation!

      Rating: 4
Good example
      code showing High Water Mark  February 21, 2002

Reviewer:  Chris Barr  from Captiva Island, FL

Thanks, Tom, for 3-step example showing query results before & after Delete and
Truncate ...
I believe I finally "got it" on High Water Mark.

      Rating: 5
Crystal
      Clear  September 05, 2002
Reviewer:  Subhro
       from Mumbai, India
HWM was always something that evaded my understanding. Today, ran a search on
this site on HWM and now I am HWM aware. Thanks...

In fact, I wanted to ask you all sorts of questions but as I was writing them, I
answered all of them... myself...

I wonder if other databases have similar concepts???

      Rating:
      4
Reader  October 14, 2002
Reviewer:
      A reader
Tom,

RDBMS 8.1.6
DB_BLOCK_SIZE 8192
1. I created a table ( 1st extent 1M next 1M  )
2. Inserted 100+ rows
3. Analyzed table
   BLOCKS = 2 EMPTY = 127
4. From DBA_EXTENTS
   BLOCKS = 130
   1 for OS , 1 for segment header, 128 for segments
5. value for '_bump_highwater_mark_count' = 0 , default of 5
6. show_space shows
   freelist = 1

I expected that 5 blocks are put in the freelist ( default for hwm)
every time Oracle bumps the HWM There is only one on the freelist.
Could you explain for us

Thanks

SQL> create  table t_r (x varchar2(2000)) tablespace RPTSVR
  2  storage
  3  ( initial 1m
  4    next 1m
  5    pctincrease 0
  6  );

Table created.

SQL> begin
  2  for k in 1..111
  3  loop
  4  insert into t_r values (rpad('*',100,'*'));
  5  end loop;
  6  commit;
  7  end;
  8  /

PL/SQL procedure successfully completed.

SQL> analyze table t_r compute statistics;

Table analyzed.

SQL> select TABLE_NAME,BLOCKS,EMPTY_BLOCKS
from dba_tables where table_name = 'T_R';

TABLE_NAME                         BLOCKS EMPTY_BLOCKS
------------------------------ ---------- ------------
T_R                                     2          127

SQL> select EXTENT_ID,FILE_ID,BLOCK_ID,BYTES,BLOCKS
     from dba_extents where SEGMENT_NAME = 'T_R';

 EXTENT_ID    FILE_ID   BLOCK_ID      BYTES     BLOCKS
---------- ---------- ---------- ---------- ----------
         0         19          2    1064960        130

SQL> select KSPPSTVL from x$ksppcv, x$ksppi
where x$ksppi.INDX = x$ksppcv.INDX and
ksppinm = '_bump_highwater_mark_count';

KSPPSTVL
----------------------------------------------------
0

SQL> exec show_space('T_R');
Free Blocks.............................1
Total Blocks............................130
Total Bytes.............................1064960
Unused Blocks...........................127
Unused Bytes............................1040384
Last Used Ext FileId....................19
Last Used Ext BlockId...................2
Last Used Block.........................3

PL/SQL procedure successfully completed.

          Followup:

what is this _bump_blah blah blah thing.  did support ask you to use that?  if
so, why?  I've never heard of them asking for someone to use that.

Since its meaning is 100% not relevant, we'll ignore its very existence.

but anyway -- your test doesn't test what you wanted to test.  It could very
well have bumped the HWM by 5 -- you filled and used 4, leaving one there.  You
would have to now iteratively add a row, commit, measure, add a row, commit,
measure -- to force it to advance the HWM and see what happens THEN.

      Rating: 5
Tom, Thanks
      very Much  October 14, 2002
Reviewer:
      A reader
SQL>  exec show_space('T_R');
Free Blocks.............................1
Total Blocks............................128
Total Bytes.............................1048576
Unused Blocks...........................122
Unused Bytes............................999424
Last Used Ext FileId....................5
Last Used Ext BlockId...................6881
Last Used Block.........................6

PL/SQL procedure successfully completed.

SQL>  insert into t_r values (rpad('*',1000,'*'));

1 row created.

SQL> commit;

Commit complete.

SQL>  exec show_space('T_R');
Free Blocks.............................5
Total Blocks............................128
Total Bytes.............................1048576
Unused Blocks...........................117
Unused Bytes............................958464
Last Used Ext FileId....................5
Last Used Ext BlockId...................6881
Last Used Block.........................11

PL/SQL procedure successfully completed.

SQL> select count(*) from t_r;

  COUNT(*)
----------
        36

It seems that the first 5 blocks Oracle allocates 1 block
at a time and subsequent to first 5, bumps 5 blocks in the
freelist for HWM

Thanks

          Followup:

Ummm, disagree:

SQL>  exec show_space('T_R');
Free Blocks.............................1
Total Blocks............................128
Total Bytes.............................1048576
Unused Blocks...........................122

128 blocks - 122 Unused blocks = 6 blocks "used".  1 block is for the segment
header which means there were 5 blocks in the table.  You filled up 4 of all of
the way with data and only filled part of the 5th block.

It was *always* 5 blocks.

      Rating:
      5
  February 12, 2003
Reviewer:  p001

One of my packages truncates a table “A” once per every 2 hours and then inserts
and updates the table with the required data.

1. Truncate table A;

2. Insert into table A;

3. Update table A:

4. No delete statements.

The current size of the table is 340 MB. When I create a copy of this table
using

Create table B nologging as select * from A;

Then the target table “B” size is around 24 MB.

Why my original table size was 15 times higher then actual size? How to fix this
problem?

Please excuse me if it’s a basic question.

          Followup:

The original storage parameters of A were such that the initial, next,
pctincrease, minextents said "Table A should allocate 340 mb"

The storage parameters of B are different.  Consider:

ops$tkyte@ORA817DEV> create table a ( x int ) tablespace users storage ( initial
340m );
Table created.

ops$tkyte@ORA817DEV> select sum(bytes) from user_extents where segment_name =
'A';

SUM(BYTES)
----------
 357040128

ops$tkyte@ORA817DEV> create table b tablespace users as select * from a;
Table created.

  1* select sum(bytes) from user_extents where segment_name = 'B'
ops$tkyte@ORA817DEV> /

SUM(BYTES)
----------
    524288

so, look at A's initial, next, pctincrease, minextents -- that'll tell you what
truncate will take it back to.

      Rating:
      5
  February 13, 2003
Reviewer:
      A reader
Thanks for your quick answer.

I verified the initial extents for both Table A and B and it was 65536.

Here are the DDL’s for my tables

1.
CREATE TABLE A (
  Col1                         NUMBER (18),
  Col2                     NUMBER (18),
  Col3                    NUMBER (18),
  Col4                   VARCHAR2 (4000),
  Col5      VARCHAR2 (4000),
    ... –other columns
    ...
)
   TABLESPACE TOOLS NOLOGGING
   PCTFREE 10
   PCTUSED 40
   INITRANS 1
   MAXTRANS 255
  STORAGE (
   INITIAL 65536
   NEXT 65536
   PCTINCREASE 0
   MINEXTENTS 1
   MAXEXTENTS 10000
   FREELISTS 1 FREELIST GROUPS 1 )
   NOCACHE;

2.
CREATE TABLE B (
  Col1                         NUMBER (18),
  Col2                     NUMBER (18),
  Col3                    NUMBER (18),
  Col4                   VARCHAR2 (4000),
  Col5      VARCHAR2 (4000),
    ... –other columns
    ...
)
   TABLESPACE TOOLS NOLOGGING
   PCTFREE 10
   PCTUSED 40
   INITRANS 1
   MAXTRANS 255
  STORAGE (
   INITIAL 65536
   NEXT 65536
   PCTINCREASE 0
   MINEXTENTS 1
   MAXEXTENTS 10000
   FREELISTS 1 FREELIST GROUPS 1 )
   NOCACHE;

I am still not sure why table A used 340 mb and table B used 24 MB.

          Followup:

you'll need to give me a full test case with which to reproduce.  Not that I
doubt you but ....

a) truncate by default releases storage (shrinks the table back to its initial
extent or initial + minextents extents -- and since you say minextents is 1....)

b) the insert would not balloon up to 340meg.

So, I think if you study your processs step by step by step -- (to give me a
test case to reproduce with) you'll find what part diverges from what you think
is happening (or we'll have a test case from which to disect and figure out what
is happening)

      Rating: 5
On a similar
      vein....  February 19, 2003
Reviewer:
      A reader  from UK
Hi Tom,

I'm pretty new to the DBA role, so excuse if this question is obvious, but i'm a
bit perplexed by the results of show_space for this setup. I need to know what
you recommend to change for this (relatively old) application storage setup.

SQL> CREATE TABLESPACE TEST DATAFILE 'C:\TEMP\TEST.DBF' SIZE 100M REUSE
autoextend on EXTENT MANAGEMENT LOCAL
  2        UNIFORM
  3        size 100k ONLINE ;

Tablespace created.

SQL> CREATE TABLE TEMP ( A VARCHAR2(10) ) TABLESPACE TEST;

Table created.

Okay, I've created the table. The HWM (as I expect) is 1.

SQL> exec show_space('TEMP');
Shows segment information.
Tablespace..................................................TEST
Tablespace Block Size.......................................8192
Num Extents.................................................1
Total Blocks................................................13
Total Bytes.................................................106496
Last Used Block (HWM).......................................1
Unused Blocks (blocks above the HWM)........................12
Unused Bytes................................................98304
Free Blocks (blocks on the freelist)........................0

PL/SQL procedure successfully completed.

If I now populate with loads of data...

SQL> BEGIN
  2    FOR i IN 1..100000 LOOP
  3      INSERT INTO temp VALUES ('XXXXXXXXXX');
  4    END LOOP;
  5  END;
  6  /

PL/SQL procedure successfully completed.

SQL> EXEC SHOW_SPACE('TEMP');
Shows segment information.
Tablespace..................................................TEST
Tablespace Block Size.......................................8192
Num Extents.................................................18
Total Blocks................................................234
Total Bytes.................................................1916928
Last Used Block (HWM).......................................5
Unused Blocks (blocks above the HWM)........................8
Unused Bytes................................................65536
Free Blocks (blocks on the freelist)........................5

PL/SQL procedure successfully completed.

Can you explain to me why the total blocks for the segment has gone to 234, but
the HWM is still only 5 (or am I reading this wrong)?

Thanks for all your invaluable help.

          Followup:

the unused blocks are the blocks above the HWM
last used block -- is not the "hwm"

the high water mark (number of blocks below the HWM) is total-blocks minus
unused blocks

so, there are 234-8 blocks under the HWM here.

      Rating:
      5
  March 02, 2003
Reviewer:
      A reader
Very informative

      Rating: 3
Why does Oracle
      Maintain a HWM..?  March 26, 2003
Reviewer:
      A reader
We have been reading about HWM, but why does oracle maintain a HWM which is not
the number of blocks currently used. Why does it need it to know the number of
blocks which were ever used by a table..?

          Followup:

Well, quite simply put -- they are the blocks that ARE in use by the table.  We
need that so that if you issue:

select * from T;

we know the set of block which we MUST scan in order to retrieve the data for
you.

      Rating: 1
They are not
      currently in use  March 27, 2003
Reviewer:
      A reader
Hi,
The blocks are not currently in use by that table, The data has been deleted and
also been committed.
Hence while scanning we should scan only the blocks which currently have data,
not necessarily had data at one point of time,
Still can't comprehend why are we scanning the blocks which are not currently in
use. can you please elaborate

Thanks

          Followup:

Hence -- tell me -- how the heck do we know the blocks are empty?

You issue :delete from t;
Whilst another session(s) issues: insert into t

would you like us to lock entire table for delete, preventing any other DML from
occuring.  Cause if we didn't -- we would have to at the end of the delete LOCK
the table (to prevent DML from occuring) and then SCAN entire said table to see
if "oh, did anyone insert while we were deleteing?".

If you would like this behavior -- that's ok, we have it -- it is called
TRUNCATE.

The blocks *are in use*.  The table is a complex data structure, it is not a
fifo/lifo queue or anything like that -- it is a big old heap.  Once used --
implies "could still be used".

      Rating: 2
Still can't
      understand it  March 28, 2003
Reviewer:
      A reader
Hi tom,
I still dont get it, I can understand you getting mad at me, as u have said its
a complex structure not a lifo/fifo methodoly. I can understand the concept if i
can understand why it needs to do that.

If the data has not been committed i can understand the blocks being read, but
if the data has been commited, only those should be read, as others are not in
use.
if we take just 2 session,
1) Delete * from tablename  <table1>
Deletes blocks from 1-25 (hypotetical)

2) Insert into the tablename <table1> select * from table2
This would insert from blocks 26 to 50 (hypotetical).

we have a third session which is doing a
select * from <table1>
if the first delete has not been commited it and insert has been it needs to
scan blocks 1 to 50, but if first delete has been commited then it should just
read blocks 26 to 50 as they are the one in use.

Can you plesase explain a little more in detail.

Thanks for your reply

          Followup:

why -- who is to say that blocks 1..50 do not have any data on them???

We do not micro-manage blocks like that -- think about the overhead.  What you
propose would have us look at a table with a row per block to see if we should
read that block or not.

The database table is an extremely complex data structure.  It is not a simple
thing.  You are thinking it is a very simple thing.

You cannot just take 2 sessions -- you must take worst case, you must take every
case.

We do not delete by block -- we delete by row.  We do not managed space in the
simplistic manner which would allow something like this.

      Rating: 2
Some more info
      pls  March 30, 2003
Reviewer:  A reader

Hi Tom,
We do not need to look at eack row, but the blocks can be read based on the
freelist,I am not trying to give a solution for HWM, but i need to know why do
we ever have something like HWM,what overhead does it reduce and what are the
advantages, as uptill now i hv read more of disadvantages of HWM, can you point
me to any documentation which would gove details about the comple table
structure and why HWM is used.
Since most of my queries have always been answered by you, thats whay i am
invariably depending on you. :)

Thanks in Advance

          Followup:

That a block is on the freelist by no means "it is empty".  It just means it has
"some space".  A block on the freelist typically (more often then not) HAS data
on it.

Once a block is formatted and used by the table -- it must be assumed that it
contains data -- for there is nothing preventing it from having data.

Now, bear in mind -- what does this affect?  Full scans only (has NO relevance
for index reads whatsoever).  So, how many times do you full scan -- or better
yet, what type of systems full scan?  Data warehouses.  Do they suffer from this
issue?  Most generally "no".   the DW is filled typically by:

o appending, never delete
o rolling windows with partitions (aging of data, no deletes)
o merging in data (update existing add new) -- no delete

So the systems that would be affected by this are not.

Again, we are back to "if you are going to empty the table, and you want to let
oracle know decisively 'these blocks do not contain data', then TRUNCATE is the
command you are looking for".

Just because stuff is on the freelist for a table does not mean "empty".  We
have to look at them during a full scan or risk giving you the wrong answer.

If you have my book - I go over the physical structure of tables, blocks,
extents, etc.  The concepts guide does as well.  Short of dumping the blocks and
reverse engineering them (not somthing I'm recommending bear in mind) thats it.

Think of the advantages during DML -- think of the advantages during a full scan
even.  What if every other block was "empty" and not to be read?  Think what
that would do to a full scan (the thought of a manual transmission car being
driven by someone who has only used automatic transmissions comes to mind --
go/stop/go/stop/go/stop).  You would need two freelists -- "sort of empty and
truly 100% empty".  You would need to micromanage blocks -- you would have to
consult this "truly 100% empty" freelist (think of them as rows) and subtract
them from your list of blocks to scan -- instead of "scan extent 5", you would
have to do "scan extent 5 except for these 1,000 scattered blocks".

      Rating: 4
Why so much
      discussion on HWM?  March 31, 2003
Reviewer:
      John  from San Jose, CA, USA
Hi Tom,

Why do so many people worry about HWM? Is it just for mental exercise to satisfy
one's curiosity or does knowing it have more practical use than I am aware of?
All I care to know is that full table scan will scan till HWM. I don't recall
ever have to manipulate HWM.

          Followup:

It is probably just to gain understanding.  Many people apply access or dbase
concepts to Oracle and that just doesn't work -- apples and toaster over
comparison.

In general, you are correct -- all you need know is

a) there is a HWM
b) full scans read up to the high water mark

and maybe

c) this becomes most obvious to people running the RBO who like to constantly
issue "select count(*) from t" just to see how many rows this table might
contain".  They will constantly full scan upto the HWM -- they should use the
CBO which will index full/fast full scan instead -- removing this problem (or
just stop counting the rows!)

      Rating:
      4
  March 31, 2003
Reviewer:  Tim
       from alpharetta, ga
"c) this becomes most obvious to people running the RBO who like to constantly
issue "select count(*) from t" just to see how many rows this table might
contain".  They will constantly full scan upto the HWM -- they should use the
CBO which will index full/fast full scan instead -- removing this problem (or
just stop counting the rows!) "

How is the index full/fast full scan better? examples please.

          Followup:

big_table@ORA920> set autotrace on
big_table@ORA920> select count(*) from big_table;

  COUNT(*)
----------
   3817191

1 row selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=806 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FAST FULL SCAN) OF 'BIG_TABLE_PK' (UNIQUE) (Cost=806
Card=3816192)

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

big_table@ORA920> select /*+ RULE */ count(*) from big_table;

  COUNT(*)
----------
   3817191

1 row selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE'

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

big_table@ORA920> set autotrace off
big_table@ORA920>

      Rating: 2
Probably thats
      what you require but everone requirement is diff  March 31,
      2003
Reviewer:  A reader
Hi Tom, Rather John

"Why do so many people worry about HWM? Is it just for mental exercise to
satisfy
one's curiosity or does knowing it have more practical use than I am aware of?
All I care to know is that full table scan will scan till HWM. I don't recall
ever have to manipulate HWM. "
The full table scan you must be performing for small rows
try performing Full table scans for tables with millions of Rows, When it comes
to tunning a Database i think even a few seconds make a difference.
"Yes its faster then before the data does come up quickly now", A lot of user
will give you this reaction even if the data just comes quickly by a few
seconds, and as a proper learning process, i like to know why is it being done
so that we can try and exploit it in a better way, rather then half of
knowledge.

More over what will be ur reaction will be when you try to explain BA's and
Project managers that its doing a Full table scan and the insert statement is
writting above HWM as we have used a Append hint to increase the performance of
upload.All they will ask is Why is HWM maintained, what do you do?  you raise
your two hands in air and say dont know ask oracle. I certainly dont like to do
that.

Thanks tom for the info

      Rating: 5
HWM on
      tablespaces  May 14, 2003
Reviewer:  Trevor

Tom there is a script on www.oracle-base.com
used to rebuild indexes it talks about a high water mark
on tablespaces..

"Rebuilding indexes within the tablespace raises the high water mark on the
tablespace. This chews disk space which cannot be easily recovered. Unix)."

I know about table HWM but what the heck is a tablspace
HWM and what are it's impacts

          Followup:

say you have a tablespace with no freespace (but autoextend datafiles)

it is 500 meg right now.  You could say the HWM for the tablespace is 500 meg
(you cannot shrink it any smaller)

Now, you rebuild a 10meg index in there.  For a moment -- you need 510 meg of
space (the index exists twice).  So the tablepsace grows to 510 -- the index
moves to the "end" of the tablespaces files (it'll cause them to grow)

When you are done, you have 500meg used but 510meg of space allocated.  You
cannot shrink it down since the new index is at the end of the files -- the
empty space is interior space in the datafiles, they cannot shrink.

you can think of that as if the tablespaces HWM was advanced to 510m

But not really, cause you could rebuild that index again -- it might move back
to the original space and you could shrink the files (so it is a HWM that can go
up and go down)

But since rebuilding indexes is mostly a waste of our time -- it is really not
too much of an issue -- is it. search for

index rebuild

on this site to read my opinion (backed up with some math) about scheduled
rebuilds of indexes.

      Rating: 5
Thanks
      Tom  May 15, 2003
Reviewer:  Trevor Welch

Thanks very much Tom
for the clear explainantion on Tablespace HWM

i will search for index rebuilds now as you suggested

Kind Regards Trevor

      Rating: 2
Delete and
      Insert  June 19, 2003
Reviewer:
      A reader
I have a table which does some constant deletes and inserts, as this is the
table we store the ids to which the users have access based on their
profile,like user x has access to all ids or all of these 10,000 ids, We need to
populate the table everytime the user logds in, Delete on this table is taking
quite some time as the number of rows per user go upto .4 million.
Is there a way to delete without redo or speed up the insert and delete, i am
currently using bulk insert, is bulk delete faster, i cant use truncate as the
other users maybe using the table, dont want to partiion and truncate as the
user creation is thru an application. Any ideas.

          Followup:

why would you not use a global temporary table with on commit preserve rows?

each user gets their own segment.
upon logging out the data "disappears"
a select * from that table would only show that user their data

      Rating:
      1
  June 19, 2003
Reviewer:
      A reader
its a web application wher the user does not necessary get that account and it
has connection pooling implemented in app server to ensur the connections are
used up rationally

          Followup:

then I guess that design decision precludes other design decisions.

We let state tables like that (use them on asktom) fill up and run a daily batch
to clean out old entries in the background.  it should be a SINGLE delete like:

delete from state_table where ...... (something to identify the rows to delete)

that is the fastest.

There is NO way to disable redo and undo generation for deletes.

June 20, 2003 Sajid -- Thanks for the question regarding
      "Import And Export", version 9.0.1

      You Asked
Hi Tom,

I am trying to do some basic imp/exp stuff referring your Oracle Expert book.
This is sequence what i did.

SQL> connect scott/tiger;

SQL> drop table t;

SQL> create table t as select rownum id,object_name from all_objects where
rownum<=1000;

D:\oracle\ora90\BIN>exp userid=scott/tiger tables=t file=(t1,t2) filesize=10k

Export: Release 9.0.1.1.1 - Production on Mon Aug 12 22:03:58 2002

(c) Copyright 2001 Oracle Corporation.  All rights reserved.

Connected to: Oracle9i Enterprise Edition Release 9.0.1.1.1 - Production
With the Partitioning option
JServer Release 9.0.1.1.1 - Production
Export done in WE8MSWIN1252 character set and AL16UTF16 NCHAR character set

About to export specified tables via Conventional Path ...
. . exporting table                              T
continuing export into file t2.DMP

Export file: EXPDAT.DMP > t3

continuing export into file t3.DMP

Export file: EXPDAT.DMP > t4

continuing export into file t4.DMP

Export file: EXPDAT.DMP > t5

continuing export into file t5.DMP
       1000 rows exported
Export terminated successfully without warnings.

SQL> select count(*) from t;

  COUNT(*)
----------
      1000

SQL> drop table t;

Table dropped.

SQL> commit;

Commit complete.

SQL> select count(*) from t;
select count(*) from t
                     *
ERROR at line 1:
ORA-00942: table or view does not exist

D:\oracle\ora90\BIN>imp userid=scott/tiger full=y file=(t1,t2,t3,t4,t5)

Import: Release 9.0.1.1.1 - Production on Mon Aug 12 22:07:50 2002

(c) Copyright 2001 Oracle Corporation.  All rights reserved.

Connected to: Oracle9i Enterprise Edition Release 9.0.1.1.1 - Production
With the Partitioning option
JServer Release 9.0.1.1.1 - Production

Export file created by EXPORT:V09.00.01 via conventional path
import done in WE8MSWIN1252 character set and AL16UTF16 NCHAR character set
IMP-00046: using FILESIZE value from export file of 10240
. importing SCOTT's objects into SCOTT
. . importing table                            "T"       1000 rows imported
Import terminated successfully with warnings.

SQL> select count(*) from t;

  COUNT(*)
----------
      1000

SQL>

My question is When i did IMP, it came back with message "Import terminated
successfully with warnings.". I dont understand what does that mean? Please
explain.

Regards,
Sajid Anwar
London

      and we said...
IMP-00046: using FILESIZE value from export file of 10240

that is the "warning", an informational message.  It was warning you that it was
deriving the filesize parameter since you didn't give it one to use.
Review &
      Followup

      Rating:
      5
Thanks  August 14, 2002
Reviewer:  Sajid
      Anwar  from London
Hi Tom,
My apology Tom, i couldnt out figure such a small issue.It is one of those thing
you just cant see in first place. Thanks a lot for your help.

Regards,
Sajid Anwar
London

      Rating:
      4
  June 19, 2003
Reviewer:
      A reader
can you please tell me where is the log of imp or exp ?

          Followup:

on screen?

June 20, 2003 --
      Thanks for the question regarding "creating trigger for deleting
      rows(giving mutating error)", version 8.1.6

      You Asked
hi i have a query regarding an old answer which you had
answered..the following is the question which i had asked and
following that, is the answer which you had answered..
my next question follows your answer below..

You Asked

hi ,
i have a table which has columns as below
primarykey is on (col1 + col2)

col1 col2 col3

0 1 a
1 1 a
2 1 b

0 2 c
1 2 b(*)
2 2 a(*)

it should not take the values because they are already there in
the previous row with col2 = 1

ie since (0,1,a) is already there (#,2,a)or (#,$,a) shouldnot be

allowed..
or since (2,1,b) is already existing (#,2,b)or (#,$,b) shouldnot

be allowed.(# means any number)($ means any number other than 1
ie col2)

ie the column col3 should be unique across col2..

i want to create a trigger(or any other constraint) to check
this ..how can i do this.
please advice..

regards

15 Feb '01:

sorry ..that was a mistake in my explanation...

let me reframe my sentence:

ie since (0,1,a) is already there (#,2,a)or (#,$,a) shouldnot be

allowed.. (1,1,a),(2,1,a),(3,1,a)...etc are to be allowed

or since (2,1,b) is already existing (#,2,b)or (#,$,b) shouldnot

be allowed.

(# means any number)($ means any number other than 1 ie other
than the col2 value with which it is associated now)

ie for every different value of col2 there has to be a different

col3; but for the same col2 there can be different col3.

for example:

col1 col2 col3
0 1 a
1 1 a
2 1 b

0 2 c
1 2 b(should not be allowed)
2 2 a(should not be allowed)
3 2 d

0 3 e
1 3 f
2 3 c(should not be allowed)
3 3 g
4 3 d(should not be allowed)

regards

-----------------------------------------------------------------
---------------
and I said...

With the creative use of

o IOT's (index organized tables)
o unique constraints
o foreign keys
o deferrable constraints
o a trigger

we can do this.  Here is an example:

ops$tkyte@ORA8I.WORLD> create table t_unique
  2  ( col3 char(1) unique,
  3    col2 int,
  4    primary key(col3,col2)
  5  )
  6  organization index
  7  /
Table created.

This will be a parent table.  You will never insert into this,
we'll do it in a trigger.  We want at most one value for col3 in

there (one 'a', one 'b' and so on) but the true primary key of
this object is col2,col3 (thats what our "child" table will
point to.

ops$tkyte@ORA8I.WORLD> create table t
  2  ( col1 int,
  3    col2 int,
  4    col3 char(1),
  5    constraint t_constraint
  6       foreign key(col3,col2)
  7       references t_unique
  8       deferrable );
Table created.

Now, here is our "child" table.  It references t_unique.  Now,
col3 must be unique in t_unique and col3,col2 must appear in
there.  That is the rule you want to enforce...

ops$tkyte@ORA8I.WORLD> create or replace trigger t_trigger
  2  after insert on t for each row
  3  begin
  4      execute immediate
  5        'set constraint t_constraint deferred';
  6      insert into t_unique
  7      ( col2, col3)
  8      values ( :new.col2, :new.col3 );
  9      execute immediate
 10         'set constraint t_constraint immediate';
 11  exception
 12      when dup_val_on_index
 13      then
 14         execute immediate
 15            'set constraint t_constraint immediate';
 16  end;
 17  /
Trigger created.

Here is the trigger that does the real work.  We defer
constraints for a moment, insert the row into the parent table
(if it already exists, we ignore it) and then check the
constraint straight away.  If the parent/child relationship
isn't enforced -- it'll fail.

Now to test it:

ops$tkyte@ORA8I.WORLD> insert into t values ( 0, 1, 'a' );
1 row created.

ops$tkyte@ORA8I.WORLD> insert into t values ( 1, 1, 'a' );
1 row created.

ops$tkyte@ORA8I.WORLD> insert into t values ( 2, 1, 'b' );
1 row created.

ops$tkyte@ORA8I.WORLD> insert into t values ( 0, 2, 'c' );
1 row created.

ops$tkyte@ORA8I.WORLD> insert into t values ( 1, 2, 'b' );
insert into t values ( 1, 2, 'b' )
*
ERROR at line 1:
ORA-02291: integrity constraint (OPS$TKYTE.T_CONSTRAINT)
violated - parent key not found

There you go -- rejected...

ops$tkyte@ORA8I.WORLD> insert into t values ( 2, 2, 'a' );
insert into t values ( 2, 2, 'a' )
*
ERROR at line 1:
ORA-02291: integrity constraint (OPS$TKYTE.T_CONSTRAINT)
violated - parent key not found

ops$tkyte@ORA8I.WORLD> insert into t values ( 3, 2, 'd' );
1 row created.

ops$tkyte@ORA8I.WORLD> insert into t values ( 0, 3, 'e' );
1 row created.

ops$tkyte@ORA8I.WORLD> insert into t values ( 1, 3, 'f' );
1 row created.

ops$tkyte@ORA8I.WORLD> insert into t values ( 2, 3, 'c' );
insert into t values ( 2, 3, 'c' )
*
ERROR at line 1:
ORA-02291: integrity constraint (OPS$TKYTE.T_CONSTRAINT)
violated - parent key not found

ops$tkyte@ORA8I.WORLD> insert into t values ( 3, 3, 'g' );
1 row created.

ops$tkyte@ORA8I.WORLD> insert into t values ( 4, 3, 'd' );
insert into t values ( 4, 3, 'd' )
*
ERROR at line 1:
ORA-02291: integrity constraint (OPS$TKYTE.T_CONSTRAINT)
violated - parent key not found

Just to test multi-row inserts as well:

ops$tkyte@ORA8I.WORLD> insert into t
  2  select rownum, rownum, chr(ascii('A')+rownum-1)
  3  from all_users where rownum < 26;

25 rows created.

ops$tkyte@ORA8I.WORLD> insert into t
  2  select rownum+1, rownum+1, chr(ascii('A')+rownum-1)
  3  from all_users where rownum < 26;
insert into t
*
ERROR at line 1:
ORA-02291: integrity constraint (OPS$TKYTE.T_CONSTRAINT)
violated - parent key not found

******************************************
FEB - 21, 2001

i would like to know how to create a trigger for deleting from
t_unique also when i delete from the table t.

example:

step 1)

eg if the table t has the following:

0 1 a
1 1 a
2 1 b

0 2 c
1 2 d
2 2 e

so table t_unique will have the following:
1 a
1 b
2 c
2 d
2 e

step 2)

eg

0 1 a <-if i delete this, then delete not reqd. in t_unique.
1 1 a <-if i delete this also, then delete reqd. in t_unique.
2 1 b

0 2 c
1 2 d
2 2 e

so table t_unique will have the following:

1 a <-delete if both the records are deleted from table t.
1 b
2 c
2 d
2 e

how to create the trigger? when i create a trigger on table t
and select count(*) from t. and then delete from t_unique, it
gives mutating table error.

regards
muralidhar

      and we said...
next time, you can just put the link to the original question
rather then duplicating it...

see

http://osi.oracle.com/~tkyte/Mutate/index.html
for how I arrived at this:

create or replace package state_pkg
as
    type myArray is table of t_unique.col3%type index by
binary_integer;

    deleted myArray;
    empty   myArray;
end;
/

create or replace trigger t_bd
before delete on t
begin
    state_pkg.deleted := state_pkg.empty;
end;
/

create or replace trigger t_adfer
after delete on t for each row
begin
    state_pkg.deleted( state_pkg.deleted.count+1 ) := :old.col3;
end;
/

create or replace trigger t_ad
after delete on t
declare
    child_exists exception;
    pragma exception_init( child_exists, -2292 );
begin
    for i in 1 .. state_pkg.deleted.count
    loop
    begin
        delete from t_unique where col3 = state_pkg.deleted(i);
    exception
        when child_exists
            then null;
    end;
    end loop;
end;
/
Review &
      Followup

      Rating:
      5
  June 20, 2003
Reviewer:  MG

Hi tom,

I have written a small trigger as follows.

create or replace trigger logon_trig
after logon on database
begin
 dbms_job.run(1);
end;
/

my job gets an error. now i get following error and can't go into oracle.

ORA-01075: you are currently logged on

how can I solve this.

thanks in advance.

          Followup:

log in as a DBA and drop the trigger.

that trigger would not be very useful.  running a job like that doesn't start it
in the background, rather, it uses YOUR session to run it.  It'll block.

June 20, 2003 Andy -- Thanks for the question regarding
      "Combining rows", version 8.1.7

      You Asked
Hi Tom

How can I - in an SQL query - combine multiple rows into a one row, when I know
the number of rows I want to combine (3 in my example), but don't want to test
the values of each row, but just take the entries as they come. e.g.

How can I turn:

Col1    Col2
------- -----
1         a
2         b
3         c
4         d
1         e
2         f
3         g
1         h

into:

Col1      Col2
-------   ------------------
1           a    e   h
2           b    f   -
3           c    g   -
4           d    -   -

i.e. I'm looking for upto 3 rows in Col2 for each value in Col1. If a row
(first, second or third) doesn't exist, then I just want a null value.

Thanks in advance,

Andy

      and we said...
ops$tkyte@ORA920> select col1, c21 || ' ' || c22 || ' ' || c23
  2    from (
  3  select col1, max(decode( rn, 1, col2 )) c21,
  4         max( decode( rn, 2, col2 )) c22, max(decode( rn, 3, col2 )) c23
  5    from (
  6  select col1, col2,
  7         row_number() over ( partition by col1 order by col2 ) rn
  8    from t
  9         )
 10   where rn <= 3
 11   group by col1
 12         )
 13  /

      COL1 C21||
---------- -----
         1 a e h
         2 b f
         3 c g
         4 d

Review &
      Followup

      Rating:
      5
  March 19, 2003
Reviewer:  Andy

Wow - Analytics do indeed rock, don't they!! Thanks for your helpful and quick
answer.

      Rating: 5
Varition on
      this theme  June 17, 2003
Reviewer:  Andy
       from Germany
Tom, as a variation on this theme, how would I show column values (for, say, a
set of address elements) in such a way as to "squeeze" out blank entries, so
that non-empty entries are "shunted" to the left to accomodate empty entries.

e.g. I'm trying to select address elements from a table in such a way as to
avoid "gaps". For instance, say my address elements (for a given row) are:

addr1 addr2 addr3 city country
----- ----- ----- ---- -------
A     B     NULL  C    D

in some cases, not all of the "addr" parts are needed (col. addr3 in this case),
so I want to squeeze out the blanks, and return it all as one row like this:

element1 element2 element3   element4
-------- -------- --------   --------
A        B        C          D

What's the best way to do this in one SQL statement?

Many thanks for your great site.

Andy

          Followup:

I don't see an "easy" way -- the problem is that the column you want next is
dependent on previous columns and -- yuck.  It gets "hard"

I guess I would use concatenation and return a SINGLE address field ( a string )

      Rating: 4
Multiple rows
      in a single column  June 20, 2003
Reviewer:
      Jean-Pierre  from FRANCE
Hi

I have a table with 1 column and n rows (I don(t know the number of rows --
where COL1 like '%...%' --) :
COL1
----
0001
0002
0155
0457
7988
....
....

I'd like to write a query which produce this :

COL1
----------------------------------------------
0001,0002,0155,0457,7988,......

Is there any way to obtain this ?

Thanks

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2196162600402

June 20, 2003 --
      Thanks for the question regarding "logon trigger", version 8.1.6.3

      You Asked
Hi

I am trying to code a small logon trigger and control the machines that can
connect to the database

create or replace trigger ctrl_host
   after logon on database
declare
   host number := 0;
   dba number := 0;
begin
   select 1 into host from v$session
        where osuser<>'SLE7412';
   select 1 into dba from dba_role_privs
        where grantee= USER and granted_role='DBA';
   if host = 1 and dba = 0 then
      raise_application_error(-1, 'Connection Refused');
   end if;
end;
/

This doesnt work because I would never get a row in the first select, I tried to
use dbms_lock.sleep(3) and see if after 3 seconds the user would login with no
problems but it doesnt seem to be feasible since the user is still waiting for
this trigger to finish it´s *firing time* and get the permission to connect. Is
my logic wrong?

the reason of selecting 1 into a variable is to use that variable later in the
IF part. Any user trying to connect from host SLE7412 will fail but if the user
has DBA privilege then he should be able to do so.
in my IF if the user is from host SLE7412 and is a normal user an error would be
raised, if the user is an user with DBA role then he can logon no matter the
host

cheers

      and we said...
well, your logic does not say that at all.

firstly, you are querying based on osuser, not the host:

   select 1 into host from v$session
        where osuser<>'SLE7412';

secondly, you are querying ALL records in v$session, not this sessions record.

You want to use the IP_ADDRESS for this as the host name would be reported by
the client and the client can lie to you.  We can get the ip address easily:

scott@ORA8I.WORLD> select sys_context('userenv', 'ip_address' ) from dual;

SYS_CONTEXT('USERENV','IP_ADDRESS')
---------------------------------------------------------------------------------
--------------------------------------------------
138.2.5.51

So, you should do something like:

  if ( sys_context('userenv','ip_address') = 'IP ADDRESS OF BAD HOST' ) AND
       proc_role( 'DBA' ) <> 1 )
  then
        raise_application_error( -20001, 'connection refused' );
  end if;

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1362403157367
for proc_role -- faster implementation there.
Review &
      Followup

      Rating:
      3
SYS_CONTEXT  January 24, 2002
Reviewer:
      Randy  from Chicago,IL
Your approch of sys_context('userenv','ip_address') is fine as long as I am
doing any client-server connection. But if I telnet into the server,
sys_context(...) cannot provide the ip of the telnet client. It appears blank.

SQL> select sys_context('userenv','ip_address') from dual;

SYS_CONTEXT('USERENV','IP_ADDRESS')
--------------------------------------------------------------------------------

          Followup:

Yes, because you are not connecting over net8 -- it only works with network
connections, not local connections.  If the ip_address is "blank", the
ip_address should be obvious -- its the server.  You can get your hostname from
"select host_name from v$instance" for logging purposes.

      Rating: 3
A
      Reader  May 29, 2002
Reviewer:  Rahul Sharma
       from India
Hi Tom
Is there any data dictionary in Oracle where it stores the IP addresses of
connected cleint machines?
When users connect in databsae through Java client and I try to find out the
name of the machine from where connecttions are coming, it gives me terminal
name Unknown.

I fired

SQL> select username,terminal,machine from v$session;

USERNAME                       TERMINAL         MACHINE
------------------------------ ---------------- -----------
SCOTT                          unknown          jdbcclient
SCOTT                          unknown          jdbcclient
USER1                          unknown          jdbcclient
USER2                          unknown          jdbcclient
USER2                          unknown          jdbcclient

From where I can get the TERMINAL/MACHINE or ip address information of connected
users.

Thanks.

          Followup:

You can get that if you AUDIT CONNECT (use auditing)

You could also create a trigger:

create or replace trigger on_logon_trigger
after logon on database
begin
    dbms_application_info.set_client_info( 'logon ' || sys_context( 'userenv',
'ip_address' ) );
end;
/

to store that info in v$session....

      Rating: 4
assign profiles
        October 02, 2002
Reviewer:  A reader

hi

can we user logon trigger to assign user a specific profile?

it seems DDL does not work with logon triggers even executing a procedure which
uses autonomous transaction

give error

ORA-04045: errors during recompilation/revalidation of CM.LOGON
ORA-01031: insufficient privileges

          Followup:

what are you trying to do in the trigger (what "command")

read

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html

      Rating: 4
set
      profile  October 02, 2002
Reviewer:
      A reader
Hi

I tried

alter user username profile XXXX

I first wrote it in the logon trigger but I got error in alert complaining about
DDL, I then put the code in a stored procedure with pragma autonomous
transaction and call it from trigger then I get the error I posted in my
previous post

Just a further question is it possible to change a user's profile per session?

          Followup:

no, that would change the users profile period

bad idea -- terrible concept -- rethink what you are trying to do.  DO NOT do
this .

      Rating:
      5
  June 20, 2003
Reviewer:  Vini

hi tom,

when I executing sql commands (not pl/sql), when error comes, I wants to know
that by executing that command, I got an error.

What is the method for that?

Thank you in advance

          Followup:

you read the screen?

not sure what you mean or what environment you might be working in at all.

June 20, 2003 Richer -- Thanks for the question regarding
      "Append hint in archivelog mode.", version 8.1.7

      You Asked
Hi Tom,

I wanted to test the append hint in an archivelog database.  I wanted to see the
difference between

-normal insert
-insert with append hint
-insert with append hint and nologging

But I don't see any difference with the redo size generated by the 3 different
options.  I thought inserting with the append hint and by asking nologging would
generate significantly redo.  What am I seeing wrong?

--------------------------------------------------------

SQ90084@MAGPROD> TRUNCATE TABLE TEST;

Table truncated.

Elapsed: 00:00:00.00
SQ90084@MAGPROD> INSERT INTO TEST SELECT * FROM DBA_OBJECTS;

7535 rows created.

Elapsed: 00:00:00.04
SQ90084@MAGPROD> SELECT * FROM V$MYSTAT WHERE STATISTIC# =101;

       SID STATISTIC#      VALUE
---------- ---------- ----------
        17        101   11299716

Elapsed: 00:00:00.00
SQ90084@MAGPROD> TRUNCATE TABLE TEST;

Table truncated.

Elapsed: 00:00:00.01
SQ90084@MAGPROD> INSERT /*+ APPEND */  INTO TEST SELECT * FROM DBA_OBJECTS;

7535 rows created.

Elapsed: 00:00:00.05
SQ90084@MAGPROD> SELECT * FROM V$MYSTAT WHERE STATISTIC# =101;

       SID STATISTIC#      VALUE
---------- ---------- ----------
        17        101   12110516

Elapsed: 00:00:00.00
SQ90084@MAGPROD> TRUNCATE TABLE TEST;

Table truncated.

Elapsed: 00:00:00.00
SQ90084@MAGPROD> INSERT /*+ APPEND */  INTO TEST SELECT * FROM DBA_OBJECTS
NOLOGGING;

7535 rows created.

Elapsed: 00:00:00.05
SQ90084@MAGPROD> SELECT * FROM V$MYSTAT WHERE STATISTIC# =101;

       SID STATISTIC#      VALUE
---------- ---------- ----------
        17        101   12921700

Elapsed: 00:00:00.00

      and we said...
your NOLOGGING isn't NOLOGGING.  that last insert is really not any different
then:

INSERT /*+ APPEND */  INTO TEST SELECT * FROM DBA_OBJECTS HI_THERE;

nologging is just a table alias in that context.  you MEANT alter table.

Here we go, in archive log mode database, using the infinitely easier autotrace:

ops$tkyte@ORA9I> drop table t;
Table dropped.

ops$tkyte@ORA9I> create table t as select * from all_objects where 1=0;
Table created.

ops$tkyte@ORA9I> set autotrace on statistics
ops$tkyte@ORA9I> insert into t select * from all_objects;

44876 rows created.

Statistics
----------------------------------------------------------
        334  recursive calls
       3528  db block gets
     408390  consistent gets
       3729  physical reads
    4980348  redo size
        786  bytes sent via SQL*Net to client
        808  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      44876  rows processed

ops$tkyte@ORA9I> commit;

Commit complete.

ops$tkyte@ORA9I> insert /*+ APPEND */ into t select * from all_objects;

44876 rows created.

Statistics
----------------------------------------------------------
        227  recursive calls
        151  db block gets
     407437  consistent gets
       2369  physical reads
    5081144  redo size
        772  bytes sent via SQL*Net to client
        822  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      44876  rows processed

ops$tkyte@ORA9I> commit;

Commit complete.

no difference - in archive log, append surpressed ROLLBACK but NOT redo,
so...

ops$tkyte@ORA9I> ALTER TABLE T NOLOGGING;

Table altered.

ops$tkyte@ORA9I> insert /*+ APPEND */ into t select * from all_objects;

44876 rows created.

Statistics
----------------------------------------------------------
        411  recursive calls
        136  db block gets
     407471  consistent gets
       1586  physical reads
      14608  redo size
        773  bytes sent via SQL*Net to client
        822  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          6  sorts (memory)
          0  sorts (disk)
      44876  rows processed

ops$tkyte@ORA9I> commit;

Commit complete.

ops$tkyte@ORA9I> set autotrace off

there you go, redo way down (but time for me to go off and backup my database
since I broke the redo chain for these blocks!)

Review
      & Followup

      Rating: 5
How didn't I
      see that!  March 06, 2003
Reviewer:  Rich

Sorry for a stupid question.  It was so obvious it almost got me blind...

      Rating:
      5
Rollback  March 06, 2003
Reviewer:
      Peter
"no difference - in archive log, append surpressed ROLLBACK but NOT redo, "

Tom what is showing you that ROLLBACK was supressed??

          Followup:

I know it was, that is why :)

Here is the "tangible evidence"

ops$tkyte@ORA9I> create table t as select * from all_objects where 1=0;

Table created.

ops$tkyte@ORA9I>
ops$tkyte@ORA9I> insert into t select * from all_objects;

44876 rows created.

ops$tkyte@ORA9I> select used_ublk
  2    from v$transaction
  3   where addr = ( select taddr from v$session where sid =
  4                    ( select sid from v$mystat where rownum = 1 )
  5                 );

 USED_UBLK
----------
        22

ops$tkyte@ORA9I> commit;

Commit complete.

ops$tkyte@ORA9I>
ops$tkyte@ORA9I> insert /*+ APPEND */ into t select * from all_objects;

44876 rows created.

ops$tkyte@ORA9I> select used_ublk
  2    from v$transaction
  3   where addr = ( select taddr from v$session where sid =
  4                    ( select sid from v$mystat where rownum = 1 )
  5                 );

 USED_UBLK
----------
         1

ops$tkyte@ORA9I> commit;

Commit complete.

That is what append does.  It writes above the HWM of the table, bypassing the
need for rollback.  That is why you cannot query the table after a direct path
insert without committing -- it sort of "breaks" our consistent read mechanism
for that session on that table.

rollback for inserts is very very small (delete rowid is all that is made)...

      Rating: 5
Excellent. But
      why doesn't oracle allow you to read the rows you just
      inserted?  March 06, 2003
Reviewer:  gs

Tom,

I was always wondering, why oracle doesn't allow to read the data inserted thru
"direct insert" without doing a commit?

Is it because, oracle doesn't have any info on this insert in the RBS? But how
come, it allows me to read after "commit"?

          Followup:

We don't have the information needed in the RBS for our consistent read
mechanism to work (as stated above).

...
That is what append does.  It writes above the HWM of the table, bypassing the
need for rollback.  That is why you cannot query the table after a direct path
insert without committing -- it sort of "breaks" our consistent read mechanism
for that session on that table
......

Insert /*+APPEND*/ is a "special" thing, a tool that you may or may not choose
to use.  It does what it does.

      Rating: 4
If rollback
      generation was surpressed then why we got the same amount of redo?
        March 07, 2003
Reviewer:  Dmitry
       from NH
Hi Tom,

You wrote in the middle of the first reply:

*no difference - in archive log, append surpressed ROLLBACK but NOT redo,*

If rollback generation was surpressed then why we got the same amount of redo as
without append?
My understanding (from your book actually) was that redo protects *both data
blocks and rollback entries*. As we do not have rollback for the operation we
should get less redo. Why the numbers are the same?

Thank you,

          Followup:

because in the insert we generated

21 blocks of undo without append
 1 blocks of undo with append

and CREATED

ops$tkyte@ORA9I> analyze table t compute statistics for table;

Table analyzed.

ops$tkyte@ORA9I> select blocks from user_tables where table_name = 'T';

    BLOCKS
----------
      1228

new blocks.

So, in one case we had redo for 1249 blocks, in the other case 1229 blocks.

No significant difference there.....  that is why the same amount of redo was
generated more or less -- we had about the same amount of blocks to log redo
for.

      Rating:
      5
Excellent  March 07, 2003
Reviewer:
      Dmitry  from NH
Yes, number of generated undo blocks times less than created data block. That's
why the difference in redo is not significant.

Thank you,

      Rating: 4
SQL Loader
      Direct Insert and Append Hint Difference  March 14, 2003

Reviewer:  Nurlan  from Kazakhstan
Hello Tom, let me continue the discussion. The question is whether any
difference between SQL Loader Direct Insert and Insert with the Append Hint.
... SQL*Loader then uses the column array structure to format Oracle data blocks
and build index keys. The newly formatted database blocks are then written
directly to the database (multiple blocks per I/O request using asynchronous
writes if the host platform supports asynchronous I/O).
Is there anything like this for Inserts with append hint?
Namely, will such inserts skip the buffer cache or any preprocessing? I did not
find anything on any differences, but they are called the same, direct inserts,
or loads.

          Followup:

they are not 100% the same but are for all intents and purposes "so similar as
to be not really distinguisable by us".

direct path inserts also do direct io.

      Rating:
      4
testing  June 19, 2003
Reviewer:
      sam  from NY USA
Tom I did some testing as follows

CREATE TABLE A1 unrecoverable parallel (degree 30)
tablespace sdmuser01
as
select Cust_num , 'PFS' as PFS_CD from tdmcustinsight a
where  a.rm_lob_code = '03'
and a.pfs_offer_code is not null
/

Elapsed: 00:00:05.66

No rollback .

insert into a1 (cust_num,PFS_CD)
select Cust_num , 'PFS' as PFS_CD from tdmcustinsight a
where  a.rm_lob_code = '03'
and a.pfs_offer_code is not null
/

Elapsed: 00:00:14.81

Statistics
----------------------------------------------------------
    9313840  redo size
     366527  rows processed

rollback usage

NAME           USED_KB   SID   PID SPID
R08                896    67   118 15906

insert /*+ APPEND */ into a1 (cust_num,PFS_CD)
select Cust_num , 'PFS' as PFS_CD from tdmcustinsight a
where  a.rm_lob_code = '03'
and a.pfs_offer_code is not null
/

Elapsed: 00:00:12.45

Statistics
----------------------------------------------------------
      13760  redo size
     366527  rows processed

NAME           USED_KB   SID   PID SPID
R05                 16    67   118 15906

CREATE TABLE A1
tablespace sdmuser01
as
select Cust_num , 'PFS' as PFS_CD from tdmcustinsight a
where  a.rm_lob_code = '03'
and a.pfs_offer_code is not null
/

Elapsed: 00:00:10.46

insert into a1 (cust_num,pfs_Cd)
select Cust_num , 'PFS' from tdmcustinsight a
where  a.rm_lob_code = '03'
and a.pfs_offer_code is not null
/

Elapsed: 00:00:13.11

Statistics
----------------------------------------------------------
    9427752  redo size
     366527  rows processed

NAME           USED_KB   SID   PID SPID
R04                896    67   118 15906

insert /*+ APPEND */ into a1 (cust_num,pfs_Cd)
select Cust_num , 'PFS' from tdmcustinsight a
where  a.rm_lob_code = '03'
and a.pfs_offer_code is not null
/

Elapsed: 00:00:12.43

Statistics
----------------------------------------------------------
      36104  redo size
     366527  rows processed

NAME           USED_KB   SID   PID SPID
R01                 16    67   118 15906

Couple of questions.
When we create a table there is lot of difference in time as against we insert
or insert with append hint.
Why is that??
   If the answer is no REDO & ROLLBACK , the numbers don't justify the
difference.

But if we  compare the timing between normal table creation (Without
unrecoverable option) & insert then elapsed time
are more meaningful.

So exactly what happens with unrecoverable option ??
Also can you show some light (or link) on parallel option for table creation.

Also if such a minimal rollback is created with append option how we can
rollback transaction successfully ???

Thanks

          Followup:

a couple of seconds wall clock difference is not meaningful -- you are taking
like 2 seconds on the wall clock.

check out the concepts guide, chapter on parallel operations.

read the chapter near the parallel stuff on direct path operations for
information on APPEND (append writes straight to disk, bypasses the buffer cache
and writes ABOVE the current high water mark for the table -- hence, if you need
to rollback, we simply put the HWM back -- data "disappears"

      Rating:
      4
testing  June 20, 2003
Reviewer:
      sam  from NY USA
"a couple of seconds wall clock difference is not meaningful -- you are taking
like 2 seconds on the wall clock."

What do u mean by that??

Also let me rephrase my question.

If we don't care about redo generation (In read intensive data warehouse) should
it be standard practice to insert data using /*+ APPEND */ hint ??

When I say only 2 sec difference what I was referring to was it's not that great
as compared to create table with unrecoverable option.
create table 05.66
insert append 12.45
insert normal 14.81

I was expecting insert append timings would be closer to create table timings.
Can you throw some light ??

Meantime I am going through concept guide too.

Sam

          Followup:

I mean, in the grand scheme of things -- 2 seconds between 10 and 12 seconds is
"noise".

maybe the disks spun slower.
maybe someone else was doing something.
your query ran faster one time, slower the next time.

everything will affect that time.  You need to use tracing and tkprof to see
what's up there.

In a data warehouse, during bulk loads, you can definitely use this feature as
long as you BACKUP right afterwards.  You can use this in any situation -- as
long as you BACKUP.

I just don't observe what you do:

create table t NOLOGGING as select * from all_objects

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      4.23       4.34          0     146054        522       31578
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      4.24       4.36          0     146054        522       31578

insert into t select * from all_objects

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.08          0          0          0           0
Execute      1      4.10       4.79          0     146487       4326       31578
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      4.11       4.88          0     146487       4326       31578

insert /*+ APPEND */ into t select * from all_objects

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.06          0          0          0           0
Execute      1      4.04       4.12          0     146050        123       31578
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      4.05       4.18          0     146050        123       31578

so, I just put it down to "you have other things going on on your system, they
are affecting you, things will run slower and faster depending on who is doing
what".

June
      20, 2003 Michael -- Thanks for the
      question regarding "Can the "analyze table" command cause major adverse
      database access problems", version 8.1.6

      You Asked
Hi Tom,

I used the following command at the SQL prompt ONLY for a few tables.

SQL>analyze table table_name estimate statistics sample 10 percent;

I was given to understand (by my DBA) that when I give this command, it keeps on
running in the background and decreases performance. He says that I must stop
the background processes. How do I do that ??

But according to what I have read this command is only used to update certain
data dictionary tables and this ends here itself. It also says that the tables
should be analyzed frequently to ensure accurate statistics.

Please elaborate and confirm my query.

Thanks in anticipation.

Michael

      and we said...
An analyze will do IO, it'll use temp space to collect statistics.  It is sort
of like running an intensive query -- no more or less.

Once this command finishes, once you get the SQL> prompt back -- it is done.  It
does not keep running in the background (if that is what your DBA meant, time
for a new DBA).

It might be better to alter the tables and set the monitoring attribute (search
for monitoring on this site for examples of what this is).  You can then use
DBMS_STATS to gather stale statistics on tables that need it -- skipping those
that do not.

You do not need to analyze every table frequently, you only need to analyze when
a substantial portion of the table has been modified.
Review &
      Followup

      Rating: 4
Analyze and
      Table locks  November 27, 2001
Reviewer:
      Mark  from ca
Could you confirm/deny the story that ANALYZE acquires table locks while
running? If so, wouldn't this effect DML in an adverse way (taking into account
WAIT/NOWAIT status).
Great Site Tom!

          Followup:

It will not affect DML, it will not block DML...

      Rating:
      2
Correction  November 27, 2001
Reviewer:
      Vito
I think you are mistaken on this last one Tom.

We have 2 TB distributed database (8.1.7) and when some of our largest tables
are being analyzed our application queries are not returning to the users until
we kill the analyze statements.  We had opened a TAR and found out that analyze
places a lock on tables.

The symptom for us was that when analyze is running on a large table, the users
queries are not returning.

          Followup:

If you run "analyze table validate structure" might lock it (in 9i, it can be
done ONLINE).

A compute stats -- no, it won't..  I have a really large table for testing stuff
with (lots of "big" questions recently.  It has 37+ million rows in it.  I
kicked off:

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

and in another session ran:

ops$tkyte@ORA817DEV.US.ORACLE.COM> update bigger_table set deptid = 100 where
empid < 1000;

999 rows updated.

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

Commit complete.

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

and in yet another session ran:

ops$tkyte@ORA817DEV.US.ORACLE.COM> @count bigger_table 8
old   1: select /*+ FULL(&1) PARALLEL(&1,&2) */
new   1: select /*+ FULL(bigger_table) PARALLEL(bigger_table,8) */
old   3: from &1
new   3: from bigger_table

  COUNT(*)
----------
  34981888

Elapsed: 00:01:42.09
ops$tkyte@ORA817DEV.US.ORACLE.COM>

The analyze is STILL running (will be).  So, lets look at a showsql to see whats
going on in the database:

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

USERNAME        SID_SERIAL      STATUS     MODULE          ACTION
CLIENT_INFO
--------------- --------------- ---------- --------------- ---------------
---------------
OPS$TKYTE       '7,686'         ACTIVE     SQL*Plus
OPS$TKYTE       '20,362'        ACTIVE     01@ showsql.sql
Elapsed: 00:00:00.01
--------------------
OPS$TKYTE(20,362) ospid = 8776 command = 3 program = sqlplus@aria-dev (TNS
V1-V3) dedicated server=8777
Tuesday   20:27  Tuesday   20:29 last et = 0
SELECT USERNAME || '('  || SID  || ','  || SERIAL#  || ') ospid
= '  || PROCESS  || ' command = '  || COMMAND  || ' program = '
|| PROGRAM  USERNAME,TO_CHAR(LOGON_TIME,' Day HH24:MI') LOGON_T
IME,TO_CHAR(SYSDATE,' Day HH24:MI') CURRENT_TIME,SQL_ADDRESS,LAS
T_CALL_ET,SID,PADDR   FROM V$SESSION  WHERE STATUS = 'ACTIVE'  A
ND RAWTOHEX(SQL_ADDRESS) != '00'  AND USERNAME IS NOT NULL  ORDE
R BY LAST_CALL_ET
--------------------
OPS$TKYTE(7,686) ospid = 8760 command = 62 program = sqlplus@aria-dev (TNS
V1-V3) dedicated server=8761
Tuesday   20:26  Tuesday   20:29 last et = 156
analyze table bigger_table
compute statistics
for table
for all
indexes
for all indexed columns

Elapsed: 00:00:00.05
ops$tkyte@ORA817DEV.US.ORACLE.COM>

sure enought, sid 7 is doing the analyze -- looking at v$lock:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from v$lock where sid = 7;
Elapsed: 00:00:00.18
ops$tkyte@ORA817DEV.US.ORACLE.COM> select sid, type, lmode, request from v$lock;

       SID TY      LMODE    REQUEST
---------- -- ---------- ----------
         2 MR          4          0
         2 MR          4          0
         2 MR          4          0
         2 MR          4          0
         2 MR          4          0
         2 MR          4          0
         2 MR          4          0
         2 MR          4          0
         2 MR          4          0
         2 MR          4          0
         2 MR          4          0
         3 RT          6          0
         5 TS          3          0
Elapsed: 00:00:00.05
ops$tkyte@ORA817DEV.US.ORACLE.COM>

sid 7 has NOTHING locked....

Not sure what was going on in your case -- but an analyze won't lock the tables
out.

and... and decided to try some updates via dblinks as well -- distributed
transactions.  I could NOT get them to block with an analyze to compute stats
going on.

      Rating: 4
Confirmation of
      what is said  November 27, 2001
Reviewer:  Dilip
      Dandekar  from Pune, India
Tom you have said that analyze command is to be executed only when data is
modified.

Does that mean if rows are getting added to transaction tables every day analyze
table need not be performed ?

Thanks

          Followup:

No, that is not what that means.  If rows are getting added every day then the
number of row and skew of the data (high/low values for columns and such) is
constantly changing.  When about 10% of the table is "different" then it was
before, it should be analyzed again.  You can automate this with MONITORING and
DBMS_STATS.

search for monitoring on this site for details

      Rating: 2
Delete
      statistics  November 27, 2001
Reviewer:
      Michael  from Mumbai, INDIA
When the statistics collected were deleted the query ran.

SQL>analyze table table_name delete statistics;

The explanation I was given is that single tables should not be analyzed because
this causes problems with the optimizer. I still find it difficult to believe.
But then again I have no answer to give the DBA's since they deleted the
statistics and everything was back to normal.

Could there be another problem ?? I know it is difficult to ascertain but thanks
for all the information.

Michael

          Followup:

Either analyze everything or analyze nothing.

Today -- with 8i -- you should be analyzing everything since so many features
are unavailable without the CBO.

By analyzing one single object, you invoked the CBO but it had no information
about any other object in the database and it "guessed".  It guessed wrong.
Give it proper information and it'll do its job.

      Rating: 5
Some
      info  November 28, 2001
Reviewer:  Jim
       from Australia
Michael,
        Yes analyzing a few tables can cause performance
problems. If you are joining one or more  tables with statistics with one or
more tables that have not been
analzed and you are using  the cpst base optimiser
then the optimiser makes up statistics for those tables
which have not been analysed and this of course can cause
it to use the wrong execution plan.

If you are using the the cost based optimiser
1: make sure ALL or NONE of the tables are analyzed
2: analyze tables that have volatile data regularly

If you need stats that the optimiser generates
then analyze the tables, save the results to another table
and delete the statistics

Hi Tom,

I used the following command at the SQL prompt ONLY for a few tables.

SQL>analyze table table_name estimate statistics sample 10 percent;

I was given to understand (by my DBA) that when I give this command, it keeps on

running in the background and decreases performance. He says that I must stop
the background processes. How do I do that ??

But according to what I have read this command is only used to update certain
data dictionary tables and this ends here itself. It also says that the tables
should be analyzed frequently to ensure accurate statistics.

Please elaborate and confirm my query.

Thanks in anticipation.

Michael

      Rating:
      5
Locks  November 28, 2001
Reviewer:
      Andre Whittick Nasser  from Rio de Janeiro, Brazil
Tom,

Yes, I myself was looking into this problem. Analyze stats won't (or shouldn't)
lock anything.

I was thinking... since he is in a distributed environment -- hence, more prone
to his overlooking some aspect, maybe:

a) Someone is explicitly locking the table.

b) Distributed transactions are locking resources to guarantee consistency, as
in 2PC mechanism (sorry I have some blind spots here)

Would you please comment ?

More stuff:

c) I wasn't able to locate an on-line book containing the locking type each DDL
command incurs. For example, an ALTER TABLE would put a share lock on the table
but ANALYZE wouldn't. Any hint ?

d) Conceptually speaking, shouldn't ANALYZE have some kind of consistent view of
the table so modifications wouldn't hinder its work ? Ok, it put no locks there.
I imagine it might use rollback segments for that. As you said, ANALYZE is
nothing more than a super select, hence the analogy. Right ?

Thanks again.

          Followup:

b) is a likely culprit.  during a brief window in the 2PC process, the modified
data is not only locked from writes -- but reads.  A very rare time that we do
have blocking reads.  Something like this could definitely jam the analyze stats
process (it reads lots) but... on its own... would not be the cause of this
issue.  I mean -- they have this problem with or without analyze.

I hypothesize they were running a VALIDATE STRUCTURE -- which will lock stuff
(until 9i)

c) I don't know of one.  What I've done to analyze this is to create an "ALTER"
trigger on a schema.  In this alter trigger I put:

   dbms_lock.sleep( 60 );

that gives me one minute to see what its doing (works in 8i and up)

it'll depend on the alter table as to what kind of lock it'll take.

d) correct.  analyze is a "super select".  in fact, if you use dbms_stats to
analyze -- you'll many times see its just running queries

      Rating: 5
About the
      DELETE STATISTICS review above  November 28, 2001

Reviewer:  Andre Whittick Nasser  from Rio de
      Janeiro, Brazil
Tom,

The Oracle material for 8i is explicit when it says that in CHOOSE mode, it's
enough that ONE table in the query be analyzed for the CBO to be used. If none
is analyzed, the RBO is used.

In the first case, the tables not explicitly analyzed will be implicitly
analyzed using other internal metadata, like using the ALL_ROWS and FIRST_ROWS
hints do -- which don't need ANALYZE either, but force the use of the CBO.

Sorry, but do you think this would go so far as  "jamming" a query, provided it
hasn't a very, very inefficient plan ?

A note: You mentioned he might be using VALIDATE STRUCTURE, but he question was
clear: he was using ESTIMATE STATISTICS.

          Followup:

Yes, that could "jam" a query badly.  It has very little information to go on.
It could see a table with 1,000 blocks under the HWM and assume there must be
1,000,000 rows in there -- don't use an index.  Upon analyzing, it could find
there really are 5 rows in there (the rest were deleted) so using an index would
be imperative.

As for the "note", agreed -- the original QUESTIONER was using estimate, but the
followup guy who said I was wrong said:

<quote>
I think you are mistaken on this last one Tom.

We have 2 TB distributed database (8.1.7) and when some of our largest tables
are being analyzed our application queries are not returning to the users until
we kill the analyze statements.  We had opened a TAR and found out that analyze
places a lock on tables.

The symptom for us was that when analyze is running on a large table, the users
queries are not returning.
</quote>

He just says "being analyzed".  It is in response to HIM that i guessed he was
doing a validate structure which will lock up data.

To the ORIGINAL poster - the answer is "analyzing to gather stats will not
lock".

      Rating: 5
Excellent
      explanation!  November 28, 2001
Reviewer:
      Joel  from Columbus, OH USA
Thanks for the explanation.  I also like the suggestion of using DBMS_STATS.

      Rating:
      5
  November 28, 2001
Reviewer:  Mark
       from Ca
I think I got my question answered. Thanks.

      Rating:
      5
Reader  November 28, 2001
Reviewer:
      Reader  from USA
Tom,

"
What I've done to analyze this is to create an "ALTER"
trigger on a schema.  In this alter trigger I put:

   dbms_lock.sleep( 60 );

that gives me one minute to see what its doing (works in 8i and up)
"

Could you give a simple example for doing this.

I just tested and found that
"alter table <table_name> validate structure"
gets a TM Mode 4 (share) Lock.

When I "analyze table <table_name> validate structure"
it errors with ORA-54 resource busy, if the table
holds TM Mode 3,5 and 6 (row exclusive, share row exclusive
                      and exclusive)
The ".... validate structure" succeeds if the table
holds TM mode 4,2 (share, row share)

Hence update and insert conflicts with "analyze .."
select for update and select, works with "analyze .."
Could you confirm this

Thanks

          Followup:

Here is the example of the trigger and a statement:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace trigger alter_trigger
  2  after alter on schema
  3  begin
  4          dbms_lock.sleep(60);
  5  end;
  6  /

Trigger created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set timing on
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t add y int;

Table altered.

Elapsed: 00:01:01.57
ops$tkyte@ORA817DEV.US.ORACLE.COM>

took 1 minute to alter the table.  That gave me more then enough time to get
another session pokeing around to see:

  1  select type, id1, lmode, request, block
  2* from v$lock where sid = 20
ops$tkyte@ORA817DEV.US.ORACLE.COM> /

TY        ID1      LMODE    REQUEST      BLOCK
-- ---------- ---------- ---------- ----------
TM      23305          6          0          0
TM         65          3          0          0
TM         66          3          0          0
TM         67          3          0          0
TM         68          3          0          0
TM         70          3          0          0

object 23305 is table T, I had a mode 6 lock on the table.

the analyze table validate structure takes a SHARE (4) table lock.  It is
compatible with RS (row share) and S (share) locks -- select and select for
updates are OK with it.

      Rating:
      4
Thanks  November 29, 2001
Reviewer:
      Michael  from INDIA
Thanks a lot for all the feedback !!

Michael

      Rating: 5
analyze and
      deadlock  January 01, 2003
Reviewer:
      A reader
Hi Tom

I understand prior 9i we should not analyze SYS schema but in note.35934.1 on
Metalink it suggests we could do it in 9i since RBO most probably will be
desupported in 10i. The reason not to analyze SYS schema is analyze some tables
such as fet$ and uet$ could cause deadlocks but since we are aware analyze dont
put any type of locks on the tables so how can this cause deadlock?

          Followup:

It goes onto say:

 The dictionary can now be analyzed without
 any problems. Note that whilst DBMS_UTILITY.ANALYZE_SCHEMA can be
 used against the SYS schema Oracle does not perform any regression
 tests with these tables analyzed and so it is possible that
 deadlocks or performance problems may be encountered. Also note that a
 significant number of large databases run extremely succesfully with
 an analyzed dictionary.

it is a deadlock between the analyze and SMON that is an issue.

      Rating: 4
Explain plan
      get's locked  June 20, 2003
Reviewer:  Christo
      Kutrovsky  from Ottawa, ON Canada
Tom,

I noticed that when I am analyzing COMPUTE STATISTICS on a table, I cannot do
explain plan on a query accessing that table.

But yet the query can run, and insert/updates are not a problem.

How would you comment this?

          Followup:

I cannot reproduce that bevahiour myself -- Oracle9ir2 9.2.0.3

I just kicked off a big analyze (still running) and

big_table@ORA920> explain plan for select * from big_table;

Explained.

no issues?

      Rating: 5
rule hint in
      10G?  December 08, 2003
Reviewer:
      A reader
if a query has a rule hint in 10G, will CBO just
ignore it?

June 21, 2003 sushant -- Thanks for the question regarding
      "difference between cursors for loop and normal explicit cursors", version
      8.1.5

      You Asked
1)when  we use a  explicit cursor

  we create a cursor
  open it
  fetch the values into variables
  close it

   if the same things are implicitly done for us when we use
   cursors for loop  why do we put more effort.

2) when we grant all on  a table to other users say with grant option
   how do we only remove the grant option with revoking other privileges.

      and we said...
1) I don't know.  Explicit cursors are

o harder to code
o harder to debug
o run SLOWER then implicit cursors
o leave more room for error
o cause you to type more

I love implicit cursors and use explicit cursor ONLY when I absolutely must or
when I went to employ bulk collects (here explicit can be much faster then
implicit).

ALWAYS use select into, NEVER use open/fetch/close to get a single row!  For
single row fetches, always use an implicit curosr.

2) you don't.  you have to revoke and re-grant.
Review &
      Followup

      Rating: 3
Can you please
      illustrate on why you would use explicit cursor ?  May 09,
      2002
Reviewer:  Paru  from Atlanta, GA
Can you please illustrate on why you would use explicit cursor for bulk
collects.  BTW, what is bulk collect? If I have millions of records that I need
to select and place it into another table using pl/sql, what would you suggest
for me to use - explicit cursor or implicit cursor?

TIA,
Paru

          Followup:

Because you usually are processing lots of rows and you don't want to fetch
100,000 rows into a set of arrays in one call -- rather you want to get say
100-500 at a time, process them, get the next 100-500 times.  So you need an
explicit cursor like:

  open c1;

  loop
        fetch c1 bulk collect into array limit 100;
         ....
        exit when c1%notfound;
  end loop;

  close c1;

so you can fetch N rows at a time.

As for

"If I have millions of records that I need
to select and place it into another table using pl/sql"

I would say your approach is FLAWED.  You don't want to involve PLSQL at all --
you want to insert into select PERIOD.

      Rating:
      3
  May 10, 2002
Reviewer:  Paru
       from Atlanta, GA
Hi Tom,

I am using insert into select, but I am putting it in a loop, cos otherwise it
does not catch my dup_val_exception.  In the sense once I get a
dup_val_on_index, I want all the other records to be inserted and then if I hit
a dup_val then I want to write to an error table.

Please tell me if this is the correct way of doing this.  I am using dblink to
connect 2 schemas as our clients might be doing the conversion from one schema
into another on the same instance or on different instance.

Also, they might convert millions of records, so please suggest to me if the
below method is correct way?

Here is my code :

begin
  for x in (select ... from table_name@dblink)
  loop
    begin
      insert into table_name
      (.....) (select ... from table_name@dblink)
    exception
      when dup_val_on_index then
           insert into error_table (table_name, date, 'found dups...');
      when invalid_number then
            insert into error table(table_name, date,'invalid no...');
     end;
end loop;
end;
/

      Rating: 5
Explicit vs
      Implicit cursors  June 17, 2002
Reviewer:
      Mark  from England
Tom,

I am now a convert to the use of implicit cursors.  There are books available by
well-respected authors (no names) that recommend (almost insist) on using
explicit cursors.  Was there ever a version of Oracle where explicit cursors
were better?

Anyway, I have come across a situation where I feel using explicit cursor is
better.

I want to return the most recent event, hence

FUNCTION f_get_latest_evn_id
  (p_req_id   IN   BINARY_INTEGER)
   RETURN BINARY_INTEGER IS
   CURSOR cur_get_events IS
    SELECT evn_id
      FROM events
     WHERE req_id = p_req_id
     ORDER BY dtm DESC,
              evn_id DESC;
   l_return_val   BINARY_INTEGER;
BEGIN
   OPEN cur_get_events;
   FETCH cur_get_events INTO l_return_val;
   CLOSE cur_get_events;
   RETURN l_return_val;
END;

Now I know  I can code it in a cursor for loop, exiting after the first record,
but I feel this may be confusing to other developers as a loop implies that you
want to retrieve more than one rocord (in my humble opinion).

I would be grateful for your views on this.

Mark

          Followup:

Like most things, there is a nugget of truth to be found in ancient history.

It was never so in the database itself, but in really old versions of forms in
client server -- a select into was "open, fetch fetch close".  It was not
optimized.  This is really really old though (and never was true in a stored
procedure in the database).

I would write your code as:

select evn_id into l_evn_id
  from (SELECT evn_id
      FROM events
     WHERE req_id = p_req_id
     ORDER BY dtm DESC,
              evn_id DESC)
where rownum = 1;

That can be much much more efficient then your method.  Consider:

declare
    l_object_name varchar2(30);
begin
    select object_name into l_object_name
      from ( select object_name
               from big_table
              order by timestamp desc )
     where rownum = 1;
end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.01       0.01          0          0          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 216
********************************************************************************

SELECT OBJECT_NAME
FROM
 (SELECT OBJECT_NAME   FROM BIG_TABLE ORDER BY TIMESTAMP DESC )  WHERE ROWNUM
  = 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      5.43       5.46      14848      14968         27           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      5.43       5.46      14848      14968         27           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 216     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  COUNT STOPKEY
      1   VIEW
      1    SORT ORDER BY STOPKEY
1101008     TABLE ACCESS FULL BIG_TABLE

declare
    l_object_name varchar2(30);
    cursor c is select object_name from big_table order by timestamp desc;
begin
    open c;
    fetch c into l_object_name;
    close c;
end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.01          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.01          0          0          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 216
********************************************************************************

SELECT OBJECT_NAME
FROM
 BIG_TABLE ORDER BY TIMESTAMP DESC

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      2      0.00       0.01          0          0          0           0
Fetch        1     19.68     164.57      25054      14968        129           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4     19.68     164.58      25054      14968        129           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 216     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT ORDER BY
1101008   TABLE ACCESS FULL BIG_TABLE

but of course it depends on indexes and such (but the rownum trick will be AT
LEAST as good, if not significantly better then).  The rownum optimization is
very nice.  Here the database KNOWS your intention (i want 1 row).  It can do
the work better.  In the second case -- it has to assume you want ALL OF the
rows.  Hence, it does a ton more work.

      Rating: 5
Re: Explicit vs
      Implicit cursors   June 17, 2002
Reviewer:
      Mark  from England
Very, very informative!  Yet again I have learned something new.

Many thanks,

Mark

      Rating: 5
oracle high
      performance sql tuning  August 26, 2002
Reviewer:
      A reader
Hi

I have this book called Oracle SQL hugh performance tuning by Guy Harrison.
There was this comparison between explicit and implicit cursors. He stated that
explicit cursor is faster than implicit because implicit cursors have to make an
extra fetch to test for row existence.

He actually suggested that a

select a
into l_a
from table
where condition

is probably slower than

declare cursor c1 select * from a where condition
begin
open c1
fetch ....
close c1
end

I am amazed! Is this true! It seems that he tested it because there was this
comparison chart in seconds!

          Followup:

In every single one of my tests (and it is trivial to setup) the select into is

o faster
o easier
o does not do the theoritical double round trip everyone proclaims.

If he tested it -- one would hope the test would be there for us to replicate?

It is trivial to test this theory (and see that IMPLICIT cursors rock, they
rule, they are the right way):

ops$tkyte@ORA817DEV.US.ORACLE.COM> set timing on
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_dummy dual.dummy%type;
  3  begin
  4      for i in 1 .. 10000
  5      loop
  6          select dummy into l_dummy from dual this_is_implicit;
  7      end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:01.77

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_dummy dual.dummy%type;
  3      cursor c is select dummy from dual this_is_explicit;
  4  begin
  5      for i in 1 .. 10000
  6      loop
  7          open c;
  8          fetch c into l_dummy;
  9          if (c%notfound)
 10          then
 11              raise no_data_found;
 12          end if;
 13          close c;
 14      end loop;
 15  end;
 16  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:02.29

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_dummy dual.dummy%type;
  3      cursor c is select dummy from dual this_is_explicit_done_right;
  4  begin
  5      for i in 1 .. 10000
  6      loop
  7          open c;
  8          fetch c into l_dummy;
  9          if (c%notfound)
 10          then
 11              raise no_data_found;
 12          end if;
 13          fetch c into l_dummy;
 14          if (c%found)
 15          then
 16              raise too_many_rows;
 17          end if;
 18          close c;
 19      end loop;
 20  end;
 21  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:02.50

I maintain that the above (or below) piece of code is what you must code in
order to get the benefits of a select into -- but none the less, the minimal
code in example 2 above is still provably slower then select into..

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_dummy dbms_sql.varchar2s;
  3      cursor c is select dummy from dual this_is_explicit_done_right2;
  4  begin
  5      for i in 1 .. 10000
  6      loop
  7          open c;
  8          fetch c bulk collect into l_dummy limit 2;
  9          close c;
 10          if ( l_dummy.count = 0 )
 11          then
 12              raise no_data_found;
 13          end if;
 14          if ( l_dummy.count > 1 )
 15          then
 16              raise too_many_rows;
 17          end if;
 18      end loop;
 19  end;
 20  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:02.48

So, as you can see, select into is "faster"

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set sql_trace=true;
Session altered.

Elapsed: 00:00:00.03
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_dummy dual.dummy%type;
  3  begin
  4      select dummy into l_dummy from dual this_shows_no_double_trip;
  5  end;
  6  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
ops$tkyte@ORA817DEV.US.ORACLE.COM>

and the TKPROF shows us:

SELECT DUMMY
FROM
 DUAL THIS_SHOWS_NO_DOUBLE_TRIP

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          1          4           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          1          4           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 554     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS FULL DUAL

If there was a double trip, the Fetch count would be 2.  We can see that with
this:

declare
    l_dummy dual.dummy%type;
    cursor c is select dummy from dual this_is_explicit_done_right;
begin
        open c;
        fetch c into l_dummy;
        if (c%notfound)
        then
            raise no_data_found;
        end if;
        fetch c into l_dummy;
        if (c%found)
        then
            raise too_many_rows;
        end if;
        close c;
end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.01       0.01          0          0          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 554
********************************************************************************

SELECT DUMMY
FROM
 DUAL THIS_IS_EXPLICIT_DONE_RIGHT

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          1          4           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          1          4           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 554     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS FULL DUAL

Note how the Fetch = 2, rows = 1.  If the select into did TWO fetches, it would
have 2 in the Fetch column.  It doesn't, it has 1 showing that an array fetch
was used like this does:

declare
    l_dummy dbms_sql.varchar2s;
    cursor c is select dummy from dual this_is_explicit_done_right2;
begin
        open c;
        fetch c bulk collect into l_dummy limit 2;
        close c;
        if ( l_dummy.count = 0 )
        then
            raise no_data_found;
        end if;
        if ( l_dummy.count > 1 )
        then
            raise too_many_rows;
        end if;
end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.01       0.00          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.02       0.01          0          0          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 554
********************************************************************************

SELECT DUMMY
FROM
 DUAL THIS_IS_EXPLICIT_DONE_RIGHT2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          1          4           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.01       0.00          0          1          4           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 554     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS FULL DUAL

See the 1 in the fetch column.  The select into is (has been for many many many
many years, at least since version 7.0 in 1993) optimized to array fetch.

Now, just to beat a dead horse, lets put it thru my simple test harness

http://asktom.oracle.com/~tkyte/runstats.html

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      cursor c is select * from dual;
  3      l_dummy dual.dummy%type;
  4      l_start number;
  5      l_run1  number;
  6      l_run2  number;
  7  begin
  8      insert into run_stats select 'before', stats.* from stats;
  9
 10      l_start := dbms_utility.get_time;
 11      for i in 1 .. 10000
 12      loop
 13          select dummy into l_dummy from dual;
 14      end loop;
 15      l_run1 := (dbms_utility.get_time-l_start);
 16      dbms_output.put_line( l_run1 || ' hsecs' );
 17
 18
 19      insert into run_stats select 'after 1', stats.* from stats;
 20      l_start := dbms_utility.get_time;
 21      for i in 1 .. 10000
 22      loop
 23          open c;
 24          fetch c into l_dummy;
 25          close c;
 26      end loop;
 27      l_run2 := (dbms_utility.get_time-l_start);
 28      dbms_output.put_line( l_run2 || ' hsecs' );
 29      dbms_output.put_line( 'run 1 ran in ' || round(l_run1/l_run2*100,2) ||
'% of the time' );
 30
 31      insert into run_stats select 'after 2', stats.* from stats;
 32  end;
 33  /
179 hsecs
244 hsecs
run 1 ran in 73.36% of the time

PL/SQL procedure successfully completed.

faster in single user mode..

ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value-a.value run1,
c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

NAME                                 RUN1       RUN2       DIFF
------------------------------ ---------- ---------- ----------
...
STAT...recursive calls              10031      30031      20000
...

26 rows selected.

and cheaper too -- lots less recursive calls (context switches)...

I'll stick with select into thanks very much!

      Rating:
      4
Reader  August 26, 2002
Reviewer:
      A reader
Tom,

Could you explain to us why there is 1 row in "execute"
(not fetch )and there were no blocks read( disk, query, current)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.01       0.01          0          0          0           1

Thsnks

          Followup:

ops$tkyte@ORA817DEV.US.ORACLE.COM> @test
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2          cursor c is select * from Dual;
  3  begin
  4          open c;
  5  end;
  6  /

PL/SQL procedure successfully completed.

SELECT *
FROM
 DUAL

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          0          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 554     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS FULL DUAL

You parsed it and opened it but did not actually fetch from it.  Or, you did a
DML operation that affected nothing:

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> update dual set dummy = 'x' where 1=0;

0 rows updated.

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

update dual set dummy = 'x'
where
 1=0

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.01          0          0          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 554

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  UPDATE DUAL
      1   FILTER
      0    TABLE ACCESS FULL DUAL

      Rating: 3
Yet to be
      convinced  November 17, 2002
Reviewer:  Phil
      Winfield  from UK
Hi Tom

You may be right but you're not comparing like with like. In your implicit
example all that is done is the implicit bit. In the explicit example you have
an if clause. I took this out and got the following results on my system.

  1  declare
  2        l_dummy dual.dummy%type;
  3    begin
  4        for i in 1 .. 10000
  5        loop
  6            select dummy into l_dummy from dual this_is_implicit;
  7        end loop;
  8*   end;
SQL> /

PL/SQL procedure successfully completed.

Elapsed: 00:00:01.22

  1  declare  l_dummy dual.dummy%type;
  2          cursor c is select dummy from dual this_is_explicit;
  3      begin
  4          for i in 1 .. 10000
  5          loop
  6              open c;
  7              fetch c into l_dummy;
  8             close c;
  9         end loop;
 10*    end;
SQL> /

PL/SQL procedure successfully completed.

Elapsed: 00:00:01.02
SQL>

So the explicit one is 20 miliseconds faster?

          Followup:

well, you need the if clause (but it matters not, read on).  what if -- just
what IF there was no data to fetch?  what then?  well, you have garbage in
l_dummy that is what.  whatever was left in l_dummy before the fetch will still
be there -- if you are in a loop doing this, that would be deadly as l_dummy
wouldn't even be NULL, it would be whatever was left in it from the last
iteration.

If you don't have that if check - you have what I would call "a bug".

Even removing the IF, using my test harness:

http://asktom.oracle.com/~tkyte/runstats.html
I cannot reproduce your results. (i attribute your .2 second to timing in plus.
You are measuring more then the operations in question - did you run in a single
user system?  if not other sessions would impact you and you need to run a
couple dozen times to "average" that. you timed the network, you timed sqlplus
drawing on your screen, etc. -- in any case -- I suggest a single block of code
that you time yourself when comparing A vs B in general.  I should have myself).

In my testing, every time, in all cases, explicit is slower.  Forget about the
facts

o that explicit is more code to type
o that explict is easier to introduce bugs (you have one in your example IMHO)

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_start number;
  3      l_run1  number;
  4      l_run2  number;
  5
  6      cursor c is select dummy from dual this_is_explicit;
  7      l_dummy  dual.dummy%type;
  8  begin
  9      insert into run_stats select 'before', stats.* from stats;
 10
 11      l_start := dbms_utility.get_time;
 12      for i in 1 .. 10000
 13      loop
 14                  select dummy into l_dummy from dual this_is_implicit;
 15      end loop;
 16      l_run1 := (dbms_utility.get_time-l_start);
 17      dbms_output.put_line( l_run1 || ' hsecs' );
 18
 19      insert into run_stats select 'after 1', stats.* from stats;
 20      l_start := dbms_utility.get_time;
 21      for i in 1 .. 10000
 22      loop
 23                  open c;
 24                  fetch c into l_dummy;
 25                  close c;
 26      end loop;
 27      l_run2 := (dbms_utility.get_time-l_start);
 28      dbms_output.put_line( l_run2 || ' hsecs' );
 29      dbms_output.put_line
 30      ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) || '% of the time' );
 31
 32      insert into run_stats select 'after 2', stats.* from stats;
 33  end;
 34  /
185 hsecs
241 hsecs
run 1 ran in 76.76% of the time

PL/SQL procedure successfully completed.

that was 817 on a 2cpu sparc running solaris (similar results on linux and
even windows)

220 hsecs
289 hsecs
run 1 ran in 76.12% of the time

PL/SQL procedure successfully completed.

138 hsecs
179 hsecs
run 1 ran in 77.09% of the time

PL/SQL procedure successfully completed.

198 hsecs
249 hsecs
run 1 ran in 79.52% of the time

PL/SQL procedure successfully completed.

125 hsecs
170 hsecs
run 1 ran in 73.53% of the time

PL/SQL procedure successfully completed.

Different releases -- same box -- from versions 7.3 8.0 9.0 9.1.  All pretty
consistent -- times are all different (thats the nature of the beast) but the
ratios of IMPLICT to EXPLICT is pretty consistent in the 70-80% range

Try that approach to measuring things and see if you can become convinced

Or, just keep on typing in extra code -- but don't forget that IF statement!
(and I think its a bug not to look for TOO_MANY_ROWS as well -- select into
generally means -- there will be at least one and at most one row.  If you just
want the first "random row" we happen to return -- use "and rownum = 1" on the
select into.

      Rating: 5
FYI, Feuerstein
      has finally changed his tune--a little.  November 17, 2002

Reviewer:  Jer Smith  from Salt Lake City UT

In the most recent (3rd) edition of PL/SQL programming, Feuerstein has finally
backed down on his statements that explicit cursors give any performance
advantage.  He still 'prefers' them as a structure (one may assume that he's
pretty used to writing them--and saying something is 'easier' or 'more readable'
is a matter of opinion to some degree--ask a Perl programmer) but has 'mellowed'
considerably in his adamancy.

      Rating: 5
Excellent
      thread  November 17, 2002
Reviewer:  Rajiv
       from NYC
Tom, You are the Master! The way you clear things beyond any speculation is
absolutely amazing.

Hats off to you!

      Rating: 4
Your Remarks on
      Closing Cursors...  November 19, 2002
Reviewer:
      Robert  from PA
Tom, I have wanted to ask you this for a while....

I have seen several times your remarks about closing cursors...something like...
"don't be so quick to close them...me, I never close them...." etc.

What exactly do you mean ?
Do you mean that if you create Explicit Cursor(s),
you keep them hanging around just because you might need to RE-OPEN them ?
I appreciate if you can demo what you mean and how that's
better.

Thanks

          Followup:

well, if you code in PLSQL and use STATIC sql -- you are done.  thats the
example.  PLSQL caches those cursors for you!

If you are programming in java -- i'm saying DO NOT

   o prepare
   o bind
   o execute
   o close

all of the time -- instead

  o prepare in your CONSTRUCTOR

  o bind + execute in your methods over and over

If you ever anticipate that statement being executed more then once or twice
(eg: you have a method to insert a row into a table and this method is called
500 times in your app -- DO NOT close the prepared statement between calls)

same for VB, C/C++ etc.

      Rating: 4
Implicit
      Cursors in Procedures  June 19, 2003
Reviewer:
      Brian
why does code like:

begin
for x in (select . . .)
loop
process;
end loop;
end;

work fine

but once you go and add

create or replace procedure myproc
as
begin
for x in (select . . .)
loop
process;
end loop;
end;

You start to receive compile errors.

Thanks.

          Followup:

you know what -- my car won't start, can you tell me why?

No error codes.
No example.
No error messages.
No clues other then "compile errors"

I'll guess (bet I'm right too :)

read:

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html

      Rating:
      3
Jeez  June 20, 2003
Reviewer:  Brian

Thanks.

I read your invoker/definer chapter a bunch after reviewing the code over and
over again thinking I had coded something wrong.  I didn't know how to ask the
question.

The error code didn't make any sense since the PL/SQL worked fine when not a
procedure. I didn't know how providing it would help.

I've learned a bunch here.

I've only been working with Oracle for a month.  I do not have a programming
background (I'm a financial analyst).  I have never had an Oracle class, and I
have had to teach myself everything to migrate away from using ACCESS to query
Oracle tables.

I'm doing the best I can.  I've moved more than half of my processes and
calculations from ACCESS and EXCEL into Oracle.

And I have to get the job done today because the developers won't have their
apps done for another year, and I have deadlines.

I apologize for not knowing how to ask the question.

Thanks Again.

          Followup:

It is that things like

"my database is going slow, why"
"i got an error from my code, why"
"i get some ora error message when connecting, why"
"i did <lengthly discussion of something> and it didn't work, why"

are impossible questions to answer -- always need the error message, always need
the error code.  There are literally hundreds of such messages/codes -- without
them, I can only I guess -- in your case I had to guess it was either pls-201,
ora-942 or ora-1031 (still don't know which one), but having that information
would make me say "ah hah, I can tell you exactly what went wrong".

Imagine if you will that you issue:

SQL> create or replace procedure p as begin null; end;
  2> /

ORA-XXXXX something went wrong, fix it and try again

and that was what you got, and even better:

SQL> show errors procedure P

Error on some line, you did something wrong, fix it and it will work ;)

scott@ORA920> create or replace procedure p
  2  as
  3  begin
  4          for x in ( select * from ops$tkyte.t )
  5          loop
  6                  null;
  7          end loop;
  8  end;
  9  /

Warning: Procedure created with compilation errors.

scott@ORA920>
scott@ORA920> show errors procedure p
Errors for PROCEDURE P:

LINE/COL ERROR
-------- -----------------------------------------------------------------
4/11     PL/SQL: SQL Statement ignored
4/35     PL/SQL: ORA-00942: table or view does not exist

perhaps, I should have mentioned the "show errors command", I forgot to
remember that you might not be aware of it

It is very useful when trying to diagnose what is going wrong where in the code
-- and would be useful to supply to anyone that would be looking at the problem.
 Sorry about that.

June 21, 2003 Srimal -- Thanks for the question regarding
      "Soundex searches", version 8.0.6

      You Asked
Hello Mr Tom,

I am trying to use Soundex search along with getting a particular number or rows
and sort it at the same time.... Could you give me an idea as to how exactly a
soundex search works... I know that using a soundex search does a full table
scan and if I introduce group by, then the process is further slows down.

Could you possibly guide me as to what are the important aspects that I need to
keep in mind when using a 'Soundex' .

I am also sending you a copy of the code that I am looking into..

There are two table A and B.. A is the Parent table and B is the child table..
with col1 joining A and B

select * from (select rownum r, t.* from (select A.col1, A.col2, B.col3 from A,
B where a.col1 = b.col1 and soundex(a.col2) = soundex('Smith')) t where rownum <
25 ) where r > 1

I see a significant decrease in speed if I introduce a group by function
(alternate to order by because of 8.0.6)

select * from (select rownum r, t.* from (select A.col1, A.col2, B.col3 from A,
B where a.col1 = b.col1 and soundex(a.col2) = soundex('Smith') group by col1,
col2, col3) t where rownum < 25 ) where r > 1

Thank you in advance
Srimal

      and we said...
Well, in Oracle8i, you can index a function (so you could index the soundex of a
column).  See

http://osi.oracle.com/~tkyte/article1/index.html
for details.

In Oracle8.0 and before, you might consider adding another column -- a "soundex"
column and using a trigger before insert or update for each row to maintain this
column.   Then you can index it and a search like:

   select * from t where soundex_column = soundex( 'some word' ) order by y;

an index on (soundex_column,y) would be able to use the index to find the
answser and sort it.

Yes, you will see a decrease in the second query.  It must return ALL rows,
group them and then return the first 25.  The first query just gets 25 rows.
Review &
      Followup

      Rating: 5
Thank
      you  September 14, 2001
Reviewer:  Srimal
       from Trenton
Thank you for a prompt reply... The solution that you presented answered a lot
of my questions...

      Rating: 4
Difference in
      words  June 20, 2003
Reviewer:  umesh
       from bangalore, india
Tom,
scott>select ename from emp ;

ENAME
-------
SMITH
ALLEN
WARD
JONES
MARTIN                                                      BLAKE
                                       CLARK
                  SCOTT
TURNER                                                      ADAMS
                                       JAMES

scott>select ename from temp ;

ENAME
----------SMITH                                                       ALLEN
                                                 WARD
                            JONES
       MARTIN                                                      BLAKE
                                              CLARK
                         SCOTT
    TURNED                                                      ADAMS
                                           JAMES1

I have 2 columns in my D.B. I need to pick up values from emp table and temp
table which differ in 1 letter or say 2 letters or say 3 letters ( according to
my needs)

I start with JAMES I need a query which gives me JAMES and JAMES1 because there
is a difference of 1 letter .

Adding to the complexity the 2 words of comparision may not sound the same

Thanks for your patience and  good work

      Rating: 4
difference in
      words  June 20, 2003
Reviewer:  umesh
       from bangalore india
scott>select ename from emp;

ENAME
----------
SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
à
TURNER
ADAMS
JAMES
à
Verri&#352;re

15 rows selected.

scott>select ename from temp;

ENAME
----------
SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
à
TURNED
ADAMS
JAMES1
à
Verri&#352;re

15 rows selected.

I have 2 columns in my D.B. I need to pick up values from emp table and temp
table which differ in 1 letter or say 2 letters or say 3 letters ( according to
my needs)

I start with JAMES I need a query which gives me JAMES and JAMES1 because there
is a difference of 1 letter .

Adding to the complexity the 2 words of comparision may not sound the same

Thanks for your patience and  good work

          Followup:

insufficient detail to make a sensible answer here ( and it is really a new
question, not a followup to the orig question but...)

if you just want to know what words are in table1/table2 and not in the other --
MINUS or FULL OUTER JOINS can do that

select * from t1 minus select * from t2 <<<=== everything in t1 not in t2
select * from t2 minus select * from t1 <<<=== everything in t2 not in t1

Now, YOU have to figure out how the heck you match JAMES up with JAMES1 from
those two subresults -- you'll need some sort of pattern match I suppose

      Rating: 4
use Levenshtein
      distance formula to find difference in words (for
      Umesh)  June 20, 2003
Reviewer:  Barbara
      Boehmer  from Norwalk, CA USA
Tom,

How about using a function to calculate the Levenshtein distance between two
strings to solve Umesh's problem of finding the difference in words.  Here is a
link for further information about the Levenshtein distance formula:

http://www.merriampark.com/ld.htm
I have included a usage example below, similar to Umesh's problem.  Please let
me know what you think.

Thanks,
Barbara

SQL> -- test data:
SQL> SELECT ename FROM emp2
  2  /

ENAME
----------
TURNER
ADAMS
JAMES
FORD
MILLER

SQL> SELECT ename FROM temp
  2  /

ENAME
----------
TURNED
ADAMS
JAMES1

SQL> -- function for calculating Levenshtein distance between two strings
SQL> -- (the number of characters that need to be changed, or alternatively
SQL> --  added or removed, to convert one string to the other):
SQL> CREATE OR REPLACE FUNCTION ld -- Levenshtein distance
  2    (p_source_string   IN VARCHAR2,
  3      p_target_string   IN VARCHAR2)
  4    RETURN             NUMBER
  5    DETERMINISTIC
  6  AS
  7    v_length_of_source    NUMBER := NVL (LENGTH (p_source_string), 0);
  8    v_length_of_target    NUMBER := NVL (LENGTH (p_target_string), 0);
  9    TYPE mytabtype IS     TABLE OF NUMBER INDEX BY BINARY_INTEGER;
 10    column_to_left         mytabtype;
 11    current_column         mytabtype;
 12    v_cost             NUMBER := 0;
 13  BEGIN
 14    IF v_length_of_source = 0 THEN
 15       RETURN v_length_of_target;
 16    ELSIF v_length_of_target = 0 THEN
 17       RETURN v_length_of_source;
 18    ELSE
 19       FOR j IN 0 .. v_length_of_target LOOP
 20         column_to_left(j) := j;
 21       END LOOP;
 22       FOR i IN 1.. v_length_of_source LOOP
 23         current_column(0) := i;
 24         FOR j IN 1 .. v_length_of_target LOOP
 25           IF SUBSTR (p_source_string, i, 1) =
 26          SUBSTR (p_target_string, j, 1)
 27           THEN v_cost := 0;
 28           ELSE v_cost := 1;
 29           END IF;
 30           current_column(j) := LEAST (current_column(j-1) + 1,
 31                       column_to_left(j) + 1,
 32                       column_to_left(j-1) + v_cost);
 33         END LOOP;
 34         FOR j IN 0 .. v_length_of_target  LOOP
 35           column_to_left(j) := current_column(j);
 36         END LOOP;
 37       END LOOP;
 38    END IF;
 39    RETURN current_column(v_length_of_target);
 40  END ld;
 41  /

Function created.

SQL> SHOW ERRORS
No errors.
SQL> -- select Levenshtein distance between ename values in emp2 and temp
SQL> -- (intentional cartesian product to compare all values):
SQL> SELECT   emp2.ename, temp.ename,
  2            ld (emp2.ename, temp.ename) AS lev_dist
  3  FROM     emp2, temp
  4  ORDER BY emp2.ename, temp.ename
  5  /

ENAME      ENAME        LEV_DIST
---------- ---------- ----------
ADAMS      ADAMS               0
ADAMS      JAMES1              4
ADAMS      TURNED              6
FORD       ADAMS               5
FORD       JAMES1              6
FORD       TURNED              4
JAMES      ADAMS               3
JAMES      JAMES1              1
JAMES      TURNED              5
MILLER     ADAMS               6
MILLER     JAMES1              6
MILLER     TURNED              5
TURNER     ADAMS               6
TURNER     JAMES1              6
TURNER     TURNED              1

15 rows selected.

SQL> -- limit selection to Levenshtein distance of one or less:
SQL> SELECT   emp2.ename, temp.ename,
  2            ld (emp2.ename, temp.ename) AS lev_dist
  3  FROM     emp2, temp
  4  WHERE    ld (emp2.ename, temp.ename) <= 1
  5  ORDER BY emp2.ename, temp.ename
  6  /

ENAME      ENAME        LEV_DIST
---------- ---------- ----------
ADAMS      ADAMS               0
JAMES      JAMES1              1
TURNER     TURNED              1

          Followup:

do that on a realistic sized set of data ;)

gotta a couple of days?  I was recently working with someone using that exact
function.  The cartesian products blow up fast (100 rows = 100x100 = 10,000
compares.  1,000 rows = 1000x1000=1,000,000 compares and so on).

We estimated that it would take a couple of weeks to finish.

So.... we created the cartesian product in a single table

c1  c2   score
--- ---- -------
...  ...  ...

leaving score "blank', then using the technique from

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:10498431232211
we wrote a Pro*C program to bulk fetch 100 rows out, compute the distance
between the two and update it back in bulk.   we got the distances computed
using "parallel 32" in about 4-5 hours that way.

      Rating: 5
Hats off to you
      Barbara Boehmer !!!!  June 20, 2003
Reviewer:
      A reader

      Rating: 5
response to
      follow-up  June 21, 2003
Reviewer:  Barbara
      Boehmer  from Norwalk, CA USA
Tom,

Thanks for the feedback.  You're right that it does get very slow with large
data sets.  It sounds like you came up with a nifty solution to speed things up.

I have been using the function for data clean-up of non-validated data from
other systems.  However, I am looping through a cursor and just comparing one
value at a time to the appropriate lookup table.  I am also able to limit the
number of rows that I have to compare to, based on other filter conditions, and
am only using the function as a last resort, when there isn't an exact match or
soundex match or match based on other criteria.  I also do some general
standardization, such as converting everything to upper case and removing double
spaces and so forth, prior to any comparisons.

I have a weekly process that takes a total of about ten minutes to load four
text files into staging tables via SQL*Loader (we are still using Oracle 8.1.7,
so I can't use external tables like in 9i yet), identify about three hundred
rows from the parent table out of about twenty thousand, and several hundred
rows from the three child tables, clean up the data in many of the columns, then
insert them into several Oracle tables on our system.  So, it has worked well
for me.  It beats the heck out of cleaning it up manually or eliminating any
data that can't be validated otherwise.

By the way, I should be receiving your book in the mail soon.  I procrastinated
on buying it until it was too late due to Wrox going out of business.  However,
a friend recently located a used copy, which I purchased, and should be
receiving in the mail by Friday.  I read Chapter One on-line.  Making that first
chapter available on-line is an excellent method of enticing people to purchase
the book to read the rest, which I am looking forward to shortly.

Keep up the good work!

Thanks,
Barbara

      Rating: 5
Thanks to
      Barbara and Tom  June 23, 2003
Reviewer:
      umesh  from bangalore india

June 21, 2003 Jenny -- Thanks for the question regarding
      "Calculating total size of tablespaces with autoextend on", version 8.1.6

      You Asked
Tom,

I have a sql statement and result set from the sql statement(see below).  All
datafiles that I have in all tablespaces have "AUTOEXTEND ON".  My question to
you is - according to the result below, does the total size (SIZE_MB) include
the possible autoextends to the datafiles, or all datafiles will be retrofit to
fixed size?

SELECT
   d.tablespace_name TABLESPACE_NAME,
   d.contents TYPE,
   d.extent_management EXT_MGT,

TO_NUMBER(TO_CHAR((a.bytes/1024/1024),'999,999,999,990.000'),'999,999,999,990.000
') SIZE_MB,
   TO_NUMBER(TO_CHAR((a.bytes/1024 /1024) - ((a.bytes -
NVL(f.bytes,0))/1024/1024),'99,999,990.000'),'99,999,990.000') FREE_MB
,

TO_NUMBER(TO_CHAR((a.bytes/1024/1024),'999,999,999,990.000'),'999,999,999,990.000
') - TO_NUMBER(TO_CHAR((a.bytes/1024 /1024
) - ((a.bytes - NVL(f.bytes,0))/1024/1024),'99,999,990.000'),'99,999,990.000')
USED_MB,
   '' || TO_CHAR(((a.bytes - NVL(f.bytes, 0.00001))/a.bytes) * 100,'990.000') ||
'  ' PCNT_USED
FROM
   sys.dba_tablespaces d,
   sys.sm$ts_avail a,
   sys.sm$ts_free f
WHERE d.tablespace_name = a.tablespace_name
AND f.tablespace_name (+) = d.tablespace_name
AND TO_CHAR(((a.bytes - NVL(f.bytes, 0.00001))/a.bytes) * 100,'990.000') >= 60
AND d.tablespace_name not in ('OLD_FILELOG', 'TOWER_MED')
AND d.tablespace_name not like 'TBSP_LUC_PT_2000%'
ORDER BY PCNT_USED desc, TABLESPACE_NAME desc;

TABLESPACE_NAME                TYPE      EXT_MGT       SIZE_MB    FREE_MB
USED_MB PCNT_USED
------------------------------ --------- ---------- ---------- ----------
---------- ----------
MON_SML                        PERMANENT LOCAL         256.063      81.25
174.813   68.269

Thanks,
Jenny

      and we said...
it is showing only currently allocated.  they are using BYTES, not MAXBYTES.

Here is a script that shows it all, usage:

SQL> @free 1

(to order by tablespace name, @free 2 to order by tablespace size and so on)

--------------------------------------------------------
-- free.sql
--
-- This SQL Plus script lists freespace by tablespace
--------------------------------------------------------

column dummy noprint
column  pct_used format 999.9       heading "%|Used"
column  name    format a16      heading "Tablespace Name"
column  Kbytes   format 999,999,999    heading "KBytes"
column  used    format 999,999,999   heading "Used"
column  free    format 999,999,999  heading "Free"
column  largest    format 999,999,999  heading "Largest"
column  max_size format 999,999,999 heading "MaxPoss|Kbytes"
column  pct_max_used format 999.9       heading "%|Max|Used"
break   on report
compute sum of kbytes on report
compute sum of free on report
compute sum of used on report

select (select decode(extent_management,'LOCAL','*','') from dba_tablespaces
where tablespace_name =
b.tablespace_name) || nvl(b.tablespace_name,
             nvl(a.tablespace_name,'UNKOWN')) name,
       kbytes_alloc kbytes,
       kbytes_alloc-nvl(kbytes_free,0) used,
       nvl(kbytes_free,0) free,
       ((kbytes_alloc-nvl(kbytes_free,0))/
                          kbytes_alloc)*100 pct_used,
       nvl(largest,0) largest,
       nvl(kbytes_max,kbytes_alloc) Max_Size,
       decode( kbytes_max, 0, 0, (kbytes_alloc/kbytes_max)*100) pct_max_used
from ( select sum(bytes)/1024 Kbytes_free,
              max(bytes)/1024 largest,
              tablespace_name
       from  sys.dba_free_space
       group by tablespace_name ) a,
     ( select sum(bytes)/1024 Kbytes_alloc,
              sum(maxbytes)/1024 Kbytes_max,
              tablespace_name
       from sys.dba_data_files
       group by tablespace_name
       union all
      select sum(bytes)/1024 Kbytes_alloc,
              sum(maxbytes)/1024 Kbytes_max,
              tablespace_name
       from sys.dba_temp_files
       group by tablespace_name )b
where a.tablespace_name (+) = b.tablespace_name
order by &1
/

Review &
      Followup

      Rating: 4
Good
      Script  November 27, 2002
Reviewer:  Jenny
       from San Francisco, CA

      Rating: 5
Nice reporting
      Script!  December 05, 2002
Reviewer:  Dapid
      Candra  from Jakarta, Indonesia
Thanks! :)

      Rating: 4
Tablespace
      /datafile re allocation  June 20, 2003
Reviewer:
      Habibulla  from India
We are having production server, which contains data ( 5 GB) for last 3 years.
(4 th financial year is going to be started from Jul'03)
2 applications are running on the oracle server (8.1.5) in Novell
netware operating system, running in archive log mode. - 2 GB RAM (6 schemas)

Since we are having only work group server, we have been told by our
vendor that, partition cannot be enabled in our version. Hence we
used to create seperate schema for each year and master /common tables will
have synonyms across the year. ( I also refered asktom for confirming
the same)

Based on consultancy service's advise, we exported all user and
imported to as "single extent", 6 months back.
(According to Asktom, it is myth and unnecessary approach)
And also the extents are not even and tables not classified
based on the tablespace extentes.

And also we used alter the existing tables (where huge data avaialble),
hence lot of "Migration" would have happened. And also chaining
is about .00004 (chained row/total). (Used your tips for checking)

Hence now I am going to do the following:

1. Keeping last 3 years transaction table in different tablespaces,
classified based on the extent size. ( I went as per your suggestion
of having tablespaces based on sizes small, medium and large)

2. Master tables in different set of tablespaces  classified based on the extent
size.

3. Index tables  in different set of tablespaces  classified based on the extent
size.

And each datafile directory, will be placed in differrent hdd based on
read/write operation.

As per the above norms, we are going to do the following:

1. Halting the production server for 24 hours
2. Taking cold backup. (2 sets)
3. Taking exports of all users (logical)

4. Dropping all schema / tablespace in production server
5. Create new tablespace according to the plan
6. Create schemas
7. Create tables based on new storage allocation & Table space name
    (as per plan)
8. Import the data in respective schemas
9. compile all invalid objects

Note: For first 3 years of operation, set of tablespaces will be used.
/ masters another set of tablespaces will be used/

And from next year of operation, different set of tablespaces will be
used for next 3 years.

My question --> Is my Approach right? Pl advise.

(I have bought your book, expert one one and using for tuning my applications)

          Followup:

missing step 0

0) after testing the following steps on my test machine, I will perform the
following 9 steps

      Rating: 4
Thanks.
        June 21, 2003
Reviewer:  Habibulla
       from India
Thanks for the answer. Yes I will do after doing it in test server.

Will this excersie helps both performance  & space management, or only "space
management"

          Followup:

probably just space and management of space in your case, not lots of extending
going on.

June 21, 2003 --
      Thanks for the question regarding "set timing on", version 8.1.6

      You Asked
Tom

1.Set timing on in sql plus show a figure like 56430 , what does that mean. How
much time does it represent?

2.I dont want to use set timing on , and using tkprof means spending 10 hours
for the dba to mail me the trace file.

I want to know how can I use dbms_utility from sqlplus  and set some variables,
then execute a query  and see how much time it has taken to execute .

How should I do it.

or let me put it this way what is the best way to find out how much time a sql
is taking using dbms_utility from sql plus.

Thansks a lot.

      and we said...
1) you have an OLD version of sqlplus on windows.  It is hundreds of seconds.

2)

set serveroutput on
variable n number
exec :n := dbms_utility.get_time;
select ......
exec dbms_output.put_line( (dbms_utility.get_time-:n)/100) || ' seconds....' );

If you just get the version of sqlplus that goes with your database -- 816 --
it'll do the "right thing"

Review &
      Followup

      Rating: 5
can set
      autotrace on trace only  May 25, 2002
Reviewer:
      Vasaku
get me only the time , without executing the query

Because when we execute the query in sql plus the resultant time will include
the time of the screen output also.

will set autotrace on trace only , do the trick for me( as I told you above I
cannot use set timing on or sql_trace, for the given reasons)

          Followup:

I think you mean "without PRINTING the query results".

set autotrace traceonly -- runs the query, fetches the rows and prints the stats
but not the row data.

set autotrace traceonly explain -- just does an explain plan for SELECTS, does
not execute them (but would be useless for timing).

autotrace adds some overhead of its own -- so beware, it can add LOTS of time to
the timings (querying v$ tables can be slow and that is what it does).

You can also use set termout off/on before/after the query to surpress the
display.

      Rating: 4
if it takes 10
      hours...  May 26, 2002
Reviewer:
      A reader
to get the tkprof output, its a time for a new dba

      Rating: 5
so this script
      should give me reasonably accurate time using sqlplus  May
      26, 2002
Reviewer:  Vasaku
set serveroutput on
set termout off
variable n number
exec :n := dbms_utility.get_time;
select ......
exec dbms_output.put_line( (dbms_utility.get_time-:n)/100) || ' seconds....' );

set termout off

But , I dont think 'exec :n := dbms_utility.get_time;
', is a valid statement from within a sql script.

Your opinion please.( what I'm trying to arrive at a is a simple solution for
the said task from sqlplus)

          Followup:

It needs no opinion.  It definitely works (i would not have suggested it
otherwise)

take a script like this:

set echo on
variable n number
exec :n := dbms_utility.get_time
set termout off
select * from all_users;
set termout on
exec dbms_output.put_line( ((dbms_utility.get_time-:n)/100) || ' seconds...' );

and it'll do this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> variable n number
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec :n := dbms_utility.get_time

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout off
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line(
((dbms_utility.get_time-:n)/100) || ' seconds...' );
.02 seconds...

PL/SQL procedure successfully completed.

Note that termout off will not 100% remove the "print" overhead as sqlplus still
goes through the steps to format -- it just doesn't print it on screen.  Whether
that is faster or slower then set autotrace traceonly depends on how active your
system is (and hence how slow the v$ queries are) and the volumn of data.

      Rating: 5
but termout off
      still showing the output  May 29, 2002
Reviewer:
      A reader
Tom, I expect termout off not to show the output but look at the following

SQL> set termout off
SQL> select 'x' from dual;

'
-
x

why is this so?

As I see you have stated that this setting will suppress output.

          Followup:

You need to run it in a script.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout off
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from dual;

D
-
X

ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout on
ops$tkyte@ORA817DEV.US.ORACLE.COM> !cat test.sql
set termout off
select * from dual;
set termout on

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

All interactively typed commands will always display on screen.

      Rating: 4
How to read
      Elapsed time reported by sql*plus  December 26, 2002

Reviewer:  Steve  from NYC, USA
Hi Tom,

I just ran very long query after I set timing on
I got the following

Elapsed: 09:593:35598.00

Is that 8 hours 593 min 35598 sec?  I am using 8.1.7 sql*plus

Thanks!

Steve

          Followup:

sqlplus set timing is "broken" occasionally -- especially in windows.

I don't know what time that might be....

tkprof is the best way to time.

SQL> set time on

would be good as well (the prompt will contain the time, you can always do the
math yourself after that)

      Rating:
      4
  March 07, 2003
Reviewer:  gree
      China
TOM,

 If I count the time for a sql script with a spool command , how to remove the
the time message in the tail of the spooled target file.

Thanks!

          Followup:

don't use set timing on.

Do this instead:

variable n number
exec :n := dbms_utility.get_time
spool ....
.....
spool off
begin
   dbms_output.put_line
   ( (round((dbms_utility.get_time - :n)/100,2)) || ' seconds' );
end;
/

      Rating: 4
How reliable is
      set timing on.  April 16, 2003
Reviewer:
      A reader
Hi tom,

Wanted to know how reliable is set timing on. Say i'm running two different
select statements on a table for the same set of results. Will the timing on
result (alone) justify which select statement is better?

Is it possible that the SQL timing shows the first select statement to be faster
but when i see the tkprof result i see that the second select statement is
faster (measuring the CPU and elapsed time) than the first one ?

Thanks and Regards

          Followup:

not at all.  tkprof is the ONLY tool to use to see what query is "better".

looking for CPU time
looking for logical io's.

timing on in sqlplus times how fast your client machine happened to be at that
instant as much as it times how fast the server was.

tkprof is the source of truth.
timing on is good for very little.

      Rating: 5
Thanks a lot
      TOM  April 16, 2003
Reviewer:  A reader

      Rating: 3
Arraysize
      ?  April 16, 2003
Reviewer:  sush  from
      SJ,CA
I think the arraysize(default is 15) in sqlplus make a difference in query
output apart from set termout on ...

How do we set the arraysize in plsql(ofcourse in sqlplus we can say set
arraysize = 1000 or so )..

          Followup:

  fetch c bulk collect into ... limit N;

for selects (look up bulk collect) and

  forall i in 1 .. N
    DML_statement

for modifications.

      Rating: 4
set timing
      on  June 21, 2003
Reviewer:  vinodhps
       from India, chennai
Hi .. tom,
In most of you answers you get that Elapsed: "time".
how are you getting that...
iam using oracle 9i ..
set timing on command gives me.. real:"timing".
can you please tell me that..
thanks

          Followup:

you are using oracle9i the database but apparently you are using sqlplus
8.something on windows as  your client.

it is your sqlplus version.

      Rating:
      5
Thanks  June 22, 2003
Reviewer:
      vinodhps  from India, chennai
Thanks tom,
Yes iam still using.. sql 8 version..
thanks..

June 21, 2003 Kailash -- Thanks for the question regarding
      "Closing/Reopening a cursor", version 8.1.7

      You Asked
Hi,

I tried submitting questions, but they do not appear in my Your questions
section and so resending this one :

DECLARE
    cursor c1 is select * from A;
    c1var c1%ROWTYPE;
    cursor c2 is select * from B where col1 = c1var. col1;
    c2var c2%ROWTYPE;
BEGIN
    OPEN c1;
    LOOP
       FETCH c1 INTO c1var;
       EXIT WHEN c1%NOTFOUND;
       OPEN c2;
       LOOP
           FETCH c2 INTO C2var;
           EXIT WHEN c2%NOTFOUND;
           /* ... Other logic */
       END LOOP;
       CLOSE c2;
    END LOOP;
    CLOSE c1;
END;

How do you avoid closing and reopening cursor c2 in a loop. Your article
suggests " do not close and open cursor in a loop". But without that is there
any way to process all rows of B for a new row of A. If so how?
Which join method will be advantageous under various scenarios. Could you
provide me with a comparison chart showing advantages/disadvantages of each
method.
What things to look for in EXPLAIN PLAN?

      and we said...
Note to all -- the "your questions tab" only includes questions:

o answered by me but not published to the public
o answered by my and published

You will not see questions to which I did not yet answer (so you can trust the
question is in my queue -- no need to submit it say 5 or 6 times over and over)
or to which I felt no response was necessary.

I answered this one from one of the other (many) copies you submitted.
June 21, 2003 Scott -- Thanks for the question regarding
      "Application logging and error handling", version 9.2.0

      You Asked
Hi Tom

Firstly I'd like to thank you for providing such a helpful and informative site
for all of us.

Next I'd like to get your "two cents worth" on a debate that is currently going
on at the site where I'm working. The subject of the debate covers the spectrum
of application logging, error handling, and a development framework.

One school of thought (the developers) like the idea of having a whole lot of
generic api functions and procedure to handle application event logging, error
logging, and so on. The merits of this approach they say is that it enables them
to create standard code templates and methodologies which make developing new
parts of applications quicker and easier because many of the "house keeping"
functions are already in existence. Therefore the one "new" code required is
what is required to implement the new features or requirements in the
application.

The other school of thought on this (mainly the dbas) is that this approach is a
terrible idea. They argue that a piece of code should (such as the code for a
feed from a source system in to a datawarehouse) should be largely stand alone,
so that all the actions that the code is taking are clearly visible and
understandable to the person who is supporting the code. The idea is to prevent
the support person needing to jump around through a bunch of other procedures
and functions to try and work out what is going on.

To give some context to the debate the application developers work inhouse
solely on this system, while the DBA function has been outsourced to a large
faceless outsourcer. This means that the DBA support is provided by a generic
pool of DBAs - who aren't always all that skilled, conscientious, or interested
- and who aren't necessarily going to be familiar with the system or the code
running within it.

So I was wondering out of interest what your thoughts were on this debate. Do
you favour code that stands alone (which may mean repetition of things like
logging functions and so on) and is easily understood by someone who is looking
at it for the first time? Or do you favour a modular, somewhat more generic
approach that allows for more rapid development, but a potentially steeper
learning curve for support people?

From reading this site I get the impression you've seen the full spectrum of
development methodoligies, theories, frameworks, and so on. Based on your
experiences what general recommendations would you make with regard to
application logging (eg the logging of application events), debug information,
and error handling? Are generic templates and standard api calls a good idea, or
do you favour a bespoke approach to problems that would mean solving each
problem on it's merits even if this meant having to reinvent the wheel on
occasion?

Thanks in advance for any advice or thoughts you might have.
Cheers
Scott

      and we said...
why are DBA's digging into code?

I am totally into modular code.

I am totally into reuse of code.

Tell me -- do you avoid the use of DBMS_SQL because it is a utility package?
Or UTL_FILE?
Or DBMS_LOB?
Or <any dbms_*, utl_* package here>?

They are the same concept.

Especially for something as straightforward as error logging and such -- it
would be so much better to be modularized.  Suppose 6 months from now, instead
of logging to a FILE (assuming they are now -- doesn't matter) they need to log
to a table?  You want to dig through the code?

Suppose they are logging to a file using a naming convention and directory path
today.  Next week, it must change to a different convention/location.  You want
to dig through that code?

I push things like this -- I've got a cool debug package they can start with if
you like.  I centralized all of that stuff myself and use it over and over.
Review &
      Followup

      Rating: 4
Application
      logging and error handling  June 21, 2003

Reviewer:  Scott  from London
I agree with pretty much all you are saying. I've been on the
modular/templated/code reuse side of the debate.

The debug package you refer to sounds like a good place to start, so if you
wouldn't mind posting it I'm sure myself and all the reader of your site
certainly wouldn't mind borrowing the ideas from it.

Why are the DBAs digging into the code you ask? Long story... it all starts with
an incredibly badly negotiated SLA for an outsourcing arrangement, and lets just
say that nobody but the outsourcer lives happily ever after.

Thanks for your thoughts
Scott

          Followup:

get this presentation

http://asktom.oracle.com/pls/ask/z?p_url=http%3A%2F%2Fasktom.oracle.com%2F%7Etkyte/NOUG.tgz&p_cat=NOUG_SLIDES&p_company=10
the debug.f stuff is in there.  (use dbms_application_info too!)

      Rating: 5
Error
      logging  June 22, 2003
Reviewer:  Darren
       from London, UK
Hi,

i use a object (type) throughout my code to doing error logging..

eg:

begin

  myLogger.RecordPoint(8000, 'any optional notes');

  insert into blah
              (cols)
       values (values..);

  ...and so on..

and (only) in the top level caller:

exception
  when others
  then
    myLogger.writeLog;
    raise; -- or redirect to an error screen (if web based);
end top_level;

the object recordPoint just keeps the ID in memory and if i have a full debug
flag on it does format_call_stack() too.
The writeLog writes out to an error table (autonomous) i have defined with that
number i remembered and other relevent data.
I have a view sitting on that table and a parent table that matches the
errorPoint number with pacakge/procedure information for our App support guys to
view that tells them where the fault occured and the sqlerrm etc.

I find it works really well in practice, and i can change the way the error
logger works without affecting the main procedural code.

      Rating: 5
Something that
      may be useful to someone  June 24, 2003
Reviewer:
      malcolm  from london
http://jakarta.apache.org/log4j
http://jakarta.apache.org/log4j/docs/manual.html
An open source java logging framework from the folks at the apache jakarta
project.  I haven't tried it yet, but I've been meaning to load the classes into
Oracle and create a pl/sql api.

      Rating:
      5
  August 15, 2003
Reviewer:
      A reader

June 22, 2003 -- Thanks for the question regarding "SPFILE
      backup", version 9.2.0

      You Asked
If I started my database by using the default spfile, my understanding is that I
should not make a copy of the spfile at os level when the instance/database is
up and running as this file is being used by oracle. Also, I should not copy the
controlfiles for that matter when the instance is up. Is this correct? If so,
how RMAN backs up the spfile along with the control files when the database is
up? Thanks.

      and we said...
rman uses database processes, not OS utilities, to read and copy the data --
hence it knows how to access these files "safely"

Just like it knows how to access the datafiles safely.
Review &
      Followup

      Rating: 5
RMAN in
      Standard Edition  June 22, 2003
Reviewer:
      Reader
We are having 9i R2 Standard Edition on Unix (AIX). Does oracle provide all
features of RMAN in standard edition as that of in Enterprise Edition. In other
words, what are the features that I miss with regard to RMAN in standard
edition. Thanks.

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96531/ch5_edit.htm#75672
show the feature available by edition.  so for example:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96531/ch5_edit.htm#89771
shows incremental backups -- not in standard.  Legato Storage Manager -- is in
standard.

for example...

June 22, 2003 Radhakrishnan -- Thanks for the question
      regarding "Resequence The Column Order in a Table", version 8.1.7

      You Asked
Hi Asktom, I want to resequence the columns in a Table. Can you please
tell me how to do it?. eg., EMP table contains following columns.

EMP Table (Orginal Sequence)
----------------------------
EMPNO,
ENAME,
SAL,
TITLE,
DEPTNO,
JOIN_DATE,
DATE_OF_BIRTH

EMP table (After resequence)
----------------------------
EMPNO,
ENAME,
TITLE,
SAL,
DEPTNO,
JOIN_DATE,
DATE_OF_BIRTH

Note: The TITLE and SAL columns are resequenced here. This table has some
dependant objects in the databse.
Best Regards,
Radha

      and we said...
Views do this, rename the table, create a view, grant on the view instead of the
table.

ops$tkyte@ORA817DEV.US.ORACLE.COM> desc emp
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 EMPNO                               NOT NULL NUMBER(4)
 ENAME                                        VARCHAR2(10)
 JOB                                          VARCHAR2(9)
 MGR                                          NUMBER(4)
 HIREDATE                                     DATE
 SAL                                          NUMBER(7,2)
 COMM                                         NUMBER(7,2)
 DEPTNO                                       NUMBER(2)

ops$tkyte@ORA817DEV.US.ORACLE.COM> rename emp to emp_table;

Table renamed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace view emp
  2  as
  3  select empno, ename, sal, job, hiredate, mgr, deptno, comm
  4  from emp_table;

View created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> desc emp;
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 EMPNO                               NOT NULL NUMBER(4)
 ENAME                                        VARCHAR2(10)
 SAL                                          NUMBER(7,2)
 JOB                                          VARCHAR2(9)
 HIREDATE                                     DATE
 MGR                                          NUMBER(4)
 DEPTNO                                       NUMBER(2)
 COMM                                         NUMBER(7,2)

ops$tkyte@ORA817DEV.US.ORACLE.COM>
Review &
      Followup

      Rating: 3
Resequence
      column order in a table  December 11, 2001

Reviewer:  Radhakrishnan A  from INDIA
Thanks tom. I find it is useful for my problem.

      Rating: 2
Degrade
      Performance  December 11, 2001
Reviewer:
      Prince  from Pakistan
Although your answer is right. But I think the view will degrade performance if
the size of the table is very large. So there should be some other way, if
Oracle is smart..

          Followup:

The view will not degrade query performance.  It REALLY TRULY does not have
anything to do with the size of the table either (that part of your comment is
totally inaccurate -- tell me, how could a VIEW affect the performance of a
query as the table gets larger????? As long as the plans are the same - and they
will be -- it cannot)..

So, I think we are sort of smart in this way.  Benchmark it and see.  The only
thing the view does is add a TINY extra step to the parse, one that you will not
notice or detect (and certainly isn't in any way affected by the SIZE of the
table).

So, this is the correct way to do it, will be the way to do it, does not affect
query plans AT ALL (and that is what would affect performance, a changed plan).

Just yet another case of fear of the unknown, or believing in myths (the myth
being "I've heard views are evil").

Not that the order of columns should even be RELEVANT, not that your
applications should even count on their order being in some fixed way (by the
way)....  This is EXACTLY what views are designed for.  Use them (please -- you
pay for these tools, just use them).

      Rating:
      4
order  December 11, 2001
Reviewer:
      j  from MA
I thought the order of columns(tables not views) had a direct impact on
selects,index performance ie: more queried columns to the top, indexed columns
at the top. We resequence all the time ( the old fashioned way ,create newtab as
select <new order> from tab - drop tab rename newtab tab - reconstraint). Lots
of tables, lots of resequencing. Have we been mistaken or has something changed
that we missed taking advantage of?

          Followup:

If and only if the row is chained (too big to fit on a block) would this matter
-- otherwise all of the data is there on the block, we read the block -- we have
everything we need.  Makes no difference really if its the last column or the
first (unless its chained, then you might notice a difference).

Indexed columns -- that really makes no difference, they are copied into the
index, the table isn't relevant (or even necessary) in a index
range/unique/whatever scan.

You have most likely been wasting CPU cycles doing this.  One way to find out
for sure -- benchmark yourself.  Save history (eg: it takes X seconds to do Y
units of work).  Save it every day.  After major changes -- compare.  Data like
that is the only way to have evidence that what you are doing has any sort of
impact.

try little tests like:

create table t
as
select 1 c1, all_objects.*, 1 c2
  from all_objects
/

alter session set sql_trace=true;
set termout off
select c1 from t;
select c1 from t;
select c1 from t;
select c2 from t;
select c2 from t;
select c2 from t;
set termout on

the tkprof report shows us:

select c1
from
 t

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        3      0.00       0.00          0          0          0           0
Execute      3      0.00       0.00          0          0          0           0
Fetch     3453      0.31       0.37          0       4158         36       51741
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     3459      0.31       0.37          0       4158         36       51741

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 63

Rows     Row Source Operation
-------  ---------------------------------------------------
  17247  TABLE ACCESS FULL T

********************************************************************************

select c2
from
 t

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        3      0.00       0.00          0          0          0           0
Execute      3      0.00       0.01          0          0          0           0
Fetch     3453      0.32       0.32          0       4158         36       51741
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     3459      0.32       0.33          0       4158         36       51741

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 63

Rows     Row Source Operation
-------  ---------------------------------------------------
  17247  TABLE ACCESS FULL T

due to the granularity of the clock -- sometimes the first query will be
marginally faster, sometimes the second -- they are the same for all intents and
purposes.

      Rating:
      4
  December 11, 2001
Reviewer:
      A reader
j  from MA your method is really outdated, the only reason I have heard you have
to resequence the column order is to put the nullable columns at the end to save
space but that was it, I have heard because I have never have to do it except
twice to maintain some kind of consistency with other tables which wasnt really
necessary but since I was asked to do it...

          Followup:

True -- thats a valid point.

You could strive to put columns you believe will most likely believe to be NULL
at the "end" of the table.  Trailing null columns in a row take zero bytes of
storage.  Interior null columns (those with non-null columns after them) take 1
byte.

it would not be worth your while to reorg the order of the columns in order to
save a byte.

It would howerver keep the developers on their toes!  You would find the
developers that used "select *" or insert into t values  - with no column list
-- very fast!!

      Rating: 5
The
      Myth  December 12, 2001
Reviewer:
      A reader
Tom...

Another Book question... :-)

Why Dont u write a Book about

"Oracle Myths and Superstitions" That will sell like a Hot Cake and i hope it
will be a small book compared to Expert One-on-One...

Regards,
Ganesh R

          Followup:

are you kidding -- it would be BIGGER then expert one on one...

believe it or not, expert one on one would have been bigger had i followed my
outline, we ended up cutting it at the end...

      Rating: 4
Ganesh is
      right.....  December 12, 2001
Reviewer:
      Adrian  from UK
Tom

Ganesh is right - reading Prince's comments above just makes you realise how
many truly ignorant "Oracle developers" there are out there. They just do not
understand what Oracle is all about - they hear a SQL-Server trained DBA make a
statement and it lives with them forever. So how about that book as Ganesh
suggests? Or even a regular feature in the Oracle Magazine on the myths and
misconceptions. The possibilities are endless:-
o views DO NOT degrade performance
o it's really OK to have lots of extents (please don't give that table a 1gig
INITIAL...!)
o count(*) and count(0) are the same
o implicit cursors are OK to use

Oh we could go on forever.......

Adrian

      Rating: 4
order of
      columns in a table  June 22, 2003
Reviewer:
      Asim Naveed  from Pakistan
Hi,
Does the order of columns in a table have any significance,
i know that putting null likely columns in the end is good.
But I am asking this question from the application
developers point of view. for e.g
if i use SELECT * FROM t in my application, does it
guarantees that the order of columns returned by this
statement is always same? (assume that i havent changed
my table). But it could happend that my database is reloaded
from a backup media.

Like oracle doesnt guarantee the order of rows returned
by SELECT statement until you use ORDER BY. Does the same
is true that oracle doesnt guarantee the order of columns
returned by the SELECT * FROM t, until you explicitly
write SELECT col1, col2, ... FROM t.

Thanks.

          Followup:

you should NEVER use select * from t in your applications -- the only place that
construct is "sometimes safe" is in STATIC sql in PLSQL.

And even then, it is a performance issue if you really only needed 5 columns,
not all 50.

In all other cases -- I would consider it a BUG.

PL/SQL, using STATIC sql, is the only safe environment -- and even then, only if
you do it right.  for example if you have:

 cursor c is select * from t;
begin
 open c;
 fetch c into x, y, z;

that is a BUG to me.  If you have;

begin
   for x in ( select * from t )
   loop
      ....

you are "safe", protected from columns being added, datatypes getting larger or
even changing in type, order of columns changing and so on.

Inserts -- same thing, ALWAYS list the columns in the insert

   insert into t ( c1, c2, c3 ) values ....

Never
   insert into t  values ( .... );   <<<==== bug waiting for you...

June 22, 2003 Con -- Thanks for the question regarding
      "Chaining Occurs when Adding additional Columns with DEFAULT VALUE",
      version 8.1.7

      You Asked
Tom,

I have been using your site for the past 2 years and can say that it has been a
great source of information for me. Many problems have been resolved by using
this great site. Now I have a problem that I cannot get my head around. This is
my first time and I am hoping you will have the silver bullet that will unlock a
problem which I have not been able to resolve over the past two days. I am at a
loss and was hoping you could give me some ideas on how to resolve the problem.

I am currently invloved in the upgrade of a SIEBEL CRM application from V6 to
V7.

There is a key table called S_EVT_ACT that contains 212 columns (I am not
kidding). In V7 another 99 columns are added during the upgrade process.

We are running an Oracle 8.1.7.3 database with a db_block_size of 8K as this is
a OLTP application.

My problem occurs when we add the additional columns during the upgrade process.
Some of the additional columns have a DEFAULT VALUE defined. When it tries to
add the second column with a DEFAULT VALUE the table goes from no chaining to
FULL chaining.

FACTS about the table:
***********************

Table Name  : S_EVT_ACT
Rows        : 4.1 million
Growth Rate : ~50,000 records per day

AVG_ROW_LEN pre  upgrade           : 578
AVG_ROW_LEN post upgrade           : 673
Record Data Length pre  upgrade    : 11,589
Record Data Length post upgrade    : 13,562
Number of Columns pre upgrade      : 212
Number of Columns post upgrade     : 311

Simply we cannot afford to have a table with row chaining occurring on every
row.

I have tried many variatons of PCTFREE , ranging from 0 to 80 and all have
failed. What I have found are the following:

1. If you add the columns without DEFAULT VALUE specifications, no row chaining
occurs.

2. If you add the DEFAULT VALUE specifications (by using ALTER TABLE {table
name} modify(... DEFAULT ..) separately after adding the columns it also causes
no row chaining.

3. However, once you update the columns with the value defined in the DEFAULT
VALUE specifications full row chaining occurs.

Basically once a value is populated further down the record row chaining occurs.
One of the steps during the upgrade process is to update all records with a
value for the columns that have been added with their DEFAULT VALUE.

NOTE: I have tried creating the table in a Database with a block size of 16K and
also got the same problem, after adding columns with their DEFAULT values.

Any help would be greatly appreciated ?

Regards,

Con Nikakis

      and we said...
The rows are probably migrating -- there is not enough room on the blocks after
adding that defaulted value.

Since most all of the rows are migrated, rather then fix them one by one I wouls

a) alter table T move;  -- that'll rebuild the table
b) alter index I rebuild;  -- all the rowids changed, all indexes need to be
rebuilt

you can do that with nologging and even parallel if you like to speed it up.

pctfree isn't going to help you here since the table ALREADY exists.

This is to be expected.  When the table was built and populated with N columns
-- the rows fit nicely on the block.  Now you come along as say "please add this
new thing with this non-null, space consuming value" -- the data no longer fits
and rows migrate.
Review & Followup

      Rating: 4
Re-
      Chaining/Migration  June 22, 2003
Reviewer:  Con
      Nikakis  from Melbourne, Australia
I understand what you are saying and I will try this, but I read another one of
your replies regarding a similar problem and found that it also applied to my
situation.

It had to do with the fact that Oracle treats rows as 'row pieces'. Each row
piece is made up of 255 columns. What I found was that the columns with the
DEFAULT VALUE specification were being added after COLUMN 255 and as such each
row was chaining across a second block.

I was able to prove this by creating a copy of the TABLE without the DEFAULT
VALUE specifications on the FLAG columns. I then inserted all rows from my
original table into the copy table, inserting nulls on the FLAG columns.

What I found was that once I did this,  there were no columns after COLUMN 255
with data in them. I then re-ran the ANALYZE TABLE .... list chained rows into
CHAINED_ROWS command and there was no chaining at all.

This leads me to the conclusion that I need to re-arrange the order of the
columns being added to the table, so that all columns that will be populated
should be added prior to COLUMN 255 so that no row-chaining occurs on the
current 4.1 million records.

At the end of the day, row chaining will occur down the track if columns after
COLUMN 255 are populated.

Is my analysis and potential solution make sense or am I way off track ?

Regards,

Con.

          Followup:

woops -- sorry about that -- I neglected the number of columns post upgrade.

every row will be chained, without exception, without "cure".  if any of the
columns beyond 255 have a value -- they will be chained.

You got it.

      Rating:
      4
  June 22, 2003
Reviewer:  Con
      Nikakis
Thank you for your prompt responses and for helping me solve this problem.

Regards,

Con.

June 23, 2003 Riaz -- Thanks for the question regarding
      "STATSPACK Report..", version 8.1.7

      You Asked
Hello Tom !

Good to see that i can ask a question....its a long time after that i am having
opportunity to ask you a question.

Here is the Statspack report for my session:

DB Name         DB Id    Instance     Inst Num Release     OPS Host
------------ ----------- ------------ -------- ----------- --- ------------
STARR         1193708462 starr               1 8.1.7.2.1   NO  STARR2

                Snap Id     Snap Time      Sessions
                ------- ------------------ --------
 Begin Snap:         77 17-Apr-03 15:31:49       66
   End Snap:         78 17-Apr-03 15:57:21       66
    Elapsed:                  25.53 (mins)

Cache Sizes
~~~~~~~~~~~
           db_block_buffers:      13000          log_buffer:    1048576
              db_block_size:       8192    shared_pool_size:   70483955

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:              4,519.98              9,551.19
              Logical reads:              3,979.81              8,409.75
              Block changes:                 13.10                 27.68
             Physical reads:                 88.17                186.31
            Physical writes:                  1.94                  4.11
                 User calls:                  5.51                 11.65
                     Parses:                  0.91                  1.92
                Hard parses:                  0.03                  0.06
                      Sorts:                  0.14                  0.29
                     Logons:                  0.14                  0.30
                   Executes:                  6.98                 14.74
               Transactions:                  0.47

  % Blocks changed per Read:    0.33    Recursive Call %:   68.76
 Rollback per transaction %:    0.41       Rows per Sort: #######

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:   99.81
            Buffer  Hit   %:   97.78    In-memory Sort %:   99.52
            Library Hit   %:   99.47        Soft Parse %:   96.84
         Execute to Parse %:   86.98         Latch Hit %:   99.97
Parse CPU to Parse Elapsd %:    0.15     % Non-Parse CPU:   99.99

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   65.16   65.80
    % SQL with executions>1:   52.46   52.54
  % Memory for SQL w/exec>1:   58.24   58.40

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                   Wait        % Total
Event                       Waits   Time (cs)   Wt Time
----------------------------------- ---------   ---------
SQL*Net message from dblink  3,718  134,049     71.99
db file sequential read     96,742   27,535     14.79
enqueue                         77   10,868      5.84
log file sync                1,068    4,003      2.15
db file scattered read       5,880    2,757      1.48
          ------------------------------------------------

 Latch Activity for DB: STARR  Instance: starr  Snaps: 77 -78
->"Get Requests", "Pct Get Miss" and "Avg Slps/Miss" are statistics for
  willing-to-wait latch get requests
->"NoWait Requests", "Pct NoWait Miss" are for no-wait latch get requests
->"Pct Misses" for both should be very close to 0.0

                                                Pct    Avg                 Pct
                                   Get          Get   Slps       NoWait NoWait
Latch Name                       Requests      Miss  /Miss     Requests   Miss
----------------------------- -------------- ------ ------ ------------ ------
enqueue hash chains                  166,702    1.2    0.0            0
enqueues                              25,168    0.3    0.0            0
library cache                         94,235    0.9    0.0            0
session allocation                     3,107    3.4    0.0            0
shared pool                            4,945    0.8    0.0            0

 Latch Sleep breakdown for DB: STARR  Instance: starr  Snaps: 77 -78
-> ordered by misses desc

                                Get                                  Spin &
Latch Name                    Requests         Misses      Sleeps Sleeps 1->4
-------------------------- -------------- ----------- ----------- ------------
enqueue hash chains               166,702       2,066          35 2031/35/0/0/
                                                                  0
checkpoint queue latch             15,739           2           1 1/1/0/0/0
          -------------------------------------------------------------

SGA breakdown difference for DB: STARR  Instance: starr  Snaps: 77 -78

Pool        Name                        Begin value      End value  Difference
----------- ------------------------ -------------- -------------- -----------
java pool   free memory                      32,768         32,768           0
large pool  free memory                  15,214,400     15,214,400           0
shared pool DML locks                       583,200        583,200           0
shared pool KGFF heap                         9,812          9,812           0
shared pool KGK heap                         17,532         17,532           0
shared pool KQLS heap                     2,071,340      2,078,720       7,380
shared pool PL/SQL DIANA                    788,208        788,208           0
shared pool PL/SQL MPCODE                   974,636        974,636           0
shared pool PL/SQL PPCODE                    55,028         55,028           0
shared pool PLS non-lib hp                    2,096          2,096           0
shared pool PX msg pool                      69,200         69,200           0
shared pool PX subheap                       10,044         10,044           0
shared pool State objects                 1,219,760      1,219,760           0
shared pool db_block_buffers              1,768,000      1,768,000           0
shared pool db_block_hash_buckets           339,096        339,096           0
shared pool db_files                        370,988        370,988           0
shared pool db_handles                      500,000        500,000           0
shared pool dictionary cache              1,412,720      1,420,900       8,180
shared pool distributed_transactions      2,160,152      2,160,152           0
shared pool enqueue_resources               425,088        425,088           0
shared pool event statistics per ses      3,801,200      3,801,200           0
shared pool fixed allocation callbac            960            960           0
shared pool free memory                  32,081,580     31,484,988    -596,592
shared pool ktlbk state objects             520,020        520,020           0
shared pool library cache                 7,228,320      7,374,548     146,228
shared pool miscellaneous                 2,077,004      2,085,408       8,404
shared pool network connections             890,880        890,880           0
shared pool processes                       808,000        808,000           0
shared pool sessions                      2,382,380      2,382,380           0
shared pool sql area                     23,915,128     24,340,848     425,720
shared pool table columns                    26,284         26,744         460
shared pool table definiti                   14,756         14,916         160
shared pool transaction_branches          4,416,000      4,416,000           0
shared pool transactions                  1,083,780      1,083,780           0
shared pool trigger defini                   46,300         46,300           0
shared pool trigger inform                    1,308          1,368          60
            db_block_buffers            106,496,000    106,496,000           0
            fixed_sga                        75,804         75,804           0
            log_buffer                    1,048,576      1,048,576           0
          -------------------------------------------------------------

 SGA Memory Summary for DB: STARR  Instance: starr  Snaps: 77 -78

SGA regions                       Size in Bytes
------------------------------ ----------------
Database Buffers                    106,496,000
Fixed Size                               75,804
Redo Buffers                          1,056,768
Variable Size                       107,335,680
                               ----------------
sum                                 214,964,252
          ------------------------------------------------------------

What are your suggestiones w.r.t:

(1) Top 5 events (specially enques)
(2) Database Block Buffer Size & Redo Buffer Size
(3) SGA Size
(4) Latches Activity

Do i have problems with sizing ? Please Advise.

Regards

      and we said...
well, given that enqueue waits are a measely 5% of the total waits...  I
wouldn't be as concerned about them as the dblink usage.  Anyway -- enqueue
waits mean you have "blockers" and "blockees" -- people going after the same
exact row(s).  It doesn't happen often but when it does -- it takes a second or
more to resolve.  This is an application issue, you are going after a shared
resource and are forcing yourself to wait for it.

The low hanging fruit here is the message from dbink -- you are waiting for a
process that is running on the remote machine.  It is taking a long time to
return the result.  You did the statspack on the wrong database perhaps -- you
need to tune the remote system!

Buffer cache analysis is inconclusive since a cache hit ratio cannot confirm or
deny that "all is good".  with a 100MB buffer cache and a 97%+ hit, it is not
the cause of a problem.  That is about all you can say about that -- it is
probably not too small, could be larger then it needs to be.

You blew out the rows per sort - that is cause for concern.  you'll want to look
at your SQL to figure out why that is.

And your soft parse % is rather low if this system has been running for any
period of time.
Review & Followup

      Rating: 5
Your expoert
      opinion needed...again  June 23, 2003
Reviewer:
      Riaz Shahid  from PRAL, Islamabad, Pakistan
Hi Tom !

I am using 8.1.7.2.1.

Consider the following:

              Snap Id     Snap Time      Sessions
              ------- ------------------ --------
Begin Snap:         63 23-Jun-03 13:59:09      24
End Snap:           64 23-Jun-03 14:06:15      24
Elapsed:                               7.10 (mins)

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Buffer Nowait %:  100.00       Redo NoWait %: 99.84
Buffer  Hit   %:   94.72    In-memory Sort %: 97.98
Library Hit   %:   94.13        Soft Parse %: 89.66
Execute to Parse %:65.24         Latch Hit %: 100.00
Parse CPU to Parse Elapsd %:   0.12
% Non-ParseCPU:               99.98

Background Wait Events for DB: STARR  Instance: starr  Snaps: 63 -64
a-> ordered by wait time desc, waits desc (idle events last)

Event             Waits Timeouts     Total Wait Avg    Wait
                                     Time(cs)   Wait   /txn
================= ===== ========    ========== ====   =====
rdbms ipc message 829        414     136,992   1652   15.9
pmon timer        139        138      42,478   3056    2.7
smon timer          3          1      15,134   50447   0.1

I am receving the following error very much frequenctly:
ORA-02049,timeout: distributed transaction waiting for lock

What may be a possible cause and solution.

Waiting for your advice....

Riaz

          Followup:

well, i'll comment on your statspack.

Unless you are a data warehouse, your soft parse% is 10% too low.  You are not
using bind variables.

But the ora-2049 simply means you are waiting for locks in a distributed
transaction and are NOT getting them.  You have a blocking/locking issue --
plain and simple.

June 23, 2003 ramesh -- Thanks for the question regarding
      "Difference between tkprof and Explain plan", version 9.0.1

      You Asked
Hi Tom,
        Can you tell me what is the difference between "tkprof" and "Explain
plan".

      and we said...
explain plan shows you what the query plan MIGHT BE if you executed the query.

tkprof shows you what the plan WAS when you executed it, as well as how many
rows flowed through each phase of the plan, as well as how many times it was
parsed, executed, fetched from, how much cpu time it took, elapsed time, how
many logical and physical I/O's and so on.

explain plan shows you what the database might do.

tkprof shows you exactly what it did.
Review &
      Followup

      Rating: 3
Thanks- Good
      Explanation  June 23, 2003
Reviewer:
      A reader

June 23, 2003 Moovarkku -- Thanks for the question regarding
      "Datafile Resize( Shrinking)", version 8.1.7

      You Asked
Dear Tom,
    Good Morning. Thank you so much for your tips and helps for DBAs.
Do we have any option to resize the datafile. Except export and import tables.

      One more thing... all data files are 500MB... in each file i have 200M to
300M freespace... But HWM is reached upto 480M...
      Database is in archive log mode and Database is Live 24/7 any good idea
which help to resize the datafile.
      My Database version is 8.1.7... Actually my tablespace size is
20GB... To make the things better i moved some huge tables into seperate
Tablespace... But the first tablespace have the freespace... I need to free
those space from OS Level...
      What i have in my mind is if it was able to free the size and Move HWM to
the maximum freespace of the datafile then i could resize that datafile will
release the freespace in OS Level.
     Here is the sample file of my Datafile
file [ SEGMENT NAME ]  extent block ID+  Segsize(KB)
12     empty           0 2 104962         419840
12     REQ_SERVICE     2 104962 112642    30720
12     empty           0 112642 113922    5120
12     CLIENT_BRANCH   1 113922 116482    10240
12     empty           0 116482 117762    5120
12     OS_BATCH_ITEMS  9 117762 122882    20480
12     empty           0 122882 125442    10240
12     CC_IMPORT_DATA  13 125442 125698   1024
12     empty           0 125698 128001    9212

     In this datafile i have total empty space is more than 400MB at begining of
this datafile. For this file HWM is upto 490MB. So i cannot resize this
datafile. Could you give some idea to resize the datafiles like this.

Thank you
Mudhalvan M.M

      and we said...
why not create a new empty LOCALLY MANAGED tablespace.

Move the tables into there.

drop the old tablespaces.

and given that you must have a habit of dropping/truncing lots of stuff
frequently (only way to get into your situation described above) consider having
more tablespaces with fewer objects or putting those things you drop/create over
and over or truncate into their OWN tablespace so as to avoid these holes in the
future.
Review
      & Followup

      Rating:
      4
  February 18, 2003
Reviewer:
      Subbarao
Hi,
 I have the same scenario in my LMT.( I have created some tables in the LMT
tablespace and later on dropped many tables in that LMT tablespaces), as such I
have holes in the datafile, Now there are no further additions into this
tablespace, Now I want to free-up the space, how can I do.
Thanks in Advance.

          Followup:

see above.

you could try alter table T move on the segment(s) that are way up there -- as
space tends to get reused at the head of the file in an LMT but basically -- the
same advice above applies.

      Rating: 5
Datafile
      resizing- run a verify?  June 23, 2003
Reviewer:
      trevor welch
Hi Tom,

Can I ask what would you do after resizing a datafile?
I mean in regards to ensuring nothing adverse happened,
would you run a db verify?

Regards
Trevor

          Followup:

i do nothing, i just use it.  unless there was an error, it is OK.

      Rating:
      5
  June 23, 2003
Reviewer:  Dave
       from Colorado
By moving a segment that is at the end of a datafile (LMT) to another TS, or to
an available space towards the beginning of the file, will thatshrink the HWM
and allow the datafile to be shrunk?

          Followup:

as long as you get rid of the extent at the "end" of the file, yes.

June 23, 2003 Seetharaman -- Thanks for the question regarding
      "Oracle Hot Backup and recovery", version Oracle

      You Asked
Hi Tom,

  Greetings and nice meeting you after long time..

  Ok.. This time I have doubt in Backup and Recovery.

  I have a database which has been running in ARCHIVELOG mode.

  I do full export every monday and hot backup tablespace backup
every wednesday.. Mean while every day I take arch backup..

 1.  Is this a good strategy for a medium sized manufacturing
company?

 2.  If my system failures suddenly one night when transactions
are comparatively less, shud i need to recover it.  Oracle Doc
says no need to do it will take care by itself.

  If it is like that When shud I need to imply manual recovery
and in which scenario i need to specify automatic recovery?..

  And time based and cancel based.. Considering all of my arch
redo logs are fine.. what;s the recovery strategy i need to
follow...

  3.  I want to know what role does a full export will do in a
archievemode database.. excepting we import specific tables.

  Pls. help me in this section  where i'm weak...

TIA

regards

Seetharaman Srinivasan

      and we said...
1) the backup frequency seems fine -- one question though -- you
do keep the last 2 or 3 backups around correct?   You do not
want just one backup set, you want the last two (at least) or
three (or more).  The reason is -- if you overwrite your backups
every week and halfway through the backup -- a disk fails
(before you copied its data) you now have NO backup as your
backup script will zero out the last copy of the file you have
not yet backed up.  It can happen, it will happen, I've seen it
happen.

Make sure to keep the last COUPLE of backups in the event
something goes wrong with one of them.

2) there are 2 types of failure we recover from:

o instance failure due to a software error, power failure, etc.
We recover automatically from the online redo logs and no
intervention is needed.  This happens as part of the normal
startup.

o media failure.  You've lost a disk(s).  We need to manually
restore a backup from tape or some other location, and then
apply archive and online redo logs to it to bring it upto date.

Instance failure - automatic recovery, no interventaion
Medai failure - intervention required.

Typically you will do a full recovery unless you want to do a
point in time recovery (eg: to a point in time prior to you
accidently dropping a table for example).

3) see

http://asktom.oracle.com/pls/ask/f?p=4950:7:::::F4950_P7_SEARCH_CRITERIA:export+corruption
....
Review
      & Followup

      Rating: 4
If I lost
      entire disk  June 23, 2003
Reviewer:  Reddy
       from Bangalore, India
Hi Tom,

I had been asked by somebody this question.

I have 12 datafiles(tablespaces), at one of the time my computer completely
gone, including Oracle basic softwre. But I have all backups. What is the
procdure to restore back. Please tell me it is very important to me.

thanks in advance

Reddy

          Followup:

I'm always curious why people have backups, yet know not what to do with them --
how could that be?  what is the purpose of taking backups, storing them, doing
them at all -- if you don't know what to do with them?

Recovery -- it is all about recovery.  testing it, learning it, knowing it,
perfecting it.  The one thing a DBA is never allowed to get wrong (cause I can
fix any other mess up, but I cannot fix you after a total loss of data and bad
backups that never have been tested).

How do you know if you have backups?  If you've never actually restored them?

Anyway, you simply install the Oracle software, restore the data, and then you
have to decide if you can do a complelete recovery (roll forward thru the
archived and online logs -- only possible if you have them all), an incomplete
recovery (roll forward thru just some of the archived/online logs) -- those
options are available if and only if you were running in the only mode a
production database should be -- archive log.  If you are noarchive log, I pray
you actually shutdown before copying those files (be surprise how many people
just like to fill tapes up with 0's and 1's that way -- not worth very much).
Then you just restore them, set your oracle sid and type "startup" in sqlplus
(of course, in windows it is more complicated as you have to use oradim to
"register" the instance)

Anyway, time for you to read:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96519/toc.htm
and if you are on windows, after you finish that, read

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/win.920/a95491/toc.htm
to learn about oradim

June
      23, 2003 Amy -- Thanks for the question
      regarding "Tricky Trigger Question", version

      You Asked
I have a trigger for insert on a table.
The trigger works like this:

if inserting in the id range of 1000000,
insert the same line with an id of 20000000.

for example:
table named test with columns id, notes.
id is a primary key and notes is a long
field.

if I insert the following:
insert into test (id, notes)values(1000001 'hi amy');

test after the insert:
id       notes
1000001  'hi amy'
20000001 'hi amy'

I have written a row trigger (copying the :new.id and :new.notes
into a
package variable).  I have a statement
trigger that does the insert using
the data in the package variable.

If the notes field is a varchar2 these triggers work.  If notes
field is a long
it does not work.

My question (finally):
How do I deal with the notes field when
the data type is long.

Oracle 8.0.5
TIA

      and we said...
Well, this isn't as tricky as your first one ;)  It'll be much
the same and we can do it given ONE constraint:  the long field
cannot contain >32k of data.  If it does -- plsql quite simply
cannot deal with it (if the field was a CLOB -- not a LONG, we
would not have this restriction and the code would be easier).

The trick is again to defer processing of the insert until an
after insert trigger -- begin careful not to recurse if we are
already processing the data.  Also, we must fetch out the newly
inserted data (as long as the long is 32k or less) and then
insert it -- we cannot insert into select with a long.  It might
look like this:

ops$tkyte@8i> create table t ( x int primary key, y long );
Table created.

ops$tkyte@8i> create or replace package state_pkg
  2  as
  3      type ridArray is table of rowid index by
binary_integer;
  4
  5      empty    ridArray;
  6      newones    ridArray;
  7
  8      already_processing boolean default FALSE;
  9  end;
 10  /
Package created.

ops$tkyte@8i> create or replace trigger t_bi
  2  before insert on t
  3  begin
  4      if ( not state_pkg.already_processing )
  5      then
  6          state_pkg.newones := state_pkg.empty;
  7      end if;
  8  end;
  9  /

Trigger created.

ops$tkyte@8i> show err
No errors.
ops$tkyte@8i>
ops$tkyte@8i> create or replace trigger t_aifer
  2  after insert on t for each row
  3  begin
  4      if ( not state_pkg.already_processing and :new.x
between 1000000 and 9999999 )
  5      then
  6          state_pkg.newones( state_pkg.newones.count+1 ) :=
:new.rowid;
  7      end if;
  8  end;
  9  /

Trigger created.

ops$tkyte@8i> show err
No errors.
ops$tkyte@8i>
ops$tkyte@8i> create or replace trigger t_ai
  2  after insert on t
  3  begin
  4      if ( not state_pkg.already_processing )
  5      then
  6          state_pkg.already_processing := TRUE;
  7          for i in 1 .. state_pkg.newones.count
  8          loop
  9              for c in ( select * from t where rowid =
state_pkg.newones(i) )
 10              loop
 11                  insert into t ( x, y ) values
 12                  ( c.x+20000000-1000000, c.y );
 13              end loop;
 14          end loop;
 15          state_pkg.newones := state_pkg.empty;
 16          state_pkg.already_processing := FALSE;
 17      end if;
 18  exception
 19      when others then
 20          state_pkg.newones := state_pkg.empty;
 21          state_pkg.already_processing := FALSE;
 22          raise;
 23  end;
 24  /

Trigger created.

ops$tkyte@8i> show err
No errors.
ops$tkyte@8i>
ops$tkyte@8i> insert into t values ( 1000001, 'Hello World' );

1 row created.

ops$tkyte@8i> select x,y  from t;

         X Y
---------- ----------------
   1000001 Hello World
  20000001 Hello World

Review &
      Followup

      Rating: 5
trigger
      help  August 31, 2001
Reviewer:
      A reader
Hi Tom
You are always help when in need...I have waited upto 12.30 mid night ,hoping u
will allow us to ask the question ,but in vain...
I am new to oracle but doing well with your help...My present problem is
I have two table by name countries, regulations...
The fields in countries table are c_id and c_name and fields in regulations are
c_id,c_name,P_name,permitted,notes...
My requirement is wheevr the user  enters  a new country in country table
like ...ex..user entered 10,USA...now i want to enter appr 10 records in to
regulations
table ..they are
10,USA,p1,yes,null
10,USA,p2,yes,null
10,USA,p3,yes,null
10,USA,p4,yes,null
10,USA,p5,yes,null
10,USA,p6,yes,null
10,USA,p7,yes,null
10,USA,p8,yes,null
10,USA,p9,yes,null
10,USA,p10,yes,null
next time if user enters 11,Canada it should insert
11,USA,p1,yes,null
11,USA,p2,yes,null
11,USA,p3,yes,null
11,USA,p4,yes,null
11,USA,p5,yes,null
11,USA,p6,yes,null
11,USA,p7,yes,null
11,USA,p8,yes,null
11,USA,p9,yes,null
11,USA,p10,yes,null should get inserted in to regulations ..pl help how to do it

          Followup:

Well, just to give you guys a heads up on timing here.  I live on the East Coast
of the US.  I usually do a batch of questions in the morning (so 9am-10am east
coast time is a good time to try to post).  I try to do another batch in the
afternoon (12-2pm east coast is pretty good).  Many times, I try to do another
at night so 9pm-11pm is good.  If you get here at midnight east coast time --
I'm already asleep for the night.

I assume you really meant "if user enters 11,Canada, it should insert

11,Canada,p1,..... (not USA again)

This is really pretty easy.  Just create a trigger on the base table:

create trigger t
after insert on countries for each row
begin
   for i in 1 .. 10
   loop
      insert into regulations values
      (:new.c_id, :new.c_name, 'p'||i, 'yes', null );
   end loop;
end;

If you have declaritive RI from regulations to countries (a foreign key), you
will need to use the technique described at:

http://osi.oracle.com/~tkyte/Mutate/index.html
to avoid the mutating table error.  You'll simply move the above logic into an
AFTER trigger (without the for each row)

      Rating: 4
Trigger
      tricks  September 03, 2001
Reviewer:
      P.Kamalanathan  from Singapore
Hi Mr.Tom,
How can i contact you?.. You are always engaged with full of questions.
expecting your kind reply ..
regards
Kamal.P ,Singapore

      Rating: 4
How would CLOB
      field make it easier?  December 11, 2001
Reviewer:
      lei  from new jersey
You said if the field was a CLOB, not LONG, the code would be easier. I suppose
that you still need one package and three triggers.

I have two tables like this:
create table testclob (
name varchar2(40),
description clob);

create table testclob_log (
name varchar2(40),
description clob,
upd_date date);

and the following trigger:
create or replace trigger log_trig
after insert on testclob
for each row
begin
    insert into testclob_log(name, description, upd_date)
    values (:new.name, :new.description, sysdate);
end;
/

insert into testclob values ('good', 'this works');
works fine;

but
insert into testclob values ('not working', '');
insert into testclob values ('not working', NULL);

do not work, and errors are:
ERROR at line 1:
ORA-00600: internal error code, arguments [kxtotolc_lobopt], [], [], [], [], [],
[], []

It seems that passing '' or NULL to CLOB causes problems.

I am thinking using the solutions you gave here, and if possible, an easier one
would be better.

Thank you very much.

- Lei

      Rating: 5
You are a
      god.  February 20, 2003
Reviewer:  Donna D.
       from Houston, TX, USA
I combined this and a couple of other answers I found through this site to
capture both a before and after image of a table with a long data type using
triggers.  Thank you!

Being a plsql newbie, you are truly a god for puting all of this out there for
me to find.

Thanks,
Donna

      Rating: 5
Another tricky
      quesyion  June 19, 2003
Reviewer:  Yogesh
       from Yogesh Pune
I have one table with 200 columns and n number of rows .. I want to create
another table which will have following structure

Tablename, columnname, valueincol

now in this table I want to populate all column values from the above table ..
now I don't know what are the columnnames  and number of columns in the original
table .. so lets assume name of the table is t1 and it has x1,x2,x3 columns and
their values are

10,20,30

I want to create a new table with rows like

t1 x1 10
t1 x2 20
t1 x3 30

Tom, I have used an approach like  getting column names from user_tab_columns
and putting them along with table name .. but when it comes to poulating its
value I have to make many calls .. here in this case 3 ... to get that data ..
can you please suggest some good way of doing it ?

          Followup:

ops$tkyte@ORA920> create table t ( tname varchar2(30), cname varchar2(30), rnum
number, value varchar2(4000) );

Table created.

for 9i...

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace procedure pivot( p_tname in varchar2 )
  2  as
  3      l_stmt long := 'insert ALL';
  4  begin
  5      for x in ( select * from user_tab_columns where table_name = p_tname )
  6      loop
  7          l_stmt := l_stmt ||
  8                 ' into t(tname,cname,rnum,value)
  9                   values
 10                   ( tname, ''' || x.column_name || ''',
 11                      rnum, ' || x.column_name || ' ) ';
 12      end loop;
 13
 14      l_stmt := l_stmt || ' select a.*, rownum rnum, :tname tname from ' ||
p_tname || ' a';
 15      execute immediate l_stmt using p_tname;
 16  end;
 17  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec pivot( 'EMP' )

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> column tname format a3
ops$tkyte@ORA920> column cname format a10
ops$tkyte@ORA920> column value format a15
ops$tkyte@ORA920> select * from t where rnum = 1;

TNA CNAME            RNUM VALUE
--- ---------- ---------- ---------------
EMP EMPNO               1 7369
EMP ENAME               1 SMITH
EMP JOB                 1 CLERK
EMP MGR                 1 7902
EMP HIREDATE            1 17-DEC-80
EMP SAL                 1 800
EMP COMM                1
EMP DEPTNO              1 20

8 rows selected.

for 8i...

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace procedure pivot( p_tname in varchar2 )
  2  as
  3      l_stmt long := 'insert into t ( tname, rnum, cname, value )
  4                      select ''' || p_tname || ''', rnum, ';
  5      l_cnames long := 'decode( cnum';
  6      l_cvals  long := 'decode( cnum, 0, NULL';
  7      l_cnt    number := 0;
  8  begin
  9
 10      for x in ( select * from user_tab_columns where table_name = p_tname
order by column_id )
 11      loop
 12          l_cnames := l_cnames || ', ' || x.column_id || ', ''' ||
x.column_name || '''';
 13          l_cvals  := l_cvals  || ', ' || x.column_id || ', ' ||
x.column_name;
 14          l_cnt := l_cnt + 1;
 15      end loop;
 16
 17      l_stmt := l_stmt || l_cnames || '), ' || l_cvals || ')
 18                from (select rownum cnum from all_objects where rownum <= :n
) A,
 19                     (select a.*, rownum rnum from ' || p_tname || ' a) B';
 20
 21      execute immediate l_stmt using l_cnt;
 22  end;
 23  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> delete from t;

112 rows deleted.

ops$tkyte@ORA920> exec pivot( 'EMP' )

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from t where rnum = 1;

TNA CNAME            RNUM VALUE
--- ---------- ---------- ---------------
EMP EMPNO               1 7369
EMP ENAME               1 SMITH
EMP JOB                 1 CLERK
EMP MGR                 1 7902
EMP HIREDATE            1 17-DEC-80
EMP SAL                 1 800
EMP COMM                1
EMP DEPTNO              1 20

8 rows selected.

      Rating: 5
Problem
      Solved  June 20, 2003
Reviewer:  Yogesh
       from Pune India
Thanks a lot TOM, once again you saved my day ... I don't have words to explain
... Thanks once again ..

      Rating: 5
One more
      problem   June 23, 2003
Reviewer:  Yogesh
       from Pune India
I was trying to execute this against a table having 190 columns ... decode have
limitation of having 255 values ... including expressions ... what can we do in
that case

Thanks

          Followup:

you'll have to add logic to break the decode into pieces, so the resulting query
is like:

decode( cnum, 0, null,
              1, column1,
              2, column2,
              ...
              100, column100 ) ||
decode( cnum, 101, column101,
              102, column102,
              ...
              190, column190 )

      Rating: 5
Got it
        June 23, 2003
Reviewer:  yogesh  from
      Pune India
Thanks a lot Tom.... I did that ... I just wanted to have your comment .. thanks
for your valuable time

June 23, 2003 vishnu -- Thanks for the question regarding "sql
      query results", version 8.1.7

      You Asked
Hi Tom ,
Here is my doubt ..I have a table with following columns
CREATE TABLE MV_DEMOGRAPHICS_CRD_DIST (
  SCHOOL_YEAR    VARCHAR2 (9),
  DISTRICT_NAME  VARCHAR2 (75),
  ETHNICITY      VARCHAR2 (50),
  GRADE          VARCHAR2 (50),
  FEMALE         NUMBER,
  MALE           NUMBER,
  TOTAL          NUMBER)

suppose it has recdords like below
school_year   district_name  ethnicity   grade   female   male total
1999           a             as           5th     200    300    500
2000           a1            bl           4th     100    200    300
2000           b1            wh           3rd     100    100    200
1999            a             bl           5th     40      60    100
2000           a1            wh            4th     50     40     90
1999           a             bl            5th     70     60     130
1999           a             wh            5th     400    300    700
1999           a             wh            4th     300    400    700
1999           a             bl            4th     100    100    200
I want to dispaly like below
                   as               bl              wh
year grade   female male tot female male tot female male t tot_of_all

1999  5th    200    300  500  40    60   100  400   300  700   1300
      4th    300    400  700  100   100  200

2000

20001

total

I tried the following but looks like i am missing some thing ...please help
select e.school_year, d.grade, a.male white_male, a.female white_female,a.male
+a.female Total_white,
                                       decode(b.male,null,0,b.male)  asian_male
,decode(b.female,null,0,b.female)
asian_female,decode(b.male+b.female,null,0,b.male+b.female) Asian_Total,
bl.male black_male ,bl.female black_female,bl.male+bl.female black_Total

      from ( select grade,male,female,ethnicity, rownum r
                from disco_test where ethnicity = 'white'  ) a,
          ( select grade,male,female,ethnicity, rownum r
                from disco_test where ethnicity = 'asian'  ) b,

( select grade,male,female,ethnicity, rownum r
                from disco_test where ethnicity = 'black'  ) bl,

(select grade ,rownum r from disco_test) d,
(select school_year,rownum r  from disco_test) e,
               ( select rownum r from all_objects where rownum < 100 ) c
     where c.r = a.r(+)
       and c.r = b.r(+)
       and c.r = d.r(+)
       and c.r = e.r(+)
       and c.r = bl.r(+)
       and (a.ethnicity is not null or b.ethnicity is not null)

      and we said...
this is a simple pivot with a group by rollup:

select decode( grouping(school_year), 1, 'total', school_year ),
       grade,
       sum(decode( ethnicity, 'as', female )) as_f,
       sum(decode( ethnicity, 'as', male ))   as_m,
       sum(decode( ethnicity, 'as', total ))  as_t,
       sum(decode( ethnicity, 'bl', female )) bl_f,
       sum(decode( ethnicity, 'bl', male ))   bl_m,
       sum(decode( ethnicity, 'bl', total ))  bl_t,
       sum(decode( ethnicity, 'wh', female )) wh_f,
       sum(decode( ethnicity, 'wh', male ))   wh_m,
       sum(decode( ethnicity, 'wh', total ))  wh_t,
       sum(total) tot_of_all
  from mv_demographics_crd_dist
 group by rollup(school_year, grade)
having grouping(grade)=0 or grouping(school_year)=1
 order by school_year, grade desc
/

June 24, 2003 NGUYEN -- Thanks for the question regarding "Get
      client remote client IP address,", version 9i

      You Asked
Hi Tom

I would like to know how to get a remote client IP adress and local client IP
adress.
Is it right to say that the local client IP adress equal to IP adress of a
Server ( client and server on the same machine)?
I suppose we use sys_contex to get it
and could I kill a session of SYS user?
Have a good day and best regards

      and we said...
ops$tkyte@ORA920> select utl_inaddr.get_host_address(host_name), host_name from
v$instance;

UTL_INADDR.GET_HOST_ADDRESS(HOST_NAME)
---------------------------------------------------------------------------------
--------------------------------------------------
HOST_NAME
----------------------------------------------------------------
192.168.0.3
tkyte-pc-isdn.us.oracle.com

gets you the server ip address.  sys_context( 'userenv', 'ip_address' ) gets you
the client address - returns NULL if the user is not using TCP/IP.

However, a login trigger is NOT going to disallow SYS from logging in.  (login
triggers cannot "fail" a DBA login).  You could send yourself an EMAIL, you
could schedule a DBMS_JOB to kill the session shortly (but hey, sys could
quickly set job_queue_processes=0 to subvert you)

Best bet -- never but never use SYS -- never.  By default people cannot log in
as SYS.

Let your best and brightest DBA's have SYSDBA or SYSOPER and don't allow for
remote sysdba/sysoper login and you'll never have a remote SYS login.

Review &
      Followup

      Rating: 2
For Oracle
      7.3  June 22, 2003
Reviewer:  A reader

If i want to run this commands in oracle 7.3?

          Followup:

not going to happen, neither of them.

you can get the client ip address if you audit connect, it is in the comments
column of the audit trail, you have to parse it out.

      Rating: 4
Remote
      login  June 23, 2003
Reviewer:
      A reader
Hi TOM
Thank you very much for your answer it help to resolve an important problem
But you say "Let your best and brightest DBA's have SYSDBA or SYSOPER and don't
allow for
remote sysdba/sysoper login and you'll never have a remote SYS login"
How can I do IT ? It was 3 month, I try to looking for a solution for this
question
Thank you very much
Have a good day and best Regards
NGUYEN ORACLE DBA

          Followup:

grant sysdba/sysoper to the dba's that should have it.

they can now

SQL> connect their_user/their_password AS SYSDBA;

they'll be in as sys but using their credentials.

Now, if you don't create a password file with orapwd and don't set up the remote
login password file, they won't be able to connect over the net.  they'll have
to be ON the machine itself.  consider:

idle> connect ops$tkyte/xxxx@ora920.us.oracle.com AS SYSDBA;
Connected.

idle> show user
USER is "SYS"

i'm able to remotely log in as SYSDBA (sys) using my credentials

idle> show parameter remote_log

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
remote_login_passwordfile            string      EXCLUSIVE

idle> alter system set remote_login_passwordfile=NONE scope=spfile;

System altered.

idle> shutdown
Database closed.
Database dismounted.
ORACLE instance shut down.

idle> connect / as sysdba
Connected to an idle instance.

I can still connect as sysdba locally (ops$tkyte is my os authenticated
account)

idle> startup
ORACLE instance started.

Total System Global Area  370217796 bytes
Fixed Size                   451396 bytes
Variable Size             100663296 bytes
Database Buffers          268435456 bytes
Redo Buffers                 667648 bytes
Database mounted.
Database opened.

idle> connect ops$tkyte/xxx@ora920.us.oracle.com AS SYSDBA;
ERROR:
ORA-01017: invalid username/password; logon denied

Warning: You are no longer connected to ORACLE.

but I cannot connect remotely as sysdba anymore

idle> connect ops$tkyte/xxx@ora920.us.oracle.com
Connected.
idle> show user
USER is "OPS$TKYTE"
idle>

I have to use a local connection to get "SYSDBA"

      Rating: 2
Oracle 7.3
        June 23, 2003
Reviewer:  A reader

Can you explain in full steps ,how can i get IP address with audit trail option
cuz i don;t know what is audit trail(i am new programmer)?

          Followup:

simple search for

7.3 ip address

on this site......

      Rating: 3
REMOTE LOGIN
      PASSWORD   June 23, 2003
Reviewer:
      A reader
Hi Tom
Thank you very much for your quickly answers.
Following your method 'I need to use OS authentification'
( Remote_login_password=None). But my problem IS :
If an user is SYS then I disallow a remote login and
for a remote client the login should be  authentificated by ORACLE normally not
by operating system ( Remote_login_password=exclusive).
Could you please help me  to find out a solution for this headache problem
Best regards
NGUYEN  ORACLE DBA (PARIS FRANCE)

          Followup:

that is fine, what I've shown you supports that (and I demonstrated that)

I can connect locally:

user/pass as sysdba

I can connect remotely

user/pass@db

I CANNOT remote connect as sysdba

user/pass@db as sysdba <<<=== fails.

You got what you want.

June 24, 2003 Om
      -- Thanks for the question regarding "Top Sql sessions", version 8.1.7

      You Asked
Hi Tom,

Thanks for all your great help and suggestions.

I make use of showsql script written by you and find it very
useful as it gives a very clear picture as "who is doing what".

Just wanted to ask you .Is there any way to display the time
in minutes or hours for a particular process .

I read the following statement in an article

col program form a30 heading "Program"
col CPUMins form 99990 heading "CPU Mins"
col value form 999,999,999,999

select rownum as rank, a.*
from (
    SELECT v.sid, program, v.value
    FROM v$statname s , v$sesstat v, v$session sess
   WHERE s.name = 'consistent gets'
     and sess.sid = v.sid
     and v.statistic#=s.statistic#
     and v.value>0
   ORDER BY v.value DESC) a
where rownum < 11
;

But not at all convinced about the time shown in minutes.
Could you please give your advise on this ?

Best regards,
Om

      and we said...
I'm not understanding something here.

You are showing the consistent gets for a session (which is the sum of all
consistent gets for the LIFE of the session across ALL queries that session
does) and are asking for "minutes" ??  It doesn't compute - we don't track the
time it took to get those consitent gets.

If you want the consistent gets and CPU time used by a session, use something
like this:

select program,
      (select value
         from v$sesstat
       where v$sesstat.sid = v$session.sid
         and statistic# = (select statistic#
                             from v$statname
                             where name = 'consistent gets' ) ) cgs,
     (select value
        from v$sesstat
       where v$sesstat.sid = v$session.sid
         and statistic# = (select statistic#
                             from v$statname
                            where name = 'CPU used by this session' ) ) cpu
  from v$session
 order by 2 desc
/

That'll be the consistent gets by session along with the CPU time used by that
session.

Review &
      Followup

      Rating: 4
what is this
      show sql script?  August 29, 2002
Reviewer:
      john
where can i get that SHOWSQL script

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:767025833873

      Rating: 3
FUp - what is
      this show sql script  August 29, 2002
Reviewer:
      Jim  from Australia
John,

A script Tom has provided, and a favourite of many
who visit asktom.

Want the source? Use the search tab to look on this site for
showsql.sql

If you get stuck just click on the following

http://asktom.oracle.com/pls/ask/f?p=4950:8:1229282::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:259015256432,%7Bshowsql.sql%7D

Regards
Jim

      Rating: 5
CPU
      usage  August 29, 2002
Reviewer:  Anton
       from NZ
Hi Tom.

Thanks for the showsql - it really comes in handy.

Someone asked me the other day to produce a report of how much CPU a database
has used over 5 minute intervals.  The only way I could think of doing this is
by using 'ps' to list out oracle background and user processes for a database
and sum the CPU seconds.  Is there any V$ table in the database that would hold
this info ?

Thanks Tom

          Followup:

create table cpu_stats
as
select sysdate dt, v$sysstat.*
  from v$sysstat
 where 1=0;

declare
  l_job number;
begin
  dbms_job.submit( l_job,
                  'insert into cpu_stats
                   select sysdate, v$sysstat.*
                     from v$sysstat
                    where name = ''CPU used by this session'';',
                   sysdate,
                  'sysdate+5/24/60' );
end;
/

will do that -- just make sure to set job_queue_processes and in 8i and before
job_queue_interval first (to let dbms_jobs work)

It'll be slighly more accurate and less resource instensive then trying to grap
this from PS (consider the dedicated servers that start right AFTER you took a
"snapshot" and end right before your next one, you will never count them!

      Rating: 4
CPU
      time  August 30, 2002
Reviewer:  Om
       from INDIA
Hi Tom,

Thanks for the select statement.I executed it on one of my
databases and got this result

PROGRAM                    CONSISTENT   cpu
                           GETS

AUTOQMONITOR4.0.EXE        179636568    1220971
rman@bspc10n01 (TNS V1-V3) 76            417529
oracle@bspc10n01 (SMON)    6508726         5415

How can we measure the values shown under CPU ?
what does 1220971 depicts for the first program ?

Please advise.

Best regards,
Om

          Followup:

Lets see, I goto the handy dandy reference guide and discover in the chapter on
statistics:

<quote>
Amount of CPU time (in 10s of milliseconds) used by a session from the time
a user call starts until it ends. If a user call completes within 10
milliseconds, the start and end user-call time are the same for purposes of this
statistics, and 0 milliseconds are added. A similar problem can exist in the
reporting by the operating system, especially on systems that suffer from many
context switches.
</quote>

      Rating: 5
How to find out
      a query execution time?  June 23, 2003
Reviewer:
      A reader
One of the pages in our Java based application is taking 12 seconds to load. The
developer is suspecting the problem is with the query. I executed manually
(sqlplus using set timing on) and it took only couple of milliseconds for that
query (< 1 second).

But they want me to prove by showing execution time for the exact query that was
part of that page.

I checked v$sql and v$sqlarea and found the query. But the cpu_time was 26000. I
don’t know why the cpu_time was so high. My guess on cpu_time is that it is the
time of actual cpu cycle time for that query.

How to find out the execution time of the above said query?

If you are going to advise me to use tkprof, can you please advice other options
too. It is difficult to find out the session as the java jdbc pool is using >50
connection and each time the query will use different sessions. I need to
approach IT to get the privilege to use tkprof if I need to enable the trace at
system level.

          Followup:

tell the developer to add:

alter session set sql_trace=true

after they grab a connection,

alter session set sql_trace=false

before they release it.   that'll get your trace.

too bad the java guys didn't instrument their pages.  what I mean by that is,
goto  asktom.oracle.com, click on an article.  Now, change NO to YES in the url.
 I can tell you in milliseconds what part of my pages are slow -- maybe they
should be able to do that too!

June 24, 2003 Sikandar -- Thanks for the question regarding
      "Analytical Function Usage", version 8.1.7

      You Asked
Dear TOM,
I have the following data,

SCOTT > select empno, deptno,sal from emp;

       EMPNO       DEPTNO          SAL
------------ ------------ ------------
        7839           10         5000
        7782           10         2450
                            Ttl   7450 <<<<<<<
        7838           10         5000
        7934           10         1300
        6666           10           10
        7777           10           10
                            Ttl   6320 <<<<<<<
        7566           20         2975
        7788           20         3000
        7876           20         1100
        7369           20          800
                            Ttl   7875 <<<<<<<

my requirement is to group on deptno but sum(sal) can't exceed 10,000. and if
exceeds then it should break it while giving total of that.

Please let me know if still any confusion.

      and we said...
ops$tkyte@ORA920LAP> select deptno, empno, sum(sal), decode( grouping(empno), 1,
'<<== total' ) flag
  2    from (
  3  select deptno, empno, sal, sum(sal) over ( partition by deptno order by
empno ) sum_sal
  4    from emp
  5         )
  6   where sum_sal <= 10000
  7   group by rollup(deptno,empno)
  8  /

    DEPTNO      EMPNO   SUM(SAL) FLAG
---------- ---------- ---------- ----------
        10       7782       3430
        10                  3430 <<== total
        20       7369       1120
        20       7566       4165
        20       7788       4200
        20                  9485 <<== total
        30       7499       2240
        30       7521       1750
        30       7654       1750
        30       7698       3990
        30                  9730 <<== total
                           22645 <<== total

12 rows selected.

Review &
      Followup

      Rating: 5
I am trying in
      this way  June 21, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
SCOTT >  select deptno, empno, sum(sal), decode( grouping(empno), 1,'<<== total'
) flag
  2         from (
  3       select deptno, empno, sal, sum(sal) over ( partition by deptno order
by empno ) sum_sal
  4         from emp
  5              )
  6        where sum_sal <= 1000
  7        group by rollup(deptno,empno)
  8  /

      DEPTNO        EMPNO     SUM(SAL) FLAG
------------ ------------ ------------ ----------
          10         6666           10
          10         7777           10
          10                        20 <<== total
          20         7369          800
          20                       800 <<== total
                                   820 <<== total

6 rows selected.

SCOTT >

In the above it is not showing all the records.
=========================================================
I am trying to solve it in this way, please give your openion. I am really
interested to know the usage of "grouping" you have used in your sample.

SCOTT > break on deptno
SCOTT > break on limit_amt skip 1
SCOTT > SELECT ALL
  2        deptno,
  3        ceil(sum(sal) over (partition by deptno order by empno)/10000)
Limit_Amt,
  4        empno,sal,
  5        sum(sal) over (partition by deptno order by empno) dept_ttl
  6      FROM
  7      emp;

 DEPTNO    LIMIT_AMT   EMPNO     SAL     DEPT_TTL
------- ------------ ------- ------- ------------
     10            1    6666      10           10
     10                 7777      10           20
     10                 7782    2450         2470
     10                 7838    5000         7470

     10            2    7839    5000        12470
     10                 7934    1300        13770

     20            1    7369     800          800
     20                 7566    2975         3775
     20                 7788    3000         6775
     20                 7876    1100         7875

     20            2    7902    3000        10875

     30            1     555    6000         6000
     30                 7499    1600         7600
     30                 7521    1250         8850

     30            2    7654    1250        10100
     30                 7698    2850        12950
     30                 7844    1500        14450
     30                 7900     950        15400

18 rows selected.

SCOTT >

          Followup:

you told it not to print all of the records.  you said "please stop at
1,000"????

      Rating: 5
My question
      was  June 22, 2003
Reviewer:  Sikandar Hayat
       from Pindi Bhattian - Pakistan
Sorry TOM,
My requirement was to show all but a goup should not exceed 10,000 if so
separate in a separate group.

          Followup:

does not compute -- don't follow it.

      Rating: 5
Sub-total
      question  June 23, 2003
Reviewer:  A reader.
       from USA
What is the best way to avoid calculating or to hide the display of sub-totals
for groups with just one row in them?

In your sample output sub-total for Dept #10 is useless and should not be
displayed.

    DEPTNO      EMPNO   SUM(SAL) FLAG
---------- ---------- ---------- ----------
        10       7782       3430
        10                  3430 <<== total <Hide this one>
        20       7369       1120
        20       7566       4165
        20       7788       4200
        20                  9485 <<== total
        30       7499       2240
        30       7521       1750
        30       7654       1750
        30       7698       3990
        30                  9730 <<== total
                           22645 <<== total

          Followup:

useless?  not really, to me it would be confusing NOT to have the subtotal (far
too easy to clump deptno=10 with deptno=20 as a single line

ops$tkyte@ORA920LAP> select deptno, empno, sum(sal) sal, decode(
grouping(empno), 1, '<<== total' ) flag
  2    from (
  3  select deptno, empno, sal, sum(sal) over ( partition by deptno order by
empno ) sum_sal
  4    from emp
  5         )
  6   where sum_sal <= 10000
  7   group by rollup(deptno,empno)
  8   having grouping(empno) = 0 or count(*) > 1
  9  /

    DEPTNO      EMPNO        SAL FLAG
---------- ---------- ---------- ----------
        10       7782       3675
        20       7369       1200
        20       7566     4462.5
        20                5662.5 <<== total
        30       7499       2400
        30       7521       1875
        30       7654       1875
        30                  6150 <<== total
                         15487.5 <<== total

9 rows selected.

(yes, I know the numbers are different, i forget what I set sal to last time
around....)

      Rating: 5
It has solved
      my problem  June 24, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
Dear TOM,
Look at the following data which shows that total of each set is less than
10,000 (this limit is increasable/decreasable). Now I developed a report in
Oracle Rreports and it is giving me desired results.

DEPTNO    LIMIT_AMT   EMPNO     SAL     DEPT_TTL
------- ------------ ------- ------- ------------
     10            1    6666      10           10
     10                 7777      10           20
     10                 7782    2450         2470
     10                 7838    5000         7470

     10            2    7839    5000        12470
     10                 7934    1300        13770

     20            1    7369     800          800
     20                 7566    2975         3775
     20                 7788    3000         6775
     20                 7876    1100         7875

     20            2    7902    3000        10875

     30            1     555    6000         6000
     30                 7499    1600         7600
     30                 7521    1250         8850

     30            2    7654    1250        10100
     30                 7698    2850        12950
     30                 7844    1500        14450
     30                 7900     950        15400

June 24, 2003 Ashiq -- Thanks for the question regarding
      "Implicit Commit", version 8.1.7

      You Asked
Hi Tom,

 As we know after every ddl oracle will do implicit commit.My question is ,why
its committing for the ddl which is got failed.For example..

scott@RMSREAL> delete from emp1;

14 rows deleted.

scott@RMSREAL> drop table emp2;
drop table emp2
           *
ERROR at line 1:
ORA-00942: table or view does not exist

scott@RMSREAL> select * from emp1;

no rows selected

  Why its doing like this?Any reason for this?

thanx

      and we said...
DDL is done conceptually like this:

begin
    COMMIT;
    do the ddl;
    COMMIT;
exception
    when others then
         ROLLBACK;
         raise;
end;

Now, if that first commit were not there AND you had outstanding work -- then we
would roll back your changes as well as ours.  So, rather then make it
unpredicable -- it does "commit -- ddl -- commit | rollback"

You can use an autonomous transaction:

http://asktom.oracle.com/~tkyte/autonomous/index.html
to avoid the commit from affecting you alltogether.

Review &
      Followup

      Rating:
      5
  December 30, 2002
Reviewer:  Ashiq
       from Trichy, Tamil Nadu, India
Thanx for explaining me very detailed

      Rating:
      5
  December 30, 2002
Reviewer:  Dave
       from Ohio
It seems like the pseudocode ...

begin
    savepoint <whatever>;
    do the ddl;
    COMMIT;
exception
    when others then
         rollback to savepoint <whatever>;
         raise;
end;

... would also work conceptually, and would not commit on failure of the DDL. Is
there an ANSI reason for not implementing/permitting such a methodology, or some
other rationale that you are aware of?

          Followup:

a user that gets blocked on the "do the ddl" inside the savepoint is blocked on
the transaction -- not the "subtransaction".  Hence it would block people on the
data dictionary -- a place we cannot afford to get jammed up.

The data dictionary is "special" -- it drives the rest of the system.  If
portions of it get locked up for extended periods of time -- it could be deadly.

      Rating: 5
Good
      clarification  December 30, 2002
Reviewer:
      Cefers.br  from Brasil
Nice explanation. I´ve already heard about 2 implicit commits on DDL, but I
wasn´t sure if this information was right. Thanks, Tom.

And just to emphasize, the implicit commits occur only for the DDL commands that
change the data dictionary.

An ALTER SESSION SET NLS_DATE_FORMAT, for example, doesn´t do COMMIT/ROLLBACK.

Am I right, Tom?

          Followup:

correct -- alter session does not commit.

Just ddl that does stuff to the data dictionary.

      Rating: 4
Implicit
      commits and DDL triggers  December 30, 2002

Reviewer:  Gary  from Sydney, Australia
Just to add, from looking at a trace file, it looks like it's more :

begin
    COMMIT;
    perform any appropriate pre-DDL trigger code;
    do the ddl;
    perform any appropriate post-DDL trigger code;
    COMMIT;
exception
    when others then
         ROLLBACK;
         raise;
end;

So it's probably advisable not to do anything big and horrible in a AFTER DDL
trigger.

      Rating: 5
Why not
      autonomous transaction?  June 23, 2003
Reviewer:
      Cefers.br  from Brasil
Tom,

I was wondering why DDL statements are not executed inside an autonomous
transaction (like sequences do), so they wouldn´t affect any pending user
transaction...

Can you clarify?

          Followup:

that would be as "confusing" as not doing it that way.  anyway, you have atrans
so if you want to, you can.

June 24, 2003 Jon -- Thanks for the question regarding
      "Returning a named constant in a SQL query", version 8.1.7

      You Asked
Is it possible to return a named constant in a SQL query?  In other words, let's
say we want to return the equivalent of:

SELECT 'Business rule violation:  '||to_char(bsnss_rule)
  FROM violation_table;

But, we would like to pick up 'Business rule violation:  ' from a package called
PKG_Constants in which is declared a constant variable called, let's say
businessRuleViolation.  We like to keep our constants in a centrally located
place where they can be easily changed without any additional programming
changes.  For example, if somebody objected to the wording of 'Business rule
violation:  ', we could just change PKG_Constants.businessRuleViolation to
'Business rule exception:  '.  This method seems to work well except in this
case.  Any suggestions?

      and we said...
Inside of PLSQL -- since PLSQL variables are just binds, this:

declare
   cursor c is select constant_pkg.bus_rule_violation || to_char(bsnss_rule)
                from violation_table;
begin
   for x in ( select constant_pkg.bus_rule_violation || to_char(bsnss_rule)
                from violation_table )
   loop
       .....

   open ref_cursor_to_be_returned_to_client for
       select constant_pkg.bus_rule_violation || to_char(bsnss_rule)
                 from violation_table;

   open ref_cursor_dynamic for
       'select :x|| to_char(bsnss_rule)
                from violation_table' using constant_pkg.bus_rule_violation ;

would all work fine.  The issue would be if the SQL is in the client code (like
VB, java, C).  then what you can do is:

create or replace context my_ctx using my_pkg
/

create or replace package my_pkg
as
   bus_rule_violation constant varchar2(255) default := 'xxxxxxxx';
   ...

   procedure init;
end;
/

create or replace package body my_pkg
as

procedure init
is
begin
  dbms_session.set_context( 'my_ctx', 'bus_rule_violation', bus_rul_violation);
  ....
end;

end;
/

then queries OUTSIDE of PLSQL would use

select sys_context( 'my_ctx', 'bus_rule_violation' ) || ......

after calling my_pkg.init once per session to fill them in.

Review &
      Followup

      Rating: 2
How about
      compile time verification?  June 24, 2003

Reviewer:  Denis
Surely it's "cool" to use dbms_session and use sys_context for this purpose
but..
1. It doesn't fully solves the problem because there's no compile time
constant's name verification. And that's IMHO the main thing.
2. It's much simpler to write a function e.g. get_my_param(p_name varchar2)
return varchar2 and use it without calling anything for a session

Is there any way to use constants and not to use literals?
To be?

Denis

          Followup:

i wasn't trying to be "cool", I was trying to solve the problem but anyway

1) huh?  what does that mean?

2) simplier, but check out the performance.  I was trying to give you high
performance, something that won't kill your system.

In PLSQL, i showed you how to use the constants.

OUTSIDE OF PLSQL, no, you cannot use plsql constants -- they do not "exist", you
have to use sql constructs.  And again, I showed you how.

You can use a logon trigger to "init" the package automagically I suppose as
well

      Rating:
      4
Thanks  June 24, 2003
Reviewer:  Jon
       from Hartford, CT USA
I knew that there were solutions in PL/SQL.  I was hoping to be able to do it in
native SQL, but you confirmed that isn't possible.  I didn't think so, but you
have many answers I am unaware of, so I thought it was worth asking the
question.  Thanks.

      Rating: 3
I think this is
      what he means...  June 24, 2003
Reviewer:  Dan
      Kefford  from New Haven, CT
Original poster:

1. It doesn't fully solves the problem because there's no compile time
constant's name verification. And that's IMHO the main thing.

Tom:

1) huh?  what does that mean?

I think he means that if you reference a constant (for the time being, assume so
in a PL/SQL block), say C_FOO, and C_FOO is not declared, you would get a
_compile-time_ error. Indeed, Java, C/C++, and other programmers use named
constants to avoid typographical errors that would otherwise go unnoticed by the
compiler.

      Rating: 3
You can always
      use PLSQL function  June 25, 2003
Reviewer:
      msc
As a work-around you can always use a function. But be aware of performance
issues - you do not want to use it with large result set (function will be
executed for each returned row, even if deterministic). And returned type is
varchar2(4000) not varchar2(255). But you get hard binding.

create or replace package MY_CONST is
   function bus_rule_violation return varchar2 deterministic;
end;
/

create or replace package body  MY_CONST is
   -- constants
   c_bus_rule_violation constant varchar2(255) := 'Business rule violation: ';

   function bus_rule_violation return varchar2 is
   begin
     return c_bus_rule_violation;
   end;
end;
/

select  my_const.bus_rule_violation  from dual;

June 24, 2003 Soumendra -- Thanks for the question regarding
      "what is OLAP", version

      You Asked
I would like to know what is "OLAP" and how can i do that
using oracle8.
Can you send me a list of resources/links.

      and we said...
OLAP is Online Analytical Processing.  You should refer to

http://www.oracle.com/olap/
for a list of solutions and papers we have in this problem
space.

Review &
      Followup

      Rating: 2
using olap
      cubes  June 23, 2003
Reviewer:  A reader
       from Pk
Hi,

I have a good experience of MS Analysis services and using
its cubes.

My question is how can I programmaticaly access
ORACLE OLAP cubes. Can you please give me a simple
java code to query and display the resultset of the cube.

Is Oracle 9i Olap technology have bugs as I have heard??
Can you please tell how to configure a 9i Olap cube
and access it in my front end applications.

Thanks

Thanks

          Followup:

does windoze have bugs as I have heard?
does linux?
does db2?

anyway,

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-OLA
the OLAP developers guides have many examples.

      Rating: 3
olap
      cubes  June 24, 2003
Reviewer:  rabeila

hello a reader from pk, could you plz contact me on my email add?
shukrya

      Rating:
      3
  June 24, 2003
Reviewer:  rabeila

its rabeila@yahoo.com

      Rating: 2
olap
      cubes  June 24, 2003
Reviewer:  rabeila

its rabeila@yahoo.com

      Rating:
      3
  June 24, 2003
Reviewer:  veeresh
       from India
This is very useful for me .
Please Explain about "OLCP"..

Regards,
Veeresh

          Followup:

search google for it

online complex processing.

      Rating: 3
Olap
      Comparison  July 06, 2003
Reviewer:
      olap_dba  from GUL
Hi,

Can you please give a detailed comparison on the OLAP
solutions provided by Microsoft And Oracle. Specifically
between MS analysis services 2000 and Oracle 9i Olap,
MDX (Multidimensional Expressions) language and Oracle
equivalent to MDX.

Thanks


Subject | Article | Last Updated (East coast time)
Field Naming | article only | Jun 25, 2003 08:51 am
resizing redo log and sizing log_buffers. | article only | Jun 25, 2003 08:56 am
FGAC | article only | Jun 25, 2003 08:57 am
dbverify - output | article only | Jun 25, 2003 09:00 am
Pragma Serially_reusaable | article only | Jun 25, 2003 09:06 am
database serial mode ...dbms_job | article only | Jun 25, 2003 11:23 am
question on precision and scale for numbers. | article only | Jun 25, 2003 11:26 am
Trying to avoid fetching across a commit | article only | Jun 25, 2003 11:40 am
Data types - performance considerations - PCTUSed Doubts - Reg | article only | Jun 25, 2003 11:42 am
Long to Varchar2 conversion.... | article only | Jun 25, 2003 11:45 am
Ask Tom in CD's | article only | Jun 25, 2003 11:49 am
How to store images into the database through forms | article only | Jun 25, 2003 11:53 am
How does this delete works? | article only | Jun 25, 2003 11:53 am
Logic Behind alter table ... move tablespace | article only | Jun 25, 2003 11:56 am
difference between sql type and plsql types | article only | Jun 25, 2003 12:05 pm
RAID 5 and RAID 1 | article only | Jun 25, 2003 12:43 pm
search a particular text string in between the long field | article only | Jun 25, 2003 06:57 pm
good naming conventions | article only | Jun 25, 2003 07:04 pm
union all view | article only | Jun 25, 2003 08:34 pm
rename a table | article only | Jun 26, 2003 09:03 am
Fine Grained Access Control | article only | Jun 26, 2003 09:07 am
Any drawbacks to using RMAN? | article only | Jun 26, 2003 09:34 am
row count on your web site | article only | Jun 26, 2003 11:53 am
Runstats valid for 9.2 | article only | Jun 26, 2003 12:30 pm
index | article only | Jun 27, 2003 08:40 am
Intelligent Partitioning Table | article only | Jun 27, 2003 08:42 am
Recovery!!! | article only | Jun 27, 2003 09:10 am
INTERPRETING "Rows" in TKPROF output | article only | Jun 27, 2003 09:21 am
Removing deadlock | article only | Jun 27, 2003 09:26 am
UTL_FILE VS DBMS_OUTPUT VS UTL_TCP | article only | Jun 27, 2003 12:25 pm
list all user(s) group by group_list | article only | Jun 27, 2003 12:46 pm
Memory structure | article only | Jun 27, 2003 02:03 pm
ENABLE NOVALIDATE validating existing data | article only | Jun 27, 2003 02:54 pm
About utl_smtp | article only | Jun 28, 2003 09:19 am
Performancetuning UTL_SMTP | article only | Jun 28, 2003 09:21 am
DATAWAREHOUSE Backups | article only | Jun 28, 2003 08:11 pm
DBA skill set trend | article only | Jun 28, 2003 08:29 pm
Global vs Sys_Context | article only | Jun 29, 2003 09:27 am
How can I pipe the SQL-output in SQL*Plus to another programm? | article only | Jun 29, 2003 10:14 am
IOT and more | article only | Jun 30, 2003 06:51 am
How to control the firing of a Trigger | article only | Jun 30, 2003 07:01 am
Storing Date and Time in Separate fields Vs Using the same field | article only | Jun 30, 2003 11:16 am
DATE to TIMESTAMP and vice-versa | article only | Jun 30, 2003 11:38 am
Connection times out after awhile | article only | Jun 30, 2003 11:41 am
Subtracting from a large table | article only | Jun 30, 2003 11:55 am
Using the LIKE predicate in a WHERE clause | article only | Jun 30, 2003 01:24 pm
Parallel Execution | article only | Jun 30, 2003 02:47 pm
any performance hit using sequence with nocache in OLTP | article only | Jun 30, 2003 04:15 pm
How to pass a PSP stored procedure parameter (Multiple Inserts) | article only | Jun 30, 2003 07:23 pm
problem while partitioning | article only | Jul 01, 2003 10:38 am
freelist, block | article only | Jul 01, 2003 12:36 pm
History on Hierarchy | article only | Jul 01, 2003 02:17 pm
Accessing data from a non oracle database | article only | Jul 01, 2003 03:06 pm
row(s) 1 - 53 of 53
June 25, 2003 Todd -- Thanks for the question regarding "Field Naming", version Version 8
You Asked
I was asked by a friend why oracle does not support field names
starting with a 0 (zero). I was not aware you could not. I have
never actually tested it. Do you know of any restriction for
leading a field with a 0 (zero)?

Thanks,

Todd Johnson
and we said...
The following rules apply when choosing an unquoted identifier:

o must be from 1 to 30 characters long.
o cannot contain quotation marks.
o are not case sensitive.
o must begin with an alphabetic character.
o can contain only alphanumeric characters and the underscore
(_), dollar sign ($), and pound sign (#).
o cannot be an Oracle reserved word (eg: SELECT).

We can violate ALL (except the length rule) of these rules using
a quoted identifier:

ops$tkyte@ORA8I.WORLD> create table "0 Begins with ZERO!"
  2  ( "0 'This has quotes'"  int,
  3    "0 'THIS has quotes'"  int,
  4    "!@%^&"                int,
  5    " "                    int
  6  )
  7  /

Table created.

ops$tkyte@ORA8I.WORLD> desc "0 Begins with ZERO!";
 Name                          Null?    Type
 ----------------------------- -------- --------------------
 0 'This has quotes'                    NUMBER(38)
 0 'THIS has quotes'                    NUMBER(38)
 !@%^&                                  NUMBER(38)
                                        NUMBER(38)
Review & Followup
Rating: 5
to use quoted identifiers or not to use June 25, 2002
Reviewer: Paul Druker from Fairfax, VA USA
We are developing a Java application that uses "application names" that are not
in sync with our table columns. We are thinking of synchronizing these names by
creating a view per each table. However, in "regular" views, names will be in
the uppercase, still not matching Java mixed-case naming convention.

I know that "it's not recommended" to use quoted identifiers. What kind of
disadvantages will we have if we use them in our views, and start performing
DMLs against these views? Will there be any additional overhead? Does Oracle
guarantee that it will scontinue supporting this feature in future?

I don't really want to use quoted identifiers, I just want to know exactly why I
shouldn't.

Followup:
quoted identifiers are part of the definition of the SQL language, they are here
to stay.

You can take some things too far -- forcing case sensitivity on SQL just cause
it is in java would be going too far in my opinion (IMHO -- the best java
programs don't have select/insert/update/delete in them, only BEGIN... END;
blocks!  That would remove that whole case sensitive issue all together)

Other tools that may access these views may have problems -- if that is not a
requirement (eg: java and only java will access these views) there is not any
overhead with regards to quoted identifiers.

Rating: 5
thist is a test June 25, 2002
Reviewer: A reader
hello

Rating: 3
Know Valid Identifer June 02, 2003
Reviewer: Vikas Sharma from Delhi India
Hi Tom,

Database :9i, mod_plsql html form

In one of my application i give user to enter the table name and column name of
the table they want to create.

When the the form is submited in the action procedure i create a string like:

create table user_enter_name (user_enter_col  datatype,... )

Then i create the statment and parse it by dbms_sql.parse and handle the
exception for invalid create statement.

I want to check whether the names enter by user are valid or not before my
string is created or executed. Somthing like a function in which i pass a word
it returns me that the word is not valid ideftifer or invalid table
name(reserved word like SHARE).

Regards,
Vikas Sharma

Followup:
there is no such builtin function.  You can write one but it involves yet more
dynamic sql -- so make sure it is "worth it" as it will hit your shared pool
pretty hard if you do this often.

Just

begin
  execute immediate 'declare ' || user_input || ' number; begin null; end;';
exception
  when others then you have an invalid identifier;
end;

Another way, that won't hit it hard would be to use dbms_utility.comma_to_table.
 This function expects a list of VALID identifiers and will parse them into a
plsql table. if they are invalid, it'll fling an exception:

ops$tkyte@ORA920LAP> declare
  2          l_table dbms_utility.uncl_array;
  3          l_n     number;
  4  begin
  5          dbms_utility.comma_to_table( 'TEST', l_n, l_table );
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> declare
  2          l_table dbms_utility.uncl_array;
  3          l_n     number;
  4  begin
  5          dbms_utility.comma_to_table( 'TEST BAD', l_n, l_table );
  6  end;
  7  /
declare
*
ERROR at line 1:
ORA-20001: comma-separated list invalid near T BAD
ORA-06512: at "SYS.DBMS_UTILITY", line 174
ORA-06512: at "SYS.DBMS_UTILITY", line 202
ORA-06512: at line 5

ops$tkyte@ORA920LAP> declare
  2          l_table dbms_utility.uncl_array;
  3          l_n     number;
  4  begin
  5          dbms_utility.comma_to_table( 'COMMIT', l_n, l_table );
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> declare
  2          l_table dbms_utility.uncl_array;
  3          l_n     number;
  4  begin
  5          dbms_utility.comma_to_table( 'TABLE', l_n, l_table );
  6  end;
  7  /
declare
*
ERROR at line 1:
ORA-00931: missing identifier
ORA-06512: at "SYS.DBMS_UTILITY", line 125
ORA-06512: at "SYS.DBMS_UTILITY", line 160
ORA-06512: at "SYS.DBMS_UTILITY", line 202
ORA-06512: at line 5

Rating: 5
forms_ddl should do the trick June 02, 2003
Reviewer: Reader from Henry, Illinois
declare
  var varchar2(400);
  iName := :block.itemName;
  invalid_obj_Name Exception;
Begin
  var := 'Declare  ' || iName || ' number; begin null;
          end;' ;
       forms_ddl(var);
        IF NOT Form_Success THEN
           Raise Invalid_obj_Name;
       END IF;
Exception
   when invalid_obj_name then
        message('Invalid column or table name ');
   when others then
        message('.. .. trigger raised unhandled exception');
End;

PS:  Tom you gave me this suggestion long back.

Followup:
mod_plsql = execute immediate.

Rating: 5
"mod_plsql" June 02, 2003
Reviewer: Reader from Henry, Illionois
I need to do something I don't usually do -- read the question completely.

Rating: 5
Thanks Tom, I now have total job security. June 02, 2003
Reviewer: Chuck Jolley from Oklahoma City, OK USA
On the theory that one should never let a feature go unused, I have desided to
redesign our DB a tiny bit to produce total job security.
No one else will be able to figure out how to query the thing.
:)

SQL*Plus: Release 8.1.7.0.0 - Production on Mon Jun 2 10:02:17 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

SQL> @connect chajol@tax_cupid
Enter password:
Connected.

chajol@tax.cupid> create table "from"("select" int, "Select" int, "from" int);

Table created.

chajol@tax.cupid> select "select", "from", sum("Select") from "from" group by
"select", "from";

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (GROUP BY)
   2    1     TABLE ACCESS (FULL) OF 'from'

chajol@tax.cupid>

Rating: 5
You too Full of 'from' June 02, 2003
Reviewer: A reader

June 25, 2003 B en -- Thanks for the question regarding "resizing redo log and sizing log_buffers.", version
You Asked
What is the easiest way to resize REDO logs! (without
using ALTER DATABASE DATAFILE 'redo01.dbf' RESIZE 96M)

These are the REDO logs
logfile
group 1 ('/oradb02/RIMSDB/redo1a.dbf',
'/oradb03/RIMSDB/redo1b.dbf') size 32m ,
group 2 ('/oradb02/RIMSDB/redo2a.dbf',
'/oradb03/RIMSDB/redo2b.dbf') size 32m ,
group 3 ('/oradb02/RIMSDB/redo3a.dbf',
'/oradb03/RIMSDB/redo3b.dbf') size 32m ,
group 4 ('/oradb02/RIMSDB/redo4a.dbf',
'/oradb03/RIMSDB/redo4b.dbf') size 32m ,
group 5 ('/oradb02/RIMSDB/redo5a.dbf',
'/oradb03/RIMSDB/redo5b.dbf') size 32m ,
group 6 ('/oradb02/RIMSDB/redo6a.dbf',
'/oradb03/RIMSDB/redo6b.dbf') size 32m ;

I need to make them larger (96M).

Also
Large (60Gb) production OLTP system what size should the
log_buffer be, currently log_buffer = 3145728 (should I make it
larger when I resize the REDO's?)
and we said...
thats a trick question.  First of all -- redo log files are NOT datafiles
(hence, that command won't work) and second of all -- you cannot resize redo log
files.

You must drop each group and then recreate it.  That is the only way.

The log_buffer automagically empties:

o every three seconds
o when you commit
o when it is 1/3 full

So, unless you have

o concurrent transactions whose total changes are 1meg AND
o they take less then three seconds from start to finish

OR

o single transactions that take less then three seconds and generate 1 or more
meg of redo

you don't need to do anything with the log buffers.  3meg is pretty huge  - its
a function of the number of concurrent transactions and the amount of data they
can change.  Since the log buffers are flushed with each commit as well -- if
you are in a situation where lots of commits are frequently happening (eg: you
do >1 TPS) having a large log buffer is not productive (waste of memory).
Resize your log buffer with that in mind.

followup to comment

I'm glad to supply more info but in this case -- there is no command to RESIZE a
log file.  As I said, you must DROP and CREATE them:

alter database drop logfile '.......';

alter database add logfile ....;

See
http://technet.oracle.com/doc/oracle8i_816/server.816/a76989/ch4b3.htm#29280
for the full syntax.
Review & Followup
Rating: 2
You're assuming a level of knowledge some of us don't yet have June 27, 2001
Reviewer: Jean Therese from Chicago
What is the command for resizing? I gathered from the response what needs to be
done, but still don't know how to do it.

This is why we're here - to learn all these necessary things we don't know.
Thanks for the volume of info.

Rating: 3
RBS: deleting last valid member of a group? June 20, 2003
Reviewer: Aivar from Canada
Hi Tom.

My question is a bit off topic, I am stuck with a sentence from
http://studyguides.cramsession.com/
for OCP
exams.
It's regarding rbs management: "You cannot drop the last valid member of a
group". Can you explain this, does it make sense?

Thanks.

Followup:
it has nothing to do with RBS

it sounds like they are talking about your online redo logs, you have N groups
of redo, each with M members.  You can drop members of a group.
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96521/onlineredo.htm#624

Rating: 4
Re: cannot drop the last valid member June 22, 2003
Reviewer: Aivar from Canada
Hi Tom,

Sorry, yes this is regarding redo! But they still have this at
http://studyguides.cramsession.com/cramsession/oracle/oracle8iadmin/guide.asp?pn=9
It says "You cannot drop the last valid member of a group".
I think this just means you can't drop all members of a redo log file group that
contains valid data. You have to use 'alter database drop logfile' and get rid
of whole group instead.

Thanks

Followup:
read the link I provided, it basically says exactly the same thing.  It is the
real documentation (and it is all there for you..)

Rating: 5
resizing redo log and sizing log_buffers June 24, 2003
Reviewer: Rahul Chaudhari from india
 Actually we can't resize the redo logfiles we have to drop the redo logfile and
then recreate the redo logfile again with the new size what-ever we want. as the
log buffers are flushed with each commit, so this option also wont work properly
for this case. u'll have to recreate the logfile again and drop the first one.

Followup:
what?

it is noted above that you cannot resize log files, but I don't understand your
"as the log buffers are flushed ... so this option won't work"

what option?
what wont work?

June 25, 2003 Ken -- Thanks for the question regarding "FGAC", version 9i
You Asked
I have read your articles on FGAC and searched your archives.  The information
that I gathered is very useful.  What I need to know at this time is this...are
there any significant changes to FGAC between version 8 and 9 that I need to
consider?

I read article 2 and actually installed the sample project to give it a test.
In ALL attempts, I am getting a policy error...except as expected when I log in
as SYS or SYSTEM.

Like you I deal in databases, but I have to support a JAVA app that is logging
in through a web interface to OAS.  Client -server is easy...this is a browser
based application.  We want to have multiple clients (companies) with multiple
employees (grouped into roles) access the app this way and only see the
appropriate data and be limited by their role once connected and recognized.  To
quote you..."sounds like a perfect use of FGAC".

My problem is now two-fold:
-recognizing the actual user as they connect.  This is further complicated by
the Java App using a connection pool; and
- solving the policy error.

Any help would be appreciated
and we said...
If you have my book -- I have a "new improved" article 2 in there, but in any
case -- I directly address debugging these things on the web at:
http://asktom.oracle.com/~tkyte/article2/index.html#Errors
use the debug.f function which uses utl_file to allow you to create a trace
file.

Between the versions 8i and 9i, the most significant enhancment is the ability
to partition FGAC by application.  By that I mean that your "hr" application can
have one policy applied to it and your "finapps" application -- a different
policy (given the same exact tables).  It is more flexible in that fashion.

Now, as for identifying the users -- what you would do is have the java guys
(who are logging in using some silly "common schema" -- i hate that) tell you
who is really there.  They will call a stored procedure (which will probably
"pain" them -- its not pure java anymore).  They will pass the identity down to
this procedure and this identity will be placed into the application context.
Your predicate policy function now will know "who is there"

In 9i, they can go a step further and use ntier proxy authentication -- works
with connection pooling.  Here they log in as their single user ON BEHALF of
some user.  So, if you select user from dual -- it'll be the identity of the guy
running the BROWSER -- not their "common schema".  If you get the PROXY_USER,
it'll be their common schema name (this is how we can do application level FGAC
-- Using this feature)

see
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/java.920/a96654/oci_func.htm#1014235
for details on that new feature.
Review & Followup
Rating: 5
Active Server Pages IIS March 03, 2003
Reviewer: Sujata from Germany
This N-Tier Authentication can also work if the middle tier app server is IIS
(Microsoft) and front end is active server pages?

Followup:
if asp can use oci or java, yup.

Rating: 4
About FGAC in your book June 24, 2003
Reviewer: Tony from India
I red your book 'Expert one-on-one' on FGAC  in page 927. It says we need to
create views for all tables to enforce security. What if I have 1000 tables?
There is a need to create 1000 views and 1000 x 3 ploicies for select, update,
insert& delete. This will increase the adminstration work and confusing if there
are more tables. Is there any simple way to do all these things? Can't I do
without views?

Followup:
where did i say "you NEED to create views"?

I said "you can"
I said "you might want to in some cases" (my example was a self referencing one
where a view was used to help us out)

I did not say "you must"

You do not need to and most of my examples in fact do not use views.

June 25, 2003 -- Thanks for the question regarding "dbverify - output", version 8.1.5
You Asked
I ran dbverfiy and got the report below.

DBVERIFY - Verification starting : FILE = system01.dbf
DBVERIFY - Verification complete

Total Pages Examined         : 35840
Total Pages Processed (Data) : 14635
Total Pages Failing   (Data) : 0
Total Pages Processed (Index): 5998
Total Pages Failing   (Index): 0
Total Pages Processed (Other): 862
Total Pages Empty            : 14345
Total Pages Marked Corrupt   : 0
Total Pages Influx           : 0

Total Pages Processed (Other)  - are these header pages?
Total Pages Influx                        - what are they ?
what do we have to do if count is greater  than 0 ?

Thanks
and we said...
Total pages Processed (Other) are pages that are not segment
pages, yes.  For example -- Here is a datafile i have that is
part of a locally managed tablespace (that is empty):

> dbv blocksize=8192 file=/tmp/lm.dbf

DBVERIFY: Release 8.1.5.0.0 - Production on Mon Jun 26 14:14:01
2000

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

DBVERIFY - Verification starting : FILE = /tmp/lm.dbf

DBVERIFY - Verification complete

Total Pages Examined         : 1288
Total Pages Processed (Data) : 0
Total Pages Failing   (Data) : 0
Total Pages Processed (Index): 0
Total Pages Failing   (Index): 0
Total Pages Processed (Other): 8
Total Pages Empty            : 1280
Total Pages Marked Corrupt   : 0
Total Pages Influx           : 0

The 8 other pages are the 64k bitmap used by the locally managed
tablespace.

Pages influx can result from hot backed up datafiles with
fractured blocks (a block whose head != tail).  This happens
because the OS blocksize is smaller then the database blocksize
and while we are backing up a file we might read the HEAD of the
block (say the first 4k of 8k) -- get interrupted -- the
database DBWR rewrites the block -- we get runable again and
finish reading the last 4k of another block.  This is
expected and normal against a live datafile or a datafile that
is the result of a hot backup.  Whether that page is actually
corrupt or not is not known at that point, only after we recover
will we know if the block is "correct".  You should not see
influx blocks against a datafile that is the result of a cold
backup.
Review & Followup
Rating: 3
June 24, 2003
Reviewer: Marc from belgium
Hello Tom,
I am not sure to understand this  :
>Whether that page is actually
>corrupt or not is not known at that point, only after we >recover will we know
if
>the block is "correct".
Please, could you explain ?
Thanks in advance,
Marc

Followup:
a hot backup using OS copies can result in "fractured blocks" since the OS reads
the file with a different block size then the database manages.

EG: OS reads 512bytes at a time.  Oracle manages a block say 8k at a time.  It
is possible for an OS copy to read the first 4k of that 8k block -- then have
Oracle OVERWRITE that block and the OS copy reads the next 4k of that block --
but that block is at a DIFFERENT point in time.

after recovery -- the block will be fine.

the hot backup -- "looks" corrupt

Rating: 5
June 25, 2003
Reviewer: A reader

June 25, 2003 Suresh -- Thanks for the question regarding "Pragma Serially_reusaable", version 8.1.7
You Asked
Tom,
  I was going through the Oracle documentation and I came across this "Pragma
serially_reusable" feature in Packages. Could you please explain when this
feature can be used?

2. When to use the "DETERMINISTIC" option in a Function.

3. What is the difference between DBMS_TRACE and SQLTRACE, TKPROF? I mean, what
more information could you get using dbms_trace over SQLTRACE,TKPROF? (Or am I
comparing apples and oranges here).

Thanks in advance,
Suresh.
and we said...
1) the documentation gives you that.

<quote>
To help you manage the use of memory, PL/SQL provides the pragma SERIALLY_
REUSABLE, which lets you mark some packages as serially reusable. You can so
mark
a package if its state is needed only for the duration of one call to the server
(forexample, an OCI call to the server or a server-to-server RPC).

The global memory for such packages is pooled in the System Global Area (SGA),
not allocated to individual users in the User Global Area (UGA). That way, the
package work area can be reused. When the call to the server ends, the memory is
returned to the pool. Each time the package is reused, its public variables are
initialized to their default values or to NULL.
</quote>

Its basically saying 'if you use a package and you don't save a state in that
package and would like avoid having the persistance of the package in your
session -- less memory -- use this'

I haven't used it myself.

2) when creating a function based index.  see
http://osi.oracle.com/~tkyte/article1/index.html
3) dbms_trace is totally different.  It is a plsql debug/diagnostic tool.  You
can trace exceptions and other events with it.  search for dbms_trace on my site
here for usage.

sql_trace + tkprof analyze your SQL performance, regardless of whether you are
running with plsql, vb, java, whatever.  see
http://asktom.oracle.com/~tkyte/tkrprof.html
for the docs on that.

apples and oranges.
Review & Followup
Rating: 4
please explain in a lay man's language December 22, 2001
Reviewer: Kulguru
Tom

What do you mean by

an OCI call to the server or a server-to-server RPC

Followup:
Well, that was a quote but it basically just means a "call".

OCI is Oracles Call Interface, a C API.  there i might make a call to a
procedure in the database, that would be a call

Or i might make a call over a database link to another database- that is a
server to server RPC (remote procedure call)

Rating: 4
SERIALLY_REUSABLE vs. DBMS_SESSION.reset_package September 09, 2002
Reviewer: A reader
In an environment with hundreds of database sessions, and hundreds of PL/SQL
packages, most of which have package variables (including PL/SQL records and
tables) that all need to be cleared out for subsequent server calls, which
approach would usually be better for performance - using the PRAGMA
SERIALLY_REUSABLE in the packages, or including a call to
DBMS_SESSION.reset_package for every call to the server?  The purpose of using
one of these features is to clear out the package variables so that no values
will be left over from a prior call to the server, which is especially important
with connection pooling.

If one is sometimes better but also sometimes worse than the other (as I expect
it is), what are the conditions that would influence which is better or worse?

Followup:
If you don't need the package variables to persist across ANY calls, not a
single call -- serially reusable (but then again, hmm, why did you use package
globals in the first place????  confusion from me there -- seems like local
variables would have done it???)

dbms_session.reset_package, while MUCH faster then logging in and out, has some
performance implications (not as fast as not doing it obviously!).  That and
it'll rip down the entire session state, eg: any opened cursors you have
(prepared statements) as well.

Rating: 5
My follow-up to your follow-up September 09, 2002
Reviewer: A reader
Thanks a lot for your help.

Package-level variables were used because the code was originally written for a
client-server environment.  It made sense in that environment because the same
database session would be maintained as the user moved from canvas to canvas or
form to form.

But now there is a requirement to have the code called from other environments
such as an application server or web server.  Package state will need to be
maintained across multiple procedure calls within the scope of a single
anonymous PL/SQL block passed in from the app/web server, but package state is
specifically _not_ to persist across multiple calls to that block.
SERIALLY_REUSABLE seems to meet that criteria, according to the Oracle
documentation.

Followup:
As long as you do not call any packages not marked serially reusable yes (eg:
all dbms_, utl_, htp, htf, owa, owa_util, etc -- they all maintain a state as
well and it might not work in your environment if they do (or it might))

Rating: 5
State in Oracle packages September 10, 2002
Reviewer: A reader
Yes, that is something important to think about because the Oracle packages
cannot be made serially reusable.  I figure that as long as they are used in
such a way that their externally observable state is cleaned out before a
connection goes back into the pool, the internal state of the Oracle packages
should not hurt the application's behavior when the next end-user grabs the
connection from the pool.  For example, if all UTL_FILE files are closed out, it
should not matter to the next end-user that the previous user who had the same
database session was using UTL_FILE.  Of course, any code using the stateful
Oracle packages will have to be reviewed and tested to death to ensure that
their state does not cause a problem.

Rating: 5
pragma SERIALLY_REUSABLE for web applications June 24, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
Would you recommend using this PRAGMA for our packages used in our web
applications? Typically, the client calls a packaged procedure, some job is done
and result is returned. In this case we do not care about the session state.

1). Is it advisible to use this for packages used in web applications?
2). Are there any cavets (problems) if we do decide to use it?
3). Any performance issues? (rutime penalties or runtime efficiency or ...)
Would its use be more efficient or would we have performance problems?
Thanks,

Followup:
no need in mod_plsql, it already takes care of that.

I would not recommend it, not heavily used.  I've not used it myself

June 25, 2003 Winston -- Thanks for the question regarding "database serial mode ...dbms_job", version 9i R2
You Asked
1. Referring to
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/hintsref.htm#7834
<quote>
The APPEND hint lets you enable direct-path INSERT if your database is running
in serial mode. Your database is in serial mode if you are not using Enterprise
Edition. Conventional INSERT is the default in serial mode, and direct-path
INSERT is the default in parallel mode.

In direct-path INSERT, data is appended to the end of the table, rather than
using existing space currently allocated to the table. As a result, direct-path
INSERT can be considerably faster than conventional INSERT.
</quote>

How can I tell if my database is running in serial mode or not? If I am using
Enterprise Edition, does it mean that my database is in serial mode? What are
the difference between serial and parallel mode?

2. I set up a db job (via dbms_job.submit and commit) and scheduled it to run
every day at 11:00pm, and it has been working very well for the past months. I
used the knowledge on this web site and ensured it would never be broken (just
writing exception to a table and ignore it). Recently I checked the exception
table and found no records there but I couldn't find my scheduled job appear in
user_jobs any more.  I wonder besides people ran dbms_job.remove(), is there any
other ways that the job could be removed from user_jobs view? In others words,
is it true that 'unless you remove them by calling dbms_job.remove or job is
done, Oracle retains jobs indefinitely'?

Thanks and Best Regards,
Winston
and we said...
1) if you haven't issued "alter session" to enable parallel DML, you are in
"serial" mode.

And bear in mind, this append hint ONLY works and should ONLY be used for large
BULK loads that use insert into SELECT.  If your insert has a VALUES clause that
hint is just a comment.

2) Oracle will retain it indefinitely IF you have an interval that evaluates to
a non-null value.
Review & Followup
Rating: 5
Thanks June 23, 2003
Reviewer: Winston
Hi Tom,

1) If I understand you correctly. Performance tuning document doesn't have to
introduce this concept 'serial mode' /'parallel mode' to explain the APPEND
HINT. Since 'serial/parallel mode is not similar to archivelog/noarchivelog
mode. serial/parallel is for session and DML level. But archivelog/noarchivelog
is for database level.

2) Thanks for the summary

Best Regards,
Winston

Followup:
sure it does.  it says "when in regular serial mode, inserts are conventional
path.  when in parallel mode, inserts will be direct path"

I will concede that their sentence structure leaves a bit to be desired in the
manner they phrased it.

Rating: 5
It's clearer now June 23, 2003
Reviewer: Winston
Hi Tom,

After reading your comments together with table 22 in
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76965/c21dlins.htm#4962,
it becomes much clearer now. Thank you so much!!

I tried out the examples of serial and parallel direct-load insert provided at
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76965/c21dlins.htm#4108,
I could see direct-load working from the tkprof result, but couldn't see
parallel is working. Here is the result.
********************************************************************************

INSERT /*+ APPEND */ INTO emp
   SELECT * FROM t_emp

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.94       1.02          0         14         26        2048
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.95       1.03          0         14         26        2048

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 114

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  LOAD AS SELECT
      0   TABLE ACCESS FULL T_EMP

********************************************************************************
********************************************************************************

COMMIT

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          5           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          0          5           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 114
********************************************************************************

ALTER TABLE emp PARALLEL 10

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.01       0.02          0          7          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.02       0.02          0          7          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 114
********************************************************************************

********************************************************************************

ALTER TABLE t_emp PARALLEL 10

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.01          0          0          1           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.01          0          0          1           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 114
********************************************************************************

ALTER SESSION ENABLE PARALLEL DML

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0

------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          0          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 114
********************************************************************************

INSERT INTO emp
   SELECT * FROM t_emp

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.01          0          0          0           0
Execute      1      0.05       2.44          0         51        297        2048
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.07       2.45          0         51        297        2048

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 114

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  LOAD AS SELECT
      0   TABLE ACCESS FULL T_EMP

********************************************************************************

ALTER SESSION ENABLE PARALLEL DML

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          0          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 114
********************************************************************************

INSERT /*+ PARALLEL(emp,12) */ INTO emp
   SELECT /*+ PARALLEL(t_emp,12) */ * FROM t_emp

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          2          8           0
Execute      1      0.02       2.55          0         51        297        2048
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.03       2.55          0         53        305        2048

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 114

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  LOAD AS SELECT
      0   TABLE ACCESS FULL T_EMP

I thought the reason why I couldn't see 'parallel' was that t_emp was too
small(2048 rows). then I loaded it up to 16384 rows, but the tkprof result
remained the same. Would you please explain why we couldn't see 'parallel'
working?

Many Thanks!

Followup:
umm, maybe you did?  what were you expecting to see, but anyway -- parallel is
to be used to take something that takes minutes or more and maybe make it
faster.

perhaps you do not have parallel set up (parallel max servers = 0 or something)

Rating: 5
It's more interesting June 24, 2003
Reviewer: Winston
idev@WYPS> show parameter parallel

NAME                                 TYPE                             VALUE
------------------------------------ --------------------------------
--------------------------

fast_start_parallel_rollback         string                           LOW
optimizer_percent_parallel           integer                          0
parallel_adaptive_multi_user         boolean                          FALSE
parallel_automatic_tuning            boolean                          FALSE
parallel_broadcast_enabled           boolean                          FALSE
parallel_execution_message_size      integer                          2152
parallel_instance_group              string
parallel_max_servers                 integer                          20
parallel_min_percent                 integer                          0
parallel_min_servers                 integer                          0
parallel_server                      boolean                          FALSE
parallel_server_instances            integer                          1
parallel_threads_per_cpu             integer                          2
recovery_parallelism                 integer                          0

I ran the following in one session

ALTER TABLE emp PARALLEL 10;

Table altered.

ALTER SESSION ENABLE PARALLEL DML;

Session altered.

 INSERT INTO emp
       SELECT * FROM t_emp;

2048 rows created.

 commit;

Cocurrently I ran the following query in another session

 SELECT slave_name,status, cpu_secs_total
  2  FROM v$pq_slave;

SLAV STAT CPU_SECS_TOTAL
---- ---- --------------
P000 BUSY              0
P001 BUSY              0
P002 BUSY              0
P003 BUSY              0
P004 BUSY              0
P005 BUSY              0
P006 BUSY              0
P007 BUSY              0
P008 BUSY              0
P009 BUSY              0
P010 IDLE              0
P011 IDLE              0
P012 IDLE              0
P013 IDLE              0
P014 IDLE              0
P015 IDLE              0
P016 IDLE              0

17 rows selected.

I believe I used parallel DML but tkprof remained the same:

INSERT INTO emp
   SELECT * FROM t_emp

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.16      10.56          0         51        297        2048
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.17      10.57          0         51        297        2048

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 114

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  LOAD AS SELECT
      0   TABLE ACCESS FULL T_EMP

********************************************************************************

I had expected to see tkprof's execution plan similar to  below
insert into t1 select * from t1;

4 rows created.

Execution Plan
----------------------------------------------------------
   0      INSERT STATEMENT Optimizer=RULE (Cost=1 Card=2000 Bytes=4030000)
   1    0   TABLE ACCESS* (FULL) OF 'T1' (Cost=1 Card=2000 Bytes=4030000)
          :Q2000

   1 PARALLEL_TO_SERIAL            SELECT /*+ Q2000 NO_EXPAND ROWID(A1) */ A1."
                                   ID",A1."DATA" FROM "T1" PX_GRANULE(0, BLOCK_
                                   RANGE, DYNAMIC)  A1

Now I assume we cannot easily tell if we're using parallel dml for
insert...select just from tkprof result. Is this right assumption?

Followup:
correct -- given the high elapsed time, hopefully you see that PQ would be the
wrongest thing in the world in this case.

June 25, 2003 Vinayak -- Thanks for the question regarding "question on precision and scale for numbers.", version
You Asked
Oracle Documentation says
Number having precision p and scale s. The precision p can range

from 1 to 38.
The scale s can range from -84 to 127.
I created a table numtest1
create table numtest1 (id number(38,-84));
I inserted a row into it
insert into numtest1(id)
values(12345678912345678912345678912345678912);
when i select from the table the output was
SQL> select * from numtest1;

ID
---------
0

Now my question is if precision can not be more than 38 what is
the use of having
scale as -84( for that matter anything more than 38)

Can u please give an appropriate answer.
If this is used somewhere can u please give me a working example
waiting for your reply
vinayak
and we said...
precision is the number of significant digits.
scale is the number of digits to the right (positive) or left
(negative) of the decimal point.

Your number was way too small to be stored in a number with 84
digits to the left of the decimal point.  Try this one instead
where I put in a really big number:

ops$tkyte@8i> create table numtest1 (id number(38,-84));
Table created.

ops$tkyte@8i> insert into numtest1(id)
values(12345678912345678912345678912345678912);
1 row created.

ops$tkyte@8i> insert into numtest1(id)
values(12345678912345678912345678912345678912*power(10,70) );
1 row created.

ops$tkyte@8i> select * from numtest1;

        ID
----------
         0
1.235E+107

It'll make lots more sense if you use more reasonable numbers to
see the effect.  The following makes a number with upto 7 digits
of "signifigance" (eg: there will never be more then 7 digits in
the number).  It also limits the number of decimal places to 2
for A, 1 for B, 0 for C.  D and E can only store numbers with a
scale of 10's and 100's repectively:

ops$tkyte@8i> create table t ( a number(7,2), b number(7,1), c
number(7,0), d number(7,-1), e number(7,-2) );
Table created.

ops$tkyte@8i> insert into t values ( 123.456, 123.456, 123.456,
123.456, 123.456 );
1 row created.

ops$tkyte@8i> select * from t;

         A          B          C          D          E
---------- ---------- ---------- ---------- ----------
    123.46      123.5        123        120        100

the precision and scale are formats applied to the data and
rounding is used to make them fit.
Review & Followup
Rating: 5
scale of number June 24, 2003
Reviewer: sean from NJ
Hi Tom,

I have a question about number (usng 817 and 9i)

create table t1(c1 number, c2 number(6,3));

insert into t1(c1, c2)
values(1, 0.5);

insert into t1(c1, c2)
values(2, 0.500);

SQL> select * from t1;

        C1         C2
---------- ----------
         1         .5
         2       .5

I understand that Oracle does not store leading or trailing zeros.  Our VB
developers want me to store 0.500 as 0.500 in the database, so they don’t need
to change any code.  Here are the reasons:
(1)    They want VB front end show 0.5 instead of .5.
(2)    The VB code checks the trailing zero.  If it is 0.5, the result of this
calculation of 0.5 * 0.111 will be 0.1.  It is 0.500, the result of this
calculation of 0.500 * 0.111 will be 0.056.

Here are the solutions I can provide:
(1)    Use varchar2 for column c2  (I don’t like it).
(2)    For trailing zero, I can ask them to use to_char(c2, 999.999), but they
have to modify the query code.

My questions are:
(1)    Since the scale of the column is 3, is it true that 0.5 and 0.500 are
treated the same in the database (which is 0.500)?
(2)    How do I add leading zero to the data and pass it to the VB?
(3)    Are there other ways to pass 0.500 to VB besides using to_char function?

Thanks so much for your help.

Followup:
how about solution 3)  get coders that do their job?  that write software to
specifications?  that fix bugs in their code?

A number is a number, it is not a string.  leading and trailing zeros are
MEANINGLESS in a number.  they are "formats", for display (you know, the things
the VB coders are responsible for)

If vb really says taht 0.5 * 0.111 = 0.1, then vb is not a language that is
usable in any environment -- there has to be something else going on there..
that doesn't even begin to make sense

ops$tkyte@ORA920LAP> select 0.5 * 0.111 from dual
  2  /

 0.5*0.111
----------
     .0555

ops$tkyte@ORA920LAP>

my solution is and will be "tell the coders to do their jobs, they have a bug in
their code.  they need to fix this bug".

Rating: 5
scale of number June 26, 2003
Reviewer: Sean from NJ
Thanks so much.

I still provided the query solution to VB developer in case they need it.

create table t1(c1 number, c2 number(6,3));

insert into t1(c1, c2)
values(1, 0.500);

insert into t1(c1 ,c2)
values(2, 100.50);

select (decode(instr(c2, '.'), 1,
        concat('0', trim(to_char(c2, .999))),
        to_char(c2, 9999.999))) from t1;

June 25, 2003 Craig -- Thanks for the question regarding "Trying to avoid fetching across a commit", version 9.0.1.2
You Asked
Hi, Tom -

I'm working on our nightly ETL process, grabbing all the prod data that is new
or newly updated from the day before.  Of course this means the primary key from
the source system is no longer unique.  My insert uses a sequence to generate a
new primary key

Now the code I have inherited loops through a cursor and commits every 1000
rows.  I've been reading this site for some time, and I know you loathe fetching
across a commit, as it leads to ORA-01555 and other such calamities.

Option A)  So I remove the loop, and thereby the commit interval, and do it as
one big bulk insert (as many as 100k rows on some nights)

Option B)  I loop through the entire cursor and insert row by row, and then
commit once I'm done.

Both options work, and both result in my DBA saying "you need to commit every n
rows!!  100k row unit of work is too big for one commit."  What can/should I do?
 Is there some option I'm yet to consider?  Your word carries wieght around
here...maybe you can convince him.

Thanks for all your help, Tom.
and we said...
your DBA needs to worry about backup and recovery and leave transactional
integrity to you as they do not have a clue in that regard (apparently).

A unit is work is never dictated by SIZE -- it is dictated by business
requirements.

Committing frequently will only:

o increase the probability of an error
o cause you to run slower
o make your code more breakable

is that what the DBA wants to do?  Make you run slower, with more errors and
stand a chance of logically corrupting the data????

100k rows?  that is toy sized -- small, small potatoes, nothing.  I do tons more
than that on my laptop.

Tell the DBA, Ok, I'll commit ever N rows -- N=10,000,000 -- no worries!

If you can, use insert /*+ append */ and bypass some of the UNDO generation
(index undo will still be generated)!  That is the only thing "committing" would
"release"....

You should find option (a) to be the most expedient, easiest to code, trivial to
maintain.
Review & Followup
Rating: 3
Trying to avoid fetching across a commit February 03, 2003
Reviewer: subbu from Eden Prairie MN, USA
hi tom, i live in old database world with 8i and few 734 where i face prblem
with rollback segments depending on the commit frequency. Does this holds good
for my environment also. we do have ETL jobs running daily and on monthly basis.

Followup:
doesn't matter how old your database is -- it has always been that

o frequent commits slow you down
o probably break your data integrity
o is harder to code

version 6 on....

All you need to do is actually SIZE your rbs for the work you do.  RBS is not
"overhead", not something to minimize, not something to skimp on.  It is an
integral component of the database -- getting it right (sized) is crucial.

Rating: 4
Conflicting info from Oracle February 03, 2003
Reviewer: Craig from Arkansas, USA
Thanks for your amazingly quick replies!

I was referred to Oracle PL/SQL Tips & Techniques, pp. 166-168, by my DBA.  The
author recommends commiting every 1000 rows "to reduce the number of rollback
segment switches, as well as to ensure all transactions within that set can be
processed by one rollback segment."

Are we talking about apples an oranges here?  The text does not support your
conclusion in this case.  Forgive me, but it can be confusing when two sets of
experts give conflicting advice.

Other pertinent info that may impact your response:  our db currently has Undo
tablespaces, with undo management set up.  The Undo Tablespace is 3GB.

Thanks again!

Followup:
That book is wrong wrong wrong.  So wrong wrong wrong

Ask the DBA where in the book is scientifically shows that this is "good" -- you
know "proves it".

Then you can point him to my book "Expert one on one Oracle" pages 143-148 the
section entitled "Bad Transaction Habits" where I prove this is a terrible idea
for more then one reason.

What text doesn't support my scientifically provable fact?

What the heck is a "rollback segment 'switch'" anyway -- that is not even
terminology we use when talking of rollback segments

see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4951966319022
or, if you don't believe me
http://www.ixora.com.au/newsletter/2001_09.htm
(steve adams)
http://www.experts-exchange.com/Databases/Oracle/Q_20328566.html
this one is really cool, shows about a 400% decrease in performance by
committing every 500/1000 rows:
http://home.clara.net/dwotton/dba/big_insert.htm#results

just search google for

"frequent commits" oracle

to read others.

Do not believe any tuning advice or information until you see factual concrete
reproducible numbers to back it up.  Everything else is conjecture, hypothesis
and mostly mythology.  If the book doesn't prove it, don't believe it is my
mantra.

Rating: 5
Excellent explanation... February 04, 2003
Reviewer: Shailesh from India
Hi Tom,

I agree with your opinion, But there are few circumstances where you require
Commit and will face ORA-1555 error. I have faced this situation "Fetch across
commit".

We have added one ID column in all tables of our Application database. This ID
will have unique value, so have created a sequence and written following Stored
Procedure. There are around 1200 tables and records count range goes from 100 to
10 Million. DB size is 5GB. So I have to commit after one table ID update.

CREATE OR REPLACE PROCEDURE UpdateAllId IS
    CURSOR  ALLTABLES IS
    SELECT  Table_Name
    FROM    User_Tables where table_name !='PLAN_TABLE';

    Row_Rec        ALLTABLES%ROWTYPE ;
    STMT        Varchar2(200);
BEGIN
    OPEN  ALLTABLES ;

    LOOP
        FETCH ALLTABLES INTO  Row_Rec ;
        EXIT WHEN ALLTABLES%NOTFOUND ;
         STMT :='UPDATE  ' || Row_Rec.Table_Name || ' SET ID = SEQID.NEXTVAL ';
         Execute Immediate STMT;
    Commit;
     END LOOP ;

    CLOSE ALLTABLES;

END UpdateAllId  ;
/

WHAT YOU WILL DO IN THIS SITUATION.

Thanks & Regards,

Shailesh

Followup:
Hope and pray I don't get an ora-1555.  Cause If I do, we'll, I'm hosed aren't
I...

I don't know what tables I processed.
I don't know what tables I have left to go.

I'm in a HOPELESS situation -- I'd have to start over and reupdate everything.

Your solutions (in my order of preference):

#1 size your RBS to do the work your system needs to do
#2 if you don't want to do #1, rethink your decision
#3 make sure your process is RESTARTABLE.  That would be:

create tablesToProcess as select table_name from user_tables where 1=0;

create or replace procedure updateAllId
is
begin
   for x in ( select * from tablesToProcess )
   loop
       dbms_application_info.set_client_info( x.table_name );
       execute immediate 'update ' || x.table_name || ' set id = seqid.nextval';
       delete from tablesToProcess where table_name = x.table_name;
       commit;
   end loop;
end;
/

And then to run this, you:

insert into tablesToProcess
select table_name
  from user_tables
 where table_name <> 'PLAN_TABLE';
commit;

exec updateAllId;

Now, if updateAllId fails for any reason -- down system, whatever - just run
updateAllId again and it picks up where it left off.

Took more brain power.
Gotta test it (it'll probably work).
Harder..
But solves the restart problem (which most people ignore)....

Rating: 4
How to handle bad data? February 04, 2003
Reviewer: A reader
Option A)  So I remove the loop, and thereby the commit interval, and do it as
one big bulk insert (as many as 100k rows on some nights)

You should find option (a) to be the most expedient, easiest to code, trivial to
maintain.

Tom,

In the above example/solution, how do you recommend handling situations with bad
data (example: trying to insert a record with a bad date)?

Followup:
If they are fetching from one table to another -- should not be an issue.

If you need that "row by row" stuff - bulk operations WITHOUT a commit (and that
is the issue here -- the commit) would be useful.  Array (bulk) fetch and Array
insert 100 or so rows at a time (without committing) and doing whatever error
handling you need.

Rating: 4
questions June 24, 2003
Reviewer: A reader
Tom,

In one of the articles referenced on expert-exchange.com, one of the posters
suggested using an order by 1 clause and preventing the read consistency issue
since the data would all be stored in a temp segment. Your thoughts?

Secondly, most of your examples revolve around update statements and ORA-01555
being caused by them. What about insert statements and delete statements inside
a for loop with commits, how could they run into an ORA-01555 error, assuming a
single user database? The for loop uses a cursor which selects from tables which
are not referenced at all in the insert statements. Could it be that some
recursive sql is being executed in the background which requires some undo
generated by the inserts/deletes?

Followup:
well, you really want to copy the data?  aren't you just working around the "we
don't want lots of undo" thing with "we love gobs and gobs of temp"

No, I do not "approve", you NEED to size your undo for your system, period,
plain and simple, done.

besides, what happens when we decide to use an index and skip the sort?  now
what?

AND you only avoid the 1555 maybe for your query -- not for all of the other
poor users of your system, your frequent commits are going to wrap the rbs --
causing them to get 1555s they would not normally get.

bad fix.

it is the COMMIT, not the update that causes the 1555 here.  change the update
to insert/update/merge and same thing happens.  It is the commit.

June 25, 2003 -- Thanks for the question regarding "Data types - performance considerations - PCTUSed Doubts - Reg", version 8.1.7.0.0
You Asked
Hi Tom

1.  Assuming that the maximum column length is 100 in a
    database then defining the column data types as char is beneficial or
varchar2
    from the performance point of view.

    Pl explain why?

    Is reading the data from the block is convenient in case of
    char   when compared   to varchar2?

2. Taking in performance considerations. Whether it is better to define a
data_type as number or number(10) assuming that validations are done   not to
exceed the digits more than 10 at the front end. if so, why?

3.  Is there any disadvantage of having the primary key data type as
varchar2(10)
    instead of number(10) assuming that the column contains only numeric values,
    from the performance point of view?

4. why is the default value of pctused is set to 40 and pctfree to 20?

   I thought that the ideal combination should be either 40+60 or 20+80?

   I thought the ideal combination should be in such a way
   that sum of pctused+pctfree should be equal to 100?
   Is this correct?  So that the space is utilised maximum? Is there going to be
any process
   overhead by such a combination? If so How?

   If we have delete intense database and we have pctfree 20 and pctused 40.
   Then the data is scattered across many blocks.  Thereby when u issue
   a query the data is to be collected from more block leading process overhead.
   Is this statement true?

   In such a scenario pctfree 20 and pctused 80 would be more appropriate.
   Is this statement true?

Regards
and we said...
1) a CHAR is simply a a VARCHAR2 that is stored blank padded.  A char is stored
with a leading byte field -- just like a varchar2.  It is in fact a varchar2
that is always the maximum length (or null).

If all of the data in the table would eventually consume 100 bytes (eg: i insert
5 bytes, update it to 10, update it to 20, .... update it to near 100) it would
be more efficient to use a char(100) as it preallocated the space and rows will
not migrate over time due to updates.  If the rows are truly varying length
(normal case) a varchar2 is appropriate.

2) you should ALWAYS put the edit in the database anyway.  even if the client
always does the edit.  This is because client applications lie to the database
-- have bugs or are rewritten.  The edit belongs in the database NO MATTER WHAT.

3) a number(10) would consume less space then a varchar2(10).

  1  create table t as
  2  select cast( rownum+9999999 as number(10) ) x, cast( rownum+9999999 as
varchar2(10) ) y
  3  from all_objects
  4* where rownum < 100
ops$tkyte@ORA8I.WORLD> /

Table created.

ops$tkyte@ORA8I.WORLD> select min(vsize(x)), max(vsize(x)), min(vsize(y)),
max(vsize(y)),
  2  avg(vsize(x)), avg(vsize(y))
  3  from t;

MIN(VSZE(X)) MAX(VSZE(X)) MIN(VSZE(Y)) MAX(VSZE(Y)) AVG(VSIZE(X)) AVG(VSIZE(Y))
------------ ------------ ------------ ------------ ------------- -------------
           2            5            8            8    4.96969697             8

it would take less time to compare (join), less storage in an index and so on to
use a number.

there would be a disadvantage to using a varchar2 over a number if the data is
truly numeric

4)

it is 40 and 10:

  1* select pct_used, pct_free from user_tables where table_name = 'T'
ops$tkyte@ORA8I.WORLD> /

  PCT_USED   PCT_FREE
---------- ----------
        40         10

the sum cannot exceed 100 -- the best value is not a sum of 100.  why is it
40/10?  They are just reasonable, general purpose numbers.  save 10% for updates
-- when we have 10% free space -- stop inserting. when the block is 60% free
space (40% used) start thinking about using it for inserts again.

The are set somewhat independent of eachother.  The default, 10 pct free, simply
says "save 10% of the block for updates.  we guess that data will grow no more
then 10% over time on each block".  If I have an 8k block size -- about 800
bytes will be reserved (not available for an INSERT) for udpates.  once we hit
the pct free mark -- the block is taken OFF of the freelist.

Later we update a row or delete a row on the block.  We discover the block is
61% FREE (less then 40% used).  That is the signal to put the block back onto
the freelist.

In general:

o High PCTFREE, Low PCTUSED – For when you insert lots of data that will be
updated and the updates will increase the size of the rows frequently. This
reserves a lot of space on the block after inserts (high PCTFREE) and makes it
so that the block must almost be empty before getting back onto the free list
(low PCTUSED).

o Low PCTFREE, High PCTUSED – If you tend to only ever INSERT or DELETE from the
table or if you do update, the update tends to shrink the row in size.

So, you say "we have a delete intensive database" -- that would be a low pctfree
and a high pctused, you want the space to be reused as soon as possible.

maybe 10/90 would be better.
Review & Followup
Rating: 5
May 23, 2001
Reviewer: KP from India
As usal yr reply is excellent. But u missed
answer to one of my question  that is
Whether it is better to define a
data_type as number or number(10)?

Rating: 4
String data type June 24, 2003
Reviewer: A reader
Hi Tom,

I found the following data type in a program:

varx string(1000);

Can you tell me what kind of data type this is and when it is appropiate to use
it?

I cannot find any information about this data type in the Oracle documentation.

Thanks for any help with this.

Followup:
it is in standard.sql as a subtype:

create or replace
package STANDARD AUTHID CURRENT_USER is              -- careful on this line;
SED edit occurs!

  type BOOLEAN is (FALSE, TRUE);

  type DATE is DATE_BASE;

  type NUMBER is NUMBER_BASE;
  subtype FLOAT is NUMBER; -- NUMBER(126)
  subtype REAL is FLOAT; -- FLOAT(63)
  subtype "DOUBLE PRECISION" is FLOAT;

.....

  subtype VARCHAR is VARCHAR2;
  subtype STRING is VARCHAR2;

June 25, 2003 Periasamy -- Thanks for the question regarding "Long to Varchar2 conversion.... ", version 8.1.5
You Asked
Hi,

Thanks for your earlier responses....

See, i have one more problem, like i want to retrive the first 4000 characters
of the long datatype, with out using the pl sql code. i just wrote a function
like

//

create or replace
function getlong1( p_rowid in rowid)
return varchar2
  as
          l_data long;
  begin
         select remarks into l_data from cr_claimheader where rowid = p_rowid;
          return substr( l_data, 1, 4000 );
  end;

//

to get the first 4000 chars of remarks i am calling this function like

//

select getlong1(rowid) from cr_claimheader where headersrno = 4005

//

Fine, this is working fine when i have the data with less than 4000 chars. in
the particular remarks (long datatype) column.

suppose if the length of data increase means, it returns just null...

how can i solve this problem in a simple manner...

Its very urgent...

Thanks and bye

regards
Periasamy
and we said...
In 8i and up, this will work:

ops$tkyte@ORA8I.WORLD> create or replace function  getlong( p_tname in varchar2,
  2                                       p_cname in varchar2,
  3                                       p_rowid in rowid ) return varchar2
  4  as
  5      l_cursor    integer default dbms_sql.open_cursor;
  6      l_n         number;
  7      l_long_val  varchar2(4000);
  8      l_long_len  number;
  9      l_buflen    number := 4000;
 10      l_curpos    number := 0;
 11  begin
 12      dbms_sql.parse( l_cursor,
 13                     'select ' || p_cname || ' from ' || p_tname ||
                                                          ' where rowid = :x',
 14                      dbms_sql.native );
 15      dbms_sql.bind_variable( l_cursor, ':x', p_rowid );
 16
 17      dbms_sql.define_column_long(l_cursor, 1);
 18      l_n := dbms_sql.execute(l_cursor);
 19
 20      if (dbms_sql.fetch_rows(l_cursor)>0)
 21      then
 22         dbms_sql.column_value_long(l_cursor, 1, l_buflen, l_curpos ,
 23                                    l_long_val, l_long_len );
 24     end if;
 25     dbms_sql.close_cursor(l_cursor);
 26     return l_long_val;
 27  end getlong;
 28  /

Function created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> drop table t;

Table dropped.

ops$tkyte@ORA8I.WORLD> create table t ( x long );

Table created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> declare
  2    data long default rpad( '*', 32000, '*' );
  3  begin
  4    insert into t values ( data );
  5  end;
  6  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select getlong('T', 'X', rowid) from t;

GETLONG('T','X',ROWID)
---------------------------------------------------------------------------------
--------------------------------------------------
**************************************************.....
Review & Followup
Rating: 5
Thanks a lot June 21, 2001
Reviewer: Periasamy Subramaniyan from Bulgaria
We planned to do some alteration... very good timely help...

Thanks a lot...

Rating: 4
How does this work... September 20, 2002
Reviewer: Subhrajyoti Paul from Mumbai, India
Tom,
 why does this happen...
pts@ptsmig.world> create table t1
  2  (x int, y long);

Table created.

pts@ptsmig.world> create table t2
  2  (x int, y varchar2(4000));

Table created.

pts@ptsmig.world> insert into t1 values(1,'abcde');

1 row created.

pts@ptsmig.world> commit;

Commit complete.

pts@ptsmig.world> insert into t2 select * from t1;
insert into t2 select * from t1
                      *
ERROR at line 1:
ORA-00997: illegal use of LONG datatype

pts@ptsmig.world> begin
  2  for a in (select * from t1) loop
  3     insert into t2 values(a.x, a.y);
  4  end loop;
  5  end;
  6  /

PL/SQL procedure successfully completed.

pts@ptsmig.world> select * from t2;

         X
----------
Y
--------------------------------------------------------------------------------
         1
abcde

When I am doing a Select-Insert, the datatype conversion gives an error but when
i am using values clause, it works fine... Why does this happen...?

Followup:
because the documentation says it will?  LONGS cannot be used in an insert into.

Put more then 32k in the long and it'll fail miserably in plsql as well.

USE CLOBS, do not, repeat, do not, use a long.

Rating: 4
But Tom, what happens in this case, why PL/SQL succeeds in this case... September 20, 2002
Reviewer: Subhrajyoti Paul
Tom,
    I have a table which was created with a long column and has around 1 M
records in it... I want to modify the column type to long. In this process, I am
saving the data into temp table with varchar2 column and modifying the main
table. So, I have to do the conversion.

   I used the loop to do that.

   I just wanted to know why this failed in PURE SQL while it worked in PL/SQL
loop....

Thanks

Rating: 4
Correction in the last review September 20, 2002
Reviewer: Subhrajyoti Paul
Sorry,
  I want to modify the column type to VARCHAR2, not long as I wrongly wrote in
the previous review...

Rating: 5
Excellent work February 05, 2003
Reviewer: Chandra S.Reddy from India
Great stuff from very great Tom.

Rating: 5
Is this againest to the coding Standards. February 11, 2003
Reviewer: Sindhu from Alaska
Tom,
Thanks for the code you provided.
In one situation I have to send a value from LONG datatyped column to
application through Stored Proc as a OUT parameter.

SQL> desc t_comment
 Name                                 Null?    Type
 ----------------------------------------------------- -----
T_NUMBER                             NOT NULL CHAR(13)
USER_COMMENT                                  LONG

In a stored Proc I implemented like.

procedure sp_xx(in_cTNo IN NUMBER, l_var OUT VARCHAR2) IS

select sf_pc_cmt_FNC(in_cTNo) into l_var from t_comment;

end;

the function "sf_pc_cmt_FNC" is exactly look like the code snippet you provided.
But when I implemented this snippent, My client refused to accept this.Below are
the client's comments.
<<< his comments >>>
" I think that there is a misunderstand information for " item #10:Use Oracle
Packages and functions for generalized common routines frequently executed"
in the Guideline for Oracle Database Application Development for PL/SQL Stored
Procedures document.   The dbms_sql package can be directory access
by the store procedure if the code related to the reference tables and it is not
heavy accessing by the applications.
We have to discuss the correct the usage of the dbms_sql package. "
<<<<>>>>

The item#10 is below.
.     Use Oracle packages and functions for generalized common routines
frequently executed
since code is efficient.  Also, use user-written packages and functions for
application shared code.
Before you write a user-written package or function, first verify it doesn’t
already exist
with Oracle.   One good reason to do this is because when Oracle upgrades to a
new release they upgrade their own packages and functions for you.   If you
write them yourself, you may have more maintenance when converting to a new
release.
< item #10 over...>

Is Oracle providing any package(DBMS_?) for this conversion.
Please let me know the way to resolve this and why client is refused to accept
this SP.

Thanks in advance.

Followup:
I don't understand this at all -- sorry?

What exactly is their issue?

My routine returns just the first 4000 characters of a long --regardless of how
long the long is.

IF your longs are all 32k or less your code should be:

is
   l_tmp long;
begin
   select long_col into l_tmp from t_comment where ...;
   return substr( l_tmp, 1, 4000 );
end;

if they EXCEED 32k for just one row -- you have to use dbms_sql in plsql

Rating: 3
Converting Long to Varchar2 with minimal redo/undo. June 24, 2003
Reviewer: A reader from Japan
Hi Tom, What method would you suggest for converting a long column to varchar2.
Our table has about 20 million rows in it. We want to generate least number of
archive logs for this. Sqlplus copy command and PL/SQL insert cannot be used as
we cannot use APPEND hint with them(We tried creating a new table and then
inserting into it,we had planed to rename the original table.We plan to create
index's with nologging after the table is created.)

The orginal table and the new table are as given below.
CREATE TABLE AD_MESSAGES
 (MSG_NUM  NUMBER(5) DEFAULT 0 NOT NULL
 ,MSG_CONTENT  LONG NOT NULL
 ,MSG_TS  DATE NOT NULL
 ,AD_DOC_DOC_NUM  NUMBER(5) NOT NULL
 ,AD_DOC_ADC_CONTAINER_NUM  NUMBER(12) NOT NULL
 ,AML_MSG_LEVEL  VARCHAR2(10) NOT NULL
 )
 TABLESPACE AD_MESSAGES
/

CREATE TABLE TEMP_AD_MESSAGES
 (MSG_NUM  NUMBER(5) DEFAULT 0 NOT NULL
 ,MSG_CONTENT  VARCHAR2(4000) NOT NULL
 ,MSG_TS  DATE NOT NULL
 ,AD_DOC_DOC_NUM  NUMBER(5) NOT NULL
 ,AD_DOC_ADC_CONTAINER_NUM  NUMBER(12) NOT NULL
 ,AML_MSG_LEVEL  VARCHAR2(10) NOT NULL
 )
 TABLESPACE AD_MESSAGE
/

ALTER TABLE TEMP_AD_MESSAGES NOLOGGING
/

DECLARE
   CURSOR cur_msg IS
   SELECT msg_num
      ,msg_content
          ,msg_ts
          ,ad_doc_doc_num
          ,ad_doc_adc_container_num
          ,aml_msg_level
     FROM AD_MESSAGES;
   --
   ln_msg_num        TEMP_AD_MESSAGES.MSG_NUM%TYPE;
   lc_msg_content    TEMP_AD_MESSAGES.MSG_CONTENT%TYPE;
   lc_msg_ts         TEMP_AD_MESSAGES.MSG_TS%TYPE;
   ln_doc_num        TEMP_AD_MESSAGES.AD_DOC_DOC_NUM%TYPE;
   ln_container_num  TEMP_AD_MESSAGES.AD_DOC_ADC_CONTAINER_NUM%TYPE;
   lc_msg_lvl        TEMP_AD_MESSAGES.AML_MSG_LEVEL%TYPE;
   --
   ln_count          NUMBER(20) := 0;
   ln_step           NUMBER(10) := 0;
BEGIN
   ln_step := 1;
   OPEN cur_msg;
   LOOP
   ln_step := 2;
   FETCH cur_msg INTO
ln_msg_num,lc_msg_content,lc_msg_ts,ln_doc_num,ln_container_num,lc_msg_lvl;
   EXIT WHEN cur_msg%NOTFOUND;
   ln_step := 3;
   INSERT /*+ APPEND */ INTO TEMP_AD_MESSAGES
                           (MSG_NUM
               ,MSG_CONTENT
               ,MSG_TS
               ,AD_DOC_DOC_NUM
               ,AD_DOC_ADC_CONTAINER_NUM
               ,AML_MSG_LEVEL)
                   VALUES
                           (ln_msg_num
                            ,lc_msg_content
                            ,lc_msg_ts
                            ,ln_doc_num
                            ,ln_container_num
                            ,lc_msg_lvl);

   ln_step := 4;
   IF ln_count > 100000 THEN
      COMMIT;
      ln_count := 0;
   END IF;
   ln_step := 5;

   ln_count := ln_count + 1;

   END LOOP;
   ln_step := 6;
   COMMIT;
EXCEPTION
   WHEN OTHERS
   THEN
      dbms_output.put_line('Error in script. '||SQLERRM ||'ln_step '||ln_step);
END;

This is generating heavy archive logs, as APPEND is ignored with 'values' clause
of insert.

What would be your suggestion for this ?

Thanks,

Followup:
there will be no way to do this without unloading the data and then using sqlldr
direct path to reload it or writing an OCI program in C using the direct path
apis.

create table as select --> won't work, longs.
insert /*+ append */ as select --> won't work, longs.
copy -> won't work, conventional path.
code you write -> won't work, conventional path.

so, maybe you can unload to a flat file (see asktom.oracle.com/~tkyte for
unloaders) and reload with sqlldr

Rating: 4
use a function to return a varchar2 June 24, 2003
Reviewer: A reader from Japan
Hi Tom,

Do you see any problem, if we create a function which takes a rowid parameter
and then selects the long column into a varchar2 variable from the rowid and
returns this varchar2 variable. After this we just use this function in the
insert statement.
For eg. insert /*+ APPEND */ into TEMP_AD_MESSAGES select MSG_NUM
,my_to_varchar_func(rowid),MSG_TS
,AD_DOC_DOC_NUM,AD_DOC_ADC_CONTAINER_NUM,AML_MSG_LEVEL from ad_messages;

We know that our long field has less than 2000 bytes of length. In test case
atleast its appearing to use direct mode insert.

Thanks,

Followup:
It would seem to me that in order to use append, you must be worried about gobs
of UNDO.

If you are worried about gobs of undo -- you must have TONS of data.

If you have tons of data, calling a function once per row is going to be slow,
I'd rather deal with the UNDO.

Rating: 4
How to deal with long for this query June 24, 2003
Reviewer: Yong from CA, USA
Tom,

Search_condition is long type in Oracle data dictionary. How can I construct a
query to get the following results without set a condition on a long type

SQL> select constraint_name from user_constraints
  2  where search_condition not like '%NOT NULL';
where search_condition not like '%NOT NULL'
      *
ERROR at line 2:
ORA-00932: inconsistent datatypes: expected NUMBER got LONG

Followup:
ops$tkyte@ORA920LAP> create or replace function get_search_condition(
p_cons_name in varchar2 ) return varchar2
  2  authid current_user
  3  is
  4      l_search_condition user_constraints.search_condition%type;
  5  begin
  6      select search_condition into l_search_condition
  7        from user_constraints
  8       where constraint_name = p_cons_name;
  9
 10      return l_search_condition;
 11  end;
 12  /

Function created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select constraint_name
  2    from user_constraints
  3   where get_search_condition(constraint_name) like '%NOT NULL%';

CONSTRAINT_NAME
------------------------------
SYS_C004792
SYS_C004794
SYS_C004181
....

Rating: 5
June 25, 2003
Reviewer: A reader
Tom,

Great! thanks,

June 25, 2003 Balaji -- Thanks for the question regarding "Ask Tom in CD's ", version 8.1.7
You Asked
Hi Tom,
  Good Morning, I am happy that i got a chance to ask a question.Currently i am
working for a Reputed Bank in Singapore on contract basis from a Indian Company,
unfortunately for some personal reason's i am getting relocated to India. In my
company at India we are not allowed to browse the Internet from Work Place. This
has become the practise in most of the Indian companies nowadays.So after going
there, i will be missing your AskTom Site. From this site i hav learnt a lot of
things and have got solutions to many of my problems by just searching for the
problems. It would be helpfull for many of the developers if we get the ASKTOM
in the form of the CD's. It would be very usefull if we get a CD where we can
browse the Archieves and get the solution for our problems. This CD/Program can
be made available in your WebSite which we can download and use it. I hope you
understand our problem and come out with some good Suggestion of how we can use
your site , eventhough we don't have the Internet Connection.

Thanking You
M.Balaji
and we said...
sorry, there is not a chance I'll get getting into that.

Question:  if you cannot get to the web, how exactly would you be downloading
from my website.

This:

"This CD/Program can
be made available in your WebSite which we can download and use it. I hope you
understand our problem and come out with some good Suggestion of how we can use
your site , eventhough we don't have the Internet Connection."

just seems to conflict with itself in so many ways.

But given the amount of technical resources on the web, I cannot fathom a
technology company in any country barring access.  They would quickly be at a
SERIOUSLY LARGE competitive disadvantage and will not be long for this world.

And based on the amount of traffic I recieve from India -- it might not be as
widespread as you have been led to believe.
Review & Followup
Rating: 5
June 12, 2003
Reviewer: Kalita from Bromley,UK(Will be back to India soon)
I think that was the situation two three years back. Now I believe most of the
companies provide Internet connections to employees.
But Tom, you should probably come to India for doing presentations some time. We
would love to hear you live. Any plans?

Regards

Followup:
that's a little ways out of my way... denmark is about as close as I'll be
getting in the next year or so ;)

Rating: 4
I'll bet... June 12, 2003
Reviewer: Jer Smith from Salt Lake City UT
That even if the company has the most draconian web policies on earth, they let
you visit the websites of the major vendors you work with.  Remember this is in
the oracle.com domain.  If you're using Oracle, and you can't browse Oracle...

Wait a minute...I might block asktom.oracle.com if I was the Indian division
of...Sybase!

Rating: 5
June 12, 2003
Reviewer: A reader

Rating: 3
Tom's question June 12, 2003
Reviewer: A reader
You can always download from home or an internet cafe and burn it to a CD and
use the CD at work.  The problem is not having access to the internet but not
having access to the internet while at work.

Rating: 5
June 12, 2003
Reviewer: Prasad from India
Hi,
I too don't think internet is an issue with most of the companies. All my
friends working in different indian companies and myself have the internet
access to uselful sites like these. The access is a limited access i.e we cannot
browse yahoo or hotmail, but nobody refuses access to sites like these.
Moreover, netaccess@home is also getting cheaper and better day by day. So, for
me, net is the easiest medium. CDs get outdated soon, whereas net content
refreshes everyday.
Now, how in the world will you get followups to followups on CD?

PS: Apropos Kalita's comment, I would also like to know, whether you have a plan
for India visit.

Rating: 4
Tom of Bollywood....... June 12, 2003
Reviewer: Robert from PA
>>..that's a little ways out of my way... denmark is about >>as close as I'll be
getting in the next year or so ;)

Oh..come now Tom.....be a little more adventurous.
Movie-making is so BIG there, They will probably
ask you to star in one. and you will become a Bollywood celebrity :)

Rating: 4
Why not Tom in Indian BOND Movie........... June 12, 2003
Reviewer: A reader

Rating: 3
RE: Why not Tom in Indian BOND Movie... June 12, 2003
Reviewer: robert from PA
>>... Why not Tom in Indian BOND Movie...

Because that's Larry's role...but so far no one from M$
want to play Dr Evil or Gold Finger...

Rating: 5
Name of the Companies which i know doesn't allow internet Access June 12, 2003
Reviewer: Balaji.M from Singapore
Hi Tom,
  Thanks for your reply. As you say i accept Indian Companies allow to browse
the internet. I also can name the companies which doesn't allow to browse are
HCL Groups, Digital Globesoft and etc doesn't allow to browse from the
workstation, they have internet kiosks to browse (obviously we cant sit there
for a long time and browse and try to find the solution to the problem).
  One more thing is as one of them said, i can browse from house, yes i accept.
Having that in my mind only i had told that if we have the Downloadable version
of AskTom i can download it in my house and burn it in the CD and Can use in my
workplace.

Thanks for all the help to Oracle Community.

Cheers
M.Balaji

Followup:
the problem is - the database is getting rather large, I have limited bandwidth,
time and energy.

So, I let you "download it".  We'll, it is not useful without a database behind
it (to find things, query things, etc).  So, now I have to develop an offline
version of asktom.  And support everyone with infrastructure support (no, don't
configure it that way, configure it this way.  no, it won't work on version 6 of
Oracle.  no, it won't work with IIS.  no, it won't run on an atari. and so
on)...

And then I'll have 500meg downloads happening frequently (there goes my
bandwidth...)

And then I'll lose the ability to say "i've had so many uses" (justify existence
thing)....

I'm just not going there -- it would be a career path in itself.

Rating: 4
Over hyped June 13, 2003
Reviewer: Anirudh Sharma from New Delhi, India
I am working with one of the companies mentioned by Balaji and I have internet
access almost 24 hrs a day(YES!!!! in office too..& thats such a mundane thing).
I am visiting asktom daily.....NO, I donot find any such issues mentioned by
Balaji. Better to land in India first and then adjudicate.

Followup:
thanks very much!

Rating: 5
You can still download from Archive!!! June 13, 2003
Reviewer: A reader from USA
Have you condsidered downloading from the "Archive" section?  You can download
by week.  If you search within the "The New Ask Tom" subject for archive you
will see Tom's suggestions.

A company that uses Oracle DB and at the same time blocks AskTom.com to its
Developer/DBA's is a BIG LOOSER!!!!!!!!!!1

Rating: 5
one small suggestion June 13, 2003
Reviewer: S.A from newyork
Tom,

Let me put a suggestion which I thought about many times previously into this
thread.

The search facility of your site is wonderful. For any kind of technical
problems, I have always been able to get excellent solutions from your site.

It would be even better if you can provide a question number or another unique
identifier to each question/thread. That would help us to index that question
and find it again easily at a later time. Many times when I am searching, I try
to find some particular thread which I know has excellent examples or detailed
discussions. But it sometimes take little time to search and find that thread
due to the wealth of information available at your site. (For even multi-word
searches, it is not uncommon to have 40-50 results from your site). If we have a
question id with the question, then I can index it and mark to myself that
'refer question so-and-so to get the last word on this topic' etc... That will
help me to bring up that thread quickly again when I want to refer it.

Just a suggestion.. I think you can implement it easily - you probably already
have a question id and you just need to display it.. Hope I was able to convince
you...

thanks a million for your priceless service,
regards,
SA.

Followup:
Sorry, I have to chime in here (Tyler, friend of Tom and one of the developers
who  works on this site), have you noticed the "bookmark this review" or
"bookmark this page links?  Each article does have a unique ID which you will
see in the URL.

Hope ths helps,
Tyler

Rating: 5
Tom, take a small vacation and come to India June 13, 2003
Reviewer: praveen from INDIA
Tom,first of all let me thank you for the priceless and invaluable service you
are doing to the Oracle community through this site. And the book which you've
written is by far THE BEST OF THE BEST books which I've read on Oracle.It helped
me give a very strong foundation in Oracle..Many chapters in that book, I've
read it again and again. It is like a text book to me and to many of my friends
and colleagues.Keep up the good work and of course, we are waiting eagerly for
your second book.
I too join Kalita, inviting you to India..If not for presentaion at least you
have a nice vacation..We will be extremely happy to receive you. Just tell
through this site that "I'm coming to India for a vacation". Rest you leave to
us!
Best Regards

Rating: 5
The Best Ones June 14, 2003
Reviewer: Bharath from India
Hi Tom,

There are so many great SQL statements in your site. How about ranking them
based on the challenges it posed.. Agreed 99.9999999999% of queries would have
been a cakewalk to you?

Followup:
that would be like taking any question I though was "not worthy" (all questions
that start and end with "my system is slow, what could be wrong" fall into that
category for example) and posting it on a wall of shame.

It's not about shaming people (which a low rank would do)...

Rating: 5
Some do not have internet access June 24, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom,
Though you might find it hard to believe, right here in PA, USA, for last two
years+, I am working (thankfully as a contractor), for a company which does not
allow internet access to contractors. As a DBA, I do not have easy access to
Metalink, your forum and other technical resources. I check most forums from
home, finding out articles and code of interest and e-mailing them to my work
address.

Is it possible that your site can provide some sort of facility to download and
sync selected threads to PDA?

Thanks

Followup:
Sorry -- but no.

it would not fit in a pda first of all.

second of all, the company you are working for will not be around very long (as
gartner likes to say "90% level of assurance on that").  they have blindfolded
their people and set them in a hole.

Rating: 3
Vacation in India ? June 25, 2003
Reviewer: fan from PA
>>Just tell
thru this site that "I'm coming to India for a vacation".

I'd seriously check on the humidity and temperature
this time of the year....

Rating: 5
off-line asktom.oracle.com June 25, 2003
Reviewer: Martin Guillen from Buenos Aires, Argentina.
Hey, there is an off-line version of asktom.oracle.com.
It's called "Expert one-on-one Oracle" :)
I recommend it.

Rating: 5
Hehehe.. July 01, 2003
Reviewer: Victor from The Netherlands
[quote]
Hey, there is an off-line version of asktom.oracle.com.
It's called "Expert one-on-one Oracle" :)
I recommend it.
[/quote]

Good one.
;)

Rating: 5
August 23, 2003
Reviewer: A reader
For Robert from PA,

Considering the number of ugly actors & models who don't know acting in
"Hollywood", I think Tom can make it in Hollywood, after all from his picture he
doesn't look bad :-))

June 25, 2003 mallika -- Thanks for the question regarding "How to store images into the database through forms", version version 6.0
You Asked
Dear tom
I am having one image table at the back end.I want to insert
images into it through front end and at the same time i want to
display the sizes of the images simultaneously.
Do we need any Built-ins like READ_IMAGE_FILE&WRITE_IMAGE_FILE?
Can u explain in detail the procedure for doing that?
Thanks
Mallika
and we said...
This is pretty easy.  The answer is READ_IMAGE_FILE.  The will
read a file stored on the machine with the form is executing.
The image will become 'visible' on screen.  When the user saves
into the database -- the image will go into the database.

WRITE_IMAGE_FILE is what you would use to export an image that
exists in the database to the users machine.  They would query a
record up -- get to the image they wanted and you would give
them a button or something to "export" the image.  The
when-button-pressed trigger would invoke write_image_file to
save the contents of the image to disk.

As a demonstration -- I did the following:

scott@8i> create table image_table ( filename varchar2(255)
primary key, image long raw );

Table created.

I created a table that had a filename as the primary key and a
image column to hold a picture.  I then created a default block
on this table -- using all of the defaults (just pressed NEXT in
the data block wizard and layout wizard, always selecting both
columns).  So, I ended up with a form that had a IMAGE_TABLE
block with columns FILENAME and IMAGE.

On this block I put a button.  I coded a "when-button-pressed"
trigger as follows:

read_image_file( :image_table.filename,
                 'JPG', 'image_table.image' );

I then ran this form.  I typed into the filename field
"c:\temp\tkyte.jpg" (thats a file i had on my machine) and
pressed the button.  The contents of the file were displayed on
screen.  I then did "action/save" to put the record into the
database and commit.  I then did a "query/enter" to clear the
block out and then "query/execute" to query up the record I just
put in there.

thats it...
Review & Followup
Rating: 3
how to insert a image into database July 18, 2001
Reviewer: kareem
>sir,
>how to insert the image into oracle8.0 database
>and how to retrive?
>so please clarify my doubt as early as possible
>with command.
>please give me procedure how to do.
>>Thanking you sir,
>your truely,
>kareem
>kareem2020@yahoo.com
>

Rating: 5
Short and Sweet February 14, 2002
Reviewer: Steve C. from Blackburn, UK
Fantastic, I've been looking for something to do this exact same thing.

Rating: 1
READ_IMAGE_FILE problems ... June 04, 2003
Reviewer: Vladas from Lithuania
I found one, but very bad thing, if in Oracle form I use READ_IMAGE_FILE for
image insert in the table (in example foto.jpg and I don't use any compressions
for this image item in my form, the Image format is JFIF) and later try to see
this image, the image quality is badly than original my image. If I use BMP type
of Image format the image quality is such as original but the image size too
big. If I understood correctly the Oracle Forms READ_IMAGE_FILE function use
some compression :(.

May be somebody know how insert image in JPEG format from Oracle forms and local
PC to BLOB field in the server with image quality such as original.

Rating: 5
Image retrieval using forms ver. 6.0 June 24, 2003
Reviewer: Archie Magnaye from Ortigas Center, Philippines
The article came in very handy. But suppose I'd like to print that particular
image, using reports ver 6.0, how do i retrieve the image from the image_table
and print it on screen or on paper? Thank you and hope to hear for your prompt
response..

Followup:
sorry, haven't used forms since 1995 myself.

try otn.oracle.com -> discussion forums, lots of forms developers out there who
have probably done this (or at least tried)

Rating: 4
How to load and display images from database June 25, 2003
Reviewer: Yong from CA, USA
Here is my way to do so

CREATE TABLE IMAGES
(
  IMAGE_NAME  VARCHAR2(30)  primary key,
  MIME_TYPE   VARCHAR2(30),
  CONTENT     BLOB
)

CREATE OR REPLACE PACKAGE BODY IMAGE AS

PROCEDURE load(filename VARCHAR2) AS
  f_lob BFILE;
  b_lob BLOB;
  image_name VARCHAR2(30);
  mime_type VARCHAR2(30);
  dot_pos NUMBER;
BEGIN
    dot_pos := INSTR(filename,'.');
    image_name := filename; --SUBSTR(filename,1,dot_pos-1);
    mime_type := 'image/'||SUBSTR( filename,dot_pos+1,length(filename) );

    INSERT INTO images values(image_name,mime_type,empty_blob() )
           RETURN content INTO b_lob;
    f_lob := BFILENAME('SOURCE_DIR',filename);
    dbms_lob.fileopen(f_lob,dbms_lob.file_readonly);
    dbms_lob.loadfromfile(b_lob,f_lob,dbms_lob.getlength(f_lob) );
    dbms_lob.fileclose(f_lob);
    COMMIT;
END;
/*=====*/
PROCEDURE get(name varchar2) AS
  vblob     BLOB;
  mime_type VARCHAR2(30);
BEGIN
  select content,mime_type
  into vblob,mime_type
  from images
  where image_name=name;
  owa_util.mime_header(mime_type, false);
  owa_util.http_header_close;
  wpg_docload.download_file(vblob);
    exception when others then
          htp.p(sqlcode || sqlerrm);
END;
/*=====*/
END;
/

SQL> create directory source_dir as '/mydir';
if you have an image file in /mydir/toolbar.gif

SQL> exec image.load('toolbar.gif');

create procedure test as
begin
  htp.p('<img src=image.get?name=toobar.gif>');
end;
/

June 25, 2003 BIJAY -- Thanks for the question regarding "How does this delete works?", version 8.1.7
You Asked
Hi Tom,

This might be a stupid question, but I am curious about how this delete works.

SQL> delete (select * from dept a, emp b where ename like 'A%' and
a.deptno=b.deptno);

Can you explain why this deletes from emp table and not from dept table even
when :
1. we have not specifically mentioned to delete from emp table
2. in the select statement, there is reference of both emp & dept table.

SQL> select count(*) from emp;

  COUNT(*)
----------
        14

SQL>  select * from dept a, emp b where ename like 'A%' and a.deptno=b.deptno;

    DEPTNO DNAME          LOC                EMPNO ENAME      JOB
---------- -------------- ------------- ---------- ---------- ---------
       MGR HIREDATE         SAL       COMM     DEPTNO
---------- --------- ---------- ---------- ----------
        30 SALES          CHICAGO             7499 ALLEN      SALESMAN
      7698 20-FEB-81       1610        300         30

        20 RESEARCH       DALLAS              7876 ADAMS      CLERK
      7788 23-MAY-87       1110                    20

SQL>  delete (select * from dept a, emp b where ename like 'A%' and
a.deptno=b.deptno);

2 rows deleted.

SQL>  select count(*) from emp;

  COUNT(*)
----------
        12

Thanks for helping us all.
Bijay Tuladhar
and we said...
Because only one table can be deleted from in a join view like that by defintion
AND if only one table is "key preserved" (search this site for the meaning of
that) the database can do the delete without any help from us -- it knows which
table it can delete from.

The facts here are:

a) deptno must be the primary key of dept
b) the database therefore knows that when we JOIN EMP to DEPT -- the emp row
will join to AT MOST one dept row (eg: we will not multiply the number of EMP
rows)

therefore, EMP is what is known as "key preserved" and can be modified view the
join view.

It is all about the metadata and known relationships.  Watch the progression
below as I add constraints:

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

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

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> delete (select * from dept a, emp b where
ename like 'A%' and
  2  a.deptno=b.deptno);
delete (select * from dept a, emp b where ename like 'A%' and
       *
ERROR at line 1:
ORA-01752: cannot delete from view without exactly one key-preserved table

Here the database is saying "heck, I don't know if I'm allowed to delete from
ANY of these tables in this view...

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table dept add constraint dept_pk
primary key(deptno);

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> delete (select * from dept a, emp b where
ename like 'A%' and
  2  a.deptno=b.deptno);

2 rows deleted.

Now it knows, it knows that EMP is key preserved -- but DEPT is not (eg: every
row in DEPT can -- might -- appear more then once in the result set, hence its
KEYS are not preserved, not unique in the view.  You cannot delete from DEPT
only EMP, hence we do

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> update emp set deptno = rownum;

12 rows updated.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table emp add constraint deptno_unique
unique(deptno);

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> delete (select * from dept a, emp b where
ename like 'A%' and
  2  a.deptno=b.deptno);
delete (select * from dept a, emp b where ename like 'A%' and
       *
ERROR at line 1:
ORA-01752: cannot delete from view without exactly one key-preserved table

Now we are back in the same boat as before but for a different reason.  Here
BOTH tables are key preserved and the database says "heck, I don't know which
one you mean now!
Review & Followup
Rating: 4
Mystery solved August 16, 2002
Reviewer: Bijay Tuladhar from Fremont, CA USA
Thanks Tom.

Rating: 5
August 17, 2002
Reviewer: A reader
It's very helpful for me. Very Great!

Rating: 5
Great August 17, 2002
Reviewer: Riaz Shahid from PRAL, Islamabad, Pakistan
Thats a Great Answer ...

Tom for me, you are "second Oracle"... since you are really a mystery.

Rating: 4
Great Answer August 17, 2002
Reviewer: Vikas Sharma from Delhi India
Great Answer..

Rating: 4
How can we allow delete June 05, 2003
Reviewer: Scott Wesley from Perth, WA
G'day

I'm pretty sure I understand the concept of key-preservation now, thanks.
But how can we "get around" these rules?
If you have 2 tables joined in a view, and want to be able to delete rows from
one table, is there some clause we can specify to allow this?
CREATE OR REPLACE VIEW ths_sales_rep_cust_vw AS
SELECT l.state_code
      ,l.acc_nbr
      ,c.cust_name
      ,c.tel_nbr
      ,l.sales_rep_nbr
FROM   trd_sales_rep_customers l
      ,customers c
WHERE    l.acc_nbr    = c.acc_nbr
/
Both tables have primary key on acc_nbr, but I want to be able to perform all
DML on just trd_sales_rep_customers.
All columns have full DML capability (all_updatable_columns), but both are key
preserved...

Thanks.

Followup:
you can "train" the view via INSTEAD OF triggers on the view.

create a trigger INSTEAD OF DELETE

Rating: 5
June 05, 2003
Reviewer: A reader
Excellent!!

Rating: 5
June 05, 2003
Reviewer: A reader
Your site is the best of all sites.

Rating: 5
Worked a treat. Thanks. June 06, 2003
Reviewer: Scott from Perth. WA

Rating: 4
Great - but where is it useful June 06, 2003
Reviewer: NeNaw from UK
I am able to understand the concept now - thanks, but
where would this be useful, and why use this form of delete - is it better for
performance ? Wouldn't this be risky to put into an application, because later
in the life span of the product, someone might want to add a unique key on emp ?
 I think I am missing something here ?

Cheers

Followup:
using that criteria "that things will change" would make the placement of any
code into a source code file dangerous.

It is useful when using views -- it permits the view that joins EMP and DEPT
together to be used to

o insert into EMP
o update EMP
o delete EMP

if you modify the database schema such that DEPTNO in EMP is unique (highly
unlikely -- you would have a 1-1 relationship at that point which basically
means you want one table, not two -- meaning the view will be dropped and the
tables just merged together...) then yes, delete would cease to function.

Rating: 5
key preserved June 06, 2003
Reviewer: Nikhil Mishra from INDIA
hi tom,

  amazing reply. terrific explanation. u r a genius. hats off to u.

nikhil

Rating: 5
June 06, 2003
Reviewer: A reader
Hi Tom,

 In short, Chield table can be key preserved but Parent can
 not .

 Am I right?

Followup:
parent can be if parent and child are 1:1 actually.

Also, bear in mind that "things change", here is the analogous example in 9iR2
showing that the delete now functions in the 1:1 case as well:

ops$tkyte@ORA920> create table p ( x int primary key );

Table created.

ops$tkyte@ORA920> create table c ( y int );

Table created.

ops$tkyte@ORA920> insert into p values ( 1 );

1 row created.

ops$tkyte@ORA920> insert into c values ( 1 );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace view v
  2  as
  3  select * from p, c where p.x = c.y;

View created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select column_name, updatable, insertable, deletable
  2    from user_updatable_columns
  3   where table_name = 'V'
  4  /

COLUMN_NAME                    UPD INS DEL
------------------------------ --- --- ---
X                              NO  NO  NO
Y                              YES YES YES

ops$tkyte@ORA920>
ops$tkyte@ORA920> delete from v;

1 row deleted.

ops$tkyte@ORA920> rollback;

Rollback complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table c add constraint c_pk primary key(y);

Table altered.

ops$tkyte@ORA920> alter view v compile;

View altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select column_name, updatable, insertable, deletable
  2    from user_updatable_columns
  3   where table_name = 'V'
  4  /

COLUMN_NAME                    UPD INS DEL
------------------------------ --- --- ---
X                              YES YES YES
Y                              YES YES YES

ops$tkyte@ORA920>
ops$tkyte@ORA920> delete from v;

1 row deleted.

Rating: 5
9i hasn't a DELETE ALL? June 24, 2003
Reviewer: Marcio from Brazil
Tom, I'd like to delete all rows from two tables from subquery like this:

select * from emp;
select * from dept;
delete from (select * from emp, dept where emp.deptno = dept.deptno);
But, if I put chk unique on dept it takes emp if not it takes dept. I would want
both. Is that possible?
thanks,
Marcio

Followup:
you can delete from ONE table at a time only.

that takes two deletes.

Rating: 5
Great thread! June 25, 2003
Reviewer: Alberto Dell'Era from Milan, Italy

June 25, 2003 Ravi -- Thanks for the question regarding "Logic Behind alter table ... move tablespace", version 816
You Asked
Tom,

Could you please enlighten me in how alter table t_name move tablespace have
been implemented in oracle. This can be done online and with nologging. But how
the data is moved from from one tablespace to another. Should oracle has to
prepare the insert statments and go through buffer cache like conventional
insert or will it act like direct insert.

I have a situation like this. I would like to know which method would be fast.

Table size 2.5G
I would like to move from tablespace a to b. All the files for a and b are
striped and in different disks
This table is an independent table. I mean no dependencies(triggers, constraints
defined on them).

First approach:
--------------

alter table t1 t_name move tablespace b nologging;

Second approach :
-----------------

create table temp_table tablespace b as select * from t1;

alter table temp_table nologging;

insert /* APPEND */ into temp_table select * from t1;

drop table t1;

rename t1 temp_table ;

Will the second approach be faster than the first approach because of direct
insert.

Which will be faster and why.
Can parallelism be implemneted in the first approach.
Will there be any appreciable difference in rollback segment and sort usage.

Your input is highly appreciated.

Thanks
Ravi
and we said...
Well the move can ONLY be online for an index organized table (IOT) not a
conventional heap table.

You would

alter table T nologging;  (to make the table be nologging)
alter table T move tablespace b;
alter table T logging;    (to put it back)

The alter table t move tablespace b nologging;  would move the table (with
logging if that was what the table had) and then set the nologging attribute.

move does not use SQL to move the table.  it is not doing inserts.

The nice thing about the move is that all of the indexes and grants and such
will stay in place.  You need to rebuild the indexes (but not recreate them)
after the move.

The second may be able to go faster if you can take advantage of parallel
inserts (make sure to use /*+ not just /* for hints.) It'll take more work on
your part to accomplish.  For 2.5 gig, I'm not sure it would be worth it -- you
might spend more time developing the procedure to do it then it would take to
just do it.
Review & Followup
Rating: 2
moving tablespace nologging January 18, 2002
Reviewer: Gitte from Germany
We use Oracle Enterprise Edition 8.1.7.2
I use:
alter table T move tablespace b nologging;

select table_name, logging from dba_tables where table_name='T';

table_name LOG
---------- ---
T          YES

Whether it works with logging or nologging I don't know, but the table isn't set
to nologging after the move.
Is there a difference to Oracle 8.1.6?
Thanks

Followup:
Ok, the logging/nologging parameter has two meanings depending on its context.

In the context you used it above, you requested that the move be done in a
nologging fashion -- if the object allowed nologging.

If you "alter table t nologging", then you would have changed the
logging/nologging attribute.

As always -- an example might help here.  We'll construct a table, move it and
see how much redo that would take and go through various scenario's:

ops$tkyte@ORA8I.WORLD> create table t tablespace UTILS
  2  as
  3  select * from all_objects;

Table created.

so there is the table we'll test with.  It started in the UTILS tablespace
and:

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select table_name, logging from user_tables where
table_name = 'T';

TABLE_NAME                     LOG
------------------------------ ---
T                              YES

its logging mode is YES (on).  Now, we'll see how much redo our session has
generated thus for and save it in a substitution variable V

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> column value new_val V
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and a.name = 'redo size'
  5  and b.value > 0
  6  /

NAME                                VALUE
------------------------------ ----------
redo size                         3904956

and we'll do your command. This command should be read in english as "move the
table T from where it is to the users tablespace, and oh by the way -- if it is
allowed, I would like this operation to be done without LOGGING".  Specifically,
this command does NOT say "move the table and change the logging mode"

ops$tkyte@ORA8I.WORLD> alter table t move tablespace users NOLOGGING;
Table altered.

ops$tkyte@ORA8I.WORLD> select a.name, b.value, b.value-&V diff
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and a.name = 'redo size'
  5  and b.value > 0
  6  /
old   1: select a.name, b.value, b.value-&V diff
new   1: select a.name, b.value, b.value-   3904956 diff

NAME                                VALUE       DIFF
------------------------------ ---------- ----------
redo size                         7790212    3885256

Now, here we can see this generated almost 4m of redo -- it would appear this
operation was in fact logged, we can further confirm that by CHANGING the
logging mode:

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select table_name, logging from user_tables where
table_name = 'T';

TABLE_NAME                     LOG
------------------------------ ---
T                              YES

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> alter table t nologging;

Table altered.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select table_name, logging from user_tables where
table_name = 'T';

TABLE_NAME                     LOG
------------------------------ ---
T                              NO

#and moving that table again:

ops$tkyte@ORA8I.WORLD> alter table t move tablespace UTILS NOLOGGING;
Table altered.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select a.name, b.value, b.value-&V diff
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and a.name = 'redo size'
  5  and b.value > 0
  6  /
old   1: select a.name, b.value, b.value-&V diff
new   1: select a.name, b.value, b.value-   7790212 diff

NAME                                VALUE       DIFF
------------------------------ ---------- ----------
redo size                         7816656      26444

Now we generate a measely 26k of redo -- just enough to log the data
dictionary changes, not the move itself.  Here we moved the object without
LOGGING all of the changes.

In fact, we'll find that you cannot simultaneously MOVE an object and alter it
in any other fashion (it is a mutually exclusive option, you either MOVE it or
you are altering it in some other fashion, but not both)

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> alter table t logging move tablespace utils;
alter table t logging move tablespace utils
                      *
ERROR at line 1:
ORA-14133: ALTER TABLE MOVE cannot be combined with other operations

Rating: 5
can we recover the move operation if use nologging? January 19, 2002
Reviewer: A reader
if our database crashes and we need to recover it since we used nologging for
this moving operation, will this move operation be recoverable...? Or after
recover it will be in its original tablespace?

Followup:
depends on the crash and circumstances.

After performaing a NON-LOGGED operation in an ARCHIVELOG mode database (as
pretty much EVERY production database should be running in) it is advisable to
do a hot backup of the affected tablespaces.  This will allow you to perform
media recovery on this tablespace.

If you do not -- AND you suffer a media failure (not a simple instance crash)
after you do the nonlogged operation -- you will have lost data.  We cannot
recover it from the archives since it is not in the archives.  You must take
care when performing these non-logged operations and ensure you have the
cooperation and OK of the guys responsible for backing UP the database.
Otherwise you can easily lose data.

In a noarchive log mode database, since we can only restore to the point in time
of the last full cold backup when you have media failure (and is the exact
reason why you need to be in archivelog mode!), this concern is not a concern.

Rating: 5
what does HEAP table mean? January 19, 2002
Reviewer: A reader
as subject

thank you

Followup:
Here is a very very short excerpt from my book on this topic, for all of the
details and lots more -- get the book:

Heap Organized Table

A heap organized table is probably used 99% (or more) of the time in
applications, although that might change over time with the advent of index
organized tables, now that index organized tables can themselves be indexed. A
heap organized table is the type of table you get by default when you issue the
CREATE TABLE statement. If you want any other type of table structure, you would
need to specify that in the CREATE statement itself.

A heap is a classic data structure studied in computer science. It is basically
a big area of space, disk or memory (disk in the case of a database table of
course), which is managed in an apparently random fashion. Data will be placed
where it fits best, not in any sort of order. Many people expect data to come
back out of a table in the same order it was put into it, but with a heap this
is definitely not assured. In fact, rather the opposite is guaranteed ? the rows
will come out in a wholly unpredictable order. This is quite easy to
demonstrate.  I will set up a table such that in my database I can fit one full
row per block (I am using an 8k blocksize).  You do not need to have the case
where you only have one row per block ? I am just taking advantage of that to
demonstrate a predictable sequence of events.  The following behavior will be
observed on tables of all sizes, in databases with any blocksize:
.......

Rating: 5
free lists January 27, 2002
Reviewer: Gitte from Germany
I moved the tables in a new tablespace with extent management local and then
analyzed the tables.
I wonder why NUM_FREELIST_BLOCKS=0 from dba_tables .
In all tables are some unused blocks and one block which has only a few rows .

Followup:
because the blocks that have NEVER had any data will be above the high water
mark, not on the freelists.

Freelists only get blocks put on them after they are used, if they've never been
used, they won't be on the freelist.

After a fresh rebuild like you just did, it is 100% natural to have very very
FEW blocks on the freelist, if any.  It just means that all of the existing
blocks with data are "packed" -- cannot accept any new inserts.  When you start
updating/deleting data, some blocks will start ending up on the free lists.

Consider (system is a dictionary MT and users is a locally MT)

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop table t;
Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t tablespace system as select *
from all_objects;
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;
Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select num_freelist_blocks from dba_tables
  2  where owner = user and table_name = 'T';
NUM_FREELIST_BLOCKS
-------------------
                  0

fully packed table, no blocks on freelist right now...

ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from t where rownum < 100;

99 rows deleted.

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select num_freelist_blocks from dba_tables
  2  where owner = user and table_name = 'T';

NUM_FREELIST_BLOCKS
-------------------
                  1

and now there are- we put some blocks on the freelist by deleting from them

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t move tablespace users;
Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;
Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select num_freelist_blocks from dba_tables
  2  where owner = user and table_name = 'T';

NUM_FREELIST_BLOCKS
-------------------
                  0

now, they are gone, all free blocks are above the HWM, not in the freelists

ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from t where rownum < 100;
99 rows deleted.

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;
Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select num_freelist_blocks from dba_tables
  2  where owner = user and table_name = 'T';

NUM_FREELIST_BLOCKS
-------------------
                  1
and here come some back, the table is not "packed" anymore, the delete freed
up some room

Rating: 5
Moving tables in 7.3.4 Parallel Server January 28, 2002
Reviewer: Irfan from Kuwait
Hi Tom,
We have Oracle 7.3.4 Parallel Server in NCR SVR4 (raw file system). I also
followed the same idea of the question (approach 2) for moving tables across
tablespaces since 7.3.4 doesn't support alter table move tablespace.
I did like:

SQL> create table tempfoo tablespace ekatsdat
  2  as select * from orgfoo;

Table created.

SQL> drop table orgfoo cascade constraints;

Table dropped.

SQL> rename tempfoo to orgfoo;

Table renamed.

I created the indexes related to orgfoo again. I would like to know

a) Is it a good solution in 7.3.4? I also had your solution in asktom, which
says to
-exp the user account
-drop all of the objects of the user
-revoke unlimited tablspace from the user
-alter the users default tablespace to target tablespace
-imp the users data

But here I want to move only one large table, not all the tables. Once if I drop
all the objects how can I import to two different tablespaces.

b) After renaming a table, Is that all the views to be run manually before
running the application, else what will happen?

c) The allocated size of the table orgfoo (Tablespace EHISTDAT) is 250 MB. This
is what I get from dba_data_files and dba_free_space before dropping table
orgfoo.

SVRMGR> Connected.
SVRMGR> select fs.tablespace_name,file_name,sum(fs.bytes)/1024/1024
remaining,df.bytes
/1024/1024
Total_space from dba_free_space fs, dba_data_files df
where fs.file_id=df.file_id
group by fs.tablespace_name,file_name,df.bytes
order by fs.tablespace_name,file_name;

TABLESPACE_NAME       FILE_NAME  REMAINING TOTAL_SPAC
-------------------------------------------------------
EHISTDAT       /dev/rdsk/c3t3d2sd  108.708984      795
EKATSDAT       /dev/rdsk/c3t3das4  391.285156      395

And quering after creating the table tempfoo in EKATSDAT and dropping the table
orgfoo, I get the following result.

TABLESPACE_NAME   FILE_NAME      REMAINING TOTAL_SPAC
-------------------------------------------------------
EHISTDAT       /dev/rdsk/c3t3d2sd  358.708984      795
EKATSDAT       /dev/rdsk/c3t3das4  281.285156      395

The number of extents and blocks also differs from dba_extents. Only tablespace
EHISTDAT releases 250MB whereas tablespace EKATSDAT not taking the whole 250MB,
instead taking only 110 MB.
Can you briefly describe the terminology behind this. Also is it a good practice
to save space?

Thanks really for eating up your time!

Followup:
The method you used is just fine.  You could do a TABLE level export, instead of
a user level export and that would work but what you did is just fine (as long
as you got all of the constraints, triggers, grants, etc -- something EXP would
get for you)

After renaming the table, you need do nothing.  Views will fix themselves, as
will any stored procedures.

As for the "size" difference -- the newly created table is freshly "compacted".
It is not unusual for them to be "smaller".  As for "is this a good way to save
space", not in my opinion.  Come back in a couple of weeks/months and you'll
find the table grows back to this size again.  Its like going on a diet, we lose
the weight for a little bit but usually end up going back to our "comfortable"
size.  Re-orging tables on a recurring basis is

a) something I do not encourage
b) something I discourage (many "opps, i lost some data" errors happen when
doing
   this stuff)
c) only "saves" space for a day or two, everything wants to get back to its
   steady state size over time anyway.

Rating: 5
instance crash when move table with nologging August 23, 2002
Reviewer: A reader
hi tom

what will happen if instance crashes in middle a moving table operation with
nologging? will we lose data?

isnt it dangerous?

Followup:
No, nologging only affects MEDIA recovery -- not instance failure.

Whilst moving the table in nologging, the table is copied from its permanent
segment to a TEMPORARY segment.  At the very end of the operation, the temporary
segment is converted into a permanent segment -- that is when the new copy
becomes the real table.

If during the move, we crash - SMON will simply clean up the temp segment and it
will be as if we never touched the table(the permanent segment is in place)

If after the move, we crash -- it is OK, the data was written directly to disk
and there is no need for instance recovery on it.

If after the move we update some data, commit and then crash -- it is OK, the
redo for the updates is available and we can recover.

If after the move and before we BACKUP the files we just did the nologging
operation into we have a DISK CRASH -- then, then "we have a problem houston".
That is why in a production environment you might consider not using NOLOGGING
or at least you will:

o backup the objects first
o perform the nologging operation
o backup the objects again

Rating: 5
so no problem for instance crash! August 23, 2002
Reviewer: A reader
thank you for your prompt and precise reply Tom.

I have a further query since you said that during move operation the permanenet
segment is untouched does that mean that the queries still reads the data from
permanenet table and not the temporary table right? And can DML operations be
performed on the table? Or there is an exclusive lock placed on the table :-?

Followup:
Yes, during an alter table move, you can read the data.

Only if the operation is done "online" (alter index rebuild online for example,
alter table move online -- only for indexed organized tables) can you also do
DML.

In Oracle9i, there is a dbms_redefinition package for doing online rebuilds of
most any object as well (allowing for DML whilst doing the move)

Rating: 3
October 04, 2002
Reviewer: Sagi from India
Hi Tom !

I have seen your example of moving table. In your example it generaged 4m or
redo (when table was in logging) and 26K of redo log.

I was trying the same but dont find of the difference. Can you point at which
stage I am doing wrong. Here is my output:

SQL> create table t
  2  tablespace users
  3  as
  4  select * from all_objects ;

SQL> select tablespace_name, table_name, logginG
  2  from user_tables
  3  where table_name='T'

TABLESPACE_NAME       TABLE_NAME           LOG
------------------------------------------- ---
USERS                T                     YES

SQL> column value new_value v
SQL> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic#=b.statistic#
  4  and a.name='redo size' and
  5  b.value > 0 ;

NAME           VALUE
------------------------- ----------
redo size     1066084

SQL> alter table t move tablespace tools nologging ;
Table altered.

SQL> select a.name, b.value, b.value - &v diff
  2  from v$statname a, v$mystat b
  3  where a.statistic#=b.statistic#
  4  and a.name='redo size' and
  5  b.value > 0 ;

NAME             VALUE       DIFF
---------------- ---------- ----------
redo size        1120404      54320

SQL> select tablespace_name, table_name, logginG
  2  from user_tables
  3  where table_name='T' ;

TABLESPACE_NAME       TABLE_NAME          LOG
---------------- ------------------------------ ---
TOOLS               T                     YES

SQL> alter table t nologging;
Table altered.

SQL> select tablespace_name, table_name, logginG
  2  from user_tables
  3  where table_name='T' ;

TABLESPACE_NAME   TABLE_NAME          LOG
------------ ------------------------------ ---
TOOLS               T                 NO

SQL> alter table t move tablespace users nologging ;
Table altered.

SQL> select a.name, b.value, b.value - &v diff
  2  from v$statname a, v$mystat b
  3  where a.statistic#=b.statistic#
  4  and a.name='redo size' and
  5   b.value > 0 ;

NAME             VALUE         DIFF
------------------------------------
redo size        1174312      53908

So If you see, when the table was in Logging it 54320 Bytes of Redo and in
No-Logging the redo is 53908 Bytes.

Infact it has taken 412 Bytes More.

I am using Oracle 8.1.7 on Windows 2000.

Anticipating your answer and explanation.

Regards,
Sagi

Followup:
You are in noarchivelog mode.

There is no need to generate redo in that mode for this operation so it doesn't
-- regardless of the logging/nologging setting..

Rating: 3
Parrallel? June 11, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
So if we need to "move" a table to another tablespace (for example moving from
DICT to LOCALLY managed tablespaces) it will be faster if we use INSERT /*+
APPEND*/ and setting the table to nologging, then doing a move (with nologging
again) ?

What about parralelism on Oracle 8.1.6 STANDARD ? Can we use something like:

alter table X move tablespace NEW parallel 4;

The command does work, but i don't know how to check if it is executed in
pararallel ...

Please advise, all we care is performane. INSERT with APPEND nologging  OR  move
nologging.

Followup:
parallel operations are a feature of EE and PE.
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76962/ch4.htm#52939
so, in SE, no parallel for you.

but why did you get the impression that insert /*+ append */ would be best?

i would just make the table T nologging and then move it:

ops$tkyte@ORA920> alter table t move nologging parallel 4;
Table altered.

easier then insert append, don't lose grants, don't lose indexes.

(you can see if something is running in parallel by querying v$px_processes
whilst it is running)

Rating: 3
Insert append.. June 12, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
Well, I did the testing, but not with pure SQLPLUS so please pardon the fact
that I am not copy/pasting the "truth" .

I created 2 tablespaces. I created a table like dba_objects and insert/appended
itself until it had ~1.8 m rows .. ~350 Mb in size with 16K blocksize.

Then I setted the table nologging (no parrallel).

Our testing machine is a 2 CPU machine with Oracle 8.1.6 EE edition non-raid
disks.
The testing, each test ran at leaest 2 times:

alter table move takes 137 seconds.
Insert /*+ append*/ takes 125 seconds.

alter table move parallel takes 150 seconds, it spawns 4 processes.

alter table move parallel 2 <b>takes 60 seconds</b>
insert /*+ append parralel */ takes ~130 seconds.

If you insist, i will redo the same tests in pure sqlplus and copy/paste the
results.

So, howcome insert /*+ append*/ is faster then move ?

redo generated was in the 200 to 350 K range.

Followup:
I would say the difference between 137 and 125 (wall clock seconds) is not
relevant.  12 seconds isn't relevant -- especially on a computer where other
things are going on.

But -- your one test shows -- alter table move parallel 2 -- is 2 times as fast
(your first parallel move was penalized probably by the fact that it started the
PQ slaves -- also, since 4 took that much longer, looks like disk contention on
the source or target disks)...

I would not conclude from this at all that insert append is faster.  I would say
it is much harder, less functional and not the right way to do it though.

Rating: 3
Followup June 12, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
It is disk contention. I was even amazed that it got so much faster with 2
slaves, as this is a plain non-raid IDE disk.

I was pretty much alone on the machine, and ran the tests one after the other,
mixing them (test1, test1, test2, test2, test1, test2) and observing the same
~12% performance difference. Each run the seconds were off by 2 or 3 for each
test.

Whenever I have the chance, I will do the test on a completelly idle multi-cpu
machine, with a 2-3 gb table to minimize the error factor. I will update this
thread with the results.

Thank you for following up the previous results.

Rating: 5
A doubt ! June 18, 2003
Reviewer: A reader
I was wondering, if a table was moved then its index would become unusable, then
where does the index rebuild command rebuild the index? Is it from the table
with new rowids (thats what i think so) or is it from the existing index (which
has invalid rowids) ???

Regards

Followup:
it has to hit the table to get the rowid info

Rating: 4
Moving a table to another tablespace June 19, 2003
Reviewer: H.S.Anand from New Delhi, India
Hi tom,
I get an error when i move a table from tablespace t1 to t2, when the table
contains a "LONG" columm.
How to move this table? Is their any specific syntax?

Regards,
H.S.Anand

Followup:
export it, import it.

longs have limits, clobs are better.

Rating: 3
pctfree, pctused and row chaining. June 25, 2003
Reviewer: Alvin from Philippines
Alter table t move tablespace users will take care of row chaining but what if i
changed the pctused and pctfree then move it to another tbs ?

Will the all the blocks in its "new" tablespace use the new pctused and pctfree
settings ?

the default settings are pctfree 10 pctused 40... if i change the pctused 80 and
move it to another tbs will the blocks be in pctused 80 mode ?

Followup:
you can specify it on the move, or it'll pick it up from the table def itself:

ops$tkyte@ORA920LAP> select pct_free from user_tables where table_name = 'EMP';

  PCT_FREE
----------
        10

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> alter table emp move pctfree 50 ;

Table altered.

ops$tkyte@ORA920LAP> select pct_free from user_tables where table_name = 'EMP';

  PCT_FREE
----------
        50

ops$tkyte@ORA920LAP> alter table emp pctfree 20;

Table altered.

ops$tkyte@ORA920LAP> alter table emp move;

Table altered.

ops$tkyte@ORA920LAP> select pct_free from user_tables where table_name = 'EMP';

  PCT_FREE
----------
        20

ops$tkyte@ORA920LAP>

Rating: 5
Thanks.. June 25, 2003
Reviewer: Alvin from Philippines
I thought every block  would have the value of the pctfree and pctused of the
extent it belongs to when the extent was created.

June 25, 2003 R -- Thanks for the question regarding "difference between sql type and plsql types", version 9.0.1.0
You Asked
you create an sql type using create type
e.g.

create or replace table_of_numbers as table of number;

or you can declare a plsql type inside a package as

...
type plsql_table_of_numbers is table of number;

Are there any differences between the two apart from the fact that
you cant do select from plsql type (e.g. in your example
of creating variable in list?)
When do you use one or the other (or does that q not make any
sense?)

Also, just like sys_refcursor plsql type is provided, are there
any other default commonly used sql or plsql types that
come in by default. E.g. a varchar2_table or a number_table would
be examples of commonly used sql or plsql types? If so where
in general are they defined?

Thanx - Please keep up the great work!:)

Menon:)
and we said...
one is a SQL type -- known to sql, 3gls, plsql -- everyone.

The other is a PLSQL type only, known only to plsql, limited 3gl access, cannot
be used as is in sql and so on.

If I need to have access to this type from 3gls and in SQL, there is only one
type to consider.

If I do not need that access, the other type is prefered -- it is hidden in the
package and does not create yet another thing in the data dictionary, its use is
limited in scope (as I deemed it to be)

DBMS_SQL has some table types I borrow frequently.
Review & Followup
Rating: 5
Does it mean anything? June 14, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
I have heard this argument:
"It is better to define your own table types in PL/SQL package than to use those
defined in DBMS_SQL package, because if you use types defined in DBMS_SQL
pacakge, this pacakge will also be loaded in memory (even if you are not going
to use its procedures) along with your own package. This would cause your
application to require more memory than may be necessary. So, to make less
memory footprint and an efficient program, declare your own types and DO NOT use
DBMS_SQL table types."

Should we be concerned?
Using DBMS_SQL types instead of defining our own within the package will have a
negative effect on performance? Is this true?
Thanks,

Followup:
I have yet to see a database that doesn't use dbms_sql, so it is probably a moot
point.  dbms_sql is used under the covers by so many things.  It is a
micro-optimization at best.

Truth be told, you would probably be best off with your own types though, but
not for those reasons.

Oracle owns DBMS_SQL.  We can change those types any time we want to.

You would own "Kamals_Types_Pkg", you would change them only when you want to.

Rating: 5
thanx for the answer to the original question! June 14, 2003
Reviewer: A reader

Rating: 5
Pitty that there is no namespace for SQL types June 19, 2003
Reviewer: A reader
PLSQL types are neat because they are encapsulated (package name acts like
object type name or namespace).

SQL types do not have namespaces and if one would like to use them in real
application one would end up complete mess with hundreds of types.

Followup:
why would you have hundreds of types?

Rating: 5
Why I have hundreds of types June 25, 2003
Reviewer: A reader
I put all my SQL into procedures and client side just calls these procuderes to
select, update, insert or delete.

Typical "select" would be packaged procedure with typed ref cursor. These
packages form interface layer between database (PLSQL) and client programm (c/s
or web based client). There interface procedures are built from viewpoint of GUI
(this is not typical object oriented interface used in 3-tier model).

Simplified example of SELECT and UPDATE procedure:

create or replace package ONE_INTERFACE_PACKAGE is

  type TPersonNameRec is record
  ( ID    number(10,0)  -- person ID
  , NAME  varchar2(50)  -- person full name to display
  );
  type TPersonNameCur is ref cursor return TPersonNameRec;

  procedure FIND_PERSON
  ( sNameToSearch IN varchar2
  , curOut out TPersonNameCur
  );

  procedure UPDATE_PERSON
  ( iID   in number(10,0) -- person ID
  , sName in varchar2(50) -- person full name to update
  );

end ONE_INTERFACE_PACKAGE;
/

create or replace package body ONE_INTERFACE_PACKAGE is

  procedure FIND_PERSON
  ( sNameToSearch IN varchar2
  , curOut out TPersonNameCur
  ) is
  begin

    open curOut for
      select ID, NAME
      from PERSONS
      where NAME like sNameToSearch;

  end;

  procedure UPDATE_PERSON
  ( iID   in number(10,0) -- person ID
  , sName in varchar2(50) -- person full name to update
  ) is
  begin

    update PERSONS
    set NAME = sName
    where ID = iID;

  end;

end ONE_INTERFACE_PACKAGE;
/

So... for each SELECT statement I would have one PLSQL record and one typed
cursor. And few hundred types collect up very rapidly.

I would like to use SQL type - because it would be supported by application
client side (by client I mean Java, Delphi, PHP etc):
* same type in database and client;
* nested tables could be useful;
* I could use same type in insert/update procedures;
* I could use array types more easily.

Some of this could be implemented with PL/SQL types also but it would be messy
at client side. SQL types would make this much easier. But I have no means to
partition SQL types at database side.

Followup:
I don't get why you would create a type for a select still.

just return ref cursors -- no types?  not getting it.

those clients do not actually SUPPORT types very well.  ref cursors -- sure,
object types -- not.

so, still not getting it I guess.

June 25, 2003 Eduardo -- Thanks for the question regarding "RAID 5 and RAID 1", version 8.1.7
You Asked
Hi Tom, greetings.

I do not have more choice to implement RAID on RS6000 system.
Either RAID 5 (implemented) and RAID 1 (not implemented yet).
I had not consider RAID 0 because I think this is not good for
my purpose, tell me if i wrong in this issue please.
Also, I had not consider RAID 0+1 due to my low budget.

I had read the documentacion "Configuring Oracle Server for VLDB",
http://www.hotsos.com/dnloads/0.Millsap1996.08.21-VLDB.pdf
from Cary Millsap.

<quote>

                                        RAID Level
                                    None 0 1 0+1 3 5
                                    ---- - - --- - -
Control file performance              2  1 2  1  5 3
Redo log file performance             4  1 5  1  2 3
system tablespace performance         2  1 2  1  5 3
Sort segment performance              4  1 5  1  2 3
Rollback segment performance          2  1 2  1  5 5
Indexed read-only data files          2  1 2  1  5 1
Sequential read-only data files       4  1 5  1  2 3
DBWR-intensive data files             1  1 2  1  5 5
Direct load-intensive data files      4  1 5  1  2 3
Data protection                       4  5 1  1  2 2
Acquisition and operating costs       1  1 5  5  3 3

Estimated relative rankings for RAID configurations from 1 (best) to 5 (worst)
for specific Oracle file types.

</quote>

Currently, i have 7 physical disks (pdisk0 to pdisk6) that make RAID 5.
(each pdisk of 9.1 GB)
I would like to reconfigure RAID 5 (4 disk + hot spare) = 36GB.
I would like to configure RAID 1 (2 disk - one for mirror) 9.1 GB

In RAID 5 I wolud like to put datafiles.
In RAID 1, i would to put :
            archive log file
            rollback segment
            temporary tablespace
            redo log file

Where am I wrong or right here.?

1.-Should I put archive log in RAID 1 or RAID 5 ?
2.-Should i put rollback in RAID 1 or RAID 5 ?
3.-Should I put temp tbs in RAID 1 or RAID 5 ?
4.-Should I put redo log file in RAID 1 or RAID 5?
5.-Should I put some datafiles in RAID 1?

6.-Should I reassess this configuration and only use RAID 5 ?
7.-Should I consider the outlines given by Mallsap?
8.-If I only use RAID 5, is there any degradation performance?
   (if any, which it can be ?)

AS can you see, i don't have enough physical disk to play with RAIDs.
I need to use most of space physical disk, my budget is very short.

Tom, can you give an explanation on this matter?
Thanx in advance.
and we said...
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:359617936136
raid 0 offers no protection from failures, so if you are going for failure
protection, raid 0 must be combined with some other raid level such as 1.

0+1 is not any more expensive then 1 is.  0 is just striping.  1 is mirrors.

I would leave rollback on the raid 5, writes to it are deferred and done by
dbwr.  I would get another cheapo but fast disk for temp (it needs no protection
and would cause contention for log).

As for 1-5 in the list, see the above link for my recommendations.

for 6, no way, raid 5 is slow.... use it only where you have to

for 7, definitely, smart guy he is.

for 8, gaurantee there will be.  raid 5 is very slow for writes.  you have to
wait for writes to LOG, you don't wait for writes to datafiles.

Disk is SOOOO cheap -- we are talking about one to three hundred US dollars for
these disks.  You've probably already spent more money than that on your salary
THINKING about this.  Don't put the horse before the cart!
Review & Followup
Rating: 5
Reader November 17, 2001
Reviewer: Reader from USA
Tom,

"
I would leave rollback on the raid 5, writes to it are deferred and done by
dbwr.  I would get another cheapo but fast disk for temp (it needs no protection
and would cause contention for log).
"

Could you elaborate this.

Does the statement imply that, when a DML is submitted,
the redo of "the rollback block changes" go to the
redolog before the redo of "actual changes to data" gets
written to the redo log. However the blocks of "rollback
changes" are written to RBS in the background by DBWR. Is
there a priority as to how soon these blocks are written
to RBS, because RBS play a vital part in the recovery
process.

Could you explain, how would temp contend for log

Thanks

Followup:
Rollback data is cached in the buffer cache just like table or index data.

it is protected by redo just like table or index data is.

Upon recovery from an instance failure, we roll forward from the online redo
logs -- this roll forward includes rolling ROLLBACK forward (recovering
rollback).  We then rollback any uncommitted transactions using this recovered
rollback data.

(if you have my book -- i go through the various scenarios with regards to
data/rollback/redo -- how it all works.  The following is a very short example
of whats in there)....

The redo of the rollback and data are treated much the same.  Say you have a
modified block of table data (block1) and a block of rollback data (block2) in
the buffer cache.  There are REDO entries owned by your transaction in the log
buffer for both block1 and block2 (to REDO the work).  If DBWR feels it must
flush block1 to make space in the buffer cache for something else -- it will
first ask LGWR to flush your redo (all of it).  DBWR can now write block1 and
leave block2 in the buffer cache.  If the system fails right now -- its OK.
When we restart -- we'll have the REDO to redo the operations to both block1 and
block2 -- block1 won't need it (we flushed it).  Block2 will -- and it will
happen.  Then, we'll discover the transaction did not commit and we will now use
block2 to rollback the work done to block1.

Rating: 5
For the previous poster... November 18, 2001
Reviewer: Connor from Glasgow
Its the redo logs that are so crucial for recovery (they contain anything
written to the rollback segments as well).  In fact, in a "perfect" system you
would see almost zero IO on the rollback segments - just as DBWR came along to
flush some rollback segment blocks out to disk, another transaction would keep
them in the buffer cache.  Of course, typically we'll see some disk IO for rb
segs - since we make them larger than the 'perfect' scenario to avoid "snapshot
too old" errors.

hth
connor

Rating: 5
August 18, 2002
Reviewer: Mike from SanAntonio, TX
Tom,
I have inherited a HP server,and there are 5 oracle databases on the server. I
have the root account.

I want to document for the configration of the disks layouts for the databases,
such as RAIDs ...

How can I find out by the Unix commends?

Followup:
Don't know, it'll be very software dependent (eg: veritas would be different
from product X).  Time to get the docs for your disk management software.

Rating: 5
For Mike.. August 19, 2002
Reviewer: Connor from UK
oracle@drl1sum1:/home/oracle >uname -a
HP-UX drl1sum1 B.11.00
oracle@drl1sum1:/home/oracle >ls -l /usr/sbin/*dis*
-r-xr-xr-x   1 bin        bin          32768 Mar  9  2000 /usr/sbin/diskinfo
-r-xr-xr-x   1 bin        bin          20480 Nov  7  1997 /usr/sbin/disksecn
lrwxr-xr-x   1 root       sys             16 Nov 23  2001 /usr/sbin/diskusg ->
/sbin/fs_wrapper
-r-sr-xr-x  31 root       sys         557056 Jul 26  2001 /usr/sbin/lvdisplay
-r-sr-xr-x  31 root       sys         557056 Jul 26  2001 /usr/sbin/pvdisplay
-r-sr-xr-x  31 root       sys         557056 Jul 26  2001 /usr/sbin/vgdisplay

Rating: 5
RAID 5 vs RAID 10 March 21, 2003
Reviewer: Tony from Canada
Hi Tom,
      First of all I would like to thank you for all your tips that are really
hard to find in any book.
We are considering implementing RAID for our Oracle db which is not very large
(about 15 gig with expected growth of 1 gig per month) , I recommended the
hardware experts about RAID 10, because of it's performance and
availability(mirroring) but they are interested in going for RAID 5, and are
telling me

"DBA's in the past were concerned with speed of storage devices and RAID levels.
 Today, I think we should be more concerned with protecting our data from disk
loss and saving our employers the cost of expensive RAID technologies.  RAID 5
delivers very good performance and data protection at a very reasonable cost.
Today, the issue of which RAID is better has become a moot point.  SCSI III (the
latest SCSI standard) has a transfer rate of 160 MB/sec with disks that rotate
at 10,000 or 15,000 rpm.  Additionally, we have SAN's and other storage devices
that utilize asynchronous cache.  (As soon as a write operation writes the data
to cache, the operation is complete.  The storage unit takes care of writing the
cache to disk.)  Other innovations in storage technology have also been
introduced.  SCSI devices are typically arbitrated.  (A traffic cop directs IO
traffic so that only one write operation per RAID set is possible.)  Today, SSA
(Serial System Architecture) allows for multiple writes to the same RAID set.
(Substantially faster than arbitrated SCSI.)  "

Well as far as I know RAID5 should be used if you have more reads and less
writes(small writes). Can you please advice me how should I go for this issue.
Are the hardware guys right about their points that RAID level doesn't matter if
hardware is fast enough and user SAN etc.

Thanks a lot.
Regards

Followup:
Ask them for the audited, reproducible benchmarks that show this to be true...

depends totally on the volume of data -- if you do not flood their cache, they
could be correct -- as long as it is a solid state cache (eg: doesn't lose data
in any circumstance)

Rating: 5
RAID March 21, 2003
Reviewer: Tony from Canada
Tom,
    Thanks a lot for your quick response, I don't understand when you say "as
long as it is a solid state cache ". Can you please explain that so that I can
tell them this with more confidence -:)
and the document on link
http://www.hotsos.com/dnloads/0.Millsap1996.08.21-VLDB.pdf
is from 1996, do we have any recent one.

Thanks again

Followup:
You would have to ask Cary if there is a more recent version of his paper
(www.hotsos.com is hit baby)

as for the solid state -- what I mean there is there is no data loss even in the
event of total power failure to the disk array, the cache is not violatile like
that.

Rating: 5
Raid March 21, 2003
Reviewer: Dan Malumphy from Cleveland, OH USA
I was lucky enough to be involved with the selection of our SAN vendor. Once
selected and installed, I had two months to perform as many benchmarks as we
could think of. First off let me say that I was quite adamant against Raid-5.
But after all my testing, I could not back my animosity.  Right now we have 11
Oracle databases running on a Raid-5 SAN. These databases have been running
about 6 months. So far so good. One thing to keep in mind is that our SAN has 1
gig of cache, which helps.
In order to make a logical conclusion in your environment, you HAVE to
benchmark.  I did, and I was very surprised by the results.

Rating: 4
RAID 0+1 vs RAID 5 June 25, 2003
Reviewer: Goh from Malaysia
Hi Tom,

   I've a customer who is just migrated from Informix to Oracle 9i. All the
oracle data files stored in 1 external disks. There are IO contention appear and
users complaints on the performance. Due the the budget constraints, the
customer cannot spend anymore money for hardware. What they have now is 4 disks
to play around with for Oracle Data Files exclude rollback, temporary, and redo
logs. In your opinion, what is the best solution that I can provide based on the
limited resource.

    1. use RAID 0+1 : 2 disks stripe + 2 disks mirror.
       * I understand that stripping doesn't improve much if
         it is less than 3-4 disks.
    2. use RAID 5 : 4 disks RAID 5
       * RAID 5 might not good for OLTP env (they have
         about 150users running on inhouse developed apps)
    3. ??

   Your sharing is very much appreciated. Thanks.

Rgds,
SHGoh

Followup:
funny, we've probably spent more money thinking about this problem -- when you
consider that a 36gig scsi drive costs what?  (not much)

4 disks -- no sizes, (no idea if IO is the actual CAUSE of the performance
problem really) -- don't know what what to tell you other then "stripe" and hope
for the best...

Rating: 5
Sane SAN June 25, 2003
Reviewer: Mark J. Bobak from Ann Arbor, MI
Just a general comment:

Read Sane SAN by James Morle.  It's available at both
http://www.oaktable.net/
and
http://www.scaleabilities.co.uk/
and well worth the read.

Rating: 2
Thanks for the info. June 26, 2003
Reviewer: SH Goh from Malaysia
Hi all,

   Sorry, I missed out something where the Storage Array cannot locate anymore
new harddisks. There are time when project is overbudget and we will somehow
have to work with the existing hardware. It is challenging to get the best
config. Cheers.

Rgds.
SHGoh

June 25, 2003 vinod -- Thanks for the question regarding "search a particular text string in between the long field", version oracle8i
You Asked
respected Tom Sir,

  I had a table in which it contains a oner long field. I  am
entering a data through  form(developer 6i).I want to search a
particluar string in this long field. But not through this form
but from some other form(search module) which search given
string in all fields. I had used %string% to check all other
varchar2 type fields but this is not working with long type
field. I hope you will help me . thanks in advance.

vinod
and we said...
In Oracle8i, you should really be using a CLOB -- not a LONG --
type as LONGS are a deprecated datatype.  If you use a CLOB
instead of a LONG, you can use dbms_lob.instr() to search inside
the clob.

Additionally, you should consider intermedia text -- see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:415419367128
for a quick blurb and a pointer to the documentation.
Review & Followup
Rating: 3
search a particular text string in between the long field March 12, 2002
Reviewer: Ron from USA
Tom,
You were suggesting the best option available, but you didn't answer the
question.Because I am using the 7.3 database and I have the same problem, I have
to search for a particular string in the long column, could you please suggest
me how do it considering the performance, because I have to do it for 2 years of
data(almost 40000 records).

Followup:
Well, excuse me but I do believe i answered THEIR question for 8i.  the answers
for them were:

o use clobs, don't use longs in the first place.

o in either case (if you stick with the LONG), look at intermedia text which
supports searching of longs and clobs very naturally.

So, you have a "brand new" question, one that never existed before on this page.
 But not to worry -- I can suggest some things (even tho 7.3 is old indeed)...

Assuming the LONG is 32k or less:

create or replace function search( p_rowid in rowidm,
                                   p_string in varchar2 ) return numberas
   ret_val  number default 0;
begin
   for x in ( select LONG_COLUMN from TABLE where rowid = p_rowid )
   loop
       if ( x.LONG_COLUMN like p_string )
       then
           ret_val := 1;
       end if;
   end loop;
   return ret_val;
end;

Now you can:

select * from t where search( t.rowid, '%hello%' ) = 1 and x > 5;
Greater then 32k -- you are very very limited in what you can do -- you'll have
to use PLSQL (not SQL as above with the search function) and the DBMS_SQL api's
to piecewise fetch from a LONG upto 32k at a time -- doing the search.  See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:426318778291
for an example of using dbms_sql to work through the long a bit at a time.

Rating: 5
thanks a lot... June 25, 2003
Reviewer: Soyoung Kim from KOREA
it's very useful function!!!!

but, is it better than using CLOB?
.. like dbms_lob.instr().

how do you think about using CLOB?

Followup:
if you can use a clob, you should use a clob.

June 25, 2003 Nag -- Thanks for the question regarding "good naming conventions", version 8.1.7
You Asked
Tom

Good naming conventions make life very easy.

But somehow it gets difficult to arrive at standard simple good naming
conventions.

What kind of naming conventions do you follow, for all kinds of variable,
cursors, packages, procedures, functions, and day to day sql or text files.

Do you have any suggestions as to how we should arrive at simple standard names.

thank you
and we said...
I have only one very simple naming convention.

create package foo
as

 g_global_variable number;

procedure bar( p_parameter_variable in date )
is
   l_local_variable varchar2(255);
begin
   ...

my variables start with g_, l_, p_

Other then that, I have no rules.  I have conventions I use, my types typically
end in _type, my packages in _pkg, my cursors -- well, they have no names cause
I only use implicit cursors.

functions, procedures and variables (after the p_, g_, l_) should have
meaningful names like "update_employee_record", "l_ename", and so on.

I guess I'm a big non-believe in "standard" names when it comes to code.  (most
of my .sql files are named a, b, c, d or test ;)  The ones I have that do stuff
are named after what they do -- showsql, free, showspace, etc.
Review & Followup
Rating: 4
What about case sensitive names or "special" characters in names? June 24, 2003
Reviewer: A reader
are there any (strong) CONS on doing such things?

Followup:
i would never do it, never.

too many things "don't work right" with them cause they are not expecting them
to be there.

Rating: 4
June 24, 2003
Reviewer: A reader
but what about doing THAT on purpose: to "hide" objects serving as wrappers for
objects in other schemas like this:

remote object    OBJECT_XY
local  wrapper "_object_xy_"
local  synonym   OBJECT_XY

thus one would be able to identify cross schema dependencies very ease through
the "special" names of the local wrappers ...

and in addition: since the intent of this approach is to make use of synonyms
instead of the wrappers the abovementioned CONs might turn into PROs ...

(what would you argue if one WANTED you to do this that way?)

Followup:
i would say i don't understand the purpose of the wrapper & synonym.

one or the other -- sure, but not both.

Rating: 4
June 25, 2003
Reviewer: Vijay from India
Hi Tom,
       Good day to you, has there never been a case when you used explicit
cursor, just asking you this since you said "my cursors -- well, they have no
names cause
I only use implicit cursors."

Thanks as always.

Best Regards,
Vijay.

Followup:
when

a) I must use a ref cursor
b) I must use bulk collect and there will be more then a low couple of hundred
rows returned (so we need to use LIMIT)

I use them.

June 25, 2003 Michal -- Thanks for the question regarding "union all view", version 8.0.6
You Asked
Hi,

I am planning to create a view which use union all of two tables.

Is it possible to use hint on query which select from view, to direct optimizer
how to access each table in view ?

Thanks,
Michal.
and we said...
You can in fact hint the view (put the hint in the view).

You can put hints in queries against views but you don't really have access to
the correlation names and such -- eg, the index( correlation_name index_name )
hint isn't as effective, you don't have the correlation_name anymore.

IMHO -- hints should be temporary stopgaps/testing tools that will not find
their way into production code.  In fact, all Oracle applications (like HR,
FinApps, etc) have a rule mandating that they may NOT be used in production
code...
Review & Followup
Rating: 1
what about global hints? February 25, 2002
Reviewer: Alexander from Russia
Hi, Tom!
Why you did not mention about "Global hints" avaliable in 8i? (see a76992, p.
7-37)

Followup:
yuck.  why bother with views If I have to know whats inside of them?  What if
the owner of the view comes back later and aliases the table in the view?  Then
my hint is useless.  Looks like a big accident waiting to happen (btw: a76992 is
more commonly referred to as the "designing and tuning for performance guide"
for those that want to read about it).

I do not recommend this approach.  It makes things waaayyyy to inter-dependent.
I just recommend "don't hint"

Rating: 3
Using hints to improve queries joining to v$ views January 15, 2003
Reviewer: Matt from Australia
You suggest here that hints should not get into production code. How do we
achieve this? I am currently looking the problem of tuning a query that accesses
a view (and the view includes a join to V$MYSTAT and V$SESSION). The problem
appears to be that these objects, being views on the datadictionary have no
optimizer statistics and so the CBO defaults cause real problems. (when the
query runs in RBO is has a sub second execution time; when it runs in CBO it
takes 15 or so minutes!)

I will include some additional information on this so that you may make some
constructive comments:

Using RBO:
==========
SELECT jb.job_sid
FROM job jb, my_session ms
WHERE jb.os_pid = ms.ospid
AND jb.actual_start_datetime=
(SELECT  max ( nj.actual_start_datetime )
   FROM job nj
   WHERE   nj.os_pid = jb.os_pid
   AND nj.job_status = 'R')

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0         20          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0         20          0           1

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: 10     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  FILTER
      1   NESTED LOOPS
      1    NESTED LOOPS
      1     NESTED LOOPS
      1      MERGE JOIN
      2       TABLE ACCESS BY INDEX ROWID JOB
     16        NESTED LOOPS
     13         NESTED LOOPS
     22          FIXED TABLE FULL X$KSUSE
     13          INDEX RANGE SCAN (object id 1075)
      2         INDEX RANGE SCAN (object id 27198)
      1       SORT JOIN
      1        FIXED TABLE FIXED INDEX X$KSUMYSTA (ind:3)
      1      FIXED TABLE FIXED INDEX X$KSUMYSTA (ind:3)
      1     FIXED TABLE FIXED INDEX X$KSUMYSTA (ind:3)
      1    FIXED TABLE FIXED INDEX X$KSUMYSTA (ind:3)
      1   SORT AGGREGATE
    255    FIXED TABLE FULL X$KSUSD
      1   SORT AGGREGATE
    255    FIXED TABLE FULL X$KSUSD
      1   SORT AGGREGATE
    255    FIXED TABLE FULL X$KSUSD
      1   SORT AGGREGATE
    255    FIXED TABLE FULL X$KSUSD
      1   SORT AGGREGATE
      1    TABLE ACCESS BY INDEX ROWID JOB
      1     INDEX RANGE SCAN (object id 27198)

Using CBO:
==========
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.66       0.67          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1    761.20     898.31       1474      11025        269           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3    761.87     898.99       1474      11025        269           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 10     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  FILTER
      1   NESTED LOOPS
      1    NESTED LOOPS
      4     NESTED LOOPS
 778714      MERGE JOIN CARTESIAN
      1       MERGE JOIN CARTESIAN
      1        MERGE JOIN CARTESIAN
      1         FIXED TABLE FIXED INDEX X$KSUMYSTA (ind:3)
      1         BUFFER SORT
      1          FIXED TABLE FIXED INDEX X$KSUMYSTA (ind:3)
      1        BUFFER SORT
      1         FIXED TABLE FIXED INDEX X$KSUMYSTA (ind:3)
 778714       BUFFER SORT
 778714        TABLE ACCESS FULL JOB
      4      FIXED TABLE FULL X$KSUSE
      1     FIXED TABLE FIXED INDEX X$KSUMYSTA (ind:3)
      1    INDEX RANGE SCAN (object id 1075)
      1   SORT AGGREGATE
    255    FIXED TABLE FULL X$KSUSD
      1   SORT AGGREGATE
    255    FIXED TABLE FULL X$KSUSD
      1   SORT AGGREGATE
    255    FIXED TABLE FULL X$KSUSD
      1   SORT AGGREGATE
    255    FIXED TABLE FULL X$KSUSD
      1   SORT AGGREGATE
      1    TABLE ACCESS BY INDEX ROWID JOB
      4     INDEX RANGE SCAN (object id 27197)

The view definition:
====================

CREATE OR REPLACE VIEW SYS.MY_SESSION AS
(SELECT S1.SID ORACLE_SID, N.SERIAL# ORACLE_SERIAL#,
         N.PROCESS OSPID,
         S1.VALUE PHYSICAL_READS, S2.VALUE PHYSICAL_WRITES,
         S3.VALUE CPU, S4.VALUE PGA_MEMORY, SU.USER_SID
  FROM V$MYSTAT S1, V$MYSTAT S2, V$MYSTAT S3, V$MYSTAT S4,
       V$SESSION N, APP_USER.USERS SU
  WHERE S1.STATISTIC# = 39
    AND S2.STATISTIC# = 40
    AND S3.STATISTIC# = 12
    AND S4.STATISTIC# = 20
    AND N.SID = S1.SID
    AND SU.USERNAME = N.USERNAME
 )

I've tried to split up the query and tune the view with no success (apart from
using the RULE hint which kind of defeats the object ). Fresh stats (DBMS_STATS)
were generated just before this test.

What would you recommend - hints in the view or global hint?
How do I avoid hints in the production code - surely I HAVE to include these?

Finally, can you suggest an approach that might help me solve this problem? - I
need to get the query to run with a similar execution time.

Followup:
I would seriously consider NOT ACCESSING V$ TABLES IN PRODUCTION CODE period.

v$ tables are generally memory structures.
v$ tables are not read consistent.
v$ tables require latches to access -- cannot modify and read memory at the same
time.

heavy access to v$ tables like this may cause some serious heavy duty
contention.

You want to make your systems run faster?  turn off all of those "pretty gui's"
that query the v$ tables like mad -- just to show you a blinking red light when
that useful "cache hit ratio" falls below some limit (sarcasm).

Also -- you should never use statistic#'s -- they can and will change between
platforms and releases and patches.  I've been burned by that personally.

Also -- read
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76961/ch3.htm#5014
<quote>
Notes:

    * You can query the dynamic performance views to extract information from
them. However, only simple queries are supported. If sorts, joins, GROUP BY
clauses and the like are needed, you should copy the information from each V$
view into a table (for example, using a CREATE TABLE ... AS SELECT statement),
and then query from those tables.

    * Because the information in the V$ views is dynamic, read consistency is
not guaranteed for SELECT operations on these views.
</quote>

And lastly -- I would say that your view "MY_SESSION" is not a view I would use
in this query.  The other junk in this view is not relevant.  Looks like you
want a simple view "MY_OSPID"

CREATE OR REPLACE VIEW SYS.MY_SESSION AS
(SELECT N.PROCESS OSPID
  FROM  V$SESSION N
 where audsid = userenv('sessionid')
)

Views should be specific -- you create a view to answer a question.  In fact, I
would go a step further and say you really don't want MY_SESSION as the view,
but rather:

SELECT jb.job_sid
FROM job jb, my_session ms
WHERE jb.os_pid = ms.ospid
AND jb.actual_start_datetime=
(SELECT  max ( nj.actual_start_datetime )
   FROM job nj
   WHERE   nj.os_pid = jb.os_pid
   AND nj.job_status = 'R')

Rating: 4
Many Thanks and Additional... January 16, 2003
Reviewer: Matt from Australia
I spotted the cut and paste typo (I've replaced my_session with my_ospid):

CREATE OR REPLACE VIEW SYS.MY_OSPID AS
(SELECT N.PROCESS OSPID
  FROM  V$SESSION N
 where audsid = userenv('sessionid')
)

SELECT jb.job_sid
FROM job jb, my_ospid ms
WHERE jb.os_pid = ms.ospid
AND jb.actual_start_datetime=
(SELECT  max ( nj.actual_start_datetime )
   FROM job nj
   WHERE   nj.os_pid = jb.os_pid
   AND nj.job_status = 'R')

Thanks for the warnings about V$ views and the STATISTICS# column. I will be
making sure these changes are made.

My current hypothesis is that the filter AND nj.job_status = 'R' is not being
interpretted correctly by the CBO. That is the histograms do not contain correct
data. The 'R' represents RUNNIING jobs. When optimizer statistics are generated
there may be no entries in the table. I need to generate suitable
stats/histograms to correctly represent the data distribution in the table.

Is it possible to generate my own histograms on nj.job_status in the data
dictionary?

Followup:
you don't need that correlated subquery.  see
http://otn.oracle.com/oramag/oracle/02-sep/o52asktom.html
and look for playing with aggregation.

Rating: 5
Excellent Approach January 19, 2003
Reviewer: Matt from Australia
It took a little while to understand how the analytic functions worked but I
have come up with the following after creating the recommended view MY_OSPID:

select job_sid
from (
  select job_sid
        ,os_pid
        ,job_status
        ,first_value(job_sid)  over ( partition by job_status order by
actual_start_datetime desc ) first_job_sid
        ,row_number() over ( partition by job_status order by
actual_start_datetime desc ) partition_rnum
  from job
     )
where job_status = 'R'
and os_pid = (select ospid from my_ospid)
and partition_rnum = 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.04       0.14          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          2          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.04       0.15          0          2          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 36

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  FILTER
      0   VIEW
      0    WINDOW SORT
      0     TABLE ACCESS BY INDEX ROWID JOB
      0      INDEX RANGE SCAN (object id 10754)
      0   FIXED TABLE FULL X$KSUSE

This is a great improvement over the other query from many minutes to a few
tenths of a second. However, it is not as fast as the RBO execution plan. It
seems odd to quibble over a few tenths of a second, but can you please try and
explain why this is so? I thought that the CBO would (with  a little help) beat
the RBO in every case.

Followup:
I would actually use the aggregate trick here, not the analytic (select substr(
max()) trick

Rating: 2
Some explanation required please... January 20, 2003
Reviewer: Matt from Australia
Can you please identify when you might use each of your suggested approaches
(when I read the referenced article it seemed to suggest that the analytic
approach would be more efficient - we are on 9.0.1.4.0)?

Can you also please explain why the CBO needs help to identify the best plan. If
we compare the original select using CBO and RBO, why was it that the CBO could
not find an execution plan that was as good as the RBO's?

Followup:
The CBO is blind sided by the v$ tables with no stats right now.  It is flying
with its eyes closed - that is why.

I was thinking:

SELECT
substr( max( to_char( actual_start_datetime, 'yyyymmddhh24miss' ) ||
jb.job_sid), 15 ) job_sid
FROM job jb
WHERE jb.os_pid = (select process from v$session where audsid =
userenv('sessionid') )
/

Rating: 3
Analytic approach/Aggregate Func Which one is better January 20, 2003
Reviewer: IB from HongKong
Tom,

  I was reading this full article. Even i got the same doubt.I have seen your
many of answers where you have suggested to use Analytic approach. How to decide
(Analytic/Agrregate)? I know it is difficult to tell but i feel some examples
from your experience will increase our knowledge.

Thanks and Regards,

Followup:
which query was easier to write and understand:

SELECT
substr(max(to_char(actual_start_datetime,'yyyymmddhh24miss')||jb.job_sid),15)
  FROM job jb
 WHERE jb.os_pid = (select process
                     from v$session
                    where audsid = userenv('sessionid') )

or:

select job_sid
from (
  select job_sid
        ,os_pid
        ,job_status
        ,first_value(job_sid)  over ( partition by job_status order by
actual_start_datetime desc ) first_job_sid
        ,row_number() over ( partition by job_status order by
actual_start_datetime desc ) partition_rnum
  from job
     )
where job_status = 'R'
and os_pid = (select ospid from my_ospid)
and partition_rnum = 1

I'm lazy -- i go with "easy".  If I had to select 20 column from job -- I may
have gone with:

select .... <whatever I needed>
  from ( select *
           from job
          WHERE os_pid = (select process
                            from v$session
                           where audsid = userenv('sessionid')
                          )
         order by actual_start_datetime DESC nulls last
        )
 where rownum = 1
/

instead....

Rating: 4
Full scan using union all inside the view June 25, 2003
Reviewer: A reader
Hi Tom,

I have a view which is a union all of some tables. All the tables have a pk.
When a run a query which joins the view with another table, the view is resolved
by the optimizer choosing a full scan on the pk instead of doing a unique
access. I have been playing around with hints in order to get the optimizer to
chose a unique access but I am not able to do it.
Can you tell how I can do in order to get a unique access in this case?

View:
create view view1 as
select * from table_1
union all
select * from table_2;

Query1:
select  /*+ ordered use_nl(B) index (A idx1) */
         count(*)
from    table_a A,
        view1 B
where   A.acc_id = &acc
    and A.at = 1
    and B.ph = A.ph;
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=17378 Card=1 Bytes=27)

   1    0   SORT (AGGREGATE)
   2    1     NESTED LOOPS (Cost=17378 Card=1 Bytes=27)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'TABLE_A' (Cost=4 Card=1
Bytes=20)

   4    3         INDEX (RANGE SCAN) OF 'IDX1' (NON-UNIQUE) (Cost=3 Card=1)

   5    2       VIEW OF 'VIEW1'
   6    5         UNION-ALL
   7    6           INDEX (FULL SCAN) OF 'PK_PA' (UNIQUE) (Cost=11370
Card=2212430 Bytes=22124300)

   8    6           INDEX (FULL SCAN) OF 'PK_iN' (UNIQUE) (Cost=6918
Card=1290158 Bytes=12901580)

But if I change the query and I use a value for the column ph, the optimizer
choses an unique scan.

select  /*+ ordered use_nl(B) index (A idx1) */
         count(*)
from    table_a A,
        view1 B
where   A.acc_id = &acc
    and A.at = &at
    and B.ph = &ph;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=8 Card=1 Bytes=16)
   1    0   SORT (AGGREGATE)
   2    1     NESTED LOOPS (Cost=8 Card=2 Bytes=32)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'TABLE_A' (
          Cost=4 Card=1 Bytes=9)

   4    3         INDEX (RANGE SCAN) OF 'IDX1' (NON-UNIQUE) (Cost=3 Card=1)

   5    2       VIEW OF 'VIEW1'
   6    5         UNION-ALL
   7    6           INDEX (UNIQUE SCAN) OF 'PK_PA' (UNIQUE) (Cost=2 Card=1
Bytes=10)

   8    6           INDEX (UNIQUE SCAN) OF 'PK_IN' (UNIQUE) (Cost=2 Card=1
Bytes=10)

Thanks in advance for any help on this.

Followup:
do you actually have stats, tables look like what? and the stats are collected
how?

Apparently, the optimizer things "a=1 returns lots of the same ph's"

for the given acc_id and at on A, how many ph's are there.

June 26, 2003 puppali -- Thanks for the question regarding " rename a table", version 8.1.6
You Asked
Hello tom,

 this is sreedhar got a silly dout..

 where can i get the info regarding  a table name has been
changed  with  RENAME <S> TO <T>
well i want to know more  about that.,,
recently i faced the problem one of our developer has changed
the
table name and performance got slow... is there any shortcut..
i mean a view or table which stores the info..

thanks in advance

with best regards..
sreedhar
and we said...
changing the name of the table will have NO impact on performace
whatsoever.  It may have have a transient affect on performance
immediately after the rename as stored procedures would have
gone invalid (and would be recompiled during their next execute)
and the shared pool would have flushed objects that referenced
the renamed object -- but within minutes all would be back to
normal.

Something else has happened in your system to affect performance
if this is a long term issue -- renaming the table had nothing
to do with it.

The rename command, unless you had auditing on, is done directly
against the data dictionary and no trail would be left behind.
Review & Followup
Rating: 2
I am getting the following error June 24, 2003
Reviewer: Abhijit from Mumbai
Hi Tom,
I am getting the error ORA-26563 : renaming this table is not allowed.
I am the schema owner in this particular schema and I have confirmed that the
tabl belongs to me.

However I think that other schemas might be referring to this table.

Please help! What should I check for and how?

Cheers,
Abhijit

Followup:
[tkyte@localhost Desktop]$ oerr ora 26563
26563, 00000, "renaming this table is not allowed"
// *Cause: Attempt to rename a replicated table, an updatable materialized view
//         table or the master table of a materialized view for which a
//         materialized view log has beencreated.
// *Action: If desired, unregister the replicated table with
//          dbms_repcat.drop_master_repobject, or use the recommended
//          procedure to rename the master table of a materialized view.

you are using replication.  this table is replicated.  renaming it would break
the system.

Rating: 1
Thanks, but I want to test my app! June 25, 2003
Reviewer: Abhijit from Mumbai
Hi Tom,
I want to test my app in this broken down state.
I want to find out where is this table being replicated?

Which view should I look at?

Cheers!
Abhijit

Followup:
seems like you have far larger problems -- lack of "change management" or
"communication" seems to be at play here.

Look in dba_dependencies.

June 26, 2003 Ken -- Thanks for the question regarding "Fine Grained Access Control", version version 8.1.6
You Asked
Tom,

I just finished reading your excellent article on Fine Grained
Access Control and I have played with it some on my own.  With
regard to the "Important Caveat" section of the article, is
there a way to force a cursor reparse?  In other words, is there
any way to change a predicate during a given session?  I realize
there are reasons to not do this.  However, with a very very
large B2B web application it would be simpler to create a
limited number of database userids (e.g., one per company)and
then change the predicate based upon the application (not
database) userid.  Creating and maintaining thousands of
database userids is very labor intensive.  Having hundreds or
thousands of different database connections through an
application server may be problematic, also.  Any ideas or
options?
and we said...
It depends on the programatic environment and how you program to
it.

From that important caveat section:

...
I should mention that there are cases where changing the
predicate in the middle of a session may be desirable. The
client applications that access objects that employ policies
that can change predicates in the middle of a session must be
coded in a specific fashion to take advantage of this. For
example, in PLSQL we would have to code the application using
dynamic sql entirely to avoid the cursor caching. If you are
employing this dynamic predicate method – then you should bear
in mind that the results will depend on how the client
application is coded, therefore you should not be enforcing a
security policy with this use of this feature. We will not be
discussing this possible use of the DBMS_RLS feature but rather
will concentrate on its intended use – to secure data.
...

So, for example, if your application logic is written in PLSQL,
you would have to use a construct like:

declare
   type rc is ref cursor;
   l_cursor     rc;
   l_rec        emp%rowtype;
begin
   open l_cursor for 'select * from emp';
   loop
      fetch l_cursor into l_rec;
      exit when l_cursor%notfound;
      ...
   end loop;
   close l_cursor;
end;

instead of

begin
    for x in ( select * from emp ) loop
       ...
    end loop;
end;

Else PLSQL will cache the parsed cursor (in the for loop
example) for your session.  We would never get a chance to
reparse it and hence the RLS function would never get called
again for it.  To avoid that, we do not allow plsql to cache the
cursor simply by hiding the cursor from view.

If I was using Pro*C, hold_cursor=NO on the precompile line
should be sufficient to avoid this issue as well.

If I was using Java, closing the prepared statements/ callable
statements and reparsing them should be sufficient.
Review & Followup
Rating: 5
A Twist... October 11, 2001
Reviewer: David Thompson from Downers Grove, IL USA
We have a similar situation where we are using connection pooling from ASP/VB
objects from the web.  In essence every page hit grabs an Oracle connection from
the pool and uses it.  So our connections are not only using the same user, but
the same session!  This can be a real problem if not designed and built
correctly.

We also us the SYS_CONTEXT and RLS policy stuff to identify predicates that
determine what a user can/not see.  If the predicates are cached, we might be
returning the wrong data to a user!!!  Real problem.

So, we added something to our "Who am I" procedure (a PL/SQL procedure to
identify the person and context who is currently using the session).  This
procedure is the FIRST call made EVERYTIME a user pulls a session from the pool.
 Since no DML statements would be in limbo at this time a call to the
DBMS_RLS.REFRESH_POLICY(...) procedure is safe (I think).

DBMS_RLS.REFRESH_POLICY basically flushes the cache of parsed SQL statements in
the sqlarea.  After calling this procedure for each policy in the database, any
future queries against a policy driven table would be forced to be reparsed.  I
know this might seem to be a brutal approach but it guarantees all our SQL
statements will have the policy applied correctly.

DBMS_RLS.REFRESH_POLICY does do a commit so we had to make sure that all
transactions have either been committed or rolled back before we put the session
back in the pool.  We also coded it such that the procedure is called only when
the "context" changes.  Thus reducing the number of calls to the procedure.

This seems to work well for us.  Any feedback would be appreciated....

Followup:
My comments on the above.

In 815 and 816 -- that would work and would be necessary HOWEVER, the hit on the
shared pool is terrible.  I personally would prefer to log off/log in, you might
even find performance to be BETTER (that shared pool hit is HUGE in terms of CPU
and latching -- simply HUGE).

In 817, you'll find that the cursors will automatically be flushed in a SESSION
when the contex is change.  This effectively corrects this.

To get the full story (44 pages of it), I have a chapter on RLS in my book that
covers this caveat above -- the fix in 817 for it -- but also covers the other
gotcha with cached cursors that still exists in 817.

Rating: 2
8.1.7.2.0 - Caching dynamic SQL October 12, 2001
Reviewer: David Thompson from Downers Grove, IL USA
Tom,

I will buy your book today and that might solve my problems.  But I found just
the opposite:  the following works in 8.1.6 but not 8.1.7!

RDBMS Version: 8.1.7.2.0

DBMS_RLS: Statements are caching

I am using the DBMS_RLS procedures to set contexts and then use them in policy
statments to reduce the number of rows returned.

It seems that, within the same session, if I change a context variable and
execute a dynamic SQL statement, the statement is parsed and executed correctly
the first three times. After that, the statement is cached and any changes to
the context variables have no effect on the dynamically parsed SQL.

Here is an example:

declare
  cnt number;
  l_user VARCHAR2(30);
begin
  for x in 1 .. 4 loop
    rts_security.set_user('UATESTER9');
    execute immediate 'select sys_context(''rts_security'',''userid'') ,
count(*) from mtp_oper_co ' into l_user,cnt;
    dbms_output.put_line (x||'-'||l_user||':'||cnt);
    rts_security.set_user('SYSADMIN');
    execute immediate 'select sys_context(''rts_security'',''userid'') ,
count(*) from mtp_oper_co ' into l_user,cnt;
    dbms_output.put_line (x||'-'||l_user||':'||cnt);
  end loop;
end;

Here is the output:

1-UATESTER9:1
1-SYSADMIN:3
2-UATESTER9:1
2-SYSADMIN:3
3-UATESTER9:1
3-SYSADMIN:3
4-UATESTER9:3
4-SYSADMIN:3

Notice on the forth try, the count for UATESTER9 is not set at 3. Any future
attempts will return 3 for this session...!>!>!>?!?!?!

Followup:
I have to assume some logic in your predicate function is bad then.  I assume
this for two reasons:

1) I cannot reproduce your situation

2) Using dynamic sql like that OBVIATES the cursor cache, those cursors are NOT
cached (dynamic sql cannot be cached by PLSQL).  Using dynamic sql and such was
a workaround for this specific issue in 815 and 816.  (in fact, the following
test case works in 815..817 the same except when we use static sql).

Here is a test case -- can you tweak it to replicate in your environment the
above issue?  Else post all of the code in your security policy for us to look
at:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace context rts_security using
rts_security
  2  /

Context created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure rts_security(
p_name in varchar2, p_val in varchar2 )
  2  as
  3  begin
  4      dbms_session.set_context( 'rts_security', p_name, p_val );
  5  end;
  6  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function rls_examp
  2  ( p_schema in varchar2, p_object in varchar2 )
  3  return varchar2
  4  as
  5  begin
  6      if ( sys_context('rts_security','userid') = 'UATESTER9' )
  7      then
  8          return 'rownum = 1';
  9      else
 10          return '1=1';
 11      end if;
 12  end;
 13  /

Function created.

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2     dbms_rls.add_policy
  3     ( object_name => 'T',
  4       policy_name => 'T_POLICY',
  5       policy_function => 'rls_examp',
  6       statement_types => 'select, insert',
  7       update_check    => TRUE );
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2    cnt       number;
  3    l_user VARCHAR2(30);
  4  begin
  5    for x in 1 .. 40
  6    loop
  7      rts_security('userid','UATESTER9');
  8      execute immediate 'select sys_context(''rts_security'',''userid'') ,
count(*)
  9                           from t ' into l_user,cnt;
 10      dbms_output.put_line (x||'-'||l_user||':'||cnt);
 11      rts_security('userid','SYSADMIN');
 12      execute immediate 'select sys_context(''rts_security'',''userid'') ,
count(*)
 13                           from t ' into l_user,cnt;
 14      dbms_output.put_line (x||'-'||l_user||':'||cnt);
 15    end loop;
 16  end;
 17  /
1-UATESTER9:1
1-SYSADMIN:3
2-UATESTER9:1
2-SYSADMIN:3
3-UATESTER9:1
3-SYSADMIN:3
4-UATESTER9:1
4-SYSADMIN:3
5-UATESTER9:1
5-SYSADMIN:3
6-UATESTER9:1
6-SYSADMIN:3
7-UATESTER9:1
7-SYSADMIN:3
8-UATESTER9:1
8-SYSADMIN:3
..... (always 1, 3, 1, 3...)

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2    cnt       number;
  3    l_user VARCHAR2(30);
  4  begin
  5    for x in 1 .. 40
  6    loop
  7          for j in 1..2
  8          loop
  9          if ( j = 1 ) then
 10                          rts_security('userid','UATESTER9');
 11                  else
 12                  rts_security('userid','SYSADMIN');
 13                  end if;
 14          select sys_context('rts_security','userid') , count(*)
 15                    into l_user,cnt
 16            from t;
 17
 18                  dbms_output.put_line (x||'-'||l_user||':'||cnt);
 19          end loop;
 20    end loop;
 21  end;
 22  /
1-UATESTER9:1
1-SYSADMIN:3
2-UATESTER9:1
2-SYSADMIN:3
3-UATESTER9:1
3-SYSADMIN:3
4-UATESTER9:1
4-SYSADMIN:3
5-UATESTER9:1
5-SYSADMIN:3
6-UATESTER9:1
6-SYSADMIN:3
7-UATESTER9:1
7-SYSADMIN:3
8-UATESTER9:1
8-SYSADMIN:3

(always 1,3,1,3,...)

The problem manifests itself in 815 and 816 only with the LAST block of code --
whereby the SAME exact static sql statement would be "frozen" as regards the
predicate function.  In 815, 816 -- it behaves differently:

ops$tkyte@ORA815.US.ORACLE.COM> declare
  2    cnt       number;
  3    l_user VARCHAR2(30);
  4  begin
  5    for x in 1 .. 40
  6    loop
  7          for j in 1..2
  8          loop
  9          if ( j = 1 ) then
 10                          rts_security('userid','UATESTER9');
 11                  else
 12                  rts_security('userid','SYSADMIN');
 13                  end if;
 14          select sys_context('rts_security','userid') , count(*)
 15                    into l_user,cnt
 16            from t;
 17
 18                  dbms_output.put_line (x||'-'||l_user||':'||cnt);
 19          end loop;
 20    end loop;
 21  end;
 22  /
1-UATESTER9:1
1-SYSADMIN:1
2-UATESTER9:1
2-SYSADMIN:1
3-UATESTER9:1
3-SYSADMIN:1
4-UATESTER9:1
4-SYSADMIN:1
5-UATESTER9:1
5-SYSADMIN:1

(always 1 for that SPECIFIC query)

Rating: 4
vishnu October 18, 2001
Reviewer: vis from USA
Hi Tom,
I don't want to burden you with another question on RLS.My requirement is simple
..I have one table called formulae  with
fields...formulae_id,name,whocan_see_it..depending on the log in only those
records should be seen by them ..As i said i want to buy yr book on RLS for
complicated things ..Can u pl give me book details and the solution please...
vishnu

Rating: 5
Returning a NULL policy February 12, 2002
Reviewer: David Thompson from Chicago, USA
Tom,

You are correct!!

Long time since my response to this but, as it turns out, if you changed your
policy statement to return a NULL instead of 1=1 you would have seen the same
results as I did.  I corrected my policy statement to NEVER return a NULL value
and now it works fine!!!!  Try your example again but return a NULL instead of
1=1 and you should see the same results I did...

Thanks for your help!

Rating: 5
Problem with cursor caching May 30, 2003
Reviewer: Bharath from USA
We are using RLS in our app:Due to some circumstance we have end up using
dbms_rls.refresh_policy() to overcome cursor sharing problem.The reason we didnt
used context variable is we have to the change in application code(front
end).Bcos of context var is for each session.
But based on your feedback on DBMS_RLS.REFRESH_POLICY it's terribel thing to
do?Is it something equal to alter system flush shared pool.

Is there any other method that i can use to overcome the cursor sharing problem.

Followup:
not unless you explain in a tad more detail what the problem IS exactly.

Rating: 5
problem with cursor sharing May 30, 2003
Reviewer: Bharath from USA
our dynamic predicate  is based on role  from the table(role_xref) which will be
changed outside the appilcation by another common security module.if user login
with role A and in the middle of  his session if Admin changes his roles then
prodicate policy has to be changed immediately.If we change the role in the
middle of the session and due to cursor cache some the sql in PLSql still uses
old predicate  not the new one.If i want to overcome these i have two options
1,Contect Var
2,dbms_rls.refresh_policy

I cant use contect Var bcos i need to change the context thru the application
which user A is accessing .In order to overcome this problem we have used
dbms_rls.refresh_policy to reparse.we are doing this in the application where we
can change the role of the user.Does it make sense?

Followup:
then you will have to flush the cached cursors as you are.

if you change the context in a session, we'll invalidate just that sessions
cached cursors.

If you change a value in a table and want the currently logged in sessions to
see that change right away, you'll need to do what you are doing.  there is no
other alternative.

Rating: 5
Problem with cursor caching May 30, 2003
Reviewer: Bharath from USA
Thanks for Quick reply.
I want to know what is the difference between changing the context and
dbms_rls.Both will make sure that sql will reparsed when it excutes next
time.The way dbms_rls.refresh is doing is removing the entry(only sql which
refering the refreshed policy) from V$open_cursor(Cached cursor) what is
happening  when i change the session_conext ?

Followup:
dbms_rls.refresh invalidates all related cached sql in the shared pool.

a context change would invalidate cursors for a single session only.

So, if you can accomplish your goals via a context change, it is more efficient
then doing it for the entire system.

In your case, you cannot.

Rating: 5
Invalidate session cached cursor May 30, 2003
Reviewer: Bharath from USA
>>we'll invalidate just that sessions cached cursors.

What do you mean by Invalidate seesion cached cursors.

I want to know whether session cached cursor parameter has any impact on FGAC.

Followup:
we'll invalidate the cursors in your session that might be affected by a change
in a context setting (eg: any cursors that rely on FGAC)

sorry for the confusion -- it has nothing to do with session cached cursors.

Rating: 5
Finally June 02, 2003
Reviewer: bharath from USA
>>sorry for the confusion -- it has nothing to do with >>session cached cursors

if session cached cursor  cache the closed cursor right.

Question 1.Will it create a problem if predicate changes inside the session and
bcos of cursor cache(closed cursor cache) it wont get reparse when it's get
executed.

Question 2.Will context changes or dbms_rls.refresh  invalidate the  session
cached cursor .so when it executes it will reparse.

Followup:
the database will handle the changing predicate for you.

it'll reparse.

Rating: 4
is it possible to add a order by clause using FGAC June 26, 2003
Reviewer: umesh from banagalore india
Tom
I have a query which says
select * from t1
order by col1;

can i use FGAC and make it

select * from t1
order by col1,col2;

Followup:
no

June 26, 2003 Steven -- Thanks for the question regarding "Any drawbacks to using RMAN?", version 8.1.7
You Asked
We will be migrating to 9.2 in the near future from 8.1.7 for a
data warehouse.  We are having a debate amongst ourselves about
using RMAN when we go to the new platform.  One camp is all for it, another is
leery because someone had a really bad experience with
the older EBU.  The main gist of the argument against using RMAN is
that we don't want to take the chance of having a fouled up backup.
The sysadmins are happy with what works, and if it ain't broke, don't
fix it.  Can you (or any other readers) list any drawbacks to using
RMAN?

Thanks.
and we said...
Nope.  Only benefits from where I sit.  But -- I will publish this and let
others freely comment.

RMAN decreases the chance of you making a mistake.  Tell me -- do you regularly
test your backups?  I was with a customer who wanted to know why their system
was down for  days -- yes, days.  Why so long?  Well, they had a problem, had to
restore from backup.  Never tested said backup.  Discovered all of a sudden
their script kicked off the copies BEFORE the alter tablespace begin backup.
Whoops - System was fried.  Support helped them scavenge what they could but
basically, it was a rebuild of the entire big database.  That would not happen
with RMAN -- less for them to think about, less mistakes possible.

EBU -- that's going back 7 or more YEARS.  It might be time to let go don't you
think?

How about this -- you have a single bad block in a 16gig datafile, you are using
RMAN in 9i.  What do you do?  You do an in place recovery of just the bad block,
you do not offline the datafile, you do not offline the tablespace, you do not
do 16gig of IO to restore the file, you do not roll 16gig of data forward.  You
recover an individual 8k block.

Oh -- you are not using rman?  Ok, take tablespace offline, restore 16gig, roll
it forward then bring it online.

Which gives you less downtime?
Which fully recovers the fastest?
Which would you prefer to do?

That is just one "uptick".

In the end -- if you know what you are doing fully, you TEST the backups on a
recurring basis, you don't mind being down for a bad block -- the old fashioned
way works but -- times are changing, there are more and more reasons to use it
-- only FUD left as a reason not to.
Review & Followup
Rating: 5
RMAN Rules January 05, 2003
Reviewer: Terry from San Jose, CA USA
On top of all of the good things you have said there are a couple of other very
very good things about RMAN:

1.  It is much (MUCH) faster than OS file copies by a long shot.  I can fully
backup small databases even on Windoze in a few minutes.
2.  It deals with the backup process at the block level so it waits for each
block to become consistent to back it up, therfore no "cracked blocks" like file
copying.
3.  You can "easily" parallelize backup and much more importantly it can
parallelize recovery.
4.  Backup retension scheduling is built-in, no more do I delete this backup or
not, it's part of your design and deployment process.
5.  It can manage the archived redo log automatically for you (getting them off
the system to somewhere safe is important) including deleting the old logs.
6.  It deals with cold backups (if anybody actually wants to do one anymore).
7.  One RMAN catalog database can manage a complete enterprise, Unix, Windoze
who cares and it can do it concurrently because the Oracle database Server is
doing the real work.
8.  Testing backups without restoral is built-in and that's nice as well.
9.  No more putting tablespaces in backup mode and hammering the daylights out
of the redo log system.
10.  Making standby databases and cloning is way simpler under RMAN becuse it is
designed to do that for you.

These are a few of my favorite things.

Rating: 4
Drawbacks of RMAN January 05, 2003
Reviewer: Terry from San Jose, CA USA
I see the comment regarding the sysadmin "not wanting to fix what ain't broke".

<RANT>
In my experience in America today there is a constant "tug-of-war" between the
system people and the Oracle DBA.  System people think servers are their
personal pleasure toy and the DBA is some dude ( or dudette)  being allowed to
play in their sandbox.</RANT>
Not to give these guys any credit they don't deserve (and they don't deserve
much for infinitely living in the past, believing thier own rhetoric and
constantly laying out storage based on space instead of I/O causing the DBA, the
application and the users constant problems ) BUT the main complaint with RMAN
from a system point of view is that it does not easily deal with Juke Box backup
tape systems when you want to write directly to tape.  I personally prefer to
have at least one backup on disk so this is not an issue.  Also, disk is
relatively inexpensive and it would not necessarily need to even be mirrored and
the benefit of having this backup on disk far out-weighs the cost of the disks
themselves and speeds up the backup process itself greatly.

Rating: 3
How to change the backup location? January 16, 2003
Reviewer: A Reader from US
Hi Tom

I am using the RMAN to take the backup of my database but RMAN always copy the
backup piece in default location. I am working on Windows OS so it is copying
the backup in C:\oracle\ora81\database location.
How can I change the location?

Thanks.

Followup:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76990/usingrma.htm#439428

      Rating: 4
Cross Platform
      Support  June 06, 2003
Reviewer:  prudent
       from CA
Hello Tom

Does RMAN provide cross platform backup and restore functionality ? For eg my
source database is in HP-UX and if I want to configure rman in SUN 5.8 will
there be any issues with regards to recovery.  Is it supported feature of rman.
Both Sun and HP will have 8.1.7.4 on it.

Thanks

          Followup:

you cannot (currently) take a datafile from operating system 1 and put it on
operating system 2.

You would have to "dump and load" to go from hp to sun right now

      Rating:
      2
  June 06, 2003
Reviewer:
      A reader
We are a small shop with homegrown backup scripts backing up to server-attached
local tape drives (/dev/rmt/...).

My biggest problem with RMAN is that it doesnt support /dev/rmt/... as a tape
device. It needs some fancy tape management library hardware/software and stuff
which is overkill for us.

Is there a way I can use RMAN on my local tape drives? Thanks

          Followup:

you would TAR to tape and register that fact with RMAN.

      Rating: 2
tar to
      tape  June 06, 2003
Reviewer:  A reader

"You would TAR to tape and register this fact with RMAN".

1. How do I do this?

2. Would this buy me any advantages, can I use the RMAN features if I do this?

Say I "register" a bunch of tar backups with RMAN. And I want to restore the 5th
backup. How can I use RMAN to restore? RMAN doesnt even know which tape the 5th
backup is on so whats the point of invoking RMAN? I could as well do a 'tar -xvf
/dev/rmt/...'.

In other words, I realize by not using a tape management library I lose out on a
lot of RMAN features, but are there _any_ RMAN features available to me by doing
this 'tar and register with rman?

Thanks

          Followup:

1)the rman docs describe how to register user performed backups

2) you've just describe the workings of -- gasp -- a tape library mgmt system
though! :)

you get the book keeping and that is about it.  the recovery catalog.

      Rating:
      4
Drawbacks  June 06, 2003
Reviewer:
      Jon
Yes, there are downsides.

I have been told by more than one user that it is actually slower to take an
rman incrmental backup than it is to take a full OS backup. This is perfectly
believable because rman has to examine each Oracle block in turn to check the
block SCN and compare it with the SCN from the last rman backup.

Finally, of course, there is the issue that Oracle Corp. suggests that rman
should be given its own instance on an separate machine to hold the catalog --
and that's quite a cost overhead for a product that otherwise looks fairly
suitable for the small to medium office site without the expert DBA.

      Rating:
      5
  June 06, 2003
Reviewer:
      A reader
Jon,

Did you ever consider testing it once before believing what someone told you?
Instead you are coming up with fancy figments of imagination to justify what you
want to believe.

RMAN is much faster than o/s backups simply because of the fact that in RMAN you
don't have to backup the entire file. You backup only the blocks under the file
HWM. For incremental backups, ofcourse you have to do some work to identify the
changed blocks, but the advantage is that you are backing up/recovering from
only the delta change.

Many of the performance problems with RMAN is related to improper setting of
large_pool. Tom can probably elaborate on that. Also remember that you can use
the controlfile as the catalog if you like. The catalog database is only
proposed if you have many instances that are to be managed centrally.

          Followup:

well, it can be slower to take an RMAN incremental in a high activity database.
the OS copy will just read lots, write lots.  the incremental will go block by
block and verify it needs to copy each block (and discover it needs many/most of
them)...  it can be slower (look forward to changes in the near future though,
hint hint)

the incrementals can be slower then a full, unless the target device for the
backup is the bottleneck and a relatively small % of the blocks have changed...

      Rating: 5
rman catalog on
      separate machine  June 25, 2003
Reviewer:
      A reader
A separate instance, for sure, but is a separate machine really necessary? If
frequent backups are taken of the catalog, wouldn't that be sufficient?

          Followup:

if you are using rman, you generally have many databases, hence "another
machine" generally does make sense (as we all know an instance per machine is
best...)

can you get away with a single machine?  sure if you are willing to manually
recover someday without the rman catalog (cause the machine crashed after you
backed up prod, but before you backed up rman)

      Rating: 5
separate
      machine  June 26, 2003
Reviewer:
      A reader
Am I wrong, but can't you recover using the controlfile if you lose the catalog?
Say you backed up prod and the machine crashed before you backed up the catalog.
You'd have to lose both the controlfile of prod and the catalog for the
situation to get sticky, right?

          Followup:

I would be assuming if you lost the rman catalog, you lost the machine -- et al,
all you have are the rman backups and a brand new machine.

I never said "you cannot recover", rather I said "you'll be DIY to a large
degree"

June 26, 2003 Winston -- Thanks for the question regarding "row
      count on your web site", version 9i

      You Asked
Hi Tom,

I have been visiting your web site quite often recently and your service is
GREAT!

I understand that your web site is always returning "Approximately" count and
you have shared with me that you frequently do something like below
select *
  from ( select a.*, rownum r
           from ( select ...., count(*) over () cnt
                     from ...
                    where .... AND ROWNUM <= 500
                   order by ... ) a
          where rownum <= :max )
 where r >= :min;

Recently I did a search by "algebra", it returned me three records, but it said
"algebra} Approximately 7 records found.
"
I wonder how this happen? You must be using different SQL for count(*)

Thanks in advance,
Winston

      and we said...
I don't do that count(*) over () cnt trick - I've told people "it exists, but my
recommendation is DON'T DO IT"

My text search uses Oracle Text.

Oracle Text has a query approximation function -- much like v$sql_plan will tell
you the estimated cardinality of your query after you parse it, Oracle Text will
tell you the estimated cardinality.

I use that function on the home page.  Purely an estimate.
Review &
      Followup

      Rating: 3
Could you show
      how you do that?  June 26, 2003
Reviewer:
      Dave  from San Diego, CA
I'm using oracle text myself and I would like to return an approximation of
number of records found.

how do I do that?

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/text.920/a96517/query.htm#1005672

June 26, 2003 --
      Thanks for the question regarding "Runstats valid for 9.2", version 9.2

      You Asked
Hi,
   Just a quick one here.
1) Is your RUNSTATS routine also valid for 9.2.If not what changes need to be
done.
2) Is is true  Oracle does not use RBS, redo on 9i DB instead UNDO is used.
2) When is Oracle releasing 10i beta version.

Thanx

      and we said...
1) yes it is.

2) undo = rollback, rollback = undo.  There is system managed UNDO accomplished
by creating an undo tablespace.  Here Oracle figures out how many rollback
segments to create, how big they should be.  There is manually managed UNDO
which is accomplished by you doing rollback segment creation as you used to have
to.

3) it already has.
Review & Followup

      Rating: 5
Where can
      ?  May 05, 2003
Reviewer:  A reader

I get more info on those beta releases.When excatly was it released.

Thanx for your quick response.

      Rating: 4
Where can I
      find information on Oracle Database release 10i ?  May 05,
      2003
Reviewer:  Sanjay  from Colorado, USA

I tried to find under products and downloads sections, and I could not find any
information on release 10i. Is there any place where I can find more information
about features and highlights of 10i Beta version of Oracle ?

Thanks.

          Followup:

exactly - 10i has not yet been "announced".  Information about it is officially
available only under NDA at this point

      Rating: 4
Beta released
      under NDA  May 05, 2003
Reviewer:  Mark J.
      Bobak  from Ann Arbor, MI
Tom can confirm, but I'm pretty certain that 10i beta program
participants sign NDAs.  (That's Non-Disclosure Agreements.)

So, you won't hear anything about the details of 10i or it's
features till the official release happens.  (Perhaps at
OpenWorld??)

-Mark

      Rating:
      4
  May 05, 2003
Reviewer:
      A reader
Thanks for the details.

Any approximate beta release date for general public(not NDA)?

          Followup:

it'll be out by the end of the year.

      Rating: 5
runstats
      problem  June 26, 2003
Reviewer:
      A reader
HI Tom
I was trying to use your runstats utility on Oracle
9.0.1.0.0 production (Enterprise Edition) - but I am getting the following error

09:13:17 oracle@ORA901> exec runstats_pkg.rs_start;
BEGIN runstats_pkg.rs_start; END;

*
ERROR at line 1:
ORA-03113: end-of-file on communication channel

09:13:26 oracle@ORA901> spool off

I then ran the select * from stats view in the script
and found it was not able to run to completion - following is the output..Any
ideas? Thank you so much!!

09:12:04 oracle@ORA901> select * from stats;

STAT...logons cumulative
         1

STAT...logons current
         1

STAT...opened cursors cumulative
        19

STAT...opened cursors current
         7

.... some more rows here
......

STAT...OS All other sleep time
         0

ERROR:
ORA-03113: end-of-file on communication channel

240 rows selected.

09:12:12 oracle@ORA901> spool off

          Followup:

can you dig further and see if there are issues with the underlying v$ views
themselves -- can you run the query in the view, or is it the view

you have filed a tar with support right?

      Rating: 5
Re: runstats
      problem  June 27, 2003
Reviewer:
      A reader
Thanx Tom - For your lightening response (How HOW do
you do it!!!?))

Anyways, I am not sure how to dig further. It may be a bug with my 9.0.1.0 - I
have run into some other
problems as well and I think I need to move to 9.0.1.4...

June 27, 2003 wallacel -- Thanks for the question regarding
      "index", version 8.1.6

      You Asked
Tom:

I have a question regarding index. can you explain me in detail
what the following means.

If the index is a concatenation of multiple columns and one
of the columns contains a NULL value, the row will be in the
index column containing the NULL value and will be left empty.

+++++++++++++++++++++++++++++++++++++++++++++
above is exactly what it says in a book. I can't even understand
it's english."the row will be in the index column containing the
NULL value and will be left empty" what does that mean?

      and we said...
I think what they are trying to say is "NULLs are not stored in
b*tree indexes" -- however -- that is not 100% accurate.

It is accurate to say that "if all of the columns in an index
entry would be NULL -- that index entry will not be made.".
That is -- if an index is built on a column with all NULLS in it
-- that index will have zero rows in it.  Consider this example:

ops$tkyte@DEV816> create table t ( x int, y int );
Table created.

ops$tkyte@DEV816> insert into t values ( 1, 1 );
1 row created.

ops$tkyte@DEV816> insert into t values ( 1, null );
1 row created.

ops$tkyte@DEV816> insert into t values ( null, 1 );
1 row created.

ops$tkyte@DEV816> insert into t values ( null, null );
1 row created.

ops$tkyte@DEV816> create index t1_idx on t(x);
Index created.

ops$tkyte@DEV816> create index t2_idx on t(y);
Index created.

ops$tkyte@DEV816> create index t3_idx on t(x,y);
Index created.

ops$tkyte@DEV816> analyze index t1_idx validate structure;
Index analyzed.

ops$tkyte@DEV816> select name, lf_rows from index_stats;

NAME                              LF_ROWS
------------------------------ ----------
T1_IDX                                  2

there are only two entries in this index since 2 out of 4
entries are entirely NULL -- the entirely NULL entries are
skipped

ops$tkyte@DEV816> analyze index t2_idx validate structure;
Index analyzed.

ops$tkyte@DEV816> select name, lf_rows from index_stats;

NAME                              LF_ROWS
------------------------------ ----------
T2_IDX                                  2

Same thing here -- 2 out of 4 are NULL, skip them

ops$tkyte@DEV816> analyze index t3_idx validate structure;
Index analyzed.

ops$tkyte@DEV816> select name, lf_rows from index_stats;

NAME                              LF_ROWS
------------------------------ ----------
T3_IDX                                  3

Here, only 1 of the index entries is entirely NULL and will
not be entered into the index.  The 2 partially NULL entries as
well as the entirely not NULL entry are made though

Review &
      Followup

      Rating: 5
Helpful and
      Informative  August 28, 2001
Reviewer:
      Chander  from India
Thanks Tom .... !

      Rating: 5
great example
      (but why VALIDATE STRUCTURE) ?  June 26, 2003

Reviewer:  Alberto Dell'Era  from Milan, Italy

Have you used ANALYZE VALIDATE STRUCTURE instead of a plain ANALYZE COMPUTE
STATISTICS for any particular reason, or just by chance ?

          Followup:

I wanted index stats to be populated so I could get the info that it contains.

ops$tkyte@ORA920> create index t_idx on t(a);
Index created.

ops$tkyte@ORA920> analyze index t_idx compute statistics;
Index analyzed.

ops$tkyte@ORA920> select * from index_stats;
no rows selected

ops$tkyte@ORA920> analyze index t_idx validate structure;
Index analyzed.

ops$tkyte@ORA920> select * from index_stats;

    HEIGHT     BLOCKS NAME                           PARTITION_NAME
      LF_ROWS    LF_BLKS LF_ROWS_LEN LF_BLK_LEN
---------- ---------- ------------------------------
------------------------------ ---------- ---------- ----------- ----------
   BR_ROWS    BR_BLKS BR_ROWS_LEN BR_BLK_LEN DEL_LF_ROWS DEL_LF_ROWS_LEN
DISTINCT_KEYS MOST_REPEATED_KEY BTREE_SPACE USED_SPACE
---------- ---------- ----------- ---------- ----------- ---------------
------------- ----------------- ----------- ----------
  PCT_USED ROWS_PER_KEY BLKS_GETS_PER_ACCESS   PRE_ROWS PRE_ROWS_LEN
OPT_CMPR_COUNT OPT_CMPR_PCTSAVE
---------- ------------ -------------------- ---------- ------------
-------------- ----------------
         1          8 T_IDX
            2          1          28       7996
         0          0           0          0           0               0
    1                 2        7996         28
         1            2                  2.5          0            0
 0                0

1 row selected.

      Rating: 5
Ok, but also
      COMPUTE STATISTICS seems to do the same  June 26, 2003

Reviewer:  Alberto Dell'Era  from Milan, Italy

(At least in 9.0.1) index_stats *seems* to be populated the same way using
either VALIDATE STRUCTURE or COMPUTE STATISTICS:

SQL> create index xx on t(c);

Index created.

SQL> analyze index xx compute statistics;

Index analyzed.

SQL> select * from index_stats;

         1         16
XX

        20          1         345       3904          0          0           0
         0           0               0             4                17
       3904        345          9            5                    4          0
           0              1               25

SQL> drop index xx;

Index dropped.

SQL>  create index xx on t(c);

Index created.

SQL> analyze index xx validate structure;

Index analyzed.

SQL>  select * from index_stats;

         1         16
XX

        20          1         345       3904          0          0           0
         0           0               0             4                17
       3904        345          9            5                    4          0
           0              1               25

I was just curious to know whether VALIDATE STRUCTURE does something that
COMPUTE STATISTICS does not (actually i was thinking about more precise results,
since "validate" seems stronger than "statistics"). In this case it seems we get
the same output, but VALIDATE (if more precise in general) may be more
appropriate for my Oracle-investigating experiments with sqlplus.

BTW Thanks for the answer in real-time ;-)

          Followup:

my example above was a cut and paste from 9203.

Interesting, but I cannot reproduce, the only thing I can think is

a) you created the index
b) validated structure
c) index_stats was populated
d) drop index
e) create index
f) computed statistics
g) index_stats APPEARED full -- but it was really left over from b)

consider

ops$tkyte@ORA9I> create table t as select * from all_users;
create table t as select * from all_users;

Table created.

ops$tkyte@ORA9I> create index t_idx on t(username);
create index t_idx on t(username);

Index created.

ops$tkyte@ORA9I> analyze index t_idx compute statistics;
analyze index t_idx compute statistics;

Index analyzed.

ops$tkyte@ORA9I> select * from index_stats;
select * from index_stats;

no rows selected

ops$tkyte@ORA9I> select * from v$version;
select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.0.1.2.0 - Production
PL/SQL Release 9.0.1.2.0 - Production
CORE    9.0.1.2.0       Production
TNS for Solaris: Version 9.0.1.2.0 - Production
NLSRTL Version 9.0.1.2.0 - Production

ops$tkyte@ORA9I> analyze index t_idx validate structure;
analyze index t_idx validate structure;

Index analyzed.

ops$tkyte@ORA9I> select * from index_stats;
select * from index_stats;

    HEIGHT     BLOCKS NAME                           PARTITION_NAME
      LF_ROWS    LF_BLKS LF_ROWS_LEN LF_BLK_LEN
---------- ---------- ------------------------------
------------------------------ ---------- ---------- ----------- ----------
   BR_ROWS    BR_BLKS BR_ROWS_LEN BR_BLK_LEN DEL_LF_ROWS DEL_LF_ROWS_LEN
DISTINCT_KEYS MOST_REPEATED_KEY BTREE_SPACE USED_SPACE
---------- ---------- ----------- ---------- ----------- ---------------
------------- ----------------- ----------- ----------
  PCT_USED ROWS_PER_KEY BLKS_GETS_PER_ACCESS   PRE_ROWS PRE_ROWS_LEN
OPT_CMPR_COUNT OPT_CMPR_PCTSAVE
---------- ------------ -------------------- ---------- ------------
-------------- ----------------
         1         64 T_IDX
           91          1        1664       7996
         0          0           0          0           0               0
   91                 1        7996       1664
        21            1                    2          0            0
 0                0

      Rating: 5
What happens
      when we drop the index?  June 26, 2003
Reviewer:
      Kamal Kishore  from New Jersey, USA
Hi Tom,
When we drop the index (after VALIDATE STRUCTURE has been done on it), does the
system keep the entry in the INDEX_STATS for that index (even after it is
dropped)?

My test run below shows that after dropping the index idx_t the index_stats
entry went away but as soon as I re-created the index, the entry in the
INDEX_STATS magically re-appeared, even without a VALIDATE STRUCTURE command
being issued?

IS this entry still kept hidden somewhere even though the index might have been
dropped?
Thanks,

SQL> create table t(x int) ;

Table created.

SQL> create index idx_t on t(x) ;

Index created.

SQL> select * from index_stats ;

no rows selected

SQL> analyze index idx_t validate structure ;

Index analyzed.

SQL> select * from index_stats ;

    HEIGHT     BLOCKS NAME                           PARTITION_NAME
      LF_ROWS
---------- ---------- ------------------------------
------------------------------ ----------
   LF_BLKS LF_ROWS_LEN LF_BLK_LEN    BR_ROWS    BR_BLKS BR_ROWS_LEN BR_BLK_LEN
DEL_LF_ROWS
---------- ----------- ---------- ---------- ---------- ----------- ----------
-----------
DEL_LF_ROWS_LEN DISTINCT_KEYS MOST_REPEATED_KEY BTREE_SPACE USED_SPACE
PCT_USED ROWS_PER_KEY
--------------- ------------- ----------------- ----------- ----------
---------- ------------
BLKS_GETS_PER_ACCESS   PRE_ROWS PRE_ROWS_LEN OPT_CMPR_COUNT OPT_CMPR_PCTSAVE
-------------------- ---------- ------------ -------------- ----------------
         1         16 IDX_T
            0
         1                   3904          0          0           0          0
        0
              0             0                 0        3904
            0
                   1          0            0              0                0

1 row selected.

SQL> drop index idx_t ;

Index dropped.

SQL> select * from index_stats ;

no rows selected

SQL> create index idx_t on t(x) ;

Index created.

SQL> select * from index_stats ;

    HEIGHT     BLOCKS NAME                           PARTITION_NAME
      LF_ROWS
---------- ---------- ------------------------------
------------------------------ ----------
   LF_BLKS LF_ROWS_LEN LF_BLK_LEN    BR_ROWS    BR_BLKS BR_ROWS_LEN BR_BLK_LEN
DEL_LF_ROWS
---------- ----------- ---------- ---------- ---------- ----------- ----------
-----------
DEL_LF_ROWS_LEN DISTINCT_KEYS MOST_REPEATED_KEY BTREE_SPACE USED_SPACE
PCT_USED ROWS_PER_KEY
--------------- ------------- ----------------- ----------- ----------
---------- ------------
BLKS_GETS_PER_ACCESS   PRE_ROWS PRE_ROWS_LEN OPT_CMPR_COUNT OPT_CMPR_PCTSAVE
-------------------- ---------- ------------ -------------- ----------------
         1         16 IDX_T
            0
         1                   3904          0          0           0          0
        0
              0             0                 0        3904
            0
                   1          0            0              0                0

1 row selected.

SQL> disconnect
Disconnected from Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production
SQL>

          Followup:

index stats is just a little "in memory thing" that only has one row and is only
visible in your session.  You are seeing a "quirk" there.

      Rating: 2
very
      strange..  June 26, 2003
Reviewer:  DD
       from MN, USA
Tom,
  I tried what Kamal did and surprisingly the stats can be seen not only when
the index is recreated but even if it is created on another column but with the
same name we see the same stats.. what is the logic behind it..
i m using 9iR2 on RHL 8.0...

Thanks

          Followup:

just a quirk, nothing I would get very excited about.  index stats contains one
row at a time.  It contains the outcome of the last validate structure.  it goes
away when you exit your session.

      Rating: 5
analyze ..
      validate structure online; DOES NOT work ...  December 23,
      2003
Reviewer:  Arindom  from Boston, USA
Tried analyze ... validate structure online; on 9.2.0.1.0.
Doesn't populate index_stats ; so I guess there's isin't much in Oracle's claim
about this enhancement (not taking out a lock on the index )

June 27, 2003 Amer -- Thanks for the question regarding
      "Intelligent Partitioning Table", version 8.1.7

      You Asked
Your help is always the best. Please repeat it this time

I want to create a table with 12 partition (for each month) then within each
month's data should be categories based on a column(CityId). The CityId will
have 5 possible values (107,108,109,110,111). I know that Composite partition is
more closer solution. I have done this but my values are stored in one
sub-partition. Rest of sub partition is empty. But when I give higher value of
column like 300,400. Other partitions start filling. How can I control this. I
want to populate one city Id per column. Please help

Amer

      and we said...
You cannot control the hash subpartions to that degree.

The hash function (as do all hash functions) work best with a LARGE number of
values.  Using hash subpartitions with a partition key that has only 5 values is
not very useful (as you have discovered).  Unless you are really really lucky --
one or more of your partitions will be empty.

You should use a hash partition key in this case that is almost unique if you
want to spread the data out.

Even in 9i when LIST partitions are added -- they will not help you achieve your
specific goal in this case as they cannot be used for subpartitions (only hash
can be combined with range to do composite) and list partitioning does not
support a composite partition key (as yours would be -- date + cityid).

So, in short, you would have to use range partitions with a composite key to
satisfy your goal right now.  For example, using the values you have above -- to
partition for the month of OCT-2001, you would have partition keys:

( to_date( '01-NOV-2001' ), 108 )   (would get octobers cityid = 107)
( to_date( '01-NOV-2001' ), 109 )   (would get octobers cityid = 108)
( to_date( '01-NOV-2001' ), 110 )   (would get octobers cityid = 109)
( to_date( '01-NOV-2001' ), 111 )   (would get octobers cityid = 110)
( to_date( '01-NOV-2001' ), 112 )   (would get octobers cityid = 111)

Review &
      Followup

      Rating:
      5
OK  October 15, 2001
Reviewer:  Mirza
      Amer Baig
That's okay. I think the last solution is the only option available. Thanks for
your support. You are my best resource.

Thanks
Amer

      Rating:
      4
  May 15, 2002
Reviewer:  Roland
      Wallner  from Norfolk, VA USA
This was what I've been looking for... I think?  This looks like multicolumn
partition key, but you said composite partition?  If it's a mulicolumn, wouldn't
any insert for the month of OCT go into the first partition, since it's less the
the bound?

If it's a composite, how do you get each country code to hash to the proper
location?

Thanks

          Followup:

It is a multi-column partition key.

I said to use a composit KEY in a range partition.

The row would go into the partition is was specified to go into.  I cannot say
if OCT would go into the first partition since we never really said where each
month goes.

My response was to NOT USE hashing at all -- but rather a "multi-column
partition key" that was range partitioned by DATE and CITY_CODE

      Rating:
      4
  May 16, 2002
Reviewer:  Roland
      Wallner  from Norfolk, VA USA
Thanks Tom!  We've been trying to implement a multicolumn key partition and been
having some trouble.

Right now we have partitions based on Month, so each months transactions would
go into a separate partition. The partitions use the Date as the key. With one
client type this hasn't been problem.

Now we are introducing another client type, so we want to put another key along
with the date... client_id.

Our example...
Create Table P_main_Test
 (
   Trans_ID     number       Not Null,
   Client_ID    number(4)    Not Null,
   P_Date       date         Not Null,
   f1           varchar2(10),
   f2           varchar2(10),
   f3           varchar2(10)
)
storage(initial 1m next 1m) tablespace users
partition by range(P_Date,client_ID)
(
    partition G_may99 values less than
(to_date('01-JUN-1999','DD-MON-YYYY'),4999)
      TABLESPACE users,
    partition C_may99 values less than
(to_date('01-JUN-1999','DD-MON-YYYY'),9999)
     TABLESPACE users,
    partition G_jun99 values less than
(to_date('01-JUL-1999','DD-MON-YYYY'),4999)
      TABLESPACE users,
    partition C_jun99 values less than
(to_date('01-JUL-1999','DD-MON-YYYY'),9999)
      TABLESPACE users,
    partition G_jul99 values less than
(to_date('01-AUG-1999','DD-MON-YYYY'),4999)
      TABLESPACE users,
    partition C_jul99 values less than
(to_date('01-AUG-1999','DD-MON-YYYY'),9999)
      TABLESPACE users
);

Sample Insert....

insert into P_Main_Test
  (Trans_id, P_date, client_id,f1,f2,f3)
values
  (1, '02-MAY-1999',4000,'aaa','bbb','ccc');
REM
insert into P_Main_Test
  (Trans_id, P_date, client_id,f1,f2,f3)
values
  (1, '02-MAY-1999',6000,'aaa','bbb','ccc');
insert into P_Main_Test
  (Trans_id, P_date, client_id,f1,f2,f3)
values
  (1, '01-JUN-1999',4000,'aaa','bbb','ccc');
REM
insert into P_Main_Test
  (Trans_id, P_date, client_id,f1,f2,f3)
values
  (1, '01-JUN-1999',6000,'aaa','bbb','ccc');

sample selects...

 select * from P_Main_Test partition(G_MAY99)
  2  /

  TRANS_ID  CLIENT_ID P_DATE    F1         F2         F3
---------- ---------- --------- ---------- ---------- ----------
         1       4000 02-MAY-99 aaa        bbb        ccc
         1       6000 02-MAY-99 aaa        bbb        ccc
         1       4000 01-JUN-99 aaa        bbb        ccc

SQL> Prompt C - May 99
C - May 99
SQL> select * from P_Main_Test partition(C_MAY99)
  2  /

  TRANS_ID  CLIENT_ID P_DATE    F1         F2         F3
---------- ---------- --------- ---------- ---------- ----------
         1       6000 01-JUN-99 aaa        bbb        ccc

I understand why, based on the Vector discussions, the both client id's are in
G_MAY99 and why 1 June is in G_MAY99...  I'm just trying to figure out a way to
make this work...

Thanks again

          Followup:

I don't think you can do it with your case (so many values for client_id).

In their case, they had 5 city ids.  They could set up a range like:

partition c107_0102 values less than (107,to_date('01-feb-2002','DD-MON-YYYY')),
partition c107_0202 values less than (107,to_date('01-mar-2002','DD-MON-YYYY')),
partition c107_0302 values less than (107,to_date('01-apr-2002','DD-MON-YYYY')),
partition c107_0402 values less than (107,to_date('01-may-2002','DD-MON-YYYY')),
...
partition c107_1202 values less than (107,to_date('01-jan-2003','DD-MON-YYYY')),
partition c108_0102 values less than (108,to_date('01-feb-2002','DD-MON-YYYY')),
partition c108_0202 values less than (108,to_date('01-mar-2002','DD-MON-YYYY')),
partition c108_0302 values less than (108,to_date('01-apr-2002','DD-MON-YYYY')),
partition c108_0402 values less than (108,to_date('01-may-2002','DD-MON-YYYY')),
...
partition c108_1202 values less than (108,to_date('01-jan-2003','DD-MON-YYYY')),
......

since they have 5 discrete values -- doing this is feasible.

You have 10,000 values, not feasible.  (see they can have 12 partitions for
city_id = 107, 12 for city_id = 108 and so on.  You would have to have N
partitions for cust_id = 1, N partitions for cust_id = 2 and so on.

In your case, recommand range partitioning by P_DATE and hash subpartitioning by
client_id (client_id has a big set of values -- 1..10000 -- hence it'll hash
evenly into two buckets very nicely.  You'll achieve what you in the end.

      Rating: 4
Similiar
      issue  September 09, 2002
Reviewer:  Rob
       from Pittsburgh, PA
Tom:

I have a similiar issue to the second follow-up in this thread.

I have a table that is partitioned by week (52 partitions). Many of the queries
against this table have a where clause whereby they select a group of stores but
don't have a reference to week in the where clause. Each query then has to scan
every partition to get the results. I was thinking of subpartitioning the table
on the store column (several thousand unique values in the store column) with 8
or 16 subpartitions. My hope is that for a query with a range of store values in
the where clause it will only have to scan a limited number of subpartitions for
each week partition, thereby cutting down on the IO. For other reasons I don't
want to have an index on the store column.

What is the best way to determine if I am getting partition elimination with
subpartitions. I have looked at @$ORACLE_HOME/rdbms/admin/utlxpls.sql, but I am
not clear on the meaning. Here is an example:

Plan Table

--------------------------------------------------------------------------------

| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |

--------------------------------------------------------------------------------

| SELECT STATEMENT          |          |    31 |    3K| 773308 |       |       |

|  PARTITION RANGE INLIST   |          |       |      |        |KEY(I) |KEY(I) |

|   PARTITION HASH ALL      |          |       |      |        |     1 |     8 |

|    TABLE ACCESS FULL      |TDS_QCF   |    31 |    3K| 773308 |KEY(I) |KEY(I) |

--------------------------------------------------------------------------------

Does partition range inlist  KEY(I) KEY(I) mean that it will read a limited
number of partitions, and then within those partitions, the partition hash all
Pstart and Pstop indicate the subpartitions for each partition that will be
accessed since the same value of store will hash to the same subpartition within
each partition, for example store_id 404 would hash to subpartition 3 within
each partition.

Rob

          Followup:

Well, with a comment like "My hope is that for a query with a range of store
values in the where clause it will only have to scan a limited number of
subpartitions for each week partition", you can stop.  HASH partitions cannot do
"range" eliminations.

So, if you do "where store between A and B" -- no joy.

If you do "where store = A or store = B" -- you will have joy.

The above query plan shows partition elimination kicking in.  I would want to
see the query to diagnose the plan normally

      Rating: 4
Some addl.
      info  September 09, 2002
Reviewer:  Rob
       from Pittsburgh, PA
Tom:

I have been doing some more thinking about this composite partitioned table. It
seems like the docs recommend hash partitioning on something close to a unique
key. While I have about 3000 unique values for the store_id column, the table
contains 1.2 billion rows.

I am trying to decide how many subpartitions to specify. My thinking is that the
more subpartitions there are the less IO there will be in query that has a where
clause like
where store_id in (23,56,980) since it may be that all 3 of these values hash to
a different subpartition, each subpartition will be smaller if I choose to
create many subpartitions. On the other hand, is there a lot of overhead to
having several thousand subpartitions. I now find that there may be 120 weeks of
data therefore 120 partitions and if I have 32 subpartitions that would be a
total of 3840 subpartitions.

What do you think of the package dbms_pclxutil for rebuilding partitioned
indexes in parallel. Have you used it?

          Followup:

And with only 3000 values, it could well be that some of those 32 subpartitions
could be empty -- depends on how well your stores hash (should probably be OK)

4000 partitions would be doable.  It'll take a couple of minutes for the create
table to go...

That package works just fine.  It basically creates the index unusable and then
lets you bulid the index subpartitions a bit at a time using dbms_jobs in the
background.  You could achieve the same yourself manually if you choose (just
fire off a couple of "alter index rebuilds online" for the index partitions
simultaneously)

      Rating:
      4
  January 03, 2003
Reviewer:
      A reader  from Switzerland
Hi Tom

When I execute package dbms_pclxutil I get an Oracle-error:

PROCEDURE dbms_pclxutil.build_part_index
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 JOBS_PER_BATCH                 NUMBER                  IN     DEFAULT
 PROCS_PER_JOB                  NUMBER                  IN     DEFAULT
 TAB_NAME                       VARCHAR2                IN     DEFAULT
 IDX_NAME                       VARCHAR2                IN     DEFAULT
 FORCE_OPT                      BOOLEAN                 IN     DEFAULT

exec dbms_pclxutil.build_part_index(1,4,'PART_TABLE','PART_INDEX')
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at "SYS.DBMS_PCLXUTIL", line 50
ORA-06512: at line 1

Because package body is wrapped and no help on Metalink is present I cannot see
the problem on line 50. Please advice. Thanks.

          Followup:

can you give me the entire schema necessary to reproduce this?  versions, OS,
etc appreciated as well.

      Rating:
      4
  January 03, 2003
Reviewer:  Marc
       from Switzerland
Thanks for extreme fast feedback. We have Oracle Version 8.1.7.4.0 - 64bit
Production on Sun Solaris 2.8 with 4 CPU's and 16 GB RAM. The table part_table
has 4 partitions and two indexes. I also have tried DBMS_PCLXUTIL for other
partitioned tables with the same problem.
Kind Regards.

          Followup:

give me the create table and everything please.

      Rating:
      4
  January 03, 2003
Reviewer:  Marc
       from Switzerland
Here is my test script:

CREATE TABLE PART_TABLE
 (DAT_PER DATE NOT NULL
 ,CIFNR VARCHAR2(12) NOT NULL
 ,WERTEART VARCHAR2(5) NOT NULL
 ,WERT NUMBER(15,2) NOT NULL
 )
 TABLESPACE USERS
 PARTITION BY RANGE (DAT_PER)
 (PARTITION PART_TABLE_2002_M01 VALUES LESS THAN
(TO_DATE('01.02.2002','DD.MM.YYYY'))
 ,PARTITION PART_TABLE_2002_M02 VALUES LESS THAN
(TO_DATE('01.03.2002','DD.MM.YYYY'))
 ,PARTITION PART_TABLE_2002_M03 VALUES LESS THAN
(TO_DATE('01.04.2002','DD.MM.YYYY'))
 ,PARTITION PART_TABLE_2002_M04 VALUES LESS THAN
(TO_DATE('01.05.2002','DD.MM.YYYY'))
 ,PARTITION PART_TABLE_2002_M05 VALUES LESS THAN
(TO_DATE('01.06.2002','DD.MM.YYYY'))
 ,PARTITION PART_TABLE_2002_M06 VALUES LESS THAN
(TO_DATE('01.07.2002','DD.MM.YYYY'))
 ,PARTITION PART_TABLE_2002_M07 VALUES LESS THAN
(TO_DATE('01.08.2002','DD.MM.YYYY'))
 ,PARTITION PART_TABLE_2002_M08 VALUES LESS THAN
(TO_DATE('01.09.2002','DD.MM.YYYY'))
 ,PARTITION PART_TABLE_2002_M09 VALUES LESS THAN
(TO_DATE('01.10.2002','DD.MM.YYYY'))
 ,PARTITION PART_TABLE_2002_M10 VALUES LESS THAN
(TO_DATE('01.11.2002','DD.MM.YYYY'))
 ,PARTITION PART_TABLE_2002_M11 VALUES LESS THAN
(TO_DATE('01.12.2002','DD.MM.YYYY'))
 ,PARTITION PART_TABLE_2002_M12 VALUES LESS THAN
(TO_DATE('01.01.2003','DD.MM.YYYY'))
 );

CREATE INDEX PART_INDEX ON PART_TABLE
 (WERTEART)
 TABLESPACE USERS
 LOCAL
 UNUSABLE;

execute dbms_pclxutil.build_part_index(1,4,'PART_TABLE','PART_INDEX')

Thanks for your investigations.

          Followup:

just for grins -- what are your NLS/character set settings?

      Rating:
      4
  January 03, 2003
Reviewer:  Marc
       from Switzerland
SQL> select * from NLS_SESSION_PARAMETERS order by 1;

PARAMETER                      VALUE
------------------------------ ----------------------------------------
NLS_CALENDAR                   GREGORIAN
NLS_COMP                       BINARY
NLS_CURRENCY                   $
NLS_DATE_FORMAT                DD-MON-YYYY
NLS_DATE_LANGUAGE              AMERICAN
NLS_DUAL_CURRENCY              $
NLS_ISO_CURRENCY               AMERICA
NLS_LANGUAGE                   AMERICAN
NLS_NUMERIC_CHARACTERS         .,
NLS_SORT                       BINARY
NLS_TERRITORY                  AMERICA
NLS_TIMESTAMP_FORMAT           DD-MON-RR HH.MI.SSXFF AM
NLS_TIMESTAMP_TZ_FORMAT        DD-MON-RR HH.MI.SSXFF AM TZH:TZM
NLS_TIME_FORMAT                HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT             HH.MI.SSXFF AM TZH:TZM

SQL> select * from NLS_DATABASE_PARAMETERS order by 1;

PARAMETER                      VALUE
------------------------------ ----------------------------------------
NLS_CALENDAR                   GREGORIAN
NLS_CHARACTERSET               WE8ISO8859P1
NLS_COMP                       BINARY
NLS_CURRENCY                   $
NLS_DATE_FORMAT                DD-MON-YYYY
NLS_DATE_LANGUAGE              AMERICAN
NLS_DUAL_CURRENCY              $
NLS_ISO_CURRENCY               AMERICA
NLS_LANGUAGE                   AMERICAN
NLS_NCHAR_CHARACTERSET         WE8ISO8859P1
NLS_NUMERIC_CHARACTERS         .,
NLS_RDBMS_VERSION              8.1.7.4.0
NLS_SORT                       BINARY
NLS_TERRITORY                  AMERICA
NLS_TIMESTAMP_FORMAT           DD-MON-RR HH.MI.SSXFF AM
NLS_TIMESTAMP_TZ_FORMAT        DD-MON-RR HH.MI.SSXFF AM TZH:TZM
NLS_TIME_FORMAT                HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT             HH.MI.SSXFF AM TZH:TZM

          Followup:

sorry -- tried everything I could to reproduce, I cannot.  This looks like a
"new problem" -- I'll have to refer you to support -- they can help set some
events to see what is happening.

      Rating:
      2
  January 04, 2003
Reviewer:
      A reader
Thank you for feedback. I'll contact Oracle Support.

      Rating:
      4
subpartitions  February 05, 2003
Reviewer:
      umesh  from bangalore, india
why is my insert inserting 3 rows here please explain

  1  CREATE TABLE MYEMP
  2  (EMPNO NUMBER,JOB VARCHAR2(20), DEPTNO NUMBER)
  3  PARTITION BY RANGE (DEPTNO)
  4  SUBPARTITION BY HASH (JOB) SUBPARTITIONS 4
  5  ( PARTITION P1 VALUES LESS THAN (11),
  6   PARTITION P2 VALUES LESS THAN (21),
  7   PARTITION P3 VALUES LESS THAN (31),
  8*  PARTITION P4 VALUES LESS THAN (MAXVALUE))
scott> /

Table created.
scott> INSERT INTO MYEMP
  2  SELECT &EMPNO,'&ENAME',&DEPTNO FROM DUAL;
Enter value for empno: 1
Enter value for ename: MANAGER
Enter value for deptno: 10
old   2: SELECT &EMPNO,'&ENAME',&DEPTNO FROM DUAL
new   2: SELECT 1,'MANAGER',10 FROM DUAL

3 rows created.

scott> /
Enter value for empno: 2
Enter value for ename: CLARK
Enter value for deptno: 20
old   2: SELECT &EMPNO,'&ENAME',&DEPTNO FROM DUAL
new   2: SELECT 2,'CLARK',20 FROM DUAL

3 rows created.
scott> SELECT * FROM MYEMP;

     EMPNO JOB                      DEPTNO
---------- -------------------- ----------
         1 MANAGER                      10
         1 MANAGER                      10
         1 MANAGER                      10
         2 CLARK                        20
         2 CLARK                        20
         2 CLARK                        20

6 rows selected.

scott>
scott> select trigger_name from user_triggers
  2  where table_name = upper('myemp');

no rows selected

          Followup:

you have 3 rows in dual.  dual is magical, dual is a SYS owned object.  You have
allowed someone to manually modify a SYS owned table which is not allowable.

ops$tkyte@ORA817DEV> create table t ( x int );

Table created.

ops$tkyte@ORA817DEV> insert into dual values ( 'x' );

1 row created.

ops$tkyte@ORA817DEV> insert into dual values ( 'x' );

1 row created.

ops$tkyte@ORA817DEV> select * from dual;

D
-
x

ops$tkyte@ORA817DEV> select count(*) from dual;

  COUNT(*)
----------
         3

ops$tkyte@ORA817DEV> insert into t select rownum from dual;

3 rows created.

ops$tkyte@ORA817DEV> select * from t;

         X
----------
         1
         2
         3

ops$tkyte@ORA817DEV> rollback;

Rollback complete.

correct your DUAL table by:

delete from dual where rownum <= 2;
commit;

      Rating: 4
How many values
      for subpartition  June 26, 2003
Reviewer:  Scott
      Wesley  from Perth, Australia
G'day Tom.

You say has subpartitions work best with a LARGE amount of subpartitions.
Please define large.
A few Metalink notes I've read discussing composite partitioning (eg: Note
125314.1) use 8 subpartitions, and recommend using a number to the power of 2.
(2,4,8,16 etc).

We are proposing partitioning a sales table by financial year, and
subpartitioning by state (9 possible values in Australia), however your comments
suggest that these subpartitions may not be used.

Your comments would be appreciated.

Thanks.

          Followup:

sorry, it was not clear.

It is not that you want/need LARGE numbers of subpartitions but rather that:

....
The hash function (as do all hash functions) work best with a LARGE number of
values.  Using hash subpartitions with a partition key that has only 5 values is

not very useful ....

You want a column with a large number of distinct values (a unique column works
best) to be the column you hash on.  If you hash into 8 subpartitions using a
key with 12 distinct values, you may well find that many of your subpartitions
are *empty* since nothing will hash there.

The hash key should have a large domain of values so as to achieve a good
distribution across the partititions

June 27, 2003 --
      Thanks for the question regarding "Recovery!!!", version 8.1.7

      You Asked
Tom,

Consider this scenario, database is in archive mode.

1.Full Exp was taken at 9.00 PM
2.Hot Backup was taken at 11.30PM.

The next day at 10 AM,lets say one of the file gets corrupted, and we  realized
that our hot backup (tape) is gone bad. What we have in our hand is export(full)
backup and archived logs.What are the steps that you recommend so that we loose,
less data as possible.

Thanks.
Justin

      and we said...
well, of course you just simply go back to the prior or prior prior backup since
everyone knows you have to keep at least 3 or more of your backups on hand in
case something like this happens.  You never never overwrite your last backup
(so by definition you have AT LEAST TWO) and prudent DBA's would keep 3 or more.

So, in order to lose nothing, just go to the other backups.

If they are all botches, well, then it is time to print the resume before they
kick you out ;)

If you lost them all but have the export, you can only import that data AS OF
the point in time of the export.  those archives are useless, just bits in a
bucket.  Maybe you could mine them using logminer to resurect many of the
modifications but good luck with that in real life.
Review &
      Followup

      Rating: 5
Number of
      backups to keep.  June 27, 2003
Reviewer:  Angus
      McTavish aged 75  from Cumbria
I agree with you about multiple copies of backups.
However, we do hot backups every day to disk (overwriting the previous day's
copy). But the whole database is mirrored, the hot backup files are mirrored, we
have a hot standby 1000 miles away (it would need to be a REALLY bad fire to get
the primary and the standby databases), we have a read-only snapshot copy of the
main database on another server. Do you still think that we should keep more
than one copy of the hot backup?

          Followup:

what if there were a block corruption that you did not detect for 2 or 3 days?
you have successfully backed it up everywhere and do not have a good copy that
doesn't have it...

If you cannot afford to lose data, your setup is not sufficient.  the read only
snapshots are async (behind).  If you crash and burned during a backup, you
would lose data.

Granted, chances are small.  Unless you are so huge as to make it physically
inpractical, multiple backups are a good idea.

      Rating:
      5
  June 27, 2003
Reviewer:
      A reader
Great!!!

June 27, 2003 Mark -- Thanks for the question regarding
      "INTERPRETING "Rows" in TKPROF output", version 8.0.6.3

      You Asked
What does the "Rows" output in TKPROF actually give you? Is
it an estimate or the actual rows accessed in the operation
or the exact total?
Perhaps I am confused about the order of operations in a
Nested LOOP.  For example

SELECT SUM(DECODE(TH.TSF_TYPE,'SA',NVL(TD.ALLOCATED_QTY,0),NVL(TD.TSF_QTY,0))
  - NVL(TD.SHIP_QTY,0) ) TSF_RESERVED_QTY
FROM
 TSFDETAIL TD,TSFHEAD TH  WHERE TH.TSF_NO = TD.TSF_NO  AND TH.STATUS IN ( 'A',
  'E','I','S'  ) AND TD.SKU = :b1  AND TH.FROM_LOC_TYPE = :b2  AND
  TH.FROM_LOC = :b3  AND DECODE(TH.TSF_TYPE,'SA',NVL(ALLOCATED_QTY,0),
  NVL(TSF_QTY,0)) > NVL(TD.SHIP_QTY,0)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute   4000      1.24       1.10          0          0          0           0
Fetch     8000      7.35      65.03       5561      97862          0        4000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    12001      8.59      66.14       5561      97862          0        4000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 695  (RMSPRD80)   (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
    296  SORT AGGREGATE
    296   NESTED LOOPS
  28200    TABLE ACCESS BY INDEX ROWID TSFDETAIL
  32200     INDEX RANGE SCAN (object id 65255)
  28200    TABLE ACCESS BY INDEX ROWID TSFHEAD
  28200     INDEX UNIQUE SCAN (object id 64654)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
    296   SORT (AGGREGATE)
    296    NESTED LOOPS
  28200     TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF
                'TSFDETAIL'
  32200      INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'TSFDETAIL_I2'
                 (NON-UNIQUE)
  28200     TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF 'TSFHEAD'
  28200      INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF 'PK_TSFHEAD'
                 (UNIQUE)

There are two tables TSFHEAD and TSFDETAIL.
TSFHEAD has 161561 rows and the statistics are current
TSFDETAIL has 1112087 rows and the stats are current.
What I'm confused about is that the TSFHEAD table is accessed after
the rows from the TSFDETAIL table yet the TSFHEAD
table is accessed by the unique index (TSF_NO).  At this point my
understanding is that there is no selective criteria to access the
index(The join would occur during the NESTED LOOPS operation)
So shouldn't the "Rows" include all the rows in the index
for each execution or the query?  Are the steps in the explain plan
not accessed in this order?

1) INDEX RANGE SCAN of TSFDETAIL_I2 (which is non unique if SKU)
2) TABLE ACCESS (by index row_id) of TSFDETAIL
3) INDEX ACCESS of PK_TSFHEAD
4) TABLE ACCESS by index rowid of TSFHEAD
5) Each row in step 2 checked against each row in step 4
6) rows from step 5 and sorted and summed
???????

      and we said...
It depends on the version (the rows number) as to what it represents.  Here is a
short snippet from my book on this subject that'll help you interpret it:

...
showing that I ran it. The last section of the TKPROF report for this query is
the query plan. The query plan that appears by default is shown below:

Rows     Row Source Operation
-------  ---------------------------------------------------
     15  SORT GROUP BY
  21792   FILTER
  21932    NESTED LOOPS
     46     TABLE ACCESS FULL USER$
  21976     TABLE ACCESS BY INDEX ROWID OBJ$
  21976      INDEX RANGE SCAN (object id 34)
      1    FIXED TABLE FULL X$KZSPR
      1    FIXED TABLE FULL X$KZSPR
      0    FIXED TABLE FULL X$KZSPR
      0    FIXED TABLE FULL X$KZSPR
      1    FIXED TABLE FULL X$KZSPR
      1    FIXED TABLE FULL X$KZSPR
      1    FIXED TABLE FULL X$KZSPR
      1    FIXED TABLE FULL X$KZSPR
      1    FIXED TABLE FULL X$KZSPR
      1    FIXED TABLE FULL X$KZSPR
      1    FIXED TABLE FULL X$KZSPR
      1    FIXED TABLE FULL X$KZSPR
      1    FIXED TABLE FULL X$KZSPR
      1    FIXED TABLE FULL X$KZSPR
  11777    NESTED LOOPS
  30159     FIXED TABLE FULL X$KZSRO
  28971     TABLE ACCESS BY INDEX ROWID OBJAUTH$
  28973      INDEX RANGE SCAN (object id 101)
    631    TABLE ACCESS BY INDEX ROWID IND$
    654     INDEX UNIQUE SCAN (object id 36)

This is the actual query plan that was used by Oracle at run-time. The
interesting thing about this plan is that the rows that flow through each step
of the plan are visible. We can see for example that 28971 rows were fetched
from OBJAUTH$. These counts are the row counts of the rows flowing out of that
step of the execution plan (after any predicates that could be applied where
applied to OBJAUTH$, it sent 28,971 onto the next step in the plan).  In
Oracle8.0 and before, this count was the count of rows inspected by that phase
of the execution plan (the number of rows flowing into this step).  For example,
if 50,000 rows were considered in OBJAUTH$ but some where clause was used to
exclude them, a TKPROF report from 8.0 would have reported 50,000 instead.
Using this sort of information, you can see what steps you might want to avoid
in a query and either record the query or use HINTS to come up with a more
desirable plan.
.......

The way this plan was processed:

Rows     Row Source Operation
-------  ---------------------------------------------------
    296  SORT AGGREGATE
    296   NESTED LOOPS
  28200    TABLE ACCESS BY INDEX ROWID TSFDETAIL
  32200     INDEX RANGE SCAN (object id 65255)
  28200    TABLE ACCESS BY INDEX ROWID TSFHEAD
  28200     INDEX UNIQUE SCAN (object id 64654)

is as follows:

1) the index TSFDETAIL_I2 was used to find rows in TSFDETAIL.  This index
returned 32,200 candidate rows (since you are 8.0, it is showing we processed
32,200 in the index.

2) we took all of the rows found in the index and when to the table TSFDETAIL.
Further application of the predicate (where clause) to these rows resulted in a
paring down of the result set from 32,200 rows to 28,200 rows.  We now have the
primary key of TSFHEAD available to us for each of these 28,200 rows.

3) we used the primary key index on TSFHEAD based on the foreign key we have in
TSFDETAILS...

4) and then accessed the TSFHEAD table.

and then aggregated the results.
Review &
      Followup

      Rating: 3
Rows and
      TKPROF  May 20, 2002
Reviewer:  Mark Swanson
       from Mpls, MN
Tom your explanation of the rows was helpful. The distinction in reporting
between versions was excellent.

      Rating: 4
Interpreting
      explain plan  June 27, 2003
Reviewer:
      A reader
tom,

here is one explain plan, where shud i start interpreting it from.... can u
please interpret this how and which lines are processed with the flow...

the above eplan was simple .. but this one makes me confused ...

Thank you very much

SQL>select count(*)
  2           from lxBO_ff8723cb t
  3           where t.lxType in (1056392609)
  4             and t.lxOid in (
  5                             select lxOid from lxString_ff8723cb where
lxType=849438949 and lxVal='175-987812')
  6             and t.lxOid in (
  7                             select lxOid from lxString_ff8723cb where
lxType=-215232983 and lxVal='810830627')
  8             and t.lxOid in (
  9                             select lxOid from lxString_ff8723cb where
lxType=-1923205007 and lxVal='S0830627')
 10            and t.lxName like 'Orders*_%' escape '*'
 11            and t.lxRev=1;
Elapsed: 00:00:00.90

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=30 Card=1 Bytes=79)
   1    0   SORT (AGGREGATE)
   2    1     NESTED LOOPS (Cost=30 Card=1 Bytes=79)
   3    2       HASH JOIN (Cost=22 Card=1 Bytes=66)
   4    3         NESTED LOOPS (Cost=14 Card=2 Bytes=106)
   5    4           VIEW OF 'VW_NSO_3' (Cost=8 Card=2 Bytes=26)
   6    5             SORT (UNIQUE) (Cost=8 Card=2 Bytes=48)
   7    6               TABLE ACCESS (BY INDEX ROWID) OF 'LXSTRING_FF8723CB'
(Cost=5 Card=2 Bytes=48)
   8    7                 INDEX (RANGE SCAN) OF 'LXSTRING_FF8723CB_LXTYPE_INDEX'
(NON-UNIQUE) (Cost=4 Card=2)
   9    4           TABLE ACCESS (BY INDEX ROWID) OF 'LXBO_FF8723CB' (Cost=3
Card=1372 Bytes=54880)
  10    9             INDEX (RANGE SCAN) OF 'LXBO_FF8723CB_LXOID_INDEX'
(NON-UNIQUE) (Cost=2 Card=1372)
  11    3         VIEW OF 'VW_NSO_2' (Cost=8 Card=2 Bytes=26)
  12   11           SORT (UNIQUE) (Cost=8 Card=2 Bytes=48)
  13   12             TABLE ACCESS (BY INDEX ROWID) OF 'LXSTRING_FF8723CB'
(Cost=5 Card=2 Bytes=48)
  14   13               INDEX (RANGE SCAN) OF 'LXSTRING_FF8723CB_LXTYPE_INDEX'
(NON-UNIQUE) (Cost=4 Card=2)
  15    2       VIEW OF 'VW_NSO_1'
  16   15         SORT (UNIQUE) (Cost=8 Card=2 Bytes=48)
  17   16           TABLE ACCESS (BY INDEX ROWID) OF 'LXSTRING_FF8723CB' (Cost=5
Card=2 Bytes=48)
  18   17             INDEX (RANGE SCAN) OF 'LXSTRING_FF8723CB_LXTYPE_INDEX'
(NON-UNIQUE) (Cost=4 Card=2)Execution Plan
----------------------------------------------------------
0      SELECT STATEMENT Optimizer=CHOOSE (Cost=30 Card=1 Bytes=79)
1    0   SORT (AGGREGATE)
2    1     NESTED LOOPS (Cost=30 Card=1 Bytes=79)
3    2       HASH JOIN (Cost=22 Card=1 Bytes=66)
4    3         NESTED LOOPS (Cost=14 Card=2 Bytes=106)
5    4           VIEW OF 'VW_NSO_3' (Cost=8 Card=2 Bytes=26)
6    5             SORT (UNIQUE) (Cost=8 Card=2 Bytes=48)
7    6               TABLE ACCESS (BY INDEX ROWID) OF 'LXSTRING_FF8723CB'
(Cost=5 Card=2 Bytes=48)
8    7                 INDEX (RANGE SCAN) OF 'LXSTRING_FF8723CB_LXTYPE_INDEX'
(NON-UNIQUE) (Cost=4 Card=2)
9    4           TABLE ACCESS (BY INDEX ROWID) OF 'LXBO_FF8723CB' (Cost=3
Card=1372 Bytes=54880)
10    9             INDEX (RANGE SCAN) OF 'LXBO_FF8723CB_LXOID_INDEX'
(NON-UNIQUE) (Cost=2 Card=1372)
11    3         VIEW OF 'VW_NSO_2' (Cost=8 Card=2 Bytes=26)
12   11           SORT (UNIQUE) (Cost=8 Card=2 Bytes=48)
13   12             TABLE ACCESS (BY INDEX ROWID) OF 'LXSTRING_FF8723CB' (Cost=5
Card=2 Bytes=48)
14   13               INDEX (RANGE SCAN) OF 'LXSTRING_FF8723CB_LXTYPE_INDEX'
(NON-UNIQUE) (Cost=4 Card=2)
15    2       VIEW OF 'VW_NSO_1'
16   15         SORT (UNIQUE) (Cost=8 Card=2 Bytes=48)
17   16           TABLE ACCESS (BY INDEX ROWID) OF 'LXSTRING_FF8723CB' (Cost=5
Card=2 Bytes=48)
18   17             INDEX (RANGE SCAN) OF 'LXSTRING_FF8723CB_LXTYPE_INDEX'
(NON-UNIQUE) (Cost=4 Card=2)

          Followup:

read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:231814117467#7344298017927

      Rating: 4
sorry twice i
      copied the execution plan ....  June 27, 2003

Reviewer:  A reader

June 27, 2003 john d -- Thanks for the question regarding
      "Removing deadlock", version Oracle Ver. 8.1.5

      You Asked
Hi Tom,

   How can I clear deadlock without killing a
   session?

thanks in advance.

john

      and we said...
Deadlocks are automatically cleared -- you never need to "clear"
them.  One or the other session will have their statement
cancelled.  They will become unblocked.  They can either

o do something else instead of getting the lock they were trying
to get and then commit.  this will let the other blocked
transaction continue.

o rollback their transaction.  this will allow the other
transaction to continue on.

The #1 cause of deadlocks in Oracle is due to unindexed foriegn
keys.  See

http://osi.oracle.com/~tkyte/unindex/index.html
for details.  You should never deadlock in Oracle.
Review &
      Followup

      Rating: 2
How long does
      it take for Oracle to clear them  April 18, 2002

Reviewer:  Mike  from Portland, OR
I've just created a forced deadlock by opening two sql*plus sessions and doing
updates out of sequence:

session 1: update names set lastname='Smith' where id=1;

session 2: update salary set taxes=500 where id=1;

session 1: update salary set amount=50000 where id=1;
waiting...

session 2: update names set firstname='Joey' where id=1;
waiting...

It's been over 10 minutes.  How long does it take for Oracle to clear this
deadlock?

          Followup:

deadlock detection takes place instantly.  one of those statements -- if that is
what you actually did -- would have been killed.

What you did above was basically  not possible

We lock at the row level, not the column - hence your flow would have been:

session 1: update names set lastname='Smith' where id=1;

session 2: update salary set taxes=500 where id=1;
BLOCKED -- WAITING -- ID=1 is ALREADY locked!!!

session 1: update salary set amount=50000 where id=1;
waiting...
this would not block, session one already owns the ROW with id=1

session 2: update names set firstname='Joey' where id=1;
waiting...
this would never have happened as session 2 would have been BLOCKED
already!!!

You did something *wrong* in your example.  Cut and paste from sqplus next time
(like I always do)

      Rating:
      4
  April 19, 2002
Reviewer:
      A reader
That scenario does not seems to be a dead lock to me.
Both sessions are accessing id=1. The second session
will wait as long as session 1 commits;

Example:
========

SQL> select * from t2;

      COL1       COL2
---------- ----------
         1
                    2
SQL> select rowid, k.* from t2 k;

ROWID                    COL1       COL2
------------------ ---------- ----------
AAABNtAANAAAFXVAAA          1
AAABNtAANAAAFXVAAB                     2

SESSION1:

SQL> begin
  2  update t2 set col2 = 1 where rowid = 'AAABNtAANAAAFXVAAA';
  3  dbms_lock.sleep(5);
  4  update t2 set col1 = 2 where rowid = 'AAABNtAANAAAFXVAAB';
  5  end;
  6  /
begin
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
ORA-06512: at line 4

SESSION2:

SQL> begin
  2  update t2 set col2 = 3 where rowid = 'AAABNtAANAAAFXVAAB';
  3  dbms_lock.sleep(5);
  4  update t2 set col2 = 4 where rowid = 'AAABNtAANAAAFXVAAA';
  5  end;
  6  /

PL/SQL procedure successfully completed.

SQL> select * from t2;

      COL1       COL2
---------- ----------
         1          4
                    3

          Followup:

Thats what I said -- the sequence of events the poster listed are not possible.
They made a mistake somewhere.

It is not a deadlock, it is a block and session one would NEVER be blocked.

      Rating: 2
I didn't do
      anything wrong...  April 22, 2002
Reviewer:
      Mike  from Portland, OR
except mislead you a bit.  I should have said two Oracle sessions, not two
SQL*Plus sessions, because I was using TOAD and SQL*Plus for my test.  Session 1
was TOAD, and for some reason the "ORA-00060: deadlock detected while waiting
for resource" popup message was obscured by Windows and I never saw it, so I
thought the deadlock never got cleared.

But it doesn't matter.  If you'll notice in my example, I am querying two
different tables (names and salary), so this deadlock scenario is valid.  And
you are right, the deadlock was cleared automatically, although it wasn't
instantaneous, it took about 15 seconds for the error message to come back.

          Followup:

Ahh, you are right -- misread the example, saw the id=1 and made an assumption.

It should take 3 seconds or less for the deadlock to be detected.  I suspect
TOAD.  Try it with pure sqlplus.

      Rating: 4
Does deadlock
      always kill one of the sessions?  June 25, 2003

Reviewer:  Alan Davey  from New Jersey
I just did a test using Oracle 8.1.7 and after a few second's in session B,
Oracle returned this message:
ORA-00060: deadlock detected while waiting for resource

However, Session B was still active and free to either commit, rollback, or do
something else.  Here is my example:

create table test (
pk number(38) primary key,
my_data varchar2(40)
)
/

begin
for x in 1..20 loop
insert into test (x,'Hello');
end loop;
end;
/

session A:
select * from test where pk = 5 for update;

session B:
select * from test where pk = 6 for update;

session A:
update test set my_data = 'test1' where pk = 5;

session B:
update test set my_data = 'testing' where pk = 6;

session A:
select * from test where pk = 6 for update;
[waits]

session B:
select * from test where pk = 5 for update;

After a few seconds, I get Oracle error message above, but session B is still
active and able to issue other commands.
Session A waits until session B commits or rollsback data.

So, did Oracle change their logic to only kill the offending statement (and not
the entire session/transaction) allowing the session to clean itself up?

Thanks.

          Followup:

oracle always had that logic.

one of the statements would get the "deadlock" error and it would have to figure
out "hmm, do i want to

a) commit
b) rollback
c) do something else and then commit or rollback"

      Rating:
      5
confused  June 26, 2003
Reviewer:  Sajid
      Anwar  from London
Hi Tom,
I am a bit confused about the terms "DEADLOCK" and "BLOCK" used in most of the
REVIEWS.
Could you please explain that to me?

Many Thanks.

Regards,
Sajid Anwar
London

          Followup:

block = you wait, you are blocked.  you are trying to modify some data that some
other session is modifying.

deadlock = you locked "row x".  they locked "row y".  You need to lock "row y",
you get blocked by them.  They now need to lock "row x".  They get blocked by
you.  You are now deadlocked - waiting on eachother.  We terminate one of the
statements (else you and they would just sit there forever waiting for
eachother).  That session decides whether it should a) commit, b) rollback, c)
goto lunch, d) do something else

      Rating: 4
How is a block
      ( not deadlock ) handled  June 26, 2003
Reviewer:
      A reader
I opened two sqlplus sessions. Db version 9.2
session a.
SQL>  update test set my_data  = upper(my_data) where pk = 1 ;

1 row updated.
session b
SQL>  update test set my_data  = lower(my_data) where pk = 1

Now session B is blocked as it is waiting for the  session A to commit or
rollback.
Q) How long will it remained Blocked. ?Does it come out of it byitself.

          Followup:

it will remain blocked until session 1 commits or rolls back.

It is also the classic LOST UPDATE bug many programmers place in their code.  It
is a bug in the application.

You are blindly updating information without "seeing it".  You are overwriting
the modifications of session 1 -- you lost their update.

You should

a) select the data out
b) update the user deciding to modify it -- select it for UPDATE (optionally
with nowait if you don't want to block) to make sure it hasn't changed
c) update it.

      Rating: 5
Excellant
      reply  June 26, 2003
Reviewer:
      A reader

      Rating: 4
Session vs.
      Statement killed  June 27, 2003
Reviewer:
      Alan  from New Jersey
Thanks for the response Tom.

The reason I asked, was that in your second response above, you said that the
session would have been killed and not the statement.

Are there scenarios where a session is killed in a deadlock situation and not
just the statement?

Finally, what is the best way to handle the following situation?

I have a table called account_balance defined as:

acc_bal_id primary key
account_id fk to account table
fiscal_year_id fk to fiscal_year table
balance1 number
balance2 number
...
balance12 number

There may be a few hundered accounts in a given fiscal year.  Users call a
routine which updates the account balance for a given fiscal period (balance 1 -
12).

Since different users can be updating the accounts at the same time, I want to
avoid deadlocks where users are updating the accounts in different orders from
each other.

How would I test the performance of selecting all accounts for a give fiscal
year for update, when I am only updating one or two accounts?  I may have 200 -
300 accounts per fiscal year.  Is this the right approach or would you suggest
something else?

Thanks.

          Followup:

sorry, corrected that typo.  what I said in the original answer:

Deadlocks are automatically cleared -- you never need to "clear" them.  One or
the other session will have their statement cancelled.  They will become
unblocked.  They can either...

is what happens (and has been happening). the STATEMENT fails, not the session.

I would think you would need to select for update in order to avoid "lost
updates" anyway.  select for update, then update seems appropriate here.

June 27, 2003 Menno -- Thanks for the question regarding
      "UTL_FILE VS DBMS_OUTPUT VS UTL_TCP", version 8.1.7

      You Asked
Having read most of your answers concerning the use of UTL_FILE to generate a
server-side file, DBMS_OUTPUT to generate a client-side file.. I was wondering
if it would be possible to trick the limits of both above mentioned packages
(serverbased & limited buffer) by using UTL_TCP to write a file to the client's
terminal.. I imagine you could use
DBMS_STANDARD.CLIENT_IP_ADRESS
to read a clients location
UTL_TCP.OPEN_CONNECTION to open a connection from the server side
and use a mixed set of spool and UTL_TCP.WRITE_LINE

I am currently using UTL_FILE for most of my file I/O, but we are currently in
the proces of invoking a new way of client communication and our sys-op's are
getting a bit tired of all the access to the unix side via clients ftp or nds..
If the above mentioned solution could be invoked it would mean a more secure
file I/O for most people...

Any ideas on the above, or is it just wishfull thinking ?

Kind regards,

Menno Stigter

      and we said...
why would you not just use mod_plsql and let them get a web page in their
browser which can be saved?  Heck, the plsql routine could even pump out a mime
type of application/octet-stream and then the browser would just pop open the
file save dialog when they clicked on a link.

You would have to client side "server" that opened a socket, to let the
procedure connect back to the client to pump the data over.  You would have to
install software on every client.  I'd bet they all already have a browser (any
will do).
June 27, 2003 Mohammad -- Thanks for the question regarding
      "list all user(s) group by group_list", version 8.1.7

      You Asked
Hello Tom,

i have the follwing table:

LOGIN_NAME      GROUP_LIST
--------------- --------------------------------------------------
Demo            1; 9000; 1000;
Koo             1000;
Feh             1000; 1006; 1021; 1030;
Hene            1006; 1000; 1021;
Jaehrling       1000;
Koell           1000;
Woeh            1000; 1001;

Now I am trying to write a query to get the below results

Demo    1
Demo    9000
Demo    1000
.
..

Could you plaese help me to solve this problem

Best regards
Mohammad

      and we said...
oh yuck.  hate it when people do that.  "relational" database... ok

ops$tkyte@ORA817DEV> create or replace type myArray as table of number
  2  /

Type created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace function str2table( p_string in varchar2
) return myArray
  2  as
  3          l_data   myArray := myArray();
  4          l_string long := p_string;
  5          l_n      number;
  6  begin
  7          while (l_string is not null)
  8          loop
  9                  l_n := instr( l_string, ';' );
 10                  if ( l_n = 0 )
 11                  then
 12                          l_n := length(l_string)+1;
 13                  end if;
 14                  l_data.extend;
 15                  l_data(l_data.count) := substr( l_string, 1, l_n-1 );
 16                  l_string := substr( l_string, l_n+1 );
 17          end loop;
 18          return l_data;
 19  end;
 20  /

Function created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> drop table t;

Table dropped.

ops$tkyte@ORA817DEV> create table t ( login_name varchar2(10), group_list
varchar2(200) );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert into t values ( 'demo', '1; 9000; 1000;' );

1 row created.

ops$tkyte@ORA817DEV> insert into t values ( 'feh', '1000; 1006; 1021; 1030;' );

1 row created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select t.login_name, t2.column_value
  2    from t, TABLE(cast( str2table(t.group_list) as myArray )) t2
  3  /

LOGIN_NAME COLUMN_VALUE
---------- ------------
demo                  1
demo               9000
demo               1000
feh                1000
feh                1006
feh                1021
feh                1030

7 rows selected.

is one way.
June 27, 2003 Kishan -- Thanks for the question regarding
      "Memory structure", version 8.1.7

      You Asked
Dear Tom:
My company is in the process of acquiring a monitoring tool for our production
databases. One of the tool we're trying has a "stealth" mechanism. According to
the vendor, this stealth mechanism is supposed to be less resource consuming to
the database, as it reads directly from the memory structure bypassing usual
database access, thereby freeing the database to do the usual tasks, without the
overhead of collecting the information. The software reads the x$tables directly
to get required information.

My question:
1. Is it really possible to read directly the memory structure (PGA,SGA, etc.)?
If it is possible, doesn't all the security go to toss?

2. As I assume to read x$tables one needs sysdba role. I don't feel comfortable
about it. The software screws something, and the database goes down. The guy who
demoed it to us isn't sure of how soon, future updates of the software will be
available in case we update the database. Is my uneasy feeling justified or am I
being paranoid?

Thanks for any suggestion you might have.

      and we said...
1) pga never (but that is ok, nothing can read the pga in any case -- except of
course the current session!)

SGA sure, it is just shared memory. they'll attach it and read it.

The question to them would be: so, do you support Oracle's latest version,
9.2.0.3/9.2.0.4 and will you have support for 10i the day after it goes
production?  If not, about how far will you lag behind - since they change the
structures at will and don't document them.  Tell me HISTORICALLY how this has
worked -- from the day 8.0 went production, how many weeks/months did it take to
get your product?  When the 8174 patchset came out -- did you support it the day
or -- or how many days behind was it.  They should have that -- forget "future"
ask them for past performance.

2) they need read access to the SGA, they do not need a database account at all
really to get at the data structures in memory, just SGA access.

Ask the guy "how does this process attach the SGA, what OS account will this run
as?"  it should attach it read only.

Beware, they can get some really funky numbers out of there as their advantage
is they are not latching these structures to get the data (latching being the
issue they are avoiding -- that a select * from v$..... would suffer from)

However that advantage is a disadvantage as well -- becuase they do not latch,
they are not corrdinated with us and we'll be changing what they are reading as
they read it (we don't realize they are peeking at the data)

bottom line, ask for references, then call the references.  post on
groups.google.com (comp.databases.oracle.tools/misc) and ask for peoples opinion
on the product, how successful they've been with it.

Most importantly, make sure you get more from it then you can get from
statspack!  You'd be surprised (and you can even write your own custom reports)
Review &
      Followup

      Rating: 4
Oracle SNMP
      support  June 27, 2003
Reviewer:  Yong
       from CA, USA
I worked for one of those companies for Oracle server monitoring software yeas
ago. We used Oracle snpm support to read MIB and SGA information instead of
using sql. But it still needs a connection to Oracle.

      Rating: 5
reading v$
      views   June 27, 2003
Reviewer:  Mario
       from Rome, Europe
Hi Tom,

you say:

"However that advantage is a disadvantage as well -- becuase they do not latch,
they are not corrdinated with us and we'll be changing what they are reading as
they read it (we don't realize they are peeking at the data)"

but if I recall correctly, even Oracle
(through sql) doesn't guarantee a
multi-versioning read on v$ views.
Is it correct?

Best regards.

Mario from (very hot) Rome, Europe.

          Followup:

we do not provide consistent -- but we do provide accurate -- reads on v$ views.

I wasn't talking about consistent read, I was talking "they could see utter
garbage very easily"

      Rating: 5
Many Thanks
      Tom  June 27, 2003
Reviewer:  Mario
       from Rome, Europe
I had misunderstood (as so often happen), sorry

      Rating: 4
But the
      software has nice manager lights  June 27, 2003

Reviewer:  Jim  from Oregon, USA
The software in quest - ion has a lot of "manager" lights - spinning graphics,
waves, bars that move etc.  It is a useful tool but if you don't know what you
are doing then it is of marginal benefit.  You could end up doing more twiddling
with things in the wrong direction.

June 27, 2003 Sajid -- Thanks for the question regarding
      "ENABLE NOVALIDATE validating existing data", version 9.0.2

      You Asked
Hi Tom,

Simple question about CONSTRAINT STATE for you this time.
This is what i am doing:

SQL> create table t (a number, constraint uniq_a UNIQUE(a));

Table created.

SQL> alter table t disable constraint uniq_a;

Table altered.

SQL> insert into t values(&a);
Enter value for a: 1
old   1: insert into t values(&a)
new   1: insert into t values(1)

1 row created.

SQL> /
Enter value for a: 1
old   1: insert into t values(&a)
new   1: insert into t values(1)

1 row created.

SQL> /
Enter value for a: 2
old   1: insert into t values(&a)
new   1: insert into t values(2)

1 row created.

SQL> select * from t;

         A
----------
         1
         1
         2

SQL> alter table t enable novalidate constraint uniq_a;
alter table t enable novalidate constraint uniq_a
*
ERROR at line 1:
ORA-02299: cannot validate (TUSKERDEV.UNIQ_A) - duplicate keys found

Here is what Oracle 9i (Release 2) Document says :

Constraint States

You can enable or disable integrity constraints at the table level using the
CREATE TABLE or ALTER TABLE statement. You can also set constraints to VALIDATE
or NOVALIDATE, in any combination with ENABLE or DISABLE, where:

ENABLE ensures that all incoming data conforms to the constraint
DISABLE allows incoming data, regardless of whether it conforms to the
constraint
VALIDATE ensures that existing data conforms to the constraint
NOVALIDATE means that some existing data may not conform to the constraint

In addition:

- ENABLE VALIDATE is the same as ENABLE. The constraint is checked and is
guaranteed
  to hold for all rows.
- ENABLE NOVALIDATE means that the constraint is checked, but it does not have
to be
  true for all rows. This allows existing rows to violate the constraint, while
ensuring
  that all new or modified rows are valid.

In an ALTER TABLE statement, ENABLE NOVALIDATE resumes constraint checking on
disabled constraints without first validating all data in the table.

DISABLE NOVALIDATE is the same as DISABLE.
The constraint is not checked and is not necessarily true.

DISABLE VALIDATE disables the constraint, drops the index on the constraint,
and disallows any modification of the constrained columns.

According to Oracle
"ENABLE NOVALIDATE means that the constraint is checked, but it does not have to
be
  true for all rows. This allows existing rows to violate the constraint, while
ensuring
  that all new or modified rows are valid."

Why am i getting error then?

Thanks in advance for your help.

Regards,
Sajid Anwar
London

      and we said...
By default a non-deferrable UNIQUE or PRIMARY KEY constraint will attempt to
create a unqiue index.  That is what is happening here.

You have a choice

a) create the constraint deferrable.  This will create a non-unique constraint.
When you disable the constraint, you have a nice side effect that the INDEX
stays in place.

ps$tkyte@ORA920> create table t (a number, constraint uniq_a UNIQUE(a)
DEFERRABLE );
Table created.

ops$tkyte@ORA920> select index_name from user_indexes where table_name = 'T';

INDEX_NAME
------------------------------
UNIQ_A

ops$tkyte@ORA920> alter table t disable constraint uniq_a;
Table altered.

ops$tkyte@ORA920> select index_name from user_indexes where table_name = 'T';

INDEX_NAME
------------------------------
UNIQ_A

ops$tkyte@ORA920> insert into t values ( 1 );
1 row created.

ops$tkyte@ORA920> insert into t values ( 1 );
1 row created.

ops$tkyte@ORA920> alter table t enable novalidate constraint uniq_a;
Table altered.

Or, you can create the constraint as normal, lose the index (which may be
equally as desirable as keeping it depending on your circumstances of course)
and create a NON-Unique index manually:

ops$tkyte@ORA920> create table t (a number, constraint uniq_a UNIQUE(a));
Table created.

ops$tkyte@ORA920> select index_name from user_indexes where table_name = 'T';

INDEX_NAME
------------------------------
UNIQ_A

ops$tkyte@ORA920> alter table t disable constraint uniq_a;
Table altered.

ops$tkyte@ORA920> select index_name from user_indexes where table_name = 'T';
no rows selected

ops$tkyte@ORA920> insert into t values ( 1 );
1 row created.

ops$tkyte@ORA920> insert into t values ( 1 );
1 row created.

ops$tkyte@ORA920> alter table t enable novalidate constraint uniq_a;
alter table t enable novalidate constraint uniq_a
*
ERROR at line 1:
ORA-02299: cannot validate (OPS$TKYTE.UNIQ_A) - duplicate keys found

ops$tkyte@ORA920> create index t_idx on t(a);
Index created.

ops$tkyte@ORA920> alter table t enable novalidate constraint uniq_a;
Table altered.

Review
      & Followup

      Rating: 5
Thanks a
      million  March 25, 2003
Reviewer:  Sajid
      Anwar  from London
You are THE STAR, Tom.
Brilliant answer and to the point.

Thanks once again.

Regards,
Sajid Anwar
London.

      Rating:
      5
perfect  April 11, 2003
Reviewer:
      Dave  from San Diego, CA
I think from here on out I'll be using asktom in the same way that I use google
to find out how to fix my problems...type in the error message and see what
happens. With google, you'll end up with oracle docs in russian that mention the
command you're using. At asktom you get real life examples of what to do.

I've been thrust in to the role of pl/sql web programmer. So far Tom's advice
has saved me time, reinforced things that I thought I was doing right but I
wasn't sure, and reminded me that guessing isn't the same thing as
experimentation.

Tom, you rock

      Rating:
      4
Website...  June 27, 2003
Reviewer:
      kashif  from Houston, TX
Tom,

Do you know of a website similar to yours for Java developers and Oracle Java
developers? Thanks.

kashif

          Followup:

http://devtrends.oracle.com/

June
      28, 2003 Balasubramanian -- Thanks for
      the question regarding "About utl_smtp", version 8.1.7

      You Asked
Hi Tom,

I created the procedure send_mail as in your book in the user System.

create or replace procedure send_mail
( p_sender in varchar2,
  p_recipient in varchar2,
  p_message in varchar2 )
as
   l_mailhost varchar2(255) := '10.228.1.75' ;
   l_mail_conn utl_smtp.connection ;
begin
   l_mail_conn := utl_smtp.open_connection(l_mailhost, 25) ;
   utl_smtp.helo(l_mail_conn, l_mailhost) ;
   utl_smtp.mail(l_mail_conn, p_sender) ;
   utl_smtp.rcpt(l_mail_conn, p_recipient ) ;
   utl_smtp.open_data(l_mail_conn) ;
   utl_smtp.write_data(l_mail_conn, p_message ) ;
   utl_smtp.close_data(l_mail_conn) ;
   utl_smtp.quit(l_mail_conn );
   dbms_output.put_line('Message send to the user successfully') ;
end ;

The procedure compiled without any error. I call the above procedure in the same
user as :

begin
   send_mail('vrbala@visto.com',
        'vr_bala@yahoo.com',
        'This is the test message from oracle server' ) ;
end ;

I got the following error. What should i do to run this.

ORA-20002: 554 Relay rejected for policy reasons.
ORA-06512: at "SYS.UTL_SMTP", line 86
ORA-06512: at "SYS.UTL_SMTP", line 223
ORA-06512: at "SYSTEM.SEND_MAIL", line 12
ORA-06512: at line 2

Thanks,
Bala

      and we said...
Talk to your system and or network administrator.  This is 100% an smtp
configuration issue.  Your host (10.228.1.75) is not permitted to relay email
(to prevent spammers from using you as a jump off point).  You need to find the
valid smtp server within your organization.
Review &
      Followup

      Rating:
      5
Thanks  December 03, 2001
Reviewer:
      Balasubramanian  from Singapore
Thanks for immediate response. Also is there any way to
check validity of smtp server using oracle procedures.
Because the administrator told this is the correct smpt
server.

Thanks,
Bala

          Followup:

You did check the "validity" of the SMTP server -- you got the message back
"relaying not allowed".  Your smtp server is simply NOT allowed to relay to
hosts outside of its domain.  You can telnet right to the SMTP server to prove
this to yourself.

The elements in bold are what you type in, please use your hostname of course.
You should get the same output as you did from Oracle -- indicating simply that
your server in visto.com is not allowed to relay to yahoo.com

$ telnet aria-dev 25
Trying 138.2.5.52...
Connected to aria-dev.us.oracle.com.
Escape character is '^]'.
220 aria-dev.us.oracle.com ESMTP Sendmail 8.9.3+Sun/8.9.1; Mon, 3 Dec 2001
09:41:19 -0500 (EST)
helo aria-dev
250 aria-dev.us.oracle.com Hello aria-dev.us.oracle.com [138.2.5.52], pleased to
meet you
mail from: tkyte@us.oracle.com
250 tkyte@us.oracle.com... Sender ok
rcpt to: Thomas.Kyte@oracle.com
250 Thomas.Kyte@oracle.com... Recipient ok
data
354 Enter mail, end with "." on a line by itself
this is a test
.
250 JAA17566 Message accepted for delivery
quit
221 aria-dev.us.oracle.com closing connection
Connection closed by foreign host.

      Rating: 5
Relaying Denied
      fixes  December 03, 2001
Reviewer:  Andy
      Finkenstadt  from Saint Charles MO
Tell your system adminstrator to allow the IP address for the database server
initiating the email to relay, by adding the address to (typically)
/etc/mail/relay-domains.

The relay prohibition comes most restrictively from that lack, or from not using
an @locally.relayable.domain sender address if the administration is using
FEATURE(relay_local_from).

      Rating: 3
Ora-20001 :450
      when executing procedure.  March 31, 2003

Reviewer:  Vivek Sharma  from Bombay, India

Hi Tom,

I am receiveing ora-20001 :450 error while executing a procedure. But no help is
provided on Metalink for this. The content of my procedure is

PROCEDURE mail_job_check IS
    v_msg varchar2 (10000);
    mailhost  varchar2(30) := '172.6.49.8' ;
    mail_conn utl_smtp.connection;
crlf      varchar2(2):= CHR(13) || CHR(10);
mesg      varchar2(20000);
c_mesg    varchar2(20000);
a_mesg    varchar2(20000);
mail_id   user_info.ui_email%type;
v_isin    varchar2(10000);
c_isin    varchar2(10000);
a_isin    varchar2(10000);
v_from_dt date;

    Cursor c1 is
    Select ui_user_cd
    from   user_info
    where  ui_active = 'N'
    and    to_char(ui_last_upd_dt,'dd-mm-yyyy')= to_char(sysdate,'dd-mm-yyyy');
BEGIN                                                           SELECT ui_email

    INTO   mail_id
    FROM   user_info
    WHERE  ui_user_cd='SFG';
    For x in c1 loop
a_mesg:=a_mesg||crlf||x.ui_user_cd;
end loop;
    IF a_mesg IS NULL THEN
       a_mesg := 'No Users Deactivated Now '||to_char(sysdate,'dd-mm-yyyy');
    END IF;
    mail_conn := utl_smtp.open_connection(mailhost, 25);
utl_smtp.helo(mail_conn, mailhost);
utl_smtp.mail(mail_conn,'vivek.sharma@pidilite.com');
utl_smtp.rcpt(mail_conn,mail_id);
    utl_smtp.rcpt(mail_conn,'vivek.sharma@pidilite.com');
mesg:= 'Date: '||TO_CHAR(SYSDATE,'DD MON YYYY HH24:MI:SS' )|| crlf ||
'From: CLMS Software Team '|| crlf ||           'Subject: Users deactivated on
'||sysdate|| crlf||           a_mesg;
    utl_smtp.data(mail_conn,mesg);
    utl_smtp.quit(mail_conn);
END;

When I execute this procedure, I get an error as

BEGIN clms.mail_job_check; END;

*
ERROR at line 1:
ORA-20001: 450 4.7.1 abhi_s@@pidilite.com... Relaying temporarily
denied. Cannot resolve PTR record for 10.16.0.75
ORA-06512: at "SYS.UTL_SMTP", line 83
ORA-06512: at "SYS.UTL_SMTP", line 223
ORA-06512: at "CLMS.MAIL_JOB_CHECK", line 34
ORA-06512: at line 1

Please suggest a workaround.

Thanks in advance.

          Followup:

workaround:  read the error message.

You are getting apparently a 450 error from your SMTP server (the ora-20001 is
utl_smtp's way to saying "error encountered -- here is more information"

If you just:

[tkyte@tkyte-pc Desktop]$ telnet 172.6.49.8 25
Trying 172.6.49.8...
Connected to xxxx.
Escape character is '^]'.
220 aria.us.oracle.com ESMTP Sendmail 8.10.2+Sun/8.10.2; Mon, 31 Mar 2003
08:35:55 -0500 (EST)
HELO <hostname -- whatever the hostname of 172.6.49.8 is>
250 aria.us.oracle.com Hello tkyte-pc.us.oracle.com [138.1.120.255], pleased to
meet you
MAIL FROM: vivek.sharma@pidilite.com
250 2.1.0 vivek.sharma@pidilite.com... Sender ok
RCPT TO: vivek.sharma@pidilite.com
250 2.1.5 vivek.sharma@pidilite.com... Recipient ok
DATA
354 Enter mail, end with "." on a line by itself
hello tom
.
250 2.0.0 h2VDb7229862 Message accepted for delivery
QUIT
221 2.0.0 aria.us.oracle.com closing connection
Connection closed by foreign host.
[tkyte@tkyte-pc Desktop]$

and you just enter the stuff in BOLD, you'll reproduce the same exact error.
 This is a SMTP configuration issue -- you need to talk to your email guys.

      Rating: 4
utl_smtp
      strangeness 9.2  April 14, 2003
Reviewer:
      Ray  from Ottawa,On,ca
declare
mailhost  varchar2(30) := '999.11.2.99' ;
mail_conn utl_smtp.connection;
mesg      varchar2(4000);
begin
mail_conn := utl_smtp.open_connection(mailhost, 25);
utl_smtp.helo(mail_conn, mailhost);

utl_smtp.mail(mail_conn,'a_b@aaa-bbb.xx.yy');

utl_smtp.rcpt(mail_conn,'a_b@aaa-bbb.xx.yy');
mesg:= 'text';
utl_smtp.data(mail_conn,mesg);
utl_smtp.quit(mail_conn);
END;

ORA-29279: SMTP permanent error: 501 strangeness between : and <
ORA-06512: at "SYS.UTL_SMTP", line 17
ORA-06512: at "SYS.UTL_SMTP", line 98
ORA-06512: at "SYS.UTL_SMTP", line 221
ORA-06512: at line 8

this error occurs at the .mail call

What is it trying to tell me with between : and <

When I telnet this IP it responds initially but that's it

The mail server is notes/domin

any help is appreciated.

          Followup:

Well, thats not a database error -- that is the SMTP server being finicky.

RFC 821 states that all envelope information should be enclosed in angle
brackets.

try putting <> about your email addresses.

      Rating:
      5
smtp  April 17, 2003
Reviewer:  Ray
       from Ottawa,on,ca
yup that did it thanks

      Rating:
      4
  June 28, 2003
Reviewer:  j.

hi tom, everything works fine so far.

now I wonder if there is any way to check whether given email addresses were
valid.

can the database get notified if mail delivery fails?

          Followup:

the email will bounce back and you can use the other parts of the javamail api
to "read" mail as easily as "send mail"

so, instead of using utl_smtp, you can load up the java mail api and use it to
do lots more.

June 28, 2003 Marc -- Thanks for the question regarding
      "Performancetuning UTL_SMTP", version 8.1.7.4

      You Asked
Hi Tom,

we are using UTL_SMTP so send a large amount of mail. It's a newsletter-project
and the mails must be delivered in a short time-period.

All Mails are relayed through a local linux-box mailserver (exim) with an 2Mbit
Internet-Connection.

Currently, the database has a throughput of about 1-2 mails (Text-only) per
second (deliveries on the input-side of the relay), but we need to speed up the
performance. For HTML-Mails with attachments the performance is (depending on
the attachment- and mailsize) even slower.

The System is running with 50MB Java-pool size on a dual-Xeon machine

We use two different methods for sending out mails (fetched by a cursor):
1. Open a new connection, send single mail, close connection for every mail:

for x in cursor
loop
   open_conn;
   send_a_mail( ...)
   close_conn;
end loop;

2. Open a connection, send all mails, close connection (used, when sending
through our relay):

open_conn;
for x in cursor
loop
   send_a_mail( ...)
end loop;
close_conn;

The performance-difference between these methods is relevant (method 2 is about
100 mails/h faster).

The send-process itself is serial, a single threaded process. So we do not send
more then one mail a time.

Our mails are personalized, so we are not able to use features like "just place
all recepients as bcc's".

Could you give me a hint about some tuning-issues...

Best Regards

Marc

      and we said...
suggest we parallelize this.  the performance is not utl_smtp related -- it is
smtp related.

So, my suggestion would be

a) set job_queue_processes to some "large" number, upto 32 in 8i, upto 1000 in
9i
b) create a table
   parameters ( job_id number primary key, primary_key_for_cursor <datatype> );

c) create a procedure

   procedure send_a_mail( p_job in number )
   as
      l_rec parameters%rowtype;
   begin
      select * into l_rec from parameters where job_id = p_job;
      for x in ( select * from send_mail_table where primary_key=l_rec.pk )
      loop
            sendmail;
      end loop;
      delete from send_mail_table where primary_key = l_rec.pk;
      commit;
   end;

d) instead of coding the loop as you have it now, do this:

   for x in cursor
   loop
       dbms_job.submit( l_job, 'send_a_mail(JOB);' );
       insert into parameters values ( l_job, x.primary_key );
       commit;
   end loop;

and sit back and watch the mail get sent as fast as possible in parallel.  You
can use alter system set job_queue_processes = 32; before you run, run it and
after its finished set job_queue_processes = <whatever is normal for you>

Review &
      Followup

      Rating:
      3
Benchmarking  April 01, 2003
Reviewer:
      Scott  from Perth, Western Australia
G'day Marc.

If you are interested, I did some benchmarking on our development box comparing
utl_smtp to the Javamail method found at

http://asktom.oracle.com/pls/ask/f?p=4950:61:213124606389073465::::P61_ID:255515160805
I found that Javamail performed much better...

      Rating: 3
Why in the
      world would you do it this way?  April 01, 2003

Reviewer:  Richard  from Florida
Marc:

I worked at a job a few years ago where I was the company
"spammer" (believe me, I'm not proud of it). I also used
a Linux system to send out the company's emails and used
an Oracle database to store the email addresses. But, I
did not use UTL_SMTP. My throughput was, on average, around
150 emails per second. So, forgive me, but, from my point
of view, your system's performance is abysmal.

I would simply spool all the email addresses to a text
file, run some shell scripts to clean up the file (remove
duplicates and bad email addresses) and then run another
shell script to send out the emails using the standard
Unix 'mail' command.

I don't know if your bottleneck is with your SMTP server or
with Oracle, but I see no reason why you should not be
spitting out at least 100 emails/second.

--- Rich

      Rating: 5
Great
      Performance-Boost  April 02, 2003
Reviewer:
      Marc Dillmann  from Germany
Thanks ... it works great. With 32 processes, we reached throughput of 600 Mails
/ Minute.

Regards

Marc

      Rating: 3
Still too
      slow  April 02, 2003
Reviewer:  Richard
       from Florida
That's only 10 emails per second.
That's still much to slow, IMHO.
Try using shell scripts, instead.

          Followup:

well, given that UTL_SMTP doesn't add that much overhead here -- i don't see the
point in ripping everything out of the database and putting it into shell
scripts.  I think the problem is their smtp server.

"spammers" have their machines setup to be email maniacs.  I sure a little
tuning of the smtp services here could possibly help, but simply doing it from a
shell script?  uh-uh.

      Rating: 5
Scott: I'm
      interested now....  April 03, 2003
Reviewer:  A
      Reader  from UK
I'm interested now, because Tom is saying UTL_SMTP doesn't add much overhead,
and shell scripts shouldn't be much much quicker, but you are saying you have
some benchmarks, presumably from a SINGLE environment (because so far everyone's
saying 'my car does 0-60 in 50 seconds using x brand tyres', 'my car does 0-60
in 4 using y brand tyres - why' - but NO-ONE (except Tom) has given much thought
to the fact that one could be a ferrari the other a mini.

However, Scott has an example on the same car, using different tyres :)  which
should prove/disprove Tom's point that UTL_SMTP doesn't add much overhead if
Scott did a scientfic test ??

Hope I haven't missed something here, but it sounds like we could do with seeing
your results (and an idea of the details of the comparison) Scott !

          Followup:

Now, before we get into this real deep....

utl_smtp talks right to the smtp server there.

from the command line, you can use "mail" for example -- but that queues the
mail and then sendmail talks to the smtp server.  So, we'll "benchmark" 100 mail
sends -- but we have to wait for the one mail send to complete (utl_smtp -- mail
is actually sent).  That is, if you run:

ops$tkyte@ORA817DEV> set timing on
ops$tkyte@ORA817DEV> begin
  2  for i in 1 .. 100 loop
  3  send_mail( 'tkyte', 'tkyte' , 'hello' );
  4  end loop;
  5  end;
  6  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:44.62

and do mailq to see the queued mails -- there won't be any -- they'll have been
delivered.

However, if you do:

$ cat test.csh
#!/bin/csh

set x = 100
while ( $x > 0 )
echo hello | mail tkyte
@ x = $x - 1
end

$ time ./test.csh
1.0u 2.0s 0:11 25% 0+0k 0+0io 0pf+0w

it would appear that is "4 times faster".  But is it?  No, not really -- only
from the end users perspective -- the guy running the script.  For you see the
mailq command now and for quite a long while actually, will say "gotta a big
queue, working on it".  mail just submitted the mail to be sent real soon -- it
didn't send it.

I achieved the same thing using dbms_job here.  Just like the OS would kick in
and startup more sendmails to process the queues, we kicked in various jobs to
process the queues.

It is 6 one way, 1/2 dozen the other.  At the end of the day -- they did the
same thing!  for if I modify test.csh to be:

$ cat test.csh
#!/bin/csh

set x = 100
while ( $x > 0 )
echo hello | mail tkyte
@ x = $x - 1
end

set x = `mailq|head -1`

while( "$x" != '/var/spool/mqueue is empty' )
        sleep 1
        set x = `mailq|head -1`
end

$ time test.csh
5.0u 6.0s 0:55 19% 0+0k 0+0io 0pf+0w

we find the time to send the mail and wait for it to be sent... is about the
same.

so, I achieved the parallelism using jobs -- the OS can do it as well -- but the
net effect is "the same" as far as truly sending the mail goes.

      Rating: 4
Benchmarking
      Results  April 03, 2003
Reviewer:  Scott
       from Perth, Western Australia
G'day Marc.

Yes, I did perform my testing in a single environment, but you might say it was
on a loaded bus (love you analogy by the way...)
In my first followup, the link I supplied had the results of my benchmarking
near the bottom (look for Scott from Perth).
It was on a development machine, with multiple users, so I ran separate batches
at different times and added up the total times (with and without attachments).
I essentially used two loops within another loop, and used dbms_utility.get_time
to calculate the timings. (Check out
http://asktom.oracle.com/~tkyte/runstats.html
Good Luck

      Rating:
      3
Hmmmm  April 04, 2003
Reviewer:
      Paul  from UK
So are we saying Javamail batches up the mails whereas UTL_SMTP sends
immediately ?  That would explain any differences found in Scott's results.

I guess it depends on the goal - if the goal is 'I don't want to tie-up my
Oracle server sending each mail' - i.e. REALLY sending it, then you might prefer
a solution that sends it on to some other server for handling quicker.  If the
requirement is - I want them out of the building and with the recipient faster,
then maybe both end up the same speed ?  To me, I'd imagine its more often the
former - free-up the database from sending mail and let the mail server queue
them up and send them.  It sounds like the choice is:

Free-up Oracle, Mails delivered same speed as UTL_SMTP
Tie-up Oracle, Mails delivered same speed as UTL_SMTP

And tie-up Oracle might mean tie-up human being if someone is monitoring or
waiting for the Oracle side to finish - if they are not, who cares how long it
takes.  Does 6 hours or 5 minutes to send a batch of emails really make that big
a difference unless you are concerned with server resources ?

I think what we are saying is it depends on the requirements - a simple speed
comparison is not enough.

Regards,

Paul

          Followup:

well said -- yes, you have to understand what is going on, how they work, what
they do and the differences between.

I sort of like "tie up oracle with dbms_job in the background" as it gives me a
single point to monitor everything -- the database.  if the email fails (bad
email address for example), the job fails and you know all about it.

      Rating: 3
how to catch
      that error?  June 28, 2003
Reviewer:  j.

as for "if the email fails (bad email address for example), the job fails and
you know all about it."

hi tom: I've tried your sample code: jobs were submitted, executed and removed
... some emails were sent but others never reached their destination due to an
invalid (non-existing) address.

did I miss anything?

          Followup:

by bad email I mean one that is not valid at all - "foo bar" for example,
that'll raise an error.

if the email was valid, foo_bar@nowhere.com, it'll bounce, you look in your
system logs for that.

June 28, 2003 shahid -- Thanks for the question regarding
      "DATAWAREHOUSE Backups", version 816.3

      You Asked
Hello Tom,
How you doing, I have got a situation where I need your help please.
The obvious answer to my question is known to me that is buying disks
but for some reasons for some months thats not possible, so please try to keep
in mind my constraints.

We have a large warehouse 90-100 GB in size using 8.1.6.3 database
on aix 4.3.
We do expertise in rman and veritas netbackup also in case you need to refer to
it.
At present we are using export with direct ption and at the same time zipping
our dump file using unix pipes via mknode etc .

We can not shutdown db at all, db is readonly business wise i.e only updates to
the db is via daily mornign batches - 4 hrs and monthly batches - 15 hrs. During
the batch redo activity is veryhigh and log switches occur frequent,We are on
noarchive mode, having 6 log groups
each having a 64 mb member .

We like to take a physicl backup in addiiton to export. Since we can not take
offline data files backup becuase we can't shutdown db, we have no option but to
enable archiving on and take hot backups with or whithout rman. We prefer to use
rman, we have netbackup from veritas.

If we turn on archiving we should have enough space to hold all archive logs
during the batch which we dont. We have separated mount points based of
different data files like /u3 for tables, /4 for indexes. /u4 for redo . /u5 for
rbs etc. but we never taken account one for archive before. So at most we can
have 1GB of space reserved for archive from one of the above mount points. npt
more than that.

Objective is to take a backup of database after the batch is over.
At that time databse is in like a query node bcuase its a wrehouse where
developers use discoverer. So we can safely take online backups
and not worry about arcive logs. But this we can't do wothout turning archive
on. How do we go about the space issue? I have thought of
writing a unix cron job to delte all logs during the batch at a minute interval
but thats not 100% secure. May be archive logsproduced so fast etc.

Is there any other way?
Can I take all tablespaces read only and still make db in noarchive mode, then
use rman to backup???

Another quick one is that i have read staspack orace press book and it seems its
aimaed a unix pltform, is there any statspak doducmen,
scripts... on nt???

Thanks

      and we said...
If you are in NOARCHIVELOG mode, there is 0% chance of doing hot backups.

The "best" you could do to emulate that is

a) do your batch load
b) place all tablespaces except things like temp, system, rollback into READ
ONLY mode
c) "transport" the tablespaces (if you have my book "Expert one on one Oracle" I
describe how to do that in the export chapter, else see the server utilities
guide).
d) put the tablespaces back into read/write mode right before your next batch
update cycle.

It is funny -- given that you could goto a Computer store and buy a 36gig SCSI
drive for about $250 usd.  You've probably burned 10 times that much just
thinking about the problem.

Statspack and the intrepretation thereof is 100% platform neutral (as is Oracle
and Oracle tuning).  I cannot imagine any "unix specific" conclusions that would
be drawn from a statspack report.  Perhaps their advice on "how to tune a IO
related issue" would contain unix'ism -- but even that is doubtful.
Review &
      Followup

      Rating: 2
please
      calrify  November 16, 2002
Reviewer:  shahid
       from ksa
Thanks but still one doubt.
Transport tablespaces require to copy the complete data files , what we inetend
is to use rman. Reason is that since its a warehouse we had planned for some
space in advance so rman would compress past high water mak and save time and
backup piece.
Is there any way to use rman with archive turn on and still able to handle the
lots of archived logs generated within the space limit?
Yes we will buy disks in july 2003 as budgted becuase even 36 gb hdd for ibm
would cost a lot, so mch that yo could buy a compaq server.

For statspack my question was that is there a book that aimed at using statspack
on nt?
Thanks

          Followup:

The laws of physics are working against you here.  You can only fit so much data
on so much disk.  period.

A datawarehouse should have virtually NOTHING above the HWM (i'm not getting
your percieved relationship there).  If you have tons above the HWM, you can
easily shrink the datafiles (search this site for

maxshrink

a script that does just that -- achieving the same results).

There is no need for statspack on NT -- it would be a duplicate of statspack on
OS/390 or statspack on Unix or statspack on Linux or statspack on anything else
-- statspack is the same code, providing the same exact information with the
same exact remedies on every platform.

      Rating: 5
Backingup a
      large database  January 08, 2003
Reviewer:  Rory
      B. Concepcion  from Philippines
Hi Tom. I have a very similar problem. I have a FRAUD MANAGEMENT SYSTEM which is
in a datawarehouse configuration. It has a number of datafiles, 2 of which are
50g in size. I know that's it's not really that big but I also have the same
dilemma, lack of space for me to backup to disk. my question is, can I backup
using rman to a 40g tape. The database is about 140g in size right now and will
still grow. My machine is HP 9000. oracle version is 8.1.7.4. Please assume that
the company wont buy additional disks for me to enable backup to disk. :-( Or is
there a way to backup without making use of rman into a 40g tape. Thanks for the
time. Good day and congratulations on a great site.

          Followup:

does your company want to lose all of its data?

If not, tell your company they will unless they buy you a proper tape drive
setup that allows you to backup the entire database without flipping tapes.

You can use a 40gig tape, sure, but do you really want to stand there and flip
tapes 5 times to do a backup?

      Rating: 5
Datawarehousing
      backup  January 08, 2003
Reviewer:  Rory
      Concepcion  from Philippines
Hi Tom,

Thanks for the response. I understand what you mean. I'll try to convince them
to buy a proper tape drive. This is my first time to backup a datawarehouse. I
did encounter databases like this but have not been tasked with the backup.

a. What would you recommend as a proper tape device that can backup databases of
this size. The backup would fit in just one tape right?

b. Can I use rman on this. Havent tried rman writing directly to tape yet. Hope
you could give some reference. Thanks.

      Rating: 5
please help
      with "rman writing directly to tape"  May 02, 2003

Reviewer:  A reader
Asking Tom to make tape hardware recommendations is way over the edge.

But it's a very valid question on how to have rman backup directly to tape.
Would you care to show an example? Thanks, Tom.

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96566/rcmintro.htm#451880
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96566/rcmconfg.htm#450596

      Rating: 5
RMAN to Tape
        May 02, 2003
Reviewer:  Dana Day
       from Phoenix, AZ USA
We use TantiaTech Harbor solution to backup Oracle directly to tape. The
solution is used across the enterprise on Oracle databases from 1GB to 800GB.
The methodology used is determining max amount of archive logs generated during
busiest activity time.
A scripting environment generates a script (on Unix only, Sun/AIX/HP) to run an
rman archive log only with archive log all delete input options to automatically
delete logs as they are backed up.
This, combined with a gauge of lowest performance to tape, gives you a objective
measure of whether your system will be able to use rman.
Works well in our environment, and I believe the scripting environment author
has made it public domain.

      Rating: 5
RMAN
      question  May 03, 2003
Reviewer:  Alexandre
       from Rio de Janeiro - Brasil
Tom,

Is there a way to allocate, for example, 2 channels, one for tape and one for
disk to generate simultaneouslly the same backupset to disk and to tape ???

Thanks,

Alexandre

      Rating: 5
Veritas Backup
      for Oracle Database.  June 28, 2003
Reviewer:
      Mohan Kumar R  from Bahrain
After taking the Oracle Backup through Veritas software, while connecting to
oracle it shows an Error "ORA-01033, Oracle Initialization or Shutdown in
progress."

How to handle this Error?

Thanks
Mohan

          Followup:

finish starting up or shutting down and then starting up i suppose?  I didn't
write the veritas backup scripts so I've no real idea what they are doing

why did you have veritas shutdown the database just to do a backup?  just use
hot backups.

June 28, 2003 Fan
      -- Thanks for the question regarding "DBA skill set trend", version 9i

      You Asked
Tom,

As the new features introduced in 9i, please talk about what you think the most
important skills as DBAs need to have, and the most unimportant kills will be
phased out.

Thanks,

      and we said...
IMHO:

o experience counts, hands on counts.  Book learning is OK but no substitute.
Being there, doing it, using the software -- that counts.  I would hire a high
school graduate with 5 years of experience before I hired a college graduate
with 1 year of experience and an OCP.

o The major #1 skill I look for -- the ability to communicate, to keep an open
mind, to realize that things change over time, the ability to change your mind
about things.  People skills are perhaps more important (at least AS important)
as technical skills.

Individual database skills like "I can write the coolest query" or "I can write
a hot backup script" -- meaningless unless you manage a professionally run
database every day, respond to end users needs, and so on.

The way I interview DBA's, I ask them questions like:

o how do you make it so the developers can use TKPROF efficiently
  (the ones that say, well, heck -- we'd never let a developer near a trace
file,
   they cannot use that tool -- they are asked to leave.

   the ones that say, well, heck -- what is tkprof -- is that something you
   wrote.  They are shown the door.

   the ones that say how they help the developers get access to the stuff they
   need, they get to continue on with the interview)

o when are the times it is valid to run a database in NOARCHIVELOG mode

  (the ones that say "all databases can run that way" are removed

   the ones that describe how a TEST instance or some data warehouses that are
   incrementally loaded can stay)

o draw me a picture of Oracle on that white board.  Show me you know what
processes/threads there are, what their jobs are.  Describe all of the files
involved in the database, what they do, what they mean.  Show how they interact.

  8 out of 10 people give me a blank start on this as if I'm kidding.
  1 out of 10 people do it totally wrong
  maybe, just maybe, 1 out of 10 people do it and do it right.

(this picture drawing is one of my most important questions)

o tell me how backups work, how recovery works.  Walk through various scenarios
and tell me what would happen.  For example, we are archivelog mode -- hot
backup  every sunday.  It is wednesday and we lost the disk with the users
tablespace on it.  What do you do now...  and so on.

So, I guess my advice is -- don't focus on the individual FEATURES, learn the
concepts -- how it works.  Features, they can be learned.  Concepts and having a
fundemental understanding of how the product functions -- either you know it or
you don't.

Review &
      Followup

      Rating: 5
DBA
      skills  December 09, 2001
Reviewer:  sanjai
       from MI, USA
Very well explained.

      Rating: 4
Good
      one  December 09, 2001
Reviewer:  Patrick
       from denver,co
Thanks Tom, good list to view!

      Rating: 3
More specific
      for 9i  December 09, 2001
Reviewer:  Fan
       from NJ, USA
Tom,

My original question is not clear enough.

I would like to know the most important 'new' features 9i has for DBA. And as a
trend, what 'old' features from 7/8/8i will gradually be phased out?

          Followup:

No, I understood your question -- sorry you didn't understand my answer.  I
don't believe there is any list of "important features you need to know in order
to be a DBA".  9i has HUNDREDS of new features.  They are all equally relevant
in different contexts.

Automatic segment space management -- relevant in OLTP.  Perhaps meaningless in
DSS with weekly loads.

Block level recovery with RMAN, totally relevant if you use rman, run in
archivelog mode.  Meaningless otherwise.

Multi-block size tablespaces -- totally relevant if you transport tablespaces
between databases with different blocksizes frequently.  Maybe not so relevant
otherwise.

My point was -- get a good overall understanding of everything that is in there,
read the docs, understand it.  What you read today may have NO relevance to what
you do today but tomorrow -- you might find a need for it and if you were not
even aware of its existence, it might as well NOT exist.

I do not believe there is a list of "you must know this", rather you want a
understanding of all there is to know, know how to find more information on it,
know how to apply it.

I don't have such a list.
The only major "features" being trended out are

o dictionary managed tablespaces
o rule based optimizationo connect internal and svrmgrl have disappeared.  AS
SYSDBA and sqlplus replace
  them

      Rating: 5
You hit the
      nail on the Head  December 09, 2001
Reviewer:
      sriram  from Thailand
 Practical and Enlighting Views.

      Rating: 5
VERY
      RIGHT  December 09, 2001
Reviewer:  Ganesh
      Aditham  from Pune,India
It is for me this way "KEEP YOUR FUNDAMENTALS RIGHT AND NOBODY CAN SHAKE YOU"
Thanks Tom for the good answer

      Rating: 5
Thats just
      superb!  December 10, 2001
Reviewer:  Srimal
       from Trenton, USA
Hi Tom,

The explanation that you have provided is just superb!

      Rating: 5
Some more
      skills  December 10, 2001
Reviewer:  Arup
      Nanda  from Norwalk, CT, USA
Hi Tom,

I don't want to budge in your responses but I thought I could contribute a
little in the discussion of DBA interviews. I do take a lot of DBA interviews,
and here is a sampling of the questions I generally ask (in addition to the oned
you mentioned).

1. You are the new DBA at a shop with several varieties of end user tools you
have not even heard of; and a development manager walks in on your first day
with a menacing look and words that shake the earth, "the database is slow".
Tell me where you would start looking and how.

Most people start with sar and vmstat, I gently prod them to a more meaningful
answer. There is no single "best" answer, but I look for the analysis skills of
the candidate, how logical his or her reasoning skills are and, most important,
is he/she just beats around the bush or logically identify potential trouble
areas and eliminate possibilities.

2. I never ask anyone, novice or advanced, two things - definition and syntax.
The fact that the candidate knows or does not know these does not prove
anything. I ask them to explain the concept in whatever way they feel
comfortable, e.g. describing an incident or stating "for example". Syntax, with
the exception of a few, is not meant to be written to the hard disk called
brain. The manuals are always there. However, you must know what options you
have and how you use them, the exact syntax not relevant.

3. When interviewing DBA candidates for a specific task, I generally ask an open
ended question on the ideas to resolve the problem without too much reference on
the constraining factors. For example, I recently interviewed candidates for a
shop doing migration. I asked the candidate a loose, open ended questions - what
are the various methods of moving data from one system to the other? Then I prod
them on pros and cons of each. This tells me volumes on teh depth of experience
of the candidates.

Hope this helps.

Arup

      Rating: 5
DBA skill set
      trend  January 15, 2002
Reviewer:  Rajendra
      Prasad  from OH
Thanks Tom
Given Good mist to everybody to be come a dba, is a prime most factor.

      Rating: 5
Concepts manual
      and Whiteboard  August 26, 2002
Reviewer:
      A reader
How can we make
    "Write Me On That Whiteboard How Oracle Works"
a part of the Mother manual?

          Followup:

It is already done -- the picture and all of the verbage is exactly what "Part I
What is Oracle?" is all about - the picture is in the section "Memory Structure
and Processes" as is most of the verage in that chapter!!

      Rating: 5
Thats true
      but...  August 26, 2002
Reviewer:
      A reader
what is missing there is how all these work together like "Redo and Rollback"
page 150 - 153 from your book.

      Rating: 5
I feel DBA job
      is not here to stay  August 26, 2002
Reviewer:
      A reader
Excellent response! As always!

Unfortunately I feel the market's direction is reducing DBA jobs. I would not
place any bet for DBA jobs!In fact we already see demise of Oracle DBAs!

Just wanted to warn to people who dream about DBA jobs (seems a few dont
understand why) :-)

          Followup:

You are kidding right?  I see no drop in the need for qualified smart people.
Whilst the nitty gritty parts of the job are "easier" then in the past -- the
job is broader, there is MORE to support (workspace mgmt, text, xml db, java in
the db, etc etc etc).  some things that were hard are easier but overall there
is more to manage.  Net effect: you still need qualified, smart people.

      Rating:
      5
  August 26, 2002
Reviewer:
      A reader
The problem with (workspace mgmt, text, xml db, java in
the db, etc etc etc etc (and a long list) is... where are they used? Probably
10% of shops (1% in Spain, in fact many OSS Spain dont even know about these
nice features)

Also in many big shops third party software are used such as SAP, Peoplesoft,
Siebel (we should have a look at Siebel pl/sql stored procs, so sad :D ),
Clarify (Clarify pl/sql functions so sad too :D ), JD Edwards, Vantive, Telco
Billing system such as Kenan/Arbor/BSCS/OPSC (I work in Telecom area so I can
only list software used in this area) and the problem is none of these software
actually exploits RDBMS features. Some of them just got certified on 8.1.7 for
10~11 months and sadly none use 8.1.7 features! They still run on the database
as we were in Dinosaur age (Oracle 7?) And everytime we tried to use some
brillant features or try to optimize some pl/sql proc/functions/packs we have to
ask the software providers first and normally we get a no :-)

Or may be I work in big shop not sure but that's the truth over here, many nice
features none used.... not even when they are suggested due to restriction
placed by third party software providers. Also Oracle is making the software
more self-manageable due to market pressure and it's the direction is heading to

      Rating:
      5
  August 27, 2002
Reviewer:  Andrew
      Schonberger  from Sydney, Australia
>> still need qualified, smart people <<

Tom,

you've made my day. It feels good to read these pages. As the ancient Greeks
used to say, "may the Oracle speak from your mouth".

Apologies for the pun. But the analogy is not far-fetched, since we are talking
about a system of beliefs here, and at stake is our professional future.  I also
apologize to those readers who haven't necessarily heard about the Oracle of
Delphi. With globalization, we might pick up similar terms from other cultures.

Sadly, the opposite school of thought is also gaining ground in the IT industry.
Namely, the emphasys on repeatable processes, and the horror of relying on the
skills of any single individual.   As Oracle licenses don't come cheap, it's not
surprising management wants predictable outcomes.  But I was glad to see Oracle
Inc. has addressed the problem, by making available free copies for developers.
Anyone who wants to learn Oracle can do so, and I've been a beneficiary of this
approach.

Andrew

      Rating: 4
DBA skill set
      trend  August 27, 2002
Reviewer:  Shailesh
       from Pune, India
Thanks Tom.

You explained things in much simple way. I am agree with your opinion. The
people who are afraid of decreasing demand of Oracle DBA, needs to understand
that Law of demand and supply is going to work in each and every area. When
supply will be more then definately demand will come down.

One thing is there, that there can't be increase supply of good DBA in short
time. Since to be good DBA , he needs practical exposure, which will take some
time. By doing some crash courses like programmers, DBA can't have expertise.

Regards,

Shailesh

      Rating: 4
DBA skill set
      trend   March 28, 2003
Reviewer:  Hien
       from Oz
Tom,

Thanks.

After reading this thread I think I need to go back my text books again. It
helps me in reviewing my skills and where I am now, in terms of knowing Oracle.

      Rating:
      5
great!  May 16, 2003
Reviewer:
      Reader  from Houston, TX
Tom, you have provided a great analysis of what makes a dba an acceptable DBA. I
have heard people who hire DBAs say that knowledge of PL/SQL is not needed for a
DBA who manages production database. I strongly disagree with that. What do you
say about this? I think the DBA without an adequate (home much though?)
knowledge in PL/SQL may not learn much as a functioning DBA. There is a common
feeling that PL/SQL is needed only for those who develop applications to work
with Oracle database. Any comments?
Thanks.

          Followup:

I don't see how they could do their job without at least a rudimentary knowledge
of it.

      Rating: 3
DBA without
      pl/sql  May 16, 2003
Reviewer:
      A reader
I have seen a lot "DBA" without pl/sql skills. The only thing they can do is to
do system level cold backup.

When dealing with performance, the only thing they can do is vmstat or hit/miss
ration. They cannt help anything on application architecture design and
application level tunning.

      Rating: 5
Can I run
      something by you?  June 18, 2003
Reviewer:  Arun
      Mathur  from Marietta, GA
Tom,

I agree wholeheartedly with the comment you made below:

"People skills are perhaps more important (at least AS important) as technical
skills."

I see you've dealt with various situations, an I thank you for sharing those
experiences. My question to you is this:

Some businesses may for example, assign one technical lead that oversees all
operations of the dept. The manager basically tells the lead, "Do X,Y, and Z,
and you have 5 people under you to get it done."  I've been in situations before
where the technical lead doesn't have the people skills you mention, and I was
wondering how to deal with these types of people? I've seen two opposite
extremes: One where people constantly clash/shout with the lead, and another
extreme where people just stop caring and simply do what they're told, whether
it's right or not.
Any thoughts on this is greatly appreciated.

Keep up the great work.

Arun

      Rating: 5
PL/SQL needed
      by DBAs  June 27, 2003
Reviewer:  Rajendra
      Majali  from India
What you are saying about need of knowledge of PL/SQL for DBAs is right, but if
you look at OCP curiculam, you will find that till 8i there was somthing related
to PL/SQL, but from 9i there is only sql and not pl/sql for DBA. Does this mean
oracle it self suggests that for DBA pl/sql is not that needed?

          Followup:

You'd have to ask the OCP people I guess.  I'm not in anyway affiliated with
that program.

      Rating: 4
Great
      Info  June 27, 2003
Reviewer:  Brian
       from Phoenix
Along the same lines, do you care what the person's original background is in?
For example, if I wanted to transition to a assistant DBA or developer role from
a finance role, what would I have to do to show that I can perform the job well?

Would being able to answer those questions you gave be a good launch point?

What's the best way to obtain those five years of experience?

          Followup:

no, don't care about the persons background (me, I was a math major who hadn't
touched a computer except for some cobol classes in college -- I found a job as
an "entry level programmer/analyst")

Not sure what to tell you as far as career advice goes.  You'd be "entry level",
a step back in they pay scale perhaps.

      Rating: 5
dba skill set
      trend  June 27, 2003
Reviewer:  Adolph

Tom,
  While I would cherish to have your cubicle (or office ?) right across from
mine, I would dread meeting you for a job interview.

      Rating: 4
Now some math
      questions...  June 27, 2003
Reviewer:
      Robert  from PA
>>.....I was a math major who hadn't
>>touched a computer except for some cobol classes in

So can sometimes take a break from this stuff & ask you math questions like
"differential equations"..."linear algebra"... ? man I gave'em all back to the
professors...

      Rating: 5
DBA skill set
      trend  June 28, 2003
Reviewer:  Poonam
       from Pune,India
Hi Tom

Information provided by you is really very useful
I would like to know one thing
If a fresher wants to get in for DBA  career what are the skills that you would
look in for an entry level DBA.
How can the person pursue a career in DBA

would be thankful if you can provide this information

Regards
Poonam

          Followup:

I'd be looking for a smart, personable person -- for entry level.

they have to be smart.
they have to be likeable.

after that, its all about training.

      Rating: 4
About that
      PL/SQL issue...for Rajendra  June 28, 2003

Reviewer:  Tarry Singh  from Netherlands
Well,

Database administration in itself is not that "duh" kind of job. Learning pl/sql
will be very advantageous to your dba career but you'd be helpful and learn in
pl/sql if...
[1] you have a development team building an application/modelling your db design
etc(most of the times an organization buys a ready made application, if you're
working for such a shop(who developed it) then it's indeed helpful , if not then
it's no point...

[2] DBA job has been made rather easy with tons of those packages(supplied
packages). You'd be able to find practically everthing there...and that's loads
of pl/sql.

But bottom line is that good/strong pl/sql knowledge will help you become a
better dba.

      Rating: 4
sql server dba
      vs oracle dba  June 28, 2003
Reviewer:
      Reader  from Houston, TX
I met someone recently with 6 years of experience as SQL server DBA. This person
has been asked by her management to undergo Oracle DBA training with Oracle
University as she may be asked to manage some oracle databses in the near
future. I was in class (9i DBA Fund 2 by Oracle Education) with this person and
this "expert" sql server DBA was saying in the class that she knows everything
already and the only reason she is in class was because she was forced to
attend. She also was saying that being a sql server dba for more than 6 years is
adequate enough to manage oracle databases as she need not have to learn
anything special about Oracle dba tasks. I always thought as myself being an
Oracle dba with more than 3 years experience that I still have so much to learn
about Oracle as it is a ocean. I don't think being a sql server dba would
qualify someone to manage oracle databases without any training either inhouse
or at Oracle Education? Any comments? Thanks.

          Followup:

She is in for a shock, as much of a shock as you would be if it were going the
other way.

Ask her to

a) draw a picture of Oracle on the whiteboard and describe how the components
work together.

b) quick -- what is in a control file and why is it there, whats important about
them.

c) how is it that Oracle can have transactions that generate many times the
amount of redo then we have configured on disk.  I mean, I can run a transaction
that generates 100meg of redo yet only have 10m of redo configured.  What is the
mechanism that allows me to do that (and as a bonus tell me "why is it relevant
that an Oracle dba knows the answer to this")

d) ok -- you need to move your system datafiles from /d01 to /d02 -- how do you
do that?

e) what is the architecture of a LMT and why are they superior to a DMT and tell
me -- how do you know which you are using?

She will know in 5 years exactly how much she did not know 5 years ago.  Only
she'll spend the first 4 years cursing because "this Oracle thing isn't just
like SQLServer, why not".  Hope she takes some notes in class.

      Rating: 2
Poooh...for my
      houston friend  June 28, 2003
Reviewer:
      Tarry  from Netherlands
Well noone(besides Tom here) will be able to answer that question better than
me.

I'm working 2 weeks only as a sql server dba(after having 3 odd years of exp on
oracle since 734>9i) and I am getting bored. I mean it. And I'm not saying I
know everything on sql server. Well I've gone thru all the routines dbcc's most
of them. I'm going through all the *.chm files(bol)although they're far far
inferior compared to oracle's online manuals or pdf's(for printing)And what is
all that the sql server dba does is
dbcc checkdb('dbname', noindex) if something's wrong. The guy who I am replacing
create's simplw sql's thru the em(you know right click table, design..) I cringe
at that view alone.

How I got the job, my boss has been an oracle dba. We'll I'm trying to do my
best, I've reverse engineerd the db and have asked for my own independent test
environment...
Well I do have my latest oracle db running at home to "play" when I'm back from
work.

Go figure.;)

      Rating:
      4
  June 29, 2003
Reviewer:  TJ Mitra
       from Wellington, New Zealand
Hello Tom

As we have graduated from Oracle 6 to Oracle 9i, our quality of DBA work has
greatly improved. What used to take long time ( like export/import - replaced by
alter table move, alter index rebuild online etc) or required an outage of the
database ( for changing db_block_buffer - replaced by online modification of
db_cache_size ) is now a history. For these very reasons and for introduction of
many advanced features, the Oracle DBAs these days can contribute in a far more
effective & intelligent way towards better database management & administration.
Thanks to Oracle for bringing quality in the RDBMS technology.

June 29, 2003 Sun -- Thanks for the question regarding "Global
      vs Sys_Context", version 8i

      You Asked
Hi Tom,
      Why We not just use the Global varibal to Replace the Sys_context. What is
the advantage and disadvantage of it? Thanks!

      and we said...
o sys_context works in regular SQL.  a global variable works only in PLSQL.

o sys_context can be used where a global could not, see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279
for an example.  That would be impossible with globals.

o sys_context works (can be set) from a logon trigger, globals would not
o sys_context is dynamic -- you can add whatever variable/value pairs you want
to.  globals -- static namespace defined at compile time.

But hey -- if the global works for you -- use it.  It is strongly typed (number,
date, varchar2, etc) and doesn't need to be converted using to_number/to_date.

Review &
      Followup

      Rating: 5
strongly typed
      --Ummmmm ??  September 14, 2001
Reviewer:
      A reader
tell us more about 'strongly typed', and related things

          Followup:

A sys_context variable doesn't have a datatype -- its just a string.

If you stick a number in there, its a string.
If you stick a date in there, its a string.

Package variables (globals) have a datatype -- number, varchar2(20), date, etc.

If you code:

....
   g_global_date  number default sysdate;
....

procedure p
is
begin
   for x in ( select * from t where x = g_global_date ) loop
   ....

when we put the date in there, it is stored as a date (it is STRONGLY typed).
On the other hand, if we put that into an application context, it'll just be a
string and you would have to "to_date" it to make it a date:

   for x in ( select * from t where x =
          TO_DATE( sys_context('my_ctx','global_date'), 'some date format' ) )

      Rating:
      4
  September 15, 2001
Reviewer:  Sun
       from HK
Thank you!

      Rating: 5
Sys context and
      PLSQL types  June 29, 2002
Reviewer:  tom

You mention that everything in the sys context is stored as a string....does
this mean it is not possible to store a user defined type in the sys context,
for example, would I be able to store a record type in the sys context?

          Followup:

Nope, not unless you encoded it into a string.  sys_context is a simple
"variable name", "variable value" pair where the value is a string.

      Rating:
      4
sys_context  January 29, 2003
Reviewer:
      mo
Tom:

when I do:

select sys_context( 'userenv', 'os_user' ) from dual;

it gives me my personal unix account name.

I am logging in with generic oracle account (dev/dev) using sql*plus for windows
and not logging into unix. how does oracle know who am i.  Is it because when
dba set the oracle account he linked it to my name somehow.

Thank you,

          Followup:

it is the OS username of the OS you logged in from.

Your windoze account name must be the same as your unix account.

      Rating:
      3
j.  June 29, 2003
Reviewer:
      A reader
hi tom,

is there a way to get session contexts initialized based on ROLES granted to the
users creating the sessions?

for example: contexts and roles are bound to applications

application/schema/context/role A
application/schema/context/role B

user X should have context A intialized if role A is granted (in order to use
application A).

what is the "best practice" to achieve this or would you in general leave this
to the client parts of the applications since they determine the CURRENT role of
their users?

          Followup:

you either do that upon login (in a login trigger) or in the application itself.
 There is no facility to have a role populate a context, however, there are
application roles (roles only enabled via a stored procedure call) that would be
able to a) enable role and b) setup context.

June 29, 2003 Martin -- Thanks for the question regarding "How
      can I pipe the SQL-output in SQL*Plus to another programm?", version

      You Asked
How can I pipe the SQL-output in
SQL*Plus to another programm?

Is there any way with the SPOOL command?

Thank you for help
kind regards
Martin Fuhr

      and we said...
Well, you can always redirect:

$ sqlplus scott/tiger @somefile | some_other_program

but named pipes will work as well, for example -- I going to
spool the output of select * from emp to GZIP and compress it.
I then zcat that file just to show it works:

--------------------------------------
set echo on

host rm test.lst
host mknod test.lst p
set scan off
host gzip < test.lst > test.lst.gz &
set scan on

set termout off
spool test
select * from emp;
spool off
set termout on

host zcat test.lst.gz
--------------------------------------

so, that makes a pipe "test.lst".  We start a program in the
background that will "eat" off of the pipe.  We then write data
to the pipe by spooling.  when we stop spooling -- the process
in the background gets and EOF and exits.

Review &
      Followup

      Rating: 4
spooling the
      output to more than one file  March 05, 2002

Reviewer:  nagaraju  from usa
can i spool the output of query to more than one file.
this becuase, i have a huge query output that returns around 80,000 records. so,
when i spool that
to a dat file. its size around 40MB. is there a way i can spool to multifiles
like
file1.dat
file2.dat
file3.dat
.....

file100.dat

thanks
nagaraju

          Followup:

No there is not.  However, I have a pro*c program -- array_flat -- that you
could easily adapt to do just that.

search this site for array_flat.  its a pro*c program that takes a query and
prints the output to stdout.  You would just pipe that to split on UNIX or
modify it to open/write some/close/open/write some/close in C.

I guess you could use sqlplus to stdout and pipe to split as well if you wanted
on Unix.

      Rating: 5
a little
      different question  June 29, 2003
Reviewer:
      Mike Chin  from NY, NY
I am working the test Oracle backup/restore on Solaris.
I have been testing the tar compress zcat unix files

1)    tar cvf - /dir1/myhome | compress > /tmp/my_home_tmp/myhome.tar.Z

This worked fine, I got the compressed myhome.tar.Z

2)    Now I want to uncompress myhome.tar.Z to new location owned by myself on
the same machine:
cd /tmp/my_home_tmp2
zcat /tmp/u4immszb_temp/u4immszb.tar.Z | tar xvf –

It will always to uncompress to the original location , /dir1/myhome.
Why?

TIA

          Followup:

it'll depend by tar types but it is probably because you added files like
/dir1/myhome/foobar.txt, /dir1/myhome/mysubdir/hello.world

not ./foobar.txt  ./mysubdir/hello.world

the files had absolute paths.

do this instead

(cd /dir1/myhome; tar cvf - .) | compress > /tmp/my_home_tmp/myhome.tar.Z

use relative paths (if you tar xvf as root it will strip the leading / by
default)

      Rating:
      5
thanks!!  June 29, 2003
Reviewer:
      Mike  from NY,NY

June 30, 2003 sean -- Thanks for the question regarding "IOT
      and more", version 8.0.6

      You Asked
Tom,

http://asktom.oracle.com/pls/ask/f?p=4950:8:65968::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:627128171401,%7Biot%7D
you stated the following, I am really not understanding why each row for each
block for regular table instead of may rows in a block for IOT. Could you
explain more on this?

Also if you can demonstrate by examples would be sppuer.

Thanks

      and we said...
The moral of the story I was trying to get across in that question was that we
can use an IOT to force physical co-location of data and for data that should be
"near" eachother this is good.

Ok, here is the scenario.  Every night you get a dump of 1,000 stock symbols,
the date, and a closing price.  It takes 2 database blocks to hold this data
(500 rows/block).  Lets say the first line of data is always the "ORCL" stock
information.  One day one of your system, block 1 of your table will have the
stock price for ORCL.  One day two of your system, block 1 will have day one but
block 3 will have day 2.  On day three - blocks 1, 3, 5 have ORCL data.  On day
four -- 1,3,5,7.  And so on.

If, on the other hand, we used an IOT (or a cluster or hash cluster for that
matter), and the primary key of our table was SYMBOL, TICKER_DATE -- we would
find that all 4 rows of ORCL data would be physically contigous....

to make this "more real", here is the demo.  Start with the tables i used:

create table heap_stocks
( symbol    varchar2(4),
  ticker_dt date,
  price     number,
  constraint heap_stocks_pk primary key (symbol,ticker_dt)
)
/

create table iot_stocks
( symbol    varchar2(4),
  ticker_dt date,
  price     number,
  constraint iot_stocks_pk primary key (symbol,ticker_dt)
)
organization index compress 1
/

To populate this, I simulated a nightly load of 1000 stocks for 200 days:

begin
    for I in 1 .. 200
    loop
       insert into heap_stocks
       select to_char(rownum,'fm0009'), trunc(sysdate)+i, rownum
         from all_objects
        where rownum <= 1000;

       insert into iot_stocks
       select to_char(rownum,'fm0009'), trunc(sysdate)+i, rownum
         from all_objects
        where rownum <= 1000;
       commit;
    end loop;
end;
/

Then, to flush the buffer cache so we can see the physical IO each would have to
do (giving us a clear indication of the number of blocks we would have to have
on hand to answer the query) I did the following:

alter tablespace users offline;
alter tablespace users online;

Now, we are ready to see what the difference is -- I ran:

alter session set sql_trace=true;
select avg(price)
  from heap_stocks
 where symbol = '0001'
/
select avg(price)
  from iot_stocks
 where symbol = '0001'
/

and the results are simply put -- astoundingly different:

select avg(price)
  from heap_stocks
 where symbol = '0001'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.05       0.22        203        203          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.05       0.22        203        203          0           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
    200   TABLE ACCESS BY INDEX ROWID HEAP_STOCKS
    201    INDEX RANGE SCAN (object id 71363)

select avg(price)
  from iot_stocks
 where symbol = '0001'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.01          3          3          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.01          3          3          0           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
    200   INDEX RANGE SCAN (object id 71365)
So, we did 203 physical IO's to process the HEAP table.  What that tells me is
that our data for stock symbol 0001 is spread out on 200 blocks (200 days, 200
blocks).  In order to cache the results for this query, we need 200 block
buffers.  We needed to do that IO to get the answer initially.

Now, looking at the IOT we did 3 physical IO's -- we cached 3 blocks -- and got
the same answer!  Not only that but by using index key compression I was able to
remove he redudant 0001's from the data -- we can cache this much more
efficiently and getting it the first time takes seriously less IO.  Very nice.

Review &
      Followup

      Rating: 4
Why the inserts
      on IOT have more PHY reads?  August 18, 2001

Reviewer:  Sean  from Atlanta
I traced the inserts, here are the results of

INSERT INTO HEAP_STOCKS SELECT TO_CHAR(ROWNUM,'fm0009'),TRUNC(SYSDATE) + :b1 ,
  ROWNUM   FROM ALL_OBJECTS  WHERE ROWNUM <= 1000

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.12       0.12          0          0          0           0
Execute     10      4.08       6.58          1      31102       7484       10000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       11      4.20       6.70          1      31102       7484       10000

And:

INSERT INTO IOT_STOCKS SELECT TO_CHAR(ROWNUM,'fm0009'),TRUNC(SYSDATE) + :b1 ,
  ROWNUM   FROM ALL_OBJECTS  WHERE ROWNUM <= 1000

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.15       0.24          0          0          0           0
Execute     10      3.45       6.71         83      31761       9712       10000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       11      3.60       6.95         83      31761       9712       10000

          Followup:

The physical IO's were due to the optimizer picking a different plan for SELECT
portion of the query (look at your tkprof you have, the execution plan of the
QUERY should be different).

I modified the example as follows:

create table t as select rownum r from all_objects where rownum <= 1000;

alter session set sql_trace=true;

alter tablespace users offline;
alter tablespace users online;

begin
    for I in 1 .. 200
    loop
       insert into heap_stocks
       select to_char(r,'fm0009'), trunc(sysdate)+i, r from t;

       insert into iot_stocks
       select to_char(r,'fm0009'), trunc(sysdate)+i, r from t;
       commit;
    end loop;
end;
/

so the plans would be the same (both must do a full scan of this tiny table T).
Now the tkprof shows for the inserts:

INSERT INTO HEAP_STOCKS SELECT TO_CHAR(R,'fm0009'),TRUNC(SYSDATE) + :b1 ,R
  FROM T

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute    200     17.10      51.05          7      14170     244301      200000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      201     17.10      51.05          7      14170     244301      200000

Rows     Row Source Operation
-------  ---------------------------------------------------
 200200  TABLE ACCESS FULL T

********************************************************************************

INSERT INTO IOT_STOCKS SELECT TO_CHAR(R,'fm0009'),TRUNC(SYSDATE) + :b1 ,R
  FROM T

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute    200     19.00      58.27          5      62238     224358      200000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      201     19.00      58.27          5      62238     224358      200000

      Rating:
      4
  August 18, 2001
Reviewer:  Sean
       from Atlanta
As your directed, I traced it again according your advise. Got the similar as
yours.

For the inserts on IOT, Oracle did alomst twice logical reads as comparison to
the non-IOT. From the tkprof Oracle did a lot the SYS objects maintaining as
shown below as part of the execution plan of inserting of the IOT. Is it fair to
say intensive DMLs, not including SELECT, SLOWER as comparison with non-IOT?

---

INSERT INTO IOT_STOCKS SELECT TO_CHAR(R,'fm0009'),TRUNC(SYSDATE) + :b1 ,R
  FROM T

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.09       0.09          0          0          0           0
Execute     20      3.89      13.06         12       3118      25518       20000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       21      3.98      13.15         12       3118      25518       20000

Misses in library cache during parse: 1
Misses in library cache during execute: 1
Optimizer goal: CHOOSE
Parsing user id: 37     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID FILE$
      2   INDEX RANGE SCAN (object id 39)
     11  TABLE ACCESS CLUSTER FET$
      2   INDEX UNIQUE SCAN (object id 7)
      0  TABLE ACCESS CLUSTER FET$
      2   INDEX UNIQUE SCAN (object id 7)
      0  TABLE ACCESS CLUSTER UET$
      2   INDEX UNIQUE SCAN (object id 9)
      1  UPDATE TSQ$
      2   TABLE ACCESS CLUSTER TSQ$
      2    INDEX UNIQUE SCAN (object id 11)
      1  DELETE FET$
...
1  UPDATE SEG$
      2   TABLE ACCESS CLUSTER SEG$
      2    INDEX UNIQUE SCAN (object id 9)
  20020  TABLE ACCESS FULL T

          Followup:

Well, I have the direct comparision in the previous followup.  In a nutshell:

HEAP:   14,170+244,301  = 258,471 logical IO's
IOT:    62,238+224,358  = 286,596 logical IO's

scarcely 2x

I see the references to fet$/uet$ in yours, perhaps the difference is that I use
LMT's exclusively (locally managed tablespaces) and avoid the recursive
spacemanagement you see to be seeing.  Try it with an LMT, looks space mgmt
related to me.

My times are not that different either (nor were yours) the IOT performed faster
in your one example (but that could be due to the different query plans for the
select) and when the plans were the same it looked like this:

what     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
heap:     201     17.10      51.05          7      14170     244301      200000
iot:      201     19.00      58.27          5      62238     224358      200000

not too different.

Given that I was loading the data nightly in this example and then querying it
frequently during the day -- the payoff was immense.  Even in other
circumstances -- the REDUCED query strain on the system would most like well
offset any INCREASED strain due to the IOT structure.

As in all cases, benchmark it in your system -- but don't just benchmark 1/2 of
the problem -- sure, the insert takes more but look at the potential payBACK
from the select.  You could find that when both are looked at.....  you'll like
what you see.

      Rating: 3
CTAS to
      IOT  June 30, 2003
Reviewer:  Alvin
       from Philippines
Is it possible to "transfer" the contents of a Heap table to an IOT via CTAS ?

          Followup:

ops$tkyte@ORA920> create table t ( x int primary key, y date );

Table created.

ops$tkyte@ORA920> insert into t select user_id, created from all_users;

41 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t2 ( x primary key, y ) organization index
  2  as
  3  select x, y from t;

Table created.

      Rating:
      5
Thanks  June 30, 2003
Reviewer:
      Alvin  from Philippines
This is unbelievably useful !

I even tried it with multiple columns defined as primary key the sql is as
follows.

create table t2 (x, y, constraint t2_pk primary key (x,y))
organization index
as select x,y from t
/

  1  select  CONSTRAINT_NAME
  2  , TABLE_NAME
  3  , COLUMN_NAME
  4* from  dba_cons_columns where table_name ='T2'
02:44:35 sys@RTBE.CRONOS> column column_name format a30
02:44:45 sys@RTBE.CRONOS> /

CONSTRAINT_NAME                TABLE_NAME                     COLUMN_NAME
------------------------------ ------------------------------
--------------------
T2_PK                          T2                             X
T2_PK                          T2                             Y

June 30, 2003 Lisa -- Thanks for the question regarding "How to
      control the firing of a Trigger", version 8.1.5.0

      You Asked
I have a trigger on insert, update and delete on a table.  The
trigger will insert into another trigger if partnumber or
description are changed,  else the insert does not take place.
The problem is that the trigger has to fire to check the
condition of partnumber or description being changed.  The main
table has 4 million records,  and the table that it inserts into
has 40 million records.  If I do a big update the the main table
the it takes forever and the database hangs and I have to kill
the process.  Is there a way to control the firing of the
trigger.

Thanks
Lisa

CREATE OR REPLACE TRIGGER MaintainSearchKey
BEFORE INSERT OR UPDATE OR DELETE ON availablelots
FOR EACH ROW
DECLARE
  v_StrLength NUMBER(4);
  v_StartPoint NUMBER(4);
  v_EndPoint Number(4);
  counter number(4);
  theStr availablelots.description%type;

BEGIN
  IF DELETING THEN
    DELETE FROM thekey2 where thekey2.inventoryid =
:old.inventoryid;
    DELETE FROM keyword_keys where inventoryid =
:old.inventoryid;
    return;
  END IF;

  IF UPDATING THEN
   if (:old.partnumber = :new.partnumber) and (:old.description
= :new.description) then
      return;
    else
       DELETE FROM thekey2 where thekey2.inventoryid =
:old.inventoryid;
       DELETE FROM keyword_keys where inventoryid =
:old.inventoryid;
    end if;
  END IF;

 -- Build new searchkey entries in thekey2 table.
  INSERT INTO thekey2 (inventoryid,searchkey) VALUES
(:new.inventoryid, upper(:new.partnumber));
  FOR i in 2..length(:new.partnumber)-3 LOOP
    INSERT INTO thekey2 (inventoryid,searchkey) VALUES
(:new.inventoryid, substr(upper(:new.partnumber),i) );
  END LOOP;

  if :new.description is not null then
     --Build the description entries into thekey2 table
     INSERT INTO thekey2 (inventoryid,searchkey) VALUES
(:new.inventoryid, upper(:new.description));
     FOR i in 2..length(:new.description)-3 LOOP
       INSERT INTO thekey2 (inventoryid,searchkey) VALUES
(:new.inventoryid, substr(upper(:new.description),i) );
     END LOOP;

     counter := 0;
     v_StartPoint := 1;
     theStr := upper(rtrim(ltrim(:new.description)));
     if theStr is not null then
     loop
        v_EndPoint := instr(theStr,' ',v_StartPoint,1);
        if v_EndPoint > 0 and v_EndPoint <> v_StartPoint then
           begin
               INSERT
               INTO keyword_keys (inventoryid,keyword)
             VALUES
(:new.inventoryid,substr(theStr,v_StartPoint,v_EndPoint-v_StartPo
int));

             exception
               when DUP_VAL_ON_INDEX then
                  NULL;
             end;
        else if v_EndPoint <> v_StartPoint then
                begin
                  INSERT
                    INTO keyword_keys (inventoryid,keyword)
                  VALUES
(:new.inventoryid,substr(theStr,v_StartPoint));
                  exit;
                  exception
                    when DUP_VAL_ON_INDEX then
                      NULL;
                end;
             end if;
        end if;
        v_StartPoint := v_endPoint+1;
        counter := counter + 1;
        if counter > 255 then

           DELETE FROM keyword_keys where inventoryid =
:new.inventoryid;

           INSERT INTO sys_errors values
               ('MAINTAIN_KEYWORD_TRIGGER',
                 SYSTEM_ERRORS.KEYWORD_IND_NUM,
                 SYSTEM_ERRORS.KEYWORD_IND_MSG
||:new.inventoryid,
                 sysdate);
            exit;
        end if;
     end loop;
     end if;
  end if;
END MaintainSearchKey;

      and we said...
Lisa -- you left the domain off of your email address, thats why
this is so long in coming to you.

Well, if you can do this in "isolation" (that is -- you are the
only one who will be updating the table during this time) and
you just want to disable the trigger, you can:

ops$tkyte@8i> alter trigger MaintainSearchKey disable;

Trigger altered.

ops$tkyte@8i> REM do the big update ...
ops$tkyte@8i> alter trigger MaintainSearchKey enable;

Trigger altered.

Alternately, if you are not updating the columns the trigger
"cares" about, you can code the trigger as I have below:

ops$tkyte@8i> create table t (x int, y int, z int );

Table created.

ops$tkyte@8i>
ops$tkyte@8i> create or replace trigger MaintainSearchKey
  2  before insert or update of y,z or delete on T
  3  for each row
  4  begin
  5          dbms_output.put_line( 'I fired...' );
  6  end;
  7  /

Trigger created.

ops$tkyte@8i>
ops$tkyte@8i> insert into t values ( 1, 1, 1 );
I fired...

1 row created.

ops$tkyte@8i>
ops$tkyte@8i> update t set x = 2;

1 row updated.   Note: Trigger did not fire

ops$tkyte@8i> update t set y = 2;
I fired...       Note: Trigger fired because we updated y

1 row updated.

ops$tkyte@8i> update t set z = 2;
I fired...       Note: Trigger fired because we updated y

1 row updated.

Review &
      Followup

      Rating: 3
Database
      Trigger  May 01, 2002
Reviewer:  Shajan
      Joseph  from Chennai, India

      Rating: 5
question
      dealing with ORA-02292 child record found  March 13, 2003

Reviewer:  A reader
Hi

I have a question regarding TRIGGERS

I have a trigger when a child table record is deleted then the trigger fires and
tries to delete the parent record, for example when I delete employee FORD whose
DEPTNO is 10 the trigger will try to delete deptno 10 in dept table. Obviously I
get ORA-02292 however I tried to deal with this as an exception in the trigger
but it does not work!

my code is

create or replace trigger emp_trg
after delete on emp
for each row
declare
  child exception;
  pragma exception_init(child, -2292 );
begin
  insert into x values(:old.deptno);
  delete dept where deptno = 10;
 exception
   when child
  then
    raise_application_error(-20001, 'ERROR1');
end;
/

insert into x is a debug part, checking if triggers gets fired

          Followup:

can you give me a REAL WORLD example whereby this would actually be useful?

      Rating: 2
Exception
      handling  June 30, 2003
Reviewer:  binu
      sakharia  from T.N(India)
greetings,

This question is regarding Java and Oracle and I wish u could help me!!

            Class.forName("oracle.jdbc.driver.OracleDriver");
            Connection conn =
DriverManager.getConnection("jdbc:oracle:thin:@"+ip_address+":"+port_no+":"+sid,
user, paswd);
String Sql_stmt=<<insert statement>>
            stmt=conn.createStatement();
            try
            {
                res_set=stmt.executeQuery(Sql_stmt);
            }
            catch(Exception e)
            {
                chk_flag=3;
                write_log_file(Sql_stmt,e,"o");
            }

this is my java coding and I have oracle trigger. This trigger takes the
voucher_no,debit_amount and credit_amount of the new row inserted into table
"voucher" and checks for the availablity of the voucher_no in table "GLBAL" and
correspondingly update the debit and credit amount of the voucher_no in the
GLBAL.When the voucher_no is not found in the "GLBAL" the trigger automatically
fails thus insertion into the voucher also fails.The exception raised by the
oracle trigger cannot be captured by the try..catch block in the java
program.The program is executed as if there is noo error occured.but in reality
the rows has not been inserted into oracle. so is there any work around for
this.

Thanxs in advance

          Followup:

#1 -- you must, as in have to, use bind variables -- especially for an
update/insert/delete.  It is imperative.

#2 -- i cannot reproduce your findings.  No versions, no platform, incomplete
test case.  Sorry.  My tests work fine.

ops$tkyte@ORA920> create table t ( x int );

Table created.

ops$tkyte@ORA920> create or replace trigger t_trigger
  2  before insert on t for each row
  3  begin
  4     raise program_error;
  5  end;
  6  /

Trigger created.

public static void Bind( int input )
throws SQLException
{
PreparedStatement stmt = conn.prepareStatement("insert into t values ( ? )");

        try
        {
            stmt.setInt(1,input);
            stmt.executeQuery();
            stmt.close();
            System.out.println( "Success" );
        }
        catch (Exception e)
        {
            System.out.println( e.getMessage() );
        }

}

public static void main(String[] args) throws Exception
{
    DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
    conn = DriverManager.getConnection
    ("jdbc:oracle:thin:@aria-dev:1541:ora920"
    //("jdbc:oracle:oci8:@ora816dev.us.oracle.com"
    ,"ops$tkyte","megan95");

    conn.setAutoCommit(false);
    Bind(5000);
    conn.close();
}

$ java test
ORA-06501: PL/SQL: program error
ORA-06512: at "OPS$TKYTE.T_TRIGGER", line 2
ORA-04088: error during execution of trigger 'OPS$TKYTE.T_TRIGGER'

error raised, exception caught.

June 30, 2003 Darryl -- Thanks for the question regarding
      "Storing Date and Time in Separate fields Vs Using the same field",
      version 7.3.4

      You Asked
Hi Tom,

My application uses separate fields for storing time components separately.
There are calculations to compute the next available  open session based on the
particular time of the day. When the sessions (slots) gets filled up, the next
available session is allocated whether the same day or the next day. The
sessions are pre-defined at the specific time of the day with pre-defined number
of slots. I am storing the contact date and time as well as the session date and
time in separate fields. Also I am storing the "Session Time" available and
number of slots in different fields ie Time_slot Varchar2(4)-- 24 hour format,
Slots NUmber. Is it a good idea to store date and time  in separate fields or in
the same date time fields. What would you suggest out of own experience? It is
also required that the time be displayed on the 12 hour format in the forms. I
would appreciate your valued help on this.

Thanks

      and we said...
YUCK

just use a single date field -- 7 bytes.  You can display it in ANY format you
like using to_char(), you can input with any format you like using TO_DATE.

You can get differences between two dates, sort them, max them, whatever...

Never never use a number or character string to store that which has a native
format like a DATE.  Don't use two fields either -- they always go together and
you can always mask one or the other off for display.
Review &
      Followup

      Rating:
      3
  November 19, 2001
Reviewer:
      A reader
Thanks Tom

      Rating:
      5
  November 20, 2001
Reviewer:
      A reader  from new york
Could you please clarify ? Is there an implication to use DATE data type when
you need just the time component for example ( time_scheduled varchar2(4) versus
( time_scheduled date )

          Followup:

If you have just a time (some representation of hours/minutes or whatever) feel
free to store it in a character string/number or date.  in Oracle9i we would
store this in an INTERVAL type specifically designed to store durations.

      Rating: 5
YUCK... or just
      a reflection  November 20, 2001
Reviewer:  Andre
      Whittick Nasser  from Rio de Janeiro, Brazil
Tom,

I have to admit you are an onomatopoeic guy ! Your interjections are sometimes
exactly what it takes to convey the essence of some questions around here.

Do not think I am abstaining from it... I know I myself have polluted your site
with plenty of nonsense. But, is it not what forums are for after all ?

      Rating:
      1
  May 07, 2003
Reviewer:  ZAlex

"You can get differences between two dates,
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^???
sort them, max them, whatever..."

FUNCTION AAAA() Return Number
IS
vlDate_Begin Date;
vlDate_End Date;
vlInterval_between INTERVAL DAY(3) TO SECOND(0);
vlRDV_MIN integer;
BEGIN
  vlInterval_between := (vlDate_End - vlDate_Begin);
                                    ^^^Error
END;

Ora/doc/9.2/b10501_01/appdev.920/a96624/03_types.htm#41556
ok
Ora/doc/9.2/b10501_01/server.920/a96540/sql_elements2a.htm#54201
not ok
I cant't understand this:
Datetime    -    Datetime    Interval    (a)
(a) This operation is not valid for DATE values.

thanks

          Followup:

well, it is quite simply because an interval is what is returned from the
difference between two TIMESTAMPS.

A number is what is returned from the difference of two DATES.  Therefore, your
code would resemble:

ops$tkyte@ORA920> create or replace function AAAA( vlDate_Begin in timestamp,
vlDate_End timestamp )
  2  Return interval day to second
  3  IS
  4    vlInterval_between INTERVAL DAY(3) TO SECOND(0);
  5  BEGIN
  6    vlInterval_between := (vlDate_End - vlDate_Begin);
  7    return vlInterval_between;
  8  END;
  9  /

Function created.

OR

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace function bbbb( vlDate_Begin in date,
vlDate_End in date )
  2  Return number
  3  IS
  4    vlRDV_MIN number;
  5  BEGIN
  6    vlRDV_MIN := (vlDate_End - vlDate_Begin);
  7    return vlRDV_MIN;
  8  END;
  9  /

Function created.

and this is what you would expect:

ops$tkyte@ORA920>
ops$tkyte@ORA920> select aaaa( sysdate, sysdate+.4321 ),
  2         bbbb( sysdate, sysdate+.4321 )
  3    from dual;

AAAA(SYSDATE,SYSDATE+.4321)
BBBB(SYSDATE,SYSDATE+.4321)
---------------------------------------------------------------------------
---------------------------
+000000000 10:22:13.000000000
            .432094907

ops$tkyte@ORA920>

the interval shows there are 10 hours, 22 minutes and 13 seconds difference
between the two, whereas the date shows there is .4321 (approximately) between
the two.

      Rating: 4
... and before
      anyone asks  May 07, 2003
Reviewer:
      A reader
... (sysdate+0.4321)-sysdate evaluates to .432094907 and not 0.4321 because
Oracle calculates the required time to the nearest second.  So in this case it's
10 hours, 22 minutes 13 seconds, while 0.4321 is acually 10 hours, 22 minutes
13.44 seconds.

      Rating: 4
How to store
      time in a separate column without any data-type
      convertion  June 30, 2003
Reviewer:  Praveen
      KV  from Bangalore
Hi Tom,

1) I understand your explanation of storing date & time together. But if I use
only the time part in my database (and never the date part), dont you think it
is a waste of space to store the date in the default format (ie both date and
time together)?

2) How can I store date and time parts in separate fields? ie without
converiting them into any other data type.

3) If I use the default date format, how can I display the date and time part
separately? Here I may even need to compare the time part with the time value
contained in a variable.

Your help is highly appreciated.

Thanks and regard

Praveen

          Followup:

1) in oracle9i, there is an interval type.  No, I don't think it is a waste --
if you store it in a character string, hh:mi:ss will take more space then a
date.  if you store it in a number, you might save 2 bytes or so per record.
sort of insignificant. (and an interval in 9i, while only storing some period of
time, will consume even more bytes but anyway)

2) you cannot, not in 8i.  in 9i, you can store a date and an interval (period
of time) separately if you like -- but if space is your overriding concern, that
would take even more room.

3) you won't be using the default date format then.

select to_date( dt, 'dd-mon-yyyy' ), to_date( dt, 'hh24:mi:ss' ) from t;

      Rating: 3
useful but
      would like to have more info if you can.  June 30, 2003

Reviewer:  Lebon Mathew  from San Francisco, CA , USA

We use Oracle Forms as our front end. We created a diffault forms out of the
date field . What we need is if forms did a query for a particular date we want
all the records for that date displayed.  this can be attained if we store the
dates with out time .. in trigger put a (trunc (sysdate)) , but at the same time
we need to make calculations on howmany mins it took to finish the Service call
so that we can messure the productivity of that call agent.  At this case we are
storing date in one field and and datetime in another ... Is it ok ? is there
any simple way in forms by which we can display all the records for a particular
date from the datetime field.

          Followup:

so, you would search:

where dt >= trunc(:dt_input) dt < trunc(:dt_input)+1

that gets everything in that day.

you want A SINGLE, SOLE (eg: one) field.

June 30, 2003 Sajid -- Thanks for the question regarding "DATE
      to TIMESTAMP and vice-versa", version 9.0.2

      You Asked
Hi Tom,

Here is a very simple question for you. If I can change the datatype of a column
from DATE to TIMESTAMP even when it contains the data, why cant I change it back
to DATE. I get an error message. Below is my test :

SQL> create table test ( e_date date );

Table created.

SQL> insert into test select sysdate from dual;

1 row created.

SQL> select * from test;

E_DATE
---------
30-JUN-03

SQL> alter table test modify e_date timestamp;

Table altered.

SQL> select * from test;

E_DATE
---------------------------------------------------------------------------
30-JUN-03 03.18.17 PM

SQL> alter table test modify e_date date;
alter table test modify e_date date
                        *
ERROR at line 1:
ORA-01439: column to be modified must be empty to change datatype

SQL>

Thanks in advance.

Regards,
Sajid Anwar
London.

      and we said...
because a timestamp is more precise then a date and going from less precise to
precise is OK (no loss of data) where as going from more precise to less precise
is NOT ok.

it is for the same basic reason as this:

ops$tkyte@ORA920> create table t ( x number(5), y varchar2(20) );

Table created.

ops$tkyte@ORA920> insert into t values ( 1, 'x' );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table t modify x number(10);

Table altered.

ops$tkyte@ORA920> alter table t modify x number(5);
alter table t modify x number(5)
                     *
ERROR at line 1:
ORA-01440: column to be modified must be empty to decrease precision or scale

Ok to go "bigger", not "smaller" and a timestamp (since it has the potential for
fractional seconds) is "bigger" then a date.

Review &
      Followup

      Rating: 5
Brilliant
      Answer  June 30, 2003
Reviewer:  Sajid Anwar
       from London

June 30, 2003 Daniel -- Thanks for the question regarding
      "Connection times out after awhile", version 9.2.0

      You Asked
Hi Tom,
   I just wanted to know if, when running a PL/SQL program, it's possible to
keep the connection alive so that no network disconnect occurs? We have to live
with a 2.5 hours network disconnect time. The PL/SQL program runs for about 12
hours (yes it's tuned properly. It just deals with gazillions of records).

Thanx

      and we said...
run it in the background using DBMS_JOB is what I would do.  I would make sure
it had a healthy amount of calls to dbms_application_info.set_session_longops so
I can monitor it.  I would use

dbms_job.submit(:n,'that_procedure;');
commit;

to submit it (making sure that job_queue_processes > 0)

Then, just watch it run in the background.

That by far would be the best as you are not reliant on the client workstation
"staying available".

You can enable sqlnet dead client detection (DCD) -- that'll cause the server to
ping the client every now and again with an "are you alive" message.  But -- I
would use dbms_job.
June 30,
      2003 -- Thanks for the question
      regarding "Subtracting from a large table", version 8.1.7

      You Asked
I have a large table of, say, cust_id and a balance of some sort. This has
millions of rows.

I have a daily transaction table with just the cust_id and the balance.

I want to reduce the balance of the large table based on the transactions in the
delta file.

Yes, I can do
for rec in (select cust_id,balance from small_table)
loop
update large_table set balance=balance+rec.balance
where cust_id=rec.cust_id;
end loop;

Is there a way I do it without the PL/SQL i.e. just using a UPDATE statement?

Also, if the update doesnt touch any rows, I want to insert the row in the large
table. This would be like a 'if sql%rowcount = 0 insert...' in the loop above.

Thanks

      and we said...
In 9i, this is trivial.  It is called MERGE.

In 8i, this is hard (the update or insert part).  You have to do it in at least
two statements.

ops$tkyte@ORA920> create table large
  2  as
  3  select rownum cust_id, trunc(dbms_random.value(1,100)) bal
  4    from all_users
  5   where rownum <= 10;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table small
  2  as
  3  select rownum cust_id, 1 amt
  4    from all_users
  5   where rownum <= 5;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into small
  2  select rownum-3, 1
  3    from all_users
  4   where rownum <= 5;

5 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from large order by cust_id;

   CUST_ID        BAL
---------- ----------
         1         58
         2         10
         3         69
         4         51
         5         30
         6         58
         7         72
         8         53
         9         46
        10         63

10 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> merge into large
  2  using ( select cust_id, sum(amt) amt from small group by cust_id ) small
  3  on ( large.cust_id = small.cust_id )
  4  when matched then update set large.bal = large.bal+small.amt
  5  when not matched then insert ( cust_id, bal ) values ( small.cust_id,
small.amt );

8 rows merged.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from large order by cust_id;

   CUST_ID        BAL
---------- ----------
        -2          1
        -1          1
         0          1
         1         60
         2         12
         3         70
         4         52
         5         31
         6         58
         7         72
         8         53
         9         46
        10         63

13 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> rollback;

Rollback complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from large order by cust_id;

   CUST_ID        BAL
---------- ----------
         1         58
         2         10
         3         69
         4         51
         5         30
         6         58
         7         72
         8         53
         9         46
        10         63

10 rows selected.

ops$tkyte@ORA920> update large
  2     set bal = bal + ( select sum(amt) from small where cust_id =
large.cust_id )
  3   where cust_id in ( select cust_id from small )
  4  /

5 rows updated.

ops$tkyte@ORA920> insert into large
  2  select cust_id, sum(amt)
  3    from small
  4   where cust_id NOT IN ( select cust_id from large )
  5   group by cust_id
  6  /

3 rows created.

ops$tkyte@ORA920> select * from large order by cust_id;

   CUST_ID        BAL
---------- ----------
        -2          1
        -1          1
         0          1
         1         60
         2         12
         3         70
         4         52
         5         31
         6         58
         7         72
         8         53
         9         46
        10         63

13 rows selected.

Review &
      Followup

      Rating: 5
Learning by
      Example  June 30, 2003
Reviewer:
      A reader
Excellent. Most useful.

June 30, 2003 Sunder -- Thanks for the question regarding
      "Using the LIKE predicate in a WHERE clause", version 8.1.6.0.0.

      You Asked
We have a TITLE table containing about 2-3 million rows and a
column called Title in it. Users frequently have to query on the
table using wildcard searches on the Title field. For instance,
they would want all titles with the words 'Biography', for which

the corresponding SQL statement constructed is:

           SELECT ......
           FROM TITLE
           WHERE ........Title LIKE '%Biography%'

This is pathetically slow since the index on Title is not used
for obvious reasons. How can I improve the response time on
this query ? I have heard something about a ConText option for
text searches. Is that relevant here ?

Please help asap.

Thanks,
Sunder

      and we said...
Yes, interMedia text can help here.  interMedia text (aka
context) will allow you to do rapid full text searches of large
amounts of data.

Basically, you would (in its most simple form)

create index search_idx
on Title(title)
indextype is ctxsys.context
/

Now, you can search like this:

select *
  from title
 where contains( title, 'Biography' ) > 0
/

You can get "scores" to order by relevancy, you can mix
relational query predicates in with it, you can do proximity,
soundex and fuzzy searchs and so on.

To read more about it, see

http://www.oracle.com/intermedia/
To read technical material on it, see

http://technet.oracle.com/doc/oracle8i_816/inter.816/index.htm
....

Review &
      Followup

      Rating: 5
How does fuzzy
      match works  June 13, 2002
Reviewer:  Pratap
      Deshmukh  from India
Hi Tom

I plan to develop an application for name and address search using Oracle text
in 9i. I would like to know the concept behind the fuzzy match, how Oracle
really does this fuzzy matching. If Oracle does not disclose the concept then at
least what to except (and what not to expect) from the fuzzy match results.

This would be of great help for me to use the tool effectively in my
application.

Thanks and regards

Pratap

          Followup:

We have internal lists of common mispellings and such - and only for select
languages

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/inter.817/a77063/cdatadi6.htm#34794
we simply expand the query out using these lists.

      Rating: 3
Help
      needed  June 27, 2003
Reviewer:  David Jiang
       from Houston, TX USA
Tom,

In the exapmple given:
SELECT ......FROM TITLE
WHERE ........Title LIKE '%Biography%'
we know the wild card search here wil search any combination of 'Biography' with
any letters in front of or after it such as: 'my-Biography', 'BiographyForJohn'

If you use interMedia Text or Oracle Text search like shown in your example:

create index search_idx
on Title(title)
indextype is ctxsys.context
/

Now, you can search like this:

select *
  from title
 where contains( title, 'Biography' ) > 0

1. Does this select query search only for 'Biography' or it can search for
'my-Biography', 'BiographyForJohn' also? What kind of setting you have to use to
make the text search possible like wild card search? Please give an example.
2. Is it dynamic ie seach sting can be anything eg Biblography?
3. In Database Configuration Assistant, oracle 9i, there is option for
interMedia Text, Oracle Text, Does Oracle Text option will install all the text
search capability you want or you have to choose both of them?

Thank you very much.

          Followup:

1) you can use wild cards -- it won't by default.  you can use fuzzy searches.
you can use near "find the rows where oracle near tom" and so on.

See the oracle text docs, it really is in there -- all of the options.

2) yes, the string can be anything, every time you search on asktom, you are
doing a search using that technology

3) they are one in the same.

      Rating: 4
Why does
      interMedia Text does not help?  June 30, 2003

Reviewer:  David Jiang  from Houston, TX USA

Tom,

Thank you so much for your prompt answer. I reviewed documents you have
mentioned about near, fuzzy etc. Thats is a great help!

I have a table called OBJECT_DATA with 389 Million rows in it.
CREATE TABLE "CENTRA"."OBJECTS_DATA" ("OBJECT_ID" NUMBER NOT NULL,"ATTRIBUTE_ID"
NUMBER NOT NULL, "FIELD_VALUE" VARCHAR2(80 byte),
CONSTRAINT "CPK_OBJECTS_DATA" PRIMARY KEY("OBJECT_ID",
    "ATTRIBUTE_ID")

When I do a query like this:
10:00:54 SQL> Select count(object_id) from objects where lower(title) like
'%cable%';

COUNT(OBJECT_ID)
----------------
           87886

Elapsed: 00:00:44.02
SQL> Select object_id from objects where lower(title) like '%cable%';

87886 rows selected.

Elapsed: 00:11:12.09

It is very slow because of full table scan. So I installed intrMedia and Text
from 9i R2 and created an Oracle Text index on title:
create index search_idx
on objects(title)
indextype is ctxsys.context;

I did the select qurey again:
13:44:00 SQL> select count(object_id) from objects where lower(title) like
'%cable%';

COUNT(OBJECT_ID)
----------------
           87886

Elapsed: 00:00:44.02

SQL>Select object_id from objects where contains(title, ‘cable’) >0;
86733 rows selected.

Elapsed: 00:13:09.07

It was an index scan, but the queries takes more time to finish.
1. Would you please tell me why it is getting slower with text index search?
2. what can I do to speed up the search if interMedia text does or does not help
here?
3. I tried this query with and without setting the pga and setting work area
policy to auto/manual. Apprently by setting the above work area to auto further
slowed down the query. Please note that I even tried pga b/w 25 MB to 500 MB. I
used your queries to calculate pga mentioned under another heading.
Here is my init param:
pga_aggregate_target=25165824 # 25 MB. I tried upto 500  MB
sort_area_size=16777216
sort_area_retained_size=8000000
workarea_size_policy=AUTO #I tried auto and manual both.

Please also advise how to  configure pga for this issue.
I hope I am not asking too much :)

          Followup:

performing 86,733 TABLE ACCESS BY ROWID's is going to be "not fast" -- they were
all single block IO.

besides, the difference (wall clock time) between 11 and 13 is not of any
consequence.  run the queries under REAL WORLD conditions (you were mostly
timing how fast sqlplus was able to print to your screen)

Run the queries with lots of different inputs.  Run the queries fetching only
the rows you would really in real life (what UI do you have that would really
get 86k rows???).  Run the queries without SCREEN IO -- eg:

alter session set sql_trace=true;
set autotrace traceonly
select ....
set autotrace off
exit

and use tkprof.

June 30, 2003 David -- Thanks for the question regarding
      "Parallel Execution", version 8.0.6

      You Asked
Hi, Tom.

I have an application that is made up of several procedures.  It was written as
seperate procedures because each procedure reads from and updates a distinct
table.  All the procedures share common 'lookup tables', but they each post
results (updates) to different data tables.  (Incidentally, when I took your
advice and made the lookup tables IOTs, it reduced total runtime by 80%.)

Now that we've got this application running faster than we ever thought it
could, there's a little bit of a temptation to want to make it run even faster.
(Complete folly, I'm sure, but it's educational.)

In the current design, each procedure logs its start time and nulls its end time
at the outset of each run, and when it finishes, it updates the end time with
the current time and calculates the elapsed time.  When a procedure starts, it
first looks to see if anything else is running, and aborts if it is.  My thought
was that I didn't want contention on the lookup tables.  Now, I'm wondering if
that won't be a problem, and if I won't get a net improvement if all 5
procedures were running at the same time.

But I'm not sure how to proceed.  I know I can run each procedure from a
different session, but that seems impractical.  I've looked at dbms_job, but
that seems more like a scheduler, though it looks like it could be used to
accomplish this.  Is dbms_job the way to do this?

Are there any criteria that you use to decide if simultaneous execution should
be faster?  Is there any reason NOT to try this?

Thanks, Tom.

      and we said...
if they write different places, there won't be real contention here.  I would
run them in parallel.

set job_queue_processes to some number larger then the number of parallel
threads you want.

set job_queue_interval to something reasonable (like 60)

Then, simply have a procedure that submits all 5, commits and exits.  The 5
procedures will start running within a minute -- all at the same time, in the
background.

if you want to monitor them, use dbms_application_info.set_session_longops
(search this site for that for details if you don't have my expert 1/1 book) so
you can see how far they are along.

There is no reason *not to try* in a controlled environment (eg: if you can test
it first to make sure there are no conflicts, that of course is preferrable --
there may be dependencies you just haven't thought about)
Review &
      Followup

      Rating: 5
What effect
      might this have on other users?  June 27, 2003

Reviewer:  David  from USA
Thanks, Tom.  I wasn't aware that there was such an option, but I'm going to
test this out on our test box.

But our production box runs a multi-user Oracle application.  We have to play
around with a num_processes setting in the applications .ini file to try to
maximize the parallel sessions without running into I/O bottlenecks.  I know
that this can be tricky sometimes.  (The ellusive 98% CPU utilization).  My
question, if I change job_queue_processes from 0 to 5, do I risk having other
users or applications spawning so many jobs that there's I/O contention?  Does
this setting only effect procedures that call multiple procedures?

As always, thanks.

          Followup:

job queue processes is a global setting, one would have to use dbms_jobs to
"abuse it"

      Rating:
      5
DBMS_APPLICATION_INFO  June 27, 2003

Reviewer:  David  from USA
Incidentally, since you mentioned it, this application uses
dbms_application_info all over the place.  I find it invaluable.  I had a
colleague of mine (and a fine human being) write a little vb app that prompts
the user for a login and password and then a module.  After the module is
entered, it opens a window with the module, action and client_info and it
updates every second.  Part of the action the application posts is the rowcount
(like dbms_application_info.set_action('calculating.rate.'||rowcount), so at a
glance I can see how many rows have been processed), and the client info divides
this rowcount by the total rows that will be processed (based on a count of the
rows in the cursor) and posts a '% complete' that's pretty accurate.

I don't know how much time we've saved using this method, but I can't imagine
developing anything without liberal use of dbms_application_info.

      Rating: 3
dbms_job within
      a stored procedure  June 30, 2003
Reviewer:
      DH  from USA
Tom, we're having trouble getting these jobs to run through dbms_job.

Can you give us an example of a submit and a run within a stored procedure?

We're tring this:

CREATE OR REPLACE PROCEDURE "CAP_JOBS" as

jobno number;

 begin

 dbms_job.submit(jobno,
      'cap_scrub_consumer(''31-may-2003'', true)',
      sysdate,
      'sysdate + 1');

 dbms_job.run(jobno);

 commit;

end;
/

In 8.0.6, we're getting PLS-00103 (encountered "END" when expecting := . ( % ;

We're stumped, and the documentation is no help.

Thanks!

          Followup:

you don't want to call dbms_job.run, that'll just be the same as running the
thing inline in an autonomous transaction.

you are missing the ';'

CREATE OR REPLACE PROCEDURE "CAP_JOBS" as
   jobno number;
begin
   dbms_job.submit(jobno,
        'cap_scrub_consumer(to_date(''31-may-2003'',''dd-mon-yyyy''), true);',
          sysdate,
         'sysdate + 1');
   commit;
end;
/

we just take the string you pass and basically wrap "begin" "end;" about it --
that is why the "encountered end when expecting...." there was no ;

      Rating:
      4
Thanks  June 30, 2003
Reviewer:  DH
       from USA
We finally found it when we read the comment in the package itself.  It made a
special point about the ';' so I looked at that and corrected it.  I had a bet
with my dba that you would find it.  He lost.

      Rating: 4
FORCE
      PARALLEL.......  June 30, 2003
Reviewer:
      Robert  from PA
Tom,
By issuing the command below...

ALTER SESSION FORCE PARALLEL QUERY

does it mean that, during the entire session, Oracle will take the parallel
route on any SQL whenever possible ?
even sub-queries in an "UPDATE/DELETE/INSERT" ?
So NO PARALLEL hint is needed ?

Any consideration I should take before jumping in & start EXE. IMMED this in all
apps that has complex queries like reports.

Thanks

          Followup:

parallel stuff is highly NONSCALABLE.

It is designed to allow a single user to consume a machine.

It only works with "really big, yet parallelizable" operations (eg: you have to
be talking minute(s) per query before you even think about this).

It has many ramifications.

It has many implications

It is not anything you'll just jump in and start using all over the place.

You'll need to test this to see if

a) your problems are parallelizable
b) you have sufficient free resources to consider parallel operations
c) you can live with the side effects of PDML

make sure to read at least the concepts guide on this topic.

June 30, 2003 Baqir -- Thanks for the question regarding "any
      performance hit using sequence with nocache in OLTP", version 9.2.0.1

      You Asked
I would appreciate if you please let us know -- if there is any performace hit
in OLTP using sequence with option "nocache"??
Thanks

      and we said...
Huge -- yes.  You will be doing a recursive SQL update to seq$ with every
"nextval" call

Why anyone would ever go with nocache is totally beyond me.  If you think it is
giving you "gap free sequences" -- think again.  The only thing is it doing is
setting "slow=true".

A cache of hundreds or even thousands would be preferable and much more
scalable.
Review & Followup

      Rating: 5
sequences with
      nocache option  November 08, 2002
Reviewer:
      Baqir Hussain  from USA
Thanks for the prompt reply. We have over 300 sequences in our schema.  We have
created sequece with 500 cache size.  These sequences will be used frequently
and will be aged out of the library cache generating a gap about 420 to 450.
Missing sequence is not a problem but with a gap of 400 or plus is not
accepttable. How can we avoid this gap for these sequences?
Can we use dbms_shared_pool.keep to pin all sequences in the shared pool and
HOW?
Is there any any performance hit putting these sequence in the shared_pool all
together?
How can we keep these sequence in the shared_pool before and after the database
shutdown?
Triggers can be used to accomplish this task. Please show me some examples of
these triggers.
Thanks in advance

          Followup:

So?  Even if they are aged out (no likely if you a) size right b) use bind
variables) so???

Why is a gap of 5 ok but 400 bad?  You do know that if you lost 400 a second --
it would take:

ops$tkyte@ORA920.US.ORACLE.COM> select (rpad('9',27,'9')/400) seconds,
  2         (rpad('9',27,'9')/400)/60 minutes,
  3         (rpad('9',27,'9')/400)/60/60 hours,
  4         (rpad('9',27,'9')/400)/60/60/24 days,
  5         (rpad('9',27,'9')/400)/60/60/24/365.25 years
  6    from dual;

   SECONDS    MINUTES      HOURS       DAYS      YEARS
---------- ---------- ---------- ---------- ----------
2.5000E+24 4.1667E+22 6.9444E+20 2.8935E+19 7.9220E+16

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

7.9e16 YEARS to exhaust that sequence....

Yes you can ping

Yes that would most like be a waste of good code on a system that was sized and
coded properly

Yes it would not matter in the long run.

search for

pinning sequence

on this site to see how to pin them and

connor "missing sequence number"

(and look for connor's comment in there) to see how to "unload" them.

But -- its a waste of really good coding time IMO

      Rating: 4
Could this be
      expanded a bit?  November 09, 2002
Reviewer:
      Bob Maggio  from Troy NY USA
So, if I have a system with many sequences, all created with nocahce, would
recreating the sequences with cache=1000 possibly improve performance? It seems
to say so but we haven't really noticed any issues on this system. But there are
100s of sequences that are this way. Any other negative implications of setting
the sequence cache high?

          Followup:

Tell you what -- I'll present the numbers and you can make the choice, refer to

http://asktom.oracle.com/~tkyte/runstats.html
for details on this test harness I'm using:

ops$tkyte@ORA920.US.ORACLE.COM> create sequence cached cache 1000;
Sequence created.

ops$tkyte@ORA920.US.ORACLE.COM> create sequence not_cached nocache;
Sequence created.

ops$tkyte@ORA920.US.ORACLE.COM> declare
  2      l_start number;
  3      l_run1  number;
  4      l_run2  number;
  5
  6      l_num   number;
  7  begin
  8      insert into run_stats select 'before', stats.* from stats;
  9
 10      l_start := dbms_utility.get_time;
 11      for i in 1 .. 100000
 12      loop
 13          select cached.nextval into l_num from dual;
 14      end loop;
 15      l_run1 := (dbms_utility.get_time-l_start);
 16      dbms_output.put_line( l_run1 || ' hsecs' );
 17
 18      insert into run_stats select 'after 1', stats.* from stats;
 19      l_start := dbms_utility.get_time;
 20      for i in 1 .. 100000
 21      loop
 22          select NOT_cached.nextval into l_num from dual;
 23      end loop;
 24      l_run2 := (dbms_utility.get_time-l_start);
 25      dbms_output.put_line( l_run2 || ' hsecs' );
 26      dbms_output.put_line
 27      ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) || '% of the time' );
 28
 29      insert into run_stats select 'after 2', stats.* from stats;
 30  end;
 31  /
2497 hsecs
14753 hsecs
run 1 ran in 16.93% of the time

PL/SQL procedure successfully completed.

Hmm, elapsed time is quite HUGELY different.... the cached sequence is
obviously "wall clock faster" but even more importantly:

ops$tkyte@ORA920.US.ORACLE.COM> select a.name, b.value-a.value run1,
c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

NAME                                 RUN1       RUN2       DIFF
------------------------------ ---------- ---------- ----------
...
STAT...consistent gets - exami        174     100005      99831
nation

STAT...consistent gets             300268     400125      99857
STAT...index fetch by key             129     100001      99872
STAT...commit cleanouts               100     100001      99901
STAT...commit cleanouts succes        100     100001      99901
sfully completed

STAT...calls to kcmgas                100     100009      99909
STAT...execute count               100120     200032      99912
STAT...parse count (total)            119     100033      99914
STAT...opened cursors cumulati        113     100033      99920
ve

STAT...deferred (CURRENT) bloc         99     100024      99925
k cleanout applications

LATCH.sequence cache               300200     200000    -100200
STAT...enqueue requests               303     200079     199776
STAT...enqueue releases               302     200079     199777
STAT...redo entries                   221     200113     199892
LATCH.row cache enqueue latch         312     200600     200288
LATCH.dml lock allocation             391     200956     200565
LATCH.redo allocation                 362     201615     201253
STAT...calls to get snapshot s     300329     600089     299760
cn: kcmgss

LATCH.undo global data                483     301121     300638
STAT...db block gets                  348     303296     302948
STAT...db block changes               429     400152     399723
LATCH.library cache pin alloca        703     401043     400340
tion

LATCH.enqueue hash chains             912     401842     400930
STAT...session logical reads       300616     703421     402805
LATCH.shared pool                  201067     701434     500367
LATCH.row cache objects               716     600609     599893
LATCH.library cache pin            401212    1201376     800164
LATCH.library cache                501960    1602596    1100636
LATCH.cache buffers chains         603008    1920502    1317494
STAT...recursive calls             101649    1500253    1398604
STAT...redo size                    99800   72413704   72313904

99 rows selected.

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

Remember -- LATCHES are locks, locks imply serialization, serialization
implies less scalable -- longer waits.

Also -- look at the STATS as well -- only 72m more redo for "nocache"

Nocache looks like a lose lose proposition to me, how about you?

      Rating:
      3
NOCACHE  November 09, 2002
Reviewer:
      Oleksandr Alesinslyy  from Germany
>Why anyone would ever go with nocache is totally beyond me.

NOCACHE may be useful if somebody need to know exact order of events (e.g.
inserts into table). Any time-based solution may be inadequete of the finite
resolution of any timer. Exect ordering may be achieved only by means of
serialization and NOCACHE provide just it. Definitely, your payment is
performance and scalability, but in some cirqumtints it may be acceptable.

Yours sincerely,
Oleksandr

          Followup:

And just exactly how would nocache help you achieve:

"...if somebody need to know exact order of events (e.g.
inserts into table..."

any better (or worse) the CACHE 1000??

The results would be EXACTLY the same, 100% exactly the same.  The values
returned to nextval just come back SLOWER with nocache -- but the values are the
same (slower -- same results -- bummer)

Now what?

      Rating: 5
NOCACHE and
      event orders  November 10, 2002
Reviewer:
      Oleksandr Alesinskyy  from Germany
Yes, you are righ, ss test result show. I have erroneously assumed that values
are cached at session level, but it seems that in reality they are cached
system-wide.

BTW,just for curiosity, wss it this waz from the moment of sequences
introduction. I have some vague recollection that long ago I have tested this on
Oracle 7.0.16 and test have shown session-level cache (i.e. when I have tried to
access cached sequence from one session and receive "i", then range of values
from "i" till "i+cache-1" was reserved for this session and next access from
another session had returned "i+cache"). But it is possible that these
recollection are wrong.

          Followup:

I do not believe that was ever the behavior - the sequences are cached in the
shared pool.

Maybe -- in version 6, before the shared pool .... but i cannot remember back
that far anymore ;)

      Rating: 5
Can the
      sequences be altered in real time without
      consequence?  November 12, 2002
Reviewer:  Bob
      Maggio  from Troy NY USA
Tom,
can I simply

alter sequence sequence_name cache 500;

to the sequences I want to change without issue? How about on live systems? Any
chance of creating a duplicate sequence or something like that?

          Followup:

no chance of creating duplicates, no.

On a live system, you would (as with all DDL) want to do this in a controlled
fashion, during time of minimal user activity to avoid locking and contention
issues.

      Rating:
      4
sequences  November 15, 2002
Reviewer:
      Baqir Hussain  from Emeryville, USA
Tom,
Happy OracleWorld
You suggested to read Conner remarks
"When you need to shut the database, on a shutdown trigger, make the sequence
nocache then back to cache."

1. Please let us know what is hapenneing?
2. Could you please write a trigger on a shutdown on database to do nocache then
cache. Thanks in advance

          Followup:

1) when you do the ddl on the sequence, it puts the value (current value) out to
seq$.  so the next time, the sequence starts with that value -- not some higher
number.

It is a vain attempt to have gap free sequences which is of course not possible.

2) its pretty easy -- I'll let you read about it:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76939/adg14evt.htm#998000
I'm totally against wasting your time on this futile effort though, it is just
such a waste of time, energy and resources.

      Rating: 4
Sequence cache
      in RAC  June 30, 2003
Reviewer:  Sam
       from San Mateo, CA
Tom,

In a RAC environment, for a particular sequence, does each node have its own
sequence cache or is there one global sequence cache?  We are trying to see if
we use a sequence as a key, whether we can assume a row with a smaller key is
always created before a row with a bigger key.

          Followup:

you could never assume that in single instance oracle really.

OPTIMALLY each instance has its own very big cache.  and 101 might be hours
newer than 55

June 30, 2003 h
      -- Thanks for the question regarding "How to pass a PSP stored procedure
      parameter (Multiple Inserts)", version 9.2.0.1

      You Asked
Hi Tom,
Thanks for your assistance over the weekend.  You suggested I use this method to
troubleshoot the "Multiple Insert" problem I'm having.

I have 2 PSP pages and all I'm trying to do is display the value (for later
insertion) that a user chooses for notification type.
The form looks like this:
           12am 1am 2am 3am 4am ... 10pm 11pm
Sunday     Email/Pager/Both/None ... Email/Pager/Both/None (dropdown list)
Monday     Email/Pager/Both/None ... Email/Pager/Both/None
Tuesday    Email/Pager/Both/None ... Email/Pager/Both/None
Wednesday  Email/Pager/Both/None ... Email/Pager/Both/None
Thursday   Email/Pager/Both/None ... Email/Pager/Both/None
Friday     Email/Pager/Both/None ... Email/Pager/Both/None
Saturday   Email/Pager/Both/None ... Email/Pager/Both/None

The key to this issue is that I'm able to display the value selected by using a
"hard-coded" parameter in Proc2 (psp_Sunday12am, psp_Monday5pm, psp_Thursday8am,
etc.) but I would like to, instead, loop through the parameters so I don't have
to explicitly list 168 inserts (24 hours x 7 days).  I believe I still need 168
declares in Proc2 but thought I could at least avoid the inserts.

I know I can avoid the inserts with 2 db lookup tables that store the day of the
week and hour of day but thought using a pl/sql table would be better?

Maybe the question is, "Can one use an arraylist to generate an insertion loop
to prevent multiple insert statements in a stored procedure?"

Thanks,
ht

      and we said...
create or replace package demo_pkg
as
    type array is table of varchar2(4000) index by binary_integer;

    procedure show_grid;

    procedure accept_grid( x in array );
end;
/
create or replace package body demo_pkg
as

procedure show_grid
is
    l_sunday    date := to_date( '06292003','mmddyyyy' );
    l_types     array;
begin
    l_types(1) := '1 None'; l_types(2) := '2 Pager'; l_types(3) := '3 Both';
l_types(4) := '4 Email';
    htp.p( '<form action=demo_pkg.accept_grid method=post>' );

    htp.p( '<table>' );
    htp.p( '<tr><td></td>' );
    for i in 0 .. 23
    loop
        htp.p( '<td>' || to_char(trunc(sysdate)+i/24,'fmhham') || '</td>' );
    end loop;
    htp.p( '</tr>' );

    for day in 0 .. 6
    loop
        htp.p( '<tr><td>' || to_char(l_sunday+day,'Day') || '</td>' );
        for hour in 0 .. 23
        loop
            htp.p( '<td><select name=X size =1>' );
            for type in 1 .. l_types.count
            loop
                htp.p( '<option  value="' ||
                         to_char(l_sunday+day,'DY') ||
                         to_char(hour,'fm00') || l_types(type) ||
                        '">' || l_types(type) || '</option>' );
            end loop;
            htp.p( '</select></td>' );
        end loop;
        htp.p( '</tr>' );
    end loop;
    htp.p( '</table>' );
    htp.p( '<input type=submit>' );
    htp.p( '</form>' );
end;

procedure accept_grid( x in array )
is
begin
    for i in 1 .. x.count
    loop
        if (x(i) not like '%None' )
        then
            htp.bold( '<br>For ' || substr( x(i), 1, 3 ) || ' at ' ||
                        substr( x(i), 4, 2 ) ||
                        ' hundred hours, you want to be ' ||
                        substr( x(i), 6 ) );
        end if;
    end loop;
end;

end;
/

run that -- see what the output from accept_grid is -- it'll be all of the
selected items DAY, TIME and method of contact.
Review &
      Followup

      Rating: 5
Packages in
      PSP?  June 30, 2003
Reviewer:  ht  from
      California
Tom,
Thanks for the code - it worked great.
One last question.  I understand it's best to use packages but is there a way to
implement this with only stored procedures?  I'm getting a
"PLS-00905 ... object ... is invalid" error when trying to compile proc
accept_grid.
I don't believe PSP will allow me to create procedures within functions.  I
could definitely create the package first and then use it in PSP but I'm just
trying to understand how I could code this without a package body.
Thanks.

          Followup:

just:

create package types
as
   type array is table of varchar2(4000) index by binary_integer;
end;
/

and then use types.array as the datatype from then on in.  stored procedures can
use it as well.

      Rating: 5
Creating a type
      within a package works great.  June 30, 2003

Reviewer:  ht  from California
Tom,
As a first-time poster, I can't be more pleased with your followup and solution.
 I was trying to create the type without a package and, of course, that wasn't
working until you set me straight.
Thanks for your valuable help.
ht

July 01, 2003 Vipin -- Thanks for the question regarding
      "problem while partitioning", version 8.1.7

      You Asked
Hi Tom,

I am having a doubt in partitioning.

I am trying to partition a unpartitioned table. For this I created a partitioned
table with range partitioning. Now I tried altering the partitioned table(which
is presently not having any data) to exchange one of its partition say the first
one p1 with the non_partitioned_table(without validation).

Now what i found was all the rows from the non partitioned table (even those
which doesn't meet the range of p1) were loaded into the partitioned table.
Further when i queried from the partitioned table using the following query the
results were as indicated:-

Select count(*) from partitioned_table partition (p1)

--returned me total count

Select count(*) from partitioned_table partition (p2) --returned 0
Select count(*) from partitioned_table partition (p3) --returned 0
Select count(*) from non_partitioned_table            --returned 0

Could you please explain the above behaviour.

Finally how i acheived the above was by creating the partitioned table with
single partition and with maxvalue, exchanged the data
and then split the single partition to multiple partions.

Thanks in advance.

Vipin

      and we said...
you said "trust me, the data is all OK in this table to go into this partition"

we trusted you.

You were wrong, the data didn't belong in that partition.

This is just like enabling a constraint without validation.  We "trust" you that
the data is conforming to the constraint.

In your case, you will

a) create a partitioned table
b) insert /*+ APPEND */ into it as select * from non-partitioned table

since you need the data split out into separate partitions.  This alter exchange
trick ONLY works when all of the data in a table belongs in a given partition.
July 01, 2003 sam
      -- Thanks for the question regarding "freelist, block", version 8.1.7

      You Asked
http://asktom.oracle.com/pls/ask/f?p=4950:8:20791::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1041685699481,

I want to discuss further on the following

part of your answer.

You'll want to make sure you have sufficient redo/rollback configured -
especially redo.  You'll want to make sure redo is fast -- use dedicated devices
and do not allow for contention on these devices.

my question.

How should we determine whether we have sufficient redo and rollback configured.

Your answer

If you have multiple processes inserting this data, make sure to set freelists
higher then the default of 1.  Set them to the number of concurrent inserters
for both tables and indexes.

My question

where do you set the free lists , and can you change it dynamically

what is the relationship between concurrent users and freelists

why should I set it the same for even indexes

where all does oracle search for space before inserting a record into a block

just like we avoid redo when doing an insert by doing the nologging option, is
there a way wherein we can avoid rollback when doing selects

      and we said...
freelists are an attribute of a table.  In Oracle8i, release 2 (816) and up, you
can:

ops$tkyte@ORA8I.WORLD> alter table tt storage ( freelists 2 );
Table altered.

prior to that, freelists were specified on the CREATE statement only (so a
rebuild would have been mandated).

The relationship between freelists and concurrent insertions is that of reduced
contention.  If you have 5 processes all clamoring for free space in the table
and only one freelist, they will serialize on the list trying to get a block.
it will not STOP them, it will SLOW them down.  If you have my book, i have an
example of this on page 200-202.

You need to set it the same for the indexes since you'll be needing space in
there if you need space in the table.  No sense in removing one roadblock but
leaving the others...

Oracle looks to the freelists for space for newly inserted data.

SELECTS use rollback -- they do not generate it.  Without using rollback, your
query would have to block when it hit a lock and it would get the wrong answer!
Just like the other databases.

followup to comment 3 below

The book is "expert one-on-one Oracle", a link to it is:

http://www.amazon.com/exec/obidos/ASIN/1861004826
on amazon

Review &
      Followup

      Rating: 5
Excellent
      resource  July 24, 2001
Reviewer:  Lionel
      Mandrake  from Burpelson, AFB
If you want an excellent resource (in addition to Tom's book, of course), check
out Jonathan Lewis' book, "Practical Oracle 8i".  The very first chapter
provides an examination of 'Data, Undo, and Redo'.  There's also the 20,000+
pages of Oracle documentation wherein this topic (plus one or two others) is
covered in *great* detail.  It is available online at:

http://otn.oracle.com/docs/content.html

      Rating:
      4
useful  July 27, 2001
Reviewer:
      A reader
Tom's book , along with Jonathan Lewis should make one feel very confident in
basics.

      Rating: 3
Need some
      info  August 03, 2001
Reviewer:  Venkata
       from Delhi, India
Can any one tell me what is the name of the book that TOM has written. Any links
to the book are welcome.

      Rating:
      3
  August 06, 2001
Reviewer:
      A reader
Tom:

i runed the example at page 200, everything works great.

however, when i run three windows instead of two, and set freelists to three,
things goes wrong.

i notice lots of increase at data block , segment header, and undo header.

why?

      Rating:
      3
  August 06, 2001
Reviewer:
      A reader
Tom:

i opened ONLY one window and run your example, there is not conflict at
v$waitstat, but when i run
select event from v$session_wait  where event !='SQL*Net message from client'
and event !='rdbms ipc message' and event !='smon timer'  and event !='SQL*Net
message to client' and event !='pmon timer'

i got lots of event like
log file parallel write
log file switch completion
log file sequential read

does that mean my system is io bounded?
what should i do?

      Rating:
      3
  August 06, 2001
Reviewer:
      A reader
Tom:

i opened ONLY one window and run your example, there is not conflict at
v$waitstat, but when i run
select event from v$session_wait  where event !='SQL*Net message from client'
and event !='rdbms ipc message' and event !='smon timer'  and event !='SQL*Net
message to client' and event !='pmon timer'

i got lots of event like
log file parallel write
log file switch completion
log file sequential read

does that mean my system is io bounded?
what should i do?

      Rating:
      5
clarification  June 30, 2003
Reviewer:
      Reader
Tom, in your demo on page 200, is the time in the following output in seconds?

SQL> select a.class, b.count-a.count count, b.time-a.time time
  2    from sess_event a, v$waitstat b
  3   where a.class = b.class
  4  /

CLASS                   COUNT       TIME
------------------ ---------- ----------
1st level bmb               0          0
2nd level bmb               0          0
3rd level bmb               0          0
bitmap block                0          0
bitmap index block          0          0
data block               2993        133
extent map                  0          0
file header block           0          0
free list                   0          0
save undo block             0          0
save undo header            0          0

CLASS                   COUNT       TIME
------------------ ---------- ----------
segment header             22          0
sort block                  0          0
system undo block           0          0
system undo header          0          0
undo block                  2          0
undo header               162          7
unused                      0          0

18 rows selected.

          Followup:

hsecs (1/100)

      Rating: 5
It took more
      than a minute to complete but ....  June 30, 2003

Reviewer:  Reader
Tom, When I ran your demo on my windows with 9i R2 database, it took little more
than two minutes to complete inserting data. However, the wait column indicates
only 133/100 secs? Any comments? Thanks for your time. Please see below the
output from sqlplus.

SQL> drop table t;

Table dropped.

SQL>
SQL> create table t ( x int );

Table created.

SQL> create global temporary table sess_event
  2  on commit preserve rows
  3  as
  4  select * from v$waitstat
  5  where 1=0
  6  /

Table created.

SQL> truncate table sess_event;

Table truncated.

SQL>
SQL> insert into sess_event
  2  select * from v$waitstat
  3  /

18 rows created.

SQL>
SQL> begin
  2          for i in 1 .. 100000
  3          loop
  4                  insert into t values ( i );
  5                  commit ;
  6          end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

SQL> select a.class, b.count-a.count count, b.time-a.time time
  2    from sess_event a, v$waitstat b
  3   where a.class = b.class
  4  /

CLASS                   COUNT       TIME
------------------ ---------- ----------
1st level bmb               0          0
2nd level bmb               0          0
3rd level bmb               0          0
bitmap block                0          0
bitmap index block          0          0
data block               2993        133
extent map                  0          0
file header block           0          0
free list                   0          0
save undo block             0          0
save undo header            0          0

CLASS                   COUNT       TIME
------------------ ---------- ----------
segment header             22          0
sort block                  0          0
system undo block           0          0
system undo header          0          0
undo block                  2          0
undo header               162          7
unused                      0          0

18 rows selected.

SQL> alter table t storage (freelists 2);

Table altered.

SQL> truncate table t;

Table truncated.

SQL> truncate table sess_event;

Table truncated.

SQL>
SQL> insert into sess_event
  2  select * from v$waitstat
  3  /

18 rows created.

SQL>
SQL> begin
  2          for i in 1 .. 100000
  3          loop
  4                  insert into t values ( i );
  5                  commit ;
  6          end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

SQL> select a.class, b.count-a.count count, b.time-a.time time
  2    from sess_event a, v$waitstat b
  3   where a.class = b.class
  4  /

CLASS                   COUNT       TIME
------------------ ---------- ----------
1st level bmb               0          0
2nd level bmb               0          0
3rd level bmb               0          0
bitmap block                0          0
bitmap index block          0          0
data block                  0          0
extent map                  0          0
file header block           0          0
free list                   0          0
save undo block             0          0
save undo header            0          0

CLASS                   COUNT       TIME
------------------ ---------- ----------
segment header              0          0
sort block                  0          0
system undo block           0          0
system undo header          0          0
undo block                  4          4
undo header                 1          2
unused                      0          0

18 rows selected.

          Followup:

are you using ASSM?  how was the tablespace created (looks ASSM to me --
automatic segment space mgmt, removes the need for freelists/freelist groups and
pctused settings)

2 users (9iR2, Redhat linux, YMMV) using manual:

data block                304       5477

2 users using ASSM

data block                 81       1189

single user (another option, perhaps you just ran A SINGLE SESSION, that is a
great way to reduce contention but not "real world" ;)

data block                  0          0

Maybe you did the last option there -- you need to run >1 session in order to
see the effects here.

      Rating: 5
more info for
      the above comments  June 30, 2003
Reviewer:
      Reader
db_cache_size=25165824
shared_pool_size=50331648
redolog file size is 100 MB

      Rating: 5
Please
      help!  June 30, 2003
Reviewer:  Reader

Tom, Please see below. I created a new tablespace for this demo. I think
somewhere I am not folllowing the steps properly as you have outlined in your
book. Could you please correct me. I am curious to know where I am making the
mistake. Thanks again.

SQL> create tablespace freelist_demo
  2  datafile 'C:\oracle\oradata\practice\freelist_demo01.dbf' size 30M
  3  uniform size 1M;

SQL> select tablespace_name,EXTENT_MANAGEMENT,SEGMENT_SPACE_MANAGEMENT
  2  from dba_tablespaces;

TABLESPACE_NAME                EXTENT_MAN SEGMEN
------------------------------ ---------- ------
SYSTEM                         LOCAL      MANUAL
UNDOTBS1                       LOCAL      MANUAL
TEMP                           LOCAL      MANUAL
CWMLITE                        LOCAL      AUTO
DRSYS                          LOCAL      AUTO
EXAMPLE                        LOCAL      AUTO
INDX                           LOCAL      AUTO
ODM                            LOCAL      AUTO
TOOLS                          LOCAL      AUTO
USERS                          LOCAL      AUTO
XDB                            LOCAL      AUTO

TABLESPACE_NAME                EXTENT_MAN SEGMEN
------------------------------ ---------- ------
READONLY                       LOCAL      MANUAL
FREELIST_DEMO                  LOCAL      MANUAL

13 rows selected.

SQL> drop table t;

Table dropped.

SQL> create table t ( x int ) tablespace freelist_demo;

Table created.

SQL> create global temporary table sess_event
  2  on commit preserve rows
  3  as
  4  select * from v$waitstat
  5  where 1=0
  6  /
create global temporary table sess_event
                              *
ERROR at line 1:
ORA-00955: name is already used by an existing object

SQL> drop table sess_event;
drop table sess_event
           *
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table
in use

------> I ran the following in two sessions as SYSTEM
simultaneously.<--------------

SQL> truncate table sess_event;

Table truncated.

SQL> insert into sess_event
  2  select * from v$waitstat
  3  /

18 rows created.

SQL> begin
  2          for i in 1 .. 100000
  3          loop
  4                  insert into t values ( i );
  5                  commit ;
  6          end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

---------> after the completion of the pl/sql I ran the following <----------

SQL> select a.class, b.count-a.count count, b.time-a.time time
  2    from sess_event a, v$waitstat b
  3   where a.class = b.class
  4  /

CLASS                   COUNT       TIME
------------------ ---------- ----------
1st level bmb               0          0
2nd level bmb               0          0
3rd level bmb               0          0
bitmap block                0          0
bitmap index block          0          0
data block               1057         54
extent map                  0          0
file header block           0          0
free list                   0          0
save undo block             0          0
save undo header            0          0

CLASS                   COUNT       TIME
------------------ ---------- ----------
segment header              3          0
sort block                  0          0
system undo block           0          0
system undo header          0          0
undo block                  0          0
undo header               159          1
unused                      0          0

18 rows selected.

          Followup:

what is timed_statistics set to

      Rating:
      5
Timed_statistics for above issue  July 01, 2003

Reviewer:  Reader
SQL*Plus: Release 9.2.0.1.0 - Production on Tue Jul 1 11:29:55 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

SQL> show parameter timed_staistics
SQL> show parameter timed_statistics

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
timed_statistics                     boolean     TRUE
SQL>

          Followup:

don't know, cannot reproduce on my machine -- I consistently and constantly get
the waits.   It almost looks like yours are in seconds, but they shouldn't be.

I have no windows machines, maybe the machine is so bad at time slicing, they
are not actually contending with eachother ;)

      Rating:
      5
OK  July 01, 2003
Reviewer:  Reader

I don't personally like to practice oracle on windows but at home no other
choice. Thanks for your time.

July 01, 2003 Thomas -- Thanks for the question regarding
      "History on Hierarchy", version 9.2

      You Asked
Hi Tom,

i have a question about Parent Child tables and storing history data.

For financial reporting we store multiple department hierarchies in a table. To
keep the diffenent hierarchies in sync we store everything in one table.

Our tables look like:

create table t1
 ( parent_id     number,
   child_id      number
 )

create table t2
 ( object_id     number,
   object_name   varchar2(100),
   some other attributes ...
 )

Both tables are connected with a forign keys (Parent and Child)

One Department can be the Child of multiple Parents (in different hierarchies).
If one Parent is used in more than one hierarchy it must have the same children.

Now my question:

We want to track the history of all changes. Also, we need to create for some
subsystems a differential report (changes made since "date"). To make historical
numbers more meaningfull, we also would like to compare prior years data with
current year (based on the current hierarchy or prior hierarchy)

Can you give me hint how todo this ? Any experience with something like this.

Thanks
Thomas

      and we said...
Well, you may well be very interested in the work space manager then.

You can version enable these tables using VIEW WO OVERWRITE (view without
overwrite)

That will keep a history of every change.

Then, you can use the procedure "gotoDate" to see the data "as it existed, at
that point in time".  Sort of like a flashback query -- but it can back back
forever...

consider this setup:

ops$tkyte@ORA920> CREATE USER wsmgmt IDENTIFIED BY wsmgmt;
User created.

ops$tkyte@ORA920> GRANT connect, resource, create table to wsmgmt;
Grant succeeded.

ops$tkyte@ORA920> grant execute on dbms_lock to wsmgmt;
Grant succeeded.

ops$tkyte@ORA920> begin
  2          DBMS_WM.GrantSystemPriv
  3      ('ACCESS_ANY_WORKSPACE, MERGE_ANY_WORKSPACE, ' ||
  4       'CREATE_ANY_WORKSPACE, REMOVE_ANY_WORKSPACE, ' ||
  5       'ROLLBACK_ANY_WORKSPACE', 'WSMGMT', 'YES');
  6  end;
  7  /
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> connect scott/tiger
Connected.

ops$tkyte@ORA920> grant select on emp to wsmgmt;
Grant succeeded.

ops$tkyte@ORA920> grant select on dept to wsmgmt;
Grant succeeded.

ops$tkyte@ORA920> connect demo/demo
Connected.
ops$tkyte@ORA920>
ops$tkyte@ORA920> set echo off
Enter to continue
=================================================================

Now we'll log in as wsmgmt and set up our schema...

ops$tkyte@ORA920>
ops$tkyte@ORA920> @connect wsmgmt/wsmgmt
ops$tkyte@ORA920> set termout off
wsmgmt@ORA920> REM GET afiedt.buf NOLIST
wsmgmt@ORA920> set termout on
wsmgmt@ORA920>
wsmgmt@ORA920> create table emp as select * from scott.emp;

Table created.

wsmgmt@ORA920> create table dept as select * from scott.dept;

Table created.

wsmgmt@ORA920>
wsmgmt@ORA920> alter table emp add constraint emp_pk primary key(empno);

Table altered.

wsmgmt@ORA920> alter table dept add constraint dept_pk primary key(deptno);

Table altered.

wsmgmt@ORA920> alter table emp add constraint emp_fk_dept foreign key(deptno)
references dept(deptno);

Table altered.

wsmgmt@ORA920> alter table emp add constraint emp_fk_emp foreign key(mgr)
references emp(empno);

Table altered.

so, there is your schema, replete with a hierarchy (empno/mgr)

wsmgmt@ORA920>
wsmgmt@ORA920> set echo off
Enter to continue
=================================================================

Version-enable the table. Specify hist option of VIEW_WO_OVERWRITE so that
the xxx_HIST view contains complete history information.

wsmgmt@ORA920>
wsmgmt@ORA920> begin
  2          DBMS_WM.EnableVersioning ('emp',  'VIEW_WO_OVERWRITE');
  3          DBMS_WM.EnableVersioning ('dept', 'VIEW_WO_OVERWRITE');
  4  end;
  5  /

PL/SQL procedure successfully completed.

wsmgmt@ORA920>
wsmgmt@ORA920>
wsmgmt@ORA920> exec dbms_lock.sleep(2);

PL/SQL procedure successfully completed.

wsmgmt@ORA920> column dt new_val starting_date
wsmgmt@ORA920> select to_char(sysdate,'dd-mon-yyyy hh24:mi:ss') dt from dual;

DT
--------------------
01-mar-2003 17:39:24

wsmgmt@ORA920> exec dbms_lock.sleep(2);

PL/SQL procedure successfully completed.

I want to be able to look at the table as it looked "right now" later on, so
I'm just remembering what time it is right now..

wsmgmt@ORA920>
wsmgmt@ORA920> update emp set sal = sal * 1.10;

14 rows updated.

wsmgmt@ORA920> commit;

Commit complete.

wsmgmt@ORA920>
wsmgmt@ORA920> select ename, sal, comm, workspace, type_of_change,
  2         to_char(createtime,'dd-mon hh24:mi:ss') created,
  3         to_char(retiretime,'dd-mon hh24:mi:ss') retired
  4    from emp_hist
  5   where ename = 'KING'
  6  /

ENAME             SAL       COMM WORKSPACE     T CREATED         RETIRED
---------- ---------- ---------- ------------- - --------------- ---------
KING             5000            LIVE          I 01-mar 17:39:15 01-mar 17:39:27
KING             5500            LIVE          U 01-mar 17:39:27

that just shows you have that builtin history going for you...

Now, blake stages a coup, takes out king in a fight:

wsmgmt@ORA920>
wsmgmt@ORA920> update emp
  2     set mgr = ( select empno from emp where ename = 'BLAKE' )
  3   where mgr = ( select empno from emp where ename = 'KING' );

3 rows updated.

wsmgmt@ORA920>
wsmgmt@ORA920> update emp
  2     set mgr = null
  3   where ename = 'BLAKE';

1 row updated.

wsmgmt@ORA920>
wsmgmt@ORA920> delete from emp where ename = 'KING';

1 row deleted.

wsmgmt@ORA920> delete from dept where deptno = 40;

1 row deleted.

wsmgmt@ORA920> commit;

Commit complete.

all gone right?  we query and sure enough:

wsmgmt@ORA920>
wsmgmt@ORA920> select rpad('*',level*2,'*') || ename
  2    from emp
  3   start with mgr is null
  4   connect by prior empno = mgr;

RPAD('*',LEVEL*2,'*')||ENAME
---------------------------------------------------------------------------------
--------------------------------------------------
**BLAKE
****ALLEN
****WARD
****MARTIN
****TURNER
****JAMES
****JONES
******SCOTT
********ADAMS
******FORD
********SMITH
****CLARK
******MILLER

13 rows selected.

wsmgmt@ORA920>

blake is in charge, however -- we "gotoDate" and

wsmgmt@ORA920> exec dbms_wm.gotoDate( to_date( '&starting_date', 'dd-mon-yyyy
hh24:mi:ss' ) );

PL/SQL procedure successfully completed.

wsmgmt@ORA920>
wsmgmt@ORA920> select rpad('*',level*2,'*') || ename
  2    from emp
  3   start with mgr is null
  4   connect by prior empno = mgr;

RPAD('*',LEVEL*2,'*')||ENAME
---------------------------------------------------------------------------------
--------------------------------------------------
**KING
****JONES
******SCOTT
********ADAMS
******FORD
********SMITH
****BLAKE
******ALLEN
******WARD
******MARTIN
******TURNER
******JAMES
****CLARK
******MILLER

14 rows selected.

wsmgmt@ORA920>

we can see what "was" in the past

read

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96628/toc.htm

Review &
      Followup

      Rating:
      3
  March 02, 2003
Reviewer:  Thomas
       from USA / Germany
Hi Tom,

i have tried to add the workspace manager, but with our design (foreign keys /
unique Keys / no PK on the Parent Child Table) we are not able to use it. There
are some more references in our system.

Do you see any way of doing this in simple SQL - Something like "Create a
protokoll Trigger and write all the changes in a new table ... etc."

Thanks
Thomas

          Followup:

maybe time to rethink the design as it really sounds like you are IN DESIGN
right now -- hmmm....

sure you can code it yourself.  I'm not about to do that for you -- it took a
while for the database to do it - and as you can imagine it has some serious
coding implications -- unique's and such make this really really REALLY hard.

You have a choice, use the builtin feature (change your rules as you see them
now to fit with what the software does) or

DIY (do it yourself).

      Rating:
      2
  March 02, 2003
Reviewer:
      A reader
Thanks a lot Tom,

I understand, that i have todo it ...

A redesign is maybe an option - but at this point i dont see how we can do this
without a uniqueness in the system. Anyway...

BTW: How was this done before WM ?

Thanks
Thomas

          Followup:

It wasn't done before WM -- unless you wrote the code, so it was done in any
neat way you dreamt up.

it still seems to me that you are IN DESIGN, not out of DESIGN so this is less
of a design change than it is a correction to existing thoughts.

anyway....

      Rating:
      3
  March 02, 2003
Reviewer:
      A reader  from USA / Germany
Does "In Design" ever end ?

Thomas

          Followup:

Yes, but you are clearly "in design" here.  Your question:

...
Now my question:

We want to track the history of all changes. Also, we need to create for some
subsystems a differential report (changes made since "date"). To make historical

numbers more meaningfull, we also would like to compare prior years data with
current year (based on the current hierarchy or prior hierarchy)
..........

leaves no ambiguity there.  you are clearly "in design"

      Rating:
      5
  March 02, 2003
Reviewer:
      A reader
Hi Tom,

First time saw example of Workspace Manager. Nice Feature!!

Few Question;

1) Why you are using  DBMS_LOCK

wsmgmt@ORA920> exec dbms_lock.sleep(2);

2) We have OLTP application. Where we can use this feature in our
application..(ie Why we need to keep multiple version of data since we have
Auditing feature enable ?)

Thanks

          Followup:

1) cause a DATE has a granularity of 1 second and the database is soooo fast
that if I didn't pause -- I wouldn't be able to "flashback" reliably!

I did it so there would be some time between the two events.

2) only you can answer that.  where do you *need* to use this feature. You would
have to have a requirement that caused you to use it.

if builtin auditing is sufficient -- thats what you need.

if not and this does what you need, then you need it ;)

      Rating:
      5
  March 02, 2003
Reviewer:
      A reader
Hi Tom

Thanks for quick reply.

I follow you example and I check database object under 'wsmgt" schema.

There are lots of views and two materilized view. Table
name like  'DEPT_AUX','DEPT_LT',EMP_AUC','EMP_LT'

What's going on here ???

Thanks.

          Followup:

did you read the link above where I say "read" ;)

it is the concepts guide for workspace management and it actually covers all of
this stuff, what it does, how it does it and so on.

      Rating: 5
well
      explained..  May 24, 2003
Reviewer:  Praveen
       from MN , USA
Great explanation, This piece saved me at least 60% of my development time.
 I would like to know once we go to a ceertain time in the past using say
dbms_wm.gotodate(sysdate-10);
How do I get back to the present date and time, is
dbms_wm.gotodate(sysdate) the only way? or is there any other way.

          Followup:

you would just "gotoworkspace" again, that resets the context.  setting to
sysdate would not work, that would just freeze you at yet a different point in
time.

Consider:

wsmgmt@ORA920> begin
  2          DBMS_WM.GotoWorkspace ('ws1');
  3  end;
  4  /

PL/SQL procedure successfully completed.

wsmgmt@ORA920> column dt new_val D
wsmgmt@ORA920> select to_char(sysdate,'yyyymmddhh24miss') dt from dual;

DT
--------------
20030525095813

wsmgmt@ORA920>
wsmgmt@ORA920> update emp set sal = sal * 1.50;

14 rows updated.

wsmgmt@ORA920> commit;

Commit complete.

wsmgmt@ORA920>
wsmgmt@ORA920> exec dbms_lock.sleep(5);

PL/SQL procedure successfully completed.

wsmgmt@ORA920>
wsmgmt@ORA920> select ename, sal
  2    from emp
  3   where ename = 'KING'
  4  /

ENAME             SAL
---------- ----------
KING             7500        <<<<<<====== current value

wsmgmt@ORA920>
wsmgmt@ORA920> exec dbms_wm.gotoDate( to_date( &D, 'yyyymmddhh24miss' ) );

PL/SQL procedure successfully completed.

wsmgmt@ORA920>
wsmgmt@ORA920> select ename, sal
  2    from emp
  3   where ename = 'KING'
  4  /

ENAME             SAL
---------- ----------
KING             5000         <<<<<==== value from before the update

wsmgmt@ORA920>
wsmgmt@ORA920> exec dbms_wm.gotoWorkSpace( 'ws1' );

PL/SQL procedure successfully completed.

wsmgmt@ORA920>
wsmgmt@ORA920> select ename, sal
  2    from emp
  3   where ename = 'KING'
  4  /

ENAME             SAL
---------- ----------
KING             7500     <<<<<<==== we see the update again

wsmgmt@ORA920>
wsmgmt@ORA920>

      Rating: 5
does Versioning
      Info inccur extra storage ..  May 25, 2003

Reviewer:  pascal  from Hamburg
Hi Tom,

Thanks for a Great Explanation above..Oracle is getting more and more
un-beatable nowadays i guess...

One question though..:-(

I am just curious as to how you guys store this Versioning information in the
Database....do you store only the Meta Data about the changes somewhere in the
Database ...

I think i have once seen a Workspace schema in the Database , although i have
never really tested it..is this where all Info gets stored ?

Sorry, i will read the Docs later...this is just a warm-up for Workspace
management stuff..

Thanks in advance.

          Followup:

it does it the way you would do it in a single table yourself.

the rows have start/stop dates on them.

When you update a row, we really UPDATE the timestamp (retire) the row and
insert a new one.

When you delete a row, we really UPDATE the timestamp (retire) the row.

if you have complete versioning turned on -- every incarnation of the row is
there.  You can also just create "savepoints" meaning only enough incarnations
of a row are kept to support getting back to the save points -- not ever
interveening change is kept.

      Rating: 4
Few questions
      on Workspace Manager  June 16, 2003
Reviewer:
      Tony  from India
1. I've version enabled a table emp. I don't want the history table to have
duplicated records. Because if I do UPDATE emp SET sal=sal , record is getting
inserted into the history table even if there is no change in sal. Is there any
way to avoid this duplicate record insertion?

2. If I version enable a single table, 14 other tables are aslo geting created.
 What if I version enable n tables?.  This will flood my db with  n X 14 tables
and subsqently loose so much of storage space. So I don't think its a feature to
be used by everyone.

3. Oracle creates triggers to capture the history. What If I want to write my
own triggers of the same type for someother purpose?

4. Is there any command to version enable all tables in a schema in a single
command?

          Followup:

1) you did an update as far as we are concerned (as far as ANYONE is concerned).
 No.

2) did you actually look at the created objects-- you might be surprised to
discover "they are not tables but those things that look like tables -- views"

3) go for it?

4) sort of, it starts with begin and ends with end...

begin for x in ( select * from user_tables ) loop ..... end loop; end;
/

(it is a single command ;)

      Rating: 4
does WSM allow
      historical queries?  June 17, 2003
Reviewer:
      Mikito Harakiri
select max(sal) from emp
where ename = 'KING'
and last_modified between '2000-JUNE-1' and '2003-JUNE-1'

          Followup:

you can query EMP_HIST -- so yes.

      Rating: 5
very useful -
      some questions  July 01, 2003
Reviewer:
      A reader  from ca, usa
Hi Tom
Thanx for a superb site - please keep up the good work!:)

Few questions about the WS management:

a. In workspace manager, is it possible to restrict the versioning to only apply
till say 30 days old – For exmple, if we are interested in just 30 days of
history and don’t want the database to incur any additional overhead, can we
specify that?

b. The database already stores enough info to rollback and redo any operation?
Is the same mechanism used internally to maintain history of operations on
tables?  If so how can it go forever back in history?

c. If we just want to save the history of certain tables for insert delete,
updates etc., is workspace too heavy a tool? (For example, it has a bunch of
additional features
such as hierarchical workspaces, lock management etc that
may not be needed if all you want is to enable history
on few tables)

d. What are “version enabled databases” from the document
link above (see section 1.5)? Is it just a database with at least one table that
is version enabled?

Please see

http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96628/long_int.htm#1005076
e. what do you mean by “and as you can imagine it has some serious coding
implications -- unique's and such make this really really REALLY hard.” Could
you please give an example that illustrates this point?

f. Someone asked: “What If I want to write my own triggers of the same type for
someother purpose?” You said “go for it”. But if we write instead of triggers of
our own, wont it override the ones written by Oracle?

g. Tom, have you ever used this feature in a real life application? If so what
was your experience – any caveats (apart from the ones documented already)?

Just for general information to others, from the doc, please note that there are
several restrictions (such as on import /export, ddl operation, referential
integrity constraints, triggers etc) that you may wanna go through before using
this feature.

          Followup:

a) i am pretty sure it is all or nothing, either the history is there, or it is
not.

b) no, it end dates records.  so an update is really "update retired column,
INSERT new row".  a delete is really "update retired column".  It stores the old
row and the new row

c) that is what benchmarking is all about -- does it meet your needs and can you
implement with it more quickly then doing it yourself (benchmark to get the
first answer, and the second answer is almost certainly "yes")

d) yes it does

e) well, you have the same record in there over and over and over -- you have to
add a version key to it, the same record can be in 2 workspaces - mergable up to
LIVE, it requires alot of thought to enable this.

f) what I meant was "go ahead and create your triggers", when you version the
table, you'll find the trigger became a procedure and it is called on your
behalf.

g) i have not personally had the need, however -- I see various support notes on
it as well a handful of "product issues" indicating you would definitely not be
among the first to use it.

as for the last comment -- i heartily aggree, that's why the link to the docs
are right there with a message to "make sure you read this"

      Rating: 5
thanx tom -
      just one more question?:)  July 01, 2003
Reviewer:
      A reader  from usa
"well, you have the same record in there over and over and over -- you have to
add a version key to it, the same record can be in 2 workspaces - mergable up to

LIVE, it requires alot of thought to enable this."

Assuming one is only interested in maintaining history:

1. Do you imply the case where a record changes to a value
rec_a, then changes to rec_b and again rec_a?

2. If you implement it yourselves - you would probably not have the workspace
concept - you would be using triggers and if so it does not matter what the
record changes to. So am I missing something?

thanx for the tremendous service you provide to thousands
of people like me! Hope to meet you some day!!!

          Followup:

1) in just the history part -- it is easier to a degree.  The primary key used
to be "empno", now the primary key is "empno, retired" for example.

We can have many records for empno=1234 in the emp table now due to the history.
 That is what I meant.

But it gets trickier -- it has to be empno=1234 ONLY for the one guys record.
it is not just as simple as adding a timestamp to the primary key to version it.

if you use a two table scheme, it is easier yes.

July 01, 2003 Roop
      -- Thanks for the question regarding "Accessing data from a non oracle
      database", version Forms [32 Bit] Version 4.5.7.15.6

      You Asked
How can I access a non oracle database (e.g Foxpro dbf) using
Forms 4.5 or how can I retrieve data from a non oracle
database using Forms 4.5

      and we said...
With Developer/2000 rel 1 (forms 4.5) we would use the Oracle
Open Client Adapter.  The Oracle Open Client Adapter for ODBC
(OCA) allows Developer/2000 on Microsoft Windows, Windows95 and
Windows NT to access ODBC-compliant data sources through ODBC
drivers.

Using the Oracle Open Client Adapter, a Developer/2000
application can access different data sources in a consistent
manner. This allows an application developer to build an
application that can run unmodified against one of several
databases. Alternatively, the application can be targeted at a
specific database, and take advantage of features particular to
that system.

I've made a document available at:

http://osi.oracle.com/~tkyte/dev2kodbc.pdf
that goes into much more detail as to exactly how to do this.
Review &
      Followup

      Rating: 5
Forxpro 2.6 Not
      supported  December 20, 2002
Reviewer:  Sikandar
      Hayat Awan  from Pindi Bhattian - Pakistan
Hi TOM,
I have downloaded your document and was trying to configure a datasource for
FoxPro 2.6 but it is giving me that "MS FoxPro drivers are no longer supported
and has been replaced with MS Visual FoxProd Drivers". I have to connect with
FoxPro 2.6 .DBF from Developer 6i?

          Followup:

so, get the new odbc drivers and use them?  you just need an odbc connection is
all.

      Rating: 5
I have to deal
      with FoxPro 2.6  December 20, 2002
Reviewer:
      Sikandar Hayat Awan  from Pindi Bhattian - Pakistan

Hi,
When I open 2.6 DBF with VF then it converts to Visual Format but I don't want
this. I have an application in FoxPro 2.6 and I have to access it from Oracle
8i, Forms 61. Where I can find drivers? I mean from Developer CD or...

          Followup:

maybe the microsoft site -- you know, the people that own and maintain foxpro...
they would be the logical ones to ask for an odbc driver from....

      Rating:
      5
  July 01, 2003
Reviewer:
      A reader
Hi Tom,

 How can I access FoxPro from SQL*PLUS ?
 Do I need Oracle haterogenous gateway ?

 Thanks

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4406709207206
generic connectivity via odbc will do.


Subject | Article | Last Updated (East coast time)
set transaction use rollback segment - snapshot too old error | article only | Jul 02, 2003 07:26 am
Boolean Datatype | article only | Jul 02, 2003 08:39 am
Current Row using Analytic function | article only | Jul 02, 2003 10:01 am
PL/SQL Procedure returning a Ref-Cursor with "embedded Cursors" | article only | Jul 02, 2003 11:13 am
password | article only | Jul 02, 2003 11:58 am
Sql query | article only | Jul 03, 2003 08:40 am
Validating rman cold backup | article only | Jul 03, 2003 08:51 am
getting rows N through M of a result set which contain duplicates | article only | Jul 03, 2003 08:59 am
Performance tuning | article only | Jul 03, 2003 09:01 am
exporting packages,function etc. from one user to another. | article only | Jul 03, 2003 09:08 am
ORA-01401: Inserted value too large for column | article only | Jul 03, 2003 09:14 am
migrate sql server to oracle | article only | Jul 03, 2003 11:21 am
Cursor sharing, bind variables, histograms and data warehousing | article only | Jul 03, 2003 07:59 pm
oerr | article only | Jul 03, 2003 08:06 pm
on NT/2000 use ocopy or copy with hot backup | article only | Jul 03, 2003 08:08 pm
Redirect Url without showing Address in the address bar | article only | Jul 03, 2003 08:20 pm
national character set | article only | Jul 04, 2003 07:53 am
Resumable insert question | article only | Jul 04, 2003 07:55 am
Job Scheduling | article only | Jul 04, 2003 07:56 am
table fetch continued row | article only | Jul 04, 2003 08:44 am
PLS-123 error | article only | Jul 04, 2003 01:07 pm
Passing parameters in Web application | article only | Jul 04, 2003 02:36 pm
Creating Foreign key with a view or materialzed view | article only | Jul 06, 2003 10:48 am
ORA-16000 database link read only query fails | article only | Jul 06, 2003 05:04 pm
SGA Shared Pool - Reg. | article only | Jul 07, 2003 07:27 am
An extent is NOT contiguous? | article only | Jul 07, 2003 07:34 am
Duplication of the functionality of an alert -- in d2k | article only | Jul 07, 2003 08:01 am
Chekboxes | article only | Jul 07, 2003 09:11 am
SID and SERVICE_NAMES | article only | Jul 07, 2003 09:42 am
Locks & Latches | article only | Jul 07, 2003 05:16 pm
calculations based on a group and using the grouped value in the calculation | article only | Jul 08, 2003 07:17 am
surrogate Key | article only | Jul 08, 2003 07:39 am
Application tuning topics. | article only | Jul 08, 2003 08:35 am
clob | article only | Jul 08, 2003 08:44 am
statspack/tkprof | article only | Jul 08, 2003 11:47 am
System Date Change | article only | Jul 08, 2003 01:00 pm
Manual database startup | article only | Jul 08, 2003 01:33 pm
selecting third row always-- Analytic functions | article only | Jul 08, 2003 01:45 pm
Passing parameter for SQL query using shell script | article only | Jul 08, 2003 06:16 pm
row(s) 1 - 39 of 39
July 02, 2003 HASAN TONGUC -- Thanks for the question regarding "set transaction use rollback segment - snapshot too old error", version 8.0.5.1
You Asked
i use this syntax for my batch jobs ;
set transaction use rollback segment rbshuge ;
since this rbs is large enough i hope the processes end but i still get the
error msg snapshýt too old with other rbs in message
and we said...
Classic error.  I hate the "use rollback segment" -- leads people to believe
they just need a big rollback segment for batch jobs.  You don't.

The probability of the ORA-1555 is a direct function of the size of your
SMALLEST rollback segment.  We need to construct a read consistent view of some
data that your batch job is running.  apparently there are other transactions,
using the small rollback segments, that are modifying the data you are reading.
In the time it takes you to open a cursor and fetch all of the rows -- some
rollback segment has wrapped around (some OTHER rollback segment).
Unfortunately -- you needed that data that was overwritten.  Because the other
rollback segments are too small -- you lose.

You need to have PERMANENTLY allocated enough rollback segment space so that no
rollback segment wraps during the execution of your long running queries.

See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:275215756923
for all about the 1555 error (understand your enemy)

Also search for 1555 on my site for more info.

You need bigger RBS's all around.  I would avoid the use of a single "big" rbs
-- it encourages this problem.
Review & Followup
Rating: 5
March 28, 2001
Reviewer: Helena Markova from Bratislava, Slovakia
"use rollback segment"

Rating: 5
DBMS_TRANSACTION.USE_ROLLBACK_SEGMENT July 01, 2003
Reviewer: Fred Shimaya from Irvine, CA
Tom, I bought your book, Expert One-On-One, and I am using it (excellent
resource by the way) along with other resources to help develop a PL/SQL Coding
Standards document for our team (your book will be cited ;)).  In compiling the
information, I notice one tip suggested by an author which seems contradictory
to what you stated in your book concerning the choosing of a rollback segment.
Of course, I have seen your other postings which indicates that other
sites/books have contradicted you, so this is nothing new.  But I just want to
clarify this.  In the book, Oracle PL/SQL Tips and Techniques (McGraw/Osbourne),
the author(s) recommend the use of DBMS_TRANSACTION.USE_ROLLBACK_SEGMENT to:

"...choose the rollback segment large enough to handle the size of the
transaction set.  Call this procedure before new transactions take place and
after any intermediate commits or rollbacks occur.  This will ensure the
specified rollback segment will be used to store the pending transactions."

Does this suggestion contradict what you mentioned in your book?  Or is it just
another way to work around what you warned against?  It would seem that the
former is correct, since using this procedure is basically doing the same, it
seems, as SET TRANSACTION...

Also, another thing they mentioned which leads me to think that this book
contains errata is the statement "Implement the use of a counter to perform a
COMMIT for a specified number of transactions."

Thanks,

Fred

Followup:
Yes, it contradicts it -- but only because in my world, all RBS's are created
equal.

None is bigger then the other, all are equally competent.

Same with the commit thing -- not a good idea, unless you want more redo, more
undo, and longer response times.  Besides -- it indicates you are doing things
entirely procedurally -- not set based and you should be trying to go set based.

So, did they have scientific reasons?  Case studies?  Any sort of proofs?

Rating: 5
Addendum July 01, 2003
Reviewer: Fred Shimaya from Irvine, CA
Oh, let me add this quote from the same book:

"If you are manipulating large sets of records between transactions, make sure
to assign the transaction to a rollback segment.  This will ensure completion
without receiving an error for exceeding the rollback segment size.  Any time a
COMMIT or ROLLBACK is executed, the rollback segment used for the transaction is
released.  Therefore, if a process commits periodically during large
transactions, the rollback segment assignment command must be included in the
process after every COMMIT."

Hehe...

Fred

Rating: 5
No Proofs, Only The Reasons Stated... July 01, 2003
Reviewer: Fred Shimaya from Irvine, CA
They offer none of that.  However, with the COMMIT thing, they do mention that
it is not necessary to add the COMMIT in a loop so that it does it upon each
iteration.  They just use an arbitrary number of 1000 in the counter to perform
the COMMIT's after every 1000th record, so that it flushes the rbs.

Followup:
and do they say "why" they think this thing that

o makes you run slower
o generates more redo/undo
o totally breaks your transactional integrity

is a good thing ;)

Rating: 5
Bad Apples In This World... July 02, 2003
Reviewer: Fred Shimaya from Irvine, CA
A book like this from Oracle Press, with a foreword from a V.P. of Oracle Sales
& Marketing...  Kinda makes me wonder if the Oracle Certification Exams are that
bad...

Rating: 5
If you are depending on RBS because you are not using 9.x September 30, 2003
Reviewer: Bernd Boecker from Monheim, Germany
If you are on 9i skip this and use UNDO TS,
otherwise...

I followed a recommendation by Tom to store 1 RBS per TBS  per file. The file is
AUTOEXTEND and the maxsize should fit for your biggest transaction. So every RBS
can support a TX upto 6 GByte (or the maxsize you choose). I shrink the RBS on a
occasional basis to waste not more disk space then necessary (after a weekly
full backup or so).

Example:

CREATE TABLESPACE "TS_RBS01"
    LOGGING
    DATAFILE 'E:\ORACLE\ORADATA\SAMBA\TS_RBS01.ora' SIZE 97M
    REUSE AUTOEXTEND
    ON NEXT  12288K MAXSIZE  6000M DEFAULT
    STORAGE ( INITIAL 12288K NEXT 12288K MINEXTENTS 2 MAXEXTENTS 505
    PCTINCREASE 0 );

CREATE TABLESPACE "TS_RBS02"
...

CREATE TABLESPACE "TS_RBS03"
...

Rating: 5
sorry missed the RBS creation part... September 30, 2003
Reviewer: A reader
CREATE ROLLBACK SEGMENT "RBS01"
    TABLESPACE "TS_RBS01"
...

CREATE ROLLBACK SEGMENT "RBS02"
    TABLESPACE "TS_RBS02"
...

CREATE ROLLBACK SEGMENT "RBS03"
    TABLESPACE "TS_RBS03"
...

July 02, 2003 Jeremy -- Thanks for the question regarding "Boolean Datatype", version 9.2.0.1.0
You Asked
Here's a real short one for you Tom:

Why doesn't Oracle RDBMS have a boolean datatype?
and we said...
since
...,
flag char(1) check (flag in ( 'Y', 'N' )),
...,

serves the same purpose, requires the same amount of space and does the same
thing - I guess we feel this is a feature we can let them have that we really
don't need.

I mean - what do you get back from a column in "access" that is a boolean?  TRUE
/ FALSE.  We'll give you Y/N -- if you would like TRUE/FALSE, we can accomplish
that easily with DECODE(flag,'Y','TRUE','N','FALSE')
Review & Followup
Rating: 3
A few reasons why... November 03, 2002
Reviewer: Jeremy Smith from Salt Lake City, UT
Clarity: if the value I'm trying to store really is a boolean, then when I'm
glancing at my model, it's clear that that's what the column is.  Sure, if it's
a char(1) that's a good indication, but not a sure-fire one.

Compatibility:  I know you're suspicious of products developed for multiple
platforms, but there are a lot of them out there.  It would be nice to not have
to make that conversion every time.

Consistency: Y/N values with a check constraint are one way to represent a
boolean. When I needed my first one, I used T and F.  Another person I know used
0 and 1.  Someone who spoke another language might choose two different
characters.

PL/SQL compatibility:  It would be nice to be able to declare booleans using
"var_name%TYPE".

and just to clarify on performance issues:

Speed: doesn't that check constraint take a tiny amount more  time than
validating against conformance to a particular datatypes?

Space: if I'm using a fixed width multi-byte character set, doesn't a char(1)
boolean take up more space than a universal boolean would?

Sorry if this sounds whiney, but it's something I find myself explaining to
nearly every new Oracle developer, and I've never heard a really good reason for
why not.  Best I've been able to come up with is "Because they don't _need_ it,
and because they don't feel like having it."  You have to admit, that's a pretty
rare answer to give w/r/t an Oracle feature.

Followup:
For clarity -- i would hope the name (which could even incorporate "boolean") or
the check constraint would tell you that.

For compatibility -- you better stick with SQL89 datatypes (basically strings
and numbers) --  not really appealing when you get down to it.

Consistency -- well, the same thing could be said about zip codes, addresses,
storing names, (and the list goes on).  Sounds like an implementation detail

Speed -- no, there wouldn't be.

space -- use a number with 1 and 0 then.  Or using utf-8 or any variable length
encoding scheme....

It just isn't a type we have -- I can say no more and no less.  ANSI doesn't
have it -- many databases don't have it (we are certainly not alone).  In the
grand scheme of things -- I would say the priotization of this is pretty "low"
(thats my opinion there)

Rating: 3
PL/SQL functions November 04, 2002
Reviewer: JBrain from England
Point taken, but I find the following annoying...
(a) Someone (or even me) writes a PL/SQL function returning a BOOLEAN.
(b) I later find a use for it in a SQL WHERE clause.

True I might be able to code the condition in SQL, but if I want to try to
maintain some semblance of code reuse I either have to provide a Y/N function
wrapper or go back to the author...

Would be nice just to be able to slip the original function into the WHERE
clause and forget about it.

Are there people out there who have coding standards banning functions which
return a BOOLEAN...?

Followup:
Are there people out there banning functions such as those returning a boolean
-- I can almost assure you there are.  I've seen EVERY construct "banned" out of
FUD at one point or another.

Rating: 2
Boolean November 04, 2002
Reviewer: Tony Andrews from UK
Since ANSI don't specify a BOOLEAN datatype, I guess our issue is with ANSI
rather than Oracle.  But I don't buy the argument that BOOLEANs are redundant
because you can use CHAR(1) with 'Y' or 'N' (what I'd call a "work around").  In
that case, why have a PL/SQL BOOLEAN type either?  BOOLEAN is a fundamental and
natural datatype, and most databases I have known would use it liberally if it
existed.

Rating: 3
What is the Boolean mapping in Java. February 05, 2003
Reviewer: Chandra S.Reddy from Hyderabad India
Hi Tom,
I have a question regarding boolean datatype.

Below is the SP, written by our client and we need to integrate this with our
application.

PROCEDURE sp_xxxx_map (
                ic_opt_number       IN  VARCHAR2,
                in_cast_number    IN  NUMBER,
                in_tyre_amount          IN  NUMBER,
                ob_is_valid             OUT BOOLEAN,
                ob_is_obgr_obgt_valid   OUT BOOLEAN,

--------------- few more parameters....

While integrating the above, everything id fine except boolean OUT parameter.
I think(correct me if am wrong) there is no SQL datatype in java(JDBC classes)
to catch the  "ob_is_valid", "ob_is_obgr_obgt_valid" parameters.
How do can go about this issue.
One idea is:
Can I write another SP(say SP1), which will call the above SP and will convert
the boolean types to CHARs like 'Y' for true and 'N' for false.
Finally I let the application to call the newly created SP1.

Followup:
just prepare and execute:

declare
   b1 boolean;
   b2 boolean;
   n1 number := 0;
   n2 number := 0;
begin
   sp_xxxx_map( ?, ?, ?, b1, b2 );
   if (b1) then n1 := 1; end if;
   if (b2) then n2 := 1; end if;
   ? := n1;
   ? := n2;
end;

from the jdbc app.

Rating: 4
pseudo-boolean datatype best practices June 13, 2003
Reviewer: Kurt from Springfield, VA USA
Tom,
Hi. This is a follow-on to the current thread.

I'm involved in a large database development project and I'd like to find out
your opinion on best practices for what I'm referring to as "pseudo-boolean
datatypes".

As mentioned in this thread, Oracle does not natively support an SQL boolean
datatype, and you've suggested an alternative implementation using a char(1)
datatype with a check constraint. I think that's a very good idea, and I've been
using a similar approach for years. So, unless you've revised your original
suggestion, let's assume the datatype and length issues have been settled -
char(1).

You've also suggested that there should be a check constraint in place that
ensures that the values are members of an explicit domain - 'Y' or 'N'. This
implies that the column can be defined as "Not Null", and I think that's a good
idea, too. Although I think ('Y','N') is the correct domain, I've seen other
implementations where ('T','F') are used, and that's fine - any 2 values are
fine as long as they're used consistently across the project.

Since joining my current project, I've heard of 2 more approaches from
well-respected Oracle developers. One developer suggests that one single value
('Y' for example) means True, and any other value (including null) means False.
For example...

Y = True
y = False
3 = False
null = False
F = False
etc.

The other developer suggests that null means False, and any non-null value means
True (a C-like paradigm). For example...

Y = True
y = True
3 = True
null = False
F = True
etc.

My implementations have always been more rigid - the column is defined as "Not
Null", 'Y' means True and 'N' means False. Anything else is an error. To help
out a bit, I use a pre-insert/update trigger which converts null values to 'N',
and uppercases all values. Then the trigger raises an exception if the value is
not in ('Y','N'), although this functionality could be accomplished using the
check constraint that you mentioned.

I try to avoid the use of "null" to indicate an explicit value. To me, it's like
making decisions based upon the absence of a value rather than making decisions
based upon the presence of a value, and that seems counter-intuitive. I
understand that there are other places where this approach is used successfully
- such as performing an outer join and using the null values in the "right-hand
side" of the join to detect missing records - but that seems more palatable to
me. I consider a "Not Null" column to be "stronger" than a nullable column,
unless you're forced to pack in arbitrary data simply to satisfy the constraint.

Also, I believe there are legitimate reasons within Oracle to favor "Not Null"
columns. For example, null values are not stored in an index, and this could
impact the resources needed to satisfy a query where that column appears in a
predicate. And, the optimizer may choose a different (possibly inferior)
execution plan based upon whether the column is nullable or not, even if the
column does not appear in an index.

Can you provide a critique of the implementations mentioned here, and make a
suggestion as to the best approach in general? The target application is a
web-based OLTP application (JSP, WebSphere), with the usual data entry screens,
searches, and reports.

Thanks, Kurt

Followup:
if a column has a domain of three values

true
false
unknow

then a constant value for TRUE is called for, a constant value for FALSE and
NULL for unknown.

If it has two -- then two constants are called for.

It seems to make more sense to me anyway to consistently code:

select * from t where ..... and shipment_has_shipped = 'Y';

.... and payment_made = 'N';

rather then,

....  and shipment_has_shipped is not null;
....  and payment_made is null;

the first two just seem to convery "more details" about what they are doing.

so, I've never used anything other then Y/N/null

Rating: 3
representing tri-state in a 2-state client June 14, 2003
Reviewer: Kurt from Springfield, VA USA
Tom,
Thanks for the response.

If the sole application sitting on top of the database only has 2
representations for the value of a boolean field, then doesn't that imply that
the database should support only 2 values? For example, a checkbox control on an
HTML form can only represent checked and unchecked, for True and False. It's
clear that when the HTML form displays a pre-populated record, it will take the
field value 'Y' and render the checkbox in a checked state, and will take the
field value 'N' and render the checkbox in an unchecked state. But what should
it do with a null value? I'm sure the usual approach is to render the checkbox
unchecked. But since that's the same as 'N', then why allow null values in the
field at all?

In a similar way, what does it mean when the user submits the HTML form with an
unchecked checkbox? To the data handler (whether that's the client JSP or the
database PL/SQL), that doesn't mean null, it means 'N' even if the user is
"unsure" of what the correct value should be. HTML forms simply don't support
tri-state boolean values.

Thanks, Kurt

Followup:
if it only has two values - Y or N, it should be defined:

flag char(1) NOT NULL check (flag in ('Y','N'))

there are no nulls to deal with then

Rating: 3
Which would you rather see July 02, 2003
Reviewer: DH from USA
Not sure about the purpose for this thread, because it seems to me that Oracle
does have a boolean datatype, but I'm curious at the present time, which you
would rather see in an application that uses these constructs a great deal:

if( flag != 1 ) then

or

if( <boolean variable> ) then

I gather from the thread that they're certainly functionally the same, but I've
seen the latter more often in your examples.  Is either one more efficient than
the other?

Followup:
i disagree, I mostly use the first case in my stuff.

I would not:

  bool_val := flag != 1;
  if ( bool_val )
  then
    ....

I would

   if ( flag != 1 )
   then
      ....

Rating: 3
funny July 02, 2003
Reviewer: DH from USA
how perception sometimes deviates from reality.  Looking back, I do see the
boolean construct in your examples, but only when the reader provided such a
construct in their question.  Ironically, I started using boolean variables only
because it appeared to me that it was your preference.

July 02, 2003 Arumugam -- Thanks for the question regarding "Current Row using Analytic function", version 8i
You Asked
Hello TOM,
    I need to get the current row from the following table and i know how it
will improve performance.

TableA
(Emplid Varchar,
 effect_dt date defualt trunc(sysdate),
 effseq number,
 Salary number)

I need to rewrite with analaytice function.

 Select salary from tableA a
 where emplid = &emplid
 and effect_dt = (select max(effect_dt) from tableA B
                  where a.emplid = b.emplid and effect_dt <= sysdate)
 and effseq = (select max(effseq) from tableA C
                 where a.emplid = c.emplid and a.effect_dt = c.effect_dt )

Thanks & regards,
Siva
and we said...
ops$tkyte@ORA920> select sal, effect_dt, effseq,
  2     max(decode(sign(effect_dt-sysdate),-1,effect_dt,0,effect_dt,null))
            over () max_ed,
  3     max(effseq) over (partition by effect_dt) max_es
  4    from emp
  5   where empno = 7844
  6  /

       SAL EFFECT_DT     EFFSEQ MAX_ED        MAX_ES
---------- --------- ---------- --------- ----------
      1500 01-JUL-03        -10 02-JUL-03        -10
      1650 02-JUL-03         10 02-JUL-03         10
      1500 02-JUL-03          9 02-JUL-03         10
      1500 03-JUL-03       -990 02-JUL-03       -990

that shows the "raw" data.  the max_ed is found by NULLING out effect_dt's
greater then right now (decode does that) and taking the MAX() value out of
whats left.  In this example, 02-jul-03 is that value.

getting max_es for that empno/date is easy -- just get the max() after whereing
on the empno and partitioning by effect_dt

ops$tkyte@ORA920>
ops$tkyte@ORA920> select *
  2    from (
  3  select sal, effect_dt, effseq,
  4         max(decode(sign(effect_dt-sysdate),-1,effect_dt,0,effect_dt,null))
over () max_ed,
  5             max(effseq) over (order by effect_dt range between 0 preceding
and 0 following ) max_es
  6    from emp
  7   where empno = 7844
  8         )
  9   where effect_dt = max_ed and effseq = max_es
 10  /

       SAL EFFECT_DT     EFFSEQ MAX_ED        MAX_ES
---------- --------- ---------- --------- ----------
      1650 02-JUL-03         10 02-JUL-03         10

Now, I loaded the table up so that there were 4 rows for this empno and put
30,000 other records in there.  I indexed (EMPNO,effect_dt,effseq) -- that is
the index that works good for your query.  tkprof says after running it 100
times:

select *
  from (
select sal, effect_dt, effseq,
       max(decode(sign(effect_dt-sysdate),-1,effect_dt,0,effect_dt,null)) over
() max_ed,
       max(effseq) over (partition by effect_dt) max_es
  from emp
 where empno = 7844
       )
 where effect_dt = max_ed and effseq = max_es

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute    100      0.03       0.02          0          0          0           0
Fetch      200      0.04       0.04          0        500          0         100
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      301      0.08       0.07          0        500          0         100

Rows     Row Source Operation
-------  ---------------------------------------------------
    100  VIEW  (cr=500 r=0 w=0 time=45293 us)
    500   WINDOW SORT (cr=500 r=0 w=0 time=42624 us)
    500    TABLE ACCESS BY INDEX ROWID EMP (cr=500 r=0 w=0 time=14709 us)
    500     INDEX RANGE SCAN EMP_IDX (cr=200 r=0 w=0 time=8112 us)(object id
38875)
********************************************************************************
Select salary from tablea A
 where emplid = 7844
  and effect_dt = (select max(effect_dt) from tableA B
                    where a.emplid = b.emplid and effect_dt <= sysdate)
                     and effseq = (select max(effseq) from tableA C
                                      where a.emplid = c.emplid and a.effect_dt
= c.effect_dt )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute    100      0.02       0.02          0          0          0           0
Fetch      200      0.07       0.08          0       1400          0         100
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      301      0.10       0.10          0       1400          0         100

Rows     Row Source Operation
-------  ---------------------------------------------------
    100  FILTER  (cr=1400 r=0 w=0 time=77805 us)
    400   SORT GROUP BY (cr=1400 r=0 w=0 time=76271 us)
   1600    MERGE JOIN CARTESIAN (cr=1400 r=0 w=0 time=63301 us)
    400     TABLE ACCESS BY INDEX ROWID TABLEA (cr=1200 r=0 w=0 time=38810 us)
    400      INDEX RANGE SCAN TABLEA_IDX (cr=800 r=0 w=0 time=31193 us)(object
id 38877)
    400       SORT AGGREGATE (cr=600 r=0 w=0 time=13025 us)
    400        FIRST ROW  (cr=600 r=0 w=0 time=8678 us)
    400         INDEX RANGE SCAN (MIN/MAX) TABLEA_IDX (cr=600 r=0 w=0 time=6683
us)(object id 38877)
   1600     BUFFER SORT (cr=200 r=0 w=0 time=14038 us)
    400      INDEX RANGE SCAN TABLEA_IDX (cr=200 r=0 w=0 time=3333 us)(object id
38877)
Review & Followup
Rating: 5
July 02, 2003
Reviewer: A reader
Excellent

Rating: 4
Excellent comparison July 02, 2003
Reviewer: Arumguam from INDIA
Hello TOM,
   It is very usefull to modify my existing query to make faster with Analitic
function.

Thanks
Siva

July 02, 2003 Klaus -- Thanks for the question regarding "PL/SQL Procedure returning a Ref-Cursor with "embedded Cursors"", version 8.1.7
You Asked
Hi Tom,

Luckily I'm in the position to ask a question right now ...
I've been searching the archives, but didn't find a solution ...

My problem is the following:

I've a query like ...

SELECT U.UserId, U.LoginName, U.PassWord, U.ForeName,
       U.SureName, U.Gender, U.Description, U.ModDate,
       U.ModUser, DSCore.getState(U.State) AS State,
       U.DefaultLanguageId,
       CURSOR(SELECT C.UserContactId,
                DSCore.getContactType(C.ContactTypeId) AS ContactType,
                C.Value, C.Description,
                DSCore.getState(C.State) AS State, C.ModUser
              FROM   UserContacts C
          WHERE  C.UserId = U.UserId) AS Contacts,
       CURSOR(SELECT A.UserAddressId,
                DSCore.getAddressType(A.AddressTypeId) AS AddressType,
                A.StreetNo, A.ZipCode, A.City, A.Country,
                DSCore.getState(A.State) AS State, A.ModUser
          FROM   UserAddresses A
              WHERE  A.UserId = U.UserId) AS Addresses
FROM   Users U
WHERE  U.UserId = pUserId;

Which executes very well from JDBC, ... even if run by a PHP-Script on a
Web-Server.

Now I'd like to implement a PL/SQL Package Procedure, which returns a ref-cursor
to the resultset of the above query, but the PL/SQL Parser encountered problems
with the CURSOR-Function ...

The DSCore.Package Functions are all implemented an run smoothly. The whole
package is pinned into the memory for performance reasons.

I've no clue, how to get around this problem - any hints are welcome!
and we said...
In 8i and before, PLSQL used a different SQL parser then the "database" did.
For this reason, there are some features in SQL that PLSQL does not understand
including (but not limited to)

o order by in a subquery
o select ( select .... ) from table (selecting a select)
o analytic functions
o group by extensions (group by rollup, group by cube)
o CURSOR in a select (obviously) ;)

So, how to solve that?  Well, if you look at the following example, we'll see
some SQL that should work:

scott@ORA817DEV.US.ORACLE.COM> select deptno, dname, cursor( select ename from
emp where emp.deptno = dept.deptno )
  2    from dept
  3   where deptno = 10
  4  /

    DEPTNO DNAME          CURSOR(SELECTENAMEFR
---------- -------------- --------------------
        10 ACCOUNTING     CURSOR STATEMENT : 3

CURSOR STATEMENT : 3

ENAME
----------
CLARK
KING
MILLER

 see that it apparently does not work (in 9i, this block does in fact execute
cleanly

scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM> variable x refcursor
scott@ORA817DEV.US.ORACLE.COM> set autoprint on
scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM> declare
  2          p_deptno number default 10;
  3  begin
  4          open :x for
  5  select deptno, dname, cursor( select ename from emp where emp.deptno =
dept.deptno )
  6    from dept
  7   where deptno = p_deptno;
  8  end;
  9  /
select deptno, dname, cursor( select ename from emp where emp.deptno =
dept.deptno )
                              *
ERROR at line 5:
ORA-06550: line 5, column 31:
PLS-00103: Encountered the symbol "SELECT" when expecting one of the following:
( ) - + mod not null others <an identifier>
<a double-quoted delimited-identifier> <a bind variable>
table avg count current exists max min prior sql stddev sum
variance execute multiset the both leading trailing forall
year month DAY_ HOUR_ MINUTE_ second TIMEZONE_HOUR_
TIMEZONE_MINUTE_ time timestamp interval date
<a string literal with character set specification>
<a number> <a single-quoted SQL stri
ORA-06550: line 6, column 3:
PLS-00103: Encountered the symbol "FROM" when expecting one of the following:
; return returning and or

and here is how to solve this issue. We'll bypass the PLSQL SQL parser and by
using dynamic SQL achieve the opening of the refcursor with the embedded CURSOR
successfully:

scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM> declare
  2          p_deptno number default 10;
  3  begin
  4          open :x for
  5  'select deptno, dname, cursor( select ename from emp where emp.deptno =
dept.deptno )
  6     from dept
  7    where deptno = :p_deptno' USING p_deptno;
  8  end;
  9  /

PL/SQL procedure successfully completed.

    DEPTNO DNAME          CURSOR(SELECTENAMEFR
---------- -------------- --------------------
        10 ACCOUNTING     CURSOR STATEMENT : 3

CURSOR STATEMENT : 3

ENAME
----------
CLARK
KING
MILLER

scott@ORA817DEV.US.ORACLE.COM>
Review & Followup
Rating: 4
... as ever ... :-)) March 12, 2002
Reviewer: Klaus from sunny Vienna/Austria (... no cangaroos ... ;-) ...)
again hi Tom ...

you made my day and saved me lot's of time/troubles ...

next time in USA, I owe you a pint and a session pool ...

Thanks a lot!

Rating: 4
What is the purpose of CURSOR? March 12, 2002
Reviewer: AB from UK
Tom

I'm interested to know what is the purpose and intended use of the CURSOR
expression and what types of application use is?

Thanks

Followup:
It can be used to get "non-square" results from the database.

Say you wanted to run a single query to retrieve:

o a list of tables you owno all of their columns and datatypes
o the constraints on each table
o the list of users that have SELECT on it
o the list of roles that have SELECT on it

Well, you would have to run at least 3 and probably 4 queries to answer that
normally.

with CURSOR -- you can run a single query that returns all of that in one query.
 You'll get a result set that contains 4 result sets for each row.

Rating: 5
March 12, 2002
Reviewer: A reader
Tom,

Can you explain CURSOR in sql statement with REAL life
example ?. How we implement this functionality and in which cases we use this.

select deptno, dname, cursor( select ename from emp where emp.deptno =
dept.deptno )  from dept
   where deptno = p_deptno;

Followup:
the original questioner had a REAL WORLD example ...

The retrieved a user, all of their contacts and all of their addresses.

Normally, you would run a query like this:

   for x in ( select * from users )
   loop
        for y in ( select * from contacts where userid=x.userid )
        loop
           ...
        end loop;
        for z in ( select * from addresses where userid=x.userid )
        loop
            ...
        end loop;
   end loop;

Their query does all of that in a single query -- instead of incurring the
overhead of running two queries / row, you just get a row with two result sets
that you can fetch from.

Rating: 2
the next logical question is... March 12, 2002
Reviewer: Scott Mattes from VA, USA
Tom,
  How does one code PL/SQL to fetch from this cursor?

Followup:
it is not useful in PLSQL as yet.  It is useful for returning results back to a
client like Java or C.

Rating: 4
Sorry to labour this one.... March 13, 2002
Reviewer: Adrian Billington from UK
Tom

Like Scott, I'd also be interested to know if we could cycle through this
statement in PL/SQL. Furthermore, what can you actually do with the resultset
once you have it - it seems to me as though you can read it but not do much else
with it unless there are some tricks to referencing the various cursor
resultsets...?

Thanks

Adrian

Followup:
In java or C, you'll get cursors (result sets) that you can read (thats what we
do with resultsets?).  You can iterate over them and do whatever you please.

PLSQL, unaware of this construct, cannot use them (yet)

Rating: 4
More please March 13, 2002
Reviewer: A reader
Hi,

As Scott and Adrian addressed, how we reference the portion of "nested cursor"
when we pass the REF cursor result set back to JAVA or C program?

Please give little example?

Thanks

Followup:
$ java RefCursor
ACCOUNTING
   CLARK
   KING
   MILLER
RESEARCH
   SMITH
   JONES
   SCOTT
   ADAMS
   FORD
SALES
   ALLEN
   WARD
   MARTIN
   BLAKE
   TURNER
   JAMES
OPERATIONS

$ cat RefCursor.java

import java.sql.*;
import java.io.*;
import oracle.jdbc.driver.*;

class RefCursor
{

public static void main (String args [])
throws SQLException, ClassNotFoundException
{
   String query  = "begin "+
                        "open ? for "+
                        "'select dname, CURSOR(select ename "+
                                                "from emp "+
                                               "where emp.deptno=dept.deptno) "+
                           "from dept'; "+
                    "end;";

   DriverManager.registerDriver
        (new oracle.jdbc.driver.OracleDriver());

   Connection conn=
       DriverManager.getConnection
       ("jdbc:oracle:thin:@aria-dev:1521:ora817dev",
         "scott", "tiger");

   Statement trace = conn.createStatement();

   CallableStatement  cstmt = conn.prepareCall(query);

   cstmt.registerOutParameter(1,OracleTypes.CURSOR);
   cstmt.execute();

   ResultSet rset= (ResultSet)cstmt.getObject(1);

   for(int i = 0;  rset.next(); i++ )
   {
    System.out.println( rset.getString(1) );
    ResultSet rset2 = (ResultSet)rset.getObject(2);
    for( int j = 0; rset2.next(); j++ )
        System.out.println( "   " + rset2.getString(1) );
    rset2.close();

   }

   rset.close();
   cstmt.close();
}
}

Rating: 1
OK, how about an example in some other language? March 13, 2002
Reviewer: Scott Mattes from VA, USA
First, are there plans for PL/SQL to be able to use this?

Second, would you give an example in Delphi, JAVA or C for using this, I am way
curious.

Followup:
yes, there are.

See java above.  No idea about "delphi", you'd have to ask them.

Rating: 5
March 13, 2002
Reviewer: A reader
Tom,

I can get same results by joing tables. What's new in
CURSOR subqueries?

Followup:
Look at the second comment on this page "What is the purpose of CURSOR? " and my
response to it.  Now, write me a single query using only joins that gets me that
data without using CURSOR (hint -- there is in fact a way to do it HOWEVER it in
itself involves using Object types!)

Rating: 5
calling the given PL/SQL Function from java/PHP -> ORA 6503 March 14, 2002
Reviewer: Klaus from Vienna, Austria
Hi Tom,

Based on your response I adapted my PL/SQL Function and it could be compiled
successfully!

Unfortunately I'll get ORA 6503, when I try to execute the function, for e.g.
with the following Java Code ...

String query  = "begin ? := DSS.getUser(1); end;";

DriverManager.registerDriver(
  new oracle.jdbc.driver.OracleDriver());
conn = DriverManager.getConnection("jdbc:oracle:oci8:@tstDB", "username",
"password");

CallableStatement  cstmt = conn.prepareCall(query);
cstmt.registerOutParameter(1,OracleTypes.CURSOR);
cstmt.execute();

ResultSet rset= (ResultSet)cstmt.getObject(1);

for (int i = 0;  rset.next(); i++) {
  System.out.println("UId   ="+rset.getString(1));
  System.out.println("Login ="+rset.getString(2));
  System.out.println("Passwd="+rset.getString(3));
  System.out.println("FName ="+rset.getString(4));
  System.out.println("SName ="+rset.getString(5));

  ResultSet rset2 = (ResultSet) rset.getObject(12);

  for (int j = 0; rset2.next(); j++) {
    System.out.println("  ContactId="+rset2.getString(1));
  }

  rset2.close();

  ResultSet rset3 = (ResultSet) rset.getObject(13);

  for (int j = 0; rset3.next(); j++) {
    System.out.println("  AddressId="+rset3.getString(1));
  }

  rset3.close();
}

rset.close();
cstmt.close();
conn.close();

The query in the PL/SQL Function works properly and delivers a record, if
executed in SQL-Plus or other tool!

FollowUp-Question from me is, how to retrieve the refCursor
from the function?

Thanks again for your help!

Followup:
well, one would need to see the PLSQL code as the error is coming from there!

Based on the error:

$ oerr ora 6503
06503, 00000, "PL/SQL: Function returned without value"
// *Cause: A call to PL/SQL function completed, but no RETURN statement was
//         executed.
// *Action: Rewrite PL/SQL function, making sure that it always returns
//          a value of a proper type.

I would GUESS that you are forgetting to return the ref cursor in the function!

You retrieve the ref cursor from the function in exactly the way I demonstrated
above with the anonymous block -- it is not any different whatsoever.  You would
change:

 String query  = "begin "+
                        "open ? for "+
                        "'select dname, CURSOR(select ename "+
                                                "from emp "+
                                               "where emp.deptno=dept.deptno) "+
                           "from dept'; "+
                    "end;";

to

  String query = "begin ? := f(); end;";

and thats it.

Rating: 5
forgotten PL/SQL Function Code March 14, 2002
Reviewer: Klaus UNGER from Vienna/Austria
Hi Tom,

Sorry about forgetting the function source ...

refCursor ist defined as follows in the package:

  TYPE refCursor IS REF CURSOR;

here is the source - as I'm returning the cursor, I got worried about the ref
Cursor declaration ...

FUNCTION getUser(pUserId Users.UserId%TYPE)
  RETURN refCursor
IS
  c         refCursor;
  BEGIN
    OPEN c FOR
    'SELECT U.UserId, U.LoginName, U.PassWord, U.ForeName, U.SureName, U.Gender,
U.Description, ' ||
    '       U.ModDate, U.ModUser, DSCore.getState(U.State) AS State,
U.DefaultLanguageId, ' ||
    '       CURSOR(SELECT C.UserContactId, DSS.getContactType(C.ContactTypeId)
AS ContactType, ' ||
    '                     C.Value, C.Description, DSCore.getState(C.State) AS
State, C.ModUser ' ||
    '              FROM   UserContacts C ' ||
    '              WHERE  C.UserId = U.UserId) AS Contacts, ' ||
    '       CURSOR(SELECT A.UserAddressId, DSS.getAddressType(A.AddressTypeId)
AS AddressType, ' ||
    '                     A.StreetNo, A.ZipCode, A.City, A.Country,
DSCore.getState(A.State) AS State, ' ||
    '                     A.ModUser ' ||
    '              FROM   UserAddresses A ' ||
    '              WHERE  A.UserId = U.UserId) AS Addresses ' ||
    'FROM   Users U ' ||
    'WHERE  U.UserId = :UID' USING pUserId;

    RETURN c;

  EXCEPTION
    WHEN OTHERS THEN
      IF (c%isOpen) THEN
        CLOSE c;
      END IF;
  END;

As I understand, I don't have to declare a Type, that specifies the structure of
the result-set. If I have to,
I wouldn't know, how to declare a cursor in a record-type ...

So again ... please enlight me with your comments!

Many thanks in advance

Klaus

PS: pint-counter++; ... ;)

Followup:
You have the major #1 bug i the world.

"when others" that is not followed immediately by RAISE.

change the code to:

 WHEN OTHERS THEN
      IF (c%isOpen) THEN
        CLOSE c;
      END IF;
      RAISE;

and then let us know what happens.

Rating: 5
... done ... March 14, 2002
Reviewer: Klaus UNGER from Vienna / Austria
Hi Tom,

before I go to self-flagellation for the stupid raise thing ... must be a kind
of code-blindness ...

Thanks ... the raise brought to light, that dynamic SQL cannot handle
bind-varibales like :UID. When I use :1 the procedure works properly ...

Thanks and sorry for any inconvenience

Klaus

Followup:
UID being one of those "magic words" ;)

ops$tkyte@ORA817DEV.US.ORACLE.COM> variable x refcursor
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2          open :x for 'select :uid from dual' using 1;
  3  end;
  4  /
begin
*
ERROR at line 1:
ORA-01745: invalid host/bind variable name
ORA-06512: at line 2

ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2          open :x for 'select :user from dual' using 1;
  3  end;
  4  /
begin
*
ERROR at line 1:
ORA-01745: invalid host/bind variable name
ORA-06512: at line 2

ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2          open :x for 'select :bv_uid from dual' using 1;
  3  end;
  4  /

PL/SQL procedure successfully completed.

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

   :BV_UID
----------
         1

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

Rating: 5
being a little bit embarrassed ... March 15, 2002
Reviewer: Klaus from Vienna/Austria
Hi Tom ...

Thanks again,
Seems to be, that I was not programming PL/SQL for a bit too long ... forget
about all "minor details", which of course have huge impacts ...

- raise
- UID

back in college I'll give myself an failed ... ;)

Thanks & kind regards
Klaus

Rating: 4
Very Informative...... April 09, 2002
Reviewer: Neeti from TX
I am not sure how many goddies are still hidden in your
treasure. That was cool. Can you point to the documentation
where I can find more information about this construct.

Thanks,
Neeti

Followup:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a85397/expressi.htm#1002754
....

      Rating: 5
How about in
      9i?  April 09, 2002
Reviewer:  A reader

You mentioned 'it is not useful in PLSQL as yet.  It is useful for returning
results back to a
client like Java or C.'
but things like the following code give may be perhaps usefull ?:

set serveroutput on

declare
  type rctyp is ref cursor;

  myrc1  rctyp;
  myrc2  rctyp;
  empno  number;
  deptno number;
  ename  varchar2(10);

begin
  open myrc1 for select deptno,
                        cursor(select e.empno, e.ename
                               from emp e
                               where e.deptno = d.deptno)
                 from dept d;

  fetch myrc1 into deptno, myrc2;
  while myrc1%FOUND loop
    dbms_output.put_line('deptno = ' || deptno);
    fetch myrc2 into empno, ename;
    while myrc2%FOUND loop
       dbms_output.put_line('empno = ' || empno || ' ename = ' || ename);
       fetch myrc2 into empno, ename;
    end loop;
    fetch myrc1 into deptno, myrc2;
  end loop;
  close myrc1;
end;
/
deptno = 10
empno = 7782 ename = CLARK
empno = 7839 ename = KING
empno = 7934 ename = MILLER
deptno = 20
empno = 7369 ename = SMITH
empno = 7566 ename = JONES
empno = 7788 ename = SCOTT
empno = 7876 ename = ADAMS
empno = 7902 ename = FORD
deptno = 30
empno = 7499 ename = ALLEN
empno = 7521 ename = WARD
empno = 7654 ename = MARTIN
empno = 7698 ename = BLAKE
empno = 7844 ename = TURNER
empno = 7900 ename = JAMES
deptno = 40

Cheers
Charly

          Followup:

Very nice, i tried binding to a dynamic ref cursor in 8i -- that didn't work.
didn't think to try in 9i!  I knew it was coming (thats why i said "yet" over
and over).

Thanks!

      Rating: 3
How to do the
      Reverse  April 23, 2002
Reviewer:  Siva
       from NJ, USA
It is useful for sending Ref Cursor from Sql to Java.
The same does not work in reverse.

I am parsing an XML in Java and converting into a sql statement. I would like to
pass the REF cursor of this SQL to PL/SQL. How do I do This.

I tried something like this in Oracle 8.1.6 . Gives some Internal Error

-- The following would be in a Java Stored procedure

    public static oracle.sql.REF getOracleResultset(String serviceResponse)
                          throws java.sql.SQLException {

-- This would be SQL I would construct from the xml
        String sqlstmt = new String("select * From emp;");
        System.out.println("Recieved serviceResponse");

        Connection conn = new OracleDriver().defaultConnection();

        CallableStatement cstmt = conn.prepareCall("begin open ? for "+sqlstmt+"
end;");

        cstmt.registerOutParameter(1, OracleTypes.CURSOR);
        cstmt.execute();

        REF refc = ((OracleCallableStatement)cstmt).getREF(1);

        return refc;
    }

          Followup:

You cannot return a result set from a java stored procedure to plsql.

You can return the STRING that is the QUERY and let the SQL layer open a ref
cursor.

Instead of:

return refc;

you will return sqlstmt;

Then the SQL layer (which you must go through regardless -- it is a layer you
will be passing through anyway) can easily open that in a ref cursor.

      Rating: 5
Maximum open
      cursors exceeded when used with ORDER BY  June 24, 2002

Reviewer:  Roger Andersson  from Sweden
I use this technique for administration of users and their roles in one
application. I return all users and all their roles in one select and it works
fine, until I add an ORDER BY clause.

Then I get ORA-01000, maximum open cursors exceeded. I have open_cursors set to
100, which I know is too low, and my query returns, and will probably never
return more than, approx 150 records.

I can probably increase open_cursors and use the select order by statement I
need for my feature.

I want to know if there is a way to use these "embedded" cursor together with
order by and resultsets with a couple of hundred up to 1000 records, without
having open_cursors set very high? If so, how should the select statement look
like? If not, do you have a suggestion for another technique that can be used to
solve this in one select statement?

One simple testcase is to start with the Java code provided above, add a couple
of hundred records to the dept table and add an order by dname to the select
statement.

          Followup:

The order by is adding some recursive sql (to allocate temp space or do
something).  It is not a function of the number of records (you would get it
sorting 1 record or 1,000,000 records).

100 is way too low.  I typically run with 1,000 to 1,500. We dynamically
allocate the array 64 at a time so setting high does not have a high cost
associated with it.

      Rating: 5
ORA-01008: not
      all variables bound  August 30, 2002
Reviewer:
      A reader
Hi Tom

I followed your example and did this PL/SQL block but whenever I execute it I
get ORA-01008, what´s wrong with my code?

DECLARE
  type ref_cursor is ref cursor;
  c1                 ref_cursor;
  cursor ch_cursor is select co_id, ch_seqno, ch_status, userlastmod, entdate
                        from contract_history ch1
                       where rownum = 1;
  r_contract_histroy ch_cursor%rowtype;
  fr_entdate         CONSTANT DATE := trunc(sysdate - 7);
  to_entdate         CONSTANT DATE := trunc(sysdate - 1);
BEGIN
  OPEN c1 for 'select co_id, ch_seqno, ch_status, userlastmod, entdate
                 from (select co_id, ch_seqno, ch_status, userlastmod, entdate,
                        dense_rank() over (partition by co_id order by ch_seqno
desc) rango
                         from contract_history
                        where co_id < 11
                          and trunc(entdate) <= :to_entdate) iv_ch
                where rango = 1
                   or (ch_seqno = 1 and trunc(entdate) <= :to_entdate)
                order by co_id, ch_seqno' USING to_entdate;
  LOOP
    FETCH c1 into r_contract_histroy;
    EXIT when c1%notfound;
    dbms_output.put_line(r_contract_histroy.co_id);
  END LOOP;
  CLOSE c1;
END;
/

ERROR at line 1:
ORA-01008: not all variables bound
ORA-06512: at line 11

          Followup:

using to_entdate, to_entdate;

You have two bind variables, only one of which was bound.

ops$tkyte@ORA920.US.ORACLE.COM> variable x refcursor
ops$tkyte@ORA920.US.ORACLE.COM> set autoprint on
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> begin
  2          open :x for 'select :y, :y from dual' using 'Hello';
  3  end;
  4  /
begin
*
ERROR at line 1:
ORA-01008: not all variables bound
ORA-06512: at line 2

ERROR:
ORA-24338: statement handle not executed

SP2-0625: Error printing variable "x"
Elapsed: 00:00:00.00
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> begin
  2          open :x for 'select :y, :y from dual' using 'Hello', 'World';
  3  end;
  4  /

PL/SQL procedure successfully completed.

:Y                               :Y
-------------------------------- --------------------------------
Hello                            World

Elapsed: 00:00:00.00
ops$tkyte@ORA920.US.ORACLE.COM>

      Rating: 5
ORA-01008 I
      found error  August 30, 2002
Reviewer:
      A reader
Hi

I noticved I have to put two variables

using to_Entdate, to_entdate

I thought if the variable names are equal I only have to put once...

cheers

      Rating: 3
Usage in
      8.05  September 03, 2002
Reviewer:  B.Harris
       from UK
Tom,

I'm unsure of how to implement in 8.0.5
ie.  i want to return a ref cursor with embedded select using sql object type
solution (due to ref cursor's not supporting dynamic sql)

i would like to
- create dynamic sql using embedded select (using dbms_sql)
then insert into collection type (sql object type)
- return as ref cursor

How would I implement define_column and column_value to insert into my object
type?

Any workarounds/examples would be helpful?

          Followup:

Not making sense to me.  Not clear what you want -- but I would suggest saving
it up for a QUESTION, not a review.

      Rating:
      5
CURSOR  December 24, 2002
Reviewer:
      mo
Tom:

You say using CURSOR makes things easier. However I still do not see it by
running the example below. Any advantages of using this in pl/sql.

SQL> select deptno,dname,cursor(select ename from emp where emp.deptno =
dept.deptno)
  2  from dept where deptno = 10;

    DEPTNO DNAME          CURSOR(SELECTENAMEFR
---------- -------------- --------------------
        10 ACCOUNTING     CURSOR STATEMENT : 3

CURSOR STATEMENT : 3

ENAME
----------
CLARK
KING
MILLER

3 rows selected.

1 row selected.

  1  select a.deptno,a.dname,b.ename from dept a, emp b where
  2* a.deptno = b.deptno and b.deptno = 10
SQL> /

    DEPTNO DNAME          ENAME
---------- -------------- ----------
        10 ACCOUNTING     CLARK
        10 ACCOUNTING     KING
        10 ACCOUNTING     MILLER

3 rows selected.

          Followup:

where did I say it was easier?

It can be quite useful in many cases.  Write me a query against

ALL_USERS, ALL_OBJECTS, ALL_TAB_PRIVS that returns:

a) a username
b) object names and their types owned by that user
c) all grants granted to that user

      Rating:
      5
cursor  December 24, 2002
Reviewer:
      mo
Tom:
 Do you mean this query:

select a.user_id,substr(a.username,1,10) username,
 substr(b.owner,1,10) owner,substr(b.object_name,1,20) object_name,
substr(c.grantor,1,10) grantor,
substr(c.table_name,1,10) table_name,
substr(c.privilege,1,10) privilege
from all_users a,all_objects b, all_tab_privs c
where a.username=b.owner and b.owner=c.grantor
SQL> /

   USER_ID USERNAME   OWNER      OBJECT_NAME          GRANTOR    TABLE_NAME
PRIVILEGE
---------- ---------- ---------- -------------------- ---------- ----------
----------
         0 SYS        SYS        /1005bd30_LnkdConsta SYS        DUAL
SELECT
         0 SYS        SYS        /10076b23_OraCustomD SYS        DUAL
SELECT
         0 SYS        SYS        /1033c8a_SqlTypeWith SYS        DUAL
SELECT
         0 SYS        SYS        /103a2e73_DefaultEdi SYS        DUAL
SELECT
         0 SYS        SYS        /10501902_BasicFileC SYS        DUAL
SELECT
         0 SYS        SYS        /106faabc_BasicTreeU SYS        DUAL
SELECT
         0 SYS        SYS        /1079c94d_NumberCons SYS        DUAL
SELECT
         0 SYS        SYS        /10804ae7_Constants  SYS        DUAL
SELECT
         0 SYS        SYS        /108343f6_MultiColor SYS        DUAL
SELECT
         0 SYS        SYS        /10845320_TypeMapImp SYS        DUAL
SELECT
         0 SYS        SYS        /10948dc3_Permission SYS        DUAL
SELECT
         0 SYS        SYS        /1095ce9b_MultiCombo SYS        DUAL
SELECT
         0 SYS        SYS        /109cbb8e_SpanShapeR SYS        DUAL
SELECT
         0 SYS        SYS        /10a793fd_LocaleElem SYS        DUAL
SELECT
         0 SYS        SYS        /10b74838_SecurityMa SYS        DUAL
SELECT
         0 SYS        SYS        /10d0c623_drawBytesI SYS        DUAL
SELECT
         0 SYS        SYS        /10dcd7b1_ProducerCo SYS        DUAL
SELECT
         0 SYS        SYS        /10e48aa3_StringExpr SYS        DUAL
SELECT

          Followup:

yes and if you don't know what is wrong with that answer you gave, you need to
pick up a book on SQL!!!

tell me, why is DUAL in there over and over and over and over -- why will
/1005bd30_LnkdConsta appear in there over and over and over.

You have two 1:m relationships.  A square result set isn't going to be very good
for that.

Suppose you have a user SYS with 500 objects in all_objects and 50 grants in
all_tab_privs.  Do you really want 25000 records back for that user???

      Rating:
      5
CURSOR  December 24, 2002
Reviewer:
      mo
well i mean if we have 1:m between user and objects and 1:m between user and
grants then this should be the query. But it seems to be the same result. Are
you saying you can not get the result you want with a join statement, and I have
to use CURSOR.

  1  select a.user_id,substr(a.username,1,10) username,
  2   substr(b.owner,1,10) owner,substr(b.object_name,1,20) object_name,
  3  substr(c.grantor,1,10) grantor,
  4  substr(c.table_name,1,10) table_name,
  5  substr(c.privilege,1,10) privilege
  6  from all_users a,all_objects b, all_tab_privs c
  7* where a.username=b.owner and a.username=c.grantor
SQL> /

   USER_ID USERNAME   OWNER      OBJECT_NAME          GRANTOR    TABLE_NAME
PRIVILEGE
---------- ---------- ---------- -------------------- ---------- ----------
----------
         0 SYS        SYS        /1005bd30_LnkdConsta SYS        DUAL
SELECT
         0 SYS        SYS        /10076b23_OraCustomD SYS        DUAL
SELECT
         0 SYS        SYS        /1033c8a_SqlTypeWith SYS        DUAL
SELECT
         0 SYS        SYS        /103a2e73_DefaultEdi SYS        DUAL
SELECT
         0 SYS        SYS        /10501902_BasicFileC SYS        DUAL
SELECT

Is this how you do it:

select username,cursor (select object_name,object_type from all_objects where
object_type='TABL
cursor (select table_name,privilege from all_tab_privs) from all_users
where username='SYS'
SQL> /

USERNAME                       CURSOR(SELECTOBJECT_ CURSOR(SELECTTABLE_N
------------------------------ -------------------- --------------------
SYS                            CURSOR STATEMENT : 2 CURSOR STATEMENT : 3

CURSOR STATEMENT : 2

OBJECT_NAME                    OBJECT_TYPE
------------------------------ ------------------
ACCESS$                        TABLE
AQ$_MESSAGE_TYPES              TABLE
AQ$_PENDING_MESSAGES           TABLE
AQ$_PROPAGATION_STATUS         TABLE
AQ$_QUEUE_STATISTICS           TABLE
AQ$_QUEUE_TABLE_AFFINITIES     TABLE
AQ$_SCHEDULES                  TABLE
ARGUMENT$                      TABLE
ASSOCIATION$                   TABLE
ATEMPTAB$                      TABLE

CURSOR STATEMENT : 3

TABLE_NAME                     PRIVILEGE
------------------------------ ----------------------------------------
DUAL                           SELECT
SYSTEM_PRIVILEGE_MAP           SELECT
TABLE_PRIVILEGE_MAP            SELECT
STMT_AUDIT_OPTION_MAP          SELECT
STANDARD                       EXECUTE
V_$BH                          SELECT
V_$SESSION_LONGOPS             SELECT
V_$NLS_PARAMETERS              SELECT
V_$NLS_VALID_VALUES            SELECT
V_$MLS_PARAMETERS              SELECT
V_$OPTION                      SELECT
V_$VERSION                     SELECT
V_$PQ_SESSTAT                  SELECT
V_$SESSION_CONNECT_INFO        SELECT
V_$PQ_TQSTAT                   SELECT
V_$ACTIVE_INSTANCES            SELECT

          Followup:

I am saying you cannot get a meaningful, useful result with joins due to the
two independent 1:m relationships

You would use a cursor() function in order to get something you can actually
use.

      Rating: 5
CURSOR
      function  December 26, 2002
Reviewer:  mo

Tom:

1. I full understand now how CURSOR function can eliminate square results when
you jon more than two tables. IS this SQL or oracle SQL*PLUS function only.

2. I noticed the the CURSOR function above, it is always doing an outer join
even without using (+) in the join.  Can you use CURSOR with inner join so that
you can take out the wher ecaluse and print out only dept numbers that have
employees in them.

3. does this have any use in using static sql in 8i pl/sql or only in 9i R2.

THanks,

          Followup:

1) there are examples using plsql and java right above!

2) where exists... where in ... will do that.

3) using static sql in plsql *won't work* with this bit of functionality in 8i.
In 9i and up -- go for it.  In 8i, dynamic sql, views.

      Rating: 4
open q for
      query using a, b, c  February 07, 2003
Reviewer:
      Edgar Chupit  from Latvia
Hello Tom,

ops$tkyte@ORA920.US.ORACLE.COM> begin
  2          open :x for 'select :y, :y from dual' using 'Hello', 'World';
  3  end;
  4  /

I have a question about this, is it possible when I have query in varchar to
find out how many and what are the names of bind variables?
Suppose I have a stored function that constructs sql query and returns ref
cursor. My function have 3 params if each of params is not null than I add where
condition. Is there a possibility to write code like that one without having to
write all possible combinations of params? It's just an example but I hope
you'll get the point.

  function test1( tbl varchar, log varchar, par varchar ) return refCur is
     q varchar(512);
     result refCur;
  begin
     q := 'SELECT TABLE_NAME, TABLESPACE_NAME '||chr(10)||
          'FROM USER_TABLES '||chr(10)||
          'WHERE MONITORING = ''NO'' '||chr(10);

     if( tbl is not null ) then
         q := q || 'AND TABLE_NAME LIKE :TBL '||chr(10);
     end if;
     if( log is not null ) then
         q := q || 'AND LOGGING = :LOG '||chr(10);
     end if;
     if( par is not null ) then
         q := q || 'AND PARTITIONED = :PAR'||chr(10);
     end if;

     if( tbl is null and log is null and par is null ) then
         open result for q;
     else if ( tbl is null and log is null and par is not null ) then
         open result for q using par;
     else if( tbl is null and log is not null and par is null ) then
         open result for q using log;
     else if( tbl is null and log is not null and par is not null ) then
         open result for q using log, par;
     else if( tbl is not null and log is null and par is null ) then
         open result for q using tbl;
     else if( tbl is not null and log is null and par is not null ) then
         open result for q using tbl, par;
     else if( tbl is not null and log is not null and par is null ) then
         open result for q using tbl, log;
     else if( tbl is not null and log is not null and par is not null ) then
         open result for q using tbl, log, par;
     end if; end if; end if; end if; end if; end if; end if; end if;

     return result;
  end;

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279
show exactly how to do that

      Rating:
      5
awesome  February 07, 2003
Reviewer:
      Edgar Chupit  from Latvia
Hello Tom,

Thanks, really beautifull solution to use sys_context for this. Thanks!

      Rating: 5
problem with
      analytic function along with pl/sql table  February 25, 2003

Reviewer:  MRL  from mumbai,India
I have this scenario:
I need to join a pl/sql table inside a procedure with a database table and also
use rank() function ...all these in the same query whose results are returned
through a REF cursor.
Now analytic function HAS to be used in a dynamic query in pl/sql. But when i
join the pl/sql table with the database table(in that dynamic query) i get an
error as 'invalid column name'. The part of the query (with out the rank()
)function works well as a normal (non-dynamic) query.
Here it is:

create or replace type obj_1 as object
(col1 varchar2(10),col2 varchar2(10);
/

create or replace type table_1 as table of obj_1;
/
/* not giving package specification here*/

create or replace package body pkg_p
as
create or replace package body prc_p
(comma_sep_string_1 in varchar2,
 comma_sep_string_2 in varchar2,
 ref_cur_var out ref_cur)
as
  array_1 dbms_utility.uncl_array;
  array_2 dbms_utility.uncl_array;

  count_1 number;
  count_2 number;

  table_2 table_1:=table_1(obj_1('',''));
  dbms_utility.comma_to_table(comma_sep_string_1,count_1 ,array_1);

  dbms_utility.comma_to_table(comma_sep_string_2,count_2 ,array_2);

table_2 .extend(count_1-1,1);

 for i in 1..count_1.count loop
   table_2(i).col1:=array_1(i);
   table_2(i).col2:=array_2(i);
 end loop;

 open ref_cur_var for
  'select a1,b1,c1,rank() over (partition by a1 order by b1 desc) as myrank
   from database_table,(select x.col1,x.col2 from TABLE(CAST(table_2 as table_1)
as x ) pl_sql_table
  where database_table.c1=pl_sql_table.col1';
end prc_p;
end pkg_p;

forgive me for syntax errors.

I get the error as 'invalid column name';
If i just return
select a1,b1,c1   from database_table,(select x.col1,x.col2 from
TABLE(CAST(table_2 as table_1) as x ) pl_sql_table
  where database_table.c1=pl_sql_table.c1
through the ref cursor (non dynamic query without rank()) it works fine.

But i need to include rank() and the join with pl/sql table...how do i do it?

Have i done a very silly mistake in the code????

I also need to do a myrank < 21 in the where clause .
how do i do it?

(i have read in this site before that using comma_to_table for such a cause is
not correct)

          Followup:

you have to bind inputs -- this is the same as trying to bind a "date" into a
query -- no different:

open ref_cur_var for
  'select a1,b1,c1,rank() over (partition by a1 order by b1 desc) as myrank
   from database_table,(select x.col1,x.col2 from TABLE(CAST( :x as table_1)
as x ) pl_sql_table
  where database_table.c1=pl_sql_table.col1'
using table_2;

to get "myrank <21", you use an inline view

open ref_cur_var for
  'select * from (
     select a1,b1,c1,rank() over (partition by a1 order by b1 desc) as myrank
   from database_table,(select x.col1,x.col2 from TABLE(CAST( :x as table_1)
as x ) pl_sql_table
  where database_table.c1=pl_sql_table.col1 ) where myrank < 21'
using table_2;

      Rating: 4
cursor() in
      views  May 29, 2003
Reviewer:  A reader

The OP's example (get user, all his contacts and all their addresses) is a great
real-world example of CURSOR.

But when I try to create a view using a SELECT with a cursor() expression, I get
ORA-22902 (cursor not allowed).

I really dont want to create 3 views (user, user_contacts, contact_address) for
this! A user view with 2 CURSOR() columns would be perfect.

How can I work around this? Thanks

          Followup:

i guess you won't be using a view -- perhaps a stored procedure that returns a
ref cursor.

      Rating: 4
Recursive
      describe on the result set  July 02, 2003

Reviewer:  Piotr Jarmuz  from Braunschweig, Germany

Hello Tom,

Is there a way to do a recursive describe on the result set with the embedded
cursors? I mean can I code:

public Node describe(ResultSetMetaData rs) throws Exception {
    ResultSetMetaData md=rs.getMetaData();
    for (int i=0; i<md.getColumnCount(); i++) {
    if (md.getColumnType(i+1)==OracleTypes.CURSOR) {
             // somehow get MetaData for this CURSOR
    }
        handle each other type
}

BEFORE I fetch actually the Cursor for the nested query?
I guess I can't but I'd like your confirmation.

My goal is to write a generic Java tree builder based on any SQL query also with
embedded cursors.

I always can do that on the fly.
I though about it as an optimization to my builder.

Another associated problem:
Can I return an array of refcursors from a PL/SQL to the client? I tried the
following in 9iR2 but it says:

declare
type cursor_array_t is table of sys_refcursor;
begin
 ...
end;

ORA-06550: line 2, column 24:
PLS-00990: Index Tables of Cursor Variables are disallowed
ORA-06550: line 2, column 1:
PL/SQL: Item ignored

It doesn't have to be an array. But I need a way to return an unknown number of
cursors. I thought about embedded cursors built with execute immediate but maybe
there is a simpler way?

Thanx a lot in advance!

          Followup:

the "describe" in java is limited to describing result sets - hence you must
retrieve the result set.  so, you have to fetch a row in order to describe the
result set contained in that row.

You cannot return an unknown number of ref cursors.

July
      02, 2003 -- Thanks for the question
      regarding "password", version 8i

      You Asked
Hi tom

i am vote yes to your new police it is beter to give only one up to three
questions .

my question: is
i create a user and password to connect to the database i well tell the user
about the password belong i need to force him to change the password next time
logon because it is very hard if you have 90 users?

can you give link for just what is new in 9i database ?

i use procedure and when i need to submit
exec procedurename ('he havn't');

i get error how can i implement the "'"
thank's

      and we said...
You cannot make them change it the next time, but you can create them and have
the password expire in N days (soon) so they are forced to change it.  See

http://technet.oracle.com/doc/oracle8i_816/server.816/a76956/secure.htm#2185
For new 9i stuff:

http://download-east.oracle.com/otndoc/oracle9i/901_doc/nav/docindex.htm#index-NEW
You use '' for a ' in a string:

exec p( 'he havn''t' )

BUT in your programs, you BETTER be using bind variables:

 begin p( :x ); end;

and bind "he havn't"  - no need to quote quote in a bind variable.
Review &
      Followup

      Rating:
      5
Ashraf  November 04, 2001
Reviewer:
      A reader
ok but for the users when the password is expired then i must run sql from the
user side which i am not install to users or i run from another pc and change
all users password
i know this future as you till is not their but i belive it must be

          Followup:

You need not run any SQL from the user side -- nothing more then you would if
you made them change the password upon the first logon.

Use the GRACE period to make it so they can login but get a warning that they
must change their password:

... <quote> ...
DBAs can also specify a grace period using the CREATE PROFILE statement. Users
enter the grace period upon the first attempt to log in to a database account
after their password has expired. During the grace period, a warning message
appears each time users try to log in to their accounts, and continues to appear
until the grace period expires. Users must change the password within the grace
period. If the password is not changed within the grace period, the account
expires and no further logins to that account are allowed until the password is
changed.

      Rating:
      4
Reader  November 04, 2001
Reviewer:
      Reader
Tom,
About the password complexity verification (utlpwdmg.sql),
does this get linked to a profile and profile get
assigned to an user.
"
CREATE/ALTER PROFILE profile_name LIMIT
PASSWORD_VERIFY_FUNCTION routine_name
"
From DBA Administration Manual,
"
Oracle recommends that you do not change passwords
using the ALTER USER statement because it does not fully support the password
verification function. Instead, you should use OCIPasswordChange() to change
passwords.
"
Does this imply that the comlexity verfication policy
can not be applied at the *command level*
(ex: alter user user1 identified by user1;)

Thanks

          Followup:

Yes it does in 816 and up.  In prior releases, it just didn't work fully.  The
ALTER USER command doesn't necessarily have explicit access to the old password
and the password verficiation routine would not therefore have access to it
either (making it hard/impossible to do the password verification).

Note that the ALTER USER and CREATE USER command will work fine for a DBA as
they are allowed to change a users password and skip the verification routine
(just like root on unix can set a users password to anything they want)

      Rating: 5
Grace
      Period  May 06, 2002
Reviewer:  Irfan
       from Kuwait
Hi Tom, from the first review, your solution
"Use the GRACE period to make it so they can login but get a warning that they
must change their password".

How can it be achieved in Oracle 734, since no such option. We use database as a
backend and the users have no permissions to run "alter user" commands.

          Followup:

You cannot achieve this in 734 since (as you pointed out) this feature does not
exist.

You would have to code your own "password expiration"/"grace period" into your
application yourself.  It would not be 100% foolproof since things like odbc
apps, sqlplus, etc won't obey this grace period and won't "expire" the password
-- only your app would.

      Rating: 4
How to change
      password to new and set it back to old afterwards?  June 26,
      2002
Reviewer:  JJ  from Deutchland
I have DBA access to the database instance.
I do not know the password for a user I need to connect to.
I want to change the password for that user to new password.
I want to run some processing using shell scripts and stuff and connecting to
this user in the database.
I then want to reset that user's password back to what it was before I changed
it.

How to do it?

Thanks- JJ

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:83012348058

      Rating: 3
Using
      OCIPasswordChange  February 25, 2003
Reviewer:
      Harri  from Finland
Can i call OCIPasswordChange from PL/SQL block? Or is there any other way to use
it without using any external code.

          Followup:

OCI can call plsql

PLSQL can call OCI via an EXTERNAL procedure only.

      Rating:
      4
password_verify_function in profile  June 11, 2003

Reviewer:  A reader  from USA
Tom,
In the manual, it said utlpwdmg.sql will create verify_function under sys
schema. Can I have my own password check function, say passwd_check, owned by
schema other than sys (like security_mgr), and in profile sepecify
password_verify_function secority_mgr.passwd_check ?

Thanks.

          Followup:

In a word, no

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96521/secure.htm#8362

      Rating:
      4
OCIPasswordChange from plsql  July 02, 2003

Reviewer:  Mark Swarts  from Buffalo
Tom,

Re: "Can i call OCIPasswordChange from PL/SQL block?...PLSQL can call OCI via an
EXTERNAL procedure only.",
I am trying to figure out the proper syntax to call OCIPasswordChange from
plsql...not having much luck. All I can find in metalink is a C program call to
it. Would you be able to assist?

Thanks very much!

          Followup:

you would have to write an external procedure in C to call OCI

what about

execute immediate 'alter user ' || user || ' identified by ' || l_password;

      Rating:
      4
  July 02, 2003
Reviewer:  Mark

Yes, we are using "alter user" currently. I was concerned though because the doc
says: "Oracle recommends that you do not change passwords using the ALTER USER
statement because it does not fully support the password verification function.
Instead, you should use LNOCIPasswordChange() to change passwords."

Thanks for the info.

July 03, 2003 Arunima -- Thanks for the question regarding "Sql
      query", version 8.1.7

      You Asked
Hi Tom,

I was wondering if there is way to solve this problem. I have a view in the
database that looks like this.

parent   child
1          2
2          3
3          4
4          5
11         22
22         33
33         44

I want to write a query on this view that gets me the details of all the implied
relationships.

Result:

parent  child
1        2
1        3
1        4
1        5
2        3
2        4
2        5
3        4
3        5
4        5
11       22
11       33
11       44
22       33
22       44
33       44

Hope I could explain the problem clearly to you.

Thanks a lot for your help.

      and we said...
In 9i, this is easy:

ops$tkyte@ORA9I.WORLD> select rpad( '*', level*2, '*' ) lvl, parent, child,
  2             sys_connect_by_path( parent, ' ' ) cbp
  3    from t
  4  connect by prior child = parent
  5  /

LVL            PARENT      CHILD CBP
---------- ---------- ---------- --------------------
**                  1          2  1
****                2          3  1 2
******              3          4  1 2 3
********            4          5  1 2 3 4
**                  2          3  2
****                3          4  2 3
******              4          5  2 3 4
**                  3          4  3
****                4          5  3 4
**                  4          5  4
**                 11         22  11
****               22         33  11 22
******             33         44  11 22 33
**                 22         33  22
****               33         44  22 33
**                 33         44  33

16 rows selected.

The sys_connect_by_path is new in 9i. You could easily substr off the first
element in order to get your desired result.

In 8i, we can achieve this with a bit of work (and it'll not be nearly as
efficient as the 9i approach):

ops$tkyte@ORA9I.WORLD> select t.*,
  2         substr( (select max( to_char(level,'fm00009' ) || '/' || t2.parent)
  3                    from t t2
  4                   where level <= t.lvl
  5                   start with t2.child = t.child
  6                 connect by prior t2.parent = t2.child), 7 )
  7   from (
  8  select rpad( '*', level*2, '*' ) lvl_str, level lvl, parent, child
  9    from t
 10  connect by prior child = parent
 11   ) t
 12  /

LVL_STR           LVL     PARENT      CHILD SUBSTR((SELECTMAX(TO_CHAR(LEVEL,'F
---------- ---------- ---------- ---------- ----------------------------------
**                  1          1          2 1
****                2          2          3 1
******              3          3          4 1
********            4          4          5 1
**                  1          2          3 2
****                2          3          4 2
******              3          4          5 2
**                  1          3          4 3
****                2          4          5 3
**                  1          4          5 4
**                  1         11         22 11
****                2         22         33 11
******              3         33         44 11
**                  1         22         33 22
****                2         33         44 22
**                  1         33         44 33

16 rows selected.

If you search google groups for sys_connect_by_path and read the posting by
Jonathan Lewis:

http://groups.google.com/groups?q=sys_connect_by_path+group:comp.databases.oracle.*&hl=en&selm=994786401.8669.0.nnrp-07.9e984b29%40news.demon.co.uk&rnum=1
you'll see a way to make sys_connect_by_path "work" in 8i (totally, 100%
unsupported and undocumented however)
Review &
      Followup

      Rating:
      5
Excellent  March 01, 2002
Reviewer:
      A reader
Thanks a lot.

      Rating: 4
sql
      query  March 01, 2002
Reviewer:  arunima

Tom,

I need to run this query on a view and the view has joins so i get an error
'Cannot have joins in connect by).

Is there a way to resolve this.

Thanks a lot.

          Followup:

9i, prior to 9i you cannot join in a connect by.

      Rating: 5
sql
      query  March 01, 2002
Reviewer:  arunima

Though I should mention here that I made it work by getting rid of the view and
nesting the subquery within the query you suggested.

It works great with all your help but i was just wondering can we make it work
on a view that has join which i think is not possible. right ?

Thanks once again.

          Followup:

correct

      Rating:
      5
  March 04, 2002
Reviewer:  arunima

Tom,

In the below mentined problem the parent column in the database is stored as raw
datatype. I created a view using the query you suggested but the results of the
substr( (select max( to_char(level,'fm00009' .......) column are in varchar2.

Is there a way to have this result in raw. The reason being I need to do a UNION
of this query with another and I am not able to do it because of inconsistent
datatypes.

Thanks a lot.

          Followup:

Just whip a hextoraw in there after the substr.  When you concat the raw to the
to_char(level,'fm000009'), it'll put it there in hex.  We just substr out the
hex and convert it back to raw:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( parent raw(1), child raw(1)
);

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( '01',     '02' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( '02',     '03' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( '03',     '04' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( '04',     '05' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( '11',     '22' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( '22',     '33' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( '33',     '44' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select t.*,
  2             hextoraw(
  3         substr( (select max( to_char(level,'fm00009' ) || '/' || t2.parent)
  4                    from t t2
  5                   where level <= t.lvl
  6                   start with t2.child = t.child
  7                 connect by prior t2.parent = t2.child), 7 )
  8                             )
  9   from (
 10  select rpad( '*', level*2, '*' ) lvl_str, level lvl, parent, child
 11    from t
 12  connect by prior child = parent
 13   ) t
 14  /

LVL_STR           LVL PA CH HEXT
---------- ---------- -- -- ----
**                  1 01 02 01
****                2 02 03 01
******              3 03 04 01
********            4 04 05 01
**                  1 02 03 02
****                2 03 04 02
******              3 04 05 02
**                  1 03 04 03
****                2 04 05 03
**                  1 04 05 04
**                  1 11 22 11
****                2 22 33 11
******              3 33 44 11
**                  1 22 33 22
****                2 33 44 22
**                  1 33 44 33

16 rows selected.

      Rating:
      5
Thanks  July 02, 2003
Reviewer:
      A reader
I wanted to show the same query the user mentioned in the original post. I am
using 8.1.7 database.

The data is

P_id  root_p_id
1    null
2    null
3    2
4    2
5    3
6    null
7    6
8    6

The results should be
P_id    root_p_id
1    1
2    2
3    2
4    2
5    2
6    6
7    6
8    6

select
(select min(t2.p_id) from p t2
            start with t2.P_ID = p.p_id
          connect by prior t2.root_p_id = t2.p_id) root_p_id
,p.p_id
from
(
select root_p_id, p_id
from p
start with root_p_id is null
connect by prior p_id = root_p_id) p

v5.4/specs54/16427.sql
It seems that I am getting correct results. Can you please verify the query and
let me know your comments (like any better way to write this query)?

          Followup:

well, that doesn't look anything like mine?  No use of LEVEL in the MIN

make sure to re-read the example and understand what it does.  I encoded a
string with LEVEL that would work with MAX

      Rating:
      5
Thanks  July 02, 2003
Reviewer:
      A reader
It seems that I didn’t convey my requirement correctly. Sorry for the confusion.

My requirement is a little different if you compare with the original post.

I want just root parent id for all child’s (what ever the child level is)

Example:
P_id    root_p_id
1    null
2    1
3    2
4    3
5    4

I want  the output like

P_id    root_p_id
1    1
2    1
3    1
4    1
5    1

Here is my logic

With the inline view

select root_p_id, p_id
from p
start with root_p_id is null connect by prior p_id = root_p_id

I am getting the data as

1    null
2    1
3    2
4    3
5    4

then the another tree query in the column level

select min(t2.p_id) from p t2
            start with t2.P_ID = p.p_id
          connect by prior t2.root_p_id = t2.p_id

is taking each p_id and traversing from leaf node to root to find out its
parent. That’s how each row is getting its parents list then I am using min to
get the root p_id. In my system root p_id is always is less then the childs.

So I wrote the query like this

select
(select min(t2.p_id) from p t2
            start with t2.P_ID = p.p_id
          connect by prior t2.root_p_id = t2.p_id) root_p_id
,p.p_id
from
(
select root_p_id, p_id
from p
start with root_p_id is null
connect by prior p_id = root_p_id) p
Please let me know your comments.

          Followup:

yes, well that is pretty much what the original was as well.

you need to incorporate LEVEL IN THERE

and you want the MAX level.

Just like my example.  You are walking the child back up the tree - subquery
result with the MAX LEVEL therefore is the one you want.  Just like in the
example

      Rating: 3
did he mean
      transitive closure?  July 03, 2003
Reviewer:
      mikito harakiri
There are 2 ways to query transitive closure in oracle.
1. Parsing sys_connect_by_path:

http://otn.oracle.com/oramag/code/tips2003/060803.html
2. Firing corellated "connect by" subquery:

select a.tail, b.head from links a, links b
where b.head in (
      select head from links
      connect by prior head = tail
      start with tail = a.tail
)

July 03, 2003 Rory -- Thanks for the question regarding
      "Validating rman cold backup", version 9.2.0.3

      You Asked
Hi TOm,

    Just some questions on RMAN. It's my first time to use it and it's great. I
have created a strategy and a script on backing up
our database using rman. But it's only a coldbackup ok in noarchivelog
mode. I know you wont agree but that's my scenario for now.
I configured RMAN with following:

RMAN> CONFIGURE DEFAULT DEVICE TYPE TO DISK;
RMAN> CONFIGURE DEVICE TYPE DISK PARALLELISM 10;
RMAN> CONFIGURE CHANNEL DEVICE TYPE DISK FORMAT '/backupdb/RMAN_%d_%s_%p.bak'
MAXPIECESIZE=2000M;

I'll include the RMAN command inside my shell script.

rman target / nocatalog <<EOF>> $rLogdir/rman_$rPetsa.log
shutdown immediate;
startup mount;
backup database include current controlfile tag='TAG_$rPetsa';
exit
EOF

Here are my questions:

1. Do you see anything wrong with the format I made in the configure device. I
mean, the backup goes to one disk daily. With the format above, would it always
be unique and wont it overlap another backup file?

2. I noticed that with parallelism set to 10, 10 backup files were created then
filled up until they reached 2gig, then they start with another batch of backup
files composed of 10. Is this the correct behavior of the parallelism parameter?

3. What should I consider in using the PARALLELISM parameter? In glance, disk
util was 100%(maybe because it was writing to a single disk, correct me if I'm
wrong) and CPU util was only 35%, MEM was 77%, Swap Util at 57%. Are the stats
ok?

4. Last question, when I tested the script on a small system of 4gig, recovery
worked fine. Now I dont have YET the pleasure of testing recovery since we dont
have a test machine for our 500gig database. What utility can I use to validate
my RMAN BACKUP FILES? Can the VALIDATE or CROSSCHECK commands to this? I mean,
can they say that my backup files are not corrupted, that the backupsets' pieces
complete?

Thanks a lot Tom and more power.

      and we said...
1) since %s is the backup set number and is unique for each backup, that should
be OK for uniqueness.  however, if you restore or recreate your control files,
it'll get reset and duplicates will result.

%F would be a nice "unique" name, or %U

2) yes, you can set the backup piece size bigger if you like (and you want files
>2gig)

3) it is mostly all about disk throughput.  rman isn't excessively cpu
intensive, but it reads all of the database and writes most of it back out.  the
amount of disks you have will throttle this.  with a single disk, you are not
getting much out of parallel 10 (other then extra contention)

4) yes they can, that is what they do.
Review &
      Followup

      Rating: 4
RMAN - RECOVERY
      PROBLEM  July 02, 2003
Reviewer:  Himnish
      Narang  from Chandigarh, India
I am in the process of testing my RMAN backups and recovery procedures. I have
taken level 0, level 1 and level 2 bakups. I am using a catalog database as
well. Full resync of the catalog has been done. After this accidently a table is
dropped which was created betweeb level 1 and level 2 backups.

I tried to recover the table using follwing procedure :

1. I deleted the files of the tablespace in which the table resides.

2. Restored the files from the level 0 backup.

3. Recover the database by using command

recover database until time <the time just before the table was deleted>.

4. database was recovered but i was unable to open the database eother with
resetlogs or noresetlogs option. the message diplayed was
'/database/data/mis1_db.dbf' needs recovery.

This is the same file which i have deleted earlier.

My queries are :
1. Can u please tell me where i had gone wrong?
2. Can't we 'recover datafile until time <  > '?

Thanx in advance.

          Followup:

in order to do an incomplete recovery like that -- ALL files must predate the
time at which you want to cancel.

In your example, you only restored the files form one tablespace -- SYSTEM for
example post dates your cancel time -- hence SYSTEM would be "newer"

You need to either

a) perform a TSPITR (tablespace point in time recovery)
b) perform a full database restore followed by a cancel based recovery (recover
until) that post dates the time of the backup.

      Rating: 5
Validating RMAN
      Backup  July 02, 2003
Reviewer:  Rory
      Concepcion  from Philippines
Thanks a lot Tom. You mean that settings of parallelism set to 10 wouldn't
really do much good coz I'm only writing to one disk? sounds logical. What if my
extra disks came and lets say there were 3 disks now to write. How do I
configure the channels. In my configure above,

CONFIGURE CHANNEL DEVICE TYPE DISK FORMAT '/backupdb/RMAN_%d_%s_%p.bak'
MAXPIECESIZE=2000M;

I didnt put any channel number so that it would be the template used by all
channels. How do I configure 3 channels? Should I put numbers to the channels?
Will they all be used? thanks.

          Followup:

it could be harming you even by introducing massive contention on a single
device.

you would allocate three channels -- see chapter 5 of the 9ir2 rman users guide,
it covers all of these concepts:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96566/rcmconc1.htm#430706

July 03, 2003 Warwick -- Thanks for the question regarding
      "getting rows N through M of a result set which contain duplicates",
      version 8.1.7

      You Asked
Hi Tom,

I have implemented your sql code in the article "getting rows N through M of a
result set" (sql below).
select *
  from ( select a.*, rownum rnum
           from ( YOUR_QUERY_GOES_HERE -- including the order by ) a
          where rownum <= MAX_ROWS )
 where rnum >= MIN_ROWS

  This has worked fine until changes were made to the web applications data
structure.

Previously, each record retrieved was contained within the same table.

Presently, for each record found there is now one or several related records
from another table.

Using the above sql results in the correct rows being retrieved and counted but
does not retrieve each distinct record (main table fields without joined fields)
for display as records N of M on the web page.

How can I retrieve records 1 of 10 which really consist of possibly 15 records
due to the join to another table?

For example, I have a table that has details on an event.  People can add 1 or
more notes about this event forming a 1 to many relationship.  I would like to
use the above sql to display the events on a web page with the notes displayed
with the event.  Because the query above produces repeated values I am unable to
display the correct value of records being shown, ie. records 1 - 10 of 100.
The above sql retrieves unique records, but what about retrieving unique records
which also have repeated values.

Warwick

      and we said...
sorry -- i don't get it.

if you put your join query in the "your_query_goes_here" part -- you get rows
1-10 of the result set.  EG: to get rows 2-3 of the join of EMP to DEPT, you
query:

scott@ORA920> select *
  2    from ( select a.*, rownum rnum
  3             from ( select dept.deptno, emp.ename
  4                              from emp, dept
  5                                     where emp.deptno = dept.deptno
  6                                     order by dept.deptno) a
  7                   where rownum <= 3 )
  8   where rnum >= 2
  9  /

    DEPTNO ENAME            RNUM
---------- ---------- ----------
        10 KING                2
        10 MILLER              3

If you mean "i want rows 1-10 of the "parent" table and join that to all of the
child records -- giving me perhaps more then 10 rows -- then you just join the
above style query to the child records.  EG: to get rows "2-3" of the dept table
and then display them with all associated emp records:

scott@ORA920> select dept.deptno, emp.ename
  2    from ( select *
  3             from ( select a.*, rownum rnum
  4                      from ( select dept.deptno
  5                                       from dept
  6                                              order by dept.deptno) a
  7                            where rownum <= 3 )
  8            where rnum >= 2 ) dept,
  9                  emp
 10   where dept.deptno = emp.deptno
 11   order by dept.deptno
 12  /

    DEPTNO ENAME
---------- ----------
        20 SMITH
        20 ADAMS
        20 FORD
        20 SCOTT
        20 JONES
        30 ALLEN
        30 BLAKE
        30 MARTIN
        30 JAMES
        30 TURNER
        30 WARD

11 rows selected.

Review &
      Followup

      Rating: 3
Extra
      Information  July 02, 2003
Reviewer:
      Warwick  from Sydney, Australia
Tom,

I have found your response useful, but not with the current situation that I am
in.
I have a dynamic sql query which retrieves records depending on the search
criteria.  I then insert the results in a table storing the starting sequence
and end sequnence numbers for use in the Records N of M query.
I then want to use the sql provided to retrieve, say records 1 to 10 of the
parent record and all of its child records.  Unfortunately, because the
resultset contains records that are already a result of a join (child records
are already associated with the parent in a single table), I am unable to use
the code provided.

Is there any other way of achieving the same effect but within a single table
structure?

Thanks for your help so far.

Warwick

          Followup:

you'll have to figure it out -- you know all of the constructs you can use --
but without a large example here, I cannot comment (cause obviously -- I'm not
following you)

My answer will most likely be "don't use that other table", but anyway.

NOT that I want a really big example here, when I'm taking  questions -- a
question by example would be just fine. (eg: please don't post reams of code
here)

July 03, 2003 Brigitte -- Thanks for the question regarding
      "Performance tuning", version 8.1.7

      You Asked
Hi Tom

I have 4 questions of performance tuning for Oracle 8.1.7

1. I read DISTINCT implies a internal sort. I have a query like

select distinct attribut1 from tab1 order by attribut1;

Can I ommit the order by clause?

2. I have a concatenated index of 3 columns. In the select statements the
columns were always queried together. The access to 2 columns is with "=" and to
the 3. column with "in". Where should the column in the index positioned which
is queried with "in" , first or last?

3. Can I use the INDEX_COMBINE hint in a query of more then 1 table? I want to
change the full table scan of a growing table to 1/3 with a bitmap index (there
are only 3 different values in the column). I hadn't success with it. I had only
success in a query with 1 table.

4. How can I alter tables to "organisation index" in a productive database?
Which problems can occur?

Thanks

Brigitte

      and we said...
1) only if you want the data to come out in any order we like.  If you want data
ordered, order it.  If you don't care about the order, don't order it.

The ONLY way to assure ordered data is to order by it.  Don't rely on the data
coming out sorted under any other circumstances.

2) won't much matter.  A query like:

select * from t where a = 5 and b = 6 and c in ( 1, 2 );

will be processed much like:

select * from t where a = 5 and b = 6 and c = 1
union all
select * from t where a = 5 and b = 6 and c = 2
/

So, I might choose to put the LEAST selective columns first and use index key
compression (eg: if you:

select count(distinct a)/count(*), count(distinct b)/ count(*), count(distinct
c)/count(*)
fromt t;

and find a's ratio to be the smallest, c's ratio to be the next smallest and b's
ratio the largest -- create the index on  "t(a,c,b) compress 2"

3) well, typically people would use PARTITIONING for this.  Using a bitmap index
to do this is the wrong approach (i pray you are not in an oltp system, I've
seen people try this "approach" in those systems only to have it fall over
dead).

This is a partitioning thing, not a bitmap index solution.

but yes, index combine works with more then one table, if the combined indexes
can be used to resolve all of the columns and the optimizer agrees it makes
sense (remember, a hint is a HINT -- not an order.  It is a suggestion, not a
directive)

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

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

ops$tkyte@ORA817DEV.US.ORACLE.COM> create bitmap index emp_bmi_1 on emp(job);
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create bitmap index emp_bmi_2 on emp(deptno);
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index dept_idx on dept(deptno);
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on
ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ index_combine( emp emp_bmi_1
emp_bmi_2 ) */
  2             emp.job, emp.deptno, dept.loc
  3    from emp, dept
  4   where emp.deptno = dept.deptno
  5    and emp.job = 'CLERK'
  6    and emp.deptno = 10
  7  /

JOB           DEPTNO LOC
--------- ---------- -------------
CLERK             10 NEW YORK

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=40)
   1    0   MERGE JOIN (CARTESIAN) (Cost=4 Card=1 Bytes=40)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'DEPT' (Cost=1 Card=1 Bytes=21)
   3    2       INDEX (RANGE SCAN) OF 'DEPT_IDX' (NON-UNIQUE) (Cost=1 Card=1)
   4    1     SORT (JOIN) (Cost=3 Card=1 Bytes=19)
   5    4       BITMAP CONVERSION (TO ROWIDS)
   6    5         BITMAP AND
   7    6           BITMAP INDEX (SINGLE VALUE) OF 'EMP_BMI_1'
   8    6           BITMAP INDEX (SINGLE VALUE) OF 'EMP_BMI_2'

4) do you mean "production" or "productive" -- probably production.

In 8i, you'll need some downtime, as you have to rebuild the object.  In 9i, you
can do this online with dbms_redefinition.  However, I'm not really 100% sure
what you are after here.

If you are interested in the structure and uses of IOTs (index organized tables)
I have a pretty extensive chapter on them and all table types in my book.

By all means BENCHMARK you use of them in a test environment prior to doing
anything.  (that advice is also all over the book)...

Review &
      Followup

      Rating: 3
Can I do
      this  March 26, 2002
Reviewer:  Pawan
       from USA
Tom,
   I have a table which has primary key (sequence number). Can I alter this
table to be INDEX ORGANIZED.This is in  a Test environment. Basically can I
create an IOT with " Create table AS Select " clause.

Thanks

          Followup:

yes, you can.

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

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table dept add constraint dept_pk
primary key(deptno);

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table dept_iot (deptno primary key,
dname, loc )
  2  organization index
  3  as
  4  select * from dept;

Table created.

      Rating:
      4
Thanks  March 26, 2002
Reviewer:
      pawan  from USA

      Rating: 4
What is
      happening?  March 26, 2002
Reviewer:  Pawan
       from USA
Tom,
    I was just trying to see the improvement that I would get by running the
same query on two tables - one index organized and the other not. I did the
following and then "SET TIMING ON" - The results are baffling me. There no
Indexes created on either table expect for the primary key.I donot have any
manual or yr book with me presently (have it at home) and hence and am not able
to go deep.Can u PLEASE help me decipher the results and point what I am doing
wrong. Also why one trace shows the Cost and other does not.
Thanks in advance
--------------
ADHOC@SF028I> create table indorg_iot (SEQ_ID Primary key,
  2  OWNER,
  3  OBJECT_NAME,
  4  SUBOBJECT_NAME,
  5  OBJECT_ID,
  6  DATA_OBJECT_ID,
  7  OBJECT_TYPE,
  8  CREATED,
  9  LAST_DDL_TIME,
 10  TIMESTAMP,
 11  STATUS,
 12  TEMPORARY,
 13  GENERATED,
 14  SECONDARY)
 15  organization index
 16  as select * from indorg;

Table created.

ADHOC@SF028I> @count
Enter table name:indorg_iot
old   1: select count(1) from    &TABLE
new   1: select count(1) from    indorg_iot

COUNT(1)
---------
1077312

ADHOC@SF028I> @count
Enter table name:not_indorg
old   1: select count(1) from    &TABLE
new   1: select count(1) from    not_indorg

COUNT(1)
---------
1077312

ADHOC@SF028I> ADHOC@SF028I> /

TABLE_NAME           INDEX_NAME
-------------------- ------------------------------
INDORG_IOT           SYS_IOT_TOP_30637

ADHOC@SF028I> set timing on
ADHOC@SF028I> select count(*)    from    not_indorg
2  where status='VALID';

COUNT(*)
---------
1069952

Elapsed: 00:00:13.01
ADHOC@SF028I> select count(*)    from    indorg_iot
2  where status='VALID';

COUNT(*)
---------
1069952

Elapsed: 00:00:27.26

ADHOC@SF028I> set autotrace traceonly explain
ADHOC@SF028I> select count(*) from not_indorg
2  where status='VALID';
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
0      SELECT STATEMENT Optimizer=CHOOSE
1    0   SORT (AGGREGATE)
2    1     TABLE ACCESS (FULL) OF 'NOT_INDORG'

ADHOC@SF028I> select count(*) from indorg_iot
2  where status='VALID';
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=5)
1    0   SORT (AGGREGATE)
2    1     INDEX (FAST FULL SCAN) OF 'SYS_IOT_TOP_30637' (UNIQUE)    (
Cost=4 Card=1 Bytes=5

      Rating:
      3
  March 26, 2002
Reviewer:  Harish
      harbham  from UK.
Can you please clarify why the least selective column should be used first ? Is
this something related to index compression ?

          Followup:

I did not say "should be" rather I said:

So, I might choose to put the LEAST selective columns first and use index key
compression (eg: if you:

so yes -- it was in regards to key compression.  There is nothing to the story
that most selective items should be first in the index.  they items in the index
should be ordered by HOW they are used, not their selectivity.

      Rating: 5
Which Order of
      Colums is Better?  September 12, 2002
Reviewer:
      Jan  from SG
I have a look up IOT table with compress=2 with fallowing columns:

CATEGORY   VARCHAR2(10)  pk
CODE       VARCHAR2(10)  pk
LANG       VARCHAR2(3)   pk
CODE_DESC  VARCHAR2(100)

In our Environment, we will have only one language. What is better, if the order
of PK is:

1) LANG,CATEGORY,CODE or
2) CATEGORY,LANG,CODE  ?

Solution 1) is more compressible, but probably Index Scanning does more jobs
here because all records has the same value in LANG column. Is it true?

Another question, if I have IOT with e.g. 5 columns and only 2 of them are PK
(col1,col2), how are the rows ordered in IOT?

It is by col1,col2,col3,col4,col5? It means that is the order of columns in
CREATE TABLE statment important?
ORDER BY

Thanks,

      Rating: 4
joined
      table  October 28, 2002
Reviewer:  Chris

Dear Tom,

I use TKPROF to analyze the query below. But I get the slower query result when
I index the nmfile. Why was that.
In other SQL statement, by index nmfile I got faster query.
#### 1. without index on nmfile

SELECT a.*
FROM t_cdr200 a, t_profile_anumber b, t_profile_yn c
WHERE
(SUBSTR(a.bnumber,1,1) != '0' AND  a.anumber = b.anumber
AND a.nmfile = c.batchNo AND c.batchID >= 1 AND c.batchID <= 1
AND a.durasi > (b.dcrphlocal+(0.01 * b.dcrphlocal)))
AND c.batchNo not in
(select d.batchNo from t_fraudullent_yn d, t_profile_yn e where d.batchNo = e.
batchNo and d.fraudcodeid = 1)
order by a.nmfile, a.anumber

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00      13842      14541         15           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00      13842      14541         15           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 37  (DBA1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  SORT ORDER BY
      0   FILTER
      1    MERGE JOIN
      2     SORT JOIN
      1      NESTED LOOPS
      2       TABLE ACCESS FULL T_PROFILE_YN
      1       TABLE ACCESS BY INDEX ROWID T_CDR200
    201        INDEX RANGE SCAN (object id 9985)
      1     FILTER
      1      SORT JOIN
 633772       TABLE ACCESS FULL T_PROFILE_ANUMBER
      0    NESTED LOOPS
      0     TABLE ACCESS FULL T_PROFILE_YN
      0     INDEX UNIQUE SCAN (object id 9654)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   SORT (ORDER BY)
      0    FILTER
      1     MERGE JOIN
      2      SORT (JOIN)
      1       NESTED LOOPS
      2        TABLE ACCESS (FULL) OF 'T_PROFILE_YN'
      1        TABLE ACCESS (BY INDEX ROWID) OF 'T_CDR200'
    201         INDEX (RANGE SCAN) OF 'NMFILE_NDX' (NON-UNIQUE)
      1      FILTER
      1       SORT (JOIN)
 633772        TABLE ACCESS (FULL) OF 'T_PROFILE_ANUMBER'
      0     NESTED LOOPS
      0      TABLE ACCESS (FULL) OF 'T_PROFILE_YN'
      0      INDEX (UNIQUE SCAN) OF 'PK_FRAUDULLENT_YN' (UNIQUE)

#### 2. with index on nmfile

SELECT a.*
FROM t_cdr200 a, t_profile_anumber b, t_profile_yn c
WHERE
(SUBSTR(a.bnumber,1,1) != '0' AND  a.anumber = b.anumber
AND a.nmfile = c.batchNo AND c.batchID >= 1 AND c.batchID <= 1
AND a.durasi > (b.dcrphlocal+(0.01 * b.dcrphlocal)))
AND c.batchNo not in
(select d.batchNo from t_fraudullent_yn d, t_profile_yn e where d.batchNo = e.
batchNo and d.fraudcodeid = 1) order by a.nmfile, a.anumber

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      4      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00      27684      29082         30           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        8      0.00       0.00      27684      29082         30           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 37  (DBA1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  SORT ORDER BY
      0   FILTER
      1    MERGE JOIN
      2     SORT JOIN
      1      NESTED LOOPS
      2       TABLE ACCESS FULL T_PROFILE_YN
      1       TABLE ACCESS BY INDEX ROWID T_CDR200
    201        INDEX RANGE SCAN (object id 9985)
      1     FILTER
      1      SORT JOIN
 633772       TABLE ACCESS FULL T_PROFILE_ANUMBER
      0    NESTED LOOPS
      0     TABLE ACCESS FULL T_PROFILE_YN
      0     INDEX UNIQUE SCAN (object id 9654)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   SORT (ORDER BY)
      0    FILTER
      1     MERGE JOIN
      2      SORT (JOIN)
      1       NESTED LOOPS
      2        TABLE ACCESS (FULL) OF 'T_PROFILE_YN'
      1        TABLE ACCESS (BY INDEX ROWID) OF 'T_CDR200'
    201         INDEX (RANGE SCAN) OF 'NMFILE_NDX' (NON-UNIQUE)
      1      FILTER
      1       SORT (JOIN)
 633772        TABLE ACCESS (FULL) OF 'T_PROFILE_ANUMBER'
      0     NESTED LOOPS
      0      TABLE ACCESS (FULL) OF 'T_PROFILE_YN'
      0      INDEX (UNIQUE SCAN) OF 'PK_FRAUDULLENT_YN' (UNIQUE)

          Followup:

geez -- they both have an index on nmfile_ndx apparently -- they are the same
query.

did you know that indexes do not

o imply fast=true
o go faster then non-index paths
o assure you the answer in less time

I would suggest using the CBO as well here.

      Rating: 4
Sorry wrong
      attachment  October 28, 2002
Reviewer:
      Chris
Sorry Tom...I put wrong report attachment. here is the right one :

######### without index on nmfile

SELECT a.*
FROM t_cdr200 a, t_profile_anumber b, t_profile_yn c
WHERE
(SUBSTR(a.bnumber,1,1) != '0' AND  a.anumber = b.anumber
AND a.nmfile = c.batchNo AND c.batchID >= 1 AND c.batchID <= 1
AND a.durasi > (b.dcrphlocal+(0.01 * b.dcrphlocal)))
AND c.batchNo not in
(select d.batchNo from t_fraudullent_yn d, t_profile_yn e where d.batchNo = e.
batchNo and d.fraudcodeid = 1)
order by a.nmfile, a.anumber

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          3          1          6           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00      17680      14543        220           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00      17683      14544        226           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 37  (DBA1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  SORT ORDER BY
      0   FILTER
      1    NESTED LOOPS
      1     MERGE JOIN
 633773      SORT JOIN
 633772       TABLE ACCESS FULL T_PROFILE_ANUMBER
 633772      FILTER
      1       SORT JOIN
      1        TABLE ACCESS FULL T_CDR200
      0     TABLE ACCESS BY INDEX ROWID T_PROFILE_YN
      0      INDEX UNIQUE SCAN (object id 9652)
      0    NESTED LOOPS
      0     TABLE ACCESS FULL T_PROFILE_YN
      0     INDEX UNIQUE SCAN (object id 9654)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   SORT (ORDER BY)
      0    FILTER
      1     NESTED LOOPS
      1      MERGE JOIN
 633773       SORT (JOIN)
 633772        TABLE ACCESS (FULL) OF 'T_PROFILE_ANUMBER'
 633772       FILTER
      1        SORT (JOIN)
      1         TABLE ACCESS (FULL) OF 'T_CDR200'
      0      TABLE ACCESS (BY INDEX ROWID) OF 'T_PROFILE_YN'
      0       INDEX (UNIQUE SCAN) OF 'PK_BATCHNO' (UNIQUE)
      0     NESTED LOOPS
      0      TABLE ACCESS (FULL) OF 'T_PROFILE_YN'
      0      INDEX (UNIQUE SCAN) OF 'PK_FRAUDULLENT_YN' (UNIQUE)

********************************************************************************

######## with index on nmfile

SELECT a.*
FROM t_cdr200 a, t_profile_anumber b, t_profile_yn c
WHERE
(SUBSTR(a.bnumber,1,1) != '0' AND  a.anumber = b.anumber
AND a.nmfile = c.batchNo AND c.batchID >= 1 AND c.batchID <= 1
AND a.durasi > (b.dcrphlocal+(0.01 * b.dcrphlocal)))
AND c.batchNo not in
(select d.batchNo from t_fraudullent_yn d, t_profile_yn e where d.batchNo = e.
batchNo and d.fraudcodeid = 1)
order by a.nmfile, a.anumber

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          3          1          6           0
Execute      4      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00      32033      29084        235           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        8      0.00       0.00      32036      29085        241           0

Misses in library cache during parse: 2
Optimizer goal: CHOOSE
Parsing user id: 37  (DBA1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  SORT ORDER BY
      0   FILTER
      1    NESTED LOOPS
      1     MERGE JOIN
 633773      SORT JOIN
 633772       TABLE ACCESS FULL T_PROFILE_ANUMBER
 633772      FILTER
      1       SORT JOIN
      1        TABLE ACCESS FULL T_CDR200
      0     TABLE ACCESS BY INDEX ROWID T_PROFILE_YN
      0      INDEX UNIQUE SCAN (object id 9652)
      0    NESTED LOOPS
      0     TABLE ACCESS FULL T_PROFILE_YN
      0     INDEX UNIQUE SCAN (object id 9654)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   SORT (ORDER BY)
      0    FILTER
      1     MERGE JOIN
      1      SORT (JOIN)
 633773       NESTED LOOPS
 633772        TABLE ACCESS (FULL) OF 'T_PROFILE_YN'
 633772        TABLE ACCESS (BY INDEX ROWID) OF 'T_CDR200'
      1         INDEX (RANGE SCAN) OF 'NMFILE_NDX' (NON-UNIQUE)
      1      FILTER
      0       SORT (JOIN)
      0        TABLE ACCESS (FULL) OF 'T_PROFILE_ANUMBER'
      0     NESTED LOOPS
      0      TABLE ACCESS (FULL) OF 'T_PROFILE_YN'
      0      INDEX (UNIQUE SCAN) OF 'PK_FRAUDULLENT_YN' (UNIQUE)

********************************************************************************

          Followup:

the answer is simple:

the plans are different.
indexes do not mean "fast=true"
the RBO is index happy.
the CBO is what you should be using...

      Rating:
      4
Great  October 29, 2002
Reviewer:
      Chris
Thanks Tom for your explanation :)
I use optimizer hints all rows and it goes faster :)

      Rating: 5
Nested Loops
        June 27, 2003
Reviewer:  A Reader
       from USA
Hi Tom,

Simple Question. There is one query joining two tables
site => contact uses hash join. site has one index and its on on site_id,
contact doesn't have any index. It takes 7 minutes to finish.

SELECT Count(*) AS ForAvg , COUNT(*) AS Expr1
  FROM SITE SITE, CONTACT CONTACT
   WHERE  SITE.SITE_ID = CONTACT.SITE_ID
     and CONTACT.MAIL_CONT In ('1', ' ')
     AND SITE.MAIL_SITE = '1'
     AND SITE.DSF_INDEX In ('1', '2')
     AND SITE.COUNTRY_CD = 'US'
     AND SITE.SS_GROUP = 'EDU'
     AND SITE.SS_SUBGRP =  'ELE'
     AND CONTACT.LIST_PRI = '191'

SQL> set autotrace traceonly
SQL> @qry3.test

1 row selected.

Elapsed: 00:07:09.33

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=28695 Card=1 Bytes=3
          9)

   1    0   SORT (AGGREGATE)
   2    1     HASH JOIN (Cost=28695 Card=243 Bytes=9477)
   3    2       TABLE ACCESS (FULL) OF 'SITE' (Cost=15205 Card=241 Byt
          es=5543)

   4    2       TABLE ACCESS (FULL) OF 'CONTACT' (Cost=13490 Card=1013
          3 Bytes=162128)

Statistics
----------------------------------------------------------
          0  recursive calls
       1998  db block gets
     188388  consistent gets
     187326  physical reads
        120  redo size
        443  bytes sent via SQL*Net to client
        503  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL>

If another condition ("SITE.LST_ORD_SS Between '20020101' AND '20021231'" ) is
added, the optimzer uses NESTED LOOPS join and it takes over 2 hrs to complete.

SQL> @qry3.test

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=28695 Card=1 Bytes=4
          7)

   1    0   SORT (AGGREGATE)
   2    1     NESTED LOOPS (Cost=28695 Card=1 Bytes=47)
   3    2       TABLE ACCESS (FULL) OF 'SITE' (Cost=15205 Card=1 Bytes
          =31)

   4    2       TABLE ACCESS (FULL) OF 'CONTACT' (Cost=13490 Card=1013
          3 Bytes=162128)

Both the joining columns are not null and SITE.SITE_ID has an index on it.

I would appreciate any comment on this.

Thanks,

          Followup:

It believes that that predicate will return 1 row (see the CARD)

how many rows from SITE does that actually return?  If the optimizer is so far
off -- why is it so far off?  are the stats current?  or really stale?

      Rating: 5
Nested Loops
        June 27, 2003
Reviewer:  A Reader
       from USA
Hi Tom,

Simple Question. There is one query joining two tables
site => contact uses hash join. site has one index and its on on site_id,
contact doesn't have any index. It takes 7 minutes to finish.

SELECT Count(*) AS ForAvg , COUNT(*) AS Expr1
  FROM SITE SITE, CONTACT CONTACT
   WHERE  SITE.SITE_ID = CONTACT.SITE_ID
     and CONTACT.MAIL_CONT In ('1', ' ')
     AND SITE.MAIL_SITE = '1'
     AND SITE.DSF_INDEX In ('1', '2')
     AND SITE.COUNTRY_CD = 'US'
     AND SITE.SS_GROUP = 'EDU'
     AND SITE.SS_SUBGRP =  'ELE'
     AND CONTACT.LIST_PRI = '191'

SQL> set autotrace traceonly
SQL> @qry3.test

1 row selected.

Elapsed: 00:07:09.33

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=28695 Card=1 Bytes=3
          9)

   1    0   SORT (AGGREGATE)
   2    1     HASH JOIN (Cost=28695 Card=243 Bytes=9477)
   3    2       TABLE ACCESS (FULL) OF 'SITE' (Cost=15205 Card=241 Byt
          es=5543)

   4    2       TABLE ACCESS (FULL) OF 'CONTACT' (Cost=13490 Card=1013
          3 Bytes=162128)

Statistics
----------------------------------------------------------
          0  recursive calls
       1998  db block gets
     188388  consistent gets
     187326  physical reads
        120  redo size
        443  bytes sent via SQL*Net to client
        503  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL>

If another condition ("SITE.LST_ORD_SS Between '20020101' AND '20021231'" ) is
added, the optimzer uses NESTED LOOPS join and it takes over 2 hrs to complete.

SQL> @qry3.test

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=28695 Card=1 Bytes=4
          7)

   1    0   SORT (AGGREGATE)
   2    1     NESTED LOOPS (Cost=28695 Card=1 Bytes=47)
   3    2       TABLE ACCESS (FULL) OF 'SITE' (Cost=15205 Card=1 Bytes
          =31)

   4    2       TABLE ACCESS (FULL) OF 'CONTACT' (Cost=13490 Card=1013
          3 Bytes=162128)

Both the joining columns are not null and SITE.SITE_ID has an index on it.

I would appreciate any comment on this.

Thanks,

      Rating:
      5
  June 30, 2003
Reviewer:  A reader
       from USA
Hi Tom, Thanks for you reply.

The site table returned 765 rows. I have gathered the stats of site again,
though they weren't stale.  I still get the same plan for the query. What's am I
missing ?.

SQL> @qry3.test

    FORAVG      EXPR1
---------- ----------
       765        765

1 row selected.

Elapsed: 3:42:48.09

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=28695 Card=1 Bytes=4
          7)

   1    0   SORT (AGGREGATE)
   2    1     NESTED LOOPS (Cost=28695 Card=1 Bytes=47)
   3    2       TABLE ACCESS (FULL) OF 'SITE' (Cost=15205 Card=1 Bytes
          =31)

   4    2       TABLE ACCESS (FULL) OF 'CONTACT' (Cost=13490 Card=1013
          3 Bytes=162128)

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

SQL>

I would appreciate any comments.

Thanks,

          Followup:

how do you GATHER the stats, exact command.

what does

SELECT Count(*) AS ForAvg , COUNT(*) AS Expr1
  FROM SITE SITE
   WHERE SITE.MAIL_SITE = '1'
     AND SITE.DSF_INDEX In ('1', '2')
     AND SITE.COUNTRY_CD = 'US'
     AND SITE.SS_GROUP = 'EDU'
     AND SITE.SS_SUBGRP =  'ELE'
     and SITE.LST_ORD_SS Between '20020101' AND '20021231'
/

      Rating:
      5
  June 30, 2003
Reviewer:  A Reader
       from USA
Hi Tom, Thanks for your reply.

I am analyzing SITE using "analyze table site compute statistics"

Optimizer choses nested loops join for most of the queries if
statistics are gathered using

"execute dbms_stats.gather_table_stats('SCOTT','SITE') "

Thanks,

          Followup:

so, what is the output of the query from right above.

      Rating:
      5
  July 01, 2003
Reviewer:  A Reader
       from USA
Thanks Tom.

Here is the output of the query :

SQL> @qry4.test

    FORAVG      EXPR1
---------- ----------
      1127       1127

Elapsed: 00:01:48.09

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=15205 Card=1 Bytes=1
          9)

   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'SITE' (Cost=15205 Card=1 Bytes=1
          9)

Statistics
----------------------------------------------------------
          0  recursive calls
       1097  db block gets
      99838  consistent gets
      99956  physical reads
         60  redo size
        443  bytes sent via SQL*Net to client
        503  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL>

Thanks,

          Followup:

are you sure you use 'analyze table site compute statistics' as that should get
histograms (do you have rows in user_tab_histograms for this table?)

Obviously, the statistics are leading the optimizer to the wrong conclusion.  It
firmly believes "one row" -- and is getting 1,127.

      Rating:
      5
  July 01, 2003
Reviewer:  A Reader
       from USA
Thanks Tom,

I have confirmed that site has had
"analyze table site compute statistics", also,  user_Tab_histograms do have
information of site.

I would appreciate any comments on this.

Thanks,

          Followup:

I would file it as a bug with support.

That the cardinality is being computed so wrong is not good.  that is the cause
of the bad plan.

given that the table appears to be a gig in size, I'm not going to ask you to
email it to me or anything like that ;)

      Rating:
      5
  July 02, 2003
Reviewer:  A Reader
       from USA
Thanks Tom for helping me understand the problem.  This site has helped me a lot
for lot of other issues also.

Just want to ask one more question regarding the problem.

Are there any other factors(init parameters, db_block_size etc.)  which can
affect analyze to gather wrong stats or optimzer to make wrong plans ?.

I would apppreciate any comment on this.

Thanks,

          Followup:

there are lots of parameters -- search this site for 10053

then, enable that event, explain a query.

read the resulting trace file.  You'll see all of the parameters that affect the
CBO (in my book due out in august, i go through many of them with examples of
the effects they have)

July 03, 2003 ilker -- Thanks for the question regarding
      "exporting packages,function etc. from one user to another.", version
8.1.6

      You Asked
Hi,

For example X user have many packages,functions,procedures etc.
And I want to delete some of them after copying to another user
(Y).
I mean I want to classify packages,functions etc... I can
copy-paste by using Procedure Builder. But this way is not
efficient because of the many packages, procedures etc.

can I do this by using more easy way...

best regards

      and we said...
One way would be to

exp userid=old_user/pw owner=old_user rows=n

imp userid=new_user/pw fromuser=old_user touser=new_user

That would do a structural copy of the old_user to the new_user
but not pull any data over.  Now you can drop anything you don't
want.

I also use a "getcode" routine myself.  getcode looks like:

---------------- getcode.sql -------------------------------
set feedback off
set heading off
set termout off
set linesize 1000
set trimspool on
set verify off
spool &1..sql
prompt set define off
select decode( type||'-'||to_char(line,'fm99999'),
               'PACKAGE BODY-1', '/'||chr(10),
                null) ||
       decode(line,1,'create or replace ', '' ) ||
       text text
  from user_source
 where name = upper('&&1')
 order by type, line;
prompt /
prompt set define on
spool off
set feedback on
set heading on
set termout on
set linesize 100
-----------------------------------------------------

That gets a single package/procedure/function and saves it to
the current working directory.  When I want ALL of the code, I
use:

------------------- getallcode.sql -------------------------
set termout off
set heading off
set feedback off
set linesize 50
spool xtmpx.sql
select '@getcode ' || object_name
from user_objects
where object_type in ( 'PROCEDURE', 'FUNCTION', 'PACKAGE' )
/
spool off
spool getallcode_INSTALL
select '@' || object_name
from user_objects
where object_type in ( 'PROCEDURE', 'FUNCTION', 'PACKAGE' )
/
spool off
set heading on
set feedback on
set linesize 130
set termout on
@xtmpx.sql
----------------------------------------------

That gets ALL of the packages/etc from the current user, spools
each one to a file and creates a getallcode_INSTALL script that
will rerun the code in another schema for you.
Review &
      Followup

      Rating: 5
Great!!
        February 11, 2002
Reviewer:  Yogeeraj
       from Mauritius
Thanks very much
saved me much time not having to re-write the same script ;)

I did only a very minor change:
         spool getallcode_INSTALL
to:
         spool getallcode_INSTALL.sql

Best Regards
Yogeeraj
PS. i was wondering there is a similar script that can help me do the same for
tables n views. I know there are some tools offering the similar facility but...
if you want to automate things they don't help much..

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:683824732911
for getview, getallviews.

For tables -- I simply use EXP tables=(.....) rows=n and then IMP full=yes
INDEXFILE=tables.sql

tables.sql will have all of the create tables in it.

      Rating: 3
Look into
      user.dba_source  February 11, 2002
Reviewer:
      Randy  from IL,CHicago
If your stored procedures are not wrapped than may be you are better of by
selecting the source code from _dba_source and compiling the output under the
new user account.

          Followup:

what does wrapping have to do with anything?

and why use dba_source (and how would wrapped code change what view you used)
when user_source (a view to which we all have access) has everything you need???

      Rating: 5
Export
      Procedures only  May 23, 2002
Reviewer:
      Munz  from Reston, USA
Tom:

1.  I did
exp userid=old_user/pw owner=old_user rows=n

imp userid=new_user/pw fromuser=old_user touser=new_user

and it exported data.  Any ideas why?

2.  I did it twice in two weeks.  The 2nd time it told me that objects already
exist and did not overwrite them.

How would you export procedures, functions, and packages only (code only) and
overwrite the old ones.

Thank you,

          Followup:

1) no it didn't.  It exported data STRUCTURES, not the actual data.

2) imp will not overwrite objects.

In answer to "how would you ...." -- I would re-read the answer above and use
getallcode.sql.

      Rating: 4
Good
      work  July 02, 2003
Reviewer:  surinder
       from India
Tom,
    i lost all the scripts to create the table structure, procedures, triggers
and now i need to install this same application on other location which also
requires the database creation. so now i need to export the table structures
without rows, packages and function by the method you told but how we export the
triggers and will it work on other database if they have the same language.

does any other method to create the same database structure over there

          Followup:

just exp with rows=n owner=schema_you_lost

the dmp file will have your entire schema.  just import that and there you go.

If you have 9i, you can use dbms_metadata to get your schema back.

If you have my book "Expert One On One Oracle" -- in there are scripts that get
it back in 8i -- part of it from the dmp file, part of it from the data
dictionary.

      Rating:
      4
thanks  July 03, 2003
Reviewer:
      surinder  from India
Tom
              Thanks for your valuable time.

I am using Oracle 8.1.7  on Linux7.2. Actually my question was that i need to
generate the whole database structure through export and import it to our
another location without data in tables. I read your another solution which was
given to another DBA. By that solution what i got now is that we can export the
whole database with

       Rows =N
       Triggers = y
       constraint = y

and it will export the whole structure of the database with packages,
procedures,table constraints,table structures, views,Synonyms and triggers
without any table data. And at our new location I will create a database only
with system tablespace and import the export file. It will automatically create
all the tablespaces and datafiles.

     Please rectify me if i am wrong
......

But now i have  doubts

1)if i give Index =y
than will those indexes will be recreated on the tables without rows.

2) If i have a different directory structure over there can i open the export
file in vi editor and change the file locations.

3)Can i export the database from os Linux 7.2 with oracle 8.1.7 and import it on
Linux 8 with oracle 8.1.7 or Linux 9.

          Followup:

it'll do what you say, yes.  make sure the mount points are the same.

1) yes
2) no, it is a binary file.  use symbolic links if you need to trick it out.
3) exports are cross system compatible, yes.

July 03, 2003 Sharvil -- Thanks for the question regarding
      "ORA-01401: Inserted value too large for column", version 8.1.7

      You Asked
Hi Tom,
Thank you so much for what you are doing. Here's an Oracle message that is
familiar to many of us developers.
"ORA-01401: Inserted value too large for column"
It doesn't tell you which column is too large, it leaves it to us to figure out
which column is causing problem. While inconvenient it is not too bad if you are
trying to insert into a table with few columns in it. However, last time I
recevied this error message, a procedure was trying to insert values into table
with 220 columns! :-) That's when I realized it's time to ask you. Is there an
easier way to find out which columns is causing the problem?

At work somoene told me that if this same thing happened in MS ACCESS, it would
tell you the column that's causing the problem. Now I know Oracle is the
grandfather of the databases, and so there should be nothing that MS Access (out
of all the databases on god's great earth) can but Oracle can't do. So I am
hoping there is some way you can show us how we can identify the value that's
too large, or the column name in which we are trying to insert. Thank you so
much!

      and we said...
wouldn't it be cool if we were just like access.  wow, I cannot wait for our
entire feature set to catch up!  (seriously, this is an enhancement I too would
like to see).

Here is an idea for you:

scott@ORA920> create or replace procedure gen_crud( p_tname in varchar2 )
  2  authid current_user
  3  as
  4    l_stmt  long;
  5    l_plist long;
  6    l_assign long;
  7    l_ins1  long;
  8    l_ins2  long;
  9    l_upd   long;
 10  begin
 11      for x in
 12      ( select column_name,
 13               decode(column_id,1,'',','||chr(10)||chr(9) ) sep,
 14              column_id,
 15              decode( data_type,
 16                      'DATE', 'to_date(p_' || column_name ||
 17                                   ', ''dd-mon-yyyy hh24:mi:ss'' )',
 18                      'p_' || column_name ) fixed_cname
 19         from user_tab_columns
 20        where table_name = upper(p_tname)
 21        order by column_id )
 22      loop
 23          l_plist := l_plist || x.sep || 'p_' || x.column_name ||
 24                     ' in varchar2 default NULL';
 25          l_assign := l_assign || chr(9) || 'begin l_rec.' ||
 26                      x.column_name || ' := ' || x.fixed_cname ||
 27                      '; exception when others then ' ||
 28                      'raise_application_error( -20000-' || x.column_id ||
 29                      ', ''' || x.column_name || ':'' || sqlerrm ); end;' ||
 30                      chr(10);
 31          l_ins1 := l_ins1 || x.sep || x.column_name;
 32          l_ins2 := l_ins2 || x.sep || 'l_rec.' || x.column_name;
 33          l_upd  := l_upd || x.sep || x.column_name || ' = l_rec.' ||
 34                    x.column_name;
 35      end loop;
 36
 37      l_stmt := '
 38  create or replace package ' || p_tname|| '_crud
 39  as
 40     procedure do_insert( ' || l_plist || ');
 41     procedure do_update( p_rowid in rowid, ' || l_plist || ');
 42  end;';
 43
 44      execute immediate l_stmt;
 45
 46      l_stmt := '
 47  create or replace package body ' || p_tname || '_crud
 48  as
 49
 50  procedure do_insert( ' || l_plist || ')
 51  as
 52      l_rec ' || p_tname || '%rowtype;
 53  begin
 54  ' || l_assign || '
 55  insert into ' || P_tname || '(' || l_ins1 || ') values (' || l_ins2 || ');
 56  end;
 57
 58  procedure do_update( p_rowid in rowid, ' || l_plist || ' )
 59  as
 60     l_rec ' || p_tname || '%rowtype;
 61  begin
 62  ' || l_assign || '
 63  update ' || p_tname || ' set ' || l_upd || ' where rowid = p_rowid;
 64  end;
 65
 66  end ' || p_tname || '_crud;';
 67
 68      execute immediate l_stmt;
 69  end;
 70  /

Procedure created.

so, now you have a utility routine (not fully baked, I just whipped this
up).

scott@ORA920> drop table t;
Table dropped.

scott@ORA920> create table t ( x varchar2(10), y date, z number );
Table created.

scott@ORA920> exec gen_crud( 't' )

PL/SQL procedure successfully completed.

scott@ORA920> @getcode t

scott@ORA920> exec t_crud.do_insert( 'helloworld', '01-jan-2003', 55 );
PL/SQL procedure successfully completed.

scott@ORA920> exec t_crud.do_insert( 'helloworld', '01-jan-2003 11:02:44', 55 );
PL/SQL procedure successfully completed.

scott@ORA920> exec t_crud.do_insert( 'hello world', '01-jan-2003 11:02:44', 55
);
BEGIN t_crud.do_insert( 'hello world', '01-jan-2003 11:02:44', 55 ); END;

*
ERROR at line 1:
ORA-20001: X:ORA-06502: PL/SQL: numeric or value error: character string buffer
too small
ORA-06512: at "SCOTT.T_CRUD", line 10
ORA-06512: at line 1

scott@ORA920> alter session set nls_date_format = 'dd-mon-yyyy hh24:mi:ss';

Session altered.

scott@ORA920> select * from t;

X          Y                             Z
---------- -------------------- ----------
helloworld 01-jan-2003 00:00:00         55
helloworld 01-jan-2003 11:02:44         55

2 rows selected.

all that package did was create a nice package:

create or replace package t_crud
as
   procedure do_insert( p_X in varchar2 default NULL,
    p_Y in varchar2 default NULL,
    p_Z in varchar2 default NULL);
   procedure do_update( p_rowid in rowid, p_X in varchar2 default NULL,
    p_Y in varchar2 default NULL,
    p_Z in varchar2 default NULL);
end;
/
create or replace package body t_crud

as

procedure do_insert( p_X in varchar2 default NULL,
    p_Y in varchar2 default NULL,
    p_Z in varchar2 default NULL)
as
    l_rec t%rowtype;
begin
    begin l_rec.X := p_X; exception when others then raise_application_error(
-20000-1, 'X:' || sqlerrm ); end;
    begin l_rec.Y := to_date(p_Y, 'dd-mon-yyyy hh24:mi:ss' ); exception when
others then raise_application_error( -20000-2, 'Y:' || sqlerrm ); end;
    begin l_rec.Z := p_Z; exception when others then raise_application_error(
-20000-3, 'Z:' || sqlerrm ); end;

insert into t(X,
    Y,
    Z) values (l_rec.X,
    l_rec.Y,
    l_rec.Z);
end;
..........

that does the insert but also "validates" the data.  Nice thing about this is
-- you can put extra auditing, whatever in this package and use it instead of
insert update or delete...

Review &
      Followup

      Rating: 4
Here's another
      option  January 05, 2003
Reviewer:  eric
      givler  from harrisburg, pa
Login to db and find your session.  Turn on server side tracing for that session
(with binds), run the code, turn off the tracing, and examine the results.  This
concept is described in a Metalink Note.

      Rating:
      3
Pro*c  July 03, 2003
Reviewer:
      rajkumar  from india
Hi tom,
        how to debug in pro*C.

          Followup:

in a debugger?  it is just c code.

July 03, 2003 Kelly -- Thanks for the question regarding
      "migrate sql server to oracle ", version 8.17

      You Asked
Hello Tom:

I have to migrate sql server database to oracle . Would you please tell me which
way is the best to do it?

Thanks

Kelly

      and we said...
see

http://otn.oracle.com/software/tech/migration/workbench/content.html
....
Review
      & Followup

      Rating: 5
Has anyone used
      Migration Workbench?  July 03, 2003
Reviewer:
      Away with SQL Server
Tom - Is Mig Workbench the only way to get RI from SS to Oracle?

Has anyone had good results going from SQL Server to Oracle?

I find the tool a bit clumsy, and *slow*, as compared to Generic Connectivity
CTAS. (Of course, RI doesn't come with CTAS)
There are lots of little manual GUI menu items you must go through just to get
it to start working.

Maybe it's me... Anyone having success?

          Followup:

there are lots of ways

first, you would have your create table scripts from SS right?  well, they are
90% of the way there -- massive global changes of "go" to "/" and adding a
couple of semi's

there are other tools like ERWin and such that reverse engineer "source"
databases and pump out ddl for "target"

      Rating:
      5
  July 03, 2003
Reviewer:  Away with SQL
      Server
True, true.. I was thinking of automating it though, with scripts.

The only way to accomplish it that I know of is SS SQL to generate Oracle DDL.
Does this seem viable?
The reason for scripting it would be you could begin to cookie cutter
conversions.

Also, we have lots of CHAR columns in our SS database.
Do you say "use VARCHAR2 all the time"?
I know for things like state code or something like that CHAR seems ok, but I
would think we should change all our CHARs to VARCHAR2s.

Your thoughts?
Thanks much for your time

          Followup:

varchar2 100% of the time without exception.

a char is nothing more then a varchar2 that is blank padded out.

July 03, 2003 Andre -- Thanks for the question regarding
      "Cursor sharing, bind variables, histograms and data warehousing", version
      8.1.6

      You Asked
I was reading about cursor sharing in Oracle8i, and Oracle
recommends that -- in a Data Warehousing environment -- we avoid
using bind variables, because the CBO may make a wrong
assumption when deciding on the selectivity of a column.

Questions:

1) When the statement is issued and it finds an a already parsed
version in the shared pool -- may the excution plan be
re-generated depending on the values of the bind variables and
the literals in a where clause (for example, when based on a
histogram an index search occurs instead of a FTS) ? Or would
the statement have to be entirely reparsed to generate a new
execution plan ? Would you please explain what happens in fact ?

2) What is the difference between a "reparse", a "soft parse"
and a "hard parse" in this context ?

3) In what step are the bind variables replaced with their
assigned values ? Wouldn't a statement with assigned values to
its bind variables have the same effect as literals, since in
both cases there are values on which the CBO could be based to
used a histogram to make the most appropriate execution plan ?
Or does using bind variables makes histograms not be used by
definition ?

4) Are histograms generated by all ANALYZE options, or only by
the FOR COLUMNS clause ?

5) Does DBMS_STATS have an option for genarating histograms ?

6) Does DBMS_STATS use the same data dictionary tables and
columns to store the results as the ANALYZE command ?

Thanks !

      and we said...
I would like to clarify the "generalization" that you should not
use bind variables in a data warehouse.  I'll try to be very
exact.  I wish they hadn't printed that "rule of thumb".

You should avoid a bind variable in the very unique situation
where you have

o a column that you have generated histograms for via the
analyze command AND

o it is known by you that the values in the column are very
skewed AND

o people frequently query on that column AND

o you desire different plans to be generate for different
values.

For example, the column in question contains the numbers 1 ..
100.  0.01% of the rows have a value of 1, 0.01% have a value of
2 (and so up upto 99).  99.01% of the rows have a value of 100
in this column.  In this very special case, you would like the
optimizer to use an index if the predicate contains "where
that_column = some_number" and some_number is between 1 and 99.
You DO NOT want the optimizer to use an index if some_number is
100 as scanning the index would be painfully slow in this case.
If you just submit a query:

   select * from that_table where some_column = :x

the optimizer has to guess and would either generate a plan that
used an index or not -- making this plan optimial only for
queries with x between 1 and 99 OR x = 100 but not both.  In
this VERY special case, you would attempt to avoid the use of
bind variables.

Short of this very very special case, you want to use bind
variables.  This very very special case is not limited to a data
warehouse -- it can happen in any database but it might be more
common to a data warehouse.  Also -- if your data warehouse runs
tons of queries -- you might consider using bind variables even
in this case for the queries in the range x between 1 and 99 and
having the special case "100" be different to avoid chewing up
space in your shared pool.

Now, to answer the questions...

1) no, the whole concept of the shared pool is to avoid the
regeneration of query plans -- we "shared" them, many people are
concurrently executing them.  If we changed the plan based on
the bind variables -- we could not be sharing a single image of
the plan, we would each need our own.

You need an entirely new statement (or different environment
with the same statement, like if you submit the query from a
session with optimizer goal set to all_rows and someone else
submits it with the optimizer goal set to first_rows, you might
get 2 copies of the statement in the shared pool with different
plans).

2) a hard parse is a parse of a query that was not found in the
shared pool.  We have to parse, qualify (figure out the emp
means scott.emp), authorize (can you select from scott.emp), and
finally optimize.

a soft parse is when we find the query in the shared pool.  We
hash the sql statement, search by the hash, verify the query
applies to us and reuse it.  We skip many of the steps above as
they have already been done for us.

a reparse I guess is when you parse the same statement over and
over.  It is wasteful in a single program and could be avoided.
For example, instead of coding:

  for i in 1 .. 10 loop
    execute immediate 'insert into t values ( :x)' using i;
  end loop;

you could use dbms_sql to:

   open_cursor
   parse the insert
   for i in 1 .. 10 loop
       bind i
       execute the statement
   end loop
   close the cursor

the first loop would have upto 10 soft parses.  The second has
1.

3) at the bind step.  when we goto execute.  It happens AFTER
optimization.  Histograms can be used but their value is limited
with bind variables.

4) only when you ask for it with the for columns.

5) see the method_opt parameter to the GATHER_* routines.  You
can specify this yes.

6) dbms_stats can be used to populate the data dictionary tables
used by the CBO or it can populate tables outside of the data
dictionary (to be transfered into the data dictionary later)
Review &
      Followup

      Rating: 5
one question on
      histograms  July 02, 2003
Reviewer:
      A reader  from ca
I understand your lucid explanation for the case
against using bind variables. Could you please explain
what you meant by the following statement?
"Histograms can be used but their value is limited with bind variables."

Thanx!

          Followup:

when using bind variables:

select * from t where x = :y;

the optimizer will use bind variable "peeking" in 9i the first time the query is
hard parsed (it'll peek at the binds and optimize the query as if the literal
value was there).  The plan you get depends on the bind variable first used --
the histograms won't be used afterwards.  they are of "limited use".

in 8i, it'll use "heuristics" to make assumptions about how much data would be
returned.  the histograms are of very limited use here -- since the optimizer
makes no assumption as to the value of :y

if you use

select * from t where x = 1;
            ....where x = 2;
 and so on...

then histograms come into play all of the time.

      Rating: 4
On the Topic of
      Bind Variables  July 03, 2003
Reviewer:
      Brian
On the topic of when to use them:

I have read through your book, and I understand that I should use bind
variables.  But I'm not clear about two things:

1) What the syntax is exactly?  I realize that in sql*plus
   you can type in variable x number, and it's a bind
   variable. Then, the x shows up as a :x.  But what if
   you're writing a procedure?  How do you bind the
   parameters that will later be used in a where
   statement?  Or are they already bound?  And that leads
   me to:

2) When do bind variables already exist implicitly (without
   declaration or special syntax)?

I find the Oracle documentation around bind variables to not be very clear on
the above.

Thanks

          Followup:

1) that is the pure and sheer beauty of plsql -- all variables in SQL in PLSQL
are in fact binds:

create procedure foo( p_input in varchar2 )
is
begin
   for x in ( select * from t where column = p_input )
   loop
     ....

p_input *is* a bind...

2) correct -- PLSQL is so tightly coupled with SQL that the distinction blurs.

July 03, 2003 STANLEY -- Thanks for the question regarding
      "oerr ", version 8.0.5

      You Asked
hi tom,
is there a utility like oerr of Unix
in NT?

thanks for the reply.

      and we said...
No there is not.

You can do this in sqlplus:

tkyte@TKYTE816> exec dbms_output.put_line( sqlerrm( -1422 ) );
ORA-01422: exact fetch returns more than requested number of
rows

Review &
      Followup

      Rating: 5
Simple
      answer  March 12, 2001
Reviewer:  Steve
      Pilgrim  from UK
I always forget this one and coming from unix it's a pain not to have "oerr".

      Rating:
      3
  July 02, 2003
Reviewer:  Tim

But is there a way to get the Cause & Action ???

Thanks !

          Followup:

thats in the documentation or if you get an oraus.msg file from unixland, you
can load it into a table easy enough

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6605099507222
for example

      Rating: 5
How about
      Jonathan Lewis method  July 03, 2003
Reviewer:
      Ashiq Shamsudeen  from Trichy, Tamil Nadu, India
Hi Tom,

Which method is good(in the sense ease of use ;) ) either JL's Or its just do
your in way to get the error ??

see JL's method

http://www.jlcomp.demon.co.uk/oerr.html
thanx
A.Ashiq

          Followup:

they are functionally equivalent.  they do pretty much exactly the same thing.

      Rating: 5
How about this
      one ??  July 03, 2003
Reviewer:  Ashiq
      Shamsudeen  from Trichy, Tamil Nadu, India
Hi tom,

 What's your comment on this method .I feel its more like Unix oerr ,isn't it?
And its give the same output like what we get from unix.

See

http://www.stormloader.com/yonghuang/freeware/Windowsoerr.html
thanx
A.Ashiq

          Followup:

that is very neat -- fortunately, I'm not exposed to windows personally so I
don't need it -- but it is very neat.

IMHO -- if you have access to a unix server, and can grab the latest oraus.msg
file, using the sqlldr trick to load it up so you can "oerr" in sqlplus is going
to be easier if you don't have all of the unix'isms that trick needs installed
already (like perl)

But very cool

      Rating: 5
How about this
      one ??  July 03, 2003
Reviewer:  Ashiq
      Shamsudeen  from Trichy, Tamil Nadu, India
Hi tom,

 What's your comment on this method .I feel its more like Unix oerr ,isn't it?
And its give the same output like what we get from unix.

 Do you recommend to go ahead on this method or simply your method ??

See

http://www.stormloader.com/yonghuang/freeware/Windowsoerr.html
thanx
A.Ashiq

July 03, 2003 Albert -- Thanks for the question regarding "on
      NT/2000 use ocopy or copy with hot backup", version 8.1.7

      You Asked
Hello Tom,

I'am a bit puzzeled by the following:
We have a Win2K Server +sp2 using 8i 8.1.7.4.1 Standard Edition. The database
runs in archive mode. We use the "traditional" hot backup method by placing the
tablespaces in backup mode (begin backup), followed by a copy of the files to a
spare disk, followed by the "alter tablespace .. end backup" command.
So we just use the Win2000 "copy" to copy files. Should we actually use the
oracle "ocopy" tool instead ?

Could you please explain ?

Thanks in advance !

      and we said...
if you do not use ocopy and we have the file opened -- your COPY will fail.

You should use ocopy -- you might find that some of your copys haven't worked in
the past!  (windows "file sharing" you know, the error "sorry cannot read file"
error -- ocopy opens the file in a manner that works around that)
Review &
      Followup

      Rating: 3
hot backup
      using copy or ocopy?  October 09, 2002
Reviewer:
      John Young  from P.R. China
Hi Tom,

Thank you for all you have done here.

my hot backup using w2k's copy,it does work!

          Followup:

then you have been lucky.

DBWR just didn't happen to be writing to the files at that point in time.  You
will get unlucky someday.

      Rating: 5
Yeah
      thanks  October 09, 2002
Reviewer:
      A reader
Thanks a lot, did not knew this, so big improvement for me here.

      Rating: 3
any difference
      in speed between copy and ocopy?  July 03, 2003

Reviewer:  Pat
I set up hot backup for my tablespaces and found that about 60g of datafiles
took about only 38 minutes using OS ocopy cmd (from records in alert log)-
consistently. This is substantially less time than a cold backup with OS copy
commands (in my system it is at least 1 minute for 1g of data. both write to
local disks). I did not have a chance to test the hot backups yet, so I'd like
to know if this sounds right? If so why ocopy would take less time than copy?

Thanks.

          Followup:

copy is the worst of the worst programs to use.  it uses 64k at a time. It is a
really old DOS program.

xcopy is significantly faster, it uses gobs of ram -- like ocopy would.  so try
that if you like.

(actually -- try rman -- nothing easier then "backup database")

July 03, 2003 Bhavesh -- Thanks for the question regarding
      "Redirect Url without showing Address in the address bar", version
      1.0.2.2.2

      You Asked
Hi Tom,
     Hope you must have had a nice vacations. I am having a bit of doubt. In one
of my PL/SQL page (PSP) I am redirecting the url to some pages based on
conditions. Say
if condition then
  owa_util.redirect_url('
http://something.com/a.htm',
'TRUE')
else
  owa_util.redirect_url('
http://something.com/b.htm',
'TRUE')
end if;

Things are working fine but the problem is that the address of the url gets
appeared in the address bar. How can I hide this address from the user. I do not
want to show the user which page they have been redirected to.
Thanking you in advance.
Best Regards

Bhavesh

      and we said...
That is the way URL redirection works!

You can "hide" the underlying URL by returning a frameset with one frame - the
source of which is your something.com page.

Something like:

<frameset COLS="*" border=0>
<frame SRC="
http://www.yahoo.com/
frameborder=0>
</frameset>

instead of url redirection.
Review &
      Followup

      Rating: 5
Thanks a
      lot  August 06, 2002
Reviewer:  Bhavesh

Hi Tom,
   As usual, you are the best. I think I have started becoming lazy in cracking
the nuts as most of the problems are already there on your forum answered by I
believe the best. Thanks a million.
Regards

Bhavesh

      Rating: 5
redirecting
      URL  July 02, 2003
Reviewer:  mo
TOm:

I am using this owa_util.redirect_url in a "save_shipment" procedure to display
the save results in another page but all I get a the name of redirect in the
save proceduee. It does not change the url and run the other one to display the
results. do you know what is happening.

--Procedure Save_shipment
.....

owa_util.redirect_url('save_new_shipment_display?i_shipment_id='||v_shipment_id||
'&i_user_id='||i_user_id);
---

This is what I get in the results page which is still pointing to the
save_shipment link.

Location: save_new_shipment_display?i_shipment_id=2183&i_user_id=john

          Followup:

you have other htp.p calls before the owa_util.redirect_url.

that must be the first owa type call (htp, etc).

The location tag must be in the header.

      Rating:
      5
redirect  July 02, 2003
Reviewer:
      mo
Tom:

yes i do have some htp statements for <html> and Header and son on but i did not
have the redirect in the <head> if that what u mean.

I also used the other code you have instead and it worked fine.
 htp.p( '<HTML><HEAD>
            <META HTTP-EQUIV="Refresh"
            CONTENT="0;URL=' || p_url || '"></HEAD><body>
            <a href="' || p_url || '">Redirecting...</a>
            </body></html>' );

Is there any difference?

          Followup:

yes there is differences.

all you needed to do was use the owa-util call and NO OTHER HTP calls (they are
meaningless no -- you do not want the user to see that page anyway -- if you use
owa_util.redirect_url and htp, I would ask you to ask yourself "why")

      Rating:
      5
redirect  July 03, 2003
Reviewer:
      mo
Tom:

Thanks I will change it to use the owa_util.redirect but:

1. What is the disadvantage of using the <META> tag like above instead of
owa_util.redirect.

2.  When do you use this "utl_http.request_pieces".  I am confused on what is
the use of each.

Thank you,

          Followup:

1) do you like having your user stare for a second or two at a screen that says
"hold on, i'm doing what you asked" -- or would it be nice for it just to look
seamless.

the meta redirect "flashes" the screen, doesn't look nice.

2) when the page exceeds the size of a plsql variable pops into mind.  if you
try to get a 500k page with utl_http, you'll find you better get it by piece
since 32k is the biggest variable.

July 04, 2003 wallacel -- Thanks for the question regarding
      "national character set", version 8.1.6

      You Asked
Tom:

Through which view I can know which character set and national
character set my server is using?

one method I know is "alter database backup controlfile to
trace"
and then check trace file.
any one else?

Thanks

      and we said...
sys@8i> select * from nls_database_parameters;

PARAMETER                      VALUE
------------------------------ ------------------------------
NLS_LANGUAGE                   AMERICAN
NLS_TERRITORY                  AMERICA
NLS_CURRENCY                   $
NLS_ISO_CURRENCY               AMERICA
NLS_NUMERIC_CHARACTERS         .,
NLS_CHARACTERSET               US7ASCII
NLS_CALENDAR                   GREGORIAN
NLS_DATE_FORMAT                DD-MON-YY
NLS_DATE_LANGUAGE              AMERICAN
NLS_SORT                       BINARY
NLS_TIME_FORMAT                HH.MI.SSXFF AM
NLS_TIMESTAMP_FORMAT           DD-MON-YY HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT             HH.MI.SSXFF AM TZH:TZM
NLS_TIMESTAMP_TZ_FORMAT        DD-MON-YY HH.MI.SSXFF AM TZH:T
NLS_DUAL_CURRENCY              $
NLS_COMP
NLS_NCHAR_CHARACTERSET         US7ASCII
NLS_RDBMS_VERSION              8.1.5.0.0

18 rows selected.
Review & Followup

      Rating: 5
Sure it's a
      simple answer but..  May 31, 2002
Reviewer:
      Someone  from Danbury, CT USA
Sometimes simple answers are very useful.  I working with some direct path
exports and this is just what I was looking for.

      Rating: 3
National
      Character Set  August 18, 2002
Reviewer:  Matt
      Adamson  from the UK
This is very useful but we should be aware that at session level, some
parameters could be different. If you want to be sure, compare the results of:

select * from nls_database_parameters;

with:

select * from nls_session_parameters;

      Rating: 3
Creating a
      DB  July 04, 2003
Reviewer:  trev

Tom

I am getting a ORA 12714 when creating a 9.2.0 database
script follows

spool /u03/usr/home/tst/oracle/admin/create/crdbTST.lst
connect / as sysdba
startup nomount pfile=/u03/usr/home/tst/oracle/admin/pfile/initTST.ora
create database "TST"
         maxlogfiles  16
         maxdatafiles 500
         character set US7ASCII
         datafile
 '/u03/usr/home/oracle/data/systemTST01.dbf' size   300M
   logfile
      group 1 ('/u03/usr/home/oracle/data/redo_g1m1_TST.log') size 2M
,
             group 2 ('/u03/usr/home/oracle/data/redo_g2m1_TST.log') size 2M
;

         datafile
         *
ERROR at line 5:
ORA-12714: invalid national character set specified

I tried adding NATIONAL CHARACTER SET AL16UTF16
after the US7ASCII

But it still fails

Any suggesitons please!

          Followup:

idle> create database "TEST"
  2           maxlogfiles  16
  3           maxdatafiles 500
  4           character set US7ASCII
  5           datafile
  6   '/tmp/systemTST01.dbf' size   300M
  7     logfile
  8        group 1 ('/tmp/redo_g1m1_TST.log') size 2M,
  9        group 2 ('/tmp/redo_g2m1_TST.log') size 2M
 10  ;

Database created.

cut and pastes always work best here, operating systems would be interesting as
well -- I see nothing obviously wrong

      Rating: 5
Details details
      details....  July 05, 2003
Reviewer:
      A reader
Tom

Sorry forgot obvious details.

I took the double quotes off the DB name and it worked fine.

DB version is 9.2.0 OS is Solaris .... ummmm can't remember
what version sorry ... will look it up when I get
back to work

July 04, 2003 Eric -- Thanks for the question regarding
      "Resumable insert question", version 9.2.0.1

      You Asked
Hi Tom, I'm testing the resumable transaction feature using an insert on a table
which is on a small tablespace.
After few minutes the transaction is suspended and a message is shown in the
alert file, everything looks fine, but later on the transaction is aborted due
to a "rollback segment FULL" (i'm testing a very long timeout of 8 hours, so i
don't want to fix the problem immediatly).
Mi questions are:
1) Does oracle suspend (for real) the transaction when some storage error
appears or it just "move" the statement to rollback/undo?
2) How Oracle manages a suspended query (select/create as select) performing a
group by/sort in case that no space is available for the temporary segment?
Thanks in advance.

Eric.

      and we said...
1) it suspends it.  There can be no "moving" of data into rollback/undo

suspendable statements work best with an undo tablespace, hope you are using
that and not rollback segments.

2) we wait for space to become available.  When we run out of temp disk space --
we wait (stop processing, generate no more data that would need to be save)
Review &
      Followup

      Rating: 4
Why the insert
      aborted???  July 18, 2002
Reviewer:  Eric
       from Argentina
Tom, thanks for the answer.
Actually, I'm using rollbacks cause I just migrate from 815 and need to check
all these new features before start using them on production.
Regarding your answer, now I'm more confused because I have no explanation about
why the insert aborted with a "rollback segment FULL" error (few minutes after
it was suspended). Any clue?
Thanks again.

          Followup:

because not all consitions are suspendable with rollback segments, only with
undo tablespaces.

show me the full error code and message (well, just the error code) and I'll try
to explain more.

      Rating: 4
Why the insert
      aborted (contd).  July 18, 2002
Reviewer:
      Eric  from Argentina
Tom, this is the secuence of my test.

- Create a 256K tablespace (local) TEST_LMT.
- Create a 1 Meg tablespace (dictionary) RBS_AUX.
- Create a rollback seg. "rbs_aux" on RBS_AUX.
- Create a table TEST on TEST_LMT as an empty copy of DBA_SOURCE.
- Set the transaction to use "rbs_aux".
- Insert into test select * from dba_source.

Looking at the alert log, this message appears
...
Thu Jul 18 18:23:36 2002
statement in resumable session 'User ORACLE(18), Session 8, Instance 1' was
suspended due to
ORA-01653: unable to extend table ORACLE.TEST by 5 in tablespace TEST_LMT
...

Once the insert is suspended, a select on v$rollstat shows that the rollback
"rbs_aux" is growing.
Two minutes later, this message appears on alert log.
...
Thu Jul 18 18:25:33 2002
FULL status of rollback segment 5 set.
statement in resumable session 'User ORACLE(18), Session 8, Instance 1' was
aborted
...

and on SQL*Plus, the insert is cancelled with this error:

SQL> insert into oracle.test select * from dba_source ;
insert into oracle.test select * from dba_source
*
ERROR at line 1:
ORA-01562: failed to extend rollback segment number 5
ORA-01650: unable to extend rollback segment RBS_AUX by 5 in tablespace RBS_AUX

I didn't test this with undo tablespace (is it possible to test the same case
using undo?).
Thanks.

Eric.

          Followup:

This is one of the non-resumable things (documented in the Admin guide) and one
of the reasons you want to test UNDO tablespaces before you try other new stuff
(they are really cool)

<quote src=Admin Guide>
Resumable Space Allocation Limitations for Dictionary-Managed Tablespaces

There are certain limitations of resumable space allocation when using
dictionary-managed tablespaces. These limitations are listed below:

1. If a DDL operation such as CREATE TABLE or CREATE INDEX is executed with
an explicit MAXEXTENTS setting which causes an out of space error during its
execution, the operation will not be suspended. Instead, it will be aborted.
This
error is treated as not repairable because the properties of an object (for
example, MAXEXTENTS) cannot be altered before its creation. However if a DML
operation causes an already existing table or index to reach the MAXEXTENTS
limit, it will be suspended and can be resumed later. This restriction can be
overcome either by setting the MAXEXTENTS clause to UNLIMITED or by using
locally managed tablespaces.

2. If rollback segments are located in dictionary managed tablespaces, then
space allocation for rollback segments is not resumable. However, space
allocation for user objects(tables, indexes, and the likes) would still be
resumable. To workaround the limitation, we recommend using automatic undo
management or placing the rollback segments in locally managed tablespaces.

</quote>

So, there are your choices.  LMTS (totally excellent as well, shouldn't use
anything else) or UNDO tablespaces instead of rollback.

      Rating: 5
Why the insert
      aborted (contd).   July 19, 2002
Reviewer:
      Eric  from Argentina
Hi Tom,
I read those limitations but i misunderstood the second point.
I'll try both options and then i'll decide. I'm not yet feeling confortable with
undo because i resist to give all the control to the database (need to get more
information about the internals of undo feature).
Thanks a lot for your time.

Eric.

          Followup:

give control to the dark side -- give it a try.  you'll like it.

fear of the unknown -- i still meet DBA's who won't let the developers use these
new fangled stored procedure things either.

I agree with your approach -- thorough testing.  Using that approach, undo
tablespaces should be one of the things you are testing....

      Rating: 5
Why the insert
      aborted??? (final words)  July 19, 2002
Reviewer:
      Eric  from Argentina
Hi Tom, I'm back just to let you know my findings.

Rollback segment on LMTs, it doesn't work. It gave me the same error as when
Rollbacks where on Dictionary tablespace.

Undo tablespace, it does work !!!
But (why there is always a "but"?), the undo segment is also growing after the
inserts stops until oracle cannot allocate another extent for it (and a second
"suspend" message appears on the alert log). Good thing about this: the
statement remains suspended.

Thanks again, you really help me on this.
Eric.

PD: Of course, now I have a lot of questions about this undo tablespaces and
what I saw. Could you please, post some link about undo tablespaces? (something
that talk about why oracle fill the tablespace with segments, why ten segments,
why I couldn't create a 1 Meg tablespace with a block size of 16 K, etc).

          Followup:

I don't understand what you mean by:

the undo segment is also growing after the
inserts stops until oracle cannot allocate another extent for it

You'll need to be a little more clear as to the circumstances.  If your
statement is suspended, there can be nothing generating rollback (unless you
have other stuff) and hence, it'll not be growing.

The admin guide documents undo tablespaces.  I don't understand what you mean
by:

"why oracle fill the tablespace with segments" -- if you mean the rollback
segments it created in there, that is what it is designed to do.  Automagically
create and destroy rollback segments as needed to support your level of
concurrent usage without you having to configure the perfect amount.  Do more
work, we'll create more rbs.  do less work, we'll create less rbs.

I don't understand the last part.

ops$tkyte@ORA920> create tablespace one_meg
  2  datafile '/tmp/one_meg' size 1m
  3  blocksize 16k
  4  /

Tablespace created.

seems to work?

      Rating: 4
Sorry, I wasn't
      clear.  July 22, 2002
Reviewer:  Eric
       from Argentina
Tom, what i mean with "the undo segment is growing..." is that if i do a select
* from v$rollstat where xacts > 1 (after the insert ins suspended) Oracle
returns only 1 row (xacts = 1) and columns "extents" and "writes" are increasing
their values until no more space is available in the undo tablespace.
Please, ingnore the rest of the question, it was my mistake.
Thanks.

Eric.

      Rating: 5
Does RESUMABLE
      statemnet work on "LOGON" trigger  July 04, 2003

Reviewer:  Ashutosh  from Australia
Hi Tom,
      Right now I don't have 9i on my machine, so i am putting this question in
your forum.
If a space allocation error occurs while chaning user quota in a logon trigger ,
would it be suspended . Would it be resumable again ?
Thanks for your great support to oracle community.
Regards,
Ash

          Followup:

what is a "space allocation error" that would happen during an "alter
user...quota..." command?

but basically -- if a statement were suspended, yes, it is resumable regardless
of where it was when it got suspended.

      Rating:
      4
  January 09, 2004
Reviewer:  Robert
       from Memphis, USA.

July 04, 2003 BharathKumar -- Thanks for the question regarding
      "Job Scheduling", version 8.1.6

      You Asked
Hi Tom,

  First of all I would like to thank you for all the guidance
you have been giving. Here is my question - For Scheduling a
backup/maintenance job (either thru Rman or OS Backup), is using
the OS Scheduling (DBMS_Job) recommended or OS Scheduling (at in
NT and Cron Job in Unix) recommended ? What are the positive and
negatives of either approach ?

Thanks and Regards

Bharath

      and we said...
Well, it is hard to schedule an RMAN or OS backup via DBMS_JOB
as both are "outside" of the database.  DBMS_JOB can run stored
procedures written in C, Java or PLSQL -- you would have to have
that procedure fire off an OS command.

You can either use OS commands to do it, such as cron, OR you
can use Oracle Enterprise Manager (OEM) to do it, OR you can use
any of a number of system management tools (patrol, open view,
etc).

All are valid.  the one to pick is the one that fits in with
your environments "way of doing things".  If you use OEM, it
would be the right tool -- it's just a natural extension of it.
If you don't and use another tool, use it.  If you don't use any
tools, cron is as good as anything.
Review &
      Followup

      Rating: 2
Pl. reply
      asap  March 30, 2001
Reviewer:  jignesh
       from london,UK
can i use Reports 3.0 for scheduling a report. i mean after developing a report,
i want to run it at a later time. how can i do that
 reply at jignesh@mahindrabt.com

      Rating: 3
what about
      run_cmd  July 04, 2003
Reviewer:  Victor
       from Russia
Would it be wrong to use java function which runs RMAN script in OS by putting
PL/SQL - function with java calling in oracle job?
Thanks.

          Followup:

No, it was one of the options.

July 04, 2003 wallacel -- Thanks for the question regarding
      "table fetch continued row", version 8.1.6

      You Asked
Tom:

if "table fetch continued row" statistics is high in v$sysstat.
we need to analyze table list chained rows.
but if there are lots of table, how do i know which one
i should examine? or examine all?
it will very time-consuming.

Regards,

      and we said...
Well, first of all -- make sure it is in fact "high"

For example, I have:

table fetch continued row     10,417,181

is that high?  Hard to say in isolation.  When I consider that
the sum of table scan rows gotten and table fetch by rowid is
34,419,835,366 -- it doesn't look so high anymore.

If you have longs and long raws in your tables, you will have
this regardless.  If you have tables with more then 255 columns,
you will have this regardless (they are stored in 255 column
rowpieces -- never as a single big row).

You will have to either analyze them or you can do a select to
find out (see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1339202657182
for how to do that).  Pick you heaviest hit tables and
start with them.

Alternatively, you could instruement your popular application,
have it select out the table fetch continued statistic from
v$sesstat either at the end of its run or periodically during
its execution and stash that somewhere with a description of the
application.  Now, you'll now which applications are hitting
this most and can concentrate on their tables.

Review &
      Followup

      Rating: 4
Percentage of
      Table fetch continued row  July 04, 2003
Reviewer:
      Vivek Sharma  from Bombay, India
Dear Tom,

My Statspack report shows that there are 24220415 table fetch continued row and
per second are 7696.4 which I think is too high. The sum of table fetch by rowid
and table scan rows gotten is  559364228 and thus table fetch continued row is
4% of this i.e.24220415/559364228.

table fetch by rowid                   114,997,281     36,541.9      5,212.2
table fetch continued row               24,220,415      7,696.4      1,097.8
table scan blocks gotten                 7,572,955      2,406.4        343.2
table scan rows gotten                 535,143,813    170,048.9     24,255.3
table scans (long tables)                      285          0.1          0.0
table scans (short tables)               1,993,184        633.4         90.3

What according to you should be the situation when I should consider
concentrating on table fetch continued row.  Secondly, what does these numbers
indicate, I think all these numbers indicate the number of rows returned.
Correct me if I am wrong.

Thanks in Advance

          Followup:

how long was that statspack for (15 minutes is good, 30 minutes is very long,
anything longer isn't good).

But in any case -- you did the right thing here.

If I simply told you that on my last car trip I stopped at 50 red lights -- what
could you say about that?  Is that good or bad?

If you say "oh, that is too high", I'll respond -- but I drove from Washington
DC to San Francisco, it was 2,500 miles.  I think it was very good.

If you say "oh, that is really good", I'll respond -- but all I did was drive
two city blocks in Manhatten during rush hour.  It was horrible.

If you say "insufficient data to draw any meaningful conclusions" -- you got the
answer right.

So, the number 24,220,415 "seems" high but as an overall percentage of rows
retrieved in your system -- it is pretty low.

The numbers you see are detailed row counts of rows processed -- not the number
of rows returned.  Consider:

ops$tkyte@ORA920> @mystat "rows gotten"
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('rows gotten')||'%'

NAME                                VALUE
------------------------------ ----------
table scan rows gotten                 29

ops$tkyte@ORA920> select count(*) from big_table;

  COUNT(*)
----------
   1000000

ops$tkyte@ORA920> @mystat "rows gotten"
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('rows gotten')||'%'

NAME                                VALUE
------------------------------ ----------
table scan rows gotten            1000029

ops$tkyte@ORA920>

I had 1 row returned -- yet, I had over 1,000,000 rows gotten from the table.

Does your system have any tables with a row width > block size?  if so, they are
chained and will remain chained FOREVER (well, bigger block size may correct
it).  Tables with longs?  Pretty much forget about it -- it is chained.

These might be migrated rows (analyze list chained rows can help you figure out
that).  If so, you can fix them BUT -- and this is very very important -- have
you

a) a performance issue
b) identified this to be the cause

if not, don't do anything.

July 04, 2003 Marcelo -- Thanks for the question regarding
      "PLS-123 error", version 8.1.6/8.0.5

      You Asked
Hi Tom,
Could you tell me more about the program too large error
(PLS-00123) ?

Normally, we modularize the procedures in order to avoid this
error, but I would like to know more about the internal
organization/storage of a procedure.

Thanks in advance,
Marcelo

      and we said...
The following support note covers this topic well...

Article-ID:         <Note:62603.1>
Folder:             PLSQL
Topic:              General Information Articles
Title:              'PLS-123 Program too Large' - Size
Limitations on PLSQL
                    Packages
Document-Type:      BULLETIN
Impact:             MEDIUM
Skill-Level:        NOVICE
Server-Version:     07 to 08
Updated-Date:       13-JUN-2000 17:41:01
References:

Overview
--------

This article contains information on PL/SQL package size
limitations. When limits are reached, you receive the following
error:

    PLS-123 Program too large

Size Limitations on PL/SQL Packages
-----------------------------------

In releases prior to 8.1.3, large programs resulted in the
PLS-123 error. This occurred because of genuine limits in the
compiler; not as a result of a bug.

When compiling a PL/SQL unit, the compiler builds a parse tree.
The  maximum size of a PL/SQL unit is determined by the size of
the parse tree.  A maximum number of diana nodes exists in this
tree.

Up to 7.3, you could have 2**14 (16K) diana nodes, and from 8.0
to 8.1.3, 2**15 (32K) diana nodes were allowed.  With 8.1.3,
this limit has been relaxed so that you can now have 2**26
(i.e., 64M) diana nodes in this tree for package and type
bodies.

Source Code Limits
------------------

While there is no easy way to translate the limits in terms of
lines of source code, it has been our observation that there
have been approximately 5 to 10 nodes per line of source code.
Prior to 8.1.3, the compiler could cleanly compile up to about
3,000 lines of code.

Starting with 8.1.3, the limit was relaxed for package bodies
and type bodies which can now have approximately up to about
6,000,000 lines of code.

   Notes:  This new limit applies only to package bodies and
type bodies.  Also, you may now start hitting some other
compiler limits  before you hit this particular compiler limit.

In terms of source code size, assume that tokens (identifiers,
operators, functions, etc.), are on average four characters
long.  Then, the maximum would be:

   Up to 7.3:         4*(2**14)=64K
   From 8.0 to 8.1.3: 4*(2**15)=128K
   With 8.1.3:        4*(2**25)=256M

This is a rough estimate.  If your code has many spaces, long
identifiers, etc., you may end up with source code larger than
this.  You may also end up with source code smaller than this if
your sources use very short identifiers, etc.

Note that this is per program unit, so package bodies are most
likely to encounter this limit.

How to Check the Current Size of a Package
------------------------------------------

To check the size of a package, the closest related number you
can use is PARSED_SIZE in the data dictionary view
USER_OBJECT_SIZE.  This value provides the size of the DIANA in
bytes as stored in the SYS.IDL_xxx$ tables and is NOT the size
in the shared pool.

The size of the DIANA portion of PL/SQL code (used during
compilation) is MUCH bigger in the shared pool than it is in the
system table.

For example, you may begin experiencing problems with a 64K
limit when the PARSED_SIZE in USER_OBJECT_SIZE is no more than
50K.

For a package, the parsed size or size of the DIANA makes sense
only  for the whole object, not separately for the specification
and body.

If you select parsed_size for a package, you receive separate
source and code sizes for the specification and body, but only a
meaningful parsed size for the whole object which is output on
the line for the package specification.  A 0 is output for the
parsed_size on the line for the package body.

The following example demonstrates this behaviour:

CREATE OR REPLACE PACKAGE example AS
  PROCEDURE dummy1;
END example;
/
CREATE OR REPLACE PACKAGE BODY example AS
  PROCEDURE dummy1 IS
  BEGIN
    NULL;
  END;
END;
/

SQL> start t1.sql;

Package created.

Package body created.

SQL> select parsed_size from user_object_size where
name='EXAMPLE';

PARSED_SIZE
-----------
        185
          0

SQL> select * from user_object_size where name='EXAMPLE';

  .....

Oracle stores both DIANA and MCODE in the database.  MCODE is
the actual code  that runs, while DIANA for a particular library
unit X contains information  that is needed to compile
procedures using library unit X.

The following are several notes:

a) DIANA is represented in IDL.  The linear version of IDL is
stored on disk.     The actual parse tree is built up and stored
in the shared pool. This   is why the size of DIANA in the
shared pool is typically larger than on    disk.

b) DIANA for called procedures is required in the shared pool
only when you    create procedures.  In production systems,
there is no need for DIANA     in the shared pool (but only for
the MCODE).

c) Starting with release 7.2, the DIANA for package bodies is
thrown away,   not used, and not stored in the database.  This
is why the PARSED_SIZE    (i.e. size of DIANA) of PACKAGE BODIES
is 0.

--> Therefore, large procedures and functions should always be
defined within  packages!

Review &
      Followup

      Rating: 4
Limit on
      Parsedsize of procedure  January 23, 2002

Reviewer:  Bhawna Rajesh  from Taiwan
I have a PL/SQL stored procedure with the following details in USER_OBJECT_SIZE
table:

NAME : SP_OFD_FIN
TYPE : PROCEDURE
SOURCE_SIZE : 71586
PARSED_SIZE : 129977
CODE_SIZE : 74013
ERROR_SIZE : 0

Server : Oracle 8.1.7 on 64-bit IBM AIX

This procedure gets uncompiled every now and then, without any changes in the
dependencies. The status of the procedure is INVALID in user_objects view. And,
when the front-end (Forms) tries to recompile this procedure at the run-time, it
fails to do so, and an error "Deadlock Detected" is returned by the server. If
we go to SQL*Plus prompt and recompile the procedure, the front end can be used
properly.

Can the status of a procedure can change automatically, due to the big size of
DIANA code?  What is the upper limit on parsed_size ??

If the PARSED_SIZE is hitting the upper limit, then we can change the procedure
to a package, where the DIANA code is not stored in database at all.

Kindly clarify my doubts.
Thanks.

          Followup:

No, the size has nothing to do with it.

Something it depends on (see DBA_DEPENDENCIES) is changing.

A procedure that is 71k in size is a pretty LARGE procedure -- I'm a modular
code fan, a subroutine should fit on a screen.  I would guess your routine is
over 2,000 lines long -- my big screen might get 100 lines/screen.  I would
suggest (not for the reason you are hitting an error with) you would consider
recoding that in a modular fashion just "because".

A package is stored in DIANA in the database, just like a procedure.  A package
can be used to break the dependency chain however, perhaps making this go away.
It is my belief that ALL production (real) code should be in a package, never in
a standalone procedure or function.

      Rating: 3
Related
      question  January 24, 2002
Reviewer:  Salas
       from Mexico
Exists a limit for a [Program Unit] size in Forms?

I have a problem opening certain PU (but only in Windows 95, 98 or 2000. Windows
NT open fine the same form and PU)

Thanks

          Followup:

I'm sure there is a limit, please contact support for this one.

      Rating: 4
why not
      recompiling on its own  January 24, 2002
Reviewer:
      Bhawna Rajesh  from Taiwan
Hi Tom,

Going by your advice, we are wrapping up this procedure into a package. It's a
customization of an existing application, so rewriting of procedures in modular
form will call in for additional efforts.
But, still I am not clear about why the procedure SP_OFD_FIN could not get
recompiled on its own, when the front end called it. Whereas, the rest of
procedures having an uncompiled state (for whatsoever reasons) get compiled
automatically, as they should. Can you throw some light on this ?

          Followup:

Well, getting a deadlock detected on this one is a strange one -- you would need
to review the trace file on the server (we generate one with deadlocks) to see
what is causing the "jam up".

      Rating: 3
speaking of
      wrapping...  January 25, 2002
Reviewer:
      A reader  from ca, usa
Would wrapping the code make things better or worse? I have a 141k package which
after wrapping it is 365k. I don't have a non 8i database to test it on.

          Followup:

it makes no difference.

      Rating:
      5
  February 25, 2003
Reviewer:  Helena
      Marková  from Bratislava, Slovakia

      Rating: 5
pls
      123  July 04, 2003
Reviewer:  A reader

Tom, a very strange situation to me. The same code below ran ok in Oracle 8.0,
but the following error ocurred in 8.1 -- the other way around would make sense
to me though. I searched Metalink but I found no reasonable explanation for
that.

That aside, any improvements you would suggest ?

Thanks for you help !

ps: I replaced my "sensitive" pieces of text with X's.

SET ECHO OFF;
SET VERIFY OFF;
SET SERVEROUTPUT ON;

spool XXXXXXXXXXXXXXXXXXXXXXX.log;

declare
i number(3) :=0;
begin

dbms_output.put_line('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXX');
dbms_output.put_line('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');

begin
  delete from XXXXXXXXXXXXXXXXX;

exception
when others then
  rollback;
  raise_application_error
(-20000,'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXX');
end;

dbms_output.put_line('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
dbms_output.put_line('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');

-- XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

begin
i := i + 1;
insert into XXXXXXXXXXXXXXXXX(XXXXXXXXXX  , XXXXXXXXXX ,XXXXXXXXXX
,XXXXXXXXXXXXXX ,XXXXXXXXXXXXXX ,XXXXXXXXXX ,XXXXXXXXXXXXXX ,XXXXXXXXXXXXXX
,XXXXXXXXXXXXXX ,XXXXXXXXXXXXXX ,XXXXXXXXXXXXXX ,XXXXXXXXXXXXXX ,XXXXXXXXXX
,XXXXXXXXXXXXXX ,XXXXXXXXXXXXXX ,XXXXXXXXXX ,XXXXXXXXXXXXXX ,XXXXXXXXXXXXXX
,XXXXXXXXXXXXXX ,XXXXXXXXXXXXXX ,XXXXXXXXXXXXXX ,XXXXXXXXXXXXXX ,XXXXXXXXXX
,XXXXXXXXXXXXXXXXXXXXX ,XXXXXXXXXXXXXXXXX ,XXXXXXXXXXXXXXXX ) values ('X',XXX
,X   ,X   ,XXX   ,X   ,XXXX   ,XXXXXX   ,X   ,XXXXXX  ,X   ,XXXXX   ,'X'   ,X
,XXXXX   ,X   ,X   ,XXXXXXXXXX   ,X   ,XXXXXXXXXX   ,XXXXXX   ,XXXXXXXXXX   ,'X'
  ,XXXX   ,XXXX   ,''); i := i + 1;
-- (Tom, here go 299 more inserts similar to the one above)

exception
when others then
  rollback;
  raise_application_error
(-XXXXX,'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXX');
end;

IF i = 300 THEN

dbms_output.put_line('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXX';
  dbms_output.put_line('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
  COMMIT;
ELSE

dbms_output.put_line('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXX');

dbms_output.put_line('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
);
  dbms_output.put_line('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
  ROLLBACK;
END IF;

end;
/
SPOOL OFF;
SET ECHO ON;
SET VERIFY ON;
exit

declare
*
ERROR at line 1:
ORA-06550: line 0, column 0:
PLS-00123: program too large

          Followup:

is it april 1st?

you don't seriously expect me to create table xxxxxxxx ( xxxxxxx int,
xxxxxxxxxxx int, .... ) and so on?

testcase = something small, concise, yet 100% self contained that anyone can
run.

(and I cannot imagine where or what you must be doing that table and column
names are sensitive.  Please use an example we can all understand -- like I do
for you)

      Rating: 2
Same as
      above  July 04, 2003
Reviewer:
      A reader
Tom, I did not intend we have a test case here or something. Just by the looks
of it -- can you tell me if something is CONSPICUOUSLY wrong -- like lines sizes
or number of lines, etc ?

The fact is, this program is not mine. It looks a bit awkward to me -- all those
inserts... I would have written something quite different. But I cannot touch
the user's code without a good reason.

Can you give me any directions here ?

          Followup:

the error arises from a parse tree growing too large.

the code will have to be rewritten to be a little more modular.

      Rating: 5
April 1st
      ??????????????????????????????????????????????????  July 04,
      2003
Reviewer:  A reader
Tom, it's not April 1st, rather July 4th !!! What are you doing working today ?
I gotta admit -- you're a hard nut to crack, Tom.

          Followup:

gotta book to finish and I use this as a continual distraction cause almost
anything is better then technical edits...

:)

July 04, 2003 Munzer -- Thanks for the question regarding
      "Passing parameters in Web application", version 8.1.7

      You Asked
Tom:

1.  IS there  a limitation on using the GET method to submit data from a HTML
form into a PL/SQL procedure.  If you have a 1500 byte record and user filled it
out completely would it be rejected?

2.  Would the limitation still exist using the POST method?or there is none
since it is using standard input?

3. For a web application that passes may be 20 parameters would be using cookies
to write values to and then read it back? is this easier?

or you would pass the parameters from one page to another?  Is this the standard
way?

Thank you,

      and we said...
1) it varies by webserver/environment. the GET information may be stored in the
environment and the max size of this varies by OS.  I would recommand not using
GET for anything over a very small amount.  Since PLSQL procedures cannot even
tell the difference between GET and POST, I would recommand using POST for all
procedures regardless.

2) there is no limit for POSTed data.

3) cookies would be harder in my opinion -- unless you add more details and tell
me exactly what you intended on doing.  cookies go back and forth with the page
just like parameters.
Review & Followup

      Rating: 5
Web
      application  January 19, 2002
Reviewer:
      Munzer  from Reston, VA
Tom:

1.  I use the get first for debugging to see the data that is being passed.  Can
not see it with post?

2.  For using cookies, I plan to do the noraml insert , select, update , delete
of data.

Basically take user's input and insert it into table.
select data from table and display it for update or delete.

Problem with large 30 columns forms, you have to use a long list of prameters.
Are you saying the same thing is with cookies?
Is there any advantage of using cookies?

          Followup:

1) well fine -- just understand that if it gets big -- it may stop working all
together.  Upto you -- I use POST for everything.  If there is an error in the
call, the PLSQL module will print out what we attempted to pass.

2) if you are saying "i'm going to use a cookie to hold a session id, and that
session id will be a primary key into a session state table, and to avoid
passing the entire state, I'll just maintain it in the database" -- then fine.
You don't NEED to use a cookie (i avoid them in most cases, in any case i don't
RELY on them as many people don't let me use them.  asktom uses a convienence
cookie -- if you have it, life is easier, if you don't, nothing is lost).  You
can just pass a sessionid from screen to screen -- look at many URLS on asktom,
eg, if you goto
http://asktom.oracle.com/
and click on a question, you'll see something like:

http://asktom.oracle.com/pls/ask/f?p=4950:8:417017::NO::
                                            ^^^^^^^

thats the session id for your session.

      Rating: 3
how to pass
      more then 32k char data  November 09, 2002

Reviewer:  Vikas Sharma  from Delhi India
Hi Tom,

Kindly let me know is there any way to pass more then 32k data from a form a
<textarea> field for example take this textarea field can i write more then 32k
data.

I tried by taking the clob parameter of form action page(procedure) but it does
not work because of clob parameter when i change it to varchar2 type parameter
then it works but it limit me for 32k data,i am using mod_plsql. with oracle
8.1.7

Thanks,
Regards,
vikas

          Followup:

Nope, you cannot.  For large objects like that -- I use file upload/download and
store it in a blob.

      Rating:
      5
textarea  November 09, 2002
Reviewer:
      mo
Tom:

1. Are you saying if I have a html form with a textarea field and user entered
more than 32K I will not be able to submit "post" data to a pl/sql procedure
that inserts it into oracle table?

          Followup:

correct

      Rating:
      4
Thanks..  November 09, 2002
Reviewer:
      Vikas Sharma  from Delhi India
Hi Tom,

Thanks a lot.
Kindly let me know which datatype column one should store the data from a form
<textarea> field when its data can be more than 4000k but less then 32K. I am
storing it in to clob.

and if i have to limit user for not writing more then 32k data into a textarea
field than i cannot use maxlength also. or i let user to write any anmount of
data but catch it when the form is submited. If i have chatch at server side
(when the form is submit)  then how to do that? How dou do it at your site?

Regards,
Vikas Sharma

          Followup:

clob is just fine, perfect -- thats the right type.

I've seen people use a javascript snippet to check the length of the field
BEFORE it is submitted (if you submit it, the page will "fail" at the app server
as 32k is larger then can be bound -- the procedure cannot catch the error).

      Rating: 4
Session
      ID's  November 11, 2002
Reviewer:  tom
       from UK
You mention that you use a session id and pass this from page to page - how is
this session ID generated [is there a standard way to do this in an oracle web
application?]

          Followup:

sequences are fairly populate

userenv('sessionid') is as well.

to_char(dbms_random,'fm00000000000') || to_char(sequence,'fm0000000000000') ||
to_char(dbms_random,'fm00000000000') works well as well -- generating a hard to
guess sessionid (which is sometimes needed).....

      Rating: 5
Uploading
      Files  July 04, 2003
Reviewer:  Ray
       from ottawa,on,ca
When the file name is typed into the input type="file" vs using the browse
button

there is no maxlength spec in html for "file", and if the text entered is > 250
chars the submit fails the proc

<FORM enctype="multipart/form-data" action="SUPPORT_DOC.Processform"
method="POST">

Is there anyway you know other than javascript to prevent entry and force use of
the browse button.

          Followup:

nope

July 06, 2003 Mukta -- Thanks for the question regarding
      "Creating Foreign key with a view or materialzed view", version 8i

      You Asked
I would like to know if I can create a foreign key which references a view?
Can I create a foreign key which references a materialized view?

      and we said...
In 8i, no.

In 9i, views can have primary / foreign keys -- but they are for query rewrite
-- not for enforcement.

A foreign key must point to a table with a real live UNIQUE or PRIMARY KEY
constraint on it.

A materialized view, being a real table, can technically have a fkey pointing to
it however -- you could break the refresh process easily.  Make sure the fkeys
you create are DEFERRABLE in this case.
July 06, 2003 --
      Thanks for the question regarding "ORA-16000 database link read only query
      fails", version 9014

      You Asked
Dear Tom,
We started up a database in read only mode,all users are able to run read only
queries .

One user who has a database link to another database cannot run a read only
query and do a minus operation.

The target database is read-write.

The source database temp data files are read-write.

Could you please enlighten us on what is going wrong?

Thanks
~ StanR

SQL> select * from QA_TEST_XXXXX;
select * from QA_TEST_XXXXX
              *
ERROR at line 1:
ORA-16000: database open for read-only access

SQL> select * from user_synonyms where synonym_name like 'QA_TEST_XXXXX';

SYNONYM_NAME                   TABLE_OWNER
------------------------------ ------------------------------
TABLE_NAME
------------------------------
DB_LINK
--------------------------------------------------------------------------------
QA_TEST_XXXXX
QA_TEST_XXXXX
XXXXX_XXX4.XXX.STATE.XX.US

      and we said...
distributed stuff starts a transaction "just in case".  If you set your
transaction read only:

scott@ORA920> select * from dual@ora9i@aria;
select * from dual@ora9i@aria
              *
ERROR at line 1:
ORA-16000: database open for read-only access

scott@ORA920> commit;

Commit complete.

scott@ORA920> set transaction read only;

Transaction set.

scott@ORA920> select * from dual@ora9i@aria;

D
-
X

scott@ORA920>

scott@ORA920> select * from dual minus select * from dual@ora9i@aria;

no rows selected

that should circumvent that.
Review &
      Followup

      Rating: 4
Still getting
      ORA-16000  April 19, 2003
Reviewer:  Saminathan
      Seerangan  from CA,USA
Tom, I'm still getting ORA-16000 error. What could be wrong?
Target dn is up and running in read-write mode.

SQL> startup nomount
ORACLE instance started.

Total System Global Area  135338868 bytes
Fixed Size                   453492 bytes
Variable Size             109051904 bytes
Database Buffers           25165824 bytes
Redo Buffers                 667648 bytes
SQL> alter database mount standby database;

Database altered.

SQL> alter database open read only;

Database altered.

SQL> set transaction read only;

Transaction set.

SQL> select * from employees@tamizh_dblk;
select * from employees@tamizh_dblk
              *
ERROR at line 1:
ORA-16000: database open for read-only access

SQL>

          Followup:

who are you logged in as.

looks like SYS.  SYS cannot do read only transactions -- the "transaction set"
is lying to you.

use a real user, one that you would actually use.  never do things as sys,
system, et. al.  they are "our" accounts.

      Rating: 5
Why SYS can't
      have READ ONLY transactions?  April 19, 2003

Reviewer:  Kamal  from New Jersey, USA
Hi Tom,
The resonse was very informative, however, This raises a question -
Why SYS may not initiate a READ ONLY transaction?
Since SYS account should not be used for any normal transaction processing, I
would assume that it would be more important for tranactions under SYS be marked
READ ONLY, to safeguard against any accidental changes.

          Followup:

SYS is special.
SYS is ours

SYS is used to do recursive SQL -- it would be deadly for SYS to be in a real
only transaction.  SYS must NOT be prevented from transactional processing --
that would be bad.

SYS should not be used by you.  That is how to prevent bad things from
happening.

      Rating: 5
We can get
      answer for this type of questions ONLY at "asktom"  April
      20, 2003
Reviewer:  Saminathan Seerangan  from CA,USA

Dear TOM, Yes i did login as SYS account. Thanks you so much for your
outstanding answer.

      Rating: 5
Wow...that was
      amazing.  April 21, 2003
Reviewer:  DeeeBeee
      Crazeee  from India
How in the world, Tom, could you ever figure out that SYS was the account logged
in? Hats off!

          Followup:

this sequence of events:

SQL> startup nomount
ORACLE instance started.

Total System Global Area  135338868 bytes
Fixed Size                   453492 bytes
Variable Size             109051904 bytes
Database Buffers           25165824 bytes
Redo Buffers                 667648 bytes
SQL> alter database mount standby database;

Database altered.

SQL> alter database open read only;

Database altered.

SQL> set transaction read only;

Transaction set.

SQL> select * from employees@tamizh_dblk;
select * from employees@tamizh_dblk
              *
ERROR at line 1:
ORA-16000: database open for read-only access

SQL>

the startup gave it away.  without any other connects after that - you KNOW
they were in as SYSDBA/SYSOPER or internal (8i).  they were sys.

      Rating: 5
Thats really
      cool  April 21, 2003
Reviewer:  DeeeBeee
      Crazeee  from India
Tnx....oops just forgot that you have acronyms! Thanks a lot for the
information. :)

          Followup:

I don't hate acronyms -- when they make sense.

SQL

for example.

It is when people use instant messaging techniques in a discussion -- using U
for You and UR for Your and so on.  I find it very hard to read personally (and
in a multi-language world, clarity is best).

If they want me to take the time to answer, they can at least take the time to
talk to me properly.

      Rating: 5
Correction
      :(  April 21, 2003
Reviewer:  DeeeBeee
      Crazeee  from India
Please read 'have' as 'hate' in my previous message

      Rating: 5
Re: ORA-16000
      database link read only query fails  July 06, 2003

Reviewer:  A reader  from Houston
Tom,
Your first posting mentions that
>>distributed stuff starts a transaction "just in case".
Why would a dbLink start a transcation, if it is just a select statement why
should it start a transcation?

Thank you

          Followup:

well, it actually won't always -- depends on versions.  later releases don't.

July 07, 2003 Koshal -- Thanks for the question regarding "SGA
      Shared Pool - Reg.", version 8.1.6.0

      You Asked
Hi Tom,
When I am executing the query for the first time it is
taking time.

Amazingingly the result is considerably faster when I
am executing it for the second time.

In yr answer to my previous question  u said that
blocks that are used to construct the result set might be
stored in the SGA Shared Pool.

What I understand from yr answer is that
during the processing of executing the
query the blocks r stored in the SGA.

Is the Block Address (the starting location and the
ending location of the block) is stored or the data
in the table is stored in the SGA.

Assume that I am having database of size 4GB and
I allocated 4GB of SGA can all the tables/procedures
be cached in the SGA.

1. When I say 'select * from table_name' which contains
    10000 rows  thnink that i have good amount of SGA.
   Is the result set stored in the SGA or Not?

2. When I say 'select Count(*) from table_name' which returns
   returns a single rows does the result set is stored in
   the SGA.  If it is stored in the SGA when I execute the
   statement for the second time it should be returned within
   a fraction of second irrespect of the time taken for
executing
   the query for the first time.

Regards

and I said...

1) The "result set" is not stored there but the blocks that are
used to construct the result set might be.  It depends.   It
depends on the size of the table, the settings of the table
(cache vs nocache) and so on.  typically a full scan like that
would go to the head of the LRU list so that those blocks are
immediately aged out of the SGA.  If the table is "cached" --
they then the blocks are buffered "normally".

2) result sets are never stored in the SGA.  Blocks that can be
used to make a result set are.  The result of a count(*) would
not be cached HOWEVER the index that was use to answer the
count(*) or the table blocks that were used might be.

A count(*) is a "highly unstable" answer -- other sessions
modifying the table will affect it continously.  It would not be

cached.  To see how to "pre answer" computationally expensive
queries, you want to read about Materialized Views.

      and we said...
There could be many things to consider here.  To see what is
really happening -- I suggest you use TIMED_STATISTICS and
SQL_TRACE in a single session.  Run the query "for the first
time", exit SQLPlus and use tkprof to generate a report from the
trace file.  Then do the same exact thing.

Those two reports will tell us exactly what the difference
between the two are.

Some of the things it could be:

o your query takes a very long time to "hard" parse (parse,
optimize and so on).  The first time it is executed we must hard
parse it.  After that, it is a "soft" parse (we find the stord
plan in the shared pool).  If the hard parse takes a long time
-- that could account for it.

o your query hits lots of blocks and the first time you run it
we must read them from disk.  We store them in the SGA based on
a hash of their DBA (data block address -- file# and block#).
The second time you run it, we read all of the needed blocks
from disk.

o your query uses lots of temp space.  You are using dictionary
managed temporary tablespaces.  The first time through we must
do LOTS of recursive sql calls to expand the temporary space to
satisfy your query.  Subsequent executions simply reuse the
space you allocated the first time around.

Those are just some of the possible causes for this behaviour.
SQL_TRACE and TKPROF will tell us exactly.  See

http://technet.oracle.com/doc/oracle8i_816/server.816/a76992/ch14_str.htm#1018
for the details.

Review &
      Followup

      Rating: 3
flush shared
      pool  July 07, 2003
Reviewer:  vj  from
      in
we have an powerbuilder application that connects to 9ir2 database using the
Oracle91 driver (SQORA32.DLL).

At times suddenly one of the procedures throwing an "no data found" exception.
we could not simulate but happens 3-4 times a day from the powerbuiler
application. if i do ALTER SYSTEM FLUSH SHARED_POOL, it works...what this could
be ?

Rgds

Vijays

          Followup:

what this could be?  probably "time to call support" assuming of course that you
have debugged the code to the level that you are pretty much positive that there
is no way in the world that the offending statement could have thrown the no
data found.

My suggestion --

o add code to the PB code that'll let you turn on
alter session set events '10046 trace name context forever, level 12';
at will.  Eg, setup a database table that your application queries, or make it a
parameter to the program so that that alter is executed upon demand.

o when the problem arises, I have to assume it is 100% reproducible after that
(else you wouldn't flush) - enable this trace.  This will dump all SQL and the
associated binds into a trace file.  Reproduce the problem.

o get that trace file and verify the bind variable values -- make sure that the
no data found should not have occured.

      Rating: 2
Flushed Shared
      Pool  July 07, 2003
Reviewer:  Jon
      Musselwhite  from United Kingdom
Not so much a review, as my own follow up. I have recently experienced something
very similiar. Upon changing cursor sharing to "FORCE", a Powerbuilder
application started returning zero rows. I raised this with Oracle who informed
that there was a bug (2092879), which when fixed/patched introduced more
problems. This has apparently been fixed in 9.2.0.3.0.

July 07, 2003 Tianhua -- Thanks for the question regarding "An
      extent is NOT contiguous?", version 8.1.6

      You Asked
Hi Tom,

Thanks!  I learned a lot from your responses.

This is a follow up question about your response to "Number of extents":

http://asktom.oracle.com/pls/ask/f?p=4950:8:24292::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:730289259844,
You said:

"PHYSICALLY, on the disk, an extent is not contigous.  File systems dole out
space in tiny chunks.  They give out space as they find it.  They do not
allocate contigous space."

However, from the article you recommended (stop defragmenting and start living),
I found the following statement in chapter 2, Tablespace Management:

"An extent refers to a collection of contiguous blocks in a file".  "An extent
is a very efficient way to represent a large number of blocks since it just
lists the START BLOCK AND LENGTH INSTEAD OF LISTING EVERY BLOCK".

It seems to me that the authors of the article believe that an extent is
PHYSICALLY contiguous. If they are wrong, their second statement has to be
wrong. Could you please explain in more details about how you come to the
conclusion that an extent is not physically contiguous?

Thanks!

Tianhua

      and we said...
No, they carefully said:

"An extent refers to a collection of contiguous blocks in a file".

If you open an OS file and you seek to the START_BLOCK and start reading --
you'll read the data for that extent.  Logically, to Oracle, the extent is a
contigous set of blocks.  The file abstracts that for us.

Physically, the first block an an extent might be on the "edge" of the disk, the
second block could be on the "middle" of the disk.  We don't care (nor know)
since we just use seeks on the file to get where we want.

The comment about "an efficient way to represent ..." was made in regards to the
data dictionary.  Instead of having to have a row per block, we have a row per
SET of blocks (start block, number of blocks).

followup to comment

This is getting boring....  I though I pretty much nailed this in:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:730289259844
Let me try one more time.

Extents are stored in files.

Files are allocated by the operating  system.  Files are themselves managed in
"extents" by the operating system.   Files in an operating system are not stored
contigously on disk.  The operating system will get a chunk here, get a chunk
there.  This is why we spend money on disk defragmentors in a vain attempt to
clean up the extents of our files.

Since extents are just pieces of files and files are not stored contigously on
disk -- we had the situation whereby extents are not contigous units of
allocations PHYSICALLY on the disk.

On a raw partition -- this *might* be different (consider raw volumns on a
striped disk array -- hmmm, if I use a 256k strip -- across 5 disks -- I can be
assured that any extent >256k in size will NOT be physically contigous!!  by
design).  On an old fashion raw partition -- using a single non-raided physical
device -- extents will be contigous.

On ANY file system -- I can assure you that unless it is a brand new, virgin
disk -- never been written to, your extents are in files that are physically
fragmented.

So, extents are logically contigous units of allocations in files that are
physically fragmented.

Period....  If it weren't so, we'd never use a disk defragger, Unix wouldn't
tell us "X% fragmented" after an fsck and so on.

followup to comment two

Mark -- thanks but that doesn't show it really.

The argument is "an extent is a contigous collection of blocks on disk, the
contents of any given extent is contigous".

That multiple extents are not necessarily contigous is not disputed, that is
correct (and that query provided by you shows that)

What is at question is "are the blocks within an extent physically contigous on
the disk upon with they reside".  The answer is: PHYSICALLY -- no, block 1 of an
extent might be very very far away from block 2 on disk.  LOGICALLY -- yes,
block 1 is right before block 2 in the file.

Files are not physically contigous, hence an extent is not physically contigous.

followup to comment 6

How very useful of you to provide that inciteful commentary.  "not" just clears
it all up ;)

followup to comment 7 by Ganesh

If you follow the link in the question above:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:730289259844
You'll see that I make the bold claim:

1 extent or 1,000 -- WHO CARES.

You ask a very good question -- "whats the point of decreasing the number of
extents".  My answer is -- it is pointless and done by people who believe in
mythology.

There is no point.  I've never rebuilt a table because its in "too many" extents
personally.

Review &
      Followup

      Rating: 1
Extents are
      contiguous space on disk  July 26, 2001
Reviewer:
      W. I. Brown  from Johnson Space Center
You told Tianhua that an extent is not contiguous space on disk.  Both my
experience and Oracle's documentation tell me that you are wrong.  "Oracle8: The
Complete Reference", chapter 19 says that a Tablespace is a file.  This is the
level that the operating system manages.  Space management within a Tablespace
is handled by the database engine. Within a Tablespace are Tables, Indexes, and
Clusters.  Tables are made up of Segments, which are made up of Extents. Extents
allocated to the same Table do not have to be, and normally are not, contiguous,
but each extent is a contiguous block (not an OS block) of space within the
Tablespace.  The size of the Extent is determined by by the storage parameter
specification for the Table or Tablespace.  If you attempt to allocate a new
extent that is larger than the largest contiguous block of space available
within a Tablespace, the allocation will either extend the Tablespace if
Autoextend is turned on, or it will fail.  If you attempt to allocate a new
Extent for a Tablespace that is larger than the largest contiguous block of
space on the disk, it will fail.

      Rating: 5
TO: W.I.
      Brown  July 26, 2001
Reviewer:  Mark
       from Indianapolis, IN USA
W.I. Brown:

Take a look at this...

The formatting will be hard to follow, but the query illustrates that extents
are, in fact, NOT contiguous ON DISK.

SQL> select   segment_type, tablespace_name, extent_id, file_id, block_id from
dba_extents where segment_name = 'DE_TX_NODE' and extent_id < 3;

SEGMENT_TYPE     TABLESPACE_NAME   EXTENT_ID    FILE_ID   BLOCK_ID
---------------- ---------------- ---------- ---------- ----------
TABLE            USERS                     0         16       1321
TABLE            USERS                     1         15       1241
TABLE            USERS                     2         16      63417

3 rows selected.

Given your experience, please explain how extent_id 0 is in file_id 16 and block
1321 while extent_id 1 is in file_id 15 and block 1241?  Extent 0 and extent 1
are in separate files!  If extents are contigious on disk, how could it be?
Also, notice that extent_id 2 is back in file_id 16, but at block 63417...

What Tom is saying makes perfect sense!

      Rating: 5
Got
      it...  July 26, 2001
Reviewer:  Mark
       from Indianapolis, IN USA
I think I understand "their" argument now...   Using my data as an example...
The tablespace (users) is a LMT with UNIFORM 128K extents.  So "they" are saying
that all 128K of each extent is physically contiguous on disk?  No way!  My OS
block size is 512 bytes.  I can guarantee that that all 256 OS blocks that are
required to make up one of my 128K extents are *not* next to each other on disk.

Anyway, sorry for adding confusion to an already 'beaten to death topic.'  You
must have the patience of Job.  Have a great vacation.

      Rating: 5
Perfect
      Explain  July 26, 2001
Reviewer:  A reader
       from USA
Hi, Tom,

hope you climb the mountain, and relax.

And also hope they are "contiguous" on the road. ;)

Have fun and we are waiting for you back.

Thanks

      Rating:
      5
  July 27, 2001
Reviewer:
      A reader

      Rating:
      1
  July 27, 2001
Reviewer:
      A reader
not

      Rating: 3
Another
      question then arises  July 27, 2001
Reviewer:
      Ganesh Aditham  from Pune , India
PHYSICALLY -- no, block 1 of an
  extent might be very very far away from block 2 on disk.  LOGICALLY -- yes,
  block 1 is right before block 2 in the file.

Then what's the point in decreasing number of extents into 1  extent for objects
by reorg ,as even in 1 extent the object gets dispersed all over the disk and
same will be the situation for objects having multiple extents,?
How will a object having less number of  extents give a better performance?
Can somebody clearify on this

      Rating: 5
Can't let it
      die  July 27, 2001
Reviewer:  Mark
       from Indianapolis, IN USA
OK, I can't let this die, so...  I downloaded a utility from sysinternals
(formerly ntinternals) that displays the distribution of a file across
clusters on an NTFS disk.

This is what I did:
SQL> create tablespace ts_temp
datafile 'C:\ORACLE\ORADATA\CDS1\TSTEMP01.DBF' size 65560K reuse
autoextend off extent management local uniform size 64M;

This is not a contrived example, as I have a tablespace for rollback segments
that are 64M each.  Notice that this is a LMT with UNIFORM extent allocation of
64M.
The size of the file is 64M + 64K (for the bitmap).  Therefore, I am forcing the
situation where this file can hold only a single extent.  I believe that the
debate
is whether or not a single extent will be physically contiguous on disk.  I then
used
the sysinternals utility to look at the file.  It is in *two* fragments
(physically
located on *two* non-adjacent disk clusters), *not* a single contiguous
fragment.  Remember this file has a *single* extent in it.
The output of the utility is:

NT 4.0 Defragmentation Demonstration Program V1.0
Copyright (C) 1997 Mark Russinovich
[ snip ]

Opening volume: C:
Open status: The operation completed successfully.

Enter commands ('?' for help):

: n C:\ORACLE\ORADATA\CDS1\TSTEMP01.DBF

Clusters for file: C:\ORACLE\ORADATA\CDS1\TSTEMP01.DBF
   VCN: 0 LCN: 1966165 LEN: 44
   VCN: 44 LCN: 2608488 LEN: 16348
Enumerate file clusters: STATUS_SUCCESS

The cluster size on my system is 4k, and you can see that the file occupies
16,392
clusters == len fragment1 + len fragment2.  Notice that the 'break' is not at a
location
where the bitmap would be in one cluster and the 'real' extent in another
cluster.
(It is 176K into the file).  Now I know someone will say, "Yeah, but that's
Windows!
A 'real' system wouldn't do it that way."  I believe that this would be the case
on
whatever platform you happen to be running.  I am also running on Solaris and
AIX, but I
don't have a utility to map the file allocation on disk, so I can't verify my
suspicion.
In any case, I simply don't understand why it is so difficult to accept that a
filesystem
is not going to allocate the space requested by Oracle in contiguous blocks on
disk.  If
someone can convince me otherwise, I would admit 'defeat'.  But at least try to
make a
stronger argument than 'not'.  Sorry about the formatting, I don't know how (or
if it is possible) to change the formatting in the 'feedback' facility.

      Rating: 5
Database files
      are (almost) never contiuous on a disk  August 01, 2001

Reviewer:  Harrison  from Haymarket, VA
Hi:  Just to point out how little the OS cares about our concept of being
physically contiguous, due to their size, non-trivial database files are large
enough that they are always on several platters, making up at least one cylinder
in a disk.  You can be assured that if the controller can connect parts of many
platters so the file is read correctly, it is not more difficult to skip parts
because they are in another file.  The controller also skips bad parts of disks
(and reads a spare side to fill in the data).
The pictures that utilities make, showing a file system as one big rectangle is
just a logical concept that combines many different platters, with multiple
heads on each side, and not the physical reality. The important part is that the
file system can read the file in order, not that it is all in one spot.  It
isn't.

      Rating: 3
Who says that
      there is no use of defraging a table.  August 02, 2001

Reviewer:  sarb  from Toronto
Its really usefull or  i will say performance enhancement by defraging a table .
Eg.  its better to have 5-10 larger extents for a table sized 10 MB rather than
1280 Extents of 8K each.  And what about at table sized 530 MB with 2200 extents
rather same table with 53 extents, certainly # of extents effects the
performance thoug they r not congiguous on disk physically. ( other parameters
also effects the size for the extent)

      Rating: 3
Ganesh -
      Extents  August 03, 2001
Reviewer:  A
      Reviewer  from Glasgow, Scotland.
Ganesh, To answer your question, there is no benefit to be had from 1 large
extent instead of a number of smaller ones. The 1 extent is just a logical view.
The data blocks are dispersed accross the disks by the OS, you cannot govern
this. If you were importing data into a new table it makes sense to put all data
into 1 large extent initially. After that there is no need to routinely
export/import to maintain this large 1 extent. Fragmentation on Disk is a
different matter!!.

      Rating: 4
An Extent
      minimalist  August 03, 2001
Reviewer:  Steve
      H  from Hants. UK
Thanks Tom (and other contributors). Before the days of AUTOEXTEND (7.3), to
avoid running out of extents, we needed to keep an eye on the number of extents
allocated. I guess this habit just kept rolling.

I wonder how many other things we still do out of habit ?

      Rating: 5
How many
      extents are too many.  August 03, 2001
Reviewer:
      Richard  from Toronto, Canada
Other RDBMS have been using bitmaps for space management since the 80s and early
90s.  Having worked with DB2 and its segmented tablespaces and uniform extent
allocations, the number of extents was never an issue.  Exporting and importing
tables just for the sake of singular extents is a wasteful enterprise.  It's
about time the Oracle community caught up with the rest of the database world in
space management methodology.

      Rating:
      5
  October 01, 2001
Reviewer:
      Catherine.  from England, UK
If there was an opportunity to ask questions at ask tom I would but there is
always a backlog.....

Anyway, I have a colleague who insists that when a datafile is created on a new
disk thats not used for anything but Oracle datafiles, then that datafile will
be created as a contiguous chunk of disk. And therefore, seeing as the datafile
is contiguous, if you allocate 1 large extent, then this means that this extent
will be contiguous physically. He explained that if there is contiguous disk
space available, the datafile will be created contiguously.

Opinions and hard facts would be most welcome because like a lot of people I
also believe that it doesn't matter how many extents you have.

          Followup:

and just how many times in your life do you get a virgin disk?  thats not in a
raid set? or using a volume manager?

It is a MOOT discussion.  Even if they (files) were contigous (and IN GENERAL
they are not), we read in chunks.  In between our reads - the disk head will be
sent elsewhere for someone else (unless you are running on virgin disks in
single user mode -- I haven't done that since the late 80's myself -- everything
has been multi-user since then and "disks" generally aren't "disks" anymore --
they are volumes under the control of some software).

In short, the facts are life are:

- lots of extents are OK
- The disk will be fragmented
- it is not something to lose any sleep over.

      Rating: 5
Sarb extents
      does NOT matter  October 02, 2001
Reviewer:
      lsc  from madrid, spain
Sarb number of extents does not matter because in OLTP system most reads are
indexed which means that the actual data is obtained by first reading the index
segment then with the rowid in the leaf the data is obtained, back again to read
index then again data and so on. That is probably why wait events

db_file_scattered_read is referred as table scans (random read)
and
db_file_sequential_read as index scans (sequential read)

So even you have a huge extent instead of 1000 you still do random read, the
head of your hard disk is always moving around from one side to another
A huge extent could benefit in Full Table Scans (but well assuming your extent
is contiguos PHYSICALLY in the disk which is very very rare)
There are two levels of contiguoness, one is OS the other is Oracle, OS is
PHYSICAL, Oracle is logical, from Oracle you see the segments/extents/blocks
(whatever) contiguos but physically they are NOT
The only problem you may have with small extents in dictionary managed
tablespace is that frequent/continuous allocation of extents would lead to
contention of ST enqueue (dictionary contention) a problem which does not arise
in locally managed tablespaces
I remember there was a way to dump the datafile header to a trec file then it is
used to check the physical location of the datafile and you could see that the
datafile had random physical addresses

      Rating: 5
logical volume
      manager  July 03, 2003
Reviewer:  Reader

Tom, within oracle, is there any tool to find where exactly the data for a table
is on which disk especially when we have hundreds of disks in a logical volume
manager? Thanks.

          Followup:

with 9i OEM, there is for some setups.

otherwise, no, not really.

      Rating: 4
Related
      Question  July 03, 2003
Reviewer:  Matt
       from Australia
I think it was James Morle (Scaling Oracle 8i) that highlighted that, for a
given disk it is always better to only use the outside of a disk platter for db
file storage. The premise is that the outside of the disk is furthest away from
the centre of the disk and so spins fastest. As a result disk seek times are
reduced.

I always wondered, how on earth do you manage to specify where on a disk a file
will go.

I've never got to the stage where this has needed to be a consideration.

Does anyone have any comments on how this is achieved?

Best Regards,

          Followup:

today -- with SAN, NAS, 96gig disks (i've heard you can get 300gig disks now
even -- just think, 4disks and you too can run a terabyte data warehouse at
home!), pretty much "not going to be in the realm of possibility"

      Rating:
      5
link?  July 04, 2003
Reviewer:
      Reader
<quote> with 9i OEM, there is for some setups.
otherwise, no, not really. <quote>

Tom, could you direct me to a link to learn how exactly I can do it using oem?
Please find time enjoy 4th of July. No need to reply to me now. We all
appreciate your dedication and you do work 365 days a year. You are truly
amazing.

      Rating: 3
What about
      redundancy and throughput?  July 07, 2003

Reviewer:  Matt  from Australia
[SNIP...]
today -- with SAN, NAS, 96gig disks (i've heard you can get 300gig disks now
even -- just think, 4disks and you too can run a terabyte data warehouse at
home!), pretty much "not going to be in the realm of possibility"
[...SNIP]

I think that J Morle also puts forward the argument for using many smaller disks
rather then fewer larger ones. The more disks the less the likelihood of a
failure and lengthy recovery (I take on board the comments about SAN and NAS). I
guess that 300 Gig disks are fine if they satisfy your I/O requirements and you
have covered the redundancy with your hardware architecture.

          Followup:

Oh yes, 100% -- we have lots to say about RAID levels too. (see BAARF)
http://www.oaktable.net/main.jsp
It is just that nobody does that - they buy whatever is on sale that week and
live with it.  96gig drives seem to be the norm these days.

I probably get a question along the lines of "Ok, I have 4 disks for my 300gig
database, what should I do with them to maximize performance"  ugg.

July 07, 2003 Maryann -- Thanks for the question regarding
      "Duplication of the functionality of an alert -- in d2k", version Forms
4.5

      You Asked
A window containing several items and 2 buttons (ok/cancel) is displayed in a
procedure disperror called from when-validate-item.
Show_window is used to display the window.

The problem is
After the call to show_window, control returns back to when-validate-item before
the user can respond to the window.
Which button the user pressed should be communicated back to which routine
called the procedure disperror

A method is required whereby control cud be paused till the user clicks a button
in window displayed

      and we said...
The problem is that when-validate-item can only use select statments and
unrestricted built-ins.  We would really need to use a GO_ITEM() to naviagate
out of the item we are validating but GO_ITEM is a restricted built-in.

We cannot navigate out of the item we are validating to another item.  You'll
need to use an alert to process this functionality if you want to keep this as
one form.

Another solution is to create a new form with this functionality (the display of
several items and 2 buttons) and use the CALL_FORM() builtin to run the window.
The calling form will suspend execution at the time it calls call_form -- the
called form will execute -- and when it exits, execute will resume from the
point of the CALL_FORM call in the when-validate-item trigger.
Review &
      Followup

      Rating: 3
Go_item
      Fails  July 07, 2003
Reviewer:  sujit
       from India
Dear Tom,
         In forms6i Go_item some times fails. the form does not respond to show
the window in which the item is and go to the specific item. Why that ?
 Regards..
 Sujit

          Followup:

it has been since 1995 since I've done a form, suggest you try

http://otn.oracle.com/
under discussion forums.  You'll really want to develop a reproducible test
case, else people are just going to say "I don't know".  I find when I develop
test cases -- about 1/2 of the time I figure it out (you know, sound of hand
slapping forehead sort of stuff).

A test case will be necessary for something like this else all you'll get is "i
don't know" or "it could be this, or it could be that, or it could be this other
thing"

July 07, 2003 Leonard -- Thanks for the question regarding
      "Chekboxes", version 9.01

      You Asked
I have this web application that includes checkboxes. My problem is how to
program the chekboxes so that when multiple choices are made they are all
submited into the database. In my case only one get submited. Here are the codes

procedure contactlist1 as
begin
htp.prn('
         <html>
      <center><B>WELCOME TO FINNGER SYSTEM</B></center>

     <form action="contactlist2">
     <center><p><H1>Please fill the form below</H1></center>

      <p><b>Please fill the form below to to contact us. The bold fields are
required</b>

    <table width="575" border="0" cellspacing="10" cellpadding="0"
bgcolor="#F3F8FF">

    <tr>

      <td>Department:</td>
      <td colspan="3">

        <select name="p_deptname">

         <option selected>Choose a department</option>
          <option value="customerservice">Customer Service</option>
          <option value="marketing">Marketing</option>
          <option value="technicalsupport">Technical Support</option>
          <option value="webmaster">Webmaster</option>
        </select>
      </td>
    </tr>

   <tr>
      <td valign="top"><b>First name:</b></td>

      <td colspan="3">
        <input type="text" name="p_firstname" size="30" maxlength="30">
      </td>
    </tr>
    <tr>
      <td valign="top"><b>Last name:</b></td>
      <td colspan="3">
        <input type="text" name="p_lastname" size="30" maxlength="30">

      </td>

    </tr>

    <tr>
      <td valign="top"><b>E-mail address:</b></td>
      <td colspan="3">
        <input type="text" name="p_email" size="30" maxlength="55">
      </td>
    </tr>
    <tr>

      <td valign="top">Company name:</td>
      <td colspan="3">
        <input type="text" name="p_companyname" size="30" maxlength="30">

    </td>
    </tr>
    <tr>
      <td valign="top">Address:</td>
      <td colspan="3">
        <input type="text" name="p_address" size="30" maxlength="50">

      </td>
    </tr>
    <tr>
      <td valign="top">City:</td>
      <td colspan="3">

       <input type="text" name="p_city" size="30" maxlength="30">
      </td>
    </tr>
    <tr>

      <td valign="top">State:</td>
      <td colspan="3">
        <input type="text" name="p_state" size="2" maxlength="2">
      </td>
    </tr>
    <tr>

     <td valign="top">Zip:</td>

     <td colspan="3">
        <input type="text" name="p_zip" size="10" maxlength="10">

      </td>
    </tr>
    <tr>
      <td valign="top">&nbsp;</td>
      <td colspan="3">&nbsp;</td>
    </tr>
    <tr>

      <td rowspan="2" valign="top">Industry:</td>
      <td>

      <input type="radio" name="p_industry" value="Accounting">
        Accounting </td>
      <td>
        <input type="radio" name="p_industry" value="IT">
        IT</td>
      <td>
        <input type="radio" name="p_industry" value="Manufacturing">
        Manufacturing </td>

    </tr>
    <tr>

      <td>

        <input type="radio" name="p_industry" value="Retail">
        Retail </td>
      <td>
        <input type="radio" name="p_industry" value="Software">
        Software </td>
      <td>
        <input type="radio" name="p_industry" value="Web">

       Web </td>

    </tr>
    <tr>
      <td valign="top">&nbsp;</td>

    </tr>
    <tr>
      <td valign="top">Company size:</td>
      <td>
        <input type="radio" name="p_companysize" value="Small">

       Less than 50</td>
      <td>
        <input type="radio" name="p_companysize" value="Medium">
        51 to 250</td>
      <td>

        <input type="radio" name="p_companysize" value="Large">
        Over 250</td>
    </tr>
    <tr>
      <td valign="top">&nbsp;</td>

    </tr>
    <tr>
      <td rowspan="5" valign="top">Newsletters:</td>
      <td colspan="3">I want to subscrible to the following newsletters</td>
    </tr>

  <tr>
      <td>
        <input type="checkbox" name="p_newsletters" value="Coldfusion">

        Coldfusion </td>

     <td>
        <input type="checkbox" name="p_newsletters" value="dreamweaver">
        Dreamweaver </td>
      <td>
        <input type="checkbox" name="p_newsletters" value="Fireworks">
        Fireworks </td>
    </tr>

   <tr>

      <td>
        <input type="checkbox" name="p_newsletters" value="flash">

        Flash </td>
      <td>
        <input type="checkbox" name="p_newsletters" value="freehand">
        Free Hand</td>
      <td>
        <input type="checkbox" name="p_newsletters" value="golive">
        Golive </td>
    </tr>

   <tr>
      <td>

        <input type="checkbox" name="p_newsletters" value="illustrator">
        illustrator </td>
      <td>
        <input type="checkbox" name="p_newsletters" value="imageready">
        ImageReady </td>
      <td>
        <input type="checkbox" name="p_newsletters" value="photoshop">

        PhotoShop </td>
    </tr>

   <tr>
      <td valign="top"></td>
      <td colspan="3"></td>
    </tr>
    <tr>
      <td valign="top"><b>Comments:</b></td>

      <td colspan="3">
        <textarea name="p_comments" cols="40"></textarea>
      </td>
      <tr><td><input type="submit" value="Submit"></td></tr>
              <tr><td><input type="reset" value="Reset"></td></tr></table>
           </form>
           ');
end;

PROCEDURE contactlist2
(
  p_deptname varchar2 DEFAULT NULL,
  p_firstname varchar2 DEFAULT NULL,
  p_lastname varchar2 DEFAULT NULL,
  p_email varchar2 DEFAULT NULL,
  p_companyname varchar2 DEFAULT NULL,
  p_address varchar2 DEFAULT NULL,
  p_city varchar2 DEFAULT NULL,
  p_state varchar2 DEFAULT NULL,
  p_zip number DEFAULT NULL,
  p_industry varchar2 DEFAULT NULL,
  p_companysize varchar2 DEFAULT NULL,
  p_newsletters varchar2 DEFAULT NULL,
  p_comments varchar2 DEFAULT NULL
) AS

 BEGIN
if p_firstname IS NOT NULL and p_lastname IS NOT NULL and p_email LIKE '%@%.%' a

nd length(p_email)>=5

then

     insert into reachus(deptname, firstname, lastname, email, companyname,
address, city, state, zip, industry, companysize, newsletters, comments)

     values(p_deptname, p_firstname, p_lastname, p_email, p_companyname,
p_address, p_city, p_state, p_zip, p_industry, p_companysize, p_newsletters,
p_comments);

htp.bodyOpen( cattributes => 'BGCOLOR="#FFFFCC"');
htp.prn('<p><b>This is the Informations you submitted' ||'<br>'||'
Department:'||p_deptname ||'<br>' ||' First Name: '|| p_firstname || '<br>' ||'
Last Name:'|| p_lastname || '.' ||'<br>'||' Email: '|| p_email || '<br>'||'
Comapny Name:'||p_companyname||'<br>'||' Address: '|| p_address ||'<br>'||'
City: '|| p_city ||'<br>'||'State: '|| p_state ||'<br>'||' Zip Code:
'||p_zip||'<br>'||' Industry: '|| p_industry||'<br>'||' Company Size:
'||p_companysize ||'<br>'||' NewsLetters:'|| p_newsletters ||'<br>'||' Comments:
'|| p_comments ||'<br>'|| ' Thank you for your interest in FinNiger'||' <br>'||
p_firstname||' ' || p_lastname||','||'your information have been successfully
registered'|| ' <a href="
Click">http://www.netikka.net/leo">Click
here to Continue</a></b>');

htp.prn('<META HTTP-EQUIV="REFRESH" CONTENT="5; URL=
'">http://www.netikka.net/leo/my_protfolio.html">'
;

else
htp.prn('<p><center><b>Please you have entered your information incorrectly.<br>

Please <a href="contactlist1">Click here</a> to try again</b></center>');

 end if;

end;

Thanks Tom for your help. Your expert one-on-one is too good to be good.

      and we said...
couple of things

1) suggest you only use varchar2 for datatypes in parameter lists from the web
-- what if someone puts in ABC for the zip code?  procedure bombs before you can
deal with it.

2) use PACKAGES!!!!

3) consider putting drop down list values, radio box values, checkbox values in
lookup tables instead of just inline like that

Now, how to accomplish your goal.  using a package setup like this:

create or replace package demo_pkg
as
    type array is table of varchar2(4000) index by binary_integer;
    empty array;

    procedure contactlist1;

   PROCEDURE contactlist2
   ( p_deptname varchar2 DEFAULT NULL,
     p_firstname varchar2 DEFAULT NULL,
     p_lastname varchar2 DEFAULT NULL,
     p_email varchar2 DEFAULT NULL,
     p_companyname varchar2 DEFAULT NULL,
     p_address varchar2 DEFAULT NULL,
     p_city varchar2 DEFAULT NULL,
     p_state varchar2 DEFAULT NULL,
     p_zip varchar2 DEFAULT NULL,
     p_industry varchar2 DEFAULT NULL,
     p_companysize varchar2 DEFAULT NULL,
     p_newsletters array DEFAULT empty,
     p_comments varchar2 DEFAULT NULL) ;

end;
/

Then, your body becomes:

create or replace package body demo_pkg
as

procedure contactlist1 as
begin
htp.prn('.............
           ');
end contactlist1;

PROCEDURE contactlist2
( p_deptname varchar2 DEFAULT NULL,
  p_firstname varchar2 DEFAULT NULL,
  p_lastname varchar2 DEFAULT NULL,
  p_email varchar2 DEFAULT NULL,
  p_companyname varchar2 DEFAULT NULL,
  p_address varchar2 DEFAULT NULL,
  p_city varchar2 DEFAULT NULL,
  p_state varchar2 DEFAULT NULL,
  p_zip varchar2 DEFAULT NULL,
  p_industry varchar2 DEFAULT NULL,
  p_companysize varchar2 DEFAULT NULL,
  p_newsletters array DEFAULT empty,
  p_comments varchar2 DEFAULT NULL)
AS
begin
    htp.p( 'deptno = ' || p_deptname || '<br>' );
    htp.p( 'firstname = ' || p_firstname || '<br>' );
    htp.p( 'lastname = ' || p_lastname || '<br>' );
    htp.p( 'email = ' || p_email || '<br>' );
    htp.p( 'companyname = ' || p_companyname || '<br>' );
    htp.p( 'address = ' || p_address || '<br>' );
    htp.p( 'city = ' || p_city || '<br>' );
    htp.p( 'state = ' || p_state || '<br>' );
    htp.p( 'zip = ' || p_zip || '<br>' );
    htp.p( 'industry = ' || p_industry || '<br>' );
    htp.p( 'companysize = ' || p_companysize || '<br>' );

    for i in 1 .. p_newsletters.count
    loop
        htp.bold( 'newsletters(' || i || ')=' || p_newsletters(i) || '<br>' );
    end loop;

    htp.p( 'comments = ' || p_comments || '<br>' );
end;

end;
/

and that is it.
Review & Followup

      Rating:
      5
Checkboxes  July 07, 2003
Reviewer:
      Leonard Anukam  from Finland
Tom thanks for this correction, and the correct way of doing this checkbox thing
that you have showed. It has been more than helpful, and has ended my headche
and solve my problem.

Thanks again and you are really the god of Oracle.

      Rating:
      5
  July 07, 2003
Reviewer:  Fan

Tom,
Since the question said right at the end "Thanks Tom for your help. Your expert
one-on-one is too good to be good"

your reply was pretty good, else you would have said..i can't read such long
code !! ;)

Cheers!

July 07, 2003 Jack -- Thanks for the question regarding "SID
      and SERVICE_NAMES", version 8.1.7

      You Asked
What is the primary difference between ORACLE SID and SERVICE_NAMES ?

What was the motivation for 8i to use SERVICE_NAMES ?

What is the primary naming convention for identifying SERVICE_NAMES parameter
for 8i?
Documentation gives example of:

    service_name=sales.us.acme.com   [ this looks like a DNS name ]

Can you provide some clarification?

thank you.

      and we said...
http://technet.oracle.com/doc/server.815/a68020/ch2.htm#140388
it explains it.

Basically, a

sid+oracle_home+hostname = a database instance. use this and you get connected
to specific instance

service=collection of equivalent instances.  used most often in a clustering
environment but has applicability in single instance database configurations as
well.

The service name generally does look like a "hostname"

July 07, 2003 Jagan -- Thanks for the question regarding "Locks
      & Latches", version 8.0.6

      You Asked
Hi Tom

Can u explain me what is the difference between Locks,Latches,Enquees and
Semaphores with an example?

Thanks
Jagan

      and we said...
latches and enqueues are both types of locks.

latches are lightweight serialization devices.  we try to get a latch, spin for
a bit and try again.  so when getting a latch, we try and try and try -- we are
not told that the latch is available, we keep trying to get it (eg: not
necessary a first come, first serve lock).  we use latches to serialize access
to in memory data structures typically (like SGA data structures)

enqueues are heavyweight serialization devices.  if we cannot get an enqueue, we
"go to sleep" and when the enqueue is available -- we are told about it in a
first come, first serve manner.  We use enqueues to perform row level locking
for example.

A semaphore is an operating system supplied serialization device that one might
use to implemented latching or enqueues.

So, latches and enqueues are types of locks, semaphores a programming device one
might use to implement latching and enqueuing.
Review &
      Followup

      Rating: 4
2 processes
      spinning at the same time  July 07, 2003
Reviewer:
      Wim Jans  from Antwerp, Belgium
Hi Tom,

Thanks for the great and useful information you provide to the Oracle community.

I'm wondering what happens when 2 processes/threads are spinning for the same
latch and the latch is freed.
How does Oracle guarantuee that the 2 spinning processes don't reserve the freed
latch at the same time.

          Followup:

using instructions like a "test and set" or some such thing -- chips and
operating systems provide us the mutual exclusion devices we need to do these
atomic operations, fast.

so the answer is very much OS dependent -- many chipsets provide a "test and
set" or equivalent instruction.

July 08, 2003 Diana -- Thanks for the question regarding
      "calculations based on a group and using the grouped value in the
      calculation", version 7.3

      You Asked
I have a table that contains a column of numeric values.
dept    dept_group      amount
----    ---------      ------
 1        a             10
 1        a             13
 1        b             11
 1        b             23
 1        b             33
 2        c             45
 2        c             55
 2        c             23

I need to calculate an avg amount for each department group,and the percentage
that each group represents of that avg

need a following report
 dept     dept_group      amount       amount/group_avg * 100
------    ----------      ------       -------------
 1        a                10             86.9%
 1        a                13             113.0%
                       group 1a avg=11.5
 1        b                11             49.3%
 1        b                23             103.1%
 1        b                33             147.9%
                       group 1b avg=22.3
 2        c                45             109.8%
 2        c                55             134.1%
 2        c                23             56.1%
                       group 2c avg=41

So for group 1a the amount/group_avg * 100
calculation = 10/11.5*100 and so on...

Can this calculation be achieved with 1 select statement?
We are currently using Oracle 7.3 but will shortly be switching to Oracle 8i.
So if functionality of 7.3 is not sufficient, please provide the solution (if
there is one) in 8i.

Thank you

      and we said...
The first query works in all releases, the second in Oracle8i release 2 EE and
up:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select t.dept,
  2             t.dept_group,
  3             t.amount,
  4             group_avg,
  5             round(100*t.amount/b.group_avg,2) pctavg
  6    from t, ( select dept, dept_group, round(avg(amount),2) group_avg
  7                from t
  8               group by dept, dept_group ) b
  9   where t.dept = b.dept
 10     and t.dept_group = b.dept_group
 11  /

      DEPT D     AMOUNT  GROUP_AVG     PCTAVG
---------- - ---------- ---------- ----------
         1 a         10       11.5      86.96
         1 a         13       11.5     113.04
         1 b         11      22.33      49.26
         1 b         23      22.33        103
         1 b         33      22.33     147.78
         2 c         45         41     109.76
         2 c         55         41     134.15
         2 c         23         41       56.1

8 rows selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select dept,
  2             dept_group,
  3             amount,
  4             round(avg(amount) over ( partition by dept, dept_group ), 2 )
group_avg,
  5             round(100*amount/(avg(amount) over ( partition by dept,
dept_group )),2) pctavg
  6    from t
  7  /

      DEPT D     AMOUNT  GROUP_AVG     PCTAVG
---------- - ---------- ---------- ----------
         1 a         10       11.5      86.96
         1 a         13       11.5     113.04
         1 b         11      22.33      49.25
         1 b         23      22.33     102.99
         1 b         33      22.33     147.76
         2 c         45         41     109.76
         2 c         55         41     134.15
         2 c         23         41       56.1

8 rows selected.

Review &
      Followup

      Rating:
      5
Great!!  May 06, 2002
Reviewer:
      Ashok

      Rating: 5
The group rows
      also have to appear as part of the result set  May 08, 2002

Reviewer:  Diana Bokser  from USA
Thank you this is wonderful,
I also need the group rows to appear as part of the result set. Is that
possible?

          Followup:

"group rows" -- what are those?

      Rating:
      4
Reader  May 08, 2002
Reviewer:
      Dmitriy  from Brooklyn, NY
For the second query maybe it is better to use in-line query insted of use over
clause twice:
select dept, dept_group, amount, group_avg
       , (100 * amount/group_avg) pctavg
  from (select dept
               ,dept_group
               ,amount
               ,round(avg(amount) over ( partition by       dept,dept_group ), 2
) group_avg
  from t)

What do you think, does it make any sence? Thank you.

          Followup:

it should be 6 one way, 1/2 dozen the other -- they use the same
partition/range/order.

      Rating: 4
Percentage
      & average  July 08, 2003
Reviewer:
      Mohd.yasin  from singpore
i using oracle 8.1.6.0.0 and forms 6i . the below query i try to execute but i
get error msg is
ORA-00439: feature not enabled: OLAP Window Functions
how to prevent .

eg
select req.id,req.rptyp_id,
to_char(rps.tran_date,'j')-to_char(req.request_date,'j') no_of_days,
round(to_char(rps.tran_date,'j')-to_char(req.request_date,'j') over (partition
by req.id,req.rptyp_id),2) gavg,
round(100*to_char(rps.tran_date,'j')-to_char(req.request_date,'j')/avg(to_char(rp
s.tran_date,'j')-to_char(req.request_date,'j') over (partition by
req.id,req.rptyp_id)),2) %
from report_requests req,report_request_stages rps
where  req.id =rps.rprq_id
and   rps.rpstg_id = '08'
and to_char(req.request_date,'MMYYYY') = '072003'

          Followup:

in 8i, the analytic functions are not part of SE.  You must have standard
edition

July 08, 2003 gvsrk -- Thanks for the question regarding
      "surrogate Key", version 8.07

      You Asked
Tom,

  we have application requirement like this.the master table is Busstop having
columns stopid,onstreet,crossstreet,direction.Stopid is primary key.Stopid
Automatically genarating in application (from GUI)based on select of the
onstreet,corssstreet,direction.
This stopid referenced in another 4 tables for stop details.The application is
brand new and we are doing data loading and maintenance.If the stopid is wrong,
i need to delete cascade or disable constraints and update( Personally i do not
like) or create correct id and assosiate dependent records to correct Id and
delete wrong ID.
It is not on going process.Once the Busstops are colected and corrected from
then it is kind of maintenance.
We have debate in our group to put a sequence as primary key for this table and
reference rather than stopid to make the update easy.
I am not inclind to this Idea to change the whole meaning of PK with out
nothing.
This is not a composite PK and not referenced in too many tables and the updates
are not on regular basis.,Then why should i create a surrogate key ?

      and we said...
If the primary key has a chance -- however remote -- of changing, well, then it
is NOT the primary key.

This would be like you changing your DNA halfway through your life.

You are not changing the concept of a primary key here -- your current set of
attributes just don't match what a primary key is.

The autogenerated "key" is something needs to be unique, but it is not the
primary key.

I would use a surrogate key here -- I almost always use surrogates personally.
Review &
      Followup

      Rating: 3
What is a
      surrogate key ?  December 03, 2002
Reviewer:
      jwc
I have heard this term in a lot of places.

what is a surrogate key

          Followup:

Main Entry: 1sur·ro·gate

Pronunciation: 's&r-&-"gAt, 's&-r&-

Function: transitive verb

Inflected Form(s): -gat·ed; -gat·ing

Etymology: Latin surrogatus, past participle of surrogare to choose in place of
another, substitute, from sub- + rogare to ask -- more at RIGHT
Date: 1533
: to put in the place of another: a : to appoint as successor, deputy, or
substitute for oneself b : SUBSTITUTE

it is a substitute primary key -- for when you don't have a real one, as above.

      Rating: 5
Don't
      joke  December 04, 2002
Reviewer:  Martin
       from UK
"This would be like you changing your DNA halfway through your life."

The way technology is going... we're not far off making this possible! ;-)

      Rating:
      3
  December 04, 2002
Reviewer:  Sagi
       from India
Hi Tom,

You said that Surrogate Key is a substitute for Primary key.

Do you mean to say that we have a column that is populated with SEQUENCE
automatically, which is called Surrogate Key (Technical Term)?

I would appreciate if you could through some more detail on this....please.

Thanx in advance.

Regards,
Sagi

          Followup:

an ID column populated via a sequence is known as a surrogate key.

if the table has a true primary key -- it is known as a "natural key"

      Rating: 5
Performance of
      surrogate vs natural  January 24, 2003
Reviewer:
      Reader  from Oshkosh, WI, USA
We have some consultants here now who are saying that "good warehouse design
practice is to use a 4-byte surrogate value instead of a business key value for
keys." They are saying that query performance is compromised when joins are
performed on the longer business key fields, and that overall performance is
affected by the resulting longer fact tables since more database pages must be
read to process a result set. Your comments, please.

          Followup:

ask them what a 4 byte surrogate value is in Oracle please.

I'd be REALLY interested to here their response.  Then, when they say "integer",
we can have a laugh as we show them the door....

      Rating: 5
Re: Performance
      of surrogate vs natural   January 24, 2003

Reviewer:  Reader  from Oshkosh, WI, USA
Yes, I had a laugh at that, too. But are the other points valid, presuming the
use of a sequence as the surrogate?

          Followup:

not really but a little.

An index on N bytes will be smaller then an index on (N+M) bytes of data.

So, if you have a compound key that is on average 25 bytes and can replace it
with a number (which varies in length from 0..22 bytes and whose avg would be
smaller then 25 bytes by alot), then

o you cache more index blocks
o you compare smaller strings

It is a very common practice to use surrogate keys, yes.  Sort of funny they
would want to go with a 4 byte integer (in a database that has no such thing)
since that would limit them to a set of 4billion rows which in a data warehouse
is pushing it.  Especially with incremental loads and stuff. Do you really want
to be limited to

a) 4 billion
b) having to play "keeper of the keys" cause you cannot just use an increasing
number -- you'll have to reuse values with such a small namespace.

      Rating: 4
Surrogate
      keys...  July 07, 2003
Reviewer:  Kashif
       from Reston, VA
Hi Tom,

Since performance becomes the de facto standard in many cases for the success of
a warehouse, why would someone NOT use surrogate keys? Secondly, are surrogate
keys the same as "factless" keys?

Thanks.

Kashif

          Followup:

generation of surrogate keys and mapping them back and forth during a load will
not make it faster to load.

generation of surrogate keys won't necessarily make queries any faster either --
if the natural key is not "huge"

I'd have to see the text book definition of "factless" key.

      Rating: 3
SKs: Is not
      only about performance  July 08, 2003
Reviewer:
      Juanje  from Madrid,SPAIN
Hi,

    well, the use of SKs in Datawarehouse environments is not only simplicity of
star design or performance. You often need to populate your dimensions with
extra records not found in the source table, such as "UNKNOWN" or "NOT APPLIES"
records. If you don't use SKs, What PK value would you use for this extra
records? How can you be sure that the values you choose won't collide with
future records from the source table? Also, taking your dimensional schemas away
from possible source key changes is a good idea. Of course creating and
maintaining SKs adds complexity and is extra work for ETL processes.

          Followup:

I think we are in agreement

July 08, 2003 Ramasamy -- Thanks for the question regarding
      "Application tuning topics.", version 8.1.7

      You Asked
When anyone involved in application tuning ,  i assume the
following are some of the areas to look at and work on.

1.QUERY Tuning:
2.Processing Logic Tuning:
3.Implementing new features and new ideas in tuning:
4.Avoiding Redundant table joins and
  Redundant join conditions.Also Avoiding redundant computations.
5.Order by clause and the effect in performance.
  (making sure order by is needed or not needed and the level of
   order by clause when we have a big query with n no of sub-selects)
6.deterministic Vs non-deterministic functions.(From your site).
  (For function based index)

Could you suggest some of the other topics (Only topics ,not in detail) to
consider when anyone involved in application tuning not
database tuning. I am asking this question in terms of developers
perspective.

      and we said...
you missed step 0

0) design and physical schema implementation

4, 5, 6 all seem to be variations of 1.

2) is more relevant even then query tuning, query tuning is easy -- implementing
efficient algorithms is hard.

It is all about building the system to efficiently answer the questions asked of
it (design, physical schema).

After that it is all about implementing such a system -- where my mantra is:

   1) if you can do it set based in a single SQL statement, do it
   2) if not, use as little PLSQL as possible to do it (using BULK operations
      when appropriate)
   3) if plsql is not "functional" enough -- use as little java as possible
   4) when you find java to be too slow, drop down to a tiny bit of C

but it starts with physical schemas and design of your application.  Have you
ever used an IOT? cluster? hash cluster? if not -- why not....
Review &
      Followup

      Rating: 5
This could help
      too...  August 29, 2003
Reviewer:  JFC
       from Dominican Republic
http://otn.oracle.com/oramag/webcolumns/2003/opinion/holdsworth_opcol.html

July 08, 2003 Venkatasubramanian -- Thanks for the question
      regarding "clob", version 8.1.7

      You Asked
Tom,

Which is the best way to store large data in a column ( around 27KB)? CLOB is
okay? We like to query and do dml operations on these  records? How best we can
do that? What are the limitations? What are the other relevant factors to be
considered?

Thanks and Regards,
Venkat

      and we said...
if you have more then 4,000 bytes to store, and the data is TEXT -- clob is the
only datatype to consider.

If it is binary, BLOB is the only datatype to consider.

You want to read the application developers guide for LOBS:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/index.htm
to get a full understanding of the implementation, how to access them, how to
perform dml on them and all.
July
      08, 2003 -- Thanks for the question
      regarding "statspack/tkprof", version 8.1.7

      You Asked
This might be a lame question.  How is STATSPACK different from TKPROF?

      and we said...
If you have my book -- I go through both in detail.

statspack is good for diagnosing "instance level" stuff -- what are the top
SQL's, what are the major system waits, what is my overall soft parse ratio, how
many times do I commit or rollback and so forth.  Things at the SYSTEM level.

tkprof is for getting information at the program execution level.  When you run
your program, it'll generate a trace file - and tkprof will format that trace
file to show you the SQL you ran, what its execution path was, how much time
each on took and so on. tkprof is for tuning a program.
Review &
      Followup

      Rating:
      4
  July 10, 2002
Reviewer:
      M.S.Murugupandiyan  from India
Now only i come to know about the statspack/tkprof.
Thanks

      Rating: 5
tkprof or
      statspack  July 08, 2003
Reviewer:
      A reader
Tom,
We are moving our Oracle 9 DB from an IBM box to a linux box.  The EJB
application we have writes the start time  and end time of processing of certain
queries at the db level and application server level(time taken at each level).
Our DBA has asked us these numbers before we move to the linux box.  The DBA
will be using these processing times as baseline times to solve any problems
that may come up in the linux box.

My questions
1) Will statspack and tkprof help us in any way in diagonising problems that may
come up after moving to Linux?  What tuning/diagonistic method is appropriate in
this situation?

Thank you

          Followup:

well, an IBM box can be a linux box ;)

1) statspack and tkprof will be very helpful yes.

if you have the tkprofs of your application before and after...

if you have statspack from before and after....

you have a baseline upon which to compare.

July 08, 2003 Krishnamoorthy -- Thanks for the question
      regarding "System Date Change", version 9.2.0.1.0

      You Asked
Hi Tom,
Thank you Very much for your reply for my previous questions.

1. When i restart the databse it says could not recognize date format. Before
shutting down i changed few times the system date.
Then I created spfile and started the instance.What could be the reason. Why the
date format cant be recognized.

2. In oracle 8i,for snapshot too old error , the transaction will be assigned to
a bigger rollback segment. In oracle 9i we didnt have the rollback segments and
we have the concept of undo tablespace how do we resolve the issue?..

3. I am working for a data conversion project. I migrate few millions of records
per the source to destination with some derivations, value transformations and
default values. To know how many records inserted
i commit records in uniform intervals. I remember there is one more way of doing
it. Write the progress into some system tables and read the system table for
progress,that is without commiting the records. Which one is advisable?..

Regards
Sankar

      and we said...
1) when I tried to start my car this morning, it wouldn't start.  before
shutting down last night, I pulled at some wires and cables in the engine.  Why
didn't my car start?

without you telling me what you actually did, what error messages or codes you
observed, or anything - i cannot say anything sensible.  You would need to tell
me exactly what you did, what the errors where and what you did to actually
correct it.

2) umm, you have the concept of snapshot too old very wrong.  In 8i (9i, 6, 7,
8, whatever) the ora-1555 happened when your long running query needed rollback
(undo) data that no long exists, it was overwritten.  You are READING data --
you are not actually generating undo, you are not "assigned" to a rollback
segment.

The 1555 happens because your rollback segments are too small and the DBA must
make them permanent larger (eg: using minextents and not using optimal)

In 9i, you still have rollback segments -- you just have a choice

choice 1) You will create and size and manage rollback segments as you always
have.  If you hit ora-1555, you will extend your rollback segments.  If you have
contention on rbs, you will add more.  You will do it all

choice 2) You will specify an undo_retention period that is longer then your
longest running query and you will have sufficient disk to contain that much
undo.  Oracle will create, size and manage rollback segments.  If you need to
extend them, it will do it.  If you need more of them, it will do it.

You still have rollback segments regardless in 9i -- it is either that YOU
manage them or Oracle does.

3) I would use dbms_application_info.set_session_longops throughout my code to
allow others to monitor my progress.

If you search this site for

dbms_application_info.set_session_longops

you'll get to see some examples and other discussions on this
Review &
      Followup

      Rating:
      3
  July 08, 2003
Reviewer:  Sankar

Really useful to sort out the issues about rollback segments

      Rating: 4
Autonomous
      Transactions can come into action  July 08, 2003

Reviewer:  Avinash Shivani  from INDIA
Dear Tom,

I'm your big fan and always keep expert one-on-one by my side for very apt
answers to difficult problems.

As for the 3rd question by Krishnamoorthy, I think one can use autonomous
transactions to solve this problem. The progress value can be inserted in a
table in an autonomous transaction.

Please give ur views on this suggestion....

Regards,

Avinash

          Followup:

yes you could but the advantage of using session longops is

a) no commits/extra work needed (we already wrote the code)
b) tools like OEM are "v$session_longops" aware -- they are not
your_schema.your_table aware.

July 08, 2003 CP
      -- Thanks for the question regarding "Manual database startup", version
      9.0.1

      You Asked
Hi Tom,
I received your book, its really a great stuff for dba (one must have).  I am
regular reader of this site and learnt a lot.

I could not connect to the idle instance (WIN2000 server) when the its not
started.

Scenario

SQL*Plus: Release 9.0.1.0.1 - Production on Sat Jun 22 20:58:37 2002

(c) Copyright 2001 Oracle Corporation.  All rights reserved.

(db is already started from NT services)

SQL> connect / as sysdba;
connected.
SQL> shutdown
.....
(db shut down)  (OK)
SQL> startup
.....
....
(db started)     (OK)

SQL>

(Now i shutdown the instance from NT service)

SQL> disconnect
SQL> connect / as sysdba;
ERROR:
ORA-24314: service handle not initialized

SQL> connect sys/dba as sysdba;
ERROR:
ORA-01017: invalid username/password; logon denied

I am in the ORA_DBA group of Win NT and have started as
administrator.

Its a production database.  I feel, i am really locked out.  If some day the
database does not start auto by NT service, i will not be able to mount the db
for any maintenance or recovery.

Thanks.

      and we said...
The SERVICE must be running in NT.  If not, there is "nothing to start".

So, start the service.  In most cases, people have the service setup to
autostart the database (and hence that'll start the database).  In other cases,
they do not -- and so the service will just start and then you use sqlplus to
startup the database afterwards.

If the service ain't there -- the database isn't either.

Just use

net start <oracle service name>

on the command line.
Review & Followup

      Rating: 4
Mannual db
      start  June 23, 2002
Reviewer:  CP Bhattarai
       from Nepal
Thanks for your prompt reply.

The db is setup to be started by NT service automatically.

What i understand from your reply is that even when the service could not start
db, service will start and i will be able to connect to the instance from SQL
Plus and mount (or start) the db.

But what about the Windows 98 and personal oracle 9.0.1.  There is no service.
I have to be able to start from SQL Plus directly.  I am facing the same problem
here.

          Followup:

Yes -- that is accurate.

Never touched windows 98 (forced to use windows nt/2000 at times), I don't know
the particulars on that platform.  Maybe someone else out there using it does.

      Rating: 5
applied patch
      8174... getting errors  July 06, 2003
Reviewer:
      Alvin  from Philippines
I installed oracle patch 8174 on our dev db and initially i got

1. linking problem with this...
/export/spare/app/oracle/product/8.1.7/lib64/ldflags
what i did was create a lib64 directory and proceded

2. Then i got another error with this file ins_rdbms.mk
it won't continue when i retried.

So i check the production server and saw the developmental servers ins_rdbms.mk
filesize is

21229 May  2  2002 ins_rdbms.mk

which was different from the production server and it's standby db's

17456 Jul  7 09:18 ins_rdbms.mk

I told myself mabye the file got corrupted or something. I stopped the
installation and renamed the former and copied the later file and re-ran the
installation.

Error still persisted so this time i opted to ignore the error.

Tried starting and i got below

/export/spare/app/oracle/product/8.1.7-> sqlplus

SQL*Plus: Release 8.1.7.0.0 - Production on Mon Jul 7 09:40:29 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Enter user-name: internal
ld.so.1: oraclertbe: fatal:
/export/spare/app/oracle/product/8.1.7/lib/libskgxp8.so: wrong ELF class:
ELFCLASS32
ERROR:
ORA-24314: service handle not initialized

/export/spare/app/oracle/product/8.1.7-> svrmgrl

Oracle Server Manager Release 3.1.7.0.0 - Production

Copyright (c) 1997, 1999, Oracle Corporation.  All Rights Reserved.

ld.so.1: oraclertbe: fatal:
/export/spare/app/oracle/product/8.1.7/lib/libskgxp8.so: wrong ELF class:
ELFCLASS32
ld.so.1: oraclertbe: fatal:
/export/spare/app/oracle/product/8.1.7/lib/libskgxp8.so: wrong ELF class:
ELFCLASS32
ORA-12547: TNS:lost contact
SVRMGR> exit
ld.so.1: oraclertbe: fatal:
/export/spare/app/oracle/product/8.1.7/lib/libskgxp8.so: wrong ELF class:
ELFCLASS32
ld.so.1: oraclertbe: fatal:
/export/spare/app/oracle/product/8.1.7/lib/libskgxp8.so: wrong ELF class:
ELFCLASS32
Server Manager complete.

then i happen to read the patch guide step 6.

If you have previously installed Oracle Label Security then add object files to
library. This step is necessary because the installation of this patch set
removes Oracle Label Security object files from RDBMS library.

Link in ols .o files using the following steps:
cd $ORACLE_HOME/lbac/lib
make -f lbac.mk xkzl
make -f lbac.mk ilbac
cd $ORACLE_HOME/rdbms/lib
make -f ins_rdbms.mk ioracle

the last line seemed to be related to the above error so i ran it...

make -f ins_rdbms.mk ioracle

*** Error code 1
make: Fatal error: Command failed for target
`/export/spare/app/oracle/product/8.1.7/rdbms/lib/oracle'

Now i think i'm doomed.

          Followup:

No, you are not doomed, you just forgot that "support" does "support".

they will ask you for more information (eg: looks like you are mixing 32 and 64
bit stuff there -- that error has nothing to do with OLS at all -- you are not
even getting the programs loaded in the first place) and help you correct this.

      Rating: 5
re ran the
      installation   July 06, 2003
Reviewer:
      Alvin  from Philippines
I got this from the installation logs.

Calling action unixActions1.7.1.8.0  make
        installMakePath = /usr/ccs/bin/make
        installMakeFileName =
/export/spare/app/oracle/product/8.1.7/rdbms/lib/ins_rdbms.mk
        installTarget = ctx_on
        installArguments = ORACLE_HOME=/export/spare/app/oracle/product/8.1.7,
        undoMakeFileName =
/export/spare/app/oracle/product/8.1.7/rdbms/lib/ins_rdbms.mk
        undoTarget = ctx_off
        undoArguments = ORACLE_HOME=/export/spare/app/oracle/product/8.1.7,
        logFile = /export/spare/app/oracle/product/8.1.7/install/make.log

Exception thrown from action: make
Exception Name: MakefileException
Exception String: Error in invoking target ctx_on of makefile
/export/spare/app/oracle/product/8.1.7/rdbms/lib/i
ns_rdbms.mk
Exception Severity: 1
Exception handling set to prompt user with options to  RETRY  IGNORE
User choice : Retry
Exception thrown from action: make
Exception Name: MakefileException
Exception String: Error in invoking target ctx_on of makefile
/export/spare/app/oracle/product/8.1.7/rdbms/lib/i
ns_rdbms.mk
Exception Severity: 1
*** Cancel Dialog:  ***
User selected: Stop installation of all products.

      Rating: 5
GUI
      installation error  July 06, 2003
Reviewer:
      Alvin  from Philippines
I'm stuck exactly at this point.

Error invoking target ctx_on of makefile
/export/spare/app/oracle/product/8.1.7/rdbms/lib/ins_rdbms.mk

          Followup:

and I cannot imagine why you haven't opened a tar with support -- I am but one
person, I sleep sometimes.  I travel lots.  I don't install on every OS, I don't
upgrade on every OS, heck -- I don't even know what OS you are on.

This is a clear support case.

      Rating:
      5
32-64.  July 07, 2003
Reviewer:
      Alvin  from Philippines
My SA told me that oracle is in 32 bit, the patch that i applied was for 64 bit.
What i did was just reinstall oracle 81700 files since it will deinstall some of
the modification by the patches and the db started.

How would i know if my oracle is 32 or 64 bit ? the box just says
'Oracle Database 8i Release 3 (8.1.7)
CD Pack
for Sun SPARC Solaris
NOV-00 Release'

I'm truly sorry for being a nuisance. In order to get support i would need my
CSI number right ? and to get a CSI number i would need to pay for support ?

          Followup:

on systems where there are 32 and 64 bit codelines, the server banner (what you
see when you fire up sqlplus) will say "64 bit" if it is 64 bit.  It'll say
"nothing" else.

$ file $ORACLE_HOME/bin/oracle

would tell you as well.

Yes, in order to get support, you have to pay for support as part of your
license.

      Rating: 4
Manually
      Starting Up the Database  July 08, 2003
Reviewer:
      Brian  from Phoenix
For development purposes (learn DBA stuff), I have installed Oracle 9.2 (not the
64 bit version) on an old Pentium II (350) box of mine (128m RAM) running
WIN2000.  I also use the box actively on the internet for media purposes since I
don't need alot of power.  And I use Excel for take home work:(

My question is:

* besides going into services on WIN2000 and shutting down
  all the Oracle processes and setting them to manual start
  and changing my preference from and to background process
  optimization, is there any other methods to handle these
  routine switches back and forth?

* what's the best manual start-up sequence for Oracle
  services?

* could this be done better using Linux with Star Office?
  If so, what Linux distribution?  I had problems with
  Redhat 6 recognizing my video card back in the day when I
  used the box for playing too much Everquest with dual
  boot running a voodoo 3 3000 card (that dinosaur).

Thanks

          Followup:

well, just set the services to manual and leave the processing
"forground/background" stuff on background (if you are not running background
stuff, won't really affect you as far as I could tell when I used windows) - you
can just pop open a dos window to do a "net start ...." command to startup the
database and a net stop to shutdown...

I always left them manual on my demo machines and just did a net start.
listener first, database second.

"better" is in the eye of the beholder.  Me, I've grown to like having linux
running so much (i use crossover office and run word/excel/ppt -- have to,
publishers work in "word") that using windows would be more painful then digging
up drivers.

But -- since you can get pretty "awesome machines" for way under 1,000 these
days -- that might be a consideration as well -- 128meg isn't going to go very
far.

      Rating:
      5
Thanks  July 08, 2003
Reviewer:
      Brian  from Phoenix
Thanks.

I'm waiting for all the new 64 bit arch. to come out from AMD, so I can build
some cheap 32 bit boxes to learn how to manage a multi-database set-up, rather
than a client server set-up.  I expect prices to drop this fall on 32 bit chips.

I would like to do the above in both linux as well as 2000. I can't afford to
bring any mainframe stuff into my place to experiment with though.

Thanks for your help.

July 08, 2003 Sharath -- Thanks for the question regarding
      "selecting third row always-- Analytic functions ", version 9.1.2

      You Asked
Dear Tom,
q1.I have q on "Selecting the third row always."
My requirement is to get 5 rows based on a condition and off that I have to
select the third row always suppose I have

ED            Fr
----------------------
ED Tier 1    8%
ED Tier 2    9%
ED Tier 3    10%
ED Tier 4    11%
ED Tier 5    12%
ED Tier 6    16%
ED Tier 7    19%
ED Tier 8    21%
ED Tier 9    30%

Case1) ED is ED Tier 5 then I have to select
ED Tier 4,ED Tier 3 (two rows above)
and
ED Tier 6,ED Tier 7 (two rows below)
so,

ED Tier 3    10%
ED Tier 4    11%
ED Tier 5    12%
ED Tier 6    16%
ED Tier 7    19%

I have to display fr% = 12%

Case2) ED is ED Tier 1 then I have to select
ED Tier 1    8%
ED Tier 2    9%
ED Tier 3    10%
ED Tier 4    11%
ED Tier 5    12%

I have to display fr% = 10%

Case3) ED is ED Tier 9 then I have to select
ED Tier 5    12%
ED Tier 6    16%
ED Tier 7    19%
ED Tier 8    21%
ED Tier 9    30%

I have to display fr% = 19%

Hope this will help. Thanks for the Patience and answer

What would be the best approach for this RANK, DENSE_RANK or TABLE or ROWNUM or
your suggestion.
This has to be a query only and cant be written in a PL/SQL Block

q2. Are analytic functions performance degraders?
TIA.

Thanks and Warm Regards,
Sharath

      and we said...
assuming ED is sortable (it must be, there is not any other way you could claim
there is a "row 3" or anything like that) and ED is unique -- the following will
do it.  I just used a number for ED -- but remember ED must be sortable (or
there is no such thing as "row 3"

ops$tkyte@ORA9201> create table t ( ed int primary key, fr number );
Table created.

ops$tkyte@ORA9201> insert into t
  2  select rownum, 7+rownum
  3    from all_objects
  4   where rownum <= 9;
9 rows created.

i used 9 rows to start because I wanted to test the boundary value
conditions)

ops$tkyte@ORA9201> analyze table t compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;
Table analyzed.

ops$tkyte@ORA9201> variable n number
ops$tkyte@ORA9201> exec :n := 5

PL/SQL procedure successfully completed.

ops$tkyte@ORA9201> select ed, fr, max(decode(rn,3,fr)) over () third_fr
  2    from (
  3  select a.*, row_number() over (order by ed) rn, :n
  4    from (
  5  select a.*, max(decode(cnt,2,r)) over () max_r
  6    from (
  7  select a.*, count(*) over(partition by r) cnt
  8    from
  9    (
 10  select a.* , rownum -1 r
 11    from ( select /*+ FIRST_ROWS */ ed, fr
 12             from t
 13            where ed >= :n
 14            order by ed ) a
 15   where rownum <= 5
 16   union all
 17  select a.* , rownum r
 18    from ( select /*+ FIRST_ROWS */ ed, fr
 19             from t
 20            where ed < :n
 21            order by ed desc ) a
 22   where rownum <= 5
 23   ) a
 24   ) a
 25   ) a
 26   where r <= decode( max_r, 1, 3, 2, 2, 3, 2, 4, 2, null, 4 )
 27   ) a
 28   order by 1
 29  /

        ED         FR   THIRD_FR
---------- ---------- ----------
         3         10         12
         4         11         12
         5         12         12
         6         13         12
         7         14         12

That shows it works for N = 5, just to test them all:

ops$tkyte@ORA9201> exec :n := 1

PL/SQL procedure successfully completed.

ops$tkyte@ORA9201> /

        ED         FR   THIRD_FR
---------- ---------- ----------
         1          8         10
         2          9         10
         3         10         10
         4         11         10
         5         12         10

ops$tkyte@ORA9201> exec :n := 2

PL/SQL procedure successfully completed.

ops$tkyte@ORA9201> /

        ED         FR   THIRD_FR
---------- ---------- ----------
         1          8         10
         2          9         10
         3         10         10
         4         11         10
         5         12         10

ops$tkyte@ORA9201> exec :n := 3

PL/SQL procedure successfully completed.

ops$tkyte@ORA9201> /

        ED         FR   THIRD_FR
---------- ---------- ----------
         1          8         10
         2          9         10
         3         10         10
         4         11         10
         5         12         10

ops$tkyte@ORA9201> exec :n := 4

PL/SQL procedure successfully completed.

ops$tkyte@ORA9201> /

        ED         FR   THIRD_FR
---------- ---------- ----------
         2          9         11
         3         10         11
         4         11         11
         5         12         11
         6         13         11

ops$tkyte@ORA9201> exec :n := 5

PL/SQL procedure successfully completed.

ops$tkyte@ORA9201> /

        ED         FR   THIRD_FR
---------- ---------- ----------
         3         10         12
         4         11         12
         5         12         12
         6         13         12
         7         14         12

ops$tkyte@ORA9201> exec :n := 6

PL/SQL procedure successfully completed.

ops$tkyte@ORA9201> /

        ED         FR   THIRD_FR
---------- ---------- ----------
         4         11         13
         5         12         13
         6         13         13
         7         14         13
         8         15         13

ops$tkyte@ORA9201> exec :n := 7

PL/SQL procedure successfully completed.

ops$tkyte@ORA9201> /

        ED         FR   THIRD_FR
---------- ---------- ----------
         5         12         14
         6         13         14
         7         14         14
         8         15         14
         9         16         14

ops$tkyte@ORA9201> exec :n := 8

PL/SQL procedure successfully completed.

ops$tkyte@ORA9201> /

        ED         FR   THIRD_FR
---------- ---------- ----------
         5         12         14
         6         13         14
         7         14         14
         8         15         14
         9         16         14

ops$tkyte@ORA9201> exec :n := 9

PL/SQL procedure successfully completed.

ops$tkyte@ORA9201> /

        ED         FR   THIRD_FR
---------- ---------- ----------
         5         12         14
         6         13         14
         7         14         14
         8         15         14
         9         16         14

Now, as ugly as that query is, some might say "well, how does it perform", not
too bad -- the rownum trick in the inner most query helps alot -- most of the
analytics are performed on 10 or less rows after that:

ops$tkyte@ORA9201> insert into t
  2  select rownum+9, 7+rownum+9
  3    from all_objects
  4  /
24193 rows created.

ops$tkyte@ORA9201> analyze table t compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;
Table analyzed.

ops$tkyte@ORA9201> exec :n := 20000
PL/SQL procedure successfully completed.

ops$tkyte@ORA9201> alter session set events '10046 trace name context forever,
level 12';

Session altered.

ops$tkyte@ORA9201> select ed, fr, max(decode(rn,3,fr)) over () third_fr
  2    from (
  3  select a.*, row_number() over (order by ed) rn, :n
  4    from (
  5  select a.*, max(decode(cnt,2,r)) over () max_r
  6    from (
  7  select a.*, count(*) over(partition by r) cnt
  8    from
  9    (
 10  select a.* , rownum -1 r
 11    from ( select /*+ FIRST_ROWS */ ed, fr
 12             from t
 13            where ed >= :n
 14            order by ed ) a
 15   where rownum <= 5
 16   union all
 17  select a.* , rownum r
 18    from ( select /*+ FIRST_ROWS */ ed, fr
 19             from t
 20            where ed < :n
 21            order by ed desc ) a
 22   where rownum <= 5
 23   ) a
 24   ) a
 25   ) a
 26   where r <= decode( max_r, 1, 3, 2, 2, 3, 2, 4, 2, null, 4 )
 27   ) a
 28   order by 1
 29  /

        ED         FR   THIRD_FR
---------- ---------- ----------
     19998      20005      20007
     19999      20006      20007
     20000      20007      20007
     20001      20008      20007
     20002      20009      20007

ops$tkyte@ORA9201>

Now, I executed that 8 times over and over and we see:

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        8      0.00       0.00          0          0          0           0
Execute      8      0.00       0.00          0          0          0           0
Fetch       16      0.00       0.00          0         48          0          40
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       32      0.01       0.01          0         48          0          40

Misses in library cache during parse: 1
Optimizer goal: FIRST_ROWS
Parsing user id: 34

Rows     Row Source Operation
-------  ---------------------------------------------------
      5  WINDOW BUFFER
      5   VIEW
      5    WINDOW SORT
      5     VIEW
     10      WINDOW BUFFER
     10       VIEW
     10        WINDOW SORT
     10         VIEW
     10          UNION-ALL
      5           COUNT STOPKEY
      5            VIEW
      5             TABLE ACCESS BY INDEX ROWID T
      5              INDEX RANGE SCAN SYS_C001670 (object id 24450)
      5           COUNT STOPKEY
      5            VIEW
      5             TABLE ACCESS BY INDEX ROWID T
      5              INDEX RANGE SCAN DESCENDING SYS_C001670 (object id 24450)

who said sql isn't procedural ;)  if you read the query from the inside out --
running the inline views you might see how I got this iteratively, over time.

q2) of course not, they are tools.  they degrade performance no more then any
other feature function.

used properly, they greatly enhance performance.
used improperly, they -- like all things -- can take a while.
Review &
      Followup

      Rating: 5
You are the
      best and thanks a Billion for the Patience in answering
        July 07, 2003
Reviewer:  A reader

      Rating: 5
You are the
      best and thanks a Billion for the Patience in answering
        July 07, 2003
Reviewer:  A reader

Dear Tom,

I just got your "Expert One on One" book from the stores and was going through
Analytic functions LAG and LEAD.
Can this be used in my case.

Thanks for the Answer
Sharath

          Followup:

go for it.  the problem is -- do you need to lag(..,4) or lead(...,4) or
lag(..2) and lead(..,2) or ......

I found this to be a very interesting query (yes, it took more then a minute)

I'd love to see other solutions, mine is embarrassingly procedural but
effective.

      Rating: 4
How about this
      solution?  July 08, 2003
Reviewer:  Jon
       from Sydney
Interesting problem.  I've come up with this, using LAG/LEAD.  Considering the 5
rows are fixed, we could build up a result matrix, fill in the blanks, and
reverse-pivot to get the result set:

select v.*,
  decode(sign(rownum-3),
          -1,lead(fr, abs(3-rownum)) over(order by ed),
          0,fr,
          1,lag(fr, abs(3-rownum)) over(order by ed)) third_fr
from
 (select
    decode(rownum+x_shift, ed1, ed1, ed2, ed2, ed3, ed3, ed4, ed4, ed5, ed5) ed,
    decode(rownum+x_shift, ed1, fr1, ed2, fr2, ed3, fr3, ed4, fr4, ed5, fr5) fr
  from
     (select least(ed1,ed2,ed3,ed4,ed5)-1 x_shift, x.*
      from
         (select ed,
            nvl(lag(ed,2) over (order by ed),lead(ed,3) over (order by ed)) ed1,
            nvl(lag(fr,2) over (order by ed),lead(fr,3) over (order by ed)) fr1,
            nvl(lag(ed,1) over (order by ed),lead(ed,4) over (order by ed)) ed2,
            nvl(lag(fr,1) over (order by ed),lead(fr,4) over (order by ed)) fr2,
            ed ed3,
            fr fr3,
            nvl(lead(ed,1) over (order by ed),lag(ed,4) over (order by ed)) ed4,
            nvl(lead(fr,1) over (order by ed),lag(fr,4) over (order by ed)) fr4,
            nvl(lead(ed,2) over (order by ed),lag(ed,3) over (order by ed)) ed5,
            nvl(lead(fr,2) over (order by ed),lag(fr,3) over (order by ed)) fr5
        from t) x
      where ed = :n) y,
     (select null
      from t where rownum <= 5) z) v

Seems to do the trick.  If we needed better performance for a large table, then
"t" could be replaced with an inline view limiting the rows for the windowing
functions.

          Followup:

that works -- however, on the "big" table it works like this:

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        8      0.00       0.00          0          0          0           0
Execute      8      0.00       0.00          0          0          0           0
Fetch       16      6.91       7.24          0        592          0          40
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       32      6.91       7.25          0        592          0          40

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 197

Rows     Row Source Operation
-------  ---------------------------------------------------
      5  WINDOW SORT (cr=74 r=0 w=0 time=1063775 us)
      5   COUNT  (cr=74 r=0 w=0 time=1063374 us)
      5    VIEW  (cr=74 r=0 w=0 time=1063353 us)
      5     COUNT  (cr=74 r=0 w=0 time=1063288 us)
      5      MERGE JOIN CARTESIAN (cr=74 r=0 w=0 time=1063272 us)
      5       VIEW  (cr=6 r=0 w=0 time=234 us)
      5        COUNT STOPKEY (cr=6 r=0 w=0 time=215 us)
      5         INDEX FAST FULL SCAN SYS_C006043 (cr=6 r=0 w=0 time=168
us)(object id 38988)
      5       BUFFER SORT (cr=68 r=0 w=0 time=1062844 us)
      1        VIEW  (cr=68 r=0 w=0 time=1062674 us)
  30173         WINDOW SORT (cr=68 r=0 w=0 time=878752 us)
  30173          TABLE ACCESS FULL T (cr=68 r=0 w=0 time=65741 us)

if you replace your "from t) x" with my first inline view, it works like this:

select v.*,
  decode(sign(rownum-3),
          -1,lead(fr, abs(3-rownum)) over(order by ed),
          0,fr,
          1,lag(fr, abs(3-rownum)) over(order by ed)) third_fr
from
 (select
    decode(rownum+x_shift, ed1, ed1, ed2, ed2, ed3, ed3, ed4, ed4, ed5, ed5) ed,
    decode(rownum+x_shift, ed1, fr1, ed2, fr2, ed3, fr3, ed4, fr4, ed5, fr5) fr
  from
     (select least(ed1,ed2,ed3,ed4,ed5)-1 x_shift, x.*
      from
         (select ed,
            nvl(lag(ed,2) over (order by ed),lead(ed,3) over (order by ed)) ed1,
            nvl(lag(fr,2) over (order by ed),lead(fr,3) over (order by ed)) fr1,
            nvl(lag(ed,1) over (order by ed),lead(ed,4) over (order by ed)) ed2,
            nvl(lag(fr,1) over (order by ed),lead(fr,4) over (order by ed)) fr2,
            ed ed3,
            fr fr3,
            nvl(lead(ed,1) over (order by ed),lag(ed,4) over (order by ed)) ed4,
            nvl(lead(fr,1) over (order by ed),lag(fr,4) over (order by ed)) fr4,
            nvl(lead(ed,2) over (order by ed),lag(ed,3) over (order by ed)) ed5,
            nvl(lead(fr,2) over (order by ed),lag(fr,3) over (order by ed)) fr5
        from
            ( select a.* , rownum -1 r
                from ( select /*+ FIRST_ROWS */ ed, fr
                         from t
                        where ed >= :n
                        order by ed ) a
               where rownum <= 5
               union all
              select a.* , rownum r
                from ( select /*+ FIRST_ROWS */ ed, fr
                         from t
                        where ed < :n
                        order by ed desc ) a
               where rownum <= 5
            ) t
        ) x
      where ed = :n) y,
     (select null
      from t where rownum <= 5) z) v

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        8      0.00       0.00          0          0          0           0
Execute      8      0.00       0.00          0          0          0           0
Fetch       16      0.01       0.01          0         64          0          40
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       32      0.01       0.01          0         64          0          40

so it would be comparable.

excellent.

and does point out that with analytics, it is really nice to use inline views if
possible, pushing predicates DOWN as far as you can, to analyze the smallest set
you need to.

      Rating: 3
Will this work
      ? I didn't check.  July 08, 2003
Reviewer:
      ks
select a.ed,a.fr ,b.fr third_fr from
( select rownum r, ed, fr t ) a,
( select rownum r, ed, fr t ) b
where
b.ed = :n and
a.r between b.r-2 and b.r +2 order by a.r;

          Followup:

why would you even post it if you haven't bothered to try it out?

since it takes all of 5 seconds to test:

ops$tkyte@ORA920> variable n number
ops$tkyte@ORA920> exec :n := 1

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select a.ed,a.fr ,b.fr third_fr from
  2  ( select rownum r, ed, fr from t ) a,
  3  ( select rownum r, ed, fr from t ) b
  4  where
  5  b.ed = :n and
  6  a.r between b.r-2 and b.r +2 order by a.r;

        ED         FR   THIRD_FR
---------- ---------- ----------
         1          8          8
         2          9          8
         3         10          8

answser = no, it doesn't work.

      Rating: 3
This works
        July 08, 2003
Reviewer:  ks
Tom,
   Actually I was wondering why we need such a complex subqueries. I still can't
figure out what's the problem with
select
...
( select rownum r,cbn_code ed from (select distinct cbn_code from t_charge_band)
) a,
( select rownum r,cbn_code ed from (select distinct cbn_code from t_charge_band)
) b
..
..;
 I have changed the query ( copied decode from above comment ).

select v.*,decode(sign(rownum-3),-1,lead(v.ed, abs(3-rownum)) over(order by ed),
0,v.ed,
1,lag(v.ed, abs(3-rownum)) over(order by ed)) third_fr
from
(
select a.r,a.ed from
( select rownum r,cbn_code ed from (select distinct cbn_code from t_charge_band)
) a,
( select rownum r,cbn_code ed from (select distinct cbn_code from t_charge_band)
) b
where
b.ed = :n and
a.r between b.r-2 and b.r +2
) v;

          Followup:

do this,

create table t ( ed int primary key, fr number, data char(40) );

insert into t
select rownum, 7+rownum, 'x'
  from all_objects;

Now, run your query against that data for :n = 15000

Let us know how it goes.  I see no "fr" in your query, no clue what
t_charge_band is, cbn_code...

but assuming that

( select rownum r,cbn_code ed from (select distinct cbn_code from t_charge_band)

can be replaced with

( select rownum r, ed from t

(eg: your select distinct was just a way to make up t), then NO, it doesn't
work:

ops$tkyte@ORA920> create table t ( ed int primary key, fr number );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t
  2  select rownum, 7+rownum
  3    from all_objects
  4   where rownum <= 9;

9 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> variable n number
ops$tkyte@ORA920> exec :n := 1

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select v.*,decode(sign(rownum-3),-1,lead(v.ed, abs(3-rownum))
over(order by ed),
  2  0,v.ed,
  3  1,lag(v.ed, abs(3-rownum)) over(order by ed)) third_fr
  4  from
  5  (
  6  select a.r,a.ed from
  7  ( select rownum r,ed from t
  8  ) a,
  9  ( select rownum r,ed from t
 10  ) b
 11  where
 12  b.ed = :n and
 13  a.r between b.r-2 and b.r +2
 14  ) v;

         R         ED   THIRD_FR
---------- ---------- ----------
         1          1          3
         2          2          3
         3          3          3

ops$tkyte@ORA920>

and doing ROWNUM as you are would be very dangerous, i see no "order by" before
assigning rownum, consider:

ops$tkyte@ORA920> truncate table t;

Table truncated.

ops$tkyte@ORA920> insert into t
  2  select 10-rownum, 7+rownum
  3    from all_objects
  4   where rownum <= 9;

9 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select v.*,decode(sign(rownum-3),-1,lead(v.ed, abs(3-rownum))
over(order by ed),
  2  0,v.ed,
  3  1,lag(v.ed, abs(3-rownum)) over(order by ed)) third_fr
  4  from
  5  (
  6  select a.r,a.ed from
  7  ( select rownum r,ed from t
  8  ) a,
  9  ( select rownum r,ed from t
 10  ) b
 11  where
 12  b.ed = :n and
 13  a.r between b.r-2 and b.r +2
 14  ) v;

         R         ED   THIRD_FR
---------- ---------- ----------
         9          1          1
         8          2          3
         7          3

ops$tkyte@ORA920>

but please -- the test case is really simple for you to work with.  Create my
table t.  put into it the 9 rows I do.  Run the 9 queries with :n := 1, 2, 3, 4,
5, 6, 7, 8, 9.  Verify the results.  Then put the remaining n thousand rows in
there and run again to make sure it performs

Then -- feel free to post that example

      Rating: 5
'nother
      query  July 08, 2003
Reviewer:  Scott
       from Chicago, IL USA
So I fastidiously averted my gaze from your solution, came up with my own, and
then realized that it's conceptually more or less the same as yours.  I find it
a pinch more readable, but then we all find our own code more readable, now
don't we?

SELECT ed
     , fr
     , MAX(DECODE(rnum, 3, fr, NULL)) OVER () AS third_fr
  FROM (
SELECT v3.*
     , ROW_NUMBER() OVER (ORDER BY ed) AS rnum
  FROM (
SELECT v2.*
     , MAX(DECODE(measure_cnt, 5, measure, NULL)) OVER () AS max_measure
  FROM (
SELECT v.*
     , ABS(ed - :n) AS measure
     , ROW_NUMBER() OVER (
         ORDER BY ABS(ed - :n) ) AS measure_cnt
  FROM (
SELECT *
  FROM t
 WHERE ed <= :n + 4
       )  v
 WHERE ed >= :n - 4
       )  v2
       )  v3
 WHERE measure <= max_measure
       )  v4;

Cheers.

          Followup:

yours work IF ed is "sequential", doesn't if ed is just a sortable, unique
field:

ops$tkyte@ORA920> select * from t;

        ED         FR
---------- ----------
        20          8
        40          9
        60         10
        80         11
       100         12
       120         13
       140         14
       160         15
       180         16

9 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> SELECT ed
  2       , fr
  3       , MAX(DECODE(rnum, 3, fr, NULL)) OVER () AS third_fr
  4    FROM (
  5  SELECT v3.*
  6       , ROW_NUMBER() OVER (ORDER BY ed) AS rnum
  7    FROM (
  8  SELECT v2.*
  9       , MAX(DECODE(measure_cnt, 5, measure, NULL)) OVER () AS max_measure
 10    FROM (
 11  SELECT v.*
 12       , ABS(ed - :n) AS measure
 13       , ROW_NUMBER() OVER (
 14           ORDER BY ABS(ed - :n) ) AS measure_cnt
 15    FROM (
 16  SELECT *
 17    FROM t
 18   WHERE ed <= :n + 4
 19         )  v
 20   WHERE ed >= :n - 4
 21         )  v2
 22         )  v3
 23   WHERE measure <= max_measure
 24         )  v4;

no rows selected

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select ed, fr, max(decode(rn,3,fr)) over () third_fr
  2    from (
  3  select a.*, row_number() over (order by ed) rn, :n
  4    from (
  5  select a.*, max(decode(cnt,2,r)) over () max_r
  6    from (
  7  select a.*, count(*) over(partition by r) cnt
  8    from
  9    (
 10  select a.* , rownum -1 r
 11    from ( select /*+ FIRST_ROWS */ ed, fr
 12             from t
 13            where ed >= :n
 14            order by ed ) a
 15   where rownum <= 5
 16   union all
 17  select a.* , rownum r
 18    from ( select /*+ FIRST_ROWS */ ed, fr
 19             from t
 20            where ed < :n
 21            order by ed desc ) a
 22   where rownum <= 5
 23   ) a
 24   ) a
 25   ) a
 26   where r <= decode( max_r, 1, 3, 2, 2, 3, 2, 4, 2, null, 4 )
 27   ) a
 28   order by 1
 29  /

        ED         FR   THIRD_FR
---------- ---------- ----------
        60         10         12
        80         11         12
       100         12         12
       120         13         12
       140         14         12

ops$tkyte@ORA920>

so, if you add an assumption, yes, but if you only assume:

assuming ED is sortable (it must be, there is not any other way you could claim
there is a "row 3" or anything like that) and ED is unique -- the following will

do it.  I just used a number for ED -- but remember ED must be sortable (or
there is no such thing as "row 3"

it doesn't work

      Rating: 5
Here is one
      without Analytic Function  July 08, 2003
Reviewer:
      Murugan  from Orlando, FL
select ed, fr
from (
      select /*+ FIRST_ROWS */ ed, fr
      from   t
      where  ed >= :n
      order by ed
     )
where rownum <= (SELECT 3 + (2-COUNT(*)) from t where ed < :n and rownum < 3)
UNION
select ed, fr
from (
      select /*+ FIRST_ROWS */ ed, fr
      from   t
      where  ed <= :n
      order by ed desc
     )
where rownum <= (SELECT 3 + (2-COUNT(*)) from t where ed > :n and rownum < 3)
order by ed

          Followup:

sweet, very nice -- now, get the third row ;)

      Rating: 5
Now I follow
      you  July 08, 2003
Reviewer:  Scott
       from Chicago, IL USA
So you meant ed to be a true key, rather than a value that contains the original
poster's "tier".  So then on grafting your inner-most query into my code (cheap
I admit, but I peered at it and it just looks that good -- though you oughtn't
let such comments go to your head of course :) ).

SELECT ed
     , fr
     , MAX(DECODE(rnum, 3, fr)) OVER () AS third_fr
  FROM (
SELECT v3.*
     , ROW_NUMBER() OVER (ORDER BY ed) AS rnum
  FROM (
SELECT v2.*
     , MAX(DECODE(measure_cnt, 5, measure)) OVER () AS max_measure
  FROM (
SELECT v.*
     , ABS(ed - :n) AS measure
     , ROW_NUMBER() OVER (
         ORDER BY ABS(ed - :n) ) AS measure_cnt
  FROM (
select a.*
  from ( select /*+ FIRST_ROWS */ ed, fr
           from t
          where ed >= :n
          order by ed ) a
 where rownum <= 5
 union all
select a.*
  from ( select /*+ FIRST_ROWS */ ed, fr
           from t
          where ed < :n
          order by ed desc ) a
 where rownum <= 5
       )  v
       )  v2
       )  v3
 WHERE measure <= max_measure
       )  v4;

While at this point I'm not convinced that the queries are meaningfully
distinct, I'm still rather fond of the clarity of my "measure <= max_measure"
condition.

Scott

      Rating: 5
ha, ha,
      ha  July 08, 2003
Reviewer:  Scott
       from Chicago, IL USA
Alright, so I pretended to "get" the fact that you intended ed to be a pk rather
than a meaningful value.  If it's in fact nothing more than that, then I need to
redefine my notion of "measure" to be a row-based one rather than a value-based
one -- yielding results that are even more akin to your initial query.

INSERT INTO t
SELECT 40 + 5*rownum, 9 + .25*rownum
  FROM user_objects
 WHERE rownum <= 3;

COMMIT;

SELECT ed
     , fr
     , MAX(DECODE(rnum, 3, fr)) OVER () AS third_fr
  FROM (
SELECT v3.*
     , ROW_NUMBER() OVER (ORDER BY ed) AS rnum
  FROM (
SELECT v2.*
     , MAX(DECODE(measure_cnt, 5, measure)) OVER () AS max_measure
  FROM (
SELECT v.*
     , ROW_NUMBER() OVER (
         ORDER BY measure ) AS measure_cnt
  FROM (
select a.*, rownum-1  measure
  from ( select /*+ FIRST_ROWS */ ed, fr
           from t
          where ed >= :n
          order by ed ) a
 where rownum <= 5
 union all
select a.*, rownum  measure
  from ( select /*+ FIRST_ROWS */ ed, fr
           from t
          where ed < :n
          order by ed desc ) a
 where rownum <= 5
       )  v
       )  v2
       )  v3
 WHERE measure <= max_measure
       )  v4;

Scott

July 08, 2003 Ashish -- Thanks for the question regarding
      "Passing parameter for SQL query using shell script", version Oracle 8.0.4

      You Asked
Hello Tom,
  I am doing following thing,
sqlplus -s /nolog  <<EOF
connect system/$pwd
@hdr
Column tablespace_name format a25 heading "TABLESPACE"
Column initial_extent format 99,999,999 heading "INITIAL"
Column next_extent format 99,999,999 heading "NEXT"
Column min_extents format 99,999,999 heading "MIN"
Column max_extents heading "MAX"
Column CONTENTS format a9 heading "CONTENTS"
TTitle left "*****  Database:  "dbname", Tablespace Information ( As of:
"xdate" )   *****" skip 2
spool temp.txt
select
tablespace_name,
initial_extent,
next_extent,
min_extents,
decode(max_extents,2147483645,'UNLIMITED',max_extents) max_extents,
CONTENTS
from
dba_tablespaces
where tablespace_name=upper('&tbsp');
spool off
clear columns
EOF
 But when I ran the script from shell it dosn't wait for the parameter and come
out from the shell.
 I want to do above query using shell script. Could you suggest me the solution?
Thanks,
Ashish Kher.

      and we said...
Well, it looks like you are trying to do an "interactive" thing (&tbsp) in a
non-interactive environment <<EOF.

Simply change &tbsp into $1 and pass the argument into the script. eg:

$ cat test.csh
#!/bin/csh

sqlplus -s /nolog  <<EOF
connect /

Column tablespace_name format a25 heading "TABLESPACE"
Column initial_extent format 99,999,999 heading "INITIAL"
Column next_extent format 99,999,999 heading "NEXT"
Column min_extents format 99,999,999 heading "MIN"
Column max_extents heading "MAX"
Column CONTENTS format a9 heading "CONTENTS"
spool temp.txt
select
tablespace_name,
initial_extent,
next_extent,
min_extents,
decode(max_extents,2147483645,'UNLIMITED',max_extents) max_extents,
CONTENTS
from
dba_tablespaces
where tablespace_name=upper('$1');
spool off
clear columns
EOF

$ ./test.csh system
Connected.

TABLESPACE                    INITIAL        NEXT         MIN
------------------------- ----------- ----------- -----------
MAX                                      CONTENTS
---------------------------------------- ---------
SYSTEM                         16,384      16,384           1
505                                      PERMANENT

Review &
      Followup

      Rating: 5
Thanks
        April 25, 2001
Reviewer:  Ashish Kher
       from Louisville, KY USA
Hello,
  I tried out with &$ but it didn't work. Actually I am working on menu driven
monitoring tool using shell script. And I was stuck up for this problem.

Thank you very much for your immediate response.

Ashish Kher.

      Rating: 4
a related
      question  July 08, 2003
Reviewer:  John
       from New Jersey, USA
How to run non-interactive SQL*Plus in Windows invironment?  I'm try to automate
a process that calls a stored procedure.  I created a .bat file like this but
it's not working.

sqlplus jw/jw@mars << EOF
exec add_to_mytest;
exit;
EOF

          Followup:

echo exec add_to_mytest | sqlplus jw/jw@mars

should work, as would:

echo exec add_to_mytest > tmp.sql
echo exit >> tmp.sql
sqlplus jw/jw@mars @tmp
erase tmp.sql

      Rating: 4
can
      also...  July 08, 2003
Reviewer:  Bricklen

add the -s option to the line, like:

sqlplus -s user/pass ...

to make it run in silent mode (doesn't display in the screen etc)

      Rating: 5
Thank you both,
      Tom and Bricklen!  July 08, 2003
Reviewer:
      John  from New Jersey, USA


Subject | Article | Last Updated (East coast time)
Chaining/migrating | article only | Jul 09, 2003 10:26 am
How to spread a table into multiple disk without using partition table feature? | article only | Jul 09, 2003 11:01 am
Partition | article only | Jul 09, 2003 11:03 am
Simple but could be handy | article only | Jul 09, 2003 11:08 am
Deleting many rows from a big table | article only | Jul 09, 2003 11:13 am
using LIKE predicate in SELECT ... FROM [CLOB field] | article only | Jul 09, 2003 01:49 pm
Using Rebuild parallel option on large table index with 6 CPU's | article only | Jul 09, 2003 01:51 pm
ASO or SSL or BOTH | article only | Jul 09, 2003 07:15 pm
Direct insert and parallel insert | article only | Jul 09, 2003 07:21 pm
Insufficient privileges | article only | Jul 09, 2003 08:13 pm
problem with date formats | article only | Jul 10, 2003 07:43 am
RMAN Image copies | article only | Jul 10, 2003 10:05 am
basic replication | article only | Jul 10, 2003 10:14 am
Oracle 9i client can not connect to db 7.3.2 | article only | Jul 10, 2003 10:21 am
Auto numbering in groups | article only | Jul 10, 2003 10:26 am
Large table | article only | Jul 10, 2003 11:40 am
export >2 gig in size | article only | Jul 10, 2003 01:28 pm
PL/SQL Debugger | article only | Jul 10, 2003 03:02 pm
user authentication | article only | Jul 10, 2003 05:15 pm
Oracle logins | article only | Jul 10, 2003 05:18 pm
Trigger v/s Procedure execution Speed (performance speed) | article only | Jul 10, 2003 05:26 pm
SDU,TDU etc | article only | Jul 11, 2003 08:16 am
Percent of Running Total | article only | Jul 11, 2003 09:28 am
sql loader -- | article only | Jul 11, 2003 10:22 am
Table design in connected/disconnected architecture | article only | Jul 11, 2003 10:42 am
too wide for sqlplus | article only | Jul 11, 2003 10:53 am
BLOB | article only | Jul 11, 2003 12:17 pm
difference between sql with clause and inline | article only | Jul 11, 2003 12:41 pm
Error when trying to view BFILE data | article only | Jul 12, 2003 09:13 am
Monitoring | article only | Jul 12, 2003 09:26 am
export and importing indexes and pk | article only | Jul 12, 2003 09:27 am
trace sessions in three-tier enviroment | article only | Jul 12, 2003 09:32 am
Combining rows of two Table as columns in SQL query | article only | Jul 12, 2003 09:39 am
Database trigger | article only | Jul 13, 2003 09:46 am
Rownum | article only | Jul 14, 2003 10:48 am
IMPLICIT CURSOR ATTRIBUTE SQL%NOTFOUND NOT WORKING | article only | Jul 15, 2003 12:45 am
Using "Oracle Text" CONTAINS syntax on multiple columns | article only | Jul 15, 2003 12:58 am
How can I suppress the other error messages from PLSQL errors | article only | Jul 15, 2003 01:00 am
Why gather index stats? | article only | Jul 15, 2003 01:11 am
sql loader -- how to load | article only | Jul 15, 2003 01:12 am
question | article only | Jul 15, 2003 09:45 am
CAPACITY PLANNING FOR REDOLOG FILE | article only | Jul 15, 2003 09:58 am
New database and password file | article only | Jul 15, 2003 10:09 am
MTS | article only | Jul 15, 2003 01:23 pm
unable to open RPC connection to external procedure agent | article only | Jul 15, 2003 01:36 pm
Java stored procedure error | article only | Jul 15, 2003 03:11 pm
Deleting Nested table records | article only | Jul 15, 2003 05:53 pm
Greater / less than in Decode | article only | Jul 15, 2003 05:57 pm
Selecting from THE (cast | article only | Jul 15, 2003 10:54 pm
row(s) 1 - 49 of 49
July 09, 2003 Brigitte -- Thanks for the question regarding "Chaining/migrating", version 816
You Asked
Hello Tom,

I've got a 900-column table with 2.000.000 rows, most of them
are CHAR(1) (avg rowlength= 1800).When I analyze this table
(compute statistics), I see the CNT_CHAIN = 800.000. But when I
do an analyze table list chained rows , I'm getting a
chained_rows table with 1.300.000 rows.
Then I'm exporting and importing the table again, analyzing the
new table and seeing that the CNT_CHAIN has decreased to
200.000. But I would think that all the rows should fit!
BTW. the db_blocksize is 8K.

Thanks in advance for your reply!

Brigitte Janssen

 The blocksize is 8K. There will be once or twice a year a
update of about 300000 records and 300 columns.
 The table looks like 1 number column (pk-key which is being
filled with a sequence starting with 1000000000)and the rest is
CHAR(1).

How do i know which 200000 rows are chained? because when i
submit the analyze table list chained rows i'm still getting
1300000 rows in my chained_rows table after exporting/importing.
and we said...
We store rows in "row pieces".  Each row piece consists of upto
255 columns.  If you have 900 columns -- each row will be stored
in a 255+255+255+135 column rowpieces. (we use a byte somewhere
on the block to address a rowpiece -- in order to be compatible
with older releases and to make it so you do not have to rewrite
your entire database when upgrading -- it is left that way).

Therefore, when you store a row with 900 columns -- it is stored
in upto 4 pieces (if the last 135 columns are NULL, there will b
3, if the last 135+255 are NULL, you will have 2 and so on.  We
do not store trailing NULLS).  You might see lots of "table
fetch continued rows" but not have many rows that are not on the
same block.  That is - the 4 rows pieces may all be on the SAME
block (so that an analyze might not see them) but the v$sysstat
"table fetch continued row" will.  We logically piece the row
pieces together, even though they all exist on the same block.
We do 4 logical IO's to get the one row.

Now, as to why some of your rows actually do span blocks when
you feel a row should fit on a block.  It is because we are
treating it as 4 row pieces -- not a single row.  The row piece
fit onto a block so we put it there.  The next row piece may
not.  Your one single logical row is being treated as if it were
4 physical rows.

We can see this side effect this way:

set termout off
set feedback off
set echo off
spool temp.sql

begin
  dbms_output.put_line( 'drop table t;' );
  dbms_output.put_line( 'create table t (' );
  for i in 1 .. 899
  loop
    dbms_output.put_line( 'c' || i || ' char(1),' );
  end loop;
  dbms_output.put_line( 'c900 char(1) )' );
  dbms_output.put_line( '/' );
end;
/

spool off
set feedback on
set termout on
@temp
set echo on

that is how I created the table with 900 columns

ops$tkyte@ORA8I.WORLD> REM c1..c255 are in row piece 1
ops$tkyte@ORA8I.WORLD> REM c256..510 are in row piece 2
ops$tkyte@ORA8I.WORLD> REM c511..765 are in row piece 3
ops$tkyte@ORA8I.WORLD> REM c766..900 are in row piece 4

now, here is a row with row piece 1 only:

ops$tkyte@ORA8I.WORLD> insert into t ( c1 ) values ( 'y' );
1 row created.

we'll save the "table fetch continued row" statistic for our
session off to the side for a moment

ops$tkyte@ORA8I.WORLD> column value new_value last

ops$tkyte@ORA8I.WORLD> select value
  2    from v$mystat
  3   where statistic# = 158
  4  /

     VALUE
----------
        46
1 row selected.

ops$tkyte@ORA8I.WORLD> select c1, c256, c511, c799 from t;

C C C C
- - - -
y

1 row selected.

Now, lets see how many table fetch continued rows we just
did:

ops$tkyte@ORA8I.WORLD> select value-&last
CONTINUED_ROWS_FROM_ABOVE
  2    from v$mystat
  3   where statistic# = 158
  4  /
old   1: select value-&last  CONTINUED_ROWS_FROM_ABOVE
new   1: select value-        46  CONTINUED_ROWS_FROM_ABOVE

CONTINUED_ROWS_FROM_ABOVE
-------------------------
                        0

1 row selected.

we did none, lets check the chained rows

ops$tkyte@ORA8I.WORLD> delete from chained_rows;
1 row deleted.

ops$tkyte@ORA8I.WORLD> analyze table t list chained rows into
chained_rows;
Table analyzed.

ops$tkyte@ORA8I.WORLD> select count(*) from chained_rows;

  COUNT(*)
----------
         0
1 row selected.

we have none.  Fine, lets update a value in row piece 2

ops$tkyte@ORA8I.WORLD> update t set c256='y';
1 row updated.

save the value again...

ops$tkyte@ORA8I.WORLD> select value
  2    from v$mystat
  3   where statistic# = 158
  4  /

     VALUE
----------
        46

1 row selected.

ops$tkyte@ORA8I.WORLD> select c1, c256, c511, c799 from t;

C C C C
- - - -
y y

1 row selected.

and now see how many we did.

ops$tkyte@ORA8I.WORLD> select value-&last
CONTINUED_ROWS_FROM_ABOVE
  2    from v$mystat
  3   where statistic# = 158
  4  /
old   1: select value-&last CONTINUED_ROWS_FROM_ABOVE
new   1: select value-        46 CONTINUED_ROWS_FROM_ABOVE

CONTINUED_ROWS_FROM_ABOVE
-------------------------
                        1

1 row selected.

ahh, apparently we have a chained row with only 2 bytes of
data.  that is because row piece 2 is there now.  Looking at
what analyze says:

ops$tkyte@ORA8I.WORLD> delete from chained_rows;
0 rows deleted.

ops$tkyte@ORA8I.WORLD> analyze table t list chained rows into
chained_rows;
Table analyzed.

ops$tkyte@ORA8I.WORLD> select count(*) from chained_rows;

  COUNT(*)
----------
         1

1 row selected.

we in fact do have a chained row.  the rest of this shows that
by updating row piece 3, we'll have 2 table fetched continued
rows and 1 chained row.  Updating row piece 4 will give us 3
table fetch continued rows and 1 chained row

ops$tkyte@ORA8I.WORLD> update t set c511='y';
1 row updated.

ops$tkyte@ORA8I.WORLD> select value
  2    from v$mystat
  3   where statistic# = 158
  4  /

     VALUE
----------
        48

1 row selected.

ops$tkyte@ORA8I.WORLD> select c1, c256, c511, c799 from t;

C C C C
- - - -
y y y

1 row selected.

ops$tkyte@ORA8I.WORLD> select value-&last
CONTINUED_ROWS_FROM_ABOVE
  2    from v$mystat
  3   where statistic# = 158
  4  /
old   1: select value-&last CONTINUED_ROWS_FROM_ABOVE
new   1: select value-        48 CONTINUED_ROWS_FROM_ABOVE

CONTINUED_ROWS_FROM_ABOVE
-------------------------
                        2

1 row selected.

ops$tkyte@ORA8I.WORLD> delete from chained_rows;

1 row deleted.

ops$tkyte@ORA8I.WORLD> analyze table t list chained rows into
chained_rows;

Table analyzed.

ops$tkyte@ORA8I.WORLD> select count(*) from chained_rows;

  COUNT(*)
----------
         1

1 row selected.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> update t set c766='y';

1 row updated.

ops$tkyte@ORA8I.WORLD> select value
  2    from v$mystat
  3   where statistic# = 158
  4  /

     VALUE
----------
        52

1 row selected.

ops$tkyte@ORA8I.WORLD> select c1, c256, c511, c799 from t;

C C C C
- - - -
y y y

1 row selected.

ops$tkyte@ORA8I.WORLD> select value-&last
CONTINUED_ROWS_FROM_ABOVE
  2    from v$mystat
  3   where statistic# = 158
  4  /
old   1: select value-&last CONTINUED_ROWS_FROM_ABOVE
new   1: select value-        52 CONTINUED_ROWS_FROM_ABOVE

CONTINUED_ROWS_FROM_ABOVE
-------------------------
                        3

1 row selected.

ops$tkyte@ORA8I.WORLD> delete from chained_rows;

1 row deleted.

ops$tkyte@ORA8I.WORLD> analyze table t list chained rows into
chained_rows;

Table analyzed.

ops$tkyte@ORA8I.WORLD> select count(*) from chained_rows;

  COUNT(*)
----------
         1

1 row selected.

So, I would hazzard a guess that the majority of your rows have
no data in row pieces 2, 3, and 4 (they are all null) and at
least 200k of them have some data in row pieces 2, 3, and/or 4.
Review & Followup
Rating: 5
Excellent explanation October 30, 2001
Reviewer: Andrew from CA, USA
Thanks for the clear example, even though it would be seldom encountered by most
developers.

Rating: 5
Great example.. July 08, 2003
Reviewer: A reader from MN, USA
I am reading the Oracle documentation and whenever I hit a roadblock i come to
this site and search for info and lo and behold i get some cool examples to
clear my doubts.

Thanks Tom for this great site. We love you.

Rating: 5
Rowid of rowpieces?? July 08, 2003
Reviewer: A reader from MN, USA
<quote>
Rowids of Row Pieces

The rowid identifies each row piece by its location or address. After they are
assigned, a given row piece retains its rowid until the corresponding row is
deleted or exported and imported using the Export and Import utilities. For
clustered tables, if the cluster key values of a row change, then the row keeps
the same rowid but also gets an additional pointer rowid for the new values.

Because rowids are constant for the lifetime of a row piece, it is useful to
reference rowids in SQL statements such as SELECT, UPDATE, and DELETE.
</quote>

As per your example above any row with more than 255 columns is stored as
separate row pieces. the oracle docs mention that a ROWID IDENTIFIES EACH ROW
PIECE BY ITS LOCATION OR ADDRESS.

does this in anyway imply that a row with more than 255 columns has more than
one ROWID? if so when we delete a row using its ROWID is only that particular
row-piece deleted. I am somewhat confused by the docs. Can you clarify?

Followup:
technically, yes -- the any chained row has more then one "rowid".

Only the HEAD rowid, the pointer to the beginning of the row, is relevant to us.

And that quote is technically no longer 100% accurate since Oracle8i.  There are
two cases I'm aware of where rowids can changed without a delete/insert

a) partitioned tables with "enable row movement" and an update that causes a row
to MOVE from one partition to another will change that rows rowid.

b) updates to the primary key of an IOT will cause the logical rowid of that row
to change -- but since we don't update primary keys.....

July 09, 2003 Frank -- Thanks for the question regarding "How to spread a table into multiple disk without using partition table feature?", version 8.1.7
You Asked
Tom,
(1) If i want to do what the subject asks in order to take advantage of the
parallel disk read for the full table scan, is it possible without using
partition table?
(2) Each table has one HWM even the table has multiple extents. For the
partitioned table, I guess each partition has its own HWM, so a full table scan
will read all blocks under HWM of every partition, am i right?
Thank you!
Frank
and we said...
1) sure, using file system striping is the most common.  Just strip a bunch of
disks.

short of that, create a tablespace with many datafiles.  Use a locally managed
tablespace of course. Use a uniform extent size that will cause the table to go
into 100 or so extents. Create the table in that tablespace.  We'll allocate
extents for it in a round robin fashion - spreading the data out.

2) yes.
Review & Followup
Rating: 4
allocate datafiles January 03, 2003
Reviewer: Frank from west palm beach, FL
Tom,
If a tablespace has datafiles such as df1, df2, df3...dfn, I though oracle would
allocate extents in df1 first until it's full, then df2, df3...etc.

But your comment seems you are saying oracle allocates the extent in each
datafile in round robin fasion even the lower number of the datafiles still have
available spaces? Lets assume a segment's last allocated extent was in
datafile(n), when it needs more extent, oracle will allocate it in the datafile
(n+1), then n+2, ... max#, 1, 2, ... n, is it true?
But, my test shows it's not true. Here it goes,

I had tablespace named test which has two datafiles #10,#11 size 512k. Then i
created a table in tbs test and inserted records, i found the file#11 started to
fill out first then file#10, not like (ext#0)in file#11,(ext#1)in
file#10,(ext#2)in file#11,(ext#3)in file#10....
So if i want to spread a 500M table into 5 disks, i can create 10 datafiles and
spread them accrodingly, just like this (file#1,#6 in disk1, file#2,#7 in
disk2...file#5,#10 in disk5). If i need more space in the feature, i need to add
datafile in the same way, like this file#11 in disk1, file#12 in disk2...etc. If
oracle did allocate extent in the round robin fashion, all i needed is creating
5 big datafiles(200M each), and set extent size to a relative small number i.e.
2M each, then oracle would put these extents evenly into the 5 datafiles. But
this is not true accroding to my observation, Oracle does NOT allocate extents
in tho round robin fashion by itself, the DBA forces oracle to spread the data
by spreading the datafiles in the round robin fashion, but NOT oracle.

SQL>  drop tablespace test including contents and datafiles;

Tablespace dropped.

SQL> create tablespace test
  2  datafile 'd:\frank\test01.dbf' size 512k
  3  extent management local;

Tablespace created.

SQL> alter tablespace test
  2  add datafile 'd:\frank\test02.dbf' size 512k;

Tablespace altered.

SQL>  create table scott.test
  2   (c char(2000))
  3   tablespace test;

Table created.

SQL> select extent_id, file_id, block_id, bytes, blocks from dba_extents
  2  where segment_name='TEST'
  3  and owner='SCOTT';

 EXTENT_ID    FILE_ID   BLOCK_ID      BYTES     BLOCKS
---------- ---------- ---------- ---------- ----------
         0         11         17      65536         16

SQL> begin
  2    for i in 1..32 loop
  3      insert into scott.test values (i);
  4      commit;
  5    end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

SQL> select extent_id, file_id, block_id, bytes, blocks from dba_extents
  2  where segment_name='TEST'
  3  and owner='SCOTT';

 EXTENT_ID    FILE_ID   BLOCK_ID      BYTES     BLOCKS
---------- ---------- ---------- ---------- ----------
         0         11         17      65536         16
         1         11         33      65536         16
         2         11         49      65536         16

SQL>  begin
  2     for i in 1..300 loop
  3      insert into scott.test values (i);
  4      commit;
  5    end loop;
  6  end;
  7  /
 begin
*
ERROR at line 1:
ORA-01653: unable to extend table SCOTT.TEST by 16 in tablespace TEST
ORA-06512: at line 3

SQL> select count(*) from scott.test;

  COUNT(*)
----------
       223

SQL>  select extent_id, file_id, block_id, bytes, blocks from dba_extents
  2   where segment_name='TEST'
  3   and owner='SCOTT';

 EXTENT_ID    FILE_ID   BLOCK_ID      BYTES     BLOCKS
---------- ---------- ---------- ---------- ----------
         0         11         17      65536         16
         1         11         33      65536         16
         2         11         49      65536         16
         3         11         65      65536         16
         4         11         81      65536         16
         5         11         97      65536         16
         6         11        113      65536         16
         7         10         17      65536         16
         8         10         33      65536         16
         9         10         49      65536         16
        10         10         65      65536         16

 EXTENT_ID    FILE_ID   BLOCK_ID      BYTES     BLOCKS
---------- ---------- ---------- ---------- ----------
        11         10         81      65536         16
        12         10         97      65536         16
        13         10        113      65536         16

14 rows selected.

SQL> select file#, substr(name, 1, 30) from v$datafile
  2  where file# in (10,11);

     FILE# SUBSTR(NAME,1,30)
---------- ------------------------------
        10 D:\FRANK\TEST01.DBF
        11 D:\FRANK\TEST02.DBF

Followup:
ops$tkyte@ORA817DEV> drop tablespace testing including contents;

Tablespace dropped.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create tablespace testing
  2  datafile '/tmp/testing01.dbf' size 1m reuse
  3  extent management local uniform size 128k
  4  /

Tablespace created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> alter tablespace testing
  2  add datafile '/tmp/testing02.dbf' size 1m reuse
  3  /

Tablespace altered.

ops$tkyte@ORA817DEV> drop table t;

Table dropped.

ops$tkyte@ORA817DEV> create table t ( x char(255) ) tablespace testing;

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> begin
  2     for i in 1 .. 20000000
  3     loop
  4          insert into t values (i);
  5          commit;
  6     end loop;
  7  end;
  8  /
begin
*
ERROR at line 1:
ORA-01653: unable to extend table OPS$TKYTE.T by 16 in tablespace TESTING
ORA-06512: at line 4

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select extent_id, file_id from dba_extents where
segment_name = 'T' and owner = user
  2  order by 1
  3  /

 EXTENT_ID    FILE_ID
---------- ----------
         0         10
         1         12
         2         10
         3         12
         4         10
         5         12
         6         10
         7         12
         8         10
         9         12
        10         10
        11         12
        12         10
        13         12

14 rows selected.

ops$tkyte@ORA817DEV> drop tablespace testing including contents;

Tablespace dropped.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create tablespace testing
  2  datafile '/tmp/testing01.dbf' size 1m reuse
  3  /

Tablespace created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> alter tablespace testing
  2  add datafile '/tmp/testing02.dbf' size 1m reuse
  3  /

Tablespace altered.

ops$tkyte@ORA817DEV> drop table t;
drop table t
           *
ERROR at line 1:
ORA-00942: table or view does not exist

ops$tkyte@ORA817DEV> create table t ( x char(255) ) tablespace testing;

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> begin
  2     for i in 1 .. 20000000
  3     loop
  4          insert into t values (i);
  5          commit;
  6     end loop;
  7  end;
  8  /
begin
*
ERROR at line 1:
ORA-01653: unable to extend table OPS$TKYTE.T by 93 in tablespace TESTING
ORA-06512: at line 4

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select extent_id, file_id from dba_extents where
segment_name = 'T' and owner = user
  2  order by 1
  3  /

 EXTENT_ID    FILE_ID
---------- ----------
         0         10
         1         12
         2         10
         3         12
         4         10
         5         12
         6         10
         7         12

8 rows selected.

i cannot reproduce using either of LMT with UNIFORM or DMT on 817 -- however,
using auto-allocate:

ops$tkyte@ORA817DEV> drop tablespace testing including contents;

Tablespace dropped.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create tablespace testing
  2  datafile '/tmp/testing01.dbf' size 1m reuse
  3  extent management local
  4  /

Tablespace created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> alter tablespace testing
  2  add datafile '/tmp/testing02.dbf' size 1m reuse
  3  /

Tablespace altered.

ops$tkyte@ORA817DEV> drop table t;
drop table t
           *
ERROR at line 1:
ORA-00942: table or view does not exist

ops$tkyte@ORA817DEV> create table t ( x char(255) ) tablespace testing;

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> begin
  2     for i in 1 .. 20000000
  3     loop
  4          insert into t values (i);
  5          commit;
  6     end loop;
  7  end;
  8  /
begin
*
ERROR at line 1:
ORA-01653: unable to extend table OPS$TKYTE.T by 128 in tablespace TESTING
ORA-06512: at line 4

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select extent_id, file_id from dba_extents where
segment_name = 'T' and owner = user
  2  order by 1
  3  /

 EXTENT_ID    FILE_ID
---------- ----------
         0         10
         1         10
         2         10
         3         10
         4         10
         5         10
         6         10
         7         10
         8         10
         9         10
        10         10
        11         10
        12         10
        13         10
        14         10
        15         12

16 rows selected.

I did, so use uniform extent sizes

just like I said:

Use a locally managed tablespace of course. Use a uniform extent size that
will cause the table to go into 100 or so extents. Create the table in that
tablespace.

Rating: 5
Excellent stuff. March 10, 2003
Reviewer: Dave from Colorado Springs
I'm implementing the poor-man's striping methodology (through round-robin extent
allocation) on a DW fact table on 9iR2, and in order to get the most out of the
excellent data segment compression feature I am defining the (LM) tablespace
with a 32K block size.

Can you confirm whether it makes sense to aim at an extent size that is a
multiple of (db_file_multiblock_read_count*32K) in this case?

I also have no hang-ups about segments having a great many extents, but where
would you draw the line -- i was thinking that 2048-4096 might be around the
most i wanted to deal with? (side note: i would like the extent size to be small
if possible as i want to get bitmap index partitions in the same TS as their
table partitions)

Thanks for any advice you can give with this.

Followup:
extent size should be a multiple of that yes.

in an lmt you really don't have to "deal" with extents -- they deal with
themselves.  2-4k of them would work.

why does the extent size matter for getting an index in the same tablespace??

Rating: 5
March 10, 2003
Reviewer: Dave from Colorado Springs
Thanks Tom,

The only reason for preferring a small extent size is that I have very many
(maybe >30) local bitmap indexes. Most of them will have very few distinct
values (in some cases only one or two distinct values per partition), so they
can be very compact. If it were not for this I would be happy to take 100
extents per segment, but that would involve some wasted space on the indexes. I
want them in the same TS as the table partitions in order to make TS Transport
feasible.

If the overhead on a couple of thousand extents is not significant, and it will
reduce my disk usage by 10%, then I'll go for that. 10% is not a hugely
significant amount but if I can put off the bureaucracy of needing any money for
more disks by 10% more minutes then it's effort well spent.

Thanks again.

Rating: 5
March 10, 2003
Reviewer: Dave from Colorado Springs
Whoa, just thought of a supplemental question.

The 32K TS is on a database where the "default" (is that the right terminology?)
block size is 8K. Does the db_file_multiblock_read_count apply as a multiple of
the 8K for all datafiles, or is it TS dependant -- ie. (32K*dfmrc) for 32K TS?,
and (8K*dmfrc) for 98K TS's on the same instance?

Followup:
Interesting question.  I set up this test (normal blocksize is 8k)

ops$tkyte@ORA920> alter session set db_file_multiblock_read_count = 8;
Session altered.

ops$tkyte@ORA920> alter session set events '10046 trace name context forever,
level 12';
Session altered.

-- flush buffer caches
ops$tkyte@ORA920> alter tablespace sixteen_k offline;
Tablespace altered.

ops$tkyte@ORA920> alter tablespace sixteen_k online;
Tablespace altered.

ops$tkyte@ORA920> alter tablespace users offline;
Tablespace altered.

ops$tkyte@ORA920> alter tablespace users online;
Tablespace altered.

-- this is in 8k users tablespace
ops$tkyte@ORA920> select /*+ full(b) */ count(*) from big_table.big_table b;

  COUNT(*)
----------
   1833792

-- this is in 16k tablespace
ops$tkyte@ORA920> select /*+ full(b) */ count(*) from big_table b;

  COUNT(*)
----------
   1833792

select /*+ full(b) */ count(*) from big_table.big_table b

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.12          0          4          3           0
Execute      1      0.01       1.26        197        196          4           0
Fetch        2      2.12      22.30      21795      22002          4           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      2.14      23.69      21992      22202         11           1

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  db file scattered read                       2774        0.63         21.16
********************************************************************************
select /*+ full(b) */ count(*) from big_table b

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.03          1          1          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      1.53       4.47      10853      10865          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      1.53       4.50      10854      10866          0           1

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  db file scattered read                       2714        0.04          3.45

so, both had the same number of IO waits more or less.  The max IO size on my
machine is 1m (tested that already)

Now, if we take the number of PIO's and divide:

ops$tkyte@ORA920> select 21795/8, 10853/4 from dual;

   21795/8    10853/4
---------- ----------
  2724.375    2713.25

we can see that the 8k blocksize tablespace did 8block IO where as the 16k did
4block IO.

Hence, they must be setting

IO_SIZE = dbmrc * default_blocksize = 64k on my system

and using that....  Looking at the raw trace file confirms this:

select /*+ full(b) */ count(*) from big_table.big_table b
END OF STMT
...
WAIT #3: nam='db file scattered read' ela= 1139 p1=9 p2=32737 p3=8
WAIT #3: nam='db file scattered read' ela= 315 p1=9 p2=33035 p3=8
WAIT #3: nam='db file scattered read' ela= 278 p1=9 p2=33043 p3=8
WAIT #3: nam='db file scattered read' ela= 283 p1=9 p2=33051 p3=8
WAIT #3: nam='db file scattered read' ela= 308 p1=9 p2=33059 p3=8
WAIT #3: nam='db file scattered read' ela= 381 p1=9 p2=33067 p3=8
WAIT #3: nam='db file scattered read' ela= 336 p1=9 p2=33075 p3=8
WAIT #3: nam='db file scattered read' ela= 356 p1=9 p2=33083 p3=8
WAIT #3: nam='db file scattered read' ela= 360 p1=9 p2=33091 p3=8
.........
select /*+ full(b) */ count(*) from big_table b
END OF STMT
...
WAIT #3: nam='db file scattered read' ela= 1633 p1=15 p2=9 p3=4
WAIT #3: nam='db file scattered read' ela= 1064 p1=15 p2=13 p3=4
WAIT #3: nam='db file scattered read' ela= 1788 p1=15 p2=17 p3=4
WAIT #3: nam='db file scattered read' ela= 785 p1=15 p2=21 p3=4
WAIT #3: nam='db file scattered read' ela= 911 p1=15 p2=25 p3=4
WAIT #3: nam='db file scattered read' ela= 783 p1=15 p2=29 p3=4
WAIT #3: nam='db file scattered read' ela= 842 p1=15 p2=33 p3=4
WAIT #3: nam='db file scattered read' ela= 797 p1=15 p2=37 p3=4

the first query in the 8k tblspace used reads of 8, whereas the second used
reads of 4 blocks.

Rating: 5
Better and better March 11, 2003
Reviewer: Dave from Colorado Springs
Great demo Tom.

Is it naive of me to think that the difference in db file scattered reads is
nearly entirely due to the different block sizes?

Followup:
If you mean the wait time -- not naive but incorrect ;)

Here, I just reran this:

select /*+ full(b) */ count(*) from big_table.big_table b

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.02       0.41        197        196          4           0
Fetch        2      1.68       4.73      21795      22002          4           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      1.70       5.15      21992      22198          8           1

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  db file scattered read                       2774        0.08          3.73

which is for all intents and purposes the same as the other 16k one.

So -- what happened?  How could this be?

It had everything to do with my test -- my test was to show the dbmrc -- not the
performance difference between the two ;)  I demonstrated that the IO size is
set -- and we use the IO size to read the data.

What happened here was:

a) i hadn't touched big_table.big_table in a while
b) I had been playing with ops$tkyte.big_table (it was "hot")
c) I'm using a cooked (ufs) filesystem, not raw.
d) my OS buffers cooked files.

Hence, when I read from big_table.big_table the first time, that was true PIO --
it really did go to disk.

queries against ops$tkyte.big_table were PIO's but they used the file system
cache to read from!  Hence the waits were dramatically different.

Just running the big_table.big_table query again -- "fixed" that.

This is why I always laugh when people say "we need to flush that buffer cache
in order to benchmark to get a baseline".  Unless they are using raw or have
mounted their disks specially to bypass the os buffer cache, they are only
kidding themselves (and even then, they are kidding themselves by flushing the
cache -- that is even more artificial then leaving the cache there as the cache
is never empty except right after startup and you do that what? once in a blue
moon)  anyway -- I digress....

This is a good example though of why moving to RAW can actually slow you down.
Suppose you had a database on UFS.  You look and say "wow, doing tons of PIO,
i've read raw is much faster, lets move to raw".  Next day performance is really
down the tubes.  What happened?

Well, by moving to raw you turned all of your PIO into true PIO, real live
honest disk reads as you've bypassed the OS filesystem cache!  In reality your
PIO wasn't all true PIO in the first place ;)  So now you are really reading
disks.   Now you make the false conclusion "raw is slow, they were wrong"....

feedback loop repeat endlessly...

Rating: 5
March 11, 2003
Reviewer: Dave from Colorado Springs
Now I don't know what's worse -- being incorrect or being naive. I saw the TS
offline/online and thought you had done that to flush the buffer cache. I see
now that I'm only damning myself more with every word.

Thanks again Tom

Followup:
well -- i did do that cause even getting a PIO from the OS buffer cache will
cause some wait.  I wanted it all to be PIO's -- the alter tablespace does that
-- but the speed difference was due to other external differences ;)

Rating: 4
July 07, 2003
Reviewer: Vipin from New york
Hi Tim,

Can't we acheive the stripping in yet another way say by the following methode:-

Using CREATE TABLE /*+ PARALLEL (DEGREE N)*/ tablespace tb1 AS SELECT * FROM
..option

Here the data file for the tablespace tb1 into which the table is craeted will
be stripped across different disks.

Please correct me if i am wrong.

Followup:
you would

a) need a tablespace with more then 1 file
b) not need parallel

say you have a tablespace with 4 files -- an LMT with uniform extents.  Extent 1
will be in file 1, extent 2 in file 2, extent 3 in file 3, extent 4 in file 4,
extent 5 in file 1 and so on.

you just need to load the data to achieve that.  even with LMTs that are
autoallocated, it'll put the first couple of small extents in the same file and
then start moving from file to file.

DMT's will do the same thing -- round robin around the available files.

Rating: 4
July 08, 2003
Reviewer: Vipin
Hi Tom,

The method you said is the perfect way to do that, but what my doubt is, can't
we do it using the method described above also.

I just wanted to confirm that my understanding about this was correct.

Followup:
creating the table in parallel would work as well, yes.  my point was, you don't
*need* parallel in order to do it.  It is all about extents.

Rating: 5
July 08, 2003
Reviewer: Vipin
Thanks Tom

Rating: 4
alter tbs offline ? July 09, 2003
Reviewer: Alvin from Philippines
What does taking a tablespace offline do ?

1. does it flush all the dirty blocks in the buffer cache to disk ?
2. and remove all the entries of all segments residing on that tablespace from
memory ?
3. Do alter tablespace begin backup have the same effect ? or does it just
freeze the scn ?

Followup:
1) yes it would

2) depends on the version -- in all it "invalidates" them at least.  in 8i, they
appear to "go away", in 9i, they'll be in v$bh still but can be considered "not
used blocks" since we don't use them when you online the tablespace

3) The dirty buffers that belong to the database files are written out to the
files and the datafiles are checkpointed.  The datafile headers are updated to
the SCN captured when the begin backup is issued.  The datafile headers are not
updated until the 'alter tablespace end backup' command is issued; however, the
datablock within the database files can continue to be read and updated.

July 09, 2003 Sushmita -- Thanks for the question regarding "Partition", version 8.1.5
You Asked
Hi,

    If i have got a single disk in which i have a single
tablespace for data. and i have a large table of which i create
ten partitions in the same tablespace . will my query run fast
on having partition as compared to single table, though i have a
single tablespace?

For partition , it is always suggested to have partition on
differrent disks. but if created on same disk, on will there be
increase in performance?

thanks and regards,
Sushmita
and we said...
It is not a requirement to have >1 disk to make use of
partitions.  If you full scan this table frequently -- you can
use partitions on a single disk to utilize partition
elimination.  Say you have partitioned the table on the column
"X" into 10 partitions and issue the query:

select count(*) from t where X = 5;

We can eliminate 9 of the 10 partitions when doing the query.
So yes -- it can still help....  It may help as well if you do
parallel DML on the table.
Review & Followup
Rating: 4
Some more Info Please July 09, 2003
Reviewer: sips from india
Dear Tom,

From your explanation, i got that even having all the partitions in a single
tablespace will help if we query that particular column using where clause.

but where iam stuck up is , you told it will help in parallel DML also. Could
you please elabore this.

Followup:
in order to effeciently perform PDML (and in 8i in order to perform PDML at all)
the table is partitioned.  The PDML will simply work against a partition at a
time

July 09, 2003 Tony -- Thanks for the question regarding "Simple but could be handy", version 8.1.6
You Asked
Tom,
Is there any option in sql*plus where desc is done with order by column names?
Sometimes there are plenty of column names, and if you are asked to extract few
columns data, one start scanning all column names, till you find one, and repeat
the process again and again. Instead, if the desc is by order, it is easy to
pick.
Thanks
and we said...
I sometimes use this script -- dumps indexes as well as columns.  It orders by
column_id but trivial to make it order by column_name.

I call it desc.sql and put it in the directory my SQLPATH environment variable
points to.  Then i can just:

SQL> @desc emp

instead of desc emp

set verify off
set linesize 72
set pagesize 9999
set feedback off

variable owner varchar2(30)
variable tname varchar2(30)
begin
  :owner := USER;
  :tname := upper('&1');
end;
/

Prompt Datatypes for Table &1

column data_type format a20
column column_name heading "Column Name"
column data_type   heading "Data|Type"
column data_length heading "Data|Length"
column nullable    heading "Nullable"

select column_name,
       data_type,
       substr(
       decode( data_type, 'NUMBER',
               decode( data_precision, NULL, NULL,
                '('||data_precision||','||data_scale||')' ),
                   data_length),
              1,11) data_length,
       decode( nullable, 'Y', 'null', 'not null' ) nullable
from all_tab_columns
where owner = :owner
  and table_name = :tname
order by column_id
/
prompt
prompt
Prompt Indexes on &1
column index_name heading "Index|Name"
column Uniqueness heading "Is|Unique" format a6
column columns format a32 word_wrapped

select substr(a.index_name,1,30) index_name,
       decode(a.uniqueness,'UNIQUE','Yes','No') uniqueness,
max(decode( b.column_position,  1, substr(b.column_name,1,30),
NULL )) ||
max(decode( b.column_position,  2, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position,  3, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position,  4, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position,  5, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position,  6, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position,  7, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position,  8, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position,  9, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position, 10, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position, 11, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position, 12, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position, 13, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position, 14, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position, 15, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position, 16, ', '||
substr(b.column_name,1,30), NULL )) columns
from all_indexes a, all_ind_columns b
where a.owner = :owner
and a.table_name = :tname
and b.table_name = a.table_name
and b.table_owner = a.owner
and a.index_name = b.index_name
group by substr(a.index_name,1,30), a.uniqueness
/
Review & Followup
Rating: 5
Desc Table With Columns Ordered December 04, 2002
Reviewer: Gene Charles from Jacksonville, FL USA
Great code - I added the && so it would only ask me for the table once. Just
change the first & in the code to &&. Also, if you make this change be advised
the next time you run it in the same session it will use the previous table.

To get around this start it with the new table as a parameter, e.g. SQL>@desc
sometable.

Rating: 4
Great!! July 09, 2003
Reviewer: Anurag from INDIA
Tom, can U give me a code for substr() function. I want to create a function
similar to substr(), but could not. Can U help???

Followup:
sure,

create or replace function my_substr( p_string in varchar2, p_from in number,
p_for in number ) return varchar2
as
begin
   return substr( P_string, p_from, p_for );
end;
/

:)  that was a joke.  If what you want is SIMILAR, yet different from substr,
having the code for substr would not be very useful.  But in plsql -- to write a
substr, one would be USING substr itself.  chicken and egg.

Rating: 4
For Anurag July 09, 2003
Reviewer: A reader
Hi Anurag,
I think you need substr code for academic interest.
But, tell you what,
It would be in C and hence difficult to understand.
Moreover, its code is IPR(Intellectual Propriety rights) of Oracle Corp. I hope,
you understand, that sharing the IPR could lead to dire consequences for an
employee.
And, if oracle shares it with us then, it will become open source and then
sooner or later there  won't be any oracle. MySQL will become the king of
databases.

July 09, 2003 Pablo -- Thanks for the question regarding "Deleting many rows from a big table", version 8.1.5
You Asked
Tom:

We have a 6 millons rows table and we need to clean it. This process will delete
1,5 millons.

My first approach was create a SP with this lines:

 SET TRANSACTION USE ROLLBACK SEGMENT Rbig;
 DELETE FROM CTDNOV WHERE CTDEVT IN (4,15);  (1,5m rows)
 COMMIT;

Then I submited a job to run the SP at night. This process took more than 4
hours to finish and generated
a huge amounts of archives.

2nd Approach (i didn't test it, i think this is slower than the 1st approach.
According to you book is
faster one commit at the end than a lots of m/n-rows commits)

from Metalink

Create PL/SQL procedure:

CREATE OR REPLACE PROCEDURE delete_tab (tablename IN VARCHAR2,
                                            empno IN NUMBER  ,
                                            nrows IN NUMBER    ) IS

 sSQL1    VARCHAR2(2000);
 sSQL2    VARCHAR2(2000);
 nCount    NUMBER;

BEGIN

  nCount := 0;
  sSQL1:='delete from '|| tablename ||
         ' where ROWNUM < ' || nrows || ' and empno=' || empno;
  sSQL2:='select count(ROWID) from ' || tablename ||
         ' where empno= ' || empno;

  LOOP

    EXECUTE IMMEDIATE sSQL1;

    EXECUTE IMMEDIATE sSQL2 INTO nCount;

    DBMS_OUTPUT.PUT_LINE('Existing records: ' || to_char(ncount) );

    commit;

    EXIT WHEN nCount = 0;

  END LOOP;

END delete_tab;
/

3. Execute above created procedure

SQL> execute delete_tab('big_emp',7369,5000)
Existing records: 60537
Existing records: 55538
Existing records: 50539
Existing records: 45540
Existing records: 40541
Existing records: 35542
Existing records: 30543
Existing records: 25544
Existing records: 20545
Existing records: 15546
Existing records: 10547
Existing records: 5548
Existing records: 549
Existing records: 0

PL/SQL procedure successfully completed.

3th Approach (it's seems to be the better choice)

  When a DELETE is issued, Oracle stores the whole deleted row in the rollback
segments,
so you can undo the changes later, if you want to. So there is an image of the
rows in rollback
which are currently not present in the table. Hence the phrase 'before image'.
Now all the rollback
blocks are written to the redo log files too. So you have the data blocks with
the table (without
the deleted rows, of course) and the rollback blocks with the old image both
producing redo,
which accounts for additional archive logs. I cant comment on the ratio of
deleted records to ratio of
redo produced, though.
  If you do an insert, only the rowid is stored in the rollback segs, which
generates less redo.
Depending on the number of rows in your tables, it may be better for you to
insert the records you
dont want to delete into a temp table, truncate the main table and move the
records back to the main.
This would probably generate less redo than delete, but that depends on number
of rows.

Above, i describe the table i want to clean

from dba_segments

SEGMENT_NAME                                EXTENTS      BYTES
---------------------------------------- ---------- ----------
CTDNOV                                           26 2276614144

from dba_extents

SEGMENT_NAME                              EXTENT_ID     Kbytes
---------------------------------------- ---------- ----------
CTDNOV                                            0        520
                                                  1        520
                                                  2        800
                                                  3       1200
                                                  4       1800
                                                  5       2680
                                                  6       4000
                                                  7       6000
                                                  8       9000
                                                  9      13480
                                                 10      20200
                                                 11      30280
                                                 12      45440
                                                 13      68136
                                                 14     102240
                                                 15     153312
                                                 16     229968
                                                 17      20480
                                                 18      30720
                                                 19      46080
                                                 20      69120
                                                 21     103680
                                                 22     155520
                                                 23     233280
                                                 24     349920
                                                 25     524880

I'm interesting about your opinion, what do you think is the best way to
clean(delete millons of rows) a big table?. How deallocate extents from the
table?, how generate the minimum amount of redo's?

Thanks in Advance and sorry for my english
Pablo R.
and we said...
Well, #2 is the slowest way and generates the most amount of redo by far.

#1 is my preferred way unless you want to get more radical (see below).

The problem with #3 is you are forgetting about indexes, you can do a direct
path INSERT /*+ APPEND */ to move the data, but the indexes would still be
maintained and they generate gobs of redo.  So, in this case the insert might do
as much as the delete.

Other cases to consider:

case1:
create table new_table unrecoverable as select * from old_table where ....;
drop table old_table;
rename new_table to old_table;
create index old_table_idx1 on old_table(c1,c2) unrecoverable parallel 5;
.....

NO log on that, just move the data to a new table, drop/rename old/new, create
indexes as fast as possible without log.

case2:
partition the data, do a parallel delete.  Each partition will use its own
rollback segment, each will run in parallel.

case3:
partition the data so that you can do a DROP partition instead of DELETE.
Review & Followup
Rating: 4
Deallocating tables January 02, 2002
Reviewer: Pablo Rovedo from Argentina
Tom: Regarding with the 3 methods described above, #1,#2 don't deallocate space
(extents) however the last approach allows redefine the table storage (reset
HWM), so we recover tablespace space.

Followup:
so?  in general, if you delete a million records, you are going to put them back
sometime won't you.  You only reclaim that space for a brief moment of time.
You were talking like this is an ongoing requirement -- if so, reclaiming the
space is sort of pointless.

if you like #3, you should REALLY like my twist on it then:

create table new_table unrecoverable as select * from old_table where ....;
drop table old_table;
rename new_table to old_table;
create index old_table_idx1 on old_table(c1,c2) unrecoverable parallel 5;

it would be faster then any of the others, "reclaims" the maximum space, and
generates the smallest redo/undo.  Satisfying all of your requirements.

Rating: 4
Deleting Rows January 03, 2002
Reviewer: Anand Gopalan from Chennai,India
Tom ,

We faced a similar situation , where in we had to delete millions of rows based
on ceratin date. i.e. to delete all rows in the table where the date is < less
than target date. Since ours is a continuouslu updated table we could not follow
approach 1 , i.e. usinng unrecoverable option as we would loose rows that are
inserted after creating a temp table from the existing one. so What we did is a
PL/SQL script

cursor
select rowid from table

In loop

delete from table where rowid = rowid

and do a commit after selected number of rows

say if count > 500
commit.

This way we avoid rollback segement contention and at the same time we also
avoid frequent commit ( instead of commit for every row)

This solution really works fast for us.

Rating: 5
Hybrid possibility January 03, 2002
Reviewer: Dave
As a variation on the partitioning and the CTAS options, in the past I have
defined the table to have a single partition, used CTAS to create a table of the
same structure with the data to be kept, and performed a partition exchange to
swap the original and the new data sets. The advantage for me was that I could
use the "excluding indexes" option, and perform index rebuilds instead of coding
the index creation scripts. Less maintenance if indexes are added, dropped or
modified.

Oh, and it also handily kept the old data set handy in case of a screwup.

Rating: 4
How to delete if the "redo" entries are not necessary December 04, 2002
Reviewer: Bill from Toronto, Canada
Hi Tom:

I have several tables (about 1 million rows) that I want to delete some of the
records (20,000 to 50,000 rows) for sure. I want a quick DELETE and there is no
requirement to generate rollback data because the data are definitely not
needed. What do you think is the best way to delete them ? I mean I could use
regular delete statement but not sure if I could even TURN OFF the redo entries
generated (before image) ? Any thoughts. Thanks.

Followup:
ARG -- why do people always say "there is no requirement for redo/rollback --
the data is not needed".

What about the data you are NOT deleting, is that data needed???? If not, just
truncate and be done with it.  If it is needed -- then you do in fact need
ROLLBACK and REDO.  redo/Rollback isn't just to restore the data you deleted in
this case, it is there to put the database table BACK the way it was so it is
not some scrambled bunch of bits and bytes in the middle of a change that is
totally unusable after a crash.  That table is a data structure -- if you crash
or fail in the middle of modifying it -- it is left in an unusable, dangerous
state.  That is what redo/rollback is all about -- protecting the data that is
left behind, as well.

they are definitely NEEDED

So, now that that is out of the way -- that redo/rollback is DESIRABLE and
unavoidable -- we'll look at why rollback isn't even the problem here -- it is
index maintanance.

Deleteing 20-50k rows without indexes -- fast.

big_table@ORA920.US.ORACLE.COM> select count(*) from t;

  COUNT(*)
----------
   1876416

big_table@ORA920.US.ORACLE.COM> set timing on
big_table@ORA920.US.ORACLE.COM> delete from t where owner in ( 'SYSTEM', 'WKSYS'
);

42560 rows deleted.

Elapsed: 00:00:13.82

so, no worries there.  pretty fast.  but...

big_table@ORA920.US.ORACLE.COM> create index t_idx2 on t(object_id);

Index created.

Elapsed: 00:00:26.92

big_table@ORA920.US.ORACLE.COM> create index t_idx1 on t(timestamp);

Index created.

Elapsed: 00:00:38.72

big_table@ORA920.US.ORACLE.COM> delete from t where owner in ( 'SYSTEM', 'WKSYS'
);

42560 rows deleted.

Elapsed: 00:00:29.49

The run time is almost tripled for the delete (still, pretty darn fast -- and
this is on my desktop PC, a p4 with 512meg and a single ide disk -- perhaps this
is as they say "much ado about nothing").  It is the index maintenance, not
redo/rollback that is an issue here.

You can use the approaches above however, in your case, I would say this is a
couple of second, maybe minutes operation depending on the WIDTH of the data and
the number of indexes you have to maintain.  I would just do the delete.

Rating: 4
How to delete if the "redo" entries are not necessary December 04, 2002
Reviewer: Bill from Toronto, Canada
Sorry, forgot to mention. Some of the tables contain Indexes.

Rating: 4
Another approach March 11, 2003
Reviewer: Praveen from Bangalore
Tom,

Why do you think that the following approach is not the fastest one ?

Create a new table with only data that are NOT to be deleted and then drop the
original table.

Thanks and regards

Praveen

Followup:
umm, where did you get the idea that my suggestion (made in the original answer)
would not be fast?

...
Other cases to consider:

case1:
create table new_table unrecoverable as select * from old_table where ....;
drop table old_table;
rename new_table to old_table;
create index old_table_idx1 on old_table(c1,c2) unrecoverable parallel 5;
........

given 1, 2, and 3 to choose from -- I would use #1.

If not constrained to their 1,2,3 -- I would use one of my case 1, case 2, case
3.

Rating: 4
partition-based parallel delete/update? July 08, 2003
Reviewer: chuan sat from VA USA
Tom
      You are extremely good!

      You mentioned somewhere above that if we delete data from muliple
partitions, the deletions will happen in parallel per partition.     Is this
automatic? or do I have to specific 'parallel' keyword somewhere in the delete
statement. Is the UPDATE also happening in parallel in partitioned table?

Thanks so much

Followup:
you have to be doing parallel dml (pdml)

see the server concepts guide -- they have a really good chapter on this topic.

Rating: 4
parallel dml on non-partitioned big table July 09, 2003
Reviewer: Ling from Toronto,canada
I don't understand why Oracle doesn't provide parallel dml(update and delete) on
non-partitioned big table. Technically, it isn't so hard: you just simulate the
way parallel query works, that is, you spawn some session, each scan a portion
of whole table, delete/update the rows that satisfy your condition. Actually, I
wrote such a utility using shell and sqlplus and it works fine. Of course, it
would be more decent to write it in Pro*C. The commit limitation on pdml on
partitioned table also applies

Followup:
we do.  see
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96520/tuningpe.htm#64628
the table only needs to be partitioned if there is a bitmap index

technically it is very hard -- think about it for a while.  consider

o integrity
o uniqueness

and the fact that you could easily -- quickly -- self deadlock yourself.

July 09, 2003 Nethanel -- Thanks for the question regarding "using LIKE predicate in SELECT ... FROM [CLOB field]", version Oracle8i
You Asked
Hi!

I have stored procedure that worked in MS SQL Server, like

SELECT {LONGVARCHAR field} FROM ...
WHERE ... LIKE '%Hi Tom%'.

After I transferred My DB to Oracle 8i, in this table
LONGVARCHAR
was replaced with CLOB. Now, when I running above-mentioned SQL,
I accept an error "Inconsistent data types". (I'm using Oracle
8i ODBC Driver)

The question is:
How can I execute the statement properly in Oracle too?

Thank you very-very much!

Me;)
and we said...
You can use functions found in the DBMS_LOB package on these
CLOBS in SQL (or even write your own in PLSQL if you wish).
There is not direct support for the LIKE operation however --
your query above may be written as:

select * from T where dbms_lob.instr(LOB_COLUMN,'Hi Tom') > 0;

Now, if you wanted to implement your own islike (the instr above
will always be much much faster), it might look like this:

create or replace
function isLike( p_clob in clob, p_str in varchar2 )
return number
as
    l_text    long;
    l_winSize number default 30000;
begin
    for i in 1 .. trunc(dbms_lob.getlength(p_clob)/l_winSize)+1
    loop
        l_text := dbms_lob.substr( p_clob,
                      l_winSize+2000, 1+(i-1)*l_winSize );
        if ( l_text like p_str )
        then
            return 1;
        end if;
    end loop;
    return 0;
end;
/

You can then query:

select * from T where islike( clob_col, '%Hi Tom%' ) > 0;
Review & Followup
Rating: 5
July 09, 2003
Reviewer: mary
Tom, is instr always faster for other datatypes (i.e. varchar2)?  I mean, if I
have a search query which includes something similar to "WHERE firstname LIKE
'%SMITH%' or lastname LIKE '%SMITH%'" , and the datatype is varchar2, would it
be better to use instr than like?

Also, are indexes used as long as there is one for each column queried in the
where clause if LIKE or INSTR is used? (no functions used against columns)?

Followup:
select *
from
 big_table.big_table where object_name like '%SMITH%' or object_name like
  '%FOO%'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.05          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      6.43      14.06      50526      50549          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      6.44      14.11      50526      50549          0           0

select *
from
 big_table.big_table where instr(object_name, 'SMITH') > 0 and
  instr(object_name, 'FOO') > 0

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      6.27      14.17      50526      50549          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      6.28      14.18      50526      50549          0           0

It does not appear to make a measurable difference and instr could be much
slower since like has a chance to use an index when appropriate (and it is
unlikely a function based index would be of any use here at all)

July 09, 2003 Robin -- Thanks for the question regarding "Using Rebuild parallel option on large table index with 6 CPU's", version 8.0.4.0.0
You Asked
Tom,

In order to tune the rebulding of an index I have experimented
with the "REBUILD PARALLEL 6" option while rebuilding an index
on a large table; e.g.

"ALTER INDEX index_name REBUILD PARALLEL 6"

Are there any side affects to using the parallel option on the
index structure (is it the same/as accurate - as if the parallel
option was ommitted?) apart from increasing speed the processing
speed?

Also the server which the database runs on has 6 CPU's.  Should
I be using "REBUILD PARALLEL 6" or "REBUILD PARALLEL 3" in the
clause as I read Oracle server uses an equal number of CPU's (or
is it server processes?) to scan and build the new index.

Regards,

Robin
and we said...
I would "rebuild parallel 12" actually, with the NOLOGGING
option if I could.

We'll work better where you have more processes then CPUs.
Especially with indexing.  indexing is IO intensive.  We'll have
lots of times where we are waiting on IO and not using the CPU.
Having more processes then CPUs will help utilize them fully.
This assumes of course you are doing this off hours.

The index will fundementally be the same after the parallel
index rebuild.
Review & Followup
Rating: 3
November 08, 2002
Reviewer: leo from TX, USA
The index parallel degree was changed after rebuilding it parallel.  Here is my
test with 8.1.7.0.0

SQL> select table_name,index_name,degree from dba_indexes
  2  where index_name='I_PLC_CUSTID_UK';

TABLE_NAME                     INDEX_NAME
------------------------------ ------------------------------
DEGREE
----------------------------------------
PLC                            I_PLC_CUSTID_UK
1

SQL> alter index loa_data.I_PLC_CUSTID_UK rebuild online;

Index altered.

Elapsed: 00:01:25.03

SQL> select table_name,index_name,degree from dba_indexes
  2  where index_name='I_PLC_CUSTID_UK';

TABLE_NAME                     INDEX_NAME
------------------------------ ------------------------------
DEGREE
----------------------------------------
PLC                            I_PLC_CUSTID_UK
1

SQL> alter index loa_data.I_PLC_CUSTID_UK rebuild online parallel 4;

Index altered.

Elapsed: 00:00:56.04
SQL> select table_name,index_name,degree from dba_indexes
  2  where index_name='I_PLC_CUSTID_UK';

TABLE_NAME                     INDEX_NAME
------------------------------ ------------------------------
DEGREE
----------------------------------------
PLC                            I_PLC_CUSTID_UK
4

Followup:
easy enough to set it back. alter index iname parallel 1

Rating: 4
something special to tune? January 23, 2003
Reviewer: zhu from cn
Hi, tom:
  I am testing how to build up the index faster during database reorg.But
however, I failed to figure it out:
  Test system has 8CPU/8GRam/8Disk Raid5.
   I tried to build index in parallel with nologging, but in parallel is even
slower than single thread:
1 thread nologging: 1Minute and 22 second.
6 thread nologging: 1 Minute adn 30 second.
12 Thread nologging: 1 Minute and 40 Second.
  No one else using this machine.
And my statspack report during parallel index build:

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
direct path read                                   64,594      179,134   41.04
PX Deq: Table Q Normal                             88,100      174,969   40.09
PX Deq: Execute Reply                                 666       61,336   14.05
PX Deq: Execution Msg                                 731       16,122    3.69
control file parallel write                           249        2,139     .49

Can you please give me some idear? IO is 100% busy though, during index
build.But from the wait event report,only direct path read is io related, and
raid5 is good for read.

Followup:
an index that builds in 1 minute?

Ignore it, find bigger fish to fry.  give me an index that takes some measurable
amount of time

1 minute -- not long enough to even consider trying to speed up for an index
rebuild.

Rating: 4
Add some data January 23, 2003
Reviewer: zhu from cn
parameter:
min_server:0
max_server:12
it is using 12 thread when rebuilding with parallel 12.
And large pool size is 30MB, but after index build, from v$sgastat , the free
large pool is still 30MB?

Followup:
and the problem with that is?

Rating: 4
1 minute is too fast? January 24, 2003
Reviewer: zhu from cn
Now I am testing table with 8M records(the table with largest records in my
system).
And I am surprised to find:
SQL> CREATE INDEX idx_bidid ON productbidinfo(bid_id)
  2  PARALLEL (DEGREE 2)
  3  NOLOGGING
  4  /

Index created.

Elapsed: 00:04:52.37
SQL>
SQL> DROP INDEX idx_bidid
  2  /

Index dropped.

Elapsed: 00:00:00.21
SQL>
SQL> CREATE INDEX idx_bidid ON productbidinfo(bid_id)
  2  PARALLEL (DEGREE 2)
  3  NOLOGGING
  4  /

Index created.

Elapsed: 00:02:47.91
SQL>
SQL> DROP INDEX idx_bidid
  2  /

Index dropped.

Elapsed: 00:00:00.17
As far as I know, parallel thread use direct path read, which means that it does
not go into sga, so why the second time, it runs faster?
And it seems that when i use parallel degree 2, oracle is actually using 4 pxxx
running, maybe 2 for read , and 2 for sorting?

Followup:
or more likely your file system has a buffer cache -- that is what is happening
here.

Rating: 4
How is index created in parallel July 09, 2003
Reviewer: Doug from CT, USA
Someone here thinks that unless the index is on a partitioned table, the only
thing subdivided in parallel is the FTS to make the index.  That's not true, is
it?  Since I imagine it's not.. can you elaborate on the mechanism by which an
index is built in pieces and then merged together seemlessly?

Thanks.

Followup:
that is not true.
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96520/tuningpe.htm#63440

July 09, 2003 Angelo -- Thanks for the question regarding "ASO or SSL or BOTH", version 8.1.7
You Asked
Tom,

I have implemented Oracle Advanced security option on an oracle 8.1.7 database.
It works fine when connect to the databse thru SQL Plus or some other client
application.  The problem I am having is when I try to connect to the database
thru a web page written in coldfusion, and the coldfusion webserver runs on the
same box as the database.  Do I also need to implement SSL for the webpage to
acces the database?
and we said...
Well, if you want to secure the contents of the generated web page, you would
need SSL between the browser and the webserver AND aso between the webserver and
the database.

If you have ASO between the webserver and database but no SSL between the
browser and webserver -- I can just snoop your webpages to gain access to any
information you send back and forth.
Review & Followup
Rating: 3
September 09, 2002
Reviewer: A reader

Rating: 3
September 09, 2002
Reviewer: A reader
I have just encryption turned on for client server(3des168)
When I attempt to log into the application with the same u/p as I use for SQL
PLUS I get invalid username/password.
When I turn off ASO/encryption I get in with no problems.
Any thought on that?

Rating: 3
September 09, 2002
Reviewer: A reader
I guess what I'm trying to figure out is how to configure ASO so I can also get
in thru the webserver>

Followup:
what version of the client software is coldfusion using.

have you used the sqlplus that comes with cold fusion's Oracle client install to
test this?

Rating: 3
ASO OR SSL September 09, 2002
Reviewer: Angelo cappelli from ROME, NY
We are running coldfusion studio 4.5.2 that runs on Coldfusion module version 5
for Apache 1.3.24.  So basically the coldfusion webserver is a mod for apache.

Do think I might have to configure the ODBC for Oracle that is provided with
coldfusion?

Followup:
Yes, but coldfusion is using some Oracle client software -- what version is
that.

Rating: 3
ASO or SSL September 09, 2002
Reviewer: A reader
Looking at the Coldfusion admin manual it states that coldfusion uses native
database drivers for Oracle 8.1.6. and higher, the database is an 8.1.7 database
so I must conclude that it is the 8.1.7 client

Followup:
Ok (you do have a TAR open with support right???), if you log into the account
where cold fusion is installed and run the sqlplus that is in that environment,
do you have any issues using ASO there.

Rating: 3
ASO or SSL September 09, 2002
Reviewer: A reader
My Bad, it is and Oracle 8.0 ODBC driver that is being used not an 8.1.7 client

Followup:
Ok, is the 8.0 client setup for ASO then?

Rating: 3
ASO or SSL September 12, 2002
Reviewer: A reader
Sorry have been gone a few days, I guess I'm going to have to dig into this
further to undersand exactly what apache server is running and then how to get
it to work with ASO.

Thanks for all.

Rating: 5
ODBC in SSL? July 09, 2003
Reviewer: A reader
Can ODBC connections be set up via SSL?  If so can you explain . .

Followup:
in theory -- should be no problem.  If you can set it up so that sqlplus can do
it (set up ASO), then any client that used the client libraries on that system
could do it.

Having never actually done it with ODBC and having no machines with odbc on them
- i cannot actually "test it"

July 09, 2003 Orna -- Thanks for the question regarding "Direct insert and parallel insert", version 8.1.5
You Asked
Hi Tom,

After reading your answers on the subject I still have some unclear points I
would appreciate if you can clarify :

1. Can a direct insert or parallel inserts be used in insert statements other
then : INSERT AS SELECT ...?

2. As I understand the above methods do not log undo and redo information . if
so - what is the advantage to further use the NOLOGGING option ?

3. Can you comment on the desired work flow when working with the above methods
in production environment ? e.g . always perform a cald backup after using these
insert methods .
Do you have standard recommandations ?

4. When tracing the CPU and elapsed times on DIRECT and PARALLEL inserts - I've
noticed a considerable amount of unexplainable ( to me ) 'elapsed' time , while
the 'CPU' time was very short , and the 'disk' was also negligable - can you
comment on that  ?

Thank you very much for your time
Orna
and we said...
1) no, you need to use insert /*+ append */ into t select .... to do a direct
path insert.  You would not ever want to do it for a "VALUES" statement anyway
as each row would go above the HWM and never reuse existing free space (eg: each
row would go into its own block).  It does not use existing space.  It is
suitable only for adding "large" amounts of data to an existing table.

2) see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:485221567528
The advantage of nologging is that when performing the operations that can make
use of it such as direct path loads, create table as select, create index and so
on -- significantly less redo is generated for the operation, potentially
speeding it up.

3) perform a backup, we don't care if it is hot or cold.  If you do not backup
and have a failure, you will have to redo the unrecoverable operation such as an
index build yourself after recovering the rest of the data.

4) you were blocked waiting for something.  Anytime the CPU is significantly
smaller then the elapsed -- you were waiting for something during that time.
Another user might have had the resource, you might have been waiting for IO,
etc.  You would use the v$ tables or utilities such as utle/bstat or statspack
or Oracle Enterprise Manager (OEM) performance packs to diagnose what these wait
events might have been.
Review & Followup
Rating: 3
does nologging really works..? February 06, 2002
Reviewer: A reader
Hi

I was trying to speed up a DWH loading process using direct insert and nologging
mode however from stats I see no difference between logging and nologging

SQL> alter table t logging;
Table altered.

Elapsed: 00:00:00.05
SQL>  insert /*+ APPEND */ into t select * from bscs_own.bscs_sogsh nologging;
893681 rows created.

Elapsed: 00:03:23.23

Statistics
----------------------------------------------------------
        140  recursive calls
       1402  db block gets
      14108  consistent gets
      14092  physical reads
      42536  redo size
       1171  bytes sent via SQL*Net to client
        778  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          7  sorts (memory)
          0  sorts (disk)
     893681  rows processed

SQL> insert /*+ APPEND */ into t select * from bscs_own.bscs_sogsh;

893681 rows created.

Elapsed: 00:02:39.63

Statistics
----------------------------------------------------------
          2  recursive calls
       1401  db block gets
      14094  consistent gets
      14091  physical reads
      42536  redo size
       1171  bytes sent via SQL*Net to client
        767  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          4  sorts (memory)
          0  sorts (disk)
     893681  rows processed

----- here it´s faster without nologging clause I wonder why?-----

SQL> alter table t nologging;
Table altered.

Elapsed: 00:00:00.08

SQL> insert /*+ APPEND */ into t select * from bscs_own.bscs_sogsh;

893681 rows created.

Elapsed: 00:03:05.01

Statistics
----------------------------------------------------------
        140  recursive calls
       1402  db block gets
      14108  consistent gets
      14090  physical reads
      42536  redo size
       1171  bytes sent via SQL*Net to client
        767  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          7  sorts (memory)
          0  sorts (disk)
     893681  rows processed

All insert generate same redo.... am I missing something here?

Followup:
I'll guess that you are in noarchivelog mode and you are on a system where other
stuff is going on.

In noarchive log mode the append hint is all you need to bypass redo generation.
 In archive log mode, it makes a difference:

ops$tkyte@ORA8I.WORLD> select log_mode from v$database;

LOG_MODE
------------
ARCHIVELOG

ops$tkyte@ORA8I.WORLD> create table t as select * from all_objects where 1=0;
Table created.

ops$tkyte@ORA8I.WORLD> set autotrace on statistics

ops$tkyte@ORA8I.WORLD> alter table t logging;
Table altered.

ops$tkyte@ORA8I.WORLD> insert /*+ append */ into t select * from all_objects;
34755 rows created.

Statistics
----------------------------------------------------------
        903  recursive calls
        120  db block gets
     472331  consistent gets
       3392  physical reads
    3871128  redo size
        991  bytes sent via SQL*Net to client
        827  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
         17  sorts (memory)
          0  sorts (disk)
      34755  rows processed

ops$tkyte@ORA8I.WORLD> commit;
Commit complete.

ops$tkyte@ORA8I.WORLD> ops$tkyte@ORA8I.WORLD> alter table t nologging;
Table altered.

ops$tkyte@ORA8I.WORLD> insert /*+ append */ into t select * from all_objects;
34755 rows created.

Statistics
----------------------------------------------------------
        335  recursive calls
        123  db block gets
     472242  consistent gets
       1704  physical reads
      10504  redo size
        994  bytes sent via SQL*Net to client
        827  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          5  sorts (memory)
          0  sorts (disk)
      34755  rows processed

so, it makes quite a bit of difference.   as for the difference in response
times:

3:23
2:39
3:05

I'll write that off to "there was other stuff being done on the system at the
same time".  You had to wait for IO (lots in the first run, least in the second
and a moderate amount in the third).  The system was busy servicing other
people.

Use sql_trace and tkprof to prove this -- if you see a disparity between cpu and
elapsed -- you are seeing "waits" for something.  If you have my book -- see
chapter 10 and it'll show you how to see what these waits were (in your case, I
would guess IO)

Rating: 4
Do you need alter session enable parallel dml with append hint April 22, 2002
Reviewer: Rob from Pittsburgh, PA
Tom

In order to get parallel writes with an
insert select stmt. is it necessary to use both the
append hint and alter session enable parallel dml.
Will either of those two cause a direct path insert
w/o the other?

Followup:
read:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/c21dlins.htm#2507
the append hint is optional.

Rating: 3
New Extend April 23, 2002
Reviewer: kiro from Bulgaria
Hi Tom
Please see example below.

REM tablespace user1 local managed uniform extend 20k
REM Oracle version 9.0.1 on Win2000

SQL> drop table test_dir;

Table dropped.

SQL> create table test_dir
  2   (id number,
  3   status varchar2(20),
  4   cd_text varchar2(20))
  5  tablespace user1
  6  storage(pctincrease 0);

Table created.

SQL>
SQL> drop table test_dir1;
drop table test_dir1
           *
ERROR at line 1:
ORA-00942: table or view does not exist

SQL> create table test_dir1
  2   (id number,
  3   status varchar2(20),
  4   cd_text varchar2(20))
  5  tablespace user1
  6  storage(pctincrease 0);

Table created.

SQL> declare
  2  i number;
  3  begin
  4  for i in 1..1000 loop
  5  insert into test_dir1 values(i,'status','text');
  6  end loop;
  7  end;
  8
  9  /

PL/SQL procedure successfully completed.

SQL> commit;

Commit complete.

SQL>
SQL> set pagesize 100;
SQL> set linesize 100;
SQL> set serverout on;
SQL> declare
  2  Cursor RunCur is
  3  select TABLE_NAME from all_tables
  4   where OWNER ='DIRECT_LOAD'
  5   and PCT_FREE IS NOT NULL;
  6
  7  TableToExplore VARCHAR2(30);
  8  begin
  9   open RunCur ;
 10   LOOP
 11    FETCH RunCur INTO TableToExplore ;
 12    EXIT WHEN RunCur%NOTFOUND ;
 13    show_space_my(Upper(TableToExplore),'DIRECT_LOAD','TABLE');
 14  dbms_output.put_line('*************************************** END OF THIS
OBJECT *****************************');
 15   END LOOP;
 16  end;
 17  /
Object Name *********** TEST_DIR

Object Type *********** TABLE

Free Blocks.............................0
Total Blocks............................5
% Free in Segment.......................0
Total Space In MB........................02
Unused Blocks...........................4
Unused Space In Mb.......................02
Last Used Ext FileId....................7
Last Used Ext BlockId...................17
Last Used Block.........................1
Partition Name..........................NO PARTITIONS

*************************************** END OF THIS OBJECT
Object Name *********** TEST_DIR1

Object Type *********** TABLE

Free Blocks.............................4
Total Blocks............................10
% Free in Segment.......................40
Total Space In MB........................04
Unused Blocks...........................0
Unused Space In Mb......................0
Last Used Ext FileId....................7
Last Used Ext BlockId...................27
Last Used Block.........................5
Partition Name..........................NO PARTITIONS

*************************************** END OF THIS OBJECT

PL/SQL procedure successfully completed.

SQL> execute  TunePack.SegmetTableInfo ;

OBJECT NAME-------------------TEST_DIR
OBJECT TYPE-------------------TABLE
Number Of Extents-------------1
Next Size Of Extent In MB-----.02
Max Extent--------------------2147483645
PCT Increase In %-------------0
Size In MB--------------------.02

************************** END OF THIS OBJECTS
OBJECT NAME-------------------TEST_DIR1
OBJECT TYPE-------------------TABLE
Number Of Extents-------------2
Next Size Of Extent In MB-----.02
Max Extent--------------------2147483645
PCT Increase In %-------------0
Size In MB--------------------.04

************************** END OF THIS OBJECTS

PL/SQL procedure successfully completed.

SQL> execute  TunePack.ExtentTableInfo ;

OBJECT NAME-------------------TEST_DIR
DB File Name Belongs to-------C:\BABADB\DATAFILE\USER1.ORA
Extend Id No------------------0
Size In MB--------------------.02
 ************************** END OF THIS OBJECTS

OBJECT NAME-------------------TEST_DIR1
DB File Name Belongs to-------C:\BABADB\DATAFILE\USER1.ORA
Extend Id No------------------0
Size In MB--------------------.02
OBJECT NAME-------------------TEST_DIR1
DB File Name Belongs to-------C:\BABADB\DATAFILE\USER1.ORA
Extend Id No------------------1
Size In MB--------------------.02

************************** END OF THIS OBJECTS
PL/SQL procedure successfully completed.

------------
I try to Use  DIRECT PATH Insert
AS you said totally new extend will be taked DESPITE of free or unused extends
in table, but I receive other kind
of result. Am i wrong in testing?
-----------

SQL> insert /*+ APPEND */ into test_dir select * from test_dir1 where id between
1 and 4;

4 rows created.

SQL> commit;

Commit complete.

SQL> declare
  2  Cursor RunCur is
  3  select TABLE_NAME from all_tables
  4   where OWNER ='DIRECT_LOAD'
  5   and PCT_FREE IS NOT NULL;
  6
  7  TableToExplore VARCHAR2(30);
  8  begin
  9   open RunCur ;
 10   LOOP
 11    FETCH RunCur INTO TableToExplore ;
 12    EXIT WHEN RunCur%NOTFOUND ;
 13    show_space_my(Upper(TableToExplore),'DIRECT_LOAD','TABLE');
 14  dbms_output.put_line('*************************************** END OF THIS
OBJECT *****************************');
 15   END LOOP;
 16  end;
 17  /
Object Name *********** TEST_DIR

Object Type *********** TABLE

Free Blocks.............................0
Total Blocks............................5
% Free in Segment.......................0
Total Space In MB........................02
Unused Blocks...........................3
Unused Space In Mb.......................01
Last Used Ext FileId....................7
Last Used Ext BlockId...................17
Last Used Block.........................2
Partition Name..........................NO PARTITIONS

*************************************** END OF THIS OBJECT
Object Name *********** TEST_DIR1

Object Type *********** TABLE

Free Blocks.............................4
Total Blocks............................10
% Free in Segment.......................40
Total Space In MB........................04
Unused Blocks...........................0
Unused Space In Mb......................0
Last Used Ext FileId....................7
Last Used Ext BlockId...................27
Last Used Block.........................5
Partition Name..........................NO PARTITIONS

*************************************** END OF THIS OBJECT

PL/SQL procedure successfully completed.

SQL> execute  TunePack.SegmetTableInfo ;
OBJECT NAME-------------------TEST_DIR
OBJECT TYPE-------------------TABLE
Number Of Extents-------------1
Next Size Of Extent In MB-----.02
Max Extent--------------------2147483645
PCT Increase In %-------------0
Size In MB--------------------.02

************************** END OF THIS OBJECTS *************************

OBJECT NAME-------------------TEST_DIR1
OBJECT TYPE-------------------TABLE
Number Of Extents-------------2
Next Size Of Extent In MB-----.02
Max Extent--------------------2147483645
PCT Increase In %-------------0
Size In MB--------------------.04

************************** END OF THIS OBJECTS *************************

PL/SQL procedure successfully completed.

SQL> execute  TunePack.ExtentTableInfo ;
OBJECT NAME-------------------TEST_DIR
DB File Name Belongs to-------C:\BABADB\DATAFILE\USER1.ORA
Extend Id No------------------0
Size In MB--------------------.02

************************** END OF THIS OBJECTS *************************

OBJECT NAME-------------------TEST_DIR1
DB File Name Belongs to-------C:\BABADB\DATAFILE\USER1.ORA
Extend Id No------------------0
Size In MB--------------------.02
OBJECT NAME-------------------TEST_DIR1
DB File Name Belongs to-------C:\BABADB\DATAFILE\USER1.ORA
Extend Id No------------------1
Size In MB--------------------.02

************************** END OF THIS OBJECTS *************************
PL/SQL procedure successfully completed.

SQL> spool off;

And we can see there is no new extend aallocated in table test_dir for direct
path insert!
Am I wrong ? Please explain?

Followup:
the append hint writes above the HWM.  sorry for the miscommunication above.  It
takes space above the HWM for the table, not necessarily a new extent.

I've corrected that.

Rating: 4
I have a trouble September 30, 2002
Reviewer: Adrian Usuy from Peru
Hi Tom, i was try to make the insert append like this:

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.4.0 - 64bit Production
With the Partitioning option
JServer Release 8.1.7.4.0 - 64bit Production
SQL> insert /*+ append */ into dwbs_con_saldos_tmp
  2  select * from dwbs_con_saldos;
insert /*+ append */ into dwbs_con_saldos_tmp
                          *
ERROR at line 1:
ORA-00600: internal error code, arguments: [15201], [], [], [], [], [], [], []
SQL>

is some way to fix that?

Thanks

Followup:
ORA-00600 = contact support, all internal errors mean that.

Rating: 4
Why slow? January 24, 2003
Reviewer: A reader
Hi, TOm

I basically doing:
insert /*+ APPEND */ INTO t(employno, ename, sal, comm, deptno)
SELECT a.employno, a.ename, a.sal, a.comm, b.deptno
from emp a, dept b
where a.employno = b.employno

The SELECT stmt has more than 864518 records, the speed
of this insert transaction is VERY slow(already more than 1 hour, still
running), could you tell me why? is it normal?
My DB is not in archive mode, so i don't think NOLOGGING
is an issue here.

THANKS

Followup:
how many indexes are we talking about.

and if you just

set autotrace traceonly statistics
select.......

how long does that go

Rating: 3
SELECT January 26, 2003
Reviewer: A reader
Hi, TOm,
Thanks for your fast response.
Actually, the SELECT stmt itself is very fast.
But the insert /*+ APPEND */ ....... stmt, takes a long
long time, more than half day.

THanks

Followup:
a) did you consider comparing the plans to see if they are the same.  insert /*+
append */ optimizes for "throughput" a select might be optimizing for first
rows.  if the plans are different, try hinting the select in the insert with
first_rows and see if they become the same and try again

b) again -- how many indexes are we talking about there.  how many rows are you
inserting.

Rating: 5
May 21, 2003
Reviewer: A reader from CA, USA
Hi Tom,

In our environment, I was able to consistently run INSERT without /*+ APPEND */
faster than the similar INSERT with the hint. Please see the example below. What
could be the reason for this?

thanks

SQL> select log_mode from v$database;

LOG_MODE
------------
NOARCHIVELOG

SQL> CREATE TABLE t (
  2  c1 int,
  3  c2 int
  4  );

Table created.

SQL>
SQL> CREATE TABLE t2 AS
  2  SELECT * FROM t
  3  WHERE  1 = 0;

Table created.

SQL>
SQL>
SQL> BEGIN
  2     FOR i IN 1..100000 LOOP
  3        INSERT INTO t VALUES(i, i);
  4     END LOOP;
  5
  6     COMMIT;
  7
  8     FOR i IN 1..5 LOOP
  9        INSERT INTO t
 10        SELECT * FROM t;
 11     END LOOP;
 12
 13     COMMIT;
 14  END;
 15  /

PL/SQL procedure successfully completed.

SQL>
SQL> SELECT COUNT(*) FROM t;

  COUNT(*)
----------
   3200000

SQL>
SQL>
SQL> set timing on
SQL>
SQL> INSERT INTO t2 SELECT * FROM t;

3200000 rows created.

Elapsed: 00:00:42.12
SQL>
SQL> ROLLBACK;

Rollback complete.

Elapsed: 00:00:08.82
SQL>
SQL> INSERT /*+ APPEND */
  2  INTO t2 SELECT * FROM t;

3200000 rows created.

Elapsed: 00:00:46.96
SQL>
SQL> ROLLBACK;

Rollback complete.

Elapsed: 00:00:00.10
SQL>

Followup:
I see no real difference here.  in the grand scheme of things 42 and 46 aren't
different to me, not wall clock timewise...

look at the amount of redo generated, look at the work performed.  it is a 2
small column table.  Not much of anything happening there.

try a real example, use the tool to speed up something "big" and "slow".

in any case, not reproducing by me here:

ops$tkyte@ORA920> create table t ( c1 int, c2 int );

Table created.

ops$tkyte@ORA920> create table t2 as select * from t;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> BEGIN
  2     FOR i IN 1..100000 LOOP
  3        INSERT INTO t VALUES(i, i);
  4     END LOOP;
  5
  6     COMMIT;
  7
  8     FOR i IN 1..5 LOOP
  9        INSERT INTO t
 10        SELECT * FROM t;
 11     END LOOP;
 12
 13     COMMIT;
 14  END;
 15  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set timing on
ops$tkyte@ORA920> insert into t2 select * from t;

3200000 rows created.

Elapsed: 00:00:32.81
ops$tkyte@ORA920> commit;

Commit complete.

Elapsed: 00:00:00.08
ops$tkyte@ORA920> truncate table t2;

Table truncated.

Elapsed: 00:00:03.50
ops$tkyte@ORA920> insert /*+ APPEND */ into t2 select * from t;

3200000 rows created.

Elapsed: 00:00:05.43
ops$tkyte@ORA920> commit;

Commit complete.

and again

ops$tkyte@ORA920> drop table t2;

Table dropped.

Elapsed: 00:00:00.29
ops$tkyte@ORA920> create table t2 as select * from t where 1=0;

Table created.

Elapsed: 00:00:00.09
ops$tkyte@ORA920>
ops$tkyte@ORA920> set timing on
ops$tkyte@ORA920> insert into t2 select * from t;

3200000 rows created.

Elapsed: 00:00:14.27
ops$tkyte@ORA920> rollback;

Rollback complete.

Elapsed: 00:00:18.07
ops$tkyte@ORA920> insert /*+ APPEND */ into t2 select * from t;

3200000 rows created.

Elapsed: 00:00:08.57
ops$tkyte@ORA920> rollback;

Rollback complete.
Elapsed: 00:00:00.02

notice already the wide flucuations in times..  and again....

ops$tkyte@ORA920> set timing on
ops$tkyte@ORA920> insert into t2 select * from t;

3200000 rows created.

Elapsed: 00:00:15.57
ops$tkyte@ORA920> rollback;

Rollback complete.

Elapsed: 00:00:19.89
ops$tkyte@ORA920> insert /*+ APPEND */ into t2 select * from t;

3200000 rows created.

Elapsed: 00:00:08.09
ops$tkyte@ORA920> rollback;

Rollback complete.

Elapsed: 00:00:00.06
ops$tkyte@ORA920>

Rating: 2
how to verify.... July 09, 2003
Reviewer: Neeti from TX,USA
insert /*+ APPEND PARALLEL (a,4) */
into a
nologging
select /*+ PARALLEL (b,4) */
from b

How to verify that inserts are running in parallel,
I se only select running in parallel...?

Thanks,
Neeti

Followup:
well, first, that nologging is not_meaningful -- not anymore then:

ops$tkyte@ORA920> insert /*+ APPEND PARALLEL (a,4) */
  2  into a
  3  I_beg_of_you_to_do_nologging
  4  select /*+ PARALLEL (b,4) */ *
  5  from b;

1 row created.

would be.  nologging is just an alias for A there.  You want to disable logging
on table A via the alter table command.

see
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96524/c21dlins.htm#10629
for the exact requirements of a parallel direct path insert.

July 09, 2003 Tony -- Thanks for the question regarding "Insufficient privileges", version Oracle 8.1.6
You Asked
Hi Tom,

I've installed an Oracle 8.1.6 Enterprise version on a fresh
machine. OEM has been installed, too. If I login with OEM with
system/manager as sysdba I get an 'ORA 01031: Insufficient
privileges' error. If I try to connect to a remote database, I
get the same. (Though I can login without the sysdba role on
both servers, but then I can't shutdown or restart the
database). If I connect from that remote database to this new
database then there are no complaints. The newly installed
database has been istalled on a Windows 2000 Advanced server
with Service Pack 1. Could this be the problem?

Thanks,
Tony Cardalda.
and we said...
this is support note:

Article-ID:         <Note:107334.1>

In the OEM Console, if you try to connect to a database, a
logon dialogue window appears.  You have a choose of
connecting as 'Normal', 'Sysdba', or 'Sysoper'.  You can
connect with the user internal as sysdba but NOT sys or system.
You will get the error:

ORA-1031 insuffient privileges.

Why?  How do you set this to work?

Solution Description
--------------------
Run svrmgr30 or svrmgrl (depending on version) and logon
as internal and then issue the following:

   grant sysdba to system;

(or any user that you want to have the sysdba role).

Then in OEM, for this user that you setup, you can have
the connect as set to SYSDBA by doing the following:

1.  Select FILE->PREFERRENCES from within the OEM Console.
2.  Highlight the database and change the role from NORMAL
    to either SYSDBA (or SYSOPER).
3.  Click APPLY.

The next time you attempt to connect from the OEM applications,
the application will connect as the role you selected.

Note: Before the SYSDBA role can be granted to a user,
      the init.ora file must have the parameter
      "REMOTE_LOGIN_PASSWORDFILE=EXCLUSIVE".  If you have to
      modify the init.ora to include this parameter, you will
      need to shutdown and restart your instance.

Explanation
-----------

By default the internal user has the sysdba privilege.
The system and sys do not.  Therefore you need to grant the
sysdba role to the user.
Review & Followup
Rating: 3
connect / as sysdba February 19, 2002
Reviewer: Sudhakar from India
Hi,

This was quite helpful. But what happens when i a connecting using connect / as
sysdba and get insuffcient privileges error. To which user do I grant sysdba
role !!!

Thanks,
Sudhakar

Followup:
the OS user.  you are using OS authentication.

If you connect /  and then do a show user -- thats the user. In UNIX land that
user must be in the DBA group (DBA being the default group -- someone might have
changed that during install)

Rating: 3
Grant failed for externally identified users October 29, 2002
Reviewer: Oracleo from India
Hi Tom,

Your site and book are great help to all dbas.
With respect to this note i have an error.
I am able to connect as 'connect /' but not as 'connect / as sysdba' as shown
below in the output. I tried granting sysdba to OPS$ADMIN,  that also failed
with an error. I am using oracle 817 on win2k .
C:\>svrmgrl

Oracle Server Manager Release 3.1.7.0.0 - Production

Copyright (c) 1997, 1999, Oracle Corporation.  All Rights Reserved.

Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production
With the Partitioning option
JServer Release 8.1.7.0.0 - Production

SVRMGR> connect / as sysdba
ORA-01031: insufficient privileges
SVRMGR> connect /
Connected.
SVRMGR> connect sys as sysdba
Password:
Connected.
SVRMGR> grant sysdba to ops$admin;
grant sysdba to ops$admin
*
ORA-01997: GRANT failed: user 'OPS$ADMIN' is identified externally
SVRMGR>

Thanks once again.

Followup:
see
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/win.817/a73008/ch7.htm#1036322
it is different in windows then on other easier platforms.  more setup involved.
 I don't do windows - don't know all of the ins and outs personally.

Rating: 4
OS Authentication problem February 10, 2003
Reviewer: Igor from France
Hi

 Solaris 8, Oracle 8.1.7.4.
 I would like to set one new user which would have
 possibility to
         connect internal (without password)
 or
         connect "/ as sysdba" (also, without password)

 In $ORACLE_HOME/rdbms/lib/config.s I find

                        .global ss_dba_grp
                       ss_dba_grp:
/* 0x0000         17 */         .align  4
/* 0x0000         18 */         .word   (.L12+0)
/* 0x0004         24 */         .align  4
/* 0x0004         25 */         .word   (.L13+0)
/* 0x0008         26 */         .type   ss_dba_grp,#object
/* 0x0008         27 */         .size   ss_dba_grp,8
        .section        ".rodata1",#alloc
/* 0x0008         13 */         .align  4
                       .L12:
/* 0x0008         15 */         .ascii  "dba\0"
/* 0x0014         20 */         .align  4
                       .L13:
/* 0x0014         22 */         .ascii  "dba\0"

 It's not likely that someone changed this file afterwards.
 So, group to connect would be dba.
 I added new user to group and tried but got
 "ORA-01031: insufficient privileges".

  Thanks for your help

                                       Igor

P.S. New user is part of dba ('groups' says it is) and
 is not member of deamon group.
P.P.S. It's local connection so I should not take care
 about password file, remote_login_passwordfile parameter
 etc. Am I right?

Followup:
so what groups are listed for someone who can do this on your system?

Rating: 5
February 11, 2003
Reviewer: Igor from France
Sorry, I haven't seen that TWO_TASK was tweaking things
 to old DB name...

 Thanks anyway

                            Igor

Rating: 4
connection problem June 19, 2003
Reviewer: kumar from India
Tom,
I am facing this problem on a 8i db installed on win2000.

SQL> connect sys/change_on_install@webtogo
Connected.
SQL> connect sys/change_on_install@webtogo as sysdba
ERROR:
ORA-01017: invalid username/password; logon denied

Warning: You are no longer connected to ORACLE.

I am connecting from the same server on which the database is installed. I have
a 9i Lite also installed on the same server. Is that causing the problem ?

Followup:
that just means the SYS password in the remote password file ISN'T
change_on_install

someone created the password file with a different password.  read
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:670117794561

Rating: 5
Thanks June 20, 2003
Reviewer: kumar from India
Tom,

That was a great explanation. The depth of info provided here is unparalleled.
None of the sites gave this much info for my problem.

Thanks again.

Rating: 4
July 09, 2003
Reviewer: A reader
Hi Tom,

  I have Oralce 9i on NT server with 4 databases on it. I can not connect
remotely (From a client) to just one of the databases as sys ! I can connect as
sys to the database localy(on server) and I can connect localy & remotely to
other databases.

I recreate Password file for that database, check the init.ora
(remote_login_passwordfile=EXCLUSIVE ) but still get the error "insufficient
privilege"

Could you please let me know what the problem is?

Thanks in advance,
Arash

Followup:
you've done something wrong somewhere.

you are using the password you burned into the password file right?  try this --
alter user sys identified by something_you_know_for_sure_is_right;  that'll sync
that password files password.  try that.

Rating: 4
July 10, 2003
Reviewer: A reader
Tom,

Just one word. You are great. I learnt manything from your site. I usually spend
at least 1 hour each day to visit your website to learn more.

Thanks again,
Arash

July 10, 2003 nag -- Thanks for the question regarding "problem with date formats", version 8.1.7
You Asked
Tom

I had been getting some weird errors

My function p accepts a date parameter. When ever Iam passing dates in the
format dd-mon-yy , I am having no problems. But when Iam sending it in any other
format Iam getting the error 'invalid month'.

FUNCTION fnc_get_date(
                         current_date          date,
                          prior_date        date
                         )
RETURN  date;
vc_date     date;
vp_date  date;
    temp_date                date;
 v_counting    number;
begin

 vc_date := TRUNC( current_date );

     v_counting := 0;
    temp_date := TRUNC( prior_date );
    while v_counting < 1 loop

        if (( to_char( temp_date, 'DY') = 'SAT') or
            ( to_char( temp_date, 'DY') = 'SUN') or
            ( Rcp_util.nf_date_is_holidayday( temp_date,21) = 2 ))
then
            temp_date := temp_date - 1;
        else
            v_counting := 1;
        end if;
    end loop;

return temp_date;

end;
and we said...
Well, there can only be ONE date format in effect at any point in time.  The
default date mask is dd-mon-rr.  If you send in some arbitrary format -- we will
not "guess" what the date is (eg: is 10-11-01 October 11'th 2001, is is November
10'th 2001, is it perhaps the first of November on 2010 - and so on).

The invoker of this function should use the TO_DATE function with the format for
that type of date they are sending (even the callers that use dd-mon-rr should
use TO_DATE -- in the event your application is ever used on a database with a
DIFFERENT default).

They should be calling:

 begin
   ? := fnc_get_date( to_date( ?, 'dd/mm/yyyy' ), to_date( ?, 'dd/mm/yyyy' ) );
 end;

for example if they use a date in dd/mm/yyyy format.
Review & Followup
Rating: 5
How do we set the data format for a database September 23, 2001
Reviewer: A reader
and

the scripts which you write , return date with time as well by deafault , how is
that made possible.

Followup:
I either use to_char( dt_field, 'dd-mon-yyyy hh24:mi:ss' )

or I've issued:

alter session set nls_date_format = 'dd-mon-yyyy hh24:mi:ss';

in my session before selecting a date.

Rating: 4
July 09, 2003
Reviewer: A reader
Hi Tom,

I have a procedure that fetchs data from a cursor in which one of its columns is
a varchar2(9) datatype, which contains values in the format 'dd-mon-yy' or the
word 'null'.

The default value for the parameter nls_date_format is 'dd-mon-rr'

If I set nls_date_format to 'mm/dd/yyyy' on a sql*plus session and then run the
procedure, I get an error 6502 PL/SQL: numeric or value error, when the cursor
tries to fetch values in the format 'dd-mon-yy'.

But If I leave the nls_date_format in its default value (dd-mon-rr) or if I set
it to 'mm/dd/yy', the procedure runs without any problem on sql*plus for any
kind of value ('dd-mon-yy' or the word null).

1-Can you tell me why I am getting this error if I change the nls_date_format to
'mm/dd/yyyy'?
2-Due to I have a mix of words and dates in a varchar2 datatype column,
shouldn't I receive the error no matter what the setting is for the
nls_Date_format parameter?

Thanks in advance for any help on this.

Followup:
1) because apparently you've done the worst thing you can do, you stored dates
as strings -- meaning that all implicit conversions will be problematic
performance wise and error wise forever.

In addition to storing a date in a string -- which is really bad -- you've
forgotten that it takes a MINIMUM of 4 characters to store a century/year.  Tell
me, what is 12-jan-40.  ugh.

create the view as

 select ...., decode( c, 'null', to_date(null), to_date(c,'dd-mon-rr') ) c

and that'll solve your implicit conversion problem -- but not your "i've no idea
what year this data is really for" problem.

2) no, the date formats are forgiving in that fashion to a point.  But that also
points out why you never never (as in never) want to rely on implicit
conversions.  what if someone sticks in 01-11-10   is that the 1st of november
2010, the 11th of jan 1910, what?

July 10, 2003 eric -- Thanks for the question regarding "RMAN Image copies", version 8.1.7
You Asked
Hi Tom,

First of all, Happy New Year !!

Is it possible to perform online, "Hot" RMAN image copies ?
It's clear the answer is YES about BACKUPs (in the Oracle docs), but all the
examples and the whole discussion around Image Copies tell nothing about that --
I would be interested in taking RMAN image copies , provided I
can do it in a "hot backup" context.

Thanks in advance for your kind and clear explanations.

Eric KERO
and we said...
Yes
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76990/rmanconc.htm#434708

....
You can create an O/S copy when the database is open or closed. If the
database is open and the datafile is not offline normal, then you must place
the tablespace in hot backup mode, that is, issue the SQL statement ALTER
TABLESPACE BEGIN BACKUP before creating the copy.
Review & Followup
Rating: 2
Misunderstanding ? January 16, 2003
Reviewer: Eric KERO from Paris , FRANCE
Hi, Tom !
Reading Oracle prose, (what I've done before soliciting you), we can see there
are separate discussions about :

A) RMAN I.C
B) O/S  I.C

as we can see below :

RMAN Image Copies
Use the RMAN copy command to create an image copy. If the original file needs to
be replaced, and if the image copy is of a datafile, then you do not need to
restore the copy. Instead, Oracle provides a switch command to point the control
file at the copy and update the recovery catalog to indicate that the copy has
been switched. Issuing the switch command in this case is equivalent to issuing
the SQL statement ALTER DATABASE RENAME DATAFILE. You can then perform media
recovery to make the copy current.

RMAN can catalog an image copy and read the metadata. This operation is
important when the recovery catalog is lost and you must perform disaster
recovery. Only image copies and archived logs can be cataloged.

O/S Image Copies
Oracle supports image copies created by mechanisms other than RMAN, also known
as O/S copies. For example, a copy of a datafile that you make with the UNIX cp
command is an O/S copy. You must catalog such O/S copies with RMAN before using
them with the restore or switch commands.

You can create an O/S copy when the database is open or closed. If the database
is open and the datafile is not offline normal, then you must place the
tablespace in hot backup mode, that is, issue the SQL statement ALTER TABLESPACE
BEGIN BACKUP before creating the copy.

I understand clearly (I hope so !) the ins and outs for 'classic' , O/S  hot
backups ; But as we can see , there's no clear, complete explanations about what
I would call "RMAN hot backups" : How do RMAN manages datafiles' "fuzziness"
when we invoke its copy command ?

Thanks in advance for your answer.

Eric

Followup:
it re-reads a fractured block if and when it encounters it.

RMAN understands that what it is reading is a datafile -- a data structure.  It
performs a logic check on what it read to ensure it is "ok".  If it is not (due
to someone writing to the same block rman was reading -- and rman getting a
split or fractured block where the head and tail of the block are from two
different points in time), rman simply re-reads it.

An OS image copy on the other hand does not have this intelligence, it just
reads stuff.  It cannot verify it is good stuff and re-read it if not.

Rating: 4
Many thanks ! January 17, 2003
Reviewer: A reader

Rating: 5
logical vs physical block corruption July 09, 2003
Reviewer: Reader
The doc says that RMAN performs the logical and physical corruption check. What
is the difference between the two? How do I enable RMAN to do the same? Thanks.

Followup:
logical corruption would be something like "this varchar2(55) field appears to
have 56 bytes"

physical corruption would be "read the block, got an error" or "read the block,
appeared bad, reread -- get different stuff, somethings wrong"

Rating: 5
how do I enable to detect logical corruption in RMAN? Thanks. July 10, 2003
Reviewer: Reader

Followup:
it does it out of the box but you can also use the validate command against the
backups.

July 10, 2003 Kenneth -- Thanks for the question regarding "basic replication", version 7.3.2
You Asked
If basic replication creates only read only tables( which are
actually views ) .
At the most I will be able to query the replicated database
. since no updates,insert , delete are possible

If my primary database crashes then this replicated is of no use
Cause even an export will actually generate read only tables
which can't be used to create a  production database.

Is there any way in which i can replicate a database where even
small changes ( like changes made to functions etc. ,   new
tables added ) can be incorporated automatically.

Can u throw some light?
Kenneth
and we said...
Well, in basic replication -- the data is actually copied --
there are "real" tables out there (with extra columns -- in 7.3
the rowid of the master row at the master site).  The views are
used to "hide" this extra column.  So, in the event of a total
failure -- you can get to these "real" tables if need be in the
event of an emergency.

What you describe above is called advanced replication.  It does
this.  it replicates not only tables but schema's themselves
(although you must use their functions in order to add indexes
and objects to the schema so they know that its been changed and
they can replicate that for you).

If you are trying to get a failover instance, you might consider
other technologies as well -- such as a STANDBY database.  See
http://www.oracle.com/oramag/oracle/00-May/o30tom.html
for an overview of some of the options.
Review & Followup
Rating: 2
Replication of Indexes July 10, 2003
Reviewer: Faran from Pakistan
(Using Oracle 9.2.0.1.0 on MS Windows 2000) I have created 2 databases on
different machines and run utlsampl.sql on both. I have made first one testing
master definition site to replicate the scott.dept table to other master site
(multi-master replication)...

SQL>...
SQL> BEGIN
  2  DBMS_REPCAT.ADD_MASTER_DATABASE (
  3  gname => 'scott_repg',
  4  master => 'fruity.benz.com',
  5  use_existing_objects => TRUE,
  6  copy_rows => FALSE,
  7  propagation_mode => 'ASYNCHRONOUS');
  8  END;
  9  /

PL/SQL procedure successfully completed.
...
SQL> BEGIN
  2  DBMS_REPCAT.GENERATE_REPLICATION_SUPPORT (
  3  sname => 'scott',
  4  oname => 'dept',
  5  type => 'TABLE',
  6  min_communication => TRUE);
  7  END;
  8  /

PL/SQL procedure successfully completed.
...

Both the sites already have pk_dept index on dept table. I have moved the index
pk_dept to a different tablespace at the second master site. It has been done
and no problem at all. Now I have some questions...

I have just added only table dept in the replicatoin group not any index. What
are the benifits to replicate the indexes? Is it necessary to add the indexes
too in the replication group or will they grow automatically as the data will be
replicated to other master sites?
If I add the index too in the replication group, would it not incur more network
traffic? Is it not suitable to create indexes separetely on at individual master
sites and just replicate the tables. In this way i may choose different
tablespaces (for indexes) at the different sites too. Or if oracle gives us the
facility to replicate the indexes and procedures where does its applicability
more better.

Any guidelines for this... would be highly appreciated.

Thanks & Regards,
Faran

Followup:
indexes are local things, they will not add to any network traffic -- only the
row level changes are there.

you would use the ddl replication facility in order to make sure you have a
consistent set of indexes everywhere -- rather then doing the create index in N
sites, you do it once and it gets pushed out to every site.

If you have an index at site 1 -- it is highly probable you need that index at
site 2, site 3, and so on.

you are replicating the DEFINITION of the index, not index data.

Rating: 4
Thanks July 10, 2003
Reviewer: Faran from Pakistan
Thanks again Tom! You're doing a great job.

Regards,
Faran

July 10, 2003 Tim -- Thanks for the question regarding "Oracle 9i client can not connect to db 7.3.2", version 7.3.2
You Asked
Hello Tom,
I installed 9i client and try to connect to 7.3.2 oracle database.
The error complaint about product_user_profile.
Error as follow:
------
SQL*Plus: Release 9.2.0.1.0 - Production on Fri Dec 6 11:59:44 2002

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Error accessing PRODUCT_USER_PROFILE
Warning:  Product user profile information not loaded!
You may need to run PUPBLD.SQL as SYSTEM
Segmentation fault
------------

I ran the pupbld.sql as user system on that DB but this didn't help.
I use this client connect to 8.0.4 database and to work fine.

Can you help me on this?

Thanks.
and we said...
7.3, being not supported at all for a couple of years, is not supported with
9iR2 clients.

There are known issues.

9iR2 and 7.3 (and before) cannot be used together.
Review & Followup
Rating: 3
Try to upgrade to 7.3.4 December 06, 2002
Reviewer: A reader
We had no problem connecting from Oracle 9.2 to Oracle 7.3.4 through a database
link.

If it´s important to you , you coult test it.

Followup:
You will, it is

a) not supported
b) there are dozens of issues with it
c) even with 734
d) it is documented that "this will not work reliably"

I would not rely at all on this working tomorrow.

Rating: 4
December 08, 2002
Reviewer: Tim Hoang from Hong Kong
Thanks very much.

Rating: 5
my solution July 10, 2003
Reviewer: Masel from Poland
you may connect to your Oracle9 database, next create database link to Oracle7
database and read data using this link.

Followup:
please reread the above

July 10, 2003 Kiran -- Thanks for the question regarding "Auto numbering in groups", version Oracle 8i
You Asked
Tha data is as follows:
Barcode  Attr1        Attr2  employee

050505    VC-SQUAT    100     1234
050505    VC-SQUAT    100     3456
890234   VM-ADT   201    9087
890234   VM-ADT   201    8976
890234   VM-ADT   201    876
890234   VM-ADT   201    6789

The above data shows that 2 employees have worked on barcode 050505 with
attributes
VC-SQUAT and 100, And 4 employees have worked on barcode 890234 with attributes
VM-ADT and 201. Similarly 'x' employees can work on a barcode 'y' with some
fixed attributes

The result should be as follows:
compute_col    Barcode  Attr1        Attr2  employee
1        050505     VC-SQUAT    100     1234
2        050505     VC-SQUAT    100     3456
1        890234   VM-ADT         201      9087
2        890234   VM-ADT         201      8976
3        890234   VM-ADT         201      876
4        890234   VM-ADT         201      6789

Please let me know if there is any possible ways to show the compute_column. I
tried with
rownum but it gave some unexpected result.

Thanks
and we said...
select .... , row_number() over ( partition by barcode, attr1, attr2
                                  order by employee )
  from ....

for example:

  1  select ename, deptno, mgr ,
  2         row_number() over ( partition by deptno, mgr order by empno ) rn
  3  from emp
  4* order by deptno, mgr, empno
scott@TKYTE816> /

ENAME      DEPTNO        MGR         RN
---------- ------ ---------- ----------
MILLER         10       7782          1
CLARK          10       7839          1
KING           10                     1
SCOTT          20       7566          1
FORD           20       7566          2
ADAMS          20       7788          1
JONES          20       7839          1
SMITH          20       7902          1
ALLEN          30       7698          1
WARD           30       7698          2
MARTIN         30       7698          3
TURNER         30       7698          4
JAMES          30       7698          5
BLAKE          30       7839          1

14 rows selected.

this works in 8.1.6 and above.
Review & Followup
Rating: 3
June 06, 2001
Reviewer: MP from BOSTON
I just simulated your problem thru a table testtable with one field A
having values
A
A
A
A
B
B
B
B
C

So basically I need an output like
A 1
A 2
A 3
A 4
B 1
B 2
B 3
B 4
C 1
to match your need.

I got this result thru this query

select  ALIAS1.A , (ALIAS2.B - ( ALIAS2.C - 1) )
        from ( SELECT A, rownum B from testtable
               order by 1) ALIAS1,
            ( select A,min(rownum) C from testtable
              group by A order by 1) ALIAS2
       where ALIAS1.A = ALIAS2.A

You can substitute the fields in place of A in this example.

Rating: 4
auto numbering in groups July 10, 2003
Reviewer: Leigh Gold from Canada
Tom,

The example below works only for Enterprise version,

select .... , row_number() over ( partition by barcode, attr1, attr2
                                  order by employee )
  from ....

Can you please give an example of how to do the above query in 8.1.6 standard
edition, ie. without the row_nubmer() over feature.

Thanks you, Tom.

Followup:
you cannot.

Rating: 4
July 10, 2003
Reviewer: Dave from Colorado
How about ...

create table my_table (my_col number);

create index my_index on my_table(my_col);
insert into my_table values (1);
insert into my_table values (1);
insert into my_table values (1);
insert into my_table values (1);
insert into my_table values (2);
insert into my_table values (2);
insert into my_table values (2);
insert into my_table values (3);
insert into my_table values (3);
insert into my_table values (4);
commit;

select
   t1.my_col,
   t1.rn-t2.min_rn+1 seq
from
   (
   select
      my_col,
      min(rn) min_rn
   from
      (
      select /*+ index_asc(m) */
         my_col,
         rownum rn
      from
         my_table
      )
   group by
      my_col
   ) t2,
   (
   select /*+ index_asc(m) */
      my_col,
      rownum rn
   from
      my_table
   ) t1
where
   t1.my_col = t2.my_col;

    MY_COL        SEQ
---------- ----------
         1          1
         1          2
         1          3
         1          4
         2          1
         2          2
         2          3
         3          1
         3          2
         4          1

I can't remember if you can ORDER in an in-line view in 8.1.6, so i used an
index hint (unreliable, i know). Note also the way that performance would be so
much worse than with an analytic function

Still, there we have it.

Followup:
order by in a subquery was added in 815.  Never use a hint to order.  if someone
renames the index or makes the column nullable -- bam, wrong answer.

July 10, 2003 Edgars -- Thanks for the question regarding "Large table ", version 9.0.1
You Asked
Hello Tom!

We have a table like
create table data( time date not null, point_id integer not null, status_id
integer not null, value number(*,3) );

This table has unique index on all fields except value. In this table we are
planing to store large amount of data (~100G rows), now index is ~2 times bigger
than a table, may be we should better in our case use index organized tables? As
I understand IOT won't add bigger overhead than we already have and possibly it
will be even faster? What type of table and insert method ( insert /*+append*/
?) will you recommend for storing large amount of unique data in a table?
and we said...
An IOT would make sense in this case.  I don't know what 100g is.  Suggest you
test performance as an IOT can be more expensive to maintain in some cases (but
with such a small non-key component it might be OK)

You will just use normal "inserts" into an IOT as it is an index structure, not
a heap (and you cannot "append" to it).  The initial load can be a direct path
load but after that it'll just be an "insert".

consider partitioning if the object is very large.  partitioned IOTS are
supported in 9i
Review & Followup
Rating: 3
100g are 100 Giga May 04, 2002
Reviewer: A reader
select to_char(1024*1024*1024*100,'999,999,999,999') "100 GIGA" from dual;

100 GIGA
----------------
 107,374,182,400

That's a huge.

Followup:
like I said then, look into partitioning.

Rating: 5
July 10, 2003
Reviewer: A reader
Tom,

Are Partitioned IOTS supported in 8.1.7.0 ?

Followup:
ops$tkyte@ORA817DEV> CREATE TABLE t
  2  (
  3    temp_date  date primary key
  4  )
  5  organization index
  6  PARTITION BY RANGE (temp_date) (
  7    PARTITION part1 VALUES LESS THAN (to_date('13-mar-2003','dd-mon-yyyy')) ,
  8    PARTITION part2 VALUES LESS THAN (to_date('14-mar-2003','dd-mon-yyyy')) ,
  9    PARTITION part3 VALUES LESS THAN (to_date('15-mar-2003','dd-mon-yyyy')) ,
 10    PARTITION part4 VALUES LESS THAN (to_date('16-mar-2003','dd-mon-yyyy')) ,
 11    PARTITION part5 VALUES LESS THAN (to_date('17-mar-2003','dd-mon-yyyy')) ,
 12    PARTITION part6 VALUES LESS THAN (to_date('18-mar-2003','dd-mon-yyyy')) ,
 13    PARTITION junk VALUES LESS THAN (MAXVALUE)
 14  )
 15  ;

Table created.

see
http://technet.oracle.com/docs/products/oracle9i/doc_library/901_doc/server.901/a90120/ch2_feat.htm#55831
for what was added in 9iR1, see the 9ir2 new features for yet more.

July 10, 2003 lawrence -- Thanks for the question regarding "export >2 gig in size", version
You Asked
exporting when the export is larger than 2gig on a HP
unix server AND NOT USING COMPRESSION.
and we said...
You would export to a device that does not support seeking such as a tape (not
recommended, really slow) or a pipe.

Why not using compression?  it'll considerably cut down on the size?

I myself use both compression AND split to make my export be in many managable
sized file (500meg is my chosen size).  You could just use split and not
compress if you want.

Basically, you would create a pipe in the OS via:

$ mknod somefilename p

and then export to that pipe.  you would set up another process in the
background that 'eats' the contents of this pipe and puts it somewhere.  I use
split, you could use 'cat' to just put it into another file (if cat supports
files >2 gig -- thats the problem here, most utilities do not, you need to use a
special file io api to 2 gig file support).

Here is a script you can use as a template.  Yes, it uses compression but you
can take that out.  Its here to show you one method of doing this.

------------------------------
#!/bin/csh -vx

setenv UID /
setenv FN  exp.`date +%j_%Y`.dmp
setenv PIPE /tmp/exp_tmp_ora8i.dmp

setenv MAXSIZE 500m
setenv EXPORT_WHAT "full=y COMPRESS=n"

echo $FN

cd /nfs/atc-netapp1/expbkup_ora8i
ls -l

rm expbkup.log export.test exp.*.dmp* $PIPE
mknod $PIPE p

date > expbkup.log
( gzip < $PIPE ) | split -b $MAXSIZE - $FN. &

# uncomment this to just SPLIT the file, not compress and split
#split -b $MAXSIZE $PIPE $FN. &

exp userid=$UID buffer=20000000 file=$PIPE $EXPORT_WHAT >>& expbkup.log
date >> expbkup.log

date > export.test
cat `echo $FN.* | sort` | gunzip > $PIPE &

# uncomment this to just SPLIT the file, not compress and split
#cat `echo $FN.* | sort` > $PIPE &

imp userid=sys/o8isgr8 file=$PIPE show=y full=y >>& export.test
date >> export.test

tail expbkup.log
tail export.test

ls -l
rm -f $PIPE
--------------------------------------------------

This also always does an 'integrity' check of the export right after it is done
with an import show=y, that shows how to use these split files with import.
Review & Followup
Rating: 5
Exporting more than 2 GB Compressed June 14, 2001
Reviewer: scott clark from USA
Hi Tom,
Thanks for the script.  I haven't actually checked the size of the export file
uncompressed and I don't think we are going to have enough disk space to support
splitting the export without compression, but I will need to verify.
Once we get a 64 bit OS at year end, this limitation will be resolved until we
hit the other magic number of 4gb.

I have friends who have 5GB compressed files on Solaris.  Why are they able to
surpass the 4GB limitaition assuming they are using a 64 bit OS?

Rating: 4
Great script, but how to import? November 05, 2001
Reviewer: Andrew Cohen from Berkeley,CA
Great script. I see you can import through the pipe on the same machine, but now
if I ftp it to a new machine, how do I piece the files back together? --
something like?:

gunzip -c *.dmp.* > $PIPE
imp ... file=$PIPE ...

but does the *.dmp.* guarantee that it will gunzip the files in the right order?

Followup:
cat `echo $FN.* | sort` | gunzip > $PIPE &

sorts the filenames, sends them to cat, which give them to gunzip in the right
order.

Rating: 4
Something interesting July 10, 2003
Reviewer: Mark Griffin from Nasa Contractor Huntsville Alabama
Tom I implemented your script into our production environment(sun solaris 5.7,
Oracle 9i db 9.0.1.4).

I changed parameter MAXSIZE 4000m(2 gig I believe).
I then submitted script to job queue. Job ran fine in that export completed
successfully(according to log).

However, I also had same job to run in crontab and I'm getting the following:
. . exporting table                  WWV_DOCUMENT$
EXP-00056: ORACLE error 24801 encountered
ORA-24801: illegal parameter value in OCI lob function
. . exporting table          WWV_DOCUMENT$_ARCHIVE
EXP-00065: Error writing lob to the dump file.
. . exporting table                      XREF_DISC        142 rows exported
EXP-00002: error in writing to export file
EXP-00002: error in writing to export file
EXP-00000: Export terminated unsuccessfully
Thu Jul 10 00:15:36 CDT 2003

Do you have any clues as to what is problem might be ?

Followup:
4000m is NOT 2gig

2048m is 2gig

you don't have large file support and that is what this is saying.

Rating: 4
ahhh okay but... July 10, 2003
Reviewer: Mark Griffin from Huntsville Alabama
Well what can i say after i "really" calculated what 4000m/1024=total gig yes
you are right ;).

However this still doesnt address fact that this script ran successfully via job
submittal(to job queue) but was not successful as crontab run ?

The other puzzling thing to me is that previously we were performing one big
dump of db. db is currently 46 gig in size and export didnt have problem with
size of file in that it didnt complain with ORA-24801 error ?

I guess this leaves question of:
Even though my job submittal was sucessful in sense it completed if my i have
valid backup(no corrupt data) ? According to my log file everything appears to
be okay.

Followup:
I can only guess then when run as a job -- the database it was run against did
not create any files >2gig.

Or, look for environmental differences.  If you run the "cron job" from the
command line -- does it succeed or fail?

what were the file sizes from the "oem job" one?

Rating: 4
I believe... July 10, 2003
Reviewer: A reader
First off, thank you much for all your help.

That Im going to have to research the cronback job submittal side of things. I
believe what is happening is the fact that cronback is so barebones(in that it
really knows nothing about environment other than what you tell it) its running
into some type of ulimit or other parameter setting problem.

July 10, 2003 Pawan -- Thanks for the question regarding "PL/SQL Debugger", version 8.1.7
You Asked
Does Oracle have a PL/SQL debugger tool?
Thanks
and we said...
yes.  It is part of IDS (internet developer suite). Jdeveloper has this feature
as well.
Review & Followup
Rating: 3
A good pl/sql debugger has PL/SQL Developer June 21, 2002
Reviewer: A reader
Get trial version on
http://www.allroundautomations.nl/plsqldev.html
and test it. For details read the MS Word file Manual.doc

Rating: 3
Quality of PL/SQL Developer Strongly Seconded June 21, 2002
Reviewer: Cory Wittenborn from Chicago, IL
The above reviewer is correct--PL/SQL Developer has an excellent line-level
debugger.  It is also my favorite IDE for Oracle (in my opinion it blows TOAD
away).  It's free to get the demo, so you might as well try it...

Rating: 3
Another vote for PL/SQL Developer June 21, 2002
Reviewer: Colin Davies from Pasadena, CA
I use both TOAD and PL/SQL Developer.  TOAD is better for moving around the
database quickly for DBA work, and PL/SQL Developer is better for PL/SQL
development, especially the debugger.  It's only $150 to buy, a real bargain.

I avoid the Oracle GUI tools like the plague (sorry, Tom!), because, IMO, Oracle
has always been utterly clueless about user interfaces, and they are fat,
clumsy, and slow to load.  JDeveloper is not bad, but then it wasn't originally
an Oracle tool, was it?

Rating: 3
IDS 9i Rel2 Giving Some trouble. July 10, 2003
Reviewer: H.S.Anand from New Delhi, India
Hi Tom,
First of all,
This thread of discussion is the closest I could get to what I want to ask. SO
PLEASE ACCEPT MY APOLOGIES IF YOU THINK I BROKE YOUR:
<quote>
Please limit this to a comment or question relevant to the other text on this
page. Please do not try to start a new thread of discussion here (I will ignore
any followups that are not related to the discussion on this page)</quote>

Here is my query!
hI TOM,
I recently got to work on Oracle Internet Developer SUite 9i Release 2, which
has both Forms and Reports 9i.
I tried to run a form containing a report. I could run the form, but whenever I
invoked the report, i get an error.

Do i need to do some extra configurations vis-a-vis the report server??

this is what I did.

I created a simple form from the table emp.

I created a control block and placed a button on it.

in the when button pressed trigger, i used the RUN_REPORT_OBJECT built in, to
call and existing report, which again has been added to the REPORT node in the
forms tree.

Now when I press the button at runtime, i get an error, you have not mentioned
the name of the report!!

Can you help?

Regards,
H.S.Anand

Followup:
sorry, its been soooo very long since I've touched forms (since about 1995) that
I'm not going to be much help on this one.

Suggest you look at otn.oracle.com -> discussion forums, they have a pretty
active developer forum over there with people who use this every day.

Hint: they'll want more then "I get an error", they'll want exact error messages
and codes!

Rating: 2
Thank you! July 10, 2003
Reviewer: H.S.Anand from New Delhi, India
Thanks for the lead tom.

July 10, 2003 Tan -- Thanks for the question regarding "user authentication", version 8.0
You Asked
hi Tom, I am stuck with the following question and would appriciate your help,

What 2 methods of user authentication are available in Oracle??? What are some
advantages and disadvantages of each???

Thank u

Tan
and we said...
Well, there are alot more then two - the two most basic ones are:

USERNAME/PASSWORD authentication.

OS Authentication.

Username/password -- you know that one well, it has the same strengths/weakness
as your OS username/password does.

OS Authentication lets me log in like this:

$ sqlplus /

SQL*Plus: Release 8.1.6.0.0 - Production on Sun Apr 1 17:34:47 2001

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Enterprise Edition Release 8.1.6.2.0 - Production
With the Partitioning option
JServer Release 8.1.6.2.0 - Production

ops$tkyte@ORA8I.WORLD>

The database pickes up my identity from the OS.  It works well on a single
machine but not over a network.  Over a network it can be very insecure as the
machines just "trust" eachother.  All I would need to do is put a machine in
your network, create a user account in the OS named after some database account
and I would be in.  Never set remote_os_authent=true for this reason.

On NT it is more secure then on Unix with domains and such.

I use the OS identified accounts for all of my cron jobs -- that way I do not
have a username/password stored in a script anywhere.  Thats the strength of it.
Review & Followup
Rating: 4
authentication through web? October 15, 2001
Reviewer: Harvey from USA
That is very helpful.  Then, I have a slightly different situation.  If I have a
web page running Java Servlet or JSP connected to our Oracle database (8.1.7),
and I want to let only our NT domain user be able to log in.  How should I set
it?  Also, which one is better for this purpose, Java Servlet or JSP?

Followup:
Well, that isn't going to work.  You have a client connected via the web to the
app server.  The app server logs into the database from  its machine.  The
database client here is the app server, not the client running a web browser.
You cannot log into the database over the web using an app server and have the
credentials passed all the way through.

the java code will have to log in "as itself", you cannot pass the NT user
through a "man in the middle".

In 9i, something that may be of interest to you is:
http://download-west.oracle.com/otndoc/oracle9i/901_doc/java.901/a90211/oci_func.htm#1014235
You can combine this with your desire to authenticate all of the way through.
The client would identify itself to the middle tier, you can then connect on
their behalf to the database.

As servelet or JSP -- it is pretty much the same to me.   JSPs are mixing html +
java, a servelet is something that generates html (generally, could be xml or
anything really) from scratch.  Depends on what tools you are using and how
comfortable you are with either technology.

Rating: 4
Good advice for batch jobs... October 15, 2001
Reviewer: Andrew from USA
Security is always a headache, a list of pros and cons certainly helps.

Rating: 5
OS authentication in ASP and UNIX environment January 23, 2003
Reviewer: Sujata from Germany
Hi Tom,
We are using Active Server Pages..(IIS server)..
and database is 8i on unix...
We are trying to move to NT username authentication...
Here is the plan:
There will be a table that has ntusername married to the employee_id.
Application will always move in as one user that has all the rights...instead of
multiple databse users that we have now...
All the security is done through the app (using employee_id of the user)..there
are security tables already in place that are using security based on
emplids....in the existing system
So, no change there....we can return emplid to the app based on the NT user name
passed in....

Questions:
What are your thoughts/ways about doing OS authentication in this environment
and security in this kind of environment set up..
Also we were currently using triggers on the tables to catch modified by's and
modified dates for any dml's
It was easy before..because we used the database user who was logged in to
populate modified by column of the table..
but now it's single user environment...so we want to store..
NTUSER name...which only application can now...so the trigger does not have the
ease of slipping in "USER" for modified by....so one solution is to change the
procedure..and queries to accept a parameter for NTUSER..and change the queries
to also update MODIFIEDBY..which they weren't doing before...
Is there another way to do this through trigger.....

Thanks for your help...

Followup:
I hate it...

Putting security into the app - yuck.  why bother with security?

I would use ntier proxy authentication and STICK WITH database accounts.  Keep
them -- but use a proxy user to authenticate with.

see
http://tahiti.oracle.com/pls/db92/db92.drilldown?word=proxy+authentication&book=&preference=

Rating: 5
proxy authentication January 23, 2003
Reviewer: Sujata from Germany
We are using 8i...
9i has lot of stuff for proxy authentication...but when I used tahiti to search
8i docs...I am not getting much...at
"
http://tahiti.oracle.com/pls/tahiti/tahiti.drilldown?remark=&word=proxy+authentication&book=&preference=&vbook=1
Is this something that is 9i only..or wasn't matured in 8i..

Followup:
the proxy authentication was available only to OCI programs in 8i.

If you are undergoing such a massive update - you might consider using software
written this century?  It has the feature you need to make this succesful with
*no* changes to the underlying logic/security already in place in the database
after all.

Rating: 5
proxy authentication 9i January 24, 2003
Reviewer: Sujata from Germany
Tom,
So, you are saying we have to go to 9i to take advantage of "proxy
authentication". If we have to go to 9i..then we will just wait for this NT user
authentication until we go to 9i..
Could you discuss..please..the caveats of going with the model where..the
application goes in as a single user..that has execute on all the packages...so
this way...no user need to have any access on any tables...
and then you do specific access through some application roles like "manager" or
"employee"....and you can assign these roles to different employees through
database tables..
This way..there are no users that have any access to any tables or any
packages..there is only a single user...
and so users can not connect through sqlplus or toad...
they can only connect through their NTUSER name...and that NTUSER name will be
linked to the employee id's in security tables in the database..and that is
where they will be assigned different roles for this particular application.
Please explain...

Thanks.

Followup:
well, with packages no user need to have access on any tables.... either -
they shouldn't, just execute on the package.

Lets see what you lose with the single user

o all auditing
o all grants
o roles
o any ability to reuse this stuff in the future when the next great programming
paradigm comes along (cause the security is bound in the middle tier now)
o you would have to rewrite your code that already exists to fit into this
single user model
o you rewrite the security you already bought and paid for.

And if you think that not being able to access the database via anything other
then your application is a bonus, I'm not sure if you've talked to the end
users.  Everyone I meet has some ad-hoc requirements at some point.   That and
the "cat's meow of programming paradigms today" -- well, thats today, tomorrow
will be a different day just as last year was different and so on....

Just my 2cents worth.

Rating: 5
Proxy Authentication March 08, 2003
Reviewer: Ray from Ottawa,ON,CA
Hi Tom.

I want to authenticate internal users (NT) web application
What do I need to set up proxy authentication

IE which Oracle products
and What do I have to do

Ive read the proxy links and can't make heads or tails out of them.

tia

Followup:
"I want to authenticate internal users (NT) web application"

I've no idea what you mean by that -- I cannot make heads nor tails out of that
myself.

is it that you have an application server that allows you to identify users by
NT authentication and will pass onto your java application this identity?

or are you asking me how to get an app server to authenticate using nt (which
I'll just say "no clues")

Rating: 4
authentication March 09, 2003
Reviewer: Ray from Ottawa,On,Ca
Ok. Gotchya. It was the latter and I guess thats a no go, which is reasonable.

Can you describe how you set up authentication for ask tom.

Currently I am using the ONE BIG APP USER approach where the DAD signed the user
into ORACLE. The PLSQL/HTML app asks for the signon.  (I know yuk) I
authenticate the user in a table with encrypted password. I use Application
context, a session cookie(encrypted session id timestamp),
and FGAC based on the app (internal user_id). The user gets fucntionality to
manage their app accounts, and administer the FGAC.

Extending this to oracle ad hoc users, would entail a Login trigger to set up
their context appropriately, otherwise they'd have no access due to the
policies.

To use oracle instead of my user table would mean I would be creating Oracle
Users on the fly. Can you explain how you do this, and to extend this to Proxy
Authentication using OID,  what steps would I take and what would it mean what
to the application? Would I need a middle tier login?, What would this be?,
Would I need Advanced Security option from Oracle? Is there a step by step
example available?

tia
Ray

Followup:
I have no authentication for asktom, it is a public website.

For the admin screen, MY interface, i use database authentication.  browser puts
up login screen and i put in my db username/password.

Creating Oracle users on the fly is easy (i do it on my internal system).  It is
just "execute immediate 'create user ' || .....".  What I do is have people
input their email address, generate a password for them, create the account and
use utl_smtp to email it to them.

to use proxy authentication -- you would do the same -- create the account but
you would also grant connect through to some application account.  Then you
would write some java or C code that authenticated the user however you wanted,
and this java or C code would connect to the database as the application user on
behalf of the real user (don't need to know the real users database password --
in fact the user might not even know what the users password in the db is in
this case).

You do not need ASO for this.

If you goto the 9i docs on otn.oracle.com and search for proxy authentication,
you'll get pointed to examples.

Rating: 3
April 29, 2003
Reviewer: John from Canada
Hi Tom,

I have two dedicate windows 2000 servers: one has Oracle database 9.0.1, the
other has application server runing with JSP which uses jdbc thin connection to
the database. There is an oracle user owns the whole application schema.
Currently we store this user name and password in a text file on the application
server. JSP application uses it for the database accessing.

I am wondering whether there is a way to hide the user name/password.

Thanks.

Followup:
i guess you would encrypt it and decrypt it in java -- but then you have this
whole key management thing...

Rating: 5
hierarchical user model July 09, 2003
Reviewer: A reader
thank you Tom for your great service!

Our requirement is to have a user model where we
have a set of application users - we use database users
internally (with roles etc). However, we also need to
assign our own privileges (such as add/delete a particular
logical application entity). These privileges result in
actions performed at applciation level. Also
the entities managed can be grouped - you could then assign
a privilege to group level instead of each individual
entity. users could be given access to a group of
entities (with varying privileges - some db based and
some application defined privileges.)

Current design builds these application
privileges on top of Oracle user accounts (using application logic.) Is that the
best way or is there an alternative?)

Thank you.

Followup:
have you looked at fine grained access control via dbms_rls?

Rating: 5
yes we have looked at FGAC July 10, 2003
Reviewer: A reader
and we are using it. However the way we are using it
is that we have defined it on one central table - the
central entity that are to be manipulated by the end users.
We restrict the user from looking at that central entity
in UI by FGAC. Also, any entity that depends on that
entity has to do a join with the central entity table
for the FGAC to take effect (I am begining to suspect
this may not be a good approach?)

I think what you are implying is that we can use FGAC
to create policies - each of which translates to a privilege, is that right? So
for example, if a privilege
translates to deleting a record from a table of a particular type then you set
up a policy attached to a
user (or a database role) to that effect, right?
So can we create a privilege for any application defined action (say defined in
a plsql package procedure) as well
then? What are the kinds of actions that we can be restricted using FGAC.

Thank you!!!

Followup:
yes, that is what I was getting at -- that you can implement with FGAC your
policies to allow or not deletion of specific records/groups of records.

<quote>
So can we create a privilege for any application defined action (say defined in
a plsql package procedure) as well
then?
</quote>

didn't quite understand what that was getting at.

you can restrict all DML via FGAC.

Rating: 5
thanx - this is what I meant July 10, 2003
Reviewer: A reader
"So can we create a privilege for any application defined action (say defined in
a plsql package procedure) as well
then?"

What I meant was - say the "action" that you want to
restrict is a complicated one (involving some dmls,
some selects etc) and is written in a plsql package.

From what I understand, FGAC does not have a way of
restricting role1 to execute that plsql procedure.

O well - may be this can be done by standard Oracle
role/privilege (say execute on plsql procedure)?:-)

So in summary:

If the app wants to restrict a user or a group of users
from doing a select or a dml on a table - we should use
FGAC.

Let us say, we have a central entity (or a table) (called
central_entity. All entities derive their privileges
from their actions defined on the central_entity or a
group of central entity central_entity_group)

Now, how would you give appropriate privileges at the
central_entity level as well as central_entity_group levels.
(central_entity_group is an application defined
concept).

Sorry for the rambling!

Thank you!!

PS : to all readers
     --------------

We should all pool money to give Tom a gift for
the tremendous amount of service (can not be valued
by me at least!) that you have given (and are giving)
to us. So what would you like, tom? I am serious!!

Followup:
<quote>
If the app wants to restrict a user or a group of users
from doing a select or a dml on a table - we should use
FGAC
</quote>

yes....

for the other two paragraphs after that -- it is a matter of design.  You will
be using SQL to restrict what they can see with each DML statement (and hence
what they can therefore modify).  So, you would design this "privs" table for
maximum access performance -- at the cost of insertability into it (eg: you give
and take away privs lots lots less often then you look them up - opimize data
retrieval at the expense of modification for this).....

Rating: 5
excellent, btw July 10, 2003
Reviewer: A reader
what gift do you want?:)

July 10, 2003 David -- Thanks for the question regarding "Oracle logins", version 8.1.6
You Asked
Is it better to use a single user name & password in an Oracle based application
& track user rights in tables, or allow each user to use a native Oracle login?

For example, I am building a web based application for 1,000 users. Should my
application always connect to Oracle with a single logon, or should I always
pass the users unique Oracle logon & use Oracle for security? Note: in the
former case I would be creating tables to store their username, password &
rights etc.

If each user has a unique Oracle logon will it significantly impact database
performance?

Thank you
and we said...
ops$tkyte@ORA8I.WORLD> select count(*) from all_users;

  COUNT(*)
----------
     47493

ops$tkyte@ORA8I.WORLD>

I love to be able to use roles, grants, auditing, "select user from dual", don't
have to worry about someone kidnapping my usernames/passwords, knowing who is
logged in at any point in time and so on....

My personal preference is to use the database accounts whenever possible.

Many people feel differently, they like to write their own security code, do
their own logon, write their own auditing.  Guess it depends if you want to code
it, or use what exists.

(my database runs pretty fast, you might even find you run slower with the DIY
approach).
Review & Followup
Rating: 5
Using Oracle native logons January 20, 2002
Reviewer: david Schwartz from New Jersey
Thanks.
Are you saying that your Oracle install has 47493 users!?

Does this have anything to do with connection pooling?

Followup:
I have 47,493 named accounts.

I run a webserver (ias of course).

At any point in time I have between 0 and 60 sessions going at once (using the
app server as the "connection concentrator").

They all log in with their own identities for applications that require an
identity, they all log in with "PUBLIC_USER" for apps that do not (eg: to
search, you need not log in.  To take an online certification test on my site,
you need to log in).

Rating: 5
Oracle Logins January 20, 2002
Reviewer: David Schwartz from New Jersey
OK.
I just want to verify - Are you saying that with your 47493 accounts Oracle has
no problem handling the unique logins & that connection pooling does not require
me to have only one login account for all my users?

Followup:
Oracle has no problem.

Connection pooling -- depends on whose you use and what it supports and how it
is implemented.  You'll have to test with yours to see.

Rating: 3
Using Oracle native logons July 26, 2002
Reviewer: ace from Singapore
I was wondering whether the security issue is also taken into consideration ?

Rating: 3
Users Logon July 09, 2003
Reviewer: Michael Dinh from USA
I am curious as to what you think eBay's implementation for user logon is -
Oracle based or just another user table?

Best Regards

Followup:
pretty sure it is *not* a db account.

Rating: 5
How do you logon a userID same as email address ? July 10, 2003
Reviewer: Amit from OH
Hi Tom,

After reading your suggestions, I am thinking of using database users instead of
storing username/password in a table. I would like my userIDs to be same as
emailIDs. However, how can I login a user with ID such as - abc@xyz.com ? I have
been trying this on SQLPLUS and it seems to think that @  separates ID from Host
string.

Thanks for your time
Amit

Followup:
@ would be part of a database link name.  I would not suggest you even go that
path.  besides, email addresses like that will be larger then 30 characters.

The real problem is email address CHANGE (been there, done that dozens of
times).  You would be making a big mistake using  email addresses (people get
married, email changes.  company decides to change domain, emails change -- i
USED to be tkyte@us.oracle.com -- not any more, just tkyte@oracle.com or
thomas.kyte@oracle.com).  It is still killing me that everyones email in the
company changed.  So many sites still have me registered as tkyte@us.oracle.com
and using them is "broken" unless i remember to update that. ugh.

Rating: 4
email ID as user July 10, 2003
Reviewer: Amit Kumar from OH
Hi Tom,

thanks for your suggestion. I do remember reading that advice in another answer
of yours. Actually, the 'brainstorm' occurred to me because amazon.com does use
email as IDs - its probably not a database user.

My real problem is : I have people applying on web to a centaralized database (
out of my control, except dumps ). I would like the applicants to move to my
website immediately after applying, and fill in some more information.
Unfortuantely, the centralized database does not allocate unique IDs to these
applicants till midnight, when a batch runs. Now, I need to uniquely identify
these applicants and also assign the same IDs at a later date ( using emails, I
can easily match it up with centralized db).

The only  unique aspect seems to me email addresss ( unless I use name/birthdate
combinations). I would like to use your approach of having database users
instead of a table with usernames. Of course, this email will be only for
identification and not for sending emails.

So, is my idea of useing email shot ? Matter of curiousity now - there is no way
to login an email ID to SQLPLUS ( I may not use this approach though)? its easy
enough to create a user like this.

Followup:
well, you could use translate( email, '@.', '__' ) -- including in the
translation table other characters that might not be useable -- but then again,
it might not be unique either

x_y@foobar.com

would become x_y_foobar_com

which is the same as

x@y.foobar.com

which probably isn't a problem -- but long emails, >30 characters, may still
present a problem.

July 10, 2003 summa -- Thanks for the question regarding "Trigger v/s Procedure execution Speed (performance speed)", version 8.1.7
You Asked
Hi,
1)   Suppose a piece of code can be implemented in either trigger or procedure.
     I want to know which is the best one in terms of execution   speed (i.e.
which will better perform in terms of speed?) .. why?

2) If I'm given a complex query, has many sub queries,joins, etc...
   How i can start performance tuning??
   I want a steps for what are all the thing i've to look into.....

  thanks in advance
Regards
Summa
and we said...
1) in a PACKAGED procedure.  The SQL in packages/procedures will be cached for
your session, reducing the number of parses whereas it is not in a trigger.

Consider:

ops$tkyte@ORA920> create table t ( x int );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace procedure p
  2  as
  3          l_cnt int;
  4  begin
  5          select count(*) into l_cnt from dual IN_PROCEDURE;
  6  end;
  7  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace trigger t_trigger
  2  before insert on t for each row
  3  declare
  4          l_cnt number;
  5  begin
  6          select count(*) into l_cnt from dual IN_TRIGGER;
  7          p;
  8  end;
  9  /

Trigger created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> alter session set sql_trace=true;

Session altered.
ops$tkyte@ORA920> insert into t values(1);

1 row created.

ops$tkyte@ORA920> /

1 row created.

ops$tkyte@ORA920> /

1 row created.

ops$tkyte@ORA920> /

1 row created.

ops$tkyte@ORA920> /

1 row created.

ops$tkyte@ORA920> /

1 row created.

SELECT count(*)
from
 dual IN_TRIGGER

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        6      0.00       0.00          0          0          0           0
Execute      6      0.00       0.00          0          0          0           0
Fetch        6      0.00       0.00          0         18          0           6
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       18      0.00       0.00          0         18          0           6

********************************************************************************
SELECT count(*)
from
 dual IN_PROCEDURE

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      6      0.00       0.00          0          0          0           0
Fetch        6      0.00       0.00          0         18          0           6
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       13      0.00       0.00          0         18          0           6

note the parse counts..

2) start by throwing out the query and asking for the QUESTION that needs to be
answered.  Then, using your knowledge of everything there is available from
analytics to subqueries to whatever -- develop a query to answer it.  Develop a
couple of different queries to answer it.  Think "hey, if there were an index
here, that would be really helpful".

I don't know -- that is sort of what I do.  I don't take the answer and try to
make it fit the question, I take the question and try to develop an optimal
answer.  How many times I've been given queries with outer joins all over the
place -- none of which was needed, or extra tables "just because".  A poorly
performing query could be the result of someone not truly knowing the question
and just sort of adding junk over time.

If they cannot phrase the question -- must not be an important question.
Review & Followup
Rating: 4
Ooops January 26, 2003
Reviewer: Cefers.br from Brasil
Tom, I think I need your help here.

I just didn't understand the meaning of IN_PROCEDURE / IN_TRIGGER at the end of
"select from DUAL" statement.

I search the Oracle OnLine help, but didn't find anything.

Can you explain?

Thanks.

Followup:
it is just a table alias, you know, like:

select * from emp E, dept D where e.deptno = d.deptno;

E and D are table aliases.

I used IN_PROCEDURE and IN_TRIGGER so that when I showed you the tkprof, it
would be clear as to which one was executed where.  That is all - nothing
"magical", just a documentation trick if you will.

Rating: 5
Ohh, I knew it! January 26, 2003
Reviewer: Cefers.br from Brasil
Hi Tom.

Now I got it. It is a tkproof trick... A nice one!

When I first looked at IN_PROCEDURE/IN_TRIGGER, they seemed like table aliases
to me. But I got a litle confused once there was only one table in the statement
(I usualy use table aliases when there's 2 or more tables on the query).

I didn't see the "tricky side" of the whole thing...

Thank you for your answer and I'm sorry about my stupid question (I feel like a
dumb now, you know)! :)

Rating: 5
Learn something new every day! January 27, 2003
Reviewer: Chuck Jolley from Oklahoma City, OK USA
It would never have occured to me that trigger code was not cached.
Is a procedure called from a trigger cached?
My database has a LOT of trigger activity.
Should I move the code into procedures in a package and just call the code in
the triggers?
Thanks!

Followup:
trigger code is cached.

the sql statements inside maybe reparsed.

Here is what you should do:

o turn on sql_trace in your application
o exercise it (run it for a while)
o run tkprof against the trace file

Now, if you see lots of PARSES and EXECUTES of SQL in this trace file that are
in triggers, like this:

SELECT count(*)
from
 dual IN_TRIGGER

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        6      0.00       0.00          0          0          0           0
Execute      6      0.00       0.00          0          0          0           0
Fetch        6      0.00       0.00          0         18          0           6
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       18      0.00       0.00          0         18          0           6

6 parses, 6 executes -- that would be a candidate for moving the logic into a
procedure so you get to 1 parse, 6 executes.

If you see lots of 1 parse, N executes -- they are fine, leave them be.  It is
only if you see excessive parsing of a statement that you need to worry about
it.

Rating: 3
Package per Module/ Package per Table? January 28, 2003
Reviewer: Tony from India
There is an argument among the developers in my company.
1. Some guys argue that we need to have one package per module that consists of
many tables.
2. Someother guys argue that we need to have one package per table to put all
insert, update , delete and select on that table in single package.

In the first approach, we will have 4 packages if there are 4 modules in an
application.
In the second approach, we will have 100 packages if there are 100 tables in the
application.

Which approach is correct?
Can you share your expertise please?

Followup:
how about option #3

3) like any other 3gl, you would put into a package that which logically belongs
together.  You would use common sense, good modular coding practices in order to
develop readable, maintainable code.

Hows that?  that is what I do -- you use common sense, traditional coding
practices, modular design and what goes into a package falls out *naturally*.

Rating: 3
Trigger Code is precompiled January 28, 2003
Reviewer: Tony from India
Tom, Reparsing should not happen as the trigger body is precompiled. Am I
correct?.
Is there any concurrency issue if triggers are extensively used in multiuser
environment?

Followup:
Hmm, compilation happens when we create the trigger.

Parsing -- when we run.  The sql isn't parsed and executed when we compile, only
when we run.

I'm sort of surprised at the comment:

"Reparsing should not happen as the trigger body is precompiled. Am I
correct?"

since my example (which I thought was as straightforward as can be???) shows
conclusively the opposite is not true????

Anyway, the only concurrency issues with triggers are those you code yourself.
That is, there is no more chance of concurrency issues with trigger then there
is with anyother construct.

Rating: 3
My Ignorance January 28, 2003
Reviewer: Tony from India
Tom, I know there is a difference between compilation and parsing, but I couln't
make out what exactly the difference is. Could you please?. [ Sorry if the
question is so basic].

Followup:
compile compiles plsql code into something that can be executed.  compiling
involves "parsing" but "parsing of the plsql" -- not a conventional sql parse.

in the context of the database, when we talk of parsing, we are generally
refering to the step you take before actually executing a statement -- parsing
of the sql statement.

Rating: 4
"the sql statements inside maybe reparsed." January 28, 2003
Reviewer: Martin from The Netherlands
Tom,

Isn't it just better to always call a (packaged) procedure and never put
SQL-statements directly in the trigger-body?
Otherwise, How will I know (beforehand)if a SQL-statement in the triggerbody
will be reparsed?

Followup:
you know this:  it will be reparsed if called more then once across calls.

Consider:

ops$tkyte@ORA920> create table t1 ( x int );
Table created.

ops$tkyte@ORA920> create table t2 ( x int );
Table created.

ops$tkyte@ORA920> create or replace procedure p
  2  as
  3  begin
  4          for x in ( select * from dual p) loop null; end loop;
  5  end;
  6  /
Procedure created.

ops$tkyte@ORA920> create trigger t1_trigger before insert on t1 for each row
  2  begin
  3          for x in ( select * from dual t1 ) loop null; end loop;
  4          p;
  5  end;
  6  /
Trigger created.

ops$tkyte@ORA920> create trigger t2_trigger before insert on t2 for each row
  2  begin
  3          for x in ( select * from dual t2 ) loop null; end loop;
  4          p;
  5  end;
  6  /
Trigger created.

ops$tkyte@ORA920> alter session set sql_trace=true;
Session altered.

ops$tkyte@ORA920> begin
  2          for i in 1 .. 4
  3          loop
  4                  insert into t1 values ( i );
  5          end loop;
  6  end;
  7  /
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> insert into t2 values ( 1 );
1 row created.

ops$tkyte@ORA920> insert into t2 values ( 1 );
1 row created.

ops$tkyte@ORA920> insert into t2 values ( 1 );
1 row created.

ops$tkyte@ORA920> insert into t2 values ( 1 );
1 row created.

Now, when we tkprof that we'll be interested in seeing the

o query in P
o query in T1
o query in T2

and how many times each was parsed/executed:

select * from dual t1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      4      0.00       0.00          0          0          0           0
Fetch        8      0.00       0.00          0         12          0           4
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       13      0.00       0.00          0         12          0           4

select * from dual p

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      8      0.00       0.00          0          0          0           0
Fetch       16      0.00       0.00          0         24          0           8
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       25      0.00       0.00          0         24          0           8

select * from dual t2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        4      0.00       0.00          0          0          0           0
Execute      4      0.00       0.00          0          0          0           0
Fetch        8      0.00       0.00          0         12          0           4
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       16      0.00       0.00          0         12          0           4

So, the query in T1 was parsed ONCE and executed 4 times.  Four inserts in a
single call - the cursor was cached.

The query in T2 however -- across calls -- was parsed 4 times, once per
call.

The query in P -- regardless of how it was invoked, via t1 or t2 -- was parsed
once per session.

Rating: 4
"the sql statements inside maybe reparsed." January 28, 2003
Reviewer: Martin from The Netherlands
Tom,

Isn't it just better to always call a (packaged) procedure and never put
SQL-statements directly in the trigger-body?
Otherwise, How will I know (beforehand)if a SQL-statement in the triggerbody
will be reparsed?

Rating: 5
parsing of pl/sql January 28, 2003
Reviewer: Ashiq Shamsudeen from Trichy, Tamil Nadu, India
Hi Tom,

   I know the parsing in context of sql statements ,Can u  explain what parsing
is all about in context of pl/sql

Followup:
in order to compile code -- someone has to parse it, turn it into a structure
that can be interpreted.  hence we parse it.

Rating: 3
confused ? conventional sql v/s pl/sql February 13, 2003
Reviewer: Ian from CT
(Sorry for submitting review of this question in another question)

When I am creating a trigger , is the compiled code not stored in the database ?

Then why would it only parse pl/sql and not  the conventional sql.
So that when trigger is actually fired at time of execution it gets parsed
statement and executes it.
Also then why is parse not happening in procedure body ? Is the conventional SQL

in procedure body parsed and stored.
I guess if you could clarify this in a little detail , this will be totally
clear as this applies to all sql code we write.

Followup:
well, goto where you first asked it:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:149212348066

      Rating: 3
Just 1 last
      thing  February 14, 2003
Reviewer:  Ian
       from CT
Things are very clear now except 1 last thing which will summarise the whole
thing :-
You began with
"SQL inside procs is cached for your session while for triggers it is not"
Q. Is this by design in Oracle ? (I guess yes) Any reason for doing it so ?
As you put it in another reply when trigger is fired across calls it gets
reparsed while in procs it is once per session.
So then it would make sense to have the same parsing/caching logic for triggers
.
Hence the question why is the behaviour different.
Thank you very much for all the help
Ian

          Followup:

q) obviously yes, it does it that way.

not sure why...

well, triggers have an entirely different history then stored procedures so the
reasoning is probably legacy in nature.  Prior to 7.3, they were not stored
compiled - they were just stored as "text".  I have a feeling that deep down
they are still very much like an anonymous plsql block as they used to be --
just stored compiled.  Hence, just like an anonymous block executed two times:

begin
  for i in 1 .. 5 loop
      for x in ( select * from emp ) loop null; end loop; <<<== sql is parsed
once
  end loop;
end;
/

begin
  for i in 1 .. 5 loop
      for x in ( select * from emp ) loop null; end loop; <<<== sql is parsed
once
  end loop;
end;
/

the sql is parsed ONCE per block execution -- that is the way anonymous blocks
are done - so I'm guessing it is some of that legacy.

      Rating: 5
This Tuning
      issue always challenges whatever you learnt!!!  February 23,
      2003
Reviewer:  Anand H S  from New Delhi, India

Hi Tom,
Great Explanation Given for the  parseing/caching of triggers when compared to
procedures. Infact I happened to use a lot of triggers in my applications and I
was once asked this question By an interviewer as to why i used trigger and not
procedure for the operations that I had just described! And I was stumped that
time!!!!

And I must say that I have come to like your style of handling all the queries!!

Great!!!

      Rating: 5
Referencing OLD
      and NEW values  February 27, 2003
Reviewer:
      Martin  from UK
Hi Tom,

just a quick question about the use of stored procs etc. within triggers. When
you write a trigger which references :OLD and :NEW attributes, what do you
recommend about utilising stored procs in that situation? Keep them in the
trigger as they were originally, or put them as parameters etc in the stored
proc?

Thanks

          Followup:

depends on what I'm doing.  Might pass them as parameters, might set them
directly.  Do whatever feels "natural", just consider them variables you didn't
have to declare.

      Rating:
      5
Confusion  March 22, 2003
Reviewer:
      Riaz Shahid  from PRAL, Islamabad, Pakistan
Can't we pin the trigger through dbms_pool so that its execution doesn't need
parse calls when called again???

Riaz

          Followup:

Pinning causes code to be "locked down" in the shared pool.

Pinning will never reduce PARSE counts.
Pinning could reduce HARD PARSES since the statement should always be found in
the shared pool

Hence, no, pinning will not affect this behaviour.  In any case, it is not the
TRIGGER that is getting parsed -- it is the SQL within the trigger.  Pinning
that SQL will do nothing either.

Pinning is something we do not have a need to ever do, not since version 8.0
when they introduced the LARGE POOL.

      Rating:
      5
  March 22, 2003
Reviewer:  Robert
       from Memphis, USA

      Rating: 4
need more
      explanation please!  March 23, 2003
Reviewer:
      Chandru  from Harlingen, TX
"Pinning is something we do not have a need to ever do, not since version 8.0
when they introduced the LARGE POOL."

Tom, could you please explain a little more why? I was under the impression that
pinning most frequently used packages/procedures at the startup would help.
Thanks.

          Followup:

help with what?  what problem were you trying to solve?

      Rating:
      5
  March 24, 2003
Reviewer:  Helena
      Marková  from Bratislava, Slovakia

      Rating: 5
excellent
      thread! a few doubts  July 09, 2003
Reviewer:
      A reader
Could you kindly explain the followin quote? Does this
imply that using anonymous blocks creates more parses than
it should? What are the two for loops depicting?

Thank you so much!!

"I have a feeling that deep down
they are still very much like an anonymous plsql block as they used to be --
just stored compiled.  Hence, just like an anonymous block executed two times

begin
  for i in 1 .. 5 loop
      for x in ( select * from emp ) loop null; end loop; <<<== sql is parsed
once
  end loop;
end;
/

begin
  for i in 1 .. 5 loop
      for x in ( select * from emp ) loop null; end loop; <<<== sql is parsed
once
  end loop;
end;
/
"

          Followup:

the two for loops are just depicting "some work performed"

Everytime you execute an anonymous block containing SQL, the SQL contained
therein is parsed once if executed (hard or soft).

If you put it into a named plsql block (procedure/function/package), the SQL
contain therein is parsed once PER SESSION if executed.

Consider:

ops$tkyte@ORA920> create or replace procedure p
  2  as
  3          l_cnt number;
  4  begin
  5          select count(*) into l_cnt from dual;
  6          select count(*) into l_cnt from dual;
  7          select count(*) into l_cnt from dual;
  8          select count(*) into l_cnt from dual;
  9          select count(*) into l_cnt from dual;
 10          select count(*) into l_cnt from dual;
 11          select count(*) into l_cnt from dual;
 12          select count(*) into l_cnt from dual;
 13          select count(*) into l_cnt from dual;
 14          select count(*) into l_cnt from dual;
 15  end;
 16  /

Procedure created.
procedure with 10 queries

ops$tkyte@ORA920>
ops$tkyte@ORA920> column value new_val V
ops$tkyte@ORA920>
ops$tkyte@ORA920> select a.name, b.value
  2    from v$statname a, v$mystat b
  3   where a.statistic# = b.statistic#
  4     and a.name = 'parse count (total)'
  5  /

NAME                                VALUE
------------------------------ ----------
parse count (total)                   356

ops$tkyte@ORA920> declare
  2          l_cnt number;
  3  begin
  4          select count(*) into l_cnt from dual;
  5          select count(*) into l_cnt from dual;
  6          select count(*) into l_cnt from dual;
  7          select count(*) into l_cnt from dual;
  8          select count(*) into l_cnt from dual;
  9          select count(*) into l_cnt from dual;
 10          select count(*) into l_cnt from dual;
 11          select count(*) into l_cnt from dual;
 12          select count(*) into l_cnt from dual;
 13          select count(*) into l_cnt from dual;
 14  end;
 15  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select a.name, b.value, b.value-&V DIFF
  2    from v$statname a, v$mystat b
  3   where a.statistic# = b.statistic#
  4     and a.name = 'parse count (total)'
  5  /
old   1: select a.name, b.value, b.value-&V DIFF
new   1: select a.name, b.value, b.value-       356 DIFF

NAME                                VALUE       DIFF
------------------------------ ---------- ----------
parse count (total)                   371         15

run an anonymous block with 10 queries and we did 15 parses -- now there was a
parse for the block of code, a parse each for the queries to print out this info
and a parse or two by sqlplus to call dbms_output.get_lines and such -- 10 of
these parsed were for the queries themselves.

ops$tkyte@ORA920> declare
  2          l_cnt number;
  3  begin
  4          select count(*) into l_cnt from dual;
  5          select count(*) into l_cnt from dual;
  6          select count(*) into l_cnt from dual;
  7          select count(*) into l_cnt from dual;
  8          select count(*) into l_cnt from dual;
  9          select count(*) into l_cnt from dual;
 10          select count(*) into l_cnt from dual;
 11          select count(*) into l_cnt from dual;
 12          select count(*) into l_cnt from dual;
 13          select count(*) into l_cnt from dual;
 14  end;
 15  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select a.name, b.value, b.value-&V DIFF
  2    from v$statname a, v$mystat b
  3   where a.statistic# = b.statistic#
  4     and a.name = 'parse count (total)'
  5  /
old   1: select a.name, b.value, b.value-&V DIFF
new   1: select a.name, b.value, b.value-       371 DIFF

NAME                                VALUE       DIFF
------------------------------ ---------- ----------
parse count (total)                   386         15

run the same block, same activity -- all 10 queries were parsed (soft parsed
but parsed)

ops$tkyte@ORA920> exec p

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select a.name, b.value, b.value-&V DIFF
  2    from v$statname a, v$mystat b
  3   where a.statistic# = b.statistic#
  4     and a.name = 'parse count (total)'
  5  /
old   1: select a.name, b.value, b.value-&V DIFF
new   1: select a.name, b.value, b.value-       386 DIFF

NAME                                VALUE       DIFF
------------------------------ ---------- ----------
parse count (total)                   401         15

initially looks no different however...

ops$tkyte@ORA920> exec p

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select a.name, b.value, b.value-&V DIFF
  2    from v$statname a, v$mystat b
  3   where a.statistic# = b.statistic#
  4     and a.name = 'parse count (total)'
  5  /
old   1: select a.name, b.value, b.value-&V DIFF
new   1: select a.name, b.value, b.value-       401 DIFF

NAME                                VALUE       DIFF
------------------------------ ---------- ----------
parse count (total)                   406          5

10 less parses!  plsql nicely cached the cursors for us, now we just have the
queries, the block that executed the procedure,
dbms_output/dbms_application_info calls.   In your real program -- you would
have ZERO parses (the 5 is only because we are using sqlplus, a very very
generic tool)

      Rating: 5
excellent -
      thank you!  July 10, 2003
Reviewer:
      A reader
could you kindly alo explain "In your real program -- you would have ZERO parses
(the 5 is only because we are using sqlplus, a very very generic tool)"

Thank you!

          Followup:

in your real program, you would parse "begin p; end;" ONCE in your program and
you would execute it over and over.

The second execute would therefore involve ZERO parses.  No parse of the
anonymous block, no parse of the SQL contained therein.  Just an EXECUTE.

      Rating: 5
I dont
      understand the question  July 10, 2003
Reviewer:
      A reader
A procedure and a trigger are two separate entities.
I guess (from the answers by Tom), we are aksing if
the code inside the trigger should be implemented
directly or should we call a plsql procedure to do
that.

          Followup:

I would implement the code as a packaged procedure and have the trigger be a one
liner that simply calls the packaged procedure.

      Rating: 4
How
      true!  July 10, 2003
Reviewer:  A reader
       from USA
"I don't take the answer and try to
make it fit the question."

This is something I seem to see a lot with a single function: DECODE.  I've been
in quite a few shops as a consultant, and there seems to be an uncanny tendancy
to look at a question and ask 'How can I make DECODE do this?'  I've seen some
incredibly convoluted, albeit creative, uses of DECODE that seem to indicate
that it's the one function that most would-be 'developers' understand
completely.

I'm curious if you've seen the same thing, and if so, why do you think that is?

          Followup:

yes, I've seen some gnarly decodes -- CASE makes them more readable.

I don't really have any "why I think this is so" answers for that....

I like to introduce decode (and now case) lots myself...  as a tuning device (to
remove unions most often)...

      Rating: 5
Excellent
      Tom!  July 11, 2003
Reviewer:  Sami
       from NJ,USA

July 11, 2003 -- Thanks for the question regarding "SDU,TDU
      etc", version 8.1.6

      You Asked
Tom:

how do i know my current setting for SDU and TDU if it is not present at
listener.ora and tnsnames.ora?
also how to know what's setting for MTU?

      and we said...
The default values for SDU and TDU are 2048, with the maximum value
possible being 32768.

MTU (Maximum Transmission Unit). This MTU value is fixed and depends
on the actual network implementation used.  On standard Ethernet networks, the
default MTU size is set to 1514 bytes. On standard token ring networks, the
default MTU size is 4202.

Review &
      Followup

      Rating: 4
Tuning SDU
      TDU  July 10, 2003
Reviewer:  Doug
       from CT, USA
I have an Oracle database getting large amounts of data over a dblink from
another Oracle database.  The top wait event is SQL*Net message from dblink.  I
understand that something has to be the top wait event no matter how things are
performing. Using the centiseconds, it looks like about 24 seconds in a 10
minute period.  This sounds worth looking into to me. That's about 5%.  One
suggestion was to hike up the SDU and TDU.  What do you think? The downside is
big mostly empty packets?  Also, since the metalink notes seem to indicate that
the listener.ora for the "remote" db must be set as well as the client, is there
any way to prevent these settings from affecting other connections from
different servers that are not presenting a problem currently?  I.e. different
clients?

          Followup:

those settings are negotiated on connect and both client and server must be
willing to go the same size -- so, it'll only affect clients that have the
setting as well (so other clients won't be affected by this)

What you have to ask is "is the amount of data divided by 24 seconds giving me a
reasonable throughput rate -- what is the amount of data transmitted per second
there".  If you are getting what seems to be a reasonable rate already, you are
done.  If not, then we can look at adjusting network settings.

July 11, 2003 Paul -- Thanks for the question regarding
      "Percent of Running Total", version 8.1.7

      You Asked
I am trying to use the new analytical functions to automate a process I must do
every month.

The problem I need to solve is:

"Given a set of ordered counts, get the sum of all previous and current counts
(the cumulative total.)  Then compute the percent of the cumulative total,
relative to the grand total of the counts."

I've tried percent_rank, which just gave the percent of the individuals rows,
but I need the percent of the cumulative count.  I got the SUM to do the
cumulative part, but can not get either an outer query or another inline
analytic function to calculate the percent of the cumulative total.

I realize this is complex, perhaps not doable, but would appreciate any
pointers.  Thanks in advance!

      and we said...
doable, for sure.

Note in this example how I compute sum(sal) two ways -- once with order by sal,
empno and once just by sal.  Note the difference in deptno=20 when we have
duplicate salaries.

scott@ORA817DEV.US.ORACLE.COM> column pct1 format 999.99
scott@ORA817DEV.US.ORACLE.COM> column pct2 format 999.99
scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM> select deptno, ename, sal,
  2             round(sal1/sal3*100,2) pct1,
  3             round(sal2/sal3*100,2) pct2
  4    from (
  5  select ename,
  6             sal,
  7             deptno,
  8             sum(sal) over ( partition by deptno order by sal, empno ) sal1,
  9             sum(sal) over ( partition by deptno order by sal ) sal2,
 10             sum(sal) over ( partition by deptno ) sal3
 11  from emp
 12             )
 13  /

    DEPTNO ENAME             SAL    PCT1    PCT2
---------- ---------- ---------- ------- -------
        10 MILLER           1300   14.86   14.86
        10 CLARK            2450   42.86   42.86
        10 KING             5000  100.00  100.00
        20 SMITH             800    7.36    7.36
        20 ADAMS            1100   17.47   17.47
        20 JONES            2975   44.83   44.83
        20 SCOTT            3000   72.41  100.00
        20 FORD             3000  100.00  100.00
        30 JAMES             950   10.11   10.11
        30 WARD             1250   23.40   36.70
        30 MARTIN           1250   36.70   36.70
        30 TURNER           1500   52.66   52.66
        30 ALLEN            1600   69.68   69.68
        30 BLAKE            2850  100.00  100.00

14 rows selected.

scott@ORA817DEV.US.ORACLE.COM>

Review &
      Followup

      Rating: 4
Thanks for the
      answer  August 19, 2002
Reviewer:  Paul
      Phillips  from Orlando, FL
The example is even more interesting with the two different percents, these
analytic functions are something I see more and more user for.

      Rating: 4
using connect
      by prior  July 10, 2003
Reviewer:  Shahrouz
      DARVISHZADEH  from Austria
Hi Tom
Is it possible to use the sum over partition with a range of rows comming from
connect by prior? for example to build such a response:

x1          45        100%
 xx2        15        33%
 xx2        20        45%
   xxx3        15          75%
   xxx3        5        25%
 xx2        10        22%

          Followup:

full example please, create table, insert into etc.

I've no idea what I'm looking at here really.

      Rating: 5
Is there some
      thing wrong?  July 10, 2003
Reviewer:  Yong
       from CA, USA
Tom,

Do you think the result should be like this
instead of 100.00% on some of records?

    DEPTNO ENAME             SAL     PCT1
---------- ---------- ---------- --------
        10 CLARK            2450    28.00
        10 KING             5000    57.14
        10 MILLER           1300    14.86
        20 SMITH             800     7.36
        20 ADAMS            1100    10.11
        20 FORD             3000    27.59
        20 SCOTT            3000    27.59
        20 JONES            2975    27.36
        30 ALLEN            1600    17.02
        30 BLAKE            2850    30.32
        30 MARTIN           1250    13.30
        30 JAMES             950    10.11
        30 TURNER           1500    15.96
        30 WARD             1250    13.30

          Followup:

?

      Rating: 5
Followup to
      review by Yong  July 10, 2003
Reviewer:
      Trevor
Tom

I think Yong is asking why in your example
(lets look at Dept 10) emp KING is showing 100%
when if you total the salaries of Dept 10
the salary for KING is 57.14% of the total salaries
for the dept.

Regards
Trevor

          Followup:

oh, that is easy -- that is because I was computing the pct based on the
cumulative (running total) salary by dept.  100% will be the end result at least
once for each of the deptno's by definition.

      Rating: 3
Yong read the
      question again  July 10, 2003
Reviewer:
      A reader  from MN, USA
Yong,
  Your solution would be true if we are taking a percent of each individuals
salary against the running total for his dept.. (see PCT3 below)
  however the main question asked here is

<quote>
         "get the sum of all previous and current counts
         (the cumulative total.)"
</quote>

 that is what the
sum(sal) over ( partition by deptno order by sal, empno ) sal1,
does in Toms query

</quote>
          "Then compute the percent of the cumulative total,
           relative to the grand total of the counts"
</quote>

 that is what the sal1/sal3 does.

Your answer would be true if the first condition in the question was not
provided.

HTH

      Rating:
      3
  July 11, 2003
Reviewer:  Shahrouz
      DARVISHZADEH  from Austria
I mean something like this:
SQL> select LPAD('  ',2*(LEVEL-1))||ename ename, job, sal
  2  from emp
  3  start with job='PRESIDENT'
  4  connect by prior empno=mgr
  5  /

ENAME                JOB              SAL  my_question
-------------------- --------- ---------- ----------
KING                 PRESIDENT       7500  43537 (=sum of all: First LEVEL)
  JONES              MANAGER         4462  16312 (=sum of all under this level)
    SCOTT            ANALYST         4500  6150  (=4500+1650)
      ADAMS          CLERK           1650  1650  (=1650)
    FORD             ANALYST         4500  5700  (=4500+1200)
      SMITH          CLERK           1200  1200  (=1200)
  BLAKE              MANAGER         4275  12225
    ALLEN            SALESMAN        2400  2400
    WARD             SALESMAN        1875  1875
    MARTIN           SALESMAN        1875  1875
    TURNER           SALESMAN        2250  2250
    JAMES            CLERK           1425  1425
  CLARK              MANAGER         3675  5625
    MILLER           CLERK           1950  1950

14 rows selected.

every manager has a sum of all sals from his own sallary + all people who are
working for him.
I think it is a very usefull query for example to determine the cost of a
project and all its sub_projects and sub_sub_projects in a company.

Darvishzadeh

          Followup:

ops$tkyte@ORA920> select rpad('*',2*level,'*') || ename ename, job, sal,
  2         (select sum(sal)
  3                from emp e2
  4                   start with e2.empno = emp.empno
  5                   connect by prior empno = mgr) my_question
  6    from emp
  7   start with mgr is null
  8   connect by prior empno = mgr;

ENAME                JOB              SAL MY_QUESTION
-------------------- --------- ---------- -----------
**KING               PRESIDENT       5000       29025
****JONES            MANAGER         2975       10875
******SCOTT          ANALYST         3000        4100
********ADAMS        CLERK           1100        1100
******FORD           ANALYST         3000        3800
********SMITH        CLERK            800         800
****BLAKE            MANAGER         2850        9400
******ALLEN          SALESMAN        1600        1600
******WARD           SALESMAN        1250        1250
******MARTIN         SALESMAN        1250        1250
******TURNER         SALESMAN        1500        1500
******JAMES          CLERK            950         950
****CLARK            MANAGER         2450        3750
******MILLER         CLERK           1300        1300

14 rows selected.

      Rating: 5
Hats off to you
      Tom!!!! I salute you!  July 11, 2003
Reviewer:
      A reader

      Rating:
      5
***Excelent***  July 14, 2003
Reviewer:
      Shahrouz DARVISHZADEH  from Austria
I hope only you know how important your work here is. Thank you very much.

Shahrouz

July 11, 2003 John -- Thanks for the question regarding "sql
      loader -- ", version 8.0.5.0.0

      You Asked
I need to add a large amount of records into a table the primary
key consists of four columns.  In the data file one column is
only listed once(for each distinct value) -- because it is the
same for multiple records.
The H and D are not part of the data.  H signifies a new product
number for all of the data below it (D) is the account number
for that particular product number.  Each new H record siqnifies
a new product number and the data below it is the account number
that appliess to that product number.

H000039166
D001221122
D001212121
H002323112
D012457888
H012578454
D778787878
D124577555
H7845452454
D1235874852

  I need to know how to make sql loader enter the product number
multiple times for all of the applicable account numbers.  The
data is over 8 million rows so putting the correct product
number with the account number is not an option.

I need to know how to reuse each product number for the
applicable account numbers and how to switch to a new product
number when it arises?

Thanks

      and we said...
Ok, if we can enforce 2 things we can do this.

1) there is a finite number of D records for each H and you have
knowledge of the finite number.

2) we can make the input file be fixed width -- with your data
above I would ask for a fixed width file of 11 bytes / line.  I
can make it so easily on unix without rewriting the entire file.

(note: if you want to use triggers we can do that, it'll work as
well.  It will force the use of the conventional path loader, no
direct path.  It will be slower then without triggers.  let me
know and I'll show you the trigger way as well)....

Ok, so lets say the finite number of D records for each H record
is 3 (it can be higher, you just have to "dup" entries in the
control file example below).  You want to load this into a table
T with columns id1 and id2.  You start with a control file:

LOAD DATA
INFILE test.dat
REPLACE
CONTINUEIF NEXT(1) = 'D'
INTO TABLE T
WHEN id2 <> BLANKS
(
 id1 position(1:10),
 id2 position(11:20)
)
INTO TABLE T
WHEN id2 <> BLANKS
(
 id1 position(1:10),
 id2 position(21:30)
)
INTO TABLE T
WHEN id2 <> BLANKS
(
 id1 position(1:10),
 id2 position(31:40)
)

That assembles one big logical record from many physical
records.  It joins all of the D records with the prior H record.
 Since we sometimes have 1, 2 or 3 records to load -- we use a
WHEN clause to get just the rows we want to load...

Now, I have my data in a file called test.input (not test.dat as
I refer to in my ctl file).  I will use awk or some other
utility to "fix width" my data on the fly.  I start by:

$ mknod test.dat p

That makes a named pipe test.dat.  I then run:

$ awk '{printf( "%-11.11s\n", $1 )}' < test.input > test.dat &

That will read each line of test.input and write it out in a
left justified field of AT least 11 bytes and at most 11 bytes
followed by a newline.  It writes this to the named pipe, it
does this in the background.

Now, I just:

$ sqlldr user/password test.ctl

and away it goes (put direct=true if you want to use the direct
path loader).
Review & Followup

      Rating: 5
direct
      insert  July 11, 2003
Reviewer:
      A reader
Hi

What is the difference between

INSERT /*+ APPEND */

and

SQLLDR direct path load?

I think they are both direct inserts but why trggers fires with insert append
and not with sqlldr direct path load?

          Followup:

direct path load via sqlldr reads data and writes to datafiles -- pretty much
bypassing the sql engine itself entirely.

insert append, on the other hand, it running in the sql engine itself so it does
fire the triggers.

July 11, 2003 Ashish -- Thanks for the question regarding
      "Table design in connected/disconnected architecture", version 8.1.7

      You Asked
Hi Tom,
I am designing a CRM application which works in connected as well disconnected
mode
i.e at the Head Office(centralized location), all  the users are connected
through
intranet but the remote branches of this Head Office cannot access the
application over web
due to poor connectivity and security concerns. These Branches contact several
customers daily and at the End of Day, download a data file consisting of daily
transactions over
to Head office for reconciliation. These data files are uploaded in to
corresponding tables at Head Office.
A customer can be contacted by both the branch and head office at the same time
I am confused in deciding the primary key and data type of a transaction table
The important data fields of this particular table are as follows

TABLE NAME CONTACT_DETAILS

CONTACT_DETAILS_ID PRIMARY KEY.....?
CUSTOMER_ID NUMBER(8) NOT NULL         --FK(CUSTOMER_M),
RESPONSE_CODE VARCHAR2(8)  NOT NULL       --FK(RESPONSE_CODES_M),
TELECALLER    VARCHAR2(8) NOT NULL    --FK(TELECALLER_M.),
BRANCHID      NUMBER(8)      NOT NULL     --FK(BRANCH_M. UNIQUE THROUGHOUT THE
SYSTEM AND CAN ONLY BE DEFINED AT HEAD OFFICE),
CONTACT_DATE  DATE NOT NULL (SYSDATE),
REMARKS VARCHAR2(255)

The reason that i cannot select sequence as the primary key is due to the
following reason
Let's say at Head Office Sequence number  10 is generated for customer A,
but at one of the branch sequence no. 10 is generated for customer B. Now at the
End of Day
record generated at Branch won't be uploaded in the main system due to primary
key violation.

There are two workaround which i've thought of but am not able to decide, which
one is better.
1) Make contact_details_id as a sequence generated value but
primary key should be a composite primary key made of following fields
CONTACT_DETAILS_ID + TELECALLER + BRANCHID + CONTACT_DATE(including time).

2) Make contact_details_id as application generated as concatenation of
Sequence || TELECALLER || BRANCHID || SYSDATE. This way no composite primary key
would be required
but primary key would be too long.

Which of these is a better solution or is there any other better alternative.

Regards
Ashish

      and we said...
sequences work great.  Say you are going to have 100 branch offices.  To be
safe, we'll say 500.

then just create the sequences as:

create sequence your_seq start with 1 increment by 500;

at the next install:

create sequence your_seq start with 2 increment by 500;

and so on -- each site will generate its own set of sequence numbers.  That is
the solution used by hundreds of other people.  works great.
Review &
      Followup

      Rating: 4
What if number
      of agencies is not decided at the installation time  January
      20, 2003
Reviewer:  Samson Dsouza  from OSAKA, Japan

Hi Tom,
We have a similar application but in SQL Server. In SQL Server there is a
datatype known as GUID(Globally unique identifier), which is guarnteed to be
unique throught world, time and space. But due to security concerns, the
organization, for which, we developed this application wants it to be converted
to Oracle.
The solution that you have mentioned is great but in our case the number of such
disconnected branches is not known.
Earlier the application scope was limited to Taiwan, but now the scope has
increased and it covers entire Asia(tomorrow , it could be the entire world). In
such a scenario, the number of disconnected branches won't remain fixed and
hence, it would be difficult to predetermine the upper limit.
Do we have anything equivalent to GUID in SQL Server?
If not then will making sequence and BranchID a composite primary key be a good
solution?

PS: Thanks for your excellent inputs

          Followup:

ops$tkyte@ORA920> select sys_guid() from dual;

SYS_GUID()
--------------------------------
B4B3DB664EF06F09E030028A5B5C6DCD

same sort of promises as far as uniqueness goes.

and even if you don't know the max -- take a hypothetical number.  Say
1,000,000.  Will you have more then 1,000,000 sites?

if not, go for it -- use the sequence with 1,000,000.  faster -- more
concurrent, easier, less storage, yadda yadda yadda

if you generate 1000 numbers a second at any site -- forever, it'll take:

ops$tkyte@ORA920> select rpad('9',27,'9')/1000/1000000/60/60/24/366 from dual;

RPAD('9',27,'9')/1000/1000000/60/60/24/366
------------------------------------------
                                3.1623E+10

YEARS to exhaust the sequences at that single site!

      Rating: 5
Great
      Explaination  January 21, 2003
Reviewer:
      Samson

      Rating: 5
Nothing wrong
      with the suggestions so far  January 21, 2003

Reviewer:  Andy Gilfrin  from Dover Kent UK

Where I used to work they had a replication system which basically added a site
ID to an unique value to get round this.

Every table had a field unique_identifier which was controlled by a sequence
table. When the value came back the site code was added to the front of the
unique_identifier and therefore it was unique.

A similar principle in your case would be have an additional field called
SITE_ID and make the primary key

SITE_ID+SEQUENCY_NUMBER

      Rating: 5
SQL Server GUID
      vs Oracle GUID  January 21, 2003
Reviewer:  Ian
      wallace  from UK
Hi Tom,
Is there any chance of collision between GUID generated by Oracle and that by
SQL Server?

          Followup:

sure, they've been known to collide with us in the past (he he, thats a joke).

Of course they could collide I guess -- I have no idea what/how they do it, what
size theirs is, what format, etc.

      Rating: 5
SQL Server GUID
      Vs Oracle GUID  January 22, 2003
Reviewer:
      Ashish  from India
In Oracle 8.1.7
select length(sys_guid()) from dual;

LENGTH(SYS_GUID())
------------------
                32

Elapsed: 00:00:00.00

In SQL Server2K

select len(newid())

36

Hence I think there shouldn't be any collision in GUIDs generated by both Oracle
and SQL Server.

      Rating: 5
sequence versus
      sys_guid  July 10, 2003
Reviewer:
      A reader
" faster -- more
concurrent, easier, less storage, yadda yadda yadda
".

I understand the "storage" part - could you explain why
sequences would be faster and more concurrent than
sys_guid() as a primary key generation method. how much
is the penalty for using sys_guid()s instead of sequences?

thanx!

          Followup:

don't know what I was thinking there -- it is not "more concurrent".  good eye,
thanks.

      Rating: 4
The difference
      between an Oracle GUID and SQL Server GUID...  July 11, 2003

Reviewer:  MDF  from Minneapolis, MN
A read above mentioned that Oracle GUID's are 32 characters long, and the SQL
Server GUID's are 36.  Just off the top of my head, I believe the difference
lies in formatting.
The SQL Server GUID's contain 4 "formatting" dashes which
separate the value into sections, the Oracle GUID is just
a string of characters...

          Followup:

16 raw BYTES long -- 32 hex characters.

Oracle's guid is just a 16 byte RAW buffer, that expands to 32 bytes when you
"look at it" in hex.

July 11, 2003 MARIGELSON -- Thanks for the question regarding
      "too wide for sqlplus", version

      You Asked
HOW TO VISUALIZE ALL THE COLUMNS AFTER A SELECT * FROM,
WHEN THESE COLUMNS EXCEED THE WIDTH OF THE SCREEN IN THE
SQLPLUS?

      and we said...
If you cannot use the "COLUMN cname FORMAT aNN" command to make
the columns fit on the screen, perhaps a small plsql routine can
be of some use.  It might work like this:

scott@ORACLE> create or replace procedure dump_table( p_tname in
varchar2 )
  2  is
  3      l_theCursor     integer default dbms_sql.open_cursor;
  4      l_columnValue   varchar2(4000);
  5      l_status    integer;
  6      l_query     varchar2(1000) default 'select * from ' ||
p_tname;
  7      l_descTbl   dbms_sql.desc_tab;
  8      l_colCnt    number;
  9  begin
 10      dbms_sql.parse(  l_theCursor,  l_query, dbms_sql.native
);
 11      dbms_sql.describe_columns( l_theCursor, l_colCnt,
l_descTbl );
 12
 13      for i in 1 .. l_colCnt loop
 14          dbms_sql.define_column( l_theCursor, i,
l_columnValue, 4000 );
 15      end loop;
 16
 17      l_status := dbms_sql.execute(l_theCursor);
 18
 19      while ( dbms_sql.fetch_rows(l_theCursor) > 0 ) loop
 20          for i in 1 .. l_colCnt loop
 21          dbms_sql.column_value( l_theCursor, i,
l_columnValue );
 22          dbms_output.put_line(
rpad(l_descTbl(i).col_name,30) || ': ' || l_columnValue );
 23          end loop;
 24          dbms_output.put_line( '-----------------' );
 25      end loop;
 26  end;
 27  /

Procedure created.

Now I can do the following:

scott@ORACLE>
scott@ORACLE> set serveroutput on size 1000000
scott@ORACLE>
scott@ORACLE> exec dump_table( 'emp' );
EMPNO                         : 7369
ENAME                         : SMITH
MGR                           : 7902
HIREDATE                      : 17-DEC-80
SAL                           : 800
DEPTNO                        : 20
-----------------
EMPNO                         : 7499
ENAME                         : ALLEN
MGR                           : 7698
HIREDATE                      : 20-FEB-81
SAL                           : 1600
DEPTNO                        : 30
-----------------

It'll print rows down the page instead of across.  This works
with Oracle8.0 and up (the dbms_sql.describe_columns routine did
not exist prior to that - if you are 7.x, and wish to persue
this, let me know and i'll show you how).

Review &
      Followup

      Rating: 5
Nirav
      Patel  October 23, 2001
Reviewer:  Nirav
      Patel  from US
I am trying to create database object(view\synonym) with name size more than 30
char.

View I can not create.
But I am able to create synonym.
And when I am trying to access data through synonym then I am getting error,
"identifier is too long".

I need to do this b'z I am trying to migrate on application fom SQL Server to
Oracle. All the views created in SQL is having name size more than 30 and ORACLE
not allowing that.

If I can't create object with more than 30 char size thne I have change the
application code. Which I don't want.

How can I create view or synonym with name more than 30 char size.

thanks,
Nirav

      Rating: 5
Nirav
      Patel  October 23, 2001
Reviewer:  Nirav
      Patel  from US
I am trying to create database object(view\synonym) with name size more than 30
char.

View I can not create.
But I am able to create synonym.
And when I am trying to access data through synonym then I am getting error,
"identifier is too long".

I need to do this b'z I am trying to migrate on application fom SQL Server to
Oracle. All the views created in SQL is having name size more than 30 and ORACLE
not allowing that.

If I can't create object with more than 30 char size thne I have change the
application code. Which I don't want.

How can I create view or synonym with name more than 30 char size.

====================================
SQL> create synonym CON_B2550_PREV_Q_19_21_TO_SCRAP_VW for t_sys;

Synonym created.

** When trying to provide privilege on this synonym to myself then I don't
privileges.

---------------------------------------------------------------------------------
-----------------------------------------------------------

SQL> select * from CON_B2550_PREV_Q_19_21_TO_SCRAP_VW;

select * from CON_B2550_PREV_Q_19_21_TO_SCRAP_VW
*
ERROR at line 1:
ORA-00972: identifier is too long

---------------------------------------------------------------------------------
----------------------------------------------------------

 * Stored as this name in the data dictionary.

SQL> select * from user_synonyms where table_owner='PATEL'

SYNONYM_NAME                                      TABLE_OWNER
TABLE_NAME                     DB_LINK
------------------------------ ------------------------------
------------------------------ --------
/f5033d37_CON_B2550_PREV_Q_19_

=====================================
thanks,
Nirav

          Followup:

30 is our limit, there is no way around that.  If you want to be database
"independent" you cannot count on anything more then 18 characters per
identifier as that is the ANSI standard.

      Rating:
      5
Reader  October 25, 2001
Reviewer:
      Reader
Tom,

How would I pass an argument like
v$session where type = 'USER' to
dump_table procedure

SQL>exec dump_table('V$SESSION where rownum < 20')
works.

Thanks

          Followup:

exec dump_table( 'v$session where type = ''USER'' ' );

      Rating: 4
closing the
      cursor  March 06, 2002
Reviewer:  Ravi Kumar
       from Chicago, IL
In your procedure there is no close_cursor call. Don't we need to close that
cursor? or it will automatically gets closed at the end of the execution? Just
wanted to know.

Thanks,
Ravi

          Followup:

thanks -- thats missing, this demo leaks a cursor.  it should have a
dbms_sql.close_cursor in there.

      Rating:
      4
  March 07, 2002
Reviewer:  Wijnand
      Engelkes
Yes, I would like to know how this can be done in Oracle 7.x.
We are on :
Oracle7 Server Release 7.3.4.4.0
PL/SQL Release 2.3.4.4.0

          Followup:

You cannot do this on 7.3, the describe columns call wasn't there.  You need 8.0
and up for this to work properly.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2960649659858
for a single "table dumper" for 7.3 -- doesn't work with any query -- just a
table.

      Rating: 5
Regarding the
      30 character limit  July 11, 2003
Reviewer:
      Justin Stewart  from PA
Tom,

I have a SQL Server 2k database hooked up via GC and am moving it's data to
Oracle via direct path inserts.
Now SQL Server has this concept of a "default database" in the ODBC DSN. So you
choose one of many databases as the default. And let's say the name of the SQL
Server db is LONGNAME20030710, and the table_schema is DBO, and the table_name
is LONG_TABLE_NAME. And lets say that LONGNAME20030710 is *not* the default.

****Now, when you try to select from this table.

select count(1) from LONGNAME20030710.DBO.LONG_TABLE_NAME@dblink;

****you get:

ERROR at line 1:
ORA-00933: SQL command not properly ended

****So, I decided to qualify the name with quotes.

select count(1) from "LONGNAME20030710.DBO.LONG_TABLE_NAME"@dblink;

****And I get:

ERROR at line 1:
ORA-00972: identifier is too long

****I qualified a database.owner.table_name for a total ****length of less than
30 bytes, and it worked.
****So my question is, are we hosed in this respect, or am ****I missing
something I could do to get around the error.

****Oh, and one last bizarre thing.

****I executed this command:

select count(1) from LONGNAME20030710."DBO.LONG_TABLE_NAME"@dblink;

****and it returned:

  COUNT(*)
----------
        15

****The table has 115 rows in it??

****And I also tried:

select count(1) from "LONGNAME20030710.DBO".LONG_TABLE_NAME@dblink;

****which also returned:

  COUNT(*)
----------
        15

****Any idea what the 15 would be?

Thanks much

          Followup:

if you just select * from it what happens.

does sqlserver have the equivalent of "sql_trace=true" so you can see whats
going on?

if you turn on the odbc trace, what sql is being sent over, anything useful in
there?

sorry -- but as you might imagine, i don't have any sqlserver databases to play
with, I don't have any computers that it will even install on.

      Rating:
      3
  July 11, 2003
Reviewer:
      A reader
The bottom line is that Oracle has the 30 character limit (I read your statement
that the ASCII standard is 18, so I know Oracle's length is nice), so what I
need to do is simply create a couple of database links, each pointing a
different init<agent>.ora file in ORACLE_HOME\hs\admin, and each of those will
utilize a different DSN, which inturn will have different default databases
assigned.

Thanks for helping me think through it.

July 11, 2003 Taufeeq -- Thanks for the question regarding
      "BLOB", version version 8.1.5

      You Asked
How can I add binary as well as ASCII file to BLOB field? Please
give me some lead.
I am developing a application for oil & gas industry and I need
to store binary as well as ascii file of data to BLOB field. For
this I have created a form in which I need to incorporate this
functionality in push button. The functionality is this.
When user press this button system would ask for 'LIS' or 'LAS'
file. (LIS is binary file and LAS is ASCII file)
Now system should insert selected file in the following table
whose structure is given below.

desc em_log_crv_vec;
Column Name                    Null?    Type
------------------------------ -------- ----
LOG_VID                        NOT NULL NUMBER
DATA                                    LONG RAW

Thanks for reply

      and we said...
Yes you can sort ascii data in a blob field.  what that implies
is that the data will be stored RAW and untranslated (eg: if you
have a client with a different characterset -- the bits and
bytes of the blob are sent to them untranslated, if the bits and
bytes had been stored in a clob -- they would have undergone
character set translation.)

Now, the above describe does not depict a BLOB field but rather
a LONG RAW.  I would strongly encourage you to use a BLOB field,
not LONG RAW.  LONG RAW is only provided for backwards
compatibility with Oracle7.  It is a severly "underpowered"
datatype as opposed to a BLOB.  You will be much happier using a
BLOB type.
Review & Followup

      Rating: 5
LONG RAW to
      BLOB  July 11, 2003
Reviewer:  Tony
       from Canada
Tom,
    Thanks a lot for all your help,
Can we use to_lob to convert LONG RAW to BLOB in a table, if not please let me
know how it can be done.

Regards

          Followup:

yes you can -- in 9ir2 you can alter the table as well.

July 11, 2003 Ganesh -- Thanks for the question regarding
      "difference between sql with clause and inline", version 9i

      You Asked
Hi Tom,
   This morning I saw an example on otn describing the sql with clause in 9i. I
also saw an example, unfortunately that page seems to have been removed so
cannot give you a reference.
 Here is the question though. How does a select statement with a inline view
differ from a select using a sql with clause?

Thanks,
Ganesh.

      and we said...
The with clause, aka subquery factoring, allows you to tell us "hey, reuse this
result over and over in the query".  We can factor out a subquery that is used
more then once and reuse it -- resulting in a perhaps "better" plan.

It can also make the query overall "more readable".

Here are more or less equivalent queries with their plans.  You can see the
plans are different -- due to the subquery factoring.  In this case, the named
subquery "free space by tablespace" is used twice

sys@ORA920> set autotrace traceonly explain
sys@ORA920> set echo on
sys@ORA920> @demo011
sys@ORA920>
sys@ORA920> set echo on
sys@ORA920> with
  2  free_space_by_tablespace as
  3       ( select sum(bytes)/1024 Kbytes_free,
  4                            max(bytes)/1024 largest,
  5                            tablespace_name
  6             from  sys.dba_free_space
  7             group by tablespace_name ),
  8  space_by_permanent_tablespace as
  9       ( select sum(bytes)/1024 Kbytes_alloc,
 10                            sum(maxbytes)/1024 Kbytes_max,
 11                            tablespace_name
 12             from sys.dba_data_files
 13             group by tablespace_name ),
 14  space_by_temporary_tablespace as
 15       ( select sum(bytes)/1024 Kbytes_alloc,
 16                            sum(maxbytes)/1024 Kbytes_max,
 17                            tablespace_name
 18             from sys.dba_temp_files
 19             group by tablespace_name )
 20  /* Now, finally to the query itself */
 21  select b.tablespace_name name,
 22             kbytes_alloc kbytes,
 23             kbytes_alloc-nvl(kbytes_free,0) used,
 24             nvl(kbytes_free,0) free,
 25             ((kbytes_alloc-nvl(kbytes_free,0))/
 26                                                    kbytes_alloc)*100
pct_used,
 27             nvl(largest,0) largest,
 28             nvl(kbytes_max,kbytes_alloc) Max_Size,
 29             decode( kbytes_max, 0, 0, (kbytes_alloc/kbytes_max)*100)
pct_max_used
 30    from free_space_by_tablespace a
 31             RIGHT OUTER JOIN
 32                space_by_permanent_tablespace b on a.tablespace_name =
b.tablespace_name
 33  union all
 34  select b.tablespace_name,
 35             kbytes_alloc kbytes,
 36             kbytes_alloc-nvl(kbytes_free,0) used,
 37             nvl(kbytes_free,0) free,
 38             ((kbytes_alloc-nvl(kbytes_free,0))/
 39                                                    kbytes_alloc)*100
pct_used,
 40             nvl(largest,0) largest,
 41             nvl(kbytes_max,kbytes_alloc) Max_Size,
 42             decode( kbytes_max, 0, 0, (kbytes_alloc/kbytes_max)*100)
pct_max_used
 43    from free_space_by_tablespace a
 44             RIGHT OUTER JOIN
 45                space_by_temporary_tablespace b on a.tablespace_name =
b.tablespace_name
 46  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=70 Card=10 Bytes=860)
   1    2     RECURSIVE EXECUTION OF 'SYS_LE_2_0'
   2    0   TEMP TABLE TRANSFORMATION
   3    2     UNION-ALL
   4    3       HASH JOIN (OUTER) (Cost=42 Card=2 Bytes=172)
   5    4         VIEW (Cost=39 Card=2 Bytes=86)
   6    5           SORT (GROUP BY) (Cost=39 Card=2 Bytes=86)
   7    6             VIEW OF 'DBA_DATA_FILES' (Cost=37 Card=2 Bytes=86)
   8    7               UNION-ALL
   9    8                 NESTED LOOPS (Cost=13 Card=1 Bytes=405)
  10    9                   NESTED LOOPS (Cost=12 Card=1 Bytes=362)
  11   10                     FIXED TABLE (FULL) OF 'X$KCCFN' (Cost=11 Card=1
Bytes=297)
  12   10                     TABLE ACCESS (BY INDEX ROWID) OF 'FILE$' (Cost=1
Card=1 Bytes=65)
  13   12                       INDEX (UNIQUE SCAN) OF 'I_FILE1' (UNIQUE)
  14    9                   TABLE ACCESS (CLUSTER) OF 'TS$' (Cost=1 Card=1
Bytes=43)
  15   14                     INDEX (UNIQUE SCAN) OF 'I_TS#' (NON-UNIQUE)
  16    8                 NESTED LOOPS (Cost=24 Card=1 Bytes=431)
  17   16                   NESTED LOOPS (Cost=23 Card=1 Bytes=388)
  18   17                     NESTED LOOPS (Cost=12 Card=1 Bytes=323)
  19   18                       FIXED TABLE (FULL) OF 'X$KCCFN' (Cost=11 Card=1
Bytes=297)
  20   18                       TABLE ACCESS (BY INDEX ROWID) OF 'FILE$' (Cost=1
Card=408 Bytes=10608)
  21   20                         INDEX (UNIQUE SCAN) OF 'I_FILE1' (UNIQUE)
  22   17                     FIXED TABLE (FIXED INDEX) OF 'X$KTFBHC (ind:1)'
  23   16                   TABLE ACCESS (CLUSTER) OF 'TS$' (Cost=1 Card=8168
Bytes=351224)
  24   23                     INDEX (UNIQUE SCAN) OF 'I_TS#' (NON-UNIQUE)
  25    4         VIEW (Cost=2 Card=2 Bytes=86)
  26   25           TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D660A_26900' (Cost=2
Card=2 Bytes=60)
  27    3       HASH JOIN (OUTER) (Cost=28 Card=8 Bytes=688)
  28   27         VIEW (Cost=25 Card=4 Bytes=172)
  29   28           SORT (GROUP BY) (Cost=25 Card=4 Bytes=1568)
  30   29             NESTED LOOPS (Cost=23 Card=4 Bytes=1568)
  31   30               NESTED LOOPS (Cost=22 Card=1 Bytes=349)
  32   31                 FIXED TABLE (FULL) OF 'X$KCCFN' (Cost=11 Card=1
Bytes=284)
  33   31                 FIXED TABLE (FIXED INDEX) OF 'X$KTFTHC (ind:1)'
  34   30               TABLE ACCESS (CLUSTER) OF 'TS$' (Cost=1 Card=82
Bytes=3526)
  35   34                 INDEX (UNIQUE SCAN) OF 'I_TS#' (NON-UNIQUE)
  36   27         VIEW (Cost=2 Card=2 Bytes=86)
  37   36           TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D660A_26900' (Cost=2
Card=2 Bytes=60)

sys@ORA920>
sys@ORA920> select (select decode(extent_management,'LOCAL','*','') from
dba_tablespaces where tablespace_name =
  2  b.tablespace_name) || nvl(b.tablespace_name,
  3                           nvl(a.tablespace_name,'UNKOWN')) name,
  4             kbytes_alloc kbytes,
  5             kbytes_alloc-nvl(kbytes_free,0) used,
  6             nvl(kbytes_free,0) free,
  7             ((kbytes_alloc-nvl(kbytes_free,0))/
  8                                                    kbytes_alloc)*100
pct_used,
  9             nvl(largest,0) largest,
 10             nvl(kbytes_max,kbytes_alloc) Max_Size,
 11             decode( kbytes_max, 0, 0, (kbytes_alloc/kbytes_max)*100)
pct_max_used
 12  from ( select sum(bytes)/1024 Kbytes_free,
 13                            max(bytes)/1024 largest,
 14                            tablespace_name
 15             from  sys.dba_free_space
 16             group by tablespace_name ) a,
 17       ( select sum(bytes)/1024 Kbytes_alloc,
 18                            sum(maxbytes)/1024 Kbytes_max,
 19                            tablespace_name
 20             from sys.dba_data_files
 21             group by tablespace_name
 22             union all
 23        select sum(bytes)/1024 Kbytes_alloc,
 24                            sum(maxbytes)/1024 Kbytes_max,
 25                            tablespace_name
 26             from sys.dba_temp_files
 27             group by tablespace_name )b
 28  where a.tablespace_name (+) = b.tablespace_name
 29  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=100 Card=6 Bytes=516)
   1    0   HASH JOIN (OUTER) (Cost=100 Card=6 Bytes=516)
   2    1     VIEW (Cost=64 Card=6 Bytes=258)
   3    2       UNION-ALL
   4    3         SORT (GROUP BY) (Cost=39 Card=2 Bytes=86)
   5    4           VIEW OF 'DBA_DATA_FILES' (Cost=37 Card=2 Bytes=86)
   6    5             UNION-ALL
   7    6               NESTED LOOPS (Cost=13 Card=1 Bytes=405)
   8    7                 NESTED LOOPS (Cost=12 Card=1 Bytes=362)
   9    8                   FIXED TABLE (FULL) OF 'X$KCCFN' (Cost=11 Card=1
Bytes=297)
  10    8                   TABLE ACCESS (BY INDEX ROWID) OF 'FILE$' (Cost=1
Card=1 Bytes=65)
  11   10                     INDEX (UNIQUE SCAN) OF 'I_FILE1' (UNIQUE)
  12    7                 TABLE ACCESS (CLUSTER) OF 'TS$' (Cost=1 Card=1
Bytes=43)
  13   12                   INDEX (UNIQUE SCAN) OF 'I_TS#' (NON-UNIQUE)
  14    6               NESTED LOOPS (Cost=24 Card=1 Bytes=431)
  15   14                 NESTED LOOPS (Cost=23 Card=1 Bytes=388)
  16   15                   NESTED LOOPS (Cost=12 Card=1 Bytes=323)
  17   16                     FIXED TABLE (FULL) OF 'X$KCCFN' (Cost=11 Card=1
Bytes=297)
  18   16                     TABLE ACCESS (BY INDEX ROWID) OF 'FILE$' (Cost=1
Card=408 Bytes=10608)
  19   18                       INDEX (UNIQUE SCAN) OF 'I_FILE1' (UNIQUE)
  20   15                   FIXED TABLE (FIXED INDEX) OF 'X$KTFBHC (ind:1)'
  21   14                 TABLE ACCESS (CLUSTER) OF 'TS$' (Cost=1 Card=8168
Bytes=351224)
  22   21                   INDEX (UNIQUE SCAN) OF 'I_TS#' (NON-UNIQUE)
  23    3         SORT (GROUP BY) (Cost=25 Card=4 Bytes=1568)
  24   23           NESTED LOOPS (Cost=23 Card=4 Bytes=1568)
  25   24             NESTED LOOPS (Cost=22 Card=1 Bytes=349)
  26   25               FIXED TABLE (FULL) OF 'X$KCCFN' (Cost=11 Card=1
Bytes=284)
  27   25               FIXED TABLE (FIXED INDEX) OF 'X$KTFTHC (ind:1)'
  28   24             TABLE ACCESS (CLUSTER) OF 'TS$' (Cost=1 Card=82
Bytes=3526)
  29   28               INDEX (UNIQUE SCAN) OF 'I_TS#' (NON-UNIQUE)
  30    1     VIEW (Cost=36 Card=2 Bytes=86)
  31   30       SORT (GROUP BY) (Cost=36 Card=2 Bytes=60)
  32   31         VIEW OF 'DBA_FREE_SPACE' (Cost=34 Card=2 Bytes=60)
  33   32           UNION-ALL
  34   33             NESTED LOOPS (Cost=12 Card=1 Bytes=121)
  35   34               NESTED LOOPS (Cost=11 Card=1 Bytes=65)
  36   35                 TABLE ACCESS (FULL) OF 'FET$' (Cost=11 Card=8168
Bytes=318552)
  37   35                 INDEX (UNIQUE SCAN) OF 'I_FILE2' (UNIQUE)
  38   34               TABLE ACCESS (CLUSTER) OF 'TS$' (Cost=1 Card=82
Bytes=4592)
  39   33             NESTED LOOPS (Cost=22 Card=1 Bytes=147)
  40   39               NESTED LOOPS (Cost=22 Card=1 Bytes=121)
  41   40                 TABLE ACCESS (FULL) OF 'TS$' (Cost=11 Card=1 Bytes=82)
  42   40                 FIXED TABLE (FIXED INDEX) OF 'X$KTFBFE (ind:1)'
  43   39               INDEX (UNIQUE SCAN) OF 'I_FILE2' (UNIQUE)
Review &
      Followup

      Rating: 3
Should one
      revisit existing code that uses inline  June 28, 2002

Reviewer:  Ganesh  from San Jose, CA USA
Thanks for the prompt response.
The plans being different for the two queries, do you think one should replace
all of the existing code using inline with sql-with or tune it on a case by case
basis .
Is it even worth it?

Thanks,
Ganesh.

          Followup:

I tune where tuning is necessary.  It is not worthwhile (in my opinion) to
revisit every line of code everytime a new feature comes out.

      Rating: 3
Differences
      between views, inline views and subquery factoring  June 28,
      2002
Reviewer:  andrew  from ca, usa
Tom
As you've shown, inline views and "subquery factoring" are different. Please
could you point out differences between inline views, views and "subquery
factoring". Automatic query re-writes is one area of uncertainty to me.

          Followup:

There is very little difference between an inline view and a view view.  A view
view is simply a stored query. An inline view just has you put the query right
there.  I would say an inline view and a view view are the same for all intents
and purposes.

This page describes the benefit of a WITH clause (subquery factoring).  It is a
way to tell the optimizer "use this result twice, it is the same"

      Rating: 4
Can I use this
      in a ctas?  January 08, 2003
Reviewer:
      A reader
Can the With clause be used in a ctas?

          Followup:

ops$tkyte@ORA920.US.ORACLE.COM> drop table t;

Table dropped.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create table t as
  2  with
  3  free_space_by_tablespace as
  4       ( select sum(bytes)/1024 Kbytes_free,
  5                            max(bytes)/1024 largest,
  6                            tablespace_name
  7             from  sys.dba_free_space
  8             group by tablespace_name ),
  9  space_by_permanent_tablespace as
 10       ( select sum(bytes)/1024 Kbytes_alloc,
 11                            sum(maxbytes)/1024 Kbytes_max,
 12                            tablespace_name
 13             from sys.dba_data_files
 14             group by tablespace_name ),
 15  space_by_temporary_tablespace as
 16       ( select sum(bytes)/1024 Kbytes_alloc,
 17                            sum(maxbytes)/1024 Kbytes_max,
 18                            tablespace_name
 19             from sys.dba_temp_files
 20             group by tablespace_name )
 21  /* Now, finally to the query itself */
 22  select b.tablespace_name name,
 23             kbytes_alloc kbytes,
 24             kbytes_alloc-nvl(kbytes_free,0) used,
 25             nvl(kbytes_free,0) free,
 26             ((kbytes_alloc-nvl(kbytes_free,0))/
 27                                                    kbytes_alloc)*100
pct_used,
 28             nvl(largest,0) largest,
 29             nvl(kbytes_max,kbytes_alloc) Max_Size,
 30             decode( kbytes_max, 0, 0, (kbytes_alloc/kbytes_max)*100)
pct_max_used
 31    from free_space_by_tablespace a
 32             RIGHT OUTER JOIN
 33                space_by_permanent_tablespace b on a.tablespace_name =
b.tablespace_name
 34  union all
 35  select b.tablespace_name,
 36             kbytes_alloc kbytes,
 37             kbytes_alloc-nvl(kbytes_free,0) used,
 38             nvl(kbytes_free,0) free,
 39             ((kbytes_alloc-nvl(kbytes_free,0))/
 40                                                    kbytes_alloc)*100
pct_used,
 41             nvl(largest,0) largest,
 42             nvl(kbytes_max,kbytes_alloc) Max_Size,
 43             decode( kbytes_max, 0, 0, (kbytes_alloc/kbytes_max)*100)
pct_max_used
 44    from free_space_by_tablespace a
 45             RIGHT OUTER JOIN
 46                space_by_temporary_tablespace b on a.tablespace_name =
b.tablespace_name
 47  /

Table created.

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

      Rating: 3
immature oracle
      designers  January 08, 2003
Reviewer:
      harakiri
First, the plan in 9i is no longer a simple hierarchical query. The correct
output is still produced by utlxpls, of course. All homegrown plan queries are
rendered invalid with new temporary table transformation.

The plan contains:

TABLE ACCESS FULL      SYS_TEMP_0FD9D6600_338EED1D

in 2 places. A temporary table build under the node:

RECURSIVE EXECUTION         SYS_LE_2_0

There is no match in the names whatsoever! Oracle probably don't have a clue
that execution plan in the new design is essentially a Directed Acyclic Graph
(DAG). DAG still can be represented as adjacency list with parent_id referring
to the parent row without need to invent kludgy names and link by the other
columns. I must be naive to expect a simple design from oracle.

Second, what is new manageabilty stuff with all those advisory views? Am I
supposed to learn more internal details? I was assuming that oracle would remove
all the unnecessary levels of implementation details, straighten up the code,
and automatically size up all those internal caches and areas. Instead, I have
to read more documentation! Isn't oracle just throwing more features and call it
manageability? Typical oracle: simplicity, via adding more complexity!

          Followup:

what the heck are you talking about and how does this at all relate to subquery
factoring and the with clause???

I have no clue what you are talking about.  the plan table is still the plan
table is still the plan table.

You have no idea the complexity of the database apparently either.

I don't get it with the advisory comment either.  Lets see, you fire up OEM, OEM
tells you graphically and textually "hey, you know what, if you sized the buffer
cache to X, the shared pool to Y -- things would be better.  Would you like me
to do that for you?".  You press button and wah-lah, done.

Tell me, how did you learn about directed acyclic graphs (might big words).  I'm
sure you didn't invent them -- I'll bet you READ about them.  You know, in some
document somewhere?

It would really be frustrating don't you think if you sized the buffer cache to
M and the shared pool to N and came in the next day to find that the database
set it to X and Y without telling you?

Remember the line "Open the pod bay doors Hal, I'm sorry Dave, I'm afraid I
can't do that"

http://www.moviesounds.com/2001/imsorry.wav
not sure I want my database reconfiguring itself at 2pm during peak load all by
itself -- do you...

If you have a real question about "how do i", and want to ask it, feel free to
submit it some day.

      Rating: 3
plan
      diff  January 09, 2003
Reviewer:  mikito

Run utlxpls and compare the output with the output you printed in the reply
above. There is a whole subtree missing:

|   1 |   RECURSIVE EXECUTION                 | SYS_LE_2_0                   |
    |       |       |
|   0 |    INSERT STATEMENT                   |                              |
 83 |  2490 |   135 |
|   1 |     LOAD AS SELECT                    |                              |
    |       |       |
|   2 |      SORT GROUP BY                    |                              |
 83 |  2490 |   135 |
|   3 |       VIEW                            |                              |
 83 |  2490 |   133 |
|   4 |        UNION-ALL                      |                              |
    |       |       |
|   5 |         NESTED LOOPS                  |                              |
 82 |  9922 |    99 |
|   6 |          NESTED LOOPS                 |                              |
 82 |  7790 |    99 |
|*  7 |           TABLE ACCESS FULL           | TS$                          |
 82 |  4592 |    17 |
|*  8 |           TABLE ACCESS CLUSTER        | FET$                         |
  1 |    39 |     1 |
|*  9 |          INDEX UNIQUE SCAN            | I_FILE2                      |
  1 |    26 |       |
|  10 |         NESTED LOOPS                  |                              |
  1 |   147 |    34 |
|  11 |          NESTED LOOPS                 |                              |
  1 |   121 |    34 |
|* 12 |           TABLE ACCESS FULL           | TS$                          |
  1 |    82 |    17 |
|* 13 |           FIXED TABLE FIXED INDEX     | X$KTFBFE (ind:1)             |
  1 |    39 |       |
|* 14 |          INDEX UNIQUE SCAN            | I_FILE2                      |
  1 |    26 |       |

Now the technical question. What is this funny name SYS_LE_2_0. How does this
relate to 'SYS_TEMP_0FD9D660A_26900'?

          Followup:

Oh -- it's you.  The guy who wants the software to do all of our thinking for
us.  Now I understand the aggressive, somewhat beligerent and superior tone
better.

never mind.

(seems funny, you know what to do, you just want to complain about it.  oh
well.)

The sys_ names are of course just system generated names -- like you would see
for constraints that are not named.  there need not be any implied relation
between the two you see.  They are just temporary names for temporary objects --
their names are not meaningful.

      Rating: 5
diffreence
      between inline view and WHERE clause  January 09, 2003

Reviewer:  A reader
Hi

since this topic is about inline views difference with other clauses I have a
doubt

if I have a query like

select ename, dname
from emp, dept
where emp.deptno = dept.deptno
and b.deptno = 10
and emp.empno between 7500 and 7600

what would it differ from

select ename, dname
from (select * from emp where empno between 7500 and 7600) a,
     (select * from dept where deptno = 10) b

considering emp.empno, emp.deptno and dept.deptno are indexed?

May be this example we wont see much difference since it's only two small tables
but with joins of 4 or 5 tables will changing WHERE clause in Inline views
change dramatically the execution plan?

          Followup:

It can -- it might not.  I can give examples where it will, and some where it
won't.  Especially when outer joins are involved (they it almost certainly WILL
change the plan and the results).

In the case like above where the two queries are semantically equivalent, it'll
most likely not change anything.

      Rating: 5
Smaller
      Query?  February 27, 2003
Reviewer:  Martin
       from UK
Hi Tom,

don't worry this is not a question about directed acyclic graphs ( I have enough
trouble spelling it, never mind what it is! ;-)).

You're example above of how subquery factoring can tell the optimiser to reuse
the relevant parts of the subquery is great, but was wondering if you could do a
quick example of a much simpler query which can benefit from the WITH clause?
Every example I've tried seems to make no benefit in plan / stats.

Thanks in advance

          Followup:

what you need is a relatively expensive subquery, used many times.  Consider:

big_table@ORA920> set echo on
big_table@ORA920> set autotrace on statistics
big_table@ORA920>
big_table@ORA920> with owners
  2  as
  3  ( select distinct owner username from all_objects )
  4  select count(*)
  5    from all_objects, owners
  6   where all_objects.owner = owners.username
  7   union all
  8  select count(*)
  9    from dba_objects, owners
 10   where dba_objects.owner = owners.username
 11  /

  COUNT(*)
----------
     30537
     30977

2 rows selected.

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

big_table@ORA920>
big_table@ORA920> select count(*)
  2    from all_objects, (select distinct owner username from all_objects )
owners
  3   where all_objects.owner = owners.username
  4   union all
  5  select count(*)
  6    from dba_objects, (select distinct owner username from all_objects )
owners
  7   where dba_objects.owner = owners.username
  8  /

  COUNT(*)
----------
     30537
     30977

2 rows selected.

Statistics
----------------------------------------------------------
         28  recursive calls
          0  db block gets
     442058  consistent gets
          0  physical reads
          0  redo size
        418  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          4  sorts (memory)
          0  sorts (disk)
          2  rows processed

big_table@ORA920> set autotrace off
big_table@ORA920>

See the 75%++ reduction in LIO's -- we didn't have to distinct owner from
all_users two times.

      Rating:
      5
Hmmm..  February 28, 2003
Reviewer:
      Martin  from UK
Hi Tom,

thanks for the response, but I can't seem to replicate the savings you show,
i.e.

SQL> ed
Wrote file afiedt.buf

  1  with owners
  2  as
  3  ( select distinct owner username from all_objects )
  4  select count(*)
  5  from all_objects, owners
  6  where all_objects.owner = owners.username
  7  union all
  8  select count(*)
  9  from dba_objects, owners
 10* where dba_objects.owner = owners.username
SQL> /

  COUNT(*)
----------
     39911
     40088

2 rows selected.

Execution Plan
----------------------------------------------------------
ERROR:
ORA-01039: insufficient privileges on underlying objects of the view

SP2-0612: Error generating AUTOTRACE EXPLAIN report

Statistics
----------------------------------------------------------
        521  recursive calls
          8  db block gets
     798808  consistent gets
        261  physical reads
          0  redo size
        426  bytes sent via SQL*Net to client
        503  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
         18  sorts (memory)
          1  sorts (disk)
          2  rows processed

SQL> select count(*)
  2    2    from all_objects, (select distinct owner username from all_objects )

  3  owners
  4    3   where all_objects.owner = owners.username
  5    4   union all
  6    5  select count(*)
  7    6    from dba_objects, (select distinct owner username from all_objects )

  8  owners
  9    7   where dba_objects.owner = owners.username
 10

SQL> ed
Wrote file afiedt.buf

  1  select count(*)
  2  from all_objects, (select distinct owner username from all_objects )
  3  owners
  4  where all_objects.owner = owners.username
  5  union all
  6  select count(*)
  7  from dba_objects, (select distinct owner username from all_objects )
  8  owners
  9* where dba_objects.owner = owners.username
SQL> /

  COUNT(*)
----------
     39911
     40088

2 rows selected.

Execution Plan
----------------------------------------------------------
ERROR:
ORA-01039: insufficient privileges on underlying objects of the view

SP2-0612: Error generating AUTOTRACE EXPLAIN report

Statistics
----------------------------------------------------------
         28  recursive calls
          6  db block gets
     798699  consistent gets
        255  physical reads
          0  redo size
        426  bytes sent via SQL*Net to client
        503  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          3  sorts (memory)
          1  sorts (disk)
          2  rows processed

I think this is the reason why I'm having difficulty finding situations where
using the WITH works better, in the
above situation it seems worse!

          Followup:

It'll vary by version -- I'll bet  you have 9iR1 perhaps?  Here is another test
from 9iR2 that shows the differences in the plans (i ran the queries 2 times to
avoid the recursive sql counts for parsing which accounts for the nomimal
differences you see above perhaps?)

ps$tkyte@ORA920> create table t1 as select * from all_objects;

Table created.

ops$tkyte@ORA920> create table t2 as select * from dba_objects;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table t1 compute statistics
  2  for table for all indexes for all indexed columns;

Table analyzed.

ops$tkyte@ORA920> analyze table t2 compute statistics
  2  for table for all indexes for all indexed columns;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> with owners
  2  as
  3  ( select distinct owner username from t1 )
  4  select count(*)
  5  from t1, owners
  6  where t1.owner = owners.username
  7  union all
  8  select count(*)
  9  from t2, owners
 10  where t2.owner = owners.username
 11  /

  COUNT(*)
----------
     29648
     30089

ops$tkyte@ORA920> set autotrace on
ops$tkyte@ORA920> /

  COUNT(*)
----------
     29648
     30089

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=138 Card=2 Bytes=68)
   1    2     RECURSIVE EXECUTION OF 'SYS_LE_2_0'
   2    0   TEMP TABLE TRANSFORMATION
   3    2     UNION-ALL
   4    3       SORT (AGGREGATE)
   5    4         HASH JOIN (Cost=68 Card=948736 Bytes=32257024)
   6    5           TABLE ACCESS (FULL) OF 'T1' (Cost=42 Card=29648
Bytes=504016)
   7    5           VIEW (Cost=8 Card=29648 Bytes=504016)
   8    7             TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6603_F54CC05A'
(Cost=8 Card=29648 Bytes=504016)
   9    3       SORT (AGGREGATE)
  10    9         HASH JOIN (Cost=70 Card=962848 Bytes=32736832)
  11   10           VIEW (Cost=8 Card=29648 Bytes=504016)
  12   11             TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6603_F54CC05A'
(Cost=8 Card=29648 Bytes=504016)
  13   10           TABLE ACCESS (FULL) OF 'T2' (Cost=43 Card=30089
Bytes=511513)

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

ops$tkyte@ORA920> set autotrace off
ops$tkyte@ORA920>
ops$tkyte@ORA920> select count(*)
  2  from t1, (select distinct owner username from t1 ) owners
  3  where t1.owner = owners.username
  4  union all
  5  select count(*)
  6  from t2, (select distinct owner username from t1 ) owners
  7  where t2.owner = owners.username
  8  /

  COUNT(*)
----------
     29648
     30089

ops$tkyte@ORA920> set autotrace on
ops$tkyte@ORA920> /

  COUNT(*)
----------
     29648
     30089

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=430 Card=2 Bytes=68)
   1    0   UNION-ALL
   2    1     SORT (AGGREGATE)
   3    2       HASH JOIN (Cost=214 Card=29648 Bytes=1008032)
   4    3         TABLE ACCESS (FULL) OF 'T1' (Cost=42 Card=29648 Bytes=504016)
   5    3         VIEW (Cost=154 Card=29648 Bytes=504016)
   6    5           SORT (UNIQUE) (Cost=154 Card=29648 Bytes=504016)
   7    6             TABLE ACCESS (FULL) OF 'T1' (Cost=42 Card=29648
Bytes=504016)
   8    1     SORT (AGGREGATE)
   9    8       HASH JOIN (Cost=216 Card=30089 Bytes=1023026)
  10    9         VIEW (Cost=154 Card=29648 Bytes=504016)
  11   10           SORT (UNIQUE) (Cost=154 Card=29648 Bytes=504016)
  12   11             TABLE ACCESS (FULL) OF 'T1' (Cost=42 Card=29648
Bytes=504016)
  13    9         TABLE ACCESS (FULL) OF 'T2' (Cost=43 Card=30089 Bytes=511513)

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

ops$tkyte@ORA920> set autotrace off

when I did this in 9iR1 -- it was a bit different as the optimizer chose:

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3517 Card=2 Bytes=68)
   1    0   UNION-ALL
   2    1     SORT (AGGREGATE)
   3    2       MERGE JOIN (Cost=1754 Card=22196 Bytes=754664)
   4    3         SORT (JOIN) (Cost=877 Card=22196 Bytes=377332)
   5    4           VIEW (Cost=877 Card=22196 Bytes=377332)
   6    5             SORT (UNIQUE) (Cost=877 Card=22196 Bytes=377332)
   7    6               TABLE ACCESS (FULL) OF 'T1' (Cost=23 Card=22196
Bytes=377332)
   8    3         SORT (JOIN) (Cost=877 Card=22196 Bytes=377332)
   9    8           TABLE ACCESS (FULL) OF 'T1' (Cost=23 Card=22196
Bytes=377332)
  10    1     SORT (AGGREGATE)
  11   10       MERGE JOIN (Cost=1763 Card=22273 Bytes=757282)
  12   11         SORT (JOIN) (Cost=877 Card=22196 Bytes=377332)
  13   12           VIEW (Cost=877 Card=22196 Bytes=377332)
  14   13             SORT (UNIQUE) (Cost=877 Card=22196 Bytes=377332)
  15   14               TABLE ACCESS (FULL) OF 'T1' (Cost=23 Card=22196
Bytes=377332)
  16   11         SORT (JOIN) (Cost=887 Card=22273 Bytes=378641)
  17   16           TABLE ACCESS (FULL) OF 'T2' (Cost=23 Card=22273
Bytes=378641)

for both plans

different init.ora settings and such will affect the outcome as well.

      Rating: 5
Error with with
      in SQQPLUS   February 28, 2003
Reviewer:  A
      Reader
Hi Tom,
When I run one of the examples in SQLPLUS, I get the following error:
SQL> with owners
SP2-0734: unknown command beginning "with owner..." - rest of line ignored.
SQL>    as
SP2-0042: unknown command "as" - rest of line ignored.
SQL>    ( select distinct owner username from all_objects )

When I copy the whole statment into buffer, I get the following:

SP2-0642: SQL*Plus internal error state 2091, context 0:0:0
Unsafe to proceed

I am running 9.2 Standard Edition.  Does this version support the "with"?

Thank you.

          Followup:

yes it does however you are using 8i or before sqlplus ;)

SQLPlus is the guy having heartburn with "with" -- see it is an SP error, not an
ORA error.

      Rating:
      5
Aha!  February 28, 2003
Reviewer:
      Martin  from UK
Yep, in 9.2 it does seem to be significantly better, and I get the kind of
savings that you show, yet in 9.1 it's worse. Oh well, another excuse (as if we
needed any) to go to 9.2.

Thanks for all your help.

          Followup:

Well, it is not "worse" -- just not "any better in some cases".  Here is the
tkprof from 9iR1 for the original example.  I chopped the plans out cause they
are huge -- but -- they were the same.

with owners
as
( select distinct owner username from all_objects )
select count(*)
from all_objects, owners
where all_objects.owner = owners.username
union all
select count(*)
from dba_objects, owners
where dba_objects.owner = owners.username

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.14       0.13          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2     14.68      15.28          0     411570          0           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4     14.82      15.41          0     411570          0           2

select count(*)
from all_objects, (select distinct owner username from all_objects ) owners
where all_objects.owner = owners.username
union all
select count(*)
from dba_objects, (select distinct owner username from all_objects ) owners
where dba_objects.owner = owners.username

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.10       0.10          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2     14.30      15.10          0     411570          0           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4     14.40      15.21          0     411570          0           2

      Rating: 5
As an
      addendum...  February 28, 2003
Reviewer:
      Martin  from UK
Actually, Tom, further quick question, since the plans for the WITH (and
WITHout..) at 9.1 are the same, does this mean we can really think of the WITH
statement (at 9.1) as shorthand for the major query? "Under the covers" is this
what oracle's doing?

Thanks

          Followup:

Not really - it all depends on the query.  Consider this now (9iR1):

ops$tkyte@ORA9I> set autotrace on statistics
ops$tkyte@ORA9I> with owners
  2  as
  3  ( select distinct owner username from all_objects where rownum >= 1)
  4  select count(*)
  5  from all_objects, owners
  6  where all_objects.owner = owners.username
  7  union all
  8  select count(*)
  9  from dba_objects, owners
 10  where dba_objects.owner = owners.username
 11  /

  COUNT(*)
----------
     22197
     22273

Statistics
----------------------------------------------------------
          4  recursive calls
         10  db block gets
     280407  consistent gets
          1  physical reads
        516  redo size
        426  bytes sent via SQL*Net to client
        503  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
          2  rows processed

ops$tkyte@ORA9I> select count(*)
  2  from all_objects, (select distinct owner username from all_objects where
rownum >= 1) owners
  3  where all_objects.owner = owners.username
  4  union all
  5  select count(*)
  6  from dba_objects, (select distinct owner username from all_objects where
rownum >= 1) owners
  7  where dba_objects.owner = owners.username
  8  /

  COUNT(*)
----------
     22197
     22273

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

ops$tkyte@ORA9I> set autotrace off

In 9iR1 is seems that if the optimizer can merge the subquery -- it will.
Adding rownum (aggregates, order bys, set operations like intersect and such)
precludes a simple merge so it materialized the subquery once off to the side
and reused it twice in the first query.

In the second query -- it did it twice.

      Rating: 5
potential to
      use the same sub query twice  July 06, 2003

Reviewer:  A reader
ops$tkyte@ORA9I> set autotrace on statistics
ops$tkyte@ORA9I> with owners
  2  as
  3  ( select distinct owner username from all_objects where rownum >= 1)
  4  select count(*)
  5  from all_objects, owners
  6  where all_objects.owner = owners.username
  7  union all
  8  select count(*)
  9  from dba_objects, owners
 10  where dba_objects.owner = owners.username
 11  /

Tom in the above , can you explain.. where is the potential to to use ( select
distinct owner username from all_objects where rownum >= 1) , twice?

          Followup:

we are joining two different views -- ALL_OBJECTS and DBA_OBJECTS to this
factored subquery -- so we use it twice.

Lines 5 and 9

      Rating: 4
In
      PL/SQL?  July 11, 2003
Reviewer:  Kashif
       from Reston, VA
Hi Tom,

Can the with clause be used inside pl/sql? Unfortunately I don't have access to
9i so I'm not able to play around with this feature. I couldn't figure it out
from the New Features and Warehousing guides, and I couldn't even find an
explanation for the WITH clause in the 9i SQL Reference.

Secondly, are the developers of the CBO not allowed to discuss any potential
features and improvements they have included in a new release of the CBO? It
seems like we're frequently guessing what the optimizer does from one release to
the next, and observing radical differences in a feature in two different
releases, without having any advance knowledge or expectation of the difference.
Or am I not reading some document somewhere? Many thanks.

Kashif

          Followup:

ops$tkyte@ORA920> declare
  2          cursor c is
  3          with q as (select * from dual)
  4          select * from q;
  5  begin
  6          null;
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>

yes, it works.

The sql reference documents "with" under select (not under "w")

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/statements_103a.htm#2065648
although in the index -- under W, there is "WITH - subquery factoring" and a
link to it as well.

Lastly, many of the new performance related features would be in the "Whats new
in" chapter for the Performance guide, this type of chapter is found in most of
the docs

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96533/whatsnew.htm#972592

July
      12, 2003 Thiruvaiyaru -- Thanks for the
      question regarding "Error when trying to view BFILE data", version 8.1.6

      You Asked
Tom,

This is regarding the question that I had asked :-

Q (1):  Viewing Data associated with BLOB Datatype
on August 14th.

I created a table called "test" with 2 columns
empno   number primary key
details binary file LOB

I created a directory as follows :-

create directory test_dir as '/home/test';

and then inserted a row to the table :-
insert into test values(1111,BFILENAME('test_dir','image.txt'));

the file image.txt resides on /home/test directory.

I tried to view the data using the following select stmnt :-

select utl_raw.cast_to_varchar2(dbms_lob.substr(details,1,100))
from test;

The result gives the following errors :-

ERROR at line 1:
ORA-22289: cannot perform FILEREAD operation on an unopened file
or LOB
ORA-06512: at "SYS.DBMS_LOB", line 680
ORA-06512: at line 1

Your help in this would be greatly appreciated.

Thanks
Sai

      and we said...
You need to write one function -- something like "blobtochar" in
the following:

ops$tkyte@8i> create table demo
  2  ( id        int primary key,
  3    theBlob    bfile
  4  )
  5  /

Table created.

ops$tkyte@8i>
ops$tkyte@8i> create or replace directory
  2  my_files as '/export/home/tkyte/public_html';

Directory created.

ops$tkyte@8i>
ops$tkyte@8i> insert into demo
  2  values
  3  ( 1, bfilename( 'MY_FILES', 'blob.sql' ) );

1 row created.

ops$tkyte@8i>
ops$tkyte@8i>
ops$tkyte@8i> create or replace
  2  function blobtochar( p_blob in bfile ) return varchar2
  3  as
  4      l_raw raw(4000);
  5      l_bfile bfile default p_blob;
  6  begin
  7      dbms_lob.fileopen( l_bfile );
  8      l_raw := dbms_lob.substr( l_bfile,4000,1 );
  9      dbms_lob.fileclose( l_bfile );
 10      return utl_raw.cast_to_varchar2(l_raw);
 11  end;
 12  /

Function created.

ops$tkyte@8i>
ops$tkyte@8i> column x format a40 word_wrapped
ops$tkyte@8i> select id, blobtochar(theBlob) x from demo
  2  /

        ID X
---------- ----------------------------------------
         1 drop table demo;
           create table demo
           ( id        int primary key,
           theBlob    bfile
           )
           /
           create or replace directory
           my_files as
           '/export/home/tkyte/public_html';
           insert into demo
           values
           ( 1, bfilename( 'MY_FILES', 'blob.sql' )
           );
           create or replace
           function blobtochar( p_blob in bfile )
           return varchar2
           as
           l_raw raw(4000);
           l_bfile bfile default p_blob;
           begin
           dbms_lob.fileopen( l_bfile );
           l_raw := dbms_lob.substr( l_bfile,4000,1
           );
           dbms_lob.fileclose( l_bfile );
           return utl_raw.cast_to_varchar2(l_raw);
           end;
           /
           column x format a40 word_wrapped
           select id, blobtochar(theBlob) x from
           demo
           /

Review &
      Followup

      Rating: 2
I got the error
      message like this ora-22288 and ora-06512  February 23, 2002

Reviewer:  Ram Mohan  from India
ORA-22288: file or LOB operation FILEOPEN failed
LFI-00108: Open expect the file [blob][sql] to exist.
ORA-06512: at "SYS.DBMS_LOB", line 475
ORA-06512: at "SCOTT.BLOBTOCHAR", line 5
ORA-06512: at line 1

          Followup:

seems self explanatory to me.

The file you requested to open to read does not exist on the SERVER in the
directory you specified.

Make sure you are not using a "network" drive on NT.  They are not visible to
the database by default (it is not like a Unix "mount" in NT, the network drives
YOU see are not the same network drives services and other background processes
'see').

Make sure the file actually EXISTS on the server, the database server can only
read files that exist on the server (otherwise the CLIENT app you write must
read the file and write it to the lob).

Make sure the file on the server is READABLE by the very un-privileged OS
account that is uses to run the database service.

Make sure you are using the right directory in the code as well.

      Rating: 3
how to use
      network drives to access data  April 24, 2002

Reviewer:  vishwesh  from India
hi,
i have the dat files and the ctl files for loading data on an application
server. i need to load data on the database server. for that i have written a
java code which will use the Runtime and exec a sqlldr utility to load data. if
i run this java code , the data loads, but if i wrap it as a procedure it
doesnt. this is because of the data being on a different machine. how can i
access data on a diff machine

          Followup:

On unix -- just mount it and it's there (ahh, the simplicity of Unix).

On NT -- read:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:241814624807
and you'll see what hoops you must jump through.

      Rating: 5
Load MS
      doc  July 11, 2003
Reviewer:  Sean
       from NJ, USA
Hi Tom,

I am using 9i release 2 on Window 2000.

I used your script.  It works fine with Bfile text file.  But the display on
SQLPLUS is a little strange for MS document of bfile (not surprised me).

My question is how to read MS word in blob or bfile using dbms_lob package?
Another question is how to load MS doc into blob without using C, Java or VB.

Thanks so much for your help.

Best,

Sean

          Followup:

dbms_lob.loadfromfile

see the application developers guide for LOBS:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-APP

or search for

load lob

on this site.

July 12, 2003 Abdullah -- Thanks for the question regarding
      "Monitoring", version 7.3.4 & 8.0.5

      You Asked
Q1)
I am trying to monitor the files access by view the v$filestat
to see where is the heavy load to dustribute datafiles butween
hard disks. My q is which column is more important PHYRDS or
PHYWRTS or other. How should i calculate the load on each
datafile. I need some information about this view and its column
meaning.

Q2)I am working with NOARCHIVE mode and redo log files size
50MB, i want to change to ARCHIVE mode. Is 50MB good size or it
shuold to be less. When switch log happen is it when redo file
become full only. Because i have 4 groups with two members for
each with 50MB and some time it wait for checkpoint to complete
to over write the redo log file.

Q3)
In sqlplus when i put timing on i received this 'real: 720' what
this mean and if i want to get the time in second like you when
i search in the search page i received with the result 'took .19
seconds'

 Thanks in advance

      and we said...
#1) Well, both are important.  One it physical reads and the
other is physical writes.  You would like reads and writes to be
evenly distributed if possible (but it won't be 100%, some files
are written to more then read and vice versa.).

See

http://technet.oracle.com/doc/server.804/a58242/ch3.htm#23664
for the details of the columns in this view.

#2) 50 meg is a fine size -- so is 5meg and 500meg.  It depends
on your needs, your time to recovery from failure, what you do
on your system.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:69012348056
for additional information.  You need to add more online
redo log groups to avoid the checkpoint not complete messages.

#3) On NT, upto version 8.1 of sqlplus, sqlplus would report
timings in thousands of seconds.  720 is 0.72 seconds.

In 8.1, sqlplus will report timings on NT in this format:

Elapsed: 00:00:00.72

In PLSQL, to get the timings as I did, you would code:

declare
   ...
   l_start number default dbms_utility.get_time;
begin
   .....

   dbms_output.put_line
   ( round( (dbms_utility.get_time-l_start)/100, 2 ) );
end;
/

Review &
      Followup

      Rating: 5
v$filestat
        July 11, 2003
Reviewer:  steve  from
      Montreal
Hi Tom,

I read the column definition of V$FILESTAT and have a
(perhaps stupid) question. The documentation says the
"time (in hundreds of a second)". But when I query
the table and order by LSTIOTIM ASC, I see this
towards the bottom (below).

Doesn't it look awefully high? 2 seconds for an IO?

select substr(name,1,40),a.file#,writetim,readtim,avgiotim,
       lstiotim from v$filestat a,v$datafile b
  where a.file#=b.file#  and writetim > 10000
  order by    LSTIOTIM ASC

SUBSTR(NAME,1,40)                      WRITETIM READTIM AVGIOTIM   LSTIOTIM
-------------------------------------- -------- ------- -------- ----------
/data3/dds/cd_mocf_7_data01.dbf         1728247   64505     1473        632
/data2/dds/cd_mocf_6_data01.dbf          140363    5005     1435        633
/oracle1/dds/perfstat01.dbf               13179     202      458        665
/index2/dds/cd_ppsmsdata_7_index01.dbf  5521152  159661      466        683
/data1/dds/cd_mocf_5_data01.dbf           67724    2743     1548        805
/data1/dds/cd_ppsmsdata_7_data01.dbf     400855   24469      665        805
/data1/dds/cd_ppcdrdata_1_data01.dbf     752300   13319      942       1414
/data2/dds/cd_ppcdrdata_2_data01.dbf     217652   15674      943       1414
/data2/dds/cd_cust_22_data01.dbf         229641   19281     1894       1917
/data1/dds/cd_cust_21_data01.dbf          11384   52581     2004       1939
/data4/dds/cd_cust_20_data01.dbf          18085  330755     2008       1944

SUBSTR(NAME,1,40)                      WRITETIM READTIM AVGIOTIM   LSTIOTIM
-------------------------------------- -------- ------- -------- ----------
/data4/dds/cd_cust_16_data01.dbf          12505  216495     2064       1952
/index1/dds/cd_ppw_ff_22_index01.dbf    2449174   68440     2455       2299

112 rows selected.

          Followup:

according to that, it is much more then 2 -- but if you are using async IO,
these times can be way off and you would need to use an OS monitoring tool to
get absolutely reliable numbers.

July 12, 2003 noga -- Thanks for the question regarding "export
      and importing indexes and pk", version 8.0.6

      You Asked
Hi,

I dropped some of the indexes in my schema, when I tried to load
them back using the export rows=n I found that some pk were
build partially, that mean I see the pk in user_constraints but
not in user_indexes. I also found that this happened only when I
had index that contained all the fields of the pk and more.
Only when I dropped the index and first created the pk, the pk
was build correctly-
Who can I continoue using the export import utility if we are
losing some indexes ?

Example-
-- See that the PK exsist partialy-
SQL>  select index_name,status from user_indexes where
table_name='Q2';

INDEX_NAME                     STATUS
------------------------------ --------
Q2_2IX                         VALID

SQL>  select constraint_name,status from user_constraints where
table_name='Q2';

CONSTRAINT_NAME                STATUS
------------------------------ --------
Q2_PK                          ENABLED
Q2_SRV_TRX_S_NO_NN             ENABLED
Q2_DVC_SFX_NO_NN               ENABLED
SYS_C0087257                   ENABLED
SYS_C0087258                   ENABLED
Q2_PHD_ID_NN                   ENABLED

6 rows selected.

SQL> alter table q2 drop constraint Q2_PK;

Table altered.

SQL> ALTER TABLE Q2 ADD CONSTRAINT Q2_PK PRIMARY KEY
(SRV_TRX_S_NO, DVC_SFX_NO)
USING INDEX ;

Table altered.

SQL> select constraint_name,status from user_constraints where
table_name='Q2';

CONSTRAINT_NAME                STATUS
------------------------------ --------
Q2_PK                          ENABLED
Q2_SRV_TRX_S_NO_NN             ENABLED
Q2_DVC_SFX_NO_NN               ENABLED
SYS_C0087257                   ENABLED
SYS_C0087258                   ENABLED
Q2_PHD_ID_NN                   ENABLED

6 rows selected.

SQL>  select index_name,status from user_indexes where
table_name='Q2';

INDEX_NAME                     STATUS
------------------------------ --------
Q2_2IX                         VALID

Droping the index-
SQL> drop  index Q2_2IX;
drop  index Q2_2IX
*
ERROR at line 1:
ORA-02429: cannot drop index used for enforcement of
unique/primary key

SQL> alter table q2 drop primary key;

Table altered.

SQL> drop  index Q2_2IX;

Index dropped.

SQL> @q2_cre_pk (creates the PK as described before)

Table altered.

SQL> select constraint_name,status from user_constraints where
table_name='Q2';

CONSTRAINT_NAME                STATUS
------------------------------ --------
Q2_PK                          ENABLED
Q2_SRV_TRX_S_NO_NN             ENABLED
Q2_DVC_SFX_NO_NN               ENABLED
SYS_C0087257                   ENABLED
SYS_C0087258                   ENABLED
Q2_PHD_ID_NN                   ENABLED

6 rows selected.

SQL> select index_name,status from user_indexes where
table_name='Q2';

INDEX_NAME                     STATUS
------------------------------ --------
Q2_PK                          VALID

SQL> CREATE INDEX Q2_2IX ON Q2 (SRV_TRX_S_NO,
  2                            DVC_SFX_NO,
                          PHD_ID);

Index created.

SQL> select index_name,status from user_indexes where
table_name='Q2';

INDEX_NAME                     STATUS
------------------------------ --------
Q2_2IX                         VALID
Q2_PK                          VALID

Thanks

      and we said...
A primary key constraint will use an EXISTING index (take it
over actually) as you have observed.  That is why:

SQL> drop  index Q2_2IX;
drop  index Q2_2IX
*
ERROR at line 1:
ORA-02429: cannot drop index used for enforcement of
unique/primary key

This is the expected behaviour.  Nothing was partially built
here -- the primary key was "borrowing" the other index to
enforce itself.

Here is an example that shows the order of creation will result
in different objects.  A constraint does not imply an index will
be created!!

tkyte@ORA806.WORLD> create table t ( x int, y int );
Table created.

tkyte@ORA806.WORLD> create index t_idx on t(x,y);
Index created.

tkyte@ORA806.WORLD> alter table t add constraint t_pk primary
key(x);
Table altered.

tkyte@ORA806.WORLD> select object_name, object_type from
user_objects;

OBJECT_NAME                    OBJECT_TYPE
------------------------------ ------------
T                              TABLE

T_IDX                          INDEX

There is no index created for this primary key -- there is
none needed since we already have one that is good enough.  To
see that we in fact have a primary key we just need to insert:

tkyte@ORA806.WORLD> insert into t(x) values(1);

1 row created.

tkyte@ORA806.WORLD> insert into t(x) values(1);
insert into t(x) values(1)
            *
ERROR at line 1:
ORA-00001: unique constraint (TKYTE.T_PK) violated

So, we have a primary key -- it is not "partial".  To see that
we 'hijacked' this index, we can:

tkyte@ORA806.WORLD> drop index t_idx;
drop index t_idx
*
ERROR at line 1:
ORA-02429: cannot drop index used for enforcement of
unique/primary key

So, to see that a different order of object creation may
result in a different set of schema objects we can:

tkyte@ORA806.WORLD> alter table t drop constraint t_pk;
Table altered.

tkyte@ORA806.WORLD> drop index t_idx;
Index dropped.

tkyte@ORA806.WORLD> alter table t add constraint t_pk primary
key(x);

Table altered.

Now that created an index for us since one does not already
exist that we can use.

tkyte@ORA806.WORLD> create index t_idx on t(x,y);

Index created.

tkyte@ORA806.WORLD> select object_name, object_type from
user_objects;

OBJECT_NAME                    OBJECT_TYPE
------------------------------ ------------
T                              TABLE

T_IDX                          INDEX
T_PK

So, now we have the "implicitly" created (but redundant) index
for the primary key as well as the additional index we created.
This is the expected behaviour in Oracle8.0 and up.
Review &
      Followup

      Rating: 4
Question about
      performance ....  February 25, 2002
Reviewer:
      Arindom Kanti Dam  from Boston, MA USA
Was trying to speed up the primary key constraint/index using parallel query. My
results :

SQL>
SQL> desc url
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 URLID                                     NOT NULL NUMBER
 BIZID                                              CHAR(10)
 URL                                                VARCHAR2(256)
 URLTYPE                                            VARCHAR2(6)
 FAILURECOUNT                                       NUMBER
 CREATEDON                                 NOT NULL DATE
 MODIFIEDON                                         DATE
 CREATEDBY                                          VARCHAR2(40)
 MODIFIEDBY                                         VARCHAR2(40)

SQL>
SQL> select OBJECT_NAME, OBJECT_TYPE
  2  from   user_objects;

OBJECT_NAME                    OBJECT_TYPE
------------------------------ ------------------
URL                            TABLE

1 row selected.

Elapsed: 00:00:00.01
SQL>
SQL> ALTER TABLE URL
  2  ADD CONSTRAINT PK_URL PRIMARY KEY (URLID)
  3  USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE (INITIAL 53248
  4  NEXT 102400 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 0
  5  FREELISTS 1 );

Table altered.

Elapsed: 00:00:55.24
SQL>
SQL> select OBJECT_NAME, OBJECT_TYPE
  2  from   user_objects;

OBJECT_NAME                    OBJECT_TYPE
------------------------------ ------------------
PK_URL                         INDEX
URL                            TABLE

2 rows selected.

Elapsed: 00:00:00.01
SQL>
SQL> rem ---
> rem --- Instead of the above ; figured I would create index 'in parallel'
SQL> rem --- and enable constraint(as prallel ddl is not possible in alter
table)
SQL> rem ---
>
SQL>
SQL>
SQL> alter session force parallel ddl;

Session altered.

Elapsed: 00:00:00.01
SQL>
SQL> ALTER TABLE URL
  2  drop CONSTRAINT PK_URL;

Table altered.

Elapsed: 00:00:00.31
SQL>
SQL> drop index pk_url;
drop index pk_url
           *
ERROR at line 1:
ORA-01418: specified index does not exist

Elapsed: 00:00:00.02
SQL>
SQL> select OBJECT_NAME, OBJECT_TYPE
  2  from   user_objects;

OBJECT_NAME                    OBJECT_TYPE
------------------------------ ------------------
URL                            TABLE

1 row selected.

Elapsed: 00:00:00.01
SQL>
SQL>
SQL> create index pk_url
  2  on URL(URLID) /*+ parallel(url,7) */
  3  PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE (INITIAL 53248
  4  NEXT 102400 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 0
  5  FREELISTS 1 );

Index created.

Elapsed: 00:00:52.69
SQL>
SQL> ALTER TABLE URL
  2  ADD CONSTRAINT PK_URL PRIMARY KEY (URLID) using index;

Table altered.

Elapsed: 00:00:59.84
SQL>
SQL> select OBJECT_NAME, OBJECT_TYPE
  2  from   user_objects;

OBJECT_NAME                    OBJECT_TYPE
------------------------------ ------------------
PK_URL                         INDEX
URL                            TABLE

2 rows selected.

Elapsed: 00:00:00.01
SQL>
SQL>

My Question is ; if the 'alter table add constraint' takes over the existing
index ; why does it take almost as long as the create index itself? Is there a
work around or suggestions to speed up the index/pk creation?

          Followup:

Well, couple things here.

You need to read the documentation for the syntax of a parallel create index.
You are using a hint (valid in DML, not DDL).  You need to use the parallel
keyword.

Additionally, you created in NON-UNIQUE index.  Hence when you added the
constraint -- it had to VALIDATE all of the data since the index it used is not
enough to say "hey, this data is OK".

For example:

REM drop table t;
REM
REM create table t as select * from all_objects;
REM insert /*+ APPEND */ into t select * from t;
REM commit;
couple more insert/commits to load it up...

ops$tkyte@ORA817DEV.US.ORACLE.COM> set timing on
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t add constraint t_pk primary
key(object_id);

Table altered.

Elapsed: 00:00:08.42

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t drop constraint t_pk;
Table altered.
Elapsed: 00:00:00.68

ops$tkyte@ORA817DEV.US.ORACLE.COM> create unique index t_idx
  2  on t(object_id) unrecoverable
  3  parallel 2
  4  /
Index created.

Elapsed: 00:00:06.99

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t add constraint t_pk primary
key(object_id)
  2  /
Table altered.

Elapsed: 00:00:00.03
ops$tkyte@ORA817DEV.US.ORACLE.COM>

Now, even if you use a NON-Unique index, you can still do this IF you know for a
FACT that the data is OK (unique).

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t drop constraint t_pk;

Table altered.

Elapsed: 00:00:00.28
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx
  2  on t(object_id) unrecoverable
  3  parallel 2
  4  /

Index created.

Elapsed: 00:00:07.86
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t add constraint t_pk primary
key(object_id) rely enforce
  2  /

Table altered.

Elapsed: 00:00:00.02
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select * from t where rownum =
1;
insert into t select * from t where rownum = 1
*
ERROR at line 1:
ORA-00001: unique constraint (OPS$TKYTE.T_PK) violated

Elapsed: 00:00:00.06
ops$tkyte@ORA817DEV.US.ORACLE.COM>

      Rating:
      5
Thanks.  February 25, 2002
Reviewer:
      Arindom Kanti Dam  from Boston, MA USA
Thanks.Helped a lot...
(Though Table 23-1 ; Oracle 8i Concepts ; 8.1.6 does state that alter session
force parallel DDL overides the Parallel Clause for a create index parallel
operation; also I did see parallel OS processes with a ps -aef)

          Followup:

Yes -- but your "hint" leads me to believe you believe it will get you an index
created with parallel 7 -- it won't, it does nothing.

the alter session for you set the degree of parallelism to the default degree of
parallelism.  Use the PARALLEL clause in the create command to get it set to
what you want explicitly.

      Rating: 4
Is this
      Normal?  July 11, 2003
Reviewer:  Sunil
      Gururaj  from Cyprus
Hi Tom,

Could you explain a bit about the below behaviour in Oracle 8.1.7.4?

There is a table LOGICAL_DATE which has the below Indexes/Constraints:

SQL>> select index_name from ind where table_name = 'LOGICAL_DATE';

Index Name
-------------------------
LOGICAL_DATE_1UQ
LOGICAL_DATE_PK

2 rows selected.

SQL>> select constraint_name,constraint_type from user_constraints where
table_name = 'LOGICAL_DATE';

Constraint Name                     C
----------------------------------- -
LGCDT_LOGICAL_DATE_NN               C
LGCDT_LOGICAL_DATE_TYPE_NN          C
LOGICAL_DATE_1UQ                    U
LOGICAL_DATE_PK                     P

4 rows selected.

Now, I drop the UQ constraint and assume that the index associated with it would
be dropped automatically as is the case with PKs

SQL>> alter table logical_date drop constraint LOGICAL_DATE_1UQ;

Table altered.

SQL>> select constraint_name,constraint_type from user_constraints where
table_name = 'LOGICAL_DATE';

Constraint Name                     C
----------------------------------- -
LGCDT_LOGICAL_DATE_NN               C
LGCDT_LOGICAL_DATE_TYPE_NN          C
LOGICAL_DATE_PK                     P

3 rows selected.

As seen above, the Constraint gets dropped but...

SQL>> select index_name from ind where table_name = 'LOGICAL_DATE';

Index Name
-------------------------
LOGICAL_DATE_1UQ
LOGICAL_DATE_PK

2 rows selected.

... the index remains. When I try to drop the index,

SQL>> drop index LOGICAL_DATE_1UQ;
drop index LOGICAL_DATE_1UQ
           *
ERROR at line 1:
ORA-02429: cannot drop index used for enforcement of unique/primary key

SQL>>

Why is this?

          Followup:

osh kosh bgosh -- how's about the DDL for that example please, instead of me
trying to figure out what permutations of DDL i might have to make up.

I can replicate your example, I can also come up with tons of examples that do
not.

Here is one of each

ops$tkyte@ORA817DEV> drop table logical_date;

Table dropped.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create table logical_date
  2  ( x int not null,
  3    y int not null,
  4    z int
  5  )
  6  /

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> alter table logical_date add constraint
LGCDT_LOGICAL_DATE_NN check (y>0);

Table altered.

ops$tkyte@ORA817DEV> alter table logical_date add constraint
LGCDT_LOGICAL_DATE_TYPE_NN check (y>0);

Table altered.

ops$tkyte@ORA817DEV> create index LOGICAL_DATE_1UQ on logical_date(x,y);

Index created.

ops$tkyte@ORA817DEV> alter table logical_date add constraint LOGICAL_DATE_1UQ
unique(x,y);

Table altered.

ops$tkyte@ORA817DEV> alter table logical_date add constraint LOGICAL_DATE_PK
primary key(x);

Table altered.

ops$tkyte@ORA817DEV> create index LOGICAL_DATE_PK on logical_date(x);

Index created.

ops$tkyte@ORA817DEV> select index_name from user_indexes;

INDEX_NAME
------------------------------
LOGICAL_DATE_1UQ
LOGICAL_DATE_PK

At this point BOTH constraints are using the SAME index for enforcement --
logical_date_1uq is used for BOTH the primary and unique constraint

ops$tkyte@ORA817DEV> alter table logical_date drop constraint logical_date_1uq;

Table altered.

ops$tkyte@ORA817DEV> select index_name from user_indexes;

INDEX_NAME
------------------------------
LOGICAL_DATE_1UQ
LOGICAL_DATE_PK

so the index 1uq stays cause IT IS FOR THE PRIMARY KEY as well -- the other
index, well, that is just an index here

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> drop table logical_date;

Table dropped.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create table logical_date
  2  ( x int not null,
  3    y int not null,
  4    z int
  5  )
  6  /

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> alter table logical_date add constraint
LGCDT_LOGICAL_DATE_NN check (y>0);

Table altered.

ops$tkyte@ORA817DEV> alter table logical_date add constraint
LGCDT_LOGICAL_DATE_TYPE_NN check (y>0);

Table altered.

ops$tkyte@ORA817DEV> alter table logical_date add constraint LOGICAL_DATE_1UQ
unique(x,y);

Table altered.

ops$tkyte@ORA817DEV> alter table logical_date add constraint LOGICAL_DATE_PK
primary key(x);

Table altered.

ops$tkyte@ORA817DEV> select index_name from user_indexes;

INDEX_NAME
------------------------------
LOGICAL_DATE_1UQ
LOGICAL_DATE_PK

ops$tkyte@ORA817DEV> alter table logical_date drop constraint logical_date_1uq;

Table altered.

ops$tkyte@ORA817DEV> select index_name from user_indexes;

INDEX_NAME
------------------------------
LOGICAL_DATE_PK

ops$tkyte@ORA817DEV>

now, since each constraint created its own index.... well, it gets dropped

      Rating: 4
Please Ignore
      the Previous Review  July 11, 2003
Reviewer:
      Sunil Gururaj  from Cyprus
Hi Tom,

Please ignore the previous review. I got the answer in the below link.

http://asktom.oracle.com/pls/ask/f?p=4950:8:417427724647734876::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:5668744528845,
Thanks,
Sunil

      Rating: 5
Indexes and
      Uniqe/PK constraints  July 11, 2003
Reviewer:
      Logan Palanisamy  from Santa Clara, CA USA
Tom,

In your second part of the example which I am quoting below, how come the PK
constraint is not using the index created by the Unique key constraint? How does
it know that the index LOGICAL_DATE_1UQ was actually created by the unique key
constraint?

<Quote>
ops$tkyte@ORA817DEV> alter table logical_date add constraint LOGICAL_DATE_1UQ
unique(x,y);

Table altered.

ops$tkyte@ORA817DEV> alter table logical_date add constraint LOGICAL_DATE_PK
primary key(x);

Table altered.

ops$tkyte@ORA817DEV> select index_name from user_indexes;

INDEX_NAME
------------------------------
LOGICAL_DATE_1UQ
LOGICAL_DATE_PK

ops$tkyte@ORA817DEV> alter table logical_date drop constraint logical_date_1uq;

Table altered.

ops$tkyte@ORA817DEV> select index_name from user_indexes;

INDEX_NAME
------------------------------
LOGICAL_DATE_PK

ops$tkyte@ORA817DEV>
< End Quote >

Can we surmise like this.

1. More than one Unique/PK constraint could use the same index (unique or not)
with the same leading edge, if the index was created separately before the
constraints.

2. A PK/Unique constraint won't use an index which was created as part of
another PK/Unique constraint. In such a case it will create its own index.

Am I correct?

          Followup:

it tends to not kidnap an index created implicitly to support a constraint in
the first place.  It'll kidnap "normal" indexes.

good summary.

July 12, 2003 --
      Thanks for the question regarding "trace sessions in three-tier
      enviroment", version 8.1.7

      You Asked
Hi Tom
Do you know any procedures, or packages or any way we can efficiently trace
sessions (SQL trace) in web based enviroment
I find it extremely difficult to accomplish such tasks specially in production
where there are hundreds of users logging in and out all the time, I was trying
to trace the SQL executed by a session and get the logic of the beggining part
of application and it was almost impossible, finally had to ask someone do it in
a seperate server...

cheers

      and we said...
Well, my approach to this has and always is -- that the APPPLICATION itself
should lend itself to doing this easily.

The code should be instrumented (lots of trace)
The code should allow for tracing easily (by setting a property somewhere)

Otherwise, you are just chasing your tail in this environment -- I mean, we can
set sql trace on from another session for some other session but -- what session
do you need it set for?

The application developer should really be trying to help you out here -- sorry
there is no easy answer for this as the application must participate.
Review &
      Followup

      Rating: 5
tracing a
      session  July 12, 2003
Reviewer:  atul
       from India
Hi,

I was told to trace a session of particular user through which one batch job
fires,

When i go to v$session to see sid,session# execution of batch finishes,This job
finishes very early..

So how could i set sql_trace on for this session

Thanks,
atrul

          Followup:

well, if it finishes so fast that you don't get a change to even set sql trace
in it, it is tuned no?  a batch that goes so fast you cannot catch it.....

it would be easiest if the batch job was parameterized to the point where by the
submitter could ask it to enable tracing (all of my stuff does!)  I mean, if it
comes and goes so fast -- what else are you going to do?

      Rating: 5
SQL Statements
      from Web Tier  July 12, 2003
Reviewer:  vt
       from NY
If you are looking for statistics on SELECT queries from Web Tier then try the
following opensource stuff:

http://www.jahia.org/jahia/page377.html
http://www.p6spy.com/

July 12, 2003 Babumanoj -- Thanks for the question regarding
      "Combining rows of two Table as columns in SQL query", version 8.1.1

      You Asked
I have two tables Employee and Leave_Detail.
An employee may have different types of leaves datails like
Emp_No,Lve_Code,Lve_NoOfTotalDays,Lve_BalanceDays etc.
Now I want to retrive details of employees with leave details.
The output should look somthing like ...

Emp_Name Lve_Code1 Lve_Balance1  Lve_Code2 Lve_Balance1  ...
ABC        L1          21           l2        55

How do i do it ?

      and we said...
If your tables look like this:

ops$tkyte@ORA8I.WORLD> create table emp
  2  ( ename varchar2(5) );

Table created.

ops$tkyte@ORA8I.WORLD> create table leave_dtl
  2  ( ename varchar2(5),
  3    leave_code varchar2(2),
  4    leave_bal  number );

Table created.

 and given data like this:

ops$tkyte@ORA8I.WORLD> insert into emp values
  2  ( 'ABC' );

1 row created.

ops$tkyte@ORA8I.WORLD> insert into leave_dtl values
  2  ( 'ABC', 'L1', 100 );

1 row created.

ops$tkyte@ORA8I.WORLD> insert into leave_dtl values
  2  ( 'ABC', 'L2', 200 );

1 row created.

ops$tkyte@ORA8I.WORLD> insert into leave_dtl values
  2  ( 'ABC', 'L3', 300 );

1 row created.

ops$tkyte@ORA8I.WORLD> insert into emp values
  2  ( 'DEF' );

1 row created.

ops$tkyte@ORA8I.WORLD> insert into leave_dtl values
  2  ( 'DEF', 'L1', 10 );

1 row created.

ops$tkyte@ORA8I.WORLD> insert into leave_dtl values
  2  ( 'DEF', 'L2', 20 );

1 row created.

ops$tkyte@ORA8I.WORLD> insert into leave_dtl values
  2  ( 'DEF', 'L3', 30 );

1 row created.

This is your query:

ops$tkyte@ORA8I.WORLD> select a.ename,
  2  max( decode( leave_code, 'L1', leave_code, null ) ) l1_cd,
  3  max( decode( leave_code, 'L1', leave_bal, null ) ) l1_bal,
  4  max( decode( leave_code, 'L2', leave_code, null ) ) l2_cd,
  5  max( decode( leave_code, 'L2', leave_bal, null ) ) l2_bal,
  6  max( decode( leave_code, 'L3', leave_code, null ) ) l3_cd,
  7  max( decode( leave_code, 'L3', leave_bal, null ) ) l3_bal
  8   from emp a, leave_dtl
  9  where a.ename= leave_dtl.ename
 10  group by a.ename
 11  /

ENAME L1     L1_BAL L2     L2_BAL L3     L3_BAL
----- -- ---------- -- ---------- -- ----------
ABC   L1        100 L2        200 L3        300
DEF   L1         10 L2         20 L3         30

ops$tkyte@ORA8I.WORLD>
Review & Followup

      Rating: 3
Combining
      rows  July 11, 2003
Reviewer:  Kishore
       from IL, USA
great article but What if I don't know the total number of leave codes ?

Ex -
Emp_name  Leave_Code Leave_bal
ABC       L1         10
ABC       L2         20
ABC       ..         ..
ABC       ..         ..
ABC       Ln         x

Is it possible to use sql query to get the results ?

          Followup:

you would need to run two queries

query 1 -- to get the domain of leval codes so you can dynamically build
query 2 -- to pivot

(since SQL sort of DEMANDS to know the number and types of columns in a query
when it parses)

Or you can use collections and return an "array"
Or you can use CURSOR() variables and return result sets with each row.

      Rating: 4
Combining rows
        July 12, 2003
Reviewer:  Ruhul Amin
       from Dhaka, Bangladesh
Great. If I don't know the total number of leave codes then you suggested to run
two queries.
query 1 -- to get the domain of leval codes so you can dynamically build
query 2 -- to pivot

More details pls. I meen an example pls.

          Followup:

do you have access to my book "Expert one on one Oracle"?

      Rating: 3
Combining rows
      of two table as columns  July 16, 2003
Reviewer:
      Arun  from Chennai,India
Mr Tom

Great,but i need one more clear cut examples pls

then can u get me the access

to ur book Expert one on one  Oracle

Regards

Arun.

      Rating: 5
Silly people
      wasting your time...  July 16, 2003
Reviewer:
      sidharth  from Bangalore, India
Dear Tom,

Yesterday I posted a question on this forum and got a reply same day even though
you have said you will be on road. Thank you for your prompt reply.

I hate to see people ask you "Tom, Can you please tell me where to get access to
your book? - Its on the WEB Page and its available in India - I got a copy some
3 months back.

Arun, Please do some scouting around of your own and you can get the book. Its
available in Bangalore anyway so it should be available in Chennai also.

Let us not keep Tom busy by asking how to find his book. He already has done a
marvellous job of writing it.

I really like your book- but only reached half of it.

Way to go...Tom,

Also when can I ask you a new question? Can you not have a team which will sift
through the questions and handle the trivial ones while only those needing your
exceptional expertise come to you? - So that you are more accessible, kind of?

July 13, 2003 Alex -- Thanks for the question regarding
      "Database trigger", version 8.1.7

      You Asked
Hi Tom,
hope you can help me out. The problem I have is this: I need to turn the SQL
trace on for a specific user. In order to do so I've created a database trigger,
which turns tracing on if the user is SADMIN. But, when SADMIN tries to login,
it gets an error message back. Without this trigger SADMIN can login and execute
the commands from SQLPLUS. I've also tried to change "ALTER SESSION" to "EXEC
DBMS_SESSION.SET_SQL_TRACE(TRUE)" and got the same error. Would you have any
idea why? The following is a trigger code and error message:

CREATE OR REPLACE TRIGGER sadmin_logon_trg
AFTER LOGON ON DATABASE
BEGIN
IF (user = 'SADMIN') THEN
EXECUTE IMMEDIATE 'ALTER SESSION SET SQL_TRACE = TRUE';
END IF;
END;

Trigger created.

connect sadmin/sadmin@sbldev1
ERROR:
ORA-00604: error occurred at recursive SQL level 1
ORA-01031: insufficient privileges
ORA-06512: at line 3

drop trigger sadmin_logon_trg;

Trigger dropped.

connect sadmin/sadmin@sbldev1
connected.

alter session set sql_trace=true;

Session altered.

Thanks,
I appreciate your help.

      and we said...
grant ALTER SESSION to <owner of trigger>

read:

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html
to see why (make sure its to the OWNER of the trigger)
Review &
      Followup

      Rating:
      5
  June 04, 2002
Reviewer:  Alex
       from Chicago, IL USA
Thanks Tom,
your response is quick and right to the point.

      Rating: 5
Oracle
      7  July 12, 2003
Reviewer:  atul  from
      India
Hi,

can this work for oracle 7..

Thanks,
atul

          Followup:

these triggers did not exist in v7, no

      Rating: 1
Oracle
      7  July 13, 2003
Reviewer:  A reader

If i want to create trigger in oracle 7 like what onlogon trigger do in oracle
8.x

          Followup:

you would have to perform what is known in the industry as an "upgrade", the
feature -- event triggers -- quite simply does not exist in that release.

July 14, 2003 kamini -- Thanks for the question regarding
      "Rownum", version 8.1.7

      You Asked
Hi Tom

I have 2 questions

1. I have a table called a where I have more than one row.  When I put a query
"SELECT * FROM A WHERE ROWNUM=1" it gives me the first row.  But if I put a
query specifying any number other than 1 for e.g. "SELECT * FROM A WHERE
ROWNUM=2" it is not returning any rows.  Why?  But at the same time if I use >
or < it returns row.   Could u please clarify me?

2. Can we have a static type variable in PL/SQL like what we have in VB, VC etc.

Thanx

      and we said...
1) rownum is assigned to rows AS THEY SATISFY the predicate.

the logic would be:

   rownum = 1
   for x in ( select * from A )
   loop
       if ( x satisifies the predicate )
       then
             OUTPUT the row
             rownum = rownum + 1
       end if;
   end loop;

in the case of where rownum = 1, the first row passes the test, is output and
rownum goes to 2.  No other row ever satisfies the predicate and rownum stays at
2 for the rest of the query.

in the case of where rownum = 2, the first row is rownum 1, it fails.  The next
row is ALSO rownum = 1 and likewise fails. And so on.  There can be NO row 2 if
there is not a row 1.

Btw: it does not return the "FIRST" row -- it returns A row.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:912210644860
that deals with the "last"

2)

declare
    x CONSTANT number default 5;
begin
    null;
end;
/
Review &
      Followup

      Rating:
      5
Rownum  July 11, 2001
Reviewer:  Helena
      Markova  from Bratislava, Slovakia
It's a very good explanation.

      Rating:
      4
  September 25, 2001
Reviewer:
      A reader
shouldnt you example better be like

v_rownum = 1
for x in ( select rownum, a.* from A )
loop
       if ( x.rownum = v_rownum )
       then
             OUTPUT the row
             v_rownum = v_rownum + 1
       end if;
end loop;

          Followup:

No, no it shouldn't.

ROWNUM is a psuedo column -- its value is assigned as rows are output.  Using
your example -- each row, regardless of whether it was output, has a "rownum".
I was trying to show that rownum -- THE ROWNUM -- is incremented only AFTER the
row is output.  Your example would imply each and every row in the base tables
would have a rownum assigned to it.

      Rating:
      4
  September 25, 2001
Reviewer:
      A reader
Hi

What do you mean by the predicate, what would it be in your example?

          Followup:

Predicate is the where clause.  In my example, the predicate is:

where rownum = 1

My example could have been:

 rownum = 1
   for x in ( select * from A )
   loop
       if ( rownum = 1 /* same as x satisfies the predicate */ )
       then
             OUTPUT the row
             rownum = rownum + 1
       end if;
   end loop;

      Rating: 4
"static"
      variable  September 25, 2001
Reviewer:  Tom
      Best  from King of Prussia, PA
I think what Kamini was asking in question 2, was for a C-like "static"
variable, meaning it maintains its value from call to call.  That would mean
declare it in the package spec, right?  Will it maintain it's value from call to
call then?

          Followup:

I posted psuedo code (not real plsql) -- there is no need for a static variable
-- in fact a static variable would mess it up (rownum needs to be reset for each
query).

      Rating: 3
Grabbing
      "slices" of result sets  September 25, 2001

Reviewer:  Daryl Biberdorf  from Carrollton, TX USA

Grabbing rows from the middle of a select (e.g., a slice) is something I've had
to do with web-based apps in the past to allow the user to move through web
pages of results ("Items 21-30 of 52").

Perhaps there is a better way, but one way to accomplish the original poster's
question is with a query like:

SELECT * FROM
    (SELECT * FROM a_table WHERE ... AND rownum <= 2)
WHERE ROWNUM=2

We've used the following to fetch a given "slice":
SELECT * FROM
    (SELECT * FROM a_table WHERE ... AND ROWNUM < 100)
WHERE ROWNUM > 90

It seems expensive, perhaps, but requires a lot less programming overhead than,
say, using temp tables to hold results. The upper and lower limit for the row
numbers can be passed easily from web browser to server application. Some
empirical testing showed it performed no worse than other, more complex,
approaches we tried.

          Followup:

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064
for how to do this.  The above example is close -- but won't work as is (the
rownum = 2 and rownum > 90 will NEVER be satisfied -- you need to select
rownum in the subquery, alias it, and reference that in the outer query)....

      Rating: 3
simpler
      query  September 25, 2001
Reviewer:
      A reader
select * from (
   select inner.*, rownum rn from (query) inner
) where rn between 100 and 200

          Followup:

simplier, but infinitely less efficient.  Consider a table:

create table t as select * from all_objects;
alter table t add constraint t_pk primary key(object_id);

(my all objects is pretty big -- about 20,000 rows).... Now, using SQL_TRACE and
TKPROF I can see:

select *
  from ( select inner.*, rownum rn
           from ( select object_id, object_name
                    from t
                   order by object_id ) inner
           where rownum <= :high
       )
 where rn >= :low

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        8      0.00       0.01          0        140          0         101
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       10      0.01       0.01          0        140          0         101

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 36

Rows     Row Source Operation
-------  ---------------------------------------------------
    101  VIEW
    200   COUNT STOPKEY
    200    VIEW
    200     TABLE ACCESS BY INDEX ROWID T
    200      INDEX FULL SCAN (object id 18584)

select *
  from ( select inner.*, rownum rn
           from ( select object_id, object_name
                    from t
                   order by object_id ) inner
       )
 where rn between :low and :high

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        8      0.35       0.34          0      16004          0         101
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       10      0.36       0.34          0      16004          0         101

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 36

Rows     Row Source Operation
-------  ---------------------------------------------------
    101  VIEW
  16865   COUNT
  16865    VIEW
  16865     TABLE ACCESS BY INDEX ROWID T
  16866      INDEX FULL SCAN (object id 18584)

(i used low and high of 100, 200)

You see, in my "more complex" version, we can push a predicate down into the
inner query.  Using the simpler one, we cannot.  So, the more complex works much
faster then the easier one for that reason.

I recommend my approach of the "where rownum <= :max ) where rn >= :min" for
definite performance gains.

      Rating: 2
rownum
      expression is not a predicate  September 25, 2001

Reviewer:  Mikito Harakiri
Predicates obey laws like this:

select where A or B

is identical to

select where A
union
select where B

Now try 'rownum = 1' instead of A and
'rownum = 2' instead of B, and note that this identity and many many others
dosn't hold.

In short, use of rownum gives unpredictable (or to put it mildly -
unintuitive:-) results. Especially, for complex queries with rownum hidden
somewhere in the middle subquery.

          Followup:

Well, first of all -- select where a or b is NOT identical to select where a
union select where b.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( a int, b int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1, 1 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1, 1 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where a = 1 or b = 1;

         A          B
---------- ----------
         1          1
         1          1

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where a = 1
  2  union
  3  select * from t where b = 1;

         A          B
---------- ----------
         1          1

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where a = 1
  2  union all
  3  select * from t where b = 1;

         A          B
---------- ----------
         1          1
         1          1
         1          1
         1          1

That shows that neither of union nor union all are the "same" as or.  So, your
basic premise is flawed.

In short, the use of rownum -- which is a PSUEDO COLUMN assigned during output,
not a static value in a table -- is a feature which you can choose to understand
or not -- to use or not.

Once you understand how it is assigned, using it can solve some very nasty
problems.

It is like understanding why:

select * from t where x NOT IN ( select y from t2 )

is totally different (gives a different answer) then

select * from t where NOT EXISTS ( select y from t2 where t2.y = t.x )

as for "Especially, for complex queries with rownum hidden
somewhere in the middle subquery" - I differ.  It is these queries where rownum
actually becomes a STATIC value, has totally predicable values.  For example:

scott@ORA817DEV.US.ORACLE.COM> select *
  2  from ( select ename, rownum r from emp )
  3  where r between 5 and 7
  4  /

ENAME               R
---------- ----------
MARTIN              5
BLAKE               6
CLARK               7

Now, r = value, r > value -- those all work "intuitively".  The only trick here
is to assign rownum after any ordering you might want to do.  To get the 3rd
highest paid person you might:

scott@ORA817DEV.US.ORACLE.COM> select ename, sal, r
  2  from ( select ename, sal, rownum r
  3          from ( select * from emp order by sal desc NULLS LAST )
  4       )
  5  where r = 3;

ENAME             SAL          R
---------- ---------- ----------
FORD             3000          3

      Rating: 5
Not In/ Not
      Exists In/Exists  September 26, 2001
Reviewer:
      J  from Ma
You said:
"It is like understanding why:

select * from t where x NOT IN ( select y from t2 )

is totally different (gives a different answer) then

select * from t where NOT EXISTS ( select y from t2 where t2.y = t.x )"

I've read otherwise, (haven't read yours, yet :( )

Could you please explain in a little more detail?

Thanks
btw I get the rownum

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:442029737684
...

      Rating: 3
rownum
      expression is not a predicate   September 26, 2001

Reviewer:  Mikito Harakiri
Hmm... I'm surprised that the optimiser was able to push down a predicate in
more complex query as compared to simpler 'between'. Is this just an Oracle
artifact or there is a reason behind?

My basic premise is valid if we put 'distinct' around all 'selects' to avoid
nasty bag semantics. Besides, many people find counterintuitive the fact that

select * from emp order by ename

and filtering first 10 records on the client is different from

select * from emp where rownum <10 order by ename

Correct, after some trial and error they get used to nested selects.

          Followup:

Well actually the "we" i referred to was us collectively.  "We" pushed the
predicate down -- Oracle could not.  You see, if Oracle DID push the predicate
down -- it would change the ANSWER.  It would have to push the

where rn between :l and :h

and if it did that -- the result would be ZERO rows.  We (we being the humans)
can figure out that we can push the "<= :h" into the inner query but we HAVE to
leave the ">= :l" in the outer query.

I disagree with the "distinct" and "nasty bag semantics" -- the point is UNION
is not OR, not close.

I do not understand why it is counter intuitive myself (i agree -- people get
confused by it).  Maybe its just understanding SQL better.

Think about it like this:

select * from emp where ename like 'A%' order by ename;

would you expect that to ORDER everything and then find all of the 'A' records
or would you expect it to find the 'A' records and then sort?

Just substitute in rownum < 10 for ename like 'A%'.  Now, you would expect it to
find the first ten records and then sort them....

      Rating: 5
you drive us
      crazy by using these small tricks  September 26, 2001

Reviewer:  A reader
select ename, sal, r
    from ( select ename, sal, rownum r
            from ( select * from emp order by sal desc NULLS LAST)
         )
    where r = 1

what does nulls last mean,  is this a part of the syntax for select clause.

      Rating: 5
Here goes the
      answer for the above comment  September 26, 2001

Reviewer:  Nag  from USA
order_by_clause
 orders rows returned by the statement. Without an order_by_clause, no guarantee
exists that the same query executed more than once will retrieve rows in the
same order. For a discussion of ordering query results, see "Sorting Query
Results".

expr orders rows based on their value for expr. The expression is based on
columns in the select list or columns in the tables, views, or materialized
views in the FROM clause.

position orders rows based on their value for the expression in this position of
the select list; position must be an integer.

 You can specify multiple expressions in the order_by_clause. Oracle first sorts
rows based on their values for the first expression. Rows with the same value
for the first expression are then sorted based on their values for the second
expression, and so on. Oracle sorts nulls following all others in ascending
order and preceding all others in descending order.

 ASC | DESC
 specifies the ordering sequence. ASC is the default.

 NULLS FIRST | NULLS LAST
 specifies whether returned rows containing null values should appear first or
last in the ordering sequence.

NULLS LAST is the default for ascending order, and NULLS FIRST is the default
for descending order.

      Rating:
      5
Ummm  September 26, 2001
Reviewer:
      A reader  from USA
select ename, sal, r
    from ( select ename, sal, rownum r
            from ( select * from emp order by sal desc NULLS FIRST)
         )
    where r = 3

I changed the first to last , and it made no difference, I expected to see no
results , but it return row 3.

Why so.

          Followup:

that just means there were no nulls in your SAL column thats all.

SAL is nullable.  If you order by sal DESC, it would put the nulls first by
default.  Lets change the query to COMM (which does have nulls)

scott@ORA817DEV.US.ORACLE.COM> select ename, comm, r
  2      from ( select ename, comm, rownum r
  3              from ( select * from emp order by comm desc NULLS FIRST)
  4           )
  5      where r = 3
  6  /

ENAME            COMM          R
---------- ---------- ----------
CLARK                          3

scott@ORA817DEV.US.ORACLE.COM> select ename, comm, r
  2      from ( select ename, comm, rownum r
  3              from ( select * from emp order by comm desc NULLS LAST)
  4           )
  5      where r = 3
  6  /

ENAME            COMM          R
---------- ---------- ----------
ALLEN             300          3

      Rating: 4
Does ROWNUM
      forces FTS?  September 27, 2001
Reviewer:
      A reader
Does the use of rownum force Oracle to do a full table scan? What I understand
how rownum works is this, for example

select ename from emp
where rownum < 3;

it firsts do select * from emp then from this result set it assigns the rownum
with a set - test fashion

for example

the first row of result set would be SMITH so Oracle assigns rownum 1 to this
row, it then tests this value to "where rownum < 3" since rownum 1 is less than
2 then this row pass the test, then do the same thing for the second row and
third, the third fells because 3 is not smaller than 3. But in order to do these
operations it needs a result set from FTS no?

Or it simply gets one row per fetch and assigns rownum then test for condition
"rownum < 3" (fetch one row set & test)? Rather than get everything then set and
test (fetch all set & test)

          Followup:

Yes and No.

Your query might say "full scan" -- but it will also say maybe "COUNT STOPKEY"
-- meaning when it has hit the end of the rownums you are looking for, it stops.
 That is why in my example above, the reply to the comment "simpler query", my
query works much faster, does much less work then the "simpler query".

The rownum caused the query to STOP after it found the max row.  We just then
weeded out the rows below the min row and returned.

So, it might full scan -- but it will STOP scanning when its found the answer.

      Rating:
      4
  September 27, 2001
Reviewer:
      A reader
So

Which one is more correct

fetch one row then set & test
or
fetch all then set & test

          Followup:

sorry -- does not compute...  don't understand your point.

If you mean rownum -- it tries to whenever possible:

   set rownum to 1
   start fetching
       test
       if successful
          output
          increment rownum
          if rownum prevents ANY more rows from being considered -- stop
              processing

      Rating:
      4
  September 27, 2001
Reviewer:
      A reader
Hi

What I mean is if the query first fetch all rows from the query then assign
rownum individually or the query fetch one row then assign rownum then fetch
another row and assign rownum and so on

option 1
for example table emp has 14 rows

select empno from emp
where rownum < 3

this would return 2 rows, what I am wondering if Oracle first select all 14 rows
into data buffer (result set) then from this result set it fetch one row then
assign rownum, test for predicate, satisfies then fetch another row if this was
the case we would get a full table scan because of first part of the query

option 2

for example table emp has 14 rows

select empno from emp
where rownum < 3

Instead of selecting all 14 rows into data buffer Oracle fetch one row into data
buffer, assign the rownum test for the predicate then fetch another row into
data buffer test again and so on for just 2 rows (since rownum < 3) so in this
case we dont do FTS

Which option is Oracle using?

          Followup:

It will fetch row, assign rownum, output row.

It will NOT full scan the entire table -- it'll stop.

This is actually demonstrated in the above queries -- the "complex" query STOPS
reading the table when it hits the rownum we used.  The "simple" query does not
due to the way it was written.

When you see the COUNT STOPKEY -- that indicates we STOP when we hit N rows.

It DOES a FTS in any case, it just STOPS doing the FTS when it found the last
row you want.

Perhaps this makes it more clear:

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

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly statistics

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where rownum < 100;

99 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
         13  consistent gets
          3  physical reads
          0  redo size
      13752  bytes sent via SQL*Net to client
       1096  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         99  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where rownum < 1000;

999 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
         83  consistent gets
         18  physical reads
          0  redo size
     130234  bytes sent via SQL*Net to client
       7756  bytes received via SQL*Net from client
         68  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        999  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where rownum < 10000;

9999 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
        794  consistent gets
        172  physical reads
          0  redo size
    1161126  bytes sent via SQL*Net to client
      74356  bytes received via SQL*Net from client
        668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       9999  rows processed

See how the consistent gets go up and up and up as we get more and more data.
We must be full scanning (no indexes) but we read less data as we need less
rows.

      Rating:
      5
Reader  September 28, 2001
Reviewer:
      Reader  from USA
Excellent mapping of FTS to statistics (consistent get)
deriving the inference

      Rating: 5
why consistent
      block gets increases?  September 28, 2001

Reviewer:  A reader
hi

why db block gets increse so little and consistent gets so much? are we reading
from RBS?

          Followup:

No, in this case we are not reading from the RBS.

DB BLOCK GETS are gets in "current" mode.  These do not change in this example
from query to query because the only blocks we need in "current" mode are those
from the segment header that tell us how to full scan the table.  The
db_block_gets are a CONSTANT in this query -- regardless of the number of rows
we fetch.

The consistent gets are the actual database blocks with our data on them. the
more rows we fetch, the more we read.

      Rating: 5
I
      guess  September 29, 2001
Reviewer:  Wells
       from USA
Tom

I guess, that the db block gets are consistent 12 because it is only a select,
if it had been an update or delete , then the db block gets would have been more
, Iam I right.

Iam I right?

          Followup:

Absolutely:

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly statistics;
ops$tkyte@ORA817DEV.US.ORACLE.COM> update t set owner = owner where rownum <
100;

99 rows updated.

Statistics
----------------------------------------------------------
         73  recursive calls
        115  db block gets
         35  consistent gets
          0  physical reads
      24432  redo size
       1145  bytes sent via SQL*Net to client
        870  bytes received via SQL*Net from client
          6  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
         99  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> update t set owner = owner where rownum <
1000;

999 rows updated.

Statistics
----------------------------------------------------------
          0  recursive calls
       1033  db block gets
         19  consistent gets
          0  physical reads
     244416  redo size
       1007  bytes sent via SQL*Net to client
        827  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
        999  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> update t set owner = owner where rownum <
10000;

9999 rows updated.

Statistics
----------------------------------------------------------
          0  recursive calls
      10245  db block gets
        140  consistent gets
          0  physical reads
    2456648  redo size
       1008  bytes sent via SQL*Net to client
        828  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
       9999  rows processed

where t was "create table t as select * from all_objects" and my all_objects has
over 10k rows...

      Rating: 3
Once Again
      Tom  September 29, 2001
Reviewer:  Wells

IN

Statistics
----------------------------------------------------------
          0  recursive calls
      10245  db block gets
        140  consistent gets
          0  physical reads
    2456648  redo size
       1008  bytes sent via SQL*Net to client
        828  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
       9999  rows processed

Here we are doing 140 consistent reads , at the read stage to see the location
of the blocks ( or is it rows) where in lie the rows which we need to update,
and then

we are doing 10245  db block gets or current gets i.e. as they exist at that
moment, and updating them , Iam I right.

1.Does this mean that our 9999 rows are located in 140 blocks.

2.Can you explain the difference 246 between db block gets and  number of rows.

Are these 246 db block gets for segment header reads .

          Followup:

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:878847787577
it goes into this with a delete.  Yes -- we do a consistent read of 140 blocks
to find 9999 rows and then do a db block get FOR EACH ROW to do the delete.  The
246 blocks extra are rollback segments/header blocks and such.

      Rating: 3
Tom, what about
      the recursive calls  September 30, 2001
Reviewer:
      Wells
How about the recursive calls

when you are updating 99 rows  there are 73 recursive calls , and when you are
updating 9999 rows the number of recursive calls are none, can you explain this

and actually how many levels of recursive levels are there, and what are they..

Thank you very much

          Followup:

You are seeing the effects of the data dictionary cache getting warmed up.  I
had never updated T before so it had to figure lots of stuff out like

- what is T (structurally)
- am I allowed to update T
- any triggers on T
- what about constraints
- etc etc etc

So, it ran lots of sql on behalf of my sql (recursive sql that is called) in
order to discover all of this.  On the subsequent queries -- this data was in
the dictionary cache in the SGA and it did not need to run that recursive sql to
find it.

      Rating: 5
How about
        September 30, 2001
Reviewer:
      A reader
((and actually how many levels of recursive levels are there, and what are
they))

can high amount of recursive sql also result from some bad database settings.

          Followup:

Sure -- you could have high recursive sql calls due to using a dictionary
managed permanent tablespace for TEMP (you should use a true temporary
tablespace).  The recursive sql in this case is due to the space management.

It could be due to you having too small of a shared pool -- not enough room to
cache everything.

On the other hand -- all SQL in PLSQL (all of YOUR sql) is recursive SQL, so it
is not all evil.

      Rating: 5
Another Rownum
      question  June 30, 2003
Reviewer:  TH
       from Manama, BAH
Dear Tom,

I have a case like this.

Table ITEMS with Fields MAIN, SUB & CODE
--------------------------------------------

MAIN     SUB     CODE
=======================
01       DXFDF    1
01       SDYHJ    2
01       SDFHJ    3
01       EWEHJ    4

02       TYTYY    1
02       TYTY     2
02       IGHGH    3

03       BHGHG    1
03       SDFHH    2

The column "CODE" is curently empty. But it should come like above. What can I
do?

Is it possible to do it thro' a single update statement? Or what is the best
solution?

Thanx

          Followup:

just

create view v
as
select main, sub, row_number() over (partition by main order by sub) code
  from t;

that'll dynamically assign code in the view.  If you want to "persist" this
data, you'll need to (psuedo code, please work out the details)

is
   cursor c is select rowid rid, code from v;
begin
   open c;
   loop
       fetch c bulk collect into l_rids, l_codes LIMIT 500;
       forall i in 1 .. l_rids.count
            update t set code = l_codes(i) where rowid = l_rids(i);
       exit when c%notfound;
   end loop
   close c;
....

      Rating:
      4
  July 14, 2003
Reviewer:
      A reader  from Virginia, USA
I understand how rownum is assigned and how one can update based on rownum.
Here's an example you used:

update t set owner = owner where rownum < 100;

Updates the first 100 rows.  But what if I wanted to Update the last 100 rows?

          Followup:

that doesn't update "the first 99 rows" as there isn't such a concept.

that updates 99 rows.  Not the first 99.

there is no such thing as "the first 99", "the last 99"

there are just rows.

You would have to SORT the data in order to have a "first" and "last".
something like:

update t
   set ...
 where rowid in ( select rid
                    from (select rowid rid from t order by X)
                   where rownum < 100 );

July
      15, 2003 PRS -- Thanks for the question
      regarding "IMPLICIT CURSOR ATTRIBUTE SQL%NOTFOUND NOT WORKING", version
      8.1.6

      You Asked
Hi Tom,
   I am using implicit cursor attributes SQL%NOTFOUND for the below
   PL/SQL block. But it raises NO_DATA_FOUND exception. It works fine
   for update and delete statement as they are not going to raise the
   NO_DATA_FOUND exception.

   Example.

declare
    l_table_name varchar2(30);
begin
    select table_name
      into l_table_name
     from  dba_tables
    where  table_name = 'XXXX';
    if sql%notfound
    then
       dbms_output.put_line('Table not found');
    else
       dbms_output.put_line('Table found');
    end if;
end;
/

Error:
SQL> /
declare
*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at line 4

    I am handling the exception SQL%NOTFOUND. But it raise NO_DATA_FOUND
exception. I can write the begin, exception, end block
to handle the exception. But why this implicit cursor attributes does not work
for SELECT statement? ORACLE manuals says it should work. ORACLE manual for
PL/SQL developer's guide says that. Can you please shed some light on this?

    Your help is appreciated.

Thanks,
PRS

      and we said...
it is working exactly as designed and documented.

A select into does one of three things:

o returns a single row successfully.
o throws TOO_MANY_ROWS if you get more than one row back
o throws NO_DATA_FOUND if you get less than one row back.

See -- it "works" for select into:

ps$tkyte@ORA920> declare
  2          data varchar2(5);
  3  begin
  4          select dummy into data from dual where 1=0;
  5
  6          if ( sql%found ) then
  7                  dbms_output.put_line( '1) Found!!' );
  8          elsif ( sql%notfound ) then
  9                  dbms_output.put_line( '1) Not Found!!' );
 10          end if;
 11  exception
 12          when no_data_found then
 13                  if ( sql%found ) then
 14                          dbms_output.put_line( '2) Found!!' );
 15                  elsif ( sql%notfound ) then
 16                          dbms_output.put_line( '2) Not Found!!' );
 17                  end if;
 18  end;
 19  /
2) Not Found!!

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          data varchar2(5);
  3  begin
  4          select dummy into data from dual where 1=1;
  5
  6          if ( sql%found ) then
  7                  dbms_output.put_line( '1) Found!!' );
  8          elsif ( sql%notfound ) then
  9                  dbms_output.put_line( '1) Not Found!!' );
 10          end if;
 11  exception
 12          when no_data_found then
 13                  if ( sql%found ) then
 14                          dbms_output.put_line( '2) Found!!' );
 15                  elsif ( sql%notfound ) then
 16                          dbms_output.put_line( '2) Not Found!!' );
 17                  end if;
 18  end;
 19  /
1) Found!!

PL/SQL procedure successfully completed.

you can "use it" but select into will ALWAYS through those exceptions -- as they
are in fact ERRORS.

<quote where=just a page or so down from where you stopped reading>

If a SELECT INTO statement fails to return a row, PL/SQL raises the predefined
exception NO_DATA_FOUND whether you check %NOTFOUND on the next line or not.
Consider the following example:

BEGIN ... SELECT sal INTO my_sal FROM emp WHERE empno = my_empno; -- might raise
NO_DATA_FOUND IF SQL%NOTFOUND THEN -- condition tested only when false ... --
this action is never taken END IF;

The check is useless because the IF condition is tested only when %NOTFOUND is
false. When PL/SQL raises NO_DATA_FOUND, normal execution stops and control
transfers to the exception-handling part of the block.
</quote>
Review & Followup

      Rating:
      3
  February 22, 2003
Reviewer:
      A reader

      Rating: 5
handle data not
      found without using exception  July 14, 2003

Reviewer:  Sean  from NJ, USA
Hi Tom,

We want to check the existence of the record in a table.  If it exists, we do
something and if it does not exist, we do something else.

If we use

select empno into v_empno from emp where ename = ‘JOHN’;

we have to use exception to handle the transaction if the record does not exist.
 Since it is a normal situation even the records does not exist, instead we use

select count(empno) into v_count from emp
        where ename = 'JOHN;

It works fine except that sometimes it takes time to count if there are a lot of
records.  My question is how to check the existence of record and do some
transaction if the record does not exist without using exception or count.  Here
is our sample code:

create or replace procedure p1
is

v_count number;

begin
    -- we want to replace count
    select count(empno) into v_count from emp
        where ename = 'JOHN';

    -- Real transsaction is more complicated than this sample code.
    if v_count > 0
    then dbms_output.put_line('JOHN exists');
    else dbms_output.put_line('JOHN does not exist') ;
    end if;

end;
/

Thanks so much for your help.

Sean

          Followup:

select count(*) into l_cnt
  from dual
 where EXISTS ( select null from emp where ename = 'JOHN' );

will return 0 if no rows exist, 1 if at least one row does.

      Rating:
      5
  October 07, 2003
Reviewer:
      A reader

July 15, 2003 Tom -- Thanks for the question regarding "Using
      "Oracle Text" CONTAINS syntax on multiple columns", version 8.1.7

      You Asked
We have a DB where users are able to search for text, using Oracle Text (I guess
it's Intermedia in 8.1.7).  Anyway, there are 3 columns that are indexed.  Each
of them is in a separate table.  The user has a single field to type in their
search criteria.  It's great that the entire set of CONTAINS syntax is available
to the users... with the '&" and '|' and '~' and all that (I just point them to
the Intermedia docs).  But, I realize now that this is not really giving them
the functionality they think it is.  When I pass through this WHERE clause
(assume all the join conditions are there):

where contains(t1.varchar2_col1,'this & that',10) > 1
or contains(t2.blob_col1,'this & that',20) > 1
or contains(t3.blob_col2,'this & that',30) > 1

I realize I'm requiring "this" and "that" to occur together in at least one of
the columns.  What the users want is this condition ACROSS THE CONCATENATION OF
ALL 3 COLUMNS.  I.E. if "this" is in one of the columns and "that" is in one of
the others, that result should be returned (as well as the case where both
strings are indeed in a single column, of course).

Is there a way to get Intermedia to index in this fashion?  I don't want to
parse the contains syntax, because that's hard, and I'd have to keep up with it
as new releases come out.

      and we said...
you want a single index across all three columns/tables.  We can do that.
Consider:

ops$tkyte@ORA817DEV> create table t1 ( id int primary key, varchar2_col
varchar2(80) );

Table created.

ops$tkyte@ORA817DEV> create table t2 ( id int primary key, clob_col1    clob );

Table created.

ops$tkyte@ORA817DEV> create table t3 ( id int primary key, clob_col2    clob );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert into t1 values ( 1, 'This' );

1 row created.

ops$tkyte@ORA817DEV> insert into t2 values ( 1, 'That' );

1 row created.

ops$tkyte@ORA817DEV> insert into t3 values ( 1, 'and the other thing' );

1 row created.

ops$tkyte@ORA817DEV> commit;

Commit complete.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> grant select on t1 to ctxsys;

Grant succeeded.

ops$tkyte@ORA817DEV> grant select on t2 to ctxsys;

Grant succeeded.

ops$tkyte@ORA817DEV> grant select on t3 to ctxsys;

Grant succeeded.

ops$tkyte@ORA817DEV> pause

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> @su ctxsys
ops$tkyte@ORA817DEV> set termout off
ctxsys@ORA817DEV> get afiedt.buf nolist
ctxsys@ORA817DEV> set termout on
ctxsys@ORA817DEV>
ctxsys@ORA817DEV> create or replace procedure glue_them_together( p_id in rowid,
p_lob in out clob )
  2  as
  3  begin
  4          for x in ( select varchar2_col, clob_col1, clob_col2
  5                                   from ops$tkyte.t1, ops$tkyte.t2,
ops$tkyte.t3
  6                                  where t1.rowid = p_id
  7                                    and t2.id(+) = t1.id
  8                                    and t3.id(+) = t1.id )
  9          loop
 10                  dbms_lob.writeAppend( p_lob, length(x.varchar2_col)+1,
x.varchar2_col||' ');
 11                  dbms_lob.append( p_lob, x.clob_col1 );
 12                  dbms_lob.writeAppend( p_lob, 1, ' ' );
 13                  dbms_lob.append( p_lob, x.clob_col2 );
 14          end loop;
 15  end;
 16  /

Procedure created.

ctxsys@ORA817DEV> grant execute on glue_them_together to ops$tkyte;

Grant succeeded.

ctxsys@ORA817DEV> pause

ctxsys@ORA817DEV>
ctxsys@ORA817DEV> @connect /
ctxsys@ORA817DEV> set termout off
ops$tkyte@ORA817DEV> REM GET afiedt.buf NOLIST
ops$tkyte@ORA817DEV> set termout on
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> exec ctx_ddl.drop_preference('foo_user_datastore');

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> exec ctx_ddl.drop_preference('my_lexer');

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> begin
  2     ctx_ddl.create_preference( 'foo_user_datastore', 'user_datastore' );
  3     ctx_ddl.set_attribute( 'foo_user_datastore', 'procedure',
'glue_them_together' );
  4  end;
  5  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> begin
  2     ctx_ddl.create_preference( 'my_lexer', 'BASIC_LEXER' );
  3     ctx_ddl.set_attribute( 'my_lexer', 'base_letter', 'YES');
  4     ctx_ddl.set_attribute( 'my_lexer', 'mixed_case', 'YES' );
  5  end;
  6  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create index t1_idx on t1(varchar2_col)
  2  indextype is ctxsys.context
  3  parameters( 'datastore foo_user_datastore lexer my_lexer');

Index created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> set define off
ops$tkyte@ORA817DEV> column varchar2_col format a20
ops$tkyte@ORA817DEV> column clob_col1 format a20
ops$tkyte@ORA817DEV> column clob_col2 format a20
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select     varchar2_col, clob_col1, clob_col2
  2   from t1, t2, t3
  3   where t1.id = t2.id
  4     and t1.id = t3.id
  5     and contains( varchar2_col, 'this & that & thing' ) > 0;

VARCHAR2_COL         CLOB_COL1            CLOB_COL2
-------------------- -------------------- --------------------
This                 That                 and the other thing

ops$tkyte@ORA817DEV> set define on

Now, you only have one index that spans all three.

now for the tricky part!

this index will only recognize changes when T1's row is updated.  Hence a
dbms_lob.write to T2 or T3 will not cause the index to refresh itself.  You'll
need to look at the code that modifies T2 and T3's lob columns and see if you
cannot add a "tickle" update to T1 -- so when you dbms_lob.write them -- you
update T1 as well and when you commit, interMedia can see the changes.

Normally, we can do this via a triggger, but since triggers do not fire up
modification of a lob's value.... that won't work here.

Review &
      Followup

      Rating: 4
different
      result :-(  April 28, 2003
Reviewer:  Mirko

This was very helpfull, thanks. I have only one problem. The created index
doesn't work. With the commands from your example, the last select returns no
rows.
This is all what i see:

VARCHAR2_COL         CLOB_COL1            CLOB_COL2
-------------------- -------------------- --------------------

What i'm doing wrong?

          Followup:

if it returned column headings -- it would return data?  sqlplus doesn't print
them out with no data found.

Please elaborate (cut and paste maybe the whole thing)....

I was using sqlplus 8174 against 8174 -- what about you.

      Rating: 4
Forgot the
      version  April 28, 2003
Reviewer:  Mirko

My oracle version is 9.2.0.1.0.
Is this part of the problem?

          Followup:

ops$tkyte@ORA920> select           varchar2_col, clob_col1, clob_col2
  2   from t1, t2, t3
  3   where t1.id = t2.id
  4     and t1.id = t3.id
  5     and contains( varchar2_col, 'this & that & thing' ) > 0;

VARCHAR2_COL         CLOB_COL1            CLOB_COL2
-------------------- -------------------- --------------------
This                 That                 and the other thing

ops$tkyte@ORA920>

ops$tkyte@ORA920> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
PL/SQL Release 9.2.0.3.0 - Production
CORE    9.2.0.3.0       Production
TNS for Linux: Version 9.2.0.3.0 - Production
NLSRTL Version 9.2.0.3.0 - Production

      Rating: 4
really no
      rows  April 29, 2003
Reviewer:  Mirko

This is my output from PL/SQL Developer:

Connected to Oracle9i Enterprise Edition Release 9.2.0.1.0
Connected as obr

SQL>
SQL> drop table t1;

Table dropped

SQL> drop table t2;

Table dropped

SQL> drop table t3;

Table dropped

SQL> create table t1 ( id int primary key, varchar2_col varchar2(80) );

Table created

SQL> create table t2 ( id int primary key, clob_col1    clob );

Table created

SQL> create table t3 ( id int primary key, clob_col2    clob );

Table created

SQL> insert into t1 values ( 1, 'This' );

1 row inserted

SQL> insert into t2 values ( 1, 'That' );

1 row inserted

SQL> insert into t3 values ( 1, 'and the other thing' );

1 row inserted

SQL> commit;

Commit complete

SQL> grant select on t1 to ctxsys;

Grant succeeded

SQL> grant select on t2 to ctxsys;

Grant succeeded

SQL> grant select on t3 to ctxsys;

Grant succeeded

SQL> pause
SQL> connect ctxsys/ctxsys
Connected to Oracle9i Enterprise Edition Release 9.2.0.1.0
Connected as ctxsys

SQL> create or replace procedure glue_them_together( p_id in rowid, p_lob in out
clob )
  2  as
  3  begin
  4            for x in ( select varchar2_col, clob_col1, clob_col2
  5                                   from obr.t1, obr.t2, obr.t3
  6
  7                                    where t1.rowid = p_id
  8                                      and t2.id(+) = t1.id
  9                                      and t3.id(+) = t1.id )
 10            loop
 11                    dbms_lob.writeAppend( p_lob, length(x.varchar2_col)+1,
x.varchar2_col||' ');
 12                    dbms_lob.append( p_lob, x.clob_col1 );
 13                    dbms_lob.writeAppend( p_lob, 1, ' ' );
 14                    dbms_lob.append( p_lob, x.clob_col2 );
 15            end loop;
 16   end;
 17  /

Procedure created

SQL> grant execute on glue_them_together to obr;

Grant succeeded

SQL> pause
SQL> connect obr/obr
Connected to Oracle9i Enterprise Edition Release 9.2.0.1.0
Connected as obr

SQL> set termout off
SQL> REM GET afiedt.buf NOLIST
SQL> set termout on
SQL> exec ctx_ddl.drop_preference('foo_user_datastore');

PL/SQL procedure successfully completed

SQL> exec ctx_ddl.drop_preference('my_lexer');

PL/SQL procedure successfully completed

SQL> begin
  2       ctx_ddl.create_preference( 'foo_user_datastore', 'user_datastore' );
  3       ctx_ddl.set_attribute( 'foo_user_datastore', 'procedure',
'glue_them_together' );
  4    end;
  5  /

PL/SQL procedure successfully completed

SQL> begin
  2       ctx_ddl.create_preference( 'my_lexer', 'BASIC_LEXER' );
  3       ctx_ddl.set_attribute( 'my_lexer', 'base_letter', 'YES');
  4       ctx_ddl.set_attribute( 'my_lexer', 'mixed_case', 'YES' );
  5  end;
  6  /

PL/SQL procedure successfully completed

SQL> create index t1_idx on t1(varchar2_col)
  2    indextype is ctxsys.context
  3    parameters( 'datastore foo_user_datastore lexer my_lexer');

Index created

SQL> set define off
SQL> column varchar2_col format a20
SQL> column clob_col1 format a20
SQL> column clob_col2 format a20
SQL> select     varchar2_col, clob_col1, clob_col2
  2     from t1, t2, t3
  3     where t1.id = t2.id
  4       and t1.id = t3.id
  5       and contains( varchar2_col, 'this & that & thing' ) > 0;

VARCHAR2_COL         CLOB_COL1            CLOB_COL2
-------------------- -------------------- --------------------

SQL>

And this is the script (for copy & paste):

drop table t1;
drop table t2;
drop table t3;
create table t1 ( id int primary key, varchar2_col varchar2(80) );
create table t2 ( id int primary key, clob_col1    clob );
create table t3 ( id int primary key, clob_col2    clob );
insert into t1 values ( 1, 'This' );
insert into t2 values ( 1, 'That' );
insert into t3 values ( 1, 'and the other thing' );
commit;

grant select on t1 to ctxsys;
grant select on t2 to ctxsys;
grant select on t3 to ctxsys;
pause
connect ctxsys/ctxsys
create or replace procedure glue_them_together( p_id in rowid, p_lob in out clob
)
as
begin
          for x in ( select varchar2_col, clob_col1, clob_col2
                                 from obr.t1, obr.t2, obr.t3

                                  where t1.rowid = p_id
                                    and t2.id(+) = t1.id
                                    and t3.id(+) = t1.id )
          loop
                  dbms_lob.writeAppend( p_lob, length(x.varchar2_col)+1,
x.varchar2_col||' ');
                  dbms_lob.append( p_lob, x.clob_col1 );
                  dbms_lob.writeAppend( p_lob, 1, ' ' );
                  dbms_lob.append( p_lob, x.clob_col2 );
          end loop;
 end;
/

grant execute on glue_them_together to obr;
pause
connect obr/obr
set termout off
REM GET afiedt.buf NOLIST
set termout on
exec ctx_ddl.drop_preference('foo_user_datastore');
exec ctx_ddl.drop_preference('my_lexer');
begin
     ctx_ddl.create_preference( 'foo_user_datastore', 'user_datastore' );
     ctx_ddl.set_attribute( 'foo_user_datastore', 'procedure',
'glue_them_together' );
  end;
/
begin
     ctx_ddl.create_preference( 'my_lexer', 'BASIC_LEXER' );
     ctx_ddl.set_attribute( 'my_lexer', 'base_letter', 'YES');
     ctx_ddl.set_attribute( 'my_lexer', 'mixed_case', 'YES' );
end;
/

create index t1_idx on t1(varchar2_col)
  indextype is ctxsys.context
  parameters( 'datastore foo_user_datastore lexer my_lexer');
set define off
column varchar2_col format a20
column clob_col1 format a20
column clob_col2 format a20
select     varchar2_col, clob_col1, clob_col2
   from t1, t2, t3
   where t1.id = t2.id
     and t1.id = t3.id
     and contains( varchar2_col, 'this & that & thing' ) > 0;

          Followup:

what version of sqlplus -- if you remove the "contains" (just select all data)
what then.

It looks like it is "working" -- the headings wouldn't print otherwise.  Don't
know why it isn't showing the data.  But if you get "nothing" without contains
-- we know it is "working"

perhaps you can put it in a plsql block and dbms_output it - just to see the
data.

TKPROF it as well.
or use autotrace -- see the "rows processed".  then we can fix sqlplus....

      Rating:
      4
Solved!  April 29, 2003
Reviewer:
      Mirko
Case sensitive search condition!
Following query works:

select     varchar2_col, clob_col1, clob_col2
   from t1, t2, t3
   where t1.id = t2.id
     and t1.id = t3.id
     and contains( varchar2_col, 'This & That & thing' ) > 0;

      Rating: 4
Good
      solution.... but I run out of cursors  July 08, 2003

Reviewer:  Tom Best  from PA
Thanks for this answer, Tom.  I did this, but intermedia is leaving a cursor
open for each call to the "glue" proc.  I don't see any bug reports about this
in metalink, either.

I get this:

SQL> create index resitem_text on siebel.s_resitem(name)
  2  indextype is ctxsys.context
  3  parameters( 'datastore foo_user_datastore lexer my_lexer')
  4  /
create index resitem_text on siebel.s_resitem(name)
*
ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 1
ORA-01000: maximum open cursors exceeded
ORA-29855: error occurred in the execution of ODCIINDEXCREATE routine
ORA-20000: interMedia Text error:
ORA-01000: maximum open cursors exceeded
DRG-50857: oracle error in drueixe
oracle error in drueixe
ORA-06512: at "CTXSYS.DRUE", line 126
ORA-06512: at "CTXSYS.TEXTINDEXMETHODS", line 78
ORA-06512: at line 1

And, sure enough, v$open_cursor contains hundreds of rows with this SQL_TEXT in
it:

  begin ctxsys."GLUE_THEM_TOGETHER"(:rid, :lob); end;

BTW - I creating the index with a user that does not own the table being
indexed.

At first I coded it exactly as you had it, and got this result, and then I coded
it with an explicit cursor, but still get the error.  Here is my code as of now:

PROCEDURE GLUE_THEM_TOGETHER( p_id in rowid, p_lob in out clob )
as

cursor x is
    select name, desc_text, faq_ques_text, resolution_text, x_problem_text
                    from siebel.s_resitem sr
                    where sr.rowid = p_id;

l_name siebel.s_resitem.name%type;
l_desc_text siebel.s_resitem.desc_text%type;
l_faq_ques_text siebel.s_resitem.faq_ques_text%type;
l_resolution_text siebel.s_resitem.resolution_text%type;
l_x_problem_text siebel.s_resitem.x_problem_text%type;

begin
/*
    for x in ( select name, desc_text, faq_ques_text, resolution_text,
x_problem_text
                    from siebel.s_resitem sr
                    where sr.rowid = p_id)
*/
    open x;
    loop
        fetch x into l_name, l_desc_text, l_faq_ques_text, l_resolution_text,
l_x_problem_text;

            dbms_lob.writeAppend( p_lob, length(l_name)+1, l_name||' ');
            dbms_lob.writeAppend( p_lob, length(l_desc_text)+1, l_desc_text||'
');
            dbms_lob.writeAppend( p_lob, length(l_faq_ques_text)+1,
l_faq_ques_text||' ');
            dbms_lob.writeAppend( p_lob, length(l_resolution_text)+1,
l_resolution_text||' ');
            dbms_lob.writeAppend( p_lob, length(l_x_problem_text)+1,
l_x_problem_text);

    end loop;
    close x;
end;

          Followup:

what version?

i've used this technique on hundreds of thousands/millions of rows.

      Rating: 3
Version is
      8.1.7.2.0  July 08, 2003
Reviewer:  Tom Best
       from PA
Version is 8.1.7.2.0

          Followup:

how many rows in the table being indexed.

      Rating: 3
# of
      rows  July 08, 2003
Reviewer:  Tom Best
       from PA
select count(*) from siebel.s_resitem
Query finished, retrieving results...
                COUNT(*)
---------------------------------------
                                  26201

desc siebel.s_resitem
Describing siebel.s_resitem....
NAME                            Null?     Type
------------------------------- --------- -----
ROW_ID                          NOT NULL  VARCHAR2(15)
CREATED                         NOT NULL  DATE
CREATED_BY                      NOT NULL  VARCHAR2(15)
LAST_UPD                        NOT NULL  DATE
LAST_UPD_BY                     NOT NULL  VARCHAR2(15)
MODIFICATION_NUM                NOT NULL  NUMBER(10,0)
CONFLICT_ID                     NOT NULL  VARCHAR2(15)
NAME                            NOT NULL  VARCHAR2(100)
SOLUTION_ITEM_FLG               NOT NULL  CHAR(1)
FILE_AUTO_UPD_FLG                         CHAR(1)
FILE_DEFER_FLG                            CHAR(1)
FILE_DOCK_REQ_FLG                         CHAR(1)
FILE_DOCK_STAT_FLG                        CHAR(1)
FILE_NAME                                 VARCHAR2(200)
DESC_TEXT                                 VARCHAR2(255)
FAQ_QUES_TEXT                             VARCHAR2(250)
FILE_DATE                                 DATE
FILE_EXT                                  VARCHAR2(10)
FILE_REV_NUM                              VARCHAR2(15)
FILE_SIZE                                 NUMBER(22,7)
FILE_SRC_PATH                             VARCHAR2(255)
FILE_SRC_TYPE                             VARCHAR2(30)
INTR_PUBLISH_FLG                          CHAR(1)
ORIG_FILE_NAME                            VARCHAR2(220)
PR_AUTHOR_ID                              VARCHAR2(15)
PUBLISH_FLG                               CHAR(1)
RESITEM_TYPE                              VARCHAR2(30)
RESOLUTION_TEXT                           VARCHAR2(2000)
SAVED_AS_FILE_NAME                        VARCHAR2(220)
STATUS_CD                                 VARCHAR2(30)
X_AREA                                    VARCHAR2(50)
X_CHANGE_FLG                              CHAR(1)
X_LANG_ID                                 VARCHAR2(15)
X_PAR_SOLUTION_ID                         VARCHAR2(15)
X_PLATFORM_CD                             VARCHAR2(30)
X_PROBLEM_TEXT                            VARCHAR2(2000)
X_PROD_DEFECT_ID                          VARCHAR2(15)
X_PROD_LINE_ID                            VARCHAR2(15)
X_VERSION                                 VARCHAR2(50)
X_CONF_FLAG                               CHAR(1)
*** SCRIPT END :  Session:CTXSYS

          Followup:

well, i've done tables much much larger then that, I've not hit this personally.
 I neither can find any bugs logged against this at all.

what is your open cursors set to currently?

is your data in need of a lob, we could try a varchar2 if it is under 32k and
see if that makes any difference (just use a varchar2 in out instead of a lob)

      Rating:
      5
  July 09, 2003
Reviewer:
      A reader
Hi Tom,

 This Question is to TOM BEST (Siebel guy).

  Why you are using intermedia in siebel application?
  There is thing called Fulcrum search available in Siebel.
  The reason for this question is we are using siebel call center and I am also
looking any possibility to use Intermedia in siebel

Thanks

      Rating: 5
My
      Stupid  July 09, 2003
Reviewer:  Tom Best
       from PA
I had a bug.  The last call to dbms_lob.writeAppend had the length + 1, but does
not need the appended space (' ').  And, indeed the doc for this function says:

"There is an error if the input amount is more than the data in the buffer."

I would not have expected this particular error, but that's good to know now.

Sorry to waste your time, Tom.

To the guy with the Siebel question... I believe predecesors of mine
investigated Fulcrum and declined to use it.  When using Oracle text, we just
have to make sure we rebuild these indexes after an SRF release, since Siebel
doesn't know about it.

      Rating:
      4
  July 14, 2003
Reviewer:
      A reader  from USA
Is it true that a table, using context indexes with USER_DATASTORE, that
whenever new entries are added to a table the index needs to be re-created.

Can this be a performance hit?

          Followup:

no, the index does not need to be recreated, it needs to be maintained (added
to) just like any index would

      Rating:
      5
  October 13, 2003
Reviewer:  David
      Pujol  from Barcelona (Catalonia)

July 15, 2003 Bhavesh -- Thanks for the question regarding "How
      can I suppress the other error messages from PLSQL errors", version

      You Asked
When we use raise_application_error procedure in
exception part, the procedure returns customized as well as
other
error message.
How can I suppress the other error message?
For example...
=======================================
SQL> execute error_mesg(60);
BEGIN error_mesg(60); END;

*
ERROR at line 1:
ORA-20001: No Department exists!
ORA-06512: at "BHAVESH.ERROR_MESG", line 18
ORA-06512: at line 1
=======================================

is the message I get  in my procedure bhavesh.error_mesg.

I don't want all other ORA_065512. All I want in my error
message
is ORA-20001 which we defined using raise_application_error
package.

Thanks for the help.
Regards,
Bhavesh

      and we said...
In SQLPlus, which is just a simple command line tool for doing
simple things, you cannot.  That is the way all plsql error
messages are reported.  The error stack is always returned,
there is no avoiding it.

If you call the procedure from a 3gl, you have more control of
course -- down to being able to edit the error message.

One approach that may work could be:

ops$tkyte@8i> create or replace procedure error_msg
  2  as
  3  begin
  4          raise_application_error( -20001, 'Bummer' );
  5  end;
  6  /

Procedure created.

ops$tkyte@8i> set serveroutput on
ops$tkyte@8i> set feedback off

ops$tkyte@8i> begin
  2          error_msg;
  3  exception
  4          when others then
  5                  dbms_output.put_line( sqlerrm );
  6  end;
  7  /
ORA-20001: Bummer

So, if you run the block as above and catch the error and just
print out the error message, not the error_stack, you can get
what you want.  It will NOT be flagged as an error to the
calling routine though which is bad.

Review &
      Followup

      Rating: 4
error
      ora-105100  July 14, 2003
Reviewer:
      vinodhps  from India, chennai
Hi Tom,
In my forms mail form login screen iam getting this error message as ora-105100
i could not able to trace the message,
it was also not availabe in oerr or any where in google too..

do you have any idea about that tom

Thanking you

          Followup:

time to call support I suppose.

      Rating: 2
Some control
      available through TRUE/FALSE  July 14, 2003

Reviewer:  andrew  from ca, usa
Connected to:
Oracle8i Enterprise Edition Release 8.1.7.3.0 - Production
With the Partitioning option
JServer Release 8.1.7.3.0 - Production

SQL> DECLARE
  2     x   NUMBER;
  3  BEGIN
  4     x := 1 / 0;
  5  EXCEPTION
  6     WHEN OTHERS THEN
  7        raise_application_error (-20501, 'Whoops!', FALSE);
  8  END;
  9  /
DECLARE
*
ERROR at line 1:
ORA-20501: Whoops!
ORA-06512: at line 7

SQL> DECLARE
  2     x   NUMBER;
  3  BEGIN
  4     x := 1 / 0;
  5  EXCEPTION
  6     WHEN OTHERS THEN
  7        raise_application_error (-20501, 'Whoops!', TRUE);
  8  END;
  9  /
DECLARE
*
ERROR at line 1:
ORA-20501: Whoops!
ORA-06512: at line 7
ORA-01476: divisor is equal to zero

          Followup:

that doesn't get rid of the stuff the original poster wanted to get rid of.

"i don't want the other ora-06512....."

July 15, 2003 Andrew -- Thanks for the question regarding "Why
      gather index stats?", version 8.1.7

      You Asked
I can't seem to find in any documentation that tells me why we have to gather
statistics on indexes as well as tables.  Is not the distribution of data in
index columns the same as that for the table?  That is, if table_a has one
column, col_1, and the distribution of values is:

value          count
~~~~~~~  ~~~~~~~~~~~
A                100
B              3,242
C              2,822
D                244
E              1,337

Then, would not these statistics also apply to the index on col_1?

      and we said...
index stats will tell us

o how much smaller the index is then then table itself (should we full scan
table or fast full scan index)

o perhaps many of the rows indexed are NULL -- they won't even be in the index,
the index might have 1,000 rows -- the table might have 10000000

o the index might have a good cluster factor, might not (read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3479923945167
to explore that idea)

and so on....
Review & Followup

      Rating: 5
is it
      necessary?  July 14, 2003
Reviewer:  Craig
       from NW Arkansas
In our nightly ETL situation, we are dropping our indexes on the tables being
inserted into.  Once the insert is complete, we then re-create those indexes and
call gather_index_stats on those indexes.  Is gathering the index stats
necessary in this case?  In general, when is it necessary?

Thanks again!

          Followup:

well, I wouldn't DROP them as you'll certain LOSE one someday (and spend all day
the next day trying to figure out "why")

what you might consider is

a) set the indexes unusable
b) set skip_unusable_indexes=true, etl
c) alter index t_idx rebuild compute statistics;

that way, the indexes never "go away", but they are not maintained.  If any of
the alter rebuilds fail, the end users will tell you about it right away (they
get an ERROR, not a very very very long running query)

but yes, after a drop and rebuild -- you would need to gather stats (but even
that can be done on the create index)

      Rating: 4
unusable
      index  July 14, 2003
Reviewer:  Vera
       from Washington, D.C.
Tom,
We too are dropping indexes and then INSERTING into tables and then recreating
indexes and then analyzing tables. I never liked it and I tried to use UNUSABLE,
but I found out that it does not work with unique indexes. Is there any way
around?

By the way, -another topic - I just received an email from another developer who
said that they are migrating to 9i and because of this they should modify all
GROUP BY and add ORDER BY (???!!!). I asked why and here is the answer:
"..we asked on metalink and they told us, that when they did explain plan on the
select statements with group by (that did not have an order by), the results
were different and SOMETIMES the group by did not work. so
thats why".
Do you know anything about it???

          Followup:

you can use non-unique indexes -- they work with constraints and such (you would
not be using INDEXES to enforce uniqueness -- you would be using constraints and
if you create the unique/primary keys DEFERRABLE, they'll use unique indexes,
you can defer the constraints -- unusable the indexes and load)

      Rating: 4
Group
      By  July 14, 2003
Reviewer:  Brian

I am currently using 9.2, and I just write queries to answer financial
questions.  I'm not a developer per se.  I just do what I have to do to answer
questions as quickly and efficently as possible.  And re-writing code isn't too
effective unless it adds some real benefit such as using bind variables if
you're not already.

I have not experienced any problems using Group By without Order By clauses in
my queries.  I never even heard of any issue.

As for the Explain Plan, with and without the Order By would be two very
different queries since you're not sorting one of them.  And as Tom often says
here, you want to run additional stats besides Explain Plan.

Tom, am I missing something?

Check out his preview of Chapter 2 from his new book, it's really good.

==

About those indexes, I query off of tables, where pretty much every field is
nullable.   Actually, 99.9% of them are.

In that case, I find my queries are rarely using the indexes (I guess because
some of the rows that I need are not in the index).  But the DBA I work with
ensures me that because the tables are partitioned by month, my queries should
take advantage of the monthly partitioning and run faster when I query selected
months without explicitly specifying the partition.  Is this true in theory?

Note: I have no way to benchmark this since I cannot convince him to test
queries against an unpartitioned version of the same tables.

Thanks

      Rating: 4
Rewriting group
      by queries for 9i  July 14, 2003
Reviewer:
      Glenn A. Santa Cruz  from Mobile, AL USA
We have found that complex view merging in 9iR3 will sometimes modify a query to
such an extent that an ORA-01446: "cannot select ROWID from view with
DISTINCT, GROUP BY, etc" will be thrown when submitting an otherwise normal
query.

These queries worked with no problems in our prior version of Oracle (8.1.7.4),
and one key difference is that Oracle 8i has "COMPLEX_VIEW_MERGING" *disabled*
by default.  Oracle 9i *enabled* complex view merging by default, and it can be
disabled with the (now) hidden init.ora parameter "_COMPLEX_VIEW_MERGING=false".

I am not advocating your changing any init.ora parameter, but rather pointing
out that our mileage did indeed vary with respect to "group by" on inline views.
 You might notice the same results.  We are currently working to reproduce the
issue within the scott/tiger schema for our TAR.

      Rating:
      4
Interesting  July 14, 2003
Reviewer:
      Brian
This is all that I could find about complex view merging:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/opt_ops.htm#1005793
I'm sure there is more.

Dumb question: why would you write code that is re-written, so that it looks
different then the code you had wrote?  Why wouldn't you just write what you
want to see and write the group by's?

If everyone left that expected the re-write in 8i, and you had to rehire, then
you would end up wasting alot of time trying to figure out what was meant by the
code as written when you moved to 9i.

I ask this out of curiousity.

Thanks.

      Rating: 1
group
      by  July 14, 2003
Reviewer:  Vera  from
      Washington D.C.
Never mind. I talked to a person who scared me with his email and after a while
I figured out that their development team thought that Oracle automatically
sorted records when they issue GROUP BY in 8+ version and not in 9i. Who
brainwashed their heads I don't know, but my fear was irrelevant. 9i has nothing
to do with it. You need your records sorted - you issue ORDER BY, period, 9i or
not.

          Followup:

it has always been true that GROUP BY does not have to SORT (and even when it
does, it does a BINARY sort which may differ from the order an ORDER BY would
use)

      Rating: 3
Follow-up on
      complex view merging   July 28, 2003
Reviewer:
      Glenn A. Santa Cruz  from Mobile, AL USA
As a follow-up to my previous comments on complex view merging (and ora-01445),
I have found the following bug via Metalink, which describes our exact problem.
You may want to take a look at bug # 2117638

This problem is directly related to using database links, which I failed to
mention in my previous comments.

July 15, 2003 satish -- Thanks for the question regarding "sql
      loader -- how to load", version 7

      You Asked
would like to know how the script would be if u want to get the
data file(flat file) sitting on unix to the tables..lets say we
have emp records stoed in flat file and we would like to import
to oracle table emp. if you could email me the complete syntax,
that will be great.

      and we said...
Well, the complete syntax takes 3 chapters in

http://technet.oracle.com/doc/server73x/SUT73/toc.htm
so i won't email ;)

But, in $ORACLE_HOME/rdbms/admin is a set of very small, common
examples.  for example:

-- Copyright (c) 1991 by Oracle Corporation
--   NAME
--     ulcase1.ctl - <one-line expansion of the name>
--   DESCRIPTION
--
--   RETURNS
--
--   NOTES
--     <other useful comments, qualifications, etc.>
--   MODIFIED   (MM/DD/YY)
--    cheigham   08/28/91 -  Creation
--
-- $Header: ulcase1.ctl,v 1.1 1991/09/02 14:50:39 CHEIGHAM Stab
--
LOAD DATA
INFILE *
INTO TABLE DEPT
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
(DEPTNO, DNAME, LOC)
BEGINDATA
12,RESEARCH,"SARATOGA"
10,"ACCOUNTING",CLEVELAND
11,"ART",SALEM
13,FINANCE,"BOSTON"
21,"SALES",PHILA.
22,"SALES",ROCHESTER
42,"INT'L","SAN FRAN"

Shows one method of loading delimited data into a table.  This
example has the data inline (INFILE *) but you could just as
well have INFILE /tmp/data.dat and delete the begindata on down
to load from a separate file.
Review &
      Followup

      Rating: 5
variable lengh
      trouble  July 14, 2003
Reviewer:  Bo

Hi, Tom.
I need to load to table people(last_name VARCHAR2(20), first_name VARCHAR2(20))
with SQL*Loader.  The input is a ' delmited text table.  We have people's last
name like "O'Reilly".  Basics the input looks like 'xxxx'O'Rilly'xxxx'.  Could
please tell me how to deal with this in a regular ' delmited control file?

Thanks,

          Followup:

not a chance if it looks like that.

even I, as a smart human, cannot tell where fields begin and end in such a file.

To me, smart human, I see 4 fields obviously.  Not 3.

      Rating: 5
Encounter the
      same problem  July 15, 2003
Reviewer:
      A reader  from USA
Bo,
I worked on a conversion from VSAM files to an Oracle DB, years and years ago,
and encountered the same problem that you have.  The only solution was to go
back to the originator of the file(s) and had them create the files differently;
different field delimiter, replace O'Reilly type names with O"Reilly (updated
the tables later).  Probably not the best solution, but it worked.

July 15, 2003 wallacel -- Thanks for the question regarding
      "question", version 8.1.6

      You Asked
Tom:

I have serveral question as below:

1.can you explain to me why smaller result set is not always
the smaller table.?(see below)

Using cost-based optimization and a nested loops join as
the means of joining,the first table in the from clause is the
driving table(all other things being equal), but only
the ordered hint will guarantee this. in nested loops
joins,choose a driving table that is the small result set(not
always the smaller table) will make fewer loops to the other
result set(from the nondriving table) and will usually result
in the best performance.

2.in Oracle8i Designing and tuning for Performance 12-10
Using Nonunique Indexes to Enforce Uniqueness
You can use an existing nonunique index on a table to enforce
uniqueness, either for UNIQUE constraints or the unique aspect
of a PRIMARY KEY constraint. The advantage of this approach is
that the index remains available and valid when the constraint
is disabled. Therefore, enabling a disabled UNIQUE or PRIMARY
 KEY constraint does not require rebuilding the unique index
associated with the constraint. This can yield significant time
savings on enable operations for large tables.Using a nonunique
index to enforce uniqueness also lets you eliminate redundant
indexes. You do not need a unique index on a primary key column
if that column already is included as the prefix of a composite
index. You can use the existing index to enable and enforce the
constraint. You also save significant space by not duplicating
the index. However, if the existing index is partitioned, then
the partitioning key of the index must also be a subset of the
UNIQUE key; otherwise,Oracle creates an additional unique index
to enforce the constraint.

I don't understand what it saying,does that mean after you creat
a primary key constraint or unique constraint, and then disable
it, it still work like not being disabled? (it says that the
index remains available and valid when the constraint is
disabled.)

3. on page 12-2 it gives the guideline for when to create
indexes
one thing is The table contains a relatively small number of
columns. and another is most queries on the table have
relatively simple where clauses.
why?

4. before i shutdown database, do i need to
        lsnrctl stop
        lsnrctl dbsnmp_stop
        oemctrl stop oms

Regards,

      and we said...
1. The smaller result set is not always the smaller table
because of other predicates.  Let's say you have:

  select *
    from a_1_billion_row_table A, a_1_million_row_table B
   where a.id = b.id
     and a.primary_key = 5;

Here, table A is many orders of magnitude larger then table B
however, it makes more sense to use table A as the driving table
since the result set that will come from table A (where
a.primary_key = 5) is probably 1 row whereas the result set that
comes from table B is 1 million rows.  So, if you push the
predicates into the from clause so that the above query looks
more like:

   select *
     from ( select * from a_1_billion_row_table
              where primary_key = 5 ) A,
          ( select * from a_1_million_row_table ) B
    where a.id = b.id;

Now perhaps it is more clear.  The virtual table A in the from
list is the "smaller" table -- but it is not really a table, it
is a result set.

2)If you use a non-unique index in the enforcement of a
primary/unique constraint what it is saying is that:

   when you DISABLE or otherwise get rid of the UNIQUE/PRIMARY
   KEY constraint, Oracle does not have to get rid of the
   underlying index since the underlying index is NOT UNIQUE.
   If the underlying index was UNIQUE -- Oracle would have to
   drop it when you disabled the constraint since the unique
   index would still be a de-facto enforcement of the unqiue
   constrain which you want disabled.

So, we can use a non-unique index for primary key/unique
constraints (in fact, if you create a DEFERRABLE constraint we
will use a non-unique index all on our own).  When the
constraint that used that non-unique index "goes away", the
underlying index can remain since it is not a "de-facto"
implemention of the constraint you are getting rid of.  If the
underlying index were unique, we would be forced to get rid of
it (else the constraint would still appear to be enabled)....

3) As for the "relatively small number of columns" -- i believe
they are getting at "table width".  You cannot take the
guidelines out of context like that -- you need to look at the
entire statement:

<quote>
Indexes improve the performance of queries that select a small
percentage of rows from a table. As a general guideline, create
indexes on tables that are queried for less than 2% or 4% of the
table’s rows. This value may be higher in situations where
all data can be retrieved from an index, or where the indexed
columns and expressions can be used for joining to other tables.

This guideline is based on the following assumptions:

...
The table contains a relatively small number of columns.
...
most queries on the table have relatively simple where clauses.
...

</quote>

It is not saying "do not index a table with lots of columns (or
more simply -- do not index a 'wide' table)".  It is saying that
the 2-4% rule is for tables that are "skinny" or narrow.  It
goes onto say:

<quote>
If these assumptions do not describe the data in your table and
the queries that access it, then an index may only be helpful if
your queries typically access at least 25% of the table’s rows.
</quote>

meaning that as the table gets WIDER (inducing more IO's for a
full scan) the usefulness of an index increases with the width
of a table (the width of the rows).  As you have a table with
increasing "width" -- the percentage of a table that is fetched
via an index that is good for an index access plan increases
from 2% to 25%.

Same logic with the relatively simple where clauses.  It is not
saying "if you have complex where clauses do not index".  It is
saying that the above rule of thumb (2-4% rows fetched) was made
with the idea that relatively simple where clauses are used.  As
the cost of evaluating a more and more complex (like a wider and
wider table) goes up -- the amount of data retrieved via an
index may be more and more.

4) no you do not need to.
Review & Followup

      Rating: 1
dbsnmp and
      shutdown  February 10, 2003
Reviewer:
      Brigitte  from Germany
Hallo Tom,

I look for dbsnmp and shutdown in your archives and found only this question,
point 4)
If I try to shutdown the database normal with dbsnmp-process - it hangs. I must
shutdown abort.
But if I will do a cold backup I must shutdown normal.
I can't stop dbsnmp or the listener because the listener listen to some
databases.
1. What can I do?
2. Must/can I kill the dbsnmp-process in my database before shutdown normal?

Thanks

          Followup:

 you should not shutdown IMHO.  shutting down to backup is an utter waste.
Also, you are probably trying to shutdown/startup UNATTENDED I bet -- and that
is a recipe for disaster.

shutdown immediate should shutdown cleanly.
If not, shutdown abort+startup enable restricted session+shutdown normal

but really -- in all honesty -- don't shutdown for a cold backup, just do it the
right way and backup online.  It is not any harder.

      Rating: 5
DBSNMP and
      SHUTDOWN  February 10, 2003
Reviewer:
      Brigitte  from Germany
Hallo Tom,

Thanks for your help. I do it only with a test database not a production
database.
Please, what means IMHO?

Thanks

          Followup:

in my humble opinion

      Rating: 3
DBSNMP process
      going haywire  July 15, 2003
Reviewer:
      Sidharth  from India
I have been searching your site to fix a problem regarding DBSNMP and found only
this. Since none of the questions asked covers my problem let me put it to you.

I have an Oracle 9iR2 database runing on WIN2k professional.Yesterday I was
playing around with TOAD(I am new to it) - mainly looking at the DBA Utilitites
and after that I see my CPU utilization hit 100% and not come down. Looking at
the task manager I found that DBSNMP process is eating all the CPU. I did
agentctl stop to stop the process and CPU utilization came down to normal. I
started it again and BANG!! CPU utilization goes back to 100%.

What could be the reason? I tried looking at topsessions and there is no session
by DBSNMP user among those.

Can you please help me out?

          Followup:

contact support for an issue like this, file an iTar.  I don't use windows at
all.

July 15, 2003 VENKATARAMAN -- Thanks for the question regarding
      "CAPACITY PLANNING FOR REDOLOG FILE", version 8.1.5

      You Asked
Hi,

While creation of the database, is there any thumb rule to
determine the size of the redolog file?  Because with redolog
files, we cannot resize the the file on fly.  So it is necessary
for me to determine the size of redo log file.

regards

VENKAT

      and we said...
You cannot resize a redo log file period.  But you most
certainly can change the number or sizes of them on the fly.

To "resize" a redo logfile,

o alter database add logfile '/.....' size BIG;
o make sure the "small" log is not current, alter system switch
logfile to make it not current
o alter database drop logfile '/small/file'

I myself start with 3 groups of 2 members of 25meg apiece and
work up from there.  Either adding more groups or making the
members larger until I have enough online redo log to get me
through at least 15-20 minutes of work without switching.  As
with all rules of thumb -- there will be times when 15-20
minutes is too long and too short.  You need to pick your time
and go from there.

Review &
      Followup

      Rating:
      3
  March 24, 2003
Reviewer:  deepa
       from india
How do i calculate the size of the redo log files.

Suppose my database is about 5GB.
And about 100 transactions per minute,
Sometimes during the peak hours i get cannot allocate redo log error.

I know adding redolog files or changing the log checkpoint interval parameter
would help.

Is there any strategy to calculate the redolog file size .

          Followup:

changing the log checkpoint interval won't really affect this.

do you generate 100 50byte transactions or 100 500k transactions per minute.

You figure out your redo generation rate and use that to help you size it.  I
generally just add logs until it goes away.  Not very scientific but hey,
brutally effecient.

      Rating: 3
What is
      capacity planning? How much is per session memory
      requirement?  July 15, 2003
Reviewer:  Tony
       from Chennai, India
1.What is capacity planning and how to do that for a database? please give me a
detailed answer or point to any useful link or white papers. It will be better
if your new book covers this topic.

2. How much is the per session memory requirement for 8i and 9i databases?

3. What should be the minimum memory size if there are 100 concurrent users for
a database?

          Followup:

1) you'll have to research that on yourself.  there are books written on the
subject, college courses given on it, careers built around it.

what I'm saying is "bigger then a breadbox".

2) somewhere between 1 byte and 1000gigabytes.  It totally depends on what you
do, how you do it.  I would feel safe with at least 1meg/connected session.

3) how much money do you have?  I would think at least 512m at a minimum

July 15, 2003 Sandy -- Thanks for the question regarding "New
      database and password file", version 8.1.5

      You Asked
Hi Tom,

I am kind of confused about password file parameter in init.ora.
What is the functionality of password file parameter exactly,
becuase whenever I am trying to create a new database (On Unix)
while password file parameter is set to the exclusive...I am
getting error "password file invalid". After making comment to
that parameter database creation works fine. Can u just tell me,
what exactly password file do....what should be the valuse for
password file in init.ora..while creating new database. I know
that, after making comment that parameter database creation
works fine..but this not the right approach though...may be some
time we need to have this parameter uncommented.
I'll appriciated U R coperation.

Thanks
Sandy

      and we said...
REMOTE_LOGIN_PASSWORDFILE specifies whether Oracle checks for a
password file and how many databases can use the password file.

 NONE: Oracle ignores any password file. Therefore, privileged
users must be authenticated by the operating system.

 EXCLUSIVE: The password file can be used by only one database
and the password file can contain names other than SYS and

INTERNAL. This setting is required for Oracle Parallel Server.

 SHARED: More than one database can use a password file.
However, the only users recognized by the password file are SYS
and INTERNAL.

The remote login password file allows remote "connect internals"
to take place.  Since we cannot use OS authentication over the
network to see of the person on the other end of the connection
is in the correct group -- we can use this to password protect
internal and have internal get prompted for a password.

To create a valid password file, you would use the orapwd
command at the command line.  See

http://asktom.oracle.com/pls/ask/f?p=4950:7:::::F4950_P7_SEARCH_CRITERIA:orapwd
for examples/discussion on this utility.
Review &
      Followup

      Rating: 3
OS
      authentication and remote_login_passwordfile   June 11, 2003

Reviewer:  jianhui  from DC
Tom,
My database is 8.1.7.2. The oracle document says the remote_login_passwordfile
MUST be NONE if using OS authentication. Below is what i got and it seems the
document is not true, because i can still use OS authentication while the
parameter is set to EXCLUSIVE can you explain it. Does setting it to EXCLUSIVE
allow both OS and network authentication?

Regards,
quote from oracle document:

{
Operating system authentication
 You must have SYSDBA privileges on your operating system, and the
initialization parameter REMOTE_LOGIN_PASSWORDFILE must be set to NONE.

See Also: Oracle9i Database Administrator's Guide to learn about authentication,
and your operating system specific documentation to learn about adding SYSDBA
privileges
}

my test screen shoot:

[dbaweb@wbapps12 dbaweb]$ id
uid=511(dbaweb) gid=511(dbaweb) groups=511(dbaweb),509(dba)
[dbaweb@wbapps12 dbaweb]$ echo $ORACLE_HOME $ORACLE_SID
/orasrc/product/8.1.7 ISPT
[dbaweb@wbapps12 dbaweb]$ sqlplus '/ as sysdba'

SQL*Plus: Release 8.1.7.0.0 - Production on Wed Jun 11 16:19:22 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.2.0 - Production
JServer Release 8.1.7.2.0 - Production

SQL> show parameter remote_lo

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------
remote_login_passwordfile            string  EXCLUSIVE
SQL>

          Followup:

well, you don't tell me what document and apparently you are using 9i docs for
8i?

but in 9i

sqlplus '/ as sysdba'

SQL*Plus: Release 9.2.0.3.0 - Production on Thu Jun 12 08:23:16 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production

sys@ORA920> show parameter remote_log

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
remote_login_passwordfile            string      EXCLUSIVE

      Rating: 3
further
      question about the password file  June 11, 2003

Reviewer:  jianhui  from DC
Tom,
A futher question is when i connect to an idle instance usgin sqlplus '/ as
sysdba' before starting the instance, how does oracle know which parameter file
should be used to locate the remote_login_passwordfile parameter???
For example, i have 2 instances in one machine, instance1 uses password file and
set the parameter to exclusive yet instance2 uses OS authentication. After i
login to the OS and set $ORACLE_HOME $ORACLE_SID, then i issue sqlplus '/ as
sysdba', how does oracle know whether to use OS authentication or PASSWORD file?
Since the setting is in the parameter file, the only way oracle can know is to
locate the parameter file by the default location derived by the $ORACLE_HOME
$ORACLE_SID, is it right? What if the initSID.ora is not in the default
location?

          Followup:

your oracle sid and oracle home determine that.

but if you use /, it is moot, that is using OS auth.  "dba group" is what gets
you in (or not)

      Rating: 3
OS
      authentication  July 14, 2003
Reviewer:
      Jianhui  from DC
Tom,
Why cannt i login as sysdba when i use oracle OS user account? Below is the copy
& paste. Oracle version is 9.2.0.1.0

SQL> conn /
Connected.
SQL> show user
USER is "OPS$ORACLE"   (-- OS authentication is working for non sysdba
connection)
SQL> conn / as sysdba
ERROR:
ORA-01031: insufficient privileges
(-- OS authentication is NOT working for sysdba connection)

Warning: You are no longer connected to ORACLE.
SQL> conn sys as sysdba
Enter password:
Connected.
SQL> select * from v$pwfile_users;

USERNAME                       SYSDB SYSOP
------------------------------ ----- -----
SYS                            TRUE  TRUE
OPS$ORACLE                     TRUE  FALSE

SQL> show parameter remote

NAME                                 TYPE        VALUE
------------------------------------ ----------- ---------------
remote_archive_enable                string      true
remote_dependencies_mode             string      TIMESTAMP
remote_listener                      string
remote_login_passwordfile            string      EXCLUSIVE
remote_os_authent                    boolean     FALSE
remote_os_roles                      boolean     FALSE
SQL> show parameter os

NAME                                 TYPE        VALUE
------------------------------------ ----------- ---------------
optimizer_index_cost_adj             integer     100
os_authent_prefix                    string      ops$
os_roles                             boolean     FALSE
remote_os_authent                    boolean     FALSE
remote_os_roles                      boolean     FALSE
timed_os_statistics                  integer     0
SQL>

          Followup:

well, you are not using the pw file when you do that and ORACLE must not be in
the "dba" group or whatever group.

do this to test:

alter user ops$oracle identified by pw;

Now, both:

connect /
connect ops$oracle/pw

will work.  try both "as sysdba" - if the second works, then v$pwfile is being
used for that.  check the groups oracle is in, make sure it is in the right
ones.

      Rating: 3
why shouldn't
      oracle belong to DBA group?  July 14, 2003

Reviewer:  jianhui  from DC
Tom,
I tested it again as you said without making any change. Why shouldnt oracle
belong to DBA group in order to use OS authentication to connect as sysdba?
Thanks!

SQL> conn ops$oracle/pw
Connected.
SQL> conn /
Connected.
SQL> conn ops$oracle/pw as sysdba
Connected.
SQL> conn / as sysdba
ERROR:
ORA-01031: insufficient privileges

Warning: You are no longer connected to ORACLE.
SQL> host id
uid=1000(oracle) gid=1000(dba)

          Followup:

that means whomever installed your software used a group other then DBA to be
the sysdba (internal) group.  You'll need to find out what they used.  DBA is
the default, but you can override that during install

      Rating: 3
a
      mistake  July 15, 2003
Reviewer:  jianhui
       from DC
Tom,
Could you please remove my latest message, because i dont want to disclose the
machine name and SID in the public website, the last line of my message has that
information.
But the question remains the same!
Many thanks!

          Followup:

open a tar, contact support, I don't know your environment -- all I know is "i
do not believe the dba group is the group"

July 15, 2003 Sagi -- Thanks for the question regarding "MTS",
      version 8.1.7

      You Asked
Hi Tom,

We are in the development Stage. We have a web application running using java.
In the application what we do is we create a pool of connections (10) and use
them. We dont close. Because we keep getting hits. So if a connection in the
pool is idle it would be in that state only for some time. When there is any
HTTP request this connection in the pool is used (This assumption is when it
goes into production).

We have around 50 guys and each one of them have got an application server
installed on his desktop. So what happens here i get 500 connections.

When I query the V$SESSION 80% of them show the status as INACTIVE. So I was
thinking would it be a good idea to have MTS in place. But then am not sure
whether or not this is a good idea. Because anyway connections are maintained
through connection pooling in java.

So need your comment whether MTS would be a good idea on Development. And also
when this moves into production then we will have only one connection pool with
10 connections or so. Would it be good idea to have MTS on Production.

Awaiting for your valuable comments.

If possible please explain in detail.

Regards,
Sagi

      and we said...
well, I'll say yes and no.

first -- it seems lke a really bad idea for 50 guys to have 50 app servers ...
why would you do that?  how can you do your testing???  If they each have their
own app server environment -- maybe, they each need their own database, then
deploy out to a common TEST area.

I would personally prefer development to look as much like a small version of
TEST as possible and TEST looks just like production ;)   So I guess I'm
introducing an entire hole another set of machines in here.

MTS would work on your development -- however, it would be DIFFERENT then what
you deploy to.  Fastest way to a headache -- do it differently in dev/test then
you do in real life.  I would not use MTS in prod as that would be like
buffering a buffer cache.
Review & Followup

      Rating:
      3
  February 13, 2003
Reviewer:  Sagi

Tom,

Q1. Each user having his own app server is something which i dont have control
on.

I was expecting the same what you said in regards to PROD. Now if I use MTS in
DEV & TEST environments, when one user has created the pool but not using at
all...the status is INACTIVE in V$SESSION. So when another new user tries to
create the pool...would the resources allocated to the first user for the pool
created be SHARED for the second user?

I dont have much knowledge on JDBC Connection pooling.

Also I did not get what you meant by this

"Fastest way to a headache -- do it differently in dev/test then you do in real
life"

Q2. When we establish a connection to the DB either through SQLPLUS, JDBC
Connection pooling or any 3rd party tools what are all the resources that get
allocated and reserved in a DEDICATED connection.

Thanx in advance.

Regards,
Sagi.

          Followup:

Using MTS, you will cut down on the number of processes.  Your database will
have to be configured very very differently - you'll need a big LARGE pool as
the UGA memory will be moved from the dedicated servers into the SGA.  Things
will work very differently.

This is what I meant by

"Fastest way to a headache -- do it differently in dev/test then you do in real
life"

If you use MTS in dev and not in PROD - because it is wrong in PROD to use it,
then dev and prod will be as different configuration wise as night and day.  You
want to get headaches everytime something new is rolled out?  Keep dev and prod
as different as possible!

q2) if you have my book expert one on one Oracle -- I go over the architecture
in depth.  As well, the server concepts manual has this basic architecture
information.

      Rating:
      4
  February 13, 2003
Reviewer:  Sagi

Thanx tom.

As usual, your answers are the best and to the point.

Regards,
Sagi

      Rating: 3
MTS
      config  June 29, 2003
Reviewer:  Alvin
       from Philippines
I cant seem to startup  my db using MTS config.

I get this error upon startup "ORA-00101: invalid specification for system
parameter MTS_DISPATCHERS"

Init.ora settings,
===================
db_name = "rtbe"
db_domain =standby1
instance_name =standby1
service_names = rtbe.standby1
mts_dispatchers="(PRO=TCP)(DISP=3)(SERVICE=RTBE.STANDBY1)"
mts_max_dispatchers=15
mts_service=rtbe.standby1
mts_servers=5
mts_max_servers=20
mts_sessions=200

My port settings is the default 1521.

On another machine i can start but i can't connect. I get this error on sqlplus
"ORA-12545"

Init.ora
============
mts_dispatchers ="(PROTOCOL=TCP)(SERVICE=rtbe.cronos)(DISPATCHERS=3)"
mts_max_dispatchers = 15
mts_service = rtbe.cronos
mts_servers = 3
mts_max_servers = 20
mts_sessions = 200

This one is also using the default port 1521.

At home using my personal oracle i have configure MTS smoothly. I can connect
and when i query v$circuit i see my dispatchers. All of the db above uses 8i
residing on Solaris 5.8 except for the personal its on a PC.

What am i doing wrong here ?

      Rating:
      3
update...  June 29, 2003
Reviewer:
      Alvin  from Philippines
I removed the init.ora param "local_listener=listener " and the error below went
away and the db started.

ORA-00101: invalid specification for system parameter MTS_DISPATCHERS

Now the ONLY problem here is the ORA-12545 on sqlpuls.

          Followup:

[tkyte@tkyte-pc Desktop]$ oerr ora 12545
12545, 00000, "Connect failed because target host or object does not exist"
// *Cause: The address specified is not valid, or the program being
// connected to does not exist.
// *Action: Ensure the ADDRESS parameters have been entered correctly; the
// most likely incorrect parameter is the node name.  Ensure that the
// executable for the server exists (perhaps "oracle" is missing.)
// If the protocol is TCP/IP, edit the TNSNAMES.ORA file to change the
// host name to a numeric IP address and try again.

your tnsnames.ora file is messed up then.

      Rating: 3
tns looks
      fine.  June 30, 2003
Reviewer:  Alvin
       from Philippines
here is my tnsnames entry (in my pc) for that server... i always use the server
ip in the host field.

Or do i need to create an entry in the tnsnames of the server also ?

Tnsnames.ora in my pc
=====================
LUTHER =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = xxx.xxx.xxx.xxx)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = rtbe.standby1)
    )
  )

I don't see anything wrong here... if i comment out the mts_ entries in the init
param then bounce the db i can connect via dedicated server.

      Rating: 4
aio_results_np
      WARNING  July 15, 2003
Reviewer:  Mahomed
      Suria  from UK
I have a proble with my system such that at regular intervals, now becoming more
frequent, followin messages are being logged in the alert log file :
Tue Jul 15 14:31:21 2003
WARNING: aio_results_np timed out 1 times, waited 120 secs
Tue Jul 15 14:31:21 2003
WARNING: aio_results_np timed out 1 times, waited 120 secs
Tue Jul 15 14:31:21 2003
WARNING: aio_results_np timed out 1 times, waited 120 secs
Tue Jul 15 14:31:21 2003
ksbsrv: No startup acknowledgement from forked process after 3 seconds
Tue Jul 15 14:31:21 2003
WARNING: aio_results_np timed out 1 times, waited 120 secs
Tue Jul 15 14:31:21 2003
WARNING: aio_results_np timed out 1 times, waited 120 secs
Tue Jul 15 14:31:21 2003
WARNING: aio_results_np timed out 1 times, waited 120 secs
Tue Jul 15 14:31:21 2003
WARNING: aio_results_np timed out 1 times, waited 120 secs
Tue Jul 15 14:36:11 2003

At the same time 2 oracle processes take up all the CPU time  as shown in
following example :

ps aux |head
USER        PID %CPU %MEM   VSZ  RSS TTY      S    STARTED         TIME COMMAND
oracle   219379 99.5  0.4  438M 8.3M ??       R    14:27:53     3:45.65
ora_s001_cdb
oracle     4600 97.6  0.3  421M 5.7M ??       R      Jun 29    04:56:21
ora_d000_cdb
root     194729  0.0  0.0 2.20M 312K ??       I    14:20:43     0:00.02 telnetd
oracle   523096  0.0  0.1  417M 2.6M ??       I      Jul 10     0:00.08
oraclecdb (DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))
oracle   456484  0.0  0.5  452M 9.4M ??       I    05:19:34    02:25:22
ora_s000_cdb
manager  521315  0.0  0.0 2.51M 400K pts/2    S    07:50:37     0:00.34 -ksh
(ksh)
root     520643  0.0  0.0 2.20M 296K ??       S    07:50:37     0:00.72 telnetd
manager  454727  0.0  0.2 19.1M 3.4M ??       S      Jul 11     0:09.62 despat
oracle   454696  0.0  0.4  453M 9.1M ??       I      Jul 11     5:28.52
oraclecdb (DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))

IS THIS AN MTS problem?. Should I not be using MTS?
My paltform is HP TRU64 UNIX 5.1 Alpha and Oracle 9.2.0.1.0
Standard edition.

Your help will be much appreciated.

Thanks

          Followup:

the two things seem wholly unrelated -- you'll have to contact support.  I don't
use tru64 and that async IO api is a tru64 one.

what you should look at is "what is that shared server doing" -- maybe it is
doing something big at that point and the two things are unrelated.

No, i cannot say "do not use shared server"

July 15, 2003 --
      Thanks for the question regarding "unable to open RPC connection to
      external procedure agent", version 8.1.6

      You Asked
Hi Tom

What does this error mean ?

SQL> declare
  2  x date;
  3  begin
  4  uni_time(x);
  5  end;
  6  /
declare
*
ERROR at line 1:
ORA-28575: unable to open RPC connection to external procedure agent
ORA-06512: at "GET_GMT", line 0
ORA-06512: at "UNI_TIME", line 10
ORA-06512: at line 4

Here is my TNSNAMES file

dev =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = dev)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SID = dev)
    )
  )

extproc_connection_data  =
         (DESCRIPTION=
                 (ADDRESS=
                 (PROTOCOL=IPC)
                 (KEY=extproc))
                 (CONNECT_DATA=(SID=extproc)
                 ))

Is there anything wrong in the tns.

Thanks in advance

Bala

      and we said...
You need to setup the listener.ora and tnsnames.ora file for extprocs.  This
setup is done on the DATABASE SERVER, not on the client.

Here is a sample listener.ora file:

----------------------------------------------------
LISTENER =
  (ADDRESS_LIST =
    (ADDRESS=(PROTOCOL=IPC)(KEY=oracle81.world))
    (ADDRESS=(PROTOCOL=TCP)(Host=sheepdog)(Port=1521))
  )
SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (GLOBAL_DBNAME=sheepdog.us.oracle.com)
      (ORACLE_HOME= /d01/home/oracle81)
      (SID_NAME = oracle81)
    )
    (SID_DESC =
      (SID_NAME = extproc)
      (ORACLE_HOME = /d01/home/oracle81)
      (PROGRAM = extproc)
    )
  )
STARTUP_WAIT_TIME_LISTENER = 0
CONNECT_TIMEOUT_LISTENER = 10
----------------------------------------------------

The important things in the listener file for extprocs:

o ADDRESS=(PROTOCOL=IPC)(KEY=oracle81.world))

set up an IPC based listener.  remember the value of the KEY= (you can make it
whatever you want, just remember what it is)

o (SID_DESC=(SID_NAME=extproc)

remember that SID_NAME, call it extproc.

The next file is the tnsnames.ora file, it needs an entry like:

---------------------------------------------------
extproc_connection_data =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = IPC)(KEY = oracle81.world))
    (CONNECT_DATA = (SID = extproc))
  )
--------------------------------------------------

Important things:

o (ADDRESS = (PROTOCOL = IPC)(KEY = oracle81.world))

should be the same as was in the listener.ora file....

o (CONNECT_DATA = (SID = extproc))

the sid must match the sid in the (SID_DESC=(SID_NAME=extproc) from the
listener.ora....

Also, if your sqlnet.ora specifies some default domain, it needs to be on the
tnsnames entry.  So if you have a sqlnet.ora with stuff like:

sqlnet.authentication_services=(none)
names.directory_path = (TNSNAMES, HOSTNAME)
names.default_domain = world
name.default_zone = world
automatic_ipc = off

in it, that would be extproc_connection_data.world -- not just
extproc_connection_data.

followup for the coment below

No, this is a one time setup.  Extprocs are run in a separate address space.
The TNS listener is used to start this separate address space.  Once the
listener is configured to start extproc services on a server, it is good to go.
You can add as many routines as you want.

Review &
      Followup

      Rating: 3
Not complete
      clear as to why we need all of this  March 23, 2001

Reviewer:  Lonnie Cumberland  from Dretoit MI USA

I can follow what you are saying, but it raises many more questions such as:

 "do we need to modify this each time that we want to add some external
proceedures?"

A more detailed description of "WHY this is need and important" would be
helpful.

      Rating:
      5
ENVS  October 21, 2002
Reviewer:
      A reader
Hi Tom,
Please tell me where exactly to we need to provide ENVS parameter in the
listener.ora. I need to provide LD_LIBRARY_PATH. Should various directories in
the LD_LIBRARY_PATH be separated by comma or colon. I am on DYNIX 4.48
environment.
If dynamic linking does not work is it possible to convert dynamic library in
static?
Thanks

          Followup:

like this in the listener.ora:

SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (SID_NAME = extproc)
      (ORACLE_HOME = /export/home/ora817)
      (ENVS=LD_LIBRARY_PATH=/ora817/ctx/lib:/ora817/lib)
      (PROGRAM = extproc)
    )
    (SID_DESC =
      (GLOBAL_NAME = ora817dev.world)
      (ORACLE_HOME = /export/home/ora817)
      (SID_NAME = ora817dev)
    )
  )

you would use the same syntax (colon, comma) as you would for setting this in
the ENVIRONMENT itself -- typically using a :

      Rating: 4
Simple
      Test  July 15, 2003
Reviewer:  Marcio
       from Brazil
Tom, I have this environment and seen doesn't work. I've been worked on this
last all week and got nothing. I've tried many things. Would you see any issue
on this?

Box:
C:\Oracle\Ora81\network\ADMIN>sqlplus /
SQL*Plus: Release 8.1.6.0.0 - Production on Tue Jul 15 09:31:12 2003
Connected to:
Oracle8i Enterprise Edition Release 8.1.6.3.0 - Production
JServer Release 8.1.6.3.0 - Production
ops$t_mp00@MRP816> host
Microsoft Windows 2000 [Version 5.00.2195]
(C) Copyright 1985-2000 Microsoft Corp.

Files:
C:\Oracle\Ora81\network\ADMIN>type sqlnet.ora
NAMES.DEFAULT_DOMAIN = aquarius.cpqd.com.br
SQLNET.AUTHENTICATION_SERVICES= (nts)
NAMES.DIRECTORY_PATH= (TNSNAMES, HOSTNAME, ONAMES)
automatic_ipc = off

C:\Oracle\Ora81\network\ADMIN>type listener.ora
LISTENER =
  (ADDRESS_LIST =
    (ADDRESS=(PROTOCOL=IPC)(KEY=extproc_chave))
    (ADDRESS=(PROTOCOL=TCP)(Host=cpqd020496)(Port=1521))
  )
SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (GLOBAL_DBNAME=mrp816)
      (ORACLE_HOME= c:\oracle\ora81)
      (SID_NAME = mrp816)
    )
    (SID_DESC =
      (SID_NAME = extproc)
      (ORACLE_HOME = c:\oracle\ora81)
      (PROGRAM = extproc)
    )
  )
STARTUP_WAIT_TIME_LISTENER = 0
CONNECT_TIMEOUT_LISTENER = 10

C:\Oracle\Ora81\network\ADMIN>type tnsnames.ora
extproc_connection_data.aquarius.cpqd.com.br =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = IPC)(KEY = extproc_chave))
    (CONNECT_DATA = (SID = extproc))
  )

mrp816.aquarius.cpqd.com.br =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = cpqd020496)(PORT = 1521))
    (CONNECT_DATA = (SERVICE_NAME = MRP816))
  )

MRP9I1.aquarius.cpqd.com.br =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = CPQD020496)(PORT = 1522))
    (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = mrp9i1))
  )

My test:
C:\Oracle\Ora81\network\ADMIN>sqlplus /@extproc_connection_data

SQL*Plus: Release 8.1.6.0.0 - Production on Tue Jul 15 09:33:56 2003

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

ERROR:
ORA-03113: end-of-file on communication channel

Enter user-name:

Here extproc.exe has generated a error unloged. But I've tried in 8.1.7, 9.2.0
in another box (same OS) -- like my notebook.

I don't know if this way is correct to test as simple as possible, because i
have no C compiler here. Is there easy test to know if ipc is up.

Seen to me the extproc is up -- doesn't?
ops$t_mp00@MRP816> host lsnrctl status

LSNRCTL for 32-bit Windows: Version 8.1.6.3.0 - Production on 15-JUL-2003
09:37:03

(c) Copyright 1998, 1999, Oracle Corporation.  All rights reserved.

Connecting to (ADDRESS=(PROTOCOL=IPC)(KEY=extproc_chave))
STATUS of the LISTENER
------------------------
Alias                     LISTENER
Version                   TNSLSNR for 32-bit Windows: Version 8.1.6.3.0 -
Production
Start Date                14-JUL-2003 18:17:08
Uptime                    0 days 15 hr. 19 min. 54 sec
Trace Level               off
Security                  OFF
SNMP                      OFF
Listener Parameter File   c:\oracle\ora81\network\admin\listener.ora
Listener Log File         c:\Oracle\Ora81\network\log\listener.log
Services Summary...
  extproc               has 1 service handler(s)
  mrp816                has 1 service handler(s)
  mrp816                has 1 service handler(s)
  mrp9i1                has 2 service handler(s)
  mrp9i1                has 2 service handler(s)
The command completed successfully

After all, I would like to know if exist a easy test to ipc.

Thanks,
Marcio

          Followup:

what are you trying to do?  you cannot sqlplus to the extproc service.

it is running, yes the extproc is there.  no clue as to why you think you can
"sqlplus" to it tho.

      Rating: 5
SQLPLUS
      IPC  July 15, 2003
Reviewer:  Marcio
       from Brazil
Sorry, I didn'd know that. I'd just want to test my ipc (extproc) connection as
simple as possible, just it.

          Followup:

tnsping it, beyond that, you need an "extproc" to run

      Rating:
      5
Thanks  July 15, 2003
Reviewer:
      Marcio  from Brazil
One more!

ops$t_mp00@MRP816> host tnsping extproc_connection_data

TNS Ping Utility for 32-bit Windows: Version 8.1.6.3.0 - Production on
15-JUL-2003 14:56:41

(c) Copyright 1997 Oracle Corporation.  All rights reserved.

Attempting to contact (ADDRESS=(PROTOCOL=IPC)(KEY=extproc0))
OK (40 msec)

July 15, 2003 Julien -- Thanks for the question regarding "Java
      stored procedure error", version 8.1.5

      You Asked
I can't connect using JDBC oci driver :
I use JDK 1.2.2

My program test.java

import java.sql.*;
import oracle.jdbc.driver.*;

public class test {

   public static int RowCount(String Table_name) throws SQLException {
    int rows = 0;
    try {
        Connection cnx =
DriverManager.getConnection("jdbc:default:connection:");
        String sql = "SELECT COUNT(*) FROM "+Table_name;
            Statement stmt = cnx.createStatement();
            ResultSet rset = stmt.executeQuery(sql);
            while (rset.next()) {rows = rset.getInt(1);}
            rset.close();
            stmt.close();
        } catch (SQLException e) {System.err.println(e.getMessage());}
    return rows;
   }

}

The Result :

SQL> call testjavalib_3('OBJ_ORA8') into :count;
java.lang.NoSuchMethodException: No applicable method found

at
oracle.aurora.util.JRIExtensions.getMaximallySpecificMethod(JRIExtensions.java)

at
oracle.aurora.util.JRIExtensions.getMaximallySpecificMethod(JRIExtensions.java)

No suitable driver

Appel terminé.

I tried to change connection but it doesn't work,
Tanks

Julien

      and we said...
It must be an error in your SQL wrapper (the part you didn't show).  If you take
my example below and change the create or replace function to bind to
test.xRowCount -- you'll get that error.  Put it back to bind to test.RowCount
and the example works as below:

ops$tkyte@DEV8I.WORLD> create or replace and compile
  2  java source named "test"
  3  as
  4  import java.sql.*;
  5  import oracle.jdbc.driver.*;
  6
  7  public class test
  8  {
  9     public static int RowCount(String Table_name)
 10     throws SQLException
 11     {
 12     int rows = 0;
 13
 14      Connection cnx = new OracleDriver().defaultConnection();
 15      String sql = "SELECT COUNT(*) FROM "+Table_name;
 16      Statement stmt = cnx.createStatement();
 17      ResultSet rset = stmt.executeQuery(sql);
 18      while (rset.next()) {rows = rset.getInt(1);}
 19      rset.close();
 20      stmt.close();
 21      return rows;
 22     }
 23
 24  }
 25  /

Java created.

ops$tkyte@DEV8I.WORLD>
ops$tkyte@DEV8I.WORLD> create or replace
  2  function testjavalib_3( p_table_name in varchar2 ) return number
  3  as
  4  language java
  5  name 'test.RowCount( java.lang.String ) return java.lang.Int';
  6  /

Function created.

ops$tkyte@DEV8I.WORLD>
ops$tkyte@DEV8I.WORLD> select testjavalib_3('dual') from dual;

TESTJAVALIB_3('DUAL')
---------------------
                    1

ops$tkyte@DEV8I.WORLD>
ops$tkyte@DEV8I.WORLD> variable count number
ops$tkyte@DEV8I.WORLD> set autoprint on
ops$tkyte@DEV8I.WORLD> call testjavalib_3('DUAL') into :count
  2  /

Call completed.

ops$tkyte@DEV8I.WORLD>
ops$tkyte@DEV8I.WORLD> print count

     COUNT
----------
         1

ops$tkyte@DEV8I.WORLD>

I will say -- that for an example such as that -- PLSQL is going to be
infinitely easier and faster (if you are doing lots of SQL, PLSQL is going to be
easier and most likely faster.  doing lots of intense computations - perhaps the
opposite is true).

For example, the same code in PLSQL will be:

ops$tkyte@DEV8I.WORLD> create or replace
  2  function countem( p_tname in varchar2 ) return number
  3  as
  4          l_cnt number;
  5  begin
  6          execute immediate
  7          'select count(*) from ' || p_tname INTO l_cnt;
  8
  9          return l_cnt;
 10  end;
 11  /

Function created.

and thats it -- very small.  When I run it from SQL:

ops$tkyte@DEV8I.WORLD> set timing on

ops$tkyte@DEV8I.WORLD> select table_name, testjavalib_3(table_name)
  2  from user_tables;

TABLE_NAME                     TESTJAVALIB_3(TABLE_NAME)
------------------------------ -------------------------
AGENCY                                                 9
....
TEMP                                                   1

19 rows selected.

Elapsed: 00:00:01.95
ops$tkyte@DEV8I.WORLD> /

TABLE_NAME                     TESTJAVALIB_3(TABLE_NAME)
------------------------------ -------------------------
AGENCY                                                 9
...
TEMP                                                   1

19 rows selected.

Elapsed: 00:00:00.14

The java took about 0.14 (ignore the first run...) and now lets see the PLSQL
based one:

ops$tkyte@DEV8I.WORLD> select table_name, countem(table_name)
  2  from user_tables;

TABLE_NAME                     COUNTEM(TABLE_NAME)
------------------------------ -------------------
AGENCY                                           9
....
TEMP                                             1

19 rows selected.

Elapsed: 00:00:00.17
ops$tkyte@DEV8I.WORLD> /

TABLE_NAME                     COUNTEM(TABLE_NAME)
------------------------------ -------------------
AGENCY                                           9
....
TEMP                                             1

19 rows selected.

Elapsed: 00:00:00.03
ops$tkyte@DEV8I.WORLD> set timing off

0.03 vs 0.14.  The PLSQL code path is just lots smaller in this case (lots less
complex)...

Review &
      Followup

      Rating: 4
why always
      static after public..  November 26, 2001
Reviewer:
      Ak  from India
Hi tom,
why it is always
 public "static" int RowCount...
Always i see "STATIC" after public keyword...
Is this anything to do with oracle...??? I asked some java
guys,but couln't get any satisfactory answer.

Thanks,

Regards
  Ak

          Followup:

A java stored procedure is called from SQL or PLSQL -- not from Java.  Hence,
there is no "self" to pass -- no "instance" defined.

A java stored procedure has at least ONE static entry point -- one member
function that is always there -- even without an instance of that class being
instantiated.  This static method may then "new" other java classes and such but
in order to get "started" you need at least one static mether.

      Rating: 5
I am looking
      for a single query.  December 11, 2002
Reviewer:
      Yong Ke Wu  from USA
I am looking for a single query. Not stored procedure or java. The following
pl/sql is much easier.

set serveroutput on
declare
  cnt  number;
begin
  for c in (select table_name from tabs) loop
      execute immediate 'select count(*) from '||c.table_name into cnt;
      dbms_output.put_line('table_name='||c.table_name||' rows='||cnt);
  end loop;
end;

          Followup:

You'll be looking for a very long time then.

How is that easier? EH?

anyway.... big sigh.

      Rating: 4
Another
      error  June 04, 2003
Reviewer:  Anirudh
       from New delhi,India
Hi Tom,
I am trying to create a Java stored procedure as follows
public class Hello
{  public static String world()
   {
 return "Hell World";

   }

}
Then I run the loadjava utility:

Microsoft Windows 2000 [Version 5.00.2195]
(C) Copyright 1985-2000 Microsoft Corp.

C:\>loadjava -user anirudh/anirudh@hcltech d:\oracle\Hello.class

C:\>

Now I create a function
CREATE OR REPLACE FUNCTION jva RETURN VARCHAR2
AS
BEGIN
 LANGUAGE JAVA NAME 'Hello.world () return java.lang.String';

END;
The I try to call the function but it gives me the following error

anirudh@HCLTECH.WORLD816>var name varchar2(64)
anirudh@HCLTECH.WORLD816>call jva() into :name ;
call jva() into :name
     *
ERROR at line 1:
ORA-29516: Aurora assertion failure: Assertion failure at eox.c:187
Uncaught exception Root of all Java exceptions:

anirudh@HCLTECH.WORLD816>

This is for the first time that I working on Java stored procedures. Please
guide me.
Thanks

          Followup:

seems like a bad install of the java stuff -- that would be an internal error.
works fine for me:

ops$tkyte@ORA817DEV> create or replace and compile
  2  java source named "Hello"
  3  as
  4  public class Hello
  5  {  public static String world()
  6     {
  7   return "Hell World";
  8
  9     }
 10
 11  }
 12  /

Java created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> CREATE OR REPLACE FUNCTION jva RETURN VARCHAR2
  2  AS
  3  LANGUAGE JAVA NAME 'Hello.world () return java.lang.String';
  4  /

Function created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> variable name varchar2(64)
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> call jva() into :name;

Call completed.

ops$tkyte@ORA817DEV> print name

NAME
------------------------------
Hell World

in 815 and 817 -- don't have 816 lying about anymore.  I like create or replace
java personally over loadjava.  Since the amount of java you put into the
database will be measured in small numbers (i mean, you'll use it sparingly of
course, PLSQL being a tad better for performance and data manipulation) it works
nicely.

you'll have to contact support if you are certain java has been properly
installed in the database itself.

      Rating:
      5
  June 04, 2003
Reviewer:
      A reader

      Rating:
      5
Success  June 04, 2003
Reviewer:
      Anirudh  from New Delhi, India
Thanks for the follow up. I tried the things on another database(817)
(previously it was 816....is that a problem?)and was successfull.
Thanks.

      Rating: 3
Unload Java
      classes  July 15, 2003
Reviewer:  Danny Wang
       from NY, USA
Hi Tom,

I am wondering is there a way to unload the Java classes from Oracle. My problem
is that I accidentally deleted the source code of my Java Stored Procedure. If I
could unload the content of the Java class, then by de-compiling it, I could
have my source code back.

Thanks

          Followup:

see the dbms_java package

i haven't tested this, but only cause I don't know how to "decompile" java
;)

in dbms_java, you'll find:

 -- convenience functions to support development environments --
  -- There procedures allow PL/SQL to get at Java Schem Objects.
  -- There are a lot of them, but they can be understood from the
  -- grammar
  --     export_<what>(name, [schema,] lob)
  --
  -- <what> is either source, class or resource
  -- name a varchar argument that is the name of the java schema object
  -- schema is an optional argument, if it is present it is a varchar that
  --   names a schema, if it ommitted the current schema is used
  -- lob is either a BLOB or CLOB.  The contents of the object are placed
  --   into it. CLOB's are allowed only for source and resource (i.e. not
  --   for class). Note that the internal representation of source uses
  --   UTF8 and that is what is stored into the BLOB
  --
  -- If the java schema object does not exist an ORA-29532 (Uncaught Java
  -- exception) will occur.

  procedure export_source(name varchar2, schema varchar2, blob BLOB)
  as language java name
  'oracle.aurora.rdbms.ExportSchemaObjects.exportSource(java.lang.String,
java.lang.String, oracle.sql.BLOB)';

  procedure export_source(name varchar2, blob BLOB)
  as language java name
  'oracle.aurora.rdbms.ExportSchemaObjects.exportSource(java.lang.String,
oracle.sql.BLOB)';

  procedure export_source(name varchar2, schema varchar2, clob CLOB)
  as language java name
  'oracle.aurora.rdbms.ExportSchemaObjects.exportSource(java.lang.String,
java.lang.String, oracle.sql.CLOB)';

  procedure export_source(name varchar2, CLOB clob)
  as language java name
  'oracle.aurora.rdbms.ExportSchemaObjects.exportSource(java.lang.String,
oracle.sql.CLOB)';

  procedure export_class(name varchar2, schema varchar2, blob BLOB)
  as language java name
  'oracle.aurora.rdbms.ExportSchemaObjects.exportClass(java.lang.String,
java.lang.String, oracle.sql.BLOB)';

  procedure export_class(name varchar2, blob BLOB)
  as language java name
  'oracle.aurora.rdbms.ExportSchemaObjects.exportClass(java.lang.String,
oracle.sql.BLOB)';

  procedure export_resource(name varchar2, schema varchar2, blob BLOB)
  as language java name
  'oracle.aurora.rdbms.ExportSchemaObjects.exportResource(java.lang.String,
java.lang.String, oracle.sql.BLOB)';

you might want to write a java program -- to call dbms_java -- to get a blob, to
write it to a file and see if that is just the bytes.

      Rating: 5
Feedback -
      Unload Java classes  July 16, 2003
Reviewer:
      Danny Wang  from NY, USA
Thanks Tom. It worked. I called dbms_java.export_class procedure, wrote blobs to
files and decompiled them. I got all my source files back!!

July 15, 2003 Sathiamathi -- Thanks for the question regarding
      "Deleting Nested table records", version 8i

      You Asked
Hi Tom,

I have a table with following columns. One of the column is consider as nested
table.

tb_users              Datatype
-------------------------------
1. userid          varchar2(15)
2. businessname    varchar2(50)
3. userprivileges  ourprivileges
( Nested table column having type of privilegetype (privilege_id
number(2),privilege_name varchar2(45))

Initially we have assigned some set of application privilege to the user..

Now i wanted to find user with privilege_id = 2

select userid, b.privilege_id from tb_users , table(userprivileges) b
where b.privilege_id = 2

I got the expected output.. we want to delete this privilege from all the user.

i want to delete the nested table record with privilege_id = 2(child table) for
all the user

I can delete privilege of one particular user using following query.

delete from the ( select userprivileges from tb_users where
userid ='sathya') where privilege_id = 2

How to do it for all the user with single query.. Instead of writing procedure
with cursor to do the same.

Thanks in Advance.

Regards,

Sathiamathi

create table tb_users (userid varchar2(15),privileges
userid

      and we said...
Ahh, the foibles of nested tables!  This is (in my opinion) one of the issues
with "OO" approaches here.

You want to have this nice representation as an object -- yet we constantly need
a relational view of the data to get the job done.

My favorite example is a "bank".  The application for the teller would like to
have an object that represents the customer.  They see the customer, with a list
of accounts that have lists of transactions.  Very nice for the teller.  However
at the end of the day, someone asks "how much money is in the bank"?  Well, now
we need a totally different view of the data -- from the perspective of
transactions, not people (you don't want to go to each person object and say
"did you do anything today?" that would not be realistic, you just want to sum
up transactions)....

This is a small extract from my book that describes this issue and offers a
solution for ONE OFF fixes (all of the details are in the book)

....
Previously I stated: 'We must always join, we cannot query the EMP data alone'
but then followed that up with a caveat: 'you can if you really need to'. It is
undocumented and not supported, so use it only as a last ditch method. Where it
will come in most handy is if you ever need to mass update the nested table
(remember, we would have to do that through the DEPT table with a join). There
is an undocumented hint, NESTED_TABLE_GET_REFS, used by EXP and IMP to deal with
nested tables. It will also be a way to see a little more about the physical
structure of the nested tables. This magic hint is easy to discover after you
export a table with a nested table. For example, I exported the table above in
order to get its 'larger' definition from IMP. After doing the export, I found
the following SQL in my shared pool (V$SQL table):

SELECT /*+NESTED_TABLE_GET_REFS+*/ NESTED_TABLE_ID,SYS_NC_ROWINFO$ FROM
"TKYTE"."EMPS_NT"

A simple query like 'select sql_text from v$sql where upper(sql_text) like
?%EMP%' found it for me. If you run this, you'll get some 'magic' results:

tkyte@TKYTE816> SELECT /*+NESTED_TABLE_GET_REFS+*/
  2         NESTED_TABLE_ID,SYS_NC_ROWINFO$
  3  FROM "TKYTE"."EMPS_NT"
  4  /

NESTED_TABLE_ID                  SYS_NC_ROWINFO$(EMPNO, ENAME,
-------------------------------- ------------------------------
9A39835005B149859735617476C9A80E EMP_TYPE(7782, 'CLARK',
                                 'MANAGER', 7839, '09-JUN-81',
                                 2450, 100)

9A39835005B149859735617476C9A80E EMP_TYPE(7839, 'KING',
                                 'PRESIDENT', NULL,
                                 '17-NOV-81', 5000, 100)

Well, this is somewhat surprising, if you describe this table:

tkyte@TKYTE816> desc emps_nt
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 EMPNO                                        NUMBER(4)
 ENAME                                        VARCHAR2(10)
 JOB                                          VARCHAR2(9)
 MGR                                          NUMBER(4)
 HIREDATE                                     DATE
 SAL                                          NUMBER(7,2)
 COMM                                         NUMBER(7,2)

These two columns don?t even show up. They are part of the hidden implementation
of nested tables. The NESTED_TABLE_ID is really a foreign key to the parent
table DEPT_AND_EMP. DEPT_AND_EMP actually has a hidden column in it that is used
to join to EMPS_NT. The SYS_NC_ROWINF$ 'column' is a magic column, it is more of
a function then a column. The nested table here is really an object table (it is
made of an object type) and SYS_NC_INFO$ is the internal way Oracle references
the row as an object, instead of referencing each of the scalar columns. Under
the covers, all Oracle has done for us is to implement a parent/child table with
system generated primary and foreign keys. If we dig a little further, we can
query the 'real' data dictionary to see all of the columns in the DEPT_AND_EMP
table:

tkyte@TKYTE816> select name
  2    from sys.col$
  3   where obj# = ( select object_id
  4                    from user_objects
  5                   where object_name = 'DEPT_AND_EMP' )
  6  /

NAME
------------------------------
DEPTNO
DNAME
LOC
EMPS
SYS_NC0000400005$

tkyte@TKYTE816> select SYS_NC0000400005$ from dept_and_emp;

SYS_NC0000400005$
--------------------------------
9A39835005B149859735617476C9A80E
A7140089B1954B39B73347EC20190D68
20D4AA0839FB49B0975FBDE367842E16
56350C866BA24ADE8CF9E47073C52296

The weird looking column name, SYS_NC0000400005$, is the system-generated key
placed into the DEPT_AND_EMP table. If you dig even further you will find that
Oracle has placed a unique index on this column. Unfortunately however, it
neglected to index the NESTED_TABLE_ID in EMPS_NT. This column really needs to
be indexed, as we are always joining FROM DEPT_AND_EMP to EMPS_NT. This is an
important thing to remember about nested tables if you use them with all of the
defaults as I did above ? always index the NESTED_TABLE_ID in the nested tables!

I've gotten off of the track though at this point. I was talking about how to
treat the nested table as if it were a real table. The NESTED_TABLE_GET_REFS
hint does that for us. We can use that like this:

tkyte@TKYTE816> select /*+ nested_table_get_refs */ empno, ename
  2  from emps_nt where ename like '%A%';

     EMPNO ENAME
---------- ----------
      7782 CLARK
      7876 ADAMS
      7499 ALLEN
      7521 WARD
      7654 MARTIN
      7698 BLAKE
      7900 JAMES
7 rows selected.

tkyte@TKYTE816> update /*+ nested_table_get_refs */ emps_nt
  2  set ename = initcap(ename);
14 rows updated.

tkyte@TKYTE816> select /*+ nested_table_get_refs */ empno, ename
  2  from emps_nt where ename like '%a%';

     EMPNO ENAME
---------- ----------
      7782 Clark
      7876 Adams
      7521 Ward
      7654 Martin
      7698 Blake
      7900 James
6 rows selected.

Again, this is not a documented supported feature. It may not work in all
environments. It has a specific functionality; for EXP and IMP to work. This is
the only environment it is assured to work in. Use it at your own risk. Use it
with caution though and do not put it into production code. Use it for one-off
fixes of data or to see what is in the nested table out of curiosity. The
supported way to report on the data is to unnest it like this:

tkyte@TKYTE816> select d.deptno, d.dname, emp.*
  2  from dept_and_emp D, table(d.emps) emp
  3  /

And that is what you should use in queries and production code.
....
Review
      & Followup

      Rating: 5
How many levels
      of nesting one can have in nested table?  July 15, 2003

Reviewer:  A reader

          Followup:

in 8i, one
in 9i, practically speaking, just a few - it gets VERY cumbersome beyond that.

July 15, 2003 William -- Thanks for the question regarding
      "Greater / less than in Decode", version 8.1.7

      You Asked
Hi Tom,
   Could you please tell me how can I determine if a column is greater
or less than a value inside DECODE. I put < or > before a number but
I was told it was invalid. (eg. Decode(sale_amount, > 100000, 'High Level') )
Thanks

Bill

      and we said...
Well, in 817, I would just use CASE:

select CASE when sale_amount > 250000, 'Really High Level'
            when sale_amount > 100000, 'High Level'
            else 'Not so high'
       END
  from t

Using decode, we can do this with SIGN:

select decode( sign( 100000 - sale_amount ) ,
               -1, 'High Level',
               'Not so high' )
  from t;
Review & Followup

      Rating:
      5
  June 21, 2002
Reviewer:
      A reader
Should we use When ... then instead of When ... , ?

          Followup:

scott@ORA817DEV.US.ORACLE.COM> select CASE when sal > 250000 then 'Really High
Level'
  2              when sal > 100000 then 'High Level'
  3              else 'Not so high'
  4         END
  5    from emp
  6  /

yes, don't know what I was thinking with the comma ;)

      Rating: 3
Be careful with
      CASE in pre-9i   June 21, 2002
Reviewer:  Colin
      Davies  from Pasadena, CA, USA
The CASE SQL statement will NOT work in PL/SQL until 9i.

          Followup:

It is easy to use it however in 8i.  You can

a) use a view -- very nice for encapsulation anyway
b) use a ref cursor

begin
   open l_cursor for 'select CASE .....' using <binds>;

   loop
      fetch l_cursor into ....;

      Rating: 3
CASE in
      8i  June 21, 2002
Reviewer:  John Ridgway
       from Houston,TX
I have used the CASE statement in PL/SQL in 8.1.7 without any problems.  The
trick is using dynamic SQL.

      Rating: 5
How to do using
      "DECODE"  June 21, 2002
Reviewer:  Suresh

Tom,
  How to get the following output by using "DECODE" command.

select empno, ename, SAL,
CASE when SAL > 4000 THEN 'ABOVE 4000'
     when SAL > 2500 THEN 'ABOVE 2500'
     ELSE 'VERY LESS' END
FROM EMP;

    EMPNO ENAME             SAL CASEWHENSA
--------- ---------- ---------- ----------
     7369 SMITH             800 VERY LESS
     7499 ALLEN            1600 VERY LESS
     7521 WARD             1250 VERY LESS
     7566 JONES            2975 ABOVE 2500
     7654 MARTIN           1250 VERY LESS
     7698 BLAKE            2850 ABOVE 2500
     7782 CLARK            2450 VERY LESS
     7788 SCOTT            3000 ABOVE 2500
     7839 KING             5000 ABOVE 4000
     7844 TURNER           1500 VERY LESS
     7876 ADAMS            1100 VERY LESS
     7900 JAMES             950 VERY LESS
     7902 FORD             3000 ABOVE 2500
     7934 MILLER           1300 VERY LESS

I tried with the following statement, but I have issues with SALARY 5000

select EMPNO, ENAME, SAL,
decode( sign( 2500 - SAL ), -1, 'ABOVE 2500','VERY LESS')   from EMP;

Thanks,
Suresh

          Followup:

scott@ORA817DEV.US.ORACLE.COM> select empno, ename, SAL,
  2  decode( sign(4000-sal),
  3                  -1, 'Above 4000',
  4                  decode( sign(2500-sal),
  5                                  -1, 'Above 2500', 'Very Less' ) ),
  6  CASE when SAL > 4000 THEN 'ABOVE 4000'
  7       when SAL > 2500 THEN 'ABOVE 2500'
  8       ELSE 'VERY LESS' END
  9  FROM EMP;

     EMPNO ENAME             SAL DECODE(SIG CASEWHENSA
---------- ---------- ---------- ---------- ----------
      7369 SMITH             800 Very Less  VERY LESS
      7499 ALLEN            1600 Very Less  VERY LESS
      7521 WARD             1250 Very Less  VERY LESS
      7566 JONES            2975 Above 2500 ABOVE 2500
      7654 MARTIN           1250 Very Less  VERY LESS
      7698 BLAKE            2850 Above 2500 ABOVE 2500
      7782 CLARK            2450 Very Less  VERY LESS
      7788 SCOTT            3000 Above 2500 ABOVE 2500
      7839 KING             5000 Above 4000 ABOVE 4000
      7844 TURNER           1500 Very Less  VERY LESS
      7876 ADAMS            1100 Very Less  VERY LESS
      7900 JAMES             950 Very Less  VERY LESS
      7902 FORD             3000 Above 2500 ABOVE 2500
      7934 MILLER           1300 Very Less  VERY LESS

14 rows selected.

      Rating: 3
Why use decode
      for ranges?  June 21, 2002
Reviewer:  John
      Ridgway  from Houston
In my experience, decode is good for getting one value to be replaced with
another (a decode).  Doing ranges seems to be better handled with CASE
statements.  If you have the query working using a CASE statement then why try
to get it to work using a DECODE?  If it's a problem of getting it to work with
PL/SQL then just use dynamic SQL.  Tom has got an example here.

http://asktom.oracle.com/pls/ask/f?p=4950:8:952378::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:948277183607,%7BPLS%7D%20and%20%7B00103%7D%20and%20%7BEncountered%7D%20and%20%7Bthe%7D%20and%20%7Bsymbol%7D%20and%20%7BCASE%7D

          Followup:

Agreed -- before 816 however, there was no other option.

      Rating: 5
How to do
      this?  June 22, 2002
Reviewer:  Sikandar Hayat
      Awan  from Pindi Bhattian - Pakistan
Hi,
The following is a table and my requirement is R - I (Receipt - Issue) for each
code.
        CODE T TDATE               QTY
------------ - ---------- ------------
           1 I 29/05/2002            2
           1 I 28/05/2002            6
           1 R 28/05/2002           15
           1 R 29/05/2002           52
           1 R 28/05/2002            5
           2 R 28/05/2002           45
           3 R 28/05/2002            3
           3 R 28/05/2002           12
           1 I 28/05/2002            1
           1 I 31/05/2002            2
           1 I 31/05/2002            6
           1 I 28/05/2002            1
           1 R 31/05/2002           15
           1 R 31/05/2002           52
           1 R 28/05/2002            5
           2 R 28/05/2002           45
           3 R 28/05/2002            3
           3 R 28/05/2002           12

Thanks for ur co.

          Followup:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select code,
  2         sum( decode( type, 'I', qty, 0 ) ) itype,
  3         sum( decode( type, 'R', qty, 0 ) ) rtype,
  4         sum( decode( type, 'R', qty, 0 ) ) -sum( decode( type, 'I', qty, 0 )
) diff
  5    from t
  6   group by code
  7  /

      CODE      ITYPE      RTYPE       DIFF
---------- ---------- ---------- ----------
         1         18        144        126
         2          0         90         90
         3          0         30         30

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

      Rating: 3
One More
      Way  June 22, 2002
Reviewer:  ANUP SINGH
       from LONDON
I Think we Can Also Use Greatest And Least Sql Functions ..

Select Decode( Greatest( Sale_Amount, 100000),   Sale_Amount, 'high Level', 'Not
So High' )
From T ;

          Followup:

yes, it is not any more readable.  I would still go with CASE personally.

scott@ORA817DEV.US.ORACLE.COM> select empno, ename, SAL,
  2         decode( sign(4000-sal),
  3                      -1, 'Above 4000',
  4                      decode( sign(2500-sal),
  5                                      -1, 'Above 2500', 'Very Less' ) ),
  6         decode( greatest(sal,4000),
  7                 4000, decode( greatest(sal,2500),
  8                               2500, 'Very Less', 'Above 2500' ), 'Above
4000' ),
  9      CASE when SAL > 4000 THEN 'ABOVE 4000'
 10           when SAL > 2500 THEN 'ABOVE 2500'
 11           ELSE 'VERY LESS' END
 12      FROM EMP;

     EMPNO ENAME             SAL DECODE(SIG DECODE(GRE CASEWHENSA
---------- ---------- ---------- ---------- ---------- ----------
      7369 SMITH             800 Very Less  Very Less  VERY LESS
      7499 ALLEN            1600 Very Less  Very Less  VERY LESS
      7521 WARD             1250 Very Less  Very Less  VERY LESS
      7566 JONES            2975 Above 2500 Above 2500 ABOVE 2500
      7654 MARTIN           1250 Very Less  Very Less  VERY LESS
      7698 BLAKE            2850 Above 2500 Above 2500 ABOVE 2500
      7782 CLARK            2450 Very Less  Very Less  VERY LESS
      7788 SCOTT            3000 Above 2500 Above 2500 ABOVE 2500
      7839 KING             5000 Above 4000 Above 4000 ABOVE 4000
      7844 TURNER           1500 Very Less  Very Less  VERY LESS
      7876 ADAMS            1100 Very Less  Very Less  VERY LESS
      7900 JAMES             950 Very Less  Very Less  VERY LESS
      7902 FORD             3000 Above 2500 Above 2500 ABOVE 2500
      7934 MILLER           1300 Very Less  Very Less  VERY LESS

14 rows selected.

      Rating: 4
Case or
      decode  June 24, 2002
Reviewer:  Sa?a
       from Belgrade, Yugoslavia
Hi Tom,

Just want to compare one example with "case" and "decode".

1.
select
 decode((select deptno from dept d where d.deptno = e.deptno), 10,'A', 20, 'B',
30, 'REST' ) as test
from emp e

2.
select
 (case
  when (select deptno from dept d where d.deptno = e.deptno) = 10
   then 'A'
  when (select deptno from dept d where d.deptno = e.deptno) = 20
     then 'B'
  else 'REST'
  end) as test
from emp e

How many times inline select would be executed in "case" example (per record )?
Is it better to use decode in such cases although I prefer "case" because it's
much more readably then "decode".

Thanks

Sasa

          Followup:

Well, here is a pretty cool optimization.  The answer is "none of the above" --
it apparently doesn't do the case per record as you and I understand it.

In any case, the decode you have would be superior to the case statement
performance wise HOWEVER, in 9i, there is a searched case (demonstrated below)
which corrects that.

Consider this example:

ops$tkyte@ORA9I.WORLD> create or replace package demo_pkg
  2  as
  3     cnt number;
  4     function f( x in number ) return number;
  5  end;
  6  /

Package created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> create or replace package body demo_pkg
  2  as
  3
  4  function f( x in number ) return number
  5  is
  6  begin
  7      cnt := cnt + 1;
  8      return x;
  9  end;
 10
 11  end;
 12  /

Package body created.

ops$tkyte@ORA9I.WORLD> drop table emp;
Table dropped.

ops$tkyte@ORA9I.WORLD> drop table dept;
Table dropped.

ops$tkyte@ORA9I.WORLD> create table emp
  2  as
  3  select rownum empno, mod(rownum,10) deptno
  4    from all_objects
  5   where rownum <= 500;

Table created.

ops$tkyte@ORA9I.WORLD> create table dept
  2  as
  3  select rownum-1 deptno
  4    from all_objects
  5   where rownum <= 10;

Table created.

ops$tkyte@ORA9I.WORLD> set autotrace traceonly

ops$tkyte@ORA9I.WORLD> exec demo_pkg.cnt := 0;
PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> select decode( (select demo_pkg.f(deptno)
  2                    from dept
  3                   where deptno = emp.deptno), 10, 'a', 20, 'b', 'rest' ) as
test
  4    from emp;

500 rows selected.

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

Statistics
----------------------------------------------------------
         38  recursive calls
         22  db block gets
        127  consistent gets
          5  physical reads
          0  redo size
      20342  bytes sent via SQL*Net to client
       4826  bytes received via SQL*Net from client
         35  SQL*Net roundtrips to/from client
          7  sorts (memory)
          0  sorts (disk)
        500  rows processed

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> exec dbms_output.put_line( 'cnt = ' || demo_pkg.cnt );
cnt = 10

PL/SQL procedure successfully completed.

So here, it executed our query 10 times -- the number of dept rows (if you
vary the number of rows in dept -- this count will vary accordingly).  We might
have expected it to do this 500 times but no - only 10.  It apparently built a
hash table out there (or some other structure) and just joined to that....

ops$tkyte@ORA9I.WORLD> exec demo_pkg.cnt := 0;
PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> select (case
  2            when (select demo_pkg.f(deptno)
  3                    from dept
  4                   where deptno = emp.deptno) = 10 then 'a'
  5            when (select demo_pkg.f(deptno)
  6                    from dept
  7                   where deptno = emp.deptno) = 20 then 'b'
  8            else 'rest'
  9          end) as test
 10    from emp;

500 rows selected.

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

Statistics
----------------------------------------------------------
          0  recursive calls
         42  db block gets
        181  consistent gets
          0  physical reads
          0  redo size
      20342  bytes sent via SQL*Net to client
       4826  bytes received via SQL*Net from client
         35  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        500  rows processed

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> exec dbms_output.put_line( 'cnt = ' || demo_pkg.cnt );
cnt = 20

PL/SQL procedure successfully completed.

Here, we have 2x the calls (but still not 500)

ops$tkyte@ORA9I.WORLD> exec demo_pkg.cnt := 0;

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> select (case (select demo_pkg.f(deptno)
  2                  from dept
  3                 where deptno = emp.deptno)
  4            when 10 then 'a'
  5            when 20 then 'a'
  6            else 'rest'
  7          end) as test
  8    from emp;

500 rows selected.

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

Statistics
----------------------------------------------------------
          0  recursive calls
         22  db block gets
        111  consistent gets
          0  physical reads
          0  redo size
      20342  bytes sent via SQL*Net to client
       4826  bytes received via SQL*Net from client
         35  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        500  rows processed

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> exec dbms_output.put_line( 'cnt = ' || demo_pkg.cnt );
cnt = 10

PL/SQL procedure successfully completed.

and there is the searched case expression that gets it back to normal..

      Rating:
      5
Great  June 27, 2002
Reviewer:  Sikandar
      Hayat Awan  from Pindi Bhattian - Pakistan

      Rating: 4
SINGLE INSERT
      FROM MULTIPLE TABLE  June 18, 2003
Reviewer:
      vinodhps  from India, chennai
Hi Tom,
iam trying to insert into single table from 2 select statements. Is it possible
.

For example i got table t_raw_matl in which i have to insert.

DESC T_RAW_MATL

IND_MATL_DAY          NUMBER
IMP_MATL_DAY          NUMBER
IND_MATL_MONTH        NUMBER
IMP_MATL_MONTH        NUMBER
IND_MATL_YEAR         NUMBER
IMP_MATL_YEAR         NUMBER

other 2 tables are 1)t_indegenious_raw_matl.
                   2)t_imported_raw_matl.

Here what iam doing is

1)select sum(qty) from t_indegenious_raw_matl;
2)select sum(qty) from t_imported_raw_matl;

Is it possible to DO

insert into t_raw_matl
CASE WHEN (SOME CONDITION) THEN
select SUM(CASE WHEN TRUNC(DATE_OF_CONS)=TRUNC(SYSDATE)
THEN Qty
ELSE 0
END)DAY,
 SUM(CASE WHEN TRUNC(DATE_OF_CONS,'MM')=TRUNC(SYSDATE,'MM')
THEN Qty
ELSE 0
END)MONTH from t_indegenious_raw_matl
ELSE
select SUM(CASE WHEN TRUNC(DATE_OF_CONS)=TRUNC(SYSDATE)
THEN Qty
ELSE 0
END)DAY,
 SUM(CASE WHEN TRUNC(DATE_OF_CONS,'MM')=TRUNC(SYSDATE,'MM')
THEN Qty
ELSE 0
END)MONTH from t_imported_raw_matl
END;

OR

insert into t_raw_matl
select sum(qty) from t_indegenious_raw_matl
select sum(qty) from t_imported_raw_matl;

IS THERE ANY EASY METHOD TO DO THIS TOM.

Thanks

          Followup:

union all?

      Rating: 5
Case Case
      Case...  July 15, 2003
Reviewer:  Sachin
       from Germany
Hi tom....
I wrote this query..it does what it needs to:
We need to send this query a date...and this is what it should do:
> 0 < 1 -- LESS THAN 1 MONTH
>= 1 < 2 -- 1 MONTH
>= 2 < 3 -- 2 MONTHS
>= 3 < 4 -- 3 MONTHS
>= 4 < 5 -- 4 MONTHS
....
.....
>= 11 < 12 -- 11 months
>= 12 < 24 -- 1 year
>= 24 < 36 -- 2 years
>= 36 < 48 -- 3 years
......
........

I was just wondering..if there is a better way to write it..I will be converting
into a function:
SELECT CASE WHEN FLOOR((FLOOR(MONTHS_BETWEEN(TRUNC(SYSDATE),TO_DATE
('07-JUL-2003'))))/12) > 0 THEN
            CASE
               WHEN FLOOR((FLOOR(MONTHS_BETWEEN(TRUNC(SYSDATE),TO_DATE
('07-JUL-2003'))))/12) = 1 THEN 1 || ' Year'
               ELSE FLOOR((FLOOR(MONTHS_BETWEEN(TRUNC(SYSDATE),TO_DATE
('07-JUL-2003'))))/12) || ' Years'

            END
            WHEN FLOOR((FLOOR(MONTHS_BETWEEN(TRUNC(SYSDATE),TO_DATE
('07-JUL-2003'))))/12) = 0 THEN
            CASE
                WHEN FLOOR(MONTHS_BETWEEN(TRUNC(SYSDATE),TO_DATE
('07-JUL-2003'))) = 0 THEN 'Less than 1 Month'
                WHEN FLOOR(MONTHS_BETWEEN(TRUNC(SYSDATE),TO_DATE
('07-JUL-2003'))) = 1 THEN '1 Month'
                ELSE FLOOR(MONTHS_BETWEEN(TRUNC(SYSDATE),TO_DATE
('07-JUL-2003'))) || ' Months'
            END
            ELSE NULL
       END
FROM DUAL;

          Followup:

no version, oh well

i would probably use an INLINE view to hide the floor(months.....)

so the query would be:

select case when N < 1 then .....
       ...
  from ( select FLOOR(MONTHS_BETWEEN(TRUNC(SYSDATE),TO_DATE('07-JUL-2003')) N
           from dual );

      Rating:
      5
case  July 16, 2003
Reviewer:
      Sachin  from Germany
Sorry...about the version..
8.1.7.4

Thanks...

      Rating:
      5
  July 16, 2003
Reviewer:  Chuck
      Jolley  from OKC, OK USA
This is how I write these using decode.
It seems easier to read to me than nested decodes if there are a lot of paths.
Though, of course, now case is even better.

chajol@tax.cupid> ed
Wrote file afiedt.buf

  1  select empno, name, sal,
  2         decode(-1,
  3                sign(4000 - sal), 'ABOVE 4000',
  4                sign(2500 - sal), 'ABOVE 2500',
  5                'VERY LESS')
  6* from emp
chajol@tax.cupid> /

     EMPNO NAME       SAL DECODE(-1,
---------- --------- ---- ----------
      7369 SMITH      800 VERY LESS
      7499 ALLEN     1600 VERY LESS
      7521 WARD      1250 VERY LESS
      7566 JONES     2975 ABOVE 2500
      7654 MARTIN    1250 VERY LESS
      7698 BLAKE     2850 ABOVE 2500
      7782 CLARK     2450 VERY LESS
      7788 SCOTT     3000 ABOVE 2500
      7839 KING      5000 ABOVE 4000
      7844 TURNER    1500 VERY LESS
      7876 ADAMS     1100 VERY LESS
      7900 JAMES      950 VERY LESS
      7902 FORD      3000 ABOVE 2500
      7934 MILLER    1300 VERY LESS

14 rows selected.

chuck

July 15, 2003 Darren -- Thanks for the question regarding
      "Selecting from THE (cast ", version 8.1.7

      You Asked
Tom,

i have an object type containing 1 varchar2(30) column and an object table of
that. I can CAST this and select from it with no problems:

("product" table lets say has an id and name)

create type prod_typ as object (
  name   varchar2(30)
);
/
create type prod_tab as table of prod_typ
/

now, in pl/sql, i can do this:

select id
  into table_of_ids
  from product prd,
       the (select cast (pt_prod_in as prod_tab)
              from dual
           ) prt
 where prod_name = prt.name;

however i want to do the same based on an object table defined as:

create type varchar2_tab as table of varchar2(30)
/

I've tried a few  things like:

select id
  into table_of_ids
  from product prd,
       the (select cast (pt_prod_in as varchar2_tab)
              from dual
           ) prt
 where prod_name = prt;

or prt.*..etc, however it always complins upon compiling the code.

Thanks.

Darren.

      and we said...
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace type varchar2_tab as table
of varchar2(30)
  2  /

Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_data varchar2_tab := varchar2_tab( 'SCOTT', USER, 'SYSTEM' );
  3  begin
  4      for x in ( select all_users.created, b.column_value
  5                   from all_users, TABLE( cast( l_data as varchar2_tab ) ) b
  6                  where all_users.username = b.COLUMN_VALUE )
  7      loop
  8          dbms_output.put_line( x.column_value || ', ' || x.created );
  9      end loop;
 10  end;
 11  /
SCOTT, 13-JUN-02
OPS$TKYTE, 02-DEC-02
SYSTEM, 28-AUG-01

PL/SQL procedure successfully completed.

COLUMN_VALUE is the name assigned to a table type column when it is constructed
directly on one of the scalar types such as varchar2, number, dates.

Also, the "THE" syntax is the "old style" -- replaced by the new (easier)
TABLE() function.
Review & Followup

      Rating:
      5
  December 17, 2002
Reviewer:
      Darren  from London, UK
Thanks Tom,

a very useful piece of information.

Regards.

      Rating: 4
performance
      issue  July 15, 2003
Reviewer:  Charlie

Hi Tom,

I used "table(cast..."  syntax a lot.  One thing I found that when you join
"table(cast" virtual table with other tables, most of time the explain plan I
got is table FULL SCAN for other tables(big tables).  Why is that?  Though you
and I hate using hints, but I can't think of any other good solutions to boost
"table(cast" involved queries.

Is there any trick to improve the query performance without using hint?  BTW,
all the tables are analyzed and I use CBO(DB version is 9.2.0.3).

thanks,

          Followup:

table(cast...) functions are treated by the cbo as table sources that return
aboutg 8k rows.

They generally do not, they generally return far fewer.

hence, the plans might be suboptimal for that reason.

Here, a hint might be necessary to "help it along" -- try FIRST_ROWS before
others


Subject | Article | Last Updated (East coast time)
Foreign Key Indexes | article only | Jul 16, 2003 09:18 am
create index or not | article only | Jul 16, 2003 09:25 am
JDBC vs Oracle NET8 Connection Pooling | article only | Jul 16, 2003 11:30 am
logging | article only | Jul 16, 2003 04:15 pm
problem with long table name | article only | Jul 17, 2003 10:36 am
Multiple | article only | Jul 17, 2003 10:41 am
Oracle Job scheduler is changing time | article only | Jul 17, 2003 03:47 pm
garbage cleanup, sql query | article only | Jul 17, 2003 04:03 pm
Intermedia text indexing | article only | Jul 17, 2003 07:41 pm
Hash joining versus index | article only | Jul 17, 2003 09:10 pm
Select Count(1): How it works | article only | Jul 17, 2003 09:13 pm
Database startup | article only | Jul 18, 2003 08:36 am
Block Corruption | article only | Jul 18, 2003 08:42 am
rollback segment | article only | Jul 18, 2003 08:43 am
sql loader and date | article only | Jul 18, 2003 08:44 am
CONVERSION DATATYPE | article only | Jul 18, 2003 08:45 am
Advantage of EUS with Thin Driver vs Proxy user with OCI driver | article only | Jul 18, 2003 05:07 pm
Reclaim space | article only | Jul 19, 2003 11:01 am
how to import sequence | article only | Jul 19, 2003 11:14 am
Triggers -- and transactions. | article only | Jul 19, 2003 11:17 am
Analytical Functions- a brief introduction | article only | Jul 19, 2003 11:19 am
Nologging does not work (yes it does...) | article only | Jul 19, 2003 11:21 am
how to get around Dbms_output limitations | article only | Jul 19, 2003 11:23 am
Can we access PLSQL table as an OUT parameter from a Procedure Using JDBC. | article only | Jul 19, 2003 11:24 am
interMedia -- how to index OS files not stored in the database. | article only | Jul 19, 2003 11:24 am
Need some help about Dynamic SQL | article only | Jul 19, 2003 11:28 am
Intermedia text and indexing | article only | Jul 19, 2003 11:31 am
Using where clause in collection | article only | Jul 19, 2003 11:39 am
Questions on Oracle PC 9i | article only | Jul 20, 2003 09:18 am
ARCHIVELOG or NOT ARCHIVELOG? | article only | Jul 20, 2003 03:53 pm
DBMS_OBFUSCATION_TOOLKIT.DES3GetKey , MD5 Functions | article only | Jul 20, 2003 09:16 pm
Automating purge statspack data | article only | Jul 21, 2003 08:12 am
How to find out how much rollback a session has to do | article only | Jul 21, 2003 08:50 am
Writing a procedure in the declaration section of another procedure | article only | Jul 21, 2003 03:21 pm
Determine whether update is coming from trigger | article only | Jul 21, 2003 03:27 pm
Number of extents | article only | Jul 21, 2003 03:29 pm
server in one time zone/clients in many | article only | Jul 22, 2003 08:01 am
Training Developers to write efficient SQL | article only | Jul 22, 2003 08:01 am
hextoraw, rawtohex question | article only | Jul 22, 2003 08:29 am
SQL Query Help!! | article only | Jul 22, 2003 08:31 am
URGENT...........regarding auto commit | article only | Jul 22, 2003 08:41 am
Some Interview Questions | article only | Jul 22, 2003 02:52 pm
How do I get a summary of previous year for a SKU in a view? | article only | Jul 22, 2003 02:57 pm
floating point arithmetic | article only | Jul 22, 2003 03:04 pm
Open Cursor Count... | article only | Jul 22, 2003 03:05 pm
BEGIN anonymous; END; | article only | Jul 22, 2003 08:11 pm
Statistics and optimization with VARRAYs | article only | Jul 22, 2003 08:13 pm
row(s) 1 - 47 of 47
July 16, 2003 Bob -- Thanks for the question regarding "Foreign Key Indexes", version 9.0.1
You Asked
Tom,
we have a system that has strict RI across tables, and has extensive foreign key
indexing. Over the growth of the system, some performance indexes have been
added for the querys that make some of the foreign key indexes seems redundant.
Originally, there was an index on foreign key A, and later an index was added on
fields A, B, and C. If I alter index index_only_on_A monitoring usage, and see
no use, would these be good candidate indexes to remove? Or because they are the
foreign key indexes, should they stay? I am unclear on this. Thanks
 Bob Maggio
and we said...
if the foreign key is A, *any* index that has A on the leading edge will
suffice.

if the only reason the index(a) exists is for the fkey, and you have
index(a,b,c) -- you can drop index(a)
Review & Followup
Rating: 3
July 16, 2003
Reviewer: Dave R from Atlanta, GA USA
Will it make any difference in performance if I create a foreign key index with
just the reference column (deptno, for instance) or a composite key by joining
that ref column with primary key (deptno, empno) when I join the two tables
which returns column values from both tables?

create table branch(branch_id, branch_nm);
*primary key on branch_id

create table dept(dept_id, dept_nm, branch_id);
*primary key on dept_id
*foreign key on branch_id

create table emp(emp_id, emp_nm, dob, dept_id);
*primary key on emp_id
*foreign key on dept_id

case 1:
Now, I want to query emp_nm and branch_nm with emp_id as input. Will it make it
faster if the foreign key index on department table has (branch_id, dept_id)
rather than just branch_id ?

case 2:
What if I want to query emp_nm and branch_nm with dept_id as input ?

case 3:
What if I want to query the same above info but with an inpout of emp_nm (empnm
like 'TOM%'). Will it be faster if I create a composite index on (emp_nm,
dept_id) to avoid table scan and just do index scan ?

Followup:
1) sure it could cause we'd never have a table access by rowid into dept

2) same answer as #1

3) yes.

July 16, 2003 -- Thanks for the question regarding "create index or not", version 817
You Asked
Hi Tom
I have a batch process that loads some tables using inserts.

The statements I am concerned about are of the following order
drop index <indexontemp>;

insert into temp ...;

create index <indexontemp> on temp;

So what we are doing is ,first dropping the index on the table being inserted
into then do the Insert & then create it back on the same table.
All this is in the same process.

SO dont you think then it doesnt matter to drop the index on the first place as
it is being created again. So the time for this process to finish would be the
same if the index was left on the table & was updated in the Insert process?
Also is there some overhead for creating index as compared to an already
existing index being updated through the insert process?

I would greatly appreciate if you could help me get some clarity.

Thanks
and we said...
It depends.  If the table is going to get LOTS of rows -- not maintaining the
indexes makes alot of sense.

I myself might set the indexes UNUSABLE (in the event of a failure AFTER the
drop, before the rebuild -- that way I don't "lose" an index by accident!).

It could look like this:

ops$tkyte@ORA717DEV.US.ORACLE.COM> create table t as select * from all_objects
where 1=0;
Table created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> create index t_idx1 on t(object_name);
Index created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> create index t_idx2 on t(object_id);
Index created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> exec :start := dbms_utility.get_time;
PL/SQL procedure successfully completed.

ops$tkyte@ORA717DEV.US.ORACLE.COM> alter index t_idx1 unusable;
Index altered.

ops$tkyte@ORA717DEV.US.ORACLE.COM> alter index t_idx2 unusable;
Index altered.

ops$tkyte@ORA717DEV.US.ORACLE.COM> alter session set skip_unusable_indexes=true;
Session altered.

ops$tkyte@ORA717DEV.US.ORACLE.COM> insert /*+ append */ into t select * from
all_objects;
16899 rows created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA717DEV.US.ORACLE.COM> insert /*+ append */ into t select * from
all_objects;
16899 rows created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA717DEV.US.ORACLE.COM> insert /*+ append */ into t select * from
all_objects;
16899 rows created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA717DEV.US.ORACLE.COM> insert /*+ append */ into t select * from
all_objects;
16899 rows created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA717DEV.US.ORACLE.COM> alter index t_idx1 rebuild nologging;
Index altered.

ops$tkyte@ORA717DEV.US.ORACLE.COM> alter index t_idx2 rebuild nologging;
Index altered.

ops$tkyte@ORA717DEV.US.ORACLE.COM> exec dbms_output.put_line(
(dbms_utility.get_time-:start) || ' hsecs' );
2309 hsecs
PL/SQL procedure successfully completed.

compare that now with leaving the indices in place:

ops$tkyte@ORA717DEV.US.ORACLE.COM> create table t as select * from all_objects
where 1=0;
Table created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> create index t_idx1 on t(object_name);
Index created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> create index t_idx2 on t(object_id);
Index created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> exec :start := dbms_utility.get_time;
PL/SQL procedure successfully completed.

ops$tkyte@ORA717DEV.US.ORACLE.COM> insert /*+ append */ into t select * from
all_objects;
16899 rows created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA717DEV.US.ORACLE.COM> insert /*+ append */ into t select * from
all_objects;
16899 rows created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA717DEV.US.ORACLE.COM> insert /*+ append */ into t select * from
all_objects;
16899 rows created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA717DEV.US.ORACLE.COM> insert /*+ append */ into t select * from
all_objects;
16899 rows created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA717DEV.US.ORACLE.COM> exec dbms_output.put_line(
(dbms_utility.get_time-:start) || ' hsecs' );
4686 hsecs

PL/SQL procedure successfully completed.

so, there are some efficiencies to be gained by rebuilding, instead of
maintaining the index data structures during a mass load.  This is only true of
course for a mass load -- the proportion of newly loaded data to existing data
must be sizable (no, i won't define sizable as your mileage may vary, benchmark
it on your stuff and see)
Review & Followup
Rating: 4
Reader October 20, 2001
Reviewer: Reader from USA
Tom,

"
I myself might set the indexes UNUSABLE (in the event of a failure AFTER the
drop, before the rebuild -- that way I don't "lose" an index by accident!).
"

What kind of failure that we forsee here

Thanks

Followup:
say after you drop the index (that commits, they are gone)
and after you load the data and commit it

the power goes out..  your load succeeded to the point it got to but it didn't
finish.

Now, the users start using the data without any indexes!  performance is down
the tubes and you are wondering why.  you find the missing indexes after a
couple of hours and put them back and everything is great.

if you set the indexes unusable -- you will get ERRORS immediately, notifying
you the data load failed, you fix it right away, you don't waste time looking
for the reason, you are told the reason.

Rating: 4
October 21, 2001
Reviewer: A reader
Tom
Is there a gain in using
alter index <idx> unusable versus

drop index idx;

Also if we use the unusable statement then is it mandatory to use the
alter session set skip_unusable_indexes=true;

Followup:
Sorry that wasn't clear.

The gain is: you don't "accidently lose the index" if the load fails.  With the
drop, you might accidently lose it, with the set unusable -- you cannot "lose
it".

You'll know right away that the index is in need of help, your users will call
you and tell you when they get the error.  Instead of everyone full scanning
your table all day while you sit there and try to figure out why performance
when down the tubes, you'll get some calls in the morning and fix it straight
away.

You set skip_unusable_indexes=true when you want to ignore the fact that the
indexes are unusable, so -- yes, for the load, we must.

Rating: 3
DBA October 22, 2001
Reviewer: ilya from Israel
Tom
The way to load data you showed is OK, however comparison
you made is incorrect since meaning of few APPENDs in
second example is : to rebuild indexes several times before
you've done with load ( remember APPEND rebuilds indexes at the end ).

Followup:
No, it is NOT incorrect.

What is incorrect?

Append rebuilds the indexes at the end -- with logging.  You want to do it
without -- do it my way.

Read the example, it clearly shows

A) there is a difference
B) there is a big difference

Regardless of the number of times I do the append -- i don't care, its going to
be a large difference.  Look:

ops$tkyte@ORA717DEV.US.ORACLE.COM> variable start number
ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> create table t as select * from all_objects;

Table created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> insert /*+ append */ into t select * from
all_objects;
ops$tkyte@ORA717DEV.US.ORACLE.COM> commit;
ops$tkyte@ORA717DEV.US.ORACLE.COM> insert /*+ append */ into t select * from
all_objects;
ops$tkyte@ORA717DEV.US.ORACLE.COM> commit;
ops$tkyte@ORA717DEV.US.ORACLE.COM> insert /*+ append */ into t select * from
all_objects;
ops$tkyte@ORA717DEV.US.ORACLE.COM> commit;

ops$tkyte@ORA717DEV.US.ORACLE.COM> create table t1 as select * from all_objects
where 1=0;
Table created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> create index t1_idx1 on t1(object_name);
Index created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> create index t1_idx2 on t1(object_id);
Index created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> create table t2 as select * from all_objects
where 1=0;
Table created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> create index t2_idx1 on t2(object_name);
Index created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> create index t2_idx2 on t2(object_id);
Index created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> column value new_val V
ops$tkyte@ORA717DEV.US.ORACLE.COM> select a.name, b.value
  2    from v$statname a, v$mystat b
  3   where a.statistic# = b.statistic#
  4     and a.name = 'redo size'
  5     and b.value > 0
  6  /

NAME                                VALUE
------------------------------ ----------
redo size                          964432

ops$tkyte@ORA717DEV.US.ORACLE.COM> exec :start := dbms_utility.get_time;
PL/SQL procedure successfully completed.

ops$tkyte@ORA717DEV.US.ORACLE.COM> alter index t1_idx1 unusable;
Index altered.

ops$tkyte@ORA717DEV.US.ORACLE.COM> alter index t1_idx2 unusable;
Index altered.

ops$tkyte@ORA717DEV.US.ORACLE.COM> alter session set skip_unusable_indexes=true;
Session altered.

ops$tkyte@ORA717DEV.US.ORACLE.COM> set autotrace on statistics
ops$tkyte@ORA717DEV.US.ORACLE.COM> insert /*+ append */ into t1 select * from t;
67932 rows created.

Statistics
----------------------------------------------------------
       3914  recursive calls
        568  db block gets
       2661  consistent gets
        928  physical reads
      70988  redo size
        998  bytes sent via SQL*Net to client
        825  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          9  sorts (memory)
          0  sorts (disk)
      67932  rows processed

ops$tkyte@ORA717DEV.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA717DEV.US.ORACLE.COM> alter index t1_idx1 rebuild nologging;
Index altered.

ops$tkyte@ORA717DEV.US.ORACLE.COM> alter index t1_idx2 rebuild nologging;
Index altered.

ops$tkyte@ORA717DEV.US.ORACLE.COM> exec dbms_output.put_line(
(dbms_utility.get_time-:start) || ' hsecs' );
1366 hsecs

PL/SQL procedure successfully completed.

ops$tkyte@ORA717DEV.US.ORACLE.COM> select a.name, b.value-&V delta_redo_size
  2    from v$statname a, v$mystat b
  3   where a.statistic# = b.statistic#
  4     and a.name = 'redo size'
  5     and b.value > 0
  6  /
old   1: select a.name, b.value-&V delta_redo_size
new   1: select a.name, b.value-    964432 delta_redo_size

NAME                           DELTA_REDO_SIZE
------------------------------ ---------------
redo size                               171708

ops$tkyte@ORA717DEV.US.ORACLE.COM> select a.name, b.value
  2    from v$statname a, v$mystat b
  3   where a.statistic# = b.statistic#
  4     and a.name = 'redo size'
  5     and b.value > 0
  6  /

NAME                                VALUE
------------------------------ ----------
redo size                         1136140

ops$tkyte@ORA717DEV.US.ORACLE.COM> exec :start := dbms_utility.get_time;
PL/SQL procedure successfully completed.

ops$tkyte@ORA717DEV.US.ORACLE.COM> set autotrace on statistics
ops$tkyte@ORA717DEV.US.ORACLE.COM> insert /*+ append */ into t2 select * from t;
67932 rows created.

Statistics
----------------------------------------------------------
       7386  recursive calls
      11266  db block gets
       5028  consistent gets
       2214  physical reads
   14820480  redo size
        999  bytes sent via SQL*Net to client
        825  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          3  sorts (memory)
          2  sorts (disk)
      67932  rows processed

ops$tkyte@ORA717DEV.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA717DEV.US.ORACLE.COM> exec dbms_output.put_line(
(dbms_utility.get_time-:start) || ' hsecs' );
2888 hsecs

PL/SQL procedure successfully completed.

ops$tkyte@ORA717DEV.US.ORACLE.COM> select a.name, b.value-&V delta_redo_size
  2    from v$statname a, v$mystat b
  3   where a.statistic# = b.statistic#
  4     and a.name = 'redo size'
  5     and b.value > 0
  6  /
old   1: select a.name, b.value-&V delta_redo_size
new   1: select a.name, b.value-   1136140 delta_redo_size

NAME                           DELTA_REDO_SIZE
------------------------------ ---------------
redo size                             14820480

171k versus 14m, 13 sec vs 28 sec.  One big insert only (same number of rows),
same relative amount of time (it was 2x faster above in the original example, it
is still 2x faster in this new example -- only each individual example is faster
cause I did it as a single statement).

So, comparision correct, valid and OK.

Rating: 4
Good example of Index impact on Bulk Updates, but what about Unique Indexes? October 22, 2001
Reviewer: Brad Worsfold from Calgary, AB Canada
Excellent answer Tom, but what about Unique indexes that are used to guarentee
data?  I suppose there is not much other than guarenteeing the data before it
goes into the table some how.

I would be interested in any other ideas around improving data loads and unique
indexes.

Followup:
1) disable the constraint
2) load the data
3) enable the constraint with EXCEPTIONS INTO to record the rowids of rows that
violate the constraint
4) clean up those rows
5) enable the constraint

that'll be faster then having the constraint enforced as you load.

Rating: 2
Yep, that is what you could do alright, but automating this task... October 22, 2001
Reviewer: Brad Worsfold from Calgary, AB Canada
Thanks for the followup Tom.

Unfortunately, I would have to automate this somewhat.  I guess we could read
the UK constraint fields based on the error records and send an email notifying
a support person to investigate, delete any related records and then rebuild the
UK constraint.

I will have to see if this will help us.

Thanks,

Brad

Rating: 5
rebuild vs drop & create July 16, 2003
Reviewer: A reader
Hi

Performance wise which operation is more desirable

rebuild the index or drop it and create it

both can done in nologging, rebuild probably offers more availability if we use
online option but speed?

Followup:
you will find them to be the same.

a rebuild of an unusable index needs to read the base table, just like create.

you would not use an online rebuild after a load since the premise is you set
the index to unusable during the load so the index is just a definition, not a
real thing at that point.

July 16, 2003 Peter -- Thanks for the question regarding "JDBC vs Oracle NET8 Connection Pooling", version 8.1.6
You Asked
1. Can you explain the different between JDBC vs Oracle Net8
connection Pooling?

2. In your previous example about web page:
   display the first 10 rows which match the query, then the
next 10..and so on, giving 2 button (Previous 10 & Next 10)
....

select *
  from ( select a.*, rownum r
           from ( select *
                    from t
                   where x = :host_variable
                   order by y ) a
          where rownum > :LowerBound )
 where r < :HigherBound
/

My question:

Each Page will redefine the LowerBound and HigherBound in
previous SQL, and hit database when user hit next/previous
button?

Thanks for your time,
Peter Cheng
and we said...
1) jdbc connection pooling is typically done by having a common
username log into the database -- the software then maintains a
pool of open connections all under the same user account.  when
your program needs a connect, one of the common connections is
returned to you.  when you are done, it is given back to the
pool for the next guy.  In the database you all appear to be the
same user in the same session.

Net8 connection pooling allows many sessions to share the same
resources.  Each session is separate and distince and will
typically have a unique userid associated with it.  You do not
lose your identity.

2) yes, each page will redefine the lowerbound and hightbound in
the SQL.

If you goto:
http://osi.oracle.com/wa/ask/owa/ask_tom_pkg.search
and view the source, you'll see on that page:

<FORM ACTION="
http://osi.oracle.com/wa/ask/owa/ask_tom_pkg.search
METHOD="POST">
<INPUT TYPE="hidden" NAME="p_start_row" VALUE="11">
<INPUT TYPE="hidden" NAME="p_search_str" VALUE="">
<INPUT TYPE="submit" VALUE="Next">
</FORM>

Here I am storing the starting row for a NEXT button.  If you go
and hit the next, the next page will have increased the
p_start_row value to 21 and a form for a PREV button will have
appeared with the p_start_row = 1.
Review & Followup
Rating: 3
Memory Release in case of pooled connections March 22, 2003
Reviewer: Shailesh from India
1.> In case of JDBC or any other form of connection pooling what happens to
memory (PGA) if the oracle server is shutdown and there are, say, 50 number of
connections pooled? Is the memory released back to OS?
2.> In case of Net8 connection will exiting through sqlplus will release the
memory (PGA) to OS if we issue command shutdown immediate at the server?

Followup:
1) pga is in the dedicated/shared server processes on the server.  if you
shutdown -- they "go away", the memory is NOT in the java application.

2) does not make sense to me.  if you do a shutdown, the pgas all disappear as
the dedicated/shared servers go away.

Rating: 4
Re:- Memory release in case of connection pooling March 24, 2003
Reviewer: Shailesh from India
Thanks for the reply.
When you say "
pga is in the dedicated/shared server processes on the server.  if you shutdown
-- they "go away", the memory is NOT in the java application." does that imply
that if connections are pooled (on different machine than the oracle server) and
if there is no client activity(i.e no DML issued) there will be still memory
occupied by the pooled connections on oracle server in form of PGA?
Also if we use OS tool (We have HP-Unix) such as GLANCE it shows that there is
memory occupied by Oracle process. However if we query V$PROCESS view the column
PGA_USED_MEM shows different values.
Do i conclude that as long as connection is live(but not acxtive i.e no DML's)
memory associated with it PGA is not released to OS?
Please comment

Followup:
Yes, if you have a connection pool that maintains a physical connection -- there
will be resources on the server (memory and otherwise).

Glance and other tools include the amount of SGA region the process has
attached, making them virtually useless here.  the SGA is shared memory and you
are double counting it.  the pga memory reported by Oracle is accurate.

the PGA is owned by the process, correct.  it'll stay there.

Rating: 4
Re :- Memory Release in case of pooled connections March 25, 2003
Reviewer: Shailesh from India
Thank You.

Rating: 5
JDBC / Net8 connection pooling? Choice? May 14, 2003
Reviewer: Tony from Chennai, INDIA
1) When will we have to go for JDBC connection pooling and for Net8 connection
pooling? Which one is better? Could you please explain the pros and cons of the
two?
2) How to create Net*8 connection pooling?

Thanx a lot.

Followup:
if you are using an app server, you would typically use the connection pooling
there -- they cache the physical connection, which is what you want in ntier.

check out the net admin guide, it has all of the setup.

Rating: 5
May 28, 2003
Reviewer: trinh from DC
From the JDBC developer guide:
"Connection pooling functionality minimizes
expensive operations in the creation and closing of sessions."
The manual does not explain futher. Could you please elaborate this?
And the concept of the connection pooling is to cache the connection physically.
How could I find the number of connections cached at OS (unix) level?

Thanks for answer my query.

Trinh

Followup:
connection pooling keeps physical connections in a pool, it does not destroy
them right away.

hence, the expensive operation of opening network connections, creating
processes, etc -- is forgone. you "connect" faster.

you cannot find them as the OS level, they don't exist there.  It is a function
of your application server.  You might be able to use netstat or something to
see connections but it would be pretty hard to determine what is what.  Just use
your application servers utilities/reporting capabilities to see what you got.

Rating: 3
Seeing cached connections in appserver under Unix May 28, 2003
Reviewer: Piotr Jarmuz from Braunschweig, Germany
trinh:

You can use lsof command under Unix to see what sockets the process has opened
(lsof stands for list opened files).

I started sqlplus in one console (PID=4425)

and executed in another window:

lsof -p 4425

...
sqlplus 4425 p_jarmuz    6u  IPv4 6708519             TCP
pitr.asco.de:45609->bill.asco.de:1521 (ESTABLISHED)
...

You can read more about lsof in man section 8.

HTH,
Regards

Rating: 5
May 28, 2003
Reviewer: A reader
Piotr,
On the SUn Solaris:
$ man lsof
No manual entry for lsof.
$

What's the platform of yours? thanks

trinh

Rating: 3
Request for Info May 28, 2003
Reviewer: Piotr Jarmuz from Braunschweig, Germany
Linux Debian/Intel x86

According to the man pages it should be also supported by Solaris, SunOS, AIX,
HP-UX literally all Unix flavors.

As for Solaris itself:
Solaris 2.5.1, 2.6, 7, 8 BETA, and 8 BETA-Refresh

Check your OS version. And maybe on Solaris this command does not come standard
and you must install it separately. You want to ask your sysadmin.

Regards

Rating: 5
JDBC connection pooling May 29, 2003
Reviewer: Sean from Atlanta
I have tried to figure out if the jdbc connection pooling configured in our
3-tier web application that includes 9iAS apache, oc4j and the infrastructure
DB, and the customer DB as the backend. For some reasons we can not obtain the
information of Connection Pooling from the developers.

On the Infra, I found 6 sessions were using " JDBC Thin Client". And on the
customer DB, all the sessions were using" JDBC Thin Client". What these tell us?
If the JDBC Connection Pooling adopted, only those database connections shown
with " JDBC Thin Client" might use the cached connections? The rest of
connections were not considered as "cached" connects at all?

select count(*),PROGRAM ,username from v$session where username not in ('SYS')
group by PROGRAM ,username order by PROGRAM;

On the 9ias Infrastructure:

  COUNT(*) PROGRAM                                       USERNAME
---------- --------------------------------------------- --------------------
         5     JDBC Thin Client                                      DCM
         1     JDBC Thin Client                                      ORASSO_PS
         9         httpd@domain.mil (TNS V1-V3)              ORASSO
         9     httpd@domain.mil (TNS V1-V3)              ORASSO_PUBLIC
        93     oidldapd@domain.mil (TNS V1-V3)           ODSCOMMON
         1     oidmon@domain.mil (TNS V1-V3)             ODSCOMMON

On the backend

COUNT(*) PROGRAM                                       USERNAME
---------- --------------------------------------------- ------------
        17 JDBC Thin Client                              product_owner

Followup:
it tells you nothing.  connection pooling can use thick or thin drivers.

you'll have to figure out that "for some reason" part.  If the developers quite
simply "don't know", well...

Rating: 4
May 30, 2003
Reviewer: Sean from Atlanta
By the theory, the connection pooling is a frame work for caches of database
connections --- "Physically". Some of the connection have been "established",
which can be verified from v$session, and others may be the open . These open
connections in the pool are no way to be verified at the database level, because
they have not been used for the database connections yet. How can I find/verify
those have not been used the connections  in the pool? Because they should be
the "sockets" , can very find out at OS level. I used the following unix
command, it gives all the established connections, but I want to find out
not-yet connections:
 lsof -i TCP:1521.
If you or anyone could help to find out the open connections of jdbc at OS
level, I will be very thankful.

As you suggested to use 9iAS monitoring tools to find out the jdbc connection
pooling infor, I used the OEM Website, it give very misleading results as such
as
One time:
JDBC Usage:
Open JDBC Connections                    11,207
 Total JDBC Connections                    22,387
    Active Transactions                    0
   Transaction Commits                    0
  Transaction Rollbacks                    0

At a other time
JDBC Usage:
Open JDBC Connections                    0
 Total JDBC Connections                    0
    Active Transactions                    0
   Transaction Commits                    0
  Transaction Rollbacks                    0

We know the result were not correct/realistic.

Followup:
you can look for processes in v$process that are not associated with sessions.
you'll get those when a circuit is setup but no sessions established.

but lots of connection pools are not so subtle, they'll always have a connection
and a session.

1521 is probably not the port things are connected on btw, port redirection
generally happens.

Rating: 3
does jdbc use tnsnames.ora ? June 09, 2003
Reviewer: Alvin from Philippines
I was configuring the db to be MTS then i was told that JDBC only uses SID only
to connect to the db and not tnsnames.ora Is this true ?

If that's the case how do i configure some servers to use dedicated/shared
server via overiding thru tnsnames (server=dedicated) or  (server=shared) ?

Since the Oracle manual states than one can only be on a shared server settings
using net8. And if the what the developer told me was true then configuring the
db for MTS would be useless.

Followup:
that is wrong.  jdbc uses net to connect.

look at the URL you give it to connect, what do they look like?

Thin:
        ("jdbc:oracle:thin:@hostname:port:sid"

has a hostname, port number and sid in it (going to use net for that)

Thick:

        ("jdbc:oracle:oci8:@tnsnames.entry"

has a tnsnames.ora entry.  going to use net for that.

Rating: 3
JDBC not using tnsnames... June 09, 2003
Reviewer: Alvin from Philippines
We're using jdbc thin.

Followup:
it is the listener that decides "dedicated" or "shared server"

you configure the listener.

Rating: 5
Send me a link... June 10, 2003
Reviewer: Alvin from Philippines
Mr. Kyte can you post a link on how i can configure the listener ? I was reading
8i's Net8 Administrator's Guide
Release 8.1.6 chapter 7 (Configuring the Listener). And i didn't find any topic
regarding the configuration of listener.ora for MTS.

I'm remotely administering our Db's and i issued netasst on my prompt and this
is what i get and it seems to hang....

/export/home/oracle-> netasst
java.lang.NullPointerException
        at oracle.ewt.lwAWT.BufferedFrame._init(Compiled Code)
        at oracle.ewt.lwAWT.BufferedFrame.<init>(Compiled Code)
        at oracle.sysman.emSDK.client.appContainer.ApplicationFrame.<init>(Compi
led Code)
        at oracle.sysman.emSDK.client.appContainer.WebApplication.main(Compiled
Code)

Is it launching a gui ?

Thanks.

Followup:
otn.oracle.com -> documentation.  it is all there, online, just waiting to be
read.

In fact, that document has an entire chapter on configuring MTS:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/network.817/a76933/mts.htm#450520
(and that is done in the database... the database needs to be MTS savvy)

Rating: 5
I've read the chapter before posting... June 10, 2003
Reviewer: Alvin from Philippines
That means to configure the DB to MTS init.ora file is where i tinker right ? i
thought i might have to add something in the listener.ora file.

Anyways since we're not using port 1521 i DO need to add the
local_listener=<value> in the

mts_dispatcher=" (address= ... ) (lis=<lisneter_local_value>) "

right ?

Followup:
yes. if you want the database to "register" with the listener.

Rating: 5
local_listener init.ora parameter not set... June 10, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
It is my understanding that if you want your listener to run on any other port
other than 1521, and you need database to be able to register with it, you need
to set the local_listener init.ora parameter to point it to port you are using
for your listener.

Is it possible to run the listener on any other port other than 1521 and NOT set
the local_listener init.ora parameter? Will the connection via listener to the
DB still work?

Why I ask this is because, At our client site the PORT setting in tnsnames.ora
file uses different port than 1521 and when I checked our client development
server, the local_listener init.ora parameter was not set.

But we can still connect to that DB? Is there something else that has been set
that eliminates the need to set the local_listener parameter and be still able
to connect?
OR maybe I'm missing someting?
Thanks,

Followup:
either the listener can POINT to a database

or

the database can register with a listener.

you must have your listener configured so as to be aware of that database.

Rating: 4
JDBC Connection Pools with unique UN/PW June 25, 2003
Reviewer: David Schwartz from NJ. USA
Is it possible to configure JDBC connection pooling so that users DO NOT loose
their unique identity? i.e., each user logs in with their unique user name &
password.

Followup:
yes, using proxy authentication
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96590/adgsec03.htm#1004584

      Rating: 4
Application
      Server Connection Pool + Proxy Authentication  July 16, 2003

Reviewer:  Robert Boyle  from Ireland
Tom

We have got the proxy authentication working which is great and solves a huge
problem for us, but how do we integrate this into the application server
connection pooling?  Do we need to write a java class to maintain the pool
ourselves, one that also creates the proxy connection?  In otherwords, is this a
viable method to use with an application server other than 9IAS?

Thanks
Robert

          Followup:

yes it is viable for all you need is the Oracle jdbc drivers and they are usable
under any application server.

You can use this with connection pooling as far as I know -- but -- I would
encourage you to jump over to devtrends.oracle.com where my "evil java
counterpart" Cameron O'Rourke does java stuff like this -- I'm not much of a mid
tier programmer myself.

      Rating: 5
I will go to
      the dark side!  July 16, 2003
Reviewer:
      A reader
Thanks Tom!

July 16, 2003 antoine -- Thanks for the question regarding
      "logging", version 8.17

      You Asked
Hi Tom,

thanks again for all your help!

Tom if you had to built an application that keep track of all the changes at the
table level for legal purpose, would you use a trigger or the logminor or
another Oracle device?

What's your point of view?

thanks,

Antoine

      and we said...
In 9i I would use workspace manangment

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4529781014729

No code, all done for me - totally transparent to the application.

In 8i, you would need to write lots of code in a trigger in order to do this.
logminer doesn't contain enough information to do it in 8i.
Review &
      Followup

      Rating:
      5
Thanks!  November 06, 2002
Reviewer:
      Antoine Bocti  from Montreal,Canada
Thanks Tom!

      Rating: 5
Workspace
      Manager Scenario or not?  November 06, 2002

Reviewer:  Jeremy Smith  from Salt Lake City

I'm in an environment where we need to store permanently a record in every state
it's ever been visible to another user.  (I may be in the same sector as
Antoine) For instance, if I have a table of 'notes' with id, title, and text,
and text has been changed five times, I need to store every value of text
permanently, along with a timestamp of when it was changed.  (background: these
are clinical observations).

For instance, if I had "1001", "Leg Amputation", "His right leg is gangrenous
and needs to be amputated" and ten minutes later modified the text to read "His
_left_ leg is gangrenous and needs to be amputated", I'd need to save the both
entries for roughly 150 years. (75 year postmortem)

We'd need to use the most current version most of the time.  In certain cases
(where we're sued by someone with zero legs) where we'd need to retrieve all the
values, or reconstruct the data as it look at time t in the past.

We're not currently using Oracle (or even an RDBMS), although we're moving to
Oracle.  The two options we looked at were
1>storing the current value in the table and updates would generate, via a
trigger, a separate "diff" table with old value and a timestamp (like a lot of
version control systems) that could be checked in rare cases to see an old value
OR
2>storing the information in the same table, and when multiple rows with similar
identifiers were found, using the one with the latest timestamp.
(1> seems like the right answer right now)

Is Workspace Manager a reasonable third possibility in this scenario(any others
you have would be great, too, of course)?   The example given in the App Dev
guide doesn't match up well to one where you wanted all users operating on the
same data, just with the ability to roll back indefinitely to an arbitrary
timestamp.

Thanks always,
Jer

          Followup:

workspace mgmt could be a very easy way for you to accomplish this with minimal
coding effort on your part.  Here is an example:

ops$tkyte@ORA920.US.ORACLE.COM> CREATE USER wsmgmt IDENTIFIED BY wsmgmt;

User created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> GRANT connect, resource, create table to wsmgmt;

Grant succeeded.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> begin
  2          DBMS_WM.GrantSystemPriv
  3      ('ACCESS_ANY_WORKSPACE, MERGE_ANY_WORKSPACE, ' ||
  4       'CREATE_ANY_WORKSPACE, REMOVE_ANY_WORKSPACE, ' ||
  5       'ROLLBACK_ANY_WORKSPACE', 'WSMGMT', 'YES');
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> connect scott/tiger
Connected.
ops$tkyte@ORA920.US.ORACLE.COM> grant select on emp to wsmgmt;

Grant succeeded.

ops$tkyte@ORA920.US.ORACLE.COM> grant select on dept to wsmgmt;

Grant succeeded.

ops$tkyte@ORA920.US.ORACLE.COM> connect demo/demo
Connected.
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

Now we'll log in as wsmgmt and set up our schema...

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> CONNECT wsmgmt/wsmgmt
Connected.
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
wsmgmt@ORA920.US.ORACLE.COM>
wsmgmt@ORA920.US.ORACLE.COM>
wsmgmt@ORA920.US.ORACLE.COM> create table emp as select * from scott.emp;

Table created.

wsmgmt@ORA920.US.ORACLE.COM> create table dept as select * from scott.dept;

Table created.

wsmgmt@ORA920.US.ORACLE.COM>
wsmgmt@ORA920.US.ORACLE.COM> alter table emp add constraint emp_pk primary
key(empno);

Table altered.

wsmgmt@ORA920.US.ORACLE.COM> alter table dept add constraint dept_pk primary
key(deptno);

Table altered.

wsmgmt@ORA920.US.ORACLE.COM> alter table emp add constraint emp_fk_dept foreign
key(deptno) references dept(deptno);

Table altered.

wsmgmt@ORA920.US.ORACLE.COM> alter table emp add constraint emp_fk_emp foreign
key(mgr) references emp(empno);

Table altered.

wsmgmt@ORA920.US.ORACLE.COM>
wsmgmt@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

Version-enable the table. Specify hist option of VIEW_WO_OVERWRITE so that
the xxx_HIST view contains complete history information.

wsmgmt@ORA920.US.ORACLE.COM>
wsmgmt@ORA920.US.ORACLE.COM> begin
  2          DBMS_WM.EnableVersioning ('emp',  'VIEW_WO_OVERWRITE');
  3          DBMS_WM.EnableVersioning ('dept', 'VIEW_WO_OVERWRITE');
  4  end;
  5  /

PL/SQL procedure successfully completed.

wsmgmt@ORA920.US.ORACLE.COM>
wsmgmt@ORA920.US.ORACLE.COM>
wsmgmt@ORA920.US.ORACLE.COM> update emp set sal = sal * 1.5 where ename =
'KING';

1 row updated.

wsmgmt@ORA920.US.ORACLE.COM> commit;

Commit complete.

wsmgmt@ORA920.US.ORACLE.COM>
wsmgmt@ORA920.US.ORACLE.COM> update emp set comm = 0.5 * sal where ename =
'KING';

1 row updated.

wsmgmt@ORA920.US.ORACLE.COM> commit;

Commit complete.

wsmgmt@ORA920.US.ORACLE.COM>
wsmgmt@ORA920.US.ORACLE.COM> update emp set job = 'BOSS' where ename = 'KING';

1 row updated.

wsmgmt@ORA920.US.ORACLE.COM> commit;

Commit complete.

wsmgmt@ORA920.US.ORACLE.COM>
wsmgmt@ORA920.US.ORACLE.COM> update emp set job = 'PRESIDENT' where ename =
'KING';

1 row updated.

wsmgmt@ORA920.US.ORACLE.COM> commit;

Commit complete.

wsmgmt@ORA920.US.ORACLE.COM> @printtbl8 'select * from emp_hist where ename =
"KING"'
wsmgmt@ORA920.US.ORACLE.COM> set verify off
wsmgmt@ORA920.US.ORACLE.COM> declare
  2      l_theCursor     integer default dbms_sql.open_cursor;
  3      l_columnValue   varchar2(4000);
  4      l_status        integer;
  5      l_descTbl       dbms_sql.desc_tab;
  6      l_colCnt        number;
  7
  8      procedure execute_immediate( p_sql in varchar2 )
  9      is
 10      BEGIN
 11          dbms_sql.parse(l_theCursor,p_sql,dbms_sql.native);
 12          l_status := dbms_sql.execute(l_theCursor);
 13      END;
 14          procedure p ( p_str in varchar2 )
 15          is
 16                  l_str   long := p_str;
 17          begin
 18                  loop
 19                  exit when l_str is null;
 20                  dbms_output.put_line( substr( l_str, 1, 250 ) );
 21                  l_str := substr( l_str, 251 );
 22                  end loop;
 23          end;
 24  begin
 25      execute_immediate( 'alter session set nls_date_format=
 26                          ''dd-mon-yyyy hh24:mi:ss'' ');
 27      dbms_sql.parse(  l_theCursor,
 28                       replace( '&1', '"', ''''),
 29                       dbms_sql.native );
 30
 31      dbms_sql.describe_columns( l_theCursor,
 32                                 l_colCnt, l_descTbl );
 33
 34      for i in 1 .. l_colCnt loop
 35          dbms_sql.define_column( l_theCursor, i,
 36                                  l_columnValue, 4000 );
 37
 38      end loop;
 39
 40      l_status := dbms_sql.execute(l_theCursor);
 41
 42      while ( dbms_sql.fetch_rows(l_theCursor) > 0 ) loop
 43          for i in 1 .. l_colCnt loop
 44              dbms_sql.column_value( l_theCursor, i,
 45                                     l_columnValue );
 46              p( rpad( l_descTbl(i).col_name,
 47                    30 ) || ': ' || l_columnValue );
 48          end loop;
 49          dbms_output.put_line( '-----------------' );
 50      end loop;
 51      execute_immediate( 'alter session set nls_date_format=
 52                             ''dd-MON-yy'' ');
 53  exception
 54      when others then
 55          execute_immediate( 'alter session set
 56                           nls_date_format=''dd-MON-yy'' ');
 57          raise;
 58  end;
 59  /
EMPNO                         : 7839
ENAME                         : KING
JOB                           : PRESIDENT
MGR                           :
HIREDATE                      : 17-nov-1981 00:00:00
SAL                           : 5000
COMM                          :
DEPTNO                        : 10
WORKSPACE                     : LIVE
VERSION                       : 0
USER_NAME                     : WSMGMT
TYPE_OF_CHANGE                : I
CREATETIME                    : 06-nov-2002 15:45:28
RETIRETIME                    : 06-nov-2002 15:45:37
-----------------
EMPNO                         : 7839
ENAME                         : KING
JOB                           : PRESIDENT
MGR                           :
HIREDATE                      : 17-nov-1981 00:00:00
SAL                           : 7500
COMM                          :
DEPTNO                        : 10
WORKSPACE                     : LIVE
VERSION                       : 0
USER_NAME                     : WSMGMT
TYPE_OF_CHANGE                : U
CREATETIME                    : 06-nov-2002 15:45:37
RETIRETIME                    : 06-nov-2002 15:45:37
-----------------
EMPNO                         : 7839
ENAME                         : KING
JOB                           : PRESIDENT
MGR                           :
HIREDATE                      : 17-nov-1981 00:00:00
SAL                           : 7500
COMM                          : 3750
DEPTNO                        : 10
WORKSPACE                     : LIVE
VERSION                       : 0
USER_NAME                     : WSMGMT
TYPE_OF_CHANGE                : U
CREATETIME                    : 06-nov-2002 15:45:37
RETIRETIME                    : 06-nov-2002 15:45:38
-----------------
EMPNO                         : 7839
ENAME                         : KING
JOB                           : BOSS
MGR                           :
HIREDATE                      : 17-nov-1981 00:00:00
SAL                           : 7500
COMM                          : 3750
DEPTNO                        : 10
WORKSPACE                     : LIVE
VERSION                       : 0
USER_NAME                     : WSMGMT
TYPE_OF_CHANGE                : U
CREATETIME                    : 06-nov-2002 15:45:38
RETIRETIME                    : 06-nov-2002 15:45:38
-----------------
EMPNO                         : 7839
ENAME                         : KING
JOB                           : PRESIDENT
MGR                           :
HIREDATE                      : 17-nov-1981 00:00:00
SAL                           : 7500
COMM                          : 3750
DEPTNO                        : 10
WORKSPACE                     : LIVE
VERSION                       : 0
USER_NAME                     : WSMGMT
TYPE_OF_CHANGE                : U
CREATETIME                    : 06-nov-2002 15:45:38
RETIRETIME                    :
-----------------

PL/SQL procedure successfully completed.

      Rating: 5
Great info
      Tom!  November 06, 2002
Reviewer:  Jeremy
      Smith  from Salt Lake City, UT
That sounds like great functionality.  So, I did some experimenting, and my only
concern is that I'm going to need to be checking all the workspace tables every
time I access the table.

Specifically:
(emp is under workspace management and emp2 is same--they're the same as
employees in the 9i "HR" schema)

SQL> select * from emp where employee_id='100';

EMPLOYEE_ID FIRST_NAME           LAST_NAME
----------- -------------------- -------------------------
EMAIL                     PHONE_NUMBER         HIRE_DATE JOB_ID         SALARY
------------------------- -------------------- --------- ---------- ----------
COMMISSION_PCT MANAGER_ID DEPARTMENT_ID
-------------- ---------- -------------
        100 Steven               King
SKING                     515.123.4567         17-JUN-87 AD_PRES         24000
                                     90

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=429)
   1    0   FILTER
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMP_LT' (Cost=1 Card=1
           Bytes=429)

   3    2       INDEX (RANGE SCAN) OF 'EMP_PKI$' (NON-UNIQUE) (Cost=1
          Card=1)

   4    1     INDEX (UNIQUE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQUE) (Co
          st=1 Card=1 Bytes=62)

   5    1     VIEW OF 'WM$TABLE_PARVERS_VIEW' (Cost=2 Card=2 Bytes=90)
   6    5       UNION-ALL
   7    6         FILTER
   8    7           INDEX (UNIQUE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQU
          E) (Cost=1 Card=1 Bytes=62)

   9    8             TABLE ACCESS (BY INDEX ROWID) OF 'WM$WORKSPACES_
          TABLE' (Cost=1 Card=1 Bytes=30)

  10    9               INDEX (RANGE SCAN) OF 'WM$WORKSPACES_PK' (UNIQ
          UE) (Cost=1 Card=1)

  11    8             TABLE ACCESS (BY INDEX ROWID) OF 'WM$WORKSPACES_
          TABLE' (Cost=1 Card=1 Bytes=30)

  12   11               INDEX (RANGE SCAN) OF 'WM$WORKSPACES_PK' (UNIQ
          UE) (Cost=1 Card=1)

  13    7           TABLE ACCESS (BY INDEX ROWID) OF 'WM$WORKSPACES_TA
          BLE' (Cost=1 Card=1 Bytes=30)

  14   13             INDEX (RANGE SCAN) OF 'WM$WORKSPACES_PK' (UNIQUE
          ) (Cost=1 Card=1)

  15    7           TABLE ACCESS (BY INDEX ROWID) OF 'WM$WORKSPACES_TA
          BLE' (Cost=1 Card=1 Bytes=30)

  16   15             INDEX (RANGE SCAN) OF 'WM$WORKSPACES_PK' (UNIQUE
          ) (Cost=1 Card=1)

  17    6         NESTED LOOPS (Cost=1 Card=1 Bytes=109)
  18   17           TABLE ACCESS (BY INDEX ROWID) OF 'WM$VERSION_TABLE
          ' (Cost=1 Card=1 Bytes=47)

  19   18             INDEX (RANGE SCAN) OF 'WM$VERSION_PK' (UNIQUE) (
          Cost=2 Card=1)

  20   17           INDEX (UNIQUE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQU
          E)

  21    1     FILTER
  22   21       TABLE ACCESS (BY INDEX ROWID) OF 'WM$NEXTVER_TABLE' (C
          ost=1 Card=1 Bytes=282)

  23   22         INDEX (RANGE SCAN) OF 'WM$NEXTVER_TABLE_NV_INDX' (NO
          N-UNIQUE) (Cost=1 Card=1)

  24   21       TABLE ACCESS (BY INDEX ROWID) OF 'WM$WORKSPACES_TABLE'
           (Cost=1 Card=1 Bytes=30)

  25   24         INDEX (RANGE SCAN) OF 'WM$WORKSPACES_PK' (UNIQUE) (C
          ost=1 Card=1)

  26   21       TABLE ACCESS (BY INDEX ROWID) OF 'WM$WORKSPACES_TABLE'
           (Cost=1 Card=1 Bytes=30)

  27   26         INDEX (RANGE SCAN) OF 'WM$WORKSPACES_PK' (UNIQUE) (C
          ost=1 Card=1)

  28   21       TABLE ACCESS (BY INDEX ROWID) OF 'WM$VERSION_TABLE' (C
          ost=1 Card=1 Bytes=47)

  29   28         INDEX (RANGE SCAN) OF 'WM$VERSION_PK' (UNIQUE) (Cost
          =1 Card=1)

  30    1     INDEX (UNIQUE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQUE) (Co
          st=1 Card=1 Bytes=62)

  31    1     VIEW OF 'WM$TABLE_WS_PARVERS_VIEW' (Cost=2 Card=2 Bytes=
          90)

  32   31       UNION-ALL
  33   32         INDEX (UNIQUE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQUE)
           (Cost=1 Card=1 Bytes=62)

  34   32         NESTED LOOPS (Cost=1 Card=1 Bytes=109)
  35   34           TABLE ACCESS (BY INDEX ROWID) OF 'WM$VERSION_TABLE
          ' (Cost=1 Card=1 Bytes=47)

  36   35             INDEX (RANGE SCAN) OF 'WM$VERSION_PK' (UNIQUE) (
          Cost=2 Card=1)

  37   34           INDEX (UNIQUE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQU
          E)

  38    1     NESTED LOOPS (Cost=3 Card=1 Bytes=93)
  39   38       TABLE ACCESS (BY INDEX ROWID) OF 'EMP_LT' (Cost=1 Card
          =1 Bytes=48)

  40   39         INDEX (RANGE SCAN) OF 'EMP_PKI$' (NON-UNIQUE) (Cost=
          1 Card=1)

  41   38       VIEW OF 'WM$TABLE_WS_PARVERS_VIEW'
  42   41         UNION-ALL (PARTITION)
  43   42           INDEX (UNIQUE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQU
          E) (Cost=1 Card=1 Bytes=62)

  44   42           NESTED LOOPS (Cost=1 Card=1 Bytes=109)
  45   44             TABLE ACCESS (BY INDEX ROWID) OF 'WM$VERSION_TAB
          LE' (Cost=1 Card=1 Bytes=47)

  46   45               INDEX (RANGE SCAN) OF 'WM$VERSION_PK' (UNIQUE)
           (Cost=2 Card=1)

  47   44             INDEX (UNIQUE SCAN) OF 'MODIFIED_TABLES_PK' (UNI
          QUE)

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

SQL> select * from emp2 where employee_id='100';

EMPLOYEE_ID FIRST_NAME           LAST_NAME
----------- -------------------- -------------------------
EMAIL                     PHONE_NUMBER         HIRE_DATE JOB_ID         SALARY
------------------------- -------------------- --------- ---------- ----------
COMMISSION_PCT MANAGER_ID DEPARTMENT_ID
-------------- ---------- -------------
        100 Steven               King
SKING                     515.123.4567         17-JUN-87 AD_PRES         24000
                                     90

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

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

SQL>

I'm concerned that in my case where I'm not planning on using any of actual
workspace features, having the actual tables tranformed into views that have to
verify their workspace slow might things down substantially.

One other question: any caveats with partitioning?

Jer

          Followup:

Well, compare it to a "real table" (copied emp into testing as well)

wsmgmt@ORA920.US.ORACLE.COM> analyze table emp_lt compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;

Table analyzed.

wsmgmt@ORA920.US.ORACLE.COM>
wsmgmt@ORA920.US.ORACLE.COM> set autotrace on
wsmgmt@ORA920.US.ORACLE.COM> select * from emp where empno = 7934;

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
----------
      7934 MILLER     CLERK           7782 23-JAN-82       1300
  10

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=61)
   1    0   FILTER
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMP_LT' (Cost=2 Card=1 Bytes=61)
   3    2       INDEX (RANGE SCAN) OF 'EMP_PKI$' (NON-UNIQUE) (Cost=1 Card=1)
   4    1     INDEX (UNIQUE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQUE) (Cost=1
Card=1 Bytes=62)
   5    1     VIEW OF 'WM$TABLE_PARVERS_VIEW' (Cost=2 Card=2 Bytes=90)
   6    5       UNION-ALL
   7    6         FILTER
   8    7           INDEX (UNIQUE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQUE) (Cost=1
Card=1 Bytes=62)
   9    8             TABLE ACCESS (BY INDEX ROWID) OF 'WM$WORKSPACES_TABLE'
(Cost=1 Card=1 Bytes=30)
  10    9               INDEX (RANGE SCAN) OF 'WM$WORKSPACES_PK' (UNIQUE)
(Cost=1 Card=1)
  11    8             TABLE ACCESS (BY INDEX ROWID) OF 'WM$WORKSPACES_TABLE'
(Cost=1 Card=1 Bytes=30)
  12   11               INDEX (RANGE SCAN) OF 'WM$WORKSPACES_PK' (UNIQUE)
(Cost=1 Card=1)
  13    7           TABLE ACCESS (BY INDEX ROWID) OF 'WM$WORKSPACES_TABLE'
(Cost=1 Card=1 Bytes=30)
  14   13             INDEX (RANGE SCAN) OF 'WM$WORKSPACES_PK' (UNIQUE) (Cost=1
Card=1)
  15    7           TABLE ACCESS (BY INDEX ROWID) OF 'WM$WORKSPACES_TABLE'
(Cost=1 Card=1 Bytes=30)
  16   15             INDEX (RANGE SCAN) OF 'WM$WORKSPACES_PK' (UNIQUE) (Cost=1
Card=1)
  17    6         NESTED LOOPS (Cost=1 Card=1 Bytes=109)
  18   17           TABLE ACCESS (BY INDEX ROWID) OF 'WM$VERSION_TABLE' (Cost=1
Card=1 Bytes=47)
  19   18             INDEX (RANGE SCAN) OF 'WM$VERSION_PK' (UNIQUE) (Cost=2
Card=1)
  20   17           INDEX (UNIQUE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQUE)
  21    1     FILTER
  22   21       TABLE ACCESS (BY INDEX ROWID) OF 'WM$NEXTVER_TABLE' (Cost=1
Card=1 Bytes=282)
  23   22         INDEX (RANGE SCAN) OF 'WM$NEXTVER_TABLE_NV_INDX' (NON-UNIQUE)
(Cost=1 Card=1)
  24   21       TABLE ACCESS (BY INDEX ROWID) OF 'WM$WORKSPACES_TABLE' (Cost=1
Card=1 Bytes=30)
  25   24         INDEX (RANGE SCAN) OF 'WM$WORKSPACES_PK' (UNIQUE) (Cost=1
Card=1)
  26   21       TABLE ACCESS (BY INDEX ROWID) OF 'WM$WORKSPACES_TABLE' (Cost=1
Card=1 Bytes=30)
  27   26         INDEX (RANGE SCAN) OF 'WM$WORKSPACES_PK' (UNIQUE) (Cost=1
Card=1)
  28   21       TABLE ACCESS (BY INDEX ROWID) OF 'WM$VERSION_TABLE' (Cost=1
Card=1 Bytes=47)
  29   28         INDEX (RANGE SCAN) OF 'WM$VERSION_PK' (UNIQUE) (Cost=1 Card=1)
  30    1     INDEX (UNIQUE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQUE) (Cost=1
Card=1 Bytes=62)
  31    1     VIEW OF 'WM$TABLE_WS_PARVERS_VIEW' (Cost=2 Card=2 Bytes=90)
  32   31       UNION-ALL
  33   32         INDEX (UNIQUE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQUE) (Cost=1
Card=1 Bytes=62)
  34   32         NESTED LOOPS (Cost=1 Card=1 Bytes=109)
  35   34           TABLE ACCESS (BY INDEX ROWID) OF 'WM$VERSION_TABLE' (Cost=1
Card=1 Bytes=47)
  36   35             INDEX (RANGE SCAN) OF 'WM$VERSION_PK' (UNIQUE) (Cost=2
Card=1)
  37   34           INDEX (UNIQUE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQUE)
  38    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMP_LT' (Cost=1 Card=1 Bytes=13)
  39   38       NESTED LOOPS (Cost=3 Card=1 Bytes=58)
  40   39         VIEW OF 'WM$TABLE_WS_PARVERS_VIEW' (Cost=2 Card=1 Bytes=45)
  41   40           UNION-ALL
  42   41             INDEX (RANGE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQUE)
(Cost=2 Card=1 Bytes=62)
  43   41             NESTED LOOPS (Cost=2 Card=1 Bytes=109)
  44   43               TABLE ACCESS (BY INDEX ROWID) OF 'WM$VERSION_TABLE'
(Cost=1 Card=1 Bytes=47)
  45   44                 INDEX (RANGE SCAN) OF 'WM$VERSION_PK' (UNIQUE) (Cost=2
Card=1)
  46   43               INDEX (RANGE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQUE)
(Cost=1 Card=1 Bytes=62)
  47   39         INDEX (RANGE SCAN) OF 'EMP_PKI$' (NON-UNIQUE)

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

wsmgmt@ORA920.US.ORACLE.COM> select * from emp where empno = 7934;

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
----------
      7934 MILLER     CLERK           7782 23-JAN-82       1300
  10

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=61)
   1    0   FILTER
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMP_LT' (Cost=2 Card=1 Bytes=61)
   3    2       INDEX (RANGE SCAN) OF 'EMP_PKI$' (NON-UNIQUE) (Cost=1 Card=1)
   4    1     INDEX (UNIQUE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQUE) (Cost=1
Card=1 Bytes=62)
   5    1     VIEW OF 'WM$TABLE_PARVERS_VIEW' (Cost=2 Card=2 Bytes=90)
   6    5       UNION-ALL
   7    6         FILTER
   8    7           INDEX (UNIQUE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQUE) (Cost=1
Card=1 Bytes=62)
   9    8             TABLE ACCESS (BY INDEX ROWID) OF 'WM$WORKSPACES_TABLE'
(Cost=1 Card=1 Bytes=30)
  10    9               INDEX (RANGE SCAN) OF 'WM$WORKSPACES_PK' (UNIQUE)
(Cost=1 Card=1)
  11    8             TABLE ACCESS (BY INDEX ROWID) OF 'WM$WORKSPACES_TABLE'
(Cost=1 Card=1 Bytes=30)
  12   11               INDEX (RANGE SCAN) OF 'WM$WORKSPACES_PK' (UNIQUE)
(Cost=1 Card=1)
  13    7           TABLE ACCESS (BY INDEX ROWID) OF 'WM$WORKSPACES_TABLE'
(Cost=1 Card=1 Bytes=30)
  14   13             INDEX (RANGE SCAN) OF 'WM$WORKSPACES_PK' (UNIQUE) (Cost=1
Card=1)
  15    7           TABLE ACCESS (BY INDEX ROWID) OF 'WM$WORKSPACES_TABLE'
(Cost=1 Card=1 Bytes=30)
  16   15             INDEX (RANGE SCAN) OF 'WM$WORKSPACES_PK' (UNIQUE) (Cost=1
Card=1)
  17    6         NESTED LOOPS (Cost=1 Card=1 Bytes=109)
  18   17           TABLE ACCESS (BY INDEX ROWID) OF 'WM$VERSION_TABLE' (Cost=1
Card=1 Bytes=47)
  19   18             INDEX (RANGE SCAN) OF 'WM$VERSION_PK' (UNIQUE) (Cost=2
Card=1)
  20   17           INDEX (UNIQUE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQUE)
  21    1     FILTER
  22   21       TABLE ACCESS (BY INDEX ROWID) OF 'WM$NEXTVER_TABLE' (Cost=1
Card=1 Bytes=282)
  23   22         INDEX (RANGE SCAN) OF 'WM$NEXTVER_TABLE_NV_INDX' (NON-UNIQUE)
(Cost=1 Card=1)
  24   21       TABLE ACCESS (BY INDEX ROWID) OF 'WM$WORKSPACES_TABLE' (Cost=1
Card=1 Bytes=30)
  25   24         INDEX (RANGE SCAN) OF 'WM$WORKSPACES_PK' (UNIQUE) (Cost=1
Card=1)
  26   21       TABLE ACCESS (BY INDEX ROWID) OF 'WM$WORKSPACES_TABLE' (Cost=1
Card=1 Bytes=30)
  27   26         INDEX (RANGE SCAN) OF 'WM$WORKSPACES_PK' (UNIQUE) (Cost=1
Card=1)
  28   21       TABLE ACCESS (BY INDEX ROWID) OF 'WM$VERSION_TABLE' (Cost=1
Card=1 Bytes=47)
  29   28         INDEX (RANGE SCAN) OF 'WM$VERSION_PK' (UNIQUE) (Cost=1 Card=1)
  30    1     INDEX (UNIQUE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQUE) (Cost=1
Card=1 Bytes=62)
  31    1     VIEW OF 'WM$TABLE_WS_PARVERS_VIEW' (Cost=2 Card=2 Bytes=90)
  32   31       UNION-ALL
  33   32         INDEX (UNIQUE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQUE) (Cost=1
Card=1 Bytes=62)
  34   32         NESTED LOOPS (Cost=1 Card=1 Bytes=109)
  35   34           TABLE ACCESS (BY INDEX ROWID) OF 'WM$VERSION_TABLE' (Cost=1
Card=1 Bytes=47)
  36   35             INDEX (RANGE SCAN) OF 'WM$VERSION_PK' (UNIQUE) (Cost=2
Card=1)
  37   34           INDEX (UNIQUE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQUE)
  38    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMP_LT' (Cost=1 Card=1 Bytes=13)
  39   38       NESTED LOOPS (Cost=3 Card=1 Bytes=58)
  40   39         VIEW OF 'WM$TABLE_WS_PARVERS_VIEW' (Cost=2 Card=1 Bytes=45)
  41   40           UNION-ALL
  42   41             INDEX (RANGE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQUE)
(Cost=2 Card=1 Bytes=62)
  43   41             NESTED LOOPS (Cost=2 Card=1 Bytes=109)
  44   43               TABLE ACCESS (BY INDEX ROWID) OF 'WM$VERSION_TABLE'
(Cost=1 Card=1 Bytes=47)
  45   44                 INDEX (RANGE SCAN) OF 'WM$VERSION_PK' (UNIQUE) (Cost=2
Card=1)
  46   43               INDEX (RANGE SCAN) OF 'MODIFIED_TABLES_PK' (UNIQUE)
(Cost=1 Card=1 Bytes=62)
  47   39         INDEX (RANGE SCAN) OF 'EMP_PKI$' (NON-UNIQUE)

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

wsmgmt@ORA920.US.ORACLE.COM> select * from testing where empno = 7934;

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
----------
      7934 MILLER     CLERK           7782 23-JAN-82       1300
  10

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'TESTING'
   2    1     INDEX (UNIQUE SCAN) OF 'TESTING_PK' (UNIQUE)

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

wsmgmt@ORA920.US.ORACLE.COM> select * from testing where empno = 7934;

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
----------
      7934 MILLER     CLERK           7782 23-JAN-82       1300
  10

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'TESTING'
   2    1     INDEX (UNIQUE SCAN) OF 'TESTING_PK' (UNIQUE)

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

wsmgmt@ORA920.US.ORACLE.COM> set autotrace off
wsmgmt@ORA920.US.ORACLE.COM>

so, in that case it is 2 consistent gets vs 3....

I'll put it this way.  If the absolute fastest tightest code is what we want --
assembler is the language we would program in.  Generally, we trade off absolute
performance at runtime vs development ease, maintainence ease, speed of
development and so on.  It is a trade off.

If you code it yourself, specific to your problem -- it'll probably be faster.
However, you'll have all of that code to develop, to maintain, to enhance, to
debug and so on.  I would just encourage you to consider this -- benchmark it
and if it meets your needs -- use it.

see

http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96628/toc.htm
for all of the details....

      Rating:
      5
Excellent  November 06, 2002
Reviewer:
      A reader
Great example, looks like a very useful piece of functionality

      Rating: 5
query to get
      updates  July 01, 2003
Reviewer:
      A reader  from ca
when you query the emp_hist, you may need to show
the previous value of a col and the current value of
a column side by side. Following query seems to do it.
for the above case of changes. Any comments, Tom?
Inserts and deletes should be simpler cases.
I guess we could write a generic routine for any table using
dynamic sql to implement the functionality.

The output is:
 @hist

ENAME      JOB_CHANGES                    SAL_CHANGES
---------- ------------------------------ ------------------------------
COMM_CHANGES
------------------------------
KING                                      old:5000 new: 7500

KING
old: new: 3750

KING       old:PRESIDENTnew: BOSS

KING       old:BOSSnew: PRESIDENT

-- the query is

column job_changes format a30
column sal_changes format a30
column comm_changes format a30
select ename,
  case when nvl(job, ' ') != nvl( prev_job, ' ') then
    'old:'|| prev_job ||  'new: ' || job
  end as job_changes,
  case when nvl(sal, 0) != nvl(prev_sal, 0) then
    'old:'|| prev_sal ||  ' new: ' || sal
  end as sal_changes,
  case when nvl(comm, 0) != nvl(prev_comm, 0) then
    'old:'|| prev_comm ||  ' new: ' || comm
  end as comm_changes
from
(
  select type_of_change, ename,  createtime, retiretime,
         job, lag(job) over (partition by empno order by  empno, createtime,
retiretime )
 prev_job,
         sal, lag(sal) over (partition by empno order by  empno, createtime,
retiretime )
 prev_sal,
         comm, lag(comm) over (partition by empno order by  empno, createtime,
retiretime
 ) prev_comm,
         row_number() over (partition by empno order by  empno, createtime,
retiretime) r
ow_number
  from emp_hist
  order by empno, createtime, retiretime
)
where type_of_change = 'U'
order by createtime, retiretime
/

          Followup:

yup, that looks fine -- lag/lead would be the obvious candidates.

If you do this for a SINGLE emp, make sure to put that predicate in the inline
view.

not sure why you have an order by in the inline view?

      Rating: 5
thanx
      tom!  July 01, 2003
Reviewer:  A reader
       from ca
And you are right - order by is not required in the inline view.

      Rating: 5
one more
      thing  July 01, 2003
Reviewer:
      A reader  from ca
do you think it is a good idea to gneralize this
using dynamic sql (for any table)?

          Followup:

only if you actually *need* to do this for every table.

      Rating: 5
Procedure/
      Function logging  July 16, 2003
Reviewer:
      Yogesh  from Pune India
If I want to keep the log of procedure / function execution with their parameter
values, Is there any way out in 8i by which we can do so ? or we need to handle
them manually ..

          Followup:

I would suggest manually.  it is the only sensible path.

      Rating:
      5
Thanks  July 16, 2003
Reviewer:  Yogesh
       from Pune India

July 17, 2003 Pauline -- Thanks for the question regarding
      "problem with long table name", version 8.1.7.2

      You Asked
Tom,
We had problem with the long table name:

SQL> sho user
USER is "REF"
SQL> select table_name from user_tables;

TABLE_NAME
------------------------------
ACCOUNT_ADDR_USAGE_TYPES
ACCOUNT_ASSOC_TYPES
ACCOUNT_STATUSES
ACCOUNT_TRANSACTION_STATUSES
ACCOUNT_TRANSACTION_TYPES
ACCOUNT_TYPES
APPLICATIONS
BUSINESS_CODES
CATEGORY_DEPARTMENTS
CERTIFICATION_STATUS
CLIENT_SEGMENTS
COMPANIES
CONTACT_POINT_USAGE_TYPES
CURRENCIES
DEPARTMENTS
FUNCTIONS
GEOGRAPHIC_REGIONS
GEOGRAPHIC_REGION_TYPES
GEO_REGION_ROLLUPS
GEO_REGION_ROLLUP_TYPES
JOBS
LANGUAGES
LEGACY_OFFICE_DEPARTMENTS
MARKETING_SEGMENTS
OFFICES
OFFICE_DEPARTMENTS
PARTY_ACCOUNT_ROLES
PARTY_ASSOC_TYPES
PARTY_ASSOC_TYPE_GROUPS
PARTY_IDENTIFICATION_TYPES
PARTY_IMPORTANCE_LEVELS
PARTY_TYPES

TABLE_NAME
------------------------------
PAYMENT_CALC_METHOD
PAY_TERM_TYPES
PHONE_TYPES
PROCESSES
ROLES
ROLE_FUNCTIONS
STANDARD_HINT_QUESTIONS
TRIGGER_ERRORS

40 rows selected.

SQL> drop table ACCOUNT_TRANSACTION_STATUSES;
drop table ACCOUNT_TRANSACTION_STATUSES
           *
ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 1
ORA-01401: inserted value too large for column
ORA-06512: at line 22

SQL> drop table ref.jobs;

Table dropped.

We only may drop the table with shot name or use drop user username
cascade to drop the table with long name.

Is this a bug? What is the workaround for it.

Thanks for your help.

Pauline

      and we said...
Is this a bug -- you betcha.

Its a bug in YOUR DDL trigger!!!

You have a trigger, it has at least 22 lines of code and on line 22 you are
inserting the ora_dict_obj_name into an audit table.  Audit table is too small
to hold it.  It fails.  We fail.

Solution... Fix trigger or fix audit table.

Example:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table ACCOUNT_TRANSACTION_STATUSES ( x
int );

Table created.

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table ACCOUNT_TRANSACTION_STATUSES ( x
int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> drop table log;
drop table log
           *
ERROR at line 1:
ORA-00942: table or view does not exist

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table log ( msg varchar2(20) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create trigger ddl_trigger before drop on
schema
  2  begin
  3          insert into log values ( ora_dict_obj_name );
  4  end;
  5  /

Trigger created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> drop table ACCOUNT_TRANSACTION_STATUSES;
drop table ACCOUNT_TRANSACTION_STATUSES
*
ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 1
ORA-01401: inserted value too large for column
ORA-06512: at line 2

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> drop trigger ddl_trigger;

Trigger dropped.

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

Table dropped.

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

Review &
      Followup

      Rating: 5
Excellent
        March 19, 2002
Reviewer:  Pauline
       from NY, U.S.A
Tom,
It is amazing that you know everything. The workaround is really working.

Thank you so much!

Pauline

      Rating: 5
it's
      wonderfull.....  March 19, 2002
Reviewer:
      vis  from usa
Hi Tom,
It's really wonderful....Excellant..We are really as you said 'Betcha's...Do you
know what is the meaning of Betcha in Hindi...Kids
Thanks
vis
USA

      Rating:
      5
amazing  March 20, 2002
Reviewer:  Vikas
      Sharma  from India
It's really wonderful ,Excellant.

      Rating: 5
"ORA-06512: at
      line 22" was the HINT. right? ;)  March 20, 2002

Reviewer:  Yogeeraj  from Mauritius

          Followup:

yes it was...  the inserted value too large was the rest of the story...

      Rating:
      3
Cool  April 13, 2002
Reviewer:
      Tomboy  from LV,US

      Rating:
      5
  July 16, 2003
Reviewer:  Vini

Hi Tom,

I wants to create table with more than 32 of length. Then I got an error (in Ora
9i):

ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 1
ORA-01401: inserted value too large for column
ORA-06512: at line 6

How can I solve this?

Thank you very much.

          Followup:

well, identifiers are limited to 30 characters.

but you give us no example.  I get this error myself:

ops$tkyte@ORA920LAP> create table t123456789012345678901234567890t ( x int );
create table t123456789012345678901234567890t ( x int )
             *
ERROR at line 1:
ORA-00972: identifier is too long

      Rating:
      5
  July 16, 2003
Reviewer:  Vini
       from USA
Hi Tom,

I am sorry, I did a mistake in earlier message. I wants to create table with
lengthy Names as more than 32 characters.

I got an error :
ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 1
ORA-01401: inserted value too large for column
ORA-06512: at line 6

Please could you explain how reach for this.

Thanks

      Rating: 4
A limit is a
      limit  July 16, 2003
Reviewer:  Jim
       from Portland, OR
The length of table names is limited which means you cannot create a table name
longer than the limit.  That is what the error message is telling you.

      Rating:
      5
  July 17, 2003
Reviewer:  Vini
       from USA
Hi Tom,

You are correct. My above mensioned error message comes from my another trigger
(tracking create objects..)

Now I corrected it, Thanks.

As Jim told "LIMIT IS LIMIT", I know that. But I am asking Is there any
possibility (Before creating Database.. ) set the length of name in sys.obj$ and
other correspondents.

Please tell me is there any posility?

          Followup:

no, there is not

July 17, 2003 Asif -- Thanks for the question regarding
      "Multiple", version 8.0.5

      You Asked
Hi Tom,

    Hope you are doing fine. As usual could you please answer the following
questions?

1.    I would like to have a query, which lists all the tables and number of
rows in each table. I don’t want to Analyze the tables in order to do this.
Something like this
SQL> Select table_name, count (get_rows_in_the_table) from table_and_rows group
by table_name;

2.    Could you please tell us how to find out the size of an index and depth of
an index and how to use them in the tuning process?

3.    What is the difference between Cache and Buffer?

4.     “Library Cache (will hold parsed tree or execution plan of SQL
statement), is case sensitive”.

Meaning: statement 1 is different from statement 2; if you notice statement 2 is
in caps.

Statement 1: select ename from emp where empno = :bind_empno;
Statement 2: SELECT ENAME FROM EMP WHERE EMPNO = :bind_empno;

   Where as, I believe they both are the same. You will have one parse tree and
it will be used to execute both the queries. Am I right?

5.    I have the following tables fin_commitments and fin_commit_details.

Fin_commitments                     Fin_Commit_Details
---------------------              ---------------------------
(pk) fin_year                      (pk) fin_year
(pk) commit_no                     (pk) commit_no
 commit_status                     (pk) serial
                                        debit
                                        credit

Select sum(debit), sum(credit)
from fin_commit_details fcd
where fin_year =  ‘20022001’
and exists (select 1 from fin_commitments where fin_year = fcd.fin_year and
commit_no = fcd.commit_no and commit_status =’APPROVED’);

Say, I have one record in fin_commitments and 5 detail records in
fin_commit_details, how many times the correlated subquery gets executed. Is it
1 time or 5 times?

6.    I was reading chapter 3: Locking and Concurrency of your book and have
come across Pessimistic and Optimistic Locking. You encourage Pessimistic
locking. I have a scenario where I have been defeated by Pessimistic locks. I
was working on a financial system where end users work on the same voucher
updating different fields according to their job and requirements. The front end
used was Forms 6i, Block properties of the voucher block are Locking Mode =
Immediate. In this way as soon as the end user modifies an item, a lock is
placed on the record. A real scenario: User 1 modified a record and left for a
tea (record still uncommitted and locked) and User 2 tries to modify the same
record he gets a message “Could not reserve record (2 tries).  Keep trying”.
This situation happened more than once and we opted to use Locking Mode =
Delayed (lock only when posting the record to the database).  Could you please
list out the possible disadvantages of the current approach (if any) and if we
use Locking Mode = Immediate, how do I handle the situation where user modifies
and we have to wait for the user to either commit or rollback.

7.    I have multiple queries within a single transaction, all of which are
supposed to fetch data at the same point in time, so that queries in this
transaction do not see the effects of other committed transactions. How do I
specify a read-only transaction?

8.    When you are asked to write a query, what steps you follow while writing
the query? How do you list down the select list, the tables, the where clause
etc.

Thanks once again,

Regards,

Momen.

      and we said...
In the form you navigated to ask a question, there is a list of requests -- one
of which is "please don't ask long multi-part questions".  Just FYI there....

1) you cannot in 8.0 do that.  You can in 8.1 and up.  In 8.0, you will have to
use a sqlplus script or a stored procedure.  A sqlplus script is probably "best"
since in 8.0 a stored procedure runs without ROLES (so it cannot see every table
you can see). In 8i, you can use authid current_user to do this in a procedure.

So, if you have a script like:

set termout off
set heading off
set feedback off
set linesize 100
set pagesize 99999
spool tmp.sql
select decode( rownum, 1, 'set heading on', 'set heading off' ) || chr(10) ||
      'select rpad( ''' || table_name || ''', 30 ) TableName, count(*) from ' ||
table_name || ';'
  from user_tables;
spool off
set termout on
@tmp

and run:

scott@ORA806.WORLD> @test

TABLENAME                        COUNT(*)
------------------------------ ----------
BONUS                                   0

DEPT                                    4

DUMMY                                   1

EMP                                    14

MDST                                11199

PLAN_TABLE                              0

SALGRADE                                5

T                                     930
scott@ORA806.WORLD>

2) analyze index validate structure;
   select * from index_stats;

read chapter 10 of the book (the one you refer to in #6) for tuning information.

3) one starts with a C and the other a B.  There is a BUFFER CACHE (where we
store database block buffers -- a database block in memory).  So, they are not
really comparable but complimentary.  There is a block buffer cache, a libarary
cache, etc.

4) You are wrong.  they are different.  It is case sensitive, white space
sensitive and so on.  they are different queries.

5) It is 5.

6) The disadvantage is that the user might find that after working on their
record diligently for 5 minutes -- they are not allowed to save it due to the
fact that someone else modified the same attributes.

Locking is a matter of common sense, if you model prohibits you from pessimistic
locking, by all means -- don't use it.  If your model permits it -- by all means
use it.

You might have had a structure that (for real world reasons) should have been
physically implemented as many 1 to 1 tables, should have been physically
partitioned by attributes.  It is rare but you might have been one of the cases
where that would have made sense.

7)

scott@ORA806.WORLD> set transaction read only;
scott@ORA806.WORLD>

8) I ask for the english version of it -- tell me what you want.  Then I write
the query.  There is no "list of steps" (if there were, natural language query
processors would actually work ;)

Review &
      Followup

      Rating:
      2
#5  July 15, 2002
Reviewer:  Ivan
       from Russia
Hello, Tom

I'm surprized by your answer to 5th question. I assume that the right answer is
"It depends on execution plan". For example, if I'll use the hint "push_subq",
the probability of a single subquery execution is high enough. And optimizer may
choose that plan without hint.

Best regards
Ivan

          Followup:

sure, depends on execution path.  this is a correlated subquery however (so
push_subq isn't really going to "mean" anything here -- we cannot evaluate it
sooner rather then later).

If the optimizer rewrites:

Select sum(debit), sum(credit)
from fin_commit_details fcd
where fin_year =  ‘20022001’
and exists (select 1 from fin_commitments where fin_year = fcd.fin_year and
commit_no = fcd.commit_no and commit_status =’APPROVED’);

into the equivalent:

Select sum(debit), sum(credit)
from fin_commit_details fcd
where fin_year =  ‘20022001’
and (fin_year,commit_no) in (select fin_year, commit_no from fin_commitments
commit_status =’APPROVED’);

which can be turned into a join , it'll still in effect do 5 probes into the
subquery.

If we keep it as an exists, it'll be 5, if it is rolled into a join, it'll be
totally different and not really "comparable" performance/execution wise to the
exists.

      Rating:
      5
Reader  July 15, 2002
Reviewer:
      A reader
Your answer to Question (1) is very useful

Is there a way to write a PL/SQL to describe tables

Example:

TABLE_NAME_1

col1 datatype
col2 datatype

....

TABLE_NAME_2

col1 datatype
col2 datatype

....

          Followup:

Yes, you can use the data dictionary (user_tab_columns for example) to query
this and print it out if you like.

      Rating: 5
#5
      again  July 15, 2002
Reviewer:  Ivan
       from Russia
In fact, that query may be rewrote (converted to join) by optimizer and there is
no sense to talk about subquery in this case. Nevertheless we (or optimizer) can
reach the single reference to FIN_COMMITMENTS table in cases of HJ, MJ and NL
(with first of FIN_COMMITMENTS).

Simply I'd answer "It depends on plan and the statement may be converted in one
without subquery" and I seem that "it is 5" is very rough and straightforward
answer.

Best regards,
Ivan

          Followup:

Even if the table is full scaned and hashed -- tell me -- how many hash probes
will we do into it?  5

      Rating: 4
For " A
      READER"  July 15, 2002
Reviewer:  Sanjay Raj
       from US
You can either get the description as Tom explained or else you can use the
following. Tom please correct if you find something wrong.

set echo off heading off feedback off
set pages 2000 linesize 78
spool description_of_tables.sql
select 'set echo off ' from dual;
select 'set pages 2000 linesize 78' from dual;
select 'set feedback off' from dual;
select 'spool datadict_new.txt' from dual;
select 'prompt TABLE='||tname||' TYPE= '||TABTYPE||
';',
'desc '||tname||';'
from tab;
select 'spool off' from dual
/
spool off
set termout off feedback off
start description_of_tables.sql;
exit;

      Rating: 4
Item
      #1  July 18, 2002
Reviewer:  A reader

If I want a total of all rows in all the tables
( management requirement ), I could do the sum
with
$cat <spool file> |awk '{{total += $2}; END {print  "column total is", total}'

Is there a way to accomplish the summing up  in Oracle

Thanks

          Followup:

create global temporary table t ( tname varchar2(30), cnt number ) on commit
delete rows;

set termout off
set heading off
set feedback off
set linesize 100
set pagesize 99999
spool tmp.sql
select 'insert into t (tname,cnt) select ''' || table_name || ''', count(*) from
' || table_name || ';'
  from user_tables;
spool off
set termout on
@tmp

set heading on
set feedback on
compute sum of cnt on report
break on report
select tname, cnt from t;

SQL> @generate_relatively_meaningless_report_to_waste_resources

TNAME                                 CNT
------------------------------ ----------
BONUS                                   0
DEPT                                    4
DUMMY                                   1
EMP                                    14
PAYMENT                                 0
R1000                                1000
SALGRADE                                5
T                                       7
                               ----------
sum                                  1031

8 rows selected.

      Rating: 5
Thanks
      Tom  July 18, 2002
Reviewer:  A reader

      Rating: 4
please answer
      the fallacy????  July 17, 2003
Reviewer:
      Anurag  from INDIA
Hi!,

What we study in SQL*Plus is that when a user is doing certain transactions like
insert/update/delete and if its session is terminated abnormally , the
transactions are rolled back. Now something happened, which I did'nt understood.

SQL> SELECT COUNT(*) FROM EMP;

COUNT(*)
-------
14

SQL> DELETE FROM EMP;
14 ROWS DELETED

SQL> DISCONNECT;
Disconnected from SQL*Plus ...........

SQL> CONN SCOTT/TIGER
Connected

SQL> SELECT COUNT(*) FROM EMP;

COUNT(*)
----------
0

The same happened if I type EXIT instead of disconnect.

But if I closed window of SQL*Plus, records are rolled back.

Is disconnect/Exit statement are DDL???

The autocommit of env. variable is also off.

Please explain...

regards,

          Followup:

sqlplus commits upon exiting/logging off normally.  that is what you are seeing
here.

commit on successful exit.

      Rating:
      4
autocommit..  July 17, 2003
Reviewer:
      DD  from MN, USA
Autocommit automatically commits when your statement is completed. This has
nothing to do with exiting a session abnormally or normally.

RKPD01> set autocommit on
RKPD01> insert into emp (empno) values(2000);

1 row created.

Commit complete.
RKPD01>

I did not do a commit here but as soon as the statement executed it was
committed automatically.

July 17, 2003 Yong -- Thanks for the question regarding "Oracle
      Job scheduler is changing time", version 9i

      You Asked
Tom,

I have been using Oracle Job to schedule jobs. One of the problems I have is the
schedule keep changing slightly each times. For example, I schedule a job
running at 8:00am every day. After few weeks, I check the schedule by select *
from dba_jobs, the time become 8:15am or 8:45. I have this problem since Oracle
7,8,9i. How fix the problem?

Another question is how to change next_sec using dbms_job.change?

Thanks,

Yong

      and we said...
the jobs are run "about when" you ask them to be run.

So, you asked for a job to run at 8am.  At 8:01 we ran it and you told us "the
next time you should run this will be sysdate+1" -- well, that is 8:01am
tomorrow.  So tommorrow at about 8:01am (say 8:02) we ran it -- and it slips.

You need to use a NEXT that does NOT slip.  You want NEXT to evaluate to 8am
TOMORROW, regardless of what time it is right now.

trunc(sysdate+1)+8/24

is tomorrow -- at midnight (trunc) plus 8 hours (8/24 is 8 hours)

that is what your "next" should be, your interval.

Next_sec is the same as next_date, change next_date

ops$tkyte@ORA920> exec dbms_job.submit( :job, 'null;', sysdate,
'trunc(sysdate+1)+8/24' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920> @bigdate

Session altered.

ops$tkyte@ORA920> select job, next_date, next_sec from user_jobs;

       JOB NEXT_DATE            NEXT_SEC
---------- -------------------- --------
    100639 15-jun-2003 08:00:00 08:00:00

ops$tkyte@ORA920> exec dbms_job.change( :job, 'null;', trunc(sysdate+1)+9/24,
'trunc(sysdate+1)+9/24');

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select job, next_date, next_sec from user_jobs;

       JOB NEXT_DATE            NEXT_SEC
---------- -------------------- --------
    100639 15-jun-2003 09:00:00 09:00:00

Review &
      Followup

      Rating:
      5
  June 16, 2003
Reviewer:
      A reader
It works. Great! Oracle documentation is not very clean on this.

Thanks,

BTW, normally runing dbms_* package, commit is not necessary. But for
dbms_job.*, we have to commit it. It is not consistent with other dbms packages.
Any reason for that?

          Followup:

only those packages that commit say "we commit" - I would say it is the
exception to commit -- not the "norm"

      Rating: 5
question about
      interupted job  July 17, 2003
Reviewer:
      Jerry  from US
Tom,

I have a question.  I have some database jobs set up to run at night.  The
server went down at night so that jobs did not run.  When the server/db is back
up online, the jobs automatically kicked in immediately.  Is there any way to
prevent the jobs from running other than manually break the job?

Thanks!

          Followup:

have the jobs themselves check the time of day.  eg instead of scheduling "p;",
schedule

 if ( to_char(sysdate,'hh24') between 0 and 6 ) then p; end if;

they'll "run" upon startup but not really run.

      Rating:
      5
thanks  July 17, 2003
Reviewer:
      Jerry  from US
Thanks Tom for the great idea and quick response even in your vacation week. You
are great!

July 17, 2003 -- Thanks for the question regarding "garbage
      cleanup, sql query", version 9.1.2

      You Asked
Tom,
 1. Do we need to worry about "garbage cleanup" in pl/sql.
    At the very least:
     - open a cursor, close it
     - create a temporary lob, free it
     - allocate an array (varray,associative array, whatever), call it's delete
method when you're done.

But what about variables? I think I read somewhere on your site that varchars
and numbers are actually C structs and memory is allocated for them until they
go out of scope. What if we have a package that runs for hours and makes use of
several variables (numbers,varchar2s,records, let's say). Let's further assume
these variables are only used in the first 5 minutes. Do we need to "free" those
resources (is it even possible)? Is pl/sql taking care of all that good stuff
for us? Is this something we should be be conscience of ?

 2. Using max(decode(...)) we can manipulate what our result set looks like in
sql. Typically, we know the values that will be decoded, but what if we do not?
For example, this snippet (i pulled off the site ):
 max( decode( to_char(end_date,'MON'), 'MAR',status, null ) ) MARCH,
        max( decode( to_char(end_date,'MON'), 'APR',status, null ) ) APRIL,
        max( decode( to_char(end_date,'MON'), 'MAY',status, null ) ) MAY,
        max( decode( to_char(end_date,'MON'), 'JUN',status, null ) ) JUNE,
        max( decode( to_char(end_date,'MON'), 'JUL',status, null ) ) JULY,
        max( decode( to_char(end_date,'MON'), 'AUG',status, null ) ) AUGUST,
        max( decode( to_char(end_date,'MON'), 'SEP',status, null ) ) SEPTEMBER,
        max( decode( to_char(end_date,'MON'), 'OCT',status, null ) ) OCTOBER,
        max( decode( to_char(end_date,'MON'), 'NOV',status, null ) ) NOVEMBER,
        max( decode( to_char(end_date,'MON'), 'DEC',status, null ) ) DECEMBER,
        max( decode( to_char(end_date,'MON'), 'JAN',status, null ) ) JANUARY,
        max( decode( to_char(end_date,'MON'), 'FEB',status, null ) ) FEBRUARY

is straight forward because we know in advance the different months.
What if there were an unknown number of months? Can we still do this in sql?

3. What's the best way to get the sum of some numbers as well as display the
numbers as a comma separated list, in sql.

for example:

    name | num
   ----------
    tom  | 5
    tom  | 5

would look like:

tom 10 5,5

thanks in advance,
 - ant

      and we said...
1) if you have local variables -- no worries, they go out of scope when you exit
the procedure and "go away"

package globals -- only need to worry about collections/table types.  you need
to set them to empty tables when you are done with them.

2) you can

a) return a collection (array) with each row -- so you have one column that is
an array.

ops$tkyte@ORA920> create or replace type myTableType as table of varchar2(30)
  2  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> column enames format a50
ops$tkyte@ORA920> select deptno, cast( multiset( select ename
  2                                   from emp
  3                                  where emp.deptno = dept.deptno ) as
myTableType ) enames
  4    from dept
  5  /

    DEPTNO ENAMES
---------- --------------------------------------------------
        10 MYTABLETYPE('CLARK', 'KING', 'MILLER')
        20 MYTABLETYPE('SMITH', 'JONES', 'SCOTT', 'ADAMS', 'F
           ORD')

        30 MYTABLETYPE('ALLEN', 'WARD', 'MARTIN', 'BLAKE', 'T
           URNER', 'JAMES')

        40 MYTABLETYPE()

b) use a cursor variable:

ops$tkyte@ORA920> select deptno, CURSOR( select ename
  2                           from emp
  3                          where emp.deptno = dept.deptno ) enames
  4    from dept
  5  /

    DEPTNO ENAMES
---------- --------------------------------------------------
        10 CURSOR STATEMENT : 2

CURSOR STATEMENT : 2

ENAME
----------
CLARK
KING
MILLER

        20 CURSOR STATEMENT : 2

CURSOR STATEMENT : 2

ENAME
----------
SMITH
JONES
SCOTT
ADAMS
FORD

        30 CURSOR STATEMENT : 2

CURSOR STATEMENT : 2

ENAME
----------
ALLEN
WARD
MARTIN
BLAKE
TURNER
JAMES

6 rows selected.

        40 CURSOR STATEMENT : 2

CURSOR STATEMENT : 2

no rows selected

c) write a query to find the domain (the months in you case) and then build a
dynamic query based on that and use dbms_sql to process it

3)

ops$tkyte@ORA920> column sal_str format a30
ops$tkyte@ORA920> select deptno, sum(sal), stragg(sal) sal_str from emp group by
deptno;

    DEPTNO   SUM(SAL) SAL_STR
---------- ---------- ------------------------------
        10       9625 2695,5500,1430
        20    11962.5 880,3300,1210,3300,3272.5
        30      10340 1760,3135,1375,1650,1045,1375

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2196162600402
for stragg
Review & Followup

      Rating:
      5
excellent  June 26, 2003
Reviewer:
      Anthony Molinaro  from New York, New York
I appreciate your quick response. It truly is impressive how well you understand
this technology and can explain it in such a terse and lucid way. Your work is
truly invaluable to the Oracle community.

Followup to your responses:

1. Great, will do.
2. a,b,c - all great answers, even more than I was
           hoping for
3. never thought of that - very,very nice code.
   Links are extremely useful.

      Rating: 5
"Excellent"
      seems too small a word (or any other phrase for that
      matter)...  June 26, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Hi Tom,
Can not find words to describe your valuable inputs and code snippets and ...

Here is my concern:
I noticed that you said this in your reply above:
<quote>
you need
to set them to empty tables when you are done with them.
</quote>

Does this mean that:

1) <collection_variable>.Delete ;

is different than:

2). <collection_variable> := <empty_collection_variable> ;

Are they different? Is one better or recommended than other?
Thanks,

          Followup:

.delete will delete an element. (release it, free it)

   type array is table of number;

   g_data array;

......

   g_data := array();

will empty the entire array in one fell swoop.

but you can see the effect of .delete in this example:

ops$tkyte@ORA920> @mystat "ga memory"
ops$tkyte@ORA920> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  /
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('ga memory')||'%'

NAME                                VALUE
------------------------------ ----------
session uga memory                  77000
session uga memory max             142464
session pga memory                 220508
session pga memory max             286044

ops$tkyte@ORA920> declare
  2     type array is table of char(2000);
  3     g_data array := array();
  4  begin
  5          for i in 1 .. 1000
  6          loop
  7                  g_data.extend;
  8                  g_data(i) := 'x';
  9          end loop;
 10  end;
 11  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> @mystat "ga memory"
ops$tkyte@ORA920> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  /
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('ga memory')||'%'

NAME                                VALUE
------------------------------ ----------
session uga memory                 142464
session uga memory max             142464
session pga memory                2652272
session pga memory max            2652272

2.6m of pga for 1000 char(2000)'s at once.  now, if we allocate and delete --
we can see the space is reused

ops$tkyte@ORA920>
ops$tkyte@ORA920> @connect /
ops$tkyte@ORA920> set termout off
ops$tkyte@ORA920> REM GET afiedt.buf NOLIST
ops$tkyte@ORA920> set termout on
ops$tkyte@ORA920>
ops$tkyte@ORA920> @mystat "ga memory"
ops$tkyte@ORA920> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  /
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('ga memory')||'%'

NAME                                VALUE
------------------------------ ----------
session uga memory                  77000
session uga memory max             142464
session pga memory                 220508
session pga memory max             286044

ops$tkyte@ORA920> declare
  2     type array is table of char(2000);
  3     g_data array := array();
  4  begin
  5          for i in 1 .. 1000
  6          loop
  7                  g_data.extend;
  8                  g_data(i) := 'x';
  9                  g_data.delete(i);
 10          end loop;
 11  end;
 12  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> @mystat "ga memory"
ops$tkyte@ORA920> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  /
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('ga memory')||'%'

NAME                                VALUE
------------------------------ ----------
session uga memory                 142464
session uga memory max             142464
session pga memory                 302472
session pga memory max             302472

the pga memory stays small -- we did not have 1000 char(2000), the .delete
allowed that to be reused.

      Rating: 5
Great demo on
      memory alloc  June 27, 2003
Reviewer:
      AMolinaro  from ny,ny
Just to be 100% sure....

When we're done with the array and we want to free it, is there any difference
between reinitializing it to empty
( g_data := array() ) or calling delete with no parameters
( g_data.delete() ) ?

I tested both and both caused the array to be empty. I also tried to assign a
value to g_data(1) after freeing the array and got a subscript beyond count
error ( for both ).
So the data was freed and the extend operations were reversed for both. I
checked v$mystat
(in 2 separate sessions) and the uga/pga seem about the same for both.
Is there any difference? If so/not which do you prefer (find more reliable) ?

thanks again,
 - Ant

          Followup:

if I have to delete all entries, g_data := array() is easiest.

If I am processing array element by element, .delete is dandy.

which ever is easier to code you should use.

      Rating: 5
A crude
      analogy  June 27, 2003
Reviewer:  Logan
      Palanisamy  from Santa Clara, CA USA
Tom,

If I may use a very crude analogy,

"g_data := array()" is like truncating the table with drop storage clause.

"g_data.delete(i)" is like deleting a row in a table.

Am I correct?

          Followup:

no -- not really, not 100%

g_data := array() releases all of the "memory" back to the pga/uga for reuse by
anything else in the session.  so that is like the truncate example.

g_data.delete(i) does the same thing for that element -- releases the memory
back to the pga/uga for reuse by anything else in the session.  a delete of a
row would release it back for that table only -- here it is not just for that
table, but for anything.  g_data.delete(i) is like a mini truncate.

      Rating: 5
nice
      query  June 28, 2003
Reviewer:  Abdul Wahab
       from Jeddah
ops$tkyte@ORA920> select no,sum(elements), stragg(elements) elements from oracle
;
    no   SUM(elements)          elements
---------- ---------- ------------------------------
     1      TOM       'great','expert','professional','cool'
                      'rock'.......

      Rating: 5
stragg
      modification  July 17, 2003
Reviewer:
      Anthony
Tom,
 Can stragg be modified to accept a delimiter rather than use a hard coded
comma?

          Followup:

easiest would be to remove the delimited from the code itself and do something
like this:

select deptno, ltrim( stragg(','||ename), ',' ) enames
  from scott.emp
 group by deptno
/

...
member function ODCIAggregateIterate(self IN OUT string_agg_type,
                                     value IN varchar2 )
return number
is
begin
    self.total := self.total || value;
    return ODCIConst.Success;
end;

member function ODCIAggregateTerminate(self IN string_agg_type,
                                       returnValue OUT varchar2,
                                       flags IN number)
return number
is
begin
    returnValue := self.total;
    return ODCIConst.Success;
end;
......

      Rating: 5
excellent work
      around  July 17, 2003
Reviewer:  Anthony

works perfectly. thank you.

July 17, 2003 Vittorio -- Thanks for the question regarding
      "Intermedia text indexing", version 8.1.6

      You Asked
I have a table with a varchar2 column (Title) and a clob column
(Body).
Can i perform a select with the contains command for searching
on each columns ?

For example i want to search the word 'Oracle' on Title and
Body.

Thanks.

      and we said...
Yes you can -- you have 2 options here.

1) create two indexes, one on title and one on body.  Your
queries would then take the form:

  select * from T where contains(title,'Oracle') > 0 OR
                        contains(body,'Oracle') > 0;

2) create a PLSQL function that returns returns the two fields
glued together.  It would look something like:

connect scott/tiger

create table foo (id number primary key, thetext clob,
                  dummycol varchar2(1) );

insert into foo(id, thetext) values(1, 'I was here');
insert into foo(id, thetext) values(2, 'I was here too');
insert into foo(id, thetext) values(3, 'And Again');
commit;

connect ctxsys/ctxsys

create or replace procedure foo_proc(
    p_id in rowid,
    p_lob IN OUT clob
)
is
begin
    for c1 in (select thetext from scott.foo where rowid = p_id)

    loop
        -- assemble into p_lob ANY data you want
        -- indexed.
        dbms_lob.copy( p_lob, c1.thetext,
                       dbms_lob.getlength( c1.thetext ));
    end loop;
end;
/

grant execute on foo_proc to scott;
grant execute on ctx_ddl to scott;

connect scott/tiger

begin
ctx_ddl.drop_preference('foo_user_datastore');
ctx_ddl.create_preference( 'foo_user_datastore',
'user_datastore' );

ctx_ddl.set_attribute( 'foo_user_datastore', 'procedure',
'foo_proc' );
end;
/

begin
ctx_ddl.drop_preference('my_lexer');
ctx_ddl.create_preference( 'my_lexer', 'BASIC_LEXER' );
ctx_ddl.set_attribute( 'my_lexer', 'base_letter', 'YES');
ctx_ddl.set_attribute( 'my_lexer', 'mixed_case', 'YES' );
end;
/

create index foo2_idx on foo(dummycol) indextype is
ctxsys.context parameters( 'datastore foo_user_datastore lexer
my_lexer');

Review &
      Followup

      Rating:
      3
  September 28, 2001
Reviewer:
      Sundararajan  from India
Hi Tom,
Thanks for your explanation
Still you are not explaining how to glue two or more columns and how to query it
What is the use of dummycol of varchar2(1) and how it used in Intermedia index?

It will be great if you articulate further as to how to retrieve result from
two columns...

          Followup:

umm, select * from t where contains( dummycol, 'the word' )

You originally asked in your question something along the lines of:

i have a table with columns c1, c2, c3, ....

I need to find all rows where c1 = value or c2 = value or c3 = value.

How can I do this efficiently.
....

I sent you here, showing how to index a FUNCTION.  Look at #2 below and you see:

 for c1 in (select thetext from scott.foo where rowid = p_id)
    loop
        -- assemble into p_lob ANY data you want
        -- indexed.
        dbms_lob.copy( p_lob, c1.thetext,
                       dbms_lob.getlength( c1.thetext ));
    end loop;

you would concatent c1, c2, c3, ... together with spaces in between and index
that.

      Rating: 1
Tried copying
      ur example but not working  December 31, 2001

Reviewer:  Sushil Vyas  from India
Hi Tom,

I am not able to figure out why after doing exactly what is shown by you when i
query 0 rows are returned.

I have copied all the steps of this example but at the end when i say
select * from foo where contains(dummycol,'too') > 0

the records fetched shoud be 1 ideally but here i see is 0 rows returned. Also i
see that the transaction has got some data to be either commited or rollback.

Please explain this.

          Followup:

so show us the steps you used as I showed you the steps I used.  Perhaps the
blocks of code that do the drop and add are failing for you as you don't have
objects to drop.  You don't mention whether you hit and errors.

make sure you haven't hit any ERRORS and if you did, correct them!

      Rating: 5
Custom
      Datastore and CTX_DOC.HIGHLIGHT   May 08, 2003

Reviewer:  Angel Faus  from Barcelona, Spain

Hi Tom,

We are using this very same approach in our site and it works great.

We use also CTX_DOC.HIGHLIGHT to generate document summaries in the text queries
(just like Google or any other search engine does). It works fine but we have
found that calling CTX_DOC.HIGHLIGHT in an custom-datastore-index will call the
original datastore procedure.

This is quite a load, because it means copying all the document text fields from
the original table to a temporary location, and this is done for every document
the appears in the results page.

Do you think that storing the "concatenated" text in an extra field and
mantaining this one with triggers would solve the problem?

Many thanks.

p.s. Great site.. really.

          Followup:

if the cost of assembling the document (which it must do) is too expensive to do
upon retrieval, that would be an approach.

I just reassemble upon retrieval myself here.  In fact, every page is
reassembled from its constituent parts each time.

      Rating: 4
How about
      catalog index  July 17, 2003
Reviewer:
      Jason  from USA
Can we concatinate multiple columns in creating catalog indexes? Thanks.

          Followup:

it is done a little differently, but in a manner, yes

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/text.920/a96517/ind.htm#1011830

July 17, 2003 Ivan
      -- Thanks for the question regarding "Hash joining versus index", version
      7.3.4.4.0

      You Asked
Tom,

Currently I'm working on a datawarehouse project and there are
some serious performance problems.
In the init.ora file the parameter HASH_JOIN_ENABLED is set to
TRUE.
Altough I've created some additional indexes but they are almost
never used ,except when I specify hints like FIRST_ROWS and of
course RULE. In all other cases a FULL TABLE SCAN + HASH JOIN is
performed in memory.

How does and when does the COST-BASED optimizer decides to use
the HASHING instead of using the indexes (in combination with
NESTED LOOPS). Is it good to initialize the parameter
HASH_JOIN_ENABLED to TRUE or should we disable it and use hints
in our query-statements ?

Thanks in advance,
Ivan Verpoort.

      and we said...
I might consider doing:

o setting hash_join_enabled=false in the init.ora

o using alter session set hash_join_enabled=true; in sessions
  that wanted to use hash joins

If i did not like the hash join effect and wanted to disable it.

Actually -- if I was building a data warehouse, i would really
want to use Oracle8i for partitions, enhanced star queries, more
parallel operations, summary tables, advanced analytic functions
(rollup, cube operators, moving averages, cumulative frequencies
and so on), function based indexes, among many dozens of other
relevant advances.

Init.ora parameters such

hash_area_size            integer 0
hash_multiblock_io_count  integer 1

affect how frequently the hash join is choosen as well.  The
hash_area_size defaults to 2 x sort_area_size if set to 0.  The
larger a hash_area_size -- the more frequently hash joins will
be seen as a better alternative.  Same with the
multiblock_io_count -- the higher that is the more appealing the
hash join it.  So, an alternative to disabling the hash join (it
truely can be faster in many cases) is to tweak the above
parameters to make it "not as" appealing.  Both of these
parameters can be set at the session level so you can explain a
query -- adjust these parameters and then either flush the
shared pool or slightly modify the query (change the case of
something, add a space) and re-explain it to see the results.

Also, make sure you are using TRUE temporary tablespaces.  The
hash joins will make use of them -- if they are permanent or are
temporary and haven't been used yet, you'll get hit hard on the
temporary extent allocations (recursive sql).  Consider this
small example:

scott@ORA734.WORLD> alter tablespace temp permanent;
Tablespace altered.

scott@ORA734.WORLD> exec dbms_lock.sleep(5);
PL/SQL procedure successfully completed.

scott@ORA734.WORLD> alter tablespace temp temporary;
Tablespace altered.

that makes all of the extents allocated in temp "go away" so
the next query that needs them will have to allocate them

scott@ORA734.WORLD> select 3, t1.*, t2.* from t1, t2 where
t1.owner = t2.username;

1145 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=10 Card=939
1    0   HASH JOIN (Cost=10 Card=939 Bytes=121131)
2    1     TABLE ACCESS (FULL) OF 'T2' (Cost=1 Card=82
3    1     TABLE ACCESS (FULL) OF 'T1' (Cost=2 Card=1145

Statistics
----------------------------------------------------------
       4015  recursive calls
        538  db block gets
       1394  consistent gets
        342  physical reads
      76282  redo size
     142606  bytes sent via SQL*Net to client
       1311  bytes received via SQL*Net from client
         79  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       1145  rows processed

scott@ORA734.WORLD> select 5, t1.*, t2.* from t1, t2 where
t1.owner = t2.username;

1145 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=10 Card=939
   1    0   HASH JOIN (Cost=10 Card=939 Bytes=121131)
   2    1     TABLE ACCESS (FULL) OF 'T2' (Cost=1 Card=82 Byt
   3    1     TABLE ACCESS (FULL) OF 'T1' (Cost=2 Card=1145 Byte

Statistics
----------------------------------------------------------
          0  recursive calls
          5  db block gets
        358  consistent gets
        342  physical reads
          0  redo size
     142606  bytes sent via SQL*Net to client
       1311  bytes received via SQL*Net from client
         79  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       1145  rows processed

scott@ORA734.WORLD>

See the huge difference in recursive calls and redo generated?
These recursive calls were all space management calls and have
some overhead associated with them.  If you use a true temporary
tablespace -- you can avoid that once the temp tablespace
"warms" up.
Review & Followup

      Rating: 4
hash joins
      issue  July 01, 2003
Reviewer:  kit
       from England
Hi Tom,
I'm still confused with hash joins and need some clarifications. please see
below and then a scenario I'm trying to resolve

fIRST oracle definition of hash join
To perform a hash join, Oracle performs the following steps:
1. Oracle performs a full table scan on each of the tables and splits each into
as many partitions as possible based on the available memory.

2. Oracle builds a hash table from one of the partitions (if possible, Oracle
selects a partition that fits into available memory). Oracle then uses the
corresponding partition in the other table to probe the hash table. All
partition pairs that do not fit into memory are placed onto disk.

3. For each pair of partitions (one from each table), Oracle uses the smaller
one to build a hash table and the larger one to probe the hash table.

f 2 big tables joining on col a and B on both tables. how does it partition

If there are indexes on both tables then hash join via index or full table scan

see my scenario below

Table 1 driving table - 22 m rows of which 170,000 rows selected            -
not long
Table 2                      17m rows  Joined via unique index
Table 3                      564 rows
Table 4                      42516
Table 5                      3104
Table 6                 31 rows

end result rows selected 168k

users run 10 MV per day for an area, table 1 and 2 do not have an area and hence
cannot use the area specified

1& 2 driven first to get just the current days trade and lookup other tables

Please let me know which one you think will be efficient

Index scan on table 1 hash full table 2
Index scan on table 1 hash index table 2  takes ages than hash full table
Index scan on table 1 NL using unique index table 2 -

Table 3 has the area which users need to run for but can only be done via tables
1 and 2.

Table 4, 5, 6 can be done via index joins or is it best via hash joins

          Followup:

the efficient approach?

analyze the tables, gather good stats, let the database decide what is the best
path.

there is insufficient data here to say anything else.  (that and I'm not a sql
compiler, thats the job of the software)

      Rating: 3
hash
      joins  July 03, 2003
Reviewer:  kit
       from england
tom i'm still confuses about hash joins if you have2 very large tables, then is
it better to scan one using an index and full table scan on the other using an
hash join.

I can also index scan both tables and then also hash jojn but takes much longer

please explain issue about how it partiions the data

if join between the two tables are say seq_num then does it partition using that
key.

i did analyse the tables, via dbms stats for all columns ,cascade

and seems to do range scan and a full on other big table

|I cant see how this would be better than using the unique columns from the
rabge scan and looking up the other big table

I know NL brings best back the first row quicker  opposed to the hash join. but
i'm interetsed in whole thoughput but seems hash is perfoming worser than the NL

Just cannot explain it.
do you need explain plans to see more clearly

          Followup:

no, full scanning a table via an index is the worst thing possible, the bigger
the table, the worse it is (unless you are an interactive application that shows
the first 10 rows and waits for the end user to ask for more of course)

use hints, play with it, make the query execute the way you *believe* would be
better with indexes and time it with tkprof.  it is the only way to believe --
seeing it believing.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6749454952894
for an example.

      Rating: 3
HASH
      joins  July 14, 2003
Reviewer:  kit
       from england
Thanks for that. still bit confused

Please see below more notes on the scenario with stats
as you see the stats below diff scenario produces diff results.

Based on the info do you think better to do Hash or NL between table 1 and 2

Table 1 driving table - 22 m rows of which 170,000 rows selected            -
not long

            done via an index. this is joined to table of 17m rows but again the
result set is
            170,000 rows

            this then joined to table of 17m rows result set 170 K

Table 2                      17m rows  Joined via unique index

                              not restricted just looking up additional deal
data

            This then joined to table of 564 rows but end result still 170k

Table 3                      564 rows

                  still 170

Index range scan on table 1 and then NL via unique index on table 2

stats

index range scan table1 and NL via unique keys table 2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      1.49       1.51          0          0          0           0
Execute      1    189.92     870.98     158877    1074196         93      168606
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2    191.41     872.49     158877    1074196         93      168606

scan range tabl1 and hash full table 2
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.40       0.40          0          0          0           0
Execute      1    117.62     510.55      22842      52528         87      168606
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2    118.02     510.95      22842      52528         87      168606

scan range tabl1 and hash full table 2- same as above but when tother people
accessing
table 2. hits def increase not sure why

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.39       0.41          0          0          0           0
Execute      1    434.66     829.61    1129310    1943101         98      168012
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2    435.05     830.02    1129310    1943101         98      168606

Same scna and hash join but sometimes get the following plan. please explain

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.03          0          0          0           0
Execute      1     72.99     348.28       4466       4467         37      166986
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2     73.01     348.31       4466       4467         37      168606

also is is

analyze table invdatew compute statistics for
table for all indexes for all indexed columns;

same as dbms stats way

Whats the diff between all indexes and all ind columns

          Followup:

i've no clue what I'm looking at here.

but there is only one tkprof that looks good to me and that is the last one
which you say "sometime get the following plan" -- but I see no plans anywhere

Please -- when I'm taking new questions -- feel free to post one there....

I think

a) analyzing the tables
b) letting the optimizer do its job

is easier then trying to be an optimizer most of the time.  let it do its job

      Rating: 4
Hash_Area_size
      estimate  July 15, 2003
Reviewer:
      A reader
Hi Tom,
How to estimate, the hash_area_size required by a query?
Is there any formula to or is it achieved through benchmarking with different
values?

          Followup:

In Oracle9i, using pga_aggregate_target, this is not neccessary.

In 8i, you set it to the amount you want sessions to use.  It is a function of
the width of the hash table and the number of estimated/real rows in the hash
table.  There is no cut and dry formula I know of to estimate this and it would
necessarily change from execution to execution of the same query with different
inputs.

      Rating: 2
hash
      joins  July 15, 2003
Reviewer:  Kit
       from england
HI,

I only put the last one so you could explain why the same query produces diff
stats.
I can see the last one is best but the same query, range table 1 and hash full2

ok
back to basics

170k out of 22M rows shows good use of index for table 1

when joining on table 2 (total of 19m rows) but does not restrict the 170 k then
is it best to use the full hash or
the NL index lookup

The gather stats is related to this tuning problem so any hints greatly
apreciated

We do let the optimiser  choose but just wondering if there are other setting
which is making it choose the
wrong plans

      Rating: 2
Hash
      joins  July 17, 2003
Reviewer:  kit
       from england
Tom could you please get a chance to look at my last query

          Followup:

Please -- when I'm taking new questions -- feel free to post one there....

(as stated). this would be a "new question" really.  insufficient data to
comment on the above.

      Rating: 1
hash
      joins  July 17, 2003
Reviewer:  kit
       from england
Tom,
I can find the moment whenever you are accepting questions

Also I think my questions is related to this topic as I'm inquiring about hash
joins

pleaselet me know what more info you require

          Followup:

sorry.

      Rating:
      1
confused  July 21, 2003
Reviewer:
      kit  from england
Tom,
do you mean you still do not understand the question or you are not able to do
look at it now

July 17, 2003 Ram -- Thanks for the question regarding "Select
      Count(1): How it works", version 8

      You Asked
Hi,
 Will the following code ever result in l_num_rec_count being more than 1 in any
case?

SELECT count(1)
INTO l_num_rec_count
FROM <Table Name>
WHERE <condition1> AND <condition2>;

I am unable to find syntax of count(1) and I have to maintain a code with this
syntax. Thanks in advance for your help.

Cheers,
Ram

      and we said...
sure, that counts the number of NON-NULL 1's returned by that query.  That is
the same as "count(*)".

It is the same as "select count(<any non-null column>) ...."

count(1) is count(*) in disguise.

followup to comment one

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1156159920245
and take me up on my challenge then!  Prove it to me, give that irrefutable
test case that shows once and for all that count(1) is faster, better, cheaper
then count(*)!

I can only offer up cases that show they are in fact -- identical (over and over
and over I can offer up these cases).  They do the same exact thing.

It'll only take one case to prove me wrong.  Here is one (in addition to the one
in the link above) that shows count(*) and count(1) are IDENTICAL, the SAME, no
different:

select count(1) from t

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2     11.97      18.33      22165      22166         72           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4     11.98      18.34      22165      22166         72           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 334  (OPS$TKYTE)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
10000000   INDEX FAST FULL SCAN (object id 71204)

********************************************************************************

select count(*) from  t

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2     11.64      16.82      22165      22166         72           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4     11.65      16.82      22165      22166         72           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
10000000   INDEX FAST FULL SCAN (object id 71204)

followup to comment three

Badhri   -- take my challenge above!

Prove me wrong !

It only takes ONE CASE and I'll be wrong (but you'll never find it).

If you look at the example I had already provided above, you'll see that the
count(*) and count(1) both used the index.......

See also:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1156159920245
This is a silly discussion -- the proof is right above! they are the same!

Nuff said.

followup to anonymous in the UK (comment 4 and maybe 5?? )

Prove it, give me that test case.  You are wrong.  COUNT(*) and COUNT(1) are the
*same* -- the same -- the same -- no different, the same.  Give us the SQLPlus
script we can all run.

All you have to do is concoct 1 example -- just one, that shows otherwise.

"potentially" -- not valid here.

count(*) counts records in a table (it does NOT have to get the full record, it
just needs to know there is a record and increments a count)

count(1) counts non null occurences of the constant 1 in a table.  It does not
have to get the full record, it just needs to know there is a record and
increments a count.

they are the same, identical, not different.

Please -- if you want to followup to this and say "count(1) is faster" -- put a
test case -- or DONT POST a followup.

Try "select count(*) from all_objects" and "select count(1) from all_objects".

All Objects (being a fairly complex view)....

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1156159920245
for the proof that a view, a table -- i don't really care -- they are in fact
the SAME.

If you did comment 5 below as well and that is your "proof" that count(1) is
more efficient (not really sure if that is you or not -- not much of a proof as
no one can see what ABC is, does or whatever...)

Lets look at the details:

The proof is in the pudding
===========================
select count(*) from ABC

COUNT(*)
----------
22766017

Elapsed: 00:00:06.25

Statistics
----------------------------------------------------------
   19 recursive calls
  401 db block gets
48481 consistent gets
    0 physical reads
  776 redo size
  478 bytes sent via SQL*Net to client
  550 bytes received via SQL*Net from client
    2 SQL*Net roundtrips to/from client
    2 sorts (memory) 0 sorts (disk)
    1 rows processed

select count(1) from ABC

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

All that shows is yet once again -- they are in fact the SAME.  In the first
count(*) -- you did a hard parse (see the recursive SQL) that accounts for the
minimal, trivial difference in block gets and consistent gets.

Since you don't show a run time for the count(1), its rather meaningless.

Use TKPROF in the future as well -- set timing on in sqlplus is radically
affected by autotrace (it counts autotrace as well).  SQL_trace+tkprof is the
only correct way to test this.
Review &
      Followup

      Rating:
      3
Count(1)  August 13, 2001
Reviewer:
      Fuad Arshad  from Rolling Meadows,IL
I know COunt(1) is count(*) in disguise but isnt count(1) faster. we've been
evaluating all our queries and we've always found that count(1) had a better
access time than count(*)

      Rating:
      4
Thanks  August 14, 2001
Reviewer:
      Ram  from Singapore
I searched in vain on the net for some info on this. This answer clarified the
issue. Thanks..

      Rating:
      3
count(1)  August 14, 2001
Reviewer:
      Badhri  from India
Hey Arshad,
      U r right count(1) is faster than count(*)... I have heard people saying
thet count(1) will run through Ur tables' index whereas count(*) doesn't...

Please let me know if I am right or wrong...

      Rating: 1
Count(1) is
      definitely quicker than count(*) on large complex
      views  August 15, 2001
Reviewer:  Anonymous
       from UK
select count(1) from a view could potentially be quicker than count(*) provided
that the view contains columns from more than 1 table etc. because of the use of
temp tsp when doing count(*). So, yes count(1) is a better habbit to form than
count(*) and potentially quicker. Its wrong to say that count(*) is always the
same as count(1)

      Rating: 5
here's the
      proof  August 15, 2001
Reviewer:  Anonymous
       from UK
The proof is in the pudding
===========================

select count(*) from ABC

  COUNT(*)
----------
  22766017

Elapsed: 00:00:06.25

Statistics
----------------------------------------------------------
         19  recursive calls
        401  db block gets
      48481  consistent gets
          0  physical reads
        776  redo size
        478  bytes sent via SQL*Net to client
        550  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
          1  rows processed

select count(1) from ABC

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

      Rating: 5
I believe
      you!  August 15, 2001
Reviewer:  Tom
       from Florida
Tom --

Just wanted you to know that there are those of us who believe you.  Thanks for
the clarification.

      Rating: 5
Great lucid
      explanation  August 15, 2001
Reviewer:  Joel
       from Columbus, OH USA
Great answer Tom!

      Rating: 1
OK - here's the
      proof. Negligible difference but the point is its not the
      same  August 16, 2001
Reviewer:
      A reader
Less Parse / Exec and CPU time. The point I am trying to make here is its not
the same.

>> select count(*) from ABC

CPU AND NON-IDLE WAIT TIMES TOTALS FOR STATEMENT

Event  Elapsed  in Statement(%)  in Overall(%)
------  ----------  ----------------  --------------
CPU  0.07  100%  37%
Wait  0.00  0%  0%
------  ----------  ----------------  --------------
total  0.07  100%  37%

STATISTICS TOTALS FOR STATEMENT

call  count  cpu  elapsed  disk  query  current  miss  rows
----------  ------  --------  ----------  ----------  ----------  ----------
-----  ----------
Parse  2  0.03  0.08  0  53  8  1  0
Exec  2  0.03  0.34  0  0  6  0  0
Fetch  4  0.01  10.26  0  0  0  0  2
Unmap  0  0.00  0.00  0  0  0  0  0
Sort Unmap  0  0.00  0.00  0  0  0  0  0
----------  ------  --------  ----------  ----------  ----------  ----------
-----  ----------
total  8  0.07  10.68  0  53  14  1  2

EXECUTION PLANs FOR STATEMENT

Parse number  : 1
Parsing User ID  : 25
Relative starting time  : Seconds 41.39
Recursive depth  : 0
Optimizer goal  : Choose

Rows  Operation  Object ID
-----  ---------------------------------------------------------------
----------
1  SORT AGGREGATE   0
0    SORT AGGREGATE   0
0      TABLE ACCESS FULL ABC   43,208

Parse number  : 2
Parsing User ID  : 25
Relative starting time  : Seconds 80.61
Recursive depth  : 0
Optimizer goal  : Choose

Rows  Operation  Object ID
-----  ---------------------------------------------------------------
----------
1  SORT AGGREGATE   0
0    SORT AGGREGATE   0
0      TABLE ACCESS FULL ABC   43,208

=======================================

>> select count(1) from ABC

CPU AND NON-IDLE WAIT TIMES TOTALS FOR STATEMENT

Event  Elapsed  in Statement(%)  in Overall(%)
------  ----------  ----------------  --------------
CPU  0.02  100%  11%
Wait  0.00  0%  0%
------  ----------  ----------------  --------------
total  0.02  100%  11%

STATISTICS TOTALS FOR STATEMENT

call  count  cpu  elapsed  disk  query  current  miss  rows
----------  ------  --------  ----------  ----------  ----------  ----------
-----  ----------
Parse  2  0.00  0.01  0  2  8  1  0
Exec  2  0.01  0.00  0  0  6  0  0
Fetch  4  0.01  10.62  0  0  0  0  2
Unmap  0  0.00  0.00  0  0  0  0  0
Sort Unmap  0  0.00  0.00  0  0  0  0  0
----------  ------  --------  ----------  ----------  ----------  ----------
-----  ----------
total  8  0.02  10.63  0  2  14  1  2

EXECUTION PLANs FOR STATEMENT

Parse number  : 1
Parsing User ID  : 25
Relative starting time  : Seconds 64.14
Recursive depth  : 0
Optimizer goal  : Choose

Rows  Operation  Object ID
-----  ---------------------------------------------------------------
----------
1  SORT AGGREGATE   0
0    SORT AGGREGATE   0
0      TABLE ACCESS FULL ABC   43,208

Parse number  : 2
Parsing User ID  : 25
Relative starting time  : Seconds 110.57
Recursive depth  : 0
Optimizer goal  : Choose

Rows  Operation  Object ID
-----  ---------------------------------------------------------------
----------
1  SORT AGGREGATE   0
0    SORT AGGREGATE   0
0      TABLE ACCESS FULL ABC   43,208

      Rating: 3
Select
      Count(1): How it works  August 21, 2001
Reviewer:
      Sam  from Tampa, FL USA
I was reading a bug and found this:
problem is in count(col) to count(*) conversion
*** 03/23/00 05:46 pm ***
one workaround is to set event 10122 to turn off count(col) ->count(*)
optimization.
Another work around is to change the count(col) to count(*),
it means the same, when the col has a NOT NULL constraint.
The bug number is 1215372.

          Followup:

Thanks for finding that.  Just goes to show that count(*) and count(col) are the
same (and that in fact count(col) is "optimized" into count(*) internally!!!!

I can explain the differences the user in the prior comments saw simply by
"random-ness".  If they looked at my examples, something count(*) is marginally
(as they said "neglible") faster then count(1) and sometimes count(1) is
marginally faster then count(*).  This is due to the granularity of the clock
used by the system -- we measure discrete events -- not a continous sequence of
events, hence timing errors are avoidable (when the event you are measuring
takes less then 1/100's of a second, we will get some rounding errors.

      Rating: 5
Geeze - be
      scientists for a moment  August 22, 2001
Reviewer:
      Jim Kennedy  from OR, USA
To expand on Tom's explanation of why sometimes count(1) is faster than
count(*).  To truly prove it you need an example and you need to run that
example repeatedly.  The limits of precision on your experiment overwhelm what
you are measuring.  You would need to run it many many times in order for the
rounding error or randomness to settle down.  For example, to test whether a
coin is fair or not (fair being 50% of the time it lands heads and 50% of the
time it lands tails) you have to flip (test) the coin a lot. (about 1,500 times
to be sure with in 3% that it is fair; you can never be 100 % sure)

      Rating: 4
Quote from a
      book  August 22, 2001
Reviewer:  Carol
       from Victoria, BC, CANADA
The only reason I believed COUNT(*) is faster than COUNT(1) is because I read it
in 'Oracle Performance Tuning', second edition, O'Reilly & Associates, Inc, page
175.  It says:

"Contrary to popular belief COUNT(*) is faster than COUNT(1).  If the rows are
returned via an index, counting the indexed column - for example, COUNT(EMP_NO)
is faster still.  The optimizer realizes from the existence of the index that
the column must also exist (non-null).  We tested the following statements on
several different computers and found that COUNT(*) consistently runs between
15% and 20% faster than COUNT(1) and that COUNT(INDEXED_COLUMN) is 5% faster
again."

Doesn't seem so true to me anymore.

      Rating:
      5
  August 23, 2001
Reviewer:  AB
       from UK
Tom provides the answer as usual, backed up in spectacular form with concrete
examples. How people can question it with "hearsay" evidence is beyond me. These
are the same people that still believe explicit cursors are faster because of
the single fetch - and how many times have we seen Tom prove us wrong on that !
The point is this - whatever you believed to be true in Oracle 7 with respect to
the finer points of SQL tuning has somewhat changed in 8i. The database evolves
and access methods improve, and if you don't keep up with it, your standard
practices quickly become outdated and in cases, obsolete.

      Rating: 4
count(*) during
      tuning?  July 16, 2003
Reviewer:  John
       from San Jose, CA
Hi Tom,

It is not about count(1), count(*)...
1. Is
   select count(1) from (select....)
   always quicker than just the
   (select ....)
   ?

2. During performance tuning, is it a good idea to compare
   the 2
   select count(1) from (select....)
   or would it skew the data and would not give you the correct comparison
between the 2
  (select ...)?

 Thanks!

John

          Followup:

1) almost always -- that and it can and will use totally different PLANS.

never never select count(*) from ( query ) if you want to benchmark query.  it
is useless.

2) it would be utterly and 100% meaningless.

      Rating:
      5
Thanks!  July 16, 2003
Reviewer:
      John  from San Jose, CA
Thanks Tom! This question has bothered me for quite a while. I could not seem to
pin down the answer. Good to know that I can always come here for consultation!

John

      Rating:
      5
count(1)  July 17, 2003
Reviewer:
      Alvin  from Philippines
I'll only believe in count(1) if it will give me atleast 10-15% less I/O. If
not, then what's the point ? it might just confused other developer's/dba's not
familiar with it.

Readability is important if you have tons of packages/scripts to maintain.

          Followup:

huh?

count(1) is pointless.
count(*) is the right way

count(1) is internally optimized to count(*)

but -- i still don't get the followup here?  count(anything) shouldn't be used
for benchmarking unless the app itself selected count.

      Rating: 3
why count(1) is
      aesthetically better  July 17, 2003
Reviewer:
      mikito harakiri
For starters, optimizer is free to tranform any query into a semantically
equivalent one. The issue is not about performance, then: we are arguing about
what form is more "intuitive" only.

Suppose we have

table A
x y
- -
1 1
1 2
1 2

then how is that count card[(1,1),(1,2),(1,2)]=3, or card[1,1,2]=3 more
"intuitively evident" than card[1,1,1]=3 ?
For math challenged, the sql translation of the above formulas is

select count(*) from A
select count(y) from A
select count(1) from A

correspondingly. I like the last form because I could similarly write

select sum(1) from A

which is much less ambiguous.

          Followup:

count(y) is not the same as count(*) or count(1), it in general returns a
different answser.

      Rating: 4
with nulls one
      can never guess query result right   July 17, 2003

Reviewer:  mikito harakiri
I thought that '*' could be always substituted with list of columns in any
expression. But you are right, it can't:

select count(*) from (
  select null a from dual
)

Now, that's intuitive!

July 18, 2003 Irshad -- Thanks for the question regarding
      "Database startup", version 8.0.5 & 8.1

      You Asked
Hi,

I have two oracle instances one on Unix and the other on NT. The
name of my parameter file is Initorcl.ora. I have created
another (modified) parameter file named init_Des2000.ora on both
the boxes. For every startup, I would like oracle to read the
new parameter file i.e init_Des2000.ora automatically.

I have noticed that when I issue startup through server manager,
it still takes initoracl.ora as the parameter file. I would like
to avoid issuing startup pfile=......., instead I want to issue
only startup.

What do I do?
Thanks.

      and we said...
By default, oracle will read the
$ORACLE_HOME/dbs/init$ORACLE_SID.ora file (/database/ on NT).
If you want to use another file, you would have to use the
pfile= to override that.

Short of that, your options are:

o comment out all of the existing entries in the current
init$ORACLE_SID.ora and add

ifile=..../init_Des2000.ora

in it.

o rename init$ORACLE_SID.ora to init$ORACLE_SID.HOLD and name
your init_Des2000.ora to init$ORACLE_SID.ora

Review &
      Followup

      Rating: 5
which init file
      oracle is using  July 18, 2003
Reviewer:
      atul  from india
Hi,

I have one question,If other DBA has started a database
with pfile='....'  (init file kept in non std directory)..

Now database is up and running..
How to find by which initfile database has been started and location for a new
DBA??

Thanks & Regards,
Atul

          Followup:

you cannot, that information is not recorded anywhere.

July 18, 2003 Mudassar -- Thanks for the question regarding
      "Block Corruption", version 8.1.5

      You Asked
Oracle 8i has some new packages for detecting and solving issues
related to block corruption. My question is how does block
corruption happen when operating system most of the time would
have bad cluster remapping?

      and we said...
Well, the OS doesn't typically do that -- the hardware might.

Block corruption comes into play in many cases.

I've seen RAID software go bad and do the wrong thing in some
cases -- so block corruption can be software induced.

I've seen SCSI cables go bad and flip bits on the way in or out
-- hardware issue but not disk drive related.  If it is on the
way in -- it is totally random and hard to track down (a re-read
of a block might give a different answer)...

I've seen controller hardware go bad and flip bits.  One time --
the controller controled 1 out of 5 disks in a raid array.
Every 5'th disk would flip bits every now and then.  Very hard
to track down.

I've seen the OS report back that it wrote the block but the
entire block is ZERO'ed out.  Who knows what layer (OS,
software, hardware, etc) did this sort of stuff.

Block corruption is rare but it does happen.  As databases get
larger and larger -- the probability of it happening at some
point nears 100%.
Review & Followup

      Rating: 2
Block
      Corruption  July 17, 2003
Reviewer:  RameshG
       from U.K
Tom,

You have said that "Block corruption is rare but it does happen.  As databases
get larger and larger
-- the probability of it happening at some point nears 100%".   I am confused
here,  we got a table with 270million records with no partionning and is using
rule based optimizer. What is the probability of a data block getting corrupted?
 Is it 100% as you have said?.

The DatabaseCapacity project has bulked up this table upto 700 million records
and has come up saying that there are no threats to this table which is
contradicting with what you have said.

Thanks
RameshG

          Followup:

270 million what?  1 byte rows?  100 byte rows?  100000000 byte rows?

number of records is not meaningful.

the capacity planner is worried about space, not recovery issues.

If you have a solid recovery plan, have tested recovery, can do recovery, know
everything about recovery -- you are safe.

If not, you are not (safe)

      Rating: 4
Just an
      eye-opener  July 17, 2003
Reviewer:  Sneaking
      around
> the capacity planner is worried about space, not recovery issues.

The capacity planner is worried about:
o Space: disk, etc
o Performance: CPU, IO, ram, network, etc

Note: sometimes both are intermixed.

      Rating: 2
Block
      Corruption  July 18, 2003
Reviewer:  Ramesh
      G  from U.K
Thanks Tom.

The following is the structure of the table.

TableName : Customer_Req_Fields
CUST_REQ_ID             NUMBER (10)   NOT NULL,
CUST_REQ_FIELD_TYPE_ID  NUMBER (6)    NOT NULL,
CUST_REQ_GROUP          NUMBER (6)    NOT NULL,
CUST_GROUP_LEVEL1       NUMBER (3),
CUST_FIELD_VALUE        VARCHAR2 (250),
CUST_GROUP_LEVEL2       NUMBER (3),
CUST_GROUP_LEVEL3       NUMBER (3),
CUST_GROUP_LEVEL4       NUMBER (3),
CUST_CREATION_DATE      DATE          DEFAULT Sysdate,
CUST_MODIFIED_DATE      DATE

Most of these fields are not null and the average row size is 103bytes. There
are nearly 300million records in this table.

Recently i have raised the issue of recovery but was told that there are no
threats to the table.  I need a solid proof to justify my argument. Could you
please explain me how to justify that the recovery is a big issue.

Thanks
RameshG

          Followup:

who would be foolish enough to say "there are no threats"?

the structure of the table is MEANINGLESS.

if you value the data, if losing it would be painful, you will have a tried,
tested, bullet proof recovery process in place.  period.

anything else is "amateur time"

July 18, 2003 wallacel -- Thanks for the question regarding
      "rollback segment", version 8.1.6

      You Asked
Tom:

I recently read your archived article and learned a lot.
however i have some question regarding "set size of rollback
segments'

<Quote>
To find out the size and number of rollback segments needed
to handle normal processing on the database you need to do
some testing.  A good test is to start with small rollback
segments and allow your application to force them to extend.
Here are the steps to run such a test:

o  Create a rollback segment tablespace.

o  Select a number of rollback segments to test and create
   them in the tablespace.

o  Create the rollback segments so that all extents are the
   same size.  Choose an extent size that you suspect will
   need between 10 to 30 extents when the segments grow to
   full size.

o  Each rollback segments should start with two extents
   before the test is run.  This is the minimum number of
   extents any rollback segment can have.

o  Only the rollback segments that you are testing and the
   SYSTEM rollback segment should be online.
   Run transactions and load typical of the application.

o  Watch for rollback segment contention.

o  Watch for the maximum size a rollback extends to.

The maximum size any one of the rollback segments reaches
during the test is the size you want to use when
configuring.  This size we will call the "minimum coverage
size".  If you see contention, adjust the number of segments
and rerun the test.  Also, if the largest size requires
fewer than 10 extents, or more than 30, it is a good idea to
lower or raise the extent size, respectively, and rerun the
test.  Otherwise, space may be getting wasted during the
test and this would throw the number off.

At the end of this test, you will have some good base
estimates for the size and number of rollback segments
needed for normal processing.  However, handling large
transactions has not been discussed yet.  Calculating the
segment size needed for these types of transactions as well
as how to meet the requests for the calculated space will be
addressed.

For sizing rollback segment extents, it is strongly
recommended that each extent be of the same size.  The minimum
and maximum number of extents for an individual segment should
be around twenty for best performance.
</quote>

My question is during the testing , do i need to limit
user transaction? since we only use system rollback segments
and testing rollback segments , and if we have a lot of
user transactions, i guess testing rollback will continue
grow without limit.

( such as if we have 6 testing rollback segments and our setting
for transactions_per_rollback_segment is 5, then we should limit
user transactions to 30 or something like that.)
Regards,

      and we said...
During the testing you want to run as many concurrent
transactions as you will in real life -- what you are trying to
find is how man/big should they be... running anything less then
what you will run won't help you find that.  if
transactions/rollback segment isn't high enough to support your
concurrent transactions during the test, increase it.

We won't use system rollback segments.  they are for deferred
rollback.

Review &
      Followup

      Rating: 4
Minimum Extents
      of Rollback Segments  July 11, 2003
Reviewer:
      Vivek Sharma  from Bombay, India
Dear Tom,

Rollback segments should have minimum 2 extents. But what is the reason behind
it ?  Can u please explain why does rollback segment have min extents 2.

Your answer on this will be highly appreciated.

Thanks and Regards
Vivek Sharma

          Followup:

predominantly because the documentation says so and they will not create
otherwise :)

they are used as a circular queue, they (the extents) are treated like a linked
list with the last entry in the list pointing to the first.  Also, since we
cannot enter and reuse the "front" of an extent until there are no active
transactions IN the extent -- that would mean that every transaction that tried
to span extents would fail (eg: you would suffer tons and tons of failed
transactions)

That is, consider if the single extent were 1meg in size.  I 100byte transaction
starts with extent pointing at the very end.  It puts 50 bytes in the extent,
finds it to be full now -- and would then FAIL since it cannot reuse the head of
the extent until there are no active transactions and by definition we know
there is at least ONE active transaction -- ours.

      Rating: 4
Not
      Convinced...  July 17, 2003
Reviewer:  Vivek
      Sharma  from Bombay, India
Dear Tom,

I am very thankfull for your prompt replies. This site provides a very good
input and is better media to gain knowledge.

I am not convinced by the answer provided by you to my above mentioned question.
I do agree that the rollback segments will not be created unless 2 extents are
provided but what is the logic behind this. Assuming that we could create it
with 1 extent, when a trasaction needs another extend to span then it checks the
head, there is one active transaction which is ours and hence it should then
create another extent (no.2). This is what normally happens. If there are 2
extents, my transaction is extent 2 wants to span, it checks that there is no
space and needs to span across another extent, it checks extent 1 (circular),
there is an active transaction and hence it allocates  the 3rd extent and so
on...So why do we need 2 extents at minumum. What is oracle's logic behind it.
Your answer will help me update my knowledge.

Thanks in advance

Regards
Vivek Sharma

          Followup:

if you have one extent
and you set max extents to one

then you would jam the transaction at the end of the rbs each and every time.

hence ONE is not a good number at all for rollback segments

hence ONE is not a permitted number for rollback segments

TWO is the minimum.

if we let you use one, someone would set maxextents to one and get errors
constantly, and say "stupid database".  so we just don't let that happen.

      Rating: 4
1st Extent of
      RBS as Segment Header  July 18, 2003
Reviewer:
      Vivek Sharma  from Bombay, India
Dear Tom,

I have read in one of the books that the first extent of the RBS is used by
oracle as the segment header to manage the transaction information within the
rollback segment.
The second extent  contains the actual change vectors.

What are your comments on this ?

Regards
Vivek Sharma

          Followup:

the head of the firt extent is, not the whole thing

July 18, 2003 vijay -- Thanks for the question regarding "sql
      loader and date", version 8.1.6.0.0

      You Asked
hi!!!

i am using sqlloader, i have a table T in my database

T (empno, start_date date, resign_date date)

my data file has data like this
(date format IN THE DATAFILE is 'YYYYMMDD')

1, 19990101,20001101
2, 19981215,20010315
3, 19950520,00000000

the string 00000000 implies that the employee is still active i.e hasn't
resigned.

when i try to load these three rows i get an obvious error not a vaild   month
for the third employee since 0000000 is not a date.

what i have to do is to load a null value for the 00000000 encountered in the
datafile

can u please tell me how replace 00000000 with a null  value such that the no
errors like "not a valid month" r not encountered.

 can we write dml statements like update, delete etc in the control
file.if yes ..please tell me how to achibe it

regards

vijay

      and we said...
Just use NULLIF:

LOAD DATA
INFILE *
INTO TABLE T
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
(empno,
 start_date  date 'YYYYMMDD',
 resign_date date 'YYYYMMDD' nullif resign_date = '00000000' )
BEGINDATA
1, 19990101,20001101
2, 19981215,20010315
3, 19950520,00000000

No, you cannot have update/delete in the control file.  sqlldr does one thing --
loads data.
Review & Followup

      Rating: 3
What about
      packed decimals ?  July 17, 2003
Reviewer:
      Adrian Billington  from UK
I have an EBCDIC file with a packed decimal that is supposed to contain a
numeric representation of a date in yyyyddd format. As we can't go from DECIMAL
to DATE in one pass, I'm trying to load it to a VARCHAR2(7), so that I can
TO_DATE it by view. But I am finding it difficult to NULLIF the field and
instead get a zero. I have tried the following in SQL*Loader and ultimately want
to use an external table :-

,   CUCCJ    POSITION ( 203 : 206 ) DECIMAL ( 7 )
"DECODE(LENGTH(:CUCCJ),1,NULL,:CUCCJ)"
--,   CUCCJ    POSITION ( 203 : 206 ) DECIMAL ( 7 ) NULLIF CUCCJ = " "
--,   CUCCJ    POSITION ( 203 : 206 ) DECIMAL ( 7 ) NULLIF (203:206) = " "
--,   CUCCJ    POSITION ( 203 : 206 ) DECIMAL ( 7 ) NULLIF CUCCJ = '0'
--,   CUCCJ    POSITION ( 203 : 206 ) DECIMAL ( 7 ) NULLIF (203:206) = '0'
--,   CUCCJ    POSITION ( 203 : 206 ) DECIMAL ( 7 ) NULLIF (203:206) = '0000000'
--,   CUCCJ    POSITION ( 203 : 206 ) DECIMAL ( 7 ) NULLIF CUCCJ = BLANKS
--,   CUCCJ    POSITION ( 203 : 206 ) DECIMAL ( 7 ) NULLIF (203:206) = BLANKS

Only the DECODE works in SQL*Loader, but as I want to use an external table, I
really need the NULLIF clause to work for me. In all cases other than the DECODE
example, I get :-

select cuccj, 'x'||cuccj||'x', length(cuccj), ascii(cuccj) from ext_cup01301;

CUCCJ   'X'||CUCC LENGTH(CUCCJ) ASCII(CUCCJ)
------- --------- ------------- ------------
0       x0x                   1           48
0       x0x                   1           48
0       x0x                   1           48
0       x0x                   1           48
0       x0x                   1           48
...

This is driving me MAD !!!

Regards
Adrian

          Followup:

with an external table -- you would simply create a view to decode anything you
wanted, it is infinitely easier.  I don't understand what issue you percieve
there?

      Rating: 2
I have a
      view...  July 18, 2003
Reviewer:  Adrian
      Billington  from UK
Tom

I do have views to "expose" my external tables. In those views, I have placed
all the business rules that we require to transform the data. What I don't want
to have to do inside the view is to clean every column as well, else I'd have
483 TRIM statements in the view against our largest XT and loads of additional
CASE/DECODEs.

SQL*Loader and XT access parameters obviously enable us to do some pretty high
level cleaning, such as LDRTRIM and NULLIF/DEFAULTIF. (Incidentally, I have a
potential bug being investigated by support with LRTRIM and LTRIM for XTs on
9.2.0.1). Anyway, I want to be able to separate the high level cleaning from the
actual data rules.

The overriding point is that I want to be able to do it using NULLIF and it
doesn't seem to be working for me. I'm not averse to workarounds because we need
them all over the place, but I just want someone to tell me that it either
doesn't work as expected in which case I'll happily use the workaround as you
suggest or does work but I've got the syntax incorrect.

Many thanks
Adrian

          Followup:

so?  you'll have them in the create table or the create view -- but you'll have
them.

rather then spend a while looking into it -- I personally would do as little as
I could in the create table and every thing I can in the create view.  One is
slightly "easier" then the other.

July 18, 2003 --
      Thanks for the question regarding "CONVERSION DATATYPE", version 8

      You Asked
Hi,

I would like to know if we can convert a long raw column into a clob or blob or
bfile.

If it is possible, can you say how?

Thanks and regards.

      and we said...
In Oracle8i release 8.1 and up, yes:

create table new_table as select c1, c2, TO_LOB(c3) from old_table;

or

insert into new_table select c1, c2, to_lob(c3) from old_table;

In Oracle9i, you can even:

alter table old_table modify ( c clob );

to convert it.

In 8.0, you must unload and reload manually.  You can use plsql to convert a
clob:

create or replace procedure long2clob( p_query in varchar2,
                                       p_bindvar in varchar2,
                                       p_clob  in out clob )
as
    l_cursor    integer default dbms_sql.open_cursor;
    l_long_val  varchar2(20);
    l_long_len  number;
    l_buflen    number := 20;
    l_curpos    number := 0;
    l_n            number;
begin
    dbms_sql.parse( l_cursor, p_query, dbms_sql.native );

    dbms_sql.bind_variable( l_cursor, ':bv', p_bindvar );
    dbms_sql.define_column_long(l_cursor, 1);
    l_n := dbms_sql.execute(l_cursor);

    if (dbms_sql.fetch_rows(l_cursor)>0)
    then
        loop
            dbms_sql.column_value_long(l_cursor, 1, l_buflen, l_curpos ,
                                        l_long_val, l_long_len );
            exit when l_long_len = 0;
            dbms_lob.write( p_clob, l_long_len, l_curpos+1, l_long_val );
            l_curpos := l_curpos + l_long_len;
      end loop;
   end if;
   dbms_sql.close_cursor(l_cursor);
exception
   when others then
      if dbms_sql.is_open(l_cursor) then
         dbms_sql.close_cursor(l_cursor);
      end if;
      raise;
end long2clob;
/

drop table test;
create table test ( y clob );

declare
    l_clob clob;
begin
    for x in ( select view_name from user_views where rownum < 25 )
    loop
        insert into test values ( empty_clob() ) returning y into l_clob;
        long2clob( 'select text from user_views where view_name = :bv',
                    x.view_name,
                    l_clob );
    end loop;
    commit;
end;
/
select dbms_lob.getlength(y) from test
/

but you must use C or some other 3gl for LONG RAW as there is no PLSQL interface
for them at all.

If you are interested, I have a very small OCI program that will unload any LONG
RAW from a table into a file (each row will become a separate file -- you select
out two columns, the filename and the long raw to put into the file).  You can
then use DBMS_LOB.LOADFROMFILE to load the files back into the BLOB columns (or
use the BFILE type on the files you just created)

http://asktom.oracle.com/~tkyte/unload.tar.gz
there are makefiles for unix and nt, you need a c compiler and oci.  The usage
is simply:

useage: ./unload user/pass@db 'select LONG_RAW, FILE_NAME from t where ...'

for every row that query returns -- it'll create a file named FILE_NAME and put
the LONG_RAW column into it.

Review &
      Followup

      Rating:
      5
Perfect  July 23, 2001
Reviewer:
      Michael
Thanks Tom, the answer is perfect!

      Rating:
      5
  July 24, 2001
Reviewer:
      A reader

      Rating: 4
Helpful, but
      why can't PL/SQL do this stuff?  October 24, 2001

Reviewer:  Tired  from Texas
This answer is helpful in that I want to unload some LONG RAWs into data files,
so that I can look at them. Thus far, it's a brick wall at every turn. The OCI C
program is helpful, but why oh why can't Oracle provide a REAL UTL_FILE package
(something that will read/write files of arbitrary size) and REAL support for
LONG RAW in PL/SQL? I write PL/SQL in my sleep. C takes more effort! (Plus I
have to locate a compiler, get my environment set up, install OCI, etc.)

      Rating:
      3
Helpful  November 28, 2001
Reviewer:
      Helen  from The wilds of Africa (Mozambique)
This points me in the right direction, I think, for converting a bunch of LONG
datatypes that should really have been VARCHAR2's - thanks Tom

      Rating: 5
Exactly what I
      needed  September 04, 2002
Reviewer:  Tom
      Best  from PA
After developing a Java solution to do this in a test DB, I then went to
production and realized I could not use the TO_LOB sql function (8.0.5).  Then I
went searching asktom some more, and found this OCI program that does EXACTLY
what I needed to do.

Thanks, Tom.

BTW - you could have done this in Java right?... as shown in:

http://download-east.oracle.com/docs/cd/A87860_01/doc/java.817/a83724/basic4.htm#1010769
under "Getting a LONG RAW Data Column with getBinaryStream()".  I admit I didn't
try it though.

          Followup:

Yes...  it could look like this:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:238014476571
even

      Rating: 4
update in oci
      returning clause  July 18, 2003
Reviewer:
      umesh  from bangalore india
Tom
CREATE TABLE B_TEST (
  ID  NUMBER,
  V   VARCHAR2(20),
  C   CLOB,
  B   BLOB)

insert into b_test (id,v) values (1,'Hello');

now one of the developer updates the c,b column with following using oci calls

update b_test set c=empty_clob(),b=empty_blob()
returning c,b into :1,:2;

the above update is fine..

but if he says
update b_test set c=empty_clob(),b=empty_blob(), id=100
returning c,b into :1,:2;

this gives a error , he has put the blob columns,.. not at the end.. but some
where in between..

If he tries to the same statement this way

update b_test set  id=100, c=empty_clob(),b=empty_blob()
returning c,b into :1,:2;
it works fine with that oci call

is it mandatory that lob columns to be updated should be at the end??

          Followup:

gee, "an error".

no, it should not be mandatory -- please file a tar with support (they'll be
curious as to the "error"

July 18, 2003 Joseph -- Thanks for the question regarding
      "Advantage of EUS with Thin Driver vs Proxy user with OCI driver ",
      version 9.0.2.0.1.0

      You Asked
Hi Tom,

For a project with a very high security requirement,
1) For connecting to database with audit in mind, can you please show what are
the advantages of an application which make use of Enterprise User Security vs
the one which is configuring LDAP user through OCI proxy user?

2) Which one of the above would be your prefer solution for a web application in
which security requirement is very high?

3) With roughly between 20,000 to 30,000 EUS in LDAP using thin driver with JNDI
data source config in Websphere application server, what would be the
performance like???

Thanks,

Joseph Nguyen

      and we said...
I asked David Knox, the Chief Security Engineer I work with to answser this and
he say:

1. Enterprise Users is an Oracle option packaged under Oracle Advanced Security
that provides a solution for identity management and single station
administration.  Enterprise users can use the same security credentials
(password and/or X.509 certificate), securely stored in the LDAP compliant
directory, to authenticate to multiple databases.  In addition to the single
sign-on/single password capability, the authorizations for the users are
centrally stored in the LDAP repository thus providing a single place for
administering users.  The users do not have to be created in the underlying
databases.  This architecture allows Oracle customers to build Internet-based
applications where securely managing and controlling thousands of users
accessing many databases can be easily accomplished.

The Enterprise Users capability compliments other Oracle security technologies
such as Oracle Label Security, Virtual Private Databases, Secure Application
Roles, and Oracle Selective Audit.  When used in combination, these technologies
provide rings of security that help to protect the most sensitive data.

Proxy authentication works by allowing the application to establish a connection
pool, and as the user makes a request, the application and database can quickly
set the connection to that of the user as though it were the user logging in
directly without actually doing it (i.e., setting up and breaking down the
network connection to the database).  The benefits are we get the security
capabilities of the database: 1. proper authorizations set, 2. access controls
enforced for that specific user, 3. auditing, all while maintaining the
performance needed for the Internet environment.

Proxy authentication solves two problems: Security and performance.  It is
called proxy authentication because we are proxying the user's identity to the
database where it can be authenticated.

You can actually use both Enterprise Users and Proxy Authentication
simultaneously!  They are independent but complimentary technologies.

2. Both.

3. You don't need to use JNDI to do anything for EUS.  The database talks to the
LDAP server for you.  But your point is relevant.  EUS does add some overhead to
the connection time - somewhere around 0.4 seconds, but your mileage may vary.
I would recommend setting up a test environment with your servers and your
network to determine if performance is acceptable.  If not, it would be
advisable to address the server and network tuning first as 30,000 users should
most definitely by maintained in LDAP (as opposed to creating 30,000 schemas.)
Review &
      Followup

      Rating: 5
JNDI with
      connection pool thin driver  April 01, 2003

Reviewer:  Joseph Nguyen  from Vienna, VA USA

Thanks Tom and very helpful info.

On the topic of JNDI, is it make sense that with enterprise user security
configured and our application is currently running as follows:

1) A Websphere connection pool data source (thin driver) with JNDI binding is
configured. The data source database username/password is the actual database
account which own all the tables, views... schema.

2) All our LDAP Enterprise User obtain database connection through our
connection manager (written in Java) which does context lookup of the 1) above
JNDI connection pool data source and return a Connection from call to
getconnection(username, password) where username and password is the LDAP
Enterprise Username/password.

3) The empty shared schema in which the EUS connect through is granted with the
tables privilege from the actual database user account in 1).

So far, seem like this configuration is working as expected. Again, is it make
sense for all these with EUS in place. Also, is the connection pool will helps
with performance?

Thanks,

Joseph Nguyen

          Followup:

 Having trouble with the wording here.  I think the answer is EUS would not be
relevant in a situation where the users are not authenticating (connecting)
directly to the database.

      Rating: 3
very
      good.  July 16, 2003
Reviewer:  Mike
       from NJ, US
Tom & Joe:
It seems you worked on the EUS and LDAP for a while. Do you have any experiences
for allowing end users to change their own password or their information? My
client has about 30s enterprise users. It is impossible to maintain their info
by admin by using EUS or ESM or ELA. What is the solution for you? You also has
20 30 s users to manage.
Thanks,
Mike

          Followup:

David Knox says....

 The ELA is the only way for users to modify their Oracle passwords. As far as
other attributes go, they can use any standard LDAP client to query and update
their attributes. You could even write a simple Web program using JNDI or
DBMS_LDAP to do that for them if there are specific attributes that need to be
updated all the time.

July 19, 2003 James -- Thanks for the question regarding
      "Reclaim space", version v8.1.6

      You Asked
Dear Tom,
     When the records of a table are all deleted and committed.
and new records are inserted again, will these new records use
the space/blocks originally used by deleted records to store
data?  If I run 'ALTER TABLESPACE tbsname COALESCE;' before
inserting new records, will all space/blocks be reclaimed and
free space in dba_data_file increase?

Rgds
James

      and we said...
Alter tablespace tbsname coalesce will NEVER change the
sum(bytes) but it might change the MAX(bytes).  coalesce will
take adjancent free blocks and make them become one big block.

The space released by a delete will be reused by subsequent
inserts.  Below is a test I sent to someone last week on this
subject.  They were worried that a table grew at some point and
wanted to reorg the database to reclaim "free space".  My take
on this is that -- if the table needed to be that big at some
point in time and a delete made it smaller but you plan on
inserting again -- just leave it alone.  It needs that space.
You have no space to reclaim, it is going to need it right back
again.  Reorg'ing to reclaim "free space" is an excercise in
futility in most cases -- the "free space" of today is the space
you need later this afternoon again.  Database tables in most
cases (depends on the usage) hit a steady state size if the
amount of data in the table stays steady.

... (from another discussion, shows space gets reused -- you can
get show_space from here:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:231414051079
)...

Tomorrow, when you pump it full of transactions again -- it'll
grow again and never give up that space.  It needs that space,
not all of the time, just sometimes.  But the fact is at some
time -- it needs that space.  It'll reuse that space as you
delete.  You didn't print out the most important stat "free
blocks".  That'll show the number of blocks on the free list
under the HWM.  You are showing total blocks allocated -- many
of them are free blocks.

So, like I said -- you could have a full time job reorging this
table every day/week.  You'll still need to have 14k blocks of
space free for it since it does grow that large under certain
circumstances (so you won't be saving space in the long term,
you might get back some today but you'll just need it again
tomorrow).  I myself prefer to let things grow to their steady
state size and go from there.

We can observe that a table in a more or less steady state will
stop growing at some point in time with minimal overhead.
Consider this example that mimics "insert and then delete some
and insert more and delete some and insert more and so on".  To
show that the first time through this process was not a fluke,
we do it 2 times.  The table initially grows to 256 blocks but
stays there as long as the amount of data in the table at peak
remains the same.  I use a char(2000) to get really big rows (a
char(2000) consumes 2003 bytes all of the time unless its null
and mine never is)

ops$tkyte@DEV816> drop table t;
Table dropped.

ops$tkyte@DEV816> drop sequence myseq;
Sequence dropped.

ops$tkyte@DEV816> create sequence myseq;
Sequence created.

ops$tkyte@DEV816> create table t ( x int, y char(2000) default
'*' );
Table created.

ops$tkyte@DEV816> begin
  2      for i in 1 .. 500 loop
  3          insert into t ( x ) values ( myseq.nextval );
  4      end loop;
  5      commit;
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@DEV816> exec show_space( 'T' );
Free Blocks.............................1
Total Blocks............................192
Total Bytes.............................1572864
Unused Blocks...........................24
Unused Bytes............................196608
Last Used Ext FileId....................7
Last Used Ext BlockId...................13065
Last Used Block.........................40

PL/SQL procedure successfully completed.

So, the OPTIMAL table with 500 rows takes 192 blocks.....  when
we delete/insert/delete/insert (steady state, 500 rows) we get:

ops$tkyte@DEV816> begin
  2      for i in 1 .. 10
  3      loop
  4          for x in ( select rowid rid, rownum rnum from t )
  5          loop
  6                  if ( mod( x.rnum, 2 ) = 1 ) then
  7                          delete from t where rowid = x.rid;
  8                  end if;
  9          end loop;
 10          commit;
 11          for i in 1 .. 250 loop
 12              insert into t ( x ) values ( myseq.nextval );
 13          end loop;
 14          commit;
 15      end loop;
 16  end;
 17  /

PL/SQL procedure successfully completed.

ops$tkyte@DEV816> exec show_space( 'T' );
Free Blocks.............................11
Total Blocks............................256
Total Bytes.............................2097152
Unused Blocks...........................59
Unused Bytes............................483328
Last Used Ext FileId....................7
Last Used Ext BlockId...................13129
Last Used Block.........................5

PL/SQL procedure successfully completed.

The table goes to 256 blocks -- 11 are free (can still take rows
inserted) and 59 are not yet used.  We added an extent to
thetable.  The total number of used blocks is just about the
same however....

Just to show it is not a fluke:

ops$tkyte@DEV816> begin
  2      for i in 1 .. 10
  3      loop
  4          for x in ( select rowid rid, rownum rnum from t )
  5          loop
  6                 if ( mod( x.rnum, 2 ) = 1 ) then
  7                       delete from t where rowid = x.rid;
  8                 end if;
  9          end loop;
 10          commit;
 11          for i in 1 .. 250 loop
 12              insert into t ( x ) values ( myseq.nextval );
 13          end loop;
 14          commit;
 15      end loop;
 16  end;
 17  /

PL/SQL procedure successfully completed.

ops$tkyte@DEV816> exec show_space( 'T' );
Free Blocks.............................15
Total Blocks............................256
Total Bytes.............................2097152
Unused Blocks...........................59
Unused Bytes............................483328
Last Used Ext FileId....................7
Last Used Ext BlockId...................13129
Last Used Block.........................5

PL/SQL procedure successfully completed.

ops$tkyte@DEV816> delete from t;
500 rows deleted.

ops$tkyte@DEV816> commit;
Commit complete.

ops$tkyte@DEV816> exec show_space( 'T' );
Free Blocks.............................196
Total Blocks............................256
Total Bytes.............................2097152
Unused Blocks...........................59
Unused Bytes............................483328
Last Used Ext FileId....................7
Last Used Ext BlockId...................13129
Last Used Block.........................5

PL/SQL procedure successfully completed.

ops$tkyte@DEV816> begin
  2      for i in 1 .. 500 loop
  3          insert into t ( x ) values ( myseq.nextval );
  4      end loop;
  5      commit;
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@DEV816>
ops$tkyte@DEV816> exec show_space( 'T' );
Free Blocks.............................30
Total Blocks............................256
Total Bytes.............................2097152
Unused Blocks...........................59
Unused Bytes............................483328
Last Used Ext FileId....................7
Last Used Ext BlockId...................13129
Last Used Block.........................5

PL/SQL procedure successfully completed.

ops$tkyte@DEV816>

the table at a steady state does not grow.  Your example
transaction table is not at its steady state peak right.  You
could save a couple of bytes today trimming the table back BUT
you also have to have that space in reserve as the table does
get that big sometime...

So, you can certainly spend the time to reorg this table, lots
of people do, it is my opinion that that operation is largely a
total waste of time since the table will just grow again (and
then stop growing).  You really need that much space for that
table.  If the table grew without bounds -- that would be
totally different story.

Review &
      Followup

      Rating: 3
Reclaim Space
      from RBS datafiles  July 18, 2003
Reviewer:
      Vikas Sharma  from Delhi India
Hi Tom,

I was performing the table level import of a table which had about 100 millon
records.

During the import the datafile in my RBS started getting increased in size from
300MB to 1.5GB when import got over. and it was abt 95% full.

But when the import is over after somtime my dba studio statistics shows the RBS
tablespace is 10% full.

Now i want to regain the space on my hard dirve. ie i want to reduce the size of
the datafile. but it not allowing me to reduced the size of the datafile and i
get ORA -03297

Pl tell me there is any way to reclaim the space.

Thanks

Regards,

Vikas Sharma

          Followup:

it is easiest to

a) create new
b) drop old

just get rid of the rbs, then you can either shrink the datafiles down and add
new ones or just create a new rbs tablespace and drop the old one.

July 19, 2003 Baqir -- Thanks for the question regarding "how
      to import sequence ", version 9.2.0.1

      You Asked
export is done on table level using 9.2.0.1
exp user/password tables=emp,foo file=test.dmp
during import sequences never got imported. This is the default behavior of
oracle.  I would appreciate if you please advise on the followings:
1. How to import sequences in table level export?
2. How to get the same sequence value at the time of export?

Thanks

      and we said...
1) why would sequences -- which are not in any way shape or form related to
tables -- by exported in a table level export?

sequences are objects -- just like a table, procedure, view, package, etc.  if
you export A TABLE, that is all you get -- the TABLE.

2) you would export a database or schema, that will get the sequences.
Review &
      Followup

      Rating: 2
how to import
      sequence  November 06, 2002
Reviewer:  Baqir
      Hussain  from USA
We do not want to export whole schema or database except for few tables with all
sequences. Is there any way to get it before or after the table level export is
done?

          Followup:

Nope.  You'll just want to script the sequences

select 'create sequence ' || sequence_name || ' start with ' || last_number+1 ||
';' from user_sequences where.....;

      Rating: 3
how to import
      sequences  November 06, 2002
Reviewer:
      A reader
Thanks for the prompt reply.
exp with tables do not import sequences. I just tried the following to import
sequences and it worked:
1. exp user/passwordd tables=emp,foo indexes=n constraints=n triggers=n rows=n
2. then import with
imp sys/manager fromuser=boo touser=boo rows=n ignore=y
and sequences were imported.

I just bought your book "expert one on one" . I am going through chapter by
chapter. It's an exellent. Waiting for your book on Tuning next spring 2003.

          Followup:

that'll get more then sequences but if you are happy with it -- cool.

      Rating: 3
Importing
      Sequences  November 07, 2002
Reviewer:  mohammad
      taha  from Dubai
IF "sequences are objects -- just like a table, procedure, view, package, etc.
if you export A TABLE, that is all you get -- the TABLE." IS TRUE THEN

HOW
"1. exp user/passwordd tables=emp,foo indexes=n constraints=n triggers=n rows=n
2. then import with
imp sys/manager fromuser=boo touser=boo rows=n ignore=y
and sequences were imported.

Followup:
that'll get more then sequences but if you are happy with it -- cool." IS
TRUE?????

ARE THEY NOT CONTRADICTORY???

          Followup:

I didn't really read their command -- assumed what they said was true.

If you have the tables=..... on there, they are mistaken.  They will NOT get
sequences.  Proof:

ops$tkyte@ORA920.US.ORACLE.COM> create table t ( x int );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM> create sequence s;

Sequence created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select object_name, object_type from
user_objects;

OBJECT_NAME                    OBJECT_TYPE
------------------------------ ------------------
S                              SEQUENCE
T                              TABLE

ops$tkyte@ORA920.US.ORACLE.COM> host exp userid=/ tables=t indexes=n
constraints=n triggers=n rows=n

Export: Release 9.2.0.1.0 - Production on Thu Nov 7 07:54:11 2002

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to: Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
Export done in WE8ISO8859P1 character set and AL16UTF16 NCHAR character set
Note: table data (rows) will not be exported
Note: indexes on tables will not be exported
Note: constraints on tables will not be exported

About to export specified tables via Conventional Path ...
. . exporting table                              T
Export terminated successfully without warnings.

ops$tkyte@ORA920.US.ORACLE.COM> drop table t;

Table dropped.

ops$tkyte@ORA920.US.ORACLE.COM> drop sequence s;

Sequence dropped.

ops$tkyte@ORA920.US.ORACLE.COM> host imp userid=/ 'fromuser=ops$tkyte'
'touser=ops$tkyte' rows=n ignore=y

Import: Release 9.2.0.1.0 - Production on Thu Nov 7 07:54:12 2002

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to: Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

Export file created by EXPORT:V09.02.00 via conventional path
import done in WE8ISO8859P1 character set and AL16UTF16 NCHAR character set
Import terminated successfully without warnings.

ops$tkyte@ORA920.US.ORACLE.COM> select object_name, object_type from
user_objects;

OBJECT_NAME                    OBJECT_TYPE
------------------------------ ------------------
T                              TABLE

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

no sequence, just table..  I guess I just assumed they had "owner=boo" which
WOULD get sequences (and other stuff)

ops$tkyte@ORA920.US.ORACLE.COM> select object_name, object_type from
user_objects;

OBJECT_NAME                    OBJECT_TYPE
------------------------------ ------------------
S                              SEQUENCE
T                              TABLE

ops$tkyte@ORA920.US.ORACLE.COM> host exp userid=/ 'owner=ops$tkyte' indexes=n
constraints=n triggers=n rows=n

Export: Release 9.2.0.1.0 - Production on Thu Nov 7 07:55:40 2002

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to: Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
Export done in WE8ISO8859P1 character set and AL16UTF16 NCHAR character set
Note: table data (rows) will not be exported
Note: indexes on tables will not be exported
Note: constraints on tables will not be exported

About to export specified users ...
. exporting pre-schema procedural objects and actions
. exporting foreign function library names for user OPS$TKYTE
. exporting PUBLIC type synonyms
. exporting private type synonyms
. exporting object type definitions for user OPS$TKYTE
About to export OPS$TKYTE's objects ...
. exporting database links
. exporting sequence numbers
. exporting cluster definitions
. about to export OPS$TKYTE's tables via Conventional Path ...
. . exporting table                              T
. exporting synonyms
. exporting views
. exporting stored procedures
. exporting operators
. exporting indextypes
. exporting posttables actions
. exporting materialized views
. exporting snapshot logs
. exporting job queues
. exporting refresh groups and children
. exporting dimensions
. exporting post-schema procedural objects and actions
. exporting statistics
Export terminated successfully without warnings.

ops$tkyte@ORA920.US.ORACLE.COM> drop table t;

Table dropped.

ops$tkyte@ORA920.US.ORACLE.COM> drop sequence s;

Sequence dropped.

ops$tkyte@ORA920.US.ORACLE.COM> host imp userid=/ 'fromuser=ops$tkyte'
'touser=ops$tkyte' rows=n ignore=y

Import: Release 9.2.0.1.0 - Production on Thu Nov 7 07:55:46 2002

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to: Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

Export file created by EXPORT:V09.02.00 via conventional path
import done in WE8ISO8859P1 character set and AL16UTF16 NCHAR character set
Import terminated successfully without warnings.

ops$tkyte@ORA920.US.ORACLE.COM> select object_name, object_type from
user_objects;

OBJECT_NAME                    OBJECT_TYPE
------------------------------ ------------------
S                              SEQUENCE
T                              TABLE

      Rating:
      3
  November 07, 2002
Reviewer:  Sagi
       from India
Hi,

I think probably TOM must have oveseen tables= clause.

It does not export sequences at the first instance because of TABLES= parameter.
I checked this for myself. Below is the output.

C:\>exp scott/tiger@rnd tables=emp,dept indexes=n rows=n constraints=n
triggers=n
 file=junk.dmp log=junk.exp

Export: Release 8.1.7.0.0 - Production on Thu Nov 7 12:35:32 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production
With the Partitioning option
JServer Release 8.1.7.0.0 - Production
Export done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set
Note: table data (rows) will not be exported
Note: indexes on tables will not be exported
Note: constraints on tables will not be exported

About to export specified tables via Conventional Path ...
<See here itself you can see Sequences not importing else it would give a
message saying
. exporting sequence numbers>

. . exporting table                            EMP
. . exporting table                           DEPT
Export terminated successfully without warnings.

C:\>imp scott/tiger@rnd fromuser=scott touser=a file=junk.dmp log=junk.imp
 show=y ignore=y

Import: Release 8.1.7.0.0 - Production on Thu Nov 7 12:38:13 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production
With the Partitioning option
JServer Release 8.1.7.0.0 - Production

Export file created by EXPORT:V08.01.07 via conventional path
import done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set
. importing SCOTT's objects into MODPLSQL
 "ALTER SCHEMA = "MODPLSQL""
 "CREATE TABLE "EMP" ("EMPNO" NUMBER(4, 0) NOT NULL ENABLE, "ENAME" CHAR(10),"
 " "JOB" CHAR(9), "MGR" NUMBER(4, 0), "HIREDATE" DATE, "SAL" NUMBER(7, 2), "C"
 "OMM" NUMBER(7, 2), "DEPTNO" NUMBER(2, 0))  PCTFREE 10 PCTUSED 40 INITRANS 1"
 " MAXTRANS 255 LOGGING STORAGE(INITIAL 40960 NEXT 40960 MINEXTENTS 1 MAXEXTE"
 "NTS 505 PCTINCREASE 50 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT) T"
 "ABLESPACE "USERS""

 "CREATE TABLE "DEPT" ("DEPTNO" NUMBER(2, 0), "DNAME" VARCHAR2(14), "LOC" VAR"
 "CHAR2(13))  PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 LOGGING STORAGE(I"
 "NITIAL 40960 NEXT 40960 MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 50 FREELIST"
 "S 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "USERS""
Import terminated successfully without warnings.

But If I remove the TABLES= parameter it does export sequence.

Please correct me TOM if I am wrong. We all are waiting to see your reply.

Regards,
Sagi

          Followup:

see above -- while you were typing this, I was typing that.

      Rating:
      3
  November 07, 2002
Reviewer:  Sagi
       from India
Hi Tom,

I hope with both answers of ours atleast people have the ideas a bit more clear
that irrespective to Oracle Version, SEQUENCES are not exported in TABLE LEVEL.

Because Your example was in 9i and mine in 8i.

Thanx once again tom for all the good work.

Regards,
Sagi.

      Rating: 4
how to import
      sequence  November 07, 2002
Reviewer:  Baqir
      Hussain  from USA
it was just a type. The actually command I used after the fisrt export was

exp user/passwordd file=test row=n constraints=n indexes=n triggers=n
consistent=y
and then import it with

imp system/manager fromuser=foo touser=foo file=test.dmp
and it imported sequences.  What is going on behind the scene I don't know. But
it worked.
The same last_number of the sequence was imported correctly.

          Followup:

sure -- but -- it'll get tons of other STUFF as well in general.  (and would be
a really slow way to get just the sequences from a schema if that is all you are
after -  me, i would use the supplied script I put above)

      Rating: 5
invalid
      number?  February 19, 2003
Reviewer:  Doug C
       from Danbury, CT USA
out of curiosity - why does the line you provided consistently give me invalid
number unless I put (last_number+1) in parenthesis ??

select 'create sequence '||sequence_name||' start with '|| last_number+1||';'
from user_sequences
                                                         *
ERROR at line 1:
ORA-01722: invalid number

But this is ok -
  1* select 'create sequence '||sequence_name||' start with '||(last_number+1)||
';' from user_sequences
SQL> /

          Followup:

sorry about that -- it is due to the operator precedence and order of operation.

it is trying to add

'create sequence '||sequence_name||' start with '|| last_number

to

1||';'

and that fails.  What we need to do is concatenate the (last_number+1) to the
string instead.  the parans get the order of operation right.

      Rating: 4
Import
      Views  July 18, 2003
Reviewer:  Nitin
       from Atlanta
We have a FULL db export. While performing the import (for restore), the process
died because it was not run in background and the host connectivity was lost. We
have created a parameter file with all the table names and imported all the
remainder of the tables.

The only object that needs to be imported now are the views. What is the best
way to import the views alone from the FULL export dump?

Can I use
IGNORE=Y ROWS=N INDEXES=N FROMUSER=ABC TOUSER=ABC

Regards

          Followup:

that is a tricky one.  imp isn't going to let you do just views.  you can do
"imp show=y" and grab the output -- the views will be there but they will need
some amount of editing.

sorry -- there isn't an easy solution for this from the dmp file.  I would be
very tempted to go back to the source system and use these scripts:

------------ getaview.sql ----------------------
set heading off
set long 99999999
set feedback off
set linesize 1000
set trimspool on
set verify off
set termout off
set embedded on

column column_name format a1000
column text format a1000

spool &1..sql
prompt create or replace view &1 (
select decode(column_id,1,'',',') || column_name  column_name
  from user_tab_columns
 where table_name = upper('&1')
 order by column_id
/
prompt ) as
select text
  from user_views
 where view_name = upper('&1')
/
prompt /
spool off

set termout on
set heading on
set feedback on
set verify on

-------------------------------------------------

---------------- getallviews.sql --------------------
set heading off
set feedback off
set linesize 1000
set trimspool on
set verify off
set termout off
set embedded on

spool tmp.sql
select '@getaview ' || view_name
from user_views
/
spool off

set termout on
set heading on
set feedback on
set verify on
----------------------------------------

that'll extract all of the view text for you for installation in the other
system.

      Rating: 4
Correction to -
      Import Views  July 18, 2003
Reviewer:  Nitin
       from Atlanta
I wanted to mention:

Can I use
IGNORE=N ROWS=N INDEXES=N FROMUSER=ABC TOUSER=ABC

Regards

          Followup:

you can try with more =N's -- grants=n constraints=n

that should work with lots of error messages (warnings)

      Rating:
      5
  August 04, 2003
Reviewer:
      A reader
"Nope.  You'll just want to script the sequences

select 'create sequence ' || sequence_name || ' start with ' || last_number+1 ||

';' from user_sequences where.....;
"

Hi Tom
just to confirm what you mean is that you will re-create
the sequences with a different starting number (one
more than the last number of the original export)
by running the script generated by that select statements
you get from spooling the command, right?

This would mean that the sequence values assigned to the
"same" rows in the table would be different in the
imported schema, right? I am trying to think of any consequences here - Any
comments?

Thank yo so much!!

      Rating: 5
sorry - dumb
      question!  August 04, 2003
Reviewer:
      A reader
Obviously you are importing the table with
the old data (including the sequence column values) so
that is why you are recreating sequence with the
"last_number +1" values - for future values of the sequence.

Dumb question!

July 19, 2003 George -- Thanks for the question regarding
      "Triggers -- and transactions.", version Oracle 8i

      You Asked
Tom,
   Are cascading triggers all subject to the commit/rollback of
the initiating task? If a task updates a table that has a
trigger associated with it that update a second table that has a
trigger associated with ... are all updates subject to the
initiating tasks commit/rollback.
   What if the last trigger executes a PL/SQL procedure that
writes a flat ascii file to disk, is this all part of the
initiating tasks' response time?

      and we said...
Historically and by default -- YES.  Triggers are considered all
part of the same exact transaction.

Not only are they part of the transaction but they are atomic
with respect to the firing statment.  What I mean by that is if
you have tables T1, T2 and T3 and table T1 has a trigger to
insert into T2 and T2 has one to insert into T3 and you execute:

  insert into T1 values ( ... );

either:

o both triggers and all three INSERTS succeed or
o all fail and are undone.

the insert is atomic -- its sideeffects (the triggers) are part
of it.  Either all of the triggers fire and succeed or they are
all undone.  To take that further lets say you execute:

   insert into some_table values ( ... );  /* this succeeds */
   insert into t1 values ( .... ); /* this fails */

After executing these two statements -- the work done by the
second insert is "undone" (including any side effects from the
triggers) but the work done by the FIRST insert is still there
-- you need to either commit it or roll it back.

Now, there are somethings you can do in plsql that are not
transactional -- writing to a file for example, or changing the
value of a global variable in a package.  These operations are
NOT undone -- only the inserts/updates/deletes you do are.
Therefore, if the trigger writes to an ascii file -- then closes
the file and then FAILS -- the file will still exist, we cannot
roll that back.  In cases like this where I want the file to
exists IF and ONLY IF the transaction succeeds, I use DBMS_JOB
to schedule the procedure to write the file right AFTER I
commit.  In that fashion -- that operation that cannot be rolled
back will execute after I succeed (and if it fails, the job
queues will report the error to me via enterprise manager or a
simple query).

In the beginning, I said "historically".  Starting with
Oracle8i, release 8.1 -- it is possible to have an "autonomous"
transaction.  they break this rule.  See

http://osi.oracle.com/~tkyte/autonomous/index.html
for info on that feature.

Review &
      Followup

      Rating:
      4
Triggers  April 12, 2002
Reviewer:
      Mahomed  from U.K
Is there any way of passing the 'new' and 'old' data structures that are
available
in triggers directly to another stored procedure instead of the way I am doing
currently as follows :

create or replace trigger trg_pallet_adiufer
after delete or insert or update on pallet for each row
declare
  o_pallet     pallet%rowtype := null;
  n_pallet     pallet%rowtype := null;
  l_retval     number;
BEGIN
  pkg_lib.lib_trace( 'TRPALLET', 2, '  old.intpal = ' ||
to_char(nvl(:old.intpal,-1)) ||
                    ' new.intpal = ' ||  to_char(nvl(:new.intpal,-1))   );

  if ( not deleting )
  then
    n_pallet.agvtrk := :new.agvtrk;
    n_pallet.bastyp := :new.bastyp;
    n_pallet.batref := :new.batref;
    n_pallet.bbfdat := :new.bbfdat;
    n_pallet.calwgt := :new.calwgt;
    n_pallet.cltcde := :new.cltcde;
    n_pallet.cltpon := :new.cltpon;
    n_pallet.copdat := :new.copdat;
    n_pallet.datrcv := :new.datrcv;
    n_pallet.dstasl := :new.dstasl;
    n_pallet.dstblk := :new.dstblk;
    n_pallet.dstcol := :new.dstcol;
    n_pallet.dstdep := :new.dstdep;
    n_pallet.dstlvl := :new.dstlvl;
    n_pallet.dstsid := :new.dstsid;
    n_pallet.imvnum := :new.imvnum;
    n_pallet.intpal := :new.intpal;
    n_pallet.intrct := :new.intrct;
    n_pallet.itmcde := :new.itmcde;
    n_pallet.labsid := :new.labsid;
    n_pallet.mhetrk := :new.mhetrk;
    n_pallet.numcas := :new.numcas;
    n_pallet.numinc := :new.numinc;
    n_pallet.palbas := :new.palbas;
    n_pallet.palhgt := :new.palhgt;
    n_pallet.palnum := :new.palnum;
    n_pallet.paltyp := :new.paltyp;
    n_pallet.palwgt := :new.palwgt;
    n_pallet.pcktyp := :new.pcktyp;
    n_pallet.piklin := :new.piklin;
    n_pallet.pikref := :new.pikref;
    n_pallet.prddat := :new.prddat;
    n_pallet.qtyuom := :new.qtyuom;
    n_pallet.rakasl := :new.rakasl;
    n_pallet.rakblk := :new.rakblk;
    n_pallet.rakcol := :new.rakcol;
    n_pallet.rakdep := :new.rakdep;
    n_pallet.raklvl := :new.raklvl;
    n_pallet.raksid := :new.raksid;
    n_pallet.rctlin := :new.rctlin;
    n_pallet.rejtxt := :new.rejtxt;
    n_pallet.rotdat := :new.rotdat;
    n_pallet.rotsec := :new.rotsec;
    n_pallet.rsvcde := :new.rsvcde;
    n_pallet.sscode := :new.sscode;
    n_pallet.stdpal := :new.stdpal;
    n_pallet.stscde := :new.stscde;
    n_pallet.stsrea := :new.stsrea;
    n_pallet.trndat := :new.trndat;
    n_pallet.trnsts := :new.trnsts;
    n_pallet.usedat := :new.usedat;
    n_pallet.whscde := :new.whscde;
  end if;
  if ( not inserting )
  then
    o_pallet.agvtrk := :old.agvtrk;
    o_pallet.bastyp := :old.bastyp;
    o_pallet.batref := :old.batref;
    o_pallet.bbfdat := :old.bbfdat;
    o_pallet.calwgt := :old.calwgt;
    o_pallet.cltcde := :old.cltcde;
    o_pallet.cltpon := :old.cltpon;
    o_pallet.copdat := :old.copdat;
    o_pallet.datrcv := :old.datrcv;
    o_pallet.dstasl := :old.dstasl;
    o_pallet.dstblk := :old.dstblk;
    o_pallet.dstcol := :old.dstcol;
    o_pallet.dstdep := :old.dstdep;
    o_pallet.dstlvl := :old.dstlvl;
    o_pallet.dstsid := :old.dstsid;
    o_pallet.imvnum := :old.imvnum;
    o_pallet.intpal := :old.intpal;
    o_pallet.intrct := :old.intrct;
    o_pallet.itmcde := :old.itmcde;
    o_pallet.labsid := :old.labsid;
    o_pallet.mhetrk := :old.mhetrk;
    o_pallet.numcas := :old.numcas;
    o_pallet.numinc := :old.numinc;
    o_pallet.palbas := :old.palbas;
    o_pallet.palhgt := :old.palhgt;
    o_pallet.palnum := :old.palnum;
    o_pallet.paltyp := :old.paltyp;
    o_pallet.palwgt := :old.palwgt;
    o_pallet.pcktyp := :old.pcktyp;
    o_pallet.piklin := :old.piklin;
    o_pallet.pikref := :old.pikref;
    o_pallet.prddat := :old.prddat;
    o_pallet.qtyuom := :old.qtyuom;
    o_pallet.rakasl := :old.rakasl;
    o_pallet.rakblk := :old.rakblk;
    o_pallet.rakcol := :old.rakcol;
    o_pallet.rakdep := :old.rakdep;
    o_pallet.raklvl := :old.raklvl;
    o_pallet.raksid := :old.raksid;
    o_pallet.rctlin := :old.rctlin;
    o_pallet.rejtxt := :old.rejtxt;
    o_pallet.rotdat := :old.rotdat;
    o_pallet.rotsec := :old.rotsec;
    o_pallet.rsvcde := :old.rsvcde;
    o_pallet.sscode := :old.sscode;
    o_pallet.stdpal := :old.stdpal;
    o_pallet.stscde := :old.stscde;
    o_pallet.stsrea := :old.stsrea;
    o_pallet.trndat := :old.trndat;
    o_pallet.trnsts := :old.trnsts;
    o_pallet.usedat := :old.usedat;
    o_pallet.whscde := :old.whscde;
  end if;

  l_retval := pkg_libpal.pallet_change('TRPALLET',1,o_pallet,n_pallet);

END;
/

          Followup:

nope, i use sql to write sql to automate the generation of the above trigger.

      Rating: 4
Cascading
      Triggers  July 18, 2003
Reviewer:  Bhupinder
       from INDIA
Dear Tom,
That was really very informative.
But,if Oracle can do cascade trigger firing? (I believe so,
but is there max level?)
Example:
Table A has an insert trigger
Table B has an update trigger

in Table A's insert trigger has SQL to update table
B.
If table B's update trigger will fire?

Put more complicate if there is table C with insert
trigger and in TableB's update trigger insert table
C....etc..etc.

How far the level can be cascaded?

          Followup:

the level will go as deep as you have the ability to open cursors for -- so
open-cursors will ultimately determine the depth.

      Rating:
      5
  July 23, 2003
Reviewer:
      A reader  from india

July 19, 2003 Sam -- Thanks for the question regarding
      "Analytical Functions- a brief introduction", version 8.1.7

      You Asked
Tom

One of our main dissapointment is that , we cannot use analytical functions in
sql from within  a procedure, as they are not compatible with the pl/sql engine.

Further, when I try to update my knowledge with the analytical functions, I get
the doubt as to the total number of analytical functions available with 8i.

Can you give a brief introduction to the analytical functions, giving the
simplest example possible for each analytical function( as you  have done in
case of CASE).

As far as I am concerned, the total number of analytical functions are
1.group by
2.rollbup
3.cube
4.partition
5.Rollover

Tom, kindly give the simples of examples possible, so that we understand the
concept first, and can then use them to reosolve complex functionalities.

( If we can use these above functions in pl/sql , I am curious, how much of
coding it would reduce, I guess a lot,  a lot lot infact.

Thank you

      and we said...
You can use them in PLSQL, you can either:

o use a view
o use native dynamic sql

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:186812348071
for example

I don't agree with your list of analytic functions.  There is

o group by rollup
o group by cube
o analytic functions such as sum, avg, lag, lead, min, max, and so on (they are
documented here:

http://technet.oracle.com/doc/oracle8i_816/server.816/a76989/functio2.htm#81409
You can search for group by rollup or group by cube on this site to see some
examples of that type of statement.

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1512805503041
is a good example of them.

Search for

partition over nulls

to see some analytic functions.  If you are really interested in this -- I can
recommand a book (mine) that has a complete chapter on nothing but how to use
analytic functions with a comparision between using and NOT using them
performance wise (they can be truly amazing at speeding many operations up as
well as doing things that are otherwise impossible or very awkward to do in SQL)

Review &
      Followup

      Rating: 4
analytical
      functions  December 26, 2001
Reviewer:
      vinay  from India
Helped me a lot. Thanx

      Rating: 5
how can I do
      this?  October 17, 2002
Reviewer:
      A reader
Hi

I would like to know if this is possible, I am using table emp

I want to show the top 3 sal and sum the rest, something like

select ename, sal
  from (select ename, sal
          from emp
        order by sal desc)
 where rownum <= 3
union
select 'REST', sum(sal)
  from (select ename, sal, rownum numrow
          from emp
        order by sal desc)
 where numrow > 3
order by 2

is this possible with analityc functions?

          Followup:

scott@ORA920.US.ORACLE.COM> select decode( rn, 1, ename, 2, ename, 3, ename,
'REST' ), sum(sal)
  2    from ( select row_number() over ( order by sal desc ) rn, ename, sal
  3             from emp
  4             )
  5   group by decode( rn, 1, ename, 2, ename, 3, ename, 'REST' )
  6   order by 2
  7  /

DECODE(RN,   SUM(SAL)
---------- ----------
ford             3000
scott            3000
king             5000
REST            18025

      Rating:
      5
here  October 17, 2002
Reviewer:
      A reader
select numrow, deptno, sal, max(total) over () rest
from (
 select
   numrow,
   deptno,
   sal,
   sum(sal) over (order by numrow range between 3 FOLLOWING and unbounded
following) total
 from
   (select
            empno,
            deptno,
            sal,
            sum(sal) over (order by empno) cumulative,
            dense_rank() over (order by sal desc) numrow
      from emp) b)
where numrow <= 3
/

          Followup:

see above, could be "shorter"

      Rating: 5
Partition
      analytical functions  October 18, 2002
Reviewer:
      IB  from India
I have read many examples from Tom's reply but still i am not very sure how
exactly it (partition)works ,when it can be used .If i know the exact use of it
i feel it will be very usefull.Could you please explain me with some good
examples

          Followup:

I'll have to refer you to my book "Expert one on one Oracle".  I have a lengthy
chapter on this topic in there with pictures and everything.

Or:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96520/analysis.htm#1020

      Rating: 5
for
      IB  October 18, 2002
Reviewer:
      A reader
Hi IB

do you know how break works in SQL*PLUS? It's probably a good point to start

      Rating: 5
Distributing
      tablespace equally  January 21, 2003
Reviewer:
      steve  from Montreal but currently in Karlskrona, Sweden

Hi Tom,

we are developing a hot backup solution. We want to
backup each tablespace once a week and perform backups
every day.  We plan to create a table like:

  create table backup_schedule (
   ts varchar2(40),   -- tablespace to backup
   day number(4,0)    -- day on which to backup 1..7
   )

The goal is that we want to populate the table so that the amount of data we
backup each day is more or less the same each day. For example, if the total
database size is 700G,
then we should backup 100G (more or less) every day.

Can you suggest (perhaps using analytical functions) a
query  that would populate this table? I hope  my question
makes sense.

BTW:  Oracle 9.2.0.1

Thanks

Steve

          Followup:

sorry -- I cannot think of a way to do a bin fitting problem in SQL -- even with
the analytics.

      Rating: 5
does steve win
      a prize  January 21, 2003
Reviewer:  Josh
       from Oregon
I suggest Steve wins a prize for stumping Tom.

Winner Winner Chicken Dinner

      Rating:
      5
awk  January 22, 2003
Reviewer:
      Steve  from Mtl
Hi Tom,

Due to lacck of time, we threw something together in awk.
Maybe useful to post (??):

First, we created an input file as follows:

select sum(bytes) total, ceil(sum(bytes)/7)
from dba_tablespaces a,
     dba_data_files b
     where a.TABLESPACE_NAME = b.TABLESPACE_NAME

/
select a.TABLESPACE_NAME,sum(bytes)
from dba_tablespaces a,
     dba_data_files b
where a.TABLESPACE_NAME = b.TABLESPACE_NAME
group by a.TABLESPACE_NAME
order by 2 desc
/

Then we ran the following awk script. Seems to work well.
No guarantees of course. But a good  starting point.

BytesPerDay=`head -1 $1 | awk '{print $2}'`
cp /dev/null $2
cat $1 | awk -v BytesPerDay=$BytesPerDay -v OutputFileName=$2 \
'
BEGIN {last=6;outfile=OutputFileName} {
        if (NR == 1) getline;
        for (i=0; i<7; i++){
                found=0;
                iindex=(i+last+1)%7;
                if(a[iindex]+$2 < BytesPerDay) {
                        found=1;
                        last=iindex;
                        a[iindex]+=$2;
                        printf("insert into backup_schedle (ts, day)
values(%c%s%c,%d)\n/\n",39,$1,39,iindex) >> out
file
                        break;
                }
        }
        #
        # if found = 0 at this point, we were not able
        # to find a place for this so we will put it
        # in the iindex with the least amount
        #
        if (found == 0) {
                smallest=a[0];
                smallesti=0;
                for(i=1;i<7;i++) {
                        if (a[i] < smallest ) {
                                smallest=a[i];
                                smallesti=i;
                        }
                }
                a[smallesti]+=$2;
                printf("insert into backup_schedule (ts day)
values(%c%s%c,%d)\n/\n",39,$1,39,iindex) >> outfile
        }
}
END {
        for(i=0;i<7;i++)
                printf("Size of backup %d = %12.0f\n",i,[i]);
}
'

Question
========

We have a few partitioned tables. Each partition has
its own tablespace. With this approach, different
parts (partitions) of a table get backed up on different
days. Perhaps not clean, but will work. Do you see any
problem with this?

          Followup:

It'll work -- as long as you keep the last 3 or so hot backups of everything and
all of the needed archive redo logs, no problem restoring with that.

      Rating: 2
Is Analytically
      functions included in 91 enterprise  March 05, 2003

Reviewer:  A reader
Hi tom,
If we need to use the analtical functions in 9'i do we have to get some
additional licese , as we are currently having Oracle 9.0.0.1 enterprise Edition
with partioning option

          Followup:

they are part of 9i SE and EE.

you can discover what you have by looking at the new features guide:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-NEW
(read the getting to know Oracle8i guide for 8i options/features)

      Rating: 2
Is Analytically
      functions included in 91 enterprise  March 05, 2003

Reviewer:  A reader
Hi tom,
If we need to use the analtical functions in 9'i do we have to get some
additional licese , as we are currently having Oracle 9.0.0.1 enterprise Edition
with partioning option

      Rating:
      3
  March 05, 2003
Reviewer:
      A reader
Hi tom,
If we need to use the analtical functions in 9'i do we have to get some
additional licese , as we are currently having Oracle 9.0.0.1 enterprise Edition
with partioning option

      Rating:
      5
  May 13, 2003
Reviewer:
      A reader
analytic functions was introduced in Introduced in Oracle 8.1.6, and in oracle
9i both standard and enterprise editions has license to use it. without
additional license.
I think you should read here

http://otn.oracle.com/products/oracle9i/content.html
about available features.

      Rating: 3
is that
      possible?  May 22, 2003
Reviewer:  sgs_ocp
       from INDIA
Hi Tom,

I would like to know
Can we use analytical functions to replace
start with and connect by

as its (latter) just hard to digest.

          Followup:

no, they are apples and toaster ovens.

totally different beasts

      Rating: 5
Are all
      analytical functions convertable to standard SQL?  July 18,
      2003
Reviewer:  Fan  from Canada
Hi Tom,

I am wondering if I can convert any analytical function to standard SQL, no
matter how complex.

If not, please give an example that has to be converted to use PL/SQL as well.

Thanks.

          Followup:

most of them probably could but they would run slower the molasses in feburary
in alaska.  complicated self joins, correlated subqueries, etc.

I'm not going to search for one that absolutely cannot be -- cause it would be
pretty near impossible to prove you couldn't.

besides -- analytics ARE standard sql.

July 19, 2003 Yong
      -- Thanks for the question regarding "Nologging does not work (yes it
      does...)", version 8.1.5

      You Asked
This is reported by Surya Rao <surya.rao@reuters.com> in a
mailing list and I reproduce it here:

SQL> create table test (a number) nologging;

Table created.

SQL> select table_name,logging
  2  FROM USER_TABLES
  3  where table_name = 'TEST';

TABLE_NAME                     LOG
------------------------------ ---
TEST                           NO

SQL> select sid from v$mystat where rownum = 1;

      SID
---------
       22

SQL> select name,a.value
  2  from v$sesstat a, v$sysstat b
  3  where b.statistic#=a.statistic#
  4  and b.name = 'redo size' and sid = 22;

NAME                                              VALUE
--------------------------------------------- ---------
redo size                                         25852

SQL> insert into test values (111);

1 row created.

SQL> select name,a.value
  2  from v$sesstat a, v$sysstat b
  3  where b.statistic#=a.statistic#
  4  and b.name = 'redo size' and sid = 22;

NAME                                              VALUE
--------------------------------------------- ---------
redo size                                         26520

SQL> commit;

Commit complete.

SQL> select name,a.value
  2  from v$sesstat a, v$sysstat b
  3  where b.statistic#=a.statistic#
  4  and b.name = 'redo size' and sid = 22;

NAME                                              VALUE
--------------------------------------------- ---------
redo size                                         26600

SQL> truncate table test;

Table truncated.

SQL> select name,a.value
  2  from v$sesstat a, v$sysstat b
  3  where b.statistic#=a.statistic#
  4  and b.name = 'redo size' and sid = 22;

NAME                                               VALUE
---------------------------------------------- ---------
redo size                                          29900

Why does redo size keep increasing? Shouldn't it be fixed for
this nologging table? Thank you.

Yong Huang

      and we said...
see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:477221446020
and

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:366018048216
Nologging only affects very very specific operations.  For
exmaple, from the alter index syntax statement in the SQL
reference:

<quote>
LOGGING|NOLOGGING

LOGGING|NOLOGGING specifies that subsequent Direct Loader
(SQL*Loader) and direct-load INSERT operations against a
nonpartitioned index, a range or hash index partition, or all
partitions or subpartitions of a composite-partitioned index
will be logged (LOGGING) or not logged (NOLOGGING) in the redo
log file.

In NOLOGGING mode, data is modified with minimal logging (to
mark new extents invalid and to record dictionary changes).
When applied during media recovery, the extent invalidation
records mark a range of blocks as logically corrupt, because the
redo data is not logged. Therefore, if you cannot afford to lose
this index, you must take a backup after the operation in
NOLOGGING mode.

If the database is run in ARCHIVELOG mode, media recovery from a
backup taken before an operation in LOGGING mode will re-create
the index. However, media recovery from a backup taken before an
operation in NOLOGGING mode will not re-create the index.
An index segment can have logging attributes different from
those of the base table and different from those of other index
segments for the same base table.
</quote>

That also explains why the truncate above generated redo -- The
statement "minimal logging (to mark new extents invalid and to
record dictionary changes)." explains where that redo comes
from.  The blocks that were truncated were not logged HOWEVER
the changes to the data dictionary itself were.

NOLOGGING works --

Review &
      Followup

      Rating: 5
What is the
      actual for CTAS using NOLOGGING  November 02, 2001

Reviewer:  Nag
wHEN THERE IS NO WHERE CLAUSE  the following works with NOLOGGING  given at the
end
SQL> create table myemp
  2  as select * from  emp
  3  nologging ;

Table created.

SQL> drop table myemp;

Table dropped.

Where you give a where clause , it does not work if nologging is given at the
end.
SQL> create table myemp
  2  as select empno,dname
  3  from emp,dept
  4  where emp.deptno=dept.deptno
  5  nologging   ;
nologging
*
ERROR at line 5:
ORA-00933: SQL command not properly ended

It works when nologging is given after the table name(when a where clause is
given in CTAS)
SQL> create table myemp nologging
  2  as select empno,dname
  3  from emp,dept
  4  where emp.deptno=dept.deptno
  5
SQL> /

Where is this difference of syntax documented in oracle documentation.

Why this change in syntax. It is confusing.

Table created.

(Tom, Yes, I do ask a lot of question , but believe me most of my questions
arise out of genuine practical situations, which every developer with startup to
intermediate experience every day, AND
I dont have words for the service you are rendering to us, I have already
mentioned to you that I see a lot of difference in the way I approach to bugs
and other technicaly difficult situations , the whole credit goes to to you)

          Followup:

This is the documented syntax.

The high level diagram for create table is something like:

create table <schema>.table
< ( relational properties ) >
< physical properties >
< table_properties >

Now, if you look at the wire diagram in the sql ref manual for physical
properties, you'll find it includes:

o segment attributes clause
o organization
o cluster
o lob storage
and so on.

part of segment attributes clause is NOLOGGIN.

The table properties wire diagram shows things like:

o range partition
o hash
o composite
o cache/nocache
o monitor/no monitor
o parallel
o AS SUBQUERY

so the as subquery is DEAD LAST in a create table statement (if present at all)

So, this is documented in the SQL ref manual under CREATE TABLE.

It is not a change in syntax -- it is just the AS SUBQUERY is DEAD LAST in the
CTAS

(btw: my comment about your "asking lots of questions" wasn't to get your dander
up, it was to make clear that i fully expect you to have my book given the
number of Q's you do ask (which is statistically higher then the rest of the
population out there, regardless of your motive).  I was really surprise when
you asked me about bind variables since my book is literally rife with examples)

      Rating:
      4
Reader  November 03, 2001
Reviewer:
      Reader  from USA
Tom,
"
In NOLOGGING mode, data is modified with minimal logging (to mark new extents
invalid and to record dictionary changes).
"
Once backup is taken, does Oracle re-visit the extents
and validate them. Could you tell us what process takes place.

Also, does Oracle determine that backup is being taken by
looking at the "Hot Backup end marker scn: " in control
file and "Backup taken at scn: " from the datafile.

Also, since the methodolgy changed by the advent of RMAN, does "Hot Backup end
marker scn: " still relevent

Thanks

          Followup:

No, the extents are not revisited.  In fact the extent invalidation is in the
redo not the table.

You see, if you do a non logged operation on a table that has data on "disk1"
and then a failure occurrs on disk 1 the following will happen:

o you'll restore disk1 from a backup
o you'll apply the redo logs to this.
o you'll need to apply the redo log with the invalidation entries (since it
happened AFTER the backup)
o it'll invalidate them, marking them "corrupt"

On the other hand if you do a non-logged operation on that table, BACKUP that
datafile right after and then a failure occurs the following will happen:

o you'll restore disk1 from that backup
o you'll apply redo logs, this time the redo log containing the invalidation
records is NOT applied -- it happened BEFORE the backup
o you are a-ok

I don't understand the "Also, does Oracle determine that backup is being
taken..." part.  If you are taking a backup the old fashioned way, you have
issued an alter tablespace begin backup (so it knows). If you are using RMAN,
RMAN knows and the database doesn't really care (no "backup" mode for
tablespaces with RMAN, not necessary.  RMAN does the book keeping its needs to
know what redo it need apply to datafiles upon recovery.

      Rating:
      5
Reader  November 03, 2001
Reviewer:
      Reader  from USA
Tom,

You have answered my question comprehensively.

The "Also, does Oracle determine that backup is being
taken..." part is:

I was referring to the
"Hot Backup end marker scn: 0x0000.00000000"
in symbolic control file dump
and
"Backup taken at scn: 0x0000.00000000 01/01/1988 00:00:00 thread:0"
in symbolic file_hdr dump

I was not sure these are still relevent with the advent
of RMAN

Thanks

      Rating: 5
wire diagram
        November 05, 2001
Reviewer:  A reader

< ( relational properties ) >
< physical properties >
< table_properties >

Tom, can you describe the above 3 kinds of properties.

Is the above order the  same for all the oracle objects. Or do some of the
objects have other kind of properties.

What do you mean by wire diagram.

By the way this is good info( especially for attacking OCP  questions).

          Followup:

By wire diagram I meant what you see at:

http://technet.oracle.com/doc/oracle8i_816/server.816/a76989/ch4g9.htm#9623
and that has the description of the three things you ask of.

      Rating: 4
How to avoid
      redo generation for index tables  January 14, 2003

Reviewer:  SK  from PA USA
SQL> create table t nologging as select * from all_objects where 1=0;

Table created.

SQL> set autotrace on statistics;
SQL>
SQL> insert /*+ append */ into t select * from all_objects;

7719 rows created.

Statistics
----------------------------------------------------------
        315  recursive calls
        251  db block gets
      28048  consistent gets
          0  physical reads
      18700  redo size
        775  bytes sent via SQL*Net to client
        701  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
       7719  rows processed

SQL> truncate table t;

Table truncated.

SQL> create index object_name_idx on t(object_name);

Index created.

SQL> insert /*+ append */ into t select * from all_objects;

7720 rows created.

Statistics
----------------------------------------------------------
        291  recursive calls
        720  db block gets
      28085  consistent gets
          2  physical reads
     892808  redo size
        776  bytes sent via SQL*Net to client
        701  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
       7720  rows processed

SQL> truncate table t;

Table truncated.

SQL> alter index object_name_idx nologging;

Index altered.

SQL> insert /*+ append */ into t select * from all_objects;

7720 rows created.

Statistics
----------------------------------------------------------
        506  recursive calls
        815  db block gets
      28141  consistent gets
          5  physical reads
     926960  redo size
        777  bytes sent via SQL*Net to client
        701  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          9  sorts (memory)
          0  sorts (disk)
       7720  rows processed

SQL> truncate table t;

Table truncated.
SQL> alter index object_name_idx disable;
alter index object_name_idx disable
*
ERROR at line 1:
ORA-02243: invalid ALTER INDEX or ALTER MATERIALIZED VIEW option

SQL> alter index object_name_idx unusable;

Index altered.

SQL> truncate table t;

Table truncated.

SQL> insert /*+ append */ into t select * from all_objects;

7720 rows created.

Statistics
----------------------------------------------------------
        435  recursive calls
        932  db block gets
      28196  consistent gets
          5  physical reads
    1097652  redo size
        779  bytes sent via SQL*Net to client
        701  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
       7720  rows processed

Q1: Why did alter index ... disable failed? Oracle 9.2 document still gives this
a valid command.

Q2: Why did the redo generated when index was in unusable state?

How to avoid redo regeration for a large table direct load insert. DB in
ARCHIVELOG mode and table and indexes are altered to nologging mode before large
inserts.

Thanks in advance.

          Followup:

q1)

ENABLE Clause ENABLE applies only to a function-based index that has been
disabled because a user-defined function used by the index was dropped or
replaced. This clause enables such an index if these conditions are true: n The
function is currently valid n The signature of the current function matches the
signature of the function when the index was created n The function is currently
marked as DETERMINISTIC Restriction on the ENABLE clause: You cannot specify any
other clauses of ALTER INDEX in the same statement with ENABLE. DISABLE Clause

DISABLE applies only to a function-based index. This clause enables you to
disable the use of a function-based index. You might want to do so, for example,
while working on the body of the function. Afterward you can either rebuild the
index or specify another ALTER INDEX statement with the ENABLE keyword.

q2) trick question - it is because the index was nolonger in the unusable state!
 the truncate "fixed it"

ops$tkyte@ORA920> create table t ( x int );

Table created.

ops$tkyte@ORA920> create index t_idx on t(x);

Index created.

ops$tkyte@ORA920> alter index t_idx unusable;

Index altered.

ops$tkyte@ORA920> select index_name, status from user_indexes where table_name =
'T';

INDEX_NAME                     STATUS
------------------------------ --------
T_IDX                          UNUSABLE

ops$tkyte@ORA920> truncate table t;

Table truncated.

ops$tkyte@ORA920> select index_name, status from user_indexes where table_name =
'T';

INDEX_NAME                     STATUS
------------------------------ --------
T_IDX                          VALID

alter the index AFTER the truncate and then alter session set
skip_unusable_indexes=true;

Indexes must be logged during inserts.  the nologging controls their behaviour
during THEIR bulk operations, like a rebuild or build.

so, truncate, unusable, load, rebuild  is what you are looking for

      Rating: 5
Can the
      samething be done with PK  January 14, 2003

Reviewer:  SK  from PA USA
Thanks for the useful information.

Can the redo generation be avoided for Primary keys while doing mass inserts.

i.e.

Disable PK, alter index unusable, insert /*+ append */, rebuild index nologging,
enable PK.

In the above I am presuming that a PK has been created using an existing index.

          Followup:

disabling the primary key that has a unique index backing it (doesn't have to be
unique) will drop the index - so, you might not have to do the unusable part --
but yes, it'll avoid the redo.

      Rating: 5
Corruption due
      to NOLOGGING or UNRECOVERABLE  July 18, 2003

Reviewer:  Igor  from San Francisco, USA
If a NOLOGGING (or UNRECOVERABLE) operation is performed on an
object and the datafile containing that object is subsequently
recovered then the data blocks affected by the NOLOGGING operation are marked as
corrupt and will signal an ORA-1578 error when accessed.

Why when I perform a NOLOGGING operation to build and index, I get a corruption
in the table datafile not in the index datafile itself?

          Followup:

you won't -- perhaps you did the table nologging as well.

I would need a step by step example, like I provide you guys to see what you
mean.

July 19, 2003 vijay -- Thanks for the question regarding "how
      to get around Dbms_output limitations", version

      You Asked
dbms_output.put_line has limitation of 255 characters
per line.  How can I get out of this limitation, so that I can
use dbms_output.put_line to have 480 character per line

      and we said...
Depending on whether the output must be on a single line (in
which case the answer is "dbms_output simply cannot do that") I
have 3 ideas that might be of use.

1) This is what I do.  I have my own procedure "p" that I call
(dbms_output.put_line is so long anyway).  P always does:

procedure p( p_string in varchar2 )
is
   l_string long default p_string;
begin
   loop
     exit when l_string is null;
     dbms_output.put_line( substr( l_string, 1, 250 ) );
     l_string := substr( l_string, 251 );
   end loop;
end;

it avoids the 255 byte issue but does not put the output on a
single line.

2) use utl_file and write directly to a file.  utl_file (added
in 7.3.3) has a 1022 byte limit/line (raised to 32k/line in
8.0.5)

3) use a plsql table type and a select -- write your own
dbms_output (that has no 1,000,000 byte limit and limits the
linesize to 2000 bytes/4000 bytes in Oracle7/8, not 255).  I
tested this in 7.3.4 and it works well.  it would look like
this:

tkyte@ORA734.WORLD> create or replace package my_dbms_output
  2  as
  3      procedure put( s in varchar2 );
  4      procedure put_line( s in varchar2 );
  5      procedure new_line;
  6
  6      function get_line( n in number ) return varchar2;
  7      pragma restrict_references( get_line, wnds, rnds );
  8
  8      function get_line_count return number;
  9      pragma restrict_references( get_line_count, wnds, rnds,
wnps );
 10
 10      pragma restrict_references( my_dbms_output, wnds, rnds,
wnps, rnps );
 11  end;
 12  /

Package created.

thats our interface, much like the existing dbms_output (but no
disable/enable -- this is always enabled).  We implement the
package body as such:

tkyte@ORA734.WORLD> create or replace package body
my_dbms_output
  2  as
  3
  3  type Array is table of varchar2(4000) index by
binary_integer;
  4  g_data        array;
  5  g_cnt        number default 1;
  6
  6      procedure put( s in varchar2 )
  7      is
  8      begin
  9          if ( g_data.last is not null ) then
 10              g_data(g_data.last) := g_data(g_data.last) ||
s;
 11          else
 12              g_data(1) := s;
 13          end if;
 14      end;
 15
 15      procedure put_line( s in varchar2 )
 16      is
 17      begin
 18          put( s );
 19          g_data(g_data.last+1) := null;
 20      end;
 21
 21      procedure new_line
 22      is
 23      begin
 24          put( null );
 25          g_data(g_data.last+1) := null;
 26      end;
 27
 27      function get_line( n in number ) return varchar2
 28      is
 29          l_str varchar2(4000) default g_data(n);
 30      begin
 31          g_data.delete(n);
 32          return l_str;
 33      end;
 34
 34      function get_line_count return number
 35      is
 36      begin
 37          return g_data.count+1;
 38      end;
 39
 39  end;
 40  /

Package body created.

The reason we use the pragmas on the get_line and get_line_count
is so we can "select * from PLSQL_TABLE" to print the data (we
don't want to use dbms_output to print for obvious reasons ;)

We can create a view on a table with lots of rows (all_objects
is typically good with 1,000 or more rows in most cases) as
such:

tkyte@ORA734.WORLD>
tkyte@ORA734.WORLD> create or replace view my_dbms_output_view
  2  as
  3  select rownum lineno, my_dbms_output.get_line( rownum )
text
  4    from all_objects
  5   where rownum < ( select my_dbms_output.get_line_count from
dual );

View created.

That view will

- find the number of lines to print from
my_dbms_output.get_line_count
- invoke the get_line procedure with rownum for each line of
output we generated

To test it and see how it works, we:

tkyte@ORA734.WORLD> begin
  2      my_dbms_output.put_line( 'Hello World' );
  3      my_dbms_output.put_line( '    hi there' );
  4      my_dbms_output.put( 'This is one line on many ' );
  5      my_dbms_output.put( ' calls to put,' );
  6      my_dbms_output.put( ' just to see it working' );
  7      my_dbms_output.new_line;
  8      my_dbms_output.put( 'end of data...' );
  9  end;
 10  /

PL/SQL procedure successfully completed.

tkyte@ORA734.WORLD> select * from my_dbms_output_view;

    LINENO TEXT
----------
--------------------------------------------------------------
         1 Hello World
         2     hi there
         3 This is one line on many  calls to put, just to see
it working
         4 end of data...

tkyte@ORA734.WORLD> select * from my_dbms_output_view;
no rows selected

So, after we run the block, we have to select * from the view
(or just select TEXT from the view to get the data we really
want to dump -- you would "set heading off, set pagesize 10000,
set feedback off, spool somefile, select text from view" to
capture the output).  The very next time we try to select * from
the view -- its empty (it empties itself as it fetches)

Another test -- to show that after getting empty it works again:

tkyte@ORA734.WORLD> begin
  2      for x in ( select rpad( ' ', rownum, ' ' ) || username
data from all_users where rownum < 11 )
  3      loop
  4          my_dbms_output.put_line( x.data );
  5      end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

tkyte@ORA734.WORLD>
tkyte@ORA734.WORLD> select * from my_dbms_output_view;

    LINENO TEXT
---------- --------------------------------------------------
         1  SYS
         2   SYSTEM
         3    DBSNMP
         4     SCOTT
         5      TKYTE
         6       USERA
         7        USERB
         8

8 rows selected.

tkyte@ORA734.WORLD>

(this little trick has other uses -- you can now select * from
plsql_table in other places knowing this.  Have you ever wanted
a variable length in list?  eg:  select * from T where x in (
"some string of variables, how many you don't know until run
time" ) )

Review &
      Followup

      Rating: 5
how to get
      around Dbms_output limitations  March 15, 2001

Reviewer:  Cauvery Kodira  from Columbia, MD USA

I have been trying in vain to get some good output on my client machine - the
results I get from spooling are too ugly. This pre-created package by Tom really
helps.
  Thanks

      Rating: 4
Great
      workaround  April 03, 2001
Reviewer:  Tony van
      der Prijt  from Netherlands
Hi there,

I've been fiddling with the same issue and this answer really saved me from the
hassle of finding it out myself (if i even would think of it...)

Thnx

      Rating:
      5
formatting  October 05, 2001
Reviewer:
      A reader
SQL>  select lineno from my_dbms_output_view;

    LINENO
----------
##########
##########
##########
##########
##########
##########
##########
##########
##########
##########
##########

SQL> desc my_dbms_output_view;
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 LINENO                                             NUMBER
 TEXT                                               VARCHAR2(4000)

format settings:

column lineno format10

a)How should I format the output from this view( when I format the above is the
output is what I get for lineno(as there is no column length declared.

b)how should I get unwrapped output from this view

          Followup:

column lineno format 999999999

lineno is a number, don't use a character string format on it.

set linesize 5000
set trimspool on
select * from my_dbms_utput_view order by lineno;

will do it.

      Rating: 5
whats going
      wrong  October 05, 2001
Reviewer:
      A reader
SQL> begin
  2          my_dbms_output.put_line( 'Hello World' );
  3          my_dbms_output.put_line( '    hi there' );
  4          my_dbms_output.put( 'This is one line on many ' );
  5          my_dbms_output.put( ' calls to put,' );
  6          my_dbms_output.put( ' just to see it working' );
  7          my_dbms_output.new_line;
  8          my_dbms_output.put( 'end of data...' );
  9  end;
 10  /

PL/SQL procedure successfully completed.

SQL> select * from my_dbms_output_view;

    LINENO
----------
TEXT
--------------------------------------------------------------------------------
         1
Hello World

         2
    hi there

         3
This is one line on many  calls to put, just to see it working

    LINENO
----------
TEXT
--------------------------------------------------------------------------------
         4
end of data...

SQL> /

no rows selected

SQL> /

no rows selected

What is the reason that we are able to see the data in the view only once, and
when wanting to see the next time the view is empty.

          Followup:

see line 31 of the packge body -- i delete it after getting it on the assumption
that once you've seen it, you've seen it.  If you don't like that, get rid of
line 31.

      Rating: 5
Correct the
      Error in the above package spec  October 06, 2001

Reviewer:  Nag
Tom
I find   pragma restrict_references( my_dbms_output, wnds, rnds, wnps, rnps );
in package specification.

But I dont find the relevant procedure or function in the package body.

Try compiling your package exactly the way you have it avertised.

thank you

          Followup:

No, you try compiling as I have it adverstised -- I already compiled it,
that is a cut and paste from sqlplus.  It compiles in all versions of Oracle I
have.

my_dbms_output is the NAME OF THE PACKAGE.  I am specifying the purity level of
the package itself.  It is valid, complete and correct.

      Rating: 5
Why do we have
      to define the purity level for the package  October 06, 2001

Reviewer:  Nag
Tom

We are used to specifying the purity levels for functions, which we call in
sql's in our package. But we have never declared purity level for the packages
itself.

What are the advantages of doing so, actually why should we do so, what is the
downside of not doing so.

I have heard that purity levels have been relaxed in 8i , but I see that you
still are specifying the purity levels, something is missing here.

Thank you

          Followup:

Look at the example, it was from 7.3.4 (see my prompt, I always have the version
in it).

I did it in 7.3.4 and 8.0 in order to help narrow down where pragma violations
where happening.  Consider:

ops$ora734@ORA734.WORLD> create or replace package my_pkg
  2  as
  3      function foo return number;
  4      pragma restrict_references( foo, wnds, rnds, wnps, rnps );
  5  end;
  6  /

Package created.

ops$ora734@ORA734.WORLD>
ops$ora734@ORA734.WORLD> create or replace package body my_pkg
  2  as
  3
  3  function foo return number
  4  is
  5  begin
  6      return 0;
  7  end;
  8
  8
  8  begin
  9      update emp set ename = ename;
 10  end;
 11  /

Warning: Package Body created with compilation errors.

ops$ora734@ORA734.WORLD> show err
Errors for PACKAGE BODY MY_PKG:

LINE/COL ERROR
-------- -----------------------------------------------------------------
0/0      PL/SQL: Compilation unit analysis terminated
3/1      PLS-00452: Subprogram 'FOO' violates its associated pragma

obviously FOO itself doesn't violate its pragma -- it doesn't do anything.  So
whats the problem?  Add the other pragma

ops$ora734@ORA734.WORLD> create or replace package my_pkg
  2  as
  3      pragma restrict_references( my_pkg, wnds, rnds, wnps, rnps );
  4
  4      function foo return number;
  5      pragma restrict_references( foo, wnds, rnds, wnps, rnps );
  6
  6  end;
  7  /

Package created.

ops$ora734@ORA734.WORLD>
ops$ora734@ORA734.WORLD> create or replace package body my_pkg
  2  as
  3
  3  function foo return number
  4  is
  5  begin
  6      return 0;
  7  end;
  8
  8
  8  begin
  9      update emp set ename = ename;
 10  end;
 11  /

Warning: Package Body created with compilation errors.

ops$ora734@ORA734.WORLD> show err
Errors for PACKAGE BODY MY_PKG:

LINE/COL ERROR
-------- -----------------------------------------------------------------
0/0      PL/SQL: Compilation unit analysis terminated
3/5      PLS-00452: Subprogram 'MY_PKG' violates its associated pragma

Ahh, now its a tad more clear where the error is.  I just did it as a matter of
habit to help me track down where the pragma was really being violated.

      Rating: 5
Drilling down
      into some basics  October 06, 2001
Reviewer:
      Nag
1.What is the actual meaning of Pragma ( in the given context)

2.What does pragma restrict references mean( we know that they are help ful in
avoiding functions from making changes in the database), but ,  can you put it
in your words.

3.A wee bit silly , but what is problem with functions changing the database.

4.O.k. in 7.3.4 we had pragma restrict references, where we avoided making
changes to the database using a functional call in sql, because that is not
desirable.

But now in 8i we have relaxed the purity levels, what does this mean, can we
make changes to the database using functional calls in SQL.

5.By purity levels do we mean the  wnds, rnds, wnps, rnps , or something else.

6.           3/5      PLS-00452: Subprogram 'MY_PKG' violates its associated
pragma

In the given example how is my_pkg violating its associated pragma. What is its
associated pragma.

My questions deal with a lot of basics, but believe me TOM, a deluge of
developers are living and are stuck with these doubts in there mind, but they
feel lazy or afraid to ask someone who has full knowledge like you.

Some clarification needed here.

          Followup:

1) a pragma is a compiler directive.  It alters the default behavior of the
compiler.

Normally the compiler really, honestly just doesn't care if we update the
database in our procedures.  It doesn't care.  However, if I ASSERT via the
pragma restrict_references that my code DOESN'T update the database (via WNDS)
and the compiler catches me lying to it (it sees that I do in fact have the
ability to update the database in my procedure), it'll fail the compilation.

Hence, when you assert the purity level, you're asking the compiler to verify
that for you.

Without the pragma in versions 7.x and 8.0, you would not be able to invoked
packaged procedures from SQL -- thats the "helpful" part of them.  They allow
packaged procedures to be called from sql.

2) see 1

3) In 8i we made is so you did not have to SPECIFY the purity level (we compute
it a compile time).

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function foo return number
  2  as
  3  begin
  4     update emp set ename = ename;
  5      return sql%rowcount;
  6  end;
  7  /

Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select foo from dual;
select foo from dual
       *
ERROR at line 1:
ORA-14551: cannot perform a DML operation inside a query
ORA-06512: at "OPS$TKYTE.FOO", line 4
ORA-06512: at line 1

you still cannot do that UNLESS the function is an autonomous transaction (and
even then -- you had better be really really smart about what you are doing).

The reason this is BAD is easily demonstrated:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t as select * from all_users
where rownum <= 10;

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table cnt ( x int );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into cnt values (0);
1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function foo return number
  2  as
  3          pragma autonomous_transaction;
  4          l_x number;
  5  begin
  6          update cnt set x = x+1 returning x into l_x;
  7          commit;
  8
  9          return l_x;
 10  end;
 11  /
Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where user_id = foo;

no rows selected

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

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from cnt;

         X
----------
        10

no surprises there -- 10 rows, cnt = 10, seems OK right.  Seems predicable
right.  No, not really:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx on t(user_id);
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where user_id = foo;

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T'
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE)

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from cnt;

         X
----------
        12

Now we did it 2 times for that query.  Why two?  I don't know, I don't really
care (truly not our business, they just called it two times to do the index
processing).  No, the result is not deterministic.  What if the optimizer
decides to change the plan again later?

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where user_id = foo;

USERNAME                          USER_ID CREATED
------------------------------ ---------- ---------
DBSNMP                                 16 28-AUG-01

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=16)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=1 Card=1 Bytes=16)

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from cnt;

         X
----------
        22

Which is is free to do.  This is why calling functions from sql that have side
effects is typically a really truly BAD IDEA.

5) yes. we mean that.

6)

Look at the spec:

ops$ora734@ORA734.WORLD> create or replace package my_pkg
  2  as
  3      pragma restrict_references( my_pkg, wnds, rnds, wnps, rnps );
  4

I asserted that this package WNDS -- it writes no database state.  Now, look at
the body:

ops$ora734@ORA734.WORLD> create or replace package body my_pkg
  2  as
  3
  3  function foo return number
  4  is
  5  begin
  6      return 0;
  7  end;
  8
  8
  8  begin
  9      update emp set ename = ename;
 10  end;

 11  /

the elaboration code for this package DOES write the database state obviously.
I lied, the compiler caught me.  A more subtle way for the package itself to
write the database state would be something like:

create or replace package my_pkg
as
     x number default foo;
      ......
end;

here foo is a function, foo updates the database, hence the package my_pkg
updates the data, hence EVERY function in my_pkg updates the database, hence no
function in my_pkg can be called from SQL.

      Rating: 5
how com purity
      levels have been relaxed in 8i  October 07, 2001

Reviewer:  A reader
It is interesting to note that , purity levels which were of such prime
importance in 7.3.4 , are not as important now.

What is the trade off, what are we trading the purity levels for.

How come something so important in previous versions , is not important now.

          Followup:

The old rules:

? The function cannot modify database tables; therefore, it cannot
execute an INSERT, UPDATE, or DELETE statement.

? Functions that read or write the values of packaged variables
cannot be executed remotely or in parallel.

? Only functions called from a SELECT, VALUES, or SET clause
can write the values of packaged variables.

? The function cannot call another subprogram that breaks one of
the foregoing rules. Also, the function cannot reference a view
that breaks one of the foregoing rules. (Oracle replaces references
to a view with a stored SELECT operation, which can include
function calls.)

The new rules:

o A function called from a query or DML statement may not end the current
transaction, create or rollback to a savepoint, or ALTER the system or session.

o A function called from a query (SELECT) statement or from a parallelized DML
statement may not execute a DML statement or otherwise modify the database.

o A function called from a DML statement may not read or modify the particular
table being modified by that DML statement.

As software evolves, features are added.  see the comment above this one for why
it can be a really truly bad idea most of the time.

The rules were mostly relaxed for functions called from DML
(insert/update/delete) since they behave much in the same way a trigger would.
It really didn't affect functions called from queries which is where 99% of the
times this functionality is used.

      Rating:
      5
Excellent  May 14, 2002
Reviewer:
      Srimal  from USA
Hello Mr Tom,

The solution that you have provided for getting around this problem is just
excellent!

Thanks a lot

      Rating: 3
avoid waiting
      for the code to complete  September 19, 2002

Reviewer:  Guy  from Israel
Tom,

When I execute a Pl/Sql code which includes DBMS_OUTPUT.PUT_LINE, the output is
flushed only after the run has eneded.

Is there a way to circumvent this, and view the messages during the run?

Thanks.

          Followup:

If you have my book -- i describe in painstaking detail how dbms_output works
(so you'll fully understand why with DBMS_OUTPUT the answer is "no, you
cannot").

You can use UTL_FILE and fopen in append/ put a line/ fclose and "tail -f" the
file.

You can use dbms_application_info.set_session_longops to report progress in real
time via a v$ view.

In 9i, you can use a pipelined function:

ps$tkyte@ORA920.LOCALHOST> create or replace type myScalarType as object
  2  ( dt timestamp, msg varchar(40) )
  3  /

Type created.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> create or replace type myTableType as table of
myScalarType
  2  /

Type created.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> create or replace function virtual_table( p_num_rows
in number )
  2  return myTableType
  3  PIPELINED    -- NOTE the pipelined keyword
  4  is
  5  begin
  6          pipe row( myScalarType( systimestamp, 'Starting...' ) );
  7      for i in 1 .. p_num_rows
  8      loop
  9                  pipe row( myScalarType( systimestamp, 'processing row ' ||
i ) );
 10                  dbms_lock.sleep( 1 );
 11      end loop;
 12
 13      return; -- note return with no value!
 14  end;
 15  /

Function created.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> set arraysize 1
ops$tkyte@ORA920.LOCALHOST> select * from TABLE( virtual_table(5) );

DT
---------------------------------------------------------------------------
MSG
----------------------------------------
18-SEP-02 06.55.52.238409 AM
Starting...

18-SEP-02 06.55.52.242138 AM
processing row 1

18-SEP-02 06.55.53.260283 AM
processing row 2

18-SEP-02 06.55.54.280308 AM
processing row 3

18-SEP-02 06.55.55.300272 AM
processing row 4

18-SEP-02 06.55.56.320289 AM
processing row 5

6 rows selected.

you cannot see it here -- but those rows appeared on my screen as they were
produced -- I saw the first row long before the last.

      Rating: 4
ask for
      improvement  May 12, 2003
Reviewer:  Peter
       from Hungary, Europe
Hi Tom,

your my_dbms_output is great! Especially because of its amazing simplicity.
I tried to improve it: to make it session-sensitive (thus the simplicity
gone...:(
Please, look through this code and write me how should it be improved (either
technically or in functionality). Yes, I'm sure, it can be several ways. But how
it should be?
(I removed the last ugly NULL-line in the case where the last writing is
put_line.)

* I needed two values (the session identifier and .g_data("session ID").LAST)
several times, that is why I created dop_assistant -- but it doesn't seem to be
the best solution. How could it be eliminated (keeping the code centralized)?

* I tried to centralize the type definitions (VARCHAR2(80) for dop_assistant.sid
and .last and fullsid and VARCHAR2(4000) etc.) to make the code more flexible
but I couldn't: if I put "SID dop.fullsid" or anything similar (e.g. some %type
...) into dop_assistant header I get an error message like: "PLS-00201:
identifier 'DOP_TYPES.FULLSID' must be declared". How can this be solved? (Among
others I created a dop_types package to hold those four types (txt_row,
txt_table, fullsid and session_table) but I couldn't use it in the way I want --
I left some trace about this in the code to inform you about what I wanted.)

I use Oracle 9iR2.

Here is the code. (As you will see I used several things learned from AskTom.
Thanks for all!)

CREATE OR REPLACE TYPE dop_assistant AS OBJECT (
    SID /*dop_types.fullsid, --*/VARCHAR2(80),
    LAST /*dop_types.fullsid, --*/VARCHAR2(80),

    constructor FUNCTION dop_assistant RETURN self AS result,

    PRAGMA RESTRICT_REFERENCES( dop_assistant, wnds, wnps )
);
/
CREATE OR REPLACE PACKAGE Dop AS

   SUBTYPE txt_row IS VARCHAR2(4000);
   TYPE txt_table IS TABLE OF txt_row INDEX BY BINARY_INTEGER;

   SUBTYPE fullsid IS VARCHAR2(80);
   TYPE session_table IS TABLE OF txt_table INDEX BY fullsid;

   g_data /*dop_types.*/session_table;

   PROCEDURE put( s IN VARCHAR2 );
   PROCEDURE p  ( s IN VARCHAR2 );

   PROCEDURE put_line( s IN VARCHAR2 );
   PROCEDURE pl ( s IN VARCHAR2 );

   PROCEDURE new_line;

   FUNCTION get_line( n IN NUMBER ) RETURN VARCHAR2;
   PRAGMA RESTRICT_REFERENCES( get_line, wnds );

   FUNCTION get_line_count RETURN NUMBER;
   PRAGMA RESTRICT_REFERENCES( get_line_count, wnds, wnps );

   PRAGMA RESTRICT_REFERENCES( Dop, wnds, wnps, rnps );

END;
/
CREATE OR REPLACE PACKAGE BODY Dop AS

    PROCEDURE put( s IN VARCHAR2 ) IS
        da dop_assistant := dop_assistant();
        l_ntt /*dop_types.*/txt_table; /* Null Txt_Table */
    BEGIN
        IF NOT g_data.EXISTS(da.SID) THEN g_data(da.SID) := l_ntt; END IF;
        IF da.LAST IS NULL THEN
            g_data(da.SID)(1) := s;
        ELSE
            g_data(da.SID)(da.LAST) :=
            g_data(da.SID)(da.LAST) || s;
        END IF;
    END;
    ----------
    PROCEDURE p( s IN VARCHAR2 ) IS BEGIN put( s ); END;
    ----------
    PROCEDURE put_line( s IN VARCHAR2 ) IS
        da dop_assistant;
    BEGIN
        put( s );
        da := dop_assistant();
        g_data(da.SID)(da.LAST+1) := NULL;
    END;
    ----------
    PROCEDURE pl( s IN VARCHAR2 ) IS BEGIN put_line( s ); END;
    ----------
    PROCEDURE new_line IS BEGIN put_line( NULL ); END;
    ----------
    FUNCTION get_line( n IN NUMBER ) RETURN VARCHAR2 IS
        da dop_assistant := dop_assistant();
        l_str /*dop_types.*/txt_row DEFAULT g_data(da.SID)(n);
    BEGIN
        IF (n = da.LAST - 1) AND g_data(da.SID)(da.LAST) IS NULL THEN
            g_data(da.SID).DELETE(n+1);
        END IF;

        g_data(da.SID).DELETE(n);

        RETURN l_str;
    END;
    ----------
    FUNCTION get_line_count RETURN NUMBER IS
        da dop_assistant := dop_assistant();
        cnt NUMBER DEFAULT g_data(da.SID).COUNT;
    BEGIN
        IF da.LAST IS NOT NULL AND g_data(da.SID)(da.LAST) IS NULL THEN RETURN
cnt;
        ELSE                                                            RETURN
cnt + 1; END IF;
    END;

END;
/
CREATE OR REPLACE TYPE BODY dop_assistant AS
    constructor FUNCTION dop_assistant RETURN self AS result AS
    BEGIN
        SELECT TO_CHAR(s.SID) || TO_CHAR(s.serial#)
          INTO self.SID FROM v$session s WHERE s.SID = ( SELECT m.SID FROM
v$mystat m WHERE ROWNUM = 1 );

        IF NOT Dop.g_data.EXISTS(self.SID) THEN self.LAST := NULL;
        ELSE                                    self.LAST :=
Dop.g_data(self.SID).LAST; END IF;

        RETURN;
    END;
END;
/
CREATE OR REPLACE VIEW dov AS
    SELECT ROWNUM lineno, Dop.get_line( ROWNUM ) text
      FROM ALL_OBJECTS WHERE ROWNUM < ( SELECT Dop.get_line_count FROM dual );

TIA:
Peter

          Followup:

as you discovered, it already was session capable.

      Rating:
      2
Sorry  May 12, 2003
Reviewer:  Peter

Huh, sorry for my foolishness -- after all I tried Tom's original code and I see
it is session-sensitive already. Otherwise my questions remains!

TIA:
Peter

      Rating: 4
compilation
      errors  July 18, 2003
Reviewer:  soy
       from usa
Warning: Package Body created with compilation errors.

I'm using 8i. SQL PLus 8.

When I paste the package body into SQL Plus it doesn't detect the end (the /) so
i have to add another one. Then I get the error. What am I doing wrong?

          Followup:

you have a space in front of the "/" then

"/" must be in position one

      Rating: 5
Handle chr(10)
      nicely  December 22, 2003
Reviewer:  Decibel
       from Austin, TX
I've tweaked the procedure Tom provided a bit to do a nicer job on embedded
linefeeds:

CREATE OR REPLACE PROCEDURE p( p_string in varchar2 )
IS
   l_string long default p_string;
   l_next int;
   l_add int;
BEGIN
        LOOP
                EXIT WHEN l_string is null;
                l_next := instr( l_string, chr(10) );
                IF l_next = 0 THEN
                        l_next := 250;
                        l_add := 1;
                ELSE
                        l_next := l_next - 1;
                        l_add := 2;
                END IF;
                dbms_output.put_line( substr( l_string, 1, l_next ) );
                l_string := substr( l_string, l_next + l_add );
        END LOOP;
END;

Hope someone finds it useful (and yes, I know long is depricated, but I didn't
feel like messing with it).

July 19, 2003 Arvind -- Thanks for the question regarding "Can
      we access PLSQL table as an OUT parameter from a Procedure Using JDBC.",
      version 8.1

      You Asked
I have a problem while accessing a procedure which is returning PLSQL table as
an OUT parameter. So can you tell me that whether I can access PLSQL table in
JDBC or not. If yes then How?
Thanks
Arvind Kapil.
Example:
procedure CCNTGPBD.GetProductBundleDtl(
 pi_contract_id     IN     NUMBER,
 po_bundle_details      OUT     po_bundle_details_tab);
CCNTGPBD IS A PACKAGE in which
po_bundle_details_tab is declared as
TYPE po_bundle_details_tab IS TABLE OF varchar2(100) INDEX BY
BINARY_INTEGER;

      and we said...
Using the 8.1.7 OCI jdbc drivers -- yes:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/java.817/a83724/indexby.htm#1000888
any other earlier driver - no.

All 8.x versions can use Nested Tables and Varrays.  You could do this:

SQL> create type javaArrayType is table of varchar2(4000);
  1  /

and then your java code would execute:

  declare
     tmp_array ccntgpbd.po_bundle_details_tab;
     o_array javaArrayType := javaARrayType();
  begin
     CCNTGPBD.GetProductBundleDtl( :bind_variable1, tmp_array );
     for i in 1 .. i_array.count loop
        o_array(i) := tmp_array(i);
     end loop;
     :bind_variable2 := o_array;
  end;

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:986230504001
for further code examples in java.

Follow up to comment

You are using the thin driver as evidenced by

"jdbc:oracle:thin:@od1:1530:dvl8i"

you need to use the OCI driver as specified above.  It it only supported with
the OCI driver.
Review & Followup

      Rating: 3
I am facing an
      exception while executing using your suggesion  May 25, 2001

Reviewer:  Arvind Kapil  from USA
Getting an error
java.sql.SQLException : Unsuppoerted feature

import java.io.*;
import java.util.*;
import java.sql.*;
import java.math.BigDecimal;
import oracle.jdbc.driver.*;
import oracle.sql.*;

public class cp{
public static Connection con;
public static Statement  stmt;
public static void main(String args[]) {
   try{
    String drv="oracle.jdbc.driver.OracleDriver";
    String url="jdbc:oracle:thin:@od1:1530:dvl8i";
    Class.forName(drv);
    con=DriverManager.getConnection(url,"akapil","akapil");
    stmt = con.createStatement();
    OracleCallableStatement stmt1 = (OracleCallableStatement)con.prepareCall
("begin procout(?); end;");
    int maxLen = 10;
    int elemSqlType = OracleTypes.NUMBER;
    int elemMaxLen = 0;
    stmt1.registerIndexTableOutParameter (1, maxLen, elemSqlType, elemMaxLen);
    procout.execute ();
    BigDecimal[] values =
      (BigDecimal[]) procout.getPlsqlIndexTable (1);
    for (int i=0; i<values.length; i++)
      System.out.println (values[i].intValue());
    procout.close();
  } catch ( Exception e){System.out.println("Error:"+e);}
}
}

      Rating:
      1
  July 18, 2003
Reviewer:
      A reader

          Followup:

how useful "mr reader"

July 19, 2003 Carlos -- Thanks for the question regarding
      "interMedia -- how to index OS files not stored in the database.", version
      version 8.1.6

      You Asked
I've read that you can "index, search and analyze text and
documents stored in Oracle8i, IN FILES, on the web, ..."

I saw code samples for docs stored in the db, but haven't seen
any for docs (Word documents) in the file system.  Do you have
some?

      and we said...
There are two ways to do this

o via BFILES
o using a file datastore

I'll demonstrate both here.  First for the BFILE example.  You
might code:

--
-- Firstly, create an Oracle8i directory object where
-- the files to be indexed are located.  The GRANT is
-- necessary so the interMedia Text process can access
-- the files.
--
CREATE OR REPLACE DIRECTORY temp_dir AS 'c:\TEMP';
GRANT READ ON DIRECTORY temp_dir TO CTXSYS;

--
-- Next, create the table which will be used to reference
-- these files (e.g., BFILES)
--
CREATE TABLE mydocs( id NUMBER PRIMARY KEY,
                     title VARCHAR2(255), thefile BFILE );

--
-- Now, insert some data into our table, referencing some Word
-- document files located in our temp directory.  Note that
-- these files exist in the TEMP directory where the database
-- server is located.
--
INSERT INTO mydocs( id, title, thefile ) VALUES( 1, 'Document1',
BFILENAME('TEMP_DIR', 'WordDoc1.doc' ));
INSERT INTO mydocs( id, title, thefile ) VALUES( 2, 'Document2',
BFILENAME('TEMP_DIR', 'WordDoc2.doc' ));
INSERT INTO mydocs( id, title, thefile ) VALUES( 3, 'Document3',
BFILENAME('TEMP_DIR', 'WordDoc3.doc' ));
COMMIT;

--
-- Now, create an interMedia Text index on the BFILE column
--
CREATE INDEX mydocs_text_index ON mydocs(thefile) INDEXTYPE IS
ctxsys.context;

--
-- And lastly, perform a query to test if the files were
-- properly filtered and indexed
--
SELECT id
  FROM mydocs
 WHERE contains( thefile, 'Hello' ) > 0;

As you can see -- using BFILES it is very straightforward.  A
simple create index.  Using a filesystem datastore is not that
much more complicated but does involve an extra step:

--
-- Firstly, create a preference which includes the directories
-- where our files are located (note, you can include multiple
-- paths in this preference if necessary)
--
BEGIN
CTX_DDL.CREATE_PREFERENCE( 'my_datastore_prefs',
'FILE_DATASTORE' );
CTX_DDL.SET_ATTRIBUTE( 'my_datastore_prefs', 'path', 'c:\TEMP'
);
END;
/

--
-- Next, create the table which will be used to reference
-- these files.  In this case, the 'thefile' column
-- will be used to contain the filename
--
CREATE TABLE mydocs( id NUMBER PRIMARY KEY, title VARCHAR2(255),
thefile VARCHAR2(255) );

--
-- Now, insert some data into our table, referencing some Word
-- document files located in our temp directory.  Note that
-- these files exist in the TEMP directory where the database
-- server is located.
--
INSERT INTO mydocs( id, title, thefile ) VALUES( 1, 'Document1',
'WordDoc1.doc' );
INSERT INTO mydocs( id, title, thefile ) VALUES( 2, 'Document2',
'WordDoc2.doc' );
INSERT INTO mydocs( id, title, thefile ) VALUES( 3, 'Document3',
'WordDoc3.doc' );
COMMIT;

--
-- Now, create an interMedia Text index, including our datastore

-- preferences created earlier.
--
CREATE INDEX mydocs_text_index ON mydocs(thefile) INDEXTYPE IS
ctxsys.context
    PARAMETERS('datastore my_datastore_prefs');

--
-- And lastly, perform a query to test if the files were
-- properly filtered and indexed
--
SELECT id
  FROM mydocs
 WHERE contains( thefile, 'Hello' ) > 0;

Review &
      Followup

      Rating: 3
returning no
      rows  April 16, 2002
Reviewer:  Charanjiv
       from Delhi, India
Hi Tom,

I followed your step without any errors, but the intermedia query does not
return any rows. Oracle Version 8.1.6, Word files were saved in Word6/95 format.
Plus do we use the same technique for all other file formats say .pdf

SQL> CREATE OR REPLACE DIRECTORY temp_dir AS 'd:\dump' ;

Directory created.

SQL> GRANT READ ON DIRECTORY temp_dir TO CTXSYS;

Grant succeeded.

SQL> CREATE TABLE mydocs( id NUMBER PRIMARY KEY,
  2                       title VARCHAR2(255), thefile BFILE );

Table created.

SQL>
SQL> INSERT INTO mydocs( id, title, thefile ) VALUES( 1, 'Document1',
  2  BFILENAME('TEMP_DIR', 't1.doc' ));

1 row created.

SQL> INSERT INTO mydocs( id, title, thefile ) VALUES( 2, 'Document2',
  2  BFILENAME('TEMP_DIR', 't2.doc' ));

1 row created.

SQL> INSERT INTO mydocs( id, title, thefile ) VALUES( 3, 'Document3',
  2  BFILENAME('TEMP_DIR', 't3.doc' ));

1 row created.

SQL> INSERT INTO mydocs( id, title, thefile ) VALUES( 4, 'Document3',
  2  BFILENAME('TEMP_DIR', 'tmp4.pdf' ));

1 row created.

SQL> COMMIT;

SQL> CREATE INDEX mydocs_text_index ON mydocs(thefile) INDEXTYPE IS
ctxsys.context;

Index created.

SQL>
SQL> SELECT id
  2    FROM mydocs
  3   WHERE contains( thefile, 'Hello' ) > 0;

no rows selected

All the word files contain

Hello World
Intermedia
Ask Tom
Error

What am i doing wrong? The intermedia option was checked during installing the
database

          Followup:

I don't have 816 anymore but check the errors tables for intermedia and see if
there are any recorded errors.

      Rating: 3
Works with
      8.1.7  April 17, 2002
Reviewer:  Charanjiv
       from Delhi, India
Hi
Upgraded to 8.1.7, the same set of sqls now is working fine. FYI - the error
table did not record any error in 8.1.6.

Thanks
Charanjiv

      Rating: 5
indexing
      bfile  July 17, 2003
Reviewer:  Sean
       from NJ, USA
Hi Tom,

I added index to bfile, but I got no return when I queried it using contains
function.  But the data is there.

I used similar way to index blob.  It works fine.  I use Oracle 902.

Thanks so much for your help.

Sean

SQL> create table bfiledemo
  2  ( id        int primary key,
  3    title     varchar2(100),
  4    bfilecol    bfile
  5  );

Table created.

SQL> create or replace directory
  2          my_files as 'c:\temp\blobtext\';

Directory created.

SQL> insert into bfiledemo(id, title, bfilecol)
  2      values ( 1, 'Hello',
  3    bfilename( 'MY_FILES', 'blob.txt' ));

1 row created.

SQL> commit;

Commit complete.

SQL> create index bfile_Index on bfiledemo(bfilecol)
  2    indextype is ctxsys.context;

Index created.

SQL> create or replace
  2     function blobtochar( p_blob in bfile ) return varchar2
  3      as
  4          l_raw raw(4000);
  5          l_bfile bfile default p_blob;
  6     begin
  7          dbms_lob.fileopen( l_bfile );
  8          l_raw := dbms_lob.substr( l_bfile,4000,1 );
  9          dbms_lob.fileclose( l_bfile );
 10         return utl_raw.cast_to_varchar2(l_raw);
 11     end;
 12  /

Function created.

SQL> select id, title, blobtochar(bfilecol) x from bfiledemo;

        ID TITLE      X
---------- ---------- ----------------------------------------
         1 Hello      Here is the question of indexing bfile

SQL> select id, title from bfiledemo
  2   where contains(bfilecol, 'indexing')>0;

no rows selected

SQL>

          Followup:

select * from CTX_USER_INDEX_ERRORS
/

whats does that say

      Rating: 5
index
      bfile  July 17, 2003
Reviewer:  Sean
       from NJ, USA
There is error.  But the path is right.  Thanks so much.

Sean

SQL> drop index bfile_index;

Index dropped.

SQL> select * from CTX_USER_INDEX_ERRORS;

no rows selected

SQL> create index bfile_Index on bfiledemo(bfilecol)
  2        indextype is ctxsys.context;

Index created.

SQL> select * from CTX_USER_INDEX_ERRORS;

ERR_INDEX_NAME                 ERR_TIMES ERR_TEXTKEY
------------------------------ --------- ------------------
ERR_TEXT
--------------------------------------------------------------------------------
BFILE_INDEX                    17-JUL-03 AAAHesAABAAANfKAAA
DRG-50857: oracle error in drstldef
ORA-22285: non-existent directory or file for FILEEXISTS operation

SQL> select id, title, blobtochar(bfilecol) x from bfiledemo;

        ID TITLE      X
---------- ---------- ----------------------------------------
         1 Hello      Here is the question of indexing bfile

SQL>

          Followup:

grant read on directory my_files to ctxsys;

      Rating: 5
index
      bfile  July 17, 2003
Reviewer:  Sean
       from NJ, USA
Hi Tom,

I used the second method you mentioned(file datastore).  It works.

Thanks,

Sean

      Rating: 5
index
      bfile  July 17, 2003
Reviewer:  Sean
       from NJ, USA
Hi Tom,

It works after:
SQL>grant read on directory my_files to ctxsys;

Thanks so much for your help.

Sean

      Rating: 5
dbms_job to
      sync index  July 18, 2003
Reviewer:  Sean
       from NJ, USA
I tried to run dbms_job to sync Oracle text index, but it failed.

Thanks so much for your help.

Sean

SQL> exec ctx_ddl.sync_index('blob_index');

PL/SQL procedure successfully completed.

SQL> variable l_job number;
SQL> exec dbms_job.submit( :l_job, 'ctx_ddl.sync_index('blob_index');',
sysdate+5/1440, 'sysdate+1/1440' );
BEGIN dbms_job.submit( :l_job, 'ctx_ddl.sync_index('blob_index');',
sysdate+5/1440, 'sysdate+1/1440'

                                                    *
ERROR at line 1:
ORA-06550: line 1, column 53:
PLS-00103: Encountered the symbol "BLOB_INDEX" when expecting one of the
following:
. ( ) , * @ % & | = - + < / > at in is mod not range rem =>
.. <an exponent (**)> <> or != or ~= >= <= <> and or like
between ||
The symbol ". was inserted before "BLOB_INDEX" to continue.

          Followup:

to get a quote in a string, you have to use quote quote

SQL> exec dbms_job.submit( :l_job, 'ctx_ddl.sync_index(''blob_index'');',
sysdate+5/1440, 'sysdate+1/1440' );

July 19, 2003 ram
      -- Thanks for the question regarding "Need some help about Dynamic SQL",
      version 8.1.7

      You Asked
Hi tom,
This is my first time.
i Have package.
below is head.
create or replace pakackage my_masterProc AS
type my_rec is RECORD
(
telephoneNUM char(10);
);

type my1_refcur is REF CURSOR return my_rec;

PROCEDURE my1_masterPROC
(
CustNum_i IN NUMBER,
DB_link_i IN CHAR,
outmycur IN OUT my1_refcur
);
END my_masterPROC;

BODY.
-----
create or replace PACKAGE BODY my_masterPROC AS
PROCEDURE my1_masterPROC(CustNum_i IN NUMBER, DB_Link_i IN CHAR, outmycur IN OUT
my1_refcur)
IS
sql_stmst varchar2(1000);
outmycur2 my1_refcur;
my2_rec  my1_rec;
BEGIN

        sql_stmst := 'Select telephoneNum from custtelephone@'||DB_LinK_i||'
where custNum = :j
                 UNION select telephoneNum from custtelephone where custNum =
:j';
                OPEN outmycur2 FOR sql_stmst USING CustNum_i, CustNum_i;
                LOOP
                 FETCH outmycur2 INTO my2_rec ;
                -dbms_output.put_line(my2_rec );
                EXIT WHEN outmycur2%NOTFOUND;
               END LOOP;
                CLOSE outmycur2;

END;

END my_masterPROC;
when i try to compile iam getting this error

LINE/COL ERROR
-------- -----------------------------------------------------------------
14/3     PL/SQL: Statement ignored
14/8     PLS-00455: cursor 'OUTMTCUR2' cannot be used in dynamic SQL OPEN
statement

when i changed it from
type my1_refcur is REF CURSOR return my_rec;
to
type my1_refcur is REF CURSOR;
i was able to compile.
but when i execute iam not getting any data back.
what is difference between a strongly typed cursor and a weak cursor.
i have to return the recordset.
is any any other way i could do this, appreciate your time and help.
can you please show me a example how to write my procedure with a weak cursor.

Thanks
ram

      and we said...
A strongly typed ref cursor cannot be dynamically opened.  Since the compiler
cannot verify that it returns the proper set of outputs, it won't permit it.

So, you must use a weaking typed ref cursor as you have done.

You are not getting any result in the client because you actually fetch and then
close the cursor!  get rid of the loop and the close.

See

http://asktom.oracle.com/~tkyte/ResultSets/index.html
for examples.
Review & Followup

      Rating: 4
A couple of
      doubts..  November 13, 2002
Reviewer:
      Abhijit  from Mumbai
Hi Tom,
I have the following stored procedure which I use for returning a resultset
and getting the count.

CREATE OR REPLACE PROCEDURE proc_getEmp
(
iRecCount     OUT NUMBER,
RC1         IN OUT Omwb_emulation.globalPkg.RCT1)
IS
Begin
    declare
        l_emp varchar2(14);
    begin
    iRecCount:=0;
         Open RC1 FOR Select Ename From EMP;
         loop
         fetch RC1 INTO l_emp;
         iRecCount := iRecCount + 1;
         exit when RC1%NOTFOUND;
         end loop;
         dbms_output.put_line('End of Count ' || TO_CHAR(iRecCount));
    end;
End;
/

I have the following questions.
1) What is Omwb_emulation.GlobalPKg? I started using this cos the original
developer had used this. Can I use something else to return the refcursor?
2) Is there a short-cut to return the recordcount Or do I have to loop through
the resultset?

Cheers!
Abhijit

          Followup:

oh yuck. total misunderstanding going on here, total.

If your procedure is reall that -- then you are NOT returning a result set, you
are returning an EMPTY cursor that the client CANNOT select from at all.

You are returning the count of records that query would have returned, if you
hadn't fetched and discarded the rows.

There is no realistic way (nor NEED in IMHO) to get the count of the records a
query will return without actually fetching them.

Your procedure should just be:

...
begin
   open rc1 for select ename from emp;
end;

period -- the client can count the rows as they fetch them.

You have a package you've created called globalPkg owned by a user
omwb_emulation that has a ref cursor type defined.  Of course you can define
your own or in Oracle9i just use the SYS_REFCURSOR builtin type.  See the above
link I have for more examples of ref cursors including how to define this type.

      Rating: 4
Thanks
      TOM!  November 14, 2002
Reviewer:  Abhijit
       from Mumbai
Hi Tom,
Thanks for clearing that up. [YOU SIMPLY ARE THE BEST]
In my actual stored procedure which I ported from SQL Server 7.0,
I do not have to return the rowcount, nor do I have to return the cursor.
There is a check after the opening of the cursor if there are any records
returned by it.
The only check that is made is
"if @@rowcount = 0 then Print 'T' Else Print 'F'"

So is there any short-cut of knowing if the cursor has been fetched or not?

Cheers!
Abhijit

          Followup:

Ok, all you want to do is:

procedure p ...
is
   l_tf  varchar2(1);
begin
   select decode( count(*), 1, 'T', 'F' ) into l_tf
     from dual
    where EXISTS ( Your_Query_Goes_Here );

   dbms_output.put_line( l_tf );
end;

      Rating: 5
Thats the best
      solution..  December 02, 2002
Reviewer:
      Abhijit  from Mumbai

      Rating: 3
Oracle type IV
      drivers and Coldfusion.  July 15, 2003
Reviewer:
      vivek Saxena  from UK
Hi Tom,
I need your advise for follwing problem.
Our working environment is : We are developing an 'end user interface' using
Coldfusion MX 'enterprise edition' and oracle database 8i(8.1.7). We are
connecting to database via oracle thin driver type IV from coldfusion.
Problem :We are trying to get data using procedure within package. If we use
strongly type ref cursor with static SQL its work fine. but our requirement has
arisen for dyanmic SQL and if we use dynamic SQL we have to change strongly type
ref cursor to weakly type ref cursor ( am i correct here?). Package and
procedure does compile properly, but when try to get result in coldfusion i am
getting follwing error : Error Executing Database Query.
[Macromedia][Oracle JDBC Driver]Unsupported data conversion.
Is there any workaround to resolve this problem.
Thanks for our time and effort.
Vivek

          Followup:

you need to use a weakly typed ref cursor for dynamic sql, yes.

ops$tkyte@ORA920LAP> create or replace package demo_pkg
  2  as
  3          type rc is ref cursor return dual%rowtype;
  4
  5          procedure p( x in out rc );
  6  end;
  7  /

Package created.

ops$tkyte@ORA920LAP> create or replace package body demo_pkg
  2  as
  3          procedure p( x in out rc )
  4          is
  5          begin
  6                  open x for 'select * from dual';
  7          end;
  8  end;
  9  /

Warning: Package Body created with compilation errors.

ops$tkyte@ORA920LAP> show error
Errors for PACKAGE BODY DEMO_PKG:

LINE/COL ERROR
-------- -----------------------------------------------------------------
6/3      PL/SQL: Statement ignored
6/8      PLS-00455: cursor 'X' cannot be used in dynamic SQL OPEN
         statement

You'll have to ask coldfusion why their software doesn't work properly I
suppose?  At the jdbc level, there is not any difference between the two.

      Rating: 4
Following
      up  July 15, 2003
Reviewer:  Vivek
       from UK
Thanks Tom for PROMPT reply.
 I think you are right , we should go back to Coldfusion for this. But if you
can suggest way ahead for us as we got stuck at this critical point in project.
Here is my test procedure

CREATE OR REPLACE PACKAGE pkg_select_wr
AS
 TYPE recwr IS RECORD (
    vwrid wr.wr_id%TYPE,
    vblid wr.bl_id%TYPE,
        vnum  int);
 TYPE curwr IS REF CURSOR RETURN recwr;
 PROCEDURE sp_select_wr (arg_wr IN varChar2, arg_bl IN varChar2,arg_sortorder IN
varchar2, arg_uBound IN number, arg_startRow IN number, rswr OUT curwr);
 END;

CREATE OR REPLACE PACKAGE BODY pkg_select_wr
AS
PROCEDURE sp_select_wr (arg_wr IN varChar2, arg_bl IN varChar2, arg_sortorder IN
varchar2, arg_uBound IN number, arg_startRow IN number, rswr OUT curwr)
AS
BEGIN
OPEN rswr FOR
select * from ( select a.*,rownum rnum from
(SELECT wr_id colA,bl_id colB FROM wr WHERE bl_id= arg_bl
ORDER BY arg_sortorder) a
where rownum <= arg_uBound ) where rnum >= arg_startRow;
END;
end;
THIS CODE WORKS FINE. NOW IF CHANGE STRONG REF TO WEAK REF BY DOING
TYPE curwr IS REF CURSOR AND CHANGE STATIC QUERY TO DYNAMIC LIKE
v_sqlsmt := 'select * from ( select a.*,rownum rnum from
          (SELECT wr_id colA,bl_id colB FROM wr WHERE 1=1';
if arg_bl is not null then
 v_sqlsmt := v_sqlsmt || ' and bl_id = '''|| arg_bl ||'''';
end if;
   v_sqlsmt := v_sqlsmt || ' order by ' ||arg_sortorder|| ') a where rownum <='
|| arg_ubound || ') where rnum >= ' || arg_startrow;

OPEN rswr FOR v_sqlsmt;
This does compile properly,but not useful.

Now could you suggest a workaround so that the cursor output is putted back in
record type, as record ,declared in package header and we can pass back REF
cursor to calling environment with those values.

Once again thanks for your work.

Vivek

          Followup:

BIND VARIABLE ALERT -- BIND VARIABLE ALERT

Please -- I beg of you -- NEVER NEVER do what you've done!  never.  use bind
variables -- do not hard code literals like that, you'll KILL your system.

(that was close)

in any case, you should just be coding:

  if ( arg_bl is not null )
  then
     open rswr for
      select *
        from ( select a.*,rownum rnum
                 from (SELECT wr_id colA,bl_id colB
                         FROM wr
                        WHERE bl_id= arg_bl
                        ORDER BY arg_sortorder) a <<<== that looks like a
bug!
                where rownum <= arg_uBound )
       where rnum >= arg_startRow;
  else
     open rswr for
      select *
        from ( select a.*,rownum rnum
                 from (SELECT wr_id colA,bl_id colB
                         FROM wr
                        ORDER BY arg_sortorder) a <<<== that looks like a
bug!
                where rownum <= arg_uBound )
       where rnum >= arg_startRow;
  end if;

Now, as for my "this looks like a bug" -- are you thinking that if you pass in
"2" for arg_sortorder -- it'll sort by the second column?  if so -- not a
chance.  it'll be like:

select * from emp order by 'ENAME';

it will order by the CONSTANT number 2!

you can

order by decode( arg_sortorder,
                 1, column1_that_is_varchar2,
                 2, to_char(column2_that_is_date,'yyyymmddhh24miss'),
                 3, to_char(column3_that_is_number_btwn_0_and_9999999,
                            '0000000' ),
                 ..... )

that is -- use decode to return a character string for each column that "sorts"

bear in mind -- this is "as bad as it gets".  the first query stands a chance of
performing -- it can get all rows where bl_id (not a very intuitive name there)
= value and then sort.  that'll be OK

the second one -- get ALL rows -- sort -- will always be pretty darn slow
regardless if WR (another "nice" name) is of any size.
and use a strongly typed one.

      Rating: 4
Ref cursor and
      dynamic SQL  July 15, 2003
Reviewer:  Vivek
       from UK
Thanks for 'ALERT' , I will keep that in head for future.

Basically i was thinking to build query , if more variable are passed to
procedure (say 5 or 6), based on the parameter value i.e. if parameter is not
null then add it to query otherwise ignore that.
eg. if six parameters are passed to procedure and only two are not null then i
would like to have my query as

 WHERE fieldname1= para1 and fieldname2= para2

 You have shown the way to move forward and i will try to build some logic
around that OR have you got any idea (if i have more variables passed to
procedure) that would make task bit easier for me.

For sortorder, it passes the parameter as column alias i.e. ColA or ColB,
depends what column user has selected from web page. Names of field, i know is
not very self explantary, comes from out of box application. FYI bl_id is for
building code,WR is for Work request and some more from application fl_id for
floor code,rm_id for room code.

Thanks again and i would say 'EVERY TIME(EVERY DAY) I VISIT THIS SITE AND I
SURELY LEARN SOMETHING NEW FROM HERE'.

Vivek

          Followup:

the column alias ISN'T DOING ANYTHING -- you are sorting by a constant!!

      Rating:
      4
  July 18, 2003
Reviewer:
      A reader
Tom,
" Now, as for my "this looks like a bug" -- are you thinking that if you pass in

"2" for arg_sortorder -- it'll sort by the second column?  if so -- not a
chance.  it'll be like:

select * from emp order by 'ENAME';

it will order by the CONSTANT number 2!  "

You are right we were sorting that by constant. Could you please explain how a
value say ENAME gets as 'ENAME'. Is it because of variable type, arg_sortorder
varchar2,we have declared.

          Followup:

you cannot pass in to a query the column to sort by.  that would change the plan

you can sort by DECODE( sortorder, 1, c1, 2, c2, 3, c3, ... )

July 19, 2003 Roland -- Thanks for the question regarding
      "Intermedia text and indexing", version 8.1.5

      You Asked
Hello,

When you use CREATE INDEX without explicitly specifying
parameters, the system assumes that the text to be indexed is
stored directly in a text column. The text column can be of type
CLOB, BLOB, BFILE, VARCHAR2, or CHAR. The columns types LONG and
LONG RAW are supported for migrating Oracle7 systems to
Oracle8i. The column types NCLOB, DATE and NUMBER cannot be
indexed

I am storing my data in a BLOB format in a table:

SQL> descr attachment
 Name                          Null?    Type
 ----------------------------- -------- --------------
 ID                            NOT NULL NUMBER(38)
 NAME                          NOT NULL VARCHAR2(100)
 DATA                          NOT NULL BLOB
 DATE_CREATED                  NOT NULL DATE
 DATE_MODIFIED                 NOT NULL DATE
 MODIFIED_BY                   NOT NULL VARCHAR2(30)
 CREATED_BY                    NOT NULL VARCHAR2(30)

In order to search content within the BLOB I create an index:

CREATE index myindex on attachment(DATA) indextype is
ctxsys.context;

I have a sql  file uploaded in the BLOB which is the sreate
script for the table.

When I search for a word in the BLOB I always retrieve 0 rows,
even though the word is in the file.

SQL> SELECT ID from attachment
  2  where contains(
  3  DATA,'create')
  4  >0;

no rows selected

How must I index a BLOB?

      and we said...
By default, if you do not explicity start the ctxsrv process,
indexes are updated only when you tell them to be.  Intermedia
text queues updates to the index and performs them in batch on
demand (using the alter index <iname> rebuild parameters
('sync') command).

If you run the ctxsrv process alongside the server, these
batched requests are processed in much closer to real time.
They are processed after you commit and may take a second or two
to occur but the data is almost immediately indexed.

Here is an example I did against a server that was not running
the ctxsrv process.  It shows that the data "appears" to not be
there until we rebuild and then demonstrates the behaviour you
can expect if the ctxsrv process is running.  It behaves the
same regardless of the datatype of the text column -- since
blobs are hard to work with when using just sqlplus and static
sql, I am using a CLOB but the same would apply for a BLOB.

ops$tkyte@dev8i> create table ctx_demo
  2  ( id number primary key,
  3    text clob
  4  )
  5  /
Table created.

ops$tkyte@dev8i> create index search_idx
  2  on ctx_demo(text)
  3  indextype is ctxsys.context
  4  /
Index created.

ops$tkyte@dev8i> insert into ctx_demo ( id, text )
  2  values ( 1, 'How Now Brown Cow' );
1 row created.

ops$tkyte@dev8i> commit;
Commit complete.

ops$tkyte@dev8i> select *
  2    from ctx_demo
  3   where contains( text, 'Now' ) > 0
  4  /

no rows selected

That shows the newly inserted data is not indexed.  In fact it
will not be indexed until we either sync the index or start the
ctxsrv process.  The following demonstrates how to sync the
index manually.

ops$tkyte@dev8i> alter index search_idx
  2  rebuild parameters( 'sync' )
  3  /
Index altered.

ops$tkyte@dev8i> select *
  2    from ctx_demo
  3   where contains( text, 'Now' ) > 0
  4  /

        ID TEXT
---------- ----------------------------------------
         1 How Now Brown Cow

Now we can query the data.  Many people schedule their indexes
to sync in the background using DBMS_JOBS.  In this fashion,
they batch the updates to the table for some period (say 1 hour)
and sync the index hourly.  Others will run the ctxsrv process
as below (for example, I run the ctxsrv process on
osi.oracle.com so that articles in this forum are indexed as
soon as I publish them) for more immediate indexing.

ops$tkyte@dev8i> host ctxsrv -user ctxsys/ctxsys>&/tmp/ctx.log&

ops$tkyte@dev8i> insert into ctx_demo (id, text)
  2  values (2, 'The fork ran away with the spoon');
1 row created.

ops$tkyte@dev8i> commit;
Commit complete.

ops$tkyte@dev8i> exec dbms_lock.sleep(10)
PL/SQL procedure successfully completed.

I must wait a bit to give interMedia text the time to get the
request, retrieve the document, parse it and update the index.
This time varies but will only be a few seconds at most for such
small documents.

ops$tkyte@dev8i> select *
  2    from ctx_demo
  3   where contains( text, 'spoon' ) > 0
  4  /

        ID TEXT
---------- ----------------------------------------
         2 The fork ran away with the spoon

So now I can query up the newly inserted document without
doing a "sync"

It should be noted that if you run the ctxsrv process and index
individual documents, the indexes will be generally "worst case"
indexes.  interMedia text uses the concept of an inverted list
index -- a word points to many documents.  If we index
individual documents, our index will become very sparse over
time as each word will point to a single document and the words
will occur many times in the index.  We will have to schedule an
online "optimization" of these indexes periodically to reduce
their space and level of fragmentation.
Review &
      Followup

      Rating: 4
How to
      defrag  April 30, 2002
Reviewer:  Rukshan
      Soza  from England
Tom,

I read your response with great interest. We are running ctxsrv background
process. But, according to your article with this method, it is likely that
fragmentation will occur. How can I periodically, defrag/optimise these indexes
?

Thanks in advance

Rukshan

          Followup:

You can rebuild them if you find it necessary.  (see the intermedia guide, the
process is well documented)

Make sure to gather METRICS before and after doing this -- so you can see if the
rebuild did anything positive or negative with regards to performance and size.
Only rebuild in the future if you see POSITIVE payback.

      Rating: 4
ctxsrv
      deprecated  April 30, 2002
Reviewer:  Wynette
      Richards  from Albuquerque, NM USA
Tom, When working with Oracle on a TAR about a year ago we were told that ctxsrv
has been deprecated and that we should not use it.  They said we should not call
"alter index ... rebuild ... ('sync')" but, instead, that we should only do a
sync by calling ctx_ddl.sync_index('index_name'). This is discussed in
http://technet.oracle.com/products/text/x/Tech_Overviews/imt_816.html
I was surprised to see you suggest using ctxsrv. Has anything changed in the
past year?

Thanks for your outstanding web site.  Thanks for the explanation re. need to
run optimize.

          Followup:

The quote is:

<quote>
PL/SQL Sync and Optimize

PL/SQL functions for sync and optimize have been added. The preferred method for
sync and optimize is alter index rebuild online. Ideally, these could be called
in a dbms_job. However, separate sync and optimize jobs can conflict because two
alter index operations cannot be run at the same time for a single index.

The new PL/SQL functions resolve both problems by proving an alternate PL/SQL
API for sync and optimize:

ctx_ddl.sync_index(<idx_name>)

idx_name is the name of the index to sync. The effective user must be the owner
of the index or ctxsys. If effective user is ctxsys, idx_name can be NULL, which
will cause this call to examine all context indexes for pending DML. This uses
the same code as ctxsrv. We now recommend processing pending DML through a
dbms_job -- an individual job for each index, a ctxsys job for all indexes, or
some combination of the two. This eliminates the need for ctxsrv, and thus
eliminates the problem of starting ctxsrv each time the database starts. The
ctxsrv program itself is now deprecated, and may be removed in some future
release.
</quote>

It is still there in 9i.

You know, I read and re-read and read again my answer.  No where do I "suggest
running ctxsrv as being best practice"

I do suggest that it can lead to fragmented indexes.
That many people use dbms_job.
That you can use ctxsrv (and it is there, it is supported -- even in 9i)....

Additionally -- this question was against version 815 -- there was no
ctx_ddl.sync_index call in that release (always look at the version in the
subject for version info - i don't update questions just because of version
changes!)

      Rating:
      4
vs  December 16, 2002
Reviewer:
      Scott  from Australia
G'day

This information is fantastic, thankyou.

In the current release, and/or 817, would you suggest using ctxsrv or sync index
with the occasional optimise?

The reason I ask is because one wonders what the optimal period is for the
DBMS_JOBs.
How labour intensive is a synchronisation of all ctx indexes on a production
database (using ctx_ddl.sync_index(NULL))?
Or does ctxsrv use similar decision making?

          Followup:

On asktom -- I use ctxsrv so the index is "up to date" immediately after I
publish a question or a review is added by you or whatever...

I perdiodically rebuild the index when I notice the "approximately so many hits"
gets way off on the search page (dead entries from modified documents in the
index).

You would have to benchmark using YOUR scenario (your "production" system might
be less loaded them my "development" system.  "production" infers nothing as far
as load -- activity -- etc.  production only infers you have tested and
benchmarked the code that is running in order to know that it'll work ok).

      Rating: 5
sync and
      optimise  February 25, 2003
Reviewer:  Scott
       from Perth, WA
G'day Tom

I'd like to clarify my understanding of the difference between
ctx_ddl.sync_index and ctx_ddl.optimize_index.

Sync_index could be used in lieu of ctxsrv and would typically be executed
during set intervals using DBMS_JOB. This ensures pending DML is available when
querying the index.
Optimize_index is used periodically to reduce fragmentation of the index,
regardless of how you synchronize. This would be executed less often than
sync_index.
Are the statements accurate?

Does the optimize_index procedure issue the alter index statement as "online",
to allow a non-blocking operation?

And our DBAs and I couldn't figure out what you meant by
"Make sure to gather METRICS before and after doing this -- so you can see if
the rebuild did anything positive or negative with regards to performance and
size. Only rebuild in the future if you see POSITIVE payback. "
ie - what METRICS?
And how you would determine positive payback?

Thankyou to your other answers in regards to Oracle Text, they've helped a lot.

Thanks.

          Followup:

yes, one adds newly added documents to the index (sync)
the other in effect rebuilds it

You can use alter index rebuild online to get "non blocking", it'll allow
concurrent access to the index during the rebuild.

What I mean by metrics (it is sad the dba's do not know how to do this ;(  )
would be to collect metrics about the queries that use this index - say all of
the TEXT queries.  Save the average LIO's per query.  Now rebuild.  After a
while, come back and make sure that they are "better"

Text indexes are more like Bitmaps -- not like B*Tree's.  they do require a bit
more care and feeding then B*Trees.

      Rating: 3
Response from
      our DBA  March 26, 2003
Reviewer:  "Gavin"
       from Perth, WA
G'day Tom.

This is what our DBA has to say in response to your comments...

<quote>
This Tom guy... how many are there, I'm assuming Tom1, Tom2 and at least Tom3...

His whole purpose in life is to get questions, research, and respond... He's not
out in the field, trying to keep up with the flood of new technology that keeps
coming out.

We don't have time to research new technology, especially when it's not being
used in production. Obviously as intermedia/text/context (whatever the hell
they're calling it this week) goes into production, we will learn to administer
and tune it. For this Tom guy(s) to give this comment shows the lack of
real-world experience he/they have...

My 2 cents.

Gavin
</quote>

          Followup:

Your DBA needs a vacation....

I don't spend too much time researching actually.  Sometimes -- let me show you
an example:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:8760267539329
that one I had to research -- you can see in the comments:

...
first of all I have to apologize for being so impatient ( when I saw some
answers on newer questions). ....

You see -- I answered the 10 "no brainers" and went on a camping trip.  Saved
the one to research for later.

Your DBA is the "wild west, I'm a hero DBA" -- obviously.  There are many of
those types.  Just whip it out there -- don't think about what'll happen.  We'll
fix things as they happen.  We don't need to think about planning for anything.
We are too smart for that.

I usually get called into work with your DBA to fix things.  Going to a customer
this morning in fact to talk about "what the heck happened to our systems, why
were we slow for a week".  ('real world' DBA's decided to 'tune' a production
box.  Changed tons of things all at once -- didn't record WHAT they changed --
performance went down the tubes.  No metrics from "when it was OK" to compare
against.  Not even numbers like "average response time needs to be X".  Just
"system is really slow -- whats wrong with Oracle??"... So after a week of
unraveling what these real worlders did -- putting files back where they came
from, unfixing all of their fixes, they think -- repeat THINK -- they are back
where they started.)

There is one of me.
I live in the real world.
I actually run a couple of systems -- day to day.

I don't understand his comment about "new technology" as this technology has
been part of Oracle since 7.3 days.

You need a new DBA.  My 2 cents.

      Rating: 5
sync oracle
      text index  July 18, 2003
Reviewer:  Sean
       from NJ, USA
Hi Tom,

I am sorry that I posted this question on the wrong thread last time.
I want to setup job for sync Oracle text index.  I got this error when I try to
create stored procedure or job:

PLS-00201: identifier 'CTX_DDL' must be declared

Thanks so much for your help.

Sean

--This one works.
SQL>exec ctx_ddl.sync_index('blob_index');

-- This onw works.
SQL> begin
  2   ctx_ddl.sync_index('blob_index');
  3  end;
  4  /

PL/SQL procedure successfully completed.

--But this one does not work
SQL> create or replace procedure p1
  2   IS
  3       begin
  4         ctx_ddl.sync_index('blob_index');
  5       end;
  6  /

Warning: Procedure created with compilation errors.

SQL> show error
Errors for PROCEDURE P1:

LINE/COL ERROR
-------- ----------------------------------------------------
4/8      PLS-00201: identifier 'CTX_DDL' must be declared
4/8      PL/SQL: Statement ignored
SQL>

-- The job wakes up every minute.  But there is error in log file
SQL> exec dbms_job.submit( :l_job, 'ctx_ddl.sync_index(''blob_index'');',
sysdate+5/1440, 'sysdate+1
/1440' );

PL/SQL procedure successfully completed.

-- Here is the error in alert.log file
Fri Jul 18 22:36:34 2003
Errors in file c:\oracle\admin\winter\udump\winter_j000_1064.trc:
ORA-12012: error on auto execute of job 22
ORA-06550: line 1, column 96:
PLS-00201: identifier 'CTX_DDL' must be declared
ORA-06550: line 1, column 96:
PL/SQL: Statement ignored

          Followup:

you have the ability to execute ctx_ddl via a role, you need a direct grant for
the procedure to work.  see

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html

      Rating: 5
sync oracle
      text index  July 18, 2003
Reviewer:  sean
       from NJ, USA
Hi Tom,

I am sorry that I posted this question on the wrong thread last time.
I want to setup job for sync Oracle text index.  I got this error when I tried
to
create stored procedure or job:

PLS-00201: identifier 'CTX_DDL' must be declared

Thanks so much for your help.

Sean

--This one works.
SQL>exec ctx_ddl.sync_index('blob_index');

-- This onw works.
SQL> begin
  2   ctx_ddl.sync_index('blob_index');
  3  end;
  4  /

PL/SQL procedure successfully completed.

--But this one does not work
SQL> create or replace procedure p1
  2   IS
  3       begin
  4         ctx_ddl.sync_index('blob_index');
  5       end;
  6  /

Warning: Procedure created with compilation errors.

SQL> show error
Errors for PROCEDURE P1:

LINE/COL ERROR
-------- ----------------------------------------------------
4/8      PLS-00201: identifier 'CTX_DDL' must be declared
4/8      PL/SQL: Statement ignored
SQL>

-- The job wakes up every minute.  But there is error in log file
SQL> exec dbms_job.submit( :l_job, 'ctx_ddl.sync_index(''blob_index'');',
sysdate+5/1440, 'sysdate+1
/1440' );

PL/SQL procedure successfully completed.

-- Here is the error in alert.log file
Fri Jul 18 22:36:34 2003
Errors in file c:\oracle\admin\winter\udump\winter_j000_1064.trc:
ORA-12012: error on auto execute of job 22
ORA-06550: line 1, column 96:
PLS-00201: identifier 'CTX_DDL' must be declared
ORA-06550: line 1, column 96:
PL/SQL: Statement ignored

      Rating: 5
sync index
      using dbms_job  July 19, 2003
Reviewer:  Sean
       from NJ, USA
Hi Tom,

Here is the answer I found.  It is strange that commit is needed in the stored
procedure.

Thanks for your help.

Sean

SQL>connect system/password
SQL>grant execute on ctx_ddl to scott
SQL>connect scott/password
SQL>create or replace procedure p10
IS
begin
    ctx_ddl.sync_index('blob_index');
    commit;
end;
/

SQL>exec dbms_job.submit( :l_job, 'p10;', sysdate+5/1440, 'sysdate+5/1440' );

-- The following does not work though the job wakes up and there is no error in
alter.log.
SQL> exec dbms_job.submit( :l_job, 'ctx_ddl.sync_index(''blob_index'');',
sysdate+5/1440, 'sysdate+5/1440' );

July 19, 2003 Edwin -- Thanks for the question regarding "Using
      where clause in collection", version 8.1.7

      You Asked
Hi Tom,

My question is that how can i select a bulk of record from pl/sql table with a
where clause into another pl/sql table.

I have a simple plsql program as such:

declare
 TYPE Line_Rec_Type IS RECORD
  ( STATUS                         VARCHAR2 (50),
    SET_OF_BOOKS_ID                NUMBER (15)
  );

  TYPE Line_Tbl_Type IS TABLE OF Line_Rec_Type
    INDEX BY BINARY_INTEGER;

a line_tbl_type;
b line_tbl_type;

begin

a(1).status := 'A';
a(1).set_of_books_id :=1;
a(2).status := 'B';
a(2).set_of_books_id :=2;
a(3).status := 'A';
a(3).set_of_books_id :=3;
a(4).status := 'B';
a(4).set_of_books_id :=4;

select cast( multiset( select *
               from  table (cast(a as line_tbl_type))
               where status.column_value = 'A'
               )as line_tbl_type )
into b
from dual;

for i in b.first..b.last loop
dbms_output.put_line(b(i).status);
dbms_output.put_line(b(i).set_of_books_id);
end loop;
end;
/

After run the program, the following error msg displayed:
declare
*
ERROR at line 1:
ORA-06550: line 26, column 28:
PLS-00382: expression is of wrong type
ORA-06550: line 25, column 1:
PL/SQL: SQL Statement ignored

Thanks in advance.

Edwin

      and we said...
You must use SQL types in SQL -- no plsql records, no plsql index by tables --
you'll use an object type and a collection of that type.

The functionality is virtually the same, the semantics a tad different.  Your
example would be:

ops$tkyte@ORA817DEV> create or replace TYPE Line_Rec_Type as object
  2    ( STATUS                         VARCHAR2 (50),
  3      SET_OF_BOOKS_ID                NUMBER (15)
  4    )
  5  /

Type created.

ops$tkyte@ORA817DEV> create or replace TYPE Line_Tbl_Type as TABLE OF
Line_Rec_Type
  2  /

Type created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> declare
  2
  3  a line_tbl_type := line_Tbl_Type();
  4  b line_tbl_type;
  5
  6  begin
  7
  8  a.extend(4);
  9  a(1) := line_rec_type( 'A', 1 );
 10  a(2) := line_rec_type( 'B', 2 );
 11  a(3) := line_rec_type( 'A', 3 );
 12  a(4) := line_rec_type( 'B', 4 );
 13
 14  select cast( multiset( select *
 15                 from  table (cast(a as line_tbl_type))
 16                 where status = 'A'
 17                 )as line_tbl_type )
 18  into b
 19  from dual;
 20
 21  for i in b.first..b.last loop
 22  dbms_output.put_line(b(i).status);
 23  dbms_output.put_line(b(i).set_of_books_id);
 24  end loop;
 25  end;
 26  /
A
1
A
3

PL/SQL procedure successfully completed.
Review &
      Followup

      Rating: 5
Thanks Tom
        March 17, 2003
Reviewer:  Edwin  from
      Hong Kong
Hi Tom,

Thanks for your excellent approach!
I still got someting mix-up in using object type.
What is the advantage of using object type rather than the record type? Since I
read many store proc. in oracle application are using record type, so i wonder
using record type can enhance the performance of the program.

          Followup:

they are programming constructs, not performance tools necessarily.

In this case -- unless you use a SQL object type -- you won't be doing what you
are attempting to do, so here the discussion is "moot".

Me, I have never typed in the lines:

type X is record ....

in my entire life.  Do I use records in PLSQL? yes --

declare
   x table%rowtype;
   y cursor%rowtype;
....

but I've never defined my own record.  Object types and collections -- all of
the time, so I can use SQL on them.  User defined record types -- I don't really
use them.

      Rating:
      5
  March 18, 2003
Reviewer:  A Reader
       from Indonesia
Can u please explain CAST and MULTISET.

Thanks

          Followup:

cast is just a function to tell oracle the datatype expected back.  You can cast
a number to number(7,2) for example -- or as in this case we are casting a
result set as a collection type.

multiset just tells Oracle "hey, this here subquery, it is not a scalar
subquery, it'll return many rows possibly".

You use them predominantly as I have above -- to cast a multi-row result set as
a collection type.

      Rating: 4
Using where
      clause in collection  July 19, 2003
Reviewer:
      Mohan K  from Bangalore, India
In PL/SQL tables it is possible tu user index by integer. Using collection
objects in SQL is it possiblt to do the same or is there any other way to create
an array with index.

Mohan

          Followup:

Not in SQL, no.

SQL = sets
PLSQL = procedural.

July
      20, 2003 Ron -- Thanks for the question
      regarding "Questions on Oracle PC 9i", version P C 9.2.01.0

      You Asked
  Tom:
  Iam executing the following logic

   select   sum(sal) sumsal , sum(comm) sumcomm,
  ( case  job
         when  'MANAGER' then '01'
         when  'CLERK' then '02'
         when  'SALESMAN' then '03'
         else '04'
   end )  assign
 from scott.emp
 group by assign;

  The error msg is

 group by assign
          *
  error at line 9:
  ora-000904:  "assign:" invalid identifer
  My question:   Can the case variable be a griup variable?

My second question :

        I am creating an external table with 4 obs.
 When I select from the table

  select * from external_table
*
ERROR at line 1:
ORA-29913: error in executing ODCIEXTTABLEOPEN callout
ORA-29400: data cartridge error
KUP-00554: error encountered while parsing access parameters
KUP-01005: syntax error: found "logfile": expecting one of: "exit, fields,
records"
KUP-01007: at line 1 column 1
ORA-06512: at "SYS.ORACLE_LOADER", line 14
ORA-06512: at line 1

From the log file

LOG file opened at 09/01/02 16:10:01

Field Definitions for table EXTERNAL_TABLE
  Record format DELIMITED BY NEWLINE
  Data in file has same endianness as the platform
  Rows with all null fields are accepted

  Fields in Data Source:

    EMPNO                           CHAR (255)
      Terminated by ","
      Trim whitespace same as SQL Loader
    ENAME                           CHAR (255)
      Terminated by ","
      Trim whitespace same as SQL Loader
    JOB                             CHAR (255)
      Terminated by ","
      Trim whitespace same as SQL Loader
    MGR                             CHAR (255)
      Terminated by ","
      Trim whitespace same as SQL Loader
    HIREDATE                        CHAR (255)
      Terminated by ","
      Trim whitespace same as SQL Loader
    SAL                             CHAR (255)
      Terminated by ","
      Trim whitespace same as SQL Loader
    COMM                            CHAR (255)
      Terminated by ","
      Trim whitespace same as SQL Loader
    DEPTNO                          CHAR (255)
      Terminated by ","
      Trim whitespace same as SQL Loader
KUP-04021: field formatting error for field EMPNO
KUP-04023: field start is after end of record
KUP-04101: record 5 rejected in file c:/oracledir/programs/\updemp.txt

 Note my file has 4 obs.  It seems to me, it is reding pass the fourth obs.
                    Than you

      and we said...
select sum(sal) sumsal, sum(comm) sumcomm, assign
  from (
  select   sal , comm,
  ( case  job
         when  'MANAGER' then '01'
         when  'CLERK' then '02'
         when  'SALESMAN' then '03'
         else '04'
   end )  assign
 from scott.emp
  )
 group by assign;

is the way to do that.  SELECTION is done "last" -- hence in your original query
-- assign did not yet exist in the group by.

In order to help you with the second one -- you would have needed to have
supplied the CREATE TABLE statement as your error lies THERE, not in the logfile
itself.

  select * from external_table
*
ERROR at line 1:
ORA-29913: error in executing ODCIEXTTABLEOPEN callout
ORA-29400: data cartridge error
KUP-00554: error encountered while parsing access parameters
KUP-01005: syntax error: found "logfile": expecting one of: "exit, fields,
records"

see, it is having an issue parsing the control file component of your create
table.

Review &
      Followup

      Rating: 5
Oracle pc 9i
      programs  September 02, 2002
Reviewer:
      A reader
Tom:
         Thank you very mush for the solution.
   This is my create statement and input file for the second question.

This is my create statement:

   create table external_table
(empno number(4),
 ename varchar2(10),
 job varchar2(9),
 mgr number(4),
 hiredate date,
 sal number(7,2),
 comm number(7,2),
 deptno number(2)
 )
 organization external
 ( type oracle_loader
   default directory data_dir
   access parameters
 ( fields terminated by ',' )
  location ('updemp.txt')
 )
/
 The following statement was exected prior
;
      create directory data_dir as 'c:/oracledir/programs
 and it was created.
  the data and above code are in the progrmas dir .  programs
  the updemp.txt file
7839,KING,MANAGER,7739,17-NOV-81,3000,,20
7940,NEHRU,PRESIDENT,,17-AUG-02,9000,,20
7935,WILSON,CLERK,7698,03-DEC-91,950,,30
7934,MILLER,ANALYST,7739,23-JAN-82,3000,,10

          Followup:

show me the output in this fashion:

ops$tkyte@ORA920.US.ORACLE.COM> create or replace directory data_dir as '/tmp/'
 2  /

Directory created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>    create table external_table
  2  (empno number(4),
  3   ename varchar2(10),
  4   job varchar2(9),
  5   mgr number(4),
  6   hiredate date,
  7   sal number(7,2),
  8   comm number(7,2),
  9   deptno number(2)
 10   )
 11   organization external
 12   ( type oracle_loader
 13     default directory data_dir
 14     access parameters
 15   ( fields terminated by ',' )
 16    location ('updemp.txt')
 17   )
 18  /

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> host echo
'7839,KING,MANAGER,7739,17-NOV-81,3000,,20' > /tmp/updemp.txt

ops$tkyte@ORA920.US.ORACLE.COM> host echo
'7940,NEHRU,PRESIDENT,,17-AUG-02,9000,,20' >> /tmp/updemp.txt

ops$tkyte@ORA920.US.ORACLE.COM> host echo
'7935,WILSON,CLERK,7698,03-DEC-91,950,,30' >> /tmp/updemp.txt

ops$tkyte@ORA920.US.ORACLE.COM> host echo
'7934,MILLER,ANALYST,7739,23-JAN-82,3000,,10 ' >> /tmp/updemp.txt

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select * from external_table
  2  /

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
------
      7839 KING       MANAGER         7739 17-NOV-81       3000
20
      7940 NEHRU      PRESIDENT            17-AUG-02       9000
20
      7935 WILSON     CLERK           7698 03-DEC-91        950
30
      7934 MILLER     ANALYST         7739 23-JAN-82       3000
10

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

which shows your example is just fine (so I think perhaps you used a DIFFERENT
create table then you posted here)

Just do it in sqlplus like I did and paste the results -- no editing by hand..

(and use the proper slash in your create directory, I don't run windows anymore
so I don't know if that is affecting it or not)

      Rating: 5
Oracle PC
      9i  September 03, 2002
Reviewer:  Ron
       from Teaneck, New Jersey
Tom:
          The create table dataement and updemp.txt file follows:
      create table external_table
(empno number(4),
 ename varchar2(10),
 job varchar2(9),
 mgr number(4),
 hiredate date,
 sal number(7,2),
 comm number(7,2),
 deptno number(2)
 )
 organization external
 ( type oracle_loader
   default directory data_dir
   access parameters
 ( fields terminated by ',' )
  location ('updemp.txt')
 )
/7839,KING,MANAGER,7739,17-NOV-81,3000,,20
7940,CHEDDIE,PRESIDENT,,17-AUG-02,9000,,20
7935,WILSON,CLERK,7698,03-DEC-91,950,,30
7934,MILLER,ANALYST,7739,23-JAN-82,3000,,10

The data_dir was successfully created.
  as:  create or replace directory data_dir as 'c:/oracledir/programs'

 The data and programs are in c:/oracledir/programs

                    Thank you
                       Ron

          Followup:

re-read my comment above and then post it JUST LIKE I DID.

as a cut and paste from sqlplus starting with the CREATE DIRECTORY command.
That is what I asked for.  We already had the stuff you put above again.

I believe you have a typo somewhere -- and that what you use in SQLPlus is
different from what you keep on putting here.

tkyte@TKYTE9I.US.ORACLE.COM> create or replace directory data_dir as 'c:/temp'
  2  /

Directory created.

tkyte@TKYTE9I.US.ORACLE.COM> create table external_table
  2  (empno number(4),
  3   ename varchar2(10),
  4   job varchar2(9),
  5   mgr number(4),
  6   hiredate date,
  7   sal number(7,2),
  8   comm number(7,2),
  9   deptno number(2)
 10   )
 11   organization external
 12   ( type oracle_loader
 13     default directory data_dir
 14     access parameters
 15   ( fields terminated by ',' )
 16    location ('updemp.txt')
 17   )
 18  /

Table created.

tkyte@TKYTE9I.US.ORACLE.COM>
tkyte@TKYTE9I.US.ORACLE.COM> select * from external_table;

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
----------
       839 KING       MANAGER         7739 17-NOV-81       3000
  20
      7940 CHEDDIE    PRESIDENT            17-AUG-02       9000
  20
      7935 WILSON     CLERK           7698 03-DEC-91        950
  30
      7934 MILLER     ANALYST         7739 23-JAN-82       3000
  10

Either you give me output *just like the above* or we'll just be ignoring you.

      Rating: 5
Records
      delimited by newline  July 20, 2003
Reviewer:
      Sikandar Hayat  from Pindi Bhattian - Pakistan
Hi,
Records delimited by newline is missing,
Data
20,Sikandar,21-SEP-98

<1> Table Creation
create table my_emps2
   (idno number,
     name varchar2(20),
     h_date date
   )
 organization external
   (type oracle_loader
    default directory load_dir
    access parameters
   (
     records delimited by newline
     badfile load_dir:'bad_load2.txt'
     logfile load_dir:'log_load2.txt'
     fields terminated by ','
 missing field values are null
    (idno,name,h_date))
 location('emps2.txt') )
 reject limit unlimited
/

Output
QL> select * from my_emps2;

     IDNO NAME                 H_DATE
--------- -------------------- ---------
       20 Sikandar             21-SEP-98

SQL> drop table my_emps2;

Table dropped.
**********************************************
<2> Created without "Records delimited by newline"

 create table my_emps2
    (idno number,
      name varchar2(20),
      h_date date
    )
  organization external
    (type oracle_loader
     default directory load_dir
     access parameters
    (
      badfile load_dir:'bad_load2.txt'
      logfile load_dir:'log_load2.txt'
      fields terminated by ','
  missing field values are null
     (idno,name,h_date))
  location('emps2.txt') )
  reject limit unlimited;

Output with error
SQL> select * from my_emps2;
select * from my_emps2
*
ERROR at line 1:
ORA-29913: error in executing ODCIEXTTABLEOPEN callout
ORA-29400: data cartridge error
KUP-00554: error encountered while parsing access parameters
KUP-01005: syntax error: found "badfile": expecting one of: "exit, fields,
records"
KUP-01007: at line 1 column 1
ORA-06512: at "SYS.ORACLE_LOADER", line 14
ORA-06512: at line 1

I hope the above will help you.

          Followup:

simple, wire diagram says "record is mandatory"

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96652/ch12.htm#1001435

July
      20, 2003 Vladimir -- Thanks for the
      question regarding "ARCHIVELOG or NOT ARCHIVELOG?", version 8i

      You Asked
Hi Tom,
thanks for your help before and I've some smalls questions for
you:
-I would like  to know what is the different between create DB
in archivelog mode or not?
-When I create a DB with Oracle Database Assistant, it create me
in which mode ARCHIVE or NOT ARCHIVE,
-How I can know if certain DB is running in ARCHIVE or NOT
ARCHIVE mode
-To change DB to ARCHIVELOG  MODE, I must to logging like system
and made:
 alter table v$database set log_mode=ARCHIVELOG;
-If I 've a DB running in NOT ARCHIVELOG MODE and change to
ARCHIVELOG  MODE, i'll loss some data?

Thanks very much
sincerely
Vlad

      and we said...
o typically a database is CREATED in noarchivelog mode to avoid
the generation of tons of archives from the create itself.
After the database is created and loaded and you are ready to go
production, you would turn on and configure archivelog mode.
Prior to that, it is typically easier to rebuild then to spend
the time generating and archiving all of that redo log (you'll
generate lots of it just to do the create and initial load).

o ok that is a fact.

o  select log_mode from v$database;

o see

http://technet.oracle.com/doc/oracle8i_816/server.816/a76956/archredo.htm#9848
for the proper procedure.  You cannot "alter table v$database"

o no, you'll be PROTECTING yourself from the potential loss of
data.  It is a database in NOarchivelog mode that will lose data
some day (due to media (disk) failure)
Review &
      Followup

      Rating: 5
Need your
      expert Advice   March 20, 2002
Reviewer:
      Yogeeraj  from Mauritius
Hi,
(Hope that my question is relevant here ;))

Is there a measurable performance degradation when operating in ARCHIVELOG Mode
compared with NOARCHIVELOG?

My story is:
We are setting up a server which will be hosting an online real time (both CPU
and Disk intensive) application.
NT server 4.5
Oracle 7.3.4
IBM E-server with RAID 5

The Application was developed on Delphi and uses DCOM objects and uses
considerable amount of memory for keeping real time data and providing them to
online/automated processes (Programmable Logic Controllers). Frequent database
operations are performed only to synchronise "memory" data with tables in the
Database (using SQLNET(?)). Hence, the database is used only for temporal
synchronisation during job start/completion, System startup and system
shutdowns. The 3rd party application has its own way to keep its data in
memory/files.

According to the "external" engineer installing the system, their company do not
recommend use of ARCHIVELOG mode - reason being performance deterioration.

So far so good....

Now, my Main production system has a link to their database, and does frequent
data uploads and downloads. (upload: send jobs; download: get job status, etc).
Thereby, synchronising everything.

We know that ARCHIVELOG mode increases robustness of the system and make
recovery relatively easy in cases of crashes.

My seniors are more keen to go for NOARCHIVELOG mode as recommended by the
external Engineer than what i recommend...

What should i do?

thank you for your patience, time and precious guidance.

Best Regards
Yogeeraj

          Followup:

gee, why would you use 7.3.4 (not supported, ancient software, missing thousands
of new features) today???

Oh well.

The "external engineer" should be fired immediately.  I will say it simply --
he is so wrong, so redicously wrong, I would call him stupid.  Their company
must LOVE to lose data as that is what happens in noarchivelog mode.  You will
lose data.

Archive logging, in a properly configured system, adds 0% performance
degradation.  It is all about IO.  If you have the right number of devices and
IO channels -- no worries, no "degradation".

So far so bad if you ask me.

Don't do it.  Just don't do it.

      Rating: 4
Performance in
      Archivelog Mode  March 20, 2002
Reviewer:
      mohammad taha  from Dubai,UAE
"If you have the right number of devices and
IO channels -- no worries, no "degradation".

Would you please explain with examples how to decide
on what should be the right number of devices and
IO channels for a database of around 50 giga.

          Followup:

50 gigs, 5meg -- size of the database has nothing to do with it.

Transaction volume, that has everything to do with it.

Archiving is very CPU deintensive -- it involves copying a file from one disk to
another disk.

You need sufficient devices to avoid contention here.  You want to make it so
that when LGWR is writing to a device, ARCH is *not* reading that device.  So,
you would have log group 1 on dev1 (mirrored to dev3).  log group 2 on dev2 (
mirrored to dev4).  log group 3 on dev1/dev3, log group 4 on dev2/dev4 and so
on.

Well, LGWR writes to dev1/dev3.  Arch is reading dev2/dev4 and writing to dev5.
Arch finishes and waits.  LGWR not writes to dev2/dev4, Arch reads dev1/dev3 and
writes to dev5.   No contention, there you go -- smooth operation, no
degradation.

Only if lgwr and arch start stomping on each other or the archiver cannot keep
up with the logs that lgwr is producing would there be an issue and then like
everything else in the database world -- you tune that process, configuring
sufficient disks -- making sure they don't contend with eachother.

The trade off -- eg not using arch, being in noarchivelog mode -- is a 100%
surefire way to LOSE data.  It is not a 10% chance, not a 50/50 chance -- you
WILL lose data (raid 5, raid whatever, you WILL lose data.  Point in time
recovery is done for dozens of reasons, media recovery being but one of them.
Even raid 5 fails (believe it or not))

      Rating: 5
multiple
      archive destinations  July 19, 2003
Reviewer:
      Reader
Tom, If I had specified multiple archive destinations, do I have to have
corresponding number of ARCH processes? Say, for example, if i have 3 archive
destinations, should have 3 ARCH processes to archive to these destinations.
Could you give me a guideline how many ARCH processes one should have in the
database and what dictates the specification of multiple ARCH processes? Thanks.

          Followup:

"have to", no

"should", you might consider it -- they can all work at the same time together.

If you find arch running behind (eg: you get "cannot allocate new log, archival
required" in your alert log), you might want more arch processes.

      Rating: 5
How does it
      do?  July 20, 2003
Reviewer:  Reader

Assuming that I have configured 3 ARCH processes and I have three members each
in my 3 log groups. I have, say, three arch destinations. How does oracle divide
the workload of copying the online redo log file to my 3 arch destinations? My
understanding is that arch process copies from only one "valid" redo member of
the group to arch destinations. Now, as I have three arch destinations, is it
like three ARCH processes that I have configured will simultaneously copy the
redo member to all of these three arch destinations?
How is it different from one arch process doing the same to three arch
destinations. Is there a one to one mapping for number of arch processes and
number of arch destinations. Thanks for your time in clarifying this.

          Followup:

it is not documented how the work is divvied up....

Article-ID:         <Note:73163.1>

has some info tho

July 20, 2003 pasko -- Thanks for the question regarding "
      DBMS_OBFUSCATION_TOOLKIT.DES3GetKey , MD5 Functions", version 8.1.6 /9.0.1

      You Asked
Hi Tom
First of all , thanks very for proving us with this Great Oracle support
platform..

Today my query is about the  DBMS_OBFUSCATION_TOOLKIT.

My requirement is to generate  fixed length string (7)  to be used as a password
, and in that string only characters aA-zZ 0-9 should appear.

My client needs to use Known Key Generation Standards  to generate the Key .
I have gone through your password generation Logic in this site and also the
spell checker, but could not see how i could do this..

Basicall i need to store this string in the Database as a password and then
users will supply this to get some infomartion from this Table back...

My logic was :

for i in 1 ..  7 loop
    get number from dbms_random.value(1, 35) ;
       substitute all characters not required
           append charcter until length 7 is reached
                and return it as a password .
end loop

But i saw this from Oracle manuals: Quote...
"Do not use DBMS_RANDOM as it is unsuitable for cryptographic key generation"

1.
So i would like to ask you , how could use
 DBMS_OBFUSCATION_TOOLKIT.DES3GetKey   or
  DBMS_OBFUSCATION_TOOLKIT.MD5
routines to generate Unique/nearly Distinct Random keys..
An example would be Greatly appreciated .

2. If i use  sys_guid() as my primary key for table that will be storing
passwords..
Do you see a way on how  this raw(16) value  could be used with the
 DBMS_OBFUSCATION_TOOLKIT  Functions? ( its  Unique)

3. Is it possible to write a Java stored procedure to read Random numbers  from
Unix Random  number file..

Best Regards

      and we said...
Well, first of all -- i would NEVER store a password in the database, encrypted
or otherwise.

A hash -- sure, the password -- no.  Unix doesn't store the password, Oracle
doesn't store the password, why should you?

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:95412348059
for how to hash.

If you want to encrypt the password, you'll not only have to generate a secure
key -- but (and this is the really really hard part) you'll have to MANAGE IT!!!
 If someone gets your key -- they got your data.  Tell me, where will you store
this key you use to encrypt the password?  (better store it somewhere safe,
it'll take something like 2 trillion years to recover your data if you lose
it).....

My suggestion (take it or leave it) don't encrypt this data, you don't NEED to,
you don't WANT to.

Review &
      Followup

      Rating: 2
Triple
      DES  June 28, 2002
Reviewer:  Andre
       from Brazil
There are no problems with key management. That's what key managers are for.
That's what key management departemnts are for.

There are whole databases for storing encrypted things, like those at VeriSign
and other trusted entities.

2 trillion years with triple DES ? Wow ! What's the brand of your calculator ?

There are prime-number based and other complex heuristics to break it in some
hours, days at most, even with really long keys. You must have gotten that from
some book or what... Sounds like sci-fi to me.

As to having a trustworthy encryption key, use the the new GETKEY feature in 9i.
It will produce a "good" FIPS-140 certified encyption key. Do not use
DBMS_RANDOM, because it produces weak keys. From de 9i App Dev
Guide-Fundamentals:

[Quote]

Oracle9i Data Encryption Capabilities
While there are many security threats that encryption cannot address well, it is
clear that an additional measure of security can be achieved by selectively
encrypting sensitive data before storage in the database. Examples of such data
could include:

Credit card numbers
National identity numbers
Passwords for applications whose users are not database users
To address these needs, Oracle9i provides a PL/SQL package to encrypt and
decrypt stored data. The package, DBMS_OBFUSCATION_TOOLKIT, is provided in both
Standard Edition and Enterprise Edition Oracle9i. This package currently
supports bulk data encryption using the Data Encryption Standard (DES)
algorithm, and includes procedures to encrypt (DESEncrypt) and decrypt
(DESDecrypt) using DES. The DBMS_OBFUSCATION_TOOLKIT also includes functions to
encrypt and decrypt using 2-key and 3-key DES, in outer cipher block chaining
mode. They require keylengths of 128 and 192 bits, respectively.

The DBMS_OBFUSCATION_TOOLKIT includes a cryptographic checksumming capabilities
(MD5), and the ability to generate a secure random number (GetKey). Secure
random number generation is important part of cryptography; predictable keys are
easily-guessed keys, and easily-guessed keys may lead to easy decryption of
data. Most cryptanalysis is done by finding weak keys or poorly-stored keys,
rather than through brute force analysis (cycling through all possible keys).

--------------------------------------------------------------------------------
Note:
Do not use DBMS_RANDOM as it is unsuitable for cryptographic key generation.

[End Quote]

Hoping to help...

          Followup:

I don't know -- what is your brand of calculator dude??  I'm not sure what you
are getting at.  I understand that two trillion would be a conservative number
for brute force cracking DES3:

<quote source=
http://www.prlsoftware.com/DES_Info.htm
, one of MANY sources that will confirm that it takes a
really utterly long time.  2 trillion might even by "short">
Triple DES (3DES)

Triple DES does not make your encryption 3 times harder to break.  It makes it 5
x 10 33 (5 Billion Trillion Trillion) times harder to break.  This is because
Triple DES means you are performing 3 DES computations with three separate keys.
 The result is that your key length is effectively increased to 24 characters
(168 bit encryption).  This means that the number of possible key combinations
that are available are:

2168 = 3.7x 10 50 (370 Trillion Trillion Trillion Trillion) different
combinations.

Assuming that you can crack the keys at a rate of 1 Million per minute...... you
would be working away for the next...... well let's just say that you still be
working on the first couple of percent when the earth ends and.... and assuming
the universe implodes you still won't have finished computing every different
combination.

This is why Triple DES is considered so strong.
</quote>

you do the math, I'm too tired right now....  Anyway, NOT that it is even
relevent.

As for the "there is no problem with key management, that is what key managers
are for", well....  I like things

o complete
o simple
o small
o easy

so, if you like things complex, hard, big, with many moving parts -- by all
MEANS introduce yet more software products into the mix.  I've never heard of
anyone using a key manager for something as so totally simple as "storing a
password".  Seems like taking an atom bomb to crach a walnut -- it'll certainly
crack the walnut but hey, it is a little overkill you know what I mean.

I'll stand by the answer here.  You do not want to encrypt the password, you
want to one way digest it, perhaps with the MD5 checksum.  That way, when
someone presents you with a username+password, you can say "yup, that is your
password" but there is NO WAY anyone can take your username and hashed password
and derive your real password.  Much more secure, simple to do, no complex
encrypt/decrypt with a key manager -- no muss, no fuss.

Always looking for the easy way...

      Rating: 1
Triple
      DES  June 28, 2002
Reviewer:  Andre
       from Brazil
There are people breaking 3k triple DES keys with HP calculators -- believe it
or not.

You are talking of dummy algorithms that take too long. But experts and natural
code-breakers can do it much faster, like I said with bounded algorithms that
work with prime numbers for example.

Okay, I know how Oracle hashes users passwords. Yes, "complete",  "simple",
"small", "easy".

You said you would NEVER store passwords in a database -- nope, too radical. So,
you would never work at a digital certification company or a goverment agency ?
They have their user data encrypted (not hashed), and have very well secured
keys. The reader didn't say that wouldn't be his case...

          Followup:

show the proof.  go for it.  I don't believe you.  A calculator -- now maybe you
are smoking something.  If it is "real", you should be able to prove it to us
right.  Go for it.

Let us see the one link somewhere that says "hey, these guys cracked des3 in
less then your lifetime".  Come on -- I'll eat the humble pie.  I know I would
have heard of des3 getting beaten - and I haven't.

I don't believe you -- but that should be trivial for you to correct as I'm sure
that the event of des3 being cracked would have made at least one newspaper --
don't you?

It is so hard to actually key in a google search that returns zero hits but:

"triple des cracked"
"des3 cracked"
"des3 broken"
"triple des broken" (well, ok that returns a hit, of a search engine for someone
looking for triple des broken)

do...

Now, the search for

"cracking triple des"

returns alot, alot of articles discussing how it hasn't happened.

A calculator -- now that is currently the stuff of SCI-FI.

I said, in response to this question, don't store the passwords encrypted.  That
is not a radical response.

So, when is the askandre site opening up anyway.

      Rating: 5
Mr Andre Please
      do not spoil the decorum of this Forum  June 28, 2002

Reviewer:  A reader
If you have a Question Ask otherwise Please leave.
 Do not waste yours and Others time Please.
U are better of focusing on the World Cup Final for Brazil rather then trying to
verbally show Tom is wrong without proofs and with just hollow statements.
 An Idle Mind is a Devils Workshop.
Final Goodbye.

      Rating:
      5
  June 29, 2002
Reviewer:
      A reader
Tom You Are The Best !!!!!!!!!

      Rating: 3
So how do we
      verify/Authenticate User   June 29, 2002
Reviewer:
      A reader
Please Explain further
Well, first of all -- i would NEVER store a password in the database, encrypted
or otherwise.

A hash -- sure, the password -- no.  Unix doesn't store the password, Oracle
doesn't store the password, why should you?

My Question is So how do we do this User verification if we have nothing in our
database to cross check with. also
Even How does Oracle Do it If passwords are not stored.

          Followup:

Please read the link to which I referred:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:95412348059
it sort of answers that (well, not sort of -- it demonstrates how to do exactly
that)

      Rating: 4
to
      andre  June 30, 2002
Reviewer:  Richard Ji
       from New York City, NY USA
Andre,

Sounds like you might be confused 3DES with DES.  Yes
DES has been cracked but not triple DES.  Please show
us a proof (a link) which shows triple DES has been
cracked.  So I can sue my bank for not using secure
encryption. :)

Richard

      Rating: 5
How to use MD5
      instead of dbms_utility.get_hash_value  July 01, 2002

Reviewer:  Pasko
Hi Tom

Thanks you  very much for your Response and all other contributers to this
query..

1.
Could you please provide us with another version of your digest routine  which
uses MD5 instead of dbms_utility.

2.Is there a chance that your digest function would return Unprintable
characters and also what would happen if i would like to skip all values which
contain "ambiguous"  characters  such as 1IO0 -   from the generated values
outputted  from your digest function.
--and does your digest routine always return 8 Byte strings

3.I  saw from above :
"use the the new GETKEY feature in 9i"
 but i don't really see this function/procedure  from the package or are they
talking about  DBMS_OBFUSCATION_TOOLKIT.DES3GetKey ?

Could you please provide us with an example of how to use this
function/procedure to generate secure keys  as it's indicated from the Docs.

4. From my orignal query, could i somehow use raw values generated from
sys_guid()  and convert them to strings of say  6 , 7 or 8 printable characters
.

Thanks in advance.

          Followup:

1)

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      function digest( p_username in varchar2, p_password in varchar2 )
return varchar2
  3      is
  4      begin
  5          return dbms_obfuscation_toolkit.md5
  6                             ( input => utl_raw.cast_to_raw(
upper(p_username)||'/'||upper(p_password) ) );
  7      end digest;
  8  begin
  9      for x in ( select username from all_users where rownum < 20 )
 10      loop
 11          dbms_output.put_line( 'User: ' || rpad( x.username , 30 ) ||
 12                                ' digest: ' || digest( x.username, 'TIGER' )
);
 13      end loop;
 14  end;
 15  /
User: SYS                            digest: 1F01F328A9343C6A822967C153827ECD
User: SYSTEM                         digest: 94FB743169267E709CE426B4DE4E7637
User: OUTLN                          digest: C21A6A62391092F1DDBB37FD285FC8CA
User: TEST_OWNER                     digest: D928C75947D8B2D4752DF483CAC4C335
User: OPS$ORA817                     digest: 21AC742C0811907D1CD48DC48490A75B
User: CTXSYS                         digest: C6910F5BE313CF6EA4F4E8106C83D24F
User: DBSNMP                         digest: AB08AC4006A9FDA674C79631843BCF9A
User: OPS$CLBECK                     digest: 35EC540E9852150ED58B0E96CC3677A1
User: APPS                           digest: 44AF9D0127E0A89577DD0649976C3333
User: TMC                            digest: 6AC936E720116C8A84DD498C43DF483D
User: WEB$CLBECK                     digest: 9942BC161F4F3BE1BA48DFA4CFE26247
User: SDILLON                        digest: 63893AA51B17DCDDD64D805ED46E19A9
User: TYPES                          digest: E8FCCCFF0F88F640015D2AB11E9D7BD9
User: TPS_SECURITY_MANAGER           digest: 071B6D68C44DC5353A7B3BC6D5C93745
User: COMMON                         digest: 46657377218B05A0613325A56EDB3D7F
User: U2                             digest: EA3F0861195433A8CDE4CEDE02E48E94
User: U1                             digest: 0E88AB91C65C39CC91464D9C31170BEB
User: USER_A                         digest: DACDF46B0B225FA9C7CA12ABA6F70231
User: SINGLE_ACCT                    digest: A5E30115B4B1C9E483C44340E6D6537B

PL/SQL procedure successfully completed.

2) no, it is returning a HEX string (0..9 A..F)

I don't understand what you mean by "ambigous characters" -- 1IO0 doesn't seem
to contain any ambigous characters?  Are you trying to verify a supplied
username/password or GENERATE a password??? What I am demonstrating is not a
password GENERATION method, it is a password VERIFICATION method.

This digest method (above) returns 32byte strings, the other 8 byte (although
you control that by controlling the size of the hash table in that one)

3) I suppose they meant the des3getKey -- that routine is straight forward
enough to call:

ops$tkyte@ORA9I.WORLD> variable s varchar2(80)
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> exec :s := dbms_random.string( 'A', 60 ) ||
dbms_random.string( 'A', 20 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> print s

S
---------------------------------------------------------------------------------
-----------------------------------------------
UpKYrZHeiooBqkvpJHuImXrLOmVzYhgBhJcNLQLwkKYAhKgoZKnXPDBjcgYPGnfPyQOBAGmtRTJUhXAo

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> declare
  2          l_seedr   raw(255);
  3      l_keyr    raw(255);
  4
  5          l_seed    varchar2(255);
  6      l_key     varchar2(255);
  7  begin
  8          l_seed := :s;
  9          l_seedr:= utl_raw.cast_to_raw( l_seed );
 10
 11          dbms_obfuscation_toolkit.desGetKey( seed => l_seedr, key => l_keyr
);
 12          dbms_output.put_line( '1) Key Length = ' || length(
utl_raw.cast_to_varchar2(l_keyr) ) ||
 13                            ' Key = "' || l_keyr  || '"' );
 14
 15          dbms_obfuscation_toolkit.desGetKey( seed_string => l_seed, key =>
l_key );
 16          dbms_output.put_line( '2) Key Length = ' || length( l_key ) ||
 17                            ' Key = "' || l_key  || '"' );
 18
 19          dbms_obfuscation_toolkit.des3GetKey( seed => l_seedr, key => l_keyr
);
 20          dbms_output.put_line( '3) Key Length = ' || length(
utl_raw.cast_to_varchar2(l_keyr) ) ||
 21                            ' Key = "' || l_keyr  || '"' );
 22
 23          dbms_obfuscation_toolkit.des3GetKey( seed_string => l_seed, key =>
l_key );
 24          dbms_output.put_line( '4) Key Length = ' || length( l_key ) ||
 25                            ' Key = "' || l_key  || '"' );
 26
 27
 28          dbms_obfuscation_toolkit.des3GetKey( which =>
dbms_obfuscation_toolkit.ThreeKeyMode,
 29
     seed => l_seedr, key => l_keyr );
 30          dbms_output.put_line( '5) Key Length = ' || length(
utl_raw.cast_to_varchar2(l_keyr) ) ||
 31                            ' Key = "' || l_keyr  || '"' );
 32
 33          dbms_obfuscation_toolkit.des3GetKey( which =>
dbms_obfuscation_toolkit.ThreeKeyMode,
 34
     seed_string => l_seed, key => l_key );
 35          dbms_output.put_line( '6) Key Length = ' || length( l_key ) ||
 36                            ' Key = "' || l_key  || '"' );
 37  end;
 38  /
1) Key Length = 8 Key = "40365655002781FA"
2) Key Length = 8 Key = "xH¨ @,"
3) Key Length = 16 Key = "6F2A0859C8AE128AAFEDEBB558EF3482"
4) Key Length = 16 Key = "~c?
                             Ë&8³b+á"Xµý"
5) Key Length = 24 Key = "C80121C7A7C24ED749919715DD3698EEF7C9E5544E2E54D2"
6) Key Length = 24 Key = "Ù=B"HN/ªã`ÿÙò:ªR©ö,ªO"

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> /
1) Key Length = 8 Key = "96670E8DE2F905CE"
2) Key Length = 8 Key = "L`ÿã£™—"
3) Key Length = 16 Key = "74653E1DDB12181DC724D55DBDEBEEB9"
4) Key Length = 16 Key = "kM?~1¯ož0EKí†$Ä"
5) Key Length = 24 Key = "E9D34EB3DCCA6D32271E0B9E8851C2F3C4694012CA738B66"
6) Key Length = 24 Key = "8€›@ÿÑ*5÷ÓxÝù„=û¥¥äkíÑ<"

PL/SQL procedure successfully completed.

Couple things to note:

a) given the same seed, this routine returns different data -- it is
non-deterministic (one of the attributes you want in a key generation routine)

b) when you use the STRING "api" (not raw), it returns a string with possibly
unprintable characters (eg: the above example "beeped" at me, a CTL-G was in
there somewhere).  I strongly suggest using only the RAW api's with
dbms_obfuscation_toolkit for all of the interfaces and using utl_raw when
necessary.  Get the key in RAW and leave it raw.  Encrypt string data into a
RAW, not a string and so on.

4) sure, but again it sounds like you are into password GENERATION, not
verification again?

      Rating: 5
I need a
      cryptographic key  July 01, 2002
Reviewer:
      Pasko
Hi  Tom

Thanks very much for your prompt Response .

Yes,Tom , you are right..

Actually i am trying to use DBMS_OBFUSCATION_TOOLKIT  to generate a password ,
although i know for sure that this package is for Data  Encryption/Decryption
and may be Password(KEY) verification.

That's why i was specific in asking about the
DES3GetKey and  MD5 Functions because i could use this to use the generated key
as My password.

I saw your password Generation Routine in this site too which is very similar to
my own ealier approach , but i wanted to use the random keys generated   from
DBMS_OBFUSCATION_TOOLKIT .( after i read from the Oracle Manuals)

What do they (Oracle people in the Docs) actually mean by writing :

"Do not use DBMS_RANDOM as it is unsuitable for cryptographic key generation"

and my aim is to generate this
"cryptographic key"   they are talking about...(but how ?? )
or may be i missed out on something.

Best regards

          Followup:

You are not generating cryptographic keys, you are trying to generate a user
password (one that they should presumably change immediately after getting
anyway).

Just use a simple algorithm with dbms_random (perhaps dbms_random.string fits
your needs)

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1235458156173
and don't "over solve" the problem.

The reason they say don't use dbms_random for generating keys for cryptology is
because if you are encrypting the data -- you must have some pretty darn
interesting data -- stuff people would be willing to try and crack.  Now, using
brute force or sophisticated techniques (even with a reallly superfast HP
calculator ;), it would take a very long time and lots of resources to decrypt
the data.  It would be much much easier to get the key and decrypt it.  If you
use a psuedo random number generator with a period (such as dbms_random), all
you need to see is a sufficiently long sequence of generated numbers to "guess"
what the next one would be (eg: as an end user, you could just encrypt a couple
of rows and look at the keys provided to you to decrypt -- based on that, you
would be able to guess what keys come next).  Hence, you can get the keys to the
data without having to "crack" the encryption.

Using the des3getkey -- you don't have such a situation.  It doesn't have a
period, you cannot guess the next key based on some sequence of generated keys.

But, in your case, you have a somewhat simple problem to solve.  I would just
use dbms_random.

      Rating: 5
But
      dbms_random.string generates unprintable characters  July
      01, 2002
Reviewer:  Pasko
HI Tom

Thanks again for your prompt response.

Tomorrow i will play with  dbms_random.string

But i am not sure if it will suit me because i would like to get only charasters
a-zA-Z0-9 in my string/key  and also
skip all strings whenever '01IO' appears..

I am not sure how often will these characters appear in my string....plus i need
to limit it to less than 8 bytes...

Is there an option from  dbms_random.string   which generates only Printable
characters?

Thanks

          Followup:

Yes (if you follow the link for dbms_random i have above, i show that) but
printable characters is much much larger then a-zA-Z0-9 -- it includes
{}[]!@$$#@ and so on.

Perhaps you'll be best suited by making a string of the characters you WANT to
use:

str varchar2(500) :=
  'ABCDEFGHIJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789';
strlen number := length(str)

for example and then doing something like:

   l_pw := null;
   for i in 1 .. 8
   loop
      l_pw := l_pw || substr( str, dbms_random.value( 1, strlen ), 1 );
   end loop;

      Rating: 5
Thanks very
      much Tom...-_-  July 02, 2002
Reviewer:
      Pasko
Hi TOM

Thanks very much for your excellent follow-up to my query..

I will play around with all these examples you have given me , but i think i
might generate a passowrd and use MD5 routine  for this password  Verification
when it is queried later by a user..

Actually in my case it's not really a passoword but rather a secret key which is
given to user and the user is not allowed to change it...i will make it expire
after some days , so that i may be able to re-use it for other users.

Best regards

      Rating: 5
To add to all
      of that 3DES CRACKING disscussion I looked into
      google.com  July 03, 2002
Reviewer:  Ian
      Matyssik  from Japan
This is a qoute from one mailing list digest. for people who still don'get it.
In a human language.
------------------------------

From: rasumner@bach.wisdom.weizmann.ac.il (Reuben Sumner)
Subject: Re: 3DES cracked in 22 hours ??? (Was: Re: (fwd) DES Challenge III
Broken in
Record 22 Hours !)
Date: 22 Jan 1999 12:49:32 GMT
Reply-To: rasumner@iname.com

On Thu, 21 Jan 1999 10:14:46 -0000, Sam Simpson <ssimpson@hertreg.ac.uk> wrote:
>Assuming 3DES has an effective keylength of 112-bits then it would take
>around 329293306 years on average to break a single key.
>
>This figure is based on the peak keys per second figure (250 Billion) quoted
>in the EFF/RSA press release.

I'm afraid I have to dissagree with your calculation.

>> kps:=250*10^9;         // keys per second
                               250000000000
>> spy:=60*60*24*365.24;  // seconds per year (average year)
                                31556736.0
>> kpy:=kps*spy;          // keys per year
                           7889184000114802736.0
>> 2^111/kpy;
                             329076927259548.0
2^111 is the average number of keys you need to search.  Thus the total number
of years is 329,076,927,259,548.  That is 329 trillion not 329 million.
Even just searching 64 bits (ie the RC5-64 search) would take 1.17 years.
The real threaght is not distributed.net.  It is amusing and a demonstration
that there are lots of wasted CPU cycles out there, but not a real threaght.
In the paper by Matt Blaze et al it is estimated that a $10 custom ASIC
can test about 200M keys/second.  At that rate a $10M computer could break
64 bits in an average of 12.8 hours.  Compare that to 1.17 years.
Even only 80 bits of something like skipjack (assuming brute force is your
best attack) would take a $1B attack 1 year (on average) to crack.

Reuben

------------------------------

------------------------------

From: bt@templetons.com (Brad Templeton)
Subject: Re: 3DES cracked in 22 hours ??? (Was: Re: (fwd) DES Challenge III
Broken in
Record 22 Hours !)
Date: 23 Jan 1999 19:10:32 PST

In article <slrn7agt61.v23.rasumner@bach.wisdom.weizmann.ac.il>,
Reuben Sumner <rasumner@iname.com> wrote:
>2^111 is the average number of keys you need to search.  Thus the total number
>of years is 329,076,927,259,548.  That is 329 trillion not 329 million.
>Even just searching 64 bits (ie the RC5-64 search) would take 1.17 years.
>The real threaght is not distributed.net.  It is amusing and a demonstration
>that there are lots of wasted CPU cycles out there, but not a real threaght.
>In the paper by Matt Blaze et al it is estimated that a $10 custom ASIC
>can test about 200M keys/second.  At that rate a $10M computer could break
>64 bits in an average of 12.8 hours.  Compare that to 1.17 years.
>Even only 80 bits of something like skipjack (assuming brute force is your
>best attack) would take a $1B attack 1 year (on average) to crack.

The difference between 128 bits and 64 bits is indeed this vast.  That
we built a machine cheaply that can crack 56 bits does mean that with more
money you can break 64 bits quickly, but 128 bits or more is another story.

Add enough bits and you reach the point where if every atom on earth were
a processor that could check a key every microsecond you still couldn't break
by brute force.  Add a few more bits and it's every atom in the universe.

Suffice to say that this means that sufficiently long keys are unbreakable
by brute force based on current understanding, even with things like DNA
computers.  Only a breakthrough in physics, like quantum computing can
break a long key with brute force.   Or a breakthrough in discrete
math -- which is no longer brute force.
--
        Brad Templeton
http://www.templetons.com/brad/
------------------------------

Hope that will explain it to some people who likes HP Calculators. Also subject
of this message is from some HP Calculators FAN.

      Rating: 5
Divisible By
      8  May 20, 2003
Reviewer:  Jerry  from
      Washington, DC USA
Your book states that the data being encrypted must have a length div. by 8.

In the MD5 digest function you provide in this thread, is the cast_to_raw()
satisfying that requirement?

When I hash all the user/passwds in our database, I get an even 32 bytes
according to length(), but I just want to confirm this.

          Followup:

md5 is a checksum, not encryption

it does not have the same 8byte multiple restriction.

It does return a 16byte key, which in hex will be 32 characters:

CREATE OR REPLACE PACKAGE dbms_obfuscation_toolkit AS
    ------------------------------- TYPES ------------------------------------
    -- Types used to make it easier for the user to reserve the correct
    -- amount of memory for a checksum.

    SUBTYPE varchar2_checksum IS VARCHAR2(16);
    SUBTYPE raw_checksum IS RAW(16);

      Rating: 5
Interesting
      read  July 17, 2003
Reviewer:  Scott
       from Perth, WA
G'day

Excellent discussion.

Do you recommend any sites that dicuss key management?

And regarding the dbms_obfuscation_toolkit, if we have the choice between DES
and DES3, why choose DES? Why not have the best?

          Followup:

I'm not personally aware of any -- but I'm sure they exist, suggest you try
googleing

"key management" encryption

you'll find des3, since it munges more bits, to be more "computationally
expensive"

      Rating:
      5
Thanks  July 18, 2003
Reviewer:
      Scott  from Perth , WA
G'day

Thanks for that Tom. I've also grabbed a few "key" ideas from your book.
CPU cycle usage didn't occur to me...

Would this be correct:
DES(string) returns gobble-d-gook.
DES(raw) returns nice to read in sql*plus characters
DES3(raw) returns highly encrypted gobble-d-gook.

Just making sure I'm using these right....
(I expected highly encrypted nice to read characters from DES3(raw))

Enjoy your holiday

Thanks.

          Followup:

none of them read "nice to read" characters?  they all return gobble-d-gook

      Rating:
      3
Curious....  July 20, 2003
Reviewer:
      Scott  from Perth, WA
v_des := dbms_obfuscation_toolkit.desencrypt(input_string =>
v_locn_name,key_string => v_key_string);
v_raw_des := dbms_obfuscation_toolkit.desencrypt(input=> v_raw_name,key=>
v_raw_key);

LOCN_NAME_ENC
rcLGNr)1    Gz    sBJP|e_GK5cPx~S`

LOCN_NAME_RAW
72634C47CE722931891247907A0973C24A9D5001FCE5DF47CB356350787E53E0

          Followup:

curious how?

      Rating: 3
"Nice to Read"
      do you mean...  July 21, 2003
Reviewer:
      Peter  from UK
Scott

By "Nice to read" I suspect you mean in that the text is entirely composed of
'printable' characters for what ever display device you are using.

In your "curious" post the raw version is almost certainly the hexadecimal dump
of the encrypted string - the hex codes line up with the printable characters in
the other string!

If you use 'raw ' you are mapping the text on to a 16 character set - this can
be displayed on your PC or whatever and can be keyed in by a human with relative
ease, but it is twice as long as the the other version.

Personally, I can't see a problem with unprintable characters in an encrypted
string. If my app encrypts it and decrypts it I am happy - if nobody else can
read it I am even happier!

I agree with Tom - a hex stream is not easy to read either - I have to start
writing on my display with a marker pen ;-}

July 21, 2003 Susan -- Thanks for the question regarding
      "Automating purge statspack data", version 8.1.7

      You Asked
Dear Tom,

Is there a way to purge statspack data automatically? For example,purging two
weeks older data at 5pm, Monday-Friday?

Best Regards,
Susan Yuan

      and we said...
The only way I know to do that would be to review the sppurge.sql script in
$ORACLE_HOME/rdbms/admin and modify it to generate the snap ids older then two
weeks instead of asking you for a LOSNAPID and HISNAPID.

If you add this:

column min_snap_id new_val LoSnapId
column max_snap_id new_val HiSnapId
select min(s.snap_id) min_snap_id, max(s.snap_id) max_snap_id
  from stats$snapshot s
     , stats$database_instance di
 where s.dbid              = :dbid
   and di.dbid             = :dbid
   and s.instance_number   = :inst_num
   and di.instance_number  = :inst_num
   and di.startup_time     = s.startup_time
   and s.snap_time < sysdate-14;

right before:

--
--  Post warning

prompt
prompt
prompt Warning
prompt ~~~~~~~
prompt sppurge.sql deletes all snapshots ranging between the lower and
prompt upper bound Snapshot Id's specified, for the database instance
prompt you are connected to.
prompt
prompt You may wish to export this data before continuing.
prompt

and save that script as something ELSE (not sppurge.sql), then running that
script will automagically delete all snapshots older then two weeks old.  You
can add an EXIT at the end of this new script and then use OEM or your OS job
scheduler to schedule this script to run every friday.

Review &
      Followup

      Rating: 5
Thanks again,
      Tom. That's exactly what I need!  December 03, 2002

Reviewer:  Susan Yuan  from CA, USA

      Rating: 3
minor
      improvement  December 11, 2002
Reviewer:  Roman
      Podshivalov  from Sterling, VA
It would be more clean to add nvl function to
min and max like :

select nvl(min(s.snap_id), 0) min_snap_id, nvl(max(s.snap_id), 0) max_snap_id
from perfstat.stats$snapshot s, perfstat.stats$database_instance di....etc

If you have nothing to delete this wont generate any errors.

PS: adding schema name will allow to run this script
from any account with appropriate privs.

      Rating: 5
purging
      stats$sqltext  July 20, 2003
Reviewer:  Fan
      Zhang  from CA, USA
Tom,

  In sppurge.sql, purging stats$sqltext is commented out because of performance
issue.  What'd be the better solution to purge the table?

          Followup:

undelete it, if you want to remove it.

      Rating: 5
purging
      stats$sqltext   July 20, 2003
Reviewer:  Fan
      Zhang  from CA, USA
Tom,

  I think I didn't ask the question clearly. The SQL 'delete from stats$sqltext
...' will take a lot resource.  Do you think a PL/SQL with frequent commit will
be faster and taking less resource?  Thanks,

          Followup:

plsql with frequent commit will achieve this:

a) it will generate more redo
b) it will generate more undo
c) it will take longer

committing frequently runs slower, takes longer

      Rating:
      5
  October 06, 2003
Reviewer:
      A reader

July 21, 2003 Arup -- Thanks for the question regarding "How to
      find out how much rollback a session has to do", version 9.2

      You Asked
Hello Tom,

As always, thanks  a lot for accepting this question. When a session rolls back
for some error (typically, when the rollback segment or undo space cannot grow),
what is the best way to determine if it is rolling back and how much is left to
be rolled back. I use the query SELECT USED_UBLK FROM V$TRANSACTION to see how
much undo blocks the transaction has allocated to it's changes. If the figure
drops, then this is an indication that the transaction is rolling back. Is this
the preferred approach, or is there a better one?

Also, how do I know how many undo blocks the transaction had when it started the
rollback process, enabling the DBA to estimate how much longer the rollback will
continue.

Thanks in advance.

Arup

      and we said...
USED_UBLK is what you would use - yes.

You need not know how many blocks were used at the beginning of the rollback.
You simply need to know:

a) how many are used NOW  - used_now
b) in say 60 seconds how many are used - used_later

Now, just take (used_later / ( used_now - used_later )) and that is
approximately how many minutes you have left (as a swag -- if of course won't be
100% accurate, but it is a good guess)

So, if you have 500 used_ublks to start,
60 seconds later you have 400

you can guess that 400/(500-400) = 4minutes is the amount remaining....

Review &
      Followup

      Rating:
      5
  January 04, 2003
Reviewer:
      A reader
Tom,

What is the correct way to join v$transaction to v$session?

          Followup:

v$transaction.addr = v$session.taddr

      Rating: 3
Negative
      used_ublk  January 04, 2003
Reviewer:
      A reader
Hi Tom,

Sometimes I see negative used_ublk value in the v$transasction.
SQL text in v$sqlarea for this v$transaction entry  is just query.
What does it mean ?

Thanks

          Followup:

it means during a rollback they took away more than they put in generally.  It
is "harmless".  consider it zero.

      Rating: 5
What's the
      FLAG  January 05, 2003
Reviewer:  Arup Nanda
       from Norwalk, CT USA
Right on dot, as always. What's the purpose of the FLAG column in v$transaction?
I see that while rolling back, it's 7811 and an ongoing transaction has 7683.
What other values are possible and what do they indicate?

          Followup:

They are undocumented bit flags actually -- it is not the numbers 7811 and 7683
so much as the HEX:

ops$tkyte@ORA920> select to_char( 7811, '0000000X' ), to_char( 7683, '0000000X'
) from dual;

TO_CHAR(7 TO_CHAR(7
--------- ---------
 00001E83  00001E03
       ^         ^

0 indicates "normal user transaction"
8 indicates "rollback,most likely - means no more changes and you cannot commit"

      Rating:
      4
  January 05, 2003
Reviewer:
      A reader
Hi Tom,

There is this column in v$transaction and the definition is

SES_ADDR  RAW(4) User session object address

I got this value when from v$transaction.

test@oracle.com> select ses_addr from v$transaction;

SES_ADDR
--------
7B20A4E8

test@oracle.com> select  UTL_RAW.CAST_TO_VARCHAR2('7B20A4E8') from dual;

UTL_RAW.CAST_TO_VARCHAR2('7B20A4E8')
------------------------------------------------------
{ ¤è

Tom why is it displaying in that format.

Thanks

          Followup:

cast_to_varchar2 expects a raw.
you sent a string -- which happened to be hex -- so it converted to a 4byte raw

cast to varchar2 just changes the "type" field in the variable -- so now you
have a 4 byte varchar2

you could achieve a similar effect by:

select chr( to_number('7b','xx' ) ) ||
       chr( to_number('20','xx' ) ) || ....

from dual

you just took 4 bytes of RAW data and said "pretend it is a string, print it"

garbage in
garbage out

      Rating:
      3
  January 05, 2003
Reviewer:
      A reader
Tom,

Thanks for the feedback, but if i wanna know the name of object how can i get
that.

          Followup:

the name of "what" object?

v$transaction is about a transaction -- a transction typically involves MANY
database objects -- so it cannot be a database object.

If you mean the "session object", that is the row in v$session.

select v$session.*
  from v$session, v$transaction
 where v$session.saddr = v$transaction.ses_addr;

gets the "session" object.

      Rating:
      5
amazing  April 09, 2003
Reviewer:
      Gabriel  from Montreal, Canada
I looked all over the internet for a method to get the sql text of transactions
that are rolling back but this the only page where I found the correct
information (as usual). So just to check Tom:
select sql_text
from v$sqlarea
where address = (select sql_address from v$session where sid in (select SID
from v$transaction,dba_rollback_segs,v$session
where SADDR=SES_ADDR and
      XIDUSN=SEGMENT_ID and
      flag=7811));
would get me that text?

Thanks

          Followup:

7811 is not the only value that gives you 0x8 in the second byte ..

you need to look at that byte and see if it is 8

      Rating: 5
Check that
      individual bit.....  April 09, 2003
Reviewer:
      Mark J. Bobak  from Ann Arbor, MI
It's a bitmap, so, look at the two numbers in binary:
7811 = 0001 1110 1000 0011 = active transaction, no rollback
7683 = 0001 1110 0000 0011 = rollback in progress

Note that the numbers only differ by one bit.  Looking
at the numbers in decimal, it's not obvious, but in binary
it sure is.

So, Starting with the right, counting left, and starting
with the first bit on the right is bit 0, we can see that
bit 7 is the bit that identifies a transaction that's being
rolled back.  So, to check if bit 7 is set, you can write:
"where bitand(flag,power(2,7))<>0"

If you ever learn what the meaning of the other bits is,
you can check those too.  For example, if bit 28 is set,
that's a transaction with the SERIALIZABLE isolation level.

Hope that helps,

-Mark

      Rating: 4
good insight
      but doesn't solve the problem  April 17, 2003

Reviewer:  Gabriel  from Montreal, Canada
Hello,
I ran the modified sql statement as suggested above, but I received no results:

select sql_text from v$sqlarea where address in (select sql_address from
v$session where sid in (select SID from
v$transaction,dba_rollback_segs,v$session where SADDR=SES_ADDR and
                  XIDUSN=SEGMENT_ID and
bitand(flag,power(2,7))<>0));

returned no rows when ran repeatedly.

select value rlbk from v$sysstat where name='user rollbacks';
select value cmt from v$sysstat where name='user commits';

continued to show increasing values in both values.

Do you have any other suggestions as to how to find the transactions that roll
back? I tried using the Trace Analyzer, TRACNLYZR.sql, but that is not available
for 8.0.6. Do you know other packages that I can use on this 8.0.6?

Thank you very much for your effort and for your timely response,

          Followup:

did you know that you had transactions rolling back?

if so, you must know the session (cause you are doing it as a test).

if so, look at the flag and see what it is for that session, show it to us and
lets see what it says....

      Rating: 5
Hmm...works for
      me....  April 18, 2003
Reviewer:  Mark J.
      Bobak  from Ann Arbor, MI
Gabriel,

I just tested it, and it worked fine for me, but I used
a different SQL than you.  I'll include it below.  Also,
note that if it's a user rollback, then the 'current'
statement will be 'rollback', NOT the original DML, which
is probably not what you were looking for.  I tried
unsucessfully to use V$SESSION.PREV_SQL_ADDR and
V$SESSION.PREV_HASH_VALUE to get the original DML.  Not
sure why that didn't work.  Tom, any insights there?  Does
PREV_SQL_ADDR and PREV_HASH_VALUE not work the way I'm
expecting?

Here is my SQL, which works, but only displays 'rollback':
select sql_text
  from v$sqlarea vsa,
       v$session vs,
       v$transaction vt
 where vsa.address = vs.sql_address
   and vsa.hash_value = vs.sql_hash_value
   and vs.taddr = vt.addr
   and bitand(vt.flag,power(2,7))>0;

Hope that helps!

-Mark

      Rating:
      4
  April 22, 2003
Reviewer:  Gabriel
       from Montreal, Canada
This sql is created by an application that opens more than one session at a
time, therefore studying any one session won't help me much, I have to look at
all of them.
Hello I placed a job in my database when the transactions were created. This is
the text of the job that executes every minute:

insert into alternate_all_text
select sql_text,USERNAME,SID,SERIAL#,OSUSER,TERMINAL, flag,'all'
  from v$sqlarea vsa,
       v$session vs,
       v$transaction vt
 where vsa.address = vs.sql_address
   and vsa.hash_value = vs.sql_hash_value
   and vs.taddr = vt.addr;

insert into alternate_all_text
select sql_text,USERNAME,SID,SERIAL#,OSUSER,TERMINAL, flag,'rolling_back'
  from v$sqlarea vsa,
       v$session vs,
       v$transaction vt
 where vsa.address = vs.sql_address
   and vsa.hash_value = vs.sql_hash_value
   and vs.taddr = vt.addr
   and bitand(vt.flag,power(2,7))>0;

insert into all_text
select USERNAME,SID,SERIAL#,OSUSER,TERMINAL,SEGMENT_NAME, flag
from v$transaction,dba_rollback_segs,v$session
where SADDR=SES_ADDR and
      XIDUSN=SEGMENT_ID;

insert into trace_table(sqldate,text)
                         select sysdate,sql_text
                         from v$sqlarea
                         where address in (select sql_address
                               from v$session where sid in (select SID
                                    from
v$transaction,dba_rollback_segs,v$session
                                    where SADDR=SES_ADDR and
                                    XIDUSN=SEGMENT_ID and
                                    bitand(flag,power(2,7))<>0));
insert into trace_table(text, sqldate, user_rollback) select
'user_rollback_value',sysdate,value from v$sysstat where name='user rollbacks';
insert into trace_table(text, sqldate, user_commit) select
'user_commit_value',sysdate,value from v$sysstat where name='user commits';
commit;

Here is some data from the tables that I insert data into:

select distinct flag distinct_flag_all_text from all_text;
select count (*) count_all_alternate_all_text from alternate_all_text where
data_indicator='all';
select count (*) count_rback_alternate_all_text from alternate_all_text where
data_indicator='rolling_back';
select min(user_rollback) min_user_rollback from trace_table;
select max(user_rollback) max_user_rollback from trace_table;
select min (user_commit) min_user_commit from trace_table;
select max (user_commit) max_user_commit from trace_table;

DISTINCT_FLAG_ALL_TEXT
----------------------
                  7683
COUNT_ALL_ALTERNATE_ALL_TEXT
----------------------------
                          79
COUNT_RBACK_ALTERNATE_ALL_TEXT
------------------------------
                             0
MIN_USER_ROLLBACK
-----------------
              261
MAX_USER_ROLLBACK
-----------------
             3663
MIN_USER_COMMIT
---------------
           1022
MAX_USER_COMMIT
---------------
           3656

As you can see there are user rollbacks, and commits but the only flag captured
is 7683. Therefore, again, no rolling back transactions were captured by any
query. I received a suggestion to use the decreasing value of used_ublk from
v$transaction, but I don't see how in the context of this application.

Thank you very much,

          Followup:

????

      Rating: 2
Would you like
      to have more information?  April 23, 2003

Reviewer:  Gabriel  from Montreal, Canada
Hello Tom,
Would you like to have more information? If yes, what else would you need to
know? And what do you mean by '????'

Thank you,

          Followup:

I'm basically saying with the ????? that i have no idea what you might be after.

is there a question buried in that big list of facts?

      Rating: 2
Would you like
      to have more information?  April 23, 2003

Reviewer:  Gabriel  from Montreal, Canada
Hello Tom,
Would you like to have more information? If yes, what else would you need to
know? And what do you mean by '????'

Thank you,

      Rating: 2
User
      followup  April 30, 2003
Reviewer:  Gabriel
       from Montreal, Canada
Hello Tom

And thank you for your quick answers.
I am after the sql text of transactions that are performing rollback. The
application developpers use a tool to generate and manage the sql text
develloped, therefore they don't see the sql text. It is my task as a DBA to
find the text of the transactions that are rolling back.

With the facts that I sent I am trying to ilustrate the fact that although
rollback is occuring as illustrated by the increasing value in user rollback,
the transactions are not captured when using:

insert into alternate_all_text
select sql_text,USERNAME,SID,SERIAL#,OSUSER,TERMINAL, flag,'rolling_back'
  from v$sqlarea vsa,
       v$session vs,
       v$transaction vt
 where vsa.address = vs.sql_address
   and vsa.hash_value = vs.sql_hash_value
   and vs.taddr = vt.addr
   and bitand(vt.flag,power(2,7))>0;

As you can see the only flag captured for all transactions is 7683. Either this
method doesn't work or my implementation is wrong.

I intend to run another test.
I placed the following query on a job that executes every 20 secs:

insert into rlbk_used_ublk
SELECT s.sid, s.sql_address, s.serial#, s.username, s.program,
        t.used_ublk, t.used_urec, sysdate
        FROM v$session s, v$transaction t
        WHERE s.taddr = t.addr;

commit;

insert into rlbk_text select sql_text, address
from v$sqlarea where address in (select sql_address from rlbk_used_ublk);

commit;

I intend to look at:

s.sid, s.sql_address, s.serial#, s.username

where the value of used_ublk is decreasing and then look for the address of all
sql executed during the test and get the text this way. This seems to be a very
labour intensive way of solving my problem of finding the text of sql that is
rolling back.

Additional information:

The application opens several sessions and connects using multiple users to
complete the work.

My question is:

Can you think of any other way of capturing the text of sql that is rolling
back?

          Followup:

a transaction is made up of many statements

there is no single "statement" that is rolling back.  You cannot get the sql
they used in that "transaction".  you see nothing because of your joins, there
isn't anything to join to necessarily.

I love those tools that "protect" those poor developers from the dreaded SQL
thing -- i understand it is really far too complex....

awesome that it opens several sessions -- that is very transactional of it.  so
scalable as well (not).

I feel sorry for you -- really I do.  This needs to be fixed in the application
-- the application needs to add a bit of instrumentation (ala
dbms_application_info.set_client_info perhaps) to allow you to see what ACTION
they are performing.

Why is it your task as DBA?  It might be your task to alert the developers to a
performance issue (rolling back) but it would be the development staffs job to
fix their BUG wouldn't it.

      Rating: 5
Thank you very
      much  April 30, 2003
Reviewer:  Gabriel
       from Montreal, Canada
Thank you very much,

Very good insight.

      Rating: 4
how much
      rollback do I need?  May 01, 2003
Reviewer:
      A reader
Hi

Is there a way to know how much rollback do I need for a transaction?
If we must run it in order to know it, is it possible to extrapolate? For
example if I have a massive update of 1 million rows, is there a way such as

run the update
check how many rows have been updated then from there get the used_ublk value so
if we updated 100000 rows and used 1000 blocks we can say 1 million rows more or
less needs 10000 blocks and kill the transaction? Or even better create an empty
copy of original table, insert 100000 rows there and run the update totally and
extrapolate from there...?

Or is this totally out of question :?

          Followup:

if I had a massive update of a million rows - I would give serious consideration
to this:

ASSUMING original table is partitioned:

create table new_table
as
select <modified_rows> in parallel with nologging

create indexes in parallel/nologging

alter table old_table exchange the partition with this new table
drop new table;

ASSUMING original table is not partitioned

create table new_table
as
select <modified_rows> in parallel with nologging

create indexes in parallel/nologging

grant on new table
drop old table
rename new table to old table

vs trying to update and maintain the indexes (which might tend to be the
largest, and most unpredicable generator of UNDO and REDO)...

Extrapolation is a "maybe" idea.  maybe it'll work, maybe it won't.  depends on
how bad the indexes get hit. If the table has no indexes (or the columns being
modified are not indexed), extrapolation should be workable.  Indexes are the
unknown variant here.

      Rating: 5
How about a
      null taddr?  June 12, 2003
Reviewer:  Doug
       from CT, USA
I'm trying to drop a schema that is "currently connected" even though all the
sessions connected to it are marked for "KILL".  I figured I'd look for things
rolling back but the taddr is null in the v$sesion columns.  Why would that be?

          Followup:

we are waiting for the client application to do something in the database so we
can report back to it "i'm sorry, you are dead, you were killed", rather then
just report back "ora-3113 eof on communication channel" which most people would
interpret as "bug"

      Rating:
      4
BITAND(VT.FLAG,POWER(2,7)) = 0  June 28, 2003

Reviewer:  Doug  from CT, USA
I have a flag
4201987 which shows a statement which is a huge select for update statement.
The session, marked for kill, is holding about 13 different locks, many of them
TOs on temp tables.
If this transaction is not rolling back, what is it doing? (it's been this way
for an hour).. An online index creation on the same table appears to blocked on
an enqueue waiting for it.

          Followup:

well, it is probably too late now, but did v$session_event have anything
interesting? was the back end chugging away or just sitting there?

      Rating: 4
Too late - but
      how about next time -   June 29, 2003
Reviewer:
      Doug  from CT, USA
Well - as you mentioned - this situation has resolved itself.  I took the nasty
route of killing the dedicated server process to speed things along.  I know you
hate that.  But.. let me prepare for next time.  Basically what I noticed was
that in v$transaction, the USED_UBLK was gradually going up from 20 - 90 over
the course of an hour.  The java app was doing absolutely nothing, and there
were only 3 connections to the entire database.  Mine, the one that was killed,
and the one trying to build the index.  I didn't think to look at session event.
 What would you suspect?  Thanks.. Dc.

      Rating: 5
What about a
      session killed?  July 21, 2003
Reviewer:  Michel
      CADOT  from France
Hi Tom,

When a session is killed, taddr in v$session is "set" to null and the row in the
v$transaction is deleted.
So, how to follow the rollback make for this session?
In addition, who executes this rollback? The server process (dedicated or
shared) or a background process?

Thanks
Michel

          Followup:

that has not been my experience.

      Rating: 3
Complement to
      my previous message  July 22, 2003
Reviewer:
      Michel CADOT  from France
Hi Tom,

I try it on my laptop (Windows NT4, Oracle8iR3).

Session 1: TEST
----------

20:03:13 mike=MIKE/ILLUVATAR TEST>update guichets set c_code_postal='00000';

26295 rows updated.

Elapsed: 00:01:39.52
20:04:52 mike=MIKE/ILLUVATAR TEST>
20:04:53 mike=MIKE/ILLUVATAR TEST>rollback;

Rollback complete.

Elapsed: 00:01:00.47
20:05:54 mike=MIKE/ILLUVATAR TEST>
20:06:11 mike=MIKE/ILLUVATAR TEST>update guichets set c_code_postal='00000';

26295 rows updated.

Elapsed: 00:01:18.53
20:07:34 mike=MIKE/ILLUVATAR TEST>
20:07:59 mike=MIKE/ILLUVATAR TEST>select * from dual;
select * from dual
*
ERROR at line 1:
ORA-00028: your session has been killed

Elapsed: 00:00:00.71
20:08:05 mike=MIKE/ILLUVATAR TEST>

Session 2: SYSTEM
----------

20:07:43 =MIKE/ILLUVATAR SYSTEM>select s.sid, s.username,
20:07:43   2         t.addr tx, t.start_time, t.status, t.start_scnb,
20:07:43   3         decode(t.noundo, 'YES', 'No RB', nvl(r.name, 'No RB'))
rollback,
20:07:43   4         round(rs.rssize/1024) rssize,
20:07:43   5
decode(substr(to_char(t.flag,'fm0000000X'),7,1),'8','YES',NULL) rollbacking
20:07:43   6  from v$rollstat rs, v$rollname r, v$session s, v$transaction t
20:07:43   7  where r.usn (+) = t.xidusn
20:07:43   8    and rs.usn (+) = t.xidusn
20:07:43   9    and s.saddr = t.ses_addr
20:07:44  10  order by 7, 6, 1
20:07:44  11  /
    Sid Utilisateur     Tx Addr  Tx Start Time        Tx Status             Tx
SCN Rollback     Taille (Ko) RB?
------- --------------- -------- -------------------- ----------------
----------- ----------- ------------ ---
     11 TEST            02BF2448 07/21/03 20:06:12    ACTIVE
6449420 RS_1               2,516

1 row selected.

Elapsed: 00:00:00.00
20:07:44 =MIKE/ILLUVATAR SYSTEM>select sid, serial#, username, taddr from
v$session where sid=11;
    Sid    SERIAL# Utilisateur     TADDR
------- ---------- --------------- --------
     11        142 TEST            02BF2448

1 row selected.

Elapsed: 00:00:00.10
20:07:44 =MIKE/ILLUVATAR SYSTEM>select * from v$transaction;
ADDR         XIDUSN    XIDSLOT     XIDSQN     UBAFIL     UBABLK     UBASQN
UBAREC Tx Status
-------- ---------- ---------- ---------- ---------- ---------- ----------
---------- ----------------
Tx Start Time             Tx SCN START_SCNW START_UEXT START_UBAFIL START_UBABLK
START_UBASQN START_UBAREC SES_ADDR
-------------------- ----------- ---------- ---------- ------------ ------------
------------ ------------ --------
      FLAG SPA REC NOU PTX PRV_XIDUSN PRV_XIDSLT PRV_XIDSQN PTX_XIDUSN
PTX_XIDSLT PTX_XIDSQN     DSCN-B     DSCN-W
---------- --- --- --- --- ---------- ---------- ---------- ----------
---------- ---------- ---------- ----------
 USED_UBLK  USED_UREC     LOG_IO     PHY_IO     CR_GET  CR_CHANGE
---------- ---------- ---------- ---------- ---------- ----------
02BF2448          1         33         29          3       6046         81
   1 ACTIVE
07/21/03 20:06:12        6449420          0          0            3            5
          64            1 02A8B7B4
      7683 NO  NO  NO  NO           0          0          0          0
0          0    6447653          0
       612      26295      81029        534        969         14

1 row selected.

Elapsed: 00:00:00.30
20:07:45 =MIKE/ILLUVATAR SYSTEM>alter system kill session '11,142';

System altered.

Elapsed: 00:00:00.80
20:07:45 =MIKE/ILLUVATAR SYSTEM>select s.sid, s.username,
20:07:45   2         t.addr tx, t.start_time, t.status, t.start_scnb,
20:07:46   3         decode(t.noundo, 'YES', 'No RB', nvl(r.name, 'No RB'))
rollback,
20:07:47   4         round(rs.rssize/1024) rssize,
20:07:47   5
decode(substr(to_char(t.flag,'fm0000000X'),7,1),'8','YES',NULL) rollbacking
20:07:48   6  from v$rollstat rs, v$rollname r, v$session s, v$transaction t
20:07:49   7  where r.usn (+) = t.xidusn
20:07:50   8    and rs.usn (+) = t.xidusn
20:07:50   9    and s.saddr = t.ses_addr
20:07:51  10  order by 7, 6, 1
20:07:52  11  /

no rows selected

Elapsed: 00:00:00.81
20:07:52 =MIKE/ILLUVATAR SYSTEM>select sid, serial#, username, taddr from
v$session where sid=11;
    Sid    SERIAL# Utilisateur     TADDR
------- ---------- --------------- --------
     11        142 TEST

1 row selected.

Elapsed: 00:00:00.71
20:07:53 =MIKE/ILLUVATAR SYSTEM>select * from v$transaction;

no rows selected

Elapsed: 00:00:00.41
20:07:55 =MIKE/ILLUVATAR SYSTEM>

Then during about a minute the process oracle.exe takes 75% of CPU and my disk
works.
As you can see, I killed the TEST session after the second "update" and just
after "alter system kill session", taddr in v$session is null and the row in
v$transaction no more exists.

Thanks for your help
Michel

      Rating: 3
Remaining time
      estimation  October 28, 2003
Reviewer:  Roberto
      Zanfrini  from IT
<predelta.sql>
DEFINE p_sid=   &1
DEFINE curtime= TO_NUMBER(NULL)
DEFINE curblks= TO_NUMBER(NULL)
DEFINE usdblks= GREATEST(used_ublk,0)

COLUMN curtime     NEW_VALUE curtime  NOPRINT
COLUMN dlttime                        NOPRINT
COLUMN curblks     NEW_VALUE curblks  NOPRINT
COLUMN dltblks                        NOPRINT
COLUMN "Blks/Sec"  FORMAT 999,990.0

<delta.sql>
SELECT
  ((Sysdate-TRUNC(Sysdate))*86400) curtime,
  ((Sysdate-TRUNC(Sysdate))*86400)-&curtime dlttime,
  &usdblks curblks,
  &usdblks "Blks",
  ((&curblks-&usdblks)) dltblks,
  (&curblks-&usdblks)/(((Sysdate-TRUNC(Sysdate))*86400)-&curtime) "Blks/Sec",
  TO_CHAR(
   TRUNC(Sysdate)+
   (DECODE(&curblks-&usdblks, 0, TO_NUMBER(NULL),
    &usdblks/((&curblks-&usdblks)/
     (((Sysdate-TRUNC(Sysdate))*86400)-&curtime)))/86400),
   'mi:ss') " Time"
 FROM v_transaction t, v$session s
 WHERE s.sid=&p_sid AND s.taddr=t.addr;

SQL> @predelta
SQL> @delta

      Blks   Blks/Sec  Time
---------- ---------- -----
        75

SQL> @delta

      Blks   Blks/Sec  Time
---------- ---------- -----
        70        0.4 02:48

SQL> @delta

      Blks   Blks/Sec  Time
---------- ---------- -----
        60        1.0 01:00

etc...

July 21, 2003 Sanjay -- Thanks for the question regarding
      "Writing a procedure in the declaration section of another procedure",
      version 8.0.5

      You Asked
Tom,
Can we write a stored procedure within the declaration
section of another procedure ? If we can, then from a
programming standpoint what is its significance ? I
recently came across some code where the developer
had written a procedure in the declare section of another proc.
i.e
procedure show_report
is
  procedure do_break_columns
  is
  begin
      -- some code
  end;
  procedure do_sum_break_columns
  is
  begin
  end;
  l_tot_rows ...
begin
   //do some stuff here
end;
This was the first time that I came across such a construct
,and hence the question
Thanks
Sanjay

      and we said...
It is a method of encapsulation.  I would use a package myself
and put SHOW_REPORT in the package specification (allowing
others to call it) but having do_break_columns and
do_sum_break_columns in the body as "hidden" functions.

In a standalone function/procedure like this, the only way to
hide this repetitive logic is to declare these "mini"
procedures.  The programmer realized that they had some code
they did over and over and didn't want to inline that code
everywhere in the procedure -- rather they put it in one
location and used it over and over.  I do it myself frequently
-- for example I have an execute immediate from v8.0 and before
I use that looks like:

create or replace procedure execute_immediate( p_sql in
varchar2,
     p_name1 in varchar2 default NULL, p_val1 in out varchar2,
     p_name2 in varchar2 default NULL, p_val2 in out varchar2,
     p_name3 in varchar2 default NULL, p_val3 in out varchar2,
     p_name4 in varchar2 default NULL, p_val4 in out varchar2)
is
    cursor_name     pls_integer default dbms_sql.open_cursor;
    ignore          pls_integer;

    procedure bind( n in varchar2, v in varchar2 )
    is begin if ( n is not null ) then
                dbms_sql.bind_variable( cursor_name, n, v, 255
);
             end if;
    end;
    procedure val( n in varchar2, v out varchar2 )
    is begin if ( n is not null ) then
                dbms_sql.variable_value( cursor_name, n, v );
             end if;
    end;
BEGIN
    dbms_sql.parse(cursor_name, p_sql, dbms_sql.native);
    bind( p_name1, p_val1 );
    bind( p_name2, p_val2 );
    bind( p_name3, p_val3 );
    bind( p_name4, p_val4 );
    ignore := dbms_sql.execute(cursor_name);
    val( p_name1, p_val1 );
    val( p_name2, p_val2 );
    val( p_name3, p_val3 );
    val( p_name4, p_val4 );
    dbms_sql.close_cursor(cursor_name);
END;
/

I could have put the code found in bind and val INLINE but I
found that to be tedious at best -- it was much easier to write
the 3 lines of code once and be done with it.  Notice how the
procedures in the procedure can see not only their variables but
all of the local, in scope variables as well (eg: i did not pass
cursor_name -- it can just see it).

Review &
      Followup

      Rating: 3
Guidlines on
      scope of procedures/variables.  November 04, 2002

Reviewer:  Kashif  from Houston, TX
Hi Tom,

Are there are any guidelines on the scope of procedures/variables i.e. when and
where should one declare them? For example, when is declaring a global
procedure/varaible optimal, and when is declaring a local procedure/variable
optimal, assuming I have the choice of declaring them anywhere? Any performance
considerations here? Is it true that global procedures/variables occupy more
space in memory?

I have read in some texts that unless there is the need to declare
variables/procedures, they should not be declared 'statically', but rather be
declared conditionally, e.g.

if criteria_are_met then
 declare
 var1 varchar2 (10);
 ...

Is this a good practice?

Is declaring procedure A within procedure Z different from declaring procedure A
outside procedure Z, in the same package? How?

Finally, if one has the option of declaring procedures/functions/variables in
the package spec versus in the package body, where should they be declared?

TIA.

Kashif

          Followup:

you only declare variables as global when they need to be -- their very usage
dictates that they must persist from call to call.

Otherwise, they should be local.

Just like in any programming language.  globals are to be avoided unless they
are necessary.

The other thing -- about having multiple nested levels:

  if criteria_are_met then
    declare
      var1 varchar2 (10);
    ...

is one I never have ascribed to for the simple reason that I never nest blocks
like that in any language.  Rather, I move that out into a subroutine
(modularize the code).  I like to keep my routines on a screen -- so I can see
the TOP of the routine and the BOTTOM of the routine at a glance.  Nested blocks
would pretty much obviate that -- so I make them routines which pushes their
variables out of the current routine all together.

declaring procedure a within procedure z limits the scope of A to Z.  Take that
block from above -- if it were not REUSABLE, no other routine would ever call
it, you might consider putting it in Z to limit it's scope.  Else, put it
outside of Z to allow others to reuse it.

You only put in the spec that which OTHERS outside of the package could
reasonably want to call.  You publish those functions/procedures that you want
exposed -- no more, no less.

      Rating: 3
is global
      really global  July 21, 2003
Reviewer:  joachim
      Mayer  from austria / europe
Hi Tom,

thx for the help you are giving programmers out there. I saw this note on global
variables from you but I am still confused because oracle seems to hide them
somehow. I will show you my example

I create a simple package with contains a variable in the header-section. Should
be global I belive.

mwstat@KXXL> create package my_pack as
  2  var constant number := 3;
  3  end;
  4  /

Package created.

mwstat@KXXL> show error
No errors.

And I can use this value var in any other package or procedure like this

mwstat@KXXL> r
  1  begin
  2  dbms_output.put_line('var:'|| my_pack.var);
  3* end;
var:3

PL/SQL procedure successfully completed.

But when I try to use my_pack.var in a view or a query I get an error

mwstat@KXXL> select * from transport_order where order_id = my_pack.var;
select * from transport_order where order_id = my_pack.var
                                               *
ERROR at line 1:
ORA-06553: PLS-221: 'VAR' is not a procedure or is undefined

I was always in the opinion the variables defined in the header are global and
global means anyone can see and use them. Does this exclude queries in oracle or
where am I mistaken?

I have about 100 constants to work with and I had the plan to put then in a
constant-package, in the header so all the other programmers may use them
instead of using plain numbers which leads to problems quite often. Is this a
good idea or would you counsel to do it different?

Thx in adavance

hannibal

          Followup:

It is GLOBAL in PL/SQL, "sql" cannot "see" it

Think of PLSQL as a layer on top of SQL.  PLSQL can see sql, PLSQL can pass
things to SQL, SQL knows not too much about PLSQL.

If the SQL is executed in the context of PLSQL,eg:

begin
  for x in ( select * from transport_order where order_id = my_pack.var )
  loop
     ....

it'll work just find, cause PLSQL will see my_pack.var and "bind" it for us.

You can either:

o do all sql in plsql (my response, it is what I do) and keep it in the package
o use an application context and have the programmers call a "init" function to
warm up their session cache (an on-login trigger could do it as well)  then you
can

   select * from t where order_id = sys_context( 'my_pack', 'var' );

o use a table and code:

   select * from t where order_id =
     ( select value from lookups where variable = 'VAR' and rownum = 1 );

they are in the order of my preference.

      Rating: 4
i see ...
        July 22, 2003
Reviewer:  joachim mayer
       from austria / europe
anything clear... thx tom.

hannibal

July 21, 2003 John -- Thanks for the question regarding
      "Determine whether update is coming from trigger", version 9.0.1

      You Asked
Hi Tom.  I have 3 tables that form a hierarchy.  They each contain costs that
are cumulative at each level, and update each other via triggers and a state
package (the way you describe at
http://osi.oracle.com/~tkyte/Mutate/index.html
.  My problem is that I am unable to
determine whether an update is coming from one of the other triggers, or from a
user directly.  Here is the way I want it to work:

User updates cost on table 2 -> trigger 2 updates costs on tables 1 and 3.

User updates cost on table 1 -> trigger 1 updates costs on tables 2 -> trigger 2
updates costs on table 3.

etc.

However, without knowing whether the update comes from a user, I wind up in an
infinite recursion.  Any guidance you can offer will be greatly appreciated.
Thanks, John

      and we said...
get who_called_me from

http://asktom.oracle.com/~tkyte/who_called_me/index.html
and then you can do the following:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t2 ( x int );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create trigger t_trigger
  2  before UPDATE on t
  3  declare
  4      l_owner varchar2(30);
  5      l_name  varchar2(30);
  6      l_lineno number;
  7      l_type  varchar2(30);
  8  begin
  9      who_called_me( l_owner, l_name, l_lineno, l_type );
 10
 11      dbms_output.put_line
 12      ( 'trigger fired due to ' || l_owner || '.' || l_name ||
 13        ' from line ' || l_lineno || ' and was type ' || l_type );
 14  end;
 15  /

Trigger created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace trigger t2_trigger
  2  before insert on t2 for each row
  3  begin
  4      update t set x = x where x = :new.x;
  5  end;
  6  /
Trigger created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure p
  2  as
  3  begin
  4      update t set x = 1;
  5  end;
  6  /
Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1 );
1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> update t set x = 1;
trigger fired due to . from line  and was type

1 row updated.

top level sql statement -- no info (type is null)

ops$tkyte@ORA817DEV.US.ORACLE.COM> begin update t set x = 1; end;
  2  /
trigger fired due to . from line 1 and was type ANONYMOUS BLOCK

anonymous block -- you get the line number and type

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec p
trigger fired due to OPS$TKYTE.P from line 4 and was type PROCEDURE

PL/SQL procedure successfully completed.

stored procedure/package/function -- you get the object name, line and
type..

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t2 values ( 1 );
trigger fired due to OPS$TKYTE.T2_TRIGGER from line 2 and was type TRIGGER

1 row created.

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

and same for trigger

Also, look at:

http://technet.oracle.com/docs/products/oracle9i/doc_library/901_doc/appdev.901/a88876/adg14evt.htm#1000872
for all kinds of cool bits of data you can get in your trigger, specially
ora_sql_txt

Review &
      Followup

      Rating: 5
Exactly what I
      needed - Thanks  August 09, 2002
Reviewer:  John
      Murphy  from Vienna, VA USA

      Rating: 5
Change in
      9.2.0.3?  June 11, 2003
Reviewer:  Cory
      Brooks  from Chapel Hill, NC
When trying to use who_called_me, I got an error on the following line:
lineno := to_number(substr( line, 13, 6 ));
After examining the contents of line, I changed 13 to 15 and it worked fine.
Should the source be changed as I'm sure many others will find it useful or is
there something funny about my installation.  Thanks,

      Rating: 4
Also get this
      error  July 21, 2003
Reviewer:  Robert Boyle
       from Ireland
I also have got a problem with the line number, this was ok on my windows
9.2.0.1 install but not on my hp install which is also 9.2.0.1

Tom, is there a reason for this error and change?

Cheers
R.

          Followup:

the size of the call stack seems to vary from port to port, you might need to
adjust a substr or two, throw in a couple of dbms_output's and see whats
happening.

      Rating: 5
Thanks
      Tom  July 22, 2003
Reviewer:  Robert Boyle
       from Ireland

      Rating: 4
Alternative
      approach  July 22, 2003
Reviewer:  andrew
       from ca, usa
An alternative approach I've used (if you have the freedom to change the code)
is to use a global variable in a package as a flag. When the user updates the
record through an API (package), the API sets something like g_api_access:=true
(the default is false). When the trigger fires, it looks at the package flag. I
find this useful for doing batch updates to data when you don't want trigger
logic to fire.

July 21, 2003 Mark -- Thanks for the question regarding "Number
      of extents", version 8.1.7

      You Asked
Hi Tom,

I am having a debate with a co-worker (who is also a DBA) in regard to the silly
myth (in my opinion) that having more than one extent is a bad thing.  My
reasoning is as follows:

So far as I know, there are only two ways Oracle accesses data in a table --
full table scan or by rowid.  The manner in which the rowid is obtained is
irrelevant for the purpose at hand.

For the case of a full table scan, not much needs to be said - every block in
the table is going to be visited.  It makes no difference how many extents there
are or are not.

For the case of access by rowid, the rowid id composed of object id, file id,
block id, and slot id.  Since extent is not included in the rowid, my feeling is
that extents don't matter.

I use locally managed tablespaces, and some of my tables have (now I hope you
are sitting down for this) around 200 extents.  My co-worker is aghast that I
don't care about this and am not bending over backwards to cram everything into
one extent.

Have I been breathing too little oxygen or am I on track with my thinking?

Thanks much,

Mark

      and we said...
see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:421419463648
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:228413960506
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:946829723249
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1092233274210
I'm 100% with you.  1 extent or 1,000 -- WHO CARES.  especially with locally
managed tablespaces where even the DDL argument some have goes away.

Ask your coworker for the scientific reasoning behind their fear.  I'd be glad
to alieviate their concerns.

followup to second comment

Oracle reads db_file_multiblock_read_count blocks at a time.  As long as your
extents are multiples of this (that is usually about 64k -- pick some multiple
of 64k) -- you'll incurr no overhead.

As for the "seek time" -- you assume that an extent is contigous on disk.  They
are not.  When I ask a file system for 100meg of space -- it does not give me
100meg of contigous space, it gives me space from here, there, everything (its
been a loooonngggg time since you had to use CCF (create contigous file) to
create a database file!).  Even if everything was in a single extent -- its
still ALL OVER the disk.   Add in volume managers, raid arrays, etc etc etc --
your data is no where NEAR contigous.

Even if it was, that would only make a difference in a single user system!  do
you really think the heads will remain positioned where you left them after a
read?  As soon as you are done reading, the OS is off servicing some other read
-- thats why controllers and such have their own CPU's in effect.  There is
perhaps a 0.00001% chance the head will be where you left it in a multi-user
system.

Another URL to check out on this would be:

http://technet.oracle.com/deploy/availability/pdf/defrag.pdf
"HOW TO STOP DEFRAGMENTING AND START LIVING: THE DEFINITIVE WORD ON
FRAGMENTATION"

I agree with their advice "2.1.4 Monitor and Potentially Relocate Segments
Having More Than 1024 Extents"

(4096 being the max suggested).....

followup to comment three below

the extent entries are in uet$/fet$ -- real tables with indexes (cluster key
indexed)  or in the bitmap of a locally managed tablespace -- not in a x$ table.
 It'll not affect the parse time of a query.

followup to comment four below

OPERATING SYSTEM FILES are not contigous on disk, that is my point.  When Oracle
allocates a 100meg data file - it does not get 100meg of contigous disk space.
This is why we have disk defragmentors and the like on windows (which is really
bad when your disk gets excessively fragmented).

An extent is a LOGICALLY contigous set of blocks -- this is true.

PHYSICALLY, on the disk, an extent is not contigous.  File systems dole out
space in tiny chunks.  They give out space as they find it.  They do not
allocate contigous space.

Raw partitions -- this might be different.  But then again the multi-user
argument kicks in.  The disk heads will NEVER be where you left them.
Additionally, even most raw partitions today are really logical volumes so the
whole disk head thing goes out the window even in a single user system.

followup to comment 5 below

An extent is a physical concept -- it is a unit of allocation.

multi-block read count is a physical concept as well -- it is a unit of read.
If you use a multi-block read count that is a multiple of 2k (64k being very
very popular) you cannot go wrong.  It would be very hard to have an extent size
that was not an even multiple of it.  Using 64k for multi-block reads lets you
use any 64k multiple extent size (64k, 1m, 5m, 10m, 100m and so on).

As I have tried to say over and over in response to comments like:

"However, isn't it still better to try to allocate the DB files in contiguous
blocks rather than have them scattered all over the disk? "

I have two things to say about that:

1) file systems do not give you contigous space.  If you ask for 1m of space
the file system will give you a whole bunch of chunks of disk spread out all
over the place.  You do not get 1m of contigous space in general.  You get 1meg
of space.

2) In a multi-user system, when many read/write requests, the probability that
the read heads are positioned where you left them is 0%.  It would not matter
if the disk space was contigous -- the heads have moved since your last read
request.

Yes, if you have a table that is 'hot', segregating it -- to spread IO out --
would be good.  does it have to be by itself?  No, not necessarily.  The overall
system should have evenly distributed IO.  You might find you can achieve this
by moving this table somewhere -- or you might find you can do this by using
raid striping or some other technology.

followup to comment 7 by Connor below..

True, raw for logs is cool but I've found that I can get most of the bang for
the buck by mounting the filesystems in direct mode if supported.

For exmaple, on solaris, I mount my database disks (which have nothing but
database on them thank you very much) like this:

/dev/dsk/c2t2d0s0  /dev/rdsk/c2t2d0s0   /d01     ufs    3   yes   forcedirectio
/dev/dsk/c2t1d0s4  /dev/rdsk/c2t1d0s4   /d02     ufs    3   yes   forcedirectio
/dev/dsk/c3t1d0s4  /dev/rdsk/c3t1d0s4   /d03     ufs    3   yes   forcedirectio
/dev/dsk/c0t3d0s4  /dev/rdsk/c0t3d0s4   /log1    ufs    3   yes   forcedirectio
/dev/dsk/c3t2d0s4  /dev/rdsk/c3t2d0s4   /log2    ufs    3   yes   forcedirectio
/dev/dsk/c3t0d0s3  /dev/rdsk/c3t0d0s3   /arch1   ufs    3   yes   forcedirectio
/dev/dsk/c0t1d0s3  /dev/rdsk/c0t1d0s3   /arch2   ufs    3   yes   forcedirectio

that forcedirectio is night and day difference for me, bypasses that nasty unix
buffer cache...  I've never tested raw vs directio -- but i do know that
directio over "non-directio" is huge.

follow up to comment 8 from Tom Sullivan

Hopefully we can agree that if you need to allocate AN extent, the overhead will
not be noticable to us humans...

If you are doing a mass insert, a bulk load, you may notice some measurable
overhead for the extent allocation -- however -- i would put this in the
"abnormal case" category.  You can pre-allocate the extents if you like (if you
know how much you are loading).  Again the overhead would be miniscule as
compared to the overall load itself.

If you do this mass load more then once -- you should consider using a larger
extent size then yes, 1,000 or so extents is OK, going over that might be
considered going over board.  See the recommandations in the "start living" link
above.  Its a pretty good read.  It suggests you size the extents for objects in
a tablespace by their ultimate size -- hence this would not really happen
(unless you mess up ;)

Review &
      Followup

      Rating:
      4
  June 06, 2001
Reviewer:  Tony
      Sangvacharakul  from Kansas City, KS
I like the way when you responded to the question and attached links that
related to the similar situation in the question.  Reading the attached links
expanded the knowledge of topic we are concerning.

Tony

      Rating: 2
Number of
      extents  June 06, 2001
Reviewer:  Mike Pope
       from Brooklyn, NY USA
Isn't there a difference between a full table scan of one extent vs many, with
the possibility of scattered extents causing increased seek time? Or is this
another of my closely-held truths being de-bunked?

Or how about this one? If the extent size is not coordinated with
db_file_multiblock-read_count, you will have an 'inefficient' full table scan?

Sorry, but it's just hard for an old-timer like me to grasp this conceptually...

      Rating: 2
Number of
      extents  June 07, 2001
Reviewer:  AKP
       from Reston, VA USA
I can only guess that the only time this becomes a problem is when there are
lots of tables with many extents.
When the database parses and executes a query it has to access base
tables(x$tables) to identify the extents, which are part of the table being
accessed.  In doing so, these base tables are being FTScanned (no indexes on
these base tables).  When you have thousands of tables and they all have 100's
of extents then it may cause some performance issues.
Which means that the access performed implicitly by the database during parse
and execution of statement will impact performance not the actual statement be
executed.
Oh! by the way if you use PQO, having lots of extents can often be of a benefit
where 1 extent doesn't take advantage of PQO.

Thanks...

      Rating:
      2
  June 09, 2001
Reviewer:
      A reader
"you assume that an extent is contigous on disk.  They
    are not."
I beleive it is true for 'an extent is contigous on disk' and segment may not.
That is why we may have segment fragmentation not extent fragmentation. I may
wrong, if so, please show example. Thanks

      Rating: 3
Number of
      extents...  June 12, 2001
Reviewer:  Mike
      Pope  from Brookly, NY, USA
Since 'extent size' and 'db_file_multiblock_read_count' are only logical
concepts, it appears we lose some control is making these specs!

However, isn't it still better to try to allocate the DB files in contiguous
blocks rather than have them scattered all over the disk? Or does the hardware
compensate?

And if you had a large table that required frequent scans wouldn't it be better
to segregate to its own (or infrequently accessed) disk to keep that seek time
down?

Just trying to understand. Is the hardware that makes all of these concepts
obsolete in the end?

      Rating: 5
PDF is
      Excellent  June 14, 2001
Reviewer:  KP
       from India
The PDF Suggested by u is excellent. This sorts of attachments really helps.

      Rating: 5
But....(just to
      be pedantic)  July 19, 2001
Reviewer:
      Connor  from UK
The only exception to this argument is the nice gains you can get by putting raw
redo logs on their disk - Nothing else moves the head and your logs scream
along...The bummer with this is nowadays you're putting 200m logs on a 35G disk
- hard to convince the bean counters on that one.

      Rating: 5
OK, Tom,
      but...  July 24, 2001
Reviewer:  Tom
      Sullivan  from NY USA
...isn't it true that there is considerable overhead involved in CREATING
extents? So, if you define your next extent too small and are inserting a large
number of rows into the table, won't it slow it down to have to keep stopping to
create new extents?

      Rating: 5
re: number of
      extents  July 24, 2001
Reviewer:  Mark
       from Indianapolis, IN USA
Well, it looks like my original question has generated a few thoughts, so I
wanted to take a second to put in my $0.02...

Keep in mind that I am using *only* Locally Managed Tablespaces (except for
system, of course) with UNIFORM allocation (I can't find a good use for
AUTOALLOCATE).  I notice no appreciable performance decrease during a "full"
load where lots of extents are being allocated/deallocated.  For example, I have
one table that during a "full" load process grows to 2,500 extents.  (This is
the extreme, most tables are between 1 and 200 extents.)  It only stays at that
number of extents for an hour or so, as another process comes along and removes
the records shortly after being loaded into this "staging" table.  (This is
third-party app, by the way, so I have no control over the way it works.
bummer.)  Could I move that table to a tablespace with a bigger extent size to
keep it from growing to 2,500 extents?  Sure.  Should I?  Not as far as I am
concerned - it is only one table, and it is not worth it to me.  This process
happens once a month.  I have timed the process with a dictionary managed
tablespace using 1 extent versus the LMT and the 2500 extents.  The LMT is
usually very slightly faster on average.  Remember that since these are LMT's,
Oracle does not have to access sys.uet$ and sys.fet$ for extent
allocation/deallocation (which would be protected by undo and redo log
generation, if I am not mistaken).  It only has to read through the bitmap in
the datafile and set a bit accordingly.  Besides, most accesses to that table
are full table scans, and the number of blocks in the table are the same
regardless of the number of extents.

Anyway, the biggest benefit for me is that I can not have any tablespace free
space fragmentation.  period.  With a dictionary managed tablespace, I can set
the defaults for a tablespace, but the application setup (which is a binary in
this case) may think it knows better and set the storage parameters differently
resulting in objects with different initial and next settings.

I know DBA's who spend countless hours exporting with compress=y and importing
so that an object is in only one extent.  It is a vicious cycle, because the
tablespace now has a bunch of objects with different extent sizes.  These
objects invariably extend again, and the whole process is repeated.  Hello
tablespace fragmentation.

My parting comments (which I know no one asked for):
Read the Oracle documentation - it has 20,000+ pages of very useful information
(and is available online, so if you can read this, you can read the
documentation), buy Tom's book, buy Jonathan Lewis' book, "Practical Oracle8i",
and, finally, have every DBA and developer read them.

Sorry for the rambling.  Thank you, and good night.

      Rating: 5
How many
      extents are too many?  August 03, 2001
Reviewer:
      Richard  from Toronto, Canada
I find the extent debate somewhat humorous.  Other RDBMS have been using bitmaps
for space management since the 80's and early 90's.  Having worked with DB2 and
its segmented tablespaces, the number of extents was never an issue.

It's about time that the Oracle community finally caught up to the rest of the
database world in space management methodology.

      Rating: 5
Raw Logical
      Volumes vs. File Systems  April 07, 2003
Reviewer:
      tony  from Canada
Tom,
    Thanks a lot for all your help,
Can you please tell me which file system is best for Oracle database and why?

Regards
Tony

          Followup:

Both, neither, Raw, Cooked....

none of the above, all of the below...

They both have pros and cons.  If you are familar with and comfortable with RAW,
no reason not to use it from the get go.  If you are using cooked -- don't just
switch over to RAW, you might be using the filesystem buffer cache as an SGA
extension and not really realizing it -- meaning moving to unbuffered raw could
KILL you.

foredirectio on a UFS gives you lots of benefits of both, i sort of like that.

      Rating: 4
Number of
      extents  June 05, 2003
Reviewer:  kelly
       from USA
With dictionary managed tablespace, the number of extents shouldn't reach over
1024.  How about LMT?  Is it ok to have 3000, 5000, etc?

I have created a database with LMT.  This is a 3rd party application.  I leave
all tables in their original tablespaces except for size 40M - 1G, I put them in
medium size tablespace and for over 1G, I put them in Large size tablespace.
For all tablespaces beside medium and large, I have uniform size of 160k.  The
problem is a lot of tables are empty and therefore they take a lot of space and
database size increase 60%.  If I set uniform size of 16k then some tables would
have 2500 extents.  Is it ok to have that many extents even in LMT?

          Followup:

you might consider system allocated extents -- they start small and grow big.

2500 extents is fine, queries by some "monitoring" tools might not be pleased
with the extra rows...

      Rating: 4
Number of
      Extents  June 05, 2003
Reviewer:  kelly
       from USA
Thank you.

Besides Tools, is there a certain number of extents that if exceeds, it's going
to hurt?

Another question.  If I specify storage for creating object in LMT, would this
cause fragmentation.  Example, I have first extent 1m next 2m minimum 4 extents
for uniform size of 2m tablespace. This is going to create 1+2+2+2=7 for first
extent - an odd number.  If I drop the object, there's 1m may not be reused for
future.

          Followup:

Nope.

I "accidently" let a table get into over 30,000 extents.  worked just fine.

specifying storage in a LMT will not cause fragmentation.

In your example, it will allocate 7m of SPACE.  If you have a uniform extent
size of 1m, you'll have 7 extents -- 512k -- 14 extents -- 5meg, 2 extents
(it'll allocate AT LEAST 7m for you is all that means)

UNIFORM means UNIFORM

Even in system managed extent sizes, it only uses initial, next, minextents and
pctincrease to determine how much SPACE to allocate -- not what extent sizes
will be used.

      Rating: 5
LMTs don't let
      you create a mess  June 05, 2003
Reviewer:
      Wolfgang Breitling  from Calgary, Canada
No, that is the beauty of LMTs: they don't let you mess up things, hard as you
may try. Once you set up an LMT with 2M uniform extents, there is no way to
create anything bu 2M extents. If you ask for (initial 1M next 2M minextents 4)
you will get 4 extents of 2M. The "next" setting in the storage clause is
ignored alltogether and the initial and minextents settings provide a lower
boundary. Oracle will create as many of the uniform extents as needed to meet or
exceed the requested space. If you'd ask for inital 11M minextents 4, you'd get
6 2M extents, which is the smallest number of extents to satisfy both your
minextents AND initial requests.

      Rating: 5
How about
      catalog  June 16, 2003
Reviewer:  Kelly
       from USA
Excellent!

How about catalog and catproc scripts?  I still need to create all dictionary
views when create database, don't I?

          Followup:

yes, you need to create the dictionary views -- this isn't about whether or not
a dictionary exists (it always exists) but rather whether extents will be
managed in tables (dictionary manged) or in bitmaps in the file headers (lmts)

      Rating: 4
extend size
        July 21, 2003
Reviewer:  Kelly  from
      USA
I created an empty database with LMT so that I can import my data from
Dictionary MT to this.  I use uniform size of 8k as thousands of our tables are
empty (3rd party application).  However, after creating all the tablespaces, the
dba_extents shows 16k instead of 8k.  Is this the minimum size for uniform
extent?  Because of this we will have a lot of space wasted. (abou 20G).
Anyway to work around?

Thanks so much.

          Followup:

what is your block size.

(and tell me, since 5 blocks was a DMT standard -- how much space have you
already SAVED here?)

      Rating:
      3
minextents  July 23, 2003
Reviewer:
      Dennis  from Missouri, USA
Kelly,

Don't forget about minextents.  That'll get you as well:
06:42:51 winkid1@MYOWN:HA52667> create tablespace test
06:43:29   2  datafile 'd:\oracle\oradata\myown\test01.dbf' size 1M autoextend
off
06:43:37   3  extent management local uniform size 40k
06:43:46   4  segment space management auto
06:43:50   5  logging
06:43:52   6  online
06:43:52   7  /

Tablespace created.

Elapsed: 00:00:00.05
06:47:50 winkid1@MYOWN:HA52667> create table a (a number) tablespace test;

Table created.

Elapsed: 00:00:00.00
06:48:09 winkid1@MYOWN:HA52667> select extents from dba_segments where
segment_name = 'A';

   EXTENTS
----------
         1

Elapsed: 00:00:00.07
06:48:16 winkid1@MYOWN:HA52667> select blocks from dba_extents where
segment_name = 'A';

    BLOCKS
----------
         5

Elapsed: 00:00:01.02
06:48:19 winkid1@MYOWN:HA52667> drop table a;

Table dropped.

Elapsed: 00:00:00.00
06:48:27 winkid1@MYOWN:HA52667> create table a (a number) tablespace test
storage(minextents 2);

Table created.

Elapsed: 00:00:00.00
06:48:42 winkid1@MYOWN:HA52667> select extents from dba_segments where
segment_name = 'A';

   EXTENTS
----------
         2

Elapsed: 00:00:00.08
06:48:47 winkid1@MYOWN:HA52667> select blocks from dba_extents where
segment_name = 'A';

    BLOCKS
----------
         5
         5

Elapsed: 00:00:01.01
06:48:51 winkid1@MYOWN:HA52667>

Additionally, there is this requirement if you are using segment space
management (this one took me by surprise...I always use 64k on my systems, so I
guess I've been lucky):
06:41:43 winkid1@MYOWN:HA52667> create tablespace test
06:41:48   2  datafile 'd:\oracle\oradata\myown\test01.dbf' size 100k
06:41:50   3  extent management local uniform size 8k
06:41:58   4  segment space management auto
06:42:10   5  logging
06:42:15   6  online
06:42:17   7  /
create tablespace test
*
ERROR at line 1:
ORA-03249: Uniform size for auto segment space managed tablespace should have
atleast 5 blocks

And lastly, check the initial sizes.  Even though your extents are of uniform
size (unless you do the autoallocate), it will allocate the number of extents
needed to fulfill your initial, plus some if initial isn't divisible by extent
size:
06:48:51 winkid1@MYOWN:HA52667> drop table a;

Table dropped.

Elapsed: 00:00:00.00
07:04:07 winkid1@MYOWN:HA52667> create table a (a number) tablespace test
storage(initial 120k);

Table created.

Elapsed: 00:00:00.00
07:04:25 winkid1@MYOWN:HA52667> select extents from dba_segments where
segment_name = 'A';

   EXTENTS
----------
         3

Elapsed: 00:00:00.07
07:04:30 winkid1@MYOWN:HA52667> select blocks from dba_extents where
segment_name = 'A';

    BLOCKS
----------
         5
         5
         5

Elapsed: 00:00:01.03
07:04:36 winkid1@MYOWN:HA52667>

ok, we asked for 120k initial, which is three extents of 40k.  Makes sense.  Now
let's ask for 100K, and you'll still see it give three extents, because that's
the minimum number to give you 100K.

07:04:36 winkid1@MYOWN:HA52667> drop table a;

Table dropped.

Elapsed: 00:00:00.00
07:07:21 winkid1@MYOWN:HA52667> create table a (a number) tablespace test
storage(initial 100k);

Table created.

Elapsed: 00:00:00.00
07:07:30 winkid1@MYOWN:HA52667> select extents from dba_segments where
segment_name = 'A';

   EXTENTS
----------
         3

Elapsed: 00:00:00.08
07:07:34 winkid1@MYOWN:HA52667> select blocks from dba_extents where
segment_name = 'A';

    BLOCKS
----------
         5
         5
         5

Elapsed: 00:00:01.02
07:07:38 winkid1@MYOWN:HA52667>

Hope that tells you where the 16k is coming from.

Dennis

      Rating: 5
Numer of
      Extents  September 05, 2003
Reviewer:  Gareth
      Adcock  from Hertfordshire, UK
This discussion, and it's associated links, was extremely usefull.  Not just
because it told me what I wanted to here and provided me with some "ammunition"
but also because it has really helped me to formulate a strategy for
reorganizing tables.

      Rating: 5
Finally seeing
      the light  October 28, 2003
Reviewer:  Dawn
      Witten  from Dallas, TX USA
As stated by others, this thread and associated links have been extremely
helpful to me.  My Oracle experience dates back to about 1990, when the
"Minimize Number of Extents!!" philosophy was preached adamantly by everyone,
including Oracle instructors.  I'm glad to finally have this myth dispelled, and
I can step confidently into the (new to me) 9i world knowing I don't have to
worry so much about space management.

July 22, 2003 Tom -- Thanks for the question regarding "server
      in one time zone/clients in many", version 8.1.5

      You Asked
Hi.  I've run into a problem and I would like your opinion of my
solution.  Our server is located in Florida (Eastern Time), and
we have branches in Alabama (Central Time).  Our accounting
period, therefore, ends at two different times; when it's 12:00
midnight here, there's still an hour left in the day in Alabama.
 If you use SYSDATE to store the time in a row, it will be the
time on our server, not the time at the client site.  If we just
store the time at the client site, we lose a true picture of the
order of events; ie one thing could happen at 12:00 in Florida,
and something could happen at 11:01 in Alabama, and if you don't
store a time zone-non specific time, you might incorrectly
assume that the Alabama event happened first.

My solution is to store the client's time in a row, and store a
time offset to GMT in the same row.  We considered storing two
times, a GMT time and a client time, but didn't know if that was
too redundant.  We also considered storing GMT and an offset to
the client's time, but then we'd need to recalculate the time
whenever we wanted a report of a single day's (or other
financial period's) events.

Please let me know if you've heard of similar or better
solutions, or if you think I'm out of my tree.

Thank you,
Tom Wurzbach

      and we said...
If you need to remember both the clients time AND the server
time -- I would suggest storing 2 dates.  That will be the best
way to avoid all confusion.  In that way you can query either
column to get the correct representation of the date/time given
the circumstances AND you'll be able to use indexes on the
individual columns to speed up queries on dates if need be.

We have a calendar/resource system we've built internally.
We've opted to store all dates in GMT in the database (it's used
worldwide) and when needed, store the datetime in the timezone
of the client as well.  We predominatly use the GMT date and
convert it to the local timezone on the fly but on the occasions
we need it -- we have the clients native date in a column as
well.
Review
      & Followup

      Rating: 3
Useful info on
      handling time zones  June 18, 2001
Reviewer:
      Nuno Souto  from Sydney, AUS

      Rating: 4
I have a date
      dimension and a Seconds dimension  March 31, 2003

Reviewer:  Ajeet  from oh,usa
Tom - I am building a datawarehouse and it will be used globally.I have a date
dimesion where grainularity is calendar date. And then I have Time of day
dimension where grainularity is second. so this dimension has
second,minute,hours.(60x60X24 rows).
Now - I came across the same issue -- we have source systems where users use GMT
, EST zone USA time, IST (Indian standard time) and many others...I am using seq
id (oracle generated) as primary keys for both of these tables--but how to
handle the different timezones.
I can store all the times in say GMT time but for reporting purpose local users
would like to use their own date and time...Can you please suggest a correct way
to handle this situtation.

Thanks so much for your help.

          Followup:

sigh, no version.

k, easiest way -- use 9i, full timezone support.

      Rating: 4
Version is
      8.1.7  April 01, 2003
Reviewer:  Ajeet
       from oh,usa
Hi Tom - we have oracle 8.1.7 (scheduled upgrade to 9i is June 2003).But by that
time our warehouse will be completed.
Even I use 9i -- How can I resolve the issue I have with seperate Date and Time
Dimension.I think it is a design flaw.Becaue if I have a Date and Time dimension
-- I can store other timezones time as attribute .
What is your thought Tom.

Thanks
Ajeet

          Followup:

I don't know what you mean by "separate date and time dimension".  if you are
saying "we store date over here and time over there", the solution is simple --
a single DATE field.

      Rating: 4
is there any
      solution similar to "set time_zone" for DATE?  July 21, 2003

Reviewer:  gs
Tom,

We have the DB on oracle 9.2.0.3. Our web based application is used by clients
from various time zones. This is an web based application and all the web
servers on the DB Server's time zone. now, we have the requirement that the each
client (thin clients) has to display the date/time in their Time Zone? Is there
effecient way to achieve this? Assume the client's time zone is stored in the
client profile table. Currently we have mostly DATE columns not TImestamp
columns!

Could you please provide me a few options.

Thanks,

          Followup:

DATES are DATES -- their functionality is frozen.  They consume 7 bytes which is
only big enough for year month day hour minute second, no more, no less.  there
is no room for a timezone.

In order to use timezones, you need to use timestamp and you can use an alter
session to set the clients timezone

July 22, 2003 Terry -- Thanks for the question regarding
      "Training Developers to write efficient SQL", version 8.1.7

      You Asked
Hi Tom, I'm going to be training some C++ programmers on how to write efficient
select statements. I've been trying to come up with a list of tips on the most
efficient way to get data. Such as when to use inline views vs hash joins or
subqueries. For example, I can think of 3 queries that would return the same
results, in an attempt to determine which one is the best I used explain plan
and auto trace. They all seem to take the same amount of time! Is this because
we are using the CBO? Does it matter how the query is written?  Here are the 3
that I tested. Is one any better then the other? The job table has about 55000
records in it, and the company table has 400. Any idea on what tips I can give
to the developers?

(1)
select count(job_id), company_name
from job, company
where job.company_id = company.company_id
group by company_id;

(2)
select job_count, company_name
from job, (select company_id,count(job_id) from jobpost_job
             group by company_id)
where job.company_id = company.company_id;
(3)
select company,(select count(job_id) from jobpost job where
company_id=company_id)
from company;

      and we said...
Well -- i think you are looking at the wrong tools -- explain plan and autotrace
are great BUT the tool they really really really need is:

TKPROF

You'll really need to supply a better example -- those queries are not
semantically "the same", won't even compile for the most part (eg: job_count in
2 isn't a column anywhere, query 3 uses jobpost, query 3 drives from company and
query 2 drives from job -- they couldn't give the same answer)

I can suggest a presentation -- if you grab my AOTC slides:

http://asktom.oracle.com/pls/ask/z?p_url=http%3A%2F%2Fasktom.oracle.com%2F%7Etkyte%2Faotc.tar.gz&p_cat=AOTC2_SLIDES&p_company=10
you might find that useful.  It is not about writing efficient SQL (i think that
is an IMPOSSIBLE topic) -- but rather what tools you can use and how to TEST
different approaches (that is what is IMPORTANT here!!!  testing testing
testing)

So, lets use EMP and DEPT for your example.  We want a list of departments and a
count of employees in each.  So, we need to setup a test with sufficient data to
actually TEST WITH:

ops$tkyte@ORA920.US.ORACLE.COM> create table dept
  2  as
  3  select * from scott.dept where 1=0;

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> insert into dept (deptno,dname,loc)
  2  select rownum, 'dept-' || rownum, 'loc-' || rownum
  3    from all_objects where rownum < 100;

99 rows created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create table emp
  2  as
  3  select * from scott.emp where 1=0;

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> alter table emp modify empno number(10);

Table altered.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> insert into emp(
empno,ename,job,mgr,hiredate,sal,comm,deptno)
  2  select rownum, 'emp-'||rownum, 'job', null, sysdate,rownum, rownum,
mod(rownum,99)+1
  3    from all_objects;

29316 rows created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> alter table dept add constraint dept_pk primary
key(deptno);

Table altered.

ops$tkyte@ORA920.US.ORACLE.COM> alter table emp add constraint emp_pk primary
key(empno);

Table altered.

ops$tkyte@ORA920.US.ORACLE.COM> create index emp_dept_idx on emp(deptno);

Index created.

ops$tkyte@ORA920.US.ORACLE.COM> alter table emp add constraint emp_dept_fk
foreign key(deptno) references dept(deptno);

Table altered.

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

Table analyzed.

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

Table analyzed.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> alter session set sql_trace=true;

Session altered.

and then I ran a whole bunch of queries -- not all of which are exactly the
same.  Here is the result:

select dept.dname, count(*)
  from dept, emp
 where emp.deptno = dept.deptno
 group by dept.dname

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        8      0.06       0.06          0         70          0          99
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       10      0.07       0.06          0         70          0          99

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 78

Rows     Row Source Operation
-------  ---------------------------------------------------
     99  SORT GROUP BY
  29316   HASH JOIN
     99    TABLE ACCESS FULL DEPT
  29316    INDEX FAST FULL SCAN EMP_DEPT_IDX (object id 31452)
********************************************************************************
select dept.dname, count(*)
  from dept left outer join emp on (emp.deptno=dept.deptno)
  group by dept.dname

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        8      0.06       0.06          0         70          0          99
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       10      0.06       0.06          0         70          0          99

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 78

Rows     Row Source Operation
-------  ---------------------------------------------------
     99  SORT GROUP BY
  29316   HASH JOIN OUTER
     99    TABLE ACCESS FULL DEPT
  29316    INDEX FAST FULL SCAN EMP_DEPT_IDX (object id 31452)
********************************************************************************
select dept.dname, (select count(*) from emp where emp.deptno=dept.deptno)
  from dept

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        8      0.01       0.01          0        178          0          99
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       10      0.01       0.01          0        178          0          99

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 78

Rows     Row Source Operation
-------  ---------------------------------------------------
     99  TABLE ACCESS FULL DEPT

********************************************************************************

select dept.dname, emp.cnt
  from dept, ( select deptno, count(*) cnt from emp group by deptno) emp
 where dept.deptno = emp.deptno

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        8      0.03       0.03          0        209          0          99
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       10      0.04       0.04          0        209          0          99

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 78

Rows     Row Source Operation
-------  ---------------------------------------------------
     99  HASH JOIN
     99   TABLE ACCESS FULL OBJ#(31448)
     99   VIEW
     99    SORT GROUP BY
  29316     TABLE ACCESS FULL OBJ#(31449)

Now -- the queries are not the same -- the outer join and "select ...(select)
from dept" are the same and the other two are the same (so there is the
potential for 2 different answers here)

This showed that the "least logical IO intensive approach" is the good old
fashioned "join".  The least CPU intensive is the "select of a select" and using
the inline view doesn't look like something we want to do.

Now, we just need to figure out -- do we want answer 1 (no outer join, don't
report on deptno's that have no employees) or answer 2 (use outer join
semantics, report 0 or NULL as the emp count when appropriate).

Then you would pick the query you find works best.

But basically - they need to learn all of the options (all of the ways to write
a query) and then compare and constrast AGAINST A REALISTICALLY SIZED SET OF
DATA!!! different approaches for tuning.  Just like they would benchmark
different algorithms in C....

That is the key to tuning!
Review &
      Followup

      Rating: 5
...but couldn't
      grab those slides  November 08, 2002
Reviewer:
      Mirjana  from Barcelona

          Followup:

fixed that -- sorry, goofed the url up.

      Rating: 4
Remember the
      mantra  November 08, 2002
Reviewer:  Jim
      Kennedy  from Portland, OR USA
Have those programmers use bind variables.

          Followup:

That is one of the first slides!!!

      Rating: 5
Tuning
      tips  November 18, 2002
Reviewer:  Terry
       from USA
Sorry about the queries, I have a good excuse but doesn't everyone! Thank you
very much for your help, this is exactly what I needed!

      Rating: 4
tkprof and
      inline views  July 21, 2003
Reviewer:  Steven
      Cooper  from England
Tom,

After running the example below the plan in the tkprof report doesn't show the
dept table anywhere.  How would you tune a SQL statement with these kind of
inline views if the plan is hidden or not accurate?  Are the stats accurate our
are they only for the emp table access and exclude the dept table access?

Thanks in advance
Steven

create table emp as
select * from scott.emp;

create table dept as
select * from scott.dept;

alter session set timed_statistics = true;
alter session set sql_trace = true;

select     ename,
    (select dname from dept d where e.deptno = d.deptno) dpeartment
from    emp e;

TKPROF
...
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.04          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0         21         48          14
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.04          0         21         48          14

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 41

Rows     Row Source Operation
-------  ---------------------------------------------------
     14  TABLE ACCESS FULL EMP

          Followup:

that is not an inline view, that is a scalar subquery and scalar subqueries do
not yet show up in an explain plan.

      Rating:
      5
  July 22, 2003
Reviewer:  chuck
      jolley  from OKC, OK USA
Be sure to get them to understand that SQL is not procedural*.
This was the hardest concept for me to grasp when I started writing SQL.
It is also what made writing SQL become VERY interesting for me.
chuck

*You can trick it, but that comes later ;)
It's the concept that is important.

July 22, 2003 Priyanka -- Thanks for the question regarding
      "hextoraw, rawtohex question", version

      You Asked
I get a problem while using functions hexttoraw and
rawtohex.while using hextoraw function  it gives the same output
as input.

      and we said...
That is because you are probably testing in SQLPlus or you are
binding a varchar in a host application.

Hexttoraw returns a RAW.  SQLPlus will convert that into a
varchar2 for display.  When you convert a raw into a varchar2 --
you get hex.

select hexttoraw('00') from dual;

does in fact return a 'raw' but sqlplus is printing it in hex.

If you do it in pro*c where you have total control over the
datatype (unlike in sqlplus which is just a "report" tool) --
you'll find that a program like:

typedef struct TAGmy_raw
{
    long            len;
    unsigned char   arr[255];
} my_raw;

main( int argc, char * argv[] )
{

EXEC SQL BEGIN DECLARE SECTION;
EXEC SQL TYPE my_raw IS LONG VARRAW(255) ;
my_raw  raw_data;
EXEC SQL END DECLARE SECTION;

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();
    EXEC SQL CONNECT :USERID;
    printf("\nConnected to ORACLE as user: %s\n\n", USERID);

    EXEC SQL select hextoraw( '00' ) into :raw_data from dual;

    printf( "it is %d bytes long and have the value %d\n",
raw_data.len, raw_data.arr[0] );

    EXEC SQL COMMIT WORK RELEASE;

    exit(0);
}

returns:

$ ./t

Connected to ORACLE as user: /

it is 1 bytes long and have the value 0

Which is expected (hex '00' turns into \0 in C)

Review &
      Followup

      Rating: 4
Space used by
      RAW  July 22, 2003
Reviewer:  Mohan K
       from Bangalore, India
I have a very latge table of two fields ech field containing positive integers
upto 50000. If I conver the number datatype to RAW(2) then each field will
ocuupy only 2 bytes. So each block can store more number of records and the
number of records fetched into SGA by a query will be more.
I read in Oracle document that RAW datatype is same as varchar2 since it is of
variable length. What will be additional overhead of variable length in storage.

Is it really of any use to convert into RAW datatype or shall I convert into
hexdecimal( uses 4 bytes) or the number datatype itself is the same as the above
methods.

Mohan

          Followup:

why not use a number?

how will use manipulate this thing otherwise?  sum it? avg it? min/max it?
ANYTHING to it.

July 22, 2003 Nilan -- Thanks for the question regarding "SQL
      Query Help!!", version 8.1.7.0.0

      You Asked
Hi Tom,

I have a requirement that reads like this, "..it will list the next 10
milestones or the milestones due in the next 14 days, which ever is greater".

Another requirement would be to "list the next Key Event due and in addition any
Key Events due in the next 30 days".

How do I write SQL statements to do this?

My table structure is as follows:

CREATE TABLE KE_MS_DIM (
  KE_MS_DESC_TX    VARCHAR2 (300),
  KE_MS_WH_KEY     NUMBER,
  KE_MS_KE_ID      VARCHAR2 (4),
  KE_MS_TYPE       CHAR (2),  -- 'KE' for 'Key Event' or 'MS' for Milestone
)

CREATE TABLE KE_MS_FACT (
  DA_SMART_KEY  NUMBER,
  KE_MS_WH_KEY  NUMBER,
  NET_NODE_DT   DATE )

The net_node_dt column contains the date that we wish to compare against.

Thank you

Nilan

      and we said...
For the "next 10 or due in the next 14 days", i believe a predicate like this
will work:

tkyte@TKYTE816> create table t ( dt date );
Table created.

tkyte@TKYTE816> begin
  2          for i in 1 .. 20
  3          loop
  4                  insert into t values ( sysdate+i-1 );
  5          end loop;
  6  end;
  7  /
PL/SQL procedure successfully completed.

tkyte@TKYTE816> select dt, rownum, sysdate+14
  2    from t
  3   where ( dt < sysdate+14 or ( dt > sysdate+14 and rownum <= 10 ) )
  4  /

DT            ROWNUM SYSDATE+1
--------- ---------- ---------
19-JUN-01          1 03-JUL-01
20-JUN-01          2 03-JUL-01
21-JUN-01          3 03-JUL-01
22-JUN-01          4 03-JUL-01
23-JUN-01          5 03-JUL-01
24-JUN-01          6 03-JUL-01
25-JUN-01          7 03-JUL-01
26-JUN-01          8 03-JUL-01
27-JUN-01          9 03-JUL-01
28-JUN-01         10 03-JUL-01
29-JUN-01         11 03-JUL-01
30-JUN-01         12 03-JUL-01
01-JUL-01         13 03-JUL-01
02-JUL-01         14 03-JUL-01

14 rows selected.

tkyte@TKYTE816> delete from t;
20 rows deleted.

tkyte@TKYTE816> begin
  2          for i in 1 .. 20
  3          loop
  4                  insert into t values ( sysdate+i+11 );
  5          end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

tkyte@TKYTE816> select dt, rownum, sysdate+14
  2    from t
  3   where ( dt < sysdate+14 or ( dt > sysdate+14 and rownum <= 10 ) )
  4  /

DT            ROWNUM SYSDATE+1
--------- ---------- ---------
01-JUL-01          1 03-JUL-01
02-JUL-01          2 03-JUL-01
04-JUL-01          3 03-JUL-01
05-JUL-01          4 03-JUL-01
06-JUL-01          5 03-JUL-01
07-JUL-01          6 03-JUL-01
08-JUL-01          7 03-JUL-01
09-JUL-01          8 03-JUL-01
10-JUL-01          9 03-JUL-01
11-JUL-01         10 03-JUL-01

10 rows selected.

For the second one, "list the next key event due and in addition any key events
due in the next 30 days"

select * from T where dt = ( select min(dt) from t )
UNION
select * from T where dt <= sysdate+30;

Will get the next key event (the first query does) and union that with all key
events in the next 30 days.  The union does a sort+distinct so if the next key
event is within the second query -- the duplicate will be removed.

Review &
      Followup

      Rating: 5
Another
      twist  June 19, 2001
Reviewer:  Nilan
       from Norfolk,VA
Thanks for the prompt answer, it has helped out tremendously!!!  I had to modify
the queries somewhat because we don't want to pull back data that is before the
sysdate.

So, the last query became

select * from T where dt = ( select min(dt) from t where dt > sysdate)
UNION
select * from T where ( dt <= sysdate+30 and dt > sysdate);

I am still having problems on the first one though.
I think the problem lies with the order of execution of this predicate:

 (  ( dt < sysdate+14 and dt > sysdate ) or
    (  dt > sysdate+14 and rownum <=10 )
 )
What is happen is this;  For some reason, the or condition gets executed FIRST!!
 Because of this, I get data back for sysdate > 14 even though the first
condition returns more than 10 rows. I think what is happening though is the
join that I have to do to get the date is throwing a wrench into the rownum
condition. It doesn't get executed in the order we expect.  Any additional
ideas?

Thanks again for your prompt response.

      Rating: 5
Answer to
      "Another Twist"  June 19, 2001
Reviewer:  Logo
      Palanisamy  from Santa Clara, CA, USA
Nilan,

Why don't you create a view t_v as select * from t where dt > sysdate and run
Tom's queries as is against the view.

Tom: You are doing a wonderful service. I am learning a lot from your answers.

Thanks

      Rating:
      3
  June 21, 2001
Reviewer:
      A reader

      Rating:
      3
  June 21, 2001
Reviewer:
      A reader
For the first query

Since results of either condition(next first 10) is a subset of the other
condition( in the next 14 days)or visa versa depending which yields more number
of rows. So the desired result can be achieved by the union query.

Like I created a table milestione with dt as date field.
The below query solves the problem.

select dt from milestone where rownum < 11
union
select dt from milestion where dt < sysdate + 14

      Rating: 4
Duplicate rows
      when joining three tables  October 02, 2002

Reviewer:  B  from India
Hi Tom,

There are three tables. Table 'a' contains the master entry, Table 'b' contains
entry related to cost and Table 'c' entries related to revenue.

Table a would contain only one row for a value in anum. Table b and c may
contain may contain 0 or more lines for a value of anum. If one of the revenue
or cost table contain more row(s) than the other for anum then duplicate values
would be returned when all the three tables are joined . Is there any way to
avoid duplication of values in an efficient manner other than calculate revenue
and cost values separately e.g. union/unoin all clause (or) functions

CREATE TABLE a (anum INT, achar CHAR(1));

CREATE TABLE b (anum INT, bline INT, bqty NUMBER(1));

CREATE TABLE c (anum INT, cline INT, cqty NUMBER(1));

INSERT INTO a VALUES(7,'A');

INSERT INTO b VALUES(7,1,10);

INSERT INTO b VALUES(7,2,-10);

INSERT INTO c VALUES(7,1,12);

INSERT INTO c VALUES(7,2,-12);

INSERT INTO c VALUES(7,3,20);

SELECT  a.anum
      , a.achar
      , b.bline
      , b.bqty
      , c.bline
      , c.bqty
  FROM a a
     , b b
     , c c
 WHERE a.anum = a.anum
   AND b.anum = c.anum
   AND a.anum = 7

Thanks

          Followup:

well, what would you LIKE the answer to look like?

I mean, if A has 5 B's and A has 10 C's -- how do you get a "square" result set
out of that?

Unless you can aggregate B and C to the same level of detail (eg: at the ANUM
level), then you are looking at a union all

      Rating: 2
date
      comparisions  October 03, 2002
Reviewer:
      mathew  from India
hi,
I thought you ALWAYS recomended explicit date comparisions in all cases.
 would statements like
select * from T where dt = ( select min(dt) from t )
UNION
select * from T where dt <= sysdate+30;

be good practice? Will they use indexes if any?

          Followup:

where did I NOT use explicit date comparisions here????

i compared dates to dates in all cases.

or's will be converted into unions as well by the optimizer.

unions can be converted into ors by the opimizer.

so, it could be thought of as 6 one way, 1/2 dozen the other.

      Rating: 4
Duplicate rows
      when joining three tables   October 03, 2002

Reviewer:  B
Thanks.

      Rating: 4
Date field in
      Where clause  July 21, 2003
Reviewer:  Ram
       from INDIA
Hi Tom

Why date field consider time stamp when it refers  in where condition..

Example
 create table t(a date);
 insert into t values(sysdate);
  select *  from t
     where a =sysdate
  the above query returns no records .when I refer   like
  select *  from t      where to_date(a) =to_date(sysdate)

Then it returning record. In my application most of the  queries  not provided
the to_date function. Can you please help me how to avoid it without using
to_date .

I have set the NLS_DATE_FORMAT ="dd-mon-yyyy hh24:mi:ss"

Thanks
Ram

          Followup:

sigh, the misunderstanding about date perplexes me constantly.  Ok.

when you did this:

insert into t values ( sysdate );

you inserted something like  21-jul-2003 08:16:45

8:16am on the 21st of july 2003.

Later, you query:

select * from t where a = sysdate;

that is like:

select * from t where a =
   to_date( '21-jul-2003 08:16:46', 'dd-mon-yyyy hh24:mi:ss' )

see -- that would be like one second later.  Now, you query

where to_date(a) = to_date(sysdate)

I do not believe you that your NLS_DATE_FORMAT is what you say it is -- I
believe it does NOT contain the time component at all.  that to_date is the same
as

to_date(to_char(a)) = to_date(to_char(sysdate))

since to_date takes a string.  that would convert a/sysdate into strings and
back into dates without losing any precision.  You MUST have an nls date format
different then what you think (a simple select sysdate from dual will prove
this, it won't have the time)

Here is an example:

scott@ORA920LAP> create table t ( a date );

Table created.

scott@ORA920LAP>
scott@ORA920LAP> insert into t values ( sysdate );

1 row created.

scott@ORA920LAP> exec dbms_lock.sleep(1);

PL/SQL procedure successfully completed.

scott@ORA920LAP>
scott@ORA920LAP> alter session set nls_date_format = 'dd-mon-yyyy hh24:mi:ss';

Session altered.

scott@ORA920LAP>
scott@ORA920LAP> select * from t;

A
--------------------
21-jul-2003 08:20:35

scott@ORA920LAP> select * from t where a = sysdate;

no rows selected

scott@ORA920LAP> select * from t where to_date(a) = to_date(sysdate);

no rows selected

scott@ORA920LAP> alter session set nls_date_format = 'dd-mon-yyyy';

Session altered.

scott@ORA920LAP> select * from t where to_date(a) = to_date(sysdate);

A
-----------
21-jul-2003

in the last one, with nls_date_format = dd-mon-yyyy, you effectively
"truncated" the date, removing the time component.

      Rating: 5
SQL Query
      Help  July 22, 2003
Reviewer:  Ram
       from INDIA
Tom,

Thanks you for immediate response..

Is there any way to fetch my records without referring the to_date function?

Can you please tell me what format should I prefer for nls_date_format for my
production database. There are some scheduled jobs going to be run some where
mid night.

          Followup:

depends on what you want to retrieve:

where a between trunc(sysdate) and trunc(sysdate)+1-1/24/60/60;

will get you all rows such that a has todays date minus the time component.

as for "what is the right nls_date_format" -- i am of the firm belief that you
should use to_date( ..., FORMAT ) on the way in (do not rely on a
nls_date_format) and to_char( ..., FORMAT ) on the way out (do not rely on a
nls_date_format)

the second you rely on nls_date_format -- you have probably added a bug to your
program.

July 22, 2003 Kedar -- Thanks for the question regarding
      "URGENT...........regarding auto commit", version 8.1.6

      You Asked
1)In sybase in a stored procedure if there are DML statements,
each statement is committed after its execution. Even if any of
the statements fail all the above DML statements are committed
and the execution continues.

2)In sqlplus if I say "set autocommit on" and execute a stored
procedure with 3 DML statements and the 3rd DML statement fails
(due to some reason)the previous 2 DML statements are not
committed, which is not the case with sybase as explained in 1.

3)Can I achieve this behavior using OCI (ocon()) or is there any
other way.

We are porting a telecom product from sybase to oracle, so  we
the need same behaviorin oracle. Please get back as soon as
possible.

Regards,
Kedar Phadke

      and we said...
Oracle != Sybase (thank goodness in my opinion, i've worked with
both).

The Sybase "transaction" (or lack thereof) model totally flies
in the face of what databases and transactions are all about.

Sybase was built predicated on the fact that locks would never
really be taken and if there were, they would be instantly given
back (else the entire system grinds to a screeching halt).
Hence their default mode of "no transactions for you".  they
make it hard to do a transaction.

There is no way to mimick the sybase behavior in Oracle short of
placing a commit after each and every statement.  A stored
procedure is considered by Oracle to be the "atomic" statement.
Either all of the statements succeed, or none of them do.
Exception handling can change the behavior, allowing some
statements to fail, have their error caught and dealt with and
not rolling back anything else.

Some APIS such as JDBC and ODBC can autocommit but they are just
issuing "commit" after each and every statement they submit.  It
would not autocommit the statements within a procedure.

Let me ask you, what should the behaviour of:

  for x in (select * from T for update )
  loop
      update t2 set ....;
  end loop;

be?  if we committed after every update -- this loop would fail
on row 2 of T since we commited.  it just doesn't work in
Oracle.

You will need to be concerned about many differences in the
porting of your application.  Beyond some small differences in
SQL dialect, larger issues are waiting for you.  We have
non-blocking reads -- they do not.  You will find your
transactions execute in a mix never before possible.  We provide
consistent reads without shared read locks (we do not even have
the concept of a row level "share read lock"), they do not.

In the areas of multi-user access and concurrency Oracle and
Sybase (and SQLserver) are about as different as you can get.
Review &
      Followup

      Rating: 4
Some questions
      about this issue...  January 05, 2002
Reviewer:
      Sven Bleckwedel  from Brazil
Tom,

I´m not secure about this, but Microsoft´s SQL Server engine IS BASED in Sybase
engine.  Is it true ?  If yes, had Microsoft changed anything after or
maintained the same point-of-view ?

Rgds,
Sven

          Followup:

MS SQL-Server and Sybase were one in the same during the 4.x version timeframe.
Sybase sold themselves to MS (basically putting themselves out of the database
business at the same time).

MS has changed alot but the basics remain the same. TSQL is their stored
procedure language.  Cursors are evil in MS SQL server, also transactions are
evil, locks should not be held for more then a nano-second to avoid stopping the
database, and so on...

They are still very similar in many respects.

      Rating: 5
Some
      recomendations for the other database
      administrators...  January 07, 2002
Reviewer:
      Sven Bleckwedel  from Brazil
Tom,

I hope that others dba can understand it, and agree that Oracle has a very well
designed database conceptual model.

Rgds,
Sven

      Rating: 4
"Oracle Facts"
      from a sybase DBA  July 22, 2003
Reviewer:
      Michael  from Germany
Hi Tom,

what's your opinion about the statements of this sybase DBA:

http://www.talusmusic.com/DBPowerSuite/
(Under Oracle Facts 1/2)

          Followup:

this is a realy old old paper that this guy put "9i" on -- I remember seeing
this years and years ago.  Nothing new.

#2 is so funny on "facts 1" - since they (sybase) invented the "updates that
move rows like crazy" -- in fact, a simple update very often moves a row and is
done via DELETE+INSERT.

look at #4
"Oracle has at least three different languages:  Server SQL, SQL*Plus and
PL/SQL"

let me rewrite it:

"Sybase has at least three different languages: Server SQL, ISQL and T-SQL"

give me a break -- we all have SQL, we all have a procedural extension to SQL
and we all have command line interfaces.

My opinion -- databases are like a religion to some and he is a following of
Sybase.  I was a user of Sybase -- I could not understand why anyone would
praise T-SQL as a "programming" language or even a "good idea".  Cursors?
simply tacked on because everyone demanded them and Sybase was (past tense, they
are sort of "was" now since they sold themselves to MS -- not smart) great at
satisfying checklists.

I remember this one RFP where the databases had to:

o support parallel processing
o support heterogenous access
o support transparent distributed query

and other features -- they were able to check each box -- never mind the fact
that in order to do "a", "b" was not possible -- nor was "c" and so on.  They
could do each point in a way -- just not all at the same time.

July
      22, 2003 Ashiq -- Thanks for the
      question regarding "Some Interview Questions", version 8.1.7

      You Asked
Hi Tom,

  Recently I attened a inteview , I have given the questions which I'm not able
to answer.

  1.When using rman ,oracle will use its own processes than the OS one.the
questions is what are the advantages oracle is getting by using it own process.
  Though referred manual but i'm not able to get it.Can you tell me the
advantages??

 2.You're going to create a table (which has 60 columns with datatypes number,
varchar2 , clob, and date).He asked me ,what way of order  columns you will
place the columns, like first numbers, then varchars,or date etc.
  For this is allow i told him i don't know.
   Tom is any rule we've follow for when we create a table??

 3. And the last one (just for curios ) either you can answer or not.
   How many hours(or average) you daily work in answering our queries or doing
research and developent in ORACLE.

thanx,
A.Ashiq

      and we said...
1) tablespaces do not need to be in hotbackup mode, resulting in less redo
generation during hot backup.

rman can be throttled (limited in the amount of IO it does) so as to not kill
the system with massive read requests

2) you put them in the order that makes most sense.  Frequently accessed columns
would perform best "first", if you have some columns that almost always will be
null -- put them last.

The ones in the middle, makes no difference at all.

In general, i would put them in the order that is most pleasing to me -- at the
end of the day, it isn't going to make significant difference.

3) most of it...
Review & Followup

      Rating:
      5
  May 23, 2003
Reviewer:  Ashiq
      Shamsudeen  from Trichy, Tamil Nadu, India
Addon to the above questions its not interview question, but personal question
about you.

   When you answering our  questions will you refer docs( manuals)(or syntax,
and crosschecking with it what you've said is right or wrong .

          Followup:

(my answer to #3 was "most of it")

I use documentation -- but mostly rely on tests to prove it.  Documentation can
be misleading or incorrect from time to time (not often) -- nothing like a test
case to prove the point (or more likely, disprove the point)

      Rating: 4
LOB
      Last?  May 23, 2003
Reviewer:  Jeremy Smith
       from Salt Lake City, UT
Re #2:
I've frequently read that if you're going to use any kind of LOB that might be
"in row", you're best off putting them last.  I think the thought is that if
you're looking for a non-blob column that's after the LOB, it's going to have to
read through your LOB.  Is this accurate?

          Followup:

a lob is a lob locator generally, a pointer.  it is small.

lobs can be inline or out of line.  by default lobs are allowed to be inline,
meaning that if they are under about 4000 bytes (like a varchar2 field), they
will be stored "in the row".  Once they exceed that threshold - they are
replaced with a pointer and stored out of line -- in the lob segment elsewhere.

LONGS and LONG RAWS did not behave this way, they were always stored inline

      Rating: 5
Search
      command  May 24, 2003
Reviewer:  Abdul Wahab
       from Jeddah,Saudi Arabia
Dear Tom,
As this thread seems to be about the interview questions,I  have one question
which was given to me once in an interview. The question was that if we want to
find a row(datatype varchar) from a table containing a particular text, what is
the command in oracle? .I said it can be done using- like '%<particular
text>%'.He wasn't happy with that, then i said we can also use SOUNDEX.He was
still unhappy!. Any way i didn't get the job there.Thanks god i got a better job
somewhere else. But i searched a lot to find the alternate answer.Tom can you
tell me is there such search command?

          Followup:

what about the obvious answer?

select * from t where column = 'particular text';

then again, they might have been trying to get you to say "interMedia text" --
context indexing.

      Rating: 4
One more
      question  May 24, 2003
Reviewer:  Reader
       from Houston, TX
It is great thread. What does it mean if someone asked me to explain the concept
of how application users can connect to the database as a single user or single
login? What would be the benefit in doing so as against creating and managing
thousands of end users in the database itself. Could you help? Thanks.

      Rating:
      5
Search  May 25, 2003
Reviewer:  Abdul
      Wahab  from Jeddah,Saudi Arabia
Hi Tom,
  no, no the  'obvious answer' is not needed here. You see i don't want to
search a text as a whole in the table , but i want to retrieve a row which
contains a particular string or set of words. For eg. if one row contains text
as "TOM IS REALLY HELPFULL FOR THE ORACLE FANS".Now i want to retrieve this row
just by specifying the condition that the row contains-"TOM" or any row that
contains "FOR".
  By the way you mentioned "intermedia text".Hmmmm...sounds interesting. i think
this was what i was suppose to tell. Can u tell me more about it or refer me to
some link.
thanx

          Followup:

see the text application developers guide:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-TEX
when you search on asktom, you are using that technology.

      Rating:
      3
REDO  May 27, 2003
Reviewer:  Van
      Driessche Erik  from Belgium
Hi Tom,

About the backup mode of a tablespace, some documentation says that there is no
difference in redo generation when you put a tablespace in backup mode. When she
is not, there is also redo generated .... or not ?

Kind regards

          Followup:

show me Oracle documentation that says "the redo generation is no different
after an alter tablespace begin backup has been issued"

otn.oracle.com has all of the docs, post a link to it.

      Rating: 1
Relevant
        June 02, 2003
Reviewer:  Erik  from
      Belgium
Hi Tom,

I did a search on metalink, redo generation hot backup ...

but in every search I receive a ton of irrelevant documents ...

do you a otn document where  I can find the necessary info.

          Followup:

what "necessary info".

if your search is as specific as your question -- I can understand why you got
lots of hits :)

      Rating:
      3
RMAN  June 02, 2003
Reviewer:  Kevin
       from CA USA
#1  regading rman backup, ramn uses Oralce processes to backup
only used (ever been used) data blocks and maintain read-consisitency of blocks
(through rbs).

          Followup:

it doesn't maintain read consistency, the backups are fuzzy/inconsistent (unless
is was "cold")..  Think of the ora-1555's that would happen during a long
running backup otherwise.

      Rating: 4
Documentation
      of Stored procedures & functions  July 22, 2003

Reviewer:  A reader
Hi Tom,
We are using database procedures for the complex business logic,in your
experience what's the format, approach that should be adopted to document all
the business logic.
I am thinking of a High level document which gives the interaction between
procedures the Process Flow and ER diagram for a procedures or business function
which it is catering for.and a low level document which gives the details of all
the queries.
But i am not very sure of this, whats the kind of documents have u generally
provided when handing over the project.

Thanks

          Followup:

what tool would your company use for java? for VB? for C?  use the same tool,
same methodology.  It is just "code"

what is handed over to a client is what the client askes to be handed.  Oracle
Designer is frequently used as our repository on large systems.  The needs vary
from situation to situation

      Rating:
      3
Nice  July 22, 2003
Reviewer:
      R.Chacravarthi  from Chennai,India
Dear Sir,
when there is a need for text based searches I think we can
use the contains function provided it is a long column.
for ex:
select * from emp where contains(column_name,'some text');
I hope this will work.
I have a question for you.
It is "what is the undo generated for insert statements"
Thanks.
Yours sincerely,
R.Chacravarthi

          Followup:

the undo generated for an insert is the sum of

a) the changes made to any indexes affected
b) a "delete rowid" entry for each inserted row (so we can "un-insert" them)

July 22, 2003 Brett -- Thanks for the question regarding "How
      do I get a summary of previous year for a SKU in a view?", version 8

      You Asked
Hi Tom,

I have the following view definition:

create or replace view jl_test_summary_v as
select to_char(b.invoice_date, 'YYYY') year, a.sku sku,
a.part_number part_number, a.model,
sum(decode(to_char(b.invoice_date, 'MM'), '01', quantity, 0)) JAN_QTY,
sum(decode(to_char(b.invoice_date, 'MM'), '02', quantity, 0)) FEB_QTY,
sum(decode(to_char(b.invoice_date, 'MM'), '03', quantity, 0)) MAR_QTY,
sum(decode(to_char(b.invoice_date, 'MM'), '04', quantity, 0)) APR_QTY,
sum(decode(to_char(b.invoice_date, 'MM'), '05', quantity, 0)) MAY_QTY,
sum(decode(to_char(b.invoice_date, 'MM'), '06', quantity, 0)) JUN_QTY,
sum(decode(to_char(b.invoice_date, 'MM'), '07', quantity, 0)) JUL_QTY,
sum(decode(to_char(b.invoice_date, 'MM'), '08', quantity, 0)) AUG_QTY,
sum(decode(to_char(b.invoice_date, 'MM'), '09', quantity, 0)) SEP_QTY,
sum(decode(to_char(b.invoice_date, 'MM'), '10', quantity, 0)) OCT_QTY,
sum(decode(to_char(b.invoice_date, 'MM'), '11', quantity, 0)) NOV_QTY,
sum(decode(to_char(b.invoice_date, 'MM'), '12', quantity, 0)) DEC_QTY,
nvl(sum(b.quantity),0) TOT_SALES
   from jl_inventory_all_v a, jl_line_items b
   where a.sku = b.sku
     and b.category = 'Subwoofers'
     and b.true_warranty = 'N'
     and substr(b.sku,1,1) = '9'
   group by to_char(b.invoice_date, 'YYYY'), a.sku, a.part_number, a.model
/

I need to include the total sku quantity for the previous year as well as the
previous 2 years (in 2 separate columns) in this view also.  How do I go about
doing this?   The database version is 8.1.7.0.1.

      and we said...
With 816 EE and up, we have very very cool analytic functions.  In your case,
the answer is the LAG function:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select year, sku, part_number, model,
tot_sales,
  2             lag(tot_sales) over ( order by year ) prior_year,
  3             lag(tot_sales,2) over ( order by year ) two_years_ago
  4    from jl_test_summary_v;

YEAR        SKU PART_NUMBER      MODEL  TOT_SALES PRIOR_YEAR TWO_YEARS_AGO
---- ---------- ----------- ---------- ---------- ---------- -------------
1999       9876           1          1       7800
2000       9876           1          1      15600       7800
2001       9876           1          1       7800      15600          7800

The lag function lets us reach back to prior (or subsequent with LEAD) rows in
the result set and incorporate their results in the current row.

If you have my book, I have a whole chapter on these functions.  You can also
refer to the SQL reference manual (list of functions and syntax) AND the data
warehousing guide (exapmles, explanation) for details.

(i created dummy data for this example via)

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into a values ( 9876, 1, 1 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2    for i in 1 .. 12
  3    loop
  4          insert into b values ( 9876, add_months(to_date('01-jan-2001'),
i-1), 100*i, 'Subwoofers', 'N' );
  5    end loop;
  6    insert into b
  7    select sku, add_months( invoice_date, -12 ), quantity, category,
true_warranty from b;
  8    insert into b
  9    select sku, add_months( invoice_date, -12 ), quantity, category,
true_warranty from b;
 10  end;
 11  /
Review
      & Followup

      Rating: 5
Very cool
      function!  January 09, 2002
Reviewer:  Brett
      Gaston  from Ft. Lauderdale, FL
I do have your book and will definitely research more about this.  Thanks for
your help.

      Rating: 3
use of Lag
      function  April 04, 2002
Reviewer:
      A reader
The query shown may not work if the data for the year 2000 is missing. For e.g.
If we had data for year 1999 and 2001 only. Lag function will show the data og
1999 in year ago column. Which is wrong.

      Rating: 5
What if I want
      to see the cumulative sum of previous year and this year
      together?  July 11, 2003
Reviewer:  Fan Liu
       from Canada
Tom, how could I build a query to retrieve the cumulative totals?

YEAR        SKU PART_NUMBER      MODEL  TOT_SALES PRIOR_YEAR  THIS_YEAR
---- ---------- ----------- ---------- ---------- ---------- ----------
1999       9876           1          1       7800                  7800
2000       9876           1          1      15600       7800      23400
2001       9876           1          1       7800      23400      31200

I came up with a solution like this:

create table sales (
prod_id number,
sale_amount number,
sale_date date)

insert into sales values(1, 100, sysdate - 90);
insert into sales values(2, 200, sysdate - 80);
insert into sales values(3, 300, sysdate - 60);
insert into sales values(1, 200, sysdate - 40);
insert into sales values(2, 400, sysdate - 30);
insert into sales values(4, 500, sysdate - 10);

SELECT sale_month,
       LAG(cumulative_total, 1)
           OVER (ORDER BY sale_month) total_of_last_month,
       LAG(cumulative_total, 0)
           OVER (ORDER BY sale_month) total_of_this_month
FROM
( SELECT trunc(sale_date, 'MONTH') sale_month,
         SUM(SUM(sale_amount))
            OVER (ORDER BY trunc(sale_date, 'MONTH')) cumulative_total
    FROM sales
   GROUP BY trunc(sale_date, 'MONTH'));

SALE_MONTH            TOTAL_OF_LAST_MONTH TOTAL_OF_THIS_MONTH
--------------------- ------------------- -------------------
4/1/2003                                                  300
5/1/2003                              300                 600
6/1/2003                              600                1200
7/1/2003                             1200                1700

How could I simplify the query without using two levels of SQL analytical
function?

      Rating: 5
(Continue my
      previous message)  July 11, 2003
Reviewer:  Fan
      Liu  from Canada
I tried this in 9.2.0.3:

SELECT
trunc(sale_date, 'MONTH') sale_month,
LAG(SUM(SUM(sale_amount))
        OVER (ORDER BY trunc(sale_date, 'MONTH')))
    OVER (ORDER BY trunc(sale_date, 'MONTH'))
 cumulative_total
FROM sales
GROUP BY trunc(sale_date, 'MONTH'));

It says:
ORA-00935: group function is nested too deeply

Will 10i support this kind of nested analytical functions?

          Followup:

why are you worried about making the query more readable and understandble by
nesting it?

This is where inline views excel, you need an inline view (you want to get the
LAG of another analytic function -- chicken and egg here -- in order to analyze
something, you need to have it)

you will use the nested inline views.

      Rating:
      5
  July 21, 2003
Reviewer:
      A reader
Tom,

I have  a similar kind of situation in which I have a table called contacts and
these are the columns

comp_id varchar2(10),
comp_name varchar2(20),
fname varchar2(30),
lname varchcar2(30),
telno varchar2(15),
faxno varchar2(15)

we record contacts of our customers, this table has entries which has redundant
data

For example lets say someone from "Oracle" calls, his entry is made,next time
when this guy calls up and says "Oracle Corp" ( this time Oracle Corp), so a new
entry is made,
so same company but 2 entries (at times more than two example GE/General
Electric/GE Corp )and yes tel no might be different as that person might be
calling from different extension, but the first 6 digits might be the same and
last 3 digits might be different.

Now i want to find out the records baased on the criteria that have comp_name
similar or same telno or the first six digits of tel. no are similar  or fax no.
is same or the first 6 digits are similar.

Am thinking of using analytic functions lag and lead,can you provide some help
on that.

Thanks

          Followup:

what you are asking to do is actually quite complex -- there are entire products
built to do just this.

I don't see how lag/lead or any analytics would work -- as you need an order by
to find data "near" other data.

      Rating:
      5
  July 22, 2003
Reviewer:
      A reader
Thanks Tom for the feedback, can you mention some products that be of help.

          Followup:

we have a tool in our warehouse builder (OWB) to do this sort of names
cleansing.  Most of them are not "real time" solutions (they are very
computationally expensive and run in the background).

You might need to build some sort of customized "search" for the person on the
end of the phone to use to identify a customer -- or run name matching software
at the end of the day.

Anyone else that works in this area out there (I'm not heavy into call center
software myself)?  they might have some ideas -- check back here later to see if
anyone adds something

July 22, 2003 Miodrag -- Thanks for the question regarding
      "floating point arithmetic", version 8.1.7

      You Asked
Hi Tom,

is there a more detailed documentation on floating point arithmetic in Oracle
than what can be found in Oracle 8i Concepts. I know NUMBER is a 38 digit
precision decimal floating point format, but couldn't find out much more than
that. I'm faced with a task of porting an application written in C++ to Oracle,
without phasing out the existing application. It uses double's naturally, and I
need
to port not only data but also calculations with as little discrepancy as
possible. Any articles on best practices and pitfalls in such a task would be
much appreciated.

Thanks,
Miodrag

      and we said...
Well, IEEE floating point math will be much much less precise then the math we
do internally.

The only way to get the same exact results, will be to retain the computations
in C/C++ (you can use external procedures, C called from PLSQL, if the
computations are to be called from within the database).

We use what amounts to BCD numbers when doing arithmetic.  We can represent
EXACTLY what IEEE approximates.

So, for example, consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> set numformat
9999999.9999999999999999999999999999999999999999
ops$tkyte@ORA817DEV.US.ORACLE.COM> select 123212.0/0.32132 from dual;

                                 123212.0/0.32132
-------------------------------------------------
  383455.7450516618946844267396987426864185000000

In C, we code:

void main(int argc, char * argv[] )
{
double x = 123212.0;
double y = 0.32132;
double x_over_y = x/y;

        printf( "%.17g/%.17g = %.17g\n", x, y, x_over_y );
}

Well, the problems start right away cause 0.32123 is not representable exactly
as a number in IEEE:

$ ./test
123212/0.32131999999999999 = 383455.74505166191

The 0.32132 is not a number a double can "be" - it approximates it.  If we take
what C gets and compare that to what you'll get in the database:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select 123212.0/0.32132 - 383455.74505166191
from dual;

              123212.0/0.32132-383455.74505166191
-------------------------------------------------
       -.0000000000153155732603012573135815000000

Depending on the scale and precision of the numbers -- the differences can
become literally huge over time.

So, if you need the precision of IEEE floating point, you may well be calling
your C from PLSQL or SQL in order to get it.

Review &
      Followup

      Rating: 3
Wish there was
      more  January 22, 2002
Reviewer:  Miodrag
      Milenkovic
Thanks for the examples Tom, I fully agree that decimal floating point is more
suitable for the real world apps.
I finally decided I'll get 15 decimal significant digits from a double since
that's the most one can store in a double and get back the same over the whole
range of a double. The decimal numbers that go in the app don't get specified
with that precision anyway. I'll do the calculations in Oracle since performance
is more of an issue than an absolute lack of discrepancy. Let me also highlight
for the audience the 17 that you used in printf("%.17g",..) is the maximum
required number of decimal digits to represent the double in decimal so that you
can store it back to the same double.

Hate to be a pain and a smartass, but what I was looking for is detailed
documentation, there's more to floating point math than radix, precision and
range, IEEE 754 is specific on a lot of points such as rounding, guard digits,
gradual underflow, subnormal numbers and such pertaining to fp regardles of
radix and precision. I'm a bit surprised this is not fully documented in Oracle.
Even the documentation for round function fails to mention that it rounds away
from 0 (i.e. +- 0.5 rounds to +-1 resp.), it's left to me to discover that and
hope that's how it will always behave. I wish it was fully spelled out even if
majority of users may be blissfully unconcerned.

How about Java though, that would be a good alternative. Couldn't find much
about Aurora's compliance with JVM spec or IEEE 754 either.

Thanks,
Miodrag

      Rating: 2
ORA-03113:
      End-of-file on communication channel  July 22, 2003

Reviewer:  Danny Wang  from NY, USA
Hi Tom,

I encountered a problem running a query. My database is version 8.1.7.4.1 on
Win2000. Here is the script I used.

CREATE TABLE T1
(
  ACCT VARCHAR2(5),
  AMOUNT NUMBER
);

INSERT INTO T1 VALUES
 ('AAA', -1.0000000000000000000000000000000000001);
INSERT INTO T1 VALUES
 ('A', -1.999999999999999999999999999999999998);
INSERT INTO T1 VALUES
 ('A', 2);
INSERT INTO T1 VALUES
 ('A', 9999.99999999999999999999999999999999996);
INSERT INTO T1 VALUES
 ('A', -10000);

COMMIT;

SELECT ACCT, SUM(AMOUNT)
  FROM T1
 GROUP BY ACCT;

SELECT ACCT, SUM(AMOUNT)
*
ERROR at line 1:
ORA-03113: end-of-file on communication channel

Do you think Oracle doesn't do floating point arithmetic correctly? Also,
sometimes after the error occurred, Oracle won't respond and no new connections
could be made.

Thanks

          Followup:

it works in 9i, fails in 8i -- please -- open a tar with support.  You have the
perfect reproducible test case.

      Rating: 4
RE: ORA-03113:
      End-of-file on communication channel  July 22, 2003

Reviewer:  Danny Wang  from NY, USA
Hi Tom,

Thanks for quick reply. We've opened a tar to Oracle.

July 22, 2003 Kamal -- Thanks for the question regarding "Open
      Cursor Count...", version 9.2.0.3

      You Asked
Hi Tom,
I was expecting the count of "opened cursors current" from my last query to be
equal to 1 (one). But it still shows me the count as two even after closing the
cursor that I opened earlier.

Any reason it should be two and not one?
Thanks,

(my_stats view is same as you described in your book expert one-on-one Oracle
page 495)

SQL> select * from my_stats where name = 'opened cursors current' ;

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
opened cursors current                                                    1

1 row selected.

SQL> create or replace package cur is
  2    cursor cur is
  3      select * from dual ;
  4  end ;
  5  /

Package created.

SQL> begin
  2    open cur.cur ;
  3  end ;
  4  /

PL/SQL procedure successfully completed.

SQL> select * from my_stats where name = 'opened cursors current' ;

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
opened cursors current                                                    2

1 row selected.

SQL> begin
  2    close cur.cur ;
  3  end ;
  4  /

PL/SQL procedure successfully completed.

SQL> select * from my_stats where name = 'opened cursors current' ;

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
opened cursors current                                                    2

1 row selected.

SQL>

      and we said...
because plsql caches things nicely for us for performance reasons.

the cursor is opened but marked as "closable", it is the way it was designed.
If you really needed a cursor elsewhere, the database would in fact close that
cursor.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3512824755435

Review &
      Followup

      Rating:
      5
Excellent...  June 23, 2003
Reviewer:
      Kamal Kishore  from New Jersey, USA
Hi Tom,
That was a good explanation. WE can see exactly to what great lengths PL/SQL can
go to help us run our software faster (it contributes whereever it can).

To see what would happen if I used DBMS_SQL to manipulate a cursor, I did the
following tests. Here the count in the last query drops down to one. This seems
to indicate that DBMS_SQL cusrsors are managed outside the scope of PL/SQL
environment, and it is not able to cache it as for other cursors, Is this
correct, or there is another explanation to this?
Thanks,

SQL> select * from my_stats where name = 'opened cursors current' ;

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
opened cursors current                                                    1

1 row selected.

SQL> variable cur number
SQL> exec :cur := dbms_sql.open_cursor ;

PL/SQL procedure successfully completed.

SQL> select * from my_stats where name = 'opened cursors current' ;

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
opened cursors current                                                    2

1 row selected.

SQL> exec dbms_sql.close_cursor(:cur) ;

PL/SQL procedure successfully completed.

SQL> select * from my_stats where name = 'opened cursors current' ;

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
opened cursors current                                                    1

1 row selected.

SQL>

          Followup:

dynamic sql and REF CURSORS (static or dynamically opened ones) are not
cachable.

this applies to STATIC SQL in PL/SQL stored procedures/functions/packages only,
it is unique in this respect.

      Rating: 5
How to find if
      cursor is "closable"?  July 22, 2003
Reviewer:
      Ashish  from NJ, USA
As you said above "
the cursor is opened but marked as "closable", it is the way it was designed"

How can we find if the cursor is marked as "closable"? Is
there dictionary view which can give us this information?

          Followup:

no, there isn't as far as I know.

July 22, 2003 --
      Thanks for the question regarding "BEGIN anonymous; END;", version 817

      You Asked
Hi,
 I have a Package Procedure.
CREATE OR REPLACE PACKAGE pkg_anonymous
IS
   PROCEDURE p_anony (ip_num1 NUMBER, ip_num2 NUMBER);
END;
/
CREATE OR REPLACE PACKAGE BODY pkg_anonymous
IS
   PROCEDURE p_anony (ip_num1 NUMBER, ip_num2 NUMBER)
   IS
      v_cnt   NUMBER (4);
      v_val   VARCHAR2 (20);
   BEGIN
      BEGIN -- Anonymous ?
         SELECT dname
           INTO v_val
           FROM dept
          WHERE deptno = ip_num1;
      EXCEPTION
         WHEN NO_DATA_FOUND
         THEN
            BEGIN -- anoymous ?
               SELECT dname
                 INTO v_val
                 FROM dept
                WHERE deptno = ip_num2;
            EXCEPTION
               WHEN NO_DATA_FOUND
               THEN
                  DBMS_OUTPUT.put_line ('NO Data found ');
            END;
      END;
   END;
END;
/

Q1)What are Anonymous Blocks?
Q2)Are nested Blocks inside named Plsql program units also Anonymous ? As is in
the above Example ?
Q3)Will Anonymous Blocks be always Hard Parsed ?
Q4)The above nested blocks I think will use Bind variables as it uses
input parameters. SO it will be re-used. Is it still a BAD Anonymous Block.Or is
it BAD to code in the above way because of dange of anonymous blocks ?
Q5) Even procedures ,packaged procedures need an anonymous block to execute. So
("Q2") is it bad to have nested begin end in such procedures even though they
may use bind variables ?
Q6)what query will detect Anonymous Blocks in the  SGA.Any scripts?

Thank Ya.

      and we said...
q1) an anonymous block is what you would execute in order to run
pkg_anonymous.p_anony

begin
   pkg_anonymous.p_anony;
end;

that is an anonymous block -- it is not named.  the top level call to a
procedure/package/function is an anonymous block.

You have another anonymous (un-named) block in your example as well

q2) yes.

q3) no, only the first time they are seen in the shared pool -- JUST like SQL,
no different.

q4) anonymous blocks are nothing to be fearful of.  They are a programming
construct.  They incurr NO additional parse overhead at all.  In your example,
there will be ONE thing parsed at runtime -- the anonymous block:

begin
   pkg_anonymous.p_anony;
end;

the interior block you have is NOT a separate statement, not parsed separately.
That anonymous block we execute to run your code will either be hard or soft
parsed depending on whether someone else ran it or not.

q5) no

q6) just query v$sql and look for things that begin with BEGIN and DECLARE.  No
need to be frightened of them -- they are NORMAL, not to be feared.
Review &
      Followup

      Rating: 5
Its To the
      Point   June 18, 2002
Reviewer:
      A reader
Thanx Much

      Rating:
      5
  June 19, 2002
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating: 5
proving that
      anonymous blocks are parsed once.  July 21, 2003

Reviewer:  A reader
I am trying to prove that anonymous blocks are parsed
only once.

I tried executing a simple anonymous block twice through
sqlplus - from the tkprof output it seems to get parsed
twice. Here is the output.

"declare
  l_count integer;
begin
  select count(*)
  into l_count
  from all_objects test_anon;
end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.01       0.00          0          0          0           0
Execute      2      0.00       0.00          0          0          0           2
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.01          0          0          0           2

Following was the test code I ran:

declare
  l_count integer;
begin
  select count(*)
  into l_count
  from all_objects test_menon_anonymous;
end;
/
/

Is this a behavior specific to sqlplus?

I know you are on vacation - please enjoy yourself and
take all the time to answer this!

Best Regards

          Followup:

parsing is 100% controlled by the client.

you would have to write a client that

a) parsed
b) executed
c) executed
d) closed

(eg: you could simulate this with dbms_sql in plsql, or a prepared statement in
jdbc/odbc)

      Rating: 5
anonymous
      blocks parsing  July 21, 2003
Reviewer:
      A reader
"I tried executing a simple anonymous block twice through
sqlplus".

Well from Tom's link
"
http://asktom.oracle.com/pls/ask/f?p=4950:8:7632120907568215256::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:7507706875149,
it looks like, across calls anonymous blocks are parsed
again and again - whereas within the same call all
sql statements are parsed only once. The behavior
for a procedure should be that across calls or within the
same calls the sql statements within a procedure gets
cached. In that sense, a packaged procedure seems preferable over anonymous
blocks?

Have a lovely vacation, Tom!
Menon:)

      Rating:
      5
  July 21, 2003
Reviewer:
      A reader
"parsing is 100% controlled by the client.

you would have to write a client that

a) parsed
b) executed
c) executed
d) closed

(eg: you could simulate this with dbms_sql in plsql, or a prepared statement in
jdbc/odbc)
"

Hi Tom
This raises a basic doubt. If I am submitting an
anonymous block from sqlplus, first time it gets
hard parsed - but second time should not it be
found in the shared pool (as anonymous blocks in this
respect are no different from any other construct -
they also get stored in shared pool for reuse)?
Should not it be reused regardless of any client behavior?

It seems that the second time it  is found in shared pool but it needs to get
soft parsed even then (because
with each statement, sqlplus opens, parses, executes
and closes.) It is kind of like the difference between
Statement and PreparedStatement in JDBC. Please confirm
if this logic is correct.

Also, the parse count shown by the trace file - is it
a hard parse or a soft parse or is there no way to tell?
From what it looks like, it seems it could be either
and there is no way to tell - please confirm if this
thinking is correct. (I guess the first one of 10 parses
for the say, 10 parses would be hard parse, remaining
9 have to be soft parses?)

Thank you for your generosity to the Oracle community -
you are the best!

          Followup:

it will be reused -- it will be soft parsed -- but it WILL BE PARSED.

it is not anything LIKE statement and preparedStatement in jcdb/odbc.  Not at
all.

In tkprof -- you'll see a line "misses in library cache" associated with the
statement.  that is the count of hard parses for that statement.

      Rating: 5
answer to part
      of previous question  July 21, 2003
Reviewer:
      A reader
"Also, the parse count shown by the trace file - is it
a hard parse or a soft parse or is there no way to tell?"

Well, if misses in Library cache is 0, then all parses
indicated by parse count are soft parses. If misses
in lib. cache = 1 and parse count = 10, then there
are 9 soft parse and one hard parse.

Regards
Menon:)

      Rating: 5
related
      question  July 21, 2003
Reviewer:
      A reader
When I execute the following sql
et echo on
declare
  l_count integer;
begin
  select count(*)
  into l_count
  from all_objects test_anon1;
  select count(*)
  into l_count
  from all_objects test_anon2;
end;
/
create or replace procedure p
is
  l_count integer;
begin
  select count(*)
  into l_count
  from all_objects test_proc1;
  select count(*)
  into l_count
  from all_objects test_proc2;
end;
/
-----
I get the following tkprof output. As shown in the output
below,tkprof separately prints the select statements inside
the anonymous blocks showing the two getting parsed.
However, for the procedure, there is only one entry in
tkprof output.

What significance does this observation have?

Thank you!!

-- output begins
...
declare
  l_count integer;
begin
  select count(*)
  into l_count
  from all_objects test_anon1;
  select count(*)
  into l_count
  from all_objects test_anon2;
end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.01       0.01          0          0          0           1
....
SELECT count(*)
  from all_objects test_anon1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.02          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      4.51       4.50          0     170409          3           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      4.54       4.53          0     170409          3           1

Misses in library cache during parse: 1
..

SELECT count(*)
  from all_objects test_anon2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.02          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      4.54       4.53          0     170409          3           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      4.56       4.56          0     170409          3           1

Misses in library cache during parse: 1
....

create or replace procedure p
is
  l_count integer;
begin
  select count(*)
  into l_count
  from all_objects test_proc1;
  select count(*)
  into l_count
  from all_objects test_proc2;
end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.02       0.04          0          0          1           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.02       0.04          0          0          1           0

Misses in library cache during parse: 1

          Followup:

well, the anonymous block actually RAN CODE.

the create procedure just compiled some code into the database.

they are apples and oranges in comparision.  one compiled and ran a program, the
other compiled.

      Rating: 5
sory about
      that!!  July 22, 2003
Reviewer:
      A reader
"well, the anonymous block actually RAN CODE."

that was a stupid question (the previous one where
I compared apples and oranges!:)) Donno what I was thinking!

Thanx again for clarifying!

      Rating: 5
parsing et.
      all  July 22, 2003
Reviewer:  A reader

"it will be reused -- it will be soft parsed -- but it WILL BE PARSED. it is not
anything LIKE statement and preparedStatement in jcdb/odbc.  Not at
all."

Hi Tom
Thanx! - I think what you are saying is this.

The second time the parse refers to a soft parse which
you can not avoid unless you execute the same statement
again (after keeping the curosr open).

Also, comparison with preparedStatement and statement
was done with following analogy. If you want to reuse
your sql statement, you have to use PreparedStatement
(or CallableStatement for stored procedures) instead
of Statement so that you can reuse the same sql statement
with bind variables. That is how I was comparing it
(it was very thin comparison though.) Also, this feature
prevents only *hard* parsing (unless you reuse the
same statement and execute it more than once as you
had shown earlier - open; execute;execute; close)
Similarly, the anonymous block in the previous question
gets reused (and gets soft parsed.)
Sorry if the analogy was not clear!

Thank you!

      Rating: 5
anonymous
      blocks versus procedure  July 22, 2003
Reviewer:
      A reader  from ca
Hi Tom,
I ran the following code to compare anonymous blocks
versus procedures.
----
set echo on
declare
  l_count integer;
begin
  select count(*)
  into l_count
  from all_objects test_anon1;
  select count(*)
  into l_count
  from all_objects test_anon2;
end;
/
/
create or replace procedure p
is
  l_count integer;
begin
  select count(*)
  into l_count
  from all_objects test_proc1;
  select count(*)
  into l_count
  from all_objects test_proc2;
end;
/
exec p;
exec p;
-----

As you can see I execute both the anonymous block and
the procedure twice. From the tkprof output below,
it seems that  the sql inside anonymous block gets soft parsed twice - whereas
the one in the procedure gets
parsed only once.

Q1. This seems to indicate that we should always prefer packaged procedures
instead of anonymous blocks in our client code - Is that correct?

Q2. In the following link, you discuss trigger
versus procedure

http://asktom.oracle.com/pls/ask/f?p=4950:8:7632120907568215256::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:7507706875149,
Is the *real* reason for which the statements inside
triggers get parsed twice is that they are treated
like anonymous blocks whereas the statement inside
the procedure gets cached (meaning it is not closed
actually) across calls?

Thank a bunch!!!

---tkprof output (edited to fit in space)
declare
  l_count integer;
begin
  select count(*)
  into l_count
  from all_objects test_anon1;
  select count(*)
  into l_count
  from all_objects test_anon2;
end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      2      0.00       0.00          0          0          0           2
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          0          0           2

Misses in library cache during parse: 0
...
SELECT count(*)
  from all_objects test_anon1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        2      9.34       9.33          0     340818          6           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        6      9.34       9.33          0     340818          6           2

Misses in library cache during parse: 0
..
SELECT count(*)
  from all_objects test_anon2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        2      9.27       9.26          0     340818          6           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        6      9.27       9.26          0     340818          6           2

Misses in library cache during parse: 0
....
ELECT count(*)
  from all_objects test_proc1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.02          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        2      6.46       6.45          0     171886          6           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      6.48       6.47          0     171886          6           2

Misses in library cache during parse: 1
...
SELECT count(*)
  from all_objects test_proc2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.02          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        2      6.62       6.61          0     171886          6           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      6.65       6.64          0     171886          6           2

Misses in library cache during parse: 1

....

          Followup:

q1) 100% correct

q2) I believe so, the trigger compiles the code at trigger compilation time -
but it is compiled as an anonymous block so the cursors are not "saved" like
they are for packages and procedures.

      Rating: 5
thanx
      Tom!!  July 22, 2003
Reviewer:
      A reader
As usuall you clarified some more things for me!:)

      Rating: 3
Enclosing sql
      statement with begin/end block  July 22, 2003

Reviewer:  Pui  from NJ, USA
In my workgroup, they have a practice of enclosing SQL statement with begin and
end (without exception block). Is there any sense of doing that? I do not think
so.

          Followup:

no, there is not - it only runs slower that way.

July 22, 2003 Miroslav -- Thanks for the question regarding
      "Statistics and optimization with VARRAYs", version 8.1.7

      You Asked
Suppose a table uses a VARRAY of a composite type (say, Address_Array_Type that
is VARRAY(500) of Address_Type).  What kind of statistics could Oracle collect
in this situation (e.g., number of entries in VARRAY, number of distinct
ZIP_CODE?).

Can you say if there are any disadvantages in general in optimization of tables
that use VARRAYs for nested tables (assume that VARRAY can be even 2000 elements
or so), as opposed to optimization of purely relational tables (say, with a
child table PREVIOUS_ADDRESSES)?

Thanks.

      and we said...
With varrays, stats don't really matter.

They are not separate tables (they are LOBS).
Their size is not relevant (they are stored out of line after about 4000 bytes).
You do not use them in where clauses (they are not part of the predicate).
You only select on them.

You only SELECT them, it is like fetching a LOB (with the overhead of a "parse"
to make it structured again)
Review &
      Followup

      Rating: 5
Specifying
      conditions on VARRAY's  October 29, 2001
Reviewer:
      Miroslav  from Santa Clara, CA
Thanks a lot.  You did answer my question.

You can still write a condition on the element of the VARRAY:

select name, prev.city, prev.state, prev.zip_code
  from people p, table( p.previous_addresses ) prev
 where prev.state = 'PA';

(excuse the copyrights ;-)

So, we could say that the optimization of a query involving a nested table (or a
simple child table) for PREVIOUS_ADDRESSES would benefit from statistics on the
columns, whereas VARRAY will not give us such capability, right?

Thanks a lot for this great service!

- Miroslav

          Followup:

But it's not really a table and its not indexed...  Its more of a "trick".

If you plan on flattening this object all of the time -- you might consider good
old fashioned relational tables with true parent/child relationships.  You'll
have more opportunities to tune such a setup.  In the above example, you have no
choice but to full scan the people table, synthesize the rows in the varray, and
apply the where clause.

In a true parent/child, an index on prev.state could be used if applicable.

      Rating: 5
Specifying
      conditions on VARRAY's  October 29, 2001
Reviewer:
      Miroslav  from Santa Clara, CA
Thanks a lot.  You did answer my question.

You can still write a condition on the element of the VARRAY:

select name, prev.city, prev.state, prev.zip_code
  from people p, table( p.previous_addresses ) prev
 where prev.state = 'PA';

(excuse the copyrights ;-)

So, we could say that the optimization of a query involving a nested table (or a
simple child table) for PREVIOUS_ADDRESSES would benefit from statistics on the
columns, whereas VARRAY will not give us such capability, right?

Thanks a lot for this great service!

- Miroslav

      Rating: 5
Thank
      you.  October 29, 2001
Reviewer:  Miroslav
       from Santa Clara, CA
Thanks again for such prompt responses.

Sorry for the duplicating the follow up.

BTW, for those who want more information about using VARRAYS (and Oracle
pragmatics in general), please see:

Expert One on One Oracle, by Thomas Kyte

http://www.amazon.com/exec/obidos/ASIN/1861004826
The "trick" above is copied from Tom's book.

-Miroslav

          Followup:

ahh, now i understand the "copyright" comment ;)

Yes, it is from the book and I went back and found right after that example I
have the text:

...
One big difference here is that in the nested table implementation, we could
have created an index on the nested table's STATE column, and the optimizer
would have been able to use that. Here, the STATE column cannot be indexed.
......

(so at least I'm consistent!)

      Rating: 5
Create Varray
      of CLOB  July 22, 2003
Reviewer:
      A reader
Hi Tom,

In 9i Documentation:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96594/adobjbas.htm#467559
A varray cannot contain LOBs. This means that a varray also cannot contain
elements of a user-defined type that has a LOB attribute

But in 9.2.0.3:

SQL> create type varray_of_clob as varray(10) of clob;
  2  /

Type created.

SQL> create type type_of_clob as object (attr clob);
  2  /

Type created.

SQL> create type varrary_of_type_of_clob as  varray(10) of type_of_clob;
  2  /

Type created.

Both succeeded. Is it just a document error or my mistake?

          Followup:

must be a doc bug


Subject | Article | Last Updated (East coast time)
Feedback on how to un-pivot a table | article only | Jul 23, 2003 07:44 am
Export/Import utility | article only | Jul 23, 2003 07:50 am
trace files location | article only | Jul 23, 2003 08:37 am
PL/SQL beginner book | article only | Jul 24, 2003 08:43 am
ARRAY PROCESSING | article only | Jul 24, 2003 08:45 am
mutating table & constraining table | article only | Jul 24, 2003 04:27 pm
Defragment Tablespace / Datafiles and Some security Tips | article only | Jul 24, 2003 04:30 pm
Calculating the Sizes | article only | Jul 25, 2003 06:41 am
How to achieve this functionality? | article only | Jul 25, 2003 07:02 am
RMAN and archivelogs | article only | Jul 25, 2003 07:03 am
logminer privileges problem again... | article only | Jul 25, 2003 07:06 am
Space difference between dba_segments, dba_tables, show_space | article only | Jul 25, 2003 04:54 pm
Extents Question | article only | Jul 25, 2003 04:59 pm
V$SQL_PLAN_STATISTICS | article only | Jul 26, 2003 04:17 pm
Sqlldr | article only | Jul 26, 2003 07:12 pm
explain plan | article only | Jul 27, 2003 09:59 am
What are the impacts when using tables in queries that columns are not fetched forne | article only | Jul 27, 2003 07:36 pm
re: variation to inline view or repeatable column calc | article only | Jul 28, 2003 08:16 am
querying a clob | article only | Jul 28, 2003 08:52 am
bind variables/ define confusion | article only | Jul 28, 2003 10:53 am
When to recompile packages/procedures/functions | article only | Jul 28, 2003 01:10 pm
client server, application server, 3 tier | article only | Jul 28, 2003 07:48 pm
Pointers in c | article only | Jul 29, 2003 06:49 am
pessimistic locking vs optimistic locking | article only | Jul 29, 2003 07:07 am
Create_stored_outlines parameter in init.ora | article only | Jul 29, 2003 07:12 am
Quick Question | article only | Jul 29, 2003 07:14 am
Rollback and rollforward | article only | Jul 29, 2003 07:17 am
Oracle Form*Builder and LOBs | article only | Jul 29, 2003 07:25 am
Why the explan is same with "IN" and "EXISTS" | article only | Jul 29, 2003 08:12 am
Storing documents Database v/s File system | article only | Jul 29, 2003 11:10 am
Lock Table | article only | Jul 29, 2003 11:15 am
Analitic Function ? | article only | Jul 29, 2003 12:13 pm
Bad performance using sys.dba_role_privs | article only | Jul 29, 2003 01:14 pm
Analytic Function with a CONNECT BY query | article only | Jul 29, 2003 01:42 pm
Date Question | article only | Jul 29, 2003 02:06 pm
redo log in clob column | article only | Jul 29, 2003 02:11 pm
Import ignore=y | article only | Jul 29, 2003 08:04 pm
Avoid runtime extent allocation | article only | Jul 29, 2003 08:17 pm
row(s) 1 - 38 of 38
July 23, 2003 Mike -- Thanks for the question regarding "Feedback on how to un-pivot a table", version 8.1.7
You Asked
To be brief I frequently am asked to produce un-pivots of data *and* calculate
values.  These calculations may also vary by department.  Hard coding these
views for a rather fluid calculation system and then to keep versions per
department wouldn't be feasible as our department structures and calculation
models change monthly; hard-coding would be maintenance prohibative since I am
the only developer for the company.

I have read your examples in your excellent book on Analytic Functions (way
cool) but they deal with pivoting a table (rows to columns) not columns to rows.

 Because of this I have resorted to writting my own domain lookup function that
uses bind variables and a lookup table that allows my calculation model to vary
by department.

Rather than hard-coding a view I wrote a small
domain lookup function that allows me flexibility (at the expense of cpu
time).  I simply wanted to share the function and see if you
had any comments and to see if you might show me how to get rid of the dlookup
function by hardcoding the below example into a simple view that can un-pivot
the data *and* calculate values per row per deptment *and* stay flexible.  At
minimum I thought my
small function might be useful for other readers.  See below for an example of
un-pivoting a table that seems more maintainable to me than using static views
if your view needs to change frequently.

Pretend that I have given two departments id's 1 and 2 and that the values of a
and b are important to a calculation they need.  Please note that at any time a
department may appear or disappear and that each department calculates their
values differently.

temp table
dept a    b
=======================================
1    1    2
2    4    5

Per department, per column this is the calculation that each one wants.

temp_lkup table
dept  col    calc
=======================================
1     a      a
1     b      b*2
2     a      a+6
2     b      b*3

desired output
dept    col    orig    calc    val
=======================================
1       a      1       a       1
1       b      2       b*2     4
2       a      4       a+6     10
2       b      5       b*3     15

<script>
create table temp (dept integer, a integer, b integer)
/
insert into temp select 1,1,2 from dual
/
insert into temp select 2,4,5 from dual
/
commit
/

select * from temp
/

create table temp_lkup (dept integer, col varchar2(16), calc varchar2(16))
/
insert into temp_lkup select 1, 'a', 'a' from dual
/
insert into temp_lkup select 1, 'b', 'b*2' from dual
/
insert into temp_lkup select 2, 'a', 'a+6' from dual
/
insert into temp_lkup select 2, 'b', 'b*3' from dual
/
commit
/
select * from temp_lkup
/

CREATE OR REPLACE  FUNCTION DLOOKUP_BIND_ONE  (vField in
    varchar2, vTable in varchar2, vWhere in varchar2 default NULL,
    vBind in varchar2) return varchar2
AUTHID CURRENT_USER
    as
       vVar varchar2(4000);
       vQuery varchar2(4000);
    begin

       -- exit if field is null
       if vfield is null then
         return null;
       end if;

       -- exit if table is null
       if vtable is null then
         return null;
       end if;

       vQuery := 'select '||vField||' from '||vTable;
       if (vWhere is not NULL) then
            vQuery := vQuery ||' where '||vWhere;
       end if;
       execute immediate vQuery into vVar using vBind;
       return vVar;
       exception
         when NO_DATA_FOUND then
           return null;
         when others then
           raise;
   end;
/

-- now to get the result set
select temp.dept, temp_lkup.col, temp_lkup.calc,
  to_number(nvl(dlookup_bind_one(col, 'temp', 'dept=:a', temp.id),0)) orig,
  to_number(nvl(dlookup_bind_one(calc, 'temp', 'dept=:a', temp.id),0)) val
  from temp, temp_lkup
  where temp.dept = temp_lkup.dept
/
</snip>
      DEPT COL              CALC                   ORIG        VAL
---------- ---------------- ---------------- ---------- ----------
         1 a                a                         1          1
         1 b                b*2                       2          4
         2 a                a+6                       4         10
         2 b                b*3                       5         15

Any ideas as to how I could improve the efficiency of this code but still keep
the lookup of the calculations per deptartment in a table?  Thanks.
and we said...
I might do it with ROWID:

CREATE OR REPLACE
FUNCTION DLOOKUP_BIND_ONE(vField in varchar2,
                          vTable in varchar2,
                          vRowid in rowid ) return varchar2
AUTHID CURRENT_USER
as
    vVar varchar2(4000);
begin
    if ( vField is null )
    then
       return null;
    end if;
    execute immediate
    'select ' || vField ||
     ' from ' || vTable ||
    ' where rowid = :rowid'
    into vVar using vRowid;

    return vVar;
end;
/

-- now to get the result set
select temp.dept, temp_lkup.col, temp_lkup.calc,
  to_number(nvl(dlookup_bind_one(col, 'temp', temp.rowid ),0)) orig,
  to_number(nvl(dlookup_bind_one(calc, 'temp', temp.rowid ),0)) val
  from temp, temp_lkup
  where temp.dept = temp_lkup.dept
/

rowid being faster than keyed reads (and obviates the need for a where clause to
be passed as well).

I removed the no_data_found cause it seems to me that that would be a big old
bug if it returned zero rows.  I would hate to get a "everything is OK" result
from my function in that case.

but otherwise it works -- I would suggest setting session_cached_cursors=N when
using this approach since ref cursors are not cached like other plsql cursors
are (and you'll be soft parsing like mad with this)
Review & Followup
Rating: 5
Thanks! May 28, 2002
Reviewer: Mike from Los Angeles, CA
I set cusor_sharing = force for the account that runs this and I have watched
the parse stats during the run and I don't seem to be suffering the soft parse
hit.  Although I didn't know that about REF Cursors not being cached.

As for ROWID (duh?).  Thanks, I didn't even think about that.  Thanks again.

Followup:
Oh you are -- every row returned is doing a soft parse!  If you return 1000
records -- you'll have done 1,000 soft parses.  It is there.

Rating: 5
how to unpivot this April 23, 2003
Reviewer: A reader
in some example on this site you have

pivoted a table and got this result

STATUS          0   1   2   3   4   5   6   7   8+
====================================================
FINAL ACTION    0   0   1   0   0   0   0   0   1

how can I unpivot it  in a single sql stmt ?

2.) how can I get

    1
    13
    10
any static values ( that i give)
using select stmt..

eg.
 select t from dual
which should give me
1
13
10
 in column not in row with out using physical table

Followup:
if there 9 columns to "unpivot" then

select c1,
       decode( r, 1, value1, 2, value2, 3, value3, ...., 9, value9 )
  from T, /* table with 9 columns to unpivot */
       (select rownum r from all_objects where rownum <= 9 )

2) eh? why 1, 13, 10.

(dual is by the way -- a physical honest to goodness table -- at least as of
2003)

but -- search this site for str2table, you'll see how

select * from TABLE( cast(str2table('1,13,10') as myTableType) );

can work for you.

Rating: 5
can't fine it April 24, 2003
Reviewer: A reader
I searched for str2table got 7 results and

all of them are saying search for str2table
i did not find the def. ?

can you give me link ?
or put it in asktom.oracle.com/~tkyte

Followup:
funny, i got 6 and the first one:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:110612348061
hit it.

Rating: 4
thanks, April 24, 2003
Reviewer: A reader

 That's because I searched, as you've said, for
   **str2table**

 and you searced on (str2tbl) may be...

Thanks, any way

Followup:
nope, searched for str2table - just did it again to test.

Rating: 4
How to do this - columns to rows July 22, 2003
Reviewer: subramanian from India
I have a table with 5 columns.
say trans_dt, col1, col2, col3, col4.
The first column is of date type and the rest are number types.
Normal select would give the following results.

Date displayed in dd.mm.yyyy format.

Trans_dt  col1   col2   col3   col4
-------- ------ ----- ------ --------
31.03.2000  100   200    300    400
31.03.2001  500   600    700    800
31.03.2002  400   300    200    900
31.03.2003  500   100    300    200

Could you give me a query to display the
results in the format given below

            trans_dt     trans_dt      trans_dt
col1
col2
col3
col4

Followup:
that is generally the domain of a reporting tool.  However, if you KNOW the
range of trans_dt, you can do it. the following walks through a transpose and
pivot:

ops$tkyte@ORA920LAP> select * from t;

TRANS_DT          C1         C2         C3         C4
--------- ---------- ---------- ---------- ----------
23-JUL-03        432       3823        159       7175
23-JUL-02       9356       2635       5324       6586
23-JUL-01       7073        279       3574       4816
23-JUL-00       1510       6416       5987       9003

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select trans_dt,
  2         'c' || r c,
  3             decode( r, 1, c1, 2, c2, 3, c3, 4, c4 ) c_val
  4    from t,
  5         (select rownum r
  6                from all_objects
  7                   where rownum <= 4)
  8  /

TRANS_DT  C          C_VAL
--------- ----- ----------
23-JUL-03 c1           432
23-JUL-02 c1          9356
23-JUL-01 c1          7073
23-JUL-00 c1          1510
23-JUL-03 c2          3823
23-JUL-02 c2          2635
23-JUL-01 c2           279
23-JUL-00 c2          6416
23-JUL-03 c3           159
23-JUL-02 c3          5324
23-JUL-01 c3          3574
23-JUL-00 c3          5987
23-JUL-03 c4          7175
23-JUL-02 c4          6586
23-JUL-01 c4          4816
23-JUL-00 c4          9003

16 rows selected.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select c,
  2         ( decode( trans_dt, to_date('23-jul-2003'), c_val ) ) c2003,
  3         ( decode( trans_dt, to_date('23-jul-2002'), c_val ) ) c2002,
  4         ( decode( trans_dt, to_date('23-jul-2001'), c_val ) ) c2001,
  5         ( decode( trans_dt, to_date('23-jul-2000'), c_val ) ) c2000
  6    from (
  7  select trans_dt,
  8         'c' || r c,
  9             decode( r, 1, c1, 2, c2, 3, c3, 4, c4 ) c_val
 10    from t,
 11         (select rownum r
 12                from all_objects
 13                   where rownum <= 4)
 14         )
 15  /

C          C2003      C2002      C2001      C2000
----- ---------- ---------- ---------- ----------
c1           432
c1                     9356
c1                                7073
c1                                           1510
c2          3823
c2                     2635
c2                                 279
c2                                           6416
c3           159
c3                     5324
c3                                3574
c3                                           5987
c4          7175
c4                     6586
c4                                4816
c4                                           9003

16 rows selected.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select c,
  2         max( decode( trans_dt, to_date('23-jul-2003'), c_val ) ) c2003,
  3         max( decode( trans_dt, to_date('23-jul-2002'), c_val ) ) c2002,
  4         max( decode( trans_dt, to_date('23-jul-2001'), c_val ) ) c2001,
  5         max( decode( trans_dt, to_date('23-jul-2000'), c_val ) ) c2000
  6    from (
  7  select trans_dt,
  8         'c' || r c,
  9             decode( r, 1, c1, 2, c2, 3, c3, 4, c4 ) c_val
 10    from t,
 11         (select rownum r
 12                from all_objects
 13                   where rownum <= 4)
 14         )
 15   group by c
 16  /

C          C2003      C2002      C2001      C2000
----- ---------- ---------- ---------- ----------
c1           432       9356       7073       1510
c2          3823       2635        279       6416
c3           159       5324       3574       5987
c4          7175       6586       4816       9003

ops$tkyte@ORA920LAP>

Rating: 5
July 23, 2003
Reviewer: Subramanian from India
Thank you very much.
This is what I wanted.

July 23, 2003 -- Thanks for the question regarding "Export/Import utility", version 8.1.5
You Asked
Can I export the database in Oracle 8i and then use the *.dmp file to import
into Oracle 8?
and we said...
Short answer -- yes, sort of.

Longer answer -- you'll want to make sure to use the Oracle8 (not 8i) EXP tool
to export from 8i and the Oracle8 IMP tool to import it.

The EXP files from an earlier release of a database may be imported using the
current releases IMP tool (eg: I can take a v7.x exp and import it with v8.1
IMP).  The EXP files from a later release cannot be imported using that IMP
(eg: I cannot take a v8.x export and import it using a v7.x tool -- even
v8.0 cannot import v8.1 exports).

You use the EXP tool of the release you want to "downgrade" to to export the
data.  If you need to goto a version 7 database from version 8, there are views
(catexp7.sql found in $ORACLE_HOME/rdbms/admin) provided to allow the v7 export
tool to run against the version 8 database.

The problem with using an 8i export against 8.0 import is that there are new
objects in the 8i export that 8.0 simply doesn't recognize.
Review & Followup
Rating: 4
importing data exported by 9.0 into 8.1.7 July 22, 2003
Reviewer: Nikunj from India
Dear Sir,

I have Oracle EE 8.1.7 at one place & Oracle EE 9.0 at another place.
both are not connected to each other and both are in different place.

Now i want to import the data exported by 9.0 server how can i do that please
give me in details steps.

Thanks,
Nikunj

Followup:
you cannot use export and import in this case.

you'll need to

a) install the 817 client on the machine hosting 9i
b) use that export tool over sqlnet on that machine to create the dmp file

Rating: 5
Speaking of import/Export ..how do i exp/imp JOBs July 22, 2003
Reviewer: pasko from Hamburg,Germany
Hi Tom,

Thanks for the above follow-up...

i have a question related slightly to this..

My DBA just did exp/imp of all  user schemas from one database to the other:
using his USERNAME ( which i guess had granted himself sysdba/sysoper role) and
imported a few dozen schemas using the FROMUSER  TOUSER option.

Everything worked fine, except that all user jobs from the imported schemas went
into his schema :
Table dba_jobs shows log_user for all jobs as his Name instead of creating the
JOBs into respective schemas

I also tried to import twice expecting to see duplicated generated constraints(
those starting with SYSnnnn_.... , but i was surprized to see that they were not
Duplicated during import

Do you have a script for getting all JOBs from one schema to another , i mean
similar to your popular getallviews/getallcode/getcode scripts ?
something which would go by the name  getalljobs..

Thanking you in advance,

Regards

Followup:
No I don't -- but it shouldn't be too hard to generate.

Rating: 4
Scheduled "Exp" using .bat file July 23, 2003
Reviewer: Fazle Rabbi from Dhaka, Bangladesh
I can export data everyday automatically using a Export.bat file via Task
Scheduler in Windows2000 platform.

In the Export.bat file i'v used the following command line-
>
>EXP SCOTT/TIGER@ORCL FILE=EXPDAT;
>

In this case i'v to take a backup of the EXPDAT.DMP file to a different location
to avoid overwriting.

Now i want to export using "sysdate+dat.dmp"(this format) as a FILE parameter of
EXP utility. How it is possible?

Please help.

Thanks
F Rabbi

Followup:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:612348047

      Rating:
      5
dbms_job.user_export does that..  July 24, 2003

Reviewer:  pasko
Hi Tom,

I finally found another alternative..

dbms_job.user_export  Procedure

Example :

SQL> var text varchar2(250) ;
SQL> var job  number  ;
SQL> exec dbms_job.user_export(:job , :text ) ;
SQL> print text

TEXT
--------------------------------------------------------------------------------
dbms_job.isubmit(job=>237,what=>'begin null; end; ',next_date=>to_date('4000-01-
01:00:00:00','YYYY-MM-DD:HH24:MI:SS'),interval=>'trunc( sysdate + 4000)',no_pars
e=>TRUE);

July 23, 2003 play -- Thanks for the question regarding "trace
      files location", version 8.1.7

      You Asked
Tom

right now every time I trace my session Iam having to ask my dba to email my
trace file and the tkprof outputfile  to me, as I dont have access to the files
on the database server (os-unix,a nd my client is on windows 2000) side.

is there anyway I can srite a unix shell script or any other way, in which all
files traced by my session go to a partcular folder, or get copied to a
particualr folder automatically, on which I can be given access.

While doing query tuning, how can v$sqlarea, v$SQLTEXT AND V$SQL  be useful to
me.

      and we said...
Well, if your DBA is willing, they can add:

utl_file_dir = /path/to/the/user/dump/destination

in the init.ora.  You can write a stored procedure that then uses UTL_FILE to
read the trace file and put it into a temporary table that looks like:

create global temporary table trace ( id int primary key, text varchar2(4000) );

Then, using this query:

select c.value || '/' || instance || '_ora_' ||
       ltrim(to_char(a.spid,'fm99999')) || '.trc'
  from v$process a, v$session b, v$parameter c, v$thread c
 where a.addr = b.paddr
   and b.audsid = userenv('sessionid')
   and c.name = 'user_dump_dest'

you can get your sessions trace filename.  You would disconnect and log back in
-- run this stored procedure giving it that file name and then you can "spool"
the trace file from the temporary table to your local file system.

Other then that - they must give you access to all trace files (or none).  Trace
files are written to a fixed location (user_dump_dest).  Everyone writes to the
same location.

As for v$sqlarea, v$sqltext, v$sql -- I use them for one thing myself -- finding
statements that don't use bind variables!  Beyond that, I use them indirectly
with statspack to get the "top N" SQL statements by IO and such....

Review &
      Followup

      Rating:
      4
  August 06, 2001
Reviewer:  Jim
       from UK
Excellent !

The stored procedure method is exactly how I allow
my developers to tkprof and obtain their trace results.

      Rating: 4
Trace files
      location  April 08, 2002
Reviewer:  Dave
      Brys  from Belgium
I like the idea , but I ran into some practical problems.

I used your procedure load_file that you gave as an example to load in an ascii
file.

When executing load_file

ERROR at line 1:
ORA-20007: ORA-01461: une valeur 'LONG' ne peut être liée que dans une colonne
de type 'LONG'
ORA-06512: à "GDC.LOAD_FILE", ligne 74
ORA-06512: à ligne 4

I suspect this is because in the database l_comments is a long field but in the
table it is a varchar2(4000).

Then I modified field comments in the table trace to a long field.

My funtion load_file is

function  load_file( p_dir       in varchar2 ,
                                       p_filename  in varchar2 )
return number
is
    l_input         utl_file.file_type;
    l_buffer        long;
    l_comments      long;
    l_id            number;
    l_cnt            number default 0;
begin
    p('for loop');
    l_input := utl_file.fopen( p_dir, p_filename, 'r' );
    loop
        p('in loop');
        begin
            utl_file.get_line( l_input, l_buffer );
        exception
            when no_data_found then exit;
        end;

        if length(l_comments) + length(l_buffer) > 32000 then
                insert into trace ( id, comments ) values (
l_id,rtrim(ltrim(l_comments,'~'),'~'));
                l_comments := '';
        end if;

        l_comments := l_comments || l_buffer || chr(10);

     end loop;

    p(l_comments);

    insert into trace (comments ) values (l_comments);

    utl_file.fclose( l_input );
    return l_cnt;
exception
    when utl_file.invalid_path then
    raise_application_error(-20001,
    'INVALID PATH: File location or filename was invalid.');
    when utl_file.invalid_mode then
    raise_application_error(-20002,
    'INVALID MODE: The open_mode parameter in FOPEN was invalid');
    when utl_file.invalid_filehandle then
    raise_application_error(-20003,
    'INVALID OPERATION:The file could not be opened or operated on as
requested.');
    when utl_file.read_error then
    raise_application_error(-20004,
    'READ_ERROR:An operating system error occured during the read operation.');
    when utl_file.write_error then
    raise_application_error(-20005,
    'WRITE_ERROR: An operating system error occured during the write
operation.');
    when utl_file.internal_error then
    raise_application_error(-20006,
    'INTERNAL_ERROR: An unspecified error in PL/SQL');
    when others then
    raise_application_error(-20007,SQLERRM);
end;

This works ok, and my table trace is filled up with one line. But then you say
"spool" the contents of trace to a file. I used the spool command in sql*plus
for this.

If I then use tkprof on the "spooled" trace file, it gives me a DR watson error.
I can still use tkprof on the original trace file.

I can send you an example on the "spooled" trace file if you want me too.
Do you have any clue ?

Best regards

Dave Brys

          Followup:

You made the load program far too complex.  You must be corrupting the trace
file in some fashion.  You just want to read a line, insert a line.  Don't
bother trying to glue them together into a big line 32k at a time.

Here is a complete example showing this at work:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create global temporary table trace_tab
  2  ( seqno int primary key,
  3    text     varchar2(4000)
  4  )
  5  on commit delete rows
  6  /
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure load_file( p_dir
  in varchar2 ,
  2                                         p_filename  in varchar2 )
  3  is
  4      l_input         utl_file.file_type;
  5      l_buffer        long;
  6      l_comments      long;
  7      l_id            number;
  8      l_cnt            number default 0;
  9  begin
 10      l_input := utl_file.fopen( p_dir, p_filename, 'r', 4000 );
 11      for recno in 1 .. 1000000
 12      loop
 13          begin
 14              utl_file.get_line( l_input, l_buffer );
 15          exception
 16              when no_data_found then exit;
 17          end;
 18              insert into trace_tab values ( recno, l_buffer );
 19       end loop;
 20      utl_file.fclose( l_input );
 21  end;
 22  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> column path new_value path
ops$tkyte@ORA817DEV.US.ORACLE.COM> column file_name new_value file_name

ops$tkyte@ORA817DEV.US.ORACLE.COM> select c.value path,
  2         instance || '_ora_' ||
  3         ltrim(to_char(a.spid,'fm99999')) || '.trc' file_name
  4    from v$process a, v$session b, v$parameter c, v$thread c
  5   where a.addr = b.paddr
  6     and b.audsid = userenv('sessionid')
  7     and c.name = 'user_dump_dest'
  8  /

PATH
---------------------------------------------------------------------------------
--------------------------------------------------
FILE_NAME
------------------------------
/export/home/ora817/admin/ora817dev/udump
ora817dev_ora_27318.trc

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set sql_trace=true;
Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from dual;

D
-
X

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from scott.emp;

  COUNT(*)
----------
        14

ops$tkyte@ORA817DEV.US.ORACLE.COM> disconnect
Disconnected from Oracle8i Enterprise Edition Release 8.1.7.2.0 - Production
With the Partitioning option
JServer Release 8.1.7.2.0 - Production

ops$tkyte@ORA817DEV.US.ORACLE.COM> connect /
Connected.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec load_file( '&path', '&file_name' );
PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set heading off
ops$tkyte@ORA817DEV.US.ORACLE.COM> set feedback off
ops$tkyte@ORA817DEV.US.ORACLE.COM> set trimspool on
ops$tkyte@ORA817DEV.US.ORACLE.COM> set linesize 255
ops$tkyte@ORA817DEV.US.ORACLE.COM> set embedded on
ops$tkyte@ORA817DEV.US.ORACLE.COM> spool &file_name
you should be a SET TERMOUT OFF here as well -- so you don't have to watch the
tracefile go by...

ops$tkyte@ORA817DEV.US.ORACLE.COM> select text from trace_tab order by seqno;
Dump file /export/home/ora817/admin/ora817dev/udump/ora817dev_ora_27318.trc
Oracle8i Enterprise Edition Release 8.1.7.2.0 - Production
With the Partitioning option
JServer Release 8.1.7.2.0 - Production
ORACLE_HOME = /export/home/ora817
System name:    SunOS
Node name:      aria-dev
Release:        5.7
Version:        Generic_106541-19
Machine:        sun4u
Instance name: ora817dev
Redo thread mounted by this instance: 1
Oracle process number: 8
Unix process pid: 27318, image: oracle@aria-dev (TNS V1-V3)

*** SESSION ID:(18.400) 2002-04-08 08:41:09.366
APPNAME mod='01@ test.sql' mh=2821176787 act='' ah=4029777240
=====================
PARSING IN CURSOR #1 len=33 dep=0 uid=216 oct=42 lid=216 tim=3042719131
hv=3732290820 ad='81c0d8e4'
alter session set sql_trace=true
END OF STMT
EXEC #1:c=0,e=3,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=3042719131
=====================
PARSING IN CURSOR #1 len=19 dep=0 uid=216 oct=3 lid=216 tim=3042719131
hv=3499509676 ad='81409b80'
select * from dual
END OF STMT
PARSE #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=3042719131
EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=3042719131
FETCH #1:c=0,e=0,p=0,cr=1,cu=4,mis=0,r=1,dep=0,og=4,tim=3042719131
FETCH #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=0,tim=3042719131
STAT #1 id=1 cnt=1 pid=0 pos=0 obj=195 op='TABLE ACCESS FULL DUAL '
=====================
PARSING IN CURSOR #1 len=31 dep=0 uid=216 oct=3 lid=216 tim=3042719131
hv=3580614502 ad='813f2f44'
select count(*) from scott.emp
END OF STMT
PARSE #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=3042719131
EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=3042719131
FETCH #1:c=0,e=0,p=0,cr=1,cu=4,mis=0,r=1,dep=0,og=4,tim=3042719131
FETCH #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=0,tim=3042719131
STAT #1 id=1 cnt=1 pid=0 pos=0 obj=0 op='SORT AGGREGATE '
STAT #1 id=2 cnt=14 pid=1 pos=1 obj=28771 op='TABLE ACCESS FULL EMP '
XCTEND rlbk=0, rd_only=1
ops$tkyte@ORA817DEV.US.ORACLE.COM> spool off

now, to show it works:

ops$tkyte@ORA817DEV.US.ORACLE.COM> host tkprof &file_name x.txt

TKPROF: Release 8.1.7.2.0 - Production on Mon Apr 8 08:41:20 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

ops$tkyte@ORA817DEV.US.ORACLE.COM> host cat x.txt

TKPROF: Release 8.1.7.2.0 - Production on Mon Apr 8 08:41:20 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Trace file: ora817dev_ora_27318.trc
Sort options: default

********************************************************************************
count    = number of times OCI procedure was executed
cpu      = cpu time in seconds executing
elapsed  = elapsed time in seconds executing
disk     = number of physical reads of buffers from disk
query    = number of buffers gotten for consistent read
current  = number of buffers gotten in current mode (usually for update)
rows     = number of rows processed by the fetch or execute call
********************************************************************************

alter session set sql_trace=true

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      1      0.00       0.03          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        1      0.00       0.03          0          0          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 216
********************************************************************************

select *
from
 dual

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          1          4           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          1          4           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 216

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS FULL DUAL

********************************************************************************

select count(*)
from
 scott.emp

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          1          4           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          1          4           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 216

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
     14   TABLE ACCESS FULL EMP

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      3      0.00       0.03          0          0          0           0
Fetch        4      0.00       0.00          0          2          8           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        9      0.00       0.03          0          2          8           2

Misses in library cache during parse: 0

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      0      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        0      0.00       0.00          0          0          0           0

Misses in library cache during parse: 0

    3  user  SQL statements in session.
    0  internal SQL statements in session.
    3  SQL statements in session.
********************************************************************************
Trace file: ora817dev_ora_27318.trc
Trace file compatibility: 8.00.04
Sort options: default

       1  session in tracefile.
       3  user  SQL statements in trace file.
       0  internal SQL statements in trace file.
       3  SQL statements in trace file.
       3  unique SQL statements in trace file.
      44  lines in trace file.

and that is it, tkprof without ever leaving sqlplus and without getting on the
server.

      Rating:
      4
Reader  April 08, 2002
Reviewer:
      A reader
Tom,

I ran a join of few dictionary views , the query took
~ 5 minutes. I turned trace 10046, level 8. The trace file
does not show any WAIT: statistic at all. How to find why
the query takes as long.

From the trace file:
====================

PARSING IN CURSOR #3 len=629 dep=0 uid=4670 oct=3 lid=4670 tim=2014767
hv=4025224304 ad='36647998'
select fs.tablespace_name ,count(bytes) FS_T, max(bytes)/1024/1024 FS_MAX,
min(initial_extent)/1024/1024 IE,
  min(next_extent)/1024/1024 NE from dba_free_space fs,dba_tablespaces ts
  where  fs.tablespace_name = ts.tablespace_name and
  fs.tablespace_name in
  (
  select tablespace_name from dba_tables where table_name in (select table_name
from dba_tab_columns where
  column_name = 'NRI')
  union
  select tablespace_name from dba_indexes where index_name in
  (select index_name from dba_indexes
  where table_name in
     (select table_name from dba_tab_columns where column_name = 'NRI'))
  ) group by fs.tablespace_name

PARSE #3:c=34007,e=34431,p=9,cr=362,cu=1,mis=1,r=0,dep=0,og=4,tim=2014768
EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=2014768
FETCH #3:c=381,e=411,p=37,cr=178775,cu=76,mis=0,r=1,dep=0,og=4,tim=2015179
FETCH #3:c=6,e=5,p=0,cr=0,cu=0,mis=0,r=7,dep=0,og=4,tim=2015208

The elapsed Time = 2015179 - 2014768 = 411 seconds?

Thanks

          Followup:

I believe you must have messed up somewhere -- and did not set the trace
properly.

      Rating: 5
RE: I turned
      trace 10046...  April 08, 2002
Reviewer:  Mark
      A. Williams  from Indianapolis, IN USA
Another discussion on AskTom (found using the amazing Search/Archives tab) that
may be relevant....

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2202891332282
Especially the quote from MetaLink:

"We do not guarantee the performance against our dba views with joins.  These
views are designed to have the optimal performance when queried directly not for
joins.   In most cases we tell customers that if they don't like our views that
are provided wiht [sic] the database, then they can go ahead and create their
own."

      Rating:
      5
Reader  April 09, 2002
Reviewer:
      A reader
Tom,

Thanks very much.

I re-created the trace file and found WAIT: stats.
Mostly 'db file sequential read'

Thanks

      Rating: 4
PLS-00307 too
      many declarations of 'to_char' match this call  November 14,
      2002
Reviewer:  Robert  from PA
Tom, when I try to put the SQL you provided into a stored function, the compiler
complains about the TO_CHAR, I have
seen this before, what gives ?

select c.value || '/' || instance || '_ora_' ||
       ltrim(to_char(a.spid,'fm99999')) || '.trc'
INTO l_return
...<snip>

PLS-00307 too many declarations of 'to_char' match this call

          Followup:

ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2  for x in ( select c.value || '/' || instance || '_ora_' ||
  3         ltrim(to_char(to_number(a.spid),'fm99999')) || '.trc'
  4    from v$process a, v$session b, v$parameter c, v$thread c
  5   where a.addr = b.paddr
  6     and b.audsid = userenv('sessionid')
  7     and c.name = 'user_dump_dest' )
  8     loop
  9     null;
 10          end loop;
 11  end;
 12  /

PL/SQL procedure successfully completed.

      Rating: 3
Set
      "_Trace_file_public=true " to make them read access for
      all  November 23, 2002
Reviewer:
      A reader
I use to set "_TRACE_FILE_PUBLIC=TRUE " in init.ora to make the trace files
,read access to all developers.So its upto the developer to identify his session
SID and locate  his/her trace files ,which are usally in format
"<ORACLE_SID>_ora_<process ID>" .

Reason DBAs won't give access to trace files,becuase they are created by default
file permissions,which only user "oracle" and uses in group "dba" can access
them.ether they can run some scripts ( via cron job) to make others access these
files OR DBA can set the above hidden parameter ,which make default file
permission of files to others with read-access.

          Followup:

beware the hidden unknown side effects of that _ init.ora parameter. (that and
it won't do much for this windows client who doesn't have access to the unix
box)

      Rating: 5
RE: public
      trace files...  November 23, 2002
Reviewer:
      Mark A. Williams  from Indianapolis, IN USA
Well, you just may find setting "_trace_files_public=true" is a better way to
make them public, since "_trace_file_public" isn't a valid parameter.  Plus you
still need to fiddle with UMASK on UNIX or directory permissions on NT or
WinMM... However, why would you want to open up a security hole like that?
About five seconds of looking at trace files with bind values, and I could have
some interesting information that slipped right through the old security hole,
right?  Here's an interesting snippet from Oracle Support:

"This is a security feature that can be bypassed by setting the hidden parameter
"_trace_files_public = true" in the "init<sid>.ora" file. This is not a secure
thing to do because many trace files include hex dumps of potentially sensitive
data."

As far as "Reason DBAs won't give access to trace files"...  The reason I don't
do it is listed right above.  SECURITY.  I'm pretty sure the security folks at
my place of work wouldn't look too kindly on bypassing security features...  (I
work in an FDA regulated environment...)  How can you ensure that only
authorized people are looking at the trace files they should be?  Do think I
would set this parameter on our credit card authorization system?  No way.

Curious what others do or how they feel about it.  (Don't want Tom to get
flooded by follow-ups though...)

Anyway, another $0.02 into the bit bucket.

- Mark

      Rating: 4
Client
      How  December 26, 2002
Reviewer:
      A reader
Hi, TOm,
I'm using SQLPLUS in client machine, now how
can execute:
host tkprof &file_name x.txt;
and
host cat x.txt

cause I'm not on the server? Please clarify.

Thanks

          Followup:

well, you cannot - you need access to the tracefiles.

Now, if you use the above mentioned trick -- you could because you can "select *
from your tracefile" and spool the results locally to run tkprof against.

      Rating: 5
forget that
      message  December 26, 2002
Reviewer:
      A reader
Hi, Tom,

I got it so please ignore the above question.
I really hope we could eleminate the quesion asked by ourself which we don't
want ask any more and of course before you answer it.

Thanks again

      Rating: 5
How can we know
      soon after a new trace file is generated?  January 13, 2003

Reviewer:  A Reader  from USA
Hi Tom,
Is there a way by which I can be informed immediatly whenever a new trace file
is generated in background_dump_dest or user_dump_dest folder.  For example,
when a new trace file is generated, I will get an email telling me about that.
Now, I check these two folders once in a while and sometimes find it's already
too late to handle the errors.  Is there a signal calling statement such as
"whenever new trace" (like in SQLPLUS, there is a WHENEVER SQLERROR, or WHENEVER
OSERROR) I can use to ask Oracle (8i) sending me an email using UTL_SMTP?  Or is
there a better way to inform me (via email or other ways) about the new trace
file soon?

Thank you very much for you help in advance.

          Followup:

there is a servererror trigger you can code to catch and notify yourself about
errors on the server.  it'll catch much more than just trace file generation
-but you can pick and choose the errors you want to catch

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76939/adg14evt.htm#999373

      Rating: 4
Perhaps It's
      Time for Old Sparky  January 14, 2003
Reviewer:
      Terry  from San Jose, CA USA
Our Friend (Cary Millsap) at Hotsos.com has provided a very nifty tool to
collect and view trace files and put those trace files to a client.  The tool is
called Sparky.  It uses Perl (don't everyone throw up just yet) on the server
side and while I wouldn't be a big fan of this on production systems I have used
it successfully in dev, test environments, it is free and relatively easy to
install.

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7115831027951
is free and in plsql (so works on all systems without using OS stuff ;)

      Rating: 5
How do we find
      the name for a JOB process  April 17, 2003

Reviewer:  Basil
My application has a switch to allow the batch component to turn tracing on. I
want to report the file name in our application's log. However, the batch
component runs as an Oracle job. The file name doesn't match for these
processes. Do you know offhand how the job-based filename is generated? It
appears to include the job process number.

          Followup:

it'll be in the background dump destination -- in the snpNN files --
intermingled with whatever other traces that snp process ran.

      Rating: 5
What to
      query?  April 18, 2003
Reviewer:  Basil

More specifically (referring to my question immediately preceding), what would I
query to determine the filename? Early in the thread, you give the following
code, which, from experience, provides a filename for non-job trace files:

select c.value || '/' || instance || '_ora_' ||
       ltrim(to_char(a.spid,'fm99999')) || '.trc'
  from v$process a, v$session b, v$parameter c, v$thread c
 where a.addr = b.paddr
   and b.audsid = userenv('sessionid')
   and c.name = 'user_dump_dest'

How would I generate the same sort of thing for a procedure running as a job?
How could the procedure KNOW if it's running as a job?

          Followup:

it'll be hard -- the a.spid part is correct -- however, the job queues handlers
will inject their name into the mix (into the filename - j000, j001, ... in 9i
and snp0, snp1, snp2, ... in 8i).

The best you'll get reliably will be the spid -- which will be in the filename
-- so ls -l $ORACLE_HOME/admin/$ORACLE_SID/bdump/*_{a.spid}.trc will find the
file for you.

You can discover if you are in a job by looking at the program that is running
you- the backgrounds identify themselves:

ops$tkyte@ORA920> drop table log;

Table dropped.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table log
  2  as
  3  select b.SID, b.SERIAL#, b.USERNAME, b.COMMAND, b.OWNERID,
  4         b.STATUS, b.SERVER, b.SCHEMANAME, b.PROCESS, b.MACHINE,
  5         b.TERMINAL, b.PROGRAM, b.TYPE, b.MODULE, b.ACTION,
  6         b.CLIENT_INFO, a.PROGRAM aprogram, a.BACKGROUND abackground
  7    from v$process a, v$session b, v$parameter c
  8   where 1=0
  9  /

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace procedure jobproc
  2  as
  3  begin
  4
  5  execute immediate 'alter session set sql_trace=true';
  6
  7  insert into log
  8  select b.SID, b.SERIAL#, b.USERNAME, b.COMMAND, b.OWNERID,
  9         b.STATUS, b.SERVER, b.SCHEMANAME, b.PROCESS, b.MACHINE,
 10         b.TERMINAL, b.PROGRAM, b.TYPE, b.MODULE, b.ACTION,
 11         b.CLIENT_INFO, a.PROGRAM aprogram, a.BACKGROUND abackground
 12    from v$process a, v$session b, v$parameter c
 13   where a.addr = b.paddr
 14     and b.sid = (select sid from v$mystat where rownum=1)
 15     and c.name = 'background_dump_dest'  ;
 16
 17  execute immediate 'alter session set sql_trace=false';
 18
 19  end;
 20  /

Procedure created.

ops$tkyte@ORA920> pause

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from log;

no rows selected

ops$tkyte@ORA920> variable n number
ops$tkyte@ORA920> exec dbms_job.submit( :n, 'jobproc;' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_lock.sleep(5)

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set echo off
SID                           : 15
SERIAL#                       : 50
USERNAME                      : OPS$TKYTE
COMMAND                       : 2
OWNERID                       : 2147483644
STATUS                        : ACTIVE
SERVER                        : DEDICATED
SCHEMANAME                    : OPS$TKYTE
PROCESS                       :
MACHINE                       :
TERMINAL                      :
PROGRAM                       :
TYPE                          : USER
MODULE                        :
ACTION                        :
CLIENT_INFO                   :
APROGRAM                      : oracle@tkyte-pc-isdn.us.oracle.com (J000)
ABACKGROUND                   :
-----------------

PL/SQL procedure successfully completed.

that shows I was using using J000 (job queue process 0) in 9i.  in fact, using
that knowledge -- we can see:

ops$tkyte@ORA920> drop table log;

Table dropped.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table log
  2  as
  3  select c.value || '/' || instance || '_' ||
  4         lower(rtrim(substr( a.program, instr( a.program, '(' )+1 ), ')' ))
  5                  || '_' || ltrim(to_char(a.spid,'fm99999')) || '.trc' msg
  6    from v$process a, v$session b, v$parameter c, v$thread
  7   where 1=0
  8  /

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace procedure jobproc
  2  as
  3  begin
  4
  5  execute immediate 'alter session set sql_trace=true';
  6
  7  insert into log
  8  select c.value || '/' || instance || '_' ||
  9         lower(rtrim(substr( a.program, instr( a.program, '(' )+1 ), ')' ))
 10                  || '_' || ltrim(to_char(a.spid,'fm99999')) || '.trc'
 11    from v$process a, v$session b, v$parameter c, v$thread
 12   where a.addr = b.paddr
 13     and b.sid = (select sid from v$mystat where rownum=1)
 14     and c.name = 'background_dump_dest'  ;
 15
 16  execute immediate 'alter session set sql_trace=false';
 17
 18  end;
 19  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from log;

no rows selected

ops$tkyte@ORA920> !ls -l $ORACLE_HOME/admin/$ORACLE_SID/bdump
total 4588
-rw-r--r--    1 ora920   ora920    4682981 Apr 18 11:36 alert_ora920.log

ops$tkyte@ORA920>
ops$tkyte@ORA920> variable n number
ops$tkyte@ORA920> exec dbms_job.submit( :n, 'jobproc;' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_lock.sleep(15)

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from log;

MSG
---------------------------------------------------------------------------------
--------------------------------------------------
/usr/oracle/ora920/OraHome1/admin/ora920/bdump/ora920_j000_2488.trc

ops$tkyte@ORA920> !ls -l $ORACLE_HOME/admin/$ORACLE_SID/bdump
total 4596
-rw-r--r--    1 ora920   ora920    4683070 Apr 18 11:37 alert_ora920.log
-rw-rw----    1 ora920   ora920       5773 Apr 18 11:37 ora920_j000_2488.trc

you'll have to customize that per version and OS of course...

      Rating: 5
That should do
      it. Is SYS_CONTEXT of any help?  April 18, 2003

Reviewer:  Basil
Thanks for the previous example. As I was working through implementing this
myself, I considered putting this job info into a context, for quick retrieval
in a proc that would want to know if it's running as a job or not. As I was
looking at the SYS_CONTEXT function, I didn't realize there were BG_JOB_ID and
FG_JOB_ID values in the USERENV namespace.

For FG_JOB_ID, I didn't get anything that looked very helpful:
SQL> select sys_context('USERENV','FG_JOB_ID') from dual;

SYS_CONTEXT('USERENV','FG_JOB_ID')
-------------------------------------
0

SQL> create table xlog (val varchar2(500));

Table created.

SQL>
SQL> create or replace procedure jobproc
  2  as
  3  begin
  4
  5  insert into xlog
  6  select sys_context('USERENV','FG_JOB_ID') from dual;
  7
  8
  9  end;
 10  /

Procedure created.

dc@SMT92> variable n number
dc@SMT92> exec dbms_job.submit( :n, 'jobproc;' );

PL/SQL procedure successfully completed.

SQL> COMMIT;

Commit complete.

SQL> select * from xlog;

VAL
-----------------
1921

I couldn't get BG_JOB_ID to be anything but NULL, both interactively and as a
job. What exactly does this do?

          Followup:

bg_job_id does appear to be a "no op"...

      Rating:
      3
tkprof..  April 22, 2003
Reviewer:
      vj  from in
tom...

assume i got a complex query like below and got the trace file...it it taking
lot of time..

SELECT  a.distributor,a.p_investor_id,a.investor_id,
b.name_fapp,b.name_sapp,b.name_tapp,b.id_new_no_fapp,b.id_old_no_fapp
,NVL(a.folio_lock,'N') folio_lock,
SUM(a.units-NVL(d.units_locked,0)) units
FROM
ISS_INVESTOR_HOLDINGS_BAL A,
ISS_INVESTOR B  ,
ISS_PLAN C,
/*(SELECT FOLIO_ID,SUM(UNITS) UNITS
FROM ISS_LOCK_TRAN
WHERE  UNTAG_REF IS NULL
GROUP BY FOLIO_ID)*/
ISS_VWLOCKEDRECS D
WHERE
NVL(A.ACCOUNT_STATUS,'O')='O' AND
a.investor_id='0000097' AND
--A TO B CONDITION
A.INVESTOR_ID = B.INVESTOR_ID AND
A.P_INVESTOR_ID = B.P_INVESTOR_ID AND
B.RECTYPE = 'L' AND
A.DISTRIBUTOR=B.DISTRIBUTOR AND
--A TO C CONDITION
C.DISTRIBUTOR=A.DISTRIBUTOR AND
C.AMC=A.AMC AND
C.SCHEME=A.SCHEME AND
C.SCHEME_TYPE ='LIP' AND
C.PLAN=A.PLAN AND
C.RECTYPE='L' AND
--A TO D CONDITION
A.FOLIO_ID=D.FOLIO_ID
GROUP BY
A.DISTRIBUTOR,A.P_INVESTOR_ID,A.INVESTOR_ID,
B.NAME_FAPP,B.NAME_SAPP,B.NAME_TAPP,B.ID_NEW_NO_FAPP,B.ID_OLD_NO_FAPP
,NVL(A.FOLIO_LOCK,'N')

now from the trace file the output i am looking at is

Rows     Row Source Operation
-------  --------------------------------------------------      1  SORT GROUP
BY
      1   NESTED LOOPS
      2    NESTED LOOPS
      2     NESTED LOOPS
      2      TABLE ACCESS BY INDEX ROWID ISS_INVESTOR
      2       INDEX RANGE SCAN (object id 82156)
      2      TABLE ACCESS FULL ISS_BAL
      2     TABLE ACCESS FULL ISS_PLAN
      1    VIEW ISS_VWLOCKEDRECS
  31113     FILTER
  31114      SORT GROUP BY
 152709       TABLE ACCESS BY INDEX ROWID ISS_TRAN
 152710        INDEX RANGE SCAN (object id 83065)

now...can you suggest me what is that i need to look to make this query
efficient..

i am looking at this and trying to analyze..

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.72          0          0          0           0
Execute      2      0.01       4.10          0          0          2           0
Fetch        2      5.44     371.15      45817     147360        146           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      5.46     375.97      45817     147360        148           1

rgds

          Followup:

looks like the view isn't appropriate for this query.  looks like the view is
being entirely materialized AND THEN that temp result merged into the rest of
the query.

so, what does the view look like.

      Rating:
      3
  April 23, 2003
Reviewer:  Vj
       from In
the view looks like this tom...

CREATE OR REPLACE VIEW ISS_VWLOCKEDRECS ( FOLIO_ID,
UNITS_LOCKED ) AS SELECT /*+ INDEX(ISS_INVESTOR_HOLDINGS_TRAN TMPSRINI) */
folio_id,SUM(units_locked) units_locked
FROM ISS_INVESTOR_HOLDINGS_TRAN
WHERE units_balance>0
GROUP BY folio_id
HAVING NVL(SUM(units_locked),0)=0

rgds

          Followup:

well, two things

-- whats up with that hint, lose it.  it is actually a major cause of your
problem here.

-- the aggregate will prevent view merging

well -- three things.

unless I'm reading that wrong -- units_locked is always ZERO or NULL in that
view.

So,

SUM(a.units-NVL(d.units_locked,0)) units

is the same as

nvl(sum(a.units),0)

and you don't need to join, you just need to "in"

..
A.FOLIO_ID in ( select FOLIO_ID from iss_vwlockedrecs )

      Rating: 4
for tracing
      what background processes?  April 26, 2003

Reviewer:  A reader
I have the trace file in the bdump, but I can not figure out for What process
(background)? What s000 stand for which process?
Thanks

/u01/oradb32a/oracle/product/admin/tol32db1/bdump/xxxdb1_s000_248.trc
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
ORACLE_HOME = /u01/oradb32a/oracle/product
System name:    SunOS
Node name:      xxx
Release:        5.8
Version:        Generic_108528-16
Machine:        sun4u
Instance name: xxxdb1
Redo thread mounted by this instance: 1
Oracle process number: 10
Unix process pid: 248, image: xxxx (S000)

*** SESSION ID:(58.10616) 2003-02-07 16:30:39.234
Invalid Login Using Trusted Authentication
*** 2003-02-07 16:30:54.255
*** SESSION ID:(105.12213) 2003-02-07 16:30:54.254
Invalid Login Using Trusted Authentication
*** 2003-02-07 16:31:10.270
*** SESSION ID:(112.17714) 2003-02-07 16:31:10.270
Invalid Login Using Trusted Authentication

          Followup:

s000 is a shared server

      Rating:
      4
  April 26, 2003
Reviewer:
      A reader
I also have seen  the s001, s002 tracefiles in the bdump. Does that mean the DB
has the MTS setup?

Thanks

          Followup:

yes, s000 was sufficient to detect that fact...

      Rating:
      5
  April 27, 2003
Reviewer:
      A reader
Tom,
Thanks you so much for the help on the tracefiles...
How can I confirm the MTS has been setup? I mean from the SYS views, init.ora...
?

Thanks,
Mike

          Followup:

show parameter MTS

in sqlplus.

      Rating: 3
Trace File... I
      can't see it!  May 28, 2003
Reviewer:
      Richard  from Oxfordshire, UK
Hi,

I have used the

select c.value || '/ORA' || to_char(a.spid, 'fm00000') || '.trc'
 from  v$process a, v$session b, v$parameter c
where  a.addr   = b.paddr
  and  b.audsid = userenv('sessionid')
  and  c.name   = 'user_dump_dest'

script from Beginning Oracle Programming, and it gives me the following:

f:\oracle\admin\bip\udump/ORA00165.trc

However, when I examine the contents of f:\oracle\admin\bip\udump I cannot see
ORA00165.trc.

What am I doing wrong?

          Followup:

so, what files do you see there, maybe from that you can figure out what needs
to be tweaked to get this right.  do you see a file with the number 165 in it --
if so, what is its format.

if there is nothing in udump, maybe you are using shared server and everything
is in bdump (meaning using sql_trace=true isn't really going to be something you
are doing, you need a dedicated connection)

      Rating: 2
Still
      Looking!  May 28, 2003
Reviewer:  Richard
       from Oxfordshire, UK
There is no file with "165" in its name in either BDUMP or UDUMP.

We did recently have an ORA-00600 situation (we use NT and Oracle Support tell
me that this error is only associated with NT), and I do wonder whether all is
now well, even though the DB is running and everyone can access it OK.

          Followup:

so, did you see a *new* trace file -- more importantly did you see YOUR trace
file.

as I recall, windows trace file numbers could wrap around easily.... find your
trace file (which is being generated)

sort the directory by date.

      Rating:
      2
  May 29, 2003
Reviewer:  Richard
       from Oxfordshire, UK
The latest trace file that can be seen in the NT directory listing is
ORA00496.trc, with a Creation Date of 25 March 2003, 16:08:50 and a Modification
Date of 5 March 2003, 16:08:51. The next latest trace file is ORA00615.trc, but
that has a Modification Date of 21 November 2002, 12:13:14.

The trace file name generated by the script today (29-MAY-2003) is called
ORA00345.trc, and I can't see that either!

Thanks for your help with this increasingly tedious thread.

          Followup:

a creation date of 25 mar, but modified 5 mar, hmmm...

anyway -- i think you are not tracing then.  Run this for me:

show parameter dump
select server from v$session where sid = (select sid from v$mystat where
rownum=1);

in sqlplus

      Rating:
      2
  May 30, 2003
Reviewer:  Richard
       from Oxfordshire, UK
Mod. date should have been 25, not 5!

=========================
Requested output follows:
=========================

SQL> show parameter dump

NAME                       TYPE    VALUE
-------------------------- ------- --------------------
background_dump_dest       string  f:\oracle\admin\bip\
max_dump_file_size         integer 10240
user_dump_dest             string  f:\oracle\admin\bip\
SQL>

and

SQL> select server from v$session where sid = (select sid
     from v$mystat where rownum=1);

SERVER
---------
DEDICATED

SQL>

          Followup:

the files are in f:\oracle\admin\bip, not f:\oracle\admin\bip\udump according to
that.

      Rating:
      2
  May 30, 2003
Reviewer:  Richard
       from Oxfordshire, UK
No trace files (only folders) in f:\oracle\admin\bip!

          Followup:

then either

o you are doing something really wrong (eg: wrong server, wrong f:\ drive
something)

o you might have max dump file size at 0

o oracle doesn't have the permissions to write to that directory.

this stuff works, really it does.  It is used millions of times a day, every
day.

      Rating: 3
Thanks for
      helping me.  May 30, 2003
Reviewer:  Richard
       from Oxfordshire, UK
Thanks for all your help. I suspect that you are right when you suggest:
o you are doing something really wrong (eg: wrong server, wrong f:\ drive
something)

I'll persevere!

      Rating: 4
trace
      files  July 23, 2003
Reviewer:  Venkat
       from India
Tom,

We get the following error while trying the method suggested. What is the
reason?

SQL> exec load_file( '&path', '&file_name' );
BEGIN load_file( '/orbicore/oradata/orbicore/admin/udump', 'orbicore_ora_3181.tr
c' ); END;

*
ERROR at line 1:
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at "SYS.UTL_FILE", line 98
ORA-06512: at "SYS.UTL_FILE", line 192
ORA-06512: at "ORBIOASIS.LOAD_FILE", line 11
ORA-06512: at line 1

Thanks,
Venkat

          Followup:

you don't have utl_file set up properly.

just try this -- create a small block that does nothing other then call
utl_file.fopen.

until that works -- this won't work.

      Rating:
      4
UTL_FILE  September 09, 2003
Reviewer:
      A reader
Hi Tom,

  I faced the same error.

ERROR at line 1:
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at "SYS.UTL_FILE", line 98
ORA-06512: at "SYS.UTL_FILE", line 192
ORA-06512: at "SYS.LOAD_FILE", line 9
ORA-06512: at line 1

You said I need to set UTL_FILE properly. I recompiled that and it was ok. I
checked grants that was fine too. What else do I need to do to fix the error?

Thanks for your time.

July 24, 2003 Jen -- Thanks for the question regarding "PL/SQL
      beginner book", version 8.1.7

      You Asked
Hi Tom,

I'm a beginner in learning PL/SQL.  Instead of reading Oracle documentation on
PL/SQL, can you please recommend a good book for a beginner to learn PL/SQL from
and would possible lead to intermediate level of PL/SQL programming?

Thank you!

      and we said...
but -- have you READ the oracle documentation:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/toc.htm
is an absolute MUST READ

and

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76939/toc.htm
is another you MUST READ

and you will find

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a77069/toc.htm
to be pretty well written.

I understand many people like feuresteins plsql books.

Oracle PL/SQL Programming, 3rd Edition
by Steven Feuerstein, Bill Pribyl

Review &
      Followup

      Rating:
      5
Books  December 10, 2002
Reviewer:
      AK  from Germany
Tom, are there hard copies (in book-form, I mean) of these 3 manuals available?

          Followup:

You can order any of the books -- yes.  Just like ordering the software.

goto doc.oracle.com -- there is a blurb there about it.

      Rating: 5
Sound
      advice  December 10, 2002
Reviewer:  Ed
       from UK
Strange though that Jen requested "Instead of reading Oracle documentation on
PL/SQL ..." when it is so easily available and of high quality.

I have pointed people towards "Beginning Oracle Programming"  by Dillon, Beck
and Kyte.

      Rating:
      4
  December 10, 2002
Reviewer:  Luda.
       from USA
I'm Oracle developer. I have to learn C programming.
What documentation or books could you recommend in this case?

Thanks.

          Followup:

http://www.amazon.com/exec/obidos/ASIN/0131103628/
that is the only C book I ever used.  Make sure to sort the reviews from worst
to best -- might not be the best beginning C book, but IMO it's the only one.

      Rating: 4
PL/SQL beginner
      book  December 10, 2002
Reviewer:  Den
      Raphaely  from Redwood Shores, CA USA
*I see that Jen's question referred to "version 8.1.7" but she might find the
latest documentation (9.2) to be more useful even if it refers to some features
her version of the database doesn't have (see the "What's New" chapter in every
manual).
* Users can download Oracle documentation from OTN in pdf form and print it
themselves at no extra cost to the management ;)

          Followup:

I always try to point the people to whatever version of docs they have in the
"version" section.

But yes, ALL documentation is available on OTN

http://technet.oracle.com/documentation/content.html

      Rating: 4
Feuerstein's
      main book not the best to learn from  December 10, 2002

Reviewer:  Jeremy Smith  from Salt Lake City

Bill Pribyl has a "Learning PL/SQL" which is a more basic introduction.  It
walks you through simpler examples and assumes a bit less knowledge.  Also,
PL/SQL Developer's workbook, by the same pair, has a lot of good exercises to
work through.

(If you do buy Feuerstein, get the most recent version...he has corrected some
mistakes/myths in his previous two)

      Rating: 4
Thank you all
      for your advise!   December 10, 2002
Reviewer:
      Jen
I will check out everything..

      Rating:
      4
  December 10, 2002
Reviewer:
      A reader  from Ca, USA
You can sort reviews on amazon.com!?

-------------------------------------------------------

http://www.amazon.com/exec/obidos/ASIN/0131103628/
that is the only C book I ever used.  Make sure to sort the reviews from worst
to best -- might not be the best beginning C book, but IMO it's the only one.
-------------------------------------------------------

          Followup:

click on the "see all customer reviews" and you can sort a variety of ways.  (i
do it on my book to see who "dissed me" :)

      Rating: 4
that is the
      only C book I ever used....  December 10, 2002

Reviewer:  Robert  from PA
that C book by Ritchie and company...isn't that the very FIRST C book ever ?
wow, going way back....

      Rating: 3
ias
      book  December 10, 2002
Reviewer:  Nikunj
       from Ahmedabad,India
is there any book available which can teach us to build site like
asktom.oracle.com ?

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3489618933902
look for marvel -- it is all online

      Rating: 5
Oracle's
      documentation is well written  December 11, 2002

Reviewer:  Dwayne Croteau  from Calgary, Alberta

Hello,

I use Oracle's documentation almost every day and I find it comprehendable and
well written.  I can't say that for other vendor (i.e. Microsoft
MSDN)documentation I have read.  In fact, I haven't purchased any Oracle books -
except Expert One on One and Beginning Oracle Programming of course :^)

      Rating: 4
oracle
      9i/9.2  December 11, 2002
Reviewer:
      hannibal  from autris/europe
tom,

seeing you talking about oracle books i would like to ask if
you could recomend a book/source about oracle9i. i am looking
for something focusing on oracle 9i for programmers ( PL/SQL ,
SQL , etc. ) who are already experienced with oracle 8.1.7
features but want to learn about the new features, their
merits and uses.

thx

hannibal

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/index.htm
there is

o a plsql manual, with a new features chapter
o a sql ref, with a new features chapter
o a new features guide
o that pesky concepts guide you need to re-read since it is a new release....

and it's all free, you can get it today!

      Rating:
      5
books  December 11, 2002
Reviewer:
      mo
Tom:

are you sure that oracle has this documentation in book form like "Oracle 8i
Concepts Release 2".  I could not find this on amazon or is it only sold through
oracle.

          Followup:

only oracle

      Rating: 5
one
      question  June 29, 2003
Reviewer:  Abdul
      Wahab  from Jeddah,K.S.A
hi TOM,
   I have some questions regarding pl/sql programming.Need your help i am trying
to write one script.  what i want is after i start the execution..it should
execute few statements and then stop ..asking the user whether to continue or
not (y/n)..and then proceed. How to do this?  i mean if i am putting the &var in
the program it is asking for it's value at the start of the program itself!. how
to make the program interactive?
 Another thing which i want to ask is ..can i use the SET operator in the pl/sql
program to set the environment..like 'set numformat 99999.99 ' if yes then how?
thnx

Abdul Wahab

          Followup:

you cannot.  PLSQL runs on the server -- over the network -- and is not an
interactive programming language.  It would be like wanting a query to stop on
row 5 and ask the user "do you like what you see".

The CLIENT code must do this interaction with the user.

SET is another CLIENT thing, it is a formatting command in SQLPlus.  You use
TO_CHAR in PLSQL with a format.

      Rating:
      5
Shucks!  June 30, 2003
Reviewer:  Abdul
      Wahab  from Jeddah,K.S.A
Thanx TOM
   Thanks for giving me this information otherwise i was wasting my time trying
to get its solution.
   But it was sad to know that PL/SQL is not an interactive language like C.Yeah
it would have been great if i could stop it at row 5 and see what it is!;)
take care

          Followup:

C is not "interactive" when run on a server either.

PLSQL can be run on a client (in forms for example).  there is can be
interactive.

C can be run on a client, there is can be interactive (well, yes then there is X
windows where C is running remotely but the UI is local).

When you run C on a server over a network, or as a background process on your
local machine (mimicking PLSQL stored procedures), it is only interactive if you
build some other client that talks to the "C server" code and interacts for you

      Rating: 3
Why so
      expensive?  June 30, 2003
Reviewer:
      A reader
The entire Oracle 9iR2 application development documentation set is around
$540!!! Even with a 20% discount for OTN members this is outrageous!  Is the
Oracle Documentation printed in special paper?  Why so expensive?  Is this being
done to save some trees (I doubt it)?

          Followup:

umm, why you would ask me is beyond me?  but you can download cd's (actually you
should already have that as part of your install) of the docs for free.

Given that individual technology books retail for $40-60, seems priced
comparably.

And yes, trees/manufacturing costs, etc have something to do with it.  I myself
haven't had a printed set of Oracle doc since version 7.2.  I have every set of
Oracle doc since on my machines.

      Rating: 4
A good book on
      ...  June 30, 2003
Reviewer:  Alex
       from UK
When I learned PL/SQL programming I found this book -

Building Intelligent Databases With Oracle Pl/Sql, Triggers, and Stored
Procedures

http://www.amazon.com/exec/obidos/tg/detail/-/0134436318/qid=1056732236/sr=1-1/ref=sr_1_1/103-5021392-9611034?v=glance&s=books
to be quite good on conceptual side.

PS. Dear Tom, Your recommendations on PLSQL and C books are very useful, thanks.
Could you also recommend a good Java book please?

          Followup:

I don't really have a recommendation on a java book -- except everyone that
reads them should also read the CONCEPTS GUIDE and APPLICATION DEVELOPERS GUIDE
and use the database...

      Rating: 5
Comment to "A
      Reader" (Why so expensive?)  June 30, 2003

Reviewer:  Dan  from Yukon, Canada
ummmm.... $540 for the complete Oracle Documentation set on paper sounds pretty
cheap.  I've never seen to whole shebang on paper (haven't seen anything on
paper since 7.3) but I understand it's over 15,000 pages.  So at $540 the cost
is under 4 cents a page.  I really don't now what volume printing costs are but
that sounds not bad.  In fact if it was much cheaper you might be able to sell
it to a recycler and make some money!

Expert One-on-One Oracle is far more expensive at almost a whopping 4.8 cents
per page!  Extortion!  (That's amazon's price, by the way).

... actually maybe I should be including the cost of the shelf I'd have to buy
if I did get the whole doc set on paper...

          Followup:

well, the appl developers stuff is about 8/10 books as I recall -- it is not the
entire set (thats 1,000$ plus for hardcopy)

store.oracle.com

you can check it out there.  Not that you want to buy them, I find the pdf to be
infinitely more useful personally.

      Rating: 5
Read the docs
      online. It's free!  July 01, 2003
Reviewer:
      A reader

      Rating:
      4
visiting..  July 10, 2003
Reviewer:
      Marian  from Iasi, Romania
Hi...I was just passing through...I stopped for a little bit and I started
reading the questions and answers. I finished by bookmarking Tom's page and
started reading the Oracle docs on PL/SQL :) I should kill you, because I lost
my sleep! Anyway..I'll follow some of the paths I found here..Thanks.

      Rating:
      5
  July 22, 2003
Reviewer:  Harish
       from London, England
Tom

Can you recommend any books which teach Oracle SQL for a relative beginner to
SQL?

Thanks
Harish

          Followup:

sorry, it has been a long time since I would have read one of those -- Not sure
of the quality of specific books since I haven't read them.

Maybe someone reading this will have a good idea for you...

      Rating: 5
Quick Start
      Book  July 22, 2003
Reviewer:  Brian
       from Phoenix
Don't laugh, but I read SAMS Teach Yourself SQL, and that got me going for the
first week.

I, then, read Beginning Oracle Programming and some of the major (Concepts, SQL
Reference, PL/SQL etc.) Oracle Docs that Tom mentioned above.

Once you get through those, Tom's book is a definite read.

      Rating: 5
On the
      contrary....Brian  July 22, 2003
Reviewer:
      Tarry  from Netherlands
Couple of years I got started on the same path as the one who started this
thread.

I found this an excellent source to start with and indeed it was Sams.

This one will teach you SQL pretty good and it's all free. Depending on your
perpicacity, you might want to jump over to more advanced book but trust me
you'll find your way out once you get started with this one.

http://165.193.123.40/content/articlex.asp?product_id={1BFB99FF-FC1E-453C-82F5-E88E7875A9FF}&element_id={B1F8D6EF-42F8-4F6B-9488-8A5AC81F8CDC}
and this stuff is all free.

I liked the illustrations then and looking back I still like it now. I always
try to picturize in thin air what I'm doing and that helps me get closer to the
answer.

Later along the path I found Tom's book(expert one-on-one) an excellent stuff to
get a deeper insight. There are lots of people who know the stuff real good but
aren't able to translate it to others. Tom can do it very well.

All the best and get started with the beautiful language we call SQL,

Tarry

      Rating: 5
Thanks
      :-)  July 23, 2003
Reviewer:  Harish
       from London, England
Thanks for the information peeps :-)

I will give the SAMS SQL book a try and also read the documentation on-line.

Harish

      Rating: 5
PL/SQL
      Practices  July 23, 2003
Reviewer:  Muneer
      Oubaid
 Hello Tom.
I am Muneer Oubaid,
 I am tring to learn more about the PL/SQL practices , Iam now in the last step
of the PL/SQL course 'the teacher book of this course is 'Introduction to
oracle: SQL and PL/SQL '

My question is if its posible ,if you could make me a favour to download
immediately aditional practices on the PL/SQL , more difficlut kind of these
practices .

                                   23-7-2003
                          many  thanks
                                       eng.Muneer Oubaid

      Rating: 4
Oracle
      Guides  July 24, 2003
Reviewer:  H.S.Anand
       from New Delhi, India
Hi tom,
are the oracle guides, available some where in PDF format?

          Followup:

otn.oracle.com -> documentation

all of them are there.

      Rating: 4
A Excellent
      PL/SQL Book  August 05, 2003
Reviewer:
      Espie  from NY, NY
Try the Oracle PL/SQL Interactive Workbook by Ben Rosenzweig & Elena Silvestrova
published by Prentice Hall.  It's a great beginners/intermediate book that's
full of many useful examples and exercises.

July 24, 2003 Nirmal -- Thanks for the question regarding
      "ARRAY PROCESSING", version 9.2

      You Asked
Hi Tom,
      I have created a TYPE as:
Create or replace type NUM_ARRAY as table of varchar2(1000);

Then I have a procedure which accepts an ARRAY of sql statements and executes
one at a time. Now this take a lot of time to process a batch of sql statement.

I was wondering if I can use the forall to execute them in a bulk as these are
all insert/update/delete statements;

Here is the Procedure:

CREATE OR REPLACE procedure give_me_an_array( p_array in num_array )
as
begin
  for i in 1 .. p_array.count loop
  BEGIN
    execute immediate p_array(i);
  EXCEPTION
  WHEN OTHERS THEN NULL;
  END;
    --dbms_output.put_line( p_array(i) );
  end loop;
  commit;
end;

We are using Java as the Front-End. Your input will be highly appricated as I am
competing against SQLServer which is doing this extremly fast without using any
ARRAYS. I am in the process of migrating from SQLServer to Oracle and the
condition is if Oracle runs faster than SQLServer.

Thanks

Nirmal

      and we said...
wow, does that approach stink or what.

No bind variables, none, nada, nunca.  i shudder at the thought.

even worse -- a when others without a RAISE!

and then -- it gets worse -- a commit?

Ugh, now I see what the problem is.  You want Oracle to run faster then
sqlserver but you want to program it the way you would program sqlserver?

Tell you what -- code it right for Oracle, code it right for SQLServer and then
run 10, 100 conncurrent sessions (you know, like the "real world") and see what
happens.  See which one scales.  See which one flies then.

I have a feeling you are testing a single user, no bind variable example.  You
are parsing every statement every time.  This just stinks, it is so wrong - so
so so so wrong in Oracle.

do the java programmers understand BIND VARIABLES?
do the java programmers understand PREPARED STATEMENTS?
do the java programmers understand that using simple string concatentation is a
SECURITY BUG big enought to drive a tractor trailer through? (hint search google
for "sql injection" in quotes and read about it - pretty scary eh?)

I'm not going to give you marginal ideas here to "fix" this.  If you use
prepared statements and bind variables and batching in the java app, you'll find
it screams.

if you want a good single user database -- go for sqlserver, have at it.  If you
do a real test, with real loads -- with the code properly written -- you'll see
a "slight" difference.

Review &
      Followup

      Rating: 4
ARRAY
      PROCESSING  May 13, 2003
Reviewer:  Nirmal
       from USA
Wow, wow, wow. That was a real response, I know the approch Stink, but i
appricate you coming back so soon.

Well I forgot to mention that we are using prepared statement and bind variables
and batch execute etc. But that was still not able to get near to SQLServer.

I am trying to figure out an alternative and test if something like this could
make a difference.. I know the code i sent you is crapy, but this is just a test
to see what will work better. I know I have lot to do.

I was wondering if i can batch up all my inserts/updated and deletes into an
Array an execute them in bulk. If possible I am sure that should work much
faster than batch executes using JDBC.. You help will be appricated..

Thanks

          Followup:

the batch executes in jdbc would be just as fast.

do you have a tkprof of the optimal solution in java?

      Rating: 4
ARRAY
      PROCESSING  May 13, 2003
Reviewer:  Nirmal
       from USA
Hi Tom,
      Sure I can get you the tkprof. I am at home right now. I will send you
tomorrow as soon as I get to the office.

Thanks

      Rating: 4
"SQL injection"
        May 14, 2003
Reviewer:  Tony  from
      Chennai, INDIA
Is SQL injection bug for SQL server alone or its applicable for Oracle db also?

          Followup:

Oracle, Informix, you name it.

consider the jsp that asks you for username and password.  It'll execute a query
like:

"select count(*) from my_user_table where username = '" + UserName +
"' and password = '" + PassWord + "'"

if count > 0 then -- you are IN...

using concatenation instead of binds.  great, I'll give you a username/password
of

username = BOB
password = a' or 'a'='a

well, your query turns into

select count(*) from my_user_table where username = 'BOB'
and password = 'a' or 'a'='a'

hmm. I can change result sets, inject arbitrary SQL into your SQL.  Not good.

Binds -- they are not just for performance....

      Rating: 4
ARRAY
      PROCESSING  May 14, 2003
Reviewer:  Nirmal
       from usa
Hi Tom,
      Continueing on our discussion, I realized that SQLServer has primary keys
as clustered on 99% of the tables. Do you think it would be a good idea to have
all my pk's under cluster(Though I am not convenced that it's a good idea)..

This Apps is having a heavy inserts and deletes..

thanks

      Rating: 5
SQL injection
      applicable for procedures?  May 15, 2003
Reviewer:
      Tony  from INDIA
we use packages or procedures and put all the quries in it.
The middle tier just calls those procedures alone by passing parameters. In this
case, do I still have to worry about SQL injection bug?

          Followup:

do you do dynamic sql in your plsql?

if so, sure, if you do not use bind variables - your plsql is subject to it.

do they NOT use bind variables?

if so, if they concatenate strings, you are very (even in a worse way) prone to
it.

Consider the routine in java or vb:

  sqlStmt := "begin p( '" + some_inputs + "' ); end;"

looks blindingly simple, cannot go wrong, or can it.

I give you some inputs:

abc' ); delete from t; dbms_output.put_line( '

suppose ABC is valid, p will be happy with that. Now what block gets executed:

begin p( 'abc' ); delete from t; dbms_output.put_line( '' ); end;

now, replace delete from t with pretty much anything you want, execute immediate
'drop table important_table'; -- think about what might go there and you would
just execute.

any application that accepts inputs from users and does not use bind variables
to get that to the database -- any database -- is 100% prone to sql injection
security bugs

      Rating: 5
Is it that much
      easy to change SQLs?  May 16, 2003
Reviewer:
      Tony  from INDIA
First of all, How is it possible to change SQLs in any application?. I have read
some sites but still not clear. If you give an explanation, even novice can
easily understand.

          Followup:

huh?

this question needs an explanation.

      Rating: 4
ARRAY
      PROCESSING  May 16, 2003
Reviewer:  Nirmal
       from USA
Hope you will appricate this one. But I am still looking for a neater stuff were
i do not have to create those many arrays to get the values from my java
program, which sends array of values to this proc.

Any suggestion will be appricated:

create or replace type CS1_NUM_ARRAY as table of number;
create or replace type CS2_DATE_ARRAY as table of date;
create or replace type CS3_NUM_ARRAY as table of Number;
create or replace type CS4_NUM_ARRAY as table of Number;
create or replace type SS1_NUM_ARRAY as table of Number;
create or replace type SS2_DATE_ARRAY as table of date;
create or replace type SS3_NUM_ARRAY as table of Number;
create or replace type SS4_NUM_ARRAY as table of Number;
create or replace type SS5_NUM_ARRAY as table of Number;
create or replace type SS6_NUM_ARRAY as table of Number;
create or replace type SS7_NUM_ARRAY as table of Number;
create or replace type SS8_NUM_ARRAY as table of Number;

CREATE OR REPLACE procedure p_get_array
(v_table varchar2, v_cs1 CS1_NUM_ARRAY,v_cs2 CS2_DATE_ARRAY,v_cs3
CS3_NUM_ARRAY,v_cs4 CS4_NUM_ARRAY,
v_ss1 SS1_NUM_ARRAY,v_ss2 SS2_DATE_ARRAY,v_ss3 SS3_NUM_ARRAY,v_ss4
SS4_NUM_ARRAY,
v_ss5 SS5_NUM_ARRAY,v_ss6 SS6_NUM_ARRAY,v_ss7 SS7_NUM_ARRAY,v_ss8 SS8_NUM_ARRAY)
AS

v1_table varchar2(50) := 'tbl_'||v_table;
l_start_time date;
l_end_time date;
l_difference number;

counter number := 0;
ins_ctr number := 0;
array_size number := 50;

-- declare array types  ...
-- -------------------
type number_table is table of number index by binary_integer;
type date_table is table of date index by binary_integer;

-- define arrays ...
-- -------------

my_cs1_array number_table;
my_cs2_array date_table;
my_cs3_array number_table;
my_cs4_array number_table;

my_ss1_array number_table;
my_ss2_array date_table;
my_ss3_array number_table;
my_ss4_array number_table;
my_ss5_array number_table;
my_ss6_array number_table;
my_ss7_array number_table;
my_ss8_array number_table;

begin

-- Now read the data and insert it into the table ...
-- ---------------------------------------------

   begin

      for k in 1..v_cs1.COUNT loop
         ins_ctr := ins_ctr + 1;
         my_cs1_array(ins_ctr) := v_cs1(ins_ctr);
         my_cs2_array(ins_ctr) := v_cs2(ins_ctr);
         my_cs3_array(ins_ctr) := v_cs3(ins_ctr);
         my_cs4_array(ins_ctr) := v_cs4(ins_ctr);

         my_ss1_array(ins_ctr) := v_ss1(ins_ctr);
         my_ss2_array(ins_ctr) := v_ss2(ins_ctr);
         my_ss3_array(ins_ctr) := v_ss3(ins_ctr);
         my_ss4_array(ins_ctr) := v_ss4(ins_ctr);
         my_ss5_array(ins_ctr) := v_ss5(ins_ctr);
         my_ss6_array(ins_ctr) := v_ss6(ins_ctr);
         my_ss7_array(ins_ctr) := v_ss7(ins_ctr);
         my_ss8_array(ins_ctr) := v_ss8(ins_ctr);

--   If the array is full, insert it into the database ...
---------------------------------------------------

         if ( ins_ctr = array_size ) then
         execute immediate
     'begin
         forall j in 1..:N
          insert into :T1

values(my_cs1_array(j),my_cs2_array(j),my_cs3_array(j),my_cs4_array(j));
        end;'
        USING rtrim(v1_table),array_size;

    execute immediate
        'begin
             forall j in 1..:N
                insert into tbl_TSAggBuffer
                    values(
my_ss1_array(j),my_ss2_array(j),my_ss3_array(j),
my_ss4_array(j),my_ss5_array(j),my_ss6_array(j),
my_ss7_array(j),my_ss8_array(j));
        end;'
        USING array_size;
        ins_ctr := 0;
         end if;
      end loop;

   exception
   when others then
   DBMS_OUTPUT.PUT_LINE(substr(sqlerrm,1,100));
   end;

-- Now insert the last batch, commit and close the file ...
-- ----------------------------------------------------

   if ( ins_ctr > 0 ) then

       execute immediate
      'begin
             forall j in 1..:N
                insert into :T1
                values
(my_cs1_array(j),my_cs2_array(j),
my_cs3_array(j),my_cs4_array(j));
    end;'
    USING rtrim(v1_table),ins_ctr;
   execute immediate
  'begin
   forall j in 1..:N
   insert into tbl_TSAggBuffer values
(my_ss1_array(j),my_ss2_array(j),
my_ss3_array(j),my_ss4_array(j),
my_ss5_array(j),my_ss6_array(j),
my_ss7_array(j),my_ss8_array(j));
   end;'
   USING ins_ctr;
   end if;
   commit;
end;
/

          Followup:

seem to me you only need two types here?

numArray
dateArray

add a third for completeness

vcArray

and you would have them all.  I don't know why you feel compelled to make all of
those types?

      Rating: 4
"SQL
      Injection"...   May 16, 2003
Reviewer:
      Robert  from PA
Tom, this "SQL Injection" is a "new" thing ?
'cause I don't think you mentioned a word of it in
"Expert 1 to 1"

          Followup:

Its been around for as long as people have been gluing in strings.  I added it
to the "bind variable repitoire" solely because people said "well, I don't need
to do this in sqlserver, blah blah blah (they have no shared pool)"

So, I gave them yet another good reason that they need to do it (ESPECIALLY in
sqlserver -- it is frightening what sql injection can do in their database!)

      Rating: 4
ARRAY
      PROCESSING  May 20, 2003
Reviewer:  Nirmal
       from USA
You Said.
seem to me you only need two types here?

numArray
dateArray

add a third for completeness

vcArray

and you would have them all.  I don't know why you feel compelled to make all of
those types?

I Said:

I will appricate if you could give a small example..

Thanks

          Followup:

example of what?

      Rating: 5
SQL Injection
      (again)  May 21, 2003
Reviewer:  Martin
       from UK
Hi Tom,

I couldn't come up with a test case where I can execute arbitrary SQL statements
if using EXECUTE IMMEDIATE for dynamic SQL, since EXECUTE IMMEDIATE will fail
with an "invalid character" error (well, it does at 9.2.0.3 anyway), i.e.

SQL> ed
Wrote file afiedt.buf

  1  begin
  2  execute immediate 'select a from t where a = 10; delete from t';
  3* end;
SQL> /
begin
*
ERROR at line 1:
ORA-00911: invalid character
ORA-06512: at line 2

Does this mean that the procedure is "safe" (in inverted commas, of course we
should be using bind variables etc.) if using this method. If not, can you give
a quick example, where systems using this method can be "got at".

Thanks in advance

          Followup:

if you are executing SQL in ORACLE, sql injection is limited to:

a) me changing the meaning of your query (the username/password example).  This
will give me access to stuff I otherwise would not have access to.

b) calling functions that take IN parameters and are callable from SQL.  So, I
might be able to run some of your code you did not intend me to run.

At the end of the day, it doesn't matter -- it is an 100% verifiable security
hole and a performance sink.

In SQLServer - the security hole is (surprise) even bigger -- in sqlserver that
statement you built would fly, it would execute.

      Rating: 3
ARRAY
      PROCESSING  May 21, 2003
Reviewer:  Nirmal
       from USA
YOUR FOLLOW-UP:
seem to me you only need two types here?

numArray
dateArray

add a third for completeness

vcArray

and you would have them all.  I don't know why you feel compelled to make all of

those types?

I SAID:

Forget about the example, could you elaborate you comments above??

          Followup:

you declared a type per variable.

I would declare three types and use them over and over and over.

...
create or replace type CS1_NUM_ARRAY as table of number;
create or replace type CS2_DATE_ARRAY as table of date;

create or replace type CS3_NUM_ARRAY as table of Number;
create or replace type CS4_NUM_ARRAY as table of Number;
create or replace type SS1_NUM_ARRAY as table of Number;
create or replace type SS2_DATE_ARRAY as table of date;...

why would you do that.

      Rating: 3
ARRAY
      PROCESSING  May 22, 2003
Reviewer:  Nirmal
       from USA
OK The reason for me to do that is that I have to pass an Array of values for
each of the columns from my java program and catch it all at once in my stored
procedure. This is how my Java looks like(Not the real program):

        String intArray[] = new String[intList.size()];
        Iterator iints = intList.iterator();
        int i=0;
        while (iints.hasNext())
        {
            intArray[i] = (String) iints.next();
            System.out.println(intArray[i]);
            i++;
        }
        System.out.println("Value of i :" + i);
        ArrayDescriptor descriptorint = ArrayDescriptor.createDescriptor(
"INTARRAY", conn );
        ARRAY int_array_to_pass = new ARRAY( descriptorint, conn, intArray );
        //Process Dates

        String dateArray[] = new String[dateList.size()];
        Iterator idates = dateList.iterator();

        int j=0;
        while (idates.hasNext())
        {
            dateArray[j] = (String) idates.next();
            System.out.println(dateArray[j]);
            j++;
        }

        ArrayDescriptor descriptordate = ArrayDescriptor.createDescriptor(
"DATEARRAY", conn );
        ARRAY date_array_to_pass = new ARRAY( descriptordate, conn, dateArray );

//OraclePreparedStatement ps =
//  (OraclePreparedStatement)conn.prepareStatement
//  ( "begin give_me_an_array(:x); end;" );

oracle.jdbc.OracleCallableStatement ps =
(oracle.jdbc.OracleCallableStatement) conn.prepareCall ("begin
give_me_an_array_test(:x,:y); end;");

ps.setARRAY( 1, int_array_to_pass );
ps.setARRAY( 2, date_array_to_pass );
ps.execute();

          Followup:

so, you only need 3 array types -- no one per input value.

      Rating: 4
how can i do
      this  May 23, 2003
Reviewer:  umesh
       from bangalore india
I have a table called user_tab which has a column user_id
in one of our application  2 users have  entered a user_id as
1)   abcd'"
2)   abcd"'

when i click on a jsp page i have to check wether this user exists
how do i write a query for the same
i tried
"select * from user_tab where user_id ='" + <user_id> + '"'
but in vain
can it be done ?
thanks

          Followup:

YOU USE BIND VARIABLES -- YOU NEVER GLUE IN STUFF LIKE THAT.

your query would be

"select * from user_tab where user_id = ?"

period, end of problem.

Do this for me -- search google for:

"sql injection"

and tell me what happens when an end user inputs

abcd' or 'a' = 'a

into your "enter a username field".  Hmmmmmmm -- that won't happen with binds
(and you'll achieve slightly more scalability).

seek out and destroy ALL JAVA CODE that doesn't use bind variables on your
system.  If not for the performance, for security.

      Rating: 1
ARRAY
      PROCESSING  May 23, 2003
Reviewer:  Nirmal
       from USA
Hi Tom(or whoever)

THANKS A LOT..

      Rating: 3
array
      processing  July 23, 2003
Reviewer:  kti
       from england
hi tom,
I know its an array procesing topic so please try and see how i can do the
following
I have a table with 6 columns a1 a2 .. a6

 I need to scan this table where if any of the columns contains 'VIT' I need
replace with the next col

 eg
 if a1 = 'VIT'
 then
     a1 = a2,
     a2 = a3
     a3 = a4
     a4 = a5
     a5 = a6
     a6 = null
  else
  if a2= 'VIT'
  then
       a2 = a3
       a3 = a4
       a4 = a5
       a5 = a6
       a6 = null
else
and so on until non of the columns a1 .. a6 = 'VIT'

          Followup:

ouch, hurts my head to think about it.

guess I would concatenate all of the columns together (say the columns are
varchar2(10)) and substr them back out:

ops$tkyte@ORA920LAP> select * from t;

C1         C2         C3         C4         C5         C6
---------- ---------- ---------- ---------- ---------- ----------
VIT        a          b          c          d          e
a          VIT        b          VIT        d          e
a          VIT        b          VIT        VIT        e
VIT        VIT        b          VIT        VIT        e

ops$tkyte@ORA920LAP> update (select t.*,
  2                 substr( data,  1, 10 ) new_c1,
  3                 substr( data, 11, 10 ) new_c2,
  4                 substr( data, 21, 10 ) new_c3,
  5                 substr( data, 31, 10 ) new_c4,
  6                 substr( data, 41, 10 ) new_c5,
  7                 substr( data, 51, 10 ) new_c6
  8            from (select t.*,
  9                         rpad( decode(c1,'VIT',null,c1), 10 ) ||
 10                         rpad( decode(c2,'VIT',null,c2), 10 ) ||
 11                         rpad( decode(c3,'VIT',null,c3), 10 ) ||
 12                         rpad( decode(c4,'VIT',null,c4), 10 ) ||
 13                         rpad( decode(c5,'VIT',null,c5), 10 ) ||
 14                         rpad( decode(c6,'VIT',null,c6), 10 ) data
 15                    from t
 16                   where c1 = 'VIT' or c2 = 'VIT' or c3 = 'VIT'
 17                      or c4 = 'VIT' or c5 = 'VIT' or c6 = 'VIT' ) t
 18          )
 19     set c1 = new_c1, c2 = new_c2, c3 = new_c3, c4 = new_c4, c5 = new_c5, c6
= new_c6
 20  /

4 rows updated.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select * from t;

C1         C2         C3         C4         C5         C6
---------- ---------- ---------- ---------- ---------- ----------
a          b          c          d          e
a          b          d          e
a          b          e
b          e

ops$tkyte@ORA920LAP>

      Rating: 2
array
      processing  July 23, 2003
Reviewer:  kit
       from england
Hi tom,
why am i seeing my questions answered status when they are  not
am i submitting them incorrectly

          Followup:

erh?

don't understand what you are saying.

      Rating: 5
array
      processing  July 24, 2003
Reviewer:  kit
       from england
sorry for the earlier comment. I thought my comments were not being read. I had
a hasj join issue that still needs looking at. Hope you can find more people
like on your team that can  anwer questions as you're the best

how did you think of that solution. I was going to use collections and loop
though the array but single update

fantastic

          Followup:

(and i told you 'hash question is bigger then a breadbox, when I'm taking
questions we can deal with it there')

July 24, 2003 sushant -- Thanks for the question regarding
      "mutating table & constraining table", version 8.1.5

      You Asked
i have read a bit about mutating error but never understood it fully.

  as far as i know  if  the table in the  triggering statement  is used in the
triggering body we get a mutating error.  but this is true only in case of row
level triggers . even in row level triggers if the triggering statement is
before insert we dont get a mutating error if we use the same table in the
triggering statement and the triggering body.

    my question is why we get this error ie why cant we use a table in the
triggering statement also in the body of the trigger.

  what is a constraining table.

if i want to grant any other user to edit an existing procedure in my schema .
how do we do it. the create any procedure just allows the other user to create a
new procedure in my schema and not edit an existing procedure.

      and we said...
The mutating table is there to protect you.

See

http://groups.google.com/groups?q=mutating+ambigous+author:tkyte%40us.oracle.com&hl=en&rnum=1&selm=370653dc.32102901%40192.86.155.100
for my explaination of this with examples.

In the very special case of an "insert into t values ( 1,2,3 )", we've relaxed
this as this is the one time (and one time only) we can determine that you will
NOT be updating more then one record.  If you use "insert into t select 1,2,3
from dual" instead -- you WILL get the mutating table error (even though to you
and me they are the same statement)

A mutating table is a table that is being modified.  A constraining table is one
involved in declaritive referential integrity with the mutating table.

As for the second question,

If you give that person the ability to CREATE ANY PROCEDURE, they certainly can
"edit" using CREATE OR REPLACE any existing procedure in your schema as well.
They can "edit" them.  Not only that, they can EDIT ANY procedure in the
database.  You should NOT use this privilege in general.  It is VERY powerful.
Very powerful.

Instead, user1 should:

create or replace procedure create_or_replace_procedure( p_str in long )
as
begin
    execute immediate 'create or replace procedure ' || p_str;
end;
/

and then grant execute on that procedure to user2.  Now, user2 can issue:

SQL> exec user1.create_or_replace_procedure( 'foo as begin dbms_output.put_line(
''hello world'' ); end;' );

Just make sure user1 has CREATE PROCEDURE granted directly to them, not via a
role!  See

http://osi.oracle.com/~tkyte/Misc/RolesAndProcedures.html
for why this is relevant.
Review &
      Followup

      Rating: 5
"Weird"
      mutating   March 07, 2002
Reviewer:  Sa?a
       from Belgrade, Yugoslavia
Hi Tom,
We have strange situation in our DB (Oracle 8.1.7) about mutating tables.
We have two tables A and B, and trigger on A (after update )
which populate table B depending on some flags.
On table B we have also trigger(before insert) which do next:
FOR EACH ROW
DECLARE
k NUMBER;
Mutating EXCEPTION;
PRAGMA EXCEPTION_INIT(Mutating, -4091);
BEGIN
SELECT COUNT(*) INTO k FROM A WHERE IdA = :NEW.IdA;
RAISE_APPLICATION_ERROR(-20010, 'Inserting of thru updateof table B is not
allowed! Please update A table instead.');
EXCEPTION
WHEN Mutating THEN
null;
END;

With this trigger we wanted prevent direct insert on B table and enforce insert
to table B only thru table A.
We force mutating table during insert to A and in trigger on table B catch this
mutating in exception handler.
Otherwise direct insert thru B will fail.
Trick is that it doesnt work for the first time logging to session.Every next
try in same session succeded.
Fir the first time in session somehow "mutating" does not work but next
time(next insert to table A in a same session) works fine.
I know that workaround is writing procedure but am I curious
about this behavior.

PS
Table B is in relation with table A.
FK constraint in table B shows to PK of table A.

          Followup:

Well, a more sensible approach can be done via the method outlined here:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:382218730166
you can use who_called_me in the b trigger to make sure the a trigger did the
insert.

In anycase, I cannot reproduce.  I would need a full testcase - from start to
finish -- in order to answer your question.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table a ( ida int primary key, flags
char(1) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table b ( idb int primary key, ida
references a );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create sequence b_seq;

Sequence created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace trigger a_trigger
  2  after update on a for each row
  3  begin
  4          if (:new.flags = 'Y')
  5          then
  6                  insert into b values ( b_seq.nextval, :new.ida );
  7          end if;
  8  end;
  9  /

Trigger created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace trigger b_trigger
  2  before insert on b
  3  FOR EACH ROW
  4  DECLARE
  5     k NUMBER;
  6     Mutating EXCEPTION;
  7     PRAGMA EXCEPTION_INIT(Mutating, -4091);
  8  BEGIN
  9     SELECT COUNT(*) INTO k FROM A WHERE IdA = :NEW.IdA;
 10     RAISE_APPLICATION_ERROR(-20010, 'Inserting of thru updateof table B is
not allowed! Please update A table instead.');
 11  EXCEPTION
 12     WHEN Mutating
 13     THEN
 14          null;
 15  END;
 16  /

Trigger created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into a values ( 1, 'N' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into b values ( b_seq.nextval, 1 );
insert into b values ( b_seq.nextval, 1 )
                       *
ERROR at line 1:
ORA-20010: Inserting of thru updateof table B is not allowed! Please update A
table instead.
ORA-06512: at "OPS$TKYTE.B_TRIGGER", line 7
ORA-04088: error during execution of trigger 'OPS$TKYTE.B_TRIGGER'

ops$tkyte@ORA817DEV.US.ORACLE.COM> update a set flags = 'Y';

1 row updated.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into b values ( b_seq.nextval, 1 );
insert into b values ( b_seq.nextval, 1 )
                       *
ERROR at line 1:
ORA-20010: Inserting of thru updateof table B is not allowed! Please update A
table instead.
ORA-06512: at "OPS$TKYTE.B_TRIGGER", line 7
ORA-04088: error during execution of trigger 'OPS$TKYTE.B_TRIGGER'

ops$tkyte@ORA817DEV.US.ORACLE.COM> update a set flags = 'Y';

1 row updated.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from b;

       IDB        IDA
---------- ----------
         2          1
         4          1

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

It seems to work for me (but it is somewhat obscure in its implementation...)

      Rating: 5
WHAT IS THE
      REASON THAT I'M GETTING THIS ERROR  June 27, 2002

Reviewer:  Vasaku

ERROR at line 1:
ORA-20002: Error update of empno...
ORA-06512: at "srily.acc_emp", line 9
ORA-04088: error during execution of trigger 'srily.acc_emp'

 create or replace trigger acc_emp
   after update
   of empno
   on emp_table
   for each row
 begin

   UPDATE emp_table
   SET empno = :new.empno
   WHERE add_empno = :old.add_empno;
 exception
     when others then
          raise_application_error (-20002, 'Error update of empn0')
 end;

why I'm I runninginto ora-04088 , and further how is ora-04088 different from
ora-04091?

Please answer I need to fix this issue..

          Followup:

It is hard to say WHY since you are hiding the error message from us by having
the when others that does provide us with the sqlerrm.

Looking at it and coupled with the 4091, mutating table, that you mention -- it
is easy to see actually.  You have an update, for each row trigger on EMP_TABLE.
 That trigger is attempting to update EMP_TABLE.  That trigger is quite simply
not allowed to READ or WRITE EMP_TABLE (the table is in fact mutating).  In fact
-- supposing this did work -- you would have just coded an infinite loop (while
updating the emp table, fire a trigger -- let this trigger update the emp table
which will in turn fire the trigger and so on)

Don't know what you are trying to accomplish but the reasons why it won't work
as coded are fairly clear.

      Rating: 5
If I have to
      acheve functionality like the above   June 28, 2002

Reviewer:  Vasaklu
How should I achieve .. it is obvious that I cannot use an update trigger on the
table I intent to update

thx pleaselet me know.

          Followup:

what is it that you are trying to achieve then?

without knowing more about what you want to do, the structure of you table --
etc -- it's not possible.

Read this first:

http://asktom.oracle.com/~tkyte/Mutate/index.html

      Rating:
      5
Excellent  July 18, 2002
Reviewer:
      Doug  from CT, USA
I had a mutating problem today and was able to solve it using your post on
groups.google.com that you pointed to above.  It seems that by deferring the
"reflexive" actions of the trigger body until all inserts are finished as one
batch, things are fine.

One quick and possibly silly follow up question.  I get the impression you
worked out that solution a while ago.  With the new version 8+ rowid format, is
there anything extra to consider or will keeping a table type of array id's as
in the example still do it.

          Followup:

well, if possible, use the primary key of the table but the rowid format is
still AOK.

      Rating: 5
Scalability of
      the ROWID workaround?  July 19, 2002
Reviewer:
      Doug  from CT, USA
Tom - I have implemented your rowid workaround for a nearly identical situation.
 I'm finding it works faster for "mid-sized" bulk inserts, such as 20 rows take
3 seconds with the rowid and 10 seconds if done as atomic individuals.  But when
I get up to 12,000 rows, it takes 2313 seconds as oppossed to 1702 seconds for
individual atomic inserts.  Any idea what could be slowing it down?

          Followup:

Memory management, big arrays, etc.

I wouldn't be using such an approach for mass data loads.  Triggers are fine on
tables you do mostly singleton inserts on.  You'll want a different approach for
bulk loading regardless.

      Rating: 5
constraining
      table?? ?   July 24, 2003
Reviewer:  jignesh
       from India
"A constraining table is one
involved in declaritive referential integrity with the mutating table."

Could you please elaborate on this ? ?

          Followup:

parent child tables in a foreign key relationship

July 24, 2003 Mohammed -- Thanks for the question regarding
      "Defragment Tablespace / Datafiles and Some security Tips", version 8.1.7

      You Asked
Dear Tom
1.I am having about 6gb of datafile Which is growing constantly even I dont have
Used space , may be only 1gb When I tried to reduce the size of the datafile, it
gave me error like ORA-03297: file contains used data beyond requested RESIZE
value I reduce only upto the used space + some MBs .

What Coalesce is and its Function.
How We can Defrag Tablespace

2.Dear Tom I wanted to protect my database by chaning all the passwords for all
users and I did . To my knowledge I find  anybody have access to server and
svrmgr utility can connect as internal
and change the password for any user in my absence, In this case how can I
protect because the server is open and anybody can work on server.

Thanks  in Advance

      and we said...
1) if you use locally managed tablespaces -- it is physically impossible to have
them be fragemented.  So, if you are using the obsolete dictionary managed
tablespaces just do this:

create tablespace best_kind
datafile ..... size .... autoextend ....
extent management local;

and then alter all of the objects in the existing DMT and move them to the new
tablespace (alter table T move tablespace best_kind, alter index I rebuild
tablespace best_kind) and be done with it.  Then drop that old fashioned
tablespace.

The only way you could have a 6gig tablespace that has 5gig free is that you
created and then subsequently dropped or truncated tons of objects.  Consider
putting those objects you frequently do that in their OWN tablespaces and manage
them separately.

coalesce simply takes adjacent free extents and merge them into one larger free
extent.  coalesc applies ONLY to dictionary managed tablespaces as space is
always automagically coalesce in locally managed.

So -- suggestion you move to LMT as it sounds like you might be on DMT and
that'll solve the vast majority of your problems.

2) limit access to the DBA group -- only DBA group can connect internal on the
server without a password.  So, protect you OS accounts and limit the number of
DBA group members.
Review & Followup

      Rating: 3
Reduce the size
      of Tablespace  June 18, 2003
Reviewer:
      A reader
Hi Tom,

  I have the same problem that our friend had before. The issue is :

I wanted to create a new database on Oracle 8.1.7. I usually copy one of the
databases on that server, then create a new instance and rename the database. I
do it this way because it is faster to create the database with same structure
and same users and same....
It works ok. This time, I forgot to reduce the size of tablesapces and imported
data to them. Now one of my tablespaces is 5GB, but it used just 500MB. It is
Managed in Dictionary tablesapce, so I assumed if I use Coalesce, it will let me
to reduce the size. But still I get the same error. ORA-03297

Before doing import, I droped the user which had objects in that tablespace, I
mean I made it empty, then I created a new user and did the import fromuser..
touser and now, it just used 500 MB. So to my knowledge, it doesn't have any
objects which is using extends above 500 MB. I don't know why it doesn't allow
me to reduce the size !

As always, please help me on this isssue.
Thanks in advance

          Followup:

because at least one of the extents in your datafiles is at the "end" of the
data file.

use LMTS and extents tend to goto the "front" of the datafile.  stop using DMTS!

      Rating:
      3
  June 19, 2003
Reviewer:
      A reader
Thanks Tom. I assumed Coalesce make all extends continues. I mean remove the
last extend and put it at the end of the list. But it seems it just coalesce the
data blocks within an extend! Am I right?

Regards,
Arash

          Followup:

coalesce doesn't move anything.  So, if your data file had:

UUUFFFUFFFFFFFFFFFFFFFFFFFFU

(u=used, f=free) as extents, coalesce would result in:

UUUF..UF...................U

with the ..... being part of one extent.  that one used extent at the end will
stop you from shrinking the file.

      Rating:
      3
  June 19, 2003
Reviewer:
      A reader
Thank you so much. I got it.

      Rating: 3
Reduce the size
      of Tablespace datafile  July 18, 2003
Reviewer:
      Vikas Sahrma  from Delhi India
Hi Tom,

Your solution of moving and dropping tha tables is good.

What can be done if the datafile is in RBS which is also DMT.

I have the same problem with RBS while doing an import of a large table.

Thanks and Regards,

Vikas Sharma

          Followup:

when using rbs's -- i always

a) created a tablespace/rbs
b) put a single rbs in that tablespace

to "shrink", it is as easy as

a) drop rbs
b) shrink file
c) create rbs

      Rating: 5
Problem in
      LMT  July 21, 2003
Reviewer:  Riyaz
       from India
Thanks for your valuable tips.

I faced one problem. In the "test server", I took exports of all users and
dropped all tablespaces. (it was DMT)
And then created LMT and imported the users. While doing import, it was giving
error. When I checked the same, the data file is FULL. But I created the new
tablespace with 20% increase in size compared to the old DMT tablespaces. Then
why error has occured. Is LMT occupy more space than DMT?

Please answer.

          Followup:

it is simply taking the

initial, next, minextents, pctincrease

from the original tablespace and allocating AT LEAST that much space in the LMT.
 Say you had segments with:

( initial 1m next 2m pctincrease 50 minextents  5 ) and it had 5 extents right
now
( initial 64k next 64k minextents 100 ) and it had 100 extents right now

and you created a 1m uniform allocated tablespace.

that first segment would take:

1 + 2 + 3 + 4.5 + 6.75 meg of space rounded UP to the next megabyte increment (
minextents = 4, pctincrease = 50% )

the second segment would take

100 * 1m   (minextents = 100)

where as right now it take 6400k (6.3meg)

You might want to move objects from dmt's to lmt's using

alter table t move tablespace LMT storage ( initial 1k next 1k minextents 1
pctincrease 0 );

just to get rid of legacy storage clauses forever.

      Rating: 5
Thanks. Pl
      confirm my understanding.  July 24, 2003
Reviewer:
      Riyaz  from Chennai
Thanks.

I used "alter tablespace" and done the job as per your suggestion.

Pl confirm my understanding based on your earlier explanation.

Suppose in DMT, it had
( initial 64k next 64k minextents 100 )
ie., 100 extents (ie 6400K space occupied),

If I import the same in LMT which has (1M initial 1 M next pctincrease 0).. then
will it allocate 100 * 1 M = 100M for that particular table?

Pl confirm my understanding.

          Followup:

yes, it will -- it will use minextents to

a) compute the space it would have taken given the init/next/pctincrease
b) compute the min extents it would have

and it will make sure that the table in the LMT

a) has at LEAST that much space
b) has at LEAST that many extents.

      Rating:
      5
correction  July 24, 2003
Reviewer:
      Riyaz  from India
Correction:
I used 'alter table move tablespace' as per your suggestion. Pl confirm my above
understanding.

      Rating: 3
fragmentation
      of LMT  July 24, 2003
Reviewer:  kiro
       from Bulgaria
Hi Tom
As you said LMT could not be fragmented, but if we have
in LMT datafile
UUUUUUUUUUUUUUUUUUUUUUUUUEE.
when U - used extend
     E - empty space(naver contain data).

and I have made truncate table T1 with app 30 extends
we will have following ficture of datafile:
UUUUUUFFFFFFFFFFFFFFUUUUUEE.
when U - used extend
     E - empty space(naver contain data)
     F - free space.
Is it possible or not? If yes I think it is also fragmentation of datafile and
you can not resize it.
Regards

          Followup:

that is not fragmented is it -- fragmentation is when you

a) ask how much free space do I have?
b) the answer is 500meg
c) you try to create a table with a 100meg initial extent and it fails with
"insufficient space"

THAT is fragmentation -- your free space is in so many small, non-contigous
chunks that you cannot use it.

In your second example ANY object would be able to use ANY of the F's -- that is
not fragmented.

      Rating: 3
usable
      holes  July 24, 2003
Reviewer:  Dennis
       from Missouri, USA
Kiro,

The ability to shrink a file and the fragmentation of a file are two concepts.

Here, your file is not fragmented as you are in LMT (assuming uniform).  This
means anytime an object needs to grow into a new extent, it will be able to find
space, since the 'free holes' are all multiples of the extent size (it's uniform
after all).  In DMT this could hold to not true, especially if you had
PCTINCREASE on your tables, or tables of different initial and next sizes.  In
DMT, your datafile was fragmented if you had say 100 MB free, but couldn't
allocate a 5M extent.  With LMT, if you have a uniform size of 5M, then if you
have 100 MB free, you will be able to allocate 20 extents to use it all.

Not being able to shrink the file merely means that an extent is near the end of
the file.  That doesn't mean it's fragmented.

Hope that helps.
Dennis

          Followup:

well said.

      Rating:
      4
  July 28, 2003
Reviewer:  Kiro
       from Bulgaria
Thanks Tom and Denis!

July 25, 2003 bsva@mailcity.com -- Thanks for the question
      regarding "Calculating the Sizes", version 8.0.4

      You Asked
Hi Tom

Would like to know if there is any standard formula for calculating the size of
RBS, Tables and Log files.  If so can you please let me know the usage of that.

Is there any recommended size for RBS, Log Files for small, medium and large db.

Thanks
Bala

      and we said...
No, not really.

For RBS, we all have rules of thumb.  Its an empirical thing.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:233214228495
for the details.  I myself like to start with about 15 or so (depending on the
number of concurrent transactions) rollback segments -- each with
initial=next=1m and minextents=25, maxextents=100.  So I have 15 to 25 25meg
rollback segments that can grow to 100meg each.  I'll shrink them before backups
back down to 25meg.

For tables, then only real way to do this is to load some sample data. See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:302216525000
it also points to some doc that has formula's, but since everything is a varying
string pretty much -- they are usually pretty far off from reality.

Log files - you size and resize (not really resize but create new and drop old)
as part of tuning.  the size of your redo affects performance more then anything
else (eg: having too small redo will not prevent you from doing anything like
having too small rollback would).

I like to start with (depending on the system) 3 25meg log files.  This is for
"moderate" systems.  The goal (some people will say) is to have sufficient
online redo log to get you through your busy period without wrapping around for
about 15-20 minutes.  Their size depends on how frequently you want to
checkpoint and how large you want them.  I've used 25meg, 100meg and 255meg.
You can go larger but I've not done so.

Review &
      Followup

      Rating:
      4
  July 24, 2003
Reviewer:
      A reader
Hi Tom,

I have the following situation:
60Meg redo per day during 16 hours of busy activity in the database.

My idea is to set the online redo log like this:
4 groups, 2 members each, 2Meg for each member size.

This is the first time I have to set a very small database and this number
sounds too small to me.
I know that online redos have a great impact in the performance and I would like
to have your advice in this case.
Do you think 2Meg is too small even when (according with my calculations) it is
going to let switch the log files every 30 minutes if the log file is not full
before that time?
Or Should I set the size to  25M as you suggested in this posting even when I
will generate 60 Meg of redo in the entire day?

Thanks in advance for any suggestion.

          Followup:

60meg is tiny, your logic is sound.

July 25, 2003 Nag
      -- Thanks for the question regarding "How to achieve this functionality?",
      version 8.1.7

      You Asked
Tom

We have created a new table whose size is 500 MB.

Now we need to load data first, and we dont want to do any data integrity checks
when we are loading the data.

But, once we load the data, we want to enaable the constraints and avoid
duplicate rows.

How should I created the constraints,  deferrable or enalbe no validate, enable
validate. What are the other modes for a constraint.

Thx

      and we said...
Create the table.

Load the data.

Create the constraints with EXCEPTIONS INTO so that if any rows violated the
constraint, you can find them fast.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 2 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t add constraint t_pk primary
key(x)
  2  EXCEPTIONS INTO EXCEPTIONS;
alter table t add constraint t_pk primary key(x)
                             *
ERROR at line 1:
ORA-02437: cannot validate (OPS$TKYTE.T_PK) - primary key violated

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from exceptions;

ROW_ID             OWNER     TABLE_NAME CONSTRAINT
------------------ --------- ---------- ------------------------------
AAAGagAABAAAF/8AAA OPS$TKYTE T          T_PK
AAAGagAABAAAF/8AAB OPS$TKYTE T          T_PK

those are the rowids of the rows with the number 1 in them.
Review &
      Followup

      Rating: 5
Direct load in
      datawarehouse  July 16, 2003
Reviewer:  Anil
       from Malaysia
Dear Tom,
I am using oracle 9iR2 on Windows 2000 OS. We are loading data in datawarehouse
tables, some tables are of size 1 gb each per load. At present i am using
sql*loader conventional mode loading. In our load we have lot of unique key and
foreign key rejections.

I want to use sql*loader direct mode. I am thinking of a process as follows.
1. Load data using sql*loader direct mode.

2. Some primary key indexes will become unusable  due to primary key constraint
violations

3. After the load is over, i will enable primary key using 'exceptions into
exceptions table'

4.After i get the rowids in exception table, i will create a table 'duplicate'
to take rows from data table to duplicate table.
create table duplicate as select * from data_table where rowid in (select row_id
from exceptions);

5. I will delete the rows from data_table
delete from data_table where rowid in (select row_id from exceptions);

6. I will insert the unique rows from duplicate table into data_table
insert into data_table select distinct * from duplicate;

7. Now i will enable the primary key constraint

Is this the fastest way to load or can u give any better suggestion.

At present we are having unique indexes to enforce primary key , should i
convert them to non unique indexes, because of design i cannot partition tables.

I had spend lot of time in your website and as per the information available in
the website, i feel this should be the best way.

Your earliest reply will be very helpful as always.

          Followup:

are you loading into tables with existing data or tables with NO existing data
eg: is this a cumulative load or a truncate + load.

if data exists, how big is it

      Rating: 5
How to achieve
      this functionality  July 17, 2003
Reviewer:
      Anil  from Malaysia
Dear Tom,
I am truncating the table and then loading it. In few tables 4 million rows get
loaded (size 1 GB).

Regards,

          Followup:

if it is "fast enough", I would leave it be -- however since you seem to be
anticipating dups maybe:

truncate
direct load with skip index maintainance (don't even attempt to do the index)
enable primary key exceptions into
remove dups if need be and enable again.

      Rating: 5
How to achieve
      this functionality  July 18, 2003
Reviewer:
      Anil  from Malaysia
Thanks Tom, your fast feedback is extremely useful.

I am stuck up at point number 6 which is

insert into data_table select distinct * from duplicate;

We have to load 50 tables, every table has different primary keys. Some table
has 1 column as primary key, some table has 2 columns as primary key and so on.
I want to insert only those rows in data_table from duplicate where columns in
primary key has unique values while other columns may have duplicates.

I had created a procedure as below to automate index rebuilding and primary key
reenabling. Indexes are non unique

 create or replace procedure rebuild_index
 2  as
 3  ind_name_rbld varchar2(50);
 4  tab_name_rbld varchar2(50);
 5  cursor cur_get_ind_name_rbld is select index_name,table_name from
user_indexes where status='UNUSABLE' ;
 6  begin
 7  open cur_get_ind_name_rbld ;
 8  loop
 9  fetch cur_get_ind_name_rbld into ind_name_rbld,tab_name_rbld ;
10  execute immediate 'alter index '|| ind_name_rbld||'  rebuild nologging' ;
11  execute immediate 'alter table '||tab_name_rbld||'  disable primary key' ;
12  execute immediate 'alter table '||tab_name_rbld||'  enable primary key
exceptions into dwhdba.disabled_constraints' ;
13  execute immediate 'create table duplicate as select * from
'||tab_name_rbld||'  where rowid in (select row_id from disabled_constraints) ';
14  execute immediate 'delete from '||tab_name_rbld||'  where rowid in  (select
row_id from dwhdba.disabled_constraints  ) ' ;
15  execute immediate 'insert into '||tab_name_rbld||'  select distinct * from
duplicate' ;
16  execute immediate 'alter table '||tab_name_rbld||'    enable primary key' ;
17  execute immediate 'drop table duplicate';
18  exit when cur_get_ind_name_rbld%notfound;
19  end loop;
20  close cur_get_ind_name_rbld;
21  end;
22  /

I am stuck up at line number 15 in which i want to insert only those rows which
have unique values in columns of primary keys. I want to put a statement like
this

'insert into '||tab_name_rbld||'  select  * from duplicate where rowid = (select
min(rowid) from duplicate t2 where
duplicate.primary_key_column1=t2.primary_key_column1
and duplicate.primary_key_column2=t2.primary_key_column2
...
...
and dups.primary_key_columnn=t2.primary_key_columnn' ;

I am wondering how to put primary key column names in this statement
dynamically.

Your earliest feedback will be extremely helpful as always.

          Followup:

 you'll have to read them out of the data dictionary and build a string or pass
them in.

      Rating: 4
why not just
      SELECT qualifying rowids from EXCEPTION ?  July 24, 2003

Reviewer:  fan  from PA
I don't get it...
In stead of creating DUPLICATE, Why not create NO_DUPLICATE just modify the
sub-sql on Line 13 to select distinct rowids from the exception table based on
"group by TABLE_NAME, CONSTRAINT..."

I mean, doesn't the "EXCEPTION INTO" table give you the information you need to
SELECT just a single one of the constraint-violating rowids ?

Thanks

          Followup:

you have a table:

id       name
1        Tom
1        Bill

and you create a unique key on ID.  which of Tom or Bill should be choosen as
the "offending row"?  we have no idea.  YOU might, so we tell you about both
records.

No, we do not pick "one of" the offending rows -- we pick EVERY offending row
and then you sort it out.

Most constraints are not like "unique" any way -- a check constraint for
example, would have all rows being equally offensive.

July 25, 2003 Paul -- Thanks for the question regarding "RMAN
      and archivelogs", version 8.1.7

      You Asked
Hi,

I have read quite a bit on Oracles RMAN utility and know that for hot backups
RMAN doesn't use old method of placing tablespaces in Archive log mode freezing
datafile headers & writing changes to Redo/ Archive logs. Hence a company with a
large DB shouldn't experience the usual overhead of extra redo during a hot RMAN
backup. With this in mind why does Oracle need the database to operate in
archive log mode for hot backups via RMAN, what exactly are the logs being used
for?

Paul.

      and we said...
You have confused some terms.

RMAN doesn't place tablespaces into BACKUP MODE.  A database is in archivelog
mode (or not), not a tablespace.

We need a database to be in archive log mode for hot backups (regardless of
whether you use RMAN or the old fashioned way).  We need this because the copies
of the files we make during this time are "fuzzy" or inconsistent.  We need the
archived redo logs to make the consistent again.
Review &
      Followup

      Rating: 3
redologs &
      RMAN  June 28, 2002
Reviewer:  A reader

I have read that the extra redo involved with old style backups is no longer
present in RMAN, from your answer you are saying that redo information is
written to the logs while the datafile is backed up & would be needed to roll
forward in advent of a recovery. Is the redo log written during a RMAN backup
the same or a lot less or none at all?

          Followup:

When you put a tablespace into backup mode -- that will cause the first change
to a block to log the entire block, instead of just the changes to the block.

Redo is otherwise generated as normal.

During a RMAN hot backup, since it doesn't put the tablespace into backup mode,
redo is just generate "as normal".  The full block isn't logged upon the first
change while backing up.

      Rating: 5
channel and
      backupset  June 28, 2003
Reviewer:  Reader

If I allocated three channels for backup, say my database contains 9 datafiles,
how will the channels be used? My understanding is as follows with regard to
BACKUPSET. Please correct me if i am wrong. Thanks.

(1) A channel (process) will backup a datafile at a time. Three channels will
copy three datafiles independently at a time.
(2) If I have specified FILESPERSET as 3, then a channel will be allocated for a
backupset. In my case, one channel will copy three data files, one at a time,
until it creates a backupset with three datafiles. At the samet time, second and
third channels will be doing the same with other backupsets.
(3) Is allocating multiple channels same as parallel query/DML in the sense that
it will be useful only with multiple cpu machine.
(4) What is the limit of number of channels I can allocate? I understand that it
depends on number of data files and also does it depend on number og CPUs?
(5) Is the concept of allocating channels the same with regard to taking image
copies and also archived redolog backups?

Thanks as always. I learn so much from you.

          Followup:

1) a channel will backup some piece of data in a serial fashion, yes.  three
channels may copy three streams of database data.
2) well, you allocated the channels yourself? but yes.
3) similar, same concept but since IO and rman processing is generally not cpu
intensive, it works well on single cpu machines -- given you have the BANDWIDTH
to support it (IO)
4) it has practical limits -- based on the number of inputs and output devices
and how much you have to backup, yes.  cpu is of less relevance here.
5) you might want to read:

http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96566/rcmconc1.htm#462102
for background info.

      Rating: 5
Persistent
      Config  June 29, 2003
Reviewer:  Reader
       from Chicago
(1) If I do not use a catalog, where my persistent configurations that I set
using CONFIGURE command are stored. If they are recorded in the target database
controlfile, will control_file_record_keeping_time decide how long these
configurations remain persistent? In other words, will the configuration details
get overwritten based on control_file_record_keeping_time parameter?
(2) If I use a catalog, will RMAN use the catalog by default to find info
regarding what to restore and what to recover? Is it like, if catalog does not
contain enough info for RMAN, will RMAN get the info from the controlfile? or if
I used catalog, RMAN will only use my catalog not the controlfile during its
operation?

THANKS so much for your time. You are the best.

          Followup:

1) see

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96566/rcmquick.htm#442236
for a caveat about that -- if you lose the control files... they are gone
without a recovery catalog.

these records are not "reusable" in the controlfile, hence they are not
controlled or affected by the record keep time.

2) you connect rman to something, be it a recovery catalog or not.  that is what
tells it where to look.

      Rating:
      5
Thanks  June 29, 2003
Reviewer:
      A reader

      Rating: 5
Very
      Usefull  June 30, 2003
Reviewer:  Prakash
      Eranki  from Singapore
Tom
What is your valuable suggestion for my question/doubt

We have a backoffice system with Oracle database.
It has currently two schemas Main and User_data. We are planning upgrade of
Backoffice System. The new system has got two oracle instances with
version3(similar to existing version) and version4( new version going to
migrate). The old version has the schemas named v3Main and v3User_data.
We have 3 sets of Main and User_data schemas in Version3 instance. We need to
restore from production at 3 different dates to these 3 different sets of
schemas . What is the best way of doing so. Please note that the new instance
got the instance name different and new hardware(obviously higer configuration)
and same oracle dbversion and different schema names. So what is the best
strategy that will work in this situation . Please guide me .

Thanks & Regards

          Followup:

I don't know what you mean by

We need to restore from production at 3 different dates

exactly, are you saying "at 3 different points in time"

are you in archivelog mode
are your backups REAL backups (eg: image backups) not exports.

      Rating:
      5
TAG  July 09, 2003
Reviewer:  Reader

Tom, when I did the backup of datafile using rman copy command, I have specified
the tag. However, when I used LIST COPY command, I could not see the tag that I
specified. Could you please help me which command I should use to view the tags
assoiciated with copy command. THANKS.

          Followup:

list backup;

      Rating: 5
list backup
      does not show!  July 10, 2003
Reviewer:
      Reader
Tom, please see below. It shows tag for backupset but not for image copy. Thanks
for your time.

user ksh > rman

Recovery Manager: Release 9.2.0.1.0 - 64bit Production

Copyright (c) 1995, 2002, Oracle Corporation.  All rights reserved.

RMAN> connect target;

connected to target database: U439 (DBID=411229649)

RMAN> backup tablespace users
2> format '$HOME/BACKUP/RMAN/df_%d_%s_%p.bus' tag='users_backupset';

Starting backup at 09-JUL-03
using target database controlfile instead of recovery catalog
allocated channel: ORA_DISK_1
channel ORA_DISK_1: sid=12 devtype=DISK
channel ORA_DISK_1: starting full datafile backupset
channel ORA_DISK_1: specifying datafile(s) in backupset
input datafile fno=00003 name=/home3/user439/ORADATA/u03/users01.dbf
channel ORA_DISK_1: starting piece 1 at 09-JUL-03
channel ORA_DISK_1: finished piece 1 at 09-JUL-03
piece handle=/home3/user439/BACKUP/RMAN/df_U439_3_1.bus comment=NONE
channel ORA_DISK_1: backup set complete, elapsed time: 00:00:01
Finished backup at 09-JUL-03

RMAN> list backupset;

List of Backup Sets
===================

BS Key  Type LV Size       Device Type Elapsed Time Completion Time
------- ---- -- ---------- ----------- ------------ ---------------
1       Full    64K        DISK        00:00:00     09-JUL-03
        BP Key: 1   Status: AVAILABLE   Tag: USERS_BACKUPSET
        Piece Name: /home3/user439/BACKUP/RMAN/df_U439_3_1.bus
  List of Datafiles in backup set 1
  File LV Type Ckp SCN    Ckp Time  Name
  ---- -- ---- ---------- --------- ----
  3       Full 100022     09-JUL-03 /home3/user439/ORADATA/u03/users01.dbf

RMAN> copy datafile '$HOME/ORADATA/u03/users01.dbf'
2> to '$HOME/BACKUP/RMAN/users01.cpy' tag='users_copy';

Starting copy at 09-JUL-03
using channel ORA_DISK_1
channel ORA_DISK_1: copied datafile 3
output filename=/home3/user439/BACKUP/RMAN/users01.cpy recid=1 stamp=498929601
Finished copy at 09-JUL-03

RMAN> list copy;

specification does not match any archive log in the recovery catalog

List of Datafile Copies
Key     File S Completion Time Ckp SCN    Ckp Time        Name
------- ---- - --------------- ---------- --------------- ----
1       3    A 09-JUL-03       100022     09-JUL-03       /home3/user439/BACKUPy

RMAN> list backup;

using target database controlfile instead of recovery catalog

List of Backup Sets
===================

BS Key  Type LV Size       Device Type Elapsed Time Completion Time
------- ---- -- ---------- ----------- ------------ ---------------
1       Full    64K        DISK        00:00:00     09-JUL-03
        BP Key: 1   Status: AVAILABLE   Tag: USERS_BACKUPSET
        Piece Name: /home3/user439/BACKUP/RMAN/df_U439_3_1.bus
  List of Datafiles in backup set 1
  File LV Type Ckp SCN    Ckp Time  Name
  ---- -- ---- ---------- --------- ----
  3       Full 100022     09-JUL-03 /home3/user439/ORADATA/u03/users01.dbf

RMAN> list copy;

specification does not match any archive log in the recovery catalog

List of Datafile Copies
Key     File S Completion Time Ckp SCN    Ckp Time        Name
------- ---- - --------------- ---------- --------------- ----
1       3    A 09-JUL-03       100022     09-JUL-03       /home3/user439/BACKUPy

RMAN>

          Followup:

don't know, don't do image copies.  Look at the rman catalog views, it is
probably in there somewhere.

      Rating: 5
in NOCATALOG
      mode, how do i get the info.  July 10, 2003

Reviewer:  Reader
RC_DATAFILE_COPY has a column for tag. However, if i don't use the catalog, how
to get the tag value for image copies? Thanks.

          Followup:

look right above at my last response "don't know, don't do image copies myself"

not much benefit in rman without the recovery catalog doing image copies.

      Rating: 5
When should I
      use image copies  July 12, 2003
Reviewer:
      Reader
Tom, in what situations creating image copies more useful than backupsets?
should my backup strategy include image copies? Thanks.

          Followup:

they are of limited use.  You might use them if you were a really big system
using something like split mirrors to backup with.

      Rating: 5
split mirrors
      and RMAN  July 12, 2003
Reviewer:  Reader

In splitting mirror and then use the mirror for backup for example in EMC, where
does RMAN come into picture? Is it like, after I made a backup using OS utility
from the split mirror, I use RMAN to register the copy with RMAN repository?
Could you please elobarate on this? Thanks.

          Followup:

yes, that is where it could come in.  it is of somewhat limited use in that
configuration.

      Rating: 4
Point intime
      recovery untile time  July 24, 2003
Reviewer:
      Reddy  from Bangalore, India
Hi Tom

I am practicing various scenarios for backup and recovery using RMAN. I want
recover database using untile time.
I gave following command
RMAN>run
 { allocate channel c1 type disk;
   set until time ='2003-07-24:11:00:00';
   restore database;
   recover database;
   sql "alter database open resetlogs"; }
but this is not able recover and giving date error.
I have altered my session in target database for matching above date format. Can
you tell me how to recover database using UNTILE time clause. I have gone
through oracle mannual all in vain.

Thanks

Reddy

          Followup:

and the error you are getting is?
and your NLS_DATE_FORMAT setting is?

      Rating: 4
SCN is more
      reliable  July 24, 2003
Reviewer:  Dana Day
       from Tempe, AZ USA
I find until SCN better than until time, with log_seq in the middle. Until time
is still going to use (ultimately) an SCN to recover to.

          Followup:

scn is more "precise" then time, yes.  more reliable?  not really.

if you know the scn, it would be preferred.  if not, time is fine.

      Rating: 5
Reddy...NLS
      date  July 25, 2003
Reviewer:  Brent

Reddy,

   One thing you can try:

     set until time "to_date('Oct 16 2000 15:30:00','Mon DD YYYY HH24:MI:SS')";

  This is from metalink, note #: 164521.1

July 25, 2003 Jonathan -- Thanks for the question regarding
      "logminer privileges problem again...", version 8.1.5

      You Asked
I've created a user and I 've granted him with
execute_catalog_role

but then when I executed the dbmslogmnr.sql script I'v got this
error(translation from french so it may be a bit different):

Package created.

Warning : Package Body created with compilation errors.

Errors for PACKAGE BODY DBMS_LOGMNR_D :

LINE/COL ERROR
--------
-----------------------------------------------------------------

164/7    PL/SQL: SQL Statement ignored
166/15   PLS-00201:  'SYS.V$COMPATIBILITY' identificator must be
declared.

169/7    PL/SQL: SQL Statement ignored
175/16   PLS-00201: 'SYS.V$DATABASE' identificator must be
declared

Privileges granted.

I granted privilege to the user on V_$COMPATIBILITY and
V_$DATABASE but it didn't work.

What should I do?

      and we said...
drop that package from that user schema.

DBMS_LOGMNR is to be installed by the user SYS only.  There is
to be one copy per database, not a copy per user.

It should already be installed in the SYS schema.
Review &
      Followup

      Rating: 5
dbms_Logmnr_d
      Error  July 23, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
Hi TOM,
In Oracle 9.2 I am trying to use the logminer but getting the following error,

SQL> execute
dbms_logmnr_d.build('dict.ora','d:\ora9i',options=>dbms_logmnr.store_in_flat_file
);
BEGIN
dbms_logmnr_d.build('dict.ora','d:\ora9i',options=>dbms_logmnr.store_in_flat_file
); END;

                                                         *
ERROR at line 1:
ORA-06550: line 1, column 58:
PLS-00201: identifier 'DBMS_LOGMNR.STORE_IN_FLAT_FILE' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

There was a bug in Oracle 8i but it is fixed in 9i so it seems some other issue.
I have also set utl_file_dir.

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96521/logminer.htm#18448

          Followup:

you missed an _D

SQL> EXECUTE DBMS_LOGMNR_D.BUILD('dictionary.ora', -
  2 '/oracle/database/', -
  3 OPTIONS => DBMS_LOGMNR_D.STORE_IN_FLAT_FILE);

      Rating: 5
Still same
      error  July 24, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
SQL> EXECUTE DBMS_LOGMNR_D.BUILD('dictionary.ora', 'd:\ora9i', OPTIONS =>
DBMS_LOGMNR_D.STORE_IN_FLAT_FILE);
BEGIN DBMS_LOGMNR_D.BUILD('dictionary.ora', 'd:\ora9i', OPTIONS =>
DBMS_LOGMNR_D.STORE_IN_FLAT_FILE); END;

                                                                   *
ERROR at line 1:
ORA-06550: line 1, column 68:
PLS-00201: identifier 'DBMS_LOGMNR_D.STORE_IN_FLAT_FILE' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

SQL> desc dbms_logmnr_d
ERROR:
ORA-04043: object dbms_logmnr_d does not exist

It seems that Oracle 9i has not create this package as default so I have to
create it manual as in 8i?

          Followup:

if it doesn't exist, it doesn't exist, install it.

      Rating: 5
My
      Mistake  July 28, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
I granted dbs to scott and was using scott to create the dictionary but the
package is in the SYS schema.

In the scott schema I have to give sys.dbms_logmnr_d.build instead of
dbms_logmnr_d.build

execute sys.dbms_logmnr_d.build(options=>sys.dbms_logmnr_d.store_in_redo_logs);

This is just to share my issue with others.

July 25, 2003 Doug -- Thanks for the question regarding "Space
      difference between dba_segments, dba_tables, show_space", version 8.1.7

      You Asked
Question for Tom -
Tom - I don't understand this huge space report
dicrepancy.

I have a tablespace that is a size of
5.5 GB

SQL> select sum(bytes)/1024/1024 from dba_data_files where tablespace_name
  2  = 'PDATA';

SUM(BYTES)/1024/1024
------------------
              5500

It is mostly full as seen by this query:
SQL> select sum(bytes)/1024/1024 from dba_free_space
  2  where tablespace_name = 'PDATA';

SUM(BYTES)/1024/1024
--------------------
             449.375

for a total of 5 GB.  Nothing has ever been
dropped or deleted (afaik - maybe I'm wrong).. i.e, I don't think this is
reflective of any high water mark problems.

it contains 80 primary key indexes..
but mostly tables..

INDEX's account for the remaining space..
however...

1* select sum(num_rows*avg_row_len)/1024/1024 from dba_tables where tablespace
_name = 'PDATA' order by table_name
SQL> /

SUM(NUM_ROWS*AVG_ROW_LEN)/1024/1024
-----------------------------------
                         2822.27288

  1  select sum(bytes)/1024/1024 from dba_segments where tablespace_name =
'PDATA'
  2* and segment_type = 'TABLE'
SQL> /

SUM(BYTES)/1024/1024
--------------------
            3962.875

PCT free is basically 10
SQL> select pct_free, count(1) from dba_tables
  2  where tablespace_name = 'PRDDATA'
  3  group by pct_free;

  PCT_FREE   COUNT(1)
---------- ----------
        10         95
        15          2

but let's assume 15
that means that (question)..  (3962-2822)/3962 should = .15?  it works out to
.28.. almost double what I would figure even factoring block headers and so
forth.

What could explain this discrepancy of space other than pctfree reserved space ?

I took show_space from your sight and modified it slightly so I could use awk to
sum of the results for the entire tablespace.

Using show_space on all the tables in the tablespace and summing up the
total_blocks,unused_blocks, & free list blocks.
for all of them I get
 507488 total blocks
and 878 unused blocks (HWM blocks)..
1,321 free list blocks..  so (507488-878-1,321)*8K = 4,139,327,488 or more than
dba_segments OR dba_tables..

Why do these numbers differ so drastically?
show_space = 4.1GB
dba_segments = 3.9GB
dba_tables = 2.8GB

Have I done something wrong?

      and we said...
I doubt you've done anything wrong -- it is just that there may be lots more
then 10% free (15% free).

Consider the following example:

SQL> create table t ( x varchar2(4000) ) pctfree 10;

Table created.

SQL>
SQL> REM declare
SQL> REM        l_cnt number;
SQL> REM begin
SQL> REM        for i in 3000 .. 4000
SQL> REM        loop
SQL> REM             execute immediate 'truncate table t';
SQL> REM             insert into t values ( rpad('*',4000,'*') );
SQL> REM             insert into t values ( rpad('*',i,'*') );
SQL> REM             commit;
SQL> REM             select count(distinct dbms_rowid.ROWID_BLOCK_NUMBER(rowid))
into l_cnt
SQL> REM               from t;
SQL> REM             if ( l_cnt = 2 )
SQL> REM             then
SQL> REM                     dbms_output.put_line( 'i = ' || i || ' put us over
the edge' );
SQL> REM                     exit;
SQL> REM             end if;
SQL> REM        end loop;
SQL> REM end;
SQL> REM /
SQL> REM
SQL> REM i = 3277 put us over the edge
SQL> REM
SQL> REM
SQL>
SQL> truncate table t;

Table truncated.

SQL> insert into t values ( rpad( '*', 4000, '*' ) );

1 row created.

SQL> insert into t values ( rpad( '*', 3276, '*' ) );

1 row created.

SQL> select dbms_rowid.ROWID_BLOCK_NUMBER(rowid) from t;

DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)
------------------------------------
                                  74
                                  74

SQL>
SQL>
SQL> truncate table t;

Table truncated.

SQL> insert into t values ( rpad( '*', 4000, '*' ) );

1 row created.

SQL> insert into t values ( rpad( '*', 3277, '*' ) );

1 row created.

SQL> select dbms_rowid.ROWID_BLOCK_NUMBER(rowid) from t;

DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)
------------------------------------
                                  74
                                  75

SQL>

Now, these two cases differ by ONE BYTE.  Yet, the first case virtually fills
block one -- the second case leaves block one virtually empty.

A row will be inserted into a row on the freelist IF the insertion of that row
will not cause the block to be filled over 10% free.  That is part of the
reasoning behind the 2.8 vs 3.8 gig differential (the other is that
avg_row_len*number of rows gives you raw input data lenght, no overhead for the
null/not null flag, length field on numbers, and other "because it is a data
structure" overhead).

Now, I'm not sure where you are going with this math:

(507488-878-1,321)*8K = 4,139,327,488

Although it is 3.85504913 gig.... (why sub out the freelist blocks?)

When looking at show space, if you want to get the same answer as dba_segments
-- you look at total blocks.  And when I look at total blocks:

ops$tkyte@ORA920.US.ORACLE.COM> select (507488*8)/1024/1024 from dual;

(507488*8)/1024/1024
--------------------
          3.87182617

I get 3.8 gig.  If you use GIG in your dba_segments query (remember, divide by
1024, not 1000!!!) you get 3.8 gig.  They are the same.

Review &
      Followup

      Rating: 5
Clears that up
      / reorgs  September 14, 2002
Reviewer:
      A reader
Ah..a math mistake.. so really I got 2 different answers, not 3. Is this kind of
extra space typical? 28%?  (seems ok). Also, some follow ups: What if I went
through the trouble of using avg(vsize( on every column, wouldn't I end up with
something closer to the lower number?  I.e.  - a lot of the extra space is just
the inability to put rows packed into a block so any raw data figure would be
wrong, right? Ok.. not "wrong".. but not reflective of the actual space used by
various tables on disk, right?

Also, I have no way of knowing how "empty" a block really is, right? (I think
you said this on your site somewhere citing that it would use an awful lot of
resources to find this out and give us something not that interesting)..  so
therefore, I cannot know how much space I would save by exp/imp reorg until it
is done - In fact, depending on the nature of the data, it might not save
anything at all. In your rpad example, a exp/imp would end up with the same
situation exactly.  Correct?

I'm planning to delete a lot of data (not just from this tablespace) and am
trying to find out how much I will save.  From your answer, it looks like I
would take the raw data number and * 1.28 to guess how many blocks would be
freed by a big delete.  That would assume that the "extra" space is sort of
uniformly distributed throughout the table.  I recognize that just doing an
exp/imp without a big delete would be futile since I will probably need the
space again in a week anyhow, but, I am curious to your answer about estimating
it anyhow.

Thanks again.
- D

          Followup:

No, you got one answer.

It took 3.8 of database blocks to hold your 2.75gig of raw input data.  Part of
that is block overhead, the other part is your pctfree and you may well have a
"dengerative case" whereby you are getting one less row per block then you would
like (like my example with 3276 vs 3277 bytes) -- leaving extra space on the
block (more then 10%)

Using avg(vsize) won't really get you any close.

An imp/exp MIGHT end up with the same result, might not.  If you insert a 4000
byte row and then a 3277 byte row and then a 4000 byte and then a 3277 byte --
that might be 4 blocks.

Now, insert 3277, 3277, 4000, 4000 -- well, that might be 3.  It depends on how
the data falls out.

If this is relatively static data, just set pctfree to 0% and pack the blocks
up.

Use ALTER TABLE MOVE, rather then exp/imp to "compact" the data.  Faster, less
chance of human error (or software error for that matter), easier.

As for sizing, I always just load a representative portion of the data, measure
it and multiply.  This examples shows clearly why all formulas for sizing are
inheritly FLAWED.  That unknown "how the shape of the data will affect this and
the order it comes in" always breaks them.

      Rating: 5
Good
      suggestion  September 15, 2002
Reviewer:  Doug
      C  from CT, USA
Hey Tom - thanks for the suggestion of using "move"..
Thanks again..Are these "degenerative" cases common?
- D

          Followup:

Degnerative cases are never "common", they are generally the exception.

But, if your dataset is one of those bad ones, you would certainly say the
opposite!

      Rating: 5
Estimating
      wrong...  July 22, 2003
Reviewer:  Marcio
       from Brazil
Tom, In this case I'm trying find out why so lost bytes on...

Look table t has a field y with varchar2(4000) and when I put 6 rows is enough
to fill up extent. I don't know how to consider rest to estimate size (near) of
table.

I analyzed table and shows me avg_row_len of 4006 and my count was:

  NUM_ROWS  AVG_SPACE AVG_ROW_LEN     BLOCKS EMPTY_BLOCKS PCT_FREE
---------- ---------- ----------- ---------- ------------ ----------
         6       4643        4006          7            0        10

avg_row_len * num_rows = bytes_occupied

    4006*6
----------
     24036

I'm messing up this... I'm not using avg_space on compute nor overhead.  I don't
know how does it compute. Could you give some tips... I would like to estimate
size of table.

Here is my script.

set echo on

drop table t
/

create table t (y varchar2(4000) )
/
declare
        type t_array is table of t%rowtype index by binary_integer;
        tb      t_array;
begin
        for i in 1..6
        loop
                tb(i).y := rpad('*',4000,'*');
        end loop;
        forall i in 1 .. tb.count
                insert into t values tb(i);
end;
/

analyze table t compute statistics
   for table
   for all indexed columns
   for all indexes
/

exec show_space('T')
/

select NUM_ROWS, avg_space, avg_row_len, blocks, empty_blocks, pct_increase,
pct_free
  from user_tables
 where table_name = 'T'
/
select blocks, bytes from user_segments where segment_name = 'T';

select extent_id, bytes, blocks
  from user_extents
 where segment_name = 'T';

set echo off

          Followup:

huh?

      Rating:
      3
Explain  July 23, 2003
Reviewer:
      Marcio  from Brazil
sorry -- too many consistent gets and hard parse yesterday night ;)

If you kindly put a short example how to compute size to:

create table t ( x varchar2(4000) )
  storage (initial 64k next 64k) ;

And fill up x with -- rpad('*',4000,'*') to estimate.

1. how many rows fit in block?
2. afaik - 1 extent (64k) have 8 blocks (8k) -- since i have created that, so
how many extents will be necessary to 1000 rows?
3. Is correct say if 1000 rows need 4 extents that my estimate size must be 4 *
64k? I see that: I can't consider just bytes used for table to estimate size. Am
I correct?
4. How could be used dbms_stat to estimate size on table?

Thanks in advanced
Marcio

          Followup:

you would just create the table, put some data in it and then you can analyze
it.

you'll find user_table has:

blocks -- how many blocks are used
num_rows -- how many rows you have

then divide.

      Rating: 4
Like
      this?  July 23, 2003
Reviewer:  Marcio
       from Brazil
Something like this?

ops$t_mp00@MRP9I1> create table t (x int primary key, y varchar2(40) )
  2  /

Table created.

Put some data there about 10%

ops$t_mp00@MRP9I1> declare
  2          type t_array is table of t%rowtype index by binary_integer;
  3          tb      t_array;
  4  begin
  5          for i in 1..2000
  6          loop
  7                  tb(i).x := i;
  8                  tb(i).y := rpad('*',40,'*');
  9          end loop;
 10          forall i in 1 .. tb.count
 11                  insert into t values tb(i);
 12  end;
 13  /

PL/SQL procedure successfully completed.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> analyze table t compute statistics
  2     for table
  3     for all indexed columns
  4     for all indexes
  5  /

Table analyzed.

So, I would like estimate size to 20k rows.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> select num_rows, blocks, empty_blocks, row_block, row_block *
20000 bl_est_20k,
  2         row_block * 20000 * 8192 by_est_20k
  3    from ( select num_rows, blocks, empty_blocks, blocks / num_rows row_block
  4             from user_tables
  5            where table_name = 'T')
  6  /

  NUM_ROWS     BLOCKS EMPTY_BLOCKS  ROW_BLOCK  BL_EST_20K  BY_EST_20K
---------- ---------- ------------ ---------- ---------- ----------
      2000         15            0      ,0075        150    1228800

1 row selected.

I would expect 150 blocks and approx 1.3 meg, but...

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> drop table t
  2  /

Table dropped.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> create table t (x int primary key, y varchar2(40) )
  2  /

Table created.

ops$t_mp00@MRP9I1> declare
  2          type t_array is table of t%rowtype index by binary_integer;
  3          tb      t_array;
  4  begin
  5          for i in 1..20000
  6          loop
  7                  tb(i).x := i;
  8                  tb(i).y := rpad('*',40,'*');
  9          end loop;
 10          forall i in 1 .. tb.count
 11                  insert into t values tb(i);
 12  end;
 13  /

PL/SQL procedure successfully completed.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> analyze table t compute statistics
  2     for table
  3     for all indexed columns
  4     for all indexes
  5  /

Table analyzed.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> select num_rows, blocks, empty_blocks, blocks / num_rows
row_block
  2     from user_tables
  3    where table_name = 'T'
  4  /

  NUM_ROWS     BLOCKS EMPTY_BLOCKS  ROW_BLOCK
---------- ---------- ------------ ----------
     20000        142          113      ,0071

1 row selected.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> exec show_space('T')
Free Blocks.............................3
Total Blocks............................256
Total Bytes.............................2097152
Unused Blocks...........................113
Unused Bytes............................925696
Last Used Ext FileId....................11
Last Used Ext BlockId...................5257
Last Used Block.........................15

PL/SQL procedure successfully completed.

The table become in 256 blocks and about 2meg.

Is this compute?

Thanks,
Marcio

          Followup:

is that REPRESENTATIVE data - do you always fill up your varchar2???

you need to load REPRESENTATIVE data -- not just maxed out data.

      Rating: 2
Yet....
      Overdose   July 24, 2003
Reviewer:  Marcio
       from Brazil
Sorry Tom, I couldn't understant that yet. When you said REPRESENTATIVE data, I
think about all_objects -- does all_objects representative doesn't? I used
number, varchar2 and date on table created. So there are some doubt yet.

ops$t_mp00@MRP9I1> create table t as select object_id, object_name, object_type,
  2                           created
  3                      from all_objects
  4                     where rownum <= 6000
  5  /

Table created.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> analyze table t compute statistics
  2     for table
  3     for all indexed columns
  4     for all indexes
  5  /

Table analyzed.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> select num_rows, blocks, empty_blocks, row_block, row_block *
30000 bl_est_30k,
  2         row_block * 30000 * 8192 by_est_30k
  3    from ( select num_rows, blocks, empty_blocks, blocks / num_rows row_block
  4             from user_tables
  5            where table_name = 'T')
  6  /

  NUM_ROWS     BLOCKS EMPTY_BLOCKS  ROW_BLOCK BL_EST_30K BY_EST_30K
---------- ---------- ------------ ---------- ---------- ----------
      6000         47            0 ,007833333        235    1925120

1 row selected.

Consider this example above, is correct say
1) I'll need 1880k to 30000 rows on this table?
2) I'll need 235 blocks to 30000 rows?

But when I fill it up, I get some % up from this... I would like know if is
correct form to estimate size of table/schema/etc?

ops$t_mp00@MRP9I1> drop table t
  2  /

Table dropped.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> create table t as select object_id, object_name, object_type,
  2                           created
  3                      from all_objects
  4  /

Table created.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> analyze table t compute statistics
  2     for table
  3     for all indexed columns
  4     for all indexes
  5  /

Table analyzed.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> select num_rows, blocks, empty_blocks, blocks / num_rows
row_block
  2     from user_tables
  3    where table_name = 'T'
  4  /

  NUM_ROWS     BLOCKS EMPTY_BLOCKS  ROW_BLOCK
---------- ---------- ------------ ----------
     30560        213           42 ,006969895

1 row selected.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> exec show_space('T')
Free Blocks.............................0
Total Blocks............................256
Total Bytes.............................2097152
Unused Blocks...........................42
Unused Bytes............................344064
Last Used Ext FileId....................11
Last Used Ext BlockId...................5257
Last Used Block.........................86

=====================================================
See it show me 2meg and about 256 blocks

And last question if i am not run away from topic. Why does lmt, since 15th
extent, allocates 1meg extents like this?

ops$t_mp00@MRP9I1> select extent_id, bytes, blocks
  2    from user_extents
  3   where segment_name = 'T'
  4  /

 EXTENT_ID      BYTES     BLOCKS
---------- ---------- ----------
         0      65536          8
         1      65536          8
         2      65536          8
         3      65536          8
         4      65536          8
         5      65536          8
         6      65536          8
         7      65536          8
         8      65536          8
         9      65536          8
        10      65536          8
        11      65536          8
        12      65536          8
        13      65536          8
        14      65536          8
        15      65536          8
        16    1048576        128 <<-----

          Followup:

I'm telling you

IF you want to size YOUR tables

YOU will take some % of YOUR data, load it, multiply it out.

I'm not really sure what you are trying to do here.

      Rating: 4
Trying get
      approx size of table  July 25, 2003
Reviewer:
      Marcio  from Brazil
I am trying to simulate sizing. Take table, who cares -- 2k/row some thing like
that. I'm gonna pick up an of your book examples ( x int, y char(2000), z date)
about 3+2000+7 bytes so +/- 2k. I would like be able to say -- hey if you put
1000 rows into there, it'll take 2meg. Just it. Wich methodology do I must use?
Take some % of my data, load it, multiply it out --> blocks/row_num *
<rows_estimated> ?

Thanks
Marcio

          Followup:

that is "max", varchar2(2000) -- what is the AVERAGE width - 20 bytes? 200
bytes? 2000 bytes?

load it

analyze it

multiply it

July
      25, 2003 -- Thanks for the question
      regarding "Extents Question", version 9r1

      You Asked
Using the following create table script:

CREATE TABLE SKU_DETAIL
(
   )
TABLESPACE COMPANYA3
LOGGING
PCTFREE 10
PCTUSED 40
INITRANS 1
MAXTRANS 255
STORAGE( INITIAL 300M  NEXT 40M PCTINCREASE 0 FREELISTS 1
        FREELIST GROUPS 1
        BUFFER_POOL DEFAULT)
NOPARALLEL
NOCACHE
/

Produces the following:

SELECT   tablespace_name,  extents,
FROM     sys.dba_segments
WHERE    extents >10
/

table name           extents
SKU_DETAIL                 81

SQL> select segment_name, extents, initial_extent from user_segments where
segment_name = 'SKU_DETAIL';

SEGMENT_NAME
    EXTENTS INITIAL_EXTENT
---------------------------------------------------------------------------------
 ---------- -------
SKU_DETAIL
  81      314572800

How come when setting initial extent to 300MB it produce 81 Extents in the
user_segments & sys.dba_segments table?

Thanks!!

      and we said...
Because you are using locally managed tablespaces and you want to teach yourself
to never use

o initial
o next
o minextents
o maxextents
o pctincrease

ever again.  Search this site for "locally managed"   or LMT for many articles
on this.  locally managed tablespaces are *great*.

What happened here was you asked for 300m of storage and you go it.  It took 81
extents -- which is ABSOLUTELY perfect, OK, not a problem, nothing to worry
about, nothing to be upset about, perfectly OK.

In a perfect world initial would ALWAYS equal NEXT and pctincrease would be
outlawed.

So, just stop using all of those storage parameters!!!!

Review &
      Followup

      Rating:
      5
Great!!!  October 05, 2002
Reviewer:
      Jeremy  from Chicago
Thanks Tom. Less to worry about.  It should be easy to teach myself to type
less.

      Rating: 5
Philosophical
      contradiction ?  October 07, 2002
Reviewer:
      Connor McDonald  from UK
Splattered all over most Oracle forums/sites nowadays is:
"Number of extents (in LMT) makes no difference" (something I subscribe to,
ignoring the negligible impact of 'n' extent map blocks)

If this is the case, I wonder why auto-allocate LMT's exist at all?  It would
seem that their only benefit is to reduce the number of extents in use (at the
cost of fragmentation).

It would appear that only 2 lmt types are needed:
- uniform
- 'user' (ie the ability to convert from dict to lmt).
and that auto-alloc is a backward step.

Thoughts?

Cheers
Connor

          Followup:

The reasoning behind auto-allocate LMT's is one of space.

I would not want to have 10's of thousands of extents in my tables in general.
Reasoning is one of performance of many data dictionary queries we run from day
to day, as well as it does affect performance in the extreme cases (10's of
thousands, as opposed to hundreds or thousands of extents).

So, what do you do when you have an application you want to sell to people and
you want to size the tables to have hundreds of extents in the extreme BUT you
don't know what tables they will actually use?  Enter auto-allocation LMT's.
Here, each table starts with a modest 64k extent that balloons up rather quickly
-- satisfying all but the largest of tables which should be partitioned at that
point anyway (meaning, it probably works for even the largest of tables as well
as each partition would be hundreds of extents).

Take the Oracle SYSTEM tablespace for example.  It is a good case of this
generic application where the designers of the system have NO IDEA how big some
objects will get!  I use lots of plsql, lots and lots of it.  My source$ and idl
tables are huge.  In my 8i system, counting extents and taking the two tables
with the most extents:

SYS                            SOURCE$                              1360
SYS                            IDL_UB1$                             1829

There they are.  Now, we don't know how big they will get when you install
Oracle -- you might use almost no plsql and they'll be tiny.  You might use a
ton -- so they'll be big.  We just don't know.

Same data, (did exp/imp trick to migrate) but using 9iR2 with a auto-allocate
extent mgmt on system we find:

SYS                            IDL_UB1$                               90
SYS                            SOURCE$                                91

  1  select segment_name, blocks, count(*)
  2    from dba_extents where segment_name in ( 'IDL_UB1$', 'SOURCE$' )
  3*  group by segment_name, blocks
ops$tkyte@ORA9I.WORLD> /

SEGMENT_NAME                       BLOCKS   COUNT(*)
------------------------------ ---------- ----------
SOURCE$                                 8         16
SOURCE$                               128         63
SOURCE$                              1024         12
IDL_UB1$                                8         16
IDL_UB1$                              128         63
IDL_UB1$                             1024         11

6 rows selected.

And you can see how they grew by 64k to 1m to 8m extents.  The more they grew,
the bigger they grew, the faster the grew.  If we didn't use them -- they would
stay small and consume little space.  If we use them -- they grow fast BUT in a
similar fashion to all other objects (removing the potential for fragmentation
and "holes" that cannot be reused)

Me, I use UNIFORM when I know the exact size an object will ultimately be over
time (and I use a small, med, large tablespace sizing).  If I don't, I'll punt
and use auto-allocate.

      Rating: 5
Tablespaces
      uniform X autoallocate  July 25, 2003
Reviewer:
      Marcio  from Brazil
Tom, consider this tables:
- a --> 1gig
- b --> 100meg
- c --> 1meg
- d --> you have no idea

I'd have create 4 tablespaces like

tbs_big uniform extent of 8m    --> table a
tbs_med uniform extent of 1m    --> table b
tbs_tiny uniform extent of 64k  --> table c
tbs_auto autoallocate           --> table d

Is this correct form to allocate my tables?

thanks,
Marcio

          Followup:

I might go with "system allocated extents" for all.  one tablespace, striped at
the OS level.

July 26, 2003 gs
      -- Thanks for the question regarding "V$SQL_PLAN_STATISTICS", version
      9.2.0.3

      You Asked
Dear Tom,

Does V$SQL_PLAN_STATISTICS shows only the statics about the current (connected)
session?

SQL> select count(*) from v$sql_plan ;
--I can get the plan details here

  COUNT(*)
----------
     18540

SQL> select count(*) from V$SQL_PLAN_STATISTICS ;

  COUNT(*)
----------
         0

      and we said...
no, it is across sessions:

ops$tkyte@ORA920> select * from v$sql_plan_statistics;

no rows selected

ops$tkyte@ORA920> select count(*) from all_objects;

  COUNT(*)
----------
     30254

ops$tkyte@ORA920> select * from v$sql_plan_statistics;

no rows selected

ops$tkyte@ORA920> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA920> select count(*) from all_objects;

  COUNT(*)
----------
     30254

ops$tkyte@ORA920> select count(*) from v$sql_plan_statistics;

  COUNT(*)
----------
        20

ops$tkyte@ORA920> @connect /
ops$tkyte@ORA920> select count(*) from v$sql_plan_statistics;

  COUNT(*)
----------
        20

ops$tkyte@ORA920> Disconnected from Oracle9i Enterprise Edition Release
9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production
[tkyte@tkyte-pc Desktop]$ plus

SQL*Plus: Release 9.2.0.3.0 - Production on Thu Jun 12 11:00:48 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production

ops$tkyte@ORA920> select count(*) from v$sql_plan_statistics;

  COUNT(*)
----------
        20

ops$tkyte@ORA920>

as shown there...

Review &
      Followup

      Rating: 4
does it mean
      sql_trace needs to be set?  June 12, 2003

Reviewer:  gs
Thanks tom for your quick response.

V$SQL_PLAN is populated automatically. But for the plan_statistics, do we need
to set the sql_trace?

          Followup:

yes, it is part of SQL_TRACE'ing.  It can be quite expensive to gather at this
level of detail.

      Rating: 5
ORA-03113:
      end-of-file on communication channel  July 26, 2003

Reviewer:  Sikandar Hayat  from Pindi Bhattian -
      Pakistan
I don't know TOM why the following is happening,

SQL> connect scott
Enter password: *****
Connected.
SQL> select * from v$sql_plan;
select * from v$sql_plan
*
ERROR at line 1:
ORA-03113: end-of-file on communication channel

SQL>

The scott has DBA privs and I am running this query on Oracle 9.2 on Win2K box.

          Followup:

contact support (as for all 3113's) please.

July 26, 2003 Krishna -- Thanks for the question regarding
      "Sqlldr", version 8.1.7

      You Asked
Tom,
I thought you were on a vacation.  Thanks for all the prompt responses for all
my questions.
I saved an excel file as csv file and tried to load data in to table.
The records having values in the last column (cells)load well but the records
with nulls in the last columns (cells) error out. I do not remember the error
very well but it says it can not find the value before end of the record and
some thing lke trailing nulls...
Then I added a dummy value in the column(cells) following last column. I had no
problems.
What am I missing here?
Thanks
Krishna

      and we said...
LOAD DATA
INFILE 'ulcase7.dat'
DISCARDFILE 'ulcase7.dis'
APPEND
INTO TABLE emp
  WHEN (57)='.'
  TRAILING NULLCOLS
  (hiredate SYSDATE,
......

you are missing "trailing nullcols" as the error stated :)

you have to tell us "there will be records that do not have all of the columns
-- it is OK"
Review & Followup

      Rating: 5
what is when
      (57)?  July 26, 2003
Reviewer:  Reader

          Followup:

it is a cut and paste from the sample CTL files -- it was saying "when position
50 of the input record = that value, keep this record"

July
      27, 2003 -- Thanks for the question
      regarding "explain plan", version 8.1.5

      You Asked
Tom

Iam reading about index join in 8i, why do we have the hint indes_ffs
when we have index join type?

What are the first things you look for in a explain plan?

Hash join

Hash join creates a copy of all the rows satisfying the join condition in the
memory, and is scanned by the inner table for finding matching rows?

Iam I right?

Thanks

      and we said...
index_ffs is used to use an index instead of a table.

an index join lets us join two indexes instead of joining two tables.  the two
options are totally different.

An index_ffs reads the index out of order using multiblock IO.
An index join scans the indexes in order (as a data structure) and joins them.

I tend to not use explain plan.  I do not find it very meaningful.  I use an
explain plan AFTER i've identified a poorly performing query and generally only
use an explain plan in a tkprof report that has row counts in it.  that helps me
find the part of the plan that is "bad".

memory or disk (temp space).  It is not scanned -- it is "hashed".  the data is
organized by hash key to avoid scanning.
Review &
      Followup

      Rating: 3
Index
      Joins  April 03, 2002
Reviewer:  Kiran
       from Cincinnati, OH USA
Tom,
Please correct me If I am wrong, As per my understanding Index Join Does the FFS
by using all the available Indexes on the Table.Example..I have two indexes ind1
and Ind2 on Tab1 and My ind1 has two columns col1 and col2 and my ind2 has a
column col3. And if my query consists of only these three columns and If I use
Index Join then it should use the IND1 and IND2 to fetch the results with out
scanning the table.
In the above answer you quoted "an index join lets us join two indexes instead
of joining two tables" this statement is confusing me..when I was looking for
help on Index joins, somewhere I found that we can use the Hint /*+
Index_join(Tab1 index1 index2) */.. I tried this hint but it never worked for
me...Could you please explain...

Thanks a lot
Kiran

      Rating: 3
/*+ INDEX_JOIN
      */ hint for self-join query  July 27, 2003

Reviewer:  Yogesh Purabiya  from India
Oracle version 7.3

In a query, I want to self-join a table; and that is the only table in the
query. There is only one index on the table. I want the optimizer to use the
index both the times (for both the aliases of the table). What hint shall I give
? ( /*+ INDEX_JOIN (M1 myTable_pk1) (M2 myTable_pk1) */) ?

With the hint /*+ RULE */ it works fine. But, since RULE is mostly to be
avoided, just wanted to know, how to use any hint with index ? ("INDEX" or
"INDEX_JOIN") I tried, but could not resolve. The Index is used for one alias,
but FTS for the other one ?!?!

table name : myTable
Columns : MC      Number
          C       Number
          MC_Desc VarChar2(255)
          C_Desc  VarChar2(50)
primary key & index on : myTable_pk1; on columns : (mc,c)

select /*+ RULE */
       MC, max (cN) c_cnt,
       max (decode (cN,  1, c, null) ) c1,
       max (decode (cN,  2, c, null) ) c2,
       max (decode (cN,  3, c, null) ) c3,
       max (decode (cN,  4, c, null) ) c4,
       max (decode (cN,  5, c, null) ) c5,
       max (decode (cN,  6, c, null) ) c6,
       max (decode (cN,  7, c, null) ) c7,
       max (decode (cN,  8, c, null) ) c8,
       max (decode (cN,  9, c, null) ) c9,
       max (decode (cN, 10, c, null) ) c10
  from
     ( select /*+ RULE */
              m1.MC, m1.C, count(*) cN
         from myTable m1, myTable m2
        where m1.MC > 0 AND m2.MC > 0
          AND m2.C <= m1.C
          AND m2.MC = m1.MC
        group by m1.MC, m1.c
     )
 where MC > 0
 group by MC;

One more query : I have put RULE at two places. Is that correct ?

          Followup:

in 7.3, use rule.  don't feel "bad" about it.  but get a plan to get to software
that is supported and supportable.

      Rating: 3
INDEX_JOIN for
      Self-Join ; further  July 27, 2003
Reviewer:
      Yogesh Purabiya  from India
Following data *may* be needed for the preivious query.

For 48487 rows, each MC has only 1 C.
For the various MC, the count of C is like (using TOAD)

CNT,COUNT(*)

5,1
6,1
10,1
12,1
45,1
4,2
9,2
3,5
2,111
1,48487

by query :

select cnt, count(*) from
(
select /*+ RULE */ mc, count(*) cnt
  from MYtABLE
 where MC > 0
 group by MC
)
 group by cnt
 order by 2,1

July 27, 2003 Jim -- Thanks for the question regarding "What
      are the impacts when using tables in queries that columns are not fetched
      forne", version 9.0.1

      You Asked
I recently worked at a client site where some of they're queries had tables in
them for no apparent reason because they were not used in producing the result
or have anything to do with columns being returned. I asked the developer why ?.

The answer was, we use this technique in our application to build generic
queries to which we can use the "from" objects and just add to/from the "select"
and "where" clauses.

example:

select a.c1,a.c2,a.c3
from   t1 a, t2 b, t3 c, t4 d
where
a.c1=1 and
a.c2=2 and
a.c1 = b.c1 and
a.c1 = c.c1 and
a.c1 = d.c1

Now, this example only retrives columns from table t1 and the joins in the where
statement from table t1 to the rest of the tables have no relevance at all in
producing the desired results.

I believe this can lead to inappropriate explain plans and additional overhead.

What is your opinion on this technique.

Thanks

Jim

      and we said...
well two things

select c1, c2, c3 from t1;

can return different answers then the query above -- a join can "drop rows" or
"multiply rows" -- if t2 has two rows that match t1 -- then c1,c2,c3 will come
back twice.  if t2 has ZERO rows -- then c1,c2,c3 will not come back.

but, if the relationships are 1-1 and mandatory -- then what they are doing
"stinks"

July 28, 2003 cosmin -- Thanks for the question regarding "re:
      variation to inline view or repeatable column calc", version 9.2

      You Asked
hello Tom,

I was wondering if there is such a concept as a repeatable/same column
calculation/reference.  Let me explain with an example:

select col1, col2, my_complex_subquery_value
from tableA
where
my_complex_subquery_value=123;

Inline views, it's my understading, would not work because in my case,
my_complex_subquery_value would depend/change with each row.  We have a very
large table (tableA, 1+ billion row table) and I would love NOT to have to
calculate that value more than once (in the 'select' and in the 'where' clause).

thanks v. much,

Cosmin

      and we said...
inline views work nicely here -- give it a try:

ops$tkyte@ORA920> create or replace function f return number
  2  as
  3  begin
  4          dbms_application_info.set_client_info( userenv( 'client_info')+1 );
  5          return 123;
  6  end;
  7  /
Function created.

ops$tkyte@ORA920> drop table emp;
Table dropped.

ops$tkyte@ORA920> create table emp as select * from scott.emp;
Table created.

ops$tkyte@ORA920> alter table emp add constraint emp_pk primary key(empno);
Table altered.

ops$tkyte@ORA920> exec dbms_stats.set_table_stats( user, 'EMP', numrows =>
100000000, numblks => 1000000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec dbms_application_info.set_client_info( 0 )
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select ename, empno, (select f() from emp e2 where e2.empno =
emp.empno ) ff
  2    from emp
  3   where (select f() from emp e2 where e2.empno = emp.empno ) = 123
  4  /

ENAME           EMPNO         FF
---------- ---------- ----------
SMITH            7369        123
ALLEN            7499        123
WARD             7521        123
JONES            7566        123
MARTIN           7654        123
BLAKE            7698        123
CLARK            7782        123
SCOTT            7788        123
KING             7839        123
TURNER           7844        123
ADAMS            7876        123
JAMES            7900        123
FORD             7902        123
MILLER           7934        123

14 rows selected.

ops$tkyte@ORA920> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
28

called twice

ops$tkyte@ORA920> exec dbms_application_info.set_client_info( 0 )
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select *
  2    from (
  3  select ename, empno, (select f() from emp e2 where e2.empno = emp.empno )
ff
  4    from emp
  5         )
  6   where ff = 123
  7  /

ENAME           EMPNO         FF
---------- ---------- ----------
SMITH            7369        123
ALLEN            7499        123
WARD             7521        123
JONES            7566        123
MARTIN           7654        123
BLAKE            7698        123
CLARK            7782        123
SCOTT            7788        123
KING             7839        123
TURNER           7844        123
ADAMS            7876        123
JAMES            7900        123
FORD             7902        123
MILLER           7934        123

14 rows selected.

ops$tkyte@ORA920> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
14

ops$tkyte@ORA920>

called once

You might have to use the NO_MERGE hint or add "where rownum > 0" to the
subquery depending on the nature of your query.

Review &
      Followup

      Rating:
      5
thx  August 01, 2003
Reviewer:
      cosmin  from florida
hi Tom,
thanks so much for the detailed response.
As always, we inquire about a topic and besides the awesome practical response
you grace us with new concepts as well.

way to go!!!

July 28, 2003 Anuja -- Thanks for the question regarding
      "querying a clob", version 9.0.1

      You Asked
Hi Tom
I am trying to follow your example given on

http://asktom.oracle.com/pls/ask/f?p=4950:61:82096015809379950::::P61_ID:9255018878716
as I have the similar situation of having stored xml in a clob.

SQL > select
  xmltype(e.x).extract('/REQUEST_DETAIL/GROUP_TYPE/text()').getStringVal()
 from t e;
xmltype(e.x).extract('/REQUEST_DETAIL/GROUP_TYPE/text()').getStringVal()
*
ERROR at line 2:
ORA-00904: invalid column name

I also tried creating a table using xmltype and got an error
sql> create table yy(x xmltype);
create table yy(x xmltype)
                  *
ERROR at line 1:
ORA-00902: invalid datatype

However i could create the table using sys.xmltype
12:34:02 a246156@rmu2.world> create table yy(x sys.xmltype);

Table created.

So i replaced xmltype in your query with sys.xmltype and it still doesnt work .

sql > select
sys.xmltype(e.x).extract('/REQUEST_DETAIL/GROUP_TYPE/text()').getStringVal()
  from t e ;
  sys.xmltype(e.x).extract('/REQUEST_DETAIL/GROUP_TYPE/text()').getStringVal()
  *
ERROR at line 2:
ORA-22876: this user-defined type is not allowed or it cannot be used in this
context

Please advise
Thank You
Anuja

      and we said...
in 9iR2 they added functionality.  in 9iR1, add a "createxml" call in there

ops$tkyte@ORA9I> select
sys.xmltype.createxml(e.x).extract('/REQUEST_DETAIL/GROUP_TYPE/text()').getSt
ringVal()
  2    from t e
  3  /

SYS.XMLTYPE.CREATEXML(E.X).EXTRACT('/REQUEST_DETAIL/GROUP_TYPE/TEXT()').GETSTRING
VAL()
---------------------------------------------------------------------------------
--------------------------------------------------
PR

Review &
      Followup

      Rating: 5
Excellent -
      thank you   April 24, 2003
Reviewer:  Anuja
      Batra  from Chicago, IL USA
Thanks Tom for this solution . It has definitely made my job easier .

      Rating:
      3
Understood  July 28, 2003
Reviewer:
      Sonika  from India
Hi Tom,
understood that EXTRACT is used for searching in XML but
as in the above example, EXTRACT requires the full XPATH. I was trying to use
wildcards and attributes in the XPATH.
I changed the XML to have a flag attribute.
<REQUEST_DETAIL>
   <GROUP_TYPE flag="1">PR</GROUP_TYPE>
   <GROUP_NAME flag="0">DATA ENGINEERING ASSET MANAG</GROUP_NAME>
   <BUS_UNIT_ACRN flag="1">SCS-FCAT</BUS_UNIT_ACRN>
</REQUEST_DETAIL>
when I write the SQL : select
e.request_detail('/REQUEST_DETAIL/*[@flag="1"]/text()').getStringVal()
from t_request e
This is the output which I got:
PRSCS-FCAT
The output gets clubbed as a single column. Is there any way that the output
comes out as two different columns?

Thanks
Sonika

          Followup:

stop already -- you posted this like a gazillion times. all over the place.  I'm
getting bored deleting them.

Look for the FIRST one you put out there.

July 28, 2003 Ivan
      -- Thanks for the question regarding "bind variables/ define confusion",
      version

      You Asked
Tom,
Could you please clarify the DEFINE command in the example
below.
I am having trouble using the DEFINE command.

INPUT:
define naming = 'Ivan'

insert into emp2
(fname)
values
(:naming);

OUTPUT:
Bind variable "NAMING" not declared.

Thanks,
Ivan

      and we said...
You are confusing a DEFINE variable -- which works like a macro
variable, it is physically replaced in the statement prior to
execution -- with a bind variable which is a placeholder in a
sql query to be filled in with a value at runtime.

To use define, you do something like:

ops$tkyte@8i> define naming='Ivan'

ops$tkyte@8i> select '&naming' from dual;
old   1: select '&naming' from dual
new   1: select 'Ivan' from dual

'IVA
----
Ivan

that took the string "select '&naming' from dual" and physically
replaced the &naming with Ivan (as it reported).  What was sent
to the SQL engine by SQLPlus was exactly "select 'Ivan' from
dual".

If I use bind variables on the other hand:

ops$tkyte@8i> variable x varchar2(25);
ops$tkyte@8i> exec :x := 'Ivan';
ops$tkyte@8i> select :x from dual;

:X
--------------------------------
Ivan

SQLPlus did not do any substition here -- SQLPlus sent to the
database the query "select :x from dual".  It then bound a value
'Ivan' to :x and executed the query -- resulting in Ivan being
return.  Note in this query -- i do not have quotes around :x
like I had around &naming -- that is because :x is a bind
variable -- not a character string constant as it was above.

The prefered method when programming is to use bind variables.
It allows us to compile the query "select :x from dual" ONCE and
use it over and over and over with different values for :x each
time.  The query "select 'Ivan' from dual" would have to be
recompiled if we changed Ivan to Tom.

Review &
      Followup

      Rating: 4
Clarifying the
      use of bind variables in SQL*Plus  March 14, 2002

Reviewer:  Toby Chamberlain  from Portland, OR USA

This was a clear and concise explanation of the difference between declaring and
using a bind variable in SQL*Plus and the declaring and using a variable via the
DEFINE statement.

      Rating: 5
How to prompt
      for a bind variable?  July 29, 2002
Reviewer:
      SR  from USA
How do you prompt for a bind variable in a SQL*Plus script with just a SQL
query?

Is the following method correct?

ACCEPT x PROMPT 'Enter value for X: '

variable x number

exec :x := &x

select :x from dual;

          Followup:

That'll do it but in sqlplus -- since the exec :x := &x will be parsed each
time, easier (and more efficient) just to:

alter session set cursor_sharing=force;
accept x prompt 'enter value for x '
select &x from dual;

cursor sharing will let sqlplus use bind variables efficiently.  One of the real
world good uses of cursor sharing.

      Rating:
      5
  August 02, 2002
Reviewer:
      A reader
Hi Tom,

Hope you had a great vacation away from all this!

How can I assign the result of a function to a define variable?
Eg:      Define dt=to_char(sysdate). I want the result of the function to be
stored in the define variable. Thanks.

          Followup:

ops$tkyte@ORA817DEV.US.ORACLE.COM> column dt new_val dt
ops$tkyte@ORA817DEV.US.ORACLE.COM> select to_char(sysdate) dt from dual;

DT
---------
05-AUG-02

ops$tkyte@ORA817DEV.US.ORACLE.COM> select '&dt' from dual;
old   1: select '&dt' from dual
new   1: select '05-AUG-02' from dual

'05-AUG-0
---------
05-AUG-02

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

      Rating:
      5
  August 02, 2002
Reviewer:
      A reader
Please ignore the above posting, I found the answer from your archives as below:

http://asktom.oracle.com/pls/ask/f?p=4950:8:1123791::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:203612348073,%7Bdefine%7D
Thanks.

      Rating:
      4
  March 27, 2003
Reviewer:  Frank
       from NL
Is it possible in SQL-Plus whether a variable exists?
In my parent-script I define a variable using accept.
In my child-script I want to use this variable if defined . If not defined (i.e.
when running this script without the parent), I want to use a default-value.
When I use &<var-name> in my child-script I get prompted for <var_name>

          Followup:

You would use DEFAULT on the accept to make the variable have a value in the
event the user gives you none.

ops$tkyte@ORA920> accept a default hello
afda
ops$tkyte@ORA920> select '&a' from dual;
old   1: select '&a' from dual
new   1: select 'afda' from dual

'AFD
----
afda

ops$tkyte@ORA920> accept a default hello

ops$tkyte@ORA920> select '&a' from dual;
old   1: select '&a' from dual
new   1: select 'hello' from dual

'HELL
-----
hello

ops$tkyte@ORA920>

      Rating: 5
accept with
      'DEFAULT' is really cool  March 27, 2003
Reviewer:
      A reader

      Rating: 5
select using
      variables mystery  July 27, 2003
Reviewer:
      A reader
Hi Tom
I have a case where using a variable gives me
unexpected results.

---
SQL> desc t1
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 NAME                                               CHAR(2000)
 DATA                                               CHAR(100)

SQL> select count(*) from t1 where name like 'name%';

  COUNT(*)
----------
      9050

SQL> @sel3
SQL> select count(*)
  2  from t1 no_binding
  3  where name like 'name%';

  COUNT(*)
----------
      9050

SQL>
SQL>
SQL> @sel4
SQL> variable x char(2000);
SQL> exec :x := 'name';

PL/SQL procedure successfully completed.

SQL> select count(*)
  2  from t1 binding
  3  where name like :x || '%';

  COUNT(*)
----------
         0

SQL>
SQL>
-----
My question is why in the second case the result gives
me 0 rows. Strange thing is if I do a bind with the
value of name3 in the second case,it gives me the
correct result as follows:

SQL> set echo on
SQL> @sel4
SQL> variable x char(2000);
SQL> exec :x := 'name3';

PL/SQL procedure successfully completed.

SQL> select count(*)
  2  from t1 binding
  3  where name like :x || '%';

      9000

SQL>

Can you kindly explain what is going on here?

          Followup:

that was

where name like 'name             <almost 2000 spaces>      ' || '%'

never use char, a char is simply a blank padded varchar2

use varchar2 for everything and you'll NEVER be sorry.

use char and you'll be kicking yourself forever.

      Rating: 5
thanx
      Tom  July 28, 2003
Reviewer:  A reader

"that was

where name like 'name             <almost 2000 spaces>      ' || '%'
"
I knew it was something to do with char...

But then how did it work with the second case
...
"SQL> set echo on
SQL> @sel4
SQL> variable x char(2000);
SQL> exec :x := 'name3';

PL/SQL procedure successfully completed.

SQL> select count(*)
  2  from t1 binding
  3  where name like :x || '%';

      9000
"

Should n't it have been
'name3  aalmost 2000 spaces %' in which case it should
have failed?

Thanx!

          Followup:

because you have data that is like:

name3                                         ....%

and you don't have any data that is like

name                                          ....%

      Rating: 5
of
      course!!  July 28, 2003
Reviewer:
      A reader
Thanx for pointing it out!! That clarifies the confusion.
I dont use char in my production - I was using it to
"fill" up the table because that is what a test case
required..I would use rpad and varchar2..

Thanx again!

Menon:)

July 28, 2003 Jim -- Thanks for the question regarding "When to
      recompile packages/procedures/functions", version 8.1.7

      You Asked
We are getting a new software package that recommends recompiling all of their
packages/procedures/functions after computing statistics saying they would use
the old plans unless this was done.  Is this necessary?

stats are collected as follows:
analyze table table_name compute statistics;

Thanks.

      and we said...
that is laughable!  no, you would not recompile packages/procedures or code.
the shared pool takes care of all of this for you transparently.

DO NOT recompile a thing, Oracle is not sqlserver!

Review &
      Followup

      Rating: 5
When
      IMPORT??  July 28, 2003
Reviewer:  Harold
      Flores  from Managua, Nicaragua
Last week i made an import of 1000 tables. The performance was very bad, the
execution plan was  all wrong until i update the statitics on tables and index,
so problem solve. I thougth that import will actually import the stats and/or
update the stats when the import ended.

Thanks for your time

Harold

July 28, 2003 rd -- Thanks for the question regarding "client
      server, application server, 3 tier", version 8.1.7

      You Asked
Tom

In 3 tier structure, the application is usually installed on the application
server, and is accssed by the client.

For example a forms application instead of being on the client is installed on
the application  server. The advantage we gain here is that , the client need
not have a lot of resources, where in a each client should have enough resources
for the installation of the application.

If a company has enough money to buy enough resources of each client, we can as
well have a client server environment instead of 3 tier.

What are the specific reasons for which 3 tier structure is chosen.

What exactly does any application server does. Actually what exactly is an
application server. How is the application downloaded to the client when a
request is made to the client. I guess this is done using a java client.

we say that application code can be distributed between  the application server
and the database server, but even in client server environment , we divide the
code between the client and the server.so how is the 3 tier environment better
for this reason.

What are the guidelines for dividing the application code between the
application server and the database server.

Thx

      and we said...
No, I disagree with your analysis -- its not about client performance at all.

The benefit of 3 tier is not that the client can be "underpowered" but rather
that you do not need to deploy your application to the clients at all.  The
client still needs to be a fully functional, up to date, workstation.

3 tier obviates the need to deploy an application to thousands of desktops.

It obviates the needs to ensure "net8" is working on all of them with their dot
release (service pack) of the OS (among dozens of other incompatibilities).

It obviates the need to patch each one when you find bug #1 (and #2, and #3 and
so on) in your application.

It ensures all are running the same "new latest version" -- immediately

It ensures you can manage your systems.

It does NOT imply the client can be a 8088 4.77 mhz processor with 256k of ram.
It removes the management problem... period.

I cannot even imagine attempting to do anything client/server today (not since
about 1995 actually).  Does Amazon install stuff on your machine? Yahoo?

Review &
      Followup

      Rating: 2
Sorry but I
      want to know this  August 19, 2001
Reviewer:
      Melvin  from UK
hi Tom, Sorry for doing this but you see I am waiting for nearly 7 weeks to ask
you a question and till date I am unsucessful, My query is can we dynamically
specify the Max record per page property in Reports.

Sorry about this.
Mel

          Followup:

Melvin -- sorry to disappoint but I'm not a reports guru, I tend to stick to the
database mostly.  I recommend you goto

http://otn.oracle.com/

there are discussion groups for the various tools, including reports, with many
experts waiting to answer (if they can)

      Rating: 5
I beg to
      differ  August 19, 2001
Reviewer:  Don
       from USA
Tom

my opinion on 3 tier and client server technologies is that, people are adopting
3 tier becasue it is there. We were happy with the mainframes when client
server's came in, and every one started running towards it. Now I see a lot of
companies doing good with client server, and now they are running towards 3
tier. I observe that most of them want to do this becasue others are doing it.

All the reasons which you mentioned in support of 3 tier, are fine. But even
without 3 tier , a client server can function equally efficiently. Yes, I do
accept your argument in case of Yahoo or Amazon. But still..

May be Iam become outdated in today's fast changing world but, people seem to be
wanting to make changes or change most of a desire to change , but not out of a
need to change.

You more deep sighted comments please.

Further, I really appreciate you for the kind of support you are proving to
developers, I wish I had someone like you 15 years back. I am really impressed
with the way you have been answering questions on new concepts like
partitioning. I think, if some one goes through all your answers to questions on
partitions, then they will get more insight into the partitioning concept , than
going through the oracle online manuals.

          Followup:

Well, I tend to disagree with your opinion.  A small story perhaps will help
clarify this.

When I joined Oracle in 1993, we were a company of about 10,000 or so people as
I recall.  I was assigned a vt100 for doing email (and my job).  I was lucky
enough to get a sun workstation as well (to work with the database, as was my
job).  We did host based (just like a mainframe) email, research, bug reporting,
tar, etc.

Ok, now these laptops come in.  Sales force automation it was called.  We had a
nice client server application to get into our corporate repository, we had a
GUI email client (Oracle office and then Oracle interoffice), we had a bug
application and so on.  Well, it took about 1 week to find bugs in every
developed client server application.  So, here comes the re-rollout to 10,000
people.  Well, it took about 1 week to get the next wave (and so on).  This was
a continual process.

Couple with that the fact of attrition and growth.  Oracle in 2001 is a 30,000+
employee company -- there are lots more laptops to contend with AND with
turnover, there are really lots more to contend with.

Couple that with the fact that when we started, we were at windows 3.11 -- then
95, then 98 -- mix some NT in, linux over here and there and now comes win2k.
Each with its own incompatibilities and configuration issues.  Oh yeah, don't
forget -- as time goes on, we upgrade hardware so now instead of everyone
running Dell laptops (thats what we started with in a homogenous environment) --
we have dell, toshiba, compaq, and others.  desktops as well.

You know what -- those client server apps never made it - never never made it.
With 100 people in a building -- true, client server works just dandy.  You can
hire someone out of college to go around and install everywhere.  with 10,000+
people -- its just not possible.

For the rollout of an application to more then a handful of people, client
server just didn't cut it.  Every machine we goto install on seems to be some
wonderful new configuration with its own quirks and nuances.  It just never
worked on a large scale.

Now, enter the web browser.  All of a sudden the clients can just run your
application immediately.  The barrier to using new applications is removed. No
more do I have to worry if SFA update 1.3.42.3 is going to corrupt my MSCVT.DLL
(or whatever) rendering my laptop (but not the guy next to me) unusable just by
running the new software.  We can deploy an application to 30,000 people in
seconds -- not weeks and bug fixes -- they are done "hot", immediately and for
everyone simultaneously.

I see this as a better client server (face it, the application server is the
client to the database server -- its client server with a twist).  It has many
of the benefits of the mainframe with lots of the features of client server.  In
my opinion, the best of both worlds.

Also, it is interesting to note that this very web site would be technically
infeasible without this particular architecture -- there is no way we could do
this without it.

      Rating: 4
Agreed Tom
        August 20, 2001
Reviewer:  Nagib
       from Cyberspace
I agree with your analysis Tom.
Now let's hope that Oracle's products live up to
their expectations.  A simple problem is facing me
now is this:

I have Form A callling Form B which does somework
then returns control to Form A using a call to EXIT_FORM.  All in Forms
Developer 6i.

This works fine with the Form_Runtime program.
But when trying the Web_Form run option Form B's EXIT_FORM call causes the
entire application to exit.  It does not return control to Form B.

Any help.  Thanks
Nagib

      Rating: 4
3
      tiers  April 09, 2003
Reviewer:  gre

Tom,
I have a question about 3 tiers.
Our project's requirement is :
" migration and upgrade of Oracle Forms v4.5 and Reports v2.5 running in a
single tier architecture on DEC Alpha VMS to Oracle Forms and Reports v6i
running in a three-tier architecture where the middle tier is hosted on the UNIX
platform (Sun Solaris), the data tier remains on VMS and the client tier is the
standard internet browser."

My question is how can I deal with the some vms functions in the forms(they use
the host() to call the vms .com file or submit some jobs). Now they want to keep
the .com files.
I got confuse about this. What is the relatioship among form, unix, VMS here?
Can you advise me ?
Thanks!

          Followup:

you can use external procedures or java stored procedures in the database to
"host" on VMS.

They will run as the Oracle Software owner (java) or the person that started the
external procedure listener (extprocs) which would be different -- but they
would run on VMS

      Rating:
      4
ger  April 09, 2003
Reviewer:
      A reader
Tom,

It sounds very helpful. I am not very familar with the area you mentioned, you
mean I call a Java stored procedure in the form which "host" vms command ? Do
you have sample code or explain more detail about this ?

Thank you very much!

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:952229840241

      Rating:
      4
  April 10, 2003
Reviewer:  gre

Hi TOM,

I am not sure if I explained my question clearly.

In my old form which is running on the VMS there is code like this :

cmd_ln:='@vms.com file ' ;
     HOST(cmd_ln);

In the new forms which will be running on the unix , but they still want to call
the VMS's vms.com file. How can I change this in the form ?
  cmd_ln:='@vms.com file ' ;
  HOST(cmd_ln);
to let it work on the Unix.

Sorry to bother you for my slow.

Thanks again!

          Followup:

No, I think I understood.

client on desktop (browser)
forms on app server(unix)
database will be on VMS right?

you want forms on unix to run vms COM file on vms -- therefore put the host
command into the database -- database runs the host command -- com file runs on
VMS.

      Rating: 5
What is Oracle
      Browser?  July 28, 2003
Reviewer:  A Reader
       from USA
Hi Tom,
I recently have a job interview with a company.  One of the questions is: how
many years of experience do you have with Oracle Broswer?  I answered: No direct
experience.  What is Oracle Browser?  I don't know anything about this.
Thank you.

          Followup:

it is the really old name for Oracle Discoverer.  It was "Data Browser", hasn't
been called that for years.

it is an ad-hoc gui query builder.

July 29, 2003 ravi kumar -- Thanks for the question regarding
      "Pointers in c", version 8.1.7

      You Asked
I want to move a list of n - numbers strings from database to array
how i can proceed?
The following following gives error? pls. help me in this regard?
Since the array row size is dynamic I went for ** but it creates problem...

void load_pricks(){
int i;
char pricks_array[10000][7];
char **pricks_list;
int pricks_list_size=0;

EXEC SQL SELECT count(*) INTO :pricks_list_size FROM pricks;

pricks_list = (char**)calloc(sizeof(char**),100);
if (pricks_list == NULL) {
   printf("Error allocating pricks_list array\n");
   exit(1);
}
EXEC SQL SELECT pricks_code_pfx INTO :pricks_array FROM pricks;
 for (i=0; i<pricks_list_size;i++)  {
  pricks_list[i] = "pricks";
  strcpy(pricks_list[i],pricks_array[i]);
 }
}

      and we said...
first it is "scary" to do a blind select into an array like that -- you don't
KNOW how many rows that query will return.  You need to array fetch "n" at a
time.  Using a cursor.

second, you are allocating 100 pointers - not 100 "elements of 7 characters".
your calloc is way off.

Here is how I would do it.  my_type is able to hold a varchar2(30).  We have an
unsized array of my_type.  We use calloc to allocate N elements of my_type (at
the right size!).  We array fetch N of those records at a time (the fetch would
need be in a loop).

void process( void )
{
typedef char  my_type [31];

EXEC SQL BEGIN DECLARE SECTION;
my_type   * pick_list;
int       n = 100;
EXEC SQL END DECLARE SECTION;
int       i;

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();

    n = 100;
    pick_list = (my_type *)calloc( sizeof(my_type), n );

    exec sql declare c
    cursor for
    select username
      from all_users;

    exec sql open c;

    exec sql for :n fetch c into :pick_list;

    for( i = 0; i < sqlca.sqlerrd[2]; i++ )
        printf( "%s\n", pick_list[i] );

    exec sql close c;
    exec sql commit;
}
Review &
      Followup

      Rating: 5
typedef inside
      BEGIN DECLARE SECTION?  July 28, 2003
Reviewer:
      Kamal Kishore  from New Jersey, USA
Hi Tom,
When I try to compile the above code (with other necessary functions etc, of
course), the Pro*C compiler complains about the following:

typedef char  my_type [31];

==========================================================
Pro*C/C++: Release 9.2.0.3.0 - Production on Mon Jul 28 19:58:23 2003

Syntax error at line 25, column 1, file array_processing.pc:
Error at line 25, column 1 in file array_processing.pc
my_type   * pick_list;
1
PCC-S-02201, Encountered the symbol "my_type" when expecting one of the followin
g:

   auto, char, const, double, enum, extern, float, int, long,
   ulong_varchar, OCIBFileLocator OCIBlobLocator,
   OCIClobLocator, OCIDateTime, OCIExtProcContext, OCIInterval,
   OCIRowid, OCIDate, OCINumber, OCIRaw, OCIString, register,
   short, signed, sql_context, sql_cursor, static, struct,
   typedef, union, unsigned, utext, uvarchar, varchar, void,
   volatile, a typedef name, a precompiled header, exec oracle,
   exec oracle begin, exec, exec sql, exec sql begin,
   exec sql end, exec sql type, exec sql var, exec sql include,
The symbol "enum," was substituted for "my_type" to continue.

Error at line 0, column 0 in file array_processing.pc
PCC-F-02102, Fatal error while doing C preprocessing

==========================================================

It requires that I put this declaration inside the EXEC SQL BEGIN DECLARE
SECTION. Once I do that it compiles OK.

Is this the expected behaviour?
Thanks,

          Followup:

I used 9203 as well -- didn't happen that way for me.  you must be using a
parse= parameter that is causing you to use the exec sql begin/end declare
sections.

      Rating: 5
This behaviour
      unique to Windows platform?  July 28, 2003

Reviewer:  Kamal Kishore  from New Jersey, USA

The same program compiles without any errors on the Unix platform.
But when I compiled on Windows platform, it generates the error message and
requires that the "typedef" be defined inside the DECLARE BEGIN SECTION.

It seems there are differences in the Pro*C implementation (or that default
switches are different) on different platforms.

          Followup:

the default parameters are different.  on windows, parse=none is the default for
some reason.

July 29, 2003 antoine -- Thanks for the question regarding
      "pessimistic locking vs optimistic locking", version 8.1.7

      You Asked
Hi Tom,

I would like to have your point of view on that, pessimistic vs optimistic
locking.

I read this article

http://www.orafaq.com/papers/locking.pdf
on optimistic locking.

What the guy is saying is better to use optimistic locking with  trigger to
ensure concurrency.

So what is point of view?

BTW: I never had your response back did I do something?

thanks anyway.

Antoine

      and we said...
I have this email in my list of "bad" emails: xxxxx@symapatico.ca  (i x'ed out
your email name, you typed in the domain wrong)

Perhaps, you used that one, the email bounced and I put it in my reject list for
future questions.  Watch your typing, the only way I can get back to you is to
have you input a valid email address in the first place.

If you have my book "Expert one on one Oracle", I cover this topic in detail.
In a stateful environment (like client server, java servlets, things that
maintain a session for you) I prefer pessimistic locking, whereby you select for
update the data.  So called "Optismistic locking" is for database managment
systems whereby write locks prevent readers from reading data (such as
sqlserver, db2, informix, et. al.)

I am a huge fan of so called pessimistic locking.  The user has very clearly
announced their intent to UPDATE THE DATA.  The lockouts they refer to are
easily handled with session timeouts (trivial) and the deadlock is so rare and
would definitely be an application bug (in both Oracle and RDB).  And to say
that RDB only offers pessimisitic locking is false.  Optimistic locking is
something you have to implement in your code yourself -- regardless of product.
It involves adding time stamp type columns to every table and having the update
statement verify the timestamp.

How annoying would it be for you to fill out a form, spend all of the time doing
the work -- only to be told at the end "sorry, the data you are trying to update
was updated -- PLEASE START ALL OVER AGAIN FROM THE VERY BEGINNING"

Thats optimistic locking for you.

I just disagree with this paragraph:

...
More challenging than the technology is overcoming resistance from seasoned
development professionals who have been using the trusted SELECT& FOR UPDATE for
all of their Oracle careers. These individuals may need to be convinced of the
benefits of using optimistic and on large development projects their support
will be crucial.
......

I haven't been convinced personally.

Optimistic locking is useful in stateless environments (such as mod_plsql and
the like).  Not only useful but critical.
Review &
      Followup

      Rating: 5
pessimistic
      locking vs optimistic locking  October 06, 2002

Reviewer:  Antoine Bocti  from Montreal, Canada

Thanks again Tom, for your point of view it was excellent!

Sorry again for the email bounced! I am very sorry.

Is it possible Tom to have the response for the email bounced?  My question was
about

Beginning Oracle Programming
and
Professional Oracle 8i Application Programming with Java, PL/SQL and XML.

What was you're contribution to each book?

It's because I want to buy those books but I want to be sure of your
contribution especially Professional Oracle 8i Application Programming with
Java, PL/SQL and XML.

Thanks Again.

          Followup:

I wrote one chapter in the Pro Oracle 8i -- not a very large contribution.

As for Beg Oracle Programming -- I wrote 3 chapters for that, reviewed the
entire thing and work real close with the other major authors (the guys on the
front cover, they report to me actually ;)  So, I can vouch for that book.

      Rating: 5
pessimistic
      locking vs optimistic locking  October 06, 2002

Reviewer:  Antoine Bocti  from Montreal, Canada

thanks again Tom!
I will buy the begining oracle programing!

My last question, if I want to be better with Oracle is it better to read a lot
and experiment a lot, and be the only one DBA in a entreprise.

OR

Work with a lot of DBA in a entreprise and share the  knowledge and read and
experiment a bit?

thanks.

          Followup:

I learned by doing with others.  Learning from and with others to me seems to be
the better way.

      Rating: 5
thanks
      again!  October 06, 2002
Reviewer:  Antoine
      Bocti  from Montreal,Canada
That's what I thought!

      Rating: 2
Pessimistic
      locking in Java etc ?  July 29, 2003
Reviewer:
      A reader
Hi Tom,
You mentioned using pessimistic locking in java, but that does not really seem
to be the way ahead specially if one is using connection pooling,As the session
can get used by any other user without the original user doing any change.
For E.g
User 1 selects the record for update,Does nothing and just closes the browser
window
User 2 login in an tries to select a record to update, unfortunatly gets the
same session as user 1, the application will display the user that record is
locked whereas it is not actually locked.

We had developed a client server application where we had used selected for
update and it worked beautifully , i did want to implement the same in the
webbased application which uses connection pooling, but could not as the
developers can't write codes in window close event for browsers events as they
can with client server technology.

The only way may be to rollback the connection right after opening,am i on the
right track.

          Followup:

of course you cannot use it when the database state is not held across requests.

July
      29, 2003 Ravi -- Thanks for the
      question regarding "Create_stored_outlines parameter in init.ora", version
      8.1.6

      You Asked
Dear Tom,

I am trying to create the stored outlines. According to 815
manual Tuning part, I added the parameter create_stored_outlines
= true. But I am getting the error when I start the database.
The error message what I get is

"LRM-00101: unknown parameter name 'create_stored_outlines'"

So, I looked for the syntax in 815 REFERENCE MANUAL for
create_stored_outlines parameter. There I could not find this as
a parameter for init.ora.

I hope that I can create the outlines using "CREATE OUTLINE"
statement. Still, if you can comment on this paramter for
enabling this paramter at system level, I would really
appreciate your help.

Regards
Ravi

      and we said...
There are 2 ways to generate plans.

One method is via SQL DDL and the other by setting a session
state variable.  We’ll look at both now and consider when you
might use each.  In either case however, you’ll want to make
sure the schema generating the outlines has the “CREATE ANY
OUTLINE” privilege granted to them.

To do the SQL DDL approach, you would:

create outline foo on select * from dual;

To use the session setting, you would:

ALTER SESSION SET CREATE_STORED_OUTLINES = TRUE;
ALTER SESSION SET CREATE_STORED_OUTLINES = FALSE;
ALTER SESSION SET CREATE_STORED_OUTLINES =
some_category_you_choose;

There is not an init.ora paramter to this on globally.  It works
at the statement or session level only.  I suppose you could
create an on logon trigger to enable it globally HOWEVER, you
want to selectively create stored outlines in a stable,
controlled environment -- you do not want to create them
"globally".

Review &
      Followup

      Rating: 4
what are stored
      outlines  July 29, 2003
Reviewer:
      A reader
Hi Tom,
What exactly are stored outlines and what can they be used for

          Followup:

If you have my book "Expert one on one Oracle" -- I have a lengthly chapter on
them.

Short of that, documentation says.....

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96524/c16sqlpl.htm#8546
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96533/outlines.htm#13547

July
      29, 2003 Steven -- Thanks for the
      question regarding "Quick Question", version 7.3.4

      You Asked
Tom,

I'm using a bind variable to specify the order by clause on a query
e.g.
select empno,ename,job
from emp
order by &ord

will this be parsed everytime it is run.  I know that bind variables in general
are parsed once run many, but is that still the case if there in the order by
clause?

Thanks in advance
Steve

      and we said...
Yes, it'll be parsed every time and in fact -- it MUST be.

That is not a bind variable -- that is a substitution variable.  SQLPlus will
substitute in the value of &ord BEFORE it sends that statement to the database.

If &ord = empno, the query oracle sees will be "select empno, ename, job from
emp order by empno".  If its job, it'll be  "... order by job"

For an order by -- you have to put the value in there.  You cannot bind an
identifier.  If you tried a real bind variable:

select * from emp order by :bv

and set :bv := 'empno'  the query Oracle would see is

select * from emp order by 'empno'

it would order by the CONSTANT 'empno' -- not the column EMPNO.
Review &
      Followup

      Rating: 5
Dynamic Order
      By  March 15, 2002
Reviewer:  Steve
       from Blackburn
Tom,

Is their anyway then to do a dynamic order by without incurring another parse???
 The reason I ask is we tend to use Lexical Paramters in or report builder
reports to allow the user to specify the order by clause, but after reading your
great book I've realised the error of our ways and I'm now trying to find an
alternative method.

          Followup:

No (well yes and no), but -- it should be OK in this case.

For example, say you have 5 columns.  That could result in LOTS of different
where clauses (tons of them, every combination of the columns taken 0, 1, 2, 3,
4 or 5 at a time -- and EACH with unique values to be compared with).  The where
clause would generate an INFINITE number of queries.

It will probably result in only 5 different "order by"s though.  (or some
smallish number, even in the low 100s maybe).  It is finite.

So -- you want to bind in the where clause, in the ORDER BY clause -- you want
the optimizer to see each order by as UNIQUE.

Eg:

select * from emp where job = :bv order by ename;
select * from emp where job = :bv order by hiredate;

suppose there was an index on (job,hiredate).  You would WANT two different
plans for each of the above queries!!  One should use the index to access AND
get the data sorted.  The other should use the index to access the data and then
invoke a sort.

Don't worry about the binds in the order by.

      Rating: 3
I like this
      decode  March 15, 2002
Reviewer:  andrew
       from ca, usa
http://asktom.oracle.com/pls/ask/f?p=4950:8:594773::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1200189296847,

      Rating: 2
Lexical
      Variable Usage in Reports  October 23, 2002

Reviewer:  Michelle Monteiro  from York, England

Hi Tom
We use lexical variables in our Reports to improve flexibility, it enables us to
use one report as opposed to producing several, simply by changing the values of
the lexicals (column and table definitions).

e.g table a has empno, ename
    table b has empno, job

so we would
select empno, &P_col P_FIELD
from &P_TABLE

This all works fine, however if I want to know the value of :P_FIELD and act
accordingly I run into problems e.g
IF :P_FIELD = 'Smith' THEN...
The value still reads the passed value of 'ename'.
Is there a value of reading the data value of the returned lexical column
variable and not the passed in string???

          Followup:

try the discussion forums on otn.oracle.com under "developer".

I've never actually written a report in my entire life.

      Rating: 3
I can't
      recreate this problem!  October 23, 2002
Reviewer:
      Steven  from Blackburn, England
Michelle,

I use lexical parameters in reports, but I have never experienced this
problem....I have just create a test report in Report Builder ver 6, which works
as expected.

heres
what I have done:
1) Create lexical parameter called P_1 initial value = sal.
2)Create sql query :
SELECT EMPNO , &P_1 C_LEX
FROM EMP
3) use layout wizard to generate default layout.
4) attach PLSQL format trigger to the C_Lex field :
function F_SALFormatTrigger return boolean is
begin
  IF :C_LEX <= 3000 THEN
      return (TRUE);
  ELSE
      return (FALSE);
  END IF;
end;

This works as it should i.e. anybody with a sal > 3000 and their sal won't be
shown.

Hope this helps

      Rating: 5
Sorting
      issues...  July 29, 2003
Reviewer:  Alvin
       from Philippines
sys@RTBE.CRONOS> create table t (name char(1));

Table created.

sys@RTBE.CRONOS> insert into t values ('B'
  2
sys@RTBE.CRONOS>
sys@RTBE.CRONOS>  insert into t values ('B')
  2  /

1 row created.

sys@RTBE.CRONOS> insert into t values ('c')
  2  /

1 row created.

sys@RTBE.CRONOS>  insert into t values ('e');

1 row created.

sys@RTBE.CRONOS>  insert into t values ('F');

1 row created.

sys@RTBE.CRONOS> insert into t values ('a');

1 row created.

sys@RTBE.CRONOS>  insert into t values ('D');

1 row created.

sys@RTBE.CRONOS> commit;

Commit complete.

sys@RTBE.CRONOS> select name from t order by name;

NAME
--------------------------------------------------
B
D
F
a
c
e

6 rows selected.

sys@RTBE.CRONOS> show parameter sort

NAME                                 TYPE    VALUE
------------------------------------ ------- -----------
nls_sort                             string  latin
sort_area_retained_size              integer 500000
sort_area_size                       integer 500000
sort_multiblock_read_count           integer 2

I changed the nls_sort setting in the init.ora and bounced the db but i'm not
getting the result as below.

sys@RTBE.CRONOS> alter session set nls_sort=latin;

Session altered.

sys@RTBE.CRONOS>  select name from t order by name;

NAME
--------------------------------------------------
a
B
c
D
e
F

6 rows selected.

Do i need to specifically change my session setting (via alter session)to get
the 2nd result set ?

          Followup:

select * from nls_session_parameters

and

select * from nls_database_parameters

if the client sets ANY of the NLS_* parameters, then all others default --
overriding the datbase default.

So, my guess is that your client is setting NLS_LANG and that is causing ALL
other NLS parameters to default and the default sort is binary.

July 29, 2003 --
      Thanks for the question regarding "Rollback and rollforward", version
8.1.7

      You Asked
Dear Tom
          Why does oracle starts with rolling forward during a recovery.Why cant
it rollback and then roll forward ?.

Regards
Narayanan

      and we said...
In order to rollback, we need the undo information.

Since undo is cached in the buffer cached and protected by redo (like any other
piece of data) -- we need to recover it first.

Therefore, we apply the online redo logs -- which recovers the instance and
brings the state of all of the database blocks to the point they were at
immediately prior to the failure.

Now, we are able to roll back because we have restored the undo information.

Prior to recovery, the needed undo does not exist!
Review &
      Followup

      Rating: 4
Rollback
        February 02, 2003
Reviewer:  A reader

Thank You Tom

      Rating: 4
Shouldnt
      Rollback be done first in this case??  June 19, 2003

Reviewer:  A reader
Hi Tom,
  Consider a situation like this.
1. User 1 updates big_table set x = 1; (10000 rows)
 this will generate
   a) blocks which are updated in buffer cache
   b) undo blocks (old image) in buffer cache
   c) redo blocks (change vectors including redo for undo) in redo buffer

2. LGWR writes these redo buffers to online REDO logs (Group 1 of 2) since it is
1/3 full.

3. Checkpoint occurs and DBWR writes all the blocks from buffer cache (a. above
)to the datafiles as well as undo blocks (b. above) to the undo tablespace. We
have "fuzzy" datafiles now.

4. User 1 leaves the session idle.

5. Another session is doing some massive updates and our Online REDO log (Group
1) has been overwritten.

6. The instance crashes.

   In this case the datafiles have data which is UNCOMITTED. Also the UNDO
tablespace has UNDO data that can be used to rollback the changes made by User
1. The REDO logs however have no information about User 1's transaction. When
instance recovery starts shouldnt it start by first rolling back the changes
made by first users session (which is already available) and then apply the redo
logs rather than the other way round.

  Hope my question makes sense..:)

          Followup:

but tell me, how do you know there is no redo for that user until after you
actually process the redo logs?  chicken and egg, we roll forward and then roll
back.

(you have fuzzy datafiles from the second you startup the database -- all read
write datafiles are fuzzy)

      Rating: 5
Gotcha
      :)  June 20, 2003
Reviewer:  A reader
       from MN, USA
Yes that makes sense.. So we always open the redo files and then rollforward.
The Rollback information for USER 1 is already in the UNDO datafile and once we
have rolled forward (for other users) upto the point where we crashed we can
then check which transactions were active during the crash. User 1's (as well as
all other users ) transactions will be seen to be active (from the RBS headers
transaction table) and that undo will be used to rollback..
yep makes a lot of sense now :)

      Rating:
      5
  July 28, 2003
Reviewer:  John

Tom,

this may be a basic question.

Customer table:

ID   Name   Date of birth
1    John   25-jan-1965
2    David  12-apr-1971

now if user1 updates customer table as
update customer set dob = '18-mar-1965' where id = 1;

where this change will be kept until this is commited?

i think it will be in data buffer cache, if so,
others would also be able see right?

          Followup:

(remember ALWAYS use to_date to convert a string to a date using a format, that
should be:

update ... set dob = to_date( '18-mar-1965', 'dd-mon-yyyy' ) where ...

)

the change is stored on the database block itself.  It might be in the buffer
cache, it might be on disk

Undo, to rollback the change is stored in the rollback segment -- which might be
in the buffer cache, might be on disk.

Redo to redo the change in the event of a system failure and assuming the block
was buffered -- but not written -- will be in the redo logs -- either in the
redo log buffer or on disk.

      Rating: 5
question on the
      followup  July 28, 2003
Reviewer:  John

tom, thanks for suggestion on the where clause and answer.

i also asked another question:

if the change is in datablock of buffer cache or data file.

the above is true for uncommited transactions also?

then others would also see the change, when the change is not yet commited
right?

          Followup:

others will NOT see the change, whether the modified block exists only in the
buffer cache or in the buffer cache and on disk or only on disk.

Read:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96524/c21cnsis.htm#17882
it is the single more important feature of Oracle for anyone to understand.

      Rating: 1
where the
      change is stored?  July 29, 2003
Reviewer:
      John
tom,

if the change is stored in data block(of buffer cache/ datafile) before commit,
then others would also be able see the non commited change right? which is not
correct.

can you please answer this question

          Followup:

see above, no they would not.  read that chapter and keep reading it until you
fully understand it.

single MOST important concept to get about oracle.

July 29, 2003 Katrin -- Thanks for the question regarding
      "Oracle Form*Builder and LOBs", version 6.0

      You Asked
We are using Oracle 8i (8.1.5) and Developer 6.0. I have a table

that contains one CLOB field.   The field could be up to 2G and
potentially more (but, of course, less than 4G).  I have created

a form for querying the contents of the table.  Users would like

to be able to perform searches on the CLOB field also.

I noticed that the default maximum length of the field in Form
Builder is 32K.  I tried setting it to 4000000000, and it reset
it to 2147483647.  Now, I tried setting the maximum query
length.  It has to be 0 or greater than maximum field length but

less than 65536.  So, I just set it to 0 but enabled querying on

the field.  When I executed the form and did an "enter query", I

could not type anything in the CLOB field!

Next, I tried setting the maximum field length to 4000000 and
left query length as 0.  Now, when I executed the form and tried

querying on the CLOB field, I got an error (inconsistent data
types).

So, I have two questions:

1) How can I make sure the length of the field is the maximum
needed (really 4G, but it looks like Form Builder allows a
maximum of 2G right now) while allowing queries on the field?

2) How can I actually make the query work?  I am assuming that
somehow I have to use dbms_lob.instr to do what I need to.
I know you can change your query data source (from clause or
stored procedure), but I am not sure if that's the right or
the best and most efficient alternative.

Thanks.

      and we said...
You will need intermedia text to do this with any measure of
efficiency.  Using dbms_lob.instr on a 2gig chunk of text is
going to be extremely excessively slow.....

See

http://technet.oracle.com/doc/inter.815/a67843/toc.htm
for more info on that.  It'll give you full text search,
proximity searching, fuzzy searches and so on.  With "gigs" of
text, you will not be able to do this without it.

On the other hand, even if forms could display it -- I do not
think you would want it to.  I believe you'll have to build some
sort of "pagination for a clob" into forms.  You really don't
want forms to allocate X gig of memory on the client do you?
It'll never work.  I have a fairly beefy Win2k laptop but
the most ram I can allocate is somewhat less then 1 gig, let
alone 2 or 3.

You will have to build a block that lets them page up and down
through the text using some reasonable chunksize (32k is going
to be reasonable).  You'll use dbms_lob.substr or dbms_lob.read
to get chunks of text.  You can use dbms_lob.getlength to figure
out how many "pages" you have and building a form to page
up/down through that will be easy.  Don't even consider trying
to bring back 2 gig of text over the network in one big read --
not only will you never have the ram to hold it -- but you'll
get really impatient waiting for it!
Review &
      Followup

      Rating:
      4
  March 22, 2001
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating: 3
CLOB field
      support in Forms 6.0.  July 29, 2003
Reviewer:
      Kiran Shah  from India
As my problem is regarding to CLOB, I hope I can ask my problem in this
thread...

I have one table which contains one CLOB field and other fields which have
NUMBER and varchar2
data type and correspondingly I have one form which have one  LONG field(for
CLOB table field) and other fields have NUMBER and CHAR type...

I am using cursor to fetch data from table based upon primary key values and
then storing these values into block fields...

When I compile my Form, it gives following error :
'type mismatch found at 'block1.field1' between FETCH cursor and INTO
variables...'

(This error occurs for CLOB field..)

Does it mean we can not get the value of CLOB fields using CURSOR ? If so then
what is the alternative...?

We are using Forms(6.0.8.11.3) and Oracle 8i DB

Thanks,
Kiran Shah.

---

          Followup:

sorry -- it is since 1995 that I last wrote a form and I don't have the software
installed anywhere.

try otn.oracle.com -> discussion forums, there is a discusion forum for
developer related topics.  lots of people there that use that tool, might be
able to help

I can say to try:

for x in ( select * from t where primary_key = :item )
loop
    :block.long_item := dbms_lob.substr( x.clob_col, 32000, 1 );
    .... other assignments ....
end loop;

July 29, 2003 Richard -- Thanks for the question regarding "Why
      the explan is same with "IN" and "EXISTS"", version 9.2.0

      You Asked
Hi Tom:
  I has two tables Serv and cq_change_cust, Serv is partition table the
partition p200307 has 1691922 rows and cq_change_cust has 1783 rows.when I
excute two statements one use in and other use exists, the explan as follows:

SQL> update serv partition(p200307) a set a.cust_id=(select cust_id from
cq_change_cust b where a.se
rv_id=b.serv_id) where serv_id in(select serv_id from cq_change_cust);

updated 1783 rows

used time:  00: 00: 50.05

Execution Plan
----------------------------------------------------------
   0      UPDATE STATEMENT Optimizer=CHOOSE (Cost=4055 Card=896 Bytes=
          13440)

   1    0   UPDATE OF 'SERV'
   2    1     HASH JOIN (SEMI) (Cost=4055 Card=896 Bytes=13440)
   3    2       TABLE ACCESS (FULL) OF 'SERV' (Cost=3139 Card=1691922
          Bytes=16919220)

   4    2       TABLE ACCESS (FULL) OF 'CQ_CHANGE_CUST' (Cost=2 Card=1
          783 Bytes=8915)

   5    1     TABLE ACCESS (FULL) OF 'CQ_CHANGE_CUST' (Cost=2 Card=1 B
          ytes=10)

SQL> update serv partition(p200307) a set a.cust_id=(select cust_id from
cq_change_cust b where a.se
rv_id=b.serv_id) where exists (select 'x' from cq_change_cust c where
c.serv_id=a.serv_id );

updated 1783 rows

used time:  00: 00: 54.04

Execution Plan
----------------------------------------------------------
   0      UPDATE STATEMENT Optimizer=CHOOSE (Cost=4055 Card=896 Bytes=
          13440)

   1    0   UPDATE OF 'SERV'
   2    1     HASH JOIN (SEMI) (Cost=4055 Card=896 Bytes=13440)
   3    2       TABLE ACCESS (FULL) OF 'SERV' (Cost=3139 Card=1691922
          Bytes=16919220)

   4    2       TABLE ACCESS (FULL) OF 'CQ_CHANGE_CUST' (Cost=2 Card=1
          783 Bytes=8915)

   5    1     TABLE ACCESS (FULL) OF 'CQ_CHANGE_CUST' (Cost=2 Card=1 B
          ytes=10)

  I think "IN" is different "EXISTS" why these plans is same?
  Can you explain why I get this plan ?

      and we said...
the CBO is smart enough to rewrite queries so that IN/exists NOT IN/not exists
and many other constructs that are known to be interchangable in certain
situations -- are interchanged by the optimizer itself.

The RBO was never that smart.  It would do what it was told to do, period.
July 29, 2003 Guru -- Thanks for the question regarding
      "Storing documents Database v/s File system", version 8.1.7.1

      You Asked
Hi Tom,
I am considering design options for an application in which a user will upload
and store documents and that will be dowloadable by others. The documents would
be MS word docs on an avg. 150 K. There would be a migration of 18,000 - 20,000
docs (from CDs) and would rise to abt. 25,000 when the app is used. The browser
front end will be PL/SQL cartridge pages for upload and download. Initially the
app will accessed by 400 - 500 people (nearly) simultaniously and within 2 weeks
it will become 300 people a day (scattered access).
The documents themselves are to be stored in the database in BLOB.
Do you think that is a good option considering the usage?
I am particularly conserned about the impact on SGA. will it not hog the SGA?

SQL> sho sga

Total System Global Area  390458388 bytes
Fixed Size                    76820 bytes
Variable Size             340172800 bytes
Database Buffers           49152000 bytes
Redo Buffers                1056768 bytes

considering the app requirements and the front end (browser based) are there
other options than storing in BLOB like the File system?

your inputs will be valueable as always... TIA..
guru

      and we said...
I stored everything in there.

We have a single multi-terabyte instance used as Oracles single file server.
All of the documents are in there.

Blobs can be NOCACHE, if you don't want them in the buffer cache -- that is upto
you.

If the data means anything to you, has any value whatsoever, you will in fact
put it into a database where it is professionally managed, backed up,
recoverable, secure.
Review & Followup

      Rating: 5
Don't forget
      about Oracle Text  June 01, 2003
Reviewer:
      Tyler D. Muth  from Reston, VA
Oracle Text provides a lot of great search features such as themes and "gist".
Check it out at:

http://otn.oracle.com/products/text/pdf/9ir2text_features_overview.pdf
and

http://otn.oracle.com/products/text/content.html

      Rating:
      5
  June 01, 2003
Reviewer:
      A reader
Tom,

I have similar kind of question related to iFS, can you tell me some adavantages
& why should i go for it.

Thanks.

          Followup:

single file server for the entire company.  no file servers sitting under
peoples desks.  no "where the heck is that file".  totally indexed, searchable.
stored in the database -- professionally (one would hope) managed and backed up.
 content management facilities.  accessible via pretty much any network protocol
you need.  transparent to the end user....

      Rating: 5
please
      elaborate..  June 02, 2003
Reviewer:  guru
       from Mumbai, India
"I stored everything in there. " - can you elaborate on this? I can see Oracle
Intermedia at work. Are you also storing everything in LOBs?

"We have a single multi-terabyte instance used as Oracles single file server.
All of the documents are in there."
I know about this instance which hosts a lot of Oracle sites.. but I did not
understand "used as Oracles single file server." can you please elaborate on
this?

          Followup:

yes, everything is in lobs.

Oracle has a single fileserver, we consolidated from hundreds of little NT file
servers into a single fileserver for the entire company.  Last I checked in
october 2002 there was:

55,000+ internal Oracle users
10,500,000+ documents
Over 3.7 TB storage used

it'll be larger by now.

      Rating: 4
Can you
      elaborate little more ?  June 11, 2003
Reviewer:
      Randy Miller  from New York, USA
Hello Tom,
Very interesting. Can you elaborate little more how Oracle file server is
created ? Are you using Oracle file system or any other technology other than
tables in terms of rows and columns ? I am looking for any the specific product
if you are using other than only Oracle RDBMS engine.

          Followup:

it uses tables in the database.

      Rating: 5
Uploading
      Files  July 29, 2003
Reviewer:  Ray White
       from Ottawa,On,CA
Hey Tom,

I don't mean to beat a dead quadraped, but...

Using 9.2
Does Mod PLSQL support uploading a file into a BFILE directly, or do you have to
upload to a BLOB and write the blob out to a BFILE.
If so...
What do I use to write the Blob out to a BFILE?
What do I use to check the existence of a File?
I have talked to you about this b4. but just getting back to it. I am writing
the BFILE , only to have it virus scanned. The virus scan intercepts the write,
and if the file exists where I write it, it is clean... Then I can get rid of
it.

Tia
Ray

          Followup:

No, file upload to the file system does not exist in mod_plsql.

dbms_lob.fileexists checks for existence.

In 9ir2, utl_file can be used to WRITE to a raw file.
In 9ir1 and before, you would need to use a java stored procedure to do the
same.

      Rating: 5
Upload
      files  July 29, 2003
Reviewer:  Ray White
       from Ottawa,ON,CA
Right on.

Thanks alot.

July 29, 2003 Mohamed -- Thanks for the question regarding
      "Lock Table", version 7.3.0

      You Asked
Dear Tom:
i have table assume X(id number);
in forms 6i:
i wrote this code in pre-insert trigger:
/**********/
select nvl(max(id),0)+1
into   :id
from x;
/**********/
it works fine,
but sometimes concurrent user save record at the same time so one of the user
failed to complete the transaction.
so i need to lock the table x and then release the lock.

      and we said...
YOU NEED TO USE A SEQUENCE, period -- 100%, absolutely, no if and or but about
it.

Review &
      Followup

      Rating: 5
Gee
      !  July 28, 2003
Reviewer:  A reader

How can such a guy be progamming in PL/SQL ??????????????
Do companies pay fellows like these ?
Loan him Oracle for Dummies or what...

      Rating:
      2
  July 28, 2003
Reviewer:  Another
      reader
Dear "reader" allow people to learn........

Hope you were not "born experienced".

      Rating: 5
Please read the
      beginner's guide first before wasting Tom's time  July 28,
      2003
Reviewer:  A reader
Dear "Another Reader" allow people to learn........

Let them read the concepts guide before wasting Tom's time. Tom is supposed to
answer questions that one would not normally find in Oracle for Dummies.

          Followup:

well, this question with the max(id)+1 comes up over and over and over and over
actually.

I publish one every now and again just to say "please -- I beg of you -- don't
do that, use a sequence"

It is a common implementation mistake.  I would rather they asked then just use
max(id)+1.  really.

      Rating:
      2
  July 28, 2003
Reviewer:
      A reader
Dear "Reader"....
If the question would not have been "relevant", Tom would have never published
it, he would have answered it and kept quite....

      Rating:
      5
  July 28, 2003
Reviewer:
      A reader
Haha..This thread is funny!

      Rating: 1
Sequence
      Problem  July 29, 2003
Reviewer:
      A reader
But by using sequence there will be missing serials.

          Followup:

so?  there will be missing "serials" regardless.  there is no such thing as a
gap free sequence.

      Rating: 5
some10+ years
      Experienced Developers still have that nvl max plus One
      thing...  July 29, 2003
Reviewer:
      A reader
Hi Tom,

Haha !

Especially very very Experienced Developers don't want to remove that piece of
code from their code..:-)

Oh, wait a minute,
didn't i see this even on some Oracle sample schemas Examples ? ( refer to
Orders Application , where there's one trigger which inserts order numbers in
Items table  )

      Rating: 1
Reader's
      behavior  July 29, 2003
Reviewer:
      A reader
Dear Tom:
Can u prevent the bad replaies from publishing like the first reader with bad
bad beahvior,
And i am asking why he replied with this bad replay.

          Followup:

everyone is entitled to their opinion -- it is pretty anonymous here (well,
except for MY stuff of course).

as long as the language is clean, it is all instructive in one way or another.

Everyone comes in at different levels here.  We have very experienced to not so
experienced.  I think the followup by the first person was "wrong", but
instructive none the less in the other followups.

If it got nasty, I would just pull the question (i've done that twice that I can
recall)

July 29, 2003 don -- Thanks for the question regarding
      "Analitic Function ?", version 8.1.7

      You Asked
Hi Tom,

First, the data:

drop table T ;
create table T (ID varchar2(5), EFFDT date, FLAG varchar2(1));
--
insert into T values('607',to_date('2002-11-28','YYYY-MM-DD'),'Y');
insert into T values('607',to_date('2002-10-16','YYYY-MM-DD'),'N');
insert into T values('607',to_date('2002-01-01','YYYY-MM-DD'),'N');
insert into T values('607',to_date('1998-01-01','YYYY-MM-DD'),'N');
insert into T values('607',to_date('1997-01-01','YYYY-MM-DD'),'N');
insert into T values('607',to_date('2004-07-01','YYYY-MM-DD'),'Y');
insert into T values('607',to_date('2002-12-01','YYYY-MM-DD'),'Y');
insert into T values('607',to_date('2001-01-01','YYYY-MM-DD'),'Y');
insert into T values('607',to_date('2000-01-21','YYYY-MM-DD'),'Y');
insert into T values('607',to_date('1999-01-01','YYYY-MM-DD'),'Y');
insert into T values('688',to_date('2003-12-21','YYYY-MM-DD'),'Y');
insert into T values('688',to_date('2001-01-01','YYYY-MM-DD'),'Y');
insert into T values('688',to_date('2002-10-28','YYYY-MM-DD'),'N');
insert into T values('688',to_date('1999-02-02','YYYY-MM-DD'),'N');
insert into T values('558',to_date('2002-09-22','YYYY-MM-DD'),'N');
insert into T values('558',to_date('2001-03-13','YYYY-MM-DD'),'Y');
insert into T values('558',to_date('1999-04-04','YYYY-MM-DD'),'Y');
insert into T values('755',to_date('1996-09-22','YYYY-MM-DD'),'N');
insert into T values('755',to_date('1995-03-13','YYYY-MM-DD'),'N');
insert into T values('755',to_date('1999-04-04','YYYY-MM-DD'),'N');
commit;

select * from t order by ID,EFFDT desc;

ID    EFFDT      F
----- ---------- -
558   2002-09-22 N
558   2001-03-13 Y
558   1999-04-04 Y <==

607   2004-07-01 Y
607   2002-12-01 Y
607   2002-11-28 Y <==
607   2002-10-16 N
607   2002-01-01 N
607   2001-01-01 Y
607   2000-01-21 Y
607   1999-01-01 Y
607   1998-01-01 N
607   1997-01-01 N

688   2003-12-21 Y <==
688   2002-10-28 N
688   2001-01-01 Y
688   1999-02-02 N

755   1999-04-04 N
755   1996-09-22 N
755   1995-03-13 N

20 rows selected.

Question for you:

I want to have:

For each ID, the last EFFDT of the first group of 'Y'
 something like
  over (partition by ID,FLAG order by ID,EFFDT desc)

ID      EFFDT
--- ----------
558 1999-04-04
607 2002-11-28
688 2003-12-21
755 N/A         or Null or Nothing or ...

Thanks
Don
----------------
July 28, 2003

 " why is 607 2002-12-01 and not 2004-07-01? "

This is our business requirement  :

 " the last EFFDT of the first group with FLAG='Y' "
       ==========        =========================

I made 2 modifications in the data, to have :
- a "first group" with 3 rows (for ID=607)
- a "first group" with 2 rows (for ID=558)
(See the pointers.)

I know it's tricky.
I tried with 4 levels of in-line views,
  but it seems to me to be too complicated.
Therefore I trust that you, with your experience, can find a better solution.

P.S. The table has 2 milion rows and 50,000 distinct IDs.

Thanks

      and we said...
ops$tkyte@ORA920> select id, max(effdt), flag
  2    from (
  3  select id,
  4         effdt,
  5         flag,
  6    nvl(lead(flag) over (partition by id order by effdt desc),'N') next_flag
  7    from t
  8         )
  9   where flag = 'Y'
 10     and next_flag = 'N'
 11   group by id, flag
 12  /

ID    MAX(EFFDT) F
----- ---------- -
558   1999-04-04 Y
607   2002-11-28 Y
688   2003-12-21 Y

Review &
      Followup

      Rating:
      5
WOW  July 29, 2003
Reviewer:
      A reader  from Peoria, IL
Only you can do it Tom.

      Rating:
      5
amazing!  July 29, 2003
Reviewer:
      A reader
Great tom - I tried it and gave up soon - looked
at your solution.

Tried to analyze your thought process here -
very instructive!

---
ID    EFFDT      F
----- ---------- -
558   2002-09-22 N
558   2001-03-13 Y
558   1999-04-04 Y <==

607   2004-07-01 Y
607   2002-12-01 Y
607   2002-11-28 Y <==
607   2002-10-16 N
607   2002-01-01 N
607   2001-01-01 Y
607   2000-01-21 Y
607   1999-01-01 Y
607   1998-01-01 N
607   1997-01-01 N

688   2003-12-21 Y <==
688   2002-10-28 N
688   2001-01-01 Y
688   1999-02-02 N

755   1999-04-04 N
755   1996-09-22 N
755   1995-03-13 N

---

The trick was to figure out in the cases
where there are multiple groups- what is the differnce
between first group and other groups.

Well, the difference is that in the first group, the
flag right after that row would always be 'N' or null
(null if this is the only group). From there you
created another column that gave you a set of values
which could be used to filter out based on where clause
and then there was a group by to arrive at the final
solution.

Let me know if I am too far off from the way you
approached this problem...

Great thinking!!!

          Followup:

that is what i did, yes.

July 29, 2003 Richer -- Thanks for the question regarding "Bad
      performance using sys.dba_role_privs", version 8.1.7.0

      You Asked
Hi Tom,

We have a very simple query using sys.dba_role_privs and a small table (around
8000 records) and an index is being used(RPT_SQ_MATRICULE_I).  The performance
is very bad and we are struggling to understand why.

The statistics are up-to-date (sys and system tables are not analyzed).  Here's
some information.  What is wrong?

Rich

SQ90084D@> set autotrace on
SQ90084D@> set lines 50
SQ90084D@> desc rhu_repertoires;
 Name                    Null?    Type
 ----------------------- -------- ----------------
 NO_EMPLOYE              NOT NULL NUMBER(8)
 CNTRS_CODE              NOT NULL NUMBER(5)
 SQ_MATRICULE            NOT NULL VARCHAR2(7)
 NOM                     NOT NULL VARCHAR2(30)
 PRENOM                  NOT NULL VARCHAR2(30)
 TITRE                            VARCHAR2(90)
 NO_TELEPHONE                     VARCHAR2(14)
 COURRIEL                         VARCHAR2(100)
 FAX_CR                           VARCHAR2(14)
 CODE_PRVNC_INFO         NOT NULL VARCHAR2(6)
 COMMENTAIRE_EMPLY                VARCHAR2(500)
 COMMENTAIRE_DRCTN                VARCHAR2(500)
 DSTRC_CODE                       VARCHAR2(2)
 DRCTN_CODE                       VARCHAR2(3)
 GRNFN_CODE                       VARCHAR2(2)
 EMPLY_GRADE                      VARCHAR2(2)
 EMPLY_CORPS                      VARCHAR2(3)
 EMPL_FNCTN                       VARCHAR2(3)
 MATRICULE               NOT NULL NUMBER(5)
 NOM_COMPLET                      VARCHAR2(60)
 NOM_INVERSE                      VARCHAR2(60)
 ELEMENT_RCHRCH                   VARCHAR2(80)

SQ90084D@>  select count(*) from rhu_repertoires where
  2  upper(sq_matricule) not in
  3  (select grantee from sys.dba_role_privs);

  COUNT(*)
----------
      6968

Elapsed: 00:04:19.00

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=8)
   1    0   SORT (AGGREGATE)
   2    1     FILTER
   3    2       INDEX (FAST FULL SCAN) OF 'RPT_SQ_MATRICULE_I' (NON-UNIQUE)
(Cost=4 Card=3
          46 Bytes=2768)

   4    2       VIEW OF 'DBA_ROLE_PRIVS' (Cost=25981 Card=1471465
Bytes=25014905)
   5    4         SORT (GROUP BY) (Cost=25981 Card=1471465 Bytes=164804080)
   6    5           NESTED LOOPS (OUTER) (Cost=4 Card=1471465 Bytes=164804080)
   7    6             NESTED LOOPS (Cost=4 Card=1471465 Bytes=126545990)
   8    7               NESTED LOOPS (Cost=4 Card=18015 Bytes=1008840)
   9    8                 INDEX (FAST FULL SCAN) OF 'I_SYSAUTH1' (UNIQUE)
(Cost=4 Card=441
          1 Bytes=114686)

  10    8                 TABLE ACCESS (CLUSTER) OF 'USER$'
  11   10                   INDEX (UNIQUE SCAN) OF 'I_USER#' (NON-UNIQUE)
  12    7               TABLE ACCESS (CLUSTER) OF 'USER$'
  13   12                 INDEX (UNIQUE SCAN) OF 'I_USER#' (NON-UNIQUE)
  14    6             INDEX (UNIQUE SCAN) OF 'I_DEFROLE1' (UNIQUE)

Statistics
----------------------------------------------------------
          7  recursive calls
      28000  db block gets
   18989188  consistent gets
          0  physical reads
          0  redo size
        369  bytes sent via SQL*Net to client
        387  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
       7005  sorts (memory)
          0  sorts (disk)
          1  rows processed

      and we said...
grantee is nullable in dba_role_privs, hence the NOT IN will tend to full scan
it each time.

also, you are mixing analyzed and non analyzed tables -- hard to optmize

you might try an "anti join" like the second query (first just to compare)

ops$tkyte@ORA920> set autotrace on
ops$tkyte@ORA920> select count(*)
  2    from t
  3   where upper(sq_matricule) not in ( select grantee from dba_role_privs )
  4  /

  COUNT(*)
----------
     10000

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2517 Card=1 Bytes=7)
   1    0   SORT (AGGREGATE)
   2    1     FILTER
   3    2       INDEX (FAST FULL SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=4 Card=500
Bytes=3500)
   4    2       FILTER
   5    4         SORT (GROUP BY) (Cost=2513 Card=271204 Bytes=30374848)
   6    5           NESTED LOOPS (OUTER) (Cost=31 Card=271204 Bytes=30374848)
   7    6             HASH JOIN (Cost=31 Card=271204 Bytes=23323544)
   8    7               TABLE ACCESS (FULL) OF 'USER$' (Cost=11 Card=8168
Bytes=245040)
   9    7               HASH JOIN (Cost=14 Card=8168 Bytes=457408)
  10    9                 TABLE ACCESS (FULL) OF 'SYSAUTH$' (Cost=2 Card=246
Bytes=6396)
  11    9                 TABLE ACCESS (FULL) OF 'USER$' (Cost=11 Card=8168
Bytes=245040)
  12    6             INDEX (UNIQUE SCAN) OF 'I_DEFROLE1' (UNIQUE)

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

ops$tkyte@ORA920> select count(*)
  2    from t, dba_role_privs
  3   where upper(sq_matricule) = grantee(+)
  4     and grantee IS NULL
  5  /

  COUNT(*)
----------
     10000

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2542 Card=1 Bytes=24)
   1    0   SORT (AGGREGATE)
   2    1     FILTER
   3    2       HASH JOIN (OUTER)
   4    3         INDEX (FAST FULL SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=4
Card=10000 Bytes=70000)
   5    3         VIEW OF 'DBA_ROLE_PRIVS' (Cost=2513 Card=271204 Bytes=4610468)
   6    5           SORT (GROUP BY) (Cost=2513 Card=271204 Bytes=30374848)
   7    6             NESTED LOOPS (OUTER) (Cost=31 Card=271204 Bytes=30374848)
   8    7               HASH JOIN (Cost=31 Card=271204 Bytes=23323544)
   9    8                 TABLE ACCESS (FULL) OF 'USER$' (Cost=11 Card=8168
Bytes=245040)
  10    8                 HASH JOIN (Cost=14 Card=8168 Bytes=457408)
  11   10                   TABLE ACCESS (FULL) OF 'SYSAUTH$' (Cost=2 Card=246
Bytes=6396)
  12   10                   TABLE ACCESS (FULL) OF 'USER$' (Cost=11 Card=8168
Bytes=245040)
  13    7               INDEX (UNIQUE SCAN) OF 'I_DEFROLE1' (UNIQUE)

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

ops$tkyte@ORA920> set autotrace off

Review &
      Followup

      Rating: 4
RE: Bad
      performance using sys.dba_role_privs  July 28, 2003

Reviewer:  Rich  from Montreal
Hi Tom,

When you are stating that you are saying:

" also, you are mixing analyzed and non analyzed tables -- hard to optmize "

I thought this should be the way to go in ORACLE (all tables analyzed and system
objects not anlayzed). Is this true?

Would you suggest to try to tune the query with a RULE Hint?  So system an
application tables would both run in a RULE mode?

Finally, note 35272.1 suggests that "If there are any costs against the
statements then the CBO is being used and the statistics should be deleted"
since our both explanation plans uses costs, does it mean that the query is done
with choose, even for the system tables?

          Followup:

when you mixed unanalyzed and analyzed tables -- the results can be horrible.
in part, you are seeing that.  You have to be very careful.

try that NOT IN with RULE, it'll be very *bad*.

Use the anti-join i demonstrated.  Is it not working for you?

      Rating:
      5
  July 28, 2003
Reviewer:
      A reader
Tom,

I have a similar situation in our application in which i see user_role_privs
being queried and thats causing lot of consistent gets, my suggestion was to
create a table say
user_role_privs_copy so we dont have to query user_role_privs. How can i keep
user_role_privs_copy in sync with user_role_privs.

Thanks..

          Followup:

why not try tuning the real query?  Look at what I achieved above!

      Rating: 5
RE: Bad
      performance using sys.dba_role_privs  July 29, 2003

Reviewer:  Rich  from Montreal
Hi Tom,

Of course the anti-join is working fine and fast.  But the problem is that we do
have to create a LOV within Designer based on table RHU_REPERTOIRES to select
nom,prenom
and sq_matricule (rhu_repertoires) for all Oracle users (all_users) where a
specific granted_role (dba_role_privs)is not already given to the user.

So the query has to be based on a single table.  What would be our choices.
Create a view based on the anti-join query?  What do you think?

SELECT RHU_REPERTOIRES.SQ_MATRICULE SQ_MATRICULE,
       RHU_REPERTOIRES.NOM NOM,
       RHU_REPERTOIRES.PRENOM PRENOM
FROM RHU_REPERTOIRES RHU_REPERTOIRES
WHERE /* CG$LOVI_WC_START LOV_EMPLOYE_NOUV 70 */
      (upper(RHU_REPERTOIRES.SQ_MATRICULE) in
   (select username from all_users minus
   select distinct grantee from dba_role_privs
   where granted_role like 'ECR_%'))

          Followup:

why does an lov have to be based on a single table?

      Rating:
      5
  July 29, 2003
Reviewer:
      A reader
Ok, here is what is did and am still getting lot of consistent gets.Table
o_facility_role_access
 has 18 records.

SQL> desc o_facility_role_access
 Name                                                                    Null?
 Type
 -----------------------------------------------------------------------
-------- ----------------------------
 ROWADR
 ROWID
 ROLE_NAME                                                               NOT
NULL VARCHAR2(32)
 FCTY_CODE                                                               NOT
NULL VARCHAR2(32)
 SYST_CODE                                                               NOT
NULL VARCHAR2(32)
 TEXT_REMARK
 VARCHAR2(2000)

select * from o_facility_role_access a, user_role_privs u
  2  where a.role_name = u.granted_role(+)
  3  and u.granted_role is null
  4  /

18 rows selected.

Execution Plan
----------------------------------------------------------
ERROR:
ORA-01039: insufficient privileges on underlying objects of the view

SP2-0612: Error generating AUTOTRACE EXPLAIN report

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         45  consistent gets
          0  physical reads
          0  redo size
       2332  bytes sent via SQL*Net to client
        536  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
         18  rows processed

          Followup:

45 consistent gets?

for 18 rows?

"alot"

does not compute to me.

      Rating:
      5
  July 29, 2003
Reviewer:
      A reader
Tom,
I am selecting one column now and the results are the same.

select rowadr from odtkrun.o_facility_role_access a, user_role_privs u
where a.role_name = u.granted_role(+)
and u.granted_role is null
/

18 rows selected.

Execution Plan
----------------------------------------------------------
ERROR:
ORA-01039: insufficient privileges on underlying objects of the view

SP2-0612: Error generating AUTOTRACE EXPLAIN report

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         44  consistent gets
          0  physical reads
          0  redo size
       1361  bytes sent via SQL*Net to client
        536  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
         18  rows processed

          Followup:

so?  what did you expect?

      Rating:
      5
  July 29, 2003
Reviewer:
      A reader
Ok Tom,

consider thisSQL>  select * from user_role_privs;

USERNAME                       GRANTED_ROLE                   ADM DEF OS_
------------------------------ ------------------------------ --- --- ---
PUBLIC                         PLUSTRACE                      NO  YES NO
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         24  consistent gets
          0  physical reads
          0  redo size
        629  bytes sent via SQL*Net to client
        425  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
          1  rows processed

Why are the consistent gets so high ???

          Followup:

umm, look at the underlying view, it does a couple of joins.  Nothing to get
"excited" about here.

July 29, 2003 Michael -- Thanks for the question regarding
      "Analytic Function with a CONNECT BY query", version 9.2.0

      You Asked
Hi Tom,

First, the data:

drop table T ;
create table T (ID NUMBER, PID NUMBER, Task varchar2(32), Work Number);

insert into T values ( 1, 0,'Task 1             ',4);
insert into T values ( 2, 1,'    Task 2         ',1);
insert into T values ( 3, 2,'        Task 3     ',null);
insert into T values ( 4, 2,'        Task 4     ',null);
insert into T values ( 5, 2,'        Task 5     ', 5);
insert into T values ( 6, 2,'        Task 6     ',null);
insert into T values ( 7, 2,'        Task 8     ', 3);
insert into T values ( 8, 2,'        Task 9     ',null);
insert into T values ( 9, 8,'            Task 10', 2);
insert into T values (10, 8,'            Task 11', 7);
insert into T values (11, 1,'    Task 12        ',null);
insert into T values (12,11,'        Task 13    ',10);
commit;

select Task, Level, Work from T
 connect by prior id = pid
 start with pid = 0;

TASK                LEVEL WORK
------------------- ----- ----
Task 1                  1   4
    Task 2              2   1
        Task 3          3
        Task 4          3
        Task 5          3   5
        Task 6          3
        Task 8          3   3
        Task 9          3
            Task 10     4   2
            Task 11     4   7
    Task 12             2
        Task 13         3  10

Question for you:

I want to have:

For each TASK the SUM of WORK of all siblings like

TASK                LEVEL WORK SUM
------------------- ----- ---- ---
Task 1                  1   4   32 (=4+18+10)
    Task 2              2   1   18 (=1+5+3+9)
        Task 3          3
        Task 4          3
        Task 5          3   5    5
        Task 6          3
        Task 8          3   3    3
        Task 9          3        9 (=2+7)
            Task 10     4   2    2
            Task 11     4   7    7
    Task 12             2       10
        Task 13         3  10   10

I tried a lot with analytic functions, but it does not work.

Thanks,
Mike

      and we said...
ops$tkyte@ORA920> select Task, Level, Work ,
  2         (select sum(work)
  3                from t t2
  4                   start with id = t.id
  5             connect by prior id = pid ) sum_work
  6    from T
  7   connect by prior id = pid
  8    start with pid = 0;

TASK                                  LEVEL       WORK   SUM_WORK
-------------------------------- ---------- ---------- ----------
Task 1                                    1          4         32
    Task 2                                2          1         18
        Task 3                            3
        Task 4                            3
        Task 5                            3          5          5
        Task 6                            3
        Task 8                            3          3          3
        Task 9                            3                     9
            Task 10                       4          2          2
            Task 11                       4          7          7
    Task 12                               2                    10
        Task 13                           3         10         10

12 rows selected.
Review & Followup

      Rating:
      5
Cool  July 29, 2003
Reviewer:  Dilip
      Patel  from MD, USA
Tom, You excel in "Analytical" skills too ;)

July 29, 2003 -- Thanks for the question regarding "Date
      Question", version 8.1.7

      You Asked
Hi Tom,

1. Given a specific month and year, is there a function which can return the
first day of the month as well as the last day?

For example, given january 2002, returns 01-Jan-2002, 31-Jan-2002.

2.Is there a table which upon querying can return all the months of the year?
Eg: cursor c_month is select month from x?
I didnt' think so, but wanted to check with you.
In the absence of any table, how can we retrieve each month of the year into the
cursor?

Thanks,
madhavi

      and we said...
1)

ops$tkyte@ORA920> @test "january 2002"
ops$tkyte@ORA920> select to_date( '&1', 'month yyyy' ),
  2         last_day(to_date('&1', 'month yyyy'))
  3    from dual;
old   1: select to_date( '&1', 'month yyyy' ),
new   1: select to_date( 'january 2002', 'month yyyy' ),
old   2:        last_day(to_date('&1', 'month yyyy'))
new   2:        last_day(to_date('january 2002', 'month yyyy'))

TO_DATE(' LAST_DAY(
--------- ---------
01-JAN-02 31-JAN-02

2)
ops$tkyte@ORA920> select to_date( rownum, 'mm' ) from all_objects where rownum
<= 12;

TO_DATE(R
---------
01-JAN-03
01-FEB-03
01-MAR-03
01-APR-03
01-MAY-03
01-JUN-03
01-JUL-03
01-AUG-03
01-SEP-03
01-OCT-03
01-NOV-03
01-DEC-03

12 rows selected.

Review &
      Followup

      Rating: 5
Elegant
      trick!  July 29, 2003
Reviewer:  Vera
       from D.C.

      Rating: 5
Most
      Useful  July 30, 2003
Reviewer:  Bipin Ganar
       from INDIA
Hi, Tom
Thanx a Lot for giving sol'n I tried but most of the time ur busy.
Before this answer i was using add_months(last_day(sysdate),-1)+1 to get
first_day
I feel now onwards it will take less time to calculate first day

July 29, 2003 gkc -- Thanks for the question regarding "redo
      log in clob column", version 8.1.5

      You Asked
I am loading about 30 million rows into table using SQL*Loader. This table has
CLOB column (inline storage)which is NOCACHE NOLOGGING and table is also in
NOLOGGING mode ( this table also has no indexes or primary key). It is
generating lots of redo log information (about 23 GB).
Can you please tell me why it is generating so many redo log OR there is another
way to reduce amount of redo logs?

Thanks a lot for your help.

I am not using direct paht load.

      and we said...
nologging affects ONLY

o direct path loads
o insert /*+ APPEND */
o create table as select
o alter table move

-- that is, "bulk" operations.  for conventional inserts -- logging MUST, has to
be, enabled.  No if and or buts about it.
Review &
      Followup

      Rating:
      3
  July 29, 2003
Reviewer:
      A reader

July 29, 2003 Peter -- Thanks for the question regarding
      "Import ignore=y", version 8.1.6

      You Asked
Tom,

I export a table that has the following constraints:
    Primary key
    Unique key
    Check

Why does an import back into the same schema with ignore=y show errors for
unique and check constraints but does not show them for primary key constraints
that exist also?

IMP-00017: following statement failed with ORACLE error 2261:
 "ALTER TABLE "TABLE1" ADD  CONSTRAINT "TABLE1_UK" UNIQUE ("COLUMN2") USING I"
 "NDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 24576 NEXT 409600 M"
 "INEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 50 FREELISTS 1 FREELIST GROUP"
 "S 1 BUFFER_POOL DEFAULT) TABLESPACE "USERS" ENABLE "
IMP-00003: ORACLE error 2261 encountered
ORA-02261: such unique or primary key already exists in the table
IMP-00017: following statement failed with ORACLE error 2264:
 "ALTER TABLE "TABLE1" ADD  CONSTRAINT "TABLE1_CK" CHECK (column2 > '') ENABL"
 "E NOVALIDATE"
IMP-00003: ORACLE error 2264 encountered
ORA-02264: name already used by an existing constraint
About to enable constraints...
Import terminated successfully with warnings.

Just Curious .

Thanks

Peter

      and we said...
ignore=y changes the behavior of CREATE statements.  If the CREATE statement
failed - no data would be imported.  Ignore=y says "go ahead, import the data
anyway".  The failure of the ALTERS affects nothing -- the data is already
imported by then, so we just "fail" them (but continue on)
Review &
      Followup

      Rating:
      4
ignore=y  May 23, 2002
Reviewer:  X
      Wang  from NJ USA
I use ignore=y option when I import the data. It only ignore CREATE TABLE
command, but failed in CREATE TYPE command.
IMP-00015: following statement failed because the object already exists:
 "CREATE TYPE "MYTYPE_T" TIMESTAMP '2002-02-10:16:24:15' OID 'F4CDB776D6771"
 "1D3AD090008C7E98428'                                                       "
How can I ignore create type command?

Thank you

          Followup:

it didn't fail, it just keeps on going.

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop user a cascade;

User dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> grant dba to a identified by a;

Grant succeeded.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> @connect a/a
ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout off
a@ORA817DEV.US.ORACLE.COM> REM GET afiedt.buf NOLIST
a@ORA817DEV.US.ORACLE.COM> set termout on
a@ORA817DEV.US.ORACLE.COM>
a@ORA817DEV.US.ORACLE.COM>
a@ORA817DEV.US.ORACLE.COM> create table t ( x int );

Table created.

a@ORA817DEV.US.ORACLE.COM> create or replace type my_type as object ( x int );
  2  /

Type created.

a@ORA817DEV.US.ORACLE.COM> create table t2 of my_type;

Table created.

a@ORA817DEV.US.ORACLE.COM> create table t3 ( x int );

Table created.

a@ORA817DEV.US.ORACLE.COM>
a@ORA817DEV.US.ORACLE.COM> !exp userid=a/a owner=a;

Export: Release 8.1.7.3.0 - Production on Thu May 23 15:28:48 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.3.0 - Production
With the Partitioning option
JServer Release 8.1.7.3.0 - Production
Export done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set

About to export specified users ...
. exporting pre-schema procedural objects and actions
. exporting foreign function library names for user A
. exporting object type definitions for user A
About to export A's objects ...
. exporting database links
. exporting sequence numbers
. exporting cluster definitions
. about to export A's tables via Conventional Path ...
. . exporting table                              T          0 rows exported
. . exporting table                             T2          0 rows exported
. . exporting table                             T3          0 rows exported
. exporting synonyms
. exporting views
. exporting stored procedures
. exporting operators
. exporting referential integrity constraints
. exporting triggers
. exporting indextypes
. exporting bitmap, functional and extensible indexes
. exporting posttables actions
. exporting snapshots
. exporting snapshot logs
. exporting job queues
. exporting refresh groups and children
. exporting dimensions
. exporting post-schema procedural objects and actions
. exporting statistics
Export terminated successfully without warnings.

a@ORA817DEV.US.ORACLE.COM>
a@ORA817DEV.US.ORACLE.COM> drop table t2;

Table dropped.

a@ORA817DEV.US.ORACLE.COM> drop table t3;

Table dropped.

a@ORA817DEV.US.ORACLE.COM>
a@ORA817DEV.US.ORACLE.COM> !imp userid=a/a full=y ignore=y

Import: Release 8.1.7.3.0 - Production on Thu May 23 15:28:57 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.3.0 - Production
With the Partitioning option
JServer Release 8.1.7.3.0 - Production

Export file created by EXPORT:V08.01.07 via conventional path
import done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set
. importing A's objects into A
. . importing table                            "T"          0 rows imported
. . importing table                           "T2"          0 rows imported
. . importing table                           "T3"          0 rows imported
Import terminated successfully without warnings.

a@ORA817DEV.US.ORACLE.COM> set echo off

OBJECT_TYPE  OBJECT_NAME                    S TABLESPACE_NAME
------------ ------------------------------ - ------------------------------
INDEX        SYS_C007424                      SYSTEM

TABLE        T                                SYSTEM
             T2
             T3                               SYSTEM

TYPE         MY_TYPE

versions and such are always appreciated

      Rating:
      5
  May 23, 2002
Reviewer:  X Wang
       from NJ USA
Version 8.1.7.3.0
Big help. I am wondering why Oracle shows IMP error messages if it skips error
and keeps going.

          Followup:

Just letting you know -- Just because the objects have the same name doesn't
mean they are the same.  It is just informing you that "something with the name
I wanted to use already exists".

      Rating: 5
Importing just
      the data...  October 21, 2002
Reviewer:  sm
       from usa
UNIX, 8.1.7.4.0
Tom,
I want to import just the data from one database to the other..
Export is done as grants=n indexes=n rows=y constraints=n full=y triggers=n
direct=y compress=n
Then Import is done as:
ignore=y

One of the schema has some snapshots...
Whenever I do data refresh this way...
some of the snapshot refresh jobs are added too...
So, instead of 10 original jobs..there are 20..and then I have to delete the
duplicate jobs..
How I can I avoid this?

          Followup:

delete the 10 jobs before you import i guess?  there are lots of nuances like
that (you might check constraints on these tables -- if you have unnamed
constraints, you might find you have a constraint PER import you've done).

The other would be

tables=(.......)

instead of FULL=.....

to copy the data, pulling just the tables you want.

      Rating: 5
Constraints per
      import?  October 21, 2002
Reviewer:  sm
       from usa
"there are lots of nuances like
that (you might check constraints on these tables -- if you have unnamed
constraints, you might find you have a constraint PER import you've done)."

I assumed you were talking about snapshot tables here..
I didn't understand this..
Export was done as constraints = 'N'
Constraints will be disabled before import..

Could you please explain the above in quotes.

Thanks...

          Followup:

I was just stating that with imp/exp -- there are lots of nuances like that to
watch out for.  It is not a general purpose "data mover" really -- it is just
what it is.

Cannot tell you how many times I've seen "what, I've 1000 constraints on that
table -- no wonder its slow".  All cause of using exp/imp with un-named
constraints

It was just a "for example"

      Rating:
      5
Ouch!!!  October 21, 2002
Reviewer:
      sm  from usa
Might as well..just use insert statement (with append hint) then...I will have
better control....
considering my database is 2 and a half gigs only...

      Rating:
      5
  October 22, 2002
Reviewer:
      A reader
Hi Tom

Is this at sqlplus or command line you have given here

a@ORA817DEV.US.ORACLE.COM> !exp userid=a/a owner=a;
a@ORA817DEV.US.ORACLE.COM> !imp userid=a/a full=y ignore=y

If this is sqlplus can you tell me how to do it.

Samit

          Followup:

That is sqlplus, that is sqlplus on unix

you probably are on windows -- use HOST <command>

      Rating:
      3
  October 22, 2002
Reviewer:
      A reader
Hi Samit,

If you are new then this question is ok else its time for you to refresh things.

! or HOST is used to execute the O\S (Operating System) commands from SQL
Prompt. '!' Used for Unix Flavours and HOST for NT.

Ex:
SQL\> !DIR      /* On Unix */
    or
SQL\> HOST dir  /* On Win-NT */

Regards,
Sagi

      Rating:
      4
  July 25, 2003
Reviewer:
      A reader  from canada
Dear Tom,

I know you are on vacation, but hope to get a chance to answer my question.

The issue is with the Import of almost a big database. There isn't any error in
export log file though. But I faced the same error for the same table twice.
Even the error force  Shutdown to the instance and I have to restart the
database again. Then if I countinue, it will work ok. I don't have any idea why
is that? could you please shed any light as to why it generate this error?

Thanks in advance,
Arash

P.S. It's Oracle 8.1.7
imp system/manager@perd file=c:\perd.exp log=c:\perd.log fromuser=graindba
touser=graindba commit=y grants=n recalculate_statistics=Y

. . importing table        "S1_SETTLEMENT_CHARGES"    1181837 rows imported
. . importing table   "S1_SETTLEMENT_CHARGE_TABLE"    6058405 rows imported
IMP-00017: following statement failed with ORACLE error 1114:
 "CREATE UNIQUE INDEX "PK_SETTLEMENT_CHARGE_TABLE" ON "S1_SETTLEMENT_CHARGE_T"
 "ABLE" ("VEHICLE_DISPOSITION_NBR" , "CONTRACT_NBR" , "PRICING_ORDER" , "SETT"
 "LEMENT_NBR" , "CHARGE_GRADE_CODE" )  PCTFREE 10 INITRANS 2 MAXTRANS 255 STO"
 "RAGE(INITIAL 40960 FREELISTS 1 FREELIST GROUPS 1)                     NOLOG"
 "GING"
IMP-00003: ORACLE error 1114 encountered
ORA-01114: IO error writing block to file 4 (block # 524297)
ORA-27069: skgfdisp: attempt to do I/O beyond the range of the file
OSD-04026: Invalid parameter passed. (OS 524303)
ORA-01114: IO error writing block to file 4 (block # 524297)
ORA-27069: skgfdisp: attempt to do I/O beyond the range of the file
OSD-04026: Invalid parameter passed. (OS 524303)
IMP-00017: following statement failed with ORACLE error 1115:
 "ALTER TABLE "S1_SETTLEMENT_CHARGE_TABLE" ADD  CONSTRAINT "PK_SETTLEMENT_CHA"
 "RGE_TABLE" PRIMARY KEY ("VEHICLE_DISPOSITION_NBR", "CONTRACT_NBR", "PRICING"
 "_ORDER", "SETTLEMENT_NBR", "CHARGE_GRADE_CODE") USING INDEX PCTFREE 10 INIT"
 "RANS 2 MAXTRANS 255 STORAGE(INITIAL 40960 FREELISTS 1 FREELIST GROUPS 1)   "
 "                  ENABLE "
IMP-00003: ORACLE error 1115 encountered
ORA-01115: IO error reading block from file 4 (block # 494963)
ORA-01110: data file 4: 'D:\DATA\DATABASES\PERDUE\DATA_FILES\GRAIN_1.DBF'
ORA-27091: skgfqio: unable to queue I/O
OSD-04026: Invalid parameter passed. (OS 494962)
IMP-00017: following statement failed with ORACLE error 3113:
 "CREATE TABLE "S1_SETTLEMENT_CHARGE_TEMP" ("SETTLEMENT_NBR" NUMBER, "CONTRAC"
 "T_NBR" NUMBER, "PRICING_ORDER" NUMBER, "VEHICLE_DISPOSITION_NBR" NUMBER, "C"
 "HARGE_GRADE_CODE" VARCHAR2(10), "CHARGE_GRADE_PRICE" NUMBER, "CHARGE_GRADE_"
 "VALUE" NUMBER(6, 3), "SORT_ORDER" NUMBER(5, 0), "PRORATED_FLAG" CHAR(1))  P"
 "CTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 LOGGING STORAGE(INITIAL 368640"
 " NEXT 65536 MINEXTENTS 1 MAXEXTENTS 200 PCTINCREASE 1 FREELISTS 1 FREELIST "
 "GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "SMARTGRAIN""
IMP-00003: ORACLE error 3113 encountered
ORA-03113: end-of-file on communication channel
IMP-00000: Import terminated unsuccessfull

          Followup:

check the ulimit for the user that started the listener.  It is possible that
the user running the listener has a low ulimit, the create index is doing direct
writes (dbwr isn't doing the writes) and the ulimit on the user is too low.

      Rating:
      3
  July 25, 2003
Reviewer:
      A reader
Hi Tom,

 Thanks for your time. If I understood well, you mean limit in profile. I
checked that and all of them are unlimited!

I start from scratch and this time without Recalculate_Statistics=Y, and so far
so good. It has finished downloading the data and it's in Enabling Constraints
phase.

Is it possible that Recalculate_Statistics=Y cause that error? If so could you
please tell me why?

Thanks millions of tons for your time,
Arash

          Followup:

I meant ULIMIT at the os level.

stats would not do it, no

      Rating:
      3
Ulimit  July 29, 2003
Reviewer:
      A reader
Dear Tom,

  I still didn't understand what was the problem with import. You told me check
ULIMIT ! I don't know what that is at OS level ! The OS is windows 2000 and
unfortunately our network administrator is on vacation. Could you please explain
it more?

Another thing is, as I told you before, It faild at point 'A', I restart the
instance and continue the import as of point 'A'. This time it worked. Reanalyze
it. But it is very very slow. Why is that?

Thanks again for your time,
Arash

          Followup:

opps -- sorry, i assumed "unix" for some reason.   ulimit doesn't "exist" on
windows.

I'll have to refer you to support for this one, sorry.

      Rating:
      3
  July 29, 2003
Reviewer:
      A reader
Thanks

      Rating:
      3
  July 29, 2003
Reviewer:
      A reader
Dear Tom,

  I found the following in Metalink. Do you think that's the issue(Not Bigger
Than 4GB)?

fact: Oracle Server - Enterprise Edition 8
fact: MS Windows NT
fact: MS Windows 2000
symptom: Rman Backup Fails
symptom: RMAN-06003: ORACLE error from target database: %s
symptom: ORA-01114: IO error writing block to file %s (block # %s)
symptom: ORA-27069: skgfdisp: attempt to do I/O beyond the range of the file
symptom: OSD-04026: Invalid parameter passed.
symptom: Select from table fails
symptom: ORA-01115: IO error reading block from file <name> block # &
lt;num>
symptom: Stack trace contains
ksedmp ksupop opiodr opitsk opiino opiodr opidrv sou2o opimai
change: resized database datafile > 4GB
cause: <Bug:1668488>
Cannot AUTOEXTEND to 4Gb boundary - full restore required
<Bug:1612154>
Instance Crashes with ORA-27069 When a Datafile Autoextends up to
4096MB

The datafile# mentioned in error ORA-01114 is larger than 4 Gb.
 Check:
 SQL> select file#, bytes, create_bytes from v$datafile;

fix:

The datafile has to be made smaller than 4Gb, by dropping the tablespace and
recreating the content of the tablespace in datafiles smaller than 4Gb.
Avoid datafiles with autoextend enabled as they might extend to 4Gb or beyond.

 Example:
 SQL> select tablespace_name
      from dba_data_files
      where file_id=<file_number> mentioned in ORA-01114 and ORA-027059&
#059;

 SQL> select contents from dba_tablespaces where tablespace_name=
      <name_of_the_tablespace>; => temporary
 As the datafile belongs to a temporary tablespace you can drop and
    recreate it with datafiles smaller than 4Gb. Do not use AUTOEXTEND
    unless in combination with MAXSIZE.

 In other cases it might be wise to secure the data of the tablespace first
 in order to be able to recreate

          Followup:

not if it works the second time around

      Rating:
      3
Bug#1668488  July 31, 2003
Reviewer:
      A reader
Hi Tom,

Just F.Y.I, I contact Oracle Support. The following was their reply:

"
if reports more than 4Gb you might be hitting Bug#1668488.
please apply ythe patshset 8174
"
I tried bith ways. I mean:
1- Added another datafile to the tablespace
2- Installed Oracle patch 8.1.7.4

both of them fixed the problem.

Regards,
Arash

July 29, 2003 Subrata -- Thanks for the question regarding
      "Avoid runtime extent allocation", version 9i

      You Asked
Hello Tom,
         A table is going to be inserted with huge number of records. I wish to
avoid the extent allocations during runtime. How do i do this? And how do I
measure how much extent to pre-allocate?

Thanks

      and we said...
use a locally managed tablespace and the overhead of extent allocation is
neglible -- allowing you to not have to worry about this at all.

Review &
      Followup

      Rating: 5
How about temp
      tablespace?  July 29, 2003
Reviewer:  Shrek
       from DC
From previous discussion threads I have followed on this website, I have
observed that a 'true' temp tablespace allocates a single extent only once, and
keeps growing from there.
  - Now, is LMT good for a 'true' temp tablespace also?
  - Is LMT good for a 'not true' temp tablespace?

          Followup:

see Wolfgang's comment -- the temp stuff is a single SEGMENT consisting of many
extents.

LMT's are the only good types of tablespaces ;)

      Rating:
      5
  July 29, 2003
Reviewer:
      A reader

      Rating: 5
Some discussion
      about how the runtime allocations is avoided  July 29, 2003

Reviewer:  Subrata  from Ohio
Tom,
   could you discuss at broad-level, about how the runtime allocations is
avoided.

Thanks

          Followup:

we allocate them once and keep them.  we then dole them out as needed.

So, when you need temp -- we look to see if we already have some temp for you
before allocating disk for more temp space.

      Rating: 5
LMT and "true"
      temp tablespace  July 29, 2003
Reviewer:
      Wolfgang Breitling  from Calgary, Canada
It's not a single extent for a true temporary tablespace but a single temporary
segment, which may, and most likely will, consist of many extents.
And, yes, a LMT is ideal for that, just as it is for any other tablespace.


Subject | Article | Last Updated (East coast time)
Unique Key Constraint - what about null entries? | article only | Jul 30, 2003 07:36 am
public procedure gives error ORA-01031: insufficient privileges | article only | Jul 30, 2003 07:36 am
SYS.XML% Packages | article only | Jul 30, 2003 11:17 am
interview question | article only | Jul 30, 2003 11:25 am
Match records from table1 against another tables using loop and cursor | article only | Jul 30, 2003 11:28 am
Passing Array of values ( integer and double) to library. | article only | Jul 30, 2003 12:36 pm
constraints Problem | article only | Jul 30, 2003 01:56 pm
Design | article only | Jul 30, 2003 03:56 pm
Ordering Results In a Query that uses Union, Continuation... | article only | Jul 30, 2003 07:33 pm
Undo Table Space. | article only | Jul 30, 2003 07:44 pm
Closing Cursors | article only | Jul 31, 2003 07:03 am
The issue of bind variables only arises( in p/sql) when we are doing dynamic sql and not with static sql | article only | Jul 31, 2003 07:04 am
Accessing | article only | Jul 31, 2003 07:04 am
shutdown immediate hung forever and ORA-03113 | article only | Jul 31, 2003 07:21 am
How to Rollup Hierarchy from CONNECT BY into Singe Row? | article only | Jul 31, 2003 11:46 am
How to do intermedia text search for a text with '_' ? | article only | Jul 31, 2003 11:51 am
Public and private Rollback segments | article only | Jul 31, 2003 07:03 pm
Database Link and tnsnames.ora | article only | Jul 31, 2003 07:14 pm
date calculation.. | article only | Jul 31, 2003 07:21 pm
Find table being reference. | article only | Jul 31, 2003 09:27 pm
Forward Declaration in Package | article only | Jul 31, 2003 09:32 pm
close cursors | article only | Jul 31, 2003 09:36 pm
Database name and Multilevel Nested Objects | article only | Jul 31, 2003 09:57 pm
Duplicate Records In a Table | article only | Aug 01, 2003 07:37 am
tables - whats the difference between lookup and master detail. | article only | Aug 01, 2003 07:52 am
Sequences | article only | Aug 01, 2003 08:00 am
Oracle Text (Token parser) | article only | Aug 01, 2003 10:33 am
Object Privilege | article only | Aug 01, 2003 10:47 am
Mixing clusters and partitioned tables. | article only | Aug 01, 2003 10:50 am
Database Locks | article only | Aug 01, 2003 11:14 am
multi-row uniqueness | article only | Aug 01, 2003 11:15 am
Time datatype | article only | Aug 01, 2003 01:36 pm
Database Backup | article only | Aug 01, 2003 07:22 pm
why simple query is so CPU consuming | article only | Aug 01, 2003 07:34 pm
selecting the common records from the table for one of the fields | article only | Aug 02, 2003 07:57 am
Difference Between Unique Index and Primary Key Index | article only | Aug 02, 2003 08:04 am
How can I get the text of the sql that is actually run in select from view upon view upon view? | article only | Aug 02, 2003 08:16 am
level clause in connect by & rownums | article only | Aug 02, 2003 08:23 am
Difference between constraints and Indexes | article only | Aug 02, 2003 09:04 am
Bind Variable Error - ORA-01006 | article only | Aug 02, 2003 01:10 pm
Index on column of only two values : 'Y' or NULL | article only | Aug 02, 2003 03:30 pm
Partition-Wise Joins | article only | Aug 02, 2003 05:29 pm
Questions on memory usage, outlines and cursor for loop | article only | Aug 02, 2003 07:23 pm
Oracle Forms6i Error Messages | article only | Aug 03, 2003 08:41 am
Query as string | article only | Aug 03, 2003 10:41 am
Locally managed tablespace and datafiles parameters | article only | Aug 03, 2003 10:52 am
Sql | article only | Aug 03, 2003 11:40 am
Privileges and roles | article only | Aug 03, 2003 02:22 pm
Parallel Query Slaves | article only | Aug 03, 2003 02:38 pm
Search Entity | article only | Aug 03, 2003 02:41 pm
Maximum memory usage in Oracle | article only | Aug 03, 2003 03:46 pm
RAISE_APPLICATION_ERROR number range caveat? | article only | Aug 03, 2003 05:22 pm
concept guide doubts | article only | Aug 04, 2003 08:13 am
SQL problem [ORA-01722: invalid number] | article only | Aug 04, 2003 08:13 am
Is connection manager supported in Standard Edition?? | article only | Aug 04, 2003 08:26 am
MAXDATAFILES | article only | Aug 04, 2003 08:31 am
Accessing Oracle Advanced queuing system in java. | article only | Aug 04, 2003 08:42 am
UNION ALL materialized views across db links | article only | Aug 04, 2003 09:18 am
In-consistencies in oracle dictionary table | article only | Aug 04, 2003 09:30 am
view or table | article only | Aug 04, 2003 10:18 am
Row Id | article only | Aug 04, 2003 12:20 pm
Hierarchial query with cumulative factor | article only | Aug 04, 2003 02:20 pm
remote batch job get ora-02049 when update our table through db link | article only | Aug 04, 2003 05:10 pm
Dynamic SQL | article only | Aug 04, 2003 06:41 pm
using PL/SQL in the using clause of Execute Immediate. | article only | Aug 05, 2003 07:10 am
Converting Longs to LOBS | article only | Aug 05, 2003 07:17 am
Bind Variables | article only | Aug 05, 2003 07:20 am
USe of Nested Table | article only | Aug 05, 2003 07:30 am
SQL Query | article only | Aug 05, 2003 12:57 pm
running OS commands from DB - Some work others do not | article only | Aug 05, 2003 01:09 pm
BLOBS etc | article only | Aug 05, 2003 01:15 pm
Issue Frequent COMMIT Statements | article only | Aug 05, 2003 02:56 pm
Oracle For UNIX and NT | article only | Aug 05, 2003 04:03 pm
Using VARRAY Datatypes in User-Defined Aggregate Functions. | article only | Aug 05, 2003 06:54 pm
Database Locks | article only | Aug 05, 2003 08:05 pm
row(s) 1 - 75 of 75
July 30, 2003 Greg -- Thanks for the question regarding "Unique Key Constraint - what about null entries?", version 8.1.6 SE
You Asked
Tom,

I want to define a table with a composite unique key across several columns.
However, I'm worried about how it will handle possible null values in a couple
of columns.  For instance, what if I have the following in a table:

LAST   FST  SIN_NUM    EMP_NUM
-----------------------------
smith  bob  111111111  11111
jones  tim  <null>     22222

With SIN_NUM and EMP_NUM together as a composite unique key.

I know that if someone attempts to insert values(johnson, mary, 111111111,
11111) into the table, the action will be blocked.

But wht about if someone attempts to insert values(brown, guy, <null>, 22222)
into the table?  Will the nulls be considered "equal" so that this action is
blocked too?
and we said...
If AT LEAST one of the columns is not null -- it will be rejected (the nulls are
not compared).

If ALL of the columns are NULL -- it will be ACEPTED (all null records are not
equal to each other)

tkyte@TKYTE816> create table x ( a int, b int, constraint ab_unique unique(a,b)
);

Table created.

tkyte@TKYTE816> insert into x values (2,null);

1 row created.

tkyte@TKYTE816> insert into x values (2,null);
insert into x values (2,null)
*
ERROR at line 1:
ORA-00001: unique constraint (TKYTE.AB_UNIQUE) violated

tkyte@TKYTE816> insert into x values (null,2);

1 row created.

tkyte@TKYTE816> insert into x values (null,2);
insert into x values (null,2)
*
ERROR at line 1:
ORA-00001: unique constraint (TKYTE.AB_UNIQUE) violated

tkyte@TKYTE816> insert into x values ( null, null );

1 row created.

tkyte@TKYTE816> insert into x values ( null, null );

1 row created.
Review & Followup
Rating: 4
April 23, 2001
Reviewer: Greg Kliewer from Toronto, Ontario, Canada

Rating: 4
composite unique key question - very helpful advice, Tom April 23, 2001
Reviewer: Greg Kliewer from Toronto, Ontario, Canada
Null values can be a tricky part of working with relational databases.  And when
you add the subtleties of composite keys to the mix, things can get downright
confusing.  Tom's response was just what I needed.

Rating: 5
update on NULL values July 30, 2003
Reviewer: Himnish Narang from Chandigarh, India
I tried the following update :

update abc
set code1='A'
where exists (select '1' from xyz
              where xyz.col1=abc.col1);

Some of the rows in abc and xyz tables are having NULL values for col1. The
system didn't update the records for the rows where the values were NULL in both
the tables.

BUt when U tried the following update it did :

update abc
set code1='A'
where col1 is null
and exists (select '1' from xyz
              where xyz.col1 is null);

Can you please explain the reason for the same.

Thanx in advance.

Followup:
because sql follows tri-valued logic by definition:

ops$tkyte@ORA920> select * from dual where null = null;

no rows selected

ops$tkyte@ORA920> select * from dual where NOT ( null = null );

no rows selected

ops$tkyte@ORA920>

null is never equal to null
null is never NOT equal to null

it is in fact "unknown" if null = null or null != null.

Rating: 5
NULL is not a value is a state July 30, 2003
Reviewer: Jose Cleto from Dominican Republic
The mantra is:

NULL is not equal to NULL, NULL is not a value is a state
NULL is not differente from NULL, NULL is not a value is a state

Think about it like the "to be" of the 3 values logic.

NULL is a state not a value, something "IS NULL" or "IS NOT NULL"

July 30, 2003 Prince -- Thanks for the question regarding "public procedure gives error ORA-01031: insufficient privileges ", version 8.1.7.2
You Asked
Tom,

I have the following procedure.

If I create a publlic synonym to the procedure and grant execute privilege to
public and then when I tried to execute the procedure it gives me the above
error.

I can create a private synonym and execute the same. I could execute the
procedure by prefixing the ower of the procedure.

Why doesn't it work, when I make the procedure as public?

Thank you very much for all your help.

SQL> connect myowner/mypass
Connected.
SQL>
SQL> CREATE OR REPLACE  PROCEDURE TMProc  ( Var_name in varchar2, var_value in
varchar2) as
  2  begin
  3     dbms_session.set_context( 'TMCtx', var_name, var_value );
  4  end;
  5  /

Procedure created.

SQL> create or replace context TMCtx using TMproc ;

Context created.

SQL> create public synonym TMProc for myowner.tmproc ;

Synonym created.

SQL> grant execute on tmproc to public ;

Grant succeeded.

SQL> connect test_owner/test_pass
Connected.
SQL> desc tmproc
PROCEDURE tmproc
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 VAR_NAME                       VARCHAR2                IN
 VAR_VALUE                      VARCHAR2                IN

SQL> execute tmproc ('var',sysdate) ;
BEGIN tmproc ('var',sysdate) ; END;

*
ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_SESSION", line 62
ORA-06512: at "TEST_OWNER.TMPROC", line 4
ORA-06512: at line 1

SQL>  create synonym tmpro for myowner.tmproc ;

Synonym created.

SQL> exec tmpro('read', sysdate) ;

PL/SQL procedure successfully completed.

SQL> desc tmpro
PROCEDURE tmpro
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 VAR_NAME                       VARCHAR2                IN
 VAR_VALUE                      VARCHAR2                IN
and we said...
Look at the error message:

SQL> execute tmproc ('var',sysdate) ;
BEGIN tmproc ('var',sysdate) ; END;

*
ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_SESSION", line 62
ORA-06512: at "TEST_OWNER.TMPROC", line 4
ORA-06512: at line 1

test_owner apparently has ITS OWN TMPROC procedure.  when test_owner executes
tmproc, it run test_owner.tmproc -- NOT myowner.tmproc.

Hence, test_owner correctly gets the ora-1031.  test_owner is NOT allowed to set
the context.

When you run the right routine, myowner.tmproc, it works as expected.
Review & Followup
Rating: 5
How silly I was!. Sorry for the trouble. April 19, 2002
Reviewer: Prince
Sorry tom for the trouble.

I guess I wasn't reading the error message properly and wasn't thinking that the
user himself has the procedure.

Thanks.

Rating: 3
even after dropping the local procedure I recieve the error April 19, 2002
Reviewer: Prince
11:13:31 SQL> connect testusr/testpass
Connected.
11:13:41 SQL> desc tmproc
PROCEDURE tmproc
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 VAR_NAME                       VARCHAR2                IN
 VAR_VALUE                      VARCHAR2                IN

11:13:45 SQL> execute tmproc('readtime',sysdate) ;
BEGIN tmproc('readtime',sysdate) ; END;

      *
ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00201: identifier 'TMPROC' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

11:13:59 SQL> execute myowner.TMPROC('readtime',sysdate) ;

PL/SQL procedure successfully completed.

SQL> select owner, SYNONYM_name, TBLE_OWNER, TABLE_NAME
from dba_synonyms where synonym_name like 'TMPROC';

OWNER       SYNONYM_NAME     TBLE_OWNER   TABLE_NAME
----------- ---------------- ------------ -------------MYUSER      TMPROC
   MYOWNER      TMPROC

PUBLIC      TMPROC           MYOWNER      TMPROC

11:17:53 DBUSR> desc myowner.tmproc
PROCEDURE myowner.tmproc
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 VAR_NAME                       VARCHAR2                IN
 VAR_VALUE                      VARCHAR2                IN

Note: Execute privilege is granted to the public.

Followup:
You have a synonym to a synonym going there.

Your DESCRIPTION of your ENVIRONMENT doesn't match REALITY.

Read the output:

public synonym tmproc -> myowner.tmproc
myowner.tmproc -> myuser.tmproc

You told me
SQL> create public synonym TMProc for myowner.tmproc ;

Synonym created.

Well, that is just not what is in place here!!!.

Start over, clean up your example.  You have procedures in schemas that
shouldn't have them.  You have synonyms pointing to synonyms.  You don't have
the procedure in myowner like you say.  You don't have public synonyms as you
describe.

I set up a clean test case -- this works as advertised, perfectly.  It only
works when the procedure is in the right schema, the synonym points to the
procedure in the correct schema and the other schema has not obscured the
synonym!!!

Rating: 3
Let me try with a clean schema, but before that.. April 19, 2002
Reviewer: Prince
I do not have synonyms pointing to synonyms.

I have one private synonym owned by MYUSER.

I am testing as the user "TESTOWNER", who does not have any private synonyms.

OWNER       SYNONYM_NAME     TBLE_OWNER   TABLE_NAME
----------- ---------------- ------------ -------------

MYUSER      TMPROC           MYOWNER      TMPROC

PUBLIC      TMPROC           MYOWNER      TMPROC

you said, "You don't have the procedure in myowner like you say"

SQL>  select owner, object_name, object_type from dba_objects where object_name
= 'TMPROC' and owner = 'MYOWNER' ;

OWNER               OBJECT_NAME         OBJECT_TYPE
------------------- ------------------- --------------
MYOWNER             TMPROC              PROCEDURE

I may be doing something really wrong. I will cleanup everything and try again.
But before trying this I would like to find how it resolves "TMPROC". Is there
anyway to find this info? (Like the unix which command)

I tried this, "alter session set events '10046 trace name context forever, level
12';" , but don't find much info (as there is no sql to execute).

thanks,

Followup:
Look- this is your stuff I just cut and pasted here:

<quote>
11:13:45 SQL> execute tmproc('readtime',sysdate) ;
BEGIN tmproc('readtime',sysdate) ; END;

      *
ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00201: identifier 'TMPROC' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

11:13:59 SQL> execute myowner.TMPROC('readtime',sysdate) ;

PL/SQL procedure successfully completed.

SQL> select owner, SYNONYM_name, TBLE_OWNER, TABLE_NAME
from dba_synonyms where synonym_name like 'TMPROC';

OWNER       SYNONYM_NAME     TBLE_OWNER   TABLE_NAME
----------- ---------------- ------------ -------------MYUSER      TMPROC

   MYOWNER      TMPROC

PUBLIC      TMPROC           MYOWNER      TMPROC

11:17:53 DBUSR> desc myowner.tmproc

</quote>

So, looking at the results from dba_synonyms.

MYOWNER.TMPPROC -> MYUSER.TMPROC
PUBLIC.TMPROC   -> MYOWNER.TMPROC

You execute tmproc, that is really tmproc, that is really myowner.tmproc, which
is REALLY myuser.tmproc.

That is a synonym to a synonym.

Now, when you run myowner.tmproc, that is really myuser.tmproc.  myuser.tmproc
is really a procedure.

I'm not making this up!!!! it is facts based on your screen shots!

Rating: 3
I know what you are saying. April 19, 2002
Reviewer: Prince
I guess it is due to the cut and paste (and fitting to the page) problem.

I guess, it misses a "linebreak" after the "--------"

OWNER       SYNONYM_NAME     TBLE_OWNER   TABLE_NAME
----------- ---------------- ------------ -------------MYUSER      TMPROC

   MYOWNER      TMPROC

PUBLIC      TMPROC           MYOWNER      TMPROC

It should be,

OWNER       SYNONYM_NAME     TBLE_OWNER   TABLE_NAME
----------- ---------------- ------------ ------------
MYUSER      TMPROC           MYOWNER      TMPROC

PUBLIC      TMPROC           MYOWNER      TMPROC

See ,
MYUSER.TMPROC      --> MYOWNER.TMPROC
PUBLIC.TMPROC      --> MYOWNER.TMPROC

Note:

I am not saying "You are making this up". I understand that you analyze and test
the question completely before answering it.

Followup:
Run this:

drop user myowner cascade;
drop user test_owner cascade;

grant dba to myowner identified by mypass;
grant connect, resource to test_owner identified by test_pass;

connect myowner/mypass

CREATE OR REPLACE  PROCEDURE TMProc  ( Var_name in varchar2, var_value in
varchar2) as
begin
   dbms_session.set_context( 'TMCtx', var_name, var_value );
end;
/

create or replace context TMCtx using TMproc ;

drop public synonym tmproc;
create public synonym TMProc for myowner.tmproc ;

grant execute on tmproc to public ;

connect test_owner/test_pass

desc tmproc

execute tmproc ('var',sysdate) ;

you should get this

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop user myowner cascade;

User dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop user test_owner cascade;

User dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> grant dba to myowner identified by mypass;

Grant succeeded.

ops$tkyte@ORA817DEV.US.ORACLE.COM> grant connect, resource to test_owner
identified by test_pass;

Grant succeeded.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> @connect myowner/mypass
ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout off
myowner@ORA817DEV.US.ORACLE.COM> REM GET afiedt.buf NOLIST
myowner@ORA817DEV.US.ORACLE.COM> set termout on
myowner@ORA817DEV.US.ORACLE.COM>
myowner@ORA817DEV.US.ORACLE.COM> CREATE OR REPLACE  PROCEDURE TMProc  ( Var_name
in varchar2, var_value in varchar2) as
  2  begin
  3     dbms_session.set_context( 'TMCtx', var_name, var_value );
  4  end;
  5  /

Procedure created.

myowner@ORA817DEV.US.ORACLE.COM>
myowner@ORA817DEV.US.ORACLE.COM> create or replace context TMCtx using TMproc ;

Context created.

myowner@ORA817DEV.US.ORACLE.COM>
myowner@ORA817DEV.US.ORACLE.COM> drop public synonym tmproc;

Synonym dropped.

myowner@ORA817DEV.US.ORACLE.COM> create public synonym TMProc for myowner.tmproc
;

Synonym created.

myowner@ORA817DEV.US.ORACLE.COM>
myowner@ORA817DEV.US.ORACLE.COM> grant execute on tmproc to public ;

Grant succeeded.

myowner@ORA817DEV.US.ORACLE.COM>
myowner@ORA817DEV.US.ORACLE.COM> @connect test_owner/test_pass
myowner@ORA817DEV.US.ORACLE.COM> set termout off
test_owner@ORA817DEV.US.ORACLE.COM> REM GET afiedt.buf NOLIST
test_owner@ORA817DEV.US.ORACLE.COM> set termout on
test_owner@ORA817DEV.US.ORACLE.COM>
test_owner@ORA817DEV.US.ORACLE.COM> desc tmproc
PROCEDURE tmproc
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 VAR_NAME                       VARCHAR2                IN
 VAR_VALUE                      VARCHAR2                IN

test_owner@ORA817DEV.US.ORACLE.COM>
test_owner@ORA817DEV.US.ORACLE.COM> execute tmproc ('var',sysdate) ;

PL/SQL procedure successfully completed.

test_owner@ORA817DEV.US.ORACLE.COM>

showing this works as it shoud

Rating: 4
I already removed the entries and tried! It worked April 19, 2002
Reviewer: Prince
I couldn't find where and what I was doing wrong. I cleaned up all the TM*
related stuff and recreated and it worked fine.

Thanks,

Rating: 5
ORA--01031 ? April 10, 2003
Reviewer: Tatiane
Tom, I am getting this strange ora 1031 error. It does not make any sense to me.
Can you help me ? I am using 9.2.0.1

DB_EQUIP@ORADES> show user
USER is "DB_EQUIP"
DB_EQUIP@ORADES> create table t (a number);

Table created.

DB_EQUIP@ORADES> insert into t values ('10');

1 row created.

DB_EQUIP@ORADES> commit;

Commit complete.

DB_EQUIP@ORADES> grant select on t to api_equip;

Grant succeeded.

DB_EQUIP@ORADES> @connect api_equip@orades
Enter password: ***********
Connected.

API_EQUIP@ORADES> create view v as select * from db_equip.t;

View created.

API_EQUIP@ORADES> select * from v;

         A
----------
        10

API_EQUIP@ORADES> grant select on v to api_rede;

Grant succeeded.

API_EQUIP@ORADES> @connect api_rede@orades
Enter password: **********
Connected.

API_REDE@ORADES> select * from api_equip.v;
select * from api_equip.v
                        *
ERROR at line 1:
ORA-01031: insufficient privileges

API_REDE@ORADES>
API_REDE@ORADES> @connect db_equip@orades
Enter password: **********
Connected.

DB_EQUIP@ORADES> grant select on t to api_rede;

Grant succeeded.

DB_EQUIP@ORADES> @connect api_rede@orades
Enter password: **********
Connected.

API_REDE@ORADES> select * from api_equip.v;
select * from api_equip.v
                        *
ERROR at line 1:
ORA-01031: insufficient privileges

API_REDE@ORADES> select * from db_equip.t;

         A
----------
        10

Followup:
I cannot reproduce your error exactly, but I see what the problem is.

the owner of the view needs to be granted the select priv WITH GRANT OPTION if
they want to be able to convey select on that to others.  Here is an example:

ops$tkyte@ORA920> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
PL/SQL Release 9.2.0.1.0 - Production
CORE    9.2.0.1.0       Production
TNS for Solaris: Version 9.2.0.1.0 - Production
NLSRTL Version 9.2.0.1.0 - Production

ops$tkyte@ORA920> create user a identified by a;
User created.

ops$tkyte@ORA920> grant create view, create session to a;
Grant succeeded.

ops$tkyte@ORA920> create user b identified by b;
User created.

ops$tkyte@ORA920> grant create session to b;
Grant succeeded.

ops$tkyte@ORA920> create table t ( x int );
Table created.

ops$tkyte@ORA920> grant select on t to a;
Grant succeeded.

ops$tkyte@ORA920> @connect a/a

a@ORA920> create view v as select * from ops$tkyte.t;
View created.

a@ORA920> grant select on v to b;
grant select on v to b
                *
ERROR at line 1:
ORA-01720: grant option does not exist for 'OPS$TKYTE.T'

that is what you should be getting -- without a test case from you with create
users and all -- I did not reproduce your exact example but close enough

b@ORA920> @connect /

ops$tkyte@ORA920> grant select on t to a WITH GRANT OPTION;
Grant succeeded.

ops$tkyte@ORA920> @connect a/a

a@ORA920> grant select on v to b;
Grant succeeded.

now it works

a@ORA920> @connect b/b

b@ORA920> select * from a.v;
no rows selected

and B can in fact access the table.

Rating: 5
That's it ! April 11, 2003
Reviewer: Tatiane
Ok, Tom... it worked when I granted WITH GRANT OPTION.

Later I saw this posting on Metalink:

Doc ID:  Note:18622.1
...
Error:  ORA 1031 Text:   insufficient privileges
-------------------------------------------------------------------------------
Cause: An attempt was made to change the current username or password without
     the appropriate privilege. This error also occurs if attempting to
UPDATE a table with only SELECT privileges, if attempting to CONNECT
INTERNAL, or if attempting to install a database without the necessary
operating system privileges. Action: Ask the database administrator to perform
the operation or grant the         required privileges.  *** Important: The
notes below are for experienced users - See [NOTE:22080.1]   Explanation:
 You do not have permission to perform the requested action.  Diagnosis:
- What user is the user connected as ?          - What is the SQL statement
being issued ?          - Who owns the objects referenced in the statement ?
Scenarios (in no particular order):
...
2. Granting a privilege on a table/view/procedure etc. not owned by you.
You need the privilege granted to you by the owner of the object WITH
GRANT OPTION in order to be able to do this.          Having the dba role
granted to you is not sufficient.          (This restriction even applies to
user SYS - or when connected         internal.)
...

Thank you soooooooooooooooo much ! ;)

Rating: 3
Difference betweeen 8.0.6 and 8.1.7.4 June 20, 2003
Reviewer: Leonardo Zacché from Rio de Janeiro, RJ, Brazil
Has this behavior changed since Oracle 8.0.6?

Because I've got a similar trouble (not entirely tested) that seems to be the
same, but worked fine in 8.0.6.

As we're moving and application from 8.0.6 to 8.1.7.4, Could you tell me where
to get (your own site or Oracle's) the differences between this versions?

Thanks in advance,
LZ

Followup:
which behaviour?  big thread here.

Rating: 4
July 16, 2003
Reviewer: A reader
Hi Tom,

Can you help me to find out what I am doing wrong?
Public has execute privileges over sys.dbms_session.

thanks for your help.

SQL>drop user comodin cascade;

User dropped.

SQL_>grant dba to comodin identified by joker;

Grant succeeded.

SQL>conn comodin@db
Enter password: *****
Connected.
SQL>show user
USER is "COMODIN"
SQL>CREATE or replace PROCEDURE set_additional_attr  ( var_contx in varchar2,
Var_name in varchar2, var_value in varchar2) as
  2  begin
  3     sys.dbms_session.set_context( var_contx, var_name, var_value );
  4  end;
  5  /

Procedure created.

SQL>create or replace context db_context using set_additional_attr ;

Context created.

SQL>execute set_additional_attr('db_name','db_context','RRRR');
BEGIN set_additional_attr('db_name','db_context','RRRR'); END;

*
ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_SESSION", line 62
ORA-06512: at "COMODIN.SET_ADDITIONAL_ATTR", line 3
ORA-06512: at line 1

Followup:
you created a context named db_context

you are trying to set a context named "DB_NAME"

appears you have the first 2 params mixed up in your set_add... call

Rating: 5
July 16, 2003
Reviewer: A reader
Thanks Tom, you are totally right. Sorry for my mistake, I can't believe I
missed that.

SQL>execute set_additional_attr('db_context','db_name','RRRR');

PL/SQL procedure successfully completed.

Thanks for you help.

Rating: 5
can we change the database connection at runtime July 30, 2003
Reviewer: sanjay mamgain from india
is it possible to change the database connection at runtime .
i mean form are connect to system/manager and at run time i want to connect to
scott user .

Followup:
what environment are we talking here.

July 30, 2003 Pierre -- Thanks for the question regarding "SYS.XML% Packages", version 8.1.7
You Asked
I'm trying to establish XML B2B data exchange through PL\SQL using the XML% and
XSL% packages of the SYS USER. I'm able to generate dynamically my XML data and
DTD fine with the xmlgen.getdtd() and xmlgen.getxml() functions, but I can't
find the way, or proper object with which I could retreive, parse and loop
through an XML file and it's Nodes. I tried the :

xmlparser.parse(p, URL);
doc := xmlparser.getDocument(p);
nl := xmldom.getElementsByTagName(doc, '*');
   for i in 0..xmldom.getLength(nl)-1 loop
      n := xmldom.item(nl, i);
      htp.p(xmldom.getNodeName(n) || ' ');
      htp.p(xmldom.getNodeType(n) || ' ');
      htp.p(xmldom.getNodeValue(n) || ' ');
   end loop;

way using for the URL parameter my own XML generating procedure, but all I get
is the getNodeName(n) and getNodeType(n) the getNodeValue(n) is Null. How do I
retreive the .value of the Node???

I' now looking at the DBMS_XMLQUERY and DBMS_XMLSAVE Packages but I'm just
poking around....Isn't there any exemples anywhere for these new Packages?? All
I get from the Oracle Sites are Java implementation exemples....

Thanks
and we said...
I asked Sean Dillon and Sergio Leunissen for assistance on this one and they
say:

Tom,

Here's some sample PL/SQL code for this particular issue:

--------------------------------------------------------------------------
set define off
set verify off

connect sys/manager

/*  PROPERTYHELPER & SETSYSTEMPROPERTY FOR PROXY SERVER SUPPORT
PropertyHelper.java contents:
-------------------------------------
public class PropertyHelper {
  public static void setSystemProperty(String name, String value) {
    System.setProperty(name,value);
  }
}
*/

create or replace procedure setSystemProperty(name in varchar2,value in
varchar2)
as language java name
'PropertyHelper.setSystemProperty(java.lang.String,java.lang.String)';
/

create or replace function getURL(p_url in varchar2)
  return clob
is
  p utl_http.html_pieces;
  c clob;
  proxy varchar2(50) := 'www-proxy.us.oracle.com';
  port  varchar2(5)  := '80';
begin
  setSystemProperty('proxySet','true');
  setSystemProperty('proxyHost',proxy);
  setSystemProperty('proxyPort',port);
  p := utl_http.request_pieces(url => p_url, proxy => proxy||':'||port);
  dbms_lob.createtemporary(c, true, dbms_lob.session);
  for i in 1 .. p.COUNT loop
    dbms_lob.write(c,length(p(i)),dbms_lob.getlength(c)+1,p(i));
  end loop;
  return c;
end getUrl;
/

create or replace function getDocument(p_url in varchar2)
  return xmldom.DOMDocument
is
  d xmldom.DOMDocument;
  p xmlparser.Parser;
  c clob;
begin
  p := xmlparser.newParser;
  c := getURL(p_url);
  xmlparser.parseCLOB(p,c);
  d := xmlparser.getDocument(p);
  xmlparser.freeParser(p);
  return d;
exception
  when others then
    xmlparser.freeParser(p);
    raise;
end;
/

declare
  u    varchar2(100) :=
'
http://p.moreover.com/cgi-local/page?c=Tech%20stocks%20news&o=xml';
  d    xmldom.DOMDocument;
  nl   xmldom.DOMNodeList;
  n    xmldom.DOMNode;
  txt  xmldom.DOMNode;
begin
  d := getDocument(u);

  nl := xmldom.getElementsByTagName(d, 'headline_text');
  for i in 0 .. xmldom.getLength(nl)-1 loop
    n := xmldom.item(nl, i);
    dbms_output.put(xmldom.getNodeName(n) || ' ');
    dbms_output.put(xmldom.getNodeType(n) || ' ');
    txt := xmldom.getFirstChild(n);
    dbms_output.put_line(xmldom.getNodeValue(txt));
  end loop;
end;
/
--------------------------------------------------------------------------

Produces...

sys@slap.us.oracle.com> @parse_moreover

Procedure created.

Function created.

Function created.

headline_text 1 Tech stocks hurt by Cisco warning
headline_text 1 Philips, Cisco pull rug out from euro tech stocks
headline_text 1 Tech Investors Get Spring Fever
headline_text 1 European Shares Open Lower as Tech Stocks Slide Again
headline_text 1 Internet Radio. If nothing else, Internet radio is well
positioned to take over the role that short-wave radio
headline_text 1 Cisco slip-up Pushes Tech Shares Down
headline_text 1 Wall Street Technology shares close lower on profit-taking,
chip downgrade
headline_text 1 BET.com Acquires 360HIPHOP
headline_text 1 AOL Time Warner Earnings May Brighten Tech Market
headline_text 1 London stocks lower on tech firm warnings
headline_text 1 European techs fall on more profit warnings
headline_text 1 OPENING BELL: Tech Warnings to Weigh On Market
headline_text 1 Markets mixed in thin trading, tech stocks dragged down
headline_text 1 Slump in demand hits Vitesse
headline_text 1 The Night Watch: Cisco Leads Big Name Tech Stocks Lower
headline_text 1 Tuning Out
headline_text 1 Value Managers Still Cutting Tech Stakes
headline_text 1 Tech Stocks Stumble, Dow Ekes Out Small Gain
headline_text 1 Here comes the flood: Broadcom and other tech stocks slow
the Nasdaq. Read todays market and message board wra
headline_text 1 Intel Leads Tech Shares Lower After Analyst Warning

PL/SQL procedure successfully completed.

Sergio's right in his assessment of the situation. The code sample is
missing a call to xmldom.getFirstChild(xmldom.DOMNode), which would return
the text entity inside the node the person is referencing. So he should be
able to add:

xmlparser.parse(p, URL);
doc := xmlparser.getDocument(p);
nl := xmldom.getElementsByTagName(doc, '*');
   for i in 0..xmldom.getLength(nl)-1 loop
      n := xmldom.item(nl, i);
      htp.p(xmldom.getNodeName(n) || ' ');
      htp.p(xmldom.getNodeType(n) || ' ');
      htp.p(xmldom.getNodeValue(xmldom.getFirstChild(n)) || ' ');
   end loop;

and be all set.

Regards,
Review & Followup
Rating: 5
SYS.XML% Packages April 17, 2001
Reviewer: Pierre Garon from Quebec, Canada
I thank you very much for your response! It was exactly what I needed and the
very short delay within which you replied is VERY IMPRESSIVE!!! It was my first
time Submitting a New Question, having entered unsuccessfully responded tars on
technet before, and let me tell you I'll be back! Thank you very much!
Versalys/Telus, my employer, was granted it's Partnership recently, and the
whole development team will be, from now on, forwarded to your site.

Rating: 4
More XML! April 09, 2002
Reviewer: Joshua Perry from Las Vegas
This is very helpful...
     I come from MSSQL world and find that information like this is not so
common in the Oracle World!  What would make this info. more useful to me is if
someone could tell me how to use the selectSingleNode function with XPath like
queries to get the Child Text of a segment, within PL/SQL.  (e.g. Given many
name segments in a doc I would like to retrieve all the say, first, last and
middle name values from that segment, such as 'DOC/NAME/FIRST', 'DOC/NAME/LAST',
etc.).  I know how to do this with XMLType, but not with xmlDOM.

Otherwise, this is a great site for Oracle info. and I look forward to exploring
it in depth.

Thanks,
Josh Perry
Systems Research & Development

Followup:
I asked Sean Dillon, our local XML guru, to take a look at this and here's what
he had to say:
-----------------------------------------------------

Josh,

Thanks for the follow-up.  It's nice to see people from the MSSQL world coming
on over to the good side ;-).  Here's a sample class using the Oracle XDK
classes and w3c classes for you to play with.  There's a really good book on the
market that talks about processing XML with Java by Steve Muench called
"Building Oracle XML Applications" that I'd recommend to anybody trying to use
Oracle and XML.  On to the class:

==============================================
import java.sql.*;
import oracle.xml.sql.query.OracleXMLQuery;
import oracle.xml.parser.v2.*;
import org.w3c.dom.*;

public class XPathSearch  {
  public static void main(String[] args) throws Exception {
    XPathSearch xsearch = new XPathSearch();
    String sql = "select * from emp";
    String xpath = "/ROWSET/ROW/DEPTNO/text()";
    String results = null;

    System.out.println( "SQL Statement: " + sql );
    System.out.println( "XPath Expression: " + xpath );
    xsearch.search( sql, xpath );
  }

  public void search( String sql, String xpath ) throws Exception {
    XMLDocument xml = getQueryAsXML( sql );
    String results = null;
    if (xpath.equals("/")) {
      xml.print(System.out);
    }

    NodeList nl = null;
    try {
      nl = xml.selectNodes( xpath );
    } catch( Exception e ) {
      e.printStackTrace();
      System.exit( 1 );
    }
    int found = nl.getLength();
    if ( found > 0 ) {
      for ( int z = 0; z < found; z++ ) {
        XMLNode curNode = ( XMLNode )nl.item( z );
        curNode.print( System.out );
        int curNodeType = curNode.getNodeType();
        if (curNodeType == Node.ATTRIBUTE_NODE ||
            curNodeType == Node.TEXT_NODE ||
            curNodeType == Node.CDATA_SECTION_NODE ) {
          System.out.println( "\n" );
        }
      }
    } else {
      System.out.println ( "XPathSearch: No matches for " + xpath );
    }
  }

  XMLDocument getQueryAsXML(String sql)
    throws Exception {
    Connection conn = getConnection();
    OracleXMLQuery q = new OracleXMLQuery( conn, sql );
    return (XMLDocument)q.getXMLDOM();
  }

  Connection getConnection() throws SQLException {
    DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
    Connection conn = DriverManager.getConnection(
      "jdbc:oracle:thin:@slaphappy.us.oracle.com:1521:SLAPDB", "scott", "tiger"
);
    conn.setAutoCommit(true);
    return conn;
  }

}
========================================

When run, this class connects to my database, uses the Oracle XDK to generate an
XML object that is all the records in the EMP table (ie, "select * from emp",
and uses XPATH to get data from the document.  This particular XPATH expression
yields the following results:

SQL Statement: select * from emp
XPath Expression: /ROWSET/ROW/DEPTNO/text()
20
30
30
20
30
30
10
20
10
30
20
30
20
10

We could also use the following XPATH expression to get different results:

SQL Statement: select * from emp
XPath Expression: /ROWSET/ROW[1]/*
<EMPNO>7369</EMPNO>
<ENAME>SMITH</ENAME>
<JOB>CLERK</JOB>
<MGR>7902</MGR>
<HIREDATE>12/17/1980 0:0:0</HIREDATE>
<SAL>800</SAL>
<DEPTNO>20</DEPTNO>

Hope that helps!

_Sean_

Rating: 2
Thanks July 29, 2003
Reviewer: Sonika from India
Hi Tom,
I am trying to use xml.tranform(xsl)in PL/SQL for generating HTML from xml and
xsl.
My xsl has the following javascript written in <script> tag

<script>
alert('This is test');
</script>

The transform method is converting single quote (') in the alert statement to
&quot; in the final output html.

I don't want &quot; in my html, I want a single quote (') as such.

I tried writing <xsl:output cdata-section-elements="script" /> in the xsl so
that the <script> tag is not transformed and returned as such like normal text
but its not working.

Due to this, I am stuck. Please help.

Thanks
Sonika

Followup:
' is not HTML, &quot is HTML.

Rating: 1
Not satisfied July 30, 2003
Reviewer: Sonika from India
Hi Tom,
I know that &quot; is HTML but it is creating problem for me.
Please find below the HTML which I get after transformation. You will see that
(') in <script> tag has been changed to &quot;.

When I see this html on IE browser, I get an error because &quot;Category&quot;
is incorrect in javascript. It should be 'Category'.
This is reason why I am asking a way for not transforming some portion of my
xsl.

<HTML>
   <HEAD>

   <script>
 function  validate(formobj)
 {
    if (!(validate_col(&quot;Category&quot;,formobj.category))) return false
  }
</script>

</HEAD>
   <BODY>
      <table>
         <tr>
            <td><label>Name</label><input type="text" name="field1"
value="Sonia"></td>
            <td><label>Study&nbsp;Number</label><input type="text" name="field2"
value="Demo Soniastudy"></td>
         </tr>
      </table>
   </BODY>
</HTML>

Thanks
Sonika

Followup:
Hi Sonika, Sean here...

How did you go about transforming your XML?  Can you post to me the code that
transforms your XML into HTML?  If you're using a "helper" package such as
something from SMeunch's book, let me know.  Some transformers can be told not
to perform this encoding, it'll help me track down an accurate answer for you if
I know how you're going about your transform.  Thanks.

Rating: 2
Thanks July 31, 2003
Reviewer: Sonika from India
Hi Sean,
I used the xmltype.transform(xsl) function in my database function. I am using
Oracle 9i Release 2.

This is my db function:

FUNCTION F_GENERATE_HTML() RETURN CLOB
AS
   /** Returns: form html as clob
   **/
  v_xmldata    XMLType;
  v_xsldata    XMLType;
  v_html       XMLType;
  v_xslclob    CLOB;
  v_xmlclob    CLOB;
  BEGIN
        select  form_xsl, form_xml
         into v_xslclob, v_xmldata
         from  test;
       -- Since XMLType.transform() method takes an XSL stylesheet as XMLType
instance,
      -- use the XMLType.createXML method to convert the XSL content received as
CLOB
      -- into an XMLType instance.
      v_xsldata := XMLType.createXML(v_xslclob);

       -- Use the XMLtype.transform() function to get the transformed XML
instance.
      -- This function applies the stylesheet to the XML document and returns a
transformed
       -- XML instance.
      v_html := v_xmldata.transform(v_xsldata);

       -- Return the transformed XML instance as a CLOB value.

       RETURN v_html.getClobVal();

  EXCEPTION  WHEN OTHERS THEN
       raise_application_error (-20102, 'Exception occurred in
F_GENERATEFORM_HTML :'||SQLERRM);
  END ; --end of f_form_html

This is the sample XSL stored in test table as clob

<?xml version="1.0" ?>
<xsl:stylesheet version="1.0"
 xmlns:xsl="
http://www.w3.org/1999/XSL/Transform
  xmlns:xdb="
http://xmlns.oracle.com/xdb
  xmlns:xsi="
">http://www.w3.org/2001/XMLSchema-instance">
  <xsl:template match="/">
  <HTML>
  <HEAD>

  <script>
  function validate()
  {
   alert('test message');
  }
  </script>

   </HEAD>
   <BODY>
   <xsl:apply-templates select="rowset"/>
   </BODY>
    </HTML>
    </xsl:template>
    <xsl:template match="rowset">
    <Form name="form1" method="post" action=""  onSubmit="return
validate(document.form1);">
<P> Testing </P>
 </Form>
 </xsl:template>
 </xsl:stylesheet>

and this is the sample XML stored in test table in XMLTYPE column

<rowset>
<ld144_8 name = "Book Name" browser = "0"></ld144_8>
<ld145_9 name = "Quantity" browser = "1">1</ld145_9>
</rowset>

Please let me know if anything else is required from my end.
Thanks
Sonika

Rating: 2
Thanks July 31, 2003
Reviewer: Sonika from India
Hi Sean,
I used the xmltype.transform(xsl) function in my database function. I am using
Oracle 9i Release 2.

This is my db function:

FUNCTION F_GENERATE_HTML() RETURN CLOB
AS
   /** Returns: form html as clob
   **/
  v_xmldata    XMLType;
  v_xsldata    XMLType;
  v_html       XMLType;
  v_xslclob    CLOB;
  v_xmlclob    CLOB;
  BEGIN
        select  form_xsl, form_xml
         into v_xslclob, v_xmldata
         from  test;
       -- Since XMLType.transform() method takes an XSL stylesheet as XMLType
instance,
      -- use the XMLType.createXML method to convert the XSL content received as
CLOB
      -- into an XMLType instance.
      v_xsldata := XMLType.createXML(v_xslclob);

       -- Use the XMLtype.transform() function to get the transformed XML
instance.
      -- This function applies the stylesheet to the XML document and returns a
transformed
       -- XML instance.
      v_html := v_xmldata.transform(v_xsldata);

       -- Return the transformed XML instance as a CLOB value.

       RETURN v_html.getClobVal();

  EXCEPTION  WHEN OTHERS THEN
       raise_application_error (-20102, 'Exception occurred in
F_GENERATEFORM_HTML :'||SQLERRM);
  END ; --end of f_form_html

This is the sample XSL stored in test table as clob

<?xml version="1.0" ?>
<xsl:stylesheet version="1.0"
 xmlns:xsl="
http://www.w3.org/1999/XSL/Transform
  xmlns:xdb="
http://xmlns.oracle.com/xdb
  xmlns:xsi="
">http://www.w3.org/2001/XMLSchema-instance">
  <xsl:template match="/">
  <HTML>
  <HEAD>

  <script>
  function validate()
  {
   alert('test message');
  }
  </script>

   </HEAD>
   <BODY>
   <xsl:apply-templates select="rowset"/>
   </BODY>
    </HTML>
    </xsl:template>
    <xsl:template match="rowset">
    <Form name="form1" method="post" action=""  onSubmit="return
validate(document.form1);">
<P> Testing </P>
 </Form>
 </xsl:template>
 </xsl:stylesheet>

and this is the sample XML stored in test table in XMLTYPE column

<rowset>
<ld144_8 name = "Book Name" browser = "0"></ld144_8>
<ld145_9 name = "Quantity" browser = "1">1</ld145_9>
</rowset>

Please let me know if anything else is required from my end.
Thanks
Sonika

Rating: 1
Sonika again July 31, 2003
Reviewer: Sonika from India
Hi Sean

I found this from Oracle. I think its a version problem at my end due to which
cdata is not working.

this is what Oracle says -
The apostrophe and ampersand is not displayed correctly on 9.2.0.1 version.
There is a known Bug 2879684 on this problem, this is fixed in 9.2.0.2 and above
versions.

Thanks for your help and time.
Sonika

July 30, 2003 nag -- Thanks for the question regarding "interview question", version 8.1.7
You Asked
One of my friends was asked the following questions in the interview.

1.What is a ref cursor? an example for ref cursor?
Can you explain it briefly, and let us know how doesit differ from a regular
cursor.

2.How can you hide your source code from others to view it ?

3.How can you avoid dead lock ?
I know what is a dead lock, but how do we prevent the users from getting into a
dead lock?

4.How does the command 'case' differ from 'decode', give an example?

Thanks
and we said...
1)
http://osi.oracle.com/~tkyte/ResultSets/index.html
2) wrap it.
http://technet.oracle.com/doc/oracle8i_816/appdev.816/a77069/c_wrap.htm#1859
3) this is a very long answer -- basically tho:

a) make sure you access and lock resources in a predicable manner.  Lock a row
in EMP and then lock a row in DEPT.  If you have two transactions that do this:

session1                   session2
update emp
set ename='x'
where empno = 5;

                           update dept set dname = 'y'
                             where deptno = 10;

update dept
set dname = 'y'
where deptno = 10;
                          update emp set ename = 'x'
                            where empno = 5;

they will deadlock.  if you always go after EMP and then DEPT -- that won't
happen.

b) index your foreign keys to avoid an issue spelt out by :
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:292016138754

4) case accepts complex expressions like an if/then/elsif block of code. decode
is simple equality like:
   if ( A = val ) then return val
   elsif ( A = other_val ) then return val2
   ....

see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:263815366705
for an example.
Review & Followup
Rating: 4
How to unwrap? July 29, 2003
Reviewer: prudent from CA
Tom

How to unwrap the wrapped code.

Thanks

Followup:
you do not.

it would sort of defeat the entire purpose no?

Rating: 1
Wrapping is not success full... July 30, 2003
Reviewer: Riaz Shahid from PRAL, Lahore, Islamabad
Hello Tom !

I have a file c:\aa.txt having a trigger source code and tried to wrap it.
aa.txt has the following code:

CREATE OR REPLACE TRIGGER "CR"."AFT_DEL" AFTER DELETE
    ON "SUSPENDED_UNITS"
    FOR EACH ROW BEGIN
insert into suspended_units_hist
(
ST_REG_NO              ,
SUSPENDED_NAME         ,
SUSPENDED_DATE         ,
ADDITION_REMARKS       ,
DELETION_REMARKS       ,
UPDATION_UID           ,
UPDATION_DATE          ,
DATA_DATE
)
values
(:old.ST_REG_NO              ,
:old.SUSPENDED_NAME         ,
:old.SUSPENDED_DATE         ,
:old.ADDITION_REMARKS       ,
:old.DELETION_REMARKS       ,
user           ,
sysdate          ,
:old.DATA_DATE
);

END;

I then wrapped it using:

C:\>wrap iname=c:\aa.txt

PL/SQL Wrapper: Release 8.1.7.2.0 - Production on Wed Jul 30 19:56:27 2003

Copyright (c) Oracle Corporation 1993, 2000.  All Rights Reserved.

Processing c:\aa.txt to aa.plb

BUT ooooooooops.....the aa.plb file has the code:

CREATE OR REPLACE TRIGGER "CR"."AFT_DEL" AFTER DELETE
    ON "SUSPENDED_UNITS"
    FOR EACH ROW BEGIN
insert into suspended_units_hist
(
ST_REG_NO              ,
SUSPENDED_NAME         ,
SUSPENDED_DATE         ,
ADDITION_REMARKS       ,
DELETION_REMARKS       ,
UPDATION_UID           ,
UPDATION_DATE          ,
DATA_DATE
)
values
(:old.ST_REG_NO              ,
:old.SUSPENDED_NAME         ,
:old.SUSPENDED_DATE         ,
:old.ADDITION_REMARKS       ,
:old.DELETION_REMARKS       ,
user           ,
sysdate          ,
:old.DATA_DATE
);
END;

Is there something wrong ???

Followup:
yes, you can only wrap procedures, functions and packages.

you want to put SQL in triggers in a packaged procedure anyway for performance
reasons so -- suggest you move all code out of trigger body into packaged
procedure for that reason and then you can wrap anything you need.

July 30, 2003 Sam -- Thanks for the question regarding "Match records from table1 against another tables using loop and cursor", version 9.2.1
You Asked
Hi Tom, and thank for taking my question and for your email:
I want to ferform loop in table2 to find out if a record(like customerNo) is
exists on table1(which is the main table in our
database).
 if the record or the match are found from table 1, then update the raw in table
2(e.x. create new field called "Result" )with
a constant variable like "A". and if the record dose not exist mark the raw with
"B" in the result column.

Now to match the record from table 1 and table 2, I should
match them by
1-CustomerNo;
   if no match or customerNo not exist in table 1
   then I match with
2-(last name and  first name and zip code and ssn#).
After that the loop will go to second row from table 2 and do
the same match from table 1.

p.s. I have around 10 tables to do the same thing(match table 3 against
table1(the main table). the table 4...10 against the original table 1.

I hope that I expressed my question in a way I can get your
advice on how to construct the loop and open teh cursors.
Thanks in advance.
and we said...
update -- no code needed:

ops$tkyte@ORA920> create table t1 ( cno int, lname varchar2(5), fname
varchar2(5), zip int, ssn# int, result varchar2(1) );

Table created.

ops$tkyte@ORA920> create table t2 ( cno int, lname varchar2(5), fname
varchar2(5), zip int, ssn# int );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t1 (cno, lname, fname, zip, ssn# ) values ( 1,
'x', 'y', 11, 111 );

1 row created.

ops$tkyte@ORA920> insert into t1 (cno, lname, fname, zip, ssn# ) values ( 2,
'a', 'b', 22, 222 );

1 row created.

ops$tkyte@ORA920> insert into t1 (cno, lname, fname, zip, ssn# ) values ( 3,
'a', 'b', 33, 333 );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t2 (cno, lname, fname, zip, ssn# ) values ( 1,
'x', 'y', 11, 111 );

1 row created.

ops$tkyte@ORA920> insert into t2 (cno, lname, fname, zip, ssn# ) values ( null,
'a', 'b', 22, 222 );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t1;

       CNO LNAME FNAME        ZIP       SSN# R
---------- ----- ----- ---------- ---------- -
         1 x     y             11        111
         2 a     b             22        222
         3 a     b             33        333

ops$tkyte@ORA920> select * from t2;

       CNO LNAME FNAME        ZIP       SSN#
---------- ----- ----- ---------- ----------
         1 x     y             11        111
           a     b             22        222

ops$tkyte@ORA920> update t1
  2    set result = (select decode( count(*), 1, 'A', 'B' )
  3                   from dual
  4                  where exists ( select null
  5                                   from t2
  6                                  where t2.cno = t1.cno
  7                                 union all
  8                                 select null
  9                                   from t2
 10                                   where t2.lname = t1.lname
 11                                   and t2.fname = t1.fname
 12                                   and t2.zip = t1.zip
 13                                   and t2.ssn# = t1.ssn# ) );

3 rows updated.

ops$tkyte@ORA920> select * from t1;

       CNO LNAME FNAME        ZIP       SSN# R
---------- ----- ----- ---------- ---------- -
         1 x     y             11        111 A
         2 a     b             22        222 A
         3 a     b             33        333 B

ops$tkyte@ORA920>
Review & Followup
Rating: 5
July 28, 2003
Reviewer: Subramanian from India
excellent as usual

Rating: 5
July 29, 2003
Reviewer: Raghavendra M.K. from Bangalore, INDIA,
Superb.

TOM,

  Which one is fast..in the below queries.  I feel integer operation is faster
and hence 2nd one is fast.

1) SELECT ....
    WHERE exists(
                 SELECT null ....
                 )

2) SELECT ....
      WHERE exists ( SELECT 1 ....
                    )
3) SELECT ...
      WHERE exists( SELECT * ....
                   )

Thanks in advance.
Raghu

Followup:
don't "feel"

prove.

They are all the same.  a where exists never actually selects any data.

only A) makes "sense".  You don't care what you are selecting, you are just
checking for existence.

Rating: 5
Match records from table1 against another tab July 30, 2003
Reviewer: Sam from USA
Hi Tom,
Thanks for your respond to my question, can you please review my code where it
failed to run:

T1 Fields: lname, fname, ssn, line, FAMILYID( the FAMILYID  is thefield tobe
updated from T2 FAMILY_ID).
T2 Fields are: Family_id, lname, fname, ssn, line_number.

UPDATE T1
set familyid = select decode(count(*),1, FAMILY_ID,0)
           from dual
           where exists(select null
                        from T2
                        where T2.SSN = T1.SSN and
                        T2.LINE_NUMBER =T1.LINE
                     union all
                    select null
                   from T2
                   where t2.lname = T1.Lname
                   and T2.FNAME=T1.FIRST_
                   and T2.LINE_NUMBER = T1.LINE));

Please advise ON changing "A" to be "Family_id number" in
(DECODE(COUNT(*),1,'a','b')
You expertise is needed.
Thanks,

Followup:
I had a side discussion with sam and it turns out his requirement was very much
different from the original stated goal.

He wanted to set familyid in T2 to

o t2.familyid from the first query if it existed
o t2.familyid from the second query if the first did not exist
o zero if neither returned a result

so..

in this example RESULT is your family id, capital Z represents your 0:

ops$tkyte@ORA920> create table t1 ( cno int, lname varchar2(5), fname
varchar2(5), zip int, ssn# int, result varchar2(1) );

Table created.

ops$tkyte@ORA920> create table t2 ( cno int, lname varchar2(5), fname
varchar2(5), zip int, ssn# int, result varchar2(1) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index t2_idx_1 on t2(cno,result);

Index created.

ops$tkyte@ORA920> create index t2_idx_2 on t2(lname,fname,zip,ssn#,result);

Index created.

.... some inserts....

ops$tkyte@ORA920> select * from t1;

       CNO LNAME FNAME        ZIP       SSN# R
---------- ----- ----- ---------- ---------- -
         1 x     y             11        111 z
         2 a     b             22        222 z
         3 a     b             33        333 z

ops$tkyte@ORA920> select * from t2;

       CNO LNAME FNAME        ZIP       SSN# R
---------- ----- ----- ---------- ---------- -
         1 x     y             11        111 x
           a     b             22        222 y

ops$tkyte@ORA920>
ops$tkyte@ORA920> update t1
  2    set result = decode( ( select result
  3                             from t2
  4                            where t2.cno = t1.cno ), NULL,
  5                          decode( (select result
  6                                     from t2
  7                                    where t2.lname = t1.lname
  8                                      and t2.fname = t1.fname
  9                                      and t2.zip = t1.zip
 10                                      and t2.ssn# = t1.ssn# ), NULL, 'Z',
 11                                   ( select result
 12                                       from t2
 13                                      where t2.lname = t1.lname
 14                                        and t2.fname = t1.fname
 15                                        and t2.zip = t1.zip
 16                                        and t2.ssn# = t1.ssn# ) ),
 17                           ( select result
 18                               from t2
 19                              where t2.cno = t1.cno ) );

3 rows updated.

ops$tkyte@ORA920> select * from t1;

       CNO LNAME FNAME        ZIP       SSN# R
---------- ----- ----- ---------- ---------- -
         1 x     y             11        111 x
         2 a     b             22        222 y
         3 a     b             33        333 Z

July 30, 2003 jairam -- Thanks for the question regarding "Passing Array of values ( integer and double) to library.", version 8.1.6
You Asked
Hi!
 I have defined a C shared library on Solaris. One of the
functions in the library has the prototype shown below.

double getCorrCoeff(int iNoOfObs, int iRating[], double
dReturn[]);

I need to be able to call this function from pl/sql
I tried creating the following pl/sql interface to the library.
create or replace library analystIC_lib
as '/export/home/jkuppusw/analystIC/c/libcorr.so';
/
create or replace package analystIC_pkg
as
    Type IntegerTabType is table of pls_integer index by
binary_integer;
    Type NumberTabType is table of double precision index by
binary_integer;

    function getCorrCoeff( a binary_integer, tRating
IntegerTabType, tReturn NumberTabType )
        return number;
end;
/
create or replace package body analystIC_pkg
is
    function getCorrCoeff( a binary_integer, tRating
IntegerTabType, tReturn NumberTabType )
    return number
    as language c
    library analystIC_lib
    name "getCorrCoeff";
end;
/
Every thing compiled fine but then i got this runtime error.
ORA-28577: argument 2 of external procedure getCorrCoeff has
unsupported
datatype
ORA-06512: at "IDB.ANALYSTIC_PKG", line 0
ORA-06512: at line 17
On checking the document i found that one of the limitations is
passing plsql collections to an external routine.
Given the scenario i have described is there any workaround to
this limitation.
Any help in this regard would be much appreciated. Thanks in
advance
Jairam
and we said...
You'll need to pass a collection.  It will be a collection of
Oracle Numbers which you can convert into C doubles. This means
you will need to wrapper your existing C function:

double getCorrCoeff(int iNoOfObs, int iRating[], double
dReturn[]);

with another that converts the Oracle number type into the C
double type.  This new function can just call getCorrCoeff to do
the work.

You'll start by defining a collection like this:

create or replace type numArray as table of number
/

And then you can have a PLSQL wrapper that looks like:

    procedure pass( p_in   in  numArray,
                    p_out  out numArray )
    as
    language C
    name "pass_numArray"
    library demoPassing
    with context
    parameters ( CONTEXT,
                 p_in  OCICOLL,
                 p_in  INDICATOR short,
                 p_out OCICOLL,
                 p_out INDICATOR short );

     -- void pass_numArray
     --   (
     --  OCIExtProcContext  *,  /*   1 : With-Context  */
     --            OCIColl  *,  /*   2 : P_IN  */
     --              short   ,  /*   3 : P_IN (Indicator) */
     --            OCIColl **,  /*   4 : P_OUT  */
     --              short  *   /*   5 : P_OUT (Indicator) */
     --   );

Here, I'm passing an array in and getting an array out to show
how to do and map the IN / OUT types.  The C function that
prints this out and copies the input array to the output array
is:

#ifdef WIN_NT
_declspec (dllexport)
#endif
void pass_numArray
 ( OCIExtProcContext * ctx        /* CONTEXT */,
   OCIColl *           p_in       /* OCICOL  */,
   short               p_in_i     /* INDICATOR short */,
   OCIColl **          p_out      /* OCICOL  */,
   short *             p_out_i    /* INDICATOR short */
 )
{
ub4        arraySize;
double     tmp_dbl;
boolean    exists;
OCINumber *ocinum;
int        i;
myCtxStruct*myCtx;

    if ( (myCtx = init( ctx )) == NULL ) return;
    debugf( myCtx,  "Enter Pass numArray" );

  /*
   * In this routine, passing a NULL array as input is an error,
   * raise the error back to the calling routine
   */
    if ( p_in_i == OCI_IND_NULL )
    {
        raise_application_error( myCtx, ERROR_ARRAY_NULL,
                                       "Input array was NULL" );
    }
    else
/*
* There are a series of OCIColl* functions that operate on these
* array types.  This one tells us the number of elements in the
* array
*/
    if ( OCICollSize( myCtx->envhp, myCtx->errhp,
                      p_in, &arraySize ) != OCI_SUCCESS )
    {
        raise_application_error(myCtx,ERROR_OCI_ERROR,
                                "%s",lastOciError(myCtx));
    }
       else
    {
        debugf( myCtx,  "IN Array is %d elements long",
                arraySize );

 /*
  * Now, lets loop over the array getting each value and
  * printing it out, then add that element to the output array
  * to be returned to the caller
  */
        for( i = 0; i < arraySize; i++ )
        {
            if (OCICollGetElem( myCtx->envhp, myCtx->errhp,
                                p_in, i,
                                &exists, (dvoid*)&ocinum, 0 )
                                                != OCI_SUCCESS )
            {
                raise_application_error(myCtx,ERROR_OCI_ERROR,
                                        "%s",
                                       lastOciError(myCtx));
                break;
            }
            if (OCINumberToReal( myCtx->errhp, ocinum,
                                 sizeof(tmp_dbl), &tmp_dbl ) !=
                                                    OCI_SUCCESS
)
            {
                raise_application_error(myCtx,ERROR_OCI_ERROR,
                                        "%s",
                                        lastOciError(myCtx));
                break;
            }
            debugf( myCtx,  "p_in[%d] = %g", i, tmp_dbl );
            if ( OCICollAppend( myCtx->envhp, myCtx->errhp,
                                ocinum, 0,
                                *p_out ) != OCI_SUCCESS )
            {
                raise_application_error(myCtx,ERROR_OCI_ERROR,
                                        "%s",
                                        lastOciError(myCtx));
                break;
            }
            debugf( myCtx,  "Appended to end of other array" );
        }
        /*
         * And lastly, set the out parameter to NOT NULL...
         */
        *p_out_i = OCI_IND_NOTNULL;
    }
    term(myCtx);
}

Now, I've got some utility routines in there I use in all of my
extprocs that you need to ignore like init, term, debugf,
raise_application_error and so on but the gist of the logic is
there.  The OCIColl* functions do the work for you.
Review & Followup
Rating: 5
Read on rdbms/lib May 27, 2003
Reviewer: A reader
Hi Tom,
Our installation doesn't have read permission on $ORACLE_HOME/rdbms/lib, so this
function would not compile. Is this the default permission? We have run an
argument with DBA's over security issues on this. I think the read permission
was there on Oracle 8.0 and 7.3, so has this changed with 8.1 and higher?
Is there any other way (without OCI) that this function could be coded?
Thanks

Followup:
why compile into rdbms lib ?

if you can code it in PLSQL -- do that.
if you can code it in Java -- do that.

Rating: 5
Array of records May 28, 2003
Reviewer: A reader
Hi Tom,
Is it possible to built interface to pass array of records?
Thanks

Followup:
a collection of object types -- yes

create type scalar ( a number, b date, c varchar2 )
/
create type array as table of scalar
/

that could be done (no, i don't have an example, i have examples of collections
of scalar types only -- that is what I use -- so I pass arrays of dates,
numbers, strings -- not "records")

Rating: 3
Create "shared library" on win 98 Oracle 8.1.7 Personal Edition ? May 28, 2003
Reviewer: A reader from Penn.
Hi Tom,

Will I be able to run the above OCI code (removing any external functions) on my
laptop running windows 98 and Personal Oracle ? What compiler is required and
can be sourced from where ? Will I be able to use the same compiler for Pro*C
programs too ?

Followup:
we support microsofts visual c/c++ compiler.  that is what you would need.

yes for pro*c

Rating: 5
Function returning collection June 16, 2003
Reviewer: A reader
Hi Tom,
Is is possible for a fuction to return a OCI collection type? Can you point me
to a prototype if this is possible?
Kind regards,

Followup:
sure it is -- just return the ocicoll type instead of getting a pointer to a
pointer of one.

  OCIColl *  function_name
    (
   OCIExtProcContext  *   /*   1 : With-Context  */
    );

Rating: 5
Init as per session July 30, 2003
Reviewer: A reader
Hi Tom,
I've worked on the sample routine provided by you above.
I noticed the following:
1. For all debugf calls File is opened and closed ie. OCIFileOpen and
OCIFileClose. Can this be modified to Open the file once per Oracle session  and
close when the session terminates?
2. For all calls to the external routine OCIFileInit is called and when the call
returns the OCIFileTerm is called. Can this be modified to Init once per Oracle
session and close when the session terminates?

I have a very important issue to discuss, ie. we do a legacy system connect. The
connect is done first time (a global variable is kept in the myCtx to tell the
connection already exists or not) the next time onwards the already made
connection is used. The problem is of not been able to close the connection.
Where exactly should this code of disconnect be there?

Your any possible help would be highly appreciated.

Followup:
well, what happens if you "crash". what happens when the file doesn't get
cleaned up?  what happens with buffered IO?

no, i would not suggest that.  i would suggest keeping the logging the way it
is.

the file stuff here is for debugging only.  you would use it to debug and then
disable it.  we normally would not even make these calls.

You'll have to CALL the disconnect code yourself.

Rating: 5
Thanks July 30, 2003
Reviewer: A reader
Thanks Tom. What we need to do is whenever the user terminates the Oracle
session (this can be a logoff trigger) this explict disconnect would have to be
done. The code for this  will not be in the main function but a separate
function, that would check the global logged on variable and if set would do the
log-off/disconnect. Am I right in thinking this?

Followup:
yes, the logoff trigger could be useful here for that sort of clean up

Rating: 5
Thanks July 31, 2003
Reviewer: A reader
Thanks Tom, you're great.

July 30, 2003 Ashiq -- Thanks for the question regarding "constraints Problem", version 8.1.7.0.1
You Asked
Hai Tom,

I'd created table emp with no primary key's.then i'd inserted 2 duplicate
rows(ie same empno no's) into it.then tried to create a primary key for that
that columns with clause 'ENABLE NOVALIDATE' ,but still its throwing error which
i'd mentioned below
and i've given the syntax what i used.

Zeo>alter table emp1 add constraint pk_emp1 primary key (empno)
enable novalidate
alter table emp1 add constraint pk_emp1 primary key (empno)
                                *
ERROR at line 1:
ORA-02437: cannot validate (SCOTT.PK_EMP1) - primary key violated

what's wrong in my statement ?
and we said...
When there is no index, and the primary key is NOT DEFERRABLE, we will create a
unique index.

You can either:

o create a non-unique index, then add the constraint
o create the constraint deferrable:

scott@ORA817DEV.US.ORACLE.COM> create table t ( x int );

Table created.

scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM> insert into t values ( 1 );

1 row created.

scott@ORA817DEV.US.ORACLE.COM> insert into t values ( 1 );

1 row created.

scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM> alter table t add constraint t_pk primary key(x)
  2  deferrable initially immediate
  3  enable novalidate
  4  /

Table altered

although I seriously question the logic behind having a primary key on a table
KNOWN to have duplicates!!!!
Review & Followup
Rating: 5
Very Good one January 12, 2002
Reviewer: Ashiq Shamsudeen A from Bangalore,Karanataka ,India
It's really very very useful one.i'm getting cleared many of my doubts by just
go throughing the site.

Rating: 4
Drop index July 30, 2003
Reviewer: Marcio from Brazil
Tom, consider this...

cpqd020496@MARCI9I1> create table t ( x int constraint x_unq unique deferrable
disable);

Table created.

cpqd020496@MARCI9I1> insert into t values (1);

1 row created.

cpqd020496@MARCI9I1> insert into t values (1);

1 row created.

cpqd020496@MARCI9I1> alter table t enable novalidate constraint x_unq;

Table altered.

cpqd020496@MARCI9I1>
cpqd020496@MARCI9I1>
cpqd020496@MARCI9I1> select index_name from user_indexes where table_name = 'T';

INDEX_NAME
------------------------------
X_UNQ

1 row selected.

cpqd020496@MARCI9I1> alter table t disable constraint x_unq;

Table altered.

cpqd020496@MARCI9I1> select index_name from user_indexes where table_name = 'T';

INDEX_NAME
------------------------------
X_UNQ

1 row selected.

cpqd020496@MARCI9I1> alter table t drop constraint x_unq;

Table altered.

cpqd020496@MARCI9I1> select index_name from user_indexes where table_name = 'T';

INDEX_NAME
------------------------------
X_UNQ

1 row selected.

Still there. I must drop it manually.
Is it possible drop index on deferrable constraint straight from command DROP or
DISABLE constraint?

Thks, Marcio

Followup:
at the point in time you disabled this constraint -- you disassociated it from
the index.

The default on the disable/drop is "keep index" hence it kept it.

You can do this on the disable (won't work on the drop since the cons/index are
not related at that point anymore)

ops$tkyte@ORA920> create table t ( x int constraint x_unq unique deferrable
disable);

Table created.

ops$tkyte@ORA920> select index_name from user_indexes where table_name = 'T';

no rows selected

ops$tkyte@ORA920> insert into t values (1);

1 row created.

ops$tkyte@ORA920> insert into t values (1);

1 row created.

ops$tkyte@ORA920> alter table t enable novalidate constraint x_unq;

Table altered.

ops$tkyte@ORA920> select index_name from user_indexes where table_name = 'T';

INDEX_NAME
------------------------------
X_UNQ

ops$tkyte@ORA920> alter table t disable constraint x_unq drop index;

Table altered.

ops$tkyte@ORA920> select index_name from user_indexes where table_name = 'T';

no rows selected

July 30, 2003 -- Thanks for the question regarding "Design", version 805
You Asked
Hey Tom
A question on designing the database. I have a database with a tablespace DATA &
schema name Analysis. All the data is stored in this schema. But I need to give
a lot of users access to this schema so that they are able to enter data in this
database through their CLIENT APPLICATIONS. Now my question is how should I make
this a centralized data store solution. Should I create public synonyms for all
my tables, indexes etc and Grant them to these users or is there a Better Way to
do this .
Thanks a Lot.
and we said...
I would do this:

o create another schema "analysis_xxx" (xxx is whatever)

o grant the minimal privs from analysis to analysis_xxx (eg: select on T1, all
on T2, select and insert on T3 and so on)

o create stored procedures in analysis_xxx that perform the required operations.

o grant execute on these procedures to some role -- maybe I have more then one
role and grant different levels of privs to different roles.

o grant the roles to people who need them.

Now, the people who need to access these procedures can do any of the following:

o reference analysis_xxx.procedure_name

o issue an "alter session set current_schema=analysis_xxx" and just reference
procedure_name

o create their OWN private synonyms that point to analysis_xxx.procedure_name
and just reference procedure_name.

Now, everything is very secure, very easy to control.  You can add customized
auditing if you like, whatever you need.  If you change the table structures,
the applications don't care (the stored procedures will hide that from you).
When some new cool interface comes along (eg: like the web did) you are ready to
support that as well.
Review & Followup
Rating: 4
July 06, 2001
Reviewer: A reader

Rating: 2
July 19, 2002
Reviewer: Nihal
Hi,
I wanted to do something similar,
have a schema which owns all objects , procedures etc called 'Owner'
Client application connect thru a schema as 'Client'
Some clients acessing thur pl/sql use schema 'Business'
Developers/Dba who can modify tables in owners schema and such alter/drops are
auditied are named 'Developers' and 'DataDba'.
So i tried your approach mentioned above.

I tried to create public synonyms for tables and procedure owned by owner
create public synonym ids for owner.ids
connected as owner and gave select/alter on ids to developer
grant select on ids to developer
grant alter on ids to developer
logged in as developer
could select from ids
but could not alter ids
gives an error as ids table or view does not exists

Tried another option
logged in as owner
granted select/alter on ids to developer
grant select on ids to developer ;
grant alter on ids to developer ;
logged in as developer
created a synonym for owner.ids ;
create synonym ids for ipms.ids ;

it still gives the same error as table or view does not exist.
How can one achieve alter/drop access to other users apart from owner and that
activity should be audited.

Another aspect when affecting design is whether the dba is internal or some
external companies.

Can the dba survive if we give all the privilige to dba apart from the delete on
audit tables ?
Suppose we use the apprach as suggested by you to audit the dba activity on a
schema.
Coz if we use some external consulting dba we need to be able to audit all their
alter activity on owner scehma, and not restricting the dba in performing its
activities.

Followup:
synonyms are not resolved in DDL -- too dangerous.  You must be explicit with
them:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create user a identified by a;

User created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create user b identified by b;

User created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> grant create session, create public synonym,
create table, unlimited tablespace to a;

Grant succeeded.

ops$tkyte@ORA817DEV.US.ORACLE.COM> grant create session to b;

Grant succeeded.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> @connect a/a
ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout off
a@ORA817DEV.US.ORACLE.COM> REM GET afiedt.buf NOLIST
a@ORA817DEV.US.ORACLE.COM> set termout on
a@ORA817DEV.US.ORACLE.COM>
a@ORA817DEV.US.ORACLE.COM> create table ids ( x int );

Table created.

a@ORA817DEV.US.ORACLE.COM> create public synonym ids for a.ids;

Synonym created.

a@ORA817DEV.US.ORACLE.COM>
a@ORA817DEV.US.ORACLE.COM> grant select on ids to b;

Grant succeeded.

a@ORA817DEV.US.ORACLE.COM> grant alter on ids to b;

Grant succeeded.

a@ORA817DEV.US.ORACLE.COM>
a@ORA817DEV.US.ORACLE.COM> @connect b/b
a@ORA817DEV.US.ORACLE.COM> set termout off
b@ORA817DEV.US.ORACLE.COM> REM GET afiedt.buf NOLIST
b@ORA817DEV.US.ORACLE.COM> set termout on
b@ORA817DEV.US.ORACLE.COM>
b@ORA817DEV.US.ORACLE.COM> select * from ids;

no rows selected

b@ORA817DEV.US.ORACLE.COM> alter table ids add y int;
alter table ids add y int
*
ERROR at line 1:
ORA-00942: table or view does not exist

b@ORA817DEV.US.ORACLE.COM> alter table A.ids add y int;

Table altered.

b@ORA817DEV.US.ORACLE.COM>

You MUST specify A.ids here.   Auditing is not affected by this -- if you have
audting on, then auditing will take place.

You can use the OS audit trail instead of the db audit trail to have the records
preserved OUTSIDE of the control of the DBA.

Note: Oracle9i Release 2 is the first release to support auditing of SYSDBA
(internal) accounts.  You'll want that if you need to audit everything they do.
Else, you'll have to grant them just SYSOPER (startup, shutdown) and dba and
nothing more.

Rating: 5
Design July 20, 2002
Reviewer: Munz from Reston, USA
Tom:

Why do you propose creating a schema and controling user access through that.
Would not this be hard to maintain.  Every time you create a new procedure or
update procedure you have to update the other schema?

2.  Can not you create a user account and assign priveleges to that account
(grant select on table to user).

Followup:
You are creating the procedures in the other schema.  So, you don't have to
"update the other schema" as an extra step -- it is the only step.

2) that is exactly what I typed in in the answer.  Create another user
account(schema) and assign privs to that account.

Rating: 3
Pl explain more July 21, 2002
Reviewer: Vikas Sharma from Delhi India
Hi Tom,

I shall be very thankful if you can pl explain your points in the answer to the
main question with the reasons, why you would like to do that. The one reason I
can understand no other users are have direct privs on the tables of the
analysis schema. Only the analysis_xxx in given the direct grant to select,
insert, update etc. Other have just execute priv through role so they cannot do
direct insert , update, delete on the tables of analysis schema. It will only be
a procedure in analysis_xxx, which will control it.

Regards,
Vikas Sharma

Followup:
i like the separation -- thats all.  I like to separate the data from the
application.  It lets me control exactly who can do what.

One schema (analysis) to hold the data.
Another schema (analysis_xxx) to hold the code.

thats it.

Rating: 4
Thanks July 22, 2002
Reviewer: Vikas from Delhi India
Hi Tom,
Thanks lot
Regards ,
Vikas

Rating: 5
excecute works on sqlplus but not through jdbc September 24, 2002
Reviewer: Bala from FL, USA
Hi Tom

I am running 8.1.6
i have granted only create session, execute on packages to
the application user.
As Application user i can run packages from sqlplus
but not thru jsp/jdbc(oracle thin driver).

If i grant select priv on tables(non of them sys owned)
to the appl user then i could execute them from jsp/jdbc.

Am i doing any thing wrong...

Thanks

Followup:
what happens when you run the packages from JDBC? Are you using ref cursors?

A long time ago, there was a bug with ref cursors + jdbc.  It was fixed a long
time ago -- but if you are using the really old 816 drivers -- you probably
still have it.

Suggest if your problem is REF CURSOR related, try a driver made in the recent
past.  They work against the older databases.

Rating: 5
excecute works on sqlplus but not through jdbc October 17, 2002
Reviewer: A reader
Hi Tom,

Yes i am using ref cursors.
I have downloaded the latest jdbc drivers classes12.zip (9.2.0), and tried.
Still getting the same table not found error.

When i try the same application, same schema with database version 9.0.1, its
not giving any errors.

is it any thing to do with 8.1.6 db.
But actual production is going to run on oracle 9.2.0,
so it is not a problem anymore.

Thanks
Bala.

Rating: 5
Separate schemas July 30, 2003
Reviewer: Marcio from Brazil
1) Have you ever seem any issue/problem with data/application works together on
same schema?

2) What is a main reason that let you keep data and application separate?

Followup:
1) just security reasons.   code in that schema can do anything to that schema's
data, whether you want it to or not.

code in another schema, can only do to that schema what that schema wants done
to it.

2) see #1

Rating: 5
November 07, 2003
Reviewer: A reader

July 30, 2003 Thiruvaiyaru -- Thanks for the question regarding "Ordering Results In a Query that uses Union, Continuation...", version 8.1.6
You Asked
Tom,

As a continuation to my previous question "Ordering Results In a Query that uses
Union" , I use a Spatial Condition in the Where Clause,
in which I cannot use an "Union All" because of duplicate entries,
but I want to get the results in the order of the query execution.

For Example :-

select x,y from z where MDSYS.LOCATOR_WITHIN_DISTANCE(COORD,
MDSYS.SDO_GEOMETRY(1,NULL, MDSYS.SDO_POINT_TYPE(-87.6521,41.9216, NULL), NULL,
NULL),'DISTANCE=25 UNITS=MILE LAYER_GTYPE=POINT') = 'TRUE'
union
select x,y from z where MDSYS.LOCATOR_WITHIN_DISTANCE(COORD,
MDSYS.SDO_GEOMETRY(1,NULL, MDSYS.SDO_POINT_TYPE(-79.9998,40.4426, NULL), NULL,
NULL),'DISTANCE=25 UNITS=MILE LAYER_GTYPE=POINT') = 'TRUE'

I want the results of 1st query to be retrieved first and then the second.

Thanks
Sai
and we said...
In order to make the stuff from the "first" query come "first" -- we need to add
to it something to make it unique form the "second" query data.

You can either have rows that come from the "first" query and then the "second"
query (union all with an order "column select 1 union all select 2")

OR

you can have duplicates removed which involves a sort distinct.

You cannot obviously have both.

It would be wasteful but you could:

(query 1)
UNION ALL
(query 2 MINUS query 1)

something like:

tkyte@TKYTE816> create table emp as select * from scott.emp;
Table created.

tkyte@TKYTE816> create table emp2 as select * from scott.emp where rownum <= 5;
Table created.

tkyte@TKYTE816> select 1, emp2.* from emp2
  2  union all
  3  ( select 2, emp.* from emp minus select 2, emp2.* from emp2 )
  4  order by 1;

         1      EMPNO ENAME      JOB              MGR HIREDATE
---------- ---------- ---------- --------- ---------- ---------
         1       7369 SMITH      CLERK           7902 17-DEC-80
         1       7499 ALLEN      SALESMAN        7698 20-FEB-81
         1       7521 WARD       SALESMAN        7698 22-FEB-81
         1       7566 JONES      MANAGER         7839 02-APR-81
         1       7654 MARTIN     SALESMAN        7698 28-SEP-81
         2       7698 BLAKE      MANAGER         7839 01-MAY-81
         2       7782 CLARK      MANAGER         7839 09-JUN-81
         2       7788 SCOTT      ANALYST         7566 09-DEC-82
         2       7839 KING       PRESIDENT            17-NOV-81
         2       7844 TURNER     SALESMAN        7698 08-SEP-81
         2       7876 ADAMS      CLERK           7788 12-JAN-83
         2       7900 JAMES      CLERK           7698 03-DEC-81
         2       7902 FORD       ANALYST         7566 03-DEC-81
         2       7934 MILLER     CLERK           7782 23-JAN-82
Review & Followup
Rating: 4
March 30, 2001
Reviewer: Helena Markova from Bratislava, Slovakia

Rating: 5
March 30, 2001
Reviewer: Saikumar from Chicago, IL USA
The response was very quick.Thank u very much for the help.

Rating: 4
Value Source July 30, 2003
Reviewer: Yogesh from Pune
I have a query like
select c1 from union1
union
select c1 from union2

I want to know, in resultset,  which is the source for that value .. qry1 / qry2
?

Is it possible ?

Followup:
select 1 whence, c1 from t1
union all
select 2 whence, c2 from t2;

whence = 1, t1
whence = 2, t2

Rating: 4
July 30, 2003
Reviewer: Yogesh from Pune
I already tried that

create table t1  (c1 number);
create table t2  (c1 number);

insert into t1 values (10);
insert into t1 values (11);
insert into t1 values (12);

insert into t2 values (12);
insert into t2 values (13);
insert into t2 values (14);

now if I use

select  1 col1, c1 from t1
union all
select  2 col2, c1 from t2;

1,10
1,11
1,12
2,12
2,13
2,14

I don't want duplicates like

1,12
2,12

what should I do ?

Followup:
well, how the heck would you know what table they came from then?

No, if you use UNION -- the rows come from the "set" as they could have come
from either of t1, t2 or BOTH t1 and t2!

(yes, I know with analytics we could dedup but think about the performance
implications on a result set of any sizable size)

ops$tkyte@ORA920> select        1 col1, c1 from t1
  2  union all
  3  select     2 col2, c1 from t2;

      COL1         C1
---------- ----------
         1         10
         1         11
         1         12
         2         12
         2         13
         2         14

6 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select *
  2    from (
  3  select whence, c1, min(whence) over (partition by c1) min_whence
  4    from (
  5  select     1 whence, c1 from t1
  6  union all
  7  select     2 whence, c1 from t2
  8         )
  9         )
 10  where whence = min_whence;

    WHENCE         C1 MIN_WHENCE
---------- ---------- ----------
         1         10          1
         1         11          1
         1         12          1
         2         13          2
         2         14          2

Rating: 5
Thanks July 31, 2003
Reviewer: Yogesh from Pune
Thanks Tom, solved my problem.

July 30, 2003 Kartik -- Thanks for the question regarding "Undo Table Space.", version 9.2
You Asked
Hi,

I have a database which contains Undo tablespace and Rollback segments under the
same directory structure.

UNDOTBS01                /u06/oradata/test/undo01.dbf
RBS                      /u06/oradata/test/rbs01.dbf
RBS02                    /u06/oradata/test/rbs02.dbf

I have been told that having the undo tablespace and rollback segments under the
same directory the instance will likely crash. Is it true? If so why?

Thanks in advance.

Kartik.
and we said...
you do not have both UNDO tablespaces and Manually configured rollback segments
enabled simulataneously.

You are EITHER using undo tablespaces
or
manually configured rollback

never both.

so the answer to this myth is "no, file placement will not crash the server"
Review & Followup
Rating: 5
Undo Tablespace July 29, 2003
Reviewer: Kartik Nair from USA
Your comments were really helpful.

Thanks.

Rating: 4
active transactions July 30, 2003
Reviewer: Reader
If i change the undo tablespace from one to another using ALTER SYSTEM command,
my understanding is that oracle would actually switch to the other one only
after all of the active transactions that are using the first one are either
committed or rollback? Meanwhile, if i wanted to find what transactions are
active in the first undo tablespace, how would i do it? Any view or query that
you have will be helpful. Thanks.

Followup:
that is not true -- it starts using the other one straight away.  consider:

ops$tkyte@ORA920> select a.username, b.segment_name, b.tablespace_name
  2    from v$session a, dba_rollback_segs b, v$transaction c
  3    where a.taddr = c.addr and b.segment_id = c.xidusn
  4  /

USERNAME                       SEGMENT_NAME                   TABLESPACE_NAME
------------------------------ ------------------------------ -----------------
OPS$TKYTE                      _SYSSMU11$                     UNDO

ops$tkyte@ORA920> alter system set undo_tablespace = undo2;

System altered.

so, one transaction using undo tablespace UNDO -- switched tablespaces and

ops$tkyte@ORA920> update dept set deptno = deptno;

2 rows updated.

ops$tkyte@ORA920> select a.username, b.segment_name, b.tablespace_name
  2    from v$session a, dba_rollback_segs b, v$transaction c
  3    where a.taddr = c.addr and b.segment_id = c.xidusn
  4  /

USERNAME                       SEGMENT_NAME                   TABLESPACE_NAME
------------------------------ ------------------------------ -----------------
OPS$TKYTE                      _SYSSMU11$                     UNDO
OPS$TKYTE                      _SYSSMU16$                     UNDO2

now both are being used....

we cannot drop tablespace "UNDO" yet, but it isn't used for new things...

And now you have the query you wanted as well :)

Rating: 5
great stuff. Thanks for the quick response. July 30, 2003
Reviewer: Reader

Rating: 5
July 30, 2003
Reviewer: A reader

Rating: 5
Very useful. July 31, 2003
Reviewer: A reader.

Rating: 5
July 31, 2003
Reviewer: Eric Grancher from Geneva, Switzerland

July 31, 2003 Kailash -- Thanks for the question regarding "Closing Cursors", version 8.1.7
You Asked
Hi Tom

In one of your earlier articles, you had mentioned that you need to keep open
the cursor and not close it and re-open it in a loop. However, I have the
following code where A is the Parent Table with two columns COl1 and COL2 and B
is the Child Table with COL1 and COL3
where col1 in B refers to the col1 in A.

DECLARE
   CURSOR c1 IS SELECT COL1 FROM A;
   c1var C1%ROWTYPE;
   CURSOR C2 IS SELECT COL1,COL2 FROM B WHERE COL1 = c1var.col1;
   c2var c2%ROWTYPE;
BEGIN
   OPEN c1;
   LOOP
      FETCH c1 INTO c1var;
      EXIT WHEN C1%NOTFOUND;
      OPEN c2;
      LOOP
          FETCH c2 INTO c2var;
          EXIT WHEN C2%NOTFOUND;
          DBMS_OUTPUT.PUT_LINE('C2.COL1 ' || c2var.col1);
          DBMS_OUTPUT.PUT_LINE('C2.COL2 ' || c2var.col2);
          /* other logic goes here */
      END LOOP;
      CLOSE c2;
   END LOOP;
END;

Question :
In the above program construct, I have to close and reopen the cursor C2 in
order to process the next row of C1. ie for each row in C1, I have to process
all the corresponding rows in C2 and so unless I close and reopen the cursor in
a loop, I would not be able to do that.
  1.So what exactly do you mean by 'Not to close and open the cursor in a loop'
and how do you acheive the above logic without closing and reopening the cursor?
Is my understanding wrong. Can you please provide me an example of what you mean
so my understanding becomes clear?
  2. In the above case, will the cursor be cached and so that it does not do a
hard parse and what is soft-parse and softer soft parse?

2. In your book on explaining the 'PGA' and UGA, you show the memory increase
after sort operations. I am not clear about the figures you had mentioned after
the sort. I tried adding the numbers and it does not match with your output.
Please explain.

3. In the case of Analytic functions, when you do a COUNT/SUM/MAX using OVER
clause, how does Oracle improve performance and how does it get the required
results. Does it perform a FTS or what mechanism it uses to get the results to
get an improved performance?

4. Which of the join conditions is better. Nested Loop/Merge Join/Hash Join?
What are the conditions under which each has to be used and will use of hint a
good practice?

5. Things to look for in EXPLAIN PLAN?
and we said...
1) that loop should be:

for x in ( select a.col1 col1_a,
                  b.col1 col1_b,
                  b.col2 col2_b
             from a, b
            where a.col1 = b.col1  )  /* adding (+) if ABSOLUTELY needed */
loop
    output...
    process....
end loop;

period, no need for explicit cursors, no need to perform a nested loops join
yourself.  In fact the database may well find an INIFINITELY better join
operation such as a hash join that you cannot do yourself if you try to "be the
optimizer" and write tons of procedural code!

hard parses are bad things

soft parses are good things

softer soft parses are a little better then a soft parse -- but do mean that you
wrote your code poorly!

lots of stuff written on that on this site and in my book -- search around.

2) bottom line -- the bigger the sort area use, the more uga or pga memory used.

The small the sort area used, the less.

My examples clearly showed "big sorts = more memory used if you let it".  that
was their bottom line. you will never get a byte for byte accounting, but the
numbers shown in the book are very close to eachother.

3) it does LESS work.  no self joins, no joins, no complex logic.  It is a
"command", it knows what you are asking and can do it more efficient.  See the
chapter on analytics in my book -- I go into this in detail.

4) none of them -- they are not join conditions, they are methods the CBO will
choose for you at the right times.

If one were better or "best", we would not have spent time coding the others.
My next book (due out in august) goes into this in much more detail then I did
in "Expert one on one Oracle"

5) nothing, everything, whatever you need to?  It is quite simply a report that
shows you what Oracle will do.  It is informational, it is instructional.
Nothing more, nothing less.
Review & Followup
Rating: 4
Please demonstrate the re-use of cursors. July 30, 2003
Reviewer: Fred from irvine, Ca
Could you provide an example that demonstrates how you can reuse the (explicit)
cursor without closing it in PL/SQL?  You do make the suggestion in your book
and showed us how you can do it in java.  I'm somewhat confused because in the
context of PL/SQL, aren't you inviting problems such as leaks and "too many
cursors opened"?  It makes sense if this is done within a loop, but your book
seems to suggest that you can reuse the cursor many times after executing the
PL/SQL stored procedure that had created it.  Forgive me if I am not making
sense.  I should be going home now instead of becoming mired in this loopy
situation ;)

Followup:
ops$tkyte@ORA920> create or replace procedure p
  2  as
  3          cursor c is select username from all_users;
  4          l_username all_users.username%type;
  5  begin
  6          open c;
  7          fetch c into l_username;
  8  end;
  9  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> show parameter open_cursors

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
open_cursors                         integer     500
ops$tkyte@ORA920> begin
  2          for i in 1 .. 1000
  3          loop
  4                  p;
  5          end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

when an explicit cursor goes out of scope, it is implicitly closed for you.

Rating: 5
Yep... Makes Sense Now July 31, 2003
Reviewer: Fred from irvine, ca
I just get giddy over this stuff...

July 31, 2003 Sam -- Thanks for the question regarding "The issue of bind variables only arises( in p/sql) when we are doing dynamic sql and not with static sql", version 8.1.7
You Asked
Tom
1.
According to you , the issue of binding variables arises only when we
are creating queries on the fly i.e. using dynamic sql.

Please give an example( and discuss) and show how we accidentally hard code
variables when using dynamic sql, and why this issue does not arise at all using
static sql.

2.Can you explain in a nutshell what is meant by collections and objects in
oracle 8i. Is pl/sql table  a collection.  Will pl/sql table , varray come
under the definition of collections.

3.Please give an example for a pl/table in which we have a whole row of
information. How can we create such a pl/sql table, a pl/sql table with multiple
columns. Is such a pl/sql table called two dimensional array.

thank you
and we said...
Sometimes I think I could write a book simply entitled "bind variables" -- given
the number of times it comes up ;)  Maybe it comes up so much cause I've been on
a crusade to get people to USE them.

1) well the reason it does not apply to static sql is that static sql is (by its
very definition) fixed at compile time.  Every time you run that program -- the
static sql will be the SAME.  It will be 100% shareable.  It is "static".

So, using static sql the query will always be:

   "select * from t where x = 'some value'"

No matter HOW MANY times you run that program, that same old query will be
executed (reused)

Using dynamic sql however, you are able to construct statements like:

   select * from t where x = 'a'
   select * from t where x = 'b'
   select * from t where x = 'c'
   .... and so on

each statement is brand new, unique, never before seen.

You wanna see how bad that is?  Look at the following.  I'll dynamically execute
"select count(*) from t where x = 1" (varying 1 from 1 to 1000) and then using
bind variables with static PLSQL, I'll execute the same:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table run_stats ( runid varchar2(15),
name varchar2(80), value int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace view stats
  2  as select 'STAT...' || a.name name, b.value
  3        from v$statname a, v$mystat b
  4       where a.statistic# = b.statistic#
  5      union all
  6      select 'LATCH.' || name,  gets
  7        from v$latch;

View created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop table t;
Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> ALTER SYSTEM FLUSH SHARED_POOL;
System altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_start number;
  3          l_cnt   number;
  4  begin
  5      insert into run_stats select 'before', stats.* from stats;
  6
  7      l_start := dbms_utility.get_time;
  8      for i in 1 .. 1000
  9      loop
 10                  execute immediate 'select count(*) from t where x = ' || i
into l_cnt;
 11      end loop;
 12      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 13
 14      insert into run_stats select 'after 1', stats.* from stats;
 15
 16      l_start := dbms_utility.get_time;
 17      for i in 1 .. 1000
 18      loop
 19                  select count(*) into l_cnt from t where x = i;
 20      end loop;
 21      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 22
 23      insert into run_stats select 'after 2', stats.* from stats;
 24  end;
 25  /
108 hsecs
18 hsecs

PL/SQL procedure successfully completed.

so, obviously it is FASTER but....

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value-a.value run1,
c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

NAME                                           RUN1       RUN2       DIFF
---------------------------------------- ---------- ---------- ----------
LATCH.active checkpoint queue latch               1          0         -1
LATCH.checkpoint queue latch                      5          4         -1
LATCH.session idle bit                            1          0         -1
STAT...db block changes                          45         44         -1
LATCH.redo writing                                1          0         -1
STAT...free buffer requested                      7          6         -1
LATCH.undo global data                            8          9          1
LATCH.session allocation                          2          0         -2
STAT...deferred (CURRENT) block cleanout          1          3          2
 applications

STAT...change write time                          2          0         -2
STAT...table fetch continued row                  2          0         -2
STAT...opened cursors current                     4          2         -2
LATCH.redo allocation                            27         30          3
LATCH.messages                                    4          0         -4
STAT...cursor authentications                     1          6          5
STAT...db block gets                           4040       4034         -6
STAT...sorts (memory)                            10          4         -6
STAT...enqueue releases                          20         13         -7
STAT...cluster key scans                         14          6         -8
STAT...enqueue requests                          21         13         -8
STAT...session cursor cache count                 9          0         -9
LATCH.library cache load lock                    10          0        -10
LATCH.enqueue hash chains                        32         20        -12
STAT...cluster key scan block gets               51         63         12
STAT...session cursor cache hits                 21          4        -17
STAT...rows fetched via callback                 18          0        -18
LATCH.enqueues                                   55         30        -25
STAT...sorts (rows)                            2039       2011        -28
STAT...buffer is pinned count                    34          0        -34
LATCH.cache buffers lru chain                    63         10        -53
STAT...calls to get snapshot scn: kcmgss       1092       1018        -74
STAT...execute count                           1088       1014        -74
STAT...parse time cpu                            79          1        -78
STAT...parse time elapsed                        91          1        -90
STAT...recursive cpu usage                      115         23        -92
STAT...table fetch by rowid                     118          9       -109
STAT...no work - consistent read gets           210         70       -140
STAT...buffer is not pinned count               332         82       -250
STAT...consistent gets                          423         91       -332
STAT...session logical reads                   4463       4125       -338
STAT...redo size                              23632      24012        380
LATCH.cache buffers chains                     8621       8222       -399
STAT...recursive calls                         2002       1184       -818
STAT...parse count (hard)                      1007          5      -1002
STAT...opened cursors cumulative               1034         15      -1019
STAT...parse count (total)                     1048         15      -1033
STAT...session pga memory max                  6656       8524       1868
LATCH.row cache objects                        6254        115      -6139
STAT...session pga memory                     15144       8524      -6620
LATCH.library cache                           21344       2269     -19075
LATCH.shared pool                             24098        131     -23967

51 rows selected.

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

there is the real kicker.  Look at the latches..  45,442 when not using binds,
2,400 using binds.  Almost 20 TIMES (two-zero TIMES) the number of latches.
Bearing in mind that latches are locks, locks = serialization device,
serialization = less concurrent, less concurrent = not scalable.....

What that means is that as you add more and more users doing this very bad thing
(not using bind variables) the run time differences between the two will
literally skyrocket....

2) collections are like arrays.  thats the nutshell.  They are a heck of a lot
like arrays.

See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2047154868085
a plsql table is a collection of sorts, yes.

3)

  create type myTableType as table of emp%rowtype index by binary_integer;

That is a table of records.  It is similar to a two dimensional array in that it
has "rows" and "columns".  You do not index the columns, you access them by name
(making it different from a 2 dim array).

In Oracle9i, we have multi-level collections which do support TRUE 2 dimensional
arrays.
Review & Followup
Rating: 5
Great example! January 11, 2002
Reviewer: Jim Kennedy from Wilsonville, OR USA
Love your demonstrative example. It is very clear.

Rating: 5
January 11, 2002
Reviewer: SB from Seattle, WA USA
From your statement
"well the reason it does not apply to static sql is that static sql is (by its
very definition) fixed at compile time.  Every time you run that program -- the
static sql will be the SAME.  It will be 100% shareable"
So, does it mean that static SQL need not use BIND VARIABLES? When I run the
function remove_constants (pg. 449 from your back), since the "some value"
changes in the where clause, I think it captures static SQL too.. Pls. advise.

Followup:
It would capture STATIC SQL as well -- but (here is the kicker) there will only
be ONE copy of that statement in the shared pool (its static).

So yes, I would find:

   select * from t where x = 1

and I would turn it into

   select * from t where x = @

but we would only find 1 copy of that particular query and would never display
it.  It would never show up as a problem query.

Rating: 5
Very Important January 11, 2002
Reviewer: Srimal from Trenton, USA
Hello Mr Tom,

The use of Bind variables not only improves the efficiency of the application,
but also of application maintainence. The code becomes easier to read and modify
(that is one more + point for bind variables).

You are right Mr Tom, you should look into a small book on bind variables, its
importance and use!

Rating: 5
January 11, 2002
Reviewer: SB from Seattle, WA USA
Hi Tom,
I issued 4 static SQL statements in the database and am sending you the results:

SQL> select ename from emp where empno = 10;

ENAME
----------

SQL> select ename from emp where empno = 20;

ENAME
----------

SQL> select ename from emp where empno = 7786;

no rows selected

SQL> select ename from emp where empno = 7374;

no rows selected

SQL> insert into sql_area_tmp (sql_text)
  2  select sql_text from v$sqlarea;

121 rows created.

SQL> commit;

Commit complete.

SQL> update sql_area_tmp
  2  set sql_text_wo_constants = remove_constants(sql_text);

121 rows updated.

SQL> select sql_text_wo_constants, count(*) from sql_area_tmp
  2  group  by sql_text_wo_constants having count(*) > 2 order by 2;

SQL_TEXT_WO_CONSTANTS
---------------------------------------------------------------------------------
---------------------------------------
  COUNT(*)
----------

SELECT ENAME FROM EMP WHERE EMPNO = @
         4

Now is the above query a "problem Query" ?

Followup:
wow, I am surprised.

Apparently I am not able to get my point across.

Ok, let me try again.

In sqlplus, everything you do is by defintion DYNAMIC SQL.  By its VERY
DEFINITION.  Static sql is sql that is known at compile time.  Now the guys in
sqlplus development had no idea that you would be issueing those 4 queries so at
the time they COMPILED sqlplus, they did not include them!  they dynamically
executed them when you asked them to.

Now, here is an example of static sql:

create procedure p
as
begin
   for x in ( select * from emp where empno = 4 )
   loop
      dbms_output.put_line( x.ename );
   end loop;
end;
/

Now that is static sql.  When I compile that procedure, that sql query in bold
is fixed in stone.  I don't care HOW MANY TIMES i execute that procedure, the
same exact query will be run over and over and over and over again.  I don't
NEED to use a bind variable for the number 4 in this case, that sql statement
will be shared very nicely by EVERYONE that ever runs this procedure.  static
sql is "static" -- static meaning "unchanging", "fixed".

Your sql above is DYNAMIC sql -- the very definition of dynamic sql.  It does
not use bind variables.  In sqlplus it is very hard to avoid this (thats why it
is considered an ad-hoc environment, a tool, not a deployment platform)....

Rating: 3
Is parsing expensive? January 11, 2002
Reviewer: Mikito Harakiri
Well, finding shared sql by hash value is fastest method, no doubt, but is
parsing that is necessary for literal replacement proven to be significantly
more expensive? AFAIK parsers in general are not doing significantly more than
one pass through the parsed text; but when calculating hash value one pass
through query string is needed as well. If current literal replacement
implementation doesn't perform, then just improve it!

Oracle has a good track of implementing every feature user asked for. Isn't it
clear yet, that users don't want to know about bind variables?

Followup:
sigh, here we go again Mikito...  We've been over this a couple of times.

I don't have a clue as to the point you are trying to make.

There is an "auto bind" feature in 816 -- it is called cursor_sharing.  If you
have my book I go into WHY auto bind is a crutch, a temporary solution and not
NEARYLY a 100% solution.  You know of this auto binder, we've been down this
road.

I personally don't care if the "users" (in this case you must understand that
the users are supposed to be professionals, they are developers, they should
care about coding high performance scalable applications) don't want to know
about bind variables.  They have to.  If they don't, I can quite simply say
"they will fail".  There are many things I "don't want to know about" but yet I
have to know about in order to do my job.

As I said in my book, "If I was to write a book on how to code a non-scalable
solution in Oracle, it would be very short.  It would have one chapter entitled
'Do not use bind variables' and the body of the chapter would simply say 'See
the title'"

Even with AUTO-BINDING, whereby we magically turn:

   select * from t where x = 5

into:

  select * from t where x = ":SYS_BV_0"

and bind in 5, you still have only gotten a very very very tiny percentage of
the payoff you get when using bind variables.  For example, a very simplistic VB
coder might write something like this (not knowing "vb", this is psuedo code):

   while more data loop
       sqlstmtm = "insert into t values ( " + some_variable_value + ")";
       execute sqlstmt
       some_variable_value = some_function
   end loop

That would basically (with autobinding)

   loop
       PARSE  <<<=== expensive, we ain't just parsing a string, there is
                     more involved in parsing a statement then a simple string
                     parse.  there are latches into the shared pool and so on.
       BIND
       EXECUTE
       CLOSE
   end loop

The code that scales, that runs efficiently, the WAY IT SHOLD BE DONE is:

    PARSE
    loop
        bind
        execute
    end loop
    CLOSE

and you know what -- if you use PLSQL -- this is automagically done for you.
We'll parse a statement ONCE, bind and execute it over and over and over and
close it only when necessary.

So, if you don't want to be "bothered" with learning the environment (sounds a
tad lazy if you ask me), use PLSQL.  We'll do all of the work for you -- the
only thing you must do is learn to BIND (to pass parameters to) a stored
procedure.  QED.

(see my book for the other reasons why cursor_sharing is only a partial solution
to the underlying problem at hand).

You know -- code that uses bind varaibles is actaully EASIER to read and EASIER
to code then code that doesn't!!! think about that.  It certainly would cut down
on the number of times people ask me how to insert a string with a quote in it,
that is for sure.

Rating: 5
Here we go again with bind Variable January 31, 2002
Reviewer: Ashok from USA
Tom Pls Clarify after reading lot of articles still.....

Here is the Sceneario:
In The Stored procedure:
                Cursor definition
                   Execute the cursor
                      Looping thru the cursor
                      //Confusion Point
                       INSERT INTO TABLE YIELD VALUES(
                        :ls_val1,:ls_val2)
                     END LOOP
                    Close Cursor

Point is:
      Will the Insert Statement will be shared for each looping or it will not
be shared bcos for each looping     there will be different value for ls_val1
and la_val2  (i.e FOR ROW 1:Insert into table yield Values('12',500)
For Row 2:Insert into table yield Values('17',800)
How oracle will take the above statment?

Pls Clarify this Point

Thx
Ashok

Followup:
Here is the procedure:

  for x in ( select * from t )
  loop
      .... some code ....
      insert into table yield values ( plsql_variable_1, plsql_variable_2, ...
);
  end loop;

(don't use those explicit cursors -- harder to code, more work for you and they
run slower).....

In this case, the insert statement will be parsed ONCE per session.

All plsql variables referenced in SQL in PLSQL are in fact bind variables.

The statement will be used and reused over and over and over.  You will see a
parse count of 1 and an execute count greater than 1.

Rating: 4
The never ending issue on bind variables... February 20, 2002
Reviewer: Aries Balaquidan from Philippines
Thank you very much for the untiring time to answer issues on bind variables....
and they're very (3x) useful.
 I am a DBA currently working out with this similar problem in our site. My
company bought a software package written in Visual Basic that issues a lot of
SQL statements that makes my Database server "scrambling" for resources all the
time. I just have one question re this issue, what if i execute my statements on
front-end tool other than oracle tools, i.e. VisualBasic, how could an sql
statment using bind variables be written? All i know is to declare a variable
and pass its value to an SQL statement.

Followup:
It depends on the LANGUAGE you use to write that query with.  It is different in
VB then in java then in c and so on.

Rating: 5
Brilliant! March 25, 2002
Reviewer: performance analyst who is tired repeating "use bind vars"
Hi Tom,

brilliant example! And with hard data. And I love your comments about "supposed
to be professionals".

Finally single location to point out.

Rating: 5
Repeated calls to a function or procedure May 22, 2002
Reviewer: Richard from W. Yorkshire, UK.
Ok I understand what you say about bind variables, especially how PL/SQL uses
them

To take things a step further:
function isValid ( i_pkey in table.column%TYPE) return boolean is
begin
  for r in (select 'x' from table t where t.pkey = i_pkey) loop
    return true;
  end loop;
  return false;
end;

This function will use bind variables by virtue of PL/SQL binding.

Will the cursor be re-used for repeated calls to isValid provided it hasn't been
shunted out of the shared pool?

Given the advantages of for loops, is this an economical use where we exit with
a return statement?

Playing around with the examples in
http://www.programmersheaven.com/other/BookSamples/book28/css/4826_Chap01.htm
to emulate repeated calls as follows:

This is your example (amended for my schema)
declare
  type rc is ref cursor;
  l_rc rc;
  l_dummy accounts.reference%TYPE;
  l_start number default dbms_utility.get_time;
begin
  for i in 1 .. 100000
    loop
      open l_rc for
      select reference
         from accounts
        where reference = lpad(i, 10, '0')
        ;
      fetch l_rc into l_dummy;
      close l_rc;
    end loop;
  dbms_output.put_line(round((dbms_utility.get_time-l_start)/100, 2 )
        ||' seconds...' );
end;
/

This kind of emulates repeated calls to a function (the inner loop which uses an
exit instead of a return statement):
declare
  l_start number default dbms_utility.get_time;
begin
  for i in 1 .. 100000 loop
    for r in (select reference
         from accounts
        where reference = lpad(i, 10, '0')) loop
     exit;
    end loop;
  end loop;
  dbms_output.put_line(round((dbms_utility.get_time-l_start)/100, 2 )
        ||' seconds...' );
end;
/

The latter showed a two fold improvement in performance, presumably because the
overhead of managing the cursor variable was removed - this suprised me!

Thanks for all the useful info...

Followup:
"Will the cursor be re-used for repeated calls to isValid provided it hasn't
been
shunted out of the shared pool?"

yes, in fact -- it will not even be soft parsed in all probability as plsql will
CACHE the cursor for us.

Rating: 5
May 23, 2002
Reviewer: Helena Markova from Bratislava, Slovakia
Very good examples ...

Rating: 5
For Mikkiti May 23, 2002
Reviewer: Connor from UK
Tom's examples show that parsing is expensive, but I actually think he's
under-stressing the cost.  Recently when trying to see what limits the size of
SQL's could be (amongst other things), we tried a:

'select * from dual union all
 select * from dual union all
  ...'

repeated up to 'n' times.  For large values of 'n', we killed the session after
10 minutes - and it will still in the parse phase.  Parsing is a nightmare -
bind variables are a dream come true

Rating: 4
November 29, 2002
Reviewer: Sagi from India
Hi Tom,

Sorry for my silly question.

Your example was excellent. But i could not get how you got the value. You said
in

"there is the real kicker.  Look at the latches..  45,442 when not using binds,
2,400 using binds"

Hod did you get 45442 and 2400???

Thanx in advance.

REgards,
Sagi

Followup:
I added:

LATCH.library cache                          21344       2269     -19075
LATCH.shared pool                            24098        131     -23967
                                         ---------     ------
                                           ~45,000      ~2400

Rating: 4
Flushing the pool...OK to just do it ? November 29, 2002
Reviewer: robert from PA
>> ops$tkyte@ORA817DEV.US.ORACLE.COM> ALTER SYSTEM FLUSH
>> SHARED_POOL;

Tom, been wondering about flushing the pool...is this
something you'd grant developers - in a dev instance - the ability to do ?
Im a little "FUD-dish" in doing it cause I dont understand it.

What consequences should one consider ? Will it cause any problem, inconvenience
to other sessions other than the obvious ?
Can I just do it ANYTIME I find a need to compare stats ?
Thanks

Followup:
In a pure development instance -- that was single user -- sure.

In a multi-user environment, I don't see the point.  the only reason you would
flush the shared pool ever in my opinion is to do something like I did above --
run a test.  Now, since a test like that is best run single user -- it would be
a database on the developers PC (which I'm all for -- be good for them to have
personal oracle locally to play with and test ideas on)

Otherwise, it isn't really useful.

It'll not accomplish its goal all of the time in a multi-user environment.
You cannot do the sorts of tests that require it in a multi-user environment.

I don't see the need for this particular feature to be exposed to them in that
environment.  On their own database - sure.

Rating: 5
test December 24, 2002
Reviewer: mo
Tom:

Why when I run your example here I get different results than yours. I get the
same total for latches (about 10000)

SQL> @c:\tools\harness_ex (copied from yours)
Input truncated to 2 characters
 27  /
14 hsecs
9 hsecs

PL/SQL procedure successfully completed.

IMSD> @harness_q

SUBSTR(A.NAME,1,20)        RUN1       RUN2       DIFF
-------------------- ---------- ---------- ----------
LATCH.cache buffers           1          2          1
STAT...calls to get        1003       1002         -1
STAT...db block chan         17         16         -1
STAT...opened cursor          1          2          1
STAT...recursive cal       1011       1012          1
STAT...total file op          1          0         -1
STAT...enqueue reque          4          3         -1
LATCH.undo global da          2          5          3
LATCH.redo allocatio          7         11          4
STAT...redo size          20756      20880        124
STAT...opened cursor       1002          3       -999
STAT...parse count (       1002          3       -999
STAT...table scans (          0       1000       1000
LATCH.shared pool          2001          1      -2000
STAT...db block gets         14       4014       4000
STAT...session logic         25       4025       4000
STAT...session uga m          0       4264       4264
LATCH.library cache       10025       2027      -7998
LATCH.cache buffers          80       8113       8033

19 rows selected.

Followup:
Look at the latches a little closer:

library cache -- down 8k
shared pool   -- down 2k

cache buffers (SOMETHING, i don't know what since you nicely chopped it off) up
8k.

So, investigate why one of

ops$tkyte@ORA920> select name from v$latchname where name like 'cache buffer%';

NAME
------------------------------
cache buffers lru chain
cache buffers chains
cache buffer handles

went up for some reason.  it could well be a side effect of a tiny buffer cache
and the insert we do in the middel for example.

It does not invalidate at all the results of this test.  What we were trying to
reduce was the library cache contention induced by hard parsing.  We did.
Although I must say -- this looks definitely like a "second run" of this without
a flush of the shared pool.

Analyze the results -- test things out -- explore -- investigate.  What more can
I say.....

Rating: 5
question on binding constants in plsql July 30, 2003
Reviewer: A reader
"All plsql variables referenced in SQL in PLSQL are in fact bind variables."

Hi does the above apply to constants in plsql. I thought
you mentioned somewhere (cant find it now) that even constants are actually
bound on in plsql. I ran the
following sql.
reate or replace procedure p
as
  l_dummy  varchar2(10);
begin
  select name
  into l_dummy
  from t1
  where name = 'name1'
    and rownum <= 1;
  dbms_output.put_line ( 'l_dummy ' || l_dummy );
end;
/
alter session set sql_trace=true;
exec p;
--
the tkprof shows:
--
SELECT name
  from t1
  where name = 'name1'
    and rownum <= 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          2          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.01          0          2          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 228     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  COUNT STOPKEY
      1   INDEX RANGE SCAN (object id 112797)

********************************************************************************

-- this seems to imply that the constant was not substituted
for a bind variable (from the select text).

Please clarify.

Thank you!

Followup:
no, i've never said that.

constants are constants.

do not OVERBIND here -- it is perfectly 100% OK and good to use constants in
static sql in PLSQL.

why?  because no matter how many billions of times you execute that procedure,
the sql will be the same over and over - you'll get great reuse.

Rating: 5
thanx Tom! July 31, 2003
Reviewer: A reader
"no, i've never said that."
I apologize for that then! May be you meant to say
that the "constants" defined in the package using constant
keyword are bound as variables. Or may be my mind is playing tricks - it has
been known to do that before !;)

Thanx for all your help, Tom!

July 31, 2003 Anil -- Thanks for the question regarding "Accessing ", version 8.1.3
You Asked
Hello Tom,
In Oracle 8.1.3, I've created an object
Create type personobj (
pid varchar(3),
pname varchar(10));

Create type addressobj (
addr1 varchar(10),
city varchar(10),
pincode varchar(10));

Create Table Empl (
person personobj,
address addressobj);

If I wish to see the records from the table I give
Select * from Empl;

What If I want to see only pname using select ? I mean I want to select only one
column.

For example,
Select person_det.person.pname from Emploee;

And Also how to give the condition.
Fo example I want to see all details of Empl where pname = 'Nilesh'

Pls advice

Anil Pant
and we said...
ops$tkyte@ORA817DEV> insert into empl values ( personobj('abc','tom'),
addressobj('x','y','z') );

1 row created.

ops$tkyte@ORA817DEV> select * from empl;

PERSON(PID, PNAME)
---------------------------------------------------------------------------------
--------------------------------------------------
ADDRESS(ADDR1, CITY, PINCODE)
---------------------------------------------------------------------------------
--------------------------------------------------
PERSONOBJ('abc', 'tom')
ADDRESSOBJ('x', 'y', 'z')

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select e.person.pname from empl e;

PERSON.PNA
----------
tom

ps$tkyte@ORA817DEV> select * from empl e where e.person.pname = 'tom';

PERSON(PID, PNAME)
---------------------------------------------------------------------------------
--------------------------------------------------
ADDRESS(ADDR1, CITY, PINCODE)
---------------------------------------------------------------------------------
--------------------------------------------------
PERSONOBJ('abc', 'tom')
ADDRESSOBJ('x', 'y', 'z')
Review & Followup
Rating: 4
Good July 31, 2003
Reviewer: Anil Pant
Hi,
Thks for the answer. Is it mandatory to give alias ?
Thanks
Anil Pant

Followup:
yes it is.

July 31, 2003 joon -- Thanks for the question regarding "shutdown immediate hung forever and ORA-03113", version 8.1.6.2
You Asked
Hi
Our client using
hp-ux 11.0 and oracle 8.1.6.2.

We have problem in backup.
Backup script doing following sequence.
1.shutdown abort
2.startup
3.shutdown immediate
4. backup all files that belongs to db.

The problem is Onece a month shutdown immediate hung forever.
We found in the morning following message in the alert log.
"SHUTDOWN: waiting for active calls to complete."
But No jobs run before backup and all tablespace pctincrease 0
except system tablespace, temp tablespace temporary.

whenever shutdown hung ,same time ORA-03113 happen.

Please help me.
thanks.
and we said...
well, the easiest and best solution to this is to NOT shutdown to do a backup.
HOT backups are easy, and do not make you shutdown.

I can only guess at what may have happened. The shutdown abort aborted a really
long running transaction that would take a while to roll back.  when you started
up, we rolled forward, opened the database and started to roll back (in the
background -- slowly so as to not overwhelm the system).  You did a shutdown
immediate -- which does a CLEAN shutdown.  It must wait for the rollback to
finish.  It could take longer to rollback then it did to do the transaction in
the first place.

So, your shutdown abort make it so the shutdown immediate would have to wait a
really long time.

This is a good reason why you want to do HOT backups -- so you don't run into
issues like this.

follow up to comment one below

Shutdown immediate is a clean shutdown.  It simply kicks off active transactions
before doing a clean shutdown whereas shutdown normal WAITS for them to
complete.

A shutdown immediate leaves the database in the same closed state as shutdown
normal does.
Review & Followup
Rating: 4
How about startup open restrict/shutdown normal June 12, 2001
Reviewer: Moorthy Rekapalli from Atlanta GA USA
How about doing the following:

svrmgrl <<EOF
shutdown abort
startup open restrict
shutdown normal
EOF

That way, you will get a normal shutdown before taking offline backups.
Shutdown immediate might work just fine for offline backups.  But, shutdown
normal will give peace of mind.

Rating: 5
ora 3113 October 03, 2002
Reviewer: mo
Tom:

We have several instances/databases running. My main database uses database
links to select data from others. The problem I am having is that everytime the
dba shuts down the instance I get this:

ORA-02068: following severe error from INS_LINK
ORA-03113: end-of-file on communication channel
ORA-06512: at "SYS.DBMS_SYS_SQL", line 1474
ORA-06512: at "SYS.OWA_UTIL", line 1663
ORA-06512: at "INSADMIN.NEW_PO", line 516
ORA-06512: at line 7

He says that he has to drop and re-create the links afterwards. I am thinking
something is wrong here. After you start a database the links should
re-establish themselves and I should not get this error the first time I call
the link.

Do you think this issue is related to "shutdown immediate" or "shutdown".  Any
hints?

BTW when I searched your site for ORA-03113 I got 68 records found and you only
displayed 15.

Thank you,

Followup:
You got APPROXIMATELY 68 records found.  Just like google does (and hey, if
google can do it, so can I cause google is just the coolest)

see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3489618933902
and search (ctl-f) for google.

Your DBA needs to open a tar with support -- the links should not need to be
dropped and recreated, there is something fundementally wrong there.

Rating: 3
Sql Report Writer 1.1 July 31, 2003
Reviewer: Bipin Ganar from INDIA
Hi Tom,
We have shifted our oracle 8.1.7 Database From open VMS to Windows 2000 Oracle
9.2.0. My other Applications are working without any problem But Reports which
are using report writer 1.1 is giving me err Ora-03113

As follows
SQL*ReportWriter : Version 1.1.14.16.1-Product on Tue Jul 29 14:56:49 2003
Copyright (c) 1987 Oracle Corporation,California,USA
All Rights reserved Wrldwid Oracle Error Number -3113
ORA-03113 : end-of-file on communication channel

Can u help me urgently

Followup:
umm, contact support -- however, that is such "old stuff" -- i have a feeling it
is going to be "sorry, please upgrade" at this point.

if report writer 1.1 was built on 7.3 support libraries - I am 100% sure that
will be the answer since 7.3 clients connecting to 9iR2 is known not to work and
is not supported at all.

Rating: 2
something for Mo July 31, 2003
Reviewer: A reader
Whats the database link u use linking to i mean whats the source database, si it
oracle or some other database, i hv used hetergenous tables and faced similar
problems, luckily hv been able to identify and get a workaround on that, let me
know if thats the architecture.

July 31, 2003 Richard -- Thanks for the question regarding "How to Rollup Hierarchy from CONNECT BY into Singe Row?", version 8.1.7
You Asked
We have an ORGANIZATIONS table that contains the rollup hierarchy for financial
reporting throughout the company.  It's teh typical structure of
ORG_ID,PARENT_ORD_ID plus a few additional attributes to select the particular
rollup structure to report (financial reporting, budget reporting, etc.)
Generating the Connect By query to obtain the hierarchy itself is fairly
straightforward, and I've taken it to the point where I generate a view with
each level of the hierarchy in a separate column.
The difficulty I'm running into now is how to cascade the hierarchy values from
parent nodes down to the current row?
For Example:
COMPANY
--DIVISION
----REGION
------DISTRICT
Row 2 would have columns COMPANY,DIVISION; row 3 would have
COMPANY,DIVISION,REGION; etc.
I considered using LAG() or FIRST_VALUE() analytic functions, but they didn't
conceptually seem to meet the need.  Would I have to resort to a pl/sql
procedure to loop thru the result set?
and we said...
Oracle 9i makes this trival and efficient -- but sys_connect_by_path is not
available in 8i:

ops$tkyte@ORA920> select rpad('*',2*level,'*')|| ename emp_name, job,
  2         sys_connect_by_path( job, '/' ) scbp
  3    from emp
  4   start with mgr is null
  5  connect by prior empno = mgr
  6  /

EMP_NAME                       JOB       SCBP
------------------------------ ---------
----------------------------------------
**KING                         PRESIDENT /PRESIDENT
****JONES                      MANAGER   /PRESIDENT/MANAGER
******SCOTT                    ANALYST   /PRESIDENT/MANAGER/ANALYST
********ADAMS                  CLERK     /PRESIDENT/MANAGER/ANALYST/CLERK
******FORD                     ANALYST   /PRESIDENT/MANAGER/ANALYST
********SMITH                  CLERK     /PRESIDENT/MANAGER/ANALYST/CLERK
****BLAKE                      MANAGER   /PRESIDENT/MANAGER
******ALLEN                    SALESMAN  /PRESIDENT/MANAGER/SALESMAN
******WARD                     SALESMAN  /PRESIDENT/MANAGER/SALESMAN
******MARTIN                   SALESMAN  /PRESIDENT/MANAGER/SALESMAN
******TURNER                   SALESMAN  /PRESIDENT/MANAGER/SALESMAN
******JAMES                    CLERK     /PRESIDENT/MANAGER/CLERK
****CLARK                      MANAGER   /PRESIDENT/MANAGER
******MILLER                   CLERK     /PRESIDENT/MANAGER/CLERK

14 rows selected.

In 8i, you can use an object type like this

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace type myArrayType as table of varchar2(30)
  2  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> column scbp format a60
ops$tkyte@ORA920> select rpad('*',2*level,'*')|| ename emp_name, job,
  2         cast( multiset( select job
  3                           from emp e2
  4                          start with empno = emp.empno
  5                         connect by prior mgr = empno ) as myArrayType ) scbp
  6    from emp
  7   start with mgr is null
  8  connect by prior empno = mgr
  9  /

EMP_NAME                       JOB       SCBP
------------------------------ ---------
------------------------------------------------------------
**KING                         PRESIDENT MYARRAYTYPE('PRESIDENT')
****JONES                      MANAGER   MYARRAYTYPE('MANAGER', 'PRESIDENT')
******SCOTT                    ANALYST   MYARRAYTYPE('ANALYST', 'MANAGER',
'PRESIDENT')
********ADAMS                  CLERK     MYARRAYTYPE('CLERK', 'ANALYST',
'MANAGER', 'PRESIDENT')
******FORD                     ANALYST   MYARRAYTYPE('ANALYST', 'MANAGER',
'PRESIDENT')
********SMITH                  CLERK     MYARRAYTYPE('CLERK', 'ANALYST',
'MANAGER', 'PRESIDENT')
****BLAKE                      MANAGER   MYARRAYTYPE('MANAGER', 'PRESIDENT')
******ALLEN                    SALESMAN  MYARRAYTYPE('SALESMAN', 'MANAGER',
'PRESIDENT')
******WARD                     SALESMAN  MYARRAYTYPE('SALESMAN', 'MANAGER',
'PRESIDENT')
******MARTIN                   SALESMAN  MYARRAYTYPE('SALESMAN', 'MANAGER',
'PRESIDENT')
******TURNER                   SALESMAN  MYARRAYTYPE('SALESMAN', 'MANAGER',
'PRESIDENT')
******JAMES                    CLERK     MYARRAYTYPE('CLERK', 'MANAGER',
'PRESIDENT')
****CLARK                      MANAGER   MYARRAYTYPE('MANAGER', 'PRESIDENT')
******MILLER                   CLERK     MYARRAYTYPE('CLERK', 'MANAGER',
'PRESIDENT')

14 rows selected.

or a cursor variable like this:

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select rpad('*',2*level,'*')|| ename emp_name, job,
  2         CURSOR( select job
  3                   from emp e2
  4                  start with empno = emp.empno
  5                 connect by prior mgr = empno ) scbp
  6    from emp
  7   start with mgr is null
  8  connect by prior empno = mgr
  9  /

EMP_NAME                       JOB       SCBP
------------------------------ ---------
------------------------------------------------------------
**KING                         PRESIDENT CURSOR STATEMENT : 3

CURSOR STATEMENT : 3

JOB
---------
PRESIDENT

****JONES                      MANAGER   CURSOR STATEMENT : 3

CURSOR STATEMENT : 3

JOB
---------
MANAGER
PRESIDENT

******SCOTT                    ANALYST   CURSOR STATEMENT : 3

CURSOR STATEMENT : 3

JOB
---------
ANALYST
MANAGER
PRESIDENT
Review & Followup
Rating: 4
sys_connect_by_path is very useful ... when it works July 31, 2003
Reviewer: vc
Please consider this:

select count(*) from (select sys_connect_by_path employee_id,',') from employees

start with employee_id=100
connect by prior employee_id=manager_id).

This query crashes each time with the error message 'ORA-03113 end-of-file on
communication channel'.

The Oracle version/OS:

Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
PL/SQL Release 9.2.0.1.0 - Production
CORE 9.2.0.1.0 Production
TNS for 32-bit Windows: Version 9.2.0.1.0 - Production
NLSRTL Version 9.2.0.1.0 - Production

The same behaviour can be seen under the 9.2.0.3 Windows and 9.2.0.1 Linux
versions.

I have a couple of other sys_connect_by_path queries crashing and producing a
core log file but this one is the simplest (uses the HR sample schema). It's a
real problem for us since we'll have to resort to writing PL/SQL in the cases
where sys_connect_by_path would suffice had it worked.

Thanks.

Followup:
and you have filed a bug with support right -- if you don't do that, not much i
can do myself.

but do contact support and reference bug2711380, possibility for a patch and a
workaround exist

Rating: 4
another 8i method August 01, 2003
Reviewer: Barbara Boehmer from Norwalk, CA USA
Here is another 8i method, using a package by Solomon Yakobson:

SQL> -- Hierarchy package by Solomon Yacobson:
SQL> CREATE OR REPLACE
  2   PACKAGE Hierarchy
  3    IS
  4           TYPE BranchTableType IS TABLE OF VARCHAR2(4000)
  5             INDEX BY BINARY_INTEGER;
  6           BranchTable BranchTableType;
  7           FUNCTION Branch(vLevel         IN NUMBER,
  8                   vValue         IN VARCHAR2,
  9                   vDelimiter      IN VARCHAR2 DEFAULT CHR(0))
 10                   RETURN VARCHAR2;
 11           PRAGMA RESTRICT_REFERENCES(Branch,WNDS);
 12  END Hierarchy;
 13  /

Package created.

SQL> CREATE OR REPLACE
  2   PACKAGE BODY Hierarchy
  3    IS
  4           ReturnValue VARCHAR2(4000);
  5    FUNCTION Branch(vLevel         IN NUMBER,
  6                 vValue         IN VARCHAR2,
  7                 vDelimiter    IN VARCHAR2 DEFAULT CHR(0))
  8                 RETURN VARCHAR2
  9      IS
 10      BEGIN
 11           BranchTable(vLevel) := vValue;
 12           ReturnValue := vValue;
 13           FOR I IN REVERSE 1..vLevel - 1 LOOP
 14             ReturnValue := BranchTable(I)|| vDelimiter || ReturnValue;
 15           END LOOP;
 16           RETURN ReturnValue;
 17    END Branch;
 18  END Hierarchy;
 19  /

Package body created.

SQL> COLUMN    emp_name FORMAT A15
SQL> COLUMN    scbp FORMAT A35
SQL> SELECT    RPAD ('*', 2 * LEVEL, '*') || ename emp_name, job,
  2          hierarchy.branch (LEVEL, job, '/') scbp
  3  FROM    emp
  4  START WITH mgr IS NULL
  5  CONNECT BY PRIOR empno = mgr
  6  /

EMP_NAME        JOB       SCBP
--------------- --------- -----------------------------------
**KING          PRESIDENT PRESIDENT
****JONES       MANAGER   PRESIDENT/MANAGER
******SCOTT     ANALYST   PRESIDENT/MANAGER/ANALYST
********ADAMS   CLERK     PRESIDENT/MANAGER/ANALYST/CLERK
******FORD      ANALYST   PRESIDENT/MANAGER/ANALYST
********SMITH   CLERK     PRESIDENT/MANAGER/ANALYST/CLERK
****BLAKE       MANAGER   PRESIDENT/MANAGER
******ALLEN     SALESMAN  PRESIDENT/MANAGER/SALESMAN
******WARD      SALESMAN  PRESIDENT/MANAGER/SALESMAN
******MARTIN    SALESMAN  PRESIDENT/MANAGER/SALESMAN
******TURNER    SALESMAN  PRESIDENT/MANAGER/SALESMAN
******JAMES     CLERK     PRESIDENT/MANAGER/CLERK
****CLARK       MANAGER   PRESIDENT/MANAGER
******MILLER    CLERK     PRESIDENT/MANAGER/CLERK

14 rows selected.

July 31, 2003 Nilanjan -- Thanks for the question regarding "How to do intermedia text search for a text with '_' ?", version 8.1.6
You Asked
I was doing the following:

SQL> create table xx (x1 number,x2 varchar2(100));

Table created.

SQL> alter table xx add constraint xx_pk primary key (x1) ;

Table altered.

SQL> insert into xx values (1,'Hello');

1 row created.

SQL> insert into xx values (2,'Hello I am here');

1 row created.

SQL> insert into xx values (3,'Hello who_is there');

1 row created.

SQL> insert into xx values (4,'Hello who_is there');

1 row created.

SQL> insert into xx values (5,'Hello  who_is_there');

1 row created.

SQL> commit;

Commit complete.

SQL> select * from xx;

        X1 X2
---------- ------------------------------------------------------------
-------------------------------
         1 Hello
         2 Hello I am here
         3 Hello who_is there
         4 Hello who_is there
         5 Hello  who_is_there

SQL> create index xx_ind_ctx on xx(x2) indextype is ctxsys.context ;

Index created.

SQL> select * from xx where contains(x2,'who') >0;

no rows selected

SQL> select * from xx where contains(x2,'there') >0;

no rows selected

SQL> select * from xx where contains(x2,'who_is') >0;

no rows selected

SQL> select * from xx where contains(x2,'hello') >0 ;

        X1 X2
---------- -----------------------------------------------------
-------------------------------
         1 Hello
         2 Hello I am here
         3 Hello who_is there
         4 Hello who_is there
         5 Hello  who_is_there

SQL> select * from xx where contains(x2,'here') >0;

        X1 X2
---------- ------------------------------------------------------------
-------------------------------
         2 Hello I am here

Questions:
1. Why the search does not work for a  word with '_' or a word that places after
a word with '_' ?
2. How to do this kind of search?
and we said...
I asked Joel Kallman, Oracle's interMedia Text Guru, to take a look at this.
And he very eloquently said:

There's actually a couple things at play here.

Firstly, by default, the lexer of interMedia Text (I'm assuming English
language here) which parses text into individual tokens will treat any
non-alphanumeric character as a token separator.  Thus, as your text
is being analyzed by interMedia Text, it will actually take the
value 'who_is_there' and convert it into individual tokens 'who', 'is',
and 'there'.  You can override this by defining custom attributes
of a lexer to indicate that you want to index the whole token, that is,
the single token 'who_is_there'.

In your specific example, though, you chose an unfortunate set of words.
interMedia Text ships with a default stoplist, that is, tokens which
should be treated as "noise", are commonly occuring, and provide little
value in a searchable index.  Thus, after interMedia Text broke
'who_is_there'
into individual tokens, each of the words 'who', 'is', and 'there' are
*all* stop words.  Hence, they won't be indexed.

If you truly want to index the '_' underscore character, you can
control this via the printjoins attribute of a custom lexer.

By definition, printjoins "specify the non-alphanumeric characters that,
when they appear anywhere in a word (beginning, middle, or end), are
processed as alphanumeric and include with the token in the Text index."

As a continuation of your example, I:

1) Drop your index
2) Insert a value with an '_' which does not contain stopwords
3) Create my lexer preference joel_lexer
4) Recreate the index, specifying my custom lexer joel_lexer
5) Find my token with an '_'.

--------
SQL> drop index xx_ind_ctx
  2  /

Index dropped.

SQL> insert into xx values (6,'Joel works for Oracle_Corporation');

1 row created.

SQL> commit;

Commit complete.

SQL> begin
  2      ctx_ddl.create_preference('joel_lexer','BASIC_LEXER');
  3      ctx_ddl.set_attribute('joel_lexer','printjoins','_');
  4  end;
  5  /

PL/SQL procedure successfully completed.

SQL> create index xx_ind_ctx on xx(x2) indextype is ctxsys.context
parameters('lexer joel_lexer');

Index created.

SQL> select x2 from xx where contains(x2,'Oracle') > 0;

no rows selected

SQL> select x2 from xx where contains(x2,'Oracle_Corporation') > 0;

X2
-------------------------------------------------------------------
Joel works for Oracle_Corporation
Review & Followup
Rating: 4
Context Example July 03, 2001
Reviewer: Tim L. from Ithaca, NY USA
I love examples.  I can see real structures, real code, and try them myself.
Several aspects of context searching and lexers illustrated here.

Rating: 5
Does it work? July 30, 2003
Reviewer: A Reader from USA
Tom,
I have tried the following:
create table t1 (id number, data varchar2(100));
insert into t1 values (1,'web-site test');
insert into t1 values (2,'web_site test');
commit;

begin
ctx_ddl.create_preference('my_lexer','BASIC_LEXER');
ctx_ddl.set_attribute('my_lexer', 'printjoins', '-_');
end;
/

Create index idx_lexer on t1 (data) indextype is ctxsys.context
parameters('LEXER my_lexer');

 select * from t1 where contains(data,'web_site')>0

        ID DATA
---------- --------------------
         2 web_site test
         1 web-site test

select * from t1 where contains(data,'web-site')>0;

no rows selected

It seems to that the intermedia text treat '_' as a single-character wildcard
and ignored the printjoins.  What did I do wrong?

Thank you for your help.

Followup:
there are various constructs to achieve this but what I do is use {} to "quote"
the strings.  If you search on asktom for

how now brown cow

not only does it surprisingly find quite a few hits but it prints out the search
string I use:

{how} and {now} and {brown} and {cow}

ops$tkyte@ORA920> select * from t1 where contains(data,'{web_site}')>0;

        ID DATA
----------
---------------------------------------------------------------------------------
-------------------
         2 web_site test

ops$tkyte@ORA920> select * from t1 where contains(data,'{web-site}')>0;

        ID DATA
----------
---------------------------------------------------------------------------------
-------------------
         1 web-site test

Rating: 5
The wildcard cannot be used when using { }. July 31, 2003
Reviewer: A Reader from USA
Tom,
The problem with using {} is that the wildcard symbol cannot be used.
select * from t1 where contains(data,'{web%}')>0;
or
select * from t1 where contains(data,'{web-%}')>0;
will not return any rows.
When people do a search, they can stop at anywhere by using a wildcard.
How should we handle this?
Thank you again for your help.

Followup:
as i said, many ways to do this, you can of course escape individual characters
as well:

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t1 where contains(data,'web\_site')>0;

        ID DATA
----------
---------------------------------------------------------------------------------
-------------------
         2 web_site test

ops$tkyte@ORA920> select * from t1 where contains(data,'web\-site')>0;

        ID DATA
----------
---------------------------------------------------------------------------------
-------------------
         1 web-site test
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/text.920/a96518/cqspcl.htm#1360
is all about escaping special characters...

July 31, 2003 Jeff -- Thanks for the question regarding "Public and private Rollback segments", version 8.1.7
You Asked
Tom,

I have questions on Rollback tablespace (and Temporary tablespace).

1. When creating a tablespace for Rollback, should I use LMT or not? If yes, how
to estimate the SIZE and UNIFORM SIZE? Any document/post on this? I believe the
performance of RBSs also depends on the structure of the tablespace they live
in.

2. What are the differences between Public RBS and Private RBS? I see one
difference is one will be online when the db starting up. How do they perform
differently in db normal time?

3. Same question on creating Temporary tablespace as question 1. I have a
temporary tablespace that has 4 GB size, but only uses 1% of it. Should I do
something on it?

I enjoy reading your answers and your book, and so happy that I can post a
question on Saturday. Thanks a lot.

Best regards,

Jeff
and we said...
1) well, since all extents in a rollback segment should be exactly the same size
-- they are an EXCELLENT candidate for LMTS with uniform extent sizes (since
that helps to enforce the "should be all the same size").

You can use
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:233214228495
to help you pick a size (or goto 9i and just use an undo tablespace and forget
about it)

2) they perform no differently.  In non OPS/RAC configurations -- use public,
they online themselves (no init.ora parameters, i like that)

3) not if you can spare the 4gig, someday you will most likely need it for
something.  On many operating systems, temp uses a sparse file so it's not
really even allocated (many operating systems that support sparse files like
UNIX)
Review & Followup
Rating: 5
Private rollback segments July 31, 2003
Reviewer: Steve from Montreal
Platform: Oracle 8.1.7 on Solaris 8.

Q1: Any truth to the following statement (the part about
    unable to be flushed after commits):

  "As well the system had wrongly configured rollback
   segments  - we found all of them being privately owned
   (impossible to be flushed after commits). So we made
   them public - the way they are supposed to be.
   We still do not know how it was possible, initially, to
   have RBSs created private !)"

Q2: Other than onlining differences, is there any other
    difference between public and private RBS in non
    RAC/OPS?

Thank you Tom,

Steve

Followup:
q1) that statement makes utterly "no sense"

it is nonesense.

the only difference between a public and private rbs is

a) a public rbs will automagically be onlined during instance startup
b) a private rbs must be either manually onlined OR specified in the init.ora
rollback_segments=(....) paramter

private rollback segments are generally used with OPS/RAC so each instance can
specify its OWN set of rollback segments -- to reduce contention.

q2) nope.

Rating: 5
Thanks July 31, 2003
Reviewer: Steve from Montreal
Blood Pressure now back to normal.

Thanks for that one Tom.

Steve

Rating: 5
July 31, 2003
Reviewer: A reader

Rating: 5
Some refuse to believe! July 31, 2003
Reviewer: Steve from Montreal
ARG. They refuse to believe (if only they knew who Tom is....).

 "The rollback segments were owned by the SYS user
  and the application is another user.

   Because of that the watermark never comes back after
   commits (flush the rollback segments) in this particular
   situation"

Steve

Followup:

they are total losers, with a capital L

they are "making stuff up", totally...  plain and simple.

they know not of what they speak of, hopefully they do not designate themselves
"DBAs"

will they believe the documentation that private RBS's are kosher?  It is so
written:
http://otn.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76956/rollbak.htm#518
Choose Between Public and Private Rollback Segments

A private rollback segment is one that is acquired explicitly by an instance
when the instance opens the database if it is named in the ROLLBACK_SEGMENTS
parameter in the initialization parameter file. A private rollback segment can
also be acquired by specifically bringing it online by manually issuing a
statement to do so. Public rollback segments form a pool of rollback segments
that any instance requiring a rollback segment can use.

A database with the Parallel Server option can have only public segments, as
long as the number of segments is high enough that each instance opening the
database can acquire at least one rollback segment in addition to its SYSTEM
rollback segment. You may also use private rollback segments when using the
Oracle Parallel Server.

If a database does not have the Parallel Server option, public and private
rollback segments are identical.

Rating: 5
Thanks August 01, 2003
Reviewer: Steve from Montreal
Thank you very very much (again) Tom.

July 31, 2003 Laura -- Thanks for the question regarding "Database Link and tnsnames.ora", version 8.1.7
You Asked
Hello Tom:

Apologies in advance for the lengthy background information:

On a Solaris 8 box, I have two 8i and one 9i database.  The listener for all
three is the 8i version; the 9i listener is not running.  I created public db
links in the 9i database to each of the 8i databases, and verified they were
active.  The 9i db links were created using the syntax "create public database
link linkname connect to user identified by password using 8i_service_name."
The 8i_service_name = database name.  Also, in the 9i tnsnames.ora, I created an
alias for the 9i database.

Although user oracle owns all three databases, I source different environment
files before starting/stopping 8i or 9i databases.

All that said:

On Solaris, having sourced my 9i environment, if I connected to the 9i database
using syntax "connect system/password", the statement "select * from
dual@8i_database_link" worked correctly.

However, if I connected to my 9i database using syntax "connect
system/password@9i_alias", the the same statement failed with error "ORA-12154:
TNS could not resolve service name."
This is the alias that is defined in the 9i tnsnames.ora file.

Last, on Windows, I used a query tool to connect to the 9i database, with a
local tnsnames.ora file where the service name = database name.  The statement
"select * from dual@8i_database_link" worked correctly.

The db link failed to work when I connected to the database using the tns alias.
 Other non-db link queries against the database worked correctly, and I verified
that the database information in v$instance was the same when connected via the
tns alias or the standard database name.

What worked is why I'm submitting this question for consideration:  when I
updated the 8i tnsnames.ora file with the 9i database alias (which I had only
placed into the 9i tnsnames.ora file), the db link finally worked.

Why would a 9i database and 9i db link refer to the 8i tnsnames.ora file to
resolve the connection via the link?

Thank you

LHSallwasser
and we said...
when you connect "system/manager" -- the dedicated server is spawned by you, it
inherits your environment.  so the dedicated server finds the tnsnames.ora by
looking in the environment which is set for 9i.

When you connect "system/manager@tns" -- the dedicated server is spawned by the
listener, it inherits the listeners environment.  so now the dedicated server
finds the tnsnames.ora by looking in the environment which is set for 8i!

You should have a SINGLE, COMMON tnsnames.ora file on this server

All environments should use TNS_ADMIN (environment variable) to locate this
single file.

Also -- the supported configuration is to use the 9i listener to access 9i and
8i.  It is not supported to use the listener of the lower release like you are.

Here is a detailed example of the system/manager vs system/manager@tns

if you use scott/tiger, then you (your sqlplus) forks off the dedicated server.
That is what reads the tnsnames.ora.  It (dedicated server) inherits YOUR
environment (oracle_home, tns_admin).

When you use scott/tiger@database -- the dedicated server inherits the LISTENERS
environment (oracle_home,tns_admin)

scott/tiger -- dedicated server could be reading a TOTALLY different
tnsnames.ora then
scott/tiger@database  -- is.

Consider the following:

1) I copied the tnsnames.ora from /var/opt/oracle into my home directory.
2) I added an entry tomsentry for a database to that file only
3) I created a database link ora817dev.us.oracle.com connect to scott identified
by tiger using 'tomsentry.world'

Now, the following are cuts and pastes:

$ sqlplus scott/tiger
SQL*Plus: Release 8.1.5.0.0 - Production on Wed Jan 29 18:45:20 2003
(c) Copyright 1999 Oracle Corporation.  All rights reserved.
Connected to:
Oracle8i Enterprise Edition Release 8.1.5.0.0 - Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.0.0 - Production

scott@ORA815> select * from dual@ora817dev.us.oracle.com;

D
-
X

scott@ORA815> set echo on
scott@ORA815> @getspid
scott@ORA815> select a.spid dedicated_server,
  2             b.process clientpid
  3    from v$process a, v$session b
  4   where a.addr = b.paddr
  5     and b.audsid = userenv('sessionid')
  6  /

DEDICATED CLIENTPID
--------- ---------
11959     11958

scott@ORA815> !/usr/ucb/ps -auxwwe | grep 11959 | grep -v grep
ora815   11959  0.1  3.68954453776 ?        S 18:45:20  0:00 oracleora815
(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq))) HOME=/export/home/tkyte
PATH=/export/home2/ora815/bin:/opt/gnu/bin:/usr/local/bin:/usr/ucb:/usr/bin:/expo
rt/home/tkyte/bin:/usr/ucb:/export/home/ora817/bin:/export/home/tkyte/src/sqlstuf
f:/usr/openwin/bin:/usr/etc:.:/usr/ccs/bin:/usr/sbin LOGNAME=tkyte HZ=100
TERM=vt100 TZ=US/Eastern SHELL=/usr/bin/csh MAIL=/var/mail/tkyte
PWD=/export/home/tkyte USER=tkyte ORACLE_HOME=/export/home2/ora815
ORACLE_SID=ora815 NLS_LANG=AMERICAN_AMERICA.WE8ISO8859P1
ORACLE_PROC_MAKEFILE=/export/home2/ora815/precomp/demo/proc/demo_proc.mk
LD_LIBRARY_PATH=/export/home2/ora815/lib
SQLPATH=/net/aria/export/home/tkyte/src/sqlstuff
MANPATH=/usr/openwin/share/man:/usr/man:/usr/local/man:/opt/GCC2723/man
CLASSPATH=.:/export/home2/ora815/jdbc/lib/classes12.zip
LARCH_PATH=.:/export/home/tkyte/lclint-2.4b/lib
LCLIMPORTDIR=/export/home/tkyte/lclint-2.4b/imports PW=tkyte/tkyte
TNS_ADMIN=/export/home/tkyte ORA_NET2_DESC=9,12

Notice how the dedicated servers environment has my tns_admin...

scott@ORA815> exit
Disconnected from Oracle8i Enterprise Edition Release 8.1.5.0.0 - Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.0.0 - Production

$  sqlplus scott/tiger@ora815.us.oracle.com
SQL*Plus: Release 8.1.5.0.0 - Production on Wed Jan 29 18:46:04 2003
(c) Copyright 1999 Oracle Corporation.  All rights reserved.
Connected to:
Oracle8i Enterprise Edition Release 8.1.5.0.0 - Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.0.0 - Production

scott@ORA815> select * from dual@ora817dev.us.oracle.com;
select * from dual@ora817dev.us.oracle.com
                   *
ERROR at line 1:
ORA-12154: TNS:could not resolve service name

scott@ORA815> @getspid

DEDICATED CLIENTPID
--------- ---------
11996     11994

scott@ORA815> !/usr/ucb/ps -auxwwe | grep 11996 | grep -v grep
ora815   11996  0.1  3.68947253800 ?        S 18:46:04  0:00 oracleora815
(LOCAL=NO) HOME=/export/home/rmattama
PATH=/export/home/ora817/bin:/opt/gnu/bin:/usr/local/bin:/usr/ucb:/usr/bin:/expor
t/home/rmattama/bin:/usr/ucb:/export/home/ora817/bin:/export/home/rmattama/src/sq
lstuff:/usr/openwin/bin:/usr/etc:.:/usr/ccs/bin:/usr/sbin:/export/home/sdillon/sa
il/bin LOGNAME=rmattama HZ=100 TERM=vt100 TZ=US/Eastern SHELL=/usr/bin/csh
MAIL=/var/mail/rmattama _INIT_PREV_LEVEL=S _INIT_RUN_LEVEL=3 _INIT_RUN_NPREV=0
_INIT_UTS_ISA=sparc _INIT_UTS_MACHINE=sun4u _INIT_UTS_NODENAME=aria-dev
_INIT_UTS_PLATFORM=SUNW,Ultra-4 _INIT_UTS_RELEASE=5.7 _INIT_UTS_SYSNAME=SunOS
_INIT_UTS_VERSION=Generic_106541-20
PWD=/export/home/rmattama/platform/people/owb USER=rmattama
ORACLE_HOME=/export/home2/ora815 ORACLE_SID=ora815
ORACLE_PROC_MAKEFILE=/export/home/ora817/precomp/demo/proc/demo_proc.mk
LD_LIBRARY_PATH=/export/home/ora817/lib
SQLPATH=/net/aria/export/home/tkyte/src/sqlstuff
MANPATH=/usr/openwin/share/man:/usr/man:/opt/gnu/man:/opt/GCC2723/man
CLASSPATH=/export/home/ora817/jdbc/lib/classes111.zip:/export/home/tkyte/j:/expor
t/home/oracle8i/sqlj/lib/runtime.zip:.
LARCH_PATH=.:/export/home/tkyte/lclint-2.4b/lib
LCLIMPORTDIR=/export/home/tkyte/lclint-2.4b/imports PW=tkyte/tkyte
ORACLE_BASE=/export/home/ora817 ORA_NET2_DESC=20,23

In the second case -- you can see that in the environment of our dedicated
server, there is NO tns_admin.

This is undoubtably what you are seeing -- that the environment of the listener
is DIFFERENT then your environment and when you connect direct, you are reading
a wholly different tnsnames.ora file.
Review & Followup
Rating: 3
inherits the listeners environment July 30, 2003
Reviewer: Kiro from Bulgaria
Hi Tom
You said:
When you connect "system/manager@tns" -- the dedicated server is spawned by the
listener, it inherits the listeners environment
I am a little bit confused about it. I am using locally installed sqlplus and
have entry in registry "TNS_ADMIN" pointing to my local
tnsnames.ora(c:\orant\tnsnames.ora). Listener in on another(remote) Win2000 box.

By my opinion every time when I have connected to "system/manager@tns" this
local tnsnames.ora will be used instead of tnsnames.ora from listeners
environment. Am I right or not?
Regard
Kiro

Followup:
you are wrong, the example above proves that.

when you connect via a listener, the environment comes from THAT LISTENER.  if
you use an 8i listener, you'll have an 8i environment;

when you connect direct, without the listener, the environment comes FROM YOU.

that was the entire point of the exercise above.

Rating: 4
July 30, 2003
Reviewer: kiro from Bulgaria
Thanks Tom

Rating: 3
inherits the listeners environment July 30, 2003
Reviewer: Haratsaris from Rio de Janeiro, RJ Brazil
Tom,
I have a Oracle Applications 11.0.3 environment with 8.1.7 database that uses
different TNS_ADMINS because I´ve got some special services like Report Agents
and Transaction Managers (FNDFS,OEORPC) which belongs from a different Oracle
Version (8.0.6). Could I just use one 8.1.7 listener for both services (
database and OAP Services) rather than another listener 8.0.6.
Thanks a lot !

Followup:
yes, you should be able to as far as I know.  I don't have an environment set up
to test it out however.  But you should be able to merge the two tnsnames into
one and use just one.  I don't know if APPS has any special requirements in this
area.

Rating: 5
Database Link and tnsnames.ora response July 30, 2003
Reviewer: LHollister Sallwasser from San Diego, CA USA
Thank you for the explanation and the test case.  This also clarified other tns
issues I've experienced.  We will configure as recommended, with the 9i listener
serving the 8i databases, as soon as possible.  One observation regarding the
review posted by Haratsaris:  given Tom's response regarding environments that
are determined by the listener, you may want to keep the 8.0.6 listener working
for the admin and middle tiers.  There are library files that exist only in the
old 8.0.6 home, and you may find that certain concurrent requests or external
interfaces fail if these files can't be found by user apps.  I run 11.5.8 ERP
and CRM.

Followup:
it'll set the oracle-home for you...

Rating: 5
Interesting behavior... July 30, 2003
Reviewer: Mark Wooldridge from warrenton, VA.
As always,  your knowledge of Oracle seems to be endless.

Sorry for asking a question in a review,  but the a previous reviewers comment
make me think.  Would the automatic_ipc parameter affect what environment you
would get?  I could see where if set to yes and if on the same box the
connection of system/manager@tns might get the local environment of the user
connected to the database.

Followup:
automatic-ipc means you used the listener.

ipc is just interprocess communication, it doesn't mean you forked the process
off and the only way to inherit an environment is to be the child process.  the
process would still be the child of the listener.

Rating: 3
Database Link and tnsnames.ora response July 31, 2003
Reviewer: Haratsaris from Rio de Janeiro, RJ Brazil
Ok, So if I use symbolic links from 8.1.7 to 8.0.6 libraries (8.1.7 missing
libraries), 8.0.6 services should work with 8.1.7 listener, am I right ?

Followup:
no no -- you just put the right oracle-home in the configuration files.

Rating: 4
Excerllent! It solves a mystery in our replication environment. October 14, 2003
Reviewer: A reader

July 31, 2003 -- Thanks for the question regarding "date calculation..", version 8.0.5
You Asked
hai,

I have a basic question.  I am having a table like...

idno number(3)
name varchar2(15)
..
..
DOB  date;

In the above table data is already loaded.  While on form(query
mode) I have to calculate the age of customer to till date...ie
age should be sysdate - DOB. if the age is less than 21 years
he/she will be eligible for bonus.

In brief how to calculate age of the customer with help of DOB
field. If the customer's age is 21yrs and 1 day, then also he is
NOT entitled for bonus.

please help

thanks

regs
priya
and we said...
select months_between( sysdate, dob )
  from t
 where months_between( sysdate, dob ) <= 21*12
/

should get it.
Review & Followup
Rating: 4
date Calculation (Age) April 25, 2001
Reviewer: dtphan@lasd.org from CA USA
You did it again, Tom !
I needed to display the Age (Event Date - DOB) & was tinkering with yyyy
arithmetics. It was a mess !
Your months_between( sysdate, dob ) is much much more simple. May I say,
elegantly simple !
Thank you again,
DP

Rating: 4
Another date calculation March 19, 2003
Reviewer: Bharath Nair from New York, NY USA
Tom,

     Please look at the following queries.

  1* SELECT ADD_MONTHS(TO_DATE('04/26/2001','MM/DD/YYYY') + 4, 3 + (5*12)) FROM
DUAL
SQL> /

ADD_MONTH
---------
31-JUL-06

SQL> ED
Wrote file afiedt.buf

  2* SELECT ADD_MONTHS(TO_DATE('04/26/2001','MM/DD/YYYY'),3 + (5*12)) + 4  FROM
DUAL
SQL> /

ADD_MONTH
---------
30-JUL-06

     In the first case, I am adding 4 days to '04/26/2001' and then forwarding
it with 99 months (3 + (5*12))

     In the second case, I am essentially doing the same, in a different order.
I am adding 99 months to
'04/26/2001' first and then adding 4 days.

     Shoud'nt the result be the same for both the queries ?

Thanks,
Bharath

Followup:
No (obviously ;)

The rules for ADD_MONTHS is:

if you add months to a date that is the LAST DAY OF A GIVEN MONTH, the results
will always be the last day of the target month.  That is, add one month to
28-feb-2003 and you get 31-mar-2003.

So, in the first case, you added months to the last day of april -- that'll
result in the last day of the month that is 99 months away.

In the second case, you added 99 months to a "non last day" in april, resulting
in the same ordinal date in the target month, then you added 4 days to that.

Rating: 4
Good, BUT... July 31, 2003
Reviewer: Benoit Hebert from Quebec, Canada
SQL> select to_char(ERP_D_NAISS, 'DD/MM/YYYY', 'NLS_DATE_LANGUAGE=french') AS
"Date of birth" FROM entree_reg_particip;

Date of bi
----------
25/08/1956
12/09/2046

Even though I input the date using 4 digits for the year, using
to_date(pDate_Birth, 'DD/MM/YYYY'), the thing can't recognize that this is a
date that should be BEFORE 1950...
This is the Y2K bug all over again!

Any ideas on how to solve it - short of deleting the database? Which is
feasible, since it was mounted only last week, and doesn't contain any data
yet...

Followup:
I don't understand?

you show us the output.

what pray tell was the INPUT?

Rating: 4
date calculation August 01, 2003
Reviewer: murali.S from India
Very Useful

July 31, 2003 john -- Thanks for the question regarding "Find table being reference.", version 8.1.7
You Asked
Hi Tom,

  How do I find table that is being referenced by another table?

  Thanks in advance

  john britto.
and we said...
if you mean "foreign key wise", then:

ops$tkyte@ORA920LAP> select table_name
  2    from user_constraints
  3   where r_constraint_name in
  4         ( select constraint_name
  5             from user_constraints
  6            where table_name = 'T1'
  7              and constraint_type in ( 'P', 'U' ) )
  8  /

just replace T1 with your table name
Review & Followup
Rating: 4
referenced by another table ? Query August 01, 2003
Reviewer: Murali.S. from India
The Answer was really useful. I did not know that the referenced Tables can be
found with SQL Query.

July 31, 2003 jignesh -- Thanks for the question regarding "Forward Declaration in Package", version 8.1.7
You Asked
Hi Tom,

Could you please explain the term "forward declaration" in packages.  What
exactly does it means?.  when do we need it? ? ?
and we said...
it is unusual to use it, you use it when you have a procedure you want to code
for whatever reason AFTER you reference it.  you can "forward declare it"

say you have:

ops$tkyte@ORA920LAP> create or replace package demo_pkg
  2  as
  3          procedure p;
  4  end;
  5  /

Package created.

and in the body you have this:

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create or replace package body demo_pkg
  2  as
  3          procedure p
  4          is
  5          begin
  6                  p2;
  7          end;
  8
  9          procedure p2
 10          is
 11          begin
 12                  null;
 13          end;
 14  end;
 15  /

Warning: Package Body created with compilation errors.

ops$tkyte@ORA920LAP> show errors
Errors for PACKAGE BODY DEMO_PKG:

LINE/COL ERROR
-------- -----------------------------------------------------------------
6/3      PLS-00313: 'P2' not declared in this scope
6/3      PL/SQL: Statement ignored

that fails, p doesn't know about p2 yet, so you can:

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create or replace package body demo_pkg
  2  as
  3          procedure p2;
  4
  5          procedure p
  6          is
  7          begin
  8                  p2;
  9          end;
 10
 11          procedure p2
 12          is
 13          begin
 14                  null;
 15          end;
 16  end;
 17  /

Package body created.

forward declare p2, but it would be BETTER to just define p2 at the top of the
package
Review & Followup
Rating: 5
Great August 01, 2003
Reviewer: A reader
Great Answer

Rating: 5
Very clear August 01, 2003
Reviewer: jignesh from INDIA
Great answer with example asusual.

Thanks a LOT.

July 31, 2003 Rakesh -- Thanks for the question regarding "close cursors", version 817
You Asked
Hi Tom

 In an interview i was asked the following question . Is it necessary to close
explicit cursors, if they are not closed will Oracle close them automatically.

What would be the repercussions of not closing cursors.

Regards
Rakesh
and we said...
depends on their scope.

if the explicit cursors were defined as local variables -- no need to close, we
close em when they go out of scope.

Bad practice (if you open it, close it) but works just fine.

If they are defined globally in a package body/spec - -then you MUST close them
before reopening.  But, I would say it is a bad practice to use globals
frequently so I would not put cursors in a package body/spec outside of a
procedure/function.
July 31, 2003 Anil -- Thanks for the question regarding "Database name and Multilevel Nested Objects", version 9.0.1
You Asked
Hi tom

1.Can we rename  database name changing the db_name parameter?
 if yes how?

2. About Multilevel Nested Objects

Here is the sample from 9.0.1 Oracle documentation

SQL.CREATE TYPE satellite_t AS OBJECT (
  name        VARCHAR2(20),
  diameter    NUMBER);

SQL>CREATE TYPE nt_sat_t AS TABLE OF satellite_t;

SQL.CREATE TYPE planet_t AS OBJECT (
  name        VARCHAR2(20),
  mass        NUMBER,
  satellites  nt_sat_t);

SQL>CREATE TYPE nt_pl_t AS TABLE OF planet_t;

But when we try to create the Index org table on nested table it is giving error

SQL> CREATE TABLE stars (
  2    name     VARCHAR2(20),
  3    age      NUMBER,
  4    planets  nt_pl_t)
  5  NESTED TABLE planets STORE AS planets_tab
  6    ( PRIMARY KEY (NESTED_TABLE_ID, name)
  7      ORGANIZATION INDEX COMPRESS
  8      NESTED TABLE satellites STORE AS satellites_tab );
CREATE TABLE stars (
*
ERROR at line 1:
ORA-02320: failure in creating storage table for nested table column PLANETS
ORA-00922: missing or invalid option

Why this error is occuring?

Regards

Anil
and we said...
1) see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:318216852435
2)
they missed a set of parens:

ops$tkyte@ORA920LAP> CREATE TABLE stars (
  2    name     VARCHAR2(20),
  3    age      NUMBER,
  4    planets  nt_pl_t)
  5  NESTED TABLE planets STORE AS planets_tab
  6    ( (PRIMARY KEY ( NESTED_TABLE_ID, name) )
  7      ORGANIZATION INDEX COMPRESS
  8      NESTED TABLE satellites STORE AS satellites_tab );

Table created.

works fine.
August 01, 2003 Ravi -- Thanks for the question regarding "Duplicate Records In a Table", version 8.1.6.x
You Asked
Hi Tom ,

I would like to know if there is a faster way to find out duplicate rows in a
table , different from what I am listing below:

I am trying to update a column called distinct_qualifier_flag to 'Y' for each
distinct row in a table . Currently I am using the sql below: This is not
performant at all , when there are lot of rows in this table . I need a better
method to identify duplicate rows in a table .

Here is the sql

update sample1 a
set distinct_qualifier_flag = 'Y'
where rowid = (select min(rowid)
               from sample1 b
               where a.context = b.context
               and   a.attribute = b.attribute
               and a.value = b.value);

In the above table sample1 there will be multiple rows with same
context , attribute and value.
and we said...
try (after having an index on (context, attribute, value) and using the CBO of
course)

update sample1
   set distinct_qualifier_flag = 'Y'
 where rowid IN ( select min(rowid)
                    from sample1
                   group by context, attribute, value );

When I run the following tests:

ops$tkyte@ORA717DEV.US.ORACLE.COM> create table t as select * from all_objects;
Table created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> insert /*+ append */ into t select * from t;
16985 rows created.

(commit that and repeat -- I had about 250k rows in T after I was done)

ops$tkyte@ORA717DEV.US.ORACLE.COM> create index t_idx on
t(owner,object_type,status);
Index created.

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

Table analyzed.

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> set autotrace on
ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> update t a
  2  set secondary = 'Y'
  3  where rowid = (select min(rowid)
  4                 from t b
  5                 where a.owner = b.owner
  6                 and   a.object_type = b.object_type
  7                   and a.status = b.status);

78 rows updated.

Execution Plan
----------------------------------------------------------
   0      UPDATE STATEMENT Optimizer=CHOOSE (Cost=107 Card=2718 Bytes=78822)
   1    0   UPDATE OF 'T'
   2    1     FILTER
   3    2       TABLE ACCESS (FULL) OF 'T' (Cost=107 Card=2718 Bytes=78822)
   4    2       SORT (AGGREGATE)
   5    4         INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=4 Card=405
Bytes=10935)

Statistics
----------------------------------------------------------
         42  recursive calls
        106  db block gets
       9747  consistent gets
          1  physical reads
      19308  redo size
       1017  bytes sent via SQL*Net to client
       1005  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
         78  rows processed

that plan was basically processed like this would be:

   for x in ( select * from T )
   loop
         select min(rowid) into l_tmp from t where ....;
         if ( x.rowid= l_tmp )
         then
               update t .... set ... where rowid = x.rowid;
         end if;
   end loop

where as the other plan will:
ops$tkyte@ORA717DEV.US.ORACLE.COM> update t
  2     set secondary = 'Y'
  3   where rowid IN ( select min(rowid)
  4                      from t
  5                     group by owner, object_type, status );

78 rows updated.

Execution Plan
----------------------------------------------------------
   0      UPDATE STATEMENT Optimizer=CHOOSE (Cost=3720 Card=913114
Bytes=14609824)
   1    0   UPDATE OF 'T'
   2    1     NESTED LOOPS (Cost=3720 Card=913114 Bytes=14609824)
   3    2       VIEW OF 'VW_NSO_1' (Cost=3384 Card=336 Bytes=2352)
   4    3         SORT (UNIQUE) (Cost=3384 Card=336 Bytes=9072)
   5    4           SORT (GROUP BY) (Cost=3384 Card=336 Bytes=9072)
   6    5             INDEX (FULL SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1196
Card=271760 Bytes=7337520)
   7    2       TABLE ACCESS (BY USER ROWID) OF 'T' (Cost=1 Card=271760
Bytes=2445840)

so it full scanned the index - picking off the min rowid for each "key" and then
used that subresult to go back to the table to update just the rows you want.

Your mileage may vary here -- its a function of the size of T, the number of
rows actually being updated, In this case however:

Statistics
----------------------------------------------------------
          0  recursive calls
         80  db block gets
       1287  consistent gets
          0  physical reads
      19092  redo size
       1017  bytes sent via SQL*Net to client
        936  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          6  sorts (memory)
          0  sorts (disk)
         78  rows processed

TKPROF bears out a better response in this case as well:

update t a
set secondary = 'Y'
where rowid = (select min(rowid)
               from t b
               where a.owner = b.owner
               and   a.object_type = b.object_type
                 and a.status = b.status)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      2.88       2.91          0       9745        106          78
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      2.89       2.92          0       9745        106          78

update t
   set secondary = 'Y'
 where rowid IN ( select min(rowid)
                    from t
                   group by owner, object_type, status )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      1      1.59       1.59          0       1287         80          78
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      1.59       1.60          0       1287         80          78
Review & Followup
Rating: 4
Thanx for the quick help October 28, 2001
Reviewer: A reader

Rating: 5
partial duplicated records in a table July 31, 2003
Reviewer: steve from NYC
Hi Tom,

I'd like to find all partial duplcated records.
In the following table, If C1,c2,C3,C4 of any 2 records are same
they are considered as the duplicate. And one specical requirement
is that if a value in a column is NULL, it is considered to be an ANY value
i.e., it will match any value. Can it be done by a SQL?

Table
c0    c1     c2    c3     c4
----------------------------------
101    a    1    2    3
102    a    1    2    3
103    a    1    2
104    a
105    b    1        1
106    b    2    3    4
108    c    2    3    4
109    c        3    4
110    c            2
111    b    1    1
112    b    2    4
113    d    1    4    5
114    d    1    4    5
115    e    34    5    6
116    e    34    5    8

Expected Result:
----------------------------------
101    a    1    2    3
102    a    1    2    3
103    a    1    2
104    a
111    b    1    1
105    b    1        1
108    c    2    3    4
109    c        3    4
113    d    1    4    5
114    d    1    4    5

Thanks!

Steve

Followup:
I do not understand the output from the inputs?

Rating: 5
partial duplicated records in a table July 31, 2003
Reviewer: Steve
Hi Tom,

Let me explain to you. For example,

c0     c1   c2   c3   c4
------------------------
101    a    1    2    3
102    a    1    2    3
103    a    1    2
104    a

1) 101, 102 are duplicates, it shows in output.
2) 103 have a null on column c4, because NULL is an ANY value (or take as *, a
wildcard) so 103 considered to be same as 102.
3) 104 has 3 Nulls, so it is same with any row which column c1 is 'a'.

Thanks!

Steve

Followup:
nope, don't see a way to do that in SQL.  sorry.

August 01, 2003 razvan -- Thanks for the question regarding "tables - whats the difference between lookup and master detail.", version 7.3.2
You Asked
hello tom,

please can you explain me what is the difference between
lookup tables and the tables in master detail relation?

thanks a lot
razvan
and we said...
A lookup table is generally a "code to a value".  For example:

create table lookup ( code int primary key,
                      value varchar2(255) );

insert into lookup values ( 1, 'Widget' );
insert into lookup values ( 2, 'Gadget' );
insert into lookup values ( 3, 'Thingy' );

I would use the lookup table to convert FROM a code to a value
for display.

I would create other tables that have something like:

create table t
( x int primary key,
  ...
  code int references lookup
)
/

and i would write lots of queries that look like:

select t.*, l.value
  from t, lookup l
 where t.code = l.code
   and t.c .....

Here, technically speaking, LOOKUP is the master table and T is
a detail table in a master detail relation.  We never query
"through" lookup though, we always goto lookup from the table T.

A real master detail, EMP and DEPT for example, is more then a
lookup.  The DEPT table is considered to be the MASTER table and
EMP the detail table.  DEPT is a table we might typically query
FROM to get the details (this is the major difference between a
lookup and a master/detail -- we query through the master table
to get the details.  We do not query through the lookup table to
get the details, we query the details and join that to the
lookup always).
Review & Followup
Rating: 5
Difference between lookup and master table January 06, 2003
Reviewer: Sean from NJ. USA
Hi Tom,

I agree with you about the definition of lookup table, but I always thought that
dept is a lookup table since we always try to get dept name by this query:

select e.ename, d.dname from emp e, dept d
where e.deptno = d.deptno;

Although sometimes we can get the location of dept:

select d.name, d.loc from dept d where d.deptno= 10;

Is it safe to say that if master table has only two columns, we can call it
lookup table?  If master table has more than two columns, we don’t call it
lookup table.

Best,

Sean

Followup:
Depends on your perspective.  To me, DEPT is a master table in a parent child
relationship.  I go from DNAME=SALES to the list of EMPs.  It all depends.

A master table is the same as a lookup table when you query from child to
parent.
A lookup table is a master table.

No, it is not safe to say anything -- lookup tables can have more then 2 columns
(zip code -> state -> region).  Masters can have 2 -- no reason they could not.

It is all "semantics", simply use the term that has more meaning.

Rating: 5
using rule engine to design lookup tables July 01, 2003
Reviewer: Sean from NJ, USA
Hi Tom,

We are in the process to redesign our database.  Currently, there are a lot of
lookup tables, such as ref_unit, ref_sample_frequency (more than seventy).  In
general, each lookup table contains less than 30 records.  One of our developers
suggested that we should use the principle of rule engine to define the lookup
values.  Only a few tables are needed to cover all lookup values, which would
cut total tables to half.

I could not find any info on the web to explain how to use the principle of rule
engine to design relational database though there are a few applications using
rule engine.

My questions:
(1)    Is the principle of rule engine a good way to design database to store
lookup values?
(2)    If it is, how?

Thanks so much for your help.

Best,

Sean

Followup:
is this like the three laws of robotics?   Not sure with the "principle of rule
engine" is.

Rating: 5
Go Generic ? July 02, 2003
Reviewer: Matt from UK
I know Tom isn't a big fan of generic table designs but in the case of lookups
we've found a generic lookup table with a domain column can handle 80% of our
needs. We then put views on top of these to give the appearance of the
individual lookup table. I think Oracle Designer uses similiar principle with
its CG_Ref_Codes table.

Followup:
so, if you end up with a ton of views -- why not tables?

with tables, fkeys work properly.
with views, they do not.

with tables, hot spots are spread out.
with views, they are not.

with tables, things are "clear" -- a fkey relationship clearly documents which
set of domain values are allowed.

with views, they are not.

generic is fine, just understand the downsides.  It is always a tradeoff.

Rating: 5
Re: Go Generic? July 02, 2003
Reviewer: Tony Andrews from London, UK
> I know Tom isn't a big fan of generic table designs but in the case of lookups

we've found a generic lookup table with a domain column can handle 80% of our
needs. We then put views on top of these to give the appearance of the
individual lookup table. I think Oracle Designer uses similiar principle with
its CG_Ref_Codes table.

Yes, Designer does that.  But what is the point?  Here are the cons:
* You can't use foreign keys for integrity, unless you:
(a) include the domain column in all the child tables and
(b) put a check constraint on the domain column in the child tables
* You are forced to use VARCHAR2(quite big) for all code values, to accommodate
all cases
* You have to create views to mimic the individual lookup tables
* Either you risk users setting up inappropriate lookup codes, or you build your
own datatype/length rules for each domain and apply them in your maintain lookup
screen

What are the pros?  The only one as far as I know is: you build one fairly
complex lookup maintenance screen instead of N trivially simple lookup
maintenance screens.

Does that one pro outweigh all the cons?  (Rhetorical question!)

Rating: 5
Foreign Keys July 02, 2003
Reviewer: Matt from UK
Would you really want foreign keys to all your lookups ? Is that not where the
crossover is between Master Detail, and simple lookups. We tend to use
check_constraints on the table column, rather than an FK.

The benefits are we don't need to maintain many lookup modules and tables. A
single module can handle it. Yes there may be some thought required about
implementing datatypes and sizes - but its not rocket science.

Followup:
ABSOLUTELY - why else have them?
what is the point of the lookup table otherwise?

You need to maintain many lookup VIEWS -- views require just as much "care and
feeding" as a small table does.

A single module can still handle multiple tables -- if you can pass in the name
of a lookup so you can query "select ... from generic_table where lookup_name =
:x", yu can query "select .... from " lookup-table

Rating: 5
Re: Go generic? July 02, 2003
Reviewer: Tony Andrews from London, UK
> Would you really want foreign keys to all your lookups ? Is that not where the
crossover is between Master Detail, and simple lookups. We tend to use
check_constraints on the table column, rather than an FK.

If you are ONLY using this approach for static domains to provide a
user-friendly list for a value that is then subject to a check constraint, then
that is fair enough.

Where I work we have the same sort of thing - only without the check
constraints!  In fact, many of the lookups are non-static: users are free to add
new codes at will.  The problems are that only the application enforces use of
correct codes, and without inspecting the application it can be hard to know
which domain a particular column's values are supposed to come from.  It's a bit
of a mess.

Rating: 3
Just to come to Matt's defence July 02, 2003
Reviewer: Paul from UK
Tom,

you said "ABSOLUTELY - why else have them? what is the point of the lookup table
otherwise?" when talking about foreign keys.

Well, the other point is to use them for decoding the lookup to something
meaningful in a user application.

I work on a DWH system where we deliberately don't enforce the foreign key
constraints, as in some of our data feeds the source systems add new codes
without bothering to tell us.  Ideally, of course, the processes would be in
place to ensure we get the codes in advance, but that doesn't happen at the
moment.  So every now and then, we run an exception report to list the orphan
codes and get somebody to investigate and add the lookup data.

But of course the best reason for having a single table containing all codes is
that you don't have to draw so many boxes on you ERD ;-)

Paul

Rating: 5
Agreed July 02, 2003
Reviewer: Matt from UK
Yep I agree about the tradeoffs - its just easier for us to define a new lookup
within the data, rather than having to create a new table for it, especially as
we are working within a rigid development environment. In our case we supply a
3rd party app for rollout on site - its good to have a 'bucket' where we can
store reference codes without going through a whole design exercise.

Rating: 4
July 02, 2003
Reviewer: Dave from Colorado
What an extraordinary way to run a DW. Does your ETL routine not add the new
codes for you then? And you really should be declaring FK's, even
disable/novalidate/rely ones for the sake of the optimizer.

Followup:
well -- if they do the novalidate/disable/rely thing AND we rely on them AND
they are wrong -- you will get the WRONG answer via query rewrites.....

so, don't do that unless we can really RELY on the underlying data.

Rating: 5
lookup tables July 02, 2003
Reviewer: Sean
Hi Tom,

Our developer said that rule engine is the way to define the rule of how to
choose values in lookup tables.  It is not really related to lookup table design
though these rules will stored in the table.  Sorry for the confusion.

I have other question of lookup tables design:
I have unit lookup table

Unit_code(pk)         Unit_desc
  Kg/Day                Kilograms per day
  Mg              Milligram

Presumably we will not change the value of unit_code.  The advantage of not
using sequences number here is that it is easy to see the content of child
tables.

Another way to design this table is:

Unit_num    Unit_code(pk)         Unit_desc
  1          Kg/Day                Kilograms per day
  2          Mg              Milligram

Since our child tables use a lot lookup tables, we need to join a lot of lookup
tables in order to see the contents in the child tables.

Which way is better?

Thanks so much for your help.

Best,

Sean

Followup:
I've always prefered to let the database do my work, therefore I will still go
back to "i'd like a lookup per domain "

Rating: 4
July 03, 2003
Reviewer: Sean from NJ
My question is whether to use meaningless number as primary key or use
short_desc as primary key in lookup table assuming we will not update
short_desc.

Thanks for your help

Best,

Followup:
short_desc would be perfectly acceptable  -- assuming the primary key is a true
primary key and not subject to change.

Rating: 3
August 01, 2003
Reviewer: Dave from Atlanta, GA USA
a) While using the lookup table, will there be any difference between the
following two queries ?

1)
select t.*, l.value
from t, lookup l
where t.code = l.code and
      t.....

2)
select t.*,
       select l.value from lookup l where l.code=t.code
from t
where t......

b) What is the best way to implement lookup tables, where there are hardly any
changes? Is caching these table a bad idea?

Followup:
a) benchmark it.  probably fairly equivalent.  set up a simulation and see.

b) i'm against "caching" as a rule cause it implies you are writing lots of
procedural code.

I prefer to do as much in a single sql statement as humanly possible.  after
that, sure, caching a small set of reference data in a plsql table type is fine
if the data is allowed to become stale.

August 01, 2003 -- Thanks for the question regarding "Sequences", version 8.1.7.0
You Asked
Hello Tom,

We have a java application which reads a flat file and loads the data
into multiple tables. If there are more than one flat file in the directory then
multiple thread processes work on those flat files in parallel.

The logic is somewhat like:

1. select temp_seq.nextval into l_value;

2. Insert into main_table(l_value, other columns);

process other details

3. Insert into detail_tables(l_value, other columns);

4. continue the same for other records in the file.

Selecing a sequence's next value each time is time consuming over JDBC. So the
logic was

to create a sequence with start value 1 increment by 1000;

1. Fetch the sequence.next val in to l_value

2. for i in 1..1000

   insert into main_table (l_value,other's);
   process other details;
   insert into detail_table (l_value,other's);

   l_value:=lvalue+1;
   end loop;

The problem here is, if the flat file has less records then all the unused
numbers will go waste.

Since we have to process large number of files with varying record count and
hold these over a period of 16 months do you think the above logic is feasible?

Thanks
and we said...
Just code

insert into main_table( temp_seq.nextval, other-columns );
insert into detail_tables( temp_seq.CURRVAL, other-columns );

you need never fetch the sequence value BACK.  Actually, I would look to do this
in a stored procedure or even better yet -- sqlldr.

Sequence numbers are never "wasted", there is a huge space for these numbers.
Losing 1,000's of them won't affect you in the long run.
Review & Followup
Rating: 2
creating sequence/p.k base on selected dept column of a table May 13, 2002
Reviewer: Godwin from Ghana
Please help me with this problem given to me by my boss.
''I need to create sequence/primary key which will pick the first 4 characters
of a column called dept and count the number of records in that same dept to
generate the p.k. the sequence must restart at every new dept. eg.
  select dept,grade from staffrecord
  insert into mytable
  values (dept,grade,seq)
Thus the records in mytable should look like
 Zoology    Senior Clerk  ZOOL001
 Zoology    Junior Clerk  ZOOL002
 Mathematics Lecturer     MATH001
 Mathematics T.A          MATH002
............  .....       ......
in that order.
Will plsql rather be okay?
Thanks

Followup:
YUCK.  What a terrible idea.

You can generate a blazing 1 key at a time!!!  And still hit dups (make sure
your app is ready to deal with "dup_val_on_index" (you need a unique constraint
on seq)

you can:

insert into mytable
select :dept, :grade, upper(substr(:dept,1,4)) || to_char(count(*)+1,'fm0000')
  from mytable
 where substr(seq,1,4) = upper(substr(:dept,1,4));

But it'll full scan mytable for every key (unless you use a function based index
on substr(seq,1,4)

Terrible idea.

Rating: 5
August 01, 2003
Reviewer: A reader
select dept,grade,upper(substr(dept,1,4)) ||
to_char(rank () over(partition by dept order by grade ) ,'fm0000')
from mytable;

Followup:
you would want to use row_number() to avoid dups -- and that works on the way
"out" nicely

August 01, 2003 Raymond -- Thanks for the question regarding "Oracle Text (Token parser)", version 9.0.2
You Asked
have you written a parser function that will translate query strings for Oracle
text into search expressions. i.e.

user input string   ==   translation
-------------------------------------------------
"word1 word2"   ==  {word1 word2}
word1 word2     ==  word1 and word2
word1 "word2 word3" == word1 and {word2 word3}
-------------------------------------------------
select score(1),col1,col2 from sometable
where contains(text,translation,1)>0;

I've seen the one that oracle has for parsing using the +,- operators at :
http://technet.oracle.com/sample_code/products/text/htdocs/query_syntax_translators/query_syntax_translators.html
But I'm looking for one that is more up with how google, yahoo and other major
search
engines parse there strings. I've written my own function to do this I'm just
wondering
if anyone else has run into this problem and how they solved it.  Mine seems to
work fine
but I'm always intrested in seeing how other developers have solved these
problems.

Thanks,

Ray
and we said...
this is my simple brute force one I use here on asktom:

function parse_search_string( p_search_str in varchar2 ) return varchar2
is
    l_temp_value   varchar2(1000);
    l_temp_value2  varchar2(1000);
    l_return_value varchar2(1000) := NULL;
    l_start_token  number := 1;
    l_in_token     boolean := FALSE;
    l_quotes       number;
    l_phrases      dbms_sql.varchar2s;
    n              number;
begin
    if nvl(length(p_search_str),0) = 0 then
        return NULL;
    end if;

    l_quotes := length(p_search_str) - length(replace(p_search_str,'"', ''));
    -- if there are quotes and the quotes are balanced, we'll extract that
    -- terms "as is" and save them into a phrases array

    if ( l_quotes > 0 and mod(l_quotes,2) = 0 )
    then
        l_temp_value2 := lower(p_search_str);
        for i in 1 .. l_quotes/2
        loop
            n := instr( l_temp_value2, '"' );
            l_temp_value := l_temp_value || substr( l_temp_value2, 1, n-1 );
            l_temp_value2 := substr( l_temp_value2, n+1 );
            n := instr( l_temp_value2, '"' );
            l_phrases(i) := substr( l_temp_value2, 1, n-1 );
            l_temp_value2 := substr( l_temp_value2, n+1 );
        end loop;
        l_temp_value := l_temp_value || l_temp_value2;
    else
        l_temp_value := lower(p_search_str);
    end if;

    --
    -- now we just clean out "garbage", things we don't want in the
    -- normal search string

    l_temp_value := trim(replace( l_temp_value, ':', ' '));
    l_temp_value := trim(replace( l_temp_value, ';', ' '));
    l_temp_value := trim(replace( l_temp_value, '"', ' '));
    l_temp_value := trim(replace( l_temp_value, ':'',', ' '));
    l_temp_value := trim(replace( l_temp_value, '(', ' '));
    l_temp_value := trim(replace( l_temp_value, ')', ' '));
    l_temp_value := trim(replace( l_temp_value, '!', ' '));
    l_temp_value := trim(replace( l_temp_value, '&', ' '));
    l_temp_value := trim(replace( l_temp_value, '+', ' '));
    l_temp_value := trim(replace( l_temp_value, '\', ' '));
    l_temp_value := trim(replace( l_temp_value, '-', ' '));
    l_temp_value := trim(replace( l_temp_value, ',', ' '));
    l_temp_value := trim(replace( l_temp_value, ' and ', ' ' ));
    l_temp_value := trim(replace( l_temp_value, ' or ', ' ' ));
    --
    if length(l_temp_value) > 0 then
        l_in_token := TRUE;
    end if;
    --
    -- now wrap each word in {} to escape anything "special"

    for i in 1..nvl(length(l_temp_value),0) loop
        if substr(l_temp_value,i,1) = ' ' then
            if l_in_token = TRUE then
                l_return_value := l_return_value || '{' ||
                    substr(l_temp_value,l_start_token, i-l_start_token) ||
                    '}' || ' and ';
                l_in_token := FALSE;
            end if;
        elsif l_in_token = FALSE then
            l_in_token := TRUE;
            l_start_token := i;
        end if;
    end loop;
    if l_in_token = TRUE then
        l_return_value := l_return_value || '{' ||
substr(l_temp_value,l_start_token) || '} and ';
    end if;

    -- and add in any phrases we parsed out...

    for i in 1 .. nvl(l_phrases.count,0)
    loop
        l_return_value := l_return_value || ' {' || l_phrases(i) || '} and ';
    end loop;
    return trim( substr( l_return_value, 1, length(l_return_value)-4 ) );
end;
Review & Followup
Rating: 4
Great! August 01, 2003
Reviewer: Raymond Fitzgerald from Salt Lake City, UT USA
Now I need to change a couple of things in my code.

Here was my first draft which works pretty good:

CREATE OR REPLACE function StrTranslate (token in varchar2 default null)
return varchar2
as
   TYPE v_bin_var1 IS TABLE OF varchar2(2000) INDEX BY BINARY_INTEGER;
   v_value_string v_bin_var1;
   build_string varchar2(4000) := NULL;
   v_count number;
   token_temp varchar2(2000);
   inPhrase        number := 0;
   len             number := 0;
   temp_token      varchar2(4000);
   char            varchar2(1);
   startPhrase     number :=0;
BEGIN
 if token is null then
  build_string := null;
 else
  -- get tokens (white spaces)
  if instr(token,' ')>0 then
   -- populate the array
   token_temp := rtrim(ltrim(upper(token)));
   v_count := 1;
   while instr(token_temp,' ')>0 loop
    v_value_string(v_count) := substr(token_temp,1,instr(token_temp,' ')-1);
    v_count := v_count + 1;
    token_temp := substr(token_temp,instr(token_temp,' ')+1);
    token_temp :=ltrim(token_temp);
   end loop;
   -- get the last one.
   if substr(token_temp,length(token_temp))='\' or
      substr(token_temp,length(token_temp))='?' or
      substr(token_temp,length(token_temp))=';' then
    token_temp :=
substr(token_temp,1,length(token_temp)-1)||'\'||substr(token_temp,length(token_te
mp));
   end if;
   v_value_string(v_count):=token_temp;
  else
   -- only one element in array
   token_temp := token;
   if substr(token_temp,length(token_temp))='\' or
      substr(token_temp,length(token_temp))='?' or
      substr(token_temp,length(token_temp))=';' then
    token_temp :=
substr(token_temp,1,length(token_temp)-1)||'\'||substr(token_temp,length(token_te
mp));
   end if;
   v_value_string(1) := upper(token_temp);
  end if;
  -- string parsed
  v_count := 0;
  for i in v_value_string.first..v_value_string.last loop
       v_count := v_count + 1;
       temp_token := NULL;
       len := length(v_value_string(i));
        -- we iterate over the string to find special web operators
       if instr(v_value_string(i),'"')>0 then
        for k in 1..len loop
          char := substr(v_value_string(i),k,1);
          if (char = '"') then
            if(inPhrase = 0) then
              inPhrase := 1;
              startPhrase := 1;
              temp_token := temp_token||'{';
            else
              inPhrase := 0;
              temp_token := temp_token||'}';
            end if;
          else
            temp_token := temp_token||char;
          end if;
        end loop;
        if build_string is not null then
          if (startPhrase=1) then
           -- in a phrase but done
           startPhrase := 0;
           if  (v_value_string(i-1) = 'AND' or
                v_value_string(i-1) = '&' or
                v_value_string(i-1) = 'OR' or
                v_value_string(i-1) = '|' or
                v_value_string(i-1) = 'NOT' or
                v_value_string(i-1) = '~' or
                v_value_string(i-1) = 'NEAR' or
                v_value_string(i-1) = ';' or
                v_value_string(i-1) = 'ACCUM') then
            build_string := build_string||' '||temp_token;
           else
            build_string := build_string||' AND '||temp_token;
           end if;
          else
            build_string := build_string||' '||temp_token;
          end if;
        else
          startPhrase := 0;
          build_string := temp_token;
        end if;
       else
        if (inPhrase=0) then
          -- not in a phrase
         if v_value_string(i) = 'AND' or
            v_value_string(i) = '&' or
            v_value_string(i) = 'OR' or
            v_value_string(i) = '|' or
            v_value_string(i) = 'NOT' or
            v_value_string(i) = '~' or
            v_value_string(i) = 'NEAR' or
            v_value_string(i) = ';' or
            v_value_string(i) = 'ACCUM' then
            build_string := build_string||' '||v_value_string(i);
         elsif (v_count <> 1) then
          if  (v_value_string(i-1) = 'AND' or
               v_value_string(i-1) = '&' or
               v_value_string(i-1) = 'OR' or
               v_value_string(i-1) = '|' or
               v_value_string(i-1) = 'NOT' or
               v_value_string(i-1) = '~' or
               v_value_string(i-1) = 'NEAR' or
               v_value_string(i-1) = ';' or
               v_value_string(i-1) = 'ACCUM') then
            build_string := build_string||' '||v_value_string(i);
          elsif (v_value_string(i) = 'ABOUT') then
            build_string := build_string||' AND {'||v_value_string(i)||'}';
          else
            build_string := build_string||' AND '||v_value_string(i);
          end if;
         else
           if v_value_string(i) = 'ABOUT' then
            build_string := '{'||v_value_string(i)||'}';
           else
            build_string := v_value_string(i);
           end if;
         end if;
        else
          -- in a Phrase
           build_string := build_string||' '||v_value_string(i);
        end if;
       end if;
   end loop;
   if inPhrase=1 then
    -- missing end of phrase
     build_string := build_string||'}';
   end if;
   build_string := ltrim(build_string);
   if substr(build_string,1,5) = 'NEAR ' then
    build_string := '{'||build_string||'}';
   end if;
 end if;
 if substr(build_string,length(build_string)-1)='{}' then
  build_string := substr(build_string,1,length(build_string)-2);
 end if;
 return(build_string);
end StrTranslate;
/

August 01, 2003 Karthikeyan -- Thanks for the question regarding "Object Privilege", version 8.1.7
You Asked
Tom

Thanks very much for your valuable time to answer this question.

Owner of TABLE_A table is USER_A.

USER_A granted PUBLIC SYNONYM with SELECT only on TABLE_A to USER_B.

USER_A created procedure PROC_A to insert record to TABLE_A.

USER_A granted PUBLIC SYNONYM with EXECUTE only on PROC_A to USER_B.

Now, USER_B has privilege SELECT ONLY on TABLE_A and EXECUTE only on PROC_A.

When USER_B executes the PROC_A procedure, it inserts record to TABLE_A.

But the USER_B has only SELECT privilege on table TABLE_A?

Please correct me, if I am missing something here.

I would appreicate, if you give your valuable time and point.

Thanks
Karthik
and we said...
that is the sheer positive BEAUTY of stored procedures....

the execute with the base privileges (minus roles) of the DEFINER (owner) of the
procedure.

the procedure becomes an executable process that others can execute (when
permitted) and executes with the privs of the definer.

This allows you to build a seriously secure system.  Look at what you just did
-- in order for B to insert into TABLE_A, they must execute your specific code
that can do whatever security validation, auditing, logging, translations,
whatever it needs

So, now user B can insert into TABLE_A in an extremely secure manner.

Suppose as a DBA you wanted to let a help desk person reset the passwords for
some set of application users?  Well, Oracle doesn't have a "grant alter user
usera, userb, userc to HELP_DESK" command -- but the DBA can code:

create procedure reset_password( p_username in varchar2, p_pw in varchar2 )
as
begin

   -- could be a table lookup of course
   if ( p_username in ( 'A', 'B', 'C', .... ) )
   then
       execute immediate 'alter user ' || p_username || ' identified by ' ||
               p_pw;
   else
       -- maybe log that they tried to alter some user
       raise_application_error( -20001, 'hey, you cannot do that!' );
   end if;
end;

Now, the owner of that procedure has the ALTER USER privilege and by granting
execute on it to HELP_DESK, they allow that person/role to alter any of the
users in the list.

You use procedures to secure your database...
August 01, 2003 Scott -- Thanks for the question regarding "Mixing clusters and partitioned tables.", version 9
You Asked
Tom,

I have been looking at clustered tables to help get better performance for a key
query in one of the applications I look after and I was wondering if combining
partitioning and clustered tables make sense.

If I create an indexed cluster wouldn't that really be a global index? Now
suppose I roll or drop a partition wouldn't that make the clustered index
unusable?  (if you have never done it don't worry about creating an example, I
could do that myself )

If I use a hash cluster I don't think I would have the same problem, however, I
don't know off hand how many hashkeys will be required for this table which is
why I was thinking of a indexed cluster.

While on the topic of hash clusters, does it make sense to use the PK column as
the hash function instead of letting Oracle choose a hash function?

Thanks,
Scott.
and we said...
you haven't tried to partition a cluster yet have you ;)

partitioning and clusters do not go together.
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96524/c12parti.htm#464767
Review & Followup
Rating: 4
How can you tell :-) August 01, 2003
Reviewer: A reader
As you deduced, I had not tried to cluster a partitioned table yet.  I was
brainstorming different approaches before putting fingers to keyboard and got
the idea of using a cluster based on the examples in your book.

Thanks for the info and link.

Scott.

August 01, 2003 Daya -- Thanks for the question regarding "Database Locks", version V6.0
You Asked
Dear Tom

Thank you for answers for my earlier questions. I would like to bring your
notices one more question.

1. I am trying to monitor the database locks in my database. Query is
Reddy815>CONNECT INTERNAL/ORACLE@PSI1 AS SYSDBA;
Connected.
Reddy815>select S1.username as " Waiting User",
  2    S1.osuser as "OS User",
  3    W.session_id as "SID",
  4    P1.spid as "PID",
  5    S2.username as "Holding User",
  6    S2.osuser as "OS User",
  7    H.session_id as "SID",
  8    P2.spid as "PID"
  9  from v$process P1,
 10    v$process P2,
 11    v$session S1,
 12    v$session S2,
 13    sys.dba_locks W,
 14    sys.dba_locks H
 15  where H.mode_held = 'Blocking'
 16    and H.mode_held = 'Null'
 17    and W.mode_requested != 'None'
 18    and W.lock_type (+)= H.lock_type
 19    and W.lock_id1 (+)= H.lock_id1
 20    and W.lock_id2 (+)= H.lock_id2
 21    and W.session_id = S1.sid (+)
 22    and H.session_id = S2.sid (+)
 23    and S1.paddr = P1.addr (+)
 24    and S2.paddr = P2.addr (+);
  sys.dba_locks H
      *
ERROR at line 14:
ORA-00942: table or view does not exist

Reddy815>desc dba_locks;
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 SESSION_ID                                         NUMBER
 LOCK_TYPE                                          VARCHAR2(26)
 MODE_HELD                                          VARCHAR2(40)
 MODE_REQUESTED                                     VARCHAR2(40)
 LOCK_ID1                                           VARCHAR2(40)
 LOCK_ID2                                           VARCHAR2(40)
 LAST_CONVERT                                       NUMBER
 BLOCKING_OTHERS                                    VARCHAR2(40)

Reddy815>select table_name from dba_tables where table_name='dba_locks';

no rows selected

Reddy815>ed
Wrote file afiedt.buf

  1* select table_name from dba_tables where table_name='DBA_LOCKS'
Reddy815>/

no rows selected

Reddy815>SPOOL OFF

Question No.1     I am using the table "dba_locks" in my query two times i.e
Line No. 13         and 14. But the error says "table or view does not exist "
at line         no.14.Why it is      not showing error at line no. 13 itself.
Question No. 2 When I describe the "dba_locks" is showing the table structure,
but when
        query "select table_name from dba_tables where table_name='dba_locks';
        saying "no rows selected " like for ALL_TABLES,DBA_TABLES also I got
        "no rows selected".

What is the reason for that and how can I who is locking and who is waiting for
that locks.

Thanks in advance

Daya
and we said...
dba_locks is a SYNONYM for sys.dba_lock.

sys@ORA8I.WORLD> select owner, object_name, object_type from dba_objects
  2  where object_name like 'DBA_LOCK%';

OWNER                          OBJECT_NAME                    OBJECT_TYPE
------------------------------ ------------------------------ ------------------
SYS                            DBA_LOCK                       VIEW
SYS                            DBA_LOCK_INTERNAL              VIEW
SYS                            DBA_LOCK_INTERNAL_RULE         VIEW
PUBLIC                         DBA_LOCK                       SYNONYM
PUBLIC                         DBA_LOCKS                      SYNONYM
PUBLIC                         DBA_LOCK_INTERNAL              SYNONYM

6 rows selected.

there is no SYS.DBA_LOCKS.

Either drop the SYS. reference or use DBA_LOCK instead.
Review & Followup
Rating: 4
Locks December 03, 2001
Reviewer: Andre Whittick Nasser from Rio de Janeiro, Brazil
Tom,

As to his final question:

 "...who is locking and who is waiting for that locks."...

taking a look at V$LOCKED_OBJECT is the answer.

Thanks !

Rating: 5
Locks October 01, 2002
Reviewer: mo
Tom:

I am getting this on a web page when I try to run a procedure that is doing
multiple inserts to tables.

Tue, 1 Oct 2002 15:34:57 GMT

ORA-02049: timeout: distributed transaction waiting for lock
ORA-06512: at "ADMIN.SAVE_NEW_PO", line 264
ORA-06512: at "ADMIN.FOO_PO", line 62
ORA-06512: at line 7

Do you know what is cause by.

oracle documentation says the following:

ORA-02049 timeout: distributed transaction waiting for lock

Cause: Exceeded INIT.ORA DISTRIBUTED_LOCK_TIMEOUT seconds waiting for lock.

Action: Treat as a deadlock.

what do i need to do.

Thanks,

Followup:
you are doing a distributed transaction.

you tried to get a lock on some object or rows in the remote database.

it took too long - it is assumed that the user holding the locks is waiting for
some rows you have locked (eg: a deadlock).  so we terminate your transaction.

either up the timeout if this is not a deadlock or figure out why you are
deadlocking with someone else.

Rating: 5
locks October 02, 2002
Reviewer: mo
Tom:

1. What is a distributed transaction and how many types there are? Is it when
you go to another database via link means distributed?

2. When do you usually get a lock? Is it when you do any DML statement? Is it in
milliseconds?

3. How would I figure out if I am deadlocking with someone else using a web
application?

Thank you,

Followup:
1) Mo, you have the book -- expert one on one -- its in there.  also read
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76960/toc.htm
it is a transaction that spans more then one database.

2) you have the book!  I go over all of this (really)

3) you would get an ora-00060 just like any other time (the web isn't different
or special in this regards)

Rating: 4
Locks or no locks ?! October 29, 2002
Reviewer: I Singh from Kansas City, MO
Tom, could you please explain this.

 select sid,
serial#,lockwait,status,row_wait_file#,row_wait_block#,row_wait_row#,sql_address,
sql_hash_value
 from v$session where row_wait_file# >0;

   SID    SERIAL# LOCKWAIT STATUS   ROW_WAIT_FILE# ROW_WAIT_BLOCK# ROW_WAIT_ROW#
SQL_ADDR SQL_HASH_VALUE
------ ---------- -------- -------- -------------- --------------- -------------
-------- --------------
    45          2          INACTIVE              6           33468             8
29FF20B0     3625054966
    49          2          INACTIVE              6           56648             0
2A44D744     1529370122
    51          2          INACTIVE              6           33468             7
2A51C4FC     2023698356
    59          2          INACTIVE              6           33468             7
2A420BDC     2189312113
    61          2          INACTIVE              6           33468            12
25EC6268     2165471036
    63          2          INACTIVE              6           33468             5
2A44A0B8     1817734235
    93        602          INACTIVE              6           57168             0
29FF20B0     3625054966
    94         34          INACTIVE              6          216794             2
2A064EC8     1969928661
    97         10          INACTIVE              6          216746             3
2A064EC8     1969928661
    99         10          INACTIVE              6          216753             2
29FF20B0     3625054966
   110         27          INACTIVE              6          216782             1
2A05990C     4250919705
select * from v$transaction and v$locked_object does not
yield any rows.

1. What are the values in the sql_address field and row_wait fields above. Is
anything being locked or waited on or not ?

2. Why is the status of those sessions inactive that is if there is any active
transaction.

Thanks in advance.

Followup:
the handy dandy REFERENCE guide that documents all of the v$ views says:

ROW_WAIT_FILE#   NUMBER

Identifier for the datafile containing the ROWID specified in ROW_WAIT_ROW#.
This column is valid only if the session is currently waiting for another
transaction to commit and the value of ROW_WAIT_OBJ# is not -1.

SQL_ADDRESS RAW(4)

Used with SQL_HASH_VALUE to identify the SQL statement that is currently being
executed

since the status is INACTIVE -- neither of these columns is particularly
relevant. The session isn't executing a statement (hence they are not waiting
and are not currently executing anything)

These columns only mean something when status is active really.

2) A session can execute an insert statement and the end user walks away.  You
have a TRANSACTION (for the insert) but the session is INACTIVE (not currently
executing a statement)

Rating: 3
OK August 01, 2003
Reviewer: R.Chacravarthi from Chennai,India
Dear Sir,
I have some questions for you and they are as follows.
1)How oracle minimizes lock contention?Does it use multiversioning to achieve
the same or it escalates a lock
when the locks reach a threshold?
2)Is there any other way for console output other than using
dbms_output.put_line()
Thanks

Followup:
1) time to read the server concepts guide -- you'll never ever be sorry you did:
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-CON
row level locking is one aspect.

multi-versioning (to provide non-blocking, consistent reads and ensure reads
never block writes) is the other.

2) from plsql?  return a ref cursor and print it.

Rating: 5
Query on note in Concepts Guide August 01, 2003
Reviewer: Tony Andrews from London, UK
Following your frequent advice, I am making a point of reading the Concepts
Guide from cover to cover.

In the section on Serializable Isolation in chapter 20 there is a Note:
"Transactions containing DML statements with subqueries should use serializable
isolation to guarantee consistent read."

I can't quite figure out why it says that.  Earlier it says "A subquery or
implicit query in a DML statement is guaranteed to be consistent as of the start
of the query and does not see the effects of the DML statement it is part of".
So why should we use serializable?

Followup:
It has to do with the set of rows the DML statement will see.

If you are updating "blindly" -- eg, you did not select the row out, your update
"query" part will see data as of the time the statement was submitted -- not as
of the time the row is actually updated.  so in some multi-user situations, your
logic may not work as you anticipated since the read is seeing data as of the
beginning of the statement -- not as of right now.

serializable won't let it "work" -- it'll simply raise the cannot serialize
access.

consider the following example.  Our goal is to maintain a rolled up value in
the DEPT table that is the sum of salary from emp.  In order to accomplish that,
we'll update DEPT after every update of EMP (could be a trigger even)

ops$tkyte@ORA920> create table dept
  2  ( deptno        int primary key,
  3    sum_of_salary number
  4  );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table emp
  2  ( empno      int primary key,
  3    deptno     references dept,
  4    salary     number
  5  );

Table created.

ops$tkyte@ORA920> insert into dept ( deptno ) values ( 1 );
1 row created.

ops$tkyte@ORA920> insert into dept ( deptno ) values ( 2 );
1 row created.

ops$tkyte@ORA920> insert into emp ( empno, deptno, salary ) values ( 100, 1, 55
);
1 row created.

ops$tkyte@ORA920> insert into emp ( empno, deptno, salary ) values ( 101, 1, 50
);
1 row created.

ops$tkyte@ORA920> update dept
  2     set sum_of_salary =
  3     ( select sum(salary)
  4         from emp
  5            where emp.deptno = dept.deptno )
  6   where dept.deptno = 1;
1 row updated.

ops$tkyte@ORA920> commit;
Commit complete.

so there is our data, it starts out "ok", the dept table has that aggregate.
Now we execute:

ops$tkyte@ORA920> insert into emp ( empno, deptno, salary )
  2  values ( 102, 2, 60 );
1 row created.

ops$tkyte@ORA920> set echo off
in another session run this and then come back here and hit enter
update emp
set deptno = 2
where empno = 100;
update dept
set sum_of_salary = ( select sum(salary)
from emp
where emp.deptno = dept.deptno )
where dept.deptno in ( 1, 2 );
ops$tkyte@ORA920> pause

so the insert takes place in session 1, the two updates take place in session
2 -- another window.  So far -- so good really.  the second session can see its
modifications so the data is OK in dept from its perspective.  Now we come back
to this session and execute:

ops$tkyte@ORA920> set echo off
this will get blocked, when it does, goto the other session and
commit
ops$tkyte@ORA920>
ops$tkyte@ORA920> update dept
  2     set sum_of_salary = (select sum(salary)
  3                            from emp
  4                           where emp.deptno = dept.deptno)
  5   where dept.deptno = 2;

this statement blocked.  Its "read" component however is frozen already at the
point in time the statment began.  the sum(salary) from emp is already known --
but does NOT include the modified rows from session 2.  remember, session 2 has
NOT YET committed -- hence the salary session 2 added to deptno 2 via the update
is not visible to us

we committed in the other session and immediately session 1 returns with:

1 row updated.

ops$tkyte@ORA920> commit;
Commit complete.

ops$tkyte@ORA920> select * from dept;

    DEPTNO SUM_OF_SALARY
---------- -------------
         1            50
         2            60

ops$tkyte@ORA920> select deptno, sum(salary) from emp group by deptno;

    DEPTNO SUM(SALARY)
---------- -----------
         1          50
         2         115

as you can see -- the rollup in dept is "wrong" because the update did not see
the data after session 2's commit but before.

In my book coming out this month, I used this same exact example -- but I did
not solve the problem using serializable.  I did it the way I prefer using read
committed.  Here, we'll rerun the simulation using serializable to see what
would happen though.  picking up right after the setup:

ops$tkyte@ORA920> set transaction isolation level serializable;
Transaction set.

employ serializable for this transaction...

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into emp ( empno, deptno, salary )
  2  values ( 102, 2, 60 );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set echo off
in another session run this and then come back here and hit enter
set transaction isolation level serializable;
update emp
set deptno = 2
where empno = 100;
update dept
set sum_of_salary = ( select sum(salary)
from emp
where emp.deptno = dept.deptno )
where dept.deptno in ( 1, 2 );
ops$tkyte@ORA920> pause

ops$tkyte@ORA920>
ops$tkyte@ORA920> set echo off
this will get blocked, when it does, goto the other session and
commit
ops$tkyte@ORA920>
ops$tkyte@ORA920> update dept
  2     set sum_of_salary = (select sum(salary)
  3                            from emp
  4                           where emp.deptno = dept.deptno)
  5   where dept.deptno = 2;

this got blocked again.  so far, no different then before...  However, when we
commit in the other session -- this immediately pops out:

update dept
       *
ERROR at line 1:
ORA-08177: can't serialize access for this transaction

Now, the result set was set as of the TIME THE TRANSACTION began.  We tried to
update some rows that changed since our transaction began and hence it must
fail.  We won't have the data coming from differing points in time.

My solution involves using a select for update to serialize access, this
alternative does effectively the same thing

ops$tkyte@ORA920>
ops$tkyte@ORA920> rollback;

Rollback complete.

ops$tkyte@ORA920> select * from dept;

    DEPTNO SUM_OF_SALARY
---------- -------------
         1            50
         2            55

ops$tkyte@ORA920> select deptno, sum(salary) from emp group by deptno;

    DEPTNO SUM(SALARY)
---------- -----------
         1          50
         2          55

Now the data is "the same"

Rating: 5
Excellent explanation August 01, 2003
Reviewer: Tony Andrews from London, UK
Thanks, that makes it all clear.  I am more familiar with the SELECT FOR UPDATE
approach also; needless to say I already your new book on order.

I bet 99% of databases where stored summaries are used as in your example use
neither approach.  I don't think I've ever seen such a database where all the
summary values matched up with the underlying data!

Followup:
if you use materialized views (which would be my preferred method actually to
accomplish this -- it is what i use on asktom for example) -- you can get
absolutely accurate rollups without worrying about this stuff as well.

August 01, 2003 Charlie -- Thanks for the question regarding "multi-row uniqueness", version 9.2.0.3
You Asked
Hi Tom,

I have a table T with 2 columns group_id and mass, a number.  For example,

GROUP_ID   MASS
--------  -----
1           100
1           200
1           300

2           300
2           200

3           600
3           700

In this table, how can I ENFORCE a group uniqueness?  For instance,
I already have mass value (100, 200, 300) as group(1) in this table, later when
an user trys to insert another group mass value(200,100,300), the database
should barf because the database finds another group mass value(100, 200, 300)
in this table.  After they are sorted, they have exactly same information.

I call this kind of behavior as "group uniqueness" or "multi-row uniqueness".
It's not we add an unique constraint on column "mass", everything is all set. We
have to make sure A GROUP OF ROWS OF THAT COLUMN is unique.

Please shed some light on it.

Is there a reasonable upper bound on the number of masses per group id?

>>
Not really. I don't want to make this restriction.  However, let's make it 40 as
upper bound if this can be solved.

thanks!
and we said...
ok, it'll be a function of the number of numbers you have and their length
but...  here is one approach.  We'll have to store the group as a row and I'll
use a varchar2 to do that.  you should be able to get at least 66 really long
numbers stuffed in there -- or hundreds of shorter ones.

A unique constraint will do the work for us then.

Here is the prototype (not fully tested and debugged -- but you get the concept)

ops$tkyte@ORA920> create table t1 ( group_id int, mass int );
Table created.

ops$tkyte@ORA920> create table t2 ( group_id int primary key, masses
varchar2(3000) unique ) organization index;
Table created.

ops$tkyte@ORA920> create or replace package state_pkg
  2  as
  3      type array is table of number index by binary_integer;
  4      g_group_ids array;
  5      g_empty     array;
  6  end;
  7  /
Package created.

we'll use this to remember what groups were modified by our DML statement...

ops$tkyte@ORA920> create or replace trigger t1_biud
  2  before insert or update or delete on t1
  3  begin
  4      state_pkg.g_group_ids := state_pkg.g_empty;
  5  end;
  6  /

Trigger created.

need to reset the array before moving on....

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace trigger t1_aiud_fer
  2  after insert or update or delete on t1 for each row
  3  begin
  4      if (inserting or updating)
  5      then
  6          state_pkg.g_group_ids(:new.group_id) := 1;
  7      end if;
  8      if (deleting or updating)
  9      then
 10          state_pkg.g_group_ids(:old.group_id) := 1;
 11      end if;
 12  end;
 13  /

Trigger created.

remember what group ids we've mucked with -- note the trick here, i'm just
setting array( GROUP_ID ) to 1.  In the after trigger, well use the INDICES in
the arrays, not the values in the array to process the data

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace trigger t1_aiud
  2  after insert or update or delete on t1
  3  declare
  4      l_group_id   number;
  5      l_string     t2.masses%type;
  6      l_masses     state_pkg.array;
  7  begin
  8      l_group_id := state_pkg.g_group_ids.first;
  9      loop
 10          delete from t2 where group_id = l_group_id;
 11          l_string := null;
 12          select mass bulk collect into l_masses
 13            from t1
 14           where group_id = l_group_id
 15           order by mass;
 16          for i in 1 .. l_masses.count
 17          loop
 18              l_string := l_string || '/' || l_masses(i);
 19          end loop;
 20          insert into t2 (group_id, masses) values (l_group_id,l_string);
 21
 22          exit when l_group_id = state_pkg.g_group_ids.last;
 23          l_group_id := state_pkg.g_group_ids.next(l_group_id);
 24      end loop;
 25  end;
 26  /

Trigger created.

here we loop over all of the indices in the array -- delete any prior record
in T2 for that group and re-insert it.

The unique constraint on masses in t2 will capture any errant data

ops$tkyte@ORA920> insert into t1 (group_id,mass) values ( 1, 100 );
1 row created.

ops$tkyte@ORA920> insert into t1 (group_id,mass) values ( 1, 200 );
1 row created.

ops$tkyte@ORA920> insert into t1 (group_id,mass) values ( 1, 300 );
1 row created.

ops$tkyte@ORA920> insert into t1 (group_id,mass) values ( 2, 300 );
1 row created.

ops$tkyte@ORA920> insert into t1 (group_id,mass) values ( 2, 200 );
1 row created.

ops$tkyte@ORA920> insert into t1 (group_id,mass) values ( 3, 600 );
1 row created.

ops$tkyte@ORA920> insert into t1 (group_id,mass) values ( 3, 700 );
1 row created.

and now introduce the duplicates:

ops$tkyte@ORA920> update t1 set mass = mass-400 where group_id = 3;
update t1 set mass = mass-400 where group_id = 3
       *
ERROR at line 1:
ORA-00001: unique constraint (OPS$TKYTE.SYS_C006250) violated
ORA-06512: at "OPS$TKYTE.T1_AIUD", line 18
ORA-04088: error during execution of trigger 'OPS$TKYTE.T1_AIUD'

ops$tkyte@ORA920>
Review & Followup
Rating: 4
COOL! July 31, 2003
Reviewer: Charlie
THANKS!

Rating: 5
Neat solution but... August 01, 2003
Reviewer: A reader
Really innovative solution, but would it be better if we updated t2 instead of
delete and insert. This is because frequent deletes would cause the HWM of t2 to
go higher and higher. On the other hand t2 is an IOT. Does it matter ?

<snip>
    --delete from t2 where group_id = l_group_id;
    l_string := null;
    select mass bulk collect into l_masses
    from t1
    where group_id = l_group_id
    order by mass;
    for i in 1 .. l_masses.count
    loop
         l_string := l_string || '/' || l_masses(i);
    end loop;
    --insert into t2 (group_id, masses) values (l_group_id,l_string);

    UPDATE t2 SET masses = l_string WHERE group_id = l_group_id;
</snip?

Followup:
benchmark it.

you need to at least:

  update t2...
  if (sql%rowcount = 0) then insert t2;

(that and a frequent delete+insert won't bump the HWM, we effciently reuse
space).

Rating: 5
Opportunity for nested table column? August 01, 2003
Reviewer: Dan Kefford from New Haven, CT
Dear Tom...

Would this be a good example of when to use a nested table column? I know that
normally you advise against doing so, but it would seem that storing the masses
together as a group, as an object, instead of a delimited list is more natural.

I tried doing this...

SQL> CREATE TYPE yly2b.mass_group_tab_type AS TABLE OF INTEGER
  2  /

Type created.

SQL> CREATE TABLE masses
  2         (group_id     NUMBER,
  3          mass_group   mass_group_tab_type)
  4  NESTED TABLE mass_group STORE AS mass_group_nt
  5  /

Table created.

SQL> ALTER TABLE masses ADD CONSTRAINT mass_group_uniq UNIQUE(mass_group)
  2  /
ALTER TABLE masses ADD CONSTRAINT mass_group_uniq UNIQUE(mass_group)
                                                         *
ERROR at line 1:
ORA-02329: column of datatype Named Table Type cannot be unique or a primary key

... but could not figure out how to enforce uniqueness of the group.

Is this just the wrong way to go, or would a future release of Oracle allow
this? Or am I doing something wrong?

Also, I am curious whether one should be concerned with your proposal (as well
as mine for that matter) in that it involves a table that is not 1NF. If faced
with this puzzle yourself in a production scenario, would you adhere to
normalization principles, so help you God, or would you make exceptions such as
this?

Hoping you had a good vacation,

dan kefford

Followup:
you cannot -- because they are just rows again -- a nested table is just a
master/detail.

normalization is *so* overrated.

August 01, 2003 Joy -- Thanks for the question regarding "Time datatype", version 9i
You Asked
Hi Tom,

In oracle, is there a time datatype. I am using Mysql database now. Some of
tables contain time datatype. We are going to move data from mysql to oracle. If
Oracle doesn't have time datatype, then what is a solution for it?

Thank you very much in advance
Joy
and we said...
In Oracle you have

DATE (7 byte field, holds YYYY MM DD HH24 MI SS data)

TIMESTAMP (7-11 byte field holds a DATE plus optional fractional seconds plus
optional timezone)

INTERVAL a duration of time -- like "8 hours 20 minutes", "5 years 3 months"

Sounds like the INTERVAL type is most appropriate for you.
Review & Followup
Rating: 2
July 26, 2003
Reviewer: Joy from Toronto, Ontario, Canada
Thanks Tom.

Rating: 2
Beware... August 01, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
Tom,

Is there any way to easily get the hour potion of an interval?

For example I have "5 days, 3 hours 15 minutes" i would like to ...

"to_char(interval_datatype, 'hours something...')"

and get "123:15:00" (123 hours, 15 minutes, 0 seconds)

Followup:
no, not really -- you would have to extract the requisite components and do the
math.

August 01, 2003 sfar -- Thanks for the question regarding "Database Backup", version 8.1.7
You Asked
I have a Warehouse DB (around 500 GB)(8.1.7). DB runs in non-archivelogmode.
This DB updates daily in the morning, and after that it is just used for read
only purpose , retrieving reports.
I want to talk backup of this DB.
One way is to shutdown the DB and take cold backup that's what I am doing
currently, but Backup take around 5/6 hours.
Is there any other way  to take backup without shutting down the DB in
non-archivelog mode  .
If I take  backup without shutting down the db(copy all the datafile,logfile,
controlfile ) at the time when it is just used for reading/selecting purpose,
What is the change of restore from that backup.
OR if you can suggest any other way to take backup in non-archivelogmode.

Thanks
and we said...
after the updates -- make each of the tablespaces except system, rollback, temp
"READ ONLY".

Then, transport them using EXPORT.

They are your backup, if you lose any one of them -- you just restore their data
file copies.

If you lose temp, ignore it and just create new.

If you lose system or rollback, you just create a new instance and import the
tablespaces.
Review & Followup
Rating: 4
sfar August 03, 2003
Reviewer: A reader

Rating: 3
This is really a good question and a good answer!! August 10, 2003
Reviewer: Saeed Ahmed from India
keep it up!!

August 01, 2003 brenda -- Thanks for the question regarding "why simple query is so CPU consuming", version 81730
You Asked
Tom,
here is the result of a query statistics:
 Logical Reads Physical Reads  SQL Text
 ------------- --------------
----------------------------------------------------------------------
        471815         404196  SELECT "ACCT_ID" ,"AUDIT_DATE" ,"COLUMN_NAME"
,"NEW_VALUE"  FROM
                                "DOW_DATA"."SA_RAMS_ACCT_AUDIT" WHERE
("COLUMN_NAME" = 'ACCOUNT
                               _STATUS' )

When I ran query plan to get the statistics, here is the result:

11:31:34 SQL>
11:31:34 SQL> set autotrace traceonly exp stat
11:31:34 SQL>
11:31:34 SQL> SELECT "ACCT_ID"
11:31:34   2      ,"AUDIT_DATE"
11:31:34   3      ,"COLUMN_NAME"
11:31:34   4      ,"NEW_VALUE"
11:31:34   5  FROM "DOW_DATA"."SA_RAMS_ACCT_AUDIT"
11:31:34   6  WHERE ("COLUMN_NAME" = 'ACCOUNT_STATUS' )
11:31:34   7  /

12788921 rows selected.

Elapsed: 00:16:981.35

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=29799 Card=4947416 B
          ytes=192949224)

   1    0   TABLE ACCESS (FULL) OF 'SA_RAMS_ACCT_AUDIT' (Cost=29799 Ca
          rd=4947416 Bytes=192949224)

Statistics
----------------------------------------------------------
         33  recursive calls
         16  db block gets
    1341005  consistent gets
     493610  physical reads
        988  redo size
  785279287  bytes sent via SQL*Net to client
   94638359  bytes received via SQL*Net from client
     852596  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)

My question is such as a simple query, why the consistent gets are 1.3 million
rows,it cause lots of CPU resource.
and we said...
umm, well, lets see.

it returned 12,788,921 rows!

doesn't seem so "simple" to me.

It actually did not do lots of CPU -- it was pretty much IO bound here.

If you want to reduce the consistent gets -- increase your arraysize to 100 or
500

SQL> set arraysize 100

but bear in mind, the complexity of a sql query has NOTHING to do with its
length, it has everything to do with the data it touches.

You are getting almost 13 million rows!

Upping your arraysize will reduce the roundtrips and the consistents gets.

But remember the old axiom "too much of a good thing is not good" -- don't go
overboard with this.

Be interested to know what you could possibly do with 12 million rows in
sqlplus.
Review & Followup
Rating: 5
August 01, 2003
Reviewer: Peter Tran from Houston, TX USA
"Be interested to know what you could possibly do with 12 million rows in
sqlplus."

Watch it lock up your SQL*PLUS console.  I hope Brenda set pause on.

August 02, 2003 vishnu -- Thanks for the question regarding "selecting the common records from the table for one of the fields", version 8i
You Asked
Hi Tom
My problem is a silly one ,but struggling for some time .It is i have a table
called
table name : regs
Country_name  color_name  permitted        notes
us            red123       yes            -----
india         white1       no             ---
us            black9      yes            not available
china         yellow      yes            0.1 gr per day
singapore     red123      yes            0.5 gr per day
korea         blue        no             ---
india         yellow      yes            0.9 gr per day
india         black9      yes            0.5 gr per day

My problem now is i am creating a report in a portal..the user selects multiple
countries and the report should return us the common color_names for those
countries where permitted =yes;
suppose if some user selects us and india it should return only

black9
please help ...getting the multiple countries is done by passing the bind
variable and  i am doing it ,but returning the common color_name for the
selected country is the problem ..pl help
and we said...
The query could be:

select color_name
  from regs
 where country_name in ( 'us', 'india' )
 group by color_name
having count(*) = 2
/

That'll get common names -- you need the list of countries and the "count" of
the number of countries you are interested in.
Review & Followup
Rating: 2
If the user selects more than two records ,,What happens ? August 23, 2001
Reviewer: A reader
Tom the answer is fine if there r only two countries ,But we don't know the user
will select how many countries...If it is many it is not working ...pl clarify
again

Followup:
If you don't know how many countries there were -- how did you construct the

where country in ( 'us', 'india' )

in the first place?  If you have a comma delimited list, you can determine the
number of elements in it via:

  length(string) - length(replace(string,',','') + 1

So, if you get the string "'us', 'india'", just use:

having count(*) = length( string) - length(replace( string,',','') + 1

replacing 'string' with 'us', 'india'

Rating: 3
August 23, 2001
Reviewer: A reader
Hi Tom ,Sorry for disturbing u again
But the problem is am witing sql for generating a report in Portal...In the
customization form the user selects the many countries and i hv to return the
common color in all the countries and

select pigments_name from BPSI_COLREG.cr_colreg where Permitted='Yes' and
Country_name in :country group by pigments_name having count(*) = length(
string) - length(replace( string,',','') + 1

the above query doesn'twork pl help...

Followup:
Have you tried replacing string with :country

Rating: 5
Line start position August 23, 2001
Reviewer: Reader from USA
Hi, Tom,

Just little suggestion:
Would you mind start your "followup from Tom" from very left please? Because
some of the readers want to print the page out but with the right portion
cutting out.
Would you use another(diff) color just to distinguish the comment of reader from
that of yours if you want to do so.

Thanks

Rating: 3
August 23, 2001
Reviewer: A reader
Yes ,Tom
I replaced with :country,even then i am not able to get the desired result..Pl
help

Followup:
mind posting "what happened".  You see, I've not programmed portal so I don't
really know what/how they are "binding" an in list.  You'll have to help me here
a bit.

Rating: 3
August 23, 2001
Reviewer: A reader
Hi Tom
I am very sorry for not giving you full details..I am really sorry ..I will
explain you my situation again ..I am creating a simple report from portal
..User will go to cusomization form and selects the multiple countries to view
the common(should be there in all the countries) color
report...He should get the common available color in all the countries...from a
table
the following is the query ...instead of "length(:country) -
length(replace(:country,',','') + 1))"  if i give just "2" and select only two
records it works fine...But user will select any no of records..

select pigments_name from BPSI_COLREG.cr_colreg where Permitted='Yes' and
Country_name in :country group by pigments_name  having
count(*)=(length(:country) - length(replace(:country,',','') + 1))

It is giving me the following error.....
Failed to parse query

Error: ORA-00907: missing right parenthesis (WWV-11230)
Failed to parse as BPSI_COLREG - select pigments_name from BPSI_COLREG.cr_colreg
where Permitted='Yes' and Country_name in ( :zz11, :zz12, :zz13 ) group by
pigments_name having count(*)=(length( ( :zz11, :zz12, :zz13 ) ) -
length(replace( ( :zz11, :zz12, :zz13 ) ,',','') + 1)) (WWV-08300)

 Cursor is not open

pl help ...pl

Followup:
Try this query then:

select pigments_name
  from BPSI_COLREG.cr_colreg
 where Permitted='Yes'
   and Country_name in :country
 group by pigments_name
having count(*)=(length( ':country' ) - length(replace( ':country' ,',','') +
1))

It looks like they are replacing :country with the "real" binds and we need to
treat that as a string.  If they just "replace" :country with the string -- even
though :country is now in quotes, this will work.  Let me know.

Rating: 3
The paranthese matching have problem August 23, 2001
Reviewer: VJ
The length(replace(....) + 1 )) part should be length(replace(....)) + 1)

Rating: 5
September 25, 2002
Reviewer: Pooja from CA, USA
Tom,

  I was going thru the reponses for this question. Your response was really
helpful. I am just wondering , what if
we need to execute another query to get value for :country (i.e. to get list of
countries that we are interested in) and want to include the query to get list
of countries in the original query it self so that we can avoid network trips
when executing from java layer.

Can you please answer?

Thanks

Followup:
it is called a subquery

select * from t where x in ( select y from another table)

Rating: 5
September 25, 2002
Reviewer: Pooja from CA USA
Hi Tom,

Thanks for your response. I think, I did not ask my question clearly.

In your follow up to the original question, you've given the following query to
find out all the common color_names for the the user selected countries.

select pigments_name
      from BPSI_COLREG.cr_colreg
     where Permitted='Yes'
       and Country_name in :country
     group by pigments_name
    having count(*)=(length( ':country' ) - length(replace( ':country' ,',','')
+
    1))

In the above scenario, user is selecting countries and we can pass the list of
countries to a bind parameter.
Suppose, I am not the list of countries from the user. Instead, I need to get
the list of countries by executing a sub query. How can I incorporate that sub
query in to the query given above and get the same result?

Followup:
    select pigments_name
      from BPSI_COLREG.cr_colreg
     where Permitted='Yes'
       and Country_name in (subquery)
     group by pigments_name
    having count(*)= (select count(*) from (subquery) )

Rating: 5
September 25, 2002
Reviewer: Pooja from CA USA
Tom,

  Is there a way that I could avoid executing the same sub query couple of times
(one to get list of countries and the other to get count(*) of the same)?
Can I get away by executing this query just one time as inline view or some
thing and pass the values to IN () and having count(*).

Followup:
In 9i -- sure, there is the WITH clause that does that

with view_name as ( subquery )
select ....

it'll know to reuse the query over and over

(but don't forget -- 99.99999999999% of the time, you'll just be hitting cache
on the subqueries!)

Rating: 4
Duplicate Rows September 26, 2002
Reviewer: tony from INDIA
What if duplicate rows exists? (Dupliacate color in the dame table)

Followup:
use your knowledge of SQL to adjust I suppose:

    select pigments_name
      from BPSI_COLREG.cr_colreg
     where Permitted='Yes'
       and Country_name in (subquery)
     group by pigments_name
    having count(distinct <whatever>)= (select count(*) from (subquery) )

I mean -- you can do whatever you need to do -- given that you know these
constructs right?

Rating: 5
August 02, 2003
Reviewer: Su Baba from Ca, USA
Hi Tom,

I have a question along the same line as this one, but with a little difference.
Say I have the following table structure and data.

CREATE TABLE a (
username  VARCHAR2(10),
token     VARCHAR2(10)
);

INSERT INTO a VALUES ('U1', 'T1');
INSERT INTO a VALUES ('U1', 'T1');
INSERT INTO a VALUES ('U1', 'T2');
INSERT INTO a VALUES ('U1', 'T3');
INSERT INTO a VALUES ('U1', 'T4');

INSERT INTO a VALUES ('U2', 'T1');
INSERT INTO a VALUES ('U2', 'T1');
INSERT INTO a VALUES ('U2', 'T2');
INSERT INTO a VALUES ('U2', 'T3');
INSERT INTO a VALUES ('U2', 'T5');

commit;

I want a query that will return me all the usernames who contain token 'T1' and
'T2', but not 'T5'.

So in the above example, the query should return 'U1', but not 'U2'.

Note that for the same username, the same token can appear multiple times.

Thanks

Followup:
ops$tkyte@ORA920> select username,
  2         count( distinct decode(token, 'T5', null, token ) ) t1_t2_cnt,
  3         count( distinct decode(token, 'T5', token, null ) ) t5_cnt
  4    from a
  5   where token in ( 'T1', 'T2', 'T5' )
  6   group by username
  7  having count( distinct decode(token, 'T5', null, token ) ) = 2
  8     and count( distinct decode(token, 'T5', token, null ) ) = 0
  9  /

USERNAME    T1_T2_CNT     T5_CNT
---------- ---------- ----------
U1                  2          0

ops$tkyte@ORA920>

August 02, 2003 Mitul -- Thanks for the question regarding "Difference Between Unique Index and Primary Key Index", version 8.06
You Asked
IS fetching row(s) using Primary key Index (in where clause) is better then
Fetching row(s) using Unique Index (in where clause)?
Is there any internal difference between those Unique Index and Primary Key
Index?
and we said...
There is no such thing as a "primary key index".

A primary key is two things:

o assures the values in a column(s) are unique
o assures they are NOT NULL

A primary key under the covers will use either a UNIQUE or NON-UNIQUE index.  If
the primary key is deferrable it'll use a non-unique index.  If the column(s)
are already indexed with a non-unique index, the primary key constraint will
rely on that index.  Otherwise a primary key will happen to create a unique
index.

Hence, there is no real difference here.  Under the covers there will be either
a unique or non-unique index.

By all means -- if something is a primary key CALL IT THAT.  Don't use a unique
index, use the primary key constraint.
Review & Followup
Rating: 2
Primary Keys are supposed to be unique aren't they? August 08, 2002
Reviewer: Paul Dubar from Australia
I have been confused by this very thing recently.

Am i right in saying that a PK will just happen to associate itself with any
index matching it's column list, and the index has nothing to do with enforcing
the constraint? If a PK defines a list of columns that must be unique, why/how
would you want to create a non-unique index on that column list?

Would you expect to see a NOT NULL constraint created too or is this implicit in
the PK constraint.

Followup:
Primary keys ARE unique.

The index used to enforce a primary key DOES NOT HAVE TO BE unique.

NOT NULL is implicit.

As for "why" the reasons are threefold (or mayby 4 or 5 or 6 fold, there are
lots of reasons, these just popped into my head):

a) you want a DEFERRABLE constraint (see the sql ref for info on that or search
this site for deferrable).  Here, the primary key is allowed to have dups during
the course of your transaction but by the time you commit, the primary key
values must be unique.  We can only do this with a non-unique index

b) you have an existing non-unique index and the primary key is on the "leading
edge" of this index.  We will use that index to enforce the uniqueness in the
data rather then creating YET ANOTHER index (better performance/space usage)

c) you have a need to enable/disable the primary key (say for large bulk loads
in a DW).  You don't want to drop the index.  If the index is non-unique and we
disable the primary key, we won't drop the index.  If the index is UNIQUE and we
disable the primary key, we drop the index.

and so on....

Rating: 3
What is the meaning on UNIQUE and NON UNIQUE August 14, 2002
Reviewer: Paul Dubar from Australia
Thanks for the useful follow up.

Perhaps its terminology that is confusing me here. I understand that if you have
a PK defined on a set of columns and an index exists matching that set of
columns (or "leading" part thereof), the PK may associate itself with that index
and it is the index that enforces the uniqueness. If the index's column list is
an exact duplicate of the PK column list, the index's contents are inherently
unique, however Oracle allows that index to be defined as NON-UNIQUE to support
the additional functionality you describe e.g. in part c of your reply.

Followup:
Well -- i don't agree with the semantics of:

the PK may associate itself with that index
and it is the index that enforces the uniqueness.

100%.

The index does not enforce the uniqueness. The constraint does and the
constraint makes use of an index (be it a UNIQUE index or an index that allows
for duplicates) to make this enforcement go faster.

The index itself does not enforce the uniqueness.

Rating: 4
Yes, but I want to keep my index... August 14, 2002
Reviewer: Colin Davies from Pasadena, California, USA
>If the index is non-unique and we disable the primary key, we won't drop the
index.  If the index is UNIQUE and we disable the primary key, we drop the
index.<

I just need a little more clarification here. If I first create a UNIQUE INDEX
and then enable a PK constraint, Oracle will use that index.  Good.  However, if
I disable or drop the PK constraint, Oracle will also drop my unique index, even
though I may wish to keep the index?  That doesn't sound right.  I created the
index independently of the PK.

Followup:
Doesn't matter, we "kidnapped it".  It fell under the ownership of the
constraint if you will.  It goes away.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create UNIQUE index t_idx on t(x);
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t add constraint t_pk primary
key(x);
Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select index_name from user_indexes;

INDEX_NAME
------------------------------
T_IDX

now you see it...

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t drop constraint t_pk;
Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select index_name from user_indexes;
no rows selected

and now you don't

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx on t(x);
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select index_name from user_indexes;

INDEX_NAME
------------------------------
T_IDX

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t add constraint t_pk primary
key(x);
Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select index_name from user_indexes;

INDEX_NAME
------------------------------
T_IDX

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t drop constraint t_pk;
Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select index_name from user_indexes;

INDEX_NAME
------------------------------
T_IDX

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

Rating: 4
It's all about metadata isn't it? August 15, 2002
Reviewer: Adrian Billington from UK
I would have thought the real difference is metadata. Having a unique index on a
column tells Oracle very little about a column and the composition of its data
when it comes to making decisions. A unique constraint, however, tells Oracle
everything it needs to know about what the composition of the data should be. A
primary key constraint takes it a stage further and defines the data's core
identifier itself. Surely this all comes into play when we get into query
rewrite and such like?

Followup:
Absolutely dead it -- it is more information. It comes into play with query
rewrite and optimization.

Rating: 5
November 26, 2002
Reviewer: A reader
Hi Tom,

Answer this...,

 "Table in oracle MUST have primary key.Table without primary key is bad design"

Thanks,

Followup:
one needs questions in order to give answers.

You made a statement -- statement is half false and half pretty much true.

Tables do not HAVE to have a primary key.
Most tables do (an audit trail might not though for example)

Rating: 5
9i to the rescue again... November 27, 2002
Reviewer: Connor McDonald from UK
alter table XXX disable constraint YYY keep index;
                                       ***********
hth
Connor

Followup:
Yeah -- but what is the usefulness here?

If the index was unique and this was a unique/primary key constraint -- you
still have the de-facto constraint.

If the index was non-unique (via deferrable constraint for example) and this was
a unique/primary key constraint -- you didn't need to do that (keep index was
implied).

So -- why disable the unique constraint, yet, keep the unique index??

Rating: 5
I didn't say it was useful.... November 27, 2002
Reviewer: Connor from UK
....I just responded to the guy who said

"However, if I disable or drop the PK constraint, Oracle will also drop my
unique index, even though I may wish to keep the index?  That doesn't sound
right.  I created the index independently of the PK."

:-)

Cheers
Connor

Followup:
Ahh -- got it, sorry about that (still confused why we added it tho ;)

Rating: 5
November 27, 2002
Reviewer: A reader
from the post by adrian billington:
"Having a unique index on a column tells Oracle very little about a column and
the composition of its data when it comes to making decisions."

Also you demonstrated above that the primary key don't need a unique index and a
non-unique index can't be "kidnapped" like a unique index.

The question is: then what is the utility of a unique index? If my primary key
can be supported by a non-unique index (though the data may be unique), I would
rather prefer to have a non unique index (because I can maintain it seperate
from the primary key). Am I right/wrong?

Followup:
Well, maybe -- it could impact the optimizer to a degree which sometimes looks
at the index.  You'll want to keep an eye on that.  Consider:

ops$tkyte@ORA920.US.ORACLE.COM> create table t1 ( x int primary key, y int );
Table created.

ops$tkyte@ORA920.US.ORACLE.COM> insert into t1 select rownum, rownum from
all_objects;
29374 rows created.

ops$tkyte@ORA920.US.ORACLE.COM> analyze table t1
  2  compute statistics
  3  for table
  4  for all indexes;
Table analyzed.

ops$tkyte@ORA920.US.ORACLE.COM> create table t2 ( x int primary key DEFERRABLE,
y int );
Table created.

ops$tkyte@ORA920.US.ORACLE.COM> insert into t2 select rownum, rownum from
all_objects;
29374 rows created.

ops$tkyte@ORA920.US.ORACLE.COM> analyze table t2
  2  compute statistics
  3  for table
  4  for all indexes;
Table analyzed.

ops$tkyte@ORA920.US.ORACLE.COM> set autotrace traceonly explain

ops$tkyte@ORA920.US.ORACLE.COM> select * from t1 where x = 5;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=12)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=2 Card=1 Bytes=12)
   2    1     INDEX (UNIQUE SCAN) OF 'SYS_C004498' (UNIQUE) (Cost=1 Card=100)

ops$tkyte@ORA920.US.ORACLE.COM> select * from t2 where x = 5;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=294 Bytes=3528)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T2' (Cost=2 Card=294 Bytes=3528)
   2    1     INDEX (RANGE SCAN) OF 'SYS_C004499' (NON-UNIQUE) (Cost=1 Card=117)

ops$tkyte@ORA920.US.ORACLE.COM> analyze table t2 compute statistics for all
indexed columns;

Table analyzed.

ops$tkyte@ORA920.US.ORACLE.COM> select * from t2 where x = 5;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=12)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T2' (Cost=2 Card=1 Bytes=12)
   2    1     INDEX (RANGE SCAN) OF 'SYS_C004499' (NON-UNIQUE) (Cost=1 Card=1)

ops$tkyte@ORA920.US.ORACLE.COM> set autotrace off

You might have to watch for conditions like that where a non-unique index will
"explode" the cardinality out.  Here, it didn't matter much but if that was down
in the middle of a big plan and gets multiplied out by something higher up in
the plan, it could have an impact.

But overall -- using a non-unique index has many advantages, yes.

Rating: 5
Brilliant November 27, 2002
Reviewer: A reader

Rating: 4
November 28, 2002
Reviewer: Sagi from India
Hi Tom,

I got confussed with the below statement of yours

"The index does not enforce the uniqueness. The constraint does and the
constraint makes use of an index (be it a UNIQUE index or an index that allows
for duplicates) to make this enforcement go faster.

The index itself does not enforce the uniqueness. "

Here is my Statements executed.....

SQL> CREATE TABLE T (X NUMBER) ;

Table created.

SQL> CREATE UNIQUE INDEX IDX_1 ON T(X) ;

Index created.

SQL> SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE FROM USER_CONSTRAINTS
  2  WHERE TABLE_NAME='T' ;

no rows selected

SQL> SELECT INDEX_NAME, INDEX_TYPE, UNIQUENESS
  2  FROM USER_INDEXES
  3  WHERE TABLE_NAME='T' ;

INDEX_NAME                     INDEX_TYPE                  UNIQUENES
------------------------------ --------------------------- ---------
IDX_1                          NORMAL                      UNIQUE

SQL>
SQL>
SQL> INSERT INTO T VALUES(1) ;

1 row created.

SQL> INSERT INTO T VALUES(1) ;
INSERT INTO T VALUES(1)
*
ERROR at line 1:
ORA-00001: unique constraint (SCOTT.IDX_1) violated

So if you see from the Statements executed about then you can see that there is
no constraint but still I am not able to allowed insert a duplicate value. This
is because of UNIQUE INDEX.

So does it mean that Uniquness is enforced here by INDEX even though there is no
constraint.

Anticipating your valuable clarrifications and reply.

Thanx in Advance.

Sagi

Followup:
It is a matter of semantics.

You create a constraint to enforce uniqueness.  That is the semantically correct
thing to do.

Yes, of course you can use a unique index to enforce uniqueness but you've
totally lost the semantics.  The data dictionary won't be screaming out "this is
a unique thing here".  Tools won't see it as unique.  No foreign keys to it.
The query rewrite won't be as smart.

If you have a column(s) that is unique -- use a constraint, do NOT use a unique
index.

Lets put it this way -- if you find yourself creating a unique index say "whoops
-- I'm doing something very wrong here".  (unless you are simply precreating it
for a constraint that will be placed on there).  You shouldn't have the need to
creat a unique index.

Rating: 5
A possible use for "KEEP INDEX" December 30, 2002
Reviewer: Connor McDonald from UK
A light bulb moment for why KEEP INDEX may have been implemented based on some
other forum postings...

Scenario:
- Partitioned table
- Unique or PK constraint backed by local (or otherwise partitioned) unique
index
- SQLLDR Direct load, some rows violate constraint, so 1 or more index
partitions unusable.

At this point you might want to:

- disable or drop constraint KEEP INDEX
- rebuild just those index partitions that are unusable
- enable or add constraint, exceptions into etc

The KEEP INDEX becomes useful because the constraint applies to the whole table,
whereas we only want to rebuild those index *partitions* that are unusable.
Without KEEP INDEX the entire index would need be to rebuilt/recreated.

(This hypothesis wholly untested btw)

Cheers
Connor

Rating: 5
two questions related to unique and primary constraints April 30, 2003
Reviewer: Unmesh from OH, USA
Thanks to all for very informative discussion. I have two questions -

1. As a summary of above discussion can I conclude that - if "uniqueness" of
data is required a unique constraint should be used instead of a unique index.

2. Is there any advantage of creating a unique index first and then creating
primary key constraint as compared to creating primary key constraint directly
(which inturn automatically creates the underlying index)? Any advantage of one
over the other?

i.e.

create table t ( x int );
create UNIQUE index t_idx on t(x);
alter table t add constraint t_pk primary key(x) using index;

Vs.

create table t ( x int );
alter table t add constraint t_pk primary key(x) using index;

Followup:
1) yes.

2) well, you could create the index in parallel nologging on a huge table --
then "kidnap it".

The one advantage would be that you know that a unique index is created.  If
there was an index on t(x,y,z) -- the unique constraint would Kidnap it -- if
you really truly want there to be a unique index, that sort of ensures it.

Rating: 5
May 23, 2003
Reviewer: Mirjana from Barcelona

Rating: 3
disabled foreign key constraints August 02, 2003
Reviewer: j.
hi tom,

a few years ago I saw an application (7.3.x) with lots of DISABLED foreign key
constraints in it. referential integrity was enforced by corresponding triggers
though - adding some additional "stuff" (e.g. to handle mutating table issues).

I wonder if the disabled foreign key constrained were just used to "describe"
the structure of the data model. is there any advantage apart from that?

Followup:
I'd betcha that system -- had it enabled the fkeys, would find lots and lots of
dirty data.

it is very very hard to do RI in a trigger properly and since they didn't
implement their system in a reasonable fashion, I would guess they did't do the
triggers right either.

yes, the constraints would have been there for some tool like an ad hoc query
builder or whatever to describe the relationships between the objects.  so the
query builder would know what joins to what.

August 02, 2003 josh -- Thanks for the question regarding "How can I get the text of the sql that is actually run in select from view upon view upon view?", version 9.2
You Asked
Greetings Tom,

I have a report, that selects from a view. This view is built upon three views.
Each of the underlying views is built upon at least 2 tables. When I run an
explain plan, I can see how the tables are being queried, but what I would
really like to see is the actual sql that is being run against the underlying
tables. I would like to see this in order to determine if my where clause
filters are making it down through the plan.

Thanks for your time!
and we said...
You would run the TKPROF and see the rows flowing through each of the steps of
the plan.

You are seeing the "real" sql when you see the query.  As soon as we get that --
it is turned into an internal data structure, nothing human readable at all.

Many times you can see it right from the plan itself:

ops$tkyte@ORA920> create table t1 as select * from all_objects;

Table created.

ops$tkyte@ORA920> alter table t1 add constraint t1_pk primary key(object_id);

Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t2 as select * from all_objects;

Table created.

ops$tkyte@ORA920> create index t2_idx on t2(object_name);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace view v1
  2  as
  3  select t1.object_id, t2.object_name
  4    from t1, t2
  5   where t1.object_id = t2.object_id(+)
  6  /

View created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace view v2
  2  as
  3  select t1.object_id, t2.object_name
  4    from t1, t2
  5   where t1.object_id = t2.object_id
  6  /

View created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly
ops$tkyte@ORA920> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA920> select * from v1 where object_name = 'TS$';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   FILTER
   2    1     MERGE JOIN (OUTER)
   3    2       SORT (JOIN)
   4    3         TABLE ACCESS (FULL) OF 'T1'
   5    2       SORT (JOIN)
   6    5         TABLE ACCESS (FULL) OF 'T2'

that would indicate that the predicate was NOT pushed -- in general, outer
joins -- aggregates -- things like that will prevent pushing..

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

ops$tkyte@ORA920> select * from v2 where object_name = 'TS$';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'T2'
   3    2       INDEX (RANGE SCAN) OF 'T2_IDX' (NON-UNIQUE)
   4    1     INDEX (UNIQUE SCAN) OF 'T1_PK' (UNIQUE)

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

tkprof prof clinches it however:

select *
from
 v1 where object_name = 'TS$'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.84       0.86        395        810          5           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.85       0.86        395        810          5           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 110

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  FILTER  (cr=810 r=395 w=262 time=865041 us)
  29391   MERGE JOIN OUTER (cr=810 r=395 w=262 time=823681 us)
  29391    SORT JOIN (cr=405 r=0 w=0 time=290619 us)
  29391     TABLE ACCESS FULL T1 (cr=405 r=0 w=0 time=47729 us)
  29391    SORT JOIN (cr=405 r=395 w=262 time=369710 us)
  29393     TABLE ACCESS FULL T2 (cr=405 r=133 w=0 time=56377 us)

see, every row flowed OUT OF the table access fulls - no predicate pushing

select *
from
 v2 where object_name = 'TS$'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          2          6          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          2          6          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 110

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  NESTED LOOPS  (cr=6 r=2 w=0 time=390 us)
      1   TABLE ACCESS BY INDEX ROWID T2 (cr=4 r=1 w=0 time=283 us)
      1    INDEX RANGE SCAN T2_IDX (cr=3 r=1 w=0 time=236 us)(object id 32649)
      1   INDEX UNIQUE SCAN T1_PK (cr=2 r=1 w=0 time=78 us)(object id 32647)

and here, 1 row flowed out -- predicate was pushed
Review & Followup
Rating: 4
Row Source Operation December 20, 2002
Reviewer: Josh
Tom,

Sometimes when I run tkprof against the trace files I collect using standard
sql_trace=true, I do not receive the row source output. How can I ensure that
the row source output is generated for each query that is within the trace file.

Also I receive the following error when I try to use explain=user/pwd@tnsname

error during retrieval of EXECUTE PLAN output
ORA-01406: fetched column value was truncated

Followup:
exit sqlplus, so the cursor gets closed.  the STAT records are not printed out
until after the cursor is closed.  SQLPlus keeps the last cursor open.

Also, make sure you are not hitting max dump file size -- that stat records will
many times be the last thing written out.

The other could be a result of using 8i or before tkprof on 9i or better trace
files.

create table PLAN_TABLE (
        statement_id    varchar2(30),
        timestamp       date,
        remarks         varchar2(80),
        operation       varchar2(30),
        options         varchar2(255),  <<<=== 9i and up

create table PLAN_TABLE (
        statement_id    varchar2(30),
        timestamp       date,
        remarks         varchar2(80),
        operation       varchar2(30),
        options         varchar2(30),   <<<<==== 8i and before

make sure you have the right sized "plan" table in 9i as well -- drop and
recreate it if you have an 8i legacy one.

Rating: 4
August 01, 2003
Reviewer: A reader
tom,

i am recieving this kind of error

java.sql.SQLException: ORA-01406: fetched column value was truncated

i am receiving this error only for two records from the view resultset .. the
problem is identified for a column (item_description)

i am using db link and accessing an oracle apps view(readonly). Oracle version i
am using is 9iR2 and the oracle apps is running in Oracle 8i.

scenario 1
~~~~~~~~~~
using jsp, i am trying to read the view it throws me the ora-01406 error..

i am selecting few columns from the view.

Scenario 2
~~~~~~~~~~
when in the same jsp, when i specify the primary key columns and query that
particular records, i am getting the records ..No error message

Scenario 3:
~~~~~~~~~~
when used in SQLPLUS. it fails with the same error

scenario 4:
~~~~~~~~~~
it works well on TOAD !!

i am pasting you the column which is giving error and which i could see in TOAD

030735,508212,200REM7668003705AM001000000000000000,Polipasto electrico marca R&M
de cadena de eslabón

item_description :Polipasto electrico marca R&M de cadena de eslabón

please see that the above data has special character.. is that bcos of this its
failing.. but again..the below record has the special character it doesnt fail

030735,508212,200REM7668003705AM001000000000000000,Instalación

item_description:Instalación

i guess, that the value for item_description is less when compared to above and
so its working fine with the " Instalación" case...

can you please help us resolve this issue..

tom, one more question for you. how does oracle handle internally when it
fetches special character...

in toad i am getting the special character properly, but the same record which i
said i am able to get when i use the primary key column in the where clause i am
not getting the special character properly...

thanks for all your time and considerations ...

Followup:
what are the character sets of everything involved here.

servers and client.

Rating: 4
August 01, 2003
Reviewer: A reader
On the server where we are having our tables:

SQL>select * from v$NLS_PARAMETERS;

PARAMETER                                                        VALUE
------------------------------------------------------------
NLS_LANGUAGE                                                     AMERICAN
NLS_TERRITORY                                                    AMERICA
NLS_CURRENCY                                                     $
NLS_ISO_CURRENCY                                                 AMERICA
NLS_NUMERIC_CHARACTERS                                           .,
NLS_CALENDAR                                                     GREGORIAN
NLS_DATE_FORMAT                                                  DD-MON-RR
NLS_DATE_LANGUAGE                                                AMERICAN
NLS_CHARACTERSET                                                 UTF8
NLS_SORT                                                         BINARY
NLS_TIME_FORMAT                                                  HH.MI.SSXFF AM

PARAMETER                                                        VALUE
------------------------------------------------------------
NLS_TIMESTAMP_FORMAT                                             DD-MON-RR
HH.MI.SSXFF A
NLS_TIME_TZ_FORMAT                                               HH.MI.SSXFF AM
TZH:TZM
NLS_TIMESTAMP_TZ_FORMAT                                          DD-MON-RR
HH.MI.SSXFF A
NLS_DUAL_CURRENCY                                                $
NLS_NCHAR_CHARACTERSET                                           UTF8
NLS_COMP                                                         BINARY
NLS_LENGTH_SEMANTICS                                             BYTE
NLS_NCHAR_CONV_EXCP                                              FALSE

19 rows selected.

SQL>select * from global_name;

GLOBAL_NAME
------------------------------------------------------------
TSTGEMWP

SQL>select * from NLS_DATABASE_PARAMETERS;

PARAMETER                      VALUE
------------------------------ -----------------------------
NLS_LANGUAGE                   AMERICAN
NLS_TERRITORY                  AMERICA
NLS_CURRENCY                   $
NLS_ISO_CURRENCY               AMERICA
NLS_NUMERIC_CHARACTERS         .,
NLS_CHARACTERSET               UTF8
NLS_CALENDAR                   GREGORIAN
NLS_DATE_FORMAT                DD-MON-RR
NLS_DATE_LANGUAGE              AMERICAN
NLS_SORT                       BINARY
NLS_TIME_FORMAT                HH.MI.SSXFF AM

PARAMETER                      VALUE
------------------------------ -----------------------------
NLS_TIMESTAMP_FORMAT           DD-MON-RR HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT             HH.MI.SSXFF AM TZR
NLS_TIMESTAMP_TZ_FORMAT        DD-MON-RR HH.MI.SSXFF AM TZR
NLS_DUAL_CURRENCY              $
NLS_COMP                       BINARY
NLS_LENGTH_SEMANTICS           BYTE
NLS_NCHAR_CONV_EXCP            FALSE
NLS_NCHAR_CHARACTERSET         UTF8
NLS_RDBMS_VERSION              9.2.0.3.0

20 rows selected.

SQL>

**************************************************************
this is on the remote server...

SQL>select * from v$NLS_PARAMETERS@DBLINK_DFWQA107;

PARAMETER                                                        VALUE
------------------------------------------------------------
NLS_LANGUAGE                                                     AMERICAN
NLS_TERRITORY                                                    AMERICA
NLS_CURRENCY                                                     $
NLS_ISO_CURRENCY                                                 AMERICA
NLS_NUMERIC_CHARACTERS                                           .,
NLS_CALENDAR                                                     GREGORIAN
NLS_DATE_FORMAT                                                  DD-MON-RR
NLS_DATE_LANGUAGE                                                AMERICAN
NLS_CHARACTERSET                                                 WE8ISO8859P1
NLS_SORT                                                         BINARY
NLS_TIME_FORMAT                                                  HH.MI.SSXFF AM

PARAMETER                                                        VALUE
------------------------------------------------------------
NLS_TIMESTAMP_FORMAT                                             DD-MON-RR
HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT                                               HH.MI.SSXFF AM
TZH:TZM
NLS_TIMESTAMP_TZ_FORMAT                                          DD-MON-RR
HH.MI.SSXFF AM TZH:TZM
NLS_DUAL_CURRENCY                                                $
NLS_NCHAR_CHARACTERSET                                           WE8ISO8859P1
NLS_COMP                                                         BINARY

17 rows selected.

SQL>select * from NLS_DATABASE_PARAMETERS@DBLINK_DFWQA107;

PARAMETER                      VALUE
------------------------------ -----------------------------
NLS_LANGUAGE                   AMERICAN
NLS_TERRITORY                  AMERICA
NLS_CURRENCY                   $
NLS_ISO_CURRENCY               AMERICA
NLS_NUMERIC_CHARACTERS         .,
NLS_CALENDAR                   GREGORIAN
NLS_DATE_FORMAT                DD-MON-YY
NLS_DATE_LANGUAGE              AMERICAN
NLS_CHARACTERSET               WE8ISO8859P1
NLS_SORT                       BINARY
NLS_NCHAR_CHARACTERSET         WE8ISO8859P1

PARAMETER                      VALUE
------------------------------ -----------------------------
NLS_TIME_FORMAT                HH.MI.SSXFF AM
NLS_TIMESTAMP_FORMAT           DD-MON-RR HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT             HH.MI.SSXFF AM TZH:TZM
NLS_TIMESTAMP_TZ_FORMAT        DD-MON-RR HH.MI.SSXFF AM TZH:TZM
NLS_RDBMS_VERSION              8.1.7.3.0
NLS_COMP                       BINARY

17 rows selected.

SQL>select * from global_name@DBLINK_DFWQA107;

GLOBAL_NAME
------------------------------------------------------------
DFWPR107.WORLD

SQL>

hope i have answered your query..i have given you the remote server and the
server which we are using.

client ..?. guess that v$NLS_PARAMETERS will give the client ...

thanks tom. hope the above output will lead us to the problem area...

Followup:
there is at least one set of character set translation going on.  the main db is
utf8, the secondary is we8iso8859p1.

I'll betcha the java client (i'm sure of that) and the sqlplus client (the
NLS_LANG setting on the client would show that) are both using a multi-byte
character set

and toad -- is not...

so, I believe this is related to utf8 -> we8iso -> utf8 and the string shrinking
and growing.

What happens if you were to access the database directly -- that'll help track
this down.

Rating: 4
August 01, 2003
Reviewer: A reader
tom

SUBSTR(PLA.ITEM_DESCRIPTION,1,50) ITEM_DESCRIPTION,

one more thing i noticed, in the view definition the actual table is substringed
to get only 50 char.

is that causing the error when selecting ....if that is not substringed..will it
behave properly..

thanks...

Followup:
doubt it.

Rating: 4
characterset translation August 01, 2003
Reviewer: A reader
tom,
ok, changing that way will not do much...

is there anyother way to set the charactset at code level /sesion level to bring
the data and the use it..

sounds absurd?...sorry for my ignorance. if that is there it would help me..

thanks

Followup:
setting the clients NLS_LANG to we8iso might help -- i'd give that a try.

Rating: 4
NLS_LANG August 02, 2003
Reviewer: A reader
tom,
can you tell me how to set the nls_lang for a standalone java prgm. or even for
a query when run through sqlplus.

also one more info i read in metalink Note:158577.1

5.4 What about database links?
------------------------------
The NLS_LANG on the server (or client) has no influence on characterset
conversion trough a database link, Oracle will do the conversion from the
(national) characterset of the source database to the  (national) characterset
of the target database (or inverse).

thanks for your time ..

Followup:
set your NLS_LANG environment variable.

August 02, 2003 sushant -- Thanks for the question regarding "level clause in connect by & rownums", version 8.1.6
You Asked
1)i have used the level clause in a connect by &achieved the output but i have
not completely understood how does it work.

select level, max(sal) from emp where level=2
 connect by prior sal > sal
 group by level

in the above query could u tell me how exactly oracle will go about evaluating
this query

2)
  i have a table  empinfo with the foll columns (
   empno    number(6) primary key,
  fname    varchar2(20),
  lname     varchar2(20),
  sal      number ,
  dob       date,
  comm_dt   date)

 we have 2 types of employees (officers & sailors) . the only way i can
distinguish between an officer and a sailor is via the empno table where every
officer is assigned a 5 digit number & a sailor is assigned a 6 digit number.
This is the employee numbering system in use since years. Now the company is
shifting to oracle. earlier sailors were assigned numbers starting with 0 say
021347.

what i want is a system defined control at the database layer where i can
prevent a 6digit entry in the empno in case of officers. what i want to ensure
in case of officers i dont want any entry preceding with 0 in the empno column.
i dont hav any other table where i can get any data where i can differntiate
between an officer & a sailor.
nor can i add a column to the existing table . can u see a solution.
and we said...
1) it'll do it like this.  First it'll:

select level, sal
  from emp
connect by prior sal > sal
/

and then, it will get just the level 2's (where clause is done AFTER the
hirearchy is exploded)

select *
  from ( select level lvl, sal
           from emp
        connect by prior sal > sal
       )
 where lvl = 2
/

And then, since you are getting a single level, it'll find the max of it:

select max( sal )
  from ( select *
           from ( select level lvl, sal
                    from emp
                 connect by prior sal > sal
                )
          where lvl = 2
       )
/

That last query is equivalent to your first query but the nesting of the inline
views shows the logical progression of that query.

It is a very strange query....

2) Well, if the number "021347" is a 6 digit number -- you are using a different
number system then I use.  that is a five digit number, 21347.  If you used
leading 0's as being significant, you will NOT be using a number field in Oracle
-- you are using character strings that happen to contain digits, you are NOT
using a number.

You will use a varchar2(6) field here.  (actually, since you are switching
databases, would not RIGHT NOW be a perfect time to fix this terrible design
decision???  You are touching all of the code right?  Fix it.  I never believe
in the "nor can I add a column to the existing table" stuff -- all things are
possible.)

Your keyboard is malfunctioning -- it drops important letters such as Y and O
without warning.  You might want to look into that!
Review & Followup
Rating: 3
August 02, 2003
Reviewer: vj from in
if i were to take the example of emp table

i want to get an output of the sum of salary for each level considering the
downlines

SELECT
LEVEL,LPAD(' ',2*(LEVEL-1)) || ename tree
FROM
    scott.emp
START WITH empno = 7839
CONNECT BY PRIOR empno = mgr
/

LEVEL    TREE
1    KING
2      JONES
3        SCOTT
4          ADAMS
3        FORD
4          SMITH
2      BLAKE
3        ALLEN
3        WARD
3        MARTIN
3        TURNER
3        JAMES
2      CLARK
3        MILLER

Now, if i were to get an additional column named salary which should be grouped
at each level how can i acheive..which means, jones will have the sum of his
salary + all the people reporting to him and so on and so forth..

Rgds

Followup:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5069359022206

August 02, 2003 Anthony -- Thanks for the question regarding
      "Difference between constraints and Indexes ", version 8.1.6

      You Asked
Hi Tom,

    First of all I personally thank you (and of course I believe that others do)
for all the support you have given to us in giving us this opportunity to post
questions thru this site.

Now with my concern:

1.    I understand that constraints enforces business rule in my database and
indexes gives our queries makes much faster if we create and index to a table.

   My question is, what is the relationship between this 2.
  For example :

 SQL> create table online_tb (seq_no number, trun_num number, head
varchar2(20) constraint NN NOT NULL, tran_Date date);

  then,
       alter table online_tb add constraint ol_tb_uk1
       unique (seq_no,trun_num, head);

SQL> select uniqueness,index_name from user_indexes where
table_name='ONLINE_TB';

UNIQUENES INDEX_NAME
--------- ------------------------------
UNIQUE    OL_TB_UK1

SQL> SELECT CONSTRAINT_NAME, TABLE_NAME FROM USER_CONSTRAINTS WHERE
TABLE_NAME='ONLINE_TB';

CONSTRAINT_NAME                TABLE_NAME
------------------------------ ------------------------------
NN                             ONLINE_TB
OL_TB_UK1                      ONLINE_TB

SQL> ALTER TABLE ONLINE_tB ADD CONSTRAINT OL_TB_PK1 PRIMARY KEY
(SEQ_NO,TRUN_NUM,HEAD);
ALTER TABLE ONLINE_tB ADD CONSTRAINT OL_TB_PK1 PRIMARY KEY
(SEQ_NO,TRUN_NUM,HEAD)
                                               *
ERROR at line 1:
ORA-02261: such unique or primary key already exists in the table

SQL> CREATE UNIQUE INDEX OL_TB_UNIQ ON ONLINE_TB (SEQ_NO,TRUN_NUM,HEAD);
CREATE UNIQUE INDEX OL_TB_UNIQ ON ONLINE_TB (SEQ_NO,TRUN_NUM,HEAD)
                                             *
ERROR at line 1:
ORA-01408: such column list already indexed

And if I create a  UNIQUE INDEX on that same columns it gives me an error, Can
you explain to me y?

2. We are receiving an error :
    'ASSERTION FAILED'
    PROGRAM : C:\ORANT\BIN\ifrun60.exe
    FILE : e:\f994w32\src\if\iff\ifrlf.c
    LINE : 890

Thank you very much and more power.

      and we said...
Well, indexes may (or may NOT) make your queries go faster -- it depends.

The relationship between indexes and constraints is:

o a constraint MIGHT create an index or use an existing index to efficient
enforce itself.  For example, a PRIMARY KEY constraint will either create an
index (unique or non-unique depending) or it will find an existing suitable
index and use it.

o an index has nothing to do with a constraint.  An index is an index.

So, a constraint MIGHT create/use and index.  An INDEX is  an INDEX, nothing
more, nothing less.

2) please contact support, they'll be able to help you.
Review &
      Followup

      Rating: 2
PK with non
      -unique index ??  March 14, 2002
Reviewer:
      A reader
Regarding your statement "a PRIMARY KEY constraint will either create an
index (unique or non-unique depending) "
 Here's my dumb question.
  Please correct me if I am wrong a primary key will always
(use or) create a unique Index and never a non unique one.
  SO why the statement :either create an
index (unique or non-unique depending).
Can u please give an example of PK using/creating non unique index
Thanx

          Followup:

read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3051796619914
for examples.

      Rating: 5
Constraints and
      Indexes  April 22, 2002
Reviewer:  Martin
       from England
Hi Tom,

Firstly, thanks for a brilliant site, the depth of knowledge and help is
priceless. Just a quick question :

You said

"

The relationship between indexes and constraints is:

o a constraint MIGHT create an index or use an existing index to efficient
enforce itself.  For example, a PRIMARY KEY constraint will either create an
index (unique or non-unique depending) or it will find an existing suitable
index and use it.

o an index has nothing to do with a constraint.  An index is an index.

So, a constraint MIGHT create/use and index.  An INDEX is  an INDEX, nothing
more, nothing less.
"

But consider the following SQL :

SQL> create table test_table ( A  VARCHAR2(10) );

Table created.

SQL> create unique index A_UI on test_table(a);

Index created.

SQL> select constraint_name, constraint_type
  2  from user_constraints
  3  where table_name = 'TEST_TABLE';

no rows selected

SQL> INSERT INTO TEST_TABLE VALUES ('A');

1 row created.

SQL> /
INSERT INTO TEST_TABLE VALUES ('A')
*
ERROR at line 1:
ORA-00001: unique constraint (BMTEST.A_UI) violated

Is this just a bit of a herring rouge error or is this a little example of how
an index ISN'T just an index, but also a constraint (whether an "internal"
constraint or otherwise)?

          Followup:

It isn't a constraint.  the error message "misnames" it.

If it were a constraint, you could create a foreign key to it -- but you cannot.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table test_table ( A  VARCHAR2(10), b
varchar2(10) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create unique index A_UI on test_table(a)
  2
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table test_table add constraint fk
foreign key(b) references test_table(a);
alter table test_table add constraint fk foreign key(b) references test_table(a)
                                                                              *
ERROR at line 1:
ORA-02270: no matching unique or primary key for this column-list

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table test_table add constraint pk
primary key(a);

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table test_table add constraint fk
foreign key(b) references test_table(a);

Table altered.

      Rating:
      4
  July 25, 2002
Reviewer:  mike

I don't understand.  If unique index can improve performance(sometimes) and
enforce uniqueness, why do we need primary key constraint?

          Followup:

A primary key MEANS SOMETHING.  It conveys a specific semantic meaning.

A unique index, does not.

A primary key may or may not use a unique index.  A primary key also infers (and
enforces) NOT NULL.

the underlying tools (index -- unique or not, plus NOT NULL constraint) are used
to build a primary key.  A primary key means, semantically more then a
unique index does.

Consider it "documentation"

      Rating: 5
Unique Index,
      Unique Constraint   July 25, 2002
Reviewer:
      Inga  from Russia
Hello Tom!
What is the better to use to enforce uniqueness - unique index or unique
constraint?

          Followup:

unique constraint.

it has more semantic meaning
it can be deferrable if you want
it is better then a unique index

      Rating:
      3
PK  August 16, 2002
Reviewer:
      Reader
Some concern about PK and Index correct me if i am wrong when we create table
index is created implicitly whether we set a PK or not...if its the case y we
need to create Index explicitly if i think that explicit index is good for
performance than what do we use for imlicit one...Thanks alot

          Followup:

When you create a table, without a primary key, there will be NO index created.

An index will be created for a primary key constraint if and only if no existing
index exists that would suffice.

      Rating: 5
Unique Index
      and Unique Constraint  July 31, 2003
Reviewer:
      Fan  from Canada
Tom,

Considering the following two statement will achieve the same results of
enforcing unique values in column a. What do you think the benefit of the first
one of having a constraint in the table? Thanks.

1. alter table t add (constraint t_uk unique (a) using index (create unique
index t_ix1 on t (a) ))
                                                            2. create unique
index t_ix1 on t (a);

Previously I always used only unique indexes without unique constraints. Is it
good approach?

          Followup:

#1 is the only correct and proper way to do it.

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4941517587762
you should use a CONSTRAINT, the index is incidental and may not even be needed.

      Rating: 4
unique index is
      a misnomer  August 01, 2003
Reviewer:  mikito
      harakiri
What is justification for "unique index" concept?

Index is just a B-tree. We can have functional indexes, composite indexes (which
are disguised form of functional indexes:-), bitmap indexes, etc. However,
mixing constraint idea with access path idea todether is just wrong.

          Followup:

internally -- all indexes are unique actually.

"create unique index" -- implies the rowid is part of the functionally dependent
data in the index, the rowid is not part of the key.

"create index" -- implies the rowid is part of the key, ensuring each entry is
in fact unique.

A composite or concatenated index is not a disguised form of function based
indexes, I don't understand that comment.  they are just indexes with a key
defined as more then one column.

But agreed -- a constraint is a constraint and should be specified as such.  An
index is an index -- used to "tune"

      Rating: 5
index rows
      ordered also by rowid ?  August 01, 2003
Reviewer:
      Alberto Dell'Era  from Milan, Italy
> "create index" -- implies the rowid is part of the key,
> ensuring each entry is in fact unique.

Does this mean that the rows of the index are ordered
by rowid also, i.e. the indexed columns with equal
values are stored in the index sorted by rowid, and
not "randomly" ?

          Followup:

I'll never tell ;)

it would not be anything you could ever rely on.

the only way you can ensure the order of rows from a query is to use an ORDER
BY....

      Rating: 5
perhaps you may
      tell if I explain myself better ... ;-)  August 02, 2003

Reviewer:  Alberto Dell'Era  from Milan, Italy

> the only way you can ensure the order of rows from a query
> is to use an ORDER BY....

Of course ... actually i was thinking about a range-scan; since pinning the
"pointed by the rowids" table buffers is a relatively expensive operation (since
you must get the corresponding cache buffer latch), if the rows were ordered by
rowid (and so by block, essentially), simply visiting the index rows as they are
stored would be enough to minimize the number of pins, increasing the
scalability.

So your telling would not distract me from the Road to Knowledge ;-), quite the
opposite ...

          Followup:

actually -- in 9i, what you will find is the table access by rowid is moved out
as far as possible in many query plans -- meaning, we'll scan and get a bunch of
rowid together and then "bulk get" the corresponding rows -- further enhancing
this concept (that is new with the cbo in 9i)

(but yes, the rowid will be the last part of the key in the b*tree with a
non-unique index like that.  hypothesis is accurate)

      Rating: 5
got the
      point  August 02, 2003
Reviewer:  Alberto
      Dell'Era  from Milano, Italy
So, Oracle will try to minimize the pins of the table buffers, and whether this
is done by ordering the index rows also by rowid, or using the new "bulk get"
feature of 9i you mention, it is inessential for us, database data developers.

And the "index rows ordering by rowid" may be no more the case in 9i [even
assuming that it were performed on previous releases ...] since there's now the
new "bulk get" CBO feature.

Thanks!

      Rating: 5
Very useful
      discussion, thanks!  September 12, 2003
Reviewer:
      David Rydzewski  from Lexington, MA

August 02, 2003 Richard -- Thanks for the question regarding
      "Bind Variable Error - ORA-01006", version 9i

      You Asked
Hi,

I'm currently learning about Bind Variables (BVs). In *Beginning Oracle
Programming* chapter 10, page 399, there is a PL/SQL example that involves BVs:

declare
    l_update_text varchar2(100) :=
      'update &table_name
          set &updated_column_name = '':a''
        where &key_column_name = :a';
  begin
    execute immediate l_update_text using '&update_column_value',
&key_column_value;
  end;
  /

I have executed the example (which I downloaded from the Wrox website a while
back) exactly as in the book, and saw error *ORA-00904: invalid column name*,
which was all well and good. I then re-executed the example, but this time I
used the correct *key_column_name*. The result was *ORA-01006 bind variable does
not exist*. Can you shed any light on this?

Many Thanks,

Richard

P.S. Unless I'm looking in the wrong place, both *One-on-One Oracle* and
*Beginning Oracle Programming* appear to have vanished from the Wrox website. Do
you know where associated downloads may be found?

      and we said...
WROX went under.  Wiley bought a couple of their books -- apress bought the
rest.  apress bought my book -- it is on their website and they may have the
downloads there (you can contact them).  they'll be reprinted shortly.

your update statement would look like this if you dbms_output.put_lined it:

update t set c1 = ':a' where c2 = :a;

you have only one bind variable in there -- the other is a character string
literal ':a'

so, you would do this:

declare
    l_update_text varchar2(100) :=
      'update &table_name
          set &updated_column_name = :a
        where &key_column_name = :b';
  begin
    execute immediate l_update_text
      using '&update_column_value',
             &key_column_value;
  end;
/

Review &
      Followup

      Rating:
      4
  August 02, 2003
Reviewer:  Richard
       from Oxfordshire, UK
Thank you!

August 02, 2003 Kin Sun -- Thanks for the question regarding
      "Index on column of only two values : 'Y' or NULL", version 8.1.6

      You Asked
Dear Tom,

Info about the table
--------------------
Name                                      Null?    Type
----------------------------------------- -------- -----------
ACN                                                VARCHAR2(3)
SREF                                               VARCHAR2(9)
PROCESSED_FLAG                                     VARCHAR2(1)

No. of rows today: 2899213
New rows inserted everyday : 5000 to 6000
The value of the processed_flag column of each new row inserted everyday is set
to NULL.

A PL/SQL would be run against this table to get data to insert into another
table everyday. The first criteria is to select those rows which the value of
the processed_flag column set to NULL. After the PL/SQL processing, the
processed_flag will be set to a value of 'Y'.

My question is : what is the best way to do indexing on the processed_flag
column which contains mostly 'Y' and relatively little NULL values, but the goal
is to find the rows which the processed_flag column is NULL.

Thanks in advance!

      and we said...
use a function based index.

create index t_idx on t( decode(processed_flag,NULL,1,null) );

then

select * from t where decode(processed_flag,null,1,null) = 1;

can use that index.  AND since entirely null entries are not made in b*tree
indexes -- ONLY the rows where processed_flag is null will be made in the index
(the index will be tiny)

see

http://asktom.oracle.com/~tkyte/article1/index.html

Review &
      Followup

      Rating: 5
Great
      Idea!  July 31, 2003
Reviewer:  Chuck Jolley
       from OKC, OK USA
I had never thought of this and can use it in several places where I have highly
skewed flags on large tables!!!!
Thanks!!!!!!
Chuck

      Rating: 4
Wouldn't BITMAP
      index be useful in the case also?  July 31, 2003

Reviewer:  jochuan  from VA USA
Tom

      How about using BITMAP index in this case?  Will it perform worst than the
function index?

          Followup:

it is a transactional type table -- lots of inserts during the day.

bitmaps would be killer bad.

      Rating: 4
Which is
      better  July 31, 2003
Reviewer:  Juan Carlos
      Reyes  from Bolivia
Hi Tom, one question
What is better
a column with to values Y and NULL
and a function index
or
a column with to values Y and N (or other value)
and a normal index?

The fact of using a null instead other value like N could be interesting for
space saving, but, is a good practice in this case where the columns has  two
values?

:) Thanks

          Followup:

either

Y and NULL

or

Y and N

and a function based index.  you only want to index the infrequently occuring
value -- not all of them.

I would index

decode( column, NULL, 1 )  for Y and NULL

decode( column, 'N', 1 ) for Y and N

      Rating: 3
Creating index
      on process flag  July 31, 2003
Reviewer:  Ketan
      Bengali  from Sunnyvale, CA, USA
I would rename column from PROCESSED_FLAG to UNPROCESSED_FLAG.

Unprocessed_flag will have value Y when the record is not processed and will be
set to NULL once it is processed.

This way you can create index on UNPROCESSED_FLAG. At all the time, index will
have only 6000 records and performance will be extremely good.

Regards,

Ketan.

          Followup:

6 one way

1/2 dozen the other.

both are indexes on about 6000 rows.

      Rating: 4
If a DSS table
      how about BITMAP Index  July 31, 2003
Reviewer:
      Kin Sun Lau  from Hong Kong
Thanks Tom!

Yet if it is a DSS system and the 6000 rows loading is a bulk insert, is it good
to use BITMAP index?

Thanks again!

          Followup:

could well be but it depends on how they are processed.

this this column has two values
only one of which is interesting
and the value flips right after beging set (from not processed to processed)

I think the b*tree is most appropriate.

      Rating: 4
performance
      overhead of function index  July 31, 2003

Reviewer:  James
although both approaches(unprocessed_flag with Y/null vs. function index) index
6000 rows, does function index add any significant overhead during query
evaluation?

          Followup:

not during evaluation -- no.  marginal (stress marginal) insert/update overhead.

      Rating: 5
:)
      thanks  August 01, 2003
Reviewer:
      A reader

      Rating:
      4
condition  August 02, 2003
Reviewer:
      A reader
Hi Tom,

In order to use function based index, do we still need to set
QUERY_REWRITE_ENABLED=TRUE
QUERY_REWRITE_INTEGRITY=TRUSTED

in Oracle version 9.2.0.3?

thanks,

          Followup:

it depends.

did you write the function or did we?

ops$tkyte@ORA920> create table t as select * from scott.emp;
Table created.

ops$tkyte@ORA920> create index t_idx1 on t(upper(ename));
Index created.

ops$tkyte@ORA920> create or replace function f( p_string in varchar2 ) return
varchar2
  2  deterministic
  3  is
  4  begin
  5          return upper(p_string);
  6  end;
  7  /
Function created.

ops$tkyte@ORA920> create index t_idx2 on t(f(ename));
Index created.

ops$tkyte@ORA920> exec dbms_stats.set_table_stats( user, 'T', numrows => 1000000
);
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> show parameter query_rewrite

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
query_rewrite_enabled                string      false
query_rewrite_integrity              string      enforced
ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select * from t where upper(ename) = 'X';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=10000 Bytes=870000)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=10000
Bytes=870000)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX1' (NON-UNIQUE) (Cost=2 Card=4000)

we'll use that no problem..

ops$tkyte@ORA920> select * from t where f(ename) = 'X';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1323 Card=10000 Bytes=870000)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=1323 Card=10000 Bytes=870000)

ops$tkyte@ORA920> alter session set query_rewrite_enabled=true;

Session altered.

ops$tkyte@ORA920> select * from t where f(ename) = 'X';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1323 Card=10000 Bytes=870000)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=1323 Card=10000 Bytes=870000)

ops$tkyte@ORA920> alter session set query_rewrite_integrity=trusted;

Session altered.

ops$tkyte@ORA920> select * from t where f(ename) = 'X';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=10000 Bytes=870000)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=10000
Bytes=870000)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX2' (NON-UNIQUE) (Cost=2 Card=4000)

You have to tell is it is OK to use that one

ops$tkyte@ORA920> set autotrace off
ops$tkyte@ORA920>

August 02, 2003 Ravi -- Thanks for the question regarding
      "Partition-Wise Joins", version 8.1.7

      You Asked
I am going through this partition-wise joins (full, partial etc.)
Says 'hash-hash' is the best case (full partition-wise join) scenario.
'range-range' is the worst case scenario. (non-partition-wise join). How could
hash join be the only one that uses partition-wise join?
Assuming that we have 2 equi-partitioned tables (by range), we can still
eliminate unnecessary partitions and join the required partitions, right?? How
come range is not partition-wise join??

In few sentences, could you clearly make the distinction. And, what is the use
of partition-wise join?? Can only effectively be used in parallel joins??

      and we said...
it is more that it says "hash hash" is "easiest" (for humans, for the computer).

"range range" is "hardest" (for humans, not for the computer).

As it says:

<quote>
Range - Range

You can also use partition-wise joins for range partitioning. However,
this is more complex to implement because you must know your data's distribution
before performing the join. Furthermore, this can lead to data skew during the
execution if you do not correctly identify the partition bounds so that you have
partitions of equal size.

The basic principle for using range-range is the same as for hash-hash: you
must equi-partition both tables. This means that the number of partitions must
be the same and the partition bounds must be identical. For example, assume that
you know in advance that you have 10 million customers, and the values for
customerid vary from 1 to 10000000. In other words, you have possibly 10 million
different values. To create 16 partitions, you can range partition both tables,
Sales on s_customerid and Customer on c_customerid. You should define partition
bounds for both tables to generate partitions of the same size. In this example,
partition bounds should be defined as 625001, 1250001, 1875001, ..., 10000001,
so each partition contains 625000 rows.
</quote>

It is just saying "hash hash is a no brainer".  If both tables are partitioned
by hash on the join key -- you get an even distribution of data AND we can do
the partition wise join.

"range range is harder".  You might get a skewed distribution, you have to make
sure to evenly distribute the data yourself by range (you have to do more work)
and so on.

It is just a continuation of the fact that hash partitioning is a 'no brain'
operation -- where as with range partitioning you have to think about it for a
while.  NOT THAT range partitioning is bad -- in fact, it is used more in my
experience then hash partitioning.  It just takes more thought.
Review &
      Followup

      Rating: 5
any examples or
      demo on how to use partition-wise joins? Thanks  August 02,
      2003
Reviewer:  Reader

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96520/parpart.htm#98285

August 02, 2003 Jeroen -- Thanks for the question regarding
      "Questions on memory usage, outlines and cursor for loop", version 8.1.5

      You Asked
Hi Tom,

I hope you have the answer to these questions. Any explanation (be it in in the
form of a hyperlink) would be greatly appreciated

1) When you have stored an execution plan for a query using stored outlines,
does it matter whether the tables in the query are analyzed or (accidentally)
not at the time of execution ?
I understand that performance of any ad-hoc queries could be bad, but how about
those with outlines stored for them

2) A few of the benefits of a CURSOR FOR loop are that opening an closing of the
CURSOR is implicitly handled ?
Would the cursor still be closed when the EXIT statement is (mis)used to step
out of the loop.

3) How can you calculate the memory size that (an entry in) a PL/SQL table of
records INDEXED BY BINARY_INTEGER will occupy
Does a entry with a high index key take more size than one with a low index key
? (I am asking this because I am curious if, when using hashed keys, a high
value for hash_size would lead to more memory usage).
Will a delete of an entry free up the memory immediately ?
Could you point to some documentation on calculation rules for datatypes in
records (I use NUMBER(unrestricted), DATE, VARCHAR2 and PLS_INTEGER)

Many thanks in advance,

Jeroen

      and we said...
1) the goal of the stored outline is to supply a set of hints (very detailed
hints) that will be used to optimize the query.

The absence/presence of statistics should not (but could) have an impact on the
choosen plan.

A key point to remember about Optimizer Plan Stability is that it is implemented
via hints. Hints are not mandates; hints are not rules. While the hinting
mechanism used by Optimizer Plan Stability is a stronger one than is exposed to
us via the documented hinting mechanism, the optimizer is still, as always, free
to follow them or not at run-time. This is a double-edged sword ? it sounds like
a defect but in reality it is a feature. If changes are made to the database
that render a set of hints obsolete (as will happen if you, say, drop an index)
then Oracle will ignore the hints and generate the best plan it can.

In most all cases, the stored outline should be used.

2) Thats one of them (less code for you to write).  An exit from a cursor for
loop will logically close the cursor -- just as a return would or an exhausted
result set.  The right thing is done.

3) Here is a way to test as it can vary version/version as the algorithms are
fine tuned over time (make sure to start with a CLEAN session -- a fresh
session, else your PGA/UGA memory might already be allocated! it will look like
memory is "free")

ops$tkyte@ORA817.US.ORACLE.COM> create table t ( msg varchar2(20),
  2                                   name varchar2(64),
  3                                   value number );

Table created.

ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> declare
  2          type empArray is table of emp%rowtype index by binary_integer;
  3
  4          l_data empArray;
  5  begin
  6          l_data(1) := null;
  7          select * into l_data(1) from emp where rownum = 1;
  8
  9
 10          insert into t
 11      select 'aa) start', a.name, b.value
 12        from v$statname a, v$mystat b
 13       where a.statistic# = b.statistic#
 14             and lower(a.name) like 'session%memory%';
 15
 16          for i in 2 .. 1000
 17          loop
 18                  l_data(i) := l_data(1);
 19
 20                  if ( mod(i,200) = 0 )
 21                  then
 22                  insert into t
 23              select 'after ' || to_char(i,'00009'), a.name, b.value
 24                from v$statname a, v$mystat b
 25               where a.statistic# = b.statistic#
 26                     and lower(a.name) like 'session%memory%';
 27                  end if;
 28          end loop;
 29  end;
 30  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> break on name skip 1
ops$tkyte@ORA817.US.ORACLE.COM> select name, msg, value from t
  2  order by name, msg
  3  /

NAME                           MSG                       VALUE
------------------------------ -------------------- ----------
session pga memory             aa) start                266064
                               after  00200             331776
                               after  00400             397488
                               after  00600             463200
                               after  00800             528912
                               after  01000             578196

session pga memory max         aa) start                266064
                               after  00200             331776
                               after  00400             397488
                               after  00600             463200
                               after  00800             528912
                               after  01000             578196

session uga memory             aa) start                 63252
                               after  00200              63252
                               after  00400              63252
                               after  00600              63252
                               after  00800              63252
                               after  01000              63252

session uga memory max         aa) start                 63252
                               after  00200              63252
                               after  00400              63252
                               after  00600              63252
                               after  00800              63252
                               after  01000              63252

So, that shows it takes about 64k for 200 of these records (each 200 record step
up used about 64k more PGA memory -- in MTS this would be UGA memory)

Now, if you use:

 16          for i in 2 .. 1000
 17          loop
 18                  l_data(i*100) := l_data(1);

instead, (a sparse array, we use entries 1, 200, 300, 400, 500, 600, ...) the
incremental memory between steps is about 120k or so (on my machine anyway with
817 -- YMMV).  So yes, it'll take more but not eggregiously so.

A delete of an entry will free up that PGA memory to be used by your session
again - you can see this by running the above test two times in your same
session.  Your PGA memory will stay the same (its already there).

Review &
      Followup

      Rating: 3
Memory Used by
      a Session  May 24, 2002
Reviewer:  Rahul
      Sharma  from India.
Hi Tom

How can we calculate the total memory occupied by one
session? What are the things on basis of that we can calculate this ?

Thanks.

          Followup:

read the answer above, it shows how to see the PGA/UGA memory used by a session

      Rating:
      5
  May 27, 2002
Reviewer:
      A reader
Hi Tom,

I perform  similar test on my machine and i got better idea about your
explanation.

Question:

 Why UGA memory is remaining the same? i.e it stays
63252 all the time.

          Followup:

because i'm in dedicated server mode, most stuff is done in the PGA -- in MTS
(shared server) mode, the UGA would be more heavily utilized.

      Rating:
      5
  May 27, 2002
Reviewer:
      A reader
Hi Tom,

 I read your book's stored outline chapter. I like this apporch for tuning.

 We are using SIEBEL CRM application.Siebel software generate query and we can't
change sql statement generated by siebel.Some of the sql generated by siebel are
not optimized.So i am planning to use outline.

 SIEBEL strictly recommand RULE optimizar.

 Can we use stored outline feature in RULE optimizer.

Thanks.

          Followup:

Well, it will use the CBO on the queries you TUNE when needed, but it'll use RBO
on the rest.  This should be OK since you WANT to change the plans for the "bad"
queries.  Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table emp
  2  as
  3  select ename, empno from scott.emp group by ename, empno
  4  /
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table emp
  2  add constraint emp_pk
  3  primary key(empno)
  4  /
Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table emp compute statistics
  2  /
Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select empno, ename from emp where empno > 0
  2  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=14 Bytes=112)
   1    0   TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=14 Bytes=112)

So, we have a FULL TABLE SCAN plan, assume we want to use that...

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace outline MyOutline
  2  for category mycategory
  3  ON
  4  select empno, ename from emp where empno > 0
  5  /
Outline created.

we create and store the outline for it...

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table emp delete statistics
  2  /
Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select empno, ename from emp where empno > 0
  2  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
   2    1     INDEX (RANGE SCAN) OF 'EMP_PK' (UNIQUE)

since we got rid of the stats, we are using RBO and RBO is "index happy"...
So it uses the index in the plan..

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set use_stored_outlines =
mycategory
  2  /

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select empno, ename from emp where empno > 0
  2  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=5 Bytes=100)
   1    0   TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=5 Bytes=100)

That shows the query plan is changed by the "use stored outlines" and the
COST, CARD, BYTES in the output show the CBO was used....

Queries that don't have an optimizer plan stored:

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from dual;

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

continue to use the RBO (since we do not see a cost, card, bytes...)

This is expected since the optimizer plan stability feature works with HINTS and
HINTS will drop you into CBO.  If no plan is stored, RBO can be used.  So only
queries you WANT to influence will use CBO and that is OK

      Rating: 5
why it is not
      doing FTS  August 02, 2003
Reviewer:  Reader

I tried your demo with employees table in 9i R2 database. For some reason, why
it is not doing full table scan in the first first part of the demo. Could you
please look at it? Thanks.

SQL> create table employees_test
  2  as
  3  select last_name, employee_id from hr.employees
  4  group by last_name, employee_id
  5  /

Table created.

SQL> alter table employees_test
  2  add constraint emp_pk
  3  primary key(employee_id)
  4  /

Table altered.

SQL> analyze table employees_test compute statistics;

Table analyzed.

SQL>
SQL> set autotrace traceonly explain
SQL> select employee_id, last_name from employees_test where employee_id > 0
  2  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=107 Bytes=107
          0)

   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMPLOYEES_TEST' (Cost=2 <---------
NOTE: not doing full table scan
          Card=107 Bytes=1070)

   2    1     INDEX (RANGE SCAN) OF 'EMP_PK' (UNIQUE) (Cost=1 Card=107
          )

SQL> create or replace outline MyOutline
  2  for category mycategory
  3  ON
  4  select employee_id, last_name from employee_test where employee_id > 0
  5  .
SQL> create or replace outline MyOutline
  2  for category mycategory
  3  ON
  4  select employee_id, last_name from employees_test where employee_id > 0
  5  /

Outline created.

SQL> analyze table employees_test delete statistics;

Table analyzed.

SQL> set autotrace traceonly explain;
SQL> select employee_id, last_name from employees_test where employee_id > 0;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMPLOYEES_TEST'
   2    1     INDEX (RANGE SCAN) OF 'EMP_PK' (UNIQUE)

SQL> alter session set use_stored_outlines = mycategory;

Session altered.

SQL> set autotrace traceonly explain;
SQL> select employee_id, last_name from employees_test where employee_id > 0;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=4 Bytes=108)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMPLOYEES_TEST' (Cost=1
          Card=4 Bytes=108)

   2    1     INDEX (RANGE SCAN) OF 'EMP_PK' (UNIQUE) (Cost=2 Card=1)

      Rating: 5
more info for
      the above  August 02, 2003
Reviewer:  Reader

I deleted rows to make it a small table as I thought if it is a small table,
optimizer might ignore the index. But still it is not doing the full table scan.

SQL> delete from employees_test where rownum < 97;

96 rows deleted.

SQL> commit;

Commit complete.

SQL> alter table employees_test deallocate unused;

Table altered.

SQL> select count(*) from hr.employees_test;

  COUNT(*)
----------
        11

SQL> analyze table employees_test compute statistics;

Table analyzed.

SQL> set autotrace traceonly explain
SQL> select employee_id, last_name from employees_test where employee_id > 0;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=11 Bytes=143)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMPLOYEES_TEST' (Cost=2
          Card=11 Bytes=143)

   2    1     INDEX (RANGE SCAN) OF 'EMP_PK' (UNIQUE) (Cost=1 Card=11)

          Followup:

deallocate unused

a) won't do anything to the optimizer
b) won't ever reclaim space from a delete.

so, how many rows are in the table?

      Rating: 5
11 rows in the
      table.  August 03, 2003
Reviewer:  Reader

Thanks for pointing out about deallocate unused issue.

 Originally the table had 107 rows and if you see my cut and paste just before
the one for which you responded that will show you 107 rows and was using the
index to access the rows. Then I deleted all except 11 rows thinking that it
could do full table scan for a small table. But it was not. Thanks for your
time.

August 03, 2003 Khandker -- Thanks for the question regarding
      "Oracle Forms6i Error Messages", version 6.0.8.8.0

      You Asked
I want the list of "Oracle Forms6i Error Messages". Please send me the link.
Thanks
F Rabbi

      and we said...
all 6i doc is online on otn.oracle.com -> documentation

as is all documentation.  easy to navigate.  just click on documentation from
that home page.
August 03, 2003
      Syed Jaffar -- Thanks for the question
      regarding "Query as string", version 8.1.7.4

      You Asked
Hi Tom,

We are using application that generates SQL statements as string. It doesn't
make use of bind parameters. When I questioned about this, they said that they
can't predict the where statement and it always changing. They are using TIBCO
application tools.

thanks in advance
Jaffar

      and we said...
they are lazy programmers.  All languages allow you to procedurally bind at
runtime even if you do not know the number of predicates, or even their
datatypes.

All languages permit this.

It is trivial to do so.

At the VERY least tell them to issue "alter session set cursor_sharing=force" so
they do not kill your system.

Sort of funny -- people use tibco (a messaging solution) in the hopes of
building a more scalable system.  Then they go ahead and do the thing that will
most inhibit scalability -- more so then most anything else they could do.  Go
figure.
Review
      & Followup

      Rating: 3
nice comment
        August 04, 2003
Reviewer:  Anthony Reddy
       from KL, Malaysia

August 03, 2003 Cristian -- Thanks for the question regarding
      "Locally managed tablespace and datafiles parameters", version 8.1.7

      You Asked
When creating Locally managed tablespace, what size would be best to use for the
next clause of the datafile. How would it vary depending in the use of uniform
or autoallocate clause.

      and we said...
if you check out asktom.oracle.com and use the link to read the sample chapters
from my next book due out this month, you'll find in chapter 3 -- I do LMTs and
cover this topic as well...

http://asktom.oracle.com/pls/ask/z?p_url=http%3A%2F%2Ftalk.eppg.com%2Findex.php%3Fc%3D3&p_cat=NEW_BOOK_SAMPLE&p_company=10
is the link...
Review & Followup

      Rating: 5
Exactly what I
      was looking for  August 03, 2003
Reviewer:
      Cristian  from Santo Domingo, Dominican Republic
Excellent as usual!
By the way, I'm waiting the release of the book, looks pretty good.

Thanks again

August 03, 2003 Manish -- Thanks for the question regarding
      "Sql", version 8.1.6

      You Asked
Hi Tom,

There was one question asked in my interview

Q1 How to check that which values in varchar2 fields contain number as value?

For e.g

 Suppose we have  a table
 1  create table x(Name  varchar2(20));

 2  select * from x;

    Name
    -------
    Manish
    Vinay123
    Kapil Jha
    Neil
    234shalini
    #$123"Manish

Then how could you select the names that contain numbers?
i.e I want to select
    Name
    --------
    Vinay123
    234shalini
    #$123"Manish

There can be any characters in the name field.

Could you please let me know what would be the appropriate sql for selecting
such values.

regards
Manish

      and we said...
ops$tkyte@ORA920> create table t ( x varchar2(20) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( 'abc' );

1 row created.

ops$tkyte@ORA920> insert into t values ( 'a2c' );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t where translate(x,'0123456789','xxxxxxxxxx')
<> x;

X
--------------------
a2c

is but one approach.

Review &
      Followup

      Rating: 5
Pretty Neat
      Stuff! Thanks.  August 03, 2003
Reviewer:
      Reader

      Rating: 3
This is a good
      trick!!  August 10, 2003
Reviewer:  Saeed
      Ahmed  from India.
Smart Tom!!

August 03, 2003 -- Thanks for the question regarding "Privileges
      and roles", version 9.2.0

      You Asked
Hi Tom

I've seen in various postings on this site that you advocate developers having a
local instance of Oracle on their desktops for them to experiment and learn on.
Personally I think this is a good approach.

I have won the battle with the DBAs and management at my site to roll these
desktop databases out to the developers (who range in skill levels from seasoned
campaigners to relative novices when it comes to Oracle experience). However I
am now being pushed in many different directions by the various groups.

The developers what full permissions to every little thing, so they can put on
their cowboy hats, do a little hacking, and prove that everything the DBAs have
been saying since time began has been wrong.

The DBAs on other hand don't want to know anything about the desktop instances,
but when pressed would prefer that the developers have no more access than they
would have in the dev environments (to avoid the dangers of "having a little bit
of knowledge" but not fully understanding it).

I am currently advocating that the desktop instances have two separate user
accounts, one for DBA type jobs, and the other for regular use. In this way the
developers can work as they normally would for the most part logged in as the
non DBA user, but when they need more access (to grant themselves access to
something for example) they can do so without needing help from the DBAs on
site.

We operate in an environment where we encourage developers not to develop code
as themselves, but instead to place code in generic application owner schemas. I
would like to preserve this method of working in the desktop instances, again to
mirror so far as possible the set up in our other environments.

Based on the outline I've provided do you think my plan is a sensible one? Of
course some of the developers would log in only as the DBA user... possibly
vindicating the DBAs arguments... but I think most of them would see the
relative benefits of having the two accounts, and use the appropriate one.

What roles/privileges/permissions would you recommend each user be granted if
you were the one creating the image that the desktop databases be based on?

If you were tasked with this job how would you recommend the desktop instances
be set up (if at all)? (or would you throw your hands up in despair and become a
landscape gardener or a tv talk show host instead? :-) ).

Thank you for your help

      and we said...
the desktop databases should be wide open.  they are a "playground" if you will.
 A learning and testing place.

Your current development infrastructure should not change a bit -- not at all.

The databases on the desktop wouldn't be used for anything "real", just to
prototype, test, explore, learn and yes -- even prove that what the DBA's have
been saying from the beginning of time is wrong sometimes.  I've been proven
"wrong" more then once.  Everyone is "wrong" sometimes.

They shouldn't change what you do in your development/test area at all.  You can
ignore the existence of the databases on the desktops.  Some of the developers
might not even use them, some will use them constantly (and become the smarter
for it).

It just isn't a computer until there is a database running on it I always say :)
Review &
      Followup

      Rating:
      2
but...  August 03, 2003
Reviewer:
      A reader
I agree, the existing environments won't change or be impacted. However we feel
it is important for the developers to know and understand what is a "dba" job
and what is a "developer" job, hence the two users.

It would be easy to hand the database over to the owner of the desktop, and have
them log in as sys to do everything. This isn't something we would encourage (or
allow) in a "real" environment however, instead we would prefer to have the
desktop database at least partially reflect reality.

If the developer tries something, and discovers they are lacking the requisite
permissions then they can grant themselves those using the other account. What
this step has done is educated the developer on the fact that to carry out
whatever it is they are testing in the dev or live environments they would need
to have this same permission granted by the DBAs. There may be a real business
reason for this feature or permission not being made available to the developer
(eg site not being licensed for that feature). It will hopefully get them to ask
the question, and learn from the response.

If the developers can learn some of what it means to be a DBA (instead of just
saying "no" all the time and complaining about not having enough disk space,
which is a common misconception), then hopefully they would be able to work
alongside each other better.

So the basic user would have something along the lines of Connect and Resource,
while the DBA user would also have DBA.

What I was after your thoughts on was the common code schema, and the
permissions associated with it. Having a whole bunch of "create any ..."
permissions in a role granted to the basic user might be one way to do it. What
other approaches are there?

          Followup:

the software will be installed on their machines.

they will just run dbca and create a new database whenever they feel like it.

i don't see personally the point in trying to "over control" these personal
instances.

August 03, 2003 Sonu -- Thanks for the question regarding
      "Parallel Query Slaves", version 9.2

      You Asked
I would like to understand the relationship between  Parallel Query Slaves ,
sort_area_size and effect of pga_aggregate_target on them.
Like pga_aggregate_target defaults sort_area_size to 65K, and if there are 12 pq
slaves kicked up then each would be alloted 65k for sorting?

Can pga_aggregate_target automatically increase or decrease this sort_area_size
value?

Thanks

Sonu

      and we said...
sort_area_size and pga_aggregate_target are mutually exclusive.

you use one...
or the other...
but not both.....

under pga_aggregate_target -- you tell Oracle "i would like you to use a pool of
X megabytes of memory for sorting and hashing.  Please diivy it up at runtime
based on the current workload, thanks"

So, at 9am with a single user -- you might get a huge sort workspace.
At noon with 1,000 other users -- you might get lots less space to work in.

With sort_area_size -- you would get the same at 9am as you did at noon, not
very efficient -- at 9am you got short changed.  At noon, you might have sent
the system swapping like mad.

Now, as for sort_area_size and even pga_aggregate_target and memory allocations.
 They are upper bounds, not minimums.  So, if you used a sort_area_size of 5m,
you don't get 5m right away, you dynamically allocation UP TO 5m as you need it.
 The nice thing again with the pga_aggregate_target is that after using your
sort workspace -- we "give it back" -- deallocate back to the os -- whenever
possible.  With Sort_area_size -- it is given back but ONLY to the process --
the process can use it for something else, but the OS won't get that 5m back
until the process goes away.

August 03, 2003 Allwyn -- Thanks for the question regarding
      "Search Entity", version 8.1.7

      You Asked
Tom,

       We have large amount of data (few millions) and our search uses
'anywhere' which forces us to use " LIKE '%TOM%' " in the name/description
column and makes our query very slower. Whenever I need any clarification
related to Oracle coding, I come to your site and search for it. I bet your
database which hosts 'Ask Tom' is huge too. Can you please tell us what search
mechanism do you use? Your search results are always very fast. I know the
search count is faked as it always makes an assumption. Do you store the
questions/answers in CLOB columns? Does your search engine has readymade save
search and result sets? I can imagine search engines like Google caching the
searches and results and not always 100% accurate. Any special search algorithm
or mechanism you follow ? Or you dont use Oracle Select and use java for some
other search patterns?

Any points related to this will be of much use.

Thanks,
Allwyn

      and we said...
Java?  why?  to make it really slow? hmmm -- nope, no java here -- just the
database.

It is all about intermedia and indexing of text.

Read about it here:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/inter.817/index.htm
it even does the "estimate the number of returned rows" function for me.
Review &
      Followup

      Rating:
      5
Thanks  August 03, 2003
Reviewer:
      A reader
Thanks for your quick response Tom. I can't believe the turn around time for my
Question was less than an hour. After submitting the question, I searched your
forums and got more links about intermedia and related topics. Very useful
information.

August 03, 2003 Peter -- Thanks for the question regarding
      "Maximum memory usage in Oracle", version 9.2.0.3

      You Asked
Hi Tom,

I've read your links regarding SGA memory sizing and a lot of Oracle's
documentation, but I have a couple of questions I couldn't find the answer for.

I'm hoping you can set me straight.

Given:

> show parameter shared_pool;

NAME                                 TYPE        VALUE
------------------------------------ -----------
shared_pool_reserved_size            big integer 15938355
shared_pool_size                     big integer 318767104

> show parameter sga_max_size;

NAME                                 TYPE        VALUE
------------------------------------ -----------
sga_max_size                         big integer 4853267616

Will Oracle ever use more than the "sga_max_size"?  Is this an upper boundary on
how much physical RAM Oracle will use?

There is a production server exclusively dedicated to Oracle.  It has 32G of
memory, but DBA configured the server to only use about 5G of memory.  Using top
shows that the server has 23G of memory free.  I asked why the DBA didn't
allocate more memory to the database server, and he said that Oracle
automatically grows and use as much memory as it needs, so there's no reason to
allocate a large chunk of RAM to Oracle.  (I didn't agree, because I think
Oracle will only use as much memory as it is configured to use.)

My concern is if Oracle is limited by some setting to the physical memory, then
it will start using swap even when more physical memory is available.

Is it true that Oracle will use as much of the physical memory as possible
before swapping?

Thanks,
-Peter

      and we said...
the sga max size is an upper bound on the SGA only.  and it is an upper bound
the dba could resize it to -- we do not dynamically grow and shrink the sga
without being told to do so.

it does not affect process memory, such as that used in a dedicated server for
sorting, hashing, holds your plsql variables, etc....

The DBA is correct in many respects.  A 5 gig SGA is huge.  We then dynamically
allocation memory in each process (dedicated server as they are known) for your
session.

Oracle doesn't SWAP.  the operating system swaps!
August 03, 2003 Tom -- Thanks for the question regarding
      "RAISE_APPLICATION_ERROR number range caveat?", version 8.1.7

      You Asked
I read somewhere that despite numbers between -20000 and -29999 being reserved
strictly for user defined errors, that Oracle has used error numbers in the
range between -20001 and -20004.
I haven't been able to retrieve the source of that info, is there any truth to
it?
Thanks!!

      and we said...
that just means that code written that uses raise_application_error is allowed
to use codes in that range.

any code that uses raise application error is restricted to that range.

we too have code that uses raise application error -- hence, we too are
restricted to that range.

Review &
      Followup

      Rating: 3
No
      caveat?  August 03, 2003
Reviewer:  Tom
      Starr  from Seattle, Washington, USA
I understand about being restricted to that range, my question is whether any
part of the range is ever prempted or used by Oracle, and I'm just asking
because I know I read that somewhere about -20001 and -2004.
Thanks again!!

          Followup:

a grep for 20001 in $oracle_home/rdbms/admin turns up many examples where we USE
it -- not "pre-empt" it, but rather use it.

      Rating: 5
Excellent
      Review  August 03, 2003
Reviewer:  Tom Starr
       from Seattle
I am confident again about using -20000 to -29999 with RAISE_APPLICATION_ERROR.
I think I understand what informed the misunderstanding I read about.
And I see i could have answered my own question.
And the replys were almost immediate.
This answer exceeded my expectations.
Thanks again.

      Rating:
      5
  August 04, 2003
Reviewer:  Helena
      Marková  from Bratislava, Slovakia

August 04, 2003 R -- Thanks for the question regarding "concept
      guide doubts", version 9.0.1

      You Asked
Hi Tom
Thank you for your awesome service and generosity!!

Quick question from the Concept Guide section "PLSQL OverView":
“PL/SQL code can be stored centrally in a database. Network traffic between
applications and the database is reduced, so application and system performance
increases. ***Even when PL/SQL is not stored in the database***, applications
can send blocks of PL/SQL to the database rather than individual SQL statements,
thereby reducing network traffic.”

How can plsql be “not stored in the database”?

Thank you!

      and we said...
say you are sitting in sqlplus on your windows client.

you have a unix server on the other side of the country.

You are connected to it....

you type in

SQL> begin
  2>    for x in ( select * from emp for update ) loop
  3>       process( emp.empno, emp.sal );
  4>       update emp set sal = emp.sal where empno = emp.empno;
  5>    end loop;
  6>    commit;
  7> end;
  8> /

Now, supposing emp has 10,000 records in it.  Rather then bringing 10,000
records over your WAN to you, processing them and sending them back -- you sent
this anonymous block from the client (sqlplus) to the server (over the wan) and
did all of the work remotely.

You can send anonymous blocks from a client (any client) to the server and have
the code run where it should.
Review &
      Followup

      Rating: 5
thanx
      Tom!  August 01, 2003
Reviewer:
      A reader
In this case, I presume the "process" procedure is in the remote repository at
which this would get executed? If that is the case, then the only time you would
encounter plsql outside database is when you type in a plsql block
(a one time thing) The moment you make a package or a
procedure out of your functionality, it has to reside
in the database. Right?

Thanx!:)

          Followup:

well, then there is forms -- but that runs entirely on the client, not in the
database (except for the sql of course)....

but yes, in general -- a package/procedure/function/trigger live in the
database.

anonymous blocks are the exception to the rule.

      Rating: 5
thank
      you!!!  August 02, 2003
Reviewer:
      A reader

      Rating: 5
some more
      concept guide doubts  August 03, 2003
Reviewer:
      A reader
In the "Multitier Materialized Views" section - we read
"Oracle supports materialized views that are hierarchical and updatable.
Multitier replication provides increased flexibility of design for a distributed
application.
Using multitier materialized views, applications can manage multilevel data
subsets with no direct connection between levels."

I have read the materialized views chpater in your
book. But cant understand what the "multi-tier materialized
view" means. - Is this in anyway related to the "dimension"
concept wherein you specify hierarchical relationship
between entities such as year, month etc - for the
purpose of intelligent query rewrites leading to
performance enhacnement?

thanx a bunch!!

          Followup:

It is a new 9i feature so you won't read about it in "Expert one on one Oracle".

what it means in a nutshell is a mv of a mv.

say you have census data.
you create a mv that aggregates it to zip code level
you could create a mv that aggregates the zip code level data to state

in the past, those two mv's would have had to of been built against the base
census data instead.

      Rating: 5
thanx - that
      gives me the basic concept  August 04, 2003

Reviewer:  A reader
Rest I can get from the references mentioned in the concept
guide!

August 04, 2003 -- Thanks for the question regarding "SQL problem
      [ORA-01722: invalid number]", version 8.1.7

      You Asked
Hi Tom,

Could you please help me with the below SQL.

I have 2 users "dev" and "admin", both are in same database.

DEV>DESC TB_CMA086_US_CITY

   Name                                        Null?    Type
   ------------------------------------------- -------- ------------
 US_CITY_ID                                  NOT NULL NUMBER
 ADDR_COUNTRY_ID                                      NUMBER
 ADDR_STATE_ID                                        NUMBER
 COUNTY_ID                                            NUMBER
 CITY_ID                                              NUMBER
 CITY_NAM                                    NOT NULL VARCHAR2(25)
 CITY_CD                                     NOT NULL VARCHAR2(6)
 CITY_ZIP_START_CD                           NOT NULL VARCHAR2(6)
 CITY_ZIP_END_CD                             NOT NULL VARCHAR2(6)

ADMIN>DESC TB_CMA086_US_CITY

   Name                                        Null?    Type
   ------------------------------------------- -------- ------------
 US_CITY_ID                                  NOT NULL NUMBER
 ADDR_COUNTRY_ID                                      NUMBER
 ADDR_STATE_ID                                        NUMBER
 COUNTY_ID                                            NUMBER
 CITY_ID                                              NUMBER
 CITY_NAM                                    NOT NULL VARCHAR2(25)
 CITY_CD                                     NOT NULL VARCHAR2(6)
 CITY_ZIP_START_CD                           NOT NULL VARCHAR2(6)
 CITY_ZIP_END_CD                             NOT NULL VARCHAR2(6)

Structure of the table are same in both the user, even data is also same.

when i execute the below SQL query from DEV

DEV>SELECT a.* FROM (
SELECT
TO_NUMBER(TRIM(CITY_ZIP_START_CD)) SCD,TO_NUMBER(TRIM(CITY_ZIP_END_CD)) ECD
FROM TB_CMA086_US_CITY
WHERE
DECODE((REPLACE(TRANSLATE(TRIM(CITY_ZIP_START_CD),'0123456789','00000000000'),'0'
,NULL)), NULL,-9876121254,-12345) = -9876121254) a
WHERE 681 >= SCD AND 681 <= ECD;

       SCD        ECD
---------- ----------
       680        682
it shows me 1 record.

when i run the same SQL from ADMIN

ADMIN>SELECT a.* FROM (
SELECT
TO_NUMBER(TRIM(CITY_ZIP_START_CD)) SCD,TO_NUMBER(TRIM(CITY_ZIP_END_CD)) ECD
FROM TB_CMA086_US_CITY
WHERE
DECODE((REPLACE(TRANSLATE(TRIM(CITY_ZIP_START_CD),'0123456789','00000000000'),'0'
,NULL)), NULL,-9876121254,-12345) = -9876121254) a
WHERE 681 >= SCD AND 681 <= ECD;

ERROR:
ORA-01722: invalid number

no rows selected

I am not able guess what may be the problem. Data in both the users are same.

Thanks for any help you provide me,
Venkat

      and we said...
You seem to think that SQL is processed in "some order".  SQL is non procedural
-- the query can and is rewritten for optimal performance.

You have made the classic mistake here of using a character string field to hold
a number. (ugh, i hate that)...

You have data in that field that is not a number.

The query is being optimized in such a fashion so that it is really being
processed as:

ops$tkyte@ORA920> SELECT TO_NUMBER(TRIM(CITY_ZIP_START_CD)) SCD,
  2         TO_NUMBER(TRIM(CITY_ZIP_END_CD)) ECD
  3    FROM TB_CMA086_US_CITY
  4   WHERE DECODE
  5         (
(REPLACE(TRANSLATE(TRIM(CITY_ZIP_START_CD),'0123456789','00000000000'),'0'
,NULL)),
  6                    NULL, -9876121254,
  7                    -12345 ) = -9876121254
  8     AND 681 >= TO_NUMBER(TRIM(CITY_ZIP_START_CD))
  9     AND 681 <= TO_NUMBER(TRIM(CITY_ZIP_END_CD))
 10  /
   AND 681 <= TO_NUMBER(TRIM(CITY_ZIP_END_CD))
                        *
ERROR at line 9:
ORA-01722: invalid number

which is totally -- 100%, unequivocally the same query as you typed in (so, we
are 100% free to execute it that way).

It should be obvious why that fails.  Now, why does it work for one and not the
other.  Well, other then "by accident", the data is different, I'll guess one
uses the CBO and one does not.  Consider:

ops$tkyte@ORA920> create table tb_cma086_us_city
  2  (
  3   CITY_ZIP_START_CD                            VARCHAR2(6) NOT NULL,
  4   CITY_ZIP_END_CD                              VARCHAR2(6) NOT NULL
  5  )
  6  /

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into tb_cma086_us_city values ( 'abc', 'def' );

1 row created.

ops$tkyte@ORA920> insert into tb_cma086_us_city values ( '680', '682' );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> SELECT a.*
  2    FROM ( SELECT TO_NUMBER(TRIM(CITY_ZIP_START_CD)) SCD,
  3                  TO_NUMBER(TRIM(CITY_ZIP_END_CD)) ECD
  4             FROM TB_CMA086_US_CITY
  5            WHERE DECODE
  6                  (
(REPLACE(TRANSLATE(TRIM(CITY_ZIP_START_CD),'0123456789','00000000000'),'0'
,NULL)),
  7                    NULL, -9876121254,
  8                    -12345 ) = -9876121254
  9         ) a
 10  WHERE 681 >= SCD AND 681 <= ECD;

       SCD        ECD
---------- ----------
       680        682

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table tb_cma086_us_city compute statistics;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> SELECT a.*
  2    FROM ( SELECT TO_NUMBER(TRIM(CITY_ZIP_START_CD)) SCD,
  3                  TO_NUMBER(TRIM(CITY_ZIP_END_CD)) ECD
  4             FROM TB_CMA086_US_CITY
  5            WHERE DECODE
  6                  (
(REPLACE(TRANSLATE(TRIM(CITY_ZIP_START_CD),'0123456789','00000000000'),'0'
,NULL)),
  7                    NULL, -9876121254,
  8                    -12345 ) = -9876121254
  9         ) a
 10  WHERE 681 >= SCD AND 681 <= ECD;
  FROM ( SELECT TO_NUMBER(TRIM(CITY_ZIP_START_CD)) SCD,
                          *
ERROR at line 2:
ORA-01722: invalid number

so, that is your problem here.  you used a string to store a number instead of
a number to store a number.

This is just a bug waiting to happen in your code -- I'll bet you have dozens of
problems like this lurking about.....  What you'll want to do is convert the
strings to numbers in the DECODE and then use THAT result in the predicate.  The
query should be:

ops$tkyte@ORA920> SELECT a.*
  2    FROM ( SELECT DECODE
  3                  (
(REPLACE(TRANSLATE(TRIM(CITY_ZIP_START_CD),'0123456789','00000000000'),'0'
,NULL)),
  4                    NULL, to_number(trim(city_zip_start_cd)) ) scd,
  5                  DECODE
  6                  (
(REPLACE(TRANSLATE(TRIM(CITY_ZIP_END_CD),'0123456789','00000000000'),'0'
,NULL)),
  7                    NULL, to_number(trim(city_zip_end_cd)) ) ecd
  8             FROM TB_CMA086_US_CITY
  9          ) a
 10   where scd <= 681
 11     and ecd >= 681
 12  /

       SCD        ECD
---------- ----------
       680        682

that'll work in all cases since the DECODE must happen in the predicate and the
to_number will happen if and only if the string consists purely of digits.
Review &
      Followup

      Rating: 5
please correct
      me ..  August 03, 2003
Reviewer:
      A reader
Tom,

Excellent demostration ... thanks for making me understand this ..but again
(sorry for my ignorance) i have a question. i do understand that the query is
executed (before your correction) as

SELECT TO_NUMBER(TRIM(CITY_ZIP_START_CD)) SCD,TO_NUMBER(TRIM(CITY_ZIP_END_CD))
ECD FROM TB_CMA086_US_CITY
WHERE
DECODE((REPLACE(TRANSLATE(TRIM(CITY_ZIP_START_CD),'0123456789','00000000000'),'0'
,NULL)),NULL, -9876121254,-12345 ) = -9876121254
AND 681 >= TO_NUMBER(TRIM(CITY_ZIP_START_CD))
AND 681 <= TO_NUMBER(TRIM(CITY_ZIP_END_CD))

but the person who posted this looks like he had intended to get the result out
of inline view and use that scd,ecd in the predicate.

to my little knowledge on sql, i understand that inline query could execute
first and then the other..is that not so ? ( hope u hate this to hear !! )
..please correct me.

Thanks for your willingness to share.

          Followup:

...
to my little knowledge on sql, i understand that inline query could execute
first and then the other..is that not so ? ( hope u hate this to hear !! )
..please correct me.
......

the operative word there is COULD.

Not "must"
Not "will"

"could"

it could -- but it wasn't -- so it failed.

SQL is by its very definition ambigous as to the order of operation.  You cannot
count on a short circut order of evaluation, you cannot count on "step a" being
done before "step b" and so on.

The optimizer is free to rewrite the query as it sees fit -- merging various
bits and pieces together.  In this case, the inline view wasn't material -- the
difference between the original query with the inline view and

SELECT TO_NUMBER(TRIM(CITY_ZIP_START_CD)) SCD,TO_NUMBER(TRIM(CITY_ZIP_END_CD))
ECD FROM TB_CMA086_US_CITY
WHERE
DECODE((REPLACE(TRANSLATE(TRIM(CITY_ZIP_START_CD),'0123456789','00000000000'),'0'

,NULL)),NULL, -9876121254,-12345 ) = -9876121254
AND 681 >= TO_NUMBER(TRIM(CITY_ZIP_START_CD))
AND 681 <= TO_NUMBER(TRIM(CITY_ZIP_END_CD))

is "zero", "nothing".  So, in effect, the optimizer executed the "non inline
version", it is the same query.

      Rating: 5
SQL problem
      [ORA-01722: invalid number  August 03, 2003

Reviewer:  Venkat  from Hyderabad
Hi Tom,

Thank you so much for your solution. My problem got resolved using your query.

In ZiP_code field we have data of both number and characters as well, like P01,
PA12.

Thanks,
Venkat

          Followup:

then you should not be using number semantics on it!  they are not numbers!

August 04, 2003 Derk Jan -- Thanks for the question regarding "Is
      connection manager supported in Standard Edition??", version 8.1.6

      You Asked
Is connection manager supported in Standard Edition??

      and we said...
see

http://technet.oracle.com/doc/oracle8i_816/server.816/a76962/ch4.htm#73594
for the entire list.

connection manager is part of Enterprise and Personal Editions only.  Not
standard.
Review
      & Followup

      Rating: 2
But Standard
      Ed. creates services ...CMan and ...CMAdmin  January 21,
      2003
Reviewer:  Oleg Oleander  from Hungary

But Standard Ed. creates services ...CMan and ...CMAdmin!?

What are these two then?

We use 8.1.7. Would like to access a db behind a firewall on W2000 network. We
start the service named "OracleOraHome81CMan" and on clients we use the
following tnsnames.ora entry:

CM_INSIGHT_FV =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = 212.40.127.17)(PORT = 1630))
      (ADDRESS = (PROTOCOL = TCP)(HOST = 212.40.127.17)(PORT = 1521))
    )
    (SOURCE_ROUTE = yes)
    (CONNECT_DATA =
      (SERVICE_NAME = INSIGHT)(SERVER=DEDICATED)
    )
  )

We opened posts 1521 and 1630 in both direction on the firewall. locally we can
"connect scott/tiger@CM_INSIGHT_FV", but it doesnt work from outside.

could you please help us, please? (please)

          Followup:

they are just services, with SE you are not licensed to use that particular
feature.  It is part of EE

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76962/ch4.htm#74521
see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:264415410962

      Rating: 5
connection
      manager and sessions  August 03, 2003
Reviewer:
      A Reader
Please clarify my understanding. Thanks.

Assuming that without a connection manager, if three users say, user A, user B,
and user C are connecting to a database that accepts dedicated server
connections only and these three users/three connections/three independent
sessions will have a dedicated server process for each user. Now with connection
manager, is it like the three users are funneled via the connection manager as
one connection (TCP/IP) to the database server. Is it correct? If so, how many
dedicated server processes to server these three users who are connected via
connection manager? Thanks.

          Followup:

cman just connects on your behalf -- normally it would be "as if" you have
connected directly so there would be three.  but cman can employ connection
concentration as well -- so there could be one (or two).

so, the answer is -- somewhere between 1 and 3 -- and if the connections have
been idle and that connection swapping out was enabled -- perhaps zero ;)

just to make it more confusing.

      Rating: 5
What is
      connection concentration and connection swapping?
      Thanks.  August 03, 2003
Reviewer:  A reader

          Followup:

see multiplexing

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/network.920/a96580/cman.htm#489424
concentrates or "muxes" many sessions on one connection.

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/network.920/a96580/intro.htm#454808
is about pooling (what I referred to as swapping)

August 04, 2003 Shivaswamy -- Thanks for the question regarding
      "MAXDATAFILES", version 817

      You Asked
Hi TOM,
Where is the maxdatafiles value stored, apart from control file?Is there a way
to access it?
Shivaswamy

      and we said...
  1* select * from v$controlfile_record_section  where type = 'DATAFILE'
sys@ORA817DEV.US.ORACLE.COM> /

TYPE      RECORD_SIZE RECORDS_TOTAL RECORDS_USED FIRST_INDEX LAST_INDEX
LAST_RECID
--------- ----------- ------------- ------------ ----------- ----------
----------
DATAFILE          180           100           19           0          0
7901

record-size has that info.
Review & Followup

      Rating:
      5
  March 18, 2002
Reviewer:
      Shivaswamy
Thanks, TOM

      Rating:
      3
Sagi  March 19, 2002
Reviewer:  sagi
       from India
Hi Tom.

I am not not up to the standard to comment on your answer. But I was wanting to
know whether its RECORDS_TOTAL or RECORD_SIZE which tells you the Max No. of
DataFiles.

I issued:

ALTER DATABASE BACKUP CONTROLFILE TO TRACE

and then when I cross verified the value it matches to RECORDS_TOTAL and not
RECORD_SIZE

Please correct me if I am wrong.

Regards,

          Followup:

sorry about that, missed by a column

if you read:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76961/ch331.htm#7439
it describes the contents of this table.  record_size is the size in bytes of
the record.  records_total is the number of entries.

      Rating:
      5
V$CONTROLFILE_RECORD_SECTION   August 03, 2003

Reviewer:  A reader
tom,

just want to make sure,

"RECORD_SIZE  NUMBER  Record size in bytes"

means the size in bytes for the control file record section of the control file
for the particular type (DATABASE, CKPT PROGRESS ...)

..hope yes.

thanks

          Followup:

yes i think?  i'm not really sure if I understand what you are asking exactly as
it seems somewhat obvious that the record_size is associated with the type?

August 04, 2003 Sadamahan -- Thanks for the question regarding
      "Accessing Oracle Advanced queuing system in java.", version 8.1.5

      You Asked
Hi,

I would like to know how to perform operations on Oracle's
Advanced queuing system (AQS) through Java servlets to read from
the queue, write etc.

Thanks you very much for the help.

Regards,

Sada

      and we said...
In 8.1.5, you would use the DBMS_AQ packages with JDBC.  There
are entry points to enqueue and dequeue messages.  See

http://technet.oracle.com/doc/server.815/a68005/toc.htm
and

http://technet.oracle.com/doc/server.815/a68001/dbms_aq.htm#998101
In 8.1.6, you might opt to use JMS instead which is layered on
top of AQ in that release.

Review &
      Followup

      Rating: 3
AQ, MSMQ
      interface/gateway ?  November 09, 2001
Reviewer:
      Sameer  from NJ
Is there any Oracle product that would let me access MSMQ via Oracle
database/AQ/PL*Sql ?

The reasoning being :
It has been decided to use MSMQ for all the systems in the company (decision not
in our control). Our database group needs to be able to access messages on the
MSMQ queue and update Oracle data in our system. Expertise of our group is
Oracle DB/PL*sql and not .net. If we could somehow access MSMQ via Oracle
database (even if it's a separate Oracle product that we have to buy it
separately), we could keep access to MSMQ transparent (to some extent) for our
group. Does such a thing exist ?

          Followup:

Not yet, not for MSMQ -- for IMB MQ, yes.

Support for MSMQ is coming soon -- the Oracle9i message gateway next release is
to support it.

      Rating: 5
Picking up from
      MQ  July 31, 2003
Reviewer:  phil  from
      UK
How do you pick up from IBM MQ from PLSQL ?

          Followup:

you'll need a gateway -- we have them.

http://tahiti.oracle.com/pls/db92/db92.drilldown?word=mqseries&book=&preference=

      Rating: 5
found the
      answer on your site ... thanks !  August 01, 2003

Reviewer:  phil

      Rating: 4
Communication
      error when installing  August 04, 2003
Reviewer:
      phil
hi tom,

I am trying to setup messaging gateway, and I cannot get past (in the AQ
developers guide) the creating of the objects , all works fine until the end of
the script catmgw.sql

Oracle 9i
Solaris 8

the error i get is as follows

Synonym created.

Synonym created.

begin
*
ERROR at line 1:
ORA-03113: end-of-file on communication channel

CREATE OR REPLACE PACKAGE mgwi_reg wrapped
*
ERROR at line 1:
ORA-03114: not connected to ORACLE

CREATE OR REPLACE PACKAGE BODY mgwi_reg wrapped
*
ERROR at line 1:
ORA-03114: not connected to ORACLE

begin
*
ERROR at line 1:
ORA-03114: not connected to ORACLE

SQL>

any ideas why I might be getting disconnected at this point ?

a little lost!

thanks
phil

          Followup:

please contact support when you get 3113.  it means "something went really
wrong"

August 04, 2003 Dan -- Thanks for the question regarding "UNION
      ALL materialized views across db links", version 9.2.0

      You Asked
Tom

We're trying to set up an Operational Data Store [ODS] to collate data from 5
other back-end databases to give a single view of the enterprise (customers,
accounts, transactions, and so on) - mostly for the web

At the moment, we've got 5 materialized views for each table, one per source DB
table, and a view over the top doing a UNION ALL to collate the information
together.

Problem is that some of the explain plans are terrifyingly inefficient when
doing complex queries.  So we're trying to find a way to get all 5 sets of data
into one table in the target DB.  This should cut down data access time (don't
need to read 5 views when all the data's in one only).  The key requirement is
the ability to do incremental refreshing of the data in the target DB.

It seems that it can't be done using a materialized view, because of the fact
that there's a DB link in there, and that means that we break the rules for
complex mviews, and therefore cant refresh FAST.

I can get it to work perfectly when the source tables and the target
materialized view are in the same database:
CREATE MATERIALIZED VIEW MV1
REFRESH FAST
AS
SELECT X, Y, 'A' MARKER FROM T1
UNION ALL
SELECT X, Y, 'B' MARKER FROM T2
;
However trying to do it across a database link fails with the ORA-12015 'cannot
fast refresh ... complex query' error:
CREATE MATERIALIZED VIEW MV1
REFRESH FAST
AS
SELECT X, Y, 'A' MARKER FROM T1@SOME_LINK
UNION ALL
SELECT X, Y, 'B' MARKER FROM T2@SOME_LINK
;
This happens even when T1@SOME_LINK and T2@SOME_LINK actually point back to the
local database!
I've also tried to do it using 2-step replication - replicating T1 and T2 into
the local DB, then doing the UNION ALL on the materialized views
CREATE MATERIALIZED VIEW T1_MV
REFRESH FAST
AS
SELECT X, Y FROM T1@SOME_LINK
;
CREATE MATERIALIZED VIEW T2_MV
REFRESH FAST
AS
SELECT X, Y FROM T2@SOME_LINK
;
(Create appropriate mv logs on T1_MV, T2_MV)
CREATE MATERIALIZED VIEW MV1
REFRESH FAST
AS
SELECT X, Y, 'A' MARKER FROM T1_MV
UNION ALL
SELECT X, Y, 'B' MARKER FROM T2_MV
;
This *always* fails with ORA-12053 - not a valid nested materialized view.

So - my question is:

Can we do what we want to do 'out of the box' with replication, or with
something else?  I can't believe Oracle can't do this sort of thing, it seems
too obvious a requirement...

Cheers

Dan Shannon

      and we said...
you would have to do it this way:

create table table at the master (central site) with all of the data.

use UPDATABLE snapshots at the 5 sites, have them select out THEIR data.

Now, when they modify their tables, they will refresh the single big table.

The other option would be to use streams if possible to capture the changes from
their logs and using an apply routine, apply the logical change records to a
single table in the centry site.

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-STR

August 04, 2003 Tony -- Thanks for the question regarding
      "In-consistencies in oracle dictionary table", version 8.1.6

      You Asked
Hi Tom,
Sometimes I wonder, why oracle tables have different size in data dictionary
tables.
Like:- column_name in user_cons_columns is varchar2(4000), and in
user_tab_columns is varchar2(30). Similarly, what is the purpose of owner in
user_cons_columns?
Thanks

      and we said...
that is not an inconsistency.

it is a fact of life.

a column name may be only 30 characters long.
but in user_cons_columns -- with object types -- it is the QUALIFIED column
name.

ops$tkyte@ORA920> create type a12345678901234567890123456789
  2  as object
  3  ( b12345678901234567890123456789 int )
  4  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t
  2  ( c12345678901234567890123456789 a12345678901234567890123456789,
  3    constraint t_unq_abc
  4    unique (c12345678901234567890123456789.b12345678901234567890123456789 )
  5  )
  6  /

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select column_name from user_tab_columns where table_name =
'T';
COLUMN_NAME
---------------------------------------------------------------------------------
--------------------------------------------------
C12345678901234567890123456789

1 row selected.

ops$tkyte@ORA920> select column_name from user_cons_columns where table_name =
'T';
COLUMN_NAME
---------------------------------------------------------------------------------
--------------------------------------------------
"C12345678901234567890123456789"."B12345678901234567890123456789"

1 row selected.

so, in user_cons_columns -- it could be very very long indeed.

agreed on owner in user_cons_columns, it is not relevant.  if you look at the
view text:

create or replace view user_cons_columns (
OWNER
,CONSTRAINT_NAME
,TABLE_NAME
,COLUMN_NAME
,POSITION
) as
select u.name, c.name, o.name,
       decode(ac.name, null, col.name, ac.name), cc.pos#
from sys.user$ u, sys.con$ c, sys.col$ col, sys.ccol$ cc, sys.cdef$ cd,
     sys.obj$ o, sys.attrcol$ ac
where c.owner# = u.user#
  and c.con# = cd.con#
  and cd.type# != 12       /* don't include log groups */
  and cd.con# = cc.con#
  and cc.obj# = col.obj#
  and cc.intcol# = col.intcol#
  and cc.obj# = o.obj#
  and c.owner# = userenv('SCHEMAID')
  and col.obj# = ac.obj#(+)
  and col.intcol# = ac.intcol#(+)

/

you can see that there is a predicate on c.owner# to a constant value -- the
current schemaid.  so owner is implied.

Review &
      Followup

      Rating: 4
Thanks a
      lot!  August 04, 2003
Reviewer:
      A reader

August 04, 2003 chirayu -- Thanks for the question regarding
      "view or table", version 8.1.6

      You Asked
  I have one very heavily used table online say Z.
  This Z table is used for insert, updates , deletes, querying by various
  users.  Inserts and updates occur almost continuously throughout the
  working hours.  At the same time various users generate reports based
  on this table.

  Now my query is, for developing reports, Shall I use a view on this table
  or use this table directly.  What a senior developer in my company says
  is that everytime a report/query is fired on this table, temporary locks
  are created on this table to maintain the integrity of the data. This will
  effect the performance of the inserts and updates.  He says that if the
  report references the view then the view will be locked and not the
  table, so the impact on inserts and deletes will be less.

  However what I argue is that since a view is just a stored query,
  it will ultimately refer the underlying base table only. Hence creating
  a view would not be an appropriate solution for that.

  What I want to know is 'Whether using a view(thus, indirectly referencing
  the table ) or directly using a table will be better'.
  Again when I fire a select statement does it create temporary locks on
  the queried table.

  (2) select max(lebsz) from sys.x$kccle
      what are these X$tables and from where can I get the details
      about it. Do I need to login with sys account to access these
      tables.

  Thanks in advance for clearing the doubts.

  Chirayu sutaria

      and we said...
We need to run:

:c/senior/greenhorn/g

If you understand the text editor vi, you'll know what that command would do and
then the question would be more appropriate.

The misunderstanding of this "sr" developer is total and complete.

firstly, in Oracle -- readers do not block writers.  Writers do not block
readers.  It is one of our fundemental features, the most fundemental of them
all.

secondly, understanding concurrency control is your primary job as a database
developer.  Not understanding this prevents you from doing your job right.

I suggest your team takes a day or two off from coding and reads:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/toc.htm
from cover to cover.  If you retain just 10% of what you read in there, you'll
already know 90% more then most.  Pay particular attention to

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/c23cnsis.htm#2414
and most importantly for this question :

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/c23cnsis.htm#2570
the answer to your question is:  You can query the table, you can query the
view, a reader of data will NOT block a writer of data.  The writer will NOT
block the reader.  A view would have no impact on these statements and it is not
even imaginable to me how it could in a database where reads do block writes and
writes block reads (such as MS-SQLServer, DB2, Informix, Sybase and so on)

X$ tables -- well, they are not documented, you use the V$ tables but there are
some basics you need to get down before you go there.  The V$ tables are
documented here:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76961/toc.htm

Review &
      Followup

      Rating: 3
Question
      related...  July 17, 2003
Reviewer:  sgs_ocp
       from UK
Tom,
I have one doubt regarding the report writer.
we run the report from a calling form.

user enters criteria for the report.
suppossing he is entering a partner code, we need to give him opion to enter a
partner code or keep it blank
if he keeps it blank that means all partners are to be considered. we pass
parameter 'ALL' in this case and in query we do it like this.

where ( partner = :partner OR :partner = 'ALL' )
and <remaining_where_clause>

there can be multiple such parameters.

i have doubt whether using 'OR' will hinder performance as it will not use
index. is it that?

will you suggest some alternative to this approach ?

recently we have tried to do it another way.
whole where clause will be constructed in a varchar2 and then it will be passed
as a lexical parameter.
so that we will avoid sending 'OR'
if :partner is 'ALL' we will omit it altogether from query.

so my second question is that what is the difference if i use lexical parameter
this way ? is this better than first ?

your suggestion are welcome.

thanks.

          Followup:

as long as the lexical parameter uses bind variables -- it is OK. and most
likely better as the optimizer would use an index when appropriate and not when
not

      Rating: 4
bind
      variables  July 31, 2003
Reviewer:  sgs_ocp
       from UK
Yes Tom, as you said ,
as long as you use bind variables it is ok.

i need some more explaination.

if i use column of query ( :column ) and other parameters like ( :parameter )
are they bind variables ?
please clarify.

          Followup:

i don't know, need a full example and a hint as to the programming environment.

      Rating: 2
report
      query  August 04, 2003
Reviewer:  sgs_ocp

hello tom,

This is the example.

i am creating lexical parameter like this

:clause_hub := 'AND pl_arr.hub IN(:p_hub_arr) ' ;

then i append to query like &clause_hub.

when i make a trace file of this report the query looks like this.

 WHERE re.company_code = : p_company_code AND re.rm_resource_code =
  cct.container_number AND pl_arr.pool_location = cct.destination AND
  rs.con_status = cct.con_status AND vo.voyage_reference (+) =
  cct.voyage_reference AND cl.container_number = cct.container_number AND
  la.lease_agree_num = cl.lease_agree_num and cl.container_number =
  cct.container_number AND re.resource_class = 'E' AND re.resource_group =
  'CT' AND ( re.resource_size = : p_size OR : p_size IS NULL ) AND (
  re.resource_type = : p_type OR : p_type IS NULL ) AND ( re.leased_flag = :
  p_lease OR : p_lease IS NULL ) AND ( INSTR ( : p_ixe , substr (
  rs.movement_type , 1 , 1 ) ) <> 0 OR : p_ixe = 'NNNNN' ) AND (
  cct.voyage_reference = : p_voy_ref OR : p_voy_ref IS NULL ) AND (
  cct.con_status = : p_status OR : p_status IS NULL ) AND ( ( cct.status_date
  < ( SYSDATE - TO_NUMBER ( : p_overdue ) ) ) OR : p_overdue = '0' ) AND (
  la.partner_code = : p_lessee_owner OR : p_lessee_owner IS NULL ) AND (
  cl.lease_agree_num = : p_lease_num OR : p_lease_num IS NULL ) AND
  cl.off_hire IS NULL AND ( pl_arr.point_code = : p_point_code_arr OR :
  p_point_code_arr IS NULL ) AND ( pl_arr.pool_location = : p_pool_arr OR :
  p_pool_arr IS NULL ) AND pl_arr.hub IN ( : p_hub_arr ) ORDER BY 13 ASC,14
  ASC,15 ASC , re.resource_size , re.resource_type , cct.container_number

shall i say that p_hub_arr is passed as bind variable.

this i want to confirm as one of my coworker is saying that database can not
identify it as a  bind variable.
also according to him bind variables are seen in trace files in the format
B<some number>. is this correct ?

also other parameters like p_company_code are bind ?

thanks in advance.

          Followup:

read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:110612348061

August 04, 2003 sandeep -- Thanks for the question regarding "Row
      Id", version 8.1.7

      You Asked
hi tom
i m having two questions
1)we create one view on a table emp
and try and fire one query
select rowid from view1
it will return rowids of emp table

then we create view2 on emp and dept tables
and we fire same query on view2
select rowid from view2
it returns the rowid of emp table not dept table
will you please tell me how it happnes

2)please tell me something about bitand function

      and we said...
1) the database looked at the primary keys and foreign keys in place.  it sees
that EMP is key preserved in that table -- meaning each row in emp will join to
AT MOST one row in DEPT.  It sees that the DEPT table is not key preserved.  It
sees that the DEPT table could join to zero one or MORE (and it is the or MORE
that prevents it from being key preserved) rows in EMP

Hence, it is allowing you to update the EMP table in this view (if you look at
the updatable columns view you'll see that).  You can insert into emp via this
view.  You can even delete from emp.  But you cannot do anything to DEPT except
read it.

So, the view produces the rowid for the updatable table in the view.

2) it does a logical bitand using binary integers....  You can see what it does
easily if you use my base conversion routines

http://asktom.oracle.com/~tkyte/hexdec/index.html
consider:

ops$tkyte@ORA920> select lpad( to_bin(55), 8, '0' ) c1,
  2         lpad( to_bin(11),  8, '0' ) c2,
  3         lpad( to_bin( bitand(55,11) ),  8, '0' ) c3
  4    from dual
  5  /

C1       C2       C3
-------- -------- --------
00110111 00001011 00000011

to_bin just shows the numbers in binary -- notice how the bitand has only the
bits turned on when they appeared in both numbers.  It just anded them together.

Review &
      Followup

      Rating:
      5
  August 04, 2003
Reviewer:  Helena
      Marková  from Bratislava, Slovakia

      Rating: 5
what is the
      drawback i am likely to face, if i dont have a key preserved column in the
      view   August 04, 2003
Reviewer:
      A reader
tom,
if suppose i dont have primary key/foreign key in my tables, how do i go about
in having a key preserved table.

it so happened in one job, its on siebel, where they dont have this kind of
relationship (atleast in the application which i am dealing with..not sure if
its a general practice in siebel.). They have only check constraints. they use
rowid as primary key. rowid are the siebel generated rowid, they are unique. we
can rely on it.i need to create a view.

one of my simplest view go like this ...

create or replace view V_Prod_Code
as
select rownum r1,
a.x_om_office_cd||'-'||a.x_base_req_dsc||'-'||a.x_alt_req_dsc
sub_requisition_num,
a.row_id row_idpc,b.prod_id prod_id,c.prod_cd prod_cd,
b.x_itm_num item_number,b.qty_req quantity
  from s_order a,s_order_item b,s_prod_int c
  where b.prod_id = c.row_id and a.row_id = b.order_id;

in this s_order.rowid is primary for s_order_item
and s_order_item.rowid is the primary key for s_order_item

from the above situation what i wanted to know if, what is the drawback i am
likely to face, if i dont have a key preserved column in the view i have
created. Do you see any chance of making my view with key preserved column.

Thank you so much for your help on this..

          Followup:

just means you cannot modify the data via that view unless you coded an 'instead
of' trigger.  no other side effects really.

      Rating: 5
Thanks
      Tom  August 04, 2003
Reviewer:
      A reader

August 04, 2003 Kim Berg -- Thanks for the question regarding
      "Hierarchial query with cumulative factor", version 8.1.7

      You Asked
Hi, Tom

I have a slight problem with a hierarchial query that I hope you can help me
with.

We have Oracle 8.1.7.4 and I've setup a testcase consisting of the following :

A table of items with an id, a name, and a type that can be either 'I'tem og
'S'et :

create table items (
  itemid    varchar2 (20),
  itemtype  char (1),
  itemname  varchar2 (40)
);

A table defining what items a Set consists of :

create table itemsets (
  parentid  varchar2 (20),
  childid   varchar2 (20),
  factor    number
);

A simple example might have the following data :

SQL> select * from items;

ITEMID               I ITEMNAME
-------------------- - ----------------------
BIKE                 S Complete bicycle
FRAME                I Bicycle frame
COMP_WHEEL           S Complete wheel
WHEEL                S Wheel w/out tyre
TYRE                 I Tyre for bicycle wheel
RING                 I Ring for wheel
SPOKE                I Spoke for wheel

SQL> select * from itemsets;

PARENTID             CHILDID                  FACTOR
-------------------- -------------------- ----------
BIKE                 FRAME                         1
BIKE                 COMP_WHEEL                    2
COMP_WHEEL           WHEEL                         1
COMP_WHEEL           TYRE                          1
WHEEL                RING                          1
WHEEL                SPOKE                        28

Meaning that a BIKE consists of one FRAME and two COMP_WHEEL's.
A COMP_WHEEL consists of one WHEEL and one TYRE.
A WHEEL consists of one RING and 28 SPOKE's.

So a hierarchial query on a BIKE would give the following result :

SQL> select lpad(' ',level*2)||parentid parentid, childid, factor
  2  from itemsets
  3  connect by prior childid = parentid
  4  start with parentid = 'BIKE';

PARENTID             CHILDID                  FACTOR
-------------------- -------------------- ----------
  BIKE               FRAME                         1
  BIKE               COMP_WHEEL                    2
    COMP_WHEEL       WHEEL                         1
      WHEEL          RING                          1
      WHEEL          SPOKE                        28
    COMP_WHEEL       TYRE                          1

Now I want to make a list that tells me what items and how many of each I need
to put together a BIKE. I want this result :

FRAME    1
RING    2
SPOKE    56
TYRE    2

If I pick these items, I can assemble a BIKE.

Given my hierarchial query from above I need to do two things :
1) Select nodes in the tree that does not have children.
2) Multiply the factor of each child with the factor of the parent.

The first part I can do using that I have the ITEMTYPE column in the ITEMS table
- no problem.

The second part is more tricky. I'd like to do this :

SQL> select items.itemid, items.itemname, tree.factor
  2  from (
  3  select childid, factor * nvl(prior factor, 1) factor
  4  from itemsets
  5  connect by prior childid = parentid
  6  start with parentid = 'BIKE'
  7  ) tree, items
  8  where items.itemid = tree.childid
  9  and items.itemtype = 'I';

ITEMID               ITEMNAME                                     FACTOR
-------------------- ---------------------------------------- ----------
FRAME                Bicycle frame                                     1
RING                 Ring for wheel                                    1     --
WRONG
SPOKE                Spoke for wheel                                  28     --
WRONG
TYRE                 Tyre for bicycle wheel                            2

That works up to level two (the factor for TYRE is correctly 2), but not for
lower levels in the tree (RING and SPOKE is wrong because the factor 2 for
COMP_WHEEL is not propagated down to the lower levels.

I've thought of a solution where I make a recursive function that returns a
TABLE that I can select from, but wouldn't it add quite an overhead ?
That overhead is not a big deal in a simple example like this, but I also have
to "expand sets" like that in queries that should answer questions such as :
"For all orders that should be shipped this week, check if we have enough items
in stock". It would be perfect to be able to "expand" the items in the orders
like the above example and then select sum(ordered_number * factor) group by
childid (or something similar).

If this CAN'T be solved easily in 8i but CAN in 9i, please specify - we plan on
upgrading some time fairly soon.

Thanks.

Kim Berg Hansen
Senior Systems Developer
kbh@thansen.dk
T.Hansen Gruppen A/S
Denmark

      and we said...
This works in 8i:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select parentid, childid,
  2         decode( (select itemType from items where itemid =
itemsets.childid),
  3                 'S', to_number(NULL),
  4                 (select exp(sum(ln(is2.factor))) from itemsets is2
  5                    start with is2.childid = itemsets.childid
  6                  connect by prior is2.parentid = is2.childid ) ) factor2
  7    from itemsets
  8   start with parentid = 'BIKE'
  9  connect by prior childid = parentid
 10  /

PARENTID        CHILDID            FACTOR2
--------------- --------------- ----------
BIKE            FRAME                    1
BIKE            COMP_WHEEL
COMP_WHEEL      WHEEL
WHEEL           RING                     2
WHEEL           SPOKE                   56
COMP_WHEEL      TYRE                     2

6 rows selected.

It is more difficult then you make out  -- as you need the entire hierarchy of
factors.  What if a bike has two wheels that have 28 spokes that each have 4
gizmos -- you need to multiply by the entire lineage there.

That is what exp(sum(ln(is2.factor))) does for us -- a product.  See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2200571416651
for background
Review & Followup

      Rating: 5
Perfect - just
      what I need  June 17, 2002
Reviewer:  Kim Berg
      Hansen  from Denmark
Thanks, Tom

That was a very nice query, and I can definitely use it - but I can also see in
the answer you linked to, that I think I will press on for an upgrade to 9i, as
I like the idea of creating user-defined aggregates.

      Rating: 4
Slight
      complication ...  June 19, 2002
Reviewer:  Kim
      Berg Hansen  from Denmark
Hi, Tom

I still like your query, but I've found a slight complication when trying it
out...

If I add another itemset - a ladies bike, which is identical to the BIKE except
for the frame. So my data now looks like this :

SQL> select * from items;

ITEMID               I ITEMNAME
-------------------- - ---------------------------
BIKE                 S Complete bicycle
FRAME                I Bicycle frame
COMP_WHEEL           S Complete wheel
WHEEL                S Wheel w/out tyre
TYRE                 I Tyre for bicycle wheel
RING                 I Ring for wheel
SPOKE                I Spoke for wheel
LADYBIKE             S Complete bicycle for ladies
LADYFRAME            I Bicycle frame lady-design

SQL> select * from itemsets;

PARENTID             CHILDID                  FACTOR
-------------------- -------------------- ----------
BIKE                 FRAME                         1
BIKE                 COMP_WHEEL                    2
COMP_WHEEL           WHEEL                         1
COMP_WHEEL           TYRE                          1
WHEEL                RING                          1
WHEEL                SPOKE                        28
LADYBIKE             LADYFRAME                     1
LADYBIKE             COMP_WHEEL                    2

The two bottom rows in each table are new.

The hierarchial query now fails :

SQL> select
  2     parentid, childid,
  3     decode( (select itemType from items where itemid =
  4              itemsets.childid),
  5              'S', to_number(NULL),
  6              (select exp(sum(ln(is2.factor))) from itemsets is2
  7              start with is2.childid = itemsets.childid
  8              connect by prior is2.parentid = is2.childid ) ) factor2
  9  from itemsets
 10  start with parentid = 'BIKE'
 11  connect by prior childid = parentid;

PARENTID             CHILDID                 FACTOR2
-------------------- -------------------- ----------
BIKE                 FRAME                         1
BIKE                 COMP_WHEEL
COMP_WHEEL           WHEEL
WHEEL                RING                          4
WHEEL                SPOKE                       112
COMP_WHEEL           TYRE                          4

Because the COMP_WHEEL is now a child of two different parents, FACTOR2 is now
too high.

What I tried to do in my original query :

SQL> select items.itemid, items.itemname, tree.factor
  2  from (
  3  select childid, factor * nvl(prior factor, 1) factor
  4  from itemsets
  5  connect by prior childid = parentid
  6  start with parentid = 'BIKE'
  7  ) tree, items
  8  where items.itemid = tree.childid
  9  and items.itemtype = 'I';

was to hope, that "prior factor" would use the alias factor instead of the table
column factor - but of course it didn't work. I knew it, but I just had :-) to
try it...

So I've now tried out my PL/SQL function approach (if you can't make it SQL, try
PL/SQL, right :-)

I've created these types :

create type item_expanded_type as object (
   childid     VARCHAR2(20),
   factor      NUMBER
);

create type item_expanded_table as table of item_expanded_type;

And I've created a function :

CREATE OR REPLACE function item_expanded (
   parent_id      VARCHAR2,
   number_items   NUMBER DEFAULT 1
) return item_expanded_table
is
   table_cnt      PLS_INTEGER := 0;
   return_table   item_expanded_table := item_expanded_table();
   child_table    item_expanded_table;
begin
   for child in (
      select itemsets.childid,
             itemsets.factor,
             items.itemtype
        from itemsets, items
       where itemsets.parentid = parent_id
         and items.itemid = itemsets.childid
   ) loop
      if child.itemtype = 'S' then
         child_table := item_expanded(child.childid, child.factor *
number_items);
         return_table.extend(child_table.count);
         for child_cnt in child_table.first..child_table.last loop
            table_cnt := table_cnt + 1;
            return_table(table_cnt) := child_table(child_cnt);
         end loop;
      else
         return_table.extend(1);
         table_cnt := table_cnt + 1;
         return_table(table_cnt) := item_expanded_type(child.childid,
child.factor * number_items);
      end if;
   end loop;
   return return_table;
end item_expanded;
/

Now I can query like this :

SQL> select
  2     parent.itemid parentid, child.childid, child.factor
  3  from
  4     items parent,
  5     table(cast(item_expanded(parent.itemid) as item_expanded_table)) child
  6  where parent.itemid = 'BIKE';

PARENTID             CHILDID                  FACTOR
-------------------- -------------------- ----------
BIKE                 TYRE                          2
BIKE                 RING                          2
BIKE                 SPOKE                        56
BIKE                 FRAME                         1

That approach gives me the correct answer, but I'm concerned about performance.

Autotrace for the hierarchial approach :

SQL> select
  2     parentid, childid,
  3     decode( (select itemType from items where itemid =
  4              itemsets.childid),
  5              'S', to_number(NULL),
  6              (select exp(sum(ln(is2.factor))) from itemsets is2
  7              start with is2.childid = itemsets.childid
  8              connect by prior is2.parentid = is2.childid ) ) factor2
  9  from itemsets
 10  start with parentid = 'BIKE'
 11  connect by prior childid = parentid;

PARENTID             CHILDID                 FACTOR2
-------------------- -------------------- ----------
BIKE                 FRAME                         1
BIKE                 COMP_WHEEL
COMP_WHEEL           WHEEL
WHEEL                RING                          4
WHEEL                SPOKE                       112
COMP_WHEEL           TYRE                          4

6 rækker er valgt.

Statistics
----------------------------------------------------------
          0  recursive calls
        116  db block gets
         45  consistent gets
          0  physical reads
          0  redo size
        690  bytes sent via SQL*Net to client
        633  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          6  rows processed

Autotrace for the procedural approach :

SQL> select
  2     parent.itemid parentid, child.childid, child.factor
  3  from
  4     items parent,
  5     table(cast(item_expanded(parent.itemid) as item_expanded_table)) child
  6  where parent.itemid = 'BIKE';

PARENTID             CHILDID                  FACTOR
-------------------- -------------------- ----------
BIKE                 TYRE                          2
BIKE                 RING                          2
BIKE                 SPOKE                        56
BIKE                 FRAME                         1

Statistics
----------------------------------------------------------
         13  recursive calls
         28  db block gets
          8  consistent gets
          0  physical reads
          0  redo size
        513  bytes sent via SQL*Net to client
        404  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          6  sorts (memory)
          0  sorts (disk)
          4  rows processed

So the question to you, Tom, is now this :

Are all those recursive calls going to kill performance or what ?

If it's not a performance killer, then I'm going to stick to the procedural
approach, as it is easier to join with than the hierarchial query (at least
while we're still using 8i :-)

Thanks again for your answers (all of your answers - great archive to search
when problems arise ...)

          Followup:

will it kill performance?  not if you have small structures to "explode".

      Rating: 5
A slight
      different question   August 04, 2003
Reviewer:
      Reader  from Boston, MA
Using the original table itemsets and sample data,
but not items table:
Database version 9.0.1

create table itemsets (
  parentid  varchar2 (20),
  childid   varchar2 (20),
  factor    number
);
SQL> select * from itemsets;

PARENTID             CHILDID                  FACTOR
-------------------- -------------------- ----------
BIKE                 FRAME                         1
BIKE                 COMP_WHEEL                    2
COMP_WHEEL           WHEEL                         1
COMP_WHEEL           TYRE                          1
WHEEL                RING                          1
WHEEL                SPOKE                        28

Is it possible to use one SQL Query to generate
the following results?
PARENTID        CHILDID            FACTOR2
--------------- --------------- ----------
BIKE            FRAME                    1
BIKE            TYRE                     2
BIKE            RING                     2
BIKE            SPOKE                   56
COMP_WHEEL      TYRE                     1
COMP_WHEEL      RING                     1
COMP_WHEEL      SPOKE                   28
WHEEL           RING                     1
WHEEL           SPOKE                   28

Thanks

          Followup:

i don't understand the outputs from the inputs I guess.

bike => comp_wheel => wheel => spoke
                   => tyre

so, where is bike comp_wheel in the output and such.

      Rating: 5
Re: A slight
      different question  August 04, 2003
Reviewer:
      Reader  from Boston, MA
I guess I meant for all the
node-to-leaf relationship with cumulative factors.

          Followup:

nope, don't get it.

      Rating: 5
Re: A slight
      different question  August 04, 2003
Reviewer:
      Reader  from Boston, MA
Allow me to try to explain:
PARENTID             CHILDID                  FACTOR
-------------------- -------------------- ----------
BIKE                 FRAME                         1
BIKE                 COMP_WHEEL                    2
COMP_WHEEL           WHEEL                         1
COMP_WHEEL           TYRE                          1
WHEEL                RING                          1
WHEEL                SPOKE                        28

frame, tyre, spoke, ring are leaf nodes because
they don't appear as parentid in itemsets.
bike, comp_wheel, wheel are non-leaf nodes
because they appear as parent id in itemsets.

I want to a report of
non leaf node (bike, comp_wheel, wheel)
to leaf node (frame, tyre, spoke, ring)
with cumulative factor

          Followup:

you can easily get enough to "get" it

ops$tkyte@ORA920> l
  1  select rpad('*',level,'*') || parentid parentid, childid, factor,
  2         sys_connect_by_path( parentid, '/' ) scbp1,
  3         sys_connect_by_path( factor, '/' ) scbp2
  4    from t
  5   where parentid in ( select parentid from t )
  6     and childid not in ( select parentid from t )
  7* connect by prior childid = parentid
ops$tkyte@ORA920> /

PARENTID             CHILDID    FACTOR SCBP1                  SCBP2
-------------------- ---------- ------ ---------------------- --------------
*BIKE                FRAME           1 /BIKE                  /1
***WHEEL             RING            1 /BIKE/COMP_WHEEL/WHEEL /2/1/1
***WHEEL             SPOKE          28 /BIKE/COMP_WHEEL/WHEEL /2/1/28
**COMP_WHEEL         TYRE            1 /BIKE/COMP_WHEEL       /2/1
**WHEEL              RING            1 /COMP_WHEEL/WHEEL      /1/1
**WHEEL              SPOKE          28 /COMP_WHEEL/WHEEL      /1/28
*COMP_WHEEL          TYRE            1 /COMP_WHEEL            /1
*WHEEL               RING            1 /WHEEL                 /1
*WHEEL               SPOKE          28 /WHEEL                 /28

9 rows selected.

in 9i using sys connect by path.

      Rating: 5
Re: A slight
      different question   August 04, 2003
Reviewer:
      Reader  from Boston, MA
Sorry for my dumbness, but I don't understand how
I can get the cumulative factor, eg,
BIKE            FRAME                    1
BIKE            TYRE                     2    <--
BIKE            RING                     2    <--
BIKE            SPOKE                   56    <--
COMP_WHEEL      TYRE                     1
COMP_WHEEL      RING                     1
COMP_WHEEL      SPOKE                   28
WHEEL           RING                     1
WHEEL           SPOKE                   28

I guess you didn't mean that I should look at
the result of SCBP2 column,

***WHEEL             SPOKE          28 /BIKE/COMP_WHEEL/WHEEL /2/1/28

find 2, 1, and 28 and do 2*1*28 in my head?

          Followup:

you could do it in your head or, you could do it in the client

all I said was "i can get you sufficient data to get that"

ops$tkyte@ORA920> create or replace function eval( p_string in varchar2 ) return
number
  2  as
  3          l_str varchar2(4000) := substr( p_string, 2 ) || '/';
  4          l_val number := 1;
  5          l_n   number;
  6  begin
  7          while ( l_str is not null )
  8          loop
  9                  l_n := instr( l_str, '/' );
 10                  l_val := l_val * substr( l_str, 1, l_n-1 );
 11                  l_str := substr( l_str, l_n+1 );
 12          end loop;
 13          return l_val;
 14  end;
 15  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select rpad('*',level,'*') || parentid parentid, childid,
factor,
  2         sys_connect_by_path( parentid, '/' ) scbp1,
  3         sys_connect_by_path( factor, '/' ) scbp2,
  4             eval( sys_connect_by_path( factor, '/' ) ) factor2
  5    from t
  6   where parentid in ( select parentid from t )
  7     and childid not in ( select parentid from t )
  8  connect by prior childid = parentid;

PARENTID             CHILDI FACTOR SCBP1                  SCBP2         FACTOR2
-------------------- ------ ------ ---------------------- ---------- ----------
*BIKE                FRAME       1 /BIKE                  /1                  1
***WHEEL             RING        1 /BIKE/COMP_WHEEL/WHEEL /2/1/1              2
***WHEEL             SPOKE      28 /BIKE/COMP_WHEEL/WHEEL /2/1/28            56
**COMP_WHEEL         TYRE        1 /BIKE/COMP_WHEEL       /2/1                2
**WHEEL              RING        1 /COMP_WHEEL/WHEEL      /1/1                1
**WHEEL              SPOKE      28 /COMP_WHEEL/WHEEL      /1/28              28
*COMP_WHEEL          TYRE        1 /COMP_WHEEL            /1                  1
*WHEEL               RING        1 /WHEEL                 /1                  1
*WHEEL               SPOKE      28 /WHEEL                 /28                28

9 rows selected.

August 04, 2003 -- Thanks for the question regarding "remote
      batch job get ora-02049 when update our table through db link", version
817

      You Asked
we have a table in database A
there is a batch job in database B update the table through a db link.
the batch job's process:

select to look for the record exist or not
if exist update
otherwise insert..

then they get ora-02049 timeout: distributed transaction waiting for lock

Acccording to the developers they are doing commit and rollback for each record
processed.

I check with metalink and there is one statement:
The use of a DB Link opens you up to distributed rules of operation      even if
you only READ from it.
what does it mean :"distributed rules"
what could cause such problem as they get the errors sometimes but not always...
Thanks
--carol

      and we said...
why would you "look to see" if a record exists?  just try to update it and if
you get "sql%rowcount = 0", you insert it...

anyway, you'll get this on the update if the record is locked on the remote site
and remains locked for longer then your timeout period.

what they should do is:

  select the record FOR UPDATE NOWAIT;
  if that succeeds
  then
      if that returned 1 records then update
      else insert
  else
      print out pretty message or try again
  end if

hope the developers know that doing a commit for each record

o makes this really slow
o consumes more resources

August 04, 2003 Tony -- Thanks for the question regarding
      "Dynamic SQL", version 8.1.6

      You Asked
Hi Tom,
Could you please show me how to do this with an example :-
-- Pseudo-code
. Pass table name (source table with NO constraints) as an argument to the
procedure
. Get all data from the 'source table'
. Insert a row into the 'Target Table' having constraints (the source and target
definition is same)
. If insert fails trap the oracle error (FK violation or Not null violation or
PK violation)
  * Use this error to lookup in the lookup table having error name, severity of
error (High or Low), and the new column value. If severity is low, then replace
the original column value with the new column value. Try again to INSERT row in
the target (with NEW COLUMN VALUE), if fails (may be becuase of another error),
repeat as * above, till all NEW COLUMN values are replaced for low severity
errors. For each replacement of low severity, create a row in log table
indicating that the original column value is replaced with the new column value.
If at any point HIGH severity is hit, no further lookup, just insert row in the
log table. The defintion of log table is same as of source/target, with an
additional column error_msg
. Repeat the whole process for next row
-- All types of errors (FK violation, PK violation, and NOT NULL violation are
defined in the lookup table with old value, new value and severity of High or
Low)
Thanks
1. There are three types of tables - slow moving like area etc. (very few
inserts), few insert tables like new agents/brokers etc, and transactional
tables like payment etc , which has couple of thousands rows per frequency of
load.
2. Columns differ from table to table, but source table and target table columns
are the same. Error table has one extra column error_msg
With best regards

      and we said...
well, this doesn't sound like a good idea to me -- but code is code.

you would have to use dbms_sql since you need column level binding capabilities.

It'll be alot of code, couple hundred lines I would expect (hence no short,
small, sweet example from me).

it isn't going to be pretty or easy. this'll get you started anyway.  it won't
handle blobs/clobs/longs/long raws but the other standard types should be fine:

ops$tkyte@ORA920> create table emp as select * from scott.emp where 1=0;

Table created.

ops$tkyte@ORA920> select * from emp;

no rows selected

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace
  2  function  copy( p_src in varchar2, p_tgt in varchar2 )
  3  return number
  4  is
  5      l_srcCursor     integer default dbms_sql.open_cursor;
  6      l_tgtCursor     integer default dbms_sql.open_cursor;
  7      l_tgtStmt       long default 'insert into ' || p_tgt || '(';
  8
  9      type array is table of varchar2(4000) index by binary_integer;
 10      l_columns       array;
 11
 12      l_status        integer;
 13      l_colCnt        number default 0;
 14      l_cnt           number default 0;
 15      l_descTbl       dbms_sql.desc_tab;
 16  begin
 17      execute immediate 'alter session set nls_date_format=''dd-mon-yyyy
hh24:mi:ss'' ';
 18
 19      dbms_sql.parse(  l_srcCursor,  'select * from ' || p_src,
dbms_sql.native );
 20
 21      /* Now, describe the outputs of the query */
 22      dbms_sql.describe_columns( l_srcCursor, l_colCnt, l_descTbl );
 23
 24      l_columns(1) := null;
 25      for i in 1 .. l_colCnt loop
 26          dbms_sql.define_column( l_srcCursor, i, l_columns(1), 4000 );
 27          l_tgtStmt := l_tgtStmt || ' ' || l_descTbl(i).col_name || ',';
 28      end loop;
 29      l_tgtStmt := rtrim( l_tgtStmt, ',' ) || ') values (';
 30      for i in 1 .. l_colCnt loop
 31          l_tgtStmt := l_tgtStmt || ' :' || l_descTbl(i).col_name || ',';
 32      end loop;
 33      l_tgtStmt := rtrim( l_tgtStmt, ',' ) || ')';
 34      dbms_sql.parse(  l_tgtCursor,  l_tgtStmt, dbms_sql.native );
 35
 36
 37      l_status := dbms_sql.execute(l_srcCursor);
 38
 39       while ( dbms_sql.fetch_rows(l_srcCursor) > 0 )
 40       loop
 41          for i in 1 .. l_colCnt loop
 42              dbms_sql.column_value( l_srcCursor, i, l_columns(i) );
 43              dbms_sql.bind_variable( l_tgtCursor, l_descTbl(i).col_name,
l_columns(i) );
 44          end loop;
 45          l_cnt := l_cnt + dbms_sql.execute(l_tgtCursor);
 46      end loop;
 47
 48      /* free up resources.. */
 49      dbms_sql.close_cursor(l_srcCursor);
 50      dbms_sql.close_cursor(l_tgtCursor);
 51
 52      /* reset the date format ...  and return */
 53      execute immediate 'alter session set nls_date_format=''dd-MON-rr'' ';
 54      return l_cnt;
 55  end copy;
 56  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_output.put_line( 'copied ' || copy( 'scott.emp',
'emp' ) || ' rows' );
copied 14 rows

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating: 5
Good to start
      but I need little more help  August 04, 2003

Reviewer:  A reader
Tom,
Thanks for this help and quick response too.
But I am stuck with the scenario :-
. When insert fails may be because of FK or NOT NULL constraint, I need to trap
that error (which I can with the exception),
and then lookup (This bit is really hard for me) in the lookup table (the
definition of lookup table is known, which will contain the error, NEW VALUE,
severity type), and then dynamically change the old value with new value, and
try to insert again with the changed (NEW) value. Repeat this process, till I
hit the high severity error. If during this process, I don't hit the high
severity error, the row is inserted in the target, and row is also inserted in
the error/log table for later on viewing. Rows with high severity errors are
inserted into the error/log table only.
All potential errors are defined in lookup table with old and new value, and
severity of error (low or high).
Thanks and Regards

          Followup:

the looking up part is easy -- at least you get to use static sql.  I did the
nastly part for you!

If you cannot take an error message, and do a look up for something based on it,
well, I don't know what to tell you.

sounds like you need to

a) write some psuedo code
b) turn that into program code
c) test it, tune it, fix it

what programmers have been doing for many years.....

      Rating: 5
Those darn
      client requirements...  August 04, 2003
Reviewer:
      Dana Day  from Phoenix, AZ USA
Tom,
Gee. Golly. Client has requirement I get it to them now, and, well, I know YOU
can do it (even if I can't :-<).

And if client wanted it tomorrow, well, they would have requested it tomorrow.

August 05, 2003 Angelo -- Thanks for the question regarding
      "using PL/SQL in the using clause of Execute Immediate.", version 8.1.5

      You Asked
Dear Tom,

What I am trying to do is collect a number of elements into a PL/SQL table, can
i pass this table as an OUT variable in the EXECUTE IMMEDIATE with a USING
clause.  My PL/SQL table definition is defined in a package spec as follows:

type v_event_table is table of incident_event.event_id%type;

type v_event_rec is record
     (event_id_data   v_event_table,
     idx  binary_integer := 0);

I want to pass back the event_id(of which there can be many) once I find the
host database where the reside by using the following.

        t_call := 'rpc_inc_events._rpc_inc_evt@' ||
                  t_accryn  '.WORLD:1, :2, :3, :4, :5, :6, :7)';

    EXECUTE IMMEDIATE 'BEGIN ' || t_call ||'; end;'
                   USING IN OUT t_incident_id,
                         IN OUT t_notes,
                         IN OUT t_asset,
                         IN OUT t_status,
                         IN OUT t_impact_sum,
                         IN OUT t_countermeasures,
                            OUT t_event_rec)
t_event_rec is defined in the parameter list of the procedure as type
v_event_rec.

      and we said...
No, you will find passing the plsql tables back and forth to be pretty much
impossible.

In order to even send the table over to the remote procedure, the local variable
t_event_rec MUST be defined as:

  t_event_rec   rpc_inc_events.v_event_table@databaselink;

you see -- in order to call rpc_inc_events.rpc_inc_evt you MUST use the same
exact type (not another type that is defined the same) or you will recieve an
error.  A static example will demonstrate this:

At the remote site I have:

reate or replace package my_pkg
as
    type myArray is table of varchar2(255) index by binary_integer;

    procedure foo( x in out int, y in out myArray );
end;
/

Locally I tried:

scott@ORA8I.WORLD> declare
  2          type myArray is table of varchar2(255) index by binary_integer;
  3          t_x    number default 4;
  4      t_y    myArray;
  5  begin
  6          my_pkg.foo@ora816dev.us.oracle.com( t_x, t_y );
  7  end;
  8  /
declare
*
ERROR at line 1:
ORA-06550: line 6, column 2:
PLS-00306: wrong number or types of arguments in call to 'FOO'
ORA-06550: line 6, column 2:
PL/SQL: Statement ignored

scott@ORA8I.WORLD>
scott@ORA8I.WORLD> declare
  2          t_x    number default 4;
  3      t_y    my_pkg.myArray@ora816dev.us.oracle.com;
  4  begin
  5          my_pkg.foo@ora816dev.us.oracle.com( t_x, t_y );
  6  end;
  7  /

PL/SQL procedure successfully completed.

See, I have to use the REMOTE type in order to call this remote procedure.  Here
in lies your problem -- you are choosing the database link dynamically, you must
define your variables statically!  You cannot possibly "use" the correct type.

In addition to that, execute immediate accepts only SQL types in the USING
section.  You cannot have plsql types in there.  If you try to use SQL types
(create type....) you run into the same problem above -- you have to use the
REMOTE sql type but that would be static (and doesn't even work, you cannot use
a remote SQL type) again, not dynamic.

Review &
      Followup

      Rating:
      2
ORA-04054  August 04, 2003
Reviewer:
      Bipin Ganar  from INDIA
Hi Tom,
I have created one package to copy the data from one database to another with
databaselink but as one of databaselink is not working it is giving me directly
error ORA-04054.

Create or replace package package1 as

procedure p_mv_data(i_data_push_id in varchar2) is

lv_dest_br varchar2(100);
sql_stmnt_pushcntrl varchar2(1000);
sql_stmnt_pushdata varchar2(1000);
lv_data_pushid varchar2(30);
lv_sqlcode number;
lv_sqlerrm varchar2(200);
dblink_not_found exception;
pragma exception_init(dblink_not_found,-04054);
begin

lv_data_pushid :=i_data_push_id;

select '@'||service_name||'.world'
into lv_dest_br
from schema1.push_control,schema1.networked_branches
where to_number(destination_db)=br_cd
and data_push_id=lv_data_pushid;

sql_stmnt_pushdata:='insert
into schema3.push_data'||''||lv_dest_br||''||
' select b.col1,b.col2,b.col3,b.col4,b.col5,b.col6,b.col7,
b.col8,b.col9,b.col10,b.col11,b.col12,b.col13,b.col14,b.col15,b.col16,b.col17,b.c
ol18,b.col19,
b.col20,b.col21,b.col22,b.col23,b.col24,b.col25,b.col26,b.col27,b.col28,b.col29,b
.col30
from schema2.push_control a ,schema2.push_data b
where a.data_push_id ='||''''||lv_data_pushid||''''||
' and a.data_push_id=b.data_push_id;

EXECUTE IMMEDIATE sql_stmnt_pushdata;

commit;

exception

when dblink_not_found then
        RAISE_APPLICATION_ERROR(-04054,'error');

        insert into datacopy.pushstring values(sql_stmnt_pushdata);

    when others
         lv_sqlcode:=sqlcode;
         lv_sqlerrm :=sqlerrm;

        dbms_job.isubmit(v_job_no,v_task,sysdate,null);

        commit;
end p_mv_data;
end package1;

Can u tell me how to handle such error ?
as procedure is getting successfully completed then i 'm not able to trace which
of the link is not working .
Although if it's not working it should go for the next dblinks and insert the
data.

          Followup:

I do not think you've given us the whole story here.

that error is raised upon COMPILATION, not execution of the block of code.

I fail to see how this example would do that.  it seems the script that is
invoking this is using a dblink and that script is what is failing to compile.

you might see this at runtime IF you were compiling an anonymous block of PLSQL
-- which are you not.  Then it should be catchable.

      Rating:
      1
  August 04, 2003
Reviewer:  sumam
      seth  from India
I've tested the same what you have mentioned in your answer,when iam using a
anonymous block it throws the exception ,how do i handle that exception in my
program,as if u see the pkg.procedure p_mv_data, when the database link fails it
never goes in to the exception part.
My objective here is to handle the pl/sql compilation exceptions thru my
program.

          Followup:

give me a concrete working example to demonstrate with.

August 05, 2003 Julie -- Thanks for the question regarding
      "Converting Longs to LOBS", version 8.1.6

      You Asked
How to I convert my long columns into LOBS?

      and we said...
Thats easy -- see the TO_LOB function available in Oracle8i
release 8.1 and up.  TO_LOB can be use in insert, create table
as select, and update statements to convert

o a LONG into a CLOB
o a LONG RAW into a BLOB

Note that PLSQL does not recognize this function, hence if you
attempt to use it in a stored procedure, it will fail.  You
should use dynamic sql to avoid that issue.  For example:

ops$tkyte@ORA816.US.ORACLE.COM> create table t1
                                ( x int, y long );

Table created.

ops$tkyte@ORA816.US.ORACLE.COM> create table t2
                                 ( x int, y clob );

Table created.

ops$tkyte@ORA816.US.ORACLE.COM>
ops$tkyte@ORA816.US.ORACLE.COM> insert into t1 values
                                   ( 1, rpad('*',4000,'*') );

1 row created.

ops$tkyte@ORA816.US.ORACLE.COM>
ops$tkyte@ORA816.US.ORACLE.COM> insert into t2
                                select x, to_lob(y) from t1;

1 row created.

ops$tkyte@ORA816.US.ORACLE.COM>
ops$tkyte@ORA816.US.ORACLE.COM> begin
  2          insert into t2 select x, to_lob(y) from t1;
  3  end;
  4  /
begin
*
ERROR at line 1:
ORA-06550: line 2, column 27:
PLS-00201: identifier 'TO_LOB' must be declared
ORA-06550: line 2, column 2:
PL/SQL: SQL Statement ignored

ops$tkyte@ORA816.US.ORACLE.COM>
ops$tkyte@ORA816.US.ORACLE.COM> begin
  2          execute immediate
  3             'insert into t2 select x, to_lob(y) from t1';
  4  end;
  5  /

PL/SQL procedure successfully completed.
Review &
      Followup

      Rating:
      4
  March 13, 2001
Reviewer:  Edward R
       from FL USA
Very terse and helpful information.
Thanks for saving my time.

      Rating:
      4
  June 08, 2001
Reviewer:
      A reader  from Lebanon

      Rating: 5
what else we
      want for this question .  June 14, 2001
Reviewer:
      Gururaj  from GA USA
Hey thank you Tom. The answer is just perfect.

      Rating: 5
Oder by a clob
      column  October 10, 2001
Reviewer:  Andre W
      Nasser  from Brazil
The manual says I cannot user order by with a clob type. How can simulate that,
like a function that is the opposite of TO_LOB, like TO_CHAR(<clob>) ?

          Followup:

you can order by the first 4000 characters of the clob only:

select * from t order by dbms_lob.substr(clob_col, 4000,1 );

      Rating: 3
LONG TO BLOB
      THRU DB LINK  January 31, 2002
Reviewer:  reddy
       from USA
Hi Tom ,
I have many tables with long raw data types in a remote db and we r developing
an application on the local db and need to access these tables ...can i insert
the remote table data in to the local db tables and convert the data type to the
blob in the local db by using db links..pl help

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:238014476571
for an approach.

      Rating: 2
conversion in
      8.0.5  June 10, 2002
Reviewer:  Armin Eberle
       from Germany
How can I convert from LONG to BLOB in 8.0.5??

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1037232794454

      Rating: 5
Look at oracle
      docid 69627.1 and then Toms answer !   June 11, 2002

Reviewer:  Hubertus Krogmann  from germany. verl, 2nd
      floor
Exactlly as we need it, but is there a solution for:

alter table x add ( c clob ) select to_lob(x.l) from x ;
alter table x drop colum l ;

means add a clob column by inserting a long column on the fly ?

          Followup:

In 9i there is an

alter table x modify ( c clob );

that'll do that in one statement (you have to rebuild all indexes on that table
afterwards as well since it tends to rewrite it)

I don't fully understand your approach above.  In 8i you can:

alter table x add ( c clob );
update x set c = to_lob(l);
alter table x drop  column l;

BUT you'll probably find a parallel create table as select UNRECOVERABLE to be
much much faster.

      Rating: 3
What about
      updates  September 27, 2002
Reviewer:  A P
      Clarke  from London, England
Tom

You state that it is possible to use TO_LOB in update statements.  Unfortunately
the documentation and my experience contradict you:

SQL> desc t_long
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 PK_COL                                             NUMBER
 COL1                                               LONG

SQL> desc t_clob
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 PK_COL                                             NUMBER
 COL1                                               CLOB

SQL> INSERT INTO t_clob (pk_col, col1)
  2  SELECT t.pk_col, to_lob(t.col1)
  3  FROM   t_long t, tl_log l
  4  WHERE  l.action = 'I'
  5  AND    t.pk_col = l.tl_pk_col
  6  /

3 rows created.

SQL> DECLARE
  2  ln number := 0;
  3  BEGIN
  4     FOR lrec IN ( SELECT t.col1, t.pk_col
  5                   FROM   t_long t, tl_log l
  6                   WHERE  l.action = 'U'
  7                   AND    l.timestamp < sysdate
  8                   AND    t.pk_col = l.tl_pk_col )
  9     LOOP
 10        EXECUTE IMMEDIATE
 11        'UPDATE t_clob c
 12        SET    c.col1 = to_lob(:1)
 13        WHERE c.pk_col = :2' USING lrec.col1, rec.pk_col;
 14     END LOOP;
 15  END;
 16  /

DECLARE
*
ERROR at line 1:
ORA-00932: inconsistent datatypes
ORA-06512: at line 10

I really need to be able to update a CLOB column from a LONG column.

Summary: we need to replicate a table that has a LONG column.  Replication does
not support LONGs.  The application is currently in Forms 4.5 (I know, I know)
which doesn't support CLOBs, or indeed VARCHAR2(4000) fields.  I am attempting
to simulate replication using my own trigger, log table and stored procedure.
This works for INSERT but not for UPDATE.  The docs say TO_LOB only works for
INSERTs.  So how can I update changed records (other than by a DELETE-INSERT
combo)?

Thanks in advance, APC

          Followup:

your right, i should not have said "update"

it'll be delete/insert.

      Rating: 4
to_lob with
      SELECT INTO  May 06, 2003
Reviewer:  Craig
       from NW Arkansas
Can to_lob be used in a select into query?  I'm not having any luck.  I'm seeing
an incosistent datatypes error.  Is this even possible?

DECLARE
    lv_view_clob  clob := empty_clob();
    lv_view_name  varchar2(10) := 'my_view';
EXECUTE IMMEDIATE 'select text from dba_views where owner = USER and view_name =
:view_name'
    INTO lv_view_clob
    USING lv_view_name;
end;

          Followup:

if the view text is 32k or less (there is a length field in there) then

declare
  l_text long;
begin

   select text
     into L_text
     from dba_views
    where owner = user
      and view_name = lv_view_name;
...

will work -- else you will have to TO_LOB it into a global temporary table and
then retreive the lob locator from there.

      Rating: 4
to_lob
        June 23, 2003
Reviewer:  umesh  from
      blore india
if t1 has a column which is of long type why do i get this

  1  begin
  2  execute immediate 'create or replace view v1 as select to_lob(y) yy from
t1';
  3* end;
scott>/
begin
*
ERROR at line 1:
ORA-00932: inconsistent datatypes
ORA-06512: at line 2

          Followup:

because to_lob is documented to work in two cases:

o in a CREATE TABLE .. AS SELECT ...
o in an INSERT INTO .. SELECT ...

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a85397/function.htm#79466
it is used to perform one time, permanent conversions of LONGS to CLOBS.

      Rating: 4
lob to
      longraw  August 05, 2003
Reviewer:  Moiz
       from India
Hi tom ,
We are implementing anywhere banking replication. and in this replication , i
have a requierement in my signature table that i would like to convert lob
column in to longraw column ..
Pls do the needful ...
regards,
Moiz Arviwala

          Followup:

the needful in this case is to say "doesn't work that way".

there isn't anything in the database to convert the correct datatype (lob) into
the deprecated datatype (long raw).  long raw -> lob, yes (to_lob).  the other
way -- no, doesn't exist.

August 05, 2003 David -- Thanks for the question regarding "Bind
      Variables", version 8.1.6

      You Asked
Can you please give a simple example of using Bind Variable with the following
basic query?

Select * From Scott.Emp Where Name = 'John'

      and we said...
SQL> variable ename varchar2(20);
SQL> exec :ename := 'John'
SQL> select * from scott.emp where ename = :ename;

shows howto in sqlplus

create or replace procedure show_emp( p_ename in varchar2 )
as
begin
    for x in ( select * from scott.emp where ename = P_ENAME )
    loop
        dbms_output.put_line( x.empno );
    end loop;
end;

shows howto in plsql (plsql variables referenced in a query ARE bind variables)

exec sql begin declare section;
   varchar   ename[20];
exec sql end declare section;

   strcpy( ename.arr, "John" );
   ename.len = strlen( ename.arr );

   exec sql declare c cursor for select * from emp where ename = :ename;
   exec sql open c;
   for(;;)
   {
      exec sql whenever notfound do break;
      exec sql fetch c into emp_record;
      ....

shows howto in pro*c (and so on and so on and so on)....

Review &
      Followup

      Rating: 4
How to bind an
      array with dynamic SQL?  August 05, 2003
Reviewer:
      Tony  from India
I'm trying to bind an array consisting of deptnos with a dynamic query and get
enames for the deptnos. If I use the following code, I get the enames of first
element(deptno)in the array and the rest of the deptnos in the array are not
bind. Is my method correct? Please help me out. Please note that deptno is
dynamically found and stored in an array.

#include <stdio.h>
#include <sqlca.h>

EXEC SQL BEGIN DECLARE SECTION;
char *connstr = "emp/emp";
VARCHAR  ename[30][10];
int      deptno[10]={10,30,20};
int      row_cnt;
VARCHAR  sql_stmt[150];
EXEC SQL END DECLARE SECTION;

EXEC SQL INCLUDE SQLCA;
EXEC SQL INCLUDE SQLDA;
EXEC SQL INCLUDE ORACA;

 main()
{
   int i;
   EXEC SQL WHENEVER SQLERROR GOTO errpt;
   EXEC SQL CONNECT :connstr;

   strcpy(sql_stmt.arr,"SELECT ENAME FROM EMP WHERE DEPTNO IN (");
   strcat(sql_stmt.arr, ":c");
   strcat(sql_stmt.arr, ")");
   sql_stmt.len = strlen(sql_stmt.arr);

   printf("%s\n",sql_stmt);

   EXEC SQL PREPARE st FROM :sql_stmt;
   EXEC SQL DECLARE c1 CURSOR FOR st;
   EXEC SQL OPEN c1 USING :deptno;
   EXEC SQL FETCH c1 INTO :ename;

   row_cnt = sqlca.sqlerrd[2];

   printf("row count: %d\n",row_cnt);

   for(i=0; i<row_cnt; i++)
   {
    printf("%s \n",ename[i].arr);

   }
   EXEC SQL CLOSE c1;
   exit(0);

 errpt:
    fprintf(stderr, "\n%s\n", sqlca.sqlerrm.sqlerrmc);
    exit(1);
}

          Followup:

it doesn't work that way.  Array binding of inputs works with
insert/update/delete type statements -- whereby you can just execute the
statement over and over.

so, when I bind an array of 10 deptnos to a delete for example, the delete would
be:

   delete from emp where empno = :x

and the server will simply execute the delete 10 times over.  Note, we did not
say "where empno in ( :x )" -- it was empno = :x and the delete was done ten
times.

Now, with a select -- it would not make sense to execute it 10 times on the
server you see -- for the first 9 results would go where?  into the bit bucket!

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:110612348061
for an approach you can use.

August 05, 2003 Rachna -- Thanks for the question regarding "USe
      of Nested Table", version 8.1.7

      You Asked
Hi ,

I want to use nested tables for the following scenario .

subProd_id is defined on the basis of certain parameter values.

create  type paramobject as object
(param_description varchar2(10)
 param_val varchar2(10)) ;

create  type paramlist as table of paramobject ;

create table param_matrix(
subprod_id varchar2(10) ,
col_name varchar2(10) , --- The PArameter Name
col_values  paramlist   ---  Possible values of the parameter for the subprod_id
)
nested table col_values store as col_values_tab ;

insert into param_matrix
('SUBPRODUCT1' , 'BOOKKEEP_CODE',paramlist(paramobj('Value F','F'),
                    paramobj('Value G','G'),
                    paramobj('Value H','H')) ;

insert into param_matrix
('SUBPRODUCT1' , 'LOC_STATUS',paramlist(paramobj('Value I','I'),
                    paramobj('Value J','J')) ;

insert into param_matrix
('SUBPRODUCT2' , 'BOOKKEEP_CODE',paramlist(paramobj('Value I','I'),
                    paramobj('Value J','J'),
                    paramobj('Value K','K'));

Actually each subproduct is unique combination of different parameters ,
defined on the basis of their values.

Is it possible to find the subproductId based on the possible parameters.
I have input of the possible values of parameters based on that I want to find
the
sub productid .
I am not able to derive the subprod_id using the combination of parameters,
e.g. if Bookkepp_code = F and Loc_status = I then subprod_id is SUbproduct1
     if Bookkepp_code = I   then subprod_id is SUbproduct2

I hope I made myself clear with the example .
Is it possible to do , as I have dynamic parameters to define each subproductId
.
Is there any other better approach for the same.

We have not only two parameters to define a subproduct_id and have many other
parameters to define a subprod_id and wanted to use this feature to extract the
subprod_id.

Thanks in advance .

Rachna

      and we said...
this is not the best way to store this information if those are common questions
-- you might give a little more time to the physical design.

Me -- I'll never use a nested table in a CREATE TABLE statement.  You spend all
of your time UN-NESTING them to make them useful again!

ops$tkyte@ORA920> select subprod_id, col_name, b.*
  2    from param_matrix a, TABLE(a.col_values) b
  3  /

SUBPROD_ID           COL_NAME             PARAM_DESC PARAM_VAL
-------------------- -------------------- ---------- ----------
SUBPRODUCT1          BOOKKEEP_CODE        Value F    F
SUBPRODUCT1          BOOKKEEP_CODE        Value G    G
SUBPRODUCT1          BOOKKEEP_CODE        Value H    H
SUBPRODUCT1          LOC_STATUS           Value I    I
SUBPRODUCT1          LOC_STATUS           Value J    J
SUBPRODUCT2          BOOKKEEP_CODE        Value I    I
SUBPRODUCT2          BOOKKEEP_CODE        Value J    J
SUBPRODUCT2          BOOKKEEP_CODE        Value K    K

8 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select subprod_id
  2    from param_matrix a, TABLE(a.col_values) b
  3   where (col_name = 'BOOKKEEP_CODE' and param_val = 'F')
  4      or (col_name = 'LOC_STATUS' and param_val = 'I')
  5   group by subprod_id
  6  having count(distinct col_name) = 2
  7  /

SUBPROD_ID
--------------------
SUBPRODUCT1

ops$tkyte@ORA920>
ops$tkyte@ORA920> select subprod_id
  2    from param_matrix a, TABLE(a.col_values) b
  3   where (col_name = 'BOOKKEEP_CODE' and param_val = 'I')
  4  /

SUBPROD_ID
--------------------
SUBPRODUCT2

Review &
      Followup

      Rating:
      3
  March 03, 2003
Reviewer:  Rachna
       from Germany
Hi ,
Thanks a lot for the reply.
I think I am not able to make myself very clear .
Actually if I put OR for finding subproduct1 then it will not be correct as the
combination of OR can also give me another subproduct ID if say another
subprod_id is defined using loc_status only and with this value. We have
hundereds of subproduct ID with different combinations of parameter and their
values (and there can be different parameters used to define the subproduct
Ids).
What I think then in that case may be we should not store the same as Nested
tables. What is the better way of storing the combination in this case.
Transaction Table
-----------------
tran# bookeep_code loc_status     param_3     param_4 param_5 Subprod_ID
1    F        'loc1'  'P3'        'P4'    'P5'    (????? )To be assigned from
matrix table which actually based on the parameter values .

2    NULL        'loc2'    'P3.1'        'P4'    'P5'

and so on. As of now we have approx. 25 parameters and based on different
combination user wants to define subproductId which we want to store in
parameter matrix to save the actual possible parameter combination .
If you need some more information , pls let me know.
Our requirement at the end is to Assign the Subproduct Ids to the transactions
based on their definition in the parameter matrix.

Thanks once again

          Followup:

  3   where (col_name = 'BOOKKEEP_CODE' and param_val = 'F')
  4      or (col_name = 'LOC_STATUS' and param_val = 'I')
  5   group by subprod_id
  6  having count(distinct col_name) = 2
  7  /

is correct.  It will not give you the wrong answer.  It gives ONLY
subproduct_ids that have two distinct col_names such that the col names are in
bookkep_code and loc_status with the corresponding param vals.

OR works perfectly here.   the having makes sure two distinct col_names are
returned and the predicate limits us to just bookkeep_cod and loc_status.

      Rating: 3
Nested table
      useful or not ?  March 03, 2003
Reviewer:  Kiran
      Shah  from India
Dear Tom,
Following is the statement from your answer :
"Me -- I'll never use a nested table in a CREATE TABLE statement.  You spend all

of your time UN-NESTING them to make them useful again!"

Can you pl. give more light on this ?
My specific queries are :
1.) Why are you not in favour of nested tables ? (By the way, we have
extensively used this facility in our design).

2.) If this is not to be used, then how can we store multiple information for
particular column in a single row?

Thanks...

          Followup:

1) they are parent child tables in disguise but ones that add:

a 16 byte raw with a unique constraint on the parent table.  Most likely you
ALREADY HAVE a primary key on the parent table and most likely it is smaller.

a 16 byte raw that you need to index on the child (not auto-indexed, you need to
know to do it).  This is the foreign key and is hidden from you.

The inability to put many types of constraints on the nested table..

They are simple parent/child tables - except you lose the ability to access the
child table directly.

2) you are NOT storing anything in a "single row".  Physically they are a parent
child table pair, nothing more, nothing less

If you have my book "Expert one on one Oracle" - I write about them in there,
describe their implementation and talk about when I would use them.

      Rating:
      5
Nesting  March 03, 2003
Reviewer:
      Paul  from England
Since the nested table data for each row is not physically stored with the row
there is a performance loss with using nesting. This indicates that using a
nested table or simply using separate table should not differ much - from a
performance viewpoint.

During the logical design - specifically RDA(Relational Data Analysis) -
attributes(colummns) that can contain more than one value for one value of the
primary key are considered "repeating data" and are thus moved to a separate
table. Consider the example of a Person table that includes the column Alias. If
a person could have more than one alias we would create a new table (Alias)
containing an AliasID, Alias, and a foreign key column pointing to the Person
table.

      Rating: 5
Nested table
      useful or not ?   March 03, 2003
Reviewer:  Tony
      Andrews  from London, UK
> By the way, we have extensively used this facility in our design).

It's unfortunate that Oracle supports concepts like nested tables in the
database (though I realise it has to, for commercial reasons).  Just because
they are there, people think they ought to use them - in fact, the less database
expertise people have, they more they seem compelled to tie themselves in knots
with over-use of OR features.  Experienced database designers are reluctant to
use them, not just because they find them difficult to use (which they do), but
because they understand the benefits of a well-normalised relational design, and
the drawbacks of the hierarchic nested model.  By all means use Object Views if
that helps your application, but store the data in the most flexible way
possible - relational tables!

      Rating: 4
but in Oracle
      9i Release 2  April 24, 2003
Reviewer:  Jan
       from Europe
In 9i, there is an option to have 'Primary-key Based Object Identifiers' instead
of 16 byte RAW.
See: 'Oracle9i Application Developer's Guide - Object-Relational Features,
Release 2 (9.2)', search for 'Primary-key Based Object Identifiers'.

I have 2 questions:

1) If we use this version of Oracle and an extra coding for un-nesting is not a
problem, is there any advantages to use Objects, Collections and Nested Tables
in data storage? Is the *Object Oriented* PL/SQL (methods, heritance etc..) more
powerful then *Relational* PL/SQL?

2) If I need to select a value based on a filtering in a Nested table, is the
Parent-Child Relational way (join of 2 tables with all the necessary indexes)
more efficient?

Thanks, Jan

          Followup:

1) i use object relational extensions as a tool to make PLSQL more powerful.  I
have yet to use these features to store data -- although as a method to store an
XML document in a "structured" format, they might have some appeal

2) not any more efficient then if you did it yourself.  It is after all just a
server built "parent child table" with primary keys, foreign keys and all.

      Rating: 5
Nested Table in
      Form  May 29, 2003
Reviewer:  A reader

Hi, Tom,

I know the nested table can not be used in version 8, probably not in form 6i.
But what is the work around if we need to stick with nested table(not create
saperate master detail tables instead) in form 6i? Are there any improvement for
this in forms 9i?

Thanks

          Followup:

nested tables were introduced in version 8.0.  they can be used.

I don't use forms, haven't done a form since 1995 when this html/http thing
caught on.  sorry...  You can try otn.oracle.com -> discussion forums.

      Rating: 5
Unfortunate
      wording in Concepts Guide  August 05, 2003

Reviewer:  Tony Andrews  from London, UK
Reading the Concepts Guide section on Object Views, I was struck by the
implications of the following text:

[Quote]
Object views allow the use of relational data in object-oriented applications.
They let users:

o Try object-oriented programming techniques without converting existing tables
o Convert data gradually and transparently from relational tables to
object-relational tables
o Use legacy RDBMS data with existing object-oriented applications
[Unquote]

The implication seems to be that relational tables are a "legacy" that should
eventually be converted to object-relational tables.  This will only encourage
all the novice Oracle designers out there to use nested tables and varrays in
their database designs as a matter of course, believing it to be an
"improvement" on doing it the relational way.
A touch of your more realistic input wouldn't have gone amiss here!

          Followup:

legacy has a bad rap.  sort of like "hacking" which used to be good but now is
considered "bad".

think about it -- the second after you have the ribbon cutting on a new system,
you have what?  A legacy system.  It could be using the coolest, newest,
hotest techniques on the planet but that system -- now that it is in place -- is
legacy.

August 05, 2003 Venkat -- Thanks for the question regarding "SQL
      Query", version 8.1.7

      You Asked
Hi Tom,

I need your help in the SQL Query.

SQL>desc emp_data

Name                                        Null?    Type
------------------------------------------- -------- ------------
EMP_NUM                                              NUMBER
EMP_NAME                                             VARCHAR2(30)
TIMESHEET_DT                                         DATE
STATUS_DSC                                           VARCHAR2(30)

SQL>select * from emp_data;

EMP_NUM EMP_NAME                       TIMESHEET_D STATUS_DSC
======= ============================== =========== ==========
     10 Venkat                         03-FEB-2003 emp
     10 Venkat                         13-FEB-2003 emp
     10 Venkat                         14-FEB-2003 emp
     10 Venkat                         24-FEB-2003 emp
     20 Vincent                        03-FEB-2003 emp
     20 Vincent                        13-FEB-2003 emp
     20 Vincent                        14-FEB-2003 emp
     20 Vincent                        24-FEB-2003 emp
     20 Vincent
     30 xavier

For emp_type 20 there are total 5 records, out of which one is having
NULL value for timesheet_dt and status_dsc. We have only one record
for 30 where again  timesheet_dt and status_dsc value is NULL.

I need to show like this,

EMP_NUM EMP_NAME                       TIMESHEET_D STATUS_DSC
======= ============================== =========== ==========
     10 Venkat                         03-FEB-2003 emp
     10 Venkat                         13-FEB-2003 emp
     10 Venkat                         14-FEB-2003 emp
     10 Venkat                         24-FEB-2003 emp
     20 Vincent                        03-FEB-2003 emp
     20 Vincent                        13-FEB-2003 emp
     20 Vincent                        14-FEB-2003 emp
     20 Vincent                        24-FEB-2003 emp
     30 xavier

The emp_type 20 with NULL timesheet_dt should not appear because
there are other records with non-null value for this where as
since 30 is having only one record that should be shown.

Please help me in writing the SQL Query for the above.

Thanks in advance,
Venkat

      and we said...
analytics rock and roll:

ops$tkyte@ORA920> select emp_num,
  2         emp_name,
  3             timesheet_dt,
  4             status_dsc
  5    from (
  6  select emp_num, emp_name, timesheet_dt, status_dsc,
  7         count(timesheet_dt) over ( partition by emp_num ) cnt
  8    from emp_data
  9         )
 10   where cnt = 0 OR timesheet_dt is not null
 11  /

   EMP_NUM EMP_NAME                       TIMESHEET STATUS_DSC
---------- ------------------------------ --------- ----------------
        10 Venkat                         03-FEB-03 emp
        10 Venkat                         13-FEB-03 emp
        10 Venkat                         14-FEB-03 emp
        10 Venkat                         24-FEB-03 emp
        20 Vincent                        03-FEB-03 emp
        20 Vincent                        13-FEB-03 emp
        20 Vincent                        14-FEB-03 emp
        20 Vincent                        24-FEB-03 emp
        30 xavier

9 rows selected.

Review &
      Followup

      Rating: 5
SQL
      Query  February 03, 2003
Reviewer:  Venkat
       from India
Thank you so much Tom.
God...give me 10% of Tom's knowledge.

      Rating: 5
Perfect, thanks
      Tom  February 03, 2003
Reviewer:  Marc
       from Switzerland

      Rating: 5
Sorry for
      asking an irrelevant question here...  August 05, 2003

Reviewer:  Riaz Shahid  from PRAL, Islamabad ,
      Pakistan
Consider the following:

cr@STARR.LHR> ed
Wrote file afiedt.buf

  1  select * from (
  2  select nic_no NIC,reg_no,name,trim(address_1) "Address"
  3  from gstt01 a
  4  where nic_no in
  5  (select nic_no from (
  6  (select count(*),nic_no from gstt01
  7  where trim(nic_no)!='0'
  8  group by nic_no
  9  having count(*)>1
 10  )
 11  )
 12  )
 13  order by 1
 14  )
 15* where rownum<10

NIC           REG_NO        Address
============= ============= ==============================
093-71-045527 0309520091628 410-FIRST FLOOR CLOTH MARKET

              0309520086691 410-FIRST FLOOR CLOTH MARKET
101-44-637033 1200581002119 38-E, BLOCK-6, PECHS,
              1200840003446 38-E, BLOCK-6, PECHS,
101-47-075162 0701980505219 PLOT # 76, STREET # 6, SECTOR 1-
1100980552364 PLOT # 76, SECTOR-6, SECTOR-I-101-52-486192 0701980125473 XX
              0701220204919 PLOT # 82, STREET # 10, I-9/2
101-54-014392 0701170100364 H.NO. 23, GALI NO. 20

9 rows selected.

Elapsed: 00:00:51.97

Statistics
==========================================================
          6  recursive calls
        132  db block gets
      23294  consistent gets
      21218  physical reads
          0  redo size
       1278  bytes sent via SQL*Net to client
        425  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          7  sorts (memory)
          1  sorts (disk)
          9  rows processed

(I've put "where rownum<10" to show you just a set of whole resut set).

I want those records (out of those as shown above) who has:
(1) the same NIC_No
(2) different reg_no
and
(3) Whose first 10 characters of column address_1 are equal.

Can you help me regarding that ???

Riaz

          Followup:

1) the same nic_no as what?
2) a different reg_no from what?
3) are equal to what?

      Rating: 2
So
      Sorry....  August 07, 2003
Reviewer:  Riaz
       from PRAL, Lahore, Pakistan
I am really sorry for not providing the complete information...really sorry for
wasting your time. I solved that ptoblem. But now i am facing problem with
another query...consider:

cr@STARR.LHR> create table test (reg_no varchar2(13), address
varchar2(60),oper_sts char(1),reg_date date);

Table created.

Elapsed: 00:00:00.46

cr@STARR.LHR> ED
Wrote file afiedt.buf

  1* insert into test values('1111111111111','HELLO','1',SYSDATE-10)
cr@STARR.LHR> /

1 row created.

Elapsed: 00:00:00.25
cr@STARR.LHR> ED
Wrote file afiedt.buf

  1* insert into test values('2222222222222','HELLO','1',SYSDATE-15)
cr@STARR.LHR> /

1 row created.

Elapsed: 00:00:00.09
cr@STARR.LHR> ED
Wrote file afiedt.buf

  1* insert into test values('3333333333333','I AM HERE','1',SYSDATE-15)
cr@STARR.LHR> /

1 row created.

Elapsed: 00:00:00.09
cr@STARR.LHR> ed
Wrote file afiedt.buf

  1* insert into test values('4444444444444','I AM NOT HERE','1',SYSDATE-15)
cr@STARR.LHR> /

1 row created.

Elapsed: 00:00:00.25
cr@STARR.LHR> COMMIT;

Commit complete.

Elapsed: 00:00:00.25
cr@STARR.LHR> SELECT * FROM TEST;

REG_NO         ADDRESS              Oper_STS     REG_DATE

1111111111111  HELLO                    1        28-JUL-03 2222222222222  HELLO
                  1        23-JUL-03 3333333333333  I AM HERE                1
     23-JUL-03 4444444444444  I AM NOT HERE            1        23-JUL-03

Elapsed: 00:00:00.97

I need only those records who have duplicate address (first 10 characters
matching).i.e; i need the following:

REG_NO         ADDRESS              Oper_STS     REG_DATE

1111111111111  HELLO                    1        28-JUL-03 2222222222222  HELLO
                  1        23-JUL-03

Can you help me ???

      Rating: 4
SQL
      QUERY  August 08, 2003
Reviewer:  Khan MD
       from Singapore UOB
Try This,
create table test (reg_no varchar2(13), address
varchar2(60),oper_sts char(1),reg_date date);

select a.* from test a , test b
where substr(a.address,1,10) = substr(b.address,1,10)
and a.rowid <> b.rowid;

select * from test a
where exists (select 1 from test
where substr(a.address,1,10) = substr(address,1,10)
group by substr(address,1,10)
having count(substr(address,1,10)) > 1);

August 05, 2003 Steven -- Thanks for the question regarding
      "running OS commands from DB - Some work others do not ", version 8.1.7

      You Asked
Hi. I followed the examples from the recent thread on the issue and have found
limited success. I can get the java to run commands such as mkdir, rmdir and ls,
but when trying to execute a shell script or create a file via "echo hello >>
steven.out" it fails.

When I call the java class from the OS, the shell script runs fine.

I have assigned read, write and execute permissions on the shell script and the
/usr/bin directory. I have assigned writeFilePermissions as well. Please could
you have a quick look over what I have done and possibly suggest what I have
forgotten or done wrong?

Java Class:

import java.io.*;

public class ExecCommand
{
  public ExecCommand()
  {
  }

  static public int runCommand(String cmd) throws IOException
  {
    // start command running
    Process proc = Runtime.getRuntime().exec(cmd);

    // wait for command to terminate
    try
    {
      proc.waitFor();
    }
    catch (InterruptedException e)
    {
      System.err.println("process was interrupted");
      return -1;
    }

    // check its exit value
    if (proc.exitValue() != 0)
    {
      System.err.println("exit value was non-zero");
      return -2;
    }

    // return success
    return 0;
  }

  public static void main(String args[]) throws IOException
  {
    String cmdString = args[0];
    System.out.println("command string is: " + cmdString);

    try
    {
      //run a command
      int retVal = runCommand(cmdString);

      System.out.println("return value is: " + retVal);
    }
    catch (IOException e)
    {
      System.err.println(e);
    }
  }
}

** Note: when the call fails, it is always a -2 returned.

Procedure in the Database:

CREATE OR REPLACE FUNCTION Exec_Command(Command IN STRING)
RETURN NUMBER IS
LANGUAGE JAVA
NAME 'ExecCommand.runCommand(java.lang.String) return int';
/

The code:

DECLARE
  RetVal NUMBER;

BEGIN
  RetVal := SOSSIX.EXEC_COMMAND ( '/usr/bin/mkdir /home/stevenh/extracts/mydir'
);
END;

-- creates a directory OK.

The code:

DECLARE
  RetVal NUMBER;

BEGIN
  RetVal := SOSSIX.EXEC_COMMAND ( '/usr/bin/sh
/home/stevenh/extracts/post_files.sh c' );
END;

-- does not produce anything. A log file should be created when the shell script
is run. The value c is a required parameter.

The following grants have been issued:

exec
dbms_java.grant_permission('SOSSIX','java.io.FilePermission','/home/stevenh/extra
cts/-','read');
exec
dbms_java.grant_permission('SOSSIX','java.io.FilePermission','/home/stevenh/extra
cts/-','execute');
exec
dbms_java.grant_permission('SOSSIX','java.io.FilePermission','/home/stevenh/extra
cts/-','write');
exec
dbms_java.grant_permission('SOSSIX','java.io.FilePermission','/usr/bin/*','execut
e');
exec
dbms_java.grant_permission('SOSSIX','java.io.FilePermission','/tmp/sh*','read,exe
cute');
exec
dbms_java.grant_permission('SOSSIX','java.lang.RuntimePermission','*','writeFileD
escriptor');
exec
dbms_java.grant_permission('SOSSIX','java.lang.RuntimePermission','*','readFileDe
scriptor');

GRANT JAVADEBUGPRIV TO SOSSIX;
GRANT JAVAIDPRIV TO SOSSIX;
GRANT JAVASYSPRIV TO SOSSIX;
GRANT JAVAUSERPRIV TO SOSSIX;
GRANT JAVA_ADMIN TO SOSSIX;
GRANT JAVA_DEPLOY TO SOSSIX;

If I am on the AIX system, I can run:

java ExecCommand "sh post_files.sh c"

The shell script runs and the log file is created. Return code is 0.

Many thanks for any help given.

Steven.

      and we said...
Well, first of all a 'command' like:

echo 'hello world' >> /tmp/foo

will *never work* since they are not a command but rather a string that the
shell parses and interprets.  This thing runs commands -- it does NOT work as a
shell!

echo 'hello world'

would work but >> -- that is a shell thing, period.

99.99% of the time what goes wrong with shell scripts is:

o the ORACLE software owner does not have rx on the directories
o the ORACLE software owner does not have rx on the script (remember, it runs as
oracle)

o the shell script tries to write or read a file ORACLE is not allowed to.

I WOULD STRONGLY encourage you to NOT grant permission on /usr//bin/* -- that
lets you run the sh and that lets you do ANYTHING (warning lights should be
going off)

Review &
      Followup

      Rating:
      2
  May 01, 2002
Reviewer:
      A reader
Thanks, I granted execute on /usr/bin/sh as I had to include the sh in the
command string when running the class standalone otherwise it wouldn't work.

"o the ORACLE software owner does not have rx on the directories
o the ORACLE software owner does not have rx on the script (remember, it runs as
oracle)

o the shell script tries to write or read a file ORACLE is not allowed to. "

I used chmod 777 on the directory the shell script is in and the same for the
shell script itself.

I know the access rights are lax at the moment but I am just trying to get the
damn thing working through the database at the moment.

          Followup:

and so did it work?  you don't say.

      Rating:
      2
Apologies  May 01, 2002
Reviewer:
      Steven Hunter  from Glasgow
Sorry, my previous response should have said that I had ALREADY granted the full
access rights to the directory and shell script before submitting my question to
you. Therefore I am still in the same posititon.

          Followup:

you say:

/home/stevenh/extracts/post_files.sh

should create a file.

Where does it attempt to CREATE this file and does the OS user that is running
the database have the ability to CREATE that file in that place. (su to oracle,
run that script.  does that work?)

      Rating: 3
Ran script as
      oracle user  May 01, 2002
Reviewer:  Steven
      Hunter  from Glasgow
su-ed to oracle, changed to the /home/stevenh/extracts directory and ran
post_files.sh - it failed.

Tried ./post_files.sh - it worked and created the log file.

The log file is created in the current directory.

Steven.

          Followup:

use a fixed directory, like /tmp/  and try it again.

tell me -- what directory is the "current" directory when the java runs?  sort
of DANGEROUS to write just to the current directly, no idea what you might
clobber.

      Rating: 4
You can use
      pipes in system commands run from database  May 01, 2002

Reviewer:  Eric Kamradt  from Utah
You can execute a pipe command in the database.  You just need to run it through
a shell.  The following command worked inside the database on my laptop, run
from PL/SQL.

bash -c "echo My name is TwoPigs > t.txt"

One could modify the java code to also return the results of standard out and
err.  see (
http://www.ckamco.com/Execute_Command.zip

          Followup:

Yes, I think I said that you would have to run a shell -- that commands like:

"echo hello >> steven.out"

are not COMMANDS but things the shell does.  I also strongly suggest (encourage,
demand) that you do not use the SHELL directly as that leaves a security hole
just big enough to drive a truck through in your database!

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:952229840241
is the code I provide to get the output back as well.

      Rating: 5
STILL having
      trouble executing a shell script...  June 07, 2002

Reviewer:  Adrian Billington  from UK
Tom

I know this is not the place but being on the other side of the Atlantic, I can
never get in to ask a question through the correct channel...

Anyway, I've read and followed this thread to the letter. I'm using your code
line for line and have setup two tests. One is with execute granted to
/usr/bin/ls and the other to my test shell script. This is all working as
expected and I can exceute both.

Ultimately I want to be able to run a couple of ETL programs that our
third-party supplies from within the database (one converts data to ASCII and
the other loads it. Both programs write logs out. To emulate these I've used the
ostest.ksh script to run sqlldr and generate a log and bad file in the process.

Now the SQL*Load part works fine but I'm getting no log or badfile. I've used
/tmp as everyone has write access, but to no avail. Any ideas?

I'm using 8.1.6.3 on Solaris 8. I have the following permissions:-

KIND     TYPE_ TYPE_NAME                      NAME
      ACTION               ENABLED
-------- ----- ------------------------------
---------------------------------------- -------------------- --------
GRANT    SYS   java.io.FilePermission
/export/home/abill2/work/OS/ostest.ksh   execute              ENABLED
GRANT    SYS   java.io.FilePermission         /usr/bin/ls
      execute              ENABLED
GRANT    SYS   java.lang.RuntimePermission    *
      writeFileDescriptor  ENABLED

Regards

Adrian

          Followup:

lets see ostest.ksh

      Rating: 5
I
      goofed  June 07, 2002
Reviewer:
      A reader
Sorry Tom, I goofed - it works and I get my log and badfile. I'll try not to
waste your time next time ;)

Regards

Adrian

      Rating: 5
Exit
      codes  June 11, 2002
Reviewer:  Adrian
      Billington  from UK
Tom, despite my last "goof" post, I am in fact having some strange results from
executing a shell script. I am consistently getting a return code of 255 from
the ls command it contains.

I am now using your JAVA source and RUN_CMD function EXACTLY. I have used your
p_host_cmd procedure and my own anon/stored PL/SQL to execute the OS commands
all to the same effect. I have wrapped your function in a utility package I have
and still get the same results.

I have execute permissions on ls and ostest.ksh.

I am executing '/usr/bin/ls -l /tmp' and
'/export/home/abill2/work/OS/ostest.ksh' separately.

The ls call works fine with a 0 return.

ostest.ksh contains one line only (/usr/bin/ls -l /tmp), has 777 on file and
directory and returns 255 EVERY time. Because I am not getting -1, this tells me
it is not raising an exception within the java itself.

As an extra, when I was testing sqlldr using ostest.ksh and deliberately
generating a bad record, I was getting a return of 2, which is correct and what
you would expect from sqlldr.

I am very perplexed.

Regards

Adrian

          Followup:

post the entire ostest.ksh script -- does it have a first line?  (#!/bin/ksh or
something) as well (otherwise it is a sh script, maybe not entirely relevant but
want all of the details).

I just did a test where I ran:

rt_test@ORA817DEV.US.ORACLE.COM> create or replace procedure RC(p_cmd in
varchar2)
  2  as
  3    x number;
  4  begin
  5    x := run_cmd(p_cmd);
  6    dbms_output.put_line( 'return code = ' || x || ' from ' || p_cmd );
  7  end;
  8  /

Procedure created.

rt_test@ORA817DEV.US.ORACLE.COM> show errors
No errors.
rt_test@ORA817DEV.US.ORACLE.COM> /

Procedure created.

rt_test@ORA817DEV.US.ORACLE.COM>
rt_test@ORA817DEV.US.ORACLE.COM>
rt_test@ORA817DEV.US.ORACLE.COM> set serveroutput on size 1000000
rt_test@ORA817DEV.US.ORACLE.COM> exec dbms_java.set_output(1000000)

PL/SQL procedure successfully completed.

rt_test@ORA817DEV.US.ORACLE.COM> /

Procedure created.

rt_test@ORA817DEV.US.ORACLE.COM> exec rc('/export/home/tkyte/work/OS/ostest.ksh'
)
total 115872
-rw-r--r--   1 tkyte    other        343 Nov 19  2001 10514.ksh
.....
-rw-r--r--   1 root     other        418 Jul 11  2001 zz
return code = 0 from /export/home/tkyte/work/OS/ostest.ksh

PL/SQL procedure successfully completed.

then I went out to the OS and did:

$ chmod a=r,u=rwx work

$ ls -ldg work
drwxr--r--   3 tkyte    other         512 Jun 11 13:26 work

and then:

rt_test@ORA817DEV.US.ORACLE.COM> exec rc('/export/home/tkyte/work/OS/ostest.ksh'
)
return code = 255 from /export/home/tkyte/work/OS/ostest.ksh

PL/SQL procedure successfully completed.

So, I believe it is a file permission problem or something SIMILAR at the OS
level once again.  Make sure the ENTIRE DIRECTORY PATH is readable and
executable

      Rating: 5
Spot
      on...  June 11, 2002
Reviewer:  Adrian
      Billington  from UK
Tom

ostest.ksh
----------
/usr/bin/ls -l /tmp

Result: 255 and ls -l not executed.

You were spot on when you mentioned #!/bin/ksh. I'm kicking myself here because
I only left it out due to my eagerness to get into the JAVA :). When I added the
hash-bang to ostest.ksh, I got a 0 and the ls listing. I'm surprised the sh
can't run the ls but nonetheless, I always use Korn anyway (well almost always
;) ). A very sloppy mistake on my part.

Incidentally, our DBA noticed that my tests were leaving quite a few "objects"
lying around attached to the processes (SQL*Plus) I was running on. He suggested
I p.destroy() them at the end of the try{ } section as good practice (obviously
they would clear out when I exited SQL*Plus).

Thanks again.

Adrian

          Followup:

Kudos to your DBA!  Well done.  Not many would have been able to trace that back
and determine the cause (they might just kill -9 them ;)

      Rating: 4
where to find
      errors returned?  June 14, 2002
Reviewer:
      George  from chester springs, pa
I have reviewed a number of the postings about running an OS command through
JAVA, and think I have followed them
correctly..  but I am now getting the following message
when I try to execute:

SQL> exec OS_EXECCMD( 'ps -ef' )

java.lang.ArrayIndexOutOfBoundsException
at OSUTIL.ExecCmd(OSUTIL:14)

I have copied an example exactly, then changed the name..
here are the first few lines in case the 14 is a line number

CREATE OR REPLACE and COMPILE JAVA SOURCE
NAMED "OSUTIL"
AS
import java.io.*;
import java.lang.*;

public class OSUTIL extends Object
{

  public static int ExecCmd(String[] args)
  {
    Runtime rt = Runtime.getRuntime();
    int        rc = -1;

    try
    {
       Process p = rt.exec(args[0]);

       int bufSize = 4096;
       BufferedInputStream bis =
        new BufferedInputStream(p.getInputStream(),bufSize);
       int len;
       byte buffer[] = new byte[bufSize];

....

I have also granted execute on everything "*" - (1: I'm just
testing the solution, 2: that access is only for 1 restricted user.)

anyway, can you suggest where I might go to debug this?

thanks, George

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:952229840241
and look for ArrayIndexOutOfBoundsException

      Rating: 5
running connect
      and disconnect in plsql  December 20, 2002

Reviewer:  A reader
Hi

How can we run connect and disconnect commands (sqlplus) inside Pl/SQL?

          Followup:

you cannot - think about it.  where does PLSQL run?  (it runs in the SERVER).
If you actually disconnected from PLSQL where would you be?  (nowhere).  That
would be sort of like an out of body experience.

If you want a plsql routine to access many databases -- we use database links
for that.

      Rating:
      5
  March 28, 2003
Reviewer:  SY
       from AVO
Wow, Tom, thanks for your 'RUN_CMD' java code , that's great.

And now, I was asked to export/import daily data from DB server X to DB server
Y, all procedures must run on DB Y (security reason and easy to manage).

I can do it through DB link. But some DBAs said the export will impact the
performance of DB X which work as a main OLTP server, they asked me to export
the data from user m of DB X to user n of DB X, and then use DB link to transfer
the data from DB X to DB Y.

I think the procedures at DB Y using DB link  to export data from user m to user
n (m & n are all in DB X) will cause very low performance,  so I plan to use
your 'RUN_CMD' java code to do the job, e.g.  DB Y>exec rc('SQLPLUS
user/pws@DB_X @C:\1.sql');

But it seems there are some better ways I don't know...
Any comments? Thx in adv. :)

          Followup:

oh my -- your DBA's want to double the work in order to reduce the work?  I
believe they have the concept backwards.

They are saying "reading the data from user M will affect performance so instead
of reading the data from user M ONCE we want you to

o read the data from user M and write it to user N -- so you read the data
anyway AND you add the additional overhead of writing, creating undo, redo etc
in this important database

o read the data AGAIN from user N -- effectively doubling the IO it takes to
achieve your goal.

So instead of:

o read user M

they want you to

o read user M
o write it all to user N
o read user N

Is that what you are saying????  If so, I'm really not understanding their
"thought process" at all.

      Rating:
      5
  March 29, 2003
Reviewer:  SY
       from AVO
Thanks,Tom! Very Clear suggestion!

1. You catch my mean. The DBAs have their opinion: As user m and user n belong
to different tablespace on different disks, so the their IO are isolate, and
read/write in the same DB is faster than dblink on different DB, so impact will
be least.

And your words tell me , that different users in the same DB have the same
IO/LOG/Process, right?  NEW knowledge ! :)

2. Tom , do you think that the best way to complete the job is still using
DBLINK to copy directly?

          Followup:

1) opinions are meaningless in situations like this.

Ask them "would it be ok if I setup a process to go into an infinite loop -- I
mean, I won't be hitting your disk so doing all of that work should be OK with
you right?"

Reallly --

In the user to user to dblink they have to

read user m's data (X ios)
write it to user n's place (Y ios to another disk, plus Z ios to log, plus M cpu
cycles to perform operations, plus N ios to rollback)
read user n's data over dblink (X ios all over again plus the probable block
cleanouts which will generate redo as well)
clear out user n's data

or then cay

read user m's data (X ios)

I cannot fathom the though process whereby they are thinking #1 is "optimal".

2) umm, yes.

      Rating: 5
trace file
      permission changing in udump  August 05, 2003

Reviewer:  Mike
Tom,
I have an interesting issue with the permission changing on the trace file in
the UDUMP.

-rw-r--r--   1 oracle   dba          627 Jul 19 10:33 s0p2dbd2_ora_10929.trc
-rw-------   1 oracle   dba          625 Jul 19 15:52 s0p2dbd2_ora_1510.trc
-rw-------   1 oracle   dba          625 Jul 19 16:29 s0p2dbd2_ora_1559.trc
-rw-r--r--   1 oracle   dba          625 Jul 21 10:33 s0p2dbd2_ora_6358.trc

You may notice that the permissions not consistent over the time. The contains
are similar. Does the permission have to do with the “Unix process pid”.

$ more oraclebase_test/product/8.1.7/admin/S0P2DBD2/udump/s0p2dbd2_ora_10929.trc

Dump file
/oraclebase_test/product/8.1.7/admin/S0P2DBD2/udump/s0p2dbd2_ora_10929.trc
Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production
ORACLE_HOME = /oraclebase_test/product/8.1.7
System name:    SunOS
Node name:      cpc56
Release:        5.8
Version:        Generic_108528-19
Machine:        sun4u
Instance name: S0P2DBD2
Redo thread mounted by this instance: 1
Oracle process number: 11
Unix process pid: 10929, image: oracle@cpc56 (TNS V1-V3)

*** 2003-07-19 10:33:42.844
*** SESSION ID:(10.1) 2003-07-19 10:33:42.797
Registered presentation
http://admin/
on database startup

$ more /oraclebase_test/product/8.1.7/admin/S0P2DBD2/udump/s0p2dbd2_ora_1510.trc

Dump file
/oraclebase_test/product/8.1.7/admin/S0P2DBD2/udump/s0p2dbd2_ora_1510.trc
Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production
ORACLE_HOME = /oraclebase_test/product/8.1.7
System name:    SunOS
Node name:      cpc56
Release:        5.8
Version:        Generic_108528-22
Machine:        sun4u
Instance name: S0P2DBD2
Redo thread mounted by this instance: 1
Oracle process number: 11
Unix process pid: 1510, image: oracle@cpc56 (TNS V1-V3)

*** 2003-07-19 15:52:21.146
*** SESSION ID:(10.1) 2003-07-19 15:52:21.105
Registered presentation
http://admin/
on database startup

$ more /oraclebase_test/product/8.1.7/admin/S0P2DBD2/udump/s0p2dbd2_ora_6358.trc

Dump file
/oraclebase_test/product/8.1.7/admin/S0P2DBD2/udump/s0p2dbd2_ora_6358.trc
Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production
ORACLE_HOME = /oraclebase_test/product/8.1.7
System name:    SunOS
Node name:      cpc56
Release:        5.8
Version:        Generic_108528-22
Machine:        sun4u
Instance name: S0P2DBD2
Redo thread mounted by this instance: 1
Oracle process number: 11
Unix process pid: 6358, image: oracle@cpc56 (TNS V1-V3)

*** 2003-07-21 10:33:57.703
*** SESSION ID:(10.1) 2003-07-21 10:33:57.642
Registered presentation
http://admin/
on database startup
$

          Followup:

they are quite old, simple chmod by someone would do that.

      Rating:
      5
  August 05, 2003
Reviewer:  Mike
       from TX
I have checked the parameter:
_trace_files_public = true

August 05, 2003 -- Thanks for the question regarding "BLOBS etc",
      version 8.0.4.0.0

      You Asked
unten stehende Procedure wirft mit der letzten Anweisung :

DBMS_LOB.COPY(  ... ) einen Fehler auf, den ich nicht be-
heben kann.

Der Code ist aus dem BeispielCode des Packages übernommen -
spielt aber nicht.

Die Initialisierung der Locator (en) scheint  zu funktionieren,
der User hat als DBA auch das Recht Execute anny Procedure ..

Die Fehlermedung ist : ORA-06512
MfG Andreas Näther

Create or Replace Procedure CopyBlobFeld IS
   bZiel BLOB;
   bQuelle BLOB;

   -- einfach mal aus Ihrer Hilfe übernommen
   dest_offset    INTEGER := 1;
   src_offset     INTEGER := 1;
   amt            INTEGER := 3000;

begin

   -- Quell-Locator initialisieren

   select "Name_des_BlobFeldes "
     into bQuelle
     from "QuellTabelle" where "PKeyFeld" = 'irgendwas
eindeutiges';

   -- Ziel-Locator initialisieren

   select "Name_des_BlobFeldes "
     into bZiel
     from "ZielTabelle" where "PKeyFeld" = 'irgendwas
eindeutiges';

   DBMS_LOB.COPY(bZiel, bQuelle, amt, dest_offset, src_offset);

end;

      and we said...
Without the full error stack -- this is hard to diagnose.  The
fact that I do not speak German didn't help either ;)

I have a feeling either:

o the BLOB from ZielTabelle is NULL or
o it is not null but you did not lock that row and therefore
cannot write to it.

I'll show you the 2 errors and then how to correctly code this
logic:

ops$tkyte@8i> create table t1 ( x int primary key, y blob );
Table created.

ops$tkyte@8i> create table t2 ( x int primary key, y blob );
Table created.

My T1 and T2 represent your QuellTabelle and ZielTabelle.

ops$tkyte@8i> insert into t1 values ( 1, utl_raw.cast_to_raw(
'How Now Brown Cow' ) );
1 row created.

ops$tkyte@8i> insert into t2 values ( 1, NULL );
1 row created.

ops$tkyte@8i> commit;
Commit complete.

ops$tkyte@8i> declare
  2      t1_blob    blob;
  3      t2_blob    blob;
  4
  5      amt    number default 3000;
  6      dest_off   number default 1;
  7      src_off    number default 1;
  8  begin
  9      select y into t1_blob from t1 where x = 1;
 10
 11      select y into t2_blob from t2 where x = 1;
 12
 13      dbms_lob.copy( t2_blob, t1_blob, amt, dest_off, src_off
);
 14  end;
 15  /
declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SYS.DBMS_LOB", line 372
ORA-06512: at line 13

If that is the error you are getting, it is due to the fact
that you have a NULL blob.  We must initialize the blob.  We can
do that as follows:

ops$tkyte@8i> delete from t1;
1 row deleted.

ops$tkyte@8i> delete from t2;
1 row deleted.

ops$tkyte@8i> commit;
Commit complete.

ops$tkyte@8i> insert into t1 values
  2  ( 1, utl_raw.cast_to_raw( 'How Now Brown Cow' ) );
1 row created.

ops$tkyte@8i> insert into t2 values ( 1, empty_blob() );
1 row created.

ops$tkyte@8i> commit;
Commit complete.

ops$tkyte@8i> declare
  2      t1_blob    blob;
  3      t2_blob    blob;
  4
  5      amt    number default 3000;
  6      dest_off   number default 1;
  7      src_off    number default 1;
  8  begin
  9      select y into t1_blob from t1 where x = 1;
 10
 11      select y into t2_blob from t2 where x = 1;
 12
 13      dbms_lob.copy( t2_blob, t1_blob, amt, dest_off, src_off
);
 14  end;
 15  /
declare
*
ERROR at line 1:
ORA-22920: row containing the LOB value is not locked
ORA-06512: at "SYS.DBMS_LOB", line 372
ORA-06512: at line 13

Now, if you are getting this error hilited above, then we must
lock the row first.  This can be accomplished via:

ops$tkyte@8i> delete from t1;
1 row deleted.

ops$tkyte@8i> delete from t2;
1 row deleted.

ops$tkyte@8i> commit;
Commit complete.

ops$tkyte@8i> insert into t1 values
  2  ( 1, utl_raw.cast_to_raw( 'How Now Brown Cow' ) );
1 row created.

ops$tkyte@8i> insert into t2 values ( 1, empty_blob() );
1 row created.

ops$tkyte@8i> commit;
Commit complete.

ops$tkyte@8i> declare
  2      t1_blob    blob;
  3      t2_blob    blob;
  4
  5      amt    number default 3000;
  6      dest_off   number default 1;
  7      src_off    number default 1;
  8  begin
  9      select y into t1_blob from t1 where x = 1;
 10
 11      select y into t2_blob from t2
                where x = 1 FOR UPDATE;
 12
 13      if ( t2_blob is NULL )
 14      then
 15          update t2 set y = empty_blob() where x = 1
 16          returning y into t2_blob;
 17      end if;
 18
 19      dbms_lob.copy( t2_blob, t1_blob, amt, dest_off, src_off
);
 20  end;
 21  /

PL/SQL procedure successfully completed.

That is the best way to code that block.  It will SELECT out
and lock the existing BLOB (the FOR UPDATE does that) and in the
event it discovers the BLOB is NULL -- it will initialize it and
retrieve its value in one statement

Review &
      Followup

      Rating: 5
Changing
      datatype from varchar2 to blob  July 24, 2003

Reviewer:  A reader  from Houston
Tom,

I am getting the following error when changing the data type of a column from
varchar2 to blob

SQL> update table1 set description_test = empty_blob();

17351 rows updated.

SQL> update table1 set description_test = UTL_RAW.CAST_TO_RAW (description);
update repair set description_test = UTL_RAW.CAST_TO_RAW (description)
                                     *
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: raw variable length too long
ORA-06512: at "SYS.UTL_RAW", line 186

The table I am changing on has 18K records and the width of the description
column is varchar2(4000).  I get the same error even without setting the blob
column to empty_blob().

Any idea why this is happening?

Thank you

          Followup:

what happens if you select max( utl_raw.length(
utl_raw.cast_to_raw(description)) ) from that table?

      Rating: 5
varchar2 to
      blob  July 24, 2003
Reviewer:  A reader

I get the same error

SQL> select max( utl_raw.length(utl_raw.cast_to_raw(description)) ) from table1;
select max( utl_raw.length(utl_raw.cast_to_raw(description)) ) from table1
                           *
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: raw variable length too long
ORA-06512: at "SYS.UTL_RAW", line 186

Thank you for answering my question so promptly even on your vacation time

          Followup:

ok, now -- try to figure out if it is ALL rows or just some row.  eg:

begin
   for x in ( select rowid rid from t )
   loop
       begin
          select ( utl_raw.length(utl_raw.cast_to_raw(description)) ) into x
            from t
           where rowid = x.rid;
       exception
          when others then dbms_output.put_line( x.rid || ' ' || sqlerrm );
       end;
   end loop;
end;

and see if it is a general issue or a problem with a certain row (and that row's
data just might be "bad")

but in any case, you are working towards a test case to give to support.

      Rating: 3
Why not insert
      using returning?  July 25, 2003
Reviewer:
      A reader
> ERROR at line 1:
> ORA-22920: row containing the LOB value is not locked
> ORA-06512: at "SYS.DBMS_LOB", line 372
> ORA-06512: at line 13
> Now, if you are getting this error hilited above, then we > must lock the row
> first.  This can be accomplished via:

Tom,
Instead of doing
1. Insert using empty_clob()
2. Issue commit
3. Select for update to lock the row
4. Write data using clob locator
5. Issue commit again

One can
1. Make use of returning clause of Insert statement to return newly created clob
locator
2. Write data using clob locator
3. Issue commit

This way
1. Mo need to comming twice
2. Can rollback if clob writing fails for some reason
3. No need to call database twice i.e. first to create a row with clob and later
to get clob locator
4. No need of locking as transaction is not commited i.e. the row that was
created using empty_clob is not visible to others
5. Better performance

What is your take?

- Thanks

          Followup:

I was showing how to update an existing, committed piece of data.

of course if you were just creating, insert into returning would make sense.

      Rating:
      4
Thanks!  July 31, 2003
Reviewer:
      A reader
Thanks Tom.

      Rating: 5
varchar2 to
      blob   August 05, 2003
Reviewer:
      A reader
Tom,

Reg: >>ok, now -- try to figure out if it is ALL rows or just some row.  eg:

When I try to update individual records, on some records(not all record) I get
the same error.

ORA-06502: PL/SQL: numeric or value error: raw variable length too long
ORA-06512: at "SYS.UTL_RAW", line 186

When I try to look at the data in the varchar2 field through SQLPlus, it comes
up fine.

Any suggestions please?

Thank you

          Followup:

if you copy one of the failing rows -- just one -- to another table -- does it
reproduce?  if so, we got ourselves a test case for support.

if not, we got ourselves a different test case for support -- possible issue
with the database data.

      Rating: 5
VARCHAR2 to
      BLOBS  August 05, 2003
Reviewer:
      A reader
Thank you

August 05, 2003 -- Thanks for the question regarding "Issue
      Frequent COMMIT Statements", version 9i

      You Asked
Tom : I' ve recently read an article about Performance on Dbasupport.com an i
couldn't believe what i was reading about commits
I usually read your answers on this site and I' ve read your book as well and
you always suggest to not commit fequently. I completly agree with you, even i'
ve compares each other.
Below you have an extract of the article. Please i would like what you think
about the author suggestion regarding commit.

Tnank in Advance
Pablo from Argentina

http://www.dbasupport.com/oracle/ora9i/performance/tuning-2.shtml#commit
"Issue Frequent COMMIT Statements

Whenever possible, issue frequent COMMIT statements in all your programs. By
issuing frequent COMMIT statements, the performance of the program is enhanced
and its resource requirements are minimized as COMMIT frees up the following
resources:

Ø       Information held in the rollback segments to undo the transaction, if
necessary

Ø       All locks acquired during statement processing

Ø       Space in the redo log buffer cache

Ø       Overhead associated with any internal Oracle mechanisms to manage the
resources in the previous three items"

      and we said...
I'll be blunt

The author is totally wrong here.

Frequent commits -- sure, "frees up" that undo -- which invariabley leads to
ORA-1555 and the failure of your process.  Thats good for performance right?

Frequent commits -- sure, "frees up" locks -- which throws transactional
integrity out the window.  Thats great for data integrity right?

Frequent commits -- sure "frees up" redo log buffer space -- by forcing you to
WAIT for a sync write to the file system every time -- you WAIT and WAIT and
WAIT.  I can see how that would "increase performance" (NOT).  Oh yeah, the
fact that the redo buffer is flushed in the background

o every three seconds
o when 1/3 full
o when 1meg full

would do the same thing (free up this resource) AND not make you wait.

o frequent commits -- there is NO resource to free up -- undo is undo, big old
circular buffer.  It is not any harder for us to manage 15 gigawads or 15 bytes
of undo.  Locks -- well, they are an attribute of the data itself, it is no more
expensive in Oracle (it would be in db2, sqlserver, informix, etc) to have one
BILLION locks vs one lock.  The redo log buffer -- that is continously taking
care of itself, regardless of whether you commit or not.

that article bullet point #12 is wrong, wrong, wrong.  So wrong.... So very
very wrong.

Review &
      Followup

      Rating: 4
Statement on
      Cache Buffer  August 09, 2002
Reviewer:  Robert
      Chin  from PA
Tom, the author, in Part-1 of the article, under #3"Shared  SQL Statement",

http://www.dbasupport.com/oracle/ora9i/performance/tuning-1.shtml#truncate
stated
"Unfortunately, the cache buffering is applied only to simple tables; multiple
table queries and joins are never cached."

Is this true ?

also he said, which I find odd...

"By default, ORACLE uses CHOOSE optimizer mode. To reduce the potential for
unplanned full table scans, you should avoid using the CHOOSE option; either use
the RBO or the CBO throughout your database."

Please clear the smoke for us
Thanks

          Followup:

he is wrong, totally wrong.

there is more wrong information that article then correct.

it is trivial to see that statements involving many tables are cached. select
sql_text from v$sql will show you this.

      Rating: 5
Tom, U R The
      Man!  August 09, 2002
Reviewer:  Mr. 420
       from Like a mouse in the cage, I am trapped inside the cubicle

:-) Thanx Tom for making sense out of that nonesense article and correcting the
incorrect information. Those who come to your web-site are truly the lucky ones.
You ARE AWESOME! :-) Please keep up the good work, and helping out the otherwise
lost souls in this vast realm of database.

      Rating: 4
It is so
      confusing  August 09, 2002
Reviewer:
      netbanker  from Toronto
Hi, Mr.Toracle,

It is so surprise to read that frequent commit does not acturally enhance the
performance of db such as locking,redo log...etc.

So, what is your recommendation towards commit frequency problem when designing
the application?

          Followup:

It is not confusing at all (well it should not be)

There is only one thing that dicates when you commit -- when is your transaction
complete?  Then you commit, period.

Frequently commiting in the mistaken belief that you are conserving resources or
making things go faster only does this for you:

o slows you down, yes, that is right, SLOWS YOU DOWN -- makes you run
SLOOOWWWWEEERRR

o doesn't conserve any resources, in fact, it consumes more (you generate MORE
redo)

o seriously puts into question the integrity of your data

If you want to hear me wail on about this for many pages -- pick up my book
"expert one on one".  The "proofs" to what I say are in there in total.

Visit steve adams website as well
http://www.ixora.com.au/
he talks about this as well

      Rating: 5
Articles
      Verification  August 10, 2002
Reviewer:
      Basharat  from Dubai
Tom, thanks for making it all clear. At the same time I will suggest that
DbaSupport.com must verify the content of articles before publishing them,
otherwise, this site will lose its credibility.

          Followup:

Hah, good luck. (we have somewhat the same issue from time to time on the Oracle
magazine site -- that is exactly how asktom got started actually).

The only way to do what you propose would be to make everyone prove
scientifically their claims -- in a manner similar to what I do here.  When I
say something, I generally try to back it up with some reproducible test case.

For example, in my book -- i have the clear and explicit test cases that shows
(proves) committing frequently

o consumes additional resources
o runs slower
o leads to serious data integrity issues
o is a waste of a developers time

      Rating: 5
One issue
        August 15, 2002
Reviewer:  Arindom Dam
       from Boston MA USA
Your description makes total sense..
However; there's one issue that would like to have your thoughts on ...

What do you propose on a large transaction running out of rollback segment space
? It seems like the only way around is to have some level of commit frequency ?

Thanks.

          Followup:

Nope, I feel (strongly) that your rollback segments should be sized for all of
your day to day transactions.  Period.

If you have a large transaction (keyword = transaction) then by definition
you cannot commit.  Else, by definition, you have lots of little itty bitty
transactions, not a big one.  If data integrity is something you care about and
you want to avoid writing TONS of procedural code -- you will right size your
rollback segments and just do it.

(you know, for those "big" one time transactions, I would just keep a closet
full of 36gig drives, they cost about USD $250.  Just plug one in, use it and
throw it away when you are done.  It'll be more cost effective then writing the
one time procedural code that needs to be restartable (for WHEN it fails) and
not update the same row twice (which typically corrupts the data).

      Rating: 5
More of the
      same ...  August 15, 2002
Reviewer:  Dave
       from Ohio
http://www.dbasupport.com/oracle/ora9i/performance/tuning-9.shtml
"49.GROUP BY and Predicate Clauses" completely misses the point of the HAVING
clause, and is trivial.
"51.Use Explicit Cursors" seems misguided
"53.Table and Index Splitting" is downright wrong

Those articles are riddled with error and urban legend, it seems

          Followup:

51 is my personal favorite.

Just fyi, the author emailed me asking for comments -- I commented on 1-26 and
sent him my feedback point by point.  We'll see what happens with it now.

(49 is a copy of 14 btw more or less)

      Rating:
      5
  August 21, 2002
Reviewer:
      A reader
Tom, Can you please post those commets (about the whole article) so it is
beneficial to everyone. Thanks for the priceless service you are doing to the
Oracle community!

          Followup:

Here is the bottom line:

ignore all the points in that article as it exists now.  I was a little "harsh",
eg: straight forward, perhaps in my review directly to him and do not feel like
cleaning it up for posting here.

I told him he should retract it all right away and then republish it later as
series of articles that debunk many of the "myths" from the tuning books out
there.   He would use the approach of scientific proof.  He has choosen to not
take that path, although he did say he would rewrite and post a corrected
version in 3 or 4 days but that was 5 days ago.

so for now, pretend that series of articles doesn't exist.

      Rating: 4
Confused
      here...  September 16, 2002
Reviewer:  Ik
       from Bangalore
Tom,
    Saw the message posted on your site's front page and thus went through the
threads of discussion.
    On my applications, both PL/SQL and Pro*C, we do a COMMIT after every 1000
records and changes status flag for the processed records. Thus next time when
the program runs (if previous run was aborted due to error), it does not pick up
the processed records.
    Thats the scenario. Do you feel that what we are doing is BAD and that
COMMIT should be only at the end of the process. The number of records to be
processed are in millions.

regards

          Followup:

I might be more in favor of a time based commit (Steve Adams has a write up of
this on his site, commit say every N minutes) rather then N records. 1000 is
sort of small, 100,000 or more would make me feel better.

You have a sort of special case -- you are easily restartable and apparently you
do not get an ora-1555 so you either

a) have code that selects 1000 records, processes, commits and starts over
b) rollback segments big enough that they do not wrap -- meaning you could do
this in one go after all
c) don't query the table you are processing (seems unlikely)
d) have been unusually lucky.

999 times out of 1000 people do not make their stuff restartable and boy do they
get cranky when they hit the "ora-1555"

      Rating: 5
Author Removed
      Pages :)  September 18, 2002
Reviewer:  Dwayne
      Croteau  from Calgary, Canada
Hey Tom,

Mission Accomplished!! - those pages are gone !!

      Rating: 4
Avoiding
      Commits when processing large volumes of data  April 29,
      2003
Reviewer:  Denise  from Virginia, USA

As one who has experienced the dreaded ora-01555 "snapshot
too old" error message when updating large number of rows..
I stopped the practice of committing 'x' number of records
during the Loop-fetch cycle.

I know many say that if Rollback Segments are sized adequately and there are
enough of them then you should
be able to process large numbers of rows(I'm speaking of
millions here...) with no problems.

What has worked for us is I have created one huge massive
Rollback Segment that I keep offline. When I know I have
a large update to run after business hours..I will take
the regular RS offline and switch on the massive RS and
run my script,query or procedure..it works everytime and
I never have a problem completing the task. When the entire
process is done...I COMMIT;

      Rating: 5
****** <-
      six stars for the article  May 23, 2003
Reviewer:
      Yavor Ivanov  from Bulgaria
Why using frequent commits? Just tear-off the transaction processing
capabilities from oracle! No transactions - no overhead. Ha. Ha. Ha.

One of the 3 basics of an transaction is _completeness_. Synonym of _integrity_

      Rating: 5
Confused about
      frequent commits Need your Help!  July 23, 2003

Reviewer:  Alan sinsuan  from GA, USA
Tom,
Here is the situation, I'm doing an insert/select statement that would insert
about 1.5 million records and I keep on hitting cannot extend rollback segment,
but our dba told me to break my statement into a small records and do a commit
on each statement. Below is what he says "
If you  are  really inserting 1.5 million records in one transaction, that is
not recommended. If all these records are going to the same table, then each
record represents a transaction. The concept of a transaction is that, related
information in "different" tables should be managed as a group. When a set of
related records has been inserted then a "commit" should occur to make the
inserts permanent." Can you guide me here? Thank you very much and more power.

          Followup:

ask the DBA -- so what happens when I've inserted 100,000 records and the
process fails.

Now, I've got 100,000 records loaded, 1.4 million more to go -- but tell me --
how pray tell do I restart my transaction?  the source data set is going to want
to give me those 100,000 records again.

You can either spend 1,000's of $$$$ on your time writing tons of procedural
code to try and solve a problem that should not exist OR

the DBA can do theirs and size your system properly.  To bulk load 1.5 million
records should probably be done in this fashion:

a) disable the indexes
b) insert /*+ APPEND */ into the table
c) rebuild the indexes

that will generate no undo of any sizable amount.  If you are in ARCHIVELOG mode
and coordinate with your DBA, you can even set the tables/indexes to nologging,
skipping REDO generation, and then have them schedule a backup.

      Rating: 5
9ir2
      documentation  August 05, 2003
Reviewer:
      bob  from pa
Tom,

In the rollback section of the 9iR2 admin guide (assuming you are still using
rollback), it says:

Approximate Rollback Segment Sizes
with regards to snapshot too old error:

This error occurs because the rollback entries needed for read consistency are
overwritten as other update entries wrap around the rollback segment. Consider
this issue when designing an application's transactions, and make them short
atomic units of work so that you can avoid this problem.
-----
Doesn't this "short atomic unit of work" hint at the commit frequently mentally?
 Should this instead say, "when the transaction is done".. which isn't unclear
at all.

That same section discusses the "use different rbs for different types of
transactions" scenario.

When database systems applications concurrently issue a mix of very short and
very long transactions, performance can be optimized if transactions are
explicitly assigned to a rollback segment based on the transaction/rollback
segment size. You can minimize dynamic extent allocation and truncation for
rollback segments. This is not required for most systems and is intended for
extremely large or small transactions.

To optimize performance when issuing a mix of extremely small and large
transactions, make a number of rollback segments of appropriate size for each
type of transaction (such as small, medium, and large). Most rollback segments
should correspond to the typical transactions, with a fewer number of rollback
segments for the atypical transactions. Then set OPTIMAL for each such rollback
segment so that the rollback segment returns to its intended size if it has to
grow.

You should tell users about the different sets of rollback segments that
correspond to the different types of transactions.
**Often, it is not beneficial to assign a transaction explicitly to a specific
rollback segment.**

However, you can assign an atypical transaction to an appropriate rollback
segment created for such transactions. For example, you can assign a transaction
that contains a large batch job to a large rollback segment.

          Followup:

...
Consider
this issue when designing an application's transactions, and make them short
atomic units of work so that you can avoid this problem.
.......

i would consider that a "bug".  Not that it is wrong for a transaction should
be:

a) as LONG as it needs to be
b) as SHORT as it can

that is -- the transaction defines its boundaries and NOTHING else.  not
rollback, nothing.  a transaction is a transaction and should not be
artificially extended or shortened.

But to say that short transactions will solve this problem isn't 100% accurate
-- unless you make the QUERIES themselves short and I think that is what they
meant.  "Transaction" includes "read"

      Rating: 4
It all depends
      on the required rollback behaviour...  August 06, 2003

Reviewer:  Manohar  from Sydney, Australia

Tom,
I think, when doing bulk updates, the required rollback behaviour is also
important in defining "Transaction".
different scenarios:
1)insert into A select * from B; (Either do the whole thing or rollback
completely)
2)for c in select * from B loop
   Begin  insert into a (1,2..) values (c.1,c.2..);
   exception when others then null
   end;--(continue with inserting other records..)
3)We cal also use Bulk insert if thats the desired rollback behaviour.
What i wanted to say is that we rollback behaviour also affects how we define
transaction.

August 05, 2003 Sandy -- Thanks for the question regarding
      "Oracle For UNIX and NT", version 8.1.5

      You Asked
Hi Tom,

I want to know:

Question: How ORACLE works internally for any INSERT, UPDATE or DELETE
transaction. I know you are going to tell me the path of any TECHNET document.
But I have gone through with documentation and understood the functionality of
all BG processes and memory structure. Basically I am trying to understand how
this all BG processes and memory structure work togeather and coordinate with
each other. What is the sequence of different events and processes for any
INSERT, UPDATE or DELETE transaction. Also let us know if you have any brief
diagram, which explain the sequence of events during any INSERT, UPDATE or
DELETE transactions.

Question: I am working in UNIX environment for ORACLE 8i but very soon I'll be
providing production support in NT/2000 environment also. I want to know, what
all I need to know in NT, how different it is from UNIX (In term of Memory
structure, processes, threads etc.)  What are the major difference between
ORACLE 8i on UNIX and ORACLE 8i on NT/2000.

Regards,
Sandy

      and we said...
I'll recommend my book actually, I work through this using an
insert/update/delete in chapter 4 on transactions.

bookpool.com has the lowest price right now:

http://www.bookpool.com/.x/igoop9ti2r/sm/1861004826
In a nutshell (in general, oversimplified) -- your server process will read or
find in the buffer cache the row to be modified.

It will modify it in the cache.  This will generate rollback (undo) blocks and
redo blocks.

You will commit.  LGWR will flush your redo buffers.

Later, DBWR will write your modified buffers.

There are no differences between Oracle8i on Unix and Oracle8i in NT/2000.  You
need (as a DBA) to understand the differences between the operating systems so
you can tune IO and so on but the setup is the same.

I admin both -- other then having to use \ on NT and / on unix, they are pretty
much the same.
Review & Followup

      Rating:
      4
  August 14, 2001
Reviewer:  bobjee
       from IND
I bought the book in bookpool from 39 bucks, in barnesandnoble , including taxes
the price is 64 dollars.

      Rating: 4
Are there any
      major differences for Oracle on NT vs Unix?  March 24, 2003

Reviewer:  A reader
Hi Tom,
Apart from strategic decision etc. etc., if I have to suggest Oracle on NT or
Unix. What are your suggestions and why?
Thanks

          Followup:

because UNIX is cooler...

Nah -- it comes down to this -- what do you have the skills set for?  what does
your company run on?  what do you want/need to use?

Those drive it.  Oracle is Oracle is Oracle -- mainframe to linux on mainframe
to linux on whatever to unix to windows.  Oracle is Oracle is Oracle....

      Rating: 4
What about
      these...?  March 24, 2003
Reviewer:
      A reader
Thanks for your quick response.
But what about reliability & scalability in relation to the OS, as I came to
know that windows machines are not as reliable and scalable as compared to unix
machines.

          Followup:

Here is my opinion on that.

In the proper hands, either of windows, unix, mainframes whatever works.

Windows is its own worst enemy here as it promotes the "hey, this is cake --
don't worry -- be happy -- you don't have to learn anything hard to make this
work".  So it lulls people into a false sense of "I know what I'm doing".  The
"practices" I see people using on windows are scary.  Why are databases
considered "less reliable" on windows?  Is it because backup and recovery
doesn't work there?  No, it is because the mindset is "easy, take it easy".
That means -- no backups, no archivelog mode, export every now and again -- etc.
 Then, when disaster strikes, these people are toast.  Not only do they not have
a backup -- they have no idea where to begin, what they can do - there is no gui
button to press.

In my opinion -- it is all about the people running the system and people
running systems on unix/mainframes just seem to take some things more seriously.
 Don't get me wrong -- there are top notch windows implementation teams out
there as well, but since it is so "easy", so "toy like" -- so many people plug
it in, turn it on and never look back.  UNTIL that disk crash, until that
failure.  Then, well, they go out of business.

There are unix/linux teams that'll be in the same boat -- it is just that
windows attracts this sort of behavior like flies to honey -- and amusingly
enough MS pushes this "hey, its easy, set up a datacenter tomorrow" sort of
idea.

Well maintained, professionally managed windows boxes can stay up just as long
as anything.  The problem is with those first two adjectives.

      Rating: 5
threads vs.
      processes  March 25, 2003
Reviewer:
      A reader
One "major" internal difference might be that on NT, there is one single process
running as oracle.exe, and those background and server processes are running as
"threads". On Unix, they are real processes at the OS level. You can kill an
individual process, but cannot kill a specific thread.

I would use unix-based system for anything that's intended to be a "server".

          Followup:

you can kill a thread.  orakill is provided for that.

      Rating: 3
threads vs.
      processes ?  March 25, 2003
Reviewer:  Tim
      Hall  from UK
You can kill individual threads under Windows NT/2000 using orakill.exe.

I read an article that suggested that Oracle10i on UNIX will also be
multi-threaded rather than multi-process. It said that the rewrite of Oracle9i
for Windows showed that the multi-threaded approach was more efficient and safer
and that these benefits could be transfered to UNIX versions. Don't know if this
is true or not.

I currently run Oracle on Tru64, Windows 2000, Windows NT and RedHat Linux.
Until recently I was also using Solaris and HP-UX also. Oracle itself has been
consistent on all of these operating systems.

Cheers

Tim...

      Rating:
      5
Orakill.exe  March 25, 2003
Reviewer:
      Oskar  from Spain
This util can be used to kill a specific thread ;p

      Rating: 3
threads vs.
      processes ?  March 25, 2003
Reviewer:  Tim
      Hall  from UK
You can also do:

Start -> Programs -> "Oracle - OraHome" -> "Configuration and Migration tools"
-> "Admiinistration Assistant for Windows NT"

This will start up a gui that lets you look at the threads and kill them if you
need to.

Cheers

Tim...

      Rating: 4
Shut down a
      Win2000 server  March 25, 2003
Reviewer:
      Bogie  from Atlanta
If I shut down my Win2000 server - what mode does the Oracle database shut down
in?  Normal - Immediate - Abort?

Is there any way to control this or should one first shut down the instances and
then shut down the server?

Thx,

Bogie

          Followup:

depends on how you configured it using oradim.  check out the admin guide for
windows for how to use oradim.

At the end of the day, shutdown normal, immediate, abort -- we'll startup from
each.  Takes longer to startup after an abort -- but then, it takes longer to
shutdown immediate so you pay the price sometime.

      Rating: 5
Unix and NT
      environment  April 18, 2003
Reviewer:
      Suresh  from India
Hi Tom,

  Sorry, I am not sure if I can post this question here...as your site is always
busy whenever I try to post this as a new question.

   We are developing our application in Oracle Forms and PL/SQL in NT
environment...but the implementation will be in UNIX environment. Since we do
not have an UNIX box now, (we will be getting one in a few months time) I want
your opinion if there will be any surprises when we implement this application
in UNIX even though it is developed in NT.

If possible, please point to any URL that has this list of
things that will be potential problem areas when we develop in NT environment
and implement it in UNIX environment.

Thanks.

          Followup:

Not a forms guy myself -- haven't used it since this web thing came along with
HTML (1995 was the last time for me)

I'll refer you to otn.oracle.com -> discussion forums -> developer... there you
will find people who use that tool every day.

      Rating: 3
How much unix
      do i need....  June 06, 2003
Reviewer:
      Alvin  from Philippines.
My problem is the reciprocal of the post earlier. I'm quite limited when it
comes to unix (user level).... how much unix should a DBA know to effectively be
a DBA on Unix platform ?

I haven't installed Oracle on a Unix env. yet.

Any good books ?

          Followup:

if a DBA works with a compentent SA, very little knowledge is necessary as the
SA will do the real OS work and you'll just have file systems you are allowed to
use.  So, rather then remembering that you have F: G: H: and other letters of
the alphabet at your disposal, you'll remember you have /d01 /d02 /d03 and so
on.

The SA will take the OS Admin/Install guide and make sure the unix kernel
parameters are set sufficiently as well.

If on the other hand, you are expected to install the OS, configure the OS,
build the file systems and all -- you'll need quite a bit of experience.

So, somewhere between

o very little
o all of it

The OS Install Guide would make an excellent document to read from cover to
cover, yes.

      Rating: 1
link posted
      from first reply  June 06, 2003
Reviewer:
      Alvin  from Philippines
Bookpool doesn't have your book anymore. Kindly posts the title. Thanks.

          Followup:

bummer, sold out.  publisher went out of business.

book is no more.

      Rating:
      5
  June 06, 2003
Reviewer:
      A reader
Hi Tom,

 In your last response you said you pick oracle press as your publisher. BUT
There books quality are very low and stincks. Your last book's  (expert one..)
quality is great. Good paper and so on...

 How would be your next one(book) ?. Is it different OR Same as usual for oracle
press....

Thanks.

          Followup:

funny, I got complaints about the last book with the font being too small and
the pages too thin.

It'll be the "regular" Oracle Press book.

      Rating: 5
Amazon.Com
      still has it...  June 06, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
http://www.amazon.com/exec/obidos/ASIN/1861004826/qid=1054903707/sr=11-1/ref=sr_11_1/104-2330960-1201540

          Followup:

I'm afraid they are on infinite backorder.

There were about 21,000 copies printed.  The printing press is turned off....

      Rating:
      3
Woohoo  June 06, 2003
Reviewer:
      Adrian  from UK
Looks like my book has become an investment. It'll soon be priceless.

      Rating:
      4
books  July 25, 2003
Reviewer:
      Junior  from va, USA
Hi, Tom:

Can you recommand a book which lead me to install Oracle on UNIX (Any plateform,
but prefer AIX), and create database step by step. If there is screen shut, it
will be better.
Any books in your mind?
Or when I issue dbca command in unix, will it brings up
X window? And the following steps will be exactly same as I create database in
Windows by using Database Configure Assistance?
Thank you for the answer.

          Followup:

well, the "installation guides" pop into mind.

dbca is dbca is dbca -- if you've used it on platform "A", it'll be the same on
platform "B"

      Rating: 5
thread vs
      session id  July 26, 2003
Reviewer:  Reader

on NT can I get the thread id as related to session ID using sql query to kill a
thread at the os level? Thanks.

          Followup:

should be spid in v$process

use orakill (supplied tool)

      Rating: 5
dual os with
      same oracle version  August 05, 2003
Reviewer:
      Junior  from va, usa
Tom:
Question again. I am using windows XP with Oracle 9i now. I am trying to install
Linux with another Oracle 9i again.
Can I insatll Oracle 9i for Linux on the same machine? Or should I deinstall
Oracle 9i for XP and reinsatll Oracle 9i for Linux? Thank you for the answer.

          Followup:

they will be on separate disk slices and not even be a tiny bit aware of
eachother.  yes, you can install both.

      Rating: 5
Win vs Linux
        August 30, 2003
Reviewer:  Sikandar Hayat
       from Pindi Bhattian - Pakistan
A nice discussion "Y use Linux instead of windows" is available at the following
link,

http://www.linuxquestions.org/questions/showthread.php?s=&threadid=20679

      Rating: 5
Microsoft Is
      Using Linux To Protect Its Own Web Site  September 01, 2003

Reviewer:  Sikandar Hayat  from Pindi Bhattian -
      Pakistan
http://story.news.yahoo.com/news?tmpl=story&cid=74&ncid=738&e=9&u=/cmp/20030821/tc_cmp/13100775

August 05, 2003 jennifer -- Thanks for the question regarding
      "Using VARRAY Datatypes in User-Defined Aggregate Functions.", version 9.2

      You Asked
Tom,

I am trying to write an aggregate function to calculate the median for a set of
numbers.  (I know that oracle provides aggregate functions that will calculate
the median for me, but due to client requirements, I had to write my own).
Because this function could have to handle unknown and large number of
inputs(100,000 or more), i want to put the all the input values into a VARRAY
first then calculate the median.  But, I am having problems initializing the
VARRAY in the odciinitialize block.  How do i do this?

-- i declared a VARRAY type first

TYPE numVarray AS VARRAY(1) OF NUMBER;

-- This is the getMedianRoutines type i wrote

CREATE OR REPLACE TYPE getMedianRoutines as object (
   x numVarray,

   STATIC FUNCTION ODCIAggregateInitialize(sctx IN OUT getMedianRoutines)
      RETURN NUMBER,
   MEMBER FUNCTION ODCIAggregateIterate(self IN OUT getMedianRoutines,
         value IN NUMBER) RETURN NUMBER,
   MEMBER FUNCTION ODCIAggregateMERGE(self IN OUT getMedianRoutines,
      ctx2 IN getMedianRoutines) return number,
   MEMBER FUNCTION ODCIAggregateTerminate(self IN getMedianRoutines,
      returnValue OUT NUMBER, flags IN NUMBER) RETURN NUMBER
)

/
CREATE OR REPLACE Type Body getMedianRoutines Is
Static Function Odciaggregateinitialize(Sctx In Out getMedianRoutines)
Return Number Is
Begin
  Sctx := getMedianRoutines( numVarray() ); -- here i TRY to initialize the x
numVARRAY variable
  Return Odciconst.Success;
End;

Member Function Odciaggregateiterate(Self In Out getMedianRoutines, Value In
Number)
Return Number Is
  i number := 1;
Begin
  x.extend;  -- i want to populate the x numVarray here
  x(i) := value;
  i := i+1;
  Return Odciconst.Success;
End;

member function ODCIAggregateMerge(self IN OUT getMedianRoutines,
   ctx2 IN getMedianRoutines) return number is
begin
   null;

  return ODCIConst.Success;
End;

Member Function Odciaggregateterminate(Self In getMedianRoutines, Returnvalue
Out
NUMBER, Flags In Number) Return Number Is

Begin
 -- I want the x numVarray to be available here and do calculation on it.
 -- bunch of code to calculate the median
End;
End;
/

CREATE OR REPLACE FUNCTION getMedian(input number )
RETURN NUMBER AGGREGATE USING getMedianRoutines;
/

and now when i try to use the function, i get these errors...

ORA-06531: Reference to uninitialized collection
ORA-06512: at "GETMEDIANROUTINES", line 14
ORA-06512: at line 1

      and we said...
You cannot tell me "that due to our requirments I have to write my own"

I don't buy it.

The reason for "doing it yourself" would be in the ever elusive and futile
attempt to be "database independent".  But the problem there is the solution
above is as Oracle specific as you can get.

Therefore, I will only tell you the right way to do it.

ops$tkyte@ORA920> select owner, percentile_cont(0.5) within group (order by
object_id) med_object_id
  2    from all_objects
  3   group by owner;

OWNER                          MED_OBJECT_ID
------------------------------ -------------
A                                      36595
AQ                                   37470.5
BIG_TABLE                            36322.5
CSMIG                                  36619
CTXSYS                                 26581
DEMO                                   36214
HR                                   29824.5
LBACSYS                                28242
MDSYS                                  26247
ODM                                    28622
ODM_MTR                              28863.5
OE                                   29894.5
OLAPSYS                                29448
OPS$ORA920                             34607
OPS$TKYTE                            37893.5
ORDPLUGINS                             26010
ORDSYS                               24814.5
OUTLN                                    439
PERFSTAT                             36510.5
PM                                     29964
PUBLIC                                 14773
QS                                     30206
QS_ADM                                 30184
QS_CBADM                             30382.5
QS_CS                                  30410
QS_ES                                  30255
QS_OS                                  30345
QS_WS                                  30300
SCOTT                                  36455
SH                                   30074.5
SYS                                    11821
SYSTEM                                4414.5
TRACE_FILES                            35786
WKSYS                                  27897
WMSYS                                   6054
WSMGMT                                 33055
XDB                                  27347.5

37 rows selected.

and there you go, hours of custom coding solved in milliseconds!

I refuse to show you how to do this

o slow
o with tons of memory
o and lots of code

the fix to the code is simple -- you just have to check for nullness of the
collection, set to empty.  But after that you have SORTING to do (yes, you gotta
sort the data) then some math to figure out the median value.  So, you haven't
even written 1/2 the code yet

So, stop and do the right thing.  I cannot imagine a client that would say "Oh
no, don't use anything builtin, write it all yourself.  I'll gladly pay you $XXX
an hour to design, code, debug and maintain this for me"....

Review &
      Followup

      Rating: 5
The Best As
      Usual  January 31, 2003
Reviewer:  Sajid
      Anwar  from London
Hi Tom,

Could you please explain the ODCIAggregateInitialize, ODCIAggregateIterate,
ODCIAggregateMERGE and ODCIAggregateTerminate in the question above? This is the
first time i have ever seen something like that. So i am totally unaware about
it. Could you please explain where it is useful? I would be grateful to you if
you can point me to the Oracle Documentation.

Regards,
Sajid Anwar
London

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2196162600402
for an example. they are used to build user defined aggregates.

see

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96590/adg10pck.htm#36206
and

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96595/dci11agg.htm#1004572

      Rating: 4
Follow
      up.  January 31, 2003
Reviewer:  Jennifer
       from san ramon
Tom,
The percentile_count() function was actually my first proposed solution to the
problem.  But, unfortunately I couldn't convince the client to use it. (Though I
would have love to be able to use it.)  I have actually written all the code to
calculate the median for me.  I got around the unable to initialize the VARRAY
problem by putting all inputs into a string (in the initialization routine),
pass it to the ODCIterminateroutine where it's parsed and put into a pl/sql
table.  Unfortunately, I can only process ~7,200 threed digit numbers using this
method.  And, yes it is slow.  Here's the result of my quick and dirty test ...
Time(sec): 128.76 # of Entry: 7236.

But, could you still show me how to use a VARRAY datatype in a user-defined
aggregate function?  Hopefully, I can convince my clients to use the
percentile_cont() function.  But if not... also, just for future reference.

Thank you,

          Followup:

why why why -- I'll be blunt -- how stupid is this client.

why do they need convincing of anything?

I hope you charge them alot.

customer: i want to compute a median
jennifer: here is how to do it, use percentile_cont()
customer: no, that works too fast and doesn't cost me anything.. I'd rather pay
          you to rewrite that functionality.

well, here is a working implementation -- not much sense in using a VARRAY.  It
does have a limitation that if the nested table gets too large, it'll blow with
an ORA-22813 -- but it "works".

Its just really really slow...

ops$tkyte@ORA920> create or replace type numArray as table of number
  2  /
create or replace type numArray as table of number
*
ERROR at line 1:
ORA-02303: cannot drop or replace a type with type or table dependents

ops$tkyte@ORA920>
ops$tkyte@ORA920> CREATE OR REPLACE TYPE getMedianRoutines as object
  2  (
  3     x numArray,
  4
  5     STATIC FUNCTION ODCIAggregateInitialize(sctx IN OUT getMedianRoutines)
  6        RETURN NUMBER,
  7     MEMBER FUNCTION ODCIAggregateIterate(self IN OUT getMedianRoutines,
  8           value IN NUMBER) RETURN NUMBER,
  9     MEMBER FUNCTION ODCIAggregateMERGE(self IN OUT getMedianRoutines,
 10        ctx2 IN getMedianRoutines) return number,
 11     MEMBER FUNCTION ODCIAggregateTerminate(self IN getMedianRoutines,
 12        returnValue OUT NUMBER, flags IN NUMBER) RETURN NUMBER
 13  )
 14
 15  /

Type created.

ops$tkyte@ORA920> CREATE OR REPLACE Type Body getMedianRoutines
  2  Is
  3
  4  Static Function Odciaggregateinitialize(Sctx In Out getMedianRoutines)
  5  Return Number
  6  Is
  7  Begin
  8    Sctx := getMedianRoutines( numArray() );
  9    Return Odciconst.Success;
 10  End;
 11
 12  Member Function Odciaggregateiterate(Self In Out getMedianRoutines, Value
In Number)
 13  Return Number
 14  Is
 15  Begin
 16    x.extend;
 17    x(x.count) := value;
 18    Return Odciconst.Success;
 19  End;
 20
 21  member function ODCIAggregateMerge(self IN OUT getMedianRoutines,
 22                                     ctx2 IN getMedianRoutines) return number
is
 23  begin
 24          for i in 1 .. ctx2.x.count
 25          loop
 26                  self.x.extend;
 27                  self.x(self.x.count) := ctx2.x(i);
 28          end loop;
 29      return ODCIConst.Success;
 30  End;
 31
 32  Member Function Odciaggregateterminate(Self In getMedianRoutines,
 33    Returnvalue Out NUMBER, Flags In Number) Return Number
 34  Is
 35    l_med number;
 36  Begin
 37   select percentile_cont(0.5) within group ( order by column_value )
 38     into l_med
 39    from TABLE( self.x );
 40   returnValue := l_med;
 41   return ODCIConst.Success;
 42  End;
 43
 44  End;
 45  /

Type body created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> CREATE OR REPLACE FUNCTION getMedian(input number )
  2  RETURN NUMBER AGGREGATE USING getMedianRoutines;
  3  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA920> select object_type, count(*),
  2         percentile_cont(0.5) within group (order by object_id) med
  3    from all_objects
  4   where owner not in ( 'SYS', 'SYSTEM' )
  5     and object_type <> 'SYNONYM'
  6   group by object_type
  7  /

OBJECT_TYPE          COUNT(*)        MED
------------------ ---------- ----------
CLUSTER                     1      38344
EVALUATION CONTEXT         10      30295
FUNCTION                   98      26396
INDEX                     562    29166.5
INDEX PARTITION           108    30088.5
INDEXTYPE                   9      26753
JAVA CLASS               1168    24986.5
JAVA RESOURCE              17      24569
JAVA SOURCE                 9      25961
LIBRARY                    37      27348
MATERIALIZED VIEW           3      30163
OPERATOR                   27      26144
PACKAGE                   218      27323
PACKAGE BODY              192      27385
PROCEDURE                  27      29838
RULE SET                   15      30362
SEQUENCE                   64      27930
TABLE                     486      29410
TABLE PARTITION            48    30020.5
TRIGGER                    89      28803
TYPE                      300    27522.5
TYPE BODY                  39      26330
VIEW                      508    29248.5

23 rows selected.

ops$tkyte@ORA920> select object_type, count(*),
  2         getMedian(object_id) med2
  3    from all_objects
  4   where owner not in ( 'SYS', 'SYSTEM' )
  5     and object_type <> 'SYNONYM'
  6   group by object_type
  7  /

OBJECT_TYPE          COUNT(*)       MED2
------------------ ---------- ----------
CLUSTER                     1      38344
EVALUATION CONTEXT         10      30295
FUNCTION                   98      26396
INDEX                     562    29166.5
INDEX PARTITION           108    30088.5
INDEXTYPE                   9      26753
JAVA CLASS               1168    24986.5
JAVA RESOURCE              17      24569
JAVA SOURCE                 9      25961
LIBRARY                    37      27348
MATERIALIZED VIEW           3      30163
OPERATOR                   27      26144
PACKAGE                   218      27323
PACKAGE BODY              192      27385
PROCEDURE                  27      29838
RULE SET                   15      30362
SEQUENCE                   64      27930
TABLE                     486      29410
TABLE PARTITION            48    30020.5
TRIGGER                    89      28803
TYPE                      300    27522.5
TYPE BODY                  39      26330
VIEW                      508    29248.5

23 rows selected.

maybe you can use this TKPROF and convince them -- "hey customer, either buy
5x the CPU you have now or else"

select object_type, count(*),
       percentile_cont(0.5) within group (order by object_id) med
  from all_objects
 where owner not in ( 'SYS', 'SYSTEM' )
   and object_type <> 'SYNONYM'
 group by object_type

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        3      0.73       0.85          0      55544          0          23
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      0.73       0.85          0      55544          0          23

********************************************************************************

select object_type, count(*),
       getMedian(object_id) med2
  from all_objects
 where owner not in ( 'SYS', 'SYSTEM' )
   and object_type <> 'SYNONYM'
 group by object_type

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.04       0.09          0        120          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        3      4.47       4.53          0      55544          0          23
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      4.52       4.63          0      55664          0          23

Had to put that synonym check in there to avoid blowing on the sorts big time.
Your own procedural median might not suffer from that....

      Rating: 5
Excellent
      answer, but alas  January 31, 2003
Reviewer:
      Jim  from Portland, OR USA
Tom,
Unfortunately, there really are client (or managers) this silly.  Usually it
stems from a desire to use a database different from Oracle and thus their
"requirement" (read hidden agenda) is the following:
1. Prove development on Oracle is expensive and difficult.
2. Make sure all code in Oracle is "database neutral" (read: portable to the
database they would rather you use, which is NOT Oracle.)
3. Put as much of the porting cost to another database and attribute it to
Oracle development thus further "proving" point 1.

Is it unethical?  I look at it as stealing or at least misappropriating company
resources that are not truly in the best interests of the company.  But to some
people the ends (get the database technology they want - someone else probably
chose it and thus it is a "legacy" system.) justify the means.

Thanks for giving good reasons for solving the problem correctly.

      Rating: 5
how about in
      8.0.6  August 04, 2003
Reviewer:  david
       from USA
any way to calculate the median?

          Followup:

here is the ansi way:

SELECT deptno, AVG(DISTINCT sal)
  FROM  (SELECT cp1.deptno, CP1.sal
           FROM emp CP1, emp CP2
          where cp1.deptno = cp2.deptno
          GROUP BY cp1.deptno, CP1.sal
         HAVING SUM(DECODE(CP1.sal, CP2.sal, 1, 0)) >=
                        ABS(SUM(SIGN(CP1.sal - CP2.sal))))
 group by deptno
/

it won't scale so well though :)

      Rating:
      4
decode  August 05, 2003
Reviewer:
      mikito harakiri
Here I am starring into decode function, and yet again forced to consult to the
SQL Reference. I guess there is a reason why they made their example indented:

       DECODE (warehouse_id, 1, 'Southlake',
                             2, 'San Francisco',
                             3, 'New Jersey',
                             4, 'Seattle',
                                'Non-domestic')

Oracle, please deprecate "decode"!

          Followup:

I don't know why you'd have to reference the docs "again"

it is a pretty simple function in my opinion

August 05, 2003 Hemi -- Thanks for the question regarding
      "Database Locks", version Oracle 805

      You Asked
Hi,
Let me try and re-phrase my question:
I am using the follwing code in order not to wait forever for a
locked row:

begin
   select 'X'
     into l_rec
   from T
    where <some_condition>
      FOR UPDATE NOWAIT;

   update T set .....
    where <some_condition>;
...

- the first select should raise an error (ora-00054) when there
is a lock, this exception being handled later.

We are having some problems with database locks and we are
suspecting that this code is waiting forever to the lock instead
of rasing the exception.
I have found a similar code (almost) for the same purpose (not
wait forever for a locked row) in one of your answers

(
http://osi.oracle.com/wa/ask/owa/ask_tom_pkg.display?p_dispid=549822768278
)
The only different in our code is that i am selecting a constant
[ select 'X' from T]and you are actually retrieving values from
the table [select * from T]
My question is if it is possible that retriving a constant from
the table and not actual values not raising the exception
(ora-00054)

The code from your answer :

declare
   l_rec    T%rowtype;
begin
   select *
     into l_rec
   from T
    where <some_condition>
      FOR UPDATE NOWAIT;

   update T set .....
    where <some_condition>;

I am using a similar code (almost) :

begin
   select 'X'
     into l_rec
   from T
...

Hope i was clear :)
Thanks,
Hemi

      and we said...
No, it won't bypass the ORA-54.  This is probably an unindexed
foriegn key.

Suggestion:

o run catblock.sql found in $ORACLE_HOME/rdbms/admin using
SVRMGRL when connected as SYS or INTERNAL

o when you suspect a block has happened -- run @utllockt using
the same tool/account as above.  It'll print out a
blocker/waiter graph.  You can use showsql (below) to see who is
running what SQL.

Another suggestion:

o get the utility "who_am_i" from

http://osi.oracle.com/~tkyte/who_called_me/index.html
and modify the who_am_i function to be this:

create or replace function who_am_i return varchar2
is
    l_owner        varchar2(30);
    l_name      varchar2(30);
    l_lineno    number;
    l_type      varchar2(30);
begin
   who_called_me( l_owner, l_name, l_lineno, l_type );
   return l_owner || '.' || l_name || ' (' || l_lineno || ')';
end;
/

and put lots of calls to

   dbms_application_info.set_client_info( who_am_i );

when you run showsql, it'll show you the owner.procedure
(lineno) in the client_info.  You can see what line they are
"stuck" on.

There are *rare* cases where a select for update nowait followed
by an update will block.  They result from UNINDEXED foreign
keys.  To see this in action, run this in one sqlplus session
and keep that session open:

--------------------------------------------------
drop table emp;
drop table dept;

create table emp as select * from scott.emp;
create table dept as select * from scott.dept;

alter table dept add constraint dept_pk primary key(deptno);
alter table emp add constraint emp_fk_dept foreign key (deptno)
references dept;

update dept set deptno = 41 where deptno = 40;
--------------------------------------------------

and in another sqlplus session run this:

---------------------------------------------------
select 'x' from emp where empno = 7370 for update nowait;
update emp set ename = lower(ename) where empno = 7370;
---------------------------------------------------

The update will block -- the first script has a share TABLE lock
which does not prevent the FOR UPDATE but does prevent the
UPDATE.  See

http://osi.oracle.com/~tkyte/unindex/index.html
for the reason and a script to help ID those unindexed foreign
keys.

-------- sqlplus script showsql -----------------------------
column status format a10
set feedback off
set serveroutput on

select username, sid, serial#, process, status
from v$session
where username is not null
/

column username format a20
column sql_text format a55 word_wrapped

set serveroutput on size 1000000
declare
    x number;
begin
    for x in
    ( select username||'('||sid||','||serial#||
                ') ospid = ' ||  process ||
                ' program = ' || program username,
             to_char(LOGON_TIME,' Day HH24:MI') logon_time,
             to_char(sysdate,' Day HH24:MI') current_time,
             sql_address, LAST_CALL_ET
        from v$session
       where status = 'ACTIVE'
         and rawtohex(sql_address) <> '00'
         and username is not null order by last_call_et )
    loop
        for y in ( select max(decode(piece,0,sql_text,null)) ||
                          max(decode(piece,1,sql_text,null)) ||
                          max(decode(piece,2,sql_text,null)) ||
                          max(decode(piece,3,sql_text,null))
                               sql_text
                     from v$sqltext_with_newlines
                    where address = x.sql_address
                      and piece < 4)
        loop
            if ( y.sql_text not like '%listener.get_cmd%' and
                 y.sql_text not like '%RAWTOHEX(SQL_ADDRESS)%')
            then
                dbms_output.put_line( '--------------------' );
                dbms_output.put_line( x.username );
                dbms_output.put_line( x.logon_time || ' ' ||
                                      x.current_time||
                                      ' last et = ' ||
                                      x.LAST_CALL_ET);
                dbms_output.put_line(
                          substr( y.sql_text, 1, 250 ) );
            end if;
        end loop;
    end loop;
end;
/

column username format a15 word_wrapped
column module format a15 word_wrapped
column action format a15 word_wrapped
column client_info format a30 word_wrapped

select username||'('||sid||','||serial#||')' username,
       module,
       action,
       client_info
from v$session
where module||action||client_info is not null;

Review &
      Followup

      Rating: 5
error creating
      a column  August 05, 2003
Reviewer:
      A reader
Tom,
I have been trying to add a column to a database table for a couple of hours now
(Oracle error message solution - Action : Try the command after a few minutes
....) and each time I get the following error.

SQL> alter table schemaA.Table1 add (description_clob clob );
alter table schemaA.Table1 add (description_clob clob )
*
ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified

SQL>

Any suggestions on why this is happenning.  The table Table1 has 2 columns of
width varchar2(4000) and one column of the datatype blob.  I am not sure if that
is any reason at all for this error to occur.  The DBA says that there are no
locks on this table.

Thank you

          Followup:

DBA is "mistaken"

ask them to look at dba_ddl_locks (if that does not exist, ask them to run
catblock.sql and try again)

      Rating: 5
error creating
      a column   August 05, 2003
Reviewer:
      A reader
Thank you


Subject | Article | Last Updated (East coast time)
unix system kernel parameters | article only | Aug 06, 2003 07:40 am
Directory List including modify date and times - casting the date problem | article only | Aug 06, 2003 07:46 am
SQL PLAN OUTPUT IN STORED PROCEDURE | article only | Aug 06, 2003 07:53 am
Client connection | article only | Aug 06, 2003 08:15 am
Dynamic SQL: Set variable contained in another variable | article only | Aug 06, 2003 08:23 am
Java - Oracle rowcount | article only | Aug 09, 2003 11:47 am
Retrieving Data from BLOB into a file. | article only | Aug 09, 2003 12:07 pm
Size of Development | article only | Aug 09, 2003 12:16 pm
How could I call dll from PL/SQL or Oracle Developer | article only | Aug 09, 2003 04:24 pm
large pool value | article only | Aug 09, 2003 04:42 pm
How to find out if a database exists in a given system | article only | Aug 09, 2003 04:51 pm
high water mark | article only | Aug 10, 2003 10:55 am
Buffer Cache Hit Ratio | article only | Aug 10, 2003 11:00 am
dbms_output.enable does not work | article only | Aug 10, 2003 11:02 am
Populating ID columns via Sequence | article only | Aug 10, 2003 11:11 am
Database not responding | article only | Aug 10, 2003 11:18 am
Overheads on RENAME | article only | Aug 10, 2003 11:42 am
How important is using a separate database for a RMAN recovery catalog | article only | Aug 10, 2003 11:55 am
data conversion | article only | Aug 10, 2003 12:03 pm
The Performance Tuning Process | article only | Aug 10, 2003 12:12 pm
Bind variables | article only | Aug 10, 2003 08:44 pm
clob datatype | article only | Aug 11, 2003 07:19 am
Doubt in For Update clause | article only | Aug 11, 2003 07:29 am
A way to index only null values | article only | Aug 11, 2003 07:38 am
Index rebuild | article only | Aug 11, 2003 07:45 am
pivot a result set | article only | Aug 11, 2003 09:59 am
ora-00604,ora-01578,ora-01110 | article only | Aug 11, 2003 10:01 am
Bind Variables | article only | Aug 11, 2003 11:21 am
move system datafiles | article only | Aug 11, 2003 11:22 am
Use Of DBMS_SQL in D2K | article only | Aug 11, 2003 01:54 pm
snapshots | article only | Aug 11, 2003 06:50 pm
BULK COLLECT INTO OBJECT? | article only | Aug 11, 2003 06:58 pm
O/S of the database? | article only | Aug 11, 2003 06:58 pm
Table access. | article only | Aug 12, 2003 08:12 am
log sequence number - why is it in the datafile header | article only | Aug 12, 2003 08:38 am
PRODUCT_USER_PROFILE question | article only | Aug 12, 2003 08:52 am
Temporary tables | article only | Aug 12, 2003 10:25 am
HTMLDB-Own Custom Packaged Procedures or built in feature | article only | Aug 12, 2003 12:04 pm
row(s) 1 - 38 of 38
August 06, 2003 sing -- Thanks for the question regarding "unix system kernel parameters", version 8.0.6
You Asked
Tom,

a.    db1 – concurrent users + processes = 410
b.    db2 – concurrents users + processes = 50
c.    db3 – concurrents users + processes = 50

The above three databases are going to be housed on one server.

What is the best way to calculate the kernel parameters based on
the above information ? Is there a formula for each of the
kernel parameters? I see the below values in the preinstallation
documentation but they do not specify the formula? Can you
please help me with the formula?

SHMMAX 4294967295
SHMMIN 1
SHMMNI 100
SHMSEG 10
SEMMNS 200
SEMMNI 70
SEMMSL Equal to the value of the PROCESSESinitialization
parameter.

Thanks,
Sing
and we said...

SHMMAX = The maximum size(in bytes) of a single shared memory
         segment.
SHMMIN = The minimum size(in bytes) of a single shared memory
         segment.

The above settings let the max be bigger then we wil ever use
and the min much smaller.  They are sufficient for pretty much
all systems.

SHMMNI = The number of shared memory identifiers.
SHMSEG = The maximum number of shared memory segments that can
         be attached by a process.

The above settings should also be sufficient.

SEMMNS = The number of semaphores in the system.
SEMMNI = The number of semaphore set identifiers in the system;
         determines the number of semaphore sets that can be
         created at any one time.
SEMMSL = The maximum number of sempahores that can be in one
         semaphore set. It should be same size as maximum number

         of Oracle processes
     (The PROCESSES parameter in the init.ora file).

SEMMSL
    Set to 10 plus the largest initsid.ora PROCESSES parameter
of an Oracle database on the system.  The PROCESSES parameter
can be found in each initsid.ora file, loc ORACLE_HOME/dbs
directory. The default value of PROCESSES for the 8.1.5 prebuilt
database is 50.

SEMMNS
    Set to the sum of the PROCESSES parameter for each Oracle
database the largest one, plus 2 times the largest PROCESSES
value, plus 10 number of Oracle databases.  For example,
consider a system that has three Oracle instances with the
PROCESSES parameter in their initsid.ora files set to the
following values:

               ORACLE_SID=A, PROCESSES=100
               ORACLE_SID=B, PROCESSES=100
               ORACLE_SID=C, PROCESSES=200

    The value of SEMMNS is calculated as follows:

    SEMMNS = ((A=100) + (B=100)) + ((C=200) * 2) +
             ((# of instances=3) * 10) = 630
Review & Followup
Rating: 4
Cool Stuff June 27, 2001
Reviewer: A reader from France

Rating: 5
September 24, 2001
Reviewer: A reader

Rating: 5
Exactly what I was looking for. July 16, 2002
Reviewer: Randal Columb from Portsmouth, NH
The title of te review says it all.  I've looked everywhere for explanations for
these settings...

Rating: 5
Some details... July 28, 2002
Reviewer: Brain.Xu from China
Hi,tom:

   You are doing a really good topic for the kernel params around Oracle.But one
point i cannot understand.it's this:

   What's mean "semaphore" concept? It has any relation between "Shared memory"?
It's in ORACLE level or OS level?
Thanks for help.

Followup:
semaphores are serialization devices (only one person at a time will get a
semaphore).  In general, they are "heavy weight" (we have light weight latches
as well) and implemented by the OS itself.  We use them to protect some shared
data structures from being updated by more then one process at a time.

Rating: 5
What about SEMMNI July 29, 2002
Reviewer: Igor from France

Followup:
what about it?  do you mean "what is a semaphore set"? if so,
http://www.google.com/search?hl=en&lr=&ie=ISO-8859-1&q=%22what+is+a+semaphore+set%22
(a search for "what is a semaphore set" on google) tells me:
http://users.evitech.fi/~tk/rtp/multi-process.html#semaphores_whatis
A semaphore set is a structure that stores a group of semaphores together, and
possibly allows the process to commit a transaction on part or all of the
semaphores in the set together. In here, a transaction means that we are
guaranteed that either all operations are done successfully, or none is done at
all. Note that a semaphore set is not a general parallel programming concept,
it's just an extra mechanism supplied by SysV IPC.

Rating: 1
July 30, 2002
Reviewer: Igor from France
I'm sorry I was not clear and made you nervous.
I just wanted to know your opinion about Mr Sing setting
and your way to calculate it.
I appreciate your job.

Rating: 5
SEMMSL parameter August 05, 2003
Reviewer: Anthony Reddy from KL, Malaysia
 Mr. Tom,
we are using hp-ux 11,
i can't see the  SEMMSL parameter, when i type the following
command as root.

root:/>kmtune | grep sem

it shows

sema
semaem
semmap
semmni
semmns
semmnu
semume
semvmx

what's wrong here.
what could be the equivalent parameter for SEMMSL.

please help.
thanks in advance.

Followup:
hmm, guess it is time to break out that HP/UX Oracle Admin Guide and see what it
says (i pointed that out on the link that sent you here...)?

Me, being someone who is not an SA, does not not admin HP/UX, does not even
really use it -- doesn't know (but I'll betcha the docs do!)
http://download-west.oracle.com/docs/html/A96167_01/pre.htm#CHDCJDHI

      Rating:
      4
  August 07, 2003
Reviewer:  Anthony
      Reddy  from KL, Malaysia
thanks for the help.

August 06, 2003 Tim -- Thanks for the question regarding
      "Directory List including modify date and times - casting the date
      problem", version 8.1.7

      You Asked
hi Tom,

I started with your dir listing code and it's working out very nice
but I am trying to figure out how to get the rest of the file info using File
object. I am having a problem with the dates specifically.
Here's my attempt but as you can see theres a problem with the dates and I cant
seem to figure it out..

GRANT JAVAUSERPRIV to SOLIVER;

drop table DIR_LIST;
create global temporary table DIR_LIST ( filename varchar2(255),lastmodified
date) on commit delete rows;

create or replace and compile java source named "DirList"
    as
    import java.io.*;
    import java.sql.*;

    public class DirList
    {
    public static void getList(String directory)
                      throws SQLException
    {
       String element;

       File path = new File(directory);
       File[] FileList = path.listFiles();
       String TheFile;
       Date ModiDate;
       #sql { DELETE FROM DIR_LIST};

       for(int i = 0; i < FileList.length; i++)
       {
           TheFile = FileList[i].getAbsolutePath();
           ModiDate = new Date(FileList[i].lastModified());

           #sql { INSERT INTO DIR_LIST (FILENAME,LASTMODIFIED)
                  VALUES (:TheFile,:ModiDate) };
       }
   }
  }
/

create or replace procedure get_dir_list( p_directory in varchar2 )
as language java
name 'DirList.getList( java.lang.String )';
/

--implementation
EXEC dbms_java.grant_permission( 'SOLIVER', 'java.io.FilePermission', 'c:
emp*','read' );

exec get_dir_list( 'c:    emp' );

select  to_char(lastmodified,'YYYY-MM-DD HH24:MI:SS'),filename from dir_list
order by lastmodified;

I would appreciate any help...

      and we said...
well, i never got along with java dates myself.  Here is the way I've done it in
the past.  I'm sure a real java programmer out there can suggest a short path.
I'm assuming the problem you were having is the time was 00:00:00 for all -- if
so, here is my workaround (suggest you don't use java_sys_priv after 815!):

ops$tkyte@ORA920> create global temporary table DIR_LIST ( filename
varchar2(255),lastmodified date ) on commit delete rows;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace and compile java source named "DirList"
  2  as
  3  import java.io.*;
  4  import java.sql.*;
  5
  6      public class DirList
  7      {
  8
  9      static private String dateStr( java.util.Date x )
 10      {
 11          if ( x != null )
 12              return (x.getYear()+1900) + "/" + (x.getMonth()+1) + "/" +
x.getDate() + " " +
 13                      x.getHours() + ":" + x.getMinutes() + ":" +
x.getSeconds();
 14         else return null;
 15      }
 16
 17      public static void getList(String directory)
 18                        throws SQLException
 19      {
 20         String element;
 21
 22
 23         File path = new File(directory);
 24         File[] FileList = path.listFiles();
 25         String TheFile;
 26         String ModiDate;
 27         #sql { DELETE FROM DIR_LIST};
 28
 29         for(int i = 0; i < FileList.length; i++)
 30         {
 31             TheFile = FileList[i].getAbsolutePath();
 32             ModiDate = dateStr( new java.util.Date(
FileList[i].lastModified() ) );
 33
 34             #sql { INSERT INTO DIR_LIST (FILENAME,LASTMODIFIED)
 35                    VALUES (:TheFile, to_date( :ModiDate, 'yyyy/mm/dd
hh24:mi:ss') ) };
 36         }
 37     }
 38    }
 39  /

Java created.

ops$tkyte@ORA920> show err
No errors.
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace procedure get_dir_list( p_directory in
varchar2 )
  2  as language java
  3  name 'DirList.getList( java.lang.String )';
  4  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_java.grant_permission( 'OPS$TKYTE',
'SYS:java.io.FilePermission', '/tmp/*', 'read' )

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_java.set_output(100000);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec get_dir_list( '/tmp/' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> @bigdate
ops$tkyte@ORA920> alter session set nls_date_format = 'dd-mon-yyyy hh24:mi:ss';

Session altered.

ops$tkyte@ORA920> select * from dir_list;

FILENAME                  LASTMODIFIED
------------------------- --------------------
/tmp/mcop-tkyte           25-jan-2003 09:18:32
/tmp/.font-unix           14-jan-2003 19:09:26
/tmp/.gdm_socket          14-jan-2003 19:09:31
/tmp/ssh-XX2vjpbQ         27-oct-2002 09:48:22
/tmp/ksocket-tkyte        25-jan-2003 19:55:02
/tmp/ac3                  28-dec-2002 10:54:18
/tmp/kde-tkyte            25-jan-2003 09:18:27
/tmp/gaim_tkyte.19403     27-oct-2002 09:48:52
/tmp/.X11-unix            14-jan-2003 19:09:31
/tmp/.X0-lock             14-jan-2003 19:09:31
/tmp/.ICE-unix            25-jan-2003 09:18:29
/tmp/ssh-XXIRgZYj         04-dec-2002 07:12:39
/tmp/gaim_tkyte.31340     04-dec-2002 12:13:25
/tmp/ssh-XXolOTKE         25-jan-2003 09:18:19
/tmp/gaim_tkyte.2033      17-nov-2002 19:09:10
/tmp/orbit-tkyte          25-jan-2003 09:18:44
/tmp/kde-lori             15-jan-2003 15:52:12
/tmp/ksocket-lori         15-jan-2003 16:06:00
/tmp/gaim_tkyte.22754     22-dec-2002 18:49:16
/tmp/hostname.txt         24-jan-2003 17:26:39
/tmp/.wine-tkyte          18-jan-2003 10:27:05
/tmp/test.dat             18-jan-2003 14:28:56
/tmp/mcop-lori            15-jan-2003 16:06:00
/tmp/gaim_tkyte.31779     05-nov-2002 07:51:15
/tmp/nsmail-1.tmp         21-jan-2003 09:10:45
/tmp/nsmail.tmp           21-jan-2003 09:10:25
/tmp/nsmail-2.tmp         21-jan-2003 09:10:45
/tmp/nsmail-3.tmp         21-jan-2003 09:10:45
/tmp/nsmail-4.tmp         21-jan-2003 09:10:45
/tmp/nsmail-5.tmp         21-jan-2003 09:10:45
/tmp/nsmail-6.tmp         21-jan-2003 09:10:45
/tmp/nsmail.html          21-jan-2003 09:10:45
/tmp/nsmail-7.tmp         21-jan-2003 09:10:45
/tmp/nsmail.eml           21-jan-2003 09:10:45
/tmp/audio                28-dec-2002 10:54:18
/tmp/nscopy.tmp           21-jan-2003 09:10:49
/tmp/orbit-root           24-jan-2003 17:32:17
/tmp/80000.trc            25-jan-2003 16:55:01
/tmp/2000.trc             25-jan-2003 16:54:51
/tmp/100.trc              25-jan-2003 16:54:41
/tmp/DbConnection.java.sw 25-jan-2003 19:50:16
p

/tmp/AdobeFnt06.lst.500   30-dec-2002 20:27:58

42 rows selected.

ops$tkyte@ORA920>
Review & Followup

      Rating: 4
Trying to run
      /usr/bin/env from PL/sql using Java Stored
      Procedure"  January 26, 2003
Reviewer:  Jamil
      Alshaibani  from Saudi (Jeddah)
Hi Tom
 Would you please tell me how to run this example because I do not know how to
run Java program?
Under this version of SQL PLUS
Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, Oracle Label Security, OLAP and Oracle Data Mining
options
JServer Release 9.2.0.1.0 – Production

Can you give an example how to run Java program with ORACLE because I am just
started learning Java?
Would you recommend me a god site for learning Java program with oracle?
Thank you very much  in advance

Jamil

          Followup:

Hey, i just cut and pasted a SQLPlus session -- type in exactly what you see on
this page and wah-lah, you have your java program.

you might be interested in:

http://www.amazon.com/exec/obidos/ASIN/1861006020/
which is a book geared towards plsql programmers who want to do a little java in
the database.

      Rating:
      5
Thanks..  January 26, 2003
Reviewer:
      Tim  from Denmark
That worked great thanks for the help.

One question what is @bigdate ?

Did what I wanted without it but just curious.

          Followup:

ops$tkyte@ORA920> @bigdate
ops$tkyte@ORA920> alter session set nls_date_format = 'dd-mon-yyyy hh24:mi:ss';

Session altered.

it just does that alter session for me

      Rating:
      4
Why  January 27, 2003
Reviewer:
      A reader
Hi, Tom,
I have the following problem:
SQL> exec dbms_java.grant_permission( 'username','SYS:java.io.FilePermission',
'c:\', 'read' );
BEGIN dbms_java.grant_permission( 'username','SYS:java.io.FilePermission',
'c:\', 'read' ); END
*
ERROR at line 1:
ORA-29532: Java call terminated by uncaught Java exception:
java.lang.SecurityException: policy table update SYS:java.io.FilePermission,
c:\
ORA-06512: at "SYS.DBMS_JAVA", line 0
ORA-06512: at line 1

Do I need any more previleges under my account

Thanks

          Followup:

Looks that way:

ops$tkyte@ORA817DEV> drop user a cascade;
User dropped.

ops$tkyte@ORA817DEV> grant create session to a identified by a;
Grant succeeded.

ops$tkyte@ORA817DEV> @connect a/a

a@ORA817DEV> exec dbms_java.grant_permission(
'SCOTT','SYS:java.io.FilePermission', '/tmp', 'read' )
BEGIN dbms_java.grant_permission( 'SCOTT','SYS:java.io.FilePermission', '/tmp',
'read' ); END;

*
ERROR at line 1:
ORA-29532: Java call terminated by uncaught Java exception:
java.lang.SecurityException: policy table update
SYS:java.io.FilePermission, /tmp
ORA-06512: at "SYS.DBMS_JAVA", line 0
ORA-06512: at line 1

a@ORA817DEV> @connect /

ops$tkyte@ORA817DEV> grant dba to a;
Grant succeeded.

ops$tkyte@ORA817DEV> @connect a/a

a@ORA817DEV> exec dbms_java.grant_permission(
'SCOTT','SYS:java.io.FilePermission', '/tmp', 'read' )

PL/SQL procedure successfully completed.

a@ORA817DEV>

see

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/java.920/a96656/security.htm#1006699
for all of the details on getting the ability to do these grants.  DBA works but
is not necessary

      Rating: 5
Use
      java.sql.Timestamp  January 27, 2003
Reviewer:
      Dan Kefford  from New Haven, CT
If the time component (hours, minutes, seconds) is critical, use
java.sql.Timestamp. Consider the following:

SQL> CREATE OR REPLACE PACKAGE dk234.fun_with_dates_pkg AS
  2     FUNCTION pass_in_date
  3       (p_date_from_java IN DATE)
  4        RETURN DATE
  5     IS
  6        l_date_from_oracle DATE ;
  7     BEGIN
  8        l_date_from_oracle := p_date_from_java ;
  9        RETURN l_date_from_oracle ;
 10     END pass_in_date ;
 11  END fun_with_dates_pkg ;
 12  /

Package created.

No errors.

Package body created.

No errors.

Source for FunWithDates.java:

package com.dankefford.test ;

import java.sql.CallableStatement ;
import java.sql.Connection ;
import java.sql.Date ;
import java.sql.DriverManager ;
import java.sql.SQLException ;
import java.sql.Timestamp ;

import java.util.Calendar ;

import oracle.jdbc.driver.OracleTypes ;

public class FunWithDates {
    private static final String    DATABASE_DRIVER   =
"oracle.jdbc.driver.OracleDriver" ;
    private static final String    DATABASE_URL      =
"jdbc:oracle:thin:@lacey:1521:BAN3" ;
    private static final String    DATABASE_USERID   = "dk234" ;
    private static final String    DATABASE_PASSWORD = "dk234" ;

    private static final String packageCall = "{ ? = call
fun_with_dates_pkg.pass_in_date(?) }" ;

    public FunWithDates() {
        Connection conn = null ;
        CallableStatement cs = null ;
        Date javaDate = new java.sql.Date(new java.util.Date().getTime()) ;
        Timestamp javaTimestamp = new java.sql.Timestamp(new
java.util.Date().getTime()) ;

        // Getters in both Date classes for hours
        // and minutes are deprecated
        // so we have to use Calendar.
        Calendar cal = Calendar.getInstance() ;

        try {
            DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());

            conn = DriverManager.getConnection(DATABASE_URL,
                                               DATABASE_USERID,
                                               DATABASE_PASSWORD ) ;

            cs = conn.prepareCall(packageCall) ;
            cs.registerOutParameter(1, OracleTypes.DATE) ;
            cs.setDate(2, javaDate) ;
            cs.execute() ;
            cal.setTime(cs.getDate(1));
            System.out.println("Passing in java.sql.Date, retrieving
java.sql.Date, the current time is " +
                               cal.get(Calendar.HOUR_OF_DAY) + ":" +
                               cal.get(Calendar.MINUTE)) ;
            cal.setTime(cs.getTimestamp(1));
            System.out.println("Passing in java.sql.Date, retrieving
java.sql.Timestamp, the current time is " +
                               cal.get(Calendar.HOUR_OF_DAY) + ":" +
                               cal.get(Calendar.MINUTE)) ;
            cs.registerOutParameter(1, OracleTypes.TIMESTAMP) ;
            cs.setTimestamp(2, javaTimestamp) ;
            cs.execute() ;
            cal.setTime(cs.getTimestamp(1));
            System.out.println("Passing in java.sql.Timestamp, retrieving
java.sql.Timestamp, the current time is " +
                               cal.get(Calendar.HOUR_OF_DAY) + ":" +
                               cal.get(Calendar.MINUTE)) ;
            cal.setTime(cs.getDate(1));
            System.out.println("Passing in java.sql.Timestamp, retrieving
java.sql.Date, the current time is " +
                               cal.get(Calendar.HOUR_OF_DAY) + ":" +
                               cal.get(Calendar.MINUTE)) ;
        } catch (SQLException sqle) {
            System.out.println(sqle.getMessage()) ;
            System.exit(1); ;
        } catch (Exception e) {
            System.out.println(e.getMessage()) ;
            System.exit(1); ;
        }
    }
    public static void main(String[] args) {
        FunWithDates fwd = new FunWithDates();
    }
}

... and the output:

Passing in java.sql.Date, retrieving java.sql.Date, the current time is 0:0

Passing in java.sql.Date, retrieving java.sql.Timestamp, the current time is 0:0

Passing in java.sql.Timestamp, retrieving java.sql.Timestamp, the current time
is 11:11

Passing in java.sql.Timestamp, retrieving java.sql.Date, the current time is 0:0

When using java.sql.Timestamp all around (the third combination in the code and
the third result in the output), the time is preserved.

HTH,

dan

          Followup:

thank you -- knew it was something simple ;)

Here is the modified, smaller code:

create or replace and compile java source named "DirList"
as
import java.io.*;
import java.sql.*;

    public class DirList
    {
    public static void getList(String directory) throws SQLException
    {
       File path = new File(directory);
       File[] FileList = path.listFiles();
       String TheFile;
       Timestamp ModiDate;
       #sql { DELETE FROM DIR_LIST};

       for(int i = 0; i < FileList.length; i++)
       {
           TheFile = FileList[i].getAbsolutePath();
           ModiDate = new Timestamp( FileList[i].lastModified() );

           #sql { INSERT INTO DIR_LIST (FILENAME,LASTMODIFIED)
                  VALUES (:TheFile, :ModiDate ) };
       }
   }
  }
/

      Rating:
      4
Lacks  January 28, 2003
Reviewer:
      A reader
Hi, Tom,
THere is a problem here:
1. If the file is a folder, then the time stamp is not the same as my OS
modified time stamp;
2. How could i read alll the other files under the nested folder, things like
recursive call, could give us details please?

Thanks

          Followup:

1) sounds like a question for javaguy@somewhere.else.com i guess
2) see #1

sorry - java is not my native language - you can lookup this sort of stuff on
the sun site.

      Rating: 5
Java and Oracle
      Book   January 28, 2003
Reviewer:  Andrew
      Gilfrin  from Dover, UK, Kent
Tom

You recommend

http://www.amazon.com/exec/obidos/ASIN/1861006020/
Which looks good but will it be any use for me as I am using 8i only and cannot
get access to 9i.

Does anybody have this book and if so is it really only usefull for 9i database
programming.

          Followup:

almost the entire book applies to 8i as well.

      Rating: 5
Thanks
        January 28, 2003
Reviewer:  Andrew Gilfrin
       from Dover, Kent, UK
Tom

Thanks for taking the time to answer my small question, I actually did think it
would and ordered it anyway. We have a very restrictive I.T. structure here and
I am unable to load 9i on my machine nor am I allowed to connect to the single
9i test database we have installed. Its a real pain as I would love to try out
some of the new features such as ANSI Join syntax.

Thanks Again

      Rating: 3
File modified
      date.  August 06, 2003
Reviewer:  Scott
       from Perth, WA
G'day

This is what we did to determine the date the file was last modified, and we
added 8 hours to get in the correct timezone.
lastModified returns milliseconds since midnight 1970.

    long   last_mod_time;
    java.sql.Timestamp mod_date;

    last_mod_time = element.lastModified() + 28800000;
    mod_date = new java.sql.Timestamp(last_mod_time);

By the way, if you had the following read,delete permission on a directory
/server/dir/*_flag
Would you infer that it meant you could delete any file ending with "_flag"
Because it doesn't seem to work that way. I need:
/server/dir/*
But I don't want to be that loose.

Thanks.

          Followup:

you'd have to read the j2ee spec to see what level of granularity you can use.
I have a feeling it is going to be

a) file name
b) directory with a *

but I'm not sure.  Maybe a j2ee expert knows and will see this.

      Rating:
      5
  September 24, 2003
Reviewer:
      A reader

August 06, 2003 Jonathan -- Thanks for the question regarding
      "SQL PLAN OUTPUT IN STORED PROCEDURE", version 9.0.1

      You Asked
Hi Tom. How can I get PL/SQL to output the results of a query performed against
the v$sql_plan table from a cursor within a stored procedure -- without it
garbling the query's result. For instance, I want all identation, white spaces
etc. that would normally result from something like the following query -- to be
preserved! How can I do that with wee old dbms_output??

[CURSOR c2 (p_in_hash_val INTEGER) IS ]

select
 LPAD(' ',2*DEPTH)||Operation||' '||Options||' '|| object_Name X_Plan,
  cardinality,cpu_cost,io_cost,cost,optimizer,temp_space
  from v$sql_plan
  where hash_value = p_in_hash_val;

The idea of placing everything in one variable didn't work either.

Thanks very much

-JR

      and we said...
It takes the address, hash_value and child number to get the plan for a query...

You need the address, plan_hash_value, child_number from v$sql and then can get
the plan.

Here is an example - stole the query from utlxpls.sql and modified it to work
against v$sql_plan instead of PLAN_TABLE:

ops$tkyte@ORA920> variable addr varchar2(20)
ops$tkyte@ORA920> variable hash number
ops$tkyte@ORA920> variable child number
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec :addr := '54E0C6FC'; :hash :=  4218865043; :child :=
   0

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> column sql_text format a70
ops$tkyte@ORA920> select sql_text from v$sql where address = :addr and
child_number = :child;

SQL_TEXT
----------------------------------------------------------------------
select u.name, o.name, t.spare1, t.pctfree$    from sys.obj$ o, sys.us
er$ u, sys.tab$ t    where  (bitand(t.trigflag, 1048576) = 1048576) an
d           o.obj#=t.obj# and o.owner# = u.user#

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select
  2  '| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart|
Pstop |'
  3  as "Plan Table" from dual
  4  union all
  5  select
  6
'--------------------------------------------------------------------------------
'
  7  from dual
  8  union all
  9  select * from
 10  (select /*+ no_merge */
 11         rpad('| '||substr(lpad(' ',1*(level-1))||operation||
 12              decode(options, null,'',' '||options), 1, 27), 28, ' ')||'|'||
 13         rpad(substr(object_name||' ',1, 9), 10, ' ')||'|'||
 14         lpad(decode(cardinality,null,'  ',
 15              decode(sign(cardinality-1000), -1, cardinality||' ',
 16              decode(sign(cardinality-1000000), -1,
trunc(cardinality/1000)||'K',
 17              decode(sign(cardinality-1000000000), -1,
trunc(cardinality/1000000)||'M',
 18                 trunc(cardinality/1000000000)||'G')))), 7, ' ') || '|' ||
 19         lpad(decode(bytes,null,' ',
 20                  decode(sign(bytes-1024), -1, bytes||' ',
 21                  decode(sign(bytes-1048576), -1, trunc(bytes/1024)||'K',
 22                  decode(sign(bytes-1073741824), -1,
trunc(bytes/1048576)||'M',
 23                         trunc(bytes/1073741824)||'G')))), 6, ' ') || '|' ||
 24         lpad(decode(cost,null,' ',
 25                  decode(sign(cost-10000000), -1, cost||' ',
 26                  decode(sign(cost-1000000000), -1, trunc(cost/1000000)||'M',
 27                         trunc(cost/1000000000)||'G'))), 8, ' ') || '|' ||
 28         lpad(decode(partition_start, 'ROW LOCATION', 'ROWID',
 29              decode(partition_start, 'KEY', 'KEY', decode(partition_start,
 30              'KEY(INLIST)', 'KEY(I)', decode(substr(partition_start, 1, 6),
 31              'NUMBER', substr(substr(partition_start, 8, 10), 1,
 32              length(substr(partition_start, 8, 10))-1),
 33              decode(partition_start,null,' ',partition_start)))))||
 34                        ' ', 7, ' ')|| '|' ||
 35         lpad(decode(partition_stop, 'ROW LOCATION', 'ROW L',
 36            decode(partition_stop, 'KEY', 'KEY', decode(partition_stop,
 37            'KEY(INLIST)', 'KEY(I)', decode(substr(partition_stop, 1, 6),
 38            'NUMBER', substr(substr(partition_stop, 8, 10), 1,
 39            length(substr(partition_stop, 8, 10))-1),
 40            decode(partition_stop,null,' ',partition_stop)))))||' ', 7, '
')||
 41                        '|' as "Explain plan"
 42  from v$sql_plan
 43  start with (address = :addr
 44              and hash_value = :hash
 45              and child_number = :child
 46              and id=0 )
 47  connect by prior id = parent_id
 48          and prior address = address
 49          and prior hash_value = hash_value
 50          and prior child_number = child_number
 51  order by id, position)
 52  union all
 53  select
 54
'--------------------------------------------------------------------------------
'
 55  from dual;

Plan Table
--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |       |      |        |       |       |
|  NESTED LOOPS             |          |       |      |        |       |       |
|   NESTED LOOPS            |          |       |      |        |       |       |
|    TABLE ACCESS FULL      |TAB$      |       |      |        |       |       |
|    TABLE ACCESS BY INDEX R|OBJ$      |       |      |        |       |       |
|     INDEX UNIQUE SCAN     |I_OBJ1    |       |      |        |       |       |
|   TABLE ACCESS CLUSTER    |USER$     |       |      |        |       |       |
|    INDEX UNIQUE SCAN      |I_USER#   |       |      |        |       |       |
--------------------------------------------------------------------------------

11 rows selected.

Review &
      Followup

      Rating: 3
Yes,
      but...  December 22, 2002
Reviewer:  JR

Tom -- thanks for the great response. I want to output the sql plan from within
a stored procedure, thus with dbms_output, and not via a regular query. To clear
this

CREATE OR REPLACE PROCEDURE xxx
IS
 ..
 CURSOR c2 (p_in_hash_value INTEGER) IS
    select
    LPAD(' ',2*DEPTH)||Operation||' '||Options||' '|| object_Name EX_Plan,
    cardinality,cpu_cost,io_cost,cost,optimizer,temp_space
  from v$sql_plan
  where hash_value = p_in_hash_val;

BEGIN
 ...
 DBMS_OUTPUT ...
***
 FOR r2 in c2(r1.hash) LOOP
 DBMS_OUTPUT   -----------------> r2.ex_plan
 DBMS_OUTPUT  -------------------THE SQLPLAN FOR THE QUERY

See what I mean? I want to output the result of our
query from within the procedure. How can this be done??

          Followup:

sorry -- I thought if you had the query you could get from here to there... Ok,
easiest way by far:

ops$tkyte@ORA920> variable addr varchar2(20)
ops$tkyte@ORA920> variable hash number
ops$tkyte@ORA920> variable child number
ops$tkyte@ORA920> variable X refcursor
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec :addr := '54E0C6FC'; :hash :=  4218865043; :child := 0

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace procedure get_plan( p_addr in raw,
  2                                                     p_hash in number,
  3                                                     p_child in number,
  4                                               p_result OUT sys_refcursor )
  5  as
  6  begin
  7
  8          open p_result for
  9  select
 10  '| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart|
Pstop |'
.......
 50  from v$sql_plan
 51  start with (address = p_addr
 52              and hash_value = p_hash
 53              and child_number = p_child
 54              and id=0 )
 55  connect by prior id = parent_id
 56          and prior address = address
 57          and prior hash_value = hash_value
 58          and prior child_number = child_number
 59  order by id, position)
 60  union all
 61  select
 62
'--------------------------------------------------------------------------------
'
 63  from dual;
 64  end;
 65  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec get_plan( :addr, :hash, :child, :X );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> print x

Plan Table
--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |       |      |        |       |       |
|  NESTED LOOPS             |          |       |      |        |       |       |
|   NESTED LOOPS            |          |       |      |        |       |       |
|    TABLE ACCESS FULL      |TAB$      |       |      |        |       |       |
|    TABLE ACCESS BY INDEX R|OBJ$      |       |      |        |       |       |
|     INDEX UNIQUE SCAN     |I_OBJ1    |       |      |        |       |       |
|   TABLE ACCESS CLUSTER    |USER$     |       |      |        |       |       |
|    INDEX UNIQUE SCAN      |I_USER#   |       |      |        |       |       |
--------------------------------------------------------------------------------

11 rows selected.

or just have AUTOPRINT set true and it'll print out.

If you really want to use dbms output

SQL> set serveroutput on size 1000000 format wrapped

ops$tkyte@ORA920> create or replace procedure get_plan( p_addr in raw,
  2                                        p_hash in number,
  3                                        p_child in number )
  4  as
  5  begin
  6      for x in (
  7  select
  8  '| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart|
Pstop |'
  9  as "Plan Table" from dual
....
 56          and prior child_number = child_number
 57  order by id, position)
 58  union all
 59  select
 60
'--------------------------------------------------------------------------------
'
 61  from dual ) loop
 62      dbms_output.put_line( x."Plan Table" );
 63      end loop;
 64  end;
 65  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec get_plan( :addr, :hash, :child );
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |       |      |        |       |       |
|  NESTED LOOPS             |          |       |      |        |       |       |
|   NESTED LOOPS            |          |       |      |        |       |       |
|    TABLE ACCESS FULL      |TAB$      |       |      |        |       |       |
|    TABLE ACCESS BY INDEX R|OBJ$      |       |      |        |       |       |
|     INDEX UNIQUE SCAN     |I_OBJ1    |       |      |        |       |       |
|   TABLE ACCESS CLUSTER    |USER$     |       |      |        |       |       |
|    INDEX UNIQUE SCAN      |I_USER#   |       |      |        |       |       |
--------------------------------------------------------------------------------

PL/SQL procedure successfully completed.

but the refcursor would be the optimal way to do it as it would allow you to
repurpose this routine later easily -- eg: if someone wanted to put a java gui
on this, or use it in a VB program or whatever -- the refcursor would be best
all around...

      Rating:
      3
CLAIRIFICATION  December 22, 2002

Reviewer:  JR
Maybe I wan't clear enough, but the first cursor
retrieves the address, plan_hash_value, child_number from v$sql. My problem is
properly formating the output (I get the plan, but it's not 'legible').

      Rating: 5
JR's
      problem  December 23, 2002
Reviewer:  Tony
       from London, UK
JR, sounds like you just need to add "FORMAT WRAPPED" to your "SET SERVEROUTPUT
ON" command.  Then leading spaces will not be stripped from the output.

      Rating: 5
Oh Gosh!!
        December 23, 2002
Reviewer:  JR

Oh Gosh!! I'm speechless! Great response, greate timing,
great help! Learned a lot. Refcursor - way past my league.
What more to say. Thanks!

      Rating:
      5
Tony  December 23, 2002
Reviewer:
      JR
Tony - thanks for your input too

      Rating: 5
what is
      v$sql_plan_statistics?  August 06, 2003
Reviewer:
      John
Tom,

1.what is v$sql_plan_statistics table? how different is from v$sql_plan table?

2.how do i know address, hash, child values of my session?

thanks in advance.

          Followup:

cut and pasted from the docs:

V$SQL_PLAN contains the execution plan information for each child cursor loaded
in the library cache.

V$SQL_PLAN_STATISTICS

V$SQL_PLAN_STATISTICS provides execution statistics at the row source level for
each child cursor.

one has the plan, the other shows the statitics associated with the plan (number
of reads done by step X for example)

check out v$open_cursor and v$sql_cursor

August 06, 2003 Balaji -- Thanks for the question regarding
      "Client connection", version 8.1.5

      You Asked
Hi Tom,
Thanks for your previous help.
I have oracle instances in two different NT boxes both are
behind firewall.

When use any client tool like SQLPlus or Designer..to connect to
those instances i am getting disconnected every 5 minutes or
so.... if i am idel.

After being idel for about 5min if i try to run something,
i am getting
first
ORA-03113: end-of-file on communication channel
and further
ORA-24323: value not allowed
Error accessing package DBMS_APPLICATION_INFO
ERROR:
ORA-03114: not connected to ORACLE.

i have only default profile set up for my account...

What could be the problem?

Thanks again
Bala.

      and we said...
Many firewalls will time out your connection if you do not use
it.  You should contact your firewall administrator to see if
this is the case.  If it is, you could enable Net8 DCD (dead
client detection).  This will have Net8 send from the server a
"ping" like packet to the client every N seconds/minutes.  This
is to see if the client is still alive.  This will appear to be
network activity and might be enough to keep the circut alive.
Review &
      Followup

      Rating:
      2
  March 19, 2001
Reviewer:  Eddy
       from OH USA

      Rating:
      5
errors  September 13, 2002
Reviewer:
      atul  from India
Sir,

Recently we are getting errors like

Spooled file is /tmp/pms_rcia_log.dat
ORA-24323: Message 24323 not found;  product=RDBMS; facility=ORA

Message 626 not found;  product=SQLPlus; facility=SP2
ERROR:
ORA-03114: not connected to ORACLE

When running a job which connect to other database run sql there..

Part of script goes like this...
echo "Generating pms rcia log file "
echo "Generating pms file ">>$LOGFILE
echo "Spooled file is /tmp/pms_rcia_log.dat"
export TWO_TASK="RDI"
sqlplus -s /
 @$sprexe/p_log.sql /tmp/p1_log.dat
export TWO_TASK=OW
echo "End of pms creation procedure. Press enter to continue...\c"
export TWO_TASK=OW

What could be the reason

Thanks.
atul

          Followup:

Looks like your oracle home isn't set, we cannot find the message files.

      Rating:
      4
  October 21, 2002
Reviewer:
      A reader
Hi Tom,

When I try to connect to the database, then I get the error

SQL>startup file=d:\oracle\admin\mysid\pfile\initmysid.ora
ORA-24323: value not allowed
ORA-01031: insufficient privileges

could you please tell me what's wrong with it?

          Followup:

unchange whatever you changed in your init.ora since your last successful
restart.  make sure you are logged in as the person who is able to start the
database as well.

Seeing as how my crystal ball is in the shop this week -- I cannot actually see
what the problem might be given that much information.

      Rating: 3
ORA-03113:
      end-of-file on communication channel   July 31, 2003

Reviewer:  Tony  from India
I'm running oracle8.1.6 on solaris5.8. When I execute a stored procedure from
the server box itself, I get the following errors. Actually,  I invoke a .sql
script in which the procedure is called.

Script:
-------
spool t.txt
set severoutput on
set timing on
select to_char(sysdate,'hh24:mi:ss') from dual;
exec mis_pkg.updmis;
select to_char(sysdate,'hh24:mi:ss') from dual;
spool off

Error:
------
ERROR at line 1:
ORA-03113: end-of-file on communication channel
ERROR at line 1:
ORA-03114: not connected to ORACLE

Elapsed: 00:00:00.00
SP2-0042: unknown command "om dual" - rest of line ignored.
not spooling currently
ORA-24323: value not allowed
Error accessing package DBMS_APPLICATION_INFO
ORA-24323: value not allowed
ERROR:
ORA-03114: not connected to ORACLE

Please help me out to fix this problem.

          Followup:

please contact support for something like this.

      Rating: 3
I use telnet to
      execute the procedure  July 31, 2003
Reviewer:
      Tony  from India
For the above question:
I use telnet to execute the procedure and get the above error

      Rating: 4
ORA
      -24323  July 31, 2003
Reviewer:  Yogesh
       from Pune
I'm also getting same error

ORA-03113: end-of-file on communication channel
and further
ORA-24323: value not allowed
Error accessing package DBMS_APPLICATION_INFO
ERROR:
ORA-03114: not connected to ORACLE.

But in my case connection is not idle .. when ever I call one procedure using
dblink .. its does this ...

procedure sign is

PROCEDURE UpdateTerminalRecord
(
p_Tid     VARCHAR2,
p_FilledRecord    Pkg_CommonUtilities.TableDefArray,
p_ErrorRecord    OUT Pkg_CommonUtilities.ErrorDefArray,
p_StatusCode               OUT              NUMBER,
p_StatusMessage            OUT              VARCHAR2
)

where

TYPE FeatureDef IS RECORD
   (
      FeatureName   VARCHAR2(50),
      FeatureValue  VARCHAR2(150)
   );

TYPE TableDefArray IS TABLE OF FeatureDef;

TYPE ErrorDef IS RECORD
   (
      FeatureName   VARCHAR2(50),
      ErrorDesc  VARCHAR2(1000)
   );

TYPE ErrorDefArray IS TABLE OF ErrorDef;

Can it be due to passing these types on dblink ?

          Followup:

when it rains, it pours.  this must be the 4th 3113 today I got - they come in
spurts I guess.

3113 = "please contact support, please"

      Rating: 4
More
      info  July 31, 2003
Reviewer:  Yogesh
       from Pune
To add more info to last query .. I don't have any firewall ... and there is one
function which is returning long .. is this function causing prob?

      Rating:
      5
  August 05, 2003
Reviewer:  MG
       from Germany
Hi Tom,

When I try to startup the database, i get following error

SQL> startup mount
ORACLE instance started.
ORA-24324: service handle not initialized
ORA-24323: value not allowed
ORA-03113: end-of-file on communication channel

Then I start the service, then it is ok. Could you please tell what could be the
reason.

Thanks

          Followup:

i guess you need to start the service first....

no version
no os
no information
no background

by service I guess you mean windoze.  on that os, you must have the background
service running first -- before starting the database instance up.

      Rating:
      5
  August 06, 2003
Reviewer:  MG
       from Germany
Hi Tom,

I am sorry, I did't give you enough information.

My OS : Windows 2000
Version (DB) : Oracle9i Enterprise Edition Release 9.2.0.1.0

Yes I mean the Win service : OracleServiceSID

Steps:
- I started the Service
- I logged into the db as sysdba
- then I shutdown the database
- startup the database --> There I get the error
- I stop the win service 'OracleServiceSID', and restart again
- then come back to the database, it works fine.

Could you please tell me, did I missed some files or settings?

thanks

          Followup:

don't know, don't use windows.  never experienced that myself.

when forced to use windows, i use net start and net stop from the command line
myself.

please contact support for assistance on this one.

August 06, 2003 -- Thanks for the question regarding "Dynamic
      SQL: Set variable contained in another variable", version 8.1.7

      You Asked
Is there a way in PL/SQL that I can set the value of a variable whose name is
contained in another variable?

Sort of like the Unix Korn shell's 'eval' function.

i.e. if I have
 my_rec table%ROWTYPE;
 x varchar2(100) := 'my_rec.col';

Can I set the value of my_rec.col without naming it explicitly?

The reason I ask is that I want to loop thru a PLSQL table containing variable
names and 2 parameters which I would then pass to a function which would set the
variable i.e.

 my_tab(i).var := my_func(my_tab(i).arg1,my_tab(i).arg2)

Here if my_tab(i).var contained x.y, I want the element y in the record x to be
actually assigned that value.

Far fetched? Thanks for any insights.

      and we said...
only if the indirectly referenced variable is a GLOBAL (declared in a package
specification and visable to all)

It will not work if the variable is a local variable in a subroutine or if the
variable is a package variable in the body.

You can do this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package my_pkg
  2  as
  3          some_variable    number := 55;
  4  end;
  5  /

Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function value_of( p_name
in varchar2 ) return varchar2
  2  is
  3          l_value long;
  4  begin
  5
  6          execute immediate 'begin :x := ' || p_name || '; end;' using out
l_value;
  7
  8          return l_value;
  9  end;
 10  /

Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( 'the value is ' ||
value_of( 'my_pkg.some_variable' ) );
the value is 55

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( 'the value is ' ||
value_of( '2 * my_pkg.some_variable' ) );
the value is 110

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating:
      3
Clarification  August 09, 2002
Reviewer:
      A reader
I am not sure I understand.

I want to set the value of my_pkg.some_variable.

i.e. If
x varchar2(100) := 'my_pkg.some_variable'
I want to be able to do "something" to x and have it magically set the value of
my_pkg.some_variable

          Followup:

Fine -- easy enough to do.  Simple variation on a theme here:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure set_value_of(
p_name in varchar2, p_newval in varchar2 )
  2  is
  3  begin
  4          execute immediate 'begin ' || p_name || ' := :x; end;' using in
p_newval;
  5  end;
  6  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec set_value_of( 'my_pkg.some_variable',
1023 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( 'the value is ' ||
value_of( 'my_pkg.some_variable' ) );
the value is 1023

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( 'the value is ' ||
value_of( '2 * my_pkg.some_variable' ) );
the value is 2046

PL/SQL procedure successfully completed.

      Rating:
      5
Reader  May 31, 2003
Reviewer:
      A reader
Could you tell me why I get ORA- 903

SQL> create or replace function sumup(p_in varchar2)
  2  return number is
  3  l_out number;
  4  begin
  5  execute immediate 'select count(*) into l_out from :x' using in p_in;
  6  return l_out;
  7  end;
  8  /

Function created.

SQL> show errors
No errors.
SQL> select sumup('T_1BYTE') from dual;
select sumup('T_1BYTE') from dual
       *
ERROR at line 1:
ORA-00903: invalid table name
ORA-06512: at "USER.SUMUP", line 5
 The user 'USER' owns the table 'T-1BYTE'.

Thanks

          Followup:

you cannot bind identifiers.

You can use binds (should use binds in most cases) where ever you could put a
character string literal.

for example:

select * from emp where ename = 'KING'

should be:

select * from emp where ename = :bv;

we can use a bind there because we can use a character string.  However, we
cannot:

select * from :bv where ename = 'KING'

because

select * from 'EMP' where ename = 'KING'

is not valid.

      Rating:
      5
Reader  May 31, 2003
Reviewer:
      A reader
Is there an alternate way to write function thar computes
count(*) for a table. I think I saw something similar in
this site before. If identifiers can not be parsed, may be
I was wrong

Thanks

          Followup:

it would be

execute immediate 'select count(*) from ' || p_in into some_variable;

      Rating: 4
setting and
      getting privat global variable with dynamic
      procedure/function  August 01, 2003
Reviewer:
      ninoslav  from croatia europe
Hi Tom,
could You show the way we could use dynamic sql in procedure to set package body
variable and function to get the value from it.
I have used for procedure:
execute immediate 'begin '
                    || p_var_txt
                    || ' := :x; end;'
                         using in p_val;
and
execute  immediate  'begin :x := '
                    ||  p_var_txt
                         ||  '; end;'
                 using out  lv_value_txt;
   return    lv_value_txt;
for function but it doesn't work for package body variable.

Thx

          Followup:

it only works for globally accessible globals - globals in the spec.

the ones in the body are not visible EXCEPT in the body.  Just pretend that
"execute immediate" is a function call (it is really) -- the dynamically
executed sql is taking place in an entirely different scope -- it is not run "in
the body", it is called "from the body" -- hence package body variables do not
exist.

      Rating: 4
setting and
      getting privat global variable with dynamic
      procedure/function  August 06, 2003
Reviewer:
      Ninoslav  from croatia / europe
Hi Tom,
thx for Your explanation.
So, to sum up: we can't use dynamic sql for encapsulation data. At least, for
now. We have to write explicit proc/function for every variable that we want to
use as a privat global variable (package body variable). However, for me, use of
dinamic sql for setting package specification variable is very usefull and i'm
gonna leave my variables in package specification till something happen about
this topic. I know it's not in the spirit of hiding information and advice about
enapsulation, but it's much better that write many procedures and functions.
Agree ?

thx
nino

          Followup:

I agree that using and abusing dynamic sql in order to create "really generic
stuff" has an extremely high price to be paid in terms of resource utilization
in the server....

be careful with this feature -- the old axiom "too much of a good thing" comes
into play here.

August 09, 2003 Senthil -- Thanks for the question regarding
      "Java - Oracle rowcount", version 8.1.7

      You Asked
Hey Tom,

Here I have a simple question. Using Oracle (thin or thick) JDBC with Oracle 8i
(8.1.7), is there any built-in methods available to get the rowcount of the
recently executed SQL statement.

I could do a custom implementation for this, but interested to know if there is
any oracle recommended efficient way to solve this issue.

Please share your ideas.

Thanks a lot for your support

UPDATE:
Question:do you mean "how many rows a select will return if I fetched all of the
data" or "how many rows did my update affect"?
Answer: how many rows a select will return if I fetched all of the data.

Thanks
- Senthil Anand

      and we said...
that answer is not known, not knowable, until AFTER you actually fetch the last
row of data.

there is no such attribute, until you actually get the last row, no one knows
how many rows you will get.
Review &
      Followup

      Rating: 4
Other
      Options  August 05, 2003
Reviewer:  Rex
       from San Jose, CA
While Tom's answer is correct, there may be other options to get the same
information.  We have been working on trying to determine the best way to
control the paging on a web based system and one of our options is to use an
aggregate function to return the total number of rows as part of the result set:

CREATE TABLE t
  (col1 number);

insert into t values (1);
insert into t values (2);
insert into t values (3);
insert into t values (4);
insert into t values (5);
insert into t values (6);
insert into t values (7);

commit;

select col1,
       COUNT(*) OVER () as Total_Records
  from t;

SQL>select col1,
  2         COUNT(*) OVER () as Total_Records
  3    from t;

      COL1 TOTAL_RECORDS
---------- -------------
         1             7
         2             7
         3             7
         4             7
         5             7
         6             7
         7             7

7 rows selected.

Execution Plan
---------------------------------------------------
   0       SELECT STATEMENT Optimizer=CHOOSE
   1    0  WINDOW (BUFFER)
   2    1    TABLE ACCESS (FULL) OF 'T'

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

We have not tested this for scalability or the impact of the size of the result
set on resources.  This is currently being tested on 8.1.7.

          Followup:

you don't even want to know what I think about:

select col1,
       COUNT(*) OVER () as Total_Records
  from t;

(oh wait, if you search around on this site, you can find out, i've covered that
-- basically it means you'll fetch the LAST ROW on the server before you fetch
the FIRST ROW.... think about it.  If your average number of pages is say "3" --
you'll triple the size of your server instantly by doing that -- to get the
first page for everyone will require a server big enough to get ALL PAGES for
EVERY USER for EVERY REQUEST.  Let me know what hardware vendor you use -- I'll
buy stock.)

      Rating: 3
COUNT(*) OVER
      ()  August 06, 2003
Reviewer:  Ik  from
      BG
Tom,

Are you saying this because COUNT(*) OVER () (or any analytic fn in general)
requires the entire data to be fetched and aggregated in memory?

If not can you please give the reason why.

Thanks.

          Followup:

Not "any analytic fn", if  you think about how they work (generally on
partitions, sorted data and the like) they only need the set of data they need
in order to operate.  they do not necessarily need the entire result set to
return their value.  they can return rows before the last row.

but if you think about count(*) over () -- what set of data does that particular
analytic function NEED in order to return the right answer?  Why it needs the
entire result set!  It cannot on the first row tell you "hey, there are 50324
more rows coming" without actually haven gotten the other 50324 rows!

      Rating: 5
Does it
      mean...  August 06, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
So, What you are saying is that this approach is similar (or same) as first
doing the COUNT(*) on your query and then again doing the same query to get the
columns of data and then return the COUNT and the data to the user.
Correct?

Essentially, You would have executed your query two times, one to get the count
and then to get the actual data.
Is my understanding correct?
Thanks,

          Followup:

no, this approach gives you an accurate count of what that result set would have
returned.

doing a count(*) and then executing the query gives you a random number that
doesn't necessarily have anything to do with the result set you got (since in a
multi-user database, the result set you counted might not be the one you
retrieve unless you use serializable/read only)

the count(*) over () causes the entire query to be materialized.

a count(*) + get me my slice of that query causes the entire query to be
materialized AND then the slice retrieved.

However -- that does not mean one is always better then the other!  You might
assume "count(*) over ()" would be better -- less work.  But it can be
considerably MORE work.  A select count(*) query can skip table access by rowids
and other phases of the plan that the other could not.  consider:

big_table@ORA920> set autotrace traceonly
big_table@ORA920> select count(*) from big_table;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=483 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FAST FULL SCAN) OF 'BIG_TABLE_PK' (UNIQUE) (Cost=483
Card=1833792)

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

big_table@ORA920> select *
  2    from ( select a.*, rownum r
  3             from ( select * from big_table ) a
  4                    where rownum <= 100 ) a
  5   where r >= 90;

11 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2507 Card=100 Bytes=15400)
   1    0   VIEW (Cost=2507 Card=100 Bytes=15400)
   2    1     COUNT (STOPKEY)
   3    2       TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=2507 Card=1833792
Bytes=165041280)

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

big_table@ORA920>
big_table@ORA920> select a.*
  2    from ( select a.*, rownum r
  3             from ( select big_table.*, count(*) over () from big_table ) a
  4                    where rownum <= 100 ) a
  5   where r >= 90;

11 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2507 Card=100 Bytes=16700)
   1    0   VIEW (Cost=2507 Card=100 Bytes=16700)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=2507 Card=1833792 Bytes=282403968)
   4    3         WINDOW (BUFFER) (Cost=2507 Card=1833792 Bytes=165041280)
   5    4           TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=2507 Card=1833792
Bytes=165041280)

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

big_table@ORA920> set autotrace off

so, count(*) + query to get slice = 3,970 logical IO's, count(*) over () =
22,082

      Rating:
      4
  August 06, 2003
Reviewer:  Dan
      Kefford  from New Haven, CT
Dear Tom...

Hoping that this question is somewhat relevant to this thread, could you go into
some detail on how interMedia/Oracle text approximates hits (counts)? Does it
track counts of indexed columns somewhere outside the index structure so that
they can be retrieved later when queried? Or is there some algorithm which one
can use to look at an index structure and immediately give a fair estimate?

Curious,

dan kefford

          Followup:

it uses its internal indexes to do that.  beyond that, no comment (you could
play with sql trace to see what it does if you like).

that is why you see "approx" on my search page, the index over time gets extra
entries and tends to over estimate after a while -- at which point I rebuild the
text index (maybe about 2 or 3 times a year).  I do that when the approximations
start to get "way off"

      Rating: 3
how does this
      compares to COUNT(*) OVER()  August 06, 2003

Reviewer:  Asim Naveed  from Pakistan
Hi,
Ok, count(*) over() will be slow.
Please comment on the following method and how
does it compares to count(*) over()

E.G
SELECT col1, col2, col3, (SELECT count(*)
                          FROM table1
                          WHERE .......)
FROM table1
WHERE .......

i.e what every query you use just put it as a scalar
query at the end of the "select list" of main query
and also replace the "select list" of scalar query
by count(*).

Please also rank the following methods.

A- USE COUNT(*) OVER()
B- USE scalar query
C- USE two queries , one for counting rows and other
   for fetching them (assuming that trasaction is set
                       to avoid incorrect results)

Thanks

          Followup:

I don't like ANY  of the methods -- none of them.  they are all equally "evil".

      Rating: 5
To
      Aseem  August 07, 2003
Reviewer:
      A reader
Hi Aseem
Tom is out till weekend..

You could easily use the runstats utility at

http://asktom.oracle.com/~tkyte/runstats.html
to compare any two approaches...

I have used this many times and it works wonderfully.
Don't forget to report back whatever you find out!;)

Regards
Menon:)

August 09, 2003 Ambarish -- Thanks for the question regarding
      "Retrieving Data from BLOB into a file.", version 8.1.7

      You Asked
Hi Tom,

We have inserted the BLOB field into the database .The file contents have been
properly inserted into the database.

I am not able to retrieve the BLOB into a file from the database.The requirement
is to put the BLOB into a file.

Thanks a lot for your co-operation.

Regards,
Ambarish.

      and we said...
Prior to Oracle9iR2 you will need to use Java, C, VB, some 3gl language.

In 9iR2 -- PLSQL can write binary files using UTL_FILE.

In 9iR1 and before, you will need to use Java or some other 3gl that has the
ability to do this.

If you have my book "Expert one on one Oracle" -- i do have an example in there
in Pro*C that writes BLOBs to files in an external procedure -- so it works like
a stored procedure call.
Review & Followup

      Rating: 3
Hope this
      helps  November 08, 2002
Reviewer:  Mio
       from US
Here's a little java client utility that I use for 8.1.7. Look at the usage
string. query_file should contain a query that returns a single lob locator. By
default it reads from Oracle and writes to lob_file. Pass -write to read from
lob_file and write to Oracle. By default it assumes a clob, pass -blob for
binary data.

// file LobUtils.java
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.FileOutputStream;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.sql.Blob;
import oracle.sql.BLOB;
import java.sql.SQLException;

import java.sql.Clob;
import oracle.sql.CLOB;

import java.io.FileReader;
import java.io.BufferedReader;
import java.io.StringWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.Writer;
import java.io.InputStreamReader;

public class LobUtils
{
    final static int bBufLen = 4 * 8192;
    String query;
    String connectString;
    String outFile;
    Connection conn;

    public LobUtils(String connectString, String query, String outFile) {
        this.connectString = connectString;
        this.query = query;
        this.outFile = outFile;
        this.conn = null;
    }

    public static void main(String[] args)
      throws FileNotFoundException, IOException, SQLException {
        if (args.length < 5) usage();
        int ii = 0;
        String connString = args[ii++];
        String queryFile = null;
        String outputFile = null;

        boolean read = true;
        boolean isBinary = false;

        for (; ii < args.length; ii++) {
            if (args[ii].equals("-write"))
                read = false;

            if (args[ii].equals("-blob"))
                isBinary = true;

            if (args[ii].equals("-qf") && ii < args.length - 1)
                queryFile = args[++ii];

            if (args[ii].equals("-lf") && ii < args.length - 1)
                outputFile = args[++ii];

        }

        if (queryFile == null || outputFile == null) usage();

            // all set
        if (read) {
            BufferedReader freader = new BufferedReader(new
FileReader(queryFile));
            StringWriter swriter = new StringWriter();
            int bufLen = 1024;
            char[] cbuf = new char[bufLen];
            int length = -1;
            while ((length = freader.read(cbuf, 0, bufLen)) != -1) {
                swriter.write(cbuf, 0, length);
            }
            freader.close();
            swriter.close();
            String query = swriter.toString();

            LobUtils lutils = new LobUtils(connString, query, outputFile);
            if (isBinary) {
                Blob blob = lutils.getBlob();
                long wrote = lutils.writeBlobToFile(blob);
                System.out.println("Wrote " + wrote + " bytes to file " +
outputFile);
            } else {
                Clob clob = lutils.getClob();
                long wrote = lutils.writeClobToFile(clob);
                System.out.println("Wrote " + wrote + " bytes to file " +
outputFile);
            }
        } else {
            BufferedReader freader = new BufferedReader(new
FileReader(queryFile));
            StringWriter swriter = new StringWriter();
            int bufLen = 1024;
            char[] cbuf = new char[bufLen];
            int length = -1;
            while ((length = freader.read(cbuf, 0, bufLen)) != -1) {
                swriter.write(cbuf, 0, length);
            }
            freader.close();
            swriter.close();
            String query = swriter.toString();

            LobUtils lutils = new LobUtils(connString, query, outputFile);
            Clob clob = lutils.getClob();
            InputStream creader = new FileInputStream(outputFile);
            long wrote = lutils.writeToOraClob(clob, creader);
            System.out.println("Wrote " + wrote + " bytes from file " +
outputFile);
        }
    }

    public Clob getClob()
      throws SQLException {
        conn = ConnUtil.getConnection(connectString);
        conn.setAutoCommit(false);
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery(query);
        Clob clob = null;
        if (rs.next()) {
            clob = rs.getClob(1);
        }
        return clob;
    }

    public Blob getBlob()
      throws SQLException {
        conn = ConnUtil.getConnection(connectString);
        conn.setAutoCommit(false);
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery(query);
        Blob blob = null;
        if (rs.next()) {
            blob = rs.getBlob(1);
        }
        return blob;
    }

    public long writeClobToFile(Clob clob)
      throws IOException, SQLException {
        long wrote = 0;
        BufferedWriter fwriter = new BufferedWriter(new FileWriter(outFile));
        wrote = readFromClob(clob, fwriter);
        fwriter.close();
        conn.commit();
        conn.close();
        return wrote;
    }

    public long writeBlobToFile(Blob blob)
      throws IOException, SQLException {
        long wrote = 0;
        OutputStream fwriter = new FileOutputStream(outFile);
        wrote = readFromBlob(blob, fwriter);
        fwriter.close();
        conn.commit();
        conn.close();
        return wrote;
    }

    private static void usage() {
        System.err.println("Usage: java LobUtils user/passwd@sid [-write]
[-blob] -qf query_file -lf lob_file");
        System.exit(1);
    }

    public static long writeToOraBlob(Blob blob, InputStream in)
      throws SQLException, IOException {
        BLOB oblob = (BLOB)blob;
        OutputStream out = oblob.getBinaryOutputStream();
        int length = -1;
        long wrote = 0;
        int chunkSize = oblob.getChunkSize();
        byte[] buf = new byte[chunkSize];
        while ((length = in.read(buf)) != -1) {
            out.write(buf, 0, length);
            wrote += length;
        }
        out.close();
        return wrote;
    }

    public long writeToOraClob(Clob clob, InputStream in)
      throws SQLException, IOException {
        CLOB oclob = (CLOB)clob;
        OutputStream out = oclob.getAsciiOutputStream();
        int length = -1;
        long wrote = 0;
        int chunkSize = oclob.getChunkSize();
        byte[] buf = new byte[chunkSize];
        while ((length = in.read(buf)) != -1) {
            out.write(buf, 0, length);
            wrote += length;
        }
        out.close();
        conn.commit();
        return wrote;
    }

    public static long readFromBlob(Blob blob, OutputStream out)
      throws SQLException, IOException {
        InputStream in = blob.getBinaryStream();
        int length = -1;
        long read = 0;
        byte[] buf = new byte[bBufLen];
        while ((length = in.read(buf)) != -1) {
            out.write(buf, 0, length);
            read += length;
        }
        in.close();
        return read;
    }

    public static long readFromClob(Clob clob, Writer out)
      throws SQLException, IOException {
        BufferedReader in = new BufferedReader(new
InputStreamReader(clob.getAsciiStream()));
        int length = -1;
        long read = 0;
        char[] buf = new char[bBufLen];
        while ((length = in.read(buf, 0, bBufLen)) != -1) {
            out.write(buf, 0, length);
            read += length;
        }
        in.close();
        return read;
    }
}

      Rating: 3
Retrieving Data
      from BLOB into a file  November 08, 2002
Reviewer:
      Ambarish Ganguly  from Shippensburg,PA,USA
Isnt it a bit surprising that using Oracle packages , I can successfully write
to a BLOB from a file, whereas we cannot read the BLOB into a file ?
I am using VB, thereby wanted to do all processing on the server side , hence
tried to have a solution .

          Followup:

9iR2 -- you can -- it is easy.

8i -- you can, java can do this easily if you like.

      Rating: 3
Retrieving Data
      from BLOB into a file... using UTL_FILE  November 11, 2002

Reviewer:  Fred Howell  from Merrimack, NH USA

I am currently searching through the Oracle documentation for UTL_FILE looking
for a proc that will allow me to write BLOBs to a file. Unfortuately i don't see
a proc that will work. The BLOBs we are working with are scanned images not
text. Is there a UTL_FILE.PUT_BLOB ( that i tried - what about an equivalent or
alternative). I have also considered the Java alternative but would prefer a
PL/SQL solution as that is where the group expertise is for support and
maintenance. Any assistance will be greatly appreciated. thanks, Fred

          Followup:

Until 9iR2 -- as stated -- java or c or some other 3gl is the only solution.

      Rating: 4
do you have an
      example for 9iR2?  February 10, 2003
Reviewer:
      gs
Tom,

Do you by any chance have an example on how to get this done in 9iR2?

Thanks

          Followup:

it is just utl_file.put_raw.  you'll have a blob, you'll dbms_lob.read or
dbms_lob.substr or whatever part of the blob, and utl_file.put_raw it.

      Rating: 4
How do I get
      around the 32K limit, if using utl_file?  February 11, 2003

Reviewer:  gs.
Thanks Tom for you response.

This is what i was trying and was getting the following error, if the length
exceeds 32K.

declare
*
ERROR at line 1:
ORA-29285: file write error
ORA-06512: at "SYS.UTL_FILE", line 18
ORA-06512: at "SYS.UTL_FILE", line 375
ORA-06512: at "SYS.UTL_FILE", line 990
ORA-06512: at line 35

declare
    i1 blob;
    i2 number;
    amt number := 10000;
    len number;
    my_vr raw(10000);
        l_output utl_file.file_type;
    p_dir varchar2(30) default 'TEST_DIR';
    p_file varchar2(30) default 'gs_test1';
begin
-- get the blob locator
    l_output := utl_file.fopen(p_dir, p_file, 'w', 32760);
    for l_cur in (SELECT record#, xsl mylob FROM docxsl where rownum = 1 order
by record#)
    loop
        len :=  DBMS_LOB.GETLENGTH(l_cur.mylob);
        i1 := l_cur.mylob ;
        dbms_output.put_line('Length of the Column :  ' || to_char(len));
        i2 := 1;
        if len < 10000 then
            DBMS_LOB.READ(i1,len,i2,my_vr);
            utl_file.put(l_output, my_vr );
        else
            DBMS_LOB.READ(i1,amt,i2,my_vr);
            utl_file.put(l_output, UTL_RAW.CAST_TO_VARCHAR2(my_vr) );
        end if;

        i2 := i2 + amt;
        while (i2 < len) loop   -- loop till entire data is fetched
        dbms_output.put_line('i2 : ' || to_char(i2));
            DBMS_LOB.READ(i1,amt,i2,my_vr);
            utl_file.put(l_output, UTL_RAW.CAST_TO_VARCHAR2(my_vr) );
            utl_file.fflush(l_output);
            i2 := i2 + amt ;
        end loop;
            utl_file.fclose(l_output);
    end loop;
end ;
/

Note: I combine the example from your site and metalink.

I have XSL files stored as BLOB in the DB. I wanted to retrieve them, the way
was they were stored. I don't want to put a "new line" after every 32K. Can I do
this using PL/SQL?

--I thought it is related to the question asked, hence I am not submitting a new
one.

Thanks very much.

          Followup:

If they are XSL why are they blobs?

but in any case, utl file has a maximum of 32k -- an unavoidable, no workaround,
hard limit.

      Rating: 5
No
      workaround!!!!  March 17, 2003
Reviewer:
      Steve  from UK
I trying to write a dbf file straight out of the database.  Right after the
header I then start to output the rows by looping through them thus:

for i in 1..vRow.count loop

    utl_file.put_raw(v_outputfile,vRow(i),TRUE);

end loop;

Now, this is fine for a small table but for anything bigger I get the write file
error - ora-29285.  Is there really no way around this?  Surely there must be!

          Followup:

got an example, use all_objects and see if it reproduces -- if so, post code
here and I'll look at it.

      Rating: 5
You'll
      recognise most of this!  March 17, 2003
Reviewer:
      Steve  from UK
The code is an extended version of your dbf file extractor and it also uses your
str2tbl function!  Not complete yet as it can't handle dates properly or
decimals, but it demonstrates the problem:

CREATE OR REPLACE package dbase_pkg
as

    -- procedure to a load a table with records
    -- from a DBASE file.
    --
    -- Uses a BFILE to read binary data and dbms_sql
    -- to dynamically insert into any table you
    -- have insert on.
    --
    -- p_dir is the name of an ORACLE Directory Object
    --       that was created via the CREATE DIRECTORY
    --       command
    --
    -- p_file is the name of a file in that directory
    --        will be the name of the DBASE file
    --
    -- p_tname is the name of the table to load from
    --
    -- p_cnames is an optional list of comma separated
    --          column names.  If not supplied, this pkg
    --          assumes the column names in the DBASE file
    --          are the same as the column names in the
    --          table
    --
    -- p_show boolean that if TRUE will cause us to just
    --        PRINT (and not insert) what we find in the
    --        DBASE files (not the data, just the info
    --        from the dbase headers....)

    procedure load_Table( p_dir    in varchar2,
                          p_file   in varchar2,
                          p_tname  in varchar2,
                          p_cnames in varchar2 default NULL,
                          p_show   in BOOLEAN default FALSE);

    -- procedure to dump a file to disk in DBASE file format.
    --
    -- in addition to the above, p_where_clause is the filter
    -- which if null will dump the entire table.

    procedure dump_table (p_dir          in varchar2,
                            p_file         in varchar2,
                          p_tname        in varchar2,
                          p_cnames       in varchar2 default NULL,
                          p_where_clause in varchar2 default ' 1=1 ');
end;
/
CREATE OR REPLACE package body dbase_pkg
as

-- Might have to change depending on platform!!!
-- Controls the byte order of binary integers read in
-- from the dbase file
BIG_ENDIAN      constant boolean default TRUE;

type dbf_header is RECORD
(
    version    varchar2(25), -- dBASE version number
    year       int,          -- 1 byte int year, add to 1900
    month      int,          -- 1 byte month
    day        int,             -- 1 byte day
    no_records VARCHAR2(8),             -- number of records in file,
                             -- 4 byte int
    hdr_len    VARCHAR2(4),             -- length of header, 2 byte int
    rec_len    VARCHAR2(4),             -- number of bytes in record,
                             -- 2 byte int
    no_fields  int           -- number of fields
);

type field_descriptor is RECORD
(
    name     varchar2(11),
    fname    varchar2(30),
    type     char(1),
    length   int,   -- 1 byte length
    decimals int    -- 1 byte scale
);

type field_descriptor_array
is table of
field_descriptor index by binary_integer;

type rowArray
is table of
varchar2(4000) index by binary_integer;

g_cursor binary_integer default dbms_sql.open_cursor;

function ite( tf in boolean, yes in varchar2, no in varchar2 )
        return varchar2
is
begin
    if ( tf ) then
       return yes;
    else
       return no;
    end if;
end ite;

-- Function to convert a binary unsigned integer
-- into a PLSQL number

function to_int( p_data in varchar2 ) return number
is
    l_number number default 0;
    l_bytes  number default length(p_data);
begin
    if (big_endian)
    then
        for i in 1 .. l_bytes loop
            l_number := l_number +
                              ascii(substr(p_data,i,1)) *
                                           power(2,8*(i-1));
        end loop;
    else
        for i in 1 .. l_bytes loop
            l_number := l_number +
                         ascii(substr(p_data,l_bytes-i+1,1)) *
                         power(2,8*(i-1));
        end loop;
    end if;

    return l_number;
end;

-- Routine to parse the DBASE header record, can get
-- all of the details of the contents of a dbase file from
-- this header

procedure get_header
(p_bfile        in bfile,
 p_bfile_offset in out NUMBER,
 p_hdr          in out dbf_header,
 p_flds         in out field_descriptor_array )
is
    l_data            varchar2(100);
    l_hdr_size        number default 32;
    l_field_desc_size number default 32;
    l_flds            field_descriptor_array;
begin
    p_flds := l_flds;

    l_data := utl_raw.cast_to_varchar2(
                       dbms_lob.substr( p_bfile,
                                        l_hdr_size,
                                        p_bfile_offset ) );
    p_bfile_offset := p_bfile_offset + l_hdr_size;

    p_hdr.version    := ascii( substr( l_data, 1, 1 ) );
    p_hdr.year       := 1900 + ascii( substr( l_data, 2, 1 ) );
    p_hdr.month      := ascii( substr( l_data, 3, 1 ) );
    p_hdr.day        := ascii( substr( l_data, 4, 1 ) );
    p_hdr.no_records := to_int( substr( l_data,  5, 4 ) );
    p_hdr.hdr_len    := to_int( substr( l_data,  9, 2 ) );
    p_hdr.rec_len    := to_int( substr( l_data, 11, 2 ) );
    p_hdr.no_fields  := trunc( (p_hdr.hdr_len - l_hdr_size)/
                                           l_field_desc_size );

    for i in 1 .. p_hdr.no_fields
    loop
        l_data := utl_raw.cast_to_varchar2(
                         dbms_lob.substr( p_bfile,
                                          l_field_desc_size,
                                          p_bfile_offset ));
        p_bfile_offset := p_bfile_offset + l_field_desc_size;

        p_flds(i).name := rtrim(substr(l_data,1,11),chr(0));
        p_flds(i).type := substr( l_data, 12, 1 );
        p_flds(i).length  := ascii( substr( l_data, 17, 1 ) );
        p_flds(i).decimals := ascii(substr(l_data,18,1) );
    end loop;

    p_bfile_offset := p_bfile_offset +
                          mod( p_hdr.hdr_len - l_hdr_size,
                               l_field_desc_size );
end;

function build_insert
( p_tname in varchar2,
  p_cnames in varchar2,
  p_flds in field_descriptor_array ) return varchar2
is
    l_insert_statement long;
begin
    l_insert_statement := 'insert into ' || p_tname || '(';
    if ( p_cnames is NOT NULL )
    then
        l_insert_statement := l_insert_statement ||
                              p_cnames || ') values (';
    else
        for i in 1 .. p_flds.count
        loop
            if ( i <> 1 )
            then
               l_insert_statement := l_insert_statement||',';
            end if;
            l_insert_statement := l_insert_statement ||
                            '"'||  p_flds(i).name || '"';
        end loop;
        l_insert_statement := l_insert_statement ||
                                           ') values (';
    end if;
    for i in 1 .. p_flds.count
    loop
        if ( i <> 1 )
        then
           l_insert_statement := l_insert_statement || ',';
        end if;
        if ( p_flds(i).type = 'D' )
        then

            l_insert_statement := l_insert_statement ||
                     'to_date(:bv' || i || ',''yyyymmdd'' )';
        else
            l_insert_statement := l_insert_statement ||
                                                ':bv' || i;
        end if;
    end loop;
    l_insert_statement := l_insert_statement || ')';

    return l_insert_statement;
end;

function get_row
( p_bfile in bfile,
  p_bfile_offset in out number,
  p_hdr in dbf_header,
  p_flds in field_descriptor_array ) return rowArray
is
    l_data     varchar2(4000);
    l_row   rowArray;
    l_n     number default 2;
begin
    l_data := utl_raw.cast_to_varchar2(
                   dbms_lob.substr( p_bfile,
                                    p_hdr.rec_len,
                                    p_bfile_offset ) );
    p_bfile_offset := p_bfile_offset + p_hdr.rec_len;

    l_row(0) := substr( l_data, 1, 1 );

    for i in 1 .. p_hdr.no_fields loop
        l_row(i) := rtrim(ltrim(substr( l_data,
                                        l_n,
                                        p_flds(i).length ) ));
        if ( p_flds(i).type = 'F' and l_row(i) = '.' )
        then
            l_row(i) := NULL;
        end if;
        l_n := l_n + p_flds(i).length;
    end loop;
    return l_row;
end get_row;

procedure show( p_hdr    in dbf_header,
                p_flds   in field_descriptor_array,
                p_tname  in varchar2,
                p_cnames in varchar2,
                p_bfile  in bfile )
is
    l_sep varchar2(1) default ',';

    procedure p(p_str in varchar2)
    is
        l_str long default p_str;
    begin
        while( l_str is not null )
        loop
            dbms_output.put_line( substr(l_str,1,250) );
            l_str := substr( l_str, 251 );
        end loop;
    end;
begin
    p( 'Sizeof DBASE File: ' || dbms_lob.getlength(p_bfile) );

    p( 'DBASE Header Information: ' );
    p( chr(9)||'Version = ' || p_hdr.version );
    p( chr(9)||'Year    = ' || p_hdr.year   );
    p( chr(9)||'Month   = ' || p_hdr.month   );
    p( chr(9)||'Day     = ' || p_hdr.day   );
    p( chr(9)||'#Recs   = ' || p_hdr.no_records);
    p( chr(9)||'Hdr Len = ' || p_hdr.hdr_len  );
    p( chr(9)||'Rec Len = ' || p_hdr.rec_len  );
    p( chr(9)||'#Fields = ' || p_hdr.no_fields );

    p( chr(10)||'Data Fields:' );
    for i in 1 .. p_hdr.no_fields
    loop
        p( 'Field(' || i || ') '
             || 'Name = "' || p_flds(i).name || '", '
             || 'Type = ' || p_flds(i).Type || ', '
             || 'Len  = ' || p_flds(i).length || ', '
             || 'Scale= ' || p_flds(i).decimals );
    end loop;

    p( chr(10) || 'Insert We would use:' );
    p( build_insert( p_tname, p_cnames, p_flds ) );

    p( chr(10) || 'Table that could be created to hold data:');
    p( 'create table ' || p_tname );
    p( '(' );

    for i in 1 .. p_hdr.no_fields
    loop
        if ( i = p_hdr.no_fields ) then l_sep := ')'; end if;
        dbms_output.put
        ( chr(9) || '"' || p_flds(i).name || '"   ');

        if ( p_flds(i).type = 'D' ) then
            p( 'date' || l_sep );
        elsif ( p_flds(i).type = 'F' ) then
            p( 'float' || l_sep );
        elsif ( p_flds(i).type = 'N' ) then
            if ( p_flds(i).decimals > 0 )
            then
                p( 'number('||p_flds(i).length||','||
                              p_flds(i).decimals || ')' ||
                              l_sep );
            else
                p( 'number('||p_flds(i).length||')'||l_sep );
            end if;
        else
            p( 'varchar2(' || p_flds(i).length || ')'||l_sep);
        end if;
    end loop;
    p( '/' );
end;

procedure load_Table( p_dir in varchar2,
                      p_file in varchar2,
                      p_tname in varchar2,
                      p_cnames in varchar2 default NULL,
                      p_show in boolean default FALSE )
is
    l_bfile      bfile;
    l_offset  number default 1;
    l_hdr     dbf_header;
    l_flds    field_descriptor_array;
    l_row      rowArray;
begin
    l_bfile := bfilename( p_dir, p_file );
    dbms_lob.fileopen( l_bfile );

    get_header( l_bfile, l_offset, l_hdr, l_flds );

    if ( p_show )
    then
        show( l_hdr, l_flds, p_tname, p_cnames, l_bfile );
    else
        dbms_sql.parse( g_cursor,
                        build_insert(p_tname,p_cnames,l_flds),
                        dbms_sql.native );

        for i in 1 .. l_hdr.no_records loop
            l_row := get_row( l_bfile,
                              l_offset,
                              l_hdr,
                              l_flds );

            if ( l_row(0) <> '*' ) -- deleted record
            then
                for i in 1..l_hdr.no_fields loop
                    dbms_sql.bind_variable( g_cursor,
                                            ':bv'||i,
                                            l_row(i),
                                            4000 );
                end loop;
                if ( dbms_sql.execute( g_cursor ) <> 1 )
                then
                    raise_application_error( -20001,
                                 'Insert failed ' || sqlerrm );
                end if;
            end if;
        end loop;
    end if;

    dbms_lob.fileclose( l_bfile );
exception
    when others then
        if ( dbms_lob.isopen( l_bfile ) > 0 ) then
            dbms_lob.fileclose( l_bfile );
        end if;
        RAISE;
end;

procedure put_header (p_tname        in varchar2,
                        p_cnames       in varchar2 DEFAULT NULL,
                      l_hdr          in out dbf_header,
                      vFlds          in out field_descriptor_array)

is

v_value_list  strTableType;

vCursor   varchar2(2000);
type rc IS ref cursor;
col_cur rc;
i  INTEGER:=0;

begin

IF p_cnames IS NOT NULL THEN
    v_value_list:=str2tbl(UPPER(p_cnames));
    vCursor:='select substr(column_name,1,11),
                     case data_type
                     when ''DATE'' then ''D''
                     when ''NUMBER'' then ''N''
                     else ''C'' end ,
                     case data_type
                     when ''NUMBER'' then NVL(data_precision,22)
                     when ''DATE'' then 8
                     else data_length end,
                     case data_type
                     when ''NUMBER'' then data_scale
                     end ,
                     column_name   from all_tab_cols
                where column_name IN (select * from TABLE
(cast(str2tbl(UPPER('''||p_cnames||''')) as strTableType)))
                and   table_name='''||upper(p_tname)||'''
                order by column_id';

else

    vCursor:='select SUBSTR(column_name,1,11),
                     case data_type
                     when ''DATE'' then ''D''
                     when ''NUMBER'' then ''N''
                     else ''C'' end ,
                     case data_type
                     when ''NUMBER'' then NVL(data_precision,22)
                     when ''DATE'' then 8
                     else data_length end,
                     case data_type
                     when ''NUMBER'' then data_scale
                     end ,
                     column_name
              from all_tab_cols
              where table_name='''||upper(p_tname)||'''
              order by column_id';
END IF;

open col_cur for vCursor;

loop
    i:=i+1;
    fetch col_cur into
vFlds(i).name,vFlds(i).type,vFlds(i).length,vFlds(i).decimals,vFlds(i).fname;
    exit when col_cur%notfound;

end loop;

close col_cur;

l_hdr.version    :='03';
l_hdr.year       :=to_number(to_char(sysdate,'yyyy'))-1900;
l_hdr.month      :=to_number(to_char(sysdate,'mm'));
l_hdr.day        :=to_number(to_char(sysdate,'dd'));
l_hdr.rec_len    :=1; -- to be set later
l_hdr.no_fields  :=vFlds.COUNT;
l_hdr.hdr_len    :=to_char((l_hdr.no_fields*32)+33,'FM000x');

end;

procedure put_rows (p_tname IN  varchar2,
                      p_where_clause in varchar2 default '1=1 ',
                    vRow in out rowarray,
                    vFlds in field_descriptor_array)

is
type rc is ref cursor;

cur rc;

i  integer:=0;
vSelectList  VARCHAR2(32767);

v_cur VARCHAR2(32767);

begin

for l in 1..vFlds.count loop

vSelectList:=vSelectList||ite(l!=1,'||','')||'utl_raw.cast_to_raw(rpad(NVL('||
case when vFlds(l).type='N' then 'to_char(' end ||vFlds(l).fname||case when
vFlds(l).type='N' then ')' end ||','' ''),'||vFlds(l).length||','' ''))';

end loop;

v_cur:='select '||vSelectList||' from '||p_tname||' where '||p_where_clause;

open cur for v_cur;

loop

    i:=i+1;
    fetch cur into vRow(i);
    exit when cur%notfound;

end loop;

close cur;

end;

procedure dump_table (p_dir          in varchar2,
                        p_file         in varchar2,
                      p_tname        in varchar2,
                      p_cnames       in varchar2 default NULL,
                      p_where_clause in varchar2 default ' 1=1 ')

is
l_hdr dbf_header;
vFlds field_descriptor_array;
vRow  rowarray;
v_cnames  VARCHAR2(4000);
v_outputfile UTL_FILE.FILE_TYPE;

vCount int;

vStartTime  DATE;
vEndTime    DATE;
begin

    vStartTime:=sysdate;

    put_header(p_tname,p_cnames,l_hdr,vFlds);

    put_rows(p_tname,p_where_clause,vRow,vFlds);

    v_outputfile := utl_file.fopen(p_dir,p_file,'w',32767);

    for i in 1..vFlds.count loop

        l_hdr.rec_len:=l_hdr.rec_len+vFlds(i).length;

    end loop;

    l_hdr.rec_len    :=to_char(to_number(l_hdr.rec_len),'FM000x');

    l_hdr.rec_len   :=substr(l_hdr.rec_len,-2)||
                       substr(l_hdr.rec_len,1,2);

    l_hdr.no_records :=to_char(vRow.count,'FM0000000x');

    l_hdr.no_records:=substr(l_hdr.no_records,-2)||
                       substr(l_hdr.no_records,5,2)||
                      substr(l_hdr.no_records,3,2)||
                      substr(l_hdr.no_records,1,2);

    l_hdr.hdr_len:=substr(l_hdr.hdr_len,-2)||
                       substr(l_hdr.hdr_len,1,2);

    utl_file.put_raw(v_outputFile,

rpad(l_hdr.version||to_char(l_hdr.year,'FM0x')||to_char(l_hdr.month,'FM0x')||
              to_char(l_hdr.day,'FM0x')||l_hdr.no_records||l_hdr.hdr_len||
              l_hdr.rec_len,64,'0'));

    for i in 1..vFlds.count loop

utl_file.put_raw(v_outputFile,utl_raw.cast_to_raw(vFlds(i).name)||replace(rpad('0
0',12-length(vFlds(i).name),'#'),'#','00')||

utl_raw.cast_to_raw(vFlds(i).type)||'00000000'||

to_char(vFlds(i).length,'FM0x')||'000000000000000000000000000000');
    end loop;

    -- terminator for the field names
    utl_file.put_raw(v_outputFile,'0D');

    for i in 1..vRow.count loop

        utl_file.put_raw(v_outputfile,'20'||vRow(i),TRUE);

            end loop;

    if utl_file.IS_OPEN(v_outputFile ) then
        UTL_FILE.FCLOSE(v_outputFile);
    end if;

    vEndTime:=sysdate;

    dbms_output.put_line('Started - '||to_char(vStartTime,'HH24:MI'));
    dbms_output.put_line('Finished - '||to_char(vEndTime,'HH24:mi'));
    dbms_output.put_line('Elapsed -
'||to_char((vEndTime-vStartTime),'hh24:mi'));
exception
when others then
    utl_file.fclose(v_outputFile);
    raise;

end;

end;
/

SQL>  exec
dbase_pkg.dump_table('c:\dbf','test.dbf','all_objects','owner,object_name,subobje
ct_name,
object_id')
BEGIN
dbase_pkg.dump_table('c:\dbf','test.dbf','all_objects','owner,object_name,subobje
ct_name,objec

*
ERROR at line 1:
ORA-29285: file write error
ORA-06512: at "STEVE.DBASE_PKG", line 564
ORA-06512: at line 1

SQL>

But if I limit the number of rows:

SQL> exec
dbase_pkg.dump_table('c:\dbf','test.dbf','all_objects','owner,object_name,subobje
ct_name,o
bject_id','rownum<100')

PL/SQL procedure successfully completed.

SQL>

          Followup:

A bit hokey but this seems to work around the issue - which seems to be a line
buffering issue:

551      for i in 1..vRow.count
552      loop
553          utl_file.put_raw(v_outputfile,'20'||vRow(i),TRUE);
554          if ( mod(i,200) = 0 )
555          then
556              utl_file.fclose( v_outputFile );
557              v_outputfile := utl_file.fopen(p_dir,p_file,'a',32767);
558          end if;
559      end loop;

Suggestion:

o put together the smallest test case possible you can
o submit that to support
o use the workaround for the time being...

      Rating:
      5
  March 17, 2003
Reviewer:  steve
       from UK
Thanks Tom - I wondered whether closing and re-opening the file would work but
thought that I must be missing something. I'll prepare a test case for support
and post here if they come up with a solution.

Once again, thanks

Steve

      Rating: 4
Did the dbf
      conversion work?  May 19, 2003
Reviewer:
      dharma  from Ca
I am trying to convert an oracle table to dbf format. and am not able to do
that. Did steves code work? Just curious, I dont want to reinvent the wheel!

Thanks

          Followup:

it is right there for you to try -- go for it.

      Rating: 5
Yes it
      did  May 19, 2003
Reviewer:  Steve
       from UK
although you're still constrained by the 32k limit - Oracle support suggested
making sure that I had a utl_file.new_line every so often but his, of course,
added a couple of unwanted bytes to the file - ok for an image but not for dbf.
So, in the end instead of using utl_file.put_raw, I had to use a small java proc
to output the file - it works a treat but didn't need 9iR2 to work!!

      Rating: 5
I should
      add.....  May 20, 2003
Reviewer:  steve
       from UK
that Oracle say that the 32k limit is fixed in 10i but that there is no backport
for it.

      Rating: 5
It
      Worked!!  May 21, 2003
Reviewer:  dharma
       from CA
The first time I tried to do it on my own and messed it up, and then the copy
paste worked fine :)
Thanks the conversion to dbf worked perfect. added the code for date and the
workaround for utl_file.

Got a a good understanding of utl_raw alsow :)

One question that bothers me is the header length is 32 byte, but why do we have
to allocate 64 bytes. (assumed because its rpad'ed with 64 0's; Is it because 32
bytes in hex is 64 in ascii?

Thanks to steve too :)

-dharma

      Rating: 5
Blob to File
      (9.2)  August 06, 2003
Reviewer:  Ray White
       from Ottawa,on,ca
Hi, I am using an above snippet (modified)

declare
    vblob blob;
    vstart number:=1;
    bytelen number := 32000;
    len number;
    my_vr raw(32000);
        l_output utl_file.file_type;
    p_dir varchar2(30) default 'f:\gol\ora_vscan';
    p_file varchar2(30) default 'CMsitegarden.pdf';
begin
-- get the blob locator
    l_output := utl_file.fopen(p_dir, p_file, 'w', 32760);
    for l_cur in (SELECT DOCUMENT_CONTENT mylob FROM gol_attached_document where
SUPP_DOCUMENT_NUMBER = 4 and rownum =1)
    loop
        len :=  DBMS_LOB.GETLENGTH(l_cur.mylob);
        vblob := l_cur.mylob ;
        dbms_output.put_line('Length of the Column :  ' || to_char(len));
        vstart := 1;
        while (vstart < len) loop   -- loop till entire data is fetched
        dbms_output.put_line('vstart : ' || to_char(vstart));
            DBMS_LOB.READ(vblob,bytelen,vstart,my_vr);
            utl_file.put_raw(l_output,my_vr);
            utl_file.fflush(l_output);
            vstart := vstart + bytelen ;
        end loop;
        utl_file.fclose(l_output);
    end loop;
end ;

This outputs my file fine. But it will not open in the software associated.

IE a .doc word cannot open
a .pdf acrobat cannot open
the file sizes match the source.

Can you tell me what I am doing wrong.

for .txt files in the blob do I have to do anything different?

tia
Ray

      Rating: 5
Blob to
      File  August 06, 2003
Reviewer:  Ray
       from ottawa, on,ca
More info on the above.

I looked at the hex of both the source and the after upload and write. Every
time the source had a 0A , the resulting file had a 0D0A. the resulting file
also had a 0D0A at the end.

          Followup:

please contact support for this one -- looks like a windowism -- \r\n is the DOS
carriage return/linefeed end of line marker.  On unix'es this doesn't happen and
thats all I got.

      Rating: 5
Java program
      posted by "Mio from US" is really useful. Thanks Mio &
      Tom.  December 31, 2003
Reviewer:  Sami
       from NJ,USA

August 09, 2003 Dennis -- Thanks for the question regarding "Size
      of Development", version 9.2.0

      You Asked
Tom,

1) I've looked in a few places (including here), but couldn't find what I was
looking for.  We are getting ready to build a data warehouse and want to know
the best approach to take.  There are two thoughts here.  Put everything down in
Dev for testing and building, and put a representative sample in dev (using
dbms_stats to simulate performance).  What way makes  sense to you?

2) In using global temp tables, I've seen a lot of people using the query that
will actually populate it, but adding an 'and 1=0' at the bottom of it.  Why is
that?  If you didn't have the 1=0, would it be some kind of perpetual loop that
the temporary table kept trying to load rows into itself?  So you have to make
it empty and then load it?  That doesn't seem to make sense, but I haven't had
much time to try it out and see full implications from it.  I just went along
and used the 1=0.

3)  Function based indexes.  Have you ever successfully gathered stats on them
with dbms_stats?  I haven't tried for a little while, so maybe I missed a patch
set, but it used to throw ora-600 and ora-904 on me, unless I used cascade =
false.  (all indexed columns as the method opt).  Just wondering if you a) used
dbms_stats successfully on them or b) used compute statistics on them (as we do
now after getting the error).  Don't spend time making a test case on this, it
is something for me to test out, but if you know you can successfully, it'll
help motivate me to test it out :)

4)  Any word when the publishers/editors will be ready to launch the book?

Thanks, and welcome back.

Dennis

      and we said...
1) how would using dbms_stats "simulate performance".  All it might do is show
you what plan would be used against the full up data.  It would NOT in any way
shape or form tell you how well that plan really works.

If you want to test your system, you need pretty much full up representative
data.

If you (or anyone that you work with) can look at a plan and say "yes, that is
the best plan" -- I want to hire you.  faking the stats is not a good way to
"test performance" ;)

2) the 1=0 prevents ANY rows from going in.  It is a common technique used to
CREATE the global temporary table:

create global temporary table gtt as select * from t where 1=0;

that just create a structure, but doesn't do any "query".  If you see:

insert into gtt select * from t where 1=0;

that would be something to question -- that would just be a waste of keystrokes.

3)

ops$tkyte@ORA920> drop table t;

Table dropped.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t
  2  as
  3  select * from all_objects;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index t_idx1 on t(upper(object_name));

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace function f( p_str in varchar2) return
varchar2
  2  DETERMINISTIC
  3  as
  4  begin
  5          return upper(p_str);
  6  end;
  7  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index t_idx2 on t(f(object_name));

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_stats.gather_table_stats( user, 'T', cascade => TRUE
);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>

If you are getting an ora-600 in a reproducible manner like that, please open a
tar with support.  that would be "a bug"

4) august..
Review & Followup

      Rating: 3
heh, didn't
      change the name of my questions  July 29, 2003

Reviewer:  Dennis  from Missouri, USA
Thanks for the answers Tom.

1) You're right on Dbms_stats.  When are they going to make reading 10 blocks
like reading 40 million?? :)  I just meant faking out the optimizer.  Rats, I
wish I could tell from that :)   So do places with 10TB of data have a 10TB dev
environment, or do they do something else?  I don't want to be stingy, but I
don't want to be wasteful either.

2)  But is there a reason why you only want to create the structure for a global
temp table?  I think every example I looked at has the 1=0 (can't remember them
all, but the ones I remember do).  From the documentation:  "Unlike permanent
tables, temporary tables and their indexes do not automatically allocate a
segment when they are created. Instead, segments are allocated when the first
INSERT (or CREATE TABLE AS SELECT) is performed.".  Ok...so the create would put
rows in the temp table....don't they just go away until a user puts something in
the temp table?  Is the 1=0 just more semantically correct?  Or is there a
hidden problem in actually loading up rows into a temp table on creation?

3)  grr.  ok I need to not be worried about this other stuff.  How can you crank
out examples so fast?? :)  I had a tar open at one time, but closed it.   I was
just sitting here this morning trying to figure out when that was and if I had
tried it recently....since you were free for questions, I thought I'd be lazy.

4) Cool :)

          Followup:

1) some of them do - yes.  but -- many of them can also test on a fragment of
their data because they craft their system to work on some horizontal fragment
of data (eg: they employ partition elimination)

others just "throw it out there and see what sticks" which is sort of scary.  At
10tb, you can probably get away with some reasonably large -- representative
subset short of 10tb.

2) there is no hidden problem but -- consider:

create global temporary table gtt as select * from one_billion_row_table;

do you want to wait whilst we copy 1 billion rows into temp -- just to have them
deleted ?

it just makes the create go "faster"

3) ;)

      Rating:
      4
Thanks  July 29, 2003
Reviewer:
      Dennis  from Missouri, USA
Ah...that's the piece I was missing.  The creation wait :)  My problem is that
last time I used it as a 'place holder' for a complex update and I went ahead
and did the create temp, insert, update (real table), and drop temp all in one
session, and it seemed like a wasted step to not load and then load, so I
started thinking, and I remembered 1=0 all the time. So I got mad at myself and
started wondering why it really mattered for global temporary tables that are
put out for the whole database (since nobody is supposed to see those rows
except for whatever they put in).  I guess I should stop using temp tables in a
temporary fashion just to meet my needs, so I stop going down these thought
paths :)

Thanks!!   As for dev....heh, we are shot then...I think our data model is
vertical, horizontal, inside-out, upside-down, and it wouldn't surprise me if
it's crooked too.

Sorry for the easy questions.

Take care.
Dennis

      Rating: 3
Guess I'm not
      as lucky as you....  August 06, 2003
Reviewer:
      Dennis  from Missouri, USA
Tom,

I guess it's a problem with my function:

I created T and the two indexes (and function) that you made as below.   Didn't
get an error (I got the ora-600 on the big guy like before, and was trying to
figure out what was different between yours and mine).

Here you go: Forgive the few times it looks like the time jumps, I had a few
copy issues and the wrong command ran (didn't figure you would want those).
13:25:15 winkid1@MYOWN:HA52667> create table t
13:25:29   2  as
13:25:37   3  select * from all_objects;

Table created.

Elapsed: 00:00:07.05
13:25:53 winkid1@MYOWN:HA52667> create index t_idx1 on t(upper(object_name));

Index created.

Elapsed: 00:00:00.07
13:25:59 winkid1@MYOWN:HA52667> create or replace function f( p_str in varchar2)
return
13:26:07   2  varchar2
13:26:42   3
13:26:50   4
13:26:50   5
13:26:50   6  exit
13:26:52   7  ;
13:26:54   8  /

Warning: Function created with compilation errors.

Elapsed: 00:00:00.02
13:26:55 winkid1@MYOWN:HA52667> create or replace function f( p_str in varchar2)
return varchar2
13:27:01   2  DETERMINISTIC
13:27:10   3  as
13:27:16   4  begin
13:27:24   5  return upper(p_str);
13:27:33   6  end;
13:27:36   7  /

Function created.

Elapsed: 00:00:00.04
13:27:37 winkid1@MYOWN:HA52667> create index t_idx2 on t(f(object_name));

Index created.

Elapsed: 00:00:00.09
13:28:20 winkid1@MYOWN:HA52667> exec dbms_stats.gather_table_stats( user, 'T',
cascade => TRUE);

PL/SQL procedure successfully completed.

Elapsed: 00:00:08.07
13:29:32 winkid1@MYOWN:HA52667> exec dbms_stats.gather_table_stats( user, 'T',
method_opt => 'for al
l indexed columns size skewonly', cascade => TRUE, degree=>8);

PL/SQL procedure successfully completed.
13:44:57 winkid1@MYOWN:HA52667> create bitmap index t234 on
13:45:06   2  t(TO_NUMBER(TO_CHAR(ADD_MONTHS(created, 6), 'YYYY')))
13:45:58   3  /

Index created.

Elapsed: 00:00:00.01
13:45:59 winkid1@MYOWN:HA52667> exec dbms_stats.gather_table_stats( user, 'T',
cascade => TRUE);

PL/SQL procedure successfully completed.

Elapsed: 00:00:07.03
13:46:18 winkid1@MYOWN:HA52667> exec dbms_stats.gather_table_stats( user, 'T',
method_opt => 'for al
l indexed columns size skewonly', cascade => TRUE, degree=>8);
BEGIN dbms_stats.gather_table_stats( user, 'T', method_opt => 'for all indexed
columns size skewonly

*
ERROR at line 1:
ORA-00904: : invalid identifier
ORA-06512: at "SYS.DBMS_STATS", line 9136
ORA-06512: at "SYS.DBMS_STATS", line 9150
ORA-06512: at line 1

Elapsed: 00:00:12.00
13:46:45 winkid1@MYOWN:HA52667>

So, is it because of the method_opt?   I had tried HIDDEN before, but can't
remember the syntax (unless this is right...seems weird though, I don't think
it's correct):

13:46:45 winkid1@MYOWN:HA52667> exec dbms_stats.gather_table_stats( user, 'T',
method_opt => 'for al
l hidden columns size skewonly', cascade => TRUE, degree=>8);
BEGIN dbms_stats.gather_table_stats( user, 'T', method_opt => 'for all hidden
columns size skewonly'

*
ERROR at line 1:
ORA-00904: : invalid identifier
ORA-06512: at "SYS.DBMS_STATS", line 9136
ORA-06512: at "SYS.DBMS_STATS", line 9150
ORA-06512: at line 1

Elapsed: 00:00:10.06
13:51:08 winkid1@MYOWN:HA52667>

Thanks,
Dennis

          Followup:

Hdr: 2782919 9.2.0.2.0 RDBMS 9.2.0.2.0 FUNC_INDEX PRODID-5 PORTID-23
Abstract: ORA-904:GATHER_TABLE_STATS FAILS ON TABLE WITH  WIH FUNCTION-BASED
INDEX

it is fixed in 9203:

ops$tkyte@ORA920> create table t
  2  as
  3  select * from all_objects;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index t_idx1 on t(upper(object_name));

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace function f( p_str in varchar2) return
varchar2
  2  DETERMINISTIC
  3  as
  4  begin
  5  return upper(p_str);
  6  end;
  7  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index t_idx2 on t(f(object_name));

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_stats.gather_table_stats( user, 'T', cascade =>
TRUE);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_stats.gather_table_stats( user, 'T', method_opt =>
'for all indexed columns size skewonly', cascade => TRUE, degree=>8);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create bitmap index t234 on
  2  t(TO_NUMBER(TO_CHAR(ADD_MONTHS(created, 6), 'YYYY')))
  3  /

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_stats.gather_table_stats( user, 'T', cascade =>
TRUE);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_stats.gather_table_stats( user, 'T', method_opt =>
'for all indexed columns size skewonly', cascade => TRUE, degree=>8);

PL/SQL procedure successfully completed.

      Rating:
      3
Intriguing  August 11, 2003
Reviewer:
      Dennis  from Missouri, USA
Ack!  You caught me switching versions (and platforms) on you.  I was on my
Windows which is just 9.2.0.0.  The original error I got long ago involved the
ora-00904 that I could manipulate into an ora-00600, and I didn't bother with
the manipulation as I thought it was the same thing all over again.   However
the question posed was for 9.2.0.3.

I just ran a test on the box itself (I was confident I could replicate the
problem on my own box) and it must not like my table structure.  I can run fine
until I add a row of data.  Basically I was able to do your test fine, and then
I added a copy of the table structure and all the indexes, and it was fine,
until I added a row of data.  I'll leave you alone now and go play with support.
 Unless you are curious and want the table structure and all 12? indexes.  But
then you'd be playing support, and you probably have better things to do.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.3.0 - 64bit Production
With the Oracle Label Security, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production

USER is "WINKID1"
HII2> connect winkid1
Enter password:
Connected.
HII2> exec dbms_stats.gather_table_stats('GILLID', 'EXT_XPAYD_PAY_DETAILS',
method_opt=>'for all indexed columns size skewonly', cascade=>TRUE, degree=>8);

PL/SQL procedure successfully completed.

HII2> insert into gillid.ext_xpayd_pay_details select * from
hrdmadm.ext_xpayd_pay_details where rownum < 2;

1 row created.

HII2> commit;

Commit complete.

HII2> exec dbms_stats.gather_table_stats('GILLID', 'EXT_XPAYD_PAY_DETAILS',
method_opt=>'for all indexed columns size skewonly', cascade=>TRUE, degree=>8);
BEGIN dbms_stats.gather_table_stats('GILLID', 'EXT_XPAYD_PAY_DETAILS',
method_opt=>'for all indexed columns size skewonly', cascade=>TRUE, degree=>8);
END;

*
ERROR at line 1:
ORA-00600: internal error code, arguments: [20083], [996], [], [], [], [], [],
[]
ORA-06512: at "SYS.DBMS_STATS", line 9357
ORA-06512: at "SYS.DBMS_STATS", line 9371
ORA-06512: at line 1

August 09, 2003 Waleed -- Thanks for the question regarding "How
      could I call dll from PL/SQL or Oracle Developer", version 8.1.7

      You Asked
Dear Sir

How could I call from PL/SQL a dll written by VB.NET?

Thanks

      and we said...
So, they are calling simple DLL's .net -- cool.  Take a 15 year old technology
and call it "internet" and it is.

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76939/adg11rtn.htm#1656
described external routines.  Yes, PLSQL can invoked a good old fashioned DLL.
Review &
      Followup

      Rating: 5
You can also
      use ORA_FFI  April 08, 2003
Reviewer:  graeme
      king  from new york
In forms (although not sure about 9i forms) you can call a dll using the
built-in package ORA_FFI....see the help for more details.

graeme

      Rating: 1
Does Oracle COM
      Automation support VB.NET?  April 09, 2003

Reviewer:  Waleed  from Jordan
Can I call dll writen in VB.NET from pl/sql using Oracle COM Automation ?

I will appreciate if you provide me an example of how to call VB.NET dll from
pl/sql by using any of the technologies.

          Followup:

did you, umm, read the documentation I pointed you to?

it shows how to call a dll from plsql.  You simply write a wrapper.

Hey, here is an example:

    procedure pass(  p_in in varchar2, p_out  out  varchar2 )
    as
    language C name "pass_str" library demoPassing
    with context parameters
    ( CONTEXT,
      p_in   STRING, p_in  INDICATOR short,
      p_out  STRING, p_out INDICATOR short, p_out  MAXLEN int );

that passes a string to and fro a C dll (or .so or .sl whatever your OS
supports).  The C prototype that would map to would be:

     -- void pass_str
     --   (
     --  OCIExtProcContext  *,  /*   1 : With-Context  */
     --               char  *,  /*   2 : P_IN  */
     --              short   ,  /*   3 : P_IN (Indicator) */
     --               char  *,  /*   4 : P_OUT  */
     --              short  *,  /*   5 : P_OUT (Indicator) */
     --                int  *   /*   6 : P_OUT (Maxlen) */
     --   );

Now, what you need to do is figure out what the C prototype for this DLL is,
create the appropriate mapping, and away you go.  Given that I have not a single
machine capable of doing "VB" -- thats about as far as I'll be going here...

      Rating: 2
Only C
      routines  April 10, 2003
Reviewer:  waleed
       from Jordan
I would like to thank you very much, but Oracle said in Oracle8i Application
Developer's Guide - Fundamentals Chapter 10 "Only C routines and routines
callable from C code are supported", then I can not use the external procedures
to call VB.NET dll, what do you say?

Thanks

          Followup:

Routines in the DLL that use the C calling format.

Once it is compiled into object code -- VB looks just like C you know.

      Rating: 4
Connect from
      VB.net to Oracle by DLL  August 07, 2003
Reviewer:
      ThemVV  from USA
I want connect from VB.net to Oracle, I used
myConnection = New OracleClient.OracleConnection("Password=QLAC_OWNER;User
ID=QLAC_OWNER; Data Source=ANCHI;Persist Security Info=False")

or uses Oledb : provider="OraOLEDB.Oracle.1"
but all of them use Data Source = "TNSNAME"
I must give TNSName to use net8 but infact I don't want setup client Oracle or
net8 client (because version, in my project, the customer used older version:
6i,....).
Can you tell me the useful DLL use connect from VB.net to Unicode ORacle 9i
without using net8 (by IP and SID like JDBC)

          Followup:

It would be called "jdbc"

If you use ODBC, you need to have the client installed (and there was no "6i"
net client - there was a forms version called 6i but that was based on the 8i
client libraries.

August 09, 2003 -- Thanks for the question regarding "large pool
      value", version 8.1.6

      You Asked
Tom:

how to measure that large pool is large enough?

after i switch from dedicate to MTS, i got an error regarding
large pool saying that it can't allocate more,
after i add some memory to large_pool_size,everything is fine.

question:
how big is big enough?

      and we said...
you need enough memory to handle all of your sessions.

How much is enough is purely a function of how much memory your sessions need.

If you use a really big plsql table for example - say 5megs of PLSQL table data
-- in each session, you'll need at least 5meg/session in the large pool.  If you
stop using that PLSQL table -- you'll need 5meg LESS /session in the large pool.

Measure your UGA memory usage:

  1  select a.name, avg(b.value)
  2  from v$statname a, v$sesstat b
  3  where a.statistic#= b.statistic# and a.name like '%uga%'
  4* group by a.name
ops$tkyte@ORA9I.WORLD> /

NAME                           AVG(B.VALUE)
------------------------------ ------------
session uga memory               52214.3333
session uga memory max           54448.6667

and that'll give you a handle on how much large pool you need to configure (the
UGA is stored in the larg pool/SGA with MTS)

followup to comment one

correct -- that shows the AVERAGE UGA across all sessions, you need that much /
session in the large pool (plus some)
Review &
      Followup

      Rating:
      3
  August 14, 2001
Reviewer:
      A reader
I should multiple it by the number of concurrent user.
am i right?

      Rating:
      5
  August 14, 2001
Reviewer:
      A reader
as usual. solve my question perfectly

      Rating: 3
large pool
      value  August 14, 2001
Reviewer:  May
       from Seattle, WA USA
What if the result of the avg(b.value) is negative?

Here is the result I run the above query:

NAME                        AVG(B.VALUE)
--------------------        ------------
session uga memory          -4854.21052631579
session uga memory max      64224.1052631579

Should we just use the value of "session uga memory max"?

      Rating: 4
which uga
      should be used to calculate?  January 14, 2002

Reviewer:  A reader
which uga value should we use to calculate the large pool?

the

session uga memory

or

session uga memory max

our database average session uga memory is around 300K however the MAX is around
1MB

we are using MTS, concurrent user about 600

          Followup:

Take the AVG of the MAX.

If everyone needs the MAX at the same time -- you need that much ram available
for them -- else here comes the 4031....

      Rating: 4
uga stats in
      KB?  January 14, 2002
Reviewer:  Mike Wilson
       from Los Angeles
Are the stats in kilobytes or bytes.  Looks like bytes to me but my numbers
could go either way.

          Followup:

bytes

      Rating: 4
How much the
      Large Pool should be deallocated?  July 25, 2002

Reviewer:  Brain.Xu  from China.
hi,Tom:

  my customer 's HP server has 3G memory and it's in dedicated server mode.It
works around 300 or 400 users as usual and if turn it into MTS mode as your
function, Large Pool size should be:

    300 * 30k(avarage MAX value)=9,000k

  It's so bigger than physical memory.and i puzzled about it: this server cannot
be turned into mts mode?

  Thanks,hope for your reply.

      Rating: 4
modify
      contents  July 25, 2002
Reviewer:  Brain.Xu
       from China.
this :

300 * 30k(avarage MAX value)=9,000k

it is a mistake from my memory. so sorry about it.

i change question as follow contents:
----------------------------------------
my customer 's HP server has 3G memory and it's in dedicated server mode.It
works around 300 or 400 users as usual.

sometimes their DBA found physical memory used 98% and server is very very
busy,but the users is just around 300,
each use 214kb memory(as your "session uga memory" scripts).
so memory total used is:

300*214KB = 60MB

and sga is 1.2GB also(Data buffer plus shard pool buffer in init.ora file).
OS system used 200MB memory as we watch(by glance).

so 3G - 1.2G - .06G -0.2G = 1500MB

where it losted in ? is the server process used in OS level?
and we found the "ACTIVE" user is so little:

SVRMGR> select count(*) from v$session where status='ACTIVE' and username is not
null;
COUNT(*)
----------
        14
1 row selected.

and i use "top" command and found this:

CPU TTY     PID USERNAME PRI NI   SIZE    RES STATE    TIME %WCPU  %CPU COMMAND
 0   ?     5624 oracle   148 20 30912K  1836K sleep    0:08  3.36  3.35
oracleora817
 0   ?     5626 oracle   154 20 30912K  1836K sleep    0:21  3.35  3.35
oracleora817
 0   ?    22597 oracle   154 20 32064K  2988K sleep    0:17  2.08  2.08
oracleora817
 0   ?       33 root     152 20     0K     0K run      0:58  1.22  1.21 vxfsd
 1   ?     6732 oracle   154 20 30912K  1836K sleep    0:38  0.94  0.93
oracleora817

notice this volumn "size", it shows that a client connection used 30mb MEMORY as
OS level?it's right?

so its puzzled me.So worry about this question.I don't know the memory 's usage
,is oracle havenot this ability for 300 users' system in C/S mode?use MTS oracle
can be finer?

Thanks,hope for your reply.

          Followup:

Most operating systems run at 100% utilized like that and it is a good
thing.  I am most familar with Solaris -- that OS runs 100% on memory use
because the OS itself will use the RAM if you are not.  If your apps need more
RAM, the OS stops using it and gives it to you.

Since you cannot put memory (or CPU or IO) into a "bank" (you cannot save it up)
your goal is actually to run at or near 100% utilization!   Anything less is
"wasted".

TOP shows the SGA in the process.  TOP is useless when looking at Oracle
processes.

Your system sounds like it is running just dandy.

      Rating: 4
when is UGA
      released  October 21, 2002
Reviewer:
      A reader
Hi

When is the memory allocated to large pool in MTS released? When the client
disconnects?

By the way, the way we use to calculate the size of large pool is get average
max uga and multiply by number of possible users, however this leads me to some
doubts. In dedicated server PGA is assigned in the server processes, in MTS part
of PGA known as UGA is allocated in large pool. BUT! In MTS server processes are
shared! Why we dont simply multiply average max uga by the number of max shared
servers?

          Followup:

When the client shrinks their UGA.  (this is exactly why you should use a large
pool with MTS.  MTS memory is not managed in an LRU fashion - it is managed as a
heap.  Client says "I need memory" and keeps memory until it says "I no longer
need memory".  Putting allocations like that in with the shared pool where most
all other objects are managed LRU like - that is what causes the fragmentation.
Lots of UGA memory that cannot be released or moved until the client says "OK")

The UGA is not part of the PGA.  The UGA is the UGA and is either allocated in
the PGA or the SGA depending on the configuration (dedicated vs shared server).

The UGA = USER global area = allocated for each session.
The PGA = PROCESS global area = allocated for each process.

In shared server, the UGA is still allocated for each SESSION and contains that
SESSIONS state (and that is the reason it must be in the SGA at that point -- a
different process will need to access that UGA memory from call to call so it
cannot be in process specific memory)

      Rating:
      5
  December 21, 2002
Reviewer:
      A reader
Hi Tom,

I am getting below error .We are using MTS config.

SQL> select dbms_metadata.get_ddl('TABLE','XX') FROM DUAL;
select dbms_metadata.get_ddl('TABLE','XX') FROM DUAL
       *
ERROR at line 1:
ORA-04031: unable to allocate 65564 bytes of shared memory ("large
pool","unknown object","session heap","kuxLpxAlloc")
ORA-06512: at "SYS.UTL_XML", line 0
ORA-06512: at "SYS.DBMS_METADATA_INT", line 4157
ORA-06512: at "SYS.DBMS_METADATA", line 458
ORA-06512: at "SYS.DBMS_METADATA", line 615
ORA-06512: at "SYS.DBMS_METADATA", line 1221
ORA-06512: at line 1

Please advice.

Thanks

          Followup:

your large pool, where we allocate the sessions UGA from in MTS mode, isn't big
enough.  Make it bigger.

      Rating: 5
Limiting the
      Size of the SGA 9i  March 10, 2003
Reviewer:
      Sikandar Hayat Awan  from Pindi Bhattian - Pakistan

I was reading about New Features of 9i and in the Robert G. Freeman New Features
book on page 16 it is mentioned that "The redo log buffer, large pool, and java
pools cannot currently be dynamically resized.". While read from
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96521/create.htm#998095
it is menioned that "You can dynamically alter the initialization parameters
affecting the size of the buffer caches, shared pool, and large pool,".

I am confused about the large pool. The book was written for 9i 9.0.0.1 and is
this change in rel 2?

          Followup:

the large pool was added to the list of changeable ones in 9iR2

      Rating: 5
Another memory
      issue!!  June 07, 2003
Reviewer:  H S Anand
       from New Delhi, India
Hi Tom,
 Recently I was working on a database (9i Rel1) on a Windows 2000 Advance Server

Box. H/w:= Dell poweredge 2650 with around 4 gb of ram and 4 X 1.8 Ghz CPU.

My SGA is around 1.8 gigs. I have the following queries:
* The machine does not allowme to size SGA more than 1.8 GB
* There are no other applications running on that server [we have ensured that]
* when we startup the database, invariably, we encounter an
"out of memory" error!

If we reduce the size of one of the SGA components, slightly, or even if we
reduce the number of dispatchers/shared servers [we changed to MTS from
Dedicated Server because of this memory problem in the first place], we are able

to startup the database

But at times after shutting the database down, without changing any parameters,
when we startup the database ,
it gives the same error! "out of memory".

If we try to initiate a dedicated server process for the DBA, by specifying
(SERVER=DEDICATED) in the tnsNames.ora,
we get the following err:
TNS:LISTNER UNABLE TO CREATE A DEDICATED SERVER PROCESS.

Any suggestions/ideas???

Cheers!
H S Anand

          Followup:

you are using an operating system that has to be banged silly sideways to allow
you to use the memory on your machine.

You'll want to read the OS admin/install guide for your operating system (a
32bit one that uses a single process architecture, limiting the server to the
amount of ram a process can address).  You can through magical switches here and
there to get a couple more bytes of addressable memory.

Not being a windows admin, I don't really know all of the steps like setting the
3gig switch in the boot.ini and other stuff.. Its documented out there tho.  see

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/win.817/a73008/ch9.htm#1005774
for example

      Rating: 5
Is it correct
      to set a large pool of 2G?  August 06, 2003

Reviewer:  A reader
Hi Tom,
Is it possible to set a large pool of 2G on a shared server? In the oracle
documentation says that the 300 range values for the large_pool is 300 KB to at
least 2 GB (actual maximum is operating system-specific).
My current SGA size is 1.6G and we are planning to switch to shared server, we
are going to have 2000 concurrent connections, and the average for session uga
memory max is 1M right now.
So if I do the calculation that means a large_pool of 2G.
We are using Oracle 9ir2 64bit and the total memory availabe in the
server(Solaris) is 8G.
Is it correct to set the large_pool to 2G in this case?

Any comment will be really appreciated.

Thanks

          Followup:

yes it would be.

August 09, 2003 -- Thanks for the question regarding "How to find
      out if a database exists in a given system", version 8.1.7

      You Asked
Tom

Suppose if a server is given to you , and you are asked to see if there is a
database created( or in other words if a  database exists in that server) or
not, what is the quickest and what are the different ways in which you can find
out( in both windows and unix).

Thanks in advance

Rag

      and we said...
If a system was given to me and asked "is there a database on there", my answer
would be "it is not relevant is it, cause you have no clue what data might be
contained there in, so it is just bits and bytes"

There is no standard way -- no matter what you come up with, I'll show you 1,000
systems that do not conform to your approach.

On unix, there is an /etc/oratab - but many people (self included) ignore it and
do not maintain it.

On windoze there are some registry entries -- but they may or may not point to
an existing database.

Review &
      Followup

      Rating: 1
listener, data
      files  June 22, 2003
Reviewer:  Rag

Well, some what dissapointing answer from the guru. If is do a lsnrctl status
and it shows that the listener is listening, doesnt it prove that there is a
database existing .

Further, what are the registry entries you are talking about on Windows, and
what is the kind of information contained in oratabo on Unix.

          Followup:

No, you missed the point Rag.

I give you a server.  Ok, now what, what are you going to do?  Is the listener
on port 1521? 1525 1234? 5231?  which port?  can you tell me?  no, you cannot.
How are you going to "status it"

And further, it shows there are services configured.  It does not tell you
"database there".  It might, it might not.

And further, there could be a dozen databases on that server, none of which
registered with that listener.

But even so -- you gotta find it first, so tell me, how are you going to find
the listener?

If you have windows, open up your registry.  I have no windows to look at.  They
are in there, under some Oracle key.

This is what an /etc/oratab might have:

[tkyte@tkyte-pc-isdn Desktop]$ cat /etc/oratab
#

# This file is used by ORACLE utilities.  It is created by root.sh
# and updated by the Database Configuration Assistant when creating
# a database.

# A colon, ':', is used as the field terminator.  A new line terminates
# the entry.  Lines beginning with a pound sign, '#', are comments.
#
# Entries are of the form:
#   $ORACLE_SID:$ORACLE_HOME:<N|Y>:
#
# The first and second fields are the system identifier and home
# directory of the database respectively.  The third filed indicates
# to the dbstart utility that the database should , "Y", or should not,
# "N", be brought up at system boot time.
#
# Multiple entries with the same $ORACLE_SID are not allowed.
#
#
*:/usr/oracle/ora920/OraHome1:N

[tkyte@tkyte-pc-isdn Desktop]$

But mine is never kept upto date -- cause I don't use it.

      Rating:
      2
listener!=database  June 22, 2003

Reviewer:  W.Hwa Chan
you can start your listener service without any databases.
The question just likes give you a box with power cable unplugged, tell me what
os has been installed? :)

          Followup:

zactly.

      Rating: 3
Hypothetical
      Scenario  June 22, 2003
Reviewer:  Jack
      Wells  from Boca Raton, FL
Tom,

<HAVINGFUN>
Suppose Larry called you tomorrow morning and said "Tom, the PeopleSoft deal
just closed... we bought them!  However, they used a scorched-earth policy in
the data center, shutdown all 50 servers, and threw away all the documentation.
Rag, my special agent in Pleasanton, has just spent the last 8 hours in their
dumpster and cannot find any of the documentation.  For reasons I can't
disclose, I need to know what servers have Oracle database(s) on them and their
names, version number, and size.  Now, you need to keep this on the QT, so I
want you to personally do it (I just fired Rag).  I'll be there in my
jet-fighter in two hours to pick you up... can you get this done in the next 24
hours?  I'll be eternally greatful."
</HAVINGFUN>

Surely you wouldn't tell him his request is not relevant.

          Followup:

you'd only be able to "guess".

There isn't a single file you can look for.

don't say:

cd /
find . -name "init*.ora" -print

cause I'll give you the system that uses spfiles.  Don't say, well look for
either or.  Cause I'll give you the system that the dba's always used
"pfile=some_name" on the startup command (there are no init*.ora).

don't say look in /etc/oratab.

there is no 100% way -- sure I can list places to look

o look for init*.ora spfile*.ora -- they PROBABLY point to a database, at least
you could try setting the oracle_home/sid to that and startup -- see if they do.

o look for /etc/oratab or use "control panel" to view services on nt (or
regedit).  that'll give you a list of maybes (but there could be hidden ones out
there)

o see if you can find an rman respository ;)

o ps -auxww | grep pmon   -- to see whats running.

guess that would be where I would start.

but really, give me a real business case.

      Rating: 4
to: Jack Wells
        June 22, 2003
Reviewer:  freek  from
      Belgium
Jack,

Which password are you going to use to log on?

      Rating: 4
Passwords...
      hmmm.  June 22, 2003
Reviewer:  Jack Wells
       from Boca Raton, FL
Freek,

Good question!  Hmmmm.  I suppose if I have physical access to the box, I could
call up HP, Microsoft, RedHat, and Sun to find out the backdoor way to reset the
root password.

      Rating: 4
Not going to
      help you log onto Oracle  June 22, 2003
Reviewer:
      Jim  from Portland, OR
Getting root password isn't neccessarily going to help you log onto the Oracle
database.

          Followup:

well, unless the system is really locked down -- su - oracle from root will get
me in.  won't need a password.

only if root is one of those neutered roots -- that don't have all of the privs
would I be really stuck.

Even then, as long as I can copy the files -- to my own system (just need read
for a moment), I got it.

So, actually, I might just mount the disks on my own system...  forget their
boot disk.

      Rating: 5
I'll tell you
      whats disappointing  June 23, 2003
Reviewer:
      Trevor Welch
Hey Rag,

Pull your head in Pal...

I'll tell you what's disappointing that fact that
ungrateful people like you get to ask questions and others
more appreciative of Tom's time don't.

If this is your typical response to someone taking
the time to answer your questions for free and often
doing so in his own time then push off.

Tom I've said it before and I'll say it again.
There is a huge number of us who really appreciate
your asktom service.

      Rating: 4
Listner
        June 23, 2003
Reviewer:  Reaz  from
      Dhaka, Bangladesh
I agree we need to  appreciate Tom to provide us this valuable time, no question
about it.

How do we register a listner to a database. Is it by configuring Listener file
in Admin folder.

Best Regards.
Reaz/Dhaka

          Followup:

typically, the database registers with a listener after the database starts up
(show parameter listener at the sqlplus/svrmgrl prompt)

you can manually do it in the listener.ora file as well.

      Rating: 2
How to check
      from JSP/ASP?  June 23, 2003
Reviewer:  Tony
       from India
How to check wheather a DB is up or not from JSP or ASP?
Becoz I wanna give clear message saying "Database is down"

          Followup:

try to connect.

if you get an ORA-1034, database is down.

      Rating: 4
to: Jack
      Wells  June 23, 2003
Reviewer:  freek
       from belgium
Doh !

      Rating: 2
From ASP /
      JSP  June 23, 2003
Reviewer:  Abhijit

Tony,
You would get an ORA-??? Listener not found on specified port error.

Cheers,

      Rating: 5
db
      alive?  June 23, 2003
Reviewer:  Abdul Wahab
       from Jeddah
hi TOm,
   I don't know much about this command but i think i read about it some
where...
    Is there a command.. something like "sysresv" to find out details of
database with out logging in it?

          Followup:

not really details.  it'll show some IPC stuff for running instances and even
then only under the oracle home/oracle sid you happen to be in (meaning, you
need to know the database is there in the first place and it has to already be
running)

      Rating:
      4
ORACLE_HOME/ORACLE_SID env variables  June 23, 2003

Reviewer:  fan  from PA
Doesn't any of these env variables or the existence of
Oracle installation directory (or ANY oracle dir) gives you a clue if have a db
on that server ?

Thanks

          Followup:

A db?  maybe

50 db's?  maybe

0 db's?  maybe

an Oracle home says "there might be one" -- or there might not be, or there
might be 50.

      Rating: 4
How to Check
      Oracle Instance on a system  June 23, 2003

Reviewer:  Yong  from CA, USA
Here is my way to do

On a window machine
1) go to control panel and click services to see any Oracle service exists
2) on a dos window type regedit to launch registry editor
3) in HKEY_LOCAL_MACHINE->SOFTWARE folder to check any Oracle folder exists
4) on a dos window do
if you are oracle or admin user
sqlplus /nolog
connect internal for Oracle 8i
or
connect /as sysdba for Oracle 9i
do some select
or startup/shutdown

If you have multiple Oracle Home, it need to do some more investigation.

ON Unix

do
1) more /var/opt/oracle/oratab for solaris
   or
   more /etc/oratab of other platform
2) you can also check /etc/rc2.d/auto_startup_scripts
3) ps -ef | grep ora_
   or
   ps -aux| grep ora_
4) ps -ef | grep oracle
   or
   ps -aux | grep oracle

Hope this help and save Tom's time

      Rating:
      3
  June 24, 2003
Reviewer:  Dave
       from UK
that still wont tell you how many databases there are on the server, in fact the
Unix one could return nothing and there could be 1000 instances on the server -
it still hasnt helped you

          Followup:

yeah, no matter how many times we say it -- it won't sink in.

      Rating: 4
Turn it
      off  June 24, 2003
Reviewer:  Steve
       from UK
for a few weeks then see if the phone rings from someone asking where their
data's gone.  If the phone don't ring then there ain't any data on there worth
keeping!

Steve

      Rating: 4
/etc/oratab
      Replacements?  August 07, 2003
Reviewer:
      Sue  from Michigan
>>This is what an /etc/oratab might have:

  <snip>

>> But mine is never kept upto date -- cause I don't use it.

What do you use?  I assume you have something somewhat equivalent, if for no
other reason than shutting down or starting up all the various Oracle-related
processes when the box boots (but perhaps that's an incorrect assumption).

What about anybody else?  What do you use instead of /etc/oratab or
/var/opt/oracle/oratab?

          Followup:

I use a database per machine :)

I just maintain an "orastart" and "orastop" script in $ORACLE_HOME/bin and
maintain that manually.  the etc/rc.d stuff just does

su -c oracle orastart

to get it going (that way, I don't have to bug 'root' to modify stuff.  dbstart
was way too limiting -- you want to start a listener (first) and only need one
listener regardless of the number of machines.  You then start the database(s).
Maybe you need to fire up ctxsrv as well (interMedia) for some purpose, maybe
you want to start up other daemons as well).

so, I just add to a very very simple script, that Oracle has 100% control over.

August 10, 2003 wallacel -- Thanks for the question regarding
      "high water mark", version 8.1.6

      You Asked
Tom:

when i insert lots of data in a table and later on delete most
of them, high water mark is high, if it is possible for me
to deallocate those space?

if so, can you tell me how?

I know i can use
alter table tableA deallocate unused keep integer
to deallocate space above high water mark.

Thanks

      and we said...
A high water by definition is a high water mark.  Just like
after a flood -- you can never reduce the high water mark on a
building, unless you rip the building down and build a new one.

The only ways to reduce the high water mark are:

o drop and recreate the object (exp/imp)
o truncate it if its empty (or exp the data, truncate it, imp
the data)
o in 8.1 and up "alter TABLE T move ....." followed immediately
by an index rebuild of all indexes will do it as well without
ever taking the data out of the table..
Review &
      Followup

      Rating:
      5
  May 30, 2002
Reviewer:
      A reader
I have some indexes that have very high initial extents and little data. Is it
possible at all to shrink them (I want to give a smaller initial extent size)
online/offline? Or the only way is to drop and recreate? I tried the "alter
index deallocate unused" without success. Thanks.

          Followup:

look up the alter index rebuild ONLINE command (with whatever new storage
parameters you want to use)

      Rating:
      4
  June 28, 2002
Reviewer:  john

tom,

i have job that runs for every 10 minutes that does:

delete from t;
insert into t from select * from t@remotedb;

i think it will be a problem with the high water mark.

there are other reports accessing this table parelly, so i can't issue truncate
the table before inserting.

how can i handle this?

          Followup:

Do you full scan the table or index read it?  Index read it, who cares about a
HWM.  Full scan it -- then we need to know -- How big is the table?

      Rating: 3
What is high
      water mark?  June 28, 2002
Reviewer:  Reddy
       from Bangalore, India
Dear Tom

So many time i heard about high water mark. But I saw some documenation. But no
clear. Actually what is that hiwater mark, why it is calling like that, and how
to find it, with this any performace issuses will arise.

Thanks

Reddy

          Followup:

You have my book ( you should, you've asked lots of questions ;)

I describe it in there, with pictures and all.  Chapter on Tables. Page 199.

Search this site for HWM and you'll find quite a few articles on it as well.

      Rating: 5
Reducing
      HWM  August 07, 2003
Reviewer:  B  from
      India
Hi Tom,

In your reply above you have said there are three ways to reduce HWM. Any other
additions to the list in 9i.

Regards,
B

          Followup:

nope not in 9i

August 10, 2003 Ik -- Thanks for the question regarding "Buffer
      Cache Hit Ratio", version 8.1.7

      You Asked
Tom,
   Given below are the details from the database i work on. I understand that
very high cache hit ratios are also very bad, but here the value seems to be
negative. Am i doing something wrong here?

Is it (1 - (Physical reads /(db block gets+consistent gets)))*100?

db block gets    2267538243.00
consistent gets    18446744034366600000.00
physical reads    18446744066965700000.00
Buffer Cache Hit Ratio    -0.000000164427738

dbblock buffers is set to 40000

This a Datawarehouse and yes, we do have serious performance problems. We are
using 8.1.7. Should we increase the buffers?

Also, please tell me why is a high value of consistent gets bad. Consistent gets
mean reading from disk into buffers and then reading blocks from the buffer,
right? We would want our queries to be accessing data from the buffers rather
than from disk. Am i wrong again?

thanks a zillion.......

      and we said...
Its a bug (if you contact support and reference bug 818237 there may be a patch)
-- they are overflowing a number.  Using the query below -- we mask it out
(strip the high bytes).  Based on those numbers:

ops$tkyte@ORA920.US.ORACLE.COM> create or replace function v( p_num in number )
return number
  2  as
  3  begin
  4      return to_number(substr( to_char(p_num,'fm'||rpad('0',15,'0')||'x'), 9
), rpad('x',8,'x'));
  5  end;
  6  /

Function created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select (1-
(v(18446744066965700000)/(v(2267538243)+v(18446744034366600000))))
  2   from dual
  3  /

(1-(V(18446744066965700000)/(V(2267538243)+V(18446744034366600000))))
---------------------------------------------------------------------
                                                           .685733504

Looks like a 68.5% cache hit.
Review &
      Followup

      Rating:
      5
Thanks...  October 19, 2002
Reviewer:
      Ik  from BG

      Rating: 4
formula in
      8i  November 28, 2002
Reviewer:  sl
       from Hong Kong
Hi Tom,
   I 've read from Metalink that the Buffer Cache Hit Ratio formula stated in
Oracle 8i Tuning Guide is wrong. The formula should be:

Hit Ratio = 1-(physical reads - physical reads direct)
            /(db block gets + consistent gets)

Is it correct?

          Followup:

it isn't really relevant.  cache hit ratios -- yuck.  you know what, in so many
years, I've never looked at one.

that one you provide takes into consideration that temp and parallel query
bypass the buffer cache.  So, do you want to count those reads or not.  It is
just a way to inflate your cache hit ratio, which seems to make people "happy" -
Yeah! I have a 98% cache hit.

goto www.hotsos.com and get the paper "are you still using cache hit ratios"

      Rating:
      1
  November 29, 2002
Reviewer:  Shreyas
       from India
I have really not understood that with the same values how can the hit ratio be
different using the same formula because this is simple maths.
Or am I interpreting your explanation wrongly

          Followup:

I have no idea where you are coming from with this?

what "same values" do you see?

      Rating:
      2
  November 29, 2002
Reviewer:
      Shreyas
I think I interpreted your reply wrongly. I just went through the explaination
on the bug you mentioned on the metalink site. What I understood from the site
is as the values are exorbitantly high one has to mask higher byte to get the
right values. In this particular case if you convert the values to hexadecimal,
hex value has leading 0xFFFFFFFF. Is the criteria to find out overflowing
numbers as mentioned in metalink.

          Followup:

I am masking out the high bytes which are set wrong, yes.  getting rid of the
leading ffffff's

      Rating: 5
STILL
      Problem  April 02, 2003
Reviewer:  Riaz
      Shahid  from PRAL , Islamabad, Pakistan
Hello Tom !

Consider this:

cr@STARR.LHR> show sga

Total System Global Area  214964252 bytes
Fixed Size                    75804 bytes
Variable Size             107335680 bytes
Database Buffers          106496000 bytes
Redo Buffers                1056768 bytes

cr@STARR.LHR> SELECT phy.value "Physical Reads",
  2             cur.value "DB Block Gets",
  3             con.value "Consistent Gets",
  4             1-((phy.value)/((cur.value)+(con.value)))*100 "Cache hit ratio"
  5      FROM   v$sysstat cur, v$sysstat con, v$sysstat phy
  6     WHERE  cur.name='db block gets'
  7      AND    con.name='consistent gets'
  8     AND    phy.name='physical reads'
  9  /

Physical Reads DB Block Gets Consistent Gets Cache hit ratio
============== ============= =============== ===============
       4372475        737385        17855646      -22.516741

  1  SELECT phy.value "Physical Reads",
  2             cur.value "DB Block Gets",
  3             con.value "Consistent Gets",
  4             1-((v(phy.value))/((v(cur.value))+(v(con.value))))*100 "Cache
hit ratio"
  5      FROM   v$sysstat cur, v$sysstat con, v$sysstat phy
  6     WHERE  cur.name='db block gets'
  7      AND    con.name='consistent gets'
  8*    AND    phy.name='physical reads'
  9  /

Physical Reads DB Block Gets Consistent Gets Cache hit ratio
============== ============= =============== ===============
       4421265        741421        18892425      -21.518589

My Question:

Why i am still getting cache hit ratio in -ve ? V is the name of function u
suggested.

CREATE OR REPLACE  FUNCTION "CR"."V"  ( p_num in number )
return number
    as
    begin
        return to_number(substr( to_char(p_num,'fm'||rpad('0',15,'0')||'x'), 9
), rpad('x',8,'x'));
    end;

Regards

          Followup:

you have your parans in the wrong place.

it is

( 1 - PIO/LIO ) * 100

you have

1 - (PIO/LIO)*100

      Rating: 4
Negative buffer
      cache ration  August 07, 2003
Reviewer:  vijay k
      bangera  from redwood city, ca
Tom,

Please answer only if relevent.  Do you think the function that strips the high
bytes in calculating the ratio work for all types of OS or version of OS
irrespective of whether it is 32 or 64 bit machine?

          Followup:

should work regardless.  you'll know right away if it doesn't ;)

It just takes a number -- turns it into a 16 character hex string and then
returns only the last 8 hex char's:

ops$tkyte@ORA920> select to_char(:x,'fm'||rpad('0',15,'0')||'x') from dual;

TO_CHAR(:X,'FM'||
-----------------
fffffff6d6fa3340

ops$tkyte@ORA920> select substr( to_char(:x,'fm'||rpad('0',15,'0')||'x'), 9 )
from dual;

SUBSTR(TO
---------
d6fa3340

ops$tkyte@ORA920> select to_number(substr(
to_char(:x,'fm'||rpad('0',15,'0')||'x'), 9 ), rpad('x',8,'x')) result from dual;

    RESULT
----------
3606721344

August 10, 2003 Sameer -- Thanks for the question regarding
      "dbms_output.enable does not work", version 8.1.7

      You Asked
Hi Tom,

I have dbms_output.enable(1000000) in my procedure but to see all the messages
thrown by dbms_output.put_line I still have to issue the command "set
serveroutput on" at the sql plus.

Isnt it true that dbms_output.enable is the alternative to this command ?

Thanks !

      and we said...
It is not true.

set serveroutput on

does 2 things:

1) is tells SQLPLUS to dump the output after each statement
2) it makes sqlplus issue the dbms_output.enable

You must use set serveroutput on if you want sqlplus to automagically dump the
output.  Else, if you just use dbms_output.enable, you would have to call
dbms_output.getlines yourself to get the result.
Review &
      Followup

      Rating:
      3
  November 14, 2001
Reviewer:
      Sameer
So isnt there a way to display the messages from a procedure in SQL Plus,
without issuing any commands at the sql prompt ?

          Followup:

correct, you must tell sqlplus to dump the output.

      Rating: 3
news to
      me...  November 14, 2001
Reviewer:  Andrew
       from USA

          Followup:

try it.  login to sqlplus (without having set serveroutput on) and try to get
dbms_output to work.  It won't. (make sure you don't have set serveroutput on in
your login.sql file)

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.enable(10000);

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( 'x' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set serveroutput on
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( 'x' );
x
x

PL/SQL procedure successfully completed.

See the second one did 2 x's -- the first and second, since plus didn't DUMP the
output the first time.

      Rating:
      1
  November 14, 2001
Reviewer:
      A reader
So if I must tell sqlplus to dump the output, what is the command
dbms_output.enable for ? By using "set serveroutput on" it still displays the
messages, then why & where use dbms_output.enable ?

          Followup:

(you know, i always wonder how people rate questions, how they come up with 1..5
stars.  here, we have accurate - detailed information that tells you exactly how
to use something, yet because they don't LIKE the answer, its only worth 1.  I
guess people rate by what they WANTED to hear, not what is said.  Oh well)....

DBMS_OUTPUT is a package to allow you to buffer "lines of text" that a client
might retrieve and do something with.

In this package there are a couple of procedures/functions.  One of which is
ENABLE.

A client (any client) can call enable.  If you call enable all this does is lets
the package BUFFER output.  If enable is not called or you call disable --
buffering of this output is disabled, does not happen.

Now, any client -- your C program, your java application, your WHATEVER, can
call dbms_output.enable to get this buffering to take place.  they can later
call dbms_output.get_line/get_lines to get this buffered data and do SOMETHING
(whatever they want) with it.

Now, sqlplus sees this functionality and says "hey, would not it be nice for me
to dump this buffer to screen for the user?".  So, they added the SQLPlus
command "set serveroutput on" which does two things

1) it tells SQLPLUS you would like it to execute dbms_output.get_lines after
each and every statement.  You would like it to do this network rounding after
each call.  You would like this extra overhead to take place (think of an
install script with hundreds/thousands of statements to be executed -- perhaps,
just perhaps you don't want this extra call after every call)

2) SQLPLUS automatically calls the dbms_output API "enable" to turn on the
buffering that happens in the package.

Since SQLPlus has NO WAY to know if you called dbms_output.enable (it could be
buried in a stored procedure call, in a trigger, where ever) -- it would have to
call dbms_output.get_lines after each ever call -- adding unnecessary overhead.

I guess the implementors of sqlplus looked at this problem and said "set
serveroutput on" isn't any harder then exec dbms_output.enable -- in fact it
would be easier so we'll do it that way.  In this fashion, sqlplus won't make
this extra call after each call -- unless the user asks us to.

Given that you can have a login.sql with

set serveroutput on size 1000000

to have this setup for you transparently, its just not really a big deal at all.

      Rating: 3
How about Huge
      data which buffer can not hold all That data  November 15,
      2001
Reviewer:  Mohammed Al-moayed  from Yemen

Hi, tom I have tried this package before but, I really get
Probelm with spooling  more than max buffer can hold .
Is thire any solution for this probelm .
Beacouse I am running  SQLs every day with about 20,000 Records to be retrive
for some SQLs ,
I was Planning to sechuling all sql by using Pl/sql and using
dbms_output.Put_line but i get buffer Error.

are thire any soultion for this

          Followup:

did you:

set serveroutput on size 1000000

??

(if yes, I can show you methods to get around this limit as well, all is not
lost, search for my_dbms_output on this site for some ideas)

      Rating: 5
The star rating
      system  November 15, 2001
Reviewer:  Connor
      McDonald  from Somewhere more appreciative than the people above
      are...
It absolutely definitively irrefutably continues to amaze me that you're
providing a resource that is:
- timely!
- free!
- packed with examples!
- well researched!
- full of quality
- done by a single guy
and people have the nerve to rate ANY response less than 4 or 5 stars. If they
don't like the response, then they should be doing one or more of:
- RTFM
- pay up for metalink support contract
- shutup and stop complaining...

Just my 2c worth.

      Rating: 5
RE: Star
      Rating  November 15, 2001
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Preach on Brother Connor!

I notice that most of the "1 star'ers" also happen to be named "A reader".
Funny that.

Anyway, the next time I see someone posting a question like "I keep getting the
error that says cannot extend segment abc by 123 in tablespace xyz... what do I
do?"  I'm going to ask for a raise - obviously I'm at the 'senior' level, right?
:)

What is so hard (apparently) about looking something up in the documentation
after all????  Or, even harder, trying it out first?  Oh, well, don't mean to
use 'AskTom' as a personal soap box...

BTW, when did deepest darkest Scotland start using 2c??? :)

- Mark

      Rating: 5
A wonderful
      (and free!) resource  November 15, 2001
Reviewer:
      Paul Rees  from Cardiff, Wales. UK
Here, Here (to Connor's previous remarks)

And long may it continue...

      Rating:
      5
  November 15, 2001
Reviewer:
      Sameer
Thanks Tom ! that explaination was excellent ! i guess, no one will get confused
with "set serveroutput on" and "dbms_output.enable" ever again ! I checked the
metalink also and found few folks confused with the above 2 commands (like me)
!!!

      Rating: 2
Rating - maybe
      a bit confusing names for rating  November 15, 2001

Reviewer:  Dushan  from MA
1-Not usefull
2-Of some use
3-Useful
4-Very useful
5-most useful

Well, maybe somebody asked something, the Tom's answer was excellent, but for
the issuer it meant 'Hey, wrong way!'
What the rating should be? 5 or 1?
1 - means 'Not useful, it did not solve my problem' but on the other hand 5 -
'Most useful, thanks for answer, I am not going waste my time in wrong way'

      Rating: 5
What did you
      try here, Mr Dushan from MA?  November 15, 2001

Reviewer:  A reader

      Rating:
      5
Reader  November 15, 2001
Reviewer:
      Reader  from USA
Excellent information and explanation
of concept behind the design of Oracle code is great !

      Rating: 5
dbms_output
      implementation question  November 16, 2001

Reviewer:  Igor  from France
Could you explain why there is not possibility to get results as they come and
not at the end
of procedure execution?
(for someone who is ready to pay extra performance cost)
What were problem during design of this package in Oracle architecture?

                                     Igor

          Followup:

It is because prior to 9i -- a stored procedure call would goto the database,
run the procedure from start to finish and then return to the client.  The
client has no ability to get results from the stored procedure until its totally
done. It is one message over, one message back.  The stored procedure runs in
the server, not on the client so the stored procedure cannot really do any I/O
on the client screen (and think of all of the interfaces - gui's, command line,
etc -- there would be no way for the stored proc to do I/O to the screen)

Now, in 9i, we can actually get interactive results from a stored procedure
using pipe lining (a cool feature that wasn't really designed to do exactly what
I'm about to do -- rather this is a nice side effect).  Consider this small
example:

ops$tkyte@ORA9I.WORLD> create or replace type messageTabType
  2  as table of varchar2(4000)
  3  /

Type created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> create or replace function my_stored_proc( p_inputs in
varchar2 )
  2  return messageTabType
  3  PIPELINED
  4  as
  5  begin
  6      for i in 1 .. 10
  7      loop
  8          pipe row( 'processing iteration      ' ||
  9                      i || ' ' || systimestamp );
 10          dbms_lock.sleep(1);
 11          pipe row( 'done processing iteration ' ||
 12                      i || ' ' || systimestamp );
 13      end loop;
 14      pipe row( 'All done, success' );
 15
 16      return;
 17  end;
 18  /

Function created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> set arraysize 1
ops$tkyte@ORA9I.WORLD> select * from table( my_stored_proc( 'foobar' ) );

COLUMN_VALUE
--------------------------------------------------------------------------------
processing iteration      1 16-NOV-01 08.14.38.335479000 AM -05:00
done processing iteration 1 16-NOV-01 08.14.39.360806000 AM -05:00
processing iteration      2 16-NOV-01 08.14.39.361372000 AM -05:00
done processing iteration 2 16-NOV-01 08.14.40.390780000 AM -05:00
processing iteration      3 16-NOV-01 08.14.40.391328000 AM -05:00
done processing iteration 3 16-NOV-01 08.14.41.420795000 AM -05:00
processing iteration      4 16-NOV-01 08.14.41.421341000 AM -05:00
done processing iteration 4 16-NOV-01 08.14.42.450819000 AM -05:00
processing iteration      5 16-NOV-01 08.14.42.451383000 AM -05:00
done processing iteration 5 16-NOV-01 08.14.43.480794000 AM -05:00
processing iteration      6 16-NOV-01 08.14.43.481343000 AM -05:00
done processing iteration 6 16-NOV-01 08.14.44.511147000 AM -05:00
processing iteration      7 16-NOV-01 08.14.44.511987000 AM -05:00
done processing iteration 7 16-NOV-01 08.14.45.540863000 AM -05:00
processing iteration      8 16-NOV-01 08.14.45.541468000 AM -05:00
done processing iteration 8 16-NOV-01 08.14.46.571104000 AM -05:00
processing iteration      9 16-NOV-01 08.14.46.571923000 AM -05:00
done processing iteration 9 16-NOV-01 08.14.47.600860000 AM -05:00
processing iteration      10 16-NOV-01 08.14.47.601463000 AM -05:00
done processing iteration 10 16-NOV-01 08.14.48.630873000 AM -05:00
All done, success

21 rows selected.

It is not obvious here but -- each pair of outputs from the procedure appeared
in sqlplus -- in real time -- every second (setting the arraysize was important
here, else plus would have waited for 15 rows to be generated).

So, in 9i, if you want "real time" output from the procedure, you would simply
use PIPE ROW instead of dbms_output and you would "select * from your_procedure"
instead of execute your_procedure.

If your procedure modifies the database, you would have to code it as an
autonomous transaction:

ops$tkyte@ORA9I.WORLD> create or replace function my_stored_proc( p_inputs in
varchar2 )
  2  return messageTabType
  3  PIPELINED
  4  as
  5          pragma autonomous_transaction;
  6  begin
  7      for i in 1 .. 10
  8      loop
  9          pipe row( 'processing iteration      ' ||
 10                      i || ' ' || systimestamp );
 11          dbms_lock.sleep(1);
 12          insert into t values ( i );
 13          pipe row( 'done processing iteration ' ||
 14                      i || ' ' || systimestamp );
 15      end loop;
 16      pipe row( 'All done, success' );
 17      commit;
 18      return;
 19  end;
 20  /

Function created.

to avoid:

ORA-14551: cannot perform a DML operation inside a query
ORA-06512: at "OPS$TKYTE.MY_STORED_PROC", line 11
ORA-06512: at line 1

but otherwise it will work just fine.

      Rating: 3
How about Huge
      data which buffer can not hold all That data  November 17,
      2001
Reviewer:  Mohammed Al-moayed  from Yemen

Hi, tom I have tried this package before but, I really get
Probelm with spooling  more than max buffer can hold .
Is thire any solution for this probelm .
Beacouse I am running  SQLs every day with about 20,000 Records to be retrive
for some SQLs ,
I was Planning to sechuling all sql by using Pl/sql and using
dbms_output.Put_line but i get buffer Error.

are thire any soultion for this

          Followup:

As I said in the other followup:

I can show you methods to get around this limit as well, all is not
lost, search for my_dbms_output on this site for some ideas

(or if you have my book, look at the dbms_output section.  There i explain in
gory detail how dbms_output works, how it packs data, why the 1,000,000 limit is
there and how to get around it when you need)

The limit on dbms_output is hard and fast.  We will have to use something else.
MY_DBMS_OUTPUT is a method of doing this, search for it and the code is all
there.

      Rating: 4
set
      serveroutput off  November 21, 2001
Reviewer:
      Andrew  from CA USA
I gather that "set serveroutput off" issues a dbms_output.disable too.
SQL> -- buffer enabled (and too small)
SQL> set serveroutput on size 2000
SQL> begin
  2  for i in 1..100 loop
  3  dbms_output.put_line(lpad('>', 80, '~'));
  4  end loop;
  5  end;
  6  /
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~>
.
.
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~>
begin
*
ERROR at line 1:
ORA-20000: ORU-10027: buffer overflow, limit of 2000 bytes
ORA-06512: at "SYS.DBMS_OUTPUT", line 91
ORA-06512: at "SYS.DBMS_OUTPUT", line 58
ORA-06512: at line 3

SQL> --wont bomb out because an implicit dbms_output.disable occurs
SQL> set serveroutput off
SQL> /

PL/SQL procedure successfully completed.

SQL> -- app re-enables buffer but serveroutout still off
SQL> set serveroutput off
SQL> begin
  2  dbms_output.enable(2000);
  3  for i in 1..100 loop
  4  dbms_output.put_line(lpad('>', 80, '~'));
  5  end loop;
  6  end;
  7  /
begin
*
ERROR at line 1:
ORA-20000: ORU-10027: buffer overflow, limit of 2000 bytes
ORA-06512: at "SYS.DBMS_OUTPUT", line 91
ORA-06512: at "SYS.DBMS_OUTPUT", line 58
ORA-06512: at line 4

          Followup:

yes, we can see this 100% with SQL_TRACE:

C:\Documents and Settings\Thomas Kyte\Desktop>sqlplus tkyte/tkyte
SQL*Plus: Release 8.1.6.0.0 - Production on Wed Nov 21 15:45:48 2001
(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Enterprise Edition Release 8.1.6.0.0 - Production
With the Partitioning option
JServer Release 8.1.6.0.0 - Production

tkyte@TKYTE816> alter session set sql_trace=true;
Session altered.

tkyte@TKYTE816> set serveroutput on
tkyte@TKYTE816> set serveroutput off
tkyte@TKYTE816> alter session set sql_trace=false;
Session altered.

tkyte@TKYTE816> @gettrace

C.VALUE||'\ORA'||TO_CHAR(A.SPID,'FM00000')||'.TRC'
---------------------------------------------------------------------------------
-------------------

C:\oracle\admin\tkyte816\udump\ORA01252.trc

tkyte@TKYTE816> host type C:\oracle\admin\tkyte816\udump\ORA01252.trc

Dump file C:\oracle\admin\tkyte816\udump\ORA01252.TRC
Wed Nov 21 15:45:56 2001
ORACLE V8.1.6.0.0 - Production vsnsta=0
vsnsql=e vsnxtr=3
Windows 2000 Version 5.0 , CPU type 586
Oracle8i Enterprise Edition Release 8.1.6.0.0 - Production
With the Partitioning option
JServer Release 8.1.6.0.0 - Production
Windows 2000 Version 5.0 , CPU type 586
Instance name: tkyte816

Redo thread mounted by this instance: 1

Oracle process number: 12

Windows thread id: 1252, image: ORACLE.EXE

*** SESSION ID:(8.99) 2001-11-21 15:45:56.241
APPNAME mod='SQL*Plus' mh=3669949024 act='' ah=4029777240
=====================
PARSING IN CURSOR #1 len=33 dep=0 uid=427 oct=42 lid=427 tim=10030494
hv=3732290820 ad='2f6f128'
alter session set sql_trace=true
END OF STMT
EXEC #1:c=5,e=5,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=4,tim=10030495
=====================
PARSING IN CURSOR #1 len=39 dep=0 uid=427 oct=47 lid=427 tim=10030728
hv=1585371720 ad='3fff3d4'
BEGIN DBMS_OUTPUT.ENABLE(1000000); END;
END OF STMT
PARSE #1:c=1,e=1,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=4,tim=10030728
EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=0,og=4,tim=10030729
=====================
PARSING IN CURSOR #1 len=31 dep=0 uid=427 oct=47 lid=427 tim=10031043
hv=607327990 ad='401e258'
BEGIN DBMS_OUTPUT.DISABLE; END;
END OF STMT
PARSE #1:c=1,e=1,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=4,tim=10031043
EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=0,og=4,tim=10031044
=====================
PARSING IN CURSOR #1 len=34 dep=0 uid=427 oct=42 lid=427 tim=10031515
hv=1582735206 ad='2f6c2f8'
alter session set sql_trace=false
END OF STMT
PARSE #1:c=1,e=1,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=4,tim=10031516
EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=10031517

      Rating:
      4
  March 27, 2003
Reviewer:  MG
       from Germany
Hi Tom,

I wants to redirect the output of procedure "P1" to "MyFile.sql", for further
execution. So I did that as following ( in Perl).

print ORA "set serveroutput on for wra \n";
print ORA "set feedback off \n";
print ORA "spool MyFile.sql \n";
print ORA "exec p1; \n";

In one of the entry in MyFile.sql I have :
host copy location1\file1.ora Location2\file2.ora

So my problem is this line will be break, some times. (Some Computers, this will
give correctly, some of them not)

ex: host copy location1\file1.ora Location2\file2.o
    ra

So, When I execute the file MyFile.sql, this command execution will be failed.

So why this "print ORA "set serveroutput on for wra \n";
", will be not working as I expect? Is there any thing wrong, the way I have
applied?

Could you please give me suggestion?

Thanks in Advance

          Followup:

ops$tkyte@ORA920> set serveroutput on format wrapped
ops$tkyte@ORA920> exec dbms_output.put_line( 'how now brown cow, the fox ran
away with the spoon' )
how now brown cow, the fox ran away with the spoon

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> set linesize 30
ops$tkyte@ORA920> exec dbms_output.put_line( 'how now brown cow, the fox ran
away with the spoon' )
how now brown cow, the fox ran
 away with the spoon

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>

set linesize 100

you are missing something like that.  Suggestion -- it is probably cause you
write in perl ;) -- but I think most people would find:

set serveroutput on format wrapped

lots less obscure then ... on for wra.  I know I did a double take trying to
remember what exactly that meant.

      Rating:
      5
  March 27, 2003
Reviewer:  MG
       from Germany
Thank you very much. Problem solved.

      Rating: 3
STORED SET
      filename................suppress output  August 07, 2003

Reviewer:  Denise  from Virginia
the explanation regarding the differences between
dbms_output.enable and set serveroutput on was very helpful.

now my dilemma:

I have a sql plus Menu screen I developed for users to
perform some batch verification procedures.(see code/output)

How do I suppress the output 'Wrote file saved_settings' from the screen
everytime the STORED SET filename REPLACE
command is executed??

I have tried everything under the sun with no results..
I even created a PL/SQL block stored in a query:
begin
 dbms_output.put_line('                  ');
end;
nothing works!!!!(gasp!!)

thanks for any tips...if there is a work around to this.
=========================================================
output:
***************************************************
* Select batch verification processing option:
* 1. Verify Client
* 2. Verify Appealcode
* 3. Verify Dollars and Count
* 4. Update Batch Status
* 5. Verify Batch Flags
* 6. Exit
***************************************************
>6
Wrote file saved_settings

prompt

SQL>

CODE:
set serveroutput on size 1000000
set echo off verify off trimspool on linesize 1000
cl screen
prompt ***************************************************
prompt * Select batch verification processing option:
prompt * 1. Verify Client
prompt * 2. Verify Appealcode
prompt * 3. Verify Dollars and Count
prompt * 4. Update Batch Status
prompt * 5. Verify Batch Flags
prompt * 6. Exit
prompt ***************************************************
accept process prompt ">"
STORE SET saved_settings REPLACE
SET FEEDBACK OFF HEADING OFF
SPOOL query.sql
select decode(&process,1,'START checkClient',2,'START checkAppealcode',
3,'START checkDollars_Count',4,'START upd_BatchStatus',5,'START
check_BatchFlags',
6,'prompt',null)
from dual;
SPOOL OFF
START saved_settings
START query

          Followup:

...
accept process prompt ">"
set termout off
STORE SET saved_settings REPLACE
set termout on
SET FEEDBACK OFF HEADING OFF
SPOOL query.sql
...

      Rating: 1
SET TERMOUT
      ON.....feedback  August 11, 2003
Reviewer:
      Denise  from Virginia,USA
Tom

wish there was a response rating option that allowed for
simply providing feedback and not giving your response a
"rating"....as your responses are always very much appreciated(sorry for the
'Not Useful' rating I'll explain why);

In order to get the system to suppress the "Wrote file...."
statement upon a menu selection I had to strategically
place TERM OFF/ON in several places within the menu script
and also had to place SET TERM ON in each of the scripts
called by the DECODE function.

If SET TERM/TERMOUT OFF/ON was placed as you suggested
it does not work when a script was called and sv parameters for user input was
required.

attached is the menu script with the changes, also one of
the five scripts with SET TERMOUT ON and the output to screen. This was the only
way I could get this to work
after attempting a myriad of solutions.

set serveroutput on size 1000000
set echo off verify off trimspool on TERM ON linesize 1000
cl screen
prompt ***************************************************
prompt * Select batch verification processing option:
prompt * 1. Verify Client
prompt * 2. Verify Appealcode
prompt * 3. Verify Dollars and Count
prompt * 4. Update Batch Status
prompt * 5. Verify Batch Flags
prompt * 6. Exit
prompt ***************************************************
accept process prompt ">"
SET TERM OFF
STORE SET saved_settings REPLACE
SET TERM ON
SET FEEDBACK OFF HEADING OFF
SPOOL query.sql
select decode(&process,1,'START checkClient',2,'START checkAppealcode',
3,'START checkDollars_Count',4,'START upd_BatchStatus',5,'START
check_BatchFlags',
6,'prompt','Please Enter Correct Selection')
from dual;
SPOOL OFF
START saved_settings
START query
**********************************************************
checkAppealcode.sql
SET TERM ON
select p.idnumber,p.usercode1,p.usercode2,bd.batchno,bd.appealcode
from prospect p,batchdonation bd
where bd.appealcode LIKE (upper('&aplcode'))
and bd.batchno between &batch1 and &batch2
and p.idnumber=bd.idnumber;
***********************************************************
output:
*************************************************
* Select batch verification processing option:
* 1. Verify Client
* 2. Verify Appealcode
* 3. Verify Dollars and Count
* 4. Update Batch Status
* 5. Verify Batch Flags
* 6. Exit
***************************************************
>2

START checkAppealcode
Enter value for aplcode:
Enter value for batch1:
Enter value for batch2:

      Rating: 4
The pipeline
      solution is interesting.  December 09, 2003

Reviewer:  Philip Heath  from Dallas, TX
Tom,

I pulled an anonymous PL/SQL block out of a Perl script, and I converted it to a
pipelined function just as your example shows.  It works in sql*plus as your
example did.  However, I went to do the execute within a select statement within
my Perl script, and OCI doesn't like it:

PLS-00428: an INTO clause is expected in this SELECT statement (DBD ERROR:
OCIStmtExecute) [for statement ``
       BEGIN

         select * from table( ccdbo.update_market_area_codes() );

       END;
   '' with params: ])

This is in Perl 5.8.0 using DBI compiled against the 9.0.1 client with a 9.2.0.4
server.  Is there a way to have OCI execute this call without an into clause?

Thanks,
Philip

August 10, 2003 Meyer -- Thanks for the question regarding
      "Populating ID columns via Sequence", version 8.17

      You Asked
Tom,

Like most, I put an "ID" column in many tables...

1)What do you consider the best way to populate from sequence:

Quasi SQL from: on insert for each row trigger...

a)select mysequence.nextval
    into :new.id
    from dual;

b)select nvl(:new.id, mysequence.nextval) into
    into :new.id
    from dual;

c)OR some other approach... maybe a default like (sysdate for dates)?

The reason I like b over a, is if I ever need to export/truncate/import or for
some unknown reason in the future or move records then insert bulk for admin...I
will keep any existing values for ID (If forget to disable the trigger). The
drawback is someone could insert their own non-sequence value that later gets
"steped on" by the sequence.

2)if you were designing a new app, regarding populating ID columns... Would you
create one sequence for all tables or one sequence per table? I normally do 1
per table, but now am thinking what difference does it make, they are just
arbitrary unique numbers... and the object that hands them out does not
serialize.

Thanks,
Meyer

      and we said...
I would not use a trigger -- period.

I would use mysequence.nextval in the INSERT statement itself.  More performant,
100% clear as to what is happening and export/import won't "hurt" you.

I would create a sequence/table for scalability issues.  There are concurrency
related issues with regards to sequences.  I would want more then one.
Review &
      Followup

      Rating: 4
You did not
      mention about performance!  November 22, 2002

Reviewer:  A reader
Tom,
With your approach, mysequence.nextval in the Insert statement, will the
performance will be better than other approaches?

          Followup:

In a huge way -- it is faster and less resource intensive.  Here is a simple
test showing it is measurably faster and then we see it uses tons less resources
(look at the redo size -- triggers can cause additional redo generation)

ops$tkyte@ORA920.US.ORACLE.COM> create table t1 ( x int primary key, y int );
Table created.

ops$tkyte@ORA920.US.ORACLE.COM> create table t2 ( x int primary key, y int );
Table created.

ops$tkyte@ORA920.US.ORACLE.COM> create sequence s;
Sequence created.

ops$tkyte@ORA920.US.ORACLE.COM> create or replace trigger t2_trigger before
insert on t2 for each row
  2  begin
  3      select s.nextval into :new.x from dual;
  4  end;
  5  /
Trigger created.

ops$tkyte@ORA920.US.ORACLE.COM> declare
  2      l_start number;
  3      l_run1  number;
  4      l_run2  number;
  5
  6      type rc is ref cursor;
  7      l_cur rc;
  8  begin
  9      insert into run_stats select 'before', stats.* from stats;
 10
 11      execute immediate 'alter session set session_cached_cursors=0';
 12      l_start := dbms_utility.get_time;
 13      insert into t1 (x,y) select s.nextval, 1 from all_objects;
 14      for i in 1 .. 10000
 15      loop
 16          insert into t1(x,y) values ( s.nextval, 1 );
 17      end loop;
 18      commit;
 19      l_run1 := (dbms_utility.get_time-l_start);
 20      dbms_output.put_line( l_run1 || ' hsecs' );
 21
 22      insert into run_stats select 'after 1', stats.* from stats;
 23      execute immediate 'alter session set session_cached_cursors=100';
 24      l_start := dbms_utility.get_time;
 25      insert into t2 (y) select 1 from all_objects;
 26      for i in 1 .. 10000
 27      loop
 28          insert into t2(y) values ( 1 );
 29      end loop;
 30      commit;
 31      l_run2 := (dbms_utility.get_time-l_start);
 32      dbms_output.put_line( l_run2 || ' hsecs' );
 33      dbms_output.put_line
 34      ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) || '% of the time' );
 35
 36      insert into run_stats select 'after 2', stats.* from stats;
 37  end;
 38  /
1123 hsecs
4206 hsecs
run 1 ran in 26.7% of the time

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> select a.name, b.value-a.value run1,
c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

NAME                                 RUN1       RUN2       DIFF
------------------------------ ---------- ---------- ----------
...
LATCH.enqueue hash chains           12238      12882        644
LATCH.cache buffers lru chain        1035       2158       1123
STAT...session cursor cache hi          0       1391       1391
ts

LATCH.checkpoint queue latch          594       2062       1468
STAT...recursive cpu usage            901       3770       2869
LATCH.simulator hash latch           4714       8862       4148
STAT...table scan blocks gotte          4      39303      39299
n

STAT...table scan rows gotten          70      39369      39299
STAT...table scans (short tabl          1      39300      39299
es)

STAT...execute count                12015      51321      39306
STAT...no work - consistent re      51167      90481      39314
ad gets

STAT...buffer is not pinned co      66931     106255      39324
unt

STAT...recursive calls              37952      77314      39362
LATCH.session allocation             1034      56536      55502
STAT...redo entries                 25649      83899      58250
LATCH.redo allocation               25728      84383      58655
STAT...db block gets                39473     127768      88295
LATCH.library cache pin alloca       8157     120162     112005
tion

LATCH.shared pool                   59586     175234     115648
STAT...db block changes             50986     167978     116992
STAT...calls to get snapshot s      16321     134191     117870
cn: kcmgss

STAT...consistent gets             140316     258205     117889
STAT...session logical reads       179789     385973     206184
LATCH.library cache pin            114812     328285     213473
STAT...session pga memory max      260724     524288     263564
LATCH.library cache                161012     615249     454237
LATCH.cache buffers chains         442879    1062600     619721
STAT...redo size                  8577744   20685844   12108100

97 rows selected.

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

      Rating: 5
Thank
      You  November 22, 2002
Reviewer:  Meyer
       from Baltimore, MD
This is helpful

      Rating: 5
STAT...redo
      size  November 22, 2002
Reviewer:  Zoran
      Martic  from Ireland
Hi Tom,

I produced the similar results in my previous month tests.
The only thing I did not reproduce was:
STAT...redo size

When I run your example I realized what is making redo size that big.

insert into t2 (y) select 1 from all_objects;

I am wondering why (my brain is not working properly on Friday)?
It looks to me a little bit strange.

If you exclude this statement and use only pure inserts (I put 100000 instead of
10000 for your loop) as you will probably use in most applications you will not
have the big difference in redo size.

Thanks in advance,
Zoran

      Rating: 5
How does this
      affect bind variables?  November 23, 2002

Reviewer:  Todd  from Minneapolis, MN USA
Tom,
You wrote:
I would use mysequence.nextval in the INSERT statement itself.

Is mysequence.nextval considered a literal and hence would need to be parsed for
each insert from an application program (not plsql)?  Our programs use
mysequence.nextval from dual in a separate statment to place it in a variable.
Are we doing extra work for no reason?

Thanks.

          Followup:

you are doing extra work and lots of it for no good reason.  It is not a literal
(they are things that could appear in quotes).  It is a reference to a database
object.

ops$tkyte@ORA920.US.ORACLE.COM> create table t ( x int );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM> create sequence s;

Sequence created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> declare
  2      l_start number;
  3      l_run1  number;
  4      l_run2  number;
  5
  6      l_seq   number;
  7  begin
  8      insert into run_stats select 'before', stats.* from stats;
  9
 10      l_start := dbms_utility.get_time;
 11      for i in 1 .. 30000
 12      loop
 13          insert into t values ( s.nextval );
 14      end loop;
 15      l_run1 := (dbms_utility.get_time-l_start);
 16      dbms_output.put_line( l_run1 || ' hsecs' );
 17
 18      insert into run_stats select 'after 1', stats.* from stats;
 19      l_start := dbms_utility.get_time;
 20      for i in 1 .. 30000
 21      loop
 22          select s.nextval into l_seq from dual;
 23          insert into t values ( l_seq );
 24      end loop;
 25      l_run2 := (dbms_utility.get_time-l_start);
 26      dbms_output.put_line( l_run2 || ' hsecs' );
 27      dbms_output.put_line
 28      ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) || '% of the time' );
 29
 30      insert into run_stats select 'after 2', stats.* from stats;
 31  end;
 32  /
925 hsecs
2068 hsecs
run 1 ran in 44.73% of the time

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> select a.name, b.value-a.value run1,
c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

NAME                                 RUN1       RUN2       DIFF
------------------------------ ---------- ---------- ----------
...
STAT...recursive calls              51177      81188      30011
LATCH.shared pool                   70751     101007      30256
LATCH.library cache pin            135274     195481      60207
LATCH.library cache                174525     234943      60418
STAT...calls to get snapshot s      34566     124568      90002
cn: kcmgss

STAT...consistent gets               1646      91654      90008
STAT...session logical reads        37322     127338      90016
LATCH.cache buffers chains         173012     354904     181892

80 rows selected.

Look at the wall clock time but really look at the additional latching.  (look
also at all of the keystrokes you used ;)

      Rating: 5
Wowee, look at
      the time  November 23, 2002
Reviewer:  J.
      Sigh  from MA, USA
<rant>
Here is another one of those "KNOWN" things shot down.
I've read "Put it as close to "IN" the database whenever you can" ish.
How much closer than a trigger can you get to the tables?
I would have expected the opposite.
Now what?
I've insisted on trigger sequence generation. The boss opts for the
"SELECT some_sq.NEXTVAL INTO n_prim_key FROM DUAL;" then the n_prim_key in the
insert. (I've always thought this was not good, network
hit-wise(double-hit)(debunk me, man :) ))
Anyway, from the fat client on a form60 front end, the boss gets a value back
and I get a slower, less scalable, sequence. Ugh!
the more I read you (and it's been since the Web version started) the dumber I
feel.
sigh!!!!!
</Rant>

          Followup:

well, don't be so harsh - truth be told you were both doing it the non-optimal
way.

But, this does point out something.  If you use this test harness:

http://asktom.oracle.com/~tkyte/runstats.html
or something similar (tkprof), you'll be able to conclusively prove the answer.

Also -- there are other things to consider

o your trigger covers everyone.  it is "complete"

o some people don't like putting the sequence reference in the insert, if
"offends" them for some reason (generally MS-SS programmers fall into this
category) so the trigger is nice for them.

      Rating: 5
JDBC support
      for sequence reference in insert?  November 23, 2002

Reviewer:  Todd  from Minneapolis, MN USA
Tom,
Does jdbc (thick or thin) support the use of sequence reference in an insert
statement?  (It is on my list to learn more about the jdbc drivers.)

Thanks.

          Followup:

yes. both.  the jdbc drivers don't really "see" the sql, it is just a string to
them for the most part.

      Rating: 5
What was the
      value of the sequene?  November 23, 2002
Reviewer:
      Todd  from Minneapolis, MN USA
Tom,
Hopefully just one more thing on sequences.  I appreciate your responses.
A developer asked about having the value of the sequence (key) for use in
inserting child rows.  If the nextval is imbedded in the insert, how do they
determine the value used by the insert without reading the row back ?
Thanks.

          Followup:

CURRVAL ;)

insert into parent ( id, ... ) values ( my_parent_seq.nextval );
insert into child ( id, fkey, ... ) values ( my_child_seq.nextval,
                                             my_parent_seq.CURRVAL, .....

Here is a good article I just read on sequences that explains them (note from
Nov2002 -- in the future, the link could be dead)

http://www.dbazine.com/gulutzan4.html

or, alternatively, you can use the insert returning syntax:

begin

insert into parent( id, ... ) values ( my_parent_seq.nextval, ... )
returning ID into l_id;

dbms_output.put_line( 'the new row is ' || l_id );

end;

      Rating: 3
What about
      Blobs?  August 07, 2003
Reviewer:  Vinnie
       from Orlando
We are inserting a blob into are table via a JAVA program with RECID as the PK
which is populated via a sequence.  We We are using BIND variables & want to do
the entire insert in a batch.  But the problem is we need the RECID to be able
to go back & insert the BLOB.  Is there anyway to get a block of numbers from
the sequence? What is your approach?

          Followup:

why not:

begin
   insert into t (c1, c2) values ( recid.nextval, empty_blob() )
   returning c1, c2 into ?, ?;
end;

and preparing that callable statement, binding the int output and the blob
output.  In that fashion, you get both the new sequence and the blob locator in
one trip -- it is already locked (assuming autocommit is off which it MUST be
for all java programs that want to work correctly)

August 10, 2003 Emily -- Thanks for the question regarding
      "Database not responding", version 8.1.6

      You Asked
I am working with a copy of a database and I tried to drop about 1,000 tables
(yes - I know - that's a lot of tables) and it died at exactly 650.  In future
attempts I will reduce the number of DDL statement I send at once - but for now
I cannot get the database to respond.  The listener is running and here are the
entries from the alert log since I started the instance this morning.

Wed Mar  6 10:58:24 2002
ORACLE Instance BRYN - Can not allocate log, archival required
Wed Mar  6 10:58:24 2002
ARCH: Connecting to console port...
Thread 1 cannot allocate new log, sequence 6
All online logs needed archiving
  Current log# 1 seq# 5 mem# 0: /oradata/oradata/BRYN/log1a.rdo
  Current log# 1 seq# 5 mem# 1: /oradata/oradata/BRYN/log1b.rdo
Wed Mar  6 11:42:38 2002
PMON failed to delete process, see PMON trace file

Is it time to bring out the backup?

      and we said...
The DDL did not kill it.  You are in archive log mode and you have not started
automatic archiving.

connect as INTERNAL or SYSDBA in sqlplus and issue:

archive log all;
archive log start;

that'll get you unstuck.

You then need to consult your DBA as to why they haven't enabled automatic
archiving, if they do not, the very next time you restart -- this'll happen
again until you start the archiver.
Review &
      Followup

      Rating:
      5
  April 22, 2003
Reviewer:  Steeve
      Bisson  from Montréal, Canada
That message can occur also if you dont have free space on disk, right?

          Followup:

not with the messages I see there...

nothing about "failed to archive a file", only "need to archive a file"

      Rating:
      5
  August 07, 2003
Reviewer:
      A reader
Tom,

I had similar situation with archive enabled and automatic, disk space
available.

What could be the reason and how should i fix it.
Thanks.

Alertlog file output
Thread 1 advanced to log sequence 5952
  Current log# 3 seq# 5952 mem# 0: /oracle/fs03a/OSDBD/logOSDBD03a.dbf
  Current log# 3 seq# 5952 mem# 1: /oracle/fs01a/OSDBD/logOSDBD03b.dbf
Thu Aug  7 14:48:57 2003
PMON failed to delete process, see PMON trace file
Thu Aug  7 15:45:14 2003
>>> WAITED TOO LONG FOR A ROW CACHE ENQUEUE LOCK! pid=23

And output from pmon file
/oracle/log/OSDBD/bg:[OSDBD] tail pmon_11700.trc
*** 2003.08.07.16.54.22.956
found process c33174a8 pid=11 serial=24 dead
*** 2003.08.07.16.54.32.966
found process c33174a8 pid=11 serial=24 dead
*** 2003.08.07.16.54.42.976
found process c33174a8 pid=11 serial=24 dead
*** 2003.08.07.16.54.52.986
found process c33174a8 pid=11 serial=24 dead
*** 2003.08.07.16.55.02.996
found process c33174a8 pid=11 serial=24 dead

          Followup:

that is not similar -- beyond the fact you had archiving on anyway.  the
problems are wholly unrelated and yours has nothing to do with archiving (what
made you think that it did?)

I suggest you contact support with the supporting trace files to get to the root
cause .

August 10, 2003 uma -- Thanks for the question regarding
      "Overheads on RENAME", version 8.1.6

      You Asked
I have a SYSTEMTEST table and PROD table in the same schema. The load to SYSTEST
table itself takes couple  of hours. After reviwing the SYSTEST table , I am
copying the SYSTEST table to PROD table. Copy from one table to another is also
taking couple of hours and sometime more than that for rollback segements
problems. Now I am thinking of using the RENAME option instead of COPY. I am
planning to rename the SYSTEST table to PROD table after reviwing the SYSTEST
table, ofcourse after renaming the PROD table to someother name. Is there any
disadvantages of doing this?

I thought of someoptions like a)import/export b)copy c)bulkload etc.,
but among these RENAME options looks better for me. Could you please suggest if
you have any otherideas and also please explain the overheads for RENAME.

Thanks

Uma

      and we said...
rename would be the least impact.  It will:

invalidate all compiled stored objects that reference the renamed tables.  Eg:
plsql code, views, and so on will go invalid and will automatically recompile
themselves upon the first execution.

I would use rename personally.  No need to double copy the data.  If you do
double copy the data,

drop or mark unusable the indexes on PROD,
use alter prod to be nologging,
use INSERT /*+ append */ into prod select * from systemtest
create or rebuild the indexes in parallel with NOLOGGING
alter prod to be logging again and backup the tablespace with prod and the
indexes ASAP

Review &
      Followup

      Rating: 4
How about
      synonyms?  April 01, 2002
Reviewer:  Peachy
       from Raleigh NC
If this table-copying is part of a periodic routine, why not create synonyms
(public or private as necessary)?  When the "new" production table is ready,
regenerate the synonyms to point to the latest & greatest version of the table.
Use the "old" production table as your test area, and when you're ready to
switch, regenerate synonyms again.  No need to change code or any other
references. Of course, a script would make it even easier.

          Followup:

The synonym change would have the same effect as the rename table.

One could do the same thing with a CREATE OR REPLACE view.

It is just as valid, I do not see it (or the view) as better or worse.

rename
synonym
view

they could all be used.

      Rating: 3
How about
      partitions?  April 01, 2002
Reviewer:
      Andrew  from CA, USA
Tom

I have found some of your advice on adding or removing partitions to a
partitioned table to be very interesting. Could partition manipulation be useful
in this case? Assume this is a 24*7 environment and we want to minimize impact
to the application.

          Followup:

Ok Ok Ok -- proof that there probably is an infinite number of ways to do
everything.

This has the same side effect as a rename, synonym or view.  Here we would
create a partitioned table with one partition and we would exchange the single
partition "SYSTEM" with the test table "PROD" after doing the validation.

So, at least 4 options that do not require rewriting the table

o rename
o synonym
o view
o partition exchange

      Rating: 3
partition
      exchange  April 01, 2002
Reviewer:  She
       from CT, US
How would I do a partition exhange for these 2 tables? I thought partitions were
for 1 table only. Could you give some examples ?

          Followup:

ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE TABLE t
  2  (
  3          x int,
  4          y int,
  5          z int
  6  )
  7  PARTITION BY RANGE (x) (
  8    PARTITION THE_ONLY_ONE VALUES LESS THAN (MAXVALUE)
  9  )
 10  ;

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1, 1, 1 );

1 row created.

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t2 ( x int, y int, z int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t2 values ( 2, 2, 2 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

         X          Y          Z
---------- ---------- ----------
         1          1          1

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t2;

         X          Y          Z
---------- ---------- ----------
         2          2          2

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t
  2  exchange partition the_only_one
  3  with table t2
  4  including indexes
  5  /

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

         X          Y          Z
---------- ---------- ----------
         2          2          2

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t2;

         X          Y          Z
---------- ---------- ----------
         1          1          1

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

that effectively "swaps" the table definitions.  t becomes t2, t2 becomes t.

      Rating: 2
I wasn't trying
      to be clever...  April 02, 2002
Reviewer:
      Andrew  from CA, USA
Tom
I wasn't trying to infer that you had overlooked partitions as a solution. I was
interested in your comments on the least disruptive approach in a 24*7
environment.

          Followup:

You know -- email and messages like this make it hard to "read" what people are
thinking.

I wasn't trying to say anything when I said:

"Ok Ok Ok -- proof that there probably is an infinite number of ways to do
everything."

I was saying it with a smile on my fact -- cause there are probably other little
tricks that will do the same thing that I didn't put up here yet.  There
probably really is an infinite number of ways to do this....

so far, all of the 4 approaches have the same effect

o momentary lock on the tables/paritions/views/synonyms
o invalidation of all dependent code

the partition -- if you are not careful as to how you swap it -- might take a
long time if we do "validation" on it.

I would use the rename.  least number of moving parts.

      Rating:
      5
  March 04, 2003
Reviewer:
      A reader
Tom,

How to rename a table in a different schema?

I have schema called “MySchema” and it is having “create any table privilege”

I have a table called t1 in schema OtherSchema.

When I executed – Rename otherschema.t1 to otherschema.t2 I am getting the
following error.

ORA-01765 specifying table's owner name is not allowed

How to solve this problem?

Thanks in advance.

          Followup:

umm, log in as the other schema i suppose?

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/statements_917a.htm#2064931
under prereqs:

Prerequisites

The object must be in your own schema.

      Rating: 3
Rebuild a table
      with "alter table xxx rename"  May 01, 2003

Reviewer:  David Pujol  from Barcelona, Catalonia

I've found a lot of waits of db file sequential read (60% vs 40% of cpu time) in
a SAP system. I want rebuild some tables with command "create table xxx_copy as
select /*+ xxx PK_index_xxx */ * from xxx" to minimize IO. After, I'll rename
table xxx for xxx_old and xxx_copy for xxx, and I'll drop indexes of xxx_old and
I'll create these indexes for xxx. Is correct this method?, Exists another
method to get the records in the same order of primary key?. What kind of
problems can I find?. RDBMS is 8.1.7.2.

thanks in advance

          Followup:

why do you believe that will minimize IO?

would you believe me if I told you it could increase it as easily as decrease
it?

db file sequential read is IO performed by an INDEX ACCESS -- hence, rebuilding
a table would not generally affect it.

But in any case,

ALTER TABLE t MOVE;

would suffice -- then rebuild the indexes.  No worries about losing grants,
indexes or support from SAP.

BUT -- (big huge, large, enormous BUT) -- unless you actually

a) identify the cause
b) test a solution
c) prove that it'll work

you are just sort of shooting in the dark and taking the system offline for no
good reason.

      Rating: 2
Rebuild a
      table  May 04, 2003
Reviewer:  David Pujol
       from Barcelona, Catalonia
Hi, thanks for you quick reply. When you say "db file sequential read is IO
performed by an INDEX ACCESS -- hence, rebuilding a table would not generally
affect it" i'm agree with you, but I think that you don't understand me. Let me
explain "better". I've seen a lot of wait for this event and I've seen that a
lot of queries on tables are via PK too. Now, I propose to order this tables in
PK order and this way, I believe that I'll minimize the # of blocks accessed
when one query acces via PK index (unique or range scan). Are you agree with
me??

Thanks in advance

          Followup:

if you access via primary key -- tell me how reducing the cluster factor will
affect anything here?

This is not explaining "better", this is moving the goal posts 50 yards.  This
is an entirely different question all together.

Tell me -- do you range scan and get considerable numbers of rows by primary key
-- if not, "sorting" the table won't do squat for you.

If you do -- I'd be interested -- what kind of application is that that range
scans and gets many rows by primary key.

      Rating: 3
Sort a
      table  May 05, 2003
Reviewer:  David Pujol
       from Barcelona, Catalonia
Ok, ok,..., my problem are range scans of primary keys on tables of 40 millions
of rows (there are a lot of range scans that return a lot of blocks). I thought
that sorting these tables in order of the primary key could help. My initial
question was if exists one other way to sort this tables and don't use "create
table as select" (lose grants, recompile packages, ...). I'm waiting for your
reply, thanks.

          Followup:

why?  why do you think that -- if you do not access the data via primary key in
a range scan???

have you set up a test case on a test machine to show that this in fact will
have any effect -- positive or even negative on your system?????

read through this:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7640334135875
here someone had a hypothesis (totally inaccurate one) and set about reorging
their entire production database in order to implement this hypothesis.  Too bad
their hypothesis would result in only slower performance at best.

      Rating: 3
Sort a
      table  May 05, 2003
Reviewer:  David Pujol
       from Barcelona, Catalonia
Ok, I'll test in integration system and I'll show you results.

      Rating: 5
exchange
      partition  August 08, 2003
Reviewer:
      A reader
Hi

How fast is exchanging a partition? Is it instantaneous? Just updating
dictionary information?

          Followup:

depends on how you can do it -- with or without validation.  It can be
instantaneous, it can also be long.

You'll want to test your method on a scaled down version in a test instance with
SQL_TRACE=true to see what goes on under the covers.

August 10, 2003 Houman -- Thanks for the question regarding "How
      important is using a separate database for a RMAN recovery catalog",
      version Oracle9i

      You Asked
Hello and happy new year,

My question is about RMAN and OEM. I am thinking to install OEM for our
customers and use it as an online backup tool because I think it is an easy to
use specialy for the poeple with no knowledge of Oracle.Please consider the
following:

1- As you know OEM needs a repository database to keep the information for jobs,
events etc.
2- As you know RMAN needs a database to keep the recovery catalog information in
 it and Oracle  recomends to keep this database separated from the production
database.

The problem is that our customers are from small to midrange size companies and
often not willing or able to allocate more resources to additional databases.
Not to mention the fact the these additional databases need licences and also
maintenace.

My question is how practical is to put the repository of the OEM in production
database and whether or not to keep RMAN cat. info in control files of
production database instead for a separate database?
should I use the production database for keeping these info or should go for
other third party backup solutions?

      and we said...
If you put the recovery catalog into the databaes you want to RECOVER -- how do
you think RMAN would be able to use it to recover that database????

If you want to use the recovery catalog, you need to put it elsewhere.
Otherwise, the recovery catalog will NOT be available if the database is damaged
(just when you actually need it MOST).

go ahead and put OEM in there if you want, but if you are using the recovery
catalog, putting it in the database you are trying to protect is not a good
approach.
Review & Followup

      Rating:
      3
  December 27, 2001
Reviewer:
      Houman  from Canada
Thank you for your response. Sorry that I formulated my question badly! What I
realy ment was about using recovery catalog versos puting the catalog
information in the control file of the database. the second option is also
provided by you. Do loose a lot Iif I choose to not using the recovery cataloge.
does it have any effect on the recovery time?

          Followup:

Read:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76990/rmanconc.htm#447660
it describes the differences.  You'll want to really make sure your control
files are well protected via redundancy.

      Rating: 5
Question about
      catalog placement and backup of same  May 22, 2003

Reviewer:  A reader
I am new to RMAN and am wondering if I put the catalog in another database, what
about backing up that database the catalog is in? Should I have an additional
catalog for that database? I.e. if I have db1 and db2 and the catalog for db1 is
in db2, then do I put a catalog for db2 in db1?

Thanks!!

          Followup:

generally, you use "old fashioned" manual backups for that one or just use rman
with the control files to back that one up.

      Rating: 5
One
      Doubt  August 08, 2003
Reviewer:  jignesh
       from india
When two or more databases are backed up using RMAN, is it appropriate to use
each database as the other&#8217;s recovery catalog. Why?

          Followup:

you could but it would probably make more sense to have a recovery catalog
database (small) elsewhere that is the repository for both.

you could -- i don't recall seeing anyone doing it that way though.  normally,
one sets up a recovery catalog database.

August 10, 2003 -- Thanks for the question regarding "data
      conversion", version 8.1.7

      You Asked
Hi Tom,

Assume I have a table
T (
c1 varchar2(32) not null,
c2 number not null)

I have one index on columns c1, and one on c2.

From your web, I understand that query:
select * from T where c1=123 is really
select * from T where to_number(c1) = 123

My question is how about:
select * from T where c2='123'?

Does Oracle change the above query to
select * from T where to_char(c2) = '123' or
select * from T where c2 = To_number('123')

My test result is Oracle is using the index on c2.
So it seems that Oracle, sometimes, applies a function
to a column, and sometimes, it applies a function
on the literal.

Thank you!

      and we said...
it is not that is applies a function to a column or a function to a literal - it
is that when you compare a STRING to a NUMBER, the STRING will be cast as a
NUMBER.

so, string = number is really to_number(string) = number, regardless of which is
a column or a literal
Review & Followup

      Rating:
      5
Others?  February 25, 2003
Reviewer:
      Doug
Tom,

Thats interesting to know...

I always use explicit cast/conversions so
I am comparing the same data type
However I know ALOT of developers rely on
implicit conversions

Just wondering if you know of how Oracle handles
other data type comparisons
eg  WHERE my_date_column BETWEEN '03-JAN-03' and '04-FEB-03'

Thanks Doug

          Followup:

...
However I know ALOT of developers rely on
implicit conversions
.....

it is my experience many developers don't REALIZE there are implicit conversions
going on

ops$tkyte@ORA920> select *
  2    from dual
  3   where sysdate BETWEEN '03-xJAN-03' and '28-FEB-03'
  4  /
 where sysdate BETWEEN '03-xJAN-03' and '28-FEB-03'
                       *
ERROR at line 3:
ORA-01843: not a valid month

date to string is converted to DATE to DATE.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2858890655722#7966686179982
for the classic example of not understanding implicit conversions

      Rating:
      5
  February 25, 2003
Reviewer:  Helena
      Marková  from Bratislava, Slovakia

      Rating: 5
why not Number
      converted to String?   February 25, 2003
Reviewer:
      Jan  from Europe
If Oracle converts Number to String, that will not raise an error in fallowing
cases:

WHERE 'ABC'=123

          Followup:

because you have to go from generic type to specific type.

it is the rule of comparision that the database is built on.

and

where '123.0' = 123
where '123' = 123
where '0123' = 123

would give different results.  there are many ways to represent a number in a
string -- but only one way to represent a string in a number.

      Rating: 5
why not Number
      converted to String?   February 25, 2003
Reviewer:
      Jan  from Europe
If Oracle converts Number to String, that will not raise an error in fallowing
cases:

WHERE 'ABC'=123

      Rating:
      5
  March 01, 2003
Reviewer:
      A reader  from Denver, CO

      Rating: 3
Can you tell me
      what happened here?  August 08, 2003
Reviewer:
      sandra  from pennsylvania,usa
Thanks Tom as always...
We have the same table structure resident on datbase A and B( same version
oracle 8.1.7),but behave differently when we do the same select statement.
(The column MAID_NO ,PHARMACY_NABP defined as varchar2 in the table
facet_provider)

In database A,when we do

SELECT DISTINCT MAID_NO FROM FACETS_PROVIDER WHERE PHARMACY_NABP = 803696;

SELECT DISTINCT MAID_NO FROM FACETS_PROVIDER WHERE PHARMACY_NABP = 803696
                                                   *
ERROR at line 1:
ORA-01722: invalid number

But when we do the same query in database B
SQL> SELECT DISTINCT MAID_NO FROM FACETS_PROVIDER WHERE PHARMACY_NABP = 803696;

MAID_NO
---------------
01914645

As we understood, oracle can do the implicit data conversion.

But why it will behave differently in different database,
and as developer report, the error just happened recently on
database A.

Can you tell me why or what can cause this issue ?

          Followup:

if parmacy_nabp is a varchar2, do NOT compare it to a number.

would you compare an apple to an orange?

you have data in there whereby pharmacy_nabp is not a number in database A.

you do not in database B.

that is all (and it is 100% expected)

if that field is a number, convert it datatype wise to prevent garbage in there.

if that field is not a number, compare it to a STRING, not a NUMBER.

August 10, 2003 Goh -- Thanks for the question regarding "The
      Performance Tuning Process", version 8.1.7

      You Asked
Dear Tom,

   I was asked to come out with the performance tuning process which will be
used as a general guideline when conducting performance tuning for customer. I
understand that there is no fix rules for perofrmance tuning. Hope you can
provide some views on these. Thanks in advance.

   Step 1 : Baseline statistic collection.
   Step 2 : Define performance tuning Goals.
   Step 3 : Determine the area and the period of the bottleneck
            (when the slowness occurs, which particular program or
             overall system slow down at certain time).
   Step 4 : Identify the main constraints
               - System Level
               - Database Level (Statspack report)
               - Program Logic Level (Statspack report)
               - Network Level
               - Client Resource Level
   Step 5 : Remove Constraints
   Step 6 : Review and Repeat until Tuning goals met.

Regards,
SHGoh

      and we said...
step 2 is really step 0 -- before anyone gets on a plane, train or automobile.

step 3 assumes it is a bursty problem, many are not.

The problem with tuning is step 5 and 6.  If we tune before we deploy -- well,
then you never get to 5 and 6.

Each and every exercise in post mortem tuning is an experience.  Everytime I do
it -- it is different.  I guess if I had to categorize i would:

a) isolate to the best of your ability which of the 15 tiers people run this is
happening on.  Eg: if you have a 48 cpu database server and it is not breaking a
sweat -- the database isn't broken.  (i've been there where they are jumping up
and down "database slow, database slow", I look at database -- database is doing
SQUAT, nothing, no work.  Database is fast)

b) make sure all of the code you ever write, your company writes is instrumented
to the gills.  Good code has as much debug/trace code in it as "real" code.
Consider what would happen if Oracle didn't have:

  sql_trace=true -- that is instruemented code
  v$ tables -- that is instrumentation
  events -- instruementation on steriods

how would you do anything?  You need to make sure every byte of code that comes
out of your shop is as instruemented, if not more so, then the database.  Else
you are blindly finding your way through a maze.  Just try to pinpoint a
bottleneck in a 14 tier really cool system if the only instruemented piece of
code is the database and the database is doing fine!

c) make sure you understand the problem -- is it a single user having an issue
with a single report?  is it everyone on a single screen?  is it some users with
the entire system?  is it all users with the entire system?  does it only happen
at lunch on wednesday?  does it only happen at 9am, 1pm, 5pm?

d) using the insstruemented trace dumps you get from each layer -- figure out
where the bottleneck might be.  This might be sql_trace=true for a single user
in a single module.  This might be statspack at the database level, this might
be some diagnostic tool for apache in the middle tier, this might be a java
source code profiler, whatever.

e) remember -- each experience will be different from the last and totally
different from the next.  They are mysteries.  If you could codify "do steps 1,
2, 3, 4, 5 and repeat", software would do this for us.  It requires some level
of intuition and experience.  You have to learn to ignore some things (eg: those
guys that want to move data files around to increase performance -- tell them
this -- Ok, you go over there and play around, just remember where everything is
RIGHT NOW and be prepared to put it all back when we say so.  Then, ignore them
and get back to work ;)   Oh, I can count on one hand without any fingers how
many times moving datafiles did the trick -- but it is what everyone does for
some reason?

sorry -- this is a non-answer answer.  If you have my book Expert one on one
Oracle -- in there I relate a tuning story where it was 100% a user interface
issue.  "Hey, the database is hung, stupid database is broken".  Turned out it
was users "using the application in a manner the developers did not forsee".
The logic the programmers had was:

o user swipes timecard
o that selects a record for update
o user hits enter key to confirm and walks away from terminal
o record committed.

What users did:

o hit enter key to clear screen from the last guy
o record committed
o user swipes timecard
o user walks away

So, guess what happens when the last guy from lunch comes in?  They leave their
record locked.  Then, a batch process wakes up in the background and tries to
post the records -- so it does a select for update on all of the records -- gets
some but gets stuck on the record this last guy didn't commit.  Now the people
that have their records locked by the batch process are stuck in line - cannot
swipe their card.  queues build up, people scream, then -- all of a sudden, all
by itself -- problem goes away.

It took two days to figure it out.  Had to goto the warehouse floor and observe
how people used the system.  It was a client program issue, nothing to do with
the database.  But, everyone was 100% sure "database is problem".

Think outside the box.
Review & Followup

      Rating:
      5
  February 18, 2003
Reviewer:
      A reader

      Rating: 3
Performance
      Tuning Process  February 18, 2003
Reviewer:
      A reader
Dear Tom,

   Thanks for the answer. Agreed. Performance Tuning requires a lots of
experience. Each problem might have different way of handle it. You have brought
up a very good example (Client Program issue or could be client resources).
Performance can be affected not only by Database but System resources, Network
congested, Client Hardware resources and "The way the program being developed
and used". Since we are DBA and should always start to look at the DATABASE
layer and System resources layer. I wonder if it is the good approach to collect
the timing for a particular complex SQL statement at the Database Console. For
instance, When user complaints system slow, I will run the complex SQL at the
console and collect the timing. I will then compare the collected timing against
the timing that I collected when system running smooth. With this performance
indicator, I would be able to conclude that the DATABASE is having a problem. I
am not sure if this is workable or not. Need your view. Thanks.

Rgds,
SHGoh

          Followup:

You need to keep a history of

o how the database has performed in the past
o how the application has performed in the past

In order to diagnose if it is doing "ok" today.  Unfortunately, when called into
tune the two things people invariably NEVER have is a history of what it looked
like when the system was "good".

I don't know what a "database console" is.  I don't know why you would collect
the timing for a particularly complex sql statement there - I mean -- it would
sound like you have the problem pre-identified here, assuming that there is a
problem sql statement.  It could be a trivial sql statement that is executed
15,000 times with the same inputs (and 15,000 iterations of something that takes
1/1000th of a second is 15 seconds of time!)

No, I don't think you can use your approach to see if the database is "OK".  It
is a little more complex then that, a couple more moving pieces are involved.
You are looking for a simple answer -- like those little green lights that flash
on those pretty GUI monitors when "things are ok" -- little red lights when they
are bad.  Problem is -- I've seen some excessively poorly tuned systems with all
green lights and some awesome systems with red lights flashing all over.

For the people with red lights -- I just tell them to lower their thresholds in
the GUI -- makes them feel better.  They go green.

For the people happy with green lights, I leave them be.  Hardware vendors love
them.

For people who don't use the GUI's and ignore the lights -- well, now we can get
somewhere ;)

      Rating:
      5
  February 19, 2003
Reviewer:
      cachitok@hotmail.com
Letme give a quote from my experience

I think to get the best tunning, you only get it with experience.

1.
 You must know what is good, what is acceptable and what is wrong. First study.

To know it you must have the experience with the hardware, Oracle version and
size of tables and type of query you do. But first you must have knowledge.
(read)

I am never going to know how much faster would have been my softaware in 7.x
because in that time I do in the way I knew Oracle.

So the first thing is or you get some book (including Tom Kyte book , not
because he answers the question, else because it´s really a good book ) and try
to get an OCP.

When you create the database with all best conditions, for example automanage,
uniformsize, automatic undo management. and know the tools and features you have
to know if you are doing something wrong or losing some feature that can
increase your performance, for example materialized views in dataware housing.

Then only then you can have an idea of what should be the performance.
For example I solved a problem in time of backing up,moving old data to a read
only tablespace, If you don´t know that this exists, you can use it.

2. Experience

You too need some experience, because if you don´t know how Oracle forms locks
records in the database, or other specific conditions ( people, software,
hardware, network, etc. )only you can has in your work.
For example recently we got a problem in a oracle forms client, the reason was
the router that disconnected the client, Word don´t has problem, but Oracle
forms yes, we demostrated it, disconnecting the network cable and reconnecting,
and the same error message appeared. Then they fixed it.

3. Understand what is important

One thing Tom helpme to understand is how you can incrase the performance, at
least in Oracle, he stopped my ideas to set exactly the size of the variable for
example NUMBER(8,3), and showme what was really important, where I should have
to look for performance problems.

4. Practice.

Even I got my OCP, I have oceans of doubts, not because I don´t understand, else
because until you don´t use a feature or see something, you don´t understand how
important it is or it is not.

Until using the hint /*+ NO_MERGE */ in a query that used views, and reduced the
time from 30:00 to 9 seconds. I didn´t understand how important was the hints, I
understand what the hints does, but I didn´t understand how useful they could
be.

5. Ask tom
If you has a problem, you can make it understandable, ask tom,
For example the explanation Tom gave  me about parameters
OPTIMIZER_INDEX_COST_ADJ  =  5
OPTIMIZER_INDEX_CACHING   =  90
saved me days of work, not only hours.
And solved a very important problem in performance.

:)

      Rating: 5
redo log space
      request  August 08, 2003
Reviewer:  Reader

Tom, the online doc describes the subject statistic as how many times a server
process had to wait for space in the online redo log, not for space in the redo
log buffer. Could you please help me in understanding this in what situations in
oracle the server process has to wait for space in online redo log files? LGWR
is the one that is writing to online redo log files from log buffer. When does
server process access online redo logs? Thanks.

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96536/apc2.htm#27146
lgwr is the server process in question here.

August 10, 2003 Tony -- Thanks for the question regarding "Bind
      variables", version 8.1.6

      You Asked
Tom,
Through dynamic sql, while inserting, I can use 'INSERT INTO '||table_name||with
column list... ||' VALUES '||with values here.. Or
first create a insert stament with bind variable, and then use the bind variable
API. I know you will suggest to use bind variable. My question is:
With insert statement with bind variable, will I get noticeable performance
benefits?
Thanks

      and we said...
umm, let me think about that... oh wait -- no I don't need to

YES.

about 1,000,000,000% or there abouts.  give or take.

don't use binds and in a single user situation you will find you spend 9 out of
10 cpu seconds PARSING..

in a multi user situation -- start adding in massive waits -- huge waits...
Review &
      Followup

      Rating: 5
Thanks a
      lot!  August 11, 2003
Reviewer:
      A reader

August 11, 2003 Parul -- Thanks for the question regarding "clob
      datatype", version Release 8.0.4.0.0

      You Asked
Tom
  I am in process of devloping a new application using
Powerbuiler/Oracle. My clients want a field with word processing
capability. I was looking into bfiletype data type. Here, I will
store RTF files. But, the problem with this is you store file
outside of database.
I am looking for information on CLOB data type. Do you have any
code examples you can share with us.

      and we said...
You want a BLOB, not a CLOB for storing word processing
documents.

If you search my site for

- blob
- lob

you'll get lots of hits, examples and all.  If you have a
specific question feel free to followup.  You might want to look
at

http://technet.oracle.com/doc/oracle8i_816/appdev.816/index.htm
even though it is 8.1.6 doc and you have 8.0.4.  The 8.1 doc set
has good write ups on LOBS and programming with them and about
90% of it applies to 8.0.

Review &
      Followup

      Rating: 5
CLOB and
      RTF  August 06, 2003
Reviewer:
      A reader
Tom,
Can CLOB can store and preserve RTF format data? or do we use use BLOB?

Thank you

          Followup:

RTF is a plain text format (you can edit the file in vi for example, it is not
binary).

it can be stored in a clob but -- it makes more sense in a blob since you don't
really want/need any character set conversion done to it.  I would vote for
blob.

      Rating: 5
CLOB and RTF
        August 10, 2003
Reviewer:  A reader

>>.........it makes more sense in a blob since you don't >>really want/need any
character set conversion done to >>it.

Can you please explain this part?
Where is the character set conversion taking place?  If we want to display the
data in the cloc field we display it through an RTF control or read the data in
the clob field into a file and save it into a DOC file.

Thank you

          Followup:

if the database is a US7ASCII and the client is a WE8ISO.... then when the
client inserts data into a varchar type (clob type), we'll convert any 8bit data
into 7bit data - which could be "not something you want to happen" -- you'd lose
characters perhaps - change data -- in the document.

you want to use a blob.

you need a word processor to "view" the rtf anyhow, so you'll be reading the
blob out, saving to disk and invoking a work processor against the file.

      Rating: 5
CLOB and
      RTF  August 10, 2003
Reviewer:
      A reader  from Houston
Tom,
If the database is a US7ASCII  and if the client is a browser (say internet
explorer).
1) Is there any way the client can change the character set on his machine(which
would then affect the data the user enters into the browser which will be going
into the CLOB field)?
2) How can we find out the character set on the client?

Thank you

          Followup:

1) the client is not the browser, the client is the application server in
between the browser and database.

2) NLS_LANG dictates that. Registry/environment variable.

August 11, 2003 Vinod -- Thanks for the question regarding "Doubt
      in For Update clause", version Oracle 7.3.4.0.0

      You Asked
Hi tom, the following code generates an error.
declare
a number;
begin
select max(value) into a from control where for update of value;
end;

ERROR at line 1:
ORA-01786: FOR UPDATE of this query expression is not allowed
ORA-06512: at line 4

wheras removing the max clause like the following works.
Can u tell me the reason why and what's happening.

declare
a number;
begin
select value into a from control where for update of value;
end;

Regds
Vinod

      and we said...
The FOR UPDATE clause has these restrictions:

Restrictions:

o You cannot specify this clause with the following other
constructs: DISTINCT operator, group_by_clause, set operators,
aggregate functions, or the CURSOR operator.

o The tables locked by this clause must all be located on the
same database, and on the same database as any LONG columns and
sequences referenced in the same statement.

I hope your not trying to lock the MAX(value) in order to
generate a sequence or anything -- you should use a sequence for
that.

If you really truely need to lock the max, you can:

  update control
     set value = value+0
   where value = ( select max(value) from control );

  select max(value) into a from control;

Review &
      Followup

      Rating: 4
Attempting to
      use for update and getting ORA-01786 compile error  August
      06, 2003
Reviewer:  Neil  from UK
Hi Tom,
This is what I was hoping to do:

SELECT a.ROWID                accountRow
      ,a.account              client
      ,a.posted               posted
      ,SUM(p.amount)          runningTot
  FROM PAYMENT_SCHEDULE       p
      ,ACCOUNTS               a
 WHERE p.account   = a.account
   AND p.sche_date < lvThreeDaysAgo
 GROUP BY
       a.ROWID
      ,a.account
      ,a.posted
   FOR UPDATE OF a.posted;

which generates the ORA-01768 compile-time error. I can see no reason why Oracle
should not be able to figure out which rows to lock when the cursor is opened -
the aggregated value is from the other table in the join.
So, my questions are:
1. Why can't the compiler let this go?
2. Could I lock the rows 'manually'?
3. Do I have to break the cursor into two separate ones to be able to use the
FOR UPDATE OF clause?

          Followup:

maybe because the rules of SQL disallow it? but we nit pick because it is what
it is.

what is your goal -- what is the reason behind locking them, do you actually
come back and update them or are you just trying to get repeatable read?

      Rating: 2
Attempting to
      use for update and getting ORA-01786 compile error   August
      07, 2003
Reviewer:  Neil  from UK
I sum what's been paid, and compare it to what's been posted. If there's a
difference, then I need to update the posted column.

          Followup:

so, single update statement does that.

ops$tkyte@ORA920> update ( select *
  2             from ( SELECT account, posted,
  3                          (select SUM(p.amount) runningTot
  4                             from t2 p
  5                            where p.account = a.account
  6                              and p.sche_date < :lvThreeDaysAgo ) runningTot
  7                      FROM t1 a )
  8            where posted <> runningTot
  9              and runningTot is not null
 10         )
 11   set posted = runningTot
 12  /

0 rows updated.

Lock and load....  (if you are using 8i, that statement will need to be done via
EXECUTE IMMEDIATE unless you make the inline view a true view).

      Rating: 5
For Update used
      very rarely  August 10, 2003
Reviewer:
      A reader
Tom

I have seen "for update" clause used, very very rarely.

Can you explain as to when a for update clause is a must, and when it is not
necessary.

Thanks

          Followup:

many times, you will not see it cause the tool you use auto-generates it.  Forms
for example uses it constantly -- frequently.

In forms, if you built a block on EMP, it'll use a normal "select * from emp"

Now, you try to update the salary of KING -- the instant you try to type over
the salary value - forms goes to the database and issues:

  select *
    from emp
   where rowid = :block.rowid
     and ename = :block.ename
     and ..... <rest of columns here>
   FOR UPDATE NOWAIT;

if that returns 0 rows -- someone else must have changed the data (one of ename,
empno, sal, etc changed -- the predicate found NO DATA)

if that returns an ora-54 -- someone else has that row locked and is working on
it.  You must wait.

if that returns a record, you can update the data on your screen, secure in the
knowledge that when you goto to update it - the data will be a)locked for you
and b) unchanged since you queried it out.

You use it to avoid "lost updates" that would otherwise happen.  This is known
as pessimistic locking.  There is another technique "optimisitic locking" which
doesn't involve locking at all really -- you would let the user update the
salary on the screen and not bother looking to see if a) the data changed, b)
someone else is working on it.  At "save time", you would do an update like:

  update emp
     set sal = :block.sal
   where rowid = :block.rowid
     and sal = <ORIGINAL_SALARY>;

if that updates a row, you win.  If not, you lose -- you spent your time doing
work that cannot be saved.

It is my experience that, sigh, many people do neither -- and end up with lost
updates all over the place, leading to race conditions that cause "buggy"
results in a multi-user environment.  If you have my book expert one on one
Oracle -- I spend a great deal of time on this topic, including a pretty good
example of such a race condition.

      Rating: 4
Attempting to
      use for update and getting ORA-01786 compile error  August
      11, 2003
Reviewer:  Neil  from UK
Thank you - a neat soultion! I can't use it this time (because I need to create
some transactions for the posted amount), but it's going straight into my
library where it can be with others of its kind...

August 11, 2003 Eli -- Thanks for the question regarding "A way
      to index only null values", version 8.1.7

      You Asked
Hi
I would  like to get your opinion regarding what i am traing to do.
I have vary big table which include millions of records. The "NAME" column is a
nullble column, but in my case include only 2000 null values. My question is how
I can index only the null values of this column. also did you see any problem
with what I am doing below ?(I am familiar with the restriction of storing  null
values on an index, and base on this limit i am trying to solve this)
also did you see a problem to create concatenate index with my method below ?

The below is my test case :
REM ---------The function -----------
CREATE OR REPLACE FUNCTION MY_LENGTH (MY_STARING     IN varchar2)
  RETURN number
  DETERMINISTIC
  AS
TMP_NUMBER number;
BEGIN
         TMP_NUMBER:=LENGTH(MY_STARING);
         IF TMP_NUMBER IS NULL
            THEN
            RETURN 0;
            ELSE
            RETURN NULL;
         END IF;

end MY_LENGTH;
/
REM ---- TEST table
create table foo (id number(10) not null,
          name varchar(10),
          mydate date not null);

REM ------ Insert data

insert into foo values (1,'ELI',sysdate);
insert into foo values (2,NULL,sysdate);
insert into foo values (3,'STAM',sysdate);
insert into foo values (4,NULL,sysdate);
insert into foo values (5,NULL,sysdate);
commit;

alter session set Optimizer_mode=first_rows;
alter session set QUERY_REWRITE_ENABLED=TRUE;
alter session set QUERY_REWRITE_INTEGRITY=TRUSTED;

REM - create the index
create index null_ix on foo(MY_LENGTH(name));

set autot on
select * from foo where MY_LENGTH(name)=0;
select * from foo where MY_LENGTH(name)is null;

      and we said...
What I would suggest is you use DECODE instead of writing you own function.
It'll be easier/faster then using your own PLSQL function.  I would have decode
return 1 when name is NULL and NULL otherwise (seems more intuitive to have it
return 1 instead of 0 to me for some reason).

So, my example would look like this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set QUERY_REWRITE_ENABLED=TRUE;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set
QUERY_REWRITE_INTEGRITY=TRUSTED;

Session altered.

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( name varchar2(30),
object_type varchar2(255) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select object_name, object_type
from all_objects;

17164 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select NULL, object_type from
all_objects where rownum <= 200;

200 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx on t( decode( name, NULL,
1, NULL ) );

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx2 on t( decode( name, NULL,
1, NULL ), object_type );

Index created.

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

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from t where
decode(name,NULL,1,NULL) = 1;

  COUNT(*)
----------
       200

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=17)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=200
Bytes=3400)

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from t where
decode(name,NULL,1,NULL) is null;

  COUNT(*)
----------
     17164

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=17)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'T' (Cost=3 Card=17164 Bytes=291788)

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from t where
decode(name,NULL,1,NULL) = 1 and object_type = 'PROCEDURE';

  COUNT(*)
----------
         0

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=26)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX2' (NON-UNIQUE) (Cost=1 Card=1
Bytes=26)

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off

That third query there shows concatenated indexes will work just fine as well

We can prove that our indexes only indexed the entirely NULL entries via:

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze index t_idx validate structure;

Index analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select lf_rows from index_stats;

   LF_ROWS
----------
       200

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze index t_idx2 validate structure;

Index analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select lf_rows from index_stats;

   LF_ROWS
----------
     17364

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

The first index only has 200 entries since only 200 rows have NULL names.  The
other index has a row for EVERY entry since the object_type column is never
null...

Review &
      Followup

      Rating: 5
Very
      good  February 24, 2002
Reviewer:  Eli
       from isreal
Can you explain in more details why concatenate index include an entry for each
column on the table .I will expectant that the first column will filter the
second column values .Did in this case NULL will be store on the index ?

          Followup:

If ANY column value in a concatenated index is NOT NULL -- an entry is made.
So, if I have an index on

t(a,b,c)

and ANY OF a,b,c is NOT NULL in a row -- an index entry is made.

      Rating: 5
Very
      good  February 24, 2002
Reviewer:  ELI
       from Isreal
I need to close the picture . for concatenate indexes shows above it's look like
the NULL's are storing on the index. (index include the same amount of record
that the table have) . did in this case null are storing on the index ? if yes
it is any why to access the table via the nullble values on the index  .

          Followup:

Think of it like this, when you

create index t_idx on t(a,b)

it is "as if" it were creating the index on

   a || b

if the result of

  a || b

is NOT NULL -- an entry will be made in the index structure.  If it is NULL, it
will not be in the index.

NULL columns may be stored in an index (a b*tree index) however, entirely NULL
index entries are NOT stored in the b*tree.

If at least one of the columns in the index is not nullable, we have great
opportunities to use this index to find nulls in the table, consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( a int, b int NOT NULL, c int
);

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t
  2  select decode( mod(rownum,100), 0, null, rownum ), rownum, rownum
  3  from all_objects;

17148 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx on t(a,b,c);

Index created.

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

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a,b from t where a is null;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=171 Bytes=1368)
   1    0   INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=171
Bytes=1368)

see how it can use an index range scan now to satisfy IS NULL.  Going further,
even if B is NULLABLE in this example:

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t modify b null;

Table altered.

under the appropriate cases, we can still use the index to satisfy the is
null.  Here I just added a predicate on b so that the leading edge of the index
is used:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select a,b from t where a is null and b = 5;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=8)
   1    0   INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=1 Bytes=8)

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off

and again, we can scan it even though we are looking for NULLs (since the
predicate precludes B from being null, we can index range scan safely)

      Rating:
      4
  February 24, 2002
Reviewer:
      A reader

      Rating: 4
null with
      index  August 11, 2003
Reviewer:  Abhi
       from india
hello Sir ..

sir if column is not indexed .. then what would be better..

where col1 is not null
   or
where length(col1) >  0 ...
column length is varchar2(1000);

Thanx

          Followup:

the first is semantically more meaningful, hence more correct.

August 11, 2003 -- Thanks for the question regarding "Index
      rebuild", version 8i

      You Asked
Hi,

 How to determine if an index needs to be rebuilt if the optimizer mode is RULE?

Thanx.

      and we said...
I'm a big believer that indexes do not need to be rebuilt except in very
perculiar cases.  99% of index rebuilds are a waste of time.

Some people look at the number of deleted entries in the index and rebuild based
on that (analyze index index_name validate structure and then query index_stats
to see statistics on the index such as deleted entries and such)...

I say that might be misleading.  If 50% of the entries are deleted (leaving
"holes" in the index) -- should we rebuild?  Maybe -- maybe not. depends on how
the index is used and what data it indexes.  Consider this:

create table emp ( empno int, ename varchar2(25) );

create index emp_idx1 on emp(empno);
create index emp_idx2 on emp(ename)

Now, suppose empno is filled in via a sequence.  Its always increasing and we
never reuse a value.  In our system, we actually delete rows when we fire
someone (a real HR system would never do that).  So, emp_idx1 develops "holes"
over time that will never be reused.  It we find it to be X% "empty" over time--
it might be a candidate for a rebuild.  We'll never reuse that deleted space
(unless we end up deleting all index entries on a block).

But, the index on ename -- that's a little more chaotic.  Say we have employees:

Bill
Bob
Mary

Say we fire Bob, now we have:

Bill
X-Bob (deleted entry)
Mary

Should we rebuild an index like that?  Probably NOT, the reason -- we are going
to hire Hillary:

Bill
Hillary
Mary

Hillary will reuse Bobs slot.  There is no reason to get rid of that slot --
only to have to rebuild it again later.  If the data you are indexing has a good
probability of reusing a slot like that -- rebuilding can actually slow you down
over time (it takes time to split a block -- with empty entries -- the chances
we need to split are reduced.  If you rebuild -- they'll go up -- you unsplit
everything, got rid of the space.  Now we have to re-split again -- every time
you rebuild).  I find most indexes hit a steady state -- if you rebuild them,
they'll go back into that state over time.  So rebuilding is somewhat self
defeating in that case.

Make sure to BENCHMARK yourself here.  Before rebuilding -- capture statistics,
performance metrics, IO's, etc -- against that index.  After rebuilding --
compare the results.  If you got nothing back, no increased performance,
decreased IO, etc -- don't rebuild it again later -- it doesn't buy you
anything.
Review & Followup

      Rating: 4
Index
      rebuild  October 04, 2002
Reviewer:  Rob
      Katofiasc  from Chicago, IL
What metrics should I be looking at to determine if the index rebuild helped or
not? Can you give a brief example of what data you would capture before and
after the index rebuild.

          Followup:

Keep stats pack reports.  Look at your top sql queries for example and see if
the IO's go down, transaction rates go up, IO's decrease, performance improves.

If you felt the index needed rebuilding -- you must have had some material
numbers that made you do it -- see if those numbers improve

      Rating: 4
Should we
      re-create index before Analyzing tables/schema  October 07,
      2002
Reviewer:  Syed  from India
We are using Cost based optimizer and some time we do large
data insert. At that time we need to analyze tables/schema to keep the
statistics current. But before Analyzing table/schema is it necessary to
re-create index ?

Please clarify.

          Followup:

NO

      Rating:
      4
  October 07, 2002
Reviewer:  Sagi
       from India
Hi Tom !

Can you give an example please.

Thanx.

Regards,
Sagi

          Followup:

example of what exactly?

      Rating: 4
Index
      Rebuild  October 07, 2002
Reviewer:  Randy
      Bollock  from Chicago, IL
Rebuilding of index helped me in couple of occasions. Let me share my
experience.

1. One fine morning users started to complain they cannt access a part of the
system. I looked into the query and submitted the same query to find the error.
It was clearly pointing to index corruption, ORA - 1502. I have no clue how on
earth that could happen. There was apperently no h/w error reported. The
database was running on HP AutoRaid(RAID 0, 1). Rebuilt the index and everything
came back well.

2. Migrated 7.3.4 database to 8.1.7. Wanted to use the LMT. But found no better
way to use LMT rather than rebuilding the indexes into LMT.

Thanks,

Randy

      Rating: 5
Would coalesce
      do the same thing?  August 05, 2003
Reviewer:
      Peter Tran  from Houston, TX USA
Tom,

In your example above, you wrote:

[quote]
Now, suppose empno is filled in via a sequence.  Its always increasing and we
never reuse a value.  In our system, we actually delete rows when we fire
someone (a real HR system would never do that).  So, emp_idx1 develops "holes"
over time that will never be reused.  It we find it to be X% "empty" over time--
it might be a candidate for a rebuild.  We'll never reuse that deleted space
(unless we end up deleting all index entries on a block).
[/quote]

Wouldn't "alter index <index_name> coalesce;" do the same thing?

Thanks,
-Peter

          Followup:

yes (not the same thing but the end result is what we are after -- reclaim
space)

      Rating: 4
Index rebuild
      releases space???  August 06, 2003
Reviewer:
      Ashwani  from INdia
Dear tom,
  One of the dba from our site told us that the rebuild of index will release
space in tablespace in which index is present...

Is that a fact??

we ran the following query after a rebuild and acutally found that free space
had increased

select a.TABLESPACE_NAME,
       a.BYTES "Size",
       (a.BYTES-b.BYTES) "Used",
       round(((a.BYTES-b.BYTES)/a.BYTES)*100,2) "PUsed",
       b.BYTES "Free",
       round((1-((a.BYTES-b.BYTES)/a.BYTES))*100,2) "PFree"
from   sys.orasnap_ddf a, sys.orasnap_dfs b
where  a.TABLESPACE_NAME = b.TABLESPACE_NAME
order  by a.tablespace_name

Regards

          Followup:

ask this dba "well, for how long?  I mean, the index got fat and used that
space, isn't it just going to grow all over again?  haven't you just reclaimed
some space that it will just have to allocate all over again?"

The question is "for how long", did you actually sell some of your disks on EBay
since they are all empty?  Or, do you still have them cause the indexes
naturally grow back to their equilibrium size?

read this:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6601312252730
those guys "reclaimed space" and then spent the next week or so painstakingly
"reallocating it" only to "reclaim it" again.  vicious loop they got into there.

      Rating: 4
is there a need
      for rebuild  August 08, 2003
Reviewer:
      Ashwani  from India
hi Tom,
follwing is the health report of our database for a day

We r using oracle APPS 11.5.5 with DB 8.1.7

No. of Sessions
---------------
        122

Buffer Hit Ratio
----------------
          50

Logical reads Physical Reads Physical Writes
------------- -------------- ---------------
   1.8447E+19      1.8447E+19      1.8447E+19

Data Dictionary Hit Ratio

Data Dict. Gets Data Dict. Cache Misses DATA DICT CACHE HIT RATIO
--------------- ----------------------- -------------------------
    7870953          340130            95.6786681

Library Cache Ratio

executions Cache Misses (1-(SUM(RELOADS)/SUM(PINS)))*100 LIBRARY CACHE PIN HIT
RATIO LIBRARY CACHE GET HIT RATIO
---------- ------------ --------------------------------
--------------------------- ---------------------------
 118433124      31851               99.9731063              99.645
98.6516038

NAMESPACE    Get Hit Ratio Pin Hit Ratio    Reloads
--------------- ------------- ------------- ----------
SQL AREA           92         99      8143
TABLE/PROCEDURE        99         99     23702
BODY               99         99         0
TRIGGER            99         99         6
INDEX               91         83         0
CLUSTER            98         98         0
OBJECT              100        100         0
PIPE               99         99         0

8 rows selected.

Memory

NAME              VALUE
-------------------- ----------
Fixed Size          73620
Variable Size          559747072
Database Buffers     1864499200
Redo Buffers        2105344
             ----------
sum             2426425236

NAME                BYTES
-------------------------- ----------
free memory             32849892
free memory             14442496

Tot SQL since startup SQL executing now
--------------------- -----------------
         37699679             24

If these are > 1% of Total Number of Requests for Data
then extra rollback segments are needed.

CLASS            COUNT
------------------ ----------
free list            1
system undo block        0
system undo header        0
undo block          428
undo header           34

Total Number of Requests for Data

SUM(VALUE)
----------
3.6893E+19

The star DBA to increase performance rebuild the indexes and is Also asking to
increase the size of SGA..
it is already very huge in Size..
Pls guide in the right direction..

Ashwani

          Followup:

wow, thats a lot of numbers.

too bad no one could determine the health of anything based on them.

Your DBA actually sees:

Logical reads Physical Reads Physical Writes
------------- -------------- ---------------
   1.8447E+19      1.8447E+19      1.8447E+19

and doesn't think "wow, something is wrong there, better look into that".  tell
that dba to read:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6047280202769
they have absolutely NO CLUE WHATSOEVER as to the true hit ratio.  NOT that the
hit ratio is very meaningful (tell them after reading that link to goto
hotsos.com and read the "are you still using cache hit ratios".

Ask the star DBA for the scientific reasoning behind rebuilding indexes and SGA
increases.  They must have some solid technical reasoning beyond "well, everyone
knows a bigger sga is better and indexes need to be rebuilt"

If they don't, well......

      Rating: 4
Is there a need
      for rebuild corrected??  August 11, 2003
Reviewer:
      Ashwani  from India
Dear tom,
   i do not think the bug u have listed in the link is applicable to us .. Sorry
i potrayed a wrong picture.. The correct pic is below

No. of Sessions
---------------
         63

Buffer Hit Ratio
----------------
       99.33

Logical reads Physical Reads Physical Writes
------------- -------------- ---------------
      1749924         8833837           71181

Data Dictionary Hit Ratio

Data Dict. Gets Data Dict. Cache Misses DATA DICT CACHE HIT RATIO
--------------- ----------------------- -------------------------
      91987            6948            92.4467588

Library Cache Ratio

executions Cache Misses (1-(SUM(RELOADS)/SUM(PINS)))*100 LIBRARY CACHE PIN HIT
RATIO LIBRARY CACHE GET HIT RATIO
---------- ------------ --------------------------------
--------------------------- ---------------------------
   7229659         11               99.9998478              99.932
93.5214788

NAMESPACE    Get Hit Ratio Pin Hit Ratio    Reloads
--------------- ------------- ------------- ----------
SQL AREA           84         99        11
TABLE/PROCEDURE        95         99         0
BODY               94         93         0
TRIGGER            88         88         0
INDEX               97         97         0
CLUSTER            96         96         0
OBJECT              100        100         0
PIPE               99         99         0

8 rows selected.

Memory

NAME              VALUE
-------------------- ----------
Fixed Size          73620
Variable Size          559747072
Database Buffers     1864499200
Redo Buffers        2105344
             ----------
sum             2426425236

NAME                BYTES
-------------------------- ----------
free memory            448321852
free memory             15347712

Tot SQL since startup SQL executing now
--------------------- -----------------
          2423600              8

INSTANCE_NUMBER INSTANCE_NAME     HOST_NAME                              VERSION
       STARTUP_T STATUS  PAR    THREAD# ARCHIVE LOG_SWITCH_ LOGINS     SHU
DATABASE_STATUS   INSTANCE_ROLE
--------------- ----------------
----------------------------------------------------------------
----------------- --------- ------- --- ---------- ------- -----------
---------- --- ----------------- ------------------
          1 PROD         chiia4ood01                              8.1.7.1.0
  10-AUG-03 OPEN    NO       1 STARTED         ALLOWED    NO    ACTIVE
PRIMARY_INSTANCE

If these are > 1% of Total Number of Requests for Data
then extra rollback segments are needed.

CLASS            COUNT
------------------ ----------
free list            0
system undo block        0
system undo header        0
undo block            1
undo header            2

Total Number of Requests for Data

SUM(VALUE)
----------
  10584138

Pls gude me now on the health report of the db and is there a need for increase
of SGA

          Followup:

gee, I wonder where the other made up data came from huh?

but now I wonder how this all adds up:

Buffer Hit Ratio
----------------
       99.33

Logical reads Physical Reads Physical Writes
------------- -------------- ---------------
      1749924         8833837           71181

tell me, how you have a 99% plus hit ratio when you appear to do 6 PIO's for
every 1 LIO.

these are meaningless numbers -- you can make ZERO decisions based on this data.

use statspack

August 11, 2003 Kelly -- Thanks for the question regarding "pivot
      a result set", version

      You Asked
Hi Tom,
I have two tables which are a 1 - M relationship.
I have a user who want a horizontal view of the
data.

For instance,
Table A
Key-1 char(3);
table A values
_______
1
2
3

Table B
bkey-a char(3);
bcode  char(1);
table b values
1 T
1 A
1 G
2 A
2 T
2 L
3 A

What he wants to see is
all matches between the tables
where the code
is "T" "A" or "L":
1, T, A
2, A, T, L
3, A

I have tried joining the tables to themselves
and doing an outer join but I end up
with multiple rows.
1, T
1, A
2  a
2  t
2  l
3  a
etc

Is this possible?

      and we said...
Sure, using decode to transpose columns like this is fairly
straightforward.  I'll do it in 2 steps so you can see how it is
done.

We start by joining A to B and creating a 'sparse' matrix.
We'll then "squash out" the redundant rows giving us the desired
effect.

ops$tkyte@8i> select key_1,
  2             decode( bcode, 'T', bcode, null ) t_code,
  3             decode( bcode, 'A', bcode, null ) a_code,
  4             decode( bcode, 'L', bcode, null ) l_code
  5    from a, b
  6   where a.key_1 = b.bkey_a
  7  /

KEY T A L
--- - - -
1   T
1     A
1
2     A
2   T
2       L
3     A

7 rows selected.

So, there is our 'sparse' matrix.  What we want to do now is
collapse the rows by key_1.  Thats what group by does for us:

ops$tkyte@8i>
ops$tkyte@8i> select key_1,
  2             max(decode( bcode, 'T', bcode, null )) t_code,
  3             max(decode( bcode, 'A', bcode, null )) a_code,
  4             max(decode( bcode, 'L', bcode, null )) l_code
  5    from a, b
  6   where a.key_1 = b.bkey_a
  7   group by key_1
  8  /

KEY T A L
--- - - -
1   T A
2   T A L
3     A

You could add a where clause in the event you have a row in A,
such there no rows in B have the value T, A, L.  EG:

ops$tkyte@8i> insert into a values ( '4' );
ops$tkyte@8i> insert into b values ( '4', 'Z' );

ops$tkyte@8i> select key_1,
  2             max(decode( bcode, 'T', bcode, null )) t_code,
  3             max(decode( bcode, 'A', bcode, null )) a_code,
  4             max(decode( bcode, 'L', bcode, null )) l_code
  5    from a, b
  6   where a.key_1 = b.bkey_a
  7   group by key_1
  8  /

KEY T A L
--- - - -
1   T A
2   T A L
3     A
4                  <<<<<====== you might not want that row
(maybe you do?)

ops$tkyte@8i> select key_1,
  2             max(decode( bcode, 'T', bcode, null )) t_code,
  3             max(decode( bcode, 'A', bcode, null )) a_code,
  4             max(decode( bcode, 'L', bcode, null )) l_code
  5    from a, b
  6   where a.key_1 = b.bkey_a
  7     and b.bcode in ( 'T', 'A', 'L' )    <<<<<====== that'll
get rid of it.
  8   group by key_1
  9  /

KEY T A L
--- - - -
1   T A
2   T A L
3     A

Review &
      Followup

      Rating:
      4
  April 04, 2001
Reviewer:  Vishaka
       from India

      Rating:
      5
Excellent  July 06, 2001
Reviewer:
      Andy  from Kuala Lumpur, Malaysia
Turning a table on its side couldn't be easier all of a sudden!

      Rating: 4
Good
      Tip  November 01, 2001
Reviewer:  Gupta Gaurav
      Sharan  from Pune, India
It is really worth to learn this trick.

      Rating: 5
pivot a result
      set  January 20, 2002
Reviewer:
      A reader
yes this really helped me in solving my problem.

Thanks again

Maria

      Rating: 4
Can we do it in
      single with oracle 8.1.7 ?  March 10, 2002

Reviewer:  A reader

      Rating: 4
Cross
      Tab  April 05, 2002
Reviewer:  A reader
       from UK
Your example of pivoting a result is very useful.

I've tried using the example but am getting a problem. I have a table which
looks like,

CODE    NAME            ID    AVG_VAL
1    Wokingham    1    95.5555555555556
1    Wokingham    2    92.156862745098
1    Wokingham    3    86.1971830985915
1    Wokingham    4    82.2222222222222
1    Wokingham    5    81.6666666666667
2    London            1    20
2    London            2    100
2    London            3    20

I want to get a single row based on the code and name, thefore looking like

CODE    NAME            1                2
1    Wokingham    95.55555555555561   92.156862745098
..
..

When I try to collapse the rows by code and name I get 'not a GROUP BY
expression'.

select code, name
      ,DECODE (id, 1, avg_val, NULL) c1
      ,DECODE (id, 2, avg_val, NULL) c2
from tmp_values
group by code, name

          Followup:

select code, name, MAX(  decode ... ), MAX(  decode.... )
  from tmp_values
group by code, name

      Rating: 5
runtime
      value  April 23, 2002
Reviewer:  reader

Hi  tom,
  in your example above you know that there are 3 (fix) values in the table a
'T' , a 'A' and a "L"

 so we just put them in the decode function and returning the values.

  in my senerio
1.) I will come to konw this value at run time
2.) for every key there are not same # of options

 can we still do it with decode ?

          Followup:

You will be using a report writing tool to do this then.

SQL means you KNOW the number of columns.

You do not know the number of columns until the data is fetched.

(i do show a way to do this in my book using dynamic sql and ref cursors -- it
is in the anlytic functions chapter)

      Rating: 5
Thanks
        April 23, 2002
Reviewer:  reader

Thanks, Tom

 I have your book. I will try from there !!

      Rating: 5
can we wrap it
        June 10, 2002
Reviewer:  GajarBajar

Tom

I'm using sqlplus

Your final output above is
KEY T A L
--- - - -
1   T A
2   T A L
3     A

Now my data after pivoting

KEY T A L
--- - - -
1   T A
2   T A L n m n f g h r e w w w ew r t g gh h h h h gf fd
3     A

can I wrap my data for column 2 and display it as

KEY T A L
--- - - -
1   T A
2   T A L n m n f g h r e w w
    w ew r t g gh h h
    h h gf fd
3   A

my pivot output will be concatanated and not separate columns like yours i.e.
the below output is the result of concatanation. The first column remains the
same.
T A L n m n f g h r e w w
    w ew r t g gh h h
    h h gf fd

I tried using chr(10) . It worked but , the columns follwing this column did not
print.

Kindly  provide the solution.

          Followup:

You cannot, why would the L column line up under the T column?  SQLPlus won't do
that for you, not at all.

      Rating: 4
Just what I
      needed  March 06, 2003
Reviewer:  Cam Hodge
       from Perth Australia
I must have spent a few hours trying to work this out, with groups, etc in the
end all I needed was the MAX( clause.

Fantastio Tom!

      Rating: 4
Tanks
        April 17, 2003
Reviewer:  Luca  from
      Italy
Your explanation on pivot drive me to the solution on problem in a view.

      Rating:
      3
Transpose..  July 23, 2003
Reviewer:
      vj  from in
I have also caught up with a situation of transpose and struggling to
implement..

If i fire a query i would get such an result set

MON    TUE    WED    THU    FRI    SAT    SUN
0    1.5    0    1.5    0    0    0
8    0    0    0    0    0    0
0    0.5    0    0    2    0    0
0    0    1    0    0    0    0
0    0    0.5    0    0    0    0
0    0    4    3    1    0    0
0    0    0    3.5    3    0    0
0    0    0    0    2    0    0
0    6    2.5    0    0    0    0

Now i know this timesheet for which week...by having a start date of the week
i.e. select mon,tue,wed,thu,fri,sat,sun from <MYTABLE> where empcode =
<MYEMPCODE> and weekstartdate = '07/01/2003'

The output i am looking is

07/01/2003-0
07/01/2003-8
07/01/2003-0
07/01/2003-0
07/01/2003-0
07/01/2003-0
07/01/2003-0
07/01/2003-0
07/01/2003-0
08/01/2003-1.5
08/01/2003-0
08/01/2003-0.5
08/01/2003-0
08/01/2003-0
08/01/2003-0
08/01/2003-0
08/01/2003-0
08/01/2003-6
..
..
..
TILL SUNDAY

the number of rows in my example is given as 9..it might be more or less than
that depends upon the number of tasks..

          Followup:

don't understand the data here.

      Rating: 4
Using
      Pivot  July 23, 2003
Reviewer:
      Amalorpavanathan  from INDIA
Hi Tom,
It is very useful for me.

      Rating:
      3
  July 24, 2003
Reviewer:  vj
       from in
SELECT B.WK_SRT_DT,A.MON,A.TUE,A.WED,A.THU,A.FRI,A.SAT,A.SUN FROM
TIMESHEETDETAIL A,TIMESHEETMASTER B
WHERE
A.TIMESHEETID = B.ID AND B.EMPLOYEEID = 1211
AND
B.WK_SRT_DT = TO_DATE('07-01-2002','DD-MM-YYYY')
/

since the alignment is missing i have given you an comma seperated output
tom..sorry for the inconv.

TASKID,MON,TUE,WED,THU,FRI,SAT,SUN
Meeting,0,1.5,0,1.5,0,0,0
Leave,8,0,0,0,0,0,0
Modifyi,0,0.5,0,0,2,0,0
MoM pre,0,0,1,0,0,0,0
Updatin,0,0,0.5,0,0,0,0
Testing,0,0,4,3,1,0,0
Process,0,0,0,3.5,3,0,0
Meeting,0,0,0,0,2,0,0
SQA Aud,0,6,2.5,0,0,0,0

week start date is 07-01-2002 which is monday and 08-01-2002 is tuesday so on
and so forth till sunday..

i want a output like

all monday data
all tuesday data
all wednesday data
...
..
all sunday data

i want to transpose the column to row.

Rgds

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4031849343543#11288826016365

      Rating:
      3
  August 11, 2003
Reviewer:  vj
       from in
Hi Tom,

COL1    COL2
====    ====
MFAA    IUWY,JKH
MFAAA    IUWY,JKH
MFA    IUWY
MFAAA    COMSC1,PBB
TEST    IUWY,JKH
MFB    COMSC1,PBB
1    COMSC1

The values in the col2 can grow...I need to pivot that to column.

i.e

MFAA    IUWY
MFAA    JKH
MFAAA    IUWY
MFAAA    JKH
...
...
MFB    COMSC1
MFB     PBB

How this can be achieved considering the values in the column 2 is not known..

Rgds

          Followup:

version?

      Rating: 5
Single column
      into Row  August 11, 2003
Reviewer:  Bipin
      Ganar  from INDIA
Hi Tom,
I have a table "abxx" with column "absg" Number(3)
which is having following rows

absg
--------
1
3
56
232
43
436
23
677
545
367
.
.
.
.
.
.
.
xxxxxx No of rows

How can i get all these values in single row with comma separated?

Like

output_absg
-----------------------------------------------------
1,3,56,232,43,436,23,677,545,367,..,..,..............

Thanx
Bipin G

          Followup:

search this site for

stragg

      Rating:
      3
version  August 11, 2003
Reviewer:
      vj  from in
the version is 9ir2 tom.

Rgds

          Followup:

you too should search for

stragg

August 11, 2003 Miguel -- Thanks for the question regarding
      "ora-00604,ora-01578,ora-01110", version 8.1.7

      You Asked
Hi Tom:

I have the next problem.

My system goes down by an electricity interruption, when I restart the server, I
have file corruption I run de e2fsck of Linux and I can start the linux. In the
first moment I have some problems to mount my Oracle 8i database, now is working
but when I try to insert a new column a new field in any table, or when I try to
run a view or when I create a new table the Oracle give this error:

ORA-00604: error ocurred at recursive SQL level 1.
ORA-01578: ORACLE data block corrupted (file #1, block #36949).
ORA-01110: data file 1:
'/usr/local/oracle/oradata/bd/system01.dbf'

I have a Red Hat 6.2 and Oracle 8i.

Can I solve this problem?.
The block and segment corrupted are of an index segment I_COL3.

Thanks for your help.

      and we said...
You have a media failure here. The OS died in the middle of a write.

You need to take your last backup of system01.dbf, restore it and roll it
forward.  Hopefully you are in archivelog mode and have been doing backups.

We can easily recover from this -- assumming you have backups.
Review &
      Followup

      Rating: 2
No
      backups  June 07, 2002
Reviewer:  Miguel
      Garcia  from Spain
I'm afraid I no have a backup. I understand that if I don`t have it y can
restore the system.

I have a backup of the filesystem a backup of the file system01.dbf can this be
usefull?

Thank you.

          Followup:

"I understand that if I don`t have it y can
restore the system."

(besides your keyboard being on the verge of failure -- it drops letters
frequently -- very important ones like vowels and such!...) that statement is
inaccurate.

You should understand that if you don't have it (a backup), you CANNOT restore
the system.

If you have a filesystem backup AND
the filesystem backup was done whilst
  a) the database was down OR
  b) the tablespaces were in backup mode

then yes, of course you can use it -- that would be a backup!

If the filesystem backup was done whilst the database was up and you did not put
tablespaces into backup mode -- doubtful that you can use it.

If you are in noarchivelog mode (which I'm guessing you are), then the database
MUST have been shutdown and you can restore as of the time of the last backup.

The index that has the bad block is a "much needed one"

sys@ORA817DEV.US.ORACLE.COM> alter index i_col3 rebuild;
alter index i_col3 rebuild
*
ERROR at line 1:
ORA-00701: object necessary for warmstarting database cannot be altered

You can try exporting whatever you can and creating a new one.

      Rating: 5
only one of
      several datafiles in a tablespace is corrupt  July 02, 2002

Reviewer:  steve  from Montreal
Hi Tom,

We are getting ORA-01578 on a couple of datafiles (not index).

Suppose we have tablespace TS1 that is comprised of multiple
datafiles:

   /db100/abc/ts1_data01.dbf
   /db100/abc/ts1_data02.dbf
   /db100/abc/ts1_data03.dbf

Suppose that only  /db100/abc/ts1_data02.dbf is experiencing
the ORA-01578. Note that the database is up and running.

Would the following be the proper procedure to recover:

  1. From SQL*Plus, enter:

       ALTER TABLESPACE TS1 OFFLINE TEMPORARY;

    (I'm assuming this "flushes" all pending writes to
     the datafiles of this tablespace).

  2. From OS level, copy /db100/abc/ts1_data02.dbf
     to a temporary location (in case of problems)

  3. restore /db100/abc/ts1_data02.dbf from the last full backup.

  4. Run dbv on the restored file to make sure it is valid.

  5. If necessary, restore archived redo log files to the
     archive destination.

  6. *** This is where I'm not 100% sure. ***

     Do we recover the single datafile or the whole tablespace?
     I'm assuming we only need to recover the datafile. But I
     also think recovering the tablespace will accomplish the same.

       RECOVER TABLESPACE TS1
       or
       RECOVER DATAFILE /db100/abc/ts1_data02.dbf

    (will likely be prompted for applying necessary redo log files)

  7. Put the tablespace back online, enter:

       ALTER TALBESPACE TS1 ONLINE;
~

          Followup:

6) you can do either or -- the other files are "recovered" and it'll just do the
datafile.

      Rating: 4
Block
      corruption  June 14, 2003
Reviewer:
      vinodhps  from India,Chennai
Hi tom ,
In this same senario..if iam  running my database on noarchive mode.When ever i
try to take Export it does not exporting that particular tablespace.

we are using
OS-UNIX HP
Oracle 8.0.4.
24x7

Can you please suggest me what to do.. now tom
thanks in advance tom

      Rating:
      3
ora-00604,ora-01578,ora-01110  August 11, 2003

Reviewer:  A reader
Hi Tom,

If for instance, corruption occurred in unique/primary key index and only tiny
(say 1 block) part of it is affected, would i be able to insert records into
that table? if yes , would i be able to delete records from child tables?

Thanks
RameshG

          Followup:

maybe, maybe not.  depends.

you need to recover or rebuild this index asap anyway, so the point is sort of
"moot"

August 11, 2003 Arthur -- Thanks for the question regarding "Bind
      Variables", version 8.1.5

      You Asked
Tom,

I have a rather lengthly query here that uses UNION ALL as well
as bind variables.  When my procedure comes to execute this
code, I receive the following error:

ORA-01008: not all variables bound

I'm not sure how to resolve this.

OPEN ej_ref_cursor FOR
   'SELECT tranname, batchnum, trannum, seqnum
    FROM ej1
    WHERE date_time_batch_closed > :v_start_date AND
          date_time_batch_closed <= :v_end_date  AND
         (tranname = "Miscellaneous Payment"     OR
          tranname = "Auto Amusement Devices"    OR
          tranname = "Certificate of Payment"    OR
          tranname = "CDOT Right of Way Permit"  OR
          tranname = "CDOT Permit"               OR
          tranname = "Public Way Permit -- New"  OR
          tranname = "50/50 Sidewalk"            OR
          tranname = "Multiple Dwellings Permit" OR
          tranname = "Intergovernmental Advices" OR
          tranname = "Building Permit"           OR
          tranname = "Administrative Hearings"   OR
          tranname = "Auto Pound"                OR
          tranname = "Tickets"                   OR
          tranname = "Ticket -- Skeletal"        OR
          tranname = "Ticket -- Backlog"         OR
          tranname = "Buildings Special Collection" OR
          tranname = "Cigarette Sale to Minors -- Warning Sign"
OR
          tranname = "Unidentified Payment") AND
          corrstat = " "
    UNION ALL
    SELECT tranname, batchnum, trannum, seqnum
    FROM ej1
    WHERE date_time_batch_closed > :v_start_date and
          date_time_batch_closed <= :v_end_date and
          tranname = "Administrative Hearings" and
          misc8 is not null and misc8 not like "$0.00" and
          corrstat = " "
    UNION ALL
    SELECT tranname, batchnum, trannum, seqnum
    FROM ej1
    WHERE date_time_batch_closed > :v_start_date and
          date_time_batch_closed <= :v_end_date and
          tranname = "Administrative Hearings" and
          misc12 is not null and misc12 not like "$0.00" and
          corrstat = " "
    UNION ALL
    SELECT tranname, batchnum, trannum, seqnum
    FROM ej1
    WHERE date_time_batch_closed > :v_start_date and
          date_time_batch_closed <= :v_end_date and
          tranname = "Building Permit" and
          corrstat = " "
    UNION ALL
    SELECT tranname, batchnum, trannum, seqnum
    FROM ej1
    WHERE DECODE(tranname,"Building Permits",
          TO_NUMBER(NVL(misc14,"$0.00"),"$9999999999V99")) != 0
AND
          tranname = "Building Permits" AND
          date_time_batch_closed > :v_start_date and
          date_time_batch_closed <= :v_end_date and
          corrstat = " "
    UNION ALL
    SELECT tranname, batchnum, trannum, seqnum
    FROM ej1
    WHERE DECODE(tranname,"Building Permits",
          TO_NUMBER(NVL(misc8,"$0.00"),"$9999999999V99")) != 0
AND
          tranname = "Building Permits" AND
          date_time_batch_closed > :v_start_date AND
          date_time_batch_closed <= :v_end_date AND
          corrstat = " "
    UNION ALL
    SELECT tranname, batchnum, trannum, seqnum
    FROM ej1
    WHERE DECODE(tranname,"Building Permits",
          TO_NUMBER(NVL(misc9,"$0.00"),"$9999999999V99")) != 0
AND
          tranname = "Building Permits" AND
          date_time_batch_closed > :v_start_date and
          date_time_batch_closed <= :v_end_date and
          corrstat = " "
    UNION ALL
    SELECT tranname, batchnum, trannum, seqnum
    FROM ej1
    WHERE date_time_batch_closed > :v_start_date and
          date_time_batch_closed <= :v_end_date and
          tranname = "Auto Pound" and
          corrstat = " "
    ORDER BY 2,3'
    USING v_start_date, v_end_date;

      and we said...
Well -- you don't really have " in there do you -- you mean ''
(two single quotes).  But the problem here is you do not have
enough bind inputs for each :variable in there.  It cannot do
them by name.  So, in the following example -- I have :x in
there two times:

scott@DEV816> declare
  2          type rc is ref cursor;
  3
  4          l_cursor rc;
  5  begin
  6          open l_cursor for
  7          'select ename from emp where ename like :x
  8           union all
  9           select ename from emp where ename like :x'
 10          using '%';
 11  end;
 12  /
declare
*
ERROR at line 1:
ORA-01008: not all variables bound
ORA-06512: at line 6

In order for this to work -- we must supply the values for :x
as it does them positionally:

scott@DEV816>
scott@DEV816>
scott@DEV816> declare
  2          type rc is ref cursor;
  3
  4          l_cursor rc;
  5  begin
  6          open l_cursor for
  7          'select ename from emp where ename like :x
  8           union all
  9           select ename from emp where ename like :x'
 10          using '%', '%';
 11  end;
 12  /

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating: 4
order of bind
      variables  March 26, 2002
Reviewer:
      nagarjuna
i am using too many bind variables in my dynamic update statement.
when i am binding them finally with 'using' clause. can i only bind in the
order(or sequence) they bound,
is there a way where i can tell by variable names

execute immediate 'update emp set sal = sal + 100 where empno = :empno and sal =
:sal' using v_empno,v_sal

how can mention in the above stmt like :empno is mapped to v_empno and :sal is
mapped to v_sal. like we do it
using dbms_sql

          Followup:

you don't, it is positional in native dynamic sql.

      Rating: 4
Now I am
      confused  March 27, 2002
Reviewer:  Georg
       from Germany
Tom,

your example is small and simple. O.K. I have to use two parameters (USING
'%','%') in your example.

In the Oracle documentation - PL/SQL User's Guide and Reference in chap. 10
Native Dynamic SQL -
there is an example just doing the opposite of your solution:

"Using Duplicate Placeholders
Placeholders in a dynamic SQL statement are associated with bind arguments in
the USING clause by position, not by name. So, if the same placeholder appears
two or more times in the SQL statement, each appearance must correspond to a
bind argument in the USING clause. For example, given the dynamic string

sql_stmt := 'INSERT INTO payroll VALUES (:x, :x, :y, :x)';

you might code the corresponding USING clause as follows:

EXECUTE IMMEDIATE sql_stmt USING a, a, b, a;

However, only the unique placeholders in a dynamic PL/SQL block are associated
with bind arguments in the USING clause by position. So, if the same placeholder
appears two or more times in a PL/SQL block, all appearances correspond to one
bind argument in the USING clause. In the example below, the first unique
placeholder (x) is associated with the first bind argument (a). Likewise, the
second unique placeholder (y) is associated with the second bind argument (b).

DECLARE
   a NUMBER := 4;
   b NUMBER := 7;
BEGIN
   plsql_block := 'BEGIN calc_stats(:x, :x, :y, :x); END;'
   EXECUTE IMMEDIATE plsql_block USING a, b;
   ...
END;
"

So, when should one give how many using parameters?

          Followup:

they are the same -- I find the first variant more pleasing personally.

      Rating: 3
How to define
      bind variable in Stored procedures  March 27, 2002

Reviewer:  Anthony  from India
We use like :x for bind variables in anonymous.
How to define bind variables in stored procedure for  simple qureis ( no dynamic
sql)?

          Followup:

don't understand what you mean here.

In PLSQL you should be aware that any variable you reference is automagically a
bind variable:

declare
   l_ename varchar2( 20) := 'KING';
begin
   for x in ( select * from emp where ename = l_ename )
   loop
      .....

In that case, l_ename IS a bind variable.

      Rating: 4
Effect of using
      Implicit binding  August 15, 2002
Reviewer:
      Subhro  from Mumbai, India
Tom,
   I read in your book that we can enable implicit use of bind variables in SQL
statements. But, you mentioned that using bind variables explicitly in the SQL
statement is preferable to having Oracle do that for us.

I have been asked to 'try to' tune a OLTP web application. I found that the
servlets are using straight forward SQL statements, i.e. not using any bind
variables and generating the query with the values of the variables and then
executing those queries thru a Utility class which handles the database access
code. Unfortuantely, I have been asked to try out other means before disturbing
the servlets.

I thought of using implicit binding, but then I remembered ur comment on that.

I have one other option. Before executing the query, I can parse the query
string and replace the literals with the bind variables. I can do that in the
class that handles database access. I have tried it out and it does work.

But the issue is, I am in no position to figure out which of the literals in the
SQL statements are really constants and I should not use bind variables for
them. So, I have to use bind variables for every literal that I find.

Should I use Implicit binding in this scenario. Will that be preferrable to
using Java code to do the same thing.

          Followup:

If you "parse the query string and replace the literals..." -- you'll be doing
EXACTLY what cursor_sharing=force does.

We have the same exact issue -- we have to use bind variables for every literal
we find (in 9i there is cursor_sharing=similar which *attempts* to address this
but it leads to lots of unique sql)

Using cursor sharing would be easier then writing and maintaining your own code,
although the authors of this system can HARDLY call it an OLTP system if they've
programmed it the way they did.  They should be ashamed of what they've done and
fix it properly.

Sounds like another case of "hire a bunch of java programmers, hide the database
from them for we all know the database is far too complex for them to comprehend
(thats that generic Utility class, written by another java programmer that never
read about the database), and see what happens".  Sort of like giving typewriter
to a bunch of .....

      Rating:
      4
  October 08, 2002
Reviewer:
      A reader
How do i 'parse the query string and replace the literals'
I am in need of a similar solution so as to use bind variables.
I generate sql statements from java with the values and formulate a string and
execute that SQL string in teh database.
In java i do..
strQry='select ....where col1='+variable
and execute it in the database.
Now if i need to use bind variables for the same query how do i do it?

          Followup:

You need to use a PreparedStatement and prepare

strQry = "select .... where col1 = ?"

and then bind to it using the pstmt.setXXXX methods.  You need to learn more of
the java JDBC API!

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1993620575194

      Rating: 3
A binding
      variables doubt  December 02, 2002
Reviewer:
      Abhijit  from Mumbai
Hi Tom,
I have a bind variables doubt. Here is my procedure.
It compiles fine, but I get a ORA-00903: invalid table name error.

create or replace procedure demodynsql IS
Begin
Declare TYPE RC1 IS REF CURSOR;
dynSQLCur RC1;
l_tname varchar2(20);
l_login varchar2(50);
Begin
     l_tname := 'USERDEFTABLE';
     l_login := 'abhi';
     Open dynSQLCur FOR
     'Select LoginID From :xTableName Where LoginID = :xLogin' Using l_tname,
l_login;
     Loop
         Exit When dynSQLCur%NOTFOUND;
         Fetch dynSQLCur Into l_login;
         dbms_output.put_line(l_login);
     End Loop;
     Exception
              When Others Then
                   Dbms_output.put_line(SQLERRM);
End;
End;

My question to you is, can I not use the xtablename as a bind variable? So how
do I resolve this problem?

Cheers!
Abhijit

          Followup:

You cannot bind an IDENTIFIER.  That would cause the plan to totally change.
The query plan for:

select * from emp;

it radically different from

select * from dept;

we would not be able to setup a plan, security check the query and so on if you
were to bind the tablename (or any identifier).

Here is a simple test to tell whether you can bind or not.  A bind can be used
anywhere a character string literal can be.  Hence, we know that:

select :x from dual;

is OK because:

select 'HELLO WORLD' from dual;

is valid.  However, we know that:

select * from :t

is invalid because:

select * from 'HELLO WORLD'

is obviously invalid.  Now, you need to be careful here -- because:

select * from t order by :x;

is valid -- because:

select * from t order by 'HELLO WORLD';

is valid but don't fool yourself into thinking that select * from t order by :x
will order by DIFFERENT columns -- rather "order by :x" is the same as "" --
nothing, you would be ordering by a CONSTANT literal value.

      Rating: 4
Bind variables
      in implicit cursor  January 07, 2003
Reviewer:
      Scott  from Perth, Western Australia
G'day Tom.

<quote>
In PLSQL you should be aware that any variable you reference is automagically a
bind variable:

declare
   l_ename varchar2( 20) := 'KING';
begin
   for x in ( select * from emp where ename = l_ename )
   loop
      .....

In that case, l_ename IS a bind variable.
</quote>

1) Does this mean this is the efficient way to bind variables in implicit
cursors? ie - is the SGA used effectively?
I was brought up using explicit cursors to open/fetch/close to get one record,
and since reading through entries on this site, I'd like to use implicit cursors
in future.
However, most of our queries are typically defined as such:
CURSOR c_states (p_state_code) IS
SELECT blah
FROM states
WHERE state_code = p_state_code;
2) How is this converted to an implicit cursor?

I performed a quick test, but I am unsure how to interpret results:
declare
   v_state_code varchar2(3) := 'WA';
begin
   for x in ( select * from states where state_code = v_state_code  )
   loop
htp.p('state:'||x.state_name);
end loop;
end x;

V$SQLTEXT had multiple entries for
 ( select * from states where state_code = v_state_code  )

V$SQL and V$SQLAREA had entries for the full "declare..end;"

(Yes I know it uses a loop when I didn't need to, but for the purposes of the
exercise...)

Thanks mate.

          Followup:

1) this is the way to use binds in implicit sql yes, works the same in explicit
sql.

2) for x in ( select blah from states where state_code = p_state_code )
   loop
      ....
   end loop;

or

   for x in c_states(p_state_code)
   loop
      ...
   end loop

either or

That IS an implicit cursor....

Look at other columns in v$sql -- see what is different.

      Rating: 5
Bind
      Variables  May 23, 2003
Reviewer:  Tarek

I have created this procedure (my first procedure!!)

CREATE OR REPLACE PROCEDURE getPersonData(
  cif   IN   VARCHAR2,
  fname  OUT   VARCHAR2,
  lname  OUT   VARCHAR2
)
IS
BEGIN
SELECT firstname,lastname
   INTO fname, lname
 FROM person
 WHERE personid = cif;
EXCEPTION
 WHEN NO_DATA_FOUND THEN
  DBMS_OUTPUT.PUT_LINE('CIF '|| cif ||'does not exist!');
END;

I'm using bind variables and try to see benefits.
But when i'm running it it's slower than running a select not using bind
variables.

SQL> exec getpersondata('0001360744',:fname,:lname);

PL/SQL procedure successfully completed.

LNAME
--------------------------------
Ghaddar

FNAME
--------------------------------
Tarek

Elapsed: 00:00:03.15
SQL> select firstname,lastname from person where personid='0001360744';

FIRSTNAME
----------------------------------------
LASTNAME
----------------------------------------
Tarek
Ghaddar

Elapsed: 00:00:02.92

Elapsed: 00:00:00.04
SQL> exec getpersondata('0002000000',:fname,:lname);
Il CIF 0002000000 non esiste!

PL/SQL procedure successfully completed.

LNAME
--------------------------------

FNAME
--------------------------------

Elapsed: 00:00:03.14
SQL> select firstname,lastname from person where personid='0002000000';

no rows selected

Elapsed: 00:00:02.93

Why it's slower?
I also queried v$sqlarea to see what do i have in shared pool and see if my
procedure is always in memory but i've seen i have different statements:
BEGIN getPersonData('0001360744',:fname,:lname); END;
BEGIN getPersonData('0002000000',:fname,:lname); END;
Why? This is what i run from sqlplus:
variable fname varchar2(20)
variable lname varchar2(20)
set serveroutput on
exec getPersonData('0001360744',:fname,:lname);
exec getPersonData('0002000000',:fname,:lname);
As far as i know using bind variables in procedures will improve performance
because statement have to be parsed only once. I thought i should see one record
in sqlarea for the procedure and not one for every value i pass. In this way i'm
not using bind variables so not having a benefit from running the procedure
instead of running sql statement?!

          Followup:

I see no real difference here.   use sql_trace and tkprof -- you'll see.  They
are in effect the same.

I can say however that 3 seconds for a simple keyed read like this implies
something is seriously wrong with your system -- that is a couple HUNDRED times
slower then it should be.

      Rating: 5
One Simple
      Question...  August 09, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Lahore,Pakistan
Hello Tom !

Consider this:

cr@STARR.LHR> ed
Wrote file afiedt.buf

  1* select * from v$version
  2  /

BANNER
================================================================
Oracle9i Enterprise Edition Release 9.0.1.1.1 - Production
PL/SQL Release 9.0.1.1.1 - Production
CORE    9.0.1.1.1       Production
TNS for 32-bit Windows: Version 9.0.1.1.0 - Production
NLSRTL Version 9.0.1.1.1 - Production

  1  CREATE OR REPLACE  PROCEDURE "REFUND"."REPLICATION_SB"  (clno in number)
  2  AS
  3  begin
  4  insert into cr.claimant_sb_master@starr.lhr(st_Reg_no)
  5  select st_reg_no from refund.claimant_sb_master
  6  where claim_no=clno;
  7* end;
cr@STARR.LHR> /

Procedure created.

Elapsed: 00:00:02.59
cr@STARR.LHR> execute replication_sb('324234');
BEGIN replication_sb('324234'); END;

*
ERROR at line 1:
ORA-01008: not all variables bound
ORA-02063: preceding line from STARR.LHR
ORA-06512: at "REFUND.REPLICATION_SB", line 4
ORA-06512: at line 1

Is there something wrong in above (simple) procedure ???

Riaz

          Followup:

looks like a product issue, time to file a tar with support.

      Rating: 5
why execute
      count is more than parse count? Thanks.  August 10, 2003

Reviewer:  Reader
Tom, please see below. I tested this in 9i R2.

SQL> select name,sleeps from v$latch where name = 'shared pool';

NAME                                                                 SLEEPS
---------------------------------------------------------------- ----------
shared pool                                                               0

SQL> SELECT pa.sid, pa.value "Hard Parses", ex.value "Execute Count"
  FROM v$sesstat pa, v$sesstat ex
 WHERE pa.sid=ex.sid
   AND pa.statistic#=(select statistic#
       FROM v$statname where name='parse count (hard)')
   AND ex.statistic#=(select statistic#
       FROM v$statname where name='execute count')
   AND pa.value>0;   2    3    4    5    6    7    8

       SID Hard Parses Execute Count
---------- ----------- -------------
         5          29            87
         6           1             8
         7          23           220
         8           3            98

SQL> set echo on

alter system flush shared_pool;

set timing on
declare
    type rc is ref cursor;
    l_rc rc;
    l_dummy all_objects.object_name%type;
    l_start number default dbms_utility.get_time;
begin
    for i in 1 .. 5000
    loop
        open l_rc for
        'select object_name
           from all_objects
          where object_id = ' || i;
        fetch l_rc into l_dummy;
        close l_rc;
    end loop;
    dbms_output.put_line
    ( round( (dbms_utility.get_time-l_start)/100, 2 ) ||
      ' seconds...' );
end;
/SQL> SQL>
System altered.

SQL> SQL> SQL>   2    3    4    5    6    7    8    9   10   11   12   13   14

PL/SQL procedure successfully completed.

Elapsed: 00:03:36.51

SQL> select name,sleeps from v$latch where name = 'shared pool';

NAME                                                                 SLEEPS
---------------------------------------------------------------- ----------
shared pool                                                               5

SQL> SELECT pa.sid, pa.value "Hard Parses", ex.value "Execute Count"
  FROM v$sesstat pa, v$sesstat ex
 WHERE pa.sid=ex.sid
   AND pa.statistic#=(select statistic#
       FROM v$statname where name='parse count (hard)')
   AND ex.statistic#=(select statistic#
       FROM v$statname where name='execute count')
   AND pa.value>0;   2    3    4    5    6    7    8

       SID Hard Parses Execute Count
---------- ----------- -------------
         5          39           107
         6           1             8
         7          94           600
         8        5065         10577  <-------------------------- Why it is more
than 10000? I was expecting around 5000

          Followup:

recursive sql.

look at your parse count (total), you'll see about 10k of them.

      Rating:
      5
Recursive?  August 11, 2003
Reviewer:
      Reader
In the above test, there will be 5000 unique statements and each of them will be
parsed again and again, so, it will be 5000 hard parses. Each staement will be
executed once after the hard parse, so 5000 executions. Where comes the
recursive sql in this case? Here parse 5000 and execute 5000 because the bind
variable is not used. I am still not getting this 10000 executions. Thanks for
the clarification. I appreciate your time.

          Followup:

sql_trace it, you'll see what recursive sql is done.

You'll track it down to:

select text
from
 view$ where rowid=:1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse     5000      0.56       0.53          0          0          0           0
Execute   5000      0.46       0.99          0          0          0           0
Fetch     5000      0.56       0.81          0      15000          0        5000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    15000      1.59       2.34          0      15000          0        5000

a recursive sql statment that is executed every time you parse against a view,
which all_objects is.

August 11, 2003 Om -- Thanks for the question regarding "move
      system datafiles", version 8.1.7

      You Asked
Hi Tom,

When we install oracle and create the database by default (not manually) ...the
system datafiles are located at a specific
location ..

Is is possible to move these (system tablespace datafiles) datafiles
from the original location to some new location ?

If yes how ?

Best regards,
Om

      and we said...
Yes, you

alter database backup controlfile to trace

shutdown

move the files

edit the generated CREATE CONTROLFILE statement (found in the user dump
destination) and change the filenames

use the generated script you just edited to startup the database.

Review &
      Followup

      Rating: 5
system
      tablespace datafiles  August 16, 2002
Reviewer:
      Om  from India
very useful for me as I never knew it's possible to move
system datafiles.

      Rating:
      3
  August 16, 2002
Reviewer:
      A reader
What is the difference between recreating a controlfile like that and updating
an existing controlfile with 'alter database rename...'? I read in recovery
manual that when using a re-created controlfile, the recovery operations should
use 'using backup controlfile'. That means there is something which
differentiates the re-created controlfile from a normal controlfile. Can you
explain that?

Also, even if I don't have the 'Create controlfile..' script, assuming that I
know the location of all the files and other parameters like maxdatafiles etc,
can I not create that script manually and run it to re-create the controlfile?
My question is, if the controlfile is easily recreatable like that, why do
people put somuch importance to it and multiplex it 5 times? Thanks for your
patience. I would appreciate if you can explain this to me..

          Followup:

Well, in order to do the rename, you need to offline the tablespace.  The one
tablespace you cannot offline is... SYSTEM.  Hence, this method will work (the
create controlfile) whereas for this specific case, the alter database would not

The backup controlfile is with a binary controlfile.  You have to use "using
backup" since the binary controlfile would have different "state of the
database" information in it (different scns and such) which would prematurely
stop the recovery process (you need to tell us "ignore that data in there,
recover until we run out of redo to apply, not when the control file says to
stop")

Yes, you can create the create control file statment yourself (obviously -- it
is just a script after all).

Why do we say multiplex it?  Can you spell downtime.... That file is the
bootstrap for us -- it, along with the parameter file, are two files that if you
don't have you won't be starting.  Do you want to sit there and say -- hmmm,
lets see, what files did I have, where are they, etc etc etc.  Or during a
recovery, do you want to hand us archive by archive to recover OR do you want us
to just recover by ourselves?

      Rating:
      3
  August 16, 2002
Reviewer:
      A reader
Thanks, but I still have some doubts:

1. We can use the 'alter database rename ' at mount stage to rename any
datafile. Or is it not possible to rename the system datafile like this? Why?

2. What happens to the SCN information in the controlfile when a controlfile is
recreated? How will the database sync the SCN with that of the datafiles?

If I issue a 'backup controlfile to <file>' at 8 am and then restore that
controlfile binary backup at 10 am and try to open the database, it will give me
a control file old error. I understand that it is because the SCN is not in
sync. But if I issue a 'backup controlfile to trace' at 8 am and use that script
to recreate a new controlfile at 10 am, why doesn't I get the error? Where does
it get the SCN information then?

So what is the use of taking a binary copy of the controlfile. Looks like having
a 'backup controlfile to trace' script is better than a binary backup. Do you
agree? Why/whynot?

Thanks again for your time.

          Followup:

1) you could but I just always did it with the create controlfile statement.

When moving system -- I do it that way
When moving ANY OTHER tablespace -- i just offline it, move the files, rename
the files online it.

2) it just happens.

The control file you create will read the files to figure out what is up.

I agree, I've never used a binary controlfile backup myself.

      Rating: 4
Relocating a
      non-system tablespace  November 13, 2002
Reviewer:
      A reader
"When moving ANY OTHER tablespace -- i just offline it, move the files, rename
the files online it".

Hi Tom, Could you please elaborate on your above statement? I have to relocate a
tablespace from a cooked datafile to a raw datafile say, from
/oradata/bigtbsp01.dbf to /dev/rdsk/bigtbsp01. How would I do that?

Although i know tha above isn't gonna help me much, i am finding your claim to
be true that raw doesn't give much benefit over cooked because I have already
moved redo logs (and undo tbsp) to raw without seeing any difference i.e. "log
file parallel write" is still our top wait event. But i'm under pressure to move
data/index to raw as well.

Thanks much.

          Followup:

offline the tablespace.
move the datafiles (how you do this depends on your OS and all)
rename the datafiles
online the tablespace.

In hind site what I might do here would be:

create new tablespace
MOVE the objects into it (clean em up), alter table move, alter index rebuild
less chance of messing up....

I wouldn't do it if your waits are not related to file writes -- just a waste of
your time and energy.

make sure to have FULL AND COMPLETE and tested (emphasis on TESTED) backups
before you do this.

also -- look to make sure the logs are on dedicated devices, that you have
enough (eg: you don't want ARCH reading the devices LGWR is writing and vice
versa -- you need like 5 devices -- arch reads 1/3 and writes 5 while lgwr
writes 2/4, lgwr writes 1/3 while arch reads 2/4 and writes 5 and so on.

Make sure your log buffer is sized right.

Make sure your applications are not "over committing" -- comitting before they
should (many people are under the 100% false impression that you want to commit
ASAP for performance -- quite the OPPOSITE is true, the more you commit the
slower you go and the more redo you generate)

      Rating: 4
How to recover
      the system tablespace?  November 28, 2002

Reviewer:  Frank  from Canada
Hi Tom,

If I lose my system tablespace, how can I recover it other than recover the
whole database? Couldn't find an example from the online doc.

Many thanks.

          Followup:

just restore the system datafiles from the last hot backup (or cold) and recover
the database.

it is no different then if you lost "USERS" (except that the database will be
down since system is sort of important to us)

      Rating: 4
Test of rename
      system datafile  February 20, 2003
Reviewer:
      Marcio  from Brazil
Tom,

I read that you always did --
<quote>
alter database backup controlfile to trace
shutdown
move the files
edit the generated CREATE CONTROLFILE statement (found in the user dump
destination) and change the filenames
</quote>
I'd like to know if this test have any fails:

D:\ORADATA\MRP816>dir
 Volume in drive D has no label.
 Volume Serial Number is 6CC4-BCAF

 Directory of D:\ORADATA\MRP816

07/02/2003  15:08       <DIR>          .
07/02/2003  15:08       <DIR>          ..
20/02/2003  08:19          113.254.400 USERS01.DBF
20/02/2003  08:19           60.825.600 INDX01.DBF
20/02/2003  08:19          164.896.768 SYSTEM01.DBF <----
20/02/2003  08:19           75.505.664 TEMP01.DBF
20/02/2003  08:19           12.591.104 TOOLS01.DBF
20/02/2003  08:19          209.723.392 RBS01.DBF
20/02/2003  08:20            3.661.824 CONTROL01.CTL
20/02/2003  08:20            3.661.824 CONTROL02.CTL
20/02/2003  08:20            3.661.824 CONTROL03.CTL
19/02/2003  08:59            1.049.088 REDO01.Log
20/02/2003  08:19            1.049.088 REDO02.Log
19/02/2003  08:59            1.049.088 REDO03.Log
14/02/2003  19:28            1.056.768 T01.Dbf

D:\ORADATA\MRP816>ren system01.dbf system.dbf
D:\ORADATA\MRP816>exit

ops$mportes@MRP816> startup mount
ORACLE instance started.

Total System Global Area   94360844 bytes
Fixed Size                    70924 bytes
Variable Size              77434880 bytes
Database Buffers           16777216 bytes
Redo Buffers                  77824 bytes
Database mounted.
ops$mportes@MRP816> select rpad(name, 50) from v$datafile;

RPAD(NAME,50)
--------------------------------------------------
D:\ORADATA\MRP816\SYSTEM01.DBF
D:\ORADATA\MRP816\RBS01.DBF
D:\ORADATA\MRP816\USERS01.DBF
D:\ORADATA\MRP816\TEMP01.DBF
D:\ORADATA\MRP816\TOOLS01.DBF
D:\ORADATA\MRP816\INDX01.DBF
D:\ORADATA\SRE\DF_SRE01.DBF

7 rows selected.

ops$mportes@MRP816> alter database rename file 'D:\ORADATA\MRP816\SYSTEM01.DBF'
to 'D:\ORADATA\MRP816\SYSTEM.DBF';

Database altered.

ops$mportes@MRP816> alter database open;

Database altered.

ops$mportes@MRP816> @conn /
ops$mportes@MRP816>
ops$mportes@MRP816>
ops$mportes@MRP816>
ops$mportes@MRP816> select rpad(name, 50) from v$datafile;

RPAD(NAME,50)
--------------------------------------------------
D:\ORADATA\MRP816\SYSTEM.DBF
D:\ORADATA\MRP816\RBS01.DBF
D:\ORADATA\MRP816\USERS01.DBF
D:\ORADATA\MRP816\TEMP01.DBF
D:\ORADATA\MRP816\TOOLS01.DBF
D:\ORADATA\MRP816\INDX01.DBF
D:\ORADATA\SRE\DF_SRE01.DBF

7 rows selected.

ops$mportes@MRP816> connect / as sysdba
Connected.
ops$mportes@MRP816> shutdown immediate;
Database closed.
Database dismounted.
ORACLE instance shut down.

[Here, I put in down the NT service and get up again] I did that to see any
problems

ops$mportes@MRP816> exit
Disconnected from Oracle8i Enterprise Edition Release 8.1.6.0.0 - Production
JServer Release 8.1.6.0.0 - Production

C:\tmp\sql>sqlplus /nolog

SQL*Plus: Release 8.1.6.0.0 - Production on Thu Feb 20 08:28:53 2003

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

idle> connect internal/oracle
Connected to an idle instance.
idle>
idle>
idle> startup
ORACLE instance started.

Total System Global Area   94360844 bytes
Fixed Size                    70924 bytes
Variable Size              77434880 bytes
Database Buffers           16777216 bytes
Redo Buffers                  77824 bytes
Database mounted.
Database opened.
idle> @conn /
ops$mportes@MRP816>
ops$mportes@MRP816>

It's seem to me OK. Have you seem any problem with this method?

          Followup:

that works as well.

      Rating: 3
But can we move
      datafiles from one d/b to another  June 26, 2003

Reviewer:  A reader
Hi Tom:

On the discussion of moving datafiles. Is it possible to move datafiles
belonging to a tablespace from one d/b to a new tablespace in another d/b and
point to these datafiles that we copied ?

I know there I can use the TTS option of exp/imp. But other than that is there
another way ?

Thanks

          Followup:

TTS is the only way to take part of DB1 and attach it to DB3

      Rating: 5
rename system
      datafile at db mount is better than re-create
      controlfile  June 26, 2003
Reviewer:
      A reader
I always like to use the method mentioned by Marcio from Brazil, when
renaming/moving system datafile. A potential problem with Tom's re-create
controlfile method is that it will wipe out any rman backup information stored
in the controlfile, won't it?

          Followup:

rman information is typically stored in a rman repository isn't it.

      Rating: 5
it's still
      better to rename system datafile after mount db   June 26,
      2003
Reviewer:  A reader
Rman catalog is NOT typically used, in my experience. In 9i, it's even easier to
simply use controlfile as the "default" rman repository.

No matter what -

shutdown db -> move system dbf -> startup mount -> rename system dbf -> open db

is better than

backup controlfile to trace -> shutdown db -> move system dbf -> edit
controlfile trace backup -> start db with it

The latter process is more error prone, and before using that edited controlfile
trace backup, you need to remove the old controlfile, unless you also edit the
initialization parameter file (init.ora) to have a different controlfile
location specified.

          Followup:

better be in the eye of the beholder and "not typically" used it "not typically
my experience"

No matter what is an overstatement, like saying "never", or "always".

both ways work
both ways are correct
both ways are valid

what about this -- you have 500 datafiles.
they were all in /d01/...., /d02/...., /d03/..... and for whatever reason you
have to move them to /u01/..., /u02/...., /u03/.....

Now, all of a sudden that control file trick with the ability to

:1,$s/\/d0/\/u0/

in vi (or editor of choice) looks more "appealing" maybe

everyone -- please use the method that pleases you the most.

      Rating: 5
if I had 500
      dbf to rename, even more appealing to do it "inside the
      database"  June 26, 2003
Reviewer:
      A reader
To rename/move 500 datafiles from /d0 to /u0, how about:

(before shutdown and move datafiles)

spool rename.sql
select 'alter database rename file '''|| file_name ||''' to '''/u0/' ||
substr(file_name, 5) ||''';' from dba_data_files;
spool off

Then shutdown, move datafiles, startup mount, run rename.sql, alter database
open.

No editing, no messing around with controlfiles, no loss of rman repository (if
it's in the controlfile :-).

      Rating: 4
what if in UNIX
        August 11, 2003
Reviewer:  a reader
       from MD, USA
Hi Tom,

what if to rename system datafile in unix environment? I think the 2 ways
described above shoulg work without rebuilding whole database. However our
production DBA said the Oracle database has to be rebuilt, otherwise the file
links get messed up. Is it a true statement?

          Followup:

they are wrong.

the above two methods both work dandy, do it all of the time.

August 11, 2003 Prakash -- Thanks for the question regarding "Use
      Of DBMS_SQL in D2K", version 2.1

      You Asked
Hi TOM,
This is in continum to my previous Question.
OK Dynamic Qurying is Not Possible For ORACLE 8.0.3/D2K(2.1)
But as an alternative I've Created a Package on Database as
Follows.

package emppkg is
type emprec is record (EMPNO  NUMBER(4),
                       ENAME  VARCHAR2(10),
                       JOB  VARCHAR2(9),
                       MGR  NUMBER(4),
                       HIREDATE  DATE,
                       SAL  NUMBER(7,2),
                       COMM   NUMBER(7,2),
                        DEPTNO  NUMBER(2)
                        );
type emptab is table of emprec index by binary_integer;
empcount number := 0;
emprecord emprec;
emptable  emptab;
procedure fill_emptab;
end;

package body emppkg is
procedure fill_emptab is
  c integer;
  rows number;
begin
c := dbms_sql.open_cursor;
  dbms_sql.parse(c,'Select * from emp',1);
  dbms_sql.define_column(c,1,emprecord.empno);
  dbms_sql.define_column(c,2,emprecord.ename,100);
  dbms_sql.define_column(c,3,emprecord.job,100);
  dbms_sql.define_column(c,4,emprecord.mgr);
  dbms_sql.define_column(c,5,emprecord.HIREDATE);
  dbms_sql.define_column(c,6,emprecord.SAL);
  dbms_sql.define_column(c,7,emprecord.COMM);
  dbms_sql.define_column(c,8,emprecord.DEPTNO);
  rows := dbms_sql.execute(c);
  empcount := 0;
  while dbms_sql.fetch_rows(c) > 0 loop
    empcount := empcount + 1;
    dbms_sql.column_value(c,1,emptable(empcount).empno);
    dbms_sql.column_value(c,2,emptable(empcount).ename);
    dbms_sql.column_value(c,3,emptable(empcount).job);
    dbms_sql.column_value(c,4,emptable(empcount).mgr);
    dbms_sql.column_value(c,5,emptable(empcount).HIREDATE);
    dbms_sql.column_value(c,6,emptable(empcount).SAL);
    dbms_sql.column_value(c,7,emptable(empcount).COMM);
    dbms_sql.column_value(c,8,emptable(empcount).DEPTNO);
  end loop;
  dbms_sql.close_cursor(c);
exception
  when others then
   dbms_sql.close_cursor(c);
end fill_emptab;
end ;

Now From forms(5.0) I'm Trying to Access this Package with
Following Command
declare
cnt Number;
begin
  emppkg.fill_emptab;
  cnt := emppkg.empcount ;
 ...
 ...
end;

While Compiling above statement I'm getting following error
Implementation Restriction : 'EMPPKG.EMPCOUNT' Cannot directly
access remote Package Variable or Cursor;

Can you Give me some Cause n Solution, Please.

Prakash.

      and we said...
Well, in order to fix the above you would simply write a
FUNCTION "get_empcount" that returned empcount.  As the error
message states -- you cannot directly access remote package
variables or cursors -- you must write a function (get/set
functions) to get and set the values in these remote variables.

But wait -- there is a much better, more efficient, easier
method to do what you are trying to do above.  Previously you
asked me how to dynamically open a ref cursor -- I told you you
cannot until you get Oracle8i, release 8.1 and up.  Now I see
you want to get a table of records -- we can do that in forms in
a much easier way.  We'll use a RECORD_GROUP.  It would look
like this:

DECLARE
  rg_name  VARCHAR2(40) := 'EmpDemo';
  rg_id    RecordGroup;
  errcode  NUMBER;
BEGIN
  /*
  ** Make sure group doesn't already exist
  */
  rg_id := Find_Group( rg_name );

  /*
  ** If it does not exist, create it
  */
  IF Id_Null(rg_id) THEN
    rg_id := Create_Group_From_Query( rg_name,
                                     'SELECT * from emp' );
  END IF;
  /*
  ** Populate the record group
  */
  errcode := Populate_Group( rg_id );

  Message( 'There are ' || get_group_row_count( rg_id ) ||
           ' rows in the result' );

  for i in 1 .. get_group_row_count( rg_id )
  loop
       Message( 'Row ' || i || ' has deptno = ' ||
                get_group_number_cell( 'EmpDemo.DEPTNO', i ) );
  end loop;

END;

Record Groups will be much more efficient then using DBMS_SQL.
Record groups will result in many less round trip calls to the
server and use array fetching to fill themselves up.  You can
create a record group from any arbitrary query.  They work much
like a 2 dimensional array with rows and columns, sort of like a
"result set" object.
Review & Followup

      Rating: 4
Thanks for the
      method  June 20, 2002
Reviewer:  Thomas
       from France
I am currently working in Forms 4.5
I am struggling with a ORA-01475 error in this code (cursor must be reparsed in
order to change bind variable's type, or something like this - I am translating
it from french...)

[...]
curseur := dbms_sql.open_cursor;
dbms_sql.parse(curseur, myquery, c_dbms_sql_lang);
DBMS_SQL.DEFINE_COLUMN(curseur, 1, l_code);
DBMS_SQL.DEFINE_COLUMN(curseur, 2, l_date); -- error raised at this point
nb_enregs := dbms_sql.execute(curseur);
[...]

The 2nd column of the query is a DATE, and the l_date is defined as a DATE. I
don't see where the problem is, but anyway...
I think I will use the record_group method, which seems to be faster and
efficient for what I need to do (just get records and put them in a block...)
Thanks for showing this example, very usefull !

      Rating: 4
dynamically
      creating record groups in forms with bind variables  August
      11, 2003
Reviewer:  Patrick  from Cincinnati, OH

Tom,
If I change your example slightly, and place the plsql in a procedure within the
form, can I reference a passed parameter and have it interpreted as a bind
variable? Here's the example:

PROCEDURE get_emp_in_dept (dept IN VARCHAR2) IS
DECLARE
  rg_name  VARCHAR2(40) := 'EmpDemo';
  rg_id    RecordGroup;
  errcode  NUMBER;
BEGIN
  /*
  ** Make sure group doesn't already exist
  */
  rg_id := Find_Group( rg_name );

  /*
  ** If it does not exist, create it
  */
  IF Id_Null(rg_id) THEN
    rg_id := Create_Group_From_Query( rg_name,
                                     'SELECT * from emp WHERE department = dept'
);
  END IF;
  /*
  ** Populate the record group
  */
  errcode := Populate_Group( rg_id );

  Message( 'There are ' || get_group_row_count( rg_id ) ||
           ' rows in the result' );

  for i in 1 .. get_group_row_count( rg_id )
  loop
       Message( 'Row ' || i || ' has deptno = ' ||
                get_group_number_cell( 'EmpDemo.DEPTNO', i ) );
  end loop;

END;

I can do it successfully if I copy dept into a global variable and/or a form
parameter and then reference them in the string using :GLOBAL.dept or
:PARAMETER.dept, but if I refer to the passed parameter directly, no luck. I
assume it just interprets dept as a string (d-e-p-t), implicitley converts it to
a character string, and searches for the equality.

Thanks for your help. Your insight on utilizing bind variables has really helped
us produce a very scalable, production-oriented system with 200+ users, and no
noticable performance issues.

          Followup:

if you want to use a bind -- you have to use a forms item like that.

your other alternative is to enable cursor_sharing=force and

'select * from emp where department = ' || dept

but using a forms item in a control block is the right way to go!

August 11, 2003 -- Thanks for the question regarding "snapshots",
      version 8.1.7

      You Asked
I want to setup snapshot between interval 8AM to 8PM it has to refresh every one
hour and also refresh at 12:15.
how to setup .

      and we said...
It will be easiest to schedule this as a DBMS_JOB yourself (eg: don't use the
interval on the snapshot -- use a call to dbms_mview.refresh( 'SNAPSHOT-NAME' );
yourself)

This is due to the relative complexity of your needs (the date computation).
Here is a procedure that will do that.  First I started with a query that tests
my "logic"

select case when (to_char(dt,'hh24') between 8 and 19)
            then case when ((to_char(dt,'hh24')<12) or (to_char(dt,'hh24mi') >=
'1215'))
                      then trunc(dt,'hh')+1/24
                      else trunc(dt)+12/24+15/24/60
                 end
            when (to_char(dt,'hh24') < 8)
            then trunc(dt)+8/24
            else trunc(dt)+1+8/24
       end,
       dt
  from (select trunc(sysdate)+7/24+rownum/24/60 dt from all_objects where rownum
< 14*60)
/

that returns output like:

CASEWHEN(TO_CHAR(DT, DT
-------------------- --------------------
07-nov-2002 08:00:00 07-nov-2002 07:01:00
07-nov-2002 08:00:00 07-nov-2002 07:02:00
07-nov-2002 08:00:00 07-nov-2002 07:03:00
07-nov-2002 08:00:00 07-nov-2002 07:04:00
....
07-nov-2002 12:00:00 07-nov-2002 11:58:00
07-nov-2002 12:00:00 07-nov-2002 11:59:00
07-nov-2002 12:15:00 07-nov-2002 12:00:00
07-nov-2002 12:15:00 07-nov-2002 12:01:00
07-nov-2002 12:15:00 07-nov-2002 12:02:00
07-nov-2002 12:15:00 07-nov-2002 12:03:00
07-nov-2002 12:15:00 07-nov-2002 12:04:00
07-nov-2002 12:15:00 07-nov-2002 12:05:00
07-nov-2002 12:15:00 07-nov-2002 12:06:00
07-nov-2002 12:15:00 07-nov-2002 12:07:00
07-nov-2002 12:15:00 07-nov-2002 12:08:00
07-nov-2002 12:15:00 07-nov-2002 12:09:00
07-nov-2002 12:15:00 07-nov-2002 12:10:00
07-nov-2002 12:15:00 07-nov-2002 12:11:00
07-nov-2002 12:15:00 07-nov-2002 12:12:00
07-nov-2002 12:15:00 07-nov-2002 12:13:00
07-nov-2002 12:15:00 07-nov-2002 12:14:00
07-nov-2002 13:00:00 07-nov-2002 12:15:00
07-nov-2002 13:00:00 07-nov-2002 12:16:00
...
08-nov-2002 08:00:00 07-nov-2002 20:55:00
08-nov-2002 08:00:00 07-nov-2002 20:56:00
08-nov-2002 08:00:00 07-nov-2002 20:57:00
08-nov-2002 08:00:00 07-nov-2002 20:58:00
08-nov-2002 08:00:00 07-nov-2002 20:59:00

839 rows selected.

showing I get the right "next date" given any input hour/minute time (eg: if
computed before 8am on the target date -- return 8am on that date.  Return an
hour later between 8am and 8pm unless it is between 12 and 12:15 then return
12:15.  After 8pm, return 8am the next day...

Then, we create a procedure like this:
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace procedure refresh_my_snapshot(
NEXT_DATE in out date )
  2  as
  3  begin
  4          dbms_mview.refresh( 'SS_NAME' );
  5      execute immediate '
  6          select case when (to_char(sysdate,''hh24'') between 8 and 19)
  7              then case when ((to_char(sysdate,''hh24'')<12) or
(to_char(sysdate,''hh24mi'') >= ''1215''))
  8                                    then trunc(sysdate,''hh'')+1/24
  9                                            else
trunc(sysdate)+12/24+15/24/60
 10                                   end
 11                          when (to_char(sysdate,''hh24'') < 8)
 12                          then trunc(sysdate)+8/24
 13                          else trunc(sysdate)+1+8/24
 14                  end
 15         from dual' into NEXT_DATE;
 16  end;
 17  /

Procedure created.

ops$tkyte@ORA920.US.ORACLE.COM> drop snapshot ss_name;

Materialized view dropped.

ops$tkyte@ORA920.US.ORACLE.COM> create snapshot ss_name
  2  as select * from scott.emp;

Materialized view created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> variable n number
ops$tkyte@ORA920.US.ORACLE.COM> exec dbms_job.submit( :n, 'refresh_my_snapshot(
NEXT_DATE );' )

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> commit;

Commit complete.

ops$tkyte@ORA920.US.ORACLE.COM> select next_date from user_jobs where job = :n;

NEXT_DATE
--------------------
07-nov-2002 08:44:57

ops$tkyte@ORA920.US.ORACLE.COM> exec dbms_job.run( :n )

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> select next_date from user_jobs where job = :n;

NEXT_DATE
--------------------
07-nov-2002 09:00:00

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

And that shows the job gets scheduled and even though there is no INTERVAL -- it
stays in the queue to run again and again....

Review &
      Followup

      Rating: 5
What would
      happen to the application?  December 09, 2002

Reviewer:  Sujata  from Germany
Hi Tom,
You have a very good site.
I have a question regarding this kind of refresh (every hour or two hours) of a
snapshot.
We are using Oracle 8.1.7.4.0 version.
We have a snapshot that we want to refresh every two hours.
There is a local table that has the primary key of the snapshot table..and some
other columns.
We will populate initially the local table with all the snapshot records.
Then we want to keep this local table equivalent to the snapshot table..meaning
during every refresh if there is a new workerid added..then that worker id will
be added to the local table..and if a worker is no longer with the firm..then
that worker will be taken out of the local table also.
How can this be done...or can it be done?
Since snapshot is getting refreshed very frequently..instead of once a
night..Won't the web page that is accessing this snapshot table will be unstable
during these refreshes?
Please give some examples.

Thank You very much

          Followup:

Instead of letting the snapshot refresh by itself, I might schedule a dbms_job
to refresh it so I could "piggy back" on that operation and do some work myself.

So, you could write a procedure:

procedure do_the_refresh
as
begin
   dbms_snapshot.refresh( 'that snapshot' );
   delete from my_local_table where primary_key_col
     NOT IN ( select primary_key_col from that_snapshot );
   insert into my_local_table ( primary_key_col )
   select primary_key_col from that_snapshot
     where primary_key_col not in ( select primary_key_col from that_table );
   commit;
end;
/

then, schedule that procedure to execute every two hours.  see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5081797376327
for some tips on that NOT IN i'm using there.

During the refresh -- there will be a brief period in time whereby the
"my_local_table" may have rows that have primary keys that were "removed" and
not have some that were "added" -- but only briefly -- during the insert/delete.

      Rating:
      5
dbms_snapshot.refresh( 'that snapshot' );  December
      10, 2002
Reviewer:  Sujata  from Germany
Hi Tom,
Thank You so much for your reply.
With the above statement, won't the application page using the snapshot table
goes unstable..since all the data in the snapshot table will be deleted and then
reloaded..so the data will all of a sudden goes away for few seconds.

Thanks..

          Followup:

if it is a FULL refresh use a snapshot group to refresh with instead -- it'll
use delete+insert and the data will stay.

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1389964969205

if it is a changes only refresh, an incremental refresh, it won't truncate the
table and you need do nothing special.

      Rating: 3
Problem on
      snapshots   June 13, 2003
Reviewer:  Tanmoy
      Datta Choudhury  from bangalore,India
hi,
we have an environment of 217 sites connected to a master site .all the 217
sites have their own oracle servers.the total system breaks into 3 levels ...

         level 1 - master server (config-oracle 8.1.6 enterprize edition
                                  on sun sparc e-450 series,
                                  O/s - solaris 8 )
         level 2 - oracle server (oracle 8.1.6 standard edition
                                  on IBM e server -220X
                                  O/S - sco unix - 7.1.1)
         level 3 - oracle server (oracle 8.1.6 standard edition
                                  on IBM net vista
                                  O/s - win nt 2000 server)

network - V-SAT

our GUI is developed on FORMS and REPORTS 6i (its a treasury management
system) and in general all the transaction are local ..for some activities
the data is commin to level 1 from level 2 and 3 for verification .some
activities are there between level 2 and 3(very less generally at the month end)

we have snapshot in all the sites ..and we created our snapshot like this ..

 CREATE SNAPSHOT  <table_name>

 REFRESH FAST START WITH SYSDATE NEXT SYSDATE + 1 WITH PRIMARY KEY
 AS SELECT * FROM  <user_name.table_name>@<dblink>;

we are facing problem in snapshot refreshing ....generally out of 217 sites
around
25 sites snapshot refresh is giving problem .exploring alert log giving us the
following
error --

   *** 2002-07-03 15:54:32.059
ORA-12012: error on auto execute of job 7
ORA-04052: error occurred when looking up remote object KHAJANE.SYS@T0000.

for ora-04052 the oracle error document are giving the following suggesstion

<oracle error message document >

for ORA-04052 error occurred when looking up remote object
stringstringstringstringstring

Cause: An error has occurred when trying to look up a remote object.

Action: Fix the error. Make sure the remote database system has run KGLR.SQL to
create necessary views used for querying/looking up objects stored in the
database.

<end>

we are unable to find the file KGLR.sql ...
can you tell us more about this file and how to correct the problem

we got ORA-00604 error for some sites and as per oracle documentation

<oracle error message document >

ORA-00604 error occurred at recursive SQL level string

Cause: An error occurred while processing a recursive SQL statement (a statement
applying to internal dictionary tables).

Action: If the situation described in the next error on the stack can be
corrected, do so; otherwise contact Oracle Customer Support.
<end>

and sometime ORA-12012: error on auto execute of job 10

but for all the above cases if we connect to the remote site and refresh
manually its
working ..

any suggesstions ..

          Followup:

so, when you contacted support and opened a tar for something like this, what
did they say?

      Rating: 2
snapshot prob
        June 14, 2003
Reviewer:  Tanmoy Datta
      Choudhury  from Bangalore,India
no we haven't contacted with oracle support till now ...i thought to get some
suggesstion from you before getting in touch with support ..just i want to
ensure from our part evrything is ok ....

      Rating: 4
difference
      between snapshot and MV  August 11, 2003
Reviewer:
      Reader
Tom, can i create materialized view in a database that is not setup for
replication. My understanding is that in versions prior to 8i, to create
snapshots, I have to run catrep.sql and configure the database for replication
env. From 8i onwards, to support MVs, does oracle run catrep.sql when I create
the database itself or MV is based on different concept than snapshots in
earlier versions. Thanks.

          Followup:

snapshots support a subset of the mv functionality.  for all intents and
purposes -- they are effectively "the same"

      Rating: 5
Does catrep.sql
      have to be run when database is created to create MVs?
      Thanks.  August 11, 2003
Reviewer:  Reader

          Followup:

standard install provides support out of the box.

catproc runs all that is needed.

August 11, 2003 Paul -- Thanks for the question regarding "BULK
      COLLECT INTO OBJECT?", version 9.2

      You Asked
Hi Tom;

  Can I bulk collect into an object type from plain varchar columns?  Here's
some type definition:

CREATE OR REPLACE TYPE UCF_ETGENTRY_TYPE AS OBJECT (
  ID    VARCHAR2(50),
 NAME    VARCHAR2(50)
  );

CREATE OR REPLACE TYPE UCF_GROUP_COLLECTION AS TABLE OF UCF_ETGENTRY_TYPE;

Can I do this (where out is a UCF_GROUP_COLLECTION)?

    SELECT name,group_id
        BULK COLLECT INTO out
        FROM ucf_group;

Apparently I cannot... is there a way I can do this ???

      and we said...
You can, but you have to select the scalar object type out -- that is what you
are bulk collecting!  an array of objects -- hence you need a single select list
item:

ops$tkyte@ORA920> create type myScalarType as object
  2  ( empno int,
  3    ename varchar2(20)
  4  )
  5  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create type myTableType as table of myScalarType
  2  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          l_data myTableType;
  3  begin
  4     select myScalarType( empno, ename )
  5       BULK COLLECT into l_data
  6           from emp;
  7  end;
  8  /

PL/SQL procedure successfully completed.
Review &
      Followup

      Rating: 5
what
      about  August 11, 2003
Reviewer:  Darren
       from London, UK
what about

select cast(multiset(select name, group_id
                       from emp) as myTableType)
  into l_data
  from dual;

is that more or less efficient?

Thanks!

p.s. on a tech note are bulk operations processed, by the sql engine, as one
operation or a for loop?

e.g.
forall idx in t_a.first..t_a.last
  insert into table
              (id)
       values (t_a(idx));

would the sql engine process this as one insert or would it do something like
for item in array
  insert row for item;
end loop;
and therefore the saving really comes from the reduction in context switches.

          Followup:

benchmark it and let us know (my guess -- no, it is not)

the forall is a bulk bind, like an array insert.  You'll see 1 execute of that
insert for each forall call, with many rows inserted in each. (instead of an
execute/row)

      Rating: 5
Tried
      it  August 12, 2003
Reviewer:  Darren
       from London, UK
SQL> set serveroutput on size 1000000
SQL> exec runstats_pkg.rs_start;

PL/SQL procedure successfully completed.

SQL> declare
  2    l_data myTableType;
  3  begin
  4    select myScalarType( id, id_code)
  5    BULK COLLECT into l_data
  6    from spg_order_lookup
  7    where rownum < 1001;
  8  end;
  9  /

PL/SQL procedure successfully completed.

SQL> exec runstats_pkg.rs_middle;

PL/SQL procedure successfully completed.

SQL> declare
  2    l_data myTableType;
  3  begin
  4    select cast(multiset(select id, id_code
  5                           from spg_order_lookup
  6                          where rownum < 1001) as myTableType)
  7      into l_data
  8      from dual;
  9  end;
 10  /

PL/SQL procedure successfully completed.

SQL> exec runstats_pkg.rs_stop(20);
Run1 ran in 27 hsecs
Run2 ran in 76 hsecs
run 1 ran in 35.53% of the time

Name                                Run1      Run2      Diff
LATCH.cache buffers chains            84       123        39
LATCH.shared pool                    106        53       -53
STAT...bytes received via SQL*       670       743        73
STAT...redo size                   1,424     1,560       136
STAT...session uga memory              0     2,836     2,836

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
443       412       -31 107.52%

PL/SQL procedure successfully completed.

SQL>

seems pretty close in terms of latches, thou the bulk collect was quicker on the
wall clock.

August 11, 2003 -- Thanks for the question regarding "O/S of the
      database?", version 8.1.7

      You Asked
Tom,
Wanted to know if there a way to find out the Operating system name/version on
which a given oracle database is running?

Thank you

      and we said...
ops$tkyte@ORA920> select dbms_utility.port_string from dual;

PORT_STRING
---------------------------------------------------------------------------------
--------------------------------------------------
Linuxi386/Linux-2.0.34-8.1.0
Review &
      Followup

      Rating: 5
O/S of the
      database  August 11, 2003
Reviewer:
      A reader
Thank you

      Rating: 4
useful
      tip  August 11, 2003
Reviewer:  andrea
       from Italy
Time ago I had to solved this issue... If I had known this I'd have saved lot of
time...

      Rating:
      5
  August 11, 2003
Reviewer:
      A reader
Hi TOM
I am getting

SQL> SELECT dbms_utility.port_string from dual;

PORT_STRING
--------------------------------------------------------------------------------
SVR4-be-8.1.0

Which operatig system we are using and what version?
Can you tell from output?

Thanks

          Followup:

scott@ORA9I> select dbms_utility.port_string from dual;

PORT_STRING
---------------------------------------------------------------------------------
--------------------------------------------------
SVR4-be-8.1.0

$ uname -a
SunOS aria 5.8 Generic_108528-15 sun4u sparc SUNW,Ultra-4

solaris, version "8"

      Rating:
      5
dbms_utility.port_string   August 11, 2003

Reviewer:  Dilip Patel  from Maryland, USA

Learned something new again. Thanks Tom.

      Rating: 5
what does that
      8.1.0 means  August 11, 2003
Reviewer:
      Kumar  from San Diego
Tom,

I did ran this command on Windows 2000 and 2003 server I got the same result as
IBMPC/WIN_NT-8.1.0.

Is there any specific way that we can know either it is WINDOWS 2000 or 2003 or
NT and XP.

What exactly meant for -8.1.0? is that referes to SQL*NET

          Followup:

ahh, must be the db version, not related to the OS, guess the os version is
sometimes there/sometimes not.

      Rating: 5
RE: What
      exactly meant for -8.1.0?  August 11, 2003

Reviewer:  Mark A. Williams  from Indianapolis, IN
      USA
> What exactly meant for -8.1.0? is that referes to SQL*NET

Oracle took some time to document these things.  From the Supplied PL/SQL
Packages documention on dbms_utility.get_port:

"This function returns a string that identifies the operating system and the TWO
TASK PROTOCOL version of the database."

- Mark

      Rating: 5
Edit my
      response  August 11, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Edit my response:

from the dbms_utility.port_string function, not get_port...

- Mark

August 12, 2003 Saibabu -- Thanks for the question regarding
      "Table access.", version 8.1.7.4

      You Asked
Hi Tom,

We have a requirement to get table names out of v$sql and number of sql
statements ran against each table group by command type. Our team is not in idea
of using auditing.

Is there a simple way to parse table names from a sql_text column in v$sql for a
heavily join sql statement.

I have tried to dig in to x$kglcursor and x$kgltable with no success. Can you
shed some light on x$kgltable, what is it used for and how can it be mapped to
any v$ view.

Thanks.

      and we said...
lets see, two approaches

approach 1:

enable auditing.  done, finished, totally completed.

approach 2:

write a SQL parser
maintain SQL parser as sql language changes
debug a big SQL parser
attempt to use undocumented tables that will change over time, breaking your
code

I don't know, but I would reconsider the "teams" idea of not using builtin
functionality personally.

Review &
      Followup

      Rating: 3
An alternate
      solution......  July 30, 2003
Reviewer:  Mark J
      Bobak  from Ann Arbor, MI
This query may help:
select cur.kglnaobj,ob.kglnaobj , ob.kglnaown from
x$kglcursor cur ,
x$kglob ob,
x$kgldp dep
where cur.kglnaobj like '%SOME_STRING_THAT_WILL_MATCH_YOUR_SQL%'
and cur.kglhdadr = dep.kglhdadr and
dep.kglrfhdl = ob.kglhdadr
/

      Rating: 4
How about
      columns accessed.  August 11, 2003
Reviewer:
      A reader
Either with auditing or with Mark's sql above, I can get tables accessed in each
sql statement.

Now, I was asked to get all the columns accessed in each sql statement along
with the table names. e.g.

sql_text, tables_accessed, column_vs_table_accessed...

Is it possible?

          Followup:

that is called fine grained auditing, available in Oracle9ir1 and up.

August 12, 2003 razvan -- Thanks for the question regarding "log
      sequence number - why is it in the datafile header", version 7.3.2

      You Asked
Hello tom,

Please can you explain me why "each datafile header contains the
most recent log sequence number being written at the time the
datafile was being written" ?
I belive that LSN is only proper for redo log and have no
connection with the datafile ...i but i think i've wrong

in generally the frequence with which the LGRW write to redo log
files is bigger then frequence with which the DBRW write to disk
the coresponding data dirty blocks because other else the blocks
in data files aren't protected by the redo log....it's right??

thanks a lot
 razvan

      and we said...
The redo log sequence number is written to the datafile header
so we know what redo logs would need be applied to this file for
media recovery.

I believe the exact quote is:

<quote>

After Disk X has been repaired, the most recent backup files are
used to restore only the damaged datafiles USERS1 and USERS2.
After restoration, the datafiles of the database exist as
illustrated in Figure 24 – 4.
..

Each datafile header contains the most recent log sequence
number being written at the time the datafile was being
written. The restored backup files will have earlier log
sequence numbers than those of the datafiles that were not
affected (not restored) by the disk crash. The control file
contains a pointer to the last log sequence number that was
written.
</quote>

so, since the datafile has the log sequence number of the first
log it needs to perform recovery and the control file has the
log sequence of the last log written, auto-recovery can prompt
for and apply all of the needed log files for the restored file.

LGWR writes to the redo log in all of the following cases:

o every three seconds
o when the log buffer is 1/3 full
o when you commit
o when the DBWR process writes modified buffers to disk

DBWR writes to the datafiles as it needs to -- to free buffers
in the shared pool or when directed to perform a checkpoint.
The frequency of writes between LGWR and DBWR is not
"comparable" with eachother in general -- they are different
mechanisms for different purposes.  LGWR writes data to protect
things, DBWR writes data for persistence.  If we modify just 1
row in 1 table 1,000,000 times (commiting after each update),
LGWR will generate a ton of log -- DBWR will have one block to
flush.  There is no really direct correlation between the
frequency of writes between the two (although it is generally
safe to say that if LGWR is writing lots, DBWR will be writing
lots as well).

Review &
      Followup

      Rating: 5
scns and log
      seuence  October 28, 2002
Reviewer:  atul
       from India
Sir,

As you said log sequence no is in data file header for recovery purpose..
Also SCN is recorded in datafile header..
So what is the use of SCN then???

Thanks.
atul

          Followup:

that is the internal oracle clock.  It tells us what "time" the stuff is current
as of.  We apply all redo since that "time" when recovering

      Rating:
      4
  October 31, 2002
Reviewer:  Lidia
      Berbenets  from Russia
Please tell us, can we obtain the most resent log sequence number that contains
in datafile header from some V$-views  or we must dump file headers to trace
file?
V$datafile_header , V$datafile do not contain such columns.
Thanks.

          Followup:

You could figure it out more or less from checkpoint_change# or last_change# and
v$archive

      Rating: 5
Running DB
      without datafile  April 26, 2003
Reviewer:
      Saminathan Seerangan  from CA,USA
Dear Tom,  Many thanks for responding to all Qs in short period of time.

How long the DB instance can be up and running after loosing one of the
unwanted(low priority) datafile?
(Also it is not part of the System/Temp/Undo tablespaces)

For example,tablespace name is TS1,corresponding datafile is "ts1_df_01.dbf"
and accidentally it has been removed during the peak hours. TS1 is default
tablespace of user "Least_Botherd_User".

Now i don't care about "Least_Botherd_User" users data and operation at this
point of time.

1)Can i continue to operate(all DBA activities) on my db instance for more than
a day without any problem?

2)Startup is the only time, the db will make sure the consistency?

3)My understanding is that the instance will go down automatically only if it is
not able to identify the sytem datafile. Am i correct?

Kindly request your comments.

          Followup:

the db can be up forever missing a datafile.  that data is unavailable but the
database is up (assuming it is not a file from system, rollback, controlfile,
log file)....

1) yes
2) no, anytime someone tries to access it.
3) it won't go down even then, it'll just be more or less dead in the water --
but still running.

      Rating: 5
Now DB is
      restarted without datafile & without recovery
      ?!?!?  April 26, 2003
Reviewer:  Saminathan
      Seerangan  from CA,USA
Dear Tom, This review is continuation of the previos one.

step(1) deleted /dba/u08/oradata/o817ut01/ts1_df_01.dbf.dbf
step(2) waited more than one hour db is functioning normally.
step(3) shutdown immediate(usning RMAN)
step(4) startup (Using RMAN)
    ==> Here i'm was expecting the DB will prompt me for recovery before
opening, But it did not.
    It open normally.

    Q1) How this is possible?

step(5) after db open, i execite the following query. TS1 status is
available.Look at step(6).

SQL>select file_name,status,tablespace_name from dba_data_files where
tablespace_name like '%TS%';

FILE_NAME                    STATUS    TABLESPACE_NAME
---------------------------------------------   --------- ---------------
/dba/u08/oradata/o817ut01/ts1_df_01.dbf.dbf    AVAILABLE TS1

step(6)
SQL> !ls /dba/u08/oradata/o817ut01/ts1_df_01.dbf.dbf
/dba/u08/oradata/o817ut01/ts1_df_01.dbf.dbf: No such file or directory

step(7) -- connecting to "Least_Botherd_User"
Connected to:
Oracle8i Enterprise Edition Release 8.1.7.1.0 - Production
With the Partitioning option
JServer Release 8.1.7.1.0 - Production

SQL> select * from after_level1_tab;
select * from after_level1_tab
              *
ERROR at line 1:
ORA-00376: file 4 cannot be read at this time
ORA-01110: data file 4: '/dba/u08/oradata/o817ut01/ts1_df_01.dbf'

what is going on? where am i loosing the track?

Tom, please clarify, TIA

          Followup:

in unix files are not erased really until everyone that had them opened, closes
them.  but in any case, i cannot reproduce

Sat Apr 26 08:46:50 2003
Errors in file /usr/oracle/ora920/admin/ora920/bdump/ora920_dbw0_22285.trc:
ORA-01157: cannot identify/lock data file 6 - see DBWR trace file
ORA-01110: data file 6: '/usr/oracle/ora920/oradata/ora920/indx01.dbf'
ORA-27037: unable to obtain file status
Linux Error: 2: No such file or directory
Additional information: 3
ORA-1157 signalled during: alter database open...

is what appears in my alert log under the same sequence of events.

      Rating: 5
CAN NOT READ
      HEADER  August 12, 2003
Reviewer:  atul
       from India
Hi,

I am under very confusing situation.
I'm running database (8.1.7)
My oracle is installed under ownership of userid "oracle"

when i login with unix id "TEST" and give oracle_sid,oracle_home,PATH variables
and then do sqlplus sys

after logging in when i give
"select file#,error from v$datafile_header;"

for some file# i get error as "CAN NOT READ HEADER"

but when i login through other unix id and do the same thing.
I'm not getting any error..

This seems very very confusing,
Could you tell me the reason behind this??

Thank & Regards,
Atul

          Followup:

sounds like you did not run the root.sh during the install and the permissions
on the oracle binaries are wrong.

what does ls -l $ORACLE_HOME/bin/oracle look like.  it should look like this:

$ ls -l $ORACLE_HOME/bin/oracle
-rwsr-s--x    1 ora920   ora920   51766646 Mar 31 13:03
/usr/oracle/ora920/bin/oracle

with the "s" bits set.

      Rating: 5
permissions are
      correct  August 26, 2003
Reviewer:  atul
       from India
Hi,

Answer to your question is:

what does ls -l $ORACLE_HOME/bin/oracle look like.  it should look like this:

-rwsr-s--x   1 oracle   dba      37601998 Mar 11 2002  oracle

How can i find out if root.sh has been ran or not??

Thanks.
Atul

August 12, 2003 John -- Thanks for the question regarding
      "PRODUCT_USER_PROFILE question", version

      You Asked
Can the PRODUCT_USER_PROFILE table be used to disable
individual commands for specific users for tools OTHER THAN SQL*Plus?

      and we said...
No, the product user profile table is only useful for SQL*plus.  The database
cannot enforce command level disabling on a per application process since the
database cannot reliably tell 'what' application is actually connecting.  Lets
say the database did implement the product user profile functionality instead of
sqlplus doing it.  If I was allowed to execute an INSERT into "program1" but not
allowed to execute "INSERT" in SQLPlus -- all I would have to do is copy
"sqlplusw.exe" to "program1.exe" and I would be able to insert in SQLPlus now
since the database thinks it is really program1 connecting.

What many people do to control what applications may perform what operations is
to use non-default, password protected roles.  Their applications, after loggin
in, enable the appropriate roles -- giving the user in this session the
appropriate privileges.

Review &
      Followup

      Rating:
      4
profile  August 12, 2003
Reviewer:
      benny  from China
Hi, tom. One question hope you can help me. I used the following statement to
limit sesion per user.
ALTER PROFILE "TEST
    FOR BENNY"
    LIMIT SESSIONS_PER_USER 2;

And I did assigned that profile to certain user. But I still can use that user
to log on more than 2 session by sql*plus . Can you help me?

          Followup:

did you set the requisite init.ora paramter for resource profiles to be enabled?

resource_limit                       boolean     FALSE

August 12, 2003 raj -- Thanks for the question regarding
      "Temporary tables", version 8.1.5

      You Asked
Hi Tom.
is there anyway i can create, do inserts,deletes and selects
from a temporary table, created at runtime and drop the table
automatically with the instance?

thanks in advance,
raj.

      and we said...
Mostly yes you can but no you should not.  (you'll be
responsible for dropping the table).

If you know the structure of the table at compile time -- It
will be 100% easier, better, and more efficient to use the
global temporary table.  They are the correct approach in
Oracle.

If you want to have a "table per person", it'll cost you (DDL is
not cheap) and it'll be messy.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:48812348054
for my comparision of SQLServer temp tables with ours and how to
dynamically create them.  The caveat about DDL committing
applies but with autonomous transactions you can avoid that as
well.  Bear in mind -- you MUST name these tables UNIQUELY else
everyone will be dropping and creating eachothers temp tables
(not a good thing)..

See

http://osi.oracle.com/~tkyte/autonomous/index.html
for info on using autonomous transactions if you must avoid the
implicit commit DDL will do.

Again -- try not to do this.  It is simply not the correct
approach in Oracle.
Review & Followup

      Rating: 3
temporary
      table  August 12, 2003
Reviewer:  vivek
       from UK
Tom,
I am creating temporary tables dynamically in one of mine procedure and then
read in one of your thread where you have mentioned

"DO NOT dynamically create them, DO NOT dynamically create
them, please -- do NOT dynamically create them."

Could you please suggest best way to handle this situation.
My client is going to create Work orders and these workorders will be created by
their vendors based on 'Contract number' issued to them.

Now vendors are approx. 150 in number and contract number issued per financial
year could be in range of 500-600.
What i am doing is creating temporary table based on contract number (which will
be unique) and then process the result.

Now after reading this thread, i am not sure, is this the correct method for
this situation. I know the table structure before hand.

could you please advise us on this.

thanks

Vivek

          Followup:

please, just put these in one place.  no need to use a machine gun approach,
just clutters things up.

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1164655862293#11686912540585

August 12, 2003 Meyer -- Thanks for the question regarding
      "HTMLDB-Own Custom Packaged Procedures or built in feature", version 9.2.0

      You Asked
I have seen arguments that applications should just give the gui developers
package/procedure specs, and not permissions to do SQL (and I agree).

For Marvel...(Saying I am making the tables and GUI myself)....

For basic forms on a table...When would I want to create/call my own procedures
(that insert, update, delete, fetch), and when would I want to use the
"Automatic Row Processing (DML)" feature in Marvel. Looking for your own insight
on this...and/or maybe from Tyler...if he is interested in a recommendation.

Thanks,
Meyer

      and we said...
Meyer,

I asked Joel Kallman to answer this and he said:

You would typically want to use API's when you want to consolidate business
logic and not code it directly in your application.

If all you are doing are straight INSERT, UPDATE, and DELETE statements against
your application tables, then you may want to consider using the Automatic Row
Processing (DML) feature in Marvel/HTML DB.  A nice benefit of using Automatic
Row Processing is that it automatically handles lost update detection (this
occurs when you navigate to a form to edit a row, and before you commit your
changes back to the database, someone else updated the row in the meantime).

This becomes an immediate candidate for wrapping your business logic in an API
when you have to do any other processing before the DML process, which may or
may not alter the process itself.

Let's say you had to perform some query or custom authorization check prior to
updating the row...you could code this logic directly in a PL/SQL process on the
page, but now the custom logic resides on the page.  Any other application which
needs to perform operations against this same table will now also have to
implement the same logic.  If this were consolidated into a package, this
business logic would only need to be authored and maintained in one place.

Even if your API simply implements nothing more than INSERT, UPDATE, and DELETE
statements, it abstracts the business logic and enables you to easily make logic
changes in the future.

So in summary, I'm a fan of consolidating logic in API's.  Do it whenever you
can.  But for simple operations and quick prototyping of an application,
Automatic Row Processing is the way to go.

Joel

------------------------------------------
I agree with Joel 100% on this response.

Tyler
Review
      & Followup

      Rating: 5
Huge
      Help!  August 11, 2003
Reviewer:  Meyer
       from Baltimore, MD
This mirrors my thoughts...

I will use the "built-in DML" for maintenance forms for my lookup tables...and
code the real stuff.

Thanks,
Meyer


Subject | Article | Last Updated (East coast time)
Extent Sizing | article only | Aug 13, 2003 02:08 pm
performance tuning | article only | Aug 13, 2003 02:56 pm
Radio buttons(mutual exclusive accross rows, not normal way) | article only | Aug 13, 2003 07:22 pm
Security while executing PSP procedures | article only | Aug 13, 2003 09:38 pm
Strange behavior of vsize() function. | article only | Aug 14, 2003 07:57 am
Oracle Components -Architecture & Relation | article only | Aug 14, 2003 08:03 am
lob tablespace -- moving a lob | article only | Aug 14, 2003 08:43 am
Reorganization | article only | Aug 14, 2003 10:28 am
Who locked whom? Which objects are locked ? What sql led to the dead lock ? Who all are waiting for the locked objects? | article only | Aug 14, 2003 02:21 pm
dbms_random.value in a subquery | article only | Aug 14, 2003 03:12 pm
Not Null Constraint | article only | Aug 14, 2003 07:55 pm
Escape character at Oracle server level | article only | Aug 14, 2003 07:59 pm
execute shell script from stored procedure | article only | Aug 14, 2003 08:04 pm
Database Backup/Export | article only | Aug 14, 2003 08:05 pm
Optimizer_statistics | article only | Aug 15, 2003 08:36 am
Index rebuild command | article only | Aug 15, 2003 08:39 am
Dynamic Reports | article only | Aug 15, 2003 10:16 am
Space Allocation | article only | Aug 15, 2003 10:20 am
unexplained writes in tablespaces with indexes | article only | Aug 15, 2003 10:57 am
Using SQL to update rows with a value from other existing rows | article only | Aug 15, 2003 11:10 am
Removing column defaults | article only | Aug 15, 2003 07:43 pm
PL/SQL Table with Join Operation | article only | Aug 15, 2003 08:19 pm
Merge uses index even when 'unusable' | article only | Aug 15, 2003 08:20 pm
SEQUENCES | article only | Aug 16, 2003 08:41 am
Localy Manage Tablespace | article only | Aug 16, 2003 08:48 am
Oracle wait events | article only | Aug 16, 2003 09:12 am
Strange sql.... | article only | Aug 17, 2003 04:29 pm
What is the error ORA-01722 | article only | Aug 17, 2003 07:50 pm
Migration from 8.0.5.1 to 8.1.7.0 | article only | Aug 18, 2003 08:44 am
Processing/Parsing XML | article only | Aug 18, 2003 09:07 am
Simple method for loading image files into 8.1.6 | article only | Aug 18, 2003 02:37 pm
Joins and Normalisation | article only | Aug 18, 2003 02:45 pm
Database Recovery | article only | Aug 18, 2003 02:52 pm
Cannot describe table | article only | Aug 18, 2003 02:54 pm
External Procedure -- data type declaration. | article only | Aug 18, 2003 02:57 pm
Query | article only | Aug 19, 2003 07:49 am
How to use FAST_START_IO_TARGET ? | article only | Aug 19, 2003 07:52 am
Break comma/space delimited text out | article only | Aug 19, 2003 10:19 am
row(s) 1 - 38 of 38
August 13, 2003 Daryl -- Thanks for the question regarding "Extent Sizing", version 8.1.7
You Asked
Tom,

I hate to ask this question because the subject has been beaten to death, but I
need some additional help.

I am migrating a database from 7.3.4 to 8.1.7.  We have decided to export and
import, and define locally managed tablespaces.  I want to go with locally
managed tablespaces with uniform extents for all 30 of our tablespaces.  But we
have a handful of tablespaces that are at least 3 gig's of data and will always
be 3 gigs of data and growing.  And the tables in that tablespace are bulk
loaded once a month.  So for those tablespaces only, my colleague thinks that it
will be better if we go out and initially grab 2 gigs (less overhead for
grabbing the extents) and have the next extent 64M or 128M to cut down on the
amount of extents since we know that the tablespace will at least be 3 gigs.  Is
there any negative sides to this, or will both methods work equally?  I think we
should still go with the uniform extents for all of the tablespaces.

Whatever route that we choose I need some clarification...

If we go with uniform extents, it states that the only have to consider PCTUSED
and PCTFREE.  You don't declare an initial extent.
So how do you control what size you want the tablespace to grow "uniformly"  so
you can manage the amount of extents?  Is the bytes after the "extent management
local uniform size" the initial and the next extent size?

example)

Create tablespace TEST
datafile C:\XXXXXXXXXXXXXX size 50M
extent management local
uniform size 64K
/

Is the SQL correct?  Where is the PCTUSED and PCTFREE allocated? Right
underneath in a storage clause?

If we go with grabbing the initial 2GIGS and a next of 64M do we have to use
"autoallocate"?  I'm not sure what the SQL would look like if this was the case.
 Can you give me an example?

I apologize for the lengthy question, and any assistance would be appreciated.

Thanks.
and we said...
IT IS OK TO HAVE LOTS OF EXTENTS.

IT IS OK TO HAVE LOTS OF EXTENTS.

IT IS OK TO HAVE LOTS OF EXTENTS.

That aside, I would not do what you are planning.  If you have a single table in
that tablespace -- I would size the extent based on how the table is expected to
grow over time.  If you think you are doing to add 50m per month, use a 64
UNIFORM extent.  The table at 3 gigs would have a MEASELY, TRIVIAL 48 extents --
no problem there.  It would have to get at least 20 to 30 times LARGER before I
even started to worry about it.

As for the grow "uniformly" question -- the only setting is the "uniform size
XXm" -- that fully controls the growth, nothing else does.

Note: you can still use initial, next, minextents -- it'll just control how much
is preallocate.  If you say "initial 130m next 150m", in an LMT with 64m
extents, we'll grab 3 extents to satisfy that 130 meg initial request.  Likewise
with minextents/initial/next.  We'll compute what we would have allocated and
allocate as many uniform extents as we need to satisfy that.

In summary:

IT'S OK TO HAVE LOTS OF EXTENTS

IT REALLY IS.

Use LMTS. Use uniform sizes.  Just load the data, don't worry about
preallocating, it'll just happen.
Review & Followup
Rating: 5
large extent size August 13, 2003
Reviewer: reader
The doc says, <quote> As a general rule larger extents are preferred over small
extents because large extents avoid dynamic extent allocation. larger extents
can have a small performance benefit because oracle server can read one large
extent from disk with fewer multi-block reads than would be required to read
many small extents. <end quote>

Is the above quote still applicable regardless of objects in DMT or LMT with
regard to Full Table Scan? Thanks.

Followup:
not applicable in LMTS as long as the extent size is a muliple of your db file
multi block read count.

August 13, 2003 Wayne -- Thanks for the question regarding "performance tuning", version 8.1.7
You Asked
Tom,

  Great to see your books are out!

1.  a. I am a believer in testing almost as much as you are.  So, how would I
test (show) that locally managed tablespaces are giving me an edge?  For
tempfiles and for permanent storage?
  b. Or, said differently, what are my advantages of using locally managed
verses dictionary managed with a database supporting 9iAS?
NOTES:  The Database supports 9iAS so there is only one raided volume.  With
only one disk it is difficult to separate IO, and I am working on little
incremental improvements.  The previous dba had kept the application well tuned,
so I doubt that I will find any quick fixes.

Thanks,

Wayne
and we said...
set up a dmt and an lmt

now,

set timing on

create table t1 ( x int ) tablespace dmt storage ( initial 64k next 64k
pctincrease 0 minextents 1000 );

drop table t1;

create table t1 ( x int ) tablespace Lmt storage ( initial 64k next 64k
pctincrease 0 minextents 1000 );

drop table t1;

Now, tell me - which are faster at extent allocation/deallocation?

Also, consider that the LMT allows for greater concurrency (you serialize space
requests at the FILE level, not at the data dictionary level)

Also, consider that with LMTS -- fragmentation is a thing of the past...  that
is not performance so much as "administrative ease"
Review & Followup
Rating: 5
Great Job! August 11, 2003
Reviewer: A reader
Again Tom,
Thanks!

Rating: 3
Data dictionary stress? August 13, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
Since space allocation is not in the data dictionary, does that mean that when
reading data we're not stressing the data dictionary so much ?
What kind of test case would you use to "prove" that it is less stressfull on
reading, if this is the case.

Followup:
tkprof and consistent reads would show you any material differences.

August 13, 2003 Alan -- Thanks for the question regarding "Radio buttons(mutual exclusive accross rows, not normal way)", version 8.1.6
You Asked
Hi, Tom

This question is some kind of developer 6i question.

Say, i have sigle form having unlimited rows with several
columns, i want to create a radio button(not check box) besides
each row. And these buttons are "mutual exclusive" ---- if you
check one then the others are unchecked. Note: for each
line(row) on the form only one button created, and only one
button among these buttons is potentially checked at one time. I
tried to created one group radio buttons on the same data block
and canvas(so they have the same number of buttons with rows),
but don't know how to get it work. And i also created buttons on
diff. block, it works(mutual exclusive) but is limited by the
number of buttons created ---- the number of buttons created is
not synchronized with the number of rows(potentially increase or
decrease at run time).

How to sovle that?

Please help me.

Thanks,
Alan
and we said...
Here is one way.  I just did this:

I built a default block on EMP.  It is displaying two records.

On that multi-record block I added a radio item.  I set that
radio items radio button value to "off" (the string "off").  I
set that radio items visible property to NO.  Just put that
anywhere on the cavas, we won't see it at runtime.

On that block I added another radio item into the same group
(radio_group4 is what mine was called).  I set that radio items
radio button value to "on" (the string "on").  I left this items
visible property at YES.  Put this where you want the radio
group to appear.

On the radio group, I set the "mapping of other values" to off
and the database property "NO".

I added a when-radio-changed trigger with this text:

declare
   curr_rec number default :system.trigger_record;
begin
   default_value( null, 'global.last_radio' );

   if name_in( 'global.last_radio' ) is not null
   then
       go_record( :global.last_radio );
    :emp.radio_group4 := 'off';
   end if;
   go_record(curr_rec);
   :global.last_radio := curr_rec;
end;

That makes it APPEAR that i have a mutually exclusive radio
group running down the side of my form alongside the EMP
records.
Review & Followup
Rating: 5
Greate March 14, 2001
Reviewer: Alan from OKC
That works great, i wish i should find that method before you -- Tom did? Just
kidding.

Thanks again

Rating: 5
March 15, 2001
Reviewer: Helena Markova from Bratislava, Slovakia
Very nice.

Rating: 4
August 13, 2003
Reviewer: anitha from canada
Was useful..thanks a lot

August 13, 2003 Prathap -- Thanks for the question regarding "Security while executing PSP procedures", version 9.0.1
You Asked
Tom,

Our application has mixture of JSP and PSP code. Application logon page is thru
a JSP page where we validate application user. This application user is not a
database user. We do have quite a few PSP procedures. If I know a PSP procedure,
I was able to invoke it thru URL directly by typing,
http:///pls//
1)How can I prevent someone directly accessing the procedure by typing the URL.
I wanted the users to come thru application logon page. I don't want to display
DAD's logon page to users to enter database userid/password. Typically if
someone specifies the URL, I want to display application's logon page.

I was reading some material about custom OWA, but I guess to implement this,
whole application should be in PSP (Means the logon page also should be a PSP).
Please correct me if I am wrong.

2)We have a generic procedure which will generate PSP code for a given TABLE.
How can I execute (create) the procedure which I am generating from another
procedure. In other words, I am generating code for a procedure from another
procedure (generator) and I want to compile the generated code within the
generator. How to achieve this?

Thanks,

Prathap.
and we said...
1) will, just as your JSP's must -- you must run code at the beginning of your
PLSQL routines that validate the user for each and every page (thats the problem
with doing your own authentication/authorization -- you have to do your own
authenticate and authorize each and every page, each and every page).

I suppose you are using some sort of cookie based mechanism -- you'll have to
write a plsql routine that validates the cookie and make SURE each and every PSP
calls that routine first thing.  If you cannot validate, redirect them to the
logon page.

2) EXECUTE IMMEDIATE sql_string;

put the "create or replace procedure ..." in sql_string and execute it.  make
sure the OWNER of the procedure has the CREATE PROCEDURE priv granted directly
to them.  Read:
http://osi.oracle.com/~tkyte/Misc/RolesAndProcedures.html
...
Review & Followup
Rating: 3
PSP Security - cookie mechanism March 12, 2002
Reviewer: Prathap from India
Tom,

Do you have any sample code for making use of cookies? If yes, can you please
provide?

In our JSP security,what we are doing is, when a user is successfully
authenticated, we are putting the session id into SESSIONS and when a page is
accessed, we will get the current session id and compare that with the one exist
in SESSIONS. If these two match, then we display the JSP page to user.

Similarly, in PSP, is there a way to retrieve the session id that was kept in
SESSIONS and also can we get the current session id (not the session id that is
created by DAD)?

Thanks for the simple trick with 'execute immediate'. It solved our problem.

Thanks,
Prathap.

Followup:
Search this site for OWA_COOKIE.  That'll show you how to access the cookies
with plsql.  Hopefully you are using HTTPS - else this isn't very secure!
Protect that sessions table as well.

Rating: 2
Displaying username in PSP. August 13, 2003
Reviewer: ht from California
Tom,
I'm using basic authentication in my DAD.  Doing a "select * from user_users" in
a psp returns the psp creator and not the current user.
What is the recommended method to display the connected user's name in a psp?
Thanks.

Followup:

htp.p( user );

user_users is a view that returns the EFFECTIVE userid:

from sys.user$ u, sys.ts$ dts, sys.ts$ tts, sys.user_astatus_map m, profile$ p,
 profile$ dp
where u.datats# = dts.ts#
  and u.tempts# = tts.ts#
  and u.astatus = m.status#
  and u.type# = 1
  and u.user# = userenv('SCHEMAID')
  and u.resource$ = p.profile#
  and dp.profile# = 0
  and dp.type# = 1
  and dp.resource# = 1
  and p.type# = 1
  and p.resource# = 1

/

in a stored procedure, that'll always be the owner of the procedure (unless
using invokers rights but you don't want that)

Rating: 5
August 13, 2003
Reviewer: A reader
Thanks - works fine.

August 14, 2003 ASHISH -- Thanks for the question regarding "Strange behavior of vsize() function.", version 9.0.1
You Asked
    While experimenting with datatype sizes in Oracle 9i(9.0.1) & Oracle
8i(8.1.6), I came accross a strange thing.
When I issued the following query -
"select vsize(hiredate) from emp;" ,  I got the result as 7.
But on issuing the following query :-
"select vsize(sysdate) from dual;" , I got the result as 8.

Since both "sysdate" as well as "hiredate" (in adove querries) return "date"
datatype, then why both querries show different results?.
NOTE : "hiredate" column in above "emp" table has "date" datatype.

I would be very pleased if you could clarify this doubt. My email address is
ashish_g7@yahoo.com.

Thanking you,
                                                                      yours
sincerely,
                                                                     Ashish
Gharde.
and we said...
Look at the datatype code from a dump - sysdate is different from a date.
sysdate is "special", it is an internal (memory) datatype, a little different
from an on disk date.

ops$tkyte@ORA920> create table t ( x date );
Table created.

ops$tkyte@ORA920> insert into t values ( sysdate );
1 row created.

ops$tkyte@ORA920> select dump(x), dump(sysdate) from t;

DUMP(X)
---------------------------------------------------------------------------------
--------------------------------------------------
DUMP(SYSDATE)
---------------------------------------------------------------------------------
--------------------------------------------------
Typ=12 Len=7: 120,103,3,23,18,28,34
Typ=13 Len=8: 211,7,3,23,17,27,33,0

My advice -- just "ignore" it.  Not really relevant to you or I in the grand
scheme of things.
Review & Followup
Rating: 4
sysdate March 23, 2003
Reviewer: bhabani from bangalore
as tom showed in DUMP(), SYSDATE takes one byte more to store the 'size of the
date(which is 7bytes )'. So in SYSDATE numeric '7' is extra.

Regds

Rating: 4
Thanks for reviewing. March 28, 2003
Reviewer: ASHISH .O. GHARDE from 71, Chintamani Apts, Atrey Layout, NAGPUR -440022, Maharashtra State, INDIA
Thanks for finding time to personally attend my question. The analysis you
provided was really informative and knowledge boosting. The way in which you
used the "dump()" function was simply great. The knowledge provided by your
analysis will greatly help in my future experiments & endeavours. And last but
not the least, Thanks for yor advice, that's one thing that's really practical,
after all.

Rating: 3
Date datatype length 8 when using functions August 14, 2003
Reviewer: Ashwani from India
PL see the following I am not able to under stand

step1 --------------
select data_length from user_tab_columns where
data_type='DATE';

DATA_LENGTH
-----------
7

step2 --------------

create table xyz
(a date);

Table created

step3 --------------

insert into xyz values (to_date('10-aug-03'))

1 row created

step4 --------------

SQL> select distinct(data_length) from user_tab_columns where
2 data_type='DATE';

DATA_LENGTH
-----------
7

step5 --------------

create or replace view temp as (Select trunc(A) as a from xyz);

view created

step 6
SQL> select distinct(data_length) from user_tab_columns where
2 data_type='DATE';

DATA_LENGTH
-----------
7
8
step 7

select TABLE_NAME,COLUMN_NAME
from user_tab_columns where
data_type='DATE' and data_length=8;

The data length for date column shows 8 when we use truc(date_column) in a
view..
As oracle uses 7 bytes to store a date type
usage
century year month date hour minute second

pls guide where has the length increased to 8..

when i use the dump function i see eight bytes of storage and in normal seven
bytes of storage.. using excess-100 in case of century and year and using excess
1 in case of hour and minute..

Strange beahviour.. Only a master can help..

Regards,
Ashwani

Followup:
it just is. it always has been.  it is not at all a teeny bit relevant to us, it
affects nothing in our code, nothing anywhere.

the variable DATE is 8 bytes
the stored data DATE is 7

August 14, 2003 Kishore -- Thanks for the question regarding "Oracle Components -Architecture & Relation", version 8.1.7
You Asked
Good day!

I was curious to know how the Oracle Components Builders, runtime, engines
(Oracle Forms, Reports, PL/SQL , SQL*plus, and others) are related with the
components in application server and the database.

Could you suggest me where can I find a diagram or chart on how the components
are linked and related.

Thanks in advance!
- Kishore
and we said...
I think this breaks it out well:
http://technet.oracle.com/docs/content.html
each little region puts each product into its "niche"
Review & Followup
Rating: 2
Architecture - Flow April 05, 2002
Reviewer: Kishore from MN, USA
Tom,
Thnaks for the response. But you sighted the products from Oracle, which in turn
has to be dug in.

Im looking in for specifically Oracle Forms & Reports on IBM AIX how transaction
flow within them. Appreciate and thanks for helping me.

Eg.
Forms Builder -> forms runtime -> PL/SQl -> SQL/plus Engine ->SQL Executor ->
Procedural Statement Executor -> Database

If you need more info on my question please lemme know.

Thanks.

Followup:
No idea what you mean.

They don't "flow" into eachother.

The forms builder builds fmx files.

The forms runtime runs them.

PLSQL is just a language that either forms or the database executes, depending
on where you put the code.

SQLPlus doesn't fit into this at all.

I think you are over analyzing something here.  forms is just a development
environment -- not much different then say building something in java or VB.
Forms submits stuff to the database to be executed, forms runs some stuff in its
own engine.  There is no "flow" as you are looking for.

Rating: 5
Follow-up , if at all accepted here... April 08, 2002
Reviewer: Pascal from Hamburg, Germany
Hi Tom,

Thanks always for Great Answers....

I would like to ask you briefly for some information on how to configure Oracle
forms 6i in the Three Tier Architecture...especially with Apache Server as my
Web Server...no Oracle Application Server...

I worked on Client-Server Forms Architecture in the past 2 Years , but i was
asked to recommend if we could use Forms 6i for our Web Platform...

I know this won't take so much of your time...
a link to a Brief  Description on how to do this would be appreciated ...

Thanks in advance,

Daily  asktom Website Visitor on all Queries

Followup:
Well, I would recommend reading the documentation -- it does cover all of this:
http://technet.oracle.com/docs/content.html
basically you'll:

install ias
setup the forms server (refering to the documentation for setup details)
deploy your forms to the app server
run your browser.

Rating: 4
oracle August 14, 2003
Reviewer: sanjay singh from india
I was curious to know how the Oracle Components Builders, runtime, engines
(Oracle Forms, Reports, PL/SQL , SQL*plus, and others) are related with the
components in application server and the database.

Could you suggest me where can I find a diagram or chart on how the components
are linked and related.

Thanks in advance!
- Kishore

Followup:
well, it is simple

oracle forms
reports

are part of the application server

plsql is mostly in the database -- but forms and reports have their own engine
as well as they use plsql as their programming language.

sqlplus is just a command line tool -- not in the app server, not in the
database.

August 14, 2003 ade -- Thanks for the question regarding "lob tablespace -- moving a lob", version 8.1.6.0.0
You Asked
How do I move lob indexes and lob segments from one tablespace
to another.
and we said...
if you have a Table T and a lob column Y, it would be:

alter table t move lob(y) store as ( tablespace users )
/

....
Review & Followup
Rating: 4
Lobs in a separate tbs June 12, 2003
Reviewer: Alex Daher from SP, Brazil
Would you recommend putting the log segments in a separate tablespace?

Followup:
supposing you mean "lob" not log...

it is up to you.  lobs make sense in their own tablespace since they use a
"versioning" scheme that is a little different from table data (they use space
very differently)

Rating: 4
Not log segments, LOB segments, sorry... June 12, 2003
Reviewer: Alex from SP, Brazil

Rating: 5
June 12, 2003
Reviewer: A reader

Rating: 4
Move LOB of partition table August 14, 2003
Reviewer: Braniko from Belgrade, Serbia and Montenegro
Hi,

The above example works fine with nonpartition tables. What if we have partition
table with LOB column?

After execution

ALTER TABLE PartitionTableName
   EXCHANGE PARTITION PartitionName WITH TABLE NonPartitionTableName
   WITHOUT VALIDATION;

And

ALTER TABLE PartitionTableName
   MOVE PARTITION PartitionName TABLESPACE  NewTableSpace

LOB segment and LOB Index segment still exist in tablespace where nonpartition
table was created.

ALTER TABLE PartitionTableName MOVE LOB (LobColumnName) STORE AS (TABLESPACE
NewTablespace)

Gives error: ORA-14511: cannot perform operation on a partitioned

Followup:
alter table <tname> move partition <pname> lob (<cname>) store as ( tablespace
<tablespace_name> )

you sort of have to specify the partition you want to operate on.

Rating: 5
August 14, 2003
Reviewer: Braniko
Thank you very much

August 14, 2003 keesara -- Thanks for the question regarding "Reorganization", version 9.0.2
You Asked
Hiii,TOm good morning,
We are using oracle9i ,database size is 100GB.
Now we want take online backup and recovery also same if recovery need.
just we need few scripts as given below,
1)Oracle9i online back sql script example
2)Archival log recovery
3)Incremental online backup
So we need practical examples as you used or can you prefer any good tools for
online backup
thanks,
keesara
and we said...
simple:

RMAN> backup database;

see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:72412348057#11716036518426
and then read the rman docs
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-REC
August 14, 2003 Nag -- Thanks for the question regarding "Who locked whom? Which objects are locked ? What sql led to the dead lock ? Who all are waiting for the locked objects? ", version 8.1.7
You Asked
Tom

Recently I trouble shooted a deadlock  in production which became a showstopper.

I realised that having scripts for the following will really make life easy...

Who locked whom?
Which objects are locked ?
What sql led to the dead lock ?
Who all are waiting for the locked objects?
What kind of lock has been acquired?
What is the order of requests for locks on an objects which is already locked ,
and other sessions have queued up for it.

When a user comes to me and says that his session is hanging up, and assuming
that it is because of a lock , how should I proceed to understand the problem
and resolve it?

If I had the above scripts it would have taken 60% less time for me to resolve
the issue. Can you please provide me links to the scripts.

Thanks

Nag
and we said...
Look in the user_dump_destination on the server.

When we deadlock, we consider that an application error and actually dump a
trace file for you.

Now, a "hang" is not a deadlock.  A deadlock is an ora-60, an error message.  As
soon as you deadlock -- we resolve it.

If you have a "hang", you can:

o run catblock.sql (from $ORACLE_HOME/rdbms/admin) once using svrmgrl when
connected as SYS or INTERNAL.

o when you have the hang, run utllockt (found in same directory) in svrmgrl when
connected as sys or internal.  It'll show a blocker/blockee report.  You can use
my "showsql" (search here for showsql) to see the sql the person being blocked
is trying to execute.  Now, you know a) the blocker, b) the blockee, c) the
thing the blockee is trying to do.

60% less time, interesting -- how did you arrive at that random number?  :)
Review & Followup
Rating: 4
What is a 'Hang' and what causes it ? July 09, 2002
Reviewer: I Singh from Kansas City, MO
Tom I have cleared 'hangs' in the past but never understood how they were
different from a deadlock with relation to Oracle not being able to detect/clear
them. Also is there a parameter that controls the frquency with which Oracle
checks for deadlocks? I had read 3 secs somewhere but I am positive when I tried
it on my m/c Oracle detected some right away and some took more than 3 secs.
Thanks in advance.

Followup:
3 seconds for a deadlock.

But a "hang" is not a deadlock.

A deadlock, if left alone, would be unresovable.  It means I have locked
something YOU want and you have locked something I want.  Unless someone tells
one of us to "go away", we'll be waiting for eachother forever.

Your "hang" is due to you locking something I want and leaving it locked for
hours.  Until you release the lock, I'll be waiting (apparently hung, but not
really).

Rating: 3
Well... January 09, 2003
Reviewer: Sergey from Russia
>>
Now, you know ... c) the thing the blockee is trying to do.
<<

What about the thing the blocker is doing at the moment? The problem might be on
the blocker side.

Thanks a lot.

Rating: 5
svrmgrl missing from 9i? August 14, 2003
Reviewer: Li from CA
Hi, Tom:
I tried to run the scripts in order to see lock information, but for 9i, there
is no svrmgrl under /bin directly. Then, what shall I do to run the scripts
without svrmgrl?
Thanks
Li

Followup:
sqlplus

Rating: 5
Re: svrmgrl missing from 9i August 14, 2003
Reviewer: A reader
"Then, what shall I do to run the scripts without svrmgrl?"

Scary.

Followup:
[ora920@localhost bin]$ ln -s sqlplus svrmgrl

will fix that right up ;)

Rating: 5
RE: will fix that right up... August 14, 2003
Reviewer: Mark A. Williams from Indianapolis, IN USA
... ln -s sqlplus svrmgrl ...

Yeah, but you just know that 'connect internal' will be coming next... :)

- Mark

Followup:
DOH!

Rating: 5
August 14, 2003
Reviewer: A reader
hahaha

Rating: 5
August 14, 2003
Reviewer: A reader
Tom,

I have seen resumes of some dba's who had mentioned troubleshoot "deadlocks". I
dont get this, how smart is that, these guys have been working for more than 5
years and they dont understand what deadlock is....

August 14, 2003 Maik -- Thanks for the question regarding "dbms_random.value in a subquery", version 920
You Asked
Hi Tom,

can u please explain, why in the first select i get different random values in A
and B, whereas in
the second select these values are equal:

Verbunden mit:
Oracle9i Enterprise Edition Release 9.2.0.2.0 - 64bit Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.2.0 - Production

SQL> select x.num a, x.num b from (select dbms_random.value() num from
dba_extents) x
     where rownum < 10;

         A          B
---------- ----------
,077041949 ,617012263
,407078487  ,65212234
,091738267 ,845731559
,725483693 ,492466273
,302446816 ,338863591
,610226867 ,355880581
,263611497 ,298027832
,224679802 ,573592356
,836255197 ,334109933

9 Zeilen ausgewählt.

SQL>
SQL> select x.num a, x.num b from (select rownum,dbms_random.value() num from
dba_extents) x
     where rownum < 10;

         A          B
---------- ----------
,326255537 ,326255537
,742920752 ,742920752
,580281292 ,580281292
,584769609 ,584769609
 ,70732845  ,70732845
,362292964 ,362292964
,127140852 ,127140852
,971983448 ,971983448
,445556903 ,445556903

9 Zeilen ausgewählt.

1000! * Thanks
and we said...
i spent a great deal of time on this phenomena in my book coming out this month
-- "tuning with rownum".

rownum (aggregates, set operations, many other things) force us to materialize
the subquery - and then use that.

the first query was "merged", that is:

 select x.num a, x.num b from (select dbms_random.value() num from
dba_extents) x
     where rownum < 10;

we processed as it's equivalent:

 select dbms_random.value() a, dbms_random.value() b from dba_extents
     where rownum < 10;

the second

select x.num a, x.num b from (select rownum,dbms_random.value() num from
dba_extents) x
     where rownum < 10;

cannot do that "merging" as it would change the effects of rownum in the inner
query.  hence that was more like:

insert into temp ( xxxx, num ) select select rownum,dbms_random.value() num from

dba_extents;

select num, num from temp where rownum < 10;

I use this "fact" as a tuning technique.  I have a couple of interesting
(multi-page) examples in the book coming out to further show where you might use
this fact.
Review & Followup
Rating: 5
How does this impact tuning? August 14, 2003
Reviewer: A reader
Very interesting! But how do you use this merge/no-merge effect for tuning?

Thanks for the continuing education!

Followup:
here is one of the shorter examples from my book -- there are others, reducing
the number of times a function is called, pagination (very important), top-n
queries...

Join Queries

Another use of ROWNUM comes into play when you have this conundrum: You run two
queries, and each query by itself runs very fast, in a couple hundredths of a
second. You need to join these two queries together. This takes a really long
runtime. What to do? Well, you can use this same ROWNUM trick, as follows:

Select *
   From ( select ?., ROWNUM r1 from ?. )  query1,
        ( select ?, ROWNUM r2 from ? ) query2
  Where join_condition?

You should find the runtime to be that of the two individual queries give or
take. Now, bear in mind, I generally see these sorts of issues in databases
where something is wrong: Statistics are wrong or missing, or some setting is
set totally inappropriately. For example, I used this technique on a system
where a domain index was added to a table. That caused the CBO to be used as
shown here:

ops$tkyte@ORA920> create table t ( x clob );
Table created.

ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select * from t;

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

ops$tkyte@ORA920> create index t_idx on t(x) indextype is ctxsys.context;
Index created.

ops$tkyte@ORA920> select * from t;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=82 Bytes=164164)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=2 Card=82 Bytes=164164)

The developers had a text query that ran really fast. They had a relational
query that ran really fast. They put them together, and it ran really slowly.
The reason is that they were using the CBO against unanalyzed tables. They did
not realize that by putting a domain index on that table, every query that would
hit it would use the CBO. The individual queries got lucky, but when they were
run together, the optimizer (given its lack of information) made a bad decision.
As a stopgap measure to make the query work, ROWNUM was very useful. However,
they need to start analyzing tables. At that point, removal of the ROWNUM trick
would probably allow the query to run even faster.

Rating: 5
NO_MERGE hint. August 14, 2003
Reviewer: Kashif
Hi Tom,

The same effect can be acheived using the NO_MERGE hint:

Wrote file afiedt.buf

  1  select x.num a, x.num b from (select /*+ NO_MERGE */ dbms_random.value()
num from
  2  dba_extents) x
  3*      where rownum < 10
SQL> /

         A          B
---------- ----------
.016041082 .016041082
.769042327 .769042327
.462616778 .462616778
.162651294 .162651294
.486199947 .486199947
.541907443 .541907443
.050641219 .050641219
.488757292 .488757292
.034866065 .034866065

9 rows selected.

Does rownum provide any value over the NO_MERGE hint?

Secondly, the way the subquery is executed without the rownum or NO_MERGE hints
seems counter-intuitive, i.e. if I am selecting the same column from the
subquery then it should return the same value. Is there a good reason why the
optimizer was not designed to perform the temp segment optimization (i.e.
materialize subquery and then produce results) by default? Did I miss the boat
completely?

Thanks in advance.

Kashif

Followup:
yes, no_merge works in many cases.

to address your second question -- it is "not" an optimization in all cases.
not merging can be deadly, we merge whenever possible.

in this case, no_merge might be the "better" choice (compare it with rownum)

Rating: 5
Extreme! August 14, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
Now this is something extremelly interesting.

However in my specific case this doesn't seem to help to force oracle to
materialize my subqueries.

I do not have the full example handy, but the general concept is:

select ..
from table t
where (t.v1 in (select v from table1a, table1b where ..)
       or t.v2 in (select v from table2a, table2b where..)
       or t.v1 = 0
      )
and ...

And this is pretty expensive. Running each query separatelly, and pasting the
values in the "in (..." part generates 10 times less consistent gets.

August 14, 2003 Howie -- Thanks for the question regarding "Not Null Constraint", version 8.0.4
You Asked
Hi Tom

Thanks for previous help !!!

1.Could you let me know how to disable,enable,drop a NOT NULL
constraint ?

2. What is the meaning of following values in Constraint_Type in
DBA_CONSTRAINTS view ?

Value  : - "O" , "V", "C"

Thanks In advance
Howie
and we said...
1) you need to find its name.  It will be in USER_CONSTRAINTS.
Its constraint type will be "C" for CHECK and its search
condition will be "COLUMN_NAME" IS NOT NULL.  Here is an
example:

ops$tkyte@ORA8I.WORLD> create table t ( x int NOT NULL );
Table created.

ops$tkyte@ORA8I.WORLD> select constraint_name
  2  from user_constraints where table_name = 'T';

CONSTRAINT_NAME
------------------------------
SYS_C0037507

ops$tkyte@ORA8I.WORLD> insert into t values ( null );
insert into t values ( null )
            *
ERROR at line 1:
ORA-01400: cannot insert NULL into ("OPS$TKYTE"."T"."X")

ops$tkyte@ORA8I.WORLD> alter table t disable constraint
SYS_C0037507;

Table altered.

ops$tkyte@ORA8I.WORLD> insert into t values ( null );

1 row created.

ops$tkyte@ORA8I.WORLD> alter table t enable constraint
SYS_C0037507;
alter table t enable constraint SYS_C0037507
                                *
ERROR at line 1:
ORA-02293: cannot validate (OPS$TKYTE.SYS_C0037507) - check
constraint violated

ops$tkyte@ORA8I.WORLD> alter table t drop constraint
SYS_C0037507;

Table altered.

2) C is a check constraint
   V is a "with check option" view constraint.
   O is a view constraint with the "with read only" constraint.
Review & Followup
Rating: 5
View With Check Option May 01, 2003
Reviewer: George Fernandes from Portugal
Tom,

    I was reading this site for more information about constraints. As mentioned
by you above, the constraint_type will be 'V' when any view is created with
check option. The search_condition column for this is NULL in dba_constraints,
So in which "table.column" can I find the check condition ?

Regards,
George Fernandes,
Portugal.

Followup:
ops$tkyte@ORA920LAP> create or replace view v as select * from t where x>0 WITH
CHECK OPTION;

View created.

ops$tkyte@ORA920LAP> @printtbl8 'select * from user_constraints where table_name
= "V"'
OWNER                         : OPS$TKYTE
CONSTRAINT_NAME               : SYS_C004562
CONSTRAINT_TYPE               : V
TABLE_NAME                    : V
SEARCH_CONDITION              :
R_OWNER                       :
R_CONSTRAINT_NAME             :
DELETE_RULE                   :
STATUS                        : ENABLED
DEFERRABLE                    : NOT DEFERRABLE
DEFERRED                      : IMMEDIATE
VALIDATED                     : NOT VALIDATED
GENERATED                     : GENERATED NAME
BAD                           :
RELY                          :
LAST_CHANGE                   : 01-may-2003 12:12:47
INDEX_OWNER                   :
INDEX_NAME                    :
INVALID                       :
VIEW_RELATED                  :
-----------------

PL/SQL procedure successfully completed.

that is the table.column -- contraint_type = 'V' for that table name in
user_constraints implies "with check option"

the check "conditition" is the predicate in the view itself (text from
user_views)

Rating: 5
Strange July 16, 2003
Reviewer: A reader
Tom,

I see check constraints in user_constraints saying that columns are defined as
not null, but when I do a describe on the tables, NOT NULL doesn't show up...

What am I missing?

Thanks

Followup:
give us an example to reproduce with

Rating: 5
Nevermind Strange July 16, 2003
Reviewer: A reader
I found my problamo.

Sorry for the clutter.

Rating: 5
Re: Strange July 16, 2003
Reviewer: Tony Andrews from London, UK
I guess this is the situation refered to:

SQL> create table t ( id int );

Table created.

SQL> desc t
 Name                            Null?    Type
 ------------------------------- -------- ----
 ID                                       NUMBER(38)

SQL> alter table t add constraint t_nn check (id is not null);

Table altered.

SQL> insert into t (id) values (null);
insert into t (id) values (null)
*
ERROR at line 1:
ORA-02290: check constraint (TANDREWS.T_NN) violated

SQL> desc t
 Name                            Null?    Type
 ------------------------------- -------- ----
 ID                                       NUMBER(38)

Followup:
that is not a NOT NULL constraint, that is a check constraint

ops$tkyte@ORA920LAP> create table t ( x int );

Table created.

ops$tkyte@ORA920LAP> alter table t modify x not null;

Table altered.

ops$tkyte@ORA920LAP> set linesize 50
ops$tkyte@ORA920LAP> desc t
 Name                    Null?    Type
 ----------------------- -------- ----------------
 X                       NOT NULL NUMBER(38)

Rating: 5
Re: Strange July 16, 2003
Reviewer: Tony Andrews from London, UK
But a NOT NULL constraint is also a check constraint?

SQL> drop table t;

Table dropped.

SQL> create table t ( id int not null);

Table created.

SQL> select constraint_name, constraint_type, search_condition
  2  from user_constraints
  3  where table_name='T';

CONSTRAINT_NAME                C
------------------------------ -
SEARCH_CONDITION
--------------------------------------------------------------------------------
SYS_C00136494                  C
"ID" IS NOT NULL

Does this mean that T now has 2 constraints: a NOT NULL constraint AND a check
constraint?

Followup:
internally not null is done as a check, but it is known that the check is for
"nullness".

If you implement your own check constraint -- we have no idea that it is for
"nullness" -- it is just some sql text.

In your case, T has one check constraint that we happen to KNOW is a "not null"
check constraint

Rating: 4
How can I get the "NOT NULL" constraint name in error? July 17, 2003
Reviewer: A reader
Hi Tom,
How can get the name of the "NOT NULL" constraint in the returned message? as in
case of PK or FK violation.
Like:
SQL> create table test1 ( a number constraint pk_test1 primary key);

Table created.

SQL> insert into test1 values (1);

1 row created.

SQL> /
insert into test1 values (1)
*
ERROR at line 1:
ORA-00001: unique constraint (TEST_DMS.PK_TEST1) violated
-- The constraint name is returned

BUT for "NOT NULL" constraint, I get
SQL> create table test (a varchar2(10) constraint nn_a not null)
  2  ;

Table created.

SQL> insert into test values (null);
insert into test values (null)
            *
ERROR at line 1:
ORA-01400: cannot insert NULL into ("TEST_DMS"."TEST"."A")

-- No constraint name

Followup:
it doesnt return that, sorry -- you do get the COLUMN name which is even better
I believe.

not null is a "funny constraint" that way -- it is different from the rest.

Rating: 5
How can i raise programatically from pl/sql August 14, 2003
Reviewer: A reader
how can I raise

ORA-01400: cannot insert NULL into ("OPS$TKYTE"."T"."X") from pl/sql

I want to do something like

being

 if(v_values  is null ) then
    raise ORA-1400;
 end if;

/**/

  I dont' want to create my own customized message but I want to use oracle
native maesage

can I do that ?

rather than creating pragme exception_intit(....);

Thanks,

Followup:

you cannot raise an ora-xxxxx error like that, only in the 2xxxx range using
raise_application_error.

you would have to actually insert a null to get that error

Rating: 5
Thanks August 14, 2003
Reviewer: A reader
it is not a column but just a variable
I want to force it to be not null

in the table other service may insert null in to table...

Followup:
you mean like this:

ops$tkyte@ORA920LAP> create or replace package demo_pkg
  2  as
  3          subtype g_not_null_number_type is number not null;
  4  end;
  5  /

Package created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> show err
No errors.
ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create or replace procedure p( p_input in
demo_pkg.g_not_null_number_type )
  2  as
  3  begin
  4      null;
  5  end;
  6  /

Procedure created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> exec p(1);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec p(NULL);
BEGIN p(NULL); END;

        *
ERROR at line 1:
ORA-06550: line 1, column 9:
PLS-00567: cannot pass NULL to a NOT NULL constrained formal parameter
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

Rating: 5
cool.. August 14, 2003
Reviewer: A reader
thank for prompt reply  :)

Rating: 5
name August 14, 2003
Reviewer: A reader
ops$tkyte@ORA920LAP> exec p(NULL);
BEGIN p(NULL); END;

        *
ERROR at line 1:
ORA-06550: line 1, column 9:
PLS-00567: cannot pass NULL to a NOT NULL constrained formal parameter
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

/***********************************/

  " formal Parameter"  ---> can you pass a name of this formal parameter

as you have "(%s)"  in ora-01400 for column name ?

Followup:
no, you can't

August 14, 2003 Lei -- Thanks for the question regarding "Escape character at Oracle server level", version Oracle 8.1.7
You Asked
In SQL*Plus, I can set default escape character like:
SQLPLUS> set escape '\'
SQLPLUS> select count(*) from dept where dname like '\_%';
is the same as
SQLPLUS> select count(*) from dept where dname like '\_%' escape '\';

It seems that I can't use the second form of SQL statement above in RougeWave,
or I don't want to specify the same escape character every time. I am hoping
that I can set a default escape character at Oracle server level.

Thanks.

- Lei
and we said...
No it is not the same.  You are confusing the SQLPlus "escape", with the escape
in SQL LIKE clauses.

This query:

select count(*) from dept where dname like '\_%';

will return strings like:

o \this is a dept
o \_this is too
o \x

that is -- any string that begins with \ and has any single character after it,
followed by zero or more characters.

the query:

select count(*) from dept where dname like '\_%' escape '\';

will return strings such as:

o _Hello
o _this is a dept
o _

that is -- any string that begins with _ and has zero or more characters after
it.

Regardless of the environment submitted (sqlplus, pro*c, jdbc, whatever)

You MUST specify the escape every time - every time.  It is part of SQL.  The
set escape in sqlplus is totally different and does NOT affect the intpretation
of \ in a query ever!

This is the ANSI syntax for LIKE, no way around it.
Review & Followup
Rating: 3
your query results are correct only when sqlplus escape is off December 10, 2001
Reviewer: lei from NJ
Tom,
Thanks for your quick response. I played with SQL*PLUS a little on Oracle 8.1.7
EE on Solaris 2.7. You query results are correct only if SQL*Plus escape is set
off. The results are different if escape is on.
SQL> show escae
escape OFF
SQL> set escape '\'
SQL> select name from dept where dname like '\_%';
All 6 records in your example will be returned...
SQL> select name from dept where dname like '\_%' escape '\';
ORA-01425 escape char must be char string of length 1
SQL> select name from dept where dname like '\_%' escape '\\';
returns all 6 records too...
SQL> select name from dept where dname like '\\_%' escape '\\';
returns string which begins with '_' and has zero or more chars after it.
SQL> select name from dept where dname like '\\_%';
returns string that begins with '\' and ...

It seems that SQL*Plus escape char does nothing but confusing me.

Thanks.

- Lei

Followup:
for sqlplus:

Command Reference 8-103

SET ESC[APE] {\| c|ON|OFF}

Defines the character you enter as the escape character. OFF undefines
the escape character. ON enables the escape character. ON changes the
value of c back to the default "\".

You can use the escape character before the substitution character (set
through SET DEFINE) to indicate that SQL*Plus should treat the substi-tution
character as an ordinary character rather than as a request for
variable substitution.
....................................

Turning that on causes SQLPlus to scan the string that is your query and upon
hitting \ (the default escape sequence), remove it and look at the NEXT
character.  If the next character is the DEFINE character -- it continues on (it
has REMOVED the \ however).  If the character after the escape character IS the
escape character -- it leaves the second one.

Consider:

ops$tkyte@ORA815.US.ORACLE.COM> set escape on

ops$tkyte@ORA815.US.ORACLE.COM> select '\x' from dual;

'
-
x

the slash is gone

ops$tkyte@ORA815.US.ORACLE.COM> select '\\x' from dual;

'\
--
\x

only one slash goes...

ops$tkyte@ORA815.US.ORACLE.COM> select '\\\x' from dual;

'\
--
\x

here the first and third slash "disappear"

ops$tkyte@ORA815.US.ORACLE.COM> select '\&x' from dual;

'&
--
&x

we get &x

ops$tkyte@ORA815.US.ORACLE.COM> select '&x' from dual;
Enter value for x:
old   1: select '&x' from dual
new   1: select '' from dual

'
-

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

we get prompted for a value of x

Rating: 5
How about inserts? May 10, 2002
Reviewer: Doug from Danbury, CT USA
Is there a way to insert a "&" ?   in a product independent manner?  Table R
just has one column of a varchar2(40).

Tried this:
SQL> insert into R valuies ('&xyz') escape '\';
Enter value for xyz:

Followup:
& is a special character to SQLPlus only.

Simply:

SQL> set define off

and it'll stop doing what it does.  It is not something that needs to be
escaped.

Rating: 3
set define off ? May 13, 2002
Reviewer: Sunil from Cyprus
Thanks Tom for all your answers. They are excellent. About inserting the & in
SQL, this can also be achieved by

set scan off

right?

Followup:
yes.

Rating: 5
Search the & July 03, 2002
Reviewer: A reader
I get the answer I want from this one.  But when I search the '&' on this site,
I get the following error:

ERR-1004 Unable to Compute Function computation_point=BEFORE_HEADER
computation_type=FUNCTION_BODY.

I get here by searching "special character".

Thank you for all the great answers.

Rating: 5
insert with escape February 06, 2003
Reviewer: Baqir Hussain from Emeryville, CA USA
Tom,
How can I insert the following sql with apostoef

INSERT INTO evaluation_parameter
VALUES( 'exceed other's needs');
Thanks

Followup:
Use a bind variable!!!! Never do anything like that except in sqlplus for one or
two inserts.

And then -- just use ''

'exceeds others''s needs'

Rating: 4
Another method June 10, 2003
Reviewer: Fenng from China
[quote]Followup:
& is a special character to SQLPlus only.

Simply:

SQL> set define off

and it'll stop doing what it does.  It is not something that needs to be
escaped.

[/quote]

SQL> truncate table demo;

Table truncated.

SQL> select * from demo;

no rows selected

SQL> desc demo;

 Name                                      Null?    Type
 ----------------------------------------- -------- -----------------
 X                                                  CHAR(20)

SQL> insert into demo values ('scott&'||'tiger');

1 row created.

SQL> select * from demo;

X
--------------------
scott&tiger

SQL>

Rating: 5
How about this.. August 14, 2003
Reviewer: A reader
SQL> create table test(x varchar2(20));

Table created.

SQL> insert into test values('clar%ke');

1 row created.

SQL> commit;

Commit complete.

SQL> select x
  2  from test
  3  where  x like '\%' ESCAPE '\' ;

no rows selected

How can I retrieve the value inserted, by doing a like look up of '%'

Followup:
like '%\%%' escape '\'

August 14, 2003 Vinay -- Thanks for the question regarding "execute shell script from stored procedure", version
You Asked
Is it possible to execute shell script from store
procedure in async. mode? If yes then how and if not is there
any
work around to doit?
and we said...
I can think of a couple of different ways....

o In Oracle8i, release 8.1, we could use java to run a system
command with an "&" after it (assuming unix) or perhaps "start
xxx.cmd" if using NT.

o In Oracle8.0 and up, we can write an external procedure in C
that runs host commands with system() and the "&". (see attached
for an external procedure example)

o In Oracle7.0 and up, we can use dbms_pipes to talk to a daemon
running outside the database.  Here is a simple example that
uses sqlplus to be the daemon:

A quick and dirty way to do this is with a csh script and
sqlplus as such (cut
and pasted from another email)

Ok, so can you do this without C?  Yes.  Here is a PL/SQL
subroutine you can
install in your schema:

create or replace procedure host( cmd in varchar2 )
as
    status number;
begin
    dbms_pipe.pack_message( cmd );
    status := dbms_pipe.send_message( 'HOST_PIPE' );
    if ( status <> 0 ) then raise_application_error( -20001,
'Pipe error' );
    end if;
end;
/

Here is a C-Shell script you can run in the background, it
should be named host.csh.  host.csh will be run by you after the
db is up.  it will create temporary scripts "tmp.csh" that it
will run.  the last thing these tmp.csh scripts do is re-run
host.csh to get the next request....

-------------------- bof ----------------------------
#!/bin/csh -f

sqlplus tkyte/tkyte <<"EOF" | grep '^#' | sed 's/^.//' > tmp.csh

set serveroutput on

declare
        status  number;
        command varchar2(255);
begin
        status := dbms_pipe.receive_message( 'HOST_PIPE' );
        if ( status <> 0 ) then
                dbms_output.put_line( '#exit' );
        else
                dbms_pipe.unpack_message( command );
                dbms_output.put_line( '##!/bin/csh -f' );
                dbms_output.put_line( '#' || command );
                dbms_output.put_line( '#exec host.csh' );
        end if;
end;
/
spool off
"EOF"

chmod +x tmp.csh
exec tmp.csh
----------------------- EOF ---------------------------------

If you run this in the background (The script), you'll be able
to have it
execute any host command you want.  Run this in one window for
example and in
anther window go into sql*plus and try:

SQL> exec host( 'ls -l' );
SQL> exec host( 'uptime' );
SQL> exec host( 'echo Hello World' );
SQL> exec host( 'exit' );

You'll see the output of ls -l, uptime, and echo happen on the
other window
where the shell script is running (shows you a way to debug
pl/sql routines, use
"host( echo some string )" and you'll get real time feedback
from your pl/sql
procedure).....

Make sure you understand the ramifications of the above.  It
does absolutely no checking anywhere that only valid commands
are executed.  If you run this as the oracle account and someone
sends "rm -rf *" -- watch out.  This is an example -- it needs
to be more robust.
Review & Followup
Rating: 5
Host Command in PL/SQL July 18, 2001
Reviewer: Peter Hewitt from Townsville, Australia
Sheer magic!

Rating: 5
DBMS_PIPE August 07, 2001
Reviewer: SV from Chicago, IL USA
It's a Good example for DBMS_PIPE and helps when ever we run into problems of
file pemissions on Unix where the file owner is Oracle (esp. When we use
UTL_FILE).

Rating: 3
October 08, 2001
Reviewer: vani from USA
create or replace procedure host( cmd in varchar2 )
as
    status number;
begin
    dbms_pipe.pack_message( cmd );
    status := dbms_pipe.send_message( 'HOST_PIPE' );
    if ( status <> 0 ) then raise_application_error( -20001, 'Pipe error' );
    end if;
end;
/
I tried executing this procedure in my schema. Later I ran it and it does not
run the unix command passed.
Please let me know what I am missing.

Followup:
what does it do if it does not run the commnd.  Have you run the script in the
background?

Rating: 3
script in Dos batch file October 29, 2001
Reviewer: A reader
Hope that your answer for this question would be helpful for me as i need to run
dos batch files from Pl/sql. Can you provide me dos batch file as like shell
script. I tried my best, but i was not succeeded.

Rating: 5
Running command from sqlplus April 16, 2002
Reviewer: Ron Chennells from York UK
Excellent example

Rating: 3
Does not work for me April 19, 2002
Reviewer: SB from NJ USA
I have the proc working fine in Oracle 8i. The unix shell script runs but does
not print anything out to the tmp.sh file. Please let me know. Thanks

Followup:
don't know -- play with it.

It is very "simple" and straight forward stuff.  Very very simple shell scripts.
 I cannot imagine any reason why tmp.sh wouldn't have something in it after a
message was read off of the pipe.

In 8i you could just use:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:952229840241
much easier, much more secure.

Rating: 4
DBMS_PIPE May 23, 2002
Reviewer: Yang Jiang from Jacksonville, FL
Good tool for Oracle7!
One question: when I try manually exec host procedure, it run one time only. But
when it's executed by a trigger, it's kept running until I killed the background
process. What's happened and how to fix it?

Thanks

Followup:
you did something wrong -- it cannot TELL if it was fired from a trigger, the
moon, whatever -- put debug in and find out whats going on

Rating: 5
run unix script from pl/sql August 24, 2002
Reviewer: mo from Reston, USA
TOm:

1.  I want to run a unix shell script that deletes some files and set up
environment from pl/SQL procedure.

SInce I have not used C or java much I am trying to implement your solution of
using dbms_pipe.  However I do not quite understand what you did:

1.  First you created a pl/sql procedure "host" that takes a "cmd" parameter and
then packs it but then you send "host_pipe". what is that for?

2. Then you create  a script host.csh that runs sqlplus and then another pl/sql
procedure that receive message.

Is this a standard file that you should always have. or basically you are
showing that you ran host.csh from pl/sql.

3.  Since you can always run unix commands in sql*plus using host cmd can you
run a script that way too? or is this what yo uare bascially trying to do.

Thank you,

Followup:
Mo,

suggest you get my book "Expert one on one".

Many (like most all) of the questions you ask in the followups are in there with
lots of examples.  Including the above question!

Rating: 5
unix from stored procedure August 24, 2002
Reviewer: Mo from Reston, USA
TOm:

I will. which chapter you cover this?

Thank you

Followup:
dbms_alert/pipe section in the appendix.

Rating: 4
execute sheel script .... November 12, 2002
Reviewer: John McAllister from Germany
Excellent simple to use.

Rating: 3
How to do this in windows August 14, 2003
Reviewer: Jeff from Seattle
Tom,

This is useful for those who are working on unix. Do you have any trick for
windows user?

I am running some pl/sql script to verify data and I want to know the progress
of the script. Unfortunately, I cannot create any temp table and start logging
into it to check the logs. Neither am I updating any table so as to check how
many rows have been updated. So I want to know somehow what is going on in the
script?

Is it possible to run the host command from pl/sql?

ps : I am working on Oracle 817 EE.

Followup:
just use dbms_application and set_session_longops

very easy.  this (the above) would be a really wrong way to do it.

search this site for those two terms for examples.

August 14, 2003 -- Thanks for the question regarding "Database Backup/Export", version 9.2
You Asked
Tom.

Regarding Backup.

I have a database system producing very few archivelogs. And I have hot standby
database. It means, I have enough time to apply my archivelogs if hardware
failure occurs. How oft must I backup my datafiles and how long should I save
the copys of my archivelogs.
Should I make a cold backup, before I delete the old archivelogfiles.
Is it correct, that if I have all my archive logs since the database creation
and my initial/first backup I can always recover my database.

Regarding export

You said, you make the export of your prod database each two week as one of your
backup strategy. What is the sense of it. I can  not use export to recover my
database to last commited transaction. For me export is really only to sent the
data from one system to other.

If I make export to sent my data to another place and another users update the
tables data to the same time. Is this export consistent. Or should I forbid
another user to access a database to the time of export?
and we said...
you never need a cold backup -- never.

You should use rman to maintain a couple of backups and do your book-keeping.

it is a matter of your MTTR -- mean time to recover. that'll drive your timing
of backups.  even a small number, multiplied by many days, will add up over
time.

also, there is the issue of "media going bad" over time.

backup
backup often
maintain 3 or more backups

I use export to

o validate my structures - any important corruption -- logical or physical --
it'll find it

o to recover that accidently dropped piece of code

o to recover that dropped, mostly static table quickly

you can export whilst people are accessing.
Review & Followup
Rating: 3
Import Synonyms August 14, 2003
Reviewer: kin ng from SF, CA USA
I have a strange problem with IMP.  When I do a SHOW=Y, I have this:

. importing SYSADMIN objects into SYSADMIN
"CREATE SYNONYM "SMP_JOB_EVENTLIST" FOR "SYSADMIN"."SMP_JOB_EVENTLIST_""
"CREATE SYNONYM "SMP_JOB_ID" FOR "SYSADMIN"."SMP_JOB_ID_""
"CREATE SYNONYM "AUTHORIZED_TO_THIS" FOR ""."AUTHORIZED_TO_THIS"@"OTHERDB""

All of a sudden the "SYSADMIN" becomes blank (see the "" after FOR above).  I
noticed this happens only to synonyms and only when the synonym was using a
DBLINK.

Now even stranger, when importing for another user, like user1, the SYNONYMs
were correct:

. importing USER1's objects into USER1
"ALTER SESSION SET CURRENT_SCHEMA= "USER1""

"CREATE SYNONYM "TBLABC" FOR "SYSADMIN"."TBLABC"@"OTHERDB""
"CREATE SYNONYM "TBLCDE" FOR "SYSADMIN"."TBLCDE"@"OTHERDB""

Needless to say the IMP failed on all those synonyms.  Do you know why I have
this strange problem?

Thanks

Followup:
i would suggest filing a tar with support on this one, i havent experienced that
(didn't test it)

August 15, 2003 -- Thanks for the question regarding "Optimizer_statistics", version 8.1.7
You Asked
Hi, Tom

1.I always use dbms_utility.analyze_statistic('USER','COMPUTE') to create
statistics for users schema. Is it creates statistics only for tables or for
tables and indexes?
Should I use ...(user,'COMPUTE','for all indexes') to compute statistics for
indexes AND tables. ( I want NOT to use histogramm).
Would you recommend to compute statisics for all indexes in users schema? I have
read that this can make Oracle to run slow, unless explicit set
optimizer_index_casching=100
2. Would you recommend to set optimizer_mode=first_rows in init.ora for OLTP
system (my Appl. use Oracle forms). Can Oracle use rule base optimizer for
access dictionary if there are no statistics for users
sys/system and optimizer_mode is first_rows instead of choose?
3.What happens when I export/import statistics between two database with
different block_size? For example, with empty_blocks in user_tables? (FULL
DATABASE export/import). What is whith other storage paramteres? F.e., same
block size, but different extents size.
Are there any statistics depend of storage parameters?

Olaf.
and we said...
1) I recommend you use DBMS_STATS and stop using DBMS_UTILITY.  DBMS_UTILTY is
there for backwards compatibility, DBMS_STATS is the proper package to use now.

To do a compute on all of the tables and indexes but NOT gather any histograms
(should consider getting histograms on INDEXED columns!) you could:

BEGIN dbms_stats.gather_schema_stats( user, 99, method_opt =>null, cascade=>
true ); end;

dbms_stats has lots more options then that.  read about it....

You might want to adjust

optimizer_index_cost_adj
optimizer_index_caching

but it'll depend on your other settings.  Jonathan Lewis has a good write up on
this in his totally excellent book "practical Oracle8i" -- highly recommened...

2) I would leave it at choose.  I let applications adjust it if they want to but
the system runs in choose mode.  If there are no stats and the optimizer goal is
set to first_rows, you'll be using the CBO.

3) I don't fully understand this part of the question.

Imp/exp dumps the data to a "flat file" and reloads it.  It is independent of
block size and OS.

storage parameters are specified in bytes, not blocks...  so regardless of the
block size -- an initial extent of 1m will be an initial extent of 1m...
Review & Followup
Rating: 5
what are hstograms and how should we gather them September 18, 2001
Reviewer: Thx from USA

Followup:
Histograms are more in depth statistics concerning the distribution of data in a
tables column.

It is useful when you have an index on a column that contains skewed data.  For
example, suppose you have a column where 50% of the values are 0 and the
remaining 50% are "unique" (eg: the value in the column is either 0 or some
unique value).

Well, if you submit the query "select * from t where column = 0" -- you do NOT
want to use the index (not selective enough).  On the other hand, a query such
as "select * from t where column > 1000" might use an index (depends on the
values).  If you compute histograms for the column -- the optimizer can figure
out when and when NOT to use the index.

Rating: 5
September 18, 2001
Reviewer: A reader
Thanks, Tom.
To the first part of my question: I always use bind variable
in my application. Is there any sense to use histogramms in my case? How much
overhead for parsing and memory when i use histogramms?
To the third part. I want to make import (FULL DATABASE) in the database with
different block size and I have an error that Oracle cannot import statistics. I
should delete all statistics in original database and after this import was
successful. It was first time I should delete statistics before export. I can
successful export statistics before.

Followup:
Yes, histograms can still make sense.

If you have a compound index on (a,b) and the optimizer understands the
distribution of the values in A and B it might optimize:

select * from t where b = ?

differently (9i INDEX SKIP SCAN for example).  There is not a significant
overhead to using histograms.

As for the last part "I have an error" is very vague.  One would need to know
the error in order to diagnose the problem (at the very least).  I doubt it had
to do with blocksizes but rather would be a "product issue" (aka -bug)

Rating: 4
dbms_utility and monitoring September 18, 2001
Reviewer: David from USA
Am I right that dbms_utility.analyze_schema cannot use info in
user_tab_modifications and dbms_stats can

Followup:
dbms_stats can with the gathering of stats on STALE tables.  dbms_utility is not
aware of these new tables (added after it was created).  In all cases,
dbms_stats would be prefered over dbms_utility to compute statistics.

Rating: 5
first_rows September 18, 2001
Reviewer: Andrej from Russia
When is the setting of first_rows in init.ora relevant and useful for me?

Followup:
Well -- it is always "relevant", if it is set -- it will definitely impact you.

as for useful, I don't know.  I personally would not use it, I would use choose
as I stated and if I wanted -- do an alter session to get first_rows
optimization.

Rating: 5
import/export and statistics for sys schema September 18, 2001
Reviewer: Olaf from Germany
Is it right that when I have the statistics for schema sys/system I cannot make
export/import?

Followup:
SYS/SYSTEM should not have stats.

SYS is never exported...

Rating: 5
September 18, 2001
Reviewer: Olaf from Germany
I have ever thought that statistics from all users are placed in the sys schema
and availiable through for example use_tables views. How can I transfer the
users statistics with schema export?

Followup:
EXP can sometimes extract some statistics.  See the server utilities guide under
exp for details.

DBMS_STATS can be used to copy stats and put them into another database as well.

Rating: 4
How to run this DBMS procedures ? September 18, 2001
Reviewer: A reader
I got error when I try to run dbms_space utilities, Do I miss any role
previlige? Scott has DBA role, I also tried with system user, same error. Please
advise.
SQL> CONN SCOTT/TIGER@DBDEMO
Connected.
SQL> EXEC SHOW_SPACE ('EMP');
BEGIN SHOW_SPACE ('EMP'); END;

      *
ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00201: identifier 'SHOW_SPACE' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

Followup:
show_space is a utility I wrote on top of dbms_space.

See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:231414051079
...

Rating: 4
A problem with statistics February 11, 2003
Reviewer: Ravi from India
Hi Tom,

The following query,

select count(*)
  from t1, t2, t3
 where t1.f2 = t2.f2 (+)
   and t2.f3 = t3.f3 (+);

gives a result of

  COUNT(*)
----------
        50

After analyzing my schema using DBMS_STATS.GATHER_SCHEMA_STATS, in a wierd way,
the same query returns the value 1.

  COUNT(*)
----------
         1

In the table T1, the column f2 is NULL except for a row.  So, obviously, the
outer join is not performed but the normal join is performed in the above query.

select /*+rule*/
       count(*)
  from t1, t2, t3
 where t1.f2 = t2.f2 (+)
   and t2.f3 = t3.f3 (+);

gives the original value of 50.

Is this a bug in Oracle?  Or can you please explain why Oracle is behaving this
way?  Also, can you please help me to solve this?  I'm using Oracle 9.2.0.

Thanks in advance
Ravi

Followup:
unless you give an entire test case -- not much I can really say -- don't know
indexes, volumes of data or whatnot.

But yes, I would put this in the category of "erroneous behavior".

Time to file a TAR with support once you get the test case together.

Rating: 4
to delete statistics August 14, 2003
Reviewer: reader
To gather statistics, I use

dbms_stats.gather_table_stats('DEMO','T');

Is there a proc to delete statistics for a particular table? Thanks.

Followup:
check it out:
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96612/d_stats2.htm#1000574
documentation and all!

Rating: 3
DBMS_STATS August 15, 2003
Reviewer: Oleksandr Alesinskyy from Bad Soden am Taunus, Germany
I dare to say that I have very bad exprience with DBMS_STATS and Oracle9i. The
worst was when very
straightforward query after applying DBMS_STATS on underlying objects starts to
produce ORA-3113. Gathering the same statistics with DBMS_UTILITY or ANALYZE
command(in all cases 'FOR TABLE FOR ALL INDEXES FOR ALL INDEXED COLUMNS') has
not cause this problem. There were other issues as well. I have opened TAR but
without big success. So I would not recommend to use DBMS_STATS despite all
Oracle recommendations.

Followup:
nothing to do with dbms_stats.
everything to do with the plan generated based on the statstics gathered.

you are killing the messenger here.  next time, analyze might do it to you and
dbms_stats won't.  3113 means "i'm sorry, something really wrong happened
processing this query plan".  it is NOT dbms_stats that did it.  It is a bug,
but not with dbms_stats.

don't kill the messenger.

August 15, 2003 Mahomed -- Thanks for the question regarding "Index rebuild command", version 7.3.4.0.0
You Asked
I am using the 'alter index x rebuild tablespace t ' command to
rebuild all my table indexes in their current tablespace once
every day via the following script in a cron job. However,
this seems to be eating up the tablespace and eventually
I run out of tablespace. I have tried the 'alter index
deallocate
unused' and 'tablespace coalesce' commands without success.
Is there anything I can get around this problem ?

#!/bin/ksh
if [ $# -eq 0 ]; then
  tblspc=LIVE_INDEX
else
  tblspc=$1
fi

sqlfile=tmp_$$_idx.sql

echo Running SQL to re-index all tables ...

sqlplus -s "user/pass"  <<EOH
SET HEADING OFF
SET ECHO OFF
SET TERM OFF
SET PAGES 0
SET FEED OFF
spool $sqlfile
select distinct 'alter index '||index_name||' rebuild tablespace
$tblspc;'
       from user_ind_columns;
select distinct 'alter index '||index_name||' deallocate
unused;'
       from user_ind_columns
/

spool off
SET TERM ON
SET FEED ON
SET ECHO ON
@$sqlfile
exit;
EOH

echo Re-indexing all tables complete.

rm -f $sqlfile

### end

Regards,
Mahomed

Thanks
and we said...
well, my first question is "why the heck would you consider
rebuilding indexes every day?"  Thats not a recommended thing to
do.  It is a rare occurence to have to actually rebuild an
index.  It should not become part of your standard
operating procedures.  You should rebuild a specific individual
index only when you need to.  That aside..

Every time you rebuild the index -- if the index goes beyond 1
extent -- its computing the default NEXT extent with the
pct-increase builtin.  If you have the defaults -- then every
time you rebuild -- your NEXT extent is getting bumped up by a
factor of 1.5.  consider this small example:

DOC>create tablespace index_test_tbs
DOC>datafile '/tmp/index_test_tbs.dbf' size 1m
DOC>autoextend on
DOC>next 1m
DOC>maxsize 500m
DOC>/
DOC>*/

tkyte@ORA734.WORLD> create index t_idx on t(x,y)
  2  tablespace index_test_tbs
  3  /

Index created.

tkyte@ORA734.WORLD> select pct_increase, initial_extent,
next_extent
  2    from user_indexes
  3   where index_name = 'T_IDX'
  4  /

PCT_INCREASE INITIAL_EXTENT NEXT_EXTENT
------------ -------------- -----------
          50          40960     2580480

tkyte@ORA734.WORLD> alter index t_idx rebuild
  2  tablespace index_test_tbs
  3  /

Index created.

tkyte@ORA734.WORLD> select pct_increase, initial_extent,
next_extent
  2    from user_indexes
  3   where index_name = 'T_IDX'
  4  /

PCT_INCREASE INITIAL_EXTENT NEXT_EXTENT
------------ -------------- -----------
          50          40960     5816320

tkyte@ORA734.WORLD> alter index t_idx rebuild
  2  tablespace index_test_tbs
  3  /

Index created.

tkyte@ORA734.WORLD> select pct_increase, initial_extent,
next_extent
  2    from user_indexes
  3   where index_name = 'T_IDX'
  4  /

PCT_INCREASE INITIAL_EXTENT NEXT_EXTENT
------------ -------------- -----------
          50          40960     8724480

tkyte@ORA734.WORLD> alter index t_idx rebuild
  2  tablespace index_test_tbs
  3  /

Index created.

tkyte@ORA734.WORLD> select pct_increase, initial_extent,
next_extent
  2    from user_indexes
  3   where index_name = 'T_IDX'
  4  /

PCT_INCREASE INITIAL_EXTENT NEXT_EXTENT
------------ -------------- -----------
          50          40960    13090816

tkyte@ORA734.WORLD> alter index t_idx rebuild
  2  tablespace index_test_tbs
  3  /

Index created.

tkyte@ORA734.WORLD> select pct_increase, initial_extent,
next_extent
  2    from user_indexes
  3   where index_name = 'T_IDX'
  4  /

PCT_INCREASE INITIAL_EXTENT NEXT_EXTENT
------------ -------------- -----------
          50          40960    19636224

So, as you can see -- the second extent on my indexes is always
1.5 times the size it was the previous time.

Your solution:

o BEST: don't do this.  its simply not necessary.

o OK: explicity set the NEXT storage parameter on the index
  rebuild yourself.  alter index T rebuild storage ( next 1m )

o Better then OK: use uniformally sized extents of Xm for every
  object and have every object have INITIAL=NEXT with a
  pctincrease of ZERO.  That way, there is no wild extent sizes,
  any extent can be used for any object.  It does not matter
  if your index is in 1, 10, or 100 extents -- performance
  will be the same (however the more extents you have, the more
  space management calls you make so DDL like rebuild will be
  affected if you have many HUNDREDS or THOUSANDS of extents,
  again the correct solution is "don't do the rebuild")
Review & Followup
Rating: 4
oracle DBA July 16, 2001
Reviewer: an from Wilmington, DE
it's just what i am looking for, recently when i worked on my index tables, i
have confused how rebuild index really means, your explanation with example
cleared my confusing. thks!!

Rating: 5
Partition cleanup -> rebuilding indexes August 15, 2003
Reviewer: Mark Rynbeek from Cambridge, UK
hi Tom,

about 'its simply not necessary' - in a system I work on now, I need to refresh
part of the schema every week or so. I thought to be smart and keep this
volatile data in separate partitions next to the more stable data in the same
tables.

The idea then is before a fresh import to truncate these big volatile
partitions, which is much faster than a delete..partition() statement.

However, after each truncate I need to rebuild indexes, including the pk for
these tables - don't seem to have much choice.. right ?

Followup:
depends -- if the indexes are global, yes, if the indexes are local, no.

in 9i, you can do many partition operations with index maintainence enabled,
avoiding the rebuild (at the cost of impacting the speed of the partition
operation of course)

Rating: 5
update global indexes August 15, 2003
Reviewer: Mark from Cambridge, UK

Thanks for pointing out the 9i feature, Tom.  The 'update global indexes' clause
avoids the rebuild, I'm happy.

Rating: 3
Rebuild global partitioned indexes August 15, 2003
Reviewer: Rob from Pittsburgh, PA
Hi Tom:

I have a situation where I have a globally partitioned index (partitioned on a
different key than the table is partiitoned on). I make the index unusable,
truncate the table and bulk load it each week (~300 M rows) I have noticed that
the rebuild of the global partitioned index is taking about 20 hours, only took
about 3 hours to create it. I am guessing that for every partition in the index
it has to full scan the WHOLE table to rebuild each of the 20 partitions i.e. 20
full tables scans to rebuild the 20 partitions. Does this sound reasonable to
you. Any alternative short of dropping the index and recreating it after the
table load?

August 15, 2003 Jeffrey -- Thanks for the question regarding "Dynamic Reports", version 2000
You Asked
How can I create dynamic reports? I want not only to be able to select which
columns to include in the report, but also specify different format masks, font
styles, color, etc. for these columns, and even break groups. Can you help me
with this?
and we said...
That would be the discoverer end user tool.  It is a graphical environment for
doing just this.

Developer and Reports are not appropriate tools for dynamic reports.

See
http://www.oracle.com/ip/deploy/ias/bus_intel_2/index.html?discoverer.html
for details.
Review & Followup
Rating: 4
Discoverer 3.1 January 24, 2002
Reviewer: Angie Hernandez from Mexico DF
Designer 6i is compatible whit discoverer 3.1 ???????
Is necesary create one EUL in owner repository of designer ????

I'm need see my tables of designer 6i in discoverer 3.1, the discoverer display
error "database Error ora-00904 invalid column name " ¿I need create EUL in the
owner repository ???

i need solution . thanks

Rating: 3
Other non AS based options to support graphical reports? June 03, 2003
Reviewer: Matt from Australia
I'm just working through some requirements for a web based application that
*may* require some very simple work flow.
Additionally there are to be some text and graphical reports.

Keeping an open mind and trying to keep this as simple as possible, I have
envisaged one server with Oracle DB, Apache Web Server, mod_plsql.

The question(s) is(are):

Can I support text and graphical reports with just this set of technologies. ie:
Can mod_plsql somehow provide graphical reporting? I guess it does not, but I
would prefer not to have to use an Apps Server if possible. Is there another
option? Can discoverer run seperate to the AS?

Followup:
what do you mean by "graphical reporting"

(tell me, whats the difference between an app server and apache running
mod_plsql)

Rating: 3
More Info June 04, 2003
Reviewer: Matt from Australia
> what do you mean by "graphical reporting"

Graphs with axes: histograms, pie charts, line graphs, overlays of each of
these.

>(tell me, whats the difference between an app server and apache running
mod_plsql)

Apart from dollars, technical setup, and additional complexity?

Looking at your link I took it that Discoverer only ran in conjunction with
Oracle9iAS, Discoverer may be a good fit to some of the ad-hoc reporting
requirements. I want to keep this application cheap and simple.

Are you suggesting that Oracle9iAS and apache running mod_plsql are functionally
the same thing? (or at least overlap functionally for the purposes of the report
functionality described above?)

Can I achieve my "graphical reporting" just using apache and mod_plsql?

Best Regards,

Followup:
histograms are easy -- and you can use SVG to get the rest.  In fact, "project
marvel" -- the stuff that runs asktom -- has this support.

There are 3 flavors of app server

o EE (the whole thing)
o SE (most of the stuff)
o Java Edition <<== sounds like what you might want to look at.

look up svg (google it)

Rating: 5
Load on Discoverer August 15, 2003
Reviewer: A reader
Tom,
When running a report on Discoverer what part(and how much) of the load goes to
Discoverer server and what part(and how much) goes to the database.  Is there
any work done at the user end of the Discoverer (User end - Web browser, User
edition Discoverer)

How much does the network bandwidth/traffic effect these results of running a
report.
Case 1:  If the Discoverer server, DB and the user  trying to run a report from
Discoverer are present in a relatively close physical location.
Case 2:  If the Discoverer server, DB and the user  trying to run a report from
Discoverer are not present in a relatively close physical location.  Say,
Discoverer server and DB present in the US and the user trying to run a report
is in Singapore?

Thank you

Followup:
it is impossible to say.

depends on what you are doing.

Disco does manipulation of data post database.
Database gets data for disco.

complex query, simple formatting -- database gets hit, middle tier not much

simple query, complex pivots, alerts (color this red when... color this green
when...) etc. -- database does nothing, middle tier does alot.

Rating: 5
Load on Discoverer August 15, 2003
Reviewer: A reader
Tom,
Is there any kind of processing done on the end user's(using a web browser) side
when runnig a report?
How badly does the physical network distance to the DB Server/Discoverer server
affect the performance

Thank you

Followup:
there is minimal client side processing....the 'cubic cache' is processed on the
mid tier...obviously if you ask for a lot of data it will be rendered on the
client which can be time consuming -- but the real work is in the DB and the
middle tier.

as for the distance between the DB and Disco -- if you need to process 1,000,000
rows, it'll have a drastic effect.  If you have to processs 500 rows, probably
no effect.  It is pure "math" at that point.

Rating: 5
Load on Discoverer August 15, 2003
Reviewer: A reader
Thank you

August 15, 2003 Robert -- Thanks for the question regarding "Space Allocation", version 9.2
You Asked
Tom,

Does Oracle 9ir2 still allocate space on 5 block boundaries?

I know we ran into this issue in 8.0 when our space reports did not pick up
potnetial problems.  If this is not the case, at what release level did it
change?

Thanks
Robert

PS: Can't wait to get your new book!
and we said...
depends on what type of tablespace you use.

locally managed -- it either uses a uniform size you specify OR it manages space
using an internal undocumented method.  so using uniform, what you ask for is
what you get.

dictionary managed -- the legacy, wrong way to do it -- same as it ever was.
August 15, 2003 Tommy -- Thanks for the question regarding "unexplained writes in tablespaces with indexes", version 8.1.7
You Asked
I have several tablespaces holding only indexes. I have observed writes to these
tablespaces in the statspack reports when I feel certain that there are no
updates to the tables or indexes. The system is a data warehouse with scheduled
loads on the weekends. The writes are occurring during the week. I also am
auditing DML and DDL statements and have not seen any updates or DDL statements
during the period when writes occur in the index tablespaces.

What could be causing the writes?
and we said...
delayed block cleanout.

when we "dirty" a block and dbwr writes it to disk before we commit (or we dirty
more then about 10% the size of the buffer cache -- as you might with a large
load), we write out blocks that look like they are "still in a transaction" --
they look like they have "locked rows"

At runtime, on the next select, we discover these blocks have what look like
"locked rows".  We discover they are in fact not locked -- the transaction
identified in the header is long gone.  We "unlock them" -- this dirties the
block.  This act causes dbwr to evently flush this cleaned out block to disk.

This is normal and expected.  Consider how long a commit would take if we had to
revisit each and every modified block in order to clean them out (unlock them in
effect).

This is a very simplistic explanation -- i go into much more detail in my book
"Expert one on one Oracle".

You can see this in part by monitoring redo generated by a statement.  when we
use a conventional path load, we can create "dirty" blocks.  When we direct path
-- we do not.  I will have to assume that on your system -- you are loading
indexed tables and the index maintanence is causing these dirty blocks.

consider (small sga here to dirty more then 10%):

ops$tkyte@ORA817DEV> create table t1 as select * from big_table.big_table where
1=0;

Table created.

ops$tkyte@ORA817DEV> create table t2 as select * from big_table.big_table where
1=0;

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert into t1 select * from big_table.big_table where
rownum <= 1000000;

1000000 rows created.

ops$tkyte@ORA817DEV> insert /*+ APPEND */ into t2 select * from
big_table.big_table where rownum <= 1000000;

1000000 rows created.

ops$tkyte@ORA817DEV> commit;

Commit complete.

ops$tkyte@ORA817DEV> set autotrace traceonly
ops$tkyte@ORA817DEV> select * from t2;

1000000 rows selected.

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

Statistics
----------------------------------------------------------
          0  recursive calls
         78  db block gets
      80038  consistent gets
      14346  physical reads
          0  redo size
  125544678  bytes sent via SQL*Net to client
    7400351  bytes received via SQL*Net from client
      66668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1000000  rows processed

ops$tkyte@ORA817DEV> select * from t1;

1000000 rows selected.

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

Statistics
----------------------------------------------------------
          0  recursive calls
         26  db block gets
      94345  consistent gets
      14335  physical reads
     859980  redo size
  125544678  bytes sent via SQL*Net to client
    7400351  bytes received via SQL*Net from client
      66668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1000000  rows processed

ops$tkyte@ORA817DEV> set autotrace off

the redo generated by the second query is the result of cleaning out those
blocks.  It would cause writes to the datafiles as well (eventually).
Review & Followup
Rating: 5
Very thorough! Thanks. August 15, 2003
Reviewer: A reader

Rating: 5
Great explanation August 15, 2003
Reviewer: Tommy from USA
Great explanation.

Thanks.

August 15, 2003 Gary -- Thanks for the question regarding "Using SQL to update rows with a value from other existing rows", version 8.1.7
You Asked
Hi Tom

Many thanks for this site.

I have a table t:

 Name                    Null?    Type
 ----------------------- -------- ----------------
 MODEL_NBR                        VARCHAR2(20)
 COLOR                            VARCHAR2(10)
 MTD_UNITS                        NUMBER(10)
 YTD_UNITS                        NUMBER(10)

with data that looks like this:

MODEL_NBR            COLOR       MTD_UNITS  YTD_UNITS
-------------------- ---------- ---------- ----------
100                  BLUE               10       1000
100                  GREEN              15        990
100                  PINK                6         50
100                  RED                20       2500
200                  BLACK              10        750
200                  ORANGE             13        430
200                  PINK                5         95
200                  RED                20       3500
300                  BLACK              22        675
300                  PINK                3        100
300                  RED                50       4200

It was decided by managment that the quantities designated by the color 'PINK'
are already included in the quantities designated by the color 'RED' and they
would like the 'RED' quantities adjusted down by the 'PINK' quantities for both
the MTD and YTD values.

My attmept at this query looks someting like:

update t t1
set t1.mtd_units = t1.mtd_units - (select sum(mtd_units) from t
                                   where t2.model_nbr = t1.model_nbr
                                     and color = 'PINK'),
    t1.ytd_units = t1.ytd_units - (select sum(ytd_units) from t
                                   where t2.model_nbr = t1.model_nbr
                                     and color = 'PINK')
where t1.color = 'RED';

The real life example has about 10 quantity columns that all need this
adjustment and the SQL obviously includes that many sub-queries.

Is there an easier/cleaner/more efficient way to accomplish this task.  Due to
the nature of the data being loaded into the table, this adjustment needs to be
made every day.

Many thanks for your help and your groupies out here very much appreciate your
tireless efforts.
and we said...
ops$tkyte@ORA920> update t t1
  2  set (mtd_units,ytd_units)
  3         = (select t1.mtd_units-t2.mtd_units ,
  4                   t1.ytd_units-t2.ytd_units
  5              from t t2
  6             where t2.model_nbr = t1.model_nbr
  7               and t2.color = 'PINK')
  8  where color = 'RED';

3 rows updated.

do you really need that "sum"?  it seems that model_nbr/color needs to be a
primary key here....
Review & Followup
Rating: 5
excellent August 15, 2003
Reviewer: A reader
excellent

Rating: 4
wonderful answers, August 18, 2003
Reviewer: Nhung

August 15, 2003 Keith -- Thanks for the question regarding "Removing column defaults", version 8.1.6
You Asked
Is there any possible way to remove a default value from a
column once it has been created? I cannot see anything in the
alter table command but I'm sure you know better.

I also noticed, in a quiet five minutes, that it is possible to
create a NOT NULL column with a NULL default. Obviously this
causes an error as soon as another field in the table is updated
and I can't think of one reason why anyone would do this. I was,
however, surprised that you could. I would like to know why this
is allowed.

Thanks in advance.
and we said...
The default DEFAULT for a columns is NULL.  For example, if you:

ops$tkyte@ORA8I.WORLD> create table t ( x int, y int );
Table created.

ops$tkyte@ORA8I.WORLD> insert into t ( x ) values ( 1 );
1 row created.

ops$tkyte@ORA8I.WORLD> set null NULL

ops$tkyte@ORA8I.WORLD> select * from t;

         X          Y
---------- ----------
         1 NULL

so, as you can see -- the DEFAULT for a column by default is
NULL..  Let's make Y have a different default:

ops$tkyte@ORA8I.WORLD> alter table t modify y default 5;
Table altered.

ops$tkyte@ORA8I.WORLD> insert into t ( x ) values ( 2 );
1 row created.

ops$tkyte@ORA8I.WORLD> select * from t;

         X          Y
---------- ----------
         1 NULL
         2          5

and we see that works.  To remove the default, we just set the
default to be the original default of NULL

ops$tkyte@ORA8I.WORLD> alter table t modify y default NULL;
Table altered.

ops$tkyte@ORA8I.WORLD> insert into t ( x ) values ( 3 );
1 row created.

ops$tkyte@ORA8I.WORLD> select * from t;

         X          Y
---------- ----------
         1 NULL
         2          5
         3 NULL

 and now it works as before.  I do not understand the second
part of your question as all NOT NULL columns by default have
NULL as their DEFAULT value to begin with?  If does not cause
any errors on updates as defaults are only supplied on INSERTS.
And of course if you do not supply a value with a non-null
column and the default is NULL (as it is unless otherwise
specified) you will get an error -- meaning that the INSERT'er
MUST supply a value for that column

ops$tkyte@ORA8I.WORLD> delete from t;
3 rows deleted.

ops$tkyte@ORA8I.WORLD> alter table t modify y default 5 NOT
NULL;
Table altered.

ops$tkyte@ORA8I.WORLD> insert into t ( x ) values ( 4 );
1 row created.

ops$tkyte@ORA8I.WORLD> alter table t modify y default NULL;
Table altered.

ops$tkyte@ORA8I.WORLD> update t set x = x+1;
1 row updated.

no errors on updates

ops$tkyte@ORA8I.WORLD> select * from t;

         X          Y
---------- ----------
         5          5

ops$tkyte@ORA8I.WORLD> insert into t ( x ) values ( 4 );
insert into t ( x ) values ( 4 )
            *
ERROR at line 1:
ORA-01400: cannot insert NULL into ("OPS$TKYTE"."T"."Y")

ops$tkyte@ORA8I.WORLD>

but of course an error on insert -- we must supply a value in
the insert statment for Y in order for the insert to succeed (or
have a trigger that supplies a value for Y)
Review & Followup
Rating: 3
Removing data defaults October 22, 2002
Reviewer: Srini from Chennai,INDIA
I created a number column X with default value as 1. Then i altered the column
default value as NULL. I created one more number column Y without default
values. But in the data dict table user_tab_columns the value displays null for
X column for which i have altered and no values(spaces) for Y column. Why is it
so?

Followup:
having a column with an explicit default of NULL is different then having a
column with the default "default" (which just happens to be null).

Same effect, just "different"

Rating: 3
Removing column defaults "NULL" August 15, 2003
Reviewer: reader from BRAZIL
Tom,
You said on case of Srini  from Chennai,INDIA :
Same effect, just "different"
However, i should have table as data model. I mean restore default for no
values(spaces). Shoul I drop and create column again ?

Followup:
I personally would forget about it, doesn't seem very relevant in the grand
scheme of things does it?

Rating: 5
Removing column defaults "NULL" August 21, 2003
Reviewer: Chris Ellison from Hampshire, UK
I have encountered the very same problem as was described in this article.

The data dict stores NULL against the column for the default when explicitly
given this as the default rather than when creating a column with no default.

I wondered whether or not there was a way a to set the default value in the data
dict to a blank rather than NULL as comparison tools such as TOAD's DBA compare
utility return these as a difference!

Mind you this would not be the only problem encountered when using such
comparison utilities. When I have time I think I will write my own........

August 15, 2003 Shelly -- Thanks for the question regarding "PL/SQL Table with Join Operation", version version 8.1.6
You Asked
Can I join a PL/SQL table with database tables? If so, what's
kind of PL/SQL table I can use: index table or nested table?
and we said...
Yes we can -- as long as you have 8.0 and up with objects and
you create the NESTED table type at the SQL level (eg: not in a
SPEC but as a top level SQL type).  Otherwise SQL won't know
what the type is all about... Here are some examples that show
how to SELECT * from a PLSQL_FUNCTION or select * from
PLSQL_VARIABLE where the function/variable is a nested table
type.  Once you can select from it - you can join to it, you can
use it in an IN LIST and so on.

See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:139812348065
for more examples...

REM instead of putting a type in a spec, do this:

ops$tkyte@DEV816> create or replace type myTableType
                                        as table of number;
  2  /

Type created.

REM here is an example of selecting from a 'static' dynamic
REM table.  this example returns 2 rows...

ops$tkyte@DEV816> select a.column_value val
  2  from the ( select cast( myTableType(1,2) as mytableType )
  3                           from dual ) a
  4  /

       VAL
----------
         1
         2

REM here is an example of selecting from a local variable
REM that is a pl/sql table filled in at run time.  Do an
REM aggregate and a sort...

ops$tkyte@DEV816> declare
  2          l_x     myTableType :=
  3                   myTableType( 1, 2, 3, 4, 5, 6, 7, 8, 9 );
  4
  5  begin
  6          for x in ( select sum( a.column_value ) val
  7                 from THE ( select cast( l_x as mytableType )
  8                              from dual ) a
  9                   )
 10          loop
 11                  dbms_output.put_line( x.val );
 12          end loop;
 13
 14          dbms_output.put_line( '---------' );
 15
 16          for x in ( select a.column_value  val
 17              from THE ( select cast( l_x as mytableType )
 18                           from dual ) a
 19             order by a.column_value desc )
 20          loop
 21                  dbms_output.put_line( x.val );
 22          end loop;
 23  end;
 24  /
45
---------
9
8
7
6
5
4
3
2
1

PL/SQL procedure successfully completed.

REM now, we want to "select * from PLSQL_FUNCTION()" not from
REM a table:

ops$tkyte@DEV816> create or replace function getMyTableType
                                return myTableType
  2  as
  3          l_x     myTableType :=
  4                  myTableType( 1, 2, 3, 4, 5, 6, 7, 8, 9 );
  5  begin
  6          return l_x;
  7  end;
  8  /

Function created.

REM here we go... selecting from it:

ops$tkyte@DEV816> select a.column_value  val
  2    from THE ( select cast( getMyTableType() as mytableType )
  3                             from dual ) a
  4  /

       VAL
----------
         1
         2
         3
         4
         5
         6
         7
         8
         9

9 rows selected.

REM just another example of the same....

ops$tkyte@DEV816> create or replace package myTableTypePkg
  2  as
  3          function get return myTableType;
  4
  5          procedure populate( p_x in number );
  6  end;
  7  /

Package created.

ops$tkyte@DEV816> create or replace package body myTableTypePkg
  2  as
  3
  4  g_x        myTableType;
  5
  6  procedure populate( p_x in number )
  7  is
  8  begin
  9
 10          -- How to fill a nested table from a query:
 11
 12          select cast( multiset( select user_id
 13                                   from all_users
 14                                  where rownum < p_x )
 15                                           AS myTableType )
 16            into g_x
 17            from dual;
 18
 19  end;
 20
 21  function get return myTableType
 22  as
 23  begin
 24          return g_x;
 25  end;
 26
 27
 28  end;
 29  /

Package body created.

ops$tkyte@DEV816> exec myTableTypePkg.populate(25)

PL/SQL procedure successfully completed.
ops$tkyte@DEV816> create or replace view X
  2  as
  3  select a.column_value  val
  4    from THE ( select cast( myTableTypePkg.get() as
                                                   mytableType )
  5                 from dual ) a
  6  /

View created.

ops$tkyte@DEV816> select * from X;

       VAL
----------
         0
         5
        11
        16
        25
        22
        26
        24
        42
        28
        30
        32
        33
        34
        36
        37
        38
        39
        40
        41
        59
        43
        46
        45

24 rows selected.
Review & Followup
Rating: 5
PL/sql table October 18, 2001
Reviewer: RS from USA
Hi Tom ,
 I want to do something like this.Doesn't work for me .Please  suggest

decalre
type   mytableType  as table of number index by binary_integer;
x mytabtype;
begin
x(1):=1;
x(2):=2;
  declare
   cursor mycur is
    select a.column_value val
    from the ( select cast( x as mytableType )
                             from dual ) a
   begin
      ------
   end;
end;

Followup:
as it says in the example below:

REM instead of putting a type in a spec, do this:

ops$tkyte@DEV816> create or replace type myTableType
                                        as table of number;
  2  /

Type created.

It MUST be a sql type -- NOT a plsql type.

Rating: 4
Need more October 18, 2001
Reviewer: READER from USA
Hi, TOm,

Could you kindly give me an example of how to set up and manipulate the "SQL"
type plsql table which has more then 1 columns?

For example:
SQL>create or replace type myTableType as table of emp%recordtype???? and how to
manipulate by sql?

Thanks

Followup:
search for:

myscalartype mytabletype

on my site here.  lots of examples (those are the names I use for my examples)

Rating: 5
Speed October 18, 2001
Reviewer: READER from USA
Compare this SQL type Table(in sqlplus) with plsql type table(in olsql), which
one is more faster(from thw performance point of view)?

How can we use the index in SQL type table if we want to assign certain values
into that structure?

Thanks

Followup:
The are roughly equivalent:

ops$tkyte@ORA717DEV.US.ORACLE.COM> create table run_stats ( runid varchar2(15),
name varchar2(80), value int );

Table created.

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> create or replace view stats
  2  as select 'STAT...' || a.name name, b.value
  3        from v$statname a, v$mystat b
  4       where a.statistic# = b.statistic#
  5      union all
  6      select 'LATCH.' || name,  gets
  7        from v$latch;

View created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> /*
DOC>create or replace view stats
DOC>as select 'STAT...' || a.name name, a.value
DOC>      from v$sysstat a
DOC>    union all
DOC>    select 'LATCH.' || name,  gets
DOC>      from v$latch;
DOC>*/
ops$tkyte@ORA717DEV.US.ORACLE.COM> column name format a40
ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> create or replace type myTableType as table
of varchar2(2000)
  2  /

Type created.

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> declare
  2          type myArrayType is table of varchar2(2000) index by
binary_integer;
  3
  4          l_sql_type myTableType;
  5
  6          l_plsql_type myArrayType;
  7          l_empty      myArrayType;
  8
  9      l_start number;
 10          l_string long;
 11  begin
 12      insert into run_stats select 'before', stats.* from stats;
 13
 14      l_start := dbms_utility.get_time;
 15      for i in 1 .. 1000
 16      loop
 17                  l_sql_type := myTableType();
 18
 19                  for i in 1 .. 500
 20                  loop
 21                          l_sql_type.extend;
 22                          l_sql_type(l_sql_type.count) := rpad( '*', 4*i, '*'
);
 23                  end loop;
 24
 25                  for i in 1 .. 500
 26                  loop
 27                          l_string := l_sql_type( dbms_random.value(1,500) );
 28                  end loop;
 29      end loop;
 30      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 31
 32      insert into run_stats select 'after 1', stats.* from stats;
 33
 34      l_start := dbms_utility.get_time;
 35      for i in 1 .. 1000
 36      loop
 37                  l_plsql_type := l_empty;
 38
 39                  for i in 1 .. 500
 40                  loop
 41                          l_plsql_type(l_plsql_type.count+1) := rpad( '*',
4*i, '*' );
 42                  end loop;
 43
 44                  for i in 1 .. 500
 45                  loop
 46                          l_string := l_plsql_type( dbms_random.value(1,500)
);
 47                  end loop;
 48      end loop;
 49          commit;
 50      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 51
 52      insert into run_stats select 'after 2', stats.* from stats;
 53  end;
 54  /
4982 hsecs
5127 hsecs

PL/SQL procedure successfully completed.

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> select a.name, b.value-a.value run1,
c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11     and a.name like '%redo%'
 12   order by abs( (c.value-b.value)-(b.value-a.value))
 13  /

NAME                                           RUN1       RUN2       DIFF
---------------------------------------- ---------- ---------- ----------
STAT...redo entries                              16         14         -2
LATCH.redo writing                               64         72          8
LATCH.redo allocation                            29         44         15
STAT...redo size                              21452      21488         36

I don't understand the second part about "the index in a sql type table" - the
sql types are roughly equivalent to the plsql table type -- you might have to
EXTEND them first (allocate)....

Rating: 5
April 16, 2002
Reviewer: Randy Richardson from St. Louis, MO
I am using the type collection for an IN statement for three columns as the user
application can select one or more ID to query. I find performance is dropping
using more than one collection type at a time.  Here is what I have, ASSOCIATION
table has concatenated index "AK_ASSOC_L1"  on all five columns
location_id,area_id,process_id ...

SQL:

SELECT
E.DESCRIPTION,
A.EQUIP_ID,
AR.DESCRIPTION AR_DESCRIPTION,
PR.DESCRIPTION PR_DESCRIPTION,
ET.DESCRIPTION ET_DESCRIPTION,
A.REQUIRESAPPROVAL
FROM CMR.ASSOCIATION A,
CMR.EQUIPMENT_TYPE ET,
CMR.AREA AR,
CMR.PROCESS PR,
CMR.EQUIPMENT E
WHERE A.LOCATION_ID = 3
AND A.AREA_ID IN (3)
AND A.PROCESS_ID IN (12,13,10)
AND A.EQUIP_TYPE_ID IN (41)
AND A.EQUIP_ID = E.EQUIP_ID
AND A.PROCESS_ID = PR.PROCESS_ID
AND A.AREA_ID = AR.AREA_ID
AND A.EQUIP_TYPE_ID = ET.EQUIP_TYPE_ID
AND A.ASSOC_TYPE_ID = 4
AND A.DELETED = 0
26 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=7 Card=1 Bytes=107)
   1    0   NESTED LOOPS (Cost=7 Card=1 Bytes=107)
   2    1     NESTED LOOPS (Cost=6 Card=1 Bytes=76)
   3    2       NESTED LOOPS (Cost=5 Card=1 Bytes=64)
   4    3         NESTED LOOPS (Cost=2 Card=1 Bytes=42)
   5    4           TABLE ACCESS (BY INDEX ROWID) OF 'AREA' (Cost=1 Card=1
Bytes=15)
   6    5             INDEX (UNIQUE SCAN) OF 'PK_AREA' (UNIQUE)
   7    4           TABLE ACCESS (BY INDEX ROWID) OF 'EQUIPMENT_TYPE' (Cost=1
Card=1 Bytes=27)
   8    7             INDEX (UNIQUE SCAN) OF 'PK_EQUIP_TYPE' (UNIQUE)
   9    3         INLIST ITERATOR
  10    9           INDEX (RANGE SCAN) OF 'AK_ASSOC_L1' (NON-UNIQUE) (Cost=3
Card=1 Bytes=22)
  11    2       INLIST ITERATOR
  12   11         TABLE ACCESS (BY INDEX ROWID) OF 'PROCESS' (Cost=1 Card=3
Bytes=36)
  13   12           INDEX (UNIQUE SCAN) OF 'PK_PROCESS' (UNIQUE)
  14    1     TABLE ACCESS (BY INDEX ROWID) OF 'EQUIPMENT' (Cost=1 Card=65077
Bytes=2017387)
  15   14       INDEX (UNIQUE SCAN) OF 'PK_EQUIPMENT' (UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        253  consistent gets
          0  physical reads
          0  redo size
       2413  bytes sent via SQL*Net to client
        536  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          5  sorts (memory)
          0  sorts (disk)
         26  rows processed

SQL With types:

SELECT
E.DESCRIPTION,
A.EQUIP_ID,
AR.DESCRIPTION AR_DESCRIPTION,
PR.DESCRIPTION PR_DESCRIPTION,
ET.DESCRIPTION ET_DESCRIPTION,
A.REQUIRESAPPROVAL
FROM CMR.ASSOCIATION A,
CMR.EQUIPMENT_TYPE ET,
CMR.AREA AR,
CMR.PROCESS PR,
CMR.EQUIPMENT E
WHERE A.LOCATION_ID = 3
AND A.AREA_ID IN (SELECT *
    FROM THE(SELECT CAST(STR2TBL('3')
        AS MYTABLETYPE)   FROM DUAL ) )
AND A.PROCESS_ID IN (SELECT *
    FROM THE(SELECT CAST(STR2TBL('12,13,10')
        AS MYTABLETYPE)   FROM DUAL ) )
AND A.EQUIP_TYPE_ID IN (SELECT *
    FROM THE(SELECT CAST(STR2TBL('41')
        AS MYTABLETYPE)   FROM DUAL ) )
AND A.EQUIP_ID = E.EQUIP_ID
AND A.PROCESS_ID = PR.PROCESS_ID
AND A.AREA_ID = AR.AREA_ID
AND A.EQUIP_TYPE_ID = ET.EQUIP_TYPE_ID
AND A.ASSOC_TYPE_ID = 4
AND A.DELETED = 0
/

26 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=671 Card=656396 Byte
          s=95833816)
   1    0   HASH JOIN (Cost=671 Card=656396 Bytes=95833816)
   2    1     VIEW OF 'VW_NSO_1' (Cost=32 Card=8168 Bytes=106184)
   3    2       SORT (UNIQUE) (Cost=32 Card=8168 Bytes=16336)
   4    3         COLLECTION ITERATOR (PICKLER FETCH)
   5    4           TABLE ACCESS (FULL) OF 'DUAL' (Cost=1 Card=82)
   6    1     HASH JOIN (Cost=235 Card=12215 Bytes=1624595)
   7    6       HASH JOIN (Cost=155 Card=824 Bytes=98880)
   8    7         NESTED LOOPS (Cost=116 Card=98 Bytes=10486)
   9    8           HASH JOIN (Cost=18 Card=98 Bytes=7448)
  10    9             HASH JOIN (Cost=10 Card=98 Bytes=4802)
  11   10               HASH JOIN (Cost=6 Card=98 Bytes=3626)
  12   11                 INDEX (RANGE SCAN) OF 'AK_ASSOC_L1' (NON-UNIQUE)
(Cost=4 Card=98 Bytes=2156)
  13   11                 TABLE ACCESS (FULL) OF 'AREA' (Cost=1 Card=222
Bytes=3330)
  14   10               TABLE ACCESS (FULL) OF 'PROCESS' (Cost=2 Card=2204
Bytes=26448)
  15    9             TABLE ACCESS (FULL) OF 'EQUIPMENT_TYPE' (Cost=5 Card=4095
Bytes=110565)
  16    8           TABLE ACCESS (BY INDEX ROWID) OF 'EQUIPMENT' (Cost=1
Card=65077 Bytes=2017387)
  17   16             INDEX (UNIQUE SCAN) OF 'PK_EQUIPMENT' (UNIQUE)
  18    7         VIEW OF 'VW_NSO_2' (Cost=32 Card=8168 Bytes=106184)
  19   18           SORT (UNIQUE) (Cost=32 Card=8168 Bytes=16336)
  20   19             COLLECTION ITERATOR (PICKLER FETCH)
  21   20               TABLE ACCESS (FULL) OF 'DUAL' (Cost=1 Card=82)
  22    6       VIEW OF 'VW_NSO_3' (Cost=32 Card=8168 Bytes=106184)
  23   22         SORT (UNIQUE) (Cost=32 Card=8168 Bytes=16336)
  24   23           COLLECTION ITERATOR (PICKLER FETCH)
  25   24             TABLE ACCESS (FULL) OF 'DUAL' (Cost=1 Card=82)

Statistics
----------------------------------------------------------
         15  recursive calls
         36  db block gets
       4410  consistent gets
         78  physical reads
          0  redo size
       2413  bytes sent via SQL*Net to client
        536  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
         20  sorts (memory)
          0  sorts (disk)
         26  rows processed

For some reason CBO wants to do full table scans on area,process and equip_type.
 I have Oracle 8.1.7.3 and all values in the above query are parameters in a
package returning a ref curser.

The application currently uses dynamic sql from the application server but this
query is called alot and the sql area have about 60 different versions of it so
I wanted to use a procedure and bind variables.

Thanks..

Followup:
in ( a, b, c )

can be processed very differently then

in ( sub-query )

sub-query can return an indeterminate number of rows. in (a,b,c) has 3 values
forever.  INLIST Iterator can be used.

Try joining to the collection instead of "in'ing" on it.  Play with the query --
try other constructs, even consider doing a cartesian product of the 3 string to
tables and joining to that.

All else fails -- use the values in the IN LIST for issue:

alter session set cursor_sharing=force;
open the query
alter session set cursor_sharing=exact;

to "auto-bind" it.

Rating: 5
April 17, 2002
Reviewer: Randy Richardson from St. Louis, MO
Thanks for the feedback.

Can you show how to join to the collection.  I'm tring:

--------------------------------
SQL> SELECT * FROM THE
  2  (SELECT CAST (STR2TBL ('1,2,3') AS MYTABLETYPE)
  3  FROM DUAL);

COLUMN_VALUE
------------
           1
           2
           3

create table junk (area_id number);

Table created.

SQL> create table junk (area_id number);

Table created.

SQL> insert into junk values(1);

1 row created.

SQL> insert into junk values(2);

1 row created.

SQL> insert into junk values(3);

1 row created.

SQL> insert into junk values(4);

1 row created.

SQL> select a.area_id
  2  from junk a , (SELECT * FROM THE
  3  (SELECT CAST (STR2TBL ('1,2,3') AS MYTABLETYPE)
  4  FROM DUAL)) b
  5  where a.area_id = b.*;
where a.area_id = b.*
                    *
ERROR at line 5:
ORA-01747: invalid user.table.column, table.column, or column specification
----------------------------------

I can't seem to be able to lable the COLUMN_VALUE to do the join.

Thanks.

Followup:
the column name -- as shown in sqlplus -- is column_value.  = b.column_value

Rating: 5
April 17, 2002
Reviewer: Randy Richardson from St. Louis, MO
I got it.  Kind of ugly though.

select a.area_id
from junk a , (SELECT c.column_value FROM THE
(SELECT CAST (STR2TBL ('1,2,3') AS MYTABLETYPE)
FROM DUAL)c) b
where a.area_id = b.column_value;

Followup:
if you have a more current release of Oracle, it is "less ugly"

ops$tkyte@ORA817DEV.US.ORACLE.COM> create type myTableType as table of number;
  2  /

Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function f( p_n in number )
return myTableType
  2  as
  3          l_data myTableType := myTableType();
  4  begin
  5          for i in 1 .. p_n loop
  6                  l_data.extend;
  7                  l_data(l_data.count) := i;
  8          end loop;
  9          return l_data;
 10  end;
 11  /

Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from TABLE(cast(f(5) as
myTableType));

COLUMN_VALUE
------------
           1
           2
           3
           4
           5

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

Rating: 4
October 31, 2002
Reviewer: A reader
Tom,

I trying to create a function using collections, if i pass empno it should
return me ename, sal, deptno of that employee..

here is my code..

SQL>  create or replace type myTableType as table of varchar2(50);
  2  /

Type created.

SQL> ed
Wrote file afiedt.buf

  1  create or replace function f( p_n in number )
  2  return myTableType
  3      as
  4              l_data myTableType := myTableType();
  5     begin
  6              for i in ( select ename||' '||sal||' '||deptno from emp where
empno=p_n ) loop
  7                      l_data.extend;
  8                      l_data(l_data.count) := i;
  9              end loop;
 10             return l_data;
 11*    end;
SQL> /

Warning: Function created with compilation errors.

SQL> sho err
Errors for FUNCTION F:

LINE/COL ERROR
-------- ---------------------------------------------------8/21     PL/SQL:
Statement ignored
8/45     PLS-00382: expression is of wrong type

Followup:
  6              for i in ( select ename||' '||sal||' '||deptno data from
emp where
empno=p_n ) loop
  7                      l_data.extend;
  8                      l_data(l_data.count) := i.data;
  9              end loop;

Rating: 5
How about returning multiple columns.. November 01, 2002
Reviewer: pasko from HH,Germany
Hi Tom,
thanks for a Great Discussion!
I have a question though :
I see that you are returning only one value :

column_value

Do we call this a pseudo column ?

What do i do i want to return multiple values .

Can my sql table type be of Type of
Database_Table%RowType ?

Thanks.

Followup:
It is not a psuedo column -- those are 'made up' columns like ROWID.

column_value is the name of this "un-named" column

You can return complex objects as well, see:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:246014735810

Rating: 3
Performance Doubts December 18, 2002
Reviewer: sundeep from Peoria, IL
Tom,
We have 8174 db and I am finding it very hard to tune queries with collection
parameters e.g in predicates like:
WHERE eqp.equipment_id IN (SELECT *
      FROM TABLE(CAST(equipment_id_arr AS num_arr)))
I also played around with using in-line views e.g:

FROM equipment eqp,
     (SELECT column_value equipment_id
        FROM TABLE(CAST(equipment_id_arr AS num_arr))) eqp_list
WHERE eqp.equipment_id = eqp_list.equipment_id

Both resulting in terrible execution plans (do full table scan of a large
equipments table etc. The befuddling part is I can't seem to figure out the
execution plan. So far it seems to work better to convert the collection of ids
or strings to a comma seperated list and using dynamic sql using IN operator.

The idea of using arrays as stored procs parameters is appealing in many
instances but unlike regular queries, I find it really hard to figure out why
optimizer is choosing a certain plan when collections are used as mentioned
above. Almost any query with object type parameters poses a conundrum and
results in an unexpected and difficult to tune plan. Please shed some more
light....

Rating: 4
How in form January 03, 2003
Reviewer: A reader
Hi, TOm,

I tried this method in local procedure in FROM(6i),
what I did is declare a local variable:
v sql_tab_type;   --- "sql_tab_type" created as follows:
                  --SQL>  create or replace type
                  --sql_tab_type as TABLE of VARCHAR(80)
                  --/
but it give me error:
RPC can not use variable in schema level user-define
types in this realeas.
My FORM is 6i and DB is 8.1.7.00

Would mind tell me the work around?

THanks

Followup:
the variable must live in the server -- especially for SQL to be able to select
from it.  You'll need to put this in a packaged procedure in the SERVER, not on
the client.

Rating: 5
Great solution but slow.... January 28, 2003
Reviewer: Rizwan Qazi from Hayward, CA USA
Tom,
   Is there any way I can make the following select fast:
select a.column_value  val
   from THE ( select cast( l_x as mytableType )
 from dual ) a

I mean not the exact sql, but anything involving object table type...when I
tried it is very very slow compared to other methods which does not involve
object table types..

Thanks in advance,
Rizwan

Followup:
its fast for me.  give me a concrete example to work with.

Rating: 4
Explain Plan June 09, 2003
Reviewer: Vipin from New york
Hi Tom,

Could you please explain why we are not able to see the complete explain plan of
following statement:-

select Deptno, Dname,
       Cursor(Select empno,
                     ename,
                     sal
              from Emp
              where Deptno = Dept.Deptno
              ) the_details
From Dept
Where Dname like 'ACC%'

We are able to see only  the plan for selection from DEPT. But not able to see
from EMP.

Followup:
because scalar subqueries do not CURRENTLY appear in explain plans.
(currently....)

Rating: 4
PL/SQL Table with Join Operation July 19, 2003
Reviewer: Mohan K from Bangalore, India
I am having a PL/SQL table index by integer TYPE defined in a package spec. I
want to use this type to declare a vbariable in another package body. How to do
this.

Also I have stored a set of values using the above type in a package. I want to
use the same values in another package.
I do not want to use SQL objects since they cannot be indexed. I do not want to
use global temporary tables since this will generate error if I use the function
from a SQL query. Any solution.

Mohan

Followup:
declare
    variable   package_name.type_name;
begin
   .....

i don't understand half of the second paragraph -- gtts have an error?

but anyway, just put the variables in the spec and access them via
package_name.variable_name.

Rating: 5
collection query mystery August 15, 2003
Reviewer: A reader
Hi Tom
I have a strange situation - I have two queries that look like:

query1
  select a.timestamp1
  bulk collect into l_timestampList
  where <where clause>;

Then I use the above l_timestampList variable in query 2

query2

SELECT count(*)
into l_count
FROM table1 a,
  TABLE ( CAST ( l_timestampList AS timestampTable)) b
WHERE a.timestamp1 = value(b)

This gives me bunch of records. However, when I try
to avoid the collection variable above and directly
put the select in query1 in query2 - I dont get any
records selected  - this query would look like:

SELECT count(*)
into l_count
FROM table1 a,
  (
    /* this is query1 */
    select a.timestamp1
    where <where clause>
  ) b
WHERE a.timestamp1 = b.timestamp1;

I am not sure why this happens.
Has it anything to do with timestamp format potentially?
(Don't think so)
I would post the original queries if those are required..but thought may be you
can think of
something of the top of your head ( the crystal ball may
not be in the shop, perhaps!;) - just kidding!)

Thanx for a great site!!

Followup:
is "a.timestamp1" a date?

is l_timestamplist a collection of varchar2?

could be as simple as time truncation on a date...

so many questions -- how about you post a complete, terse, test case?

Rating: 5
thanx Tom! August 15, 2003
Reviewer: A reader
"is "a.timestamp1" a date?"

Yes
" is l_timestamplist a collection of varchar2?"

No it is a collection of dates...

I will try and post a test case asap - it will take
some time for me to make it terse enough.

Thanx a bunch!!

August 15, 2003 andrew -- Thanks for the question regarding "Merge uses index even when 'unusable'", version 9.2
You Asked
When I run the following series of statements I get the error "ORA-01502: index
<index name> or partition of such index is in unusable state".   I found that if
my source and destination tables had  a small number of rows, say five to ten,
that the merge works fine.  However, with a large number of rows -- as my
example uses -- I get the error.  It appears that the merge goes thorugh a stage
where it 'insists' on using the index and ignores the fact that it is unusable.
Can you offer any assistance?

DROP TABLE sales_item;
CREATE TABLE sales_item
            AS SELECT my_seq.nextval AS seq_id,
                      a.*
                 FROM all_objects a;

-- now have about 8,100 rows

BEGIN
FOR i in 1 .. 11
LOOP
  INSERT /*+ APPEND */
    INTO sales_item
         SELECT my_seq.nextval,  owner,
                object_name,      subobject_name,
                object_id,        data_object_id,
                object_type,      created,
                last_ddl_time,    timestamp,
                status,           temporary,
                generated,        secondary
           FROM sales_item;
  COMMIT;   -- necessary because modifying table in parallel
END LOOP;
END;
-- now have about 16.7 million rows.
/

CREATE UNIQUE INDEX sales_item_pk ON sales_item (seq_id);

ALTER TABLE sales_item
      ADD CONSTRAINT sales_item_pk
      PRIMARY KEY (seq_id)
      USING INDEX;

BEGIN
DBMS_STATS.GATHER_TABLE_STATS (
   ownname          => 'prod',
   tabname          => 'sales_item',
   method_opt       => 'FOR ALL INDEXED COLUMNS SIZE 1',
   degree           => 4);
END;
/

DROP TABLE si_load;
CREATE TABLE si_load
       AS SELECT * from sales_item
           WHERE seq_id > 10000000;
COMMIT;

INSERT /*+ APPEND */
  INTO si_load
       SELECT my_seq.nextval,  owner,
                object_name,      subobject_name,
                object_id,        data_object_id,
                object_type,      created,
                last_ddl_time,    timestamp,
                status,           temporary,
                generated,        secondary
           FROM sales_item
          WHERE seq_id < 8000000;
COMMIT;
-- Now have 7.9 million new rows and 6.7 update rows.

BEGIN
DBMS_STATS.GATHER_TABLE_STATS (
   ownname          => 'prod',
   tabname          => 'si_load',
   method_opt       => 'FOR ALL INDEXED COLUMNS SIZE 1',
   degree           => 4);
END;
/

alter index sales_item_pk unusable;
alter table sales_item disable primary key keep index;
alter session set skip_unusable_indexes = true;

MERGE
 INTO sales_item si
USING (SELECT seq_id,
              owner,
              object_name,
              subobject_name,
              object_id,
              data_object_id,
              object_type,
              created,
              last_ddl_time,
              timestamp,
              status,
              temporary,
              generated,
              secondary
         FROM si_load) sl
   ON (    si.seq_id = sl.seq_id)
 WHEN MATCHED THEN
      UPDATE
         SET si.owner         = sl.owner,
             si.object_name   = sl.object_name,
             si.object_id     = sl.object_id
 WHEN NOT MATCHED THEN
      INSERT (si.seq_id,
              si.owner,
              si.object_name,
              si.subobject_name,
              si.object_id,
              si.data_object_id,
              si.object_type,
              si.created,
              si.last_ddl_time,
              si.timestamp,
              si.status,
              si.temporary,
              si.generated,
              si.secondary)
      VALUES (sl.seq_id,
              sl.owner,
              sl.object_name,
              sl.subobject_name,
              sl.object_id,
              sl.data_object_id,
              sl.object_type,
              sl.created,
              sl.last_ddl_time,
              sl.timestamp,
              sl.status,
              sl.temporary,
              sl.generated,
              sl.secondary);

/* error message returned . . .
MERGE
*
ERROR at line 1:
ORA-01502: index 'PROD.SALES_ITEM_PK' or partition of such index is in unusable
state
*/
-- then rebuild indexes, gather stats, and etc.
Thanks
and we said...
Ok, it is a "fine nuance" of the skip_unusable_indexes at work here.  A simple
INSERT would have failed as well.
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/statements_22a.htm#2182515
<quote>
If an index is used to enforce a UNIQUE constraint on a table, then allowing
insert and update operations on the table might violate the constraint.
Therefore, this setting does not disable error reporting for unusable indexes
that are unique.
</quote>

So, two options.

a) drop the index, you need to rebuild it from the base table anyway so except
for the fact that it might "go missing" after the merge (not likely if you get
the constraint re-enabled), and the merge will be happy.

b) use a NON-UNIQUE index (as demonstrated below).  This will work since the
primary key index need not be unique.

ops$tkyte@ORA920> create table sales_item as select rownum seq_id, 1 data from
dual;

Table created.

ops$tkyte@ORA920> create index sales_item_pk on sales_item(seq_id);
Index created.

ops$tkyte@ORA920> alter table sales_item add constraint sales_item_pk primary
key(seq_id);
Table altered.

ops$tkyte@ORA920> create table si_load as select -rownum seq_id, 1 data from
dual;
Table created.

ops$tkyte@ORA920> alter index sales_item_pk unusable;
Index altered.

ops$tkyte@ORA920> alter table sales_item disable primary key keep index;
Table altered.

ops$tkyte@ORA920> alter session set skip_unusable_indexes = true;
Session altered.

ops$tkyte@ORA920> MERGE
  2   INTO sales_item si
  3  USING si_load sl
  4     ON ( si.seq_id = sl.seq_id)
  5   WHEN MATCHED THEN
  6        UPDATE SET si.data = sl.data
  7   WHEN NOT MATCHED THEN
  8        INSERT (si.seq_id,si.data) values (sl.seq_id,sl.data);

1 row merged.
Review & Followup
Rating: 5
Most curious February 05, 2003
Reviewer: Andrew from Troy MI, USA
It is most curious that oracle still enforces the uniqueness required of the PK
even though the PK is disabled and the supporting index is unusable.

Oh well, thanks for the help.

Rating: 5
Problem with UNUSABLE August 15, 2003
Reviewer: Vera from D.C.
Hi,Tom,
I have 8.1.7.4.1.
In my package, before large insert into table from remote DB, using EXECUTE
IMMEDIATE I altered all its non-unique indexes to UNUSABLE, altered
session(SKIP_UNUSABLE_INDEXES=TRUE). And then issued INSERT/SELECT. My proc ran
forever and than I got the following error data in me log table:

ERROR_LOCATION    ERROR_MESSAGE     ERROR_TEXT
extr_customer    ORA-00926: missing VALUES keyword  ALTER INDEX CP_CASE_ID
UNUSABLE
extr_customer    ORA-00926: missing VALUES keyword  ALTER INDEX CP_CASE_CUST
UNUSABLE
extr_customer    ORA-00926: missing VALUES keyword  ALTER INDEX CP_FAMILY_CUST
UNUSABLE
extr_customer    ORA-00926: missing VALUES keyword  ALTER INDEX CP_CUST_ID
UNUSABLE

When I did exactly the same things in the same sequential order from SQL*PLUS
exerything went fine and fast.
I think this is werid. What do you think?

Thank you

Followup:
I think one would need to see some code (terse code, small) that makes this
happen.

sounds like you missed something somewhere.  seems the alter index statements
were part of an insert??

Rating: 1
Tom please never mind!!! August 15, 2003
Reviewer: Vera

I am an idiot. Passed wrong string!

August 16, 2003 Brij -- Thanks for the question regarding "SEQUENCES", version 8i
You Asked
Sir,
I have Oracle 8i installed on WIN NT platform.Here for some
web application am generating a unique number through sequence
and this number we are incrementing by one with no cache option.
So every time this sequence number is supposed to increment by
one.But what's happening is that this number at least jump once
in a day by two or more numbers.In that case we always have to
reset the sequence number.
Please suggest the solution.
and we said...
Sequences do not generate "gap free" series of numbers.  The
only way to do that is in a highly serialized, blocking fashion
(hence if you really truely believe you need it -- you might
consider rethinking your need for it).

What is happening in your case is some session has issued:

  select seq.nextval from dual;

and then rolled back (or otherwise aborted the transaction and
never inserted this value into a table permanently).  The
sequence values are not "given back" when you roll back -- they
are generated and then gone forever.

Again -- if you really need a gap free sequence of numbers, the
only way to do that in any system is in a serialized, blocking
fashion.  You'll have but one transaction at a time taking
place.  I would reconsider the need for such a thing.
Review & Followup
Rating: 3
Reset Sequence? November 27, 2002
Reviewer: Ashish pandit from INDIA
Why not Oracle come up with in built reset_sequence procedure. Is there in RDBMS
constraints for doing so.

Followup:
No, it just isn't something that there is widespread need for.

Give me the business case whereby in a production system you are reseting a
sequence on an ongoing basis?

Rating: 4
sequence jump by 20 April 21, 2003
Reviewer: A reader from de
Hi Tom:

thank you for your help always.
I created a table with id column, and a sequenct seq_id following as:

CREATE sequence seq_id
INCREMENT BY 1
START WITH 1
MAXVALUE 1.0E28
NOCYCLE
/

the sequence is called through a procedure, but how i got the sequence increased
by 20,

1
21
41
42
61
81
82
101
121
141
142
161
181
201
202
203
204
205
206
221
222
223
241
242
243
244
261
262
281
282
301
302
303
304
321
322
323
324
341
361
381
382
383
384
385
386
401
402
403
404
405
406
407
408
409
410
411
412
421
441
442
461
462
463
481
501
502
503
521
541
542
543
544
545
546
547
548
561
581
582
583
601
602
621
641
642
643
644
645
646

thank you,
an
(our oracle db in 8.1.7)

Followup:
very small shared pool?

but even so -- do the math and you tell me  -- if you generate 1,000 per second,
every second -- how many years would it take to exhaust the sequence if it skips
20 EVERY time?

Rating: 4
Why your sequence sometimes jumps by 20 April 22, 2003
Reviewer: Dan Kefford from New Haven, CT
Straight from the Oracle8i SQL reference guide for the CREATE SEQUENCE
statement:

<cite>
Sequence Parameters

.
.
.

CACHE
integer

Specify how many values of the sequence Oracle preallocates and keeps in memory
for faster access. This integer value can have 28 or fewer digits. The minimum
value for this parameter is 2. For sequences that cycle, this value must be less
than the number of values in the cycle. You cannot cache more values than will
fit in a given cycle of sequence numbers. Therefore, the maximum value allowed
for CACHE must be less than the value determined by the following formula:

(CEIL (MAXVALUE - MINVALUE)) / ABS (INCREMENT)

If a system failure occurs, all cached sequence values that have not been used
in committed DML statements are lost. The potential number of lost values is
equal to the value of the CACHE parameter.

NOCACHE

Specify NOCACHE to indicate that values of the sequence are not preallocated.

If you omit both CACHE and NOCACHE, Oracle caches 20 sequence numbers by
default.
</cite>

That last bit is the critical detail.

Followup:
and setting nocache is like setting:

fast = false
slow = true
I_want_to_be_as_inefficient_as_possible = Please

Rating: 4
NOCACHE April 23, 2003
Reviewer: Ed from Cheshire, UK
I thought I'd do a little benchmarking (makes a change from you doing it :-))

seqtest.sql:
-----------------------------------------------------------
CREATE SEQUENCE nocacheseq NOCACHE;
CREATE SEQUENCE cache10seq CACHE 10;
CREATE SEQUENCE cache100seq CACHE 100;
CREATE SEQUENCE cache1000seq CACHE 1000;
CREATE SEQUENCE cache10000seq CACHE 10000;

DECLARE
n number;
x number;
BEGIN
n := dbms_utility.get_time;
FOR i IN 1..100000 LOOP
  SELECT nocacheseq.nextval INTO x FROM DUAL;
END LOOP;
dbms_output.put_line( 'NOCACHE : '||((dbms_utility.get_time-n)/100)||'
seconds....' );
n := dbms_utility.get_time;
FOR i IN 1..100000 LOOP
  SELECT cache10seq.nextval INTO x FROM DUAL;
END LOOP;
dbms_output.put_line( 'CACHE 10 : '||((dbms_utility.get_time-n)/100)||'
seconds....' );
n := dbms_utility.get_time;
FOR i IN 1..100000 LOOP
  SELECT cache100seq.nextval INTO x FROM DUAL;
END LOOP;
dbms_output.put_line( 'CACHE 100 : '||((dbms_utility.get_time-n)/100)||'
seconds....' );
FOR i IN 1..100000 LOOP
  SELECT cache1000seq.nextval INTO x FROM DUAL;
END LOOP;
dbms_output.put_line( 'CACHE 1000 : '||((dbms_utility.get_time-n)/100)||'
seconds....' );
FOR i IN 1..100000 LOOP
  SELECT cache10000seq.nextval INTO x FROM DUAL;
END LOOP;
dbms_output.put_line( 'CACHE 10000 : '||((dbms_utility.get_time-n)/100)||'
seconds....' );
END;
/

DROP SEQUENCE nocacheseq;
DROP SEQUENCE cache10seq;
DROP SEQUENCE cache100seq;
DROP SEQUENCE cache1000seq;
DROP SEQUENCE cache10000seq;
-----------------------------------------------------------
SQL> @seqtest

Sequence created.

Sequence created.

Sequence created.

Sequence created.

Sequence created.

NOCACHE : 82.25 seconds....
CACHE 10 : 15.47 seconds....
CACHE 100 : 8.05 seconds....
CACHE 1000 : 15.32 seconds....
CACHE 10000 : 22.54 seconds....

PL/SQL procedure successfully completed.

Sequence dropped.

Sequence dropped.

Sequence dropped.

Sequence dropped.

Sequence dropped.

Interesting that CACHE <largevalue> doesn't necessarily equate to <fast>.  Seems
like Oracle's default of 20 isn't too bad.

However, in the system I once developed I'll admit to creating a NOCAHE sequence
...

It was a call tracking system, where calls are only ever entered manually, hence
the overhead in performance is not noticeable to the user.  Also, the sequence
is used to refer to each call, so if the call number is 2671 (for example) this
is liable to be less prone to error or mistyping than if the number's up to
5645738 (or whatever).  Thirdly, the users can get a general "feel" for how many
calls are coming in by the current call number.  Originally, the users wanted a
guarantee that they'd be no gaps (sigh), but I talked them out of it.  I think
their "business reason" was that "we'd like it".

Ed

Followup:
test is flawed.

measuring elapsed time is excessively misleading, you need to look at lots of
other things... so -- lets see some more information:

first, I see different timing results (single user system, i'm the only CPU user
-- big impact on test)

ops$tkyte@ORA920LAP> drop sequence s1;

Sequence dropped.

ops$tkyte@ORA920LAP> drop sequence s2;

Sequence dropped.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create sequence s1 cache 20;

Sequence created.

ops$tkyte@ORA920LAP> create sequence s2 cache 1000000;

Sequence created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> delete from run_stats;

0 rows deleted.

ops$tkyte@ORA920LAP> commit;

Commit complete.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> declare
  2      l_start number;
  3      l_run1  number;
  4      l_run2  number;
  5      l_x     number;
  6  begin
  7      insert into run_stats
  8      select 'before', stats.* from stats;
  9
 10      l_start := dbms_utility.get_time;
 11      for i in 1 .. 100000
 12      loop
 13          select s1.nextval into l_x from dual;
 14      end loop;
 15      l_run1 := (dbms_utility.get_time-l_start);
 16      dbms_output.put_line( l_run1 || ' hsecs' );
 17
 18      insert into run_stats
 19      select 'after 1', stats.* from stats;
 20      l_start := dbms_utility.get_time;
 21      for i in 1 .. 100000
 22      loop
 23          select s2.nextval into l_x from dual;
 24      end loop;
 25      l_run2 := (dbms_utility.get_time-l_start);
 26      dbms_output.put_line( l_run2 || ' hsecs' );
 27      dbms_output.put_line
 28      ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) ||
 29        '% of the time' );
 30
 31      insert into run_stats
 32      select 'after 2', stats.* from stats;
 33  end;
 34  /
1323 hsecs
898 hsecs
run 1 ran in 147.33% of the time

PL/SQL procedure successfully completed.

Now, looking at system statistics and latching:

ops$tkyte@ORA920LAP> select a.name, b.value-a.value run1,
  2         c.value-b.value run2,
  3         ( (c.value-b.value)-(b.value-a.value)) diff
  4    from run_stats a, run_stats b, run_stats c
  5   where a.name = b.name
  6     and b.name = c.name
  7     and a.runid = 'before'
  8     and b.runid = 'after 1'
  9     and c.runid = 'after 2'
 10     and (c.value-a.value) > 0
 11     and (c.value-b.value) <> (b.value-a.value)
 12   order by abs( (c.value-b.value)-(b.value-a.value))
 13  /

STAT...session logical reads       320207     300046     -20161
LATCH.shared pool                  225174     200165     -25009
LATCH.row cache objects             30048         46     -30002
LATCH.enqueue hash chains           30037         23     -30014
LATCH.library cache pin            440136     400120     -40016
LATCH.library cache                560326     500306     -60020
STAT...session pga memory max       65536          0     -65536
LATCH.cache buffers chains         666008     600232     -65776
STAT...recursive calls             170039     100045     -69994
STAT...redo size                  4367648      28328   -4339320

78 rows selected.

the second one generates lots less redo (alot), does less work, latches a
whole lot less (remember a latch is a lock, locks are serialization devices,
serialization devices inhibit scalability....)

So, in a multi-user environment or large data load -- having higher cache values
will be more scalable, more performant.

Hopefully a system would not require a user to type in such a number?  I mean,
where would the number come from in the first place?  these surrogate keys are
used to tie the data together but are rarely manually entered

Rating: 5
Very interesting April 23, 2003
Reviewer: Ed from Cheshire, UK
I'll certainly take that into account when developing a single-user,
large-data-load system I have coming up.  I'll benchmark a more realistic
scenario then.

So what does my flawed (and hastily created) test indicate?  I understand that
measuring elapsed times is very dodgy, but of course, in the real world the
actual elapsed time is precisely what you're interested in.

<quote> Hopefully a system would not require a user to type in such a number?
</quote>

Well, it might not be the system requiring the user to do something - rather
that the user required that the system provided this facility (as in the real
example that I worked on).  Manager calls to worker "Go and have a look at call
5438, please", for example.

Ed

Rating: 5
D'oh - stupid me April 23, 2003
Reviewer: A reader
Rereading me own test, it indicates that I didn't reset my time variable, n.

Sorry for wasting your time

Ed

Followup:
doh!  didn't even see that one ;)

Rating: 5
nocache August 07, 2003
Reviewer: A reader
Tom
Agreed on nocache - What is the practical cache size
you recommend? You have benchmarked one with 1000000
as cache size - is that reasonable in a runtime environment?

Thanx!

Followup:
sure, as long as you don't mind "losing" 1,000,000 sequences in the event of a
shutdown abort/crash.

don't worry about running out of sequences.  Even if you shutdown abort once a
second, it would take:

ops$tkyte@ORA920> select (rpad( '9',27,'9' ) / 1000000)/60/60/24/366 from dual;

(RPAD('9',27,'9')/1000000)/60/60/24/366
---------------------------------------
                             3.1623E+13

that many years to run out.

Rating: 5
thanx Tom! August 15, 2003
Reviewer: A reader
"don't worry about running out of sequences. "

But do we have to be concerned about memory consumption
in SGA in any way since sequences I think get cached there?
Probably not - since they are just numbers - and each of them takes a very small
number of bytes?

Followup:
correct, a sequence is a small thing.  my comment was about running out of
values in a sequence -- even with really really large gaps.  you won't run out
of values.

August 16, 2003 Jagjeet -- Thanks for the question regarding "Localy Manage Tablespace", version 9.2.0
You Asked
hello Sir ..

can u explain this .

Oracle migrates the data for the entire row to a new data block,assuming the
entire
row can fit in a new block.Oracle preserves the original row piece of a migrated

row to point to the new block containing the migrated row.The rowid of a
migrated
row does not change.

[ when full row is migrating .. then why oracle does not change its rowid ? ]

-----------------
[ What is the Reason Behind this. Pls. Explain ]

When the SYSTEM tablespace is locally managed, you must define a default
temporary
tablespace when creating a database. A locally managed SYSTEM tablespace
cannot be used for default temporary storage.
-----------------
In a database with a locally managed SYSTEM tablespace,dictionary tablespaces
cannot be created.It is possible to plug in a dictionary managed tablespace
using the
transportable feature, but it cannot be made writable.
-----------------
and we said...
it is all about performance and stability.

migration of a row -- if it changed the rowid -- would have to revisit ALL
indexes and modify them as well.  since rowid is part of the "key" of the index
sometimes this would be doubly expensive.

many applications use the rowid during transaction processing (eg: read rowid
out during select, lock row via rowid (fast), update row by rowid (fast)).  if
rowid was wholly unstable -- this would not work.

the last part is a "statement", i see no question there.
Review & Followup
Rating: 4
August 14, 2003
Reviewer: Vipin from Newyork
Hi tom,

Isn't it true that it was mandatory that the SYSTEM tablespace be dictionary
managed until 8.1.7.

search for "You cannot create a locally managed system tablespace" in the
following link
http://otn.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76956/tspaces.htm#9633
Is it that in only after 9i this can be locally managed?
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/c04space.htm#1003
Please confirm

Followup:
correct

Rating: 4
Re: August 16, 2003
Reviewer: Jagjeet Singh from india
Ques 1.

" When the SYSTEM tablespace is locally managed, you must define a default
temporary tablespace when creating a database. A locally managed SYSTEM
tablespace cannot be used for default temporary storage."

 why a localy manage system tablespace cannot be used for
 default temp tablespace ?

Ques. 2
-----------------
In a database with a locally managed SYSTEM tablespace,dictionary tablespaces
cannot be created.It is possible to plug in a dictionary managed tablespace
using the transportable feature, but it cannot be made writable.

sir, i want to know what is the Reason Behind this ?

Followup:
q1) because

a) we don't want you to
b) a lmt used for temp cannot have permanent objects in it
c) system should never be used for temp anyhow

q2) because

a) it is not supported, uet$ and fet$ are not to be used in a database with only
LMTs and to have a dictionary managed tablespace, uet$ and fet$ are mandatory.

August 16, 2003 Ahlam -- Thanks for the question regarding "Oracle wait events", version 734 through 816
You Asked
One of our systems has serious problems on certain days.   I am
looking at some oracle views (v$system_event, v$session_event &
v$session_wait) to make sense of the info gathered.

Top waits now are:
   db_file_sequential_read
   log_file_parallel_write
   io done.

I looked in the oracle8 Ref for a list of Oracle Wait Events.  I
found it, but the explanation of P1-P3 in v$session_wait is so
weak.  Do you know of a better place to look?

Thanks in advance.
and we said...
For the db file sequential read, see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:957829973821
Log file parallel write is writing redo records to the redo log
files from the log buffer.  The wait time is the wait for LGWR
to complete multiple log file writes. Even though they are
written in parallel, one needs to wait for the last I/O to be on
disk before the parallel write is complete.

Parameters:
               P1 - No. of log members.
               P2 - No. of log blocks.
               P3 - No. of i/o requests

        files   Number of files written to.
        blocks  Number of blocks to be written.
        requests Number of I/O requests.

P2 blocks will be written to each log member, in P3 number of
requests. i.e. If there were 2 requests, there would be two
physical writes per log member. The no. of log blocks will be
divided up between the requests.

IO done, this event is used by the IO slave processes
(oracleInnn) when waiting for slave IO processes to complete a
request    or by idle slave IO processes waiting for work.

This wait event is used in both IDLE and NON-IDLE conditions
so can be very misleading.
Review & Followup
Rating: 4
Interesting.. October 16, 2001
Reviewer: Doug Cowles from CT, USA
Very helpful.  One more question though.. can you explain the meaning of idle
and non-idle conditions?

Followup:
It basically means that it can happen as the result of an idle wait (which is
"innocent") or as the result of a non-idle wait (we were REALLY truly waiting
for this to finish before doing something else)

For example:

ops$tkyte@ORA717DEV.US.ORACLE.COM> select event, time_waited
  2  from v$session_event
  3  where sid = ( select sid from v$mystat where rownum = 1 )
  4  and event = 'SQL*Net message from client'
  5  /

EVENT                                                            TIME_WAITED
---------------------------------------------------------------- -----------
SQL*Net message from client                                              263

ops$tkyte@ORA717DEV.US.ORACLE.COM> rem wait a bit....
ops$tkyte@ORA717DEV.US.ORACLE.COM> @a
ops$tkyte@ORA717DEV.US.ORACLE.COM> select event, time_waited
  2  from v$session_event
  3  where sid = ( select sid from v$mystat where rownum = 1 )
  4  and event = 'SQL*Net message from client'
  5  /

EVENT                                                            TIME_WAITED
---------------------------------------------------------------- -----------
SQL*Net message from client                                              925

Here the sql*net message from client is a true IDLE wait event.  As we sit in
sqlplus, it just keeps on ticking up.....  It is showing that we are idle

However, its not ALWAYS an idle wait event.  Say you are running a pro*c app you
wrote, its a batch program.  Its taking too long to run.  You look and see that
the sql*net message from client is HUGE -- guess what that means.  In this case,
it means the database is not the bottleneck, its spent alot of time WAITING for
you to tell it to do something.  In this case, this is a non-idle condition, we
were not idle (as an entire process -- client+database)....

Rating: 4
Reader October 16, 2001
Reviewer: Reader from USA
Tom,

There is only one LGWR process right? How does it
write in parallel to redo log members

What constitutes the # of i/o waits (P3 value)

Thanks

Followup:
There can be IO slaves in the system and most all systems support async IO (fire
the write off but don't wait for it so you can fire that next one off -- then
wait for them all to finish)..

P3 is the number of requests - not waits.

Rating: 3
Reader October 17, 2001
Reviewer: Reader from USA
I understand async i/o. In some some systems ( ex:Silicon Graphics), even
disk_asynch_io = true, does not enable
asynch i/o because of some hardware requirements.

In such cases, does Oracle write to log members alternatively in a round-robbin
fashion. If so how big a
Oracle buffer is used for each batch of logfile parallel
write. ( I've read from an article that ARC process reads
from the redo logs alternatively buffer = archive_log_buffer_size).

Again could you let us know what constitutes *no. of io requests*

Thanks

Followup:
the number of IO requests is how many requests we made to perform IO.  How many
writes we did to write the data.  If you have 1meg of data to write, you
typically don't do it in one big call.

As I said, we can use IO slaves to simulate async IO or if there is no async io,
we would do it sequentially.  We dump the log buffer (happens upon commit, every
3 seconds, when 1/3 full, when 1meg full -- whichever comes first).  It is a
function of the size of the log buffer which you control.

Rating: 4
Follow up on IDLE and Non-idle conditions October 17, 2001
Reviewer: Doug from CT, USA
Just to follow up on idle and non-idle conditions.  I understand the difference,
however you said that the log posting waits can include both idle and non-idle
conditions and can therefore be misleading.  Does this mean that I'm going to
get log file sync and log parallel write waits even if there is nothing wrong
and it is just waiting for a checkpoint interval for example?  Log file sync and
log file parallel wait are showing up as the #2 and #3 wait events in one system
I watch over which I think is peculiar.   I usually seem them in the top 10 or
so but never right at the top.

Followup:
IO done is the idle/non-idle event.  Log file Parallel Write -- not an
idle/non-idle event.

Log stuff -- real time waits.

IO done, maybe real time, maybe not.

Rating: 4
What about PX DEQ waits October 03, 2002
Reviewer: DGR from TN
I can't find ANY documentation on these, but they keep showing up on my reports
and people are asking me what can be done about them.

I know some of these must be meaningless (when looking for problems), but can
all of them be ignored?

The two I see most often are Execute Reply and Credit:send blkd.

I realize that 2 parallel operations can take different amounts of time to
finish their tasks and that some waits cannot be avoided (or even considered
bad), I just don't know which ones.

I've seen lot's of questions on the internet about these, but no answers.  Can
you tell if any of the PX Deq waits are interesting or significant/correctable?

Thanks.

Followup:
search for

px deq

on this site.  Ignore this article, read the other (currently) two.

Rating: 4
SQL*Net message from client waited time May 07, 2003
Reviewer: vinodhps from India,chennai
Hi... Tom,
In our production system , while executing one procedure it take very long time
to execute it when i run the query individualy it run fast.. so i traced the
session and found that my SQL*Net message from client  waited that much time...
Here what will the exact reason for this Tom.

SQL> select * from v$session_event where sid=88;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.03          0          0          0           0
Execute  18480     36.69      88.65          0          0          0           0
Fetch    18480      8.77      20.28          0      73920          0       18480
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    36961     45.46     108.96          0      73920          0       18480

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 324     (recursive depth: 1)

     SID EVENT                          TOTAL_WAITS TOTAL_TIMEOUTS TIME_WAITED
AVERAGE_WAIT
--------- ------------------------------ ----------- -------------- -----------
------------
 MAX_WAIT
---------
        3

       88 SQL*Net more data to client              1              0           0
          0
        0

       88 SQL*Net message from client            304              0      680756
  2239.3289
   382821

SQL> select * from v$session_wait where sid=88;

      SID      SEQ# EVENT
--------- --------- ------------------------------
P1TEXT                                                                  P1 P1RAW
---------------------------------------------------------------- ---------
--------
P2TEXT                                                                  P2 P2RAW
---------------------------------------------------------------- ---------
--------
P3TEXT                                                                  P3 P3RAW
   WAIT_TIME
---------------------------------------------------------------- ---------
-------- ---------
SECONDS_IN_WAIT STATE
--------------- -------------------
       88      2132 SQL*Net message from client
driver id                                                        675562835
28444553
ytes                                                                   1
00000001
                                                                         0 00
           0
            136 WAITING

Thanks in advance and Your doing  great job tom........

Followup:
sqlnet message from client means .....

we were sitting idle waiting for the client to bug us to do something.  meaning,
the slowdown -- well, it is in the client, it is not asking us to do any work.

now, looking at that tkprof -- that "whatever" statement you have the report for
runs instantly -- less then 5/1000ths of a second by the wall clock.

However, you do it lots and anytime you do something lots, it ADDS UP.

I'm always suspicious when I see statements being executed 10's of thousands of
times.  It leads me to believe the coder only knew how to do row at a time
processing, and doesn't do any bulk SET oriented operations.

That is like the difference between going to the library and asking for War and
Peace a page at a time -- making the librarian run back and forth and back and
forth

versus

Asking for War and Peace -- one bulk operation....

I think the problem lies in the logic of the client.

Rating: 5
SQL*Net message from client waited time May 07, 2003
Reviewer: vinodhps from India , Chennai
Thanks for your immediate responce tom, well my query goes like this below.

Can you please tell me with an example about can librarian run page for "WAR" &
"PEACE"

SELECT
        SUBSTR (a.prd_cd,1,1) PRD_GRP,
        SUBSTR (a.prd_cd, 2, 3) variety_cd,
        SUBSTR (a.prd_cd, 5, 1) std_ret_ind,
        TO_NUMBER (SUBSTR (a.prd_cd,6,3)) gsm,
        TO_NUMBER (SUBSTR (a.prd_cd, 9, 4)) / 10 width,
        TO_NUMBER (SUBSTR (a.prd_cd, 13, 4)) / 10 length,a.mchn_no,
        (SUM(NVL(a.st,0)) - NVL(b.pend_qty,0)) qty,
        DECODE( SUBSTR(a.prd_cd,2,3) , c.variety_cd, c.dscnt_cd,
            DECODE(c.variety_cd, 'ALL', c.dscnt_cd,NULL)) dscnt_cd,
        DECODE( SUBSTR(a.prd_cd,2,3) , c.variety_cd, c.oth_dscnt,
            DECODE(c.variety_cd, 'ALL', c.oth_dscnt,NULL)) oth_dscnt,
        DECODE( SUBSTR(a.prd_cd,2,3) , c.variety_cd, c.remarks,
            DECODE(c.variety_cd, 'ALL', c.remarks,NULL)) remarks
       FROM  (
    SELECT prd_cd, mchn_no,
        Func_mks_gdw_ret_stock (prd_cd,DECODE ( MCHN_NO , 1, 1, 2,2),
        DECODE ( MCHN_NO  , 1, st_date, 2, st_date), 1) st
    FROM     (
        SELECT  prd_cd, mchn_no, MAX (stock_date) st_date
        FROM    t_gdw_daily_stock
        WHERE   stock_date >= '1-APR-2001'
        AND      (DECODE (SUBSTR (prd_cd, 13, 4), '0000', 'R', 'S') = 'B'
            OR   'B' = 'B')
        AND      (SUBSTR(prd_cd,2,3) = 'CRW' OR 'CRW' = 'ALL')
        AND      ( (TO_NUMBER(SUBSTR(prd_cd,6,3)) BETWEEN 999 AND 999)
            OR     (999 = 999 AND 999 = 999 ))
        AND      ( (SUBSTR(prd_cd,5,1)= 'B' ) OR ('B' = 'B'))
        AND   (mchn_no = 0    OR 0 = 0)
        GROUP BY prd_cd,mchn_no
        )
        ) a,
        (
        SELECT  prd_cd, SUM (NVL(accpt_qty,0) - NVL (dsp_qty, 0)) pend_qty
        FROM t_ord_prd_dtls
        WHERE  ROWID IN
           (
           SELECT    /*+ index_ffs(T_ORD_PRD_DTLS CS_ORD_PRD_DTL_PK) */ rowid
                  FROM    t_ord_prd_dtls
                  WHERE   prd_status = 'N'
           AND   (accpt_qty - NVL(dsp_qty,0)) >= .2
                  AND   (DECODE (SUBSTR (prd_cd, 13, 4), '0000', 'R', 'S') = 'B'
OR 'B'= 'B')
           AND   (SUBSTR(prd_cd,2,3) = 'CRW' OR 'CRW' = 'ALL')
             AND   ( (TO_NUMBER(SUBSTR(prd_cd,6,3)) BETWEEN 999 AND 999)
            OR (999 = 999 AND 999  = 999 ))
           AND   ( (SUBSTR(prd_cd,5,1)= 'B'  ) OR ('B' ='B'))
           AND    (mchn_no = 0 OR 0 = 0)
           )
          GROUP BY prd_cd
        ) b,
            (
        SELECT a.mchn_no, a.variety_cd, a.from_gsm, a.to_gsm, a.from_width,
              a.to_width, a.from_length, a.to_length, a.dscnt_cd,
             a.oth_dscnt, a.std_ret_ind, a.remarks, b.amt
        FROM     t_mks_stock_lot_mst a,
             t_mks_gen_dscnt_mst b
        WHERE  a.DSCNT_CD = b.DSCNT_CD
        AND    TRUNC(SYSDATE) BETWEEN a.FROM_DATE AND a.TO_DATE
        ) c
    WHERE a.prd_cd = b.prd_cd
       AND    (( TO_NUMBER(SUBSTR(a.prd_cd,7,2)) BETWEEN c.from_gsm AND
c.to_gsm)
           OR (TO_NUMBER(SUBSTR(a.prd_cd,7,2)) =
TO_NUMBER(SUBSTR(a.prd_cd,7,2))))
       AND      (( TO_NUMBER(SUBSTR(a.prd_cd,9,4))/10 BETWEEN c.from_width AND
c.to_width )
           OR ( TO_NUMBER(SUBSTR(a.prd_cd,9,4))/10 =
TO_NUMBER(SUBSTR(a.prd_cd,9,4))/10) )
       AND      ((TO_NUMBER(SUBSTR(a.prd_cd,13,4))/10 BETWEEN c.from_length AND
c.to_length)
           OR ( TO_NUMBER(SUBSTR(a.prd_cd,13,4))/10 =
TO_NUMBER(SUBSTR(a.prd_cd,13,4))/10))
      AND      (SUBSTR(a.prd_cd,2,3)     = c.variety_cd
        OR  'ALL' = c.variety_cd
        OR SUBSTR(a.prd_cd,2,3) =SUBSTR(a.prd_cd,2,3))
    AND      (SUBSTR(a.prd_cd,5,1)     = c.std_ret_ind
        OR 'B' = c.std_ret_ind
        OR SUBSTR(a.prd_cd,5,1) = SUBSTR(a.prd_cd,5,1))
    AND    (a.mchn_no = c.mchn_no
        OR c.mchn_no = 0
        OR a.mchn_no = a.mchn_no )
    GROUP BY
        SUBSTR (a.prd_cd,1,1) ,
        SUBSTR (a.prd_cd,2,3) ,
        SUBSTR (a.prd_cd,5,1) ,
        TO_NUMBER (SUBSTR (a.prd_cd,6,3)) ,
        TO_NUMBER (SUBSTR (a.prd_cd, 9, 4)) / 10 ,
        TO_NUMBER (SUBSTR (a.prd_cd, 13, 4)) / 10 , a.mchn_no, B.PEND_QTY,
        DECODE( SUBSTR(a.prd_cd,2,3) , c.variety_cd, c.dscnt_cd,
            DECODE(c.variety_cd, 'ALL', c.dscnt_cd,NULL)) ,
        DECODE( SUBSTR(a.prd_cd,2,3) , c.variety_cd, c.oth_dscnt,
            DECODE(c.variety_cd, 'ALL', c.oth_dscnt,NULL)) ,
        DECODE( SUBSTR(a.prd_cd,2,3) , c.variety_cd, c.remarks,
            DECODE(c.variety_cd, 'ALL', c.remarks,NULL))
       HAVING (SUM(NVL(a.st,0)) - NVL(b.pend_qty,0)) > 0

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.04          0          0          0           0
Execute      1      0.00       0.02          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.06          0          0          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 324
********************************************************************************

SELECT (NVL(OPNG_STOCK,0) + NVL(RCPT_STOCK,0)  - NVL(ISS_STOCK,0)  -
  NVL(RET_STOCK,0) )
FROM
 T_GDW_DAILY_STOCK  WHERE STOCK_DATE = :b1  AND PRD_CD = :b2  AND MCHN_NO =
  :b3

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.03          0          0          0           0
Execute  18480     36.69      88.65          0          0          0           0
Fetch    18480      8.77      20.28          0      73920          0       18480
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    36961     45.46     108.96          0      73920          0       18480

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 324     (recursive depth: 1)

Thanks in Advance Tom,

Followup:
look at your logic

try to figure out "well, hey, why do I run this query 18,480 times.  PERHAPS I
am missing an opportunity to join"

that is, instead of codeing like this:

  for x in ( query1 )
  loop
       run query2 with inputs from query1
       process

just do this

   for x in ( query1 joined to query2 )
   loop
       process.

Rating: 4
May 08, 2003
Reviewer: vinodhps from India, Chennai
Hi Tom,

That was great tom, and iam very happy with your responce
This time i got 2 different senario
1)In this table i got 35,000 rows and 316 block appox~  75 avg row len 8KB block
size but here why it takes these much of time..(wait time)

Here using 10046 i traced the session

SELECT SUM (DECODE (doc_type, 'FI', 1, 'BI', 1, 'EI', 1, 'DN', 1, 'IC', 1, -1) *
out_amt),
         SUM (DECODE (doc_type, 'FI', 1, 'BI', 1, 'WI', 1, 0))
      FROM   t_fas_sale_awa
      WHERE  br_code = br_code
      AND    doc_type IN ('FI', 'BI', 'EI', 'DN', 'IC', 'CN', 'ADV', 'UMR')
      AND    doc_no = doc_no
      AND    prd_group_ind = DECODE (:b5, 'B', prd_group_ind, :b5)
      AND    indr_code = DECODE (:b4, 'ALL', indr_code, :b4)
      AND    cons_cd = DECODE (:b3, 'ALL', cons_cd, :b3)
      AND    :b2 - doc_date > :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute   2573      1.03       1.03          0          0          0           0
Fetch     2573    303.79     817.92     183000     975167          0        2573
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     5146    304.82     818.95     183000     975167          0        2573

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 21     (recursive depth: 2)

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  db file sequential read                      1680        0.11         10.21
  db file scattered read                      19032        0.20        503.57
********************************************************************************

2) At same session the overall value make me worry about the procedure

SQL> @t1

Query Plan
--------------------------------------------------------------
SELECT STATEMENT    [CHOOSE] Cost=19 Rows=2332 Bytes=16324
  TABLE ACCESS FULL T_MKS_CONS_MST  [ANALYZED]

/*
select cons_cd, func_fas_chk_custind_avail_bal ('P', 'ALL', cons_cd, sysdate,
                        60, 'Y', 'B') ans
from   t_mks_cons_mst
where  cons_type = 'C';
*/

CREATE OR REPLACE
FUNCTION func_fas_chk_custind_avail_bal (f_prd_group_ind    IN    VARCHAR2,
                         f_indr_code        IN    VARCHAR2,
                     f_cust_code        IN    VARCHAR2,
                     f_as_on_date        IN    DATE,
                     f_days            IN    NUMBER,
                     f_is_2give_net_bal    IN    VARCHAR2,
                     f_chk_what        IN    VARCHAR2)
RETURN NUMBER AS
v_awa_bal        NUMBER := 0;
v_pending_qty_bal    NUMBER := 0;
v_os_inv_cnt        NUMBER := 0;
v_ret_val        NUMBER := 0;
v_limit            NUMBER := 0;
v_days            NUMBER := f_days;
BEGIN
   BEGIN
      IF f_is_2give_net_bal = 'Y' THEN
     IF f_cust_code <> 'ALL' THEN
            SELECT cred_lmt, credit_days
        INTO   v_limit, v_days
        FROM   t_mks_cons_cred_lmt_mst
        WHERE  cons_cd = f_cust_code
        AND    f_as_on_date BETWEEN from_date AND NVL (to_date, f_as_on_date);
         ELSE
            IF f_indr_code <> 'ALL' THEN
               SELECT cred_lmt, credit_days
           INTO   v_limit, v_days
           FROM   t_mks_ind_cred_lmt_mst
           WHERE  ind_cd = f_indr_code
           AND    f_as_on_date BETWEEN from_date AND NVL (to_date,
f_as_on_date);
            END IF;
         END IF;
      END IF;
   EXCEPTION
      WHEN NO_DATA_FOUND THEN
         v_limit := 0; -- temp val so that output will fail
     v_days := 0;
   END;

   IF f_chk_what IN ('A', 'B') THEN
      SELECT SUM (DECODE (doc_type, 'FI', 1, 'BI', 1, 'EI', 1, 'DN', 1, 'IC', 1,
-1) * out_amt),
         SUM (DECODE (doc_type, 'FI', 1, 'BI', 1, 'WI', 1, 0))
      INTO   v_awa_bal, v_os_inv_cnt
      FROM   t_fas_sale_awa
      WHERE  br_code = br_code
      AND    doc_type IN ('FI', 'BI', 'EI', 'DN', 'IC', 'CN', 'ADV', 'UMR')
      AND    doc_no = doc_no
      AND    prd_group_ind = DECODE (f_prd_group_ind, 'B', prd_group_ind,
f_prd_group_ind)
      AND    indr_code = DECODE (f_indr_code, 'ALL', indr_code, f_indr_code)
      AND    cons_cd = DECODE (f_cust_code, 'ALL', cons_cd, f_cust_code)
      AND    f_as_on_date - doc_date > v_days;
   END IF;

   IF f_chk_what IN ('O', 'B') THEN
      IF f_cust_code <> 'ALL' THEN
         SELECT NVL (pending_order_value, 0)
     INTO   v_pending_qty_bal
     FROM   t_mks_cons_mst
     WHERE  cons_cd = f_cust_code;
      ELSE
         IF f_indr_code <> 'ALL' THEN
            SELECT NVL (pending_order_value, 0)
        INTO   v_pending_qty_bal
        FROM   t_mks_ind_mst
        WHERE  ind_cd = f_indr_code;
     END IF;
      END IF;
   END IF;

   v_ret_val := v_awa_bal + v_pending_qty_bal;

   v_ret_val := v_ret_val - v_limit;

   IF v_ret_val > 0 AND v_os_inv_cnt > 0 AND f_cust_code <> 'ALL' THEN
      v_ret_val := v_ret_val * -1;
   END IF;

   RETURN (v_ret_val);
END func_fas_chk_custind_avail_bal;
/

Followup:
well that is about as ugly as it gets.  sorry -- you'll want to relook at the
logic, see if you can tighten it up, see if you can move things around.  It is a
bummer when the data you need is strewn throughout your database sort of all
over the place.  Makes it really hard to piece back together.

Rating: 4
"WAR" & "PEACE" May 08, 2003
Reviewer: vinodhps from India, chennai
Hi tom ,
With Your Referance for

just do this

   for x in ( query1 joined to query2 )
   loop
       process.

Actually i would like to tell what we are doing is..

we got 6696 consinee for each person we are doing that process how do i join
query one with query two.

so do i have to process 6696 times ...

can you clear my doubt Tom..

Thanks In Advance.

Rating: 5
SQL*Net message from Client - more on August 16, 2003
Reviewer: A reader
Hi Tom,
I run a query from SQL*Plus (connected directly - no connect string passed). I
have a very unpredictable and high wait against SQL*Net message from client,
when I see the SQL Trace. Why is that when a query is submitted a non-idle event
SQL*Net message from client kicks into? Please tell me more on this event. This
is the only event that is making the difference to the query from a minute to a
sec.
Thanks

Followup:
need

a) example
b) how you are computing waits
c) versions
d) platforms
e) lots of stuff so I could make an educated guess...

August 17, 2003 neeti -- Thanks for the question regarding "Strange sql....", version 8.1.7
You Asked
  Hi Tom,

        How are you...??

        Last week I stumbled upon a strange sql.

        select rowid,cust_id,cust_name
          from ( select cust_id,cust_name
                   from customer );

       The sql worked file but I could'nt figure out when the inline
 sql has no rowid then from where the outer sql is getting the rowid.

    Please explain.

Regards,
Neeti
and we said...
ROWID is a psuedo column that is pulled whenever it can be pulled.

consider if you:

create view v as select ename, empno from emp;

you can still

select rowid, rownum, ename, empno from emp;

the rowid is implicit when possible.  if you added an aggregate or something to
the inline view, the rowid would "poof" away.

ops$tkyte@ORA920> select rowid, empno
  2  from ( select empno from emp );

ROWID                   EMPNO
------------------ ----------
AAANdhAAGAAAABKAAA       7369
AAANdhAAGAAAABKAAB       7499
AAANdhAAGAAAABKAAC       7521
AAANdhAAGAAAABKAAD       7566
AAANdhAAGAAAABKAAE       7654
AAANdhAAGAAAABKAAF       7698
AAANdhAAGAAAABKAAG       7782
AAANdhAAGAAAABKAAH       7788
AAANdhAAGAAAABKAAI       7839
AAANdhAAGAAAABKAAJ       7844
AAANdhAAGAAAABKAAK       7876
AAANdhAAGAAAABKAAL       7900
AAANdhAAGAAAABKAAM       7902
AAANdhAAGAAAABKAAN       7934

14 rows selected.

ops$tkyte@ORA920> c/from emp/from emp group by empno
  2* from ( select empno from emp group by empno )
ops$tkyte@ORA920> /
select rowid, empno
       *
ERROR at line 1:
ORA-01446: cannot select ROWID from view with DISTINCT, GROUP BY, etc.
Review & Followup
Rating: 4
Thanks....:-) August 18, 2003
Reviewer: A reader

August 17, 2003 Richard -- Thanks for the question regarding "What is the error ORA-01722", version
You Asked
What is the error ORA-01722
and we said...
ORA-1722 is Invalid number.  We've attempted to either explicity
or implicity convert a character string to a number and it is
failing.

This can happen for a number of reasons.  It generally happens
in SQL only (during a query) not in plsql (plsql throws a
different exception for this error).

You can see this error easily by:

ops$tkyte@8i> select to_number('abc') from dual;
select to_number('abc') from dual
                 *
ERROR at line 1:
ORA-01722: invalid number

This error seems to creep into queries in the strangest ways.  A
change in the order of a predicate can make it come and go --
depending on the order of evaluation in the predicate.  Consider
this example:

ops$tkyte@8i> create table  t ( x int, y varchar2(25) );

Table created.

ops$tkyte@8i>
ops$tkyte@8i> insert into t values ( 1, 'abc' );

1 row created.

ops$tkyte@8i> insert into t values ( 2, '123' );

1 row created.

ops$tkyte@8i>
ops$tkyte@8i>
ops$tkyte@8i> select * from t where y > 100 and x = 2;

         X Y
---------- -------------------------
         2 123

ops$tkyte@8i> select * from t where x = 2 and y > 100;
select * from t where x = 2 and y > 100
                                *
ERROR at line 1:
ORA-01722: invalid number

The first query worked since we *tend* to evaluate queries from
the bottom up.  We evaluated the x=2 part first and never tried
to do 'abc' > 100.  In the second query, the y>100 was evaluated
first.  Y was promoted to a number and then compared to 100.
'abc' could not be converted so ORA-1722.

The only general purpose solution is to always compare like
types to like types.  You should either convert the column Y
entirely to numbers (clean the data) or use a character string
comparision (which changes the meaning of the predicate -- y >
100 is very different from y > '100' )
Review & Followup
Rating: 5
ORa-01722 March 27, 2001
Reviewer: Tom Petrella from Melville, NY
I was getting this error and it was driving me nuts because I know everything
was syntactically correct and there were no invalid numbers. Be rearranging the
order of the where clause i got it to work. Thanks !

Rating: 4
Is there a surefire way to avoid this? July 11, 2002
Reviewer: Adrian from Exeter England
Apart from the obvious method, (i.e. Always compare like data-types), is there a
sure-fire way to avoid this sort of problem happening?

If I have a domain table cg_ref_codes with fields domain, low_value, high_value,
abbreviation, meaning (all varchar2). If further some of my domains contain
purely numeric values. Is there a way that I can not have to worry about which
way my predicates are evaulated. Or will I always have to wrap a to_char()
around my numeric columns?

The reason I ask is becuase I have just had to trouble-shoot this problem for
one of my developers, who is on two weeks leave. His package works fine on the
development box (NT Oracle 8.1.6), but when run on the test/integration machine
(VAX Oracle 8.1.7.2) this error was returned.

Followup:
The only sure fire way to avoid this in pretty much every language is:

compare numbers to numbers,
strings to strings,
dates to dates,
etc to etc....

always avoid the implicit conversion -- don't store numbers in varchar2's (i
know, i know "its a generic model", well, generic models have their limited
advantages -- and they have their very serious drawbacks).

specific code = more reliable code.
generic code = "pretty cool, but will it work" sometimes....

Rating: 5
with CBO your example works December 10, 2002
Reviewer: A reader
Hi

if I analyze the table from your example then the query works. Is this
limitation or behaviour shows only in RBO?

SQL> analyze table t compute statistics;

Table analyzed.

SQL> select * from t where x = 2 and y > 100;

         X Y
---------- -------------------------
         2 123

Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1299201885045
which is the converse of this example.  there rbo "works" and cbo "fails"

but neither "fails" really - the query was wrong to begin with.  assumptions
were made that were not valid -- that there is a defined order of operation in
SQL.

By definition -- there is no defined order!

Rating: 3
ORA-1722 using V$PARAMETER December 10, 2002
Reviewer: Pablo Rovedo from Argentina
I have an interesting example where CBO works but RBO doesn't.

sys@ROP816> ed
Wrote file afiedt.buf

  1  select /*+ RULE */ value
  2  from sys.v_$parameter
  3  where name = 'log_checkpoint_timeout' and
  4* value > 1000
sys@ROP816> /
value > 1000
*
ERROR at line 4:
ORA-01722: invalid number

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   MERGE JOIN
   2    1     FIXED TABLE (FULL) OF 'X$KSPPCV'
   3    1     FILTER
   4    3       SORT (JOIN)
   5    4         FIXED TABLE (FULL) OF 'X$KSPPI'

but if we use CBO the query works

sys@ROP816> ed
Wrote file afiedt.buf

  1  select /*+ FIRST_ROWS */ value
  2  from sys.v_$parameter
  3  where name = 'log_checkpoint_timeout' and
  4* value > 1000
sys@ROP816> /

VALUE
--------------------------------------------------------
1800

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=32 Card=1 Bytes=337)
   1    0   NESTED LOOPS (Cost=32 Card=1 Bytes=337)
   2    1     FIXED TABLE (FULL) OF 'X$KSPPI' (Cost=16 Card=1 Bytes=60)
   3    1     FIXED TABLE (FIXED INDEX #2) OF 'X$KSPPCV' (Cost=16 Card=5
Bytes=1385)

The explanation to this is because of the first plan involves full table scan of
x$ksppcv, computing to_number() function on non-numeric values.

Cheers
Pablo Rovedo

Followup:
My whole point here is simple:  there is no defined order, period.

A simple change in plan will cause it to "fail"

I can show you 1,000 where RBO "works" CBO "fails"
I can show you another 1,000 where CBO "works" RBO "fails"

It is neither CBO or RBO here, it is the decidely undecided way in which queries
are evaluated.

Rating: 2
continuing the same topic. February 24, 2003
Reviewer: Yogesh Bhardwaj from Bangalore, India
hi tom!
in continuation of the disscussion i wanna know why i'm getting the output while
i'm compare different datatypes.
t is the same table(x int,y varchar2(25));

SQL> select * from t where '123'=123;

         X Y
---------- -------------------------
         1 abc
         2 123
in the above query '123' is string and 123 is number. so it should give the
error ora 1722. but it doesn't.

if i'm firing the query like
SQL> select * from t where y=123;
select * from t where y=123
                      *
ERROR at line 1:
ORA-01722: invalid number

Followup:
well, '123' = 123 is the same as 123 = 123 -- the string '123' can and is
converted to a number, the comparision made and "true" is returned.

All rows come out.

But, if you code where 'abc' = 123, well, that'll fail since 'abc' CANNOT be
converted to a number.  So, that query flops over and dies.

convert the NUMBER to a string

select * from t where y = to_char(123);

will work dandy.

Rating: 5
Excellent February 24, 2003
Reviewer: Doug
That sort of thing could drive a DBA/Developer
to drink!

Gotta fly Happy Hour is on

Rating: 5
Another Question Regarding Datatypes and Output August 17, 2003
Reviewer: Deanna from SF
Hi Tom,

What would happen in this scenario...
The developers created the following table:
Table1
Field1 = datatype_name
Field2 = value_data

datatype_name = Numeric or Qualitative
value_data can be
123
+
-

The end user wants to output the data to excel from Oracle such that if the
datatype_name = Numeric the string is converted to a number and if the
datatype_name = Qualitative the string is left as a string.

I tried a decode statement

decode (datatype_name , 'Numeric', to_number(value_data), to_text (value_data))

Didnt work, Oracle errors...

Any suggestions on how to output both numeric and text in the same column, while
avoiding having to convert everything to a string?

Thanks again!

Followup:
A column is EITHER number or string -- not both.

there is the sys.anydata type (search for it here).  but -- will the client
application be ready to handle it.

Rating: 5
August 18, 2003
Reviewer: Helena Marková from Bratislava, Slovakia

Rating: 4
Superb!!! August 18, 2003
Reviewer: A reader

August 18, 2003 Jack -- Thanks for the question regarding "Migration from 8.0.5.1 to 8.1.7.0", version 8.1.7
You Asked
Tom,

i have read through the migration guide for upgrade to 8i from 8.0.5.

There is a lot of stuff there, but i feel i have a pretty good
handle on upgrading. I have already upgraded our development and test
databases to 8i with minimal difficulty.

We had to do a couple of Net8 tweaks in our configuration, but all
appears to be running well ( including recompilation of application
code with lib64 libraries )

We have our production migration coming up very soon. This is a career
moment for me, so my anxiety is pretty high.

Are there any critical points in the migration that i should pay
close attention to ?? Are there any "gotchas" that i should be
weary of that are not covered in the guide ?

Any suggestions you might have would be greatly appreciated.

Thanks,
and we said...
You have done everything correct so far.  You practiced, you've done it more
then once.

I would take your production instance, restore it to test and upgrade it over
and over until you do it with NO difficultly.  So you can say "it will take use
2 hours, 37 minutes and 22 seconds to upgrade" and be dead on (however long it
will take -- you'll know it)

Have a solid fall back plan in case the poop hits the fan.  That is, test your
ability to RECOVER your backups and be back on 8051 instantly.

Also, question mgmt as to why they are jumping from a frying pan into a fire?
why would anyone goto 8174 given that it goes out of defect resolution support
in 4.5 months from today??????  seems like a alot of work that you'll just be
repeating again very very soon.
Review & Followup
Rating: 4
follow up August 18, 2003
Reviewer: A reader
I read in the Oracle documentation ( some
time ago - i think it was the 9i docs ) ... that there was no direct upgrade
path from 8.0.5 to 9i ...

That you needed to be at least 8.0.6 before you could
directly upgrade to 9i

This seems to be the rationale for upgrading to
8i first ... and plan for 9i upgrade next year ....

can you confirm the upgrade path question ??

Thanks,

Followup:
see
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96530/migprep.htm#1006849
but why you would go from 805 -> 8i and stay (necessitating a test/deploy) and
then -> 9i (necessitating a test/deploy)

rather then:

805->806-> 9i with 806 as but a touch point on the way to get upgraded to a
supported release of the product (and a version whose defect support does not
end in 4 months!)

August 18, 2003 Adrian -- Thanks for the question regarding "Processing/Parsing XML", version 9.2.0
You Asked
The fundamental problem I have is to try and extract data from an XML string
stored in a clob, and store this in another table, along with additional fields
from the first table.

The table definition and some sample data is at the end of the post.

The data is tick data from a data feed. Within the XML string
<F><N>8</N><V>+900</V></F> represents the a new value of +900 for Field Number 8
for the given instrument code etc.

A typical update will only contain the fields that have changed, however based
upon that update I need to perform validations on the current status of an
instrument.

e.g. I have a rule that says F1 must be less than F2 otherwise raise an alert.
If initially I have two fields with values
F1 : 100
F2 : 102
and I recieve an update that contains
<F><N>2</N><V>99</V></F>
then even though I have not recieved information about F1, I still need to
perform the check. So I need to select the 'current' value of F1 from the most
recent update containing that value.

I thought it might be a good idea to transform the data as it arrives into a
different format, similar to:

Instrument_code   varchar2(90)
Tick_Type         number
ID                number
FID               number
FIDValue          varchar2(30)

Once I have this, then I can create a view for the 'current' state of any
instrument_code, and can relativly easily see how all the updates progressed
throughout the day.

My problem is getting the data into the new layout. One option would be to write
a parser to extract the sections I want, and insert the data.

Another perhaps better option would be to use some of the new XML features
supplied. I tried playing around with DBMS_XMLSAVE, and managed to process the
<F><N><V></V></N></F> into a table, but I couldn't see a way of adding the other
columns of data to this table.

This is what I tried. Where ACH_NV is a table with columns N, and V, and
occasionly various other columns as i tried different things. ACH_TEST is a
subset of the SWX_Updates table (30 rows instead of 3 million)

declare
l_ctx  dbms_xmlsave.ctxtype;
l_rows pls_integer;
l_clob clob;
begin
   for rec in (select tick_data
                   from ach_test) loop
   l_ctx := dbms_xmlsave.newcontext('ACH_NV');
   dbms_xmlsave.setrowtag(l_ctx, 'F');
   l_clob := rec.tick_data;
   l_rows := DBMS_XMLSave.insertXML(l_ctx, l_clob);
   dbms_xmlsave.closeContext(l_ctx);
   end loop;
end;
/

Are there any tricks I am missing with PL/SQL? I am new to the XMLDB features,
and have so far spent a few days trying out different scenarios. However I am
mainly a DBA, and Java/XML are not really my areas of expertise. Would any
solution only apply to 9i? Currently the database is 8.1.7, but I have plans to
migrate it to 9i in the future.

Thanks in advance for your help

Adrian

desc swx_updates;
Name                          Null?    Type
----------------------------- -------- --------------------
INSTRUMENT_CODE               NOT NULL VARCHAR2(90)
SNAP_TIME                              NUMBER
TICK_DATA                              CLOB
TICK_TYPE                              NUMBER
ID                                     NUMBER
TARGET_PARTITION                       NUMBER
CAPTURE_DATE                           DATE
BID                                    NUMBER
ASK                                    NUMBER
Some sample data:

17:02:41 neiqt@ACH9I.US.ORACLE.COM> set long 99999
17:02:44 neiqt@ACH9I.US.ORACLE.COM> select * from swx_updates where
instrument_code = 'ZZZZ.S' and rownum <= 10;

INSTRUMENT_CODE
           SNAP_TIME
---------------------------------------------------------------------------------
--------- ----------
TICK_DATA
 TICK_TYPE         ID TARGET_PARTITION
--------------------------------------------------------------------------------
---------- ---------- ----------------
CAPTURE_DATE                BID        ASK
-------------------- ---------- ----------
ZZZZ.S
          1.0529E+12
<?xml version="1.0"?><Record><Type>VERIFY</Type><Source>SSL4.5</Source><Instrume
       318  311777598
nt>ZZZZ.S</Instrument><F><N>1</N><V>4022</V></F><F><N>2</N><V>116</V></F><F><N>3
</N><V>CONZZETA HLDNG I</V></F><F><N>4</N><V>0</V></F><F><N>6</N><V>+910</V></F>
<F><N>7</N><V>+910</V></F><F><N>8</N><V>+900</V></F><F><N>9</N><V>+900</V></F><F
><N>10</N><V>+900</V></F><F><N>11</N><V>-15</V></F><F><N>12</N><V>+921</V></F><F
><N>13</N><V>+900</V></F><F><N>14</N><V>1</V></F><F><N>15</N><V>756</V></F><F><N
>16</N><V>14 MAY 2003</V></F><F><N>18</N><V>12:38</V></F><F><N>19</N><V>+921</V>
</F><F><N>21</N><V>+925</V></F><F><N>22</N><V>+890</V></F><F><N>25</N><V>+910</V
></F><F><N>28</N><V>    </V></F><F><N>29</N><V>  :  </V></F><F><N>30</N><V>+20</
V></F><F><N>31</N><V>+20</V></F><F><N>32</N><V>+155</V></F><F><N>34</N><V>+94.10
</V></F><F><N>35</N><V>+2.703</V></F><F><N>36</N><V>+9.83</V></F><F><N>38</N><V>
09 MAY 2003</V></F><F><N>53</N><V>0</V></F><F><N>56</N><V>-1.62</V></F><F><N>71<
/N><V>+25.00</V></F><F><N>75</N><V>+0</V></F><F><N>76</N><V>+0</V></F><F><N>77</
N><V>+9</V></F><F><N>78</N><V>000000265798</V></F><F><N>79</N><V>13 MAY 2003</V>
</F><F><N>90</N><V>+1129</V></F><F><N>91</N><V>+850</V></F><F><N>105</N><V>****<
/V></F><F><N>100</N><V>+140925</V></F><F><N>110</N><V>0</V></F><F><N>111</N><V>0
</V></F><F><N>117</N><V>0</V></F><F><N>118</N><V>0</V></F><F><N>289</N><V>+0</V>
</F><F><N>259</N><V>113</V></F><F><N>5</N><V>14:39</V></F><F><N>37</N><V>10</V><
/F><F><N>39</N><V>09 MAY 2003</V></F><F><N>55</N><V>+0</V></F><F><N>66</N><V>+0<
/V></F><F><N>67</N><V>           </V></F><F><N>68</N><V>           </V></F><F><N
>98</N><V>+940</V></F><F><N>104</N><V>0</V></F><F><N>131</N><V>215</V></F><F><N>
134</N><V>+0</V></F><F><N>162</N><V></V></F><F><N>163</N><V></V></F><F><N>178</N
><V>+10</V></F><F><N>291</N><V>+0</V></F><F><N>292</N><V>+0</V></F><F><N>345</N>
<V> </V></F><F><N>346</N><V> </V></F><F><N>350</N><V>13 MAY 2002</V></F><F><N>35
1</N><V>09 APR 2003</V></F><F><N>372</N><V>+0.00</V></F><F><N>373</N><V>+0</V></
F><F><N>374</N><V>1</V></F><F><N>375</N><V>  :  :  </V></F><F><N>379</N><V>14:38
:07</V></F><F><N>380</N><V>1</V></F><F><N>436</N><V>+0</V></F><F><N>437</N><V>+0
</V></F><F><N>438</N><V>+0</V></F><F><N>439</N><V>+0</V></F><F><N>440</N><V>+0</
V></F><F><N>441</N><V>+0</V></F><F><N>442</N><V>+0</V></F><F><N>443</N><V>+0</V>
</F><F><N>444</N><V>+0</V></F><F><N>445</N><V>+0</V></F><F><N>728</N><V>ZZZZ.S
  </V></F><F><N>730</N><V>+0</V></F><F><N>731</N><V>+0</V></F><F><N>732</N><V>+0
</V></F><F><N>733</N><V>+0</V></F><F><N>734</N><V>+0</V></F><F><N>735</N><V>+0</
V></F><F><N>736</N><V>+0</V></F><F><N>737</N><V>+0</V></F><F><N>738</N><V>+0</V>
</F><F><N>739</N><V>+0</V></F><F><N>740</N><V>+0</V></F><F><N>741</N><V>+0</V></
F><F><N>742</N><V>+0</V></F><F><N>743</N><V>+0</V></F><F><N>744</N><V>+0</V></F>
<F><N>745</N><V>+0</V></F><F><N>746</N><V>+0</V></F><F><N>747</N><V>+0</V></F><F
><N>800</N><V>0#.SMIGS</V></F><F><N>801</N><V></V></F><F><N>825</N><V>0</V></F><
F><N>869</N><V>28</V></F><F><N>899</N><V>+0</V></F><F><N>947</N><V>+0</V></F><F>
<N>963</N><V>+925</V></F><F><N>967</N><V>ZZZZ.SB2</V></F><F><N>975</N><V></V></F
><F><N>976</N><V></V></F><F><N>977</N><V></V></F><F><N>978</N><V></V></F><F><N>9
79</N><V></V></F><F><N>996</N><V>+155</V></F><F><N>997</N><V>+0</V></F><F><N>998
</N><V>+940.00</V></F><F><N>999</N><V>+0</V></F><F><N>1000</N><V>OnVol </V></F><
F><N>1001</N><V>OffVol</V></F><F><N>1002</N><V>TX03  </V></F><F><N>1003</N><V></
V></F><F><N>1021</N><V>+0</V></F><F><N>1023</N><V>+0</V></F><F><N>1028</N><V>13
MAY 2003</V></F><F><N>1030</N><V>+0</V></F><F><N>1031</N><V>+900</V></F><F><N>10
35</N><V>CZH   </V></F><F><N>1036</N><V></V></F><F><N>1037</N><V></V></F><F><N>1
051</N><V>           </V></F><F><N>1055</N><V>25</V></F><F><N>1056</N><V>CH00026
57986</V></F><F><N>1067</N><V>  :  :  </V></F><F><N>1080</N><V>la@</V></F><F><N>
1352</N><V></V></F><F><N>1379</N><V>+909.19</V></F><F><N>1383</N><V>@HB</V></F><
F><N>1465</N><V>+0</V></F><F><N>1496</N><V>+0.00</V></F><F><N>1501</N><V></V></F
><F><N>1709</N><V>335</V></F><F><N>1787</N><V>+0</V></F><F><N>1788</N><V>
    </V></F><F><N>2127</N><V>-1.60</V></F><F><N>2128</N><V>-18.19</V></F><F><N>2
320</N><V></V></F><F><N>2382</N><V>0</V></F><F><N>2388</N><V>+909.194</V></F><F>
<N>2405</N><V>+0.00</V></F><F><N>2406</N><V>+0.00</V></F><F><N>2407</N><V>+0</V>
</F><F><N>2408</N><V>+0</V></F><F><N>2409</N><V>+0</V></F><F><N>2410</N><V>+0</V
></F><F><N>2411</N><V>+0</V></F><F><N>2412</N><V>+0</V></F><F><N>2413</N><V>+0</
V></F><F><N>2414</N><V>+0</V></F><F><N>2415</N><V>+0</V></F><F><N>2416</N><V>+0<
/V></F><F><N>2417</N><V>+0</V></F><F><N>2418</N><V>+0</V></F><F><N>2419</N><V>+0
</V></F><F><N>2420</N><V>+0</V></F><F><N>2421</N><V>+0</V></F><F><N>2422</N><V>+
0</V></F><F><N>2423</N><V>+0</V></F><F><N>2424</N><V>+0</V></F><F><N>2425</N><V>
+0</V></F><F><N>2426</N><V>+0</V></F><F><N>2427</N><V>+0</V></F><F><N>2428</N><V
>+0</V></F><F><N>2429</N><V>+0</V></F><F><N>2430</N><V>+0</V></F><F><N>2431</N><
V>+0</V></F><F><N>2432</N><V>+0</V></F><F><N>2433</N><V>+0</V></F><F><N>2434</N>
<V>+0</V></F><F><N>2435</N><V>+0</V></F><F><N>2436</N><V>+0</V></F><F><N>3131</N
><V>+0</V></F><F><N>3132</N><V>+0</V></F></Record>
14-MAY-2003 15:56:03        890        910

ZZZZ.S
          1.0529E+12
<?xml version="1.0"?><Record><Type>UPDATE</Type><Source>SSL4.5</Source><Instrume
       316  311793940
nt>ZZZZ.S</Instrument><F><N>118</N><V>214</V></F><F><N>379</N><V>17:20:00</V></F
><F><N>963</N><V>+910</V></F><F><N>1028</N><V>14 MAY 2003</V></F><F><N>1030</N><
V>+0</V></F></Record>
14-MAY-2003 16:13:23

ZZZZ.S
          1.0529E+12
<?xml version="1.0"?><Record><Type>UPDATE</Type><Source>SSL4.5</Source><Instrume
       316  311793943
nt>ZZZZ.S</Instrument><F><N>131</N><V>214</V></F><F><N>379</N><V>17:20:00</V></F
><F><N>1031</N><V>+0</V></F></Record>
14-MAY-2003 16:13:23

ZZZZ.S
          1.0529E+12
<?xml version="1.0"?><Record><Type>UPDATE</Type><Source>SSL4.5</Source><Instrume
       316  311804017
nt>ZZZZ.S</Instrument><F><N>379</N><V>17:20:00</V></F></Record>
14-MAY-2003 16:25:28

ZZZZ.S
          1.0529E+12
<?xml version="1.0"?><Record><Type>UPDATE</Type><Source>SSL4.5</Source><Instrume
       316  311804018
nt>ZZZZ.S</Instrument><F><N>118</N><V>208</V></F><F><N>379</N><V>17:31:54</V></F
></Record>
14-MAY-2003 16:25:28

ZZZZ.S
          1.0529E+12
<?xml version="1.0"?><Record><Type>UPDATE</Type><Source>SSL4.5</Source><Instrume
       316  311804019
nt>ZZZZ.S</Instrument><F><N>379</N><V>17:31:54</V></F></Record>
14-MAY-2003 16:25:28

ZZZZ.S
          1.0529E+12
<?xml version="1.0"?><Record><Type>UPDATE</Type><Source>SSL4.5</Source><Instrume
       316  311968914
nt>ZZZZ.S</Instrument><F><N>5</N><V>20:35</V></F><F><N>32</N><V>+155</V></F><F><
N>100</N><V>+140925</V></F><F><N>118</N><V>166</V></F><F><N>178</N><V>+10</V></F
><F><N>372</N><V>+140925.00</V></F><F><N>373</N><V>+155</V></F><F><N>374</N><V>4
</V></F><F><N>379</N><V>14:25:07</V></F></Record>
14-MAY-2003 21:28:24

ZZZZ.S
          1.0529E+12
<?xml version="1.0"?><Record><Type>UPDATE</Type><Source>SSL4.5</Source><Instrume
       316  311971877
nt>ZZZZ.S</Instrument><F><N>131</N><V>0</V></F></Record>
14-MAY-2003 21:38:32

ZZZZ.S
          1.0530E+12
<?xml version="1.0"?><Record><Type>UNKNOWN</Type><Source>SSL4.5</Source><Instrum
       312  312003489
ent>ZZZZ.S</Instrument><F><N>6</N><V>+0</V></F><F><N>7</N><V>+0</V></F><F><N>8</
N><V>+0</V></F><F><N>9</N><V>+0</V></F><F><N>10</N><V>+0</V></F><F><N>11</N><V>+
0</V></F><F><N>12</N><V>+0</V></F><F><N>13</N><V>+0</V></F><F><N>22</N><V>+0</V>
</F><F><N>25</N><V>+0</V></F><F><N>19</N><V>+0</V></F><F><N>56</N><V>+0.00</V></
F><F><N>32</N><V>+0</V></F><F><N>30</N><V>+0</V></F><F><N>31</N><V>+0</V></F><F>
<N>14</N><V>0</V></F><F><N>118</N><V>0</V></F><F><N>110</N><V>0</V></F><F><N>111
</N><V>0</V></F><F><N>21</N><V>+910</V></F><F><N>35</N><V>+2.747</V></F><F><N>36
</N><V>+9.67</V></F><F><N>77</N><V>+0</V></F><F><N>79</N><V>14 MAY 2003</V></F><
F><N>100</N><V>+0</V></F><F><N>131</N><V>0</V></F><F><N>178</N><V>+0</V></F><F><
N>345</N><V> </V></F><F><N>346</N><V> </V></F><F><N>372</N><V>+0.00</V></F><F><N
>373</N><V>+0</V></F><F><N>374</N><V>0</V></F><F><N>996</N><V>+0</V></F><F><N>99
7</N><V>+0</V></F><F><N>999</N><V>+0</V></F><F><N>1031</N><V>+0</V></F><F><N>137
9</N><V>+0.00</V></F><F><N>2127</N><V>-3.19</V></F><F><N>2388</N><V>+0</V></F></
Record>
15-MAY-2003 04:45:37

ZZZZ.S
          1.0530E+12
<?xml version="1.0"?><Record><Type>UPDATE</Type><Source>SSL4.5</Source><Instrume
       316  312006539
nt>ZZZZ.S</Instrument><F><N>118</N><V>208</V></F><F><N>379</N><V>06:00:00</V></F
></Record>
15-MAY-2003 05:39:10

10 rows selected.

Elapsed: 00:00:04.07
17:03:05 neiqt@ACH9I.US.ORACLE.COM> spool off
and we said...
I asked Sean Dillon, our local XML technologist, to take a look at this and
here's what he had to say:
--------------

Hiya Adrian,

You want to look into the XMLType in 9i R2.  There are some cool features you
can use to accomplish what you want to do.  XMLSEQUENCE is a cool feature that
will break down many nodes into individual xmltypes, and then the TABLE function
will turn those into rows.  This will make sense in the code sample below.
Basically, I'd use a table to insert the XML document into, and then have an on
insert trigger manage breaking out the values:

--Sample table for xml documents.  The additional VARCHAR2 columns are for
--example purposes only, you may or may not choose to extract these values
--in the trigger.

sdillon@SLAP92> create table xmldocs (
  2    xmldoc     xmltype,
  3    thetype    varchar2(50),
  4    source     varchar2(50),
  5    instrument varchar2(50) )
  6  /
Table created.

--Sample table to hold the F Node values:

sdillon@SLAP92> create table fvals (
  2    n varchar2(10),
  3    v varchar2(10)
  4  )
  5  /
Table created.

--An on-insert trigger to extract the values into the additional VARCHAR2
--columns and into the FVALS table:

sdillon@SLAP92> create or replace trigger xmldocs_bifert
  2  before insert on xmldocs
  3  for each row
  4  begin
  5    if (:new.xmldoc is null) then
  6      return;
  7    end if;
  8
  9    select xmltype.getstringval(:new.xmldoc.extract('/Record/Type/text()')),
 10           xmltype.getstringval(:new.xmldoc.extract('/Record/Source/text()'))
 11           xmltype.getstringval(:new.xmldoc.extract('/Record/Instrument/text(
 12      into :new.thetype, :new.source, :new.instrument
 13      from dual;
 14
 15    for i in ( select xmltype.extract(value(t), '/F/N/text()' ).getstringval(
 16                      xmltype.extract(value(t), '/F/V/text()' ).getstringval(
 17                 from table( xmlsequence( :new.xmldoc.extract('/Record/F') )
 18    loop
 19      insert into fvals values (i.N, i.V);
 20    end loop;
 21  end xmldocs_bifert;
 22  /
Trigger created.

--With the trigger, all you need to do is insert the document at this point:

sdillon@SLAP92> insert into xmldocs (xmldoc)
  2  values (xmltype.createxml(
  3  '<?xml version="1.0"?>
  4  <Record>
  5          <Type>VERIFY</Type>
  6          <Source>SSL4.5</Source>
  7          <Instrument>ZZZZ.S</Instrument>
  8          <F>
  9                  <N>1</N>
 10                  <V>4022</V>
 11          </F>
 12          <F>
 13                  <N>2</N>
 14                  <V>116</V>
 15          </F>
 16  </Record>'))
 17  /
1 row created.

--And automatically, the values are extracted appropriately and inserted
--into the tables by the trigger:

sdillon@SLAP92> select thetype, source, instrument
  2    from xmldocs
  3  /

THETYPE    SOURCE     INSTRUMENT
---------- ---------- ----------
VERIFY     SSL4.5     ZZZZ.S

sdillon@SLAP92> select *
  2    from fvals
  3  /

N          V
---------- ----------
1          4022
2          116

I hope that helps!

_smd_
Review & Followup
Rating: 3
Useful but trigger trimmed from the right side August 18, 2003
Reviewer: Sasa from Belgrade,Serbia
Hi Tom,

Actually this question is intended for Sean Dillon (XML guru)
for this very usefull script but trigger is trimmed from the right side so it
should look like this.

CREATE OR REPLACE TRIGGER xmldocs_bifert
before insert on xmldocs
for each row
begin
  if (:new.xmldoc is null) then
    return;
  end if;
        --
  select xmltype.getstringval(:new.xmldoc.extract('/Record/Type/text()')),
        xmltype.getstringval(:new.xmldoc.extract('/Record/Source/text()')),
        xmltype.getstringval(:new.xmldoc.extract('/Record/Instrument/text()'))
   into :new.thetype, :new.source, :new.instrument
   from dual;
    --
 for i in ( select xmltype.extract(value(t), '/F/N/text()' ).getstringval() as
N,
                   xmltype.extract(value(t), '/F/V/text()' ).getstringval() as V
              from table( xmlsequence( :new.xmldoc.extract('/Record/F'))) t
           )
 loop
   insert into fvals values (i.N, i.V);
 end loop;
end xmldocs_bifert;
/

Anyway, thanks.

Followup:
Hi, Sean here.  Right you are, it looks like the trigger was trimmed on the
right... (doh!)  Thanks for pointing it out!

Rating: 4
Thanks August 18, 2003
Reviewer: Adrian Carlson-Hedges from Exeter, England
Very helpful. Thank you.

August 18, 2003 Shawn -- Thanks for the question regarding "Simple method for loading image files into 8.1.6", version 8.1.6
You Asked
Having only worked with Oracle for a few months, I am trying to
develop an efficient method for loading image files into our
database(8.1.6).  My developers are using EJB, JSP methodologies
and will also require info for methods of image retrieval from
the database.  I also understand the two options for image
storage (bfile and internal) and would like to implement both.
Would you have any suggestions?  Thanx.
and we said...
If the images are on the database server machine -- probably the
easiest method is dbms_lob.loadfromfile.  For example:

create table demo
( id        int primary key,
  theBlob   blob
)
/

create or replace directory my_files as
'/export/home/tkyte/public_html';

create or replace procedure load_a_file( p_file in varchar2 )
as
    l_blob  blob;
    l_bfile bfile;
begin
    insert into demo values ( 1, empty_blob() )
    returning theBlob into l_blob;
    l_bfile := bfilename( 'MY_FILES', p_file );
    dbms_lob.fileopen( l_bfile );
    dbms_lob.loadfromfile( l_blob, l_bfile,
                           dbms_lob.getlength( l_bfile ) );
    dbms_lob.fileclose( l_bfile );
end;
/

would give you a procedure to call to load a file into the
database.  It also shows you how to create a BFILE as it created
one temporarily to load the file.   I could have just inserted
l_bfile instead of writing l_blob if I wanted the files
"external" from the database.

Additionally, if they want to do the load in Java, you can look
at:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:238014476571
which shows you how to use streams to either READ or WRITE a
blob (which answers the second part of your question as well -
how to read them back out).

I would also suggest looking at interMedia and its capabilities
with images as well.  It can add some interesting features and
give you useful information about your images (such as type,
size and so on).

see
http://www.oracle.com/intermedia/
for more info on that.
Review & Followup
Rating: 4
Store an image in ora database August 28, 2001
Reviewer: Yuvraj S Pardeshi from India-Maharashtra-Pune
Thanks a lot to oracle team and it experts .
Sir i am Yuvraj S Pardesjhi working as a Trainee Lab Faculty was desprately
trying to insert image into the oracle database and i found it on your web site
.
As i am subscriber of the oracle magzine which i receive every two months about
the latest news of oracle corporation.
Thanks to all of you

Rating: 4
External bfile load August 18, 2003
Reviewer: DF
Can you elaborate more on how to load an external bfile locator using this
method? I have hundreds of image files that I need to load as external bfiles
into a table.

Followup:
hmm, load_a_file wasn't descriptive enough?  I mean, it is just a stored
procedure, you give it a  file name and "it loads it"

here is yet another way:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:624423639385
using sqlldr.

August 18, 2003 chandra -- Thanks for the question regarding "Joins and Normalisation", version 8.1.6
You Asked
Hi Tom

1)Can U please help me out about the different joins concept like
outer join, self join,inner join,equi join, non equi join  and other joins with
examples.
2)And the Normalisation concept examples starting from 1NF.. 5NF.
3) i have a table called gl_sub_acc_balances where number of records
are less then 15000 stores all the transactions for a particular year related to
Debtors ,Debtors amounts and creditors and creditors amounts for a month. Thru
Form this table is populated where aggregate functions are used for example
SUM(CR_AMOUNT) and SUM(DR_AMOUNT). When an exe-query is issued it is taking long
time.
Can u suggest what would be the fault and also the general gudelines
of using aggregate functions while querying.

Thanks
Regards
GCS
and we said...
1)
outer join -- it makes up "missing" data.  If I join from dept to emp by deptno
(in the scott/tiger schema) I'll find the deptno=40 has no matches in the EMP
table.  An outer join will allow the dept row to appear in the result set and
will make up a "null" row for emp:

scott@ORA8I.WORLD> select dept.deptno, count(empno)
  2  from emp, dept
  3  where emp.deptno = dept.deptno
  4  group by dept.deptno
  5  /

    DEPTNO COUNT(EMPNO)
---------- ------------
        10            3
        20            5
        30            6

scott@ORA8I.WORLD> select dept.deptno, count(empno)
  2  from emp, dept
  3  where emp.deptno(+) = dept.deptno
  4  group by dept.deptno
  5  /

    DEPTNO COUNT(EMPNO)
---------- ------------
        10            3
        20            5
        30            6
        40            0   <<<<<======= row is included in outer join

A self join is a table joined to itself.

scott@ORA8I.WORLD> select a.ename, b.ename
  2  from emp a, emp b
  3  where a.mgr = b.empno;

ENAME      ENAME
---------- ----------
SMITH      FORD
ALLEN      BLAKE
.....

here we joined emp to emp to turn the mgr into a mgr name.

Inner Join = Equi Join = Natural Join is the same as the above example.  Its a
join that uses equality like the above.

A non-equi join -- well, that would be a join that doesn't use equality.  for
example:

  1  select a.ename, a.sal, b.ename, b.sal
  2  from emp a, emp b
  3  where a.sal < b.sal
  4* order by a.ename
scott@ORA8I.WORLD> /

ENAME             SAL ENAME             SAL
---------- ---------- ---------- ----------
ADAMS            1100 ALLEN            1600
ADAMS            1100 WARD             1250
ADAMS            1100 JONES            2975
ADAMS            1100 MARTIN           1250
ADAMS            1100 KING             5000
ADAMS            1100 FORD             3000
ADAMS            1100 MILLER           1300
ADAMS            1100 TURNER           1500
ADAMS            1100 SCOTT            3000
ADAMS            1100 BLAKE            2850
ADAMS            1100 CLARK            2450
ALLEN            1600 JONES            2975
....

shows every one in EMP joined to every one that makes more than they do....

2)
http://www.phpbuilder.com/columns/barry20000731.php3
is as good a write up on that as I've seen....

3) use sql_trace and tkprof to see what it is doing.  insufficient data to give
you an answer on that one.

see
http://asktom.oracle.com/~tkyte/tkprof.html
...
Review & Followup
Rating: 3
Nice August 18, 2003
Reviewer: R.Chacravarthi from Chennai,India
Dear Sir,
Well and wish the same from you.I have questions for you
1)Does Normalisation always lead to table joins?
2)I tried to found the type of a variable.For ex.
 I tried the dump function.But it returns the type in the form of numeric value
which needs to be mapped to correspon-
ding data types.I also tried the format like
 Table.column.getTypeName().But this requires the column to
be of type sys.AnyData.
Is there any other function in Oracle to find the type of a
variable similar to the one typeof() in Javascript.
3)What do you mean by variable and equal access to data.
I think variable access as concurrency and I don't know about equal access to
data.
Expecting your reply,
Yours sincerely,
R.Chacravarthi

Followup:
1) no, getting the data you need in response to a query leads to joining when
needed....

2) don't know what you mean -- what environment are we talking here?  every
programming environment has a way to describe the columns in a SQL query really.

3) here, I don't know what you mean either -- i didn't talk about variable
access???

August 18, 2003 Raman -- Thanks for the question regarding "Database Recovery", version 8.0.4
You Asked
Hi Tom,

by mistake, we had delete one datafile and now the DB could not
be open, but can be mount. Unfortunately, we are running our DB
in NOARCHIVE mode and we don't hav a backup too. b'coz we are
fools (I just came to know that.)

plz. help me, how to recover the DB in this situation. we hav
created the datafile again in the same path, but its asking for
particular thread no. , that we don't hav.

plz. help me..

regards.

raman.
and we said...
Here are the steps you can use to get the database going again
without that tablespace (if you have no backups, its pretty much
gone -- if you want to try and recover it, please Call support
before doing anything else.  Everything you do will preclude
other paths of actions. It would be best to have a converstation
with a person to plan the attack....

So, let's say you are willing to get rid of the tablespace that
the datafile you removed was part of.  Here is an example that
shows me creating a tablespace, rm'ing the file, and then
recovering:

$ svrmgrl

Oracle Server Manager Release 3.0.6.0.0 - Production

(c) Copyright 1999, Oracle Corporation.  All Rights Reserved.

Oracle8 Enterprise Edition Release 8.0.6.0.0 - Production
With the Partitioning and Objects options
PL/SQL Release 8.0.6.0.0 - Production

SVRMGR> connect internal
Connected.
SVRMGR> create tablespace test datafile '/tmp/test.dbf' size 1m;
Statement processed.
SVRMGR> !rm /tmp/test.dbf
SVRMGR> !ls /tmp/test.dbf
/tmp/test.dbf not found

REM the data file is gone now....

SVRMGR> shutdown immediate
ORA-01116: error in opening database file 8
ORA-01110: data file 8: '/tmp/test.dbf'
ORA-27041: unable to open file
SVR4 Error: 2: No such file or directory
Additional information: 3
SVRMGR> shutdown abort
ORACLE instance shut down.
SVRMGR> startup
ORACLE instance started.
Total System Global Area                         11999664 bytes
Fixed Size                                          51632 bytes
Variable Size                                     7364608 bytes
Database Buffers                                  4505600 bytes
Redo Buffers                                        77824 bytes
Database mounted.
ORA-01157: cannot identify data file 8 - file not found
ORA-01110: data file 8: '/tmp/test.dbf'
SVRMGR> shutdown abort;
ORACLE instance shut down.

SVRMGR> startup nomount;  -- step 1, startup nomount..
ORACLE instance started.
Total System Global Area                         11999664 bytes
Fixed Size                                          51632 bytes
Variable Size                                     7364608 bytes
Database Buffers                                  4505600 bytes
Redo Buffers                                        77824 bytes
SVRMGR> alter database mount; -- step 2, mount the database
Statement processed.

SVRMGR> alter database datafile '/tmp/test.dbf' offline
drop;
Statement processed. -- step 3, get rid of the offending file

SVRMGR> alter database open; -- your database will open now
Statement processed.

SVRMGR> drop tablespace test including contents;
Statement processed. -- but we must get rid of the tablespace
SVRMGR>

There is a chance for recovery depending on your
circumstances.  If you want to attempt a recovery of the data --
you might be able to.  You should contact support in that case
and walk through the options.  Again -- don't do anything in
that case (especially the above!), that might preclude some
options from being taken.
Review & Followup
Rating: 5
Good example November 11, 2002
Reviewer: Fung from Hong Kong
It is a good example of opening a database while one db file is missing

Rating: 4
August 18, 2003
Reviewer: A reader
Tom,

I read "Except for the SYSTEM tablespace or a tablespace with an active undo
segment,
tablespaces can be taken offline, leaving the database running."

correct me if i am wrong, a tablespace can have a active undo segment only when
we are using the tablespace for some database operation - am i right? ...

thanks,

Followup:
no, an undo segment is generally in a tablespace SEPARATE from where the
transaction is taking place.

A tablespace can have an active undo segment if

a) the tablespace is an UNDO Tablespace in 9i OR contains rollback segments in
9i and before

AND

b) there is an active transaction in the database

Rating: 5
Thanks for making me understand August 19, 2003
Reviewer: A reader

August 18, 2003 saigals -- Thanks for the question regarding "Cannot describe table", version 7.3.4
You Asked
Hi Tom,

I created a user using the following code:

SQL> DROP USER isiuser CASCADE;
SQL>
SQL> CREATE USER isiuser
  2  IDENTIFIED BY orion
  3  DEFAULT TABLESPACE isicustom01
  4  TEMPORARY TABLESPACE temp
  5  QUOTA 0 ON system
  6  QUOTA 1m ON isicustom01;
SQL>
SQL> GRANT CREATE SESSION TO isiuser;
SQL> GRANT CREATE TABLE TO isiuser;
SQL> GRANT RESOURCE TO isiuser;
SQL>
SQL> CONNECT ISIUSER/ORION;
Connected.
SQL> SELECT * FROM USER_USERS;

ISIUSER                              3433 ISICUSTOM01
TEMP                           '01/14/02'

I created a table using the following sql code:

SQL>
SQL> CONNECT ISIUSER/ORION;
Connected.
SQL> DROP TABLE "isigltbl";
SQL> CREATE TABLE "isigltbl"
  2   ("YEAR"                                   NUMBER(4)               NOT NULL
,
  3    "PERIOD"                         NUMBER(2)               NOT NULL,
  4    "INVOICE_NO"                     NUMBER(10)              NOT NULL,
  5    "ITEM"                                   VARCHAR2(10)    NOT NULL,
  6    "AR"                                     NUMBER(11,2),
  7    "EARNED_REV"                     NUMBER(11,2),
  8    "DEFERRED_REV"           NUMBER(11,2),
  9    "SALES"                          NUMBER(11,2),
 10    "CM"                                     NUMBER(11,2),
 11    "CASH"                                   NUMBER(11,2),
 12    "TAX"                                    NUMBER(11,2),
 13    "WRITEOFF"                       NUMBER(11,2),
 14    "REFUND"                         NUMBER(11,2),
 15    "COMM"                                   NUMBER(11,2),
 16    "TYPE"                                   VARCHAR2(1),
 17    "SETID"                          VARCHAR2(5),
 18    "EXPENSE"                                NUMBER(11,2),
 19    "PREPAID_EXPENSE"        NUMBER(11,2),
 20    "RECLASS"                                NUMBER(11,2));
SQL>
SQL> SELECT * FROM USER_TABLES;

isigltbl                       ISICUSTOM01
                                       10         40          1        255
         20480       20480           1         249           50          1
              1 N                                  0          0          0
          0          1          1     N ENABLED

SQL>
SQL> spool off;

When I do describe command, it says the object does not exist.

SQL> connect isiuser/orion
Connected.
SQL> show user
user is "ISIUSER"
SQL> desc isigltbl
Object does not exist.
SQL>

    Please help. Thanks saigals
and we said...
comment 1, when you:

GRANT RESOURCE TO isiuser;

you implicitly grant UNLIMITED TABLESPACE as well -- so your quotas are
meaningless.  I recommend not using resource or dba or connect -- create your
own roles or use direct privs as you started to do.

Second, once you create an object with quoted identifiers -- you'll have to use
quoted identifiers.

Create table "t" ( x int );
desc "t"

will work.

desc t

will not as it looks for T but T doesn't exist, "t" does.  I recommend you NEVER
use quoted identifiers, they are a total pain in the neck.
Review & Followup
Rating: 3
"Cannot describe table", version 7.3.4 January 15, 2002
Reviewer: saigals singh from Jersey city
Thank you Tom. I realized it soon after I sent it to you.
Thanks a lot. It is best not to use quotes. I always get a great and satisfying
response from your forum.

Rating: 3
August 18, 2003
Reviewer: Ashutosh
Hi Tom,
I have assumption that DESCRIBE TABLE_NAME/VIEW_NAME is internal SQL*Plus
function. I always wonder what SQL statement is issued against database and
which tables/views are accessed when we issue these commands for table and view.

Thanks in advance

Followup:
there isn't one.  It makes an internal call (OCI lets you do this, Pro*C lets
you do this, PL/SQL lets you do this, JDBC lets you do this, etc etc etc).

This internal call goes against the dictionary cache.

The dictionary cache might have the answer, or we might have to run a bunch of
recursive sql to get the dictionary cache warmed up with the answer.

August 18, 2003 Bala -- Thanks for the question regarding "External Procedure -- data type declaration. ", version 8.0.5
You Asked
Hi Tom,

I having problems in calling a 'C' ext proc in my plsql
procedure.
The EXT proc requires char(10000) as one of the parmeter.
If i declare char(10000) in the calling plsql proc then
i am getting a 'unable to read memory error'

If i declare char(4000) then i am getting
'unable to write at memory' error.

I tried datatype long also,
getting 'unable to read at mem' error.

This is the sqlplus session........
SQL> drop library ssan3;

Library dropped.

SQL>   create library ssan3 as
      'f:\ssa\ssaname\myusa\n3sgus.dll';

Library created.

SQL>1  CREATE OR REPLACE PROCEDURE tcc_SSA_n3sgus(
  2            P1 IN OUT CHAR,
  3            P2 IN OUT CHAR,
  4            P3 IN OUT CHAR,
  5            P4 IN OUT CHAR,
  6            P5 IN OUT CHAR,
  7            P6 IN OUT CHAR,
  8            P7 IN OUT CHAR,
  9            P8 IN OUT CHAR,
 10            P9 IN OUT CHAR,
 11            P10 IN OUT CHAR, ----=====> CHAR(10000)
 12            P11 IN OUT CHAR,
 13            P12 IN OUT CHAR)
 14       AS
 15            EXTERNAL LIBRARY ssan3
 16            NAME "n3sgusd"
 17*           LANGUAGE C;
SQL> /

Procedure created.

SQL> create or replace  PROCEDURE SSA_GetKeyStack(
  2            sSSANameIn     IN VARCHAR2,
  3            sSSAKeyString  OUT VARCHAR2,
  4            sSSAKeynumber  OUT INTEGER)
  5       IS
  6  -- constants for key stack fields
  7            KS_KEY_SZ      CONSTANT INTEGER := 8;
  8            KS_TYPE        CONSTANT INTEGER := 2;
  9            KS_ELEMENT_SZ  CONSTANT INTEGER := (KS_TYPE + 2);
 10
 11  -- SSA n3sgus function call argument lists
 12   SSA_Nameset_Service      CHAR(8);
 13          SSA_Response_Code        CHAR(20);
 14          SSA_Nameset_Function     CHAR(32);
 15          SSA_Name_In              CHAR(75);
 16          SSA_Name_Clean           CHAR(75);
 17          SSA_Words_Stack          CHAR(258);
 18          SSA_Keys_Stack           CHAR(202);
 19          SSA_Search_Table         CHAR(806);
 20          SSA_Categories           CHAR(600);
 21          SSA_Work_Area            CHAR(10000);
--===========> HERE
 22          SSA_Dummy_Parm           CHAR(1);
 23
 24       BEGIN
 25  -- init variables for calling NAMESETP service
 26            SSA_Nameset_Service := 'NAMESETP';
 27            SSA_Response_Code   := '9000000000' ||
'9000000000';
 28            SSA_Nameset_Function:= '*NOSTAB*';
 29            SSA_Name_In         := sSSANameIn;
 30            SSA_Name_Clean      := ' ';
 31            SSA_Words_Stack     := ' ';
 32            SSA_Keys_Stack      := ' ';
 33            SSA_Search_Table    := ' ';
 34            SSA_Categories      := ' ';
 35            SSA_Work_Area       := ' ';
 36            SSA_Dummy_Parm      := ' ';
 37
 38  -- Call to SSA external procedure
 39            tcc_SSA_n3sgus(
 40                      SSA_Nameset_Service,
 41                      SSA_Response_Code,
 42                      SSA_Nameset_Function,
 43                      SSA_Name_In,
 44                      SSA_Name_Clean,
 45                      SSA_Words_Stack,
 46                      SSA_Keys_Stack,
 47                      SSA_Search_Table,
 48                      SSA_Categories,
 49                      SSA_Work_Area,
 50                      SSA_Dummy_Parm,
 51                      SSA_Dummy_Parm
 52                      );
 53
 54            sSSAKeynumber := TO_NUMBER(SUBSTR(SSA_Keys_Stack,
1, 2));
 55
 56            sSSAKeyString :=' ';
 57
 58            FOR i IN 0..(sSSAKeynumber-1) LOOP
 59  -- stripes out the key types, returning only a string with
all keys
 60      sSSAKeyString:=sSSAKeyString||SUBSTR(SSA_Keys_Stack, 3
+ i * KS_ELEMENT_SZ,
 61  KS_KEY_SZ) ;
 62
 63            END LOOP;
 64
 65       END SSA_GetKeyStack;
 66  /

Procedure created.

SQL> declare
  2  n  INTEGER;
  3  keys    VARCHAR2(100);
  4  Name    VARCHAR2(70);
  5
  6  BEGIN
  7
  8      Name:='JHON SMITH';
  9      n:=0;
 10      keys:=' ';
 11
 12      SSA_GetKeyStack(Name,keys,n);
 13      DBMS_OUTPUT.PUT_LINE('* Name = '||name);
 14      DBMS_OUTPUT.PUT_LINE('*');
 15      DBMS_OUTPUT.PUT_LINE('* Keys');
 16      DBMS_OUTPUT.PUT_LINE('* --------');
 17
 18
 19      FOR i IN 0..(n-1) LOOP
 20
 21          DBMS_OUTPUT.PUT_LINE(i || ' ' ||
SUBSTR(keys,1+i*8,8));
 22
 23      END LOOP;
 24
 25  END;
 26  /

======> here i am getting extproc.exe   application error
      the instruction at "0x00000000c" referenced momory at
       "0x00000000c". The memory could not be "read" .

       If i try char(4000) then i am getting memory could be
written error.

declare
*

ERROR at line 1:
ORA-28576: lost RPC connection to external procedure agent
ORA-06512: at "BIN.TCC_SSA_N3SGUS", line 0
ORA-06512: at "BIN.SSA_GETKEYSTACK", line 39
ORA-06512: at line 12
------------------------------------------

Please help.........

Thanks
Bala..
and we said...
The problem is in the C code somewhere, not in the PLSQL code.

Here is an example of how I would do this.  I would *never* pass
a CHAR string to C without passing the length and the maxlength
of the buffer.  Also, I would never consider passing anything
out to C without a NULL indicator (you'll get no data found
exceptions flung at the weirdest times if you don't).

I would prototype a PLSQL routine to take an IN or IN OUT char
string as follows:

    procedure pass(  p_in    in      char,
                     p_out   in out  char )
    as
    language C
    name "pass_str"
    library demoPassing
    with context
    parameters ( CONTEXT,
                 p_in  STRING,
                 p_in  INDICATOR short,

                 p_out  STRING,
                 p_out  INDICATOR short,
                 p_out  MAXLEN int );

The C prototype for that will be:

  void pass_str
    (
   OCIExtProcContext  *,  /*   1 : With-Context  */
                char  *,  /*   2 : P_IN  */
               short   ,  /*   3 : P_IN (Indicator) */
                 int   ,  /*   4 : P_IN (Length) */
                char  *,  /*   5 : P_OUT  */
               short  *,  /*   6 : P_OUT (Indicator) */
                 int  *,  /*   7 : P_OUT (Length) */
                 int  *   /*   8 : P_OUT (Maxlen) */
    );

I always pass a context, always -- you need it to be able to
transmit error messages back to the client via
OCIExtProcRaiseExcpWithMsg.

Now, given that I have the above prototype, my C function looks
like:

#ifdef WIN_NT
_declspec (dllexport)
#endif
void pass_str
 ( OCIExtProcContext * ctx        /* CONTEXT */,

   char *             p_istr     /* STRING */,
   short              p_istr_i   /* INDICATOR short */,
   int                p_istr_l   /* LENGTH int */,

   char *             p_ostr     /* STRING */,
   short *            p_ostr_i   /* INDICATOR short */,
   int *              p_ostr_l   /* LENGTH int */,
   int *              p_ostr_ml  /* MAXLEN int */
 )
{
    if ( p_istr_i == OCI_IND_NOTNULL )
    {
        if ( *p_ostr_ml >= p_istr_l )
        {
            memmove( p_ostr, p_istr, p_istr_l );
            *p_ostr_l = p_istr_l;
            *p_ostr_i = OCI_IND_NOTNULL;
        }
        else
        {
            OCIExtProcRaiseExcpWithMsg(ctx,20001,
                                      "String too small",0);
        }
    }
}

And when I run:

tkyte@TKYTE816> declare
  2          x      char(10000) default '*';
  3          y      char(10000) default 'xxx';
  4  begin
  5          demo_passing_pkg.pass( x, y );
  6
  7          if ( x = y ) then
  8                  dbms_output.put_line( 'Success' );
  9          else
 10                  dbms_output.put_line( 'Failed' );
 11          end if;
 12  end;
 13  /
Success

PL/SQL procedure successfully completed.

tkyte@TKYTE816>
tkyte@TKYTE816>
tkyte@TKYTE816> declare
  2          x      char(10001) default '*';
  3          y      char(10000) default 'xxx';
  4  begin
  5          demo_passing_pkg.pass( x, y );
  6
  7          if ( x = y ) then
  8                  dbms_output.put_line( 'Success' );
  9          else
 10                  dbms_output.put_line( 'Failed' );
 11          end if;
 12  end;
 13  /
declare
*
ERROR at line 1:
ORA-20001: String too small
ORA-06512: at "TKYTE.DEMO_PASSING_PKG", line 0
ORA-06512: at line 5

It works as expected.

Can you try that small example and if you still have problems --
we would need to see the C code and the PLSQL mapping to the C
code as that is where the bug is.
Review & Followup
Rating: 3
what am I missing? June 25, 2002
Reviewer: George from Chester Springs, PA USA
Tom,

I am receiving the error "Lost RPC connection", and trying
to copy this example as closely as possible to get results... I thought perhaps
it was something to do with
my C code, but when I add a main() that calls my sh()
function, the resulting executable works fine.. so I have
focussed on passing parameters.. I have had no luck :-(
Ultimately, I'd like to execute something... for now I'd
be happy returning a number without raising an error.
Any help you can lend is greatly appreciated:

my SQL:

CREATE OR REPLACE LIBRARY spi_unix_extproc is
   '/tmp_mnt/home/gliblick/oscommands/spi_unix_extproc.so' ;
/

CREATE OR REPLACE PACKAGE spi_unix_util IS

  FUNCTION oscommand( ps_cmd in varchar2 )
     RETURN NUMBER ;

end spi_unix_util;
/

CREATE OR REPLACE PACKAGE BODY spi_unix_util IS

  FUNCTION oscommand( ps_cmd in varchar2 ) RETURN NUMBER
  AS
  LANGUAGE C
  NAME "sh"
  LIBRARY spi_unix_extproc
  WITH CONTEXT
  PARAMETERS ( CONTEXT,
               ps_cmd          STRING,
               ps_cmd  LENGTH  SB4,
               RETURN  INDICATOR int );

END spi_unix_util;
/

my C code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <oci.h>

#ifdef WIN_NT
_declspec (dllexport)
#endif

int sh( OCIExtProcContext      *ctx,
        char                   *cmd,
        short                  cmd_i,
        short                  *return_indicator )
{
   int num = 199;
/*
   num = system(cmd);
*/

   return( num );
}

and when I test it from SQL*Plus:

SQL> @test_c
BEGIN :x := spi_unix_util.oscommand( 'touch
/tmp_mount/home/gliblick/oscommand/z' ); END;

*
ERROR at line 1:
ORA-28576: lost RPC connection to external procedure agent
ORA-06512: at "CALSDBA.SPI_UNIX_UTIL", line 0
ORA-06512: at line 1

I have also had our DBA compile and run this test as
the Oracle UNIX user, in the $ORACLE_HOME directory.. but
no luck.  I am now at a loss for how to proceed...

Followup:
That function maps to a C routine with a prototype like this:

Maps to a C function with the following prototype:
  OCINumber *sh
    (
   OCIExtProcContext  *,  /*   1 : With-Context  */
                char  *,  /*   2 : PS_CMD  */
                 sb4   ,  /*   3 : PS_CMD (Length) */
                 int  *   /*   4 : RETURN (Indicator) */
    );

You have short, where you should have sb4 (4 bytes, not 2 bytes).  Also, you
have short * for return indicator, that is wrong as well.

You are not setting the return indicator at all -- thats wrong as well.

Lastly -- you are saying the function returns a NUMBER.  Well, that would map to
a C Datatype of "OCINumber *" -- that is where the grievous error is in this
case!  You are returning the number 199 which, when we dereference as a pointer,
causes an immediate seg-fault!

If you have my book -- i spend about 70 pages on this topic, going over
everything you need to know (if you are interested)

Your quick fix (after fixing the INPUT parameters!) is to return the plsql type
BINARY_INTEGER.  That is mapped to the C int type which is what you are
returning....

Rating: 4
bah humbug ;-) June 26, 2002
Reviewer: George from Chester Springs, PA USA
with this advice I have made something work...
and bought your book (at full price, grumble, grumble)
Both you and your book have been very helpful and I will
soon understand enough to complete my requirements :-)
What happenned to the good old days when I just executed
a "host" command - you don't have to answer that ?!

thanks and good tidings, George :-)

Rating: 5
ORA-30110 August 14, 2003
Reviewer: A reader
Hi Tom,

We are on AIX 5.1 with Oracle 8.1.7 and are getting the following
error in a
External Procedure:

ORA-30110: syntax error at '%.*s'

Please set up your test case as:

CREATE OR REPLACE LIBRARY test AS '/usr/dk/test/test.so';
/
CREATE OR REPLACE FUNCTION GetDummy
RETURN VARCHAR2
AS LANGUAGE C
LIBRARY test
NAME "getDummy"
WITH CONTEXT
PARAMETERS (CONTEXT,RETURN INDICATOR short,RETURN LENGTH int,RETURN
STRING
);
/

The parameter file test.ini contains:

debugf = true

File test.c is:

#include <stdio.h>
#include <string.h>
#include <oci.h>

OCIEnv    *envhp;
OCISvcCtx *svchp;
OCIError  *errhp;

char *getDummy( OCIExtProcContext *ctx, short *return_i, int
*return_l )
{

  char *dummy = OCIExtProcAllocCallMemory(ctx,50);

  ub1          false = 0, debugf_flag;
  text         *errbuf = OCIExtProcAllocCallMemory(ctx,512);
  sb4          errcode = 0;

  if ( OCIExtProcGetEnv( ctx, &envhp, &svchp, &errhp ) != OCI_SUCCESS
)
  {
     OCIExtProcRaiseExcpWithMsg(ctx,20001,"Error",0);
     return NULL;
  }

  if ( OCIExtractInit( envhp, errhp )   != OCI_SUCCESS )
  {
     OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf,
(ub4) sizeof(errbuf), OCI_HTYPE_ERROR);
     OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
     return NULL;
  }

  if ( OCIExtractSetNumKeys( envhp, errhp, 1 ) != OCI_SUCCESS )
  {
     OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf,
(ub4) sizeof(errbuf), OCI_HTYPE_ERROR);
     OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
     return NULL;
  }

  if ( OCIExtractSetKey( envhp, errhp, (text *) "debugf",
OCI_EXTRACT_TYPE_BOOLEAN,   0, &false, NULL, NULL ) != OCI_SUCCESS )
  {
     OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf,
(ub4) sizeof(errbuf), OCI_HTYPE_ERROR);
     OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
     return NULL;
  }

  if ( OCIExtractFromFile( envhp, errhp, 0, (text *)
"/tmp/test.ini" ) !=
OCI_SUCCESS )
  {
     OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf,
(ub4) sizeof(errbuf), OCI_HTYPE_ERROR);
     OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
     return NULL;
  }
  if ( OCIExtractToBool( envhp, errhp,    (text *) "debugf",
0,  &debugf_flag
) != OCI_SUCCESS )
  {
     OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf,
(ub4) sizeof(errbuf), OCI_HTYPE_ERROR);
     OCIExtProcRaiseExcpWithMsg(ctx,20002,"here1",0);
     return NULL;
  }
  if ( OCIExtractTerm( envhp, errhp ) != OCI_SUCCESS )
  {
     OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf,
(ub4) sizeof(errbuf), OCI_HTYPE_ERROR);
     OCIExtProcRaiseExcpWithMsg(ctx,20003,"here2",0);
     return NULL;
  }

  strcpy(dummy,"Success.");
  *return_l = strlen(dummy);
  *return_i = OCI_IND_NOTNULL;
  return dummy;
}

We compile using $ make -f $ORACLE_HOME/rdbms/demo/demo_rdbms.mk
extproc_with_context SHARED_LIBNAME=test.so OBJS="test.o"
Please help
Regards,

Followup:
how are you calling it?

i created a /test/temp.ini with:

[tkyte@localhost test2]$ cat /tmp/test.ini
debugf = true

[tkyte@localhost test2]$ od -c /tmp/test.ini
0000000   d   e   b   u   g   f       =       t   r   u   e  \n  \n
0000017

and then:

ops$tkyte@ORA920LAP> select getdummy from dual;

GETDUMMY
---------------------------------------------------
Success.

worked first time...

Rating: 5
Thanks August 14, 2003
Reviewer: A reader
Dear Tom,
I provided this test case to Oracle Metalink, they opened the bug with the
number:3056017 initially. The bug diagnostics concentrated the test case to the
memory allocation at the end, and bypassed the Cartridge Services routines. They
have later on closed the bug, even when I've requested them to have a look at
the Cartridge Sevices routines.
They now doubt if the above test case is valid, and are advising me to test the
above in SYS or SYSTEM account.
The whole story is in TAR# 3163706.996 and TAR#2735554.1 (extension of the
previous TAR).
I've waited endlessly for you in your forum for confirmation on this test case,
and am extremely indebted on your prompt response on this.
Oracle Metalink on the other hand, promised us the resolution by 15th Aug (1
month after it was reported) after diagnosing this as a Bug, then denied it as a
Bug, it is now not reporducible at Metalink and have asked to test it with
SYS/SYSTEM account.

Extremely sorry for boring you with all this.
I've tried the od -c command it looks exactly the same as you have shown me. I
have also tried to know the return code from OCIExtractFromFile it is OCI_ERROR,
and error is ORA-30110, which says it could not find the occurance of debugf in
the test.ini
Is there something I can do to find the solution?
Extremely thankful for your help.
Best regards,

Followup:
well, they closed the bug because of a bug in the test case:

char *getDummy( OCIExtProcContext *ctx, short *return_i, int *return_l )
{
  int *myCtx;
  char dummy[50];
....

  }
  strcpy(dummy,"Success.");
  *return_l = strlen(dummy);
  *return_i = OCI_IND_NOTNULL;
  return dummy;
}

you are returning a pointer to a stack variable!!!

that is why that bug was "not a bug"

but -- can you put a couple of

fopen( 'a')
fprintf ( 'im on line %d', __LINENO__ )
fclose

debug lines in there to see which call exactly is failing?

Rating: 5
Thanks August 14, 2003
Reviewer: A reader
Dear Tom,
Thanks very much for your response.
The code now looks like this:
#include <stdio.h>
#include <string.h>
#include <oci.h>

OCIEnv    *envhp;
OCISvcCtx *svchp;
OCIError  *errhp;
FILE      *fp;

char *getDummy( OCIExtProcContext *ctx, short *return_i, int
*return_l )
{

  char *dummy = OCIExtProcAllocCallMemory(ctx,50);

  ub1          false = 0, debugf_flag;
  text         *errbuf = OCIExtProcAllocCallMemory(ctx,512);
  sb4          errcode = 0;

  if ( OCIExtProcGetEnv( ctx, &envhp, &svchp, &errhp ) != OCI_SUCCESS
)
  {
     OCIExtProcRaiseExcpWithMsg(ctx,20001,(text
*)"Error",0);
     return NULL;
  }

  if ( OCIExtractInit( envhp, errhp )   != OCI_SUCCESS )
  {
     OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
            errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);
     OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
     return NULL;
  }

  if ( OCIExtractSetNumKeys( envhp, errhp, 1 ) != OCI_SUCCESS )
  {
     OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
            errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);
     OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
     return NULL;
  }

  if ( OCIExtractSetKey( envhp, errhp, (text *) "debugf",
                            OCI_EXTRACT_TYPE_BOOLEAN,
                            0, &false, NULL, NULL ) != OCI_SUCCESS )
  {
     OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
            errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);
     OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
     return NULL;
  }

  if ( OCIExtractFromFile( envhp, errhp, 0,
                   (text *) "/tmp/test.ini" )
== OCI_ERROR )
  {
     fp = fopen("/tmp/a","w");
     fprintf(fp,"im on line %d",__LINE__);
     fclose(fp);
     OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
            errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);
     OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
     return NULL;
  }
  if ( OCIExtractToBool( envhp, errhp,    (text *) "debugf",
0,
                        &debugf_flag ) == OCI_NO_DATA )
  {
     OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
            errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);
     OCIExtProcRaiseExcpWithMsg(ctx,20222,"here1",0);
     return NULL;
  }
  if ( OCIExtractTerm( envhp, errhp ) != OCI_SUCCESS )
  {
     OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
            errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);
     OCIExtProcRaiseExcpWithMsg(ctx,20003,"here2",0);
     return NULL;
  }

  strcpy(dummy,"Success.");
  *return_l = strlen(dummy);
  *return_i = OCI_IND_NOTNULL;
  return dummy;
}

The error in  /tmp/a is
im on line 56

The line 56 is fprintf line (there is only one in the code).

Thanks very much for your time and effort.
Kind regards,

Followup:
would you mind fopening /tmp/test.ini , freading the first 1k of it and fwriting
what you get into the debug? just to see what we see?

i'll have to try later on 817 (only 9ir2 right now) but it'll be solaris

Rating: 5
Thanks August 14, 2003
Reviewer: A reader
Thanks very much Tom for your time and effort. I do not have the words for
appreciation here.

I tried the following code:
#include <stdio.h>

FILE  *fi,*fo;
char  buffer[1024];

main()
{

     fi = fopen("/tmp/test.ini","r");
     fo = fopen("/tmp/debuf","w");
     fread(buffer,sizeof(char),1024,fi);
     fwrite(buffer,sizeof(char),1024,fo);
     fclose(fi);
     fclose(fo);
}

$ cc -o dk dk.c
$ dk
$ ll /tmp/debuf
-rw-rw-r--   1 eve      eve            1024 Aug 14 19:47 /tmp/debuf
$ cat /tmp/debuf
debugf = true

$ cat
/tmp/test.ini
debugf = true

$ ll /tmp/test.ini
-rwxrwxrwx   1 eve      eve              15 Aug 14 18:19 /tmp/test.ini
$

I feel (based on the response of the first Support Analyst) that the problem is
with the AIX machines, it is not with the solaris.
Thanks once again.
Kind regards

#include <stdio.h>

FILE  *fi,*fo;
char  buffer[1024];

main()
{

     fi = fopen("/tmp/test.ini","r");
     fo = fopen("/tmp/debuf","w");
     fread(buffer,sizeof(char),1024,fi);
     fwrite(buffer,sizeof(char),1024,fo);
     fclose(fi);
     fclose(fo);
}

Followup:
you need to

n = fread()
fwrite( ..., n, ... )

and do it in the EXTPROC -- i wanna see what it sees

i'll try later.

Rating: 5
Thanks August 15, 2003
Reviewer: A reader
Hi Tom,
Sorry I misunderstood.
#include <stdio.h>
#include <string.h>
#include <oci.h>

OCIEnv    *envhp;
OCISvcCtx *svchp;
OCIError  *errhp;

char *getDummy( OCIExtProcContext *ctx, short *return_i, int
*return_l )
{
  FILE  *fi,*fo;
  char  buffer[1024];
  int n;
  char *dummy;
  dummy=OCIExtProcAllocCallMemory(ctx,50);

  if ( OCIExtProcGetEnv( ctx, &envhp, &svchp, &errhp ) != OCI_SUCCESS
)
  {

  }
  fi = fopen("/tmp/test.ini","r");
  fo = fopen("/tmp/debuf","w");
  n = fread(buffer,sizeof(char),1024,fi);
  fwrite(buffer,sizeof(char),n,fo);
  fclose(fi);
  fclose(fo);
  strcpy(dummy,"Success.");
  *return_l = strlen(dummy);
  *return_i = OCI_IND_NOTNULL;
  return dummy;
}

$ ll /tmp/debuf
-rw-r--r--   1 oracle   dba              15 Aug 15 08:59 /tmp/debuf
$ cat /tmp/debuf
debugf = true

$ cat /tmp/test.ini
debugf = true

$ ll /tmp/test.ini
-rwxrwxrwx   1 eve      eve              15 Aug 14 18:19 /tmp/test.ini

$ od -c /tmp/test.ini
0000000    d   e   b   u   g   f       =       t   r   u   e  \n  \n
0000017

$ od -c /tmp/debuf
0000000    d   e   b   u   g   f       =       t   r   u   e  \n  \n
0000017
$

Followup:
Ok, lets try this piece of code.  this is a generic template I use for all of my
external procedures (if you have my book expert one on one Oracle -- this is
from there)

include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>

#include <oci.h>

#ifdef WIN_NT
#define INI_FILE_NAME "c:\\temp\\extproc.ini"
#else
#define INI_FILE_NAME "/tmp/extproc.ini"
#endif

#define strupr(a) {char * cp; for(cp=a;*cp;*cp=toupper(*cp), cp++);}

/*
 * This is our "context".  It holds what normally might be global variables
 * in a typical program.  We cannot (should not) use global variables in an
 * external procedure.  Also, since static data will be reinitialized
 * between calls -- globals would not work correct anyway.  We will use the
 * OCI context management API calls to get and set a global context for
 * our extproc.  You would add any state variables you needed to preserve
 * from call to call in this structure below.
 *
 * the debugf_* variables are initialized from a parameter file.  In our
 * init() routine, we will read the parameter file and set these values.
 * you may add your own parameters to this structure as well and set them
 * in init() as I do.
 *
 * curr_lineno and curr_filename are set by the debugf macro below so when
 * we create a trace file, I can tell you what line number and source code
 * file a message was generated from
 *
 */

typedef struct myCtx
{
  OCIExtProcContext * ctx;      /* Context passed to all external procs */
  OCIEnv *            envhp;    /* OCI environment handle */
  OCISvcCtx *         svchp;    /* OCI Service handle */
  OCIError *          errhp;    /* OCI Error handle */

  int                 curr_lineno;
  char *              curr_filename;

  ub1                 debugf_flag;
  char                debugf_path[255];
  char                debugf_filename[50];

  /* add your own state variables here... */
}
    myCtxStruct;

/*
 * Here is the implementation of our "debugf" routine.  It is used to
 * instrument the code and aid in debugging runtime issues.
 *
 * It works much like the C printf function (well, exactly like it)
 * in that you send a C format and then a varying number of arguments
 * to be formatted and printed.  We add to it the Year, Month, Day,
 * Hour, Minute, Second in GMT to each trace record as well as the name
 * of the C source code file and the line number.
 *
 * Note that in order to be as OS independent as possible, we use the
 * OCI API's to write to files.  The File API's are expected to have
 * been initialized in the init() routine alread and will be closed out
 * in the term() routine.
 */

void _debugf( myCtxStruct * myCtx, char * fmt, ... )
{
va_list         ap;
OCIFileObject * fp;
time_t          theTime = time(NULL);
char            msg[8192];
ub4             bytes;

    if ( OCIFileOpen( myCtx->envhp, myCtx->errhp, &fp,
                      myCtx->debugf_filename,
                      myCtx->debugf_path,
                      OCI_FILE_WRITE_ONLY, OCI_FILE_APPEND|OCI_FILE_CREATE,
                      OCI_FILE_TEXT ) != OCI_SUCCESS ) return;

    strftime( msg, sizeof(msg),
             "%y%m%d %H%M%S GMT ", gmtime(&theTime) );
    OCIFileWrite( myCtx->envhp, myCtx->errhp, fp, msg, strlen(msg), &bytes );

    va_start(ap,fmt);
    vsprintf( msg, fmt, ap );
    va_end(ap);
    strcat( msg,"\n");

    OCIFileWrite( myCtx->envhp, myCtx->errhp, fp, msg, strlen(msg), &bytes );
    OCIFileClose( myCtx->envhp, myCtx->errhp, fp );
}

/*
 * this macro is a more convienent way to use debugf. Instead of have
 * to pass the __LINE__, __FILE__ each time we call -- we just code:
 *
 *     debugf( myCtx, "This is some format %s", some_string );
 *
 * and this macro will set them in our context and then call _debugf for us.
 *
 */
void _debugf( myCtxStruct * myCtx, char * fmt, ... );
#define debugf  \
if ((myCtx!=NULL) && (myCtx->debugf_flag)) \
    myCtx->curr_lineno = __LINE__, \
    myCtx->curr_filename = __FILE__, \
    _debugf

/*
 * This routine works like the PLSQL builtin by the same name.  It
 * sets an application defined error message and error code.  It also
 * works like the C printf routine in that you pass to it a C format
 * string and then a varying number of arguments
 *
 * The resulting string is limited to 8k in size.  Note the call
 * to debugf.  Debugf is the macro above, we will use it throughout
 * the code to 'instrument' it.
 */

static int raise_application_error(  myCtxStruct * myCtx,
                                     int           errCode,
                                     char *        errMsg, ...)
{
char    msg[8192];
va_list ap;

    va_start(ap,errMsg);
    vsprintf( msg, errMsg, ap );
    va_end(ap);

    debugf( myCtx,  "raise application error( %d, %s )", errCode, msg );
    if ( OCIExtProcRaiseExcpWithMsg(myCtx->ctx,errCode,msg,0) ==
                                                OCIEXTPROC_ERROR )
    {
      debugf( myCtx,  "Unable to raise exception" );
    }
    return -1;
}

/*
 * This is a convienence routine to allocate storage for an error message
 * and return it.  Note that the type of storage we allocate is CALL based,
 * hence, when we return from the extproc, OCI will automatically free it
 * for us.
 *
 * This routine removes the trailing newline from the error message as well
 */
static char * lastOciError( myCtxStruct * myCtx )
{
sb4       errcode;
char      * errbuf = (char*)OCIExtProcAllocCallMemory( myCtx->ctx, 256 );

    strcpy( errbuf, "unable to retrieve message\n" );
    OCIErrorGet( myCtx->errhp, 1, NULL, &errcode, errbuf,
                 255, OCI_HTYPE_ERROR );
    errbuf[strlen(errbuf)-1] = 0;
    return errbuf;
}

/*
 * Here is the init routine.  This routine must be called very early
 * in the execution of any external procedure using this template.  It
 * will
 *   o retrieve the OCI handles and set them in the context for us
 *   o retrieve our CONTEXT, if the context has not yet been allocated,
 *     it will allocate storage for our context and save it.  The memory
 *     is allocated to last as long as our process lasts.
 *   o it will read and retrieve the values from the parameter file if
 *     they have not been.  You can add more parameters of your own by:
 *     - adding elements the the myCtxStruct above
 *     - increment the counter sent into ExtractSetNumKeys appropriately
 *     - adding a call to ExtractSetKey
 *     - adding a call to ExtractTo* (string, bool, int, etc)
 *   o it will initialize the FILE apis via OCIFileInit().  This is crucial
 *     for the successful operation of debugf above.
 *
 * You may add other "init" type of calls.  for example, if you choose to
 * use the String Formatting Interface (similar to vsprintf() but in OCI)
 * You could add a call to OCIFormatInit() here.  You would add the
 * corresponding OCIFormatTerm() call to term() below.
 *
 */
static myCtxStruct * init( OCIExtProcContext * ctx )
{
ub1          false = 0;
myCtxStruct *myCtx = NULL;
OCIEnv      *envhp;
OCISvcCtx   *svchp;
OCIError    *errhp;
ub4          key = 1;

    if ( OCIExtProcGetEnv( ctx, &envhp, &svchp, &errhp ) != OCI_SUCCESS )
    {
         OCIExtProcRaiseExcpWithMsg(ctx,20000,
                                   "failed to get OCI Connection",0);
         return NULL;
    }
    if ( OCIContextGetValue( envhp, errhp, (ub1*)&key, sizeof(key),
                             (dvoid**)&myCtx ) != OCI_SUCCESS )
    {
        OCIExtProcRaiseExcpWithMsg(ctx,20000,"failed to get OCI Context",0);
        return NULL;
    }

    if ( myCtx == NULL )
    {
        if ( OCIMemoryAlloc( envhp, errhp, (dvoid**)&myCtx,
                             OCI_DURATION_PROCESS,
                             sizeof(myCtxStruct),
                             OCI_MEMORY_CLEARED ) != OCI_SUCCESS )
        {
            OCIExtProcRaiseExcpWithMsg(ctx,20000,
                                      "failed to get OCI Memory",0);
            return NULL;
        }
        myCtx->ctx   = ctx;

        myCtx->envhp = envhp;
        myCtx->svchp = svchp;
        myCtx->errhp = errhp;
        if ( OCIContextSetValue( envhp, errhp,
                                 OCI_DURATION_SESSION, (ub1*)&key,
                                 sizeof(key), myCtx ) != OCI_SUCCESS )
        {
            raise_application_error(myCtx, 20000, "%s", lastOciError(myCtx));
            return NULL;
        }

        if (( OCIExtractInit( envhp, errhp ) != OCI_SUCCESS )  ||
            ( OCIExtractSetNumKeys( envhp, errhp, 3 ) != OCI_SUCCESS ) ||
            ( OCIExtractSetKey( envhp, errhp, "debugf",
                                OCI_EXTRACT_TYPE_BOOLEAN,
                                0, &false, NULL, NULL ) != OCI_SUCCESS ) ||
            ( OCIExtractSetKey( envhp, errhp, "debugf_filename",
                                OCI_EXTRACT_TYPE_STRING,
                                0, "extproc.log",
                                NULL, NULL ) != OCI_SUCCESS )  ||
            ( OCIExtractSetKey( envhp, errhp, "debugf_path",
                                OCI_EXTRACT_TYPE_STRING,
                                0, "", NULL, NULL ) != OCI_SUCCESS )  ||
            ( OCIExtractFromFile( envhp, errhp, 0,
                                  INI_FILE_NAME ) != OCI_SUCCESS ) ||
            ( OCIExtractToBool( envhp, errhp, "debugf", 0,
                                &myCtx->debugf_flag ) != OCI_SUCCESS ) ||
            ( OCIExtractToStr( envhp, errhp, "debugf_filename", 0,
                               myCtx->debugf_filename,
                               sizeof(myCtx->debugf_filename ) )
                                                     != OCI_SUCCESS ) ||
            ( OCIExtractToStr( envhp, errhp, "debugf_path",
                               0, myCtx->debugf_path,
                               sizeof(myCtx->debugf_path ) )
                                                     != OCI_SUCCESS ) ||
            ( OCIExtractTerm( envhp, errhp ) != OCI_SUCCESS ))
        {
            raise_application_error(myCtx, 20000, "%s", lastOciError(myCtx));
            return NULL;
        }
    }
    else
    {
        myCtx->ctx   = ctx;
        myCtx->envhp = envhp;
        myCtx->svchp = svchp;
        myCtx->errhp = errhp;
    }
    if ( OCIFileInit( myCtx->envhp, myCtx->errhp ) != OCI_SUCCESS )
    {
        raise_application_error(myCtx, 20000, "%s", lastOciError(myCtx));
        return NULL;
    }
    return myCtx;
}

/*
 * This must be called after any successful call to init() above.  It
 * should be the last thing you call in your routine before returning
 * from C to SQL
 */
static void term( myCtxStruct * myCtx )
{
    OCIFileTerm( myCtx->envhp, myCtx->errhp );
}

/*
 * everthing above this is BOILERPLATE!!!
 */

/*
 * With the string (the varchar) we'll use 2 parameters -- the
 * indicator variable and the LENGTH field.  This time, much like an
 * OUT parameter, we set the length field to let the caller know how
 * long the returned string is.
 *
 * Many of the same considerations apply for returning strings as above
 * -- we'll allocate storage, set the indicator, supply the value and
 * return it...
 */
#ifdef WIN_NT
_declspec (dllexport)
#endif
char * return_string
 ( OCIExtProcContext * ctx,
   short *             return_i,
   int   *             return_l )
{
char * data_we_want_to_return = "Hello World!";

char * return_value;
myCtxStruct*myCtx;

    if ( (myCtx = init( ctx )) == NULL ) return NULL;
    debugf( myCtx,  "Enter return String" );

    return_value = (char *)OCIExtProcAllocCallMemory(ctx,
                                          strlen(data_we_want_to_return)+1 );

    if( return_value == NULL )
    {
        raise_application_error( myCtx, -20001, "%s", "no memory" );
    }
    else
    {
        *return_i = OCI_IND_NULL;
        strcpy( return_value, data_we_want_to_return );
        *return_l = strlen(return_value);
        *return_i = OCI_IND_NOTNULL;
    }
    term(myCtx);
    return return_value;
}

The Makefile I use is:

MAKEFILE= $(ORACLE_HOME)/rdbms/demo/demo_rdbms.mk

INCLUDE= -I$(ORACLE_HOME)/rdbms/demo \
         -I$(ORACLE_HOME)/rdbms/public \
         -I$(ORACLE_HOME)/plsql/public \
         -I$(ORACLE_HOME)/network/public

TGTDLL= extproc.so
OBJS  = extproc.o

all: extproc.so

clean:
    rm *.o

extproc.so: $(OBJS)
    $(MAKE) -f $(MAKEFILE) extproc_callback \
     SHARED_LIBNAME=$(TGTDLL) OBJS=$(OBJS)

CC=cc
CFLAGS= -g -I. $(INCLUDE) -Wall

but your command line should work as well...  Then test.sql is:

create or replace library demoPassing
as '/usr/oracle/ora920/bin/extproc.so'
/

create or replace
    function return_string return varchar2
    as
    language C name "return_string" library demoPassing
    with context parameters
    ( CONTEXT, RETURN INDICATOR short, RETURN LENGTH int, RETURN STRING );

      -- char *return_string
      --   (
      --  OCIExtProcContext  *,  /*   1 : With-Context  */
      --              short  *,  /*   2 : RETURN (Indicator) */
      --                int  *   /*   3 : RETURN (Length) */
      --   );
/

select return_string from dual;

i could not reproduce your issue on solaris with your code - could be a bug in
the code -- if this code works, we'll have to see what is different about them

Rating: 5
Thanks! August 15, 2003
Reviewer: A reader
Dear Tom,
I made two changes to your code:
1. removed the -Wall from the compiler options (not available on  AIX)
2. put the return_string in quotes
The code fails with the message:
SQL> ed
Wrote file afiedt.buf

  1  create or replace
  2  function return_string return varchar2
  3  as
  4  language C name
  5  "return_string"
  6  library demoPassing
  7  with context parameters ( CONTEXT,
  8                            RETURN INDICATOR short,
  9                            RETURN LENGTH int,
 10*                           RETURN STRING );
SQL>
SQL> /

Function created.
SQL> select return_string from dual
  2  /
select return_string from dual
*
ERROR at line 1:
ORA-20000:

SQL> /

RETURN_STRING
----------------------------------------------------------------------------

----
Hello World!

SQL>

Runs well on the next run.
Please note that if I add the -G compiler option it fails with ORA-21500. The
message I reported in the TAR. Also, as before the second run is okay.

SQL> select return_string from dual;
select return_string from dual
                          *
ERROR at line 1:
ORA-20000: ORA-21500:T?

SQL> /

RETURN_STRING
----------------------------------------------------------------------------

----
Hello World!

SQL>

Tom, the test case I submitted to Metalink has drawn heavily on your code (I had
your book, but lost it. So planning to buy another). I only goofed up in the
memory allocation at the end that returned crap on AIX (the only excuse I have
for myself is that worked on the IBM/ Dynix NUMA Oracle 8.1.5 very well - so
didn't figure out the problem). So if you would allow me to relate this to the
analysis by bug diagnostics at Metalink, in the Reproducibility section 8.1.7.4
AIX 32 bit ORA-21500 is returned.
May be this problem would be reproducible after all. I've the management after
me to report on the status as it is 15th Aug.
Many thanks

Followup:
I would say this is a bug on AIX -- to do with parsing that file.  That my code
fails and then runs is expected cause I only parse that file once per session.

I do not have an AIX box anywhere to play with -- so my suggestion is

simplify the test case -- function returning an integer, just send the context,
no indicators, minimal code.

something they can easily reproduce in house.  If you like, you can put it here
before filing with them and we'll eyeball it to make sure it is OK first.

NO GLOBALS -- they always cause suspicion.

Rating: 5
Thanks August 18, 2003
Reviewer: A reader
Dear Tom,

Please see if this is okay?
test.c file:

#include <oci.h>

OCIEnv    *envhp;
OCISvcCtx *svchp;
OCIError  *errhp;

int getDummy( OCIExtProcContext *ctx )
{
  ub1   false = 0, debugf_flag;
  text  *errbuf = OCIExtProcAllocCallMemory(ctx,512);
  sb4   errcode = 0;

  if ( OCIExtProcGetEnv( ctx, &envhp, &svchp, &errhp ) != OCI_SUCCESS
)
  {
     OCIExtProcRaiseExcpWithMsg(ctx,20001,(text
*)"Error",0);
     return NULL;
  }
  if (( OCIExtractInit( envhp, errhp )   != OCI_SUCCESS ) ||
      ( OCIExtractSetNumKeys( envhp, errhp, 1 ) != OCI_SUCCESS ) ||
      ( OCIExtractSetKey( envhp, errhp, (text *) "debugf",
                            OCI_EXTRACT_TYPE_BOOLEAN,
                0, &false, NULL, NULL ) != OCI_SUCCESS )
||
      ( OCIExtractFromFile( envhp, errhp, 0,
                   (text *) "/tmp/test.ini" )
!= OCI_SUCCESS )
||
      ( OCIExtractToBool( envhp, errhp,  (text *) "debugf",
0,
                        &debugf_flag ) != OCI_SUCCESS )  ||
      ( OCIExtractTerm( envhp, errhp ) != OCI_SUCCESS ))
  {
     OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
            errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);
     OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
     return NULL;
  }
  return ((int) 0);
}

CREATE OR REPLACE FUNCTION GetDummy
RETURN binary_integer
AS LANGUAGE C
LIBRARY test
NAME "getDummy"
WITH CONTEXT
PARAMETERS (CONTEXT, RETURN int);
/

Error that is returned (instead of 0 expected):
SQL> /
select getdummy from dual
*
ERROR at line 1:
ORA-30110: syntax error at '%.*s'

Thanks

Followup:
     OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
            errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);

shouldn't sizeof(errbuf) be 511?  since you allocated 512 bytes?

but otherwise -- looks good.

Rating: 5
Thanks August 18, 2003
Reviewer: A reader
Dear Tom,
All the examples that Oracle have provided use sizeof(errbuf) and not
sizeof(errbuf) -1.

Followup:
that works if errbuf was defined as

char errbuf[255];

sizeof(errbuf) would be 255 -- telling us "255 bytes of storage for the error
message"

if you however:

char * errbuf = call_to_allocate_memory();

sizeof( errbuf ) is now 4 (generally), even if you allocated 255 bytes.

Rating: 5
Thanks August 19, 2003
Reviewer: A reader
Thanks Tom,
I have forwarded the test case to metalink.
Also for above, it's because sizeof is determined at compile time and not run
time.
Thanks again

August 19, 2003 Ashiq -- Thanks for the question regarding "Query", version 8.1.7
You Asked
Hi Tom,

  i've table t ,which has 2 columns .

dt   varchar2(10)
time  varchar2(20)

dt        time
10/10/2001    12:12:54 am
10/11/2001    06:12:54 am
10/12/2001    03:00:00 am
10/12/2001      03:00:05 am

I need a query which should Retrieve the records based upon date & time.
like  if i want the retrieve all the records from the table till yesterday
morning 5 am.

thanx,
and we said...
Oh YUCK.  That really stinks doesn't it.  Do they store numbers in strings as
well?  Guess we really only need one datatype -- varchar2(4000)!

man oh man, i hate it when people do that....

anyway,

where to_date( dt || ' ' || time, 'mm/dd/yyyy hh:mi:ss am' )
      > trunc(sysdate)-1+5/24
  and dt in ( to_char(sysdate,'mm/dd/yyyy'), to_char(sysdate-1,'mm/dd/yyyy') )

The to_date > yesterday at 5am will get just the records you need.

the and dt in ( a, b ) will let you make use of an index on dt if one exists.
Review & Followup
Rating: 3
Cool answer, Tom October 23, 2002
Reviewer: Cefers.br from Brasil
Tom,

Your last answer was so funny!

I´m in my Oracle class right now, and I just started laughing because of your
words. Ha ha!

Now I know why Oracle created the new Oracle 9i ANYDATA type. He he he...

Thanks for helping us in a so funny way.

Rating: 5
query October 23, 2002
Reviewer: Ashiq from Trichy,Tamilnadu India
Thanx for ur timely reply.It'll be great if accept more questions ,so that we
can get chance more often to post our queries.

Rating: 3
spliting data weekly October 24, 2002
Reviewer: A reader from India
Dear Tom,
I want to generate a matrix report week wise.ie I give from date and to date and
the output should be split across weeks.
Eg
x[varchar2(20)]            y[date]
aaa                        01-oct-02
bbb                        02-oct-02
aaa                        02-oct-02
aaa                        07-oct-02
bbb                        24-oct-02
...
.
.
My Output should be

(weeks)      1       2        3        4         5
aaa          2       1        0        0         0
bbb          1       0        0        1         0
.
.
I want a generalised query where I will give any date but within a month and a
weekly report should be generated as shown above.
TIA.

Followup:
you just need to come up with the DECODE or CASE that puts your date into a
"week" using whatever format you want -- and then:

select x,
       sum( decode( F(y), 1, 1, 0 ) ) c1,
       sum( decode( F(y), 2, 1, 0 ) ) c1,
...
       sum( decode( F(y), N, 1, 0 ) ) cN
 from t
group by x;

you just need to figure out what you want "F" to be.

Rating: 4
August 19, 2003
Reviewer: Mani from Mumbai India
tom
   I have one more query about this following format.

(weeks)      1       2        3        4         5
aaa          2       1        0        0         0
bbb          1       0        0        1         0
.
.

     here i don't want to display the details under the heading 3 and 5 i.e., i
don't want to display header itself. bec both are having zero values. Is it
possible to hide the headers?

Followup:
not really - you don't know until you fetch the very very very last row (nor do
we) that all of the values in that column are zero.

this would be something for a reporting tool to deal with, not sql itself.

August 19, 2003 Andre -- Thanks for the question regarding "How to use FAST_START_IO_TARGET ?", version 8.1.6
You Asked
Hi Tom,

I know there this new feature in 8i, FAST_START_IO_TARGET, which
allows you to tune checkpoiting in order to reduce instance
recovery time. What I do not understand is what is the great
advantage of this feature comprared to the traditional
LOG_CHECKPOINT_INTERVAL and LOG_CHECKPOINT_TIMEOUT parameters,
since they accomplish the same task ? Would you please elaborate
on that ?

Regards.
and we said...
FAST_START_IO_TARGET (available only with the Oracle Enterprise
Edition) specifies the number of IOs that should be needed
during crash or instance  recovery. It imposes a more accurate
bound on the number of recovery IOs than
DB_BLOCK_MAX_DIRTY_TARGET.

When this parameter is set, DBWn writes dirty buffers out more
aggressively  to keep the number of blocks that must be
processed during recovery below  the value specified in the
parameter. Note that this parameter does not  impose a hard
limit on the number of recovery IOs. There may be transient
workload situations in which the number of IOs needed during
recovery is greater than the value specified in this parameter,
but if this occurs, DBWn will not slow down database activity.

Smaller values for this parameter result in faster recovery
times. This improvement in recovery performance is achieved at
the expense of additional writing activity during normal
processing. See the Oracle8i Backup and Recovery Guide and
V$INSTANCE_RECOVERY for more information.

Setting this parameter's value to 0 disables the mechanism that
limits the number of IOs that need to be performed during
recovery. All other writing activity is unaffected.

So, fast_start_io_target is limiting the number of blocks that
need recovery.  log_checkpoint_interval on the other hand,
attempts to limit the number of redo blocks that need to be
processed (100 redo blocks could mean 1000 db blocks, or it
could mean 1 db block).  the log_checkpiong_timeout is time
based -- not number of IOs based so it is different as well.

See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:344617462392
for the details on the log_checkpoint_* parameters.
Review & Followup
Rating: 5
FSIOT vs. DB_BLOCK_MAX_DIRTY_TARGET December 04, 2001
Reviewer: Andre Whittick Nasser from Rio de Janeiro, Brazil
Tom,

I was taking a closer look at all those parameters regulating checkpoint
activity:

- LOG_CHECKPOINT_INTERVAL
- LOG_CHECKPOINT_TIMEOUT
- DB_BLOCK_MAX_DIRTY_TARGET
- FAST_START_IO_TARGET (EE)
- FAST_START_MTTR_TARGET (9i)

I understand the idea behind each of the 5 parameters above, including the
checkpoint queue, etc.

All os there parameters regulate "how much" the data files may be "inconcistent"
with the redo files, so for example, in the case of an instance recovery (roll
forward), it's possible to impose a "limit" on the blocks applied.

My question is about DB_BLOCK_MAX_DIRTY_TARGET (here DBMDT) and
FAST_START_IO_TARGET (here FSIOT).

Now, conceptually, FSIOT and DBMDT are different:

- FSIOT tunes how many blocks will be applied in the roll forward process, so
you can e.g. agree on a SLA with a customer or management.

- DBMDT is the greatest number of dirty blocks in the buffer pool, when the
server process triggers DBWR to flush.

However, in practice, they share the same essence, that is, the (approximate)
maximum number of blocks that are allowed to be inconsistent between data files
and redo files.

My question is, since they are functionally very close, can I simulate FSIOT
using DBMDT ? After all, why FSTIO ?

Followup:
they are functionaly very close and in 9i, fsiot is depricated in favor of a
fast_start_mttr_target specified in seconds to recover, rather then IO's to
recover.

Rating: 5
About dirty blocks December 04, 2001
Reviewer: Andre Whittick Nasser from Rio de Janeiro, Brazil
Thanks again.

One more question.

During DML processing, DBWR flushes the buffer pool to the datafiles in some
situations:

a) Every 3 seconds;
b) When the server process searches the LRU list for free blocks up to a certain
limit;
c) When the dirty list reaches a threshold because of block coming from the LRU
list;
d) When signaled by LGWR.

Both values b) and c) are not documented nor directly related to any parameter.
Are they "hardcoded" ?

What makes a lot of sense to me and yet I was not able to test, is: Is
DB_BLOCK_DIRTY_TARGET the limit in item c) ?

Followup:
well, letter a sounds like LGWR with respect to the redo buffers, not DBWR.

C would match the description of that init parameter, yes.

Rating: 5
LGWR December 06, 2001
Reviewer: Andre Whittick Nasser from Rio de Janeiro, Brazil
Sorry Tom,

You're right. a) is related to LGWR, of course.

Thanks once more

Rating: 4
Truncate and Checkpoint August 19, 2003
Reviewer: Nathan from United Kingdom
We have a high insert ( over 35 million rows inserted per hour), with followingh
config
Cache Sizes (end)
~~~~~~~~~~~~~~~~~
Buffer Cache:   4,096M      Std Block Size:         8K
Shared Pool Size: 256M          Log Buffer:   153,600K

A truncate of 2.3 million row table took over 29 minutes.
I did a test , here by truncate a table with one column , one row and I selected
it once , It took over 5 minutes ...

I understood from Jonathan Lewis's column it was due the checkpoint activity
.... but i'm not convinced about the time it takes for this operation ..

Also interestingly ,
dbms_stats.gather_table_stats(ownname=>'STBMADM',tabname=>'REPORT_STBL',estimate_
percent=>0.1);
of  2.3 million row table takes over 50 minutes !!!!

I have just taken up this new role .. and these numbers are all too huge for me
and i'm puzzled .. is there anything fundamentally wrong ? like version
9.0.1.2.0...? would a patch resolve these issues ?

Thanks
Nathan

Followup:
the patch would be faster disk?

you understand that it takes quite a while to checkpoint individual blocks
(scattered writes all over the place -- worst possible kind of IO).

Perhaps you should be DIRECT PATHING this data, no dirty blocks in the buffer
cache to checkpoint.

Have you taken the time to see what your disks are actually doing during these
operations?  you might be surprised.  remember if you load 35million rows using
conventional sql, they will load as "dirty blocks" that need cleaning -- so that
analyze for example will "clean" each block (eg: a read will generate redo and
re-dirty the block so if you loaded a block and it got flushed, the next read of
the block could re-dirty the block and cause it to be written yet again)

sounds like a system in need of some direct path options.

August 19, 2003 Jim -- Thanks for the question regarding "Break comma/space delimited text out", version 7.3.4
You Asked
I have a NAME field that is stored as follows:

Jones,John Paul
Bonham III,John H
Page Jr.,James Patrick
Plant,Robert
Grant,Peter J

Unfortunately thats the way Peoplesoft stores data and I need to break it out
for a new software package we are migrating to.  I need in separate columns:
firstname, lastname, middlename, suffix.

I was able to accomplish some of this with the following code:

select
name "Full Name",
SUBSTR(name,1,DECODE(INSTR(NAME,','),0,30,INSTR(NAME,',')-1)) Last,
LTRIM(DECODE(INSTR(NAME,','),0,NULL,SUBSTR(NAME,INSTR(NAME,',')+1))) First,
LTRIM(DECODE(INSTR(NAME,' '),0,NULL,SUBSTR(NAME,INSTR(NAME,' ')+1))) Middle
from table;

It works perfect for people with no middle name.  With people with a middle
initial or name, it still puts that in the first name column and people with a
suffix, it totally messes up.

Thank you for your time.
and we said...
I've taken the verbose, pedantic approach on this -- but it is perhaps the
easiest to "understand"

Using inline views, we can sort of "procedurally" process this data -- work your
way from the inner most view out (running each on one by one to see what it
does)

ops$tkyte@ORA734> column name format a22
ops$tkyte@ORA734> column title format a5
ops$tkyte@ORA734> column first_name format a10
ops$tkyte@ORA734> column last_name format a10
ops$tkyte@ORA734> column middle format a7
ops$tkyte@ORA734>
ops$tkyte@ORA734>
ops$tkyte@ORA734> select name,
  2             decode(first,'Mr',first,'Mrs',first,'Ms',first,null) title,
  3             decode(first,'Mr',second,'Mrs',second,'Ms',second,first)
first_name,
  4             decode(first,'Mr',third,'Mrs',third,'Ms',third,second) middle,
  5         last_name
  6    from (
  7  select name, last_name, first,
  8             decode( instr(second,' '), 0, second,
                         substr(second,1,instr(second,' ')-1) ) second,
  9             decode( instr(second,' '), 0, '',
                         substr(second,instr(second,' ')+1) ) third
 10    from (
 11  select name,
 12         last_name,
 13             decode( instr(rest,' '), 0, rest,
                           substr(rest,1,instr(rest,' ')-1) ) first,
 14             decode( instr(rest,' '), 0, '',
                           substr(rest,instr(rest,' ')+1) ) second
 15    from (
 16  select name,
 17         decode( instr(name,','), 0, name,
                       substr(name,1,instr(name,',')-1) ) last_name,
 18         decode( instr(name,','), 0, '',
                       substr(name,instr(name,',')+1) ) rest
 19    from t
 20         )
 21         )
 22         )
 23  /

Here, name is the original input string and the rest is the "parsed"
representation

NAME                   TITLE FIRST_NAME MIDDLE  LAST_NAME
---------------------- ----- ---------- ------- ----------
Jones,John Paul              John       Paul    Jones
Bonham III,John H            John       H       Bonham III
Page Jr.,James Patrick       James      Patrick Page Jr.
Plant,Robert                 Robert             Plant
Grant,Peter J                Peter      J       Grant
Barrett,Mr Syd         Mr    Syd                Barrett
Waters,Mr George R     Mr    George     R       Waters

7 rows selected.

Don't know what you want to do with the "suffix" -- figured you might have
prefixes.  Getting the suffix out is "easier" I think -- just add that parse to
the second level inline view
Review & Followup
Rating: 5
It Works! March 03, 2003
Reviewer: Jim Salasek from Cleveland, OH USA
Exactly what I needed...Thank you very much.

Rating: 5
saved me some awful code May 02, 2003
Reviewer: George from USA
nesting the selects and snipping off the chunks of string with the same code was
the best idea
I ended up with
    select lvl4 ,
           lvl3,
                substr(rest,
                     (instr(rest,'/')+1),
                         instr(substr(rest,instr(rest,'/')+1,500),'/')  -1) as
lvl2,
            rtrim(substr(rest,instr(substr(rest,instr(rest,'/')+1,50),'/') +
1,100)) as lvl1, cbp
            from(
    select lvl4 ,
                substr(rest,
                     (instr(rest,'/')+1),
                         instr(substr(rest,instr(rest,'/')+1,500),'/')  -1) as
lvl3,
            substr(rest,instr(substr(rest,instr(rest,'/')+1,50),'/') + 1,100) as
rest, cbp
            from
    (select substr(cbp,
                     (instr(cbp,'/')+1),
                         instr(substr(cbp,instr(cbp,'/')+1,500),'/')  -1) as
lvl4,
                         substr(cbp,instr(substr(cbp,instr(cbp,'/')+1,50),'/') +
1,100) as rest,
        cbp
    from
    (select rpad('*',2*level,'*')||row_id , par_ou_id,
                sys_connect_by_path( row_id, '/' ) cbp
      from siebel.s_org_ext
      where level > 3
      start with par_ou_id  is null
      connect by prior row_id = par_ou_id
      order SIBLINGS by row_id)))

Rating: 3
solved :) August 19, 2003
Reviewer: juancarlosreyesp@yahoo.com from Bolivia
SELECT  BBB,
DECODE( TRUNC((ASCii(SUBSTR(BBB,1,1))-37.5)/10) /* only 1 when it's number */
,1,
RPAD( SUBSTR( BBB,1,LENGTH(BBB)-LENGTH(LTRIM( BBB,'1234567890')))
      ,3, '*' ),'***') ||
 SUBSTR(LTRIM( BBB,'1234567890'),1,3),

CASE WHEN TRUNC((ASCii(SUBSTR(BBB,1,1))-37.5)/10) /* only 1 when it's number */
= 1 THEN
RPAD( SUBSTR( BBB,1,LENGTH(BBB)-LENGTH(LTRIM( BBB,'1234567890')))
      ,3, '*' )
ELSE '***' END ||
 SUBSTR(LTRIM( BBB,'1234567890'),1,3)

FROM
(
SELECT 'ABC EFD11' BBB FROM DUAL
UNION ALL
SELECT '12A2BEFD32' BBB FROM DUAL
UNION ALL
SELECT '3ABs EFD34' BBB FROM DUAL
UNION ALL
SELECT '123ABs EFD22' BBB FROM DUAL
)

BBB                   DECODE(TRUNC((ASCII(SUBSTR(BBB
ABC EFD11                        ***ABC                      12A2BEFD32
             12*A2B
3ABs EFD34                       3**ABs
123ABs EFD22                     123ABs

If you can't use case, use only the field that uses decode
I used this TRUNC((ASCii(SUBSTR(BBB,1,1))-37.5)/10), so you could use decode
instead of case you too could use a between x and y with case

Rating: 3
:) one detail more, if you have less that 3 characters August 19, 2003
Reviewer: A reader
SELECT  BBB,
DECODE( TRUNC((ASCii(SUBSTR(BBB,1,1))-37.5)/10) /* only 1 when it's number */
,1,
RPAD( SUBSTR( BBB,1,LENGTH(BBB)-LENGTH(LTRIM( BBB,'1234567890')))
      ,3, '*' ),'***') ||
RPAD(  SUBSTR(LTRIM( BBB,'1234567890'),1,3),3, '*' ),

CASE WHEN TRUNC((ASCii(SUBSTR(BBB,1,1))-37.5)/10) /* only 1 when it's number */
= 1 THEN
RPAD( SUBSTR( BBB,1,LENGTH(BBB)-LENGTH(LTRIM( BBB,'1234567890')))
      ,3, '*' )
ELSE '***' END ||
RPAD(  SUBSTR(LTRIM( BBB,'1234567890'),1,3),3, '*' )

FROM
(
SELECT 'ABC EFD11' BBB FROM DUAL
UNION ALL
SELECT '12A2BEFD32' BBB FROM DUAL
UNION ALL
SELECT '3ABs EFD34' BBB FROM DUAL
UNION ALL
SELECT '123ABs EFD22' BBB FROM DUAL
UNION ALL
SELECT '123AB' BBB FROM DUAL
)

Rating: 3
Idea :) August 19, 2003
Reviewer: A reader
Hi Tom,
:( yes another one, the patiente is a virtue.
Even when I know this is not a place for entertaining, it could be interesting
that from time to time you could stablish some challenges (monthly) , like the
previous query.
Is nice and it helps to learn a lot.
For example the one who developed the best package in java for pop-up. The price
is to put your name in your list as the winner nothing more.
But I know that the problem comes again, about who is going to evaluate the
answers, and the answer is, you can guess, you and as you don't have time,
nothing is going to be done.
:(


Subject | Article | Last Updated (East coast time)
View on Concatenated columns | article only | Aug 20, 2003 09:10 pm
tracing - tkprof - parsing | article only | Aug 21, 2003 07:41 am
Connecting to SQL Server | article only | Aug 21, 2003 07:46 am
SQL Query Tuning | article only | Aug 21, 2003 07:56 am
Can hierachical query be used on flat structure? | article only | Aug 21, 2003 08:17 am
Oracle 9i Database Concepts Guide | article only | Aug 21, 2003 05:02 pm
Locate Previous SQL statement executed | article only | Aug 21, 2003 05:06 pm
Reg. DBMS_STAT | article only | Aug 21, 2003 05:26 pm
write "consistency" | article only | Aug 21, 2003 06:06 pm
INSTR and SUBSTR? | article only | Aug 21, 2003 06:10 pm
How to flush the db buffer cache | article only | Aug 21, 2003 06:57 pm
Log buffer size tuning | article only | Aug 21, 2003 07:06 pm
FK where ever possible? | article only | Aug 21, 2003 07:08 pm
Connection pools and global temporary tables | article only | Aug 21, 2003 07:34 pm
A Few Questions | article only | Aug 22, 2003 08:46 am
rollback segments | article only | Aug 22, 2003 08:48 am
Converting Dictionary Managed to LMT tablespace | article only | Aug 22, 2003 09:01 am
CURSOR SHARING | article only | Aug 22, 2003 07:36 pm
Two sessions signaling the same alert | article only | Aug 22, 2003 08:18 pm
dropping and adding partitions periodically | article only | Aug 23, 2003 09:59 am
To trace sql statements in a trigger | article only | Aug 23, 2003 10:26 am
Redo logs? | article only | Aug 23, 2003 10:27 am
Cursor optimization | article only | Aug 23, 2003 11:08 am
Querying history table | article only | Aug 23, 2003 12:10 pm
shrink the tablespace | article only | Aug 23, 2003 03:44 pm
Resource Manager | article only | Aug 23, 2003 06:29 pm
OCI_DURATION_PROCESS definition change in 9iR2 | article only | Aug 23, 2003 06:33 pm
Download source code and errata of Expert one-on-one | article only | Aug 23, 2003 08:53 pm
IOT and create table as | article only | Aug 23, 2003 09:32 pm
trim in sql*plus | article only | Aug 25, 2003 06:32 am
What is async IO ? | article only | Aug 25, 2003 06:38 am
UTC CONVERSION USING PLSQL | article only | Aug 25, 2003 06:49 am
unable to extend next_extent | article only | Aug 25, 2003 06:55 am
Oracle Security Information. | article only | Aug 25, 2003 02:40 pm
Stored Functions | article only | Aug 26, 2003 07:53 am
Database Performance: Things you should not do? | article only | Aug 26, 2003 10:30 am
9i and SQL Server 2000 | article only | Aug 26, 2003 10:52 am
Import and export between dbs with different character sets | article only | Aug 26, 2003 01:33 pm
row(s) 1 - 38 of 38
August 20, 2003 bhabani -- Thanks for the question regarding "View on Concatenated columns", version 8.1.7.0.0
You Asked
Hi Tom

Following is my experimentation and I would like to know how Oracle ASSIGNs the
datatype-length for the view columns.

=================
SQL> CREATE TABLE t5(
  2  a NUMBER,
  3  b VARCHAR2(50));

Table created.

SQL> CREATE OR REPLACE VIEW vt5 AS
  2  SELECT a||b x
  3   FROM t5;

SQL> DESC vt5
 Name                                      Null?    Type
 ----------------------------------------- -------- ---------------
 X                                                  VARCHAR2(90)

==================

Thanks and Regards
and we said...
it does it by maxing out things.

number is like number(38)

so, 38 digits + one sign space (-/+) + one space for '.' = 40

50+40 = 90.

Consider:

ops$tkyte@ORA920> create table t ( a number, b varchar2(50) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace view v as
  2  select to_char(a) a_string, a||b a_concat_b, substr(b,1,20) b_substr1,
substr( b,1,a ) a_substr2
  3    from t
  4  /

View created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> desc v
 Name                    Null?    Type
 ----------------------- -------- ----------------
 A_STRING                         VARCHAR2(40)
 A_CONCAT_B                       VARCHAR2(90)
 B_SUBSTR1                        VARCHAR2(20)
 A_SUBSTR2                        VARCHAR2(50)

ops$tkyte@ORA920>
Review & Followup
Rating: 4
May 26, 2003
Reviewer: bhabani from india
During number to character conversion why the dot '.' is required as you have
shown?

TIA

Followup:
-1234567890123456789012345.6789012345678

we need to be able to show that number, with a "." (decimal -- maybe a , where
you are)

Rating: 4
performance on a view with concatenated coulmn in where clause August 19, 2003
Reviewer: A reader
tom,

i have a query like this, on oracle 9ir2,

create or replace view vw_line_item as
select
rownum||'-'||a.x_om_office_cd||'-'||a.x_base_req_dsc||'-'||a.x_alt_req_dsc r1,
a.x_om_office_cd||'-'||a.x_base_req_dsc||'-'||a.x_alt_req_dsc
sub_requisition_num,
a.row_id row_id,
b.prod_id prod_id,
c.prod_cd prod_cd,
b.x_itm_num item_number,
b.qty_req quantity
from s_order a,s_order_item b,s_prod_int c where
b.prod_id = c.row_id
and a.row_id = b.order_id;

the columns that make sub_requisition_num are not indexed.
one of my technical manager said that the column sub_requisition_num made out of
concatenating three columns will have negative performance on the view if the
view is searched based on the sub_requisition_num - is that so?.

please suggest your opinion on this. what can i reply him.

From my understanding - i dont think it will have negative impact...he also said
that it will make a full table scan if that columns are not indexed.The columns
are made out of dsc, i dont think my dba will index that description columns ...

thanks for your time and consideration

Followup:
ask your technical manager -- so, with rownum in there does it really matter.

the serious issue is rownum!  not the concatentation

even if you didn't have the concat, rownum would pretty much preclude predicate
pushing.

the view looks totally 10000% like a bad idea to me -- but not because of the
concatenation -- its all about rownum.  whats up with that?  that same query run
two times against the same data could easily return different answers -- do you
intend for that to happen??

Rating: 4
rownum in r1 column to bring uniqueness of records & performance impact if i concatnate the columns August 19, 2003
Reviewer: A reader
tom,

thanks, i brought in rownum in r1 column to bring uniqueness of records. i am
not going to use it in my predicate. just that instead of keeping rownum, i have
contactnated few columns - does it still matter?..

"that same query run two times against the same data could easily return
different answers -- do you intend for that to happen?? "

i understand, but the values of other columns will not change -right?. i didnt
find any other method to bring uniqueness on that. please help me in corecting
the view.

in addition to this, what is the performance impact if i concatnate the columns
and use that in the predicate.

thankyou so much.

Followup:
glue in rowid instead.  it'll be unique, as obscure, and faster.

yes, using a predicate on  a||b||c will be not efficient in "general" unless you
use a FBI (function based index)

Rating: 5
August 20, 2003
Reviewer: Riaz Shahid from PRAL, Lahore, Pakistan
Hay ! you are talking about a view. What do you mean by a "uniqueness" in that
sense ??? You already have rowid (and that is unique). if you are going to
select rownum for serial no., you can easily do that on fly. Why you need to
store that ??? I can't understand the logic behined storing rownum. You can
simply do

select rownum,col1,col2,.....
from YourView;

OR

select rownum,a.*
from YourView a;

if you just want a serial no.

Riaz

Rating: 5
Function based index - not been used August 21, 2003
Reviewer: A reader
i dont have a key preserved table in my joins. the source database does not have
that. they maintain integrity of data at the business component level and there
are not pk's, fk's on their table. so i cannot use rowid.

Tom,

First - I am not sure if i have created index properly, please let me know the
right way...

I tried FBI. optimizer did not use it. please let me know where i am going
wrong. how to make my fbi that i created to work.

SQL>create index idx_fbi on
s_order(x_om_office_cd||'-'||x_base_req_dsc||'-'||x_alt_req_dsc);

Index created.
SQL>analyze index  idx_fbi compute statistics;

Index analyzed.

SQL>analyze table s_order  compute statistics;

Table analyzed.

SQL>sho parameter QUERY_REWRITE_INTEGRITY

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
query_rewrite_integrity              string  enforced
SQL>sho parameter QUERY_REWRITE_ENABLED

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
query_rewrite_enabled                boolean FALSE
SQL>alter session set QUERY_REWRITE_INTEGRITY=TRUSTED ;

Session altered.

SQL>alter session set QUERY_REWRITE_ENABLED=true;

Session altered.

SQL>sho parameter COMPATIBLE

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
compatible                           string  8.1.0
SQL>set autotrace on
SQL>select * from vw_line_item where SUB_REQUISITION_NUM='333-21GCX-A';

R1
SUB_REQUISITION_NUM    ROW_ID          PROD_ID         PROD_CD
     ITEM_NUMBER   QUANTITY

--------------------------------------------------------------------
--------------------- --------------------- ---------------
------------------------------ ----------- ----------
1-333-21GCX-A                                                        333-21GCX-A
       1-GZA           1-3W-273        PTE
1          1
8-333-21GCX-A                                                        333-21GCX-A
       1-GZA           1-3W-273        PTE
1          1
9-333-21GCX-A                                                        333-21GCX-A
       1-GZA           1-3W-273        PTE
1          1
10-333-21GCX-A                                                       333-21GCX-A
       1-GZA           1-3W-273        PTE
1          1
11-333-21GCX-A                                                       333-21GCX-A
       1-GZA           1-3W-655        PTE
2          1

Elapsed: 00:00:08.06

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2213 Card=93898
Bytes=10516576)
   1    0   VIEW OF 'VW_LINE_ITEM' (Cost=2213 Card=93898 Bytes=10516576)
   2    1     COUNT
   3    2       HASH JOIN (Cost=2213 Card=93898 Bytes=4601002)
   4    3         TABLE ACCESS (FULL) OF 'S_PROD_INT' (Cost=6 Card=775
Bytes=8525)
   5    3         HASH JOIN (Cost=2012 Card=93898 Bytes=3568124)
   6    5           TABLE ACCESS (FULL) OF 'S_ORDER' (Cost=100 Card=5361
Bytes=91137)
   7    5           TABLE ACCESS (FULL) OF 'S_ORDER_ITEM' (Cost=1222 Card=93898
Bytes=1971858)

Statistics
----------------------------------------------------------
        998  recursive calls
         71  db block gets
     108377  consistent gets
       7294  physical reads
          0  redo size
        573  bytes sent via SQL*Net to client
        316  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
         32  sorts (memory)
          0  sorts (disk)
          5  rows processed

SQL>select /*+ index( s_orders idx_fbi ) */ *  from vw_line_item where
SUB_REQUISITION_NUM='333-21GCX-A';

<output ignored>

Elapsed: 00:00:08.47

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2213 Card=93898
Bytes=10516576)
   1    0   VIEW OF 'VW_LINE_ITEM' (Cost=2213 Card=93898 Bytes=10516576)
   2    1     COUNT
   3    2       HASH JOIN (Cost=2213 Card=93898 Bytes=4601002)
   4    3         TABLE ACCESS (FULL) OF 'S_PROD_INT' (Cost=6 Card=775
Bytes=8525)
   5    3         HASH JOIN (Cost=2012 Card=93898 Bytes=3568124)
   6    5           TABLE ACCESS (FULL) OF 'S_ORDER' (Cost=100 Card=5361
Bytes=91137)
   7    5           TABLE ACCESS (FULL) OF 'S_ORDER_ITEM' (Cost=1222 Card=93898
Bytes=1971858)

Statistics
----------------------------------------------------------
         22  recursive calls
         66  db block gets
     108198  consistent gets
       7289  physical reads
          0  redo size
        573  bytes sent via SQL*Net to client
        316  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          4  sorts (memory)
          0  sorts (disk)
          5  rows processed

SQL>alter session set optimizer_index_cost_adj=1;

Session altered.

SQL>select /*+ index( s_orders idx_fbi ) */ *  from vw_line_item where
SUB_REQUISITION_NUM='333-21GCX-A';

<output ignored>

Elapsed: 00:00:07.54

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2101 Card=93898
Bytes=10516576)
   1    0   VIEW OF 'VW_LINE_ITEM' (Cost=2101 Card=93898 Bytes=10516576)
   2    1     COUNT
   3    2       NESTED LOOPS (Cost=2101 Card=93898 Bytes=4601002)
   4    3         NESTED LOOPS (Cost=224 Card=93898 Bytes=3004736)
   5    4           TABLE ACCESS (FULL) OF 'S_PROD_INT' (Cost=6 Card=775
Bytes=8525)
   6    4           TABLE ACCESS (BY INDEX ROWID) OF 'S_ORDER_ITEM' (Cost=1
Card=93898 Bytes=1971858)
   7    6             INDEX (RANGE SCAN) OF
'IDX_S_ORDER_ITEM_PROD_ID'(NON-UNIQUE) (Cost=1 Card=93898)
   8    3         TABLE ACCESS (BY INDEX ROWID) OF 'S_ORDER' (Cost=1 Card=5361
Bytes=91137)
   9    8           INDEX (RANGE SCAN) OF 'IDX_SORDER_ROW_ID' (NON-UNIQUE)
(Cost=1 Card=5361)

Statistics
----------------------------------------------------------
          8  recursive calls
         66  db block gets
     108194  consistent gets
       7291  physical reads
          0  redo size
        573  bytes sent via SQL*Net to client
        316  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          5  rows processed

SQL>select /*+ FIRST_ROWS */ *  from vw_line_item where
SUB_REQUISITION_NUM='333-21GCX-A';

<output ignored>

Elapsed: 00:00:20.03

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=2101 Card=93898
Bytes=10516576)
   1    0   VIEW OF 'VW_LINE_ITEM' (Cost=2101 Card=93898 Bytes=10516576)
   2    1     COUNT
   3    2       NESTED LOOPS (Cost=2101 Card=93898 Bytes=4601002)
   4    3         NESTED LOOPS (Cost=224 Card=93898 Bytes=3004736)
   5    4           TABLE ACCESS (FULL) OF 'S_PROD_INT' (Cost=6 Card=775
Bytes=8525)
   6    4           TABLE ACCESS (BY INDEX ROWID) OF 'S_ORDER_ITEM' (Cost=1
Card=93898 Bytes=1971858)
   7    6             INDEX (RANGE SCAN) OF
'IDX_S_ORDER_ITEM_PROD_ID'(NON-UNIQUE) (Cost=1 Card=93898)
   8    3         TABLE ACCESS (BY INDEX ROWID) OF 'S_ORDER' (Cost=1 Card=5361
Bytes=91137)
   9    8           INDEX (RANGE SCAN) OF 'IDX_SORDER_ROW_ID' (NON-UNIQUE)
(Cost=1 Card=5361)

Statistics
----------------------------------------------------------
         14  recursive calls
          4  db block gets
     572945  consistent gets
      20096  physical reads
          0  redo size
        591  bytes sent via SQL*Net to client
        316  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          4  sorts (memory)
          0  sorts (disk)
          5  rows processed

I dont see my index idx_fbi being used.

Thanks a lot

Rating: 5
August 21, 2003
Reviewer: Riaz Shahid from PRAL, LAhore, PAkistan
Well...

I think the CBO is smart enough to think "Hay ! I am getting the same cost in
full table scan Vs index scan...So i am using full scan".

I can't see any significant difference in explain plains (In fact, queries using
indexes are little bit "bad"). Also look at the last query's explain plain and
its consistent gets...its much more than the first query (Not using index).

Index doesn't **always" mean/imply fast....REMEMBER THAT.

Riaz

Rating: 5
function based index August 21, 2003
Reviewer: A reader
Tom,

my question is -

1. have i created the function based index properly
2. why cbo ignored even when i provided hints to use the fbi

Thanks for your time and consideration

August 21, 2003 b -- Thanks for the question regarding "tracing - tkprof - parsing", version 8.1.7.0
You Asked
Hi Tom

I did a sql trace for a session and got the following tkprof output:

............
............
BEGIN tsp_da_PositionRelease(150, 72956, :sResp, :sMsg); END;
/
BEGIN tsp_da_PositionRelease(176, 72949, :sResp, :sMsg); END;
/
............
............

[I used "record" parameter with tkprof]

The :sResp, :sMsg are the out parameter in the stored proc.
I wanted to know if the 1st two parameters are dynamic i.e whether the
application is using Bind variables or not.

Q1-- So I want to know the sqltrace file contents are Before the binding or
after the binding. I have idea that binding of the value to the variable is
during run time but there is a state of time when the variables are replaced
with Values. So does sqltrace takes the statements after binding?

------------------
The application which calls this stored proc is a C++ program and I have no idea
how bind variables can be implemented there (what I know is from a java program
use of prepared/callable statements with "? " makes them bind variables).

So can I give a solution like:

create or replace procedure new_proc(
p1 number,
p2 number,
p3 out varchar2,
p4 out varchar2)
begin
        tsp_da_PositionRelease(:p1,:p2,:p3,:p4);
end;
/

The C++ will be calling new_proc(-,-,-,-).
Q2-- I suppose i am using bind variables for tsp_da_PositionRelease. Am I
correct?

--------------------
Q3-- When a stored proc calls another stored proc2, will stored proc2 be listed
in the sqltrace file?

Thanks and Regards Tom
and we said...
the application is CLEARLY NOT using bind variables else you would see one parse
of:

begin tsp_da_PositionRelease( :x, :y, :sResp, :sMsg ); end;

the so very sad thing here is the coder OBVIOUSLY knows all about binds, they
just choose purposely not to use them.  sigh, so sad.  so so sad.

the trace files show you what the develop submitted to the database.  we do not
rewrite the sql with literals after the fact. (we can however do the opposite,
rewrite the sql with BINDS replacing all literals but that is another story)

The coder already KNOWS how to use binds -- they used 2 of them here!!!!!!

q3-- only if they call it dynamically which would be bad in general.
Review & Followup
Rating: 5
I agree, it's sad... August 18, 2003
Reviewer: Peter Tran from Houston, TX USA
We have the same problem at my work.  I've been harping on the programmers to
use bind variables, and so they spent their time to convert their JAVA
Statements to use PreparedStatements with bind variables.

It turns out that one programmer was using an IN list with a variable number of
values.  Rather than converting this part of the predicate to use bind variables
too, he/she left it as is.  So each SQL was a unique statement even though, the
SQL was using bind variables appropriately in other places.

I got in a very heated debate when I said that even if you're using bind
variables, the IN list causes Oracle to treat each SQL as a unique SQL and so
you're still hard parsing the SQL.  *aaaaahhhhhhhhhh*

-Peter

Rating: 4
August 20, 2003
Reviewer: b from bangalore
Hi Tom

Referring to the 1st post, if we incorporate a new procedure new_proc which the
C++ program be calling with some BINDs replacing all literals by:

create or replace procedure new_proc(
p1 number,
p2 number,
p3 out varchar2,
p4 out varchar2)
begin
        tsp_da_PositionRelease(150, 72956, :sResp, :sMsg);
end;
/

then, will there be performance gain (as I suppose it will avoid reparsing of
tsp_da_positionrelease).

-----------
Ref: Q3: Why it would be bad if the stored called proc2 is called dynamically?

Thanks and Regards

Followup:
how would that help?????

you have an infinite set of values for the first two:

...
BEGIN tsp_da_PositionRelease(150, 72956, :sResp, :sMsg); END;
/
BEGIN tsp_da_PositionRelease(176, 72949, :sResp, :sMsg); END;
/
....

they need to simply prepare:

begin tsp_da_PositionRelease( :1, :2, :3, :4 ); end;

bind all 4, execute it.  done.

as for q3 -- anytime you can do it statically in plsql, the better.

it should be obvious that calling a procedure P directly would be faster then
building a string, to pass to execute immediate, which has to parse the string,
find out if you are allow to call P (after figuring out, what is this P thing)
and then calling P.

static compile time binding = fast
dynamic runtime binding = slower then static compile time binding...

August 21, 2003 Dennis -- Thanks for the question regarding "Connecting to SQL Server", version 8.16
You Asked
I am looking for the best way to move data from Oracle to SQL
Server 7.0 using the Oracle database.  I have a SQL Server
databse behind a firwall that cannot get out to Oracle but the
Oracle database can potentially get in.  I heard something about
an Oracle Gateway to Sql Server but I can'tremember where I saw
it.  Any thoughts?
Thanks.
and we said...
See
http://www.oracle.com/gateways/
Another option in 8.1.6 is to actually load a "thin" jdbc driver
for sqlserver directly into Oracle.  Then a java stored
procedure can actually open a connection to another database
from within the database itself.  I've not done this with
SQLServer but have had success using this technique with DB2
personally.
Review & Followup
Rating: 3
connecting to sql server April 08, 2002
Reviewer: kit from england
Tom,
would you have examples of how to move data from oracle to sql server

Followup:
umm, let me think about that -- nope, i don't happen to have any of that lying
about ;)  Not much use for sqlserver where I work.

You can use the gateways (insert into sqlserver_table select * from
oracle_table)

You can use "flat" to get the data out and BCP it back in:
http://osi.oracle.com/~tkyte/flat/index.html
I've heard you can use access to "attach" to two odbc data sources and copy data
from one to the other...

Rating: 3
Try DTS in SQL 2k April 08, 2002
Reviewer: A reader
in SQL 2k there is a service or a functionality that allows you to connect and
move data form oracle to sql 2k
using OLEDB

 you can use that....

Rating: 3
From SQL Server to Oracle August 20, 2003
Reviewer: Asim Naveed from Pakistan
Hi,

I have two databases, 1 in oracle and 1 in SQL server.
both databases are duplicate of eachother.
i.e both have the same star schema datawarehouse of about
2,700,000 records. I used import/export feature of
DTS(a tool provided by microsoft for transfering data
between differrent databases) to transfer this schema
between SQL SERVER and Oracle.

When I do ORACLE to SQL SERVER transfer it finishes very
quickly (hardly 5 minutes). But when I do SQL SERVER to
ORACLE transfer it takes very very long time. I am right
now doing this , its half an hour passed and less than 12
percent is complete.

Can you please give me some hints that where the problem
might be.

Thanks

Followup:
what tuning/setup have you done on Oracle?

do you have "checkpoint not complete" messages in your alert log?
how are things sized?

you do know that dts is a tool that just happens to work against Oracle using
MS's api they call "odbc" right?  it isn't exactly the optimum way to blow lots
of data into Oracle.

On my desktop pc -- what you would run word processing and such on -- I can load
that data in less then 5 minutes myself -- using things "Oracle".

Using sqlserver tools against <any database other then sqlserver> is going to be
less then effcient.

August 21, 2003 Irfan -- Thanks for the question regarding "SQL Query Tuning", version 9i
You Asked
Hello Tom,

I have a live table (Investor) with customers records and another table
(Invdatew) has the same records but have one extra column for date,. After each
end of day I use dbms_job to insert the the exact data from live table to the
other one plus sysdate. But it takes so long that it takes approximately three
hours for inserting 36 rows (rows depends on new entries per day). Can you
please help tune this query.

SQL> insert into invdatew
     select inv, name, nama, type, clas, stat, act, rbrk, rest, reme,
     adde, adda, cnum, dload, sysdate from investor
     where inv not in
     (select inv from invdatew);

36 rows created.

Execution Plan
----------------------------------------------------------
   0      INSERT STATEMENT Optimizer=CHOOSE (Cost=170 Card=3672 Bytes=
          168912)

   1    0   FILTER
   2    1     TABLE ACCESS (FULL) OF 'INVESTOR' (Cost=170 Card=3672 By
          tes=168912)

   3    1     TABLE ACCESS (FULL) OF 'INVDATEW' (Cost=196 Card=2614 By
          tes=33982)

Statistics
----------------------------------------------------------
          0  recursive calls
     284958  db block gets
   47270538  consistent gets
   42091341  physical reads
       4528  redo size
        886  bytes sent via SQL*Net to client
        808  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         36  rows processed

Thanks
and we said...
Well, not having the table defs makes it a bit of a guess but I'll guess that
inv is defined as nullable which prevents us from doing some nice optimizations.
 Here is an example where I compare the results of three inserts and their
performance.  This should get you going.  Suggest the hash anti-join method as
being the probable "best".  Depends on the size of the tables really.  Also, I
assume an index on invdatew for the NOT EXISTS query, if no such index, don't do
that one!

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

Table dropped.

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table invdatew
  2  as
  3  select 1 inv, a.*, sysdate dt from all_objects a where 1=0;

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table investor
  2  as
  3  select 1 inv, a.* from all_objects a where 1=0;

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create index invdatew_idx on invdatew(inv);

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec gen_data( 'INVDATEW', 5000);

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec gen_data( 'INVESTOR', 5000);

PL/SQL procedure successfully completed.

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

Table analyzed.

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

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into invdatew
  2  select a.*, sysdate from investor a
  3   where inv not in (select inv from invdatew);

5000 rows created.

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

Rollback complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into invdatew
  2  select a.*, sysdate from investor a
  3   where inv is not null
  4     and inv not in (select /*+ HASH_AJ */ inv from invdatew where inv is not
null );

5000 rows created.

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

Rollback complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into invdatew
  2  select a.*, sysdate from investor a
  3   where not exists ( select *
  4                        from invdatew
  5                       where invdatew.inv = a.inv );

5000 rows created.

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

Rollback complete.

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

Now, the three queries are *different* in that if INV is null in invdatew -- the
first one returns a different answer (no rows) where as the second two might
return some rows -- see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:442029737684
for the reasoning behind that...

So, I think the second two queries are OK for you (if you have null in inv in
invdatew you would always get ZERO rows -- probably not what you wanted right?)

So, looking at the tkprof for these inserts we see hugely different performance
characteristics:

insert into invdatew
select a.*, sysdate from investor a
 where inv not in (select inv from invdatew)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.02          0          0          0           0
Execute      1     85.64      86.08          0    1974190      31754        5000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2     85.66      86.10          0    1974190      31754        5000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 511

Rows     Row Source Operation
-------  ---------------------------------------------------
   5001  FILTER
   5001   TABLE ACCESS FULL INVESTOR
   5000   TABLE ACCESS FULL INVDATEW

This query was processed like this:

   for each row in investor
   loop
       FULL SCAN INVDATEW making sure that INV not in there
   end loop

that is -- 5000 full scans of INVDATEW (5000 = number of rows in investor!)

********************************************************************************

insert into invdatew
select a.*, sysdate from investor a
 where inv is not null
   and inv not in (select /*+ HASH_AJ */ inv from invdatew where inv is not null
)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.67       3.84          0        550      10631        5000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.68       3.85          0        550      10631        5000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 511

Rows     Row Source Operation
-------  ---------------------------------------------------
   5001  HASH JOIN ANTI
   5000   TABLE ACCESS FULL INVESTOR
   5000   VIEW VW_NSO_1
   5000    INDEX FAST FULL SCAN (object id 44573)

Wow -- that is different -- from over 85 cpu seconds down to .6, over 86
seconds runtime to under 4 seconds.

This was processed sort of like this:

    for every row in outer join investor to invdatew
          if invdatew.inv is null then insert this record

Much more efficient...
********************************************************************************

insert into invdatew
select a.*, sysdate from investor a
 where not exists ( select *
                      from invdatew
                     where invdatew.inv = a.inv )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.72       4.32          0      10672      10623        5000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.72       4.32          0      10672      10623        5000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 511

Rows     Row Source Operation
-------  ---------------------------------------------------
   5001  FILTER
   5001   TABLE ACCESS FULL INVESTOR
   5000   INDEX RANGE SCAN (object id 44573)

Now, not as good as the hash_aj but.... very close, very close.  The logical
IO's would make me avoid this approach however, rather do 550 LIO's then 10,672.
This was processed like this:

     for every record in investor
           run a subquery that uses an index to find a row in invdatew
           if not found
                 then insert
           end if;
     end loop

Hopefully, you can use that hash_aj and your query will run in minutes or less.
Review & Followup
Rating: 5
Great !!! August 21, 2002
Reviewer: Irfan from Kuwait
Hello Tom,
Thanks very much for the solution. I worked fine, just took less than 40 seconds
(compared to 3 hours, wow..). You are so humble and nice.

Followup:
Dig it -- did it 270 times faster ;)  sweet...

which one did you end up using?

Rating: 5
As you suggested -- Second One August 22, 2002
Reviewer: Irfan from Kuwait
<quote>
Hopefully, you can use that hash_aj and your query will run in minutes or less.
</quote>

Hi Tom,
I used the second one and it just fires. Amazed... Never know that hash_aj is so
powerful - and your ideas are so brilliant.

Thanks.

Rating: 5
what is hash_aj September 27, 2002
Reviewer: rajiv
tom,

brilliant

what is this hash_aj?how it worked? is it new feature in 9i?

thanks
rajiv

Followup:
It is a CBO thing, around for a while -- the example was in 817 here.
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76992/hints.htm#21060

Rating: 5
Terrific! March 28, 2003
Reviewer: Urs from Germany
Tom,

the "where inv is not null" thing saved me more than 99% execution time. How can
I rate 6 stars? (I am not worthy...)

However, shouldn't the CBO be clever enough to know that NOT NULL fields will
always be not null?

Followup:
inv was not "not null"

if the columns is NOT NULL in the data dictionary -- yes, it is "smart enough".
If you do not tell it -- if the field permits NULL's then it is smart enough to
know that and goes for the correct answer instead of a fast wrong one.

Rating: 5
Excellent!!! March 28, 2003
Reviewer: Mirjana from Barcelona

Rating: 3
Why not auto? March 28, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
Why did not the CBO choose to use HASH_AJ in this case?

For example, if we are to manually define the statistics for those tables, what
should they be in order for the CBO to choose to use HASH_AJ without giving a
hint to the query?

Followup:
and can and will -- especially in 9i (incremental improvements).

also -- just discovered the test database I was using had always_anti_join =
nested_loops ;(

turning that off -- we get:

insert into invdatew
select a.*, sysdate from investor a
 where inv is not null
   and inv not in (select inv from invdatew where inv is not null )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.01       0.02          0          0          0           0
Execute      2      0.72       4.86        350        556      11122        5000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.73       4.88        350        556      11122        5000

Misses in library cache during parse: 2
Optimizer goal: CHOOSE
Parsing user id: 687

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  HASH JOIN ANTI
      0   TABLE ACCESS FULL INVESTOR
      0   VIEW VW_NSO_1
      0    INDEX FULL SCAN (object id 55526)

right all -- hash aj.

Rating: 5
NOT NULL column on remote table March 31, 2003
Reviewer: Urs from Germany
Thanks for your reply, Tom.

In my case, I guess the CBO had no chance to know that the column is declared
NOT NULL as it is selected from a remote database across a db link.

Is there a way to help the CBO in using the remote db's data dictionary?

Urs

Followup:
distributed stuff changes all of the rules -- not every access plan is
available.

Rating: 4
ALWAYS_ANTI_JOIN April 01, 2003
Reviewer: Kashif from Houston, TX
Hi Tom,

I know this parameter is obsolete in 9i, but I was wondering what the value
NESTED_LOOPS means. Thanks.

Kashif

Followup:
means to use a nested loops anti join and is the reason my 8i example originally
had a HASH_AJ hint in it.

Rating: 5
Awesome! April 02, 2003
Reviewer: Max from Moscow, Russia
subj.

Rating: 5
How about using Append Hint August 11, 2003
Reviewer: A reader
Does it run more efficiently if we use the append hint along with the hash_AJ
hint ?

Followup:
maybe, maybe not.

it won't reuse any existing free space in the table if you do.

your dba must (has to, imperative) schedule a backup around your processing if
you do this in nologging mode.

it won't have a tremendous affect if there are lots of indexes on the table (as
they are always logged)

and given that the original question had:

...
But it takes so long that it takes approximately three
hours for inserting 36 rows
.....

for 36 rows -- append isn't going to save much of anything!

Rating: 4
Superb Tom! August 13, 2003
Reviewer: Denise from Virginia, USA
how do you assign always_anti_join=nested_loops in 8.1.7.

I had never known this existed until reading this thread.

We have queries that run excruciatingly slow and now I will
go back and take a look at them. Will print out your examples(I have a "script"
book that I keep with all your examples from this website) and add to the
others.

Followup:
ops$tkyte@ORA817DEV> show parameter always

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
always_anti_join                     string  NESTED_LOOPS
always_semi_join                     string  standard
ops$tkyte@ORA817DEV>

they are just init.ora parameters....

Rating: 5
Any Difference? August 13, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
Is there a difference between the two?

SQL> alter table emp add constraint ck_emp_deptno check(deptno is not null) ;

and

SQL> alter table emp modify (deptno not null) ;

The reason I ask is because if I use the first ALTER TABLE and then do the
following query, I get this plan:

SQL> select * from emp where deptno not in (select /*+ HASH_AJ */ deptno from
dept) ;

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=40)
   1    0   FILTER
   2    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=1 Bytes=40)
   3    1     INDEX (FULL SCAN) OF 'PK_DEPT' (UNIQUE) (Cost=1 Card=1 Bytes=2)

And, When I use the second ALTER TABLE command and then run the query again, I
get this plan:

SQL> select * from emp where deptno not in (select /*+ HASH_AJ */ deptno from
dept) ;

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=1 Bytes=42)
   1    0   HASH JOIN (ANTI) (Cost=5 Card=1 Bytes=42)
   2    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=14 Bytes=560)
   3    1     INDEX (FULL SCAN) OF 'PK_DEPT' (UNIQUE) (Cost=1 Card=4 Bytes=8)

Are the two alter table statements behave differently?

Thanks,

Followup:
you just found the difference then.

the check constraint is just that -- a check constraint.  it could be "check the
sky is blue" for all we know.  It is not a NOT NULL constraint.

in a not in, the subquery must be known to NOT return nulls for the anti-join

you could add the predicate "where column is not null" as well

Rating: 5
I Agree. August 13, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
One reason of my question was that at the client site they always insist that
all integrity constraints (including NOT NULL) must be enforced using ALTER
TABLE ADD CONSTRAINT and never use the ALTER TABLE MODIFY.

I was looking for a example where it would make a difference and found one.
Thanks.

To sum it up, would you then say that NOT NULL check must always and always be
enforced using ALTER TABLE MODIFY?
Thanks,

Followup:
by definition a not null can only be added via modify -- not add constraint.
the wire diagrams in the sql reference tell us so.
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/statements_32a.htm#2104002
shows we can add an "out of line" constraint with ADD
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/clauses3a.htm#1001938
shows us NOT NULL is an "in line" constraint only.

the syntax they are looking for for NOT NULL is:

ops$tkyte@ORA920LAP> alter table t modify ( x constraint x_not_null not null );

Table altered.

it is imperative they use NOT NULL and not a check constraint that checks if X
is not null if they would like the optimizer to be able to fully do its job.

Rating: 3
??? could you please explain in detail ??? August 14, 2003
Reviewer: A reader
I've to admit, I didn't get your answer above:

<quote>
you just found the difference then.

the check constraint is just that -- a check constraint.  it could be "check the
sky is blue" for all we know.  It is not a NOT NULL constraint.

in a not in, the subquery must be known to NOT return nulls for the anti-join

you could add the predicate "where column is not null" as well
</quote>

could you please provide some further explanation?

Followup:
umm, one of them tells the optimizer

a) nothing  (that is the check constraint.  to the optimizer that is just that
-- some ARBITRARY check constraint)

the other tells the optimizer

b) the column does not allow null values, you never need to concern yourself
that the column would be NULL -- it always has a value.

Rating: 3
not able to tune the query August 14, 2003
Reviewer: mohsin from INDIA
Hi
Need help in tuning the data warehouse query
select
borrowername,
     DESDEALER,
     DESSOURCE,
      dimrepmonth,
            locdealer,
            lan,
      dimdisbmonth,
     disbdate,
     dimstartmonth,
     DIMCURROSMONTHS,
     dimcurrdpd,
     subitem,
     desdealer,
     BRDITEM,
     desitem,
     a.dimitem,
     TOTALNETDISBAMT,
     TOTALGROSSDISBAMT,
     TOTALEMIOUTSTANDING,
     TOTALPRINOUTSTANDING,
     TOTALINTOUTSTANDING,
     TOTALOUTSTANDING,
     descbucket
from     mining.crash_monthlyfact a,
     MINING.CRASH_DIMDEALER C,
       mining.crash_dimitem d,
     mining.crash_dimbucket e,
     mining.crash_dimemi h,
     mining.crash_dimsource f
where       a.DIMitem =d.dimitem
AND         A.DIMDEALER=C.DIMDEALER
and         a.dimcurrbkt=e.dimbucket
and         a.dimemi=h.dimemi
and a.dimsource=f.dimsource
and a.dimcurrbkt>=2
and c.locdealer='HYDERABAD'
AND a.DISBDATE >=to_date('01-JAN-02')

The execution Path of the Query is :::
 0      SELECT STATEMENT Optimizer=CHOOSE (Cost=10150 Card=3896 Byte
        s=907768)

 1    0   HASH JOIN (Cost=10150 Card=3896 Bytes=907768)
 2    1     HASH JOIN (Cost=10126 Card=3896 Bytes=720760)
 3    2       TABLE ACCESS (FULL) OF 'CRASH_DIMSOURCE' (Cost=6 Card=
        19880 Bytes=397600)

 4    2       NESTED LOOPS (Cost=10115 Card=4081 Bytes=673365)
 5    4         HASH JOIN (Cost=10115 Card=4081 Bytes=665203)
 6    5           TABLE ACCESS (FULL) OF 'CRASH_DIMBUCKET' (Cost=1 C
        ard=14 Bytes=126)

 7    5           HASH JOIN (Cost=10113 Card=4081 Bytes=628474)
 8    7             TABLE ACCESS (FULL) OF 'CRASH_DIMDEALER' (Cost=2
        3 Card=248 Bytes=9672)

 9    7             TABLE ACCESS (FULL) OF 'CRASH_MONTHLYFACT' (Cost
        =10086 Card=344633 Bytes=39632795)

10    4         INDEX (RANGE SCAN) OF 'CRASHDIMEMI' (NON-UNIQUE)
11    1     TABLE ACCESS (FULL) OF 'CRASH_DIMITEM' (Cost=17 Card=213
        65 Bytes=1025520)

The execution plan shows the FTS of CRASH_MONTHLYFACT.. Althougt the DISBDATE
column of CRASH_MONTHLYFACT is indexed using a B tree
PLs help us ...

Followup:
so,

a) how many rows does crash_monthly fact actually have?

b) are the statistics totally up to date?  show me the output of

set autotrace traceonly explain
select * from crash_monthlyfact

c) how many rows in that table SATISFY the predicate on the date?

d) how many blocks in that table and what is the clustering factor of the index
thereon?

e) you do know that indexes do not in any way shape or form imply "i will be
fast", they as often imply "i'll be as slow as molasses in feburary in alaska"

Rating: 4
TUNING OF SQL QUERY August 20, 2003
Reviewer: Mohsin from INDIA
a) how many rows does crash_monthly fact actually have?
Ans:2081311

b) are the statistics totally up to date?  show me the output of

set autotrace traceonly explain
select * from crash_monthlyfact

Ans:
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=10086 Card=2081311 B
          ytes=1371583949)

   1    0   TABLE ACCESS (FULL) OF 'CRASH_MONTHLYFACT' (Cost=10086 Car
          d=2081311 Bytes=1371583949)

c) how many rows in that table SATISFY the predicate on the date?
Ans: 739770
d) how many blocks in that table and what is the clustering factor of the index
thereon?
Ans:
   BLOCKS
---------
   112000

CLUSTERING_FACTOR
-----------------
           918023
PLS GUIDE..

Followup:
so, why do you believe that using this index would be useful?

everything I see indicates "it would not"

When the cluster factor is nearer the number of rows in the table and far from
the number of blocks in the table -- that means the data in the table is not
sorted by the index itself. Meaning as we index range scan thru that index,
we'll be reading lots of blocks all over the place -- not just a few blocks.

facts:

o index and table are not sorted the same

o reading N rows via index will result in N different blocks being accessed in
the table. (it is nice when reading N rows via an index results in 1 or 2 blocks
being accessed -- your cluster factor however indicates N rows => N blocks)

o reading 112,000 table blocks using single block IO (and reading and re-reading
many of those blocks 7,8,9,10 times bear in mind, as we skip around) is
INFINITELY less effecient then full scanning.

Do this for us, HINT the query, get the index plan you think would be better and
then post us the benchmark results between the two!

August 21, 2003 Jerry -- Thanks for the question regarding "Can hierachical query be used on flat structure?", version 8.1.7
You Asked
Hierarchical queries can be used when there is a top-down relationship.  What's
the best way to walk down a flat relationship?  For example, I have a switchlink
table that describe that relationship between connected switches.

SQL> desc switchlink
 Name                                      Null?    Type
 ----------------------------------------- -------- ---------------
 switch1id                                 NOT NULL NUMBER(10)
 switch2id                                 NOT NULL NUMBER(10)

SQL> select * from switchlink;
     SWITCH1ID            SWITCH2ID
-------------- --------------------
         14854                14890
         14890                14854
         14890                10097
         10097                14890
         11273                10097
         10097                11273

One switch can connect to another switch that is connected to other switches.
The table has to put the relationship twice but just switch the values to
indicate the link can start from each side.

I'd like to find out what's the complete switchlink map, e.g.
14854-->14890-->10097-->11273-->....  In order to do that, I have to use inner
joins.  It has two problems:
1) I don't know how many switches are connected and how many inner joins I would
need.
2) The query becomes very slow with the increase of data.

Is there any easy way to handle this?  Or should the data model be changed, if
so, how?

Thanks!
and we said...
you may be able to use this:

ops$tkyte@ORA920> select * from t;

       ID1        ID2
---------- ----------
     14854      14890
     14890      14854
     14890      10097
     10097      14890
     11273      10097
     10097      11273

6 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select rpad( '*', level, '*' ) lvl, id1, id2
  2    from t
  3   start with id1 = 14854
  4   connect by prior id2 = id1 and prior id1 <> id2
  5  /

LVL               ID1        ID2
---------- ---------- ----------
*               14854      14890
**              14890      10097
***             10097      11273

to avoid the infinite loop -- not thoroughly tested, didn't try all
combos/boundary cases.  let me know how it works....
Review & Followup
Rating: 5
Great Solution! August 17, 2003
Reviewer: Jerry from USA
It's exactly what I am looking for.  Tested on real data and it works perfectly.
Can't tell you how I grateful I am.  Thanks a million!

Rating: 5
performance on batch processing August 18, 2003
Reviewer: Jerry from USA
Tom,

One more followup question on this.  Your method works very well for a single
row processing, i.e. if a bind variable is passed in for

start with id1 = :id1

However, the performance seems to be very slow if I use this in batch
processing.  Let's say I have many switches, for each of the switch, I'll get
the connected switches that match a certain criteria (e.g, connect to an array)
and put those in a table.

The code would be like:

BEGIN
   FOR i IN (SELECT start_switchid
               FROM switch_pool)
   LOOP
      INSERT INTO RESULT_SET
         SELECT a.id2, b.array_id
           FROM (SELECT     id2
                       FROM t
                 START WITH id1 = i.start_switchid
                 CONNECT BY PRIOR id2 = id1 AND PRIOR id1 <> id2) a,
                (SELECT switch_id, array_id
                   FROM switch_array_link
                  WHERE link_type = 'switch_to_array') b
          WHERE a.id2 = b.switch_id;
   END LOOP;
END;

The processing is really slow.  Is there any way to speed this up?

Thanks again for the help!

Followup:
create tables/indexes and the plan would be useful (tkprof would be great!)

Rating: 5
ORA-01436 CONNECT BY loop in user data August 19, 2003
Reviewer: Jerry from USA
Thanks.  I recreated the testing table.  Forgot to put indexes on.

Another question: I got the ORA-01436 error using the first query.  I believe
it's caused by the following scenarios:

HostA-->SwitchA-->SwitchB-->SwitchC--SwitchD-->SwitchB

Is there anyway to stop before last hop that produced the problem and have only
the following?
HostA-->SwitchA-->SwitchB-->SwitchC--SwitchD

Thanks as always!

Followup:
not unless there is some rule like "switches will always be related in
increasing order" or something (so we can put a predicate in the connect by to
filter on like I did already)

Rating: 4
Follow up August 20, 2003
Reviewer: Jerry from USA
I was thinking one of those two options:
1) Exclude all switches that are previously found in the loop

e.g,
have the following connections:
startpoint-->switchA-->switchB--switchC--switchD-->switchA

for each of connections, exclude all non-intermediate connections found earlier]

for switch D, it would be like:
SELECT     RPAD ('*', LEVEL, '*') lvl, id1, id2
      FROM t
where id2 not in (startpoint, switchA, switchB)
START WITH id1 = startpoint
CONNECT BY PRIOR id2 = id1 AND PRIOR id1 <> id2

In 9, there is the sys_connect_by_path that can help with the rollups for the
not in list.  But I can't seem to make it work in the where clause.

2. Is there a way to capture the level where ORA-01436 error occured?  Then stop
before that level?

Followup:
where is evaluated AFTER the connect by though.

and sys_connect_by_path cannot be used in the connect by part (which is where it
would have to be)

2) no

Rating: 4
Doable in 10i August 21, 2003
Reviewer: Jerry from USA
So the short answer is that it is not doable in oracle, right?

Do you know if I can do this in 10i?  There is a legitimate need to stop before
the infinite loop.

August 21, 2003 -- Thanks for the question regarding "Oracle 9i Database Concepts Guide", version 9iR2
You Asked
Tom,

I have learned a lot from your site.  One point that you constantly bring up
that wasn't getting through my thick head though was, READ THE CONCEPTS GUIDE.
So over the past couple of weeks, I put a copy of the pdf version on my laptop
and worked my way through it.  I am very glad that I did (a re-read is comming
soon)!!  Many things I didn't understand are clear now and being a consultant
that works with a lot of other developers and DBAs, I know most all of you have
not read this either.  Anyway, my question is...  Do you have a ranking list of
other documentation developers and DBAs should read?

Like
1) Concepts Guide
2) ....
etc.

Thanks a million.
and we said...
Concepts Guide

Application Developers Guide
PLSQL Guide
Performance Guide

for developers

Backup and Recovery Guide
RMAN guide
backup and recovery guide
Admin guide
backup and recovery Guide
Performance Guide
and most importantly - the backup and recovery guide

for DBA's
Review & Followup
Rating: 5
Thanks October 23, 2002
Reviewer: A reader

Rating: 3
Good but... October 23, 2002
Reviewer: Chris Slattery from Dublin,Ireland
No SQL Reference ? ;-)

Followup:
for reading?  nah, for reference -- sure, you need it.  But to sit and read?

Rating: 4
Any good Oracle Manual for SQL October 23, 2002
Reviewer: Bhavesh Tailor from Charlotte, NC, USA
Tom:

U seem to write some *amazing* sql queries. Any good Oracle manual for getting
better at SQL?
Thanks a lot, Tom

Followup:
practice, i don't recall ever reading a SQL book beyond the SQL/DS manuals in
87.

Rating: 5
Great on strssing backup and recovery. October 24, 2002
Reviewer: Rory from Philippines
Hi Tom,

    Backup and Recovery really is important. But how come you just listed RMAN
once. Shouldn't it be side by side with backup and recovery. After all it's a
whole new world with rman.

Followup:
understanding the mechanics -- how the files relate to eachother, what happens
when "X" is lost (what choices do you have) that is what I consider most
important.

RMAN is then a tool you can use based on that knowledge to automate a backup for
you.

Rating: 5
March 19, 2003
Reviewer: Robert from Memphis, USA

Rating: 5
Some hints aboot books March 20, 2003
Reviewer: Bernd Boecker from Germany, Bonn
I agree:

Read original up-to-date information from new Oracle versions

additionally,
found helpfull with lots of case studies + expierence:

For Backup& Recovery 8i:

Oracle8i Backup & Recovery
by Rama Velpuri, Anand Adkoli, George Williams
http://www.amazon.com/exec/obidos/tg/detail/-/0072127171/qid=1048149979/sr=1-2/ref=sr_1_2/102-3360042-3271302?v=glance&s=books
* RMAN (9i):

Oracle9i RMAN Backup & Recovery
by Robert G. Freeman, Matthew Hart
http://www.amazon.com/exec/obidos/tg/detail/-/0072226625/qid=1048150738/sr=1-1/ref=sr_1_1/102-3360042-3271302?v=glance&s=books
and most important: and practice for yourself...

Rating: 5
Excellent!! August 20, 2003
Reviewer: A reader
Tom,

I am a DBA and now will be working on projects related to Oracle 11i
Applications, can you or someone provide a similar list of documention for 11i
Applications.

Thanks..

Followup:
same list! (need that to manage a database)

and then the oracle apps docs, of which i know *less then nothing* about --
sorry on that one.

August 21, 2003 Nikos -- Thanks for the question regarding "Locate Previous SQL statement executed", version 8.1.5
You Asked
Hello Tom,
I'd like to know if there is a way to locate the previous to the last SQL
statement executed by a session (because the last one will be the one at
V$SESSION, V$SQLTEXT to find out the statement) FROM WITHIN THE SAME SESSION. I
need this info, so when an error is encountered (at the EXCEPTION section of a
PL/SQL procedure), I'll be able to locate the problematic statement in a
straightforward manner and log it.
e.g.
Procedure test
   insert into t1(a, b, c) values (1, 2, 3);
   update t1 set a = a + 1;
EXCEPTION
   WHEN OTHERS THEN
      *** Can I find here the problematic statement without
      *** the use of variables before each statement?

I'm not sure this is possible though, because that would require a statement log
per session (pretty much data). Anyway, I'd like your confirmation, before I
move on to alternative solutions.
Thank you and keep the good work
and we said...
Ok, here is one way to do it.  Actually, I'll have to show you an 815 way and
then a better 816 and up way.... (maybe enough to motivate you to upgrade!)

Anyway, in 815 we added a DBMS_TRACE package.  If you enable exception tracing
we can

o capture the exception details into a trace file in 815
o capture the exception details into a TABLE in 816 and up.

In 815, it would look like this:

ops$tkyte@ORA815.US.ORACLE.COM> create table t
  2  ( x int constraint check_x check (x>0),
  3    y int constraint check_y check (y>0)
  4  );

Table created.

ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM> create or replace directory udump_dir
  2  as '/export/home2/ora815/admin/ora815/udump'
  3  /

Directory created.

ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM> create or replace procedure p( p_x in int, p_y
in int )
  2  as
  3  begin
  4      insert into t values ( p_x, p_y );
  5      update t set y = -p_y where x = p_x;
  6  exception
  7      when others then
  8      declare
  9          l_bfile bfile;
 10          l_file  varchar2(255);
 11          l_str   varchar2(255);
 12      begin
 13
 14          select c.value || '/' || instance || '_ora_' ||
 15                 ltrim(to_char(to_number(a.spid),'fm99999')) || '.trc'
 16            into l_file
 17            from v$process a, v$session b, v$parameter c, v$thread c
 18           where a.addr = b.paddr
 19             and b.audsid = userenv('sessionid')
 20             and c.name = 'user_dump_dest';
 21
 22          l_bfile := bfilename( 'UDUMP_DIR', l_file );
 23          dbms_lob.fileopen( l_bfile );
 24
 25          l_str := utl_raw.cast_to_varchar2(
 26                    dbms_lob.substr( l_bfile, 250,
                             greatest(1,dbms_lob.getlength(l_bfile)-250) )
 27                      );
 28          dbms_output.put_line( l_str );
 29      end;
 30  end;
 31  /

Procedure created.

ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM> exec dbms_trace.set_plsql_trace(
dbms_trace.trace_all_exceptions );

PL/SQL procedure successfully completed.

ops$tkyte@ORA815.US.ORACLE.COM> exec p(1,1)
D:(9.474) 2001.11.12.14.37.17.000
------------ PL/SQL TRACE INFORMATION -----------
Levels set :  4
------------ PL/SQL TRACE
INFORMATION -----------
Levels set :  4
Trace:    Pre-defined exception - OER 2290 at line 5 of PROCEDURE OPS$TKYTE.P:

PL/SQL procedure successfully completed.

ops$tkyte@ORA815.US.ORACLE.COM> exec p(-1,-1)
line 5 of PROCEDURE OPS$TKYTE.P:
------------ PL/SQL TRACE INFORMATION -----------
Levels set :  4
------------ PL/SQL TRACE
INFORMATION -----------
Levels set :  4
Trace:    Pre-defined exception - OER 2290 at line 4 of PROCEDURE OPS$TKYTE.P:

PL/SQL procedure successfully completed

Here, we have a procedure that will always fail either on the INSERT or the
UPDATE.  We run it two times -- failing on each.  The trace information we
collected shows use the line number and procedure that failed.

I would expect you would put the code in the exception block in a STORED
PROCEDURE of course, you wouldn't what that code embedded everywhere of course.

Another reason for putting it in a stored procedure is that in 816, you'll
change the interface.  The data is stored in a TABLE instead of a trace file
(much nicer, easier to get the info).  In 816 and up, this example would be:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure p( p_x in int,
p_y in int )
  2  as
  3  begin
  4      insert into t values ( p_x, p_y );
  5      update t set y = -p_y where x = p_x;
  6  exception
  7      when others then
  8      declare
  9          l_runid number;
 10      begin
 11          select sys.plsql_trace_runnumber.currval into l_runid
 12            from dual;
 13
 14          for x in ( select *
 15                       from sys.plsql_trace_events
 16                      where runid = l_runid
 17                        and event_kind = 52
 18                      order by event_seq DESC )
 19          loop
 20              dbms_output.put_line( 'Exception occured in source ' ||
x.event_unit ||
 21                                    ' on line ' || x.event_line );
 22              exit;
 23          end loop;
 24      end;
 25  end;
 26  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_trace.set_plsql_trace(
dbms_trace.trace_all_exceptions );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec p(1,1)
Exception occured in source P on line 5

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec p(-1,-1)
Exception occured in source P on line 4

PL/SQL procedure successfully completed.
Review & Followup
Rating: 5
How about MTS mode? November 13, 2001
Reviewer: Nikos Papakyriakou from Athens - GREECE
Tom thanks a lot for your reply. It was much help. One more thing though. Oracle
manual (8.1.5) notes that Trace cannot be used when in MTS mode. Is there an
alternative when running in this mode?

Followup:
That is because your trace file is not your trace file (and this only applies in
815 -- maybe a better reason still to upgrade to supported software).

In 816 and up, since it writes to a table, your trace is your trace -- we can id
it via the sequence -- there is no ambiguity.

When using MTS in 815, you will not be able to use this technique and there is
no alternative short of coding it yourself.  You could use a procedure like my
"who_called_me"
http://osi.oracle.com/~tkyte/who_called_me/index.html
and you would be able to write a small routine "mark" or something like that
that asked "who called me" and gets the line number, owner, procedure name it
its caller and saves them in a set of package variables.  You could then code:

ops$tkyte@ORA815.US.ORACLE.COM> create or replace package mark
  2  as
  3      g_owner    varchar2(30);
  4      g_name     varchar2(30);
  5      g_lineno   number;
  6      g_caller_t varchar2(255);
  7
  8      procedure it;
  9  end;
 10  /

Package created.

ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM> create or replace package body mark
  2  as
  3
  4  procedure it
  5  as
  6  begin
  7      who_called_me( g_owner, g_name, g_lineno, g_caller_t );
  8  end;
  9
 10
 11  end;
 12  /

Package body created.

ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM> drop table t;

Table dropped.

ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM> create table t
  2  ( x int constraint check_x check (x>0),
  3    y int constraint check_y check (y>0)
  4  );

Table created.

ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM> create or replace procedure p( p_x in int, p_y
in int )
  2  as
  3  begin
  4      mark.it;
  5      insert into t values ( p_x, p_y );
  6      mark.it;
  7      update t set y = -p_y where x = p_x;
  8  exception
  9      when others then
 10          dbms_output.put_line( 'Error on line ' || mark.g_lineno ||
 11                                ' of ' || mark.g_caller_t || ' ' ||
 12                                mark.g_owner || '.' || mark.g_name );
 13  end;
 14  /

Procedure created.

ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM> exec p(1,1)
Error on line 6 of PROCEDURE OPS$TKYTE.P

PL/SQL procedure successfully completed.

ops$tkyte@ORA815.US.ORACLE.COM> exec p(-1,-1)
Error on line 4 of PROCEDURE OPS$TKYTE.P

PL/SQL procedure successfully completed.

Rating: 3
November 13, 2001
Reviewer: A reader
Tom,

I ran your procedure script, but it failed since the objects
sys.plsql_trace_events, sys.plsql_trace_runnumber are not there in SYS schema.

Is there any script file to create these objects?...

Thanks.

Followup:
in 816 and up:

@tracetab.sql
@dbmspbt.sql
@prvtpbt.plb

when connected as SYS ( or internal, or "as sysdba" ).

in 815 only:

@dbmspbt.sql
@prvtpbt.plb

when connected as SYS ( or internal, or "as sysdba" )

as there aren't any trace tables.  These are found in $ORACLE_HOME/rdbms/admin

Rating: 5
can DBMS_TRACE be called from PL/SQL ? June 04, 2002
Reviewer: Philippe from Belgium
Hi Tom !

I'm trying to set the trace ON from a PL/SQL procedure and it doesn't seem to
work (no events in PLSQL_TRACE_EVENTS).

Here is my test case :

--==================================================
CREATE OR REPLACE PROCEDURE traceexc AS
   l_runid   NUMBER;
BEGIN
   SELECT sys.plsql_trace_runnumber.CURRVAL
     INTO l_runid
     FROM DUAL;

   FOR x IN (SELECT   *
                 FROM sys.plsql_trace_events
                WHERE runid = l_runid AND event_kind =
DBMS_TRACE.plsql_trace_excp_raised
             ORDER BY event_seq DESC) LOOP
      DBMS_OUTPUT.put_line (
            'Exception '
         || x.excp
         || '('
         || SQLERRM (SQLCODE)
         || ') occured in '
         || x.event_unit_kind
         || ' '
         || x.event_unit_owner
         || '.'
         || x.event_unit
         || ' at line '
         || x.event_line
      );
      EXIT;
   END LOOP;
END;
--==================================================
CREATE OR REPLACE PROCEDURE testexc AS
BEGIN
   EXECUTE IMMEDIATE 'insert into tototata values (1,12)'; -- Will raise an
exception
EXCEPTION
   WHEN OTHERS THEN
      traceexc;
      raise;
END testexc;
--==================================================
CREATE OR REPLACE PROCEDURE test AS
BEGIN
   DBMS_TRACE.set_plsql_trace (DBMS_TRACE.trace_all_exceptions);
   testexc;
END test;
--==================================================

Any idea on how to enable DBMS_TRACE from PL/SQL ?

Thanks in advance !

Followup:
do it as i did it -- I did trace on AND THEN ran the code.  You are

"turning on trace, run the code"

It is my experience that you have to turn on trace and then it will affect all
SUBSEQUENT runs of code, not the currently executing piece of code.

ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE OR REPLACE PROCEDURE test AS
  2  BEGIN
  3     DBMS_TRACE.set_plsql_trace (DBMS_TRACE.trace_all_exceptions);
  4     testexc;
  5  END test;
  6  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec test
BEGIN test; END;

*
ERROR at line 1:
ORA-00942: table or view does not exist
ORA-06512: at "OPS$TKYTE.TESTEXC", line 7
ORA-06512: at "OPS$TKYTE.TEST", line 4
ORA-06512: at line 1

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec   DBMS_TRACE.set_plsql_trace
(DBMS_TRACE.trace_all_exceptions);

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE OR REPLACE PROCEDURE test AS
  2  BEGIN
  3     testexc;
  4  END test;
  5  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec test
Exception 942(ORA-00942: table or view does not exist) occured in PROCEDURE
OPS$TKYTE.TESTEXC at line 3
BEGIN test; END;

*
ERROR at line 1:
ORA-00942: table or view does not exist
ORA-06512: at "OPS$TKYTE.TESTEXC", line 7
ORA-06512: at "OPS$TKYTE.TEST", line 3
ORA-06512: at line 1

Rating: 2
I have to set_trace in PL/SQL... June 05, 2002
Reviewer: Philippe from Louvain-la-Neuve, Belgium
I did some more tests... and if, in my test procedure I set the trace to the
trace_all_calls level i.e.

DBMS_TRACE.set_plsql_trace (DBMS_TRACE.trace_all_calls);

then I get a full trace (including exceptions).

As my procedure is submitted as a JOB I think I need to set the trace in the
procedure itself...

I also noticed that the trace contains a lot of

PL/SQL Virtual Machine started
PL/SQL Virtual Machine stopped

events... what's that ?

Followup:
each stack built/unbuilt (new scope) will tend to do that.

Rating: 3
what about the difference between trace levels ? June 06, 2002
Reviewer: Philippe from Louvain-la-Neuve, Belgium
What about the fact that a set trace at trace_all_calls level i.e.

DBMS_TRACE.set_plsql_trace (DBMS_TRACE.trace_all_calls);

generates full trace (including exceptions) BUT a trace set (at the same place)
at trace_all_exceptions level doesn't generate trace for raised exceptions ??

Followup:
For this one, you'll have to open a TAR with support to see if it is

a) supposed to do that.
b) an issue that needs to be corrected.

Rating: 5
June 06, 2002
Reviewer: A reader
Tom,
TABLE sys.plsql_trace_events does not exits.
We are using 8.1.7

I have "select any table" privileges

SQL> desc sys.plsql_trace_events;
ERROR:
ORA-04043: object sys.plsql_trace_events does not exist

Followup:
see $ORACLE_HOME/rdbms/admin/tracetab.sql (or the docs)
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76936/dbms_tra.htm#998100
Suggest you read the docs regardless (although I just now notice there is a doc
bug, they say SYSTEM in the docs, but the script correctly says and does SYS)

Rating: 4
Problem in creating udump August 13, 2002
Reviewer: Siva from INDIA
Hello TOM,
        As you mentioned in the script i ran the oracle (/admin folder)  scripts
and your sample example.
        I got the following entries. But This file is not created in the udump
folder.
        Is there any problem in My setting.
        Platform : Oracle 9.0.1 on WIN98.

SQL>  select c.value || '\' || instance || '_ora_' ||
  2          ltrim(to_char(to_number(a.spid),'fm99999999999999')) || '.trc'
  3     from sys.v$process a, sys.v$session b, sys.v$parameter c, sys.v$thread c
  4    where a.addr = b.paddr
  5      and b.audsid = userenv('sessionid')
  6      and c.name = 'user_dump_dest'
  7  /

C.VALUE||'/'||INSTANCE||'_ORA_'||LTRIM(TO_CHAR(TO_NUMBER(A.SPID),'FM999999999999
--------------------------------------------------------------------------------
c:\oracle\admin\siva\udump\siva_ora_-658843.trc
c:\oracle\admin\siva\udump\siva_ora_-659555.trc
c:\oracle\admin\siva\udump\siva_ora_-667303.trc
c:\oracle\admin\siva\udump\siva_ora_-646979.trc
c:\oracle\admin\siva\udump\siva_ora_-649015.trc
c:\oracle\admin\siva\udump\siva_ora_-651179.trc
c:\oracle\admin\siva\udump\siva_ora_-734359.trc
c:\oracle\admin\siva\udump\siva_ora_-207687.trc

If i will get one entry and run the procedure i got the following error. I think
this file
is not exists.

SQL> exec p(1,1);
BEGIN p(1,1); END;

*
ERROR at line 1:
ORA-22288: file or LOB operation FILEOPEN failed
The system cannot find the file specified.
ORA-06512: at "SYS.DBMS_LOB", line 475
ORA-06512: at "SYS.P", line 22
ORA-02290: check constraint (SYS.CHECK_Y) violated
ORA-06512: at line 1

regards,
siva

Followup:
Look at the file names generated by that query.

See how the spid is negative apparently.

You'll have to look at v$process and at what is actually in the file system and
correct the query for your specific version on your specific platform.

The generated filenames are obviously wrong -- you need to correct that first

Rating: 4
Retreiving call stuck from exception August 13, 2002
Reviewer: Sasa Petkovic from Belgrade, Yugoslavia
Hi Tom,

Please could you answer me how to find original call stuck when the exception
occured?

Regards

Sasa

Followup:
call "stack" perhaps.

You can get the call stack using dbms_utility.format_call_stack -- BUT it'll be
the call stack from the perspective of the exception handler.

That is it, that is the only thing you have access to.  Nothing more, nothing
less.

Rating: 3
But how ADO or TOAD get the call stack ..? August 14, 2002
Reviewer: Sasa Petkovic from Belgrade, Yugoslavia
Hi Tom,

 You answered about call stack :
"You can get the call stack using dbms_utility.format_call_stack -- BUT it'll be

the call stack from the perspective of the exception handler."

We know about dbms_utility.format_call_stack and in a some way it is not
correct, because it shows only the path to the exception handler not to place
where the real exception occurred .
We want to know how TOAD or ADO get the "real" call stack?

Thanks

Sasa

Followup:
A client application gets the full error stack cause the error is propagated
out.

Once you have an exception block in PLSQL and catch the exception -- the error
goes away, it by very definition doesn't exist.  You caught it, you are
dealing with it, you are handling it.

So, if you let the error propagate to the client, they get an error stack (we
have an error)

If you catch the error in plsql, the error is gone, you just erased it.

That is how toad (a client application) gets it.

Rating: 5
Finally one "mistery " is resolved. August 15, 2002
Reviewer: Sasa from Belgrade, Yugoslavia
Thanks a lot Tom.

As usually you were brilliant.

Regards,

Sasa

Rating: 5
DBMS_TRACE exceptions inside PLSQL September 20, 2002
Reviewer: Raghid Ajamoughli from Cleveland, OH USA
Tom

I am able to get DBMS_TRACE to catch exceptions inside PL/SQL Block, here is
how:
I used "dbms_trace.plsql_trace_excp_handled" instead of
"dbms_trace.trace_all_exceptions"

for example:
procedure test as
begin
 dbms_trace.set_plsql_trace(dbms_trace.plsql_trace_excp_handled);
 p(1,1);
 dbms_trace.clear_plsql_trace;
end;

I do not know of any implications, but I would appreciate if someone at Oracle
can confirm this method.

Thanks
Raghid Ajamoughli

Rating: 5
It's most useful! But .. June 12, 2003
Reviewer: Li ys from CHINA
It's most useful indeed,But I think I can't use it in my situation.I want to
exec a huge *.sql file in SQL*PLUS,and want to trap the problematic statement,I
use the 'WHENEVER SQLERROR EXIT' and 'spool some.log',but this log file have not
the problematic statement and only the error code,so I can't catch which
statement error by log.

And,The sql file is too huge,It contains statements such as:
'create table ...','insert into ...',eg.So I also can't use 'begin.. end..
exception.. ' like you said above.

Unless I write 'begin.. end.. exception..' by each statement such as
'insert',But It's too huge to act.

If can use 'WHENEVER SQLERROR EXIT pl/sql' is so good,but I know it's
impossible,Could you give me directions?

Thanks a lot!

Followup:
sqlplus you see is a very simple command line tool for running simple scripts.

It was never really designed as an "installation tool".

sounds like you want to have an installation tool.  You can use OUI (oracle
universal installer) or DIY (do it yourself).

For example, put your sql in a very simple delimited file.  Write a simple small
program to read that file and execute the SQL.  You now have complete control
over what it does.

You can even write this program in PLSQL and use a BFILE or UTL_FILE to read the
SQL and dynamically execute it.  (BFILE would be good, won't require a reboot of
the server if you don't have utl_file_dir set, although in 9iR2, utl_file can
use a directory object instead of utl_file_dir so in that relate, utl_file would
be the way to go)

Rating: 5
Great Thread July 24, 2003
Reviewer: Mike Schmidt from St. Louis, MO
> Once you have an exception block in PLSQL and catch
> the exception -- the error goes away, it by very
> definition doesn't exist.  You caught it, you are
> dealing with it, you are handling it.

I've often wondered if there is any way to force the error number into a trace
file (e.g. ERROR #c:err=e tim=t) even though an exception handler is catching
and handling the exception and not re-raising it.  This would be good evidence
to show an inexperienced developer the error that was thrown (and then ignored)
in the developer's WHEN OTHERS THEN ... handler.

Followup:
search for

dbms_trace

on this site (yes)

Rating: 5
whats the eebsite address of ur friend to whom we can ask architectural questions, about java etc July 24, 2003
Reviewer: A reader

Followup:
devtrends.oracle.com

Rating: 5
thanx Tom! August 20, 2003
Reviewer: A reader
I am a little confused about using of debug.f package
of yours versus dbms_trace method that you explained above.
Are the complements to each other - and if so in what
way. I think that one could use debug.f in the following
ways:

1. To know where exactly you are in the procedure - to
see the info on what is happenning in sequence
2. To "time" the procedures. - Since time is
   automatically generated - this may not be an issue -
   You could also do it using dbms_utility.get_time
   and debug.f the times it took
So debug.f - helps mainly in non-exception errors
   and performance tracking/problems

You would use dbms_trace to trace exceptions - Not
all errors are exceptions -

hence you need both. Is this analysis correct?

Thanx!!

Followup:

dbms_trace is an internal, supplied package that can perform a rudimentary trace
of your plsql code -- what happened where and so on.

debug.f is just a way for you to create your own custom tracing -- like a
personal "sql_trace=true"

with debug.f, you can:

debug.f( 'in routine foo, x = %s, y = %s', x, y );

you won't get stuff like that from dbms_trace.  with dbms_trace you'll get stuff
you cannot possibly print out with debug.f (like the actual line number an
exception was raised on originally)

August 21, 2003 Sri -- Thanks for the question regarding "Reg. DBMS_STAT", version 9.0.1
You Asked
Dear Tom,

  To enable CBO in my schema, I tried doing this: -

DBMS_STATS.GATHER_SCHEMA_STATS
                       (ownname=>''SRI'',
estimate_percent=>DBMS_STATS.AUTO_SAMPLE_SIZE,
                        method_opt=>''FOR ALL COLUMNS SIZE AUTO'',
                        cascade=>TRUE)

  But Oracle is computing the statistics for histograms also.  I understand that
this would be expensive.  To avoid this, what I should be doing?  How my
statement should be?  Also, kindly review the above statement and advice.

  Thank you.
and we said...
you told it to...

for all columns size auto

that says "hey oracle, compute histograms"....

so,

a) how long does that command take to run.
b) most importantly -- is that too long?

many people use for all indexed columns, or just for columns <names of columns
they WANT histograms on>

It is advisable to collect histograms on at least one column in order to get the
avg_row_len computed, else, if you use null for method opt, avg_row_len will be
set to 100.
Review & Followup
Rating: 2
avg_row_len May 29, 2003
Reviewer: Bill Davison from Fort Collins, CO
At least on v8.1.7 I did not find this statement to be true:

"It is advisable to collect histograms on at least one column in order to get
the avg_row_len computed, else, if you use null for method opt, avg_row_len will
be set to 100."

If I use method_opt=>'FOR ALL INDEXED COLUMNS SIZE 1' all my avg_row_len values
default to 100.

If I use method_opt=>'FOR ALL COLUMNS SIZE 1' I get meaningful values in
avg_row_len.

I'd love to avoid the overhead of 'for all columns', but I need the avg_row_len.
 Any suggestions?

Followup:
arg, it is an in progress "issue". sorry for the misquote there.

Rating: 3
dbms_stats source August 20, 2003
Reviewer: robert from PA
I may have heard wrong....
I heard dbms_stats source is viewable and not wrapped
so one can see the v$ objects accessed.
But gui tools says the body is wrapped.

Is the source in some script file ?
Thanks

Followup:
nope, it is wrapped

  1  select line, text
  2  from dba_source
  3  where name = 'DBMS_STATS'
  4  and type = 'PACKAGE BODY'
  5* and line = 1
ops$tkyte@ORA920> /

      LINE
----------
TEXT
---------------------------------------------------------------------------------
--------------------------------------------------
         1
package body dbms_stats wrapped
0
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
3
b

August 21, 2003 Alberto -- Thanks for the question regarding "write "consistency"", version 8.1.7/9.0.1
You Asked
Hi Tom,

I have always worked under the assumption that a statement like

update t set ... where ...

is evaluated as

1) take a consistent snapshot of the table at statement start time
2) for every row in the snapshot,
   wait on the row if the row is locked by another transaction,
   and then modify the row if the where clause is satisfied.

I.e. any modification made by transactions started and committed
after the statement start time (SCN) are not seen by the update statement, since
they are not part of the "consistent snapshot".

The following lab experiment (that develops from an actual business case) has
made me rethink about it.

We will update a big table with an update statement (transaction labeled
LONG),but we will make it block in the middle of the table waiting for the
transaction BLOCKER to release the lock. While LONG is waiting, we will modify
the first and last row  with another transaction FIRSTLAST, commit it, and then
rollback BLOCKER to free LONG.
Then, we will repeat all of the above but committing BLOCKER instead of
rollbacking it.

create table t (id int, y int, filler char(10) default ' ') initrans 2 nologging
storage (initial 1M next 1m pctincrease 0 maxextents unlimited);

insert /*+ APPEND */ into t select rownum, 0, ' '  from
all_objects,all_objects,all_objects where rownum <=1000000;
commit;

BLOCKER>  update t set y = -1 where id = 500000;

LONG>   update t set y = rownum where y = -1 or id=500000;

-- it blocks on row 500000

FIRSTLAST> update t set y = -1 where id in (1,1000000);
FIRSTLAST> commit;

BLOCKER> rollback;

LONG > select * from t where y <> 0;

        ID          Y FILLER
---------- ---------- ----------
         1         -1
    500000          1
   1000000         -1

This works as I expected; since FIRSTLAST started and committed after LONG, its
modifications are not part of the "where clause snapshot" of LONG, and so the
first and last rows do not get modified by LONG [ie LONG sees the old value (0)
of y on the first and last row, even if the new value (-1) would satisfy the
where clause of LONG]. So far, so good.

-----------------

Now we will perfom the very same experiment (resetting the table with
all the y set to zero), but now committing BLOCKER instead of rollbacking it:

update t set y = 0 where y <> 0;
commit;

BLOCKER>  update t set y = -1 where id = 500000;

LONG>   update t set y = rownum where y = -1 or id=500000;

-- it blocks on row 500000

FIRSTLAST> update t set y = -1 where id in (1,1000000);
FIRSTLAST> commit;

BLOCKER> commit; -- this is the only difference

LONG > select * from t where y <> 0;

        ID          Y FILLER
---------- ---------- ----------
         1          1
    500000          2
   1000000          3

Now, even if FIRSTLAST started and committed after LONG, and so its
modifications are not part of the "where clause snapshot" of LONG
shot at start time, LONG actually has seen the modifications of FIRSTLAST (in
addition to those of BLOCKER, of course).

This has been reproduced in both 8.1.7 and 9.0.1.

If the update statement of LONG had been rollbacked and then restarted
after detecting that BLOCKER had modified a row, we would have been observed
this result.

Question a) Has Oracle really restarted the statement ? Or better, may I develop
my software taking the stmt restart as consistency/concurrency rule, even if
Oracle has made something different, but equivalent to a restart ?

Question b) What's the rationale for this behaviour ? I intuitively understand
that it's in order to see all-or-none of the modifications made by BLOCKER, that
is, to preserve the atomicity of BLOCKER ... but I would like to have this
intuition confirmed, or not.

[Side note: LONG updates y with the rownum, and assuming that the rownum is
assigned to each row as the stmt scans them, it seems that LONG has updated the
rows starting with id=1, then 500000, then 1000000, even if it was blocked on
id=500000 and had already visited id=1 and had discarded it (my assumption of
the actual Oracle's work in this case, i know that in general the order of
scanning is absolutely neither predictable nor reproducibile). Just food for
thought, no practical implications whatsoever.]

--

The lab experiment performed above was actually motivated by a real business
case, since we need to modify, with an update stmt similar to that of LONG, a
big table subject to (a lot of) concurrent modifications similar to the one of
BLOCKER and FIRSTLAST. I may easily imagine other business cases in which we
need to batch update a big OLTP-type table, subject to a lot of updates (or
inserts, deletes) per seconds, 24x7. Say, "raise the salary of all employees
around midnight, where ...". "around" means "best-effort", it's ok if we update
as per the table image of 00:05 AM, but of course not 01:00 PM.

Assuming that the answer of question (a) is "yes", I can't simply fire the batch
update around midnight, since the row-to-modify-set snapshot is not simply taken
at stmt start, but actually re-evaluated each time some OLTP transactions
modifies a row.
In extreme, the stmt may be restarted forever ... and not consistently with the
"around midnight" snapshot anyway.

Probably the best way would be to, in a nutshell (the real implementation should
be more sophisticated, using temp tables, etc):

create table snap (row_id rowid) as select rowid row_id from t where ...

And then, update t using snap as the row-to-modify-set.

Question c) Am i being too cautious, or plainly wrong, considering such a
strategy ?

Thanks for your time and attention
Alberto
and we said...
oh geez -- i'm almost afraid to publish this one.  It is going to cause quite a
bit of followup I believe.

The result set is consistent -- but it may well be consistent as of a restart
time.  We can see this with a much much smaller example -- I just used 5 rows in
a controlled manner, very similar to your example.

we'll use a row trigger and an autonomous transactions to see whats going on
under the covers though.  Here is the setup:

ops$tkyte@ORA920> create sequence s;
Sequence created.

ops$tkyte@ORA920> create table msg1
  2  ( seq int primary key, sid int,
  3    old_id int, old_y int,
  4    new_id int, new_y int );
Table created.

ops$tkyte@ORA920> create table msg2
  2  ( seq int primary key, sid int,
  3    old_id int, old_y int,
  4    new_id int, new_y int );
Table created.

ops$tkyte@ORA920> create table t
  2  as
  3  select rownum id, 0 y
  4    from all_users
  5   where rownum <= 5;
Table created.

ops$tkyte@ORA920> select count(*)
  2    from t;

  COUNT(*)
----------
         5

so, t is our 5 row table we'll do concurrent things on.  msg1 will be a table
we'll log the before/after image row by row transactionally.  msg2 will be a
table we'll do the same but using an autonomous transaction:

ops$tkyte@ORA920> create or replace procedure log_msg2( p_seq in int,
  2                                        p_sid in int,
  3                                        p_old_id in int,
  4                                        p_old_y  in int,
  5                                        p_new_id in int,
  6                                        p_new_y  in int )
  7  as
  8     pragma autonomous_transaction;
  9  begin
 10      insert into msg2
 11      (seq, sid, old_id, old_y, new_id, new_y )
 12      values
 13      (p_seq, p_sid, p_old_id, p_old_y, p_new_id, p_new_y );
 14      commit;
 15  end;
 16  /
Procedure created.

ops$tkyte@ORA920> create or replace trigger t_trigger before update on t for
each row
  2  declare
  3      l_seq number;
  4  begin
  5      select s.nextval into l_seq from dual;
  6
  7      insert into msg1
  8      (seq, sid, old_id, old_y, new_id, new_y )
  9      values
 10      (l_seq, userenv('sessionid'), :old.id, :old.y, :new.id, :new.y );
 11
 12      log_msg2
 13      (l_seq, userenv('sessionid'), :old.id, :old.y, :new.id, :new.y );
 14  end;
 15  /

Trigger created.

that trigger will log entries in both tables -- we'll be able to see the
atrans entry immediately, we'll only see committed entries in msg1 however

ops$tkyte@ORA920> update t set y = -1 where id = 3;
1 row updated.

set the "middle" row to -1, this locks it as well of course

ops$tkyte@ORA920> set echo off
in another session issue:
update t set y = 1 where y = -1 or id = 2 or id = 3;
then come back and hit enter
ops$tkyte@ORA920> pause

I did that, it blocked of course.  it updated a single row -- where id=2 and
blocked on id=3

ops$tkyte@ORA920> select decode(msg1.seq,null,'NOT VISIBLE'), msg2.*
  2        from msg2, msg1
  3   where msg2.seq = msg1.seq(+)
  4   order by msg2.seq;

DECODE(MSG1        SEQ        SID     OLD_ID      OLD_Y     NEW_ID      NEW_Y
----------- ---------- ---------- ---------- ---------- ---------- ----------
                     1      12565          3          0          3         -1
NOT VISIBLE          2      12566          2          0          2          1

we can see it updated row id=2 -- the trigger fired.  It is waiting on
id=3...

ops$tkyte@ORA920> set echo off
in another session issue:
update t set y = -1 where id in ( 1,5 );
commit;
ops$tkyte@ORA920> pause

that is your "update the first and last row".  I did that, that transaction
went right off -- no problem:

ops$tkyte@ORA920> select decode(msg1.seq,null,'NOT VISIBLE'), msg2.*
  2        from msg2, msg1
  3   where msg2.seq = msg1.seq(+)
  4   order by msg2.seq;

DECODE(MSG1        SEQ        SID     OLD_ID      OLD_Y     NEW_ID      NEW_Y
----------- ---------- ---------- ---------- ---------- ---------- ----------
                     1      12565          3          0          3         -1
NOT VISIBLE          2      12566          2          0          2          1
                     3      12567          1          0          1         -1
                     4      12567          5          0          5         -1

we can see now 4 rows in our log table.  seq=1 is the first update that we did
and is blocking session2 over there.  seq=2 is the one row that blocked session
updated so far.  seq=3/4 are the entries for the committed "first/last" row
update

ops$tkyte@ORA920> commit;
Commit complete.

here is where it gets *very* interesting

ops$tkyte@ORA920> select decode(msg1.seq,null,'NOT VISIBLE'), msg2.*
  2        from msg2, msg1
  3   where msg2.seq = msg1.seq(+)
  4   order by msg2.seq;

DECODE(MSG1        SEQ        SID     OLD_ID      OLD_Y     NEW_ID      NEW_Y
----------- ---------- ---------- ---------- ---------- ---------- ----------
                     1      12565          3          0          3         -1
NOT VISIBLE          2      12566          2          0          2          1
                     3      12567          1          0          1         -1
                     4      12567          5          0          5         -1

NOT VISIBLE          5      12566          3          0          3          1

NOT VISIBLE          6      12566          1         -1          1          1
NOT VISIBLE          7      12566          2          0          2          1
NOT VISIBLE          8      12566          3         -1          3          1
NOT VISIBLE          9      12566          5         -1          5          1

9 rows selected.

hmmm -- when we committed the first thing to happen was the row with id=3 was
updated -- we can see that -- seq=5 is that update.  HOWEVER, that was actually
rolled back (as was the update to id=2 as we'll see below further) and the
update itself was restarted from the beginning.  It "reupdated" id=2 and id=3
and added ids 1 and 5 to the mix (y=-1 part of the predicate picked them up).

ops$tkyte@ORA920> set echo off
commit in the formerly blocked session
ops$tkyte@ORA920> pause

ops$tkyte@ORA920> select * from t;

        ID          Y
---------- ----------
         1          1
         2          1
         3          1
         4          0
         5          1

so we can clearly see it updated 4 rows and

ops$tkyte@ORA920> select decode(msg1.seq,null,'UNDONE'), msg2.*
  2        from msg2, msg1
  3   where msg2.seq = msg1.seq(+)
  4   order by msg2.seq;

DECODE        SEQ        SID     OLD_ID      OLD_Y     NEW_ID      NEW_Y
------ ---------- ---------- ---------- ---------- ---------- ----------
                1      12565          3          0          3         -1
UNDONE          2      12566          2          0          2          1
                3      12567          1          0          1         -1
                4      12567          5          0          5         -1
UNDONE          5      12566          3          0          3          1
                6      12566          1         -1          1          1
                7      12566          2          0          2          1
                8      12566          3         -1          3          1
                9      12566          5         -1          5          1

9 rows selected.

now that everything is "committed" we can see the seq=2 and seq=5 were
"undone" -- rolled back.  It was worked performed that was "unperformed" in
order to allow us to "redo it" (don't want to give that 10% raise 2 times, or 3
times or N times do you?)

so, Oracle just change the time at which your update started on you -- slid it a
bit if you will.

The statement will should never be restarted forever as others will queue,
blocked, behind this long running statement -- they (the ones that get blocked)
will stay blocked until this long running statement completes.  the
mini-rollbacks will not unblock them.

If I had a large bulk update to perform like this, I would be sorely temped to
lock the table to prevent interference from the mini-transactions.  You have a
conflict of methods here -- and what would happen is most of your users would
end up getting blocked after multiple restarts of the long running statement
(eg: the cumulative apparent down time for them would be larger).

If you think about it -- it must restart in order to "do the right thing", it
really needs to see the data as of "now" in order to safely update it (what if
the other updates REMOVED rows from consideration?)

I would say your situation sounds shaky - very very excessively prone to the
lost update issues.  locking the table for this mass update sounds prudent and
most efficient - or letting the database do what it does.
Review & Followup
Rating: 5
very interesting thread.. August 03, 2003
Reviewer: pasko from Hamburg
Hi Tom,
Thanks for a great answer above...
but what do you mean by saying :

"or letting the database do what it does"

Thanks in advance.

Followup:
the answer demonstrated what the database "does"

I said

o either lock the table, do the batch or....
o let it do what it does as described above.

Rating: 5
many many thanks August 04, 2003
Reviewer: Alberto Dell'Era from Milan, Italy
As always, your *superb* answer not only has cleared my doubts, but has opened
the way for more investigations ... i couldn't ask for more!

So, many many thanks, again.

Really, really thought-provoking ... i have to think about it   deeply.

Thanks!
Alberto

Rating: 5
final review August 04, 2003
Reviewer: Alberto Dell'Era from Milan, Italy
Ok, since the "followup flooding" has not happened ;-), would you mind checking
my understanding ? I will understand if you ignore this long followup, of
course, and enqueue myself after the "sorry I have a large backlog" semaphore.

a) if a "long" transaction blocks on a locked row, it will wait, check the new
values and then

   if (the new values satisfy the where clause) {
      mini-rollback the changes, and restart the update (or delete) [case A]
   } else {
      continue processing [case B]
   }

b) while mini-rollbacking, the "long" tx will not release its TX lock, so txs
enqueued on it will stay blocked, helping preventing the "starvation" (ie
"restart ad infinitum") of "long";

c) the reason for mini-rollbacking and restarting in [case A] is that "long" has
seen a change (has been affected) by another tx "tx-other", but "tx-other" may
have as well changed a row already scanned by "long", and so if "long" didn't
restart, it would  have seen only a part of the changes of "tx-other", breaking
the "tx-other" atomicity;

d) the reason for continuing processing in [case B] is that "long" has not seen
(yet) any changes made by "tx-other", so it doesn't matter whether "tx-other"
has changed a row already scanned by "long" or not: atomicity means "see all or
nothing", and here we are on the "nothing" side;

e) if we were still at the college, c+d may be expressed as "serialize long
completely after tx-other";

f) i still think that "long" may be restarted forever, because e.g. it may
happen that
   - "long" is blocked on a row;
   - a "brand-new-tx" modifies a not-locked row;
   - "long" restarts, blocks again on that row, and restarts again.
But [my speculation], while mini-rollbacking, "long" will not give away the
locks on the rows it has already modified, so the probability that a
"brand-new-tx" is able to step into decreases as time progresses, so in real
life, we may say that the starvation is near-to-impossible.

g) if you don't mind, a small curiosity: how long did it take to read and answer
my question ?

Thanks, again ...
Alberto

Followup:
a) yes...

b) yes...

c) that AND what if the row had been changed in such a way so as to no make it
meet the criteria anymore?  sure the before image did -- but the current value
does not.  Suppose you had a rule that said "when a = 5, B must be between 10
and 15, else B must be 20".  you issue an update "update t set b = 11 where a =
5".  while processing you got blocked.  it was a block due to someone updating a
single sole row -- "update t set a = 6 where id = 1234".  Your update will fail
-- EVEN THOUGH you said clearly "only when a=5 do this update" -- because a is
now 6.

so, it restarts, and skips that row.

d) i think I follow you -- yes.

e) in a manner

f) it is possible i suppose, but highly highly improbable.

g) reading it took longer then answering it.  It was hard to digest, but it was
a very interesting one.  I might make it the subject of a presentation actually.
 I was surprised at the lack of followup -- I did think it would generate so
"oh, look at that" ;)

Rating: 5
Thank you for a very good explanation August 05, 2003
Reviewer: Helena Marková from Bratislava, Slovakia

Rating: 5
thanks August 05, 2003
Reviewer: Alberto Dell'Era from Milano, Italy
Many thanks for your re-answering ... and I'm happy to see that the question got
your interest, as got mine in first place.

And now, armed with your scaffolding and example, let's sail towards new
adventures in the Land of Consistency & Concurrency ;-)

Rating: 5
Why different processing for Rollback/Commit ? August 05, 2003
Reviewer: Eugen
During study of this treat I was wondering, why Oracle used a different
processing when the blocking session issues a rollback instead of a commit.
Sure, the data of the blocked row isn't changed with a rollback, but even tough
it seems to me, it would be more consistent (processing wise) when the rollback
of a blocked row would also lead to mini-rollbacks in the LONG transaction.

What do I miss?

Followup:
it is "if the data changed values".

take the original example and change:

update t set y = -1 where id = 3;

to

update t set y = y where id = 3;

the blocking events will be the same.  session 1 will have id=3 locked. session
2 will update id=2 and then block on id=3.  session 3 will then update id=1 and
id=5 and commit.  when session=1 COMMITS this time -- session 2 will unblock and
find that "hey, y didn't change -- no worries here, lets continue onward..." and
will update two rows only.

Here is the script I used minus the sqlplus prompts and all -- this one shows
that even when you commit -- if you didn't change the value -- it continues on
like it would have for a rollback.

It would not be any more or less consistent either way -- this is "just as"
consistent AND much more performant...

drop table msg1;
drop table msg2;
drop sequence s;
drop table t;

set echo on

create sequence s;

create table msg1
( seq int primary key, sid int,
  old_id int, old_y int,
  new_id int, new_y int );

create table msg2
( seq int primary key, sid int,
  old_id int, old_y int,
  new_id int, new_y int );

create table t
as
select rownum id, 0 y
  from all_users
 where rownum <= 5;

select count(*)
  from t;

create or replace procedure log_msg2( p_seq in int,
                                      p_sid in int,
                                      p_old_id in int,
                                      p_old_y  in int,
                                      p_new_id in int,
                                      p_new_y  in int )
as
   pragma autonomous_transaction;
begin
    insert into msg2
    (seq, sid, old_id, old_y, new_id, new_y )
    values
    (p_seq, p_sid, p_old_id, p_old_y, p_new_id, p_new_y );
    commit;
end;
/

create or replace trigger t_trigger before update on t for each row
declare
    l_seq number;
begin
    select s.nextval into l_seq from dual;

    insert into msg1
    (seq, sid, old_id, old_y, new_id, new_y )
    values
    (l_seq, userenv('sessionid'), :old.id, :old.y, :new.id, :new.y );

    log_msg2
    (l_seq, userenv('sessionid'), :old.id, :old.y, :new.id, :new.y );
end;
/
update t set y = y where id = 3;

set echo off
prompt in another session issue:
prompt update t set y = 1 where y = -1 or id = 2 or id = 3;;
prompt then come back and hit enter
pause

select decode(msg1.seq,null,'NOT VISIBLE'), msg2.*
      from msg2, msg1
 where msg2.seq = msg1.seq(+)
 order by msg2.seq;

set echo off
prompt in another session issue:
prompt update t set y = -1 where id in ( 1,5 );;
prompt commit;;
pause

select decode(msg1.seq,null,'NOT VISIBLE'), msg2.*
      from msg2, msg1
 where msg2.seq = msg1.seq(+)
 order by msg2.seq;

commit;

select decode(msg1.seq,null,'NOT VISIBLE'), msg2.*
      from msg2, msg1
 where msg2.seq = msg1.seq(+)
 order by msg2.seq;

set echo off
prompt commit in the formerly blocked session
pause

select * from t;

select decode(msg1.seq,null,'UNDONE'), msg2.*
      from msg2, msg1
 where msg2.seq = msg1.seq(+)
 order by msg2.seq;

Rating: 5
Will take me about 6 months... August 05, 2003
Reviewer: sPh from St. Louis, Missouri USA
I think it will take me about six months to figure this one out.  I always tell
people one of the strengths of Oracle is that you can understand what goes on
"under the hood" and this is a great example.

sPh

Followup:
interesting variations

a) the one where I changed the update t set y = -1 where id =3; to
                               update t set y = y where id = 3;

only 2 rows updated instead of 4...

b) add a column Z with all zeroes to the table t.  make the first update

   update t set y = y, z = 1 where id = 3;

Now see how many (try to guess)

c) keeping that column Z with all zeros, add this trigger:

create or replace trigger t after update on t for each row
begin
    dbms_output.put_line( :new.z );
end;
/

and use this update:

   update t set y = y, z = 1 where id = 3;

now see how many rows updated.....

Rating: 5
Excellent example August 05, 2003
Reviewer: shan from DC
Not that I followed everything what you said here...I am reading it again and
again and am realizing now what an awesome complex beast is Oracle. I don't
think if anyone can explain this more lucidly than you do with your examples.
Thank you somuch for this great forum!!

best regards,
Shan.

Rating: 5
interesting variation of the interesting variation August 05, 2003
Reviewer: Alberto Dell'Era from Milan, Italy
> a) the one where I changed the update t set y = -1 where id =3; to
>                               update t set y = y where id = 3;
>
> only 2 rows updated instead of 4...

Yes! That's because the update "update t set y = 1 where y = -1 or id = 2 or id
= 3" (which i labeled "long") didn't get restarted, so it didn't see the update
to -1 made by "first/last". Ie it was consistent as the image before
first/last".

Here's your script, which i've modified using client_info as a logging device,
and its final results:

drop table msg1;
drop table msg2;
drop sequence s;
drop table t;

set echo on
set lines 123
col client_info format a15
set pages 9999

create sequence s;

create table msg1
( seq int primary key, sid int,
  old_id int, old_y int,
  new_id int, new_y int );

create table msg2
( seq int primary key, sid int,
  old_id int, old_y int,
  new_id int, new_y int,
  client_info varchar2(64) );

create table t
as
select rownum id, 0 y
  from all_users
 where rownum <= 5;

select count(*)
  from t;

create or replace procedure log_msg2( p_seq in int,
                                      p_sid in int,
                                      p_old_id in int,
                                      p_old_y  in int,
                                      p_new_id in int,
                                      p_new_y  in int )
as
   pragma autonomous_transaction;
   client_info varchar2(64);
begin
    dbms_application_info.read_client_info (client_info);

    insert into msg2
    (seq, sid, old_id, old_y, new_id, new_y, client_info )
    values
    (p_seq, p_sid, p_old_id, p_old_y, p_new_id, p_new_y, client_info);
    commit;
end;
/
show errors;

create or replace trigger t_trigger before update on t for each row
declare
    l_seq number;

begin
    select s.nextval into l_seq from dual;

    insert into msg1
    (seq, sid, old_id, old_y, new_id, new_y )
    values
    (l_seq, userenv('sessionid'), :old.id, :old.y, :new.id, :new.y );

    log_msg2
    (l_seq, userenv('sessionid'), :old.id, :old.y, :new.id, :new.y);
end;
/
exec dbms_application_info.set_client_info ('blocker');
update t set y = y where id = 3;
--update t set y = -1 where id = 3;

set echo off
prompt in another session issue:
prompt exec dbms_application_info.set_client_info ('long');;
prompt update t set y = 1 where y = -1 or id = 2 or id = 3;;
prompt then come back and hit enter
pause

select decode(msg1.seq,null,'NOT VISIBLE'), msg2.*
      from msg2, msg1
 where msg2.seq = msg1.seq(+)
 order by msg2.seq;

set echo off
prompt in another session issue:
prompt exec dbms_application_info.set_client_info ('first/last');;
prompt update t set y = -1 where id in ( 1,5 );;
prompt commit;;
prompt then come back and hit enter
pause

select decode(msg1.seq,null,'NOT VISIBLE'), msg2.*
      from msg2, msg1
 where msg2.seq = msg1.seq(+)
 order by msg2.seq;

commit;

select decode(msg1.seq,null,'NOT VISIBLE'), msg2.*
      from msg2, msg1
 where msg2.seq = msg1.seq(+)
 order by msg2.seq;

set echo off
prompt commit in the formerly blocked session (long)
prompt then come back and hit enter
pause

select * from t;

select decode(msg1.seq,null,'UNDONE'), msg2.*
      from msg2, msg1
 where msg2.seq = msg1.seq(+)
 order by msg2.seq;

Final Results:

        ID          Y
---------- ----------
         1         -1
         2          1
         3          1
         4          0
         5         -1

DECODE(MSG1.SEQ,NU        SEQ        SID     OLD_ID      OLD_Y     NEW_ID
NEW_Y CLIENT_INFO
------------------ ---------- ---------- ---------- ---------- ----------
---------- ---------------
                            1       3967          3          0          3
  0 blocker
                            2       3968          2          0          2
  1 long
                            3       3969          1          0          1
 -1 first/last
                            4       3969          5          0          5
 -1 first/last
                            5       3968          3          0          3
  1 long

But now, another interesting variation of the interesting variation: what if
id=5 satisfied the where condition
right from the beginning, and it gets later modified by "first/last" to a
non-satisfying value (here i've chosen 99) ?
So let's start with this table image:

        ID          Y
---------- ----------
         1          0
         2          0
         3          0
         4          0
         5         -1

Here's the setup:

drop table msg1;
drop table msg2;
drop sequence s;
drop table t;

set echo on
set lines 123
col client_info format a15
set pages 9999

create sequence s;

create table msg1
( seq int primary key, sid int,
  old_id int, old_y int,
  new_id int, new_y int );

create table msg2
( seq int primary key, sid int,
  old_id int, old_y int,
  new_id int, new_y int,
  client_info varchar2(64) );

create table t
as
select rownum id, 0 y
  from all_users
 where rownum <= 5;

update t set y = -1 where id = 5;

select * from t;

create or replace procedure log_msg2( p_seq in int,
                                      p_sid in int,
                                      p_old_id in int,
                                      p_old_y  in int,
                                      p_new_id in int,
                                      p_new_y  in int )
as
   pragma autonomous_transaction;
   client_info varchar2(64);
begin
    dbms_application_info.read_client_info (client_info);

    insert into msg2
    (seq, sid, old_id, old_y, new_id, new_y, client_info )
    values
    (p_seq, p_sid, p_old_id, p_old_y, p_new_id, p_new_y, client_info);
    commit;
end;
/

create or replace trigger t_trigger before update on t for each row
declare
    l_seq number;

begin
    select s.nextval into l_seq from dual;

    insert into msg1
    (seq, sid, old_id, old_y, new_id, new_y )
    values
    (l_seq, userenv('sessionid'), :old.id, :old.y, :new.id, :new.y );

    log_msg2
    (l_seq, userenv('sessionid'), :old.id, :old.y, :new.id, :new.y);
end;
/
exec dbms_application_info.set_client_info ('blocker');
--update t set y = -1 where id = 3;
update t set y = y where id = 3;

set echo off
prompt in another session issue:
prompt exec dbms_application_info.set_client_info ('long');;
prompt update t set y = 1 where y = -1 or id = 2 or id = 3;;
prompt then come back and hit enter
pause

select decode(msg1.seq,null,'NOT VISIBLE'), msg2.*
      from msg2, msg1
 where msg2.seq = msg1.seq(+)
 order by msg2.seq;

set echo off
prompt in another session issue:
prompt exec dbms_application_info.set_client_info ('first/last');;
prompt update t set y = 99 where id in ( 1,5 );;
prompt commit;;
prompt then come back and hit enter
pause

select decode(msg1.seq,null,'NOT VISIBLE'), msg2.*
      from msg2, msg1
 where msg2.seq = msg1.seq(+)
 order by msg2.seq;

commit;

select decode(msg1.seq,null,'NOT VISIBLE'), msg2.*
      from msg2, msg1
 where msg2.seq = msg1.seq(+)
 order by msg2.seq;

set echo off
prompt commit in the formerly blocked session (long)
prompt then come back and hit enter
pause

select * from t;

select decode(msg1.seq,null,'UNDONE'), msg2.*
      from msg2, msg1
 where msg2.seq = msg1.seq(+)
 order by msg2.seq;

Final result:

        ID          Y
---------- ----------
         1         99
         2          1
         3          1
         4          0
         5         99

DECODE(MSG1.SEQ,NU        SEQ        SID     OLD_ID      OLD_Y     NEW_ID
NEW_Y CLIENT_INFO
------------------ ---------- ---------- ---------- ---------- ----------
---------- ---------------
                            1       3971          3          0          3
  0 blocker
UNDONE                      2       3968          2          0          2
  1 long
                            3       3969          1          0          1
 99 first/last
                            4       3969          5         -1          5
 99 first/last
UNDONE                      5       3968          3          0          3
  1 long
UNDONE                      6       3968          5         -1          5
  1 long
                            7       3968          2          0          2
  1 long
                            8       3968          3          0          3
  1 long

seq=6 made the update (old_y=-1 comes from the snapshot taken at statement
start, i believe), but then "long" saw that the row was modified, so the
statement restarted.
Do you agree ?
My subconscious mind is shouting "block SCN ... block SCN .. serializable ..
mixup of consistent get and current mode get" but i'm too sleepy to shape that
intuition ;-), perhaps tomorrow ...

Rating: 5
how many dirty buffers August 19, 2003
Reviewer: amit poddar from New Haven, CT USA
Hi,

If each update gets a read consistent block image that means there would be
multiple dirty buffers for the same block.  How are theses changes merged since
finally only one block is written to the disk. (If all the dirty images are
written to the disk all the changes would not be on the disk.

Followup:
there is only one dirty buffer, the last one. the rest are just read consistent
versions of the same block at different points in time.

last block version is the only true one

Rating: 3
followup August 20, 2003
Reviewer: amit poddar from Connecticut, CT
Hi,

So am i correct in assuming that:

while updating oracle creates a read consistent view just for reading purpose
and makes the change in the orignal block and not the read consistent view of
the block created

thanks
amit

Followup:
we have to update the last version, yes.

consider:

update emp set sal = sal + 1;

if two people did that at more or less the same time and we didn't modify the
LAST version for both -- we'd lose updates.

Rating: 5
correct statement August 20, 2003
Reviewer: Freek from Belgium
Is following statement correct:

the update/delete statement will see the values as they exist on the moment that
the statement could be performed without being blocked by prior update/delete
statements of other sessions, and not as they existed on the moment the
statement started

Followup:
not really.  sort of.

say the update statement starts at time T1.

as it is processing, it encounters a locked row and blocks.  time is now T1+x.

it unblocks and sees a changed row.

two cases:

   1) row is effectively unchanged from time T1 (we can compare them by getting
      read consistent image and checking against current).  Then we proceed as
      normal.

      by effectively unchanged -- i mean the set of relevant columns to us is
      unchanged, the ones we used in the predicate, that we are changing with
      the set

   2) row is DIFFERENT.  statement rolls back and restarts.  Time is now T1
      all over again.  We moved the goal posts!

so, the update always sees the rows as of the time the statement began, we just
move the concept of "when the statement began"

Rating: 5
Most usefull August 20, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
I would like to see the answer to Freek's statement.

Amazing .. this is a very important point.

Rating: 1
Christo August 20, 2003
Reviewer: On a plate from UK
Rather than having someone feed you the answer why not work it out yourself with
two sql*plus sessions ?

Rating: 4
to: Christo August 21, 2003
Reviewer: Freek from Belgium
What I tried to do is summarize Tom's answer, and just wanted to know if I
didn't miss some nuance.

(I did in fact did a quick test by letting one session multiply a value in a
table but not committing and then let a second session multiply the same value.
The result supported the statement)

Rating: 4
Different results August 21, 2003
Reviewer: Rob from Amsterdam
Hi Tom,

After execution of your script, I get different results.
It seems that Oracle didn't "slid a bit" in my case. We use Oracle 9.2.0. Can
you explain why this is happening ?

The output was:

rob@DEV> update t set y = y where id = 3;

1 rij is bijgewerkt.

rob@DEV>
rob@DEV> set echo off
in another session issue:
update t set y = 1 where y = -1 or id = 2 or id = 3;
then come back and hit enter

DECODE(MSG1        SEQ        SID     OLD_ID      OLD_Y     NEW_ID      NEW_Y
----------- ---------- ---------- ---------- ---------- ---------- ----------
                     1      25494          3          0          3          0
NOT VISIBLE          2      25508          2          0          2          1

in another session issue:
update t set y = -1 where id in ( 1,5 );
commit;

DECODE(MSG1        SEQ        SID     OLD_ID      OLD_Y     NEW_ID      NEW_Y
----------- ---------- ---------- ---------- ---------- ---------- ----------
                     1      25494          3          0          3          0
NOT VISIBLE          2      25508          2          0          2          1
                     3      25510          1          0          1         -1
                     4      25510          5          0          5         -1

Commit is voltooid.

DECODE(MSG1        SEQ        SID     OLD_ID      OLD_Y     NEW_ID      NEW_Y
----------- ---------- ---------- ---------- ---------- ---------- ----------
                     1      25494          3          0          3          0
NOT VISIBLE          2      25508          2          0          2          1
                     3      25510          1          0          1         -1
                     4      25510          5          0          5         -1
NOT VISIBLE          5      25508          3          0          3          1

commit in the formerly blocked session

        ID          Y
---------- ----------
         1         -1
         2          1
         3          1
         4          0
         5         -1

DECODE        SEQ        SID     OLD_ID      OLD_Y     NEW_ID      NEW_Y
------ ---------- ---------- ---------- ---------- ---------- ----------
                1      25494          3          0          3          0
                2      25508          2          0          2          1
                3      25510          1          0          1         -1
                4      25510          5          0          5         -1
                5      25508          3          0          3          1

Followup:
 update t set y = y where id = 3;

you did the y=y test case, not the y=-1 test.  that is why

Rating: 4
changing statement August 21, 2003
Reviewer: freek from belgium
more complicated then I thought:

"the update/delete statement will see the values as they exist on the moment
that the statement could be performed without being blocked by prior
update/delete statements of other sessions that change a value used by the
query, and not as they existed on the moment the statement started."

create table test
( field1    number(10,0),
  field2    number(10,0),
  field3    number(10,0)
)
/

insert into test
values(5,5,1);

insert into test
values(4,5,1);

commit;

    FIELD1     FIELD2     FIELD3
---------- ---------- ----------
         5          5          1
         4          5          1

(before each test the table will be reset to this state)
TEST1
-----
session 1:

update test
set field1 = field1 * 1
where field1 = 5;

session 2:
-- this session will be blocked
update test
set field2 = field2 * 2
where field1 = 5;

session 3:

update test
set field1 = 5
where field1 = 4;

commit;

session 1: commit; -- will unblock session 2
session 2: commit;

result:

    FIELD1     FIELD2     FIELD3
---------- ---------- ----------
         5         10          1
         5          5          1

TEST 2:
session 1:

update test
set field2 = field2 * 1
where field1 = 5;

session 2:
-- this session will be blocked
update test
set field2 = field2 * 2
where field1 = 5;

session 3:

update test
set field1 = 5
where field1 = 4;

commit;

session 1: commit; -- will unblock session 2
session 2: commit;

result:

    FIELD1     FIELD2     FIELD3
---------- ---------- ----------
         5         10          1
         5          5          1

TEST 3
------
session 1:

update test
set field3 = field3 * 2
where field1 = 5;

session 2:
-- this session will be blocked
update test
set field2 = field2 * 2
where field1 = 5;

session 3:

update test
set field1 = 5
where field1 = 4;

commit;

session 1: commit; -- will unblock session 2
session 2: commit;

result:

    FIELD1     FIELD2     FIELD3
---------- ---------- ----------
         5         10          2
         5          5          1

TEST 4
------
session 1:

update test
set field2 = field2 * 2
where field1 = 5;

session 2:
-- this session will be blocked
update test
set field2 = field2 * 2
where field1 = 5;

session 3:

update test
set field1 = 5
where field1 = 4;

commit;

session 1: commit; -- will unblock session 2
session 2: commit;

result:

    FIELD1     FIELD2     FIELD3
---------- ---------- ----------
         5         20          1
         5          5          1

but if I understand Tom's answer correctly I should get:

    FIELD1     FIELD2     FIELD3
---------- ---------- ----------
         5         20          1
         5         10          1

?

freek@dev01> select banner from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
PL/SQL Release 9.2.0.1.0 - Production
CORE    9.2.0.1.0       Production
TNS for 32-bit Windows: Version 9.2.0.1.0 - Production
NLSRTL Version 9.2.0.1.0 - Production

Rating: 5
Answer ! :) August 21, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada.
To freek ,

Test4 did not do as you expected, because session 1 did not change a field which
is part of the where predicates in session 2.

Change
session 2:
-- this session will be blocked
update test
set field2 = field2 * 2
where field1 = 5;

TO
session 2:
-- this session will be blocked
update test
set field2 = field2 * 2
where field1 = 5 and field2 > 0;

And you will observe the result expected.

Rating: 5
Thanks August 21, 2003
Reviewer: freek from Belgium
christo,

thank you for showing me the mistake in my logic.
It is not the change in the value that we want to update that is controlling the
"restarting" of the statement but the change of the value that is in the
predicate.

I have now reread Tom's answer to my first post and it becomes clear now.

greetings

Freek

Rating: 5
Thank you August 22, 2003
Reviewer: rob from Amsterdam

Rating: 5
Thanks September 23, 2003
Reviewer: Pieraldo Antonello from Italy
so much!

August 21, 2003 Suresh -- Thanks for the question regarding "INSTR and SUBSTR?", version 8.1.7
You Asked
Tom,
   I would be getting the following string as an input to a database procedure
and I need to split it up as follows.

Input string: '101,20,102,30,103,40,104,20.....'

The result should be:
Variable1            Variable2
*******************************
101                  20
102                  30
103                  40
104                  20
.....

   I think I can use INSTR and SUBSTR functions to solve it, but the code would
look complicated. Is there any other (better) method to solve it? As always
thanks so much for your help.

Suresh
and we said...
I would create a simple "parse" routine.  Additionally I would us object types
to easily return this data as an array.  I chose object types over PLSQL record
types so I have the flexibility of using this function in SQL as well:

ops$tkyte@ORA815> create or replace type myScalarType as object
  2  ( variable1 number,
  3    variable2 number
  4  )
  5  /

Type created.

ops$tkyte@ORA815>
ops$tkyte@ORA815> create or replace type myArrayType as table of myScalarType
  2  /

Type created.

ops$tkyte@ORA815>
ops$tkyte@ORA815> create or replace function transform( p_str in varchar2 )
return myArrayType
  2  as
  3      l_data myArrayType := myArrayType();
  4      l_str  long default p_str || ',';
  5      n      number;
  6  begin
  7      loop
  8          n := instr( l_str, ',' );
  9          exit when (nvl(n,0) = 0);
 10
 11          l_data.extend;
 12          l_data(l_data.count) := myScalarType(substr(l_str,1,n-1), null );
 13          l_str := substr( l_str, n+1 );
 14          n := instr( l_str, ',' );
 15          exit when (nvl(n,0) = 0);
 16
 17          l_data(l_data.count).variable2 := substr(l_str,1,n-1);
 18          l_str := substr( l_str, n+1 );
 19      end loop;
 20
 21      return l_data;
 22  end;
 23  /

Function created.

ops$tkyte@ORA815>
ops$tkyte@ORA815>
ops$tkyte@ORA815>
ops$tkyte@ORA815> select *
  2    from TABLE( cast( transform('101,20,102,30,103,40,104,20') as myArrayType
) );

 VARIABLE1  VARIABLE2
---------- ----------
       101         20
       102         30
       103         40
       104         20
Review & Followup
Rating: 3
What if temporary table is used? August 27, 2001
Reviewer: VJ from Boston, MA USA
Instead of defining types, what if temporary table with 2 columns is used?

Rating: 3
Good suggestion August 27, 2001
Reviewer: Vijayaraghavan from Bangalore City, Karnataka State in INDIA
I did using temp tables and not types.
Temp tables are easy and avoid casting types in the query.
Which one really improves performance?

Followup:
You can definitely use a temp table -- the thing I like about the table type is
that it is a PLSQL datatype as well as a SQL datatype so I can use it in SQL
when I want to or just return it as an array to my function when I want it that
way.  Best of both worlds.

Rating: 2
Record Group August 21, 2003
Reviewer: IDL
I need to make this process in record group in form builder..
I mean i want to write SQL code not PL/SQL one without making stored procedure

Followup:
it won't be happening.

a record group is quite simply "a client phenomena".  the do NOT exist on the
server at all.

so, you won't be getting exactly what you want....

(but, if you are willing to do what is needed, you can accomplish your goal --
which is more important?)

August 21, 2003 Arup -- Thanks for the question regarding "How to flush the db buffer cache", version 9.2.0.2
You Asked
Tom,

ALTER SYSTEM FLUSH SHARED_POOL flushes the shared pool entries, not the block
buffers. Is there a way to flush them, too? This is important for a tuning
excercise where several methods are tried one after the other, but we would like
to reduce the effect of a pre-existing block in the buffer, without doing a
database restart.

Thanks a ton, as always.

Arup
and we said...
actually -- it is important as a tuning tool NOT to do that....

it is important to run the test, ignore the results, then run it two or three
times and average out those results.

In the real world -- the buffer cache will never be devoid of results.

To tune -- your goal is "reduce the LIO's" -- for the PIO's will take care of
themselves.

Flushing the shared pool and buffer cache is even more artificial then NOT
flushing them.

anyway, since no one listens to this advice anyway -- i suspect because it flies
in the face of "CW" conventional wisdom -- I'll tell you how.  just offline the
tablespace, online it again (but it'll be artificial!!! not to mention wrong)

Allow me to demonstrate how to do this -- but also WHY it is *meaningless*.
flushcache is a small c program i have, allocates all of the memory on my laptop
and touches it. flushing the OS cache and doing some heavy damage:

big_table@ORA920> select count(data_object_id) from big_table;

1 row selected.

Elapsed: 00:00:01.10

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1329 Card=1 Bytes=2)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1329 Card=1000000
Bytes=2000000)

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

big_table@ORA920> alter tablespac users offline;
alter tablespac users offline
      *
ERROR at line 1:
ORA-00940: invalid ALTER command

Elapsed: 00:00:00.03
big_table@ORA920> alter tablespace users offline;

Tablespace altered.

Elapsed: 00:00:00.05
big_table@ORA920> alter tablespace users online;

Tablespace altered.

Elapsed: 00:00:00.03
big_table@ORA920> !flushcache;
allocated 512m of ram 0x4212F008
touching it all.................done...

big_table@ORA920> select count(data_object_id) from big_table;

1 row selected.

Elapsed: 00:00:12.03

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1329 Card=1 Bytes=2)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1329 Card=1000000
Bytes=2000000)

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

big_table@ORA920> select count(data_object_id) from big_table;

1 row selected.

Elapsed: 00:00:01.10

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1329 Card=1 Bytes=2)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1329 Card=1000000
Bytes=2000000)

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

So, how could that be?  does it take 1 second or 12 seconds to do 13,656 PIO's?

Well, it depends.

are you prepared to reboot your server after each query?  No, probably not,
but that would be as real world as flushing the buffer cache.

Just do it by:

run it once
run it over and over <<<=== actually keeping timings now

Like the old saying "if you watch the pennies, the dollars will take care of
themselves" -- if you watch the LIO's -- the PIO's will take care of
themselves
Review & Followup
Rating: 5
PIOs, Again???? January 20, 2003
Reviewer: Arup Nanda from Norwalk, CT, USA
Tom,

I agree with you that LIOs are more important and watching them is probably the
sanest thing to do. This was necessary to convince the management with fixed
ideas the futility a tuning excercise which I considered futile. Anyway...
that's another issue.

I am curious how the PIOs are same in subsequent calls to select
count(object_id) from big_table. In the first time it was called after the
flushcache program, I understand there are some physical IOs. However, in the
next call, shouldn't the blocks be up on the db buffer cache? Why another set of
PIOs?

Thanks.

Arup

Followup:
my laptop has an insufficient buffer cache to hold it all.  13k blocks flushed
it out.

Rating: 3
Thanks for the question regarding "How to flush the db buffer cache", version 9.2.0.2 January 21, 2003
Reviewer: Greg Johnson from Brisbane, Queensland, Australia
Tom, you mentioned in your follow up to this question, that you had to perform
the same number of PIO's because your Buffer Cache was 13K smaller than the
table. It's my understanding that for a full table scan on an Uncached table,
Oracle reads blocks into the LRU end of the buffer cache at the rate of
DB_MULTI_BLOCK_READ_COUNT + 1 Blocks. Therefore, in this case, EVERY time you
re-ran the count, you'd have to perform the same number of PIO's to get the same
result.

Could the difference in time be related to loading the  blocks into the HD's
cache?

Followup:
13k was the number of blocks.   Not that the buffer cache was 13k smaller -- it
was that I had 13,000 blocks to read.

by reading 13,000 blocks -- i totally cycled them all through the buffer cache
and back out again.  by the time I read the last block from that table -- the
first block was no longer in my cache.  that is what I meant.

Yes, each and every time I run this, I'll do every block as a PIO.

The difference in time (as I was trying to point out) was exactly that.  I
flushed the OS buffer cache by allocating and touching every byte of ram on my
disk.  I was trying to point out that on most systems, simply flushing the
buffer cache is like "yeah, so what".  It doesn't help you tune -- it is
misleading -- it is meaningless.

Rating: 4
Reducing logical IOs isn't always desirable January 21, 2003
Reviewer: Garry from London
Hi Tom.
To tune your goal is "reduce the LIO's" -- for the PIO's will take care of
themselves.

You can further qualify this by saying that "to tune QUERIES THAT USE THE BUFFER
CACHE your goal is to reduce LIOs". In some cases, far greater performance gains
can be made by cutting out the buffer cache completely and doing full table
scans in parallel.

Followup:
but won't that reduce LIO's :)

Rating: 5
Good Testing Advice January 21, 2003
Reviewer: Robert from Memphis, USA
Tom,

I had always thought that "cw" method would be the best. Thanks for pointing out
and proving it is not!

Thanks,

Robert.

Followup:
Other CW's (that are all 100% wrong)

o indexes mean fast=true, if the optimizer doesn't use them, the optimizer has
  a bug

o indexes need to be rebuilt frequently or on a scheduled basis

o rebuilt indexes are faster, smaller and better

o segments should have few extents -- double digit extents are really bad for
  performance

o all cursors in code should be explicit cursors, implicit cursors are slow and
  bad

o select count(1) is better then select count(*)

o procedural code is faster then doing it in SQL

o adding more CPU will make our systems faster

o index space is not reused

o nologging stops all redo log from being generated on that segment

o the most selective fiedls must be first in an index

o you should commit frequently to save resources and time

o a cold backup is better then a hot backup

Rating: 3
What is the meaning of 'CW' January 21, 2003
Reviewer: A reader
What does 'CW' mean? What is its expansion

Followup:
...
anyway, since no one listens to this advice anyway -- i suspect because it flies

in the face of "CW" conventional wisdom -- I'll tell you how.
.....

Rating: 5
more examples January 21, 2003
Reviewer: A reader
Hi Tom, Can you show us the following two CW is wrong in your usual way?

o adding more CPU will make our systems faster

Followup:
You have a system.

It is "running slow".

80% of the users are OLTP.  OLTP is CPU de-intensive, IO intensive.  The OLTP
users are constrained by the amount of IO they can get.

20% of the users are running reports.  The processing of the data for the
reports is sucking the life out of the CPU.  These reports do a lot of IO but
they are constrained by the CPU they can grab (so the amount of IO they do is
gated by the amount of CPU they have).

You look at the system and say "oh my, CPU is at or near 100% utilized, we need
more".

So, you buy more CPUs put them in.  Now everything goes really slow for the OLTP
guys (the ones that actually make money booking orders).  Why?  Cause the
reports that were CPU bound now have lots of CPU and have DOUBLED the amount of
IO they were doing -- meaning the IO bottleneck that was the gating factor for
the transactional guys is even worse then before.

some bottlenecks are good -- we use them to control access to resources.  For
example think "connection pools in an app server" -- or think "transaction
processing monitors -- tpms like tuxedo or CICS" -- or thing "shared server --
aka MTS under Oracle".  All of those artificially constrain a resource in order
to achieve the best overall performance.

What we need to do is identify what bottleneck we need to remove in order to
increase performance -- throwing more hardware at a problem does not assure
success, in fact the opposite can be true.

Rating: 5
Reader January 22, 2003
Reviewer: A reader
Tom,

In your experiment, PIO is almost
constant for FTS everytime you run the query. Is this not
true that when Oracle reads multiblock_read_count
number of blocks from disk, if Oracle finds
a block that is already in the cache, it will
skip that block , and not read it from disk.
Has the algorithm been changed

Thanks

Followup:
if it reads mbrc (multi block read count) from disk -- it reads mbrc from disk
-- the IO request is "read N blocks from file X starting at block Y".  It never
had the ability to say "read N blocks from file X starting as block Y but skip
the 2cnd and 5th one"

It might do single block io if the data is cached, might blow off the cache and
do multi-block io.

Rating: 5
Reader January 22, 2003
Reviewer: A reader
Tom,

This is an excerpt from the article "Predicting Multiblock
Read call size" (Hotsos)

<excerpt>
10. WAIT #47: nam='db file scattered read' ela= 0 p1=12 p2=71980 p3=9
11. WAIT #47: nam='db file scattered read' ela= 0 p1=12 p2=71992 p3=4
12. WAIT #47: nam='db file scattered read' ela= 0 p1=12 p2=71997 p3=16
13. WAIT #47: nam='db file scattered read' ela= 0 p1=12 p2=72013 p3=4
14. WAIT #47: nam='db file scattered read' ela= 0 p1=12 p2=72018 p3=3
15. WAIT #47: nam='db file scattered read' ela= 0 p1=12 p2=6857 p3=2
16. WAIT #47: nam='db file sequential read' ela= 0 p1=12 p2=6861 p3=1

<excerpt>

Here each 'db file scattered read' calls fetched 9,4, 16, 4, 32 block at each
call.

In your comment you have said
"
might blow off the cache and
do multi-block io.
"

Is this an enhancement in newer version of Oracle.

Thanks

Followup:
I don't get the connection between the wait output you put there and your
conclusion.  please elaborate -- whats your point?

Rating: 5
Read January 23, 2003
Reviewer: A reader
Tom,

For the event 'db file scattered read' (FTS) ,Oracle suppose to
read from diak at a rate of batches of multilock_read_count
number of blocks, The wait events was taken from setting
event  10046 to trace the SQL.

For one file (p1 = 12), Oracle starts reading from block# 71980 (p2)
9 blocks (p3) upto block 71988. Then Oracle skips 3 blocks
71989, 71990, 71991. Oracle then starts reading from block# 71992 (p2)
4 blocks (p3). It seems Oracle did not continuously read 16 blocks
(9+3+4) because the block# 71989, 71990, 71991 may already be in
the buffer cache

I do not have access to the article right now, since I am at work
and I do not have password to the hotsos site.

I'll update additional information from the article later on

Thanks

Followup:
it is skipping blocks sure in that example by reading "chip chop", but turn on
parallel query or any other myriad of features and it won't...

it is still doing multi-block io in that case.

as i said "It might do single block io if the data is cached, might blow
off the cache and do multi-block io."

but in my example -- none of the blocks would have been found in the cache since
by the time I read the last blocks -- the first blocks were long gone.

Rating: 5
Care to share your flushcache.c : ) January 23, 2003
Reviewer: A reader

Followup:
all it does it allocate a big chunk of memory and touch it.  flushes the file
system buffer cache (as well as most of the rest of the operating system!)

it is "slow=true"

#include "stdio.h"
#include "malloc.h"
#include "assert.h"

int main()
{
int    meg  = 512;
int    i;
long   size = meg*1024*1024;
char * ptr = malloc( size );

    assert( ptr != NULL );
    printf( "allocated %dm of ram 0x%X\n", size/1024/1024, ptr );
        printf( "touching it all...." ); fflush(stdout);
    for( i = 0; i < meg; i++ )
    {
        memset( ptr+(i*1024*1024), 0, 1024*1024 );
        printf( "." ); fflush(stdout);
    }
        printf( "done...\n" );
    return 0;
}

Rating: 5
Reader January 24, 2003
Reviewer: A reader
Thanks Very Much

Rating: 5
full table scan where size of buffer cache is less than size of the table June 19, 2003
Reviewer: Arul from CA, USA
Hello Tom,

On our oltp system, a sql query from an application is doing a full table scan
on a 60 million row table whose size is ~4000MB.

For a 30 minute interval of statspack, the buffer gets/exec and physical
reads/exec is ~270,000 for this query; there are 3 entries for this query,  2
executions per hash_value (not sure why?) -- so a total of 6 executions during a
30 minute interval.

The buffer cache is set at 750MB, there are several other critical applications
that use the same oracle instance.

So I am thinking that the server process that does a full table scan, reads all
blocks for this table into the buffer cache for each execution. Thats a lot --
4000MB into a 750MB buffer cache !?!?

Is my understanding correct? If so, is n't this affecting other applications?
what happens to the current blocks for other server processes in the cache? how
does Oracle handle this? is n't this very very very bad? so far, I have n't
heard any complaints from anyone !

I would love to hear your comments, appreciate your time on this one.

(I can tune this query to use indexes, this brings down the execution time from
a couple of minutes to sub seconds, as well as the lios and pios)

Thank you.

Followup:
if you can tune this query to be subsecond and bring the LIO's down -- you don't
even need to have this conversation!

yes, it is affecting others.

yes, it is used part of (not all of) the buffer cache.  It will not flush the
entire thing, but it will impact the utilization (big tables that are scanned
are buffered in a way that causes those full scan blocks to be aged out much
faster then other blocks -- so the full scan tends to age out it's own blocks
first, it will not blow out the entire buffer cache)

yes, if you can index it and the index does not materially affect any other
component of the system -- that would be the correct thing to do.

Rating: 5
Consistent Gets and Physical Reads August 21, 2003
Reviewer: Matt from UK
Tom,

You say we should strive to reduce the LIO - I have a situation where we have a
plan generated with a low LIO which performs worse than a plan with higher
(about 20times) LIO. The higher elapsed time I guess is due to greater physical
reads, generated by the full table scans for the Hash_Joins, compared to the
smaller set of unique index lookups generated by NL. What I don't understand is
how the hash_join generates a far smaller number of consistent gets, but higher
physical reads.

1  SELECT COUNT(*)
  2  FROM XX_EXPLOSION_TEMP BeT,
  3      Mtl_System_Items_Vl Msi,
  4       Mtl_System_Items_Vl MsitoP
  5  WHERE Bet.Organization_ID   = Msitop.Organization_Id
  6  AND   Bet.Top_Item_ID       = Msitop.Inventory_Item_Id
  7  AND   BEt.Organization_Id   = Msi.Organization_Id
  8  AND   Bet.Component_Item_Id = Msi.Inventory_Item_Id
  9* AND   Bet.Group_ID          = 999
SQL> set timing on
SQL> set autotrace on
SQL> /

  COUNT(*)
----------
     29724

Elapsed: 00:00:15.21

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3891 Card=1 Bytes=52
          )

   1    0   SORT (AGGREGATE)
   2    1     HASH JOIN (Cost=3891 Card=29937 Bytes=1556724)
   3    2       HASH JOIN (Cost=3232 Card=29937 Bytes=1197480)
   4    3         HASH JOIN (Cost=2579 Card=29937 Bytes=838236)
   5    4           HASH JOIN (Cost=1370 Card=29831 Bytes=626451)
   6    5             TABLE ACCESS (FULL) OF 'XX_EXPLOSION_TEMP' (Cost
          =164 Card=29726 Bytes=416164)

   7    5             INDEX (FULL SCAN) OF 'MTL_SYSTEM_ITEMS_B_U1' (UN
          IQUE) (Cost=1195 Card=447564 Bytes=3132948)

   8    4           INDEX (FULL SCAN) OF 'MTL_SYSTEM_ITEMS_B_U1' (UNIQ
          UE) (Cost=1195 Card=447564 Bytes=3132948)

   9    3         TABLE ACCESS (FULL) OF 'MTL_SYSTEM_ITEMS_TL' (Cost=6
          34 Card=447564 Bytes=5370768)

  10    2       TABLE ACCESS (FULL) OF 'MTL_SYSTEM_ITEMS_TL' (Cost=634
           Card=447564 Bytes=5370768)

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

 1  SELECT /*+ Rule */ COUNT(*)
  2  FROM XX_EXPLOSION_TEMP BeT,
  3      Mtl_System_Items_Vl Msi,
  4       Mtl_System_Items_Vl MsitoP
  5  WHERE Bet.Organization_ID   = Msitop.Organization_Id
  6  AND   Bet.Top_Item_ID       = Msitop.Inventory_Item_Id
  7  AND   BEt.Organization_Id   = Msi.Organization_Id
  8  AND   Bet.Component_Item_Id = Msi.Inventory_Item_Id
  9* AND   Bet.Group_ID          = :Group_ID
SQL> c/:Group_ID/999
  9* AND   Bet.Group_ID          = 999
SQL> l
  1  SELECT /*+ Rule */ COUNT(*)
  2  FROM XX_EXPLOSION_TEMP BeT,
  3      Mtl_System_Items_Vl Msi,
  4       Mtl_System_Items_Vl MsitoP
  5  WHERE Bet.Organization_ID   = Msitop.Organization_Id
  6  AND   Bet.Top_Item_ID       = Msitop.Inventory_Item_Id
  7  AND   BEt.Organization_Id   = Msi.Organization_Id
  8  AND   Bet.Component_Item_Id = Msi.Inventory_Item_Id
  9* AND   Bet.Group_ID          = 999
SQL> /

  COUNT(*)
----------
     29724

Elapsed: 00:00:08.47

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   SORT (AGGREGATE)
   2    1     NESTED LOOPS
   3    2       NESTED LOOPS
   4    3         NESTED LOOPS
   5    4           NESTED LOOPS
   6    5             TABLE ACCESS (BY INDEX ROWID) OF 'XX_EXPLOSION_T
          EMP'

   7    6               INDEX (RANGE SCAN) OF 'XX_EXPLOSION_TEMP_N4' (
          NON-UNIQUE)

   8    5             INDEX (UNIQUE SCAN) OF 'MTL_SYSTEM_ITEMS_B_U1' (
          UNIQUE)

   9    4           INDEX (UNIQUE SCAN) OF 'MTL_SYSTEM_ITEMS_TL_U1' (U
          NIQUE)

  10    3         INDEX (UNIQUE SCAN) OF 'MTL_SYSTEM_ITEMS_B_U1' (UNIQ
          UE)

  11    2       INDEX (UNIQUE SCAN) OF 'MTL_SYSTEM_ITEMS_TL_U1' (UNIQU
          E)

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

Followup:
yes, in general -- it is all about LIO.

whats your version
hash area size? or pga_aggregrate_target?

Rating: 5
Think I have found my answer August 21, 2003
Reviewer: Matt from UK
http://asktom.oracle.com/pls/ask/f?p=4950:8:4861532271858202556::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:6601251003901,
Its bizarre that Oracle APPS specifically state that we should use the following
values:

o optimizer_index_caching  - 0

o optimizer_index_cost_adj - 100

Perhaps you should have a word with them Tom !

August 21, 2003 lisa -- Thanks for the question regarding "Log
      buffer size tuning", version 8.1.6

      You Asked
Oracle documents say Log buffer is normally small compared with the total SGA
size, its key ratio is the space request ratio: redo log space requests / redo
entries.

Could you tell me what is the key ratio we are looking when adjust log buffer in
initsid ora file ?
and how to adjust the log buffer size ?

As always, thank you for your response.

      and we said...
Well, I just look at wait events.  If you see lots of log_buffer_space waits,
consider making the log_buffer init.ora parameter bigger.

It is true that the log_buffer is trivial in size compared to the rest of the
sga.  Your block buffer cache is generally many 10's to 100's of megs (or even
in the gigs) in size.  Your shared pool is somewhere between 25 and 100 meg or
so.  Your log_buffer is generally 5meg or less.

Since the log buffer is flushed:

o every 3 seconds
o every commit
o when 1/3 full
o when 1meg full

regardless -- having a HUGE one just wastes ram as one of the above 4 events
will kick in an flush it.
Review & Followup

      Rating: 5
LGRW
      flushing  October 24, 2001
Reviewer:  Andre
      Whittick Nasser  from Rio de Janeiro, Brazil
Additionally to the LGWR flushing triggering events you mentioned, Oracle says
LGWR writes "before DBWR writes to disk". That is something that is not clear to
many people. It means...

Quote from the Concepts Guide:

Before DBWn can write a modified buffer, all redo records associated with the
changes to the buffer must be written to disk (the write-ahead protocol). If
DBWn finds that some redo records have not been written, it signals LGWR to
write the redo records to disk and waits for LGWR to complete writing the redo
log buffer before it can write out the data buffers.

Thanks !

          Followup:

correct -- dbwr might nudge lgwr to make sure the redo for a block is flushed
BEFORE the block is....

      Rating: 5
log buffer
      space  October 24, 2001
Reviewer:
      A reader  from Tx, USA
You said- "If you see lots of log_buffer_space waits,
consider making the log_buffer init.ora parameter bigger."
How much do you consider 'lots'
I checked the v$system_event and found 115 in the morning and 180 in the
afternoon, Is this consider a lot?

          Followup:

turn on timed statistics and view the time_waited in v$system_event.  If that
amount of time is more then you want to have -- fix it.  If the time waited is
neglible in the grand scheme of things, ignore it.

Looking at the wait count of 115, its not realy possible to say if that is good
or bad.  If you had but 1 transaction, its probably pretty bad.  If you had
1,000,000, its probably pretty good.  You need to look at that number in regards
to your system to determine if it is "bad" or not.

      Rating:
      5
  July 19, 2002
Reviewer:
      A reader
Hi Tom,

Our DBA has set this values for log buffers and buffer cache

SQL> sho parameter buffer

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
buffer_pool_keep                     string
buffer_pool_recycle                  string
db_block_buffers                     integer 22000  <-------
log_buffer                           integer 3276800 <-----
use_indirect_data_buffers            boolean FALSE

 As far as i know log buufer shuold be small aroung 5M.

 This setting is in production.

 Our environment is working without any problem.

 Any comment regarding above buffer values?

 Thanks

          Followup:

If I told you "I stopped at 50 red lights on my last car trip" -- could you
comment on that?  Is that "good" or is that "bad"

Regardless of what you say -- given just that amount of data -- I will
successfully argue the counter.  If you say it is bad, I'll prove it was very
good and vice versa.

No, I cannot comment.  If the log buffer should always be 5m - it wouldn't be a
parameter ;)

Without knowing

o the transaction rates
o the sizes of the transactions
o the amount of ram on the machine
o the number of users
o the configuration (dedicated or shared)
o the block buffer size
o etc etc etc

one cannot really comment.

      Rating:
      5
  July 19, 2002
Reviewer:
      A reader
o the transaction rates
  what is it? how can i get that?
o the sizes of the transactions
   Siebel CRM application with 150 Concurrent user.
o the amount of ram on the machine
      5 GB (Solaris OS)
o the number of users
  150 Concurrent user
o the configuration (dedicated or shared)
   Dedicated
o the block buffer size
    8k

- ORACLE 8.1.6.1

          Followup:

If you developed or are deploying a system and don't know what "transaction
rates" are (defined) and don't know what they are going to be (eg: we will be
doing 1,000 transactions per minute) -- you could be in trouble.

Not ever having done a Sighbel implementation, I cannot comment on the size of
their transactions.

The numbers look OK to me, especially if "system is running fine".

      Rating:
      5
  May 07, 2003
Reviewer:
      A reader
Hi Tom,

 Asking same question again in this thread. Now system
is NOT Running fine(after a Year) It is getting worst day by day (Siebel CRM
Implementation)

During the day time CRM database is doing only query(90% of the time) and during
night we have batch processing from lagacy system which update CRM database.

Queries are taking long time during day
Our settings are

SQL> sho parameter buffer

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
buffer_pool_keep                     string
buffer_pool_recycle                  string
db_block_buffers                     integer 22000  <-------
log_buffer                           integer 3276800 <-----
use_indirect_data_buffers            boolean FALSE

sort_area_retained_size              integer 253952
sort_area_size                       integer 1032192
hash_area_size                       integer 2064384

any comments on init.ora parameter or others setting.

Thnaks

          Followup:

I have no idea who you are, or what this might be a continuation of.

I can say that looking at init.ora parameters is sort of a useless exercise.
Especially incomplete ones.

For example,

ops$tkyte@ORA920> select 22000*2/1024, 22000*4/1024, 22000*8/1024, 22000*16/1024
from dual;

22000*2/1024 22000*4/1024 22000*8/1024 22000*16/1024
------------ ------------ ------------ -------------
    42.96875      85.9375      171.875        343.75

so, do you have 43m, 86m, 172m or a 344meg buffer cache.

Even if you were to answer that -- so what?  without lots of other stuff -- they
are just numbers, like 123 is a number.  by itself -- nothing.

Now, since you are running an off the shelf application, provided by a
relatively large company with lots of customers -- I would suspect they would
have

a) helped you size your system
b) ensured you bought sufficient resources for your implementation
c) gave you best practices for tuning Oracle under siebel
d) have consultants on site working with you to ensure your success?

A 15 minute long statspack report performed during your peak performance times
would be useful if you want any comments from us over here.

      Rating:
      5
  May 07, 2003
Reviewer:
      A reader
Hi Tom,

Thnaks for the feedback.
I am your one of the biggest fan.

Siebel uses RULE base optimizer.
what about log_buffer =  3276800 setting.

Any advice from siebel regarding database always stinks.Even if they have
problem is their siftware they most of the time blame oracle.

Their consultant always give good imresssion about sql server and DB2 databases
(But they accept most of their users(60-70%) uses oracle as database)

          Followup:

log buffer does not affect SELECT query performance

      Rating:
      3
Log_buffer  August 21, 2003
Reviewer:
      Nathan  from UK
Tom,

How can I reduce the size of Log_buffer ? If the log_buffer is smaller than
needed we can increase it by looking at log_buffer_space waits, what about the
other way round ?

The log_buffer setting, 150M  at my new work place seems very very high ( for
any kind of transactions ). A truncate of a single row table takes 5 minutes (
local write wait !) There is indeed a huge io activity on the database , (around
35 million per day ) ( nologging ). I suspect this setting but to bounce the db
i need to be able to prove it and be certain about the value. Is there any way i
can check log_buffer usage and its impact on the db  ? Also there are waits due
to db file sequential read  and db file scattered read , I intend to increase
the multi block read count to 32 ( block size 8) Would this help or increase  IO
bottle neck congestion ?

Top command issued generally shows these values !
178 processes: 174 sleeping, 2 running, 2 on cpu

CPU states:  4.2% idle, 35.5% user,  5.4% kernel, 54.9% iowait,  0.0% swap

Memory: 10G real, 207M free, 8347M swap in use, 11G swap free

  PID USERNAME THR PRI NICE  SIZE   RES STATE   TIME    CPU COMMAND

25513 oracle     1  40    0    0K    0K cpu11  90:51 12.55% oracle

14210 oracle     1  40    0    0K    0K run    32:56  8.20% oracle

22698 oracle     1  60    0    0K    0K sleep   7:33  5.75% oracle

 3980 oracle     1  60    2    0K    0K sleep  37:59  2.59% oracle

15230 oracle     1  60    0    0K    0K sleep  15:17  2.14% oracle

 5227 oracle     1  60    0    0K    0K sleep   3:36  0.92% oracle

10377 oracle     1  60    0    0K    0K sleep 203:12  0.29% oracle

27037 oracle     1  58    0    0K    0K sleep  10:30  0.27% oracle

10339 oracle     1  60    0    0K    0K sleep 211:19  0.26% oracle

13441 oracle     1  58    0    0K    0K sleep  18.9H  0.24% oracle

23789 oracle     1  60    0    0K    0K sleep   0:20  0.23% oracle

23653 oracle     1  58    0    0K    0K sleep   0:20  0.22% oracle

12447 oracle     1  60    0    0K    0K sleep   0:04  0.21% oracle

10390 oracle     1  58    0    0K    0K sleep 265:04  0.18% oracle

10359 oracle     1  60    0    0K    0K sleep 201:36  0.18% oracle
Regards
Nathan

          Followup:

the log buffer will flush

every 3 seconds
when 1/3 full
upon commit
when some threshold is hit -- forget if it is 1/3/5 meg -- buffered

your issues are not log buffer related.

a truncate taking really long means either:

o table has lots of dictionary managed extents -- solution = use lmt's

o table has lots and lots of dirty blocks in the buffer cache and we are
checkpointing them out of the system.  solution = wait (make io faster)

August 21, 2003 anthony -- Thanks for the question regarding "FK
      where ever possible?", version 8.1.7

      You Asked
1. I have a schema with about 150 tables.
   Do I have to define FK tightly for all the tables
   where ever possible or only limited FKs are enough?
   Maintenance will become a pain if we define FK
   for all the tables. what are the pros and cons?
   Do we have to create  index for all FKs in 8i also?

2.create table t_emp as select * from scott.emp
  where 1=0;
 If table is created as above, constraints are not getting created. Why?

3. Is there any good site/Book/white paper on Tables Design?

Thanks in Advance,
anthony

      and we said...
1) if you have a foreign key relationship, by all means do it.

That is like asking "should I put a primary key on all of my primary keys?" or
something.

If you have a foreign key -- define it.

2) that just creates a table and copies the data, constraints are never copied
as part of create table as select -- it is just the way it works.

3) there are lots, there are entire tomes written on this subject, there are
college courses on this.  You could goto amazon.com and search on something like
database design and see hundreds.
Review &
      Followup

      Rating: 5
More on
      FKs  September 20, 2002
Reviewer:
      A reader
Dear Tom,
We have a system in which there is a warehouse type of a load in excess of 3
million and related cascade of data to other tables, once a month. The rest of
the month system is a pure OLTP with a few hundred coming in. A senior oracle
consultant advised to remove all FK constraints on the system citing performance
reasons, and advised programatic validations of FKs. Should we have FKs here, if
we enable/ disable before the big data load, will it not be a costly Index
builds when FKs are enabled? Or, should we leave it like this?
Your help extremely appriciated, as always.
Regards

          Followup:

You know, SR smeener -- me, I value CORRECT, CONSISTENT, verifiable data over a
nudge in performance (especially since if I'm 1000% sure the data is clean, a
super SR consultant would know to disable the constraints, bulk load and then
enable them without making us verify them -- if in fact the goal is to make sure
the data load is as fast as possible)

(what is the relationship between "index builds" and FK's by the way???  While
its true that a FK should be indexed if you delete from the parent or update the
parent key, it will be the SAME if you do the validation yourself -- do you
*really* want to full scan the child table whenever you delete a parent? (no --
you don't, you'd have an index)

Does this person really think that doing constraint validate in the client app
is

a) faster
b) reliable

if so, they would be wrong.

Leave the FK's there.

Tune your load if you are 1000% sure the data is clean by disable/enable rely
without validation.

Don't TOUCH your OLTP application, DO NOT, repeat don't even *think about*,
moving the integrity constraint checking into the client.

      Rating: 4
No
      FK  September 21, 2002
Reviewer:  tony
       from INDIA
I've seen some web applications at some customers' site whithout having even a
single FK in the database, but FK is enforced through the front-end application
only. Is it a correct approach?

          Followup:

No -- isn't that blatantly obvious from my reply????

"if you have a foreign key relationship, by all means do it.

That is like asking "should I put a primary key on all of my primary keys?" or
something.  "

It is stupid to enforce it via the application AND I'll betcha $5 they aren't
even doing it right (eg: when they delete from the parent, do the LOCK the
entire child table first?  Else what is to prevent another session from
inserting a child record that points to this parent that is going to be deleted.
The probably coded something like:

   select count(*) from child where fk = :pk;
   if count was zero then delete from parent where pk = fk;

During the count, someone is inserting -- you won't see that, they delete -
ORPHAN child)

It is excessively, extremely, very difficult to do RI all by yourself.

      Rating:
      4
  September 21, 2002
Reviewer:  Dave
       from Ohio
I would add the FK's, even if they are disabled and not verified. If the load
rocess ensures that there can be no violations, then set them to RELY.

FK's are an important piece of meta-data for both the database optimizer and
support staff.

      Rating: 4
then why oracle
      applications dont have FK  September 22, 2002

Reviewer:  david
Hi

I personally think business rules must be enforced in the database however I
have worked with Oracle Applications which hasnt got a single FK! In 11i there
are some but not all neither! I once asked someone from Oracle Consulting why
this is happening in Oracle Applications and he said it's to improve
performance.... Now is this guy like the senior oracle consultant we saw here?

          Followup:

No, it is because Oracle apps, unlike the ones you will write in house, are a
generic -- 75% done -- implementation.

The cannot always enforce fk's in the DB (although they are starting to move
them into there when possible) because the relationships are not hard and fast-
they are not known until AFTER you configure and install the software.

In code you write, this is rarely the case.  You have a model, the model has
FK's, use the database to enforce them.

      Rating: 5
Primary Key can
      reference foreign key ?   August 21, 2003

Reviewer:  Amit Kumar
Hi Tom,

I am designing a web based application in which there are number of forms a user
needs to fill in.

The user must fill in the first page ( or form ), and the rest he can fill in at
his leisure. Right now, for each page I have a different table, and each table
has the same primary key - user_id.

Consider :
create table s_basic_info
(
USER_ID                integer primary key,
FIRST_NAME            varchar2(35),
MIDDLE_NAME            varchar2(35),
LAST_NAME            varchar2(35),
.
.
);

Another one is :
create table s_local_address
(
  USER_ID                integer primary key,
  LOCAL_STREET                          varchar2(100),
  .
  .
);

I would like to create a referential integrity from s_local_address.user_id to
s_basic_info.user_id. Do you think, local_address primary key can be a
referential integrity too ? alternatively, can I just make
s_local_address.user_id not null and unique and let it reference s_basic_info ?

Thanks.

          Followup:

if a user can have only one local_address then those fields rightly belong in
s_basic_info.

not a big fan of 1-1 relationships in separate tables, not necessary.

but if you keep them separate and it is 1-1 -- meaning user_id is unique in
s_local_address, that would be an appropriate primary key.

August 21, 2003 Pratap -- Thanks for the question regarding
      "Connection pools and global temporary tables", version 9.0.0

      You Asked
Hi Tom

This is how I have implemented the search functionality on a web application -
(Application server is WebLogic)

The functionality is such that if the numer of rows fetched exceeds a threshold
then
an error is to be displayed to the user.

So I do the following steps -

1. insert into a global temporary table.... select ..where rownum <= threshold +
1
2. Get sql%rowcount
3. If count > threshold then I pass an out parameter with the error message
4. If count <=  threshold then I open a ref cursor from the global temporary
table and pass it as an out parameter

I am facing a problem -

The weblogic application has a connection pool. So data in the temporary table
is not being deleted when the connection is closed.
I have to delete the data from the temporary table before inserting into it.
This I feel is an overhead and will cause a problem if many users get the same
connection from the pool.

The Java guys say that the same connection would never be given to two different
clients

My questions are -

1. Is this true that the same connection would never be given to different
clients

2. Is there any better way of impelmenting this functionality. I can only think
of using analytic function -
count(*) over()
The java guys can then open the result set and after reading the first row
decide whether to display the
list or not.

Thanks and regards

Pratap

      and we said...
Why you would give an error for a search is beyond me -- I would just limit the
hits to threshold and tell them (if they have the PATIENCE to page down to
"threshold") that hey "your search returned lots of stuff, there might be more
but we are only going to show you the first N hits".

That way -- no temp table, no muss, no fuss, end user is happy, you are happy
and they'll NEVER have the patience to page down to the end anyway (so they'll
never see this)

Ask the "java guys" this:  If the same connection is never given to two
different clients -- what is the point of the connection pool?  You see, the
entire POINT of a connection pool is to share the connection between clients
(else, you might just as well have each client get their own connection right).
Now, maybe what the java guys mean is that "we grab the connection and keep for
a couple of pages whilst the user is paging up and down and then we give it back
-- freeing it for someone else".  In that case, you can safely TRUNCATE the
global temporary table before doing the insert (fast, low overhead, concurrent)

I would

a) trash the temp table, period.
b) just return the result set that you are currently inserting

personally.
Review & Followup

      Rating: 3
temp tables vs
      package variables  June 04, 2003
Reviewer:
      Jan
As I understood temporary tables are supported by connection pooling, so web
users will see their data there also in case the connections are shared between
the users? If yes, so why it does not work in similar way for package variables?
What is the difference?

Thanks

      Rating:
      4
Question  August 21, 2003
Reviewer:
      A reader
Is it a good idea to use  ref cursors  selecting  from global temporary tables
which are fed values after some compex calculation  in 3 tier
web app with weblogic Oracle.

 The issue we have is it returns duplicate rows whenver the Browser refreshes
the page i.e it re-calulates the procedure to populate global tEmp table.
For the connection pool the Oracle session is not closed but reused.
Thanx
Will on commit delete rows work for Global Temp table in 3 tier.
Or should I abondon global temp table.
SO whats the alternative

      Rating: 4
Question Oracle
      ver 9.2  August 21, 2003
Reviewer:
      A reader
Is it a good idea to use  ref cursors  selecting  from global temporary tables
which are fed values after some compex calculation  in 3 tier
web app with weblogic Oracle.

 The issue we have is it returns duplicate rows whenver the Browser refreshes
the page i.e it re-calulates the procedure to populate global tEmp table.
For the connection pool the Oracle session is not closed but reused.

Thanx
Thanx
Will on commit delete rows work for Global Temp table in 3 tier.
Or should I abondon global temp table.
SO whats the alternative

          Followup:

it is a really good practice in sqlserver - they sort of invented the need to do
so.

me, I prefer just to run a single query personally.

you need to "on commit delete rows" and make sure the connect pool "commits"
before reusing the connection.

      Rating: 5
Thanx much Sir
      for all your help  August 21, 2003
Reviewer:
      A reader

August 22, 2003 Sagi -- Thanks for the question regarding "A Few
      Questions", version 8.1.7

      You Asked
Hi Tom.

I was looking into COALESCE. I checked the PCT_INCREASE parameter for the
tablespaces, as I understand that if its 0 (Zero) then SMON automatically
coalesce's it.

SQL> select tablespace_name, pct_increase from dba_tablespaces
  2  order by 1 ;

TABLESPACE_NAME                PCT_INCREASE
------------------------------ ------------
DRSYS                                    50
INDX                                      0
RBS                                      50
SYSTEM                                   50
TEMP                                      0
TOOLS                                     0
USERS                                     0

I issued the query against DBA_FREE_SPACE_COALESCED

SQL> SELECT tablespace_name tname, total_extents, EXTENTS_COALESCED,
PERCENT_EXTENTS_COALESCED FROM DBA_FREE_SPACE_COALESCED ;

TNAME TOTAL_EXTENTS EXTENTS_COALESCED PERCENT_EXTENTS_COALESCED
---------------------------------------------------------------
SYSTEM        7                 7                       100
RBS          13                13                       100
USERS      1525                11                .721311475
TEMP       2003                 4                .199700449
TOOLS        98                 1                1.02040816
INDX          1                 1                       100
DRSYS         2                 2                       100

I issued the below commands:

SQL> ALTER TABLESPACE USERS COALESCE ;
SQL> ALTER TABLESPACE TOOLS COALESCE ;

Now the Output is:

TNAME   TOTAL_EXTENTS   EXTENTS_COALESCED   PERCENT_EXTENTS_COALESCED
---------------------------------------------------------------------
SYSTEM         7                 7                       100
RBS           13                13                       100
USERS         11                11                       100
TEMP        2003                 4                .199700449
TOOLS          1                 1                       100
INDX           1                 1                       100
DRSYS          2                 2                       100

The question is I read somewhere saying that if the count in DBA_FREE_SPACE is
greater than the no. of datafiles, for a given tablespace then the tablespace is
fragmented.

Before I manually COALESCed the Tablespace for USERS and TOOLS the Total No. of
Extents were 1525 and 98. AFter I executed the "ALTER TABLESPACE ......
COALESCE' its now 11 and 1.

So I now expect that if I issue SELECT COUNT(*) FROM DBA_FREE_SPACE WHERE
TABLESPACE_NAME='USERS' it should return me a single row because I have only one
datafile. But still i get 11 rows.....

Here is the output......

  1  select * from dba_free_space
  2*  where tablespace_name='USERS'
SQL> /

TABLESPACE_NAME  FILE_ID   BLOCK_ID      BYTES     BLOCKS RELATIVE_FNO
------------------------------ ---------- ---------- ---------- ------USERS
         3         98     917504        112            3
USERS               3        258   17956864       2192            3
USERS               3      21042   15073280       1840            3
USERS               3          2     655360         80            3
USERS               3       2466   31981568       3904            3
USERS               3       6386   33685504       4112            3
USERS               3      17362   15728640       1920            3
USERS               3      22898   22142976       2703            3
USERS               3      16322    8388608       1024            3
USERS               3      10514   47448064       5792            3
USERS               3      19298   14155776       1728            3

So what is wrong or am i understanding wrong...

I would appreciate if you could explain and demonstrate with an example of
yours. Because they are simply the great.

Regards,
Sagi

      and we said...
"as I understand that if its 0 (Zero) then SMON automatically coalesce's it."

That is backwards. pctinc = 0, no auto coalescing.

Actually, I'm going to PASS on this -- as you should be using ONLY locally
managed tablespaces and they NEVER NEVER need coalescing so this entire
discussion would be *moot*

Review &
      Followup

      Rating:
      3
  October 16, 2002
Reviewer:  Sagi
       from India
Hi Tom !

Thanx for the reply. I got what you wanted to convey. But then i could not get
this:

"That is backwards. pctinc = 0, no auto coalescing."

What did you mean by this. When does SMON do auto coalescing? When the pctinc=0
or pctinc>0?

Thanx.

Regards,
Sagi

          Followup:

pctinc > 0

you said:

if its 0 (Zero) then SMON automatically
coalesce's it.

which is wrong, it would be "if it is NOT zero....."

      Rating: 5
For
      Sagi  October 16, 2002
Reviewer:  Connor
       from UK
The query to see what needs coalescing is:

select ...
from  fet$ f, ts$ t
where t.ts#=f.ts#
and   t.dflextpct!=0    <=== pctincrease
and   t.bitmapped=0

so when pctinc > 0 means coalesce.

But think about it a second...You make pctinc > 0 to attemp to minimise the
effects of fragmentation.  But what causes that fragmentation? Having pctinc >
0!!

pctinc should be 0 always.  Even an Oracle person once told us in a lecture that
"pctincrease was the wrong solution to a real problem"

      Rating:
      3
  October 28, 2002
Reviewer:  Sagi
       from India
Hi Tom,

I know we have to use LMT's from Oralce 8i. This is my TEST DATABASE. So please
ignore that i am using DMT.

I read many places and also you too suggest/recommend to use PCTINCREASE=0,
INITIAL=NEXT=<same size>. In this case there is no need to do COALESCE.

I was just curious and today executed the query:

SQL> select a.tablespace_name, a.file_id, a.block_id, a.blocks, b.block_id
  2  from dba_free_space a, dba_free_space b
  3  where a.tablespace_name=b.tablespace_name and
  4  a.file_id=b.file_id and
  5  a.block_id + a.blocks= b.block_id and
  6  A.tablespace_name != 'TEMP' and
  7  b.tablespace_name != 'TEMP'

TABLESPACE_NAME  FILE_ID   BLOCK_ID     BLOCKS   BLOCK_ID
----------------------------------------------------------
USERS               3      19298        128      19426

SQL> SELECT TABLESPACE_NAME, INITIAL_EXTENT,
   2 NEXT_EXTENT, PCT_INCREASE FROM DBA_TABLESPACES
   3 WHERE TABLESPACE_NAME='USERS'

TABLESPACE_NAME  INITIAL_EXTENT NEXT_EXTENT PCT_INCREASE
----------------------------------------------------------
USERS                  131072      131072            0

How come that with PCTINCREASE=0 and INITIAL and NEXT of same size, I get
COALESCE...

This is just for knowledge. Would appreciate your time and response.

Thanx in advance.

Regards,
Sagi

          Followup:

why do you believe you "get coalesce"

what do you mean by "i get coalesce"

      Rating:
      2
  October 28, 2002
Reviewer:  Sagi
       from India
Hi Tom,

Thanx for the reply.

What I meant was that though i have the PCTINCREASE=0 and equi (INITIAL=NEXT), i
have fragmentation. This is because the query returns me the output

SQL> select a.tablespace_name, a.file_id, a.block_id, a.blocks, b.block_id
  2  from dba_free_space a, dba_free_space b
  3  where a.tablespace_name=b.tablespace_name and
  4  a.file_id=b.file_id and
  5  a.block_id + a.blocks= b.block_id and
  6  A.tablespace_name != 'TEMP' and
  7  b.tablespace_name != 'TEMP'

When why should there be fragmentation???

Appreiciate and anticipating your response.

Thanx in advance.

Regards
Sagi

          Followup:

how in the world do you get "fragmentation" out of that result?

fragmentation occurrs when you have lots of free extents and none of them are
the same size.  Meaning you have 500meg total free space yet you cannot allocate
a next extent of 41.5 meg cause none of the fragements left can work for you.

All you've done is shown there is one free extent that is followed by another
free extent.  These extents NEED NOT be coalesced since initial=next and
pctincrease = 0 (meaning all extents should be the SAME SIZE -- so these two
free extents are equally useful cause they are the same size)

      Rating:
      4
  October 29, 2002
Reviewer:  Sagi
       from India
Hi Tom,

Thanx for the explanation. The fragmentation concept is clear now.

Thanx for your valuable time.

Regards,
Sagi

      Rating: 4
Clarification
      on coalescing  February 23, 2003
Reviewer:
      Dhamodaran  from India
Hi Tom,

Now I am clear in tablespace fragmentation.

But I want to make sure that Oracle auto coalesce adjuscent free extents only
when pctincrease is > zero?

I read this from Oracle Documentation :

"When a segment is dropped or truncated, a limited form of coalescing is
performed if the PCTINCREASE value for the segment is not zero. This is done
even if PCTINCREASE=0 for the tablespace containing the segment."
at this location:

http://gwynne.cs.ualberta.ca/~oracle/817doc/server.817/a76956/tspaces.htm#748
This makes me a conclusion, whatever the value of pctincrease, Oracle does auto
coalesce. Is it so?
How Oracle behaves in this scenario, if my DMT (non-equisized extents)is using
"PCTINCREASE is 0" and not able to allocate next extent because of
fragmentation. whether coalescing takesplace at this moment and allocate next
extent or Oracle will throw "unable to extend ..." error??

please correct i was wrong anywhere..

I appreciate your time.
Thanks a lot Tom,
Dhamodaran

          Followup:

if you use LMT's - you need not worry about this AT ALL.

if you insist upon using DMT's, leave pctincrease at zero (else you end up with
SWISS CHEESE in your tablespace where no two extents are the same size).  Issue
alter tablespace T coalesce if and when you need to.

If you never drop / truncate -- you never need to coalesce.

If you frequently drop / truncate -- these objects belong in their own "special"
tablespace, one that you can coalesce whenever you feel like.

      Rating:
      4
Thanks  February 24, 2003
Reviewer:
      Dhamodaran  from India
Thanks Tom,
Thanks for a quick reply, I got what you are trying to say.

Just to clarify, if the following is right?

If the tablespace pctincrease is set to non-zero, when drop/truncate of objects
happens auto coalescing takesplace by oracle immediately.
And if pctinc is 0, when dropping/truncating the objects
coalescing is not taken place immediately. But when oracle need to allocate new
extent, if oracle finds the new extent size is greater than the available free
contiguous blocks, that moment oracle does the coalescing and tries to allocate
the next extent.

Thanks a lot,
Dhamodaran

          Followup:

"auto coalescing" takes place soon -- not immediately.  smon does it.

      Rating:
      4
Thanks  February 25, 2003
Reviewer:
      Dhamodaran  from India
Tom,

I got your point, now I am clear.

Many Thanks,
Dhamodaran

      Rating: 3
How to get
      large extent  August 22, 2003
Reviewer:  Mohan
      K  from Bangalore, India
When I try to import some large tables gives the error unable to allocate
INITIAL extent. When I checked DBA_FREE_SPACE there is more than enough
freespace. So I ran the following query

SQL> select bytes from(select bytes, rownum rnum from dba_free_space order by
bytes desc) where rnum<25;

From the query I found the maximum extent size is much less than what is
required for the tables. The freespace is distributed in a number of extents. I
tried to coalesce the tablespace. But it does not work. How to form a single
large extent. Is export/Import the entire database is the only solution.

Mohan

          Followup:

time to learn about locally managed tablespaces and forget about free space
fragmentation.

here is what you can do:

a) create a new tablespace using locally managed extents.  let the system decide
the extent size if you like, or you can use uniform.  up to you.

b) alter table T move tablespace new_tablespace;
c) alter index I rebuild <tablespace .... if you want>;

do b and c for each table/set of indexes on table

d) drop old tablespace

and never look back.

August 22, 2003 Mark -- Thanks for the question regarding
      "rollback segments", version 8.0.5

      You Asked
What causes a rollback segment to shrink backdown to its optimal size?

We have 5 rollback segments (with optimal = 50 meg)4 of them have extended to
about 225 meg each.  They are taking up most of the space in our rollback
tablespace, we have one gig allocated to rollback.

We recently had a db crash, could that have something to do with them not
shrinking back to the optimal size?

Should we manually shrink them?

There are currently no long running transactions that might be using the 4 large
rollback segments.

Any info woud be appreciated.

      and we said...
This is a quote from the concepts guide

http://technet.oracle.com/doc/oracle8i_816/server.816/a76965/c02block.htm#4693
<quote>
When you create or alter a rollback segment, you can use the storage parameter
OPTIMAL (which applies only to rollback segments) to specify the optimal size of
the segment in bytes. If a transaction needs to continue writing rollback
information
from one extent to another extent in the rollback segment, Oracle compares the
current size of the rollback segment to the segment’s optimal size. If the
rollback segment is larger than its optimal size, and if the extents immediately
following the
extent just filled are inactive, then Oracle deallocates consecutive nonactive
extents from the rollback segment until the total size of the rollback segment
is equal to or
close to, but not less than, its optimal size. Oracle always frees the oldest
inactive extents, as these are the least likely to be used by consistent reads.
</quote>

So, the way to get them to "shrink" is to accidently have a transaction that
spans extents.

You cannot really control this (when this happens).  If you want to control it,
you'll need to shrink it manually.

I myself never use optimal.  My approach is to put a rollback segment in a
tablespace all by itself.  I have 1 datafile in this tablespace.  I let it
autoextend to some maxsize.  When I notice they have grown, I offline them, drop
them, resize the datafile back down, and recreate them.  I then try to figure
out WHY they had to grow.  I never want them to grow.  I thought I sized them to
be the right size in the first place.  ( i don't believe in letting RBS grow - I
always try to have sufficient RBS permanently allocated in the first place.  I
like lots of same sized RBS's)
Review &
      Followup

      Rating: 4
Rollback
      Segment doesn't shrink  August 22, 2003
Reviewer:
      Tony  from India
I've created a rollback segement of 1.5GB size. After a batch process, free
space left out in the rollback segement is 15MB only and it continues to be the
same (Rollback Segment doesn't shrink) even after running other processes. Is
there any performance implications because of this? Do I have to worry about
this behaviour?

          Followup:

it is normal, the rbs extends.

you can use optimal -- but I don't like it myself.

in 8i, i used to create a single RBS in a single tablespace.  so i'd have N
tablespaces (rbs_01_tbs, rbs_02_tbs, .... ) and N rbs's (rbs_01, rbs_02).  I
would let them autoextend when needed and periodically, if i wanted to "shrink"
one that grew abnormally due to a batch process, i would

a) drop the rbs
b) shrink the tablespaces single datafile
c) recreate the rbs

August 22, 2003 Ravi -- Thanks for the question regarding
      "Converting Dictionary Managed to LMT tablespace", version 816

      You Asked
Tom,

For example, after moving a dictionary managed tablespace to
locally managed autoallocate tablespace using dbms_space_admin
pack, the extent_management filed of dba_tablespaces will show
as USER.(rather than LOCAL).  Could you please explain me what
does that mean. My question is, will the extent size of the
objects after converting be
a) controlled by oracle automatically
b) or will be based on the storage charactersitcs of the objects
getting created

Thanks
Ravi

      and we said...
This support note covers this topic:

Article-ID:         <Note:120061.1>
Circulation:        PUBLISHED (EXTERNAL)
Folder:             server.Rdbms.DBA.Admin
Topic:              Managing Tablespaces
Title:              Next Extent Size After Migrating Tablespace
from Dictionary
                    to Locally Managed
Document-Type:      BULLETIN
Impact:             LOW
Skill-Level:        NOVICE
Server-Version:     08
Updated-Date:       18-OCT-2000 12:31:11
References:
Attachments:        NONE
Content-Type:       TEXT/PLAIN
Keywords:           DICTIONARY; LOCALLY; MANAGED; MIGRATE;
Products:           5/RDBMS (V8.1);
Platforms:          GENERIC;

PURPOSE
-------

To Explain why migrated tablespaces are not subject to the
UNIFORM/SYSTEM policy of newly created locally managed
tablespaces

SCOPE & APPLICATION
-------------------
An Understanding of the Architecture of Migrated Tablespaces

MIGRATED TABLESPACES:
-----------------------------
After migrating a dictionary managed tablespace to locally
managed tablespace you notice that the tables are still
extending with the  next extent specified even though it's now a
locally managed tablespace

Example:
==================================================
MIGRATE FROM DICTIONARY MANAGED TO LOCALLY MANAGED
==================================================
SQL> begin
  2  dbms_space_admin.tablespace_migrate_to_local
  3  (tablespace_name=> 'USERS', rfno=>5);
  4  end;
  5  /

PL/SQL procedure successfully completed.

===============================================
Verify that tablespace is now locally-managed:
===============================================

SQL> select TABLESPACE_NAME, EXTENT_MANAGEMENT
  2  FROM DBA_TABLESPACES
  3  where tablespace_name = 'USERS';

TABLESPACE_NAME                EXTENT_MAN
------------------------------ ----------
USERS                          LOCAL

=================================================
CREATE TABLE with the storage segments;
=================================================

SQL> create table     DW_GSO_COLLN_NEW
  2  tablespace USERS
  3  storage (initial 500M next 500M pctincrease 0)
  4  as select * from cdwadm.dw_gso_colln
  5  where rownum <    10000000;
Table created.

SQL> select segment_name,extent_id,bytes
  2  from     dba_extents
  3  where segment_name='DW_GSO_COLLN_NEW';
SEGMENT_NAME                   EXTENT_ID   BYTES
------------------------------ ----------  ----------
DW_GSO_COLLN_NEW                        0  524288000
DW_GSO_COLLN_NEW                        1  524288000
DW_GSO_COLLN_NEW                        2  524288000

QUESTION:

 Why is the table extending with the next extent that is
 specified even though it's locally managed tablespace.

ANSWER:

Migrated tablespaces are not subject to the UNIFORM/SYSTEM
policy of newly created locally managed tablespaces. This would
be too difficult to implement, since the tablespace is likely to
contain the existing objects which already violate  new policy.
For this reason, migrated tablespace only support the same
allocation policy as the dictionary tablespaces, i.e. we do
whatever the user tells us to do. If you select from
DBA_TABLESPACES, you should see "USER" value in the
ALLOCATION_TYPE column for migrated tablespaces, and UNIFORM or
SYSTEM value for tablespaces which were created as locally
managed.

Therefore, the user will not get the policy benefits from
migration, but can still get performance benefits - no ST
enqueue contention and more efficient extent operations.

SQL> select TABLESPACE_NAME,
    EXTENT_MANAGEMENT, ALLOCATION_TYPE,PLUGGED_IN
     FROM DBA_TABLESPACES  3  ;

TABLESPACE_NAME          EXTENT_MAN ALLOCATIO PLU
------------------------ ---------- --------- ---
SYSTEM                   DICTIONARY USER      NO
RBS                      DICTIONARY USER      NO
TEMP                     DICTIONARY USER      NO
TOOLS                    DICTIONARY USER      NO
USERS                    LOCAL      USER      NO <--MIGRATED
IDX                      DICTIONARY USER      NO
MRFACTDATA               LOCAL      USER      NO <--MIGRATED
LOCAL_AUTO               LOCAL      SYSTEM    NO <--NOT MIGRATED
LOCALLY_MANAGED          LOCAL      UNIFORM   NO <--NOT MIGRATED

9 rows selected.

Review &
      Followup

      Rating: 4
Migrating
      ditionary to LMT  April 09, 2002
Reviewer:
      aries  from Philippines
Its good! Im already enjoying the benefits of LMT (uniform sizes), but for newly
created tablespaces.

My follow up question is how can i really migrate tablespace and make full use
of the benefits of LMT (ie. uniform sizes)? Do i have to do table import/export
and recreate all my tablespaces as LMT?

          Followup:

While you can migrate a DMT to LMT -- I would strongly advise against doing so.
you lose the main benefit - that of uniform extents.

You can create a new LMT and then use use alter table move/alter index rebuild
to move the objects into it.  See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:47812348053
for examples.

      Rating: 3
Uniform
      extents  October 09, 2002
Reviewer:  H
       from USA
Well, if we have initial=next and pctincrease=0 in DMT and use dbms_space_admin
to migrate to LMT, then we can get uniform extents that is get the full benefit
of LMT. What are your thoughts?

Thanks.

          Followup:

Not really, a migrated tablespace won't have the bitmaps at the "front" like a
natural one would -- and the extents probably aren't all exactly the same, the
algorithm allows for some rounding and different sized extents in a DMT even
with intial=next and pctincrease=0

      Rating:
      4
LMT  October 14, 2002
Reviewer:  H
       from USA
Hi Tom,
I am having a discussion with a collegue, and he thinks that although we dont
get the benefits of uniform extents there are still performance gains when we
migrate using the procedure from DMT to LMT.

Please explain what do you think?

thanks.

          Followup:

you get the performance when adding additional extents.

You get none of the other niceness, which is one of the major reasons to use
LMTS.  No more fragementation, nice, orderly, no muss, no fuss.  No stupid
pctincreases!

      Rating: 5
Migrating
      production database...  January 08, 2003
Reviewer:
      Senthil  from Boston, MA
Tom,

I understand we can use dbms_space_admin package to migrate from dmt to lmt. We
are in the process of moving on from 8i to 9i. Our application uses various
tablespace names in it effectively while creating tables and indexes, and the
database size can go up to 120GB. What would be the best method to migrate, or
what do you suggest the safest way?

Please advise.

thanks,
senthil

          Followup:

i would create new tablespaces and using

alter table T move tablespace NEW_TBS;
alter index I rebuild tablespace NEW_TBS;

move the objects at my leisure from DMT to LMT.

I don't like the migration of a dmt to lmt.  it is not as clean.

      Rating: 5
Has my question
      reached you???  January 11, 2003
Reviewer:
      Senthil  from Boston, MA, USA
Tom,

Did i put my question in the wrong place? Or should i have to fill up few more
details.

thanks indeed.

          Followup:

i look at 99% of the reviews, sometimes I miss one or two -- it is not really a
place for an entirely new question here...

      Rating:
      5
  August 20, 2003
Reviewer:  Mg
       from Germany
Hi Tom,

I have export(full), from Oracle 8i. Then I try to import the same dump file to
oracle 9i. Then I get following error :

ORA-12913 : Cannot create dictionary managed tablespace

So could you please tell me how can I overcome this problem.

Thanks

          Followup:

precreate your tablespaces.

you have a locally managed system tablespace (this is good!)

you cannot have any DMT's when you have this (this too is good!)

just create the necessary tablespaces and import away.

      Rating:
      5
  August 20, 2003
Reviewer:  MG
       from Germany
Hi Tom,

I like to add additional on above question.

What I want :
 I have oracle 8i database. I need to take tablespaces, users and other objects
from oracle 8i to oracle 9i.

problems:
 (1). In the new database (ora 9i ), I need different tablespace name, and the
datafile's structure also the different. Cannot create dictionary managed
tablespace
 (2). I cannot connect both instance from one pc.

Could you please tell me how Can I do this in easiest way?

Thanks

          Followup:

I don't understand (1).  why you "need" a different tablespace name.  You can
easily precreate the tablespaces using whatever datafiles you want and import.

as for (2), you haven't set something up right is all I can say.  similar to me
stating to you "my car won't start -- why and how do i fix it".  no information
supplied to give you a clue as to what is wrong -- no error messages, no error
codes....

      Rating:
      5
  August 22, 2003
Reviewer:  MG
       from Germany
Hi Tom,

(1). Actually Tom, my office needs different tablespace name(according to their
new policies..). So thats why different ts should given.
(2). Current Environment:
  * In Ora8i has different ts name along different data file paths.
  * So I needs to recreate a all the objects(tables/indexes etc) in ora8i to ora
9i (with above desired ts name, datafile path changes).
  * So 1st I have exp from ora8i and then try to imp from 9i. It fails:
     "Due to missing paths, as in ora8i (now in 9i machine it is not existing)"

  * 2nd I have created same structure(dir struc) ans 8i, and then try to import.
Then I got an error

   ORA-12913 : Cannot create dictionary managed tablespace
  * Then I have seen in your site, once told, we have set the system tablespace
to dictionary, before we imp dictionary managed objects.
 But as this is not so efficient, I wants to imp them into local managed
tablespace, But I don't know how? Could you please tell how to do this?

          Followup:

you'll have to precreate the objects using ddl scripts then.

imp won't map a set of tablespaces in the dmp file to a different set of
tablespace names in the target database.

you will precreate your tablespaces.
you will precreate your tables.
you will then import tables only
you will then create by hand all of your indexes in the right place
and finally you can import the rest of the stuff -- constraints, grants, etc.

lots of work for a tablespace nameing policy.

August 22, 2003 Sajid -- Thanks for the question regarding
      "CURSOR SHARING", version 8.1.7

      You Asked
Hi Tom,

I read your answer about CURSOR SHARING recently. I found one question recently
from one readers
SELECT substr("abcdef",2,4) FROM DUAL;

You said that the reason is CURSOR SHARING. But i am still not clear. Could you
please elaborate it? How CURSOR SHARING comes into picture in the case?

Thanks in advance,

      and we said...
Cursor sharing = force will turn:

select substr( 'abcdef', 2, 4 ) from dual

into:

select substr( :bv1, :bv2, :bv3 ) from dual;

it'll replace ALL constants with bind variables.  Hence, we no longer know that
the max length is 4 characters, it could be much larger (or smaller)

Review &
      Followup

      Rating: 4
still
      doubt  July 02, 2002
Reviewer:  Sajid Anwar
       from London
Hi Tom,

As you said,

select substr( 'abcdef', 2, 4 ) from dual

into:

select substr( :bv1, :bv2, :bv3 ) from dual;

it'll replace ALL constants with bind variables.  Hence, we no longer know that
the max length is 4 characters, it could be much larger (or smaller)

My doubt is :
We have all the three values for the bind variables here like :bv1 = 'abcdef'
     :bv2 = 2
     :bv3 = 4

How come we no longer know that max length? Please clarify.

Thanks,

          Followup:

If you give the sql engine:

select substr( :bv1, :bv2, :bv3 ) from dual;

and ask it to parse it and then describe it -- what does it know?  I know that
you and I know it is 4, but what does the SOFTWARE know (it doesn't know
anymore, it is a generic reusable sql statment -- that is the purpose of cursor
sharing).

Just because bv3 is 4 for you doesn't mean that bv3 = 4 for everyone.  Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter system flush shared_pool;
System altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select sql_text from v$sql where
upper(sql_text) like '%TKYTE_DEMO%';

no rows selected

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set cursor_sharing=force;
Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select substr( user, 1, 20 ) from dual
tkyte_demo;

SUBSTR(USER,1,20)
------------------------------
OPS$TKYTE

ops$tkyte@ORA817DEV.US.ORACLE.COM> select substr( user, 1, 10 ) from dual
tkyte_demo;

SUBSTR(USER,1,10)
------------------------------
OPS$TKYTE

ops$tkyte@ORA817DEV.US.ORACLE.COM> select substr( user, 1, 5 ) from dual
tkyte_demo;

SUBSTR(USER,1,5)
------------------------------
OPS$T

ops$tkyte@ORA817DEV.US.ORACLE.COM> select substr( user, 1, 1 ) from dual
tkyte_demo;

SUBSTR(USER,1,1)
------------------------------
O

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select sql_text from v$sql where
upper(sql_text) like '%TKYTE_DEMO%';

SQL_TEXT
---------------------------------------------------------------------------------
--------------------------------------------------
select substr( user, :"SYS_B_0", :"SYS_B_1" ) from dual tkyte_demo

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

So tell me -- what value of :"SYS_B_1" should have been used?? 20, 10, 5 or 1??

      Rating: 2
Cursor sharing
      with statements containing both constants and bind
      variables  August 22, 2003
Reviewer:  Steve
       from UK
Tom
I heard a rumour that cursor sharing did not convert constants to variables when
the statement contained both.  Is this true?

          Followup:

ops$tkyte@ORA920LAP> variable x number
ops$tkyte@ORA920LAP> exec :x := 5;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> create table emp as select * from scott.emp;

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> alter session set cursor_sharing = force;

Session altered.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select * from emp where empno = :x and ename = 'x';

no rows selected

ops$tkyte@ORA920LAP> select * from emp where empno = 5 and ename = 'x';

no rows selected

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select sql_text from v$sql where sql_text like 'select *
from emp where empno = %';

SQL_TEXT
---------------------------------------------------------------------------------
--------------------------------------------------
select * from emp where empno = :"SYS_B_0" and ename = :"SYS_B_1"
select * from emp where empno = :x and ename = :"SYS_B_0"

ops$tkyte@ORA920LAP>

auto-bound both of them...

August 22, 2003 Cefers.br -- Thanks for the question regarding
      "Two sessions signaling the same alert", version 9.2

      You Asked
Hi Tom,

I have the following scenario (on Oracle 9i):

The SOURCE_APP application inserts records into TABLE_1 that has a before insert
trigger (see body below) that sends a signal (using DBMS_ALERT.SIGNAL) to
ALERT_1. The TARGET_APP application is interested on this alert, so it is
waiting for signals.

>>> Begining of Trigger Body <<<
  BEGIN
    DBMS_ALERT.SIGNAL('ALERT_1', '');
  END;
>>> End of Trigger Body <<<

There are lots of users connected on SOURCE_APP and TARGET_APP.

The problem is that SOURCE_APP can insert until 10 records into TABLE_1 before
COMMIT. This way, the first user that logs into SOURCE_APP and inserts a record
(sending the ALERT_1 signal) will lock other concurrent users on SOURCE_APP,
util he commits his job.

I read DBMS_ALERT description and I know this is the excpected behavior between
concurrent sessions signaling the same alert.

My question is: How can I implement this communication channel between
SOURCE_APP and TARGET_APP? What is the best approach?

DBMS_PIPE doesn´t seem to fit here, once all users logged in TARGET_APP must be
alerted when a record is inserted in TABLE_1.

Thanks.

      and we said...
make sure job queue processes are set to something non zero.

then make your trigger be:

declare
   l_job number;
begin
   dbms_job.submit( l_job, 'dbms_alert.signal( ''alert_1'', null );' );
end;
/

now, the alert will be signaled SHORTLY after you commit and any and all
serialization will take place in the job queues -- in the background -- not in
your client applications.

alternatively, use advanced queues -- AQ -- to do notification.

Review &
      Followup

      Rating:
      5
Nice!  August 22, 2003
Reviewer:
      Cefers.br  from Brasil
Great Tom,

Your solution worked perfectly.

Thanks a lot.

      Rating:
      5
Great  August 22, 2003
Reviewer:
      A reader
Could you or the poser of the question kindly explain
why there would be a serialization here in the first
place? How can inserts cause serialization and locking?

          Followup:

when you dbms_alert.signal( 'X' ......)

no one else can signal 'X' until you  commit or rollback.  hence, you serialize.

      Rating: 5
Thank you
      Tom!!!  August 22, 2003
Reviewer:
      A reader
that explains it!!

August 23, 2003 sj -- Thanks for the question regarding "dropping
      and adding partitions periodically", version 8.1.7

      You Asked
Tom

We have a procedure p which drops a partition and adds a partition to a
partitioned table t every day, and then the current days data from table t1 is
inserted into t( we are not doing exchange partitions due to some business
conditions, the table t is being inserted records via a trigger).

Now the criteria for dropping the partitions is sysdate-30. That is every day we
decide which partition to drop by doing a sysdate-30 and then concatanating it
to the partition name prefix ex: part_vk_07_13_01.

So every day a partition is created ex:part_vk_08_13_01 is created and a
partition which is sysdate-30 ex: part_vk_07_13_01 is dropped.

Now what we have over looked while writing the pl/ql process is the Saturday's
and Sunday's. This process is not run on weekends. This will not create any
problem for creating new partition , but for dropping partitions, it becomes an
issue. Becasue from what I guess( I guess because Iam not able to understand it
totally)when you do a sysdate-30, there might be no partitions at all for that
day , as that day might be a saturday or sunday.  But I can handle the resulting
errors by using a local pl/sql block with  the procedure, and doing a when
others than   null;, this will see that the procedure executes successfully.

But Iam really concerned, whether some of the partitions for some days will be
left undeleted. How should I handle this. the whole point is there shouldnt be
any partions which are one month old and left undeleted. Is there a chance that
this might happen , with my logic of doing sysdate-30 for determining the
partion name to delete.

Guide me, in providing a bugless solution.

Secondly, there is a proposal that we should not have one month, but virtually
3o days historical data. In that case I cannot do a sysdate-30 to derive the
name of the partition drop, as there will be no partitions for sat'day and
sun'day, and no partition will be dropped if sysdate-30 happens to be one of
them.

or is there an easier to solution to arrive at the partition to drop
as per the criteria specified.

I dont know if I was clear enough.

      and we said...
I would name the partitions like this:

part_vk_YYYYMMDD

so they are sortable in an ASCII sense (mm_dd_yy is not sortable properly).

then, look for the OLDEST partitions you want to delete:

select partition_name
  from user_tab_partitions t1
 where table_name = 'T'
   and 30 <= ( select count(*)
                 from user_tab_partitions t2
                where table_name = 'T'
                  and t1.partition_name < t2.partition_name )

These are the partitions you want to drop then (the set of partitions such that
there are 30 or more partitions created LATER then it)

followup to comment

No substr needed nor intended.

If the partition name is as I say it should be:

part_vk_YYYYMMDD

it can remain a string, it'll sort properly in all cases as a string.

followup to comment

First, a single day will not have 2 partitions -- my partition name is:

part_vk_YYYYMMDD

(the problem statement is that there is a partition by day).  So, no "two in a
day" problem here.

Second, what my query does is to find the set of partitions for a table such
that there are 30 or MORE partitions existing that are greater then it.  Eg, if
I have partitions:

part_vk_20010101
part_vk_20010102
....
part_vk_20010131

there are 0 partitions greater than part_vk_20010131,
there is 1 partition greater than part_vk_20010130
there is 2 partitions greater than part_vk_20010129
...
there is 30 partitions greater then part_vk_20010101 <<--- this one will be
returned.

The subquery just finds how many partitions exist that are greater then the a
given partition and keeps rows where there are 30 or more partitions (the ones
we want to delete)

followup to comment about "what if there are two partitions/day"

Simply make the partition names:

part_vk_YYYYMMDDHH24MISS

Add the time to the date in a fashion that sorts properly.  Then you are done.
You might have to twiddle with the 30 (eg: make it 60 or something) depending on
whether you want the last 30 partitions or the last 30 days.

followup to comment 7

The goal:

find the OLDEST partitions by date.

The assumpation -- the date is part of the partition name.
We control the format of the partition name.

A fact: if you take a DATE and format it using:

YYYYMMDDHH24MISS

and sort it as a string -- it will SORT correctly (by date), eg:

20010812012432  (1:24:32 on August 12'th 2001) will sort before:
20010812113501 (11:35:01 on the same day)

By simply incorporating the TIME into the partition name in a manner that SORTS
correctly, when represented as a string, the same exact query above finds the
proper answer.

Followup to comment 10 "would doing a substr be more costly"

Not measurably so.  Connors point was "Tom is using a known format, a format he
carefully picked to sort all by itself -- if you use a format that includes the
DATE but it not naturally sortable -- you can use SUBSTR to pick off the DATE,
to_date to convert it and order by that".

With the trivial amount of data we are talking about in this example, it'll be
just as fast using EITHER method.

followup to comment 9 and 10 "how about an example"

Good idea.  In order to conserve space, I'll change the assumations.  Here I
want to keep the last 5 days online (not 30).  My partition name is
part_vk_YYYYMMDD.  I have an extra partition called "the_rest" I will constantly
split to add new days.

ops$tkyte@ORA817.US.ORACLE.COM> create TABLE partitioned
  2  ( timestamp date,
  3    id        int
  4  )
  5  PARTITION BY RANGE (timestamp)
  6  (
  7  PARTITION part_vk_20010801 VALUES LESS THAN
  8          ( to_date('02-Aug-2001','dd-mon-yyyy') ) ,
  9  PARTITION part_vk_20010802 VALUES LESS THAN
 10          ( to_date('03-Aug-2001','dd-mon-yyyy') ) ,
 11  PARTITION part_vk_20010803 VALUES LESS THAN
 12          ( to_date('04-Aug-2001','dd-mon-yyyy') ) ,
 13  PARTITION part_vk_20010804 VALUES LESS THAN
 14          ( to_date('05-Aug-2001','dd-mon-yyyy') ) ,
 15  PARTITION part_vk_20010805 VALUES LESS THAN
 16          ( to_date('06-Aug-2001','dd-mon-yyyy') ) ,
 17  PARTITION the_rest VALUES LESS THAN ( maxvalue )
 18  )
 19  /

Table created.

ops$tkyte@ORA817.US.ORACLE.COM> select partition_name
  2    from user_tab_partitions t1
  3   where table_name = 'PARTITIONED'
  4     and partition_name <> 'THE_REST'
  5     and 5 <=  ( select count(*)
  6                   from user_tab_partitions t2
  7                  where t2.table_name = 'PARTITIONED'
  8                    and t2.partition_name <> 'THE_REST'
  9                    and t1.partition_name < t2.partition_name )
 10  /

no rows selected

ops$tkyte@ORA817.US.ORACLE.COM> REM Ok, lets set up for Aug 6 data:

ops$tkyte@ORA817.US.ORACLE.COM> alter table partitioned
  2  split partition the_rest
  3  at ( to_date( '07-aug-2001', 'dd-mon-yyyy' ) )
  4  into ( partition part_vk_20010806, partition the_rest )
  5  /

Table altered.

ops$tkyte@ORA817.US.ORACLE.COM> select partition_name
  2    from user_tab_partitions t1
  3   where table_name = 'PARTITIONED'
  4     and partition_name <> 'THE_REST'
  5     and 5 <=  ( select count(*)
  6                   from user_tab_partitions t2
  7                  where t2.table_name = 'PARTITIONED'
  8                    and t2.partition_name <> 'THE_REST'
  9                    and t1.partition_name < t2.partition_name )
 10  /

PARTITION_NAME
------------------------------
PART_VK_20010801

and there it is -- the oldest partition in there (the one with the oldest
data, the one we want to drop).  This all counts on the partition name having
the date in that nice sortable order in it

REM Now, just to see that if we had more then 1 partition we need to drop,
REM this still works, we'll split again without dropping:

REM Ok, lets set up for Aug 7 data:

ops$tkyte@ORA817.US.ORACLE.COM> alter table partitioned
  2  split partition the_rest
  3  at ( to_date( '08-aug-2001', 'dd-mon-yyyy' ) )
  4  into ( partition part_vk_20010807, partition the_rest )
  5  /

Table altered.

ops$tkyte@ORA817.US.ORACLE.COM> select partition_name
  2    from user_tab_partitions t1
  3   where table_name = 'PARTITIONED'
  4     and partition_name <> 'THE_REST'
  5     and 5 <=  ( select count(*)
  6                   from user_tab_partitions t2
  7                  where t2.table_name = 'PARTITIONED'
  8                    and t2.partition_name <> 'THE_REST'
  9                    and t1.partition_name < t2.partition_name )
 10  /

PARTITION_NAME
------------------------------
PART_VK_20010801
PART_VK_20010802

and now we get the TWO oldest partitions we need to drop, we drop them:

ops$tkyte@ORA817.US.ORACLE.COM> alter table partitioned drop partition
part_vk_20010801;

Table altered.

ops$tkyte@ORA817.US.ORACLE.COM> alter table partitioned drop partition
part_vk_20010802;

Table altered.

REM and now that the oldest are gone, we get no more rows:

ops$tkyte@ORA817.US.ORACLE.COM> select partition_name
  2    from user_tab_partitions t1
  3   where table_name = 'PARTITIONED'
  4     and partition_name <> 'THE_REST'
  5     and 5 <=  ( select count(*)
  6                   from user_tab_partitions t2
  7                  where t2.table_name = 'PARTITIONED'
  8                    and t2.partition_name <> 'THE_REST'
  9                    and t1.partition_name < t2.partition_name )
 10  /

no rows selected

followup to comment 11 "does the figure 30..."

My query, given partition names of the format

part_vk_YYYYMMDD

will return ALL partition names for partitions such that there are 30 or more
partitions existing that are NEWER.

If you have 30 partitions exactly, this query will return zero records.  If you
have 31 partitions, this query will return 1 record and the partition name will
have the date of the OLDEST partition.  If you have 32 partitions, this query
will return 2 records and the partition names will be of the two OLDEST
partitions.  And so on.

followup to comment 12 "in my opinion"

opinion has nothing to do with it.

It is a FACT that if you take a DATE and store it as a STRING using the MASK:

YYYYMMDDHH24MISS

That will sort correctly (as a date would have sorted).  We are storing a FIXED
WIDTH string.  All strings start with the YEAR.  Any string that starts with
2000 for example is less then the strings that start with 2001 and greater than
a string that starts with 1999.  So the YEAR part sorts right, then onto the
MONTH part.  01 is less then 06 is less then 12.  That part sorts right and so
on.

This WORKS.  It is a very common technique.  It is factually accurate.  Try it
yourself.  I thought the above example:
...
A fact: if you take a DATE and format it using:

YYYYMMDDHH24MISS

and sort it as a string -- it will SORT correctly (by date), eg:

20010812012432  (1:24:32 on August 12'th 2001) will sort before:
20010812113501 (11:35:01 on the same day)
....
made it clear.

sigh, followup to comment 13.... "i doubt it"

Ok, I'm running out of ways to say it.

Do you believe me that 'a' < 'b', that if we sort two rows with 'a' in one row
and 'b' in another, 'a' will come out before 'b'?

Going further, is '0' < '1' and '1' < '2' (on upto '9').  If you believe that,
then going a step further it is understandable that

'19' < '20'

since '1' < '2' (and strings are compared from left to right, character by
character).  So, if '19' < '20', then it is understandable that

'1999' < '2000'

for the same reason -- hence:

'19991231' < '20000101'  (dec 31, 1999 is LESS THAN jan 1, 2000) and so on.

By putting the YEAR and than the MONTH and than the DAY (and than the hour using
a 24 hour clock and than the minute and than the second) we can put a DATE into
a string that sorts in the same fashion the date would have sorted.
Review &
      Followup

      Rating:
      4
  August 14, 2001
Reviewer:
      A reader
Tom

I guess you have to substr the partition name to get the date only from your
query.

      Rating:
      4
question  August 14, 2001
Reviewer:
      bobjee  from IND
can you explain this corelated subquery step wise. Never seen anything like this
for this kind of a situation. What if you have multiple partitions for a single
day i.e. if a single day has say 2 partitions, and you have to drop both of
them. How will your query handle it.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
I.select partition_name
  from user_tab_partitions t1
 where table_name = 'T'
 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
II.and 30 <= ( select count(*)
                 from user_tab_partitions t2
                where table_name = 'T'
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

III. and t1.partition_name < t2.partition_name )
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

can you explain these 3 steps, especially II, and III.

      Rating:
      4
  August 14, 2001
Reviewer:
      A reader
How will you handle the same problem, if each day has 2 partitions attached to
it. It is not a part of the question, but what if it is so..

      Rating: 4
One day
      offset  August 14, 2001
Reviewer:  Still
      learning  from usa
Hi,
I think there is one day offset based on the order of the procedure---- delete
the old ones before create a new one vs
delete the old ones after create a new one, that is 30 or 29 issue---- one day
offset? Correct me if i'm wrong(the original question is create before delete of
course).

Thanks

      Rating: 4
Dropping oldest
      partition...  August 15, 2001
Reviewer:
      Connor  from UK
One easy way of identifying the "oldest" partition is that it is always has a
partition_id of 1 (or is zero?). Either way, the partition id's are renamed to
keep this in sync when partitions are split/added/dropped etc

      Rating:
      3
  August 15, 2001
Reviewer:
      A reader
How will you handle the same problem, if each day has 2 partitions attached to
it. It is not a part of the question, but what if it is so..

      Rating:
      4
  August 15, 2001
Reviewer:
      A reader
Tom this is not clear
'
Simply make the partition names:

part_vk_YYYYMMDDHH24MISS
'

Can you show it to us. How will it sort as per the date, it has to be a date
column to sort according to the date.

This is a bit confusing

      Rating: 5
I'm not sure
      what is "not clear"  August 15, 2001
Reviewer:
      Connor  from UK
Basically what Tom is suggesting is naming scheme where the name contains
information that is important to you for partition maintenance.  If you want to
yield date type information, then you store date-type info in the partition
name.  By having it year-first/then-month/then-day etc, then you can sort in
"date order".  If you did not like that naming convention, you could substr out
the date part of the name and sort on to_date() of that.

      Rating: 4
without seeing
      the structure of table t it is difficult  August 15, 2001

Reviewer:  Berry  from USA

Instead of giving just the query , can you create the tables with multiple
partitions for different days, and show us how you will finding these group of
partitions for the oldest day and dropping them.

      Rating:
      4
request  August 15, 2001
Reviewer:
      Tommy Waldrop  from USA
Connor

Woudnt doing a substr be more costly that doing it in just sql.

I repeat what berry requested.

      Rating:
      3
Jerry  August 15, 2001
Reviewer:
      Jerry  from USA
Does the figure 30 include the max value partition.
iam not sure and confident that this will return exactly the partitions needed
as per the  question asked.

      Rating:
      2
  August 15, 2001
Reviewer:
      A reader
In my opinion only a date column can sort like that, not a varchar2 column

      Rating: 2
I doubt it, is
      just like others  August 15, 2001
Reviewer:
      Behen  from Cananda
Tom

We have always done a substr of the string , and converted it into date, and
then carried out the sorting.

We have never done this

Can you prove that this works.

      Rating:
      5
  August 15, 2001
Reviewer:  mark
       from ca
this sort method has become very popular since y2k efforts required the need for
the yyyy...i would like to see someone prove that it doesn't work...tom...great
site!

      Rating:
      5
Perfect  August 15, 2001
Reviewer:
      Still learning  from usa
Great interactive discussion and great site.

However, may i suggest adding sequence number to all the follow up comment
please? Because it took me several times to fugure out where "comment 12" is(we
have so many comment on this topic).

Tom, great answer!

Thanks

      Rating:
      5
Perfect  August 15, 2001
Reviewer:
      Still learning  from usa
Great interactive discussion and great site.

However, may i suggest adding sequence number to all the follow up comment
please? Because it took me several times to fugure out where "comment 12" is(we
have so many comment on this topic).

Tom, great answer!

Thanks

      Rating:
      3
  August 16, 2001
Reviewer:  AB
       from UK
I cannot believe so many "Oracle developers" are actually questioning the
sorting of YYYYMMDD and wasting Tom's time. It has always been a standard way of
sorting dates as strings for years, and not just in Oracle. On the question of
getting rid of the oldest partitions, how about having a parameter of partitions
to retain, determining whether there are any to drop and then dropping them as
follows in pseudo-code:- (while max(partition_position) > partitions_to_retain
drop the oldest partition off the bottom where partition_position = 1).......

      Rating: 5
Great Answer,
      Greater site  August 16, 2001
Reviewer:  Wayne
      Phillips  from South Africa (Cape Town)
I have been to your site once and I'm hooked for life.
Keep it up Tom, your'e explinations are thorough and methodical. Better than the
"brush-offs" you get by most "Guru's" of your stature on the Internet.
Sorry but I have been giving your URL to all my developer friends when I am
unable to assist them. I hope I haven't added to your woes but "when your'e
good, your'e GOOD"
Thanks from Cape Town, South Africa

      Rating: 5
dropping and
      adding partitions periodically  August 16, 2003

Reviewer:  AD  from UK
Table A is truncated and then populated on a monthly basis by a user. The idea
is to create a partitioned table B which will be populated on a monthly basis
from table A. I want to create a process that will create a partition each month
before inserting the data from table A into table B( I am not planning on to do
exchange partition since the data on table A is still required to be there until
the next month). There is no archiving requirement but the history table (table
B) is required to retain data for 36 months.

My question is, should I create a package to achieve this or just few sql
scripts to do each step like creating an empty partition, insert (direct load
)data into the partitioned table and drop the oldest partition if required
etc... Could you please suggest me what should be the best way to tackle this.

Thanks in advance for your time.

          Followup:

I would create a stored procedure to do this personally.

same as a script, easier to manage and run.

      Rating: 3
no. of
      partitions  August 16, 2003
Reviewer:  jack
       from Columbus, OH USA
Tom

Are there are any limitations on number of partitions that can be created on a
Table?
Also, pl. explain the disadvantages/advantages of having serveral partitions (eg
: > 1000 or so ) on a particular table.

          Followup:

documentation says....

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96536/ch44.htm#288033
the only real disadvantage -- if you need that many -- is the recursive sql
overhead of getting that into the dictionary cache (warm up the cache after
startup)...

      Rating: 3
response for
      no.of partitions  August 19, 2003
Reviewer:
      Jack  from Columbus, OH USA
Tom,
When you get a chance, pl.reply to the above question

thanks
Jack

      Rating: 5
dynamic
      paritioning  August 22, 2003
Reviewer:
      A reader
Our requirement is that we dynamically add partition
during a transaction of a job if required. Does this make
sense? My fear is that this compromises transaction integrity (due to impicit
ddl commit).
What are your opinion and how would you deal with this?

Thanx!

          Followup:

use an autonomous transaction and it will not affect your current transaction.

one of the FEW valid uses of an autonomous transaction perhaps if you truly must
do this dynamically.

August 23, 2003 -- Thanks for the question regarding "To trace
      sql statements in a trigger", version 8.1.7

      You Asked
Tom,

  I couldn't find other Q&A related to this, so I have to post it. Hope you can
help me to solve this.

  I would like to search for inefficient query in a trigger in a production
server. I think I have two choices to do this:
  1. use tkprof
  2. use set autotrace on and manually examine one by one

  I couldn't use method 1 b'cos we(developer) don't have any access to the
client's server to read the tkprof report.

  Other than method 2, is there any other method to do that?

  Thks.

Rgds,
Jesse

      and we said...
Then the client is quite simply preventing you from doing the job they hired you
to do???

Here is a solution that I'm proposing in my next book I'm working on, I'll let
you peek at it.  This is the 817 way using global temp tables -- there is an
easier way using pipelined functions in 9i.  We'll give client sessions --
regardless of platform or access to the database servers filesystem -- access to
THEIR trace files via sqlplus.  you'll set up a user:

create user trace_files identified by trace_files default tablespace users quota
unlimited on users;

grant create any directory, /* to read user dump dest */
      create session ,  /* to log on in the first place */
      create table ,    /* used to hold users -> trace files */
      create view ,     /* used so users can see what traces they have */
      create procedure , /* to create the function that returns the trace data
*/
      create trigger ,  /* to capture trace file names upon logoff */
      administer database trigger /* to create the logoff trigger */
to trace_files;

/* these are needed to find the trace file name */
grant select on v_$process to trace_files;
grant select on v_$session to trace_files;
grant select on v_$instance to trace_files;

in that user, you'll execute:

create view session_trace_file_name
as
select d.instance_name || '_ora_' || ltrim(to_char(a.spid)) || '.trc' filename
  from v$process a, v$session b, v$instance d
 where a.addr = b.paddr
   and b.audsid = userenv('sessionid')
/

create table avail_trace_files
( username varchar2(30) default user,
  filename varchar2(512),
  dt       date default sysdate,
  constraint avail_trace_files_pk primary key(username,filename)
)
organization index
/

create view user_avail_trace_files
as
select * from avail_trace_files where username = user;

grant select on user_avail_trace_files to public;

create global temporary table trace_file_text
( id number primary key,
  text varchar(4000)
)
/
grant select on trace_file_text to public
/

create or replace directory UDUMP_DIR
as '/export/home/ora817/admin/ora817dev/udump'
/
note -- that is MY user dump dest, use the right one for you of course

create or replace trigger capture_trace_files
before logoff on database
begin
    for x in ( select * from session_trace_file_name )
    loop
        if ( dbms_lob.fileexists( bfilename('UDUMP_DIR', x.filename ) ) = 1 )
        then
            insert into avail_trace_files (filename) values (x.filename);
        end if;
    end loop;
end;
/

create or replace procedure trace_file_contents( p_filename in varchar2 )
as
    l_bfile                  bfile := bfilename('UDUMP_DIR',p_filename);
    l_last                   number := 1;
    l_current                number;
begin
    select rownum into l_current
      from user_avail_trace_files
     where filename = p_filename;

    delete from trace_file_text;
    dbms_lob.fileopen( l_bfile );
    loop
        l_current := dbms_lob.instr( l_bfile, '0A', l_last, 1 );
        exit when (nvl(l_current,0) = 0);
        insert into trace_file_text (id,text)
        values (l_last, utl_raw.cast_to_varchar2(
                         dbms_lob.substr( l_bfile, l_current-l_last+1,
                                          l_last ) )
        );
        l_last := l_current+1;
    end loop;
    dbms_lob.fileclose(l_bfile);
end;
/
grant execute on trace_file_contents to public
/

Now, a developer can use a script like this (i called it tklast)

column filename new_val f
select filename
  from trace_files.user_avail_trace_files
 where dt = ( select max(dt)
                from trace_files.user_avail_trace_files
            )
/

exec trace_files.trace_file_contents('&f')
set termout off
set heading off
set feedback off
set embedded on
set linesize 4000
set trimspool on
set verify off
spool &f
select text from trace_files.trace_file_text order by id;
spool off
set verify on
set feedback on
set heading on
set termout on
host tkprof &f tk.prf
edit tk.prf

and have a session like this:

SQL> alter session set sql_trace=true;
SQL> run some sql
SQL> connect user/password (to log out and log in)
SQL> @tklast

there you go -- that'll trace THEIR last session.  You can modify tklast to
suite your needs but thats the idea.

Now access to the servers file system is moot, you do not need it

TKPROF for the masses!

Review &
      Followup

      Rating:
      5
Cool!!!!!!!!!!!!  January 03, 2003

Reviewer:  Reader
Excellent way of getting using TKPROF for developers

      Rating: 5
Perfect, but
      just one reminder  January 03, 2003
Reviewer:
      Dusan  from Czech rep.
I spent about three hours of tuning this stuff on my database (8.1.7.4, Sun
Solaris, SunOS 5.8). The problem was that trace files were in lowercase:
-rw-r-----   1 oracle   oinstall    1270 Jan  3 16:38 i42_ora_11628.trc

and query returned first part in uppercase:
select d.instance_name || '_ora_' || ltrim(to_char(a.spid)) || '.trc' filename
 from v$process a, v$session b, v$instance d
where a.addr = b.paddr
/
I42_ora_11628.trc

          Followup:

hey -- thanks -- I'll make that change.

      Rating:
      5
  March 19, 2003
Reviewer:  Robert
       from Memphis, USA

      Rating: 4
One more
      potential gotcha  May 15, 2003
Reviewer:  Glenn
      A. Santa Cruz  from Mobile, AL USA
Tom, you may want to include (or at least mention) the parameter
TRACEFILE_IDENTIFIER when constructing the tracefile name.  Even though this is
not supported on all operating systems (according to Oracle docs), it could
certainly affect your script.

When multiple developers are simultaneously tracing, we use this parameter to
help more easily identify trace files.

          Followup:

good point - i'll just add 'don't use tracefile identifier if you want this
stuff to work'

      Rating: 5
"tkprof"
      trivia  May 15, 2003
Reviewer:
      A reader
Excellent solution.

Tom, can you tell us a little bit about the history of tkprof - why is it named
as such? Thanks!

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5228516811673#9625338821966

      Rating:
      5
Excellent!  May 19, 2003
Reviewer:
      Mirjana  from Barcelona

      Rating: 5
tkprof a trace
      file from session which updates tables with triggers  August
      23, 2003
Reviewer:  A reader
Hi

I am a bit confused of how to tkprof trace file generated when there are
triggers being fired as well. I did a small test, create emp table and myobj
table (as select * from dba_objects)

create or replace trigger after_upd_on_obj
after update on obj
for each row
begin
  update myobj set object_id=1;
end;
/

then run

update emp set deptno=20;

tkprof the session and saw this

update emp set deptno=20

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0       1058      38649          12
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0       1058      38649          12

UPDATE MYOBJ SET OBJECT_ID=1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute     12      0.00       0.00          0       1469      42121       39804
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       13      0.00       0.00          0       1469      42121       39804

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      2      0.00       0.00          0       1058      38649          12
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0       1058      38649          12

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse      107      0.00       0.00          0          0          0           0
Execute    118      0.00       0.00          0       1803      42184       39867
Fetch       64      0.00       0.00          0        129          0          22
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      289      0.00       0.00          0       1932      42184       39889

I dont understand why update emp set deptno=20 generates so much current mode
reads, also the non-recursive and recursive doesnt make sense to me neither. I
guess trigger is treated as recursive right?

If we sum current mode reads of update emp and update myobj is

38649 + 42121 = 80770 not seen anywhere in the tkprof file

I then test again by disabling the trigger and for only 20 current mode reads
for update emp as follows:

update emp set deptno=20

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          1         30          12
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          1         30          12

confused!

          Followup:

care to share the entire test case with us from scratch.

i could probably gen up the same example, but it's so much easier when you do
what I do which is show how to reproduce from square one!!

August 23, 2003 Ali -- Thanks for the question regarding "Redo
      logs?", version 8.0.5

      You Asked
Hi Tom,
Our database running in archivelog mode, most of the time our developer run
thier bulk load, update, or delete during working hours while the normal users
are working on the system and the make the server slow because of the highly
generation of redo log files.
Today there was more than 1.7 GB of redo log files generated during working
hours, our database size around 8.5 GB.
I want to know which developer are generating that redo log files?

      and we said...
well, 1.7gig, I wish I could generate that little myself.  that would be nice.
(it is really pretty small)

you would have to monitor v$sesstat periodically during the day:

ops$tkyte@ORA920> select a.username, a.program, c.value
  2  from v$session a, v$statname b, v$sesstat c
  3  where a.sid = c.sid and b.statistic# = c.statistic#
  4  and b.name = 'redo size'
  5  /

USERNAME  PROGRAM                                               VALUE
--------- ------------------------------------------------ ----------
          oracle@tkyte-pc-isdn.us.oracle.com (PMON)                 0
          oracle@tkyte-pc-isdn.us.oracle.com (DBW0)             25240
          oracle@tkyte-pc-isdn.us.oracle.com (LGWR)                 0
          oracle@tkyte-pc-isdn.us.oracle.com (CKPT)                 0
          oracle@tkyte-pc-isdn.us.oracle.com (SMON)            140960
          oracle@tkyte-pc-isdn.us.oracle.com (RECO)                 0
          oracle@tkyte-pc-isdn.us.oracle.com (CJQ0)                 0
          oracle@tkyte-pc-isdn.us.oracle.com (ARC0)                 0
          oracle@tkyte-pc-isdn.us.oracle.com (ARC1)                 0
OPS$TKYTE sqlplus@tkyte-pc-isdn.us.oracle.com (TNS V1-V3)         612

10 rows selected.

but bear in mind, as soon as a session logs out -- poof, this data is gone for
that session -- so you sort of have to "watch for it"

In 8i, you could use a logoff trigger to record it, but not in 8.0 and before.

The real question I have for you is -- why are your DEVELOPERS running what seem
to be DBA tasks in your production database?  Seems you have an organizational
problem here, not a database one....  A little "control" goes a long way -- they
should not be privileged to do this in a production instance, that would solve
the problem easily.
Review & Followup

      Rating: 5
SELECT FOR
      UPDATE  August 20, 2003
Reviewer:  Ali

Thanks for your response.
I was able to track the sessions that was generating most of the redo logs. I
trace one of these sessions and found that this redo generated by one form
(Developer 2000 - form 5). This form used to enter the employee leave and in the
trace it shows that there is SELECT FOR UPDATE and to insert 1 row this generate
more than 20MB. We check that form but we couldn't find from where this SELECT
FOR UPDATE comming, we see it in the trace file only.
Can you help us to find SELECT FOR UPDATE in the form?

          Followup:

i don't get the relationship between the "insert 1 row" and select for update.
we don't select for update to insert.

to modify a row -- yes.
to delete a row -- yes.
to insert a row -- no we do not select for update.

do the tkprof with aggregate=no, you'll see the SQL in order of execution.  you
can look above the "for update" you believe is the culprit and see what was
happening immediately prior to that.

      Rating: 5
SELECT FOR
      UPDATE  August 21, 2003
Reviewer:  Ali

I dont find the relation between the insert and select for update to, but when
we run that form we get SELECT FOR UPDATE. I will tell you the steps we follow
it:
1- Enable tracing for new session that will run only that form.
2- Run just the specific form.
3- Disable the tracing.
4- Run tkprof.
This form suppose to insert just one row, but when we look to the tkprof report
we found the SELECT FOR UPDATE.
We generate the code of the form but we couldn't find any SELECT FOR UPDATE.
So, we need your help to find that?

          Followup:

aggregate=no, follow the sequential logic.

      Rating: 5
SELECT FOR
      UPDATE  August 23, 2003
Reviewer:  Ali

I use tkprof with aggregate=no and  I got the same result. Still I see the
SELECT FOR UPDATE.

          Followup:

and what was immediately in front of select for update

i wasnt expecting it to go away.  I trying to show you how to figure out where
it is coming from.  look UP in the trace file for what was happening right
before that for a couple of statements.  that should give you a clue as to where
exactly in the form you are and what you are doing at that point.

August 23, 2003 Nikhil -- Thanks for the question regarding
      "Cursor optimization", version 8

      You Asked
Dear Tom,

I have a cursor,which takes data from a temp table which has emailids, and other
related fields like fname,lastname,sourceid...
It fetches rows one by one and
checks for a email id, if it is found in table x then, it updates all the rest
of the fields of x and takes unique id, concerned to that email.This unique id
gets inserted in table y.
if this email is not found in xthen, it insert in x.

The problem is , that i have around 2 million records in x. And sometimes i have
50,000 to 1 million records in temp table. This cursor takes very long time to
execute. How can make it fast.

Cursor is follows:

create or replace procedure insertx1
(mailerid in number)
as
cursor c1 is
select isourceid,vcemail,vcfirstname,vclastname,vcsourcepeopleid from tmpx;

cnt number;
icurpeid number;

begin

for rec1 in c1 loop

    select count(1) into cnt
    from x
    where vcemail=rec1.vcemail;
        if cnt=0 then
        --genearate a new peid
        select SEQ_IPEID.nextval into icurpeid from dual;
        insert into
x(ipeid,vcemail,isourceid,vcfirstname,vclastname,vcsourcepeopleid)
values(icurpeid,rec1.vcemail,rec1.isourceid,rec1.vcfirstname,rec1.vclastname,rec1
.vcsourcepeopleid);
        commit;
        end if;
        if cnt=1 then
        --get ipeid from x
        update x set
vcfirstname=rec1.vcfirstname,vclastname=rec1.vclastname,vcsourcepeopleid=rec1.vcs
ourcepeopleid where vcemail=rec1.vcemail;
        commit;
        select ipeid into icurpeid from x where vcemail=rec1.vcemail;
    end if;
    insert into y(ipmid,ipeid,imailerid)
values(SEQ_IPMID.nextval,icurpeid,mailerid);
    exit when c1%notfound;
end loop;
commit;
end;

Kindly help me out.

      and we said...
it is all about sets....  think "set based".  don't thing "row by row by very
very slow row by slow row"

ops$tkyte@ORA920> create table t ( emailid number primary key, data varchar2(30)
);

Table created.

ops$tkyte@ORA920> create table x ( emailid number primary key, data varchar2(30)
);

Table created.

ops$tkyte@ORA920> create table y ( emailid number primary key );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into x
  2  select rownum, object_name
  3    from big_table.big_table;

1833792 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t
  2  select rownum - 50000, object_name
  3    from big_table.big_table
  4   where rownum <= 100000;

100000 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table x compute statistics for table for all indexes
for all indexed columns;

Table analyzed.

ops$tkyte@ORA920> analyze table t compute statistics for table for all indexes
for all indexed columns;

Table analyzed.

ops$tkyte@ORA920> create or replace procedure merge_data
  2  as
  3  begin
  4      update ( select t.data t_data, x.data x_data
  5                 from t, x
  6                where t.emailid = x.emailid )
  7         set x_data = t_data;
  8      dbms_output.put_line( 'Updated ' || sql%rowcount );
  9
 10      insert into y
 11      select t.emailid
 12        from t
 13       where emailid in (select emailid from x);
 14
 15      insert into x
 16      select * from t
 17       where emailid NOT IN ( select emailid from x );
 18      dbms_output.put_line( 'Inserted ' || sql%rowcount );
 19  end;
 20  /

Procedure created.

Elapsed: 00:00:00.26
ops$tkyte@ORA920>
ops$tkyte@ORA920> set timing on
ops$tkyte@ORA920> exec merge_data;
Updated 50000
Inserted 50000

PL/SQL procedure successfully completed.

Elapsed: 00:00:09.84

under 10 seconds... on a desktop PC, not a real server...

Keys to understand -- the not in will be really horrible with the RBO, make sure
to use the CBO!!!  we need to use a hash anti join on that guy.  My plans were
for example:

-------------------------------------------------------------------------------
| Id  | Operation             |  Name    | Rows  | Bytes |TempSpc| Cost (%CPU)|
-------------------------------------------------------------------------------
|   0 | INSERT STATEMENT      |          |     1 |    27 |       |  1186  (23)|
|*  1 |  HASH JOIN ANTI       |          |     1 |    27 |  3328K|  1186  (23)|
|   2 |   TABLE ACCESS FULL   | T        |   100K|  2148K|       |    55  (17)|
|   3 |   INDEX FAST FULL SCAN| SYS_C09  |  1833K|  8954K|       |   442  (23)|
-------------------------------------------------------------------------------

you'll be looking for something similar.

Review &
      Followup

      Rating: 2
Thx , but after
      using this cursor i am getting oracle error  August 26, 2003

Reviewer:  Nikhil Sukul  from Mumbai,INDIA

I am getting this error:
ORA-01779: cannot modify a column which maps to a non key-preserved table
ORA-06512: at "GLOBALEMAILUSER.MERGE_DATA", line 4

August 23, 2003 -- Thanks for the question regarding "Querying
      history table", version 8.1.7

      You Asked
I have a cust table and a after each row level trigger that captures all :new
values on every insert & update into a history cust_h table along with a
timestamp and a unique history_id (sequence generated)

Say I want to browse changes made to the 'last_name' column in the table. I
would do

select distinct last_name from cust_h
where cust_no=1234 and timestamp between <start> and <end>

[I need the distinct because if some other column has been modified, the
last_name column would be the same for this history row]

This is fine, but for each of the rows returned above, I also want the timestamp
for that row. i.e.

select timestamp, distinct last_name from cust_h
where cust_no=1234 and timestamp between <start> and <end>

Of course, this doesnt work.

In general, I want to browse changes made to a arbitrary set of columns (select
distinct col1,col2,...,colN from cust_h) and have the timestamp/pk of the hist
table returned with each row.

Any ideas?

do you have cases where last_name goes like this:

smith
smith
smith
frank
frank
smith
smith

eg: it can "change back"?

Yes, it sure can. Maybe last name is a bad example, but my cust table has a
bunch of other attributes which can certainly change back and forth. I want to
track changes to, say 10, columns in the cust table.

i.e. I will do a

select distinct col1,col2,...,col10 from cust_h
where ...
and render the results in my web page/service, whatever.

      and we said...
ops$tkyte@ORA920> select *
  2    from t
  3   order by dt
  4  /

DATA       DT
---------- ---------
smith      08-FEB-03
smith      09-FEB-03
smith      10-FEB-03
frank      11-FEB-03
frank      12-FEB-03
frank      13-FEB-03
frank      15-FEB-03
smith      18-FEB-03
smith      28-FEB-03
smith      10-MAR-03

10 rows selected.

ops$tkyte@ORA920> select *
  2   from (
  3  select data, dt, lag(data) over (order by dt) last_data
  4    from t
  5        )
  6   where last_data is null OR last_data <> data
  7  /

DATA       DT        LAST_DATA
---------- --------- ----------
smith      08-FEB-03
frank      11-FEB-03 smith
smith      18-FEB-03 frank

will help you there.  We'll just keep records where the previous record either

a) didn't exist
b) had a different value...

Review &
      Followup

      Rating:
      2
  March 11, 2003
Reviewer:
      A reader
I had the same idea. It works fine when I am just interested in one column. When
I want to see changes to, say 10 columns, what do I do?

ops$tkyte@ORA920> select *
  2   from (
  3  select dt, data1, lag(data1) over (order by dt) last_data1,data2,lag(data2)
over (order by dt),....
  4    from t
  5        )
  6   where last_data1 is null OR last_data1 <> data1
or last_data2 is null or last_data2 <> data2
or ....

the query becomes very unwieldy with all the ORs.

Is there a better way?

          Followup:

well, you only need to code it once -- use a stored procedure to return a
refcursor given inputs.

You have 10 fields, you sort of need to compare them if you only want a row
output when they change.

      Rating: 5
Not sure I'm
      adding much to the discussion, but...  March 11, 2003

Reviewer:  Dan Kefford  from New Haven, CT

Would something like this be less "unwieldy?"

SQL> CREATE TABLE t
  2  (data1 VARCHAR2(10), data2 VARCHAR2(10), dt DATE)
  3  /

Table created.

SQL> INSERT INTO t
  2  VALUES('frank','smith','08-FEB-03')
  3  /

1 row created.

SQL> INSERT INTO t
  2  VALUES('frank','smith','09-FEB-03')
  3  /

1 row created.

SQL> INSERT INTO t
  2  VALUES('john','smith','11-FEB-03')
  3  /

1 row created.

SQL> INSERT INTO t
  2  VALUES('john','smith','12-FEB-03')
  3  /

1 row created.

SQL> INSERT INTO t
  2  VALUES('jim','smith','13-FEB-03')
  3  /

1 row created.

SQL> INSERT INTO t
  2  VALUES('jim','jones','14-FEB-03')
  3  /

1 row created.

SQL> INSERT INTO t
  2  VALUES('jim','jones','15-FEB-03')
  3  /

1 row created.

SQL> INSERT INTO t
  2  VALUES('jim','jones','16-FEB-03')
  3  /

1 row created.

SQL> INSERT INTO t
  2  VALUES('jim','smith','17-FEB-03')
  3  /

1 row created.

SQL> INSERT INTO t
  2  VALUES('jim','jones','18-FEB-03')
  3  /

1 row created.

SQL> INSERT INTO t
  2  VALUES('jim','jones','19-FEB-03')
  3  /

1 row created.

SQL> INSERT INTO t
  2  VALUES('frank','jones','20-FEB-03')
  3  /

1 row created.

SQL> COMMIT
  2  /

Commit complete.

SQL> SELECT  *
  2  FROM   (SELECT  iv1.*,
  3                 (CASE WHEN last_data1 IS NULL OR
  4                            last_data2 IS NULL THEN 'new'
  5                       WHEN last_data1 != data1 OR
  6                            last_data2 != data2 THEN 'changed'
  7                       ELSE 'unchanged' END) status
  8          FROM   (SELECT  dt,
  9                          data1,
 10                          LAG(data1) OVER (ORDER BY dt) last_data1,
 11                          data2,
 12                          LAG(data2) OVER (ORDER BY dt) last_data2
 13                  FROM    t) iv1)
 14  WHERE   status IN ('new','changed')
 15  /

DT        DATA1      LAST_DATA1 DATA2      LAST_DATA2 STATUS
--------- ---------- ---------- ---------- ---------- ---------
08-FEB-03 frank                 smith                 new
11-FEB-03 john       frank      smith      smith      changed
13-FEB-03 jim        john       smith      smith      changed
14-FEB-03 jim        jim        jones      smith      changed
17-FEB-03 jim        jim        smith      jones      changed
18-FEB-03 jim        jim        jones      smith      changed
20-FEB-03 frank      jim        jones      jones      changed

7 rows selected.

Or even:

SQL> SELECT  *
  2  FROM   (SELECT  iv1.*,
  3                 (CASE WHEN last_data1 IS NULL OR
  4                            last_data2 IS NULL THEN 'new'
  5                       WHEN last_data1 != data1 THEN 'data1 changed'
  6                       WHEN last_data2 != data2 THEN 'data2 changed'
  7                       ELSE 'UNCHANGED' END) status
  8          FROM   (SELECT  dt,
  9                          data1,
 10                          LAG(data1) OVER (ORDER BY dt) last_data1,
 11                          data2,
 12                          LAG(data2) OVER (ORDER BY dt) last_data2
 13                  FROM    t) iv1)
 14  WHERE   status    = 'new'
 15  OR      status LIKE '%changed'
 16  /

DT        DATA1      LAST_DATA1 DATA2      LAST_DATA2 STATUS
--------- ---------- ---------- ---------- ---------- -------------
08-FEB-03 frank                 smith                 new
11-FEB-03 john       frank      smith      smith      data1 changed
13-FEB-03 jim        john       smith      smith      data1 changed
14-FEB-03 jim        jim        jones      smith      data2 changed
17-FEB-03 jim        jim        smith      jones      data2 changed
18-FEB-03 jim        jim        jones      smith      data2 changed
20-FEB-03 frank      jim        jones      jones      data1 changed

7 rows selected.

It's certainly not less code, but maybe it's a little more clear what is being
achieved, especially for the maintenance programmer.

      Rating: 4
Fantastic for
      Enterprise/9i standard, how about 8i standard ?  August 22,
      2003
Reviewer:  A reader  from Canada
We're not yet @ 9i (by January), so I'm wondering how this could be done without
analytical functions.

          Followup:

lag and lead like functionality would be accomplished by running what amount to
painfully slow correlated subqueries, 8i SE should be able to get last_sal2 and
next_sal2 -- slower, but it'll get it:

ops$tkyte@ORA920> select empno, hiredate, sal,
  2         lag(sal) over ( order by hiredate ) last_sal,
  3         lead(sal) over ( order by hiredate ) next_sal,
  4         to_number( substr(
  5            (select max(to_char(hiredate,'yyyymmddhh24miss') || sal)
  6               from emp e2
  7              where e2.hiredate < emp.hiredate ), 15 ) ) last_sal2,
  8         to_number( substr(
  9            (select min(to_char(hiredate,'yyyymmddhh24miss') || sal)
 10               from emp e2
 11              where e2.hiredate > emp.hiredate ), 15 ) ) next_sal2
 12    from emp
 13   order by hiredate
 14  /

     EMPNO HIREDATE         SAL   LAST_SAL   NEXT_SAL  LAST_SAL2  NEXT_SAL2
---------- --------- ---------- ---------- ---------- ---------- ----------
      7369 17-DEC-80        800                  1600                  1600
      7499 20-FEB-81       1600        800       1250        800       1250
      7521 22-FEB-81       1250       1600       2975       1600       2975
      7566 02-APR-81       2975       1250       2850       1250       2850
      7698 01-MAY-81       2850       2975       2450       2975       2450
      7782 09-JUN-81       2450       2850       1500       2850       1500
      7844 08-SEP-81       1500       2450       1250       2450       1250
      7654 28-SEP-81       1250       1500       5000       1500       5000
      7839 17-NOV-81       5000       1250        950       1250       3000
      7900 03-DEC-81        950       5000       3000       5000       1300
      7902 03-DEC-81       3000        950       1300       5000       1300
      7934 23-JAN-82       1300       3000       3000        950       3000
      7788 09-DEC-82       3000       1300       1100       1300       1100
      7876 12-JAN-83       1100       3000                  3000

14 rows selected.

note that bolded line.  each answer in this case is equally valid.  we had 2
people "next" -- so the results may differ given the same repeating value for
what you are lagging/leading over!

      Rating: 2
I'm missing
      something? Lag and lead in 8i standard?  August 23, 2003

Reviewer:  A reader  from Canada
When using a query based on the one provided I still get ORA-00439 Feature not
Enabled. Lag and lead are not available in my 8i standard, unless I've missing
some init.ora parameter?

          Followup:

drop the last_sal and next_sal

they were there purely for demonstration purposes -- to show you can get similar
functionality using the subqueries.

in 8i SE you are only interested in the poor performaning scalar subqueries of
course.

August 23, 2003 Frank -- Thanks for the question regarding
      "shrink the tablespace", version 9

      You Asked
Hi, Tom,
Why is it diffcult for Oracle to reset the high water mark(HWM) after deleting
the records so we dont have to use TRUNCATE to reset the HWM? What's the idea of
introducing the HWM? THX!

      and we said...
Let me turn this around on you.

If you delete all of the records -- why is it so hard for you to not use
TRUNCATE?

Hmm.

Anyway, lets say you delete LOTS of records.

The table started as (from front to back)

record
record
record
record
record
record
record
record
record
record
record
record

you delete lots of stuff and end up with:

record
empty space
empty space
empty space
empty space
empty space
empty space
empty space
empty space
empty space
empty space
empty space
empty space
record

Well, impossible to shrink and impractable for us to determine if we could even
shrink or not.

The space is in fact RECLAIMED.  Its all on the freelist for that table, we'll
reuse it -- FOR THAT TABLE.

A table is a very complex data structure.  It would be so onerous to "shrink it"
and require physically moving tons of data.  You can shrink it via alter table
move or the dbms_redefinition package online if you like.
Review &
      Followup

      Rating: 5
That made HUGE
      sense!  September 28, 2002
Reviewer:
      A reader
Thank you Tom!~

      Rating: 4
HWM
      doubts  August 22, 2003
Reviewer:  Vipin
       from NY
Hi Tom,

I wanted to post this as a separate question but I am unable to catch the thread
for new questions.

I am not able to justify the following test case:-
I have a table by name TEST_TABLE.

Following are the statistics which are available for this table:-

    BLOCKS EMPTY_BLOCKS  AVG_SPACE NUM_FREELIST_BLOCKS BLOCKS*8192
AVG_ROW_LEN*NUM_ROWS
---------- ------------ ---------- ------------------- -----------
--------------------
      7379          300        945                   4    60448768
52218192

SQL> exec show_space('TEST_TABLE');
Free Blocks.............................4
Total Blocks............................7680
Total Bytes.............................62914560
Unused Blocks...........................300
Unused Bytes............................2457600
Last Used Ext FileId....................14
Last Used Ext BlockId...................33282
Last Used Block.........................340
Maximum no: of rows a block has.........74
Minimum no: of rows a block has.........19
Average no: of rows a block has.........34.04

Here my inference is that there are 300 blocks above HWM and 7680-300 i.e. 7380
blocks which are below HWM and hence will be scanned till the  HWM while a full
scan.

Now I performed the following operation in which I first moved the table to
tablespace TAB_SP2 from TAB_SP1 and then back to TAB_SP1 which according to my
understanding will reset the HWM.

SQL> alter table TEST_TABLE move tablespace tab_sp2;

Table altered

SQL> alter table TEST_TABLE move tablespace tab_sp1;

Table altered

SQL> Select 'Alter index '||index_name||' rebuild ;'
  2  from user_indexes
  3  where table_name = 'TEST_TABLE';

'ALTERINDEX'||INDEX_NAME||'REB
----------------------------------------------------
Alter index IDX1_TEST_TABLE rebuild ;
Alter index IDX2_TEST_TABLE rebuild ;

SQL>
SQL> Alter index IDX1_TEST_TABLE rebuild ;

Index altered

SQL> Alter index IDX2_TEST_TABLE rebuild ;

Index altered

SQL> ANALYZE TABLE TEST_TABLE COMPUTE STATISTICS FOR TABLE FOR ALL INDEXES FOR
ALL INDEXED COLUMNS;

Table analyzed

Now the statistics appears to be :-

    BLOCKS EMPTY_BLOCKS  AVG_SPACE NUM_FREELIST_BLOCKS BLOCKS*8192
AVG_ROW_LEN*NUM_ROWS TABLESPACE_NAME
---------- ------------ ---------- ------------------- -----------
-------------------- ------------------------------
      7387          292        953                   0    60514304
52218192 TAB_SP1

SQL> ed
SQL> begin show_space('TEST_TABLE'); end;
  2  /
Free Blocks.............................0
Total Blocks............................7680
Total Bytes.............................62914560
Unused Blocks...........................292
Unused Bytes............................2392064
Last Used Ext FileId....................14
Last Used Ext BlockId...................33282
Last Used Block.........................348
Maximum no: of rows a block has.........72
Minimum no: of rows a block has.........19
Average no: of rows a block has.........33.99

PL/SQL procedure successfully completed

Here as per my understanding still I have 7680-292=7388 blocks below HWM and
will be scanned till HWM while full table scan. So how come the HWM has been
resetted.

Please correct if I am wrong in these understandings.

One more doubt in this scenario is how are these results of show_space related
to the outputs which we get from the USER_TABLES query listed above.

          Followup:

what do you mean by "so how come the HWM has been resetted???"

what are you looking for here?

all this shows is

o blocks were already "full"
o moving just resulted in having the same thing you started with

      Rating: 5
doubt on your
      response  August 22, 2003
Reviewer:
      A reader
"Let me turn this around on you.

If you delete all of the records -- why is it so hard for you to not use
TRUNCATE?"

I thought that truncate does not get logged? Which means it does not generate
redo - which is why you would use delete in general, i.e. If you want the
operation to be recoverable???

truncate would be used only if you are
1. sure you dont care about the deleted data
2. you have coordinated with dba for a backup to
go back to in order to recover if required.

          Followup:

the operation (truncate) is recoverable.  it is considered DDL so it is done as
an atomic statement all by itself -- but it is "recoverable".

you do not need a backup after a truncate.

if you are deleting all of the data -- you obviously do not care about it.

a single:

truncate table t;

is quite simply an efficient:

delete from t;
commit;

      Rating:
      2
  August 23, 2003
Reviewer:  Vipin
       from NY
Sorry Tom,

It was a typo. What I meant is "so how come the HWM has not been resetted???"

What I meant is still I will have to scan till HWM, for 7388 blocks, when I do a
full scan.

If HWM has been resetted then the as per my understanding unused blocks must be
equal to total blocks provided number of blocks on the freelist is 0 else it
must be total blocks minus the number of blocks on the freelist.

Please correct me.

          Followup:

you have 7,388 blocks of data in your table -- that is why???????

you had a table that was already nicely packed, full.

you moved it.

you ended up with the same thing you started with.

i don't know what you were expecting.  if the table got "smaller" every time we
moved it, i would just move it 100 times and it would fit on a block :)

      Rating:
      3
  August 23, 2003
Reviewer:  Vipin
       from NY
Hi Tom,

Could you please tell me how many of the blocks are above and how many are below
HWM and hence how many blocks will be scanned till the HWM when a full table
scan takes place in the above 2 cases (before and after the move). Hence I will
be able to understand how a alter table move affects HWM(if it has done here).

Sorry for pestering you but a bit confused here.

Thanks a lot.

          Followup:

ok, you started with a table:

SQL> exec show_space('TEST_TABLE');
Free Blocks.............................4
Total Blocks............................7680
Total Bytes.............................62914560
Unused Blocks...........................300

it had 7680-300 blocks below the HWM.  7380 total.

you did a move of that table.  after that move, you had:

SQL> begin show_space('TEST_TABLE'); end;
  2  /
Free Blocks.............................0
Total Blocks............................7680
Total Bytes.............................62914560
Unused Blocks...........................292

7680-292 = 7388 blocks below the HWM.  This all just means that the table was
already PACKED -- moving it actually made it LARGER since the pctfree was
reapplied and moved blocks around.  Consider (big_table is just a copy of
all_objects over and over and over):

big_table@ORA920> create table t
  2  as
  3  select *
  4    from big_table
  5   where rownum <= 100000;

Table created.

big_table@ORA920>
big_table@ORA920> analyze table t compute statistics for table;

Table analyzed.

big_table@ORA920> select blocks from user_tables where table_name = 'T';

    BLOCKS
----------
      1233

to full scan will read 1,233 blocks.  that is how much it will take

big_table@ORA920>
big_table@ORA920> update t set object_name = substr( 'xx'||object_name,1,30);

100000 rows updated.

big_table@ORA920> analyze table t compute statistics for table;

Table analyzed.

big_table@ORA920> select blocks from user_tables where table_name = 'T';

    BLOCKS
----------
      1233

to full scan will STILL take 1,233 -- but bear in mind that we just made most
rows 2 bytes bigger.  We've taken some blocks OVER their pctfree with this
update.  rebuilding will result in a BIGGER table now:

big_table@ORA920>
big_table@ORA920> alter table t move;

Table altered.

big_table@ORA920> analyze table t compute statistics for table;

Table analyzed.

big_table@ORA920> select blocks from user_tables where table_name = 'T';

    BLOCKS
----------
      1254

which is did, predicably.  the HWM is reset!!! reset UP.  it'll take 1,254
blocks to full scan now!

big_table@ORA920>
big_table@ORA920>
big_table@ORA920> delete from t where mod(object_id,2) = 0;

49979 rows deleted.

big_table@ORA920> analyze table t compute statistics for table;

Table analyzed.

big_table@ORA920> select blocks from user_tables where table_name = 'T';

    BLOCKS
----------
      1254

here is where a "reorg" can lower the HWM:

big_table@ORA920>
big_table@ORA920>
big_table@ORA920> alter table t move;

Table altered.

big_table@ORA920> analyze table t compute statistics for table;

Table analyzed.

big_table@ORA920> select blocks from user_tables where table_name = 'T';

    BLOCKS
----------
       628

there, now the HWM is lowered.

hope that clears it up.  You just had a case whereby moving the table reset the
HWM UP, instead of down.

the hwm could

a) go up
b) go down
c) stay the same

all depending on what is in the table!!!

      Rating:
      5
  August 23, 2003
Reviewer:  Vipin

That was wonderful explanation.
Thanks a lot

August 23, 2003 -- Thanks for the question regarding "Resource
      Manager", version 9i

      You Asked
I have read that if a resource consumer group has meore than one plan directives
referring to it , it may have more than one max_estimated_exec_time. The
Database Resource Manager will then choose the most restrictive of all of
incoming values.
can you please give me some example relating this?
Thanks
Natalia

      and we said...
Basically, they are saying when you have a multilevel plan, you can have some
directives contributing to a resource consumer group that some from more then
one plan.  For example, see the graphic on:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96521/dbrm.htm#12607
there the "other groups" get plan directives from both maildb and bugdb -- this
is just describing what happens when this occurs -- what directives will be
used.

My example looks like this:

                              lvl1  (top level plan)
                             /    \
                            /      \
                           /        \
          max=10         lvl2a     lvl2b     max=100
                           \        /
                            \      /
                             \    /
                     demo res. consumer group

so, my demo resource consumer group will inherit from both lvl2a andlvl2b --
what will the max est exec time be?  the minimum of 10 and 100.

Consider:

ops$tkyte@ORA920.US.ORACLE.COM> begin
  2          dbms_resource_manager.create_pending_area;
  3
  4          dbms_resource_manager.create_plan
  5          ( 'lvl1', comment => 'top level plan' );
  6
  7          dbms_resource_manager.create_plan
  8          ( 'lvl2a', comment => 'second level plan' );
  9
 10          dbms_resource_manager.create_plan
 11          ( 'lvl2b', comment => 'second level plan' );
 12
 13          dbms_resource_manager.create_consumer_group
 14          ( consumer_group => 'demo', comment => 'yes it is a demo' );
 15
 16          dbms_resource_manager.create_plan_directive
 17          ( plan => 'lvl2a',
 18            group_or_subplan => 'demo',
 19            comment => 'its a demo',
 20            MAX_EST_EXEC_TIME => 10 );
 21
 22          dbms_resource_manager.create_plan_directive
 23          ( plan => 'lvl2b',
 24            group_or_subplan => 'demo',
 25            comment => 'its a demo',
 26            MAX_EST_EXEC_TIME => 100 );
 27
 28          dbms_resource_manager.create_plan_directive
 29          ( plan => 'lvl1',
 30            group_or_subplan => 'lvl2a',
 31            comment => 'its a demo' );
 32
 33          dbms_resource_manager.create_plan_directive
 34          ( plan => 'lvl1',
 35            group_or_subplan => 'lvl2b',
 36            comment => 'its a demo' );
 37
 38          dbms_resource_manager.create_plan_directive
 39          ( plan => 'lvl1',
 40            group_or_subplan => 'OTHER_GROUPS',
 41            comment => 'its a demo' );
 42
 43          dbms_resource_manager.create_plan_directive
 44          ( plan => 'lvl2a',
 45            group_or_subplan => 'OTHER_GROUPS',
 46            comment => 'its a demo' );
 47          dbms_resource_manager.create_plan_directive
 48          ( plan => 'lvl2b',
 49            group_or_subplan => 'OTHER_GROUPS',
 50            comment => 'its a demo' );
 51          dbms_resource_manager.validate_pending_area;
 52
 53          dbms_resource_manager.submit_pending_area;
 54
 55  end;
 56  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================
Lets create that poor end user who is to be limited and assign him to this plan

ops$tkyte@ORA920.US.ORACLE.COM> drop user low_priority cascade;
User dropped.

ops$tkyte@ORA920.US.ORACLE.COM> create user low_priority identified by x;
User created.

ops$tkyte@ORA920.US.ORACLE.COM> grant connect, resource to low_priority;
Grant succeeded.

ops$tkyte@ORA920.US.ORACLE.COM> begin
  2          dbms_resource_manager_privs.grant_switch_consumer_group
  3          ( 'low_priority', 'demo', TRUE );
  4
  5          dbms_resource_manager.set_initial_consumer_group
  6          ( 'low_priority', 'demo' );
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

Now, we need to make the plan active

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> alter system set resource_manager_plan = lvl1
scope = memory;

System altered.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> connect low_priority/x
Connected.
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

Now, we'll create a table, try to select from it and then see
what happens when we attempt a stupid query

low_priority@ORA920.US.ORACLE.COM>
low_priority@ORA920.US.ORACLE.COM> create table t as select * from all_objects;

Table created.

low_priority@ORA920.US.ORACLE.COM>
low_priority@ORA920.US.ORACLE.COM> analyze table t compute statistics;

Table analyzed.

low_priority@ORA920.US.ORACLE.COM>
low_priority@ORA920.US.ORACLE.COM> select count(*) from t;

  COUNT(*)
----------
     23638

low_priority@ORA920.US.ORACLE.COM>
low_priority@ORA920.US.ORACLE.COM> select count(*) from t,t;
select count(*) from t,t
                     *
ERROR at line 1:
ORA-07455: estimated execution time (9141 secs), exceeds limit (10 secs)

see how it picked 10 seconds as the limit

Review &
      Followup

      Rating: 5
Thanks A
      Lot  October 15, 2002
Reviewer:  Natalia

Sir thanks a Lot.
Now it is absolutely clear to me

      Rating: 5
question about
      document example  October 18, 2002
Reviewer:
      A reader
hi

from figure shown in

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96521/dbrm.htm#12607
I have doubts

Postman group is assigned 40% CPU of 30% CPU assigned to maildb plan then users
group and mail maint group are assigned 80% ad 20% of 40% CPU assigned to
postman group (is my understanding right?).

For example if we have 20 CPUs in MYDBPLAN then 6 would be assigned to
MAILDBPLAN, then from those 6, 1.2 would be assigned to POSTMAN GROUP and from
that 1.2, it would be then divided again into two groups USERS GROUP and MAIL
MAINT GROUP. My question is where is the other 60% of MAILDBPLAN assigned to?

In multilevel groups what does level mean? Does it mean Level 2 uses CPU
assignment from Level 1, and Level 3 from Level and Level 4 from Level 3?

          Followup:

goto the picture again and click on the link:

Text description of admin057.gif follows
Text description of the illustration admin057.gif

      Rating: 4
read it and
      dont get it :-(  October 18, 2002
Reviewer:
      A reader
I dont get it :-(

It says MYDB_PLAN. MAILDB_PLAN receives 30 percent of the CPU at level 1 but it
also says MAILDB_PLAN. MAIL_POSTMAN_GROUP receives 40% of the CPU at level 1,
are these same level 1? Where is the other 60% assigned?

          Followup:

the 60% remaining at level 1 is split 80/20 by MAIL_USERS_GROUP and
MAIL_MAINTENANCE_GROUP at level 2.

      Rating: 3
then that's
      even more confusing  October 18, 2002
Reviewer:
      A reader
hi

I thought level 2 has to be below level 1, in the figure nowhere says that USER
GROUP and MAIL MAINT GROUP is below any level 1?

If those two groups is using the 60% then if I look BUG DB PLAN using same
criteria as before then where does BUG MAINT GROUP get 100% from????

It's even more confusing!

      Rating: 3
my
      understanding is  October 18, 2002
Reviewer:
      A reader
Hi

My understanding about level is if I assign 80% and 20% CPU to two sub plans
then those become level 1 assigments.
Then if each sub plan has 2 consumer groups then those would be level 2. So in
summary it would be

                      X PLAN
          80%                            20%
         A PLAN                         B PLAN
40%               60%          10%              90%
V GROUP           W GROUP      Y GROUP          Z GROUP

40% and 60% in V and W group are 40% and 60% of 80% of total CPU usage, this is
what Text description of the illustration admin027.gif in same chapter suggests
but in other figure it seems like talk about different things

      Rating: 4
when does the
      plan kick in?  August 23, 2003
Reviewer:
      Reader
Tom, is it true that the resurce utilization plan that we set up using resource
manager kicks in only when the cpu utilization has reached a "peak" value? If
so, is there any value for the peak in percentage of total CPU? Thanks.

          Followup:

it does not hold back (since you cannot possibly put CPU in the bank and use it
later.  if you are 50% idle right now, no point at all in throttleing something
-- that 50% idle is never reusable)

it tries to make sure the various consumer groups get their requisite slices
over time.  it is more a function of what the groups are to get total wise more
then anything

      Rating:
      5
How?  August 23, 2003
Reviewer:
      Reader
"it tries to make sure the various consumer groups get their requisite slices
over time."

suppose I have allocated 50% of CPU to group A, and no other is group is doing
anything now, will group A only get 50%? or the resource manager will throttle
group A only if there is not enough cpu for other groups? In otherwords when
does resource manager kick in? Thanks.

          Followup:

A will get 100%

you cannot put these resources in the bank and use them later.

resource manager kicks in when a group is not getting what you asked for at a
minimum.

August 23, 2003 Patrick -- Thanks for the question regarding
      "OCI_DURATION_PROCESS definition change in 9iR2", version 9.0.1

      You Asked
I have several external procedure shared libraries that I developed and maintain
for an application that we sell.  I use OCIMemoryAlloc() with a
OCI_DURATION_PROCESS argument in an init() function just like you describe in
your book.  With release 9iR2, the definition of OCI_DURATION_PROCESS changed
from

#define  OCI_DURATION_PROCESS (OCIDuration)(OCI_DURATION_BEGIN-4)

to

#define  OCI_DURATION_PROCESS (OCIDuration)(OCI_DURATION_BEGIN-5)

I already have to build a release of the shared libraries for each platform we
support, but now I have to also build a 9i and a 9iR2 version of the libraries
also.

My question is: Is there some way of determining the value of this argument
dynamically and passing it at runtime so that versions of the library that we
build with 9.0.1 will work when run in a 9iR2 database?  Or is it just too much
to ask that I won't have to build and maintain a version of a library for each
release of the database.

      and we said...
You must build a binary version for each release -- MUST.  Not just for this
reason -- but for supportability reasons as well.

The parameter values are free to change, the libraries will certainly change.
The names of libraries can and will change (so your dynamic links will fail when
we move function F from liba to libB for example or just remove liba all
together).

It would be "optimal" if you could ship your source and have it build on site
with the customers install (but that is harder -- granted but would result in
the most current libraries/headers being used).

Short of that, you will need a version for each major release at the very least.
Review &
      Followup

      Rating: 4
Re-linking is
      expected, recompiling was not  December 01, 2002

Reviewer:  Patrick Mealey  from Minnesota, USA

I have come to expect that re-linking is necessary.  We have always shipped .o
and .a files along with Makefiles so that the executable or library could be
linked on-site with whatever version of Oracle they had, but this is the first
time that a compile-time definition has changed forcing us to recompile and have
different .o and .a files.  Maybe we had just been lucky in the past.

          Followup:

Yes, well, since all OCI functions are really macros:

....
#define OCIExtProcAllocCallMemory(with_context, amount) \
ociepacm(with_context, (size_t)amount)
.......

you need to be really really careful that way.  The underlying ociepacm function
might be changed in future releases -- eg to need a third parameter for new
functionality.  We would do that by changing the macro:

#define OCIExtProcAllocCallMemory(with_context, amount) \
ociepacm(with_context, (size_t)amount, NULL )

#define OCIExtProcAllocCallMemoryNew(with_context, amount, third_parameter ) \
ociepacm(with_context, (size_t)amount, third_parameter )

So, the existing OCIExtProcAllocCallMemory function call will supply the third
argument as NULL (indicating to ociepacm to do the "old thing") whereas the new
function will let you pass that new 3rd paramter indicating "do the new thing"

So, as with all macro based interfaces -- you really really need to compile with
the headers for that version.  You are not calling C functions directly, they
are all macros.

      Rating: 5
The
      reason?  July 02, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Hi Tom,
In your book expert one-on-one Oracle page 797, in the code listing for the
lastOciError function, you are using OCIExtProcAllocCallMemory to allocate
memory to store and return the last OCI error encountered.

Can it alternatively be implemented by declaring the "errbuf" variable as a
"static char errbuf[256]"?

What would be the advantage of using the OCIExtProcAllocCallMemory as opposed to
a static char buffer declaration (I'm assuming this construct could also be
used, correct me if I'm wrong)?
Thanks,

          Followup:

that would be as bad as a global variable -- two invocations of that routine
refer to the same piece of memory.  It would be "a bug"

static and globals are to be avoided in extprocs - that is why I dynamically
allocated it.

      Rating: 4
Extproc
        August 23, 2003
Reviewer:  Piotr Jarmuz
       from Braunschweig, Germany
Hello Tom,

In your book you wrote that "C" external procedure agents can and will be reused
by several sessions and this is why we should never use static nor global
variables in them but instead use OCIContextSetValue to maintain the state
between the calls. But...

I just tried 100 concurrent sessions of various DB users from heterogenous
environments Windoze, Linux Debian, Linux Redhat, Sun Solaris. Various languages
Java, Perl, Pro*C clients as well as SQL*Plus and dreaded TOAD :)

All of them were calling my little OCI routine that jest calls getpid() (so it
gets PID of agent itself and returns it to SQL layer) and I counted exactly 100
instances of agent process so 100 different PIDs.

The same was reported after I executed the OS command on the server:

ps aux | grep "^oracle.*extproc" | wc -l

100

I use Oracle 9iR2 in dedicated mode. So under quite a load I was not able to
force the sharing of extproc agent processes. Each session had its own agent.
Can you explain why? And if this is now in 9i stable and documented behavior
then why bother with contexts and the such? I haven't tested it yet in MTS mode
nor under another OS on the server side.

I am just curious...

Regards

          Followup:

they can be -- that is more then enough.

using globals is a bad practice.  use the context as I describe.  it is the
documented, safe method.

      Rating:
      4
Thanx  August 23, 2003
Reviewer:  Piotr
      Jarmuz  from Braunschweig, Germany
Thanx.

I am asking cause I have my little hobby project to integrate Perl VM into
Oracle Server. I would use embedded perl in OCI routine with one entry point:

void call_perl(OCIExtProcContext *ctx, char* p_routine, OCIColl *p_inarr, short
*p_inarr_i, OCIColl **p_outarr, short *p_outarr_i);

create or replace procedure call_perl(p_routine varchar2, p_inarr string_table,
p_outarr out NOCOPY string_table) as
language C name 'call_perl' library pvm
with context parameters
(context, p_routine STRING, p_inarr OCIColl, p_inarr_i INDICATOR, p_outarr
OCIColl, p_outarr_i INDICATOR)

The routine itself would instantiate PVM on first call and passed the strings
from OCIColl into the Perl routine as scalars Perl would return array of outputs
which would be packed into out OCIColl array. In Perl everything can be coerced
to strings so this would be a good mapping between PLSQL and Perl...

Then a PLSQL-Perl user would just write a wrapper around the common entry point
with fixed number of types and parameters

that would simply cast all params as strings and packed them into the input
OCIColl and passed on to C and Perl where Perl routine would process them and
returned an array of scalars back to C and this one would packed them into
output OCIColl array.

So far so good... But the problem is PVM state if I had the guarantee there is
ALWAYS 1:1 mapping between user session and external agent process I'd be
home...

With OCI contexts I fear it might be a little inefficient and difficult to
implement in first place.

Anyway I must still think it through. But basically it should work. This will
not be as tightly integrated as Java is but I just like the idea...

Anyway if it ever works I will let you know :-)

Thanx again!

Regards

August 23, 2003 -- Thanks for the question regarding "Download
      source code and errata of Expert one-on-one", version 8.1.7

      You Asked
Hi Tom,

I have bought your book Expert one-on-one Oracle (Editor Wrox) three months ago
and I am starting to read it now during my vacations.
In the book, it mentions that you can download the source code and errata from
www.wrox.com. However, I could not find it in the website anymore.
I then noticed that you have a reprint of the book with a different editor.
Do you know where I can get the source ?
Thank you.

      and we said...
http://support.apress.com/code_listing.asp?s=0&view=all

August 23, 2003 carly -- Thanks for the question regarding "IOT
      and create table as", version 8.1.7

      You Asked
Hi Tom,

Bought your book, thanks for sharing the knowledge.  Still reading...

Vers: 8.1.7.4

Is it possible to create an IOT using create table "as" syntax?
It seems that I can't define the primary key and "organization index"
at the same time. This would be a nice feature, if it doesn't already
exist and I just missed it.

Thanks,

Carly

      and we said...
ops$tkyte@ORA817DEV> create table emp_iot
  2  (empno, ename, job, hiredate, sal, comm, deptno,
  3   constraint emp_iot_pk primary key(empno))
  4  organization index
  5  as
  6  select empno, ename, job, hiredate, sal, comm, deptno
  7    from scott.emp
  8  /

Table created.

Review &
      Followup

      Rating: 5
Create IOT as
      create table "as"  February 08, 2003
Reviewer:
      carly  from Moondog, MO
Thanks so much.

      Rating: 3
Is it possible
      to convert normal table to IOT? Version 8.1.7.4  April 22,
      2003
Reviewer:  Sunil Gururaj  from CYPRUS

Hi Tom,

Is it possible to alter a normal table to an Index Organized Table?

Thanks,
Sunil Gururaj

          Followup:

with a rebuild -- sure.

you have to rebuild it - it requires a pretty "massive" physical move of data.

      Rating: 2
Does the RBO
      know about IOT's?  June 20, 2003
Reviewer:
      Matt  from Australia
app_status_set is an IOT.

The application is part way through moving towards the CBO.

SQL> set autotrace traceonly  explain
SQL> select * from app_status_set;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=RULE (Cost=2 Card=2000 Bytes=2800
          0)

   1    0   INDEX (FAST FULL SCAN) OF 'JSS_IOT_PK' (UNIQUE) (Cost=2 Ca
          rd=2000 Bytes=28000)

My conclusion is that the CBO is being used (since I am seeing Cost=2 Card=2000
Bytes=2800).

I want to implement some IOT's for database lookups. If part of the system is
RULE (enforced at DB level) and the rest is COST (enforced at session level)
then I am concluding this is a dangerous thing to do. Any comments?

COST is enforced at the session level as we slowly convert RBO modules to the
CBO.

          Followup:

could be dangerous, yes.

IOT related queries are only done by the CBO.   It can be tricky.  You may have
to manually hint some of those queries -- remembering to REMOVE the hints once
you start analyzing.

      Rating:
      3
  June 20, 2003
Reviewer:  Skip
       from AR USA
Is it possible to create an IOT in parallel?

          Followup:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/c22paral.htm#4281

      Rating: 5
Any practical
      limit...  June 20, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Hi Tom,
Is there any, so called, upper limit on how big your IOT should be. Can you have
a IOT table for virtually any sized normal table?

Are there any size considerations when creating IOT tables that if the table is
going to be bigger than a specific size, better not make it an IOT? Or any table
is a candidate for an IOT regardless of its size?
Thanks,

          Followup:

there are practical limits to everything.  I'm not sure I want a single segment
larger then somewhere between 5-10gig but only for administrative reasons.
Partitioning would become a tool I would want at that point.

Any time the features of an IOT are appealing, it is a candidate for use in
place of a heap organized table, regardless of size.

It is more the "row width" that would be of a concern.  If I had to make use of
overflow segments, I would think about that and the ramifications of that AND
BENCHMARK IT to make sure it made sense.

      Rating: 5
IOT vs.
      Heap  July 18, 2003
Reviewer:  Alvin
       from Philippines
All the queries that used the primary key in the predicate runs 4x faster but
what bothers me is this.

 SELECT DISTINCT (destination)
            FROM rtbetelephony.cdr_heap
           WHERE groupexchangecode = '1'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        6     74.71     126.23     269602     288478          4          74
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        8     74.71     126.23     269602     288478          4          74

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 48

Rows     Row Source Operation
-------  ---------------------------------------------------
     74  SORT UNIQUE
 167803   TABLE ACCESS FULL CDR_HEAP

********************************************************************************

 SELECT /*+ FULL */ DISTINCT (destination)
            FROM rtbetelephony.rtbetelcalldetailrecords
           WHERE groupexchangecode = '1'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        6     91.14     388.54     280524     290636       3672          74
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        8     91.14     388.54     280524     290636       3672          74

almost the same number of physical i/o but runs 3x slower. and it has more
current reads. When i created the table via ctas it raised an error saying that
i should put the "overflow" in the ddl statement. I think its because of this.

01429, 00000, "Index-Organized Table: no data segment to store overflow
row-pieces"
// *Cause: No overflow segment defined.
// *Action: Add overflow segment.

We're using 8.1.7.0 on an solaris 5.8. It's not patched up could this be what's
causing the above error ?

          Followup:

insufficient data to diagnose anything, heck I've no idea WHAT I'm looking at --
why the full hint is there, how to reproduce, nothing -- nada.

tell you what -- in a week or so, when I get back from vacation, feel free to
post this as a question -- there is insufficient data (like I've no idea what
these two tables look like, where they came from etc)

if you have my book "expert one on one oracle", read chapter 10, see what you
are waiting on in the second query, looks like IO waits and the disks the second
table is on is just "slower" then the first.

      Rating: 4
Create table
      as.....  July 18, 2003
Reviewer:  Sagar
       from India
Hi Tom,
    Pls forgive me if this question is not relevant to this thread....but I have
had this doubt for many days...
When we want to create a table based on the structure of some other table w/o
copying the records, we normally use

CREATE TABLE T1 AS SELECT * FROM T2 WHERE 1=0;

Now does this actually go and check the where condition for each row...so if
there are 1M rows, won't this be an expensive operation.
Is the following statement better the first one

CREATE TABLE T1 AS SELECT * FROM T2 WHERE ROWNUM = 0

Please let me know what exactly happens in the above 2 statements

Thanks in advance
Sagar

          Followup:

the optimizer sees the totaly false predicate and stops.

      Rating:
      5
rule  July 18, 2003
Reviewer:
      A reader
Hi Tom
In the first post, how come the optimizer is RULE
- yet it says "Cost=" etc? I thought if you see
"Cost=", it should be a CBO?

Thanx!
"select * from app_status_set;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=RULE (Cost=2 Card=2000 Bytes=2800
          0)

   1    0   INDEX (FAST FULL SCAN) OF 'JSS_IOT_PK' (UNIQUE) (Cost=2 Ca
          rd=2000 Bytes=28000)
"

          Followup:

what is app_status_set

if it is an iot, table with non default degree of parallelism, partitioned,
having a domain index, ...... only the CBO can be used.

      Rating: 5
IOT vs Heap and
      Overflow blocks  July 21, 2003
Reviewer:
      Alvin  from Philippines
Sorry for the above posts.

The said table RTBETELCALLDETAILRECORDS is defined as follows...

(
  CALLDETAILRECORDID       NUMBER (21)   NOT NULL,
  GROUPID                  NUMBER (10)   NOT NULL,
  BRANDID                  NUMBER (10)   NOT NULL,
  USERID                   NUMBER (10)   NOT NULL,
  ACCOUNTID                VARCHAR2 (256),
  BRANDPRODUCTID           NUMBER (21)   NOT NULL,
  TIME                     NUMBER (21)   NOT NULL,
  ANI                      VARCHAR2 (256),
  DNIS                     VARCHAR2 (256),
  DESCRIPTION              VARCHAR2 (256),
  ORIGINATINGHOST          VARCHAR2 (256),
  TERMINATINGHOST          VARCHAR2 (256),
  DURATION                 NUMBER (21,2),
  CHARGEABLEDURATION       NUMBER (21,2),
  DESTINATION              VARCHAR2 (256),
  FAXPAGES                 NUMBER (21),
  FAXBYTES                 NUMBER (21),
  BRANDCHARGEABLEDURATION  NUMBER (21,2),
  DIALSTRING               VARCHAR2 (256),
  ROAMINGCHARGE            NUMBER (21,6),
  CALLCHARGE               NUMBER (21,6),
  GROUPCALLCHARGE          NUMBER (21,6),
  BRANDCALLCHARGE          NUMBER (21,6),
  GROUPNAME                VARCHAR2 (256),
  BRANDNAME                VARCHAR2 (256),
  USERNAME                 VARCHAR2 (256),
  CARRIERID                NUMBER (21),
  CARRIERNAME              VARCHAR2 (256),
  GROUPSERVICEID           VARCHAR2 (256),
  GROUPPRODUCTID           NUMBER (21),
  GROUPPROFIT              NUMBER (21,6),
  BRANDPROFIT              NUMBER (21,6),
  INITIALBRANDBASERATE     NUMBER (21,6),
  INITIALGROUPBASERATE     NUMBER (21,6),
  SESSIONID                VARCHAR2 (256)
     CONSTRAINT RTBETELCDR_NN   NOT NULL,
  ACCOUNTTYPE              VARCHAR2 (256),
  ACCOUNTBATCHNUMBER       NUMBER (21),
  GROUPEXCHANGECODE        VARCHAR2 (256),
  BRANDEXCHANGECODE        VARCHAR2 (256),
  ORIGINATINGHOSTID        NUMBER (21),
  TERMINATINGHOSTID        NUMBER (21),
  TERMINATINGGROUPID       NUMBER (10),
  TERMINATINGGROUPNAME     VARCHAR2 (256),
  GROUPSERVICENAME         VARCHAR2 (256),
  BRANDSERVICENAME         VARCHAR2 (1024),
  ERRORCODE                VARCHAR2 (256),
  BRANDSERVICEID           NUMBER (21),
  BILLINGRECORDID          NUMBER (21)   DEFAULT 0,
  UNITPRICE                VARCHAR2 (2056),
  BRANDUNITPRICE           VARCHAR2 (2056),
  GROUPUNITPRICE           VARCHAR2 (2056),
  LOCAL_ID                 VARCHAR2 (256),
  CURRENCY                 VARCHAR2 (256),
  LOCALCALLCHARGE          NUMBER (21,6),
  RATEVSDOLLAR             NUMBER (21,6),
  EXCHANGECODE             VARCHAR2 (256),
  GROUPTRANSACTIONFEE      NUMBER (21,6),
  SYS_PROCTIME             NUMBER (21)   DEFAULT 0,
  SYS_PROCEND              NUMBER (21)   DEFAULT 0,
  RTBECLIENTIP             VARCHAR2 (256),
  TERMINATINGANI           VARCHAR2 (256),
  TERMINATINGDNIS          VARCHAR2 (256),
  ORIGMEDIASRCADDRESS      VARCHAR2 (256),
  ORIGMEDIADSTADDRESS      VARCHAR2 (256),
  TERMMEDIASRCADDRESS      VARCHAR2 (256),
  TERMMEDIADSTADDRESS      VARCHAR2 (256),
  GROUPANTICDR             NUMBER (21)   DEFAULT 0,
  BRANDANTICDR             NUMBER (21)   DEFAULT 0,
  USERANTICDR              NUMBER (21)   DEFAULT 0,
  GROUPCHARGEABLEDURATION  NUMBER (21,2),
  SERIALNUMBER             NUMBER (21),
  CONSTRAINT RTBETELCDRS_CDRID_PK )

When i try to create this table oracle tells me that i should include the
'overflow' in the CTAS Statement. Is it because if i total the bytes allocated
per row it will exceed the default pctthreshold 50 on a 8kb block db.

I was attributing the excess time spent querying the db on looking for the
overflow blocks.

          Followup:

that does not look like a good candidate for an IOT at all.  too fat.

      Rating:
      4
clarification  August 23, 2003
Reviewer:
      Reader
from the doc, "Index-organized tables are suitable for accessing data by the
primary key or any key that is a valid prefix of the primary key." What does any
key that is a valid prefix of the primary key mean? Thanks.

          Followup:

if the table was:

create table t
( a int,
  b int,
  c int,
  d int,
  primary key(a,b,c)
) organization index;

a,b,c
a,b
a

are all valid "prefixes" of the primary key a,b,c

      Rating: 5
inverted
      index  August 23, 2003
Reviewer:  Kumar
       from Atlanta
I have read somewhere that IOT is useful in applications that use 'inverted
indexes'? What does an inverted index mean? Thanks.

          Followup:

it is an "inverted list" type index.

like "word" to "document" type indexes.

really a bitmap index like Oracle Text indexes are better -- there you can have
a "word" to "document/document/document...../document" index entry, even more
efficient.

Normally -- to "index a document", you might:

create table docs ( id, document )
create table docs_words ( word, id, primary key(word,id) );

there you have this redundant table docs_words -- that you never really use --
just to have the index.

it is when you can easily do away with the table and just have the index that
IOT's shine.

      Rating:
      5
Thanks.  August 23, 2003
Reviewer:
      Reader

August 25, 2003 raj -- Thanks for the question regarding "trim in
      sql*plus", version 8.1.6

      You Asked
Hi Tom,
I have a varchar2(30) field which when displayed on sqlplus, doesn't seem to be
trimming the trailing spaces when I use rtim or trim in select stmt:

set head off
set colsep ","
set trim on
set wrap off
set linesize 800
select part_id,
    'CATEGORY',
    rtrim(part_desc),
    serial_num
from parts

      345,CATEGORY,MOTOR BELT             ,031-14-024144

What could be wrong?
Thanks,
Raj

      and we said...
It is trimming it -- its just that sqlplus formats the results into columns
based on their widths.  sqlplus is a reporting tool -- it likes to print things
"pretty".

If you want them squashed together, you would:

select part_id || ',CATEGORY,' || part_desc || ',' || serial_num from parts;

See

http://osi.oracle.com/~tkyte/flat/index.html
for a simple tool that'll help you do this.
Review &
      Followup

      Rating: 4
linesize below
      80  March 28, 2002
Reviewer:  korada

using sql plus formatting commands i am writing query data to a flat file.

there i gave linesize as 300(set linesize 300). now when the result data line
size becomes below 80 its adding blank spaces at the end of the text

these are the contents of sql file

set heading off
set termout off
set verify off
set feedback off
set linesize 300
spool &1..dat
select
'ForCompany|XXXXX|XXXXXXXXXXXXX|S|'||nvl(em_id_c,'<LLNULL>')||'|1|'||
nvl(title_c,'<LLNULL>')||'|<LLNULL>|<LLEOR>|' to_file from em_title where
actv_i='Y';
spool off
set linesize 80
set feedback on
set heading on
set termout on
set verify on

any idea why is this happening

          Followup:

Well, you could just grab my flat script -- it does all of this for you.

All you need to add is:

set trimspool on

to trim the output.

      Rating: 5
unloading data
      in the form of insert statements  May 23, 2003

Reviewer:  A reader
Hi Tom,
I used your utl_file method to unload data in CSV format. Works excellently! Do
you a similar script that would unload data as a bunch of insert statements that
could later be called from a sql script to load data back in?
For example, instead of:
123,John Smith
234,Jane Smith
...

I have a need to see:
insert into employee(emp_id,emp_name) values (123,'John Smith');
insert into employee(emp_id,emp_name) values (234,'John Smith');
...

Any help you can provide?
Thanks.

          Followup:

not a chance, wouldn't do it.  it would kill the recieving system.

I do unload a sqlldr format file for easy reload though.

      Rating: 2
This is for
      very tiny tables  May 23, 2003
Reviewer:
      A reader
Tom, thanks for your reply. We are not talking massive data here.. just a
handful of records that get loaded as part of static, reference data during
initial schema creation at the time of system deployment. I have a bunch of sql
scripts that create the initial schema and I just thought it would be easier for
me to integrate a sql file with a few insert statements rather than call sqlldr.
Thanks.

      Rating:
      4
  August 24, 2003
Reviewer:  gre

Tom,
I have a script to spool a table to a flat file written by a programmer before.
The the set commands in the script  are very simalr  with yours.But he has
a command  " set space 0 " . What is this command means here ? I could not find
this set command in the oracle dcoument. (I guess it probably was used in oracle
7 ,if so
we have related set command in later versions to handle this if necessary)

Thanks !

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a90842/apc.htm#634746

August 25, 2003 Ravi -- Thanks for the question regarding "What
      is async IO ?", version 815

      You Asked
Dear Tom,

Sorry for asking a unix question in this forum. But  the answer
to this question will be useful to many readers like me.

What exactly is the sync IO or async IO in solaris.

From where to where the DBWR copies when the async IO is enabled

but IO slaves is not configured in init.ora

From where to where the DBWR  and IO slaves copies when the
async IO is enabled and IO slaves is configured in init.ora

From where to where the DBWR and IO slaves(dbwr's) copies when
the async IO is disabled but IO slaves is configured in init.ora

Thanks
Ravi

      and we said...
DBWR must write blocks to the datafiles and ensure they get
written.  These blocks are protected by redo in the oneline redo
log.  Before we can reuse a redo log file, we must ensure that
the blocks protected by that redo are safely and totally written
to disk.

DBWR can do this in one of two ways.  Lets say DBWR has 100
blocks to write to disk.  It could:

   for i in 1 .. 100
   loop
      write block X to disk and wait for the OS to tell
      us the operation is totally complete (the block is
      not buffered by the OS or anything, it is on disk)
   end loop

As you can imagine, that would be slow.  For each block, we
would have to wait for the OS to tell us the block is written.
If there was any contention on that disk (eg: another process
was reading /writeing that disk), we would have to wait event
longer.

Another way is to write the block to disk and not wait for the
OS to tell us the block has been written.  rather, the OS will
"call back" to us and tell us that block X was written.  The
logic might look like this:

  for i in 1 .. 100
  loop
     write block X and return immediately, don't wait
  end loop

  now wait for the 100 blocks to "call back" to us, let the OS
  do its work and wait.

In this case, we give the OS all of the work we want done and
let it schedule it as fast as it can.  While it is off doing its
thing, we are submitting yet more blocks to be written.  We can
get some parallel processing going on here and things go faster.

When the OS does not support async IO, we can use mutliple
processes to mimick it.  DBWR will use the slaves to do the
syncronous writes and use many of them simultaneously.  When the
slaves are done, they call be to DBWR just like the OS would.
Review &
      Followup

      Rating: 3
Shows alot of
      waits  June 16, 2002
Reviewer:  Nihal
       from s'pore
Hi,
what will be the remedy if the waits r 2 high..???

          Followup:

r2 d2, star wars questions -- cool. (either that or your keyboard is broken).

fix the reason behind the waits of course.  As there are literally dozens and
dozens of potential waits, it would be quite hard to tell you what to do (other
then read the server reference, which lists the waits -- describes them, giving
you information you can use to then reduce them).  Eg: if the wait is for IO,
how would you fix that?

o redistributed IO over more devices
o look at the query to see if it is efficiently processing data
o look at the buffer cache to see if it is sized right
o etc....

      Rating:
      5
  June 17, 2002
Reviewer:
      A reader
Hi Tom,

 I am a developer and learing oracle DBA stuff.
 I am really confused with SALVE word. What is that?
 As per above question DBWR SLAVES,what is that?

 I know there is DBWR process that will write blocks from buffer cache to disk
whenever checkpoint(CKPT) occur.

 Please explan about SLAVE process.

 Thanks

          Followup:

A salve would be a soothing medication you spread on a wound ;)  Guess you mean
"slave" in all cases.

dbwr slave is a dbwr process under the total control of a master dbwr process.
It does the real work, the master process (the coordinator) just tells it what
to do, when to do it, how to do it.

      Rating:
      5
  June 17, 2002
Reviewer:
      A reader
Hi Tom,

 Now i understood about DBWR SLAVE process.
 another question.

 Accouring to your reply DBWR process is coordinator and SALVES are the
processes which are doing real work.

 1)Does that mean there is always DBWR SLAVE process in the database for DBWR
process?

 2)How many DBWR SLAVE process we can have in database?

 I know there is DBWRnnn Parameter in init.ora file.

 3)MULTIPLE DBWR SALVE process will distribute dirty buffers write to disk. Am i
right?

 Thanks

      Rating: 5
Followup to A
      Reader question 17 June 2002  June 17, 2002

Reviewer:  Jim  from Australia
Lets see if I can answer your questions correctly.

1) No, if your server supports asynch IO then
   there would be no point having slaves for the DBWR
   process since slaves are used to simulate asynch IO

2) There is an init ora parameter db_writer_processes
   which controls the number of DBWR processes.
   The maximum number of DBWR processes is 10, most systems
   have 1 DBWR but on large multi CPU machines it is set
   higher.

   If you change DBWR_PROCESSES you should also configure
   DB_BLOCK_LRU_LATCHES. This controls the number of
   LRU (least recently used) list latches. It is recommended
   each DBWR process have it's own list.
   There is a seperate parameter for SLAVES  called
   DBWn_IO_SLAVES.

3) Yes the DBWR or it's SLAVE writes
dirty blocks from the buffer cache to disk.

Multiple DBWR are more efficient then SLAVES because the buffer cache is
effectively partitioned such that each database writer process maintains a
disjoint set of buffers. The allows the buffer cache processing to be
parallelized across the available database writer processes.
DBWR IO slaves do not parallelize  buffer processing because a single DBWR is
used to allocate work to the I/O slaves. IO slaves I suspect will have a higher
overhead
then multiple DBWRs since there is IPC communication between the Master DBWR and
it's slaves which results in
context switches.

Multi DBWR I believe divide the buffer cache into equal sized groups of buffers
(working sets).

The working sets are assigned to the DBWRs in a round robin
fashion which ensures  each receives a share of the different pools such as
keep, recycle default etc.
The number of working sets is defined by the number of
LRU latches.

I have noticed that a recovery after a shutdown abort is
faster with a single DBWR then with multiple DBWRs
and I haven't worked out why yet. There is no appreciable
difference when starting the database with multi DBWR
after normal or immediate shutdown.

Since I don't work for Oracle I could be completely wrong
but hopefully Tom will comment on what I have said

Hi Tom,

 Now i understood about DBWR SLAVE process.
 another question.

 Accouring to your reply DBWR process is coordinator and SALVES are the
processes which are doing real work.

 1)Does that mean there is always DBWR SLAVE process in the database for DBWR
process?

 2)How many DBWR SLAVE process we can have in database?

 I know there is DBWRnnn Parameter in init.ora file.

 3)MULTIPLE DBWR SALVE process will distribute dirty buffers write to disk. Am i

right?

 Thanks

      Rating:
      5
  June 17, 2002
Reviewer:
      A reader
Hi Jim
 Your explanation regarding DBWR SLAVE is really helpful to understand the
process.

 It is now very clar to me.

 Another question.

 What is the role of DB_BLOCK_LUR_LATCHES in the database?

 Is this parameter(DB_BLOCK_LUR_LATCHES)  protect db buffer cache?  (According
to latche defination(Protect shared data structure)).

 I know it is set to multiple of no. of CPU.

 Can you please explain in depth.

 I would appricate any help.

      Rating: 4
Jim's
      review  February 26, 2003
Reviewer:  Aivar
       from ON Canada
Thanks for good work Tom!
Could you provide comment on Jim's follow up, as whether
his comments are accurate.

Thanks.

          Followup:

looks correct at a quick glance.

      Rating: 5
Jim's
      review  March 02, 2003
Reviewer:  Trevor
      Welch
Jim ...
Very clear explanation

      Rating:
      4
FILESYSTEMIO_OPTIONS parameter  August 24, 2003

Reviewer:  Subramanian  from Houston, TX
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/iodesign.htm#PFGRF015
"With Oracle9i Release 2 (9.2), you can use the FILESYSTEMIO_OPTIONS
initialization parameter to enable or disable asynchronous I/O or direct I/O on
file system files. This parameter is platform-specific and has a default value
that is best for a particular platform. It can be dynamically changed to update
the default setting."

Tom, If my system does not support async io, can i force it using the
aforementioned parameter? Could you please clarify what the doc says on using
this parameter? Thanks.

          Followup:

if your system doesn't do async io, you aren't going to be able to force it to
do so.

That would be like trying to force me to play the piano.  I don't know how, it
just isn't going to happen.

      Rating:
      4
Thanks  August 24, 2003
Reviewer:
      subramanian  from Houston, tX
"That would be like trying to force me to play the piano."

I enjoyed reading your answer. I don't know how to either!

How does the parameter DISK_ASYNCH_IO different from the new 9i parameter.

thanks again.

          Followup:

check out support note

Article-ID:         <Note:205259.1>

for a linux example for example.  different there is treatment of RAW vs
"cooked" filesystems.

      Rating: 5
Why to set both
      parameters to true?  August 24, 2003
Reviewer:
      Reader
I read the support note, to my surprise, it is suggesting to set both to true as
follows. Is this true only with linux? Is this 9i parameter
'filesystemio_options' applicable only for linux? Thanks.

Parameter settings in init.ora for filesystem files:
a) Make sure that all Oracle datafiles reside on filesystems that support
asynchronous I/O. (For example, ext2)

b) set 'disk_asynch_io=true' (note default value is true)

c) set 'filesystemio_options=asynch'

          Followup:

others have it as well.

      Rating:
      5
why?  August 24, 2003
Reviewer:
      John  from Minneapolis, MN
Why do we have to set both 'disk_asynch_io=true' and also
'filesystemio_options=asynch'?

Is there any special with 9i parameter filesystemio_options?

          Followup:

it controls the behaviour of IO against file system files (not raw) is all.  it
was an underscore (undocumented) in earlier releases.

August 25, 2003 Lakshmi -- Thanks for the question regarding "UTC
      CONVERSION USING PLSQL", version 8.1.7

      You Asked
Hi Tom
         I want to convert a time to UTC format( in 8i database). I am aware
that we can write a java stored procedure for this. However I want to implement
this logic using PL/SQL. Can I achieve this thru PL/SQL

Many thanks
Lakshmi

      and we said...
there is a new_time builtin function.

or see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5011677391274#8202740361338

Review &
      Followup

      Rating: 5
UTC
      dates  August 25, 2003
Reviewer:  Lakshmi
       from Bangalore India
Thanks a lot tom !!

August 25, 2003 Gattu -- Thanks for the question regarding
      "unable to extend next_extent", version 7.1.3

      You Asked
Tom,

I have following output, which indicates that i can't extend next_extent. I used
folowing query.

select next_extent,segment_name from sys.dba_segments
where tablespace_name = 'TEST_3';

NEXT_EXTENT   SEGMENT_NAME
---------------------------------
25165824      EMPLOYEE

select segment_name,(bytes/1024/1024) Bytes_MB from sys.dba_segments
where tablespace_name = 'TEST_3';

SEGMENT_NAME  Bytes_MB
----------------------------
EMPLOYEE      1679.80469

Can you please give me your answer to my question. What should i do right now to
get rid of above problem.

Thanks in advance.

Gattu

      and we said...
you have "no problem" that I can see (besides that you are using software that
predates the existence of my daughter who is starting the 3rd grade today!  if
713 was a kid, it would be in the 4th grade)

the first query just says "hey, if employee were to extend, it would want
25,165,824 -- 24m"

the second query says "employee is currently 1.6 gigabytes in size"

soooo, i see no problems uness the tablespace that employee is in does not have
24meg free contigous space (that would be a query on dba_free_space) in which
case, you would just "add more space" -- resize a datafile larger or add another
datafile.
August 25, 2003 -- Thanks for the question regarding "Oracle
      Security Information.", version 8.1.7

      You Asked
Hi Tom,

I am looking for a script , Procedure or a query to extract the information
regarding the security implemented in the database for the all the users.

I want to get all the users along with assigned roles, privileges assigned to
them either through Roles or Directly.

Like Tkyte  Developer,    Insert,               Table1, Table2, ... Tablen
                          Update,              Same for all
                          Delete
            DBA,          Create any table,
                          Drop any table,
                          Grant any role to any user
            Manager4Query Select on any table,
                          Execute any package.

Hope you have got my question. Our database is fairly big. We have about 5000
tables, 1500 Users, about 2000 packages, 1000 Forms, 400 Reports, and lot of
Synonyms,Views, Functions and Procedures.

I need to find every user with assigned role, (Other roles as well that are
assigned to this role), all the privs assigned to the roles, also need to find
if there was any other privs that was assigned directly and all the objects that
the roles and Privs are given.

Other question, if the PUBLIC has been given privs to execute any package,
select any table, would it be enough for the users who need the select priv on
all tables and execute on all packs.

Please help me find the Solution. It would be preferable if I get he query
becuase to create any view or to create any procedure I wold have to get direct
select priv from the DBA on Dictionary Tales. I do have DBA role.

      and we said...
DBA_TAB_PRIVS -- has all of the users/roles and what privs on tables/procedures
                 and such they have

DBA_SYS_PRIVS -- has all of the user/roles and system privs (like select any
                 table and so on) they have
DBA_ROLE_PRIVS-- has all of the users and what roles they have

If public has been given privs to "execute any procedure" and "select any
table", ALL users have this (and I wouldn't bother writing this report as that
would be the MOST insecure database on the planet.  All I need is "CREATE
SESSION" and "EXECUTE ANY PROCEDURE" and I can totally do anything I want to in
your database).

You just need to query these three tables and you'll get all of the information
you ask for.
Review & Followup

      Rating: 4
From what Table
      I can get all The transaction done by User   November 25,
      2001
Reviewer:  Mohammed Al-moayed  from Yemen

hi Tom,
I am  looking for the table that i can get all the transaction done by User like
delete ,Insert ,Update for sepcific Table ,view,..
Or shuld I create it and do Procedure for Saveing all of transaction .

because  in my privileges , I want to save all of User Transaction from login to
logout .

I hope I can Get that table :)

          Followup:

You would enable auditing in the database and turn on auditing on the objects
you wanted to capture this information for.

See the Server Admin Guide

http://technet.oracle.com/doc/oracle8i_816/server.816/a76956/audit.htm#1108
for details on auditing.

      Rating: 4
Security check
      on privileges to Public  August 25, 2003
Reviewer:
      Juancarlosreyesp@yahoo.com  from Bolivia
Hi Tom, do you have a query or a trick that help you to evaluate privileges
granted to PUBLIC.

There is a lot, to check one by one, some of them I don't know if should be or
shouldn't be assigned to public.

Thank you :)

          Followup:

no i do not. i never grant to public so it's never been a priority of mine.

you could install a seed database, get a list of what publics got in your
version by default.  ANYTHING beyond that -- suspect it.

      Rating: 5
Thanks
      :)  August 25, 2003
Reviewer:  A reader

      Rating:
      5
  December 09, 2003
Reviewer:
      A reader

August 26, 2003 Ian -- Thanks for the question regarding "Stored
      Functions", version SQL Plus 3.2.3 on DB 7.3.4

      You Asked
I have written various stored functions to help inexperienced
developers after a recent changes in our underlying database
structure. These functions work well in a Select or Group By
Having clause but can not be used in the WHERE clause as they
cause the select to run very slowly. Any tips?

      and we said...
Well, when used in "SELECT" clause, the function is only
executed when you actually fetch the data -- in the aggregate
clauses -- only after most of the data has been processed.  So,
they run rather rapidly since they process relatively few rows
(or in the case of a select, a few rows at a time while the
client fetches them so any overhead is not noticed).

In the case of a where clause -- they can tend to get executed
once per row in the query -- even if they return a constant.
That is because they might return a different value for each
call (so they call it for every row).

I'd need some examples of what you are trying to do to tell you
exactly how to optimize this but.... I can give a few pointers
for making these run faster.

Lets say you were attempting to parameterize a view or a query.
You had a stored procedure that filled in some package variables
and functions to return these values (or in fact ANY function
you knew only needed to be called ONCE per query -- not once per
ROW per query) you could use the trick below to speed things up.
 Here is an example of what I mean:

scott@ORA734.WORLD> create or replace package my_pkg
  2  as
  3      procedure set_data( p_data in varchar2 );
  4
  4      function get_data1 return varchar2;
  5      pragma restrict_references
  6             ( get_data1, wnds, rnds, wnps );
  7
  7      function get_data2 return varchar2;
  8      pragma restrict_references
  9             ( get_data2, wnds, rnds, wnps );
 10
 10      pragma restrict_references
 11             ( my_pkg, wnds, rnds, wnps, rnps );
 12  end;
 13  /

Package created.

scott@ORA734.WORLD> create or replace package body my_pkg
  2  as
  3      g_data    varchar2(255);
  4
  4      procedure set_data( p_data in varchar2 )
  5      is
  6      begin
  7          g_data := p_data;
  8      end;
  9
  9
  9      function get_data1 return varchar2
 10      is
 11      begin
 12          return g_data;
 13      end;
 14
 14      function get_data2 return varchar2
 15      is
 16      begin
 17          return g_data;
 18      end;
 19  end;
 20  /

Package body created.

So, we have a simple package to return some data from a
function.  the value returned by get_data never changes over the
course of the SELECT call -- it is deterministic in nature.

scott@ORA734.WORLD> exec my_pkg.set_data( 'KING' );
PL/SQL procedure successfully completed.

scott@ORA734.WORLD> alter session set sql_trace=true;
Session altered.
We'll use sql_trace in 7.x to see how many times the plsql
routine is actually called from sql...

scott@ORA734.WORLD> select ename, empno
  2    from emp
  3   where ename = my_pkg.get_data1;

ENAME           EMPNO
---------- ----------
KING             7839

scott@ORA734.WORLD> select ename, empno
  2    from emp
  3   where ename = ( select my_pkg.get_data2 from dual );

ENAME           EMPNO
---------- ----------
KING             7839

When we look at the SQL trace for this, we'll find the the
predicate:
  3   where ename = my_pkg.get_data1;

causes the function to be called once per row in the query
whereas the predicate:
  3   where ename = ( select my_pkg.get_data2 from dual );

caused the function to be called once PER QUERY...

begin :r:="MY_PKG"."GET_DATA1";end;

call     count       cpu    elapsed       disk      ...
------- ------  -------- ---------- ---------- -----...
Parse        1      0.01       0.01          0      ...
Execute     14      0.02       0.02          0      ...
Fetch        0      0.00       0.00          0      ...
------- ------  -------- ---------- ---------- -----...
total       15      0.03       0.03          0      ...

begin :r:="MY_PKG"."GET_DATA2";end;

call     count       cpu    elapsed       disk      ...
------- ------  -------- ---------- ---------- -----...
Parse        1      0.01       0.01          0      ...
Execute      1      0.01       0.01          0      ...
Fetch        0      0.00       0.00          0      ...
------- ------  -------- ---------- ---------- -----...
total        2      0.02       0.02          0      ...

(it should be noted that if we had an index on ENAME, similar
execution profiles would have been seen for both predicates -
the optizer would have evaluated the function ONCE and then gone
to the index for the rows -- this trick applies to UNINDEXed
columns or when the optimizer decides to not use the index on
the column)

Another optimization trick is to keep the complex code in the
PLSQL routine but use DECODE on some of the "easier" conditions
to limit the number of times a routine is called.  For example,
once I implemented some "row level security" in Oracle7 using
this PLSQL called in a where clause feature.  We had three
columns we need to perform some complex logic on and this
function would return 1 if you could see it, 0 if you could not.
 In my case, almost all of the rows could be seen, it was
only when the 3 columns had some value in it we really needed to
call the function.  Since most of the data did not need to go
through this routine, we set up our views as such:

   select *
     from t
    where decode( c1||c2||c3, NULL, 1, f(c1,c2,c3) ) = 1;

That checked first in decode (very fast, native sql) to see if
we needed to even call the function and if not, return 1 else
call the function and return what it returned...  In this
fashion, we noticed almost no perform hit since most of the rows
were done by decode and never called f().

Review &
      Followup

      Rating: 4
Parameterised
      Views and Application contexts...  January 24, 2003

Reviewer:  Matt  from Australia
We used 'parameterised views' quiet intentively in our 7.3.4.0 application and
noticed simliar performance degradation over time. This (your explainiation) is
something like what I thought was happening (that and the optimiser not being
able to identify the PL/SQL function call as a possible bind value.).

We are using PL/SQL functions in the where to return the value of a packaged
global variable with in a view. An example view definition might be something
like

select * from table t_p1
where t_p1.col1 = my_package.my_function()
UNION ALL
select * from table t_p2
where t_p1.col1 = my_package.my_function()
...
UNION ALL
select * from table t_pn
where t_p1.col1 = my_package.my_function()

Now that we have upgraded (9.0.1.4.0) I was going to update this to take
advantage of an application context. A function would set the context once
before any query on the view and the view definition would be changed to access
this context instead of the PL/SQL function. I think that this was I get the
benefit of improved performance (no longer multiple calls to the PL/SQL
function) when querying these views as well as the use of bind variables (the
optimiser is aware of the sys_context() call and treats it as a bind variable).

Is my understanding correct?

Is this approach valid for both the RBO (using currently) and CBO (implementing
this currently)?

Thanks and regards.

          Followup:

Yes, AND you can do this in 734 with:

dbms_application_info.set_client_info( .... );

and then use

where t_p1.col1 = userenv( 'client_info' )

as well - it is "bindable" in 73 with client_info

      Rating:
      3
OK  August 25, 2003
Reviewer:
      R.Saravanan  from Chennai,India
Dear Sir,
Well and wish the same from you.
 I tried to create a function which takes a number and tests
for prime.I don't know to proceed with the code since I am
new to oracle.Could you please help?
I want the function like
 create or replace function test_prime(param in number)
 return varchar2 is
 begin
  if..code which tests the number for prime then
   return 'Number is prime'
  else
   return 'Not prime'
 end if;
end;
I want the code like this.
Thanks
R.Saravanan

          Followup:

lets see -- maybe teach how to fish today....

I googled:

"determine if a number is prime"

found an algorithm and converted from C to plsql and tadah:

ops$tkyte@ORA920> create or replace function is_prime( p_n in number ) return
varchar2
  2  as
  3          l_stop number := ceil(sqrt(p_n));
  4  begin
  5          for i in 2 .. l_stop
  6          loop
  7                  if ( mod(p_n,i) = 0 )
  8                  then
  9                          return 'not prime';
 10                  end if;
 11          end loop;
 12          return 'prime';
 13  end;
 14  /

Function created.

ops$tkyte@ORA920> column is_prime format a10
ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select user_id, is_prime(user_id) is_prime from all_users;

   USER_ID IS_PRIME
---------- ----------
         0 prime
         5 prime
        11 prime
        19 prime
        21 not prime
        30 not prime
        31 prime
        32 not prime
        33 not prime
        35 not prime
        36 not prime
        39 not prime
        40 not prime
        42 not prime
        43 prime
        44 not prime
        46 not prime
        47 prime
        48 not prime

seems to work, but not tested 100%.  if you really need to have a prime
checker, you'll need to do the necessary legwork to validate this
mathematically!

      Rating: 3
but I was told
      that 2 is the smallest prime ;o)  August 25, 2003

Reviewer:  A reader

          Followup:

there you go, that's why I added the caveat about "validate this ok"

it isn't really a database question -- just trying to show how I might have gone
about getting the answer all by myself.

it is a quick fix, no?

  3          l_stop number := least( ceil(sqrt(p_n)), p_n-1 );

      Rating: 5
Define prime
      number = number which is divisible by 1 and itself   August
      26, 2003
Reviewer:  pasko  from HH,Germany

i think Tom is right, if i correctly remember a prime number definition from my
College Days::=
as a :

number which is divisible ONLY by 1 and itself.
divisible, meaning without remainder.

such as:
2,3,5,7,11,13

      Rating: 5
I Went
      Fishing  September 18, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
I find things like prime numbers, pythagorean triples, and perfect triangles to
be interesting, so I did a little fishing based on Tom's code as a starting
point.  I wrote some code that takes a slightly different approach to
determining if a number is prime.  While the code itself is slightly more
verbose, it does the necessary legwork to perform validation and eliminate
numbers which are clearly non-prime early in the logic.  In addition, the
run-time performance seems to be improved.

Sample code from above slightly modified to return number instead of varchar2:

SQL> create or replace function is_prime(p_in in number) return number as
  2    l_stop number := least(ceil(sqrt(p_in)),p_in-1);
  3  begin
  4    for i in 2..l_stop
  5    loop
  6      if (mod(p_in,i) = 0) then
  7        return 0;
  8      end if;
  9    end loop;
 10    return 1;
 11  end;
 12  /

Function created.

SQL> set timing on
SQL> begin
  2    for i in 1..1000000
  3    loop
  4      :v_prime := is_prime(i);
  5    end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

Elapsed: 00:04:24.06

SQL> create or replace package utl_math as
  2    function is_prime(p_in number) return number;
  3  end utl_math;
  4  /

Package created.

SQL> create or replace package body utl_math as
  2    function is_prime(p_in number) return number is
  3      l_sqrt number;
  4      l_sqrt_ceil number;
  5      l_divisor number;
  6      l_divisor_squared number;
  7    begin
  8      -- prime numbers must be >= 2
  9      if p_in < 2 then
 10        return 0;
 11      end if;
 12
 13      -- only integers can be prime
 14      if p_in != ceil(p_in) then
 15        return 0;
 16      end if;
 17
 18      -- 2 is the only even prime, so it is a special case
 19      if p_in = 2 then
 20        return 1;
 21      end if;
 22
 23      -- eliminate all other even numbers
 24      if mod(p_in,2) = 0 then
 25        return 0;
 26      end if;
 27
 28      -- if the sqrt of the number is an integer, the number is not prime
 29      l_sqrt := sqrt(p_in);
 30      l_sqrt_ceil := ceil(l_sqrt);
 31
 32      if l_sqrt = l_sqrt_ceil then
 33        return 0;
 34      end if;
 35
 36      -- the number has passed the basic elimination tests and may be prime
 37      -- loop through set of odd divisors to determine if number is prime
 38      l_divisor := 3;
 39
 40      for i in 1..l_sqrt_ceil
 41      loop
 42        l_divisor_squared := l_divisor * l_divisor;
 43
 44        -- l_divisor is a factor of p_in, therefore not a prime
 45        if mod(p_in,l_divisor) = 0 and l_divisor_squared < p_in then
 46          return 0;
 47        end if;
 48
 49        -- no factor found, therefore number is a prime
 50        if l_divisor_squared > p_in then
 51          return 1;
 52        end if;
 53
 54        l_divisor := l_divisor + 2;
 55      end loop;
 56    end is_prime;
 57  end utl_math;
 58  /

Package body created.

SQL> begin
  2    for i in 1..1000000
  3    loop
  4      :v_prime := utl_math.is_prime(i);
  5    end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

Elapsed: 00:02:52.01

This seems like a decent approach, but anyone see anything obvious that I missed
out?

Thanks,

Mark

August 26, 2003 Raghu -- Thanks for the question regarding
      "Database Performance: Things you should not do?", version 8.1.7

      You Asked
Hi Tom,
I have a general question and I would appreciate if you could share your views.
If someone asks you, "What would be the top ten deadly sins that should be
avoided in an Oracle database?", what would you say? I understand that this is a
very very generic question. Let's say, if this question was directed from a
database performance standpoint, what would be your answer then?

Let me do the honours by answering the first point on top of this list:
1) USE BIND VARIABLES :)

"Top Ten" is just a number. If you could from your experience list even a few
that would be great.

Thanks,
Raghu

      and we said...
Off the top of my head (all from the book actually) of things to avoid:

1) not using bind variables

2) allowing you to have a parse to execute ratio on statements of 1 when it
could be near zero (eg: parse once, execute many)

3) (vainly) attempting to be database independent -- to the point where you
refuse to use sequences, plsql, triggers, decode, analytics, etc etc etc

4) developing on an empty database (and then rolling out to production and
wondering "why does this go slow, works great on my laptop)

5) not testing to scale (see #4, works great on my laptop but throw 10 users at
it and man does it stink)

6) not reading the concepts guide, and therefore not being aware of whats
available to you in the database.  (change concepts to application developers
guide and so on -- eg: NOT READING THE DOCS)

7) getting stuck in DIY (do it yourself) mode.  How many times I've heard --
yeah, I found out about Advance Queues in the database after I already wrote
10,000 lines of code to do the same thing.  My code is *almost working*, help me
get it to work -- instead of scrapping it and using AQ.  Change AQ to
replication, to sequence generation, to <any database technology here> for more
examples

8) pretending we are SQLServer and should behave just like them.  They must have
invented this database thing right.

9) not using the tools available -- every coder should know of TKPROF
(sql_trace+timed_statistics).  dbms_profiler.  statspack.  And they should use
them.

10) not out-lawing a feature due to FUD. Eg: I've seen lots of places where
views are outlawed because of "fear of bad performance with a view".  Geez, give
me a break.  PLSQL outlawed.  Triggers = no way.  IOT's or any feature invented
after version 6  -- no go.
Review &
      Followup

      Rating:
      5
Great  September 22, 2002
Reviewer:
      Raghu
Tom,
Thanks for taking time to answer such a generic question. Very helpful.
Raghu

      Rating:
      3
  August 26, 2003
Reviewer:  Ghulam
      Moinuddin  from Pakistan
Could you explain No 4 in Detail

          Followup:

that full scan of zero records goes really fast

that full scan of a billion records goes slow

so, develop on empty database and expect it to go fast?  probably not.

      Rating: 5
Excellent
      article  August 26, 2003
Reviewer:
      A reader
Excellent article !

August 26, 2003 A Reader -- Thanks for the question regarding "9i
      and SQL Server 2000", version 9i

      You Asked
first of all thanks for this very informative site

Sorry if this is an out of line question but I am curious now…anyway...here it
is

I had an interesting argument with my friend he works as a SQL Server DBA
We were talking about clustering and he said that this feature (real time
clustering) in 9i is there in SQL Server from the beginning.
Also he pointed me to following page

http://www.microsoft.com/sql/evaluation/compare/mythandreality.asp
I know every company says there product is the best but what is the reality

      and we said...
SQL Server has shared nothing clustering.  we have and have had shared
everything.  With sql server, you put the data for department 10 on node 1,
department 20 on the other node.  If you need dept = 10 data, you goto node 1.
need dept = 20, you goto node 2.  With Oracle -- you goto the node you want,
they all access any bit of data, no physical partitioning of data needed nor
desired.

We have supported clustering since the late 80's -- a couple of years before
windows nt was dreamt of, and many years before clustering was even supported by
windows (we are talking windows 386 and windows 286 - how many people remember
those "operating systems") and many years before sql server even existed.

My response to all MS tied in people is the same.  A couple of years (not many)
Unix was "king" and NT stood for not there.  Before that VAX was pretty popular
(brief run on Wang somewhere in there as well).  Before that IBM ruled.  Seems
that every 5 years or so, the platform changes -- what remains constant is
movement to a new platform.  Every platform -- has had Oracle.  So, when the
platform changes (i predicate when, not if) to something else (don't know what
-- linux, maybe, something totally different, maybe) where will those sqlserver
databases be?

There is something to be said for portability -- Ubiquity is nice!

Review &
      Followup

      Rating: 4
what do you say
      about this?  April 23, 2002
Reviewer:  a
      reader  from USA
Tom what do you say about this?
Oracle has reworked its Oracle Parallel Server feature and now calls it Real
Application Clusters. While Oracle emphasizes the scalability this solution
offers for clusters of computers, it does not provide any audited evidence that
Real Application Clusters deliver scalability in such a configuration. At the
same time, SQL Server 2000 holds the top TPC-C results with Distributed
Partitioned Views–based cluster systems.

          Followup:

We run SAP, Peoplesoft and Oracle apps on clustered environments showing great
scalability.  Those are *real* applications (hence the name).

Anyone run SAP, peoplesoft on clustered sqlserver?  No -- you cannot because
they did not design their application to be physically partitioned like that.

Once upon a time ago, there was a benchmark -- tpc-d.  they retired it we got so
good at it.  TPC's are interesting to a degree.  Real world -- I don't think
anyone will argue that (with a straight face).

Have you read the TPC-C spec?  I have, zero percent chance I would design an
application with their rules -- zero.

      Rating: 5
Thank
      You!  April 23, 2002
Reviewer:  chad
       from Salt Lake, Utah
Great reply Tom!  Unfortunately, I work in a mixed environment where we have
Oracle for the back-end and VB 6.0 for the front.  Every day I go through the
same crap with the "Microsoft Group" on how much better SQL Server 2000 is than
anything Oracle has.  I am very limited in my experience with SQL Server, so
it's hard to debate things with them.  Do you have any articles or comparisons
that I could use to quite the mob.  Thanks!

          Followup:

Sorry -- I don't even go there.  For any piece of literature I would point you
at, they would point you at some piece as well.

Databases have always been "religious" with people.  I remember the same level
of "support" for the predecesor to SQLServer -- Sybase.  That didn't get them
very far though.

      Rating:
      4
  April 23, 2002
Reviewer:
      A reader
"Anyone run SAP, peoplesoft on clustered sqlserver?  No -- you cannot because
they did not design their application to be physically partitioned like that."

Not entirely true: I have worked on SAP 4.6 on Clustered SQLServer on MS
Cluster. I am not saying it is a big deal -just pointing out a fact. It was a
shared nothing architecture strictly for high availability. When one node goes
down another takes over. Both access data on a shared EMC array. No dynamic load
balancing etc.. The application(SAP) needs to be aware of the clustering (so
when one node is down it can redirect you). You have to follow SAP
specifications to implement it. I think that is what Tom mentioned when he said
Oracle can run real applications. The clustering is transparent there.

I fully agree that Oracle clustering is on another class itself. You cant
compare it with SQL server clustering. No way!

      Rating: 5
RE: Not
      entirely true...  April 23, 2002
Reviewer:  Mark
      A. Williams  from Indianapolis, IN USA
"A Reader":

Your statement "Not entirely true..." is mixing apples and oranges, I believe.
You seem to be describing a MSCS configuration, which is not the same as
"Distributed Partitioned Views" in SQL Server.  Now, feel free to correct me in
regard to the "DPV", because I never work with SQL Server, but I do have
experience (more than I want, truth be told) with Oracle on MSCS.

With 9iRAC, you can run against a clustered database with no changes to the
application.  As you pointed out, with MSCS, you have to configure SAP to be
"aware".  Again, I don't work with SAP, so I'm going by your statements.

- Mark

      Rating: 5
RE:
      Comparisons...  April 23, 2002
Reviewer:  Mark
      A. Williams  from Indianapolis, IN USA
Chad:

For some comparisons between Oracle and other databases, you may want to check
out these links:

DB2:

http://www.oracle.com/ip/deploy/database/oracle9i/collateral/AMR_oracle9i_wp.pdf
MS SQL Server:

http://www.oracle.com/ip/deploy/database/oracle9i/index.html?sqlserver.html
There's not a ton of technical details, but you may find some of the info
interesting...

Of course, all other databases are inferior by definition, right? :)

HTH,

Mark

      Rating: 4
9i and SQL
      Server 2000  April 24, 2002
Reviewer:  Bob
       from Irvine, CA
Thanks Tom, I too use this site A LOT.  You're the best.  Like some others, I am
in a mixed shop and have worked extensively with Oracle 6, 7, 8i and now 9i, and
during those many years I've worked with MSSQL from version 4.9 up thru 2000
(worked with Sybase 10, 11 & 12 as well).   I have a lot of hind sight, and both
DB's are blindingly fast now, so speed (with a little or lot of tuning) is a
non-issue as far as I'm concerned.  Both have their architectural strengths and
weaknesses.  The most important business concern for me is to build a DB app
that's fast, scaleable, has a long life cycle and is within budget and time
constraints.  One other factor I measure by, I call it the "Headache factor",
and it has to be as low as possible.  My business conclusion after many years is
solely based upon juggling time and resources to get the app in production so
the business can be profitable.  It doesn't matter how much Oracle or MSSQL
senior talent I have on staff to do the job, SQL2000, hands down, has been the
winner EVERY time.  It's really not even close.  And yes, databases is like
religion here, but when the exec's look at the bottom line, the business tools
have to work efficiently, and be cheap.  "A man with an argument is never at the
mercy of a man with an experience."

          Followup:

I was agreeing with you to the very end -- I've walked into more then one
situation where replacing ss2k was the way to go.  I've worked with them all
myself.

      Rating: 5
Oracle for
      Small Apps?  April 24, 2002
Reviewer:  Ken
       from Marietta, GA USA
Tom,

I'm a devoted member of the Church of Oracle, but I have to admit I have
"experimented" with MSSQL.  I have an application in mind that I want to develop
and sell to small retail businesses, 3 to 5 users on the system at a time.  I've
been reading you book and am convinced that putting the app in the database is
the way to go.  I really want to use Oracle for tons of reasons ( %ROWTYPE by
itself is enough when it comes to writing procedures) but it is so expensive.
MS lets developers distribute the core sql engine in their apps for free
provided they use Visual Studio or some other MS tool.

There is no doubt that Oracle is the only choice for big apps.  Do they not want
the small app business? Have they conceded that to MS?

          Followup:

Oracle SE with small numbers of users is very inexpensive.  You are looking at
$1,500 USD for 5 named users.  Thats less then I paid for my last desktop
PC/monitor (last week).

I concede nothing personally.

      Rating:
      4
  April 24, 2002
Reviewer:
      A reader
>RE: Not entirely true...  April 23, 2002
>Reviewer:  Mark A. Williams  from Indianapolis, IN USA

>"A Reader":

>Your statement "Not entirely true..." is mixing apples and >oranges, I believe.

>You seem to be describing a MSCS....

I was not talking about the DPVs. Microsoft provides clustered SQL server
solutions on MS cluster. And SAP supports their application (SAP R/3) on that
platform. The architecture of MS cluster is share-nothing (which is different
from that of Oracle parallel server/RAC). Tom said nobody run SAP on clustered
SQL server and I was just mentioning that is not "entirely true". You can run
SAP on clustered MS SQL indeed, but your SAP application has to be configured
specifically for that.
I have worked on SQL server on MS cluster and OPS on Sun cluster. And I know
that the OPS architecture is a class apart.

-A Reader.

      Rating: 5
RE: Not
      entirely true...  April 24, 2002
Reviewer:  Mark
      A. Williams  from Indianapolis, IN USA
A Reader:

Thanks for the follow-up... as I said, I don't use SQL Server or SAP, so I was
not sure if I was on track or not... but, now I know!

- Mark

      Rating:
      5
  April 24, 2002
Reviewer:  Tianhua
      Wu  from Atlanta, GA
Hi Tom,

I love your comment on NT!  But I thougt NT means "not trustworthy"?

      Rating: 4
Minor Feature
      Set Differences can make a big difference  April 24, 2002

Reviewer:  Jer Smith  from Salt Lake City, UT

Right now, I'm working on a project that's yet to make a final decision between
SQL2000 and Oracle 9i.  Some minor features can make huge differences.  For
instance, in our environment, we're finding that Oracle's XMLtype and more
configurable partitioning (as opposed to making thousands of tables and using
SQL Server's partitioned views) can give us a lot of value.  Microsoft either
isn't working on these, or won't admit they are.  SQL Server does seem to
perform faster on the quality of machine and OS that we're forced to use (Win2K,
2 CPU) but it's those two simple things that make Oracle appealing.

I think the world would really be served if someone had a web page explaining
the real feature differences between the two products...ideally be an
independent party.

      Rating: 5
OK
      people...Oracle or SQL Server ..which is easier to
      learn....  April 25, 2002
Reviewer:
      A reader
Hi Tom and  Everybody in this Database fight discussion...

I think sometimes all this fight comes just because people have lack of
knowledge in the Features / Functionality provided by these DB Vendors...

I have some Oracle Knowledge , but i doubt if there are really people out there
who  know Oracle + MS Sql Server databases 100% ....so in fact one can not
really argue/and/ or Compare the TWO,  if one  doesn't  really have enough
knowledge about BOTH the Databases ..

      Rating: 5
Why is there so
      much difference in Prices?  April 25, 2002

Reviewer:  a reader  from USA
The reason I am asking is this will impact which database to use I personally
want to use ORACLE but clients say if there is this much price difference then
SQL Server---thats what they want

Number of CPUs Oracle9i
Enterprise Edition SQL Server 2000 Enterprise Edition With SQL Server
1 $40,000 $19,999
2 $80,000 $39,998
4 $160,000 $79,996
8 $320,000 $159,992
16 $640,000 $319,984
32 $1,280,000 $639,968

With OLAP or Data Mining

Number of CPUs Oracle9i
Enterprise Edition SQL Server 2000 Enterprise Edition With SQL Server
1 $60,000 $19,999
2 $120,000 $39,998
4 $240,000 $79,996
8 $480,000 $159,992
16 $960,000 $319,984
32 $1,920,000 $639,968

With OLAP and Data Mining

Number of CPUs Oracle9i
Enterprise Edition SQL Server 2000 Enterprise Edition With SQL Server
1 $80,000 $19,999
2 $160,000 $39,998
4 $320,000 $79,996
8 $640,000 $159,992
16 $1,280,000 $319,984
32 $2,560,000 $639,968

Oracle9i Standard Edition and SQL Server 2000 Standard Edition

Number of CPUs Oracle9i
Standard Edition SQL Server 2000
Standard Edition With SQL Server
1 $15,000 $4,999
2 $30,000 $9,998
4 $60,000 $19,996
8 $120,000 $39,992
16 $240,000 $79,984
32 $480,000 $159,968

          Followup:

I seriously question the SS prices and options -- where can I read that myself.

      Rating: 4
take a look at
      URLs  April 25, 2002
Reviewer:  a reader
       from USA
http://www.microsoft.com/sql/howtobuy/production.asp

http://www.microsoft.com/sql/evaluation/compare/pricecomparison.asp

          Followup:

And exactly where do I find the equivalent of our "getting to know" type of
guides that explains what they mean by OLAP and what they mean by data mining
and what they have included in their EE?  Not the one paragraph things (and bear
in mind, I don't have days to research this -- something akin to the Oracle8i
getting to know guide for example)

I know for a fact that the OLAP stuff we have in EE bests their OLAP offering
and the OLAP stuff we have more more is stuff they don't even have in SQLServer
really (are the cubes stored in SQLServer).  Datamining -- I don't think they
have exactly the same definition as we do (in fact, it is not even close).

So, before we can compare, we sort of need the "list" as well.  I would have
thought if they would compare the price, they would have defined some terms and
shown perhaps how the products really stack up in functionality?

I really don't want to go tit-for-tat, but quick question for you -- are
upgrades included with this?  When SQLServer 13 comes out -- do you just "get
that"?  Is this license from them perpetual or does it expire at some point?

Also - have you ever seen a 32 processer SQLServer box (sort of funny to think
about actually)....

      Rating: 4
32 Processor
      SQL Server...  April 26, 2002
Reviewer:  Jer
      Smith  from Salt Lake City, UT
Hadn't until a few months ago...

http://www.unisys.com/hw/servers/es7000/overview.asp
(It runs Linux and Oracle too, but due to a big marketing arrangement with
Microsoft, they don't talk about it)

      Rating: 5
Oracle SE = SQL
      EE  April 26, 2002
Reviewer:  Bala
       from Florida
We should not be comparing the prices of oracle EE with
SQL EE.
The features you with Oracle SE are more than what you
get with SQL EE.

      Rating:
      3
  April 26, 2002
Reviewer:
      A reader
Tom,

In your reply to the Original post, you said:

"With sql server, you put the data for department 10 on node 1, department 20 on
the other node.  If you need dept = 10 data, you goto node 1. need dept = 20,
you goto node 2.  With Oracle -- you goto the node you want, they all access any
bit of data, no physical partitioning of data needed nor desired."

On all the clustered systems I have seen (Oracle and SQL Server), the data
resided in an external disk array (and never on the node). All the nodes can
access the data directly. I do not know if any cluster system will work without
an external disk array (and keep the data in the internal disks of respective
nodes -that sounds silly to me). So could you please explain what do you mean by
physical partitioning in the above paragraph?

If the cluster uses a shared external array do you consider it as
shared-everything cluster or shared-nothing cluster? Also I want to know if
shared-everything and shared-nothing is a property of the clustering technology
(like MSCS or IBM HACMP) lying beneath the application (such as OPS) or is it a
property of the application ie OPS. Could you please clarify what is the
difference between them(shared-nothing and everything)?

Thank you verymuch.

          Followup:

SQLServer uses a shared nothing architecture, much like db2 on Unix (but UNLIKE
db2 on the mainframe).

While all of the disks may be available to every node, they choose NOT to
access the data from every node, they make you goto the node where the data is
to get it.  This way, they do not have to keep the caches consistent.

It also means that you have to design your application specifically to their
architecture and heaven forbid if you need to re-allocate the data.  Suppose
"department 10" gets really big and you need to split it -- that's a physical
rebuild.  You are basically building a really large distributed database.

See

http://dbforums.com/t340728.html
for another description of this.

So, while we all use potentially shared storage -- they choose not to share it
(like ibm on unix, unlike ibm on the mainframe).  We choose to share it.

It is harder for us, easier for you.

      Rating: 5
Where do I get
      information about Shared Nothing and Shared
      Everything?  April 26, 2002
Reviewer:  a
      reader  from LA USA
tom could you give some good URLs for
Share nothing and share everything.

Thank You

          Followup:

Not that I know of personally -- no.  Anyone else?

      Rating: 5
Where do I get
      information about Shared Nothing and Shared
      Everything?  April 26, 2002
Reviewer:  a
      reader  from LA USA
tom could you give some good URLs for
Share nothing and share everything.

Thank You

      Rating: 5
Careful with
      pricing  April 26, 2002
Reviewer:  Jim
      Kennedy  from Portland, OR
One needs to also be careful with the MS pricing.  It can be very complex
because you may have to buy CALs (client access licenses) and they usually are
not included in the price or are included with a base of 5.

Also there is more to a product in price.  Had a reproducable problem that
effected a large client that was caused in a 100% MS environment and could not
get MS to step up to the plate to fix it.  With Oracle I have recieved a special
patch for a much smaller client to the DB in under 4 hours. (once we were able
to rprodcue the problem)

      Rating:
      5
  April 26, 2002
Reviewer:
      A reader
Thanks Tom. The forum you referred to answered it very clearly. I knew that OPS
provides failover and load balancing, while SQL server provides only failover. I
have it confirmed now.

A question to the gentleman who mentioned about 32 nod SQL server? Do they have
one node to access data and 31 redundant nodes waiting to takeover on a failure?
Or they have 32 physical partitions with no failover. Either way this sounds
funny. Not worth the effort.

Thanks again Tom, for clearing it up as day!

      Rating: 5
ashraf
      jamal  April 27, 2002
Reviewer:
      A reader
All above is forget that:

- MS.sql server is mean taht windows server only
- MS.slq mean windows application only
- MS.sql mean lot of security miss
- MS.sql is microsoft...

      Rating: 4
SQL Server -
      Oracle comparison  May 03, 2002
Reviewer:
      Paul  from England
I've used Oracle8i and little of 9i, as well as SQL Server 7.0 and 2000. I'll
try to give a reasonably unbiased comparison between the offerings. Note that
this is mostly a high-level look at the systems. Since I have yet to use
Oracle9i in great depth, some points may be neglected.

Firstly, it is important to consider the target audience of the two systems. I
consider Microsoft's target audience to be primarily those who like an easy to
set-up system that requires little initial tuning/configuration. Notice the
limited install options and the way Microsoft designs most of it's products. SQL
Server Enterprise Manager is also very basic and simple to use.
Oracle's target audience appears to be at a slightly higher level. Although it
is possible to set up a basic database system relatively quickly, the wizards
allow for a much more configurable system. The tools provided with Oracle8i also
assume a higher level of technical know-how.

Consider a couple of points about SQL Server 2000:
No bitmap indexes!
Very basic Enterprise Manager(compared to 9i OEM)
Transact-SQL language less powerful than PL/SQL
Faster database than Oracle on similar hardware/OS
Less scalable
Limited platform support
Lower "HEADACHE factor" (I agree with Bob)

Notes on self-tuning:
Microsoft claims that SQL Server 2000 has self-tuning memory, file sizes, and
I/O. Oracle9i apparently now supports these (I have yet to look into this more
deeply). Great! But, as any experienced IT person will tell you computers are
DUMB, they cannot adapt! For certain cases the self-tuning will work very well,
but once you present the program with a scenario it doesn't know how to handle,
things fall apart. Oracle, and some degree Microsoft, recognises this and thus
allows the DBA to tune the database manually.

So in my final opinion it comes down to features and personal preference. If you
like SQL Server 2000 and it supports what the business needs, then by all means
use it. The same goes for Oracle9i.

      Rating: 5
About
      TPC-C  May 24, 2002
Reviewer:  A reader

Dear Tom,
Why should an application not be designed with TPC-C rules?
Thanks for a great site. I am waiting for your performace book.
Regards,

          Followup:

what are "tpc-c" rules?

      Rating: 5
It's actually
      Windows vs. UNIX(es)  May 24, 2002
Reviewer:
      Randy  from TX
First I'll say I was using SQLServer before I got into Oracle, so I have a
certain respect for what SQLServer can do.  But Microsoft products (all of them)
don't force you to become as knowledgeable as you should to run them--they
assume too much (often you'd rather HAVE to configure something before it'll
work).

The problem most SQLServer people have with Oracle is that it *makes* you know
the database.  Just think of the SQLServer databases sitting out there that just
got installed and never tuned after that.  Oracle's just more configurable.
I've administered both, and there are just *way* too many times I've wanted to
do something that Oracle can do that SQLServer won't.  Even though I sometimes
think "geez, why do I have to do through all this?" with Oracle, it's worth it
because I know why it's doing what it's doing because I told it to.

      Rating: 5
TPC
      rules  May 31, 2002
Reviewer:  A reader

Dear Tom,
Please refer your earlier comment on a followup to this question.
"Have you read the TPC-C spec?  I have, zero percent chance I would design an
application with their rules -- zero."
I wish to understand and appreciate this, please help me.
Regards,

          Followup:

For example, they make it impossible to use a sequence to generate order id's --
they impose serialization around the order id primary key by using a table to
generate them.

      Rating: 5
Own
      test  May 31, 2002
Reviewer:  Harri
       from Finland
Some people thinks that MSSqlserver is faster than Oracle...

Why don't you test it by yourself:

Open two sqlserver's query analyzer sessions(or some another tool) and do this:

create table test (a number)
insert into test values (123)

begin transaction
update test set a=234

--hop to another session and do:
select * from test

...and put the timer on.

Do the same in Oracle and compare results - which one is faster?

          Followup:

excellent example.  great way to show why they "autocommit" (transactions are
not them processing)....

I remember this one cool issue too (it was sybase but basically the same thing)

isql | sed | isql

where the first isql was dumping some data -- sed was creating an update of that
data -- updating the same row.  The second isql was doing the update.

Since an update is/was generally a DELETE + INSERT -- this actually created an
infinite loop.  The update created more data at the END of the table, the first
isql session -- when it got there -- read it and re-read that row.  Read
consistency is a great thing indeed.....

when I had processed over 50,000 updates against a 2,000 row table -- I killed
it and spent the rest of the day trying to figure out "now why the heck would
that happen like that"....

      Rating: 4
comparison of
      Oracle 8i and SQL Server2000  June 12, 2002

Reviewer:  R.V.Reddy  from Bangalore, India

Hi Tom

This article is very very interesting. since I am working on both databases I
red MS online books it says:
SQL Server2000 is 200 times better than Oracle 8i in Performance basis ( cpu
cost, elaspsed time ) even I tested with index and without index with SQL
Server2000 and Oracle 8i(8.1.5) with around 2 lakhs of records. SQL Server2000
is taking less time.

But When I compare same thing with Orcle 9i and SQL Server2000, Oracle 9i is the
best.

Can you compare what are all the best in Oracle 8i and SQL Server200 ( In fact I
got interviewed for this question).

Thanks
Reddy

          Followup:

The author of that book was smoking something funny.

You have my book -- you see when I say something, I show all factors involved, I
show the proof, I show the hard numbers and I make it so you can reproduce it.

Did that author do the same or do they just say things.

200 times -- think about it -- we would need a 200 gHz (giga) cpu just to keep
up with them?  I think *not*.

I would take on sqlserver2k with Oracle7.  In single user mode, I have seen them
"best us" -- but -- how many single user systems do you run in the real world.
Give me 10 or more users -- then see what happens.  Things like non-blocking
reads, unlimited row level locking, highly concurrent operations kill them.

Simple question -- if a user in one session updates a row in a transactional
fashion (meaning you are actually using transactions -- something sqlserver
abhores -- given their DEFAULT and primary mode is "no transactions for you")
and a user in another session tries to select * from that table -- how many rows
per second will that second user get?   Answer: 0.  Oh -- and another question
to ask -- after that second user waits for some indeterminate period of time --
will they get the right answer (an answer that ACTUALLY existed in the database
at some point in time?) -- Answer: most likely NOT.

      Rating: 5
Take a look how
      confident M$ is!  June 14, 2002
Reviewer:
      A reader
Me personaly do not like M$ here is URI.

http://www.theregister.co.uk/content/53/21003.html

      Rating: 3
More Corporate
      Bull from MS  June 26, 2002
Reviewer:
      A reader
IBM=Mainframe
MS=Windows

Oracle=Sum( Mainframe+Windows+Unix...etc..etc )

Doesn't look like a one product company to me.

      Rating: 5
I just had to
      jump in  October 30, 2002
Reviewer:  Dean
      Reynolds  from Nashville, TN
I search this site quite ofetn and when I saw this thread I had to jump in.

I have worked with Oracle from 7.3 and MSSQL from 6 (and every major version of
the two to the latest versions).

I love Oracle and I want that bias noted.

On a MS server, MSSQL almost always (saw the note on 9i, haven't tested it)
outperforms Oracle for a single user on a simple query (including joins, but not
parallel query, etc.).

The reason is because of the architectures.  I can't draw a graph in this
response, but the basic idea is that MSSQL is user focused. I mean that in a
very literal sense.  First-come, first-served.  Oracle certainly provides the
same interest in users, but anticipates(better) that other users may be
interested in this as well.

If you do some load testing (I have) just on a Windows box (very unfair to
compare Windows performance to a good Linux build), you will see an interesting
trend.

Even if it's not notiicable to end users, Oracle has a bigger resource footprint
(overall) for a single user than does MSSQL.  As you add users to your load
testing something else interesting happens.  The Oracle resource footprint
(overall/per user)rises very gradually, whereas the MSSQL footprint sprints to a
larger and larger footprint (overall/per user).  Run a load test with 1, then
300, then 1000 users.  Look at the resource costs(overall/per user).  Oracle
really shines when you look under the hood.  It was built from the ground up as
a multi-user database (also look at simple things like locking architecture over
the last 3 versions of MSSQL to see how mutli-user they expected to be).

ALL MS products are designed for ease of use, period.  Oracle has gotten easier
and easier to use (it will never be easier for a part-time DBA/developer, DBA/
network admin, DBA/ receptionist), but does not sacrifice what it is just to
make some tasks easier.

Talk to experienced DBA's about how easy it is to do real DBA tasks (managing
the database proactively and dynamically) and you'll find it's much easier in
Oracle than in MSSQL.  For one thing, there's very little you CAN'T do in Oracle
thru SQL/PL-SQL if you know what you're doing.  Though MSSQL has opened more
access points for DBA's in recent releases, you are still constrained to some
degree by the GUI interfaces to the database.  Oracle allows you to manage your
database, not just configure it.

I've architected databases and data warehoues in Oracle and MSSQL.  I can tell
you it's a lot faster in MSSQL thanks to the many tools and GUI's provided, but
is not nearly as flexible as Oracle in design options.  Any VLDB on MSSQL is
ugly, a large DW in MSSQL is very ugly.

Security is much better implemented in Oracle than in MSSQL.

You can take your Oracle database with you from platform to platform ( a big
advantage in the Java shops I've worked with), with MSSQL you will only ever be
able to use a MS OS. I would hate to be the one to convince some CIO that MS
will seriously support Linux in the future.

Cost is a complicated issue. Cost in $$$ is always negotiable (even with Oracle)
when you're talking about licenses (keep in mind, when you buy Oracle, you're
done with Oracle, put it on Linux for free, when you buy MSSQL look for the
additional costs (other licenses, etc).  But cost is not only an issue at the
time of purchase.  If your business is based on delivering reliable data to
clients (internal and external), what is the cost if that data is unavailable
or, worse, inaccurate?

It is mythically cheaper to maintain a MSSQL staff than an Oracle staff.  Not
really true many times.  It is a cheaper skill set, though.

The bottom line is, what do you need a database for (today and tomorrow)?

As a database consultant I am asked weekly "What's the best database?"

Depending on my mood, I say Access or MySQL.  That's a stupid answer for a
stupid question.  Figure out what you want to do first.

quick and easy = MSSQL
quick and reliable = Oracle

Almost all of the developers who say MSSQL beats Oracle hands down are .NET
certified and are trying to build a system that rewrites the database in VB or
C# anyway.  They also have a one-sided perspective of what a database is
(usually a simple repository of data).  Unfortunately, programmers (usually
front-end ones)get put in charge of more projects than data architects.

:)

      Rating: 5
I
      agree  November 04, 2002
Reviewer:
      catherine  from UK
I have to say "Dean Reynolds  from Nashville, TN" talks a lot of sense. This is
my view too!!

      Rating: 5
mssql
      newsgroup  November 04, 2002
Reviewer:
      Catherine.  from UK
If you want the equivelant mssql newsgroup argument - interesting read - search
for "sql server and oracle" in the subject line in:
http://support.microsoft.com/newsgroups/default.aspx?ICP=GSS3&NewsGroup=microsoft.public.sqlserver.server&SLCID=US&sd=GN&id=fh;en-gb;newsgroups

      Rating: 5
9i versus CACHE
      5  January 03, 2003
Reviewer:  mo

Tom:

I just got the latest version of Software development magazine and one article
claims that this database CACHE 5 is faster than oracle with large tables.  have
you ever benchmarked against it or aware of it?

<According to InterSystems, Cache 5 performs 300 times faster than Oracle 9i on
a 10-million-row table.>

http://www.intersystems.com/analysts/reviews/klas.html
http://searchdatabase.techtarget.com/originalContent/0,289142,sid13_gci854569,00.html
http://www.infoworld.com/articles/hn/xml/02/09/16/020916hncache.xml

          Followup:

so, umm, where are their industry standard, audited benchmarks -- vs gut feel
cold calls?

      Rating: 4
I can never
      stop laughing at Microsoft !  January 04, 2003

Reviewer:  Terry  from San Jose, CA USA
When I see this type of URL   (
http://www.microsoft.com/sql/evaluation/compare/mythandreality.asp
 ) and I see the .asp at the end I just
know I am moving to the "granny lane" of information processing.  I have done so
much Performance and Scalability Testing over the past several years, especially
during the dot com crazy days on Oracle, DB2, SQL Server, Sysbase, ,Teradata,
MySQL  and others and in every single test scenario Microsoft everything gave up
early.  The Webserver, the database, the O/S it is all small scale period.  I
would choose Oracle Stored Procedures and Mod Plsql any day over any MS Com crap
accessing SQL Server, and guess what Oracle web enablement works out of the box
and has worked for many years.  Nothing Web enabled works out of the box for
Microsoft when it comes to the database.

When Microsoft says they have "Data Center" software that just means the price
not the quality, not the scalability, not reliability.   My most humorous moment
with a "SQL Server Crew" was in Silicon Valley where a famous infrastructure
provider firm was using a SQL Server development team to work on Oracle and they
were trying to do a Distributed Query on Oracle.  The humor was they had never
heard of a database link and were re-inventing something in com/vb and all other
forms of other MS Crap as these guys always do.  I walked in and solved the
problem in 1 minute or less.  My point is not only do most of these guys not
know the answer they don't even know the question.  When life is viewed through
a toilet paper roll you don't see much and that's pretty much how it is at
Microsoft.

      Rating: 4
I Just can't
      stop laughing at Microsoft Further  January 04, 2003

Reviewer:  Terry  from San Jose, CA USA
Further to the Microsoft Techie "dumbo show" in Silicon Valley referenced
earlier.  My second to most ridiculous escapade was catching a group of
Microsoft SQL Server "Gurus" attempting to do relatively simple date math in
Oracle but decided to write a C Program to accomplish their task.  The task was
simple, the date was represented in number as an offset of The Epoch in days
plus the seconds of the day.  Pretty simple task for an Oracle guy in Oracle, I
did it in a single SQL statement (as any Oracle person would)  but for Mr. Sql
Server an emergency MS Tar and a conference call to Redmond, WA resulted in a
brain fart C Program.  Date math is strong in Oracle, not so hot in SQL Server
apparently.  That toilet paper roll is gettin' smaller and smaller.

      Rating: 4
I Just can't
      stop laughing at Microsoft Yet Again  January 04, 2003

Reviewer:  A reader
Tom,
You may even remember this one!  I was contracting at a famous printer company
out of Silicon Valley as an OAS Performance Tuner.  This famous client (who
recently merged with a Houston based Computer Manufacturing company) decided to
go an a wild Microsoft "boon doggle".  The internet site involved was handling
between 600-1000 concurrent requests for 16 hours per day (pretty busy) average
response time less than 1 second.  The MS implementation (com object you know
using Active Server Pages .asp) designed to replace it would not scale beyond 25
users and in fact reverse scaled when more mid-tier application servers were
added.  The site was retested many times by myself and by Mercury Interactive at
huge costs to the client.  Nothing changed except the development costs just
kept going up with no visible return.  These people tweaked the living daylights
out of everything in sight.  The solution actually performed worse when more
mid-tiers and Webservers were added.  The same content was tested using Apache,
MODPLSQL out of the box on the same hardware and it scaled to well over 300
concurrent users with no loss of performance.

This was not about com or IIS vs Apache or Oracle vs MSSQL this was about
building performance architectures and knowing what that performance means from
network to database to schema.  In Microsoft you waste your time  "screwing
around" and tweaking this and playing around all day in the registry looking for
the holy grail as to why your application will not scale to the task at hand.
On Oracle you build and deploy.  That is not to say there are not issues on
Oracle but these issues in my 15 years experience are solvable and that's mostly
what this site and Tom's book is certainly all about.

      Rating: 5
cache
      5  January 04, 2003
Reviewer:  mo

Tom:

I assume you never heard or worked with it before?

Where do you find industry standard audit benchmark tests at? is there some
organizations that do that?

          Followup:

Cache'  the database formally known as MUMPS -- used widely in the healthcare
industry -- and given that I work in Oracle GEH (government, education and
healthcare) I've heard all about it.  Many LEGACY systems are locked up tighter
then a drum in it and they are looking to get out of it.  Yes, I've heard of it.

www.tpc.org -- the Transaction Processing Council.

      Rating: 5
SQL SERVER
      cheaper NOT  January 04, 2003
Reviewer:  Jim
       from Portland, OR USA
Small SQL Server DB with maybe 50 concurrent users and it has to have a 4 CPU
machine with 2 gigs of ram and a whole additional server just to run reports on
the data. (same schema)  The additional server was needed because we couldn't
run users and reports on the same machine without contention.  Of course, when
it replicates (read only) the data daily it locks the tables so users can't even
read the data while that table is being copied.

On another DB with Oracle we never have these problems and there are 300
concurrent users, people running reports and it is a SUN box with 2 CPUs and not
a performance problem.  Of course, readers don't lock writers or other readers.
:-)

      Rating: 5
common....whats
      the bottom line  January 04, 2003
Reviewer:
      canucus  from LA
hey i am not a M$ fan but still ... most of you use M$ windows....dont
you.....lot of companies have implimented M$ solutions very efficiently....
we should give them credit for that....and the bottom line is .... one should
choose there tools as per there requirements and not otherway round.....
well thats what I think....
enjoy.....
tom's fan

          Followup:

but -- given that Oracle runs on windows -- what does that mean to the "what is
common"...

what is common is that Oracle runs on all OS's and SS does not.  And if you look
at the history of OS's -- well, the OS du-jour changes about every 5 years.
Check back in about 3 years and we'll see what's happening then.  Me, I cannot
wait (i personally feel I'm already on the next OS but I could be wrong -- might
be something totally different, but as I sit here missing *nothing* from the
windows environment and the guys I work with are running something different
(mac attack -- they are coming back and you know what?  they are really cool.  I
just cannot bring myself to buy a mac though for some reason ;)

      Rating: 5
mac
      attack  January 05, 2003
Reviewer:  Stu
      Charlton  from Toronto ON Canada
I use a powerbook g4 running Oracle 9iR2 developer's edition... it's a good unix
+ I get the desktop apps if I ever need em.   as good as or better than linux,
imho.  no regrets.

      Rating:
      5
ANSI  January 16, 2003
Reviewer:
      Alexandre  from Brasil
Tom,

Does ANSI tells anything about consistent read being or not ANSI compliance ???

Cause a friend of mine (DB2 DBA) insist that Oracle does not follow ANSI rules
because consistent read is not ANSI compliance...

Is that true ???

Thanks,

Alexandre

          Followup:

Sure it does and yes it is.

First, ANSI has 4 definitions.
Second, we were the first database ever to

a) get sql89 nist certified
b) get sql92 nist certified

and seeing as how nist doesn't do it any more -- that'll be that.  we passed the
tests -- which must be run in isolation=serializable (i know, I was part of the
team that did it for sql92 way back in 1993)

Anyway -- here is a short blurb from my book "Expert one on one Oracle" -- I
spend quite a number of pages on this very topic:

<quote>
The ANSI/ISO SQL standard adds to the concurrency control mix as well. In this
standard, they define four levels of transaction isolation with different
possible outcomes for the same transaction mixes. That is, the same work
performed in the same fashion with the same inputs, may result in different
answers given your isolation level. These isolation levels are defined in terms
of three 'phenomena' that are either permitted or not at a given level. These
phenomena are:

o Dirty read - The meaning of this is as bad as it sounds. You are permitted to
read uncommitted 'dirty' data. This is the effect you would achieve by just
opening an OS file someone else is writing, and reading whatever data happened
to be there. Data integrity is compromised, foreign keys violated, unique
constraints ignored.

o Non-repeatable read - This simply means that if you read a row at time T1, and
attempt to re-read that row at time T2, the row may have changed. It may have
disappeared, it may have been updated, and so on.

o Phantom read - This means that if you executed a query at time T1, and
re-execute it at time T2, additional rows may have been added to the database,
which affects your results. This differs from the non-repeatable read in that in
this case, data you already read has not been changed but rather that more data
satisfies your query criteria than before.

SQL takes these three phenomena and creates four isolation levels based on the
existence, or lack thereof, of the above phenomena. They are:

Isolation Level           Dirty Read    Non-Repeatable Read    Phantom Read
Read Uncommitted    Permitted    Permitted                Permitted
Read Committed                         Permitted                Permitted
Repeatable Read                                             Permitted
Serializable

Oracle supports explicitly two of the above isolation levels as they are defined
- read committed and serializable. This doesn't tell the whole story however.
The SQL standard was attempting to set up isolation levels that would permit
various degrees of consistency for queries performed in each level. Repeatable
read is the isolation level they claim you must be at to get a read consistent
result from a query. Read committed does not give you consistent results. Read
uncommitted is the level to use to get non-blocking reads.

In Oracle, read committed has all of the attributes needed for read consistent
queries. In other databases, read committed queries can and will return answers
that never existed in the database at any point in time (db2 for examlple does
that -- returns answers that never existed ever -- eg, it makes up answers).
Moreover, Oracle supports the spirit of read uncommitted as well. The goal of
providing a dirty read is to supply a non-blocking read; queries that are not
blocked by and do not block, updates of the same data. Dirty reads are an
implementation other databases must use in order to provide non-blocking reads.
Dirty reads are not necessary in Oracle, it neither supports them, nor does it
need them.

In addition to the four defined SQL isolation levels, Oracle provides another
level - READ_ONLY. A read-only transaction is equivalent to a read-only
repeatable read or serializable in SQL92. Updates by a read-only transaction are
not permitted in this mode (other sessions may update data, but not the
read-only transaction), but you get a repeatable read and serializable read,
without phantoms.

.....
(lots more about these isolation levels and how to use Oracle with them chopped
out)

      Rating:
      5
ANSI  January 16, 2003
Reviewer:
      Alexandre  from Brasil
Thanks very much Tom,

Just great,

But, I could not even imagine how DB2 does this, as you said:

" db2 for examlple does
that -- returns answers that never existed ever -- eg, it makes up answers"...

Could you explain a bit more this...

Thanks again,

Alexandre

          Followup:

ok, say you have a 3 "page" or 3 block table.  pretend there is 1 row / block.
assume the values are:

ACCT   BALANCE   ACCT_TYPE
----   -------   ---------
   1       500   savings
   2       100   savings
   1      1000   checking

Obviously -- there is 1600 in the bank there right?  Assume the following
operations and we are using the default DB2 isolation "read committed".  What we
need to do is report back "how much money is in the bank".  At the same time --
we need to process transactions

time       operation by query                 operation by update
------     -------------------------          -----------------------
t1         start query, read block 1
           running total = 500, while
           reading -- we have a shared
           read lock on the block

t2                                            account 1 pulls up to ATM
                                              and tries to take 400 from
                                              checking (block 1) but has to
                                              wait -- query has block 1 shared
                                              locked and we need it exclusive

t3         query finished with block 1, released
           shared lock, moved onto block 2, running
           total = 600

t4                                            we get exclusive lock on block 1
                                              and change the value to 100.

t5                                            we are actually doing a transfer,
                                              so now we get block 3 and add
                                              400 to it.  block 3 has an
                                              exclusive lock on it.

t6         give up block 2, try to move onto
           block 3.  we cannot - so we wait...

t7                                            we commit -- releasing block 3

t8         move onto block 3, read 1400 -- add
           to 600 and report back the answer
           "there be 2,000 in the bank!", hmmm

There are other interesting scenarios -- say you want a consistent read -- a
correct answer.  In db2 -- that is "repeatable read", they achieve that by
LEAVING the shared locks on as they read it.  So, it the above timeline, the
query would have blocked the update until the query finished and committed.
Given that block 2 really could represent a TON of blocks in the example, that
could be a while (no atm transactions for you)...  Or even better if the data
were organized:

ACCT   BALANCE   ACCT_TYPE
----   -------   ---------
   1      1000   checking
   2       100   savings
   1       500   savings

now what happens is:

query starts -- gets and keeps shared lock on block 1 (1000,checking).  Update
starts -- gets x-lock on block 3 (500,savings -- updated to 100,savings) and
keeps it.

query keeps running....

update tries to get block 1 but cannot, query has it locked.  update blocks.

query runs and gets to block 3, cannot get a shared lock.

We now have a deadlock.  In order to get the right answer, we had to lock, but
by locking -- we deadlock.  catch 22 -- what do we do?

This is why db2 supports dirty read (forget about locks, what a waste of time
right?  who cares if you get the right answer, just blow through them).

Excerpt from a IBM redbook on dirty reads:

1.3.2.1 Case 2 - Without Locking

1. User A updates value 100 to 0 into the DB2 table column.
2. User B reads the updated value 0 and makes program decisions based on that
value.
3. User A cancels the process and changes value 0 back to value 100 for the
DB2 table column.

This case shows that, without locking, User B made a wrong program decision,
and because of that the business data in the database is potentially
corrupted’

or repeatable reads:

3.3.1.1 Strategy 1 - Conversation Integrity (Not Recommended)
A commit point is created at the end of the conversation. The lock duration may
include user think time. Therefore, this strategy should not be used in
applications where other users need concurrent access to the tables.
…

Read-only application might get away with this strategy in some cases. S-locks
are normally released before commit point, but an S-locked page or row cannot be
updated by another process and an S-lock may have a very long duration. It may
include user think time.

We do not recommend this strategy even for read-only applications, although it
may be the most convenient one from a programming point of view.’

Oracle does not have the concept of a shared read lock.
Oracle does not need shared read locks to return a consistent result.
Oracle does not have a write block a read (queries are never blocked by updates)
An update is blocked only by an update of the same data (no lock escalation --
as db2 "provides" as a "feature")

It is -- in my opinion -- one of the largest differentiators between the
databases -- their locking and concurrency models...

      Rating:
      5
ANSI  January 17, 2003
Reviewer:
      Alexandre  from Brasil
Just grate...

Tom,

I´ve just searched and did not find at google and ANSI site for a paper or
offical document that describe isolation level just to prove to this friend of
mine (DBA DB2)... cause even reading your answer he is telling that Oracle is
not fully ANSI compliance, and you, as an Oracle member, probably has a biased
opinion...

Could you point me a site that I could find this document to "rub in his
face"...

Thanks a lot...

          Followup:

you can buy it from ansi.

there are books on it -- mine is a pure cut and paste here.

      Rating:
      5
Oooops  January 17, 2003
Reviewer:
      Alexandre  from Brasil
Oooops...

Sorry Tom,

I´ve meant great, not grate...

By the way... when your next book is coming???

      Rating: 3
IBM not
      stupid  January 17, 2003
Reviewer:
      A reader
Tom,

The engineers at IBM can't be that stupid (although the ones at M$ possibly
are). Why did they choose to have a lock manager, rather than making the lock an
attribute of the data?

Is there a downside to the way Oracle implemements row-level locks?

          Followup:

Well, don't really know what to think about this comment.  All I've done, all I
ever do here, is present facts.  You make your own decision.  It should be
obvious from the url where my opinions when stated come from.

So, to the question at hand...

There is a hard way to do things, which is the path Oracle took.

Then there is the easy way to do things, which is the path IBM took.

It is not stupidity here "reader", it is "what is easier".  This locking thing
is the differentiator between databases.  SQLServer -- used to be page based
only -- why?  because they were stupid?  No, Sybase wasn't stupid -- they just
wanted to be quick to market and you know what is easier -- it isn't row level
locking, not when the database manages things at the page level.  It took them a
loooonnnnggggg time to get row level locking - and I would say they still didn't
get it right.

Look at clustering technology -- there is an easy way for the databases to go --
it is called "shared nothing".  Here you have to physically partition your
database and affiliate a node in a cluster with some slice of data.  IBM does
this on unix with db2/udb for unix.  It is impossible to use this software
solution unless you specificially design your application 100% to its
constraints.  Eg: easy for IBM, hard for you

Then, there is the hard way.  It is called shared everything.  Here -- you need
not physically partition the data -- all nodes in the cluster are peers of
eachother.  Your applications just run.  IBM does this on Zeos (mainframe --
os/390).  so tell me, why -- why two implementations of the same technology?
are they "stupid"  or did they have a time to market issue.  One of the
implementations is wrong isn't it?  Or is it?  You tell me.

With our locking and concurrency model -- do you trade off consistency (correct
answers) for concurrency?  no.

do you trade off performance (how many rows per second does db2 give you when it
hits a locked row?) for concurrency? no.

do we force you to resort to dirty reads (which are as nasty as they sound) in
order to get lots of queries going with lots of modifications? no.

do we escalate locks because they are a scarce resource?  no -- never.  In fact
-- straight from the db2 guides:

...
If maxlocks is set too low, lock escalation happens when there is still enough
lock space for other concurrent applications. If maxlocks is set too high, a few
applications can consume most of the lock space, and other applications will
have to perform lock escalation. The need for lock escalation in this case
results in poor concurrency.
....

darned if you do, darned if you don't... (it would be interesting to read the
Adminstration Guide: Performance document for db2 and check out the pages of
information on all of the isolation levels, concurrency considerations,
maxlocks, lock escalation and so forth.  You won't find such documentation for
Oracle since -- well, since you don't really think about these low level basic
things that a database should take care of for you)

Take the facts.
Take the implementations.
Understand them.
Understand their implications.

Then and only then, draw your own personal conclusions.

      Rating: 5
Excellent
      answer  January 17, 2003
Reviewer:  Srinivasan
      Sankar  from Boston, MA USA
Very crisp answer to a very complicated question. I'm in the process of a
proposal to convert the DB environment from SQL to Oracle. This will be very
helpful in my presentation to speak for Oracle

      Rating: 5
Here are some
      comparisons  January 17, 2003
Reviewer:
      Sikandar Hayat Awan  from Pindi Bhattian - Pakistan

Nice discussion in this thread. Here are some white papers addressing comparison
between Oracle and SQL-2000.

http://technet.oracle.com/deploy/performance/content.html

      Rating: 5
another
      link  February 08, 2003
Reviewer:  Kevin
       from Singapore
Here is another discussion on Oracle vs MS SQL.
Stan Sorensen, director of SQL Server at Microsoft provides his views on the
much debated topic.

http://searchwin2000.techtarget.com/originalContent/0,289142,sid1_gci835654,00.html
For me, its a never ending battle. Microsoft's strengths don't lie in building
better products but in aggressive marketing, which perhaps Oracle doesn't do
that well. Its my personal opinion and is not substantiated by any studies or
data. But I feel Microsoft uses its desktop presence to the max. Most of the
Servers would be in Unix but the decision makers (CEO,CIO,CFO) etc all use
windows desktops. They tie up with each and every computer training institute.
They tie up with different companies to use and market their products. e.g. very
recently Bill Gates met CEO of Sheinsei Bank (Tokyo) and assured him of 24 hr
onsite support, if they move to SQL Server (haven't heard Larry Ellison do so).
Needless to say Bank did so and even force its subsidaries to do the
same.(Remember what they did to Netscape, but since Oracle is no Netscape, I
don't see SQL Server surpassing Oracle. But its presence definitely adds value
to the competition and ultimately the end users/developers/dba etc are getting
better and better products in shorter span of time.

      Rating:
      3
  May 27, 2003
Reviewer:
      A reader
---x---
Moreover, Oracle supports the spirit of read uncommitted as well. The goal of
providing a dirty read is to supply a non-blocking read; queries that are not
blocked by and do not block, updates of the same data. Dirty reads are an
implementation other databases must use in order to provide non-blocking reads.
Dirty reads are not necessary in Oracle, it neither supports them, nor does it
need them.
---x---

One of the goals of "dirty reads" could also be to avoid ORA-1555 errors!

What if I want a query, however long-running to always succeed without getting
the dreaded ORA-1555?

i.e. I dont my query to ever go to rollback, just read the data (dirty or not)
as of when the query gets to it.

In other words, isolation level = "read uncommited" in ANSI SQL. SQL Server
supports this, any reason why Oracle doesnt?

Currently, I can never guarantee that a long-running query will NOT get
ORA-1555. How can I give my users/developers this guarantee?

          Followup:

because a dirty read is exactly as bad as it sounds.

I remember using sqlserver.... (sybase but same concept, it was born of the same
"really good stuff")

had one process that read data out, piped to another filter that turned it into
an update, piped to another process that updated the row.

run against a 5,000 row table.
after it updates 25,000 rows in this 5,000 row table I killed it.  anyone guess
what happened?

sqlserver tends to process many updates as DELETE+INSERT.  The insert went to
the "bottom" of the table.  Hence when I updated row 1 -- it added row 5001.  It
was a never ending cycle.

No, we will not ever not provide a consistent, correct read.  Period.

You as a DBA can solve the ora-1555 "problem" by sizing your system as per the
workload it performs.  I can count the unexpected 1555's I've gotten (I've
gotten many as I set up tests to show where/when/how they occur) on one hand.

Dirty read -- yeah, thats the ticket.  You want dirty reads?  Just use this
query:

select dbms_random.random from all-objects;

it will apparently satisfy your needs entirely.  there are apis to generate
random strings as well.

      Rating: 2
I dont
      understand  May 27, 2003
Reviewer:
      A reader
I dont understand your attitude. Are you saying that ANSI SQL has the "read
uncommited" concept for the heck of it? Just de-facto added to the standard
because of something that SQL Server's(or Sybase or whatever)  ability (or lack
rather) to support consistent reads the way Oracle did?

Keeping your anecdote about how Sybase implements update=delete+insert and the
resulting endless cycle aside, Oracle implements update the right way, correct?
You still dont see the advantage, in some situations, that a dirty read would
provide?

How can I size my system so that no SELECT, never, ever gets a ORA-1555? That
would, in all likelihood make me size my RBS very high when in reality it would
not be necessary 99.9% of the time.

That said, why doesnt Oracle implement the read-uncommited isolation level?

Many process, ad-hoc queries, etc, really dont care about read consistency, just
get me the data as of the time it is read. If the block is commited, return it,
otherwise read the block from rollback. Dont "freeze" the database as of the
point in time my SELECT started. That is too drastic.

          Followup:

they loosely define many levels of concurrency in terms of phenomena.

they are characterizations.

they are not even part of the standard really -- when they test, the only one
they test for is serializable -- none of the other have been part of the test --
ever.

I see, will not be able to see, have not seen "dirty read" being good.

You know when I see it -- in informix/sybase/db2 implementations when they are
trying to mimick Oracles non-block read.  Why?  cause without it -- their
application doesn't even begin to run -- they cannot have concurrent reads and
writes.

Tell me -- why would you need dirty read in sybase or MS to do a long running
report?  since they don't have 1555's (since they don't do that), what is the
reasoning behind using a dirty read (beyond getting garbage, wrong, not useful
answers?)  does it save resources in their database (no).  the only reason you
are using it is because your system won't function without it.  Your queries
would either take years to run as they continously get blocked or they would
deadlock with other updates (if you needed a consistent and correct answer and
used shared read locks).

      Rating: 2
Not
      quite...  May 28, 2003
Reviewer:
      A reader
---x--
Many process, ad-hoc queries, etc, really dont care about read consistency, just
get me the data as of the time it is read. If the block is commited, return it,
otherwise read the block from rollback. Dont "freeze" the database as of the
point in time my SELECT started. That is too drastic.
---x---

you have still not addressed the above comment. Yes, I understand all that you
are saying about "writers blocking readers" being bad. Reads should never be
blocked, thats terrible design, I agree.

But at the same time, forcing me to size my rollbacks so as to accomodate even
the longest running query is also not good.

My system already has enough RBS to support the largest transaction it has. But
occasional queries still get 1555, because, I guess, they just happen to execute
during periods of high db activity. But high db activity shouldnt be a reason
for SELECTs to get 1555, right?

Bottom line: what do you recommend I do to eliminate 1555? Like I said, my rbs
is already sized to handle the largest tranasction. Beyond this, what can I do?

          Followup:

I don't agree with you even a teeny bit.

I've not met the process that really wants to read the same row 15 times in a
single query execution(more then possible in "read committed" and "dirty read"
in sqlserver/db2/informix)

I've met more then one process that when confronted with this reality goes "oh,
you mean that can really happen"

I don't have to address everything.  I don't agree with your premise even a
little.

We don't "force you" to do anything.  We do things in a certain way.  You either
size the system properly -- or -- you don't.  That is your choice.

what do I recommend?  I recommend the only solution -- learn that RBS is not
just about rolling back.  Learn that RBS is an integral, vital, crucial, not to
be treated as something to "minimize" part of Oracle.  Learn that reading the
same row 15 times in a select when it should be read once is something to be
embarassed about, not something to demand in a product.

      Rating:
      5
uh  May 28, 2003
Reviewer:  Stu
      Charlton  from Toronto, ON Canada
"My system already has enough RBS to support the largest transaction it has"

and

" But occasional queries still get 1555, because, I guess, they just happen to
execute during periods of high db activity"

are conflicting statements.

Size your RBS so that you don't get 1555's at peak loads. Why is this such a bad
thing?  Is disk space really that expensive?  Perhaps your priorities are
different, but data accuracy usual plays a more important role than the
relatively minor amount of disk storage required for RBS.  I wouldn't trade them
in for a dirty read any day.

      Rating: 5
How large
      systems are running on SQLServer database...  May 28, 2003

Reviewer:  Kamal Kishore  from New Jersey, USA

The mere thought that other databases FORCE you to do a dirty read in order to
continue processing in reasonable amount of time, is Freighting.
As said by earlier poster, I would not sacrifice this for saving few extra MB of
disk space.
But, It makes me wonder as to how these databases are running some of the high
volume production systems without any performance issues?
Why is their user community not complaining about the slowness of (or,
non-responsive) database?
How are they able to pull it off with such dreaded monsters (like the dirty
read) embedded deep into their systems?
It makes me wonder???

      Rating: 4
Another
      Approach to the Question  May 28, 2003
Reviewer:
      A reader
The discussion so far as been around features and price for the two products
(ORACLE and MS's Equivalent).

Coming from a finance background with an intermediate SQL understanding, my
concern is obtaining data as quickly and as easily as possible.

How can I do this?

First, where do I find employees that know whatever DB you are deciding to go
to?  How much do they cost between the two systems?  What's the availability of
good people?

If the purpose of the database is provide timely information, then you need
people who know how to use it.

Second, what is the expected development time between the two systems? Again,
how is this going to get done?

Third, how much do I have to spend today, so I don't have to spend more
tomorrow?

Finally, what if you are not able to upgrade as often as you would like, is this
still a viable solution?

If an IT person were to come to me with a request to spend cash on a system,
those are the questions I would want answered in as straightforward a manner as
possible with as much supporting info that I could understand or be explained to
me.

      Rating:
      2
  May 28, 2003
Reviewer:
      A reader
1. Read committed: committed here refers to data commited _before_ my SELECT
statement started. Instead, if this definition is changed to include data that
is commited as and when the SELECT statement processes it, whats the down-side?
In other words, before the SELECT statement returns a data block/row, check if
this data is commited or dirty (uncommited). If committed, fine. If not, get the
corresponding data from rollback. Why wont this work?

2. ORA-1555: No transaction in my system ever gets 'unable to extend segment %s
by %d in tablespace RBS' so I know my RBS is sized properly. I took the largest
transaction, added 50% to it and created the RBS tablespace. Created as many
segments as needed to accomodate my concurrent active transactions and
everything is fine.

But how exactly do I size in a methodical fashion to avoid 1555? The problem, if
I understand it right, is not one large transaction that is blowing my rbs, it
is many small transactions! So, how can I size for this? Thanks.

          Followup:

1) read committed in Oracle does what you describe.

read committed in sqlserver, db2, informix, et. al.  means the "changed
definition".  read committed does not MEAN what you say it MEANS.  ANSI
describes isolation in terms of phenomena and transaction boundaries.  NOT in
terms of statements.

2) no, you are wrong.  You have a fundemental misunderstanding of the database
you are managing.  You believe falsely that RBS is about WRITE transactions.

I'll say it simply "it is about reading and writing".  If you get 1555 -- then
BY DEFINITION YOUR RBS IS IMPROPERLY SIZED.

You are using the wrong metrics.

You size your rbs such that it takes longer to wrap around and reuse an extent
then it does for your longest running query to execute.  In 9i, this becomes
somewhat simple with the undo retention period -- you don't size rbs, we do.
you tell us how long to retain undo.

      Rating: 5
Forget about
      these comparisons  June 24, 2003
Reviewer:  Get
      out while you can  from Oracle
Let's just talk about how to get your data out of Microshaft, and into Oracle.

I have a program that generates and executes:

1) Oracle Create table syntax from SQL Server tables
2) MS's bcp commands to export all desired tables to flat files
3) Oracle SQL*Loader syntax to suck all that flat file data into the Oracle
tables it just created.

: to get yourself out of MSSS2k, and into Oracle

There are a few prerequisits, and assumptions, but if you know what you're
doing, you'll figure it out.

Tom - Do you mind if I post such a script, or would you rather I not?

          Followup:

2 things --

o generic connectivity, you can just create table as select from ms.

o the oracle migration toolkit (on otn.oracle.com) does this

if you have a link -- to your scripts, thats great (but they would be too large
for here)

      Rating: 3
Moving from
      Oracle to DB2  July 08, 2003
Reviewer:  Ralesh
      Yougard
Dear Tom,

We are in the brink of migrating from Oracle to DB2 UDB. Oracle Corp just does
not seems to serve our need as IBM does, in terms of service. We have taken a
look at Migration Workbench, but there is not an option to go to BD2. You would
you suggest ?

Your feedback will be very much appreciated.

          Followup:

you are kidding right?

If IBM is such a better servicer of the customer, as you seem to be suggesting
they are, they would be the ones to provide a tool to migrate.  I'm sure they'll
be more then willing to send in an army of global services people to help you
out.

So, where have we fallen down on service that you think IBM will better serve
you?

      Rating: 3
This must be a
      joke  July 08, 2003
Reviewer:  Wendy
       from Mason, OH USA
I can't believe there is anyone in their right mind who would move FROM Oracle
TO DB2.
If it is not a joke, your headaches are about to increase tenfold.  The amount
of work to manage a DB2 database alone is monumental.
Forget about OLTP and reporting in one database.  The indexing strategy and
optimization paths cause your database to 'lean' one way or the other (queries
vs update/insert).  You will lose the flexibility and forgiveness of the Oracle
environment.
If you indeed make the switch, it will be interesting to hear how you feel about
your decision 6 months from now.

      Rating: 4
ask the DB2
      experts about dynamic sql  July 08, 2003
Reviewer:
      Jim  from Portland, OR USA
Maybe they have fixed this, but I doubt it.  I worked with DB2 years ago on a
mainframe and our application did dynamic sql (with bind variables).  Dynamic
sql to DB2 is sql that has not been bound to a plan before hand.  This means
that the optimizer in DB2 has to generate an explain plan each time (regardless
of using bind variables or not).  Since DB2 uses page level locks everyone using
DB2 on the mainframe would serialize behind that user.  They could not insert
another explain plan - which is an entry in a table because that page was locked
(due to the insert)They would be blocked from generating an explain plan until
your session issued a commit statement. (thus forcing things to be auto commit)
The poor Cobol programmers had fits because they could not compile their
programs until someone issued a commit after a select.(any dml actually)  It was
a mess and did not scale well.

So ask the DB2 expert to describe in detail how dynamic SQL works in DB2 and
make sure he/she understands that you do not want to run in autocommit mode.

      Rating: 5
Should Oracle
      provide the tools?  July 08, 2003
Reviewer:
      Kamal Kishore  from New Jersey, USA
I wonder if people expect Oracle to provide tools and software to be able to
migrate from Oracle to other vendor databases?

I think if Oracle does provde such tools (not saying that it should), such
customers will clearly see that how easy it was to be able to migrate (thanks to
Oracle supplied tools) but then how hard and diffcult it became to manage the
migrated database (thanks to other vendors database and support).

I think that would win such customers back.

I do not know if this qualifies as good humor, but...
:-)

      Rating: 3
To
      DB2  July 09, 2003
Reviewer:  Ralesh Yougard

Dear Tom, IBM teams have always been commited to our work in other projects, as
well as we outsource them. Whereas Oracle is ok in general, IBM is always around
helping us. I think the Oracle Database is very good, but IBM's support is much
better. The question about OMW is only a personal curiosity. They can help us
alright. We don't like Oracle support, in general so distant. You're right, IBM
sends an amry if we need. Many thanks.

          Followup:

well, IBM global services do Oracle development even more then DB2 development
(meaning most of their projects are on systems that run Oracle).

If you would like oursourcing, we do that as well.

You are confusing things here.  IBM global services are not *db2 support*
anymore then Oracle consultants are Oracle support.  You'll have consultants
going through the same support chain for Db2 support.

Anyone will send you an Army if you send them money.

      Rating: 3
IBM will send
      you an army ( you bet )!!!  July 09, 2003

Reviewer:  Alex  from NY USA
Ralesh,

If we need IBM to be around you all the time what your IT(MIS or whatever you
call it ) is doing  ? Of course that IBM will send you people that's their
biggest business. But if you guys think that you will need baby sitting all day,
then just close the IT and outsource the whole thing to IBM . Oracle provides
the best database technology( my opinion ) not just bunch of consultants, but
somebody have to read the documentation coming with the database  !

Regards,
Alex

      Rating:
      3
DB2  July 09, 2003
Reviewer:  Ralesh
      Yougard
Dear Mr. Tom,

If anyone can send in an army for money, then Oracle does not like money.
Because we have tried to have people here and they just were not up to IBM's
service level. So we made them not come back any more.

      Rating:
      5
  July 09, 2003
Reviewer:
      A reader
Mr Yogurt,

You seem to be from a "take-my-money-and-tell-me-a-story" type company. Oracle
is probably not good for you, it for people who can understand and appreciate
the differece. You are looking for a bunch of consultants who will tell you
stories and put you to sleep. You certainly have made the right choice with IBM.

Goodluck!

      Rating: 3
9i and SQL
      Server 2000  July 15, 2003
Reviewer:  Venky

In Oracle i know how to delete duplicate records.

Can anybody from SQL-Server show me how to delete duplicate records from a table
using SQL statement. I tried so hard but with no success.

      Rating: 5
9i versus CACHE
      5   July 25, 2003
Reviewer:  Alex  from
      Riverside, CA
<According to InterSystems, Cache 5 performs 300 times faster than Oracle 9i on
a 10-million-row table.>

Are you sure it is 300x faster, not 299x, not 301x but exactly 300 ?

... sometimes I really wonder how people arrive at such figures.

      Rating: 4
SQL VERS
      9I  July 29, 2003
Reviewer:  ashraf

WE should say SQL server hanging hanging slow
we should sqy oracle fast not hanging more opreating systems

      Rating: 5
Migrate to
      Oracle  July 31, 2003
Reviewer:  xw
       from Paris
Hi all,

For those interested in migrating to Oracle, the following url may help :

http://otn.oracle.com/tech/migration/workbench/content.html
You may also get helps from PTS (Partener Technical Services) which has a lot of
migration experience.

best regards,

      Rating: 3
analytics in
      sql server  August 26, 2003
Reviewer:  Asim
      Naveed  from Pakistan
I have read that analytic functions are standard SQL /99.

But i couldnt find analytic functions in SQL of SQL SERVER.
Am I right or analytics are not standard SQL.

Thanks

          Followup:

there is no vendor, anywhere on this world, that is even nearly compliant with
the monolithic thing that is called "sql" anymore.

The standard was feasible in days gone way by...

it is so large now that being compliant, well, I just don't see anyone doing it.

they (NIST) do not even run tests anymore like they used it.

analytics are part of the spec somewhere, haven't bothered to read them for
years.

August 26, 2003 alvaro -- Thanks for the question regarding
      "Import and export between dbs with different character sets", version
      Oracle Server 7.3.4

      You Asked
I have a db with us7ascii character set, in this db I can´t
store characters like ñ. For example the word "niño" is stored
like ni?o.
I have another db with WE8ISO8859P1 character set (oracle 8.1.5
enterprise edition on NT). I want to Know if is there any
problems when I going to do an export/import from db (us7ascii)
to db WE8ISO8859P1. How Oracle db with WE8ISO8859P1 character
set
recognize ni?no ???. Export/Import make the conversion without
problems ???

Thanks,

Acueto

      and we said...
the data will remain the same -- it'll still be ni?o since its
stored with 7bits in the US7ASCII database.  The 8bit database
won't add the extra bit.

It will be exactly as if a client using US7ASCII connected to
the WE8ISO8859P1 database and inserted the data directly -- the
client character set is obeyed on the way in and out.  Data will
be converted from US7ASCII to WE8ISO8859P1 on the way in (no
change) and from WE8ISO8859P1 to US7ASCII on the way out (strip
that high bit).

Review &
      Followup

      Rating: 1
What will
      happend with the letter ascii (164 )  March 27, 2001

Reviewer:  Mario Goren mlgoren@bellsouth.net  from
      USA
It is not clear if I will see the letter after import in the other version of
character set.
Is the same with US7ASCII to WE8DEC.

      Rating: 4
It got me
      imp-0016 error  April 10, 2002
Reviewer:
      lisa  from tx
The export file from Oracle 734 is US7ASCII format,our software upgrade to newer
version with Oracle 817 with Oracle9 iAS, the new database created in the new DB
is required to use nls_lang setting "UTF8", When I took the export file
(w/US7ASCII) import into new db (w/UTF8)it gives me imp-0016 error (required
character set conversion (type 1 to 871) not supported.

How do I make the exported dump load into new Oracle 817?
I am aware that they are not the same format, but I can't change them. the old
db dump is US7ASCII, the new db requires the UTF8, so we can use the web brower
feature.

Do you know how do we get by with this ?

          Followup:

Set your NLS_LANG environment variable to AMERICAN_AMERICA.US7ASCII before doing
the import and that'll work.  You must have it set to .UTF8 right now and that
is the cause of the error.

This is a documented (albeit confusingly documented) restriction.  Page 2.53 of
the utilities guide has:

<quote>
Character Set Conversion

The following sections describe character conversion for CHAR and NCHAR data.

CHAR Data

Up to three character set conversions may be required for character data during
an export/import operation:

1. Export writes export files using the character set specified in the NLS_LANG
environment variable for the user session. A character set conversion is
performed if the value of NLS_LANG differs from the database character set.

2. If the character set in the export file is different than the Import user
session character set, Import performs a character set conversion to its user
session character set. Import can perform this conversion only if the ratio of
the width of the widest character in its user session character set to the width
of the smallest character in the export file character set is 1.

3. A final character set conversion may be performed if the target database?s
character set is different from Import?s user session character set.

</quote>

You are hitting the issue in #2, the sessions character set (UTF8) is larger
than 1.  The export data was exported with US7ASCII -- that width is 1.  The
ratio of these numbers is not 1 -- hence IMP cannot do the conversion (and
that's what it is trying to do) -- we have to let the database do the conversion
to UTF8.

Setting the NLS_LANG should clear that right up.  I've tested to ensure this is
so.

      Rating: 4
ora-12712
      error  April 10, 2002
Reviewer:  lisa
       from tx
Thank you for your quick reply, but when I try to change the character in
Oracle817 db (which was UTF8 character set), and got the following error:

H:\Oracle>SVRMGRL

Oracle Server Manager Release 3.1.7.0.0 - Production

Copyright (c) 1997, 1999, Oracle Corporation.  All Rights Reserved.

Oracle8i Release 8.1.7.0.0 - Production
JServer Release 8.1.7.0.0 - Production

SVRMGR> CONNECT INTERNAL
Connected.
SVRMGR> SHUTDOWN IMMEDIATE;
Database closed.
Database dismounted.
ORACLE instance shut down.
SVRMGR> STARTUP MOUNT;
ORACLE instance started.
Total System Global Area                        175650844 bytes
Fixed Size                                          75804 bytes
Variable Size                                   121839616 bytes
Database Buffers                                 53657600 bytes
Redo Buffers                                        77824 bytes
Database mounted.
SVRMGR> ALTER SYSTEM ENABLE RESTRICTED SESSION;
Statement processed.
SVRMGR> ALTER DATABASE OPEN;
Statement processed.
SVRMGR> ALTER DATABASE CHARACTER SET US7ASCII;
ALTER DATABASE CHARACTER SET US7ASCII
                                     *
ORA-12712: new character set must be a superset of old character set
SVRMGR> ALTER DATABASE NATIONAL CHARACTER SET US7ASCII;
ALTER DATABASE NATIONAL CHARACTER SET US7ASCII
                                              *
ORA-12712: new character set must be a superset of old character set
SVRMGR>

          Followup:

No, that's not what I was saying to do.

Set the ENVIRONMENT variable NLS_LANG.  You have it set already, to UTF8

$ setenv NLS_LANG AMERICAN_AMERICA.UTF8

$ imp userid=scott/tiger tables=emp ignore=y

Import: Release 9.0.1.2.0 - Production on Wed Apr 10 12:21:59 2002

(c) Copyright 2001 Oracle Corporation.  All rights reserved.

Connected to: Oracle9i Enterprise Edition Release 9.0.1.2.0 - Production
With the Partitioning option
JServer Release 9.0.1.2.0 - Production

IMP-00016: required character set conversion (type 1 to 871) not supported
IMP-00000: Import terminated unsuccessfully

set it to this:
$ setenv NLS_LANG AMERICAN_AMERICA.US7ASCII

$ !imp
imp userid=scott/tiger tables=emp ignore=y

Import: Release 9.0.1.2.0 - Production on Wed Apr 10 12:22:12 2002

(c) Copyright 2001 Oracle Corporation.  All rights reserved.

Connected to: Oracle9i Enterprise Edition Release 9.0.1.2.0 - Production
With the Partitioning option
JServer Release 9.0.1.2.0 - Production

Export file created by EXPORT:V07.03.04 via conventional path
. importing SCOTT's objects into SCOTT
. . importing table                          "EMP"
......

and do the import, thats all you need to do.  don't touch the database!

      Rating: 3
Encrypted data
      problem  August 26, 2002
Reviewer:  Rahul
       from India
Hi Tom

We have Oracle816 installed and in db we are storing some encrypted password
based on DES statndard which is done at Java end and java program converts a
simple string to UNICODE string and stored in db.

The problem is when we take an export of that schema and import on the same
server which is having UTF8 char set then
after importing all the passwords do corrupted.

I'm doing both operation from server only so I don't think that is there any
char set problem.

Please suggest something.

Regards
Rahul.

          Followup:

sounds like you are using a varchar2 or char to store this BINARY DATA.

That would be an inappropriate type to store encrypted data, use RAW instead and
your worries go away (it is RAW, binary data after all -- you wouldn't store a
date in string or a number right?  same thing here.  Use the right type for the
data)

      Rating:
      4
Alternative  August 27, 2002
Reviewer:
      Rahul  from India
Hi Tom

You are right , we are storing the encrypted passwords in varchar2 type field.
But as I know during export/import Oracle doesn't touch the data whatever is in
the column, so why it is not storing the encrypted data as it was at the time of
export.
And also I would appreciate if you tell me any other workaround because I don't
want to change the datatype of column at this stage.

Regards
Rahul.

          Followup:

You are wrong -- during import/export the data can and often does go through
character set translation.

Unless

a) source database has CHARACTER SET X
b) client running export has their NLS_LANG set to X
c) client running import has their NLS_LANG set to X
d) target database has CHARACTER SET X

you won't get very far.  You do not mention what the character sets where so I
have to assume that somewhere in A, B, C, D -- X changed to Y and the data was
changed.

You have a bug in your program -- that bug is you are storing binary data in a
totally improper datatype.  You should really fix your bug.

      Rating: 4
Agree
      but...  August 29, 2002
Reviewer:  Rahul
       from India
Hi Tom

I am fully agree with you that we are storing encrypted data in wrong datatype.
But I want to tell you one more thing, in one more project we are doing the same
thing but not from Java end rather we are using DBMS_OBFUSCATION_TOOLKIT to make
the password encrypted/decrypted and storing in the varchar2 datatype but this
database never shown this kind of error in imp/exp.
So please clear my doubt that what could be the reason??

Regards
Rahul.

          Followup:

You had the fortunate luck of have the NLS_LANG set on the client equal to that
of the database and the two databases had the same character set.  Period.

Consider this test done on UNIX where the default NLS_LANG is us7ascii:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function crypt( p_str in
varchar2 ) return varchar2
  2  as
  3      l_data  varchar2(255);
  4          l_return varchar2(255);
  5  begin
  6      l_data := rpad( p_str, (trunc(length(p_str)/8)+1)*8, chr(0) );
  7
  8      dbms_obfuscation_toolkit.DESEncrypt
  9          ( input_string => l_data,
 10            key_string   => 'MagicKey',
 11            encrypted_string=> l_return );
 12          return l_return;
 13  end;
 14  /

Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( id int,  x varchar2(80) );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 2, crypt('How Now
Brown Cow') );
1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> host unsetenv NLS_LANG; exp userid=/ tables=t

Export: Release 8.1.7.4.0 - Production on Thu Aug 29 19:35:05 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production
Export done in US7ASCII character set and WE8ISO8859P1 NCHAR character set
server uses WE8ISO8859P1 character set (possible charset conversion)

About to export specified tables via Conventional Path ...
. . exporting table                              T          1 rows exported
Export terminated successfully without warnings.

ops$tkyte@ORA817DEV.US.ORACLE.COM> update t set id = 1;
1 row updated.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> host imp userid=/ full=y ignore=y

Import: Release 8.1.7.4.0 - Production on Thu Aug 29 19:35:05 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production

Export file created by EXPORT:V08.01.07 via conventional path
import done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set
export client uses US7ASCII character set (possible charset conversion)
. importing OPS$TKYTE's objects into OPS$TKYTE
. . importing table                            "T"          1 rows imported
Import terminated successfully without warnings.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> column dump_x format a40
ops$tkyte@ORA817DEV.US.ORACLE.COM> select id, dump(x,16) dump_x from t;

        ID DUMP_X
---------- ----------------------------------------
         1 Typ=1 Len=24: 1a,d2,7,72,8f,3d,f0,dd,77,
           5d,f0,91,53,57,f1,3a,dd,d0,f4,ca,3f,54,6
           ,ac

         2 Typ=1 Len=24: 1a,4f,7,72,3f,3d,3f,59,77,
           5d,3f,3f,53,57,3f,3a,59,3f,6f,45,3f,54,6
           ,3f

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

see how the data is DEFINITELY different.  wrong datatype = bug in your code

      Rating: 5
Conversion of
      some spec. char to UTF8 not correct  April 09, 2003

Reviewer:  Amra Sivcevic  from Des Plaines, IL

Hi Tom,

Our problem is connected to the original question.
We have problem converting data from WE8ISO8859P1 to UTF8
on Sun Solaris 8(64 bit).

Database 1 - Oracle 815 AMERICAN_AMERICA.UTF8 character set.
Database 2 - Oracle 815 AMERICAN_AMERICA.WE8ISO8859P1 character set.

We are supposed to import some tables with European special characters into
Database 1.
We exported Table1 from Database2 using env variable
NLS_LANG=AMERICAN_AMERICA.WE8ISO8859P1
and imported Table1 into Database1 using env variable
NLS_LANG=AMERICAN_AMERICA.WE8ISO8859P1.

We exported Table1 from Database2 using env variable
NLS_LANG=AMERICAN_AMERICA.UTF8
and imported Table1 into Database1 using env variable
NLS_LANG=AMERICAN_AMERICA.UTF8 and got
the same results.

Most special characters appears to be stored properly except special
character (ASCII 132 - European underline qoutes);
Dump of that special character from Dababase 1 shows 239,191,189 what is not
correct. If we use convert function we cannot get 132.

Please can you explain how we'll get properly stored
ASCII 132 from Database 2 into Database 1,
what NLS_LANG we should use on the server and client.

Thanks is advance
Amra

          Followup:

Well -- they are "correct" it is just that you are not "happy" with the results.

Why isn't it "correct", we are using the prescribed, dictated mapping of a
single byte character to a multi-byte character.

      Rating: 5
How to convert
      from RDB to Oracle  June 15, 2003
Reviewer:
      Abdul Wahab  from Jeddah
Dear Tom,
    I have been given a task to migrate the data from RDB running on VAX into
Oracle 8.1.7 on UNIX TRU64 with character set AR8ISO8859P6. FTP was done to
transfer the files(*.rdb) containing the data onto the windows machine.Now how
can i use these files to convert it and store it into to oracle database.To make
the matter worst for me and challenging for you ..the data is bilingual
containing both english and Arabic.
    thanks in advance

          Followup:

(looking forward a followup.... gateways come with ias if you have ias, you
don't buy them separately btw.  many people have them and don't even realize it.
 One windows you DO have the generic connectivity -- via ODBC -- gateway with
the database regardless, so you CAN in fact create a dblink from windows to rdb
on vax given an odbc driver -- anyway)

you have to dump the data out.  Unless you have rdb running on windows, having
those binary datafiles from VAX is less then useful.  It would be like taking
the unix Oracle datafiles and giving them to sqlserver on windows.  They would
be big old blobs of data, nothing more.

      Rating: 5
How to convert
      from RDB to Oracle  June 15, 2003
Reviewer:
      Abdul Wahab  from Jeddah
Dear Tom,
    I have been given a task to migrate the data from RDB running on VAX into
Oracle 8.1.7 on UNIX TRU64 with character set AR8ISO8859P6. FTP was done to
transfer the files(*.rdb) containing the data onto the windows machine.Now how
can i use these files to convert it and store it into to oracle database.To make
the matter worst for me and challenging for you ..the data is bilingual
containing both english and Arabic.
    thanks in advance
P.S please don't ask me to use expensive tools like oracle transparent gateway
and all.

      Rating: 3
An unsupported
      feature  August 21, 2003
Reviewer:  Tamas
      Szecsy  from Hungary
For those who have character conversion/migration problems there is a metalink
note that follows. You could
1. export the data in the old character set
2. go to the new instance, alter the dtabase character set to the old one
3. import the data
4. alter the character set back to the new one.

I did this on Windows 2000 for WEISO859P1 >> EEISO859P2 conversion. It
functioned, though as you might note it in the text below, it is not supported
by Oracle. This is only viable on 8.1.x destination databases and upwords.

Metalink note
===============
NOTE:66320.1
Title: V8: Changing the database or national character set

PURPOSE

To explain how to change the database or national character set of
an existing Oracle8 database without having to recreate the database.

SCOPE & APPLICATION

The method described here is documented in the Oracle 8.1.x documentation.
It is not documented but it can be used in version 8.0.x.

Before using this method it is essential to do a full backup of the
database.

This method does not work in Oracle7.

Note that changing the database or the national character set as described
in this document does not change the actual character codes, it only changes
the character set declaration. If you want to convert the contents of the
database from one character set to another you must use the Oracle Export
and Import utilities. This is needed, for example, if the source character
set is not a subset of the target character set.

RELATED DOCUMENTS

<Note:13856.1> V7: CHANGING DATABASE CHARACTER SET
<Note:62107.1> The National Character Set in Oracle8

Oracle8i National Language Support Guide

CHANGING THE DATABASE OR NATIONAL CHARACTER SET
================================================

In Oracle7 it is possible to update the "SYS.PROPS$" table to change the
database character set. The method is unsupported but it is externally
published (see <Note:13856.1>). It is used by Oracle Installer to install
seed databases.

This method is not legal in Oracle8 because character set information is
stored in many other places in the Data Dictionary besides SYS.PROPS$, for
example with each table column, PL/SQL argument, etc.

In Oracle8 there is another way of changing the database or national character
set. The method uses two commands, which are documented in the Oracle8i
National Language Support Guide:

ALTER DATABASE [<db_name>] CHARACTER SET <new_character_set>
ALTER DATABASE [<db_name>] NATIONAL CHARACTER SET <new_NCHAR_character_set>

The database name is optional. The character set name should be specified
without quotes, for example:

ALTER DATABASE CHARACTER SET WE8ISO8859P1

To change the database character set perform the following steps. Not all
of these steps are absolutely necessary, but they are highly recommended:

SVRMGR> SHUTDOWN IMMEDIATE; -- or NORMAL

<do a full database backup>

SVRMGR> STARTUP MOUNT;
SVRMGR> ALTER SYSTEM ENABLE RESTRICTED SESSION;
SVRMGR> ALTER SYSTEM SET JOB_QUEUE_PROCESSES=0;
SVRMGR> ALTER SYSTEM SET AQ_TM_PROCESSES=0;
SVRMGR> ALTER DATABASE OPEN;
SVRMGR> ALTER DATABASE CHARACTER SET <new_character_set>;
SVRMGR> SHUTDOWN IMMEDIATE; -- OR NORMAL
SVRMGR> STARTUP;

To change the national character set replace the ALTER DATABASE CHARACTER
SET command with ALTER DATABASE NATIONAL CHARACTER SET. You can issue both
commands together if you wish.

The ALTER DATABASE [NATIONAL] CHARACTER SET command will return:

ORA-01679: database must be mounted EXCLUSIVE and not open to activate

- if you do not enable restricted session
- if you startup the instance in PARALLEL/SHARED mode
- if you do not set the number of queue processes to 0
- if you do not set the number of AQ time manager processes to 0
- if anybody is logged in apart from you.

This error message is misleading. The command requires the database to be
open but only one session, the one executing the command, is allowed.

The above method will only work if the old character set is US7ASCII (or if
you change the character set to itself). If the old character set is neither
US7ASCII nor equal to the new character set, the ALTER DATABASE [NATIONAL]
CHARACTER SET command will return:

- in Oracle 8.1.5 and above:

ORA-12712: new character set must be a superset of old character set

- in Oracle 8.0.5 and 8.0.6:

ORA-12710: new character set must be a superset of old character set

- in Oracle 8.0.3 and 8.0.4:

ORA-24329: invalid character set identifier

**************************** INTERNAL_USE ********************************

In Oracle 8.1 an undocumented option INTERNAL_USE has been added to
the ALTER DATABASE [NATIONAL] CHARACTER SET command:

ALTER DATABASE [<db_name>]
[NATIONAL] CHARACTER SET INTERNAL_USE <new_character_set>

If included in the statement this option will switch the character set
verification off allowing any database character set to be changed.

*** Improper use of the option can lead to corruption of the database. ***


Subject | Article | Last Updated (East coast time)
String Stings | article only | Aug 27, 2003 07:52 am
db_keep_cache_size | article only | Aug 27, 2003 07:58 am
How to call asp code in pl/sql and how will the server react to it | article only | Aug 27, 2003 08:06 am
Parsing with Ref cursor | article only | Aug 28, 2003 07:46 am
How to load fixed position file into multiple tables | article only | Aug 28, 2003 07:46 am
Developer/2000 forms(5.0) | article only | Aug 28, 2003 07:47 am
Returning clob to varchar from a stored procedure | article only | Aug 28, 2003 07:48 am
Spooling | article only | Aug 29, 2003 07:45 am
date query | article only | Aug 29, 2003 07:48 am
Statspack Buffer Hit %=8.75 | article only | Aug 29, 2003 07:52 am
reclaim space | article only | Aug 29, 2003 08:03 am
Performance differences between UNIX, Linux, NT, and 2000. | article only | Aug 29, 2003 08:39 am
Procedures, roles and grants | article only | Aug 29, 2003 08:42 am
Problems with forms or libraries that reference a recompiled package spec | article only | Aug 29, 2003 08:58 am
password | article only | Aug 29, 2003 09:08 am
cache hit question. | article only | Aug 29, 2003 09:20 am
Detecting dead sessions | article only | Aug 29, 2003 09:40 am
Standby Database | article only | Aug 29, 2003 10:42 am
partitition key in the query... | article only | Aug 29, 2003 12:46 pm
decode | article only | Aug 29, 2003 03:01 pm
Sort by Age | article only | Aug 29, 2003 03:45 pm
Getting Foreign and Primary Key syntax from the Database | article only | Aug 30, 2003 10:30 am
Archive Log List | article only | Aug 30, 2003 10:44 am
Multiplexing and Mirroring | article only | Aug 30, 2003 10:52 am
Checkpoint in init.ora file | article only | Aug 31, 2003 09:41 am
Asynchronous update after row insertion | article only | Aug 31, 2003 09:46 am
New facility ‘ Combination “we said” and “follow-up” ‘ | article only | Aug 31, 2003 08:37 pm
mail_pkg on p.1233 in 9.2.0 | article only | Aug 31, 2003 11:57 pm
Does RULE based Optimizer takes into consideration the existing statistics? | article only | Sep 01, 2003 08:09 am
removing a datafile from TEMP ts | article only | Sep 01, 2003 08:33 am
Automatic startup & shutdown of database. | article only | Sep 01, 2003 08:35 am
Loading XML file to a table via DBMS_XMLSave.insertXML | article only | Sep 01, 2003 09:29 am
using execute immediate | article only | Sep 01, 2003 12:33 pm
Data Guard | article only | Sep 01, 2003 12:35 pm
Job Scheduling | article only | Sep 01, 2003 05:58 pm
geting ORA-29531 when call to java stored function | article only | Sep 02, 2003 07:16 am
Views perform better? | article only | Sep 02, 2003 07:33 am
Separating Values From One Column into Multiple Columns | article only | Sep 02, 2003 07:33 am
Merge - oracle 9i | article only | Sep 02, 2003 10:45 am
internal - password | article only | Sep 02, 2003 10:47 am
Missing Sequence Number.. | article only | Sep 02, 2003 10:48 am
Get Rows | article only | Sep 02, 2003 06:36 pm
row(s) 1 - 42 of 42
August 27, 2003 -- Thanks for the question regarding "String Stings", version 817
You Asked
Hello Sir,
Table mytab(str varchar2(5),
            num number(7)
            );
insert into mytab(str) values ('100');
insert into mytab(str) values ('100A');
insert into mytab(str) values ('100B');
insert into mytab(str) values ('321');
insert into mytab(str) values ('321A');
insert into mytab(str) values ('11');
insert into mytab(str) values ('11A');
Values in the Str column will always start with a number between
1 and 9 and the last character may be either numeric or alphabetic with only
capital letters allowed.
example
   str  100
        100A
        100B
        321
        321A
        11
        11A

I want to sort these columns on  a numeric sort Order.
So i thought of having another column num and
scan these str and convert them to number as follows
       100  --> 10000
       100A --> 10001
       100B --> 10002
       321 -->  32100
       321A --> 32101
       11 -->   1100
       11A -->  1101
I have the algorithm defined now the implementation is a bit tricky.
probably a trigger may be used here.
I scan the last character if its numeric then return concat with '00'
 if its alphabet then concat with the appropriate sequence number starting from
'01' for 'A' to '27' for 'Z'
Questions 1)select replace('100A','A','01') FROM DUAL
             gives
                   10001
I am getting stuck up on the alphabetic portion how to make the above generic.

 2)Is my approach correct Any suggestions for a better approach for numeric sort
? may be order by decode(....)
 3) Can this be done in sql even if so by using stored functions ?
and we said...
Looks like an MOS or AOC to me.... (going back to my contracting for the govt
days)....

I liked to store them in two fields -- as they are really two separate
attributes.  It is MUCH easier to put together than it is to rip apart!

Anyway, this'll do it:

ops$tkyte@ORA920> select str,
  2
decode(length(replace(translate(str,'0123456789','0000000000'),'0','')),
  3                NULL, str, substr(str,1,length(str)-1) ) ||
  4         to_char(
decode(length(replace(translate(str,'0123456789','0000000000'),'0','')),
  5                         NULL, 0,
  6                         ascii(substr(str,length(str)))-64), 'fm00')
  7    from t
  8  /

STR        DECODE(LENGTH
---------- -------------
100        10000
100A       10001
100B       10002
321        32100
321A       32101
11         1100
11A        1101

7 rows selected.
Review & Followup
Rating: 5
Could you pls explain a little January 03, 2003
Reviewer: A reader
Thanx much for you answer Could you pls explain briefly the steps here.

  Why do y do that ascii  and why that -64.
I am trying to understand your code by running individual peices.
Thanx much

Followup:
first part:

  2
decode(length(replace(translate(str,'0123456789','0000000000'),'0','')),
  3                NULL, str, substr(str,1,length(str)-1) ) ||

the replace(translate()) trick simply

a) turns all digits into 0
b) turns all 0 into nothing

if the result of that is NULL -- the string is just a number (return str)
if the result of that is not null -- return the string minus the last character

so 100 returns 100
   100A returns 100

Next:
  4         to_char(
decode(length(replace(translate(str,'0123456789','0000000000'),'0','')),
  5                         NULL, 0,
  6                         ascii(substr(str,length(str)))-64), 'fm00')

looks at the string again.

If NULL -- then return 0, no digits.

else, knowing that A is ascii 65, B is ascii 66 and so on, we take the last
character and get it's ascii code, subtract 64 from it and that returns 1 for A,
2 for B and so on.

Format that as a 2 digit number with leading zeros and NO blank for the sign
(thats what fm00 does)

Concatenate the two and you have your desired output

Rating: 5
You are unbelievable January 03, 2003
Reviewer: A reader
Thanx once again .
  How do you do that is a mystery.

Rating: 5
Excellent January 03, 2003
Reviewer: li from Vancouver, Canada
Excellent as always!

Rating: 4
number to character January 28, 2003
Reviewer: Abdal-Rahman Agha from Yemen
For the following:

select instr(to_char(9.25),'.') from dual; -- returns 2
BUT
select instr(to_char(9.00),'.') from dual; -- returns 0

how could we let the second one return 2 also?

Followup:
select instr('9.00','.') from dual;

or

select instr(to_char(9.00,'fm9.00'),'.') from dual;

select instr(to_char(9.00),'.') from dual is EXACTLY the same as
select instr(to_char(9),'.') from dual;

the .00 is just there for us humans -- it is not part of a number, it is a
format only.  A number never actually has 9.00 -- it has 9 and nothing more.

Rating: 3
Extract the fraction portion January 28, 2003
Reviewer: Abdal-Rahman Agha from Yemen
Thanks Tom for you response, BUT
  Let us say I have a column of type number, and I need to extract the number
after the decimal point. The length of the number varies, may be 12, or 123409.2
or 999.123 or 123456789.12345, what ever. So, how can I expect to put the format
'fmt....' in the 'To_char' function?

Followup:
select number-trunc(number)
  from t

there you go, everything after the decimal.  don't use a string operation for a
mathematical function.

Rating: 4
Simpler solution January 28, 2003
Reviewer: Mike Madland from Bellevue, WA USA
If all you want to do is sort, why not just:

SQL> r
  1  select str
  2  from mytab
  3  order by translate(str,'0123456789','0123456789'),
  4*
translate(str,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','ABCDEFGHIJKLMNOPQRSTUVWXYZ')

STR
-----
100
100A
100B
11
11A
321
321A

7 rows selected.

Followup:
but the str = translate(str,'0123456789','0123456789')

??

so it doesn't sort right??  you left off something after the comma -- so I'm not
sure of your whole idea but I think you were trying:

ops$tkyte@ORA920> select str, translate(str,'0123456789','0123456789')
  2    from t
  3   order by translate(str,'0123456789','0123456789'),
  4
translate(str,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','ABCDEFGHIJKLMNOPQRSTUVWXYZ')
  5  /

STR   TRANS
----- -----
100   100
100A  100A
100B  100B
11    11
112   112
11A   11A
321   321
321A  321A

8 rows selected.

Now, note that I stuck 112 in there

ops$tkyte@ORA920>
ops$tkyte@ORA920> select str,
  2    decode(length(replace(translate(str,'0123456789','0000000000'),'0','')),
  3                      NULL, str, substr(str,1,length(str)-1) ) ||
  4                   to_char(
  5
decode(length(replace(translate(str,'0123456789','0000000000'),'0','')),
  6                                  NULL, 0,
  7                                  ascii(substr(str,length(str)))-64), 'fm00')
  8             from t
  9  /

STR   DECODE(L
----- --------
100   10000
100A  10001
100B  10002
11    1100
11A   1101
112   11200
321   32100
321A  32101

8 rows selected.

And see where my 112 is vs yours?

Rating: 5
Follow up January 28, 2003
Reviewer: Mike Madland from Bellevue, WA USA
Sorry, my

translate(str,'0123456789','0123456789')

was in my brain actually a
translate(str,'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ','0123456789')

but I posted too quickly I think.

My idea is simply to take the string and sort by the numeric part first then
sort by the character part:

  1  select str,
  2         to_number(translate(str,'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ',
  3                                 '0123456789')) num,
  4         translate(str,'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
  5                           'ABCDEFGHIJKLMNOPQRSTUVWXYZ') str2
  6    from mytab
  7   order by to_number(translate(str,'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ',
  8                                    '0123456789')),
  9             NVL(translate(str,'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
 10*                              'ABCDEFGHIJKLMNOPQRSTUVWXYZ'),CHR(1))

STR          NUM STR2
----- ---------- -----
11            11
11A           11 A
100          100
100A         100 A
100B         100 B
112          112
321          321
321A         321 A

8 rows selected.

If you add an order by on your decode with a to_number:

  1  select str,
  2
decode(length(replace(translate(str,'0123456789','0000000000'),'0','')),
  3                        NULL, str, substr(str,1,length(str)-1) ) ||
  4                     to_char(
  5  decode(length(replace(translate(str,'0123456789','0000000000'),'0','')),
  6                                    NULL, 0,
  7                                  ascii(substr(str,length(str)))-64), 'fm00')
  8              from t
  9   order by to_number(
 10
decode(length(replace(translate(str,'0123456789','0000000000'),'0','')),
 11                        NULL, str, substr(str,1,length(str)-1) ) ||
 12                     to_char(
 13  decode(length(replace(translate(str,'0123456789','0000000000'),'0','')),
 14                                    NULL, 0,
 15                                  ascii(substr(str,length(str)))-64), 'fm00')
 16* )

STR   DECODE(L
----- --------
11    1100
11A   1101
100   10000
100A  10001
100B  10002
112   11200
321   32100
321A  32101

8 rows selected.

We get the same answer. The original poster wanted "to sort these columns on  a
numeric sort Order" and this seems to do just that.

Followup:
yes, that'll work :)

Rating: 4
String Stings January 28, 2003
Reviewer: Rich from NJ
Another way of doing it?

  1  select str,
  2         decode(ltrim(str, '1234567890'), NULL, str || '00',
  3         substr(str, 1, instr(str, ltrim(str, '1234567890')) - 1)
  4         || to_char(ascii(ltrim(str, '1234567890')) - 64, 'fm00'))
  5  from rj
  6  order by to_number(decode(ltrim(str, '1234567890'), NULL, str || '00',
  7         substr(str, 1, instr(str, ltrim(str, '1234567890')) - 1)
  8*        || to_char(ascii(ltrim(str, '1234567890')) - 64, 'fm00')))
SQL> /

STR   DECODE(L
----- --------
11    1100
11A   1101
11B   1102
11C   1103
100   10000
100A  10001
100B  10002
100C  10003
110   11000
110A  11001
110B  11002
110C  11003
321A  32101
321B  32102
321C  32103

Rating: 5
Amazed ! January 29, 2003
Reviewer: ram from india
the way you explain it seems to make it very simple ! thanks for the wonderful
site and book . i am amazed by your knowledge !

Rating: 3
Function returning a leading "<space>". August 06, 2003
Reviewer: ht from California
Tom,
Besides doing a "select ltrim(function_name) from dual", is there a way to
remove the leading " " when running a function that returns a number?  This is
causing issues when setting the results to a ksh env variable.

My ksh script calls other functions that return varchar2s and the leading space
is removed by "set pagesize 0".

Optimally, I would like to return no leading spaces when calling a function that
returns a number without using ltrim in the function call.

Here's my code:

set echo on;
create or replace package testpkg1
as
        function testfunc1
        return number;
end;
/
show errors;

create or replace package body testpkg1
as
        function testfunc1
        return number
as
        l_controlfileseq number;
begin
select controlfile_sequence# into l_controlfileseq from v$database;

        return ltrim(l_controlfileseq);

end testfunc1;

end testpkg1;
/

show errors;

exit;

Here's the results:
select testpkg1.testfunc1 x from dual;

         X
----------
     59290

1 row selected.

select ltrim(testpkg1.testfunc1) x from dual
X
--------------------------------------------------------------------------------
59290

Thanks in advance.

Followup:
short of having your function return a STRING instead?

you can

a) use ltrim to convert to a string implicitly and then remove the default
single space in the front that comes from saving a space for a sign.

b) return a string that is "good" already -- don't return a number.  eg:  return
to_char( l_controfileseq,'fm9999999999999' ) instead.

c) use to_char in the select

ops$tkyte@ORA920> select 1234, to_char(1234,'fm99999999' ) from dual;

      1234 TO_CHAR(1
---------- ---------
      1234 1234

d) use dbms_output instead of selecting it

ops$tkyte@ORA920> set serveroutput on
ops$tkyte@ORA920> set verify off
ops$tkyte@ORA920> set embedded on
ops$tkyte@ORA920> set feedback off
ops$tkyte@ORA920> exec dbms_output.put_line( testpkg1.testfunc1 );
46668
ops$tkyte@ORA920>

as some choices.

Rating: 3
August 09, 2003
Reviewer: A reader
Thanks Tom - that's what I thought.  I'll probably return a varchar2 instead of
a number to avoid having my code break if my <space> gets removed for some
reason.

Rating: 4
August 26, 2003
Reviewer: gre
Tom,

How to extract a number 3.2 to 00320 if the number type
is number(5,2). I use the repacle(to_char(a,'000.00'),'.',null). But there is a
balnk or null behind this. SURPRISE! if i don't want to use function to connect
sub sting ,what is the easiest way ?  (oracle 9)

THANKS!

Followup:
ops$tkyte@ORA920LAP> create table t ( x number(5,2) );

Table created.

ops$tkyte@ORA920LAP> insert into t values ( 3.2 );

1 row created.

ops$tkyte@ORA920LAP> select '"' || to_char( x, 'fm000v00' ) || '"' from t;

'"'||TO_
--------
"00320"

August 27, 2003 Sonu -- Thanks for the question regarding "db_keep_cache_size", version 9.2
You Asked
I am using Oracle 9.2 on Windows 2000 Ad Server. Total RAM on m/c is 4gb.

We have defined db_cache_size = 1gb and trying to define
db_keep_cache_size = 400mb and db_recycle_cache_size= 400mb and getting an OS
error "O/S-Error: (OS 8) Not enough storage is available to process this
command"

I would like to know whether db_keep_cache_size and db_recycle_cache_size gets
allocated from db_cache_size or not.

Manual says max db_keep_cache_size and db_recycle_cache_size are OS
dependent, how can I determine their value for Windows 2000?

Thanks
and we said...
you are running on windows.  convincing windows to use memory is tediously hard.

On windows, you have a single process, this process is limited in the ram it can
use.  You are trying to allocate 1024M + 400M + 400M of cache -- they are all
separate caches.  Add in your shared pool, the amount of ram your USERS will
actually need in their PGA+UGA and you are seriously close to what windows can
do.

You'll need to contact support or look the Oracle OS Admin Guide for windows to
get more details about twisting its arm to let you use the ram.  There are 3 gig
switches in the boot.ini, magic commands to be executed and so on.  I'm just not
familar with windows on that level personally and have no computers running it
currently.

Tell you what -- I'll publish this and let others follow up, see what they say.
If there are any windows users out there running "advanced server" with large
SGAs, tell us what you did and how it is going...
Review & Followup
Rating: 3
db_keep_cache_size/db_recycle_cache_size June 16, 2003
Reviewer: Sonu Sehgal from Boston,MA
Thanks for your advice on setting switches in Boot.ini file.
I refered to Oracle Press Book Oracle9i for Windows 2000 Tips & Techniques
Jesse/Hart/Sale about setting 3gb switch.

Also in their book on Pg35 Fig:2-1 They seem to suggest that
KEEP Buffer Pool(sized by db_keep_cache_size) and RECYCLE Buffer Pool (sized by
db_recycle_cache_size) gets allocated
from common pool sized by db_cache_size
or
DEFAULT Buffer Pool = db_cache_size-(db_keep_cache_size+db_recycle_cache_size)
Is this correct?

Followup:
it is as I said.  db_cache_size + db_keep_cache_size + db_recycle_cache_size =
totally buffer cache size.

they are cumulative.  it is easy to see simply by setting each to say 16m and
seeing the SGA size, then set only the db_cache_size and see the SGA size.

the documentation is clear on this as well, db_cache_size is defined as:

DB_CACHE_SIZE specifies the size of the DEFAULT buffer pool for buffers with
the primary block size (the block size defined by the DB_BLOCK_SIZE parameter).

DB_KEEP_CACHE_SIZE specifies the size of the KEEP buffer pool. The size of
the buffers in the KEEP buffer pool is the primary block size (the block size
defined by the DB_BLOCK_SIZE parameter).

and so on, they are separate caches.

Rating: 3
July 01, 2003
Reviewer: Vipin from New york
Hi tom,

Is it possible to KEEP non standard block data in KEEP pool of buffer cache. The
situation is I have a tablespace which is of block size 16K and I want a full
table scan on any table from this tablespace to be kept in KEEP pool. But my
standard block size of the database is 8K.

Is this possible?

Followup:
there is just a keep pool for the standard size.

seems like a "strange" request.  You can of course alter table cache, which
changes the mechanism by which the blocks are cached after a full scan --
they'll be in the 16k cache of course.

Rating: 3
July 01, 2003
Reviewer: Vipin
The request was not for implementing, it was just to get the info.

So you mean to say that only primary or standard block sized data can take the
advantage of KEEP, RECYCLE pools.

Please correct me if I am wrong.

Followup:
that is correct.

Rating: 5
recycle pool does not need to be that big July 02, 2003
Reviewer: A reader
It may be a complete waste of memory to set db_recycle_cache_size = 400mb, so
big as the db_keep_cache_size. What's point of having such a large recycle pool,
knowing that its buffers are going to aged out quickly? The recycle buffer pool
is better used as a "separation mechanism" to single out those "bad guys" -
mostly infrequently used full table scans that would otherwise flood the
db_cache_size. Just keep a tiny recycle pool, because tables with
buffer_pool=recycle are supposed to be physical reads most often anyway. We
assign them to recycle pool just so that they will not pollute the default pool.

Maybe this is a little off-topic, but what's your take about this, Tom? Thanks!

Followup:
good point, it would be considered "too large"

Rating: 5
How do I decide which objects go into recycle pool? August 26, 2003
Reviewer: Reader
Only the full scan tables are candidates for recycle pool or any other objects?
Thanks.

Followup:
any object can be in the recycle pool accessed via any access method.

Rating: 4
Db_keep_cache_size November 16, 2003
Reviewer: Janson A. Hall from Davidsonville, MD USA
Tom,
   As I understand your comment the db_keep / recycle and default are sperate
caches and memory allocations are summed for SGA.  However, in the book Creating
a Self-Tuning Oracle Database by Donald K. Burleson he states the keep and
recycle are substracted from the default cache. On page60 "Remember as subsets
of the DEFAULT pool, the db_keep_cache_size and db_recycle_cache_size are
substracted from the db_cache_size.".  I only bring this up since this book is
also referenced on the OTN website.

BTW: I tried this and I agree with you

Thanks,

August 27, 2003 Anuj -- Thanks for the question regarding "How to call asp code in pl/sql and how will the server react to it", version 4
You Asked
Hi Tom,
well thanks for the wonder full advice u have gave me last week.
here is my next question for u to solve.
 well i would like to call a page of asp through pl/sql web
cartridge.
 how will i to do  this ?? As the  server need's to know the
extension .asp for the asp page to run.   what all are the
require (in term of server) for me to run asp based code through
pl/sql.  well i was able to solve the problem that i asked u
last we  in asp, but i want to run that complete code in pl/sql
so please advice me as soon as possible.
1. will oracle web application server be able to run asp code
directly without pl/sql.
2. how can i intrelate asp code and pl/sql code. too pass the
variable to each other
and we said...
Well, ASP's can invoke PLSQL easily (they just run ODBC and are
typically logged into a database).

There is an example of ASP calling plsql available at
http://osi.oracle.com/~tkyte/ResultSets/index.html
That shows how to pass variables from from ASP to plsql and back
again.

Now, plsql is running in the database.  It can only call an ASP
via a call to the UTL_HTTP package.  That is, it can formulate a
URL, execute the URL and get the page back.  Let me know if you
need an example of that.
Review & Followup
Rating: 4
Is it possible? April 28, 2003
Reviewer: ko from Dallas, TX 75287
I think your answer is very useful, however, I like to extend the question a
lillte bit. I just wonder if it is possible to call Oracle Store Procedured or
even connect to Oracle database if I use ASP on Windows and call to Oracle on
UNIX. I am familiar with java, it can be done easily, but I do not know about
ASP.

Thanks,

Followup:
ASP is just a fancy way of saying "I'm a programmer who uses the language basic"

Yes, stored procedures can be called, sure.  See
http://asktom.oracle.com/~tkyte/ResultSets/index.html
for an example

Rating: 4
ASP an bind variables August 27, 2003
Reviewer: Sasa from Belgrade, Serbia
Hi,

I talk to our ASP developers about how bind variables are important. Do you
happen to have some examples of using bind variables in ASP?

Followup:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:528893984337#11998104419309

      Rating: 5
Here is an
      example using OO4O  August 27, 2003
Reviewer:
      Chuck Jolley  from OKC, OK USA
Here is an example using Oracle's OO4O.
This uses one of our databases so the sql will need to be changed to work
elsewhere.
It also contains a lot of debug output as a demo.

< %
   Response.Buffer = true
   Response.Expires = -255
   Response.Expiresabsolute = Now() - 2
   Response.AddHeader "cache-control","no-store"
   Response.CacheControl = "no-cache"

   'what are we looking for?
   dim v_acctid
   v_acctid   = trim(request("acctid")) 'posted into us
   dim src_str
   src_str = "select a.acct_no acct_no, a.tax_type tax_type, a.tax_year
tax_year, a.name1 name1 "
   src_str = src_str + " from tax.accounts a "
   src_str = src_str + "where a.acct_id = :acctidparm" 'NB no semicolon

   'Do our Database thing
   dim varOraSession
   dim varOraDatabase
   dim varOraDynaset

   ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
   ' In Global.asa under Sub Application_OnStart put:
   '      Application("tax_OO4O_DB_Name") = "DB alias"
   '      Application("tax_OO4O_User/PW") = "username/password"
   ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

   'From Global.asa:
   response.write("<br>" + "New Connection:" + "<br>")
   response.write(Application("tax_OO4O_DB_Name") + "<br>")
   response.write(Application("tax_OO4O_User/PW") + "<br>")

   'Create Database Objects
   Set varOraSession  = Server.CreateObject("OracleInProcServer.XOraSession")
   Set varOraDatabase =
varOraSession.OpenDatabase(Application("tax_OO4O_DB_Name"),
Application("tax_OO4O_User/PW"), 0)

   response.write("<br>" + "From Oracle Connection:" + "<br>")
   response.write("OO4O Version: " + varOraSession.OIPVersionNumber + "<br>")
   response.write("DatabaseName: " + varOraDatabase.DatabaseName + "<br>")
   response.write("Oracle Version: " + varOraDatabase.RDBMSVersion + "<br>")

   response.write("<br>" + "Searching For: " + "<br>")
   response.write(src_str  + "<br>")

   'bind acctidparm to v_acctid and create result set
   varOraDatabase.Parameters.Add "acctidparm", 0, 1
   varOraDatabase.Parameters("acctidparm").Value = v_acctid
   Set varOraDynaset  = varOraDatabase.CreateDynaset(src_str, &H4) '&H4 = Read
Only

   'get the data
   varOraDynaset.Refresh

   'Show it
   response.write("<br>Result:<br>")
   do while not varOraDynaset.EOF
      response.write("Acct_no: " + varOraDynaset.Fields("acct_no") + ",  ")
      varOraDynaset.MoveNext
   loop

   Set varOraSession = nothing
   Set varOraDatabase = nothing
   Set varOraDynaset = nothing
   v_acctid = ""
   src_str = ""
% >

      Rating: 5
Why
      ASP/COM/VB/etc. at all?  August 27, 2003
Reviewer:
      Mark A. Williams  from Indianapolis, IN USA
Not looking to start a flame war or offend anyone, etc., but why use
ASP/COM/VB/etc in the first place?  Something like mod_plsql provides an
extremely elegant and easy interface.  The following code builds on Chuck's OO4O
sample from above...  I used the all_objects view as follows to create the
'accounts' table:

create table accounts
(
  acct_no number,
  tax_type varchar2(18),
  tax_year number,
  name1 varchar2(30)
);

alter table accounts add constraint accounts_pk primary key (acct_no);

alter table accounts nologging;

insert /*+ append */ into accounts
select object_id, object_type, object_id, object_name from all_objects;

commit;

alter table accounts logging;

create or replace package demo as
  procedure print_account_info(p_acct_no in accounts.acct_no%type);
end;

create or replace package body demo as
  procedure print_account_info(p_acct_no in accounts.acct_no%type) is
  begin
    htp.htmlOpen;
    htp.headOpen;
    htp.title('Print Account Information Sample');
    htp.headClose;
    htp.bodyOpen;
    htp.tableOpen('BORDER=3');
    for c_acct_info in (select   acct_no,
                                 tax_type,
                                 tax_year,
                                 name1
                        from     accounts
                        where    acct_no = p_acct_no)
    loop
      htp.tableHeader('Account No');
      htp.tableHeader('Tax Type');
      htp.tableHeader('Tax Year');
      htp.tableHeader('Name');
      htp.tableRowOpen;
      htp.tableData(to_char(c_acct_info.acct_no));
      htp.tableData(c_acct_info.tax_type);
      htp.tableData(to_char(c_acct_info.tax_year));
      htp.tableData(c_acct_info.name1);
      htp.tableRowClose;
    end loop;
    htp.tableClose;
    htp.bodyClose;
    htp.htmlClose;
  end;
end;

Perhaps it is because I am not a developer by trade, but code like that seems
easier to read at the very least, and, for the topic at hand, eliminates the
'how do I use binds' question.  Plus there are *many* other benefits to using
mod_plsql.

Again, not looking for a flame war, and perhaps this is not the best forum for
such a discussion, but just curious about why so many people seem to use VB-type
development tools/environments and "do it on the client"...

- Mark

      Rating:
      5
  August 27, 2003
Reviewer:  Chuck
      Jolley  from OKC, OK USA
We are a Windows + Front Page + ASP shop for web stuff.
Not much I can do about that.

I work on internal programming and DB design.
Creating web pages is not my main job.(Thank God!)
I don't even use OO4O in the production pages (all 4 of them) that I am
responsible for. ;)
I just wrote that while playing around with OO4O.

How do you get the code from the package to display on a page?
Chuck

      Rating: 5
Hi
      Chuck  August 27, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Hi Chuck,

> We are a Windows + Front Page + ASP shop for web stuff.

That is the essence of my question I guess.  I just wonder how it is that
companies make the decision to go that direction.

> How do you get the code from the package to display on a page?

When you install and configure mod_plsql you can configure things known as Data
Access Descriptors - they are very easy and intuitive.  Most of the
configuaration is done for you during the install.

A sample URL for the example I posted would be:

http://testserver/pls/webdemo/demo.list_account_info?p_acct_no=12345
The '/pls' is a virtual path defined on the Oracle webserver that maps to
mod_plsql.  It is automatically configured during the install.  'webdemo' is the
DAD (Data Access Descriptor), 'demo' is the package, and 'list_account_info' is
(surprise) the procedure within the package body.

I have not played with OO4O much at all, though I have tinkered with it a tiny
bit.  I am a "production" DBA, so most of my day is not spent coding...

Thanks,

Mark

August 28, 2003 Manoj -- Thanks for the question regarding
      "Parsing with Ref cursor", version 8.1.6

      You Asked
Hi Tom

We are using reference cursor to return result set for functions which are
called from java program. In the function, we open a refernece cursor with some
select statement. (And return that result set through that cursor.) Since there
are 30 + procedure frequently called,  We are getting low parse hit ratio for
those statements, presuming statements are being reparsed due to use of ref
cursor.

1) Is my assumption correct that use of ref cursor is expensixe ?
2) Do you suggest a better approach.

Appreciated as usual and thanks in advance.

      and we said...
Ref cursors will cause AT LEAST a soft parse each time, yes.  It is unavoidable
-- normally PLSQL can "cache" cursors for us and avoid the soft parse but with
ref cursors -- it must open them each time.

"expensive" is a relative term.  If the alternative is putting the logic into
the application, then this is not expensive.

If the system is to be the highest of highly scalable systems, this might be a
performance hit that is not acceptable.

You can remove most all of this hit by using session_cached_cursors.  Consider:

ops$tkyte@ORA817.US.ORACLE.COM> create table t ( runid varchar2(15), name
varchar2(80), value int );
Table created.

ops$tkyte@ORA817.US.ORACLE.COM> create or replace view stats
  2  as select 'STAT ' || a.name name, b.value
  3        from v$statname a, v$mystat b
  4       where a.statistic# = b.statistic#
  5      union all
  6      select 'LATCH ' || name,  gets
  7        from v$latch;

View created.

ops$tkyte@ORA817.US.ORACLE.COM> column name format a40

ops$tkyte@ORA817.US.ORACLE.COM> declare
  2      type refcur is ref cursor;
  3      rc refcur;
  4      emp_rec emp%rowtype;
  5  begin
  6      execute immediate 'alter session set session_cached_cursors=0';
  7
  8      insert into t select 'before', stats.* from stats;
  9
 10      for i in 1 .. 100
 11      loop
 12          open rc for select * from emp where rownum = 1;
 13          loop
 14              fetch rc into emp_rec;
 15              exit when rc%notfound;
 16          end loop;
 17          close rc;
 18      end loop;
 19
 20      insert into t select 'after 1', stats.* from stats;
 21
 22      execute immediate 'alter session set session_cached_cursors=10';
 23
 24      for i in 1 .. 100
 25      loop
 26          open rc for select * from emp where rownum = 1;
 27          loop
 28              fetch rc into emp_rec;
 29              exit when rc%notfound;
 30          end loop;
 31          close rc;
 32      end loop;
 33
 34      insert into t select 'after 2', stats.* from stats;
 35  end;
 36  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> select a.name, b.value-a.value run1,
c.value-b.value run2,
  2         abs( (c.value-b.value)-(b.value-a.value)) diff
  3    from t a, t b, t c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by 4
 12  /

NAME                                           RUN1       RUN2       DIFF
---------------------------------------- ---------- ---------- ----------
LATCH checkpoint queue latch                      5          6          1
LATCH redo writing                                1          0          1
STAT calls to get snapshot scn: kcmgss          106        105          1
LATCH undo global data                            1          0          1
STAT consistent gets                            112        111          1
STAT execute count                              105        106          1
STAT parse count (total)                        105        106          1
STAT recursive calls                            530        531          1
STAT opened cursors cumulative                  105        106          1
STAT enqueue releases                             3          2          1
STAT change write time                            1          0          1
LATCH direct msg latch                            2          0          2
LATCH session queue latch                         2          0          2
LATCH done queue latch                            2          0          2
STAT session cursor cache count                   0          2          2
STAT free buffer requested                        4          2          2
STAT enqueue requests                             4          2          2
LATCH cache buffers lru chain                     3          6          3
STAT redo entries                                16         13          3
STAT recursive cpu usage                         12          9          3
LATCH ksfv messages                               4          0          4
LATCH messages                                    5          0          5
STAT db block changes                            28         22          6
LATCH redo allocation                            14         21          7
STAT db block gets                              426        417          9
STAT session logical reads                      538        528         10
LATCH cache buffers chains                     1099       1117         18
LATCH shared pool                               320        218        102
STAT session cursor cache hits                    0        102        102
STAT redo size                                21056      20940        116
LATCH library cache                            1285        481        804

31 rows selected.

See how reduced the LATCHes to the library cache are, we wll -- the shared pool
latch is much lower as well.  Using this parameter, you can remove most of the
overhead.

It'll still show up as a soft parse in tkprof and such but the overhead of the
soft parse is mostly offset -- you would have to look at the cursor cache hits
to see how well you are doing there.

followup to comment

Session_cached_cursors is settable in the init.ora file -- it can be set at the
system level for all sessions (and should be!)

Review &
      Followup

      Rating: 5
Great
      response  July 05, 2001
Reviewer:  Manoj
       from Palo alto, CA
Nothing on the earth could have explained it better. Can you help me once more.
Do I need to modify all the procedures to use session_cache_cursor or there is
any system level parameter for it.

      Rating: 5
Most
      useful  July 06, 2001
Reviewer:  Harrison
       from Haymarket, VA USA
Really good, if it is not in the book we can glue
the pages in (I don't know what that other reader
was talking about "Nobody could do it better...",
you might do really well on the next one, and then
start to expect "Nobody..." every time, and our lives
will be hell trying to deal with you. I am in favor
of some moderation on these compliments until we are
satisfied that you have answered all the questions we
can come up with.

      Rating:
      4
REFCURSOR  April 30, 2002
Reviewer:
      Phil Williams  from Palo Alto ,CA USA
Is this statement still true for 9i or is there a new methodology availble now ?

          Followup:

Ref Cursors HAVE to parse each time.

The reason we cannot cache them (like plsql cursors) is due to the fact that you
could have a procedure:

procedure p( x in out refcursor_type )
is
begin
   open x for select * from dual;
end;

Now, if there was JUST one cached cursor and a client went:

   p( cursor1 );
   p( cursor2 );

we would be in a heap of trouble!  They would both be the "same" cursor.  It
gets even more muddied when p looks like:

procedure p( x in out refcursor_type )
is
begin
   if some_condition then
       open x for select * from t;
   else
       open x for select * from t2;
   end if;
end;

Here, the meaning of "x" changes from call to call.

to lessen the impact, one should use session_cached_cursors.

      Rating: 3
Not quite
      getting it....sorry  December 08, 2002
Reviewer:
      A reader  from BG
Tom,

Just got confused on these two sentences here.
"Ref cursors will cause AT LEAST a soft parse each time, yes."

"Ref Cursors HAVE to parse each time."

Soft parse indicates that we do not parse the query again. So, ref cursors cause
hard parse each time. Do i make sense?

My second question is in Oracle reports application, would all the ref cursor
queries be parsed every time it runs?

Thanks for your time

          Followup:

read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2588723819082
a soft parse does NOT mean we do not parse again -- if it did, it wouldn't have
the word parse in it!!

A soft parse is a parse.  A hard parse is a parse. They are both parsed, just
different kinds and hopefully that writeup I point you to will clear that up.

when you use a REF CURSOR, by definition -- a parse will take place on each and
every open.  It might be a soft parse, it might be a hard parse -- but it'll be
a parse.

      Rating:
      5
  December 09, 2002
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
If I set session_cached_cursors to say 100 then are there any other implications
that I need to aware of, like increase in memory requirements etc.?
Thanks

          Followup:

marginal increase in UGA memory in the dedicated server or SGA depending on
where your UGA is coming from.

      Rating: 3
Parsed cursor
      cache  December 09, 2002
Reviewer:  Christophe
      Parmentier  from France, Riviera ;)
Hi, Tom
Reading this article I reminded something that bothered me some time ago...

In our DataWarehouse, we use dynamic tables, with dynamic names, we can't bind a
table name, does that mean the cursor will always be hard parsed ?

Example:

'Select ... from ' || DYN_TABLE_NAME || ' WHERE Field = :1'

Is it useful to bind a such request even if it will be parsed each time, at
least for the Table_name ?

I thought binding a such request was useless, was I right ?

Thanks for the help ;) I'll probably sleep better!

          Followup:

if dyn_table_name is different each time, you might as well not bind.

      Rating: 5
Soft/Hard
      Parse  December 12, 2002
Reviewer:  A reader
       from India
This is on my earlier question of ref cursor parsing. In that sense how is ref
cursor different from regular cursors. THis is with reference only to the
context of parsing.

Can you please illustrate with examples.

regards

          Followup:

Ok, in order to show that ref cursors cannot be cached, all I need to do is open
a "single" ref cursor twice.  If we cached - there could only be ONE instance of
it right?  Well, the semantics of ref cursors do not prevent them from being
opened as many times as the caller wants.  So, consider:

ops$tkyte@ORA920> create or replace package demo_pkg
  2  as
  3      type rc is ref cursor;
  4
  5      procedure get_rc( p_cursor out rc );
  6
  7      procedure open_regular_cursor;
  8  end;
  9  /

Package created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package body demo_pkg
  2  as
  3
  4  cursor C is select * from DUAL;
  5
  6  procedure get_rc( p_cursor out rc )
  7  is
  8  begin
  9      open p_cursor for select * from dual;
 10  end;
 11
 12  procedure open_regular_cursor
 13  is
 14  begin
 15      open C;
 16  end;
 17
 18
 19  end;
 20  /

Package body created.

ops$tkyte@ORA920> show errors
No errors.
ops$tkyte@ORA920> pause

ops$tkyte@ORA920>
ops$tkyte@ORA920> variable x refcursor
ops$tkyte@ORA920> variable y refcursor
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec demo_pkg.get_rc( :x );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec demo_pkg.get_rc( :y );

PL/SQL procedure successfully completed.

that "single" ref cursor is opened twice now -- but it isn't really that the
ref cursor is opened, there are TWO separate cursor variables that have open
result sets associated with them.  PLSQL doesn't have a cursor to cache -- the
client OWNS this cursor so plsql cannot cache them.  We had to have 2 parses in
the above -- one each for :x and :y

ops$tkyte@ORA920>
ops$tkyte@ORA920> print x

D
-
X

ops$tkyte@ORA920> print y

D
-
X

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec demo_pkg.open_regular_cursor

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec demo_pkg.open_regular_cursor
BEGIN demo_pkg.open_regular_cursor; END;

*
ERROR at line 1:
ORA-06511: PL/SQL: cursor already open
ORA-06512: at "OPS$TKYTE.DEMO_PKG", line 4
ORA-06512: at "OPS$TKYTE.DEMO_PKG", line 15
ORA-06512: at line 1

that just shows why PLSQL can "cache" a cursor -- it owns them, there can only
be one instance of it -- it is "well behaved"

Maybe this is even a better example:

ps$tkyte@ORA920> create or replace package body demo_pkg
  2  as
  3
  4  cursor C is select * from DUAL;
  5
  6  cnt number := 1;
  7
  8  procedure get_rc( p_cursor out rc )
  9  is
 10  begin
 11      if ( cnt = 1 )
 12      then
 13          open p_cursor for select 'Hello', 55 from dual;
 14          cnt := 2;
 15      else
 16          open p_cursor for select 'World', sysdate from dual;
 17      end if;
 18  end;
 19
 20  procedure open_regular_cursor
 21  is
 22  begin
 23      open C;
 24  end;
 25
 26
 27  end;
 28  /

Package body created.

ops$tkyte@ORA920> show errors
No errors.
ops$tkyte@ORA920> pause

ops$tkyte@ORA920>
ops$tkyte@ORA920> variable x refcursor
ops$tkyte@ORA920> variable y refcursor
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec demo_pkg.get_rc( :x );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec demo_pkg.get_rc( :y );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> print x

'HELL         55
----- ----------
Hello         55

ops$tkyte@ORA920> print y

'WORL SYSDATE
----- ---------
World 12-DEC-02

ops$tkyte@ORA920>

how could PLSQL cache that?  it doesn't even know what the cursor will be
until someone askes for it and the code runs...

      Rating:
      5
Thanks  December 12, 2002
Reviewer:  A
      reader  from India
That was a very clear explanation.

Summing it up regular cursors would be cached but Ref Cursors cannot be cached
because its value would not be known until runtime.
But, we can get around this by setting session_cached_cursors. Correct?

My other question and the most important question to me is specific to my
application - Oracle Reports. Would the same issue of caching be there on ref
cursor queries on reports also? Or will a regular query and ref cursor query
have the same performance when taken in Oracle Reports.

Really appreciate your time and effort...

          Followup:

You can "help it" by using session cached cursors which is a softer "soft
parse".

In reports - if you enable sql_trace and look at the parse/execute counts on
your queries, you would be able to tell if you would benefit from session cached
cursors.  If parse > 1, you would most likely benefit from it.

      Rating:
      4
session_cursor_max and open_cursor  May 17, 2003

Reviewer:  A reader
sorry for my confusion.

If I have value set as 500 for open_cursor, is not that the cursor will persist
for the session. Then why should we set session_cursor_max again?

Another silly question...
If procedure open the ref cursor for select * from emp;, will there be 2 cursor,
one for ref_cur and other for 'select * from emp'

          Followup:

what is "session cursor max"?

open cursor is the maximum number of concurrent open cursors you have.  we
allocate them 64 at a time (so no, it is not the number the "persist" for a
session, it is a threshold, a maximum boundary value).

a ref cursor is a cursor.  if you open a ref cursor for select * from emp there
is ONE open cursor -- the ref cursor.  the select * from emp, that is just text.

      Rating: 4
Sorry, I meant
      session_cached_cursor  May 18, 2003
Reviewer:
      A reader

          Followup:

that caches cursors you closed, for performance.

so, open cursors = max cursors you can have.
session cached cursors = how many we try to keep open when you prematurely
closed them, for performance.

they are apples and oranges when comparing.

      Rating: 3
Is not parsing
      happens for tetxt  May 18, 2003
Reviewer:
      A reader
Tom

Is it not true that parsing happens for SQL text. So how ref cursor will be
parsed ?

          Followup:

exactly like a "non ref cursor" would be.

      Rating: 3
I wish I could
      transport your inteliigence........  May 18, 2003

Reviewer:  A reader
Tom, you are expert and I am not. I know everything is clear in your mind, but
not mine.

Let me rephrase it.

There is a statement called ( p_ref is predifned ref cursor)

open p_ref for select * from emp;

so here we have a cursor (p_ref) and a SQL statement.

Now which one of the 2 is getting parsed.

My understanding (confusion) is that

a) SQL statement is parsed ( syntax/semantics checking, optmization, parse tree
etc).

b) Cursor is handle for statement execution and does not need parsing.

I know if I get answer from anyone, it's you.

I am sorry for killing your valuable time, but hope that I am not only person
who will be benefited by the answer.

          Followup:

the cursor is getting opened.

the statement that will be parsed is represented by the SQL you have "select *
from emp"

the SQL gets parsed, the cursor points to the parsed representation.

      Rating:
      3
Query  August 14, 2003
Reviewer:
      Peter  from India
so...
CREATE OR REPLACE PROCEDURE T1
(
    vCursor OUT  XX_RECORDSET.OUT_CURSOR
)
BEGIN
OPEN vCursor FOR
SELECT * FROM EMP;
END;
/
-------------------
CREATE OR REPLACE PROCEDURE T2
(
    vCursor OUT  XX_RECORDSET.OUT_CURSOR
)AS
vStmt varchar2(2000);
BEGIN
vStmt :="Select * from EMP";
OPEN vCursor FOR
vStmt;
END;
/

Both the procedure are same??.
Because I am facing some problem here.
I have a procedure like this

CREATE OR REPLACE PROCEDURE Test1
(
    vCursor OUT  XX_RECORDSET.OUT_CURSOR
)
vStmt varchar2(2000);
BEGIN
vStmt :="Select * from EMP";
IF Some_Condition THEN
     vStmt := vStmt ||"Where EMPID =v_id"
END IF;
IF Some_Condition THEN
     vStmt := vStmt ||"AND EMP_NAME =v_Name"
END IF;
IF Some_Condition THEN
     vStmt := vStmt ||"AND MGR=v_Mgr"
END IF;
OPEN vCursor FOR
vStmt;
END;

This is working fine.
But I don't like to use dynamic SQL.
So I have rewritten this procedure like this

CREATE OR REPLACE PROCEDURE Test2
(
    vCursor ........
)
BEGIN
IF Some_Condition THEN
     OPEN vCursor FOR
     Select * from EMP Where EMPID =v_id;
ELSIF Some_Condition THEN
     OPEN vCursor FOR
     Select * from EMP Where EMPID =v_id
     AND EMP_NAME =v_Name;
ELSIF Some_Condition THEN
     OPEN vCursor FOR
     Select * from EMP Where EMPID =v_id
     AND EMP_NAME =v_Nam
     AND MGR=v_Mgr;
END IF;
END;

logically both are giving same result.No problem. But I would like to know about
the efficienty point of view.

Actually my question is in the procedure Test1, I have only one "Open vCursor
For Selec..." statement. But
in the Test2 procedure I have 3 "Open vCursor FOR Select ..." statement.
According to your previous
explanation, soft/hard parse will be done. So in the procedure Test2 it has to
parse 3 sql statement.
But in the Test1 procedure the parsing will be done for one SQL statement. So
which way will be usefull?. Here
I have just given these procedures for example. But in my real application I
have used this same logic.
So I end up with 12 SQL statements.
So please suggest me, which approach I should follow?

Thanks in advance
Peter

          Followup:

static sql is always preferred for

o performance
o maintanence
o realiability

in plsql.  procedure test2 will only parse THE single statement you request.

      Rating: 4
Dynamic no
      impact?  August 28, 2003
Reviewer:  Jon
       from Sydney
Since ref cursors are parsed each time, is there actually any difference between
using static sql and dynamic sql when associate with a ref cursor (assuming no
binding required).  My testing seems to indiciate there is no significant
difference:

declare
    type refcursor is ref cursor;
    rc refcursor;
    r_client client%rowtype;
begin
    execute immediate 'truncate table run_stats';
    commit;
    insert into run_stats select 'before', stats.* from stats;
    for x in 1..100 loop
        open rc for 'select * from client where client_id = 711345';
        loop
            fetch rc into r_client;
            exit when rc%notfound;
        end loop;
        close rc;
    end loop;
    insert into run_stats select 'after 1', stats.* from stats;
    for x in 1..100 loop
        open rc for select * from client where client_id = 711345;
        loop
            fetch rc into r_client;
            exit when rc%notfound;
        end loop;
        close rc;
    end loop;
    insert into run_stats select 'after 2', stats.* from stats;
    commit;
end;
/

select * from report_stats;

                                        Run 1   Run 2   Diff
------------------------------------------------------------
STAT redo size                          20900   20984   84
LATCH cache buffers chains              562     586     24
LATCH shared pool                       275     266     9
LATCH redo allocation                   13      21      8
STAT db block gets                      26      19      7
STAT session logical reads              437     430     7
LATCH library cache                     458     464     6
STAT db block changes                   27      22      5
LATCH checkpoint queue latch            2       6       4
STAT redo entries                       16      13      3
STAT enqueue requests                   5       3       2
STAT calls to get snapshot scn: kcmgss  106     105     1
STAT parse time cpu                     3       2       1
STAT recursive cpu usage                16      17      1
STAT free buffer requested              4       3       1
STAT enqueue releases                   4       3       1

          Followup:

you lose the dependencies....

you lose the assurance that your SQL is correct....

if you can do it statically, do it.

August 28, 2003 Prashant -- Thanks for the question regarding
      "How to load fixed position file into multiple tables", version 8.1.6

      You Asked
Tom,
    I have a sql*loader question for you.
    I have a fixed position datafile as example shown below.

    1TEST11234042002500052002500062002500072002500
    2TEST21234042002500052002500062002500072002500

    I want to load character from position 1 to 10 into one table
    EMP_SUM and position 11 to rest into other table EMP_DET.
    But from position 11 to rest are multiple records for EMP_DET.
    That means position 11 to 19 is one records, 20 to 28 is second
    record. Like that I have total (9*10)=90 characters from position
    11. So actually I need to insert 10 records into EMP_DET table and
    one record into EMP_SUM table. Also I need to copy character from
    position 1 to 6 for each record of emp_Det.

    The structure of both table are as under.

    EMP_SUM
    emp_seq number(1)
    emp_name varchar2(5)
    tot_amount number(4)

    EMP_DET
    emp_seq number(1)
    emp_name varchar2(5)
    pay_month varchar2(6)
    pay_amount number(3)

    Datafile is a fiexd position characters file. One line of a datafile is a
combination of one record into EMP_SUM table and
10 records in EMP_DET table. Currently I have 10 datafiles to
read positions from 11 to rest and insert multiple records into the
EMP_DET table. Is there any way I can have only one control file
for this situation and it will insert one records into EMP_SUM table
and 10 records into EMP_DET table?
    You suggestions are always appreciated.

Thanks,
Prashant Shah

      and we said...
Just use multiple into clauses:

LOAD DATA
infile *
replace
INTO TABLE EMP_SUM
(
emp_seq   position(1:1),
emp_name  position(2:6),
tot_amount position(7:10)
)
into TABLE EMP_DET
(
emp_seq   position(1:1),
emp_name  position(2:6),
pay_month position(11:16),
pay_amount position(17:19)
)
into TABLE EMP_DET
(
emp_seq   position(1:1),
emp_name  position(2:6),
pay_month position(20:25),
pay_amount position(26:28)
)
into TABLE EMP_DET
(
emp_seq   position(1:1),
emp_name  position(2:6),
pay_month position(29:34),
pay_amount position(35:37)
)
begindata
1TEST11234042002500052002500062002500072002500
2TEST21234042002500052002500062002500072002500

for example loads the first three segments -- the other 7 you can fill in.
Review &
      Followup

      Rating: 5
How Many Times
      does this Parse the Input File.  August 08, 2002

Reviewer:  A reader
Hi Tom,

Just wanted to know if SQL Loader will parse the input file from top to end for
each into Clause.

Awaiting your response.

Regards,
Ganesh R

          Followup:

one pass -- every record will be processed by every into clause.

      Rating: 2
Similiar
      Question  August 04, 2003
Reviewer:  vivek
       from UK
Hi,
How can i load single record into multiple tables if the data for the parent
table is repeated again and again. I have table for region,sites,and building.
and data is like

LONDON,SITE1,BUILDING1
LONDON,SITE2,BUILDING2
LONDON,SITE2,BUILDING3
LONDON,SITE2,BUILDING4
LONDON,SITE3,BUILDING5
LONDON,SITE3,BUILDING6
LONDON,SITE3,BUILDING7
LONDON,SITE3,BUILDING8
....

I have written a control file with 'APPEND OPTION' for all tables and columns
are position holders. Problem i am facing is RECORD 2 is rejected as primary key
violated for region table.

Is there any way around, so that i can pull this type of data into respective
tables, even if values for region and sites are repeated. Site table has got
region and site code as primary code. Building table as got region and site code
along with building code, though not part of primary key.

          Followup:

you mean "short of ignoring the fact that there are rejects on the one table"?

no, not really.  think about it.  you'd be asking sqlldr itself to "de-dup" data
(that is what a database does).

I would just:

a) load one table.
b) create the other tables as select distinct from the first.

      Rating:
      4
  August 04, 2003
Reviewer:  Vipin
       from New york
Hi tom,

how would the control file look like, if the above requirement of loading into
multiple tables, was from a delimited file.

          Followup:

http://asktom.oracle.com/~tkyte/SkipCols/index.html
you can use something like the delimited implemenation there so you are using
postition instead.

      Rating:
      2
Help  August 27, 2003
Reviewer:  SD
       from USA
Hi!
I want to load data in multiple tables but its giving problems :(

Control File :
load data
append
into table a
when (001:003) = '001'
fields terminated by "," optionally enclosed by "'" trailing nullcols
(field1,field2)
into table b
when (001:003) = '002'
fields terminated by "," optionally enclosed by "'" trailing nullcols
(field3, field4 date 'yyyymmdd')

Data File:
001,'a',1234
002,'b',20030101

The error is
Record 2: Discarded - all columns null.

Table A:
  1 Row successfully loaded.
  0 Rows not loaded due to data errors.
  0 Rows not loaded because all WHEN clauses were failed.
  0 Rows not loaded because all fields were null.

Table B:
  0 Rows successfully loaded.
  0 Rows not loaded due to data errors.
  0 Rows not loaded because all WHEN clauses were failed.
  1 Row not loaded because all fields were null.

Please Help!
TIA

          Followup:

problem is when using "terminated by..." the record is parsed only once.

so, for the row

001,'a',1234

field1 = 001
field2 = 'a'
field3 = 1234
field4 = <nothing>

for 002,'b',20030101

field1 = 002
field2 = 'b'
field3 = 20030101
field4 = <nothing>

multiple into's are hard to use with delimited data like that.

You can either:

a) load the tables one after the other (run sqlldr two times)
b) use positional notation -- see

http://asktom.oracle.com/~tkyte/SkipCols/index.html
for an example of what you might be able to do (using my delimited package)

      Rating: 3
Can this be
      done using external tables?  August 28, 2003

Reviewer:  Matt  from Australia
Can we also load into multiple tables from an external tables?

What about if the data file is binary (with a fxed record size?)

Just wondered.

Thanks.

          Followup:

sure, using a multi-table insert....

binary -- yes.

August 28, 2003 habeebuddin -- Thanks for the question regarding
      "Developer/2000 forms(5.0)", version 2.1

      You Asked
Sir,

Q(1): In oracle forms I want to set the item base table property
at runtime.  At present this property can be set at design time
only.

Q(2): In oracle forms I want to edit the console line
statistical messages like enter-query,list vlues etc..

Regards...

      and we said...
For #1, you cannot set the base table at runtime however you can
use a private synonym to achieve a similar effect.

Lets say you want a block to be able to query either t1 or t2 at
runtime.  You might create some DDL like this in the database:

scott@8i> create table t1 ( x int, y int );
Table created.

scott@8i> create table t2 ( x int, y int );
Table created.

scott@8i> insert into t1 values ( 1, 1 );
1 row created.

scott@8i> insert into t2 values ( 2, 2 );
1 row created.

scott@8i> create synonym my_table for t1;
Synonym created.

So, now we will create our form on MY_TABLE -- not on t1 or t2.
As an example, create the tables above and put the values into
them.  Create the synonym.  Create a default block on MY_TABLE.
Put 2 buttons on the form -- one labeled T1 and the other T2.
In a when button pressed trigger on T1 place this code:

forms_ddl( 'drop synonym my_table' );
forms_ddl( 'create synonym my_table for t1' );

likewise for the T2 button place this code:

forms_ddl( 'drop synonym my_table' );
forms_ddl( 'create synonym my_table for t2' );

Now query the database -- 1,1 comes up.  Press the T2 button and
requery -- 2,2 comes up.  Press T1 and requery -- back to 1,1.

As for #2, you will create FORM level "KEY-*" triggers.  For
example, if you create a KEY-ENTQRY (enter query trigger) that
contains the code:

message( 'You have just entered into Enter Query Mode!',
          NO_ACKNOWLEDGE );
Enter_Query;

The enter_query message will now be what I have above, not the
default message.
Review & Followup

      Rating: 3
Querying two
      tables in Forms 6i  June 20, 2003
Reviewer:
      Ofoe  from Nigeria
Hi Tom,
I used your search to find out a similar to my question and happen to find a
related answer here but if you could be more specific to my question, i would be
much greatful.
Am using the forms in-built query in the default menu to query records from two
tables but am recieving an error saying invalid column name which I understood
to mean the column name is not part of the base table name in the form where the
query is being performed.
Here is the example:
table1(a number,b varchar2(4)) -- base table in forms
table2(b varchar2(4),c varchar2(2))

Now, my form is based on table1 which has a Tabular layout displaying 6 records
on the canvas. When I perform a query on table1 alone i was able to get all
records in it.
But now I added a condition to the WHERE cluase in the block property of table1
i.e table1.b=table2.b AND table2.c IN ('12','14'). Before I did this,I added
another block in the same form based on table2 and at the WHEN-NEW-BLOCK
INSTANCE trigger on table1 block I joined the two blocks with the join condition
:table1.b := :table2.b
Now after i have compiled the form and run it I recieved the error 'invalid
column name ' complaining of column C which is not part of table1. What is your
suggestion that will help me achieve my result?
Thanks.

          Followup:

you cannot "join" blocks, blocks can be built on a joined view.

if you do not need the value of table2.c on screen, just

where b in ( select b from table2 where c in ( 12, 14 ) );

now your query selects from ONE table

if you need two tables joined and displayed -- create a view, base the block on
that.

      Rating: 4
Master -detail
      query  June 21, 2003
Reviewer:  anurag
       from INDIA
Hi!

I am having two blocks 'm' & 'd', joined on a same column say 'y'. Master block
has one record and child block has 5 records display. I want to fetch certain
values from a lookup table 't' in detail block on the basis of column 'z' in
master block. How could i do and which trigger should i use.

thanks in adv.

      Rating: 1
what about my
      query.............  June 24, 2003
Reviewer:
      anurag  from INDIA
Dear Tom,

My query remains unanswered. Do you require more details on this.........

pl. reply to my query as below:

 am having two blocks 'm' & 'd', joined on a same column say 'y'. Master block
has one record and child block has 5 records display. I want to fetch certain
values from a lookup table 't' in detail block on the basis of column 'z' in
master block. How could i do and which trigger should i use.

thanks.

          Followup:

i don't see all of these -- this is a simple "post change" or some trigger or
anything.  I myself have not used forms since 1995.

I would just have created a view for the detail block -- do the work in the
database.

      Rating:
      2
  June 24, 2003
Reviewer:  Gary
       from Sydney, Australia
"I want to fetch certain values from a lookup table 't' in detail block on the
basis of column 'z' in master block."

If the values are derived solely from master block columns, then logically it
should sit in the master block. If the columns from which it is derived are
returned from a query (as opposed to being entered by a user), then the dervied
value should also be picked up as part of the query. [Forms 6i allows you to
base a block on a FROM clause so your block can be based on
FROM (select a.col1, a.col2, b.col3 from taba a, tabb b
where a.col1 = b.col1a). Much better than POST_QUERY triggers with all those
repeated executions - but Tom would still prefer a permanent database view :)]
If the columns from which it is derived are user entered you will need to use a
POST_CHANGE or WHEN_VALIDATE_ITEM trigger.

If you want columns from a master block displayed in a detail block, then use
the Item property 'Copy Value from Item' in the Data section.

PS. To bring the issue back to the original habeebuddin problem, the
QUERY_DATA_SOURCE_NAME can be set at runtime, so you can dynamically switch a
block from being based on tablea to tableb. You can't switch a block's item from
being a base table item to a non-basetable item or vice versa or between
different base columns in a table.

      Rating: 5
A very good
      Gary's answer  June 25, 2003
Reviewer:  Helena
      Marková  from Bratislava, Slovakia
For Forms there is also a discussion forum on Technet there:

http://www.oracle.com/forums/forum.jsp?forum=82

      Rating:
      3
Nice  August 28, 2003
Reviewer:
      R.Chakravarthi  from Chennai,India
Dear Sir,
I have some doubts in forms.
1)What is meant by validation?could you please explain on
 when-validate-item trigger?can post-text-item can be used
instead of when-validate-item?
2)could you please provide link to oracle forms(v6) reference manual?
It will be of great help to me.
Thanks.

          Followup:

1) means YOU write some code to validate the user input (eg: field X must be
between 1 and 5 -- let me write a validate trigger to make sure that is so)

2) otn.oracle.com -> documentation has all of the online docs.

August 28, 2003 Jesse -- Thanks for the question regarding
      "Returning clob to varchar from a stored procedure", version
      Oracle8.0.5/Sun OS

      You Asked
Hi Tom,
The issue is to return the text details stored in a table as
clob(after
loaded into the table from a text file). Is there a limitation
like
4000 for the size of an OUT parameter to passed into a stored
procedure?

I have created the following stored procedure
create or replace procedure test_proc(myid number, mydoc out
varchar2) is
    c_lob        clob;
    myclob_text    varchar2(4000);
    len        number:=0;
    amount        number:=100;
    offset        number;
begin

    select theclob
    into c_lob
    from demo
    where id=myid;

    len := dbms_lob.getlength(c_lob);
    offset := 1;
    while (len > 0) loop
        myclob_text := dbms_lob.substr (c_lob, amount, offset);

--        dbms_lob.read(c_lob, amt2, offset, myclob_text);
--        dbms_output.put_line (myclob_text);
        len := len - amount;
        offset := offset + amount;
        mydoc := mydoc || myclob_text;
    end loop;
end;
/

Testing using:
SQL> variable txt varchar2(4000);
SQL> exec test_proc(1, :txt);
Error: ORA-06502 PL/SQL: numeric or value error

Text in id 1 is loaded from a file size of 16K.
If I passed in the id=2 which contains 2 lines of text, it is
OK.

If I would like to call this stored procedure using thin
client JDBC calls, will it works for big clob?

What is the solution if I were to return the clob->varchar2 from

a stored procedure?

Thank you in advance

Regards,
Hwee

      and we said...
There is a 32k LIMIT on the size of an OUT parameter (or IN
parameter or in fact all PLSQL variables).  The reason you are
getting

SQL> variable txt varchar2(4000);
SQL> exec test_proc(1, :txt);
Error: ORA-06502 PL/SQL: numeric or value error

is due to the fact that you are trying to stuff 16k of data into
4000 bytes of allocated space.  SQLPlus will not let you define
a variable of greater them 4000 bytes (but you can in other
languages).

Below shows that a plsql routine can in fact return 16k of data:

ops$tkyte@DEV8I.WORLD> create table t ( x int, y clob );

Table created.

ops$tkyte@DEV8I.WORLD>
ops$tkyte@DEV8I.WORLD> declare
  2          tmp     long;
  3          c       clob;
  4  begin
  5          tmp := rpad( '*', 16384, '*' );
  6          insert into t values ( 1, empty_clob() )
                      returning y into c;
  7          dbms_lob.writeappend( c, length(tmp), tmp );
  8
  9          tmp := rpad( '*', 2048, '*' );
 10          insert into t values ( 2, empty_clob() )
                      returning y into c;
 11          dbms_lob.writeappend( c, length(tmp), tmp );
 12  end;
 13  /

PL/SQL procedure successfully completed.

ops$tkyte@DEV8I.WORLD> select x, dbms_lob.getlength(y) from t
  2  /

         X DBMS_LOB.GETLENGTH(Y)
---------- ---------------------
         1                 16384
         2                  2048

ops$tkyte@DEV8I.WORLD> create or replace procedure test_proc(
myid number, mydoc OUT varchar2 )
  2  as
  3          l_clob  clob;
  4  begin
  5          select y into l_clob
  6            from T
  7           where x = myid;
  8
  9          mydoc := dbms_lob.substr( l_clob, 32765, 1 );
 10  end;
 11  /

Procedure created.

ops$tkyte@DEV8I.WORLD> declare
  2          test_bind_variable long;
  3  begin
  4          test_proc( 1, test_bind_variable );
  5          dbms_output.put_line( 'returned length is ' ||
length(test_bind_variable) );
  6  end;
  7  /
returned length is 16384

PL/SQL procedure successfully completed.

ops$tkyte@DEV8I.WORLD>

In Java, using the 8.1.5 thin driver, I coded the following:

import java.io.*;
import java.sql.*;
import java.util.*;

class test
{

public static void main(String args[])
{
    Statement statement = null;

    try
    {
    DriverManager.registerDriver
        (new oracle.jdbc.driver.OracleDriver());

    Connection connection =
      DriverManager.getConnection
        ("jdbc:oracle:thin:@aria-dev:1521:ora8idev",
         "scott", "tiger");

    DatabaseMetaData conMD = connection.getMetaData();
    System.out.println("JDBC Driver Name        :\t" +
    conMD.getDriverName());
    System.out.println("JDBC Driver Version     :\t" +
    conMD.getDriverVersion());
    System.out.println("Database Product Name   :\t" +
    conMD.getDatabaseProductName());
    System.out.println("Database Product Version:\t" +
    conMD.getDatabaseProductVersion());
    System.out.println("Max Statements          :\t" +
    conMD.getMaxStatements());
    System.out.println();

    connection.setAutoCommit(false);

    statement = connection.createStatement();

    CallableStatement cstmt = connection.prepareCall(
        "begin test_proc( 1, ? ); end;" );

    cstmt.registerOutParameter( 1, java.sql.Types.CHAR );
    System.out.println( "prepared & registered" );
    cstmt.executeUpdate();

    String newVal = cstmt.getString( 1 );
    System.out.println
       ( newVal.length() + " bytes retrieved..." );

    }
    catch( Exception e )
    {
        e.printStackTrace();
    }

}

}

and it returns:

$ java test
JDBC Driver Name        :       Oracle JDBC driver
JDBC Driver Version     :       8.1.5.0.0
Database Product Name   :       Oracle
Database Product Version:       Oracle8i Enterprise Edition
Release 8.1.5.0.0 - Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.0.0 - Production
Max Statements          :       0

prepared & registered
16384 bytes retrieved...

So, you should be able to use this method to retrieve UPTO 32k
of text.  Beyond that you would have to use streams on the clobs.
Review &
      Followup

      Rating: 5
Is there a 4k
      limit when returning a portion of a clob in a
      function?  August 28, 2003
Reviewer:
      Anthony  from ny
Tom,
 I am trying to create a function to return a portion of a clob. The size is
greater than 4k but less than 10k.
I'm having some trouble understanding what's going on here:

create table t( a int, b date, c clob );

insert into t values( 1, sysdate, rpad('*',400,'*') );
commit;

create or replace function get_clob( p_ptr clob )
return varchar2
is
begin
    return dbms_lob.substr( p_ptr, dbms_lob.getlength(p_ptr), 1 );
end;
/

  /* test it out */
SQL> col text form a50
SQL> select a,b,get_clob(c) text from t;
         A B         TEXT
---------- --------- --------------------------------------------------
         1 28-AUG-03 **************************************************
                     **************************************************
                     **************************************************
                     **************************************************
                     ...

so far so good. The return val is less than or equal to the SQL limit of 4k.

/* putting in a bigger clob */
declare
 l_ptr clob;
begin
    insert into t values( 2, sysdate, empty_clob() ) returning c into l_ptr;
    dbms_lob.write( l_ptr, 32767, 1, rpad( '*',32767,'*' ) );
    commit;
end;
/

SQL> select dbms_lob.getlength(c) from t;

DBMS_LOB.GETLENGTH(C)
---------------------
                 4000
                32767

/* the following error is expected */
SQL> select a,b,get_clob(c) from t where a=2;
ERROR:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at "SCOTT.GET_CLOB", line 5

/* but, this doesn't work.... */
SQL> declare
         x varchar2(32767);
     begin
         select get_clob( c ) into x from t where a=2;
         dbms_output.put_line(length(x));
     end;
  /
declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at "SCOTT.GET_CLOB", line 5
ORA-06512: at line 4

/* ...while this does */
SQL> declare
        x varchar2(32767);
        y clob;
     begin
        select c into y from t where a=2;
        x := get_clob( y );
        dbms_output.put_line( x );
     end;
32767

PL/SQL procedure successfully completed.

Why does the last anonymous block succeed?
Is this first block doing it in SQL (even though it's in a pl/sql block),
therefore restricted to 4k as well?

 Thanks in advance,
  -Ant

          Followup:

sql is limited to 4000 bytes unless you just return the CLOB/BLOB itself.

plsql is limited to 32k unless you return the clob/blob itself

August 29, 2003 Michel -- Thanks for the question regarding
      "Spooling", version 7.3.4

      You Asked
Hi Tom,

and thank you for all the tips.

Here's my problem : we work on a UNIX platform and we have
developed several sql scripts which are launched from a korn
shell script using SQL*Plus. In the shell we start spooling the
results in a file, then we start each sql script one by one,
then we stop spooling just as follows :

sqlplus -s USER/PASSWORD <<STOP
     spool SPOOLFILE
     start script1.sql
     start script2.sql
     spool off
STOP

Since we work on a large database, several hours can pass
between the beginning and the end of the korn shell. Oracle only
writes in the spool file when the order spool off is issued so
that we cannot follow the results of each sql script as soon as
they completes.

The only way we have found so far in order to follow the running
of the several sql scripts is to issue the spooling orders
(spool <file> and spool off) inside each sql script and append
the content of each spooling file to a unique log file as soon
as it is created by Oracle. This is most inconvenient since
there is a lot of sql scripts launched.

So the question is : is there a way to force Oracle to write in
the spool file while each script is executed OR as soon as a
transaction have been completed ?

All the best,

      and we said...
spool uses buffered file io (eg: like the C fopen, fwrite, fread
functions provide).  The OS buffers this information, typically
in 8k chunks.  A spool file should report its results about
every 8k.  There is no way to "flush" the spool.

One idea for you is to instruement the code with calls to
dbms_application_info.set_action and set_client_info.  In this
fashion, you can set fields in the v$session dynamic performance
view.  In fact, SQLPlus is already doing this for you.  As your
script above runs -- sqlplus is setting your sessions MODULE
field to the name of the script that is running.  You can use
this to monitor your scripts in the database.  Just

select module, action, client_info
from v$session where username = 'USER';

to see this information.

Another option that might work for you would be to redirect
sqlplus's output instead of spooling.  You may find that:

#!/bin/ksh

sqlplus -s scott/tiger <<STOP >> test.lst
@test
exit
STOP

works better for you.  Doing a tail -f on test.lst gives me
immediate feedback.

Review &
      Followup

      Rating: 5
The exact
      question I was looking for.  February 12, 2003

Reviewer:  Robert  from Memphis, USA

      Rating: 3
Spool to an
      existing File  March 25, 2003
Reviewer:
      ANUP  from LONDON
Is There Any option so that We Can Spool The Output Of a Sql SCRIPT File To An
Existing File Without Overwriting it.

I Mean Can We open a file in Appeand mode by spool.

Thanks and regards

          Followup:

no there is not.

      Rating: 3
Appending to a
      spool file  March 25, 2003
Reviewer:  Mike
       from Minneapolis
It would be a nice enhancement to be able to append
to an existing spool file.  The workaround now is to have the OS redirect the
output to a file instead of appending,
and in the process do the appending...
I hope that Oracle would consider this sometime in the future...

          Followup:

The only way to get an enhancement request into the system is to develop a
business case for it and file it as an official enhancement request using
metalink.oracle.com -- the Oracle support web site.

If no one ever puts it into the system, it'll never have a chance of getting
acted on except by sheer accident.

      Rating: 5
How to spool to
      any directory/filename  August 28, 2003
Reviewer:
      A reader  from USA
I saw the example you provided to spool to any filename; passed as a parameter.
But, is there a way to spool to a filename where the directory name has spaces.
The directory name will not be known until run-time.

The script is
spool &spool_file

select Col1 from Table1 where Col3 = 'Y';

spool off;
exit;

And it I would like to run as

sqlplus user/pass@sid @my_script 'e:\AHD Desc\logs\My_file.txt'

          Followup:

directories with spaces -- another great windoze innovation.  even though you
could do it on unix forever -- no one actually *did*.  such a bad idea...  cause
of so many problems...

in anycase, you cannot specify a spool name with a space in it in 9i.  you'll
have to use the mapped 8.3 dos filename instead.

August 29, 2003 Munzer -- Thanks for the question regarding "date
      query ", version 8.1.7

      You Asked
Tom:

1.  I had a problem with a date query using the operator 'between'.

WHen I do

Select * from table where date is between '01-JAN-02' and '17-JAN-02'

it  does not give me the records marked with a date '17-JAN-02'
I have to change it to '18-JAN-02' to get those.

I thought the between operator use <= and >= so i should be getting anything on
those dates and between those dates but it does not.

Any ideas.

2.  As a developer, is there any way where I can check using the browser or
SQL*PLUS whether a web application is set as "STATELESS" or "STATEFUL".

The dba said he changed it from stateless to stateful but nothing happened in
speed.  I want to confirm the configuration change?
Can you check all parameters set for the web application?

Thank you,

      and we said...
1)
toms rule #1: never compare strings to dates and dates to strings.  Always
compare strings to string and dates to dates!

toms rule #2: stop using YY, just stop -- now, forever!!!.  Always use
to_date and ALWAYS use a 4 character year mask.  Just do it!  You will never be
sorry you did.  You WILL be sorry if you do not. (i still cannot believe we did
not all learn this painful lesson in 2000!)

The problem you have is that:

where dt_col between '01-jan-02' and '17-jan-02'

is the same as:

where dt_col between to_date( '01-jan-2002 00:00:00', 'dd-mon-yyyy hh24:mi:ss' )
                 and to_date( '17-jan-2002 00:00:00', 'dd-mon-yyyy hh24:mi:ss' )

So, the date:  17-jan-2002 12:00:00 (noon on jan 17th, 2002) is NOT between
those two.  You could:

where dt_col between to_date( '01-jan-2002 00:00:00', 'dd-mon-yyyy hh24:mi:ss' )
                 and to_date( '17-jan-2002 23:59:59', 'dd-mon-yyyy hh24:mi:ss' )

or if you just wanted to supply the days as bind variables (in the form of a
string like '01-jan-2002') you could:

where dt_col between to_date( :bv1, 'dd-mon-yyyy' )
                 and to_date( :bv2, 'dd-mon-yyyy' ) + (1-1/24/60/60)

that would do the same (adding 1-1/24/60/60 adds 23:59:59

2) if you are using the mod_plsql module, you should see database sessions that
are connected persistently in "stateful" mode.  (query v$session). changing from
stateless to stateful wont necessarily affect performance unless you have lots
of requests coming in simultaneously and actually connecting to the database is
the bottleneck (on a smallish system, this will not be the case). if the PLSQL
code or SQL code is what is slow, this setting won't make a bit of difference!
Use tools like dbms_profiler and tkprof to check this.  If you are interested --
my book has lots of details on using these tools to tune with.

Review &
      Followup

      Rating: 5
Date
      Query  February 23, 2002
Reviewer:  Munzer
       from Reston, VA
Tom:

1.  I do not understand why i have to use to_Date is SQL book says that between
should find dates min>=date<=max.

SInce I am passing those as parameters from a web page, how can is use the time
in to_date fucntion with the parameter

where date between to_date(p_fromdate,23:59:59) and to_Date(p_todate,23:59:59)

2.  if you use bind variables like you say
where dt_col between to_date( :bv1, 'dd-mon-yyyy' )
                 and to_date( :bv2, 'dd-mon-yyyy' ) + (1-1/24/60/60)

WHERE DO YOU bind  variables and what do you use. owa_util.bind_variables

3.  I do not undesrtand how going from stateless web app to stateful does not
make a differene in speed.  On one app I noticed big difference since i am only
using one connection rather than connectio per page.

THe app has 100 procedures but packages are not used.  DO you think grouping
those procedures into packages by functional area would spped the page loading.
IT is taking about 5-6 seconds to run the page now.

Thank you,

          Followup:

1) you dont HAVE to use to_date -- I am strongly ENCOURAGING you to use to_date
to avoid ambiguities (compare strings to strings, numbers to numbers, dates to
dates -- don't RISK confusion comparing strings to numbers, dates to strings).

Always use YYYY formats as well!!!

Now as for: "SQL book says that between  should find dates min>=date<=max."  --
I tried to tell you -- IT IS DOING just that!!

It is because

17-jan-02 is really January 17th, 2002 at 0 hundred hours (eg: an hour before
1am on jan 17th).

Therefore, only dates in your database that are LESS THAN or EQUAL to January
17th, 2002 at 0 hundred hours would be returned.  You wanted ALL dates between 0
hundred hours January 1st, 2002 and MIDNIGHT January 17th, 2002 -- hence you
need to use a date that is MIDNIGHT January 17th, 2002 (well, really 23:59:59 on
January 17th, 2002)....

with the p_fromdate and p_todate -- if the contents of those PLSQL variables is
01-jan-02 and 17-jan-02 (and I would BEG you to alter your interface to make it
be 2002, not 02!) then you would code:

  where date between to_date( p_fromdate, 'dd-mon-yy' )
                 and to_date( p_todate, 'dd-mon-yy' ) + (1-1/24/60/60)

that gets you from january 1st, 2002 at 0 hundred hours to midnight on january
17th, 2002.

2) yes, you would use owa_util.bind_variables if you are using
owa_util.list_print/cells_print

3) Then on that app, the pages must have been very very fast and the connection
was a large portion of the total request time (or for some reason the connection
was taking much longer then it should).

If it is taking 5-6 seconds to run a page -- you have SLOW sql and/or SLOW PLSQL
code.  Using dbms_profiler and/or tkprof (start with tkprof) you will find out
which it is.  5-6 seconds indicates your application itself is slow, not the
connection (a connection should take a small fraction of a second).

As for packages -- another suggestion I have is that all REAL production code
would use NOTHING other then packages and would never use a stand alone
function.  See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:286816015990
for why.  So yes, put EVERYTHING into packages.  EVERYTHING.

      Rating: 5
date
      query  February 23, 2002
Reviewer:  Munz
       from Reston, VA
Tom:

if you are designing a screen and prompting the user to enter dates how would
you prompot them to enter format:

DD-MON-YY or
DD-MON-YYYY or
 DD/MM/YYYY or
 MM/DD/YYYY or
DD/MM/YY or
MM/DD/YY

I am sure user does not like to enter YYYY for year.

How does oracle store date format?

Can you take the user format and re-format it and store it in oracle's format

          Followup:

The above things are STRINGS, they are not dates.  They are strings that can be
converted into dates.

Maybe in your application, if someone enters

01-jan-30
01-jan-99

it is clear what that means.  Depending on your default NLS_DATE_FORMAT that
will either be:

01-jan-2030
01-jan-2099

OR

01-jan-2030
01-jan-1999

ambiguities like that scare the $#@ out of me.  If you are going to allow them
to enter

dd-mon-YY

then you better use a DATE format in your TO_DATE to avoid all ambiguities.  I
would suggest using

to_date( string, 'dd-mon-RR' )

and learn what the different between RR and YY is (see the SQL reference manual
under date formats).  In that scenario, no matter what database you install your
code into - you KNOW whats going to happen.  I don't like to rely on defaults,
be explicit.

Best would be to have some javascript that as you TABBED out of the date field,
it would REWRITE the date from dd-mon-yy into dd-mon-yyyy (your java script
would look at the input, see if only two digits were supplied and supply the
other two).  In that fashion the end user need not supply the century but they
will SEE the century that will be used -- giving them the opportunity to
overwrite that with the correct century when needed.

      Rating:
      5
v$session  February 27, 2002
Reviewer:
      Munz  from Reston, USA
Tom:

Which column(s) in v$session tells you whether you have stateful or stateless
environment.

I did a select on the table but could not figure it out

          Followup:

none really.  "stateful" and "stateless" is a WEB concept, done at the app
server layer.

If you see lots of connected but "idle" sessions -- and the usernames correspond
to the usernames of your web applications, then you probably have stateful.

select username, status from v$session where username is not null;

      Rating:
      3
  February 27, 2002
Reviewer:  VL
       from HK
Select * from table
where date >= to_date('20020101', 'YYYYMMDD')
and date < to_date('20010117', 'YYYYMMDD') + 1

          Followup:

variation on a theme.  same thing as the between does yes.

      Rating:
      4
  February 28, 2002
Reviewer:
      Stuart  from Scotland
I've also used + .99999 (5 nines) which appears to set the time to 23:59:59 - I
find this a wee bit easier to remember than (1-1/24/60/60)!

ie
where date between to_date( p_fromdate, 'dd-mon-yy' )
                 and to_date( p_todate, 'dd-mon-yy' ) + .99999

          Followup:

Depends on how you think I guess.  I'm trained to see:

1-1/24/60/60

and think that's 1 day minus 1 second.  I see it so often -- that .99999 would
confuse the heck out of me.  I'm so used to adding an hour (1/24)  or a minute
(1/24/60) or so on -- that I always use that. Even when asked to add 4 hours 20
minutes, i'll code:

4*(1/24) + 20*(1/24/60)

      Rating: 5
Does this means
      all Dates in April ...?  April 29, 2002
Reviewer:
      Pascal  from Hamburg,Germany
Hi Tom,
Using your logic i note that my query is using the Index and is very fast...

 select count(*)  from  my_big_table
 where idx_date_col
 between to_date('01.04.2002' , 'DD.MM.YYYY')  and
last_day(to_date('04.2002' ,'MM.YYYY') ) + 1 - 1/24*60*60) ;

I want to use that to get all records  in April , 2002 .

Do you think it's correct ?

Thanks

          Followup:

Nope:

  1  select
  2  (last_day(to_date('04.2002' ,'MM.YYYY') ) + 1 - 1/24*60*60)
  3* from dual
ops$tkyte@ORA817DEV.US.ORACLE.COM> /

(LAST_DAY(TO_DATE('0
--------------------
02-dec-2001 00:00:00

this:

  1  select
  2  (last_day(to_date('04.2002' ,'MM.YYYY') ) + 1 - 1/24/60/60)
  3* from dual
ops$tkyte@ORA817DEV.US.ORACLE.COM> /

(LAST_DAY(TO_DATE('0
--------------------
30-apr-2002 23:59:59

is what you want (/60/60 NOT *60*60)

      Rating: 5
Sorry , I
      forgot the ( Bracket there)   April 29, 2002

Reviewer:  Pascal  from Hamburg,Germany
Hi Tom ,

Thanks for your prompt reply ,

Sorry i  forgot the Left Bracket in the Denominator :

It should have been :

select last_day(to_date('04.2002' ,'MM.YYYY') ) + 1 -
  1/(24*60*60) from dual;

          Followup:

That is correct then.

      Rating: 3
Stateless vs
      Statefull  April 30, 2002
Reviewer:  Johan
      Snyman  from JHB SA
Wouldn't connection pooling done by modplsql also result in connected but idle
sessions with the web app's usernames in v$session, regardless of stateless or
statefull configuration ?

One could look in the DAD configuration file (if using modplsql) and look at the
stateful = setting (No for stateless, Yes for stateful), or alternatively look
at the setting via the DAD admin URL (which we have completely disabled for
security reasons).

The file is in $ORACLE_HOME/Apache/modplsql/cfg

Or DAD setting have stateful = No and v$session filtered for session with the
appserver machine field looks like this:

select sid,serial#,username,status,server from v$session where machine = 'e250';
SID SERIAL# USERNAME   STATUS   SERVER
 32 22027   HENDRIKAB  INACTIVE DEDICATED
 33 55660   ALLASTAIRH INACTIVE DEDICATED
 34 41854   HENDRIKAB  INACTIVE DEDICATED
 38 43595   HENDRIKAB  INACTIVE DEDICATED
 49 56163   HENDRIKAB  INACTIVE DEDICATED
 58 28926   HENDRIKAB  INACTIVE DEDICATED
 65 9406    HENDRIKAB  INACTIVE DEDICATED
 79 47751   HENDRIKAB  INACTIVE DEDICATED

As you can see, the same user doesn't always pick up the same session, but if
someone else login via the web app, one of the idle entries just get replaced,
e.g.:

select sid,serial#,username,status,server from v$session where machine = 'e250';
SID SERIAL# USERNAME   STATUS   SERVER
 32 22027   HENDRIKAB  INACTIVE DEDICATED
 33 55660   ALLASTAIRH INACTIVE DEDICATED
 34 41856   JOHANS     INACTIVE DEDICATED
 38 43595   HENDRIKAB  INACTIVE DEDICATED
 49 56163   HENDRIKAB  INACTIVE DEDICATED
 58 28926   HENDRIKAB  INACTIVE DEDICATED
 65 9406    HENDRIKAB  INACTIVE DEDICATED
 79 47751   HENDRIKAB  INACTIVE DEDICATED

Therefore, queries on v$session definitely doesn't decisively indicate stateless
or statefull operation. As far as I can see, one would have to look at the
config file, or maybe you'll have to look at v$session over time after changing
to statefull and verify that the sid/serial#/USERNAME combination never change
for the login in question, or maybe start more statefull connections than what
is available in the pool (I haven't tested what happens in that case - our test
environment is not online at the moment).

          Followup:

You are correct.  I never use (or even consider using) "stateful" connections
myself.

Yes, v$session won't tell you if it is stateless or stateful.

      Rating: 5
Still wondering
      about some of this  May 21, 2002
Reviewer:  Bill
      Bunke  from Madison, WI USA
First of all, your two rules cannot be said often enough. Thanks for emphasizing
them! (I am also surprised that we did not truly learn our lesson from y2k.)

Regarding date ranges: I too have had to deal with this type of question from
colleagues. My answer varies slightly in that I feel uncomfortable in
hard-coding the last possible moment in the day (1 second before midnight
currently).

With the addition of timestamps in 9i, I can foresee where they will become
universal in date-time usages, and I don't rule out the possibility that their
granularity may find its way into date typed values (and shouldn't we all be
encouraging code that avoids possible future problems?!).

Because of those reasons, I've always recommended the following approach for
date ranges (to select inclusive dates from Jan 1st 2002 to Jan 17th 2002):

WHERE 1=1
AND   dt_col BETWEEN TO_DATE('01-jan-2002','dd-mon-yyyy')
                 AND TO_DATE('17-jan-2002','dd-mon-yyyy')+1
AND   dt_col <>      TO_DATE('17-jan-2002','dd-mon-yyyy')

This to me is a universal definition of inclusiveness for dates (in the generic
sense, including DATE and TIMESTAMP data formats).

It might be worth considering having a LAST_TIME built-in function developed,
that returned the last possible date-time value for the date parameter passed,
since this scenario is so prevalent (after all, LAST_DAY as a built-in is not
really needed [it is the same as TRUNC (ADD_MONTHS(your_date,1) , 'MON')-1].

Thanks.

          Followup:

That'll work -- that is true, with the TIMESTAMP type in 9i -- the 23:59:59
would miss 25:59:59.99999

      Rating: 4
Why so much
      trouble?  May 22, 2002
Reviewer:  Kim Berg
      Hansen  from Denmark
Followup to Bill's review.

This seems a bit excessive to me :

AND   dt_col BETWEEN TO_DATE('01-jan-2002','dd-mon-yyyy')
                 AND TO_DATE('17-jan-2002','dd-mon-yyyy')+1
AND   dt_col <>      TO_DATE('17-jan-2002','dd-mon-yyyy')

I would have used :

AND   dt_col >= TO_DATE('01-jan-2002','dd-mon-yyyy')
AND   dt_col <  TO_DATE('17-jan-2002','dd-mon-yyyy')+1

But since all you big brains seems to want to use BETWEEN - I wonder if there's
a reason for that ?

Does the optimizer work better with BETWEEN than my way of doing it ?

Regards

Kim Berg Hansen
Senior Systems Engineer
T.Hansen Group
Denmark

          Followup:

Well -- on the "big brains" comment -- we are all just people.  He saw my idea,
saw the flaw (one second won't work in 9i with a timestamp type!) and corrected
it.  You just refined it -- that makes you a big brain too I guess!

That works as well -- very well and it is more terse.  The other (with the
between) seems to "say more" to me (when reading it) however the <> part does
tend to confuse it for other readers.

Thanks for the followup.

      Rating: 4
Does Trunc will
      stop the index  May 22, 2002
Reviewer:
      Yamani  from Saudi Arabia
For the example above :

where dt_col between to_date( '01-jan-2002 00:00:00', 'dd-mon-yyyy hh24:mi:ss' )
                 and to_date( '17-jan-2002 23:59:59', 'dd-mon-yyyy hh24:mi:ss' )

Can't we use TRUNC instead:
AND   TRUNC(dt_col) >= TO_DATE('01-jan-2002','dd-mon-yyyy')
AND   TRUNC(dt_col) <  TO_DATE('17-jan-2002','dd-mon-yyyy')

Is this have any problem with Index.

Thanks,

          Followup:

unless you have an index on TRUNC(dt_col) -- yes, using trunc on dt_col would
obviate an index on dt_col.

      Rating: 4
followup to
      Bill's review  May 23, 2002
Reviewer:
      A reader
shouldn't the statement

WHERE 1=1
AND   dt_col BETWEEN TO_DATE('01-jan-2002','dd-mon-yyyy')
                 AND TO_DATE('17-jan-2002','dd-mon-yyyy')+1
AND   dt_col <>      TO_DATE('17-jan-2002','dd-mon-yyyy')

be

WHERE 1=1
AND   dt_col BETWEEN TO_DATE('01-jan-2002','dd-mon-yyyy')
                 AND TO_DATE('17-jan-2002','dd-mon-yyyy')+1
AND   dt_col <>      TO_DATE('18-jan-2002','dd-mon-yyyy')

      Rating: 2
Bill's followup
      to A Readers followup   May 28, 2002
Reviewer:
      Bill Bunke  from Madison, WI USA
Yes, there was a typo in the sample code snippet posted; however, your fix,
although accurate, would not be the syntax of choice. Introducing another data
value (which of course would be actually handled by a host variable) is not
efficient; all you want or need to have are the two dates signifying the beg/end
you are interested in.

Here is an amended snippet:

WHERE 1=1
AND   dt_col BETWEEN TO_DATE(:v_beg_dt,'dd-mon-yyyy')
                 AND TO_DATE(:v_end_dt,'dd-mon-yyyy')+1
AND   dt_col <>      TO_DATE(:v_end_dt,'dd-mon-yyyy')+1

-- where :v_beg_dt = '01-JAN-2002'
-- and   :v_end_dt = '17-JAN-2002'
-- or some other values (perhaps parameter driven)

Taking others comments into account, I still think Oracle should provide an
End-of-Day functionality, either with a LAST_TIME function or perhaps an EOD
format suffix (adds last possible time component to supplied date).

Thanks for your comments.

      Rating: 5
To_date
      PROBLEM  August 16, 2002
Reviewer:  atul
       from india
Sir,

I have a query like..

SELECT A.DIV_CD,D.DIV_DESc
 FROM TEST1 A,TEST2 B WHERE TO_DATE(RCIA_DT)=TO_DATE(:b1)AND ("SOME MORE
STATEMENTS")

now because of TO_DATE(RCIA_DT)=TO_DATE(:b1) it's doing full table scan so how
to avoid that full table scan??

Thanks.
atul

          Followup:

Sounds like RCIA_DT is a string then (else why would you to_date a date right)

So, if RCIA_DT is a string, just use "where rcia_dt = :b1 and ...."

QED

If rcia_dt is in fact a date, lose the to_date (cause you could easily corrupt a
date by using to_date on it!)

Consider this (and think through WHY it is happening, hint, my NLS_DATE_FORMAT
is dd-mon-rr)

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x date );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( to_date(
'01-jan-1904', 'dd-mon-yyyy' ) );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select x, to_date(x), to_date(x) - x,
to_char(to_date(x),'dd-mon-yyyy')
  2    from t;

X         TO_DATE(X TO_DATE(X)-X TO_CHAR(TO_
--------- --------- ------------ -----------
01-JAN-04 01-JAN-04        36525 01-jan-2004

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

to_date(x) is not equal to x if X is a date in this example!!!

      Rating: 5
date
      insert  January 09, 2003
Reviewer:  mo

Tom:

if you have a html form where you prompt for an "effective date" and that is
also part of the primary key in the table, oracle inserts the data with 00:00:00
timestamp which creates a problem in validating primary key.

SO I want to insert the date with the time the user entered with it:

Would you do insert into table(data) values
(to_Date(p_date)||to_char(sysdate,'hh24:mi:ss') )

OR you would add the time from 00:00:00 to current system time to the date.
How do you convert the current time to number and add it to date with 00:00:00
timestamp.

2. is there a way to update a timestamp for a date in the database?

Thank you,

          Followup:

why that would "create a problem in validating primary key" is way beyond my
understanding.

Why would you add the rather RANDOM string of digits that happen to be the
current hour, minute, second?  Why not just use 00:00:00 (midnight)?  It is as
VALID AS ANY OTHER TIME OF THE DAY is it not????

I would use 00:00:00 and figure out why I did wrong in my model that I felt
compelled to add a random number to my date to "fix a validation problem"

update updates data -- yes. just update the data???

      Rating: 3
another
      query  January 09, 2003
Reviewer:  PK
       from ALASKA
I have a table which has column declared as varchar2.
Date is stored in that field. Why ? Its existing system so I
can't ask it.
Coming to my concern, Now the people have realised there mistake want to correct
the data. So, is there a way I can get only the correct date fields and avoid
the other records ?
here is sample data from the table :-
PK>select * from temp1;

ID
---------------------------------------------------------------------------------
-------------------
2002-01-01
2002-01-23
2002-13-01
2002-12-31
2002-12-34
200 -12-01
 002-12 01
  02- 1- 1
2002 01 01
2002- 4-01
2002-12- 9

11 rows selected.

Elapsed: 00:00:00.80

I have written something like this
PK>l
  1  select to_date(id,'yyyy-mm-dd') from temp1
  2  where length(id) = 10
  3    and length(ltrim(rtrim(substr(id,1,4)))) = 4
  4    and substr(id,5,1) = '-'
  5    and substr(id,8,1) = '-'
  6    and sign(substr(id,6,2) - 12) < 1
  7*   and sign(substr(id,9,2) - 31) < 1
PK>/

TO_DATE(I
---------
01-JAN-02
23-JAN-02
31-DEC-02
01-APR-02
09-DEC-02

5 rows selected.

Elapsed: 00:00:00.40
PK>

IS there a better way ?

Thanks
PK

          Followup:

You can:

create or replace function my_to_date( p_string in varchar2 ) return date
as
begin
   return to_date( p_string, 'yyyy-mm-dd' );
exception
   when others then return null;
end;

instead -- then

select my_to_date( id ) from temp1;

      Rating: 3
Wrong result
      set  January 10, 2003
Reviewer:  PK
       from ALASKA
Thanks for the immediate response.
But when I'm using the function I see some discrepancy :-
HEre is the data from the table
PK>select * from temp1;

ID
----------------------------------
2002-01-01
2002-01-23
2002-13-01
2002-12-31
2002-12-34
200 -12-01
 002-12 01
  02- 1- 1
2002 01 01
2002- 4-01
2002-12- 9
2003-10-10

12 rows selected.

Elapsed: 00:00:00.10

The blanks are null.

PK>l
  1  CREATE OR REPLACE FUNCTION MY_to_Date
  2    (v_inp_str IN VARCHAR2)
  3    RETURN  date IS
  4  BEGIN
  5
  6      RETURN to_date(v_inp_str,'YYYY-MM-DD');
  7  EXCEPTION
  8     WHEN OTHERS THEN
  9       return  NULL ;
 10* END;
PK>/

Function created.

Elapsed: 00:00:00.60
PK>

PK>select my_to_date( id ) from temp1;

MY_TO_DAT
---------
01-JAN-02
23-JAN-02

31-DEC-02

01-DEC-00
01-DEC-02
01-JAN-02
01-JAN-02
01-APR-02
09-DEC-02
10-OCT-03

12 rows selected.

Elapsed: 00:00:00.50
PK>

There are blank lines in between thats fine but I have a question regarding the
number of columns returned .

PK>select to_char(my_to_date(id),'dd-mon-yyyy') from temp1;

TO_CHAR(MY_
-----------
01-jan-2002
23-jan-2002

31-dec-2002

01-dec-0200
01-dec-0002
01-jan-0002
01-jan-2002
01-apr-2002
09-dec-2002
10-oct-2003

12 rows selected.

Elapsed: 00:00:00.70

Why are the null spaces treated as zeros ??

Thanks in advance.

          Followup:

null spaces are not null spaces.  they are blanks.

put whatever logic you need into my_to_date to make it work for you.

      Rating: 4
like ,
      =  February 21, 2003
Reviewer:  umesh
       from bangalore India
  1   select to_date ( hiredate,'dd-mon-yyyy:hh24-mi-ss') from emp
  2   where to_date(hiredate,'dd-mon-yyyy:hh24-mi-ss') +1  =
  3*  to_date('24-JAN-1982:12-00-00','dd-mon-yyyy:hh24-mi-ss')
SQL> /

no rows selected

  1   select to_date ( hiredate,'dd-mon-yyyy:hh24-mi-ss') from emp
  2   where to_date(hiredate,'dd-mon-yyyy:hh24-mi-ss') +1  like
  3*  to_date('24-JAN-1982:12-00-00','dd-mon-yyyy:hh24-mi-ss')
SQL> /

TO_DATE(H
---------
23-JAN-82

          Followup:

what is your point?  you get what you ask for.

your default date format is apparently dd-mon-yy or maybe rr.

to_date(hiredate,'dd-mon-yyyy:hh24-mi-ss')

is really:

to_date( TO_CHAR(hiredate,'dd-mon-yy'), 'dd-mon-yyyy:hh24-mi-ss' )

there are soooo many things wrong with these two queries -- where to begin....

a) first you are using to_date on a column that is ALREADY A DATE.  That
means hiredate is turned into a string using the default format (dd-mon-rr) and
then converted back into a date -- and you used YYYY sooooo guess what year that
hiredate is???  1982 or 2082 or maybe even something more bizzare??  Watch:

scott@ORA920> select to_date(hiredate,'dd-mon-yyyy:hh24-mi-ss'),
  2         to_char(to_date(hiredate,'dd-mon-yyyy:hh24-mi-ss'),'yyyy'),
  3         to_char(hiredate,'yyyy')
  4    from emp
  5  /

TO_DATE(H TO_C TO_C
--------- ---- ----
17-DEC-80 0080 1980
20-FEB-81 0081 1981
22-FEB-81 0081 1981
02-APR-81 0081 1981
28-SEP-81 0081 1981
01-MAY-81 0081 1981
09-JUN-81 0081 1981
09-DEC-82 0082 1982
17-NOV-81 0081 1981
08-SEP-81 0081 1981
12-JAN-83 0083 1983
03-DEC-81 0081 1981
03-DEC-81 0081 1981
23-JAN-82 0082 1982

14 rows selected.

so,
rule number 1 -- beware of IMPLICIT CONVERSIONS OF DATES and NUMBERS!!!
rule number 2 -- never to_number a number, never to_date a date

b) in the second query you make the same mistake as the first but get
unintentially "lucky".  LIKE works on strings -- sooooo that predicate is "where
string like string".  Soooo there is yet another implicit conversion, there are
to_char's wrapped about those to_dates:

select to_date ( hiredate,'dd-mon-yyyy:hh24-mi-ss')
  from emp
 where TO_CHAR(to_date(hiredate,'dd-mon-yyyy:hh24-mi-ss') +1)  like
       TO_CHAR(to_date('24-JAN-1982:12-00-00','dd-mon-yyyy:hh24-mi-ss'))
/

so, yes, when you COMPARE the strings:

23-JAN-82 with 23-JAN-82

they are "like" eachother.  However, if you looked at the REAL DATES -- you see
that you just had a hit on

23-jan-0082 with 23-JAN-1982

eg: you got the wrong answer in general.

sooo

rule number 3, compare dates to dates, strings to strings, numbers to numbers
and never anything other combination

rule number 4, always be on the lookout for implicit conversions.  You have 6 of
them in your two queries.  They are the cause of all of your problems

      Rating: 5
date = date
      character like character  February 23, 2003

Reviewer:  umesh  from bangalore India
Thank you Tom. I learn something daily from you

      Rating: 4
problems with
      date?  March 07, 2003
Reviewer:  Alejandro
      Berrones Cruz  from Mexico
I did a SQL like this

select empleid, emple
from emp
where hiredate = to_date('05/03/2003','dd/mm/yyyy')

But there are not records... I have 10 records with that hiredate in emp table
I´m using Oracle 9i.... and the hiredate field is a date type

          Followup:

no, you don't.  Plain and simple, you do not have 10 records with that date.

select to_date( hiredate, 'mm/dd/yyyy hh24:mi:ss' ) from emp
where to_char(hiredate,'mm/dd/yyyy') = '05/03/2003';

report back what THAT says.

      Rating: 5
Date
      conversion.  May 30, 2003
Reviewer:  A
      Reader  from USA
Hi Tom,
Do we have a function or way to format the date difference to "dd hh24:mi:ss"
format?  For example, after minus date2 from from date1, we get 0.25.  Can we
convert it into '6:00:00'?
Thank you for your help.

          Followup:

in 9i, there is an interval type -- so yes.

in 8i, diy:

http://asktom.oracle.com/~tkyte/Misc/DateDiff.html

      Rating: 4
Interval
      Arithmatic on systimestamp?  August 07, 2003

Reviewer:  jochuan  from VA, USA
Tom

   I changed the datatype of datetime columns in my batch_log table
(BATCH_LOG.START_DATETIME and BATCH_LOG.END_DATETIME) from sysdate to
systimestamp to get more detailed time information.

I used to be able to do

select batch_type
, avg(end_datetime-start_datetime)
, max(end_datetime-start_datetime)
, min(end_datetime-start_datetime)
group by batch_type

now with the systimestamp data type, I cannot do avg() or any aggregrate
functions because it errors out

0RA-00932: INCONSISTENT DATATYPES: expected NUMBER got INTERVAL

In this case, is there a way to do interval arithmatic like I used to do?(does
not seem to be supported in the manual)

Thanks so much!

          Followup:

you would have to implement a user defined aggregate right now.

here is a simple sum

create or replace type day_to_second_sum_type as object
(
   total interval day to second,

   static function
        ODCIAggregateInitialize(sctx IN OUT day_to_second_sum_type )
        return number,

   member function
        ODCIAggregateIterate(self IN OUT day_to_second_sum_type ,
                             value IN interval day to second )
        return number,

   member function
        ODCIAggregateTerminate(self IN day_to_second_sum_type,
                               returnValue OUT  interval day to second,
                               flags IN number)
        return number,

   member function
        ODCIAggregateMerge(self IN OUT day_to_second_sum_type,
                           ctx2 IN day_to_second_sum_type)
        return number
);
/
create or replace type body day_to_second_sum_type
is

static function ODCIAggregateInitialize(sctx IN OUT day_to_second_sum_type)
return number
is
begin
    sctx := day_to_second_sum_type( numtodsinterval( 0, 'SECOND' ) );
    return ODCIConst.Success;
end;

member function ODCIAggregateIterate(self IN OUT day_to_second_sum_type,
                                     value IN interval day to second )
return number
is
begin
    self.total := self.total + value;
    return ODCIConst.Success;
end;

member function ODCIAggregateTerminate(self IN day_to_second_sum_type,
                                       returnValue OUT interval day to second,
                                       flags IN number)
return number
is
begin
    returnValue := self.total;
    return ODCIConst.Success;
end;

member function ODCIAggregateMerge(self IN OUT day_to_second_sum_type,
                                   ctx2 IN day_to_second_sum_type)
return number
is
begin
    self.total := self.total + ctx2.total;
    return ODCIConst.Success;
end;

end;
/

CREATE or replace
FUNCTION ds_sum(input interval day to second )
RETURN interval day to second
PARALLEL_ENABLE AGGREGATE USING day_to_second_sum_type;
/

      Rating:
      4
  August 28, 2003
Reviewer:  reader

Tom,
I need to get the past 12 months information (begining from current month), I
used the following query, I felt not safety. Because the 2002 i_date might  be
0002 or 2002 , so I can not get the correct result. Can you have a good idea ?

SELECT I_DATE FROM T
WHERE
TO_CHAR(ADD_MONTHS(sysdate,-11),'YYYYMM') < TO_CHAR(I_DATE,'YYYYMM') AND
TO_CHAR(I,'YYYYMM') <= TO_CHAR(sysdate,'YYYYMM');

Thank you!

          Followup:

umm, and exactly why might the i_date be in the year "2"????

select i_date
from t
where i_date >= add_months(sysdate,12);

would be the most appropriate way to do that and if you actually have dates with
the year "2" in them -- you need to *fix* that data.  it is obviously wrong.

August 29, 2003 H
      -- Thanks for the question regarding "Statspack Buffer Hit %=8.75",
      version 8.1.7

      You Asked
What are my options when :
1. I have Buffer Hit % = 8.75 (target is 100%)
2. db file sequential read (waits) = 3200691

Thanks.

      and we said...
goto www.hotsos.com and read

• Are you still using cache hit ratios?

Let me ask you a question.  On my last car trip, I had to stop and wait for 500
red lights.  Is that good or bad?

No matter what you answer, I'll successfully argue the counter is actually true.
 You cannot look at a number -- 3,200,691 -- and draw ANY conclusions.  Even the
8.75% is meaningless without tons of context.

Is that over a 5 minute window or a 5000 minute window.
Are you a data warehouse with lots of full scans (cache hit = low)
Are you a high end OLTP system (cache hit will be low, but 8.75% might be a tad
lower then normal)

Have you looked at your system in a holistic sense, are your applications TUNED
to death.  Or are you starting at the end -- trying to tune the system when you
need to start with the applications.
Review &
      Followup

      Rating: 5
traffic
      lights  August 18, 2002
Reviewer:
      A reader
why did you actualy count how many times you had to stop at the red light
inspiration from jonathan lewis presentation at the OOW

          Followup:

He actually asked me if he could borrow that example -- I've been using it for
years ;)

That was when he delivered is "fan hit ratio" paper right -- that was pretty
funny.

      Rating: 5
Getting the hit
      ratio you want!  August 18, 2002
Reviewer:  Mark
      J. Bobak  from Dearborn Heights, MI
I will just add, if you really want a better hit ratio,
go to Connor McDonald's website:
http://www.oracledba.co.uk/
and click 'Tuning' and 'Custom Hit Ratio'.  Download and
run that script.  Enter the hit ratio you want.  Wait for
it to run.  Viola!  When it's done, your hit ratio will be
exactly what you asked for!

Now stop, think.  What just happened?

You consumed a bunch of resources, and your hit ratio
improved.  Did that have any bearing on the overall health
or performance of your instance?

      Rating: 2
Statspack
      Buffer Hit %=8.75  August 19, 2002
Reviewer:
      h  from USA
The snapshot window is 15 min. It a case tool used for generating code for 3rd
party app. It does perform inserts/updates with only 1 or 2 users.

Maybe this will help narrow down my options.

          Followup:

Well, given the information you gave -- I can still say this "nothing".

It could be totally normal, if the tool reads most all of the database that is,
or if your buffer cache is really too small.

You need to look at the *system*, not just these two numbers.  I cannot give you
a magic setting like "set block buffers to X", you need to trade off the size of
your SGA with the amount of ram you have, how many dedicated servers you need to
support and so on.

If you post the statspack report and a description of your box -- ram, etc (the
first 30,000 bytes as thats all that'll fit in these little text windows), I'm
sure we'll comment on it.  These two numbers in isolation -- MEANINGLESS.

      Rating: 2
Buffer
      Hit  August 19, 2002
Reviewer:  H  from
      USA
Condensed version:
HP N class, 6CPU, 2G RAM

STATSPACK report for

DB Name         DB Id    Instance     Inst Num Release     OPS Host
------------ ----------- ------------ -------- ----------- --- ------------
CSE60         4093422558 cse60               1 8.1.7.4.0   NO  himhpp01

                Snap Id     Snap Time      Sessions
                ------- ------------------ --------
 Begin Snap:         11 19-Aug-02 11:35:21       15
   End Snap:         12 19-Aug-02 11:49:41       15
    Elapsed:                  14.33 (mins)

Cache Sizes
~~~~~~~~~~~
           db_block_buffers:       5500          log_buffer:     163840
              db_block_size:       8192    shared_pool_size:   75000000

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:              6,039.36             20,053.47
              Logical reads:              6,306.61             20,940.86
              Block changes:                 40.86                135.67
             Physical reads:              5,601.12             18,598.30
            Physical writes:                120.13                398.87
                 User calls:                 10.17                 33.78
                     Parses:                 15.53                 51.56
                Hard parses:                  0.11                  0.37
                      Sorts:                  0.55                  1.82
                     Logons:                  0.00                  0.00
                   Executes:                 24.30                 80.69
               Transactions:                  0.30

  % Blocks changed per Read:    0.65    Recursive Call %:   94.27
 Rollback per transaction %:    0.00       Rows per Sort:   95.99

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:   99.99
            Buffer  Hit   %:   11.19    In-memory Sort %:  100.00
            Library Hit   %:   99.11        Soft Parse %:   99.28
         Execute to Parse %:   36.10         Latch Hit %:  100.00
Parse CPU to Parse Elapsd %:             % Non-Parse CPU:

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   12.95   15.46
    % SQL with executions>1:   61.40   65.71
  % Memory for SQL w/exec>1:   49.25   69.30

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
db file sequential read                         4,803,110            0     .00
direct path write                                     812            0     .00
control file sequential read                          475            0     .00
log file parallel write                               466            0     .00
db file parallel write                                292            0     .00
          -------------------------------------------------------------
 Wait Events for DB: CSE60  Instance: cse60  Snaps: 11 -12
-> cs - centisecond -  100th of a second
-> ms - millisecond - 1000th of a second
-> ordered by wait time desc, waits desc (idle events last)

                                                                    Avg
                                                     Total Wait    wait  Waits
Event                               Waits   Timeouts  Time (cs)    (ms)   /txn
---------------------------- ------------ ---------- ----------- ------ ------
db file sequential read         4,803,110          0           0      0 ######
direct path write                     812          0           0      0    3.1
control file sequential read          475          0           0      0    1.8
log file parallel write               466          0           0      0    1.8
db file parallel write                292          0           0      0    1.1
control file parallel write           288          0           0      0    1.1
log file sync                         257          1           0      0    1.0
direct path read                      112          0           0      0    0.4
SQL*Net break/reset to clien          104          0           0      0    0.4
refresh controlfile command            92          0           0      0    0.4
file open                              41          0           0      0    0.2
latch free                             13         13           0      0    0.1
db file scattered read                 12          0           0      0    0.0
file identify                           2          0           0      0    0.0
log file single write                   2          0           0      0    0.0
LGWR wait for redo copy                 1          0           0      0    0.0
log file switch completion              1          0           0      0    0.0
log file sequential read                1          0           0      0    0.0
enqueue                                 1          0           0      0    0.0
SQL*Net message from client         8,433          0           0      0   32.6
SQL*Net message to client           8,433          0           0      0   32.6
          -------------------------------------------------------------
 Background Wait Events for DB: CSE60  Instance: cse60  Snaps: 11 -12
-> ordered by wait time desc, waits desc (idle events last)

                                                                    Avg
                                                     Total Wait    wait  Waits
Event                               Waits   Timeouts  Time (cs)    (ms)   /txn
---------------------------- ------------ ---------- ----------- ------ ------
log file parallel write               467          0           0      0    1.8
db file parallel write                292          0           0      0    1.1
control file parallel write           288          0           0      0    1.1
control file sequential read          200          0           0      0    0.8
file open                              35          0           0      0    0.1
db file sequential read                19          0           0      0    0.1
direct path read                        7          0           0      0    0.0
direct path write                       7          0           0      0    0.0
db file scattered read                  2          0           0      0    0.0
file identify                           2          0           0      0    0.0
log file single write                   2          0           0      0    0.0
latch free                              2          2           0      0    0.0
LGWR wait for redo copy                 1          0           0      0    0.0
enqueue                                 1          0           0      0    0.0
log file sequential read                1          0           0      0    0.0
rdbms ipc message                   4,132      1,388           0      0   16.0
pmon timer                            279        279           0      0    1.1
smon timer                              3          3           0      0    0.0
          -------------------------------------------------------------

Instance Activity Stats for DB: CSE60  Instance: cse60  Snaps: 11 -12

Statistic                                    Total   per Second    per Trans
--------------------------------- ---------------- ------------ ------------
CR blocks created                            4,808          5.6         18.6
DBWR buffers scanned                       132,985        154.6        513.5
DBWR checkpoint buffers written                  4          0.0          0.0
DBWR checkpoints                                 1          0.0          0.0
DBWR free buffers found                    132,327        153.9        510.9
DBWR lru scans                                 328          0.4          1.3
DBWR make free requests                        328          0.4          1.3
DBWR summed scan depth                     132,985        154.6        513.5
DBWR transaction table writes                   22          0.0          0.1
DBWR undo block writes                         358          0.4          1.4
SQL*Net roundtrips to/from client            8,432          9.8         32.6
background checkpoints completed                 1          0.0          0.0
background checkpoints started                   1          0.0          0.0
background timeouts                          1,401          1.6          5.4
buffer is not pinned count               5,076,200      5,902.6     19,599.2
buffer is pinned count                  28,346,131     32,960.6    109,444.5
bytes received via SQL*Net from c          895,445      1,041.2      3,457.3
bytes sent via SQL*Net to client         1,346,215      1,565.4      5,197.7
calls to get snapshot scn: kcmgss           28,796         33.5        111.2
calls to kcmgas                              2,088          2.4          8.1
calls to kcmgcs                                 43          0.1          0.2
cleanouts only - consistent read                 3          0.0          0.0
cluster key scan block gets                208,771        242.8        806.1
cluster key scans                           46,591         54.2        179.9
commit cleanouts                             3,967          4.6         15.3
commit cleanouts successfully com            3,967          4.6         15.3
consistent changes                           6,419          7.5         24.8
consistent gets                          5,388,971      6,266.3     20,806.8
cursor authentications                          12          0.0          0.1
data blocks consistent reads - un            4,807          5.6         18.6
db block changes                            35,138         40.9        135.7
db block gets                               34,712         40.4        134.0
deferred (CURRENT) block cleanout            2,914          3.4         11.3
dirty buffers inspected                        277          0.3          1.1
enqueue releases                            11,195         13.0         43.2
enqueue requests                            11,196         13.0         43.2
execute count                               20,899         24.3         80.7
free buffer inspected                          280          0.3          1.1
free buffer requested                    4,808,672      5,591.5     18,566.3
hot buffers moved to head of LRU            29,422         34.2        113.6
immediate (CR) block cleanout app                3          0.0          0.0
immediate (CURRENT) block cleanou               40          0.1          0.2
index fast full scans (full)                    43          0.1          0.2
leaf node splits                                 9          0.0          0.0
logons cumulative                                1          0.0          0.0
logons current
messages received                            3,077          3.6         11.9
messages sent                                3,077          3.6         11.9
no buffer to keep pinned count                   0          0.0          0.0
no work - consistent read gets           5,340,602      6,210.0     20,620.1
opened cursors cumulative                   13,378         15.6         51.7
parse count (hard)                              96          0.1          0.4
parse count (total)                         13,354         15.5         51.6
physical reads                           4,816,959      5,601.1     18,598.3
physical reads direct                       13,342         15.5         51.5
physical writes                            103,308        120.1        398.9
 Instance Activity Stats for DB: CSE60  Instance: cse60  Snaps: 11 -12

Statistic                                    Total   per Second    per Trans
--------------------------------- ---------------- ------------ ------------
physical writes direct                     102,369        119.0        395.3
physical writes non checkpoint             103,307        120.1        398.9
pinned buffers inspected                         1          0.0          0.0
prefetched blocks                               29          0.0          0.1
recursive calls                            143,832        167.3        555.3
redo blocks written                          5,403          6.3         20.9
redo buffer allocation retries                   1          0.0          0.0
redo entries                                18,346         21.3         70.8
redo log space requests                          1          0.0          0.0
redo size                                5,193,848      6,039.4     20,053.5
redo synch writes                              260          0.3          1.0
redo wastage                               247,032        287.3        953.8
redo writes                                    467          0.5          1.8
rollbacks only - consistent read             4,807          5.6         18.6
rows fetched via callback                    3,149          3.7         12.2
session logical reads                    5,423,683      6,306.6     20,940.9
session pga memory                      20,383,472     23,701.7     78,700.7
session pga memory max                  20,307,752     23,613.7     78,408.3
session uga memory                          80,024         93.1        309.0
session uga memory max                  19,999,360     23,255.1     77,217.6
sorts (memory)                                 472          0.6          1.8
sorts (rows)                                45,306         52.7        174.9
summed dirty queue length                       63          0.1          0.2
switch current to new buffer
table fetch by rowid                    16,624,200     19,330.5     64,186.1
table fetch continued row                        0          0.0          0.0
table scan blocks gotten                     1,107          1.3          4.3
table scan rows gotten                       4,761          5.5         18.4
table scans (long tables)                        0          0.0          0.0
table scans (short tables)                     141          0.2          0.5
total file opens                                41          0.1          0.2
user calls                                   8,749         10.2         33.8
user commits                                   259          0.3          1.0
          -------------------------------------------------------------
 Tablespace IO Stats for DB: CSE60  Instance: cse60  Snaps: 11 -12
->ordered by IOs (Reads + Writes) desc

Tablespace
------------------------------
                 Av      Av     Av                    Av        Buffer Av Buf
         Reads Reads/s Rd(ms) Blks/Rd       Writes Writes/s      Waits Wt(ms)
-------------- ------- ------ ------- ------------ -------- ---------- ------
IEFSENCY_TAB
     4,561,434   5,304    0.0     1.0          103        0          0    0.0
IEFSENCY_IDX
       239,801     279    0.0     1.0          393        0          0    0.0
IEFSENCY_TEMP
           150       0    0.0    89.2          847        1          0    0.0
IEFSENCY_ROLL
           158       0    0.0     1.0          379        0          0    0.0
SYSTEM
           215       0    0.0     1.1           17        0          0    0.0
          -------------------------------------------------------------
 File IO Stats for DB: CSE60  Instance: cse60  Snaps: 11 -12
->ordered by Tablespace, File

Tablespace               Filename
------------------------ ----------------------------------------------------
                 Av      Av     Av                    Av        Buffer Av Buf
         Reads Reads/s Rd(ms) Blks/Rd       Writes Writes/s      Waits Wt(ms)
-------------- ------- ------ ------- ------------ -------- ---------- ------
IEFSENCY_IDX             /maxdb1/cse60/idxcse60.dbf
       111,029     129    0.0     1.0          322        0          0
                         /maxdb1/cse60/idxcse60b.dbf
       128,772     150    0.0     1.0           71        0          0

IEFSENCY_ROLL            /maxdb1/cse60/rollcse60.dbf
           158       0    0.0     1.0          379        0          0

IEFSENCY_TAB             /maxdb1/cse60/tabcse60.dbf
     2,348,996   2,731    0.0     1.0           83        0          0
                         /maxdb1/cse60/tabcse60b.dbf
     2,212,438   2,573    0.0     1.0           20        0          0

IEFSENCY_TEMP            /maxdb1/cse60/tempcse60.dbf
           150       0    0.0    89.2          847        1          0

SYSTEM                   /maxdb1/cse60/dbscse60.dbf
           215       0    0.0     1.1           17        0          0

          -------------------------------------------------------------
 Buffer Pool Statistics for DB: CSE60  Instance: cse60  Snaps: 11 -12
-> Pools   D: default pool,  K: keep pool,  R: recycle pool

                                                      Free    Write     Buffer
       Buffer    Consistent    Physical   Physical  Buffer Complete       Busy
P        Gets          Gets       Reads     Writes   Waits    Waits      Waits
- ----------- ------------- ----------- ---------- ------- -------- ----------
D   4,807,278     5,348,727   4,802,243        931       0        0          0
          -------------------------------------------------------------
 Rollback Segment Stats for DB: CSE60  Instance: cse60  Snaps: 11 -12
->A high value for "Pct Waits" suggests more rollback segments may be required

       Trans Table      Pct   Undo Bytes
RBS No     Gets       Waits     Written        Wraps  Shrinks  Extends
------ ------------ ------- --------------- -------- -------- --------
     0          4.0    0.00               0        0        0        0
     2      7,454.0    0.00       1,591,334        0        0        0
     3        192.0    0.00          14,980        0        0        0
     4        176.0    0.00          13,256        0        0        0
     5        192.0    0.00          89,912        0        0        0
          -------------------------------------------------------------
 Rollback Segment Storage for DB: CSE60  Instance: cse60  Snaps: 11 -12
->Optimal Size should be larger than Avg Active

RBS No    Segment Size      Avg Active    Optimal Size    Maximum Size
------ --------------- --------------- --------------- ---------------
     0         401,408               0                         401,408
     2     128,196,608               0     127,959,040     128,196,608
     3     128,196,608               0     127,959,040     128,196,608
     4     128,196,608               0     127,959,040     128,196,608
     5     128,196,608               0     127,959,040     128,196,608
          -------------------------------------------------------------
 Latch Activity for DB: CSE60  Instance: cse60  Snaps: 11 -12
->"Get Requests", "Pct Get Miss" and "Avg Slps/Miss" are statistics for
  willing-to-wait latch get requests
->"NoWait Requests", "Pct NoWait Miss" are for no-wait latch get requests
->"Pct Misses" for both should be very close to 0.0

                                                Pct    Avg                 Pct
                                   Get          Get   Slps       NoWait NoWait
Latch Name                       Requests      Miss  /Miss     Requests   Miss
----------------------------- -------------- ------ ------ ------------ ------
Token Manager                             33    0.0                   0
active checkpoint queue latch          3,380    0.1    0.0            0
archive control                            5    0.0                   0
cache buffer handles                       4    0.0                   0
cache buffers chains              15,764,691    0.0    0.2    4,802,254    0.0
cache buffers lru chain               19,889    0.1    0.0    4,808,753    0.0
channel handle pool latch                  1    0.0                   0
channel operations parent lat              2    0.0                   0
checkpoint queue latch                19,728    0.0    0.0            0
dml lock allocation                   11,069    0.0                   0
enqueue hash chains                   22,382    0.0                   0
enqueues                              29,953    0.0                   0
event group latch                          1    0.0                   0
job_queue_processes parameter             13    0.0                   0
ktm global data                            3    0.0                   0
latch wait list                            1    0.0                   1    0.0
library cache                        179,851    0.0    0.2            0
library cache load lock                  294    0.0                   0
list of block allocation               4,195    0.0                   0
loader state object freelist               2    0.0                   0
messages                              12,791    0.2    0.1            0
mostly latch-free SCN                  2,556    0.0                   0
multiblock read objects                   44    0.0                   0
ncodef allocation latch                   13    0.0                   0
process allocation                         1    0.0                   1    0.0
process group creation                     1    0.0                   0
redo allocation                       19,441    0.0                   0
redo writing                           6,058    0.7    0.1            0
row cache objects                     85,217    0.0    0.0            0
sequence cache                             6    0.0                   0
session allocation                       875    0.0                   0
session idle bit                      17,934    0.0                   0
session switching                         13    0.0                   0
shared pool                           16,905    0.0                   0
sort extent pool                          17    0.0                   0
transaction allocation                 4,662    0.0                   0
transaction branch allocation             13    0.0                   0
undo global data                      14,921    0.0                   0
user lock                                  2    0.0                   0
          -------------------------------------------------------------
 Latch Sleep breakdown for DB: CSE60  Instance: cse60  Snaps: 11 -12
-> ordered by misses desc

                                Get                                  Spin &
Latch Name                    Requests         Misses      Sleeps Sleeps 1->4
-------------------------- -------------- ----------- ----------- ------------
redo writing                        6,058          42           4 38/4/0/0/0
cache buffers chains           15,764,691          32           5 27/5/0/0/0
messages                           12,791          29           2 27/2/0/0/0
cache buffers lru chain            19,889          21           1 20/1/0/0/0
library cache                     179,851           5           1 4/1/0/0/0
          -------------------------------------------------------------
 Latch Miss Sources for DB: CSE60  Instance: cse60  Snaps: 11 -12
-> only latches with sleeps are shown
-> ordered by name, sleeps desc

                                                     NoWait             Waiter
Latch Name               Where                       Misses     Sleeps  Sleeps
------------------------ -------------------------- ------- ---------- -------
cache buffers chains     kcbgtcr: kslbegin                0          4       1
cache buffers chains     kcbchg: kslbegin: bufs not       0          1       1
cache buffers lru chain  kcbzgb: multiple sets nowa       0          1       0
library cache            kglpnal: child: alloc spac       0          1       0
messages                 ksarcv: after wait               0          2       0
redo writing             kcrfsr                           0          4       0
          -------------------------------------------------------------
 Dictionary Cache Stats for DB: CSE60  Instance: cse60  Snaps: 11 -12
->"Pct Misses"  should be very low (< 2% in most cases)
->"Cache Usage" is the number of cache entries being used
->"Pct SGA"     is the ratio of usage to allocated size for that cache

                           Get         Pct     Scan   Pct      Mod  Final  Pct
Cache                    Requests     Miss Requests  Miss      Req  Usage  SGA
---------------------- ------------ ------ -------- ----- -------- ------ ----
dc_constraints                    0               0              0      0    0
dc_database_links                 0               0              0      0    0
dc_files                        301    2.3        0              0      7   64
dc_free_extents               3,294   50.7    1,606   0.0    3,188     64   27
dc_global_oids                    0               0              0      0    0
dc_histogram_data                 0               0              0      0    0
dc_histogram_data_valu            0               0              0      0    0
dc_histogram_defs               110   30.9        0              0    203   97
dc_object_ids                    67   23.9        0              0    154   96
dc_objects                      233   34.8        0              0    268   98
dc_outlines                       0               0              0      0    0
dc_profiles                       1    0.0        0              0      1   14
dc_rollback_segments             36    0.0        0              0      7   88
dc_segments                   1,692    0.7        0          1,613    112   93
dc_sequence_grants                0               0              0      0    0
dc_sequences                      0               0              0      3   25
dc_synonyms                      34   73.5        0              0     38   90
dc_tablespace_quotas              0               0              0      0    0
dc_tablespaces                4,839    0.0        0              0      2   50
dc_used_extents               3,188   50.4        0          3,188    162   69
dc_user_grants                1,281    0.1        0              0     12   80
dc_usernames                    143    0.7        0              0      6   50
dc_users                      1,326    0.0        0              0     14   88
ifs_acl_cache_entries             0               0              0      0    0
          -------------------------------------------------------------

Library Cache Activity for DB: CSE60  Instance: cse60  Snaps: 11 -12
->"Pct Misses"  should be very low

                    Get       Pct        Pin        Pct               Invali-
Namespace         Requests    Miss     Requests     Miss     Reloads  dations
--------------- ------------ ------ -------------- ------ ---------- --------
BODY                       0                     0                 0        0
CLUSTER                   20    0.0             13    0.0          0        0
INDEX                      0                     0                 0        0
OBJECT                     0                     0                 0        0
PIPE                       0                     0                 0        0
SQL AREA              13,396    0.3         47,761    0.3         51        0
TABLE/PROCEDURE          385   31.9            505   53.5          0        0
TRIGGER                    0                     0                 0        0
          -------------------------------------------------------------
 SGA Memory Summary for DB: CSE60  Instance: cse60  Snaps: 11 -12

SGA regions                       Size in Bytes
------------------------------ ----------------
Database Buffers                     45,056,000
Fixed Size                              104,936
Redo Buffers                            172,032
Variable Size                        79,687,680
                               ----------------
sum                                 125,020,648
          -------------------------------------------------------------

          Followup:

Never run statspack without timed statistics -- its only about 10% as useful as
it could be.

2gig of ram.... 45meg buffer pool.  You might give serious consideration to say
a 500meg buffer pool maybe.

All of your reads are on one mount point:

IEFSENCY_TAB             /maxdb1/cse60/tabcse60.dbf
     2,348,996   2,731    0.0     1.0           83        0          0
                         /maxdb1/cse60/tabcse60b.dbf
     2,212,438   2,573    0.0     1.0           20        0          0

consider OS striping or spreading the IO out by building that tablespace with
smaller files on different devices.

You cut out the top SQL report -- so it isn't possible to say if you have bad
sql that does lots of LIO's, look for that and tune them if so.

      Rating: 3
Thank You-here
      is the one with timed statistics.-PART 1  August 20, 2002

Reviewer:  H  from USA
STATSPACK report for

DB Name         DB Id    Instance     Inst Num Release     OPS Host
------------ ----------- ------------ -------- ----------- --- ------------
CSE60         4093422558 cse60               1 8.1.7.4.0   NO  himhpp01

                Snap Id     Snap Time      Sessions
                ------- ------------------ --------
 Begin Snap:         13 20-Aug-02 10:43:21       15
   End Snap:         14 20-Aug-02 10:59:06       15
    Elapsed:                  15.75 (mins)

Cache Sizes
~~~~~~~~~~~
           db_block_buffers:       5500          log_buffer:     163840
              db_block_size:       8192    shared_pool_size:   75000000

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:              2,331.72             12,591.27
              Logical reads:                713.64              3,853.66
              Block changes:                 15.93                 86.05
             Physical reads:                461.54              2,492.31
            Physical writes:                  9.03                 48.77
                 User calls:                 70.79                382.28
                     Parses:                  1.03                  5.57
                Hard parses:                  0.01                  0.07
                      Sorts:                  0.43                  2.34
                     Logons:                  0.00                  0.02
                   Executes:                 68.13                367.92
               Transactions:                  0.19

  % Blocks changed per Read:    2.23    Recursive Call %:   12.30
 Rollback per transaction %:    1.14       Rows per Sort:   23.27

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:  100.00
            Buffer  Hit   %:   35.33    In-memory Sort %:  100.00
            Library Hit   %:   99.95        Soft Parse %:   98.67
         Execute to Parse %:   98.49         Latch Hit %:  100.00
Parse CPU to Parse Elapsd %:             % Non-Parse CPU:   97.30

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   41.17   41.44
    % SQL with executions>1:   59.18   59.40
  % Memory for SQL w/exec>1:   37.41   37.64

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
db file sequential read                           435,707           67   97.10
log file sync                                         173            1    1.45
control file sequential read                           29            1    1.45
control file parallel write                           307            0     .00
log file parallel write                               244            0     .00
          -------------------------------------------------------------
 Wait Events for DB: CSE60  Instance: cse60  Snaps: 13 -14
-> cs - centisecond -  100th of a second
-> ms - millisecond - 1000th of a second
-> ordered by wait time desc, waits desc (idle events last)

                                                                    Avg
                                                     Total Wait    wait  Waits
Event                               Waits   Timeouts  Time (cs)    (ms)   /txn
---------------------------- ------------ ---------- ----------- ------ ------
db file sequential read           435,707          0          67      0 ######
log file sync                         173          0           1      0    1.0
control file sequential read           29          0           1      0    0.2
control file parallel write           307          0           0      0    1.8
log file parallel write               244          0           0      0    1.4
db file parallel write                127          0           0      0    0.7
direct path write                      60          0           0      0    0.3
file open                              24          0           0      0    0.1
db file scattered read                 23          0           0      0    0.1
refresh controlfile command             6          0           0      0    0.0
SQL*Net more data to client             4          0           0      0    0.0
latch free                              4          4           0      0    0.0
LGWR wait for redo copy                 1          0           0      0    0.0
SQL*Net message from client        66,898          0      94,356     14  382.3
SQL*Net message to client          66,899          0           0      0  382.3
SQL*Net more data from clien            8          0           0      0    0.0
          -------------------------------------------------------------
 Background Wait Events for DB: CSE60  Instance: cse60  Snaps: 13 -14
-> ordered by wait time desc, waits desc (idle events last)

                                                                    Avg
                                                     Total Wait    wait  Waits
Event                               Waits   Timeouts  Time (cs)    (ms)   /txn
---------------------------- ------------ ---------- ----------- ------ ------
control file parallel write           307          0           0      0    1.8
log file parallel write               244          0           0      0    1.4
db file parallel write                127          0           0      0    0.7
db file scattered read                 23          0           0      0    0.1
db file sequential read                16          0           0      0    0.1
control file sequential read           12          0           0      0    0.1
LGWR wait for redo copy                 1          0           0      0    0.0
rdbms ipc message                   2,111      1,533           0      0   12.1
pmon timer                            306        306           0      0    1.7
smon timer                              3          3           0      0    0.0
          -------------------------------------------------------------
 SQL ordered by Gets for DB: CSE60  Instance: cse60  Snaps: 13 -14
-> End Buffer Gets Threshold:   10000
-> Note that resources reported for PL/SQL includes the resources used by
   all SQL statements called within the PL/SQL code.  As individual SQL
   statements are also reported, it is possible and valid for the summed
   total % to exceed 100

  Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
         65,184       16,293            4.0     9.7   2698526331
select OBJ_MODEL_ID ,OBJ_ID ,OBJ_TYPE_CODE ,OBJ_ORG_ENCY_ID ,OBJ
_ORG_OBJ_ID ,OBJ_PARENT_MDL_OBJ ,OBJ_MAX_ACCESS ,OBJ_DEL_PENDING
 ,OBJ_CHG_STATUS ,OBJ_ACCESS ,OBJ_CKO_STATUS ,OBJ_TEMP_OBJECT_ID
 ,OBJ_NAME ,OBJ_NAM_CHG_STATUS ,to_char(OBJ_DATE,'yyyy-mm-dd') ,
to_char(OBJ_TIME,'hh24:mi:ss') ,OBJ_USER into :b0,:b1,:b2,:b3,:b

         59,037       19,563            3.0     8.8   1352855255
select TEXT_VALUE ,TEXT_CHG_STATUS ,to_char(TEXT_DATE,'yyyy-mm-d
d') ,to_char(TEXT_TIME,'hh24:mi:ss') ,TEXT_USER into :b0,:b1,:b2
,:b3,:b4  from DTXT where (TEXT_OBJ_ID=:b5 and TEXT_PROP_CODE=:b
6)

         42,978       10,742            4.0     6.4   2795188432
select NAME08_PROP_1 ,NAME08_CHG_STATUS ,to_char(NAME_DATE,'yyyy
-mm-dd') ,to_char(NAME_TIME,'hh24:mi:ss') ,NAME_USER into :b0,:b
1,:b2,:b3,:b4  from DNAME where NAME_OBJ_ID=:b5

         32,993           41          804.7     4.9   3585280127
select OBJ_ID ,OBJ_CHG_STATUS  from DOBJ where (OBJ_MODEL_ID=:b0
 and OBJ_TYPE_CODE=:b1)

         12,812            3        4,270.7     1.9   3376831664
BEGIN statspack.snap; END;

         11,740        2,935            4.0     1.7   2779448552
select OBJ_NAME ,OBJ_NAM_CHG_STATUS ,to_char(OBJ_PROP_DATE,'yyyy
-mm-dd') ,to_char(OBJ_PROP_TIME,'hh24:mi:ss') ,OBJ_PROP_USER int
o :b0,:b1,:b2,:b3,:b4  from DOBJ where OBJ_ID=:b5

          9,712        2,428            4.0     1.4   1576125624
select OBJ_INT_PROP_4 ,OBJ_IN_CHG_STATUS ,to_char(OBJ_PROP_DATE,
'yyyy-mm-dd') ,to_char(OBJ_PROP_TIME,'hh24:mi:ss') ,OBJ_PROP_USE
R into :b0,:b1,:b2,:b3,:b4  from DOBJ where OBJ_ID=:b5

          5,032        2,516            2.0     0.7   4220414680
select U_USER_ID ,U_USER_NAME ,U_ENCY_AUTH ,U_ADD_MODEL_AUTH ,U_
ADD_USER_AUTH ,U_CR_USER_ID ,to_char(U_CR_DATE,'yyyy-mm-dd') ,to
_char(U_CR_TIME,'hh24:mi:ss') ,U_USER_OR_GROUP into :b0,:b1,:b2,
:b3,:b4,:b5,:b6,:b7,:b8  from DUSR where U_USER_ID=:b0

          4,408        1,102            4.0     0.7   1159184827
select NAME32_PROP_2 ,NAME32_CHG_STATUS ,to_char(NAME_DATE,'yyyy
-mm-dd') ,to_char(NAME_TIME,'hh24:mi:ss') ,NAME_USER into :b0,:b
1,:b2,:b3,:b4  from DNAME where NAME_OBJ_ID=:b5

          3,912          978            4.0     0.6   3262287878
select ASSOC_MODEL_ID ,ASSOC_FROM_OBJ_ID ,ASSOC_SEQ ,ASSOC_NEXT_
OBJ_ID ,ASSOC_DEL_PENDING ,ASSOC_CHG_STATUS ,to_char(ASSOC_DATE,
'yyyy-mm-dd') ,to_char(ASSOC_TIME,'hh24:mi;ss') ,ASSOC_USER into
 :b0,:b1,:b2,:b3,:b4,:b5,:b6,:b7,:b8  from DASC where ((ASSOC_TO
 SQL ordered by Gets for DB: CSE60  Instance: cse60  Snaps: 13 -14
-> End Buffer Gets Threshold:   10000
-> Note that resources reported for PL/SQL includes the resources used by
   all SQL statements called within the PL/SQL code.  As individual SQL
   statements are also reported, it is possible and valid for the summed
   total % to exceed 100

  Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
_OBJ_ID=:b9 and ASSOC_TYPE_CODE=:b10) and ASSOC_CHG_STATUS not

          3,394        1,098            3.1     0.5   1386399857
select ASSOC_MODEL_ID ,ASSOC_SEQ ,ASSOC_NEXT_OBJ_ID ,ASSOC_DEL_P
ENDING ,ASSOC_CHG_STATUS ,to_char(ASSOC_DATE,'yyyy-mm-dd') ,to_c
har(ASSOC_TIME,'hh24:mi:ss') ,ASSOC_USER into :b0,:b1,:b2,:b3,:b
4,:b5,:b6,:b7  from DASC where ((ASSOC_FROM_OBJ_ID=:b8 and ASSOC
_TYPE_CODE=:b9) and ASSOC_TO_OBJ_ID=:b10)

          3,315          461            7.2     0.5    513137242
insert into DASC (ASSOC_MODEL_ID,ASSOC_FROM_OBJ_ID,ASSOC_TYPE_CO
DE,ASSOC_TO_OBJ_ID,ASSOC_SEQ,ASSOC_NEXT_OBJ_ID,ASSOC_DEL_PENDING
,ASSOC_CHG_STATUS,ASSOC_DATE,ASSOC_TIME,ASSOC_USER) values (:b0,
:b1,:b2,:b3,:b4,:b5,:b6,:b7,to_date(:b8,'yyyy-mm-dd'),to_date(:b
9,'hh24:mi:ss'),:b10)

          2,313          161           14.4     0.3   2350670400
insert into DOBJ (OBJ_MODEL_ID,OBJ_ID,OBJ_TYPE_CODE,OBJ_ORG_ENCY
_ID,OBJ_ORG_OBJ_ID,OBJ_PARENT_MDL_OBJ,OBJ_MAX_ACCESS,OBJ_DEL_PEN
DING,OBJ_CHG_STATUS,OBJ_ACCESS,OBJ_CKO_STATUS,OBJ_TEMP_OBJECT_ID
,OBJ_NAME,OBJ_NAM_CHG_STATUS,OBJ_DATE,OBJ_TIME,OBJ_USER,OBJ_CHAR
_PROP_1,OBJ_CHAR_PROP_2,OBJ_CHAR_PROP_3,OBJ_CHAR_PROP_4,OBJ_CHAR

          1,607          803            2.0     0.2   3972953663
select MODEL_ID ,MODEL_TYPE ,MODEL_NAME ,MODEL_STATUS ,MODEL_REL
EASE ,MODEL_OWNER_ID ,MODEL_CR_USER_ID ,to_char(MODEL_CR_DATE,'y
yyy-mm-dd') ,to_char(MODEL_CR_TIME,'hh24:mi:ss') ,MODEL_USER_ID
,to_char(MODEL_DATE,'yyyy-mm-dd') ,to_char(MODEL_TIME,'hh24:mi:s
s') ,MODEL_PARENT_ENCY ,MODEL_PARENT_MODEL ,MODEL_PARENT_CKO ,to

          1,494          628            2.4     0.2   4127727110
select SE_CKO_ID  from DSUBEX where SE_OBJ_ID=:b0

          1,337          153            8.7     0.2    585519363
select ASSOC_MODEL_ID ,ASSOC_FROM_OBJ_ID ,ASSOC_TYPE_CODE ,ASSOC
_TO_OBJ_ID ,ASSOC_SEQ ,ASSOC_NEXT_OBJ_ID ,ASSOC_DEL_PENDING ,ASS
OC_CHG_STATUS ,to_char(ASSOC_DATE,'yyyy-mm-dd') ,to_char(ASSOC_T
IME,'hh24:mi;ss') ,ASSOC_USER  from DASC where ASSOC_TO_OBJ_ID=:
b0 order by ASSOC_TYPE_CODE

          1,110          314            3.5     0.2    828342459
select ASSOC_MODEL_ID ,ASSOC_SEQ ,ASSOC_TO_OBJ_ID ,ASSOC_NEXT_OB
J_ID ,ASSOC_DEL_PENDING ,ASSOC_CHG_STATUS ,to_char(ASSOC_DATE,'y
yyy-mm-dd') ,to_char(ASSOC_TIME,'hh24:mi;ss') ,ASSOC_USER into :
b0,:b1,:b2,:b3,:b4,:b5,:b6,:b7,:b8  from DASC where ((ASSOC_FROM
_OBJ_ID=:b9 and ASSOC_TYPE_CODE=:b10) and ASSOC_CHG_STATUS not

          -------------------------------------------------------------
 SQL ordered by Reads for DB: CSE60  Instance: cse60  Snaps: 13 -14
-> End Disk Reads Threshold:    1000

 Physical Reads  Executions  Reads per Exec % Total  Hash Value
--------------- ------------ -------------- ------- ------------
         25,401           41          619.5     5.8   3585280127
select OBJ_ID ,OBJ_CHG_STATUS  from DOBJ where (OBJ_MODEL_ID=:b0
 and OBJ_TYPE_CODE=:b1)

         14,328       16,293            0.9     3.3   2698526331
select OBJ_MODEL_ID ,OBJ_ID ,OBJ_TYPE_CODE ,OBJ_ORG_ENCY_ID ,OBJ
_ORG_OBJ_ID ,OBJ_PARENT_MDL_OBJ ,OBJ_MAX_ACCESS ,OBJ_DEL_PENDING
 ,OBJ_CHG_STATUS ,OBJ_ACCESS ,OBJ_CKO_STATUS ,OBJ_TEMP_OBJECT_ID
 ,OBJ_NAME ,OBJ_NAM_CHG_STATUS ,to_char(OBJ_DATE,'yyyy-mm-dd') ,
to_char(OBJ_TIME,'hh24:mi:ss') ,OBJ_USER into :b0,:b1,:b2,:b3,:b

         10,236       10,742            1.0     2.3   2795188432
select NAME08_PROP_1 ,NAME08_CHG_STATUS ,to_char(NAME_DATE,'yyyy
-mm-dd') ,to_char(NAME_TIME,'hh24:mi:ss') ,NAME_USER into :b0,:b
1,:b2,:b3,:b4  from DNAME where NAME_OBJ_ID=:b5

          2,335       19,563            0.1     0.5   1352855255
select TEXT_VALUE ,TEXT_CHG_STATUS ,to_char(TEXT_DATE,'yyyy-mm-d
d') ,to_char(TEXT_TIME,'hh24:mi:ss') ,TEXT_USER into :b0,:b1,:b2
,:b3,:b4  from DTXT where (TEXT_OBJ_ID=:b5 and TEXT_PROP_CODE=:b
6)

          1,343        1,102            1.2     0.3   1159184827
select NAME32_PROP_2 ,NAME32_CHG_STATUS ,to_char(NAME_DATE,'yyyy
-mm-dd') ,to_char(NAME_TIME,'hh24:mi:ss') ,NAME_USER into :b0,:b
1,:b2,:b3,:b4  from DNAME where NAME_OBJ_ID=:b5

            346            3          115.3     0.1   3376831664
BEGIN statspack.snap; END;

            291          978            0.3     0.1   3262287878
select ASSOC_MODEL_ID ,ASSOC_FROM_OBJ_ID ,ASSOC_SEQ ,ASSOC_NEXT_
OBJ_ID ,ASSOC_DEL_PENDING ,ASSOC_CHG_STATUS ,to_char(ASSOC_DATE,
'yyyy-mm-dd') ,to_char(ASSOC_TIME,'hh24:mi;ss') ,ASSOC_USER into
 :b0,:b1,:b2,:b3,:b4,:b5,:b6,:b7,:b8  from DASC where ((ASSOC_TO
_OBJ_ID=:b9 and ASSOC_TYPE_CODE=:b10) and ASSOC_CHG_STATUS not

            225            1          225.0     0.1   1212505517
select i.obj#, i.flags, u.name, o.name     from sys.obj$ o, sys.
user$ u, sys.ind$ i    where  (bitand(i.flags, 256) = 256 or bit
and(i.flags, 512) = 512) and           (not((i.type# = 9) and bi
tand(i.flags,8) = 8)) and           o.obj#=i.obj# and o.owner# =
 u.user#

            200          314            0.6     0.0    828342459
select ASSOC_MODEL_ID ,ASSOC_SEQ ,ASSOC_TO_OBJ_ID ,ASSOC_NEXT_OB
J_ID ,ASSOC_DEL_PENDING ,ASSOC_CHG_STATUS ,to_char(ASSOC_DATE,'y
yyy-mm-dd') ,to_char(ASSOC_TIME,'hh24:mi;ss') ,ASSOC_USER into :
b0,:b1,:b2,:b3,:b4,:b5,:b6,:b7,:b8  from DASC where ((ASSOC_FROM
_OBJ_ID=:b9 and ASSOC_TYPE_CODE=:b10) and ASSOC_CHG_STATUS not

             80          161            0.5     0.0   2350670400
insert into DOBJ (OBJ_MODEL_ID,OBJ_ID,OBJ_TYPE_CODE,OBJ_ORG_ENCY
_ID,OBJ_ORG_OBJ_ID,OBJ_PARENT_MDL_OBJ,OBJ_MAX_ACCESS,OBJ_DEL_PEN
DING,OBJ_CHG_STATUS,OBJ_ACCESS,OBJ_CKO_STATUS,OBJ_TEMP_OBJECT_ID
 SQL ordered by Reads for DB: CSE60  Instance: cse60  Snaps: 13 -14
-> End Disk Reads Threshold:    1000

 Physical Reads  Executions  Reads per Exec % Total  Hash Value
--------------- ------------ -------------- ------- ------------
,OBJ_NAME,OBJ_NAM_CHG_STATUS,OBJ_DATE,OBJ_TIME,OBJ_USER,OBJ_CHAR
_PROP_1,OBJ_CHAR_PROP_2,OBJ_CHAR_PROP_3,OBJ_CHAR_PROP_4,OBJ_CHAR

             71          183            0.4     0.0   3707929896
select OBJ_MODEL_ID ,OBJ_ID ,OBJ_TYPE_CODE ,OBJ_ORG_ENCY_ID ,OBJ
_ORG_OBJ_ID ,OBJ_PARENT_MDL_OBJ ,OBJ_MAX_ACCESS ,OBJ_DEL_PENDING
 ,OBJ_CHG_STATUS ,OBJ_ACCESS ,OBJ_CKO_STATUS ,OBJ_TEMP_OBJECT_ID
 ,OBJ_NAME ,OBJ_NAM_CHG_STATUS ,to_char(OBJ_DATE,'yyyy-mm-dd') ,
to_char(OBJ_TIME,'hh24:mi:ss') ,OBJ_USER into :b0,:b1,:b2,:b3,:b

             58          153            0.4     0.0    887856235
select DX_ENCYCLOPEDIA_CP into :b0  from DXCPID where DX_REQUEST
ER_CP=:b1

             37          101            0.4     0.0   2216582187
select ts#,file#,block#,nvl(bobj#,0),nvl(tab#,0),intcols,nvl(clu
cols,0),audit$,flags,pctfree$,pctused$,initrans,maxtrans,rowcnt,
blkcnt,empcnt,avgspc,chncnt,avgrln,analyzetime, samplesize,cols,
property,nvl(degree,1),nvl(instances,1),avgspc_flb,flbcnt,kernel
cols,nvl(trigflag, 0),nvl(spare1,0),nvl(spare2,0),spare4,nvl(spa

             28          102            0.3     0.0    199702406
select i.obj#,i.ts#,i.file#,i.block#,i.intcols,i.type#,i.flags,
i.property,i.pctfree$,i.initrans,i.maxtrans,i.blevel,i.leafcnt,i
.distkey, i.lblkkey,i.dblkkey,i.clufac,i.cols,i.analyzetime,i.sa
mplesize,i.dataobj#, nvl(i.degree,1),nvl(i.instances,1),i.rowcnt
,mod(i.pctthres$,256),i.indmethod#,i.trunccnt,nvl(c.unicols,0),n

             23          153            0.2     0.0    585519363
select ASSOC_MODEL_ID ,ASSOC_FROM_OBJ_ID ,ASSOC_TYPE_CODE ,ASSOC
_TO_OBJ_ID ,ASSOC_SEQ ,ASSOC_NEXT_OBJ_ID ,ASSOC_DEL_PENDING ,ASS
OC_CHG_STATUS ,to_char(ASSOC_DATE,'yyyy-mm-dd') ,to_char(ASSOC_T
IME,'hh24:mi;ss') ,ASSOC_USER  from DASC where ASSOC_TO_OBJ_ID=:
b0 order by ASSOC_TYPE_CODE

             22            3            7.3     0.0   1714733582
select f.file#, f.block#, f.ts#, f.length from fet$ f, ts$ t whe
re t.ts#=f.ts# and t.dflextpct!=0 and t.bitmapped=0

             17          127            0.1     0.0   2513378726
select NAME32_PROP_2 ,NAME32_PROP_3 ,NAME08_PROP_1 ,NAME08_PROP_
2 ,NAME08_PROP_3 ,NAME08_PROP_4 ,NAME08_PROP_5 ,NAME08_PROP_6 ,N

          -------------------------------------------------------------
 SQL ordered by Executions for DB: CSE60  Instance: cse60  Snaps: 13 -14
-> End Executions Threshold:     100

 Executions   Rows Processed    Rows per Exec   Hash Value
------------ ---------------- ---------------- ------------
      19,563              143              0.0   1352855255
select TEXT_VALUE ,TEXT_CHG_STATUS ,to_char(TEXT_DATE,'yyyy-mm-d
d') ,to_char(TEXT_TIME,'hh24:mi:ss') ,TEXT_USER into :b0,:b1,:b2
,:b3,:b4  from DTXT where (TEXT_OBJ_ID=:b5 and TEXT_PROP_CODE=:b
6)

      16,293           16,293              1.0   2698526331
select OBJ_MODEL_ID ,OBJ_ID ,OBJ_TYPE_CODE ,OBJ_ORG_ENCY_ID ,OBJ
_ORG_OBJ_ID ,OBJ_PARENT_MDL_OBJ ,OBJ_MAX_ACCESS ,OBJ_DEL_PENDING
 ,OBJ_CHG_STATUS ,OBJ_ACCESS ,OBJ_CKO_STATUS ,OBJ_TEMP_OBJECT_ID
 ,OBJ_NAME ,OBJ_NAM_CHG_STATUS ,to_char(OBJ_DATE,'yyyy-mm-dd') ,
to_char(OBJ_TIME,'hh24:mi:ss') ,OBJ_USER into :b0,:b1,:b2,:b3,:b

      10,742           10,742              1.0   2795188432
select NAME08_PROP_1 ,NAME08_CHG_STATUS ,to_char(NAME_DATE,'yyyy
-mm-dd') ,to_char(NAME_TIME,'hh24:mi:ss') ,NAME_USER into :b0,:b
1,:b2,:b3,:b4  from DNAME where NAME_OBJ_ID=:b5

       2,935            2,935              1.0   2779448552
select OBJ_NAME ,OBJ_NAM_CHG_STATUS ,to_char(OBJ_PROP_DATE,'yyyy
-mm-dd') ,to_char(OBJ_PROP_TIME,'hh24:mi:ss') ,OBJ_PROP_USER int
o :b0,:b1,:b2,:b3,:b4  from DOBJ where OBJ_ID=:b5

       2,516            2,516              1.0   4220414680
select U_USER_ID ,U_USER_NAME ,U_ENCY_AUTH ,U_ADD_MODEL_AUTH ,U_
ADD_USER_AUTH ,U_CR_USER_ID ,to_char(U_CR_DATE,'yyyy-mm-dd') ,to
_char(U_CR_TIME,'hh24:mi:ss') ,U_USER_OR_GROUP into :b0,:b1,:b2,
:b3,:b4,:b5,:b6,:b7,:b8  from DUSR where U_USER_ID=:b0

       2,428            2,428              1.0   1576125624
select OBJ_INT_PROP_4 ,OBJ_IN_CHG_STATUS ,to_char(OBJ_PROP_DATE,
'yyyy-mm-dd') ,to_char(OBJ_PROP_TIME,'hh24:mi:ss') ,OBJ_PROP_USE
R into :b0,:b1,:b2,:b3,:b4  from DOBJ where OBJ_ID=:b5

       1,102            1,102              1.0   1159184827
select NAME32_PROP_2 ,NAME32_CHG_STATUS ,to_char(NAME_DATE,'yyyy
-mm-dd') ,to_char(NAME_TIME,'hh24:mi:ss') ,NAME_USER into :b0,:b
1,:b2,:b3,:b4  from DNAME where NAME_OBJ_ID=:b5

       1,098               98              0.1   1386399857
select ASSOC_MODEL_ID ,ASSOC_SEQ ,ASSOC_NEXT_OBJ_ID ,ASSOC_DEL_P
ENDING ,ASSOC_CHG_STATUS ,to_char(ASSOC_DATE,'yyyy-mm-dd') ,to_c
har(ASSOC_TIME,'hh24:mi:ss') ,ASSOC_USER into :b0,:b1,:b2,:b3,:b
4,:b5,:b6,:b7  from DASC where ((ASSOC_FROM_OBJ_ID=:b8 and ASSOC
_TYPE_CODE=:b9) and ASSOC_TO_OBJ_ID=:b10)

         978              978              1.0   3262287878
select ASSOC_MODEL_ID ,ASSOC_FROM_OBJ_ID ,ASSOC_SEQ ,ASSOC_NEXT_
OBJ_ID ,ASSOC_DEL_PENDING ,ASSOC_CHG_STATUS ,to_char(ASSOC_DATE,
'yyyy-mm-dd') ,to_char(ASSOC_TIME,'hh24:mi;ss') ,ASSOC_USER into
 :b0,:b1,:b2,:b3,:b4,:b5,:b6,:b7,:b8  from DASC where ((ASSOC_TO
_OBJ_ID=:b9 and ASSOC_TYPE_CODE=:b10) and ASSOC_CHG_STATUS not

         803              803              1.0   3972953663
select MODEL_ID ,MODEL_TYPE ,MODEL_NAME ,MODEL_STATUS ,MODEL_REL
 SQL ordered by Executions for DB: CSE60  Instance: cse60  Snaps: 13 -14
-> End Executions Threshold:     100

          Followup:

up your buffer cache, but I see it's already improved 300% just by running the
thing ;)

This is the only query that pops out as a query to look into

Physical Reads  Executions  Reads per Exec % Total  Hash Value
--------------- ------------ -------------- ------- ------------
         25,401           41          619.5     5.8   3585280127
select OBJ_ID ,OBJ_CHG_STATUS  from DOBJ where (OBJ_MODEL_ID=:b0
 and OBJ_TYPE_CODE=:b1)

      Rating: 3
STATSPACK
      REPORT PART 2   August 20, 2002
Reviewer:  H
       from USA
 Executions   Rows Processed    Rows per Exec   Hash Value
------------ ---------------- ---------------- ------------
EASE ,MODEL_OWNER_ID ,MODEL_CR_USER_ID ,to_char(MODEL_CR_DATE,'y
yyy-mm-dd') ,to_char(MODEL_CR_TIME,'hh24:mi:ss') ,MODEL_USER_ID
,to_char(MODEL_DATE,'yyyy-mm-dd') ,to_char(MODEL_TIME,'hh24:mi:s
s') ,MODEL_PARENT_ENCY ,MODEL_PARENT_MODEL ,MODEL_PARENT_CKO ,to

         724                0              0.0   1804555764
select CKO_MODEL_ID ,CKO_ID ,CKO_ENCY_ID ,CKO_USER_ID ,to_char(C
KO_DATE,'yyyy-mm-dd') ,to_char(CKO_TIME,'hh24:mi:ss') ,CKO_SUBSE
T_ID ,CKO_SUBSET_ALL ,CKO_NEW_MODEL ,CKO_COMMIT_CNT ,CKO_STATUS
into :b0,:b1,:b2,:b3,:b4,:b5,:b6,:b7,:b8,:b9,:b10  from DCKOID w
here (CKO_MODEL_ID=:b0 and CKO_SUBSET_ID=0)

         628                0              0.0   4127727110
select SE_CKO_ID  from DSUBEX where SE_OBJ_ID=:b0

         461              461              1.0    513137242
insert into DASC (ASSOC_MODEL_ID,ASSOC_FROM_OBJ_ID,ASSOC_TYPE_CO
DE,ASSOC_TO_OBJ_ID,ASSOC_SEQ,ASSOC_NEXT_OBJ_ID,ASSOC_DEL_PENDING
,ASSOC_CHG_STATUS,ASSOC_DATE,ASSOC_TIME,ASSOC_USER) values (:b0,
:b1,:b2,:b3,:b4,:b5,:b6,:b7,to_date(:b8,'yyyy-mm-dd'),to_date(:b
9,'hh24:mi:ss'),:b10)

         314              160              0.5    828342459
select ASSOC_MODEL_ID ,ASSOC_SEQ ,ASSOC_TO_OBJ_ID ,ASSOC_NEXT_OB
J_ID ,ASSOC_DEL_PENDING ,ASSOC_CHG_STATUS ,to_char(ASSOC_DATE,'y
yyy-mm-dd') ,to_char(ASSOC_TIME,'hh24:mi;ss') ,ASSOC_USER into :
b0,:b1,:b2,:b3,:b4,:b5,:b6,:b7,:b8  from DASC where ((ASSOC_FROM
_OBJ_ID=:b9 and ASSOC_TYPE_CODE=:b10) and ASSOC_CHG_STATUS not

         206            2,960             14.4   2294910170
select ASSOC_MODEL_ID ,ASSOC_FROM_OBJ_ID ,ASSOC_TYPE_CODE ,ASSOC
_TO_OBJ_ID ,ASSOC_SEQ ,ASSOC_NEXT_OBJ_ID ,ASSOC_DEL_PENDING ,ASS
OC_CHG_STATUS ,to_char(ASSOC_DATE,'yyyy-mm-dd') ,to_char(ASSOC_T
IME,'hh24:mi;ss') ,ASSOC_USER  from DASC where (ASSOC_FROM_OBJ_I
D=:b0 and ASSOC_TYPE_CODE=:b1) order by ASSOC_SEQ

         183               18              0.1   3707929896
select OBJ_MODEL_ID ,OBJ_ID ,OBJ_TYPE_CODE ,OBJ_ORG_ENCY_ID ,OBJ
_ORG_OBJ_ID ,OBJ_PARENT_MDL_OBJ ,OBJ_MAX_ACCESS ,OBJ_DEL_PENDING
 ,OBJ_CHG_STATUS ,OBJ_ACCESS ,OBJ_CKO_STATUS ,OBJ_TEMP_OBJECT_ID

          -------------------------------------------------------------
 Instance Activity Stats for DB: CSE60  Instance: cse60  Snaps: 13 -14

Statistic                                    Total   per Second    per Trans
--------------------------------- ---------------- ------------ ------------
CPU used by this session                        37          0.0          0.2
CPU used when call started                      37          0.0          0.2
CR blocks created                              286          0.3          1.6
DBWR buffers scanned                        58,550         62.0        334.6
DBWR checkpoint buffers written                  0          0.0          0.0
DBWR checkpoints                                 0          0.0          0.0
DBWR free buffers found                     58,003         61.4        331.5
DBWR lru scans                                 179          0.2          1.0
DBWR make free requests                        179          0.2          1.0
DBWR summed scan depth                      58,550         62.0        334.6
DBWR transaction table writes                    3          0.0          0.0
DBWR undo block writes                         120          0.1          0.7
SQL*Net roundtrips to/from client           66,890         70.8        382.2
background checkpoints completed                 0          0.0          0.0
background checkpoints started                   0          0.0          0.0
background timeouts                          1,538          1.6          8.8
branch node splits                               0          0.0          0.0
buffer is not pinned count                 494,904        523.7      2,828.0
buffer is pinned count                   2,496,337      2,641.6     14,264.8
bytes received via SQL*Net from c        6,457,459      6,833.3     36,899.8
bytes sent via SQL*Net to client        12,591,669     13,324.5     71,952.4
calls to get snapshot scn: kcmgss           65,185         69.0        372.5
calls to kcmgas                                324          0.3          1.9
calls to kcmgcs                                 40          0.0          0.2
change write time                                1          0.0          0.0
cleanouts and rollbacks - consist                0          0.0          0.0
cleanouts only - consistent read               402          0.4          2.3
cluster key scan block gets                    995          1.1          5.7
cluster key scans                              430          0.5          2.5
commit cleanout failures: block l                4          0.0          0.0
commit cleanout failures: buffer                 0          0.0          0.0
commit cleanout failures: callbac                1          0.0          0.0
commit cleanouts                               614          0.7          3.5
commit cleanouts successfully com              609          0.6          3.5
consistent changes                             562          0.6          3.2
consistent gets                            657,269        695.5      3,755.8
cursor authentications                          13          0.0          0.1
data blocks consistent reads - un              275          0.3          1.6
db block changes                            15,058         15.9         86.1
db block gets                               17,124         18.1         97.9
deferred (CURRENT) block cleanout              453          0.5          2.6
dirty buffers inspected                        240          0.3          1.4
enqueue releases                             1,268          1.3          7.3
enqueue requests                             1,274          1.4          7.3
enqueue timeouts                                 0          0.0          0.0
execute count                               64,386         68.1        367.9
free buffer inspected                          240          0.3          1.4
free buffer requested                      436,549        462.0      2,494.6
hot buffers moved to head of LRU             9,531         10.1         54.5
immediate (CR) block cleanout app              402          0.4          2.3
immediate (CURRENT) block cleanou               36          0.0          0.2
index fast full scans (full)                     0          0.0          0.0
leaf node splits                                 9          0.0          0.1
logons cumulative                                4          0.0          0.0
logons current
messages received                              733          0.8          4.2
 Instance Activity Stats for DB: CSE60  Instance: cse60  Snaps: 13 -14

Statistic                                    Total   per Second    per Trans
--------------------------------- ---------------- ------------ ------------
messages sent                                  733          0.8          4.2
no buffer to keep pinned count                   0          0.0          0.0
no work - consistent read gets             437,987        463.5      2,502.8
opened cursors cumulative                      920          1.0          5.3
opened cursors current
parse count (hard)                              13          0.0          0.1
parse count (total)                            975          1.0          5.6
parse time cpu                                   1          0.0          0.0
physical reads                             436,154        461.5      2,492.3
physical reads direct                            0          0.0          0.0
physical writes                              8,534          9.0         48.8
physical writes direct                       7,747          8.2         44.3
physical writes non checkpoint               8,533          9.0         48.8
pinned buffers inspected                         0          0.0          0.0
prefetched blocks                              215          0.2          1.2
recursive calls                              9,379          9.9         53.6
recursive cpu usage                             36          0.0          0.2
redo blocks written                          2,346          2.5         13.4
redo buffer allocation retries                   0          0.0          0.0
redo entries                                 7,804          8.3         44.6
redo log space requests                          0          0.0          0.0
redo size                                2,203,472      2,331.7     12,591.3
redo synch time                                  1          0.0          0.0
redo synch writes                              175          0.2          1.0
redo wastage                               143,344        151.7        819.1
redo writes                                    245          0.3          1.4
rollback changes - undo records a            2,269          2.4         13.0
rollbacks only - consistent read               275          0.3          1.6
rows fetched via callback                   38,206         40.4        218.3
session logical reads                      674,390        713.6      3,853.7
session pga memory                      13,874,104     14,681.6     79,280.6
session pga memory max                  14,145,176     14,968.4     80,829.6
session uga memory                      13,175,184     13,942.0     75,286.8
session uga memory max                  13,454,944     14,238.0     76,885.4
sorts (memory)                                 409          0.4          2.3
sorts (rows)                                 9,519         10.1         54.4
summed dirty queue length                        8          0.0          0.1
switch current to new buffer
table fetch by rowid                     1,492,652      1,579.5      8,529.4
table fetch continued row                        0          0.0          0.0
table scan blocks gotten                       253          0.3          1.5
table scan rows gotten                         477          0.5          2.7
table scans (long tables)                        1          0.0          0.0
table scans (short tables)                      10          0.0          0.1
total file opens                                24          0.0          0.1
transaction rollbacks                            2          0.0          0.0
transaction tables consistent rea               11          0.0          0.1
transaction tables consistent rea              149          0.2          0.9
user calls                                  66,899         70.8        382.3
user commits                                   173          0.2          1.0
user rollbacks                                   2          0.0          0.0
write clones created in foregroun                1          0.0          0.0
          -------------------------------------------------------------
 Tablespace IO Stats for DB: CSE60  Instance: cse60  Snaps: 13 -14
->ordered by IOs (Reads + Writes) desc

Tablespace
------------------------------
                 Av      Av     Av                    Av        Buffer Av Buf
         Reads Reads/s Rd(ms) Blks/Rd       Writes Writes/s      Waits Wt(ms)
-------------- ------- ------ ------- ------------ -------- ---------- ------
IEFSENCY_TAB
       402,088     425    0.0     1.0          362        0          0    0.0
IEFSENCY_IDX
        33,237      35    0.0     1.0          302        0          0    0.0
IEFSENCY_ROLL
            55       0    0.0     1.0          123        0          0    0.0
SYSTEM
            75       0    0.3     3.9            0        0          0    0.0
IEFSENCY_TEMP
             0       0    0.0                   61        0          0    0.0
          -------------------------------------------------------------
 File IO Stats for DB: CSE60  Instance: cse60  Snaps: 13 -14
->ordered by Tablespace, File

Tablespace               Filename
------------------------ ----------------------------------------------------
                 Av      Av     Av                    Av        Buffer Av Buf
         Reads Reads/s Rd(ms) Blks/Rd       Writes Writes/s      Waits Wt(ms)
-------------- ------- ------ ------- ------------ -------- ---------- ------
IEFSENCY_IDX             /maxdb1/cse60/idxcse60.dbf
        20,862      22    0.0     1.0          246        0          0
                         /maxdb1/cse60/idxcse60b.dbf
        12,375      13    0.0     1.0           56        0          0

IEFSENCY_ROLL            /maxdb1/cse60/rollcse60.dbf
            55       0    0.0     1.0          123        0          0

IEFSENCY_TAB             /maxdb1/cse60/tabcse60.dbf
       210,397     223    0.0     1.0          330        0          0
                         /maxdb1/cse60/tabcse60b.dbf
       191,691     203    0.0     1.0           32        0          0

IEFSENCY_TEMP            /maxdb1/cse60/tempcse60.dbf
             0       0                          61        0          0

SYSTEM                   /maxdb1/cse60/dbscse60.dbf
            75       0    0.3     3.9            0        0          0

          -------------------------------------------------------------
 Buffer Pool Statistics for DB: CSE60  Instance: cse60  Snaps: 13 -14
-> Pools   D: default pool,  K: keep pool,  R: recycle pool

                                                      Free    Write     Buffer
       Buffer    Consistent    Physical   Physical  Buffer Complete       Busy
P        Gets          Gets       Reads     Writes   Waits    Waits      Waits
- ----------- ------------- ----------- ---------- ------- -------- ----------
D     436,100       461,581     435,701        787       0        0          0
          -------------------------------------------------------------
 Rollback Segment Stats for DB: CSE60  Instance: cse60  Snaps: 13 -14
->A high value for "Pct Waits" suggests more rollback segments may be required

       Trans Table      Pct   Undo Bytes
RBS No     Gets       Waits     Written        Wraps  Shrinks  Extends
------ ------------ ------- --------------- -------- -------- --------
     0          4.0    0.00               0        0        0        0
     2        113.0    0.00         139,910        0        0        0
     3        674.0    0.00         417,480        0        0        0
     4        121.0    0.00          10,946        0        0        0
     5        114.0    0.00           8,030        0        0        0
          -------------------------------------------------------------
 Rollback Segment Storage for DB: CSE60  Instance: cse60  Snaps: 13 -14
->Optimal Size should be larger than Avg Active

RBS No    Segment Size      Avg Active    Optimal Size    Maximum Size
------ --------------- --------------- --------------- ---------------
     0         401,408               0                         401,408
     2     128,196,608               0     127,959,040     128,196,608
     3     128,196,608       2,435,891     127,959,040     128,196,608
     4     128,196,608       3,473,530     127,959,040     128,196,608
     5     128,196,608       7,192,289     127,959,040     128,196,608
          -------------------------------------------------------------
 Latch Activity for DB: CSE60  Instance: cse60  Snaps: 13 -14
->"Get Requests", "Pct Get Miss" and "Avg Slps/Miss" are statistics for
  willing-to-wait latch get requests
->"NoWait Requests", "Pct NoWait Miss" are for no-wait latch get requests
->"Pct Misses" for both should be very close to 0.0

                                                Pct    Avg                 Pct
                                   Get          Get   Slps       NoWait NoWait
Latch Name                       Requests      Miss  /Miss     Requests   Miss
----------------------------- -------------- ------ ------ ------------ ------
Token Manager                             24    0.0                   0
active checkpoint queue latch          1,410    0.0                   0
cache buffer handles                       2    0.0                   0
cache buffers chains               1,652,311    0.0             436,641    0.0
cache buffers lru chain                2,529    0.6    0.0      436,637    0.0
channel handle pool latch                  7    0.0                   0
channel operations parent lat             11    0.0                   0
checkpoint queue latch                 9,853    0.0    0.0            0
dml lock allocation                    1,245    0.0                   0
enqueue hash chains                    2,540    0.0                   0
enqueues                               3,382    0.0                   0
event group latch                          4    0.0                   0
job_queue_processes parameter             15    0.0                   0
ktm global data                            3    0.0                   0
library cache                        142,168    0.0                   0
library cache load lock                   18    0.0                   0
list of block allocation                 678    0.0                   0
messages                               5,997    0.3    0.1            0
mostly latch-free SCN                    568    0.0                   0
multiblock read objects                   50    0.0                   0
ncodef allocation latch                   15    0.0                   0
process allocation                         4    0.0                   4    0.0
process group creation                     7    0.0                   0
redo allocation                        8,539    0.0    0.0            0
redo writing                           3,348    0.7    0.1            0
row cache objects                     12,083    0.0                   0
sequence cache                            15    0.0                   0
session allocation                       401    0.0                   0
session idle bit                     133,990    0.0                   0
session switching                         15    0.0                   0
shared pool                            1,576    0.0                   0
sort extent pool                           4    0.0                   0
transaction allocation                   843    0.0                   0
transaction branch allocation             15    0.0                   0
undo global data                       2,090    0.0                   0
user lock                                 14    0.0                   0
          -------------------------------------------------------------
 Latch Sleep breakdown for DB: CSE60  Instance: cse60  Snaps: 13 -14
-> ordered by misses desc

                                Get                                  Spin &
Latch Name                    Requests         Misses      Sleeps Sleeps 1->4
-------------------------- -------------- ----------- ----------- ------------
redo writing                        3,348          25           3 22/3/0/0/0
messages                            5,997          20           1 19/1/0/0/0
          -------------------------------------------------------------
 Latch Miss Sources for DB: CSE60  Instance: cse60  Snaps: 13 -14
-> only latches with sleeps are shown
-> ordered by name, sleeps desc

                                                     NoWait             Waiter
Latch Name               Where                       Misses     Sleeps  Sleeps
------------------------ -------------------------- ------- ---------- -------
messages                 ksarcv: after wait               0          1       0
redo writing             kcrfsr                           0          3       0
          -------------------------------------------------------------
 Dictionary Cache Stats for DB: CSE60  Instance: cse60  Snaps: 13 -14
->"Pct Misses"  should be very low (< 2% in most cases)
->"Cache Usage" is the number of cache entries being used
->"Pct SGA"     is the ratio of usage to allocated size for that cache

                           Get         Pct     Scan   Pct      Mod  Final  Pct
Cache                    Requests     Miss Requests  Miss      Req  Usage  SGA
---------------------- ------------ ------ -------- ----- -------- ------ ----
dc_constraints                    0               0              0      1   25
dc_database_links                 0               0              0      0    0
dc_files                          0               0              0      7   64
dc_free_extents                 138    0.0      138   0.0      138     89   38
dc_global_oids                    0               0              0      0    0
dc_histogram_data                 0               0              0      0    0
dc_histogram_data_valu            0               0              0      0    0
dc_histogram_defs                67   17.9        0              0    347   98
dc_object_ids                    57    7.0        0              0    204  100
dc_objects                       19   10.5        0              0    475   99
dc_outlines                       0               0              0      0    0
dc_profiles                       4    0.0        0              0      1   14
dc_rollback_segments             36    0.0        0              0      7   88
dc_segments                     178    3.9        0            138    148   94
dc_sequence_grants                0               0              0      0    0
dc_sequences                      0               0              0      3   25
dc_synonyms                       0               0              0     81   98
dc_tablespace_quotas              0               0              0      1   50
dc_tablespaces                2,686    0.0        0              0      5   63
dc_used_extents                 138  100.0        0            138    140   60
dc_user_grants                   24    0.0        0              0     12   80
dc_usernames                     10    0.0        0              0      7   58
dc_users                         39    0.0        0              0     14   88
ifs_acl_cache_entries             0               0              0      0    0
          -------------------------------------------------------------

Library Cache Activity for DB: CSE60  Instance: cse60  Snaps: 13 -14
->"Pct Misses"  should be very low

                    Get       Pct        Pin        Pct               Invali-
Namespace         Requests    Miss     Requests     Miss     Reloads  dations
--------------- ------------ ------ -------------- ------ ---------- --------
BODY                       1    0.0              1    0.0          0        0
CLUSTER                    0                     0                 0        0
INDEX                      0                     0                 0        0
OBJECT                     0                     0                 0        0
PIPE                       0                     0                 0        0
SQL AREA                 968    1.3         66,345    0.0          0        0
TABLE/PROCEDURE           32    6.3            140    6.4          0        0
TRIGGER                    0                     0                 0        0
          -------------------------------------------------------------
 SGA Memory Summary for DB: CSE60  Instance: cse60  Snaps: 13 -14

SGA regions                       Size in Bytes
------------------------------ ----------------
Database Buffers                     45,056,000
Fixed Size                              104,936
Redo Buffers                            172,032
Variable Size                        79,687,680
                               ----------------
sum                                 125,020,648
          -------------------------------------------------------------

SGA breakdown difference for DB: CSE60  Instance: cse60  Snaps: 13 -14

Pool        Name                        Begin value      End value  Difference
----------- ------------------------ -------------- -------------- -----------
java pool   free memory                   1,003,520      1,003,520           0
shared pool DML locks                        84,000         84,000           0
shared pool KGFF heap                         6,456          6,456           0
shared pool KGK heap                          1,848          1,848           0
shared pool KQLS heap                     1,889,376      1,921,240      31,864
shared pool LRMPD SGA Table                  56,448         56,448           0
shared pool PL/SQL DIANA                    479,224        479,224           0
shared pool PL/SQL MPCODE                   192,216        192,216           0
shared pool PLS non-lib hp                    2,136          2,136           0
shared pool SYSTEM PARAMETERS               105,112        105,112           0
shared pool State objects                   220,000        220,000           0
shared pool branches                         36,576         36,576           0
shared pool db_block_buffers              1,144,000      1,144,000           0
shared pool db_block_hash_buckets           380,848        380,848           0
shared pool db_handles                       88,000         88,000           0
shared pool dictionary cache              1,258,936      1,269,232      10,296
shared pool enqueue_resources                56,160         56,160           0
shared pool event statistics per ses        399,280        399,280           0
shared pool fixed allocation callbac          1,904          1,904           0
shared pool free memory                  46,283,016     46,064,992    -218,024
shared pool java static objs                 41,128         41,128           0
shared pool ktlbk state objects              67,536         67,536           0
shared pool latch nowait fails or sl         37,632         37,632           0
shared pool library cache                 4,852,384      4,926,056      73,672
shared pool long op statistics array         57,040         57,040           0
shared pool message pool freequeue          191,192        191,192           0
shared pool miscellaneous                   529,744        538,072       8,328
shared pool processes                       107,200        107,200           0
shared pool sessions                        279,680        279,680           0
shared pool simulator trace entries          60,000         60,000           0
shared pool sql area                     19,537,144     19,630,160      93,016
shared pool table columns                    23,760         24,304         544
shared pool table definiti                    4,408          4,712         304
shared pool transactions                    191,520        191,520           0
shared pool trigger inform                      448            448           0
            db_block_buffers             45,056,000     45,056,000           0
            fixed_sga                       104,936        104,936           0
            log_buffer                      163,840        163,840           0
          -------------------------------------------------------------
 init.ora Parameters for DB: CSE60  Instance: cse60  Snaps: 13 -14

                                                                  End value
Parameter Name                Begin value                       (if different)
----------------------------- --------------------------------- --------------
background_dump_dest          /home/oracle/admin/cse60/bdump
compatible                    8.1.7.3
control_file_record_keep_time 0
control_files                 /maxdb1/cse60/ctrl1cse60.dbf, /ho
core_dump_dest                /home/oracle/admin/cse60/cdump
db_block_buffers              5500
db_block_lru_latches          3
db_block_size                 8192
db_file_multiblock_read_count 16
db_files                      20
db_name                       cse60
db_writer_processes           3
dml_locks                     500
java_pool_size                1M
local_listener                (address_list=(address=(protocol=
log_buffer                    163840
log_checkpoint_interval       10000
max_dump_file_size            10240
optimizer_mode                CHOOSE
processes                     100
remote_login_passwordfile     EXCLUSIVE
rollback_segments             roll1, roll2, roll3, roll4
shared_pool_size              75000000
sort_area_retained_size       2000000
sort_area_size                10000000
user_dump_dest                /home/oracle/admin/cse60/udump
          -------------------------------------------------------------

End of Report

      Rating: 3
I am
      stumped-but I have not given up ...  August 20, 2002

Reviewer:  H  from USA
Tom,
Apologize for loading you with questions. But, I think only the DBA God can help
me here:

I doubled my buffer cache to 90 M, and increased my SGA to 100M, when I run the
following :
        select name, bytes/1024/1024 "Size in MB"
         from v$sgastat
         where name='free memory' and pool='shared pool'
my free memory is 90M
Also,
my buffer cache hit ratio leveled out at 23.5% ! which i get by
select round(((1-(sum(decode(name,
        'physical reads', value,0))/
        (sum(decode(name, 'db block gets', value,0))+
        (sum(decode(name, 'consistent gets', value, 0))))))*100),2)
        || '%' "Buffer Cache Hit Ratio"
        from v$sysstat

Any thoughts - please advice

          Followup:

That is the buffer cache hit ratio since the database was up -- and it ain't
been up long.

Use the cache hit ratio from your statspack report for some window of time.

Warm up the cache, run your workload.
Do a statspack snapshot
Do work for 15 minutes
Do another snapshot and see what it says.

      Rating: 3
No
      improvement-any other ideas  August 20, 2002

Reviewer:  H  from USA
No luck Buffer Hit % is still stuck at 16%. Challenges of tuning packages....!

Thanks for your help

          Followup:

My only idea is -- ignore the cache hit ratio.  It is not what you want to focus
on.

Do you have a desired end state you need to get to, eg: X tps.  If not, that is
step one.  Then step two would be trying to figure out if this machine can even
get there.

(90m is small on this machine, I did say 500m.  Also, you need to warm your
cache before measuring anything)

      Rating: 3
Memory
      ?  August 21, 2002
Reviewer:  H  from
      USA
Using the following :
        select name, bytes/1024/1024 "Size in MB"
         from v$sgastat
         where name='free memory' and pool='shared pool'
i get free memory same as my shared pool, please explain if possible...

          Followup:

umm, you have memory allocated to your shared pool that you are not using.

your buffer cache is still just 90m.

      Rating:
      3
Memory  August 21, 2002
Reviewer:  H
       from USA
Not sure if I follow you. I have reduced my buffer cache to 45M. Although i had
same free memory with 100M

          Followup:

run this query instead:

ops$tkyte@ORA8I.WORLD> select name,
  2             round(bytes/1024/1024,2) mbytes,
  3             mbytes_tot,
  4             round(round(bytes/1024/1024,2)/mbytes_tot*100) pct,
  5             round(to_number(value)/1024/1024) shared_pool_size
  6    from v$sgastat,
  7             (select round(sum(bytes)/1024/1024) mbytes_tot
  8                    from v$sgastat
  9                   where pool = 'shared pool'),
 10             (select value , rownum
 11                    from v$parameter
 12                   where name = 'shared_pool_size' )
 13   where pool = 'shared pool'
 14  /

NAME                               MBYTES MBYTES_TOT        PCT SHARED_POOL_SIZE
------------------------------ ---------- ---------- ---------- ----------------
free memory                         27.33        104         26               98
miscellaneous                         2.4        104          2               98
table columns                         .02        104          0               98
table definiti                        .01        104          0               98
VIRTUAL CIRCUITS                      .46        104          0               98
KQLS heap                            1.44        104          1               98
PL/SQL DIANA                         3.05        104          3               98
trigger source                          0        104          0               98
PLS non-lib hp                          0        104          0               98
sessions                              .62        104          1               98
type object de                        .03        104          0               98
State objects                         .29        104          0               98
fixed allocation callback               0        104          0               98
joxs heap init                          0        104          0               98
pl/sql source                         .07        104          0               98
KGFF heap                             .06        104          0               98
trigger inform                          0        104          0               98
dictionary cache                     4.96        104          5               98
trigger defini                        .01        104          0               98
KGK heap                              .01        104          0               98
db_block_hash_buckets                 .38        104          0               98
library cache                       20.92        104         20               98
db_block_buffers                     2.13        104          2               98
sql area                            18.14        104         17               98
PL/SQL MPCODE                       20.83        104         20               98
joxlod: in ehe                        .06        104          0               98
temporary tabl                          0        104          0               98
event statistics per sess             .99        104          1               98

28 rows selected.

it'll show you the areas of the shared pool being used, how big the shared pool
ACTUALLY is (which is larger then shared_pool_size you'll note) and what pct of
the shared pool each region is consuming.

      Rating: 3
DML statements
      not using memory  August 23, 2002
Reviewer:
      H  from USA
Our case tool that I have posted the statspack report for, does not use memory
for DML,select statements. And this is causing for buffer cache hit ratio being
around 25% and as you can imagine has a serious impact on performance for
develpers.

Any ideas please help..!

Thanks in advance.

          Followup:

It is impossible for your case tool to not use memory for DML/select statements
as the buffer cache is something WE control, behind the scenes.

I cannot "imagine" that this has a serious impact on performance.  In fact,
quite the 100% OPPOSITE could be quite true.  A cache hit ratio has no meaning
to me or anyone else as an indicator of a tuned system.

What you are seeing is that the same data isn't being used !!  In an OLTP type
system, low cache hit ratios can be the NORM.  I read the row, look at the row
and no one else looks at it.

But anyway, you seem destined to keep making your block buffer cache smaller and
smaller, even though i've said (more then once) that on this 2gig machine, maybe
-- just maybe -- a larger block buffer cache would be called for.

      Rating: 3
DML not using
      memory.  August 23, 2002
Reviewer:  H
       from USA
Tom,
Thanks for your reply, I have increased the buffer cache and it has helped
somewhat. However I am approaching this in a cautious manner as there are 7
other instances running on this machine(prod) and therefore shareing/contending
for the same  memory. I do not want to give too much to this case tool and bring
down other mission critcal prod. apps.

I dont think i can do much about it from DB prespective.

Thanks for your help.

          Followup:

well, there you go then.  Insufficient resources to run your application.  Not
too much we can do here then.

If you have a teeny tiny buffer cache and an application that hits a ton of data
(and hits it over and over), by definition "cache hit ratio will be low" as we
cannot cache all of the stuff you are using.

      Rating: 3
Huge wait
      time  September 23, 2002
Reviewer:
      A reader
Tom,  Following is the top wait event recorded in statspack for an hour
duration.  For one hour, is it possible to have 38 days of wait time.  Rather,
how to interprete this.

                                       Wait     % Total
Event                        Waits  Time (cs)   Wt Time
--------------------- ------------ ------------ -------
latch free             152,008,963  312,258,641   97.18

Thanks.

          Followup:

You had many hundreds of people waiting on it concurrently.  If 100 people wait
1 second -- that's 100 seconds of wait time (and it might have take 1 second on
the wall clock)

You need to read:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1163635055580
run that script.  I'll bet we have yet another system that hasn't heard about
bind variables yet -- and it is literally KILLING your performance.

      Rating: 4
Bug
      2079526  September 25, 2002
Reviewer:  Muhammad
      Raf'ii
There were around 50 concurrent sessions at that time.  Even if we consider 100
sessions, it can't wait 9 hours in an hour.  Could this be related to bug
#2079526?

Thanks.

          Followup:

why can't it wait 9hours in an hour?  you have 50 sessions, you could easily
have 50 hours of wait in one hour.

It could be -- but the bug would be that the waits actually did exist (not that
the wait time reported is incorrect).

I would use the script I pointed you to first, the refered to bug was fixed in
8172 patchset so if you have that, it won't be that.

Look to see if you have a bind variable issue first.

      Rating:
      4
  September 25, 2002
Reviewer:
      A reader
Hi Tom,

Yes, you are right!  But, if you look at the statistics

                                       Wait     % Total
Event                        Waits  Time (cs)   Wt Time
--------------------- ------------ ------------ -------
latch free             152,008,963  312,258,641   97.18

there are 867 hours of wait time in an hour for just 50 concurrent sessions.
How is it possible?

We are using 8173.  But, Note:176129.1 says Bug:2079526 affects 8.1.7.3
inclusive.  So, can I say that it is a bug?

Thanks Tom.

          Followup:

Read the bug -- the bug introduces a large wait in certain very specific
conditions.

It does not say "this bug makes the wait times appear to be larger then humanly
possible".

So I would say "this bug is most likely totally unrelated".

Your premise is "we could not have waited that long in that period", the bugs
premise is "we make you wait longer".  There is no way that bug would make you
wait longer then you think you could have waited.

It could be that some counter wrapped around -- giving you erroneous results
during this hour.

      Rating: 5
Could you look
      at this?  February 10, 2003
Reviewer:  steve
       from UK
Running on W2K with 2Gb RAM.  Users complaining that the application is slow:

STATSPACK report for

DB Name         DB Id    Instance     Inst Num Release     OPS Host
------------ ----------- ------------ -------- ----------- --- ------------
EU0037P        547408264 eu0037p             1 8.1.7.4.1   NO  FSQNT22

                Snap Id     Snap Time      Sessions
                ------- ------------------ --------
 Begin Snap:                          1595 10-Feb-03 09:00:02       65
   End Snap:                          1596 10-Feb-03 10:00:11       65
    Elapsed:                              60.15 (mins)

Cache Sizes
~~~~~~~~~~~
           db_block_buffers:     200000          log_buffer:
    163840
              db_block_size:       4096    shared_pool_size:
      100M

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:              6,733.20              1,064.58
              Logical reads:             10,915.41              1,725.83
              Block changes:                 48.36                  7.65
             Physical reads:              3,737.62                590.95
            Physical writes:                  2.68                  0.42
                 User calls:                111.61                 17.65
                     Parses:                 25.47                  4.03
                Hard parses:                  1.14                  0.18
                      Sorts:                  6.59                  1.04
                     Logons:                  3.11                  0.49
                   Executes:                 61.69                  9.75
               Transactions:                  6.32

  % Blocks changed per Read:    0.44    Recursive Call %:               60.55
 Rollback per transaction %:   66.62       Rows per Sort:               15.15

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:              100.00
            Buffer  Hit   %:   65.76    In-memory Sort %:              100.00
            Library Hit   %:   98.76        Soft Parse %:               95.54
         Execute to Parse %:   58.71         Latch Hit %:               99.98
Parse CPU to Parse Elapsd %:   39.52     % Non-Parse CPU:               99.99

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   59.44   60.61
    % SQL with executions>1:   94.13   92.01
  % Memory for SQL w/exec>1:   99.81   99.75

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
db file scattered read                            958,889      412,840   63.82
enqueue                                               354       99,266   15.34
latch free                                          3,813       88,550   13.69
db file sequential read                            25,703       20,866    3.23
log file sync                                       7,922        9,463    1.46

And:

 SQL ordered by Gets for DB: EU0037P  Instance: eu0037p  Snaps: 1595 -1596
-> End Buffer Gets Threshold:   10000
-> Note that resources reported for PL/SQL includes the resources used by
   all SQL statements called within the PL/SQL code.  As individual SQL
   statements are also reported, it is possible and valid for the summed
   total % to exceed 100

  Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
     13,251,754       15,328          864.5    33.6     48801517
select min(safsmain.safsid) into :b0  from safsmain where (safsm
ain.methodreq=UPPER(:b1) and safsmain.pending='YES')

      7,741,920           59      131,219.0    19.7      8654675
BEGIN unload_jobs_gb(:1); END;

      5,658,345        3,399        1,664.7    14.4   2825963052
select statlog.rowid  ,statlog.safsid ,TO_CHAR(statusdate,'YYYYM
MDDHH24MISS') ,METHODID ,STATUSID ,ORIGINID ,DESTID  from statlo
g ,safsmain where statlog.safsid=safsmain.safsid

      5,625,648           92       61,148.3    14.3    540687189
SELECT *   FROM UKDATA.TELCASES  WHERE CAS_CASEID = :b1

      4,355,710           60       72,595.2    11.1   4161497675
BEGIN load_jobs_gb(:1); END;

      4,280,613           60       71,343.6    10.9   1372567761
SELECT *   FROM UKDATA.TELCASES  WHERE CAS_CALLSTAT = 'Logged' F
OR UPDATE

And;

 Tablespace IO Stats for DB: EU0037P  Instance: eu0037p  Snaps: 1595 -1596
->ordered by IOs (Reads + Writes) desc

Tablespace
------------------------------
                 Av      Av     Av                    Av        Buffer Av Buf
         Reads Reads/s Rd(ms) Blks/Rd       Writes Writes/s      Waits Wt(ms)
-------------- ------- ------ ------- ------------ -------- ---------- ------
UK_V_LARGE
       969,985     269    4.2    13.9        3,304        1        184    9.2
UK_LARGE
        13,521       4   18.6     2.7           76        0         26   27.7
INDX
           386       0   20.4     1.0        1,783        0          0    0.0
RBS
             1       0 ######     1.0        2,059        1          2    0.0
SYSTEM
           639       0   15.9     1.1        1,051        0         11    1.8
UK_SMALL
             1       0  840.0     1.0          671        0          1    0.0
RBS_TEMP
             1       0  890.0     1.0          351        0          0    0.0
UK_MEDIUM
            25       0   49.6     1.0          275        0          0    0.0
FR_LARGE
            18       0   90.6     1.0           73        0          0    0.0
FR_V_LARGE
            11       0  113.6     1.0           26        0          0    0.0
FR_MEDIUM
             2       0  580.0     1.0            7        0          0    0.0
FR_SMALL
             1       0 ######     1.0            5        0          0    0.0
ARC_INDEX
             1       0 ######     1.0            1        0          0    0.0
GR_LARGE
             1       0 ######     1.0            1        0          0    0.0
GR_MEDIUM
             1       0 ######     1.0            1        0          0    0.0
GR_SMALL
             1       0 ######     1.0            1        0          0    0.0
GR_V_LARGE
             1       0  660.0     1.0            1        0          0    0.0
SAFS_LOG
             1       0 ######     1.0            1        0          0    0.0
TEMP
             1       0 ######     1.0            1        0          0    0.0
TOOLS
             1       0 ######     1.0            1        0          0    0.0

Now I know that the parse to execute is low and I'm working with the third party
suppliers to address that.  But the thing that stands out for me is the IO on
the tablespaces and one tablespace in particular - uk_v_large.  What strategies
can I use to address that issue?

Cheers

Steve

          Followup:

soft parse ratio stinks.  should be over 99% on a transactional application
which this is.  they are building dynamic queries somewhere and NOT using bind
variables.

the best way to fix IO is to remove it.  consider:

  Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
     13,251,754       15,328          864.5    33.6     48801517
select min(safsmain.safsid) into :b0  from safsmain where (safsm
ain.methodreq=UPPER(:b1) and safsmain.pending='YES')

No way that should take 864.5 buffer gets/exec.  That is a pretty darn popular
query in the system.  it is what we would call "low hanging fruit of enormous
proportions".  Consider:

ops$tkyte@ORA920> create table t as
  2  select rownum safsid, to_char(mod( rownum, 100 )) methodreq,
  3         decode( mod(rownum,2), 0, 'YES', 'NO' ) pending, all_objects.*
  4    from all_objects;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace on
ops$tkyte@ORA920> select min(safsid)
  2    from t
  3   where methodreq = upper( '56' )
  4     and pending = 'YES'
  5  /

MIN(SAFSID)
-----------
         56

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'T'

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

ops$tkyte@ORA920> create index t_idx on t(pending,methodreq,safsid);

Index created.

ops$tkyte@ORA920> analyze table t compute statistics
  2  for table for all indexes for all indexed columns;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select min(safsid)
  2    from t
  3   where methodreq = upper( '56' )
  4     and pending = 'YES'
  5  /

MIN(SAFSID)
-----------
         56

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=9)
   1    0   SORT (AGGREGATE)
   2    1     FIRST ROW (Cost=2 Card=98 Bytes=882)
   3    2       INDEX (RANGE SCAN (MIN/MAX)) OF 'T_IDX' (NON-UNIQUE) (Cost=2
Card=300)

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

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace off

there shouldn't be more then 3 or 4 consistent gets per execute on that
query

You have to concentrate on reducing IO and then when IO is at its lowest, you
can worry about how best to spread out the remaining stuff.

It is like the old saying "if you watch the pennies, the dollars take care of
themselves".  If you want the LIO's the PIO's will take care of themselves --
fix the low hanging fruit, which gives you the biggest payback and then move
onto the things that will give you 5-10% increases at best in most cases,
reorging IO patterns...

If you have the ability to stripe your disks -- go for it -- that'll help spread
IO out.  All I have to work with is "2gig of ram" which is pretty much "not
meaningful" for tuning IO patterns ;)

      Rating: 5
Oops - should
      have also said:  February 10, 2003
Reviewer:
      Steve  from UK
Perhaps I should also include:

 File IO Stats for DB: EU0037P  Instance: eu0037p  Snaps: 1595 -1596
->ordered by Tablespace, File

Tablespace               Filename
------------------------ ----------------------------------------------------
                 Av      Av     Av                    Av        Buffer Av Buf
         Reads Reads/s Rd(ms) Blks/Rd       Writes Writes/s      Waits Wt(ms)
-------------- ------- ------ ------- ------------ -------- ---------- ------
            25       0   49.6     1.0          275        0          0

UK_SMALL                 D:\ORANT8_EU0037P\DATABASE\UK_SMALL.ORA
             1       0  840.0     1.0          671        0          1    0.0

UK_V_LARGE               D:\ORANT8_EU0037P\DATABASE\UK_V_LARGE.ORA
       969,985     269    4.2    13.9        3,304        1        184    9.2

      Rating: 5
Excellent but
      why?  February 10, 2003
Reviewer:  Steve
       from UK
We already had an index:
CREATE INDEX TPSAFS.SAFSMAIN_ITEMKEY
 ON SAFSMAIN(SAFSID, METHODREQ, PENDING)
Which gave the following output:

  1  select min(safsmain.safsid)  from safsmain
  2* where (safsmain.methodreq=UPPER('idp1') and safsmain.pending='YES')
tpsafs@EU0037P.FSQ.EU.MARS> /
 MIN(SAFSMAIN.SAFSID)
--------------------

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=85 Card=1 Bytes=12)

   1    0   SORT (AGGREGATE)

   2    1     INDEX (FAST FULL SCAN) OF 'SAFSMAIN_ITEMKEY' (NON-UNIQUE
          ) (Cost=85 Card=16702 Bytes=200424)

Statistics
----------------------------------------------------------
          0  recursive calls
          8  db block gets
        878  consistent gets
          0  physical reads
          0  redo size
        377  bytes sent via SQL*Net to client
        425  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed
I then changed the order:

  1* create index t_idx on safsmain(pending,methodreq,safsid)
tpsafs@EU0037P.FSQ.EU.MARS> /

Index created.

tpsafs@EU0037P.FSQ.EU.MARS> select min(safsmain.safsid)  from safsmain
  2  where (safsmain.methodreq=UPPER('idp1') and safsmain.pending='YES');
 MIN(SAFSMAIN.SAFSID)
--------------------

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=12)

   1    0   SORT (AGGREGATE)

   2    1     FIRST ROW (Cost=2 Card=16702 Bytes=200424)

   3    2       INDEX (RANGE SCAN (MIN/MAX)) OF 'T_IDX' (NON-UNIQUE) (
          Cost=2 Card=16702)

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

I realise the index is created in a different order but can you explain why it
makes such a huge difference?

Thanks

          Followup:

becuase the second index can use the predicate:

 2  where (safsmain.methodreq=UPPER('idp1') and safsmain.pending='YES');

to zero in on the methodreq/pending rows and the MIN() aggregate further tells
us "just goto the FIRST methodreq/pending and get that one".  We just navigate
to the FIRST index entry for methodreq/pending and return....  That will take N
logical IOs where N = height of the tree.

when the index was on:

CREATE INDEX TPSAFS.SAFSMAIN_ITEMKEY
 ON SAFSMAIN(SAFSID, METHODREQ, PENDING)

well, we cannot zero in on methodreq/pending so easily -- you probably fell into
the (totally FALSE) trap of "you should put the most selective stuff first in an
index"  Nothing but nothing could be further from the truth!!!

      Rating: 5
Low hanging
      fruit  February 11, 2003
Reviewer:  Steve
       from Steve
Excellent - thank you.  OK, I understand the last one but next on my hit list is
this query and this one seems to be gumming up the system because the
application also does a select * .... for update on a regular basis:

ukdata@EU0037P> var b1 number
ukdata@EU0037P> exec :b1:=763437

PL/SQL procedure successfully completed.

ukdata@EU0037P> set autotrace on
ukdata@EU0037P> select * from telcases where cas_caseid=:b1;

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=617)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'CASES' (Cost=3 Card=1 By
          tes=617)

   2    1     INDEX (UNIQUE SCAN) OF 'CASE_PK' (UNIQUE) (Cost=2 Card=2
          )

Statistics
----------------------------------------------------------
          0  recursive calls
          8  db block gets
      61244  consistent gets
      42011  physical reads
          0  redo size
       4560  bytes sent via SQL*Net to client
        314  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

ukdata@EU0037P> SELECT *   FROM UKDATA.TELCASES  WHERE CAS_CALLSTAT = 'Logged'
  2  /

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6119 Card=37663 Byte
          s=23238071)

   1    0   TABLE ACCESS (FULL) OF 'CASES' (Cost=6119 Card=37663 Bytes
          =23238071)

Statistics
----------------------------------------------------------
          0  recursive calls
          8  db block gets
      61244  consistent gets
      42124  physical reads
          0  redo size
       4560  bytes sent via SQL*Net to client
        314  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          0  rows processed

ukdata@EU0037P>

Why would the gets be exactly the same for both queries when one uses an index
and the other does not?

There are 302748 rows in the table which looks like:

CREATE TABLE TELCASES
(
  CAS_ID                CHAR(14),
  CAS_PROD              CHAR(10),
  CAS_CASEID            CHAR(14)                NOT NULL,
  CAS_FUDATE            DATE,
  CAS_NAME              CHAR(40),
  CAS_CNNAME            CHAR(35),
  CAS_PREFIX            CHAR(5),
  CAS_FNAME             CHAR(12),
  CAS_MIDINIT           CHAR(1),
  CAS_LNAME             CHAR(20),
  CAS_SUFFIX            CHAR(5),
  CAS_PHONE             CHAR(24),
  CAS_FAX               CHAR(24),
  CAS_EMAIL             CHAR(40),
  CAS_CONTMETH          CHAR(10),
  CAS_REASON            VARCHAR2(100),
  CAS_CATGRY            CHAR(15),
  CAS_REPADD            CHAR(10),
  CAS_ADDTM             NUMBER(38),
  CAS_ADDDATE           DATE,
  CAS_REPACC            CHAR(10),
  CAS_ACCTM             NUMBER(38),
  CAS_ACCDATE           DATE,
  CAS_REPCLO            CHAR(10),
  CAS_REPASSN           CHAR(10),
  CAS_CLOTM             NUMBER(38),
  CAS_CLODATE           DATE,
  CAS_SEVERITY          CHAR(10),
  CAS_CALLSTAT          CHAR(10),
  CAS_TTIME             NUMBER(38),
  CAS_ELAP_TIME         NUMBER(38),
  CAS_TIME_TYPE         CHAR(1),
  CAS_BILLCODE          CHAR(10),
  CAS_CALLTYPE          CHAR(10),
  CAS_TCALL             NUMBER(38),
  CAS_ABSTRACT          CHAR(60),
  CAS_SOLUTION          CHAR(60),
  CAS_QUEASSN           CHAR(10),
  CAS_TIMER_DATE        DATE,
  CAS_TIMER_TIME        NUMBER(38),
  UPDATE_CAS            DATE,
  UPTIME_CAS            NUMBER(38),
  LOCKED_CAS            CHAR(8),
  CAS_MCID              CHAR(12),
  CAS_HEALTH_CHECK      CHAR(1),
  CAS_ETA_DATE          DATE,
  CAS_ETA_TIME          NUMBER(38),
  CAS_CALL_NUMBER       NUMBER(38),
  CAS_CALLER            CHAR(35),
  CAS_ACCESS            CHAR(25),
  CAS_INSTRUCTION       VARCHAR2(250),
  CAS_DATE_DUE          DATE,
  CAS_CONTACT           CHAR(35),
  CAS_CONTACT_PHONE     CHAR(24),
  CAS_NOTE              VARCHAR2(250),
  CAS_CLASS             CHAR(20),
  CAS_SATISFACTION      NUMBER(38),
  CAS_FAULT1            NUMBER(38),
  CAS_FAULT2            NUMBER(38),
  CAS_FAULT3            NUMBER(38),
  CAS_FAULT4            NUMBER(38),
  CAS_FAULT5            NUMBER(38),
  CAS_FAULT6            NUMBER(38),
  CAS_TYPE              CHAR(5),
  CAS_VEND              CHAR(5),
  CAS_CHARGEABLE        VARCHAR2(1),
  CAS_ORDER_NO          VARCHAR2(20),
  CAS_NO_MASK           VARCHAR2(15),
  CAS_ACID              CHAR(3),
  CAS_ENGINEER          CHAR(30),
  CAS_DEFAULT_ACID      CHAR(3),
  CAS_DEFAULT_ENGINEER  CHAR(30),
  CAS_TOWN              CHAR(32),
  CAS_POSTCODE          CHAR(10),
  CAS_ENGINEER_REGION   CHAR(1),
  CAS_ORIGIN            VARCHAR2(1),
  CAS_SLA               CHAR(1)                 NOT NULL,
  CAS_ALLOCDATE         DATE,
  CAS_ALLOCUSER         CHAR(10),
  CAS_OCSTAT            CHAR(1)
)
TABLESPACE UK_V_LARGE
PCTUSED    40
PCTFREE    10
INITRANS   1
MAXTRANS   255
STORAGE    (
            INITIAL          5M
            NEXT             5M
            MINEXTENTS       1
            MAXEXTENTS       2147483645
            PCTINCREASE      0
            FREELISTS        1
            FREELIST GROUPS  1
            BUFFER_POOL      DEFAULT
           )
LOGGING
NOCACHE
NOPARALLEL
MONITORING;

Thanks

Steve

          Followup:

autotrace can "lie"

use sql_trace=true and tkprof:

alter session set sql_trace=true;
run the query
exit

run tkprof WITHOUT explain= and see what query plan is actually there and we'll
go from there.

      Rating: 5
You're
      right.....  February 12, 2003
Reviewer:
      steve  from UK
autotrace does lie!!!  TKPROF showed the query doing a full table scan and
ignoring the index.  Further investigation showed that the index had not been
analysed!!!  D'oh!!  It now uses the index and gets are down to 3!

Thanks

Steve

      Rating: 5
Autotrace
      lie?  February 13, 2003
Reviewer:  Robert
       from Memphis, USA.
Tom,

Very good info....

But... Autotrace lie?
Autotrace is a simple and handy tool.

Can we ever trust it?
If so under what circumstances... or under what circumstances should we not
trust it?

Thanks,

Robert.

          Followup:

autotrace is pretty good -- but it only tells you the possible plan that would
be used, it is just an explain plan interface.

the source of truth -- the ACTUAL plan that was used -- it only available via
sql_trace and tkprof.

you want to see what probably will happen -- explain plan
you want to see what actually HAPPENED (past tense) -- sql_trace

      Rating:
      5
  May 13, 2003
Reviewer:  Mike
       from TX
            Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:     235 12-May-03 18:01:09       29     237.7
  End Snap:     236 12-May-03 18:16:14       33 #########
   Elapsed:               15.08 (mins)

Cache Sizes (end)
~~~~~~~~~~~~~~~~~
               Buffer Cache:        32M      Std Block Size:         4K
           Shared Pool Size:        48M          Log Buffer:     1,024K

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:              4,048.07             99,013.51
              Logical reads:              2,987.07             73,062.05
              Block changes:                 15.90                388.89
             Physical reads:              2,716.69             66,448.81
            Physical writes:                  2.04                 49.89
                 User calls:                 19.47                476.16
                     Parses:                  4.62                112.95
                Hard parses:                  0.69                 16.76
                      Sorts:                  5.47                133.68
                     Logons:                  0.42                 10.19
                   Executes:                  5.56                136.11
               Transactions:                  0.04

  % Blocks changed per Read:    0.53    Recursive Call %:    57.32
 Rollback per transaction %:    0.00       Rows per Sort:    11.36

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:  100.00
            Buffer  Hit   %:    9.09    In-memory Sort %:   99.96
            Library Hit   %:   92.18        Soft Parse %:   85.16
         Execute to Parse %:   17.02         Latch Hit %:   99.96
Parse CPU to Parse Elapsd %:   98.19     % Non-Parse CPU:   98.45

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   90.48   93.93
    % SQL with executions>1:   33.75   34.48
  % Memory for SQL w/exec>1:   30.48   38.40

Top 5 Timed Events
~~~~~~~~~~~~~~~~~~                                                     % Total
Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
CPU time                                                          209    80.05
db file scattered read                            339,803          45    17.14
control file parallel write                           293           2      .67
direct path write                                      55           2      .66
db file parallel write                                112           1      .52
          -------------------------------------------------------------

Tom,
It's a web oltp application.
It looks like the shared-pool is almost full, and the DB cache is too small.
and:
 % SQL with executions>1:   33.75   34.48
  % Memory for SQL w/exec>1:   30.48   38.40

 Execute to Parse %:   17.02

The above tells us the application not using bind variables.
So, I shall increase the database buffers and shared-pool size and make the
application to use bind variables as much as possible. Are these assertments
correct? What could others you recommend besed the stats?
Thanks many.
Mike

          Followup:

soft parse % tells us the entire story.  85% is about 14.5% below where it needs
be.  No binds.

Do not increase the shared pool -- it is full because of unique sql.  making it
bigger will not make it less full, just harder for us to manage.

Look like a lot of junky SQL as well -- 73,062.05 per transaction just to
generate a page???  My asktom home page for example takes a whopping 300 or so.
32m probably isn't too small, the SQL needs an index or two to help it out
perhaps.  Given they don't use binds, they probably didn't spend a ton of time
on database design 101 either, so the model is probably totally illsuited to the
job.

The good news here - lots of low hanging fruit.  They must

o use binds
o review the sql
o change their model to answer the questions they ask
o get those LIO's down (and the PIO's will take care of themselves)

      Rating:
      5
  May 13, 2003
Reviewer:
      A reader
Thank you so much for the adive.
"
Look like a lot of junky SQL as well -- 73,062.05 per transaction just to
    generate a page???
"

The snapshots was 15min. I do not know how many pages generated. Should one page
cost about a few hundred LIO?

"
 32m probably isn't too small, the SQL needs an index or two to help it out
perhaps.
"
You do mean 32MB DB Buffer size is fine?
We currently have 32M DB Buffer and 48M for the shared-pool.
I feel 32M for db buffer is little to small.

          Followup:

a page would be a transaction.

32m might be too small, might not be.  I would look at the SQL first.  73k
logical IO's per transaction is what I might expect from a data warehouse, not
an interactive web site.

cut down the LIO's and the PIO's will take care of themselves.

The problem is if you up the buffer cache and make the problem "bearable", you
will still

a) have the problem
b) never get it fixed
c) be running at a fraction of the efficiency you should

      Rating: 5
Interesting
      Stats on 9ias R2 Infrastructure Database  May 13, 2003

Reviewer:  Sean  from Atlanta
  Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:     156 12-May-03 17:15:04      175      30.4
  End Snap:     157 12-May-03 17:30:03      176      30.5
   Elapsed:               14.98 (mins)

Cache Sizes (end)
~~~~~~~~~~~~~~~~~
               Buffer Cache:       512M      Std Block Size:         8K
           Shared Pool Size:        32M          Log Buffer:     1,024K

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:              3,568.14             20,965.75
              Logical reads:              5,639.06             33,134.08
              Block changes:                 24.99                146.86
             Physical reads:                  0.98                  5.75
            Physical writes:                  1.33                  7.80
                 User calls:                  9.70                 57.01
                     Parses:                  8.66                 50.90
                Hard parses:                  0.04                  0.23
                      Sorts:                  7.08                 41.63
                     Logons:                  0.01                  0.08
                   Executes:                 13.89                 81.64
               Transactions:                  0.17

  % Blocks changed per Read:    0.44    Recursive Call %:    72.33
 Rollback per transaction %:    0.65       Rows per Sort:     3.75

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:  100.00
            Buffer  Hit   %:   99.99    In-memory Sort %:  100.00
            Library Hit   %:   99.88        Soft Parse %:   99.55
         Execute to Parse %:   37.66         Latch Hit %:   99.98
Parse CPU to Parse Elapsd %:  106.36     % Non-Parse CPU:   98.31

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   91.23   92.58
    % SQL with executions>1:   76.36   75.61
  % Memory for SQL w/exec>1:   83.32   82.58

HiTom,
it is the stats for 9ias infrastructure DB of our web production system.
Is it interesting 33,134.08 LIO per transcation for 9ias repository for
providing 9ias in Product Metadata Service, Security Service and Management
Service?
Though it Looks like Oracle did good in using bind.

          Followup:

depends, the infrastructure database is NOT generating short, sweet transactions
in the form of a webpage for example.

How many hours or days long might an OID "transaction" span?

This is not a transactional system you are looking at.

again, one must look at the SQL and say "does that look right", "is it too high"

      Rating:
      4
  May 13, 2003
Reviewer:  Sean
       from Atlanta
I fully agree with on looking into bad sqls.

"
How many hours or days long might an OID "transaction" span?  "

Tom, I might not anwser that, but I could look into the statspack:
Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:            182,622.76            396,339.57
              Logical reads:             45,336.28             98,391.68
              Block changes:              1,172.27              2,544.13
             Physical reads:                 12.33                 26.75
            Physical writes:                 38.90                 84.42
                 User calls:                 46.27                100.41
                     Parses:                 40.72                 88.38
                Hard parses:                  0.17                  0.37
                      Sorts:                 51.40                111.55
                     Logons:                  0.01                  0.02
                   Executes:                 48.76                105.82
               Transactions:                  0.46

it stats User calls: 46.27/s and 100.41/tx and the transactions: 0.46/s.

Instance Activity Stats for DB: IASDB  Instance: iasdb  Snaps: 226 -227

Statistic                                      Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------

user calls                                    41,870           46.3        100.4
user commits                                     417            0.5          1.0

Based on these stats, I think 9ias infra is for transacational uses. I do not
know why the repository so busy....

          Followup:

that is TPS, not the duration of a transaction.  It would only take one "really
slow guy" out there doing a big transaction to through the rest of the numbers
out of wack.

bottom line = look at the sql to see if it is unreasonable.

      Rating:
      4
  May 20, 2003
Reviewer:
      A reader
Dear Tom, you have told:
“
2gig of ram.... 45meg buffer pool.  You might give serious consideration to say
a 500meg buffer pool maybe.
“
How to justify the 500M is a good number? How can we tell the db cache is
oversized?
I mean when to test, how can I justify that by what indictor? I know you discard
the buffer hit ratio. As the matter of fact, increase db cache directly results
the high hit ratio. If increase the size of the buffer could help the
performance, then why is higher the buffer ratio not a good thing?  I also
understood you have stated oversized shared-pool results difficulty to manage.
Does it hold the same for oversized db cache? If so, could you elaborate how
difficult to manage an oversized db buffer.

Thanks In Avance

          Followup:

in 9i, you have a buffer cache advisor that takes some of the guess work out of
the mix.  That is what I would suggest for that.

I pulled the number 500 out of the air on this one...  probably should not have.
 someone will derive "well, tom says you should use 25% of your ram for buffer
cache" (i didn't).  just looked like 45m was a tad too small -- but they
probably have SQL to fix first.

      Rating:
      4
  May 20, 2003
Reviewer:
      A reader
the DB_CACHE_ADVICE for collecting stats of the
impact of the change of the buffer cache. Does it the similar to buffer ratio
hit? If we have have the ratio, why bother the cache advice?

          Followup:

what use is that ratio.  if you have a 99.999% cache hit, is that good, is that
bad -- does that tell you if your buffer cache it 5x bigger then it needs to be,
does it tell you if your system is tuned

(rhetorical question, answers are all "no" or "cannot tell")

      Rating:
      4
  May 20, 2003
Reviewer:
      A reader
Then what about the Cache Advice? Does it yield usefull or Not?

          Followup:

look upwards two reviews.  where I said "in 9i, i would use ...."

      Rating:
      5
  May 21, 2003
Reviewer:
      A reader
Tom,
What are possible  nagitive impacts, if we had a too large DB CACHE?
Thanks.

          Followup:

you improperly allocate memory to the wrong place.  you could use that memory to
your benefit elsewhere but cannot since you gave it to the db cache.

      Rating:
      4
  May 21, 2003
Reviewer:
      A reader
"
you improperly allocate memory to the wrong place.  you could use that memory to

     your benefit elsewhere but cannot since you gave it to the db cache.
"
Any impact on the DB performance?
Thanks

          Followup:

yes, if you allocate 750m to the buffer cache on a system that has 1gig of ram
and 500 dedicated servers -- think about it, what is that system going to be
doing (besides swapping)

      Rating: 5
how to tell if
      it's reasonable or lot  May 26, 2003
Reviewer:
      A reader
This is a really helpful discussion!

In one of your follow-up you mentioned

<quote>
bottom line = look at the sql to see if it is unreasonable.
</quote>

Can you share with us  how to tell tell if it's reasonable or not?

Many Thanks!

          Followup:

use your judgement based on your knowledge of the data.

if you see a query like:

select max(id) from t where ....

doing more then 4 or 5 logical IO's per execution, that is not reasonable.  If
you see a 16 table join being executed 100 times per second that is doing 10,000
logical IO's to return 5 rows - that is not reasonable (not reasonable for two
reasons -- the lio's are high AND you execute it lots -- that is called low
hanging fruit)

      Rating: 5
Need
      advise.  July 01, 2003
Reviewer:  Nasir
      Sarwar  from Houston, TX USA
Tom,

Thanks for all your advises you've given us time to time. It gave us a better
understanding of Oracle.

Here is my statspack report. Can you please identify what areas need to worth
looking and what need to be further tune. I like to have your advise before I
implement my changes. Once again, thanks alot for all of your help!

My STATSPACK Report:

STATSPACK report for

DB Name         DB Id    Instance     Inst Num Release     Cluster Host
------------ ----------- ------------ -------- ----------- ------- ------------
REP9A          167154787 rep9a               1 9.2.0.1.0   NO      REPUBLIC

            Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:       1 01-Jul-03 13:56:30       10       4.9
  End Snap:       3 01-Jul-03 15:05:50       36       2.1
   Elapsed:               69.33 (mins)

Cache Sizes (end)
~~~~~~~~~~~~~~~~~
               Buffer Cache:       320M      Std Block Size:         8K
           Shared Pool Size:        72M          Log Buffer:       512K

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:              1,363.05              7,114.52
              Logical reads:              1,671.19              8,722.89
              Block changes:                 10.55                 55.07
             Physical reads:                537.52              2,805.62
            Physical writes:                414.76              2,164.87
                 User calls:                 48.79                254.66
                     Parses:                 10.36                 54.09
                Hard parses:                  3.37                 17.59
                      Sorts:                  1.58                  8.23
                     Logons:                  0.05                  0.27
                   Executes:                 11.00                 57.39
               Transactions:                  0.19

  % Blocks changed per Read:    0.63    Recursive Call %:    17.63
 Rollback per transaction %:   80.30       Rows per Sort:    46.70

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:   97.01       Redo NoWait %:  100.00
            Buffer  Hit   %:   76.91    In-memory Sort %:  100.00
            Library Hit   %:   87.15        Soft Parse %:   67.49
         Execute to Parse %:    5.75         Latch Hit %:   94.42
Parse CPU to Parse Elapsd %:   87.15     % Non-Parse CPU:   98.81

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   31.54   81.02
    % SQL with executions>1:   66.50   98.02
  % Memory for SQL w/exec>1:   45.69   92.20

Top 5 Timed Events
~~~~~~~~~~~~~~~~~~                                                     % Total
Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
direct path write                                 151,890      21,304    63.11
direct path read                                   45,338       3,761    11.14
buffer busy waits                                 213,204       2,737     8.11
db file scattered read                            103,809       1,687     5.00
PX Deq Credit: send blkd                           54,518       1,470     4.36
          -------------------------------------------------------------
 Wait Events for DB: REP9A  Instance: rep9a  Snaps: 1 -3
-> s  - second
-> cs - centisecond -     100th of a second
-> ms - millisecond -    1000th of a second
-> us - microsecond - 1000000th of a second
-> ordered by wait time desc, waits desc (idle events last)

                                                                   Avg
                                                     Total Wait   wait    Waits
Event                               Waits   Timeouts   Time (s)   (ms)     /txn
---------------------------- ------------ ---------- ---------- ------ --------
direct path write                 151,890          0     21,304    140    190.6
direct path read                   45,338          0      3,761     83     56.9
buffer busy waits                 213,204          0      2,737     13    267.5
db file scattered read            103,809          0      1,687     16    130.2
PX Deq Credit: send blkd           54,518          0      1,470     27     68.4
PX Deq: Execute Reply               1,430        476      1,276    893      1.8
PX Deq Credit: need buffer         58,004          2        215      4     72.8
PX qref latch                         178        140        144    807      0.2
control file parallel write         1,329          0         63     48      1.7
db file sequential read             4,593          0         42      9      5.8
log file parallel write             1,193      1,104         21     17      1.5
control file sequential read          636          0         13     21      0.8
log file sync                         192          0         13     70      0.2
latch free                          2,045      1,597         12      6      2.6
enqueue                               120          0          6     53      0.2
db file parallel write                284        133          4     14      0.4
library cache pin                      25          0          1     45      0.0
SQL*Net more data to client         2,870          0          1      0      3.6
PX Deq: Signal ACK                     23          7          1     27      0.0
db file parallel read                   2          0          0    139      0.0
PX Deq: Join ACK                       77          0          0      2      0.1
row cache lock                          8          0          0      8      0.0
buffer deadlock                         4          4          0      4      0.0
PX Deq: Parse Reply                    25          0          0      0      0.0
sort segment request                    2          0          0      5      0.0
PX Deq: Table Q Sample                 13          0          0      0      0.0
LGWR wait for redo copy                11          0          0      0      0.0
PX Deq: Table Q qref                    4          0          0      0      0.0
SQL*Net message from client       202,549          0     24,216    120    254.1
wakeup time manager                   130        130      4,446  34204      0.2
PX Idle Wait                        1,887      1,829      3,801   2014      2.4
PX Deq: Table Q Normal            459,810          1        932      2    576.9
PX Deq: Execution Msg               1,740        280        600    345      2.2
jobq slave wait                       158        151        475   3009      0.2
SQL*Net message to client         202,576          0          0      0    254.2
          -------------------------------------------------------------
 Background Wait Events for DB: REP9A  Instance: rep9a  Snaps: 1 -3
-> ordered by wait time desc, waits desc (idle events last)

                                                                   Avg
                                                     Total Wait   wait    Waits
Event                               Waits   Timeouts   Time (s)   (ms)     /txn
---------------------------- ------------ ---------- ---------- ------ --------
control file parallel write         1,329          0         63     48      1.7
log file parallel write             1,193      1,104         21     17      1.5
control file sequential read          532          0         12     23      0.7
db file parallel write                284        133          4     14      0.4
db file scattered read                 35          0          0      3      0.0
db file sequential read                13          0          0      3      0.0
latch free                              7          0          0      6      0.0
LGWR wait for redo copy                11          0          0      0      0.0
rdbms ipc message                   7,241      6,025     20,952   2894      9.1
smon timer                             25         10      4,450 ######      0.0
          -------------------------------------------------------------
 SQL ordered by Gets for DB: REP9A  Instance: rep9a  Snaps: 1 -3
-> End Buffer Gets Threshold:   10000
-> Note that resources reported for PL/SQL includes the resources used by
   all SQL statements called within the PL/SQL code.  As individual SQL
   statements are also reported, it is possible and valid for the summed
   total % to exceed 100

                                                     CPU      Elapsd
  Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
      4,302,380           20      215,119.0   61.9   347.25   4559.19 1486125977
SELECT DISTINCT MAX(a.object_id) oid  FROM objects a  WHERE a.ob
ject_type IN ('D','C')  AND  LOWER  (a.title) LIKE LOWER ('%tire
%')  AND a.object_id IN  (SELECT MAX(oo.object_id)   FROM object
s oo   WHERE system_id=a.system_id) GROUP BY a.system_id   ORDER
 BY oid

        665,509           21       31,690.9    9.6   168.45   6270.42 3063408322
 SELECT /*+ INDEX_COMBINE(O) */ DISTINCT n.class_id, n.attribute
_id, n.role_id  FROM node_map nm, members m, objects o,  nvisibl
e_map n, report_columns e  WHERE nm.node_id LIKE '__'  AND nm.li
nk_node_id = m.node_id AND m.object_id = o.object_id  AND o.clas
s_id = n.class_id  AND e.format_id = 3  AND e.attribute_id = n.a

        665,208           21       31,676.6    9.6   123.19   9641.05 2498503827
 SELECT /*+ INDEX_COMBINE(O) */ DISTINCT n.class_id, n.attribute
_id, n.role_id  FROM node_map nm, members m, objects o,  nvisibl
e_map n, report_columns e  WHERE nm.node_id LIKE 'AN__'  AND nm.
link_node_id = m.node_id AND m.object_id = o.object_id  AND o.cl
ass_id = n.class_id  AND e.format_id = 3  AND e.attribute_id = n

        565,612           20       28,280.6    8.1   145.48   9561.46 2562928129
 SELECT /*+ INDEX_COMBINE(O) */ DISTINCT n.class_id, n.attribute
_id, n.role_id  FROM node_map nm, members m, objects o,  nvisibl
e_map n, report_columns e  WHERE nm.node_id LIKE 'ANAC'  AND nm.
link_node_id = m.node_id AND m.object_id = o.object_id  AND o.cl
ass_id = n.class_id  AND e.format_id = 1  AND e.attribute_id = n

        344,592            1      344,592.0    5.0     0.00     43.50  264016115
DECLARE job BINARY_INTEGER := :job; next_date DATE := :mydate;
broken BOOLEAN := FALSE; BEGIN cen_pwsecr.set_user_inactive; :my
date := next_date; IF broken THEN :b := 1; ELSE :b := 0; END IF;
 END;

        328,239            1      328,239.0    4.7    42.64     42.96 1225405709
INSERT INTO CENTRA3.prefs_oneint (user_id, pref_id, int_value, i
user_id)            SELECT null, 136, 0, iuser_id from users
        WHERE iuser_id not in (SELECT iuser_id from prefs_oneint
 WHERE pref_id = 136)

         97,947            6       16,324.5    1.4    19.52   1134.34 3862257058
SELECT /*+ Q379001 INDEX_RRS(A1 "CPK_MEMBERS") */ A1."NODE_ID" C
0,A1."OBJECT_ID" C1 FROM "CENTRA3"."MEMBERS"  PX_GRANULE(0, BLOC
K_RANGE, DYNAMIC) A1

         65,300            4       16,325.0    0.9     8.22    374.71  488408906
SELECT /*+ Q377002 INDEX_RRS(A1 "CPK_MEMBERS") */ A1."OBJECT_ID"
 C0,A1."NODE_ID" C1 FROM "CENTRA3"."MEMBERS"  PX_GRANULE(0, BLOC
K_RANGE, DYNAMIC) A1

 SQL ordered by Gets for DB: REP9A  Instance: rep9a  Snaps: 1 -3
-> End Buffer Gets Threshold:   10000
-> Note that resources reported for PL/SQL includes the resources used by
   all SQL statements called within the PL/SQL code.  As individual SQL
   statements are also reported, it is possible and valid for the summed
   total % to exceed 100

                                                     CPU      Elapsd
  Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
         65,298            4       16,324.5    0.9    11.53    480.10 3503104803
SELECT /*+ Q378002 INDEX_RRS(A1 "CPK_MEMBERS") */ A1."OBJECT_ID"
 C0,A1."NODE_ID" C1 FROM "CENTRA3"."MEMBERS"  PX_GRANULE(0, BLOC
K_RANGE, DYNAMIC) A1

         39,669           21        1,889.0    0.6     4.14      5.15 2060749648
 SELECT LOWER(title) x, a.object_id y, a.system_id,  a.class_id,
 a.format, a.object_name, a.object_type, a.title,  TO_CHAR(a.cre
ate_date,'RRRRMMDDHH24MISS'), a.iuser_id,  a.node_of_origination
, a.status, a.abstract, a.create_by_role,  a.version, a.revision
, a.system_version, a.system_revision,  a.review_counter, a.revi

         27,968        3,995            7.0    0.4     1.00      1.47  436967754
SELECT sharename, sharevalue FROM censhare WHERE dbname = UPPER(
'centra3') AND sharename IN ('DBSTATUS','PASSWORD')

         15,594            1       15,594.0    0.2     0.14      0.16 2306896506
UPDATE CENTRA3.prefs_oneint set int_value = 1   where pref_id =
136   and iuser_id in (select iuser_id from users where sysdate
- last_login > 180)

         13,031          151           86.3    0.2     0.11      2.50  238087931
select t.schema, t.name, t.flags, q.name from system.aq$_queue_t
ables t, sys.aq$_queue_table_affinities aft,      system.aq$_que
ues q where aft.table_objno = t.objno and aft.owner_instance = :
1 and        q.table_objno = t.objno and q.usage = 0 and       b
itand(t.flags, 4+16+32+64+128+256) = 0 for update of t.name, aft

         12,800           20          640.0    0.2     0.11      0.14 3341946015
SELECT b.attribute_id,b.type, b.default_value, b.name,  b.descri
ption,  TO_CHAR(b.create_date,'RRRRMMDDHH24MISS'), b.create_by_r
ole,  b.iuser_id,  b.perform_immediate, b.perform_before,  b.per
form_after, b.perform_on_click, b.delivered, b.selectable,  c.ro
le_name, d.proper_name, e.seq_num seq  FROM attribute_map a, att

          4,250          604            7.0    0.1     0.02      0.26 3460529092
select t.name,  (select owner_instance from sys.aq$_queue_table_
affinities   where table_objno = t.objno)  from system.aq$_queue
_tables t where t.name = :1 and t.schema = :2 for update skip lo
cked

          1,632          811            2.0    0.0     0.02      0.13 2963598673
select job, nvl2(last_date, 1, 0) from sys.job$ where (((:1 <= n
ext_date) and (next_date < :2))    or  ((last_date is null) and
(next_date < :3))) and (field1 = :4 or (field1 = 0 and 'Y' = :5)

          -------------------------------------------------------------
 SQL ordered by Reads for DB: REP9A  Instance: rep9a  Snaps: 1 -3
-> End Disk Reads Threshold:    1000

                                                     CPU      Elapsd
 Physical Reads  Executions  Reads per Exec %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
      1,466,832           20       73,341.6   65.6   347.25   4559.19 1486125977
SELECT DISTINCT MAX(a.object_id) oid  FROM objects a  WHERE a.ob
ject_type IN ('D','C')  AND  LOWER  (a.title) LIKE LOWER ('%tire
%')  AND a.object_id IN  (SELECT MAX(oo.object_id)   FROM object
s oo   WHERE system_id=a.system_id) GROUP BY a.system_id   ORDER
 BY oid

        431,900           20       21,595.0   19.3   145.48   9561.46 2562928129
 SELECT /*+ INDEX_COMBINE(O) */ DISTINCT n.class_id, n.attribute
_id, n.role_id  FROM node_map nm, members m, objects o,  nvisibl
e_map n, report_columns e  WHERE nm.node_id LIKE 'ANAC'  AND nm.
link_node_id = m.node_id AND m.object_id = o.object_id  AND o.cl
ass_id = n.class_id  AND e.format_id = 1  AND e.attribute_id = n

         97,941            6       16,323.5    4.4    19.52   1134.34 3862257058
SELECT /*+ Q379001 INDEX_RRS(A1 "CPK_MEMBERS") */ A1."NODE_ID" C
0,A1."OBJECT_ID" C1 FROM "CENTRA3"."MEMBERS"  PX_GRANULE(0, BLOC
K_RANGE, DYNAMIC) A1

         66,540            6       11,090.0    3.0    33.97   1674.48 3830354677
SELECT /*+ ORDERED NO_EXPAND USE_HASH(A2) SWAP_JOIN_INPUTS(A2) *
/ A1.C7 C0,A1.C2 C1,A1.C8 C2 FROM :Q379000 A1,:Q379001 A2 WHERE
A2.C1=A1.C1 AND A1.C0=A2.C0

         65,294            4       16,323.5    2.9     8.22    374.71  488408906
SELECT /*+ Q377002 INDEX_RRS(A1 "CPK_MEMBERS") */ A1."OBJECT_ID"
 C0,A1."NODE_ID" C1 FROM "CENTRA3"."MEMBERS"  PX_GRANULE(0, BLOC
K_RANGE, DYNAMIC) A1

         65,294            4       16,323.5    2.9    11.53    480.10 3503104803
SELECT /*+ Q378002 INDEX_RRS(A1 "CPK_MEMBERS") */ A1."OBJECT_ID"
 C0,A1."NODE_ID" C1 FROM "CENTRA3"."MEMBERS"  PX_GRANULE(0, BLOC
K_RANGE, DYNAMIC) A1

         34,466           21        1,641.2    1.5   168.45   6270.42 3063408322
 SELECT /*+ INDEX_COMBINE(O) */ DISTINCT n.class_id, n.attribute
_id, n.role_id  FROM node_map nm, members m, objects o,  nvisibl
e_map n, report_columns e  WHERE nm.node_id LIKE '__'  AND nm.li
nk_node_id = m.node_id AND m.object_id = o.object_id  AND o.clas
s_id = n.class_id  AND e.format_id = 3  AND e.attribute_id = n.a

         25,307           21        1,205.1    1.1   123.19   9641.05 2498503827
 SELECT /*+ INDEX_COMBINE(O) */ DISTINCT n.class_id, n.attribute
_id, n.role_id  FROM node_map nm, members m, objects o,  nvisibl
e_map n, report_columns e  WHERE nm.node_id LIKE 'AN__'  AND nm.
link_node_id = m.node_id AND m.object_id = o.object_id  AND o.cl
ass_id = n.class_id  AND e.format_id = 3  AND e.attribute_id = n

            538            1          538.0    0.0     0.00     43.50  264016115
DECLARE job BINARY_INTEGER := :job; next_date DATE := :mydate;
broken BOOLEAN := FALSE; BEGIN cen_pwsecr.set_user_inactive; :my
date := next_date; IF broken THEN :b := 1; ELSE :b := 0; END IF;
 END;

 SQL ordered by Reads for DB: REP9A  Instance: rep9a  Snaps: 1 -3
-> End Disk Reads Threshold:    1000

                                                     CPU      Elapsd
 Physical Reads  Executions  Reads per Exec %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
            480            1          480.0    0.0    42.64     42.96 1225405709
INSERT INTO CENTRA3.prefs_oneint (user_id, pref_id, int_value, i
user_id)            SELECT null, 136, 0, iuser_id from users
        WHERE iuser_id not in (SELECT iuser_id from prefs_oneint
 WHERE pref_id = 136)

             82          151            0.5    0.0     0.11      2.50  238087931
select t.schema, t.name, t.flags, q.name from system.aq$_queue_t
ables t, sys.aq$_queue_table_affinities aft,      system.aq$_que
ues q where aft.table_objno = t.objno and aft.owner_instance = :
1 and        q.table_objno = t.objno and q.usage = 0 and       b
itand(t.flags, 4+16+32+64+128+256) = 0 for update of t.name, aft

             57        3,995            0.0    0.0     1.00      1.47  436967754
SELECT sharename, sharevalue FROM censhare WHERE dbname = UPPER(
'centra3') AND sharename IN ('DBSTATUS','PASSWORD')

             36           21            1.7    0.0     4.14      5.15 2060749648
 SELECT LOWER(title) x, a.object_id y, a.system_id,  a.class_id,
 a.format, a.object_name, a.object_type, a.title,  TO_CHAR(a.cre
ate_date,'RRRRMMDDHH24MISS'), a.iuser_id,  a.node_of_origination
, a.status, a.abstract, a.create_by_role,  a.version, a.revision
, a.system_version, a.system_revision,  a.review_counter, a.revi

             32          106            0.3    0.0     0.02      0.21  365454555
select cols,audit$,textlength,intcols,property,flags,rowid from
view$ where obj#=:1

             20          121            0.2    0.0     0.06      0.20 3844343967
select i.obj#,i.ts#,i.file#,i.block#,i.intcols,i.type#,i.flags,
i.property,i.pctfree$,i.initrans,i.maxtrans,i.blevel,i.leafcnt,i
.distkey, i.lblkkey,i.dblkkey,i.clufac,i.cols,i.analyzetime,i.sa
mplesize,i.dataobj#, nvl(i.degree,1),nvl(i.instances,1),i.rowcnt
,mod(i.pctthres$,256),i.indmethod#,i.trunccnt,nvl(c.unicols,0),n

             10          150            0.1    0.0     0.22      0.25 3617023014
SELECT VALUE FROM NLS_INSTANCE_PARAMETERS WHERE PARAMETER ='NLS_
DATE_FORMAT'

             10          125            0.1    0.0     0.02      0.07 3680293972
select ts#,file#,block#,nvl(bobj#,0),nvl(tab#,0),intcols,nvl(clu
cols,0),audit$,flags,pctfree$,pctused$,initrans,maxtrans,rowcnt,
blkcnt,empcnt,avgspc,chncnt,avgrln,analyzetime, samplesize,cols,

          -------------------------------------------------------------
 SQL ordered by Executions for DB: REP9A  Instance: rep9a  Snaps: 1 -3
-> End Executions Threshold:     100

                                                CPU per    Elap per
 Executions   Rows Processed   Rows per Exec    Exec (s)   Exec (s)  Hash Value
------------ --------------- ---------------- ----------- ---------- ----------
       3,995           7,984              2.0       0.00        0.00  436967754
SELECT sharename, sharevalue FROM censhare WHERE dbname = UPPER(
'centra3') AND sharename IN ('DBSTATUS','PASSWORD')

         811               5              0.0       0.00        0.00 2963598673
select job, nvl2(last_date, 1, 0) from sys.job$ where (((:1 <= n
ext_date) and (next_date < :2))    or  ((last_date is null) and
(next_date < :3))) and (field1 = :4 or (field1 = 0 and 'Y' = :5)
) and (this_date is null) order by next_date, job

         810             810              1.0       0.00        0.00 2964743345
select count(*) from sys.job$ where (next_date > sysdate) and (n
ext_date < (sysdate+5/86400))

         604             604              1.0       0.00        0.00 3460529092
select t.name,  (select owner_instance from sys.aq$_queue_table_
affinities   where table_objno = t.objno)  from system.aq$_queue
_tables t where t.name = :1 and t.schema = :2 for update skip lo
cked

         307             307              1.0       0.00        0.00  718191745
SELECT max_transaction_id.NEXTVAL FROM dual

         233             252              1.1       0.00        0.00 2085632044
select intcol#,nvl(pos#,0),col# from ccol$ where con#=:1

         205             327              1.6       0.00        0.00 1930240031
select pos#,intcol#,col#,spare1,bo#,spare2 from icol$ where obj#
=:1

         162             162              1.0       0.00        0.00 2614338980
SELECT type FROM attributes WHERE attribute_id = 7

         162           3,390             20.9       0.00        0.00 3013728279
select privilege#,level from sysauth$ connect by grantee#=prior
privilege# and privilege#>0 start with (grantee#=:1 or grantee#=
1) and privilege#>0

         157             137              0.9       0.00        0.00 4059714361
select type#,blocks,extents,minexts,maxexts,extsize,extpct,user#
,iniexts,NVL(lists,65535),NVL(groups,65535),cachehint,hwmincr, N
VL(spare1,0) from seg$ where ts#=:1 and file#=:2 and block#=:3

         151             604              4.0       0.00        0.02  238087931
select t.schema, t.name, t.flags, q.name from system.aq$_queue_t
ables t, sys.aq$_queue_table_affinities aft,      system.aq$_que
ues q where aft.table_objno = t.objno and aft.owner_instance = :
1 and        q.table_objno = t.objno and q.usage = 0 and       b
itand(t.flags, 4+16+32+64+128+256) = 0 for update of t.name, aft

         151               0              0.0       0.00        0.00 2780709284
select q_name, state, delay, expiration, rowid, msgid,   dequeue
_msgid, chain_no, local_order_no, enq_time, enq_tid, step_no,
priority, exception_qschema, exception_queue, retry_count, corri
 SQL ordered by Executions for DB: REP9A  Instance: rep9a  Snaps: 1 -3
-> End Executions Threshold:     100

                                                CPU per    Elap per
 Executions   Rows Processed   Rows per Exec    Exec (s)   Exec (s)  Hash Value
------------ --------------- ---------------- ----------- ---------- ----------
d,   time_manager_info, sender_name, sender_address, sender_prot
ocol   from SYS.AQ_SRVNTFN_TABLE   where time_manager_info <= :1

         151               0              0.0       0.00        0.00 2979542350
select q_name, state, delay, expiration, rowid, msgid,    dequeu
e_msgid, chain_no, local_order_no, enq_time, enq_tid, step_no,
 priority, exception_qschema, exception_queue, retry_count, corr
id,     time_manager_info   from SYS.AQ_EVENT_TABLE   where time
_manager_info <= :1 and state != :2   for update skip locked

         151             151              1.0       0.00        0.00 3048181047
select 1 from sys.aq$_queue_table_affinities

         151               0              0.0       0.00        0.00 3188984641
select q_name, state, delay, expiration, rowid, msgid,    dequeu
e_msgid, chain_no, local_order_no, enq_time, enq_tid, step_no,
 priority, exception_qschema, exception_queue, retry_count, corr
id,     time_manager_info   from SYSTEM.DEF$_AQCALL   where time
_manager_info <= :1 and state != :2   for update skip locked

         151               0              0.0       0.00        0.00 3223513021
select 1 from aq$_schedules

         151               0              0.0       0.00        0.00 3719012396
select q_name, state, delay, expiration, rowid, msgid,    dequeu
e_msgid, chain_no, local_order_no, enq_time, enq_tid, step_no,
 priority, exception_qschema, exception_queue, retry_count, corr
id,     time_manager_info   from SYSTEM.DEF$_AQERROR   where tim
e_manager_info <= :1 and state != :2   for update skip locked

         150               0              0.0       0.00        0.00 1567373371
ALTER SESSION SET NLS_LANGUAGE = 'AMERICAN'

         150               0              0.0       0.00        0.00 3073757550
ALTER SESSION SET NLS_TERRITORY = 'AMERICA'

         150             150              1.0       0.00        0.00 3617023014
SELECT VALUE FROM NLS_INSTANCE_PARAMETERS WHERE PARAMETER ='NLS_
DATE_FORMAT'

         143             143              1.0       0.00        0.00 2249281901
update seg$ set type#=:4,blocks=:5,extents=:6,minexts=:7,maxexts
=:8,extsize=:9,extpct=:10,user#=:11,iniexts=:12,lists=decode(:13
, 65535, NULL, :13),groups=decode(:14, 65535, NULL, :14), cacheh
int=:15, hwmincr=:16, spare1=DECODE(:17,0,NULL,:17) where ts#=:1
 and file#=:2 and block#=:3

         139             119              0.9       0.00        0.00  189272129
select o.owner#,o.name,o.namespace,o.remoteowner,o.linkname,o.su
bname,o.dataobj#,o.flags from obj$ o where o.obj#=:1

         125             125              1.0       0.00        0.00 3680293972
select ts#,file#,block#,nvl(bobj#,0),nvl(tab#,0),intcols,nvl(clu
cols,0),audit$,flags,pctfree$,pctused$,initrans,maxtrans,rowcnt,
 SQL ordered by Executions for DB: REP9A  Instance: rep9a  Snaps: 1 -3
-> End Executions Threshold:     100

                                                CPU per    Elap per
 Executions   Rows Processed   Rows per Exec    Exec (s)   Exec (s)  Hash Value
------------ --------------- ---------------- ----------- ---------- ----------
blkcnt,empcnt,avgspc,chncnt,avgrln,analyzetime, samplesize,cols,

          -------------------------------------------------------------
 SQL ordered by Parse Calls for DB: REP9A  Instance: rep9a  Snaps: 1 -3
-> End Parse Calls Threshold:      1000

                           % Total
 Parse Calls  Executions   Parses  Hash Value
------------ ------------ -------- -----

          Followup:

 Elapsed:               69.33 (mins)
is 3 to 5 times too long.  15-30 minutes max.

Transactions:                  0.19
is very low -- 1 transaction ever 5 seconds.  pretty low volume

Soft Parse %:   67.49
is about as bad as you get unless you are a data warehouse!  should be above 99
for most normal systems.

Rollback per transaction %:   80.30
ugh, why do all of the work and then rollback 8 times out of 10?  You know
rolling is typically harder then doing the work in the first place!

Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
direct path write                                 151,890      21,304    63.11
direct path read                                   45,338       3,761    11.14

one might believe your sort/hash area/pga_aggregate_target sizes are a tad
small.  lots of reads/write to/from temp?

low hanging fruit here:

  Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
      4,302,380           20      215,119.0   61.9   347.25   4559.19 1486125977
SELECT DISTINCT MAX(a.object_id) oid  FROM objects a  WHERE a.ob
ject_type IN ('D','C')  AND  LOWER  (a.title) LIKE LOWER ('%tire
%')  AND a.object_id IN  (SELECT MAX(oo.object_id)   FROM object
s oo   WHERE system_id=a.system_id) GROUP BY a.system_id   ORDER
 BY oid

a single table query that takes almost a 1/4 of a MILLION I/O's per execution!
that's a bummer.  I'd want to be taking a look at that.

Lots of things to fix here.  BIND variables will be job #1 I think.

      Rating: 5
Low soft parse
      %  July 17, 2003
Reviewer:  Arun Gupta
       from Harrisburg, PA USA
Tom
The soft parse %, as indicated by the statspack report, is consistently hovering
close to 90%. I have checked for literal sqls using the utility provided on your
site. There are no literal sqls. What else can contribute to a low soft parse%?
Please give me some pointers as I am really at a loss here.

Thanks

          Followup:

too small of a shared pool can have that effect -- you don't have enough room to
contain your working set of SQL and hence some are getting aged out and then
subsequently re-hard-parsed later.

      Rating: 5
Soft Parse %
      poor because the app is too well-written?? ;-)  July 17,
      2003
Reviewer:  Mark J. Bobak  from Ann Arbor, MI

Tom,

Great discussion.  I just re-read it from the beginning.

I was just thinking, not that it would ever happen, but
I suppose that if you have a perfectly written application,
one where every session that connects only parses once on
each SQL that it uses, and the connections are all sustained
over long periods of time, it's possible to have a soft
parse ratio that's lower than your stated ideal of 99+%.  ;-)

I know, I know, never happen in a million years, and even
if it did, the app would be running so well, noone would
have to run a StatsPack report in the first place, right?

Oh well, dreaming is free....;-)

          Followup:

yes, that is a situation I'd love to be in, the parse count would be very near
zero ;)

      Rating: 4
what to do when
      system hangs  July 18, 2003
Reviewer:
      Winston
I managed to take two snapshots when system hanged and I found the top SQLs are
not ours... they are recursive and it look like they're SYS's.

By saying 'HANG', I mean it took more than 1 minute to connect to Oracle via
SQL*PLUS.

Tom, is there anything we can do about this?

Thanks a lot.

               Snap Id     Snap Time      Sessions
                ------- ------------------ --------
 Begin Snap:                            23 17-Jul-03 01:30:14       39
   End Snap:                            24 17-Jul-03 01:30:51       39
    Elapsed:                               0.62 (mins)

Cache Sizes
~~~~~~~~~~~
           db_block_buffers:      60000          log_buffer:
    163840
              db_block_size:       8192    shared_pool_size:
 104857600

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:          1,714,160.86         63,423,952.00
              Logical reads:             25,383.32            939,183.00
              Block changes:             22,931.73            848,474.00
             Physical reads:                 19.68                728.00
            Physical writes:                259.41              9,598.00
                 User calls:                  0.92                 34.00
                     Parses:                  1.84                 68.00
                Hard parses:                  0.24                  9.00
                      Sorts:                  2.03                 75.00
                     Logons:                  0.11                  4.00
                   Executes:                  4.22                156.00
               Transactions:                  0.03

  % Blocks changed per Read:   90.34    Recursive Call %:               97.97
 Rollback per transaction %:    0.00       Rows per Sort:              180.41

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:              100.00
            Buffer  Hit   %:   99.92    In-memory Sort %:              100.00
            Library Hit   %:   72.36        Soft Parse %:               86.76
         Execute to Parse %:   56.41         Latch Hit %:               99.99
Parse CPU to Parse Elapsd %:   61.54     % Non-Parse CPU:               99.95

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   52.09   52.86
    % SQL with executions>1:   46.68   47.95
  % Memory for SQL w/exec>1:   49.33   52.51

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
db file parallel write                                 32        3,754   62.15
log file parallel write                             2,178        1,073   17.76
db file sequential read                               698          637   10.55
log buffer space                                      148          440    7.28
log file sequential read                            1,604           47     .78
          -------------------------------------------------------------
Wait Events for DB: WZHG  Instance: WZHG  Snaps: 23 -24
-> cs - centisecond -  100th of a second
-> ms - millisecond - 1000th of a second
-> ordered by wait time desc, waits desc (idle events last)

                                                                    Avg
                                                     Total Wait    wait  Waits
Event                               Waits   Timeouts  Time (cs)    (ms)   /txn
---------------------------- ------------ ---------- ----------- ------ ------
db file parallel write                 32          0       3,754   1173   32.0
log file parallel write             2,178          0       1,073      5 ######
db file sequential read               698          0         637      9  698.0
log buffer space                      148          0         440     30  148.0
log file sequential read            1,604          0          47      0 ######
control file parallel write            26          0          28     11   26.0
log file switch completion              2          0          21    105    2.0
log file single write                   4          0          16     40    4.0
latch free                              5          5           9     18    5.0
LGWR wait for redo copy               392          0           7      0  392.0
log file sync                           1          0           7     70    1.0
control file sequential read           81          0           1      0   81.0
file identify                           8          0           0      0    8.0
file open                               8          0           0      0    8.0
refresh controlfile command             6          0           0      0    6.0
SQL*Net message from client            21          0       8,839   4209   21.0
SQL*Net message to client              21          0           0      0   21.0
          -------------------------------------------------------------
Background Wait Events for DB: WZHG  Instance: WZHG  Snaps: 23 -24
-> ordered by wait time desc, waits desc (idle events last)

                                                                    Avg
                                                     Total Wait    wait  Waits
Event                               Waits   Timeouts  Time (cs)    (ms)   /txn
---------------------------- ------------ ---------- ----------- ------ ------
db file parallel write                 32          0       3,754   1173   32.0
log file parallel write             2,176          0       1,061      5 ######
log file sequential read            1,604          0          47      0 ######
control file parallel write            26          0          28     11   26.0
log file single write                   4          0          16     40    4.0
LGWR wait for redo copy               392          0           7      0  392.0
control file sequential read           62          0           1      0   62.0
file identify                           8          0           0      0    8.0
file open                               7          0           0      0    7.0
rdbms ipc message                   5,172         16       9,243     18 ######
pmon timer                             11         11       3,388   3080   11.0
          -------------------------------------------------------------
SQL ordered by Gets for DB: WZHG  Instance: WZHG  Snaps: 23 -24
-> End Buffer Gets Threshold:   10000
-> Note that resources reported for PL/SQL includes the resources used by
   all SQL statements called within the PL/SQL code.  As individual SQL
   statements are also reported, it is possible and valid for the summed
   total % to exceed 100

  Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
             29           10            2.9     0.0    955191413
select obj#,type#,ctime,mtime,stime,status,dataobj#,flags,oid$,
spare1 from obj$ where owner#=:1 and name=:2 and namespace=:3 an
d(remoteowner=:4 or remoteowner is null and :4 is null)and(linkn
ame=:5 or linkname is null and :5 is null)and(subname=:6 or subn
ame is null and :6 is null)

             24            3            8.0     0.0   1737259834
select owner#,name,namespace,remoteowner,linkname,p_timestamp,p_
obj#, d_owner#, nvl(property,0) from dependency$,obj$ where d_ob
j#=:1 and p_obj#=obj#(+) order by order#

             12            3            4.0     0.0   1004464078
select grantee#,privilege#,nvl(col#,0),max(nvl(option$,0)) from
objauth$ where obj#=:1 group by grantee#,privilege#,nvl(col#,0)
order by grantee#

             12            3            4.0     0.0   4049165760
select order#,columns,types from access$ where d_obj#=:1

              9            3            3.0     0.0    365454555
select cols,audit$,textlength,intcols,property,flags,rowid from
view$ where obj#=:1

              9            3            3.0     0.0    395844583
select name,intcol#,segcol#,type#,length,nvl(precision#,0),decod
e(type#,2,nvl(scale,-127/*MAXSB1MINAL*/),178,scale,179,scale,180
,scale,181,scale,182,scale,183,scale,231,scale,0),null$,fixedsto
rage,nvl(deflength,0),default$,rowid,col#,property, charsetid,ch
arsetform,spare1,spare2 from col$ where obj#=:1 order by intcol#

              6            3            2.0     0.0   1966425544
select text from view$ where rowid=:1

              6            3            2.0     0.0   4261939565
select col#, grantee#, privilege#,max(nvl(option$,0)) from objau
th$ where obj#=:1 and col# is not null group by privilege#, col#
, grantee# order by col#, grantee#

              3            1            3.0     0.0    189272129
select o.owner#,o.name,o.namespace,o.remoteowner,o.linkname,o.su
bname,o.dataobj#,o.flags from obj$ o where o.obj#=:1

              3            3            1.0     0.0    313510536
select job from sys.job$  where next_date < sysdate  and (field1
 = :1 or (field1 = 0 and 'Y' = :2)) order by next_date, job

              3            3            1.0     0.0   1536916657
select con#,type#,condlength,intcols,robj#,rcon#,match#,refact,n
vl(enabled,0),rowid,cols,nvl(defer,0),mtime,nvl(spare1,0) from c
def$ where obj#=:1
SQL ordered by Gets for DB: WZHG  Instance: WZHG  Snaps: 23 -24
-> End Buffer Gets Threshold:   10000
-> Note that resources reported for PL/SQL includes the resources used by
   all SQL statements called within the PL/SQL code.  As individual SQL
   statements are also reported, it is possible and valid for the summed
   total % to exceed 100

  Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------

              3            1            3.0     0.0   1705880752
select file# from file$ where ts#=:1

              0            1            0.0     0.0   1697159799
BEGIN DBMS_OUTPUT.GET_LINES(:LINES, :NUMLINES); END;

              0            8            0.0     0.0   1754398600
select act from x$ksusex where sid=:1 and serial=:2

          -------------------------------------------------------------
SQL ordered by Reads for DB: WZHG  Instance: WZHG  Snaps: 23 -24
-> End Disk Reads Threshold:    1000

 Physical Reads  Executions  Reads per Exec % Total  Hash Value
--------------- ------------ -------------- ------- ------------
              1            3            0.3     0.1    395844583
select name,intcol#,segcol#,type#,length,nvl(precision#,0),decod
e(type#,2,nvl(scale,-127/*MAXSB1MINAL*/),178,scale,179,scale,180
,scale,181,scale,182,scale,183,scale,231,scale,0),null$,fixedsto
rage,nvl(deflength,0),default$,rowid,col#,property, charsetid,ch
arsetform,spare1,spare2 from col$ where obj#=:1 order by intcol#

              0            1            0.0     0.0    189272129
select o.owner#,o.name,o.namespace,o.remoteowner,o.linkname,o.su
bname,o.dataobj#,o.flags from obj$ o where o.obj#=:1

              0            3            0.0     0.0    313510536
select job from sys.job$  where next_date < sysdate  and (field1
 = :1 or (field1 = 0 and 'Y' = :2)) order by next_date, job

              0            3            0.0     0.0    365454555
select cols,audit$,textlength,intcols,property,flags,rowid from
view$ where obj#=:1

              0           10            0.0     0.0    955191413
select obj#,type#,ctime,mtime,stime,status,dataobj#,flags,oid$,
spare1 from obj$ where owner#=:1 and name=:2 and namespace=:3 an
d(remoteowner=:4 or remoteowner is null and :4 is null)and(linkn
ame=:5 or linkname is null and :5 is null)and(subname=:6 or subn
ame is null and :6 is null)

              0            3            0.0     0.0   1004464078
select grantee#,privilege#,nvl(col#,0),max(nvl(option$,0)) from
objauth$ where obj#=:1 group by grantee#,privilege#,nvl(col#,0)
order by grantee#

              0            3            0.0     0.0   1536916657
select con#,type#,condlength,intcols,robj#,rcon#,match#,refact,n
vl(enabled,0),rowid,cols,nvl(defer,0),mtime,nvl(spare1,0) from c
def$ where obj#=:1

              0            1            0.0     0.0   1697159799
BEGIN DBMS_OUTPUT.GET_LINES(:LINES, :NUMLINES); END;

              0            1            0.0     0.0   1705880752
select file# from file$ where ts#=:1

              0            3            0.0     0.0   1737259834
select owner#,name,namespace,remoteowner,linkname,p_timestamp,p_
obj#, d_owner#, nvl(property,0) from dependency$,obj$ where d_ob
j#=:1 and p_obj#=obj#(+) order by order#

              0            8            0.0     0.0   1754398600
select act from x$ksusex where sid=:1 and serial=:2

              0            3            0.0     0.0   1966425544
select text from view$ where rowid=:1

              0            3            0.0     0.0   4049165760
select order#,columns,types from access$ where d_obj#=:1

SQL ordered by Reads for DB: WZHG  Instance: WZHG  Snaps: 23 -24
-> End Disk Reads Threshold:    1000

 Physical Reads  Executions  Reads per Exec % Total  Hash Value
--------------- ------------ -------------- ------- ------------

              0            3            0.0     0.0   4261939565
select col#, grantee#, privilege#,max(nvl(option$,0)) from objau
th$ where obj#=:1 and col# is not null group by privilege#, col#
, grantee# order by col#, grantee#

SQL ordered by Executions for DB: WZHG  Instance: WZHG  Snaps: 23 -24
-> End Executions Threshold:     100

 Executions   Rows Processed    Rows per Exec   Hash Value
------------ ---------------- ---------------- ------------
          10                9              0.9    955191413
select obj#,type#,ctime,mtime,stime,status,dataobj#,flags,oid$,
spare1 from obj$ where owner#=:1 and name=:2 and namespace=:3 an
d(remoteowner=:4 or remoteowner is null and :4 is null)and(linkn
ame=:5 or linkname is null and :5 is null)and(subname=:6 or subn
ame is null and :6 is null)

           8                8              1.0   1754398600
select act from x$ksusex where sid=:1 and serial=:2

           3                0              0.0    313510536
select job from sys.job$  where next_date < sysdate  and (field1
 = :1 or (field1 = 0 and 'Y' = :2)) order by next_date, job

           3                3              1.0    365454555
select cols,audit$,textlength,intcols,property,flags,rowid from
view$ where obj#=:1

           3               52             17.3    395844583
select name,intcol#,segcol#,type#,length,nvl(precision#,0),decod
e(type#,2,nvl(scale,-127/*MAXSB1MINAL*/),178,scale,179,scale,180
,scale,181,scale,182,scale,183,scale,231,scale,0),null$,fixedsto
rage,nvl(deflength,0),default$,rowid,col#,property, charsetid,ch
arsetform,spare1,spare2 from col$ where obj#=:1 order by intcol#

           3                8              2.7   1004464078
select grantee#,privilege#,nvl(col#,0),max(nvl(option$,0)) from
objauth$ where obj#=:1 group by grantee#,privilege#,nvl(col#,0)
order by grantee#

           3                0              0.0   1536916657
select con#,type#,condlength,intcols,robj#,rcon#,match#,refact,n
vl(enabled,0),rowid,cols,nvl(defer,0),mtime,nvl(spare1,0) from c
def$ where obj#=:1

           3                6              2.0   1737259834
select owner#,name,namespace,remoteowner,linkname,p_timestamp,p_
obj#, d_owner#, nvl(property,0) from dependency$,obj$ where d_ob
j#=:1 and p_obj#=obj#(+) order by order#

           3                3              1.0   1966425544
select text from view$ where rowid=:1

           3                3              1.0   4049165760
select order#,columns,types from access$ where d_obj#=:1

           3                0              0.0   4261939565
select col#, grantee#, privilege#,max(nvl(option$,0)) from objau
th$ where obj#=:1 and col# is not null group by privilege#, col#
, grantee# order by col#, grantee#

           1                1              1.0    189272129
select o.owner#,o.name,o.namespace,o.remoteowner,o.linkname,o.su
bname,o.dataobj#,o.flags from obj$ o where o.obj#=:1

SQL ordered by Executions for DB: WZHG  Instance: WZHG  Snaps: 23 -24
-> End Executions Threshold:     100

 Executions   Rows Processed    Rows per Exec   Hash Value
------------ ---------------- ---------------- ------------
           1                1              1.0   1697159799
BEGIN DBMS_OUTPUT.GET_LINES(:LINES, :NUMLINES); END;

           1                1              1.0   1705880752
select file# from file$ where ts#=:1

Instance Activity Stats for DB: WZHG  Instance: WZHG  Snaps: 23 -24

Statistic                                    Total   per Second    per Trans
--------------------------------- ---------------- ------------ ------------
CPU used by this session                       148          4.0        148.0
CPU used when call started                     148          4.0        148.0
CR blocks created                                3          0.1          3.0
Cached Commit SCN referenced                     0          0.0          0.0
DBWR buffers scanned                             0          0.0          0.0
DBWR checkpoint buffers written              9,598        259.4      9,598.0
DBWR checkpoints                                 1          0.0          1.0
DBWR free buffers found                          0          0.0          0.0
DBWR lru scans                                   0          0.0          0.0
DBWR make free requests                          0          0.0          0.0
DBWR revisited being-written buff                0          0.0          0.0
DBWR summed scan depth                           0          0.0          0.0
DBWR transaction table writes                    3          0.1          3.0
DBWR undo block writes                       2,585         69.9      2,585.0
SQL*Net roundtrips to/from client               21          0.6         21.0
SQL*Net roundtrips to/from dblink                0          0.0          0.0
background checkpoints completed                 0          0.0          0.0
background checkpoints started                   1          0.0          1.0
background timeouts                             29          0.8         29.0
branch node splits                               0          0.0          0.0
buffer is not pinned count                   1,118         30.2      1,118.0
buffer is pinned count                       3,592         97.1      3,592.0
bytes received via SQL*Net from c            2,712         73.3      2,712.0
bytes received via SQL*Net from d                0          0.0          0.0
bytes sent via SQL*Net to client             3,792        102.5      3,792.0
bytes sent via SQL*Net to dblink                 0          0.0          0.0
calls to get snapshot scn: kcmgss              197          5.3        197.0
calls to kcmgas                                 24          0.7         24.0
calls to kcmgcs                                 45          1.2         45.0
change write time                            1,538         41.6      1,538.0
cleanouts and rollbacks - consist                0          0.0          0.0
cleanouts only - consistent read                 0          0.0          0.0
cluster key scan block gets                     12          0.3         12.0
cluster key scans                               11          0.3         11.0
commit cleanout failures: block l                0          0.0          0.0
commit cleanout failures: callbac                1          0.0          1.0
commit cleanouts                               134          3.6        134.0
commit cleanouts successfully com              133          3.6        133.0
consistent changes                               3          0.1          3.0
consistent gets                              8,912        240.9      8,912.0
cursor authentications                           0          0.0          0.0
data blocks consistent reads - un                3          0.1          3.0
db block changes                           848,474     22,931.7    848,474.0
db block gets                              930,261     25,142.2    930,261.0
deferred (CURRENT) block cleanout               56          1.5         56.0
dirty buffers inspected                          0          0.0          0.0
enqueue conversions                              0          0.0          0.0
enqueue releases                                88          2.4         88.0
enqueue requests                                88          2.4         88.0
enqueue timeouts                                 0          0.0          0.0
enqueue waits                                    0          0.0          0.0
execute count                                  156          4.2        156.0
free buffer inspected                            0          0.0          0.0
free buffer requested                        4,270        115.4      4,270.0
hot buffers moved to head of LRU                94          2.5         94.0
immediate (CR) block cleanout app                0          0.0          0.0
Instance Activity Stats for DB: WZHG  Instance: WZHG  Snaps: 23 -24

Statistic                                    Total   per Second    per Trans
--------------------------------- ---------------- ------------ ------------
immediate (CURRENT) block cleanou               49          1.3         49.0
index fast full scans (full)                     0          0.0          0.0
leaf node splits                                21          0.6         21.0
logons cumulative                                4          0.1          4.0
logons current
messages received                            2,239         60.5      2,239.0
messages sent                                2,239         60.5      2,239.0
no buffer to keep pinned count                   0          0.0          0.0
no work - consistent read gets                 622         16.8        622.0
opened cursors cumulative                       62          1.7         62.0
parse count (hard)                               9          0.2          9.0
parse count (total)                             68          1.8         68.0
parse time cpu                                   8          0.2          8.0
parse time elapsed                              13          0.4         13.0
physical reads                                 728         19.7        728.0
physical reads direct                            0          0.0          0.0
physical reads direct (lob)                      0          0.0          0.0
physical writes                              9,598        259.4      9,598.0
physical writes direct                           0          0.0          0.0
physical writes non checkpoint               4,291        116.0      4,291.0
pinned buffers inspected                         0          0.0          0.0
prefetched blocks                                0          0.0          0.0
prefetched blocks aged out before                0          0.0          0.0
process last non-idle time                       0          0.0          0.0
recovery array read time                         0          0.0          0.0
recovery array reads                             0          0.0          0.0
recovery blocks read                             0          0.0          0.0
recursive calls                              1,638         44.3      1,638.0
recursive cpu usage                            108          2.9        108.0
redo blocks written                        129,237      3,492.9    129,237.0
redo buffer allocation retries                 145          3.9        145.0
redo entries                               424,253     11,466.3    424,253.0
redo log space requests                          2          0.1          2.0
redo log space wait time                        21          0.6         21.0
redo size                               63,423,952  1,714,160.9 ############
redo synch time                                  7          0.2          7.0
redo synch writes                                1          0.0          1.0
redo wastage                               636,344     17,198.5    636,344.0
redo write time                              2,702         73.0      2,702.0
redo writer latching time                        7          0.2          7.0
redo writes                                  2,162         58.4      2,162.0
rollback changes - undo records a          423,689     11,451.1    423,689.0
rollbacks only - consistent read                 3          0.1          3.0
rows fetched via callback                      402         10.9        402.0
session connect time                             0          0.0          0.0
session cursor cache hits                       33          0.9         33.0
session logical reads                      939,183     25,383.3    939,183.0
session pga memory                       2,247,880     60,753.5  2,247,880.0
session pga memory max                   2,242,992     60,621.4  2,242,992.0
session uga memory                          52,592      1,421.4     52,592.0
session uga memory max                     194,840      5,266.0    194,840.0
sorts (disk)                                     0          0.0          0.0
sorts (memory)                                  75          2.0         75.0
sorts (rows)                                13,531        365.7     13,531.0
summed dirty queue length                        0          0.0          0.0
switch current to new buffer
Instance Activity Stats for DB: WZHG  Instance: WZHG  Snaps: 23 -24

Statistic                                    Total   per Second    per Trans
--------------------------------- ---------------- ------------ ------------
table fetch by rowid                           487         13.2        487.0
table fetch continued row                        0          0.0          0.0
table scan blocks gotten                         1          0.0          1.0
table scan rows gotten                           1          0.0          1.0
table scans (long tables)                        0          0.0          0.0
table scans (rowid ranges)                       0          0.0          0.0
table scans (short tables)                       1          0.0          1.0
total file opens                                 8          0.2          8.0
transaction rollbacks                            0          0.0          0.0
user calls                                      34          0.9         34.0
user commits                                     1          0.0          1.0
user rollbacks                                   0          0.0          0.0
write clones created in backgroun                0          0.0          0.0
write clones created in foregroun            3,445         93.1      3,445.0
Tablespace IO Stats for DB: WZHG  Instance: WZHG  Snaps: 23 -24
->ordered by IOs (Reads + Writes) desc

Tablespace
------------------------------
                 Av      Av     Av                    Av        Buffer Av Buf
         Reads Reads/s Rd(ms) Blks/Rd       Writes Writes/s      Waits Wt(ms)
-------------- ------- ------ ------- ------------ -------- ---------- ------
SWSPIX_01
            11       0   50.9     1.0        6,085      164          0    0.0
RBS
           351       9    5.9     1.0        2,860       77          0    0.0
SWSPDA_01
           207       6   12.7     1.0        1,180       32          0    0.0
TOOLS
           138       4   11.2     1.0            9        0          0    0.0
SYSTEM
             8       0    2.5     1.0            5        0          0    0.0

File IO Stats for DB: WZHG  Instance: WZHG  Snaps: 23 -24
->ordered by Tablespace, File

Tablespace               Filename
------------------------ ----------------------------------------------------
                 Av      Av     Av                    Av        Buffer Av Buf
         Reads Reads/s Rd(ms) Blks/Rd       Writes Writes/s      Waits Wt(ms)
-------------- ------- ------ ------- ------------ -------- ---------- ------
RBS                      /orafs1/u104/ORACLE/WZHG/rbs01.dbf
           351       9    5.9     1.0        2,860       77          0

SWSPDA_01                /orafs1/u104/ORACLE/WZHG/swspda_01_01.dbf
           116       3    3.1     1.0          545       15          0
                         /orafs1/u104/ORACLE/WZHG/swspda_01_02.dbf
            91       2   24.8     1.0          635       17          0

SWSPIX_01                /orafs1/u104/ORACLE/WZHG/swspix_01_01.dbf
            11       0   50.9     1.0        6,085      164          0

SYSTEM                   /orafs1/u104/ORACLE/WZHG/system01.dbf
             8       0    2.5     1.0            5        0          0

TOOLS                    /orafs1/u102/ORACLE/WZHG/tools01.dbf
           138       4   11.2     1.0            9        0          0
Buffer Pool Statistics for DB: WZHG  Instance: WZHG  Snaps: 23 -24
-> Pools   D: default pool,  K: keep pool,  R: recycle pool

                                                      Free    Write     Buffer
       Buffer    Consistent    Physical   Physical  Buffer Complete       Busy
P        Gets          Gets       Reads     Writes   Waits    Waits      Waits
- ----------- ------------- ----------- ---------- ------- -------- ----------
D       4,222         1,063         712      6,496       0        0          0
Rollback Segment Stats for DB: WZHG  Instance: WZHG  Snaps: 23 -24
->A high value for "Pct Waits" suggests more rollback segments may be required

       Trans Table      Pct   Undo Bytes
RBS No     Gets       Waits     Written        Wraps  Shrinks  Extends
------ ------------ ------- --------------- -------- -------- --------
     0          1.0    0.00               0        0        0        0
     2          2.0    0.00           2,116        0        0        0
     3          1.0    0.00               0        0        0        0
     4          1.0    0.00               0        0        0        0
     5          1.0    0.00               0        0        0        0
     6          1.0    0.00               0        0        0        0
     7        106.0    0.00         277,532        1        0        0
 **** MESSAGE TRUNCATED ****

          Followup:

your snap was a tad too short.  those statements in that report did no real work
-- look at them, they are trivial.

but you are generating 2meg of log per second, look at the currently EXECUTING
sql, you have a big insert/update/delete going on there.

      Rating: 5
My system is
      running very slow.  August 18, 2003
Reviewer:
      Rajendra  from India
I am using IBM-AIX (4 CPU 14 GB RAM) machine. Oracle Database version 8.1.7.4. I
am getting complaints from user that system is running very slow. Following is
the statspack report when system was running at it's worst. Any suggestions from
you? We are using third party application.

Thanks,

Rajendra

STATSPACK report for

DB Name         DB Id    Instance     Inst Num Release     OPS Host
------------ ----------- ------------ -------- ----------- --- ------------
COSMOS        2613389798 cosmos              1 8.1.7.4.0   NO  cosdb

                Snap Id     Snap Time      Sessions
                ------- ------------------ --------
 Begin Snap:         29 16-Aug-03 12:56:02      321
   End Snap:         30 16-Aug-03 13:11:26      321
    Elapsed:                  15.40 (mins)

Cache Sizes
~~~~~~~~~~~
           db_block_buffers:     280000          log_buffer:    1048576
              db_block_size:       8192    shared_pool_size:  283886080

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:             43,052.77              2,748.62
              Logical reads:             33,632.64              2,147.21
              Block changes:                165.85                 10.59
             Physical reads:              3,813.79                243.48
            Physical writes:                 21.87                  1.40
                 User calls:                505.47                 32.27
                     Parses:                 59.55                  3.80
                Hard parses:                  0.57                  0.04
                      Sorts:                 14.62                  0.93
                     Logons:                  1.63                  0.10
                   Executes:                318.16                 20.31
               Transactions:                 15.66

  % Blocks changed per Read:    0.49    Recursive Call %:   15.32
 Rollback per transaction %:   28.67       Rows per Sort:   82.43

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:   99.40       Redo NoWait %:  100.00
            Buffer  Hit   %:   88.66    In-memory Sort %:   99.93
            Library Hit   %:   99.69        Soft Parse %:   99.04
         Execute to Parse %:   81.28         Latch Hit %:   99.77
Parse CPU to Parse Elapsd %:   55.62     % Non-Parse CPU:   99.86

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   94.71   95.23
    % SQL with executions>1:   34.19   34.03
  % Memory for SQL w/exec>1:   31.64   31.52

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
buffer busy waits                                 185,413    4,341,557   81.86
db file scattered read                            557,356      477,342    9.00
latch free                                        100,489      254,082    4.79
db file sequential read                         2,230,272      213,807    4.03
log file sync                                      11,494       10,687     .20
          -------------------------------------------------------------
 Wait Events for DB: COSMOS  Instance: cosmos  Snaps: 29 -30
-> cs - centisecond -  100th of a second
-> ms - millisecond - 1000th of a second
-> ordered by wait time desc, waits desc (idle events last)

                                                                    Avg
                                                     Total Wait    wait  Waits
Event                               Waits   Timeouts  Time (cs)    (ms)   /txn
---------------------------- ------------ ---------- ----------- ------ ------
buffer busy waits                 185,413     38,144   4,341,557    234   12.8
db file scattered read            557,356          0     477,342      9   38.5
latch free                        100,489     99,530     254,082     25    6.9
db file sequential read         2,230,272          0     213,807      1  154.1
log file sync                      11,494          2      10,687      9    0.8
log file parallel write            11,382          0       2,593      2    0.8
file open                           2,824          0       1,667      6    0.2
db file parallel write                365          0       1,381     38    0.0
control file parallel write           320          0         244      8    0.0
direct path read                    1,116          0         184      2    0.1
log file sequential read              644          0          27      0    0.0
direct path write                      83          0           7      1    0.0
LGWR wait for redo copy                30          1           7      2    0.0
SQL*Net break/reset to clien           79          0           6      1    0.0
log file switch completion              1          0           6     60    0.0
SQL*Net more data to client            88          0           3      0    0.0
row cache lock                          4          0           2      5    0.0
refresh controlfile command             6          0           1      2    0.0
log file single write                   4          0           1      3    0.0
control file sequential read           98          0           0      0    0.0
file identify                           8          0           0      0    0.0
SQL*Net message from client       468,754          0  18,623,949    397   32.4
SQL*Net more data from clien       22,828          0       1,214      1    1.6
SQL*Net message to client         468,757          0         151      0   32.4
          -------------------------------------------------------------
 Background Wait Events for DB: COSMOS  Instance: cosmos  Snaps: 29 -30
-> ordered by wait time desc, waits desc (idle events last)

                                                                    Avg
                                                     Total Wait    wait  Waits
Event                               Waits   Timeouts  Time (cs)    (ms)   /txn
---------------------------- ------------ ---------- ----------- ------ ------
log file parallel write            11,380          0       2,593      2    0.8
db file parallel write                364          0       1,380     38    0.0
control file parallel write           320          0         244      8    0.0
latch free                             46         46          71     15    0.0
log file sequential read              644          0          27      0    0.0
direct path read                       73          0          17      2    0.0
LGWR wait for redo copy                30          1           7      2    0.0
db file scattered read                 13          0           4      3    0.0
file open                             245          0           1      0    0.0
db file sequential read                 8          0           1      1    0.0
log file single write                   4          0           1      3    0.0
control file sequential read           69          0           0      0    0.0
direct path write                      53          0           0      0    0.0
file identify                           8          0           0      0    0.0
rdbms ipc message                  22,382        848     357,741    160    1.5
smon timer                              3          3      92,150 ######    0.0
pmon timer                            433        278      92,108   2127    0.0
          -------------------------------------------------------------
 SQL ordered by Gets for DB: COSMOS  Instance: cosmos  Snaps: 29 -30
-> End Buffer Gets Threshold:   10000
-> Note that resources reported for PL/SQL includes the resources used by
   all SQL statements called within the PL/SQL code.  As individual SQL
   statements are also reported, it is possible and valid for the summed
   total % to exceed 100

  Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
        981,097           38       25,818.3     3.2   3487886524
SELECT tba_cust_mast_gen_tbl.cust_id,tba_cust_mast_gen_tbl.cust_
name,tba_cust_mast_gen_tbl.cust_type_code,tba_cust_mast_gen_tbl.
del_flg FROM TBA_CUST_MAST_GEN_TBL , FPC where TBA_CUST_MAST_GEN
_TBL.entity_cre_flg =  :1  and (  :2  is NULL or exp_imp_ind= :3
 ) and FPC.party_code(+) = TBA_CUST_MAST_GEN_TBL.cust_id AND   :

        785,755            1      785,755.0     2.5   3244435841
SELECT HOC.PARTY_ACID, HOC.PART_TRAN_TYPE, BAT.NFT_TYPE, BAT.NFT
_NUM FROM BAT, HOC WHERE  HOC.NFT_TYPE || NULL = BAT.NFT_TYPE AN
D HOC.NFT_NUM || NULL = BAT.NFT_NUM AND HOC.HO_TRAN_TYPE = :1  A
ND BAT.BAR_OR_ADVC_TYPE = :2  AND HOC.ENTITY_CRE_FLG = :3  AND B
AT.ENTITY_CRE_FLG = :4  AND HOC.DEL_FLG = :5  AND BAT.DEL_FLG =

        630,331          147        4,288.0     2.0   2344222695
SELECT COUNT(*) FROM TBA_PRINT_QUEUE_TBL WHERE USER_ID =  :1

        379,206            8       47,400.8     1.2   3294558041
select schm_type from gam where ltrim(foracid) = '012350003'

        357,131            3      119,043.7     1.1    946332232
 SELECT distinct(sih.si_srl_num)  ,sih.sol_id  ,sih.si_freq_type
 ,TO_CHAR(sih.si_start_date , 'DD-MM-YYYY HH24:MI:SS') ,TO_CHAR(
sih.si_end_date, 'DD-MM-YYYY HH24:MI:SS') ,TO_CHAR(sih.si_susp_u
pto_date, 'DD-MM-YYYY HH24:MI:SS') ,sih.carry_for_alwd_flg FROM
SIH , PYRD, PYRH , SST , ADM WHERE   sst.set_id =  :1  and sih.s

        331,625            7       47,375.0     1.1   2905796127
select schm_type from gam where ltrim(foracid) = '011370024'

        284,108            6       47,351.3     0.9    698997535
select schm_type from gam where ltrim(foracid) = '02420430199001
2'

        284,072            6       47,345.3     0.9   3219630629
select schm_type from gam where ltrim(foracid) = '003870002'

        274,925            1      274,925.0     0.9   3376831664
BEGIN statspack.snap; END;

        261,506            1      261,506.0     0.8    625421128
INSERT INTO STATS$SQLTEXT ( HASH_VALUE,TEXT_SUBSET,PIECE,SQL_TEX
T,ADDRESS,COMMAND_TYPE,LAST_SNAP_ID )  SELECT ST1.HASH_VALUE,SS.
TEXT_SUBSET,ST1.PIECE,ST1.SQL_TEXT,ST1.ADDRESS,ST1.COMMAND_TYPE,
SS.SNAP_ID   FROM V$SQLTEXT ST1,STATS$SQL_SUMMARY SS  WHERE SS.S
NAP_ID = :b1  AND SS.DBID = :b2  AND SS.INSTANCE_NUMBER = :b3  A

        237,175            5       47,435.0     0.8   1216533714
select schm_type from gam where ltrim(foracid) = '02120430122003
3'

 SQL ordered by Gets for DB: COSMOS  Instance: cosmos  Snaps: 29 -30
-> End Buffer Gets Threshold:   10000
-> Note that resources reported for PL/SQL includes the resources used by
   all SQL statements called within the PL/SQL code.  As individual SQL
   statements are also reported, it is possible and valid for the summed
   total % to exceed 100

  Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
        236,839            5       47,367.8     0.8    523242490
select schm_type from gam where ltrim(foracid) = '02910010426'

        236,676            5       47,335.2     0.8   1455919800
select schm_type from gam where ltrim(foracid) = 'ZZ56113'

        236,661            5       47,332.2     0.8   3860892109
select schm_type from gam where ltrim(foracid) = '00910511822000
2'

        189,446            4       47,361.5     0.6   2990940268
select schm_type from gam where ltrim(foracid) = '00320420100525
8'

        189,343            4       47,335.8     0.6    917911480
select schm_type from gam where ltrim(foracid) = '034900004'

        169,152            1      169,152.0     0.5   2229231736
declare  glshcode ctd.gl_sub_head_code%type; glshdesc gsh.gl_sub
_head_desc%type; tranamt  ctd.tran_amt%type; trantype ctd.tran_t
ype%type; parttrantype ctd.part_tran_type%type; soldesc  sol.sol
_desc%type; ctr  number(5); dbdate  gct.db_stat_date%type; delfl
g  gct.del_flg%type; cashdr  gst.tot_cash_dr_amt%type; cashcr  g

        163,643            1      163,643.0     0.5    559670275
SELECT CTD.GL_SUB_HEAD_CODE,CTD.TRAN_TYPE,CTD.PART_TRAN_TYPE,SUM
(TRAN_AMT),COUNT(TRAN_AMT)   FROM CTD,CTH  WHERE CTH.INIT_SOL_ID
 = UPPER('008')  AND GL_SUB_HEAD_CODE != '51010'  AND CTD.TRAN_D
ATE = :b1  AND CTD.TRAN_DATE = CTH.TRAN_DATE  AND CTD.TRAN_ID =
CTH.TRAN_ID  AND CTD.DEL_FLG = 'N'  AND CTD.PSTD_FLG = 'Y'  GROU

        142,365            3       47,455.0     0.5   3134530522
select schm_type from gam where ltrim(foracid) = '039350004'

        142,342            3       47,447.3     0.5   1594883170
select schm_type from gam where ltrim(foracid) = '034870002'

        142,288            3       47,429.3     0.5    858788750
select schm_type from gam where ltrim(foracid) = '01220420192256
1'

        142,267            3       47,422.3     0.5   3062676710
select schm_type from gam where ltrim(foracid) = '037450005'

        142,241            3       47,413.7     0.5   3202625664
select schm_type from gam where ltrim(foracid) = '009870002'

        142,239            3       47,413.0     0.5   4000704793
select schm_type from gam where ltrim(foracid) = '042870002'

        142,174            3       47,391.3     0.5   2287059575
 SQL ordered by Gets for DB: COSMOS  Instance: cosmos  Snaps: 29 -30
-> End Buffer Gets Threshold:   10000
-> Note that resources reported for PL/SQL includes the resources used by
   all SQL statements called within the PL/SQL code.  As individual SQL
   statements are also reported, it is possible and valid for the summed
   total % to exceed 100

  Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
select schm_type from gam where ltrim(foracid) = '01520420122077
7'

        142,163            3       47,387.7     0.5    543710942
select schm_type from gam where ltrim(foracid) = '01220420120112
8'

        142,153            3       47,384.3     0.5   2128458730
select schm_type from gam where ltrim(foracid) = '037870002'

        142,110            3       47,370.0     0.5   1570053712
select schm_type from gam where ltrim(foracid) = '01220420100163
4'

        142,101            3       47,367.0     0.5   2196065293
select schm_type from gam where ltrim(foracid) = '019870002'

        142,044            3       47,348.0     0.5   3713032413
select schm_type from gam where ltrim(foracid) = '01220420195031
0'

        142,035            3       47,345.0     0.5   1131688256
select schm_type from gam where ltrim(foracid) = '021870002'

          -------------------------------------------------------------
 SQL ordered by Reads for DB: COSMOS  Instance: cosmos  Snaps: 29 -30
-> End Disk Reads Threshold:    1000

 Physical Reads  Executions  Reads per Exec % Total  Hash Value
--------------- ------------ -------------- ------- ------------
         47,805            8        5,975.6     1.4   3294558041
select schm_type from gam where ltrim(foracid) = '012350003'

         42,106            7        6,015.1     1.2   2905796127
select schm_type from gam where ltrim(foracid) = '011370024'

         40,154            6        6,692.3     1.1   3219630629
select schm_type from gam where ltrim(foracid) = '003870002'

         35,178            6        5,863.0     1.0    698997535
select schm_type from gam where ltrim(foracid) = '02420430199001
2'

         33,351            5        6,670.2     0.9   1216533714
select schm_type from gam where ltrim(foracid) = '02120430122003
3'

         31,483            5        6,296.6     0.9   1455919800
select schm_type from gam where ltrim(foracid) = 'ZZ56113'

         28,356            5        5,671.2     0.8    523242490
select schm_type from gam where ltrim(foracid) = '02910010426'

         26,189            5        5,237.8     0.7   3860892109
select schm_type from gam where ltrim(foracid) = '00910511822000
2'

         25,784            4        6,446.0     0.7   2990940268
select schm_type from gam where ltrim(foracid) = '00320420100525
8'

         23,786            3        7,928.7     0.7   1600151028
select schm_type from gam where ltrim(foracid) = '012950007'

         22,799            4        5,699.8     0.6    917911480
select schm_type from gam where ltrim(foracid) = '034900004'

         20,850            3        6,950.0     0.6   3062676710
select schm_type from gam where ltrim(foracid) = '037450005'

         20,672            3        6,890.7     0.6   2128458730
select schm_type from gam where ltrim(foracid) = '037870002'

         20,618            3        6,872.7     0.6   4000704793
select schm_type from gam where ltrim(foracid) = '042870002'

         20,408            3        6,802.7     0.6   2287059575
select schm_type from gam where ltrim(foracid) = '01520420122077
7'

         19,616            3        6,538.7     0.6   3202625664
select schm_type from gam where ltrim(foracid) = '009870002'

         19,247            3        6,415.7     0.5    858788750
select schm_type from gam where ltrim(foracid) = '01220420192256
 SQL ordered by Reads for DB: COSMOS  Instance: cosmos  Snaps: 29 -30
-> End Disk Reads Threshold:    1000

 Physical Reads  Executions  Reads per Exec % Total  Hash Value
--------------- ------------ -------------- ------- ------------
1'

         18,976            3        6,325.3     0.5   1131688256
select schm_type from gam where ltrim(foracid) = '021870002'

         17,132            3        5,710.7     0.5   2516663073
select schm_type from gam where ltrim(foracid) = '01110513423003
0'

         16,838            3        5,612.7     0.5   2196065293
select schm_type from gam where ltrim(foracid) = '019870002'

         16,446            2        8,223.0     0.5   1580880405
select schm_type from gam where ltrim(foracid) = '01220420121060
4'

         16,431            3        5,477.0     0.5   1594883170
select schm_type from gam where ltrim(foracid) = '034870002'

         16,387            3        5,462.3     0.5    543710942
select schm_type from gam where ltrim(foracid) = '01220420120112
8'

         16,307            3        5,435.7     0.5   3134530522
select schm_type from gam where ltrim(foracid) = '039350004'

         16,056            2        8,028.0     0.5    989036002
select schm_type from gam where ltrim(foracid) = '01

          Followup:

Rollback per transaction %:   28.67

is high, basically says -- "1 out of 3 times we do work, we undo it all.  And
since undoing it is as expensive as doing it -- we effectively have added an
additional 30% processing on our system"

so, why so many rollbacks?

someone needs to tell 3rd party software vendor about a new amazing discovery in
the datbase -- "bind variables"

select schm_type from gam where ltrim(foracid) = '012350003'

         42,106            7        6,015.1     1.2   2905796127
select schm_type from gam where ltrim(foracid) = '011370024'

         40,154            6        6,692.3     1.1   3219630629
select schm_type from gam where ltrim(foracid) = '003870002'

and function based indexes, be nice not to FULL SCAN gam each time eh?  Add
all of those up and boy, you have a killer query there !

fix that query (make it take lots less IO and make it use binds) and things will
be much better.

      Rating: 5
My system is
      running very slow.  August 19, 2003
Reviewer:
      Rajendra  from India
Thanks for the reply . This a great help for me.

      Rating:
      5
  August 28, 2003
Reviewer:
      A reader
Tom,

Can you please explain what is "buffer is not pinned count"
and why is it caused.

Thanks a lot....

          Followup:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76961/apc2.htm#25962
in the reference guide, statistics are described.

August 29, 2003 Prince -- Thanks for the question regarding
      "reclaim space", version 8.1.7.4

      You Asked
Dear Tom,

I have a table which has more than 90% of the rows deleted and never will be
used. I would like to reclaim the space. Currently it has 133 extents (LMT with
uniform size of 512K). It has data less than 512K.

What is the straight forward way to unallocate all the extents but a few. Also
accidently it got 30 initial extents allocated. (Once allocated, can the initial
extents be deallocated?)

I am thinking of using the "alter table move..." to achieve the above two
(resize the space and reduce the initial extents). Is there any better way?

Also, when I try your show_space procedure, I get the following error (Only if I
try to get the space of an object owned by another user)
Note: I am logged in as an user with DBA privs.

DBUSR> exec show_space('PRENTRY','PARTNER_OWNER','TABLE') ;
BEGIN show_space('PRENTRY','PARTNER_OWNER','TABLE') ; END;

*
ERROR at line 1:
ORA-00942: table or view does not exist
ORA-06512: at "SYS.DBMS_SPACE", line 55
ORA-06512: at "DBUSR.SHOW_SPACE", line 20
ORA-06512: at line 1

DBUSR> desc PARTNER_OWNER.PRENTRY
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 CNY#                                      NOT NULL NUMBER(8)
 RECORD#                                   NOT NULL NUMBER(8)
 ....
 ....
 ....

DBUSR> select * from tab where tname like 'T%';

TNAME                          TABTYPE  CLUSTERID
------------------------------ ------- ----------
TBL_SPA                        TABLE
TBS_SPA                        TABLE

DBUSR> exec show_space('DBUSR','TBL_SPA','TABLE') ;
BEGIN show_space('DBUSR','TBL_SPA','TABLE') ; END;

DBUSR> exec show_space('TBL_SPA','DBUSR','TABLE') ;
Free Blocks.............................2
Total Blocks............................32
Total Bytes.............................131072
Unused Blocks...........................16
Unused Bytes............................65536
Last Used Ext FileId....................5
Last Used Ext BlockId...................209
Last Used Block.........................16

PL/SQL procedure successfully completed.

      and we said...
alter table move is the BEST way to do it.  You can specify all new storage
parameters at that point if you want.

The owner of show_space (DBUSR) doesn't have DIRECT access to the table in
question.  You might consider using AUTHID CURRENT_USER if you want to install
the procedure in dbusr and use it in other schemas.
Review &
      Followup

      Rating:
      4
DBMS_SPACE.UNUSED_SPACE in the standalone mode
      works.  July 17, 2002
Reviewer:  Prince

Tom,

If I just execute DBMS_SPACE.UNUSED_SPACE as an anonymous block it works fine.

Doesn't the DBMS_SPACE obtain the space info from the dict tables? Why do I need
to have the privilege to access the other user's table directly, if that is the
case?

Also, once allocated more initial extents (say 5120K on 512K uniform sized LMT),
can the unused extents be deallocated, by using deallocate unused?

Thanks,

  1  declare
  2     segment_owner              VARCHAR2(30) default 'PARTNER_OWNER';
  3     segment_name               VARCHAR2(30) default 'PRENTRY';
  4     segment_type               VARCHAR2(30) default 'TABLE';
  5     total_blocks               NUMBER;
  6     total_bytes                NUMBER;
  7     unused_blocks              NUMBER;
  8     unused_bytes               NUMBER;
  9     last_used_extent_file_id   NUMBER;
 10     last_used_extent_block_id  NUMBER;
 11     last_used_block            NUMBER;
 12     partition_name             VARCHAR2(30) DEFAULT NULL;
 13  begin
 14     DBMS_SPACE.UNUSED_SPACE (
 15     segment_owner              ,
 16     segment_name               ,
 17     segment_type               ,
 18     total_blocks               ,
 19     total_bytes                ,
 20     unused_blocks              ,
 21     unused_bytes               ,
 22     last_used_extent_file_id   ,
 23     last_used_extent_block_id  ,
 24     last_used_block            ,
 25     partition_name             );
 26     dbms_output.put_line('total blk : ' || total_blocks               );
 27     dbms_output.put_line('Unused blk : ' || unused_blocks              );
 28     dbms_output.put_line('last unused blk : ' || last_used_block
);
 29* end ;
12:32:36 DBUSR> /
total blk : 17024
Unused blk : 33
last unused blk : 95

PL/SQL procedure successfully completed.

          Followup:

read

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html
roles are not enabled in the stored procedure -- they are in the anonymous
block.  your user has the ability to see the table via a role, not a direct
grant.  Use authid current_user to make this a better general purpose solution.

We try not to bypass security in any case, so yes, the owner of the procedure
does need access (it is not a simple dictionary read)

ops$tkyte@ORA920.US.ORACLE.COM> create table t ( x int ) storage (initial 200k);

Table created.

ops$tkyte@ORA920.US.ORACLE.COM> select count(*) from user_extents where
  2  segment_name = 'T';

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

ops$tkyte@ORA920.US.ORACLE.COM> alter table t deallocate unused;

Table altered.

ops$tkyte@ORA920.US.ORACLE.COM> select count(*) from user_extents where
  2  segment_name = 'T';

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

      Rating: 4
I got the
      answer to my second question.   July 17, 2002

Reviewer:  Prince.
Tom,

I could reclaim the initail extent, if the extra extents were never used.

Thanks.

      Rating: 4
I got it
      now  July 18, 2002
Reviewer:  Prince.

Regarding the second case, I used "keep 0K" in my "deallocate unused "command.

      Rating: 5
initial
      extent  August 28, 2003
Reviewer:  Reader

If my table has initial extent say 100M, and i have not inserted any data in it,
If I used the command deallocate unused, can I reclaim all the space except a
block for block header info from the extent? Thanks.

          Followup:

In a DMT, perhaps:

ops$tkyte@ORA817DEV> create table t ( x int ) tablespace system storage (
initial 10m );

Table created.

ops$tkyte@ORA817DEV> select blocks from dba_extents where segment_name = 'T' and
owner = user;

    BLOCKS
----------
      1280

ops$tkyte@ORA817DEV> alter table t deallocate unused keep 0k;

Table altered.

ops$tkyte@ORA817DEV> select blocks from dba_extents where segment_name = 'T' and
owner = user;

    BLOCKS
----------
         1

ops$tkyte@ORA817DEV>

In an LMT, you will be constrained by the uniform or system allocated extent
size as all extents are the same or system managed size.

August 29, 2003 Debra -- Thanks for the question regarding
      "Performance differences between UNIX, Linux, NT, and 2000.", version 8i

      You Asked
I am trying to find documentation on case studies comparing peformance issues
when running Oracle on various operating systems.

Thanks,
Debra

      and we said...
We have no such document.  We are operating system agnostic, we exploit features
of each.  We can more than do the job on each.  Your choice of OS should be made
in favor of what features you want, what skills you have, what you already use.

I'm pretty sure that given any system, you can always make it go 10% faster.
Tuning in itself is a never ending goal unless you have some metrics to meet.
What you really need to do is figure out what you want to do performance wise,
then size the system you would need with each OS (typically done via
benchmarking) and see which one is the best fit.
Review &
      Followup

      Rating:
      3
But...  December 12, 2001
Reviewer:
      A reader  from CA, USA
Interesting question, but there must be some guidelines you can give. e.g. a
really large application or data warehouse probably isn't suitable on an Intel
platform and hence NT or 2000 (assuming NT/2000 is generally limited to Intel).
Is this a myth?

          Followup:

You can do very large databases on win nt/2k -- if you know what you are doing.
You can do very large databases on unix -- if you know what you are doing.

As I said -- it depends on what you are comfortable with, what you know, and how
much you really know it.  We have very large databases on both -- its more of a
hunch on my part, but if pressed i would say most of the really large ones are
Unix based.

      Rating: 3
Chapter 2 of
      your book and benchmarking  December 12, 2001

Reviewer:  Sam
In chapter 2 of your book, talking about different kind of databases, you talk
about 'BENCHMARKING' , I did not understand.

Can you let us know what is the concept of benchmarking.

          Followup:

from the dictionary:

Main Entry: bench·mark·ing

Function: noun
Date: 1976

: the study of a competitor's product or business practices in order to improve
the performance of one's own
company---------------------------------------------

one way to benchmark is to implement a process using the best practices for each
product -- run them -- and see which performs adequately for your environment.

another way to benchmark is using a tool like statspack -- to gather and retain
historical performance statistics so that over time you can see how you are
doing, you can benchmark your current performance against past performance.

      Rating: 4
Performance
      benchmarking  August 28, 2003
Reviewer:
      Pushparaj Arulappan  from NJ, USA
Tom,

We are planning to migrate to a new HP Server which is
called SUPERDOME. It has been told that the SUPERDOME
server has the very powerful processors. Currently we have
HP V25000, CPU=16 @ 440 MHZ, Memory=32 GB)
and the new server would be CPU=12 @ 875 MHZ, mem=32 GB.

We have 8 Oracle9i instances running in the current server
and the same will be migrated to the new server.

Is there any performance benchmark that I can do in the old server and repeat
the same test on the new server after the migration to verify if we get any
performance improvement
on the new server.

Thanks
Pushparaj

          Followup:

8 instances is of course 7 too many....

you could run a tpc-c on the old/new servers -- but that would just tell you how
fast/slow your hardware can run a tpc-c.

it is really hard to benchmark an 8 instance server -- you cannot benchmark
individual instances as they all interfere with eachother.  It is in my opinion
a really bad idea to have many instances on a server, no way to manage
resources, to control things.  I'd rather have 8 2cpu machines or 1 16 cpu
machine with a single instance.

August 29, 2003 Dusan -- Thanks for the question regarding
      "Procedures, roles and grants", version 8.1.6

      You Asked
Hi Tom,

We are using 2 schemas, user1, user2.
User1 has user1_table, and created role role1. The following was
issued:
grant select, insert, update, delete on user1_table to role1;
grant role1 to user2
User2 created private synonym user1_table for user1.user1_table
and tried to create :

create or replace procedure aa as
aa user1_table.column%TYPE;
begin
null;
end;
/
Warning: Procedure created with compilation errors.

SQL> show err
Errors for PROCEDURE AA:

LINE/COL ERROR
--------
-----------------------------------------------------------------

2/4      PLS-00201: identifier 'USER1.USER1_TABLE' must be
declared
2/4      PL/SQL: Item ignored

Why user2 cannot access user1_table in his procedure, if he has
rights on the this table via role1?
We do not want to grant  user1_table to user2 directly.

Thanks,

Dusan

      and we said...
See

http://osi.oracle.com/~tkyte/Misc/RolesAndProcedures.html
You have no choice but to grant select on user1_table to user2
directly.  It is the only way to make it work.

Why is it that roles are not used during the compilation of a
procedure?  It has to do with the dependency model.  Oracle is
not storing exactly WHY you are allowed to access T ¡V only that
you are (directly able to -- not via a role).  Any change to
your privileges that might cause access to T to go away will
cause the procedure to become invalid and necessitate its
recompilation.  Without roles ¡V that means only ¡§REVOKE SELECT
ANY TABLE¡¨ or ¡§REVOKE SELECT ON T¡¨ from the definers account
or from PUBLIC.  With roles ¡V it greatly expands the number of
times we would invalidate this procedure.  If some role that was
granted to some role that was granted to this user was modified,
this procedure might go invalid, even if we did not rely on that
privilege from that role.  ROLES are designed to be very fluid
when compared to GRANTS given to users as far as privilege sets
go. For a minute, let¡¦s say that roles did give us privileges
in stored objects.  Now, most any time anything was revoked from
ANY ROLE we had, or any role any role we have has (and so on --
roles can and are granted to roles) -- many of our objects would
become invalid. Think about that -- REVOKE some privilege from a
ROLE and suddenly your entire database must be recompiled!

Consider the impact of revoking some system privilege from a
ROLE, it would be like doing that to PUBLIC now -- don't do it,
just think about it (if you do revoke some powerful system
privilege from PUBLIC, do it on a test database).  Revoking
SELECT ANY TABLE from PUBLIC for example would cause virtually
every procedure in the database to go invalid.  If procedures
relied on roles ¡V virtually every procedure in the database
would constantly become invalid due to small changes in
permissions.  Since one of the major benefits of procedures is
the ¡§compile once, run many¡¨ model ¡V this would be disastrous
for performance.

Also consider that roles may be

ƒá Non-default: If I have a non-default role and I enable it and
I compile a procedure that relies on those privileges, when I
log out I no longer have that role -- should my procedure become
invalid -- why? Why not? I could easily argue both sides.

ƒá Password Protected: if someone changes the password on a
ROLE, should everything that might need that role be recompiled?
 I might be granted that role but not knowing the new password
¡V I can no longer enable it. Should the privileges still be
available?  Why or Why not?  Again, arguing either side of this
is easy.  There are cases for and against each.

The bottom line with respect to roles in procedures with
definers rights are:

ƒá You have thousands or tens of thousands of end users. They
don't create stored objects (they should not). We need roles to
manage these people.  Roles are designed for these people (end
users).

ƒá You have far fewer application schema's (things that hold
stored objects). For these we want to be explicit as to exactly
what privileges we need and why.  In security terms this is
called the concept of 'least privileges' -- you want to
specifically say what privilege you need and why you need it. If
you inherit lots of privileges from roles you cannot do that
effectively. We can manage to be explicit since the number of
development schemas is SMALL (but the number of end users is
large)...

ƒá Having the direct relationship between the definer and the
procedure makes for a much more efficient database.  We
recompile objects only when we need to, not when we might need
to.  It is a large efficiency enhancement.

Review &
      Followup

      Rating:
      4
  March 28, 2003
Reviewer:
      A reader

      Rating: 4
But, how can
      you tell?  April 02, 2003
Reviewer:  Robert
       from Memphis, USA
Tom,

Good info on *why* explicit privs must be granted.

... But is there a way to tell if a privilege has been granted to a user
explicitly vs. via a role?

From what I can tell it looks like dba_sys_privs, dba_tab_privs, etc. show the
same info whether or not a privilige has been granted explicitly or via a role.

This is an important issue, because of the way Oracle handles privileges with
procedures, views, etc.

How can we tell if a user's privilege has been granted explicitly or via a role?

Thanks,

Robert.

          Followup:

select * from dba_sys_privs where grantee in ( 'PUBLIC', <SOMEONE> );
select * from dba_tab_privs where grantee in ( 'PUBLIC', <SOMEONE> );

shows you what you have directly granted to you (or to public, which counts as
well)

      Rating:
      5
Thanks....and....Sorry about that!  April 02, 2003

Reviewer:  Robert  from Memphis, USA
Tom,

Sorry for wasting your time.
I was seeing unusual behaviour from privileges after having done a database
refresh (prod to test) from a hot backup, and I *assumed* that role privileges
were showing up in dba_sys_privs.
A quick test/demo ala AskTom proved otherwise, as you said.
Should have done this before asking you.
Thanks for all your help.

Robert.

      Rating: 5
application
      roles  July 18, 2003
Reviewer:  Reader

Could you please help me understand how one can enable roles through package. I
was reading the doc and the syntax for doing this is,

create role role_name
identified using schema.package;

In the package do i have to assign privileges to the role? I would appreciate if
you have an example. Thanks.

          Followup:

That just makes a role that can only be enabled via the stored procedure --
meaning the end user (client application) has to run your piece of code -- which
can validate if it wants to enable the role or not -- at runtime.

You grant privs to the role as normal -- via grant.

      Rating: 5
that means
      ...  July 19, 2003
Reviewer:  Reader

So, does it mean that I can use SET ROLE statement in the procedure to enable
and disable roles? Thanks.

          Followup:

you would use dbms_session.set_role

      Rating: 5
Grant
      Issues  August 28, 2003
Reviewer:  Yogesh
       from Pune India
I have 2 schemas X and Y, I have a procedure p1 in schema X
which creates some tables dynamically based on logic. User Y have execute grants
on procedure p1.

Do Y also need to have grants on all objects which procedure p1 is referening to
? I feel no ... as procedure is going be executed in X schema ... but called
from Y.

But when I tried executing it from Y, it starts giving insufficient privileges
.. or table not found errors. When I issed grants to user Y,then it started
working fine ... I could not understand why ...

          Followup:

Y only needs execute on P1.

P1 will execute with the base privs of X (no roles).

You would actually need to provide "an example" showing your issue.

August 29, 2003 Gustavo -- Thanks for the question regarding
      "Problems with forms or libraries that reference a recompiled package
      spec", version 2.1

      You Asked
I read the article about automatic compiling published on
September/october 2000 in Oracle Magazine.
We work over Oracle 7.3 and developer 2.1 and we have the
following problem :sometimes when we compile a package
specification (for example we only change the parameter type
from number to char) the forms and libraries that reference that
package will show the message ORA-04068 "existing state of
packages has been discarded" if the forms or libraries are not
recompiled.
Are there any settings I could use in the Oracle Database or
Developer/2000, or patches I could use in order to compile the
package without having to recompile the forms or libraries ?

Thank You

      and we said...
Well, if you change the SPECIFICATION (changing the datatype
from number to char for example) will necessarily force the
recompilation of dependent code!  You've totally changed what
the calling is calling -- forms needs to rebuild its dependent
code to now cast a number into a character string before calling
the procedure.

Specs should never change in production and when they do -- you
need to recompile all dependent objects.  The only solution
short of that would be to use dynamic sql to invoke everything
and do all of the binding yourself (at which point you still
have a problem -- you would be binding a number to a string
which might work for IN but probably won't work for IN OUT
variables).

It is like it would be if you were programming in C or Java and
suddenly changed a subroutine from taking an "int" as a
parameter to taking a "char *" -- all code that calls that must
be recompiled and relinked after changing the datatypes being
passed.  PLSQL will automatically cast the type for us but it
must be recompiled to do so..
Review &
      Followup

      Rating:
      4
  July 26, 2002
Reviewer:
      A reader
Hi Tom,
We are running into the same error (ORA-04068: existing state of packages has
been discarded), but we didn't change the specification, we only recreate the
package body.
Every time when a developer recreate a package body,
all the dependent object run into this problem the first time when an
application tries to execute the dependent object after the change, and if you
go to the dba_objects the dependent object is valid.
We also run into this problem with remote dependent object, but in this case if
you check the dba_objects for the remote dependent object you can see that the
object is invalid.
Is there any way to avoid this error and the cancelation of the execution? Is
there any way to let the applications  to recompile the dependent object without
aborting the execution?

Thanks

          Followup:

You changed the package body -- they have a state as well.  You changed the code
people were running, they MUST get this message and then they can "start over".
You've trashed their state, plain and simple.

The STATE of the package body you just recompiled -- gone, wiped out, must be
re-established (and imagine the bugs if we DID'T raise this error???)

      Rating:
      5
  July 28, 2002
Reviewer:
      A reader
Thanks Tom,

I understood what you said.
But I have a two more questions:

1-why the dependent objects are still valid in the dba_objects after we change
the package? Shouldn't they be invalid too? because in the case of remote
dependent object if you go to the dba_object (in the remote database) you will
find that the dependent object will be invalid.

2-how does exactly work the remote_dependent_mode parameter? I will really
appreciate if you can explain me both alternatives timestamp and signature. I
read the oracle documentation but it wasn't very clear to me.

Thanks

          Followup:

1) the objects are dependent on the SPEC, not the body.  You did not change what
they are dependent on so -- no, they should not be invalidated (it is one of the
major advantages of packages that they are not)

2) with timestamp, if the timestamp in the remote data dictionary changed (the
package was compiled for any reason and the data dictionary reflects that) --
the package that is dependent on the remote code will go invalid AT THE POINT IN
TIME it first references the remote code.

In "signature" mode, we will remember not the timestamp but rather the name of
the remote object, the number types and names of the inputs/outputs and the
return value type if any.  That is the signature of the object.  If that changes
-- then the local object will go invalid at the point in time it first
references the remote code.

      Rating:
      5
  July 29, 2002
Reviewer:
      A reader
Hi Tom,

If I understood well, if I only recreate the specification (in my case i didn't
change anything in the code, i only recreate it) and the body wasn't changed,
the remote dependent object should not either receive any error or get invalid
when it is execute.

I was doing some tests and taking into account what you said, I still don't
understand why I am receiving the error,
ORA-04068: existing state of packages has been discarded
ORA-04062: timestamp of package "SCOTT.PKG_A" has been changed

Here is my test:

I have a package (named pkg_A) in database A and a function (named funct_B) in
database B which calls pkg_a through a dblink.
Every time when a recreate the specification (I don't change anything I just
recreated it) I receive the the error 4068 and 4062.

Databas A and B has the remote_dependent_object=signature

Database A: Before the change
----------

SQL>select * from dba_objects where object_name='PKG_A' and owner='SCOTT';

OWNER
------------------------------
OBJECT_NAME
--------------------------------------------------------------------------------
SUBOBJECT_NAME                  OBJECT_ID DATA_OBJECT_ID OBJECT_TYPE
------------------------------ ---------- -------------- ------------------
CREATED   LAST_DDL_ TIMESTAMP           STATUS  T G S
--------- --------- ------------------- ------- - - -
SCOTT
PKG_A
                                    41071                PACKAGE
24-JUL-02 26-JUL-02 2002-07-26:15:28:22 VALID   N N N

SCOTT
PKG_A
                                    41072                PACKAGE BODY
24-JUL-02 26-JUL-02 2002-07-26:15:54:53 VALID   N N N

Database B: before the change
----------

SQL>select * from dba_objects where object_name='FUNCT_B');

OWNER
------------------------------
OBJECT_NAME
--------------------------------------------------------------------------------
SUBOBJECT_NAME                  OBJECT_ID DATA_OBJECT_ID OBJECT_TYPE
------------------------------ ---------- -------------- ------------------
CREATED   LAST_DDL_ TIMESTAMP           STATUS  T G S
--------- --------- ------------------- ------- - - -
TIGER
FUNCT_B
                                    36359                FUNCTION
24-JUL-02 26-JUL-02 2002-07-26:16:18:03 VALID   N N N

After I recreated the package specification, the status is the following:

Database A: after the change
-----------

OWNER
------------------------------
OBJECT_NAME
--------------------------------------------------------------------------------
SUBOBJECT_NAME                  OBJECT_ID DATA_OBJECT_ID OBJECT_TYPE
------------------------------ ---------- -------------- ------------------
CREATED   LAST_DDL_ TIMESTAMP           STATUS  T G S
--------- --------- ------------------- ------- - - -
SCOTT
PKG_A
                                    41071                PACKAGE
24-JUL-02 29-JUL-02 2002-07-29:11:11:56 VALID   N N N

SCOTT
PKG_A
                                    41072                PACKAGE BODY
24-JUL-02 29-JUL-02 2002-07-29:11:10:30 INVALID N N N

SQL_>alter package scott.PKG_A compile body;

Package body altered.

SCOTT
PKG_A
                                    41071                PACKAGE
24-JUL-02 29-JUL-02 2002-07-29:11:11:56 VALID   N N N

SCOTT
PKG_A
                                    41072                PACKAGE BODY
24-JUL-02 29-JUL-02 2002-07-29:11:12:06 VALID   N N N

Database B: after the change (including the recompilation of the body)
-----------

SQL>select tiger.FUNCT_B from dual;
select tiger.FUNCT_B from dual
       *
ERROR at line 1:
ORA-04068: existing state of packages has been discarded
ORA-04061: existing state of package "SCOTT.PKG_A" has been invalidated
ORA-04065: not executed, altered or dropped package "SCOTT.PKG_A"
ORA-06512: at "TIGER.FUNCT_B", line 4
ORA-06512: at line 1

OWNER
------------------------------
OBJECT_NAME
--------------------------------------------------------------------------------
SUBOBJECT_NAME                  OBJECT_ID DATA_OBJECT_ID OBJECT_TYPE
------------------------------ ---------- -------------- ------------------
CREATED   LAST_DDL_ TIMESTAMP           STATUS  T G S
--------- --------- ------------------- ------- - - -
TIGER
FUNCT_B
                                    36359                FUNCTION
24-JUL-02 26-JUL-02 2002-07-26:16:18:03 INVALID N N N

Thanks for taking time to explain this to me. I really appreciated.

          Followup:

Yes, you trashed the state -- the package state.  We don't care about
timestamp/signature here, this is the STATE you are talking about.  You wiped
out the package state.

The timestamp/signature argument only applies to the initial invocation -- the
caller will look at the callee and say "do I need to recompile myself, did this
change since I've last looked?  No -- Ok, continue"

You got page that point, trashed the state and tried to run it again.  Doesn't
matter timestamp or signature -- you trashed the state.

      Rating:
      5
  July 29, 2002
Reviewer:
      A reader
Hi Tom,

Thanks now I understand better.

When you say "you trashed the state", what do you exactly mean? How do you
define the "state" of an object?

thanks again

          Followup:

the state is the settings of any and all global variables -- the "state" of the
package.  You "trashed" it -- eg: wiped it out -- by replacing it.

      Rating: 3
Problems with
      forms or libraries that reference a recompiled package
      spec  August 05, 2002
Reviewer:  Hohenthal
       from Helsinki, Finnland
If application should be running all the time, then it is very difficult to find
time to bring new versions of package bodies, because of this 'existing state of
package has been discarded'.  Actually many packages don't have this 'package
state', what is meaningfull for application. They may use global variables for
caching or they may even have this restrict references pragmas RNPS, WNPS.  If
they have RNPS,WNPS pragmas it would be very usefull, if oracle would
automatically flush this package and use new version. Maybe some cursors should
be reopened automatically. Other possibility would be some kind of 'statesless'
pragma (like this new deterministic) stating that application programmer is
taking responsibility of this 'existing state of package has discarded problem'
when new version of package body is brought into database.

          Followup:

do you understand that if we just flushed these packages -- wiping out the state
-- it would be a very very bad thing.  It would lead to horrible bugs (a
state is something you need to be ABLE TO COUNT ON BEING THERE!!!!!!)

There is a stateless pragma -- serially_reusable. It may be useful in your case.
 However, you won't be using globals (a state) for ANYTHING -- ever.

      Rating: 2
Problems with
      forms or libraries that reference a recompiled package
      spec  August 06, 2002
Reviewer:  Hohenthal,
      Helsinki  from Finnland
I have in mind applications that are using transaction monitors or application
servers and should be working 7 * 24. If they are calling package (pack_a) that
is further calling some other package (pack_b) then replacing body of package
pack_b can cause this 'existing state of package has been ...'. However this
package may not even use global variables (restrict_references is WNPS, RNPS)
and application programmer  knows that this package does not assume that cursors
are open  or anything like that.
(I do agree that flushing the state of package can be dangerous, but to many
applications it doesn't matter. Most procedures and functions are actually used
in stateless fashion (e.g routine fetching name of customer)).

I have not used this serially_reusable pragma, but does it solve this problem ?
It would be very usefull, that those wanted package bodies could be replaced on
the fly.

          Followup:

Tell me, in this 24x7 environment -- when you have to update your C code that is
in the TPM, what do you do then (hey, you shutdown since you cannot replace C
code which is being used).  The SAME exact issue is arising here.  The same
exact issue.

Did you read about it (serially reusable)?????

I can only lead you so far -- after that you must investigate this yourself.

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a77069/05_ora.htm#36195
It effectively takes away the "statefullness" of the package.

(also, in a TPM environment - i don't see how you would be hitting this
"existing state" error since a TPM by its very definition is "single message in
-- do a single transaction -- and get the heck out of there".  There is no long
running session with multiple calls to and from the database).

      Rating:
      5
  December 26, 2002
Reviewer:
      A reader
Tom,

I have a package (pkg_a) on database A and a function (func_X) on database B
which calls pkg_a through a db link.
Database A and B has the remote_dependencies_mode=SIGNATURE.
If I change the package specification (say I added a new procedure in the body
so I need to add that procedure in the specification) the first time when I call
the function (func_x) on database B the function runs OK, it doesn't get
invalid.
I tested this calling the function in a new sql session and also in another sql
session which was using the function (func_x) before I made the changes in the
package specification. In both cases the function run ok and it didn't get
invalid at any time.
Shouldn't the function (func_x) on database B get invalid when I run it after
recompile the package specification in database A?

Thanks for your help.

          Followup:

only if the signature of the called function / procedure changes -- which it
didn't.  you added a new procedure, you didn't touch the old ones.

      Rating:
      5
  December 26, 2002
Reviewer:
      A reader
Tom,
Thanks for answering me, I understand your point.
But I have another question, I have another example using the same package
(pkg_a) and funtion (func_x) but now I added a new function in database B called
func_y. This new function called another procedure which is also inside pkg_a
but this procedure is different from the one called by func_x.
If I change the name of the procedure called by func_x in the package (body and
specification) and then I run func_y everything is ok, func_y runs without
problems and both function (func_x and func_y) are valid after running func_y.
But If I run func_x as I changed the name of the procedure in pack_a, func_x
fails and invalidates func_x and func_y.
Why did in this last case both functions get invalid if func_y is using a
different procedure which was not changed in pkg_a?

Thanks

          Followup:

because func_y in your session was made aware of the change indirectly by func_x
I suppose.  The server invalidated the package and everything you had dependent
on it.

(these word problems -- very hard to follow, me head hurts now ;)

      Rating: 5
ORA-04061
      error  June 25, 2003
Reviewer:
      A reader
I am running into this error as well. In my case I
changed just the implementation. I am not sure what
should be done to remove this error. I understand
why this error is but - to get back to a clean state
should i just log off from the application and log
back in - should i bounce the database? What is
typically done during developing a package (when it undergoes a lot of changes?)

Thank you - please keep up the great work!!

          Followup:

your implementation changed or maintained a state.

you rebuilt the implementation, state = "gone"

just reconnect or just resubmit the request.

      Rating:
      4
  August 28, 2003
Reviewer:  Pauline
       from NY, U.S.A.
Tom,
When we compiled package body on production database at day time, we get errors
of
ERROR 2003-08-21 16:35:17,081 - ConsignmentDAO.selectConsignment 1:
consignmentID=159660087 lotID=0
java.sql.SQLException: ORA-04068: existing state of packages has been discarded
ORA-04061: existing state of package body "CORECODE.LOT_PKG" has been
invalidated
ORA-04065: not executed, altered or dropped package body "CORECODE.LOT_PKG"
ORA-06508: PL/SQL: could not find program unit being called
ORA-06512: at "CORECODE.CONSIGNMENT_PKG", line 447
ORA-06512: at line 1

According to your comment, we changed the code people were running, they MUST
get this
message and then they can "start over".

But in my impression, sometimes when we compile package on staging database, if
user has
the long running transaction which executes the package, I could not compile
that package
(getting time out while compiling the package) until user finished the
transaction.

Since we won't be allow to reproduce this situation on production, I need your
help to
explain the above errors occured on which situation

1. Users have the connections through web logic. The package code is 'cached'.
We start to
compile package body. The users have not log out in their connection and then
tried to
execute the package would get above errors.

2. The users were executing old version of package body. In the middle of
process, we start
to compile package body. Then we receive the above errors.

we will appreciate your detail explanation.

Many thanks.

          Followup:

#2 is the answer.

the user executed the package.... it has a state.  the state was set.  user
stopped executing the package but the session remains, the state remains.

you came along and recompile the code.

bamm -- there goes that state.

user tries to execute the package -- but package reasonably says "hey, surprise
surprise -- since the last time you saw me, I've totally changed and your prior
state is *GONE*"

      Rating: 5
Your great
      answer let me know the mystery behind the seeing  September
      04, 2003
Reviewer:  A reader
Tom,
Thanks very much for your detailed explanation. Now I know
what is the problem.

Pauline

      Rating:
      5
  December 15, 2003
Reviewer:
      A reader

August 29, 2003 Eduardo -- Thanks for the question regarding
      "password", version 8.1.7

      You Asked
Hi tom.

As you know, you can retrieve the password in Oracle form by using
the get_application_property built-in.
How can I "hide" the password if developers want to get the
password this way?
I think that there is not security if somone else can retrieve the
password that belongs to the owner of a application.
Is there any way to avoid this built-in function?

On the other hand, how can I connect from Oracle Forms with
logon built-in if I'm using the OPS$ user ?

Thanx in advance

      and we said...
well, why wouldn't the developers just create their own login screen then and
ask for the username/password.  Now the password is just a text item to them.

The developer would just use their own login screen, and the end user could
never tell the difference.

If you distrust your developers so much, you need to hire new developers.

Code reviews of developed code is what you use to prevent this "trojan horse"
issue from happening.  You need to review code prior to having said code put
into production.
what is to prevent the developer from reading the users hard drive and mailing
themselves password files for later crack attempts (or reading any sensitive
data and sending it to themselves).  If you don't trust the software you deploy,
you have huge problems.

Using ops$ accounts is easy.  Just enter / in the login.
Review &
      Followup

      Rating: 2
I
      disagree  July 02, 2002
Reviewer:  Juan Carlos
      Reyes  from Bolivia
>If you distrust your developers so much, you need to hire >new developers.

I disagree this commentary for 2 reasons.

1. You can trust in you employees but your clients doesn´t and your clients ask
for some kind of guarantee.
2. Several investigation had concluded that
   25% are alwas honest
   25% tries to steal as much as they can
   50% steals in the way they can
   You coud read for example the following book.
    "Corporate Fraud" by Michael J. Comer

The fact that you can change the pasword and after restore it, Is too, another
big security problem in Oracle, because you can change the password do something
and after restore it older using its encrypted string.

I think oracle should enforce security in this area.
I would like to know you opinion about this.

Thanks

          Followup:

1) then why would they trust the answer you give them?  I would trust you saying
"they are not kidnapping your password, "trust me" -- no more then anything
else.

Hey, how about the guys that wrote the windows logon?  what about them?

What about the guys who write the HTML page that takes your username password?
what about them?

What about the guys who wrote the basic authentication dialog in Netscape,
Mozilla, IE?  What about them?

What about the guys how wrote the ALTER USER command that changes your password?

What about the guys who wrote SQLPlus that accepts your username and password.

and so on -- think about it.  The opportunity to get your password exists in
sooo many places.

If you read my answer, if the developer were so dishonest -- there would be
nothing stopping them from putting a trojan horse in there.  Just like those
emails people get asking them for their AOL password to "correct a problem".
Hmm.

2) I could read lots of books -- I understand human nature.  For example over
90% of all system failures are caused by "pilot error".  Should I therefore stop
allowing humans to run my system and hire monkeys to run them instead?  I won't
have that system failure problem anymore will I.

The only way you can do what you describe (the changing and changing back) is to
have the very powerful ALTER USER privelege.  If you (you personally) CHOOSE to
grant this priv, so be it.  You do not HAVE to, hence the problem only exists
when you personally introduce the problem into the mix.  Additionally, there are
many many ways to authenticate to Oracle -- OS authentication, x.509
authentication, biometric authentication, global users, and so on.  Security is
something we try to pay particular attention to.  Username/password is one of
the weaker authentication methods *in any system*.  There are infinitely
stronger ways to accomplish this.

      Rating: 3
You`re rigth
        July 03, 2002
Reviewer:  Cachito
       from Bolivia
You`re rigth about what you said.
But sometimes you need the possibility to be more secure.
Try to understand my situation.

This happened recently.

Information that belongs to one of our customers were sold to one of their
competitors.

we couldn't say nobody of our programmer could do this, because this would be
lie.

And this was a very uncomfortable situation for every one.

We can't change to OPS$ user authentication.
Then we analyzed the the possibility to make more secure
for example encrypting and using wrap utility to protect security functions that
encrypts with a secure key
that only the manager knows.

And the only point we found for this security was the get_application_property,
not because I don't believe someone could replace a form with one that save the
password to file; else because we need the possibility to disable that option to
say the customer we can´t do this and under this conditions you are safe.

After they discovered was an exemployee, but you could understand the situation,
it could be interesting to give more guarantees to customers.

I complaint about that, that we cant say to our costumers, except the managers
no body can do that, even if they want, stole information something.

I understan always there is going to be a way to stole information, at least for
a good dba, but the point is to make it more difficult.

Thanks you for you answer.

      Rating: 5
About security
      and internet in Windows platform (key loggers)  June 11,
      2003
Reviewer:  A reader
I want to quote something that had been very usefull to me, and I think can help
you to help to keep the security in your network.
some time ago I installed a key logger(a software that logs all what you types
including usernames and passwords) in my machine, I tried some antispy softares,
but the point is that none of that "antispy" software detected it.

The problem of the spyware software is that you can install in a machine, and
program to send to an anonymous email, and never return to that machine.
Is really a big big hole in security, that many network administrators and dba's
like me, didn't know about.

After a long time I found a software
if found this

http://www.webroot.com/wb/products/spysweeper/index.php
testing I found in one machine I found a software that was sending passwords,
the spyware I installed, and additionally a resident adware was the responsible
for the lost in performance and other problems. For people like me that don't
know too much about spyware and adware, it had been a really excellent software
(5 stars)

You can try it

http://www.webroot.com/services/spyaudit_03.htm
P.D. I am not the owner neither has shares in webroot products, really is a good
product for security anti spys and anti adware, to strength the security in you
network, I think there are others but this worked for me.

      Rating: 5
Tom one more
      about security, fusioning software  June 13, 2003

Reviewer:  A reader
I share this with you, because I think you´ll see this is very interesting as I
did.

I´m amazed how easy it to stole a password.
in windows, and I bet there is something similar in linux.

I did this.
I downloaded a trial from

http://www.blazingtools.com/downloads/i_bpk2003.exe
With this software you can fusion for example, the winzip installer with the
keylogger installer.

This means you have only the winzip installer.

But when you run winzip installer, it installs this spyware too and you neither
knew about this

I ask my self how much administrator test the machine searching spywares. The
antivirus don´t do this work.
Very very interesting

          Followup:

umm, not very interesting really.  you need to gain physical administrator
access to the machine, meaning -- you are the guy in charge of security.

      Rating:
      5
  June 13, 2003
Reviewer:
      A reader
Yes you are right,
1. but sometimes there is a different person that install the software for first
time and for update it, that person has enough privileges for installing any
software.
I´m not telling you cannot protect against it, what I say is, first you must
know it is possible, Me for example, I didn´t know about this spywares, I never
would had think that could be possible do this.
I thought with a good antivirus and a good firewall is enough.

:) Thanks

      Rating:
      5
demonstrated  August 13, 2003
Reviewer:
      A reader
Hi Tom
:) tested,

I added a spyware to an a
application ( you must practice before trying ).
I ask a friend, and bet a hamburger if he would detect what I was going to do,
he was not very conviced, but as I told that was better to do it with his
knowledge than without it.

I copied the executable, he runned his antivirus, it was execute for a person
who makes installation, the spyware was installed, and sent information to his
email. The person who installed it didn't note nothig strange.

The point is, not everyone can do it, but if you can put an intruder file, or if
the person enter to a specific site without the protection, ( for example from a
friends advise to visit specific sites )is possible to install a spyware that
stole you passwords.

Again, The point is several network administrator don't see it as a security
problem, they neither install some software to search adware and spyware, very
curious.

      Rating:
      5
  August 14, 2003
Reviewer:
      A reader
I found another good adaware Ad-aware 6.0, from lavasoft

      Rating: 5
As you need to
      have antiviru, you need to have a spyware detector  August
      26, 2003
Reviewer:  A reader
Hi Tom, a statistic says
"9 or 10 computers are infected by ad-ware and spyware"
I personally found 2 spywares in my machine that was sending confidential
information to internet.

My conclusion, base in what I tested is
"As you need to have antivirus, you need to have a spyware detector"
You don't need to have an antivirus, or a spyware detector; but if you don't
have you run the risk to lost confidential information or being infected by a
virus.
Which is the sense of a lot of security (for example change your password ever
week) if a spyware is copying that information to who knows who in some place.

:) What do you think about this Tom, I'm right or wrong?

          Followup:

well, you could run linux or mac and just forget about silly things like this...

that is my solution.

with "blaster", and "sobig.f" these last weeks -- I've no worries.  I run a
firewall on my machines, I know exactly what traffic goes back and forth.
Nothing comes in or goes out that I didn't permit.

sobig.f was annoying cause it caused people to send me about 500 emails on
tuesday last week.  I'm sure I deleted some (i know for a fact of one) email
that I should have read.  None of it came (surprisingly) from linux or mac's, it
was all windows generated email.

Most annoying -- that silly thing forged headers and sent email "as if it were
from me".

this topic really isn't relevant to "database" stuff...

      Rating: 5
:) Thank
      you  August 26, 2003
Reviewer:
      A reader

      Rating:
      3
password  August 29, 2003
Reviewer:
      vj  from in
Tom,

My Database : 9IR2.

We got to generate the PIN number for the account holders. This has got to be an

8 digit number. Which is the right approach for the generation / cryption.

i came up with something like this. but the overall feeling is exposing the
magic key...

PACKAGE BODY Pk_Encryption AS

--declare the values of the key
v_keystring   VARCHAR2(32):='MYMAGICSTRING';
v_encryptstr  VARCHAR2(2048);
v_decryptstr  VARCHAR2(2048);

--function encrypt encrypts the string
FUNCTION fn_encrypt(i_inputstring IN VARCHAR2) RETURN VARCHAR2 IS

BEGIN
    v_encryptstr := NULL;

    DBMS_OBFUSCATION_TOOLKIT.DES3ENCRYPT(input_string=> i_inputstring,key_string

=>v_keystring,encrypted_string =>v_encryptstr);
--    RETURN UTL_RAW.cast_to_raw(v_encryptstr);
    RETURN v_encryptstr;
END fn_encrypt;

--function decrypt encrypts the string
FUNCTION fn_decrypt(i_inputstring IN VARCHAR2) RETURN VARCHAR2 IS
BEGIN
    v_encryptstr  := NULL;
    v_decryptstr  := NULL;

DBMS_OBFUSCATION_TOOLKIT.des3decrypt(input_string=>i_inputstring,key_string=>v_ke

ystring,decrypted_string=>v_decryptstr);
    RETURN v_decryptstr;
END fn_decrypt;

END Pk_Encryption;

Rgds

          Followup:

don't store passwords in the database, store a HASH

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:95412348059
no muss, no fuss, no chance of theft and no keys to manage.

      Rating: 5
one
      tip  September 12, 2003
Reviewer:
      juancarlosreyesp@yahoo.com  from Bolivia
Recently we found a program called something like sahaagent, a user when
entering to some page, accepted to be installed.
The point is that when this was installed all the tns comunication failed,
nobody could get what it was about, the machine was reinstalled, it works for a
time until the user did the same, until my friend found this software installed,
and to try desinstalled it, and all tns communications worked normal.

August 29, 2003 ben -- Thanks for the question regarding "cache
      hit question.", version

      You Asked
Hello Again!
Question

We have a 8CPU DEC 8400 (UNIX)server
4Gb Memory. heavy throughput OLTP, banking system. Running
7.3.4.
(65Gb instance) ARCHIVELOG mode

We have had a low Buffer Cache Hit Rate (eg <85%)
We have recently added 2 Gb more server memory.

I have added more buffers, we now have 200000 (Hit Rate is 90%),

how excessive is this figure? Compared with other sites?
I intend to add more, as the last increment has improved the
Rate.

The UNIX team tell me that I have plenty more memory to
utilize....

Should I consider changing any other INIT.ORA para to cateer for

such a large data cache! (Note: All other SGA hit rate are
>99.9%)

Thanks

      and we said...
whats the block size, is this a 390meg block buffer cache (2k)
or 781meg (4k) or 1.5gig  (8k) cache?

On an oltp system probably should be 2k blocksize so I'm
assuming 390meg which is smallish.  Also, don't forget OLTP by
its very nature, would have a low cache hit ratio.

o OLTP is characterized by creating lots of new data.  you
cannot have buffered what you never had.  after i create the
data -- i never read it again (or at least I never read it
frequently again)

o OLTP is characterized by lots of scattered reads here and
there that are never read again (eg: Hey customer 1234 just
called, pull up their record (won't be in the buffer so thats a
miss).  Now customer 124322 called, pull up theirs (won't be in
th buffer so thats a miss).  and so on.  You read a lot of
single rows ONCE and don't come back to them again for hours or
days (if at all).  Only when you re-read the same information
lots will your cache hit go up.

o how often do you bound this instance?  that'll kill these
numbers.  you have to look at a weeks worth of work or so to
really compute a good hit ratio.

The tried and true method would be to add block buffers until
performance decreases.  Look at other factors as well ( i love
utlbstat/estat reports, they give a good, quick aggregate view
of the entire system ).

Review &
      Followup

      Rating: 3
Hit
      Ratio  March 27, 2002
Reviewer:  Brigitte
       from Germany
What means this difference of cache hit ratio:

 SELECT 1 -(phy.value / (cur.value + con.value)) "CACHE HIT RATIO"
 FROM v$sysstat cur, v$sysstat con, v$sysstat phy
 WHERE cur.name = 'db block gets'
 AND con.name = 'consistent gets'
 AND phy.name = 'physical reads'
 /

 CACHE HIT RATIO
 ---------------
      .993579918

 SELECT name,
 1-(physical_reads / (db_block_gets + consistent_gets)) "HIT_RATIO"
 FROM v$buffer_pool_statistics
 /

 NAME                            HIT_RATIO
 ------------------------------ ----------
 KEEP                           .999882139
 DEFAULT                        .687961184

Thanks

          Followup:

one shows overall hit.

the other shows the hit by buffer pool.

      Rating: 1
hit
      ratio  March 27, 2002
Reviewer:  Brigitte
       from Germany
Thanks for your quick response.

from another database:

 CACHE HIT RATIO
 ---------------
      .995902728

 NAME                            HIT_RATIO
 ------------------------------ ----------
 KEEP                           .992088486
 DEFAULT                        .994330993

the hit ratio over the 2 buffers together is better than each hit ratio ??

          Followup:

show the RAW numbers involved, don't just show the results -- give us the
numbers used in the formula.  then maybe we can explain easier.

      Rating: 5
Cynical hat
      on....  March 27, 2002
Reviewer:  Connor
       from UK
Anyone wishing to improve their hit ratios can download the "choose_hit_ratio"
procedure on my web site (www.oracledba.co.uk).  You just type in the hit ratio
you want and away it goes...

(This is a way of saying to readers that relying solely on hit ratios is
probably not such a great way of tuning a database)

Cheers
Connor

      Rating: 3
cache hit
      ratio  April 15, 2002
Reviewer:  Brigitte
       from Germany
Hallo Tom,

thanks for your answer. Here is my question in more detail:

V$SYSSTAT yields:

SELECT name, value
FROM v$sysstat
WHERE name in ('db block gets', 'consistent gets', 'physical reads');

NAME                                      VALUE
------------------------------ ----------------
db block gets                        10,388,820
consistent gets                   1,177,714,359
physical reads                       73,862,473

SELECT 1 -(phy.value / (cur.value + con.value)) "CACHE HIT RATIO"
FROM v$sysstat cur, v$sysstat con, v$sysstat phy
WHERE cur.name = 'db block gets'
AND con.name = 'consistent gets'
AND phy.name = 'physical reads';

CACHE HIT RATIO
---------------
     .937831601

I found in a book:

def p=sum(decode(statistic#,40,value,0))
def i=sum(decode(statistic#,39,value,38,value,0))
def h=(1-(&p/&i))*100
select &p physical, &i logical, &h "hit rate",
decode(sign(&h-70),-1,'increase db_block_buffer',
decode(sign(&h-95),1,'decrease db_block_buffer','okay')) comments
from v$sysstat;

    PHYSICAL        LOGICAL   hit rate COMMENTS
------------ -------------- ---------- ------------------------
  73,862,494  1,188,103,400 93.7831594 okay

but V$BUFFER_POOL_STATISTICS yields:

select name,DB_BLOCK_GETS,CONSISTENT_GETS,PHYSICAL_READS
from v$buffer_pool_statistics;

NAME         DB_BLOCK_GETS  CONSISTENT_GETS   PHYSICAL_READS
---------- --------------- ---------------- ----------------
KEEP             2,035,375        4,342,206              160
DEFAULT          8,353,444      201,863,015       73,732,666
           --------------- ---------------- ---------------
Summe           10,388,819      206,205,221       73,732,826

SELECT name,
1-(physical_reads / (db_block_gets + consistent_gets)) "HIT_RATIO"
FROM v$buffer_pool_statistics
WHERE db_block_gets + consistent_gets > 0;

NAME        HIT_RATIO
---------- ----------
KEEP       .999974912
DEFAULT    .649253601

SELECT 1-(sum(physical_reads) / (sum(db_block_gets) + sum(consistent_gets)))
"HIT_RATIO"
FROM v$buffer_pool_statistics
WHERE db_block_gets + consistent_gets > 0;

HIT_RATIO
----------
.659580541

It would mean the default buffer cache is not okay.
But there is a big difference in consistent gets between
v$sysstat (1,177,714,359) and v$buffer_pool_statistics (206,205,221).

Brigitte

      Rating: 1
Buffer cache
      hit ratio  June 17, 2002
Reviewer:  Brigitte
       from Germany
Hallo Tom,

please could I get a response to my question from April 15, 2002?

Thanks

          Followup:

it is obviously an issue with the consistent gets in the v$buffer_pool dynamic
view vs v$sysstat.  what more could be said?  Look at the raw numbers -- it
seems somewhat obvious where the discrepency is.  Feel free to contact support
for assistance.  It should be noted that in various 817 release -- the counts in
v$buffer_pool_statistics will be zero for these columns (there was a performance
issues with regards to this).  They added it back in -- but the numbers still
differ.

<flame = low>

Also -- you must bear in mind that (as the review page says), this is not the
best place to ASK a question -- I see perhaps 90% of the followups but never
100%.  I read through them as I have time.  They are NOT handled like a question
would be (where I have an interface to be notified when it is updated and such).

You cannot expect a response 100% of the time in this interface.  Save it -- ask
it as a question -- and then you can.

Also, there are great resources like:

o
http://otn.oracle.com/
discussion forums
o
http://www.google.com/
under discussions, look at the comp.databases.oracle.* groups.  The
usenet newsgroups are a GREAT resource.

Heck, if you goto google and search on v$buffer_pool_statistics consistent_gets

http://www.google.com/advanced_group_search?hl=en
you'll actually see a discussion on this very topic.

<flame = off>

      Rating: 5
cache hit
      ratio  June 20, 2002
Reviewer:  Brigitte
       from Germany
Thanks for your answer and the links

      Rating:
      4
  May 13, 2003
Reviewer:  mike
       from sa
"
OLTP is characterized by lots of scattered reads here and there that are never
read again (eg: Hey customer 1234 just called, pull up their record (won't be in

the buffer so thats a miss).  Now customer 124322 called, pull up theirs (won't
be in th buffer so thats a miss).  and so on.  You read a lot of single rows
ONCE and don't come back to them again for hours or days (if at all).  Only when

you re-read the same information lots will your cache hit go up.

"

Tom, what's about to cache the data/tables in db-buffer? could that help the
performance?

          Followup:

don't understand the context of your question " what's about to cache the
data/tables in db-buffer? could that help the
performance? "

      Rating:
      4
  May 14, 2003
Reviewer:  mike
       from sa
don't understand the context of your question " what's about to cache the
data/tables in db-buffer? could that help the
performance? "
--------------------------

I meant if we cache the tables with the records have been used often, then you
do not have the misses (have to get them from disks). Can that improve the
performance and have high the ratio of buffer hits as well? If not , then that's
the problems of that?
Thanks

          Followup:

IF you have finely tuned queries
THEN a good cache hit ratio is something you might expect
MAYBE

still not getting the gist of your question...

does it take less time to get something out of the buffer cache then from disk?
probably...

does a good cache hit ratio mean you are tuned?  100% absolutely not.  In fact,
it can just as easily mean your not tuned at all, all of the way tuned -- or
anything in between.  It is meaningless as a measurement of "tuned"

      Rating: 4
BUFFER HIT
      RATIO  August 29, 2003
Reviewer:  Deepak
      Gupta  from delhi ,India
Dear Mr. Tom,

One day at a sudden , My database buffer hit ratio come down to 38% , it  used
to generally above 90% .

why it happend ?  what action immediate can take ?

my  database Oracle8 Release 8.0.4.0.0 - Production
o/s: win nt

SQL> select  sum(decode(NAME, 'consistent gets',VALUE, 0)) consistent_Get ,
  2   sum(decode(NAME, 'db block gets',VALUE, 0)) db_blk_read,
  3   sum(decode(NAME, 'physical reads',VALUE, 0)) phy_read,
  4   round((sum(decode(name, 'consistent gets',value, 0)) +
  5          sum(decode(name, 'db block gets',value, 0)) -
  6          sum(decode(name, 'physical reads',value, 0))) /
  7         (sum(decode(name, 'consistent gets',value, 0)) +
  8          sum(decode(name, 'db block gets',value, 0))) * 100,2) hit_ratio
  9  from   v$sysstat
 10  /

CONSISTENT_GET DB_BLK_READ  PHY_READ HIT_RATIO
-------------- ----------- --------- ---------
       5757158      342459   3737028     38.73

thanks

          Followup:

q) why it happened?
a) you did more physical IO.

q) what action to take?
a) reduce physical IO if and only if you have actually identified this to be a
problem.

are you having a performance issue or are you bothered by some tool blinking red
saying "warning warning your cache hit ratio is down"?

The solution is to determine WHY you are suddenly doing more PIO then usual.
what changed (please, don't say "nothing", something did)

August 29, 2003 Om -- Thanks for the question regarding
      "Detecting dead sessions ", version 8.1.6

      You Asked
Hi Tom,

Thanks very much for the examples on BIND variables.I have now
understood the importance of these variables.

Can you please send me some stuff or a link towards some good
pages which can explain jow we can detect dead connections in
ORACLE ?

I saw few links but none of them seems to be complete or useful ?

Warm Regards,
Om

      and we said...
Dead client Detection (DCD) explained

http://technet.oracle.com/doc/oracle8i_816/network.816/a76933/concepts.htm#997825
DCD setup (sqlnet.expire_time setting in the sqlnet.ora on the SERVER)

http://technet.oracle.com/doc/oracle8i_816/network.816/a76933/params.htm#437583
....

Review &
      Followup

      Rating: 5
Detecting dead
      sesions  October 27, 2001
Reviewer:  Om
       from INDIA
I got exactly what I was looking for.You are
simply great Tom.The ORACLE world in IT just
cannot do anything without people like you.

Many Thanks
Om

      Rating: 5
Great
      !  October 29, 2001
Reviewer:  Andre Whittick
      Nasser  from Rio de Janeiro, Brazil
Thanks Tom, that was one of the "blind spots" I had in mind regarding
connections.

By the way... When you kill a user, its status is marked KILLED in V$SESION.

I was told, or read somewhere that, from time to time, PMON wakes up to clean
these entries. But I had a system running during weeks and the entries were
still there.

1) Is that true ?

2) Is there any way to really remove those "dirty" entries ?

3) What is the bevahior of Dead Client Detection towards the entry in V$SESSION.
Is it marked KILLED ?

Thanks again !

          Followup:

1) no.  PMON will roll them back.  PMON will not make them "go away".  When they
are killed, their resources (locks) have been released but they are still
technically connected.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1058832409881
they stay connected until their client application tries to access the database
at which point they get the "your session has been killed" error and the session
goes away then.  Otherwise, they would get something like ORA-3113 EOF on
communication channel or "packet write failure" -- rather nasty looking errors.

2) not any official way -- when the client session touches the database ,they
are removed.

3) With DCD, the session is cleaned up is the CLIENT is no longer there.  If a
killed client is still there, DCD won't do anything (PMON has already released
the resources, nothing to do)

      Rating: 3
DCD not
      working.  February 08, 2002
Reviewer:  Arindom
      Kanti Dam  from Boston, MA USA
Hello,
I am Sun Solaris 8 , 8.1.7.0.0
Have tried getting DCD to work ; both with MTS and dedicated server mode.
Have these entries in my sqlnet.ora:

sqlnet.expire_time=1

trace_level_server=16

trace_directory_server=/disk01/oracle/product/8.1.7/network/trace

However on reastarting the listener; the following on the *.trc files shows :

grep -i dead *.trc
svr_2051.trc:niotns: Dead connection detection being turned off.

Also noticed that 'dead' client connections are not being cleaned. It is a pain
; though managable in a direct connect mode ; though a showstopper in MTS....

Is this something version specific or does DCD really work?

Thanks.

          Followup:

1) make sure you are using the right sqlnet.ora
2) I tried this on 8172 on solaris and cannot reproduce at all. only when I
don't have the expire time set do I get

/tmp/svr_13616.trc:niotns: Not enabling dead connection detection.

Perhaps it was an issue with 8170 and patching it to the current level will
correct it.

In fact, there was a known issue with DCD and MTS that was fixed in 8171 (it did
not work properly, would not get all of the sessions)

      Rating: 4
What are these
      process ??  February 09, 2002
Reviewer:
      A reader
Hi Tom,

Apart from the BG process Running in my System i have got two process which
mimic the BG process in the sense that the username, OSuser all are null.

But the lOGON_Time Of These sessions are Geting updated to the current time...

What are these process ???

Thanks.

Regards,
Ganesh Raja

          Followup:

Sorry, my crystal ball is in the shop this week and visualizing the other needed
information (like the PROGRAM name for example among others  like OS, versions,
etc etc, etc) is a little hazy.

      Rating: 3
Sorry About
      That ...  February 10, 2002
Reviewer:
      A reader
Hi Tom,

Sorry about that ...

Should have given u more information.

OS : WIN 2000 Sp2
Oracle : 8.1.7.2

Output from v$session

       SID    SERIAL# LOGON_TIME          PROGRAM              STATUS
---------- ---------- ------------------- -------------------- --------
         1          1 09.02.2002 17:05:33 ORACLE.EXE           ACTIVE
         2          1 09.02.2002 17:05:33 ORACLE.EXE           ACTIVE
         3          1 09.02.2002 17:05:34 ORACLE.EXE           ACTIVE
         4          1 09.02.2002 17:05:34 ORACLE.EXE           ACTIVE
         5          1 09.02.2002 17:05:35 ORACLE.EXE           ACTIVE
         6          1 09.02.2002 17:05:36 ORACLE.EXE           ACTIVE
         7      22345 10.02.2002 09:02:47                      ACTIVE
         8      22345 10.02.2002 09:02:47                      ACTIVE
         9          1 09.02.2002 17:05:36 ORACLE.EXE           ACTIVE
        10      20193 10.02.2002 08:47:13 sqlplus.exe          ACTIVE

As i told u the other values like the OSUSER, TERMINAl, MACHINE and all are
null..

What are these...

Again a Sorry for the previous Post !!!

Regards,
Ganesh R

          Followup:

do you have job queue processes = 2?  Those look like they might be the snp
processes/threads.

      Rating: 5
Thanks Tom
      !!!  February 10, 2002
Reviewer:
      A reader

      Rating: 5
From time to
      time  September 05, 2002
Reviewer:  Alex
       from Brazil
A simple doubt:
Oracle Documentation (Oracle Concepts) says:
"Like SMON, PMON wakes up regularly to check whether it is needed, and can be
called if another process detects the need for it."What does "regularly" mean?
Every seconnd? Milisecond?

Thank you very much!!!

          Followup:

about every 3 seconds.

      Rating: 5
no
      sqlnet.ora  September 05, 2002
Reviewer:
      atul  from India
Sir,

I am using 8.0.4 and would i be able to set DCD for this version..

ALSO i didn't found sqlnet.ora in my server..
Only Tnsnames and listener files are present...

So how anyone can connect without sqlnet.ora?
And if i have to set DCD have i have to create sqlnet.ora?

Thanks.
atul

          Followup:

Yes, DCD was in 8.0

sqlnet.ora is an OPTIONAL file.

yes, you would create it.

      Rating: 3
Detecting dead
      connections and Database shutdowns  December 30, 2002

Reviewer:  Nihal  from s'pore
Tom,
We are using Oracle 9.1, on windows NT which at times when shuting down normally
(using immediate option) gives waiting for active calls errors, on investigation
we have concluded that this is due to oracle making calls (selects) to
non-oracle systems using 'hsodbc' and if the non-oracle system is down the
session hangs, since all calls to non-oracle systems start a transaction,
rollback segment is used even for selects, which is not being rolled back as the
session is hung state and the database cannot be brought down with the immediate
option with a session in hanged state.
we have to use abort option to shutdown the database in such situations.
If we use DCD whats are the chances that the database will go down smoothly,
i.e. if the database is being brought down with immediate option, will DCD be
used on hanged/dead connections to rolled them back. I mean does it come in play
when the database is in the process of being shutdown but can't shutdown as some
sessions is hung up.
We are also using Jobs to do some batch processing, does DCD in any way affect
job processes.

As far as the need to shutdown goes, We do need to shutdown the database
everyday for Backup, Company policy is for cold backup and it will remain so
untill the disastor and recovery using hot backups have been throughly tested by
the org, which can be quite a few months

Thanks in advance

Nihal

          Followup:

You'll have to test -- i've no experience with that.

I doubt it would work - DCD is for client connections - the server pings the
client.  With hsodbc -- the server is the client.  there is no Oracle net on the
other end to ping back -- they do not do our protocol.

Company policy is STUPID.

Cold backups & Hot backups in archive log mode are *the same*.  you take the
*same* exact steps to recover with them.  if this takes months -- well -- don't
know who you are working for but anyway....

      Rating: 2
Detecting dead
      connections and database shutdowns  January 02, 2003

Reviewer:  Nihal  from s'pore
Hi Tom,
Is there a way to detect if the DCD has been enabled, i have put the expire_time
in the sqlnet .ora, but how do i asertain that what i have done is right, what i
need to know is that is there a command which can be executed in sqlplus or thru
lsnrctl which will tell us that expire_time is 1 minute.

The connection architecture is as follows Using jdbc to connect to Oracle which
in turns connects to remote DB using hsodbc or using pl/sql to connect to Oracle
which in turn connects to hsodbc, there is a client in both the cases which is
not the server that will execute the sql statement, for the remote db Oracle
server is the client, the lock is at the oracle database level hence if the
client (Java/plsqlplus does not respond ) then oracle should rollback the
process.

          Followup:

kill the client.

See if the process in the database goes away.

      Rating:
      5
Perfect  June 25, 2003
Reviewer:
      Evan  from Georgia
Dead on to what I needed. Thanks

      Rating: 5
EXPIRE_TIME is
      late...  August 29, 2003
Reviewer:
      Cefers.br  from Brasil
Hi Tom,

I´ve configured DCD, but it seems to be working a litle late.

My SQLNET.EXPIRE_TIME is 30 min but in the tests I´ve done, the dead connections
went away after about 50-60 min (quite after the expected).

I´m running Oracle 9iR2 on Linux Advanced Server 2.0. The tests were done in
clients running on Win 98 (Oracle Client 8) and Win XP (Oracle Client 9).

My tests were just like that:

* Connect on SqlPlus
* Powerdown the client machine (direct in the power button)
* Trace the dead client session in V$SESSION every 5 minutes, until it´s gone

My guess is there is some kind of problem between Windows clients on Linux
Server.

Do you have any idea? Have you seen this before?

Once again, thanks very much.

          Followup:

everything is done in "big windows of time"..

solution for you would be to specify a significantly smaller window of time, 5
or 10 minutes is usually sufficient and it will very much be "oh, around 5/10
minutes give or take"

August 29, 2003 San -- Thanks for the question regarding "Standby
      Database", version 8.1.5

      You Asked
Hi Tom,

I am in the process of setup the standby database for my
production database. I have gone through with couple of
doucments. Most of the things are clear to me, but still there
are some comfusion:
1. Do I need to have totally separate hardware (Harddisk,
controller etc.) or Can I create standby database om same
hardware also?
2. Do I need to run my primary database only archive mode or can
I run into noarchive mode also and use standby database
utility. Same question for standby database also "In what mode
(Archive/noarchive)" I need to run standby database so I can
switch over whenever its required.

3. In one of the standby database documentaion I have seen "The
standby database is kept constantly in recovery mode.", and my
question is What do you mean by recovery mode?

4. Do I have to set following initialization parameters at
standby databse site:
-db_file_standby_name_convert  =  ("/dbs/","/dbs/s")
-log_file_standby_name_convert  =  ("/dbs/","/dbs/s")

If yes why not I have to set those parameter at primary database
site. What are the exactly use of those parameters and what
are the different and default values are for above mentioned
initialization parameters.

5. While performing Switchover from primary to standby database:
1. I have to apply all archived redo logs to the standby
database.
2. Issue the CANCEL command to halt recovery.

and my question is when to issue cancel command? How do I know
that?

Thanks
San

      and we said...
1) you can but it is POINTLESS.  A standby database is for a
catastrophic failure -- e.g. the computer with the database on
it got burned up in a fire.  The machine is gone.  The disks all
exploded.

That would be like keeping your backup on the same disks as the
original copies -- it would not do very much for you.

We support running a standby on the same machine for testing
purposes only.  In production, the standby should have its own
machine, own processors, own disk and would most likely be on a
different network alltogether.

2) A production database by definition will run in ARCHIVELOG
mode -- regardless of whether you are using standby or not.  If
you do not run in this mode, you must be willing to LOSE DATA at
some point in time.

That aside, a standby database requires archivelog mode.

All production machines must run in archive log mode else they
will lose committed transactions the instant a disk fails (and
that should happen about once a year in many systems)

3) it is constantly apply archived redo logs from the primary
database as they are generated -- it is recovering the
transactions from the redo log.

4) no, not if the file system setup is the same.  They allow you
to have different mount points on the standby site.  They simply
translate file names from the primary site into file names on
the standby.  You might have Oracle stuff in /u01/oradata on the
primary but in /u25/oradata on the standby.

5) after you've had the last log applied from primary, issue the
cancel recovery command.  We've no more logs to apply so its
time to tell us to stop waiting for more, cancel recovery.
Review &
      Followup

      Rating: 4
Feedback
      Requested  April 18, 2003
Reviewer:  Saminathan
      Seerangan  from CA,USA
Dear Tom,

1.Why DB Link is not working is standby database?

I have created DB LINK in primary db and the link does exist in standby
db-DBA_DB_LINKS table. But when i issue a select statement like

SQL> select * from employees@tamizh_dblk;
select * from employees@tamizh_dblk
              *
ERROR at line 1:
ORA-16000: database open for read-only access

I'm just selecting, i'm not updating anything. Could someone explain to me why
DB link access is not allowed?

Could you please provide me list of skipped SQL Statements on a Physical Standby
Database?

2. I did setup physical standby DB without temporary tablespace. Do i have to
create
TEMPORARY tablespace? Is it mandatory?

My understanding is that if I go for large SORT operation(in read only mode),
standby DB should have temporary tablespace. Correct?

3.The ROWID between PRIMARY and physical standby DB will be always the same.
Right?

Expecting your valuable response as always.

          Followup:

1)

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:8908307196113
(searched for ora-16000)

The documentation is awesome in this regards -- you haven't given a version but
it is all on OTN and the considerations you need to take into account about
statements against a physical standby are all there.  Assuming 817:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76995/toc.htm
and check out chapter 4!

2) see

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76995/standbyr.htm#27494
mandatory -- no, almost always necessary -- yes.

3) correct.  they are PHYSCIAL standbys -- bit for bit copies.

      Rating: 4
additional info
      for question # 2  April 18, 2003
Reviewer:
      Saminathan Seerangan  from CA,USA
Tom, How come TEMP tablespace appears without datafile in physical STANDBY DB?

SQL> connect sys as sysdba( connected to STANDBY DB)
SQL> select tablespace_name,status from dba_tablespaces;

SYSTEM                         ONLINE
UNDOTBS1                       ONLINE
<b>TEMP                        ONLINE</b>
DRSYS                          ONLINE
INDX                           ONLINE
TOOLS                          ONLINE
USERS                          ONLINE
XDB                            ONLINE

8 rows selected.

SQL> select tablespace_name,file_name from dba_data_files where tablespace_name
like '%TEMP%';

no rows selected

SQL>

          Followup:

temp files are in dba_temp_files

      Rating: 4
Cross Platform
      Stnadby Database  April 18, 2003
Reviewer:
      Randy  from Chicago,US
Tom,

I am taking this opprotunity to ask you ,if it is possible to have standby
database in windows box for a production database in Unix box. I know the
archivelogs are not platform independent, at least was not in 8i. Just wonder if
it's possible in 9i or coming 10i ? Do you have any insight about that ?

Thanks

          Followup:

standby's are totally -- 100% "homogenous".

In 9iR2 you can use Streams to have a reporting instance as above, which loosely
could be considered as such -- but it wouldn't really be a standby in the normal
sense.

      Rating: 3
Cross Platform
      Standby Db  April 18, 2003
Reviewer:
      A reader
Can you explain little more what Stream is ? It's also not very clear what you
meant by reporting instance. What's the similarities it has with Standby db ?

          Followup:

all about streams:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96571/toc.htm

A standby database (logical or physical) can be used as a reporting instance
(run reports).

You can build a reporting instance as well using streams (a copy of the data to
be used to run reports).

A standby can be a reporting instance.

In a pinch, sometimes, depending on your needs, a reporting instance could be
considered a "last ditct fail over site".

      Rating: 5
Logical Standby
      and Streams  April 18, 2003
Reviewer:  Logan
      Palanisamy  from Santa Clara, CA USA
Tom,

You said

"In 9iR2 you can use Streams to have a reporting instance as above, which
loosely could be considered as such -- but it wouldn't really be a standby in
the normal sense"

Isn't that called "Logical Standby" database in 9iR2?

I guess Logical Standby uses the Streams technology. The Streams technology
could be used for other things as well, besides Logical Standby. Am I correct?

          Followup:

No, there is a logical standby -- automated, out of the box -- but HOMOGENOUS.
It is a COMBO of streams + data guard (lgwr shipping redo -> another machine,
thats data guard, then streams converting that into logical change records ->
sql apply)

streams by itself would be very different.  You would lose the data guard
component and be set up for potential data loss in the event of a production box
failure.  but it can be hetergenous.

      Rating: 4
hidden archive
      logs  April 30, 2003
Reviewer:  Alvin
       from Philippines
One of our standby db's had to be discontinued because of network problems.

I ran the following command :

alter system set log_archive_dest_2=defer

when the network was ok i enabled the earlier command i copied the gap sequence
missing re-applied the logs.

But the funny thing is the archive logs becomes hidden or invisible after it has
been applied and can only be seen by the unix command:

du -a

but if you list (ls -al arch_1_xxxx.arc) a specific archive log it looks fine
and regular.

(???) i always get the weirdest error! karma ? <-- pls. ignore ranting.

          Followup:

umm, what command is "missing them" then - seems that ls sees then, du sees
them?

(knowing what unix you be on would be useful as well)

      Rating:
      2
  May 02, 2003
Reviewer:  Alvin
       from Philippines
I ran the list (ls) command...

/export/home/oracle/arch-> ls
/export/home/oracle/arch-> ls
/export/home/oracle/arch-> ls -a
/export/home/oracle/arch-> ls -al
total 4568816

i don't see any files.
but if i ran 'du -a' i get...

1024    ./arch_1_64452.arc
1024    ./arch_1_64453.arc
1024    ./arch_1_64454.arc
1024    ./arch_1_64455.arc

/export/home/oracle/arch-> uname -a
SunOS <server_name> 5.8 Generic_108528-11 sun4u sparc
SUNW,UltraSPARC-IIi-cEngine

when i transfered the gap sequence the files seems to be visible to 'ls' command
but i think after applying the logs it became invisible ? do you think oracle
has something to do with it ? i did check if the oracle-user owns the
subdirectory and oracle does.

my Unix admin guy is as boggled as i am.

          Followup:

this would be a question for Sun really -- I am not able to reproduce through
any concocted test of my own on solaris

      Rating: 4
To refresh or
      not to refresh the CF  May 11, 2003
Reviewer:
      Alvin  from Philippines
After adding 5x2mb worth of logs i'm still getting the cannot allocate logs
warnings... the last time i did it i refreshed the controlfile of both
standby's.

I'm planning to add more but after browsing through the standby docs of 8i i
get...

<cut from docs>
Chapter 4 - Performing Maintenance on a Standby Database

Adding or Dropping Redo Logs on the Primary Database
You can add redo log file groups or members to the primary database without
affecting the standby database. Similarly, you can drop log file groups or
members from the primary database without affecting your standby database.
Enabling and disabling of threads at the primary database has no effect on the
standby database.

Consider whether to keep the online redo log configuration the same at the
primary and standby databases. Although differences in the online redo log
configuration between the primary and standby databases do not affect the
standby database functionality, they do affect the performance of the standby
database after activation. For example, if the primary database has 10 redo logs
and the standby database has 2, and you then activate the standby database so
that it functions as the new primary database, the new primary database is
forced to archive more frequently than the old primary database.

To prevent problems after standby activations, Oracle Corporation recommends
keeping the online redo log configuration the same at the primary and standby
databases. Note that when you enable a log file thread with the ALTER DATABASE
ENABLE THREAD statement at the primary database, you must create a new control
file for your standby database before activating it. See Refreshing the Standby
Database Control File for procedures.
<end cut>

Can i add extra log files to the primary and not refresh the standby control
file ?? reading from the text above it seems like  oracle "recomends" (not
require) refreshing the standby controlfile so the standby would perform like
the primary. But i feel that i can add redologs without refreshing the Standby
CF.

          Followup:

you can add them without refreshing the standby, the caveat they are talking
about is "hey, if you activate -- fail over -- to the standby, the tuning you
did for the redo logs won't be over there, they will not be sufficient perhaps
to handle your load, so maybe you want to keep them the same"

You can always add more logs to the standby if/when you failover.

      Rating: 5
init ora
      compatible parameter  May 13, 2003
Reviewer:
      Alvin  from Philippines
Greetings Sir,

After reading the concurrency part of your book... I tried increasing the
freelists of my table and i got this message.

ORA-00406: COMPATIBLE parameter needs to be 8.1.6.0.0 or greater

besides standby databases what other module/s or technology would be affected ?
(our frontend is developed in java)

          Followup:

have a backup just in case (good idea regardless) but changing compatible
upwards is generally "safe".  I'm not aware of any thing other then standbys and
RAC.

In general, once it is set "up", it cannot be set "back down"

      Rating: 5
accidentally
      hit the submit..  May 13, 2003
Reviewer:
      Alvin  from Philippines.
what other modules would be affected when i change the compatible settings in
the ini.ora files ?

      Rating:
      4
SID's  June 16, 2003
Reviewer:
      Alvin  from Philippines
Can the Primary Db's SID be the same with the standby's SID ? or it is required
that i change it ?

I get this error from the v$archive_dest view.

rtbe.standby3
17-JUN-03         71422          0 ORA-16032: parameter  destination string
cannot be translated

i rechecked the init.ora file and the db_file_name_convert and the
log_file_name_convert seems to be correct.

Would it be better for me to use the (Service_name=....) in the primary's
tnsnames.ora or (SID=....)

Standby's Parameter
====================
OS configs ORACLE_SID=rtbe (same with the primary's)

init params
===========
db_name = "rtbe"
db_domain =standby3
instance_name = rtbe
service_names = rtbe.standby3

          Followup:

[tkyte@localhost tkyte]$ oerr ora 16032
16032, 00000, "parameter %s destination string cannot be translated"
// *Cause:  The value for the specified parameter contained a destination
//          string that could not be translated.
// *Action: Use a valid destination string in the specified parameter.

you have a simple configuration issue here.  the sid is OK.  your net
configuration is not.

      Rating: 5
Pls don't
      answer the above post !!!!  June 17, 2003

Reviewer:  Alvin  from Philippines
It was the standby_dest_archive param...

i put in the standby_dest_archive ='Location=....' i removed it and the standby
is now recieving archive files.

Sorry !

      Rating: 5
stand by
      db  June 30, 2003
Reviewer:  A reader
       from Houston
Tom,

We are looking at copying data from a 24 x 7 production box(box 1) to a
different box (box 2).  Box 2 will have the excat same copy of data in real time
(almost real time).  de-normalized views will be built on Box 2 and Box 2 will
then be used for datawarehousing purposes.

To transfer data from box 1 to box 2, we are looking at using shareplex,  a tool
from quest.  Shareplex copies redo logs from box1 to box 2.

My questions
1) Is there a way in Oracle to move all the data from box 1 to box 2 in almost
real time? (standby db!)
2) For keeping the standby db in synch with the primary db, is there any
advantage using the archive redo logs?
Why can't we use the redo logs instead of the archive redo logs?
3) Do Oracle Streams in 9i come into picture here anyway?

Thank you

          Followup:

if you have 9iR2, you already own something that does all of that and more:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96653/standbycon.htm#1013849

      Rating: 5
stand by
      DB  June 30, 2003
Reviewer:  A reader
       from Houston
Tom,
Do we have the concept of Logical Standby Database (or equivalent) on Oracle 8i?

Thank you

          Followup:

No.  New in 9iR2

      Rating:
      5
Reader  June 30, 2003
Reviewer:
      A reader
Is creating a database in a hetrogenious
platform using shareplex, quest supported by Oracle?

          Followup:

shareplex is supported by quest, not by Oracle.

      Rating: 5
Patch Standby
      DB first...  July 01, 2003
Reviewer:  Alvin
       from Philippines
I have an OLTP DB version 8.1.7.0.0 and we wanted to check the duration or
downtime we would incur if we patched it to 8.1.7.4.0

So my boss asked me if we could patched up the standby db first to see how long
it would take before patching up production. Oracle Docs states that OS, DB
vers, Directory structures should be the same in a standby db environment.

Is this safe ? patching up standby's before the production ?

          Followup:

this is what "test" is for.  do your "test" or "development" machine - since you
of course need to TEST your application after the upgrade.

It seems you want HA (high availability).  That is why you are running a standby
server.  Performing an upgrade without testing it would sort of defeat your
goals.

      Rating:
      4
clarification..  July 01, 2003
Reviewer:
      Alvin  from Philippines
Does that means that i can go ahead and patch up my standby DB to 8.1.7.4.0 and
open it as read only to see if it's ok ?
Then if it is i can go ahead and patch the production server ?

How do i check if its ok ? via Trace logs ?

      Rating: 5
i meant
      development server this time  July 02, 2003

Reviewer:  Alvin  from philippines
I get your point. Standby for HA.

How would i know if after patching the "DEVELOPMENT" server that everythings a
ok ?

Via trace logs ? if it starts up ok ? is there a table that i should query ?

          Followup:

you would test your application?  you would have an environment that looks like
production, you would upgrade it, you would test your feature/function set --
make sure everything works.

You know, what you do before inflicting a new release of your applications on
the unsuspecting end users.

      Rating:
      4
Doubt  August 08, 2003
Reviewer:
      jignesh  from india
In order to keep a standby database up-to-date, archived redo-log files need to
be transferred from the primary to the standby server. Two ways to do this:--

1 Use operating system commands to transfer the files.
2 Set the STANDBY_ARCHIVE_DEST parameter on the standby database

I am not clear with 2nd point.  help plese

          Followup:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76995/toc.htm
read about the standby database.

      Rating: 3
A use for
      Oracle Streams?  August 11, 2003
Reviewer:
      Matt  from Australia
I just saw your reference to Oracle Streams. This looks good!

I was wondering whether it would be possible to use this technology to identify
the data changes in a DB, and then automatically generate the SQL that produced
these changes.

With this SQL it would be possible to accurately mimic the data changes in a
production environment to examine database performance. All that would be needed
to reflect real usage would be to codify pauses between statement execution. The
result would be a production data load simulator - but without having to write
something to interpret raw trace files.

Thanks in advance.

          Followup:

You can use this to automatically generate row by row, equivalent sql.

that is, in production you issued:

   update t
      set x = 5
     where y = 10;

that updated 100 rows.  with streams, you could generate 100 updates, a row at a
time, that did the same thing.

You would not be able to gauge performance in this fashion as the "query"
portion of the update is effectively gone.  Your complex selects included in the
modification dml is "gone".

      Rating: 4
Streams
      problem?  August 29, 2003
Reviewer:  Sasa
       from Belgrade, Serbia
Hi Tom,

We created streams between two DBs (through Oracle enterprise manager console
who creates setup scripts) and during running setup sripts(part for adding table
rules) we got an error that capture process does not exist although doc claims
that capture process (if does not exist) will be created during adding table
rules.
I will copy/paste this exception.

BEGIN
 dbms_streams_adm.add_table_rules(
   table_name => '"MMFIXEDODDS"."FOSPORTTYPE"',
   streams_type => 'CAPTURE',
   streams_name => 'STRMADMIN_CAPTURE',
   queue_name => '"STRMADMIN"."STREAMS_QUEUE"',
   include_dml => TRUE,
   include_ddl => TRUE,
   source_database => 'SJAWU');
END;
/

error AT line 1:
ora-26701: streams process strmadmin_capture does NOT exist
ora-06512: AT "SYS.DBMS_STREAMS_ADM", line 75
ora-06512: AT "SYS.DBMS_STREAMS_ADM", line 21
ora-06512: AT line 2

Source DB
----------
Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
PL/SQL Release 9.2.0.3.0 - Production
CORE    9.2.0.3.0    Production
TNS for 32-bit Windows: Version 9.2.0.3.0 - Production
NLSRTL Version 9.2.0.3.0 - Production

Destination DB
--------------
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
PL/SQL Release 9.2.0.4.0 - Production
CORE    9.2.0.3.0    Production
TNS for 32-bit Windows: Version 9.2.0.4.0 - Production
NLSRTL Version 9.2.0.4.0 - Production

Thanks in advance.

          Followup:

did you use a drop user cascade during this setup -- to clean up schemas and
such?

      Rating: 2
Streams
      problem  August 29, 2003
Reviewer:  Sasa
       from Belgrade, Serbia
Yes we did as we haven't found in doc any way for "cleaning" such things.

          Followup:

contact support

i believe it is the same issue as

2378852

Abstract:           Cannot drop APPLY process after DROP USER CASCADE of APPLY
user
Tags:               STREAMS
Details:
  Cannot drop APPLY after DROP USER CASCADE on the APPLY user.
  Eg: - Connect as a user u1 and create an apply process a1
        using an API in the dbms_apply_adm or dbms_streams_adm packages.
      - Start the apply process by calling dbms_apply_adm.start_apply('a1').
      - Connect as user u2 and drop user u1 cascade.
      - Recreate user u1 and grant the same privileges as before.
      - Connect again as u1 and try to drop or create the apply process a1,
        Both fail.
  Workaround:
    Use a new apply process name.

and there does seem to be a patch for 9204

August 29, 2003 --
      Thanks for the question regarding "partitition key in the query...",
      version 8.1.7

      You Asked
Tom

1.Is it necessary that we should include the partiton key in the where clause
for the partition elimination to occur..

2.Is it necessary to include all the columns part of the primary key.. in the
query.. for partition elimination..

or

3.Will it suffice to include only the first column of the partition key in the
query...

Say a partition key has 4 columns , 1,2,3,4

and my query is

select name, something,
from table
where coolumn3='sdsdsds'

Will partition elimination occur in this case..

4.One of my partition bounds is values less than 'TOMKYTE'

Can I insert a value like 'TOMKYTEF' into this partition..

When you use a varchar2 column in the partition key , and say
values less than  'AMERICA' , what will happen , will oracle look at each and
every alphabet, while allowing a value to be inserted..

when partition bound is 'AMERICA',  a value like 'AMERIB' will go into this
partition.. what will happen to a value like ..'AMERIBB'.

TOM, I BOUGHT YOUR BOOK TODAY..IAM SO EXCITED..( it price seems to be very
expensive..59 bucks, but I know its worth).

Bye

      and we said...
1) pretty much, if you don't include at least part of the key, there is NO WAY
to do the elimination...

say you have a table T partitioned on (x)

you query:

select count(*) from t where y = 5;

Well, Y=5 can be in every partition -- hence, it'll check everypartition.

Now, if you have a global index on Y that is partitioned by Y, we'll do index
partition elimination -- so you have the opportunity to do elimination on
different objects....

2) nope, it is not neccessary.

3) sure, for example (just to make it concrete, the proof is in the pudding as
they say):

ops$tkyte@ORA815> CREATE TABLE t
  2  ( cc        varchar2(2) check ( cc in ( 'US', 'UK' ) ),
  3    type_code varchar2(1) check ( type_code in ('A','B','C') )
  4  )
  5  PARTITION BY RANGE (cc, type_code)
  6  ( PARTITION p1 VALUES LESS THAN ( 'UK', 'B' ) tablespace users,
  7    PARTITION p2 VALUES LESS THAN ( 'UK', 'C' ) tablespace users,
  8    PARTITION p3 VALUES LESS THAN ( 'UK', 'D' ) tablespace users,
  9    PARTITION p4 VALUES LESS THAN ( 'US', 'B' ) tablespace system,
 10    PARTITION p5 VALUES LESS THAN ( 'US', 'C' ) tablespace system,
 11    PARTITION p6 VALUES LESS THAN ( 'US', 'D' ) tablespace system
 12  )
 13  /
Table created.

ops$tkyte@ORA815> insert into t values ( 'UK', 'A' );
ops$tkyte@ORA815> insert into t values ( 'UK', 'B' );
ops$tkyte@ORA815> insert into t values ( 'UK', 'C' );
ops$tkyte@ORA815> insert into t values ( 'US', 'A' );
ops$tkyte@ORA815> insert into t values ( 'US', 'B' );
ops$tkyte@ORA815> insert into t values ( 'US', 'C' );

ops$tkyte@ORA815> alter tablespace users offline;
Tablespace altered.

ops$tkyte@ORA815> select * from t where cc = 'UK';
select * from t where cc = 'UK'
              *
ERROR at line 1:
ORA-00376: file 7 cannot be read at this time
ORA-01110: data file 7: '/d03/oradata/ora815/users.dbf'

ops$tkyte@ORA815> select * from t where cc = 'US';

CC T
-- -
US A
US B
US C

ops$tkyte@ORA815>

So that shows that partition elimination must have been taking place.

4) No, TOMKYTEF is NOT less then TOMKYTE:

ops$tkyte@ORA815> CREATE TABLE t
  2  ( c varchar2(20)
  3  )
  4  PARTITION BY RANGE (c)
  5  ( PARTITION p1 VALUES LESS THAN ( 'TOMKYTE' ),
  6    PARTITION p2 VALUES LESS THAN ( MAXVALUE  )
  7  )
  8  /

Table created.

ops$tkyte@ORA815>
ops$tkyte@ORA815>
ops$tkyte@ORA815> insert into t values ( 'TOMKYTEF' );

1 row created.

ops$tkyte@ORA815>
ops$tkyte@ORA815> select * from dual where 'TOMKYTEF' < 'TOMKYTE';

no rows selected

ops$tkyte@ORA815>
ops$tkyte@ORA815> select * from t partition(p1);

no rows selected

ops$tkyte@ORA815> select * from t partition(p2);

C
--------------------
TOMKYTEF

It'll go into some other partition....

It'll do a character string compare, just like ORDER BY will do, or < in SQL.

If the partition bound is AMERICA, then since AMERIB is less then AMERICA and so
is AMERIBB:

ops$tkyte@ORA815> select * from dual where 'AMERIB' < 'AMERICA';

D
-
X

ops$tkyte@ORA815> c/B'/BB'
  1* select * from dual where 'AMERIBB' < 'AMERICA'
ops$tkyte@ORA815> /

D
-
X

AMERIB and AMERIBB could both go into that partition, yes.

(hey the book is only 47.99 on amazon and 45.59 on BarnesandNoble, I never buy
books in the store any more)....

Hope you enjoy it.

Review &
      Followup

      Rating: 3
A question
      about varchar partition key  August 28, 2003

Reviewer:  Arturo Saravia Moya  from Peru
Hi Tom,

I have a table with a column local_time varchar2(14), it stores values like
'20030826110000' ('yyyymmddhh24miss') and I'll partition it like :

partition by range (local_time)
(
partition p_11 values less than ('20030801000000') tablespace datos,
........
partition p_MAX values less than (maxvalue) tablespace datos
)

normally the range partition have a key based on date or number column. What
about a varchar partition key?. Any performance issues?.

Thanx.

pd: I can't change the column type, a legacy system use this table ( grows about
12 millions rows per month).

          Followup:

Arg, why would you not use the proper datatype????  a date!!!!!

worse, you put a number in string!!!!!

I can see you are serious about your database you are using partitioning -- you
are big.  but why -- why would you use the entirely WRONG datatype!!

forget about performance, think about the poor optimizer!

in this table -- str, num, dt all have the "same date".  we run arguably the
same query but look:

ops$tkyte@ORA920> define dt1 = 200303310000
ops$tkyte@ORA920> define dt2 = 200304010000
ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t where str between '&dt1' and '&dt2';
old   1: select * from t where str between '&dt1' and '&dt2'
new   1: select * from t where str between '200303310000' and '200304010000'

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=18 Card=6289 Bytes=182381)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=18 Card=6289 Bytes=182381)

ops$tkyte@ORA920> select * from t where num between &dt1 and &dt2;
old   1: select * from t where num between &dt1 and &dt2
new   1: select * from t where num between 200303310000 and 200304010000

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=18 Card=14 Bytes=406)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=18 Card=14 Bytes=406)

ops$tkyte@ORA920> select * from t
  2   where dt between to_date( '&dt1','yyyymmddhh24miss')
  3                and to_date('&dt2','yyyymmddhh24miss');
old   2:  where dt between to_date( '&dt1','yyyymmddhh24miss')
new   2:  where dt between to_date( '200303310000','yyyymmddhh24miss')
old   3:               and to_date('&dt2','yyyymmddhh24miss')
new   3:               and to_date('200304010000','yyyymmddhh24miss')

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=18 Card=201 Bytes=5829)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=18 Card=201 Bytes=5829)

think about that -- in a string -- there are lots of values 'between' those
two.  In a number, less.  But only when using a date -- can the optimizer truly
understand that ther is a single DAY between the two.

You'll really have a good chance of messing stuff up that way.

ugh....

the varchar2 is bigger then a number
the number would be less efficient then the date

the date would be the most efficient, yes you are paying a price for using a
string.

      Rating: 5
What does card
      value mean? Thanks.  August 29, 2003
Reviewer:
      Reader

          Followup:

cardinality -- expected number of rows.

      Rating: 5
Thanks
      Tom....  August 29, 2003
Reviewer:  Arturo
      Saravia Moya  from Peru
Thanx for your response. I see the light.

But...

This table have indexes based on local_time column and the application uses
hints on queries, will I see the same card with RBO?
Not my case but, what if I analyze this table regularly?

You're rigth about date type. I'm paying the price of a inherited database.

Regards.

          Followup:

that would stink -- to hint every query

you would never see CARD with RBO -- RBO doesn't use CARD.

that and a partitioned table cannot be queried using an RBO query at all.

I analyzed this table right before using it.  stats were 100%.

      Rating:
      5
  August 29, 2003
Reviewer:  Arturo
      Saravia Moya  from Peru
Thanks again Tom.

I'm thinking to contact the app provider and give several recomendations.

August 29, 2003 -- Thanks for the question regarding "decode",
      version 8

      You Asked
Create table test_hd
(empno number(6), basic_salary number(10) );

Create table test_ln
(empno number(6), ded_code number(10), cal_flg number(2) , value number(10) );

create table beni
(empno number(6), ded_code number(10), value number(10) );

select * from test_hd;
empno    basic_salary
101             5000
102             3000
103             3000

select * from test_ln;
EMPNO   DED_CODE    CAL_FLG      VALUE
---------- ---------- ---------- ----------
       101         10          1       1000
       101         20          1       1000
       102         10          2       1000
       102         20          1        500
       102         30          1        600

select * from beni;
EMPNO   DED_CODE      VALUE
---------- ---------- ----------
       101         30        500
       101         40       1000
       102         30       1000
       102         40       1000

here I want to add test_ln.value into test_hd.basic_salary if test_ln.cal_flg =1
and
if beni.ded_code is 30 then column heading = trans and if ded_code=40 then
column heading = food

SQL> select test_hd.empno,
  2    sum(decode(test_ln.cal_flg,1,test_ln.value,0))+test_hd.basic_salary
basic_Salary,
  3    max(decode(beni.ded_Code,30,beni.value,0)) trans,
  4     max(decode(beni.ded_Code,40,beni.value,0)) food
  5      from test_hd,test_ln, beni
  6   where test_ln.empno=test_hd.empno
  7    and beni.empno=test_hd.empno
  8   group by test_hd.empno, test_hd.basic_salary;

     EMPNO BASIC_SALARY      TRANS       FOOD
---------- ------------ ---------- ----------
       101         9000        500       1000
       102         5200       1000       1000

the above statement shows wrong baic_salary emp 101 must have 7000 and 102
salary must have 4100
help me please to get correct result

thank u

      and we said...
common problem when joining together many tables, each at different levels of
details, and doing aggregates.

Since there are two rows in beni for each empno, the rows in test_ln are added
in "twice" as well due to the join.

So, we must aggregate to the appropriate level and then join.  One approach:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select test_hd.empno,
  2         sum(decode(test_ln.cal_flg,1,test_ln.value,0))+test_hd.basic_salary
basic_Salary,
  3         trans, food
  4    from test_hd,
  5         test_ln,
  6         (select empno,
  7                 sum(decode(ded_code,30,value,0)) TRANS,
  8                 sum(decode(ded_code,40,value,0)) food
  9            from beni
 10           group by empno) beni
 11   where test_ln.empno=test_hd.empno
 12     and beni.empno=test_hd.empno
 13   group by test_hd.empno, test_hd.basic_salary, trans, food;

     EMPNO BASIC_SALARY      TRANS       FOOD
---------- ------------ ---------- ----------
       101         7000        500       1000
       102         4100       1000       1000

and

ops$tkyte@ORA817DEV.US.ORACLE.COM> select t.*,
    (select value from beni where beni.empno = t.empno and beni.ded_code = 30 )
trans,
  2 (select value from beni where beni.empno = t.empno and beni.ded_code = 40 )
food
  3    from ( select test_hd.empno,
  4
sum(decode(test_ln.cal_flg,1,test_ln.value,0))+test_hd.basic_salary basic_Salary
  5             from test_hd,
  6                  test_ln
  7            where test_ln.empno=test_hd.empno
  8            group by test_hd.empno, test_hd.basic_salary
  9         ) t
 10  /

     EMPNO BASIC_SALARY      TRANS       FOOD
---------- ------------ ---------- ----------
       101         7000        500       1000
       102         4100       1000       1000

is another...

Review &
      Followup

      Rating:
      4
  March 19, 2002
Reviewer:
      A reader
Excellent tom as usual thank u very much and nice explanation too thanks again

      Rating:
      5
  March 19, 2002
Reviewer:  Helena
      Markova  from Bratislava, Slovakia
Thank you for very good explanation.

      Rating: 4
Conditional
      decode  March 19, 2002
Reviewer:  Irfan
       from Kuwait
Hi Tom,
I have a table like this,

SQL> select value from t group by value;

  VALUE
----------
         1
         2
         3
         4
         5
         6
         7
         8
         9
        20

10 rows selected.

SQL> select decode(value, 1,'001', 2,'001', 3,'001', 4,'001', 5,'001',
               6,'002', 7,'002', 8,'002', 9,'002', 10,'002',
            11,'003', 12,'003', 13,'003', 14,'003', 15,'003',
            16,'004', 17,'004', 18,'004', 19,'004', 20,'004')
           from t group by value;

DEC
---
001
001
001
001
001
002
002
002
002
004

10 rows selected.

Can you guide me if there is any better way to have conditional decode. For
example I would like the value from 1-5 should return 001, 2-10 is 002 etc.,
instead of defining for each and every row.
Thanks a lot.

          Followup:

Well, I don't see how 1-5 could return 001 and 2-10 return 002 as 2-5 seems to
be both 001 and 002...

In any case, your choices are:

decode -- very fast, but sort of hard coded.  If you add addition values to T
(say 21) -- decode won't decode it.

lookup tables -- perhaps not as fast, but very flexible.  If you

create table lookup( x int primary key, val varchar2(10) );

create table t ( val references lookup );

you can ensure that every val in T has a lookup code and if you want to add a
new lookup code later, say for 21, you just insert it.  Then you join in the
query.

Of course, in your example, you could just use a simple function as it appears
you want to have 1-5 = 001, 6-10 = 002, and so on (by 5's).

ops$tkyte@ORA817DEV.US.ORACLE.COM> select x, to_char(trunc((x-1)/5)+1, '000' )
from t;

         X TO_C
---------- ----
         1  001
         2  001
         3  001
         4  001
         5  001
         6  002
         7  002
         8  002
         9  002
        10  002
        11  003
        12  003
        13  003
        14  003
        15  003
        16  004
        17  004
        18  004
        19  004
        20  004
        21  005

21 rows selected.

      Rating:
      3
  March 19, 2002
Reviewer:
      A reader
tom in decode statement can we give this  Criteria?

 if beni.ded_code >40 and beni.ded_code <50 then
      beni.value * 2;
 elsif beni.ded_code >50 and beni.ded_code <60 then
      beni.value *3;
     else
     beni.value * 4
end if;
and column heading as other
thenk u

          Followup:

I would use CASE for that.

select
  case when (ded_code > 40 and ded_code < 50 ) then value*2
       when (ded_code > 50 and ded_code < 60 ) then value*3
       else value*4
  end
from t;

decode can do that, with some work.

For example:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select x,
  2         decode( sign(x-40),
  3                 1, decode( sign(x-50),
  4                             1, decode(sign(x-60),
  5                                       -1, 'value*3', 'value*4 (a)'),
  6                            -1, 'value*2',
  7                            'value*4 (b)' ),
  8                 'value*4 (c)' ),
  9         case when (x > 40 and x < 50 ) then 'value*2'
 10              when (x > 50 and x < 60 ) then 'value*3'
 11         else 'value*4'
 12         end
 13  from t;

         X DECODE(SIGN CASEWHE
---------- ----------- -------
        39 value*4 (c) value*4
        40 value*4 (c) value*4
        41 value*2     value*2
        42 value*2     value*2
        43 value*2     value*2
        44 value*2     value*2
        45 value*2     value*2
        46 value*2     value*2
        47 value*2     value*2
        48 value*2     value*2
        49 value*2     value*2
        50 value*4 (b) value*4
        51 value*3     value*3
        52 value*3     value*3
        53 value*3     value*3
        54 value*3     value*3
        55 value*3     value*3
        56 value*3     value*3
        57 value*3     value*3
        58 value*3     value*3
        59 value*3     value*3
        60 value*4 (a) value*4
        61 value*4 (a) value*4

23 rows selected.

You can pick whether you want to use decode or CASE for this (case is new with
816 and up only)

      Rating:
      4
  March 19, 2002
Reviewer:  Irfan
       from Kuwait
Hi Tom,
Thanks for the solution. Actually it should be
1-5 is 001, 6-10 is 002 etc. (as you have cleared out, thanx). But I don't want
to hard code this by 5 like
to_char(trunc((x-1)/5)+1, '000' ).
Its not by 5's. It could be from 1-6 is 001, 7-20 is 002 or 21-50 is 003 etc.
Any conditional decode please?

          Followup:

It is called a LOOKUP table as described.  You can do it statically with decode
or generically with a lookup table.

You appear to have the need for a lookup table to be joined to.

      Rating: 4
Specific
      format  July 27, 2002
Reviewer:  PN

Hi Tom
Thanks for all previous helps
I am trying following query to get the "Required Output", but not getting Please
help to solve the problem.

select
count(distinct sr_no) COUNT,
TO_CHAR(F_DATE,'MM-RR') MON,
decode(SIGN(F_DATE-S_DATE),-1,'N','Y') TOT
from S_TRANS
WHERE TO_CHAR(F_DATE,'MM-RR')='JUL-02'

OUT OUT=

COUNT)    MON    TOT y/n
74    JUL-02    N
216    JUL-02    Y

"Required Output" :=

COUNT    MON    TOT n      TOT y      Ratio N    Ratio Y
290     JUL-02    74            216       25.51        74.49

Thanks
PN

          Followup:

Umm,

is

'07-02' = 'JUL-02'

NO, it is not.  You used MM on the date format.  You are comparing apples and
oranges here.

Better to query:

select count(distinct sr_no) COUNT, trunc(f_date,'month') mon,
       decode(SIGN(F_DATE-S_DATE),-1,'N','Y') TOT
  from S_TRANS
 WHERE f_date between to_date( 'jul-2002', 'mon-yyyy' ) and
              add_months( to_date( 'jul-2002', 'mon-yyyy' ), 1 )
   and f_date <> add_months( to_date( 'jul-2002', 'mon-yyyy' ), 1 )
/

that'll let you use an index on f_date if relevant.

USE 4 DIGIT YEARS!!! Yikes, I thought we all learned that big time 2 years
ago!!!

      Rating: 4
rEQUIRED
      FORMAT  July 28, 2002
Reviewer:  PN

Hi Tom
Sorry ,it was a mistake in date format,
But I want output in required in one row as below
"Required Output" :=

COUNT    MON    TOT n      TOT y      Ratio N    Ratio Y
290     JUL-02    74            216       25.51        74.49

where ratio N and ratio Y is percentage of (tot N/Count )*100 and  (tot Y/Count
)*100 .

Please help to proceed.
Thanks
PN

Hi Tom
Thanks for all previous helps
I am trying following query to get the "Required Output", but not getting Please

help to solve the problem.

select
count(distinct sr_no) COUNT,
TO_CHAR(F_DATE,'MM-RR') MON,
decode(SIGN(F_DATE-S_DATE),-1,'N','Y') TOT
from S_TRANS
WHERE TO_CHAR(F_DATE,'MM-RR')='JUL-02'

OUT OUT=

COUNT)    MON    TOT y/n
74    JUL-02    N
216    JUL-02    Y

"Required Output" :=

COUNT    MON    TOT n      TOT y      Ratio N    Ratio Y
290     JUL-02    74            216       25.51        74.49

Thanks
PN

Followup:
Umm,

is

'07-02' = 'JUL-02'

NO, it is not.  You used MM on the date format.  You are comparing apples and
oranges here.

Better to query:

select count(distinct sr_no) COUNT, trunc(f_date,'month') mon,
       decode(SIGN(F_DATE-S_DATE),-1,'N','Y') TOT
  from S_TRANS
 WHERE f_date between to_date( 'jul-2002', 'mon-yyyy' ) and
              add_months( to_date( 'jul-2002', 'mon-yyyy' ), 1 )
   and f_date <> add_months( to_date( 'jul-2002', 'mon-yyyy' ), 1 )
/

that'll let you use an index on f_date if relevant.

USE 4 DIGIT YEARS!!! Yikes, I thought we all learned that big time 2 years
ago!!!

          Followup:

try

select cnt, mon, tot_n, tot_y,
       decode( cnt, to_number(null), round(tot_n/cnt,2) ) n_ratio,
       decode( cnt, to_number(null), round(tot_y/cnt,2) ) y_ratio
  from (
select count(distinct sr_no) CNT,
       trunc(f_date,'month') mon,
       sum(decode(SIGN(F_DATE-S_DATE),-1,1,0)) TOT_N,
       sum(decode(SIGN(F_DATE-S_DATE),-1,0,1)) TOT_Y
  from S_TRANS
 WHERE f_date between to_date( 'jul-2002', 'mon-yyyy' ) and
              add_months( to_date( 'jul-2002', 'mon-yyyy' ), 1 )
   and f_date <> add_months( to_date( 'jul-2002', 'mon-yyyy' ), 1 )
 group by trunc(f_date,'month')
       )
/

      Rating:
      4
performance  April 01, 2003
Reviewer:
      A reader
Hi, Tom,
1. One of DBAs told us that in the pl/sql code, if you can us "if ..elsif...
else" stmt, then don't use DECODE/or NVL function, because that will decrease
the overall performance because of the parser used here.
2. As much as possible to write "generic" procedure/function instead of separate
ones because that will requests deferent chunk of codes resident in share pool
when other process calls these procedures/functions under diff condition/times,
some will reside in the share pool(ok), some will not reside in share pool(hurt
the performance for it will find the code and put them into the share pool).
Could you make a comment on the aboce please?

THanks

          Followup:

1)
DECODE -- could be true.

NVL -- nah, not true.

So -- why?  well, in order to use DECODE in plsql, you have to SELECT it from
dual and every select in plsql is a context switch from the plsql engine to the
SQL engine.

The discussion is moot in 9i since you have CASE in plsql -- no need for decode.

2) if they are trying to say "write reusable, generic code because that is the
way good code is written" they are correct.

They are correct in the premise "write reusable, generic code".
They are using the wrong argument...

and like everything -- it can be taken to illogical extremes.  If in order to
write truly generic code you end up doing tons and tons of dynamic SQL or
dynamically executed blocks of code -- you are doing it wrong as well.

It all boils down to

o common sense.
o there are no hard and fast rules, it is all greyspace here.  different
approaches for different situations

      Rating:
      4
  August 29, 2003
Reviewer:  Vipin
       from NY
Hi Tom,

I had a scary experience with Decode, please correct me (We don't mind even if
it is sarcastic;) if I miss something here.

I did the following test case to confirm my doubt:-

DECLARE
v_date           DATE;
v1_date          DATE;
i_date           DATE := to_date('01/01/2003 09:34:33 AM', 'mm/dd/yyyy hh:mi:ss
AM');
BEGIN
    Dbms_Output.put_line(To_char(i_date, 'mm/dd/yyyy hh:mi:ss AM'));

    SELECT DECODE(i_date, NULL, sysdate, i_date), i_date
    INTO   v_date, v1_date
    FROM   dual;

    Dbms_Output.put_line('V1 : '||To_char(v1_date, 'mm/dd/yyyy hh:mi:ss AM'));
    Dbms_Output.put_line('V  : '||To_char(v_date, 'mm/dd/yyyy hh:mi:ss AM'));
END;

The result which I got is :-

01/01/2003 09:34:33 AM
V1 : 01/01/2003 09:34:33 AM
V  : 01/01/2003 12:00:00 AM  --Where is my time component????

Is this a documented fact that decode looses the time component of the date or
else is there something which I am missing. My database is 8i (8.1.7)

Please help out with this.

          Followup:

your example doesn't match my reality?

ops$tkyte@ORA817DEV> DECLARE
  2  v_date           DATE;
  3  v1_date          DATE;
  4  i_date           DATE := to_date('01/01/2003 09:34:33 AM', 'mm/dd/yyyy
hh:mi:ss AM');
  5  BEGIN
  6      Dbms_Output.put_line(To_char(i_date, 'mm/dd/yyyy hh:mi:ss AM'));
  7
  8      SELECT DECODE(i_date, null, sysdate, i_date), i_date
  9      INTO   v_date, v1_date
 10      FROM   dual;
 11
 12      Dbms_Output.put_line('V1 : '||To_char(v1_date, 'mm/dd/yyyy hh:mi:ss
AM'));
 13      Dbms_Output.put_line('V  : '||To_char(v_date, 'mm/dd/yyyy hh:mi:ss
AM'));
 14  END;
 15  /
01/01/2003 09:34:33 AM
V1 : 01/01/2003 09:34:33 AM
V  : 01/01/2003 09:34:33 AM

PL/SQL procedure successfully completed.

can you cut and paste  straight from sqlplus the example

      Rating: 5
nvl versus
      if  August 29, 2003
Reviewer:  A reader

"DECODE -- could be true.
NVL -- nah, not true.
"
Well, following is one of the guidelines for our team that
said the opposite - so I benchmarked using Tom's test
harness. Here is the guideline and my comments with
benchmark.

---
The guideline said:
"
When checking for the validity of return values from a SQL statement, keep in
mind that you may get a NULL value back as well. So if you have a return value
from a column which you would like to check, instead of :
IF (l_column_name IS NULL)
THEN
  l_column_name := 'Some thing';
END IF;
which is an expensive operation, Use
l_column_name := NVL(l_column_name, 'Some thing');"

My response:

How expensive is this indeed? This may be more true from the point of view of
elegance and code readability rather than performance. Well, anyways, I ran a
test to find out. Surprisingly,  I found that the if statement runs in around
20% of the time taken by nvl operation!  So the performance caveat (if it
applies which does not in my opinion) seems to indicate that we should prefer if
statement in the above case! I would perhaps still use nvl for readability
purposes unless the code runs in a huge loop.
Here is my test run - It uses the test harness written by Tom Kyte at
http://asktom.oracle.com/~tkyte/runstats.html
test@apps> @t
test@apps> declare
  2    l_column_value varchar2(20);
  3    l_start number;
  4    l_run1 number;
  5    l_run2 number;
  6    l_loop_counter number := 1000000;
  7  begin
  8    select 'x'
  9    into l_column_value
 10    from dual;
 11
 12    runstats_pkg.rs_start;
 13
 14    for i in 1..l_loop_counter loop
 15    if( l_column_value is null ) then
 16      l_column_value := 'Some thing';
 17    end if;
 18    end loop;
 19    runstats_pkg.rs_middle;
 20    for i in 1 .. l_loop_counter loop
 21    l_column_value := nvl( l_column_value, 'Some thing');
 22    end loop;
 23    runstats_pkg.rs_stop(200);
 24
 25  end;
 26  /
Run1 ran in 122 hsecs                <== time taken by if statement
Run2 ran in 677 hsecs                <== time taken by nvl statement
run 1 ran in 18.02% of the time

PL/SQL procedure successfully completed.

Elapsed: 00:00:08.47
test@apps>
test@apps> spool off

          Followup:

ahh, but what about just

select nvl('x','some thing') into l_column_value from dual;

if I didn't want nulls in the first place, i would have fixed it in the sql

      Rating: 5
yes - but that
      perhaps misses the point  August 29, 2003

Reviewer:  A reader
"ahh, but what about just

select nvl('x','some thing') into l_column_value from dual;

if I didn't want nulls in the first place, i would have fixed it in the sql
"

You are right. But that is perhaps missing the point.

The benchmark proves that
using if statement takes around 20% of the time that
nvl takes. Does not mean that you should use if always
instead of nvl. But it is good to know how much the
difference is (it is substantial!) before you make a
choice. So the DBAs in the original statement did have
a point perhaps.
It again shows what you yourself say - "always benchmark" either to prove or
disprove, learn or at the minimum
to measure the impact your choices will make.

      Rating:
      4
  August 29, 2003
Reviewer:  Vipin
       from NY
Sorry Tom,

I messed it up slightly. I executed it again in SQL and please find the results.

SQL>
SQL> DECLARE
  2  v_date           DATE;
  3  v1_date          DATE;
  4  i_date           DATE := to_date('01/01/2003 09:34:33 AM', 'mm/dd/yyyy
hh:mi:ss AM');
  5  BEGIN
  6      Dbms_Output.put_line(To_char(i_date, 'mm/dd/yyyy hh:mi:ss AM'));
  7
  8      SELECT DECODE(i_date, NULL, NULL, i_date), i_date
  9      INTO   v_date, v1_date
 10      FROM   dual;
 11
 12      Dbms_Output.put_line('V1 : '||To_char(v1_date, 'mm/dd/yyyy hh:mi:ss
AM'));   --Not loosing the time component
 13      Dbms_Output.put_line('V  : '||To_char(v_date, 'mm/dd/yyyy hh:mi:ss
AM'));    --Loosing the time component.
 14  END;
 15  /
01/01/2003 09:34:33 AM
V1 : 01/01/2003 09:34:33 AM
V  : 01/01/2003 12:00:00 AM

PL/SQL procedure successfully completed

Please find the change in Decode statement :-

DECODE(i_date, NULL, NULL, i_date), i_date
instead of :-
DECODE(i_date, NULL, sysdate, i_date), i_date.

In reality I wanted to use another decode statement there in instead of
"Sysdate".

So please don't ignore this thread thinking "DECODE(i_date, NULL, NULL, i_date),
i_date"  doesn't make any sense.

Thanks

          Followup:

well, it is the defined expected behaviour.

your:

DECODE(i_date, NULL, NULL, i_date)

is really:

to_date( decode( i_date, null, null, to_char(i_date) ) )

you see, decode looks at the first returned value and says "that, that is the
type I return".  In your case that was:

DECODE(i_date, NULL, NULL, i_date)

Now, decode also says "if i return NULL, i will return a varchar2" by
definition.  Hence your decode returned STRINGS, not dates and there is an
implicit to_date on there.

Now, your default date format probably does not include the time -- so it got
chomped.  Consider:

ops$tkyte@ORA920> alter session set nls_date_format = 'dd-mon-rr';

Session altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> DECLARE
  2  v_date           DATE;
  3  v1_date          DATE;
  4  i_date           DATE := to_date('01/01/2003 09:34:33 AM', 'mm/dd/yyyy
hh:mi:ss AM');
  5  BEGIN
  6      Dbms_Output.put_line(To_char(i_date, 'mm/dd/yyyy hh:mi:ss AM'));
  7
  8      SELECT DECODE(i_date, NULL, NULL, i_date), i_date
  9      INTO   v_date, v1_date
 10      FROM   dual;
 11
 12      Dbms_Output.put_line('V1 : '||To_char(v1_date, 'mm/dd/yyyy hh:mi:ss
AM'));   --Not loosing the time component
 13      Dbms_Output.put_line('V  : '||To_char(v_date, 'mm/dd/yyyy hh:mi:ss
AM'));    --Loosing the time component.
 14  END;
 15  /
01/01/2003 09:34:33 AM
V1 : 01/01/2003 09:34:33 AM
V  : 01/01/2003 12:00:00 AM

PL/SQL procedure successfully completed.

default date format and time goes "poof"

ops$tkyte@ORA920> alter session set nls_date_format = 'dd-mon-rrrr hh24miss';

Session altered.

ops$tkyte@ORA920> DECLARE
  2  v_date           DATE;
  3  v1_date          DATE;
  4  i_date           DATE := to_date('01/01/2003 09:34:33 AM', 'mm/dd/yyyy
hh:mi:ss AM');
  5  BEGIN
  6      Dbms_Output.put_line(To_char(i_date, 'mm/dd/yyyy hh:mi:ss AM'));
  7
  8      SELECT DECODE(i_date, NULL, NULL, i_date), i_date
  9      INTO   v_date, v1_date
 10      FROM   dual;
 11
 12      Dbms_Output.put_line('V1 : '||To_char(v1_date, 'mm/dd/yyyy hh:mi:ss
AM'));   --Not loosing the time component
 13      Dbms_Output.put_line('V  : '||To_char(v_date, 'mm/dd/yyyy hh:mi:ss
AM'));    --Loosing the time component.
 14  END;
 15  /
01/01/2003 09:34:33 AM
V1 : 01/01/2003 09:34:33 AM
V  : 01/01/2003 09:34:33 AM

PL/SQL procedure successfully completed.

date format that saves time, time stays "there"

Better way -- make sure the first return value from decode is what you really
want it to be:

ops$tkyte@ORA920> alter session set nls_date_format = 'dd-mon-rr';

Session altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> DECLARE
  2  v_date           DATE;
  3  v1_date          DATE;
  4  i_date           DATE := to_date('01/01/2003 09:34:33 AM', 'mm/dd/yyyy
hh:mi:ss AM');
  5  BEGIN
  6      Dbms_Output.put_line(To_char(i_date, 'mm/dd/yyyy hh:mi:ss AM'));
  7
  8      SELECT DECODE(i_date, NULL, to_date(NULL), i_date), i_date
  9      INTO   v_date, v1_date
 10      FROM   dual;
 11
 12      Dbms_Output.put_line('V1 : '||To_char(v1_date, 'mm/dd/yyyy hh:mi:ss
AM'));   --Not loosing the time component
 13      Dbms_Output.put_line('V  : '||To_char(v_date, 'mm/dd/yyyy hh:mi:ss
AM'));    --Loosing the time component.
 14  END;
 15  /
01/01/2003 09:34:33 AM
V1 : 01/01/2003 09:34:33 AM
V  : 01/01/2003 09:34:33 AM

PL/SQL procedure successfully completed.

to_date(null) returns a date -- hence decode returns a date -- hence none of the
implicit nastiness happens.

the decode you really have -- must have the same problem, the returned decode is
in fact a string -- not a date.

      Rating: 5
Back to
      basics!  August 29, 2003
Reviewer:
      A reader

      Rating:
      5
  August 29, 2003
Reviewer:  Vipin

Thanks Tom

August 29, 2003 Ramasamy -- Thanks for the question regarding
      "Sort by Age", version 8.1.7

      You Asked
Tom,

I am a regular reader of your site.
Thanks a lot for maintaining well and providing free service
to the Oracle community. Now a days i am not seeing much new threads.
Is there any way you could provide us, an option to sort the questions
by AGE (Your column on your screen).

Thanks a lot.

      and we said...
advanced search can do that.

beware, with over 25,000 articles, it might take a second or two - you would
want to use a time filter as well (please -- it'll be nicer on the database!)

I've been traveling tons recently -- try to take a couple of questions a day,
but it gets hard -- unless I start ignoring the reviews/followups.  When there
are 100+ of them a day -- not much time for a truly new question.  its a
conundrum...
August 30, 2003
      Sundar -- Thanks for the question regarding
      "Getting Foreign and Primary Key syntax from the Database", version 9.2.0

      You Asked
Hai Tom,
Need your help in getting the following syntax. This is the example output which
I would like to use for my Database Tables. Are there any built in procedures to
be used if so what are they and how to use them. Can this data be retrived from
any table of any of the users in the Database. We are using 9i Version Database.

USER_DEPENDENCIES IS NOT GIVING THE INFORMATION FOR TABLES.

ALTER TABLE EMP ADD
(CONSTRAINT PK_EMP PRIMARY KEY (EMPNO))
/
ALTER TABLE EMP ADD
(CONSTRAINT FK_DEPTNO FOREIGN KEY (DEPTNO)
REFERENCES DEPT (DEPTNO))
/
Regards
Sundar

      and we said...
user_constraints would be the place I would look for constraints.
user_dependencies -- well, that I would use to see who relies on who.

But, anyway, you mean like this:

ops$tkyte@ORA920> begin
  2          dbms_metadata.set_transform_param
  3          ( DBMS_METADATA.SESSION_TRANSFORM, 'CONSTRAINTS_AS_ALTER', true );
  4          dbms_metadata.set_transform_param
  5          ( DBMS_METADATA.SESSION_TRANSFORM, 'STORAGE', false );
  6          dbms_metadata.set_transform_param
  7          ( DBMS_METADATA.SESSION_TRANSFORM, 'SQLTERMINATOR', TRUE );
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select dbms_metadata.get_ddl( 'TABLE', 'EMP' ) from dual;

DBMS_METADATA.GET_DDL('TABLE','EMP')
--------------------------------------------------------------------------------

  CREATE TABLE "OPS$TKYTE"."EMP"
   (    "EMPNO" NUMBER(*,0),
        "MGR" NUMBER(*,0)
   ) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
  TABLESPACE "USERS" ;
 CREATE UNIQUE INDEX "OPS$TKYTE"."EMP_PK" ON "OPS$TKYTE"."EMP" ("EMPNO")
  PCTFREE 10 INITRANS 2 MAXTRANS 255
  TABLESPACE "USERS" ;
  ALTER TABLE "OPS$TKYTE"."EMP" ADD CONSTRAINT "EMP_PK" PRIMARY KEY ("EMPNO")
  USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255
  TABLESPACE "USERS"  ENABLE;
  ALTER TABLE "OPS$TKYTE"."EMP" ADD CONSTRAINT "EMP_FK_EMP" FOREIGN KEY ("MGR")
          REFERENCES "OPS$TKYTE"."EMP" ("EMPNO") ENABLE;

Review &
      Followup

      Rating: 4
Getting Foreign
      and Primary Key syntax from the Database  January 24, 2003

Reviewer:  sundar anupindi  from India
This is very useful for getting the Reveser Enginnering of the database with
this I would like to generate the syntax for the creation of the table in the
MySql Database.

      Rating:
      5
  January 29, 2003
Reviewer:  Renu
      Gharpue  from Dallas, TX USA
CREATE TABLE "OPS$TKYTE"."EMP"
   (    "EMPNO" NUMBER(*,0),
        "MGR" NUMBER(*,0)
   ) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
  TABLESPACE "USERS" ;
 CREATE UNIQUE INDEX "OPS$TKYTE"."EMP_PK" ON "OPS$TKYTE"."EMP" ("EMPNO")
  PCTFREE 10 INITRANS 2 MAXTRANS 255
  TABLESPACE "USERS" ;
  ALTER TABLE "OPS$TKYTE"."EMP" ADD CONSTRAINT "EMP_PK" PRIMARY KEY ("EMPNO")
  USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255
  TABLESPACE "USERS"  ENABLE;
  ALTER TABLE "OPS$TKYTE"."EMP" ADD CONSTRAINT "EMP_FK_EMP" FOREIGN KEY ("MGR")
          REFERENCES "OPS$TKYTE"."EMP" ("EMPNO") ENABLE;
For me breaks words
like
ALTER TABLE "OPS$TKYTE"."EMP" ADD CONSTRAINT "EMP_
FK_EMP" FOREIGN KEY ("MGR")
          REFERENCES "OPS$TKYTE"."EMP" ("EMPNO") ENABLE;
How can this be avoided?
eg breaking of "EMP_FK_EMP"
into "EMP_ and FK_EMP" FOREIGN KEY ("MGR") in next line.
.. thnaks
.. Renu

          Followup:

just use the typical SQL Plus formatting commands then:

ops$tkyte@ORA920> column x format a20 WORD_WRAPPED
ops$tkyte@ORA920> select dbms_metadata.get_ddl( 'TABLE', 'EMP' ) x from dual;

X
--------------------
CREATE TABLE
"OPS$TKYTE"."EMP"
(       "EMPNO"
NUMBER(4,0) NOT NULL
ENABLE,
"ENAME"
VARCHAR2(10),
"JOB" VARCHAR2(9),
"MGR" NUMBER(4,0),
"HIREDATE" DATE,
"SAL" NUMBER(7,2),
"COMM" NUMBER(7,2),
"DEPTNO" NUMBER(2,0)
) PCTFREE 10 PCTUSED
40 INITRANS 1
MAXTRANS 255
NOCOMPRESS LOGGING
TABLESPACE "USERS" ;

ops$tkyte@ORA920> column x format a80 WORD_WRAPPED
ops$tkyte@ORA920> select dbms_metadata.get_ddl( 'TABLE', 'EMP' ) x from dual;

X
--------------------------------------------------------------------------------
CREATE TABLE "OPS$TKYTE"."EMP"
(       "EMPNO" NUMBER(4,0) NOT NULL ENABLE,
"ENAME" VARCHAR2(10),
"JOB" VARCHAR2(9),
"MGR" NUMBER(4,0),
"HIREDATE" DATE,
"SAL" NUMBER(7,2),
"COMM" NUMBER(7,2),
"DEPTNO" NUMBER(2,0)
) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
TABLESPACE "USERS" ;

      Rating: 4
ddl
      tracking  January 29, 2003
Reviewer:  mo

Tom:

1. is dbms_metadata a separate package that I have to install to test the above?

2. is it used to track everything that was done for a table?

          Followup:

1) nope, you just need to install Oracle9i

2) it extracts the ddl from the data dictionary.  it keeps track of nothing, it
just reports on what is there.

      Rating:
      3
  January 30, 2003
Reviewer:  kumar s
      b  from India
I believe this package (dbms_metadata) is introduced in 9i. Is there any method
to generate a similar code in 8i?

          Followup:

$ exp userid=scott/tiger tables=emp

Export: Release 8.1.7.4.0 - Production on Thu Jan 30 08:47:40 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production
Export done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set

About to export specified tables via Conventional Path ...
. . exporting table                            EMP         14 rows exported
Export terminated successfully without warnings.

$ imp userid=scott/tiger full=y indexfile=emp.sql

Import: Release 8.1.7.4.0 - Production on Thu Jan 30 08:47:48 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production

Export file created by EXPORT:V08.01.07 via conventional path
import done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set
. . skipping table "EMP"

Import terminated successfully without warnings.

$ cat emp.sql

REM  CREATE TABLE "SCOTT"."EMP" ("EMPNO" NUMBER(4, 0) NOT NULL ENABLE,
REM  "ENAME" VARCHAR2(10), "MGR" NUMBER(4, 0), "HIREDATE" DATE, "SAL"
REM  NUMBER(7, 2), "COMM" NUMBER(7, 2), "DEPTNO" NUMBER(2, 0), "JOB"
REM  VARCHAR2(10)) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 LOGGING
REM  STORAGE(INITIAL 16384 NEXT 16384 MINEXTENTS 1 MAXEXTENTS 2147483645
REM  PCTINCREASE 10 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
REM  TABLESPACE "SYSTEM" ;
REM  ... 14 rows

      Rating:
      5
DDL  January 30, 2003
Reviewer:  mo

great way of getting table info in 8i.

      Rating:
      4
dbms_metadata.get_ddl only shows first 2
      columns  August 29, 2003
Reviewer:  ht
       from California
Hi Tom,
Which sqlplus setting do I need to set to view the entire scott.emp table?

TIA
ht

select dbms_metadata.get_ddl( 'TABLE','EMP','SCOTT' ) x from dual
  2  ;

X
--------------------------------------------------------------------------------

  CREATE TABLE "SCOTT"."EMP"
   (    "EMPNO" NUMBER(4,0),
        "ENAME" VARCHAR2(10),

1 row selected.

select * from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.3.0 - 64bit Production
PL/SQL Release 9.2.0.3.0 - Production
CORE    9.2.0.3.0       Production
TNS for Solaris: Version 9.2.0.3.0 - Production
NLSRTL Version 9.2.0.3.0 - Production

5 rows selected.

desc scott.emp;
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 EMPNO                                     NOT NULL NUMBER(4)
 ENAME                                              VARCHAR2(10)
 JOB                                                VARCHAR2(9)
 MGR                                                NUMBER(4)
 HIREDATE                                           DATE
 SAL                                                NUMBER(7,2)
 COMM                                               NUMBER(7,2)
 DEPTNO                                             NUMBER(2)

          Followup:

SQL> set long 50000

      Rating:
      5
Thanks.  September 05, 2003
Reviewer:
      ht  from california

August 30, 2003 Betty -- Thanks for the question regarding
      "Archive Log List", version 8.1.6

      You Asked
Tom,
What privileges do I need to excute SQL archive log list;

Thanks,
Betty

      and we said...
You need to be connected "internal" or better yet as
SYSDBA/SYSOPER:

sys@DEV816> connect sys/xxxxx
Connected.
sys@DEV816> archive log list;
ORA-01031: insufficient privileges

sys@DEV816> connect sys/xxxxx as sysdba
Connected.
sys@DEV816> archive log list;
Database log mode              No Archive Mode
Automatic archival             Disabled
Archive destination            /export/home/ora816/dbs/arch
Oldest online log sequence     1860
Current log sequence           1862

This is documented in the SQLPlus guide on the usage for the
SQLPlus/SVRMGRL command "archive log"
Review &
      Followup

      Rating: 3
Archive Log
      List  August 29, 2003
Reviewer:  Deepak
      Gupta  from delhi, India
Oldest online log sequence     2090
Current log sequence           2094
SQL> select * from v$log;

   GROUP#   THREAD# SEQUENCE#     BYTES   MEMBERS ARC STATUS
FIRST_CHANGE# FIRST_TIM
--------- --------- --------- --------- --------- --- ----------------
------------- ---------
        1         1      2092   1048576         1 YES INACTIVE
25600134 28-AUG-03
        2         1      2093   1048576         1 YES INACTIVE
25600169 28-AUG-03
        3         1      2091   1048576         1 YES INACTIVE
25600041 28-AUG-03
        4         1      2094   1048576         1 NO  CURRENT
25600344 29-AUG-03
        5         1      2090   5120000         1 YES INACTIVE
25599765 28-AUG-03

On which factors the "Oldest online log sequence" value depend ?
I mean what is reason of difference between to log sequence value (2090,2094)  ?
( why not "Oldest online log sequence " 2093 )

          Followup:

why would 2093 be the oldest?!?

      Rating: 3
Archive Log
      List  August 30, 2003
Reviewer:  Deepak
      Gupta  from Delhi,India
Dear Mr. Tom,

     why would 2093 be the oldest?!?
because 2094 is current and  previous one ( or second last ) should be oldest

Actually my question is

which parameters / factors decided which one log sequence would be oldest ?

          Followup:

no, the oldest would be the oldest like 2092 or before.

still not getting what you are getting at here.  log sequence numbers are just
that -- sequences -- increasing.

i have no idea what you mean by "oldest" -- in your display, one would think
2090 would be the "oldest" you have displayed.

August 30, 2003 Reji -- Thanks for the question regarding
      "Multiplexing and Mirroring", version 8.1.5 & 8.1.6

      You Asked
Tom:
   I am using Oracle8.1.6 on Solaris 7.  The control files are
   multiplexed on three different disks but I have a single
   disk controller. Now, If something happend to the disk
   controller all these control files to become unavailable and
   the Oracle instance will crash ( Please correct me if I am
   wrong here ).  How does it differ from, if I have three
   disk controllers ( controller/disk ) and if one of the disk
   or controller failed, what will happen? - I am expecting the
   same thing even if the disks are on different controllers.

   If I have the control files multiplexed on different disks
   and I have mirrored the disk and the mirrored disks on a
   a separate controller then I will be safe?  Would you please
   explain this little bit more.  My goal to make sure that
   the control files are "safe".

   TIA
   Reji

      and we said...
The loss of a controler will only disable access to the data
until you get a new controler -- it will not permanently lose
access to the data.

The point of spreading control files over multiple disks is to
avoid losing permanently ALL control files.

The loss of a single control file will cause the database to
halt so even if all three disks had their own controllers, once
one of the disks became unavailable -- the database would halt
and you would have to either decide to run with 2 control files
or find a 3'rd mount point to copy one of the good control files
to and restart the instance.

If you have 3 copies, on 3 three physically separate devices and
these devices are themselves mirrored (6 copies on 6 physically
separate devices) you are about as safe as you can get.  You
should backup you control files to trace periodically and after
any structural change as well.
Review &
      Followup

      Rating: 5
Thanks and more
      info please!  April 30, 2003
Reviewer:
      A reader
"The loss of a single control file will cause the database to halt so even if
all three disks had their own controllers, once one of the disks became
unavailable
-- the database would halt and you would have to either decide to run with 2
control files or find a 3'rd mount point to copy one of the good control files
to and restart the instance."

I am confused by your statement "database would halt". I thought, during the
database operation, even if I lose a controlfile, Oracle will continue to work
with other two controlfiles (assuming I have three controlfiles specified in
control_files init parameter). Only when the instance is restarted next time,
Oracle will startup only if all of the three controlfiles are functional. Am i
right? or am i still very confused? Thanks. Your forum is the best. I can't tell
you how much I learn from your site.

          Followup:

try it -- see what happens.

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96524/c04space.htm#10193

      Rating:
      5
  May 01, 2003
Reviewer:
      A reader

      Rating: 5
error during
      Multiplexing on disk in different pc   August 29, 2003

Reviewer:  anurag  from india
Hi!

I was trying to create a mirror image of controlfile in a different pc-hard-disk
on network. I got an os error. I tried to create a tablespace datafile on same
device, I got same OS error (5). Where i am wrong??? The path is also correct
and drives are correctly mapped and path defined. All privilege given. The os is
win2k.

thanks in adv.

          Followup:

you don't even want to in your wildest dreams consider doing such a thing.

not even in your WILDEST dreams.  thank goodness it did not work.  network disks
on a database -- that is in the bucket of "really bad idea".

the only "network" storage that is usable with a database are things like NAS --
and then only from certified vendors.

      Rating: 3
What about
      OPS?  August 29, 2003
Reviewer:  Piotr
      Jarmuz  from Braunschweig, Germany
Aren't network drives used in OPS environment?
I thought Oracle had share-everything architecture.

Regards.

          Followup:

they are not normal nfs mounted or windows shares -- not by a long shot.

they are clustered filesystems (real special)
they are nas by certified vendors
they are san by certfied vendors
they are raw disks

they are not windows shares
they are not just a bunch of disks nfs mounted across servers..

      Rating: 5
good book on
      storage?  August 29, 2003
Reviewer:
      A reader
Hi Tom
A little off topic but could you kindly recommend good books/references on
storage that gives details about
different layers of storage (from disks to volume managers to file systems to
apps such as data bases) for someone
who is a novice in this area?

Thanx!

          Followup:

i don't know of any -- but maybe someone else will chime in...

      Rating: 5
It's
      SAN  August 29, 2003
Reviewer:  angelo
       from albany, NY
Databases usually use SAN.  A NAS is a file-server like environment.

A google search should help explain the differences.

          Followup:

well, it is more like "today many databases are using SAN, NAS and direct
attatch storage"

we are all clouded by our environment -- the netapp fileserver is getting more
"popular".

      Rating:
      4
SAN  August 29, 2003
Reviewer:
      Angelo  from Albany, NY
Maybe, people are using NAS for their Oracle databases, but that would be a bad
choice.

"Analysts, including the Meta group, have issued reports and quotes recommending
that NAS should not be used for database applications."

http://www.mti.com/white_papers/WP20004.PDF

          Followup:

and gartner predicated sybase would kill off oracle due to their replication
technology...

and that informix would win the vldb war and object skirmish...

and things change constantly.  beware analysts. that paper is over 3 years old,
given the way technlogy moves, it might as well be 20 years old.  some of the
information contained therein is not accurate in 2003.

that and well, you got it from a SAN vendor -- what would you expect?

so, take it as you would as advice from here or anywhere -- something to
consider, not gospel truth.

      Rating:
      3
agree  August 30, 2003
Reviewer:
      Anurag  from INDIA
Tom I agree with U that file on network disk is bad idea. But why it did'nt
work? What is the thing behind??

          Followup:

when you log into windoze, you see drive X mapped from machine foo.

when I log into that same windoze machine, do i have drive X?  no, windows is
not a multi-user operating system like unix and other systems.  It is a single
user system -- every user has their own settings.

so, while YOU can see that disk -- the oracle database, running as a service on
that machine, cannot.  you can make it so that it does but you cannot use it for
datafiles or control files.  it goes waaaay beyond a bad idea -- bad idea is far
too generous.

search for

utl_file network nt

on this site to see how to make window shares visible.

August 31, 2003 Seetharaman -- Thanks for the question regarding
      "Checkpoint in init.ora file", version 8.1.6

      You Asked
Hi Tom,

  Greetings!... I want to know the basic concepts of the following in the Oracle
8i db-s.

  What is the significance of the checkpoint parameters in the init ora file?..
How it is used in the database?.. What's the relation between checkpoint and
cursors?...

  log_checkpoint_interval = 10000
  log_checkpoint_timeout = 1800

  What is open_cursors?.. is it new in Oracle 8i?.. and how it is related to the
transactions we do?...

  pls. tell me in detail

Thanks in advance

regards

Seetharaman Srinivasan

      and we said...
for the first two, see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:344617462392
for open_cursors, it is an upper limit on the number of concurrent open cursors
(any SQL statement) a given session may have.  It has been around for a long
time (not new in Oracle8i).

The Oracle8i reference manual has an in depth description of each init.ora
parameter.

Review &
      Followup

      Rating: 4
cyheckpoint and
      LRU  August 07, 2003
Reviewer:  ygp
       from India
From your book chapter-2 (Architecture), I see two different points and get
confused.

(1) You mention that oracle manages Block Buffer Cache by (a) Dirty blocks list
and (b) LRU (now touch count)

(2) You also mention that the bb_cache_lru_latches (is that the right name ?!)
is used by the DBWn

So, are the LRU and "dirty block list" same OR different ?

I think they are different. But, then why the parameter contais the word "_LRU_"
?

          Followup:

dirty block list just points to the lru -- it is not a copy, it is a list of
blocks (that are pointed to) that are dirty.

      Rating: 4
Checkpoint
      & LRU  August 31, 2003
Reviewer:  ygp
       from India
Thanks a lot.

I thought that both the lists are exclusive of each oter.

LRU having only non-dirty blocks.

ditry-blocks only in the "dirty list"

One more query :

I understand that after checkpoint each-and-every (ALL) blocks become non-dirty.

Is that right ?

          Followup:

checkpoints are not a discrete event really -- more of a continous event.  If a
checkpoint were an atomic, discrete thing -- that would be true.  but remember
even while flushing dirty buffers -- there are new buffers being dirtied.  It is
the never ending job.

August 31, 2003 Dale -- Thanks for the question regarding
      "Asynchronous update after row insertion", version 9.2.0.1

      You Asked
Hi,

in our system we have a process that receives large numbers of requests, each of
which will add a row in a table. The intial request provides enough information
for another business process to calculate a derived column, but the business
process is long and involved (a java stored proc requiring a TCP connection and
taking seconds to complete). My question is how to maintain good inflows of
records into the data table while also eventually updating the derived column. I
believe that a trigger cannot be used to calculate the column as this would mean
each row would take seconds to insert into the table. What is the best way to
have the long running derivation and update done asynchronously after the
insertion of each row? It is not critical that the derived column be available
immediately with the rest of the row, it can be filled in "later", but we do
want this process to occur ASAP after insertion of the row.

thanks

Dale

      and we said...
you can either:

o use dbms_job to schedule a stored procedure to execute after you commit.  you
would create a trigger like this:

create trigger x before insert on t for each row
declare
   l_job  number;
begin
   dbms_job.submit( l_job, 'stored_procedure(JOB);' );
   :new.job_id = l_job;
end;

you would add a job_id column to your "inflow" table.  This trigger would
schedule a stored procedure to run after commit.  this stored procedure would
receive as input its job number -- this job number would also be stored on the
row itself.  the stored procedure would then be able to:

   select * into l_rec from t where job_id = PASSED_IN_JOB;

to retrieve the inserted record.

o use AQ (advanced queues).  you would write daemon processes that dequeued and
processed "messages".

dbms_job would be the "simplest" solution.
Review &
      Followup

      Rating: 4
But how does
      dbms_job scale?  August 15, 2003
Reviewer:  Dale
      Ogilvie  from New Zealand
There is potential for a large spike of records to arrive in "inflow". How will
oracle cope with a large number of submitted jobs waiting to complete as our
long running java code services the requests. There is a FIFO queue on the other
side of the java stored proc but I can imagine a raft of jobs waiting on the
oracle side of things. Would this tend one toward a AQ implementation or can
dbms_job cope?

Thanks

          Followup:

dbms_job can cope...

but AQ has lots to offer.  dbms_job is very "simple", AQ can either be very
"simple" or infinitely flexible (prioritization, routing, retention, etc)

depends on your needs.

      Rating: 4
Exploring
      dbms_job  August 31, 2003
Reviewer:  Dale
      Ogilvie  from New Zealand
Our problem is that

1. we need a high rate of insertion into an inflow table.
2. we need to calculate a derived column on inflow, using a calculation that
takes a long time.
3. Given 1. we don't want to wait for the calculation of the derived column
before inserting into inflow.

I've looked a bit further into using dbms_job.submit( 'long_running(JOB);') and
adding job_id as a column to inflow as a solution, and I have a couple of issues
with it.

1. I believe that the job id generated for a new job comes from a sys sequence
that has a relatively low maximum (1 billion or thereabouts). Given that there
will be very large numbers of rows in our inflow table using job id as a key, I
wonder whether this might be too small. Is there any way to increase the maximum
on the job id sequence, or do I have to code for the possibility of 'select *
from inflow where job_id = x' returning more than one row? If I do, what is the
best way to return the last added row matching job id?

2. We might split out the derived column into its own table, so that rows in
inflow do not require to be updated, at the cost of a join when it comes time to
"select" the inflow columns plus the derived_column.

Which do you think is preferable:
"update inflow set derived_col = y where job_id = x"
or
"insert into derived (job_id, derived_col) values ( x, y )"

Both these sql snippets to store derived_col would have to be modified if I
can't rely on job_id being unique.

3. The long running calculation process can handle 12 simultaneous calls (the
server runs 12 threads). I think that this means that I should set
job_queue_processes to 12. Would 12 be right?

thanks

Dale

          Followup:

sys@ORA920> @printtbl8 'select * from user_sequences where sequence_name =
"JOBSEQ" '
SEQUENCE_NAME                 : JOBSEQ
MIN_VALUE                     : 1
MAX_VALUE                     : 999999999
INCREMENT_BY                  : 1
CYCLE_FLAG                    : Y
ORDER_FLAG                    : N
CACHE_SIZE                    : 20
LAST_NUMBER                   : 100719
-----------------

it can cycle...

but -- what you might look at would be to use AQ (advanced queues) whereby you
would insert NULL into this derived column and plop the primary key value into a
queue -- a queue that you write the consumer for.  consumer gets primary key,
does work, updates and message goes away.

You can deploy as many "dequeuers" as you like and can even have them run on
physically separate machines over the network if you wanted

August 31, 2003 Deepak -- Thanks for the question regarding "New
      facility ‘ Combination “we said” and “follow-up” ‘", version 8.1.6

      You Asked
Dear Tom,

I would like to suggest you, kindly provide a facility such that your “We said “
and “follow up” combine together skipping user questions or review.

So that one can easily read or take a printout without review or follow-up
question

Thanks + Regards

Deepak Gupta

      and we said...
i don't think so -- this is why.

I am not perfect nor am I always 100% complete.

The followups -- I rely on them to keep me honest, to make me prove the point.
Many times -- more often then I like really -- the followups are "fix ups" to
the original answer.  I tend not to edit the original response unless the error
was egregious.

So, without the followup part -- the answer is in my opinion -- incomplete.  we
really need that part.
Review & Followup

      Rating: 3
additional
      facility / feature  August 30, 2003
Reviewer:
      Deepak gupta  from delhi,india
Dear Mr. Tom,

       Hi,

what you have said is right but I want this as additional like "advanced secach"
or "Archives" etc.

anyway thanks a lot for reply.

      Rating: 3
Usefulness of
      followups  August 31, 2003
Reviewer:  Gary
       from Sydney, Australia
I can see some follow ups are necessary (adding in extra info for the original
query, or other peoples experience).

However maybe you can limit the follow ups so that after a period of inactivity,
say three weeks [time for everyone to have a quick holiday], they are locked
from further followups.

A lot of later follow-ups are really 'new questions' on a related theme. If they
got forced to go the 'new question' route, you'd have more time for new
questions.

          Followup:

that -- that is a good idea, i'll debate it with others and see what we think.

I'm sort of partial to that one ...

      Rating:
      2
But...  August 31, 2003
Reviewer:
      Matt  from Australia
..some of the later additions add additional clarity to the original post.

Tom, I understand that you may want to manage this a little more closely (to
avoid updates really being new posts, and also avoid having to spend lots of
time responding to the updates rather than taking new questions), but the
current format works really well (for me  :o) ).

How about you limit the number of open updates that need a response rather than
limiting each thread with a timeout? This way you will limit the amount of
'catching up' you need to do when you are doing other things.

Just some thoughts.

      Rating: 5
Like the timout
      idea...  September 01, 2003
Reviewer:
      Christoph  from Austria
Hi Tom,

i really like the idea of timing out the threads. Sometimes when one is
searching your site then thread is statring with, say dbms version 8.0.5 and the
desired information is some 30 followups later (for question older than a year
you have to read all the "old" techniques to get information about how it could
be solved in 9iR2). So is would really be pleased if there was something like a
timout to "force" people asking new questions for new versions.

regards christoph
p.s.: really, really like your site - rock on

August 31, 2003 Jeff -- Thanks for the question regarding
      "mail_pkg on p.1233 in 9.2.0", version 9.2.0.1.0

      You Asked
Tom,
I used your package MAIL_PKG on your book p.1233 in 8.1.7, and it worked very
well. But, after I updated our Oracle to 9.2.0, the package doesn't work and I
got errors when compiling it. Could you please take a look on it? Thanks a lot.

Jeff
-------------
Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, Oracle Label Security, OLAP and Oracle Data Mining
options
JServer Release 9.2.0.1.0 - Production
SQL> alter package mail_pkg compile body;

Warning: Package Body altered with compilation errors.

Errors for PACKAGE BODY MAIL_PKG:

LINE/COL ERROR
-------- ------------------------------------------------------------
28/17    PLS-00593: default value of parameter "P_FROM" in body must match that
of spec
32/17    PLS-00593: default value of parameter "P_SUBJECT" in body must match
that of spec
33/17    PLS-00593: default value of parameter "P_BODY" in body must match that
of spec

      and we said...
I made stupid programmer error #1032

See the spec:

tkyte@TKYTE816> create or replace package mail_pkg
  2  as
  3          type array is table of varchar2(255);
  4
  5          procedure send( p_sender_email in varchar2,
  6                          p_from         in varchar2,
  7                          p_to           in array default array(),
  8                          p_cc           in array default array(),
  9                          p_bcc          in array default array(),
 10                          p_subject      in varchar2,
 11                          p_body         in long );
 12  end;
 13  /
Package created

And see the body:

 27  procedure send( p_sender_email in varchar2,
 28                  p_from         in varchar2 default NULL,
 29                  p_to           in array default array(),
 30                  p_cc           in array default array(),
 31                  p_bcc          in array default array(),
 32                  p_subject      in varchar2 default NULL,
 33                  p_body         in long  default NULL )
 34  is

and see that they in fact do not match..... gotta make them match.

Either

a) remove the default for from, subject, body in the BODY or
b) add the default for them in the SPEC

doing a) will make it exactly like it was in 8i.  doing b) will fix it but
change the behavior subtly...

Review &
      Followup

      Rating:
      5
excellent  March 01, 2003
Reviewer:
      Jeff

      Rating: 3
PSP syntax to
      default an array value?  July 31, 2003
Reviewer:
      h  from California
Tom,
What is the PSP syntax to default an array?  I'm trying to prevent an error if
the user selects no values in a multiple dropdown list.

<%@ plsql parameter="in_array" type="pkg1.array1" default="xyz"%>

Thanks,
h

          Followup:

when you created pkg1:

create or replace package pgk1
as
   type array1 is table of number index by binary_integer;

   g_empty_array1 array1;
end;
/

add that variable, and you can:

  default="pkg1.g_empty_array1"

      Rating:
      5
  July 31, 2003
Reviewer:
      A reader
Hi Tom,

What is the change of behavior from 8.1.7 to 9i that caused this problem? Why is
it a problem in 9i and not in 8i to have defaults defined only in pkgbody?

thanks.

          Followup:

they fixed a bug in the language - it *never* should have worked in 8i and
before.

In 8i and before, you could have:

ops$tkyte@ORA817DEV> create or replace package demo_pkg
  2  as
  3          procedure p( x in number default 5 );
  4  end;
  5  /

Package created.

ops$tkyte@ORA817DEV> create or replace package body demo_pkg
  2  as
  3          procedure p( x in number default 55555 )
  4          is
  5          begin
  6                  null;
  7          end;
  8  end;
  9  /

Package body created.

Look at the defaults 5 and 55555 -- confusing to say the least.  It was a bug.
In 9i you get:

ops$tkyte@ORA920LAP> create or replace package demo_pkg
  2  as
  3          procedure p( x in number default 5 );
  4  end;
  5  /

Package created.

ops$tkyte@ORA920LAP> create or replace package body demo_pkg
  2  as
  3          procedure p( x in number default 55555 )
  4          is
  5          begin
  6                  null;
  7          end;
  8  end;
  9  /

Warning: Package Body created with compilation errors.

ops$tkyte@ORA920LAP> show err
Errors for PACKAGE BODY DEMO_PKG:

LINE/COL ERROR
-------- -----------------------------------------------------------------
3/15     PLS-00593: default value of parameter "X" in body must match that
         of spec

      Rating: 5
Empty array
      passing works fine.  July 31, 2003
Reviewer:
      h  from california
As usual, your answer was right on track.  Thanks for the assistance.

      Rating: 3
Correct syntax
      for PSP out parameter declaration?  August 29, 2003

Reviewer:  ht  from California
Tom,
I'm back again.  Can you provide an example on passing parameters back to a
calling PSP proc?

This is what I'm using but I'm not able to capture the returned value:

Processing proc:
<%@ plsql procedure="test_out_parm" %>
<%@ plsql parameter="pass_it_back" type="out varchar2"%>

Calling proc:
<% test_out_parm(l_parm); %>

When loading the psp, I get no errors but the returned value isn't displayed in
the calling psp.

Can procedures with "out" parameters be written/retrieved using PSPs?

TIA
ht

          Followup:

you sort of need to print it out.  you would use htp.p or some other function to
actually "display" it.

think about how ugly pages would be if we just dumped all OUT parameters after a
call in a format we decided was good?  you have to do the formatting.

      Rating: 3
Still not
      returning the stored proc out parameter.  August 31, 2003

Reviewer:  ht  from California
Hi Tom,
The out value isn't being displayed either so I'm wondering if this
functionality even exists in PSP.

Processing proc:
<%@ plsql procedure="test_out_parm" %>
<%@ plsql parameter="pass_it_back" type="out varchar2"%>

Calling proc:
<% test_out_parm(l_parm); %>

<%=l_parm%>

Thanks,
ht

      Rating: 3
Here's my PSP
      code:  August 31, 2003
Reviewer:  ht
       from California
Tom,
I stripped down my code to give you an example of what I'm trying to do.  I'm
trying to return the value of a checkbox using a stored proc that has an "out"
parameter but the value doesn't seem to be returned/printed.  What am I doing
wrong?
TIA,
ht
<%@ page language="PL/SQL" %>

<%@ plsql procedure="test_call_cb1" %>

<%!
cb1 varchar2(10);
%>

<%@ page language="PL/SQL" %>

<%@ plsql procedure="test_cb1" %>

<%@ plsql parameter="cb1" type="out varchar2"%>

<%!
%>

<center>
<head>
</head>
<html>
<body>
<ul>

<table >
<tr>
        <td > <input type="checkbox" name="cb1" value="yes" checked>Checkbox
Test
</table>

</body>
</html>

<center>
<head>
</head>
<html>
<body >
<ul>

<%
test_cb1(cb1);
%>

<table >
<tr>
        <td > Display Checkbox Value:<%=cb1%>
</table>
</body>
</html>

          Followup:

are you thinking that plsql and html are processed concurrently?  not sure what
you are trying to do here really.    but when the page is generated -- whatever
test_cb1 returns is what you get -- happens way before the end user ever sees
the page and will remain static for as long as they have it.

      Rating: 4
Trying to
        August 31, 2003
Reviewer:  ht  from
      California
Tom,
My apologies.  The previous post of my code was done incorrectly, the 2 procs
are below - I also didn't do a good job in explaining my issue.
Based on the user's name, I would like to default a checkbox that is used to
display certain columns in reports they view.  To implement this report
preference, I thought that displaying the test_cb1 proc at the top of each page
and reading it's selected value before returning the report results would be a
good solution.
Basically, a user can elect to view all columns in a table or a minimal amount
of columns.  It seems that my implementation of using a stored proc with an
"out" parameter is flawed.  Can you tell me what I'm doing wrong?

Thanks again.
ht
<%@ page language="PL/SQL" %>

<%@ plsql procedure="test_call_cb1" %>

<%!
cb1 varchar2(10);
%>

<center>
<head>
</head>
<html>
<body >
<ul>

<%
test_cb1(cb1);
%>

<table >
<tr>
        <td > Display Checkbox Value:<%=cb1%>
</table>
</body>
</html>

<%@ page language="PL/SQL" %>

<%@ plsql procedure="test_cb1" %>

<%@ plsql parameter="cb1" type="out varchar2"%>

<%!
%>

<center>
<head>
</head>
<html>
<body>
<ul>

<table >
<tr>
        <td > <input type="checkbox" name="cb1" value="yes" checked>Checkbox
Test
</table>

</body>
</html>

          Followup:

sigh, if you go into sqlplus and just run

variable :x varchar2(25)
exec test_cb1(:x)
print x

what do you see?  I've no idea what test_cb1 does, what value it
supplies/provides.  whatever it spits out in sqlplus is what you can expect to
see here.

      Rating: 3
Not working for
      me in sqlplus.  August 31, 2003
Reviewer:
      ht  from California
I'm not getting this.  I get a "bind variable "X" not declared" error when
entering your test case.
ht

          Followup:

sorry:

variable x varchar2(25)

      Rating: 4
Need to store
      value in the db.  September 01, 2003
Reviewer:
      ht  from California
Tom,
Thanks for your persistence on this matter.  I think I finally have this figured
out.

variable x varchar2(25)
exec test_cb1(:x)

PL/SQL procedure successfully completed.

print x

X
--------------------------------------------------------------------------------

This means to me that I need to write the checkbox value to the database and
retrieve it before each report is executed.  You "concurrency" remark above
pointed me in this direction.  Since the checkbox value is set in a generic
header form, it won't be retrievable using a stored proc "out" parameter.
Instead, it seems that I need to record the value in the database and retrieve
it's current value before each report execution.
Please let me know if I'm on the right track or if there's another way I can
set/read checkbox values using PSP.

Thanks,
ht

September 01, 2003 -- Thanks for the question regarding "Does RULE
      based Optimizer takes into consideration the existing statistics?",
      version 8.0.6

      You Asked
Hi Tom,

We have a database on version 8.0.6 in production with RULE based optimizer.We
are tuning some SQL's of some reports which works on the above database. When
tuning some SQL's(without any hints),we analyzed all the tables in that SQL and
saw a huge gain in performance. Does the RULE based optimizer makes the
execution plan based on the existing statistics in dba_tables, dba_indexes,
dba_histograms?.Please explain.

We are planning to migrate 8.0.6 database into 8.1.7 and change the optimizer
parameter to CHOOSE.We were planning to test on a testing machine for any
performance impacts after changing the optimizer_mode from RULE to CHOOSE.Can
you please give some pointers to what are the things that needs to be looked
after when changing the optimizer mode to CHOOSE.

Thank you for your support.

regards,
Vijaya Chander V.S

      and we said...
when you analyzed, the CBO kicked in -- unless it was really set to RULE.  If
the plans changed -- it was not set to rule and the CBO is being used.

you can use "set autotrace traceonly explain" to find out.

consider:

ops$tkyte@ORA920> create table t ( x int );

Table created.

ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select * from t;

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

ops$tkyte@ORA920> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA920> select * from t;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=13)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=3 Card=1 Bytes=13)

if you see cost/card/bytes -- you are using the CBO.  if not, you are using the
RBO. analyzing means NOTHING to the RBO, it cannot -- willnot -- use any of that
information.

after making an upgrade like that, you will of course need to fully test your
application -- for correctness, for scale -- before inflicting it on your end
users.

Review &
      Followup

      Rating: 4
thank
      you  August 31, 2003
Reviewer:  Vijaya Chander
      V.S  from Melbourne,Australia
Thank you for ur detailed explination. But how can i have init<SID>.ora
parameter optimizer_mode=RULE and the query without hints uses the CBO.We know
that RBO does not use the statistics,but some of my queries executes faster when
i analyze.

Can you please provide me some websites/papers for the optimizer change from
RBO(8.0.6) to CBO(8.1.7) migration.

thank you for ur support.

          Followup:

how about this

delete the stats
enable sql_trace + timed_statistics
run query
exit sqlplus
run tkprof

do the same but analyze the tables instead of delete the stats

post the results.  if you are using RBO, they will be exactly the same.

September 01, 2003 Tia -- Thanks for the question regarding
      "removing a datafile from TEMP ts", version 8i

      You Asked
Hello Tom,

I was trying to remove one of the datafiles in the TEMP tablespace because it
was too small, and typed ALTER DATABASE DATAFILE 'filename' OFFLINE DROP;  I
thought this would then allow me to delete the datafile from Unix later.  But I
forgot to first type ALTER TABLESPACE TEMP OFFLINE;

So now, my v$datafile view shows that the file status is 'RECOVER'.  If I try to
make the datafile come back online I get ORA-01113 "file needs media recovery".
If I try to take the TEMP tablespace offline I get ORA-01191 "file is already
offline - cannot do a normal offline".  So I'm not sure what to try next.  The
db is running okay but my goal is drop and consolidate and resize some datafiles
in the TEMP tablespace.  The db is not in archive mode, so I'm not sure how I
would recover anyway.  Besides, the TEMP ts is now empty of blocks.

Thanks before hand,
Tia

      and we said...
see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:515222144417
datafiles can not be dropped.  The offline drop is not a method to do this.

If a file is too small, you can ALTER it to resize it. (same with too big).
Once a file is in a tablspace -- that file is part of that tablespace forever.

You should drop that tablespace (good thing it is just temp) and start over with
a new one.
Review & Followup

      Rating: 4
Way around to
      remove datafile from TEMP  March 22, 2001

Reviewer:  Bose  from Chicago,US
Since the datafile belongs to TEMP tablespace, you can always drop the TEMP
tablespace including contents and then delete the file/s physically from the OS.

      Rating: 3
what about
      dependencies  April 25, 2002
Reviewer:
      A reader
I have temp tablespace very big compare to the db

also I have all users assigned temp for all temporary work
like sort..

what happen to them if I drop temp ?

          Followup:

They won't be able to sort to disk.  They will get an error if you drop it and
they need to sort to disk.

If you drop it, recreate it right away.

      Rating: 5
resizing
      tempfile in 9.0.2  August 28, 2002
Reviewer:
      A reader
hi

is it possible to resize tempfile in 9.0.2?

I tried:

SQL> shutdown
Database closed.
Database dismounted.
ORACLE instance shut down.
SQL> startup
ORACLE instance started.

Total System Global Area   76618332 bytes
Fixed Size                   453212 bytes
Variable Size              50331648 bytes
Database Buffers           25165824 bytes
Redo Buffers                 667648 bytes
Database mounted.
Database opened.

SQL> alter database tempfile 'D:\ORACLE\ORADATA\PTL920\TEMP01.DBF' resize 500M;
alter database tempfile 'D:\ORACLE\ORADATA\PTL920\TEMP01.DBF' resize 500M
*
ERROR at line 1:
ORA-03297: file contains used data beyond requested RESIZE value

why? I just started the database and temporary tablespace has not been used yet!

          Followup:

If this is a bitmapped temporary tablespace, you'll need to drop it right before
shutting down and just recreate it upon startup.

They cannot be resized.

      Rating: 5
what a pain you
      cannot resize tempfiles in 9.2  September 07, 2002

Reviewer:  A reader
that's really a pain!

any logic why they cannot resized?

          Followup:

If this is a bitmapped temporary tablespace, you'll need to drop it right before

shutting down and just recreate it upon startup.

If you feel that is too much of a restriction, don't use that type of temporary
tablespace.

      Rating: 3
well I can
      resize bitmapped tbsp in 8.1.7  September 26, 2002

Reviewer:  A reader
Hi

you said you cannot resize bitmapped tablespace however I have temporary
tablespace locally managed created as follows:

create temporary tablespace temp
tempfile 'D:\ORACLE\ORADATA\RMAN817\TEMP01.DBF' reuse;

it is exactly the same as a temp tablespace in 9i but in 8i you can resize this
because I have done it but in 9i not. How so? Any difference between temp
tablespace in 8i or 9i? (I dont see any actually, they are both locally managed,
tempfiles)

          Followup:

that wasn't bitmapped in 8i that is why. (not talking about locally managed,
talking about segment space managment, new with 9i)

      Rating:
      3
  September 27, 2002
Reviewer:  Sagi
       from India
Hi Tom.

q1) First of all what is the solutino for Tia's Problem. The link you gave was
very useful indeed. But You have not told what Tia should do. As already he has
executed the below commands on his DB already:

ALTER DATABASE DATAFILE 'filename' OFFLINE DROP;

Now as we know you can not put the DATAFILE to ONLINE or take the TABLESPACE
OFFLINE.

I think the Solution-1 to his problem would be:

ALTER DATABASE RECOVER DATAFILE 'filename' ;
ALTER DATABASE DATAFILE 'filename' ONLINE ;

Now to follow the correct method what you have shown in the link i.e. :

He can create a new TEMPORARY TABLESPACE by deciding the total space he needs.

CREATE TEMPORARY TABLESPACE <NewTempTablespace>......

with adequate space

Once this is over he can issue:

DROP TABLESPACE <OldTableSpaceName>

Please let me know is this the correct approach for his problem.

Lastely, you said

"I have temp tablespace very big compare to the db

also I have all users assigned temp for all temporary work
like sort..

what happen to them if I drop temp ?

Followup:
They won't be able to sort to disk.  They will get an error if you drop it and
they need to sort to disk.

If you drop it, recreate it right away."

Can you give a small example for this. Please.

Note: SORT_AREA_SIZE is the space that determines the space in memory to be used
for operations like UNION, DISTINCT, ORDER BY etc.

If We dont have enough SORT_AREA_SIZE then it creates TEMPORARY SEGMENTS which
are on TEMPORARY TABLESPACE. Am I Right?

Regards,
Sagi

      Rating: 5
dropping a
      temporary tablespace and recreating it  October 31, 2002

Reviewer:  sm  from usa
8.1.7.4.0 on HP-UX database size 2.5 g

I had a temporary tablespace that was created like
CREATE TEMPORARY TABLESPACE TEMP TEMPFILE <dbf_file> SIZE 500M AUTOEXTEND ON
NEXT 50M MAXSIZE 1048576K
EXTENT MANAGEMENT LOCAL
UNIFORM SIZE 64K;

Because of a bad query by a user..I got ORA-1652: unable to extend temp segment
by 8 in tablespace TEMP (in prod)

The size of the tablespace/datafile had gone to it's max
-- 1 g

I tried to resize it in PROD with
ALTER DATABASE TEMPFILE <dbf_file> RESIZE 500M;

It gave me an error:
*
ORA-03297: file contains used data beyond requested RESIZE value

Then I thought let me recreate the TEMP tablespace..because I also wanted to
increase the uniform size of the extent from 64K to 512K (Sort_area_size 128k).
So, I went to an exact replica of our PROD database (TEST database)..and
dropped temp tablespace as:
drop tablespace temp;
and it worked O.K.

then when I tried to create the temp again..it said:
ORA-01119: error in creating database file <dbf_file>
ORA-27038: skgfrcre: file exists

So, I tried to drop the tempfile as:
alter database tempfile <dbf_file> drop;
it gave an error:
ORA-01516: nonexistent log file, datafile or tempfile
<dbf_file>

I shutdown the database and tried the steps to drop the tempfile again..same
error..
v$tempfile and dba_temp_files shows "0 rows selected"

Questions:
1. How much have I screwed up the TEST database?
2. How to proceed with PROD database situation..TEMP tablespace in it is sitting
at 1g (it's max)...
I want to resize it..and change the uniform size to 512K (as suggested by you)
3. Prevention from ora-1652

Thanks a zillion..

          Followup:

$ rm <tmpfilename>

the file still exists in the OS.

or use the REUSE option on the create temporary tablespace .... command to reuse
the existing file (it is trying to protect you from overwriting an existing
file)

      Rating: 5
dropping a
      temporary tablespace and recreating it CONTD.  October 31,
      2002
Reviewer:  SM  from USA
SQLWKS> SELECT * FROM V$SORT_SEGMENT
     2>
TABLESPACE_NAME                 SEGMENT_FI SEGMENT_BL EXTENT_SIZ CURRENT_US
TOTAL_EXTE TOTAL_BLOC USED_EXTEN USED_BLOCK FREE_EXTEN FREE_BLOCK ADDED_EXTE
EXTENT_HIT FREED_EXTE FREE_REQUE MAX_SIZE   MAX_BLOCKS MAX_USED_S MAX_USED_B
MAX_SORT_S MAX_SORT_B RELATIVE_F
------------------------------- ---------- ---------- ---------- ----------
---------- ---------- ---------- ---------- ---------- ---------- ----------
---------- ---------- ---------- ---------- ---------- ---------- ----------
---------- ---------- ----------
TEMP                                     0          0          8          0
16383     131064          0          0      16383     131064      14747
4111023          0          0      16383     131064      16383     131064
16383     131064          0
1 row selected.

      Rating: 3
Data file not
      existent  September 01, 2003
Reviewer:  Ashwani
       from India
Dear Tom,
   V r facing a very funny situation..
1) Select * from dba_temp_files;
FILE_NAME
FILE_ID    TABLESPACE_NAME                BYTES      BLOCKS     STATUS
RELATIVE_F AUT MAXBYTES   MAXBLOCKS  INCREMENT_ USER_BYTES USER_BLOCK
--------------------------------------------------------------------------------
---------- ------------------------------ ---------- ---------- ---------
---------- --- ---------- ---------- ---------- ---------- ----------
/cppf2arch_local/ofin6/app/proddata/temp02.dbf
         1 TEMP                           1073750016     131073 AVAILABLE
  1 NO           0          0          0 1072693248     130944
/cppf2arch_local/ofin7/app/proddata/temp03.dbf
         2 TEMP                           1073750016     131073 AVAILABLE
  2 NO           0          0          0 1072693248     130944
/cppf2arch_local/ofin7/app/proddata/temp01.dbf
         3 TEMP                           1073741824     131072 AVAILABLE
  3 NO           0          0          0 1072693248     130944
3 rows selected.

.. Now i Say

 alter database datafile '/cppf2arch_local/ofin7/app/proddata/temp01.dbf'
     2> offline drop;
alter database datafile '/cppf2arch_local/ofin7/app/proddata/temp01.dbf'
*
ORA-01516: nonexistent log file, datafile or tempfile
'/cppf2arch_local/ofin7/app/proddata/temp01.dbf'

What is wrong in that ???
I am trying to drop a temp file because it is corrupted..

Pls guide..
Regards,
Ashwani.

          Followup:

well, the correct approach would be to DROP THE TABLESPACE, not the datafile

and to drop a single TEMPfile, you use TEMPfile, not DATAfile.  you asked it to
drop the datafile 'foo' and there isn't one, there is a tempfile 'foo' -- but
don't drop IT, drop the tablespace.

      Rating: 5
to drop
      tempfiles  September 01, 2003
Reviewer:
      A reader
ALTER DATABASE TEMPFILE 'xxx.dbf' DROP;

then simply add another one (temp file of course)

September 01, 2003 Irshad -- Thanks for the question regarding
      "Automatic startup & shutdown of database.", version 8.0.5

      You Asked
Hi,

Thanks very much for answering my earlier question about
Database startup parameter file.

In connection with the same question, I want to know the
function of file strtorcle.cmd on my NT box in
$ORACLE_HOME\database directory.

Also, how can I automate the automatic startup & shutdown of my
oracle instance on UNIX and NT during boot of OS.

Thanks.

Irshad.

      and we said...
the strt$ORACLE_SID.cmd file (there will be one per database on
an NT machine with 8.0) is the command script that is used to
start the database up.  It just has the call to oradimXX.exe to
start the database.

This script is obsoleted in 8.1 and up.

To autostart the databases on NT, just use the control panel and
set the OracleStart services to "automatic".  Then, the
requisite services will get started and Oracle will start on a
reboot.

On Unix, it varies depending on the OS.  See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:387218751430
for a common method.
Review & Followup

      Rating: 2
Automatically
      starting the DB  November 01, 2002
Reviewer:
      Wilson  from Zambia
I have a problem on Win2000 with this whenever the server restarts, the db does
not yet the services are started. Secondly, when another user with
administrative rights log into the Server after a reboot, the services will be
started but the database will fail to open. Until the users who installed the db
logs on.

          Followup:

Using the windows admin guide, you can find out how to use ORADIM to set the
autostart property of your database to true:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/win.817/a73008/ch5.htm#1027981
it is set to start the services but not the database by default.

      Rating: 2
Automatically
      starting the DB  November 01, 2002
Reviewer:
      Wilson  from Zambia
I have a problem on Win2000 and 8.1.7 with this whenever the server restarts,
the db does not start automatically yet the services are showing as started.
Secondly, when another user with administrative rights log into the Server after
a reboot, the services will be started but the database will fail to open. Until
the user who installed the db logs on and then manually start the database.

      Rating: 4
Oracle not
      starting automatically in WIN NT/2000  September 01, 2003

Reviewer:  A reader
I have installed Oracle 8.1.7 in Windows 2000. When I start the windows OS I
find the services have been started, but Oracle is not running. So I have to run
svrmgrl and startup the database. In 9i on Windows also this problem is
happening. How to make Oracle start automatically.

          Followup:

read the nt admin guides (all available on otn.oracle.com)  they describe how to
use oradim to set the autostart'ness of your database.

September 01, 2003 Simon -- Thanks for the question regarding
      "Loading XML file to a table via DBMS_XMLSave.insertXML", version 9.0.1

      You Asked
Tom,

It may be something obvious I'm missing so do tell me to sod off, but I'm
stucker than someone that's stood on a tube of superglue.

I have a problem. I'm trying to show it's possible to load an XML file, parse
it, and put the documents fields into a table.

I can load the file into memory, but when I parse it I get a java erro that the
root element cannot be found by 'DBMS_XMLSave.insertXML', i.e. the java based
XML SQL Utility (XSU) cannot read the file.

The best I can come up with is that the file I'm loading is coming via a BFILE
and is not being translated properly before it's put into the CLOB. I.e. it's
still binary and not being converted into char.

If this is the problem, can you see (without coding something in C using OCI)
how I can convert so it can be read.

Additional info - I'm using Oracle 9i, and NLS_CHARACTERSET is set to UTF8
(cause we have to cope with welsh).

==============set up==============
This is the file I want to put in a table
-------------------------
<?xml version="1.0"?>
<ROWSET>
        <ROW num="1">
                <SURNAME>Rushton</SURNAME>
                <FORENAMES>Simon</FORENAMES>
                <DOB>30/01/1975</DOB>
                <CHARGE>Eating too much cake</CHARGE>
        </ROW>
</ROWSET>
-------------------------

This is the directory it lives in
(GRANT ANY DIRECTORY to <user>)
CREATE DIRECTORY testdir as '<location of XML file>'

This creates the table I want to populate
-------------------------
CREATE TABLE rushtons.defendant
(surname VARCHAR2(20) NOT NULL
,forenames VARCHAR2(20)
,dob DATE
,charge VARCHAR2(40))
/
-------------------------

This creates the SP to insert the xml into the table
-------------------------

CREATE OR REPLACE PROCEDURE testInsert( xmlDoc IN CLOB) IS
  insCtx DBMS_XMLSave.ctxType;
  doc CLOB;
  rows NUMBER;
BEGIN

  insCtx := DBMS_XMLSave.newContext('rushtons.defendant'); -- get the save
context

  --FALLS OVER HERE
   rows := DBMS_XMLSave.insertXML(insCtx, xmlDoc);

   DBMS_XMLSave.closeContext(insCtx);

END;
/-------------------------

And this is the fella that loads a file into a CLOB, from the file system via a
BFILE
-------------------------
CREATE OR REPLACE PROCEDURE run AS
  xml_file CLOB;
  fils  BFILE := BFILENAME ('TESTDIR', 'acase.xml');
BEGIN

  DBMS_LOB.CREATETEMPORARY(xml_file, TRUE);

   IF (DBMS_LOB.FILEEXISTS(fils) != 0)
   THEN
      DBMS_OUTPUT.PUT_LINE('BFILE exists');
   ELSE
      DBMS_OUTPUT.PUT_LINE('BFILE does not exist');
   END IF;

   IF (DBMS_LOB.FILEISOPEN(fils) = 1)
   THEN
      DBMS_OUTPUT.PUT_LINE('BFILE is open');
   ELSE
      DBMS_OUTPUT.PUT_LINE('BFILE is not open');
   END IF;

  DBMS_LOB.FILEOPEN(fils, DBMS_LOB.FILE_READONLY);

   IF (DBMS_LOB.FILEISOPEN(fils) = 1)
   THEN
      DBMS_OUTPUT.PUT_LINE('BFILE is open');
   ELSE
      DBMS_OUTPUT.PUT_LINE('BFILE is not open');
   END IF;

-- CHARACTER SET CONVERSION REQUIRED HERE?:
--      from
http://download-uk.oracle.com/otndoc/oracle9i/901_doc/appdev.901/a888
79/adl02bs3.htm

  DBMS_LOB.LOADFROMFILE(xml_file, fils, DBMS_LOB.GETLENGTH( fils ) );

  testInsert(xml_file);

  DBMS_LOB.FILECLOSE(fils);

END;
-------------------------

And this is the error message I get
-------------------------

SQL> exec run
BEGIN run; END;

*
ERROR at line 1:
ORA-29532: Java call terminated by uncaught Java exception:
oracle.xml.sql.OracleXMLSQLException: Start of root element expected.
ORA-06512: at "SYS.DBMS_XMLSAVE", line 27
ORA-06512: at "RUSHTONS.TESTINSERT", line 19
ORA-06512: at "RUSHTONS.RUN", line 37
ORA-06512: at line 1
-------------------------

      and we said...
I asked Sean Dillon, our local XML guy to take a look at this.  Here's what he
had to say:
---------------------------------------------------------

It's failing because you're trying to load a BFILE into a UTF-8 database.
Oracle is reading the file and the file's not UTF-8, therefore Oracle doesn't
recognize the data as being XML.  I tried your code above and it works fine in a
standard CHARSET 9i database. (one problem with your XML, DOB had to be
"30/01/1975 0:0:0").

If you want to pursue the BFILE approach, I'd say that's a support issue.  Call
Oracle support and send them your test case.

I worked around this in my UTF-8 database by using Java to load the XML into a
CLOB in the database, then using a stored procedure in the database to read the
CLOB and use DBMS_XMLSAVE to parse it into a table.  Here's the scripts I used
to accomplish this:

=============================================================
create table emp
as select * from scott.emp where 1=0
/

REM ************************************************
REM *
REM * Setup a PL/SQL function to insert XML data
REM * into the EMP table. This function assumes ALL
REM * columns are present in the table.
REM *
REM ************************************************

create table tmp_clob(
  id      number,
  theclob clob)
/

create sequence tmp_clob_seq
/

create trigger bi_tmp_clob
before insert
on tmp_clob
for each row
begin
  select tmp_clob_seq.nextval
    into :new.id
    from dual;
end;
/

create or replace function insert_xml_emps(
  p_tablename  in varchar2,
  p_in_tmpclob in number)
  return number
is
  l_ctx    dbms_xmlsave.ctxType;
  l_rows   number;
begin
  for c1 in (select theclob
               from tmp_clob
              where id = p_in_tmpclob) loop

    l_ctx := dbms_xmlsave.newContext(p_tableName);
    l_rows := dbms_xmlsave.insertxml(l_ctx,c1.theclob);
    dbms_xmlsave.closeContext(l_ctx);

    delete from tmp_clob
     where id = p_in_tmpclob;
  end loop;
  return l_rows;
end insert_xml_emps;
/

THEN I used this Java class to insert the file into the TMP_CLOB table, get the
ID, and call the stored procedure to process the CLOB into the EMP table:

import java.io.*;
import java.sql.*;
import oracle.jdbc.driver.*;
import oracle.xml.sql.*;
import oracle.sql.*;

/**
 *
 * This class responsible for accepting a username, password, and filename
 * and inserting the file into a temporary clob table in the database.
 * Once the clob is inserted, we call a PL/SQL stored procedure based on
 * the XML SQL Utility to parse the CLOB as XML, and insert it into the
 * EMP table.
 *
 */
public class InsertEmps {

  /* YOU'LL have to change these connection properties */
  static String conStr = "jdbc:oracle:thin:@138.1.122.165:1521:9i_utf8";

  public static void main(String args[])
    throws SQLException, FileNotFoundException, IOException {

    if (args.length != 3) {
      System.out.println("\nUsage:\n java InsertEmps <username> <password>
<filename>\n");
      System.exit(0);
    }

    DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
    Connection conn = DriverManager.getConnection(conStr, args[0], args[1]);
    conn.setAutoCommit(false);

    String sql =
      "begin insert into tmp_clob(theclob) " +
      "values (empty_clob()) return theclob,id into ?,?; end;";
    OracleCallableStatement ocs =
(OracleCallableStatement)conn.prepareCall(sql);
    ocs.registerOutParameter(1, OracleTypes.CLOB);
    ocs.registerOutParameter(2, OracleTypes.NUMBER);
    ocs.executeUpdate();
    oracle.sql.NUMBER clobId = ocs.getNUMBER(2);

    InputStream is = new FileInputStream(args[2]);
    OutputStream os = (ocs.getCLOB(1)).getAsciiOutputStream();

    byte[] buf = new byte[1024];
    int length;

    while ((length = is.read(buf, 0, 1024)) != -1) {
        os.write(buf, 0, length);
    }

    is.close();
    os.close();
    ocs.close();

    OracleCallableStatement ocs2 =
      (OracleCallableStatement)conn.prepareCall("begin ? := insert_xml_emps(?,
?); end;");
    ocs2.registerOutParameter(1, OracleTypes.NUMBER);
    ocs2.setString(2,"EMP");
    ocs2.setNUMBER(3, clobId);
    try {
      ocs2.execute();
      System.out.println("Inserted " + (ocs2.getNUMBER(1)).longValue() + "
records.");
    } catch (Exception e) {
      e.printStackTrace();
    }

    ocs2.close();
    conn.commit();
    conn.close();
  }
}

Given an XML document that looks like this:
<?xml version = '1.0'?>
<ROWSET>
   <ROW num="1">
      <EMPNO>7369</EMPNO>
      <ENAME>SMITH</ENAME>
      <JOB>CLERK</JOB>
      <MGR>7902</MGR>
      <HIREDATE>12/17/1980 0:0:0</HIREDATE
      <SAL>800</SAL>
      <DEPTNO>20</DEPTNO>
   </ROW>
   <ROW num="2">
      <EMPNO>7499</EMPNO>
      <ENAME>ALLEN</ENAME>
      <JOB>SALESMAN</JOB>
      <MGR>7698</MGR>
      <HIREDATE>2/20/1981 0:0:0</HIREDATE>
      <SAL>1600</SAL>
      <COMM>300</COMM>
      <DEPTNO>30</DEPTNO>
   </ROW>
   <ROW num="3">
      <EMPNO>7521</EMPNO>
      <ENAME>WARD</ENAME>
      <JOB>SALESMAN</JOB>
      <MGR>7698</MGR>
      <HIREDATE>2/22/1981 0:0:0</HIREDATE>
      <SAL>1250</SAL>
      <COMM>500</COMM>
      <DEPTNO>30</DEPTNO>
   </ROW>
   <ROW num="4">
      <EMPNO>7566</EMPNO>
      <ENAME>JONES</ENAME>
      <JOB>MANAGER</JOB>
      <MGR>7839</MGR>
      <HIREDATE>4/2/1981 0:0:0</HIREDATE>
      <SAL>2975</SAL>
      <DEPTNO>20</DEPTNO>
   </ROW>
   <ROW num="5">
      <EMPNO>7654</EMPNO>
      <ENAME>MARTIN</ENAME>
      <JOB>SALESMAN</JOB>
      <MGR>7698</MGR>
      <HIREDATE>9/28/1981 0:0:0</HIREDATE>
      <SAL>1250</SAL>
      <COMM>1400</COMM>
      <DEPTNO>30</DEPTNO>
   </ROW>
   <ROW num="6">
      <EMPNO>7698</EMPNO>
      <ENAME>BLAKE</ENAME>
      <JOB>MANAGER</JOB>
      <MGR>7839</MGR>
      <HIREDATE>5/1/1981 0:0:0</HIREDATE>
      <SAL>2850</SAL>
      <DEPTNO>30</DEPTNO>
   </ROW>
   <ROW num="7">
      <EMPNO>7782</EMPNO>
      <ENAME>CLARK</ENAME>
      <JOB>MANAGER</JOB>
      <MGR>7839</MGR>
      <HIREDATE>6/9/1981 0:0:0</HIREDATE>
      <SAL>2450</SAL>
      <DEPTNO>10</DEPTNO>
   </ROW>
   <ROW num="8">
      <EMPNO>7788</EMPNO>
      <ENAME>SCOTT</ENAME>
      <JOB>ANALYST</JOB>
      <MGR>7566</MGR>
      <HIREDATE>4/19/1987 0:0:0</HIREDATE>
      <SAL>3000</SAL>
      <DEPTNO>20</DEPTNO>
   </ROW>
   <ROW num="9">
      <EMPNO>7839</EMPNO>
      <ENAME>KING</ENAME>
      <JOB>PRESIDENT</JOB>
      <HIREDATE>11/17/1981 0:0:0</HIREDATE>
      <SAL>5000</SAL>
      <DEPTNO>10</DEPTNO>
   </ROW>
   <ROW num="10">
      <EMPNO>7844</EMPNO>
      <ENAME>TURNER</ENAME>
      <JOB>SALESMAN</JOB>
      <MGR>7698</MGR>
      <HIREDATE>9/8/1981 0:0:0</HIREDATE>
      <SAL>1500</SAL>
      <COMM>0</COMM>
      <DEPTNO>30</DEPTNO>
   </ROW>
   <ROW num="11">
      <EMPNO>7876</EMPNO>
      <ENAME>ADAMS</ENAME>
      <JOB>CLERK</JOB>
      <MGR>7788</MGR>
      <HIREDATE>5/23/1987 0:0:0</HIREDATE>
      <SAL>1100</SAL>
      <DEPTNO>20</DEPTNO>
   </ROW>
   <ROW num="12">
      <EMPNO>7900</EMPNO>
      <ENAME>JAMES</ENAME>
      <JOB>CLERK</JOB>
      <MGR>7698</MGR>
      <HIREDATE>12/3/1981 0:0:0</HIREDATE>
      <SAL>950</SAL>
      <DEPTNO>30</DEPTNO>
   </ROW>
   <ROW num="13">
      <EMPNO>7902</EMPNO>
      <ENAME>FORD</ENAME>
      <JOB>ANALYST</JOB>
      <MGR>7566</MGR>
      <HIREDATE>12/3/1981 0:0:0</HIREDATE>
      <SAL>3000</SAL>
      <DEPTNO>20</DEPTNO>
   </ROW>
   <ROW num="14">
      <EMPNO>7934</EMPNO>
      <ENAME>MILLER</ENAME>
      <JOB>CLERK</JOB>
      <MGR>7782</MGR>
      <HIREDATE>1/23/1982 0:0:0</HIREDATE>
      <SAL>1300</SAL>
      <DEPTNO>10</DEPTNO>
   </ROW>
</ROWSET>

Using the Java class looks something like this:

SQL> select * from emp;
no rows selected

SQL> exit
Disconnected from Oracle9i Enterprise Edition Release 9.0.1.2.1 - Production
With the Partitioning option
JServer Release 9.0.1.2.0 - Production

C:\Documents and Settings\sdillon\My
Documents\Oracle\Training\9i_Class\oldscripts>java InsertEmps sdillon sdillon
emp.xml
Inserted 14 records.

C:\Documents and Settings\sdillon\My
Documents\Oracle\Training\9i_Class\oldscripts>sqlplus sdillon/sdillon@9i_utf8

SQL*Plus: Release 9.0.1.0.1 - Production on Wed Feb 27 15:07:23 2002

(c) Copyright 2001 Oracle Corporation.  All rights reserved.
Connected to:
Oracle9i Enterprise Edition Release 9.0.1.2.1 - Production
With the Partitioning option
JServer Release 9.0.1.2.0 - Production

SQL> select * from emp;

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

14 rows selected.

Hope that helps!
Review & Followup

      Rating: 4
Loading XML
      file to a table via DBMS_XMLSave.insertXML  February 28,
      2002
Reviewer:  Simon Rushton  from london, UK

Tom, Sean,

Thanks sooo much for putting the time in to look at my problem. I appreciate it.

I have a reply - I'll mail it to thomas.kyte@oracle.com.

Cheers,

Simon

      Rating: 4
Inserting
      NON-UTF8 into UTF8 *requires* Java?  October 31, 2002

Reviewer:  Basil
If I read Sean Dillon's answer correctly, if I have a 9i database using UTF8 as
the character set, I'm *forced* to use Java to load XML documents from the
filesystem into the database?

I've got the following function (gratuitously ripped off
from the XML DB developer guide, but with some kind
of naming convention imposed on it):

CREATE OR REPLACE FUNCTION getDocument( p_filename IN VARCHAR2,
        p_dirname IN VARCHAR2 DEFAULT 'XMLDIR' )
    RETURN CLOB
IS
    v_bfile bfile;
    v_clob clob;
BEGIN
    v_bfile := BFILENAME( p_dirname, p_filename );
    DBMS_LOB.OPEN( v_bfile );
    DBMS_LOB.CREATETEMPORARY( v_clob, TRUE, DBMS_LOB.SESSION );
    DBMS_LOB.LOADFROMFILE( v_clob, v_bfile, DBMS_LOB.GETLENGTH( v_bfile ) );
    DBMS_LOB.CLOSE( v_bfile );
    RETURN v_clob;
END getDocument;
/

  1* select dbms_lob.substr(getDocument( 'Test.xsl'),1,200) from dual;

DBMS_LOB.SUBSTR(GETDOCUMENT('TEST.XSL'),1,80)
--------------------------------------------------------------------------------
¿

My database is set up for UTF-8. The file, of course, is
in the default character set for my machine, which
runs Windows 2000.

Can this work, or must I really endure the headache of
connecting, preparing, binding, and all that other junk
I use PL/SQL to avoid? If the latter, I think Oracle needs to reconsider their
recent moves to push PL/SQL to the status of second-class language.

      Rating: 5
Which is the
      right way to convert XML data into ROWS?  April 16, 2003

Reviewer:  Mihail Daskalov  from Bulgaria
We have some concerns loading data from XML into oracle table.
The XML structure is not like the one expected from insertXML procedure.
instead of
<ROWSET>
        <ROW num="1">
                <SURNAME>Rushton</SURNAME>
                <FORENAMES>Simon</FORENAMES>
                <DOB>30/01/1975</DOB>
                <CHARGE>Eating too much cake</CHARGE>
        </ROW>
</ROWSET>
we have structure similar to
<DATA>
  <timeseries>
    <series_key>
      <dimension>USD</dimension>
      <dimension>S312</dimension>
      <dimension>CODE_635</dimension>
    </series_key>
    <observation>
      <single_period>
        <period_value>200304</period_value>
      </single_period>
      <value>87345.34</value>
      <status>A</status>
    </observation>
   </timeseries>
   <!-- many other time series -->
</ROWSET>

The structure comes from not-yet-standard GESMES (General Statistical Message)
as XML.

Which would be the right way to load the information in the database.
case 1, transform it somehow with XSL, and use insertXML
case 2, use extract with XPATH to get the values from the database.

I wrote a query

SELECT
extract(value(d),'//series_key/dimension[1]/text()').getStringVal() as dim1,
extract(value(d),'//series_key/dimension[2]/text()').getStringVal() as dim2,
extract(value(d),'//series_key/dimension[3]/text()').getStringVal() as dim3,
extract(value(d),'//value/text()').getStringVal() as val
FROM xml_test t,
TABLE(XMLSequence(XMLType(t.contents).extract('//data/timeseries'))) d;

table xml_test contains 1 rows, and the contents column is CLOB.

It seems to do what I expect, but it is quite slow.
Processing a 2MB file takes more than 3 minutes on a P4 to produce 7000 rows.
Is there way to speed it up?
What if I load it with XML Schema which is registered in XML DB? Will it help?
How to do it?

About case 1:
someone mentioned XSLT is not good for large XML files, because the whole
document must be in memory.

http://marvel.oracle.com/pls/otn/f?p=17000:8:::::F17000_P8_DISPLAYID:1520615643

Thank you Tom ,
for providing the best answers for the Oracle Developer Community

      Rating: 5
Loading
      XML  June 27, 2003
Reviewer:  Fred Howell
       from Merrimack, NH
I am just starting to work with XML and Oracle. I wasn't aware of DBMS_XMLSAVE
but it clearly resolved my problem of loading XML data into Oracle. I am not
working on a tool to use AQ and XML to pass messages between databases. The XML
adds a level of flexibility to the message as i don't have to be concerned about
the structure of the payload as i would be an object_type. XML in a CLOB really
make my life easier. Thanks for the tip!

      Rating: 4
Problems
      loading XML document using DBMS_LOB.LOADCLOBFROMFILE  July
      02, 2003
Reviewer:  saj  from New york, USA

i am using oracle 9i r2 and have used the dbms_lob/dbms_xmlgen  packages to
create XML files. These XML files are then sent to translators and we have to
load them back into the database.

My problem is that using DBMS_LOB.LOADCLOBFROMFILE is not returning the entire
XML document to me. It is truncating the document

Here are the procedures

PROCEDURE  p_Read_Text_From_File
     ( p_directory  IN  VARCHAR2
      ,p_filename   IN  VARCHAR2
   ,p_clob    OUT  CLOB
   ,p_debug   IN  BOOLEAN DEFAULT FALSE
   ,p_Errnum   OUT NUMBER )
IS

 vClob      CLOB;
 vInFile    BFILE;
 vErrNum    NUMBER;
 vdest  INTEGER := 1;
 VSRC  INTEGER := 1;
 vWarn  INTEGER;
 vLang  INTEGER := 0;
 vMaxSize NUMBER  := DBMS_LOB.LOBMAXSIZE;
BEGIN

  p_Errnum := 1;

  IF p_directory is NULL or LENGTH(p_directory) = 0 THEN
    p_ErrNum := -20004;
    RAISE_APPLICATION_ERROR(p_ErrNum, 'Please provide the name of the directory
from where to read the File.');
  END IF;

  IF p_filename is NULL or LENGTH(p_filename) = 0 THEN
   p_ErrNum := -20004;
    RAISE_APPLICATION_ERROR(p_ErrNum, 'Please provide the name of the file to be
read.');
  END IF;

  IF p_Debug THEN
     DBMS_OUTPUT.PUT_LINE('directory = ' ||p_directory || '; filename = ' ||
p_fileName);
  DBMS_OUTPUT.PUT_LINE('vMaxSize = ' ||vMaxSize);
  END IF;

 vClob := Empty_Clob();
 vClob := ' ';
 vInFile := bfilename( p_Directory, p_filename);

 dbms_lob.fileopen( vInfile, dbms_lob.file_readonly);

 if p_Debug then
  dbms_output.put_line('filesize=' ||  dbms_lob.getlength(vInFile));
 end if;

 DBMS_LOB.LOADCLOBFROMFILE (vClob,vInfile, dbms_lob.LOBMAXSIZE,vdest,vsrc,
0,vLang,vWarn);

 dbms_lob.fileclose( vInFile );

 IF p_debug THEN
   dbms_output.PUT_LINE('clobsize=' || dbms_lob.GETLENGTH(vClob));
 END IF;

 p_Clob :=  vClob;

EXCEPTION
 WHEN OTHERS THEN
  p_ErrNum := PKG_UTILITIES.WW_ERROR('PKG_XML_UTILS', 'p_Read_Text_From_File ',
SQLCODE, SQLERRM);
END p_Read_Text_From_File ;

PROCEDURE p_READ_XML(pTableName IN VARCHAR2 default null)
IS
  vTableName  VARCHAR2(50) := pTableName;
  vDataClob   CLOB;
  vFileName   VARCHAR2(50);
  vFileExt    VARCHAR2(4) := '.xml';
  vDir        VARCHAR2(25) default 'GMF_XML';
  vSQL        VARCHAR2(32767);
  v_Errnum   NUMBER;
  LCLOB    CLOB;
  vID    number;
  --vDate    varchar2(8) default to_char(sysdate, 'YYYYMMDD');

BEGIN

 IF vTableName IS NULL THEN
  vTableName := 'COURSE';
 END IF;

 vFileName := vTableName || vFileExt;

 PKG_XML_UTILS.p_Read_Text_From_File
     ( vDir,vfilename,vDataClob,TRUE,v_Errnum);

 dbms_output.put_line('Size of dataclob=' || dbms_lob.GETLENGTH(vDataClob));

 IF v_ErrNum = 1 THEN
   SELECT tmp_clob_seq.nextval INTO vID FROM DUAL;
   insert into tmp_clob values (vID, vDataClob);-- returning THECLOB into lclob;
   IF SQL%ROWCOUNT > 0 THEN
     COMMIT;
   END IF;

   SELECT THECLOB INTO LCLOB FROM TMP_CLOB WHERE ID = vID;

   DBMS_OUTPUT.PUT_LINE('THECLOB=' || DBMS_LOB.GETLENGTH(LCLOB));

 END IF;

END p_Read_XML;

The actual File system size of the ARTICLE.XML is 5907 KB
5097*1094

Here is the output of the sizes from DBMS_OUTPUT

directory = GMF_XML; filename = ARTICLE.xml
vMaxSize = 4294967295 (LOBMAXSIZE)

filesize=6047883

clobsize=6047876

Size of dataclob=6047876

THECLOB=6047876

Can you tell me why is my clob not getting the complete file? ( it is less than
lobmaxsize)

          Followup:

try this:

    dbms_lob.loadfromfile( l_blob, l_bfile,
                           dbms_lob.getlength( l_bfile ) );

much easier -- rather than me debugging your code, give that a try first.

      Rating: 3
The original
      code that I submitted works fine  July 02, 2003

Reviewer:  saj  from New York, USA
The original code that I submitted works fine
and it worked because, I opened the XML document in Notepad and used save as to
save in UTF_8 encoding. (I can't do this for every single file because there
will be hundreds in the directory).

The file was then read properly, no truncation occured
This XML document was created using the
following procedure? How can I get my XML generated in UTF_8 encoded format?

v_CTX := DBMS_XMLGEN.newContext(p_sqltext);
DBMS_XMLGEN.setConvertSpecialChars (v_CTX, FALSE);
dbms_lob.createtemporary(v_clob,true,dbms_lob.session);
v_CLOB := DBMS_XMLGEN.getXml(v_CTX);
dbms_xmlgen.CLOSECONTEXT(v_ctx);

and to write the xml to file I use
 v_Amt         NUMBER DEFAULT 255;
 v_Offset      NUMBER DEFAULT 1;

 v_File := utl_file.fopen(v_directory, p_fileName, p_mode, 32767);

  while ( v_Offset < v_Length )
  loop
      utl_file.put(v_File, dbms_lob.substr(v_Clob,v_Amt,v_Offset) );
      utl_file.fflush(v_File);
      v_Offset := v_Offset + v_Amt;
  end loop;
  utl_file.new_line(v_File);
  utl_file.fclose(v_File);

How can I get UTL_FILE to write the file in UTF_8 format?

          Followup:

utl_file uses the databases character set.

      Rating: 4
thanks for the
      prompt response  July 02, 2003
Reviewer:
      saj  from New York, USA
I ran the following SQL and found that my database is UTF8

select * from nls_database_parameters where parameter like '%CHARACTERSET';

NLS_CHARACTERSET= UTF8
NLS_NCHAR_CHARACTERSET= UTF8
and
NLS_LANGUAGE = AMERICAN

I found a similar problem in this page/forum. Is it related to my problem (there
was no response to this thread)
Inserting NON-UTF8 into UTF8 *requires* Java?  October 31, 2002
Reviewer:  Basil

Also what I noticed is that if the XML or the data from SQL that generates the
XML have special characters, the file is written as a UTF-8 encoded file. If it
doesn't, it is plain ASCII file.

And my issue is that even if it is a UTF-8 file I still have to open the file in
Notepad/do a save as UTF-8. Then read the XML and use DBMS_XMLSave to insert
into the db.

The exception I get is :
Unhandled Error in p_Load_XML_To_Table - ORA-29532: Java call terminated by
uncaught Java exception: oracle.xml.sql.OracleXMLSQLException: Invalid char in
text.

If I manually save the file as UTF_8, then I don’t get the exception mentioned
above.

          Followup:

I'll have to refer you to support -- I haven't really worked through this
situation myself.

      Rating: 4
Thanks a
      million  July 02, 2003
Reviewer:  SAJ
       from new york, USA
Appreciate all the time spent.

I am hoping our DBA's will be able get this resolved from Oracle support or
metalink (as they call it!)

      Rating: 3
Put BLOB into
      XML ?  September 01, 2003
Reviewer:  Sasa
       from Belgrade, Serbia
Hi Tom

I found in doc that only way to load blob in XML is as follows(in FAQ):

"Answer: XML requires all characters to be interpreted, therefore there is no
provision for including raw binary data in an XML document. That being said, you
can UUENCODE the data and include it in a CDATA section. The limitation on the
encoding technique is to be sure it only produces legal characters for a CDATA
section."

Could you please provide some example for that?

Current release Oracle 9.2.0.4

Regards

          Followup:

there are many uuencode (or base64 encode -- even better) in java.  tons of
them.  you just need to look a bit.

      Rating:
      5
  November 03, 2003
Reviewer:
      RICHARD  from WASHINGTON
Hi Tom,

Related to the top question, does XDK 9.2.0.6 (most recent) resolve the issue of
non-utf-8 xml file upload into utf-8 database as we discussed many times before?

Please advice what is the solution.  I hope by installing XDK 9.2.06, uploading
non-utf-8 xml file is not an issue anymore, please confirm.

Thanks for help.

Richard

September 01, 2003 Senthil -- Thanks for the question regarding
      "using execute immediate", version 8.1.7

      You Asked
Tom...
a quick question to you about sql datatypes error i am getting.

here are the steps...

SQL> create or replace package pkg_ref_cursor as
  2     type refcursor is ref cursor;
  3  end;
  4  /

Package created.

SQL> create or replace procedure get_row(p_row_out out pkg_ref_cursor.refcursor)
as
  2  begin
  3     open p_row_out for select * from tab where rownum < 5;
  4  end;
  5  /

Procedure created.

SQL> var r refcursor
SQL> exec get_row(:r);

PL/SQL procedure successfully completed.

SQL> print r

TNAME                          TABTYPE  CLUSTERID
------------------------------ ------- ----------
ACTIVE_PACKAGES                TABLE
ADMIN_ACTIVITY                 TABLE
CDA_ATTRIBUTES                 TABLE
CDA_NODES                      TABLE

--it works fine upto this step...

SQL> get chk.sql
  1  create or replace procedure master_get_row (p_identify in varchar2,
p_row_out out pkg_ref_cursor.refcursor) as
  2     l_sql_string varchar2(512);
  3  begin
  4     if p_identify = 'get_row' then
  5        l_sql_string := 'begin get_row(:x); end;';
  6        execute immediate l_sql_string using out p_row_out;
  7     end if;
  8  end;
  9  /
 10* show errors;
SQL> @chk.sql

Warning: Procedure created with compilation errors.

Errors for PROCEDURE MASTER_GET_ROW:

LINE/COL ERROR
-------- -----------------------------------------------------------------
6/7      PL/SQL: Statement ignored
6/48     PLS-00457: expressions have to be of SQL types

I am just trying to call the procedure from another procedure. And, i just
showed an example of part of our environment. we are going through a lot of
if-then's in the procedure "master_get_row", to get the procedure
name("get_row") and call it finally, which returns result set. We would be
having a lot of procedures like "get_row", which will be accessing information
for various conditions from various tables. The point is, it returns the result
set and we collect it into a weekly defined cursor. We need to return the
(received)result set back to the application. would it be possible to do it
using 'Execute immediate', instead of going through a lot of if-then's. if not,
is there any workaround to solve this.

      and we said...
Not possible.  Sounds like a very "obscure" approach to the problem -- to
dynamically call a process that returns a result set based on a data value in a
row (hard to understand and maintain code like that after a while, after the
person that wrote it moves on)...

You cannot do that with a ref cursor.  You would have to use a DBMS_SQL cursor
(which is just a number) instead.
Review &
      Followup

      Rating:
      5
Thanks...  March 18, 2002
Reviewer:
      Senthil  from Massachusetts, USA
Thank you for your response....

Could you kindly give me an example of how to achieve the same using dbms_sql.
Also, is dbms_sql supported in 8.1.7 & 9i. Do you see any downsides to using
dbms_sql. Also, i noticed in your response, that this is an obscure way of
solving the problem. what way would you suggest we use? This problem is critical
to our system and your suggestions would be greatly appreciated.

Many thanks.

          Followup:

When I say "obscure", it's that code that dynamically builds and runs other bits
of code at runtime, while very flexible -- is frequently "obscure" -- hard to
tune, hard to debug -- hard to figure out whats up.

DBMS_SQL is fully supported in all releases since 7.1 on up.

If you search this site for DBMS_SQL, you'll see tons of examples.

      Rating: 5
can i convert
      this?  March 18, 2002
Reviewer:  Senthil
       from Massachusetts, USA
Tom-

Sorry...to bother you again. One last question. I am trying to use dbms_sql to
return the resultset back to the application. I am using the following code...

create or replace procedure get_hit_list(
          x_primary_key      IN VARCHAR2,
          x_index_table_name IN VARCHAR2,
          x_start_date       IN DATE,
          x_end_date         IN DATE,
          x_index_data       OUT hit_list_types.HitListCursor) IS
v_sqlstmt varchar2(1000):=null;
x_return  types.rc;
v_cursor  number;
x_num     number;

BEGIN
   v_cursor:=dbms_sql.open_cursor;
   v_sqlstmt:='declare x_data hit_list_types.HitListCursor; '||
              'begin :x_index_data:="hl_'||x_index_table_name||'"(:x_primary_key
,:x_start_date,:x_end_date); END; ';

   dbms_sql.parse(v_cursor,v_sqlstmt,DBMS_SQL.NATIVE);
   dbms_sql.bind_variable(v_cursor,':x_index_data',x_index_data);
   dbms_sql.bind_variable(v_cursor,':x_primary_key',x_primary_key);
   dbms_sql.bind_variable(v_cursor,':x_start_date',x_start_date);
   dbms_sql.bind_variable(v_cursor,':x_end_date',x_end_date);

   x_num:=dbms_sql.execute(v_cursor);

end;
/

I know its a kind of complexity. The out (cursor) parameter is weekly define. We
issue calls to any function, that would return any number of rows, with any
number of columns. I am wondering would that be able to do using dbm_sql method.
The reason i am asking is, if i try to compile the above procedure i am getting
this error.
Warning: Procedure created with compilation errors.

Errors for PROCEDURE GET_HIT_LIST:

LINE/COL ERROR
-------- -----------------------------------------------------------------
18/4     PLS-00306: wrong number or types of arguments in call to
         'BIND_VARIABLE'

18/4     PL/SQL: Statement ignored

If you think that we can't do that using dbms_sql, can you please suggest the
workaround, or any better method.

Thanks for your time Tom.

          Followup:

The problem is the cursor can only be dynamically passed about in this fashion
IF it is a dbms_sql cursor type -- which is just a number.  It cannot be a REF
CURSOR.  The caller cannot be expecting a ref cursor -- they could only be
expecting a DBMS_SQL cursor and that cursor can ONLY be manipulated by calling
other DBMS_SQL functions on it.

So, the CALLER of this function, get_hit_list, would have to be expecting a
DBMS_SQL cursor -- not a ref cursor.

What is the CLIENT that is calling this get_hit_list procedure, what language is
that client written in (perhaps the answer lies in the client)

      Rating:
      5
  March 18, 2002
Reviewer:  Senthil
       from Massachusetts, USA
The CLIENT is java which is expecting resultset to be returnd. The dynamically
called functions are returning weakly defined ref cursor.

          Followup:

My suggestion is to write a JAVA subroutine (did I just date myself, i guess it
would be a "method" in some utility class) that dynamically prepares the string:

 "begin :? := \"hl_" + x_index_table_name +  "\"(?, ?, ? ); end;"

and executes that.  Let everyone call that routine -- instead of calling the
plsql routine you had in mind (since you cannot achieve what you want to achieve
in any other way)

      Rating: 4
very
      good  March 18, 2002
Reviewer:
      A reader
very good

      Rating: 5
Thanks a lot
      Tom  March 19, 2002
Reviewer:  A reader

your solution really helped me.

:)
Senthil.

      Rating: 5
Good
      explanation but...  January 28, 2003
Reviewer:
      Chenna
I have the following function...

FUNCTION test(tKEY IN NUMBER,tKEY_category IN VARCHAR2, TABLE_NAME IN
VARCHAR2) RETURN varchar2
IS
VARTKEY VARCHAR2(30);
VARSTRING VARCHAR2(2000);
BEGIN
VARSTRING := 'SELECT Col1 FROM     test_table WHERE test_key=
(SELECT testkey FROM ' || TABlE_NAME ||
'   WHERE  ' ||  tKEY_category ||' = '||  tKEY  ||')' || ' AND COL_DETAIL1=
'||''''||'VERIFY'||''''||'  AND COL_DETAIL2=
'||''''||'SOLD'||'''';
EXECUTE IMMEDIATE  VARSTRING INTO VARTKEY;
RETURN vartkeyR;

END;

If I use it just like the way it is coded above, it will work but.. it will clog
the library cache, as this is a very often used function (with same values for
the parameters most of the time), but as we are using dynamic sql and each time
a the sql is treated as brand new and has to be parsed again.

Is there a way I can code this using bind variables, and avoid parsing if same
parameters are using again and again.

Let us know

          Followup:

l_query := 'select col1
              from test_table
             where test_key = ( select testkey
                                  from ' || table_name || '
                                 where ' || tkey_category || ' = :x )
               and col_detail1 = ''VERIFY''
               and col_detail2 = ''SOLD'';

execute immediate l_query into vartkey using tkey;

that'll cause a softparse each time given the same table_name, tkey_category.
You could take it a step further with DBMS_SQL and have logic like:

create package BODY dyn_sql
as

   last_table_name varchar2(30) := 'x';
   last_cat varchar2(30) := 'x';
   dbms_sql_curosr int := dbms_sql.open_cursor;

procedure do_the_query( table_name tkey_category .... )
is
begin

  if ( table_name <> LAST_TABLE_NAME <> tkey_category <> LAST_CAT )
  then
      build query into string
      dbms_sql.parse query
      dbms_sql.define output from query
  end if;
  dbms_sql bind
  dbms_sql execute
  dbms_sql variable value
end

that'll parse if and only if the tablename or category column name changes....

      Rating: 3
PLS-00306:
      wrong number or types of arguments in call to '||'  April
      21, 2003
Reviewer:  Tahir  from CA, USA
Below is an error I receive after executing following lines of code.
Any suggestions or work-around is greatly appreciated.

Thanks,
Tahir

PLS-00306: wrong number or types of arguments in call to '||'

CREATE OR REPLACE TYPE t_IdList AS TABLE OF NUMBER;

declare

 v_idlist t_IdList;
 v_sql LONG;

 begin

-- DELETE_TABLES is a driving table with list of tables to delete from.
 SELECT CAST ( MULTISET ( SELECT id
                            FROM DELETE_TABLES
                            WHERE SEQUENCE_COUNT > v_lower
                             AND SEQUENCE_COUNT <= v_upper)
              AS t_IdList )
    INTO v_idlist
    FROM dual;

 v_sql := 'DELETE FROM DOCUMENT_HISTORY
                 WHERE objectid IN (SELECT *
                                      FROM TABLE ( CAST ( '||v_idlist||' AS
'||t_IdList ||') ) )'; -- < Somewhere in here is the error.

    execute immediate v_sql;

  DBMS_OUTPUT.PUT_LINE('Rows deleted: '||sql%rowcount);

end;

          Followup:

tell me -- what would ....' || v_idlist || '... do????

is t_idlist a variable?  nope -- it is a datatype...

v_sql := 'DELETE FROM DOCUMENT_HISTORY
                 WHERE objectid IN (SELECT *
                                      FROM TABLE ( CAST ( :x AS t_idList ) ) )';

    execute immediate v_sql USING v_idlist;

      Rating: 5
Binding
      differences.  April 22, 2003
Reviewer:
      Tahir  from CA, USA
Your answer was very helpful.
But why [ ( CAST ( '||v_idlist||' ] is not the same as [ (CAST (:x  ] execute
immediate v_sql USING v_idlist?

How these binding methods differ?

Thank you for your time.

          Followup:

what does:

x: = v_idlist;

where x is a varchar2(4000) mean?  v_idlist is NOT a string, strings can be used
with ||, v_idlist -- not being a string, not being a thing that can be CAST to a
string -- cannot be used with ||

|| is not binding -- it is the opposite of binding

:x is binding

      Rating: 5
Parsing TABLE
      TYPE  April 23, 2003
Reviewer:  Tahir
       from CA, USA
So now learning from the above lesson how does Oracle bind x: to v_idlist?
I mean by using v_idlist in the [ WHERE IN ] clause how does Oracle parse TYPE
TABLE OF NUMBER into comma-delimited string and in case of TYPE TABLE OF
VARHCAR2 into comma-delimited single quoted string?
Oracle docs unfortunately do not shed much light into this unless I was
searching in the wrong shed.

Thank you,
Tahir

          Followup:

not at all, you used:

   FROM TABLE ( CAST ( :x AS t_idList ) ) )';

TABLE is a "function" that expects a collection type (an array) and treats it as
if it were a TABLE source (rows in a table)

no different then

   FROM (select * from table )

      Rating: 4
Outputting
      DBMS_SQL as a cursor  August 27, 2003
Reviewer:
      Russell Flower  from Melbourne, Australia
Tom,
We currently have Crystal Reports that use results of Oracle Stored Procs as
their recordset.
We have a Package created with the following:
PACKAGE C_SP_POC AS
     TYPE MeasCC_Comp_CurType IS REF CURSOR; --RETURN
PIA_VW_CR_KRAS_ALLDATA%ROWTYPE;
, and a subsequent procedure that sets the columns to return (same column names
every time).
The procedures take a number of parameters (including the cursor as OUT), used
to filter the user's selection.  There is also a parameter called "Group Level"
indicating a management level in the staff structure (ie Member, Team, Area
etc.).

The Procedure has a number of if statements (basically a CASE statement) for the
various Group Levels.

Example:
PROCEDURE CONNECT_SP_CR_MEASURE_CC_COMP
(
"Group Level" IN VARCHAR2,
curResultSet  OUT C_SP_POC.MeasCC_Comp_CurType
) AS

BEGIN
    IF UPPER("Group Level") = 'MEMBER' THEN
         OPEN curResultSet FOR select * from (
             select  region, DISTRICT ,SUBDISTRICT , EMP_NAME,
             Sum(ITF) ITF, Sum(Completed) Completed, Sum(Jobs) Jobs, Sum(Travel)
Travel,
            Sum(Work) Work, Sum(NA) NA, Sum(Dist) Dist, Sum(WTIME) WTIME,
Sum(TTIME) TTIME
             FROM CONNECT_MEASURES_CC M, CONNECT_PEOPLE_STRUCTURE P
            WHERE M.AGS = P.AGS
            GROUP BY  region, DISTRICT, SUBDISTRICT, EMP_NAME
        );
    END IF;

    IF (UPPER("Group Level") = 'TEAM')  THEN
         OPEN curResultSet FOR select * from (
             select  region, DISTRICT ,SUBDISTRICT , ' ' EMP_NAME,
             Sum(ITF) ITF, Sum(Completed) Completed, Sum(Jobs) Jobs, Sum(Travel)
Travel,
            Sum(Work) Work, Sum(NA) NA, Sum(Dist) Dist, Sum(WTIME) WTIME,
Sum(TTIME) TTIME
             FROM CONNECT_MEASURES_CC M, CONNECT_PEOPLE_STRUCTURE P
            WHERE M.AGS = P.AGS
            GROUP BY  region, DISTRICT, SUBDISTRICT, ' ', ' '
        );
    END IF;
END;

Is it possible to write this using DBMS_SQL, to write one statement dynamically
(ie the Select to change relevant columns to spaces and the GROUP BY), instead
of having multiple copies of essentially the same code.

Thanks in Advance.

          Followup:

select region, district, subdistrict,
       decode( "Group Level", 'MEMBER', emp_name, 'TEAM', null ) emp_name,
       sum( ....... )
  from ....
 where ....
 group by region, district, subdistrict,
       decode( "Group Level", 'MEMBER', emp_name, 'TEAM', null ) emp_name

you can do that in a single static sql query.

      Rating: 5
All praises go
      to Tom!  August 28, 2003
Reviewer:  Russell
       from Melbourne, Australia
Thanks Tom.
Your answer was just so simple, and elegant.

Why didn't I think of that!  It just needs someone sometimes, to look from the
outside, and go, why don't you try this, instead of trying to do something so
difficult.

      Rating: 3
regarding the
      original question  September 01, 2003
Reviewer:
      Oleg Oleander  from Hungary
Tom!

I have almost the same exact situaltion like Senthil's. I implement a
general-use FILTER_TY. The types created under this add the table-dependent
logic, but all types would use the same interface to supply parameters and get
the result set. I would like to use bind variables and return a ref cursor to
the clinet (which is c# using odp.net).
The number of binded variables vary so I tried to build the whole statment
dynamically:

decalre
 b1 NUMBER;
 ref_cur is ref cursor;
begin
 execute immediate
  'begin
       open :c for ''select a,b,c from tab
                        where col = :x''
        using 12;
   end';
 using out ref_cur;
end;

But this is not working (I guess because the workarea for the cursor exists only
for the duration of the inner-block, since there is no other reference to it??).

I dont understand your java solution:
"begin :? := \"hl_" + x_index_table_name +  "\"(?, ?, ? ); end;"
It seems to be a function call but what is hl_ and the slashes, and how does
that solve the problem. Could it be a server side java method?

Please clarify, Im rather stucked here
Thanks a ton, Tom.

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279
your approach needs to be more along the lines of

begin
   open :c for 'select a,b,c from tab where col = :x' using ?;
end;

September 01, 2003 -- Thanks for the question regarding "Data
      Guard", version 9.0.0

      You Asked
Hello, Tom

Can you explain guaranteed protection mode of the Data Guard?
What happens with primary database instance in this mode if standby
database is unavailiable? Documentation says in this case all changes to primary
is prohibited. If it is so, it is not HA tool for me.
In this case I have more that double risk that my database becomes unavailiabe:
in case of hardware failure on primary AND standby PLUS network between them.

      and we said...
There are 4 modes to data guard:

a) guaranteed
b) instant
c) rapid
d) delayed (this is the old standby from 8i and before mode)

Data Guard is many things to many people.  Data Guard can be "make sure my data
persists EVEN if the building in which it was inserted is destroyed".  In that
case, guaranteed is what you want.  Here you prize the data more then anything
else in the world.  Who might use this?  Well -- a bank would.  If you transfer
$100,000 from account 1 to account 2 and get a confirmation back -- you expect
that to be persistant.  Well, if it weren't done in more then one location --
SIMULTANEOUSLY -- you would be less assured of that being the case.  Banking
institutions would use this level of assurance to have the data reside in two
geographically disperse locations at all times.  They would use this as a
replacement for syncronous replication and a two phase commit.  Here data guard
is providing two things:

a) extremely high levels of data assurance.  If it was committed, we got it.
b) a failover site that can be failed over to AS SOON AS A standby site for it
is constructed (makes no sense to fail over if your requirement is to have two
copies AT ALL TIMES)

So, data guard in this mode provides for exactly what they need.

I believe most situations will permit RAPID mode -- but you must remember that
running in rapid mode when the standby is unavailable -- is sort of like running
with a failed mirror disk in mirrored pairs.  You can still write to the disk
but if the disk fails -- you no longer have a mirror do you.  It is up to you
whether you want to continue or you want to stop.

In short -- data guard has many levels, not just one, and you must pick the
appropriate level based on YOUR needs, YOUR situation.  If one size fit all --
well, that would be boring ;)

Review &
      Followup

      Rating: 5
Redo X SQL
      stream  June 03, 2002
Reviewer:  Andre
       from Brazil
1) The redo stream is made up of blocks using Data Guard whereas it was done in
whole logs in 8i. Is it correct ? What size are these blocks ?

2) In 9i R2, there's this new SQL sream. How does it work compared to the
previous release ?

          Followup:

1) no, not really.  using Data Guard, we can have lgwr stream the logs over to
the standby instance, but we still wait until a log is filled to apply it.  It
is just a different transport.  Instead of ARCH doing it, LGWR does it.  This
allows us to make it so the latest greatest logs are on the standby site
(waiting to be filled, waiting to be applied)

subtle difference, but a difference.

In 9iR2, we can mine the logs in more "real time"

2) it can happen in more "real time" basically.  See

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96571/toc.htm
....

      Rating: 4
difference
      between guaranteed and instant mode  October 17, 2002

Reviewer:  A reader
hi

I have difficulties understanding the difference between these two modes:

In guaranteed data divergence is not allowed whereas in instant it is but data
loss is not allowed. But consider this situation

If I am in guaranteed mode then if I lose network connection my primary database
transactions wll not commit

If I am in instant mode then if network connection is lost then my primary
database transactions will not commit too (since no data loss means no changes
can be made in primary if data is not in standby, without network data will
never get to standby)

So what´s the difference in this case? I see zero difference :-0

          Followup:

guaranteed mode says

"when we cannot get the redo to the failover site when we commit -- STOP, FAIL,
HALT"

instant says

"when we cannot get the redo to the failover site when we commit -- don't stop,
don't fail -- rather fall back into using archives, notify the DBA, make OEM
flash red, get the problem fixed ASAP"

guaranteed mode = 2 phase commit like protection.  either both or neither are
updated.

instant = when all things are working normally, standby is upto the second.
when  something breaks in the connectivity between the two -- SOUND THE ALARMS,
but don't stop production

      Rating: 5
Data
      Guard  September 01, 2003
Reviewer:  David

Tom, I am setting up a 9.2 data guard environment. We have gone through the
procedures and some testing.

However we don't have enough experience with in production.

We have:

log_archive_dest_1='service=prod2 lgwr'

that is "maximum performance".

My worst fear is that the standby db may affect the primary somehow (making it
hang, for one) if something is wrong. Is it possible ? Or should I just take it
easy ?

          Followup:

why is that a "fear"

for some people -- that is the desired result actually.  having production
running without a failure is their fear.

if you are configured for maximum performance, then the database will fall back
to archive log shipping in the event the standby is not available.  You may have
to perform some manual syncing after the standby becomes available if you move
the archives on us.

      Rating:
      5
  November 06, 2003
Reviewer:
      A reader
Tom,

1. if using data guard command line utility dgmgrl.
Server1 is my production site and Server2 is my standby site
From where should i execute dgmgrl from production or fom standy.

2.Once physical standby database is set up it should be in mount mode or managed
recovery mode for setting up data guard configuration.

Thanks.

September 01, 2003 Muhammad Amjad -- Thanks for the question
      regarding "Job Scheduling", version 7.x

      You Asked
Hi Tom,
can u tell me that how can i scheduled my procedures execution. for example
there is a procedure namely 'abc' i want to execute it at 4'o clock every day.
it would by highly appreciated if u provide my any code, or reffer documents
etc. in this regard.

pl. accept my thanx in advance.

amjad

      and we said...
DBMS_JOB does this exactly.

to read about dbms_job:

select text
  from all_source
 where name = 'DBMS_JOB'
   and type = 'PACKAGE'
 order by line;

In your case, it is very easy:

declare
   l_job number;
begin
   dbms_job.submit( l_job,
                   'abc;',
                    trunc(sysdate)+4/24,
                   'trunc(sysdate)+1+4/24' );
end;

will run the job at 4am (change 4 to 16 if you meant 4pm, it was not clear)
every day.

make sure to set job_queue_processes and job_queue_interval (init.ora
parameters).

Review &
      Followup

      Rating:
      4
  March 22, 2001
Reviewer:  Alex
      MacDonald  from UK

      Rating:
      3
Values  May 03, 2001
Reviewer:
      Gururaj  from GA USA
You could have explained the parameteds and provided values to the parameters.

Gururaj

      Rating:
      5
  May 14, 2001
Reviewer:  Sudhakar
       from CT, USA

      Rating: 5
Great
      Help  January 04, 2002
Reviewer:  Bijay
      Tuladhar  from Fremont, CA
Tom,
I was wondering why my dbms_job wasn't after submitting the job, but as I went
through your old cases, from this case, I learnt that I need to set
job_queue_interval and job_queue_processes parameter. And it WORKED!

Thank you very much for helping us all.
Bijay

      Rating: 3
It doesn't work
      for me!!!!  January 13, 2002
Reviewer:  Sarada
      Priya  from Kuala Lumpur, Malaysia
My procedure needs to run everyday at 6 AM in the morning and I have scheduled
it as follows:

SET AUTOPRINT ON
VAR jobno NUMBER;
BEGIN
   DBMS_JOB.SUBMIT(:jobno, 'pcrsadm.upload_ddr_data;', TRUNC(SYSDATE) + 6/24,
'TRUNC(SYSDATE + 1) + 6/24');
   COMMIT;
END;
/

It creates the Job for me but never runs it.

Please help me solve this...

          Followup:

make sure to set job_queue_processes and job_queue_interval (init.ora
parameters).

Also, check your ALERT log on the server to see if it has been running but
FAILING due to some error.

      Rating: 4
UNIX
      CRON  January 14, 2002
Reviewer:
      A reader
UNIX CRON would be another alternative for this problem.

          Followup:

If you are running on Unix that would be true.

I personally try to schedule everything I can via dbms_job.  First -- it gives
me one place to look for all problems (scan the alert and I'm done looking for
problems).  Second -- I can see when the job last ran, when it'll run again very
easily using SQL from anywhere.  Third -- the jobs will ONLY run when the
database is available.  I've seen more then one cron job totally mess up stuff
cause it "assumed" the database would be up -- no chance of that here.  Fourth
-- never the need for a password with dbms_job, with cron -- unless you use OS
identified accounts, you'll be putting a password somewhere.  And the list goes
on....

Yes, cron could do it but I think dbms_job is infinitely superior for something
like this.  About the only thing I wouldn't use dbms_job for might be a backup
(although that is not out of the realm of possibilities ;)

      Rating:
      5
How  January 14, 2002
Reviewer:
      A reader
Hi, TOm,

How to backup DB by using DBMS_JOB, could you polish that if possible?

Thanks

          Followup:

A java stored procedure would be able to copy the files easy enough and dynamic
sql can issue the "alter tablespace begin/end backup".

      Rating:
      5
INIT.ORA  December 10, 2002
Reviewer:
      Jerry  from Texas
THIS FOLLOW UP THAT YOU GAVE SOLVED MY PROBLEM WITH THE JOBS NOT RUNNING :
Followup:
make sure to set job_queue_processes and job_queue_interval (init.ora
parameters).

Thanks

      Rating: 3
Job with start
      time in the past  March 05, 2003
Reviewer:  Sasa
      petkovic  from Belgrade, Yugoslavia
Hi Tom,

We had interesting situation on our production DB.
We have a job which runs every morinng at 5AM, and it works
perfectly every day.
This morning job run, finished good as usually. After that someone removed this
job at 9AM(due to different reasons, no matter what they were) and submited
again after half an hour.
But in the definition of the job, start time is in the past(trunc(sysdate)+5/24)
and job run once again at 9:30AM.Of course we expected to see this job running
tommorrow at 5AM.
According your recommendation : dont believe to anyone, make a proper test case
and prove it, I did it and got next:

CREATE TABLE TEST(datum VARCHAR2 (35));

DECLARE
jobno NUMBER;
lstart DATE := TRUNC(SYSDATE) + 5/24 ;
BEGIN
  dbms_job.submit(jobno, '<<TEST>>BEGIN INSERT INTO test ( datum) VALUES (
to_char(SYSDATE, ''dd/mm/yyyy hh24:mi'')); commit;END;', lstart ,
'trunc(SYSDATE) + 1 + 5/24');
COMMIT;
dbms_output.put_line(jobno);
END;

select * from test

gave me a sysdate in field "datum"

Although is now (in our time zone ) 3PM I got record in table test which proved
me that jobs runs with submiting.

I changed pl/sql block for submiting, removed this test job and submited again
and got next:

DECLARE
jobno NUMBER;
lstart DATE;
BEGIN
    SELECT DECODE(SIGN(SYSDATE - (TRUNC(SYSDATE)+5/24)), 1,
TRUNC(SYSDATE)+1+5/24, TRUNC(SYSDATE)+5/24) INTO lstart FROM dual;
    dbms_job.submit(jobno, '<<TEST>>BEGIN INSERT INTO test(datum) VALUES
(to_char(SYSDATE, ''dd/mm/yyyy hh24:mi'')); commit;END;', lstart ,
'trunc(SYSDATE) + 1 + 5/24');
    COMMIT;
    dbms_output.put_line(jobno);
END;

This time I compared sysdate with usually time for submiting and if sysdate is
greater then 5AM then moved running to next day.

This was proved with table "test" and record in it.There was no record in table
test with a such submiting.
I can sure you that I read documentation but couldn't find this feature
explained.

Thanks for your time.

          Followup:

ops$tkyte@ORA817DEV> exec dbms_job.submit( :j, 'null;', sysdate-5000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> commit;

Commit complete.

ops$tkyte@ORA817DEV> select next_date from user_jobs;

NEXT_DATE
-----------
26-JUN-1989

It doesn't care what the start date is - it just finds everything that "should
have run" and runs it.  It is only the INTERVAL that must evaluate to some date
in the future.

      Rating: 3
Recursive
      calling  July 09, 2003
Reviewer:  JK
       from Boston, USA
Hi Tom

 I have to schedule a job & rerun the same once it is completed. The job might
take anywhere between 30secs to 1min or more. Therefore we cannot schedule it to
happen every 45secs. If the job takes 15secs to complete, we want it to kick off
again.
 Can you suggest me a code snippet for this one please?
Thanks & Regards
JK

          Followup:

what about having the last lines of your routine be:

.....
  dbms_job.submit( l_job, 'myself;' );
  commit;
end;
/

but you can also just:

create or replace procedure myself( ...., p_NEXT_DATE OUT DATE )
as
begin
   ......

   p_NEXT_DATE := sysdate + 1/24/60/60*45;
end;
/

when you submit the job, use

  dbms_job.submit( ...., 'myself(....., NEXT_DATE );' )

have the job itself compute the next_date.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:9088073430469#9099572884547
for why and how that works.

      Rating: 5
Thanks for your
      quick response  July 09, 2003
Reviewer:  KJ
       from Boston USA
Appreciate your help!

Regards
KJ

      Rating: 4
What about jobs
      to be run every hour.  August 13, 2003
Reviewer:
      Shailandra  from NJ
Hi Tom,

What will be the syntex of DBMS_JOB to run a job every hour at hour past 15
minutes?

Thanks

          Followup:

the interval would be:

trunc(sysdate,'hh') + 1/24 + 15/24/60;

take the current time back the "last hour", add 1 hour (1/24), add 15 minutes
(15/24/60)

so, at 1:30, that'll return 2:15, at 2:15, that'll return 3:15.

      Rating: 4
Just what I
      needed!  September 01, 2003
Reviewer:
      A reader
Tom,
I'm reading up on dbms_job from O'Reilly's Oracle In a Nutshell, and on this
forum. You mentioned the job_queue_interval parameter. The Nutshell book
specifies that this parameter is for Oracle 8/8i only. Is there an equivalent
parameter for 9i, or is job_queue_processes the only parameter I need to worry
about?

          Followup:

job_queue_processes is the only one in 9i -- the interval is obsoleted in that
release.

      Rating:
      5
  October 06, 2003
Reviewer:
      A reader

September 02, 2003 Eli -- Thanks for the question regarding "geting
      ORA-29531 when call to java stored function", version 8.1.6

      You Asked
Hi tom
I get ORA-29531 when i call to java stored function .
The following is the calls i used:

public class calcDist {
  public static  double JcalcDist(double x1,double y1,double
x2,double y2)
  {
    double dist_deg = 0;
    double dist_earth = 0;
    dist_deg =
Math.acos(Math.sin(y1)*Math.sin(y2)+Math.cos(y1)*Math.cos(y2)*Mat
h.cos(x2-x1));
    dist_earth = dist_deg * (1852 * 60);
    return dist_earth;
 }
}

i use the loadJava utility to load the class and no error was
found.
the status of the javaClass is vaild (query user_objects)
the following is the name of the stored function :
CREATE OR REPLACE FUNCTION CALC_DIST_JAVA(X1 IN NUMBER,
                                          Y1 IN NUMBER,
                                             X2 IN NUMBER,
                                          Y2 IN NUMBER) RETURN
NUMBER
AS LANGUAGE JAVA
NAME 'calcDist.JcalcDist(java.lang.double,
java.lang.double,java.lang.double, java.lang.double ) return
double' ;
/
Function created.

when i run the following i get the above message:
select CALC_DIST_JAVA(123,1,7,8) from dual
                                      *
ERROR at line 1:
ORA-29531: no method JcalcDist in class calcDist

can you help (i found some think on the metalink that ask me to
add static to the metod but me metod includ static word).

      and we said...
You have the mapping wrong.  There is no such thing as a
java.lang.double (there is a java.lang.Double with a capital D).
 It would look like this:

ops$tkyte@DEV816> create or replace
  2     and compile java source named "calcDist"
  3  as
  4  public class calcDist {
  5    public static  double JcalcDist(double x1,
  6                                    double y1,
  7                                    double x2,
  8                                    double y2)
  9    {
 10      double dist_deg = 0;
 11      double dist_earth = 0;
 12
 13      dist_deg = Math.acos(Math.sin(y1)*
 14                           Math.sin(y2)+
 15                           Math.cos(y1)*
 16                           Math.cos(y2)*
 17                           Math.cos(x2-x1));
 18
 19      dist_earth = dist_deg * (1852 * 60);
 20      return dist_earth;
 21   }
 22  }
 23  /

Java created.

ops$tkyte@DEV816>
ops$tkyte@DEV816>
ops$tkyte@DEV816>
ops$tkyte@DEV816> CREATE OR REPLACE FUNCTION CALC_DIST_JAVA(
                                               X1 IN NUMBER,
  2                                            Y1 IN NUMBER,
  3                                            X2 IN NUMBER,
  4                                            Y2 IN NUMBER)
  5  RETURN NUMBER
  6  AS LANGUAGE JAVA
  7  NAME 'calcDist.JcalcDist
  8  (double,
  9   double,
 10   double,
 11   double )
 12  return double' ;
 13  /

Function created.

ops$tkyte@DEV816>
ops$tkyte@DEV816> exec dbms_output.put_line( calc_dist_java( 1,
1, 1, 1 ) );
0

PL/SQL procedure successfully completed.
Review &
      Followup

      Rating: 3
But if I need
      the same thing in oracle...  September 02, 2003

Reviewer:  Kathy Johns  from NJ
Hi Tom,
Your answer was excellent. But I have a similar problem
that I want to resolve using oracle.

We are on Sun Solaris Oracle 8.1.7 Sun server

I have a table of 23million zip codes and plus4 with their
longitude and latitude.

e.g.

zip   plus4 latitude longitude store_key
===== ===== ======== ========= =========
59001 6001  45.5172  -109.4424
59006 9753  45.9573  -108.1022

I have another table with 1000 stores with store_key
latitude and longitude
e.g.
store_key  latitude longitude
=========  ======== =========
59001      45.5172  -109.4424
59006      45.9573  -108.1022

The req is to assign each row in 23Million row table
the closest store.

The sql I am using for this is...

select y.*
from (
select x.*
, row_number() over (partition by x.zip, x.plus4 order by x.calcdist) rn
from
(select l.zip, l.plus4, d.dlrid,
(acos(sin((3.141592654/180)*(l.latitude))*sin((3.141592654/180)*(d.stor_lat))+cos
((3.141592654/180)*(l.latitude))*cos((3.141592654/180)*(d.stor_lat))*cos((3.14159
2654/180)*(l.longitude)-(3.141592654/180)*((d.stor_lon))))*3963)calcdist
from latlon l, stores d
where l.zip = '20724'
and l.plus4 is null
) x
) y
where y.rn = 1
/

This sql returns one row in aprrox 5 seconds with PK on zip and plus4 column on
latlon table.

When I multiply 5 sec to 23Million and give estimate to my boss I see myself
losing my job next day.

I tried PL/SQL and realise that the mathematical calculation are taking most of
the time and total
calc in this case are 23,000,000,000 (23MM * 1K)

Is there a better way to resolve this problem.

PS : One of our front end programmer claimed that he can
do this in 80Hrs in Foxpro on IGig machine.....:(
His process is running as we speak.

I am a die hard oracle fan and hope even this time I will
pass with flying colours.

Thanks,
Kathy

          Followup:

see

http://otn.oracle.com/oramag/oracle/03-may/o33asktom.html
sounds like you might want to look into the builtin locator functionality.

September 02, 2003 Preeti -- Thanks for the question regarding
      "Views perform better?", version 8.1.7

      You Asked
We are displaying several complicated reports in our web application. These
reports use joins in many Orcale tables. Tables may have 1000s of records. Do we
gain any performance by making Views that perform complicated SQL. And then in
our application just code SQL as
SELECT * FROM view_name.

      and we said...
No, a view is simply a stored query. When you submit "select * from view_name"
we simply retrieve the view definition and rewrite the query using it.

Now, there are MATERIALIZED views which can definitely speed up reporting --
especially if you do large aggregations of data.  See

http://technet.oracle.com/doc/oracle8i_816/server.816/a76994/mv.htm#721
for an overview of this technology.
Review &
      Followup

      Rating:
      4
Well,  August 30, 2001
Reviewer:
      John  from US
We create a query

Create view my_view select * from emp;

Then I issue the statement

Select * from myview;

IS  SELECT * FROM EMP  resolved first and then select * from myview.

Is there any difference between
Create view my_view as select * from emp; and
Create view my_view as select col1,col2,col3 ....from emp;

          Followup:

When  you issue the statement:

select * from myview;

it is processed as:

select * from ( select * from emp );

going further, if you issue:

select * from myview where empno = 1234;

that will be:

  select * from ( select * from emp ) where empno = 1234;

which will be rewritten internally as:

  select * from (select * from emp where empno=1234);

allowing indexes and so on to be used (this is predicate "pushing" whereby
predicates are pushed into subqueries when possible).

There is no difference between "select *" and "select c1, c2, c3, ...".  In
fact, select * is turned into select c1, c2, c3:

scott@ORA817DEV.US.ORACLE.COM> create or replace view v as select * from emp;
View created.

scott@ORA817DEV.US.ORACLE.COM> select text from user_views where view_name =
'V';

TEXT
--------------------------------------------------------------------------------
select "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" from emp

Now, there IS a difference between select emp.* and select * however:

scott@ORA817DEV.US.ORACLE.COM> create or replace view v as select emp.* from
emp;

View created.

scott@ORA817DEV.US.ORACLE.COM> select text from user_views where view_name =
'V';

TEXT
--------------------------------------------------------------------------------
select emp.* from emp

The difference is subtle and will only become apparent if you add/drop/reorder
the columns in the EMP table.  My advice is to NOT use correlation names in
VIEWS ever.  It can only lead to obscure hard to find issues later.

      Rating:
      5
Well  August 30, 2001
Reviewer:
      A reader
Is select * from ( select * from emp );
more expensive than

select * from emp;

          Followup:

It is neglible -- the shared pool caches query plans.  After the first parse --
you will not notice any difference (and even then, the first parse will not be
noticeably affected).

      Rating: 4
no
      convinced  August 30, 2001
Reviewer:  Steve

You should not forget that select * from myview; will be pushed out of the
shared pool as it will lead to a full table scan.

So every time we issue the query we should do a select * from (select * from
emp;)

In my opinion this will definitely more expensive, then just a select * from
emp.

How about when we have a 1 gig table , and you create a view on that, and do a
select * from from that view. Woudnt that be very costly  then doing  a select *
from the table itself.

          Followup:

Huh ???  You seem to be totally mixing up concepts.

A full scan will not cause the shared pool to do anything -- the shared pool is
the shared pool.  The buffer cache is a separate piece of memory.  Full scan --
index read -- who cares, it will NOT affect the shared pool.

Select * from myview where myview is equal to "select * from emp" will do the
SAME EXACT THING as "select * from ( select * from emp )"

Views are not evil.
Views are not evil.
(just keep repeating that over and over).
Views are useful.

In my experience, based on fact -- neither of those queries will be more or less
expensive then the other.

As for the comment

"How about when we have a 1 gig table , and you create a view on that, and do a
select * from from that view. Woudnt that be very costly  then doing  a select *

from the table itself."

Doing a select * on a 1 gig table, versus doing a select * from a view of that 1
gig table will be EXACTLY the same-- no different.  As I said -- queries against
views are simply rewritten to be queries against base tables.  Views are just
stored queries -- not stored data, not overhead, not dangerous.

      Rating: 5
Views and
      'select *'  August 30, 2001
Reviewer:
      Connor  from UK
Its not good practice to use 'select *' when creating a view, because you can
get all sort of nasty anomalies when you redefine things.  For example, create a
view as "select *" and then drop/recreate the table with columns in different
order...You'll get a very different result between "select a.*" and "select *"

      Rating: 4
View
      Performance  September 04, 2001
Reviewer:
      Robert Eke  from England
I found the discussion of get value.
I have some large tables which I was going to place a few views over in an
attempt to speed things up.

The detail and examples in this discussion have proved very useful in the use of
views and their draw backs.

For some reason I was always told that selecting data from layered views is bad.
 It only seems bad if the sql within the combined views is not optimised.

A very useful review.

      Rating: 4
Views improving
      performance  September 04, 2001
Reviewer:  Matt
      T
I have found views very useful when using Data Querying
tools which automatically generate SQL (such as discoverer).

By pre-joining your tables into a single view you can
directly effect the performance using hints. This
was particularly useful in the days of Rule based optimiser.

      Rating: 4
Performance of
      Views  September 01, 2003
Reviewer:  Sri
       from India
Dear Tom,

  We have a debate here regarding the performance of views compared to the
underlying tables.  One argument is that the query in the view is pre-compiled,
so Oracle need not go through all the steps involved in Query processing when I
do a 'Select * from my_view' as compared to 'Select * from my_base_table'.  I am
pretty much convinced from tkprof and explain plan that it is not happening that
way.  But, my doubt is,

1. why it is not happening that way?

I did the following in my 8i (8.1.7.0) Database: -

create table base_table
 (roll varchar2(4) primary key,
  name varchar2(10) not null,
  mark1 number(3)
 )
/
insert into base_table values ('1', 'First', NULL)
/
insert into base_table values ('2', 'Second', 10)
/
create view vw_temp as select * from base_table
/
select * from vw_temp
/
ROLL NAME            MARK1
---- ---------- ----------
1    First
2    Second             10

desc vw_temp

Name                                      Null?    Type
------------------------------------- -------- ---------
ROLL                                  NOT NULL VARCHAR2(4)
NAME                                  NOT NULL VARCHAR2(10)
MARK1                                          NUMBER(3)

alter table base_table add (Mark2 Number(3))
/

desc vw_temp

ERROR:
ORA-24372: invalid object for describe

select * from vw_temp
/

ROLL NAME            MARK1
---- ---------- ----------
1    First
2    Second             10

insert into base_table values ('3', 'Third', 10, 20)
/

select * from vw_temp
/

ROLL NAME            MARK1
---- ---------- ----------
1    First
2    Second             10
3    Third              10

select * from (select * from base_table)
/

ROLL NAME            MARK1      MARK2
---- ---------- ---------- ----------
1    First
2    Second             10
3    Third              10         20

2. Why did the desc return an error?  If I do the describe after the Select, it
is working.  Why is this so?

Thank you.

          Followup:

1) a view is nothing more then a stored piece of text.

when you query "select * from my_view" that is rewritten as "select * from (
YOUR_QUERY_TEXT_HERE )" in effect and then parsed and optimized.

it would not be POSSIBLE to store the compiled plan for a view -- think about
it:

select * from view where x = 1;

it would be nice if X had an index -- or even better, X was the primary key --
for that to use an index.  however "select * from view" should not.

every query -- without exception - is hard parsed and cached in the shared pool
the first time.  no query plans are stored in the data dictionary.

2) the view was invalid, you cannot describe an invalid view.  the act of using
the view compiled it.  you can describe valid objects.

      Rating: 4
rowid's in
      view  September 01, 2003
Reviewer:  Raghu
       from India
Hi Tom,

I have a view  based on two tables.

When i do a SELECT * FROM VIEW_NAME, the tkprof report shows me that the select
statement as rowid associated with it.

I would like to know this rowid is from which table out of the two i have in  my
view.

Recently i had a problem.i had a view which was inturn based on other views.
when a queried from the SQL*PLUS with a where clause i got the correct results.

But when i used forms, it was giving me different count of rows.
Through tkprof i saw that the query was using rowid.
when i executed the same query from the tkprof in  SQL*PLUS ,the count was wrong
again as it had rowid with it.

so i removed the rowid from the query and executed in SQL*PLUS it worked right
again.

Then i changed the view definition , and all the view inside the view definition
were changed with the table name and it worked fine for me.

Can you please explain the behaviour of this.

          Followup:

need a concrete example to work with.

select * from view_name will not show a rowid in a tkprof report.  you would
need to actually select rowid from view_name to get that.

the rowid will come from the first key preserved table.  Here is a simple
example:

ops$tkyte@ORA920> create table emp as select * from scott.emp where deptno = 10
and rownum = 1;
Table created.

ops$tkyte@ORA920> create table dept as select * from scott.dept where deptno =
10;
Table created.

ops$tkyte@ORA920> create or replace view V
  2  as
  3  select emp.ename, dept.dname
  4    from emp, dept
  5   where emp.deptno = dept.deptno;
View created.

ops$tkyte@ORA920> select rowid from V;
select rowid from V
                  *
ERROR at line 1:
ORA-01445: cannot select ROWID from a join view without a key-preserved table

ops$tkyte@ORA920> select column_name, insertable, updatable, deletable
  2    from user_updatable_columns
  3   where table_name = 'V';

COLUMN_NAME                    INS UPD DEL
------------------------------ --- --- ---
ENAME                          NO  NO  NO
DNAME                          NO  NO  NO

In this case -- the view is not updatable -- there are NO rowids to be had
here.  We need a primary/unique key in order to be updatable:

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table dept add constraint dept_pk primary key(deptno);

Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select rowid from V;

ROWID
------------------
AAANjpAAGAAAABKAAA

ops$tkyte@ORA920> select column_name, insertable, updatable, deletable
  2    from user_updatable_columns
  3   where table_name = 'V';

COLUMN_NAME                    INS UPD DEL
------------------------------ --- --- ---
ENAME                          YES YES YES
DNAME                          NO  NO  NO

Now, the emp table is updatable in this view -- since Oracle knows that each
row in EMP will join to AT MOST one row in dept -- so the update against EMP
will not be "ambigous" -- a row in emp appears in the view at most once...

ops$tkyte@ORA920> select ename from emp where rowid = (select rowid from V);

ENAME
----------
CLARK

ops$tkyte@ORA920> select dname from dept where rowid = (select rowid from V);
select dname from dept where rowid = (select rowid from V)
                  *
ERROR at line 1:
ORA-01410: invalid ROWID

that just proves the rowid belongs to EMP, not dept...

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table emp add constraint emp_pk primary key(deptno);

Table altered.

now, we make BOTH tables updatable...

ops$tkyte@ORA920>
ops$tkyte@ORA920> select rowid from V;

ROWID
------------------
AAANjqAAGAAAABSAAA

ops$tkyte@ORA920> select column_name, insertable, updatable, deletable
  2    from user_updatable_columns
  3   where table_name = 'V';

COLUMN_NAME                    INS UPD DEL
------------------------------ --- --- ---
ENAME                          YES YES YES
DNAME                          YES YES YES

still have a rowid -- but from which table??

ops$tkyte@ORA920> select ename from emp where rowid = (select rowid from V);
select ename from emp where rowid = (select rowid from V)
                  *
ERROR at line 1:
ORA-01410: invalid ROWID

ops$tkyte@ORA920> select dname from dept where rowid = (select rowid from V);

DNAME
--------------
ACCOUNTING

dept apparently -- recreate view with tables swapped

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace view V
  2  as
  3  select emp.ename, dept.dname
  4    from dept, emp
  5   where emp.deptno = dept.deptno;

View created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select rowid from V;

ROWID
------------------
AAANjpAAGAAAABKAAA

ops$tkyte@ORA920> select column_name, insertable, updatable, deletable
  2    from user_updatable_columns
  3   where table_name = 'V';

COLUMN_NAME                    INS UPD DEL
------------------------------ --- --- ---
ENAME                          YES YES YES
DNAME                          YES YES YES

ops$tkyte@ORA920> select ename from emp where rowid = (select rowid from V);

ENAME
----------
CLARK

ops$tkyte@ORA920> select dname from dept where rowid = (select rowid from V);
select dname from dept where rowid = (select rowid from V)
                  *
ERROR at line 1:
ORA-01410: invalid ROWID

now it comes from emp that last bit is EMPIRICAL and NOT TO BE RELIED ON.
If you have an updatable join view with more then one table in it -- the rowid
is 100% unreliable, do not use it -- you don't really know which table it is
for!!!

As for your "wrong answers" -- you'll really have to supply a test case!

      Rating: 3
If its just a
      piece of text ...  September 01, 2003
Reviewer:
      Oleg Oleander  from Hungary
than what do you think about the idea of returning resultsets as views created
on the fly. I know that DDLs are costly, but If its just a piece of  text ... or
does it places lockes and not scale? I originally planed to use ref cursor but
its a general-use filtering type I implement and I cannot find a way to return a
ref cursor.
please comment

Tnx very much

          Followup:

why cannot you return a ref cursor?

you do NOT want to create views one the fly -- not a chance.

You CAN return a ref cursor.  Tell me why you think you cannot and we'll fix
that.

      Rating: 4
not good idea
      using select * with developer 200  September 01, 2003

Reviewer:  juancarlosreyesp@yahoo.com
additionally
The problem of using SELECT * FROM EMP
causes if you add a new column to emp, the need to recompile de report or form
in some situations.

      Rating: 4
because the
      number of binded variables is unknown at compile
      time  September 02, 2003
Reviewer:  Oleg
      Oleander  from Hungary
Tom,
We are using 9i.
I read your articles:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:139812348065
about binding list, and

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279
about sys_context.

I implement a general-use FILTER_TY. The types created under this add the
table-dependent logic, but all types would use the same interface to
supply parameters and get the result set. I would like to use bind
variables and return a ref cursor to the clinet (which is c# using odp.net).
The parameters passed to the FILTER_TY are of two types.
Either a scalar value or a list. The number of parameters of each type are
unknown, which is the problem when using 'execute immediate' or 'open ... for'.
Based on the above mentioned articles I can think of two possible solutions:

1. For binding scalar values I can use sys_context with the appropriate
   type conversion.
   For binding lists I can use nested tables If I limit the number of
   parameters of this type.
   It will look something like this:

   create type VARCHAR2_TAB_TY     is table of VARCHAR2(255);
   create type VARCHAR2_TAB_TAB_TY is table of VARCHAR2_TAB_TY;

   procedure proc1 (c out sys_refcursor)
     scalar_value_1 VARCHAR2(255);
     scalar_value_2 VARCHAR2(255);
     list_values    VARCHAR2_TAB_TAB_TY; -- in real, these parameters are
                                         -- passed to the FILTER_TY

     v_sql        VARCHAR2(32767) := 'select a,b,c from tab where ';
     c             is ref cursor;
   begin
     -- for each scalar value I can:
     v_sql := v_sql || 'col_1 = TO_NUMBER(sys_context( ''MY_CTX'',
                                           ''scalar_value_1'' )) ';
     dbms_session.set_context( 'MY_CTX', 'scalar_value_1',  scalar_value_1);

     -- for each list value that is set or NOT set (empty) I can:
     v_sql := v_sql || 'col_2 in (select * from TABLE(:X)) ';

     ...

     -- and then opening the cursor like:
     open c for v_sql
      using list_values(1), list_values(2), ..., list_values(n);

     -- n is a constant, the maximum number of list_values the rutine can
handle.
     -- 30 should be enough.

   end;

2. For binding scalar values I can use sys_context with the appropriate type
   conversion.
   For binding lists I can use a plsql table. In this case I have to convert
   lists stored in nested tables into comma separated simple varchar2
   list, and then I can bind it like scalar values thru sys_context.
   This approach is not usable though, because sys_context values can
   store values only up to 255 chars.

Q: Is there any better way?
   In case 1, will binding of empty nested tables cause any problem?

Thank you very much Tom. Looking forward to your answer.

          Followup:

you got the scalar part right

but for the list, I would use something like:

   loop i 1 .. N over list
       dbms_session.set_context( 'my_ctx', 'list_name_' || i, list_value(i) );
   end loop;
   v_sql := v_sql || ' col_2 in ( select value from session_context
                                   where namespace = ''MY_CTX''
                          and attribute like ''LIST\_NAME\_%'' escape ''\'' ) ';

just blow all of the values into the context and use session_context to retrieve
the list in the query.

      Rating: 5
Thats what I
      was looking for...  September 03, 2003
Reviewer:
      Oleg Oleander
I knew you'd know it.
Thanks Tom, I didnt think of that.
Respect.

September 02, 2003 mary -- Thanks for the question regarding
      "Separating Values From One Column into Multiple Columns", version
      9.2.0.1, 8.1.7

      You Asked
I am trying to separate the values from one column into multiple columns, i.e.,
I have a column in my database called IP Address which holds values like
111.111.111.111, but I want to split each set of numbers into it's own separate
column.

Sometimes the values of the data are in the format of 111.11.11.11 and other
times they are in the format of 111.11.11.111 (the size of the last set of
numbers varies).

I'm looking for an example using the best sql function to handle this.

      and we said...
ops$tkyte@ORA920> select x,
  2     substr( x, 1, instr(x,'.')-1 ) x1,
  3     substr( x, instr(x,'.')+1, instr(x,'.',1,2)-instr(x,'.')-1 ) x2,
  4     substr( x, instr(x,'.',1,2)+1, instr(x,'.',1,3)-instr(x,'.',1,2)-1 ) x3,
  5     substr( x, instr(x,'.',1,3)+1 ) x4
  6    from t
  7  /

X               X1   X2   X3   X4
--------------- ---- ---- ---- ----
1.2.3.4         1    2    3    4
11.21.31.41     11   21   31   41
111.21.31.41    111  21   31   41
111.21.31.411   111  21   31   411

ops$tkyte@ORA920>

Review &
      Followup

      Rating: 5
and how we
      gonna do this on 10g  August 31, 2003
Reviewer:
      thierry
Can this problem not be seen as a regular expression? Will that offer a more
elegant implementation for the original question?

          Followup:

regex is for searches, it doesn't "break up"

that requires a parser...

so, in 10g, it'll look -- well -- exactly the same.

i don't see the inelegance in "find start pos", "calculate length", "substr"
myself.

      Rating: 5
works
      great  August 31, 2003
Reviewer:  mary

thanks Tom

      Rating: 4
What about
      this?  August 31, 2003
Reviewer:  Jon
       from Sydney
I was thinking that rewritting the query like this be less CPU intensive, only
doing 3 instr per row instead of 8:

select substr(x, 1, d1-1) x1,
       substr(x, d1+1, d2-d1-1) x2,
       substr(x, d2+1, d3-d2-1) x3,
       substr(x, d3+1) x4
from
   (select x,
        instr(x,'.',1,1) d1,
        instr(x,'.',1,2) d2,
        instr(x,'.',1,3) d3
    from t);

However, when I tried it out on 100,000 rows, it was actually a few seconds
slower.  Why?

Thx,
Jon

          Followup:

you are ascribing procedural constructs to SQL and none can really apply.

your query is really calling it 8 times.

mine calls it 6 -- a quirk of the optimization.  your query is rewritten flat
but the duplicate invocations of instr are not factored out.  mine, without any
such rewrite, seems to have the duplicate instr calls factored out.

consider:

ops$tkyte@ORA920> create or replace function myinstr( s1 in varchar2, s2 in
varchar,
  2       n1 in number default null, n2 in number  default null) return number
  3  is
  4  begin
  5          dbms_application_info.set_client_info(userenv('client_info')+1);
  6          return instr(s1,s2,n1,n2);
  7  end;
  8  /

Function created.

ops$tkyte@ORA920> drop table t;
Table dropped.

ops$tkyte@ORA920> create table t ( x varchar2(15) );
Table created.

ops$tkyte@ORA920> insert into t values ( '1.1.1.1' );
1 row created.

ops$tkyte@ORA920> exec dbms_application_info.set_client_info(0);
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select substr(x, 1, d1-1) x1,
  2         substr(x, d1+1, d2-d1-1) x2,
  3         substr(x, d2+1, d3-d2-1) x3,
  4         substr(x, d3+1) x4
  5  from
  6     (select x,
  7          myinstr(x,'.',1,1) d1,
  8          myinstr(x,'.',1,2) d2,
  9          myinstr(x,'.',1,3) d3
 10      from t);

X1              X2              X3              X4
--------------- --------------- --------------- ---------------
1               1               1               1

ops$tkyte@ORA920> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
8

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_application_info.set_client_info(0);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select x,
  2     substr( x, 1, myinstr(x,'.')-1 ) x1,
  3     substr( x, myinstr(x,'.')+1, myinstr(x,'.',1,2)-myinstr(x,'.')-1 ) x2,
  4     substr( x, myinstr(x,'.',1,2)+1, myinstr(x,'.',1,3)-myinstr(x,'.',1,2)-1
) x3,
  5     substr( x, myinstr(x,'.',1,3)+1 ) x4
  6    from t
  7  /

X               X1              X2              X3              X4
--------------- --------------- --------------- --------------- ---------------
1.1.1.1                                         1               1

ops$tkyte@ORA920> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
6

      Rating:
      5
Interesting...  September 01, 2003

Reviewer:  Mark Wooldridge  from Vienna, VA

Tom,

Great use of dbms_application_info to understand what is happening, but the
implementation of instr has a twist or your implementation of myinstr has an
interesting side effect.

Passing null for the position or occurrence of the character provides erroneous
results.

select instr('1.2.3.4', '.', null, null) from dual
/
SQL> set null *NULL*
SQL> l
  1* select instr('1.2.3.4', '.', null, null) from dual
SQL> /

INSTR('1.2.3.4','.',NULL,NULL)
------------------------------
*NULL*

1 row selected.
does not return the same result as
select instr('1.2.3.4', '.', 1, 1) from dual
/
SQL> select instr('1.2.3.4', '.', 1, 1) from dual
  2
SQL> /

INSTR('1.2.3.4','.',1,1)
------------------------
                       2

1 row selected.

after modifying your myinstr function to default the start position and
occurrence to 1 and re-executing the test I find that myinstr is executed 8
times.

Would there be a way to use deterministic on the function and identify if it is
being re-executed or or just the result being obtained?

          Followup:

cool, didn't even think about that -- passing null's to it is different then not
passing anything at all!

so, they are both called 8.  deterministic currently only has meaning to
function based indexes and materialized views, it is not used anywhere else.
so, it would not apply here.

      Rating: 5
Well how about
      this then...  September 02, 2003
Reviewer:
      Jon  from Sydney
begin
    dbms_application_info.set_client_info(0);
end;

select substr(x, 1, d1-1) x1,
       substr(x, d1+1, d2-d1-1) x2,
       substr(x, d2+1, d3-d2-1) x3,
       substr(x, d3+1) x4
from
   (select /*+ no_merge */ x,
        myinstr(x,'.',1,1) d1,
        myinstr(x,'.',1,2) d2,
        myinstr(x,'.',1,3) d3
    from t);

select userenv('client_info') calls from dual
CALLS
----------------------------------------------------------------
3
1 row selected

Now we are down to 3...

          Followup:

yup, adding "where rownum >= 0" to the inline view will have a similar effect.

September 02, 2003 Ron -- Thanks for the question regarding "Merge -
      oracle 9i", version pc oracle 9.2.0

      You Asked
 Tom:
           I am using the merge command, using the this  program  with this
message:
  MERGE INTO newemp n1
USING external_table e1
ON ( n1.empno = e1.empno )
WHEN MATCHED THEN UPDATE
    SET n1.empno    = e1.empno,
        n1.ename    = e1.ename,
        n1.job      = e1.job,
        n1.mgr      = e1.mgr,
        n1.hiredate = e1.hiredate,
        n1.sal      = e1.sal,
        n1.comm     = e1.comm,
        n1.deptno   = e1.deptno
WHEN NOT MATCHED THEN
INSERT ( empno, ename, job, mgr, hiredate, sal, comm, deptno )
values ( e1.empno,
         e1.ename,
         e1.job,
         e1.mgr,
         e1.hiredate,
         e1.sal,
         e1.comm,
         e1.deptno )
/
SQL> desc newemp;
 Name                                      Null?    Type
 ----------------------------------------- -------- --------------
 EMPNO                                              NUMBER(4)
 ENAME                                              VARCHAR2(10)
 JOB                                                VARCHAR2(9)
 MGR                                                NUMBER(4)
 HIREDATE                                           DATE
 SAL                                                NUMBER(7,2)
 COMM                                               NUMBER(7,2)
 DEPTNO                                             NUMBER(2)

SQL> @ c:\oracledir\programs\merge1.sql
ON ( n1.empno = e1.empno )
     *
ERROR at line 3:
ORA-00904: "N1"."EMPNO": invalid identifier

           Thank You
                   Ron

      and we said...
It appears to be an undocumented restriction that you cannot UPDATE any column
you are MERGING on -- this works:

ops$tkyte@ORA920.US.ORACLE.COM> MERGE INTO newemp  n1
  2  USING external_table e1
  3  ON ( n1.empno = e1.empno  )
  4  WHEN MATCHED THEN UPDATE
  5      SET -- n1.empno   = e1.empno,
  6           n1.ename    = e1.ename,
  7           n1.job      = e1.job,
  8           n1.mgr      = e1.mgr,
  9           n1.hiredate = e1.hiredate,
 10           n1.sal      = e1.sal,
 11           n1.comm     = e1.comm,
 12           n1.deptno   = e1.deptno
 13  WHEN NOT MATCHED THEN
 14  INSERT ( empno, ename, job, mgr, hiredate, sal, comm, deptno )
 15  values ( e1.empno,
 16           e1.ename,
 17           e1.job,
 18           e1.mgr,
 19           e1.hiredate,
 20           e1.sal,
 21           e1.comm,
 22           e1.deptno )
 23  /

14 rows merged.

but if you add to the ON something like "and n1.sal > 55", you would get

ON ( n1.empno = e1.empno and n1.sal > 55 )
                             *
ERROR at line 3:
ORA-00904: "N1"."SAL": invalid identifier

Review &
      Followup

      Rating: 5
What about
      identifying the inserted/updated records?  August 22, 2003

Reviewer:  Nitin Sood  from Albany, NY
Thanks... for the clarification. When I started using MERGE about 8 months ago,
I ran into this exact problem... got the answer right here!

However, is there an easy way to identify which records have been inserted
versus the updated records following a MERGE execution?

This might be required if after the MERGE into a table, the inserted records
have to processed in one way and the updated ones in another.

          Followup:

well, you could certainly FLAG THEM.

when matched update set .... status = 'U'
when not matched insert ( ...., status, ... ) values ( ...., 'I', .... )

      Rating: 4
Awww,
      man...  August 22, 2003
Reviewer:  Dan
      Kefford  from New Haven, CT
Too bad MERGE doesn't support the RETURNING CLAUSE:

SQL> DECLARE
  2     updated_rows_tab    DBMS_SQL.number_table ;
  3     inserted_rows_tab   DBMS_SQL.number_table ;
  4  BEGIN
  5     MERGE INTO target_tab t1
  6     USING source_tab s1 ON (t1.key_col = s1.key_col)
  7        WHEN MATCHED THEN
  8           UPDATE SET t1.key_val = s1.key_val
  9           RETURNING s1.key_col BULK COLLECT INTO updated_rows_tab
 10        WHEN NOT MATCHED THEN
 11           INSERT (key_col,
 12                   key_val)
 13           VALUES (s1.key_col,
 14                   s1.key_val)
 15           RETURNING s1.key_col BULK COLLECT INTO inserted_rows_tab ;
 16     DBMS_OUTPUT.PUT_LINE(updated_rows_tab.COUNT || ' rows updated.') ;
 17     DBMS_OUTPUT.PUT_LINE(inserted_rows_tab.COUNT || ' rows inserted.') ;
 18  END ;
 19  /
         RETURNING s1.key_col BULK COLLECT INTO updated_rows_tab
                                           *
ERROR at line 9:
ORA-06550: line 9, column 10:
PL/SQL: ORA-00905: missing keyword
ORA-06550: line 5, column 4:
PL/SQL: SQL Statement ignored

Are there any plans to support this construction in the future?

          Followup:

not that I am aware of.

      Rating: 5
here is the
      solutions  August 22, 2003
Reviewer:  Bijay

set echo on
set pause on
DROP Table t1;
drop table t2;
drop sequence shop_ssq;

create table t1
 ( pfcntr number(6),
   brand varchar2(2),
   customer_number number(6),
   city varchar2(24),
   zip1 number (6));

 INSERT INTO T1 VALUES(1,'DD',1,'USA',1);
 INSERT INTO T1 VALUES(2,'DD',1,'USA',1);
 INSERT INTO T1 VALUES(3,'DD',1,'USA',1);
 INSERT INTO T1( PFCNTR, BRAND, CUSTOMER_NUMBER,CITY) VALUES(4,'DD',1,'BBSR');
commit;
cl scr
REM select *  from t1;

create sequence shop_ssq;
select shop_ssq.nextval from dual;

create table t2
  ( pfcntr number(6),
    shop_sk number(6) primary key,
    brand varchar2(2),
    customer_number number(6),
    city varchar2(24),
    zip1 number(6),
    status varchar2(1) );

 merge into t2
   using ( select pfcntr, brand, customer_number, city, zip1 from t1
           MINUS
           select pfcntr, brand, customer_number, city, zip1 from t2 ) t1
      on ( t2.pfcntr = t1.pfcntr AND t2.brand = t1.brand)
    when matched
    then update
     set t2.customer_number = t1.customer_number,
         t2.city = t1.city,
         t2.zip1 = t1.zip1,
         t2.status = 'Y'
    when not matched
    then insert ( shop_sk, pfcntr, brand, customer_number, city, zip1, status )
             values ( shop_ssq.nextval, t1.pfcntr, t1.brand, t1.customer_number,
t1.city, t1.zip1, 'N' );

REM 2nd day after adding 2 rows and chaging one row.
cl scr
 select * from t1  order by pfcntr;
 select * from t2  order by pfcntr;

 update t1 set city = 'Modified to INDIA' wherE pfcntr=1;

 INSERT INTO T1 VALUES(5,'DD',1,'Newly Added',1);
 INSERT INTO T1 VALUES(6,'DD',1,'Newly Added',1);
 commit;

 merge into t2
   using ( select pfcntr, brand, customer_number, city, zip1 from t1
           MINUS
           select pfcntr, brand, customer_number, city, zip1 from t2 ) t1
      on ( t2.pfcntr = t1.pfcntr AND  t2.brand = t1.brand)
    when matched
    then update
     set   t2.customer_number = t1.customer_number,
           t2.city = t1.city,
           t2.zip1 = t1.zip1,
           t2.status = 'Y'
  when not matched
  then insert ( shop_sk, pfcntr, brand, customer_number, city, zip1, status )
       values ( shop_ssq.nextval, t1.pfcntr, t1.brand,t1.customer_number,
t1.city, t1.zip1, 'N' );
commit;
 select * from t1  order by pfcntr;
 select * from t2  order by pfcntr;

September 02, 2003 Sridhar -- Thanks for the question regarding
      "internal - password", version 8.0.5

      You Asked
How to change the password of 'internal' ?

      and we said...
The easiest way to answer this is to use an existing Oracle
Support note.  This is note #18089.1

1) Introduction
~~~~~~~~~~~~~~~
  This article describes the database DBA 'CONNECT INTERNAL'
functionality.

  In Oracle7 there are 3 special classes of user with privilege
to perform   special operations (such as shutdown and startup)
on the database:

        - A DBA user (SYSDBA)
        - An OPERATOR user (SYSOPER)
        - Connect INTERNAL privilege

  This article discusses 'CONNECT INTERNAL' and the issues that
determine if a user has permission to connect internal or not.
The details here apply to releases 7.0.16 through to 8.1. If
using Oracle 7.1.6 or higher Oracle strongly advises you use the
SYSDBA or SYSOPER privileges instead as 'CONNECT INTERNAL' may
be removed after Oracle 8.1 .

  For details of SYSDBA and SYSOPER see <Note:50507.1>

2) Local Connections
~~~~~~~~~~~~~~~~~~~~
  To make local 'internal' connections to an Oracle instance a
user must either:

        a) Supply a password
   OR   b) Be a user belonging to a special Unix 'dba' group.

  The first of these follows the same rules as defined in
Section (3) below so is not discussed in this section. Here we
concentrate on the rules for option (b).

  2.1) DBA Group
  ~~~~~~~~~~~~~~
    The 'DBA' group is chosen at installation time and is
usually the group 'dba' by default.

    The DBA group is compiled into the 'oracle' executable and
so it the same for all databases running from a given
ORACLE_HOME directory. The actual group being used as this DBA
group can be checked thus:

        cd $ORACLE_HOME/rdbms/lib
        cat config.[cs]

    The line '#define SS_DBA_GRP "group"' should name the chosen
DBA group. If you wish to change the DBA group change the group
name shown in this file.

    Eg: Change: #define SS_DBA_GRP "dba"
        to:     #define SS_DBA_GRP "mygroup"

    To effect any changes to the DBA group and to be sure you
are using the group defined in this file relink the Oracle
executable as below. Be sure to shutdown all databases before
relinking:

Oracle Version < 7.3        Oracle Version >= 7.3
~~~~~~~~~~~~~~~~~~~~        ~~~~~~~~~~~~~~~~~~~~~
rm config.o            rm config.o

make -f oracle.mk config.o    make -f ins_rdbms.mk config.o
make -f oracle.mk ioracle    make -f ins_rdbms.mk ioracle

    (Note the above instructions may vary depending on your
platform / release)

    For a DBA group to be accepted by Oracle it must:

        - Be compiled into the Oracle executable
        - The group name must exist in /etc/group (or in 'ypcat
          group' if NIS is being used)
        - It CANNOT be the group called 'daemon'

  2..2) Local Connect Internal
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    On Unix systems a user may be a member of more than one
group and should be allowed to connect internal without a
password provided:

        - One of the groups of which they are a member is the
DBA group as defined in config.c (config.s on some platforms)
and as linked into the 'oracle' executable.

        - The DBA group is a valid group as defined in
/etc/group (Or as defined in NIS by 'ypcat group')

        - The users PRIMARY group (Ie: the one shown by the 'id'
command) is not the special group 'daemon'.

    It is quite common for the 'root' user to be required to
have 'connect internal' privilege. Unfortunately it is also
common for the root users primary group to be the group 'daemon'
which precludes it from being allowed to connect internal. There
are two ways to tackle this problem:

        a) Make the root users PRIMARY group the DBA group

     or b) Where available use the 'newgrp' command to change
the users primary group to the DBA group.

           Eg:  $ newgrp dbagroup
                $ sqldba mode=line
                SQLDBA> connect internal

           This can also be used in shellscripts thus:
                :
                newgrp dbagroup <<!
                  # Commands requiring connect internal
privilege
                  # Eg: dbstart
                !

     or c) For systems where 'newgrp' is not available or does
not work from scripts you can use 'su' instead. Eg:
        :
        su - oracle <<!
          # Commands requiring connect internal privilege
        !
       Note: The user you 'su' to should be able to 'connect
internal' without a password, for example by having their
primary
 group as DBA.

    Some Oracle releases have problems with identifying the DBA
group when it is not the users primary group. If you encounter
problems with connect internal and the DBA group is set
correctly try making the users primary group the DBA group, or
use 'newgrp' as in (b) above.

3) Remote Connect Internal
~~~~~~~~~~~~~~~~~~~~~~~~~~
  Remote connect internal requires the database to be configured
to allow remote DBA operations. The remote user will HAVE to
supply a password in order to connect internal. This is a
tightening in security from Oracle 6 where remote users could
connect internal without a password.

  Ie: In Oracle7 to perform a remote connect internal you must
use the syntax 'CONNECT INTERNAL/PASSWORD'

  To allow remote internal connections you must:
        - Set up a password file for the database on the server
        - Set up any relevant init.ora parameters
        - Set up any SQL*Net options required

  3.1) Setting up a Password File
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    The connect internal password protection is controlled by an
Oracle 'Password' file. The exact commands used to set up this
file are different for different Oracle releases but the basic
concept is that a special file is created to hold the 'connect
internal' password.

   Oracle  Init.Ora Parameters        Command
   ~~~~~~  ~~~~~~~~~~~~~~~~~~~        ~~~~~~~
    7.1.x   remote_login_passwordfile  orapwd file=<fname>
password=<pwd>
    7.0.x                              orapasswd

    7.1.x
    ~~~~~
      To create a password file log in as the Oracle software
owner and issue the command:

        orapwd file=$ORACLE_HOME/dbs/orapw$ORACLE_SID
password=mypasswd

      using the required password. The file name is important
and should be specified as above. You should create this file
when the database is shut down.

      To change a password:
                - shut down the database,
                - Rename the $ORACLE_HOME/dbs/orapw$ORACLE_SID
file
                - Issue a new orapwd command with a new password

    7.0.x
    ~~~~~
      To create a password file log in as the Oracle software
owner and issue the command:

         orapasswd

      This will prompt for the ORACLE_HOME, the ORACLE_SID and
the 'connect internal' password. Enter the required values.

      To change a password re-run the 'orapasswd' tool.

  3.2) Setting up the Init.Ora file
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    To enable remote internal connections set the init.ora
parameters thus:

    7.1.x
    ~~~~~
      Set REMOTE_LOGIN_PASSWORDFILE to either EXCLUSIVE or
SHARED. EXCLUSIVE forces the password file to be tied
exclusively to a single instance.  To disable remote internal
connections set REMOTE_LOGIN_PASSWORDFILE to NONE.

    7.0.x
    ~~~~~
      No init.ora changes are required for 7.0.x releases.

    Note: The setting of REMOTE_OS_AUTHENT does NOT affect the
ability to connect internal from a remote machine.

  3.3) SQL*Net Options
  ~~~~~~~~~~~~~~~~~~~~
    From 7.0.16 onwards there are no SQL*Net settings that
control the ability to connect internal from remote machines. In
particular the settings shown below are no longer required:

        SQL*Net V1:     The dbaon / dbaoff options are
irrelevant

        SQL*Net V2:     The REMOTE_DBA_OPS_ALLOWED /
REMOTE_DBA_OPS_DENIED
                        parameters are irrelevant

4) Bugs and Special Notes
~~~~~~~~~~~~~~~~~~~~~~~~~

  Common Errors
  ~~~~~~~~~~~~~
    ORA-09910: Unable to find ORACLE password file entry for
user.
      On 7.0.x the $ORACLE_HOME/dbs/orapasswd file cannot be
accessed.

    ORA-01031: insufficient privileges
      Connect Internal has been issued with no password.
      For local connections the user is NOT in the DBA group as
compiled
      into the 'oracle' executable.
      For remote connections you must always supply a password.

      This error can also occur after a successful connect
internal/password
      if there REMOTE_LOGIN_PASSWORDFILE is either unset or set
to NONE in
      the init.ora file.

    ORA-01017: invalid username/password; logon denied
      This is a fairly general error that indicates one of the
following:
        - REMOTE_LOGIN_PASSWORDFILE is set to NONE
        - The password file does not exist
        - The password supplied does not match the one in the
password file
        - The password file been changed since the instance was
started

  7.1.x - Deleting/Changing the 'orapw$SID' File
<Bug:262100>
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    If you delete the Oracle password file while the instance is
running
    you will NOT be able to connect internal from remote
machines, even if
    you re-create the file. You must:
        - Shutdown the instance (using a local connection)
        - Create the new password file
        - You can now connect internal remotely and restart the
instance

  7.1.3 - REMOTE_LOGIN_PASSWORDFILE ignored
<Bug:209449>
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    If a password file exists at all then you can perform remote
    CONNECT INTERNAL operations even if the value of
REMOTE_LOGIN_PASSWORDFILE
    is set to NONE (the default).

    The workaround is to delete the password file to prevent
remote internal
    connections.

  7.1,7.2 - Security Loophole                    <Bug:321727>
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Be aware that there is a potential security loophole in the
orapwd
    implementation in Oracle 7.1 and 7.2. Details of this are
NOT disclosed
    so as not to compromise existing users of the option. The
fix is in
    Oracle 7.3.2.

Review &
      Followup

      Rating:
      4
  October 22, 2002
Reviewer:
      A reader

      Rating: 5
connect
      internal  October 23, 2002
Reviewer:  atul
       from india
Sir,

We are using oracle 8...and as per your document if a user
is added in dba group he can connect with connect internal.

I can connect to connect internal with user oracle..
But when i try to connect internal from other user
which in same group where oracle user is,i can't able to conect..
connect internal asks password..

What is wrong??

Thanks.
atul

          Followup:

When you install -- you pick the OS group that has the ability to "connect
internal" to a local instance (not using sqlnet) without providing a password.

Either you are not connecting locally (trying over the network -- eg: TWO_TASK
environment variable is set) or the group the installer of your software chose
was not DBA.

      Rating:
      5
  October 23, 2002
Reviewer:
      A reader
Hi Tom

when I connect to database thru SVRMGRL  by connect internal
It is not asking me any password but initial days I used to provide password
i.e. 3 months back.
.My worry  is if it is like this any one can enter and do the something
nonsense.
What could be the reason. and what to do for authentication .
Please give me the solution.

Thanks

          Followup:

find out what you changed 3 months ago.

You must be local (on the server)
you must be in the right group now (whereas before you were not)

      Rating: 5
connect
      internal  October 24, 2002
Reviewer:  atul
       from india
Sir,

As discussed earlier i have two users in DBA grouP
one is oracle and other is test..

by logging to oracle i can connect internal
But when logging to test i  can't do it it asks password..

I have checked Oracle_home variable is set different in test
also we are using TWO_TASK..

so by setting ORACLE_HOME same as oracle can i connect??

Thanks.
atul

          Followup:

by using two_task you are connecting over the network -- meaning you will be
using a password.

if you connect "direct", in the same fashion you do with "oracle" user -- it
will work in the same fashion.

      Rating: 5
Is this a BUG
      ???  January 21, 2003
Reviewer:  Riaz Shahid
       from PRAL, Islamabad, Pakistan
Dear Sir !

Consider the following:

Microsoft Windows 2000 [Version 5.00.2195]
(C) Copyright 1985-1999 Microsoft Corp.

C:\>svrmgrl

Oracle Server Manager Release 3.1.7.0.0 - Production

Copyright (c) 1997, 1999, Oracle Corporation.  All Rights Reserved

Oracle8i Enterprise Edition Release 8.1.7.2.1 - Production
With the Partitioning option
JServer Release 8.1.7.2.1 - Production

SVRMGR> connect hgy
Password:
ORA-01017: invalid username/password; logon denied

SVRMGR> connect hgy as sysdba
Password:
Connected.
SVRMGR>
SVRMGR>
SVRMGR> select count(1) from tab;
COUNT(1)
----------
      1595
1 row selected.
SVRMGR>

My question is: Why the username/password not verified when we connect as sysdba
? The same problem is DBA studio. The user hgy deos not exist. But DBA Studion
and Server Manager Utility is allowing it logon as sysdba.
My remort_apssword_authentication is set to exculsive. I've changed password of
SYS but the internal password is not changed.

Internal's password should be the same as that of SYS (due to the fact that
internal is not a username) but it is not in this case.

I am using 8.1.7.2.1

What should i do...please suggest

Regards

Riaz

          Followup:

it is as if you are connecting via

connect / as sysdba

the OS account you are in as is allowed to do this.  don't use that OS account
if you don't like that behavior.  On the local machine, there are accounts that
need no username/password -- the OS does it all.  This is by design, the way it
is supposed to work.

Here the username/password were just "formalities" that were ignored.

SVRMGR> connect not_a_user_at_all as sysdba
Password:
Connected.
SVRMGR> select * from all_users where username = 'NOT_A_USER_AT_ALL';
USERNAME                       USER_ID    CREATED
------------------------------ ---------- ---------
0 rows selected.
SVRMGR>

but that only works because:

SVRMGR> connect / as sysdba;
Connected.
SVRMGR>

works.  If I log into the OS as another user that is not allowed to do this:

SVRMGR> connect not_a_user_at_all as sysdba
Password:
ORA-01031: insufficient privileges
SVRMGR>

is what happens.

      Rating: 5
Still some
      questions...  January 23, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Islamabad, Pakistan
Thats Great...

But one part of my question is not answered:

The password of internal and sys should be the same. But it isn't. Is there ant
parameter we'll have to use ? Or i'll have to use ORAPWD utility ?

Regards

Riaz

          Followup:

they will not be the same in all cases.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:670117794561
for example

      Rating:
      5
Great  January 25, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Islamabad, Pakistan
That was simple and to the point.

Thank you Tom...

      Rating:
      5
Problem  March 15, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Islamabad, PAkistan
Tom !

Does that mean that any user (even on the other machine) having ora_dba role at
OS level can access my DB as sysdba ? Suppose my DB is running on M1 and another
user on M2 having Ora_dba for that machine (at OS level) can access DB without
any password;

How Can i prevent such access ???

Regards

          Followup:

anyuser on the server that is in the DBA group can connect as sysdba.  simply
limit access to this group.

anyuser on some OTHER server - no, they cannot.

      Rating: 5
Still Problem
      ...  March 17, 2003
Reviewer:  Riaz Shahid
       from PRAL, Islamabad, Pakistan
Hello Tom !

Here's my test for this (starr2 is tns name for my DB server and i am connecting
from other machine).

SQL> connect lahore@starr2
Enter password: ******
Connected.
SQL> connect internal@starr2 as sysdba
Connected.
SQL> connect sdfsdfsd@starr2 as sysdba
Enter password: ****
ERROR:
ORA-01017: invalid username/password; logon denied

Warning: You are no longer connected

My question is :
Why i am able to connect internal as sysdba even from other machine. Shouldn't
it ask internal password ???

Regards

Riaz

          Followup:

what is the output of

show parameter remote

on that server, additionally -- care to share relevant pieces of information
such as operating system and versions of software....

      Rating: 5
Desired
      Output  March 17, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Islamabad, Pakistan
Hello !

Here is your desired information.

Oracle Server Manager Release 3.1.7.0.0 - Production

Copyright (c) 1997, 1999, Oracle Corporation.  All Rights Reserved.

Oracle8i Enterprise Edition Release 8.1.7.2.1 - Production
With the Partitioning option
JServer Release 8.1.7.2.1 - Production

SVRMGR> connect internal@starr2
Connected.
SVRMGR> connect internal@starr2 as sysdba
Connected.
SVRMGR>
SVRMGR> show parameter remote
NAME                                TYPE    VALUE
----------------------------------- ------- ------------------------------
remote_dependencies_mode            string  TIMESTAMP
remote_login_passwordfile           string  NONE
remote_os_authent                   boolean FALSE
remote_os_roles                     boolean FALSE
SVRMGR>

Riaz

          Followup:

have you checked what database you are ACTUALLY connected to?

I cannot reproduce your situation.  Please open a tar with support on this one.

      Rating: 3
Oracle
      DBA  April 16, 2003
Reviewer:  Arash
       from Canada
Hi Tom

when I connect to database as Internal thru SVRMGRL(Oracle 8.1.7, Windows NT
4.0), it is not asking me any password. What could be the reason? Please give me
the solution.

The password file has been created at the time of creating an instance and also
remote_login_passwordfile=EXCLUSIVE
has been set in init.ora

Thanks,
Arash

P.S. I read your following advice
(You must be local (on the server)
 you must be in the right group now (whereas before you were not)

but I still don't know what I should do?

          Followup:

you are logging in at the OS level as a user who is privelged enough to do that.

solution -- log in as someone else, or remove the privs of that user at the OS
level.

      Rating: 3
Oracle
      DBA  April 17, 2003
Reviewer:  Arash
       from Canada
Thanks Tom. You are a brilliant.
I have another question which might not related to you !

1- What privileges should I revoke from user?
2- How can I do that?

Because, I just used the default values and I didn't grant any privileges to any
user.

Thanks in advance,
Arash

          Followup:

It is a "windoze" thing -- there must be a GUI to make it trivial to do
somewhere ;)

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/win.817/a73008/ch7.htm#1042639

      Rating:
      3
  April 17, 2003
Reviewer:  Arash

Sorry Tom,

  I forgot to mention something. I have another servers as well which I logged
in as Administrator which is an OS user account on the Network. So the
privileges for Administrator are the same for all servers. But I just have this
problem on one of the servers and as you said, if I login as Arash on that
server, SVRMGRL asks for the Internal password.

Please let me know, what cause this?

Thanks again,
Arash

          Followup:

see the windows NT admin guide to which I pointed you above.

      Rating:
      1
  April 17, 2003
Reviewer:  Arash
       from Canada
Thanks a lot. It works & I learned many new things.

Happy Easter,
Arash

      Rating: 4
Why no password
      required ?  June 16, 2003
Reviewer:  Robert
       from PA
Tom,
2 questions:
1) I have always wondered...
   Why is it that in 8i you can just "Connect internal..."
and in 9i "Connect / as sysdba", WITHOUT supplying password ?
How does this stop any joe from doing it ?

2) 9iR2/Linux : I can do "Connect / as sysdba"
to my instance without getting prompted for password.

But for OEM Repository instance I get prompted for password.
, and I have your modified version of login.sql in SQLPATH,
so my problem now with this instance is that "dbshut"
won't work properly.....I don't want to have to manually do "shutdown
immediate/abort".....

Alternatives or how to disable this password prompting, please.

Thanks

          Followup:

1) tell me, who do you have to be logged in as in order to do that -- any old
joe cannot do that, only ones that you've purposely allowed to at the OS level
can.

2) oem is probably running over the network and you cannot "/ as sysdba" over
the network.

add

define gname=idle

to the login.sql above "column ... new_value gname"

that'll make it so the login.sql doesn't "hang" when it connects to an idle (not
started) database.

      Rating:
      1
  July 09, 2003
Reviewer:
      A reader

      Rating: 4
Machine name
      change !!  September 01, 2003
Reviewer:
      A reader
Tom,
i want to know, what are the changes i need to do when my machine name is
changed. i have my server on windows 2000. Mostly we wont change the machine
name, but this time we have to change it for some unavoidable reasons.

I understand that we cannot start the server but is there any way on how to
restore it ? without reinstallation with the new server name.

Thanks for all that you do for Oracle Community.

          Followup:

why can't you start it?

if you change the hostname, you might have to update some configuration files --
like your tnsnames and listener.ora's but you can "start it"

what problems/errors are you facing here?

      Rating: 4
which all
      configuration files i need to change?  September 02, 2003

Reviewer:  A reader
Tom,

I just wanted to know the same from you ..i mean which all configuration files i
need to change.

Sorry for my ignorance - i was under the impression that we cannot start once
host name is changed. I do know that we need to change at tnsnames and
listener.ora, not sure if any other files other than these are affected. wanted
your help on this.

Thanks for your time,

          Followup:

just scan your init.ora, mts settings can be in there...

there could be some oem configuration files if you use that as well -- but I'm
not familar with them.

September 02, 2003 M. -- Thanks for the question regarding "Missing
      Sequence Number..", version 8.1.5

      You Asked
Hi Tom,

Some times while different users using any application using a Sequence Number,
miss some numbers. Even some times 5 to 10 Numbers gap...( I mean may be up to
10 Numbers Missing).. I know those users  can delete those records.

How does it hapen, I know that if you have cached numbers and the Server goes
down , you may lose those numbers, But how when there is nothing like that...

and What about if 2 or 4 Users accessing the Nextval from the same Sequence,
will be there any Dead Lock??? and How is it resolved.

Thanks..

      and we said...
Sequences will never generate a gap free sequence of numbers.

If someone calls seq.nextval and doesn't insert the value somewhere (or does
insert it and later rolls back their transaction) that number is GONE.

Sequences are database objects and may be aged out of the shared pool like
everything else -- it doesn't take a database shutdown to produce a gap.

You should never count on a sequence generating anything even close to a gap
free sequence of numbers.  They are a high speed, extremely scalable multi-user
way to generate surrogate keys for a table.

There is never a chance of a deadlock on a sequence -- they are designed to be
multi-user and highly scalable.  They remove the concurrency related issues
normally associated with unique key generation.

follow up to comment below

He said he knew that when the database was shutdown he would lose cached
entries, he asked why he might see that without a shutdown -- that is what I
answered.

NOT caching the sequence is not a good response.  That obviates much of the
speed of the sequence -- understanding that sequences do not generate gap free
sequences of number is the right answer.  A rollback will introduce a gap -- you
must be prepared to live with it.  My answer is always "set the cache high, go
for concurrency, live with the fact that there will be gaps".

followup to comment two below

A cache size of 500 or even 1,000 would not be unreasonable in that case.
You'll avoid the recursive transaction that happens every time the sequence runs
out of cached entries.

followup to comment three below

Good point -- i should have made that one ;)

the "cache" is simply a pair of numbers -- the start of the cache and the end of
the cache.  If you cache 2 or cache 2,000 -- it is the same amount of space.

followup to comment 6 below

(i can't let connor get the last word in here ;)

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:263215360334
for how to pin a sequence upon startup.  The "de-pin" would be similar in a
shutdown trigger.

However, my 2 cents here is ..... this is a lot of code in an attempt to
circumvent something that is meaningless.  contigous sequences of numbers are
pretty much impossible with sequences (only takes but one rollback -- and those
will happen).  This is alot of time spent on something that should really just
be "ignored" all together.  You can get sort of close to having a gap free
sequence but close only counts in grenades.  You cannot rely on it being gap
free (and it will not be gap free)....

Review &
      Followup

      Rating: 2
yes it
      happen  April 23, 2001
Reviewer:  ashraf
      gamak  from kuwait
yes tom it happen when the sequance is cache when the database shutdown the gap
is begin because som number in the cache so he must nit use cashe sequance

      Rating: 4
How to
      determine ideal cache size?  July 20, 2001

Reviewer:  Paul Dong  from New York
Tom, noting your recommendation to "set the cache high", what would you consider
to be an ideal cache size?

We use sequences to generate surrogate keys to facts and dimensions stored in
the Data Warehouse.  It would be normal for the process to needs thousands of
sequence numbers at a time, (and more, when initial load is occuring).  Rather
than avoiding gaps, my goal is maximizing throughput.

Thanks!

      Rating: 5
and to add to
      Tom's last comment...  July 20, 2001
Reviewer:
      Connor  from UK
Its often thought that a large sequence cache somehow uses more memory but this
is not the case - a large cache is (very) good

      Rating: 4
Really Good
      Point (last one)  July 22, 2001
Reviewer:
      sreenu  from singapore
Thanks Tom for expanding that point. Great logic.

      Rating: 3
How can I avoid
      gap while a sequence ages out?  July 22, 2001

Reviewer:  hahaer  from Chine
I created a sequence at cache size 500. From 9pm to 10pm this sequence will be
used frequently, out of that period, it's used little. So every day ,a gap about
420 to 450 will be created because it ages out of library cache. How can I avoid
this gad for this sequence? Thanks in advance.

      Rating: 4
Response to
      comment 5 (sequence aging out)  July 23, 2001

Reviewer:  Connor  from UK
You can keep a sequence in the shared pool using DBMS_SHARED_POOL.KEEP.  Keep
the sequence using a startup trigger.  When you need to shut the database, on a
shutdown trigger, make the sequence nocache then back to cache.  This "unloads"
it from the shared pool and minimises (not eliminates) the sequence number gaps.

      Rating: 4
Thanks
      Connor  July 23, 2001
Reviewer:  Gururaj
      Kulkarni  from Norcross GA,USA
For getting more from Tom :) and sharing good info with us.

      Rating: 4
Speciall y
      cacheing lot of numbers  July 23, 2001
Reviewer:
      Pichaimani Balaubramanian  from San Jose, CA USA
Was a real eye opener

      Rating: 5
Metalink
      implies non-gap sequence  September 02, 2003

Reviewer:  A reader
Well, they're wrong of course, but Note:62002.1 says:
"...if an application requires a sequence which never skips any numbers, a
non-cached sequence must be used. "
which kind of implies that you can actually achieve a non-gap sequence, doesn't
it?

          Followup:

it should say

"If an application requires a sequence which never skips any numbers, a sequence
CANNOT BE USED"

non-cached will have gaps - i can 100% assure you of that.

September 02, 2003 Marcio -- Thanks for the question regarding "Get
      Rows", version 9.2.0

      You Asked
Tom,

1) Is this best way (performance) to get count(*) from all tables?

scott@MRP9I1> create or replace function get_rows( p_tname in varchar2 ) return
number
  2   as
  3        l_columnValue    number default NULL;
  4    begin
  5        execute immediate
  6           'select count(*)
  7             from ' || p_tname INTO l_columnValue;
  8        return l_columnValue;
  9    end;
 10  /

Function created.

scott@MRP9I1>
scott@MRP9I1> select table_name, get_rows(table_name) nrows
  2    from user_tables
  3  /

TABLE_NAME                          NROWS
------------------------------ ----------
BONUS                                   0
DEPT                                    4
EMP                                    15
SALGRADE                                5

4 rows selected.

2) does @showsql show imp/exp activity? Guess not, how can I see this activity?

Best regards,
Marcio

      and we said...
1) no, static sql just:

select count(*) from bonus

is going to be "faster" then

select table_name, get_rows(table_name) nrows
  from user_tables where table_name = 'BONUS'

but, if you want to get the count of all tables right now -- that would be the
easiest way...

2) yes it does -- imp/emp just does sql -- imp spends lots of time issuing
"create index" and "constraints" -- which means it'll show active in showsql but
not have any sql to show for it (a peek at v$session_longops can be helpful in
figuring out what it is doing as well)
Review &
      Followup

      Rating: 3
Count(*) from
      all tables  September 03, 2003
Reviewer:  Angus
      McTavish aged 75  from Cumbria, England
Run this in sqlplus to get a count of all tables:

store set store replace
set lines 100
set head off
set feed off
set term off
set wrap off
COLUMN DUMCOL FORMAT A30 TRUNC

spool cnt.sql
select 'select '''||table_name||''' DUMCOL, count(*) from '||table_name||';'
from user_tables
/
spool off
spool counts.lst
@cnt
spool off
CLEAR COLUMNS
@store
ed counts.lst

      Rating: 2
IMHO --
      Function is better  September 03, 2003
Reviewer:
      Marcio  from Br
I think more easer function -- to get all of count(*)
Consider:
Tables                                        rows
-----------------------------------------------------
LOCALIDADE                                    24275
LOCAL_ANATEL                                  37000
LOCAL_DETRAF                                  24275

*-------------------
* script method
*-------------------

store set store replace
set lines 100
set head off
set feed off
set term off
set wrap off
COLUMN DUMCOL FORMAT A30 TRUNC

spool cnt.sql
select 'select '''||table_name||''' DUMCOL, count(*) from '||table_name||';'
from user_tables
where table_name like 'LOCA%'
/
spool off
spool counts.lst
@cnt
spool off
CLEAR COLUMNS
@store

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       10      0.00       0.00          0          0          0           0
Execute     11      0.00       0.00          0          0          0           3
Fetch        9      0.62       0.61          0        821          0           7
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       30      0.62       0.62          0        821          0          10

*----------------------
* Function Method:
*----------------------
select table_name, get_rows(table_name)
  from user_tables
 where table_name like 'LOCA%'
/

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        4      0.01       0.00          0          0          0           0
Execute      4      0.00       0.00          0          0          0           0
Fetch        4      0.65       0.63          0        771          0           4
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       12      0.66       0.64          0        771          0           4

--
Anyway thanks for suggestion
Marcio


Subject | Article | Last Updated (East coast time)
how can a block belong to multiple segments | article only | Sep 03, 2003 06:45 am
Partitioning | article only | Sep 03, 2003 06:51 am
inserting data into a blob and retrieving it | article only | Sep 03, 2003 06:56 am
Trace_SQL, Explain Plan and Parsing | article only | Sep 03, 2003 07:00 am
Syn database and in-memory process | article only | Sep 03, 2003 07:02 am
How Oracle handles daylight saving time change | article only | Sep 03, 2003 07:06 am
error handling with implicit cursors | article only | Sep 03, 2003 09:02 am
Problem when executing UTL_SMTP | article only | Sep 03, 2003 11:55 am
How to bypass the Listener & connect directly to a Dispatcher | article only | Sep 04, 2003 08:37 am
Limit CPU Usage | article only | Sep 04, 2003 08:51 am
optimizing some plsql | article only | Sep 04, 2003 08:56 am
Mass Delete | article only | Sep 04, 2003 11:14 am
Project marvel | article only | Sep 04, 2003 11:35 am
Can I create a synonym for a role? | article only | Sep 05, 2003 01:29 pm
What is the best book for STATSPACK tuning | article only | Sep 05, 2003 02:02 pm
How to Increase Font Size in SQL *Plus | article only | Sep 05, 2003 03:15 pm
Index Tablespace | article only | Sep 05, 2003 03:28 pm
Transporting tablespaces / Backup recovery | article only | Sep 05, 2003 03:39 pm
Transaction | article only | Sep 05, 2003 03:40 pm
SQL loader - reenable disabled constraints option | article only | Sep 05, 2003 04:29 pm
v$lock self join takes time | article only | Sep 05, 2003 06:53 pm
correlated update | article only | Sep 05, 2003 07:04 pm
Question in DYNAMIC SQL | article only | Sep 05, 2003 07:07 pm
Bind Variables / Cursor Sharing with Multiple Table Join | article only | Sep 06, 2003 08:57 am
max | article only | Sep 06, 2003 11:56 am
second max | article only | Sep 06, 2003 12:01 pm
Sql*Loader problem | article only | Sep 06, 2003 01:03 pm
current mode gets and rowids | article only | Sep 06, 2003 01:16 pm
Script to insert a test record into a table | article only | Sep 06, 2003 02:14 pm
Speeding up insert into database tables from external database. | article only | Sep 06, 2003 04:56 pm
Partition | article only | Sep 06, 2003 06:25 pm
Switching between rows and columns | article only | Sep 07, 2003 08:37 am
To create a view from a table where some of the columns in the table stores the code value and those columns have to be joined to the master table to get the decoded value. How do I go about writing the query to create the view object | article only | Sep 07, 2003 04:44 pm
Oracle & Perl, external tables. | article only | Sep 07, 2003 06:05 pm
re: data files & parallel execution | article only | Sep 07, 2003 06:06 pm
Log off from mod pl/sql app | article only | Sep 08, 2003 11:26 am
problem with import | article only | Sep 08, 2003 12:43 pm
advantages and disadvantages of indexes, cursor usage, what are x$ tables. | article only | Sep 08, 2003 12:50 pm
Renaing index name | article only | Sep 08, 2003 12:53 pm
Using DDL in pl/sql | article only | Sep 08, 2003 02:51 pm
starter db | article only | Sep 08, 2003 02:55 pm
View or table to query for droped database objects -tables- | article only | Sep 08, 2003 05:38 pm
ORA-1000 Maximum Cursors Exceeded | article only | Sep 08, 2003 05:39 pm
hierarchy nested table | article only | Sep 08, 2003 05:42 pm
selection / Projection | article only | Sep 08, 2003 05:44 pm
shrink raw device | article only | Sep 08, 2003 05:54 pm
Support for Oracle 8i | article only | Sep 09, 2003 11:12 am
regarding pl/sql cartridges. | article only | Sep 09, 2003 11:35 am
database link | article only | Sep 09, 2003 11:44 am
Command Line Parameters | article only | Sep 09, 2003 12:00 pm
Running Form Totals in web form | article only | Sep 09, 2003 12:02 pm
Please guide to compile pro*c program without connecting to database | article only | Sep 09, 2003 09:21 pm
row(s) 1 - 52 of 52
September 03, 2003 Solomon -- Thanks for the question regarding "how can a block belong to multiple segments", version 8.1.7
You Asked
Hi tom,

I have a very very fundamental question. Please help me clear this doubt.

I have read that a segment can belong to multiple datafiles but to only one
tablespace and no extents can belong to multiple datafiles, and no blocks can
belong to multiple extents.

But in your book while I was going through the sections which describes block
overheads, you say that TABLE DIRECTORY of block header can have information
about multiple tables since a block can belong to multiple tables.

Now this goes against the basic concept of no block can belong to multiple
extents and no extent can belong to multiple segments hence no blocks can belong
to multiple segments. What I understand is each table is one more segments
(based on partitioned or not) , so how can a block belong to multiple segments?
and we said...
A table is not necessarily a segment -- therein lies your confusion.

A block can hold data from many tables.  When the tables are in a cluster.  When
the tables are in a cluster -- the CLUSTER is the segment -- the table is not.

Partitioning is another case where the TABLE IS NOT THE SEGMENT -- there the
partitions are the segment -- the table is just the table.

Only when a table is the segment would your argument be true.

when you get to the chapter on tables, I go over clusters in much more detail.
Wait till you find out that rowids are only unique in tables -- not in a
database!
Review & Followup
Rating: 3
September 02, 2003
Reviewer: Solomon
Hi Tom,

thanks for the explanation.

So I think that a block will belong to more than one table only in the case of
CLUSTERS. Because even in the case of partitioned tables, one table might have
multiple segments and hence multiple extents and hence multiple blocks, which
means that there can be a 1 to many mapping between TABLE and BLOCKS but there
can never be a many to one mapping, right?

Please correct me if I am wrong.

Followup:
well, i guess technically a bitmap join index would be another case -- the data
(key value) can come from T1 but the rowids in the bitmaps are pointing to T2...

but yes, in general, only when blocks are in a cluster will you see more then
one table on them at a time.

September 03, 2003 Srini -- Thanks for the question regarding "Partitioning", version 8.1.6
You Asked
  1  create table dummy
  2  (comment_skey number not null)
  3  STORAGE (INITIAL 1M NEXT 1M PCTINCREASE 0 MAXEXTENTS UNLIMITED)
  4         PARTITION BY HASH(COMMENT_SKEY)
  5*        (PARTITION DIMCOMMENTS_P1 TABLESPACE DIMCOMMENTS_P1_DATA)
SQL> /
create table dummy
*
ERROR at line 1:
ORA-00439: feature not enabled: Partitioning

Why am i getting this message.  I set the partition_view_enabled = true in the
parameter file.  Do I need to run any other script.

Appreciate your quick help.

Thanks,

Srini
and we said...
Partitioning is a separately licensed option available with Enterprise and
Personal Editions of Oracle only.  You apparently have Standard Edition.

See
http://technet.oracle.com/doc/oracle8i_816/server.816/a76962/ch4.htm#73594
for a feature/option matrix.
Review & Followup
Rating: 1
February 24, 2003
Reviewer: A reader
Tom,
 could you please illustrate the influence of the
PARTITION_VIEW_ENABLED parameter with an example.

Followup:
well, goto to the Oracle7 (yes, Oracle7) docs
http://technet.oracle.com/documentation/oracle7.html
get
A48506_1      PDF             Oracle7 Server Tuning, Release 7.3.3

and read chapter 11.  Then remember that partition views are included only for
backwards compatibility (meaning no development has been done to them since
7.3), and they will be "not there" in some future release as per the 8i
documentation...

Rating: 4
February 24, 2003
Reviewer: A reader
:) Thanks, that's why I don't find it in the documentation.
This means that is better set this paramter in false?

Rating: 5
PARTITION_VIEW_ENABLED is useless. March 11, 2003
Reviewer: juancarlosreyesp@yahoo.com
Using PARTITION_VIEW_ENABLED

I tried with two scenarios enabling an disabling the parameter and I didn't see
nothing different.
View
A) including a where clause in the view
SELECT
HCA_FECHA , HCA_FEMER , HCA_PRMER , HCA_TRMER , HCA_FEVAL , HCA_PRVAL ,
HCA_TRVAL , HCA_CLASE_OPERACION , HCA_LIQ , HCA_CONTRAPARTE ,
HCA_NRO_OPERACION_VEVC , HCA_DSC , HCA_DSM , HCA_TCUSD , HCA_CODCART , HCA_INST
, HCA_SERIE , HCA_DEBE , HCA_HABER , HCA_ENRP , HCA_ENCR , HCA_ENCUST ,
HCA_QUIEN_CUST , HCA_NRO_OPERACIONADQ , HCA_FEADQ , HCA_PRADQ , HCA_TRADQ ,
HCA_TDADQ , HCA_PLAZORP , HCA_CODCLI , HCA_LESCOLOCACION , HCA_CODVAL ,
HCA_TIPOMER , HCA_DEVACUM , HCA_DEVENGADO , HCA_POSICION , HCA_PRTEOADQ ,
HCA_TASAINCAP , HCA_ULTFEDEV , HCA_ULTPRINCAP , HCA_CUSTEO , HCA_TRANSFER ,
HCA_TIT_EXCESO
FROM SOA.HICARTERA_RW where hca_fecha > to_date( '10102002','ddmmyyyy')
UNION ALL
SELECT
HCA_FECHA , HCA_FEMER , HCA_PRMER , HCA_TRMER , HCA_FEVAL , HCA_PRVAL ,
HCA_TRVAL , HCA_CLASE_OPERACION , HCA_LIQ , HCA_CONTRAPARTE ,
HCA_NRO_OPERACION_VEVC , HCA_DSC , HCA_DSM , HCA_TCUSD , HCA_CODCART , HCA_INST
, HCA_SERIE , HCA_DEBE , HCA_HABER , HCA_ENRP , HCA_ENCR , HCA_ENCUST ,
HCA_QUIEN_CUST , HCA_NRO_OPERACIONADQ , HCA_FEADQ , HCA_PRADQ , HCA_TRADQ ,
HCA_TDADQ , HCA_PLAZORP , HCA_CODCLI , HCA_LESCOLOCACION , HCA_CODVAL ,
HCA_TIPOMER , HCA_DEVACUM , HCA_DEVENGADO , HCA_POSICION , HCA_PRTEOADQ ,
HCA_TASAINCAP , HCA_ULTFEDEV , HCA_ULTPRINCAP , HCA_CUSTEO , HCA_TRANSFER ,
HCA_TIT_EXCESO
FROM SOA.HICARTERA_RO where hca_fecha < to_date( '10102002','ddmmyyyy')

B) not including a where clause in the view
SELECT
HCA_FECHA , HCA_FEMER , HCA_PRMER , HCA_TRMER , HCA_FEVAL , HCA_PRVAL ,
HCA_TRVAL , HCA_CLASE_OPERACION , HCA_LIQ , HCA_CONTRAPARTE ,
HCA_NRO_OPERACION_VEVC , HCA_DSC , HCA_DSM , HCA_TCUSD , HCA_CODCART , HCA_INST
, HCA_SERIE , HCA_DEBE , HCA_HABER , HCA_ENRP , HCA_ENCR , HCA_ENCUST ,
HCA_QUIEN_CUST , HCA_NRO_OPERACIONADQ , HCA_FEADQ , HCA_PRADQ , HCA_TRADQ ,
HCA_TDADQ , HCA_PLAZORP , HCA_CODCLI , HCA_LESCOLOCACION , HCA_CODVAL ,
HCA_TIPOMER , HCA_DEVACUM , HCA_DEVENGADO , HCA_POSICION , HCA_PRTEOADQ ,
HCA_TASAINCAP , HCA_ULTFEDEV , HCA_ULTPRINCAP , HCA_CUSTEO , HCA_TRANSFER ,
HCA_TIT_EXCESO
FROM SOA.HICARTERA_RW
UNION ALL
SELECT
HCA_FECHA , HCA_FEMER , HCA_PRMER , HCA_TRMER , HCA_FEVAL , HCA_PRVAL ,
HCA_TRVAL , HCA_CLASE_OPERACION , HCA_LIQ , HCA_CONTRAPARTE ,
HCA_NRO_OPERACION_VEVC , HCA_DSC , HCA_DSM , HCA_TCUSD , HCA_CODCART , HCA_INST
, HCA_SERIE , HCA_DEBE , HCA_HABER , HCA_ENRP , HCA_ENCR , HCA_ENCUST ,
HCA_QUIEN_CUST , HCA_NRO_OPERACIONADQ , HCA_FEADQ , HCA_PRADQ , HCA_TRADQ ,
HCA_TDADQ , HCA_PLAZORP , HCA_CODCLI , HCA_LESCOLOCACION , HCA_CODVAL ,
HCA_TIPOMER , HCA_DEVACUM , HCA_DEVENGADO , HCA_POSICION , HCA_PRTEOADQ ,
HCA_TASAINCAP , HCA_ULTFEDEV , HCA_ULTPRINCAP , HCA_CUSTEO , HCA_TRANSFER ,
HCA_TIT_EXCESO
FROM SOA.HICARTERA_RO

WITHOUT PARTITION_VIEW_ENABLED
A) with where clause
SQL> select count(*) from hicartera where hca_fecha =
to_date('01102002','ddmmyyyy');

  COUNT(*)
----------
       217

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=9)
   1    0   SORT (AGGREGATE)
   2    1     VIEW OF 'HICARTERA' (Cost=4 Card=263 Bytes=2367)
   3    2       UNION-ALL
   4    3         FILTER
   5    4           INDEX (RANGE SCAN) OF 'CST_HCA_CODCART_RO' (UNIQUE
          ) (Cost=2 Card=1 Bytes=7)

   6    3         INDEX (RANGE SCAN) OF 'IDX_FEINSTSERIE' (NON-UNIQUE)
           (Cost=4 Card=262 Bytes=1834)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          4  consistent gets
          0  physical reads
          0  redo size
        380  bytes sent via SQL*Net to client
        503  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed
B) without where clause
SQL> select count(*) from hicartera where hca_fecha =
to_date('01102002','ddmmyyyy');

  COUNT(*)
----------
       217

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=9)
   1    0   SORT (AGGREGATE)
   2    1     VIEW OF 'HICARTERA' (Cost=4 Card=459 Bytes=4131)
   3    2       UNION-ALL
   4    3         INDEX (RANGE SCAN) OF 'CST_HCA_CODCART_RO' (UNIQUE)
          (Cost=2 Card=197 Bytes=1379)

   5    3         INDEX (RANGE SCAN) OF 'IDX_FEINSTSERIE' (NON-UNIQUE)
           (Cost=4 Card=262 Bytes=1834)

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

WITH PARTITION_VIEW_ENABLED
A)with where clause
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=9)
   1    0   SORT (AGGREGATE)
   2    1     VIEW OF 'HICARTERA' (Cost=4 Card=263 Bytes=2367)
   3    2       UNION-ALL
   4    3         FILTER
   5    4           INDEX (RANGE SCAN) OF 'CST_HCA_CODCART_RO' (UNIQUE
          ) (Cost=2 Card=1 Bytes=7)

   6    3         INDEX (RANGE SCAN) OF 'IDX_FEINSTSERIE' (NON-UNIQUE)
           (Cost=4 Card=262 Bytes=1834)

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

B)without where clause
SQL> select count(*) from hicartera where hca_fecha =
to_date('01102002','ddmmyyyy');

  COUNT(*)
----------
       217

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=9)
   1    0   SORT (AGGREGATE)
   2    1     VIEW OF 'HICARTERA' (Cost=4 Card=459 Bytes=4131)
   3    2       UNION-ALL
   4    3         INDEX (RANGE SCAN) OF 'CST_HCA_CODCART_RO' (UNIQUE)
          (Cost=2 Card=197 Bytes=1379)

   5    3         INDEX (RANGE SCAN) OF 'IDX_FEINSTSERIE' (NON-UNIQUE)
           (Cost=4 Card=262 Bytes=1834)

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

Would you suggest to set this parameter to FALSE?
Because the improvement I saw was when I set the where clause
(Cost=2 Card=1 Bytes=7), and the value for this parameter had no effect.

Thanks :)

Followup:
what the heck am I looking for here?

first -- partitioned views were a 7.3 thing, partitioning is 8.0 and above.
partitioned views are not really used anymore.

second -- what the heck am I looking for here?  there is so little data as to
not be meaningful.

anyway..

Rating: 5
March 11, 2003
Reviewer: juancarlosreyesp@yahoo.com
Thanks

The point is that I can't use partitioning in Oracle Standar Edition.

Which other information should I send, (suggestion) I agree with you it could be
a good idea to set the procedure to send information, so this could be usefull
for every one.

Do you want I follow this steps to send you statistics? or which other you
prefer :)

*****************************
TIMED_STATISTICS     = TRUE
STATISTICS_LEVEL     = ALL
*****************************
CREATE OR REPLACE TRIGGER SYS.TGR_ENABLE_TRACE
 AFTER
 LOGON
 ON DATABASE
BEGIN
 IF USER = 'SUP' THEN
  EXECUTE IMMEDIATE 'ALTER SESSION SET EVENTS ''10046 TRACE NAME CONTEXT
FOREVER, LEVEL 12''';
 END IF;
END;
/
*************************
TKPROF \SID_ora_xxx.trc C:\check.TXT

AND Finally get the data from C:\check.TXT???

Followup:
nothing -- send nothing.

this "feature" is officially depreciated, not to be used, defunct.  It had a
brief life of 7.3 -- period.  it is a technical dead end.

Rating: 5
March 11, 2003
Reviewer: A reader
:) Thanks Tom

Rating: 5
September 02, 2003
Reviewer: A reader
Tom,

I have Oracle Enterprise Edition 8.1.7.4 on HP-UX 11.0 which was upgraded from
8.1.7. Partitioning option has not been installed, if i wanna enable this
feature, can you please recommend me the steps.

Thanks..

Followup:
you would typically use dbca to add options to the database.

September 03, 2003 Shankar Ganesh -- Thanks for the question regarding "inserting data into a blob and retrieving it", version 8.1.6
You Asked
Hi Tom ,
I had written two procedures to write and read data from a blob.
they are as follows.

CREATE OR REPLACE PROCEDURE writeDataToLOB_proc IS
   Lob_loc         BLOB;
   Buffer          raw(32767);
   Amount          BINARY_INTEGER := 1;
   Position        INTEGER := 1;
   i               INTEGER;
   len                number;

BEGIN

   buffer:='98765';

   len:=length(buffer)/2;

   insert into CREATE$JAVA$LOB$TABLE (name, lob, loadtime)
      values ('1000', empty_blob(), sysdate);

   SELECT lob INTO Lob_loc
        FROM CREATE$JAVA$LOB$TABLE
           WHERE name = '1000';

   DBMS_LOB.OPEN (Lob_loc, DBMS_LOB.LOB_READWRITE);

   DBMS_LOB.WRITE (Lob_loc, len, 1, buffer );

   DBMS_LOB.CLOSE (Lob_loc);

commit;
END;
/

CREATE OR REPLACE PROCEDURE ReadDataFromLOB_proc IS

   Lob_loc         BLOB;
   Buffer          raw(32767);
   Amount          BINARY_INTEGER := 1;
   Position        INTEGER := 1;
   len              number;

BEGIN

   /* Select a LOB: */

   SELECT lob INTO Lob_loc
        FROM CREATE$JAVA$LOB$TABLE
           WHERE name = '1000';

   len:=DBMS_LOB.GETLENGTH(Lob_Loc);

   dbms_output.put_line('THE LENGTH IS:'||len);

   len := len*2;

   DBMS_LOB.READ (Lob_loc,len, Position, Buffer);

   dbms_output.put_line('the blob is read:'||Buffer);

END;
/

the problem is the display is not consistent.

if i write the value "1122"(even number of data)to the blob the display is
coming correctly as "1122"

if i write the value "11223"(odd number of data) to the blob the display is
coming as "011223"(a zero is getting appended in the beginning)

can u please tell me how to solve this issue..

thanks
Shankar Ganesh.
and we said...
STRONLY recommand you stop using CREATE$JAVA$LOB$TABLE.  that is a system table
and has a purpose.

Its the conversion between HEX and RAW thats getting you.  You are using a RAW
type but -- you are converting it to a character string (when you print it) and
you are converting character strings to raws by assigning them.

So, for example:

   buffer:='98765';

is really assigning:

  buffer = 0x9 || 0x87 || 0x65;

3 raw characters were placed into the raw buffer.

You need to use the UTL_RAW package to deal with raws.  Here are your routines
(parameterized):

scott@ORA8I.WORLD> CREATE OR REPLACE PROCEDURE writeDataToLOB_proc( p_name in
varchar2,
  2                                                   p_buffer in RAW )
  3  IS
  4     Lob_loc         BLOB;
  5  BEGIN
  6     insert into t (name, lob, loadtime)
  7     values (p_name, empty_blob(), sysdate)
  8     RETURNING lob into lob_loc;
  9
 10     dbms_lob.writeAppend( lob_loc, utl_raw.length(p_buffer), p_buffer );
 11  end;
 12  /

Procedure created.

scott@ORA8I.WORLD>
scott@ORA8I.WORLD> CREATE OR REPLACE PROCEDURE ReadDataFromLOB_proc( p_name in
varchar2 )
  2  IS
  3     Lob_loc         BLOB;
  4  BEGIN
  5     SELECT lob INTO Lob_loc
  6       FROM t
  7      WHERE name = p_name;
  8
  9     dbms_output.put_line('THE LENGTH IS: '||dbms_lob.getlength(lob_loc));
 10
 11     -- just print out the first 200 bytes since put_line
 12     -- cannot do more then 255
 13     dbms_output.put_line('the blob is read: '||
 14               utl_raw.cast_to_varchar2( dbms_lob.substr( lob_loc, 200, 1 ) )
 15                           );
 16  END;
 17  /

Procedure created.

scott@ORA8I.WORLD>
scott@ORA8I.WORLD> delete from t;

0 rows deleted.

scott@ORA8I.WORLD>
scott@ORA8I.WORLD> exec writeDataToLob_proc( '1001', utl_raw.cast_to_raw(
'101023' ) );

PL/SQL procedure successfully completed.

scott@ORA8I.WORLD> exec writeDataToLob_proc( '1002', utl_raw.cast_to_raw( 'Hello
World' ) );

PL/SQL procedure successfully completed.

scott@ORA8I.WORLD>
scott@ORA8I.WORLD> exec readDataFromLob_proc( '1001' );
THE LENGTH IS: 6
the blob is read: 101023

PL/SQL procedure successfully completed.

scott@ORA8I.WORLD> exec readDataFromLob_proc( '1002' );
THE LENGTH IS: 11
the blob is read: Hello World

PL/SQL procedure successfully completed.
Review & Followup
Rating: 5
most useful solutions i had ever seen July 20, 2001
Reviewer: shankar ganesh from india
i had always valued u r solutions much and it had given me a lot of lead into my
project.
thanks a lot

Rating: 3
Tell me how to .... April 01, 2002
Reviewer: Son
Can you tell me how to store picture in field Long Raw at my DB.
I uses Program VB to read picture and store it in my db.
Can you send for me some Document about VB and Oracle
thank you very much
I am looking forward to hearing from you.

Rating: 5
Tom Kyte Rocks August 16, 2002
Reviewer: Greg J from Washington, DC
I have been banging my head against this ASP problem where I could upload a file
directly into the DB using a SQL insert and update but I couldn't get a stored
procedure to do the same. I didn't realize my BLOB parameter should be a RAW for
external datatypes. The method for testing in SQL*Plus was helpful too.

Rating: 2
Display PDF stored in Database January 08, 2003
Reviewer: Laila Mac from India
Dear Sir
   i have searched for a lot of oracle sites to find out the solution to display
the pdf file inserted in the BLOB column of the table but failed to find out any
solution.
  I am using oracle 8i 8.1.6. Can you please tell me how can i display the pdf
file stored in the database? I am using client/server
  thanks

Followup:
client server -- hmm, which if the 5,000 or so client server programming
paradigms might you be using.

Then, maybe I can point you to what you need.

(people still do client server??)

Rating: 1
Horrible Response!!!!! January 08, 2003
Reviewer: Laila Mac from India
Sorry! but this is a horrible response.
everybody cannot move to web. What about those who are using oracle/developer 6i
in client/server. should they move to some other database environment to get
desired functionalities???
 If you cannot easily retrieve files inserted in the blob column then what is
the use of storing such files in the database. If we have to use Java, pro C or
other 3gl for getting full advantage from oracle then it means oracle has
nothing without the existence of java etc.

Followup:
dude -- settle yourself back down into your chair and tell me

WHICH OF THE 5,000 CLIENT SERVER OPTIONS ARE YOU USING AND I'LL TELL YOU WHAT TO
DO SINCE YOU DON'T SEEM TO BE ABLE TO FIND IT IN THE DOCUMENTATION YOURSELF.

eh?  cannot you answer the question?  tell me the tool you are using and I'll
point you to information.  I'm not going to do it for the 5,000 tools that are
out there.

What a horrible comment from you.

Rating: 5
My goodness January 09, 2003
Reviewer: Alexandre from Rio de Janeiro
Tom,

Our dear pope should canonize you...
Too much patience with such people above...

[]

Followup:
you mean you couldn't tell it was getting pretty stretched on that follow of
mine?

Rating: 5
communication problem January 10, 2003
Reviewer: hrishy from PA
Hi Tom

Laila Mac is using Developer6i..so she is looking for a solution with forms and
reports..You could point her to that solution tom..

Rating: 2
Developer 6i January 11, 2003
Reviewer: Laila Mac from India
I am using Developer6i with oracle 8.1.6

Followup:
You would use the OLE object embedding in all probability.

There should be an activex control capable of showing a pdf -the rest just "sort
of happens".  Not being a windows programmer, thats as far as I can go into the
tool itself -- suggest you goto otn.oracle.com under discussion forums - there
you will find a forms discussion group.  Odds are, someone there can be more
specific.

There are even support notes on metalink.oracle.com that discuss this in detail.
<Note:76602.1> for example is

How to Integrate Adobe Acrobat Reader into Oracle Forms Via An OCX Control

Rating: 4
Unable to see thePDF March 07, 2003
Reviewer: praveen from India
Hi Tom
I have sucessfully executed this code for Package.
When I am executing this package I am getting
Procedure succesfully completed,but unable to see the PDF.
can u guide me hoe can i see the pdf
from pl/sql

Followup:
you will never see the pdf in sqlplus -- it is just "stuff"

you can use owa_util.showpage to dump it, but it'll just be gibberish on the
screen.

Rating: 3
What if greater then 32k? August 20, 2003
Reviewer: Sasa from Belgarde, Serbia
Hi Tom,

What if in your example in procedure writeDataToLOB
param p_buffer  is greater then 32k?
We will get an error ORA-06502: PL/SQL: numeric or value error.
How to get proper chunks in procedures?
Should we substr raw(to limit to 32k)and do writeappend with this chunk?
In that case we would have loop with count maximum number of chunks?
For example if image has 250k we would have something like:
for 1..ceil(250000/32000)
loop
 substr p_buffer to 32k
 writeappend(with previosly created chunk);
end loop;

Am I correct or I am in the wrong hole ?

Followup:
you would just call dbms_lob.writeAppend over and over in another procedure if
you like.  Once the blob is there, you can select it out, writeAppend it.

you cannot have a p_buffer in plsql > 32k so the psuedo code you have won't fly.

the CLIENT would send 32k at a time.

Rating: 4
archive BLOB data offline September 02, 2003
Reviewer: Raghu from India
our application requires a functionality which
will archive application files offline.

the files used by the application is stored in the
database as BLOB table.

the user will select the files which he wishes to archive and these
files should be removed from the database and stored offline outside database in
a file system.

later when the user requires them these files should be
retrieved back.

any suggestion as how this should be implemented.

Background of the functionality
------------------------------
many departments are using the application.Each department pays for the
space they use in the database. when the sizes of all the files stored
by the department exceeds the space allocated to them, then the department
has to remove some files from the database.

but these files should be stored somewhere so that these can be brought back
into the database.

the relation data other then files are never deleted.they always exists in the
database.

So can you suggest with your experience as how can i implement this
functionality.

Followup:
I would strongly encourage you to rethink the priorities here.

one you remove them from the database the requirement:

"but these files should be stored somewhere so that these can be brought back
into the database."

is virtually UNOBTAINABLE.  Not only that but what have you done for yourself?
why does it cost you more to leave the data be in the database where it is
protected, secured, backed up (and more importantly recoverable), professionally
managed?

it seems to me it would be infinitely more costly to store the data OUTSIDE of
the database.  You'll be chasing down files, you'll be backing up bits and
pieces all over the place, you'll hit file system limits and have to come up
with fancy algorithms to splay the data over many mount points, and you'll have
to come up with a way to remember where the files are (and heaven forbid someone
MOVES them)

My approach would be to charge departments more for the less space they use.  To
encourage the centralization of document storage.  To protect my companies
assets.  In Oracle, we have a single multi-terabyte file store in the database.
No quotas -- no chargebacks -- just a plea from IT to put everything you got in
there so we don't lose it.

Data is money, data is your company.  Disk is cheap compared to losing stuff.

in order to take the blob out and put it on disk you would need to code either a
"client" that runs on the user desktop if you want the files on their machines
(really bad idea) or a java stored procedure/C based external procedure on the
server to write to the servers file system (really bad idea #2)

Rating: 4
September 02, 2003
Reviewer: A reader
Thanks for the prompt reply.

But data into our application comes automatically from test benches or
measurement devices like oscilloscope.

for eg. an experiment performed with an oscilloscope results in huge amounts of
data within no time.

So the idea from the customer was if an offline archiving functionality can be
provided then these can be archived offline from the database and bring them
only when required.

would you still stand by storing these results into the database.

Followup:
is this "archived" data of any value whatsoever to you.

Rating: 3
September 02, 2003
Reviewer: Raghu
Yes, basically these are results of the experiment. so the user might require
this .

Currently we are in the requirements phase and the user has given such a
requirement. so i thought of having your opinion.

if you can suggest some alternative solution.

Followup:
well, you have my opinion -- data of value is data you keep in a database.

anything not of value, you can do with what you like.

So, my solution would be to get enough disk to hold my data..

you really haven't talked volumes here, that'll be something you want to
consider.  if you are talking about petabytes of raw number files -- maybe you
want a really good HFS (hierarchical file system) and spend the time making sure
you can reliably link pointers (bfiles) in Oracle to this specialized storage
system to retrieve this data.  It'll be code, it'll be rules, it'll be harder --
but you might find the cost of a big old HFS with different kinds of storage to
be of use.

If you are talking giga/tera bytes -- i would be looking at a database only
solution.

Rating: 5
Excellent September 02, 2003
Reviewer: Raghu from India
Thanks a lot Tom.I have your opinion in mind.
I will discuss the same with the customer.

Another question.
I already have your expert one-on-one from WROX.
How different are the two new books(Effective Oracle By Design and Expert
One-On-One) compared to the one i already have.

your book is very useful in day today life and i have learnt a lot from it.

Followup:
they are night and day different.

you can read the sample chapters online for the new book -- link on the home
page.  decide for yourself.

Rating: 5
Have u visited India September 05, 2003
Reviewer: A reader
Have u ever visited bangalore.
Is ther any plans in future.

September 03, 2003 Jan -- Thanks for the question regarding "Trace_SQL, Explain Plan and Parsing", version 8.1.7
You Asked
I have fallowing questions.

1. I executed e.g. 10 times the same queries from TOAD (tried also from SQL*)
    SELECT * FROM my_view

   In TKPROF output, there were:
  ...
  ...
  Parse       10
  Execute     10
  ..

  but if I did the same from Oracle Forms (block based on the same query), it
was as I expected:

  Parse       1
  Execute     10

I tried many different queries, all were simple SELECT, avoiding NOT USED BIND
VARIABLES mistakes, if I did it from TOAD or SQL*, Parse Count was everytime the
same as Execute Count. Why?

2. In your book, you are saying that for SELECT query, there is no Execute, but
in my queries, it is. Why?

3. Explain Plan (Row Source Operation) in TKPROF output is for some queries
only. I did several different queries, for some of them there is no Explain
Plan. Why?

4. Can you please explain what is different between Query   and Current  in
Explain Plan. I cannot get it from your book.

Thanks
and we said...
1) that is because toad and sqlplus are simple command line environments.  When
you type in a query they ALWAYS:

a) parse
b) bind (if necessary)
c) execute
d) fetch (over and over)
e) close

They cannot cache cursors (like forms and most every other programming
environment) since they have *NO CLUE* what you might type in next.  They just
have one cursor area and keep using it over and over.

This is normal.
This is to be expected.
There is nothing you can do about it.

2) And where did I say that?

3) You did not exit your session before running tkprof.

Do this:

SQL> alter session set sql_trace=true;
SQL> select count(*) from emp;
SQL> host

... now, run tkprof on your trace file, you'll find no plan ...
$ exit

SQL> exit

... now run tkprof again and you will ...

Basically, you processed the trace file before the trace file was complete.

4) quote from book

<quote>

o QUERY – How many blocks we processed in 'consistent read' mode. This will
include counts of blocks read from the rollback segment in order to 'rollback' a
block.

o CURRENT – How many blocks were read in 'current' mode. Current mode blocks are
retrieved as they exist right now, not in a consistent read fashion. Normally,
blocks retrieve for a query are gotten as they existed when the query began.
Current mode blocks are gotten as they exist right now, not from a previous
point in time.  During a SELECT, we might see current mode retrievals due to
reading the data dictionary to find the extent information for a table to do a
full scan (we need the 'right now' information on that, not the consistent
read). During a modification, we will access the blocks in current mode in order
to write to them.
</quote>

Now, if you read 33-37, you'll get an understanding of what multi-versioning and
read consistency is all about.  Query mode blocks are those blocks read in
consistent read mode.  We may have had to of read the rollback (undo) segments
in order to "reconstruct them".

Sorry, but I guess I just don't know how to say it any other way.  See also
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/c23cnsis.htm#17882
which is the concepts guide.  It explains it as well.
Review & Followup
Rating: 3
RE: September 04, 2002
Reviewer: Jan from SG
Thanks for your answers on questions 1 and 3. For questions 2 and 4, here are
some more details:

2) quote from your book, p. 458:

<Start>
Here we see the three main phases of the query:

o  The PARSE phase - ...

o  The EXECUTE phase - This is the work done by Oracle upon the OPEN or EXECUTE
of the query. For a SELECT, this will be many times 'empty' whereas for an
UPDATE, this will be where all of the work is done.

o  Then, there is the FETCH phase - For a SELECT, thsi will be where most of the
work is done and visible, but a statment like an UPDATE will show no work (you
don`t 'FETCH' from an UPDATE).

<End>

I understood it that t if I make UPDATE I will find in output Execute=1 and
Fetch=0. I tested it and it is as I expected:
---
update my_table set column1 ='test'

Parse        1
Execute      1
Fetch        0
---

According to my understanding I expected for SELECT:
---
Parse        1
Execute      0
Fetch        1
---

but it is:
---
Parse        1
Execute      1
Fetch        1
---

4) and your answer about different between Query and Current - I understand the
words you used for explanation and also the sentences made from these words and
on the end - I understand your idea expressed by your sentences. What I don`t
understand is the behavior of the Oracle. Here are the steps I made:

a) Start up Database
b) Connect from TOAD
c) SELECT * FROM my_table.

I tried it for different tables, I swear there couldn`t be any updates before I
ran these SELECTs, I was just one user. In tkprof output, I found:
---
select * from AHC_T6X_CONFIG

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0        203          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.01          2          1          4          25
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.01          2        204          4          25

Why there is not Query = 0? There should be no blocks in Rollback Segment.

Thanks, Jan

Followup:
By empty -- i meant "it would happen but cpu, elapsed, disk, query, current,
rows will all be empty -- the section will in effect be empty.  Execute count
must be greater then 0 else you can never get to the Fetch phase!"

Query = all blocks read to answer your query INCLUDING BLOCKS READ FROM
ROLLBACK. It includes blocks read from rollback --  it is not exclusively blocks
read from rollback.

Here is a way to see that in action, consider:

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

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from t from_sql;

  COUNT(*)
----------
     23620

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set sql_trace=false;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> update t set object_id = object_id;

23620 rows updated.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2          PRAGMA AUTONOMOUS_TRANSACTION;
  3          n  number;
  4  begin
  5          execute immediate 'alter session set sql_trace=true';
  6          select count(*) into n from t from_plsql;
  7          execute immediate 'alter session set sql_trace=false';
  8  end;
  9  /

PL/SQL procedure successfully completed.

Note that the autonomous transaction makes it so that the plsql block is a
separate transaction in this session and hence cannot see the un-commited work
we've done.  The tkprof will now show us:

select count(*)
from
 t from_sql

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.08       0.26        324        328         12           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.08       0.26        324        328         12           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 565

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
  23620   TABLE ACCESS FULL T

********************************************************************************

SELECT COUNT(*)
FROM
 T FROM_PLSQL

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.47       0.47          0      24272         12           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.47       0.47          0      24272         12           1

See the difference in query mode gets?  The first one didn't have to read the
RBS at all.  The second one had to read it for each and every row in the
table!

Rating: 4
Tkprof in Oracle 9i September 03, 2003
Reviewer: Mohamed Abd El Mawla from Egypt Cairo
Hello Tom
There is a problem in the tkprof tool in my company..
The utility already run in another system running Oracle 9i Database under
windows 2000 Professional..
But in my company does not sun..Oracle 9i engine under windows 2000 server...
Please send me the full steps to run the tkprof with the files obtained from the
session trace..
, the output files from the tkprof ..

Thank' for your considerations..

Mohamed Abd El Mawla
Oracle 9i Certified Professional Database Administrator.

Followup:
umm, it is no different on windows then anywhere else

c:\> tkprof tracename reportname

Rating: 5
???? September 03, 2003
Reviewer: Dave
"Oracle 9i Certified Professional Database Administrator."

really?

Rating: 5
to Mohamed September 03, 2003
Reviewer: not Oracle Certified
.. you should have not mentioned you are Oracle Certified DBA, it will not
increase your credit.

Rating: 5
RE: Certified September 03, 2003
Reviewer: Mark A. Williams from Indianapolis, IN USA
I about spit my coffee on my screen on that one.

That was a good chuckle to start my day.

But seriously, it is stuff like that that gives OCP a bad name.  Oracle
Education is trying to remedy the situation via the Certified Masters program
wherein you have to demonstrate in a practicum that you actually know what you
are talking about/doing.

It is just a shame that Oracle never did document stuff like tkprof.  Oh, wait,
they did!

- Mark
Mousepad certified something or other

September 03, 2003 Umanga -- Thanks for the question regarding "Syn database and in-memory process", version 9.2.0
You Asked
Hi Tom

We have an Oracle database updated by some on-line and Batch Transactions. There
is an in-memory process written in C++ which
uses a subset of the tables from the above mentioned database. The purpose of
the in-memory process is to handle inserts and updates of a high volume
transaction processing system where speed is very important.

Our problem is how do we keep the data of tables which are common
to both systems  in the database and in the in-memory process in sync .

thanks in advance
Umanga
and we said...
well, it has been my experience that Oracle is pretty good at high volume OLTP.
It was one of our design goals actually.

I would erase the C++ code and use the database.

plain and simple.

I'm 100% dead serious here.  do they have transactional capabilities there?
redo?  undo?  if I walk along and kick something -- cause a hardware failure,
pull the plug -- do they have some 25 years of design and development to ensure
that I can recover my work?

the way to keep them in sync is to have ONE of them -- in the database.
Review & Followup
Rating: 2
Sync database and in-memory process September 03, 2003
Reviewer: Umanga Dumbukola from Sri Lanka
Hi,

There are mainly two types of transactions, Order Processing and Execution of
Trades.
Order processing involves about 6 database reads for validations , one update
and an insert.

For an Execution of a Trade , there are about 6 database reads for validations ,
about 3 inserts and 2
updates.

Our target is to achieve minimum 500 orders per sec and 100 trades per sec, all
taking place only in the
database, no C++ in-memory process ,

Can you please give us an idea how we could achieve this  .

Followup:
benchmark and then buy a system that meets the requirements.

sounds very "tpc-c'ish", they 100's of thousands of transactions per minutes.

September 03, 2003 Jason -- Thanks for the question regarding "How Oracle handles daylight saving time change", version 8.1.6
You Asked
Hi Tome,

I would like to know how Oracle handles the daylight saving time change (both go
forward and backward) in Oracle8 and 8i. Is there any potential problems caused
by the time change? What is the impact of the time change on Oracle database
backup and recovery?
Thanks,

Jason
and we said...
There are no issues in the database, only in applications that use the database.

All things inside the database are governed by our SCN, a number, a ticker -- it
is not based on the wall clock.

Applications can be affected as all of a sudden there are two 1AM's on a given
day.

Recovery per-say is not affected at all.  However, if you do a point in time
recovery -- and try to recover to 1:30AM on the day that had two 1:30am's, the
recovery application will recover to the first 1:30am -- if you needed to
recover to the second 1:30am, you'll need to mine the logs to find the SCN that
was in place at the second 1:30am and recover to the SCN, not the time.
Review & Followup
Rating: 3
What about jobs? October 23, 2002
Reviewer: Bob from NJ USA
For example, if you need to run a job every 5 minutes and the next time is 1:03
AM, but the time change moves the time back an hour, how would you go about
getting the job to run at the new time of 12:03?

Followup:
... only in applications that use the database ....

So, lets look at this, is it time to "fall back".  At 2am -- it will become 1am
all of a sudden.

In 9i with timezone support -- you can easily deal with this.  In 8i -- it is
somewhat more complex, you'll need to "code" timezone support -- that is, your
job (which can compute the next time as an out parameter instead of using the
INTERVAL parameter to submit) would have to be aware of it.

Rating: 5
Handling Day light savings in 8i October 23, 2002
Reviewer: Kulguru
Tom

Can you please illustrate as to how we can handling day light savings in 8i ,
with an example.

Thaks

Followup:
You'll have to code the logic for it -- i don't have it.

something like a lookup table of when the time shift happens for you --
something your job would inspect to come up with the "right" time for you.

Rating: 4
October 23, 2002
Reviewer: A reader
Using examples:

-- Add 1 hr. and 59 min.
SQL> select to_char(to_date('27/10/2002','DD/MM/YYYY')
+ 119/(24*60), 'DAY HH:MI:SS AM') from dual;

you would get

TO_CHAR(TO_DATE('27/1
---------------------
SUNDAY    01:59:00 AM

and

-- ADD 2 hr. and 1 min.
SQL> select to_char(to_date('27/10/2002','DD/MM/YYYY')
+ 121/(24*60), 'DAY HH:MI:SS AM') from dual;

TO_CHAR(TO_DATE('27/1
---------------------
SUNDAY    02:01:00 AM

We are using 8.1.7.3 so the second query returns

Sunday 2:01 AM.

But what your saying that in 9i it would return

Sunday 1:01 AM.

is that correct?  This would be very nice.

Thanks

Rating: 4
dst aware September 03, 2003
Reviewer: Phil from UK
Hi Tom,

I am still a little confused, sorry, about how Oracle 9i is day light savings
aware.

I have tested and seen the Oracle was aware that dst changed in Sydney/Aus to
cater for the Olympics. However what if the powers that be decide that they
should change in Melbourne for the Commonwealth Games (British thing) next year.
?

Is there something that needs to be done to make it aware of changes on a
regular basis, or does it only get updated with released versions of Oracle.

I know there are companies out there that supply this information, with the
expansion of timezones in 9i i may be lucky ??

thanks

Followup:
you'd have to contact support for that one.

September 03, 2003 Susan -- Thanks for the question regarding "error handling with implicit cursors", version 8.1.7
You Asked
Tom,

I want to handle an implicit cursor that returns no data.  I assume that a
NO_DATA_FOUND exception would be the approach, however, when I provide a schema
that doesn't exist no error message is thrown.  What am I missing?  Thanks.

CREATE OR REPLACE procedure add_security(P_SCHEMA IN VARCHAR2, P_TABLE IN
VARCHAR2 := NULL)
AS

ERROR_TXT          VARCHAR2(200);

begin

if p_table is null
then
for schema_rec in (select table_name from all_tables where owner = p_schema)

loop

dbms_rls.add_policy
(object_schema => p_schema,
 object_name => schema_rec.table_name,
 policy_name => 'T_POLICY',
 function_schema => 'ADEPT',
 policy_function => 'sec_access.sec_policy',
 statement_types => 'insert, update, delete',
 update_check => TRUE);

end loop;

else

            dbms_rls.add_policy
               (object_schema => p_schema,
               object_name => p_table,
               policy_name => 'T_POLICY',
               function_schema => 'ADEPT',
               policy_function => 'sec_access.sec_policy',
               statement_types => 'insert, update, delete',
               update_check => TRUE);
end if;

EXCEPTION
WHEN NO_DATA_FOUND THEN
SQL_ERRORS_FILE(ERROR_TXT,'CHECK SCHEMA NAME');

end;
/
and we said...
implicit cursors like that NEVER fling a no data found.

select * from dual where 1=0;

is NOT an error.

You could code something like:

as
   ..
   l_found boolean default false;
begin
   if ( p_table is null )
   then
      for x in ( select .... )
      loop
            l_found := TRUE;
            dbms_rls...
      end loop;
      if ( NOT l_found ) then sql_errors_file() end if;
....
Review & Followup
Rating: 3
just a question September 03, 2003
Reviewer: Mouad from Yemen
Can I move data from any other programming language to oracle?
if it is, yes? how can I do that?

thanks alot

Followup:
yes, most every language of commercial value has an api to oracle -- to rdbms's
in general in fact.

"how" varies by language

java -> jdbc
c -> pro*c or oci or odbc
vb -> odbc
perl -> dbi perl, dbd perl
and so on

September 03, 2003 KK -- Thanks for the question regarding "Problem when executing UTL_SMTP", version 8.1.6
You Asked
I've written a program which uses the utl_smtp to send
a email to myself.  However, when I run the procedure,
the following error occurs:

ORA-29540: class oracle/plsql/net/TCPConnection does not exist
and we said...
Some of the installer scripts forget to load the needed jar file
for TCP/IP support in PLSQL.

Simply:

cd plsql/jlib
loadjava -user sys/password plsql.jar
Review & Followup
Rating: 4
April 17, 2001
Reviewer: Deepak Tanna from India
Thanx it solved our developers problem

Rating: 2
The suggested solution gives error January 15, 2002
Reviewer: anoop from India
Doing what is written:
cd plsql/jlib
loadjava -user sys/password plsql.jar

This command is giving me
ORA-00406 error- COMPATIBLE parameter needs to be 8.1.0.0.0. or greater.

I am having 8.1.6.0.0 on my machine

Please advice

Followup:
so, reset your compatible parameter to 8.1.6 and try again.

It is in your init.ora file.  shutdown, change it, startup.

Rating: 2
Another issue please help January 15, 2002
Reviewer: NEETI from TX
I tried to load java and got the following errors

Error while retrieving errors for oracle/plsql/net/TCPConnection
    ORA-00904: invalid column name

Error while retrieving errors for oracle/plsql/net/InternetAddress
    ORA-00904: invalid column name

Error while creating resource META-INF/MANIFEST.MF
    ORA-29547: Java system class not available: oracle/aurora/rdbms/Compiler

loadjava: 3 errors

thanks,

Followup:
search for

ORA-29547

on this site...

Rating: 5
One More! January 16, 2002
Reviewer: Ashok from USA,JPMORGAN
I am Getting this error

any thing is wrong?

[GIB_UAT7-8.1.7] {oracle} : loadjava -f -v -r -u sys/eric plsql.jar
Unable to initialize threads: cannot find class java/lang/Thread
Could not create Java VM

Thx
Ashok

Rating: 5
Error while load java January 17, 2002
Reviewer: Ashok from USA
loadjava
Unable to initialize threads: cannot find class java/lang/Thread
Could not create Java VM

Pls Advice

Followup:
sounds like your CLASSPATH is not properly set.

Rating: 5
Error while LoadJava Command January 17, 2002
Reviewer: Ashok from USA
Thx for Quick Response

I checked the class path

This is what i did

set env classpath /etc/..../lib/
(Setting the classpath for classes111.zip file

I tried ECHO $classpath  i was able to see the classpath

But still i am getting this error

Pls Advice

Thx
Ashok

Followup:
does $ORACLE_HOME/JRE/lib/rt.jar exist and what is the contents of your

$ORACLE_HOME/bin/loadjava

script.

Rating: 5
Error Loading Java January 17, 2002
Reviewer: Ashok from USA
Thx for ur Quick Response

Yes rt.jar  exist.

Checked the loadjava script and we found that path for .jar files &
classes111.zip files are wrong,so  changed to correct path

But now we r Getting a different Error Msg:

Version not compatibale with the Java Version in system classes check ur
Classpath

Pls Advice

Ashok

Followup:
sounds like you have a very messed up install -- have never hit this (never had
to edit the loadjava script myself).

Don't know what to tell you other then contact support and try to figure out
what went wrong in an install or upgrade.

Rating: 2
loadjava -user sys/xxx plsql.jar giving some errors January 18, 2002
Reviewer: anoop from India
Hi Tom,

Running the load java i got the following errors.....
the log file is below:

Please advice

ORA-04030: out of process memory when trying to allocate 554648 bytes (joxcx
callheap,ioc_allocate ufree)
ORA-04030: out of process memory when trying to allocate 65036 bytes
(callheap,KQL tmpbuf)
COUNT(*)
----------
      6600
1 row selected.
Statement processed.
JVMRMACTION
------------
FULL_REMOVAL
Statement processed.
DBMS_JAVA.LONGNAME(NAME)
--------------------------------------------------------------------------------
ORA-04031: unable to allocate 4032 bytes of shared memory ("shared
pool","oracle/aurora/rdbms/DbmsJavaSYS","joxlod: in ehe","ioc_allocate_pal")
DBMS_JAVA.LONGNAME(O.NAME)
TEXT
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
ORA-04031: unable to allocate 4032 bytes of shared memory ("shared
pool","oracle/aurora/rdbms/DbmsJavaSYS","joxlod: in ehe","ioc_allocate_pal")
Statement processed.
Statement processed.
drop public synonym get_error$
                    *
ORA-01432: public synonym to be dropped does not exist
Statement processed.
Statement processed.
Statement processed.
JVMRMACTION
------------
FULL_REMOVAL
Statement processed.
Statement processed.
Statement processed.
Statement processed.
ORA-04031: unable to allocate 4032 bytes of shared memory ("shared
pool","java/lang/ObjectSYS","joxlod: in ehe","ioc_allocate_pal")
Statement processed.
ORA-04031: unable to allocate 4032 bytes of shared memory ("shared
pool","java/lang/ObjectSYS","joxlod: in ehe","ioc_allocate_pal")
ORA-04031: unable to allocate 4032 bytes of shared memory ("shared
pool","java/lang/ObjectSYS","joxlod: in ehe","ioc_allocate_pal")
ORA-04031: unable to allocate 4032 bytes of shared memory ("shared
pool","java/lang/ObjectSYS","joxlod: in ehe","ioc_allocate_pal")
------------
FULL_REMOVAL
Statement processed.
Statement processed.
Statement processed.
Statement processed.
Statement processed.
Statement processed.
Statement processed.
Statement processed.
drop table aurora$startup$classes$
           *
ORA-00942: table or view does not exist
drop table aurora$shutdown$classes$
           *
ORA-00942: table or view does not exist
Statement processed.
Statement processed.
Statement processed.
Statement processed.
Statement processed.
Statement processed.
Statement processed.
Statement processed.
JVMRMACTION
------------
FULL_REMOVAL
Statement processed.
Statement processed.
Statement processed.
Statement processed.
Statement processed.
Statement processed.
JVMRMACTION
------------
FULL_REMOVAL
Statement processed.
Statement processed.
Statement processed.
JVMRMACTION
------------
FULL_REMOVAL
Statement processed.
Statement processed.
JVMRMACTION
------------
FULL_REMOVAL
Statement processed.
JVMRMACTION
------------
FULL_REMOVAL
Statement processed.
ORA-04021: timeout occurred while waiting to lock object SYS.INITJVMAUX
ORA-06512: at line 4

Followup:
make sure your shared pool and java pool sizes are set large enough in the
init.ora.  they are not as it is.

Rating: 5
Well worked March 18, 2002
Reviewer: Krishna from Chennai, India
Hi Tom,

  I was facing the problem in one of our development servers, for a long time.
By loading the plsql.jar file, my mail procedure started working fine.

Thanks,
krishna

Rating: 4
where is the directory plsql/jlib March 29, 2002
Reviewer: nagarju
tom,
i want to use utl_smtp, but i am also getting the same error. only our dba can
run that loadjava command given below.

where exactely is this directory present?

>>Simply:

>>cd plsql/jlib
>>loadjava -user sys/password plsql.jar

and, how do we ensure java server stuff is installed or not?

my oracel version is 8.1.6

Followup:
it is under your oracle_home.

look at your banner returned by sqlplus and see if it mentions Java.

Rating: 5
where is the directory plsql/jlib March 29, 2002
Reviewer: nagarju
this is what appears when i log in

Oracle8i Enterprise Edition Release 8.1.6.3.0 - Production
With the Partitioning option
JServer Release 8.1.6.3.0 - Production

now is that enough if i run that
"loadjava" command or anything else i have to do?

Followup:
that should do it.

Rating: 4
March 30, 2002
Reviewer: Nirmal
Hope that Mr. Tom has much more patience to respond all type of questions. The
questioner can try out the problem with all posibilites themselves before
raising questions. So that Tom becomes cool ;-).

Rating: 3
Error April 05, 2002
Reviewer: Yogesh from Pune, India
when I was executing

loadjava -user sys/pass plsql.jar  I faced following error

Error while computing shortname of oracle/plsql/net/TCPConnection
    ORA-06550: line 1, column 13:
PLS-00201: identifier 'DBMS_JAVA.SHORTNAME' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

Error while computing shortname of oracle/plsql/net/InternetAddress
    ORA-06550: line 1, column 13:
PLS-00201: identifier 'DBMS_JAVA.SHORTNAME' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

Error while creating resource META-INF/MANIFEST.MF
    ORA-29547: Java system class not available: oracle/aurora/rdbms/Compiler

loadjava: 3 errors

What could be the reason

Followup:
Sounds like java isn't installed properly in your database.  DBMS_JAVA is a
supplied package that would be there.

Either have your DBA look into it (they can rmjvm and initjvm -- scripts -- to
reinstall java properly) or contact support for assistance.

Rating: 3
Sorry / Thanks April 05, 2002
Reviewer: Yogesh from Pune, India
Pervious error which I mentioned was due to dbms_java. This Package was not
installed in my DB.  After installing when I used following code for sending
mail

create or replace
  PROCEDURE send_mail (p_sender       IN VARCHAR2,
                       p_recipient IN VARCHAR2,
                       p_message   IN VARCHAR2)
  as
     l_mailhost VARCHAR2(255) := 'aa.bb.com';
     l_mail_conn utl_smtp.connection;
  BEGIN
     l_mail_conn := utl_smtp.open_connection(l_mailhost, 25);
     utl_smtp.helo(l_mail_conn, l_mailhost);
     utl_smtp.mail(l_mail_conn, p_sender);
     utl_smtp.rcpt(l_mail_conn, p_recipient);
     utl_smtp.open_data(l_mail_conn );
     utl_smtp.write_data(l_mail_conn, p_message);
     utl_smtp.close_data(l_mail_conn );
     utl_smtp.quit(l_mail_conn);
  end;
  /

begin
send_mail( 'yogeshb@bb.com',  'matty@bb.com', 'Test mail from DB' );
end;
/

Mail was sent, but mail was not having any info like, from /to as well text.

From : Sender Unspecified
To :none
subject : (No subject)

Why this is so ?

Followup:
you didn't write the

To: .......

record.  The to list is just "data" in the email body.

Search for utl_smtp and see some of the "larger" examples I have.

Rating: 4
Got it April 05, 2002
Reviewer: Yogesh from Pune, India
Thanks a lot Tom, got solution. Sorry for trouble.

Rating: 5
another error April 19, 2002
Reviewer: rajiv
tom,
after running "loadjava -user sys/password plsql.jar"
I get:
/export/vol/oracle.8.1.6/product/dbms/8.1.6/bin/loadjava[16]:
/app/oracle/product/dbms/8.1.6/JRE/bin/jre:  not found

please help

Followup:
Then you have no java installed or your install is messed up.

cd $ORACLE_HOME/JRE/bin

cd if it is there.

Rating: 3
Helpful - almost August 28, 2003
Reviewer: A reader
Thanks a million, Tom, for all I have gleaned from this site.

I need some assistance with what you wrote above:
"loadjava -user sys/password plsql.jar"

I tried that and I get the error:
"SQL error while connecting with oci8 driver to default database: ORA 12560:
TNS: Protocol adapter error
could not open error
loadjava: 2 errors"

I am a developer and not a DBA so I have limited access to the database.  What
am I missing here?

Thanks,

Leora

Followup:
if you are not a DBA what are you doing with the SYS password?????? that pretty
much gives you carte blanche to the database.

so, do you have a local instance, or is it on the network (eg: were you on the
server itself or on your pc)

Rating: 3
ORA-29541 : executing UTL_SMTP September 02, 2003
Reviewer: Rose from Brazil
Hi Tom,
I'm using Oracle 8.1.7 (Windows NT 4.0). I did:
cd plsql/jlib
loadjava -user sys/password plsql.jar

withou problem.
The script send_mail was compiled without errors

create or replace procedure send_mail  ( p_sender in varchar2, p_recipient in
varchar2, p_message in varchar2 ) as
    l_mailhost varchar2(255) := '40.10.1.18' ;
    l_mail_conn utl_smtp.connection ;
begin
    l_mail_conn := utl_smtp.open_connection(l_mailhost, 25) ;
    utl_smtp.helo(l_mail_conn, l_mailhost) ;
    utl_smtp.mail(l_mail_conn, p_sender) ;
    utl_smtp.rcpt(l_mail_conn, p_recipient ) ;
    utl_smtp.open_data(l_mail_conn) ;
    utl_smtp.write_data(l_mail_conn, p_message ) ;
    utl_smtp.close_data(l_mail_conn) ;
    utl_smtp.quit(l_mail_conn );
    dbms_output.put_line('Message send to the user successfully') ;
end ;

However, when I execute send_mail, it returns:
ORA-29541: class SYS.oracle/plsql/net/TCPConnection could not be resolved
ORA-06512: at "SYS.UTL_TCP", line 537
ORA-06512: at "SYS.UTL_TCP", line 199
ORA-06512: at "SYS.UTL_SMTP", line 99
ORA-06512: at "SYS.UTL_SMTP", line 121
ORA-06512: at "SYS.SEND_MAIL", line 6
ORA-06512: at line 2

Please help!!

Followup:
hmm, i haven't seen that one.

was this database upgraded over time?  was the upgrade done "by hand" or "by
tool"

the only thing I see related is a bad "upgrade".  if this is the case, you may
need to remove and then reinstall the jvm.  I would suggest filing a tar with
support on this (and if you do the remove/reinstall -- please backup or at least
test it on a test system first)

Rating: 5
RE: Could not be resolved September 02, 2003
Reviewer: Mark A. Williams from Indianapolis, IN USA
I found a TAR on MetaLink that suggests loading as:

loadjava -resolve -force -user sys/password $ORACLE_HOME/plsql/jlib/plsql.jar

HTH,

Mark

Followup:
going to have to sublet you some space here :)

(thanks!)

Rating: 4
executing utl_smtp September 03, 2003
Reviewer: rose from br
Tom,
thanks for your quick response!!
My server machine has various Oracle version(8.1.5, 8.1.7, 9i-rel2).
V.8.1.7 isn't a upgrade (it is a new installation), and on this version I am
working:
I executed loadjava -user sys/pwd plsql.jar and I've got error Ora-00406 .Then I
modified init.ora, compatible= 8.1.7 (it was 8.0.5).

I tried loadjava -resolve -force -user sys/password
$ORACLE_HOME/plsql/jlib/plsql.jar  (thanks Mark) but the problem persists
(executing send_mail(), I've got the error ORA-29541: class
SYS.oracle/plsql/net/TCPConnection could not be resolved)

Obs: I execute send_mail() in Oracle 9i and got success

Thanks !!

Followup:
sounds like java isn't installed correctly in the database.  you can try doing
that over

see rmjvm and initjvm.sql in $OH/javavm/install

backup or test on a system you care not so much about -- please don't do this on
a "live" box.

Rating: 5
RE: Could not be resolved September 03, 2003
Reviewer: Mark A. Williams from Indianapolis, IN USA
> compatible= 8.1.7 (it was 8.0.5)

From the readme.txt file in the javavm directory:

1.5  Compatibility Mode

To use Java in the Oracle8i server, you must set

    compatibility = 8.1.0

or higher.

I have not used this package, but if compatible must be 8.1.0 or higher and
yours was set to 8.0.5 when the javavm was installed, I would suggest reloading
the javavm now that compatible is set correctly.

HTH,

Mark

September 04, 2003 sunil -- Thanks for the question regarding "How to bypass the Listener & connect directly to a Dispatcher", version 9.0.1
You Asked
Hi Tom,

  Please let me know how to bypass the Listener & connect directly to a
Dispatcher.
 I could not get much info from the documentation.

 Also let me know the advantages & disadvantages of using eigther a DPS or a MTS
mode of connection. Rather a comparison between the two would be great. I would
be more interested in terms of performance, reliability, deployment factors etc.

Thanks a Lot
Sunil
and we said...
You cannot get information from the documentation about how to do the
impossible.

You must go via the listener.

Tell me -- what tcp/ip ports would the dispatcher be using?  Answer: it is
unknown until the dispatcher starts what port they will be using.  The
dispatcher starts, connects to the listener (who runs on a fixed, known tpc/ip
port) and tells it what port it is using.  The client connects to the listener,
the listener tells the client "hey, connect to this IP/port to get to a
dispatcher".  Tomorrow, the dispatcher will be running on a totally different
port on that same instance, you don't know the port numbers -- only the listener
does.

I don't know what you mean by "DPS" mode of connection.  It is not a term we
(oracle) uses as far as I know.

If you would like the in's and outs of DEDICATED server mode vs SHARED server
(MTS) mode -- I have a pretty good write up of that in my book when I cover the
Oracle architecture.
Review & Followup
Rating: 4
Reader February 25, 2002
Reviewer: A reader
A program (in "C") that executes in a elapsed time
of 0.5 min. if runs without hanging.

If this program runs in MTS environment intermittently
(1 in 4 times) hangs 1 miniute in the middle and then
completes successfully in a elapsed time of 1.5 minutes

If the same program runs in (server=dedicate in TNS), it
executes (100 out of 100) successfully non-hanging

What will be in your opinion I could look for
to pin down the cause

Thanks

Followup:
as the computer on star trek always used to say to capt. kirk "insufficient
data".

One would need to know how many shared servers you have and so on.  Sounds like
contention.

A batch process (one that connects and does nothing but sql) should not be using
a shared server connection.  shared servers are designed for "persistent
connections that are not used 100% of the time".  Your program sounds like it is
designed for dedicated server.

Rating: 4
Reader February 25, 2002
Reviewer: A reader
The shared servers spawnd 6 max 25
Dispatchers spawned 4 max 14

I was running the program when the system is idle, no
other sessions to contend. I am thinking if the pattern of
SQL*Net message to from client could cause performance
issue when connecting as MTS vs connecting as dedicated
server. client is at a differnet unix server connect to
unix database server.

Thanks

Followup:
No, there will be no difference.

Once connected, the conversation between the dispatcher and the client and the
dedicated server and the client is pretty much identicial.

Rating: 3
September 03, 2003
Reviewer: A reader
Hi Tom,
You said
<start quote> A batch process (one that connects and does nothing but sql)
should not be using a shared server connection.<end quote>

How can I do in order to force a job to use the entry that I have set in the
tnsnames.ora file for dedicated connections. I have check the parameters for the
submit option for the dbms_job package and there is no way to identify which
entry the job has to use.

Thanks for any help on this.

Followup:
dbms_job always uses "a dedicated sever" -- it uses snpN processes, they are
"dedicated" connections.

September 04, 2003 Shivaswamy -- Thanks for the question regarding "Limit CPU Usage ", version 817
You Asked
Tom,

DSS Environment. Users log in with their own user account to the application.
When they submit a particular job in multiple sessions, the CPU Usage shots up
on 8 CPU Server(NT). Infact, it goes like this. One user submits say 7
concurrent sessions (Through Custom Software )and the job will continue in
multiple legs, _ I mean, once this set is over it will call next 7 sessions and
so on till the job is complete. This number, - 7 sessions - is determined, based
on the CPU availability. What I want to do is say give 85% for this user/job and
AT ANY TIME this user/job should not use more than 85%(even if it is available,
becuase the job may hit all of 100% during some part of the leg)  and others
will manage within remaining 15%.

I checked Profile & DBMS_RESOURCE_MANAGER. I found them not suitable for this
task. The latter will be of use ONLY when there are other jobs demanding CPU. If
not, it will allow this user to hog 100%, which I want to avoid.(Beacuse to use
continuosly at 100% is not good, right?) What I want is say 85% and NO MORE for
this user.

Is there a way to do this?

Thanks in advance,
Shivaswamy
and we said...
Why why why -- do you erroneously believe that 100% CPU utilization is bad????

That is exactly what we should all be striving for!!!

It is not like we can put "cpu in the bank" and use it later.  The 15% you want
to with-hold from this user cannot be used by anyone else later!!!

In fact, suppose you could do it -- suppose you could limit them to 85%
utilization.... Ok, say they run a thing that takes an hour if they had 100% of
the CPU.  It takes OVER an hour with 85%.  Say they start this at 8am (they are
being nice)....  Now, you limit them to 85%.  9am comes and everyone else comes
to work -- but wait -- this job is STILL running (it could have been done but
you won't let it).  So, this user makes everyone else run slower cause you kept
15% of the CPU in the bank but the bank cannot "carry forward".

Let them use 100% of the machine if no one is there (you WANT THAT - you really
really really do).

Use dbms_resource_manager to limit them to 85% when there are others on the box.
 but when they are the only ones there -- let them use it all (your approach is
100% fundementally flawed)

We have failed if our machines continously run at X% utilized and X is less then
100.  100% utilization is our GOAL, not our fear.  Yikes.
Review & Followup
Rating: 4
What about "Processor Que Length..?" September 17, 2002
Reviewer: Shivaswamy from Dallas, TX
Tom,

Thanks for your your encouraging words. I have two follow ups:

1. They can submit 10(or 20 for that matter) instead of 7 concurrent sessions.(I
know, we hit 85 to 90% for 7 sessions) Then it is 100% always. Is that
acceptable?( I have no qualms because this is the most important job that has
highest priority as of now.)

2. With 100% and more, "Processor Que Length" (on NT) increases up to 14, at
times. I have read that, that is not good. Is not that bad?

Followup:
1) it is not only totally acceptable, it is EXACTLY what you want to happen.

2) Now what you want to do is limit the number of concurrent sessions they use.
To avoid them swamping the system.  You can use the resource manager in 9i to
limit their number of concurrent sessions so that even if they submit 20 -- only
12 or so run (which is what you are trying to achieve).

they should be taught that 20 is too many however.

Rating: 4
Thanks September 18, 2002
Reviewer: Shivaswamy from dallas, TX
Thanks, a lot, Tom.

Rating: 5
CPU utilization per instance September 03, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom
The client is running multiple Oracle instances on the same Sun box. They want
to know if there is a way to find out at OS level the CPU utilization per
instance.

Is there a shell script that you have handy to do this?
Thanks

Followup:
no, it would be really really hard -- if even possible.

consider that an instance constantly has processes coming and going and coming
and going.

All you can do is use v$sysstat - record the "cpu used" statistics.  wait a bit.
 record them again and subtract.  that'll be approximiately the cpu used by each
instance during the period of observation (key word = APPROXIMATELY.  If a
statement starts BEFORE you recorded and finished AFTER you recorded - you won't
see its cpu time.  If a statement started 5 hours BEFORE you recorded, finished
and then you took the second recording -- all 5 hours of cpu time would be
dumped into that observation)

September 04, 2003 ling -- Thanks for the question regarding "optimizing some plsql", version
You Asked
if I need to retrive and delete over
a million records from a table, how do
I optimize my PL/SQL code to make it
faster(except the using of ROWID AND
PLS_INTEGER)?
and we said...
This is a tricky question to answer.  Doing something 1million
times in plsql is going to be somewhat slow.

I would consider myself trying to partition the problem (eg: I
have 1mill records with a ID column.  the min value is 1 and the
max value is 5,000,000 for example.  It is fairly well
distributed.  I have 8 CPU's on my machine so I'll run about 16
parallel jobs.  I'll divide 5mill/16 to get non-overlapping ID
ranges and run my procedure 16 times in parallel (in different
sessions) each with a different range of IDs to process).

Watch out if you decide to try and "save space" by committing
frequently if you hold a cursor open across fetches!!!  You'll
hit the infamous ORA-1555 snapshot too old for sure this way.
If you have a construct like:

  for x in ( select * from t where ... )
  loop
      ...... do something
      delete from T where ....
      if ( mod( counter, 1000 ) == 0 ) then
         commit;  -- commit every 1,000 rows processed
      end if;
  end loop;

you are asking for trouble.  You'll get an error eventually and
have to restart.  It would be preferable to do something like:

loop
   processed_rows = FALSE;
   for x in ( select * from t where .... AND ROWNUM <= 1000 )
   loop
      processed_rows = TRUE;
      ..... do something
      delete from T where ....
   end loop;
   commit;
   exit when NOT processed_rows;
end loop;

that does not keep any cursors open across commits and will not
hit an ORA-1555 because of that.

Also, try to do as much in SQL as possible -- if you can take a
couple lines of procedural plsql and turn that into a nice
DECODE or something -- it'll be faster.

Also, if you are using Oracle8i, release 8.1, use the BULK
COLLECT (array fetching) and "FORALL" array
inserts/updates/deletes from plsql to cut down massively on the
context switches between plsql and sql.  this can make your
PLSQL run many times faster. Consider:

ops$tkyte@8i> create table t as select user_id from all_users;
Table created.
Elapsed: 00:00:03.47

ops$tkyte@8i> create unique index t_idx on t(user_id);
Index created.
Elapsed: 00:00:01.13

ops$tkyte@8i> begin
  2      for x in ( select * from t ) loop
  3                  delete from T where user_id = x.user_id;
  4      end loop;
  5      commit;
  6  end;
  7  /
PL/SQL procedure successfully completed.
Elapsed: 00:00:09.31

ops$tkyte@8i> select * from t;
no rows selected
Elapsed: 00:00:00.00

ops$tkyte@8i> @drop t;
ops$tkyte@8i> create table t as select user_id from all_users;
Table created.
Elapsed: 00:00:02.98

ops$tkyte@8i> create unique index t_idx on t(user_id);
Index created.
Elapsed: 00:00:01.13

ops$tkyte@8i> declare
  2      type numTab is table of number(4) index by
binary_integer;
  3      data numTab;
  4          empty numtab;
  5  begin
  6          loop
  7                  data := empty;
  8                  select user_id BULK COLLECT into data from
t where rownum <= 1000;
  9
 10                  exit when data.count = 0;
 11
 12                  forall i in 1 .. data.count
 13                          delete from T where user_id =
data(i);
 14          end loop;
 15  end;
 16  /
PL/SQL procedure successfully completed.
Elapsed: 00:00:04.43

ops$tkyte@8i> select * from t;
no rows selected
Elapsed: 00:00:00.01

The two blocks do the same exact thing -- one does it row at a
time, the other 1,000 rows at a time (there are about 30k rows
in my all_users table).  The array processing is at least 2x
faster and can be much faster depending on what you are doing.
Review & Followup
Rating: 5
number of parallel jobs and number of cpus August 30, 2003
Reviewer: Winston
Would you please elaborate "I have 8 CPU's on my machine so I'll run about 16
parallel jobs. " ?

Thanks!

Followup:
because in general running 8 things on 8 cpu's when the 8 things do not require
100% of the cpu would leave idle cpu.

since you cannot put idle cpu in the bank and use it later in the day -- i
wanted to use it all.

so, 2x is a good start place for many things.

Rating: 5
How PL/SQL divides the task into 16 threads September 03, 2003
Reviewer: Mohan K from Bangalore
I have two questions

1. where in the PL/SQL script can we specify about the number of CPU's and
number of processes.

2. I am running a CPU intensive PL/SQL program. This program reads CLOB field
from the table processes it and write output to another table. I want to fetch
1600 rows of data at a time using BULK collect, process in parallel in 8 CPU's
and insert the results using BULK insert. How can I achieve this?.

Mohan

Followup:
plsql won't divide a task into 16 threads.

YOU would decide to run 16 copies of your plsql program simultaneously --
against different sections of the data....

see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:10498431232211
for a query you can run to get 16 rowid pairs -- that completely cover your
table.

then you can write your procedure to:

   select * from t where rowid between P_LO_ROWID and P_HI_ROWID;

and start 16 copies of your plsql program, passing in one of the pairs.  the
entire table will be processed in parallel.

interesting number 1600 -- how did you arrive at that?  I might go for 100-500
at a time (memory)

Rating: 4
1000 rows September 03, 2003
Reviewer: lenny from Madison, WI
Tom, I like the bulk collect option, but I don't quite see how you are
specifying to process 1000 rows.  Please elaborate.

Followup:
ctl-f and type in 1000

Rating: 4
Parallel threads in PL/SQL September 03, 2003
Reviewer: Mohan from Bangalore
Hi Tom,

The link was very useful. My idea was to fetch 1600 rows at a time using bulk
collect, then distribute 200 rows each to 8 CPU's. If 8 seperate PL/SQL programs
are run in parallel using dbms_job then it is better to fetch 200 rows by the
PL/SQL script.

Mohan

Followup:
thats the way to do it, yes -- 8 copies of the program, fetching the N-hundred
rows each.

September 04, 2003 Pawan -- Thanks for the question regarding "Mass Delete", version 8.1.7.0.0
You Asked
Tom,
   Two Very simple questions for you.
A.  In a datawarehouse environment there are instances when after loading say
more than 500 million records one discovers that one of the loads that loaded
200 million records was not correct and needs to delete these 200 million
records based on a WHERE condition. Is the following SQL the best way to do it
or there is a better way
DELETE FROM TABLE where function(column)='A CONSTANT'
where funtion is say - upper.
   This table is not partitioned.

B. A user (in a datawarehouse) test instance DROPS/TRUNCATES a table having 600
million records. The table is not partitioned. The backup is available but in
Oracle 8i there is a LOG MINER utility. Will it be faster to recover using the
LOG MINER(if possible) or just restore from backup. Also can you tell how to set
up and use the log miner utility.

Thanks as usual for your Great answers.
and we said...
a) in a data warehouse, hopefully you are using partitioning.  You can do a mas
delete in parallel using parallel DML (see the server concepts guide)

In your case however, since more then 5/10% of the data is going to be zapped, I
would be very tempted to create table as select... unrecoverable and save the
data I wanted to keep and drop the other table.  It would most likely be much
faster then deleting

b) log miner is not useful to recover from a drop/truncate (other then it can
help you nail down the SCN you want to use to perform a point in time recovery
to).

If you have my book -- i have a complete section on log miner including the
installation/setup.  Otherwise the Admin Guide and Supplied Packages guide does
contain this information as well.
Review & Followup
Rating: 4
Clarification April 05, 2002
Reviewer: Pawan from USA
Thanks for the prompt reply. You took less than 5 mins to answer.
Anyway when you "CREATE TABLE AS SELECT -- UNRECOVERABLE'
Do you mean
CREATE table NEW_TABLE
UNRECOVERABLE
AS SELECT * FROM OLD_TABLE
WHERE (Conditions to leave out records that I want to delete and only Keep what
I want)

Will it be faster than
DELETE from table
WHERE ( conditions to select what I want to delete)

Followup:
yes, that is what i mean.  It'll be faster and result in a more "compact" set of
data afterwards.

Rating: 3
What about using this script April 05, 2002
Reviewer: Anant from usa
Tom,
  I use the following query or Mass deletes. Is it the best way to do it.

declare
Cursor c1 is
select rowid rec_no from TABLENAME
where SALES_CODE IN
('11','12')
;
begiN
    for c1_rec in c1 loop
       delete from TABLENAME
       where rowid=c1_rec.rec_no;
    if (mod(c1%rowcount,25000)=0)
    then
       commit;
    end if;
    end loop;
 commit;
 exception
 when others then
    raise;
end;

Can I use Bind variables here? If so how

Followup:
That is an ora-1555 just waiting to happen.

It works but is rather inefficient.  You should just

delete from table where ....;

and size your undo appropriately in my opinion (gosh, with the cost of disks
today -- you probably cost your company MORE money in writing and testing that
then an 18gig drive would have cost to hold the undo)

You did use bind variables where appropriate -- in the delete.  In the query,
you didn't really need to use them as the codes never change.

Rating: 3
Little more explanation would help April 05, 2002
Reviewer: Anant from usa
Thanks Tom. When you say " That is an ora-1555 just waiting to happen." Do you
mean that it will run out of rollback space - even when the RBSs are huge. Sorry
but if you could explain this with an example it would help a beginner like me.

Followup:
The query

select rowid rec_no from TABLENAME
where SALES_CODE IN
('11','12')

needs a consistent read.  In order to do that, it uses the rollback segments.
when you commit - you allow the undo you just created to be overwritten when we
wrap the rbs.  If that happens AND you need that undo (it is against the table
you are querying after all), you will get an ora-1555 on the query.

If your rbs's are huge -- whe are you doing all of this work -- just do the
delete and be done with it!

If you have my book - lots of examples in there, lots of detail.  search this
site for 1555 or ora-01555 for many more.

Rating: 5
INSERT /*+ APPEND */ verse Create table ... unrecoverable April 05, 2002
Reviewer: Chadc from Salt Lake City, UT USA
In this example, you used CREATE TABLE T_NEW AS SELECT * FROM T_OLD
UNRECOVERABLE.  How would this differ from creating a new table (CREATE TABLE AS
SELECT ... WHERE ROWNUM < 1), then doing an INSERT /*+ APPEND / INTO T_NEW
SELECT * FROM T_OLD WHERE ...?  Any big advantage to one over the other with the
goal of moving the data into a new table as quickly as possible?

Followup:
Well, one requires more typing then the other.

I like to type less.

Hence, since I have to create the table anyway, I would use the create table as
select.

Rating: 4
Constraints April 07, 2002
Reviewer: Irfan from Kuwait
Hi Tom,
How about constraints when we

create table as select... unrecoverable
and
drop the other table.

All the constraints will be ok? Since we have got lot of constraints.

Thanks ...

Followup:
You have to migrate the constraints yourself.  They are not moved with the
create table as select.

It would still most likely be faster then doing a delete of a large percentage
of the data, especially if you can put the constraints back on without
validating them (eg: if you have a constraint like "check x between 1 and 10",
you know that the copied data cannot possibly violate this constraint, hence you
can put it on without having us actually validate it very quickly)

Rating: 3
Mass Delete September 03, 2003
Reviewer: RameshG from UK
Tom,

We have got a table with blob data type.  Our online system creates records in
this by coverting XML into blob.  We need to retain this data for 90 days,
anything older than 3 months can be deleted. Out system creates nearly 1GB data
per week. Average row size is around 200K.

What we are doing just now is:
We have created 5 tables with names comp1, comp2, comp3, comp4, comp5. We then
created a view which points to any of the table but to only one table at a time.
 At the end of the 3rd week our cron script recreates the view and points to the
table that was in use 14 weeks ago. i.e If the view is pointing to comp5 next
time when the cron script is run it points to comp1 and so on. If the table that
was last used becomes 10 weeks old then cron scripts drops and recreates the
table.

The diagnostic team has been provided access to this table through another view
which is the union of all five tables. Only diagnostic team uses this table.

We are neither using partitioning nor cost-based optimizer.

Is this the best way of regaining the space?
Can we not just delete data that is 3 months old? It may or may not reduce
high-water mark but the space(if any!) will be utilized by next insert anyway.
Could you explain me if my assumption is incorrect.

Could you also explain me the best way of doing this.

Many thanks
RameshG

Followup:
i would use partitioning, very easy to do a sliding window with that.

Rating: 1
Mass Delete September 04, 2003
Reviewer: RameshG from UK
Sorry Tom. We will not be able to use partitioning option until the proposal for
it is accepted and project is kicked off which will happen in Q2 of 2004.

Could you explain me the best way of doing this job in the absence of
partitioning.

Thanks
RameshG

Followup:
you already did -- use the views and do it yourself.

Rating: 5
CTAS and insert append September 04, 2003
Reviewer: A reader
What is better more efficient.
1)create table t nologging as select million_rows from table_X

2)create table t nologging as select million_rows from table_x where 1 = 2 ;--
create empty table
 insert /*+ Append */ into t select million_rows from table_x ;

Because in 1 we donot use append hint.Or what is the difference between them

Followup:
a CTAS works just like an insert/append

a CTAS can be more efficient then insert/append in some rare cases using
parallel query, but in general -- they will be more or less "the same"

September 04, 2003 An -- Thanks for the question regarding "Project marvel ", version 9
You Asked
Hi Tom,
I just came to know about Project Marvel online development tool through your
site,
I am using Emp table to create a sample project .
I am trying to reterive the max(sal) in "sal" field based on value in "Job "
filed when creating new record.
(select max(sal) into :F15_p7_sal from emp where job=:f15_p7_job)

But every time it gives some error ,Please give some idea .
Thanks
An
and we said...
An,

I'm a colleague of Tom's and one of the developers of Project Marvel.  I'd like
to start off by directing you to the forums section on OTN.  The following link
will take you too the general forum for Project Marvel:
http://www.oracle.com/forums/forum.jsp?id=1079824
While Tom is an excellent (and seemingly endless) source of information, we
would prefer not to burden him with questions on Project Marvel.  We have
several developers dedicated to answering questions on this forum.

And now for your question.  At first glance, it looks like a case issue.  All
item names in Marvel are case sensitive.  As a general rule, name everything in
marvel (items, buttons, etc.) that you need to reference using upper case and
reference them the same way.  In the future, I would also recommend that you
include the error.  This makes debugging so much easier.  Hope this helps.

Thanks,
Tyler

Tyler D. Muth
Technologist
Oracle Special Web Systems
tyler.muth@oracle.com
Review & Followup
Rating: 3
Where the tutorial ? September 03, 2003
Reviewer: Robert from CT
I just signed up for an account on PM.

There used to be a lot of documentations & an excellent tutorial at
marvel.oracle.com...easily locatable.

Now it's the login page...

Where is all that good stuff gone to ?
Thanks

Rating: 4
docs found September 03, 2003
Reviewer: Robert from CT
Sorry, tom
the docs I was refering to are inside the enviorment.
(gotta sign up to read it...)

Been looking all around for PM download....
Is PM downloadable ? you have the download link ?
Thanks

Rating: 4
Where to place HTML DB files ? September 04, 2003
Reviewer: Robert from CT
Tom, I found the download & installed successfully.

But instruction didn't say anything about this install location :

Where is the download(rtflows.zip) "supposed" to be unzipped to ? a sub-dir
under ORACLE_HOME OK ?

I unzipped to /tmp, ran installation from there....fine.
I did copy "images" directory to where I mapped /i/ to.

What about all the other stuff ?
Any of those files still required ?
Should I move them to a perm location ?

Also when I tried to install "Sample Application" - I got error message
complaining about storage --- I did the "provisioning" but still won't go - what
should I do ?

Thanks

Followup:
Robert,

These files do not need to exist in your ORACLE_HOME.  The only files which need
to persist after installation is the image directory hierarchy.  If you copied
these to somewhere accessible by Oracle HTTP Server, you're good to do.

"What about all the other stuff ? "

Not sure what stuff you're talking about, but as indicated above, only the image
hierarchy needs to persist.

If your workspace was not provisioned with sufficient amount of storage to
accomodate installation of a sample application, you can either:

1) Manually increase the size of the tablespace provisioned for your workspace

or

2) As an admin "user" of your installation, you can (from the Administration
area for your workspace) submit a request for more storage, then as the service
administrator (i.e., internal workspace), approve the request, which will then
increase the size of the tablespace.

FYI, I recommend perusing and/or posting Project Marvel specific questions to
the OTN forum dedicated to this purpose, at:
http://www.oracle.com/forums/forum.jsp?forum=137

September 05, 2003 Jerry -- Thanks for the question regarding "Can I
      create a synonym for a role?", version 7.3.4.2

      You Asked
User1 has tableA
User2 is granted RoleY

When I use this syntax as User1

SQL> create synonym RoleY.tableA for tableA;

I get the following error message:

create synonym RoleY.tableA for tableA
               *
Error at line 1:
ORA-01917: user or role '' does not exist

To me this implies that a role can be granted or assigned a synonym.
The role does exist; I use it for other purposes.  I don't want to
create a public synonym if I don't have to.

Please advise.

      and we said...
A role is a collection of privileges.  A role "owns" no objects.  Even if the
above worked and created a synonym TABLEA in the "role" ROLEY, what would happen
if a user had been granted:

ROLEY
ROLEX

and ROLEY.TABLEA was a synonym that pointed to USER1.TABLEA
and ROLEX.TABLEA (another synonym) pointed to USER2.TABLEA

which synonym would have worked?  It is ambigous.

A solution that may work well for you will be as such:

- there exists a command "ALTER SESSION SET CURRENT_SCHEMA=<schema_name>".  This
command is heavily used by IMP/EXP.  It is not documented until Oracle8.1 but it
exists in all Oracle7.x and 8.x releases.

- This command does not change any PRIVILEGES you have

- see for details

http://technet.oracle.com/doc/oracle8i_816/server.816/a76989/ch4c2.htm#42646
but in short this:

...
CURRENT_SCHEMA = schema

 changes the current schema of the session to the specified schema. Subsequent
unqualified references to schema objects during the session will resolve to
objects in the specified schema. The setting persists for the duration of the
session or until you issue another ALTER SESSION SET CURRENT_SCHEMA statement.
CURRENT_SCHEMA is a session parameter only, not an initialization parameter.

 This setting offers a convenient way to perform operations on objects in a
schema other than that of the current user without having to qualify the objects
with the schema name. This setting changes the current schema, but it does not
change the session user or the current user, nor does it give you any additional
system or object privileges for the session. For more information on this
parameter, see Oracle8i Application Developer's Guide - Fundamentals.
....

- now you could create a real SCHEMA (account) called APPLICATION_Y or something
like that.  APPLICATION_Y would create views of all relevant objects (better
then synonyms IMO).

- your users (or their application) would simply issue:

alter session set current_schema=application_y;

And now if they:

select * from tablea;

that will really be:

select * from application_y.tablea;

and as that is a view, it'll REALLY be:

select * from user1.tablea;  (but your users will need the grant on the VIEW,
not the table.  If you use a synonym, they will need a grant on USER1.TABLEA)

Hope that makes sense.

Review &
      Followup

      Rating: 5
When all the
      objects are in another schema  March 23, 2001

Reviewer:  Jerry Treshinsky  from Thousand Oaks, CA

This was exactly what I needed! I can now skip PUBLIC synonyms or multiple
personal synonyms.  Thanks Tom!

      Rating: 5
Perfect
      Solution  March 18, 2002
Reviewer:  Thomas
      Stiegler  from Roehrsdorf, Germany
Hi Tom,
the mentioned
alter session set current_schema=application_y
command is exactly what I was looking for!

      Rating: 5
Setting
      current_schema in logon trigger  July 12, 2002

Reviewer:  Johan Snyman  from JHB SA
Tom,

I have an application that reside on a 8.1.6.2.0 database with other
applications and would like to limit the creation of public synonyms to avoid
name clashing between the apps. I am therefore looking at using the ... set
current_schema ... in an on-logon trigger to set the users default schema to
that of the appropriate application.

I have found that if I try to create the logon trigger as anther user (the owner
of the application objects), using:

create or replace trigger setdiggerschema
after logon
on scott.schema
begin
execute immediate 'alter session set current_schema = digger';
end;

Then it does not work, i.e. when I logon as scott, the default schema is still
scott, not digger.

If I grant create trigger to scott and create the trigger while logged on as
scott:

create or replace trigger setdiggerschema
after logon
on schema
begin
execute immediate 'alter session set current_schema = digger';
end;

Then it works, but this have several drawbacks, of which I am mostly concerned
about granting create trigger...

Is there another way to do this ?

Can I create a logon trigger that will apply to a group of users, (both cases
above require that I create the trigger for every new user added).

Your help will be much appreciated, since the 816 docs does not cover logon
triggers very well.

          Followup:

I just did this in 817 -- it works.

I did it in 815 -- it did not.

I don't have 816, but I would have to assume based on the above that -- it was a
bug fixed in the terminal release of 8i.

I can only suggest in this case -- since you are trying to add new functionality
(doing development apparently) that you upgrade to the current supported
release.  There were some quirks with logon triggers in the early 8i releases --
fixed in 817.

      Rating: 3
It does not
      work for me in 8.1.7.0.0  September 04, 2003

Reviewer:  Sonali Kelkar  from Waltham, MA

--- PSASRV is PSA schema with DBA priviledges.
-- PSASRV2 has nothing in it... it is just a user.
SQLWKS> connect psasrv@oratest
Connected.
SQLWKS> create user psasrv2
     2> identified by psasrv2
     3> default tablespace psasrv
     4> temporary tablespace TEMP;
Statement processed.
SQLWKS>
SQLWKS> alter user psasrv2
     2> quota 500M on psasrv;
Statement processed.
SQLWKS>
SQLWKS> grant connect to psasrv2;
Statement processed.
SQLWKS>
SQLWKS> create role Sonali;
Statement processed.
SQLWKS> grant CREATE Trigger  to Sonali;
Statement processed.
SQLWKS>
SQLWKS> GRANT Sonali  TO psasrv2 WITH ADMIN OPTION;
Statement processed.
SQLWKS> grant DELETE,INSERT,SELECT,UPDATE on psasrv.MWEBRES to Sonali;
Statement processed.

SQLWKS> connect psasrv2@oratest
Connected.
SQLWKS> select count(*) from mwebres
     2>
select count(*) from mwebres
                     *
ORA-00942: table or view does not exist

SQLWKS> connect psasrv@oratest
Connected.

SQLWKS> create or replace trigger logontrigger
     2> after logon on database
     3> declare
     4> pragma autonomous_transaction;
     5>
     6> Begin
     7>
     8>     if user = 'PSASRV2' then
     9>      execute immediate 'alter session set current_schema = PSASRV';
    10>     end if;
    11>
    12> end ;
    13> /
Statement processed.

SQLWKS> connect psasrv2@oratest
Connected.

SQLWKS> select count(*) from mwebres
     2>
select count(*) from mwebres
                     *
ORA-00942: table or view does not exist

It works in 8.1.7.4 and 9.2 and 9.0... why not in 8.1.7.0.0 ??

Thanks
Sonali

          Followup:

are you sure the trigger is even firing?  perhaps someone has disabled it
(_sys_trig_enabled)

8174 is the only supported 8i release right now.  using 8170 would not be
advisable for any real system.

September 05, 2003 tammy -- Thanks for the question regarding "What
      is the best book for STATSPACK tuning", version 8.1.7.3.0

      You Asked
Hi Tom: We have oracle 8.1.7.3.0 and are going to migrate to oracle 9.2.0.1.0,
what is the best book or documentation for me to follow and get some short cuts
to turn this huge 4 terabytes DW?

      and we said...
Well, I think my chapter 10 (expert one on one oracle) does a good job of
slicing a statspack reports.  Not excessively deep (but then statspack is just
showing you whats in the v$ tables, knowledge of those is important.)

Don Burleson wrote a pretty good one (so i've heard, haven't read it myself)

http://www.amazon.com/exec/obidos/tg/detail/-/007222360X/qid=1032905924/sr=8-1/ref=sr_8_1/102-7710810-7914531?v=glance&n=507846
(search amazon for statspack)

And if you have OEM the tuning pack in 9ir2 is really cool -- so cool that for
the first time, I'm using a gui myself.

I'll publish this cause I always get good followups when I say "sorry, don't
really have a book recommendation -- maybe someone else does"....

Review &
      Followup

      Rating:
      5
Sold...  September 25, 2002
Reviewer:
      Connor McDonald  from UK
"I'm using a gui myself"

Sold your soul to the devil eh?

(I'm omitting a mindless rant about "GUI?! In my day, we only had a 4-key
keyboard, a rock and 2 pieces of string for performance tuning" )

:-)
Connor

          Followup:

My first experience with a computer was RPG on punch cards (yuck, high school
course, had to MAIL the cards we punched one week to a downtown processing
center where they were "compiled" and the listing was mailed back to us).

My next experience in college.  There we had teletypes until my sr. year.  Can
you IMAGINE editing a file you cannot "see".  You hit "backspace" and it would
literally go back and type an "X" over the thing that was there.  If you've ever
used "edlin" on DOS, it is a very close experience to a teletype only a lot
friendlier.  Talk about noisy too.  Get in a room with 100 teletypes going
sometime.  And when the telecom line would go down -- we were using RJE's -- the
silence would be un-nerving (until someone cursed really loud of course ;)

And then my job as a "green screen programmer" on MVS and VM/CMS  (VM/CMS was
actually pretty darn cool for a mainframe).  I thought I hit the big time when I
acutally got a 40 line terminal -- instead of the usual 24 line ones!  But
again, we were a remote entry site -- so I got very used to a little thunderbolt
on the bottom of my screen (meaning -- hey, no communication) and screen
switches that took a good 20-30 seconds apiece.

Ahh, progress.  Now I have 2 21" monitors with 3200x1200 dots and 4 virtual
desktops giving me effectively 6400x2400 and it still isn't enough room for
everything....

      Rating:
      4
  May 16, 2003
Reviewer:  Mike

"
And if you have OEM the tuning pack in 9ir2 is really cool -- so cool that for
the first time, I'm using a gui myself.
"

I really do not know many experienced performance tuner are   relied on Tuning
Pack or Oracle Expert to do the job? Since you work for Oracle, I believe that
you might not want to give your true assessment for the tools. My boss is going
to mike me to use the Oracle Expert; I fell just badly for not someone not a
database expert to such decision.

          Followup:

read the articles here.  I wrote all of them.  Read my book if you like, I wrote
the thing myself.

then you can judge my abilities in "tuning sql" and "tuning in general".

All you need to do is evaluate it - I would never expect someone to buy
something "just cause someone said so".  I would expect they would evaluate it,
use it, test drive it - then buy it.

      Rating: 5
A question
      about SQL snapshots in statspack  May 16, 2003

Reviewer:  A reader
I found this to be somewhat mind-boggling... Take this scenario:

I take a snapshot #1 at 12:00. I take a snapshot #2 at 12:15.

Then I run spreport, which will subtract snap #1 stats from snap #2 stats.

Take situation A: A SQL was in the shared pool at 12:00 (snap #1) and was aged
out before 12:15 (snap #2), so it existed only in snap #1.

Take situation B: Another SQL was issued after 12:00 (snap #1) and was not aged
out before 12:15 (snap #2), so it existed only in snap #2.

Questions ahead:

a. Since both SQLs were either in snap #1 OR snap #2 -- not both -- and did not
persist across all the time between and including snap #1 and snap #2, what will
happen to them in the report ? Will they just not appear ?

b. Will statspack track only SQL statements that were both in snap #1 AND snap
#2 ?

c. What if I need to know their statistics ?

d. What if another (third) SQL were in the shared pool -- say -- from 12:05 till
12:10 -- that is, after snap #1 and before snap #2 ?

Thanks

          Followup:

a) consider this -- how likely is that to happen?  I've never dug into statspack
to see what exactly would happen.  You would need to dig into the queries it
uses.

But consider for case 1 -- a query that was in at 12 and aged out before 12:15
-- you don't care about that, obviously it wasn't really executed.

for case 2 -- when the query is introduced AFTER the observation window began, a
simple script such as:

alter system flush shared_pool;
exec statspack.snap
select count(*) from all_objects I_WAS_IN_AFTER;
select count(*) from all_objects I_WAS_IN_AFTER;
select count(*) from all_objects I_WAS_IN_AFTER;
select count(*) from all_objects I_WAS_IN_AFTER;
exec statspack.snap
set echo off
@?/rdbms/admin/spreport

shows us that the query will appear in the report......

Case 1 -- you don't care as it implies the query wasn't used OR your shared pool
is so small that your first step in correcting your performance issues would be
to INCREASE the shared pool (and that would be obvious from the hard parse ratio
and really low library cache hit)

Case 2 -- it shows up.

b) no.

c) ahh this one is EASY, make sure you use bind variables and size your shared
pool appropriately!

d) see c)

      Rating: 4
OEM &
      Statspack  September 04, 2003
Reviewer:
      Richard  from VA
I've just discovered Capacity Planner and Performance Monitor.  They seem to be
great, but I have a couple of questions....

Is it possible to generate an spreport from OEM?

Since I'm collecting stats in the repository, can I stop taking snapshots?

          Followup:

oem is not statspack, no, you do not "spreport" from OEM.

you can use either, or both.  me, i like statspack.  others might like oem.
depends on what outputs you want.

September 05, 2003 M. -- Thanks for the question regarding "How to
      Increase Font Size in SQL *Plus", version 8.1.5

      You Asked
Hi Tom,

Can you please explain me How can I increase the Font Size/Name in SQL *Plus.

Is there any way to change the Font Size and Font Name of SQL *Plus. Sometimes
it's really hard to concentrate on that default Font.

Greatly Appreciated.
Thanks...

      and we said...
No, not in the GUI sqlplus however....

I never use the "gui" one.  I myself always use the character mode one
(sqlplus.exe, not sqlplusw.exe).

I can set the colors, font size, screen height, width and scrollback buffer for
the dos window.

Review &
      Followup

      Rating: 1
Could you show
      us how do you do it in SQLPLUS?  November 14, 2002

Reviewer:  Abhijit  from Mumbai
Hi Tom,
Could you please show us how do you set the font-size/colors in SQLPLUS.exe?

Cheers!
Abhijit

          Followup:

Edit / Properties in the little DOS window menu in the upper left hand side of
your window

      Rating: 5
How about
      iSQLPlus Tom ?  November 15, 2002
Reviewer:
      pasko  from HH,Germany
Hi Tom,

I would like to use iSQLPlus , if at all such an exe  exists..because i read
somewhere that there's a new WEB enabled SQLPlus with Oracle 9i..but i never
really saw it in the Oracle's BIN directory...

How do i install  the new WEB  sqlplus?

Thanks

          Followup:

step one, read the documentation:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a90842/toc.htm
as long as you've setup the apache webserver that comes with -- your read to go.

I just

o logged in as my oracle account
o cd $ORACLE_HOME/Apache/Apache/bin
o ./apachectl start

and then the url http: //hostname:7777/isqlplus just "worked"

      Rating: 5
You could try
      this to change font in GUI SQL-Plus  November 15, 2002

Reviewer:  Raghu
You can change the font in SQL*Plus for Windows NT/2000.

In regedt32, go to
    HKEY_LOCAL_MACHINE
                -> SOFTWARE
                    -> ORACLE
                         -> HOME0

Create a new registry value called SQLPLUS_FONT of type REG_EXPAND_SZ and set it
to your favourite fixed-width font, Eg. Courier New

Create a new registry value called SQLPLUS_FONT_SIZE of type REG_EXPAND_SZ and
set it to the size you want (16 is a good size).

Exit from the registry and try to log-on to SQL-Plus and see if the font size is
any different.

This has worked for me on Oracle 8.1.7. I have not tried on other releases.

I found this tip on the web.

          Followup:

It is actually in the documentation for sqlplusw as well

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/win.817/a82954/apa.htm#423093
as on the "web".

However -- it'll have ZERO effect on "sqlplus.exe" which is the DOS (character
mode -- my favorite) version.

It is "new" with 817.

      Rating:
      5
sql*plus  November 17, 2002
Reviewer:
      mo
Tom:

Is there a way to make sql*plus output wrap columns on he same page. for exampl
if I have 6  columns it will print:

COL1   COL2   COL3
xxx     xxx   xxxx

COL4  COL5   COL6
xxx   xxx    xxxx

Currently I use set linesize 2000 and I keep scrolling inside the window to see
all data

      Rating: 5
sql
      *PLus  January 31, 2003
Reviewer:
      A reader
Hi tom,

  I just want to know in the
"
SQL*Plus User's Guide and Reference
Release 8.1.7
Part Number A82950-01
"

There are lots of commands, that we can use in sqlplus

 does sql * plus submit the same string as typed
 to the orace datadbase server or
 parse it
 process it and if required convert it in the query
 get the result set or error code and msg.
 format it ( based on all "SET"  options
 display it

 so just to describe the table scott.emp,
 how much work is done ?
1.) parse the stmt
2.) get the key word 'DESC' or 'DESCRIBE' upper or lower
    case only  >=4 chars are allowed in command
3.) search if the keyword is found in some keyword table
4.) if not found get oracle error or based on key word,
    query 1 or 2 datadictionary views
5.) handle the resultset
6.) veryfy all the setting of curret setting
    set,compute etc..
7.) check for pause
8.) display result ?

 is it true or you have a smart way to do it ?

 so any other editor like oracle worksheet(OEM),toad,pl/sql developer does that
same thing ?

TIA

          Followup:

sqlplus doesn't change your SQL, it just submits it.

SET options are applied to the results (the fetched data) only.

To desc scott.emp your steps would be valid.

get stmt
parse stmt
figure out what statment is
process stmt

that is what pretty much all command line environments do with everything.

      Rating:
      5
sqlplus.exe  February 08, 2003
Reviewer:
      Jerry  from Washington, DC USA
Another nice thing about the command line version is that one can use the
"doskey" function there, which is nice for re-executing one-liners or alter
session type commands.

      Rating: 3
about the &
      in SQLPLUS  September 03, 2003
Reviewer:  Asim
      Naveed  from Pakistan
Sir,

I wrote the follwoing SQL in SQLPLUS

SELECT 'ABC & DEF' FROM DUAL

what i want from above SELECT is the string "ABC & DEF"

but when i execute the sql, it starts asking me for
the input value. How can i print the above string in
sqlplus

Thanks

          Followup:

SQL> set define off

      Rating: 3
but what in the
      case we also need input  September 04, 2003

Reviewer:  Asim Naveed  from Pakistan
Thanks, but i was just thinking what if i need
an input also

e.g

SELECT 'ABC & DEF' || name2 FROM t1 where name1= &m_name1.

I want the user to input name1 every time the query
executes, if I set define off then it doesnt ask for
input.

Thanks

          Followup:

set define ^

select 'ABC & DEF' || name2 from t1 where name1 = ^m_name1;

you cannot have your cake and eat it too -- there must be SOME character that
says "hey, if you see me, the stuff after me is special".  It is either ON or
OFF.

You can change it but there will be "one character" that is either special -- or
not.

September 05, 2003 Tom -- Thanks for the question regarding "Index
      Tablespace", version 8i

      You Asked
Hi Tom,

Is it still a good idea to keep indexes in their own tablespace?
 Does this inhance performance or is it more of a recovery
issue?  Does the answer differ from one platform to another?

Thanks in advance,

Tom

      and we said...
Yes, no, maybe.

The idea, born in the 1980's when systems were tiny and user
counts were in the single digits, was that you separated indexes
from data into separate tablespaces on different disks.

In that fashion, you positioned the head of the disk in the
index tablespace and the head of the disk in the data tablespace
and that would be better then seeking 2 times on the same disk.

Drives back then were really slow at seeking and typically
measured in the 10's to 100's of megabytes (if you were lucky)

Today, with logical volumes, raid, NN gigabyte (nn is rapidly
becoming NNN gigabytes) drives, hundreds/thousands of concurrent
users, thousands of tables, 10's of thousands of indexes -- this
sort of "optimization" is sort of impossible.

What you strive for today is to be able to manage things, to
spread IO out evenly avoiding hot spots.

Since I believe all things should be in locally managed
tablespaces with UNIFORM extent sizes, I would say that yes,
indexes would be in a different tablespace from the data but
only because they are a different SIZE then the data.  My table
with 50 columns and an average row size of 4k might belong in a
tablespace that has 5meg extents whereas the index on a single
number column might belong in a tablespace with 512k or 1m
extents.

I tend to keep my indexes separate from the data but for the
above sizing reason.  The tablespaces frequently end up on the
same exact mount points.  You strive for even io across your
disks and you may end up with indexes and data on the same
devices.
Review
      & Followup

      Rating: 3
9.1
      documentation suggests seperate because...  November 28,
      2002
Reviewer:  A reader
Hi

in Oracle 9i documentation Concepts guide chapter 11 where it says HOW INDEXES
ARE STORED it states this:

Furthermore, you can improve performance of queries that use an index by storing
an index and its table in different tablespaces located on different disk
drives, because Oracle can retrieve both index and table data in parallel.

So Oracle can really read both index and data in parallel? if that was the case
then we must seperate index from data no?

          Followup:

that is a documentation bug.  It is not 100% true nor false

it is (well should be) obvious that it cannot access them in parallel for a
single session.  We read an index to find the table data.  Index then data.
Index read, then table access by rowid.  Not "index+data" at the same time,
INDEX leads us to DATA.

Now, in a multi-user system -- we are back to the "old reasons".  It was a "poor
mans stripe".  Put things on different devices to spread the IO out.  Someone
will be reading the index disk whilst someone else is reading the data disk.

Also -- did it go onto say "of course, you best make sure the tablespaces are on
DIFFERENT PHYSICAL DEVICES".  if it didn't, then it left you hanging -- without
giving you the really important piece of information.  That is is trying to
spread IO out across all devices.  Not that index and data need to be separated
like matter and anti-matter.

The SAME exact benefits could have been achieved just by using small extent
sizes -- creating one tablespace with datafiles from many disks -- and putting
all of the data in there.  We tend to allocate extents in a round robin fashion
from the available datafiles in the tablespace so the index and data extents
would be splayed across the disks.  This would achieve the same goal -- to
achieve EVEN IO.

That is the goal, the only goal here -- achieve an even distribution of IO
across as many devices as you can give us.  Achieve that however you want but
putting indexes on disk1 and data on disk2 is perhaps the weakest, least
effective way to do that!!

      Rating: 3
how about batch
      processing  September 04, 2003
Reviewer:  Dick
      Stins  from The Netherlands, Amersfoort
Tom,

This is very interesting. I guess that you are right in a multiuser environment
(oltp) with lot's of users all doing different kind of things on lot's of
different tables and indexes. All movements of the heads on the disks are quiet
randomly and it is very hard to tell which constructions are able to reduce the
head movements (still bottleneck on mechanical drives).

But quiet often are the machines at night dedicated for batch processing. In
this case it is easier to predict and the transactions on the head movements.
When you put indexes and data on one disk, than you are guaranteed that the
heads will be swapping between those physical index and data sectors on the
harddisk.

When you put the indexes and the data on different harddisks, then it might help
to reduce the head movements for this specific situation.

Ofcourse it depends on the kind of hardware, cache, ....
So you need to test.

Parallel read of indexes and data might be hard (or not?) but what happens with
updates? Indexes and data might be updated in parallel.

          Followup:

NO NO NO.

no you don't.

period.

it won't.  it hasn't.  it never really did.

if you are using indexes (single block, random io -- as you traverse from ROOT
block to branch block to leaf block you'll have read data from 3 TOTALLY
different places on the disk), you are accessing the table using single RANDOM
block IO as well.

So no, it won't help "keep the heads where they should be" cause the heads are
NEVER where they should be after any given read -- they always have to move
elsewhwere to do the next read.

so, if you are using indexes, forget about this argument, it doesn't work.

(the updates are not done in parallel and the updates involve tons of single
block, random IO once again to walk the index structure -- the heads are flying
all over the place)

      Rating: 1
flying
      heads  September 06, 2003
Reviewer:  Dick Stins
       from The Netherlands, Amersfoort
Tom,

Thanks for you interesting answer.
You are right you keep head movements for index searches, but when the index is
not defragmented in lot's of extents and in the same tablespace, then the heads
are still closer to the needed blocks. That saves big head movements so it takes
less time to read.

You should know that in batch processing and data warehouses, you not only have
index searches but also lots of inserts, updates, deletes, full table searches,
hash joins. I can't imagine that your proposition needs some extra refinement.

Concerning my information, you can do parallel updates when the updates are
spread over different partitions.

I might have an interesting example. But first I need to do some extra testing
to be sure.

September 05, 2003 Vallabh -- Thanks for the question regarding
      "Transporting tablespaces / Backup recovery", version 8.1.1.5

      You Asked
1. While checking whether tablespace is self contained the
statement
exec dbms_tts.transport_set_check ('users', true)  ...gave
following error

ERROR at line 1:
ORA-01001: invalid cursor
ORA-06512: at "SYS.DBMS_SYS_SQL", line 480
ORA-06512: at "SYS.DBMS_SQL", line 19
ORA-06512: at "SYS.DBMS_TTS", line 645
ORA-19733: COMPATIBLE parameter needs to be 8.1.0.0.0 or greater
ORA-06512: at "SYS.DBMS_TTS", line 667
ORA-06512: at line 1

what is the reason? what can be a solution?

i checked the comaptible parameter it is 8.0.5..will installing
higher version solve the problem?

2. What is recovery catalog? how to create it? is it must to
create recovery catalog before taking backup?

      and we said...
1) You do not need to install a higher version.  You need to
change your compatible init.ora parameter.  It is advisable to
FULLY BACKUP your database before changing that parameter.

2) It is part of rman.  It is only necessary if you are using
rman.  If you are using rman, you have a lot to read about
before you need a recovery catalog so I will refer you to the
rman documentation where you can learn how to create a catalog
as well as use rman:

http://technet.oracle.com/doc/oracle8i_816/server.816/a76990/toc.htm
...

Review &
      Followup

      Rating:
      4
dbms_tts.transport_set_check ( .. .. )  September
      04, 2003
Reviewer:  A reader
Tom,
can you please tell me where exactly we will be using this procedure and what
for this is being used.

exec dbms_tts.transport_set_check ('users', true)

thanks

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96612/d_tts2.htm#999561
we document that stuff :)

September 05, 2003 Fiza -- Thanks for the question regarding
      "Transaction", version 9.2

      You Asked
Hi Tom-- I am new to Oracle kind of confuse what the transaction is in my
understanding it is some change to table that we commit...and if we dont say
commit its not transaction...am i right...will appreciate you help...Thanks

      and we said...
If you have my book "Expert one on one Oracle" -- i cover topics exactly like
this (and lots more) in great detail.

In Oracle -- a transaction begins with the first statement submitted to the
database.

It ends when you commit or rollback.

If you don't say "commit", then whatever tool you used to connect to the
database will either commit or rollback FOR YOU depending on how it was built.
SQLPlus -- defaults to committing when you exit.  PRO*C defaults to rolling back
if you exit without saying otherwise.
Review &
      Followup

      Rating:
      4
Selecting  November 10, 2002
Reviewer:
      A reader
Thanks for your help even if we select someting is it still a transaction...coz
in that case we just pulling data out from database...Thanks alot

      Rating:
      5
  November 10, 2002
Reviewer:
      A reader
What u mean by PRO*C??

      Rating: 4
largest
      transaction  September 05, 2003
Reviewer:
      A reader
Is there any way to  find the largest transaction, including completed ones?

          Followup:

well, in theory you could mine the logs using logminer, but it would be tedious.

no, there is nothing builtin to give you this sort of very very low level
detail.

September 05, 2003 Orna -- Thanks for the question regarding "SQL
      loader - reenable disabled constraints option", version 8.0.6

      You Asked
Hi Tom

I am loading data via SQL loader to table A , with primary key
A_PK ( on the DMID attribute)

I created an EXCEPTIONS table and renamed it to TEST_EXCEPT , to
avoid using a reserved word as a table name.

My control file looks like that:

 load data
 infile a.csv
 truncate
 into table a
 reenable disabled_constraints exceptions test_except
 fields terminated by ';'
 (
 DMID char.....
 )

 My data file contained 1200 records to load,  2 of them with
 duplicate primary key ( same DMID  ).

 I run the following  command using the direct path insert
option :

 sqlldr userid=user/pass control=a.ctl direct=true

 I would expect that :
 * Data will be loaded to table A
 * The PK unique constraint will not be enabled due to the
duplicate values
 * The duplicate records will be inserted to test_except

 But what really happened was :
 * Data was indeed loaded and constraint not enabled ( as
expected )
* The duplicate records were not loaded to the exception table.

It seems like the loader completely ignored the reenable
clouse. Am I doing something wrong ?

Thanks
Orna

      and we said...
This is the correct behavior.  During a direct load, primary key
constraints are not disabled, but are verified (as are unique
constraints) when the indexes are rebuilt at the end of the
load.  When duplicates are found, the index is left in direct
load state.  The primary key constraint must be disabled and
then reenabled using the exceptions clause in order to populate
the exceptions table.

See

http://technet.oracle.com/doc/oracle8i_816/server.816/a76955/ch08.htm#1472
it describes that during a direct path load, NOT NULL, UNIQUE
and PRIMARY KEY constraints remain in an enabled mode -- hence
they are not re-enabled at the end since they were never
disabled.
Review
      & Followup

      Rating: 3
Test Direct
      load  October 10, 2002
Reviewer:  kiro

Hi Tom
I have tried to reproduce reenabling of index. I am using direct path load.
After first start of data everithing was OK.
But after second load with same data I expected rows into ex table, but see
result below:

Invoice_10 is partition table with 12 part

mfapp> truncate table invoice_10;

Table truncated.

mfapp> alter table INVOICE_10 drop CONSTRAINT PK_INVOICE_10;

Table altered.

mfapp> ALTER TABLE INVOICE_10 ADD (
  2        CONSTRAINT PK_INVOICE_10
  3        PRIMARY KEY (YEAR, INVOICE_NO ,TILL_NO)
  4  USING INDEX
  5  TABLESPACE MEGAFINS_INDEX_LARGE
  6  STORAGE  (
  7    INITIAL   10 M
  8    NEXT   10 M
  9    MINEXTENTS  1
 10    MAXEXTENTS  UNLIMITED
 11    )
 12  LOCAL
 13  );

Table altered.

control file for loading is:
OPTIONS ( ERRORS=10000, ROWS=5000, BINDSIZE=220000 )
LOAD DATA
APPEND
INTO TABLE INVOICE_10
reenable disabled_constraints exceptions ex
WHEN (1) <> '*'
    FIELDS TERMINATED BY '^'
.....

after first load:
mfapp> select count(*) from invoice_10;

  COUNT(*)
----------
       790

mfapp> select count(*) from ex;

  COUNT(*)
----------
         0

mfapp> desc ex;
 Name                            Null?    Type
 ------------------------------- -------- ----
 ROW_ID                                   ROWID
 OWNER                                    VARCHAR2(30)
 TABLE_NAME                               VARCHAR2(30)
 CONSTRAINT                               VARCHAR2(30)

this is OK.

NEXT step:

mfapp> alter table invoice_10 disable CONSTRAINT PK_INVOICE_10;

Table altered.

and start again Direct Path loading with same data:

mfapp> select count(*) from invoice_10;

  COUNT(*)
----------
      1580

mfapp> select count(*) from ex;

  COUNT(*)
----------
         0

Q1) Why? Is there somenting with partition tables? Can you explain,please?

thanks
kiro

          Followup:

sqlldr will only disable constraints that relate to other tables (eg: foreign
keys) NOT the primary key.

SQLLDR will only re-enable the constraints which IT disabled, not the ones you
did yourself.

      Rating: 4
Direct load -
      SQL Loader and insert /*+ append */  October 16, 2002

Reviewer:  Dushan
During a direct load (SQL Loader), primary key constraints are not disabled.
But before direct path load as insert /*+ append */, primary (unique) keys must
be disabled, what means the PK, UK's indexes are dropped. And that's the pain -
in the moment of enabling PK, UK's - the storage information about the indexes
before is gone, and those indexes are created with defaults!
I would expect that when enabling PK, UK's, the indexes are created with the
same parameters as they were defined before disabling PK, UK's.
Is't there any way how to achieve this?

          Followup:

really? (but, instead of drop -- set unusable, skip_unusable_indexes=true, do
the thing and rebuild!!!  don't drop!!!!)

but anyway (did it in 817 up at least)

ops$tkyte@ORA920.LOCALHOST> create table t ( x int primary key );
Table created.

  1* insert /*+ append */ into t select rownum from all_users
ops$tkyte@ORA920.LOCALHOST> /

36 rows created.

ops$tkyte@ORA920.LOCALHOST> select * from t;
select * from t
              *
ERROR at line 1:
ORA-12838: cannot read/modify an object after modifying it in parallel

ops$tkyte@ORA920.LOCALHOST> commit;

Commit complete.

ops$tkyte@ORA920.LOCALHOST> insert /*+ append */ into t select rownum from
all_users;
insert /*+ append */ into t select rownum from all_users
*
ERROR at line 1:
ORA-00001: unique constraint (OPS$TKYTE.SYS_C003125) violated

I didn't drop any constraints here.

      Rating:
      3
  October 17, 2002
Reviewer:  Dushan

The point is that the speed of  direct path load insert (insert /*+ append */)
for table with disabled PK, UK's, plus time spent on enabling PK, UK's (creating
indexes) is much faster then speed of the same statement with enabled PK, UK's.
But there are some annoying side effects regarding unique indexes when enabling
PK, UK's.
As I understood they do not exist for direct path load using SQL*Loader.

system@OD6I> @connect system/osystemi@od6i
Connected.

GLOBAL_NAME
---------------------------------------------------------------------------------
-------------------
system@OD6I

Elapsed: 00:00:00.00
system@OD6I> grant connect, resource to a identified by a;

Grant succeeded.

Elapsed: 00:00:00.60
system@OD6I> @connect a/a@od6i
Connected.

GLOBAL_NAME
---------------------------------------------------------------------------------
-------------------
a@OD6I

Elapsed: 00:00:00.10
a@OD6I> create table dv_all_objects as
  2  select * from all_objects
  3  where 1=0
  4  /

Table created.

Elapsed: 00:00:00.20
a@OD6I> ed
Wrote file afiedt.buf

  1  ALTER TABLE DV_all_objects
  2  ADD (CONSTRAINT DV_all_obj_pk PRIMARY KEY
  3*     (object_ID) using index tablespace users)
a@OD6I> /

Table altered.

Elapsed: 00:00:00.50
a@OD6I> select index_name, status, tablespace_name from user_indexes
  2  where table_name='DV_ALL_OBJECTS';

INDEX_NAME                     STATUS   TABLESPACE_NAME
------------------------------ -------- ------------------------------
DV_ALL_OBJ_PK                  VALID    USERS

Elapsed: 00:00:00.20

-- OK, index DV_ALL_OBJ_PK for PK DV_ALL_OBJ_PK is in tablespace users

a@OD6I> insert /* +append */ into DV_all_objects
  2  select * from all_objects;

21954 rows created.

Elapsed: 00:00:28.41
a@OD6I> truncate table DV_all_objects;

Table truncated.

Elapsed: 00:00:07.80
a@OD6I> alter index DV_ALL_OBJ_PK unusable;

Index altered.

Elapsed: 00:00:00.40
a@OD6I> alter session set skip_unusable_indexes=true
  2  ;

Session altered.

Elapsed: 00:00:00.00
a@OD6I> insert /* +append */ into DV_all_objects
  2  select * from all_objects;
insert /* +append */ into DV_all_objects
*
ERROR at line 1:
ORA-01502: index 'A.DV_ALL_OBJ_PK' or partition of such index is in unusable
state

Elapsed: 00:00:00.20

-- Oops! It does not work for indexes belonging to PK, UK's !
-- BTW, no surprise, I've found this in J. Lewis, Practical Oracle

a@OD6I> alter table DV_all_objects disable constraint DV_ALL_OBJ_PK;

Table altered.

Elapsed: 00:00:00.00
a@OD6I> select index_name, status, tablespace_name from user_indexes
  2  where table_name='DV_ALL_OBJECTS';

no rows selected

Elapsed: 00:00:00.20

-- Index for PK is gone !!!!!!!!!!

a@OD6I> insert /* +append */ into DV_all_objects
  2  select * from all_objects;

21953 rows created.

Elapsed: 00:00:13.19

a@OD6I> alter table DV_all_objects enable constraint DV_ALL_OBJ_PK;

Table altered.

Elapsed: 00:00:03.35

-- Well, much faster 16:54 sec comparing to 28:41 sec

a@OD6I> select index_name, status, tablespace_name from user_indexes
  2  where table_name='DV_ALL_OBJECTS';

INDEX_NAME                     STATUS   TABLESPACE_NAME
------------------------------ -------- ------------------------------
DV_ALL_OBJ_PK                  VALID    SYSTEM

Elapsed: 00:00:00.10

-- Index for PK was created, BUT in default tablespace for user A
-- i.e.'SYSTEM' instead of 'USERS', as it was before disabling PK

          Followup:

use a deferrable constraint -- really nice for when you want to dislable a
primary key from time to time without totally dropping the index:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int, constraint t_pk
primary key (x) deferrable );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t disable constraint t_pk;

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter index t_pk unusable;

Index altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set skip_unusable_indexes=true;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select rownum
from all_users;

46 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter index t_pk rebuild;

Index altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t enable constraint t_pk;

Table altered.

      Rating: 4
Dushan - mind
      the HINT syntax pitfall  October 17, 2002

Reviewer:  andrew  from ca, usa
Just a note that the hint syntax gets ignored unless the "+" is the first
character after the comment.

-- hint used
select /*+ full(emp) */ ename from emp where empno = 7934;
Operation    Object Name    Rows    Bytes    Cost    TQ    In/Out    PStart
PStop

SELECT STATEMENT Hint=CHOOSE        1           1
  TABLE ACCESS FULL    EMP    1      8      1

-- hint ignored
select /* +full(emp) */ ename from emp where empno = 7934;
Operation    Object Name    Rows    Bytes    Cost    TQ    In/Out    PStart
PStop

SELECT STATEMENT Hint=CHOOSE        1           1
  TABLE ACCESS BY INDEX ROWID    EMP    1      8      1
    INDEX UNIQUE SCAN    EMP_PRIMARY_KEY    1

      Rating: 5
Do we need
      unique indexes?  October 18, 2002
Reviewer:
      Dushan
Thanks for response. Nevertheless, some more thoughts.
In case of existing non-unique indexes on column(s) which should be PK, UK, we
can create primary or unique key on this column(s)using index(no matter if
deferrable or non deferrable). The index (even if non-unique) is reused by PK,
or UK. When disabling PK, UK, the index is not dropped, which is cool, much
bigger flexibility comparing to PK+unique index. For me, the strategy for
creating PK, UK's on table seems like :
1) Create non-unique index on PK, UK's column(s)
2) Create PK, UK's using index
So, do we need unique indexes at all? Or, is there any performance cons when
going the way above?

          Followup:

I would

3) use deferrable constraints

which will either kidnap or create a non-unique index for you.

the optimizer understands that the values are unique (given primary key) even if
a unique index is not present.

      Rating:
      3
  May 13, 2003
Reviewer:  Arash

Hi Tom,

   In general what events may cause to have a disable constraint like disable
foreign key ? I know that you can manually disable or enable a constraint. But
what else could disable a foreign key automatically?

Thanks for your time,
Arash

          Followup:

nothing will do it "automagically", you have to ask for it to be done.

      Rating: 4
disabling
      indexes   May 19, 2003
Reviewer:  Lakshmi
       from Bangalore India
Excellent !!!

      Rating: 5
What about a
      LOAD with truncate   September 05, 2003
Reviewer:
      Rich  from Montréal, Canada
Hi Tom,

How would you do a load (conventional way) on a table that have many indexes,
when using a Truncate in the ctl file.  Since the table is truncate, the
unusables indexes are then put back as valid.  Would you DROP/CREATE the
indexes?

          Followup:

truncate the table, set the indexes unusable, load, rebuild...

or look at skip_index_maintenance.

September 05, 2003 Nitin -- Thanks for the question regarding
      "v$lock self join takes time ", version 8.1.7.4

      You Asked
Dear Tom ,

we have automated the process of clearing lock by creating a procedure for it
and running it through dbms_job every 5 minutes.
Following is my procedure :

CREATE OR REPLACE PROCEDURE KILL_LOCKS IS
a varchar2(20);

cursor c is select sid||','||serial# s from v$session
where sid IN(select a.sid blocker
             from v$lock a, v$lock b,v$session s
             where a.block = 1  and b.request > 0
             and a.id1 = b.id1  and a.id2 = b.id2
             and b.sid=s.sid and s.last_call_et>60
            );

begin
for rec in c loop
execute immediate 'alter system kill session '||''''||rec.s||'''';
end loop;
end;
/

the procedure is working fine but it takes lot of time to execute the query
(picked up from  this site only) :
select a.sid blocker
             from v$lock a, v$lock b,v$session s
             where a.block = 1  and b.request > 0
             and a.id1 = b.id1  and a.id2 = b.id2
             and b.sid=s.sid and s.last_call_et>60

when i further drill down it seems there is some problem with the self joins
which is taking lot time and resource.
because my procedure will be running every five minutes or so , this may become
an overhead for my system .

What is the reason that the query takes so much of time and can you please
suggest some alternate query which can give me the same results.

Thanks

      and we said...
wow, this is bizzare.  Kill the patient, instead of curing them.  Really -- i
cannot imagine.

Anyway - v$ queries are hugely expensive to query -- particularly in a
production system.

I would seriously consider NOT ACCESSING V$ TABLES IN PRODUCTION CODE period.

v$ tables are generally memory structures.
v$ tables are not read consistent.
v$ tables require latches to access -- cannot modify and read memory at the same

time.

heavy access to v$ tables like this may cause some serious heavy duty
contention.

You want to make your systems run faster?  turn off all of those "pretty gui's"
that query the v$ tables like mad -- just to show you a blinking red light when
that useful "cache hit ratio" falls below some limit (sarcasm).

Also -- read

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76961/ch3.htm#5014
<quote>
Notes:

    * You can query the dynamic performance views to extract information from
them. However, only simple queries are supported. If sorts, joins, GROUP BY
clauses and the like are needed, you should copy the information from each V$
view into a table (for example, using a CREATE TABLE ... AS SELECT statement),
and then query from those tables.

    * Because the information in the V$ views is dynamic, read consistency is
not guaranteed for SELECT operations on these views.
</quote>

So, you should copy v$lock into a temporary table first, then join.

But anyway -- I would fix the problem, not kill the patient.  Seems a tad HARSH
and totally backwards.  Don't people get mad when you snipe them like this??????

Review &
      Followup

      Rating: 5
What about
      statspack ?  January 18, 2003
Reviewer:  Stefan
      Jaeschke  from Bingen, Germany
Hi Toms,
your answer scared me - I never thought about the described problem with
accessing the v$views in production (and yes - I am using these GUIs).

But what about statspack. Is it OK to take a snapshot every 30 or 15 minutes (in
production) and query the perfstat tables ?

And why is statspack that fast ? I takes less than 2 seconds to take a snapshot.

Thanks for your answer.

Stefan

          Followup:

statspack copies the data into real tables for analysis.

It doesn't query them real time.

statspack pings the system for the duration of the snapshot, most systems can
deal with that once every 15/30 minutes.

the pretty gui's that show you "real time green and red flashing lights" --
well, they are another all together.  really pretty and all -- but if they are
checking out your v$ tables ever N seconds - they themselves could be the
largest cause of contention.

      Rating: 5
extremely
      useful hint  July 07, 2003
Reviewer:  Cid
      Klautau  from Brazil
Tom,

I will never query any V$ table again. The response time is really amazing when
you use a physical copy of a memory structure.
This peace of information is priceless!
I would like to congratulate you and your team for the excelent quality of your
job in this site.

          Followup:

never say never!

      Rating: 3
GV$ takes
      longer  September 05, 2003
Reviewer:  houman
       from Canada
I am trying to convert a version of the query in this note to use GV$ view
instead of V$. I mean from
SELECT
    (SELECT username from v$session where sid=a.sid) blocker,a.sid,(SELECT
module from v$session where sid=a.sid) module,(SELECT action from v$session
where sid=a.sid) action,' is blocking ',
    (SELECT username from v$session where sid=b.sid) blockee,b.sid,(SELECT
module from v$session where sid=b.sid) module,(SELECT action from v$session
where sid=b.sid) action,
    ' Since ',a.ctime FROM v$lock a, v$lock b
WHERE a.block = 1
      and b.request > 0
      and a.id1 = b.id1
      and a.id2 = b.id2;

TO

SELECT
    (SELECT username from gv$session c where sid=a.sid and c.inst_id=a.inst_id )
blocker,a.sid,
    (SELECT module   from gv$session c where sid=a.sid and c.inst_id=a.inst_id )
module,
    (SELECT action   from gv$session c where sid=a.sid and c.inst_id=a.inst_id )
action,' is blocking ',
    (SELECT username from gv$session c where sid=b.sid and c.inst_id=a.inst_id )
blockee,b.sid,
    (SELECT module   from gv$session c where sid=b.sid and c.inst_id=b.inst_id )
module,
    (SELECT action   from gv$session c where sid=b.sid and c.inst_id=b.inst_id )
action,
    ' Since ',a.ctime from gv$lock a, gv$lock b
WHERE a.block = 1
      and b.request > 0
      and a.id1 = b.id1
      and a.id2 = b.id2
      and a.inst_id = b.inst_id;

It almost never comes back to me! what is wrong in this query?

          Followup:

I guess I would remove the scalar subqueries -- see if they were the issue...

September 05, 2003 sean -- Thanks for the question regarding
      "correlated update", version 8.0.6

      You Asked
Tom,
By refer to note 10696.1

In order to avoid the FTS on the target table, unobtemp,I want to change the
correlated update from

UPDATE unobtemp
SET    (tdte, COMMIT)   = (SELECT MIN(ctr.trans_date), SUM(ctr.trans_amount)
                 FROM   commit_trans_reg ctr
                 WHERE  ctr.prac_line_no = unobtemp.line_no
                 AND    ctr.prac_no = unobtemp.doc_no
                 AND    ctr.gl_acct_num LIKE '457%'
                 AND    DECODE(length(:xacpd), 6, ctr.acct_period, 8,
to_char(ctr.trans_date, 'yyyymmdd')) <= :xacpd)
WHERE EXISTS (SELECT 'X'
              FROM   commit_trans_reg ctr
              WHERE  ctr.prac_line_no = unobtemp.line_no
              AND    ctr.prac_no = unobtemp.doc_no
              AND    ctr.gl_acct_num LIKE '457%'
              AND    DECODE(length(:xacpd), 6, ctr.acct_period, 8,
to_char(ctr.trans_date, 'yyyymmdd')) <= :xacpd)
/

To

declare
        cursor source is
        select MIN(trans_date) tr_date
        , SUM(trans_amount) tr_amount
        , prac_line_no
        , prac_no
         from
        commit_trans_reg
        where gl_acct_num LIKE '457%'
        AND DECODE(length(200106), 6, acct_period, 8, to_char(trans_date,
'yyyymmdd'))
                <= '200106';
begin
        for row in source loop
                update unobtemp
                SET    (tdte, COMMIT)  = (row.tr_date, row.tr_amount)
                where row.prac_line_no = unobtemp.line_no
                AND    row.prac_no = unobtemp.doc_no;
        end loop;
exception
 when others then
        null;
end;

And got error:

ERROR at line 15:
ORA-06550: line 15, column 29:
PLS-00103: Encountered the symbol "ROW" when expecting one of the following:
( select
The symbol "select" was substituted for "ROW" to continue.
ORA-06550: line 15, column 55:
PLS-00103: Encountered the symbol ")" when expecting one of the following:
. ( , * @ % & - + / mod rem
 an exponent (**) as
from ||

How to fix it?

TIA

      and we said...
I do believe thats the oldest note i've seen ;)

It would be much better to keep this in SQL in my opinion.  Just change the
EXISTS to IN and it should happen if indexes are in place, consider:

ops$tkyte@ORA817.US.ORACLE.COM> create table unobtemp ( tdte date, commit
number, line_no number, doc_no number );

Table created.

ops$tkyte@ORA817.US.ORACLE.COM> create index unobtemp_idx on
unobtemp(line_no,doc_no);

Index created.

ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> create table commit_trans_reg ( trans_date date,
trans_amount number, prac_line_no number, prac_no number,
  2  gl_acct_num varchar2(25), acct_period number );

Table created.

ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> create index commit_trans_reg_idx on
commit_trans_reg(prac_line_no,prac_no);

Index created.

ops$tkyte@ORA817.US.ORACLE.COM> create index commit_trans_reg_idx2 on
commit_trans_reg(gl_acct_num);

Index created.

ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> variable xacpd varchar2(20)
ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> set autotrace on explain
ops$tkyte@ORA817.US.ORACLE.COM> UPDATE unobtemp
  2  SET    (tdte, COMMIT)      = (SELECT MIN(ctr.trans_date),
SUM(ctr.trans_amount)
  3                   FROM   commit_trans_reg ctr
  4                   WHERE  ctr.prac_line_no = unobtemp.line_no
  5                   AND    ctr.prac_no = unobtemp.doc_no
  6                   AND    ctr.gl_acct_num LIKE '457%'
  7                   AND    DECODE(length(:xacpd), 6, ctr.acct_period, 8,
  8  to_char(ctr.trans_date, 'yyyymmdd')) <= :xacpd)
  9  WHERE EXISTS (SELECT 'X'
 10                FROM   commit_trans_reg ctr
 11                WHERE  ctr.prac_line_no = unobtemp.line_no
 12                AND    ctr.prac_no = unobtemp.doc_no
 13                AND    ctr.gl_acct_num LIKE '457%'
 14                AND    DECODE(length(:xacpd), 6, ctr.acct_period, 8,
 15                                    to_char(ctr.trans_date, 'yyyymmdd')) <=
:xacpd)
 16  /

0 rows updated.

Execution Plan
----------------------------------------------------------
   0      UPDATE STATEMENT Optimizer=CHOOSE
   1    0   UPDATE OF 'UNOBTEMP'
   2    1     FILTER
   3    2       TABLE ACCESS (FULL) OF 'UNOBTEMP'
   4    2       TABLE ACCESS (BY INDEX ROWID) OF 'COMMIT_TRANS_REG'
   5    4         INDEX (RANGE SCAN) OF 'COMMIT_TRANS_REG_IDX' (NON-UNIQUE)
   6    0   SORT (AGGREGATE)
   7    6     TABLE ACCESS (BY INDEX ROWID) OF 'COMMIT_TRANS_REG'
   8    7       INDEX (RANGE SCAN) OF 'COMMIT_TRANS_REG_IDX' (NON-UNIQUE)

ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> UPDATE unobtemp
  2  SET    (tdte, COMMIT)      = (SELECT MIN(ctr.trans_date),
SUM(ctr.trans_amount)
  3                   FROM   commit_trans_reg ctr
  4                   WHERE  ctr.prac_line_no = unobtemp.line_no
  5                   AND    ctr.prac_no = unobtemp.doc_no
  6                   AND    ctr.gl_acct_num LIKE '457%'
  7                   AND    DECODE(length(:xacpd), 6, ctr.acct_period, 8,
  8                                        to_char(ctr.trans_date, 'yyyymmdd'))
<= :xacpd)
  9  WHERE (line_no,doc_no) IN (SELECT prac_line_no, prac_no
 10                               FROM   commit_trans_reg ctr
 11                              where    ctr.gl_acct_num LIKE '457%'
 12                                AND    DECODE(length(:xacpd), 6,
ctr.acct_period, 8,
 13                                            to_char(ctr.trans_date,
'yyyymmdd')) <= :xacpd)
 14  /

0 rows updated.

Execution Plan
----------------------------------------------------------
   0      UPDATE STATEMENT Optimizer=CHOOSE
   1    0   UPDATE OF 'UNOBTEMP'
   2    1     NESTED LOOPS
   3    2       VIEW OF 'VW_NSO_1'
   4    3         SORT (UNIQUE)
   5    4           TABLE ACCESS (BY INDEX ROWID) OF 'COMMIT_TRANS_REG'
   6    5             INDEX (RANGE SCAN) OF 'COMMIT_TRANS_REG_IDX2' (NON-UNIQUE)
   7    2       INDEX (RANGE SCAN) OF 'UNOBTEMP_IDX' (NON-UNIQUE)
   8    0   SORT (AGGREGATE)
   9    8     TABLE ACCESS (BY INDEX ROWID) OF 'COMMIT_TRANS_REG'
  10    9       INDEX (RANGE SCAN) OF 'COMMIT_TRANS_REG_IDX' (NON-UNIQUE)
Review &
      Followup

      Rating: 5
Thanks
      Tom!  August 18, 2001
Reviewer:  Sean
       from Atlanta

      Rating: 4
referencing
      outer table  October 03, 2002
Reviewer:
      A reader
Hi

If I have a correlated update like this

update emp a
set comm = (select avg(sal) from emp b
              where a.deptno = b.deptno);

this works

but if I have

update emp b
   set comm = (select avg(sal)
                 from (select * from emp a
                        where a.deptno = b.deptno));

this doesnt work, any restrictions?

          Followup:

the correlation name B can only go one level down.  the correlated subquery may
ONLY reference its parent query.

      Rating: 5
regarding last
      post  October 11, 2002
Reviewer:
      A reader
Hi

I guess the only way for the last post to work is using PL/SQL right?

          Followup:

no

update emp b
   set comm = (select avg(sal) from emp a where a.deptno = b.deptno )
/

is the same as the last post but it works.

      Rating: 4
Need some help
      regarding Coorelated Update  October 12, 2002

Reviewer:  Riaz Shahid  from PRAL, Islamabad,
      Pakistan
Hello Tom !

I have a table with the following structure:

SQL> desc recovery_info

Name                    Null?    Type
----------------------- -------- ----------------
RECOVERY_NO                      NUMBER(4)
COLLECTORATE_CODE                CHAR(2)
ST_REG_NO                        VARCHAR2(13)
RECOVERD_AMT                     NUMBER(14,2)

and sample data in this table is like :

Rec#      Coll Code   St Reg No     Recovered Amt
----      ---------   ------------  --------------
           03         0311111111111          10000
           03         0311111111111          23000
           03         0311111111111           5000
           01         0101392301528         100000
           05         0592999972455        5600000

I want to assign unique Recovery no to each record w.r.t columns
Collectorate_Code,St_Reg_No. The data (after updation) should look like :

Rec#      Coll Code   St Reg No     Recovered Amt
----      ---------   ------------  --------------
1          03         0311111111111          10000
2          03         0311111111111          23000
3          03         0311111111111           5000
1          01         0101392301528         100000
1          05         0592999972455        5600000

Please advise ...

Regards

Riaz

          Followup:

I would not assign that which is most likely going to change

Also -- I don't see why:

Rec#      Coll Code   St Reg No     Recovered Amt
----      ---------   ------------  --------------
1          03         0311111111111          10000
2          03         0311111111111          23000
3          03         0311111111111           5000

rec# is 23000 I would think either of:

Rec#      Coll Code   St Reg No     Recovered Amt
----      ---------   ------------  --------------
1          03         0311111111111           5000
2          03         0311111111111          10000
3          03         0311111111111          23000

or

Rec#      Coll Code   St Reg No     Recovered Amt
----      ---------   ------------  --------------
1          03         0311111111111          23000
2          03         0311111111111          10000
3          03         0311111111111           5000

would be the ONLY logical way to do it (order by recovered amt desc or asc).  I
mean, you seem to imply that the physical "order" (which doesn't exist!) has
some meaning but it cannot.

When I "needed" this number, I would use the row_number() analytic function to
materialize it:

select
row_number() over (partition by coll_code, st_reg_no order by recoverd_amt)
rec#, coll_code, st_reg_no, recovered_amt
from t;

Period.  A simple insert/update/delete on this table would render the rec# in
there obsolete -- hence this is a classic example of a column that needs to be
computed on the fly (sort of like AGE, you would never store AGE in a table, you
would store date_of_birth and compute AGE on the fly)

      Rating:
      5
Excellent  October 14, 2002
Reviewer:
      Riaz Shahid  from PRAL< Islamabad, Pakistan

      Rating: 5
Update
      error  July 09, 2003
Reviewer:  Martin

This one is no big deal for you. I wish to update the name in the company table
to that in company_temp where the checking account number are the same. What's
wrong ?

(idle)> ed
Wrote file afiedt.buf

  1  update company c
  2  set name=(select t.name
  3                   from company_tmp t
  4*                  where t.ca=c.ca)
(idle)> /
set name=(select t.name
                 *
ERROR at line 2:
ORA-01427: single-row subquery returns more than one row

          Followup:

select t.ca, count(*)
  from company_tmp t
 group by t.ca
 having count(*) > 1;

you have dups in your company_tmp table -- which one did you want Oracle to use?
 that is what it is complaining about.

      Rating: 5
How to optimize
      correlated update?  August 29, 2003
Reviewer:
      Jeff  from Miami
Tom,
Great advice as usual.

I have following query that takes long time to run and never gets more than 3%
of Oracle resources for some reason.

I'm wondering if somehow I can rewrite this and avoid the duplicated query in
the WHERE clause.  Seems like this shouldn't be necessary, if I can get Oracle
to correlate the Select query with the update statement.

UPDATE PGP_PERSON NOLOGGING SET DATE_OF_BIRTH =
(SELECT s.date_of_birth
FROM ods.ship_passenger s, pgp_cruise_history c, pgp_person p
WHERE s.voy_num = c.voy_num
AND s.book_num = c.book_num
AND s.seq_num = c.seq_num
AND c.person_num = p.person_num
AND c.matched_ind IS NOT NULL
AND NVL(s.date_of_birth,TO_DATE('01-JAN-03')) <>
NVL(p.DATE_OF_BIRTH,TO_DATE('01-JAN-03')))

WHERE person_num IN
(SELECT c.person_num FROM pgp_cruise_history c, pgp_person p, ods.ship_passenger
s
WHERE s.voy_num = c.voy_num
AND s.book_num = c.book_num
AND s.seq_num  = c.seq_num
AND c.person_num = p.person_num
AND c.matched_ind IS NOT NULL
AND NVL(s.date_of_birth,TO_DATE('01-JAN-03')) <>
NVL(p.DATE_OF_BIRTH,TO_DATE('01-JAN-03')))

Thanks for your help Tom.
Jeff

          Followup:

i'm wondering what metric you used to compute "3% of Oracle resources"

but anyway....

that nologging -- hope it "makes you feel better", it has no more effect then:

update pgp_person FEEL_BETTER set date_of_birth = .....

it is just a correlation name :)

But -- are you sure you have that update right at all????  the subquery says:

take all of the rows in s, c, p and join them together.
find the none null c.matched_ind rows and keep them.
Then, find me a row where the s.date_of_birth isn't equal to the p.date_of_birth

But -- I see NO relation to the outer table which is aliased as "nologging".
that is -- you have no correlation between the table you are updating and the
rows you are updating with!!!!

I believe the entire update is "wrong" -- i cannot see the circumstances where
it would make sense.

Can you phrase, in english, the requirements here?

      Rating: 5
Correlated
      Update  August 29, 2003
Reviewer:  Jeff
       from Miami
Tom,

Environment:  Oracle 8.1.7, Unix 2 cpu

When I said 3% oracle resources, I think I meant Unix CPU time.  When I run unix
top it never shows greater than 3%.

Also, when this runs, it never spawns parallel queries.

I have the 3 tables s, c, & p.
I want to update p.date_of_birth to s.date_of_birth if they are different.  I
have to go thru c to get to p. That's it!  Period.  Simple stuff right?

I rewrote as PL/SQL, but would prefer single SQL statement:

DECLARE
counter INTEGER;
BEGIN
counter:= 0;

FOR s IN (SELECT s.date_of_birth, c.person_num
    FROM ods.ship_passenger s, pgp_cruise_history c
    WHERE s.voy_num = c.voy_num
    AND s.book_num = c.book_num
    AND s.seq_num = c.seq_num
    AND c.matched_ind IS NOT NULL)

LOOP

UPDATE pgp_person

SET date_of_birth = s.date_of_birth, update_by = 'SDM18649', update_date =
TRUNC(SYSDATE), load_timestamp = SYSDATE

WHERE
      person_num = s.person_num
      AND
      NVL(s.date_of_birth,TO_DATE('01-JAN-03')) <>
NVL(DATE_OF_BIRTH,TO_DATE('01-JAN-03'))
IF SQL%FOUND THEN counter:= counter + 1; END IF;

END LOOP;
dbms_output.put_line ('Total records updated are:  "||TO_CHAR(counter));
END;

This PL/SQL never spawns parallel query, but seems to accomplish what I want.
However, it has been running for 4 hours.

It seems like I should be able to do something like:

UPDATE PGP_PERSON p SET DATE_OF_BIRTH =
(SELECT s.date_of_birth
FROM ods.ship_passenger s, pgp_cruise_history c
WHERE s.voy_num = c.voy_num
AND s.book_num = c.book_num
AND s.seq_num = c.seq_num
AND c.person_num = p.person_num
AND c.matched_ind IS NOT NULL
AND NVL(s.date_of_birth,TO_DATE('01-JAN-03')) <>
NVL(p.DATE_OF_BIRTH,TO_DATE('01-JAN-03')))

But I'm not sure if I need to put a WHERE clause on the outer query or if the
query will be correlated by referencing the p.person num in the subquery, making
the WHERE clause unnecessary.

PS - thanks for the NOLOGGING tip, but I swear my updates go faster when I use
it.

Thanks again!!!!
Jeff

          Followup:

if you have the proper primary keys to assure uniqueness on S and C, this does
it (i have the keys noted in the creates)

ops$tkyte@ORA920> create table pgp_person
  2  ( date_of_birth date,
  3    update_by varchar2(10),
  4    update_date date,
  5    load_timestamp date,
  6    person_num int );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table ship_passenger
  2  ( date_of_birth date,
  3    person_num int,
  4    voy_num int,
  5    book_num int,
  6    seq_num int,
  7    primary key (person_num) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table pgp_cruise_history
  2  ( voy_num int,
  3    book_num int,
  4    seq_num int,
  5    matched_ind int,
  6    primary key(voy_num,book_num,seq_num) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> update ( select p.date_of_birth dob1, s.date_of_birth dob2,
  2                  p.update_by, p.update_date, p.load_timestamp
  3             from pgp_person p, pgp_cruise_history c, ship_passenger s
  4                    where p.person_num = s.person_num
  5                      and s.voy_num = c.voy_num
  6                          and s.book_num = c.book_num
  7                          and s.seq_num = c.seq_num
  8                          and c.matched_ind is not null
  9                          and s.date_of_birth <> p.date_of_birth )
 10    set dob1 = dob2,
 11        update_by = 'sdm18649',
 12            update_date = trunc(sysdate),
 13            load_timestamp = sysdate;

0 rows updated.

      Rating: 5
advice on
      approach  September 03, 2003
Reviewer:
      A reader
Hi tom
Thanx for the great site and your generosity as usual!
I have the following situation (with data):
------------
drop table t1;
create table t1 as
select 't1_' || rownum name, 'TYPE' type, 't1_'||sys_guid() id
from all_objects
where rownum <= 10;

drop table t2;
create table t2 as
select 't1_' || rownum name, 'TYPE' type, 't2_'||sys_guid() id
from all_objects
where rownum <= 5;

insert into t2 values ( 't2_1', 'TYPE', 't2_'||sys_guid());

drop table t11;
drop table t12;
drop table t13;
drop table t14;
drop table t15;

create table t11 as
select * from t1
union select * from t2;
create table t12 as
select * from t1
union select * from t2;
create table t13 as
select * from t1
union select * from t2;
create table t14 as
select * from t1
union select * from t2;
create table t15 as
select * from t1
union select * from t2;

/*
Step 1:
 now we need to select those records from t2 for which name
and type don't have a match in t1.

*/
select id, name, type
from t2
where (name, type) not in
(
  select b.name, b.type
  from t1 b
);

/*
Step 2:
    for each of these records -we need to update 5 tables
   t11, t12, t13, t14, t15  set their id to
   the id from t2 selected above concatenated with a string
   'NEW' for matching name and type.
*/
  update t11
   set id = (select 'NEW'||id from t2
             where name = :name
               and type = :type);

  update t12
   set id = (select id from t2
             where name = :name
               and type = :type);

and so on.. where :name and :type are names and types selected in step 1 above.
For any of the given tables
there may be more than one records for the same name,type
in which case all the id's of all records need to be
changed as specified ((I realize that the corelated update as shown will not
work in such cases.)

My question is : is there a better way to do the above
as compared to the outlined solution. In particular,
can we do the update in step1 directly in statement
instead of in a loop as I am doing?

Thanx!

          Followup:

don't see how the updates work -- they update every row in the table?

but you can just push the subquery straight in there

update t12 set id = ( select id from t2 where (name,type) in ( select YOUR
SUBQUERY ) )

      Rating: 5
thanx
      Tom!  September 04, 2003
Reviewer:
      A reader
"don't see how the updates work -- they update every row in the table?"
update t12
   set id = (select id from t2
             where name = :name
               and type = :type);

So in t12, we need to update to set any id for a row
that has the same name and type column values for the rows
that get selected from the corelated subquery.

So, I think the update should look like:

update t12
   set id = (select id from t2
             where name = :name
               and type = :type)
where (t12.name, t12.type) = ( t2.name, t2.type);

Sorry - haven't run this one yet - am testing it as we
speak.

So the question is (with my answers):

1. Should I use dynamic sql cos the similar query will
   be written but for different tables.

   Answer: NO - static is faster than dynamic and list
   of tables is not likely to change . However, the code
   would look a little ugly with similar updates
   for different tables.
2. It seems that you imply that I should put the corelated
   subquery inside each update  instead of running it
  once and running individual updates for each record,
  correct?

Thanx!

          Followup:

2) correct

      Rating:
      5
thanx!  September 05, 2003
Reviewer:
      A reader
I am having a little trouble writing this.
Say my loop is:
--
for rec in ( select a, b, c
             from source_table
           )
loop

  update destination_table s
  set s.a = rec.a
  where s.a =
  (
    select a
    from table1
    where b = rec.b
  );

end loop;

--

how would you remove the loop and do this in a single
update?

Thank you so much!!

          Followup:

confusing choices of aliases -- assuming we look at:

for st in ( select a, b, c
             from source_table
           )
loop

  update destination_table dt
  set dt.a = st.a
  where dt.a =
  (
    select t1.a
    from table1
    where t1.b = st.b
  );

end loop;

instead (st = source table, dt = dest table, t1 = table1)...

We can derive from this:

a) c is not meaningful....  never used...

b) source_table.a BETTER be a primary key or two runs of this could result in a
different result (eg: A better be unique in source_table, else this is *wrong*
logic)

c) b in table1 must be unique as well -- else the subquery would return more
then 1 row sometimes....

So, we can create:

drop table st;
create table st ( a int primary key, b int );

drop table dt;
create table dt ( a int );

drop table t1;
create table t1 ( a int , b int primary key );

Now, all we need to do is join DT to ST by A.  and then ST to T1 by B and DT to
T1 by A....

update(
select dt.a dt_a, st.a st_a
  from dt, st, t1
 where dt.a = st.a
   and dt.a = t1.a
   and st.b = t1.b )
set dt_a = st_a;

pretty sure that does it....

      Rating: 5
thanx
      Tom!!!!  September 05, 2003
Reviewer:
      A reader
That should work though I did not get a chance to
test it out - I think the conditions that you said
should be true are true - so I should be all set.

Thanx again!!!!

September 05, 2003 Murali -- Thanks for the question regarding
      "Question in DYNAMIC SQL", version 8.1.7

      You Asked
Good Morning Tom

When I run the following querry with passing of variable l_start_date I am
getting an error, If I dont pass l_start_Date
as an parameter instead if I hardcode with some value it works.

I think the way in which I am passing l_start_date as variable in l_ap_sql is
wrong.
I dont know where I am doing wrong

ERROR at line 1:
ORA-00904: invalid column name
ORA-06512: at line

What I am looking is If User passes l_Start_date and l_end_date as parameters it
should give
the count in the table ( AP_INVOICES_INTERFACE ) for those dates,  otherwise it
should give count for all the dates

=================================================================
SET SERVEROUTPUT ON SIZE 1000000

DECLARE

    l_site               VARCHAR2(5)     :=  '&P_SITE'       ;
    l_start_date         DATE            :=  '&P_START_DATE' ;
    show_my_link         VARCHAR2(50)                        ;
    l_ap_invoices        NUMBER          := 0                ;

BEGIN

      show_my_link  := get_db_link(l_site) ;

    l_ap_sql := 'SELECT COUNT(*) FROM ap_invoices_interface'||'@'||show_my_link
               ||' '||'WHERE global_attribute18 IS NULL '|| '( AND creation_date
 between  ' || l_start_date || 'AND'|| l_end_date||

  ' OR '|| l_start_date IS NULL || )';

            EXECUTE IMMEDIATE (l_ap_sql )      INTO l_ap_invoices      ;

      dbms_output.Put_line(' The Number of AP Invoices  IS : '||
(l_ap_invoices));
END;

 ===============================================================

Thanking you in advance

Murali

      and we said...
You need to use BIND VARIABLES!!!! so very very very very important.

Never never never glue values in like that.

l_query :=
'select count(*)
   from ap_invoices_interface@' || show_my_link || '
  where global_attribute18 is null';

if ( l_start_date is NOT NULL )
then
  l_query := l_query || ' and creation_date between :x and :y';
else
  l_query := l_query || ' and (:x is null and :y is null)';
end;

execute immediate l_query into l_ap_invoices using l_start_date, l_end_date;

Review &
      Followup

      Rating:
      3
good  October 28, 2002
Reviewer:
      santosh
it is very useful to me and my students also please send free books and
.......querrys

      Rating: 4
what about
      dynamic where clause?  November 14, 2002
Reviewer:
      Sarah  from UK
Tom,
This example assumed that the where clause always uses the same bind variables.
What would you do if the where clause in this query is not always the same?

I want to be able to add it extra parts to the where clause depending on the
user search criteria.
I have your book and I've got an example using SYS_CONTEXT working against a
partitioned table. When I try to do this against a remote table using a db_link
the query is trying to look at all partitions.

An example is:
INSERT /*+ APPEND */ INTO local_search_results
(SEARCH_KEY,
 test1,
 test2)
SELECT
 SYS_CONTEXT('CTX_TRANS_SEARCH', 'pv_search_key'),
 test1,
 test2
 FROM view_on_big_partitioned_table@remote.world
 WHERE
     partition_field >= TO_DATE(SYS_CONTEXT('CTX_TRANS_SEARCH', 'pd_date_from'),
'YYYYMMDD HH24MISS')
 AND partition_field < TO_DATE(SYS_CONTEXT('CTX_TRANS_SEARCH', 'pd_date_to'),
'YYYYMMDD HH24MISS')
 AND test_amount >= TO_NUMBER(SYS_CONTEXT('CTX_TRANS_SEARCH', 'pi_test_amount'))
 AND ROWNUM <= TO_NUMBER(SYS_CONTEXT('CTX_TRANS_SEARCH', 'pi_get_rows'))

Is this the best way to do it?
Thanks you your help

          Followup:

two things

o partition elimination is still happening - it is just that the PLAN cannot
reflect exactly WHICH ones will be eliminated.  It is only looking at the data
it needs to

o if you use literals -- the plan will reflect exactly what partitions are used
since it can definitely tell at parse times.

So, here is an idea, let me say this (hold onto your hats)

insert /*+ append */

that indicates to me that this query is not executed over and over and over and
over.  Maybe -- once a day, once a year, once a month.  It is a mass load --
infrequent.

So, maybe -- in this case -- you can get away without using binds - literals
would be ok.

When you do the same query hundreds/thousands of times a day -- BINDS ARE A
MUST.

When you do a query once a day or even less -- BINDS are something to consider.

      Rating: 3
Thanks
        November 15, 2002
Reviewer:  Sarah
       from UK
Tom,
Thanks for the quick reply.
This query is repeated many times a day/minute so I think I will need bind
variables (but I will take out the /*+ APPEND */ hint).
The only way I can think to do it is as in your first suggestion calling one of
a set of 'execute immediate' queries depending on the number of values I've
built up.
If you can think of a cleaner way, that would be a big help.
Thanks

      Rating: 4
How can I use
      bind variable in dates like these  August 29, 2003

Reviewer:  JAMES
IF p_date_created_from <> ' ' and p_date_created_to <> ' ' then

        str := str || ' and trunc(batch_apis.date_created) between''' ||
v_date_created_from || '''';
       str := str || ' and ''' || v_date_created_to || '''';

  END IF;

          Followup:

hmm, that should be:

  if ( p_date_created_from is not null and p_date_created_to is not null )
  then
     str := str || ' and batch_apis.date_created
                      between :x and trunc(:y)+1-1/24/60/60';
  end if;

and then you open .... using p_date_created_from, p_date_created_to

if you cannot explicitly bind, use this technique:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279
using an application context

      Rating: 4
Please
      see  August 29, 2003
Reviewer:
      A reader
if ( p_hiredate is NOT NULL ) then
           dbms_session.set_context( 'MY_CTX', 'HIREDATE',
                     to_char(p_hiredate,'yyyymmddhh24miss'));
          l_query := l_query ||
                 ' and hiredate >
                    to_date(
                       sys_context( ''MY_CTX'',
                                    ''HIREDATE'' ),
                        ''yyyymmddhh24miss'') ';
       end if;

but when I put the key word between is not working for me.

          Followup:

'not working for me' doesn't tell us very much.

between works great - assuming you build a VALID query...

      Rating: 4
still having
      problem using appication context in this dynamic
      sql  September 03, 2003
Reviewer:  mike
       from new york
  str :=  str || ' and batch_ccs.date_of_manuf between ''' ||
v_date_of_manuf_from|| '''';

  str := str || 'and ''' || v_date_of_manuf_to|| '''';

          Followup:

well, i don't see any application context, i don't see any error.

i do see you NOT using binds which is really bad.

maybe if you give us a complete, concise example (concise being as important as
complete) -- maybe we can comment.

      Rating: 4
I am trying to
      use app context to get a range of dates  September 03, 2003

Reviewer:  A reader
dbms_session.set_context( 'BATCHAPI_CTX',
'batch_apis.date_of_manuf',to_char(v_date_of_manuf_to,'mm/dd/yyyy'));

            str := str ||' and batch_apis.date_of_manuf => to_date(sys_context(
''BATCHAPI_CTX'', ''batch_apis.date_of_manuf'' ),''mm/dd/yyyy'') ';

            str := str ||' and batch_apis.date_of_manuf <= to_date(sys_context(
''BATCHAPI_CTX'', ''batch_apis.date_of_manuf'' ),''mm/dd/yyyy'') ';

          Followup:

well, your logic boils down to:

and COLUMN >= constant and COLUMN <= constant

which is the same as

and COLUMN = constant

is that what you meant to program -- a simple, concise -- yet complete example
would be something against say dual or EMP and would be something we call can
run in sqlplus ourselves.

I still have no idea WHAT the issue you have is -- but perhaps it is this error
in logic here.

      Rating: 1
this is the
      eror message  September 03, 2003
Reviewer:
      A reader
ORA-00936: missing expression ORA-06512: at "ENOTEBOOK.BATCH_API_JAVA_PKG", line
384 ORA-06512: at line 1

f date_of_manuf_from and date_of_manuf_to then
             dbms_session.set_context( 'BATCHAPI_CTX',
'batch_apis.date_of_manuf',to_char(v_date_of_manuf_from,'mm/dd/yyyy'));
            str := str ||' and date_of_manuf_from => date_of_manuf_from
to_date(sys_context( ''BATCHAPI_CTX'', ''date_of_manuf_from'' ),''mm/dd/yyyy'')
';
            str := str ||' and date_of_manuf_to < date_of_manuf_to
to_date(sys_context( ''BATCHAPI_CTX'', ''date_of_manuf_to'' ),''mm/dd/yyyy'') ';

      end if;

          Followup:

cut your example down.

find out what quote you miskakenly didn't put in there.  It could be many lines
up from this one.

also, the sql you have here, which is totally different from the last sql above
bear in mind, is not syntactically valid at all -- look at it.

it would generate:

 ... and COLUMN => COLUMN to_date( CONSTANT )

what is that?

      Rating: 3
FOLLOW
      UP  September 04, 2003
Reviewer:  MIKE

Hi tom,

ALL I WANT TO DO WITH THIS DYNAMIC SQL IS TO USE APPLICATION CONTEXT TO TAKE
ADVANTAGE OF BINDING.AND I AM HAVING A TOUGH TIME SELECTING BETWEEN 2 DATES.
WOULD YOU PLEASE LET ME KNOW WHAT AM I DOING WRONG??

  if date_of_manuf_from and date_of_manuf_to then

         dbms_session.set_context( 'BATCHAPI_CTX',
'batch_apis.date_of_manuf',to_char(v_date_of_manuf_from,'mm/dd/yyyy'));
            str := str ||' and date_of_manuf_from => date_of_manuf_from
to_date(sys_context(''BATCHAPI_CTX'',             ''date_of_manuf_from''
),''mm/dd/yyyy'') ';

            str := str ||' and date_of_manuf_to <= date_of_manuf_to
to_date(sys_context( ''BATCHAPI_CTX'',
''date_of_manuf_to'' ),''mm/dd/yyyy'') ';

end if

          Followup:

i've tried to say it over and over -- your sql is totally meaningless.  stop,
take a look at it.  see what string you are building.  think about what string
you WANT to be building.

your sql is not even sql.

you keep saying 'between two dates', but i only ever see a single date.

at least this time, you have two different dates you are looking at in the
table.  but look -- just look -- at your SQL

... and date_of_manuf_from => date_of_manuf_from to_date( sys_context(
'batchapi_ctx', 'date_of_munf_from' ), 'mm/dd/yyyy' ) ....

what kind of SQL is that??????

here is a short, concise, yet complete (eg: you too can run this) example of
what I believe you are trying to do.  building a test case is a very good thing
when you hit a problem.  999 times out of 1000 i find my own bug in the
development of a simple test case.

ops$tkyte@ORA920LAP> create table t ( x int, startd date, endd date );

Table created.

ops$tkyte@ORA920LAP> insert into t values ( 1, sysdate-5, sysdate+5 );

1 row created.

ops$tkyte@ORA920LAP> insert into t values ( 2, sysdate-50, sysdate-45 );

1 row created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create or replace package demo_pkg
  2  as
  3      type rc is ref cursor;
  4
  5      procedure p( p_cursor in out rc,
  6                   p_date   in date );
  7  end;
  8  /

Package created.

ops$tkyte@ORA920LAP> create or replace package body demo_pkg
  2  as
  3      procedure p( p_cursor in out rc,
  4                   p_date   in date )
  5      is
  6          l_str long;
  7      begin
  8          dbms_session.set_context( 'my_ctx', 'the_date',
to_char(p_date,'yyyymmdd') );
  9
 10          l_str := 'select * from t where 1=1 ';
 11
 12          l_str := l_str ||
 13          ' and startd <=
to_date(sys_context(''my_ctx'',''the_date''),''yyyymmdd'' )
 14            and endd >=
to_date(sys_context(''my_ctx'',''the_date''),''yyyymmdd'' )';
 15
 16          open p_cursor for l_str;
 17      end;
 18  end;
 19  /

Package body created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> variable x refcursor
ops$tkyte@ORA920LAP> exec demo_pkg.p( :x, sysdate );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> print x

         X STARTD               ENDD
---------- -------------------- --------------------
         1 30-aug-2003 10:12:54 09-sep-2003 10:12:54

      Rating:
      4
Thanks  September 04, 2003
Reviewer:
      mike
Hi tom,

I understand now your example. I noticed you are using
2 fields to do your search in between dates. however, the developer as I
undersand it now, is using ONE field(batch_apis.date_of_manuf) to do his range
search. Maybe we can't apply application context in this situation.

Thanks very much for your effort and patience.

          Followup:

tell me please -- how a single date can be used for a "range"

application context OR NOT -- if you have a single date -- explain to me "range"

if you can hard code it, I can bind it.

      Rating: 3
I am sending
      you the whole thing the part I am having a problem is with
        September 05, 2003
Reviewer:
      A reader
the part that I am having problem is
if date_of_manuf_from and date_of_manuf_to then.....

function view_log (
      p_batch                       in   varchar2 default null,
      p_name                        in   varchar2 default null,
      p_coa                         in   varchar2 default null,
      p_batch_size                  in   varchar2 default null,
      p_batch_size_units            in   varchar2 default null,
      p_manufacturer                in   varchar2 default null,
      p_date_of_manuf_from          in   varchar2 default null,
      p_date_of_manuf_to            in   varchar2 default null,
      p_manuf_batch_no              in   varchar2 default null,
      p_description                 in   varchar2 default null,
      p_comments                    in   varchar2 default null,
      p_received_date_from          in   varchar2 default null,
      p_received_date_to            in   varchar2 default null,
      p_received_location           in   varchar2 default null,
      p_updated                     in   varchar2 default null,
      p_created_by                  in   varchar2 default null,
      p_date_created_from           in   varchar2 default null,
      p_date_created_to             in   varchar2 default null,
      p_order                       in   varchar2 default null
   )
      return cursortype_api
   is
      v_log                     cursortype_api;
      str                       varchar2 (3000);
      v_order                   varchar2 (50);
      v_date_of_manuf_from      date := to_date (p_date_of_manuf_from,
'mm/dd/yyyy');
      v_date_of_manuf_to        date := to_date (p_date_of_manuf_to,
'mm/dd/yyyy');
      v_received_date_from      date := to_date (p_received_date_from,
'mm/dd/yyyy');
      v_received_date_to        date := to_date (p_received_date_to,
'mm/dd/yyyy');
      v_date_created_from       date := to_date (p_date_created_from,
'mm/dd/yyyy');
      v_date_created_to         date := to_date (p_date_created_to,
'mm/dd/yyyy');

      ---------- CONDITIONS FOR WHERE CLAUSE -----------------------------------
      updated                   constant boolean       default p_updated  <> '
';
      created_by                constant boolean       default p_created_by  <>
' ';
      batch                     constant boolean       default p_batch <> ' ';
      name                      constant boolean       default p_name <> ' ';
      coa                       constant boolean       default p_coa <> ' ';
      batch_size                constant boolean       default p_batch_size <> '
';
      batch_size_units          constant boolean       default
p_batch_size_units <> ' ';
      manufacturer              constant boolean       default p_manufacturer <>
' ';
      manuf_batch_no            constant boolean       default p_manuf_batch_no
<> ' ';
      description               constant boolean       default p_description <>
' ';
      received_location         constant boolean       default
p_received_location <> ' ';
      comments                  constant boolean       default p_comments <> '
';
      date_of_manuf_from        constant boolean       default
p_date_of_manuf_from <> ' ';
      date_of_manuf_to          constant boolean       default
p_date_of_manuf_to <> ' ';
      received_date_from        constant boolean       default
p_received_date_from <> ' ';
      received_date_to          constant boolean       default
p_received_date_to <> ' ';
      date_created_from         constant boolean       default
p_date_created_from <> ' ';
      date_created_to           constant boolean       default p_date_created_to
<> ' ';

   BEGIN
      ----building select statement--------------
      str :=
         'Select
            batch_apis.ID,
            batch_apis.batch BATCH,
            components_lkup.component_name NAME,
            nvl(batch_apis.coa,''.'') COA,
            nvl(batch_apis.batch_size,''.'') BATCH_SIZE,
            nvl(batch_apis.batch_size_units,''.'') BATCH_SIZE_UNITS,
            manufacturer_lkup.name MANUF,
            nvl(to_char(batch_apis.date_of_manuf, ''mm/dd/yyyy''),''.'')
DATE_OF_MANUF,
            nvl(batch_apis.manuf_batch_no,''.'') MANUF_BATCH_NO,
            nvl(batch_apis.description,''.'') DESCRIPTION,
            nvl(to_char(batch_apis.received_date, ''mm/dd/yyyy''),''.'')
RECEIVED_DATE,
            nvl(locations_lkup.location,''.'') RECEIVED_LOCATION,
            nvl(batch_apis.comments,''.'') COMMENTS,
            batch_apis.updated,
            columns_java_pkg.get_chemist_name_by_usid(batch_apis.created_by)
CREATED_BY,
            to_char(batch_apis.date_created, ''mm/dd/yyyy hh24:mi:ss'')
CREATED_DATE';

      ---------define table(s) to select from
------------------------------------
      str := str || ' from batch_apis, manufacturer_lkup, products,
                      locations_lkup, components_lkup';

      ----------define where
conditions-------------------------------------------
      str := str || ' where batch_apis.record_status = ''CURRENT''';
      str := str || '   and product_fk_id            = products.id(+)';
      str := str || '   and manufacturer_fk_id       = manufacturer_lkup.id(+)';
      str := str || '   and received_location_fk_id  = locations_lkup.id(+)';
      str := str || '   and components_lkup.id       =
products.component_fk_id';

      if updated then
        dbms_session.set_context( 'BATCHAPI_CTX',
'batch_apis.updated','%'||upper (p_updated)||'%');
        str := str || ' and upper(batch_apis.updated)  like sys_context(
''BATCHAPI_CTX'', ''batch_apis.updated'' )';
      end if;

      if created_by then
        dbms_session.set_context( 'BATCHAPI_CTX',
'batch_apis.created_by','%'||upper (p_created_by)||'%');
        str := str || ' and upper(batch_apis.created_by)  like sys_context(
''BATCHAPI_CTX'', ''batch_apis.created_by'' )';
      end if;

     -- if date_created_from and date_created_to then
     --   dbms_session.set_context( 'BATCHAPI_CTX',
'batch_apis.date_created',to_char(v_date_created_from ,'mm/dd/yyyy'));
     --   str := str ||' and batch_apis.date_created >= to_date(sys_context(
''BATCHAPI_CTX'', ''batch_apis.date_created'' ),''mm/dd/yyyy'') ';
     --   dbms_session.set_context( 'BATCHAPI_CTX',
'batch_apis.date_created',to_char(v_date_created_to,'mm/dd/yyyy'));
     --   str := str ||' and batch_apis.date_created <= to_date(sys_context(
''BATCHAPI_CTX'', ''batch_apis.date_created'' ),''mm/dd/yyyy'') ';
     -- end if;

      if batch then
        dbms_session.set_context( 'BATCHAPI_CTX', 'batch','%'||p_batch||'%');
        str := str || ' and batch like sys_context( ''BATCHAPI_CTX'', ''batch''
)';
      end if;

      if name then
        dbms_session.set_context( 'BATCHAPI_CTX',
'component_name','%'||upper(p_name)||'%');
        str := str || ' and upper(component_name)like sys_context(
''BATCHAPI_CTX'', ''component_name'' )';
      end if;

      if coa then
        dbms_session.set_context( 'BATCHAPI_CTX',
'batch_apis.coa','%'||upper(p_coa)||'%');
        str := str || ' and upper(batch_apis.coa)like sys_context(
''BATCHAPI_CTX'', ''batch_apis.coa'' )';
      end if;

      if batch_size then
        dbms_session.set_context( 'BATCHAPI_CTX',
'batch_apis.batch_size','%'||upper(p_batch_size)||'%');
        str := str || ' and upper(batch_apis.batch_size)like sys_context(
''BATCHAPI_CTX'', ''batch_apis.batch_size'' )';
      end if;

      if batch_size_units then
        dbms_session.set_context( 'BATCHAPI_CTX',
'batch_apis.batch_size_units','%'||upper(p_batch_size_units)||'%');
        str := str || ' and upper(batch_apis.batch_size_units)like sys_context(
''BATCHAPI_CTX'', ''batch_apis.batch_size_units'' )';
      end if;

      if manufacturer then
        dbms_session.set_context( 'BATCHAPI_CTX',
'manufacturer_lkup.name','%'||upper(p_manufacturer)||'%');
        str := str || ' and upper(manufacturer_lkup.name)like sys_context(
''BATCHAPI_CTX'', ''manufacturer_lkup.name'' )';
      end if;

      if manuf_batch_no then
        dbms_session.set_context( 'BATCHAPI_CTX',
'batch_apis.manuf_batch_no','%'||upper(p_manuf_batch_no)||'%');
        str := str || ' and upper(batch_apis.manuf_batch_no)like sys_context(
''BATCHAPI_CTX'', ''batch_apis.manuf_batch_no'' )';
      end if;

      if description then
        dbms_session.set_context( 'BATCHAPI_CTX',
'batch_apis.description','%'||upper(p_description)||'%');
        str := str || ' and upper(batch_apis.description)like sys_context(
''BATCHAPI_CTX'', ''batch_apis.description'' )';
      end if;

      if received_location then
        dbms_session.set_context( 'BATCHAPI_CTX',
'locations_lkup.location','%'||upper(p_received_location)||'%');
        str := str || ' and upper(locations_lkup.location)like sys_context(
''BATCHAPI_CTX'', ''locations_lkup.location'' )';
      end if;

      if comments then
        dbms_session.set_context( 'BATCHAPI_CTX',
'batch_apis.comments','%'||upper(p_comments)||'%');
        str := str || ' and upper(batch_apis.comments)like sys_context(
''BATCHAPI_CTX'', ''batch_apis.comments'' )';
      end if;

      if date_of_manuf_from and date_of_manuf_to then

  dbms_session.set_context( 'BATCHAPI_CTX',
'batch_apis.date_of_manuf',to_char(v_date_of_manuf_from,'mm/dd/yyyy'));
  dbms_session.set_context( 'BATCHAPI_CTX',
'batch_apis.date_of_manuf',to_char(v_date_of_manuf_to,'mm/dd/yyyy'));
  str := str || ' and date_of_manuf BETWEEN
to_date(sys_context(''BATCHAPI_CTX'',''date_of_manuf_from''),''mm/dd/yyyy'')
                 and
to_date(sys_context(''BATCHAPI_CTX'',''date_of_manuf_to''),''mm/dd/yyyy'' )';

      end if;

     -- if received_date_from and received_date_to then
     --   dbms_session.set_context( 'BATCHAPI_CTX',
'batch_apis.received_date',to_char(v_date_of_manuf,'mm/dd/yyyy'));
     --   str := str ||' and batch_apis.received_date >= to_date(sys_context(
''BATCHAPI_CTX'', ''batch_apis.received_date'' ),''mm/dd/yyyy'') ';
     --   dbms_session.set_context( 'BATCHAPI_CTX',
'batch_apis.received_date',to_char(v_received_date_to,'mm/dd/yyyy'));
     --   str := str ||' and batch_apis.received_date <= to_date(sys_context(
''BATCHAPI_CTX'', ''batch_apis.received_date'' ),''mm/dd/yyyy'') ';
     -- end if;

          Followup:

that is neither

a) small and concise

b) fully runnable on my system.

nor, does it really tell me "what the heck the problem is"

at this point, I can really only throw my hands up and say "I give up".

we won't get anywhere here, apparently. the concept here is pretty simple "build
a sql string that runs".  sys_context makes this no harder, nay -- it makes it
easier -- then hard coding literals.

I've given examples, I don't know what else to do.  sorry.

September 06, 2003 Allen -- Thanks for the question regarding "Bind
      Variables / Cursor Sharing with Multiple Table Join", version 8.1.7

      You Asked
I found the following quote on dbasupport.com - "Unfortunately, the cache
buffering is applied only to simple tables; multiple table queries and joins are
never cached".

Does this mean that bind variables have no performance benefits when used with
multi-table joins?

      and we said...
Read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4951966319022
That article is so riddled with errors, so badly put together, so wrong in its
advice that the only thing I can say is "ignore its very existence on this
planet".

I have sent an email to webmaster@dbasupport.com with my point by point rebut to
many of the "tips" there asking them if they don't think they might want to take
it down.
Review
      & Followup

      Rating: 5
I should've
      known!  September 04, 2002
Reviewer:  Allen
       from Melbourne, Australia
Thank you for saving us from bogus information!

      Rating: 5
Good
      news  September 17, 2002
Reviewer:  Kalita

If you go to dbasupport now you will see this
Note: The Performance Tuning series is unavailable at this time...we expect it
to return soon.

Hopefully they won't come back with the same set of
tips ::-))

          Followup:

Thanks -- they did take it down.

      Rating: 5
Table Joins vs.
      Bind Variables  September 05, 2003
Reviewer:
      A reader
Hi Tom, say I need to make a join of multiple tables on a common column with a
specific value. Is there any difference by joining all columns or just make each
of them joined to the bind variable?

For example:

t1.col1 = t2.col1 and t2.col1 = t3.col1 and t3.col1 = :b1

or

t1.col1 = :b1 and t2.col1 = :b1 and t3.col1 = :b1

          Followup:

in general, it'll be the "same" -- transitivity and all.

September 06, 2003 -- Thanks for the question regarding "max",
      version 9i

      You Asked
Tom:

How are you.

I just submitted a question, but i guess it is not clear enough.

what I am asking is:

I run a complicate query which return a lot of rows, and I only want one row
which has max value out of the set.

for example.
complicate query returns:
table(a,b)
1,1
1,2
4,5
100,55
.....

i want to return row (100,55,..) because 100 is the max.

what i did now is use cursor.
order the query by column a.
and return the first row and discard the rest.
but i guess it is not good because order by will sort every row which is not
good.
so is there any better way to do that?

Thansk

      and we said...
i'm at a loss to understand your first comment about "i just submitted a
quesiton

select *
 from ( YOUR_COMPLEX_QUERY WITH ORDER_BY_DESCENDING_STMT )
where rownum = 1;

think about it -- in order to find the max, you need to sort of look at every
row.  With the top-n query above however, we have an optimization such that we
realize you want only 1 row -- so we set up an "array" of size 1 and in order to
"sort", we just put the first row of the query in this array and then we get the
second row -- if it is greater then the already existing one -- it goes out and
that goes in.

Instead of building an entire sorted result set -- and then getting the first
row, this just keeps the "first row" -- much more efficient then using a cursor
with open/fetch/close -- that assumes you want the last row as much as the first
row.

Here is an example, I artifically constrained the sort area size just to show
the effects here.  Big table is a copy of all objects with 1.8 million rows in
it.  I ran this:

alter session set workarea_size_policy = manual;
alter session set sort_area_size = 65536;

                            alter session set events '10046 trace name context
forever, level 12';

select *
  from ( select object_name, object_id
           from big_table
          order by object_id desc )
 where rownum = 1;

declare
    cursor c is select object_name, object_id from big_table order by object_id
desc;

    l_rec c%rowtype;
begin
    open c;
    fetch c into l_rec;
    close c;
end;
/

arguably -- those two return the SAME result right?  but look at the
performance:

********************************************************************************
select *
  from ( select object_name, object_id
           from big_table
          order by object_id desc )
 where rownum = 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      7.53       9.42      23350      23369          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      7.53       9.42      23350      23369          0           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  COUNT STOPKEY (cr=23369 r=23350 w=0 time=9422142 us)
      1   VIEW  (cr=23369 r=23350 w=0 time=9422127 us)
      1    SORT ORDER BY STOPKEY (cr=23369 r=23350 w=0 time=9422120 us)
1833792     TABLE ACCESS FULL BIG_TABLE (cr=23369 r=23350 w=0 time=6009265 us)
********************************************************************************
SELECT object_name, object_id
from
 big_table order by object_id desc

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      2      0.00       0.01          0          0          0           0
Fetch        1     20.06      59.96      71383      23369       4006           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4     20.07      59.97      71383      23369       4006           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT ORDER BY (cr=23369 r=71383 w=56340 time=59964614 us)
1833792   TABLE ACCESS FULL BIG_TABLE (cr=23369 r=23357 w=0 time=4171363 us)

Note how the second query had writes -- w=NNNN counts.

it used temp -- it sorted the ENTIRE result set and then returned the first row.
 The other one just looked at it row by row -- keeping only the single row that
was "biggest"

Note:  this is a 9i and up optimization -- you might not see the same
effects before that.

Review &
      Followup

      Rating: 5
*******I KNOW
      YOU HAVE BEST ANSWERS*******  September 06, 2003

Reviewer:  jim
THANKS A LOT

September 06, 2003 -- Thanks for the question regarding "second
      max", version 9i

      You Asked
table (a int, b int);
(1)
what to know the row where a is max
for example:

1,1
2,3
5,6
8,9

return 8,9

(2)
want to return second biggest.

for example
return 5,6

how to write query so it runs fast

      and we said...
This is just a pagination query:

select *
  from ( select a.*, rownum r
           from ( YOUR_QUERY_HERE_WITH_FIRST_ROWS_HINT_AND_ORDER_BY )
          where rownum <= :max_row )
 where r >= :min_row

if you want the second row, set max=min=2.
September 06, 2003 Francisco -- Thanks for the question regarding
      "Sql*Loader problem", version 8.1.7.4

      You Asked
Hi Tom.

I am having problems to load the next record:

more FAMA.dat
Fac.SQLL|1|DESCRIPCIÓN SQL|31-DEC-02|2002/12/01
00:00:00|1000|0|0|5|1063|32|1000|31-DEC-02||||2|||||||||||||||1|2|3|4|5|6|7|Y
ES|A9|A10|A11|A12|13|14|YES|0|A17|11|12|12|112|12|YES|A24|A25|A26|A27|21|21|21

Part of my control file looks like:

LOAD DATA
APPEND
INTO TABLE FA_MASS_ADDITIONS
FIELDS TERMINATED BY '|'
TRAILING NULLCOLS
(
INVOICE_NUMBER CHAR,
FIXED_ASSETS_UNITS      CONSTANT        '1',
DESCRIPTION     CHAR,
DATE_PLACED_IN_SERVICE  DATE    "DD-MON-RR", <---"Here is the error"
GLOBAL_ATTRIBUTE1 DATE 'YYYY/MM/DD HH:MM:SS',
FIXED_ASSETS_COST,
DEPRN_RESERVE,
YTD_DEPRN,
ASSET_CATEGORY_ID,
EXPENSE_CODE_COMBINATION_ID,

The log file says:

DATE_PLACED_IN_SERVICE               NEXT     *   |       DATE DD-MON-RR  ......

Record 1: Rejected - Error on table "FA"."FA_MASS_ADDITIONS", column
DATE_PLACED_IN_SERVICE.
ORA-01858: a non-numeric character was found where a numeric was expected.

I have verified this column and it is a DATE format.

Any help will be really appreciated.

TIA.

      and we said...
well, I see the issue straight off -- but, so you can find it yourself in the
future, I'll show you what I do when faced with such an issue.

I recreate the table with all "big varchar2's" -- 25 looks big enough here:

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t
  2  ( c1 varchar2(25),
  3    c2 varchar2(25),
  4    c3 varchar2(25),
  5    c4 varchar2(25),
  6    c5 varchar2(25),
  7    c6 varchar2(25),
  8    c7 varchar2(25),
  9    c8 varchar2(25),
 10    c9 varchar2(25)
 11  )
 12  /

Table created.

and then i load a bad record.  In your case, I used:

ops$tkyte@ORA920> !cat test.ctl
LOAD DATA
infile *
APPEND
INTO TABLE T
FIELDS TERMINATED BY '|'
TRAILING NULLCOLS
( c1,
  c2 constant '1',
  c3,
  c4,
  c5,
  c6,
  c7,
  c8,
  c9 )
begindata
Fac.SQLL|1|DESCRIPCIÓN SQL|31-DEC-02|2002/12/01
00:00:00|1000|0|0|5|1063|32|1000|31-DEC-02|

now, when I load that -- i see:

ops$tkyte@ORA920> @printtbl8 'select * from t'
C1                            : Fac.SQLL
C2                            : 1
C3                            : 1
C4                            : DESCRIPCISN SQL
C5                            : 31-DEC-02
C6                            : 2002/12/01 00:00:00
C7                            : 1000
C8                            : 0
C9                            : 0
-----------------

PL/SQL procedure successfully completed.

so, you should be able to see the issue now -- that constant doesn't "skip"
a column -- it just stuffed a constant in there.  your fields don't line up at
all.  Your date is in column 5 -- not column 4.  So, you can:

ops$tkyte@ORA920> !cat test.ctl
LOAD DATA
infile *
replace
INTO TABLE T
FIELDS TERMINATED BY '|'
TRAILING NULLCOLS
( c1,
  foo filler,
  c3,
  c4,
  c5,
  c6,
  c7,
  c8,
  c9,
  c2 constant '1')
begindata
Fac.SQLL|1|DESCRIPCIÓN SQL|31-DEC-02|2002/12/01
00:00:00|1000|0|0|5|1063|32|1000|31-DEC-02|

ops$tkyte@ORA920> !sqlldr / test

SQL*Loader: Release 9.2.0.3.0 - Production on Sat Sep 6 13:04:42 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Commit point reached - logical record count 1

ops$tkyte@ORA920> @printtbl8 'select * from t';
C1                            : Fac.SQLL
C2                            : 1
C3                            : DESCRIPCISN SQL
C4                            : 31-DEC-02
C5                            : 2002/12/01 00:00:00
C6                            : 1000
C7                            : 0
C8                            : 0
C9                            : 5
-----------------

PL/SQL procedure successfully completed.

and now, they'll be in the right place

Review &
      Followup

      Rating: 5
It
      works!!!!  September 06, 2003
Reviewer:
      Francisco Martínez  from Monterrey, N.L.
Thanks a lot Tom, it is working.

I really appreciate your valuable help.

Best regards.

Servidata, PROEZA TI.
Francisco Martínez Oviedo.
Oracle DBA & Developer
OCP DBA 8 & 8i

      Rating: 3
Do not put OCP
      in bottom  September 07, 2003
Reviewer:
      A reader
The more question one reads from OCP people, they get more scared.

September 06, 2003 -- Thanks for the question regarding "current
      mode gets and rowids", version 9i2

      You Asked
Hi Tom,
While attempting to understand the recent AskTom thread on
"write-consistency", a question arose:

During an update statement, does oracle use the rowid to find rows
in current mode?  If it does use the rowid, what happens in this
situation:

Session 1 executes an update statement:
  update t set x = 4 where y = 5;
  (column y is not the PK on table t)

A consistent read identifies a row to be updated.
Oracle then has to "re-get" the row in current mode.

After session 1's consistent read, but before its current mode read,
all of the following (unlikely though theoretically possible) events
occur:
  1. session 2 deletes the row and commits.
  2. session 3 inserts a row which by chance has the same rowid
     as the previously deleted row.  It also has a value of 5 for y.
     Session 3 commits.

Then, session 1's current mode read will be using a valid rowid,
and the value of y in that row will satisfy the where predicate.
Despite the matching rowid and predicate value, it is not the same
row that session 1 originally read.  Does oracle update that row,
even though it is not the row which was read during the consistent
read portion of the update?

Thanks!

      and we said...
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:11504247549852
is the original link.

well, i would hardly call them unlikely, probably happens tons more then you
think.

You see, the consistent read happens "atomically" in theory.  If you have an
update that runs for 30 seconds -- the consistent reads all "happened" at time =
T0.

Now in case 1, the row is "gone" -- well, that is a "change".  restart. just
like the update would.  If you take the script from the other link and change
the update where id in ( 1,5 ) to delete from where id in (1,5) -- you'll see
the "undone" and "redone" effect just like the update would.

In case 2, the row would have changed and it knows that it changed (remember, it
has access to everything -- all undo, all versions of the block).  it would be
just the same as case 1 actually.  Much trickier to emulate for sure.
Review &
      Followup

      Rating:
      5
Emulation...  September 07, 2003
Reviewer:
      A reader  from Bay Area, CA
Tom,
The following is a test of what happens when a row that is
part of a session's update statement is deleted by another
session, and that other session inserts a different row
with the same rowid.

create table t (c1 int, c2 char(2000), c3 char(2000), c4 char(2000));

create or replace trigger t_bufer before update on t for each row
begin
    dbms_output.put_line ('Updating row, c1 is ' || to_char(:new.c1));
    dbms_output.put_line ('    rowid = ' || :new.rowid);
    dbms_output.put_line ('    block number = ' ||
to_char(dbms_rowid.rowid_block_number(:new.rowid)));
    dbms_output.put_line ('    row number = ' ||
to_char(dbms_rowid.rowid_row_number(:new.rowid)));
    dbms_output.put_line ('End Update Info');
    dbms_lock.sleep (120);
end;

create or replace trigger t_aifer after insert on t for each row
begin
    dbms_output.put_line ('Inserting row, c1 is ' || to_char(:new.c1));
    dbms_output.put_line ('    rowid = ' || :new.rowid);
    dbms_output.put_line ('    block number = ' ||
to_char(dbms_rowid.rowid_block_number(:new.rowid)));
    dbms_output.put_line ('    row number = ' ||
to_char(dbms_rowid.rowid_row_number(:new.rowid)));
    dbms_output.put_line ('End Insert Info');
end;

---------------------------------------------------
Session 1:
SQL> insert into t values (1, 'x', 'y', 'z');
Inserting row, c1 is 1
rowid = AAAXa8AAJAAAA4KAAA
block number = 3594
row number = 0
End Insert Info

1 row created.

SQL> commit;

Commit complete.

---------------------------------------------------
Session 2:
SQL> insert into t values (2, 'x', 'y', 'z');
Inserting row, c1 is 2
rowid = AAAXa8AAJAAAA4LAAA
block number = 3595
row number = 0
End Insert Info

1 row created.

SQL> commit;

Commit complete.

---------------------------------------------------
Session 1:
SQL> update t set c4 = c4 where c1 = 1 or c1 = 2;
    **sleeps**

---------------------------------------------------
While Session 1 is sleeping....
Session2:
SQL> delete from t where c1 = 2;

1 row deleted.

SQL> commit;

Commit complete.

SQL> insert into t values (2, 'm', 'n', 'o');
Inserting row, c1 is 2
rowid = AAAXa8AAJAAAA4LAAA
block number = 3595
row number = 0
End Insert Info

1 row created.

SQL> commit;

Commit complete.

---------------------------------------------------
Session 1's update completes...

Updating row, c1 is 1
rowid = AAAXa8AAJAAAA4KAAA
block number = 3594
row number = 0
End Update Info
Updating row, c1 is 2
rowid = AAAXa8AAJAAAA4LAAA
block number = 3595
row number = 0
End Update Info

2 rows updated.
---------------------------------------------------
---------------------------------------------------
Comments:
The row with c1 = 2 and c2 = 'm' did not even exist when session 1's update
statement began.  Yet it is being updated.  And it appears to be
updated without the update statement getting re-started.

If you get a chance, perhaps you could try to verify my test results.
---------------------------------------------------
---------------------------------------------------
Btw, I re-ran the above test, except this time Session 2 did not re-insert the
row after deleting it.
Session 1 re-started the update statement as expected:

SQL> update t set c4 = c4 where c1 = 1 or c1 = 2;
Updating row, c1 is 1
rowid = AAAXa/AAJAAAA4KAAA
block number = 3594
row number = 0
End Update Info
Updating row, c1 is 1
rowid = AAAXa/AAJAAAA4KAAA
block number = 3594
row number = 0
End Update Info

1 row updated.

      Rating:
      5
Summary...  September 09, 2003
Reviewer:
      A reader  from Bay Area, CA
Summary of the "write-consistency" thread in combination with my test results:

When a row that is read during the consistent read of an update statement
matches the WHERE clause of the update statement, oracle attempts
to get the row in current mode.  When getting the row in current mode:

 - If the row no longer exists (it has been deleted by another session),
   oracle rolls back the update statement and restarts it.

 - If the row exists AND the row has not changed since the consistent read,
   oracle updates the row and continues.

 - If the row exists AND the row has changed since the consistent read
   AND the row still matches the WHERE clause criteria, oracle updates
   the row and continues.

 - If the row exists AND the row has changed since the consistent read
   AND the row no longer matches the WHERE clause criteria,
   oracle rolls back the update statement and restarts it.

 - If the row no longer exists (it has been deleted by another session)
   AND another session has inserted a row with the same rowid AND that
   row happens to match the WHERE clause criteria, what oracle should
   do is recognize that the original row has been deleted, roll back
   the update statement, and restart it.  However, my preliminary
   (and as yet unproven) testing shows that the newly inserted row
   is getting updated without the update statement being rolled back
   and restarted.

September 06, 2003 -- Thanks for the question regarding "Script to
      insert a test record into a table", version 8.1.7

      You Asked
Tom

Y'day , I had to insert a record for testing purposes. The table has 40 columns,
and it was a real real pain writing the whole insert statement.

I had to create the whole statements as I had to insert into only 37 columns of
the 40.

Is there any script or can their be any script which can create a insert
statement for a given table.

Is so can you please share it/or discuss it.

Thanks

      and we said...
Here is a script I use to generate random data into a table:

set define off

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

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

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

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

September 06, 2003 Brett -- Thanks for the question regarding
      "Speeding up insert into database tables from external database.", version
      9.2.0.4

      You Asked
Tom,

I have an application written in C and we want to mirror the data into Oracle
tables.  The application they my consultants wrote basically sends every changed
record to an Oracle table procedure that I have written.  In essence, it is an
upsert procedure using the merge command.  The problem is that I have tables
with several million records.  If I opt to sync a table, the application reads
each record and inserts them one by one into Oracle with a commit after each
insert.  Is there a way to speed up this process?

Thanks,

Brett

      and we said...
don't do it row by row and don't commit after each record

row by row = as slow as you can possibly go.
commit frequently = generate more redo, more undo and make it go even slower

batch process 100-500 rows at a time and commit on a "timed basis" or
transactionally (whichever makes more sense).

You'll find that many times faster.
Review &
      Followup

      Rating: 5
How would this
      work?  September 06, 2003
Reviewer:  Brett
       from Ft. Lauderdale, FL
I think the developers are using the same logic for "synching" the tables and
performing the production updates.  Basically, here is the process:  Update a
record in the external app, write the record to a queue file, another thread
monitors the queue file and calls the upsert procedure, and the record in the
queue file is update as sent.

In this scenario, would if be wise to issue a commit periodically...say every 15
mins?

          Followup:

it would be wise for the thing monitoring the queue file to give you 500 of them
at a time.

the committing is up to you -- the more you do it, the slower you go -- but the
longer between the more work you might have to replay.  It is a judgement call
on your part.

      Rating:
      5
Great.....  September 06, 2003
Reviewer:
      Brett  from Ft. Lauderdale, FL
Thanks, Tom.  Since the records in the queue file will be from different tables
and going into different Oracle tables, it may be problematic to sort through
them to figure out what goes where.  Actually, in production, I don't think that
committing after every update will be that bad since the user base is very low.
The initial synching of the tables, however, is very painful.  Fortunately, that
process goes table by table, so I may be able to get them to issue a commit
after each table synch.

Thanks for the great advice.

September 06, 2003 Vitaly -- Thanks for the question regarding
      "Partition", version 8.1.7

      You Asked
We have a very large non-partition table ~30GB.We like improve
performance using partition.
Can you advise the best way to partition the table?
Thanks in advance.

      and we said...
No I cannot.

You need to know how the table is used.
You need to know what types of queries you use on the table.
You need to know lots of things about the table in order to
suggest a knowledgable, efficient partitioning scheme for it.

Adding partitions to a table may not do anything for performance
with regards to the table.  If you are always indexing scanning
the table -- no increase in performance may be seen.  If you
full scan frequently and you can partition intelligently so that
many partitions can be elinimated from being scanned -- you may
see significant improvement.

Partitioning is of most benefit to the administrators of the
system.  It provides for more availability.  It can in some
cases improve performance.

You need to study partitioning and what it has to offer.  Then
look at how you use the table, how you query the table and see
if there is some reasonable partition key that would actually
improve your performance given how partitioning works.
Review &
      Followup

      Rating: 5
Partion
      Performance...  May 31, 2002
Reviewer:
      Senthil  from Boston, MA, USA
Tom-

Sorry to cross-post, as this question closely relates to my question, i am
posting here. I am having a performance question on partition based table. here
is the scenario...

SQL> drop table table2002
  2  /

Table dropped.

SQL>
SQL> CREATE TABLE table2002
  2  (  x int,
  3     y int,
  4     z DATE
  5  )
  6     PARTITION BY RANGE (z)
  7     (  PARTITION tab_2002_m1 VALUES
  8                  LESS THAN(to_date('01-feb-2002','dd-mon-yyyy')),
  9        PARTITION tab_2002_m2 VALUES
 10                  LESS THAN(to_date('01-mar-2002','dd-mon-yyyy')),
 11        PARTITION tab_2002_m3 VALUES
 12                  LESS THAN(to_date('01-apr-2002','dd-mon-yyyy')),
 13        PARTITION tab_2002_m4 VALUES
 14                  LESS THAN(to_date('01-may-2002','dd-mon-yyyy'))
 15     )
 16  /

Table created.

SQL>
SQL> CREATE INDEX test_i ON table2002(z) LOCAL
  2  /

Index created.

SQL> insert into table2002 select 1, 1, to_date('01-01-2002', 'mm-dd-yyyy') from
all_objects where rownum < 1001;

1000 rows created.

SQL>
SQL> insert into table2002 select 1, 1, to_date('02-01-2002', 'mm-dd-yyyy') from
all_objects where rownum < 1001;

1000 rows created.

SQL>
SQL> insert into table2002 select 1, 1, to_date('03-01-2002', 'mm-dd-yyyy') from
all_objects where rownum < 1001;

1000 rows created.

SQL>
SQL> insert into table2002 select 1, 1, to_date('04-01-2002', 'mm-dd-yyyy') from
all_objects where rownum < 1001;

1000 rows created.

SQL> select * from table2002 where z = '2002-01-01' and x = 1;

1000 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=35)
   1    0   TABLE ACCESS (BY LOCAL INDEX ROWID) OF 'TABLE2002' (Cost=1 Card=1
Bytes=35)
   2    1     INDEX (RANGE SCAN) OF 'TEST_I' (NON-UNIQUE) (Cost=1 Card=1)

Statistics
----------------------------------------------------------
         42  recursive calls
          0  db block gets
        149  consistent gets
          0  physical reads
         60  redo size
      39895  bytes sent via SQL*Net to client
       7756  bytes received via SQL*Net from client
         68  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
       1000  rows processed

SQL>
SQL> select * from table2002 partition(tab_2002_m1) where z = '2002-01-01' and x
= 1;

1000 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=35)
   1    0   TABLE ACCESS (BY LOCAL INDEX ROWID) OF 'TABLE2002' (Cost=1 Card=1
Bytes=35)
   2    1     INDEX (RANGE SCAN) OF 'TEST_I' (NON-UNIQUE) (Cost=1 Card=1)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        146  consistent gets
          0  physical reads
          0  redo size
      39895  bytes sent via SQL*Net to client
       7756  bytes received via SQL*Net from client
         68  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
       1000  rows processed

SQL> get selectchk.sql
  1  set autotrace off
  2  delete from plan_table;
  3  prompt select * from table2002 where z = '2002-01-01' and x = 1;
  4  explain plan set statement_id = 'DD' for
  5     select * from table2002 where z = '2002-01-01' and x = 1;
  6  @?/rdbms/admin/utlxpls.sql
  7  delete from plan_table;
  8  prompt select * from table2002 partition(tab_2002_m1) where z =
'2002-01-01' and x = 1;
  9  explain plan set statement_id = 'DD' for
 10     select * from table2002 partition(tab_2002_m1) where z = '2002-01-01'
and x = 1;
 11* @?/rdbms/admin/utlxpls.sql
 12  .
SQL> @selectchk.sql

3 rows deleted.

select * from table2002 where z = '2002-01-01' and x = 1     <-- statementA

Explained.

Plan Table
--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |     1 |   35 |      1 |       |       |
|  TABLE ACCESS BY LOCAL IND|TABLE2002 |     1 |   35 |      1 |     1 |     1 |
|   INDEX RANGE SCAN        |TEST_I    |     1 |      |      1 |     1 |     1 |
--------------------------------------------------------------------------------

6 rows selected.

3 rows deleted.

select * from table2002 partition(tab_2002_m1) where z = '2002-01-01' and x = 1
<-- statementB

Explained.

Plan Table
--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |     1 |   35 |      1 |       |       |
|  TABLE ACCESS BY LOCAL IND|TABLE2002 |     1 |   35 |      1 |     1 |     1 |
|   INDEX RANGE SCAN        |TEST_I    |     1 |      |      1 |     1 |     1 |
--------------------------------------------------------------------------------

6 rows selected.

Is there any difference between statementA and statementB. All i am going to
retrieve data from partition1(tab_2002_m1) using two different queries retrieve.
Which query performs better, first(statementA) or second(statementB). Based on
the above result, i couldnt come to conclusion. Please disregard this question,
if both query performs same.

thanks,
senthil

          Followup:

It defeats the purpose of having partitioning if you are going to hard code
partition names into the query anyway.

Only use the partition name in the query for very very special cases.

PLEASE use to_date with an explicit format ALWAYS, never rely on implicit
conversions!

select * from table2002 where z = to_date( '2002-01-01', 'yyyy-mm-dd' ) and x =
1

is the correct query to use.

      Rating:
      4
  June 25, 2002
Reviewer:  David
       from Nashville, TN
If a table is never accessed with a full table scan - would it make sense to
partition the index only? (and not the table), or is this even possible?

          Followup:

You can partition the index and not the table but -- why???

      Rating: 4
why partition
      index?  June 26, 2002
Reviewer:  David
       from Nashville, TN
We have a large table (5GB, 120 million rows, 2 GB index) that holds an
attendance record for each student for each day of the year.  Any processing
that accesses (SELECT, INSERT, UPDATE) this table is still running longer than
we need and we are looking for ways to improve the performance.  All of the SQL
is tuned and uses the index - no full table scans.  We are hoping some
partitioning scheme will dramatically improve performance.

The statement you made that partitioning could help if a table is full table
scanned made me think it will not help performance since we are using an index.
My next thought was, "well, maybe breaking the index into smaller chunks would
help."

-David

          Followup:

Using partitioning could dramatically slow down stuff as well -- be very very
careful here.

Do you index fast full scan?  If not, this *might* not help very much.

I can help in increasing some concurrency issues as you'll have N indexes
instead of just one (more freelists, less hot spots and so on)

If you partition *wrong* -- based on how you use your indexes -- you'll end up
killing performance if you are not careful.  If you do not include the partition
key in the query -- you'll end up having to index scan MANY index partitions --
whereas before you just had one.

I'm dubious on this one.  If the indexes are big enough to partition -- so is
the table.

      Rating:
      4
  June 26, 2002
Reviewer:  David
       from Nashville, TN
Yep - I'm all for partitioning table and index if that helps performance.  I
just threw the index only question out cause that's where I was in my thought
process...

Thanks a ton for your feedback.  I'm trying different partitioning schemes in
our test environment to find the right one.  I'm testing with about 7% of the
data that is in production and I have 15 table and index partitions (each on
their own locally managed tablespace).  So far, I have managed to DOUBLE the
execution time  ;-(  Not good, but still working with it...

      Rating:
      5
range_partitioning_clause  February 24, 2003

Reviewer:  Sikandar Hayat Awan  from Pindi Bhattian -
      Pakistan
I have performed the following test to use partitioning but want to know some
issues I am feeling I may face after implementation of this feature,

CREATE TABLE table2003
    (  x int,
       y int,
       z DATE
    )
       PARTITION BY RANGE (z)
       (  PARTITION tab_2003_jan VALUES
                    LESS THAN(to_date('01/02/2003','dd/mm/yyyy')),
          PARTITION tab_2003_feb VALUES
                   LESS THAN(to_date('01/03/2003','dd/mm/yyyy')),
         PARTITION tab_2003_mar VALUES
                   LESS THAN(to_date('01/04/2003','dd/mm/yyyy')),
         PARTITION tab_2003_apr VALUES
                   LESS THAN(to_date('01/05/2003','dd/mm/yyyy'))
      )

SCOTT > select * from table2003;

           X            Y Z
------------ ------------ ----------
           1            1 01/01/2003
           1            1 01/01/2003
           1            1 01/01/2003
           1            1 01/01/2003
           1            1 01/01/2003
           2            2 02/01/2003
           2            2 02/01/2003
           2            2 02/01/2003
           2            2 02/01/2003
           2            2 02/01/2003
           3            3 03/01/2003
           3            3 03/01/2003
           3            3 03/01/2003
           3            3 03/01/2003
           3            3 03/01/2003
           4            4 04/01/2003
           4            4 04/01/2003
           4            4 04/01/2003
           4            4 04/01/2003
           4            4 04/01/2003
           5            5 05/01/2003
           5            5 05/01/2003
           5            5 05/01/2003
           5            5 05/01/2003
           5            5 05/01/2003

1- There is no range for may data so 05/01/2003 will be in which partition?
2- I have a 2 GB table with generic data and this data is used for atleast not
more than 2 months. There is an application which reads data with the barcode
scanner on the form. I have to delete old data to reduce the size of this table
so I was considering the pratition option. But I think I have to add new month
at the start of each month. Is it?
e.g.
As in the above test I don't have any partition for May so what should I do in
the real scene?

3- As I have already mentioned that only 2 months old data will be read by the
application so I don't think so that there will be any performance issue. If
there are any then what are those?

4- I don't want to modify the application so if I just use this feature what
kind of changes I may expect in the application or the application will stay as
it is?

5- On the form barcode is read and against that number values are searched from
this table. Currently the speed of scanning is 35 coupons / minute and there is
no performance issue but only administration is difficult. That's why I am
planning to adopt this feature but before going for it I want to know pros and
cons of this feature.

-----
I am reading
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a85397/statem3e.htm#2062235
and you book but unable to get enough info. Is there any
better source from where I can get more detailed help.

One more favour that if I have to search in the Oracle docs then what is the
best way to do this as I got many links from you which are best and correct
findings but when we go to find any info it is hard for us.

Now as this is a reply to an existing posting then how to search it after 2 days
if I want to check any response to this posting? If I will search for
"Pratition" then it will find more postings and it is hard to get the concerned
one.

Sorry for above questions as you are modifying asktop so I raised these
questions.

          Followup:

1) insert into table2003 values ( 1,1,to_date('05/01/2003', 'mm/dd/yyyy') )
            *
ERROR at line 1:
ORA-14400: inserted partition key does not map to any partition

it'll not go anywhere -- you didn't tell us where it should go so it fails.
there is a MAXVALUE clause you can use if you like

2) You could do one of two things:

o have as part of your concepts of operations the addition of a new partition AT
the point in time you drop the OLD one.

o use a derived column "month" and set it to to_char(dt,'mm') and partition by
that column (1,2,3,4,...,12) and just truncate the oldest partitition before
that month happens (eg: truncate partition 3 before MARCH -- on feb 28th).  then
the partitions will just be used round robin

3) insufficient data to even figure out what to say on my part.  I've no ideas
what self imposed performance implications you might be having -- I don't know
your system

4) If you understand the physics of the application.  If you understand the
physics behind partition.  If you design the partitioning scheme in such as was
as to not impact the application.  IF YOU TEST TO SCALE the application before
putting it into production -- you can introduce partitioning without touching
the application at all.

if on the other hand you do something like partition by date - -but the
application NEVER uses date in any queries and you use LOCALLY partitioned
indexes -- you'll increase the IO on your system 12x which could be disasterous!

5) the only thing I can say is "test it".  that is the only way to get to know
the pro's and con's IN YOUR CASE.  Someone elses 'feature' is anothers 'bug'.
What one person really digs -- you could find to be a show stopper.

As Jonathan Lewis says "test to destruction" -- test test test.

The place for detailed help now is from within.  You have the technical details
-- but NO ONE but NO ONE can tell you from a generic book "partitioning will be
good for you".

You must take the knowledge you've learned, test it in practice, see how to
apply it.

You can bookmark pages on this site using the "bookmark this page" link above

      Rating: 5
Correction in
      the last post  February 25, 2003
Reviewer:
      Sikandar Hayat Awan  from Pindi Bhattian-Pakistan
In my previous the date format confused me.

  1  insert into table2003
  2   select 5, 5,
  3  to_date('05-15-2003', 'mm-dd-yyyy')
  4  from
  5* all_objects where rownum < 6
SCOTT > /
insert into table2003
            *
ERROR at line 1:
ORA-14400: inserted partition key is beyond highest legal partition key

Now my question is as I received the above error which I was expecting so what
to do to resolve this.

          Followup:

Umm, add a partition to the table that lets you hold that data?

      Rating: 5
partition
      design   July 19, 2003
Reviewer:  Steve
       from NYC, USA
Hi Tom,

In data warehousing applications, what is the best number of partitions I should
choose when I design a table if We have a 12 CPUs machine and we use hash
partition?
Is it a good design if we pick the number of partitions equal to number of CPUs?

Thanks!

Steve

Thanks!

Steve

          Followup:

partitioning has nothing in my experience to do with the number of CPU's.

it has everything to do with how you use the data, what your goals are.

We, unlike most other databases, do not need to be partitioned physically to be
parallel -- we'll parallelize regardless of partitioning (or lack thereof).

use partitioning as a tool to ease administration, make the data more available,
perhaps even increase performance.  It isn't tied to the hard ware.

      Rating: 4
cpu's parallel
      query partitioning  September 04, 2003
Reviewer:
      Dick Stins  from The Netherlands, Amersfoort
I guess that Steve wants to optimize the cpu load combined with partitioning and
parallel query.

There is no answer. You should test and test and test lots of configurations in
a realistic (production) environment.

Some suggestions:
This depends of the load of your machine and the degree you choose for parallel
query and ofcourse the number of partitions. And lots of other stuff like io
controllers and harddisks configurations. The degree should be less than the
number of cpu's when other processes need also some cpu resources. When you have
the machine dedicated for your parallel query, it might be effective to choose
the degree something like the number of CPU's or twice the number of cpu's, but
you really should test this and it is configuration dependend.

It might be efficient when each partition has his own dedicated disk
configuration, but I have seen also databases on one logical disk (lot's of raid
sets combined to one logical volumes) which easily improved the performance with
multithreading up to the number of cpu's.

So you should test your configuration to find out.

          Followup:

but

partitioning and parallel query

are not synonomous.  we need not be partitioned to parallel query at all.

so, really, partitioning doesn't come into the "cpu" equation here.

      Rating:
      1
syn-on-y-mous  September 06, 2003

Reviewer:  Dick Stins  from The Netherlands

Yes.
Partitioning can help to distribute IO over different disks.
Parallel query which needs lots of cpu power can help to exploit the
partitioning concept to a better performance for some operations (others will
slow down).

Resources (cpu's, disks, io controllers, ..) are not unlimited, so there is for
every configuration an "optimal" situation.

          Followup:

so can

s-t-r-i-p-i-n-g (even easier)

so can

e-x-t-e-n-t-s

(many of them)

so can m-a-n-y things...

and IO and CPU are not syn-on-y-mous at all.

pq is not cpu intensive....
no more then a full scan is....

it can help you BETTER EXPLOIT your cpus... but to say it is "cpu intensive",
well, thats just wrong.

I'll re-iterate.  I totally and 100% fail to see the relationship between
partitioning and cpu.

show us an example -- back it up with "science".

make sure to use technology widely used by people that would be using

a) partitioning
b) parallel query
c) smp

(eg:  not a windoze box with four ide disks, people serious about systems)

      Rating:
      3
exploit  September 08, 2003
Reviewer:
      Dick Stins  from The Netherlands
Tom,

Very interesting that parallel query can help you BETTER EXPLOIT your cpu's.

Thanks for your insights.

When I have new interesting examples to discuss, I will let you know.

September 07, 2003 Sindhu -- Thanks for the question regarding
      "Switching between rows and columns", version 9

      You Asked
I want to display rows as columns in a select statement. Can i do that?For eg in
table emp the data is like

Empno   Ename   Job
1       Smith   Manager
2       Allen   Clerk
3       Koshy   Clerk
4       Shawn   Analyst

I want to display as follows

Column Names -  Manager   Clerk    Analyst
                1         2        1

Is this possible through a select statement other than using Decode?

      and we said...
well, you can use CASE instead of decode if you feel decode to be "evil", but
basically, you are going to

select count(decode( job, 'Manager', 1 )) manager,
       count(decode( job, 'Clerk', 1 )) clerk,
       ....
  from emp;

September 07, 2003 Kavitha -- Thanks for the question regarding "To
      create a view from a table where some of the columns in the table stores
      the code value and those columns have to be joined to the master table to
      get the decoded value. How do I go about writing the query to create the
      view object ", version 9i

      You Asked
Hi ,

This is my scenario. I have a Table A which has the following column

1. Study Id
2. Sex
3. Height
4. Weight

The records in this table are like

Study Id    Sex     Height     Weight
1            1       160         60
2            2       170         100

I have another Table B which stores the columns of Table A that stores the code
id value and not the actual Value. Table B structure is

1. Column Name
2. Code Id

The records in the table is

Column Name            Code Id
Sex                     2000

I have another Table C which stores the code Id, the value and the decode value.
The structure for Table C is

1. Code Id
2. Value
3. Decode Value

The record in this table is

Code ID           Value        Decode Value
2000                1            Female
2000                2            Male

My problem is I have to construct a view on Table A that shows  the decode value
for the columns that actually stores the value of the code Id. The view should
look like below

Study Id     Sex      Height       Weight
1            Female    160          60
2            Male      170          100

How do I write a query to create the above view?

Your reply is appreciated.

Thanks & Regards,
Kavitha

      and we said...
normalization rum amok!  agh, why would someone make it so hard to ask questions
of their data model!!

select study_id,
       (select decode_value
          from t3
         where code_id = (select code_id from t2 where column_name = 'Sex'
           and value = t1.sex ) ) sex,
       height,
       weight
  from t1;

is one approach.  you could just join as well.
September 07, 2003 Piotr -- Thanks for the question regarding
      "Oracle & Perl, external tables.", version 9.2.0

      You Asked
1. Perl and Oracle.
I liked your statement:
<quote author="Tom Kyte">
I firmly believe the best java programs are those that have ZERO
"selects/inserts/updates/deletes" in them.  Hence, using ref cursors is the way
to go.  Lets you tune without bothering those java programmers.
</quote>
And I want to do alike...
There is no problem as far as Java is concerned either pure JDBC (at least in
JDK 1.4) or JDBC with some Oracle specific stuff in the code. I'd like to follow
that approach as well while programming in Perl. Thus I want to be able to do
the following:
a) pass in/out an array of values (bulk store/fetch/update)
b) pass in/out object types (also with an array of such)

But the Perl DBI does not have any methods to do so. Do you know if this is at
all possible even at the cost of using Perl-Oracle DBD directly? If so could you
give an example or a link to a such?

2. Languages' API and performance.

The query:
select * from table_with_million_rows where rownum<=100

lives as straight select and also in a stored procedure returning refcursor. My
client programs are written in Java (using both thin and OCI driver), Perl (only
OCI is available) and PL/SQL. This query is (deliberately) softparsed and
executed 100 times and all 100 rows are fetched and immediately discarded (I
time only the SQL stuff). In both Perl DBI and Java JDBC client software the
multiple row fetch is set to 10 rows and the network sniffer indeed shows that
there were 10 client-server round-trips.

Each row is on average 100 bytes and at the time of tests I made sure to be the
only user logged on DB.

Average timings on my system are:
---------------------------------------------------------------------------------
--------
language    driver        straight query    stored procedure returning cursor
[msec]
---------------------------------------------------------------------------------
--------
Java 1.3    thin Oracle9i    17.1        21.3
Java 1.3    OCI Oracle8i    14.8        17.2
Perl 5.8    OCI Oracle8i    7.7        85.4
PL/SQL        internal    13.4        14.7

Client machine: AMD Athlon    1533 MHz (3060 Bogomips), 1 GB RAM
OS:        Linux Debian 2.4.19
Oracle:        Oracle 8i Client

Server machine: Pentium 3    400 MHz (800 Bogomips), 1 GB RAM, 9x SCSI 10 GB, 2x
EIDE 30GB
OS:        Linux Debian 2.4.18
Oracle:        Oracle 9.2.0.0.0 Enterprise Edition

Network:    100 Mbit Ethernet
---------------------------------------------------------------------------------
--------
This 85 msec in Perl results from a known performance bug in Perl-Oracle DBD and
hopefully will be fixed. But anyway tests show that the straight query is always
by 5-20% faster than exactly the same query hidden in stored procedure and
returned as refcursor. I tried less or more complicated queries (seems like: the
more complicated query in terms of parse effort and optimizer plan
complexity/per fetched row number, the less the difference in performance,
stored procedure call overhead is constant and tend to get neglectable when
query complexity rises)

Assuming that there is this 5-20% performance hit between straight select and
refcursor returned to the client would you still recommend to use stored
procedures over straight queries? For testing and tuning it is great but in
production it would better to use straight select, wouldn't it? Also how would
you explain that Perl DBI/DBD (although its not "really pure" Perl but shared
library written in "C" and scripted by Perl) running on remote machine is by 40%
faster than PL/SQL running inside the DB server?

And here are the most important code excerpts:
-----------------------------------------------------------------------
JAVA

// run() methods are called from timer engine also 100 times

class Doit implements Timeable {
        public void run(Object o) throws Exception {
                Connection con=(Connection)o;
                CallableStatement stmt=con.prepareCall("{ ? = call test.test_ref
}");
                stmt.registerOutParameter(1,OracleTypes.CURSOR);
                stmt.execute();
                ResultSet rs=(ResultSet)stmt.getObject(1);
                while (rs.next());
            // discard;
                stmt.close();
        }
}

class Doit2 implements Timeable {
        public void run(Object o) throws Exception {
                Connection con=(Connection)o;
                Statement stmt = con.createStatement();
                ResultSet rs = stmt.executeQuery("select * from
table_with_million_rows where rownum<=100");
                while (rs.next());
            // discard;
                stmt.close();
        }
}

-----------------------------------------------------------------------
PERL

# These routines are called 100 times

sub doit {
        my $sth=$dbh->prepare("begin ?:=test.test_ref; end;");
        my $crs;
        $sth->bind_param_inout(1,\$crs,0,{ora_type=>ORA_RSET});
        $sth->execute();
        while (my $row=$crs->fetchrow_arrayref()) {
        # discard;
        }
        $sth->finish();
}

sub doit2 {
        my $sth=$dbh->prepare("select * from table_with_million_rows where
rownum<=100");
        $sth->execute();
        while (my $row=$sth->fetchrow_arrayref()) {
        # discard;
        }
        $sth->finish();
}

-----------------------------------------------------------------------
PL/SQL

declare
t1 number default dbms_utils.get_time;
t2 number;
begin
    for i in 1..100 loop
        for x in (select * from table_with_million_rows where rownum<=100)
        loop
            -- discard;
            null;
        end loop;
    end loop;
    t2:=dbms_utils.get_time;
    dbms_output.put_line('Time: '|| (t2-t1)*10/100 || ' msec');
end;

declare
t1 number default dbms_utility.get_time;
t2 number;
type rc is ref cursor;
cur rc;
x table_with_million_rows%rowtype;
begin
    for i in 1..100 loop
        cur:=test.test_ref;
        loop
            fetch cur into x;
            exit when cur%notfound;
            -- discard;
        end loop;
        close cur;
    end loop;
    t2:=dbms_utility.get_time;
    dbms_output.put_line('Time: '|| (t2-t1)*10/100 || ' msec');
end;

-- packaged function returning refcursor

CREATE OR REPLACE PACKAGE test IS
type rctype is ref cursor;
FUNCTION test_ref RETURN rctype;
END test;
/
CREATE OR REPLACE PACKAGE BODY test AS

FUNCTION test_ref return rctype IS
rc rctype;
BEGIN
     open rc for select * from table_with_million_rows where rownum<=100;
     return rc;
END test_ref;

END test;
/

3. SQL*Loader vs. external tables
We have the CSV format using SOH (ASCII 0x01) and STX (ASCII 0x02) as field
terminator and record delimiter respectively (in this example it is not
necessary but we have data that may contain any printable characters including
newline, tabs, spaces, form feeds, etc.). The SQL*Loader control file looks:

LOAD DATA
INFILE bds_2_pblk.csv "str X'02'"
INTO TABLE bds_2_pblk
REPLACE
FIELDS TERMINATED BY X'01'
TRAILING NULLCOLS
(BDS_ID,PBLK_ID)

And it all works fine. But when I want to use Oracle 9i's external tables:

create directory load_dir as '/var/tmp';

create table test_loader
(bds_id number, pblk_id number)
organization external
(
  type oracle_loader
  default directory load_dir
  access parameters
  ( records delimited by X'02'
    fields terminated by X'01'
  )
location ('bds_2_pblk.csv')
)

ORA-29913: error in executing ODCIEXTTABLEOPEN callout
ORA-29400: data cartridge error
KUP-00554: error encountered while parsing access parameters
KUP-01005: syntax error: found "identifier": expecting one of:
"double-quoted-string, hexprefix, newline, single-quoted-string"
KUP-01008: the bad identifier was: X
KUP-01007: at line 1 column 22
ORA-06512: at "SYS.ORACLE_LOADER", line 14
ORA-06512: at line 1

I tried (almost) everything and it just doesn't work! I get syntax error either
already at "create table..." or yet another at the "select * from test_loader"
when I enclose X in double quotes moreover files like
TEST_LOADER_XXXX.bad/TEST_LOADER_XXXX.log are created and the error says:

ORA-29913: error in executing ODCIEXTTABLEFETCH callout
ORA-30653: reject limit reached
ORA-06512: at "SYS.ORACLE_LOADER", line 14
ORA-06512: at line 1

I reckon the problem is 99.99% trivial and reduces itself to the magic word
HEXPREFIX in error KUP-01005. Am I right? So what is the syntax of hexprefix in
external table statement? I thought SQLLDR and external tables are technically
the same thing. But are they really? Internally maybe but at least syntactically
they are not.
Sure I could use the hint from your book (multiple character separators e.g.
'||\n') but in first place we would like to use the existing format. And I
searched through the Internet and found nothing. You seem to be my last hope :-)
A derivative question: How can I incorporate control characters in PL/SQL string
literals? Maybe I'm blind but there is no way to do this according to the PL/SQL
manuals! Well chr(n) and concatenation would do the trick but I want that in
literal strings.

4. When archive logs get full.
What happens when the database is running in archive mode and the disk(s) where
archive logs are stored gets full? With online logs they simply get reused in a
circular fashion. Will they (archive logs) be overwritten in a circular fashion
as well. If not what does the Oracle do about that? Do archived logs go to a
'bit-bucket'?

      and we said...
wow, lots of text here.  lets see what I can say...

first -- sorry so long, but that's what happens with large multi-part q's.  I
had everything but the external table, filed it away (and subsequently *forgot*
about it) -- then found it again and researched it.....

1) I know how to do ref cursors (see

http://asktom.oracle.com/~tkyte/ResultSets/index.html
) there is an example there.  Beyond that, I've never ever used perl and hence
do not have lots (like no) examples....

2) It is not all about raw performance.  i would prefer to pay for a 5-10%
faster CPU then lose the capability of tuning (once that code gets out of the
database -- into the hands of the java guys who think "this database thing isn't
worth thinking about" the chances of EVER fixing it are

a) slim
b) or none

I would use ref cursors, yes.  Hopefully, those timings are in thousands of
seconds -- else you have a fundemental problem.  If they are seconds, then 17
seconds to return 100 rows?  that should be INSTANTANEOUS.

3)

ops$tkyte@ORA920.US.ORACLE.COM> !cat test.c
void main()
{
  printf( "123%c456%c789%c123%c", 0x01, 0x02, 0x01, 0x02 );
}

used that to generate the data

ops$tkyte@ORA920.US.ORACLE.COM> create or replace directory load_dir as '/tmp'
  2  /
Directory created.

ops$tkyte@ORA920.US.ORACLE.COM> drop table test_loader;
Table dropped.

ops$tkyte@ORA920.US.ORACLE.COM> create table test_loader
  2  ( bds_id number, pblk_id number )
  3  organization external
  4  (
  5  type oracle_loader
  6  default directory load_dir
  7  access parameters
  8  (
  9   records delimited by 0x'02'
 10   fields terminated by 0x'01'
 11    )
 12    location ( 'bds_2_pblk.csv' )
 13  )
 14  /

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select * from test_loader;

    BDS_ID    PBLK_ID
---------- ----------
       123        456
       789        123

4) the database stops -- we need those for recovery, overwriting them would make
it IMPOSSIBLE for us to recover from backups hence the only sensible thing to do
is stop.  you have to put them elsewhere then we go again
Review &
      Followup

      Rating: 4
Very
      good  September 26, 2002
Reviewer:  Piotr
      Jarmuz  from Braunschweig, Germany
Thanx! Especially for external tables tip.

It was timed in miliseconds and it was INSTANTANEOUS :-).

I agree. The benefits from shifting tuning efforts from developer's shoulders to
some Oracle savvy seem to compensate for this 5-10% overhead at least in OLTP
applications.

If you never used Perl you perhaps should have a try.
I'm sure you'd love it ;-).

To me Perl, Oracle and Apache are just made one for another.

It would be nice if next Oracle version (10i ???) could have Perl Virtual
Machine embedded in it. The Oracle-Perl world out there would most appreciate
it.

          Followup:

I've looked at perl.

To me, If I cannot read a language without digging through a ton of manuals, I
don't want to touch it ;)

I'll stick with

sql
plsql
java
c
(and sed,awk,grep of course...)

      Rating:
      5
  September 26, 2002
Reviewer:
      A reader
Piotr Jarmuz:

Could you please explain / point us to some websites that has more info, on
"Pearl virtual machine" - never heard of it before.

      Rating:
      4
PVM  September 27, 2002
Reviewer:  Piotr
      Jarmuz  from Braunschweig, Germany
You can read about PVM here:

http://www.ebb.org/bkuhn/writings/technical/thesis/node25.html
http://www.perldoc.com/perl5.6/pod/perlfaq1.html
http://www.netthink.co.uk/downloads/internals/index.html
http://opensource.devx.com/articles/PerlFAQ/PerlFAQ-1.shtml
It also explains the difference between the JVM and PVM, efficiency, internal
representation of data, etc.

Regards

      Rating:
      5
  September 27, 2002
Reviewer:
      A reader
Thank you verymuch.

      Rating: 5
Delimited By
      more than one character  April 03, 2003
Reviewer:
      Sri  from NJ
Dear Tom,

  I want to use more than one character as my "delimited by".  Say, I want to
use a @ and a NewLine as delimiter.  I tried giving
 records delimited by 0x'400a'
  where 40 is the hex for @ and 0a is the one for Newline.  I have the .dat file
as follows -

1|John|Marketing@
2|Blake|Logistics@
3|Smith|Personnel And
Human Resources@
4|Harry|Finance@

and the external table definition is as follows: -
CREATE TABLE Emp_Ext
(Emp_No    Number(3),
 EName     Varchar2(50),
 Dept      Varchar2(100)
)
ORGANIZATION EXTERNAL
( TYPE ORACLE_LOADER
  DEFAULT DIRECTORY MyData_Dir
  ACCESS PARAMETERS
  ( RECORDS DELIMITED BY 0x'400a'
    FIELDS TERMINATED BY '|'
    MISSING FIELD VALUES ARE NULL )
  LOCATION ('Emp.dat')
)
/

The select * from Emp_Ext is returning only one row.  Please help me in solving
this.  Thank you.

          Followup:

ops$tkyte@ORA920> !cat /tmp/Emp.dat
1|John|Marketing@
2|Blake|Logistics@
3|Smith|Personnel And
Human Resources@
4|Harry|Finance@

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace directory mydata_dir as '/tmp'
  2  /

Directory created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> CREATE TABLE Emp_Ext
  2  (Emp_No    Number(3),
  3   EName     Varchar2(50),
  4   Dept      Varchar2(100)
  5  )
  6  ORGANIZATION EXTERNAL
  7  ( TYPE ORACLE_LOADER
  8    DEFAULT DIRECTORY MyData_Dir
  9    ACCESS PARAMETERS
 10    ( RECORDS DELIMITED BY 0x'400a'
 11      FIELDS TERMINATED BY '|'
 12      MISSING FIELD VALUES ARE NULL )
 13    LOCATION ('Emp.dat')
 14  )
 15  /

Table created.

ops$tkyte@ORA920> column ename format a20
ops$tkyte@ORA920> column dept format a30
ops$tkyte@ORA920> select * from emp_ext;

    EMP_NO ENAME                DEPT
---------- -------------------- ------------------------------
         1 John                 Marketing
         2 Blake                Logistics
         3 Smith                Personnel And
                                Human Resources

         4 Harry                Finance

works for me...

tried it in:

9012
9201
9203

you might have your data in the wrong directory or something -- in anycase,
review the logs that go with external tables.

      Rating: 4
Still not
      working...  April 03, 2003
Reviewer:  Sri
       from NJ
Dear Tom,

  Thank you for your quick response.

  But when I ran the same query, I am getting the following result: -

    EMP_NO ENAME                DEPT
---------- -------------------- ------------------------------
         1 John                 Marketing@

                                2

  My Emp.Log file is as follows: -

 LOG file opened at 04/03/03 16:00:42

Field Definitions for table EMP_EXT
  Record format DELIMITED, delimited by 400a
  Data in file has same endianness as the platform

  Fields in Data Source:

    EMP_NO                          CHAR (255)
      Terminated by "|"
      Trim whitespace same as SQL Loader
    ENAME                           CHAR (255)
      Terminated by "|"
      Trim whitespace same as SQL Loader
    DEPT                            CHAR (255)
      Terminated by "|"
      Trim whitespace same as SQL Loader

  Really can't understand why it is not working for me.  I am using Oracle 9i
(9.0.1.1.1).  Is there any knows issues for external tables for this particular
version Tom?

  By the way, I was wondering why that 1 line space is there in between 3rd and
4th record in your output.

  Thank you.

          Followup:

just due to sqlplus formatting -- the data is OK:

ops$tkyte@ORA9I> column ename format a20
ops$tkyte@ORA9I> column dept format a30
ops$tkyte@ORA9I> select '"' || emp_no || '"'empno, '"' || ename || '"'ename, '"'
|| dept || '"'dept from emp_ext
  2  /

EMPNO                  ENAME                DEPT
---------------------- -------------------- ------------------------------
"1"                    "John"               "Marketing"
"2"                    "Blake"              "Logistics"
"3"                    "Smith"              "Personnel And
                                            Human Resources"

"4"                    "Harry"              "Finance"

I used 9.0.1.2 -- I don't have 9.0.1.1 -- don't know of anything as specific as
this being a problem.

      Rating:
      3
  April 03, 2003
Reviewer:
      A reader
could you please explain why you "firmly believe the best java programs are
those that have ZERO "selects/inserts/updates/deletes" in them."?

          Followup:

because PLSQL should have it all....

because then I can fix it without having to dig into the java code.

because then we can tune it.

because then we can figure out what uses what.

because then we can have people who understand databases do the database stuff.

because we can make them use bind variables for the sql (just have to force them
to use binds for the stored procedure calls)

and so on...

      Rating: 5
PERL is nice ,
      but ...  April 04, 2003
Reviewer:  pasko
       from Hamburg
Hi Tom and Piotr...

Thanks for a  great thread above...

I am also using PERL sometimes to interface Oracle with some Web GUI clients and
i have to always use  REF cursors as we can not pass PL/SQL Tables..

Perl is also very good for Pattern matching stuff...

I have also observed that although all Package 'IN' parameters should be seen as
BIND Variables in v$sql view ,
that's not the case when using PERL ...

I was surprized the other Day when i looked at this view and saw no BIND values
( :b1 , :b2 etc.. ) at all from all pERL  WEB client programs...

i see :

BEGIN  :curResult :=
my_package.ret_ref_cur_function('real value1' ,
 'real value 2'   );   END;

instead of :

BEGIN  :curResult := my_package.ret_ref_cur_function( :my_bind1 ,
:my_bind2       );   END;

I know it's messing up our shared pool...

Has anyone any idea how come perl does not use BIND variables even when using
stored subprograms in a Package ?

Thanks in advance.

      Rating: 4
Nothing wrong
      with Perl bind variables!  April 14, 2003

Reviewer:  Piotr Jarmuz  from Braunschweig, Germany

Hello,

I checked at my site and it all works great:
For example I look at library cache:

select sql_text from v$sql where upper(sql_text) like '%JOEYS%'

And I see in one of the rows something like this:

begin joeys.touch_message(:p1,:p2); end;

It DOES use bind variables. And the Perl code for this call is as follows:

$query="begin joeys.touch_message(?,?); end;";
eval {
    $sth=$dbh_proddb->prepare_cached($query);
    $sth->bind_param(1,$usr_id);
    $sth->bind_param(2,$msg_id);
    $sth->execute();
    $sth->finish();
    $dbh_proddb->commit();
}; if $@ {
    $dbh_proddb->rollback();
    handle_error();
    ...
}

Note:

It uses client side caching so I only do at most one hard parse and some soft
parses in all the Apache processes next requests use cached handle so no parse
at all.
More exactly if there are N Apache children and each does M requests on average,
this aproach gives:
i) 1 hard parse
ii) N-1 soft parses
iii)(M-1)*N no parse at all!

Not bad as for a "stateless" environment ;)

Beside look like I don't use autocommit "feature" and control commits and
rollbacks explicitly

As for the versions, we use:
1. Oracle 9iR2 EE client and server
2. Oracle DBD 1.06
3. Perl DBI 1.34

The only problematic issue I know about Perl Oracle DBD is that it is slow when
working with ref cursors returned from stored procedures due to not prefetching
on such refcursors. Might be fixed in the latest release. I got a newsletter
recently about that but I did not have much time to explore it yet...

Best regards

      Rating: 5
Perl Vs.
      Java  August 20, 2003
Reviewer:
      A reader
Hi Tom,
Now that there is Java in the database
would you suggest to still continue be using perl.

:) Thank you

          Followup:

if you got perl

if you know perl

if you like perl

if perl is satisfying your needs

seems like you should be using perl (i wouldn't, i don't have it, know it, like
it)...

but the same could be said of java
or vb
or C
or even plsql

      Rating: 5
Thanks
      :)  August 21, 2003
Reviewer:  A reader

      Rating: 5
Perl vs.
      Java  September 06, 2003
Reviewer:  Piotr
      Jarmuz  from Poznan, Poland
Paraphrasing W. Churchil words:

There are too many companies with too many programers that have invested too
much money in such a CRAPPY implementation! While Perl is Open Source...

But hey! Linux is too!

Java as language is coherent, complete but the implementation... it sucks!
Oracle's Autora is little different but still...

I think it sucks...

Think of Perl as SCRIPTED "C"... It's no more no less but a scripted C.

I might even set out to rewrite the JVM... soon...

BTW...
My Perl-Oracle integration is done...

Your book EO1O1 page: 1044 made me sure: there IS 1:1
UGA to extproc agent...

Regards.

          Followup:

(you probably don't want to know how much i shudder when I see perl code, i like
java better then perl)....

so, it seems to be a matter of preference...

      Rating: 5
Arrange a
      meeting...  September 07, 2003
Reviewer:  Piotr
      Jarmuz  from Poznan, Poland
Oracle vs. Perl
Larry vs. Larry

Larry Ellison - Larry Wall ;)

Regards.

GRID IS COMMING...

September 07, 2003 cosmin -- Thanks for the question regarding "re:
      data files & parallel execution", version 8.1.7

      You Asked
hi Tom,
we have 8.1.7 on a Unix platform with storage on 3 Raided PODS.  I was wondering
if performance of parallel execution (select, update, etc) is impacted by the
number of datafiles assigned to a tablespace.  In essence, we have some large
tables (hundreds of millions of rows) one of which is on a tablespace with one
datafile, one table is in a tablespace of 5 datafiles, one table is in a
tablespace of 9 datafiles (the datafile sizes also being uneven), all datafiles
of any one tablespace being on the same POD.  I thought, as a rule of thumb, if
at all possible, would be to create even numbered datafiles (possibly 2 x No. of
CPU's) and furthermore, spread those files among the PODS.

Even though the PODS are raided, it is my belief that evening the file #'s and
sizes as much as possible would greatly benefit parallel executions.  Is this
assumption correct?

thx much,
Cosmin

      and we said...
as long as the disks are striped, we'll dynamically partition the file up for
parallel operations.

you just want to make sure the tables are over as many PHYSICAL devices as you
can and that the IO is even when processing.

striping at the OS level with a single logical volume is just fine.

you can always BENCHMARK to get over "beliefs" :)

that is what I would do.

but a single file, striped at the os level, is going to be just fine.  You might
even find better actually -- less file open calls going on.
Review &
      Followup

      Rating: 5
thanks a bunch
      for your informative help !!!  September 06, 2003

Reviewer:  cosmin  from Florida

      Rating: 4
What is
      POD  September 07, 2003
Reviewer:  Doug
       from CT, USA
What is meant my POD, PODS in this case.

          Followup:

i was guessing

Plain
Old
DiskS

just like jbod is just a bunch of disks.

      Rating: 4
I meant
      "by"  September 07, 2003
Reviewer:  Doug
       from dcowles@us.ibm.com
I meant -
What is meant *BY* POD.. or PODS..

September 08, 2003 Brij -- Thanks for the question regarding "Log
      off from mod pl/sql app", version 8174

      You Asked
Hi Tom,

Hope you are well. Thanks for asking my questions in the past...These ones are
really bringing tears to my eyes!!!

My problems...(so many, don't know where to start).

i)

We developed an app which uses the mod pl/sql gateway using
IE5.5 as web client with an Oracle 8174 datavase server. What we
effectively need is a log out button/procedure. This is what we
would expect to happen

a) Log off event terminates oracle session and deauthenticates
user (logmeoff proc) and breaks connection between web client
and apache.

b) User can then re-log in to Oracle via broweser.

What actually happens is that although we can kill the oracle
session, the web client to web server connection is persistent
and I receive an 'ora-00028 not logged on' error message!
:-(...brings tears to my eyes!!! And I have to restart the http
server before I can log back in again...bad, very bad.

So what I really really need help on, is a method to break the
web client/web server connection...or any advice on how to
develop a log off procedure; perhaps disconnecting sessions is
not the best option!

ii) We want to monitor which users are invalidating license
agreements by logging on to our app via 2 remote
terminals...what I am doing is this:

     if ( in_hostname is NOT NULL ) then
        -- Direct connection over tcp/ip network
        dbms_application_info.SET_CLIENT_INFO(in_hostname);
     ELSIF (OWA.num_cgi_vars IS NOT NULL)  -- This is an Apache
session
     THEN
        -- Get IP address of browser from Apache assigned OWA
variables
        l_ip := (OWA_UTIL.get_cgi_env ('REMOTE_ADDR'));
        if( l_ip is NULL ) then
            l_errm := 'No hostname supplied, and has not nee
supplied by OWA variable OWA_UTIL.get_cgi_env()';
            raise err;
        end if;
        dbms_application_info.SET_CLIENT_INFO(l_ip);
     ELSE
        l_errm := 'No hostname supplied, nor is it available
from OWA variable';
        raise err;
     END IF;

However, dbms_application_info.SET_CLIENT_INFO(l_ip) seems to
hijack a single oracle session and reset this every time a new
user logs on to Oracle...again, very bad. Is there any way I can
force a one to one connection without turing connection pooling
off...this would be a real performance drag for us.

Thanks in advance...Your help is greatly appreciated.

Brij

      and we said...
why are you killing the session in the database?  It'll go away all by itself
after a period of disuse.  If the user logs back into the browser - it'll pick
up where it left off.

It seems you are trying to manage license control at the database level, which
will be quite impossible frankly in a 3tier environment like this.

Sounds like you need AUDITING more then anything.  Rather then trying to do this
"real time", just keep an audit trail that can be reviewed for license
compliance.

The only thing I've ever seen license compliance things do is prevent users from
doing their job, making them really really mad, breaking the system.

Having an audit trail will do everything you need AND provide you with a ton of
extra information/statistics.
Review &
      Followup

      Rating:
      2
Interesting...  December 12, 2002

Reviewer:  Brij  from London, UK
LOL @ impossible in three tier system!

Well, I do need to enforce restrictions such that a user cannot log on to the
appliaction twice from seperate terminals at the same time...How would you
propose this be done then. Is there any way to enforce stateful one-to-one
connections from web client -> mod pl/sql gateway -> oracle server???

Furthermore, there needs to be some way for a user to log out of the
system...other than just closing the browser??

Finally, if audit connects, this will give me a report/audit trail but can this
be leveraged for real-time licence management?

          Followup:

I won't propose anything for reasons I stated above.  I view licensing stuff
like this to be a really bad idea that only leads to heavily increased support
calls and maintenance.  I've been on the recieving end of far too many of these
"ideas" and license servers -- ugg.

Sorry, you'll have to ask elsewhere.

(you seem to know of logmeoff??  it logs you out)...

The PL/SQL Gateway allows users to log off (clear HTTP authentication
information) programatically through a PL/SQL procedure without having to exit
all instances of the browser.

This requires cookies.  A sample procedure for this this:

                create or replace procedure my_logout as
                begin
                    -- Send a redirect to a public home page
                    htp.p('Location: ' || 'AnyPublicURLYouChoose');
                    -- Send a cookie to indicate that that you want to logout
                    owa_cookie.send('WDB_GATEWAY_LOGOUT', 'YES', path=>'/');
                    -- Close the http header
                    htp.p;
                end;
                /

I would not audit connects, I'm talking an application level audit trail.  Every
page view, insert a row into a table with as much data as you want collected.

If you see a pattern of a user hitting a page on terminal "a" and then "b" and
then "a" and then "b" and then "a" in a brief span of time -- guess what, you've
got someone sharing an account.

Say you did it "real time".  UserA logs in -- does some work -- exits browser or
browser crashes or windoze blue screens.  (app server has no clue this happened
NONE, zippo).  UserA tries to log in from another terminal.  What now?  It is
not a license violation but you cannot tell the difference and that user cannot
log out cause their session is just totally gone.

      Rating: 3
logoff on web
      from pl/sql  December 12, 2002
Reviewer:  Yong
      Ke Wu  from USA
try make this call on your site

http://your_host:your_port/pls/DAD/logmeoff

      Rating: 5
Am i facing the
      same problem?  December 13, 2002
Reviewer:
      Yogeeraj  from Mauritius
hello,

My Database server (8.1.7.4) is hanging everyday at high load between 8:30 and
11:30 a.m. No errors in the alert_log. Users unable to connect and quite
strangely the number of connections keeps on increasing. (select count(*) from
v$session)

I am running 9iAS 1.0.2.2.1 patch 5a on HP-UX 11.0. I am running only Web forms
and Web Reports.

I am currently restarting the reports server every hour to clear the "zombie"
sessions created each time a user runs a web report.

Please guide me out of this problem.

Regards
Yogeeraj

          Followup:

Umm, think about it -- we have no code that says

 if time between 8:30 am and 11:30 am then
     hang the server, annoy the world
 end if

you need to sort of diagnose what is going on in your system between those hours
(sounds like morning startup -- people coming to work -- perhaps your server is
just undersized, cannot handle the load of starting all of those sessions).

If you have a reports server creating zombies continously -- well, that would be
somethig you would goto support and open a TAR about....

So, goto support, fix the zombie issue (perhaps you are simply running out of
processes due to the zombie processes and that is the issue).  Identify the
cause (not just the symptoms) of your issues.

      Rating: 5
Thanks for the
      guidance  December 13, 2002
Reviewer:
      Yogeeraj  from Mauritius
Hi,

thank you for the guidance. :)

Have just created a TAR for this problem.

Quite strange that the database is not complaining with any error messages...

How can i dissect a kernel parameter with an database initialization parameter
problem?

thank you for your guidance.

Best Regards
Yogeeraj

          Followup:

you are looking too low, too deep.

what does a kernel parameter have to do with this.

Look "higher up" the stack -- find out what the CAUSE is.

      Rating: 5
OK -
      thanks  December 14, 2002
Reviewer:
      Yogeeraj  from Mauritius
hello,

thanks. i am currently working with a Metalink Production Engineer. I have
already got a list of tasks (hang analysis, system trace and tusc) to be done
during the problem period (which i have already done today [14/12/2002]during
the problem occurance - 10:45 a.m.) and will have to upload the trace and other
diagnostics files. I am sure will be able to find our the real problem shortly.

We really appreciate overall Oracle commitment and support.

Best Regards
Yogeeraj

      Rating: 5
Correlate the
      Infasturecture DB session with Client's IP address  April
      30, 2003
Reviewer:  Sean  from Atlanta
Tom,

We have the web application system: the three tier 9iAS R2 and a customer DB 9i
R2.
In the 9iAS configuration, there are 2 clusters, which are 2 WEBCACHES and 2
OC4Js, and the infrastructure database, which is 9i R1.

I have just been brought in for looking into the performance issues in the
system. There were some users complaints the pages running slow. I have used to
trace application session to find out the most of the problems. Right I
understood the databases are only part in the equation, there are webcache/oc4j
can kill the performance.  But I want to start with which I felled comfortable,
the SQL_TRACE. However it is not any more! I have the problem to correlating
database sessions with the client PC’s IP addresses, which users get in the web
application form. The web application is on a few Sun’s Solaris. The problem I
have, I am unable to find out which client’s IP address in the
$ORACLE_HOME/Apache/Apache/logs/access_log triggered the creation of a PID in
the UNIX system. If I could, I was able to trace the session with the client IP
address.

Please advise the new tech to diagnosis of the performance problem.

Sean

          Followup:

why do you need a clients ip address?

you trace

you tkprof

you find bad sql

you fix (for all users)

why is the users ip address relevant?

      Rating:
      3
  April 30, 2003
Reviewer:  Sean
       from Atlanta
"why do you need a clients ip address?

you trace

you tkprof

you find bad sql

you fix (for all users)

why is the users ip address relevant?
"
Well, the problem with the 9ias Infrastructure DB.
There are these 9iAS schemas such as ORASSO, ODS. The tables in problematic
queries are owned by these Oracle 9iAS schemas. The shared pool only captures
part of the problem queries. The users may log out the web site, the problem
queries might be still in the shared pool. So you may not enable to trace the
sessions. You can not find out where they are belonged, such as Packages,
because the codes are encrypted. DBA_SOURCE are not used for viewing Oracle
preparatory codes. Right, called Oracle support, has gone on-where. The customer
claims there are a few thousands concurrent users at the peak time. If I can
trace back to the users in the front tier by IP address. I can call the user,
and then trace the problem queries.

          Followup:

v$sqltext_with_newlines has the entire query.

I still don't see how the ip address would help here at all in the situation you
describe??? why do you need a human here?  what could they provide?

      Rating:
      3
  April 30, 2003
Reviewer:  Sean

" The shared pool only captures
part of the problem queries."

I meant the query got truncated, as such

SELECT /*+ USE_nl(STORE) INDEX(STORE EL_ATTRSTORE) FIRST_ROWS
*/
DN.ENTRYID ....
...
WHERE AT1.TRR = :4 AND AT2.ATT =

          Followup:

v$sql_text_with_newlines

      Rating: 3
Can't get
      deauthentication to work using custom
      authentication  September 04, 2003
Reviewer:
      Stephane Gouin  from Canada
Hi Tom,

I designed a stateless app, (9iAS R2 and 9.2.0.3 database) using custom
authentication and global context. In my owa_custom.authorize function, I
validate the credentials, if successful, I set a cookie with a unique session id
(using sys_guid), which is also my client_identifier for my global context. The
authorize function checks for existence of session cookie on subsequent calls,
and if exists, resets the client_identifier using the cookie value.

The problem I am running into is I can't seem to log the user out using any of
the techniques outlined in various threads on asktom. I tried using logmeoff,
but the HTTP authentication info does not get cleared (I still get in). I also
tried using the cookie approach, but it does not work, since the authorize
function does not know if this is a first login attempt (new browser session) or
another login following a logout.

The only solution seems to be to rely on users closing their browsers...

Thanks for the help

God bless

          Followup:

using your custom auth -- it is really easy.

write yourself a logoff procedure

let the user call it.

it'll set your cookie to some meaningless value which the remaining routines, if
called will say "hey, you are not logged in"

you have it "easy" really -- all you need do is mess up the cookie and they are
logged out.

      Rating: 2
Not sure if I
      understand the solution...  September 04, 2003

Reviewer:  Stephane Gouin  from Canada
Thanks for the prompt reply.

Here is a basic flow of what I am doing in the owa_custom.authorize function:

1. check for user_id in the header, return false if null
2. if user_id is good, then fetch the session cookie
3. If session cookie is null, this is a new login, so fetch context data from DB
using user_id, set the session cookie with sys_guid as value, set default
application context and return true
4. if session cookie is not null, user is already logged in so reset the
client_identifier in my global context with the session cookie value and return
true
5. any exception, return false

I'm using the session cookie to "re-connect" my user's browser session to the
global context in the database, and also to check if he logged in already, so I
don't have to set the context again, and authenticate his credentials again...,
if the session cookie is set with a valid session id, then I set the
client_identifier and return true.

Not clear on how the proposed solution fits in this implementation...

Thanks again for your help

God bless

          Followup:

all your log off function needs to do is

a) set user_id null.

then your step 1) will return false.  user is not logged in anymore.  right?

      Rating: 2
That is the
      idea  September 08, 2003
Reviewer:  Stephane
      Gouin  from Canada
That's essentially what I want to do, since my function basically assumes if
user_id in the HTTP header is null, then this is your first login attempt.

The issue though, is how to set it back to null. I have read through
documentation and posts, (asktom, metalink, etc), but could not successfully
implement.

Logmeoff seems to be suggested quite often, but it doesn't seem to work when
using custom OWA. BTW, the DAD user is the actual schema object owner as well,
so maybe that has something to do with it? This is how our dev is set-up, but
our prod DAD will have its own user, with controlled access to the main schema,
via roles.

          Followup:

sorry -- don't know.  never really used custom authentication myself.  I always
use database authentication.

September 08, 2003 zaibi -- Thanks for the question regarding
      "problem with import", version oracle 7.3

      You Asked
hi,

I am facing following error while import.
please solve,.

With the distributed option
PL/SQL Release 2.3.4.0.0 - Production

Export file created by EXPORT:V07.01.04
. importing OMS's objects into OMS
IMP-00017: following statement failed with ORACLE error 1658:
 "CREATE TABLE "OMS_BILL_BACK" ("CNSG_NO" VARCHAR2(12),
"BILL_REF#" VARCHAR2("
 "10), "BKG_DAT" DATE, "CUS_NO" VARCHAR2(10), "SHP_TYP_NO"
VARCHAR2(3), "PROD"
 "_NO" VARCHAR2(2), "SERVICE_NO" VARCHAR2(3), "SEC_NO"
VARCHAR2(3), "ORGN" VA"
 "RCHAR2(3), "DSTN" VARCHAR2(3), "WTT_BKG" NUMBER(9, 2),
"WTT_ACT" NUMBER(9, "
 "2), "PAY_MODE_NO" VARCHAR2(3), "BKG_STAF_NO" VARCHAR2(5),
"OT_SRVS_AMT" NUM"
 "BER(12, 2), "HNDLG_CHRG" NUMBER(12, 2), "OTHER_CHRG"
NUMBER(12, 2), "AMOUNT"
 "" NUMBER(12, 2), "PARTNER_AMT" NUMBER(12, 2), "PART_COMM"
NUMBER(12, 2), "A"
 "MT_CAL" NUMBER(12, 2), "HNDLG_INST" VARCHAR2(3), "EXCISE"
NUMBER(11, 2), "C"
 "NSG_VAL" NUMBER(12, 2), "INSUR_AMT" NUMBER(10, 2), "ROUTE_NO"
VARCHAR2(5), "
 ""USERID" VARCHAR2(20), "SYSID" VARCHAR2(10), "DISCOUNT_SHIP"
NUMBER(12, 2),"
 " "DISCOUNT_BOTT" NUMBER(12, 2), "DISCOUNT_FREQ" NUMBER(12, 2),
"INCEN_AMT" "
 "NUMBER(12, 2), "CREATE_DATIME" DATE, "SCAN_STAT2" VARCHAR2(1),
"SCAN_DATE" "
 "DATE, "RPT_DATE" DATE, "EOD_STATUS" VARCHAR2(1), "BILL_STATUS"
VARCHAR2(1),"
 " "INC_STATUS" VARCHAR2(1), "CUT_OF_DATE" DATE, "PED"
NUMBER(11, 2), "OCTROI"
 "" NUMBER(11, 2), "CMD_CODE" VARCHAR2(3), "PHASE" VARCHAR2(2),
"STAFF_NO" VA"
 "RCHAR2(8), "DISCOUNT" VARCHAR2(1))  PCTFREE 10 PCTUSED 40
INITRANS 1 MAXTRA"
 "NS 255 STORAGE(INITIAL 14735360 NEXT 1048576 MINEXTENTS 1
MAXEXTENTS 121 PC"
 "TINCREASE 0 FREELISTS 1 FREELIST GROUPS 1) TABLESPACE "OMS""
IMP-00003: ORACLE error 1658 encountered
ORA-01658: unable to create INITIAL extent for segment in
tablespace OMS
Import terminated successfully with warnings.

      and we said...
That seems to be somewhat self explainatory.  You are getting

ORA-1658: unable to create INITIAL extent for segment in
tablespace OMS.

That means the tablespace OMS you have setup does not 14m of
contigous free space in it.  Your table is initial 14m next  1m
-- it needs 14m of contigous space to create.  You do not have
that.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:112012348062
for a script that will show you how much space is in a
tablespace as well as the largest contigous free chunk of space.

Your options are:

o create the table before you import -- use a smaller INITIAL
extent.  when you import, use IGNORE=Y to ignore object creation
errors (so import will just load the table, not create it)

o add more space to the tablespace or, if there are lots of free
space chunks that are adjacent, try issuing "alter tablespace
OMS coalesce" and then running the FREE.SQL script again to see
if you have 14m or better as the largest chunk.

Review &
      Followup

      Rating: 4
Import table's
      Indexes  July 02, 2002
Reviewer:  Irfan
       from Kuwait
Hi Tom,
I have got enough space in tablespace and when trying to import tables, I get
the same error during import of index of that table.

IMP-00017: following statement failed with ORACLE error 1658:
 "CREATE INDEX "HISTFILEIX" ON "HISTFILE" ("DAT" , "TRA" )  PCTFREE 10 INITRANS
2 MAXTRANS 255 STORAGE (INITIAL 74618880 NEXT 37285888 MINEXTENTS 1 MAXE "XTENTS
121 PCTINCREASE 50 FREELISTS 1) TABLESPACE "HISTDATA""
IMP-00003: ORACLE error 1658 encountered
ORA-01658: unable to create INITIAL extent for segment in tablespace TRADING

Every time I manually create this index without initial extent again. I have
nearyl 143 MB of free space in that tablespace whereas the initial extent
requires only 74 MB.
Thanks...

          Followup:

You do not have enough free contigous space then.

re-read the answer here -- it describes exactly your issue.

....
That means the tablespace OMS you have setup does not 14m of contigous free
space in it.  Your table is initial 14m next  1m -- it needs 14m of contigous
space to create.  You do not have that.
......

you may well have 143mb of free space but the largest contigous chunk of free
space may well be something like 1m (you might have 143 - 1mb free chunks.  you
therefore don't have a 74mb free chunk, hence the allocation will fail)

a script (link to a script) is provided here.

      Rating: 4
SUN
      CHEN  July 29, 2002
Reviewer:  SUN CEHN
       from HK
HI, Tom
  I run the following sql scripts in oracle8i and export the results to the
file.txt. But I find that the output of the file have space between the records.
and I don't want this. How can I solve it and why it happened? thanks........

REM  (1) Control the output format

set serveroutput on size 50000
set lin 500
set echo off
set feedback off
set pagesize 50000
set arraysize 100
set verify off
set underline =
set colsep |
REM  (2) Accepting input parameters

accept v_outdir prompt 'Please input directory for outputing file (e.g. C:\sis):
'

accept v_ac_yr_mth prompt 'Please input the extract A/C Year Month (YYYYMM): '

define p_str_dt = '01'

set head off
select 'Please refer to &&v_outdir\Motor_Claims_Enquiry'||'.txt for the output
file' from dual
/
set head on

spool &v_outdir\Motor_Claims_Enquiry..txt

 select b.Channel Channel,
        b.Policy_No,
        b.Name_of_Insured,
        b.NCD,
        b.Vehicle_No,
        b.Make,
        b.Model,
        b.Year,
    b.Date_of_Accident,
    km.key_desc Cause_of_loss,
    TO_CHAR(b.Paid, '999,999,990.00') Paid,
        TO_CHAR(b.os, '999,999,990.00') OS
 from
     (select ltrim(substr(km.key_desc, instr(km.key_desc,' '))) Channel,
             a.Policy_No,
         a.Name_of_Insured,
             a.NCD,
         a.Vehicle_No,
         a.Make,
         a.Model,
         a.Year,
         a.Date_of_Accident,
         a.cs_of_lss,
         a.Paid,
         a.os
        from (select am.sbu_main_cd,
             cm.pcy_no Policy_No,
             p.isrd_nm Name_of_Insured,
                     m.ncd_prctg NCD,
             m.rgtn_no Vehicle_No,
             m.cmpy_mk Make,
             m.mdl_of_car Model,
             m.rgtn_yr Year,
             cm.acdt_dt Date_of_Accident,
             cm.cs_of_lss,
             cm.clm_pymnt Paid,
                 cm.grs_rsrv os
        from ac_m am,
             clm_m cm,
             pcy_info p,
             mtr m
           where cm.comp_cd = am.comp_cd
             and cm.ac_no = am.ac_no
             and substr(cm.pcy_no, 4,7) = p.pcy_srl
             and cm.pkge_typ_cd = p.pkge_typ_cd
             and cm.prcp_cd     = p.prcp_cd
             and cm.endt_yr     = p.endt_yr
             and cm.endt_srl    = p.endt_srl
             and cm.itm_ky = m.itm_ky
         and trunc(rgtn_dt) >= (select fm_tx_dt
                                          from gl_intrfc_prd_m
                                         where ac_yr_mth =
trunc(to_date('&&v_ac_yr_mth&&p_str_dt', 'YYYYMMDD'), 'YYYY')
                                           and comp_grp_cd = 'SW')
         and trunc(rgtn_dt) <= (select to_tx_dt
                                          from gl_intrfc_prd_m
                                         where ac_yr_mth =
to_date('&&v_ac_yr_mth&&p_str_dt', 'YYYYMMDD')
                                           and comp_grp_cd = 'SW')) a,
         key_mast km
     where a.sbu_main_cd = km.key_value
       and km.key_id = 'sbu_main_cd') b,
        key_mast km
  where b.cs_of_lss = km.key_value
    and km.key_id = 'cs_of_lss'
  order by b.policy_no
/

spool off

set verify on
set feedback on
set echo on

          Followup:

how nice -- a query I cannot possibly run.  Hmmm.

I'll guess (after running your script with "select * from emp")

you have set linesize 500
you do not have trimspool on

each line is therefore 500 bytes wide, you really don't have space between the
records rather your editor is wrapping the text and it LOOKS like space between
the records.  You just have a bunch of 500 byte records is all.

set trimspool on

and try again.  I gather it "used to work" in 7 or 8 and you are saying "it
doesn't work" in 8i.  I suspect if that is the case then in 7/8 you had a
login.sql that does the set trimspool and you forgot to move that to 8i.

      Rating: 5
Not importing
      table's indexes  September 08, 2003
Reviewer:
      Ishaque Hussain  from Fords, NJ USA
We are using Oracle version 8.1.7.4.5.  We are trying to import data into an
empty table.  The table doesn't have any indexes on it.  We have in the import
clause indexes=n.  However, the import is still trying to create indexes and the
import is failing because there is not enough space for the index.  Is there
some kind of order in which to write the import statement or is there another
parameter that we could be missing?

          Followup:

constraints=no as well ?  perhaps you are seeing the primary key/unique
constraint indexes?

September 08, 2003 -- Thanks for the question regarding "advantages
      and disadvantages of indexes, cursor usage, what are x$ tables.", version
      8.1.7

      You Asked
Tom

I recently attended an interview.. one of the question was to tell the
advantages and disadvantages of using indexes.

my answer was that indexes are useful for faster access to rows in a table,
where as they can be disadvantageous when the table size is small and we use the
index to get the data.

Could there be a more accurate and effective answer?

Further

Replying to when we would be using cursors?  I answered when we need to return
more than one row from a query then we will use a cursor, else we will use a
select statement?

Could there be a more accurate and effective answer? What is the main advantage
or advantages of using cursors?

Further..

What is the use of x$tables, as far as I know we use them to see the definiton
of the v$ views? is there more to it?

Thanks

      and we said...
Indexes are useful when

o you want to access a small percentage of the rows in a table, say less then 5%
for a small table and less then 15% for a larger table.

o the index itself can be used to answer the query.  for example, it is
advantageous to use the primary key index to answer the "select count(*) from T"
query using a fast full index scan as the index is generally many times smaller
than the table itself.

Indexes can be a bother when you have over-indexed the table (eg: you have an
index on (a,b,c) and (a,b) and (a) or indexes you NEVER use).  Then they slow
down the DML operations of insert/update/delete un-necessarily.

As for "when to use cursors" -- sort of funny since:

   update t set x = 5;

is done using a cursor (an implicit one) as is:

   select * into x from dual;

as is:

   for x in ( select * from emp ) loop

as is

   open c1
   loop
      fetch c1 into record;
      exit when c1%notfound;
   end loop;
   close c1;

those are ALL cursors.  You won't be doing any SQL in Oracle without a cursor!

I would suppose however they are asking "when would you procedurally loop over
the rows in a result set?"  -- which is a really broad question that I would
have them refine or clarify.  I would do it whenever I needed to access each
individual row in a query (which is pretty much every time I run one).

X$ tables are undocumented tables that are based on Oracle data structures.
99.9% of the time they are not of general use as their contents and structure
changes from release to release.  You must be careful when using them as they
may introduce performace issues upon querying and since their contents are
undocumented and ever changing -- basing decisions on their contents can be
suspect.
Review
      & Followup

      Rating: 5
Pls reply these
      Questions Immd. Tom pls,  September 08, 2003

Reviewer:  raja  from India

hi tom , how are u ?

    pls reply below questions Immd....Tom,

     what are the Advantages and Disadvantages of Normalization ?

     what r the advantages and Disavantages of Indexes ?

     what is Explain Plan ? pls give clear Idea about this ?

     what is the Difference Between Simple Loop and For Loop in PL/SQL 8i ,

     Is    simple Loop is Using in case of For Loop and Viseversa ?

        ( simple Loop :
               loop
                statement's;
                exit condition;
               End Loop
        )

     How to count the rows in two table's using Single Query without
       using Union ? (Suppose Dept has 4 rows and Emp has 10 rows result to get
14 in a Single Query ?)

     pls reply above Question Immd Tom,....

  Thanking u ,
    raja.

          Followup:

geez, "please reply immediately".  hmmmmmmm

I'll say this though -- your keyboard is failing.  It is dropping some of the
MOST important letters in this language we are using -- vowels.

these are broad ranging big questions, not really germane to the original
question.  When I'm taking new ones, feel free to post there.

there are books written on normalization and data structures.  if you are
interested, my recent book "Effective Oracle by Design" covers topics like this
-- but I do not go into normalization per say (except to say -- hey, do it when
it works, ignore it when it doesn't)

the index question seems obvious isn't it?

explain plan -- documented in the performance guide.... fully.  (lots in my book
too)

don't know the "made up" term "simple loop"

why you wouldn't want to use a "union ALL" (union wouldn't be very effcient) is
beyond me

select count(*)
 from ( select null from emp union all select null from dept )

but

select (select count(*) from emp ) + (select count(*) from dept ) from dual;

September 08, 2003 -- Thanks for the question regarding "Renaing
      index name", version 8.1.5

      You Asked
We have a unique index (test2_x01) implicitly created to enforce
the primary key constraint as follows:

CREATE TABLE DBATEST.TEST2 (
    COLA                           CHAR(160)         null,
    COLB                           DATE              not null,
    CONSTRAINT TEST2_X01 Primary Key (COLB)
        USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255
        TABLESPACE USERS
        STORAGE (INITIAL     81920
             NEXT        49152
             MINEXTENTS  1
             MAXEXTENTS  121
             PCTINCREASE 0) )
        MAXTRANS 255 INITRANS 1
        PCTFREE 10 PCTUSED 40
        TABLESPACE TOOLS
    NOPARALLEL
    STORAGE (INITIAL     262144
             NEXT        262144
             MINEXTENTS  1
             MAXEXTENTS  505
             PCTINCREASE 0 )
;
I then issued
alter index dbatest.test2_x01 rename to test2_newx01;

The alter was successful.

However, when I selected from dba_constraints, the
constraint_name
remained  unchanged as shown below.

I would expect the constraint name to be renamed as well.
Is this a bug ?

OWNER    CONSTRAINT_NAME    CONSTRAINT_TYPE    TABLE_NAME
SEARCH_CONDITION    R_OWNER    R_CONSTRAINT_NAME    DELETE_RULE
  STATUS    DEFERRABLE    DEFERRED    VALIDATED    GENERATED
BAD    RELY    LAST_CHANGE
-----    ---------------    ---------------    ----------
----------------    -------    -----------------    -----------
  ------    ----------    --------    ---------    ---------
---    ----    -----------
DBATEST    TEST2_X01           P                  TEST2

ENABLED    NOT DEFERRABLE    IMMEDIATE    VALIDATED    USER NAME
         00 11 28 09:26:56
1 row affected
Script Completed: NOV-28-2000 09:33:18

Thanks.

      and we said...
It is not a bug -- the constraint and index name do not have to
be the same.  By default, if the constraint must create the
index -- it'll name it after itself.

You can create an unique index named "foo" on t(x).

Now add a constraint "bar" that is unique(x) on T.

The index will remain "foo" and the constraint will be named
bar.  The constraint "bar" will use the index "foo".

The fact that a constraint MAY NEED an index does not mean imply
or mean that the constraint "owns" the index.  They are 2
independent objects and can be manipulated different.
Review &
      Followup

      Rating: 1
Renaing index
      name  November 26, 2002
Reviewer:  Eran
      gavriel  from Israel
i need to how still rename a primry key (index and the coresponding constaint)
if if they are two different objects.

is  it possible to rename a constraint?

          Followup:

In 9i, yes, you can rename a constraint.

      Rating: 3
in 8.1.7.x you
      can do this. Not sure about 8.1.5!   November 26, 2002

Reviewer:  Prince
alter index <old_name> rename to <nex_name> ;

      Rating: 4
I haven't seen
      the solution is already given! Please ignore the above
      posting  November 26, 2002
Reviewer:  Prince

      Rating: 5
Renaming
      Index  September 08, 2003
Reviewer:  M
      Hamilton  from Rolla, MO USA
Could not find the correct syntex in Oracle books, but found it here.  And IT
WORKED!  Thanks so much!!
M

          Followup:

really -- no one tried the documentation?

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a85397/stateme6.htm#2050160
it is surprising what you might find in there ;)

      Rating: 5
Oracle has
      documentation?  September 08, 2003
Reviewer:
      A reader
You mean you actually tell how to use Oracle? Wow! What a concept!

          Followup:

well, i guess in retrospect -- this one might have been a hard find.  there
unfortunately is that legacy "rename table" command and that might have sent
them off to the wrong direction.

September 08, 2003 -- Thanks for the question regarding "Using DDL
      in pl/sql", version 8.1.7

      You Asked
As I understand (and have done on occasion) DDL is permitted in pl/sql so long
as it is executed as dynamic sql and explicit permissions are granted.

But, Why are DDL statements in pl/sql (e.g., Drop Table xx;, or ALter table ...
; )  illegal?  What is the reasoning behind this?

      and we said...
let me turn it around

why SHOULD they be legal?????

but in short, plsql is a statically compiled language.  It tries to verify and
validate all sql and set up dependencies.  this lets us do things like code:

  for x in ( select * from emp )
  loop
     ...

very natually.  plsql figured out what emp was, how to set up a record for it
and created a dependency between this procedure and the table EMP -- so that any
change to EMP would cause this to recompile and "fix itself"

So, if we had:

begin
   create table t ( x int );
end;
/

in a procedure, we are in "trouble" already.  there is no table table to be
"bound to", to be dependent on.  We cannot validate stuff -- the entire model
plsql works under is "broken"

enter dynamic sql -- the ability to get around this dependency, compile time
binding feature.

That is why dynamic sql will work -- no dependency, no checks.

it is very rare to do DDL in plsql -- very very very rare.  And the few times
youy need it, dynamic sql does it.  And hopefully makes it obscure enough people
don't feel tempted to do it frequently!!!
Review &
      Followup

      Rating: 5
Always great
      explanations  September 08, 2003
Reviewer:
      A reader
Thank you for taking the time to answer.

September 08, 2003 -- Thanks for the question regarding "starter
      db", version 920

      You Asked
Hi Tom,

When I use dbca to clone the starter db, it creats a lot
of users, like, Scott, HR, SH, OUTLN, etc, and lot of tablespaces
in Oracle 920.

My question is: do you have scripts to clean the starter db
to be like the one manually created?

I mean, for users, only sys/system/outln/dbsnmp,
and for tablespaces, system, maybe untotbs, temp, tools, and
users only.

Thank you!

      and we said...
use dbca to create a template database and use that to create what you want.

you can then create one of them and use RMAN to clone that database over and
over and over where ever you want (just back it up and restore it where ever)
September 08, 2003 Carlos -- Thanks for the question regarding "View
      or table to query for droped database objects -tables-", version 9.2.0.1.0

      You Asked
Is there a view or table to query for droped database
objects like tables? I am using 8i and 9i.

Thank you very much

Carlos

      and we said...
it is hard to query for that which no longer exists.

unless you had auditing enabled, we do not keep track of that information -- no,
not in 9i and before.
Review & Followup

      Rating:
      3
  September 08, 2003
Reviewer:  Carlos
      Gongora  from Cochabamba Bolivia
Thanks for your answer. ¿How can I enable Auditing in the Database?

What I was needing this for is to have a log of dropped objects where I can make
some querys.

Carlos

          Followup:

you would check out the ADMIN guide, there is a chapter on auditing.

Until auditing is enabled, you won't have such a list.

you could "roll your own" with a database trigger "after drop", but auditing
does it easier.

September 08, 2003 Wayne -- Thanks for the question regarding
      "ORA-1000 Maximum Cursors Exceeded", version 8.1.5

      You Asked
Tom,

I have written a Java servlet to handle database access for a
site
and will recieve the ORA-1000 error on the servlet indicating
that
there exists too many open cursors.

The calls look similar to the following:

       Connection con = conn.getConnection();

       cstmt = con.prepareCall("{call getPaletteByID(?,?)}");
       cstmt.registerOutParameter(1,OracleTypes.CURSOR);
       cstmt.registerOutParameter(2,OracleTypes.CURSOR);

       // send the query to the database
       conn.sendCallable(cstmt);

       // out parameters are placed in vector outside of here

       Vector rsets = conn.receiveVector(2);
       java.sql.ResultSet rs = (ResultSet) rsets.elementAt(0);
       java.sql.ResultSet rs1 = (ResultSet) rsets.elementAt(1);

       // processing of result sets

       rs.close();
       rs1.close();
       cstmt.close();
       conn.close();

I thought that this should close the cursors but it appears that
it does not.  Am I right to assume that this is the problem and
if so is there any way I can close those cursors from the java
servlet.

I appreciate any help you can offer.

thanks,

wayne

Tom,

Your suggestions guided us to the correct spot to discover that
our connection management was reusing a PreparedStatement object
without closing it from the previous use on two particular
methods.

thanks,

wayne

      and we said...
o while this servlet is executing, please review the contents of
v$open_cursor view for this session.  That will show the cursors
that are still open (if visible).  Let me know what you see in
there.

o I was working on a similar issue and what it turned out to be
in there case was they were using their own connection pooling.
Each time they "grabbed a session" -- they were executing an
"alter session" statement.  This one statment (which will not
show up on v$open_cursor btw) was not being closed (an oversight
on their part).  closing it fixed their issue -- can you verify
that something like that is not happening here?

Review &
      Followup

      Rating: 3
the same
      issue...  October 08, 2001
Reviewer:
      Senthil
Tom...
as i was looking for the same issue in your site...i found this message. We use
java, weblogic and Oracle 8.1.7. When user started executing the query...i am
able to see the package being called by the user...and also SELECT statements
used in the procedure(package)...that uses ref cursor as a OUT parameter.
Everytime...when they issue close(Result set, Callable statement, Connection) on
the application side...its not closing the actual sql statements. At
somepoint...i am getting ORA-1000 error. As your 2nd point says...i am not
seeing any other sql/cursor statements. What and where is wrong. Would you
please give me some idea...
thanks.

          Followup:

You are not closing that cursor -- You must be missing a close.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:553222846752
I've seen this a thousand times -- you are missing a close on the result set.

      Rating: 4
how to close
      the open DB cursors  September 08, 2003
Reviewer:
      lenny  from Madison, wi
Okay so I have a java app running on Websphere against my oracle 8.1.7.4.1
database and I am seeing cursors left open.  I know it is just a matter of time
before I see ora-1000.  This is a fairly new app and is only in pilot with a
small number of users(150) that are migrating from our C/S app.  I wrote a quick
vb.net app to parse every line of the java app and report on unclosed
connections, prepared statements, and resultsets.  The report is big and my dev
team is working on it.

I can't easily bounce the db and raise the open_cursors parameter and I would
rather not flush the shared pool.  Can you make any other short-term fix
recommendations to deal with the problem or close the cursors?

Thanks in advance.

          Followup:

you have a bug in the code.  unfortunately, we cannot unbug buggy code.  In this
case, you'll need to bounce the app server or convince it to close connections
on a recurring basis to release the cursors they are leaving open.

Oracle cannot tell "they are really done with that cursor", it would be a fatal
error for us to close them.

flushing the shared pool will have NO effect btw -- an open cursor is a session
thing.

      Rating:
      3
  September 09, 2003
Reviewer:
      A reader  from Midwest City,OK
try commenting conn.close();

September 08, 2003 Oleg -- Thanks for the question regarding
      "hierarchy nested table", version 9.0.1

      You Asked
How create table?
create type a as object(
  n1 number);
/
create type aa as table of a;
/
create table l_a (
 id number,
 list_aa aa)
nested table list_aa store as tab_list_aa;
O'key.

create type b as object(
  k1 number,
  b_aa aa);
/

create type bb as table of b;
/

create table l_b (
 id number,
 list_bb bb)
nested table list_bb store as tab_list_bb;

ORA-02320:
ORA-22913:
What I must indicate for to create nested "nested" table?
           Thank you, Oleg.

      and we said...
In 9i -- which supports multi-level collections (collections of collections) you
have to specify where ALL of the nested tables will go.  You have two of them
and only tell us about one:

tkyte@TKYTE9I.US.ORACLE.COM> create table l_b (
  2   id number,
  3   list_bb bb)
  4  nested table list_bb store as tab_list_bb ( nested table b_aa store as
tab_list_aa2 );

Table created.

will do it.

Consider:

create type array1_type as table of number
/
create type array2_type as table of array1_type
/
create type array3_type as table of array2_type
/

and:

create table t ( x int,
                 single_dim array1_type,
                 two_dim    array2_type,
                 three_dim  array3_type )
 nested table single_dim store as single_dim_table
 nested table two_dim    store as two_dim_table ( nested table column_value
                                                   store as two_dim_table_2 )
 nested table three_dim store as three_dim_table
       ( nested table column_value store as three_dim_table_2
           ( nested table column_value store as three_dim_table_3 )
       )
/

Review &
      Followup

      Rating:
      4
  September 08, 2003
Reviewer:
      Anita  from Pittsburgh, PA
Can you please give me an example to use this nested table

Thanks a lot.

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96594/adobjbas.htm#462243
in the application developers guide for object relational features -- lots of
examples of the various capabilities.

September 08, 2003 Marcio -- Thanks for the question regarding
      "selection / Projection", version 920

      You Asked
Tom, I bear in mind this:

Selection:
select * from emp;

Projection:
select ename, sal from emp;

Now, even when I use predicate WHERE selection and projection are keeping been
the same. I mean:

Selection:
select *
  from emp
 where deptno = 10;

Projection:
select ename, sal
  from emp
 where deptno = 10;

Is that correct?

Thanks,
Marcio

      and we said...
projection is the columns you selected,
where is a selection.

it can be confusing -- SELECT is not a selection, it is a projection.

http://www.dbazine.com/celko3.html
has an overview.
Review & Followup

      Rating: 5
Sample of tha
      selection / Projection  September 08, 2003

Reviewer:  Marcio  from br
Well, let's see:

- When select pick up a column or so, without predicate is projection -- like
this:

select ename, sal
  from emp;

But if put some predicate this is projection with selection -- like this

select ename, sal
  from emp
 where deptno = 10;

- Now if you pick up all of columns is a selection either with or without
predicate -- like this:

select * from emp;
select * from emp where deptno = 10;

Well sooo confuse.
Is that correct?

          Followup:

projection = set of columns you want

selection = set of rows you want

forget about "*", "*" is just shorthand for every column.  It is STILL a
projection as far as I'm concerned.

      Rating: 5
To
      Marcio  September 08, 2003
Reviewer:
      Tatiane  from RJ, Brazil
Marcio,

I think that you mean "restriction", not "selection" when you use a where
clause.

Beijos, Tatiane

          Followup:

no, we mean "selection"

that is sql-ease for "predicate"

goes back to the relational algebra mucky muck it is all based on.

September 08, 2003 Marcio -- Thanks for the question regarding
      "shrink raw device", version 920

      You Asked
Is possible shrink raw devices?

thanks,
Marcio

      and we said...
oracle can grow and shrink the amount of a raw device we use -- but as for
whether a raw device can be shrunk -- that would be a question for your
operating system/volumne management software vendor -- not me.

We can shrink the amount of raw disk we use
We do not manage raw disk (yet :) in 9i and before
Review &
      Followup

      Rating: 4
Can 10g manage
      raw?  September 08, 2003
Reviewer:
      A reader
Yet another teaser about 10g? Can you say now? 10g is already unveiled.

          Followup:

10g comes with a feature calls ASM or automated storage management.  you give us
the disks, we manage them -- like a logical volume manager.  striping,
mirroring, dynamic balance/rebalance, etc.

quick peek:

http://www.oracleworld2003.com/catalog/catalog/sessionDetail.jsp?SESSION_ID=40140&form=searchform
(if that link no longer works in the future, don't sweat it -- just look it up
in the documentation for 10g and above)

September 09, 2003 Krishnamoorthy -- Thanks for the question
      regarding "Support for Oracle 8i", version 9.0.2

      You Asked
Hi Tom,

Thanks you very much for your wonderful replies to my previous questions.

Oracle 8 mainly conentrated on Very Large Database(VLDB). 8i and Oracle
9i,concentrates mainly on Internet technology. And now oracle 10G is mainly on
Grid computing. Of course there are various other enhancements are also there.

Today oracle lauches officially the Oracle 10G. When It is expected to relase?..
If Oralce 10 G is relased how long oracle will suporrt oracle 8i?.

When will be the next version of forms will be relased?..

Regards
K.Sankar.

      and we said...
Well, Oracle8 was a ton of features -- it was "big" and "objects"

to say 8i and 9i were mainly "internet technologies"???  external tables?
merge? data guard? locally managed tablespace?  oracle managed files (etc etc
etc)

don't get lost in the name.

but -- the next release is due out this calendar year.

Oracle8i support for defect resolution ends at the end of this calendar year.
it will still be supported (contact support for all of the details and dates)
after that.

As for forms, there is a new release of Oracle AS 10g coming out in a month or
two, forms goes with the application server - so that is when forms will be
coming next.
September 09, 2003
      rama -- Thanks for the question regarding
      "regarding pl/sql cartridges.", version 4.0

      You Asked
Hi Tom...

 This is my first question to u...hoping for an early reply.

My question is : Is there a way where i can incrementally
populate a page.
I have a pl/sql cartridge which generates and loads the data.
Now..if data is slightly huge...say about 10000 records...
entire data is loaded all at once. I want first 50 records to be
displayed immediately..and then the next records..so that , the
end user does not feel that data is delayed.

I know, if i use ASP, flush() will work...something similar to
that in pl/sql cartridges.

Thanks in advance.

Regds...
rama

Hi Tom..

  Thanks for the quick reply. Yes, I am very much interested
in META DATA . Please suggest a solution as it is an important
requirement here with High Priority.

Thanks in advance.

Regds..
rama

      and we said...
Here is the example.  You would run "run_the_long_running" from
the browser.  It submits the long running job to the job queue.
Every 5 seconds, it'll have "monitor_the_long_running" monitor
the job in the job queue and display some informative message as
to the progress.  The actual long running job should call
dbms_application_info.set_action to inform the end user as to
its progress:

create table long_running_tbl
( session_id int primary key, message varchar2(2000) );

create or replace
procedure long_running( p_session_id in number )
as
begin
    -- the next three statements should be the first 3
    -- if ANY long running job...
    update long_running_tbl
       set message = 'Running'
     where session_id = p_session_id;

    dbms_application_info.set_client_info
    ( 'lr ' || p_session_id );
    commit;

    -- here is your logic.  call set action to tell the end
    -- user what is going on...

    for i in 1 .. 10
    loop
        dbms_application_info.set_action
        ( 'Looping, i = ' || i );

        -- if you run this demo, you will need to have SYS
        -- or INTERNAL grant you execute on dbms_lock!
        dbms_lock.sleep( 10 );
    end loop;

    -- these next 2 should be the last two of ALL long running
    -- jobs...
    delete from long_running_tbl where session_id =
p_session_id;
    commit;
end;
/

create or replace procedure run_the_long_running
as
    l_url         varchar2(100) default
    '
http://aria.us.oracle.com/wa/webdemo/owa/monitor_long_running?p_session_id=';
    l_jobid       number;
begin
    dbms_job.submit( l_jobid, 'long_running( job );' );
    insert into long_running_tbl values
    ( l_jobid, 'Waiting to start' );
    commit;

    htp.p( '<HTML>
            <HEAD>
            <META HTTP-EQUIV="Refresh"
            CONTENT="5;URL='||l_url || l_jobid || '">
            <TITLE>Monitor</TITLE>
            </HEAD>
            <BODY>
            <H1>Please wait, Waiting for job to start...</h1>
            </BODY>
            </HTML>' );
end;
/

-- the owner of this procedure needs to have been granted
-- SELECT on v_$session by SYS or INTERNAL

create or replace
procedure monitor_long_running( p_session_id in number )
as
    l_msg long_running_tbl.message%type;
    l_url         varchar2(100) default
    '
http://aria.us.oracle.com/wa/webdemo/owa/monitor_long_running?p_session_id=';
begin
    begin
        select message into l_msg
          from long_running_tbl
         where session_id = p_session_id;
    exception
        when no_data_found then
            htp.header( 1, 'Job is done running' );
            return;
    end;

    htp.p( '<HTML>
            <HEAD>
            <META HTTP-EQUIV="Refresh"
            CONTENT="5;URL='||l_url || p_session_id || '">
            <TITLE>Monitor</TITLE>
            </HEAD>
            <BODY><H1>' || l_msg || '</h1>' );

    for x in ( select action
                 from v$session
                where client_info = 'lr ' || p_session_id )
    loop
        htp.p( x.action );
    end loop;

    htp.p( '</BODY>
            </HTML>' );
end;
/
Review &
      Followup

      Rating: 5
Generate Excel
      Report Using PL/SQL Cartridges and app server or web server from
      forms.  September 09, 2003
Reviewer:  raja
       from India
  hi Tom,

      how to generate the Excel_report from forms6i      (Dev 2000 )in the Web
by using PL/SQL Cartridge and app server or web server from forms 6i (Oracle
9iAS).

    pls reply immd....

          Followup:

"immediately" -- sounds so pushy...

try otn.oracle.com -> discussion forums.  I've not used forms since 1995 myself.
 I don't use windows at all.  Excel?  don't have it. sorry.

      Rating: 3
dumping
      spreadsheet on web  September 09, 2003
Reviewer:
      eric givler  from harrisburg, pa
The easiest way to do this is to set a mime type to an excel file and then just
output an HTML table.  The browser will launch excel and display it.  You could
use web.show_document to display it.

You might be able to do the same thing by having a routine create a .csv file
and then launching a browser to that url.  It would probably open excel too.

September 09, 2003 -- Thanks for the question regarding "database
      link", version 8.1.6

      You Asked
i create a database link like in the following:
i am creating a link to CANDB from another database called IASDB

SQL>  create public database link candb using 'candb';

Database link created.

SQL> select db_link from all_db_links;

DB_LINK
------------------------------------------------
CANDB.US.ORACLE.COM

when i see the name it has  a domain attached to it:
US.ORACLE.COM

i check the sqlnet.ora ,init.ora etc..
the db_domain is hashed out.
the global_names is true
sqlnet.ora doesnt have any entry
i connect thru svrmgrl
SQL> select * from global_name;
GLOBAL_NAME
--------------------------------------------
IASDB.US.ORACLE.COM

SQL> alter database rename global_name to IASDB;

SQL> select * from global_name;
GLOBAL_NAME
--------------------------------------------
IASDB.US.ORACLE.COM

it doesnot change...i am facing difficulty during multimaster
replication setup and alaso while accessing some tables ..it
says that candb.us.oracle.com refers to the database candb

      and we said...
  If you issue an 'alter database rename global_name to
<new_global_name>;' and you don't specify a db_domain,  then the
db_domain will remain the same as that which was last set using
the rename global_name command regardless of what db_domain is
set to.

   Thus if, in your init you have db_name = testone and
db_domain = world and you issue the following: 'alter database
rename global_name to testtwo.us.oracle.com' and then select
from global_name you will see the following
'testtwo.us.oracle.com'.

   Now if you issue 'alter database rename global_name to
testthree;'  (note that here we don't specify a db_domain) and
then select from global_name you will see
'testthree.us.oracle.com' even though db_domain
is set to world in our init.ora.

You have to use a domain.  It will default to world (when hashed
out).  You MUST use a domain. The database name will be at least
x.y but can by x.y.z.a.b.c.....

Review &
      Followup

      Rating: 4
Network domain
      and database domain.  September 25, 2002
Reviewer:
      A reader
Tom:
Can we use a database domain other than the network domain? (I know we can still
connect, but is it advisable?)

We have all our unix database servers under network domain domain say xyz.com
(FQN like: server1.xyz.com, server2.xyz.com etc..). The db_domain of all the
databases are set to .world (explicitely in init.ora, I don't know why, but
people here are against changing it..). We have names.default_domain=world in
sqlnet.ora and we can connect using sqlplus. Now, I was trying to install a
monitoring tool and it failed to connect because the network domain and database
domain does not match! The tech support confirmed that it is the problem! Why
would that be a problem if Oracle sqlplus can connect, I asked. They just said
that is how it works!

Can you please throw some light? What is the purpose of the domain_name init.ora
parameter? Why is the difference in network domain and database name not a
problem for Oracle, while it is for some? Also, are there any other problems if
we set the db_domain different from network domain name?

TIA,
AR

          Followup:

They are confused.  The db domain affects database links only.

It should have no affect on a 3rd party tools ability to connect -- IN FACT, the
third party tool cannot even tell the domain until *after* they connect.

Anyway, maybe this tool just needs a tnsnames entry setup special for them.  try
that.  Maybe they are just taking your tnsnames entry you give them and adding
the domain afterwords (my domains are .worlds and some .oracle.com but my
"network" domain is us.oracle.com -- I've never ever had an issue)

      Rating:
      5
  September 25, 2002
Reviewer:
      A reader
That was the fix! (This is pretty boring, how come you *always* have the right
answers? Just kidding...)

Could you please explain the purpose of db_domain parameter? Is it just for
organizing your databases heirarchically? Is there anyway we can inherit the
network domain name automatically as db_domain?

Thanks for hosting this great site.

          Followup:

The db_domain is something I don't generally really use or even pay much
attention to.

from the reference guide:

...
In a distributed database system, DB_DOMAIN specifies the logical location of
the database within the network structure. You should set this parameter if this
database is or ever will be part of a distributed system. The value consists of
the extension components of a global database name, consisting of valid
identifiers, separated by periods. Oracle Corporation recommends that you
specify DB_ DOMAIN as a unique string for all databases in a domain.

<note from tom -- that means the db domain is recommended to NOT be your network
domain but named after your "group">

This parameter allows one department to create a database without worrying that
it might have the same name as a database created by another department. If one
sales department s DB_DOMAIN is JAPAN.ACME.COM, then their SALES database
(SALES.JAPAN.ACME.COM) is uniquely distinguished from another database with
DB_NAME = SALES but with DB_DOMAIN = US.ACME.COM.

If you omit the domains from the name of a database link, Oracle expands the
name by qualifying the database with the domain of your local database as it
currently exists in the data dictionary, and then stores the link name in the
data dictionary. The characters valid in a database domain name are:
alphanumeric characters, underscore (_), and number sign (#).

<note from tom -- that part is a little vital -- and is the time when the
db_domain most affects you>
.........

      Rating: 4
domain name in
      tnsnames.ora  November 08, 2002
Reviewer:
      Oracle-leo  from India
When i add an alias in tnsnames.ora using net8 configuration asst on
win2k/8.1.7, the alias is suffixed with <alias>.world or <alias>.in.oracle.com.
But when i connect using system/manager@alias, it throws error as tns could not
resolve name and expects full name like <alias>.in.oracle.com.

Is it normal behavior or there is some problem somewhere. How can i avoid
suffixing of domain name in tnsnames.ora.

Thanks,

          Followup:

edit your sqlnet.ora and add a

NAMES.DEFAULT_DOMAIN = in.oracle.com

and you won't have to supply alias.in.oracle.com - just alias.

      Rating: 4
Why suddently
      cannot work?  May 23, 2003
Reviewer:  Junior
       from VA, USA
Tom:
I created database link and worked fine in the very begining. However, couple
weeks later, this link is not working any more. I worked on both database every
day, and I am able to switch between these 2 databases by using connect
scott/scott@db1 and connect scott/scott@db2.
The only thing I did is I have a batch file which shoutdown db1 every night and
make a cold backed up. It will bring up the db1 after the backed up is done.
Will this cause problem? Do you know why the link suddently not work? I am using
Oracle 9.2. and running on Window 2000.

Thank you

          Followup:

hey, my car won't start this morning.

its worked every day.

No, I won't tell you the make or model.
I won't tell you the symptoms.
I won't tell you anything really....

so, tell me -- why won't my car start?

(sorry, could not resist.  think about it -- what possible answer could I
sitting here in my office -- without the ability to see your computer screen and
read the error message -- give you?  no error code, no messages, no cut and
paste, NOTHING)

      Rating: 5
Database v/s
      car  May 25, 2003
Reviewer:  A reader

HA HA HA HA HA HA HA

      Rating: 3
database
      links  May 27, 2003
Reviewer:  Junior
       from VA, USA
Tom:

Sorry, you are right, I should paste error message here.

SQL> select db_link from all_db_links;

DB_LINK
--------------------------------------------------------------------------
db14

SQL>  desc person@db14;
ERROR:
ORA-12545: Connect failed because target host or object does not exist

I checked tnsname and I believe it should be fine. I am able to switch the
connection between those 2 databases. This links used to work fine, but
suddently it just not worked. I droped this link and I am able to recreate it
again. However, it still not works. So...

Thanks.

          Followup:

someone changed the hostname or ip address of your server.

goto your tnsnames.ora file, verify it is correct (and it won't be).  either the
hostname/ipaddress will be wrong.

[tkyte@tkyte-pc Desktop]$ oerr ora 12545
12545, 00000, "Connect failed because target host or object does not exist"
// *Cause: The address specified is not valid, or the program being
// connected to does not exist.
// *Action: Ensure the ADDRESS parameters have been entered correctly; the
// most likely incorrect parameter is the node name.  Ensure that the
// executable for the server exists (perhaps "oracle" is missing.)
// If the protocol is TCP/IP, edit the TNSNAMES.ORA file to change the
// host name to a numeric IP address and try again.
[tkyte@tkyte-pc Desktop]$

      Rating: 4
database link
      between 7.1.5 and 9.2  June 05, 2003
Reviewer:
      John  from Canada
Hi Tom,

We have a database server 9.0.1. We want to upgrad to 9.2.

We must have a database link to one oracle 7.1.5 database(our client database,
they plan to upgrade it next year. We don't have the control.). Through the
link, we use select statement to retrieve infomation from 2 tables
frequently(almost "select" every couple of seconds), no other complicated stuff.
I heard about 9.2 has issues to access v7 database. Could you please confirm
that 9.2 can't "select" from v7 through the database link at regular basis?

Thanks.

          Followup:

you cannot, in production, connect 9.2 to 7.x

not supported.
not supportable.

      Rating: 5
Remote database
      users  September 09, 2003
Reviewer:
      A reader
Hi Tom,
We have three oracle sites let's say site1, site2 and site3. Currently every
site connects to each other using database links for transaction processing. I
am working at site1.
How can I know that how many users (with details) or sessions are currently
connected to site1 from site2 or site3??
Thanks in advance

          Followup:

look at the program column in v$session

September 09, 2003 Fred -- Thanks for the question regarding
      "Command Line Parameters", version 8.1.5 for Windows NT

      You Asked
Hi Tom,

I am looking for a way to run SQL*Plus and/or server manager
from another application written in VB passing it a script name
and logon information (user id, password, and connection
string).  I am having difficulty finding a reference for command
line parameters for SQL*Plus.  I have been able to get it to
work with either the script name or the logon information but
not both.  Can you direct me to the appropriate link, or show me
how I can accomplish this?

Thanks in advance for your help,
Fred

      and we said...
The sqlplus guide has that:

http://technet.oracle.com/doc/server.815/a66736/ch71.htm#216
In short, it'll be:

sqlplus user/password@database  @script_to_run

or for server manager it'll be:

svrmgrl @script_to_run

as server manager wants the CONNECT u/p@database to be in the
script itself -- not on the command line.
Review &
      Followup

      Rating: 3
Passing
      parameters to a sql or pl/sql script  September 09, 2003

Reviewer:  John  from Chicago
Tom,

Is it possible to pass parameters to a sql or pl/sql script at the sql command
line instead of making the script to prompt for input? Say something like this
(list of employees for dept 30) :

sql> @emplist 30

          Followup:

yes, just use &1, &2, ... &n in the script for the positional parameters.

September 09, 2003 Munzer -- Thanks for the question regarding
      "Running Form Totals in web form", version 8.1.7

      You Asked
Tom:

1.  I have a PLSQL/HTML form.  I have 5 columns:

Item No.  Description          Qty  Unit PRice  TOtal($)

I think that if I want to compute totals automatically and grand total?

1. In Pl/SQL I will have to create a refresh button then pass all form values to
a procedure, compute the toal and pass them back to the original procedure which
is pain of you have 20 parameters.

2.  I can have a javascript button that refresh page, that users can click, but
then I think I still have to pass all field values users entered on the form.

3.  I am wondering whether you can define the total as a display or input text
field that gets its value from (qty*unit price).  AS soon user enters unit price
and exits out it computes total.

DO you know the best way to do it and how yo also do grand total?

Thanks you

      and we said...
1) you only need 5 parameters.

create procedure sum_them_up( p_item_in in owa.vc_arr,
                              p_descript in owa.vc_arr,
                              p_qty in owa.vc_arr,
                              p_unit in owa.vc_arr,
                              p_price in owa.vc_arr )
is
   l_grand_total number default 0;
begin
    for i in 1 .. p_item_in.count
    loop
        l_grand_total := l_grand_total + p_qty(i) * p_price(i);
        htp.p( p_item_in(i) || .......  || ' total: ' || (p_qty(i)*p_price(i))
);
    end loop;
    htp.p( 'and the total is ' || l_grand_total );
end;

Use ARRAYS...

2) you can use javascript right on the form with a simple "when changed" event.
you would call a javascript routine in the page that would loop over the form
values and sum them up.  Any book on javascript would most likely have this as
the classic example.

3) using javascript, sure -- it's easy. You'll want to pick up a javascript book
somewhere.

Review &
      Followup

      Rating:
      5
totals  December 23, 2002
Reviewer:
      mo
Tom:

1.  In pl/sql I have an implicit cursor
  FOR x in (Select class,item_number,quantity from items)
  LOOP
  htp.p(x.class);
  htp.p(x.item_number);
  htp.p(x.quantity);
  END LOOP;

I want to insert one row (header)for the class then print all records that
belong to that class and then print the subtotal (footer) for quantity in the
class, and total (overall footer) for all subtotals at bottom of group.  How
would you do that?

          Followup:

drop table t;

create table t
as
select owner class, object_id item_number, rownum quantity
  from all_objects
 where mod(object_id,10) = 1
/

select class, decode(item_number,null,'Subtotal: ', item_number ) item_number,
sum(quantity)
  from t
 group by rollup (class, item_number)
 having ( grouping(item_number) = 1 ) or
        ( grouping(class) = 0 and grouping(item_number) = 0 )
/

run that test and see what you get... then read abit about group by rollup, play
with the grouping built function -- look at group by cube as well.

Make sure to read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3027089372477
if you are using 8i before asking "how can I make this work in plsql"...

a heading before each class -- you'll figure that out, it the olden days it was
called "control break processing" -- just keep a "last class" variable and when
it changes -- pump out a header, and last last_class = x.class.

      Rating:
      5
totals  December 23, 2002
Reviewer:
      mo
Tom:

I did the above in a way with 2 loops and one variable: find distinct classes
and then find all records in a class and then keep adding quantity to subtotal
in every pass.  however it slowed down the page by 12- 15 seconds. is there a
better way to do it in one SQL statement?

          Followup:

see above.

      Rating:
      5
totals  December 23, 2002
Reviewer:
      mo
Tom:

1. OK I read the material on group by rollup.  First I do not know why you refer
me to the other link since that discusses using a CURSOR function in pl/sql and
which I included can not be done in 8i pl/sql while can be done in 9i. Do I need
to use a CURSOR  here?

All I need is :

FOR X in (select class, decode,item_number,null,'Subtotal: ', item_number )
item_number,
sum(quantity)
  from t
 group by rollup (class, item_number)
 having ( grouping(item_number) = 1 ) or
        ( grouping(class) = 0 and grouping(item_number) = 0 ) )
LOOP
..
END LOOP;

2.  I ran your query above with and without the having clause and I got the same
results. What is the purpose of that having clause?

3.  I did the pl/sql code like I described above using two loops.  I was trying
to see the performance improvement when i do it using one query like above. I
SET TIMING ON and ran the query and the procedure that creates the html tags
with the two loops.

The query in SQL*PLUS tool 5 seconds.  For the other procedure it took 1:50
seconds but printing HTMl took 60 seconds before I got a buffer overflow error.

I am not sure if my testing is CORRECT.  Is this how you determine which way is
faster? Is this how you time web page procedures?

SQL> execute inv_report;

PL/SQL procedure successfully completed.

Elapsed: 00:00:01.50

SQL>execute owa_util.showpage
....................
BEGIN owa_util.showpage; END;
<TR>
<TD>MS142</TD>
<TD>NEWS OCT-DEC 1996</TD>
<TD
align="center">0</TD>
<TD align="center">0</TD>
<TD align="center">0</TD>
</TR>
*
ERROR at line 1:
ORA-20000: ORU-10027: buffer overflow, limit of 374850 bytes
ORA-06512: at "SYS.DBMS_OUTPUT", line 91
ORA-06512: at "SYS.DBMS_OUTPUT", line 58
ORA-06512: at "SYS.HTP", line 791
ORA-06512: at "SYS.OWA_UTIL", line 306
ORA-06512: at line 1

Elapsed: 00:00:55.90

          Followup:

I sent you to that link because my answer begins with:

...
In 8i and before, PLSQL used a different SQL parser then the "database" did.
For this reason, there are some features in SQL that PLSQL does not understand
including (but not limited to)

o order by in a subquery
o select ( select .... ) from table (selecting a select)
o analytic functions
o group by extensions (group by rollup, group by cube)
o CURSOR in a select (obviously) ;

Ok -- in 8i, group by rollup won't fly directly in plsql, you would need to do
what I outlined there....

I figured, since you supplied no VERSION, that you might be in 8i and if you
were - that would have been your very next question "why doesn't this work in
plsql".  Was trying to pre-empt that.

2) sorry -- should just be "having grouping(class) = 0"

3) just run the procedures with TKPROF turned on and don't dump the output.  Or
use runstats,

http://asktom.oracle.com/~tkyte/runstats.html

      Rating:
      5
totals  December 23, 2002
Reviewer:
      mo
Tom:

1. OK I read the material on group by rollup.  First I do not know why you refer
me to the other link since that discusses using a CURSOR function in pl/sql and
which I included can not be done in 8i pl/sql while can be done in 9i. Do I need
to use a CURSOR  here?

All I need is :

FOR X in (select class, decode,item_number,null,'Subtotal: ', item_number )
item_number,
sum(quantity)
  from t
 group by rollup (class, item_number)
 having ( grouping(item_number) = 1 ) or
        ( grouping(class) = 0 and grouping(item_number) = 0 ) )
LOOP
..
END LOOP;

2.  I ran your query above with and without the having clause and I got the same
results. What is the purpose of that having clause?

3.  I did the pl/sql code like I described above using two loops.  I was trying
to see the performance improvement when i do it using one query like above. I
SET TIMING ON and ran the query and the procedure that creates the html tags
with the two loops.

The query in SQL*PLUS tool 5 seconds.  For the other procedure it took 1:50
seconds but printing HTMl took 60 seconds before I got a buffer overflow error.

I am not sure if my testing is CORRECT.  Is this how you determine which way is
faster? Is this how you time web page procedures?

SQL> execute inv_report;

PL/SQL procedure successfully completed.

Elapsed: 00:00:01.50

SQL>execute owa_util.showpage
....................
BEGIN owa_util.showpage; END;
<TR>
<TD>MS142</TD>
<TD>NEWS OCT-DEC 1996</TD>
<TD
align="center">0</TD>
<TD align="center">0</TD>
<TD align="center">0</TD>
</TR>
*
ERROR at line 1:
ORA-20000: ORU-10027: buffer overflow, limit of 374850 bytes
ORA-06512: at "SYS.DBMS_OUTPUT", line 91
ORA-06512: at "SYS.DBMS_OUTPUT", line 58
ORA-06512: at "SYS.HTP", line 791
ORA-06512: at "SYS.OWA_UTIL", line 306
ORA-06512: at line 1

Elapsed: 00:00:55.90

      Rating:
      5
runstats.sql  December 24, 2002
Reviewer:
      mo
Tom:

I can do a "Select * from v$statname" and the others.  However I can not do
"select * from sys.v$statname".

How do I know if I have access to those system views directly and not via a role
?

          Followup:

ops$tkyte@ORA920> select * from sys.v$statname;
select * from sys.v$statname
                  *
ERROR at line 1:
ORA-00942: table or view does not exist

neither can i, cause there isn't any such thing.  v$statname is a synonym --
not a table, not a view.  the view is:

ops$tkyte@ORA920> select * from sys.v_$statname where rownum = 1;

                     STATISTIC# Tablespace Name                            CLASS
------------------------------- ---------------- -------------------------------
                              0 logons cumulativ                               1
                                e

to see if you have access to something via a role, simply

SQL> set role none;
SQL> try to access it.

      Rating: 5
web
      totals  December 24, 2002
Reviewer:  mo

Tom:

1. this the pl/sql version I have.

SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle8i Enterprise Edition Release 8.1.7.2.0 - 64bit Production
PL/SQL Release 8.1.7.2.0 - Production
CORE    8.1.7.0.0       Production
TNS for IBM/AIX RISC System/6000: Version 8.1.7.2.0 - Production
NLSRTL Version 3.4.1.0.0 - Production

2.  FOr the sql, even though it compiled fine in Navigator, I got an error on
the web page. Does that mean i can not do it with this version or I have to run
it dynamically and using the database SQL parser.

FOR x in ( select class, decode(item_number,null,'Subtotal: ', item_number )
                    item_number,description,
                    compute_qty_org('IMS',item_number) n_hand
                    from stock_item where class <> '99'
                     group by rollup (class, item_number)
                     having  grouping(class) = 0 )
        LOOP

ORA-00979: not a GROUP BY expression
ORA-06512: at "IS.INVENTORY_REPORT_TEST", line 47
ORA-06512: at line 7

          Followup:

Mo, you just need to take this advice:

Make sure to read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3027089372477
if you are using 8i before asking "how can I make this work in plsql"...

It is the link you couldn't figure out why I sent you to read -- but then even
after I told you exactly why you should read it -- you couldn't put it together?

re-read this series of followups -- the answer lies therein

      Rating:
      5
total  December 24, 2002
Reviewer:
      mo
TOm:

after reading other link 3 times , I found
the other link talks about using CURSOR in a pl/sql program.

the other thing  I see that applies here is that you say pl/sql in 8i does not
support these:
o group by extensions (group by rollup, group by cube)

Are you also implying to to dynamic SQL for a workaround:

open :x for 'select class, decode       (item_number,null,'Subtotal: ',
item_number )
 item_number,description, compute_qty_org('INS',item_number) on_hand
from stock_item where class <> '99'
 group by rollup (class, item_number)
 having  grouping(class) = 0 '

          Followup:

The other link talks about:

<quote>
...
In 8i and before, PLSQL used a different SQL parser then the "database" did.
For this reason, there are some features in SQL that PLSQL does not understand
including (but not limited to)

o order by in a subquery
o select ( select .... ) from table (selecting a select)
o analytic functions
o group by extensions (group by rollup, group by cube)
o CURSOR in a select (obviously) ;

Ok -- in 8i, group by rollup won't fly directly in plsql, you would need to do
what I outlined there....
</quote>

it describes an entire set of SQL "things" that PLSQL in 8i just didn't
recognize such as CURSOR() but also -- as I tried to point out -- group by
rollup.  It describes TWO solutions to the problem -- views and dynamically
opened SQL.

I'm not implying -- I'm saying in no uncertain terms, if you want to use group
by rollup in plsql in version 8i, you will either

o use a view to hide the construct from plsql
o use a dynamically opened query to hide the contruct from plsql

      Rating: 5
group by
      rollup  December 25, 2002
Reviewer:  mo

Tom:

Thanks.  It is working fine using one loop (to find class)inside of a loop (find
all items in one class). The items table is about 1000 records. I wonder whether
doing it dynamically would be better or not.  The page is taking about 5-7
seconds which is not bad response time.

It is strange how it compiles fine in SQL Navigator (using group by rollup) and
then give you the error when you run it.  I thought the compiler will parse the
SQL and tells you that this is not valid SQL before even trying to run it.

      Rating: 5
how to run the
      form in web  September 09, 2003
Reviewer:
      suresh  from India
  hi Tom,

     i installed Oracle 9iAS ,i am using PL/SQL Cartridge and forms 6i how to
run a form in the web, what are the steps to follow ?

          Followup:

that is sort of an "read the documentation" type of question... all of the docs
are freely available on otn.oracle.com -> documentation

September 09, 2003 Dhruba -- Thanks for the question regarding
      "Please guide to compile pro*c program without connecting to database",
      version 8.1.7

      You Asked
Dear Tom,
We have a requirement that we need to provide the object code of the pro*c
programs and  wrapped PL/SQL packages to one of our clients .
So we need to be able to pre-compile our  pro*c source code without connecting
to the database .
We are using Oracle 8.1.7 . Pro*C/C++: Release 8.1.7.3.0

The pro*c program uses

-Several Pl/SQL packages (These packages are used for other purposes also)
-PL/SQL package variables
-table and views

I know that we can use EXEC SQL DECLARE...TABLE
but we would like to declare this outside the main program .How  can we do that
?

Similary can we declare the PL/SQL packages ?

I am using SQLCHECK=SEMANTICS and without USER

but getting the following errors :

        PCC-S-02346, PL/SQL found semantic errors
                              l_msg_rec     pl0003p.pl_msgs_t;
    ...................................................1
    PLS-S-00201, identifier 'PL0003P.PL_MSGS_T' must be declared
                              l_msg_rec     pl0003p.pl_msgs_t;
….
Etc.
Would you please guide me?

Thanks,
Dhruba.

      and we said...
you would have to use 100% dynamic sql in all calls to plsql -- else you need to
connect to the database so we can describe the package.

so, you can either dynamically execute all plsql calls or you connect to the db.

here is an example of the dynamic calls, i prepare and declare a cursor for each
plsql package entry point I need to call

static void plex_init( ServerPipe )
char *    ServerPipe;
{
EXEC SQL BEGIN DECLARE SECTION;
    VARCHAR        theStmt[500];
EXEC SQL END DECLARE SECTION;

#define READ_PIPE \
"begin plex.read_pipe( output=>:b1:i1 ); end;"
#define WRITE_PIPE \
"begin plex.write_pipe(input=>:b1:i1,theType=>:b2:i2); end;"
#define ACCEPT \
"begin plex.accept_client_request('%s',theFunction=>:b1:i1);end;"
#define SEND \
"begin plex.send_client_response; end;"

    EXEC SQL WHENEVER SQLERROR DO sqlerror();

    vstrcpy( theStmt, READ_PIPE );
    EXEC SQL PREPARE READ_PIPE_STMT FROM :theStmt;
    EXEC SQL DECLARE READ_PIPE_CURS CURSOR FOR READ_PIPE_STMT;

    vstrcpy( theStmt, WRITE_PIPE );
    EXEC SQL PREPARE WRITE_PIPE_STMT FROM :theStmt;
    EXEC SQL DECLARE WRITE_PIPE_CURS CURSOR FOR WRITE_PIPE_STMT;

    sprintf( theStmt.arr, ACCEPT, ServerPipe );
    theStmt.len = strlen( theStmt.arr );
    EXEC SQL PREPARE ACCEPT_STMT FROM :theStmt;
    EXEC SQL DECLARE ACCEPT_CURS CURSOR FOR ACCEPT_STMT;

        vstrcpy( theStmt, SEND );
    EXEC SQL PREPARE SEND_STMT FROM :theStmt;
    EXEC SQL DECLARE SEND_CURS CURSOR FOR SEND_STMT;
}

and then to call on, we just "open" the cursor like this

static void read_pipe( indicator )
short *     indicator;
{
    vmaxlen( bigBuff );
    EXEC SQL OPEN READ_PIPE_CURS USING :bigBuff:bigBuff_i;

    if ( !bigBuff_i )
    {
        vnull( bigBuff );
        Debug( "read %d bytes = '%s'\n", bigBuff.len, bigBuff.arr);
    }
    else
    {
        *bigBuff.arr = 0;
        Debug( "read *null* from pipe\n" );
    }
    *indicator = bigBuff_i;
}

static void write_pipe( type )
char    * type;
{
EXEC SQL BEGIN DECLARE SECTION;
    VARCHAR        theType[2];
EXEC SQL END DECLARE SECTION;

    vstrcpy( theType, type );
    EXEC SQL OPEN WRITE_PIPE_CURS USING :bigBuff:bigBuff_i, :theType;

    if ( !bigBuff_i )
    {
        Debug( "wrote %d bytes = '%*.*s'\n",
                bigBuff.len, bigBuff.len, bigBuff.len, bigBuff.arr);
    }
    else
    {
        Debug( "wrote *null* on pipe\n" );
    }
}

since we hid the code in dynamic sql, pro*c just goes... without connecting


Subject | Article | Last Updated (East coast time)
Reclaiming space | article only | Sep 10, 2003 09:50 am
New features good, how about the old features? | article only | Sep 10, 2003 10:33 am
EXPORT - direct=y | article only | Sep 10, 2003 02:31 pm
Asynchronus I/O | article only | Sep 10, 2003 07:35 pm
Urgent Help on converting UTF8 to UCS2 | article only | Sep 10, 2003 07:40 pm
SQL_TRACE -- how to enable in other sessions. | article only | Sep 10, 2003 08:07 pm
How to find out which process caused the maximum IO request to db server? | article only | Sep 11, 2003 08:27 am
User Quota | article only | Sep 11, 2003 08:29 am
SQLLoader | article only | Sep 11, 2003 08:39 am
Installation/Upgradation | article only | Sep 11, 2003 08:40 am
generate test data automagically | article only | Sep 11, 2003 08:43 am
Cursor Expressions Vs Scalar Subqueries | article only | Sep 11, 2003 09:54 am
nls_date_format | article only | Sep 11, 2003 06:59 pm
rollback segment | article only | Sep 11, 2003 07:02 pm
DB/Application slow after migrating to 9i | article only | Sep 11, 2003 07:06 pm
Snapshot / Materialized View | article only | Sep 11, 2003 07:19 pm
Object Methods vs Packages | article only | Sep 11, 2003 07:43 pm
Materialized View Refresh | article only | Sep 11, 2003 07:47 pm
Performance - top N query | article only | Sep 11, 2003 07:49 pm
Select a date value in a date column in a different format | article only | Sep 11, 2003 07:54 pm
Develop Environment | article only | Sep 11, 2003 07:55 pm
Archive Log file | article only | Sep 11, 2003 07:56 pm
sort_area_size | article only | Sep 12, 2003 09:46 am
database sessions -- seeing their sql. | article only | Sep 12, 2003 09:50 am
Using CTAS to reclaim space and improve/modify storage parameters | article only | Sep 12, 2003 09:53 am
Partition elimination | article only | Sep 12, 2003 10:03 am
tree validation | article only | Sep 12, 2003 10:05 am
copy of schema | article only | Sep 12, 2003 10:07 am
Reverse Key | article only | Sep 12, 2003 10:25 am
trigger firing order question | article only | Sep 12, 2003 10:49 am
Fastest way to save off Duplicates | article only | Sep 12, 2003 10:52 am
Connect sys/xxxxx does not work in 9i | article only | Sep 12, 2003 02:11 pm
what to use when | article only | Sep 12, 2003 02:44 pm
Before Delete Trigger to insert into history table , the record being deleted. | article only | Sep 12, 2003 02:46 pm
Private procedures on Object Type | article only | Sep 12, 2003 02:52 pm
Oracle Replication | article only | Sep 12, 2003 04:22 pm
when do reads reflect multiblock_read_count * block_size | article only | Sep 12, 2003 07:49 pm
Padding a Long Variable | article only | Sep 12, 2003 07:57 pm
Multiple Listeners | article only | Sep 13, 2003 09:28 am
Some Standard Doubts | article only | Sep 13, 2003 09:31 am
hot backup | article only | Sep 13, 2003 09:37 am
Clarifications on closing cursor and cursor bind variables | article only | Sep 13, 2003 12:15 pm
utllockt.sql in 9imet | article only | Sep 13, 2003 12:20 pm
Oracle's Object Relational Model | article only | Sep 14, 2003 02:31 pm
Segment Level Statistics | article only | Sep 14, 2003 10:17 pm
Query and transaction processing statistics | article only | Sep 14, 2003 10:20 pm
Right approach to monitoring sessions and concurrent transactions | article only | Sep 15, 2003 08:48 am
Security | article only | Sep 15, 2003 09:42 am
Audit on Trigger or Oracle Audit | article only | Sep 15, 2003 10:00 am
Can I replace just the first occurrence of the string? | article only | Sep 15, 2003 11:05 am
inserting data in lob columns | article only | Sep 15, 2003 11:26 am
Reg Nested Tables | article only | Sep 15, 2003 02:57 pm
removing control characters from text | article only | Sep 15, 2003 04:14 pm
add a column and update the column with a big table | article only | Sep 16, 2003 07:50 am
Delete Rule is "No Action" of FOREIGN KEY 's Constraint | article only | Sep 16, 2003 07:55 am
Partitioning the table by range on a column and no index/key on this ? | article only | Sep 16, 2003 08:06 am
quick question | article only | Sep 16, 2003 08:10 am
Nologging or Noarchive log , Archivelog Maintenace | article only | Sep 16, 2003 08:38 am
Foreign key | article only | Sep 16, 2003 09:16 am
Question regarding Redo log generation | article only | Sep 16, 2003 09:30 am
Check for record existence in C++ | article only | Sep 16, 2003 10:29 am
a procedure to rollback in-doubt transactions. | article only | Sep 16, 2003 10:39 am
Query on multiple union all results into one | article only | Sep 16, 2003 11:04 am
monitor cpu and disk I/O usage and contention | article only | Sep 16, 2003 12:50 pm
Underlying Algorithm For File Extention | article only | Sep 16, 2003 06:35 pm
AUTO COMMIT | article only | Sep 16, 2003 08:18 pm
Displaying the maximum value for every record | article only | Sep 16, 2003 08:57 pm
row(s) 1 - 67 of 67
September 10, 2003 CJ -- Thanks for the question regarding "Reclaiming space", version 8.1.7
You Asked
If I have a partitioned table in a tablespace with uniform size of, for example,
128M, and the partitions are only taking up approximately 16M per partition.
Using "alter table x move partition p tablespace tbsp_16m", and moving those
partitions to a tablespace with uniform size of 16M does not seem to reclaim the
unused space, it just allocates more extents to reach the same size as the table
was in the 128M uniform tablespace, and the unused blocks are as many as there
were before. Short of recreating the entire table, is there anyway way to shrink
the partitions?

Thanks.
and we said...
add a storage clause -- (initial 1k next 1k pctincrease 1k minextents 1)

else it picks up the existing storage clause based on the 128m tablespace it was
created in.

consider:

DOC>create tablespace t_1m datafile size 20m
DOC>extent management local uniform size 1m;
DOC>
DOC>create tablespace t_2m datafile size 20m
DOC>extent management local uniform size 2m;
DOC>*/

ops$tkyte@ORA920LAP> alter user ops$tkyte default tablespace t_2m;
User altered.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> CREATE TABLE t
  2  (
  3    data       char(255),
  4    temp_date  date
  5  )
  6  PARTITION BY RANGE (temp_date) (
  7    PARTITION p1 VALUES LESS THAN (to_date('18-mar-2003','dd-mon-yyyy')) ,
  8    PARTITION p2 VALUES LESS THAN (MAXVALUE)
  9  )
 10  ;

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select partition_name, extent_id, blocks
  2    from user_extents
  3   where segment_name = 'T'
  4  /

PARTITION_NAME                  EXTENT_ID     BLOCKS
------------------------------ ---------- ----------
P1                                      0        256
P2                                      0        256

so, both partitions take 256 blocks -- the default storage for them represents
this as well.  when we move one:

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> alter table t move partition p1 tablespace t_1m
  2  /

Table altered.

ops$tkyte@ORA920LAP> select partition_name, extent_id, blocks
  2    from user_extents
  3   where segment_name = 'T'
  4  /

PARTITION_NAME                  EXTENT_ID     BLOCKS
------------------------------ ---------- ----------
P1                                      0        128
P1                                      1        128
P2                                      0        256

the storage clause moved with them -- so, the same amount of space is
allocated.  we could:

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> alter table t move partition p2 tablespace t_1m storage (
initial 1k next 1k minextents 1 )
  2  /

Table altered.

ops$tkyte@ORA920LAP> select partition_name, extent_id, blocks
  2    from user_extents
  3   where segment_name = 'T'
  4  /

PARTITION_NAME                  EXTENT_ID     BLOCKS
------------------------------ ---------- ----------
P1                                      0        128
P1                                      1        128
P2                                      0        128

and that just allocated the minimum space -- for the existing ones we can

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> alter table t modify partition p1 deallocate unused keep 1k
  2  /

Table altered.

ops$tkyte@ORA920LAP> select partition_name, extent_id, blocks
  2    from user_extents
  3   where segment_name = 'T'
  4  /

PARTITION_NAME                  EXTENT_ID     BLOCKS
------------------------------ ---------- ----------
P1                                      0        128
P2                                      0        128

to release the extents that are not used
Review & Followup
Rating: 5
September 10, 2003
Reviewer: A reader

September 10, 2003 -- Thanks for the question regarding "New features good, how about the old features?", version 8.1.7
You Asked
Hi Tom,

   In 8i or 9i, Oracle provides you a lot of features to simplify the DBA tasks
such as AUM, Segment Management Local, Extent Management Local etc. Since Oracle
recommends the users to use these new features, I have the feeling that the old
features (i.e. manual rollback segments, FREELIST, PCTUSED) are all "evil" in
the database. But why Oracle implements such features so late (8i or even 9i)?
What are the use of these old features? Does Oracle finally accept that these
features are bring more harm than good in their database?

Thanks,
David
and we said...
interesting questions.

do you get everything perfect the first time around?  I wish I did (would make
writing books easier, would not have to go through that painful edit stage).

Even now, looking back two years ago -- i would make changes to my book "Expert
One on One Oracle" -- because I have increased knowledge, know more then I did
before.

Those features are not evil, we've just learned over the years what the best
practices are -- how to automate them.  The software gets incrementally smarter,
like we do and can automate the mundane tasks that DBA's have.  This frees the
DBA's to perform the other stuff they need to do in support of other new
features.

Think of it like seat belts.

When I was growing up -- cars did not have them.

When I was a little older, they were optional features you could buy.  They
started as just lapbelts, no shoulder straps.

Later - they added shoulder straps and make them standard features.  But no one
really used them.

Then, they added that little buzzer or light to cars -- "warning, put on your
seat belt" (so people would disable that feature or work around it -- we used to
pull the fuse out so the buzzer wouldn't go off or just buckle the belts and sit
on them)

Then, they made it so that the car "strapped you in" as you sat down.  Made it
sort of unavoidable.

Now, it just seems like the natural thing to do -- sit in a car, put on the
belt.

The database, with its feature set, is alot like that.  We learn over time, we
automate over time.  These features are adopted over time with varying degrees
of resistance...
Review & Followup
Rating: 5
Very Articulate March 10, 2003
Reviewer: A reader
Tom,
Off track but I am in awe of you personna. Are you by any chance going to
publish your autobiography?
I am so much inspired by your knowledge that i crave to know more about you as a
person and not just a technical expert.

Rating: 5
Pretty soon you'll have your own stalker! March 10, 2003
Reviewer: A reader

Rating: 5
Sex and drugs and Oracle March 10, 2003
Reviewer: A reader
I'd never heard of Oracle Groupies until now...

Rating: 5
March 10, 2003
Reviewer: Dave
this thread turned sinister real quick. bit of a conversation stopper, actually.

Rating: 3
Automated carstraps vs automated dba tasks March 10, 2003
Reviewer: Tarry Singh from Netherlands
Well,
since the car automatically straps you up and
the database automates your tasks(more and more in coming releases).

So you know, you(the aspiring dba) won't be needed anymore...sigh...

Rating: 4
OK, to continue the analogy about seat belts... March 10, 2003
Reviewer: Jeremy Smith from Salt Lake City
When you were a kid (unless you're much older than you look...) there had been
at least one car (the Tucker) that had a seat belt.  So there _were_ companies
that had built what's now considered a critical feature into their product.
Although it did take a long time to make it into mainstream automobiles.  You
see this in technology all the time (look at the NeXT user interface compared to
what else was out there at the time and compare it to the best UIs out there
today) but most of the time, those features hit consumers ten years later.

Often, in RDBMS', the most innovative company has been Oracle, and from what you
write, one might get the (false?) impression that you believe Oracle can do no
wrong or lacks nothing.  Of course, if that were true, than there'd be no reason
for a next version, and we could stop worrying about upgrades. :)  So we know
that even Tom thinks Oracle can do some things better.

So, without putting your employment at risk, what do you think Oracle _doesn't_
have that you've seen in another product or theorized about that will eventually
be there?  Maybe the implementation has been terrible or impossible, but it's
"had it."

Any "seatbelt"-like features out there that you expect to see in a few years?
(Oracle 12?)

Followup:
...
and from what you
write, one might get the (false?) impression that you believe Oracle can do no
wrong or lacks nothing.
......

I hope I didn't convey that impression -- rather, I was stating "we get it
wrong, we fix it over time, wish we could get it right the first time around but
we can't"

Ok, I'll take a shot - I've always wondered why the optimizer couldn't generate
a plan (as it does) based on statistics but then change it's mind over time as
the reality of the situation hits it - eg: it thought it would get 1 row from
step X of the plan, but reality has shown time and time again that it gets 100.
Based on it thinking it was getting 1 row -- it was doing a merge join
cartesian, but now that it knows it gets 100 -- it should quickly change its
mind...

You know, sort of an adaptive "i'll change based on reality as well as
hypothetical response times" ;)

Rating: 5
For Jeremy... March 10, 2003
Reviewer: Mark A. Williams from Indianapolis, IN USA
Jeremy,

My gosh, Mark Jarvis better not see this - Oracle 12???  You left off the
all-important "i".  12i, 12i, 12i...

And just because I like to chuck my $0.02 in, how about a default index
tablespace?

- Mark

Followup:
nah, that just promotes the myth that indexes and tables are like matter and
anti-matter.

Rating: 5
Matter and Anti-Matter... March 10, 2003
Reviewer: Mark A. Williams from Indianapolis, IN USA
Touché...  :)

- Mark

Rating: 4
My list March 10, 2003
Reviewer: Jeremy Smith from Salt Lake City, UT
There are what I've seen in other databases that I'd like in Oracle:

Database:
*An SQL99 boolean datatype (but I've already asked/complained about that)
*The ability to have shared-nothing clustering if that's what your application
wants or needs, including transparent partitioning across such a shared-nothing
cluster.
*Lots of nice included datatype extensions/prewritten check constraints (for
example in PostgreSQL you can have an IP address or Mac address type without
having to go all object-oriented) (These could be implemented however is
neccessary, but the validation code would be maintained by oracle)
*operating system independence for files...or at least much better tools to
migrate platforms
*better transparent sql gateways

Tools:

* code sense/tab completion for gui and development coding tools including
sqlplus.
* Something like MSSQL's graphical query plan tool and an editor that suggests
syntax corrections and typos.
* Better autoformatting in SQL*Plus

And there's lots of things I only dream about that no one has done, such as
truly abstracting physical files from logical tables and views.   (which is what
all the Relational Database theory pundits want)

Followup:
* Something like MSSQL's graphical query plan tool and an editor that suggests
syntax corrections and typos.

that exists in OEM's tuning pack.

Rating: 4
On the dev side: March 11, 2003
Reviewer: Robert from PA
I have always wondered why oh why Oracle won't pre-package many of the (common)
functions/procedures that one sees custom-written over and over again
everywhere.

Rating: 4
What is SQL Model Clause September 09, 2003
Reviewer: Venkat
Hi Tom,

Sorry, i could not wait any longer to hear more about the SQL Model clause you
presented yesterday. Please pardon me for posting as a review message but i
really really wanted to know. If you can explain and point to any documentation,
I really appreciate it.

Thanks a million in advance,

Followup:
documentation will be coming out when 10g comes out -- it doesn't exist as of
right now.

September 10, 2003 -- Thanks for the question regarding "EXPORT - direct=y", version 8.1.5
You Asked
Since the DIRECT=Y option of EXPORT speeds up  export jobs
significantly
, I wonder if I should always use this option.
Are there any situations in which this is undesirable ?

Thanks.
and we said...
There are 2 cases (from the server utilities guide) where direct
path export either will not be used by export OR export will
just abort:

You cannot export certain tables using direct path. For example,
you cannot export tables using object features on LOBs. If you
specify direct path for export, tables containing objects and
LOBs will be exported using conventional path.

Direct path Export exports in the database server character set
only. If the character set of the export session is not the same
as the database character set when an export is initiated,
Export displays a warning and aborts. Using the NLS_LANG
parameter, specify the session character set to be the same as
that of the database before retrying the export.
Review & Followup
Rating: 4
NLS_LANG set December 05, 2001
Reviewer: Lisa from TX
Could you show me where can I set the NLS_LANG to WE8ISO8859P1.
I have a dmp file that has "National character set WE8ISO8859P1" in his
database. When I tried to import, I got
Import: Release 8.1.7.0.0 - Production on Wed Dec 5 11:16:46 2001

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Release 8.1.7.0.0 - Production
JServer Release 8.1.7.0.0 - Production

IMP-00016: required character set conversion (type 31 to 871) not supported
IMP-00000: Import terminated unsuccessfully

Followup:
you can set it in the environment:

setenv NLS_LANG AMERICAN_AMERICA.WE8ISO8859P1

(use the appropriate syntax for your shell of course)

Rating: 4
No kidding December 06, 2001
Reviewer: Doug from CT, USA
So Tom, other than BLOBs and objects and character set issues, direct path is
better?  I only ask because in my somewhat limited experience it doesn't seem
that commonly used.  Would this be something bordering on a recommendation on
your part?

Followup:
Now, this would be a recommendation (after you test an import once).  In the
early releases of the direct path export, there was an issue with trailing null
columns causing the dmp file to be "not readable" by import -- this issue was
resolved long ago.  If you are on supported software (supported releases of
Oracle) -- definitely use it, it's much faster.

Rating: 5
difference between .... September 09, 2003
Reviewer: Reader
Tom, what is the difference between BUFFER and RECORD LENGTH parameter and when
should I use them? thanks.

Followup:
buffer is a memory buffer (how much to save up before inserting)

record lenght is an IO size (how much to read/write at a time)

the bigger the buffer, the bigger the arraysize, the more rows inserted in a
single call.

you use this to tune the import/export process generally.  the server Utilities
guide does provide some further guidelines.

Rating: 5
EXPORT Utility September 10, 2003
Reviewer: S.Darshan from Singapore
Hi Mr.Tom,

I have a user with 200 tables and 50 Partitions.  I want to export all the
tables one at a time(in one dmp files) and All the partitions in another dmp
files. Can you help me?

With best Regards
S.Darshan

Followup:
you would have to use a parameter file and list tables=(a,b,c,d,e,f,....)

it really isn't setup to do "all but this one table" sort of stuff. you would
have to list what you want.

one thing you could do is put a fine grained access control policy on the one
table -- one that returns 1=0, so the table will appear empty to EXP.  then,
export all tables.  Then drop the policy and export the single table.

September 10, 2003 Anil -- Thanks for the question regarding "Asynchronus I/O", version 9.0.1
You Asked
Hi Tom

1.I have Question abt DBWR and IO_SLAVES.
  What these slaves are?
  I heard they used to do the Asynchronous I/O ?.
  What is this Asynchronous I/O?
  On a single CPU m/c you should consider adding more IO_SLAVES than
  multiple DBWR why?

2. What is the diff between Latches and Enques?

3. How to upgrade a Oracle8i database to 9i(9.0.1). Do we need to     install
Oracle Data migration Assistant on the 8i site or we can upgrade remotely.
I want to do it using Oracle Data migration Assistant only.
This is just for practice.

About SQL> Development database 9i(9.0.1)

SQL>CREATE TYPE EMP AS OBJECT( EMPNO NUMBER(10),SAL NUMBER(10);
SQL>CREATE TYPE EMPLIST AS TABLE OF EMP;

NOW I WANT THE EMPNO SHOULD BE UNIQUE ? HOW I ACHIEVE THIS.
WHY THE CONSTRAINTS ARE NOT SUPPORTED ON OBJECTS ?
As per my understanding they are stored internally as a table.
I read somewhere you can use Object_id as empno will it work if yes how?
I will be greatful if you demonstrate it with an example.

Regards

Anil
and we said...
1) the slaves do the IO for dbwr.

Normally, DBWR would do the IO.  If the OS supports async io, it would use that.
 Async IO just means that DBWR would make a request to the OS to write something
to disk and instead of waiting for it to be written, the write call would
return immediate.  However, DBWR does not consider the write completed yet --
rather, DBWR will wait for the OS to asyncronously signal it (call back) to say
"write completed"

You can get much better throughput this way.  DBWR might have 100 blocks to
write out and instead of write/wait/write/wait..... it will write write
write.... and then wait for all of the writes to complete -- the OS will notify
it.

Now, some OS's/configurations don't support that -- so you can use IO slaves to
emulate it.  DBWR will ask an IO slave to do the IO for it and the slave will
notify DBWR upon success (or failure of course) of the write just like the OS
would have.

the number of slaves is more directly related to the number of disks, not cpus

2) here is a small excerpt from my book "Expert one on one Oracle" on that
topic:

Latches and Internal Locks (Enqueues)

Latches and enqueues are serialization devices used to coordinate multi-user
access to shared data structures, objects and files. Latches are locks which are
held for very small periods of time - extremely small periods, for example the
time it takes to modify an in memory data structure. They are used to protect
certain memory structures, such as the database block buffer cache or the
library cache in the shared pool, as described previously in Chapter 2. Latches
are typically requested internally in a 'willing to wait' mode. This means that
if the latch is not available, the requesting session will sleep for a short
period of time and retry the operation later. Other latches may be requested in
an immediate mode, meaning that the process will go do something else, rather
than spin and wait for the latch to become available. Since many requestors may
be waiting for a latch at the same time, you may see some processes waiting
longer than others. Latches are assigned rather randomly, based on the luck of
the 'draw' if you will. Whichever session asks for a latch right after it was
released will get it. There is no line of latch waiters, just a 'mob' of waiters
constantly retrying.

Oracle uses atomic instructions like 'test and set' for operating on latches.
Since the instructions to set and free latches are atomic, the operating system
itself guarantees that only one process gets it. Since it is only one
instruction, it can be quite fast. Latches are held for short periods of time
and provide a mechanism for clean up in case a holder dies abnormally while
holding it. This cleaning up process would be performed by PMON.

Enqueues are another, more sophisticated serialization device, and differ from
latches in that they allow the requestor to 'queue up' and wait for the
resource. With a latch request, the requestor is told right away whether they
got the latch or not. With an enqueue, the requestor will be blocked until they
do. Enqueues are a little more 'heavy' than latches for this reason - they
provide additional functionality (but are not as fast as a latch can be).
Enqueues provide further functionality above latches as well, in that they may
be obtained at various levels so you can have many 'share' locks and locks with
various degrees of 'shareability'.  We use enqueue locks when updating rows in a
database table for example.

3) With every edition of the database comes a vital document -- the Migration
Guide.  You would read that from cover to cover to discover your options and
plan your update.
http://technet.oracle.com/docs/products/oracle9i/doc_library/901_doc/nav/docindex.htm#index-MIG
4) Constraints are in fact supported and used on objects.  You might want to
give:
http://technet.oracle.com/docs/products/oracle9i/doc_library/901_doc/appdev.901/a88878/toc.htm
the application developers guide for object relational features.

Specifically -- a quick search for constraint in the table of contents sent me
straight to:
http://technet.oracle.com/docs/products/oracle9i/doc_library/901_doc/appdev.901/a88878/adobjbas.htm#454744

which has this example:

Constraints for Object Tables

You can define constraints on an object table just as you can on other tables.

You can define constraints on the leaf-level scalar attributes of a column
object, with the exception of REFs that are not scoped.

The following examples illustrate the possibilities.

The first example places a primary key constraint on the SSNO column of the
object table PERSON_EXTENT:

CREATE TYPE location (
  building_no NUMBER,
  city        VARCHAR2(40) );

CREATE TYPE person (
  ssno        NUMBER,
  name        VARCHAR2(100),
  address     VARCHAR2(100),
  office      location );

CREATE TABLE person_extent OF person (
  ssno        PRIMARY KEY );

The DEPARTMENT table in the next example has a column whose type is the object
type LOCATION defined in the previous example. The example defines constraints
on scalar attributes of the LOCATION objects that appear in the DEPT_LOC column
of the table.

CREATE TABLE department (
  deptno      CHAR(5) PRIMARY KEY,
  dept_name   CHAR(20),
  dept_mgr    person,
  dept_loc    location,
  CONSTRAINT  dept_loc_cons1
      UNIQUE (dept_loc.building_no, dept_loc.city),
  CONSTRAINT  dept_loc_cons2
       CHECK (dept_loc.city IS NOT NULL) );
Review & Followup
Rating: 4
Is it Async I and O or O only September 10, 2003
Reviewer: Sriram Kumar from Bagalore,India
Hi Tom,

I have a doubt. We always hear about Async IO in connection with DBWR process
(which mainly does the writing). I wonder whether the read operations that are
performed by the user processes are also Async.

To elaborate, whenever the user process requires some information that needs to
be read from the datafile does it do a async read or a synchronous read? (
Assume that  the OS supports Async IO)

Pls throw some light.

Many Thanks

Sriram Kumar

Followup:
pretty much only makes sense to do a blocking read -- we sort of need to get the
data to do anything else.

there are async read aheads that occur, but that is generally below the oracle
level.

Rating: 4
Thanks for the clarification September 10, 2003
Reviewer: Sriram Kumar from Bangalore,India
Hi Tom,

Thank you for the clarification provided. I fully understand from your reply
that the user process does a sync read. Based on your reply, we can conclude
that from Oracle's prespective async IO is actually Async O only.

Async Input is not useful because the the process that requires the Input neads
the data to proceed with its next operation.

Thanks and Regards

Sriram Kumar

September 10, 2003 chet -- Thanks for the question regarding "Urgent Help on converting UTF8 to UCS2", version 8.1.7
You Asked
I want to get data from Oracle to insert into MS SQL Server that is I have a
string in UTF8 encoding and need to convert to UCS2.  How to do this in Java?
Your help is greatly appreciated.
Thanks
and we said...
It just happens automagically.  I read this in the "National Language Support
Guide" book, chapter 6, section "JDBC Class Library".
http://technet.oracle.com/doc/oracle8i_816/server.816/a76966/ch6.htm#8036
=======================================

The [JDBC] library always accepts US7ASCII, UTF8 or WE8ISO8859P1 encoded
string data from the input stream of the JDBC drivers. It also accepts
UCS2 for the JDBC server-side driver. The JDBC Class Library converts
the input stream to UCS2 before passing it to the client applications.
If the input stream is in UTF8, the JDBC Class Library converts the UTF8
encoded string to UCS2 by using the bit-wise operation defined in the
UTF8-to-UCS2 conversion algorithm. If the input stream is in US7ASCII or
WE8ISO8859P1, it converts the input string to UCS2 by casting the bytes
to Java JDBC characters.

At database connection time, the JDBC Class Library sets the server
NLS_LANGUAGE and NLS_TERRITORY parameters to correspond to the locale of
the Java VM that runs the JDBC driver. This operation is performed on
the JDBC OCI and JDBC Thin drivers only, and ensures that the server and
the Java client communicate in the same language.
Review & Followup
Rating: 3
Utf8 and JDBC September 08, 2002
Reviewer: Heriawan from Singapore
Could you give me more detail of examples how java using oracle jdbc can store
multilanguages for double bytes eg:chinese,japanese,etc into Oracle database
whose UTF8 character set?

Followup:
Just do it -- it just does it.

An example on this ascii screen would look something like this:

@#$@!~#@$#@!

Not very interesting.  The code is the code is the code, regardless of whether
you have ascii text or chinese.

Rating: 3
java.sql.SQLException: Fail to convert between UTF8 and UCS2 September 10, 2003
Reviewer: Vikas Khanna from INDIA
Hi Tom,

This is in regards to a very critical issue which is being faced by us. Hope you
will provide us with some solutions.

We are using Oracle Thin Driver to read from Oracle 7.3 database. The databsase
containg information in Japanese chatacters. (UTF-8 characher set).

For some of the records the application throws following exception while calling
getString function of ResultSet...

java.sql.SQLException: Fail to convert between UTF8 and UCS2

Pls. suggest how to read this data that throws this error.
Is it due to some invalid characters in database for which conversion is not
able to work with.

Also for that column we checked with the Substr() function by getting character
by character and then we found that on the 21st character this Exception does
come and prior to this all 20 characters had no problem.

Please suggest what can be done.Hoping to get a solution ASAP.

With Kind regards,
Vikas Khanna

Followup:
sorry -- i don't really program data, definitely do not have a 7.3 database with
japanese characters.

no ideas.

September 10, 2003 Mike -- Thanks for the question regarding "SQL_TRACE -- how to enable in other sessions.", version 8.1.5
You Asked
Hi, Tom,

I would like to turn on sql_trace for some sessions without shutting down
database. But it doesn't seem like I can do "alter system set sql_trace = true".
Also the package "DBMS_SYSTEM" is wrapped in the database. I can't find
definition for this package. Am I supposed to use this package to turn on
sql_trace in other sessions?

Thanks,
Mike
and we said...
It is true, you cannot do an SYSTEM level alter to turn on global tracing,
however you can describe dbms_system like this:

ops$tkyte@8i> desc sys.dbms_system
PROCEDURE DIST_TXN_SYNC
[snipped a bunch of undocumented procedures...]

PROCEDURE SET_SQL_TRACE_IN_SESSION
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 SID                            NUMBER                  IN
 SERIAL#                        NUMBER                  IN
 SQL_TRACE                      BOOLEAN                 IN

So, if you get the SID, SERIAL# of a session from v$session you can turn on
tracing using dbms_system.  You'll need execute on it of course and since there
is no public synonym you'll need to refer to it by its fully qualified name.
So, a call such as:

ops$tkyte@8i> exec sys.dbms_system.set_sql_trace_in_session( 46, 13217, TRUE );

PL/SQL procedure successfully completed.

would enable tracing in that session.

followup to comment one

ops$tkyte@ORA8I.WORLD> alter system set sql_trace = TRUE;
alter system set sql_trace = TRUE
                 *
ERROR at line 1:
ORA-02095: specified initialization parameter cannot be modified

My documentation does not say that sql_trace can be set at a system level -- if
you provide me a version/page number, i'll gladly file a documentation bug on
that.

as for the "batch upon logon", my suggestion is to let the batch start, then
enable tracing in the batch program using dbms_system.

additionally you can use a LOGON trigger to issue an alter session set
sql_trace=true;
Review & Followup
Rating: 2
August 08, 2001
Reviewer: A reader
But why the Oracle Manual indicates "alter system set sql_trace = TRUE" if this
is not possible  ?

Besides, what if there is a batch which logon upon execution, then there is no
way to know its SID and SERIAL# in advance.  Therefore, other than shutting down
the intanace to set the global tracing, I don't see there is any other ways.

Rating: 3
August 08, 2001
Reviewer: Tianhua Wu from Atlanta, GA
If you want to turn on sql_trace on system (instance) level, you need put
sql_trace = true in the init.ora file and bounce the database.
Interestingly,  when I query v$parameter, Oracle indicates that this parameter
can not be changed at session or system level on fly.  But everybody know that
it can be changed at session level.

Rating: 3
August 08, 2001
Reviewer: Tianhua Wu from Atlanta, GA
Follow up on my previous comment, you can set events 10046 at level 12 which is
equivalent of sql trace at system level. However it only affects all new
sessions started after you set the events. Also if you want to turn it off, you
must bounce the database.

Rating: 3
August 08, 2001
Reviewer: Prince
You probably may be able to turn of the 10046 tracing by

alter system set events '10046 trace name context off' ;

Rating: 4
August 08, 2001
Reviewer: Tianhua Wu from Atlanta, GA
Thanks, Prince.  It works.

Rating: 4
Reader October 30, 2001
Reviewer: Reader
Tom,

I like to turn the sql trace on Production *online*
database using
dbms_system(<sid>,<serial#>,true/false),
dbms_system.set_ev or dbms_system.set-sql_trace_in_session.

Being a 3-tier system with tuxedo as the middle tier,
it is was difficult to  choose a process that can be traced. Hence I tried
"oradebug"
successfully by
            SQL>oradebug setospid
            SQL>oradebug event 10046 ...
   and trace the shared server process.
   (example ora_s005_<SID> process from v$process)
   The trace file was found in *bdump* directory and
   it had SQL and bind values

Your comments please and any suggestions for better
way to trace

Thanks

Followup:
using tuxedo and shared servers makes it very very hard to get a meaningful
trace at all.

shared servers dump their traces to the bdump directory -- dedicated servers
(the only kind I find a meaningful trace from) to the udump.

I never trace with shared servers, it just is a mess and pretty meaningless as
my transaction with 5 statements in it might very well be processed by 5
different shared servers!  No way to see whats really happening.

Rating: 4
Reader October 31, 2001
Reviewer: Reader from USA
Tom,

Per URL
http://asktom.oracle.com/pls/ask/f?p=4950:8:228105::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1098633453913,%7Bdedicate%7D
"
It is still 1 for each user process in MTS however in MTS a user process is
shared by many users.
"

According to the statement, I inferred that 1 shared server
is dedicated to a user process (session) for all the
required processing of parse, bind, exec, and fetch.

Is this true. Is seems like the fetch is done by one shared
server and the bind is done by another shared server.
Example:
BIND #7, and
EXEC #7 are found in the trace file of "s003"

However the corresponding SQL statement is found
CURSOR #7 of another trace file "s004"

Is this possible or I am missing something

Thanks

Followup:
No, you misread that quote.

They asked about how to set the processes parameter.  I said in MTS you still
need to add one to processes for each USER process you will use -- just that in
MTS, a user process is shared by many users.

With a sql statement the parse can be done by server1, the execute by server2,
the fetch of the first 10 rows by server3, the fetch of the next 10 rows by
server4 and so on.

Using SQL_TRACE with MTS is virtually useless.  The trace data gets written all
over the place.

Rating: 4
Bug November 01, 2001
Reviewer: A reader
Hi, Tom,

Here is the docu: Oracle 8i Desiging and Tuning for
Performance Release 2(8.1.6)  A76992-01

Section 6: Using SQL Trace and TKPROF under step 2:
<Quote>
alter system set sql_trace=true/false
<QUOTE END>

Please file a bug on that,

Thanks

Rating: 5
Reader December 08, 2001
Reviewer: Reader from USA
Tom,

<quote>
With a sql statement the parse can be done by server1, the execute by server2,
the fetch of the first 10 rows by server3, the fetch of the next 10 rows by
server4 and so on.
<quote>

What is the rational in switching the shared  server in
midstream of fetching rows. Is there a time slice strategy or some other similar
strategy involed. Could you explain, when context switching shared servers, will
it lead to holding DML locks longer in case of DMLs, which
can cause resource contention problems

Thanks

Followup:
When you submit a request to the server (eg: PARSE this query), we will get a
shared server (any one) and use it to parse.  When the parse is done, and
control returns to the client, the shared server you used is free to be used by
someone else.  When you say "execute this parsed statement", that can be done by
another shared server, different from the first.  It will execute the statement
from beginning to end without giving up control.  When the statement is
executed, the client regains control and the shared server is free to service
another user.  When you ask to "fetch the first 10 rows", yet another shared
server is used (might be the same one you used already or a new one).  When the
first 10 rows are returned to you - that shared server is returned to the pool
to be used by some other session.

So, in short, you monopolize a shared server for the duration of a call.  If you
really want to jam up your system with MTS, just fire up lots of processes that
call "begin dbms_lock.sleep(1000); end;".  Each one of those will monopolize a
shared server for 1,000 seconds -- making it impossible for other sessions to
use it.  When using MTS, you must be using short calls.

As for DML locks -- they are (as ALWAYS) held for the duration of your
TRANSACTION.  If you submit:

  update t1
  insert t2
  delete t3
  commit

you will probably make at least 4 calls (one each for each of the statements)
and you will hold a shared server for the duration of each of the calls (eg:
when you are NOT executing a statement, you are NOT using a shared server).
Your locks that you are gaining over the course of your transaction are held
until you COMMIT as normal.

Rating: 5
Reader December 12, 2001
Reviewer: Reader from USA
Tom,

Thanks for your complete, comprehensive and outstanding
information.

Few follow up questions, that I like to clarify.

1. select <column_list> for update;
   Performs parse and execute only and two shared servers
   are utilized

2. Other queries like: select * from <object_name>;
   Performs parse, execute and fetch and hence use >= 3.
   During the fetch phase, switching of shared servers is
   based upon the settings like, arraysize (in SQL) or
   sort_area_size (if sort etc. is performed).... Is
   this correct assumption.

Thanks very much

Followup:
You cannot tell how many shared servers would be used by each.  At least one for
#1, maybe more then one.  It is a function of the number of round trips (calls)
to the database.

If you set autotrace on in sqlplus, you can get a good feel for this:

scott@ORA817DEV.US.ORACLE.COM> select ename from emp for update;

14 rows selected.

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

scott@ORA817DEV.US.ORACLE.COM> set arraysize 10
scott@ORA817DEV.US.ORACLE.COM> select ename from emp for update;

14 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
         22  db block gets
          4  consistent gets
          0  physical reads
          0  redo size
       1249  bytes sent via SQL*Net to client
        536  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         14  rows processed

scott@ORA817DEV.US.ORACLE.COM>

which should also answer #2 -- it is a function of the number of fetches you
need to make.  sort_area_size won't have anything to do with it (i won't make
more or less calls due to the sort_area_size, i'll sort to disk or not based on
it), arraysize in sqlplus does.

Rating: 5
Reader December 15, 2001
Reviewer: Reader from USA
Tom

Thanks very much

BTW, by "sort_area_size", I actually meant the
sort buffer = sort_multiblock_read_count*db_block_buffer,
sort_multiblock_read_count having a dynamic scope for tuning

Rating: 5
July 25, 2003
Reviewer: A reader

Rating: 4
Insufficient privs September 04, 2003
Reviewer: Ma$e
Hi Tom:

I created a logon trigger to start SQL tracing for a specific user. Here is the
code for the trigger: (Note: I get the insufficient error even for system
account)

CREATE OR REPLACE TRIGGER Trgr_LiveLink_Logon
After logon on database
Begin
            if ( user='LL92') then

                execute immediate 'alter session set sql_trace=true';

            End if;

            if ( user='SYSTEM') then

                execute immediate 'alter session set sql_trace=true';

            End if;

End;

The resulting trace file reports insufficient privs.

Dump file d:\oracle\admin\o9ir2\udump\o9ir2_ora_2396.trc
Thu Sep 04 13:24:56 2003
ORACLE V9.2.0.1.0 - Production vsnsta=0
vsnsql=12 vsnxtr=3
Windows 2000 Version 5.0 Service Pack 4, CPU type 586
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, Oracle Label Security, OLAP and Oracle Data Mining
options
JServer Release 9.2.0.1.0 - Production
Windows 2000 Version 5.0 Service Pack 4, CPU type 586
Instance name: o9ir2

Redo thread mounted by this instance: 1

Oracle process number: 18

Windows thread id: 2396, image: ORACLE.EXE

*** SESSION ID:(10.37) 2003-09-04 13:24:56.000
Skipped error 604 during the execution of SYSTEM.TRGR_LL_LOGON
*** 2003-09-04 13:24:56.000
ksedmp: internal or fatal error
ORA-00604: error occurred at recursive SQL level 1
ORA-01031: insufficient privileges
ORA-06512: at line 17

What privs beyond the following do I need ?

SQL>  select * from dba_sys_privs where grantee='LL92';

GRANTEE                        PRIVILEGE                                ADM
------------------------------ ---------------------------------------- ---
LL92                           CREATE VIEW                              NO
LL92                           CREATE TABLE                             NO
LL92                           ALTER SESSION                            NO
LL92                           CREATE SESSION                           NO
LL92                           CREATE TRIGGER                           NO
LL92                           CREATE SEQUENCE                          NO
LL92                           CREATE PROCEDURE                         NO
LL92                           UNLIMITED TABLESPACE                     NO

8 rows selected.

I'm sure it's something silly, that I've overlooked.

Thanks for being my second set of eyes.

Followup:
the OWNER of the trigger needs to have ALTER SESSION granted directly to them.

system for example, does not.

sys@ORA920> select * from dba_sys_privs where grantee='SYSTEM';

GRANTEE                        PRIVILEGE                                ADM
------------------------------ ---------------------------------------- ---
SYSTEM                         UNLIMITED TABLESPACE                     YES

that is why it would fail under system.

Rating: 4
Could this be the cause??? September 05, 2003
Reviewer: Ma$e
I created the following trigger in the schema of the user that I want to trace
and it works.

CREATE OR REPLACE TRIGGER LL92.Trgr_LL_Logon
After logon on database
Begin
            if ( user='LL92') then
                execute immediate 'alter session set sql_trace=true';
            End if;
End;

I'll take this as a working solution.

Is there a better/different way of doing this ?

Thanks

Followup:
umm, look up one review?

that user HAD the alter session priv.

the user that you tried to create the trigger in did not.

the OWNER of the trigger needs to have ALTER SESSION granted directly to them.

Rating: 5
DBMS_SYSTEM September 09, 2003
Reviewer: Tatiane
Tom,

Any idea why DBMS_SYSTEM is not listed in the Oracle9i Supplied PL/SQL Packages
and Types Reference ?

Very strange -- all other packages seem to be.
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96612/toc.htm

      Rating: 5
Why not answer
      ?  September 10, 2003
Reviewer:  Tatiane

Tom, why don't you sometimes answer questions, like above ?

I'm saaaaad :'(

          Followup:

i do not see each and every followup

dbms_system does not officially exist.

it is not documented
it is not supported
it is not available to anyone but sys
it is not meant to be generally used

September 11, 2003 zhu -- Thanks for the question regarding "How to
      find out which process caused the maximum IO request to db server?",
      version 8.1.7

      You Asked
Hi, tom:
  Recently I met a severe performance issue on my db server, though finally
resolved it, it took me quite long time and have adverse impact on client
response time. It is because of Heavy IO wait in the db server.
  It is that after a heavy batch operation on the db server, some module of the
app begin to slow down and middle ware (tuxedo) slow down. Server has high IO
wait(at worst time, 90% cpu spent on IO wait). How can i find out which
process(It is a dedicated database server) caused the most io request to the
server(I am in unix, sun solaris).As It is middle ware, connection is kept for a
long time, and i checked v$sesstat for physical read, redo size , hard to find
out solution.(though finally i found the clue from v$sesstat through reboot unix
and middleware), I need some method to quickly find out which bad process pool
the whole system down.(unix tips, or oracle tips, anything that will work for
me, according to your experience.)
  Thanks very much.

      and we said...
why do you care about a TRANSIENT process (a dedicated server).  suppose I told
you it was dedicated server 1234.  Now what?

You want to find the SQL, the processing, that is doing this -- not a dedicated
server.

You will use statspack.
You will take a snapshot.
15 minutes later take another snapshot.
You will analyze the report and it'll show you the top sql.

You would have found the problem in 15 minutes in the first place had you simply
used statspack from the get go.  It'll tell you the top 5 wait events (IO will
be there), then you can refer to the top SQL to find the sql with the most
physical IO.  Then you can refer to the top IO by tablespace to find the
tablespace with the most IO.  And so on.  Everything you need will be there.

Read
$ORACLE_HOME/rdbms/admin/spdoc.txt

Review &
      Followup

      Rating: 3
statspack does
      not work in my case  January 04, 2003
Reviewer:
      zhu  from cn
Tom, I did used statspack but failed to find that bad sql.
Yes, the top wait event is :
Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
db file sequential read                         1,477,384   17,496,979   84.31
db file parallel write                             41,433    1,107,580    5.34
enqueue                                            21,379      503,590    2.43
buffer busy waits                                  44,763      494,411    2.38
control file parallel write                         4,538      252,388    1.22

And I did look at the statspack sql statement part:
Buffer gets largest:
--I removed the detailed sql here.

and disk read largest sql:
^LSQL ordered by Reads for DB: BIDDB  Instance: biddb  Snaps: 854 -855
-> End Disk Reads Threshold:    1000
--I do not post the detailed sql here.
None of them caused this problem. In fact, It is a big update that caused this
problem.And because of Tuxedo timeout control, It always failed to do its update
and it is controled by Tuxedo to do this update every twenty minutes, and with
more and more records to update, the update is unable to finish timely, and
terminated by tuxedo and caused a lot of rollback with again caused heavy IO
wait
Statspack does not work in my case.In fact, I used statspack very frequently.

          Followup:

If it were a big update -- it would have fell in that report clearly.

The top sql includes updates.

since I wasn't actually there, I cannot comment further.  A big update -- even
one that rolls back -- would have been there.  Perhaps you were measuring AFTER
the fact -- when the update wasn't in fact running.

How long of a window did you use for statspack (hopefully not more than 15-30
minutes at most)?

What WAS the top SQL in that report.

      Rating:
      4
  January 04, 2003
Reviewer:  zhu

Yes, the time interval is 4 hours:
                Snap Id     Snap Time      Sessions
                ------- ------------------ --------
 Begin Snap:        854 02-Jan-03 17:32:46      458
   End Snap:        855 02-Jan-03 21:59:06      458
    Elapsed:                 266.33 (mins)
But during the whole day, system is behavioring poorly because of that sql.

Top SQL in Statspack:
LSQL ordered by Gets for DB: BIDDB  Instance: biddb  Snaps: 854 -855
-> End Buffer Gets Threshold:   30000
-> Note that resources reported for PL/SQL includes the resources used by
   all SQL statements called within the PL/SQL code.  As individual SQL
   statements are also reported, it is possible and valid for the summed
   total % to exceed 100

  Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
     54,986,214       36,769        1,495.5    18.3    630678823
select t1.product_id ,0 ,NVL(t1.BRAND_ID,0) ,NVL(decode(t1.ON_SH
ELL_TYPE,2,t1.FIX_PRICE,6,t1.price1,7,t1.start_price,18,t1.FIX_P
RICE,t1.CURRENT_BID_PRICE),0) ,NVL(t1.DISCOUNT_RATE,0) ,NVL(t1.Q
UANTITY,0) ,TO_CHAR(t1.START_DATE_DTM,'YYYY-MM-DD HH24:MI:SS') ,
TO_CHAR(t1.END_DATE_DTM,'YYYY-MM-DD HH24:MI:SS') ,NVL(t1.STATUS,

     36,589,129       23,830        1,535.4    12.2   1828704478
SELECT TO_CHAR(A.JUDGE_ID) || CHR(9)  || TO_CHAR(A.AUCTION_ID)
|| CHR(9)  || TO_CHAR(A.PRODUCT_ID)  || CHR(9)  || RTRIM(A.TITLE
)  || CHR(9)  || RTRIM(A.AUCTION_TYPE)  || CHR(9)  || TO_CHAR(A.
CRITIC_ID)  || CHR(9)  || RTRIM(B.USER_ACCOUNT)  || CHR(9)  || T
O_CHAR(B.USER_TRUST_SCORE)  || CHR(9)  || TO_CHAR(B.USER_TRUST_C

     10,098,274       85,435          118.2     3.4   2026580849
select count(*)  into :b0  from products a where ((((((:b1<>0 an
d SHOP_GOODS_CLASS_ID=:b1) or :b1=0) or (:b1=(-1) and (nvl(SHOP_
GOODS_CLASS_ID,0)=0 or  not exists (select *  from ShopGoodsClas
s b ,shopcatalog c where (((a.shop_goods_class_id=b.shop_goods_c
lass_id and b.shop_id=c.shop_id) and c.user_id=:b5) and c.status

     10,004,276       23,837          419.7     3.3   1969715255
begin OPEN :vCursor FOR SELECT TO_CHAR ( A . JUDGE_ID ) || CHR (
 9 ) || TO_CHAR ( A . AUCTION_ID ) || CHR ( 9 ) || TO_CHAR ( A .
 PRODUCT_ID ) || CHR ( 9 ) || RTRIM ( A . TITLE ) || CHR ( 9 ) |
| RTRIM ( A . AUCTION_TYPE ) || CHR ( 9 ) || TO_CHAR ( A . CRITI
C_ID ) || CHR ( 9 ) || RTRIM ( B . USER_ACCOUNT ) || CHR ( 9 ) |

      9,884,393       45,044          219.4     3.3   2203634836
select CRITIC_IDENTITY ,CRITIC_REMARK ,count(*)   from UserJudge
 where (((ACCEPTER_ID=:b0 and JUDGE_STATE=:b1) and (SYSDATE-CRIT
IC_TIME_DTM)<=:b2) and ACCP_VISIBLE=1) group by CRITIC_IDENTITY,
CRITIC_REMARK

      7,301,279        9,567          763.2     2.4   1396772918
select count(B.UC_REF_ID) ,A.PRODUCT_ID ,A.TITLE  from PRODUCTS
A ,UC_USERCOMMENT B where (((((((A.STATUS in (1,2) and A.PRODUCT
_ID=B.UC_REF_ID) and B.UC_REF_CODE like '1-%') and B.UC_RECEIVER
_ID=:b0) and A.SELLER_ID=DECODE(:b1,1,A.SELLER_ID,2,:b0,0,A.SELL
ER_ID)) and B.UC_POSTER_TYPE=DECODE(:b1,1,2,2,1,0,B.UC_POSTER_TY

      7,181,092      678,559           10.6     2.4   3308940087
begin :sellcount := GetSellAmount ( :p_id ) ; END ;

      7,068,876       22,522          313.9     2.4     54290869
select A.CRITIC_IDENTITY ,A.CRITIC_REMARK ,count(*)   from UserJ
udge A ,USERPRIVATEINFO B where ((((A.CRITIC_ID=B.USER_ID and B.
USER_TYPE>=:b0) and A.ACCEPTER_ID=:b1) and A.JUDGE_STATE=:b2) an
d A.ACCP_VISIBLE=1) group by A.CRITIC_IDENTITY,A.CRITIC_REMARK

Disk read sql:
^LSQL ordered by Reads for DB: BIDDB  Instance: biddb  Snaps: 854 -855
-> End Disk Reads Threshold:    1000

 Physical Reads  Executions  Reads per Exec % Total  Hash Value
--------------- ------------ -------------- ------- ------------
        123,750       28,308            4.4     6.5   3636753446
SELECT A.PRODUCT_ID,B.QUANTITY_GET   FROM PRODUCTS A,PRODUCTBIDI
NFO B  WHERE B.USER_ID = :b1  AND B.STATUS = 0  AND B.PRODUCT_ID
 = A.PRODUCT_ID  AND A.STATUS IN ( 1,2  ) AND A.END_DATE_DTM >=
SYSDATE  AND A.ON_SHELL_TYPE != 6 ORDER BY A.END_DATE_DTM,A.PROD
UCT_ID,B.ROWID DESC

        123,747       14,543            8.5     6.5   2343244299
begin OPEN :myeach_buyinginfo_cur FOR SELECT A . PRODUCT_ID , B
. QUANTITY_GET FROM PRODUCTS A , PRODUCTBIDINFO B WHERE B . USER
_ID = :lUser_id AND B . STATUS = 0 AND B . PRODUCT_ID = A . PROD
UCT_ID AND A . STATUS IN ( 1 , 2 ) AND A . END_DATE_DTM >= SYSDA
TE AND A . ON_SHELL_TYPE <> 6 ORDER BY A . END_DATE_DTM , A . PR

        102,595        3,879           26.4     5.4    275259148
select NVL(sum(TX_TransactionAmount),0) into :b0  from TX_Transa
ction where (((PA_AccountID=:b1 and TO_char(TX_TransactionDate,'
YYYYMM')=:b2) and TX_Status not  in (1005,4005)) and TX_Transact
ionAmount<0)

         93,979           15        6,265.3     4.9    998854616
select count(*)  into :b0  from RealNameVerifyInfo A where (((A.
DATE_MEM_APPL between TO_DATE(:b1,:b2) and TO_DATE(:b3,:b2) and
ITEM_TYPE=1) and ITEM_STATUS=1) and CREDITCARD_TYPE=:b5)

         82,794       22,912            3.6     4.4   1835894617
select count(*)  into :b0  from uc_usercomment where (uc_poster_
id=:b1 and uc_create_dtm>=(sysdate-:b2))

         78,503           12        6,541.9     4.1    450156570
SELECT B.USER_ACCOUNT,A.REAL_NAME,A.ITEM_VALUE,NVL(A.CREDITCARD_
TYPE,0),NVL(TO_CHAR(A.CREDITEXPIRE_DATE,:b1) || :b2 ,' ') || NVL
(TO_CHAR(A.CREDITEXPIRE_DATE,:b3) || :b4 ,' ')  || NVL(TO_CHAR(A
.CREDITEXPIRE_DATE,:b5) || :b6 ,' ') ,A.TRACE_NO,NVL(A.IDCARD_NO
,' ')   FROM REALNAMEVERIFYINFO A,USERPRIVATEINFO B  WHERE A.USE

         77,713            7       11,101.9     4.1   1894247638
select count(*)  into :b0  from USERPRIVATEINFO where (USER_ID>0
 and USER_STATUS=:b1)

         56,095           12        4,674.6     2.9   2366728271
select count(*)  into :b0  from st_smstrace where (((((:b1 is  n
ot null  and rtrim(st_mpnumber)=:b1) or :b1 is null ) and ((:b4<
>0 and st_status=:b4) or (:b4=0 and st_status<>136))) and ((:b7
is  not null  and st_submittime>=to_date(:b7,'yyyy-mm-dd')) or :
b7 is null )) and ((:b10 is  not null  and st_submittime<=to_dat

         51,844        5,741            9.0     2.7   1273485882
select count(distinct A.PRODUCT_ID) into :b0  from PRODUCTS A ,U
C_USERCOMMENT B where (((((((A.STATUS in (1,2) and A.PRODUCT_ID=
B.UC_REF_ID) and B.UC_REF_CODE like '1-%') and A.SELLER_ID=DECOD
E(:b1,1,A.SELLER_ID,2,:b2,0,A.SELLER_ID)) and B.UC_RECEIVER_ID=:
b2) and B.UC_POSTER_TYPE=DECODE(:b1,1,2,2,1,0,B.UC_POSTER_TYPE))

         51,052       15,033            3.4     2.7    292807924
^LSQL ordered by Reads for DB: BIDDB  Instance: biddb  Snaps: 854 -855

And the Actual SQL that caused the server slow down is:
UPDATE products
        set status=3
        WHERE end_date_dtm<=sysdate and
              end_date_dtm>sysdate-0.1 and
              ( ( ( (status=1) or (status=2) ) and (end_date_dtm<sysdate) ) or
              ( ( (status=1) or (status=2) ) and (quantity=quantity_saled_fix) )
) and
              ( (on_shell_type<6) or (on_shell_type=18) ) and
              nvl(cost,0)<>1 ;
This SQL will update about 6K records, and should be able to finish in 3
minutes(I tested this SQL in another machine with the actual data exported from
the standby database, which has not been updated because of 6 hour later than
the production), However, it failed to finish the SQL in time and rolled back by
tuxedo.
  The strange thing is that in a less powerful machine (3500 8CPU 5 old disk
raid5), when I was testing the update, IO wait is about just 30% and IO busy is
just about 50-80%, but in the production with 12 CPU and 9 disk Raid5,(with
concurrent user), when I manually run the sql, 5 minutes passed and it did not
finish.Execution plan is the same and the data is the same.IO busy(from iostat)
is 100% and cpu spent on IO wait is 90%(From top and sar/iostat) and I have to
abort this SQL and later break this update into several update and commit.
Thanks.

          Followup:

4 hours is at least 8 times too long.  You cannot pinpoint a bottleneck with
such a large timeframe.  15-30 minutes *max*

Seems to me, you tune these two:

  Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
     54,986,214       36,769        1,495.5    18.3    630678823
select t1.product_id ,0 ,NVL(t1.BRAND_ID,0) ,NVL(decode(t1.ON_SH
ELL_TYPE,2,t1.FIX_PRICE,6,t1.price1,7,t1.start_price,18,t1.FIX_P
RICE,t1.CURRENT_BID_PRICE),0) ,NVL(t1.DISCOUNT_RATE,0) ,NVL(t1.Q
UANTITY,0) ,TO_CHAR(t1.START_DATE_DTM,'YYYY-MM-DD HH24:MI:SS') ,
TO_CHAR(t1.END_DATE_DTM,'YYYY-MM-DD HH24:MI:SS') ,NVL(t1.STATUS,

     36,589,129       23,830        1,535.4    12.2   1828704478
SELECT TO_CHAR(A.JUDGE_ID) || CHR(9)  || TO_CHAR(A.AUCTION_ID)
|| CHR(9)  || TO_CHAR(A.PRODUCT_ID)  || CHR(9)  || RTRIM(A.TITLE
)  || CHR(9)  || RTRIM(A.AUCTION_TYPE)  || CHR(9)  || TO_CHAR(A.
CRITIC_ID)  || CHR(9)  || RTRIM(B.USER_ACCOUNT)  || CHR(9)  || T
O_CHAR(B.USER_TRUST_SCORE)  || CHR(9)  || TO_CHAR(B.USER_TRUST_C

And you'll get your BIGGEST bang for the buch.  55million executions in 4 hours:

ops$tkyte@ORA920> select 32769/(4*60*60) from dual;

36769/(4*60*60)
---------------
     2.55340278

that's 2.5 times A SECOND.  Fix that query and you'll see a huge gain in overall
system response time!

If you notice -- you have enqueue waits -- what are the odds this update is
actually getting BLOCKED?

9 disk Raid5 <<<== raid5 = "slow = true", sure hope redo, temp and arch is NOT
on raid 5, no matter what glorious things the hardware vendor said about it.

why not consider running the SQL in the database, not via tuxedo, pre-empt the
tuxedo problem entirely.  Tuxedo isn't for batch -- it is for high end OLTP.

(but running the same poorly performing query 2.5 times a second -- some very
very looooowwww  hanging fruit there).

Tell me, when you tested the update -- did you do it when you had Upteem
hundred/thousand users concurrently hitting the same data?

      Rating: 3
Some
      ?'s  February 04, 2003
Reviewer:  reader
       from USA
Tom,
In your respose :
select 32769/(4*60*60) from dual;

what does each value represent?

Also, I am facing a situation where enqueue is the top wait event. can you
please tell me what is this wait event?

thanks.

          Followup:

it was from:

  Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
     54,986,214       36,769        1,495.5    18.3    630678823
....

And you'll get your BIGGEST bang for the buch.  55million executions in 4 hours:

ops$tkyte@ORA920> select 32769/(4*60*60) from dual;

36769/(4*60*60)
---------------
     2.55340278

that's 2.5 times A SECOND.

that query is executed 2.5 times a second (4 hours * 60 minutes * 60 seconds )

Enqueue -- easy, thats a fancy name for a LOCK.  You have locking contention
(eg: I've updated a row you are waiting to update and you wait for me to commit)

      Rating: 5
Can you explain
      what this indicates  February 04, 2003
Reviewer:
      Bala  from NJ, USA
How do you arrive at the figure of 55 million executions from the report...

What does that mean ..

that query is executed 2.5 times a second (4 hours * 60 minutes * 60 seconds )

Can you interpret this

          Followup:

sorry that 55mill exec/hour should have been 36k exec/hour -- the math was right
the text wasn't:

Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
     54,986,214       36,769        1,495.5    18.3    630678823
....

And you'll get your BIGGEST bang for the buch.  36,769 executions in 4
hours:

ops$tkyte@ORA920> select 32769/(4*60*60) from dual;

36769/(4*60*60)
---------------
     2.55340278

that's 2.5 times A SECOND.

that query is executed 2.5 times a second (4 hours * 60 minutes * 60 seconds )

What I meant by that was take the number of seconds in 4 hours -- 4*60*60 and
divide 36,769 by that amount and you get 2.5 executions per second.

      Rating: 4
I am in same
      situation  February 05, 2003
Reviewer:
      reader  from USA
I am trying to resolve locking issues on our production system. Just want to get
your opinion on the following, which is just a tiny bit of information from tons
that i generated (tkprof and statspack).

I am particularly interested in resolving enqueue. And the table in the query
was the one being locked.

Thanks.
                                                                    Avg
                                                     Total Wait    wait  Waits
Event                               Waits   Timeouts  Time (cs)    (ms)   /txn
---------------------------- ------------ ---------- ----------- ------ ------
enqueue                               915        904     278,899   3048    2.9
db file sequential read           107,245          0     126,190     12  336.2
latch free                          3,920      3,609       5,391     14   12.3

  Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
     17,463,282       18,033          968.4    46.6   2921279387
select TRACE01.TYPE ,TRACE01.STATUS ,TRACE01.SEQUENCE into :b0,:
b1,:b2  from TRACE TRACE01 where TRACE01.TYPE=NVL(RTRIM(:b3,' ')
,' ')

          Followup:

my opinion?  looks like you have a blocking problem -- that is all I can say
about that.

no idea of the duration of the statspack.  is that 2788 seconds of wait over 15
minutes, a day, a week....

use v$lock to query up who's blocking who and on what object periodically.

      Rating: 4
In
      addition  February 05, 2003
Reviewer:
      reader  from USA
                Snap Id     Snap Time      Sessions
                ------- ------------------ --------
 Begin Snap:         12 31-Jan-03 22:03:19       26
   End Snap:         14 31-Jan-03 22:30:45       26
    Elapsed:                  27.43 (mins)

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
enqueue                                               915      278,899   66.86
db file sequential read                           107,245      126,190   30.25

BIG question is :
how can i have enqueue wait of 47min when the duration of
snapshot is 27 min.

please help -thanks

          Followup:

if you have 47 people wait 1 minute each, you'll have 47 minutes of enqueue
waits.

It is cumulative.  you can have a huge number of people waiting.  If you have
100 people ALL BLOCKED by the same guy for 5 seconds -- that'll be 500 seconds
of wait time.

      Rating:
      4
Blocking?  February 05, 2003
Reviewer:
      reader  from USA
This is a month end batch job. how and what triggers blocking? is this different
than locking?

the object that is blocked is the table in the top sql.

thanks for your help. your comments are very helpful

          Followup:

i update a row

you want to update the same row

I block you until I commit.

it is contention for the same resource.  Locking is something that can lead to
blocking if the locked elements need to be locked by more then one session at
the same time.

that top sql is a select without for update.  Other then it does probably way
too much LIO -- that query isn't relevant.

It'll be the insert/update/deletes or select for updates against that object
that are.

      Rating: 4
OK 2nd
      statement in Top SQL  February 05, 2003
Reviewer:
      reader  from USA
Tom,
here is the second statement in TOP sql:
***********************************************
select TRACE01.TYPE ,TRACE01.SEQUENCE ,TRACE01.NUMBER0 ,TRACE01.
RANGE_START ,TRACE01.RANGE_END ,TRACE01.STATUS ,TO_CHAR(TRACE01.
LAST_UPDATED,'YYYY-MM-DDHH24.MI.SS')  from TRACE TRACE01 where (
TRACE01.TYPE=:b0 and TRACE01.STATUS=NVL(RTRIM(:b1,' '),' ')) for
 update of NUMBER0
***************************************
key point is that it is the same table with a update of
statement. Copule of questions :

1. table is small 220 rows, i am told that a process will go only after its row
via TYPE col. my understanding is that for update of clause should not lock the
table as it is going via TYPE hence will only lock its row. is my understanding
correct?

2.Any comments on db file sequential read wait event it is also quite hige for
the snapshot window.

Thanks for your help. i appreciate your comments and they are helping in
clearing out the path.

thanks.

          Followup:

1) correct.  as long as they each use the OWN :b0 values for that bind --
they'll not block eachother.

2) it is indicative of doing lots and lots of indexed reads that cause lots of
physical IOs

      Rating:
      4
  February 05, 2003
Reviewer:
      reader  from USA
Tom,
what does waits and total wait time columns indicate?

does the difference between enqueue wait time and snapshot duration means that I
have many sessions going after the same row?

is it possible to get that information from statspack report?

also i thing enqueue wait event indicates application issue, am i on the right
path?

thanks and appreciate your time.

          Followup:

waits = number of discrete times you waited on that event.
total wait time = sum of the times you waited..

100 people wait for 2 seconds apiece means -- 100 waits, 200 seconds total wait
time

the enqueue waits means you have a blocking/blockee issue.  whether it is a
single row or many rows or something else -- cannot say.   statspack is
aggregate instance stuff, you would have to monitor v$lock to track it down.

enqueue waits are typically an application issue, yes.

      Rating: 4
Autotrace
      columns  February 05, 2003
Reviewer:  reader
       from USA
Tom,
what is your take on the following, also if you can reinforce my understanding
of what each columns means would be a great help. I trid to search the site
couldnt find what i was looking for, unless i missed something.

thanks for your time and advice.

SQL> /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=32)
   1    0   FOR UPDATE
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'TEST_TRACE' (Cost=1 Ca
          rd=1 Bytes=32)

   3    2       INDEX (UNIQUE SCAN) OF 'TRACE1UX' (UNIQUE)

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

SQL> /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=32)
   1    0   FOR UPDATE
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'TEST_TRACE' (Cost=1 Ca
          rd=1 Bytes=32)

   3    2       INDEX (UNIQUE SCAN) OF 'TRACE1UX' (UNIQUE)

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

          Followup:

looks like the first time around it did a HARD parse (eg: query was brand new,
never seen before).  the recursive sql was to parse the query.

the second time, no hard parse, thus no recursive sql, thus less logical IOS...

      Rating: 4
Why no commit
      in tkprof  February 07, 2003
Reviewer:
      reader  from USA
Tom,
when I do select for update and issue a commit it does not get recorded in
tkprof. It records it when i do insert statement and commit. Can you please
explain why this behaviour?

          Followup:

i see it in mine:

PARSING IN CURSOR #3 len=26 dep=0 uid=146 oct=2 lid=146 tim=1020160631338492
hv=11516084 ad='5eb5fd78'
insert into t values ( 1 )
END OF STMT
PARSE #3:c=3906,e=3149,p=0,cr=1,cu=0,mis=1,r=0,dep=0,og=4,tim=1020160631338469
BINDS #3:
EXEC #3:c=0,e=720,p=0,cr=1,cu=20,mis=0,r=1,dep=0,og=4,tim=1020160631339352
WAIT #3: nam='SQL*Net message to client' ela= 7 p1=1650815232 p2=1 p3=0
WAIT #3: nam='SQL*Net message from client' ela= 1490 p1=1650815232 p2=1 p3=0
=====================
PARSING IN CURSOR #3 len=6 dep=0 uid=146 oct=44 lid=146 tim=1020160631344825
hv=3867936055 ad='5daf8d44'
commit
END OF STMT
PARSE #3:c=0,e=114,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=1020160631344801
XCTEND rlbk=0, rd_only=0
EXEC #3:c=0,e=719,p=0,cr=0,cu=1,mis=0,r=0,dep=0,og=4,tim=1020160631345671
WAIT #3: nam='SQL*Net message to client' ela= 6 p1=1650815232 p2=1 p3=0
WAIT #3: nam='SQL*Net message from client' ela= 860 p1=1650815232 p2=1 p3=0
=====================
PARSING IN CURSOR #3 len=26 dep=0 uid=146 oct=3 lid=146 tim=1020160631348200
hv=2108716148 ad='5eba58f8'
select * from t for update
END OF STMT
PARSE #3:c=1953,e=1051,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=4,tim=1020160631348175
BINDS #3:
EXEC #3:c=0,e=545,p=0,cr=7,cu=2,mis=0,r=0,dep=0,og=4,tim=1020160631348928
WAIT #3: nam='SQL*Net message to client' ela= 5 p1=1650815232 p2=1 p3=0
FETCH #3:c=0,e=96,p=0,cr=5,cu=0,mis=0,r=1,dep=0,og=4,tim=1020160631349194
WAIT #3: nam='SQL*Net message from client' ela= 518 p1=1650815232 p2=1 p3=0
FETCH #3:c=0,e=83,p=0,cr=2,cu=0,mis=0,r=0,dep=0,og=4,tim=1020160631350070
WAIT #3: nam='SQL*Net message to client' ela= 5 p1=1650815232 p2=1 p3=0
WAIT #3: nam='SQL*Net message from client' ela= 1431762 p1=1650815232 p2=1 p3=0
STAT #3 id=1 cnt=1 pid=0 pos=1 obj=0 op='FOR UPDATE  (cr=7 r=0 w=0 time=146 us)'
STAT #3 id=2 cnt=2 pid=1 pos=1 obj=39210 op='TABLE ACCESS FULL T (cr=14 r=0 w=0
time=278 us)'
=====================
PARSING IN CURSOR #3 len=6 dep=0 uid=146 oct=44 lid=146 tim=1020160632782666
hv=3867936055 ad='5daf8d44'
commit
END OF STMT
PARSE #3:c=0,e=113,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=1020160632782645
XCTEND rlbk=0, rd_only=0
EXEC #3:c=0,e=323,p=0,cr=0,cu=1,mis=0,r=0,dep=0,og=4,tim=1020160632783109
WAIT #3: nam='log file sync' ela= 379 p1=652 p2=0 p3=0
WAIT #3: nam='SQL*Net message to client' ela= 6 p1=1650815232 p2=1 p3=0
WAIT #3: nam='SQL*Net message from client' ela= 554701 p1=1650815232 p2=1 p3=0
XCTEND rlbk=0, rd_only=1
=====================

      Rating: 4
Please ignore
      previous ?  February 07, 2003
Reviewer:
      reader  from USA
I was aggregating please ignore previous ?

thank you

      Rating: 4
Selecting the
      correct snapshot time interval  March 12, 2003

Reviewer:  Mohsin  from Canada
Tom:

Earlier on in this thread you suggested a 15-30min sampling interval. Could you
please share your real life experience on the correct selection of this
interval.

Say a sql starts at time t1 and ends at time t2
but my first snapshot was taken at t1-14 mins and the next one was at t1+1 min.
So from the time the sql started and finished the performance metrics are now in
two snapshots. How would this be represented in the report if I ran the
statspack report that spanned only one snapshot and not the next (which is when
the sql actually completed).
Which raises the issue, Having collected statspack info at 15 min interval, is
the report that spans several snapshots an average of data collected over all
the snapshots.

thanks for sharing your vast pool of knowledge.

MJ

          Followup:

15-30 minutes is the correct sample size.

You are looking at the SYSTEM, not a single query, not a single user.  You take
a 15 minute window and say "so, what's happening"

      Rating: 2
Agreed
      but  March 13, 2003
Reviewer:  Mohsin
       from Canada
I agree that the data collected is at the system level.

So what path can I follow to "drill" down a statspack datacollection exercise
during which we collected snapshot data at a 15min interval over a business day
and now I need to analyze the data to determine the "cause of bad/poor
performance" or determine what was happening in the d/b.

Let's say we identified that users percieved poor performance was at around
10:00 am.

How should I progress from here?

          Followup:

umm, look at the top timed events and go from there.

If you have no waits of any measure, then you have done what you can -- beyond
changing your ALGORITHMS -- which means you have to go back to the applications
and use profilers (dbms_profiler or sql_trace+tkprof) and make them more
efficient.

If you have massive waits on resources, remove them -- how you do that varies
wait by wait.  Big library cache latch waits?  you didn't use binds.  big PIO
waits -- maybe a query or two or more needs to be tuned and so on.

      Rating:
      5
dba  March 13, 2003
Reviewer:  Harry
       from Canada
Tom, is there any sql script that can be used to find most expensive sql without
using stats pack, and better if it also tell me from how long this query is
active.

Regards
Garry

          Followup:

define "most expensive".

then look at v$sql -- lots of stuff there (hey, thats what statspack uses)

but stats pack will tell you the most "expensive" (by MANY different criteria)
over a period of time which is more relevant probably then "forever"

      Rating: 5
What does CPU
      Time mean  April 02, 2003
Reviewer:  Arun
      Gupta  from Harriburg, PA USA
Tom
In Oracle 9.2, in the statspack report, I get:

Top 5 timed events                       %Total
Event                         Time(s)   Ela time
-------------------------------------------------
CPU Time                      70         79.8

What is the meaning of CPU Time and why is it appearing ?
Thanks

          Followup:

The Top 5 Wait events section on the first page of the Statspack report
(spreport.sql) has been renamed to Top 5 Timed events as it now includes CPU
usage as well as wait events. This avoids the need to look at the v$sysstat
section of the report to determine if CPU usage or wait events are the main time
consumers on a system. Note: this does not mean that there is a CPU time wait
event!

from spdoc.txt in rdbms/admin:

9.  New and Changed Features
----------------------------

9.1.  Changes between 9.0 and 9.2

Changes on the Summary Page of the Instance Report (spreport.sql)

  o  The Top 5 Wait Events has been changed to be the Top 5 Timed Events.

     What was previously the Top 5 Wait Events has been expanded to give the
     Top 5 time usage within the instance: i.e. in addition to including Wait
     events, this section can now include the 'CPU used by this session'
     statistic.  This statistic will appear in the Top 5 only if it's value
     is one of the the Top 5 users of time for the snapshot interval.

         Note that the name of the statistic 'CPU used by this session' will
         actually appear in the Top 5 section as 'CPU Time'.  The statistic
         name is masked in the Top 5 to avoid the confusion of the suffix
         'by this session'.
         The statistic will continue to appear in the System Statistics
         (SYSSTAT) section of the report as 'CPU used by this session'.

    Additionally, instead of the percentage calculation being the % Total
     Wait Time (which is time for each wait event divided by the total wait
     time for this snapshot interval), the percentage calculation is now
     % Total Elapsed Time (which is time for each timed event divided by
     the total elapsed time).
     i.e.
       previously:  time for each wait event  / total wait time for all events
       now:         time for each timed event / total elapsed time

     The total elapsed time is computed using the total wait time for all
     events added to the total CPU time used for the interval
     i.e.
       total elapsed time = total wait time + total CPU time

     Purpose
     ~~~~~~~
     The purpose for including CPU time with wait events:

     When tuning a system, the first step is to identify where the most of the
     time is spent, in order to identify where the most productive tuning
     effort should be concentrated.

     The majority of time could be spent in waiting for events to complete
     (and so be identifyable in the wait event data), or the system could be
     consuming much CPU (for which Operating System statistics, and the Oracle
     CPU statistic 'CPU used by this session' in SYSSTAT are examined).
     Having the CPU Time co-located with the wait events in the Top 5 section
     of the instance report makes it easier to compare the relative values
     and to identify whether the most productive investigation would occur
     by drilling down the wait events, or in reducing Oracle CPU usage
     (e.g. by tuning SQL).

      Rating: 5
any
      demo?  July 25, 2003
Reviewer:  Reader

Tom, do you have any demo to illustrate oracle wait events? Thanks.

          Followup:

umm, no -- not really.

      Rating:
      5
  July 26, 2003
Reviewer:
      A reader
Tom in v$sqlarea the column sql_text is varchar2(1000), what if the query is
more than that and I would like to see the entire query without getting
truncated.

Thanks.

          Followup:

v$sqltext_with_newlines

has the full text

      Rating:
      5
  July 26, 2003
Reviewer:
      A reader
Thanks Tom!!

      Rating: 4
Confused with
      CPU time.  August 21, 2003
Reviewer:  Babu
       from Sunnyvale CA
Hi Tom,

Excume me for asking a simple question here:
What is CPU time, how it is measured, and how is it realted load average???

I see the following output in my statspack report with 4hr interval(very long,
i'm gonna use 15 min intervals from now onwards).

Statistic                                    Total   per Second    per Trans
--------------------------------- ---------------- ------------ ------------
CPU used by this session                 2,513,525         58.1          9.5
CPU used when call started               2,515,025         58.2          9.5

What is CPU used by this session "Toal", "Per second", and "per transaction"????

Whats is the unit of CPU time, when it says CPU used by this session per second
is 58.1 -- what does 58.1 indicate.

Please explain. Thanks in advance...

          Followup:

cpu seconds is the number of seconds the cpu was being used.

that number is in centi seconds (100'ths of a second).

so that says "every second, you used 0.58 cpu seconds".  if you have a single
cpu, that means on average you used about 60% of it.

is that a 12 hour snap really?

      Rating: 5
Re: Confused
      with CPU.  August 21, 2003
Reviewer:  Babu
       from Sunnyvale, CA
Yes, it was a 12 hr snap report.

We have, 42% cpu time event, 25% db file sequential wait event, and 10% db file
scattered read wait event.

I can tune sequential and scattered read wait events, but how can I tune CPU
time event?

I think, tuning large buffer gets versus executions sql statements in v$sql will
reduce CPU time event. Are there any other ways to tune CPU time event, other
than looking at expensive sql's.

Thanks a lot for your time.

          Followup:

math doesn't lie!  i was a math major in college :)  I knew it was 12 hours, not
4.

the cpu event is just a TIMED EVENT.

it is not a wait event

42% means "i used 42 out of 100 units of some resource that I cannot put into
the bank and use later"

you should only be worried if you are near 100%!!!  42% is great -- really.  it
means you bought two times at much hardware as you really needed!!!!

      Rating: 5
Math
      major...;)  August 21, 2003
Reviewer:
      Kishan  from Singapore
Dear Tom:
I am not sure if you are a math major or magician. Top of this page has this
computatin...We all know you can do wonders with Oracle, but how did you manage
this? Numbers don't lie eh?:)

ops$tkyte@ORA920> select 32769/(4*60*60) from dual;

36769/(4*60*60)
---------------
     2.55340278

          Followup:

ctl-f for 32769

or

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7143307018863#7685770076438

      Rating: 2
Asks for some
      username/password  August 22, 2003
Reviewer:
      A reader
Hi Tom,

 The link you have given in above response asks some userid/password ???

          Followup:

whoops, corrected that.

      Rating: 4
CPU usage from
      STATSPACK  August 22, 2003
Reviewer:  Leo
       from USA
Base on the report, our system CPU usage would be more 100%.  But I monitored
the system, the CPU usage was about 30%.

                                                                Snap Length
Start Id    End Id       Start Time             End Time         (Minutes)
--------  --------  --------------------  --------------------  -----------
    5239      5241  14-Aug-03 14:00:05    14-Aug-03 14:30:03          29.97

Statistic                                    Total   per Second    per Trans
--------------------------------- ---------------- ------------ ------------
CPU used by this session                 1,615,612        898.6         19.6
CPU used when call started                 530,644        295.1          6.4

The system has 8 CPUs, the percentage would be

8.98/8=1.12 = 112%

Is my calculation right?

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7641015793792#8768340190291
and consider that information....

all you needed was a job that was running for 5 hours to finish in that window.

      Rating: 5
physical
      reads  August 22, 2003
Reviewer:  Reader

when I am loading data using sql loader, how does oracle account for physical
reads and writes? Would oracle consider this (sqlloader operation) also a part
of cache hit ratio? Thanks.

          Followup:

depends on whether you use conventional path loads (sql inserts are used) or
direct path loads (read input files and write database files, skip the sql
engine for inserting)

conventional path loading, sqlldr is just another program doing sql against the
database, nothing special at all.

direct path loading, sqlldr bypasses the buffer cache.

cache hit ratios are virtually meaningless!

      Rating: 5
CPU Time in
      statspack  September 10, 2003
Reviewer:
      A reader  from orlando,FL
Sir,
If CPU time is the topper in the statspack report, should  my priority be to
look into the topper sql's OR first tune the foreground waits ( which appear
after CPU Time) and then look into the sql's if possible.
Rgds..

          Followup:

no...

in many cases -- you would be jumping up and down for joy, you actually used a
resource that if you don't use -- you cannot "save up" (eg: if you have a cpu
minute and only use 30 cpu seconds, you'll never ever get that other 30 cpu
seconds back again)....

it just shows you about how much cpu you used during the window of observation.
If you have a 15 minute stats pack, on a 2 cpu box, you have 15*60*2 cpu seconds
(1800).  if the statspack reports says "hey, you had 1,000 cpu seconds" -- that
means there was 800 more you didn't use (you were about 50-60% utilized).

It is just a number...

You need to understand what it represents in the grand context of things...

remember -- there will always be a top five timed events, they are just numbers.
 you may need to do nothing, you may need to do something.

September 11, 2003 atul -- Thanks for the question regarding "User
      Quota", version 8.0.4

      You Asked
Sir,

Generally when we select on DBA_TS_QUOTAS view we will find
BYTES < MAX_BYTES..

But when i did select on a view to get the user quotas approaching towards max i
got surprise to see one USERS quota..

USERNAME                            BYTES  MAX_BYTES
------------------------------ ---------- ----------
User                              114233600   10485760

Here you can see BYTES  > MAX_BYTES..

All other users are having BYTES < MAX_BYTES......

What would be the cause of this?

Thanks.
atul

      and we said...
ops$tkyte@ORA817DEV.US.ORACLE.COM> select tablespace_name, username, bytes,
max_bytes
  2  from dba_ts_quotas;

TABLESPACE_NAME                USERNAME                       BYTES  MAX_BYTES
------------------------------ ------------------------- ---------- ----------
USERS                          OPS$ORA817                         0         -1
USERS                          U2                                 0         -1
USERS                          U1                           1572864         -1
USERS                          PERFSTAT                    53477376         -1

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter user perfstat quota 500k on users;

User altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select tablespace_name, username, bytes,
max_bytes
  2  from dba_ts_quotas;

TABLESPACE_NAME                USERNAME                       BYTES  MAX_BYTES
------------------------------ ------------------------- ---------- ----------
USERS                          OPS$ORA817                         0         -1
USERS                          U2                                 0         -1
USERS                          U1                           1572864         -1
USERS                          PERFSTAT                    53477376     516096

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

Just set their quota to be less then what they already allocated.  This just
means the quota was set AFTER the fact.  After they already allocated the data
space.
Review
      & Followup

      Rating: 4
operating
      system quota  September 15, 2002
Reviewer:
      Abdal-Rahman Agha  from Yemen
Hi Tom,

  I have problem in this area but slightly different. I tried to enter as dba to
my database, but I faced with this error:

   ORA-12549: Operating system resource quota exceeded.

There is no any defined or specified quota from my server.
What is the problem?

          Followup:

Insufficient data to say anything about this.  Open a TAR with support where
they will request lots more info (like, say -- versions of software, versions of
OS, type of OS, etc etc etc -- you know, stuff people would need to know in
order to comment).

      Rating:
      4
  September 10, 2003
Reviewer:
      A reader
Tom,
This is on 9iR2

SQL>sho user
USER is "GE1STOP"
SQL>select * from USER_TS_QUOTAS;

TABLESPACE_NAME                     BYTES  MAX_BYTES     BLOCKS MAX_BLOCKS
------------------------------ ---------- ---------- ---------- ----------
USERS                             5373952          0        656          0

oracle documentation says..
MAX_BYTES
 NUMBER
  User's quota in bytes, or -1 if no limit

and in my MAX_BYTES column i see only 0.

what does it say?..
Thanks

          Followup:

ps$tkyte@ORA920LAP> alter user ops$tkyte quota unlimited on users;

User altered.

ops$tkyte@ORA920LAP> select * from USER_TS_QUOTAS;

TABLESPACE_NAME                     BYTES  MAX_BYTES     BLOCKS MAX_BLOCKS
------------------------------ ---------- ---------- ---------- ----------
USERS                             1572864         -1        192         -1
T_2M                             10485760          0       1280          0
T_1M                                    0          0          0          0

ops$tkyte@ORA920LAP> alter user ops$tkyte quota 0k on users;

User altered.

ops$tkyte@ORA920LAP> select * from USER_TS_QUOTAS;

TABLESPACE_NAME                     BYTES  MAX_BYTES     BLOCKS MAX_BLOCKS
------------------------------ ---------- ---------- ---------- ----------
USERS                             1572864          0        192          0
T_2M                             10485760          0       1280          0
T_1M                                    0          0          0          0

you have a 0k quota, not unlimited quota, on that tablespace

if you are still able to create things however, you might be a dba with
"unlimited tablespace" which overrides that setting.

September 11, 2003 -- Thanks for the question regarding "SQLLoader",
      version 8.0.5

      You Asked
Hi Tom,

I need to load the data from text file into multiple tables.
In each row, the first, second, fourth fields should go into
table"A", third, sixth fields should go into table"B" and
fifth field should go into table "C".
The fields are terminated by ";". Can we load this into multiple
tables?

Thanks in advance,

Jagan

      and we said...
in 8.0, we can do this with some PLSQL code.  See

http://osi.oracle.com/~tkyte/SkipCols/index.html
for the delimited package I use below.

Basically I started with:

create table t1 (field1 int, field2 int, field4 int );
create table t2 (field3 int, field6 int);
create table t3 (field5 int );

And a control file that was:

LOAD DATA
INFILE *
REPLACE
INTO TABLE T1
(
  FIELD1 position(1:1024) "delimited.word(:field1,1,NULL,';')",
  FIELD2 position(1:1)    "delimited.word(:field1,2,NULL,';')",
  FIELD4 position(1:1)    "delimited.word(:field1,4,NULL,';')"
)
INTO TABLE T2
(
  FIELD3 position(1:1024) "delimited.word(:field3,3,NULL,';')",
  FIELD6 position(1:1)    "delimited.word(:field3,6,NULL,';')"
)
INTO TABLE T3
(
  FIELD5 position(1:1024) "delimited.word(:field5,5,NULL,';')"
)
BEGINDATA
1;2;3;4;5;6;7;8;9;10;11;12;13;14;15;16;17;18;19;20;21;22

After running it -- I saw:

ops$tkyte@8i> select * from t1;

    FIELD1     FIELD2     FIELD4
---------- ---------- ----------
         1          2          4

ops$tkyte@8i> c/1/2/
  1* select * from t2
ops$tkyte@8i> /

    FIELD3     FIELD6
---------- ----------
         3          6

ops$tkyte@8i> c/2/3
  1* select * from t3
ops$tkyte@8i> /

    FIELD5
----------
         5

ops$tkyte@8i>
Review & Followup

      Rating: 3
How to do
      this  December 11, 2002
Reviewer:  pawan
       from USA
I am new to Oracle and have learnt a lot from this site. I have a very simple
question.
Name                                      Null?    Type
----------------------------------------- -------- ----------------------------
BUSINESS_UNIT_ID_CREDIT                   NOT NULL VARCHAR2(5)
CREDIT_MGR_ID                                      VARCHAR2(5)
CREDIT_MGR_NAME                                    VARCHAR2(50)
FI_NUMBER                                          VARCHAR2(18)
FI_NAME                                            VARCHAR2(50)
FI_CITY                                            VARCHAR2(50)
FI_STATE                                           VARCHAR2(3)
PROMOTION_CD                              NOT NULL VARCHAR2(18)
PROMOTION_DESC                            NOT NULL VARCHAR2(20)
CHECK_NO                                           VARCHAR2(14)
CHECK_DT                                           DATE
LOC_ID                                             VARCHAR2(5)
PROMOTION_DT                              NOT NULL DATE
PROMOTION_CATEGORY                        NOT NULL VARCHAR2(20)
PROMOTION_RSN_CD                          NOT NULL VARCHAR2(3)
PROMOTION_RSN_DESC                        NOT NULL VARCHAR2(25)
PROMOTION_ID                              NOT NULL VARCHAR2(9)
REFERENCE_ID                                       VARCHAR2(14)
DYS_PAST_DUE                              NOT NULL NUMBER(6)
PROMOTION_BALANCE                         NOT NULL NUMBER(15,2)
AGED_AMT_1_5                                       NUMBER(15,2)
AGED_AMT_6_30                                      NUMBER(15,2)
AGED_AMT_31_60                                     NUMBER(15,2)
AGED_AMT_61_90                                     NUMBER(15,2)
AGED_AMT_91_180                                    NUMBER(15,2)
AGED_AMT_181_365                                   NUMBER(15,2)
AGED_AMT_OVER_365                                  NUMBER(15,2)
PROMOTION_STATUS                          NOT NULL VARCHAR2(2)
PROMOTION_LST_UPD_DT                      NOT NULL DATE

I have table called PROMOTIONS which has the structure as shown above. I need to
load data into the table which is available in a flat (data.csv) file. The data
is pipe delimited. I need to skip the first row which has the date and then for
each record in the flat file I need to skip the first field (which is always -
ORACLE GROUP) and then start loading from the second field onwards. The last two
columns in the table viz. PROMOTION_STATUS and PROMOTION_LST_UPD_DT are to be
populated with ‘O’ and PROMOTION_LST_UPD_DT is to be sysdate. Sample data file
(say data.csv) is as under:
12/02/02|         |     |   |                         |                  |
                     |               |   |                  |
|              |        |     |        |                    |   |
         |         |              |      |               |               |
        |               |               |               |               |
       |
ORACLE GROUP|01100|   |                         |UNIDENTIFIED      |
               |               |   |601               |DIVISION PROMOTION
|              |        |     |05/31/02|PROMOTIONAL RELATED |034|PROMOTIONAL
CREDIT       |P80165280|102501        |   185|         -49.00|               |
            |               |               |               |         -49.00|
           |
ORACLE GROUP|01100|   |                         |UNIDENTIFIED      |
               |               |   |651               |DIVISION CREDIT MEMO
|              |        |     |12/28/01|RETURNS             |202|UNKNOWN - NF
         |96204325 |              |   340|       -1070.19|               |
        |               |               |               |       -1070.19|
       |
ORACLE GROUP|01100|   |                         |UNIDENTIFIED      |
               |               |   |651               |DIVISION CREDIT MEMO
|              |        |     |01/09/02|RETURNS             |202|UNKNOWN - NF
         |96213909 |              |   328|        -551.29|               |
        |               |               |               |        -551.29|
       |
ORACLE GROUP|01100|   |                         |UNIDENTIFIED      |
               |               |   |651               |DIVISION CREDIT MEMO
|              |        |     |02/05/02|RETURNS             |202|UNKNOWN - NF
         |96239262 |              |   301|        -146.48|               |
        |               |               |               |        -146.48|
       |
ORACLE GROUP|01100|   |                         |UNIDENTIFIED      |
               |               |   |651               |DIVISION CREDIT MEMO
|              |        |     |02/13/02|RETURNS             |202|UNKNOWN - NF
         |96247863 |              |   293|         -15.90|               |
        |               |               |               |         -15.90|
       |
ORACLE GROUP|01100|   |                         |UNIDENTIFIED      |
               |               |   |651               |DIVISION CREDIT MEMO
|              |        |     |02/20/02|RETURNS             |202|UNKNOWN - NF
         |96251086 |              |   286|       -1261.51|               |
        |               |               |               |       -1261.51|
       |
ORACLE GROUP|01100|   |                         |UNIDENTIFIED      |
               |               |   |651               |DIVISION CREDIT MEMO
|              |        |     |02/20/02|RETURNS             |202|UNKNOWN - NF
         |96251087 |              |   286|       -2923.89|               |
        |               |               |               |       -2923.89|
       |
ORACLE GROUP|01100|   |                         |UNIDENTIFIED      |
               |               |   |651               |DIVISION CREDIT MEMO
|              |        |     |02/21/02|RETURNS             |202|UNKNOWN - NF
         |96253112 |              |   285|         -22.91|               |
        |               |               |               |         -22.91|
       |
ORACLE GROUP|01100|   |                         |UNIDENTIFIED      |
               |               |   |651               |DIVISION CREDIT MEMO
|              |        |     |03/19/02|COLLECTION RELATED  |241|UNKNOWN - NF
         |96280401 |N/A/AB068     |   259|         -76.32|               |
        |               |               |               |         -76.32|
       |

I am new to Oracle and will be obliged if you can show me a SQL*Loader script to
do it. I don’t have manuals handy and someone guided me top this site.
Thanks

          Followup:

Hey, that "i don't have the manuals handy" doesn't cut it in 2002!!!

If you can get here, you have the manuals totally handy.

http://otn.oracle.com/
 -> DOCUMENTATION!!!  It is all there.

Anyway, to answer your question -- to skip the first record, simply use skip=

sqlldr userid=x/y control=yourfile.ctl skip=1

In order to skip that first field, use FILLER, see:

http://asktom.oracle.com/~tkyte/SkipCols/index.html

      Rating: 4
Here is
        December 11, 2002
Reviewer:  Pawan
       from USA
Thanks Tom. Here is what I have coded. The BUSINESS_NAME field is not there in
table and I have just used as filler to skip the first field in the flat file.
The final table is not yet created and so I cannot test it.
LOAD DATA
INFILE /prod/data/input/di.csv
TRUNCATE INTO TABLE PRD.PROMOTIONS
FIELDS TERMINATED BY '|'
(
BUSINESS_NAME FILLER,
BUSINESS_UNIT_ID_CREDIT,
CREDIT_MGR_ID,
CREDIT_MGR_NAME,
FI_NUMBER,
FI_NAME,
FI_CITY,
FI_STATE,
PROMOTION_CD,
PROMOTION_DESC,
CHECK_NO,
CHECK_DT,
LOC_ID,
PROMOTION_DT,
PROMOTION_CATEGORY,
PROMOTION_RSN_CD,
PROMOTION_RSN_DESC,
PROMOTION_ID,
REFERENCE_ID,
DYS_PAST_DUE,
PROMOTION_BALANCE,
AGED_AMT_1_5,
AGED_AMT_6_30,
AGED_AMT_31_60,
AGED_AMT_61_90,
AGED_AMT_91_180,
AGED_AMT_181_365,
AGED_AMT_OVER_365,
PROMOTION_STATUS,
PROMOTION_LST_UPD_DT
)

I will use the skip=1 in the command line while invoking SQLLOADER.

      Rating: 3
I can't
      load  December 11, 2002
Reviewer:  Pawan
       from USA
Sorry to be a pain Tom. I wrote the contol file and tried running the sqlloader
from the command prompt but it fails.
Here is my control file
----------------------------------------------------
LOAD DATA
INFILE "/home/ /di.csv"
INTO TABLE SPOT.PROMOTIONS
FIELDS TERMINATED BY '|'
TRAILING NULLCOLS
(
BUSINESS_NAME FILLER,
BUSINESS_UNIT_ID_CREDIT,
CREDIT_MGR_ID,
CREDIT_MGR_NAME,
FI_NUMBER,
FI_NAME,
FI_CITY,
FI_STATE,
PROMOTION_CD,
PROMOTION_DESC,
CHECK_NO,
CHECK_DT DATE NULLIF CHECK_DT=BLANKS,
LOC_ID,
PROMOTION_DT "MM/DD/YYYY",
PROMOTION_CATEGORY,
PROMOTION_RSN_CD,
PROMOTION_RSN_DESC,
PROMOTION_ID,
REFERENCE_ID,
DYS_PAST_DUE,
PROMOTION_BALANCE,
AGED_AMT_1_5,
AGED_AMT_6_30,
AGED_AMT_31_60,
AGED_AMT_61_90,
AGED_AMT_91_180,
AGED_AMT_181_365,
AGED_AMT_OVER_365,
PROMOTION_STATUS,  ----„³ I need to enter a Default Value ¡¥O¡¦
PROMOTION_LST_UPD_DT sysdate
)
---------------------------------------------------
The last two fields in the table are not in the flat file. I need to have
default values for those two - one will have SYSDATE and the other
PROMOTION_STATUS will have 'O'.

When I run using
sqlldr userid=u/p control=promo.ctl skip=1
 Nothing gets loaded and I get the following in the log file
Record 1: Rejected - Error on table PROMOTIONS, column PROMOTION_DT.
ORA-00984: column not allowed here

Please help me

          Followup:

just to prove the manuals are handy:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76955/ch05.htm#4959
read about "generating data" with sqlldr - but, that isn't your error.

PROMOTION_DT "MM/DD/YYYY",

is clearly the error (not the last two columns).

PROMOTION_DT date "MM/DD/YYYY",

      Rating:
      4
Thanks  December 12, 2002
Reviewer:
      pawan  from USA
Thanks Tom. I poured over the manual online yesterday and could solve the
problem.

      Rating: 4
Another
      SQLLoader Error  February 19, 2003
Reviewer:
      Steve  from Montreal
Hi Tom,

We are having an SQL Loader error. I'm not sure if I
should add this to this thread or not (what do you prefer?)

We occasionally get the following error while SQL-Loading
into a partitioned table:

      Record 712: Rejected - Error on table XXXX.
      ORA-06540: PL/SQL: compilation error
      ORA-06553: PLS-907: cannot load library unit DWADM.PP_CALLS (referenced by
DWADM.LONG_DISTANCE_IND)
      ORA-06512: at "DDSADM.MO_AFTER_INS_ROW_TR", line 11
      ORA-04088: error during execution of trigger 'DDSADM.MO_AFTER_INS_ROW_TR'

      Table XXXX:
        1859 Rows successfully loaded.
        1 Row not loaded due to data errors.
        0 Rows not loaded because all WHEN clauses were failed.
        0 Rows not loaded because all fields were null.

      Space allocated for bind array:                  64428 bytes(42 rows)
      Space allocated for memory besides bind array:        0 bytes

      Total logical records skipped:          0
      Total logical records read:          1860
      Total logical records rejected:         1
      Total logical records discarded:        0

I've checked metalink and your site but struck out. Here's
some additional info/notes.

  1. Solaris 8, Oracle 8.1.7, 64 bit.
  2. The target table is large, partitioned, and has a trigger.
  3. There are not database links involved (single instance)
  4. there are 2 schemas: ddsadm and dwadm
  5. we are loading into ddsadm.

One other thing (not sure if this is related, relavent, or helpful). We often
see views (not sure about functions) become invalid and we don't know why. Could
views become invalid due to "rolling" partition deletion/creation?

          Followup:

views can become invalid due to index maintenance which occurs with rolling
windows alot.  The "updatable" or not setting must be re-evalauated for a view.

Does the affected procedure make use of these views?  could it be that a
combination of

o loading
o triggering
o rolling
o index invalidation/rebuilding

is happening at the same time?

      Rating:
      5
Bingo!  February 19, 2003
Reviewer:
      Steve  from Montreal
You got it. All (without exception) are happening right around 8 PM (and almost
exactly at 20:00). This is when our cronjob starts. This cron job:

    o exports the oldest partition
    o truncates the oldest partition
    o moves the oldest partition (now truncated) to another tablespace
    o rebuilds the indexes on that truncated partition.

As far as I can tell, this is what is happening:

SQL*Loader -> into Table T
           -> Trigger on T uses fn1, fn2, and fn3.

the trigger only uses functions but does not access the views. Does the same
apply to functions? i.e. Can functions become invalid due to the rolling window
activity (which would explain the SQL*Loader error we are getting)

          Followup:

are the functions in a package?
are their other functions in the package dependant on the views?
do the views actually go invalid?
do the procedures access the partitioned table itself? (if so -- that is
definitely the cause, they'll go invalid at that point since Oracle remembers a
procedure is dependent on a TABLE, not a partition of a table -- so partition
operations invalidate dependent procedures -- they do not know if coded:

    select ... from partitioned_table

or
    select .... from partitioned_table partition(p1)

so they invalidate to check it out.

      Rating: 5
Is this
      it?...  February 20, 2003
Reviewer:  Steve
       from Montreal
Note: Functions are not in a package.

Is this the reason...

1. The table with the rolling window is:   PP_CALLS.
2. One of the functions in question is:    LONG_DISTANCE_IND

3. The error contains:
     ORA-06553: PLS-907: cannot load library unit DWADM.PP_CALLS (referenced by
     DWADM.LONG_DISTANCE_IND)

4. The trigger on the SQL*Loader table calls the function:  LONG_DISTANCE_IND

5. The function contains the following lines:

   FUNCTION LONG_DISTANCE_IND
    (col1 PP_CALLS.CT_CT_ID%TYPE
     col2 PP_CALLS.SERVED_MSISDN%TYPE...
    )
    RETURN PP_CALLS.LONG_DISTANCE_ID%TYPE
    IS
    -- PL/SQL Specification
    LONG_DISTANCE_ID PP_CALLS.LONG_DISTANCE_ID%TYPE;
    ...

i.e. a bunch of references to types in the rolling window table.

Do you think that's the cause?

Thanks a lot Tom.

Steve

          Followup:

yes, consider:

ops$tkyte@ORA817DEV> CREATE TABLE t
  2  (
  3    year_month  varchar(4)
  4  )
  5  PARTITION BY RANGE (year_month) (
  6    PARTITION part_jan_2000 VALUES LESS THAN ('0002') ,
  7    PARTITION part_feb_2000 VALUES LESS THAN ('0003') ,
  8    PARTITION junk VALUES LESS THAN (MAXVALUE)
  9  )
 10  ;

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace function f return number
  2  as
  3          data t.year_month%type;
  4  begin
  5          null;
  6  end;
  7  /

Function created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select status from user_objects where object_name = 'F';

STATUS
-------
VALID

ops$tkyte@ORA817DEV> alter table t drop partition part_jan_2000;

Table altered.

ops$tkyte@ORA817DEV> select status from user_objects where object_name = 'F';

STATUS
-------
INVALID

the partition operation invalidates the function -- if you do this at the wrong
time and try to revalidate the function -- you could get "wedged" as you are
here.  The function needs to DDL lock the table -- the table cannot be because
you are rolling the windows -- function cannot recompile itself -- bamm.

      Rating:
      5
Thanks!  February 20, 2003
Reviewer:
      steve  from Montreal
As always, a big big thanks Tom!

      Rating: 4
sqloader cobol
      file on unix  September 11, 2003
Reviewer:
      lura
Hi Tom,
questions about sqlloader (To load a .dat file which was generated by a Cobol
extract program into a
table(oracle 9i, platform is unix).

1,I have a record in the file like abc123.. , I can not load it correctly(the
field located is not right), but I create a new file using vi and copy the
record to the new one, it will be successful. What happend here ?

2.For cobole type PIC 9(n) comp, x(n) etc,  How can use the
position to locate them ?

Thank you very much!

          Followup:

1) no clue what you are saying here.

i've no idea why you "can not load it correctly"
no idea how vi fixed it.

2) you know the start field position and the length right?  that is what you'll
need in order to use position which just needs the start and length or start and
stop column numbers.

September 11, 2003 Muhammad -- Thanks for the question regarding
      "Installation/Upgradation", version 8.0.5

      You Asked
Dear Tom,
I am using Oracle 8.0.5 at home on a stand alone PC. Oracle
Corporation has sent me complimentary copy of Oracle 8i ver
8.1.5  I am interested to install 8i. My questions are:

No.1  What is actual difference in Oracle8 ver8.0.5 and 8i ver
8.1.5      by the point of view of installation.
No.2  Is 8i is upgraded version of Oracle 8.

No.3  Can I simply upgrade Oracle8.0.5 to Oracle 8i .

I am working on WindowsNT Server4.

Regards and looking forward for an early response.

Ajmal

      and we said...
In the installation -- it'll look very different.  8i uses a
java based installer, the old win32 installer from 8.0 is gone.
the installation interface will look different but is pretty
much the same scenario.

Oracle8i release 8.1 is the next release of Oracle after 8.0.
To see the features of 8.1.5 over 8.0, see:

http://technet.oracle.com/doc/server.815/a68020/toc.htm
it covers them all.

Yes, you can upgrade 8.0.5 to 8i.  See

http://technet.oracle.com/doc/server.815/a67774/toc.htm
for the procedure.

Review &
      Followup

      Rating: 3
SYSCONFIGTAB
      file Parameter Settings and Users   March 27, 2003

Reviewer:  Reader  from India
Tom,

I did not understand the Additional SYSCONFIGTAB file Parameter Settings . its
there in this link ...
http://download-west.oracle.com/docs/html/A96167_01/pre.htm#CHDCEHBG
can you please tell me exactly which parameter i should set and how to calculate
it. I am going to install the Oracle 9i db in a new server (OS Solaris 2.8).

i dont find the file /etc/sysconfigtab in my server.!!! anyother file name then?

Also.

I am going to have more than one instance in the server.Lets have 3 instances..
in that case.. how many users need to be assigned to dba group, how many to oper
group and how many to oinstall.

Thanks ... for all your valuable time for us.

          Followup:

do you have a UNIX SA -- they would know what to do.

/etc/sysconfigtab - as the docs say -- would be tru64.  are you tru64? if not,
use the right file for that OS and as it varies by OS, you would use the guide
for that OS.  Best performed by a system admin as putting in bad stuff could
render your system inoperable.

see also

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:454420165038

      Rating: 4
Its for
      Solaris,..  March 27, 2003
Reviewer:  Reader

Its for Solaris, and NOT tru64.

Sorry for my ignorance ...if i have not insatalled my db how will i know the
value of ...
SQL> select sum(value) totalsz from v$sga

??? to calculate vm:  gh_chunks = <sgavalue + 2>

Please let me know the answer for my 2nd question , about users and group. Do
you advice me to have 3 oracle users to dba group and one for oinstall and 3 for
oper - sounds absurd. - I am learning from you.... TOM,

Thanks ...

          Followup:

guess -- how big are you going to set your SGA to?  100meg, 200meg?  1gig -- you
should easily be able to "ball park" that estimate.

as for how many users -- depends on your needs.  do you need the 3 instances
(which IMHO is 2 more then you want to have) to be separately managed by 3
people.  if not, one user is sufficient.

      Rating: 4
Thankyou
      Tom  March 27, 2003
Reviewer:  Reader

      Rating: 3
Cleint
      installation  September 11, 2003
Reviewer:
      Siva  from INDIA
Hello TOM,
      We have project which is running on SQL Server. We are started to convert
this project.

We have prepared setup for SQL Server using install scheild software. I don't
know how to prepare the installscheild setup to install it client machine for
Oracle.

now if the cleint machine has two SQL Server files (sqlres.rll and sqlsrv32.dll)
you can start and work our project.

Is there any files in oracle like in SQL Server?. I know we need tnsnames.ora
and sqlnet.ora. Which dll files used to connect to the cleint machine. I don't
need other tools in cleint machine.

Regards,
Siva

          Followup:

you use the Oracle universal installer to install the oracle client software.

September 11, 2003 Ivan -- Thanks for the question regarding
      "generate test data automagically", version 8.1.6

      You Asked
 To me is often send only description of tables, I need to create and some
business logic to put in stored packages. But no flat files to load test data.
That is the reason for my thoughts of dynamic procedure, which should (depended
of column type in tables) insert number of test records.

- And I think about using pl/sql table for information in:
select column_id,column_name, data_type
from   user_tab_columns
where table_name = upper(TableName)

to put it in something like:

create or replace procedure
create_test(TableName, nRecords)
declare
ExcStmt   VARCHAR2(2000);
TestChar  VARCHAR2;
TestDate  DATE;
TestNum   NUMBER;
i         number;
…
Begin
TestChar := 'A';
TestDate := to_DATE('01012000', 'DDMMYYYY');
TestNum  := 0;

for i in 1 .. nRecords
loop
   for j in 1 .. max(column_id)
   loop
      -- preparing data for insert record
      if data_type(colomn_id) = 'NUMBER' then
          -- somehow
          colomn_name(column_id) := TestNum + 1;
      elsif data_type(colomn_id) = 'DATE' then
          colomn_name(column_id) := TestDate + 1;
      else
          colomn_name(column_id) := TestChar;
      end if;
      ExcStmt := 'INSERT INTO '||TableName|| ... --record
      EXECUTE IMMEDIATE ExcStmt;
   end loop;
end loop;
…

      and we said...
This'll work then.  Instead of dbms_random, you could use your own values, but
this is as good as anything else:

scott@TKYTE9I.US.ORACLE.COM> create or replace procedure clone( p_tname in
varchar2, p_records in number )
  2  authid current_user
  3  as
  4      l_insert long;
  5      l_rows   number default 0;
  6  begin
  7
  8     execute immediate 'create table clone_' || p_tname ||
  9                       ' as select * from ' || p_tname ||
 10                       ' where 1=0';
 11
 12      l_insert := 'insert into clone_' || p_tname ||
 13                  ' select ';
 14
 15      for x in ( select data_type, data_length,
 16                  rpad( '9',data_precision,'9')/power(10,data_scale) maxval
 17                   from user_tab_columns
 18                  where table_name = 'CLONE_' || upper(p_tname)
 19                  order by column_id )
 20      loop
 21          if ( x.data_type in ('NUMBER', 'FLOAT' ))
 22          then
 23              l_insert := l_insert || 'dbms_random.value(1,' || x.maxval ||
                                                                           '),';
 24          elsif ( x.data_type = 'DATE' )
 25          then
 26              l_insert := l_insert ||
                       'sysdate+dbms_random.value+dbms_random.value(1,1000),';
 27          else
 28              l_insert := l_insert || 'dbms_random.string(''A'',' ||
                                            x.data_length || '),';
 29          end if;
 30      end loop;
 31      l_insert := rtrim(l_insert,',') ||
                       ' from all_objects where rownum <= :n';
 32
 33      loop
 34          execute immediate l_insert using p_records - l_rows;
 35          l_rows := l_rows + sql%rowcount;
 36          exit when ( l_rows >= p_records );
 37      end loop;
 38  end;
 39  /

Procedure created.

scott@TKYTE9I.US.ORACLE.COM> exec clone( 'emp', 5 );

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.01
scott@TKYTE9I.US.ORACLE.COM> select * from clone_emp;

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
------
      4923 BrCyhfdHaQ mGSqkWMvy       8302 07-SEP-02    2765.67   89231.32
27
       323 ImuBZCYDrt TdjoflvYE       9613 11-AUG-03   82158.44   34478.25
89
      8773 jnTPtKkchC KzUezmTTL       8432 20-MAY-04    29909.7   23860.67
24
      7374 aETDfeptSS ObVBgtnAP       9033 28-MAR-03   34012.77   92188.64
45
      4530 MKGvauUMOQ GXwcRjGUn         51 07-AUG-04    84510.9   93629.66
79

Review &
      Followup

      Rating: 5
Just to
      add...  December 07, 2001
Reviewer:  Connor
       from Brighton, UK
Good ol' mod, trunc and rownum are great for creating data - all you need is a
nice table to source it from (for example sys.source$).

insert into mytable
select rownum, mod(rownum,100), trunc(rownum/10)
from sys.source$
where rownum < ...

So you can tewak the frequency and distribution of data - which helps when
benchmarking optimizer decisions.

          Followup:

definitely true -- there you need participate in the cloning more closely --
here they seem to want to just say "copy the structure and fill er up with
regular please".

I myself use the mod/rownum trick..

      Rating: 5
100 % mach with
      what I need  December 10, 2001
Reviewer:  Ivan
      Korac  from Ulm, Germany
Even more, You demonstrate to us, very welcome,  usage of dbms_random
My compliments

      Rating: 5
more
      complcted  February 13, 2002
Reviewer:  Ivan
       from Ulm, Germany
Is it possible that when generating data values, foreign key constraints can be
respected?  i.e for emp, because of foreign key from dept, value for dept field
should be randomly selected from the domain of dept.

          Followup:

Sure, just load up the primary key values of the DEPT table into a plsql table
type and use dbms_random to index randomly into the array:

scott@ORA817DEV.US.ORACLE.COM> declare
  2          type numArray is table of number index by binary_integer;
  3
  4          l_deptnos  numArray;
  5  begin
  6          select deptno bulk collect into l_deptnos from dept;
  7
  8          for i in 1 .. 5
  9          loop
 10               dbms_output.put_line( l_deptnos( dbms_random.value( 1,
                                                          l_deptnos.count ) ) );
 11          end loop;
 12  end;
 13  /
40
10
40
20
30

PL/SQL procedure successfully completed.

      Rating: 3
problems in
      clone  March 18, 2002
Reviewer:  Enrique
      Ortiz  from Barcelona, Spain
Hi I think the procedure clone is great but i found a
problem

If you define a column as NUMBER in the source table data_precition and
data_scale ( line 15 ) returns Null
and the line appended is 'dbms_random.value(1,)'

          Followup:

Yes, change

rpad( '9',data_precision,'9')/power(10,data_scale) maxval
to
nvl(rpad( '9',data_precision,'9')/power(10,data_scale),9999999999) maxval

in the select and that'll fix that up.

      Rating: 2
Test
      Data  March 19, 2002
Reviewer:  Vijay.S
       from India
GENERATE TEST DATA AUTOMATICALLY...

This will not handle if the table has got an referencing columns (F.Keys)..

Rgds

      Rating: 4
Flexibility of
      insert into select approach..,  March 20, 2003

Reviewer:  Matt  from Australia
I've been using the:

insert into mytable
select rownum, mod(rownum,100), trunc(rownum/10)
from sys.source$
where rownum < ...

approach as well. It seems to work well for simple data setups.

I want to generate data that looks like this:

ID  ID_TYPE VERSION
1   1      1
1   1      2
1   1      3
.   .      .
.   .      .
.   .      .
1   1      10
1   2      1
1   2      2
1   2      3
.   .      .
.   .      .
.   .      .
1   2      10
1   3      1
1   3      2
1   3      3
.   .      .
.   .      .
.   .      .
1   3      10
2   1      1
2   1      2
2   1      3
.   .      .
.   .      .
.   .      .
2   1      10

That is, ID is a number, every ID has three ID_TYPE (1,2,3) and each ID,ID_TYPE
combo has ten VERSIONS (1,2,...10)

How can I do this in one SQL statement? Is it possible?

I've come up with a select that looks like this:

select a.r
     , b.s
     , c.t
from (select rownum r from all_objects where rownum < 11 ) a
    ,(select rownum s from all_objects where rownum <= 4 ) b
    ,(select rownum t from all_objects where rownum <= 11 ) c
where rownum < 1000
order by 1,2,3

Which I think produces what I want:

r       s       t

1    1    1
1    1    2
1    2    1
1    2    2
1    3    1
2    1    1
2    1    2
2    2    1
2    3    1

I can then MOD, TRUNC, TO_DATE() +, these values to generate the real data set.

Is there a better way of doing this, of have I just answered my own question?

          Followup:

your query will take

1..10 "X" 1..4 "X" 1..11

which generates 440 rows -- and comes up with every combination of them...

I do it all of the time.  Another technique (some slow but effective) is:

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

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

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

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

it does RANDOM data -- not within bounds like you have.  What you did is right
if you want all of the rows where r is between 1 and 10 combined with S from
1..4 and so on.

      Rating: 5
Thanks for
      that.  March 20, 2003
Reviewer:  Matt
       from Australia
Nice procedure.

I have read about using application_info as you do ( I assume that this
populates v$long_ops )

"dbms_application_info.set_module( l_rows || ' rows of ' || p_records, '' "

I guess that this just has the same overhead as a simple insert?

Is this the only mechanism to monitor the progress of transactions like these?
ie: It seems to me that you would need to consider this approach when designing
the routine rather than being ableto retro-fit this monitoring in existing code
(which I may have to do soon...).

          Followup:

it has so little overhead -- infinitely less then a simple insert.  It modifies
memory.. It doesn't touch disk, generate undo, create redo.

...
It seems to me that you would need to consider this approach when designing
the routine rather than being ableto retro-fit this monitoring in existing code
.....

Bravo -- well said!  I like that statement so much.  Yes, you actually need to
consider things like this when designing your application.  I'm very very big
into this -- instrumenting your code to death with tons of stuff like this (just
like the database did for us) so you can audit performance, pinpoint issues --
all at the drop of a hat.

How many times I hear "It is slow, why?"  -- well where is it slow -- "I don't
know, somewhere in the middle of this really big process"  -- ok, turn your
tracing and timings and such so we can see where -- "huh?  wadda mean".....

      Rating: 4
Test data
      genberation for clustered table.  March 25, 2003

Reviewer:  Matt  from Australia
Is there a speedy way to get bulk data into an index clustered table?

ict is the index clustered table, clustered on C1, C2, C3, C4

insert /*+ append */ into ict
select mrs.C1
     , mrs.C2
     , mrs.C3
     , mrs.C4
     , mrs.C5
     , 'A' C6
     , 999.99 C7
     , a.r interval
     , 1 C8
     , 1 C9
from puser.my_driver mrs
   , (select rownum r from all_objects where rownum <= 96) a

I have 360,000 rows in puser.my_driver. These took 1.5 minutes to load. The
statement above ran for over an hour before I decided to investigate.

Thanks

          Followup:

so you were loading 34,560,000 rows into this cluster....

no idea what the data looked like (is that hundreds of meg of data or 10s of gig
of data)

no idea what the cluster key looks like -- how the cluster was sized...

makes it hard.

      Rating: 3
Schoolboy
      Error?  March 27, 2003
Reviewer:  Matt
       from Australia
OK, I think my data load is roughly 600Mb (average row length of 57bytes,
loading 120,000*96 rows - 120,000 comes from the missing predicate that I failed
to cut and paste that restricts the data load).

That tablespace that this data was going into was defined thus:

CREATE TABLESPACE TEST_PARTITION_001
DATAFILE '/db/oradata/NDEVT/test_partition_001_N9DEVT_01.dbf' SIZE 512064K
AUTOEXTEND ON NEXT 512K MAXSIZE 1000M
EXTENT MANAGEMENT LOCAL
UNIFORM SIZE 512K
SEGMENT SPACE MANAGEMENT AUTO

But the datafile was then resized to 1000M (which should really have been
1024064K)

The cluster is defined like this:

CREATE CLUSTER ICT_C
  ("C1" NUMBER(10, 0)
   , "C2" NUMBER(4, 0)
   , "C3" DATE
   , "C4" NUMBER(3, 0)
  )
 TABLESPACE "TEST_PARTITION_001"
/

CREATE INDEX ICT_C_IDX
ON CLUSTER "ICT_C"
TABLESPACE "TEST_PARTITION_001"
/

CREATE TABLE "ICT"
  ("C1" NUMBER(10, 0) NOT NULL ENABLE
   , "C2" NUMBER(4, 0)
   , "C3" DATE NOT NULL ENABLE
   , "C4" NUMBER(3, 0) NOT NULL ENABLE
   , interval NUMBER(4, 0) NOT NULL ENABLE
   , "C5" NUMBER(12, 0) NOT NULL ENABLE
   , "C6" VARCHAR2(10)
   , "C7" NUMBER(12, 2)
   , "C8" NUMBER(5, 0)
   , "C9" NUMBER(5, 0))
 CLUSTER "ICT_C"
("C1", "C2", "C3", "C4")
/

These are the only objects in this TS.

I am thinking that as I have not sized the objects they are defaulting to 512K
extents and then dynamically extending. ie: 1200 dynamic space allocations.

The data dictionary changes (and redo) and the fact that I need the index
enabled for the cluster is slowing this load down.

Do you agree?

Is there anythig that I can do (apart from understand LMS SMA and size the
objects correctly!) that could speed this data load up?

Thanks!

          Followup:

no, don't agree.  First, you cannot direct path load clusters (that append is
meaningless).  So, this is the same as "insert into t select"

the 512k extents -- not really a big deal.  It is a locally managed tablespace,
there are no data dictionary changes here -- it is bitmap updates.

I think it is at least partially due to the fact you haven't sized your cluster
(using SIZE on the create cluster) and that the initial load of data isn't
sorted by the cluster key.

Given a value for C1, C2, C3, C4 what would you expect this query:

select sum(vsize(mrs.C1 ))+
      sum(vsize(mrs.C2))+
      sum(vsize(mrs.C3))+
      sum(vsize(mrs.C4))+
      sum(vsize(mrs.C5))+
      sum(vsize('A'))+
      sum(vsize(999.99))+
      sum(vsize(a.r))+
      sum(vsize(1))+
      sum(vsize(1))
from puser.my_driver mrs
   , (select rownum r from all_objects where rownum <= 96)
where c1 = :c1 and c2 = :c2 and c3 = :c3 and c4 = :c4

Assuming that c1,c2,c3,c4 are unique in mrs -- we would get 96 rows for each
cluster key and you said 57 bytes, so that is about 6k/key.  so, if I build a
driver table:

ops$tkyte@ORA920> create table my_driver
  2  as
  3  select rownum c1, mod(rownum,9999) c2, created c3, mod(rownum,999) c4,
rownum c5
  4    from big_table
  5   where rownum <= 120000
  6  /

Table created.

ops$tkyte@ORA920> CREATE CLUSTER ICT_C
  2    ("C1" NUMBER(10, 0)
  3     , "C2" NUMBER(4, 0)
  4     , "C3" DATE
  5     , "C4" NUMBER(3, 0)
  6    )
  7   size 6192
  8   TABLESPACE "TEST_PARTITION_001"
  9  /

Cluster created.

And it is also related to the "order" of the data.  A cluster differs big time
from a HEAP table in that data has a physical location.  If you look at how
Oracle itself uses a cluster in the data dictionary -- you'll find that all of
the rows for a cluster key more or less go into the database "at the same time".
 that is when clusters work best.  During a bulk load of a cluster -- you really
want to try and get the data into the table in key order.  It is not that the
keys must be sorted -- just that all or most of the rows for a given key go in
at about the same time.  This results in the most efficient load as well as most
tightly packed cluster possible (least amount of chaining).

I modified your query to help facilitate this.  first, I lost all_objects and
just use the simple my_driver table twice.  Additionally -- I used an ordered
hint in this very special case to tell Oracle "read the MY_DRIVER table and join
the first row to the 96 rows, then read the second row from MY_DRIVER and join
to the 96 rows and so on".  So the net result was that the cluster was loaded by
key:

ops$tkyte@ORA920> delete from plan_table;

7 rows deleted.

ops$tkyte@ORA920> explain plan for
  2  insert into ict
  3  (c1,c2,c3,c4,c5,c6,c7,interval,c8,c9)
  4  select /*+ ORDERED */ mrs.C1 , mrs.C2 , mrs.C3 , mrs.C4 , mrs.C5 , 'A' C6 ,
999.99 C7 , a.r interval , 1 C8 , 1 C9
  5  from my_driver mrs
  6     , (select rownum r from my_driver where rownum <= 96) a;

Explained.

ops$tkyte@ORA920> set echo off

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
--------------------------------------------------

---------------------------------------------------------------------
| Id  | Operation             |  Name       | Rows  | Bytes | Cost  |
---------------------------------------------------------------------
|   0 | INSERT STATEMENT      |             |    11M|   461M|  6240K|
|   1 |  MERGE JOIN CARTESIAN |             |    11M|   461M|  6240K|
|   2 |   TABLE ACCESS FULL   | MY_DRIVER   |   120K|  3398K|    52 |
|   3 |   BUFFER SORT         |             |    96 |  1248 |  6240K|
|   4 |    VIEW               |             |    96 |  1248 |       |
|*  5 |     COUNT STOPKEY     |             |       |       |       |
|   6 |      TABLE ACCESS FULL| MY_DRIVER   |   120K|       |    52 |
---------------------------------------------------------------------

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

   5 - filter(ROWNUM<=96)

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
--------------------------------------------------

Note: cpu costing is off

19 rows selected.

ops$tkyte@ORA920> set timing on
ops$tkyte@ORA920> insert into ict
  2  (c1,c2,c3,c4,c5,c6,c7,interval,c8,c9)
  3  select /*+ ORDERED */ mrs.C1 , mrs.C2 , mrs.C3 , mrs.C4 , mrs.C5 , 'A' C6 ,
999.99 C7 , a.r interval , 1 C8 , 1 C9
  4  from my_driver mrs
  5     , (select rownum r from my_driver where rownum <= 96) a;

11520000 rows created.

Elapsed: 00:14:06.33

ops$tkyte@ORA920> select count(*) from user_extents where segment_name =
'ICT_C';

  COUNT(*)
----------
      1884

Now that was 14 minutes on a stock, $800 compaq desktop pc with an icky IDE
single disk drive and 512meg of RAM running Linux.  I would assume your server
would probably best that.

clusters are used to provide high performance retrievals -- they have necessary
overhead on the load in order to do that.  They are more efficient on the buffer
cache since all of the rows for a key will be on as few blocks as possible.
Don't be discouraged by a slow load time -- as the payback at query retrieve
time can far outweigh any negative effects there.  Pay me once up front and reap
the benefits on every query -- or pay me over and over with each query.

      Rating: 5
Simply
      Superb!!!!  March 28, 2003
Reviewer:
      A reader

      Rating:
      4
  March 30, 2003
Reviewer:  Matt
       from Australia
Ok, I understand why I can't direct path load clusters.

I also appreciate that the space management is not such an issue.

I haven't had chance to re-test my dummy data load with your suggested apporach,
it all seems to make sense though.

I reality the data loaded would always come in in cluster key order, I've only
seen the /*+ ORDERED hint */ in some RBO tuning. It is interesting what you say
about the data needing to go in at around the same time. In an earlier load I
was using "order by" to generate the data set (before I realised that things
were slow).

How does this approach differ from the /*+ ORDERED hint */?

Thinking about this I guess that this is more efficient: Driving the query with
my_driver to achieve this is better than a huge sort of the end result set.

I analyzed a sample of live data to get the 57 byte average row length. I also
tried your query which came out with 2592 bytes.

I assume that your query produces the bytes size of a set of 96 rows (why the
sum() though?)?

This should be roughly 57*96 = 5472 . Which doesn't add up. Nope, I see it! The
cluster key needs to be stored only  once. So, I assume that I can subtract 95
times the size required for the cluster key from 5472 and get your result. Errr,
however using your vsize() approach I calculate that each cluster key needs 14
bytes (which isn't enough to accound for the difference). Either I am mistaken,
or else there is something else going on here that I don't yet understand.

Can you shed any light?

Assuming all the above is correct I would need to size my cluster to be 2592
bytes per key, so each cluster entry has enough space for the 96 required rows.

However, I think I am slightly off track. Does the above make sense?

          Followup:

I used ORDERED in this case to have the database read the BIG table with the
cluster key and join each row in that table with the 96 rows in the other table.
 It gave me all 96 rows for a cluster key all at once.  It did not sort the
data, just processed the tables in the from clause in the ORDER i wanted.  If
you

set pagsize 100
set pause on
select ....  (the query with the ordered hint)

you'll see 96 rows with the same cluster key, then 96 rows with the next key and
so on.

The sum was to get the size of the data associated with a cluster key value -- I
had where c1 = .. c2 = ... and so on in there.  It would have summed up the
vsizes of all the of the columns associated with a cluster key.

Yes, I forgot to subtract out the cluster key in the calculation -- you are
correct.

If you load up a couple of representative rows and query:

select avg(vsize(c1)+vsize(c2)+vsize(c3)+vsize(4)+sz)
from
( select c1, c2, c3, c4, sum( vsize(c5)+vsize(c6)+ ..... vsize(cN) ) sz
    from t
   group by c1, c2, c3, c4 )

eg:

AVG(VSIZE(C1)+VSIZE(C2)+VSIZE(C3)+VSIZE(C4)+SZ)
-----------------------------------------------
                                     1447.72682

ops$tkyte@ORA920> l
  1  select avg( vsize(c1)+vsize(c2)+vsize(c3)+vsize(c4) + sz )
  2    from (
  3  select c1, c2, c3, c4,
sum(vsize(c5)+vsize(c6)+vsize(c7)+vsize(interval)+vsize(c8)+vsize(c9)) sz
  4    from (
  5  select *
  6    from (
  7  select /*+ ORDERED */ mrs.C1 , mrs.C2 , mrs.C3 , mrs.C4 , mrs.C5 , 'A' C6 ,
999.99 C7 , a.r interval , 1 C8 , 1 C9
  8    from my_driver mrs
  9    , (select rownum r from my_driver where rownum <= 96) a
 10         )
 11         )
 12   group by c1, c2, c3, c4
 13*        )
ops$tkyte@ORA920> /

AVG(VSIZE(C1)+VSIZE(C2)+VSIZE(C3)+VSIZE(C4)+SZ)
-----------------------------------------------
                                     1447.72682

for the data I generated (YMMV)...

It is less of an issue if this is read mostly data that is bulk loaded -- or if
the keys and related rows are all created more or less at the same time (eg:
when you do a CREATE TABLE -- you load up a cluster with all of the data at
once).  There, right sizing the cluster key size isn't as important since all of
the values go in at once.

If loading out of order -- it is much much more important.

      Rating:
      5
Error  May 20, 2003
Reviewer:
      A reader
SQL>  CREATE TABLE TEST( X VARCHAR2(10));

Table created.

SQL> EXEC GEN_DATA('TEST',2);
insert /*+ append */ into TEST select dbms_random.string('A',10) from
all_objects where rownum <= :N

BEGIN GEN_DATA('TEST',2); END;

*
ERROR at line 1:
ORA-00904: invalid column name
ORA-06512: at "JOHNDEV.GEN_DATA", line 33
ORA-06512: at line 1

--I added dbms_output.put_line to see the actual insert statement

The :N should be replaced by p_records, but is not happeneing. Can you please
guide me in finding out where I'm going wrong. I havent changed your routine,
except the dbms_output.put_line.

Thanks

          Followup:

no it shouldn't, :n is a bind variable.

it is most likely that dbms_random is not in your database.  did you check that
out

ops$tkyte@ORA817DEV> create or replace procedure gen_data( p_tname in varchar2,
p_records in number )
  2  authid current_user
  3  as
  4      l_insert long;
  5      l_rows   number default 0;
  6  begin
  7
  8          dbms_application_info.set_client_info( 'gen_data ' || p_tname );
  9      l_insert := 'insert /*+ append */ into ' || p_tname ||
 10                  ' select ';
 11
 12      for x in ( select data_type, data_length,
 13                  nvl(rpad(
'9',data_precision,'9')/power(10,data_scale),9999999999) maxval
 14                   from user_tab_columns
 15                  where table_name = upper(p_tname)
 16                  order by column_id )
 17      loop
 18          if ( x.data_type in ('NUMBER', 'FLOAT' ))
 19          then
 20              l_insert := l_insert || 'dbms_random.value(1,' || x.maxval ||
'),';
 21          elsif ( x.data_type = 'DATE' )
 22          then
 23              l_insert := l_insert ||
 24                    'sysdate+dbms_random.value+dbms_random.value(1,1000),';
 25          else
 26              l_insert := l_insert || 'dbms_random.string(''A'',' ||
 27                                         x.data_length || '),';
 28          end if;
 29      end loop;
 30      l_insert := rtrim(l_insert,',') ||
 31                    ' from all_objects where rownum <= :n';
 32
 33          dbms_output.put_line( l_insert );
 34      loop
 35          execute immediate l_insert using p_records - l_rows;
 36          l_rows := l_rows + sql%rowcount;
 37                  commit;
 38                  dbms_application_info.set_module( l_rows || ' rows of ' ||
p_records, '' );
 39          exit when ( l_rows >= p_records );
 40      end loop;
 41  end;
 42  /

Procedure created.

ops$tkyte@ORA817DEV> drop table test;

Table dropped.

ops$tkyte@ORA817DEV> create table test( x varchar2(10) );

Table created.

ops$tkyte@ORA817DEV> exec gen_data('TEST', 2 )
insert /*+ append */ into TEST select dbms_random.string('A',10) from
all_objects where rownum <= :n

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> select * from test;

X
----------
\JlLWWKogS
IUnoXAtHOR

ops$tkyte@ORA817DEV>

      Rating:
      5
Bingo  May 21, 2003
Reviewer:
      A reader
Yes you are right dbms_random was not installed

Now when I'm trying to create the dbms_random package, I ran into the following
question.

Why should I run it logged in as sys, can I do it logged in as any other user?
and once it is created how should I grant all the privileges to everyone , and
the special admin option to the dba user.

Usually how does it happen , sys grants all the privs with admin option to dba
user, who in turn grants it to others?

Thanks

          Followup:

you have to install database packages as SYS or SYSDBA (or internal)

it is just the rules of the game.

      Rating: 4
generating data
      , joining randomly  September 06, 2003
Reviewer:
      Asim Naveed  from Pakistan
Hi,

I have a table locations whose columns are

Country
Region
City
loc_id

I have to generate data for testing. There
will 20 country, each country will have 4 regions
and each region have 8 cities.

I used the SQL.

INSERT INTO locations (country, region, city, loc_id)
SELECT country, region, city, rownum FROM
(
SELECT 'Country'||rownum country
FROM big_table Where rownum < 21
),
(
SELECT 'Region'||rownum region
FROM big_table Where rownum < 5
),
(
SELECT 'City'||rownum city
FROM big_table Where rownum < 9
)

It works fine. But now I need to assign random
no. of cities to each region. Currently
its assigning 8 cities to each region. What I want
is, Each region will have a random no. of cities
between 1 to 8. for e.g region1 has 2 cities,
region 2 has 5 cities and so on.
Can the last inline view's rows be joind to the others
randomly?

Is there a way to do this in ONLY SQL.

Thanks

          Followup:

ops$tkyte@ORA920> INSERT INTO locations (country, region, city, loc_id)
  2  select country, region, city,  loc_id
  3  from (
  4  SELECT country, region, city, rownum loc_id, rr, dbms_random.value(1,9)
rand FROM
  5  (
  6  SELECT 'Country'||rownum country
  7  FROM big_table Where rownum < 21
  8  ),
  9  (
 10  SELECT 'Region'||rownum region
 11  FROM big_table Where rownum < 5
 12  ),
 13  (
 14  SELECT 'City'||rownum city, rownum RR
 15  FROM big_table Where rownum < 9
 16  )
 17  )
 18  where rr <= rand;

347 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select min(cnt), max(cnt), avg(cnt)
  2    from (
  3  select country, region, count(*) cnt
  4    from locations
  5   group by country, region
  6         );

  MIN(CNT)   MAX(CNT)   AVG(CNT)
---------- ---------- ----------
         2          7     4.3375

      Rating:
      4
great  September 06, 2003
Reviewer:
      Asim Naveed  from Pakistan
Thank you very much, Although it solved my problem
but i need something additional, and that is the
cities should be assigned consecutive numbers.

If a region has 3 cities, they should be numbered as
city1, city2, city3. It cannot be city2, city4, city7.

Is this possible ?

Thanks

          Followup:

ops$tkyte@ORA920> INSERT INTO locations (country, region, city, loc_id)
  2  select country, region, city,  loc_id
  3  from (
  4  SELECT country, region, city, rownum loc_id, rr, dbms_random.value(1,9)
rand FROM
  5  (
  6  SELECT 'Country'||rownum country
  7  FROM big_table Where rownum < 21
  8  ),
  9  (
 10  SELECT 'Region'||rownum region
 11  FROM big_table Where rownum < 5
 12  ),
 13  (
 14  SELECT 'City'||rownum city, rownum RR
 15  FROM big_table Where rownum < 9
 16  )
 17  )
 18  where rr <= rand;

look at the query

where rr <= rand

rr = rownum from "city"
rr <= threshold

we are getting the first N cities, not a random N, the FIRST n

you already have it!

      Rating: 3
its not
      assigning consecutive city no.s  September 07, 2003

Reviewer:  Asim Naveed  from Pakistan
I have executed following SQL on my computer, i
added an ORDER BY in the end to show the problem to you.

select country, region, city,  loc_id
  from (
  SELECT country, region, city, rownum loc_id, rr, dbms_random.value(1,9)
rand FROM
   (
   SELECT 'Country'||rownum country
   FROM emp, emp, emp Where rownum < 21
   ),
   (
   SELECT 'Region'||rownum region
   FROM emp, emp, emp Where rownum < 5
   ),
   (
   SELECT 'City'||rownum city, rownum RR
   FROM emp, emp, emp Where rownum < 9
   )
   )
   where rr <= rand
order by 1, 2, 3

The results  at this time on my computer was this.
(Actually there were many rows, I am showing you only
first few rows)

COUNTRY        REGION       CITY       LOC_ID
---------------------------------------------------
Country1       Region1      City1      1
Country1       Region1      City2      81
Country1       Region1      City3      161
Country1       Region1      City5      321
Country1       Region1      City6      401
Country1       Region2      City1      21
Country1       Region2      City2      101
Country1       Region2      City3      181
Country1       Region2      City5      341
Country1       Region2      City7      501
Country1       Region3      City1      41
Country1       Region3      City2      121
Country1       Region3      City4      281
Country1       Region3      City5      361
Country1       Region4      City1      61
Country1       Region4      City2      141
Country1       Region4      City3      221
Country1       Region4      City4      301
Country10      Region1      City1      10
Country10      Region1      City2      90
Country10      Region1      City3      170
Country10      Region1      City4      250
Country10      Region1      City5      330
Country10      Region1      City6      410
Country10      Region2      City1      30
Country10      Region2      City2      110
Country10      Region2      City3      190
Country10      Region2      City8      590

Look at the cities of country1 , region1

Country1       Region1      City1      1
Country1       Region1      City2      81
Country1       Region1      City3      161
Country1       Region1      City5      321
Country1       Region1      City6      401

there are 5 cities but they are numbered as
city1, city2, city3, city5, city6. It should be
city1, city2, city3, city4, city5.

Also Look at the cities of country1 , region2

Country1       Region2      City1      21
Country1       Region2      City2      101
Country1       Region2      City3      181
Country1       Region2      City5      341
Country1       Region2      City7      501

there are 5 cities but they are numbered as
city1, city2, city3, city5, city7. It should be
city1, city2, city3, city4, city5.

Also look at the cities of country10 region2

Country10      Region2      City1      30
Country10      Region2      City2      110
Country10      Region2      City3      190
Country10      Region2      City8      590

They are numbered city1, city2, city3, city8. it should
be city1, city2, city3, city4.

Its actually cross joining all inline views and then
randomly excluding the rows from it because the
dbms_random.value will potentially generate a different
random no. of each row not for each country, region.

Thanks.

          Followup:

yes, i guess that is true -- sorry about that.

you can use row_number() in the outer query instead of rownum in the inner query
to build the city name.

ops$tkyte@ORA920LAP> select country, region, 'City' || row_number() over
(partition by country, region order by loc_id) city, loc_id
  2    from (
  3  select country, region, loc_id
  4    from (
  5    SELECT country, region, city, rownum loc_id, rr, dbms_random.value(1,9)
  6  rand FROM
  7     (
  8     SELECT 'Country'||rownum country
  9     FROM emp, emp, emp Where rownum < 21
 10     ),
 11     (
 12     SELECT 'Region'||rownum region
 13     FROM emp, emp, emp Where rownum < 5
 14     ),
 15     (
 16     SELECT 'City'||rownum city, rownum RR
 17     FROM emp, emp, emp Where rownum < 9
 18     )
 19     )
 20     where rr <= rand
 21     )
 22  order by 1, 2, 3
 23  /

COUNTRY    REGION     CITY           LOC_ID
---------- ---------- ---------- ----------
Country1   Region1    City1               1
Country1              City2              81
Country1              City3             161
Country1              City4             241
Country1              City5             321

Country1   Region2    City1              21
Country1              City2             101
Country1              City3             181
Country1              City4             261
Country1              City5             421

      Rating: 4
i think i
      solved it  September 07, 2003
Reviewer:  Asim
      Naveed  from Pakistan
I just made a little change and now its working perfectly.
But please give me a better solution if possible
here is the final query

select country, region, 'City'||
ROW_NUMBER() OVER (PARTITION BY country, region order by city) city,   loc_id
  from (
  SELECT country, region, city, rownum loc_id, rr, dbms_random.value(1,9)
rand FROM
   (
   SELECT 'Country'||rownum country
   FROM emp, emp, emp Where rownum < 21
   ),
   (
   SELECT 'Region'||rownum region
   FROM emp, emp, emp Where rownum < 5
   ),
   (
   SELECT 'City'||rownum city, rownum RR
   FROM emp, emp, emp Where rownum < 9
   )
   )
   where rr <= rand
order by 1, 2, 3

      Rating: 5
Now deleting
      randomly  September 10, 2003
Reviewer:  Asim
      Naveed  from Pakistan
Thanks for your valuable answers. I have another question
now.

I generated alot of data in my fact table which have
several dimensions. About 12 million records. Now I want
to randomly delete 9 million records from the table.

Thanks again for your help.

          Followup:

"randomly delete?????"

what possible use could that be.  sorry, but I find this an "amazing" request.

no idea what you could be possibly doing.

      Rating: 1
Randomly delete
      rows from a table  September 11, 2003
Reviewer:
      Asim Naveed  from Pakistan
Actually we are developing a front end B.I. application.
To test the performance of that application, I have been
assigned a task to generate large data automatically in
the datawarehouse. The data should be generated in
such a way that it should look like real data not
computer generated data. For example if Quarter 1-1998
has sales of 300,000 the Quarter 2-1998 sales should
be around 300,000.

Therefore I generated the data using SELECT .... FROM bigTable ....... and
dbms_random.value. I generated
about 12 million rows in my fact table. The data is
generated randomly and it looks like real data not
computer generated data. Till here its all fine.
But when I started making Materialized Views for
aggregations, it took very long time (because of my computers small RAM and
CPU). So i decided  to cut down the
data from 12 million to only 3 million rows. But I still
want the data to look like its random and real.

If I use DELETE FROM sales WHERE time_id = '1'
or DELETE FROM sales WHERE product_id = 4 , the data
will show that there is no sales in a particular day
or of a particular product respectively. So I thought
its good to randomly delete 9 million rows.

May be the structure of the table can help decide
some strategy to randomly delete rows. So here it
is

SQL> desc sales
 Name                       Null?    Type
-------------------------- -------- --------
SALES_ID                   NOT NULL NUMBER(10)
MARKET_ID                  NOT NULL NUMBER(10)
PRODUCT_ID                 NOT NULL NUMBER(10)
TIME_ID                    NOT NULL NUMBER(10)
SALES_CHANNEL_ID           NOT NULL NUMBER(10)
SALESMAN_ID                NOT NULL NUMBER(10)
SALES                      NOT NULL NUMBER(11,2)
SALES_QUANTITY             NOT NULL NUMBER(11,2)
TODATE                     NOT NULL VARCHAR2(10)
CAMPAIGN_COST              NOT NULL NUMBER(11,2)

          Followup:

maybe you just drop the table and rerun your data generation program to
regenerate it with just 3 million rows.

maybe you use the SAMPLE clause and do a create table as select with a  25%
random sample -- drop the big table and rename the small, new table.

September 11, 2003 Jerry -- Thanks for the question regarding
      "Cursor Expressions Vs Scalar Subqueries", version 9.2

      You Asked
Hi Tom.  Hope you've got your snow shovel at the ready.

I am trying to understand the difference between these 2 types of SQL
statements.  Can you tell me if the following are equivalent, or is one
preferred over the other?

They seem to return the same thing, albeit formatted differently.

select deptno,(select count(*) from emp where deptno = dept.deptno)
  from dept
/

select deptno,cursor (select count(*) from emp where deptno = dept.deptno)
  from dept
/

      and we said...
well, one returns a "square" result set composed of traditional scalar
variables.

The other returns a "jagged" result set composed of traditional and
non-traditional (cursor variable) types.

The first one will return data like this ALWAYS:

deptno    count
deptno    count
deptno    count
....

The second one IN GENERAL returns data like this:

deptno    cnt1
          cnt2
          ....
          cntN
deptno    cnt1
          ...
          cntM

that is what I mean by "jagged".

the first form is preferred if that is what you want.  There will be less round
trips, less back and forth for that -- you get deptno and count for each row.
You do not get deptno + cursor that you must fetch (go back to database) to get
count and then close for each row.

The second form is nice when you have a parent table (dept) and you sometimes
want to see the child rows (emp) but "on demand".  For example, you fetch
"select deptno, cursor( from emp ) from dept" and the user says "hmmm, I'd like
to see the emps in deptno 20" -- great, you just fetch that cursor and display
them -- you didn't have to bring everyone else back with that query.

Also useful when you have more then 1:M relationship in a query:

select deptno, cursor( on emp table) emps, cursor( on budget table ) budgets
  from dept

Now you can get a result set like:

deptno   emp1      budget item1
         emp2      budget item2
         emp3
         emp4

4 emps, 2 budget items and one deptno -- in one "row"

I got my plow ready and waiting in the garage ;)  it be a snowing....
Review &
      Followup

      Rating: 4
How to make
      this readable ?  February 15, 2003
Reviewer:
      reader  from London
ora92>select deptno,cursor (select count(*) from emp where deptno = dept.deptno)
  2    from dept
  3  /

    DEPTNO CURSOR(SELECTCOUNT(*
---------- --------------------
        10 CURSOR STATEMENT : 2

CURSOR STATEMENT : 2

  COUNT(*)
----------
         3

        20 CURSOR STATEMENT : 2

CURSOR STATEMENT : 2

  COUNT(*)
----------
         5

        30 CURSOR STATEMENT : 2

CURSOR STATEMENT : 2

  COUNT(*)
----------
         6

        40 CURSOR STATEMENT : 2

CURSOR STATEMENT : 2

  COUNT(*)
----------
         0

Can we get rid of "CURSOR STATEMENT : 2"(Also I don't know what is its
significance there) from the output so that the output is in a readable format.

Thanks

          Followup:

sqlplus is what it is -- a very very simple, yet comprehensive and flexible tool
for quick ad-hocing of the data.

You want pretty -- time for a report generation tool or a custom developed
application.

sqlplus just does what it does -- dump data pretty much.  That happens to be
what it does with cursor variables.

      Rating:
      5
  June 10, 2003
Reviewer:  Vipin
       from New york
Hi Tom,

I think there exists one more difference:-

In case of SCALAR subqueries you can't select multiple columns on the inner
select, it will return

ORA-00913: too many values.

But for Cursor expressions this is not an issue. Since it is a cursor variable
it returns whatever number of columns you need.

Please validate this.

Thanks

Vipin

          Followup:

well.... there are tricks

ops$tkyte@ORA920> create or replace type myScalarType as object
  2  ( ename varchar2(30),
  3    job      varchar2(10),
  4    hiredate date )
  5  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select dname, t.data.ename, t.data.job, t.data.hiredate
  2    from (
  3  select dname, (select myScalarType(ename,job,hiredate)
  4                   from emp
  5                  where rownum = 1
  6                    and emp.deptno = dept.deptno ) data
  7    from scott.dept
  8         ) t
  9  /

DNAME          DATA.ENAME                     DATA.JOB   DATA.HIRE
-------------- ------------------------------ ---------- ---------
ACCOUNTING     CLARK                          MANAGER    09-JUN-81
RESEARCH       SMITH                          CLERK      17-DEC-80
SALES          ALLEN                          SALESMAN   20-FEB-81
OPERATIONS

ops$tkyte@ORA920>
ops$tkyte@ORA920> select dname,
  2         ltrim(substr( data, 1, 30 )) ename,
  3         ltrim(substr( data, 31, 10 )) job,
  4         to_date(substr( data, 40 ),'yyyymmddhh24miss') hiredate
  5    from (
  6  select dname,
  7        (select
rpad(ename,30)||rpad(job,30)||to_char(hiredate,'yyyymmddhh24miss')
  8           from emp
  9          where rownum = 1
 10            and emp.deptno = dept.deptno) data
 11    from scott.dept
 12         )
 13  /

DNAME          ENAME                          JOB        HIREDATE
-------------- ------------------------------ ---------- ---------
ACCOUNTING     CLARK                          MANAGER    09-JUN-81
RESEARCH       SMITH                          CLERK      17-DEC-80
SALES          ALLEN                          SALESMAN   20-FEB-81
OPERATIONS

ops$tkyte@ORA920>

      Rating:
      4
  June 18, 2003
Reviewer:  Vipin
       from New york
Hi Tom,

But the select statement within the select statement should always return single
row , right?

Otherwise we will get the following error

ORA-01427: single-row subquery returns more than one row.

But this is not be the case with Cursor expression.

Please comment on this

          Followup:

it returns one column for one row.  that column JUST HAPPENS to be a result set
itself.

the cursor is a "scalar" in that sense.

      Rating: 4
isn't
      open_cursors a problem  August 12, 2003
Reviewer:
      Vipin  from New york
Hi Tom,

If we use cursor expressions, isn't there a risk of overflowing the open_cursors
parameter and finally get the infamous error "Maximum number of cursors
exceeded....".

Suppose my query is returning 5000 rows and I am using this query to form my XML
document (where in cursor expressions are used), there are all potential chances
that I might overflow this value.

Please advise.

          Followup:

sure, anyone can "leak" cursors.

if you don't close them, yes, you will have a problem.

      Rating:
      4
  August 12, 2003
Reviewer:  Vipin
       from New york
And you won't be able to close them when you use them in a cursor expression. Am
I right?

          Followup:

why not? they are just cursors...

      Rating:
      2
  August 13, 2003
Reviewer:  Vipin
       from Newyork
Hi Tom,

If we execute the following query and if it returns 5000 rows, won't you get the
MAXIMUM OPEN CURSORS..error here if open_cursors parmater value is less than
5000?

select deptno,cursor (select empno, ename, sal
                      from   emp
                      where deptno = dept.deptno)
from   dept

Now how can we ensure that the cursors are closed here and hence prevent this
error.

I think the only way is to set the open_cursors value to as high as 5000 (which
will surely have other repercussions.)

So surely I will not use CURSOR expressions when so many rows are returned. But
wouldn't this be a restriction to cursor expression when we compare them with
SCALAR subqueries (select in select) or else can this be averted.

Please advise.

          Followup:

the *client* would close them after fetching and using them.

it is NOT a problem, unless the client has a bug and you certainly do not need
to be using CURSOR() for a client to have a cursor leak

try it in sqlplus -- very easy to see.

this is NOT a problem.

      Rating:
      5
thanx  August 13, 2003
Reviewer:
      A reader
hi Tom
this is an interesting question. If I run the above
query in sqlplus, why dont we run into "max cursor"
issue - if there are 5000 cursors? I understand that the
client has to close them - but if you open simultaneously
5K cursors then you would have exceeded the limit before
the client even closes the cursors , is n't it?

Thanx!

          Followup:

you have not opened 5k cursors simultaneously.

plus fetches 15 rows at a time by default.  processes 15 rows.  closes 15
cursors.

gets next 15. processes. closes.

      Rating:
      5
thanx!  August 13, 2003
Reviewer:
      A reader
that explains it!!
So if our cursor limit is 4000 - but we set
arraysize to 5000 - say and then run the above query
AND if the query returns more than 4000 rows - then we will
hit the "max open cursor" error, right?

Thanx!

          Followup:

give it a try. (and you'll find no, it doesn't)

the cursor isn't opened till you use it, it is closed when you close it.

ops$tkyte@ORA920LAP> show parameter open_cursors

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
open_cursors                         integer     30
ops$tkyte@ORA920LAP> set arraysize 100
ops$tkyte@ORA920LAP> select object_name, cursor(select * from dual) from
all_objects where rownum <= 100;

.....
/13b65381_AccessibleState      CURSOR STATEMENT : 2

CURSOR STATEMENT : 2

D
-
X

100 rows selected.

ops$tkyte@ORA920LAP>

it just works.

      Rating:
      4
  August 13, 2003
Reviewer:  Vipin
       from New york
More over if you have OPEN_CURSORS value set to say 25, even if your array size
is 15, if 11 other concurrent sessions are opening a cursor each in their
sessions, again I think you are at a potential threat while you retreive your
next set of 15 cursors.

Tom, could you please enlighten/correct us on this.

          Followup:

er?

open-cursors is a per session limit, not a threshold over sessions.

      Rating:
      5
thanx!  August 13, 2003
Reviewer:
      A reader
that example explains it!

      Rating:
      4
  August 13, 2003
Reviewer:  Vipin
       from Newyork
sorry, my mistake.

But there are still doubts which lingers. As you said unless you open these
cursors simultenously it is not a problem. If you refer to my first doubt :-

"Suppose my query is returning 5000 rows and I am using this query to form my
XML document (where in cursor expressions are used), there are all potential
chances that I might overflow this value."

If I am using DBMS_XMLGEN to generate an XML using cursor expressions then all
these cursors will be simultenously opened and wouldn't I get the MAX CURSORS
error here???

          Followup:

why would it have them open all at once?  it is a sequential thing to build XML
from a result set.  row by row by row by row.

      Rating:
      4
  August 13, 2003
Reviewer:  Vipin
       from Newyork
Hi Tom,

I just tried out this in the following manner:-

SQL> sho parameters open%
NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
open_cursors                         integer     300
open_links                           integer     4
open_links_per_instance              integer     4
read_only_open_delayed               boolean     FALSE
session_max_open_files               integer     10
SQL>

As discussed earlier, I was trying to use cursor expressions in XML generations
as shown below.

DECLARE
v_clob CLOB;
v_ctx  dbms_xmlgen.ctxtype;
BEGIN
  v_ctx := dbms_xmlgen.newcontext('select object_name, cursor(select count(*)
from all_objects b
  WHERE b.owner = a.owner
  AND   b.object_name = a.object_name)
  FROM  all_objects a');

/* Please note below that I have set maxrows to 100. */

  dbms_xmlgen.setmaxrows(v_ctx,100);
  v_clob := dbms_xmlgen.getxml(v_ctx);
END;

This ran without an error and please note the query results for current open
cursors in the same session:-

select b.name, a.value
from
v$mystat a,
v$statname b
where a.statistic# = b.statistic#
and b.name like 'opened cursor%'

opened cursors cumulative    122
opened cursors current            103

Now I ran the XML generation block once again which again ran without error and
open cursor query showed me the
following results:-

opened cursors cumulative    234
opened cursors current            204

Now I ran the XML generation block once again and I got the maximum open cursors
exceeded error.open cursor query showed me the following results:-

opened cursors cumulative    341
opened cursors current            205

So it means that If we try to generate this XML with setmaxrows as any thing
more than 300 or probably nearer to 300, it will give me the error (which it
gave when I ran in another session.)

Please comment.

          Followup:

please file a tar with support -- given you have the nice test case.

Looks like they have a "leak" perhaps

      Rating: 4
CURSOR
      Expression and views  August 18, 2003
Reviewer:
      Adrian Billington  from UK
Tom

The docs state quite clearly under Restrictions that we cannot have nested
cursor expressions inside views. This appears to be untrue to a degree and I'd
like some clarification. The demo below shows a cursor expression being happily
contained in a view, but this is only accessible from server-side sqlplus. When
I try to select from the view using latest TOAD, I get rows returned but with
NULL data. Same of SQL*Plus. If I try a really old SQL*Plus client (such as
8.0.6) I get internal error codes.

Can you shed some light on what we should do with CURSOR and views ? Version =
9.2.0.3.

920>
920> create type myRowType as object
  2  (      x int
  3  ,      y char(1)
  4  );
  5  /

Type created.

920>
920> create type myTableType as table of myRowType;
  2  /

Type created.

920>
920> create function plfx (
  2                  cur_in sys_refcursor
  3                  ) return myTableType pipelined as
  4
  5     type typ_aa_allobs is table of all_objects%rowtype
  6        index by binary_integer;
  7     aa_allobs typ_aa_allobs;
  8
  9     rec_out myRowType := myRowType(null, null);
 10
 11  begin
 12
 13     fetch cur_in bulk collect into aa_allobs;
 14
 15     if aa_allobs.count > 0 then
 16
 17        for i in aa_allobs.first .. aa_allobs.last loop
 18
 19           rec_out := myRowType ( aa_allobs(i).object_id,
 20                                  substr(aa_allobs(i).object_type,1,1) );
 21
 22           pipe row (rec_out);
 23
 24        end loop;
 25
 26     end if;
 27
 28     return;
 29
 30  end;
 31  /

Function created.

920>
920> select *
  2  from   table ( plfx ( cursor (
  3                                select *
  4                                from all_objects
  5                                where rownum <= 5
  6                               ) ) );

         X Y
---------- -
   1474420 J
   1464692 J
   1479676 J
   1470748 J
   1479479 J

920>
920> create view vplfx
  2  as
  3     select *
  4     from   table ( plfx ( cursor (
  5                                   select *
  6                                   from all_objects
  7                                   where rownum <= 5
  8                                  ) ) );

View created.

920>
920> select *
  2  from   vplfx;

         X Y
---------- -
   1474420 J
   1464692 J
   1479676 J
   1470748 J
   1479479 J

Regards
Adrian

          Followup:

I think they were talking about this cursor expression:

ops$tkyte@ORA920> create or replace view v as select dname, cursor(select ename
from emp where emp.deptno=dept.deptno)
  2  from dept;
create or replace view v as select dname, cursor(select ename from emp where
emp.deptno=dept.deptno)
                                          *
ERROR at line 1:
ORA-22902: CURSOR expression not allowed

a selected cursor variable.

Please -- please -- file a bug for your other observed behaviour with support!

      Rating: 4
That's cleared
      it up...  August 18, 2003
Reviewer:  Adrian
      Billington  from UK
Thanks. I see what they mean now.

As for the odd behaviour from client tools, I'll raise an iTAR. It's wierd, even
server-side operations such as a CTAS or an INSERT..SELECT from the view
executed from the client tools populate with NULL data...

Regards
Adrian

      Rating:
      3
Nice  September 11, 2003
Reviewer:
      John  from Sydney,Australia
Dear Sir,
As we are new to Oracle ,We want to know what is a "scalar
subquery".How is it different from other sub queries?Could you please explain
with some examples?Clarity is expected.

          Followup:

select dname, (select count(*) from emp where emp.deptno = dept.deptno )
  from dept;

text in bold is a scalar subquery.  it is a subquery that returns a single row
and column -- it is "scalar", like a column.

September 11, 2003 Tony -- Thanks for the question regarding
      "nls_date_format", version 8.1.6

      You Asked
Tom,
One of the query submitted through OLAP tool is showing in SGA as below:
SELECT   EDWDM.Dim.TERMINCEP FROM   EDWDM.Dim WHERE   (    EDWDM.Dim.TERMINCEP
=  '03-07-2001 00:00:00'   )
When I try to run directly through sqlplus:
ERROR at line 1:
ORA-01843: not a valid month
which I expected because
 1* select * from nls_session_parameters
system@pedw> /

PARAMETER                      VALUE
------------------------------ ----------------------------------------
NLS_LANGUAGE                   AMERICAN
NLS_TERRITORY                  AMERICA
NLS_CURRENCY                   $
NLS_ISO_CURRENCY               AMERICA
NLS_NUMERIC_CHARACTERS         .,
NLS_CALENDAR                   GREGORIAN
NLS_DATE_FORMAT                DD-MON-RR
NLS_DATE_LANGUAGE              AMERICAN
NLS_SORT                       BINARY
NLS_TIME_FORMAT                HH.MI.SSXFF AM
NLS_TIMESTAMP_FORMAT           DD-MON-RR HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT             HH.MI.SSXFF AM TZH:TZM
NLS_TIMESTAMP_TZ_FORMAT        DD-MON-RR HH.MI.SSXFF AM TZH:TZM
NLS_DUAL_CURRENCY              $
NLS_COMP                       BINARY

My questions:
1.How oracle was able to process it when it came from OLAP?
2. When I do-
show parameters nls_

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
nls_calendar                         string
nls_comp                             string
nls_currency                         string
nls_date_format                      string
nls_date_language                    string
nls_dual_currency                    string
nls_iso_currency                     string
nls_language                         string  AMERICAN
nls_numeric_characters               string
nls_sort                             string
nls_territory                        string  AMERICA
nls_time_format                      string
nls_time_tz_format                   string
nls_timestamp_format                 string
nls_timestamp_tz_format              string

Why nls_date_format value not shown here, and how it is shown in
nls_session_parameters table as DD-MON-RR.
Thanks,
Tony

      and we said...
1) the olap tool must have issued:

alter session set nls_date_format = "dd-mm-yyyy hh24:mi:ss"

before running the query.  I hate it when "tools" compare dates to strings,
strings to numbers, and so on.

I would much rather prefer the "tool" did this:

SELECT   EDWDM.Dim.TERMINCEP FROM   EDWDM.Dim WHERE   (    EDWDM.Dim.TERMINCEP
=  TO_DATE('03-07-2001 00:00:00','DD-MM-YYYY hh24:mi:ss')   )

That way

a) we know it is a date
b) we know that 03 is a Day, 07 is a MONTH (i'm guessing that is so)
c) it would work regardless of the NLS_DATE_FORMAT
d) we are comparing a date to a date -- we KNOW there are no (bad) implicit
conversions taking place!

2) that is normal.  That just means you didn't set anything in the init.ora.  I
see the same thing, but if I add nls_date_format = "DD-MON-RR" to my init.ora:

SVRMGR> show parameter nls
NAME                                TYPE    VALUE
----------------------------------- ------- ------------------------------
nls_calendar                        string
nls_comp                            string
nls_currency                        string
nls_date_format                     string  DD-MON-RR
nls_date_language                   string
nls_dual_currency                   string
nls_iso_currency                    string
nls_language                        string  AMERICAN
nls_numeric_characters              string
nls_sort                            string
nls_territory                       string  AMERICA
nls_time_format                     string
nls_time_tz_format                  string
nls_timestamp_format                string
nls_timestamp_tz_format             string
SVRMGR>

I get that.

nls_session_parameters shows you what is in effect for your session -- its like
a v$ table (memory based).  show parameter shows you the parameters from the
init.ora.

Review &
      Followup

      Rating: 4
Show parameter
      must reference session...?  May 21, 2002
Reviewer:
      AB  from UK
Tom

I issued a show parameter for "query_rewrite_enabled" which was false. As you
say, this came from the init.ora.

After I issued an alter session to set it to true, the show parameter command
showed it to be true. So the show parameter must be session based ???

      Rating: 3
hundredth of a
      second  September 11, 2002
Reviewer:  ram
       from india
some questions in time formats.

1) I do a 'set timing on' in sqlplus and i run a procedure. the time taken is
shown as 'Real: some_value'. how do i get it to show the better and easier to
understand '00:00:00:00' format ?

2) how do we determine the time in precision of hundredth or milli seconds ? i
searched the ora docs and i see only upto hh:mi:ss and another option for the
time in seconds from midnight !

3) what should i use for nls_time_format to show me 00:00:00:00 format ?
its currently, HH.MI.SSXFF AM in nls_session_parameters.
pls clarify.

          Followup:

1) use a current version of SQLPLUS (eg: 8i and up)

2) the Oracle DATE datatype only supports down to seconds.  If you use Oracle9i,
there is a timestamp datatype that supports upto 9 digits of fractional seconds.

3) use to_char on the SYSTIMESTAMP (not sysdate) in 9i and up -- fractional
seconds do not exist prior to that and based on q1 of yours, you have 8.0 or
before.

      Rating: 3
how about
      numbers...  September 11, 2003
Reviewer:  vj
       from in
Tom,

I have a column with datatype number(20,6) and got below rows

18
18.2
18.21
18.876

My Issue :

i would have prefered to be an client / server d2k guy for this problem,
unfortunately the customers wanted to be in the WEB...i would have done with
simple format setting to get below output...

18.000000
18.200000
18.210000
18.876000

but my java guys who write jsp programs has this question !!! i know the
trailing zeros wont be stored in the database, so when i retrieve the value in
my page i got to PAD those zeros to the extent of the decimal width blah blah
blah for which we have to write java scripts etc..etc.......why i dont have some
setting like NLS-DATE-FORMAT for the number..when i query, it should show me
with zeros ??? How about that ??

Your help is highly appreciated...

          Followup:

umm, when you select just

select to_char( x, '99.0000000' ) x from .....

no javascript?

how about that

      Rating: 5
client /
      server?  September 11, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
"i would have prefered to be an client / server d2k guy for this problem,
unfortunately the customers wanted to be in the WEB"

Those silly customers.

- Mark

September 11, 2003 Vrishali -- Thanks for the question regarding
      "rollback segment ", version 8.1.6.2.0

      You Asked
Hi Tom,

1. While creating new datbase, Oracle create few rollback
segemnts including one system rollback segment. My question is -
on which factor the no. of rollback segments (at the time of
database creation) depends on. or is there any fixed value
assigned for them while creating at the time of dataabse
creation?

2. While running tuning queries on rollback segemnt , it is
always showing values in "wrap" columns but there are no
"shrinks" of rollback segments. Also "wait" column also shows
value.
Does that mean I have to increase no. of rollback segements in
the database?

Thanks

      and we said...
1) during database creation, you only need enough to create the
database.  we need at least one non-system rollback segment so 1
is the minimum.  The default install just creates a couple.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:233214228495
for one method of discovering how many rollback segments of
what size YOU need.

2) wraps are normal and if you don't see extends -- ok.  If you
are not getting ORA-1555 on queries, they are big enough.

I prefer to use v$waitstat, although excessive waits in
v$rollstat will tell you the same thing basically

select class,count from v$WAITSTAT where class in
('undo header','undo block','system undo header',
  'system undo block');

detects contention for rollback segments (indicating the
possible need for more)
Review & Followup

      Rating:
      4
  December 05, 2001
Reviewer:  Mike
       from Atlanta
you said:
"wraps are normal and if you don't see extends -- ok.  If you are not getting
    ORA-1555 on queries, they are big enough.  "
Could you explain more on the Extends? I have just checked my database and found
one rb seg:
Shrink=3, wrap=19, extend=10, waits=1

trek@o817>  select class,count from v$WAITSTAT where class in
  2  ('undo header','undo block','system undo header',
  3    'system undo block');

CLASS                  COUNT
------------------ ---------
system undo header         0
system undo block          0
undo header                1
undo block                 0

How do you think the rollback segment?

          Followup:

EXTENDS is the number of times the rollback segement had to add more space.
This means you had some long running transaction (does not have to be BIG, just
LONG running, it could have been big) that caused the rollback segment to grow
in size instead of wrapping.

Is 10 extends bad (it looks like it happened 3 times based on the shrinks, we
extended 3/4 extents 3 times)?  Hard to say, depends on the time.  If this is
over a 4 week period and is only on one rollback segment -- probably not.  If
this is over a 1 hour or 1 day period -- you might want to look for the cause,
to find the culprit transaction and see why its taking so long you need to
extend.

      Rating:
      4
  February 02, 2002
Reviewer:
      SUBBARAO  from india
We have created a database using DBASSIST wizard with shared
mode(MTS enabled), we have done the installation with the default
tablespaces. It has created a tablespace RBS and installation
went thru. When i checked for this query "SELECT * FROM
DBA_ROLLBACK_SEGS" It has listed SYSTEM rollback segment
and also other 29 rollback segments
named RBS0 .. RBS29. They are created by default.
Some time before when I created a database it has created only
7 rollback segments. In my installtions no where
i have gave this sought of paramerters.

1) My question is -
on which factor the no. of rollback segments (at the time of
database creation) depends on.
2) I didn't plan for my database for the number of rollback segments.
   Can i have this many(default) rollback segments initially.

          Followup:

the number of rollback segments created will depend on the type of database you
pick and other options (eg: mixed workload, dss, oltp and so on).

You can just drop them if you don't want them.  The theory with MTS is you have
LOTS and LOTS of users (thats why you are using MTS).  You are an OLTP system
with very short transactions (lots of insert/update/delete type activity).  You
would need more then the "usual" number of rbs's in such a system.

If you don't have lots of users, you probably don't need or want MTS.

      Rating:
      5
  September 14, 2002
Reviewer:  mike
       from SanAntoio,TX
You said:
"
EXTENDS is the number of times the rollback segement had to add more space.
    This means you had some long running transaction (does not have to be BIG,
just
    LONG running, it could have been big) that caused the rollback segment to
grow
    in size instead of wrapping.
"
For my understanding the segment size may not be to grow upto a point , if the
maxextents set explicitly. Question becomes what will be happening? Oracle will
allocate the new segment for the transaction or will return an Ora-1555?

          Followup:

If you run out of the ability to extend and you needed to extend you'll get a
"failed to extend" error.

01562, 00000, "failed to extend rollback segment number %s"
// *Cause: Failure occurred when trying to extend rollback segment
// *Action: This is normally followed by another error message that caused
//     the failure. You may take the rollback segment offline to perform
//         maintainence. Use the alter rollback segment offline command
//         to take the rollback segment offline.

      Rating: 4
where to see
      this values from   August 25, 2003
Reviewer:
      A reader
tom,

can you please tell me which tables to look for this values ...I have just
checked my database and found
one rb seg:
Shrink=3, wrap=19, extend=10, waits=1 -- as mike says above.  i am new to this
dba stuff...but very much interested in becoming ...

thanks as always

          Followup:

check out the v$tables, this one is v$rollstat

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96536/ch3158.htm#1098327
all documented here

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96536/toc.htm

      Rating: 4
Unable to drop
      rollback segment. Why?  September 11, 2003

Reviewer:  Tony  from India
My rollback segment is "partly avilable" state.
When I try to offline, I get the following error:

ORA-01598: rollback segment 'RBS2' is not online.

And when I drop the RBS, I get the following error:

ORA-01545: rollback segment 'RBS2' specified not available.

When I drop the rbs tablespace, I get the following error:

ORA-01548: active rollback segment 'RBS2' found, terminate dropping tablespace .

Please help me out.

          Followup:

the rbs is not available for new transactions (it is being offlined)

but there is still a transaction that has not yet committed that is using it.

      Rating: 3
Why rbs in
      partly available state?  September 11, 2003

Reviewer:  Tony  from India
I shutdown and restart the db. but the rbs is in partly available status.
1. When do we have this state.
2. If any pending transaction, It should have been commited/rolledback when
restarted the db. Why still "partly available" state appears?

          Followup:

what is "partly available" status?

September 11, 2003 -- Thanks for the question regarding
      "DB/Application slow after migrating to 9i", version 8.1.7

      You Asked
Tom,
We recently migrated from Oracle 8i(8.1.7) to ORACLE 9i(9.2.0.3).  After the
migration, the application(EJB based)/queries which were running fine on 8i are
running noticably slow on 9i.  When we approched our DBA, he said that the way
queries work(execution plan etc..) on 8i  is different from 9i and that we need
to look into all the the SQL's that have been written.

If we go with that approach it will take us a long time before we can find and
fix the problem.
Can you suggest a better way(things that we may have to look at..etc..) of
approaching this problem?

Thank you

      and we said...
sigh, when you tested this application on your test machines -- what did you
experience there?

when you compare the statspack report from 8i to 9i under similar loads -- does
anything jump out at you?

when you compare the tkprof reports for the application from 8i to 9i -- what
were the fundemental differences?

Do you have the ability to do any of the above????  Did you test??  Do you have
a history of how this application performed in the past?
Review &
      Followup

      Rating: 3
Slow
      Performance After Migration  May 30, 2003

Reviewer:  Ruben Badilla Aguilar  from Mexico

I had similar problem when migrate from Oracle 8i (8.1.7.3) to Oracle 9i
(9.2.0).
When we look at statitics (tkprof and statspack) made in Oracle 8i before
migration and stats gathered in Oracle 9i by applying test on that database, we
realized that execution plans changed from Oracle 8i to Oracle 9i.

We worked hard trying to figure out where the problems can be found.
Trying with new Oracle 9i parameters over and over, I found that changing the
parameter OPTIMIZER_FEATURES_ENABLE=9.2.0 (default) to
OPTIMIZER_FEATURES_ENABLE=8.1.7, our application ran successfully as in Oracle
8i.
Also next parameters was set to improve performance:
(* usefulness )

*db_cache_size=1500m
*pga_aggregate_target=314572800
*workarea_size_policy=AUTO
undo_management=auto
undo_tablespace=rbs_01
undo_retention=900
shared_pool_size=117440512

I hope this can be useful for you.

Regards,
Ruben

      Rating: 5
DB/Application
      slow after migrating to 9i  May 30, 2003
Reviewer:
      A reader

      Rating: 5
no problems if
      you read the documentation  May 30, 2003
Reviewer:
      Tommy  from USA
The DBA is wrong in saying that you need to re-write all your code.
If you read the document Oracle9i Database Migration Release 2 (9.2)and follow
the recommendations you should not have ANY problems. Pay special attention to
the new initialization parameters and understand SPFILE usage.  I've seen
several DBO's (not real DBA's) still trying to update an init.ora when their
database was running from the SPFILE

Here's the link :

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96530/toc.htm

      Rating: 3
DBA/Application
      slow in Oracle 9.2.0.3  September 11, 2003

Reviewer:  reader  from NY Usa
Tom we had to roll back our upgrade plans from 8.1.7.4 to 9.2.0.3. There were
indeed performance problems. The queries were very slow. If you look in metalink
at bug # 3090690, 3086748, 2853398..

          Followup:

2 of which are not bugs and one of which has a simple workaround to implement
(trivial actually)??!??

September 11, 2003 -- Thanks for the question regarding "Snapshot /
      Materialized View", version 8i

      You Asked
Hi Tom,
I have question - when I create a snapshot / materialised view I find Undefined
object type is created with INVALID status
Steps

SQL> create table trial ( pk number(2) constraint pk_trial primary key );
Table created.
SQL> insert into trial values (1);
1 row created.
SQL> create materialized view trial1 as select * from trial;
Materialized view created.
SQL>  select object_type from user_objects;
OBJECT_TYPE
------------------
TABLE
INDEX
INDEX
VIEW
TABLE
TABLE
UNDEFINED

SQL> drop materialized view trial1;

Materialized view dropped.

SQL> select object_type from user_objects;

OBJECT_TYPE
------------------
TABLE
INDEX
VIEW
TABLE

I wonder what is this ? Am I doing something wrong or this is normal behaviour .
??
Regards
MKS

      and we said...
It is normal and OK.

The object is a SUMMARY object associated with materialized views.  Just an
internally used object type.
Review &
      Followup

      Rating: 4
Materialized
      Views  June 27, 2002
Reviewer:  SK
       from US
Tom,

Thanks you for answering my question on following link on MV.

http://asktom.oracle.com/pls/ask/f?p=4950:8:979816::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1197551832658,%7BMaterialized%7D%20and%20%7BView%7D
I would like to tell that I have tried both REFRESH ON COMMIT and REFERESH
COMPLETE without any materialized view log. but the performance is very poor for
refresh. Am I missing some tuning variable here to make it faster.

Or Is it a good choice to go for dropping and recreating the MV rather than
refreshing when refreshing of MV is taking longer than recreating the
materialized view.

AS far as the specified sample was concerned I took a small sample of a big
table to test as I need to implement this really soon. But the actual PROD
database table contains 100-500 million rows.

My application had a earlier solution of truncate, insert /*+append*/ into t2
select col1,count(*) from t2 group by col1;

Later on due to this massive update I was inclined to use MV but MV update is a
major concern. Your expert opinion ans solution will be highly appreciated.

Thanks

          Followup:

Your MV is not a candidate for refresh on commit if you plan on inserting 50k
rows on a regular basis.

REFRESH complete should not take anymore time then the initial build did.

      Rating: 2
So UNDEFINED
      and INVALID is normal  September 11, 2003

Reviewer:  Beth  from Minnesota, USA
Hey Tom,
I am new to MVIews.  So just to be clear, the object_type in dba_objects will
show up as 'UNDEFINED' for any material view?  And the status in dba_objects
will be 'INVALID', even though there is no problem?

After changing an underlying table, where do you look to make sure everything is
still valid and working?  COMPILE_STATE in dba_mviews?  Are there any steps, in
general, that need to be done to recompile, revalidate, etc., after performing
DDL on the associated tables?

Thanks

          Followup:

in 8i, you'll see this:

ops$tkyte@ORA817DEV> create table trial ( pk number(2) constraint pk_trial
primary key );

Table created.

ops$tkyte@ORA817DEV> insert into trial values (1);

1 row created.

ops$tkyte@ORA817DEV> create materialized view trial1 as select * from trial;

Materialized view created.

ops$tkyte@ORA817DEV> select object_name, status, object_type from user_objects
  2  where object_name like '%TRIAL%';

OBJECT_NAME                    STATUS  OBJECT_TYPE
------------------------------ ------- ------------------
PK_TRIAL                       VALID   INDEX
PK_TRIAL1                      VALID   INDEX
TRIAL                          VALID   TABLE
TRIAL1                         VALID   TABLE
TRIAL1                         VALID   UNDEFINED

ops$tkyte@ORA817DEV>

status should be OK, but in 9i, you'll see this:

OBJECT_NAME                    STATUS  OBJECT_TYPE
------------------------------ ------- ------------------
PK_TRIAL                       VALID   INDEX
PK_TRIAL1                      VALID   INDEX
TRIAL                          VALID   TABLE
TRIAL1                         VALID   TABLE
TRIAL1                         VALID   MATERIALIZED VIEW

September 11, 2003 Adrian -- Thanks for the question regarding
      "Object Methods vs Packages", version 8.1.6

      You Asked
Tom

In the chapter on Using Object Relational Features in your book, you present
your "api" to UTL_FILE, which contains some great stuff such as avoiding the
enormous EXCEPTION handler we have to write every time we use the UTL_FILE
package. Another great feature is the "inheritance" (forgive me if the
terminology os wrong - I am not an O-O programmer in any way shape or form),
such that when you declare a variable of your fileType, it "inherits" all of the
functions and procedures contained within that fileType, much as I've seen when
reading JAVA.

My question is simple. Why have you written this as objects and methods instead
of a "wrapper" package to UTL_FILE? I assume there must be more to it than
simply to demonstrate object features...? Looking at this example, shouldn't all
our own "APIs" that we all write over built-in packages be constructed in this
way?

Many thanks

Adrian

      and we said...
It is the "encapsultation" and OO of Java that I was trying to show. To
demonstrate that you can do that in PLSQL as well and its a great use of PLSQL
in the database -- using the O-R stuff in PLSQL code.

A package implementation would be hard.  I wanted to be able to do something
like:

declare
   input_file  fileType := fileType.open( 'c:\temp', 'input.dat', 'r' );
   output_file fileType := fileType.open( 'c:\temp', 'output.dat', 'w' );
begin
   output_file.put_line( input_file.get_line() );
   output_file.close;
   input_file.close;
end;

the variables input_file and output_file remember their state.  I don't have to
pass a record (like UTL_FILE does) to each call to the API's -- the object type
hides that from me.

It's just more "OO", hides some of the complexity.

Some might say "it's cooler".

I was just trying to show a valid, useful way to use the O-R extensions in the
database.  Yes, I could have written it as a package but every API call would
have an extra parameter -- a record of variables, the "state".  this is done
automagically with the object types and the magic SELF parameter that is always
passed.

It is not appropriate for EVERY api, just ones where you find yourself passing a
"state" variable in (like UTL_FILE, UTL_TCP for example)
Review &
      Followup

      Rating: 4
That's cleared
      that one up...!  April 19, 2002
Reviewer:
      Adrian Billington  from UK
Thanks, this is really useful - as well as being another method in the SQL and
PL/SQL arsenal,  at the very least it's really handy to be able to save on
(tedious) parameter passing. Time to play with this I think !

Regards

Adrian

      Rating: 5
Great
      Example  September 11, 2003
Reviewer:  Mike
      Schmidt  from St. Louis, MO
Tom:

Thanks for the great and useful object-relational PL/SQL example on pg 883 of
your book (UTL_FILE API).  Great example! Thanks also for posting your source at
Wrox/Apress.

I'm not a strong OOP developer so this might sound like a dumb question but how
could your API be enhanced with a private counter and set/get methods to keep
track of the lines read/written to the file?  Would the approach be similar to
any PL/SQL package where one declares the private variable in the body vs.
header and then increment or reset after each put_line, open/close call?  I
realize putf adds complexity but that's OK as I'm more interested in how you
would approach setting/getting private data counters like this in the Type Body.
 I mean, it could be very handy to interrogate the object for the current number
of lines written via something like f.get_lines_written.  Your object-based API
seems like it would nicely accommodate counters like this.

Thank you,

-Mike

          Followup:

yes, you would just add a counter to the object spec and increment it in the
body and have a "getter" function to return it (or just access the attribute)

      Rating: 4
Works
      Great  September 13, 2003
Reviewer:  Mike
       from St. Louis, MO
Thanks Tom.  I added lines read & written counters and get methods.  Even
searched for '\n' substrings in format string passed to putf().  Thanks for the
great API to build on.

September 11, 2003 Ajeet -- Thanks for the question regarding
      "Materialized View Refresh", version 8.1.7

      You Asked
Hi Tom,
I have a concept realted question and I am not sure where to look for so as
always I come to you.

My question is How oracle perform a complete refresh of a materialized view ?
Does Oracle internally use insert /*+ APPEND */ into select * from .....?
I am asking this question because I was doing a complete refresh of a huge
materialized view (having more than 5 million rows) and I got rollback segment
insufficinet error.So I thought how actually this complete refresh
works.Logically I can not think that oracle will commit (I have no fact
here--Just using my limited understanding of oracle).And if it uses insert /*+
APPEND */ -- it should not use much rollback segment space.I do have a 2 GB of
Rollback segment Tablespace.
I searched the answer in concept guide and the sources known to me,but I did'nt
get any information on this.
Any insight on this from you will help me understand this.

Kind regards -- Ajeet

      and we said...
A complete refresh of a MV that is not in a refresh group with more then one
table is done via:

truncate + insert /*+ APPEND */

(you can observe this with SQL_TRACE on during the refresh).  If the MV is in a
refresh group with more than one table, then we have to do:

delete + insert

since the objects must be refreshed "consistently" with respect to eachother.

Now, don't forget -- if you have indexes, indexes ARE maintained during a insert
/*+ append */ and indexes are the biggest consumers of undo/redo -- given that
they are hugely complex data structures.  Consider:

ops$tkyte@ORA817DEV> create table t as select * from all_objects where 1=0;

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert /*+ append */ into t select * from all_objects;

26804 rows created.

ops$tkyte@ORA817DEV> select used_ublk from v$transaction;

 USED_UBLK
----------
         1

ops$tkyte@ORA817DEV> rollback;

Rollback complete.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create index t_idx1 on t(object_id);

Index created.

ops$tkyte@ORA817DEV> create index t_idx2 on t(owner,object_type,object_name);

Index created.

ops$tkyte@ORA817DEV> insert /*+ append */ into t select * from all_objects;

26806 rows created.

ops$tkyte@ORA817DEV> select used_ublk from v$transaction;

 USED_UBLK
----------
       219

see, two simple indexes -- 219 blocks of UNDO versus 1 block without.  It is
interesting to note that table T is 300 blocks in this example...

If you:

create materialized view t
refresh complete
as
select *
  from all_objects;

and then

ops$tkyte@ORA817DEV> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA817DEV> exec dbms_mview.refresh( 'T' );

PL/SQL procedure successfully completed.

you'll find:

truncate table "OPS$TKYTE"."T" purge snapshot log

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          1          4           0
Execute      1      0.01       0.12          2        349         66           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.01       0.13          2        350         70           0

********************************************************************************

INSERT /*+ APPEND */ INTO "OPS$TKYTE"."T"select * from all_objects

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.06       0.06          0        238          0           0
Execute      1      3.95       8.06        388      64806         63       22273
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      4.01       8.12        388      65044         63       22273

in the trace.

Also, having "2 gig of rollback segment tablespace" doesn't mean much.  It could
mean you have twenty 100meg rollback segments, or fourty 50meg rollback segments
or two 1gig rollback segments or any of the virtually infinite sizes in
between...

Review &
      Followup

      Rating: 5
simply
      great  April 10, 2003
Reviewer:  Ajeet
       from OH,usa
Tom -- It was a great explanation.you have told so many times to use sql_trace
but I was not using it for create materialized view...as I was under impression
that oracle does some magic and I can not see --in case of materialized views.
I learn everyday from this site no doubt about it.
Thanks again.

      Rating: 5
Super
      duper  September 11, 2003
Reviewer:  Craig
       from NW Arkansas
Great info on *how* Oracle does the inserts.  I've been looking high and low for
that.  When we are refreshing our matierialized views, is there any way to tell
how many rows were inserted/updated/deleted (in general, affected)?  I'm hoping
for something like SQL%ROWCOUNT.

Thanks again, Tom

ps.  I finally ordered Expert One-On-One...should be here tomorrow!!!

          Followup:

not really -- the mview is just "refreshed", the number of new/deleted/updated
rows is not exposed.

September 11, 2003 Janardhan -- Thanks for the question regarding
      "Performance - top N query", version 8.1.5

      You Asked
Hi,

Here We are executing a query which returns top 2 salaries
dept wise from emp.

Query:select sal,deptno
      from emp a
      where 2>(select count(sal)
               from emp
               where deptno=a.deptno
           and sal>a.sal)
      order by deptno,sal;

But if there are more then 12,00,000 records in EMP table
it is taking lot of time to retrieve results..more then 10 mins.
suggest me solution.

select count(*) from emp is taking 2 mins to give result.

Thanks in advance..

Regards,

Janardhan.

      and we said...
In Oracle8i, release 8.1 and up, you can use order by in a
subquery.  Therefore:

select sal, deptno
  from ( select sal, deptno
           from emp
          order by SAL desc )
 where rownum <= 2
order by deptno, sal
/

will work...

Now, that query is DIFFERENT from the one you have, here is an
exmaple showing the different results you can expect and an
alternate query that is equivalent to yours:

ops$tkyte@8i> create table emp ( sal int, deptno int );
Table created.

ops$tkyte@8i> insert into emp values ( 100, 10 );
ops$tkyte@8i> insert into emp values ( 100, 10 );
ops$tkyte@8i> insert into emp values ( 100, 10 );
ops$tkyte@8i> insert into emp values ( 200, 10 );
ops$tkyte@8i> insert into emp values ( 200, 10 );
ops$tkyte@8i> insert into emp values ( 200, 10 );
ops$tkyte@8i> insert into emp values ( 300, 10 );
1 row created.

ops$tkyte@8i> select sal,deptno
  2    from emp a
  3   where 2>(select count(sal)
  4              from emp
  5             where deptno=a.deptno
  6               and sal>a.sal)
  7   order by deptno,sal
  8  /

       SAL     DEPTNO
---------- ----------
       200         10
       200         10
       200         10
       300         10

ops$tkyte@8i> select sal, deptno
  2    from ( select sal, deptno
  3             from emp
  4            order by SAL desc )
  5   where rownum <= 2
  6  order by deptno, sal
  7  /

       SAL     DEPTNO
---------- ----------
       200         10
       300         10

ops$tkyte@8i> select sal, deptno
  2    from emp
  3   where sal >= any ( select sal
  4                        from (select sal
  5                                from emp
  6                               order by sal desc)
  7                       where rownum <= 2 )
  8   order by deptno, sal
  9  /

       SAL     DEPTNO
---------- ----------
       200         10
       200         10
       200         10
       300         10

Review &
      Followup

      Rating:
      5
  April 09, 2002
Reviewer:
      A reader
what is the significance of any in the above query, what will happen if we
remove  it.

          Followup:

If you remove it, the query fails in most cases:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select sal, deptno
  2    from emp
  3   where sal >=        ( select sal
  4                        from (select sal
  5                                from emp
  6                               order by sal desc)
  7                       where rownum <= 2 )
  8   order by deptno, sal
  9  /
 where sal >=     ( select sal
                    *
ERROR at line 3:
ORA-01427: single-row subquery returns more than one ro

the any keyword does exactly what it sounds like it does --

where sal >= ANY ( subquery )

says when SAL is greater than or equal to ANY of the values in the subquery.

My subquery generated a set, I wanted all salaries greater then or equal to any
of the values in that set.

      Rating: 4
Performance -
      Top N Queries  April 09, 2002
Reviewer:  Rich
      Sekunda  from New Jersey
There always seems to be several approaches to writing an SQL statement but I've
become a big fan of analytic funtions. The following works well too.

select *
FROM (select annual_sal_amt,
             deptid,
             dense_rank()
             over (partition by deptid
                   order by annual_sal_amt desc) dr
             from offer
             where annual_sal_amt > 0 )
where dr <= 2
order by deptid,
         annual_sal_amt desc

          Followup:

absolutely (this question was from 815 -- predated analytic functions, new with
816 and up)

      Rating: 5
why did you
      choose ANY instead of in  April 12, 2002
Reviewer:
      A reader
Look at the following...

  1  select sal, deptno
  2     from emp
  3     where sal in  ( select sal
  4                         from (select sal
  5                                  from emp
  6                                 order by sal desc)
  7                         where rownum <= 2 )
  8*   order by deptno, sal

      SAL    DEPTNO
--------- ---------
     5000        10
     3000        20
     3000        20
Wrote file afiedt.buf

  1  select sal, deptno
  2     from emp
  3     where sal >= any  ( select sal
  4                         from (select sal
  5                                  from emp
  6                                 order by sal desc)
  7                         where rownum <= 2 )
  8*   order by deptno, sal

      SAL    DEPTNO
--------- ---------
     5000        10
     3000        20
     3000        20

The result is the same when you used ANY  or when I used IN .

Why did you choose ANY over IN.

Whose performance is better.

          Followup:

benchmark it to see which is faster.

I suppose at the moment I wrote it, >= ANY fit in with my thinking better then
in did.

I generate the set of sals in descending order (should have added NULLS LAST to
that in retrospect)...

I pick off the first two...

Now I want anyone that makes a salary >= any of those salaries....

I wrote the sql as I thought the problem out -- the sql is a literal translation
of thw way I thought about getting the answer.

Besides, how many times do you see someone use ANY or SOME?

      Rating:
      5
Interesting  April 12, 2002
Reviewer:
      A reader
as you have said

'Besides, how many times do you see someone use ANY or SOME?  '

can you show when and how to use SOME,    and

Why do you think developers never use ANY or SOME( these things are never even
asked in any interviews)

          Followup:

They are pretty much the same as "in" or "=" in most general cases.

Can be useful for this -- find me all of the items such that the price is
greater then ANY/SOME (they are synonymous) of the prices of the items in
department x

select *
  from items
 where price > ANY ( select price from items where department = 'X' )

it probably more intuitive than:

select *
  from items
 where price >= ( select min(price) from items where department = 'X' )

The fact is, you can pretty much do all of the ANY/SOME processing with IN or =
(combined with min/max).  Hence it is not used very often.

along the same lines -- how many people know about

o having
o union all (vs union and what the difference is)
o minus
o intersect

and so on (analytic functions, NULLS first, NULLS last, update a join, and so on
and so on and so on)...

I ask this question in interviews.  You have a table:

create table t ( .....,  month number, ..... );

Month is always a number between 1 and 12.

I ask three questions:

1) how many rows are in the table

2) how many rows BY MONTH are in the table (i want to  know how many rows for
month one, month two and so on)

3) what MONTH has the most rows (and for a special bonus, tell me why this
question is ambigous)

You would be surprised how many people struggle with #1 -- let alone #2.  I've
had people YELL at me that #3 cannot possibly be done in a single query, no
doubt about it.  They get really dejected when I put up at least three different
answers before asking if they would like more.  I don't expect people to get #3
-- it's a tricky one, mostly want to see how they handle the pressure.  It's #2
and #1 that gets me -- most people who rated themselves 8 or 9's (i have them
rate their sql ability on a scale of 1..10 -- higher is better) *cannot do it*.
I have them re-rate themselves after the three questions.  It's interesting.

(my favorite answer to question #3 involves using a HAVING clause with ">= ALL"
in it -- not any, not some, but ALL...)

      Rating:
      1
  April 12, 2002
Reviewer:  IS
       from Kansas City
I think the question was whah are the top 2 salaries for each department. I
think you answered what are the top two salaries in all the departments. Within
the realms of SQL I am not sure if there is a better way than being used. If
there is I would love to know.

          Followup:

Look at that -- no one pointed that out yet.  You are correct, i read the
question wrong.  The answer to that is:

select *
  from ( select sal, deptno, row_number() over ( partition by deptno
                                                 order by sal desc NULLS LAST )
rn
           from emp
       )
where rn <= 2;

thanks!

      Rating:
      5
  September 10, 2003
Reviewer:
      A reader
Version 8.1.7.4

Tom,

I have a table like this

T1
==
c1 int
c2 varchar2(10)
c3 varchar2(10)

this table has 100,000 records.

Now,user can enter any number from 1 to 6.
lets say the user enters 6.What i want to do is divide
ceil(100000/6)
that returns 16667.

That is like 6 sets of 16667 round about ( last set might have less records).

I want to write an update statement like this

   for the 1st set of 16667, c1 should be 1
   for the 2nd set of 1667, c1 should be 2
   for the 3rd set of 1667, c1 should be 3
   .....
   ..
   for the 6th set...      c1 should be 6

can this be done in sql without writing plsql.

Thanks.

          Followup:

ntile does that.

  1* select ename, ntile(6) over (order by ename) from emp
scott@ORA920LAP> /

ENAME      NTILE(6)OVER(ORDERBYENAME)
---------- --------------------------
ADAMS                               1
ALLEN                               1
BLAKE                               1
CLARK                               2
FORD                                2
JAMES                               2
JONES                               3
KING                                3
MARTIN                              4
MILLER                              4
SCOTT                               5
SMITH                               5
TURNER                              6
WARD                                6

14 rows selected.

      Rating: 5
You are the
      best..  September 10, 2003
Reviewer:
      A reader
Thanks Tom!!

      Rating: 3
Regarding your
      3 interview questions  September 11, 2003

Reviewer:  Asim  from Pakistan
Hi,

Regarding your 3 interview questions earlier in this
discussion.

Why people find the Q.2 difficult? isnt it a simple
GROUP BY query, and GROUP BY is very commonly used unlike
ANY or SOME. Isnt this simple query is the answer.

SELECT MONTH, COUNT(*) FROM T GROUP BY MONTH

And about question 1, i am really surpriced how can
one tell that how many rows are there in the table unless
he executes SELECT COUNT(*) FROM T. and even this query
doesnt have any uncommonly used clause.

Do you mean to say that even
these simple queries are uncommon
amongs the people.

OR

am I misunderstanding the whole thing??????

          Followup:

yes, it is that simple.

yes, many people cannot do it. (really, trust me, starting asking it in
interviews and see what happens yourself)

you are not misunderstanding at all.

it is as simple as it sounds, yet apparently "really hard"

      Rating:
      5
  September 11, 2003
Reviewer:
      A reader
Tom,

I tried updating using ntile with a 15 records table t1,but all records of c1
were updated to 1.

SQL>update t1 a set c1 = ( select ntile(6) over (order by c1 ) from t1 where
t1.rowid=a.rowid )

15 rows updated.

SQL>select c1 from t1;

c1
--
1
1
1
1
1
..
..
15 rows selected.

          Followup:

well, ntile is being evaluated how there?

for a single row at a time.  not for the entire set.

you didn't ask "how do i update a result set like this".  ntile works nicely on
retrieval -- but not for update.

ops$tkyte@ORA920> create table emp as select ename, 0 xxx from scott.emp;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> update emp
  2    set xxx = trunc( rownum / (select count(*)/6 from emp ) - 0.0001 )+1
  3  /

14 rows updated.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from emp;

ENAME             XXX
---------- ----------
SMITH               1
ALLEN               1
WARD                2
JONES               2
MARTIN              3
BLAKE               3
CLARK               3
SCOTT               4
KING                4
TURNER              5
ADAMS               5
JAMES               6
FORD                6
MILLER              6

14 rows selected.

would be one approach -- but best to just retrieve this value using ntile

      Rating: 4
I would like
      your opinion about this query please  September 11, 2003

Reviewer:  A reader
SELECT
         employees.USID,
         review_status,
         jobs.ID,
         jobs.STATUS,
         JOB_JAVA_PKG.GET_CHEMIST(jobs.ID) ASSIGNED_TO,
         jobs.TEST ,
         JOB_JAVA_PKG.get_review_status(jobs.ID) REVIEW_STATUS,
         jobs.CREATED_BY ,
         jobs.REASON_FOR_CHANGE,
         DECODE(STATUS,'CANCELLED',jobs.MODIFIED_BY,NULL) MODIFIED_BY
    FROM JOBS, job_chemists, employees, enotebook_reviews
    where jobs.id = job_fk_id
    and job_chemists.RECORD_STATUS='CURRENT'
    and enotebook_reviews.RECORD_STATUS='CURRENT'
    and job_chemists.employee_fk_id = employee_id
    and enotebook_reviews.RECORD_STATUS= jobs.record_status

          Followup:

i don't like the indentation, i code like this:

select c1,
       c2,
       c3
  from t1, t2, t3
 where x = y
   and z = a
/
      ^
      ^
      |
  i like the nice "alley"

other then that, looks dandy...

as long as it answers your questions?  couple of cartesian products in there
which is suspicious but legitimate.

oh wait, i see "java" in there -- hmmm, probably don't need that, just use SQL.

September 11, 2003 Nag -- Thanks for the question regarding "Select
      a date value in a date column in a different format", version 8.1.7

      You Asked
SQL> DROP TABLE T;

Table dropped.

SQL> create table t( x date);

Table created.

SQL> insert into t values('01-JAN-2003');

1 row created.

SQL> commit;

Commit complete.

SQL> select to_char(x,'mm/dd/yyyy') from t;

TO_CHAR(X,
----------
01/01/2003

SQL> select to_date(to_char(x,'mm/dd/yyyy'),'mm/dd/yyyy') from t;

TO_DATE(T
---------
01-JAN-03

*******I WANT TO SELECT THE DATE VALUE FROM THE DATE COLUMN IN THE 'MM/DD/YYYY'
FORMAT. BUT I'M NOT BEING ABLE TO DO SO. I'M CONVERTING IT FIRST INTO CHAR , AND
THEN CONVERTING IT INTO DATE ONCE AGAIN INTO A DATE WITH 'mm/dd/yyyy' FORMAT
,BUT IT IS NOT WORKING .. WHY?

--NEITHER DOES WORK THE BELOW

SQL> select to_date(x,'mm/dd/yyyy') from t;
select to_date(x,'mm/dd/yyyy') from t
               *
ERROR at line 1:
ORA-01858: a non-numeric character was found where a numeric was expected

NOW i'm trying to convert  a date stored in a varchar2 column as a date and I'm
having the problem again..

SQL> DESC T;
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 X                                                  VARCHAR2(10)

SQL> SELECT * FROM T;

X
----------
O1-JAN-02

SQL> SELECT to_date(x,'mm/dd/yyyy') FROM T ;
SELECT to_date(x,'mm/dd/yyyy') FROM T
               *
ERROR at line 1:
ORA-01858: a non-numeric character was found where a numeric was expected

      and we said...
You are misunderstanding.

A date is stored in an internal 7 byte format that the machine likes to use
(bits and bytes).

For us poor humans, who cannot read the bits and bytes -- we have date formats.
The formats are EDITS applied to the bits and bytes that convert them from the
internal format (a DATE) to a string so we can read them.

The dates are not STORED using that format -- the format is used only for
DISPLAY when you select it out.  All dates are stored in a 7 byte machine format
(and in 9i, a timestamp might take 11 bytes for timezones and fractional
seconds).

So, ALL YOU NEED TO DO (after you stop shouting) is

select to_char( x, 'mm/dd/yyyy' ) from t;

When you insert, simply "insert into t values ( to_date( SOME_STRING,
'mm/dd/yyyy' ) )"

You can change the default date format using alter session set
nls_date_format='mm/dd/yyyy';  but I would strongly DISCOURAGE that.  You should
use explicit formats upon insert/update and select -- that way, you know exactly
what you'll get and in case someone else changes the session settings, you won't
be affected AT ALL.
Review & Followup

      Rating: 5
Sorry for
      shouting .. but   August 25, 2002
Reviewer:
      Nag
I have no control over how users insert.. what I want to see is how I can
convert

select to_char( x, 'mm/dd/yyyy' ) from t;  into  a date with the date format
mm/dd/yyyy

is that possible ?

          Followup:

I told you how.  alter session.  See also

http://asktom.oracle.com/~tkyte/Misc/NLSDateFormat.html
there you go -- and guess what, you DO in fact have control over how they insert
-- as their inserts will NOT work in any arbitrary format (and lack of access is
the ultimate in control).  Hence, they will either need to alter session (bad
idea) OR they will need to say what they mean (always a good idea)

      Rating:
      5
Clarification  February 08, 2003
Reviewer:
      Nasser
Tom

So if the Nls_date_format is set to 'dd-mon-yy' , not matter how you enter the
date it will insert it in the same format i.e. dd-mon-yy..

Is that what you are tryig to say.. I have tested this as below..

SQL> create table t( x date);

Table created.

SQL> insert into t values(sysdate);

1 row created.

SQL> commit;

Commit complete.

SQL> select * from t;

X
---------
08-FEB-03

SQL> insert into t values(to_date('02/08/2003', 'mm/dd/yyyy') );

1 row created.

SQL> commit;

Commit complete.

SQL> select * from t;

X
---------
08-FEB-03
08-FEB-03

SQL> insert into t values(to_date('2003/02/07','yyyy/mm/dd'));

1 row created.

SQL> commit;

Commit complete.

SQL> select * from t;

X
---------
08-FEB-03
08-FEB-03
07-FEB-03

So now if I want to start storing the date in my datbase in 'yyyy/mm/dd' format
then I should set my nls_date format to 'yyyy/mm/dd.

          Followup:

No, not at all:

<quote>
A date is stored in an internal 7 byte format that the machine likes to use
(bits and bytes).

For us poor humans, who cannot read the bits and bytes -- we have date formats.

The formats are EDITS applied to the bits and bytes that convert them from the

internal format (a DATE) to a string so we can read them.
</quote from above...>

the format string is just an edit, just an edit -- a format, nothing more,
nothing less.

the date always has  century, year, month, day, hour, minute, second --
regardless of the format used to insert it or retrieve it.

      Rating: 5
Got
      you...  February 08, 2003
Reviewer:  Nasser

SQL> alter session set nls_date_format='yyyy-mm-dd' ;

Session altered.

SQL> select * from t;

X
----------
2003-02-08
2003-02-08
2003-02-07

insert into t values(to_date('02/08/2003', 'mm/dd/yyyy') );

SQL> select * from t;

X
----------
2003-02-08
2003-02-08
2003-02-07
2003-02-08

How can I know the nls_date_format of our database?

      Rating: 4
7 byte
      format  February 08, 2003
Reviewer:  Nasser

Tom

SQL> select length('dd-mon-yyyy') from dual;

LENGTH('DD-MON-YYYY')
---------------------
                   11

A dates internal format should take 11 bytes. How come according to you it is
only 7 bytes...

What is the format which enables storage of date in  7 bytes, please give
insight into it...

      Rating: 5
7
      bytes  February 08, 2003
Reviewer:  Tony
      Andrews  from London, UK
Nasser, that's really funny!  But you could have gone further:

SQL> select to_char( sysdate,'DAY DD MONTH YYYY HH:MM:SSAM') from dual
  2  /

TO_CHAR(SYSDATE,'DAYDDMONTHYYYYHH:MM:S
--------------------------------------
SATURDAY  08 FEBRUARY  2003 11:02:27PM

SQL> select length(to_char( sysdate,'DAY DD MONTH YYYY HH:MM:SSAM')) from dual
  2  /

LENGTH(TO_CHAR(SYSDATE,'DAYDDMONTHYYYYHH:MM:SSAM'))
---------------------------------------------------
                                                 38

And it STILL fits into 7 bytes!!!  I guess that's the difference between
INTERNAL formats and EXTERNAL formats for you!

      Rating: 5
Dear
      Tony  February 08, 2003
Reviewer:  Nasser

My question in brief was...

What is the internal format in which the date is stored? and I was just giving
an example.

      Rating: 4
Full
      Format  February 09, 2003
Reviewer:
      LakshmiNarasimhan  from Dubai
I believe Oracle will store every portion of date ,

The Year (Full year)
Month
Date
Time (AM / PM indicator or 24 hours format)

Am I rigt Tom?

Regards

      Rating: 5
Date
      format  February 09, 2003
Reviewer:  Tony
      Andrews  from London, UK
Nasser,

Sorry my previous answer was a little "tongue in cheek"!

Oracle DATE columns are stored internally as the number of seconds since some
date way in the past.  We have 7 bytes to store it in - how big is that?
1 byte = 8 bits.  This can store numeric values up to (2**8)-1 = 255
7 bytes = 56 bits.  This can store numeric values up to (2**56)-1 = 7.2058E+16,
i.e. 72,058,000,000,000,000

That is the number of different DATE values, in seconds, there can be.  How many
years is that?

7.2058E+16 / 60 / 60 / 24 / 365 = 2,284,931,318

i.e. 2 billion years.

So with a 7-byte DATE column you could in theory record any date and time (to
the second) from around 1 billion BC to around 1 billion AD.

I'm not saying that's EXACTLY how Oracle dates work, but it is similar, and
shows how they could easily accomodate all the dates you'll ever need in 7 bytes
or less.

          Followup:

see

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/c10datyp.htm#796
it is not the number of seconds since some epoch - but rather the

1 century
2 year
3 month
4 day
5 hour
6 minute
7 second

http://otn.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76975/oci03typ.htm#421890
has the exact layout/details.

      Rating:
      5
  February 10, 2003
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating: 5
Get ready all
      you consultants!!!  February 10, 2003
Reviewer:
      John  from MA
The year 2000 problem WILL happen again and we'll call it the year 4712 problem.

      Rating: 5
Date format
      chaning query results  September 11, 2003

Reviewer:  A reader
The date format 'MM RRRR' is returning wrong results where as 'RRRRMM' returning
the correct
data. Can you please explain as to why this is happening. Is there a more
efficient way of doing the date comparision for the given case.

SQL> DESC TEST;
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 ID                                        NOT NULL NUMBER
 BEGIN_DT                                           DATE
 END_DT                                             DATE
 VOL                                                NUMBER

SQL> SELECT * FROM TEST;

        ID BEGIN_DT  END_DT           VOL
---------- --------- --------- ----------
         1 26-FEB-03 31-DEC-03
         2 01-JAN-03 31-DEC-07        100
         3 01-JAN-03 31-DEC-03          3
         4 01-JAN-04 31-DEC-08        400
         5 01-JAN-03 31-DEC-04         80
         6 01-JAN-05 31-DEC-14       1000
         7 01-MAY-03 30-SEP-03        100

7 rows selected.

SQL>   select  *
  2            from TEST
  3            where TO_CHAR(BEGIN_dT,'MM RRRR') <=  TO_CHAR(SYSDATE, 'MM RRRR')
  4               and  TO_CHAR(end_dT,'MM RRRR') >= TO_CHAR(SYSDATE, 'MM RRRR')

  5  /

        ID BEGIN_DT  END_DT           VOL
---------- --------- --------- ----------
         1 26-FEB-03 31-DEC-03
         2 01-JAN-03 31-DEC-07        100
         3 01-JAN-03 31-DEC-03          3
         4 01-JAN-04 31-DEC-08        400
         5 01-JAN-03 31-DEC-04         80
         6 01-JAN-05 31-DEC-14       1000
         7 01-MAY-03 30-SEP-03        100

7 rows selected.

SQL>  select  *
  2            from TEST
  3            where TO_CHAR(BEGIN_dT,'RRRRMM') <=  TO_CHAR(SYSDATE, 'RRRRMM')
  4               and  TO_CHAR(end_dT,'RRRRMM') >= TO_CHAR(SYSDATE, 'RRRRMM')
  5  /

        ID BEGIN_DT  END_DT           VOL
---------- --------- --------- ----------
         1 26-FEB-03 31-DEC-03
         2 01-JAN-03 31-DEC-07        100
         3 01-JAN-03 31-DEC-03          3
         5 01-JAN-03 31-DEC-04         80
         7 01-MAY-03 30-SEP-03        100

          Followup:

it is giving you 110% what you asked for.

think about it.

think "strings"

you do not have dates -- you have two strings.

tell me, which is "bigger"

12 xxxx
01 xxxx

??? hmmm, now what about

12 1902
01 2001

which is bigger?  (same answer, they are STRINGS not dates!!!!!)

you have nice DATES!!! why -- why -- why would you convert them into strings?!?!

where begin_dt < add_months(trunc(sysdate,'mm'),1)
  and end_dt >= trunc(sysdate,'mm')

that is what you want.

give me everything where the begin_date happens this month and before AND
end_date is in this month or later.

September 11, 2003 Marcio -- Thanks for the question regarding
      "Develop Environment", version 920

      You Asked
Tom, lucky day -- My birthday :) Well, always talk to you is gift.
Lets a question:

I need setup a develop environment like:

1 DBA
1 DA
8 Java Developers (btw -- they don't like pl/sql)
1 team leader
6 testers

To do that i have 1 server. We'll build a software database based to
Telecommunications.

I'm thinking in: 6 schemas
Dev_data, Dev_source
Tst_data, Tst_source
Prd_data, Prd_source

Where

*_data is under DBA control. Here I'd think put just data objects following an
of your advises, and create synonym to *_source to some privs.

Dev_source is under developers control and would put synonym objects (tables,
sequences, etc) into dev_data with select/insert/update/delete as needed.

Tst_source as well as dev_source but under control to tester and

Prd_source as well as tst_source under control of system.

Now, I can't close thing yet. Does this configuration work? I mean would you
suggest best setup to this environment?

Thks alot
Marcio

      and we said...
you're doomed if they "dont like plsql".  But anyway.  Let's hope at least they
"appreciate bind variables" -- if they don't, you are truly utterly doomed!

yes, it works -- it works even better when they figure out "hey, we can put some
stored procedures in dev_source and really go fast, both coding wise and
performance/scalability"

Review &
      Followup

      Rating: 5
They don't like
      either  August 15, 2003
Reviewer:  Marcio
       from Br
The last discution with them was about 2 issues:

o turn off autocommit on drive jdbc
o PLEASE use Bind Variable

The battle is up! At least autocommit is turned off.

Thanks,
Marcio

          Followup:

i fear for you.

if they do not use binds, you are hopelessly DEAD IN THE WATER.

      Rating: 5
i don't get
      it  August 15, 2003
Reviewer:  a dba
       from USA
I deal with this all the time. They take no time to code with the database and
use binds, but they find all the time in the world to complain that the DB is
'slow'. To a crawl. To a stop. Then they say they "found" the solution:

alter system flush shared_pool ;

I've shown the examples, i've shown the proof in REAL 'Tom-like' fashion - in
real SQl*Plus sessions - but it just does not sink in.
If the query takes too long, the fix is to slap a GUI on it with a message box
that says "This might take a few minutes.."

still shaking my head after all these years -
-realized I can't change others

          Followup:

in my next book -- i give the performance reason AND the security reason.

ask them if they ever heard of

"sql injection"

and if not, goto google and see what happens when they search for that.  see if
they can comprehend the implications.  if not, time to hire new programmers for
the ones you have -- have no clue.

funny thing is it is EASIER to code with binds then without!  just try to search
for

Ben and Jerry's

using their stuff -- how much code did *they* have to write to glue that string
in?  bind -- done.

      Rating: 5
I will sing to
      that.  August 15, 2003
Reviewer:  onegooddab
       from USA
Misery loves company and I am glad I am not alone :)

      Rating: 5
a developer
      point of view  August 15, 2003
Reviewer:
      A reader
I can totally understand the frustration - before
having known Tom's work - I was one of the
developers who had no idea about the powers of database.
I remember in one of our projects we were caching huge
records in session and when someone (another developer) suggested that we should
directly query everytime
from the db) - I was aghast. However, when the developer
implemented the solution and it worked fast (with less
session memory consumption) - I was really surprised!:) Anyways, I think I had a
complete overhaul in my outlook once I read expert one on one (I am reading it
for the second time now) and also now ) I visit Tom's site every day! Strangely,
now I find myself in the position of convincing people of simple things such as
using FK cascade deletes - using plsql more often etc..
At least I feel comfortable that I am on the right side
now.

Thanx to Tom !!!

          Followup:

just feel the force luke...

      Rating: 4
Speaking of
      BV.....  August 15, 2003
Reviewer:  Robert
       from PA
>>"appreciate bind variables" -- if they don't, you are truly utterly doomed!

Tom,
I am working on this part of a program to build an ARRAY of dynamic SQLs -
except the WHERE clause, don't know nothing about the SQL built on the fly based
on metadata.

I then fire the SQL in sequence...

I'm going down the path of DBMS_SQL...(need to know the column names)
Now in terms of binding in WHERE clause I doubt I can make the program work with
DBMS_SQL.BIND_VARIABLE...
My question is : Performance wise, do you think using SYS_CONTEXT is just as
good & effective as using DBMS_SQL.BIND_VARIABLE otherwise ?

Thanks

          Followup:

in sql, sys_context is as effective as binding, yes.  you don't want to call
sys_context heavily from PLSQL -- but from SQL, go for it.

      Rating:
      5
Clarification...  August 15, 2003

Reviewer:  Kamal Kishore  from New Jersey, USA

<quote>
>> but from SQL, go for it.
</quote>

You mean SQL from within PL/SQL?
Thanks,

          Followup:

no, mean just "sql" -- where ever.

      Rating:
      5
Roles  August 19, 2003
Reviewer:
      Marcio  from BR
Shall I create a role to my_DBA instead to use DBA from default database as well
as RESOURCE? Any reason if so?

Is "CREATE ANY PROCEDURE" danger? Why? I mean, don't want to see you break or
crack any account, but I would like to know how would be done to protect myself.

thks, Marcio

          Followup:

sure, my_dba would be better security wise then DBA since you control what
MY_DBA can do (and dba is very very very powerful)

with create any procedure, I can create code in sys's or system's schema -- i
can RECREATE code in their schemas.  bad idea.

      Rating: 5
SQL
      Injection  August 19, 2003
Reviewer:  Marcio
       from Br
Don't get that -- why does sql injection cannot be used when bind variable is
coded?

And... I did some example of sql injection to my team check out... funny thing!
;)

Thks,
Marcio

          Followup:

because

select * from emp where ename = :x

will be select * from where ename = :x regardless of what :x is..

select * from emp where ename = || some string

is whatever some_string wants it to be.

      Rating: 4
Not as
      effective from PL/SQL  August 20, 2003
Reviewer:
      Tom  from Florida, USA
Tom, you wrote
<in sql, sys_context is as effective as binding, yes.  you don't want to call
sys_context heavily from PLSQL -- but from SQL, go for it.>

Can you please explain your reasoning for this statement?

          Followup:

each sys_context call involves recursive SQL (it has to be selected -- it is not
a plsql function, it is a sql call).  hence, if you reference sys_context over
and over and over and over and over in your PLSQL, it is quite expensive.

In a SQL query however -- select * from emp where empno = sys_context( 'x','y' )
-- sys_context is invoked once, in a very inexpensive way (you are ALREADY in
the sql engine) and its return value is bound into the statement.  so, even if
empno were not indexed and there were a billion rows to compare to --that would
be just like "select * from emp where empno = :x"

Consider:

ops$tkyte@ORA920> create or replace procedure p1
  2  as
  3  begin
  4      for i in 1 .. 100000
  5      loop
  6          if ( sys_context( 'userenv', 'current_schema' ) = 'SCOTT' )
  7          then
  8              null;
  9          end if;
 10      end loop;
 11  end;
 12  /

Procedure created.

ops$tkyte@ORA920> create or replace procedure p2
  2  as
  3      l_current_schema  varchar2(100);
  4  begin
  5      l_current_schema := sys_context( 'userenv', 'current_schema' );
  6      for i in 1 .. 100000
  7      loop
  8          if ( l_current_schema = 'SCOTT' )
  9          then
 10              null;
 11          end if;
 12      end loop;
 13  end;
 14  /

Procedure created.

Now, using runstats:

ops$tkyte@ORA920> exec runStats_pkg.rs_start

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec p1

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runStats_pkg.rs_middle

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec p2

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runStats_pkg.rs_stop
Run1 ran in 3552 hsecs
Run2 ran in 11 hsecs
run 1 ran in 32290.91% of the time

that is HUGE right?  it is even more "huge"

...
TAT...recursive cpu usage           2,140           4      -2,136
STAT...CPU used by this sessio       2,945           8      -2,937
STAT...CPU used when call star       2,945           8      -2,937
STAT...Elapsed Time                  3,582          22      -3,560
STAT...table scan blocks gotte     100,000           1     -99,999
STAT...table scan rows gotten      100,000           1     -99,999
STAT...table scans (short tabl     100,000           1     -99,999
STAT...execute count               100,088          11    -100,077
STAT...no work - consistent re     100,151          11    -100,140
STAT...buffer is not pinned co     100,301          21    -100,280
LATCH.shared pool                  101,198         331    -100,867
STAT...recursive calls             101,372          38    -101,334
LATCH.library cache pin            200,726         169    -200,557
LATCH.library cache                201,401         432    -200,969
STAT...calls to get snapshot s     300,094           9    -300,085
STAT...consistent gets             300,394          15    -300,379
STAT...session logical reads       300,931         546    -300,385
LATCH.cache buffers chains         603,270       2,649    -600,621

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
1,109,571       4,421  -1,105,150 25,097.74%

PL/SQL procedure successfully completed.

the first implementation shows you want to watch your calls to sys_context in
plsql.  don't use sys_context as a general purpose replacement for variables if
you are going to access them heavily.

In SQL -- they work wonderfully.  that is what they were designed primarily for.

      Rating: 3
Please explain
      use of sys_context   August 20, 2003
Reviewer:
      Nick  from NYC
Tom,
Can you please expand on the use of SYS_CONTEXT as mentioned here?

Thanks a million.

      Rating: 5
for
      Nick  August 20, 2003
Reviewer:  Riaz Shahid
       from PRAL, LAhore, Pakistan
Nick !

Here are quick links for sys_context....the documentation:

http://otn.oracle.com/doc/oracle8i_816/appdev.816/a76939.pdf
http://otn.oracle.com/doc/oracle8i_816/server.816/a76989.pdf
http://otn.oracle.com/docs/products/oracle9i/doc_library/901_doc/appdev.901/a88876/adgsec02.htm

Regards

      Rating: 3
for Nick
      2  August 20, 2003
Reviewer:  robert
       from PA
>>Nick !
>>Here are quick links for sys_context....the documentation:

or better...with examples & expert insights etc. - just search this site for
"sys_context"

      Rating: 5
About PUBLIC
        August 20, 2003
Reviewer:  Marcio
       from Br
Tom, about original topic, I would like to know about PUBLIC, can I use it? or
shall I create role my_public?

I want to keep my privileges as simple as possible, so am thinking use this kind
of stuff

ops$marcio@MARCI9I1> create table t ( x int );

Table created.

ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1> create or replace procedure t_ins ( p_number in number )
  2  as
  3  begin
  4          insert into t values ( p_number );
  5          commit;
  6  end;
  7  /

Procedure created.

ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1> create public synonym t for t;

Synonym created.

ops$marcio@MARCI9I1> create public synonym t_ins for t_ins;

Synonym created.

ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1> grant execute on t_ins to public;

Grant succeeded.

ops$marcio@MARCI9I1> grant select on t to public;

Grant succeeded.

ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1> grant create session to a identified by a;

Grant succeeded.

ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1> @conn a/a
ops$marcio@MARCI9I1> set termout off
a@MARCI9I1> set feedback on
a@MARCI9I1> get afiedt.buf nolist
a@MARCI9I1>
a@MARCI9I1> select * from session_privs;

PRIVILEGE
----------------------------------------
CREATE SESSION

1 row selected.

a@MARCI9I1>
a@MARCI9I1> exec t_ins ( 1 );

PL/SQL procedure successfully completed.

a@MARCI9I1> exec t_ins ( 2 );

PL/SQL procedure successfully completed.

a@MARCI9I1>
a@MARCI9I1> select * from t;

         X
----------
         1
         2

Do you see any problems?

thks, Marcio

          Followup:

use public if it meets your needs, it is fine.  everyone has access to public.

easy also equals "virtually no security" in your case.  watch what you wish for.

      Rating:
      5
  August 21, 2003
Reviewer:  Dave
       from Colorado
Instead of ...

1 DBA
1 DA
8 Java Developers
1 team leader
6 testers

... it would seem that a more suitable blend would be ...

1 DBA
1 DA
4 Java Developers
4 PL/SQL DB developers
1 team leader
6 testers

Does that seem like a better balance?

          Followup:

how about

1 dba
1 da
4 plsql developers
1 team leader
6 testers

:)

      Rating: 5
I want to my
      part $$$  August 22, 2003
Reviewer:  Marcio
       from br
Hey Tom and reviewers what about schedule? -- 4 developers against 8 -- humm...
50%???

Well, If I get rid of 4 developers -- is about some money less. So, I want my
part ;)

Regards,
Marcio

      Rating: 3
Behave wise
      customer problem.  August 23, 2003
Reviewer:
      Marcio  from Br
I don't know if it fits for this thread, but I would like to know your advice.
I've been called some times to customers where meeting the pretty bad (ugly
even) situation. Procedures, when they exist are doing dynamic sql, bind
variable technic is not being used and they do not have idea of it is, the
intelligence of the system is in the application one, at last we would have that
to shoot it out and start over -- rewrite entire system.
Some times the calls are specifics, to fix one definitive problem -- performance
(mainly).
At this point I'd like your opinion.

** Which must be my position (behavior)? How do I must hold me and explain that
the system of the company was drawn of incorrect form and that it will not be
able to get scalability nor the performance that they want? How to do that
whitout loose the customer? Even bad, some times the customer DID write the
application...

          Followup:

I do it via education.

I'm called in frequently in such circumstances.  Bind variables being the killer
one frequently.

A simple, small, test case proves it out.  Sometimes (rare)
cursor_sharing=force|similar can be used as a bandage -- but even that doesn't
fix the problem (you are over parsing in that system still).

but basically, if you tell the people who wrote it and they won't do anything
about it -- you need to talk to the people that manage the people that wrote it
and explain what it is:

a) they did
b) why it is wrong
c) how to fix it

and hope they "make it so" (and then get some database trained people on staff
as well!)

      Rating:
      4
Implementation  August 27, 2003
Reviewer:
      Marcio  from Br
Tom, could you tell me how can i implement this?

as I said, have that team (1 dba, 1 da, ...) and 2 schemas -- dev_data *CREATE
TABLE* and dev_source *CREATE PROCEDURES*

In this implementation I was thinking this:

- source_role (ALTER SESSION, CREATE CLUSTER, CREATE DATABASE LINK, CREATE
INDEXTYPE, CREATE OPERATOR, CREATE PROCEDURE, CREATE SEQUENCE, CREATE SESSION,
CREATE SYNONYM, CREATE TRIGGER, CREATE TYPE, CREATE VIEW )

- source_data (ALTER SESSION, CREATE TABLE)

- test_role (SELECTS, EXECUTES...)

- creates tables on dev_data and give out permission to dev_source.

Questions:

1) To figure out with procedures, do I have to give INDIVIDUAL OBJECTS _grants_
to anyone who will make procedures or it is possible use role?

2) Shall I create one user to one team member? And so, how does look like
grants? And how to use roles to improve productivity set up of news developers?

3) Do I must create synonymous for that environment?

Best regards,
Marcio

          Followup:

1) to create a compiled stored object, the owner of the compiled stored object
(procedure, view, trigger...) must have been granted access directly to the
object.  DEV_SOURCE would need grants

2) roles are for managing your hundreds/thousands of end users.  they are not
useful for development.  the way I work it is developers actually have their own
database on their own machine they test/develop against.  they are responsible
for keeping it up to date with the code in "dev".  when they get good code, they
check it in and it gets deployed to "dev" for the others to see.

there is only one dev schema -- it just exists in all databases.

3) i see no need for synonyms

      Rating:
      4
  August 27, 2003
Reviewer:  Vipin
       from NY
Hi Tom,

In your earlier followup you mentioned that SYS_CONTEXT can be costly because of
the context switching between SQL and PL/SQL, when used in PL/SQL.

But in 9i aren't both the engines same (since I am able to do in PL/SQL,
whatever I am able to do in SQL, earlier I had to use an EXECUTE IMMEDIATE.).

So is it that this restrcition no longer exists in 9i? If this restriction is
still there then could you please explain what is the reason for this.

          Followup:

no, they are not the same.

I do not understand your reference to "earlier i have to use an execute
immediate" -- or do you mean that "before there were sql constructs not
recognized by the plsql parser in 8i that are in 9i?"  if so that just is
because in 9i, the plsql engine uses the same sql parser AT COMPILE time -- not
that plsql and sql "are the same"

      Rating: 4
Soft
      parse  September 11, 2003
Reviewer:  Marcio
       from br
Ref Cursor = soft parse each time, so this could become performance issue.
period.
But, you have to work with result set passing through procedures and functions.
I've set my session_cached_cursors to 100 to minimize a soft parse cost, but
it's still a parse.
How do we code to get result sets without have alot soft parses? Maybe a record
type (I've never seem a example for that), in fact didn't see nothing around to
replace refcursor to catch result sets -- refcursor = soft parse and so on...

Thks,
Marcio

          Followup:

you use a result set (ref cursor) to return something to the client.

inside of plsql, you generally don't use them

soooo

if you do your data logic exactly where it 110% belongs (in the database) you'll
find you return a single result set (or two or three) at the end -- and since
you are N-tier (assumption on my part) using a connection pool -- you are
parsing no more or less then you would in any environment.

September 11, 2003 Bhavani -- Thanks for the question regarding
      "Archive Log file", version 8.1.7

      You Asked
Hi Tom,

Recently we Shifted our Database server from one location to other location.
Here is the steps our DBA done.

1) On 4th of April, we exported (Using export and import utilities) all our data
to the New Machine
2) On 6th we shipped our New Machine to the other location
3) on 12th We have taken the Archive Log file from the current server (It is
about 30 GB) and closed down the current server, applied the Archive Log on the
New machine.

But we didn't get all the data till 12th.  There is data only upto 4th of April.
 Then we again manually transferred the data

What might have went Wrong Tom?  Is this the best way for Migration?  Is there
any other best way to Do it.  (My DBA thought, complete Export and Import might
take very long time, may be 1 day.  Thats why he did it like that).

Thank you very much for your valuble answers.

Sincerely
Bhavani

      and we said...
That is the answer then.

EXP is a point in time picture of your database.  It is a logical backup, not a
physical backup.  It is a copy of the data. It is a point in time picture of
your database.

It is physically impossible to do a point in time recovery with that -- once the
imp is done, thats it.

Your DBA isn't much of a DBA I'm afraid.  If he thought:

a) this would be more efficient
b) that you could apply archives to the imported data
c) that he THINKS he did apply archives to that database (that is IMPOSSIBLE,
couldn't have happened!!!  I don't know what he did but there is ZERO chance he
applied any of those archives to this other database)

He should have:

a) made sure the database (old one) is running in archive log mode.
b) made a hot or cold backup of it
c) restored that to the new machine
d) started applying the accumulated logs
e) when he was nearly done -- he would shutdown the old database
f) continue applying the archives to the new one and apply the online redo logs
from the old one
g) open the database

Thats how to do what you are trying.  EXP -- that plays no role in this unless
you:

a) shutdown old
b) startup with restricted session
c) export old
d) shutdown old
e) import into new

Review &
      Followup

      Rating: 5
Great
      Explanation  April 13, 2002
Reviewer:
      A reader
This Answer is really Helpful.  We all think, we know too much.  This shows how
much we actually know.  Thank you very much Tom once again, for your valuble
answers.

      Rating: 3
control
      file  April 14, 2002
Reviewer:
      A reader
what about control file

          Followup:

That would be covered in:

..
b) made a hot or cold backup of it
c) restored that to the new machine
...

Maybe I'm assuming too much but I would have to assume a DBA would know how to
take a hot backup and restore it to another machine, they'll be doing a cancel
based recovery if they do it this way with a non-binary backup of the control
file.

      Rating:
      3
  April 14, 2002
Reviewer:  Dave
       from USA
helo
1  "e) when he was nearly done -- he would shutdown the      old   database
"
means the older data base i s now closed for transactions? or else meaning

2 "EXP is a point in time picture of your database.  It is a logical backup, not
a
physical backup.  It is a copy of the data. It is a point in time picture of
your database."

if this a logical picture but not physical when we import an export file dont we
get a physical data by imp.

          Followup:

means the older database is now closed for business, it is shutdown, no more
work to be done against it -- the end goal here is a switchover.

As for the exp thing -- when I say it is a logical backup - you get a DIFFERENT
but EQUIVALENT database out of it.  It is not the same database as you started.
Oh, it might have the same *data* but it will have the data in different places
on the disk, different rowids.  PHYSICALLY it is not the same database.
Logically is is a full point in time copy of the original database.  You cannot
take archived redo log from the old database and apply to this logical copy.

      Rating:
      4
asktom.com  April 14, 2002
Reviewer:
      liz  from FL
hi tom
1.do yoiu take bakups of asktom database
2.what is the size of your database.
3.you keep archiving mode?
4.how old data we can get from your site

          Followup:

Of course I do.  I have a large amount of time invested in the questions/answers
here.  It would be the biggest mistake in the world NOT to take backups.
Seriously, we run all of our production databases like this in archivelog mode.
Fred (my DBA/SA) runs a hot backup about every other night.

Asktom's database is pretty small, just about 6gig.  But we run 2 other
databases on this machine as well -- and have about 10 other databases here and
there that he manages as well.

We have a nice tape library that all of the machines are hooked into and
practice restores every now and again.

If you click here:

http://asktom.oracle.com/pls/ask/f?p=4950:6
you'll see the data goes back to May of 2000, but it's really back to January
2000 (jan/feb/mar/apr of 2000 we did it via email, thats when I decided to write
the first "asktom" interface on the database.  wasn't scaling up with email)

      Rating: 4
what to do with
      archivelogs  April 14, 2002
Reviewer:  liz
       from FL
hi tom
when we take the fresh backup should we delete the archivelogs till here or
every archive log shoul d always be kept along .what is the rule when should the
archivelogs be deleted

          Followup:

I would suggest you read:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/index.htm
all of the backup and recovery books.

You probably want to use RMAN.  You will want to keep at least 2 of the last
backups available and all of the archives needed to restore each of them fully.
You need AT LEAST two backups because backups are done to tape and tape can go
bad, accidents happen.

You CANNOT take a backup over your last backup (ask yourself, what happens if a
disk fails in the middle of the backup and I'm overwriting my last backup?
well the answer is you just toasted your database, you have no backup now,
cannot complete the current backup, you lose).

I keep the last 5 backups and all archives needed to restore them, the archives
are stored redundantly as well.

      Rating:
      5
archivelogs  April 14, 2002
Reviewer:
      liz  from FL
hi tom
thankyou, reallly we know nothing
but big confusion about keeping archive logs
lets suppose i took bakup on apr 01, then i took a backup on april 05, i keep
only last backup ie apr 05  will i have to keep the archive logs from apr 01 to
curent day.
i ask this because keeping all archivelogs will make heap of them

          Followup:

I won't tell you what HEAP of something you'll be in if you don't get a really
good handle on backup and recovery.

I think I was pretty clear -- that you cannot keep just the last backup, that
you should have redudant copies of archives.

What happens WHEN (not if, WHEN) the backup from april 5'th isn't readable.
You'll really be wanting that April 1'st backup won't you.

You need to budget for this.  You need to buy the hardware for this.

Hopefully you do not need a catastrophic loss of data to convince you of this.

      Rating:
      5
asktom.com  April 14, 2002
Reviewer:
      Mike  from NY, NY
How did you design your application of asktom.com or what kind tool did you use?
Did you use the Oracle Designer? Does an application schema own all of the
application objects? Are the tables partitioned?
Thanks.

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1995355238285
....

no designer (its not a very big app)
no partitioning (the tables are not huge, I've recieved 16,000+ questions to
date)

One schema owns the "application logic" tables (our framework), another schema
owns the "data tables".

      Rating: 5
RE: thank you,
      really we know nothing...  April 14, 2002

Reviewer:  Mark A. Williams  from Indianapolis, IN
      USA
Liz:

I don't know what part of FL you are in, but Oracle have a really nice training
facility right by the airport in Orlando.  Also, the complete set of
documentation is available on OTN for free!

Sorry for sticking my nose in, but it seems ALOT less expensive to pay approx.
$2000 for a nice backup and recovery training course then it would be to lose
production data...

- Mark

      Rating: 3
A
      reader  April 15, 2002
Reviewer:  A reader
       from USA
Hi tom,
   We have a production database and disaster recovery center database.In
production we take daily hot backup
along with archive log files.we ship this tape to
our recovery center(diff location).Now here i have a little
confusion.I don't want to restore daily, the datafiles from the tape in a
recovery database.what i want to do is, let
say today i am setting up the recovery center db.

1)I will take the hotback and archive from production and
apply to it.(first time)

2)Next day when i get next day tape i just want to apply
the new archive from the tape to recovery db and do cancel based recovery .Or i
have to again restore all database files+ controlfile and apply archive ??

3)Is my approach ok of just applying the archive log file
every day and do cancel based recovery??

Your comments please

          Followup:

This is so popular, we have a name for it -- the standby database.

Read:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76995/toc.htm
it does this. (#3 -- no, not OK, you have to either

save all archives and apply them when you failover

use the standby approach)

      Rating:
      4
archivelogs  April 15, 2002
Reviewer:
      ash k  from ca
hello
modifying liz's question
 backup on ,01,05,10,15 of april taken, keep last three backups 05,10,15 today
is 20 april.
we must keep the archivelogs from 01 till today (20 april) am i right or
wrong.or we must keep even older backups.

          Followup:

You need the backups to make the april 5 backup consistent.  That would be the
archives generated during and after the april 5 backup.

April 5 is the answer.

      Rating:
      4
mike  April 15, 2002
Reviewer:  liz
       from FL
mike
let me clear my confusions

      Rating: 5
Cold BackUp and
      Archive Log Files  April 16, 2002
Reviewer:
      Madhu  from India
Hi Tom,

The same thing happened to us long back.  But in our case, we did Cold Backup
and then applied Archive Log fies for 16 days.  But Archive Log files applied
only for the first 4 days, and we don't know what happened, no data is there
from the 5th to 16th day.  But when we applied archive log files, it said
archive log files applied successfully.

We don't know what went wrong.  When we analysed the whole process, on 5th day,
we changed one of the tables Structure.  Because of that Archiving went wrong?
or any other problem might have occred?

Thank you very much Tom.

Madhu

          Followup:

Sorry -- but you are comparing apples to oranges here.  The above (exp/imp)
would not work in any case.  Yours should work in all cases.

Not having the details, I would guess that you used the wrong control files or
did a point in time recovery.  Without seeing what you did, I cannot tell you
what went wrong.

No -- a simple schema change will not affect recovery.

      Rating: 4
Data
      Guard  April 16, 2002
Reviewer:  Irfan
       from Kuwait
Hi Tom,
Why don't we use data guard facility instead of stand by database (for updating
data in a different location)?

Thanks.

          Followup:

data guard = name of standby in 9i.

they are the same.

      Rating: 4
Datagaurd
      ???  April 17, 2002
Reviewer:  A reader

Tom,

From the above Comment.. Do u mean to say there is no ifference between
datagaurd and standby databases.

I Heard something about Always in sync without time lag.

Can u elobrate a Bit.

Thanks.

Regards,
Ganesh R

          Followup:

Data Guard is the 9i name for standby.

A TON of new features were added.

There are many things you can do in 9i data guard that you cannot do in 8i
"standby".

However, data guard is the next generation "standby" -- the names are different,
that is all.  As always, what features you have depends on the version you have.

      Rating: 4
exp/imp works
      as if in archive mode  April 17, 2002
Reviewer:
      Irfan  from Kuwait
Hi Tom,
Sorry but would like to know that our production system is runnig on archive log
and the disaster (under the same roof) is without the archive log. Daily the
cron takes a full export from production and remotely copies to the disaster
box. Then in a cyclic fashion the cron in disaster machine starts importing and
morning we have the updated data in disaster machine. We never applied archived
logs (since disaster is not in archive mode), eventhough we run the application
in disaster but we get the exact data as in production. I really wonder how it
is, though the control file is not at all updated? Please suggest.
Thanks .

          Followup:

You do not have the "exact data as in production".  You have a POINT IN TIME
COPY of the data from production -- the point in time is the time of the actual
export.

Do this -- at 3pm in the afternoon -- look at your "disaster" box, tell me what
would happen if you failed over right then and there.  Would you lose the work
that was performed from 9am to 3pm that day on production?  The answer is YES,
YES you would.   You would have on "disaster" only the data from the last
export, nothing more -- nothing less.

If that satisfies your needs, great.  I myself would feel more comfortable with
a standby instance that is slightly more up to date.

      Rating: 5
A
      reader  April 18, 2002
Reviewer:
      A reader  from US
Hi Tom,
Can we use incremental/Cumulative exp/imp as backup/recover method?  I never use
it.  Oracle 8i documentation mentioned the method and I don't understand well.
I will never use it as backup/recover.  Hoever it's good to know why. Can you
please explain why we can or cannot use it or in what cases we can use it.  The
following are copied from the documentation.
"An incremental export extracts only tables that have changed since the last
incremental, cumulative, or complete export. Therefore, an import from an
incremental export file imports the table definition and all of its data, not
just the changed rows.
It is important to note that, because importing an incremental export file
imports new versions of existing objects, existing objects are dropped before
new ones are imported. This behavior differs from a normal import. During a
normal import, objects are not dropped and an error is usually generated if the
object already exists."

Thanks a lot!

          Followup:

EXP is a logical copy.  It is not a physical backup.

You cannot do a full recovery using EXP -- period.  It is a point in time copy.
nothing more, nothing less.

doing a backup ,a real honest backup, is

a) easier
b) faster
c) better

then exp.  exp is (in my opinion) NOT A BACKUP TOOL.  Backups mean *to me* that
I can restore and then roll forward using the archives.  YOu cannot do that with
exp.

      Rating: 3
A
      Reader  April 18, 2002
Reviewer:
      A reader
I guess the case at the beginning of this topic, they may use incremental export
because the old server was shut down in the last day.  Couldn't they?  I will
vever use incremental/cumulative exp/imp as backup/recover tool. That does not
mean in no case it should be used.
Thank you for all the excellent answers!

          Followup:

Well, the documentation does say -- it'll not be supported for very long so
don't get used to it (incrementals with exp/imp, not the tool itself)

      Rating: 4
Archive Log
      Sizes  April 23, 2002
Reviewer:  Irfan
       from irfan_pk@hotmail.com
Tom, Below is the archive log listing of our three machines running OPS 734.

Node 1
-------
-rw-r-----   1 oracle   dba      4196352 Apr 23 11:02 NODE1_9851.log
-rw-r-----   1 oracle   dba      4196352 Apr 23 10:55 NODE1_9850.log
-rw-r-----   1 oracle   dba      4196352 Apr 23 10:47 NODE1_9849.log
-rw-r-----   1 oracle   dba      4196352 Apr 23 10:37 NODE1_9848.log

Node2
-------
-rw-r-----   1 oracle   dba         6144 Apr 23 11:00 NODE2_3755.log
-rw-r-----   1 oracle   dba         4096 Apr 23 10:33 NODE2_3754.log
-rw-r-----   1 oracle   dba        34816 Apr 23 10:06 NODE2_3753.log

Node3
-------
-rw-r-----   1 oracle   dba         6144 Apr 23 10:56 NODE3_4357.log
-rw-r-----   1 oracle   dba        14336 Apr 23 10:28 NODE3_4356.log
-rw-r-----   1 oracle   dba         6144 Apr 23 10:01 NODE3_4355.log

I would like to know:
a) Why all the Nodes doesn't have the same archive log sizes.
   From v$log I get the bytes =  4194304 bytes (for all)
   Particularly Node2 and Node3 are not at all uniformed comparing to Node1.
b) From v$log is 4194304 bytes, why Node1 log sizes are 4196352 bytes.
c) For Node1 when I execute:

SVRMGR> archive log list
Database log mode                 Archive Mode
Automatic archival                Enabled
Archive destination               /oracle/admin/PKS/archive/NODE
Oldest online log sequence        9850
Next log sequence to archive      9852
Current log sequence              9852

 - Current log sequence is 9852, where as for the listing above for Node1, the
current file the log is being written is 9851. What is the logic behind this?
d)Yesterday's last archive log and today's first archive log(Apr 22 & Apr 23)

-rw-r-----   1 oracle   dba      4196352 Apr 23 07:21 NODE1_9833.log
-rw-r-----   1 oracle   dba      3430400 Apr 22 14:13 NODE1_9832.log

- Whenver I see a new archive log is created, is of size 4196352 - but why that
Apr 22's closing is smaller than normal 4196352 bytes. Is it not not fully
written?

Thanks.

          Followup:

do you or someone you work with issue "alter system switch logfile".

      Rating: 4
Only at
      EOD  April 23, 2002
Reviewer:  A reader

Hi Tom,
But only end of the day we do switch like after 14 hrs. I am sure nobody does it
before that. I really wonder, please guide.
Thanks

          Followup:

Here we go -- just to show that you can learn something new every day (i always
do...)

Article-ID:         <Note:45204.1>
Circulation:        PUBLISHED (EXTERNAL)
Folder:             server.OPS.Parallelserver
Topic:              Archives
Title:              Inconsistent Archive Redo Log Size with OPS
Document-Type:      FAQ
Impact:             MEDIUM
Skill-Level:        CASUAL
Updated-Date:       05-FEB-2002 12:20:26
References:
Attachments:        NONE
Content-Type:       TEXT/PLAIN
Products:           0;
Platforms:          89;

This article is filed for OpenVMS but is generic for Oracle7 Parallel Server.

Archive Redo Log Sizes and Oracle Parallel Server
-------------------------------------------------

Redo logs are archived for 2 reasons:

  o The current on line log has filled up and the new logfile to be used
    needs archiving.

  o A user forces the logs to be archived or switched before they have
    been filled.

Assuming manual archiving has not taken place, the size of the archived
redo log should be the same as the size of the on-line redo logs being
archived.  If manual archiving has not been done, all archived logs will
be the same size.

An exception to the rule is with Oracle Parallel Server. This can cause
smaller archived redo log files to be generated when no manual archiving
has occurred. This note explains why these smaller archived files
are generated.

Whenever a new logfile is generated it will have a log sequence number.
The log sequence number is used for recovery and will be unique across all
instances in the parallel server configuration.

It is important that the log sequence numbers generated by the different
instances do not get too far out of step. The primary reason for this is
to keep the recovery process simple.

By keeping log sequence numbers within a close range it means that recovery
doesn't have to cope with the situation where large amounts of redo come
from one thread and no redo comes from another thread.

A smaller archivelog is generally seen when one of the instances in the
configuration is doing a lot more work than the others.

The smaller archived redo log file is generated for the 'quieter' instance.
The file is not the size of the current online log because only the filled
portion of a redo log is written to the archive location. (For an idle
instance within a Parallel Server Configuration, the generated file will
contain only the redo log file header block)

      Rating: 4
Review of
      original question  June 16, 2002
Reviewer:
      Irfan  from India
Going back to the original question.

If the Export/Import is a point in time snapshot then will he face any problem
running his new db in archive log mode and in case of crash will he be able to
apply archive logs to his imported DB?

          Followup:

NO

As I said:

EXP is a point in time picture of your database.  It is a logical backup, not
a
physical backup.  It is a copy of the data. It is a point in time picture of
your database.

An export dmp file can never be rolled forward from the logs, NEVER.

      Rating: 5
logical
      backup  March 15, 2003
Reviewer:  mo

Tom:

We had to restore a procedure from a logical backup done everynight.  DBA said
you can not restore one procedure and he to do all of the which are a lot?  is
there a way to only restore one procedure from a logical backup.

          Followup:

imp .... show=y

and you can grab the code out of there.  (thats in "the book") as well.

But you know what, a DBA that needs to use a "logical backup" (an export) is not
a DBA... DMP files are not "backups" in any real sense of the word.  Oh -- I
know the docs say "backup", but it is not.

      Rating: 4
recovery
      question  May 24, 2003
Reviewer:  Reader
       from Houston, TX
When oracle does instance recovery, it uses the "current" online redolog file.
When the media recovery is needed, oracle applied both archive log and online
redolog files. In both situations, during recovery, while applying redo records
will Oracle generate redo again in the log buffer? It may seem to be a silly
question to you, but i was just wondering? Thanks.
Also, if I use backup control file to do recovery, i have to restore all of my
datafiles from backup. How does Oracle know the current status of the database
checkpoint information as I am not using the current control file. Is it like
Oracle gets the checkpoint info from archive log files and updates the backup
control file with info regarding the current status of the database. I may have
added or resized datafiles since my last backup of control file and how my
backup control file is aware of this info? Thanks as always.

          Followup:

no, it reads the redo and applies it, it does not generate any.

Oracle *doesn't* know the current status of the stuff -- thats the issue with
losing ALL copies of your current control files, it complicates recovery (cannot
be as automatic as usual).

      Rating: 4
Oracle asks for
      the archive logs generated after the hotbackup
      taken.  September 11, 2003
Reviewer:
      Belinda  from Raritan, NJ USA
Tom,
When I was doing the recovery testing, if I remove all database files and
restore only the datafiles from previous hotbackup.  then recreate controlfile
with resetlogs (as no controlfile and online redos), later recover database
using backup controlfile until cancel, Oracle asks for the archivelog files
which are generated after the hotbackup was taken. I wonder where Oracle gets
the archivelog info from? as these archivelog info should not be in the header
of the datafiles from the old backup and we don't have current controlfile and
online redos.

          Followup:

you said to recovery until cancel

oracle knows the algorithm used to generate archive log file names

hence, it just asks for that which is assumes might exist until you say "stop,
thats enough"

September 12, 2003 -- Thanks for the question regarding
      "sort_area_size", version 9.2

      You Asked
Hello sir,
         In some of your postings I see you alter this value for a session.
*How does that help.Why do we need to do it.Does it reserve virtual memory from
the  physicaldisk for sorting those order by ,group by s in the query or its
reserves from the memory allocated to Oracle.

*Sometimes when I run a CTAS query which would create million rows The dba says
it affects the temp tablespace, it gets full Why ?

      and we said...
in 9i, you shouldn't be using sort_area_size -- you would use
PGA_AGGREGATE_TARGET instead.

sort area size tells Oracle how much memory you are willing to use to sort
result sets in ram before paging it out to disk.

as for filling temp, i can only guess (insufficient data supplied to really say)
-- you might be doing an aggregation, a sort, a set operation like
minus/intersect/etc.  If these cannot be compleleted in memory, they are written
to temp.

sounds like the dba has undersized your temporary tablespace for the work you
need to perform.
Review & Followup

      Rating: 5
Thanx Sir -
      sort_area_size obsolete  September 11, 2003

Reviewer:  A reader
Thanx Sir for you answers.I dont know what we would do without you.
Does that mean sort_area_size is obsolete in 9i

          Followup:

when using pga_aggregate_targets and dedicated servers -- yes.

when not using pga agg tgt, no
when using shared server, no (shared servers do not yet use dynamic workareas)

September 12, 2003 Maira -- Thanks for the question regarding
      "database sessions -- seeing their sql.", version 8

      You Asked
How can I see (with SQL*plus) that is the last SQL statement
that a specific user has given....

      and we said...
A script such as follows works well.  It shows you who's logged
in and active -- and if active, the statement they are executing
(and the last et text shows you how long that statement has been
executing).  Currently, it shows only SQL that is executing
right now, just change the predicate from "where status =
'ACTIVE'" to "where status = status" if you want to see the
currently executing as well as LAST executed (in which case the
last et column text shows you how long they've been idle -- not
how long that statement took to execute):

column status format a10
set feedback off
set serveroutput on

select username, sid, serial#, process, status
from v$session
where username is not null
/

column username format a20
column sql_text format a55 word_wrapped

set serveroutput on size 1000000
declare
    x number;
begin
    for x in
    ( select username||'('||sid||','||serial#||
                ') ospid = ' ||  process ||
                ' program = ' || program username,
             to_char(LOGON_TIME,' Day HH24:MI') logon_time,
             to_char(sysdate,' Day HH24:MI') current_time,
             sql_address, LAST_CALL_ET
        from v$session
       where status = 'ACTIVE'
         and rawtohex(sql_address) <> '00'
         and username is not null order by last_call_et )
    loop
        for y in ( select max(decode(piece,0,sql_text,null)) ||
                          max(decode(piece,1,sql_text,null)) ||
                          max(decode(piece,2,sql_text,null)) ||
                          max(decode(piece,3,sql_text,null))
                               sql_text
                     from v$sqltext_with_newlines
                    where address = x.sql_address
                      and piece < 4)
        loop
            if ( y.sql_text not like '%listener.get_cmd%' and
                 y.sql_text not like '%RAWTOHEX(SQL_ADDRESS)%')
            then
                dbms_output.put_line( '--------------------' );
                dbms_output.put_line( x.username );
                dbms_output.put_line( x.logon_time || ' ' ||
                                      x.current_time||
                                      ' last et = ' ||
                                      x.LAST_CALL_ET);
                dbms_output.put_line(
                          substr( y.sql_text, 1, 250 ) );
            end if;
        end loop;
    end loop;
end;
/

column username format a15 word_wrapped
column module format a15 word_wrapped
column action format a15 word_wrapped
column client_info format a30 word_wrapped

select username||'('||sid||','||serial#||')' username,
       module,
       action,
       client_info
from v$session
where module||action||client_info is not null;

Review &
      Followup

      Rating: 5
How can I see
      (with SQL*plus) that is the last SQL statement   September
      30, 2001
Reviewer:  Vishal Shah  from Atlanta, GA USA

This article was extremely helpful. We had been worrying over how to find the
culprit sql for a few hours. Your code helped us solve the issue in minutes.
thanks a bunch.

      Rating: 2
Bind
      variables  March 14, 2002
Reviewer:  Dan
      Benins  from Cambridge, UK
This prints out the general SQL but it replaces the values with bind variables
ie :b1 is there anyway to get the actual sql with the actual values.

          Followup:

Not without having TRACED the session.  The bind variable values are not visible
outside of the session itself.

In Oracle9i, using fine grained auditing, we can capture the query and the
inputs (binds) to the query in the audit trail -- that will permit you to see
this information.

      Rating: 5
RE: This prints
      out the general SQL...  March 14, 2002
Reviewer:
      Mark A. Williams  from Indianapolis, IN USA
>>> This prints out the general SQL but it replaces the values with bind
variables...

I don't think Tom's code is replacing the values with bind variables as
suggested - the actual code would be using bind variables (which I would
consider a good thing).

Did I misread Tom's code?

- Mark

          Followup:

No, you are correct -- i'm not rewriting the SQL.  The bind variables (good
things) were in there, he was looking for the values -- which you cannot get
from v$ tables (only for YOUR session could you see that)

      Rating: 5
9i fine grained
      auditing  March 14, 2002
Reviewer:  Doug
       from Australia -
Tom,
Have you got any examples on fine grained auditing ?

          Followup:

Here is one:

Now, we'll create our application schema

demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM> create table emp as select * from scott.emp;

Table created.

demo@TKYTE9I.US.ORACLE.COM> create table dept as select * from scott.dept;

Table created.

demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

And set up auditing on it.  We are interested in the EMP table
and only when sal > 1500 and when you select the SAL column:

demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM> begin
  2          dbms_fga.add_policy
  3          ( object_schema   => user,
  4            object_name     => 'EMP',
  5            policy_name     => 'ENAME_SAL',
  6            audit_condition => 'sal > 1500',
  7            audit_column    => 'SAL' );
  8  end;
  9  /

PL/SQL procedure successfully completed.

demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

if you do not use CBO, you will get "false positives", it
cannot check your predicate using RBO

demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM> analyze table emp compute statistics;

Table analyzed.

demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

Now, we'll see that if we do not select SAL, no audit

demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM> select empno, ename from emp;

     EMPNO ENAME
---------- ----------
      7369 SMITH
      7499 ALLEN
      7521 WARD
      7566 JONES
      7654 MARTIN
      7698 BLAKE
      7782 CLARK
      7788 SCOTT
      7839 KING
      7844 TURNER
      7876 ADAMS
      7900 JAMES
      7902 FORD
      7934 MILLER

14 rows selected.

demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM> exec print_table( 'select * from
dba_fga_audit_trail' )

PL/SQL procedure successfully completed.

demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

Now, we'll only statements that should be audited are (and fully at that)

demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM> variable sal number
demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM> execute :sal := 1400

PL/SQL procedure successfully completed.

demo@TKYTE9I.US.ORACLE.COM> select empno, ename from emp where sal < :sal;

     EMPNO ENAME
---------- ----------
      7369 SMITH
      7521 WARD
      7654 MARTIN
      7876 ADAMS
      7900 JAMES
      7934 MILLER

6 rows selected.

demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM> execute :sal := 1700

PL/SQL procedure successfully completed.

demo@TKYTE9I.US.ORACLE.COM> select empno, ename from emp where sal < :sal;

     EMPNO ENAME
---------- ----------
      7369 SMITH
      7499 ALLEN
      7521 WARD
      7654 MARTIN
      7844 TURNER
      7876 ADAMS
      7900 JAMES
      7934 MILLER

8 rows selected.

demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM> exec print_table( 'select * from
dba_fga_audit_trail' )
SESSION_ID                    : 1520
TIMESTAMP                     : 14-mar-2002 18:21:08
DB_USER                       : DEMO
OS_USER                       : Thomas?Kyte
OBJECT_SCHEMA                 : DEMO
OBJECT_NAME                   : EMP
POLICY_NAME                   : ENAME_SAL
SCN                           : 919457
SQL_TEXT                      : select empno, ename from emp where sal < :sal
SQL_BIND                      :  #1(4):1700
COMMENT$TEXT                  :
-----------------

PL/SQL procedure successfully completed.

demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

The FGA works on the base tables, you cannot get around it via
a higher level view

demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM> create or replace view v
  2  as
  3  select emp.ename, emp.sal salary, dept.dname
  4    from emp, dept
  5   where emp.deptno = dept.deptno
  6  /

View created.

demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

And now select from the view

demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM> select dname, salary from v;

DNAME              SALARY
-------------- ----------
ACCOUNTING           1300
ACCOUNTING           5500
ACCOUNTING           2450
RESEARCH             3000
RESEARCH             1100
RESEARCH             3000
RESEARCH             2975
RESEARCH              800
SALES                 950
SALES                1500
SALES                2850
SALES                1250
SALES                1250
SALES                1600

14 rows selected.

demo@TKYTE9I.US.ORACLE.COM> exec print_table( 'select * from
dba_fga_audit_trail' )
SESSION_ID                    : 1520
TIMESTAMP                     : 14-mar-2002 18:21:08
DB_USER                       : DEMO
OS_USER                       : Thomas?Kyte
OBJECT_SCHEMA                 : DEMO
OBJECT_NAME                   : EMP
POLICY_NAME                   : ENAME_SAL
SCN                           : 919457
SQL_TEXT                      : select empno, ename from emp where sal < :sal
SQL_BIND                      :  #1(4):1700
COMMENT$TEXT                  :
-----------------
SESSION_ID                    : 1520
TIMESTAMP                     : 14-mar-2002 18:21:26
DB_USER                       : DEMO
OS_USER                       : Thomas?Kyte
OBJECT_SCHEMA                 : DEMO
OBJECT_NAME                   : EMP
POLICY_NAME                   : ENAME_SAL
SCN                           : 919467
SQL_TEXT                      : select dname, salary from v
SQL_BIND                      :
COMMENT$TEXT                  :
-----------------

PL/SQL procedure successfully completed.

demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM>
demo@TKYTE9I.US.ORACLE.COM> set echo off

      Rating: 4
How to track
      DDL's  September 09, 2003
Reviewer:  Sai
       from Mountain View, CA
Hi Tom,

We have a big table creation's(ctas) is going on in our database. How can I
track which session is creating what table and complete create table statement?

If I get what ctas statement a user is executing, I can estimate how long is it
going to run from the table size. It is too hard to judge from "db file
scattered read scan" wait event vs table name, becuase we do have lots of full
table scan's going on aside from the ctas commands.

          Followup:

take a look at v$session_longops

      Rating: 2
Is it a
      typo  September 11, 2003
Reviewer:
      A reader
Tom,
In your response you have mentioned to replace WHERE status = 'ACTIVE' with
"WHERE status = status". Can you let me know what that means.

          Followup:

just pulls all rows where status is not null, I was just saying "if you'd like
to see more data... for sessions that are not active"

September 12, 2003 Ken -- Thanks for the question regarding "Using
      CTAS to reclaim space and improve/modify storage parameters", version
7.3.3

      You Asked
   I recently became DBA for a modest Oracle Apps implementation.  We run
Inventory, Order Entry, Purchasing, and some BOM.

   I ran a number of space mgmt scripts, e.g. my variant of your free.sql, and
discovered - confirmed in conversation with my predecessor - that many of the
storage params were not set in accordance with generally accepted Oracle
standards.  Some of my most active tables had been set to pctincrease = 50 or
even 100!, and most had miniscule next_extents, etc, etc.

   I've done some rectifying (pctincrease = 0, etc) after reading most of your
thoughts on space mgmt and numerous books.  I've rebuilt some indexes as well.
I would like to "rebuild" some of the tables to modify the storage params.  I
know I can to this with Exp/Imp.

   However, I read an Oramag article (Jan/Feb 2000) by Don Burleson and he
disusses using CTAS, taking care to have enough space on your disk. Any indexes
on that table would then be rebuilt.

   For example, I have numerous tables about 100M in size.  Initial extents are
8192K, next_extents are 8192K.  Some of these tables had pctincrease = 50 or
100, so I inherited some rather large actual next_extent sizes. One was 130M.

   Do you have any thoughts on using CTAS in this fashion?  Thanks.

      and we said...
CTAS as select can be tricky, the thing that EXP/IMP does for you is preserve
grants, indexes, triggers, constraints -- everything.

CTAS will preserve NOT NULL constraints and that is it

You have to preserve everything else yourself.  You could lose grants, indexes,
constraints, triggers -- anything related to the table if you are not really
careful.

You might want to have an EXP of the table just in case in any event.  At least
you can use "IMP show=y" on it to see what DDL you need to re-run to create any
missing grants/etc.
Review & Followup

      Rating: 4
Manage space in
      Large table  September 12, 2003
Reviewer:
      Mohan
To optimally allocate space for tables do the following

1. Analyze all large tables
SQL> analyze table <large_table> compute statistics;

2. Calculate the actual space used in MB from
SQL> select table_name, blocks*(db_blocksize)/1048576 tsize
             from user_tables;

3. Move the table with the required storage values
SQL> alter table <large_table> move storage(INITIAL 120M next 8M pctincrease 0
maxextents unlimited);

Mohan

          Followup:

use LMT's

don't use storage clauses.

if you use system allocated extent sizes you can skip steps 1, 2, and 3 above
all together.

(well, you still need to analyze for the optimizer but you do not need to do it
to compute the size for this operation).

September 12, 2003 Bill -- Thanks for the question regarding
      "Partition elimination", version 9.0.1.3

      You Asked
Hi Tom,

I have a table that is using range partitioning. I have a query I feel should be
utilizing partition elimination but that doesn't show up from observing
autotrace output. The table has 340,632 blocks. The autotrace output shows a
FULL scan but also only shows 133,214 logical reads. This leads me to believe
that it is using partition elimination. Is this just a problem with autotrace?

The table ddl is as follows and I have also included the output from a sample
query while autotrace is on.

Thanks!

DDL
---
CREATE TABLE QT_MOMENT_TERM_P (
       SYMBOL_ID            NUMBER NOT NULL,
       TERM_CODE            VARCHAR2(8) NOT NULL,
       APPLICABILITY_DATE   DATE NOT NULL,
       CALCULATION_TIMESTAMP DATE NOT NULL,
       VALID_IND            CHAR(1) NULL,
       QT_SIGMA             NUMBER NULL,
       QT_SKEW              NUMBER NULL,
       QT_KURTOSIS          NUMBER NULL,
       QT_QOPT              NUMBER NULL,
       QT_DSIGDSPOT         NUMBER NULL,
       QT_DSIGMA            NUMBER NULL,
       QT_DSKEW             NUMBER NULL,
       QT_DKURTOSIS         NUMBER NULL,
       QT_DQOPT             NUMBER NULL,
       BUNDLER_ALGORITHM_ID NUMBER NULL,
       CREATION_TIMESTAMP   DATE DEFAULT SYSDATE NULL,
       EXPIRATION_DATE      DATE NULL,
       LAST_MODIFIED_TIMESTAMP DATE NULL,
       LAST_CLOSE_DATE      DATE NULL,
       VERSION_ID           NUMBER NOT NULL,
       BUS_DAYS_TO_EXPIRATION NUMBER NULL,
       DESCRIPTION_ID       NUMBER NULL
)
       PCTFREE 10
       PCTUSED 40
       INITRANS 4
       MAXTRANS 255
       TABLESPACE APP_TBS
       STORAGE (
              INITIAL 75M
              NEXT 75M
              MINEXTENTS 1
              MAXEXTENTS 2147483645
              PCTINCREASE 0
              FREELISTS 4
              BUFFER_POOL DEFAULT
       )
       LOGGING
       PARTITION BY RANGE (APPLICABILITY_DATE)
       (PARTITION P1999ALL
        VALUES LESS THAN (TO_DATE('01/01/2000', 'MM/DD/YYYY')),
        PARTITION P2000
        VALUES LESS THAN (TO_DATE('01/01/2001', 'MM/DD/YYYY')),
        PARTITION P2001
        VALUES LESS THAN (TO_DATE('01/01/2002', 'MM/DD/YYYY')),
        PARTITION PCURRENT
        VALUES LESS THAN (MAXVALUE)
       )

;

CREATE UNIQUE INDEX PKQT_MOMENT_TERM_P ON QT_MOMENT_TERM_P
(

       SYMBOL_ID                      ASC,
       APPLICABILITY_DATE             ASC,
       VERSION_ID                     ASC,
       TERM_CODE                      ASC,
       CALCULATION_TIMESTAMP          ASC
) local compress 2
       PCTFREE 10
       INITRANS 8
       TABLESPACE BTREE_TBS
       STORAGE (
              INITIAL 50M
              NEXT 50M
              PCTINCREASE 0
              FREELISTS 4
       ) ;

ALTER TABLE QT_MOMENT_TERM_P
       ADD  ( PRIMARY KEY (SYMBOL_ID, APPLICABILITY_DATE, VERSION_ID,
              TERM_CODE, CALCULATION_TIMESTAMP) );

CREATE UNIQUE INDEX XAK1QT_MOMENT_TERM_P ON QT_MOMENT_TERM_P
(
       APPLICABILITY_DATE             ASC,
       SYMBOL_ID                      ASC,
       TERM_CODE                      ASC,
       VERSION_ID                     ASC,
       CALCULATION_TIMESTAMP          ASC
) local compress 2
       PCTFREE 10
       INITRANS 8
       TABLESPACE BTREE_TBS
       STORAGE (
              INITIAL 50M
              NEXT 50M
              PCTINCREASE 0
              FREELISTS 4
       ) ;

ALTER TABLE QT_MOMENT_TERM_P
       ADD  ( FOREIGN KEY (SYMBOL_ID)
                             REFERENCES SYMBOL ) ;

Here's the query and the output from autotrace:
select * from qt_moment_term where applicability_date = to_date('12/10/2002',
'MM/DD/YYYY')
SQL> /

127619 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6889 Card=62909 Byte
          s=6794172)

   1    0   TABLE ACCESS (FULL) OF 'QT_MOMENT_TERM' (Cost=6889 Card=62
          909 Bytes=6794172)

Statistics
----------------------------------------------------------
       1376  recursive calls
          0  db block gets
     133214  consistent gets
     123954  physical reads
          0  redo size
   23616200  bytes sent via SQL*Net to client
     944703  bytes received via SQL*Net from client
       8509  SQL*Net roundtrips to/from client
         55  sorts (memory)
          0  sorts (disk)
     127619  rows processed

      and we said...
see the example in

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2259517028934
in the review/followup section.

autotrace doesn't show the partition start/stop.  using explain plan will.

Review &
      Followup

      Rating: 4
plan table does
      show partition elimination  December 13, 2002

Reviewer:  Bill Ennis  from Chicago, IL
Yep, you're correct plan table shows a full scan at the PARTITION level. Any
idea why autotrace doesn't show this level of detail?

Also, it looks like the dbms_xplan package wasn't installed in my server. I
don't have admin rights so I couldn't install. I was unaware of this package,
looks useful though.

          Followup:

because autotrace prints what it prints.

dbms_xplan is new in 9iR2 -- sorry about that.

use utlxpls.sql in $ORACLE_HOME/rdbms/admin as a "sqlplus script"

it'll do the same thing.

      Rating: 4
Partition
      elimination and indexes  September 12, 2003

Reviewer:  Alvin  from Philippines
My query's predicate is as follows....

                   WHERE groupid = 761
                     AND (TIME BETWEEN 1063170000385 AND 1062478799385)
                     AND duration > 0
                ORDER BY TIME))
 WHERE rn BETWEEN 1 AND 50

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.02          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1     55.55     609.73     428733     470969         96           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3     55.56     609.75     428733     470969         96           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 53

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  VIEW
      0   COUNT
      0    VIEW
      0     SORT ORDER BY
      0      TABLE ACCESS FULL RTBETELCALLDETAILRECORDS PARTITION: START=3
STOP=3

Does the plan mean that it used partition elimination ??

My table is partitioned by the Time column. I have local indexes on all the
columns in the predicate duration,time and groupip columns. I think that oracle
should've used the group index or at least one of the 3. The optimizer ( 8i )
seems to think that full table scan would be much more better.

Running the same query on our developmental server yields a diff. plan
altogether and it uses the time index.

          Followup:

it read partition 3, start/stop = 3....

you don't show the entire query BUT it should be like this:

select *
  from ( select a.*, rownum rn
           from (select /*+ FIRST_ROWS */
                  ....your query here ....
                  order by time ) a
          where rownum <= 50 )
 where rn >= 1;

replacing 50 and 1 with binds of course.  You want to see a "COUNT STOPKEY" in
your plan -- by putting rownum in there -- you are precluding that from
happening.  first rows -- which is what you want (looks like a partial query
from a classic "pagination" attempt)...

(but when you hide half of the data -- i can only guess at things.....)

September 12, 2003 June -- Thanks for the question regarding "tree
      validation ", version 8.1.6

      You Asked
HERE is my scenario:

Table test has the columns of
id varchar2(2) not null,
parent_id varchar2(2);

select * from test;
will return the value of
id parent_id
-- ---------
b1 b2
b2 b3
b3 b4
b4
b6

I need to write a procedure with the parameters of p_id, and
p_parent_id to update   the table test.  The value of p_id
should have already existed as id. Whenever the values passed
from p_id, p_parent_id.  I just need to
update test
set parent_id = p_parent_id
where id = p_id

However, I need to check whether the connect by condition
results a loop in the hierarchy
by updating the record.  If the updating will result a loop or
tree validation - one row is both the parent (or grandparent or
direct ancestor) and a child (or a grandchild or a direct
descendent) of another row, then the record should not be
inserted; if the updating will not result a loop,  then the
record should be inserted.
Basically I need to avoid something like this :
id parent_id
-- ---------
b1 b2
b2 b3
b3 b4
b6
b4 b2

The last row of (b4, b2) should not be allowed.

There is other business logic in my code, but this one is the
toughest one and I don't have much
clue.

What is in my mind is that I would first insert into the table
without checking.  Since the hierarchy
query -  connect by will return an error by oracle if the loop
exists, I will rollback the updating.  If no error is thrown by
the connect by query after the updating, then commit.  But I
feel this is so awkward.  And since the  hierarchy tree
validation is just a part of this procedure, a lot of other
processes need to be done in the procedure, I feel rollback is
not a good choice after a lot of DML statement in my code.
Could you please guide me?
Thanks!!

      and we said...
You have the right logic -- use the power of the connect by
itself to test the data.  I've seen people try to do it
themselves but the only thing you could do it write infinitely
less efficient code then the database already has builtin to it.

If you use savepoints, you'll have a finer degree of control
over your transaction.  A rollback after lots of DML is a "good"
thing (its what we are all about in fact -- giving you
transactions).  If you decide after doing some work that the
work you've done must be undone -- well that is exactly what you
bought us for.  Don't be afraid to rollback -- it is a natural
operation.

Here is an example:

ops$tkyte@ORA8I.WORLD> create or replace
  2  procedure upd_parent( p_id in char,
  3                        p_parent in char )
  4  as
  5      l_cnt number;
  6
  7      connect_by_error exception ;
  8      pragma exception_init( connect_by_error, -1436 );
  9  begin
 10      begin
 11          savepoint test_parent_is_ok;
 12
 13          update t
 14                set parent_id = p_parent
 15           where id = p_id;
 16
 17          select count(*) into l_cnt
 18            from ( select *
 19                     from t
 20                    start with id = p_id
 21                  connect by prior parent_id = id );
 22
 23          dbms_output.put_line
 24          ( 'Update Successful, no connect by error' );
 25      exception
 26          when connect_by_error then
 27              rollback to test_parent_is_ok;
 28              dbms_output.put_line( 'Undid the update' );
 29      end;
 30  end;
 31  /

Procedure created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select * from t;

ID PA
-- --
b1 b2
b2 b3
b3 b4
b6
b4

ops$tkyte@ORA8I.WORLD> exec upd_parent( 'b4', 'b6' );
Update Successful, no connect by error

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD> select * from t;

ID PA
-- --
b1 b2
b2 b3
b3 b4
b6
b4 b6

ops$tkyte@ORA8I.WORLD> exec upd_parent( 'b4', 'b2' );
Undid the update

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD> select * from t;

ID PA
-- --
b1 b2
b2 b3
b3 b4
b6
b4 b6

The rollback to savepoint will only undo the work done since the
savepoint.  Hence if you want the larger part of the transaction
to succeed -- this can still work for you.  Notice how the
uncommitted update to b4 with a parent of b6 survives the failed
attempt...
Review & Followup

      Rating: 4
How to
      completely avoid Savepoints ?  September 11, 2003

Reviewer:  Praveen  from Bangalore
Hi Tom,

How do achieve the same result, as given above without using
any savepoints ? For ex: I have processed 100 records (out of say 200 records)
of a table (and ofcourse did 100 updations, without any commit).
How do I roll back the updations made by only the 101-st record if some error
occurs during the processing of that record, and continue with the next record ?
I dont want to use any savepoints at all.
Is that possible? If so how do we determine the trade-off between the two
methods?

Thanks and regards

Praveen

          Followup:

you do it with savepoints.

there isn't another way to do it.

nothing wrong with savepoints is there?  it is the proper tool for the job after
all.

      Rating: 5
Savepoints
        September 11, 2003
Reviewer:  Praveen
       from Bangalore
Tom,

Thankyou so much for the prompt and valuble answer.

Warm regards

Praveen

      Rating: 4
Savepoints and
      locks  September 12, 2003
Reviewer:  Praveen
       from Bangalore
Hi Tom,

Thanks for you great answer regarding savepoint uses. But here are some other
points of view - against it.

According to the people who were arguing against the use of savepoints is that,

1) They create too many locks on the table. For ex. in the scenario that I had
given above,
suppose we commit only after every 1000 record and during that period
we issue rollback(s) to a certain savepoint (based on some error condition). The
argument is that
this can increase the number of locks on the table where updations take place.
(I cannot think of a
possiblity how this occurs).

2) Another argument is that savepoint makes the code less maintainable. ie they
are similar to GOTO
statements. When transactions becomes more complex, too many savepoint can be a
mess.(are they ?).

How do you feel about this ? Some of them are in desperate research for an
alternate method (for savepoints).

All this issue arised when an event log table getting locked frequently (and a
DBA intervention is required).
Explanation of the DBA is that lack of commit/rollback for a long time can
result in such locks. But nobody
issues a UPDATE statement on that table. All the events are INSERTED by a
procedure (not autonomous) called
by every one in the team (and no commits). But after sometime, because of some
reason further INSERTions to
the event log table fails due to locks. Why does that happen? Is that due to the
savepoints in the transactions?

(If that is true I badly deserve a Oracle beginner's refresher course. :( .....)

Thanks in advance for your valueble explanation.

Warm regards

Praveen

          Followup:

1) b.s.  total, unadulterated, uninformed b.s.  100%...

ask them to list all of the places where implicit savepoints take place.  see if
they know.  there are millions of them.

they are "mistaken"

2) see #1.  tell them to explain how a block structure makes it like a goto?
ask them if they know that Oracle throws hundreds of savepoints into your code
magically, already!!  it is happening all of the time.

tell them to provide us a clear, concise, consistent and reproducible case
whereby savepoints introduce ANY of the above issues.  I'll gladly address that.

September 12, 2003 mohammad -- Thanks for the question regarding
      "copy of schema", version 8.1.6.0

      You Asked
We can use COPY command to copy one table from one database to another
database.So,far I use this table by table.Now, I want to know whether We can
copy full schema(all tables of a schema by one copy command) from one database
to another

      and we said...
No, you must issue a command per table.

The way to automate this is to write SQL that writes SQL:

set heading off
set feedback off
set linesize 255
set trimspool on
spool tmp.sql
select 'copy from remote_db replace ' || table_name ....
  from user_tables@remote_db
/
spool off
@tmp.sql

Review &
      Followup

      Rating:
      4
thanks  October 08, 2001
Reviewer:
      mohammad toaha  from dubai
It will be very much helpful.

      Rating:
      4
thanks  October 11, 2001
Reviewer:
      toaha  from dubai
Hi tom,

Following your advice, I have created the following script(incomplete).please
tell me how to add 'USING SELECT * FROM TABLE_NAME' after each statement.

set feedback off
set linesize 255
set trimspool on
spool tmp.sql
select 'copy from scott/tiger@remote_db to test/test@local_db replace ' ||
table_name from user_tables@remote_db  using select * from 'table_name' ( How to
add this at the end of the statement?)
/
spool off
@tmp.sql

          Followup:

select 'copy from scott/tiger@remote_db to test/test@local_db replace ' ||
        table_name || ' using select * from ' || table_name
  from user_tables@remote_db
/

is what you are looking for.

      Rating: 3
Problem in
      copying  October 29, 2001
Reviewer:  mohammad
      toaha  from Dubai
The Scenerio is:

 :dewadba> copy from dewadba/dewadba@disbdc to dewadba/dewadba@orcl replace
w_valve using select * from w_valve;

Array fetch/bind size is 1. (arraysize is 1)
Will commit when done. (copycommit is 0)
Maximum long size is 80. (long is 80)
Table W_VALVE created.

   12759 rows selected from dewadba@disbdc.
   12759 rows inserted into W_VALVE.
   12759 rows committed into W_VALVE at dewadba@orcl.

  :dewadba> copy from dewadba/dewadba@disbdc to dewadba/dewadba@orcl replace
w_noc using select * from w_noc;

Array fetch/bind size is 1. (arraysize is 1)
Will commit when done. (copycommit is 0)
Maximum long size is 80. (long is 80)

After this stage,I get the following message:

SQLPLUSW.exe has generated errors and will be closed by Windows.You will need to
restart the program.

An error log is being created.

And the error log says that:

Windows cannot determine the user or computer name. Return value (1722).

In the context of the above,Would you let me know:
a. effect of Array fetch/bind size
b. effect of long size
    on this command.
c.What can be done to avoid getting this error message?

          Followup:

try using sqlplus.exe instead of sqlplusw.exe.  Contact support for assistance
with sqlplusw.exe, they'll ask you for relevant information like your version,
your operating system, and so on.

      Rating: 4
Incremental
      Copy of schema tables  March 02, 2002
Reviewer:
      mohammad toaha  from Dubai , UAE
Is it posible to do incremental copying of tables.If yes,please tell me how with
examples.

thanks for help always.

          Followup:

That is called replication.  See

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76959/toc.htm
a simple "snapshot" is probably what you are looking for.

      Rating: 3
differences ?
        September 12, 2003
Reviewer:  Deepak Gupta
       from Delhi, India
Hi Mr. Tom,

what is advantages and disadvantages ( differences )between two commands ?

copy from scott/tiger@remote_db to test/test@local_db replace .........

and

create table ....
as select * from  ...@remote_db

Thnaks + Regards

          Followup:

one uses inserts, writes to the buffer cache, generates undo and redo, and uses
the buffer cache. (copy command)

the other does direct writes to the datafiles, skips most undo, can skip redo if
you like and does not use the buffer cache to write to. (create table)

      Rating: 5
RE: differences
      between the two commands   September 12, 2003

Reviewer:  Mark A. Williams  from Indianapolis, IN
      USA
In addition to what Tom has already mentioned, the "copy" command is deprecated,
and db links are the suggested replacement.

- Mark

September 12, 2003 Keith -- Thanks for the question regarding
      "Reverse Key", version Oracle 8i 8.1.7

      You Asked
Tom,

I have many primary keys that are sequences. Thinking of using reverse keys. How
do I change the index created by the primary key? Rebuild as a reverse key?

Thanks.

Keith Cutler

More info:

Reading through the books I received with my classroom training for Enterprise
DBA Part 2: Performance and Tuning, reverse keys are suggested for use with
columns using sequences. I have very many sequences used to create primary key
values. That is why I posed this question. Why would you suggest not using a
reverse key? Thanks as always.

      and we said...
why not:

reverse key indexes void the ability of an index range scan.
They only work with exact equality.

They create the worst case index structure (if we always insert on one side,
that actually generates a very good index structure.  If you are interested in
this, see "Practical Oracle8i" by Jonathan Lewis -- an EXCELLENT book IMO. He
goes over this in some detail)

why you would:

you are using OPS and need to remove a hot spot from an index on a table every
node inserts into.  Period.  thats the only reason.

Review &
      Followup

      Rating: 4
But how are the
      keys reversed  May 05, 2003
Reviewer:
      Praveen  from India
Hi Tom,

  Learned a lot from your site. I have doubt here. I was going through some
question while preparing for my OCP fundamentals I paper when I came across this
question. It says:
The value stored in an index for a column is '596849'. The DBA then issues the
alter index reverse statement. What does the data in the index now look like?
A. '948695'

B. '849596'

C. '596849'

D. '695948'

And the answer which is said correct is '695948' but I thought the answer to be
'849596'. My doubt is how the keys are reversed and stored in the reverse key
index?

          Followup:

well, not knowing if that is a varchar for sure or not, I tested with a number
and a varchar:

create table t ( x number, y varchar2(10) );
insert into t values ( 596849, '596849' );

create index t1_idx on t(x);
create index t2_idx on t(y);

i dumped the blocks, the t1_idx had these bytes:
col 0; len 4; (4):  c3 3c 45 32

and t2_idx had:
col 0; len 6; (6):  35 39 36 38 34 39

and if you

  1  select dump(596849,16),
  2         chr(to_number('35','xx'))||
  3         chr(to_number('39','xx'))||
  4         chr(to_number('36','xx'))||
  5         chr(to_number('38','xx'))||
  6         chr(to_number('34','xx'))||
  7         chr(to_number('39','xx'))
  8*   from dual
ops$tkyte@ORA920> /

DUMP(596849,16)          CHR(TO
------------------------ ------
Typ=2 Len=4: c3,3c,45,32 596849

You can see how that data matches up

Then I:

alter index t1_idx rebuild reverse;
alter index t2_idx rebuild reverse;

and dumped again...

col 0; len 4; (4):  32 45 3c c3
col 0; len 6; (6):  39 34 38 36 39 35

which is:

 1  select chr(to_number('39','xx'))||
  2         chr(to_number('34','xx'))||
  3         chr(to_number('38','xx'))||
  4         chr(to_number('36','xx'))||
  5         chr(to_number('39','xx'))||
  6         chr(to_number('35','xx'))
  7*   from dual
ops$tkyte@ORA920> /

CHR(TO
------
948695

it reverses the entire string -- I fail to see how (d) could be the correct
answer.  The only answer that could be correct would be (a)

      Rating: 5
reverse key
      index  September 11, 2003
Reviewer:
      A reader  from orlando,FL
Sir,
In your book expert one-on-one you say that reverse key indexes are for use
mainly in OPS environment. In these forums you do recommend using sequences, if
possible.  In
situations,  where sequences are used in *single*  instance oracle in indexkey
generation,  would you say that reverse key indexes are the way to go in a
concurrent environment assuming the loss of range scan is not a issue? Basically
I dont get the caveat 'Only in OPS' since single instance situations could also
face the same problem if multiple processes are hitting the same index block.

Rgds..

          Followup:

we are pretty good at concurrency controls and row level locking.

what sort of rates are you talking about here?

      Rating: 5
Reverse Key
      Index  September 11, 2003
Reviewer:
      A reader  from orlando,FL
Sorry, I do not have the numbers since it was a theoritical thought that crossed
my mind. Put it other way, I was trying to get your esteemed opinion/thought to
this question:
From a reverese-key-index block point of view what difference *it* sees being
accessed thru a multi-instance server process Vs a single instance server
process to perform less for the single-instance server process. I am sure i am
missing something.

Thanks as always...

          Followup:

for 99.999999% of implementations it is far far beyond where you have to go.

most people never incurr the sort of contention in a single instance you might
need in order to really think about this feature.  initrans and multiple
freelists are what most people need to look at if anything.

      Rating: 3
More
      clarification on Reverse Index  September 12, 2003

Reviewer:  Anil Pant  from Hyderabad, India

Hello,
When I had read abt the Reverse key Index I was understood that it stores in
reverse order. Whether its varchar or number. And ur statement 'it reverses the
entire string -- I fail to see how (d) could be the correct answer.  The only
answer that could be correct would be (a)' also say the same thing.
What will happen if index on date in reverse order ? Also I could under stand
how u arrived that answer is (a). I mean this query

  select chr(to_number('39','xx'))||
           chr(to_number('34','xx'))||
           chr(to_number('38','xx'))||
           chr(to_number('36','xx'))||
           chr(to_number('39','xx'))||
           chr(to_number('35','xx'))
     from dual

CHR(TO
------
948695

          Followup:

dates are fixed width 7 byte values.

they will just be reversed.

that query just takes the ascii codes (in hex) and returns the ascii character
assoicated with them.

      Rating: 5
Range
      scan  September 12, 2003
Reviewer:  Reader

"reverse key indexes void the ability of an index range scan.
They only work with exact equality.'

Does it mean if my query needs to do range scan on cols with reverse key index,
does oracle ignore using the reverse key indexes on those columns and go for
full table scan instead? Thanks for clarification.

I have bought your new book, Effective Oracle by Design .. This weekend is going
to be a reading weekend....

          Followup:

if you create a reverse key index on a number column "N" then

where n between 1 and 50

*cannot* range scan that index.  the data is no longer stored sorted.  we could
fast full scan that index, we could full scan that index - but we could not
really range scan it.

HOWEVER, if you create a reverse key index on a pair of columns (N,M) then:

where n = 5 and m between 1 and 50

can range scan -- we'll range scan to find N=5 and look at each of the N=5
values in the index for M between 1 and 50...

but in general, NO range scanning with reverse keys.

September 12, 2003 Neil -- Thanks for the question regarding
      "trigger firing order question", version

      You Asked
When you have created more than one trigger on the same
table (for example to get aorund the mutating table problem),
how
does oracle determine which to process first ? In the example on

your home page, the two after triggers "parent_aifer" and
"parent_ai" would need to be processed in that order, so are
they
fired in the order they are created ?

thanks

NeilC

      and we said...
there are 4 types of DML triggers:

BEFORE
BEFORE, FOR EACH ROW
AFTER, FOR EACH ROW
AFTER

All BEFORE triggers are fired prior to the BEFORE, FOR EACH ROW.
 The BEFORE, FOR EACH ROW are fired prior to the AFTER, FOR EACH
ROW.  These in turn fire before the AFTER triggers.

What is not determinate is the firing order of "same type"
triggers.  If I had 2 BEFORE triggers on a table -- I cannot
safely determine the order in which those 2 triggers will fire.
I can be assured they will BOTH fire before any ROW triggers and
the AFTER trigger but that is all.

So, yes -- the aifer (After Insert For Each Row) trigger will
always fire before the ai (After Insert) trigger.

\
Review &
      Followup

      Rating:
      4
Kathy  September 12, 2003
Reviewer:
      Kathy Dodge  from Dayton, Oh
When using the AFTER trigger, does the update occur on the database before the
trigger is fired and is it committed before the trigger is fired.

          Followup:

in an AFTER trigger, the statement that triggered the trigger is "done", so the
update would have taken place.  a read of that table in the AFTER trigger would
show the data AFTER the update.

a commit is only done when YOU say so.  we do not commit for you.

September 12, 2003 Jim -- Thanks for the question regarding "Fastest
      way to save off Duplicates", version 8i

      You Asked
Tom,

I am interested in finding the fastest way to save off
the duplicate records to a duplicates table.  My source
table has about 13 million rows and the users want to remove
all duplicates from the primary table and store them in a
duplicates table for later review.  (I know, not exactly the
smartest thing to do.  However, I have not control over this).
I expect there to be a relatively low number of dups.
(less than 1%).  Here is what I have come up with.

create table dup_rowids
as
(select rowid
from table_a
minus
select Max(rowid)
from table_a
      group by A, B, C);

Using this list, I can then create table for those records
that are dups and those records that are unique

Am I on the right track?

Thanks,
Jim

      and we said...
how about adding a unique constraint on the columns in question and using
"exceptions into".  For example:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( a int, b int, c int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec gen_data( 'T', 500 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select * from t where rownum <
10;

9 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table exceptions(row_id rowid,
  2                          owner varchar2(30),
  3                          table_name varchar2(30),
  4                          constraint varchar2(30));

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t add constraint t_unique
unique(a,b,c) exceptions into exceptions;
alter table t add constraint t_unique unique(a,b,c) exceptions into exceptions
                             *
ERROR at line 1:
ORA-02299: cannot validate (OPS$TKYTE.T_UNIQUE) - duplicate keys found

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table dups
  2  as
  3  select *
  4    from t
  5   where rowid in ( select row_id from exceptions )
  6  /
create table dups
             *
ERROR at line 1:
ORA-00955: name is already used by an existing object

ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from t where rowid in ( select row_id
from exceptions );

18 rows deleted.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select distinct * from dups;

9 rows created.

Review &
      Followup

      Rating: 4
One more
      tip  June 17, 2002
Reviewer:  A Reader
       from USA
Tom,

In your response you have assumed that the duplicate rows are absolutely
identical, column by column. Sometimes it's not teh case; only the values in the
PK column(s) are identical but the rest may be different. So a distinct * will
not work in that case. Rather I suggest the following after the dups table is
created.

delete dups o where rowid in
(select rowid from dups where a = o.a
and rownum < 2)

Then the dups table will be free of duplicated. In the last stage just issue :
"insert into t select * from dups" instead of select distinct * from dups.

Hope this helps.

      Rating: 3
Another
      tip  June 18, 2002
Reviewer:  Raman
       from India
try this :

SELECT * FROM emp a
WHERE rowid > ANY
(SELECT rowid FROM emp b
 WHERE a.ename = b.ename
)

will give you all the duplicate copies for records.

Raman.

      Rating: 5
more
      ideas  March 13, 2003
Reviewer:  Igor
       from San Francisco, CA
If you dont need to delete rows from dups.

You could

INSERT INTO t
   SELECT *
     FROM dups
    WHERE ROWID IN (SELECT row_id
                  FROM (SELECT DISTINCT id, MAX (ROWID) AS row_id
                               FROM dups
                               GROUP BY id))

Its faster.

          Followup:

really?  faster?

where is the test that shows this.

      Rating: 4
How about
      writing a pl/sql proc to remove dups  March 14, 2003

Reviewer:  Chandru  from Harlingen, TX USA

Tom,

If I write a pl/sql proc as below to remove duplicate records, would this be
fast enough to clean a huge table, say, with million records? Any comments will
be appreciated. Thanks. -- Chandru

create or replace procedure rem_dups
is
  cursor dup_rec_cur
  is
    select n, count(*)
      from test
     group by n
     having count(*) > 1;

  cursor record_id_cur (p_record number)
  is
    select n,rowid
      from test
     where n = p_record;

  v_rowid1 rowid;

begin -- rem_dups

  for r in dup_rec_cur loop
    for s in record_id_cur(r.n) loop
      v_rowid1 := s.rowid;
      exit;

    end loop;

    delete from test
     where rowid <> v_rowid1 and n = r.n;

  end loop;
  commit;
end rem_dups;
/

          Followup:

just use sql, never write code unless you have to.

      Rating: 4
Got
      it!  March 15, 2003
Reviewer:  Chandru
       from Harlingen, TX
Thanks. I thought exec a pl/sql proc is simpler than multiple sql statements to
identify the dups and then delete them. However, I fully agree with you that if
sql can do it more efficiently than a pl/sql procedure, stick with sql.

      Rating: 3
Listing
      duplicates  September 11, 2003
Reviewer:
      Joe  from Atlanta
Hi Tom,

I have a table containing Bill of Material items. How do I find the parent BOM
items using the same child items?

Here is the data for table bom_list:

bom_nbr      item     qty
1001       BK7001       3
1001       TL0905       2 <-
1001       JJ1209       1
1002       AA1023       3
1002       AP0922       2
1003       WR7834       1
1003       TL0905       5 <-
1003       XT1129       1
1003       LM9870       1
1003       VB0997       1 <-
1004       SK2545       4
1004       AJ0005       1
1004       SG1212       1
1004       KF5412       1
1004       AS7890       1
1004       VB0997       2 <-
1005       SA1990       1
1005       TL0905       1 <-

This is the output that I am trying to get:

bom_nbr      item     qty
1001       TL0905       2
1003       TL0905       5
1005       TL0905       1
1003       VB0997       1
1004       VB0997       2

Thanks,
Joe

          Followup:

no idea why your result set is what it is.

is there some logic hiding in there?  I see your arrows, but don't really know
why you have arrows there?

      Rating:
      3
  September 12, 2003
Reviewer:  Joe
       from Atlanta
Tom,

I have typed in the arrows to indicate that these are the repeating items. I
want to find the repeating items in various BOM. So in the output list, I have
listed the items that are repeating in different BOMs. And this is the output I
am trying to reproduce.

Do this help?

Thanks.

          Followup:

select *
  from bom_table
 where item in ( select item
                   from bom_table
                  group by item
                 having count(bom_nbr) > 1)

is one approach.

      Rating: 3
One more
      way  November 04, 2003
Reviewer:  Oleksandr
      Alesinskyy  from Hofheim, Germany
What about:

delete from DUPTAB where rowid in (select rid from(
select rowid rid,row_number() over(partition by DUPCOL1,DUPCOL2,...,DUPCOLN
order by rowid desc) rn
from DUPTAB
) where rn>1);

I guess it is fastest if no index exists on DUPCOL1...DUPCOLN as this approach
saves 1 full scan

September 12, 2003 thomas -- Thanks for the question regarding
      "Connect sys/xxxxx does not work in 9i", version 9.0.1

      You Asked
I've just installed 9i and went to log in as SYS but discover that I cannot.
Whats wrong?  I cannot get in as internal anymore either

C:\>sqlplus sys/manager

SQL*Plus: Release 9.0.1.0.0 - Production on Mon Jul 9 10:05:35 2001

(c) Copyright 2001 Oracle Corporation.  All rights reserved.

ERROR:
ORA-28009: connection to sys should be as sysdba or sysoper

Enter user-name: internal
Enter password:
ERROR:
ORA-09275: CONNECT INTERNAL is not a valid DBA connection

      and we said...
Well, there are two things at work here -- we'll work backwards through them.
First for the

ORA-09275: CONNECT INTERNAL is not a valid DBA connection

error.  This is due to the desupport of the INTERNAL account in Oracle9i.  For a
while now we have been recommending people stop using INTERNAL and start using
the "connect user/password as sysdba" or "as sysoper" syntax.  A person
connected as "sysdba" has the same capabilities as someone connected as INTERNAL
would have.  So, connect internal is a feature that was removed in Oracle9i.

The second issue,

ORA-28009: connection to sys should be as sysdba or sysoper

is not really new to Oracle9i but more people will hit it in 9i then in 8i and
before.  Oracle9i defaults the init.ora parameter "O7_DICTIONARY_ACCESSIBILITY"
to FALSE whereas all prior releases defaulted it to TRUE.

There are two side effects of this parameter being set to FALSE:

o connect sys/password will not function, you will get the ORA-28009 error

o Access to the "real" data dictionary owned by SYS will not be available to
users, even if they have the SELECT ANY TABLE privelege.  These are not the data
dictionary views like ALL_OBJECTS but rather the base tables like SYS.OBJ$ that
will be unaccessible.

Now the first side effect, that "sys/password" will not work without
sysoper/sysdba, is not clearly documented anywhere.  The fact that the
O7_DICTIONARY_ACCESSIBILITY init.ora parameter causes this is somewhat hard to
track down -- but that is what is causing this.

The other side effect -- that the "real" data dictionary is not accessible to
normal users is well documented.  That is after all the purpose of this
initialization parameter.  That said however, there is of course a caveat to
this.  Portions of the documentation still state:

<quote>
  If this parameter is set to false and you need to access objects in the
  SYS schema, then you must be granted explicit object privilege. Also,
  the following roles, which can be granted to the database administrator,
  also allow access to dictionary objects: SELECT_CATALOG_ROLE,
  EXECUTE_CATALOG_ROLE, and DELETE_CATALOG_ROLE.
</quote>

But in Oracle9i that is not accurate.  There is a new system privilege "SELECT
ANY DICTIONARY" the permits access to the SYS schema (and by default the DBA
role has this privilege granted to it).  The "Oracle9i Database README Release
Notes" included with the 9i software does describe this change:

<quote>
   18.3 Data Dictionary Protection

   Data dictionary protection is now enabled by default. Specifically, the
   O7_DICTIONARY_ACCESSIBILITY init.ora parameter is set to FALSE on
   installation. As a result, regular users who are not database administrators
   with ANY privileges (for example, SELECT ANY TABLE) can no longer use the
   ANY privilege upon data dictionary objects; however, the user can access
   non-SYS schema objects using the ANY privilege.

   Users making a database administrator user type connection (for example,
   CONNECT / AS SYSBDA) can exercise privilege on data dictionary objects,
   since SYSDBA has all privileges.

   A new system privilege, SELECT ANY DICTIONARY, provides users with SELECT
   access to any object in the SYS schema without giving them DBA privileges.

   Oracle recommends that the dictionary protection feature remain enabled as
   it is a more secure configuration. If O7_DICTIONARY_ACCESSIBILITY is set to
   TRUE, then regular users with the ANY privilege can use these privileges --
   perhaps maliciously -- to alter data dictionary objects.
</quote>

My recommendation would be to leave the the O7_DICTIONARY_ACCESSIBILITY set to
false and change scripts that connect as SYS to connect as some other user.
Using the SYS account should be avoided in any case -- you should never use it
to create objects, cannot use it to create triggers and some commands (for
example "set transaction read only") don't even work when connected as SYS.

Consider SYS to be a special account that you never need to use anymore.

Review &
      Followup

      Rating: 4
Thanks for the
      explanation  July 10, 2001
Reviewer:  Mark J.
      Bobak  from Ann Arbor, MI
Hi Tom,

Thanks for the clear summary of how it's supposed to work.

There is a similar issue in MetaLink, that also discusses
a potential bug related to this.  I thought you may be interested in checking
out:

http://metalink.oracle.com/metalink/plsql/for_main.fetchMessage?p_message_id=245602.999&p_forum_id=5&p_after_post=N&p_forum_scope=a&p_forum_time=7&p_myThread=1&p_expandAll=0

      Rating: 5
9i Catalog
      Views access on Forms 6i  October 16, 2002

Reviewer:  Bruno Di Rei Araujo  from Uberlandia, MG,
      Brazil
Great Tom, that is really what I was looking for !

Although it worked on sqlplus, I just can't do the same on my Forms 6i: I logged
with the privilegded user and tried to compile the form, but it still reports
"DBA_ROLES identifier must be declared" on my cursors... The *same* cursor works
fine in sqlplus.

Why? Do I have to update all the clients with Oracle9i Client for it to work, I
mean, does the Forms 6i client works fine with Oracle DB 9i?

Thanks a lot !

          Followup:

should not matter the client.

Suggestion: try creating a view yourself (select * from dba_views) and see what
happens.

      Rating: 5
how to connect
      from command line  September 12, 2003
Reviewer:
      Sikandar Hayat  from Pindi Bhattian - Pakistan
I have Oracle 9i R2 on RH7.2 and I am trying to connect in the following way,

$ sqlplus / as sysdba
$ sqlplus sys/password as sysdba

both the above are not working?

but the following worked,
$ sqlplus "/ as sysdba"

Why this?

          Followup:

because that is the way parameters work

the "username/password as sysdba"  is a SINGLE parameter

the way to do that in unix/linux is via ""

      Rating: 5
Very clear
      explanation and very helpful.  September 12, 2003

Reviewer:  Pauline  from NY,USA

      Rating: 5
Explained the
      reason for install problems with DBTools 4.x in 9i environments
      !  January 05, 2004
Reviewer:  Sven
      Bleckwedel  from Santos/SP - Brazil
Hi Tom,

Thanks very much for explaining this.  I was searching at the forum site from
the DBTools utility for a reason for these ORA-28009 problems and remembered
that in your site some answers can be the easiest way to obtain it...

Thanks again,
Sven

September 12, 2003 Solomon -- Thanks for the question regarding
      "what to use when", version 8.1.7

      You Asked
Hi Tom,

I know that this is a very basic question but only you can explain it to us in
an understandable way :)

I can use PL/SQL table of records or global temoprary table or create a varray
or nested table of a TYPE , for almost same requirements.

with all those options available WHAT TO CHOOSE WHEN is my question?

      and we said...
Well, I never use varrays -- too limiting, they force you to constraint the
upper index bound when you create them.  Since I would never create a table
column of type varray or nested table (in a real table) the difference between
them is not meaningful to me (varray's and nested tables are really only
different from eachother when used as datatypes in table create statements)

I avoid plsql table of records when I KNOW the table of records will be
"leaving" plsql.  If I was fetching some data, processing it in plsql, and
returning some answer -- so the records never left the scope of PLSQL, i would
use a table of records.  Easy to define -- "type recArray is table of
emp%rowtype index by binary_integer" and efficient.

If on the other hand, I needed to return the 'table of records' to any other
client -- that would rule out plsql tables of plsql records.  They cannot be
returned to any other client other then plsql.  There I would use a collection
of objects:

create type myScalarType as object ( ..... );
create type myArrayType as table of myScalarType;

that lets me return from PLSQL a ref cursor opened as:

   select * from TABLE( cast( plsql_variable as myArrayType ) );

that way, ANY client on the planet can use the output.

I use global temporary tables when clients want to SEND me an array of records.
Rather then trying to map to object types and collections in the 3gl (which I
find "hard"), I array insert into a global temporary table the same data (which
I find "easy").  Plsql would read the gtt (bulk collecting it into a table of
records or a collection of objects depending on the need) and process it.

Review &
      Followup

      Rating:
      5
  June 12, 2003
Reviewer:
      A reader

      Rating:
      4
  September 11, 2003
Reviewer:
      Budhi
Hi Tom,

You said:-

"I use global temporary tables when clients want to SEND me an array of records.

Rather then trying to map to object types and collections in the 3gl (which I
find "hard"), I array insert into a global temporary table the same data (which
I find "easy").  Plsql would read the gtt (bulk collecting it into a table of
records or a collection of objects depending on the need) and process it."

I have a requirement in which I will get an array of records from the front end.
They will be sending it to me separting the columns using delimiters and
separating the records again using another delimiter.

How would you handle this case?

Say for eg front passes to save procedure the following text:-

F3435,4/5/2002,SCOTT,34.11,,|G56347,10/12/2001,TIGER,,21,12

You have to insert into a table with following structure:-

employee
---------
empno
hiredate
ename
sal
comm
bonus

Is there any better way to pass the array of records from front end (like ASP)
and handle it at back end.

Please respond

          Followup:

i would not have them glue a string together (cpu processing) and then have me
unglue them by parsing it (cpu again)

i would do exactly what I said I would do -- insert the data into the gtt.

      Rating:
      2
how????  September 11, 2003
Reviewer:
      Budhi
How would you insert these data into a gtt???
Would you ask the front end to call insert statements into the gtt, multiple
times for this?

Then if I have 10 rows in the array would the front end do 10 calls?

Could you please explain how you would do otherwise?

          Followup:

batch em up - most every API i've ever seen has this ability.  but yes, you
would insert 10 rows.

      Rating:
      2
how?  September 12, 2003
Reviewer:
      Budhi
Hi Tom,

But how would you manage all these when you have a middle tier also.

The situation is like:-

ASP calls com to execute a batch and insert 10 rows to gtt.
After the execution of this call COM will commit and give away the session.

Next time when ASp calls the stored proc for inserting these records into actual
tables from gtt, it will be other session and you might never see the data in
gtt.

Do you mean to say that COM has to do the batch insert and then call the stored
proc and then give away the connection.

Please comment.

          Followup:

if every statement is a separate transaction -- you have a much much much larger
problem then this -- you have a mess.

a page should be a transaction, not ever statment on the page.  if every
statement is -- i'd really have to ask "what the heck ever happened to
transactionality integrity -- did MS really outlaw it"?

      Rating:
      3
  September 12, 2003
Reviewer:
      Budhi
Hi tom,

So the sample way COM does the entire process would be something like this
right? :-

--COM begin trans call here
begin
  insert into temp
  value  (1234, 'scott', 34.2, null, null);

  insert into temp
  value  (1334, 'tiger', 24.2, null, null);

  insert into temp
  value  (3433, 'king', 31.54, null, null);
end;

Call my_store_proc;

--COM commit trans call here

Temp is my gtt.

Here my_store_proc will do the bulk insert or update operation from the gtt to
my regular tables.

Please correct me if I am wrong.

          Followup:

oh my -- hopefully you know what "bind variables" are and you would PREPARE a
single insert statment and execute it over and over with different values

you would NEVER in a billion years build a string like that begin/end block with
inserts like that.  never.

      Rating:
      2
  September 12, 2003
Reviewer:
      A reader
If I bind the variables, is the approach fine?

          Followup:

no, you have three insert statements.

you only want one.

then, it is OK

September 12, 2003 monica -- Thanks for the question regarding
      "Before Delete Trigger to insert into history table , the record being
      deleted.", version 8.1.6.0.0

      You Asked
I have a table called T1.
Now I want to insert a record into the table T1_History whenever
a record is updated or deleted in T1.
How to achieve this? I want to write a Before Update or Delete
trigger on T1 so that before updating or deleting the record in
T1 I can move it into the history table. but, the table gives a
mutating error which is right since I'm trying to query the same
table. But, then how to achieve this functionality.
Pl. help, this is very urgent.

Thanx
And with warm regards
monica

      and we said...
You do not have to query the table to get the data.  You want to
use an AFTER, FOR EACH ROW trigger (it'll be faster and just as
correct)...

Just:

create or replace trigger audit_that_table
after update or delete on that_table
for each row
begin
  insert into history_table ( c1, c2, .., cn )
  values
  ( :old.c1, :old.c2, ..., :old.cn );
end;
/

That'll do it.
Review & Followup

      Rating: 4
how to do for
      all table  January 18, 2002
Reviewer:  Ak
       from India
Hi tom,
 1) My problem is ,i have 500 tables in a schema,i need to
capture old and new value for all this table(when ever any field value for any
table is getting changed),and i don't
want to write a trigger for all this 500 tables seprately.Is there any way for
this other then trigger ??
How about performance in this case ??

2)Is there any way with which I can capture all the update
statement for all the session.

Thanks,
 Regards
   Ak

          Followup:

You need to write a trigger / table -- that is quite simply the way it works.
Triggers are put onto tables, period.

In Oracle9i, you can get the triggering statement using a builtin function.  You
cannot in Oracle8i and before.

      Rating: 4
Follow-up to
      Follow-up for AK  January 18, 2002
Reviewer:
      Steven R. Gerber  from NY, USA
1. How about SQL to gen. SQL? Something like
SELECT
 'CREATE TRIGGER ON ' || t.Table_Name
 ...
FROM
 All_Tab_Cols t
??? I know that the real query would be much better ...

      Rating: 5
Capturing
      Deletes  April 18, 2002
Reviewer:  Chris Soza
       from England
Tom,

How can this be used to capture deletes on a certain condition. for eg: old.c1
like abc% then insert record to history table on delete from main table ?

          Followup:

begin
  if ( :old.c1 like 'abc%' )
  then
     insert into history_table ( c1, c2, .., cn )
     values
     ( :old.c1, :old.c2, ..., :old.cn );
  end if;
end;
/

      Rating: 4
Oracle
      DBA  May 20, 2003
Reviewer:  A reader

Good day Tom,

  Today, I faced a problem, which I haven't seen or read anything about it
before! Here is the issue:

I have the following table:
D820> desc s1_vehicle_processing
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
------------------------------------
 VEHICLE_DISPOSITION_NBR                               NOT NULL NUMBER
 CONTRACT_NBR                                          NOT NULL NUMBER
 RELEASE_NBR                                           NOT NULL NUMBER
 QUANTITY_ENTRY_NBR                                    NOT NULL NUMBER
 SCALE_QUANTITY                                                 NUMBER
 REPORT_QUANTITY                                                NUMBER
 OFFICIAL_QUANTITY                                              NUMBER
 VENDOR_ID                                             NOT NULL VARCHAR2(10)
 APPLIED_SCALE_QUANTITY                                         NUMBER
 APPLIED_REPORT_QUANTITY                                        NUMBER
 SCALE_HOLD_QUANTITY                                            NUMBER
 REPORT_HOLD_QUANTITY                                           NUMBER
 APPLIED_TYPE                                          NOT NULL CHAR(1)
 QUANTITY_ADDED_TO_CONTRACT                                     NUMBER
 GRADE_GOVERN_STATUS                                   NOT NULL NUMBER(5)
 SETTLEMENT_STATUS                                     NOT NULL CHAR(1)
 SCALE_TICKET_INBOUND_OUTBOUND                         NOT NULL CHAR(1)
 INVOICED_STORAGE_FLAG                                 NOT NULL CHAR(1)
 DRYING_FLAG                                           NOT NULL CHAR(1)
 DRYING_AMOUNT                                         NOT NULL NUMBER(20,5)
 ADVANCE_STATUS                                        NOT NULL CHAR(1)
 FREIGHT_FLAG                                          NOT NULL CHAR(1)
 FREIGHT_AMOUNT                                        NOT NULL NUMBER(20,5)
 ADD_DATE                                              NOT NULL DATE
 CHANGE_DATE                                           NOT NULL DATE
 TERM_CODE                                                      VARCHAR2(10)
 DUE_DATE                                                       DATE
 ADVANCE_COUNTER                                       NOT NULL NUMBER
 MARKET_ZONE                                                    VARCHAR2(20)
 MAX_RECEIPT_NBR                                       NOT NULL NUMBER(5)
 INVENTORY_DOLLAR_VALUE                                NOT NULL NUMBER(18,6)
 INVENTORY_CONTRACT_EXCH_RATE                          NOT NULL NUMBER(20,10)

On this table I have a BEFORE UPDATE trigger. When I issue the following command
the first one works and the second one failed. Oracle Error #ora-04020

ALTER TABLE s1_vehicle_processing
    ADD inventory_dollar_value     number(18,6)  DEFAULT 0     not null
/

ALTER TABLE s1_vehicle_processing
    ADD inventory_contract_exch_rate number(20,10) DEFAULT 1   not null
/

I noticed Update trigger became Invalid. and after I recompiled the trigger, the
second statement will work.

I know that because of structure changes, the trigger became invalid, but I
suppose that Oracle recompile it automatically.

Could please tell me what's going on?

Thanks for your time,

          Followup:

no versions, no nothing.  could be a "product issue" in some older release.  I
could not reproduce on 9iR2 in any case:

ops$tkyte@ORA920> create table t ( x int );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create trigger t_trigger before update on t
  2  begin
  3          if ( 1=0 )
  4          then
  5             raise_application_error( -20000, 'x' );
  6          end if;
  7  end;
  8  /

Trigger created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table t add y number(18,6) default 0 not null
  2  /

Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> @invalid
ops$tkyte@ORA920> break on object_type skip 1
ops$tkyte@ORA920> column status format a10
ops$tkyte@ORA920> select object_type, object_name, status
  2  from user_objects
  3  where status = 'INVALID'
  4  order by object_type, object_name
  5  /

OBJECT_TYPE        OBJECT_NAME                    STATUS
------------------ ------------------------------ ----------
TRIGGER            T_TRIGGER                      INVALID

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table t add z number(20,10) default 1 not null
  2  /

Table altered.

I can say, it would be more efficient, specially with the defaults, to do that
in a single statement:

ops$tkyte@ORA920> alter table t add ( y number(18,6) default 0 not null, z
number(20,10) default 1 not null )
  2  /

Table altered.

why make two passes after all... that'll update both columns in a single update
instead of two full scans of the table...

      Rating:
      3
  May 21, 2003
Reviewer:
      A reader
hi tom,

do you think of fine grained auditing when you say that one can get the
triggering statement in 9i?

          Followup:

no, there is a function that will give you the sql text of the triggering
statement in 9i.

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96590/adg14evt.htm#1005274

      Rating:
      4
  May 21, 2003
Reviewer:
      A reader
Hi Tom,

Thanks millions of tons. That's right. It's a version problem. Sorry I forgot to
mention that I am using 8.0.5.

Best Regards,
Arash

      Rating: 5
AFTER vs.
      BEFORE  September 12, 2003
Reviewer:
      A reader
Tom,

In your example you used AFTER UPDATE OR DELETE, could I use BEFORE UPDATE OR
DELETE?

create or replace trigger audit_that_table
after update or delete on that_table
for each row
begin
  insert into history_table ( c1, c2, .., cn )
  values
  ( :old.c1, :old.c2, ..., :old.cn );
end;
/

          Followup:

only if you want to consume more resources -- and perhaps audit the wrong
values....

it is more costly to use a before trigger.

the before trigger might not see the actual "new" values which could be
problematic -- maybe not in your case -- but in general.

September 12, 2003 Alan -- Thanks for the question regarding
      "Private procedures on Object Type", version Oracle 8.1.6 Enterprise
      Edition

      You Asked
  Hi Tom,

  I have two questions for you.

  First - I'd like to know if it's possible to create private
procedures or functions and private variables, like I do with
packages, using object type. I have an example to show this.

create or replace type OBJ_EXAMPLE as object
(

  -- Public variable declarations
  var_1 Varchar2(10),

  member procedure setVar_1(pValue in varchar2),
  -- Public function and procedure declarations
  member function getVar_1(pValue in varchar2) return varchar2

)
/
create or replace type body OBJ_EXAMPLE is

  -- private variable declarations
  Var_Priv Varchar2(10); -- /* Is it possible ? How ?) */

  -- private procedures/functions declarations

  /* It's a private method. Is it possible ? How ? */

  function CheckValueVar(pValue in varchar2) return boolean is
  begin
    if not (pValue is null) then
      return (true);
    else
      return (false);
    end if;
  end;

  procedure setVar_1(pValue in varchar2) is
  begin
    if CheckValueVar(pValue) then
      var_1 := pValue;
    end if;
  end;

  -- Public function and procedure declarations
  function getVar_1(pValue in varchar2) return varchar2 is
  begin
    Return(Var_1);
  end;

end;
/

  Second - I'd like to know of you have a script that free an
instance of a package from RAM memory that is not being used by
a user, that's acessing my home page, wihtout close his web
session. Because when a user begin to navigate in my site the
Oracle allocates every package that the user instances in the
RAM memory and releases it only when his session ends. So what
can I do to free memory without close the user session.

                                          Thanks Tom

                                               Alan

      and we said...
Private member functions and data are not currently supported in
object types.

The method I typically use for private member functions is to
create a package off to the side that operates on this type.
Now, my type body can call this package and pass the object
instance data to it but as long as I do not grant execute on the
package to anyone else -- no one else can execute these packaged
functions.

There is really no way to implement "private" data in object
types however.

You can "deinstantiate" the users package state by calling

   begin
      dbms_session.reset_package;
   end;

That will "unload" all loaded packages (all of them, not juse
some).  The session state will be as if they just logged in.
Any global package variables/cursors will be reset/closed as a
result of this so beware of that.

An alternative is to use "serially reusable" code.  This is code
that will live for the life of a database call.  At the end of
the call -- the package is "deinstantiated".  This allows you to
have some packages that stay loaded for the duration of the
session and others that are removed after each and every call to
the database.  See

http://technet.oracle.com/doc/oracle8i_816/appdev.816/a77069/05_ora.htm#36195
for more information on that topic.

Review &
      Followup

      Rating: 5
private
      attributes  September 12, 2003
Reviewer:
      Leao
I am using 9ir2 and you are mentioning 8i above. Does Oracle now support private
attributes ? Any new hint how to simulate it ?

          Followup:

attributes are currently always public.

September 12, 2003 Dean -- Thanks for the question regarding "Oracle
      Replication", version 9.2.0

      You Asked
There are several tables (one to many relationships) located on one database
(source) that are required to be combined into one large table containing all of
the columns in another database (destination).

I have a view on the initial source database that pulls all the tables and
columns into one record. The table on the destination database will look exactly
like the structure of this view. A materialized view on the destination database
would work however it completely refreshes itself at scheduled intervals (and
this will be millions of records). I would like to have the table data on the
destination database asynchronously refreshed from the data in the source view.
Is this possible to do on an incremental basis using Oracle replication, and if
so, how is this set up?

      and we said...
it can be possible, subject to certain conditions.

see

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96567/repmview.htm#53762
(and the entire book:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96567/toc.htm
) for details and tools.
September
      12, 2003 Doug -- Thanks for the
      question regarding "when do reads reflect multiblock_read_count *
      block_size", version 8.1.7.4

      You Asked
Tom - can I expect ALL read requests from my OS to disk to reflect
multiblock_read_count * block_size or only under certain operations and
circumstances?  I suggested 8174 but would also be interested in 9i.

      and we said...
no, you cannot.

multiblock IO is done ONLY for full scan type operations...

an index fast full scan = multi block io
a full tablescan = multi block io

index range scan = single block io
table access by rowid = single block io

in an oltp system, one would expect mostly single block IO (db file sequential
read)

in a dw you might expect lots more multi-block IO (db file scattered read)
Review &
      Followup

      Rating:
      3
Curious...  September 12, 2003
Reviewer:
      Christo Kutrovsky  from Ottawa, ON Canada
Just out of curiousity

"db file sequential read" and "db file scattered read" seems to have reversed
meanings. I think what they reflect should be reversed.

Was that an error back then, and everyone got used, or I am seeing them from the
wrong side? (i.e. sequential read been reading sequentially from the datafile)

Thanks.

          Followup:

it is an unfortunate naming but they do have meaning.

db file sequential read means "hey, we are reading sequentially, block by
block".  generaly it means "you did an index read, which pointed to another read
to be done in sequence (range scan) and each index entry points to a single
block to be read in sequence"

you are "sequentially reading" a data structure.

db file scattered read means "we are getting a heap of blocks which we'll be
scattering in the buffer cache by hashing their data base block address and
finding the right cache buffer chain to stuff them into"

so there, the scattered refers to how we are storing them in the buffer cache,
not how they were actually read.

      Rating: 4
Not getting
      those results  September 12, 2003
Reviewer:
      Doug  from CT, USA
Tom - I am using truss in unix to track read size requests and during a FTS I am
not getting block_size*multiblock during read requests - I am only getting
blocksize.  Is it accurate to check this with a 10046 level 8 and looking at the
p3 column to see what Oracle is asking for?

          Followup:

i wouldn't use truss, no.

you would use a 10046 level 8 or 12 and look at p3.

September 12, 2003 Carolyn -- Thanks for the question regarding
      "Padding a Long Variable", version Oracle 8.0.5

      You Asked
Tom-

I've coded a procedure to export information from the Oracle
Database to a text file using PL/SQL.  It is necessary to Pad
all the variables (LPAD Numbers with zeros, RPAD varchars with
spaces) for the Mainframe system to read them accurately.

Everything works great except I'm getting the following error
when I attempt to pad a LONG:
DECLARE
*
ERROR at line 1:
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at "SYS.UTL_FILE", line 103
ORA-06512: at "SYS.UTL_FILE", line 222
ORA-06512: at line 311

I've attempted to throw the LONG into a CHAR thinking you just
can't pad a long, but it does not PAD more than 400 spaces.

Can you help?
I'm new to this.
Thanks,
Carrie

      and we said...
I think, based on the line numbers in the stack trace, this is
due to your linesize exceeding the default limit of 1022
bytes/line.  When you fopen the file -- you can specify a
maxlinesize upto 32k.  The default is 1022.

Here is an example showing this and how to solve it.  It takes
any query and dumps it to a fixed length file based on the data
dictionary lengths....

tkyte@ORA806.WORLD> create or replace
  2  function  dump_csv( p_query        in varchar2,
  3                      p_dir       in varchar2 ,
  4                      p_filename  in varchar2,
  5                      p_max_linesize in number default 32000
)
  6  return number
  7  is
  8      l_output        utl_file.file_type;
  9      l_theCursor     integer default dbms_sql.open_cursor;
 10      l_columnValue   varchar2(4000);
 11      l_status        integer;
 12      l_colCnt        number default 0;
 13      l_cnt           number default 0;
 14      l_line          long;
 15      l_descTbl       dbms_sql.desc_tab;
 16  begin
 17      l_output := utl_file.fopen( p_dir, p_filename,
 18                                  'w', p_max_linesize );
 19
 20      dbms_sql.parse(  l_theCursor,  p_query,
                            dbms_sql.native );
 21      dbms_sql.describe_columns( l_theCursor,
 22                                 l_colCnt, l_descTbl );
 23
 24      for i in 1 .. l_colCnt loop
 25          dbms_sql.define_column( l_theCursor, i,
 26                                  l_columnValue, 4000 );
 27
 28          if ( l_descTbl(i).col_type = 2 )  /* number type */
 29          then
 30              L_descTbl(i).col_max_len :=
 31                    l_descTbl(i).col_precision+2;
 32          elsif ( l_descTbl(i).col_type = 12 ) /* date type
*/
 33          then
 34              /* length of my date format */
 35              l_descTbl(i).col_max_len := 20;
 36          elsif ( l_descTbl(i).col_Type = 8 ) /* LONG type */
 37          then
 38              l_descTbl(i).col_max_len := 2000;
 39          end if;
 40      end loop;
 41
 42      l_status := dbms_sql.execute(l_theCursor);
 43
 44      loop
 45          exit when ( dbms_sql.fetch_rows(l_theCursor) <= 0
);
 46          l_line := null;
 47          for i in 1 .. l_colCnt loop
 48              dbms_sql.column_value( l_theCursor, i,
 49                                     l_columnValue );
 50              l_line := l_line ||
                        rpad( nvl(l_columnValue,' '),
 51                              l_descTbl(i).col_max_len );
 52          end loop;
 53          utl_file.put_line( l_output, l_line );
 54          l_cnt := l_cnt+1;
 55      end loop;
 56      dbms_sql.close_cursor(l_theCursor);
 57      utl_file.fclose( l_output );
 58      return l_cnt;
 59  end dump_csv;
 60  /

Function created.

tkyte@ORA806.WORLD>
tkyte@ORA806.WORLD> declare
  2      l_rows    number;
  3  begin
  4      l_rows := dump_csv( 'select * from user_views' ,
  5                          '/tmp',
  6                          'test.dat',
  7                           1022 );
  8  end;
  9  /
declare
*
ERROR at line 1:
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at "SYS.UTL_FILE", line 103
ORA-06512: at "SYS.UTL_FILE", line 306
ORA-06512: at "TKYTE.DUMP_CSV", line 52
ORA-06512: at line 4

tkyte@ORA806.WORLD>
tkyte@ORA806.WORLD> declare
  2      l_rows    number;
  3  begin
  4      l_rows := dump_csv( 'select * from user_views' ,
  5                          '/tmp',
  6                          'test.dat' );
  7      dbms_output.put_line( to_char(l_rows) ||
  8                            ' rows extracted to ascii file'
);
  9  end;
 10  /
1 rows extracted to ascii file

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating:
      3
Error  June 27, 2002
Reviewer:
      jimmy  from Malaysia
I am getting error
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at "SYS.UTL_FILE", line 98
ORA-06512: at "SYS.UTL_FILE", line 157
ORA-06512: at "MSSL.F_USEP_CSV", line 13
ORA-06512: at line 4

The line 4, 13 declares and use utl_file.

          Followup:

did you set the utl_file_dir init.ora parameter.  what is the exact code.

      Rating: 3
Export
      information from the Oracle Database to a text file using
      PL/SQL.  September 23, 2002
Reviewer:  Nenu
       from San Diego, CA - USA
I did try to the below function with p_max_linesize but i am still getting this
error.

ERROR at line 1:
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at "SYS.UTL_FILE", line 82
ORA-06512: at "SYS.UTL_FILE", line 120
ORA-06512: at "SURFPAC_DEV.DUMP_CSV", line 15
ORA-06512: at line 4

Any help would be greatly appriciated.
Thank Much.

          Followup:

Yeah, the stack trace is totally different (error on line 15, NOT 311)

So, the fopen is failing -- I would hazzard a guess that you didn't set the
UTL_FILE_DIR init.ora parameter properly (or at all)

      Rating: 4
Export
      information from the Oracle Database to a text file using
      PL/SQL.  September 24, 2002
Reviewer:  nenu
       from CA, USA
Thanks. I had not set the parameter in the init.ora file.

I had a other question regarding this. I need to schedule a Job from the server,
so that, it calls this function every month on the 7th day and dumps the query
results in a specified directory.

I have little knowledge about this. Could you please suggest what i need to do.
I am very new to all this.

Thanks Much.

          Followup:

You want to read up on DBMS_JOB, documentation is found in the supplied packages
guide:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76936/dbms_job.htm#999107

You can

declare
   l_job number;
begin
   dbms_job.submit( l_job,
                    'your_stored_procedure;'
                     add_months(trunc(sysdate,'month'),1)+6,
                    'add_months(trunc(sysdate,''month''),1)+6' );
   commit;
end;
/

(but make sure to set the relevant JOB_QUEUE_* init.ora parameters! so jobs run
by themselves)

that'll run your procedure on the 7th day of every month at midnight.

      Rating: 4
Export
      information from the Oracle Database to a text file using
      PL/SQL.  September 25, 2002
Reviewer:  nenu
       from CA, SAN DIEGO
I am a bit confused about the setting of the job.
I read we need to set the JOB_QUEUE_PROCESSES init parameter greater than 0 for
the job to be run automatically. Is this the only parameter we set.

Function I call is the dump_csv function which you have described above to dump
the Ascii file. Do I set the IN coming parameters in that function p_query,
p_separator, p_dir, p_filename in the dbms_job.submit, when i call this function
from there. The above parameters(dump_csv function ) are going to be same
always. How do I execute the dbms_job.submit procedure.

Thanks for replying to all my questions.
Regards.

          Followup:

before 9i, you have job_queue_processes = N and N must be greater then zero for
jobs to run by themselves.

you also have job_queue_interval = N and N is in seconds and defines how often
the queue will be checked.  60 is typically a good number.

I thought I did show you how to call dbms_job:

declare
   l_job number;
begin
   dbms_job.submit( l_job,
                    'your_stored_procedure;'
                     add_months(trunc(sysdate,'month'),1)+6,
                    'add_months(trunc(sysdate,''month''),1)+6' );
   commit;
end;
/

just change your_stored_procedure; to

'dump_csv( ''select ....'', ''/blah'', ''test.csv'' );'

      Rating: 4
Export
      information from the Oracle Database to a text file using PL/SQL.
        September 25, 2002
Reviewer:  nenu
       from CA, USA
I get the below error. Do i need to set a value for NO_PARSE in this procedure
call.

Thanks for all the help!
Regards.

SQL>  declare
  2        l_job number;
  3     begin
  4       dbms_job.submit( l_job,
  5   'dump_csv( ''select * from    frms_bor_approps;'', ', ', ''/frmstest'',
''test007.txt'');',
  6             add_months(trunc(sysdate,'month'),1)+6,
  7            'add_months(trunc(sysdate,''month''),1)+6' );
  8         commit;
  9*    end;
 10  /
     dbms_job.submit( l_job,
     *
ERROR at line 4:
ORA-06550: line 4, column 6:
PLS-00306: wrong number or types of arguments in call to 'SUBMIT'
ORA-06550: line 4, column 6:
PL/SQL: Statement ignored

          Followup:

Ok, three things

1) you are missing a quote in there
...approps;'', ', ', ''/frmstes...
...approps;'', '','', ''/frmstes      <<<-should be

2) no semi in the select:

...approps;'', '','', ''/frmstes
...approps '', '','', ''/frmstes      <<<-should be

3) whoops, it is a function, so:

ops$tkyte@ORA920.US.ORACLE.COM> declare
  2        l_job number;
  3  begin
  4       dbms_job.submit( l_job,
  5             'declare
  6                 n int;
  7              begin
  8                  n := dump_csv( ''select * from    frms_bor_approps'',
  9                                 '','',
 10                                 ''/frmstest'',
 11                                 ''test007.txt'');
 12              end;',
 13             add_months(trunc(sysdate,'month'),1)+6,
 14            'add_months(trunc(sysdate,''month''),1)+6' );
 15         commit;
 16     end;
 17  /

PL/SQL procedure successfully completed.

works nicely.

      Rating: 5
Export
      information from the Oracle Database to a text file using
      PL/SQL.  September 25, 2002
Reviewer:  Nenu
       from CA, USA
Thanks Tom, it worked perfect. Couldn't have done it without your help.

Regards.

      Rating: 5
Export
      information from the Oracle Database to a text file using PL/SQL.
        September 26, 2002
Reviewer:  nenu
       from CA, USA
Hi Tom, I ran a test to see if it dump the file in the specified directory. But,
i didn't see the dump file.
I ran a query on dba_jobs, it did show me the next_sec which changed after 2
mins when i ran the same query. So, that means the job did run. So, why don't i
see the dump file in the specified directory.

Thanks Much.
Regards.

select JOB, LAST_DATE, NEXT_DATE, WHAT, THIS_SEC, NEXT_SEC, INTERVAL from
dba_jobs;

declare
          l_job number;
    begin
         dbms_job.submit( l_job,
               'declare
                   n int;
                begin
                    n := dump_csv( ''select sysdate from dual'',
                                   '','',
                                  ''/frmstest'',
                                  ''test008.txt'');
               end;',
              sysdate + 2 / 1440,
             'sysdate + 2 / 1440' );
          commit;
      end;
/

          Followup:

how about failures?  any failures?

whats in your alert log?

      Rating: 5
Export
      information from the Oracle Database to a text file using PL/SQL.
        September 26, 2002
Reviewer:  nenu
       from CA, USA
Tom, this is what is see in my alert log file.
Do you think p_separator is causing problem here. As we just have one field in
the query,"sysdate".

Thanks Much.
Regards.

Thu Sep 26 08:36:16 2002
Errors in file D:\ORANT\RDBMS73\trace\frmsSNP0.TRC:
ORA-12012: error on auto execute of job 2
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at "SYS.UTL_FILE", line 82
ORA-06512: at "SYS.UTL_FILE", line 120
ORA-06512: at "SURFPAC_DEV.DUMP_CSV", line 15
ORA-06512: at line 4

Thu Sep 26 08:39:16 2002
Errors in file D:\ORANT\RDBMS73\trace\frmsSNP0.TRC:
ORA-12012: error on auto execute of job 2
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at "SYS.UTL_FILE", line 82
ORA-06512: at "SYS.UTL_FILE", line 120
ORA-06512: at "SURFPAC_DEV.DUMP_CSV", line 15
ORA-06512: at line 4

          Followup:

what is line 15 of your dump_csv.

If it is the fopen -- you forgot to set up utl_file_dir

      Rating: 5
Export
      information from the Oracle Database to a text file using PL/SQL.
        September 26, 2002
Reviewer:  nenu
       from CA, USA
Hi Tom, It creates a dump file through test procedure for function dump_csv.
But, it doesn't create the dump file when i schedule it through DBMS_JOB.submit.

I did create the util_file_dir init.ora parameter. Also, the job_queue_processes
parameter is set to 1. Do, i need to increase that no. I am running this job
from my machine and the "d:\frmstest" directory exist in the server machine.
Should that make a difference. But, it creates a dump file from my machine when
i run the test proc for dump_csv function.

Sorry, for asking so many questions.
Thanks for all the help.
Regards.

below are the line no.
SQL> 15
 15*    begin
SQL> 16
 16*        l_output := utl_file.fopen( p_dir, p_filename, 'w');

SQL> declare
  2          l_rows    number;
  3      begin
  4          l_rows := dump_csv( 'select FUND, TYPE from frmsfund_xref',
  5                              ',',
  6                              'd:\frmstest',
  7                              'test.dat' );
  8         dbms_output.put_line( to_char(l_rows) ||
  9                               ' rows extracted to ascii file' );
 10     end;
 11  /

PL/SQL procedure successfully completed..

          Followup:

I do not understand something here.

What do you mean "i am running the job from my machine" (where is the job really
running)?

and

"it created a sump file from my machine when ..." (where is that file created)?

you are using windows semantics above, but unix files below:

s$tkyte@ORA920.US.ORACLE.COM> declare
  2        l_job number;
  3  begin
  4       dbms_job.submit( l_job,
  5             'declare
  6                 n int;
  7              begin
  8                  n := dump_csv( ''select * from    frms_bor_approps'',
  9                                 '','',
 10                                 ''/frmstest'',
 11                                 ''test007.txt'');
 12              end;',
 13             add_months(trunc(sysdate,'month'),1)+6,
 14            'add_months(trunc(sysdate,''month''),1)+6' );
 15         commit;
 16     end;
 17  /

I rather suspect the UNIX database where the job is running (different from the
windows database you have been testing with) is NOT setup with utl_file_dir
properly.

Run your tests on the database where the JOB is scheduled to run, not on your
local database!!!!!

      Rating: 5
Export
      information from the Oracle Database to a text file using PL/SQL.
        September 26, 2002
Reviewer:  nenu
       from CA, USA
Hi Tom, I have Window NT system both at client(my machine) and server machine. I
was running the job from my machine logging on to the server. The directory
"d:\frmstest" is created at the server. I did run the job at the server. And I
am getting the same error.
The init.ora file is set with all the parameters required to run the job.

utl_file_dir = d:\frmstest
job_queue_processes = 1
job_queue_interval = 60

Sorry, for all the confusion.
Regards.

          Followup:

then stop using unix paths, you used a unix path in the dbms_job.submit.

You posted:

Thanks Much.
Regards.

select JOB, LAST_DATE, NEXT_DATE, WHAT, THIS_SEC, NEXT_SEC, INTERVAL from
dba_jobs;

declare
          l_job number;
    begin
         dbms_job.submit( l_job,
               'declare
                   n int;
                begin
                    n := dump_csv( ''select sysdate from dual'',
                                   '','',
                                  ''/frmstest'',
                                  ''test008.txt'');
               end;',
              sysdate + 2 / 1440,
             'sysdate + 2 / 1440' );
          commit;
      end;
/

and that is clearly a unix path.

Do this:

select job, what from user_jobs

post that result.

      Rating: 5
Export
      information from the Oracle Database to a text file using PL/SQL.
        September 26, 2002
Reviewer:  nenu
       from CA, USA
Tom, I run the below procedure and I have also displayed the results.
Thanks Much.
Regards.

declare
          l_job number;
    begin
         dbms_job.submit( l_job,
               'declare
                   n int;
                begin
                    n := dump_csv( ''select FUND, TYPE from frmsfund_xref'',
                                   '','',
                                  ''d:/frmstest'',
                                  ''test.txt'');
               end;',
              sysdate + 2 / 1440,
             'sysdate + 2 / 1440' );
          commit;
      end;
   /

Results:

SQL> select job, what from user_jobs;

       JOB
----------
WHAT
--------------------------------------------------------------------------------
         8
declare
                   n int;
                begin
                    n := dump_csv( 'select FUND, TYPE from frmsfund_xref',
                                   ',',
                                  'd:/frmstest',
                                  'test.txt');
               end;

       JOB
----------
WHAT
--------------------------------------------------------------------------------

          Followup:

d:/

whats up with that.

use the same thing as you use with utl_file_dir.  But anyway.  do this for us:

declare
                   n int;
                begin
                    n := dump_csv( 'select FUND, TYPE from frmsfund_xref',
                                   ',',
                                  'd:/frmstest',
                                  'test.txt');
               end;
/

exec dbms_job.run( 8 )

and cut and paste those exact steps from sqlplus.....  if you have access to
v$parameter, also do

show parameter utl

      Rating: 5
is there any
      limitation on how big a long could grow in
      memory?  September 26, 2002
Reviewer:  P
       from LA
hi tom,
we are usig to generate a report and store it in database
when I test my procedure with 40 records it works just fine but as soon as I go
to 50 pages it gives me error
*********
SQL> /
DECLARE
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "NCDBMEL.PKG_SYSTEM_REPORTS", line 86
ORA-06512: at "NCDBMEL.PKG_SYSTEM_REPORTS", line 327
ORA-06512: at "NCDBMEL.PKG_SYSTEM_REPORTS", line 369
ORA-06512: at line 9
***********

well our process is something like this

cursor for loop
 longvar := longvar || append retrieved text to long
 longvar := longvar || carriage return
 if lines > 35 then page break
 if pages = 1000 then insert into table (long variable)
 longvar = null;
end cursor for loop

there could be 100< no. of file < 200 files per report
is there any limitation on using long in stored procedures?

          Followup:

Looks like you've gone over the 32k limit for PLSQL variables -- a long in PLSQL
is really a varchar2(32k)

You probably (definitely) want to use a CLOB.

  insert into table .... values ( ... , empty_clob() )
  returning clob_col into l_clob;

  cursor for loop
    dbms_lob.writeAppend( l_clob, length(text), text );
    if lines > 35 then writeappend page break;
    if pages = 1000 then
       insert into table .... values ( ... , empty_clob() )
       returning clob_col into l_clob;
  end loop

      Rating: 5
Help with
      Errors  September 27, 2002
Reviewer:  Robert
      Ware  from St. Louis, Mo.
Tom,

First, I want to thank you for all that you do and the invaluable information
you have provided us with.  Also, I want to share this with everybody as I have
found it very useful.

When using utl_file, the following exception handling block could help you find
problems with your implementation.

EXCEPTION
  WHEN UTL_FILE.INVALID_PATH then
    RAISE_APPLICATION_ERROR (-20101, 'UTL FILE: Invalid Path');
  WHEN UTL_FILE.INVALID_MODE then
    RAISE_APPLICATION_ERROR (-20102, 'UTL FILE: Invalid Mode');
  WHEN UTL_FILE.INVALID_OPERATION then
    RAISE_APPLICATION_ERROR (-20103, 'UTL FILE: Invalid Operation');
  WHEN UTL_FILE.INTERNAL_ERROR then
    RAISE_APPLICATION_ERROR (-20104, 'UTL FILE: Internal Error');
  WHEN UTL_FILE.INVALID_FILEHANDLE then
    RAISE_APPLICATION_ERROR (-20105, 'UTL FILE: Invalid File Handle');
  WHEN UTL_FILE.WRITE_ERROR then
    RAISE_APPLICATION_ERROR (-20106, 'UTL FILE: OS Error occurred during write
operation');
  WHEN OTHERS then
    v_errorcode := SQLCODE;
    v_errortext := SUBSTR(SQLERRM, 1, 200);
    RAISE_APPLICATION_ERROR (-20999, v_errortext);
end;

These and other utl_file errors are documented in Oracle expert one-on-one pages
1202-1203 and on OTN at:

http://download-west.oracle.com/docs/cd/A87860_01/doc/index.htm

      Rating: 5
Re:is there any
      limitation on how big a long could grow in
      memory?  September 27, 2002
Reviewer:  P
       from LA
thank you tom for your clarification
I have on equestion though if LONG is 32K in PLSQL then
why CLOB should work?
so CLOB doesnt have 32K limitation in PL/SQL?

          Followup:

The clob isn't an "in memory variable".

It is a database object -- when you dbms_lob.write to a clob that you inserted -
you are writing to the database (or temp if it is a dbms_lob.createtemporary
lob)

      Rating: 5
Export
      information from the Oracle Database to a text file using PL/SQL.
        September 27, 2002
Reviewer:  nenu
       from CA, USA
Hi Tom, It works. It dumps the file after every 2 mins. I changed the integer to
number in the dbms_job.submit call.
Thanks for all the help!
Regards.

Here's what i am doing:
declare
          l_job number;
    begin
         dbms_job.submit( l_job,
               'declare
        l_rows    number;
    begin
        l_rows := dump_csv( ''SELECT
                OBLI_MONTH,
                OBLI_FY,
                OBLI_PROGUIC_UIC,
                OBLI_FUND_CODE,
                OBLI_YTD_ADJ_OBS
                  FROM
                 DUMP'',
                            '','',
                            ''d:\frmstest'',
                            ''test09.dat'' );
               end;',
              sysdate + 2 / 1440,
             'sysdate + 2 / 1440' );
          commit;
      end;
/

          Followup:

and fixed the path -- which is ultimately what fixed it for you.

This was blowing up on the fopen call, not even remotely related to the datatype
of l_rows.

      Rating: 5
using CLOB in
      memory  September 27, 2002
Reviewer:  DADA
       from US
tom why are you using insert statement again inside
if pages = 1000 then

**************
insert into table .... values ( ... , empty_clob() )
  returning clob_col into l_clob;

  cursor for loop
    dbms_lob.writeAppend( l_clob, length(text), text );
    if lines > 35 then writeappend page break;
    if pages = 1000 then
       --WHY INSERT and NOT UPDATE
       insert into table .... values ( ... , empty_clob() )
       returning clob_col into l_clob;
  end loop

          Followup:

you'd have to ask P from LA, it's their code.  I was just redoing it with a
clob.

      Rating: 5
long variable
      in plsql  September 27, 2002
Reviewer:  P
       from LA
hi tom,
in my pseudo code i have only 1 insert statement in yours you have 2
I guess his question is why do you have 2 insert statements
-------
dada
we are trying to generate and store report in a oracle table
P

          Followup:

really?

when I read:

cursor for loop
 longvar := longvar || append retrieved text to long
 longvar := longvar || carriage return
 if lines > 35 then page break
 if pages = 1000 then insert into table (long variable)
 longvar = null;
end cursor for loop

I see you are building a page, inserting it when there are 1000 pages.  So,
every 1000 pages -- New row (i ASSUME there must be an insert at the bottom of
your code, else the first 999 pages would go into the great bit bucket in the
sky right)

I inserted a row -- put 1000 pages in it, insert a row, put a 1000 pages in it.

Just like your psuedo code.

but, you should do what you really mean to do, of course.

      Rating: 5
clob in
      plsql  September 27, 2002
Reviewer:  dada
       from US
yes tom there should an insert at the end

otherwise LAST (not first -  i guess you wanted to say last) 999 pages will not
be written to DB
anyway
***
so in your pseudo code when you say
insert ...returning ;
append ;
--append is really update am I right?

          Followup:

correct, thanks.  the "last" 999 pages will not be inserted....

append is

   dbms_lob.writeAppend( to the clob the new text )

Their psuedo had a row for every 1000 pages, that is what I was trying to
emulate.  Insert a row -- writeAppend 1000 pages to the clob, insert a new row,
writeappend 1000 pages to the clob and so on.

      Rating: 5
Export
      information from the Oracle Database to a text file using PL/SQL.
        September 30, 2002
Reviewer:  nenu
       from CA, USA
Hi Tom, The procedure runs on the 7th day of the month at midnight. Were do we
set the Time in the parameters Interval, Next_Date if the procedure had to run
on the 7th day of the month at 5 o'clock in the morning.
Thanks Much.
Regards.

This is what the procedure does now:
 add_months(trunc(sysdate,'month'),1)+6,
 'add_months(trunc(sysdate,''month''),1)+6'

          Followup:

'add_months(trunc(sysdate,''month''),1)+6+5/24'

      Rating: 5
Export
      information from the Oracle Database to a text file using PL/SQL.
        October 01, 2002
Reviewer:  nenu
       from CA, USA
Thanks Much Tom.
Should i use 'add_months(trunc(sysdate,''month''),1)+6+5/24'
only for the parameter Interval or also for the parameter Next_Date.

Regards.

          Followup:

depends, they are different parameters

one says when to first run it, the other says "here is the formula to compute
the next time to run"

if you want the first run at 5am, you need to use that 5/24, else it runs at
midnight.

      Rating: 5
Export
      information from the Oracle Database to a text file using PL/SQL.
        October 02, 2002
Reviewer:  nenu
       from CA, USA
Hi Tom,The procedure needs to run at 5.00 am every 7th day of the month. So, I
need to have both the next_date and the interval as same. I tried running it for
next day and it needn't work. But, when i run it the same day e.g.
sysdate+17/24, it runs.
Thanks Much.
Regards.

The below job didn't work:

declare
          l_job number;
    begin
         dbms_job.submit( l_job,
               'declare
        l_rows    number;
    begin
        l_rows := dump_csv( ''SELECT
                OBLI_MONTH,
                OBLI_FY,
                OBLI_PROGUIC_UIC,
                OBLI_FUND_CODE,
                OBLI_YTD_ADJ_OBS
                  FROM
                 DUMP
                ORDER BY OBLI_PROGUIC_UIC,OBLI_FUND_CODE'',
                            ''     '',
                            ''d:\frmstest'',
                            ''CNSPBORS.txt'' );
               end;',
               trunc(sysdate,'month')+16/24,
             'trunc(sysdate,''month'')+16/24' );
          commit;
      end;
/

          Followup:

               trunc(sysdate,'month')+6+5/24,
             'add_months(trunc(sysdate,''month''),1)+6+5/24' );
          commit;
      end;

I have *no idea* why you were using what you did, no idea at all.

next_date and interval should be *similar* not the same.  next_date needs to
evaluate to NEXT MONTH right?

If you schedule this before the 7th day of the month, the above is what you want
to use.  If you schedule this on  or after the 7th day of the month, they would
be the same.

      Rating: 5
Very helpful
      reply  January 10, 2003
Reviewer:  Rahul
      Priyadarshi  from Mysore,India
Hi Tom,

This solution was very helpful to me as I had coded a similar senario of dumping
a database to flat file.

Though I did not come across this problem as my data size did not exceed the
limit but I will implement this in the code so that such a problem does not
arrise.

      Rating: 4
Line Size
      GreaterTThan 32 k  August 08, 2003
Reviewer:  A
      Reader  from A Reader
hi tom ..
if  line size is greater than 32k then ?

          Followup:

you won't be using utl_file.

      Rating: 4
We mat
      avoid  September 12, 2003
Reviewer:
      Tathagat  from USA
CREATE OR REPLACE FUNCTION Dump_Csv( p_query        IN VARCHAR2,
                       p_separator IN VARCHAR2 DEFAULT ',',
                      p_dir       IN VARCHAR2 ,
                      p_filename  IN VARCHAR2,p_max_linesize IN NUMBER DEFAULT
32000 )
-- 'p_max_linesize' this is due to your linesize exceeding THE DEFAULT LIMIT OF
1022 bytes/line.
-- WHEN you fopen THE FILE
-- you can specify a maxlinesize upto 32k.  The default is 1022.

  RETURN NUMBER
   IS
      l_output        UTL_FILE.FILE_TYPE;
       l_theCursor     INTEGER DEFAULT DBMS_SQL.OPEN_CURSOR;
      l_columnValue   VARCHAR2(2000);
      l_status        INTEGER;
      file_dir          VARCHAR2(40);
       l_colCnt        NUMBER DEFAULT 0;
      l_separator     VARCHAR2(10) DEFAULT '';
      l_cnt           NUMBER DEFAULT 0;
   BEGIN
   -- This line is to avoid error because of wrong p_dir name
   -- utl_file_dir init.ora parameter. Has to be set by DBA and
   -- user should have the privilege to execute this command;
SELECT VALUE INTO file_dir FROM v$parameter WHERE NAME ='utl_file_dir';

       l_output := UTL_FILE.FOPEN( file_dir, p_filename, 'w', p_max_linesize );

      DBMS_SQL.PARSE(  l_theCursor,  p_query,
                                           dbms_sql.native );

      FOR i IN 1 .. 255 LOOP
          BEGIN
              DBMS_SQL.DEFINE_COLUMN( l_theCursor, i,
                                      l_columnValue, 2000 );
             l_colCnt := i;
           EXCEPTION
              WHEN OTHERS THEN
                 IF ( SQLCODE = -1007 ) THEN EXIT;
                 ELSE
                     RAISE;
                 END IF;
         END;
     END LOOP;

      DBMS_SQL.DEFINE_COLUMN( l_theCursor, 1,
                              l_columnValue, 2000 );

     l_status := DBMS_SQL.EXECUTE(l_theCursor);

     LOOP
          EXIT WHEN ( DBMS_SQL.FETCH_ROWS(l_theCursor) <= 0 );
          l_separator := '';
         FOR i IN 1 .. l_colCnt LOOP
             DBMS_SQL.COLUMN_VALUE( l_theCursor, i,
                                   l_columnValue );
             UTL_FILE.PUT( l_output,
                            l_separator || l_columnValue );
             l_separator := p_separator;
         END LOOP;
          UTL_FILE.NEW_LINE( l_output );
          l_cnt := l_cnt+1;
     END LOOP;
      DBMS_SQL.CLOSE_CURSOR(l_theCursor);

      UTL_FILE.FCLOSE( l_output );
      RETURN l_cnt;
  END Dump_Csv;
/

          Followup:

thats not too wise.

utl-file-dir is typically NOT just a directory name.

you should sort of know where you are writing to, period.

      Rating: 4
spool to
      text  September 13, 2003
Reviewer:
      Santhanam  from Bangalore India
Tom nice example

cheers

September 13, 2003 Theodoros -- Thanks for the question regarding
      "Multiple Listeners", version 8.1.6.0.0

      You Asked
Hi,
My question is regarding the automating startup of multiple
listeners. I'm running an Oracle 8.1.6.0.0 on Sun Solaris with
two listeners (LISTENER and EXTPROC_LISTENER).After a server
reboot I noticed that only one listener was started (LISTENER).
Is there a way to start both listeners automatically on server
restart?

The platform is:
SunOS 5.6 Generic_105181-19 sun4u sparc SUNW,Ultra-60
System = SunOS
Node = xxxxxxxxx
Release = 5.6
KernelID = Generic_105181-19
Machine = sun4u
BusType = <unknown>
Serial = <unknown>
Users = <unknown>
OEM# = 0
Origin# = 1
NumCPU = 2

Thanks
Theo.

      and we said...
On SunOS, we use startup and shutdown scripts found in the
/etc/rc*.d directories to make things happen.  Many times, these
just run a script "dbstart" and "dbshut".  I prefer to do the
following myself....

I'll setup as root the startup/shutdown script in /etc/rc3.d and
/etc/rc0.d.  I call mine "/etc/rc3.d/S100oracle" (startup)  and
"/etc/rc0.d/K100oracle" (kill).  These scripts are actually a
symbolic link to the single script /etc/init.d/oracle.  It
contains only:

#!/bin/sh

# Start/stop processes required for oracle

case "$1" in
'start')
    su - oracle8i -c orastart
    su - oraweb  -c webstart
    su - oracle8 -c orastart
    ;;
'stop')
    su - oracle8 -c orastop
    su - oraweb  -c webstop
    su - oracle8i -c orastop
    ;;
esac

As you can see -- I have 3 Oracle accounts on this machine, one
for the webserver and 2 for the 2 different releases of Oracle I
have.  I run simply a script "orastart" to start stuff for them
and "orastop" to stop them.  I write orastart and orastop.  The
nice thing about this is that if oracle8i needs to add/remove
something from its startup/shutdown -- he doesn't need to get
root to update the script, he just modifies orastart/stop.

My orastart script for Oracle8i looks like this:

#!/bin/csh

lsnrctl start

svrmgrl << EOF
connect internal
startup
exit
EOF
ctxsrv -user ctxsys/ctxsys>&/export/home/oracle8i/tmp/ctx.log &

I put that in $ORACLE_HOME/bin for Oracle8i.  It starts the
listener, starts the database and then starts a context daemon.
The shutdown is similar, it looks like:

#!/bin/csh

lsnrctl stop

svrmgrl << EOF
connect internal
shutdown immediate
exit
EOF

If I needed to start more then one listener, I would have the
appropriate commands in there to do so.

This shows how to automate the starting of virtually anything
(not just Oracle stuff) on Solaris....

Review &
      Followup

      Rating: 4
service
      handler(s) in listeners  April 10, 2003
Reviewer:
      reader
Tom,

Can you please explain me the Services Summary...(seen below) in oracle
listeners ... i was thinking till now that, once the client connects to the
server, the oracle creates an handlers to service the request and once the
service is processed the listener will wait for further request... will the
handlers remain till the instance or the listener is shutdown....

please clarify me. ...pls explain me on the actual process of client to server
connection on your words

LSNRCTL>  status
Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=HYD280R.)(PORT=1521)))
STATUS of the LISTENER
------------------------
Alias                     LISTENER
Version                   TNSLSNR for Solaris: Version 8.1.7.1.0 - Production
Start Date                31-MAR-2003 04:30:39
Uptime                    10 days 2 hr. 59 min. 26 sec
Trace Level               off
Security                  OFF
SNMP                      OFF
Listener Parameter File   /u01/app/oracle/product/8.1.5/network/admin/listener.o
ra
Listener Log File         /u01/app/oracle/product/8.1.5/network/log/listener.log
Services Summary...
  DEVEENGC              has 1 service handler(s)
  DEVESCLC              has 1 service handler(s)
  DEVETMCT              has 1 service handler(s)
  DEVETMCT              has 1 service handler(s)
  PLSExtProc            has 1 service handler(s)
  TSTEENWL              has 1 service handler(s)
  TSTEENWS              has 1 service handler(s)
  TSTEENWS              has 1 service handler(s)
  TSTETMCT              has 1 service handler(s)
  TSTETMCT              has 1 service handler(s)
The command completed successfully

Thanks for your great work,..

          Followup:

when a client connects, they talk to the listener for a brief instant in time --
the listeners job is to hand you off to the server.  The server might setup a
dedicated server process for you or let you use a shared server....

Here, you are just see that many instances have registered themselves with this
listener - this listener has an instance per service here to handle requests.
These are the service names you could use in a tns names entry to connect to a
given instance.

      Rating: 4
How to set
      multiple listeners.  April 11, 2003
Reviewer:
      SUBBARAO  from India
Dear Tom,

 I have a database with 700 users accessing. I have only one listener process
running. How to check whether I need to increase the process? If so can you give
us the sample listener file with multiple listeners configured.

Regds
SUBBARAO.

          Followup:

the listener is only involved during login.

do you have problems logging in?

you would use net assistant to set up such a configuration.

      Rating:
      4
  April 11, 2003
Reviewer:
      A reader  from Toronto, Canada
How about using 'sevice' of lsnrctl to see if there are any refusal to
connection requests. If there are, does it mean either another listener or MTS
is needed ?

          Followup:

The "refused" count is bumped whenever the listener is not successful at
passing a connection to another process.  You should look in your listener's
log for specific failure messages.  The issues can range from the serious
(eg., out-of-swap space) to the benign (eg., a direct hand-off to a dispatcher
failed because a conduit for the handoff could not be established so the
listener used redirect instead).

So in general, no -- does not mean another listener or shared server is needed,
in fact -- there  probably aren't enough resources for another listener.

      Rating: 4
redirect
      concept  May 10, 2003
Reviewer:  A reader
       from Houston, TX
Tom,

Could you please clarify when and what circumstance the listener would redirect
a session? Oracle document says, "redirect messages are used, for example, when
dispatchers are remote to the listener". My understanding is that PMON would
register the dispatchers info with the listener when I specify it in the
parameter file. In this context, does remote mean the dispatchers are not
configured in the local server?

Thanks for your time in clarifying this.

          Followup:

redirects are used normally when you use shared server.

you

a) connect to listener
b) listener determines which dispatcher you should connect to
c) listener on SOME platforms can instantly redirect/hand off the connection, on
   others it will
d) return the ip/port to connect to the dispatcher to the client
e) stop the connection
f) the client connects to that dispatcher

note that on windows, this happended even with dedicated server connections
until shared_sockets were implemented and would be common in 8.0 and before.

      Rating: 3
redirect
      concept  May 11, 2003
Reviewer:  A reader
       from Houston, TX
Thanks. I understand what you say. However, I am still confused with dispatchers
being remote to the listener as the doc says,

"redirect messages are used, for example, when
dispatchers are remote to the listener".

Is it like in OPS a listener may have info about dispatchers configured on other
server. In this case, can the listener redirect the connection to other server?
I am just guessing Tom. I would be greatful if I understand what remote means in
this context. Just a curiosity. Thanks.

          Followup:

a listener can be running on a machine totally separate and distinct from the
database itself.

that is what they mean by "are remote to the listener"

common in an OPS/RAC environment where a single listener is servicing MANY
instances -- each instance being on a different machine.

      Rating: 4
Number of
      listeners required?  May 12, 2003
Reviewer:
      Tony  from India
Tom,

How to decide the number of listeners required for a database?. How many
connections should we have per listener?

          Followup:

one listener.

the connections are transient, you connect to a listener to get to a database.
the listener will not have any connections except during the initial login.

      Rating: 4
automate
      startup and shut down listerners on sunOS  May 12, 2003

Reviewer:  Leonard Anukam  from Finland
But how about windows

          Followup:

windex works wonders for keeping them clean.  I find that by using a squeegee,
you can clean them in a small fraction of the time.

(set your services to "autostart" in control panel, read

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/win.817/a73008/toc.htm

      Rating: 4
Listener Load
      Balancing  May 13, 2003
Reviewer:  Tony
       from India
Tom,
    What is Lister load sharing and when should we go for Listener Load
sharing?. Is there any limit on number of concurrent connections Listener
handle?

          Followup:

you switch load balancing and sharing, assume you mean balance.

it is used in a multi-instance setup (like RAC or OPS).  There are N instances,
all equivalent (all get to the same database).  The listener will balance the
load across all available instances so that no single instance gets too much
work and gets overloaded while the others are sitting idle.

      Rating: 4
Dynamic Service
      Registration?  May 13, 2003
Reviewer:  Tony
       from India
Tom,
    What is Dynamic Service Registration? How to enable it?
Is Listenr not required if dynamic service registration is enabled?

          Followup:

read

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/network.920/a96580/concepts.htm#1041193
(in fact, read the entire book -- you'll learn all there is to learn about net8
setup and configuration)

      Rating: 5
Service
      Handler  May 29, 2003
Reviewer:  A reader
       from Pal
What is Service Handler.

          Followup:

it is a named service (instance or set of instances) that registered with a
listener

it is how you connect to the database (well, you can still use a sid but service
is prefered)

      Rating: 4
re:Service
      Handler  May 29, 2003
Reviewer:  JD
       from Pal
What Is diff. between Service handler
when database is in Dedicated/MTS mode.

          Followup:

nothing

a database is never in "dedicated" or "shared server"/MTS mode.

those are methods of connecting, they are not modes the database "runs in". A
database can do both at the same time (they would each have their own service
handler)

      Rating: 4
orastart on
      oracle 9  September 03, 2003
Reviewer:  Andy
       from Germany
Back to your orastart script Tom - to adapt this to oracle 9.1.2, is it simply a
matter of using sqlplus instead of svrmgrl, and logging in as sys instead of
internal?

          Followup:

yup

      Rating: 5
RE: oracle
      9.1.2  September 03, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
> oracle 9.1.2

Is there such a release as 9.1.2?

- Mark

          Followup:

nope.

there was a 9.0 and a 9.2 but there is no 9.1

      Rating: 3
Automatic
      Startup of DB and APPS on Reboot..... Good or Bad  September
      04, 2003
Reviewer:  Robert  from Memphis, USA

Hi Tom,

Can you please help settle a difference of opinion.
Regarding automatic startup scripts to startup the Database and FinApps
processes...

Is there a down side to doing this?

One opinion is that this is a bad idea, because you could have problems if the
database and Apps (e.g. concurrent managers) started up automatically after
being shut down in some kind of crash (e.g. someone trips over the cord to the
Sun box).

What is your opinion of this?

Thanks,

Robert.

          Followup:

tell me what problems they anticipate?

a well designed startup script should be OK -- i mean, a human will make more
mistakes then a script that has been tested.

      Rating: 4
Potential
      Problems with Automatic Restart  September 08, 2003

Reviewer:  Robert  from Memphis, USA
Tom,

Oracle Applications 11.5.9
Oracle Database 9.2.0.3
Solaris 5.8

Here are the concerns as I understand them.

1. Database. Suppose a radical problem happens to the database such as a
corruption, bad disk, deleted datafile, etc. The concern is that when the
database is restarted using the auto start scripts, there could be serious
problems, which could have been avoided if someone had been alerted beforehand,
taken a look at the alert logs, etc., fixed things, then started the database
manually.

2. Oracle Applications. specifically Concurrent Manager jobs. Suppose one or
more CM jobs are running when the database crashes. Suppose these jobs are (1)
in various states of update and/or (2) one in a series of jobs (i.e.: request
set) which have dependencies before and after it. The concern is that when the
CM processes automatically get restarted, the CM will begin processing these
jobs which may either (1) be in an 'unstable' state, or else (2) skip a failed,
unfinished job, which could cause the wrong data to be updated or not updated,
etc.

Thanks,

Robert

          Followup:

tis up to you....  if you don't feel comfortable with it, by all means don't do
it.

the cm jobs shouldn't be in an unstable state, but if you think a human can
glance at it and say "ok to start", that is what you should do I suppose.

      Rating: 5
Can I start a
      remote listener?  September 13, 2003
Reviewer:
      TH  from Manama, BAH
Dear Tom..

1. Can we start a database listener from another machine. (Windows based)?

2. When I connected through a tnsname, listener was required. I tried to connect
without host string it get connected even though the listener was not started.
What is the magic? The listner is not mandatory in a server?(I tried the
connection in the server itself)

Thanx,

Thaha, Bahrain

          Followup:

1) yes, you can  -- using shared server configuration

2) when not connecting over a network, you need no listener.  your client
process will directly "fork and exec" the dedicated server (unix) or get in
touch with the server and have it create a thread (windoze)

September 13, 2003 Saibabu -- Thanks for the question regarding
      "Some Standard Doubts", version 8.1.5

      You Asked
Hi Tom,
   I have a few doubts to clarify:
  1)When Iam creating a table with tablespace option as dba
user,Iam getting the errors :604,1422,6512 at line 12:.Then I
ran the same statement as sys user but it is created in the same
tablespace.what might be the possible reasons.It has generated
trace file for that.
  2)We are running several databases on one Unix box.I would
like to have a sql prompt as database name,so that it is easyto
identify on which database we are running.I know we can
otherwise get it from V$database,Oracle_sid.
  3)According to the documentation and metalink note : We cannot
define constraints or default for columns of object type,LOB,ref
and collections.But we can define it on the object instances of
object table only.Is it correct?If something is missing please
explain.
  4)Why should we need the deferrable and not deferrable
features for constraints.In waht circumstances it is
usefull.Like it should check for constraint for each statement
instead at the end of transaction.
  5)I have taken some tablespaces in read only mode and some in
offline.Iam able to drop a table and to some extent alter a
table.How is it possible.I read that sometimes dropping a table
takes days,doesn't it change the datafile of corresponding
table.
If it is not,then changing system datafile should not take days
just to update extents tables.Clearly my doubt is whether
table's datafile will be written when the table is dropped.It
may not be the case since we are able do it when tablespace is
offline and read only mode.Please explain in more detail on
this.
  6)Does the checkpoint surely occurs for every logfile change?
I didn't get the difference in meaning of
log_checkpoint_interval between pre8 and 8i.In either case
checkpoint occurs after specified no of redo blocks.Let us say
we have a logfile of capacity 1000 blocks,interval set to 100
blocks,before 8 checkpoint should occur for every 100 blocks
means 10 times,and in 8i incremental checkpoint should not lag
by 100 blocks means
10 checkpoints ate every 100 blocks of redo.Where exactly is the
difference between the.Please take your time to answer in
detail.
  7)Where can if find a breaif but important information on
basic tuning,like queries on
v$sysstat,v$sesstat,V$waitstat,v$librarycache,v$rowcache,v$system
_event,v$session_event,v$filestat and what parameters or
statistics basically to look at in general for most of the
databases.Iam looking for some kind of metalink note or some
technet link,something like a 10 to 20 page document for
interview preparations.
 Your cooperation is appreciated.
 Thanks in advance.
  Saibabu

      and we said...
please try to put so many questions in as separate ones -- some
I need more feedback on, some I can answer fully....  It will
also make the subject lines better so that others can pick and
choose what they want to read...

1) would need to see the statement, the full error stack (cut
and pasted from SQLPlus preferably) along with the head of the
trace file.

2) I use a login.sql file that looks like this:

...
column global_name new_value gname
set termout off
select lower(user) || '@' ||
decode(global_name, 'ORACLE8.WORLD', '8.0', 'ORA816.WORLD',
'8i', 'ORA816.US.ORACLE.COM', 'DEV816', global_name )
global_name from global_name;
set termout on
set sqlprompt '&gname> '
...

I have my SQLPATH environment variable pointing to the directory
where this file is and when I log in -- it sets my prompt to the
GLOBAL NAME of the database. "select instance from v$thread" in
place of my global_name query would give you the sid instead.

3) please point me to the requisite documentation you refer to
(support note # and documentation reference).  It is hard to
argue with something you haven't read.  I believe what you are
refering to is the fact that in a TYPE definition you cannot use
constraints and defaults -- you can only use them when creating
a TABLE.  So,

ops$tkyte@DEV816> create type myType as object ( x int default 1
);
  2  /

Warning: Type created with compilation errors.

fails but

ops$tkyte@DEV816> create type myType as object ( x int );
  2  /

Type created.

ops$tkyte@DEV816>
ops$tkyte@DEV816> create table t
  2  ( x int,
  3    y myType default myType(1)
  4  )
  5  /

succeeds.

4) Deferrable constraints where introduced in support of complex
transactions and to permit constraints on Snapshots (aka
Materialized views).  When refreshing a snapshot -- we refresh
table by table.  So, let's say you have snapshots on EMP and
DEPT.  Normally EMP has a foreign key to DEPT but since a
snapshot would refresh EMP fully and then DEPT -- you could not
have a foreign key from EMP to DEPT if the constraints were
checked at the statement level.  You would have the case where
if you added a new department and some employees in it -- we
would add EMP records that did not have a DEPT yet.  Hence, if
you added the foreign key as a deferrable constraint -- we can
set all constraints deferred and then fully refresh EMP and DEPT
and then commit.  The commit will validate the new data (which
will be OK now -- the DEPT we added is there and the EMP records
are no orphans).

Additionally, if you wanted to perform your own update cascade
-- you can defer the foreign key constraints, update the parent
and then update the child records and then set the constraint
mode back to immediate to verify all is well.

Those are two examples.

5) Dropping of a table only requires writing to SYSTEM.  In a
dictionary managed tablespace -- we just update the data
dictionary and the extents are freed.  We do not need access to
the datafile.  In a locally managed tablespace -- the segments
will appear as allocated with file_id.block_id as the segment
name.  Eventually -- when the datafile is writable again, SMON
will clear this entries out.

Again, it is hard to clarify statements without a reference to
the original document -- so the part about "i have read that
someties droppping a table takes days" -- I'll have to defer.  I
can just say I've never seen it or heard of it taking days.  In
dictionary managed tablespace -- a table with thousands of
extents can take a measurably long time (recursive SQL) but not
days -- minutes and maybe even stretching into an hour but not
days.  Again, it is the special case of a table with THOUSANDS
of extents in a dictionary managed tablespace.

6) A checkpoint is initiated for every log switch yes.
See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:344617462392
where I spell out the differences between pre-8i and 8i
behaviour regarding these parameters.  I go into some detail
there.

7) The server tuning guide goes into that in great detail.  It
is what I would recommend.  The Server reference is handy as
well to determine what is in the v$ tables.

Review &
      Followup

      Rating: 3
A
      doubt  December 10, 2002
Reviewer:  Abhijit
       from Mumbai
Hi Tom,
I have a simple question. When I am logged in as sys or system, Oracle doesn't
allow me to create a database.
I am using Oracle 8.1.7 on Windows NT. I am accessing this through SQL Plus from
a Windows NT workstation.

I want to create a database on the "c Drive" of the server. I tried the
following command

CREATE DATABASE newtest

CONTROLFILE REUSE
LOGFILE
   GROUP 1 ('diskb:log1.log', 'diskc:log1.log') SIZE 50K,
   GROUP 2 ('diskb:log2.log', 'diskc:log2.log') SIZE 50K
MAXLOGFILES 5
MAXLOGHISTORY 100
DATAFILE 'diska:dbone.dat' SIZE 2M
MAXDATAFILES 10
MAXINSTANCES 2
ARCHIVELOG

I keep getting insufficient privileges. Could you please explain why do 'sys' or
'system' have insufficient privileges? Do I need to run this from the Server
rather than the workstation?

I want to create a database, a Locally managed tablespace and create a login who
will be in charge of that tablespace.

Could you please help me out?

Cheers!
Abhijit

          Followup:

Well, I would recommend using DBCA but in anycase, you want to be connected AS
SYSDBA -- the administrative account that can start and stop databases, sys and
system cannot.

      Rating: 5
Dropping table
      take days  December 11, 2002
Reviewer:
      Senthil  from Boston, MA
FYI on item 5...

The metalink doc id : 68836.1 gives an example of dropping a table with many
extents would take days.  He may have referred to the example.

      Rating: 4
SYS and
      SYSTEM  September 13, 2003
Reviewer:  TH
       from Manama, BAH
1. What exactly is the difference between SYS and SYSTEM? (I believe SYS is the
owner of all data dictionaries)
Why do we need SYSTEM, if SYS is there?

2. IS SYSDBA different than SYSTEM or SYS? What exactly is SYSDBA and SYSOPER?

Thanks,
TH

          Followup:

1) because we made it so.  they are our accounts, we use them.  much of the data
dictionary is SYS owned, some is SYSTEM owned.

you never need to log in using either accounts credentials -- never. ignore
themn, pretend they do not exist.

2) very very different.

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96521/dba.htm#1064

September 13, 2003 Kenneth -- Thanks for the question regarding "hot
      backup", version 8.0.5

      You Asked
I do  a hot backup within which in the end i reuse my control
file and the spool file.

The timestamp of ctrl file and spool file on NT are changing but
not of the datafiles. It would be incorrect to state that
throughtout the day there isn't any activity on those data
files.

moreover on NT the timestamp of the hot backed up datafiles
always show one prior to the current online backup file.

Infact we tried to create a table for a user in his default TBS
then we only backed up that TBS . The time stamp changed for the
moment but again went back to its old time.
v$backup shows the exact time of the backup but NT OS differs.

Any idea why timestamp on NT is behaving this way.

My another query is
during hot backup how are SCN , data file headers and log
sequence numbers handled , if there are user activity on an
active Tablespace.

Thanks in Advance.
Kenneth

      and we said...
I don't really pay any attention to the file timestamps as they
are not in any way relevant to the operation of the database.
The spool file and control file will be updated more frequently
because we flush to them constantly.  The datafiles on the other
hand are written to in a lazy fashion (in the background) by
DBWR during a checkpoint.  Otherwise -- I can "read and write"
those files without every having physically reading or writing
them.

As for the last part of the question, see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:271815712711
we might be constantly writing to the datafile during a hot
backup -- we just do not update the header of the datafile with
the scn during a checkpoint -- in that fashion the datafile will
request all archived redo log from the point in time of the
beginning of the backup on to recover with.
Review &
      Followup

      Rating: 5
Why is the SCN
      "frozen"  August 19, 2002
Reviewer:  steve
       from Montreal
Hi Tom,

We had this exact problem on Friday night. The Unix server
had a "non-elegant" stop while several tablespace were
in hot backup mode. Startup of the Oracle server revealed
datafiles needing recovery.

All that was required in the end was

  alter database datafile '<fname>' end backup;

My question is why is the SCN number "frozen" on datafiles
in backup mode? Am I right is thinking that, with the
exception of this SCN number, these datafiles are as
up-to-date as they would be had they not been in backup
mode? As far as I can tell, we didn't need any
archived redo log files during startup to bring these
files up-to-date. Or were we just fortunate that all
we needed were the online redo log files?

I was wondering if you could expand a little on
the internals of what's going on in this case.

Thanks Tom.

Steve

          Followup:

that is exactly what the begin backup does -- it freezes the datafile header.

This is so that we know what redo logs we need to apply to a given file to make
it consistent.  While you are backing up that file hot, we are still writing to
it -- it is logically inconsistent.  Some of the backed up blocks could be from
the SCN in place at the time the backup began -- others from the time it ended
and others from various points in between.

Hence, we need to get the redo from the oldest and apply all of that to make the
file consistent again.

You will only ever need online redo log files to recover from an instance
failure like that.  You need archives only when you perform media recovery
(restoring an older, already backed up datafile)

      Rating: 4
Thanks for the
      question regarding "hot backup", version 8.0.5  October 14,
      2002
Reviewer:  Nathan  from Singapore
Hi Mr.Tom..
 Please advise me to take Hot backups.. the steps and files in order for backups
.
regards
Nathan

          Followup:

The steps are easy -- there is one:

a) read and understand the contents of

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76993/toc.htm
and there you go.  This is so important, I'm not going to give you a simple 5
line answer.  You really need to understand it entirely.

      Rating: 4
Thanks for the
      question regarding "hot backup", version 8.0.5  October 14,
      2002
Reviewer:  Nathan  from Singapore
Hi Mr.Tom..
 Please advise me to take Hot backups.. the steps and files in order for backups
.
regards
Nathan

      Rating: 4
Hot backup on
      OSF UNIX   January 02, 2003
Reviewer:
      Mahomed  from U.K
Hi Tom,

I am doing hot backup on a unix system with ADVFS file system as follows:
1. Set tablespaces in hot backup mode.
2. use operating system commands to copy data files.
3. Set end backup for tablespaces.
4. force a log switch.
5. Copy archive logs.

My question is , will this method work for UFS file system,
as I understand UFS does not allow you to copy files if they are open.

Thanks

          Followup:

UFS sure does.  NTFS - you need to use ocopy.

      Rating: 5
size of hot
      backup  January 12, 2003
Reviewer:  atul
       from India
Sir,

I want to calculate size of hotbackup..
we take hot backup on tape in compress format..
for that..

i did,

select sum(bytes) from dba_data_files;

answer is=3.5438E+10 ie 35GB
compress means 17 GB
so,is that correct way to calculate size???

Thanks.
atul

          Followup:

you would have to find out how many blocks, segment by segment, were below the
hwm for the segments.

search for

show_space dbms_free_space

on this site for script that'll do it (but, I would just have 35gig of tape
ready and go for it knowing the backup will be 35gig or less).  rather then
spend all of that time trying to compute an estimate.

      Rating: 5
I have a
      situation in live server  June 19, 2003
Reviewer:
      Ismail  from Chennai
I used to take hot backup twice a week. And having last 3 backups and enough
archive log backups for last 15 days. Now my datafile is having a problem now.
I took backup using
alter tablespace <tsname> begin backup;
using os command copy the datafiles with respect to Tablespace
alter tablespace <ts name> end backup;
repeated for all tablespaces

Now. what is the way for me to recover the same.
Can I do like this
1. Copy the datafile backuped to original location
2. applying the archive log

Can I have the steps for doing it?. Pl help me out.

          Followup:

kidding right?  you are learning recovery - the only job a DBA is not allowed to
mess up -- in real time?

Please contact support -- have them walk you through it (too many nuances, I'm
not going to risk it here).

then, immediately sign up for some training or hire someone who has this skill
down pat.

      Rating: 5
Pl. paste the
      whole script you have used to take backup.  June 19, 2003

Reviewer:  Sikandar Hayat  from Pindi Bhattian -
      Pakistan

      Rating:
      5
done  June 20, 2003
Reviewer:
      Ismail  from Chennai, India
Our oracle server is in AMC for next 1 year. I have been asked take up the job
after AMC gets completed. Hence I am interested in learning this (and
theortically went for courses and reading books).

Persons from company who renders service, is not open to me with their steps.

Hence I want to know from you and also can get the knowledge from the persons,
when they are doing the same in our live server. (sorry for not telling this in
first instance)

Hence please give your steps are give me suitable link.

          Followup:

huh?

      Rating:
      5
  June 20, 2003
Reviewer:
      A reader
This is for Ismail, please give a search and you will find lots of info as well
as links that Tom has pointed..

      Rating: 5
Using
      Sequential Device while taking hot backup  August 27, 2003

Reviewer:  Himnish Narang  from Chandigarh, INDIA

Hi Tom,

I have following queries regarding the hot backup.

While taking the hot backup can we specify the archivals to go on a sequential
device like TAPE / DAT etc. (by setting the init parameter). What will happen
when we give 'alter tablespace end backup;' command? Will the system
automatically takes the archivals from TAPE/DAT or we will have to copy down the
archivals on to disk and then apply.

Please clarify. Thanx for your time.

          Followup:

we do not archive to tape.

ending backup doesn't need to access any archives.  we have no need of any
archived redo log unless you are actually performing a media recovery.

      Rating: 4
restoring to
      another box requires archives much earlier than the earlist hot backup
      file  August 27, 2003
Reviewer:  Pat
       from MD USA
Hi Tom,

I was restoring the production database to another box using hot backup files. I
recreated the control file because file placement on 2 boxes are so different it
is better I just change them all together. I then recovered the database using
archives copied from production server and opened the database resetlogs. So
everything went fine.

The only question I have is: my hot backups are done in batches, but the
earliest backup files have the timestamp as, say 8/19/03 6:45pm. So when I
started recovery, I thought it will ask for archive log after that, but instead,
it asked for archives dating far back than that, say 8/17/03 7:05pm. Is there
any logic to this? I know that you don't pay much attention to timestamp of the
files, but if you don't, how can you estimate what archives you need to keep?

Your input is most appreciated.

          Followup:

not that i don't believe you but.... can you double check.

when you put the tablespace into backup mode, it checkpoints.

when you back it up -- you need to keep all archives generated from right at the
time you said "begin backup" on.

      Rating: 4
Undo Tablespace
      size  September 13, 2003
Reviewer:  Mahomed
      Suria  from U.K
Hi Tom,

I am running Oracle 9.2.0.1.0 on HP Unix V5.1 Alpha.
My undo tablespace has grown to very large size
as shown below. As a consequence, the hot backup takes 8 to 9 hours. My
questions are:

1. Is it correct for the file to be so large?
2. Can make it smaller and how do I work out
   what is the optimum size?
3. Do I need to backup all the tablespaces?
   Currently, I do not backup the HIST tablespaces
   which are read - only

SQL> select tablespace_name,sum(bytes)/(1024*1024) from dba_data_files
  2  group by tablespace_name
  3  ;

TABLESPACE_NAME                SUM(BYTES)/(1024*1024)
------------------------------ ----------------------
DRSYS                                              20
INDX                                               25
LIVE_DATA                                        1024
LIVE_HIST                                         512
LIVE_HIST_INDEX                                   512
LIVE_INDEX                                        512
SYSTEM                                            350
TOOLS                                              10
UNDOTBS1                                         8800
USERS                                              25
XDB                                            38.125

11 rows selected.

Thanks,
Mahomed

          Followup:

it takes 8 to 9 hours to copy that much (little) data????

well, it is easy enough to "shrink" your undo -- i'm assuming system managed
undo here -- it would be:

ops$tkyte@ORA920> create undo tablespace undo_new
  2  datafile size 100m autoextend on next 1m maxsize 2048m;

Tablespace created.

ops$tkyte@ORA920> alter system set undo_tablespace = undo_new;

System altered.

ops$tkyte@ORA920> drop tablespace undotbs1;

Tablespace dropped.

and that'll take your largest tablespace out of the picture.  note the drop of
your old undo tablespace won't work if there are active transactions in it -- so
try to do this when the system is near idle if possible (or just wait a bit for
it to empty out after doing the alter system)

You can search this site for maxshrink as well to find a script that will resize
your files as small as possible

(but if your throughput on backup is less then 2gig per hour as you say -- well,
you have a problem there that needs fixing as well)

September 13, 2003 -- Thanks for the question regarding
      "Clarifications on closing cursor and cursor bind variables", version 8.0

      You Asked
Hello Tom,

  I have couple of basic questions. Wanted to clarify them

1) When using for cursor_name to open the cursor, Do I need to explaicilty close
it once I get all the rows in a loop?

2) Have a number table type (called NUM_TABLE) defined in database.

   If i've a cursor like the following.

    cursor c
    slect emp_name
    from  emp
    where emp_id in (
          SELECT * FROM TABLE (CAST(p_emp_ids as NUM_TABLE))
      );

   Every time I execute this preocedure, I need to diffrent set of
   emp ids in p_emp_ids table.
   Wondering if it uses bin variable for p_emp_ids insternally so that
   this sql does not need to be parsed every time I execute this.

   Could you please clarify if this is considered as bind variable or not?

      and we said...
1) depends on HOW you do it

declare
   cursor c as select * from emp;
begin
   for x in c
   loop
       ...
   end loop;
end;
/

nope, you don't close it, it is done for you.

declare
   cursor c as as select * from emp;
begin
   open c;
   loop
      fetch c into ...
       exit when c%notfound;
   end loop
   CLOSE C;
end;
/

yup, you have to close it.  if you open it -- you close it.  I almost NEVER use
open/close (ref cursors and bulk collect being the ONLY two times I do).

I try to ALWAYS use

  for x in ( select .... )

or

  for x in CURSOR_NAME

I like for x in (select .... best of all by far.

2) it'll definitely be a bind variable.
September 13, 2003 Pauline -- Thanks for the question regarding
      "utllockt.sql in 9imet", version 9.2.0

      You Asked
Tom,
Utllockt.sql is the useful script to show lock tree if there is any lock. In
oracle8i, since we may use server manager to run it, we may see very good ourput
for the result. But in 9i, server manager is no longer available, but it seems
that Oracle doesn't modify utllockt.sql to adapt SQL*Plus in 9i--to have good
output. Could you tell is there any alternative way to run it in Oracle9i
SQL*Plus and have the same good output as 8i running by server manager ?
Also if you may provide some other good scripts to show locks, it will be
appreciated.

As always, thanks for all your great help.

Pauline

      and we said...
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:839412906735
ctl-f for

is blocking

to see a very simple query.  no hierarchy -- but you typically do not need that
(and you could easily adapt to a hierarchy if you wanted)

but for utllockt, a simple:

column waiting_session format a17

"fixes" it.  it was just a "set charwidth 17" that wasn't working.
Review &
      Followup

      Rating: 5
Excellent
      answer   September 14, 2003
Reviewer:
      Pauline  from NY,USA.

September 14, 2003 Mahendra -- Thanks for the question regarding
      "Oracle's Object Relational Model", version 8.1.7

      You Asked
Dear Tom,

"How fair to use Oracle's Object Relational Model in
commercial application development like Production
Control System?"

What impression I am having regarding Oracle's ORDBMS
model is, it is not widely accepted in applications
like “Production Control System”. I haven’t seen a
single site having implemented ORDBMS approach into
commercial application development. In fact there are
many limitations at Database level (Oracle 8i) as well
as at front-end tools. Some of those have been
resolved in Oracle-9i, but still I doubt, whether it
will be fruitful in all aspects like Space Management,
Backup & Recovery, Performance etc.

Thanks in advance,

Mahendra Jadav.

      and we said...
Well, it won't affect backup and recovery at all since objects are just tables
in the end.

Performance wise and space mgmt -- you have to look at it this way.  Objects are
generic, they can hold anything.  You can of course come up with infinitely more
precise and efficient structures for YOUR specific problem.  Its just like a
language -- I can write assembler code that will blow away C at runtime, I can
write C code that will blow away Java at runtime, and so on.  The thing is -- I
can write the java must faster then the C code and the C code much faster then
assembler.  I'm trading off performance and size (ram in the case of a language)
for ease of development and time to market.  Objects in the database -- same
thing.  I call it "syntactic sugar" cause its just really tables underneath.

I personally find the OR stuff very very useful in my PLSQL -- I use it
constantly in my programming.  I tend not to use the physical structures -- I
use plain old tables with plain old columns.  I will use OR views to give me
easier to use data structures in my code however.

Review &
      Followup

      Rating: 3
Thanks for your
      fast response  December 30, 2001
Reviewer:
      Mahendra Jadav  from INDIA
Thanks a lot for your fast response.

I am not getting your statement

"I tend not to use the physical structures -- I
use plain old tables with plain old columns.  I will use OR views to give me
easier to use data structures in my code however "

Here what do you mean by plain tables? In fact, I am talking about Object tables
through out the database design.

Thank You.

Mahendra Jadav.

          Followup:

given

create type scalarType as object ( x  int, y int );
create type tableType as table of scalarType;
create type objectType as object ( a int, b date, c tableType, d scalarType );

I would not:

create table T of objecttype nested table c store as t_c;

I would instead create regular "old fashioned" (use my own physical structures)
tables with primary keys, foreign keys and then create an OBJECT RELATIONAL VIEW
over them.
I prefer to have total control over the physical structures, I do not want an
extra 16byte raw added to the parent table with a unique constraint and a 16
byte raw added to the child table when there most likely exists a perfect
NATURAL way to link these tables....

      Rating: 3
Good
      response  December 31, 2001
Reviewer:  Anu
      Kohli  from India
Tom,
That was a good response.
What I understand from your response is that use of objects results in ease of
usage but results in performance depreciation, Right???
Are there any cases where use of Object Oriented and Object relational
technology can lead to performance improvement or better space management?

          Followup:

Well, if you use OR views, you are using OR technologies and you can get good
space managment as well as performance.  Once of the cool things is the ability
to one ONE trip to the server -- get a "jagged" result set.  Usually result sets
are square -- rows/columns.  With objects, they can be jagged.  I can
materialize an object that has a single "row" for the DEPARTMENT, an ARRAY of
employees, an ARRAY of projects the department is working on and so on.  A
result set that would require AT LEAST 2 queries in a pure relational model and
would typically bring back the DEPARTMENT information for every employee (not
just once).  So yes, there can be savings in that fashion.

      Rating: 4
Little OO
      problem...  May 22, 2003
Reviewer:  Piotr
      Jarmuz  from Braunschweig, Germany
How can I call an overridden method inside the overriding method in PL/SQL?

e.g.
in java I'd use:

void method(int x) {
  super.method(x);
  ...
}

in C++:
virtual void method(int x) {
  BaseClass::method(x);
  ...
}

How to achieve this in PL/SQL?

Regards

          Followup:

you cannot currently.

      Rating:
      2
Hmmm...  May 23, 2003
Reviewer:  Piotr
      Jarmuz  from Braunschweig, Germany
So it really means that OO extensions in PL/SQL are of VERY, VERY limited use
(at least to me and at least now)...

Sure one can use them but anything requiring non-trivial class hierarchy where
one could slightly reimplement the functionality of a base class in a derived
class is virtually impossible (or maybe there are some tricks to achieve this?)

I believe that lack of possibility to make such calls is a language design flaw
(omission?) not a pre-meditated feature ;) Cause technically it should not be
that hard to fix it basically in ~ 100 lines of source code for the PL/SQL
compiler. Do you think if the next release will have this fundamental feature
properly implemented?

Thank you anyway, bad information sometimes is better than no information at
all...

Regards

      Rating: 5
Dont Shoot the
      Messenger  May 30, 2003
Reviewer:
      A reader
Piotr, do you mean bad *news* is better than no news? I agree, because knowing
the bad news at least lets you prepare for alternatives...

      Rating: 2
Never meant to
      shoot anyone :-)  May 31, 2003
Reviewer:  Piotr
      Jarmuz  from Braunschweig, Germany
But yes I meant that I can at least stop scratching my head
and stop looking for non-existent solution...

Hey! But seems like I have found an akward, ugly and dirty solution to my
problem... Small sample of what and how I do:

CREATE OR REPLACE type person_ot
is object
(
   firstname varchar2(100),
   lastname varchar2(100),

   member procedure print,
   member procedure do_print,
   static function make return person_ot
) not final
/
CREATE OR REPLACE type body person_ot
is
   member procedure do_print
   is
   begin
        p('Firstname=' || firstname || ' Lastname=' || lastname);
   end;

   member procedure print
   is
   begin
           do_print;
   end;

   static function make return person_ot
   is
   begin
           return person_ot(null,null);
   end;
end;
/

CREATE OR REPLACE type user_ot under person_ot (
   name varchar2(30),
   password varchar2(30),
   overriding member procedure print
);
/
CREATE OR REPLACE type body user_ot
is
   overriding member procedure print
   is
   begin
    self.do_print;
    p('Name=' || name || ' Password=' || password);
   end;
end;
/

Ugly? Sure but it works :-)))
Right now it is a matter of habit I must simply remember to call do_XXX()
instead of XXX() directly whenever I want to override XXX()... But anyway I hope
that Oracle development team will fix it in the next release.

Regards

      Rating: 5
table design
      question  July 23, 2003
Reviewer:
      A reader
Hi Tom
Which is better and why?
1.

CREATE TYPE type_object AS OBJECT (x integer );
CREATE TABLE type_object_table OF type_object;

2. CREATE TABLE normal_relational_table( x integer );

In the first alternative, I have an object type and I am
creating a table of that object type. in the second
one, I am creating a normal relational table with
effectively the same information.

What are the pros and cons of each approach? (assume
thay in the object we are not using any methods etc.)

Thank you - hope you are having a lovely vacation!!

          Followup:

#2 seems to have less code doesn't it.

I never use object types for persistant stored objects - in plsql, all over the
place.  as a storage mechanism -- no.

      Rating: 5
object table
      versus relational table  July 23, 2003
Reviewer:
      A reader
one disadvantage could be I cant create primary key
or fk or any other constraints on the object table (
at least I dont of a method)
You also mention in your book that object tables
(actually nested tables which are not exactly object
tables) get stored as relational underneath - with
lots of magic that can surprise you (e.g. hidden primary
keys in nested table etc) That could also be a disadvantage.

          Followup:

you can have primary/foreign/etc etc on an object table.

      Rating:
      5
  September 14, 2003
Reviewer:
      A reader
"Hi Tom
Which is better and why?
1.

CREATE TYPE type_object AS OBJECT (x integer );
CREATE TABLE type_object_table OF type_object;

2. CREATE TABLE normal_relational_table( x integer );

In the first alternative, I have an object type and I am
creating a table of that object type. in the second
one, I am creating a normal relational table with
effectively the same information.

What are the pros and cons of each approach? "

OK - I decided to run some benchmarks on the above scenario.

I created the following schema:

-------
drop table object_table;
drop table relational_table;
drop type type_object;
create type type_object as object ( x integer, y varchar2(50), z date );
/
create table object_table of type_object;
create table relational_table( x integer, y varchar2(50), z date );
alter table object_table add constraint object_table_pk primary key ( x );
alter table relational_table add constraint relational_table_pk primary key ( x
);

-------

Then I ran the following benchmark
---
set echo on
declare
  num_of_records int default 100000;
begin
  runstats_pkg.rs_start;
  insert into object_table
  select rownum, 'xxxyyy'||rownum, sysdate + 20
  from all_objects, all_objects
  where rownum <= num_of_records;
  runstats_pkg.rs_middle;
  insert into relational_table
  select rownum, 'xxxyyy'||rownum, sysdate + 20
  from all_objects, all_objects
  where rownum <= num_of_records;
  runstats_pkg.rs_stop(200);
end;
/
commit;
exec show_space ( 'RELATIONAL_TABLE' )
exec show_space ( 'OBJECT_TABLE' )
-- benchmarking queries (only with primary key based )
declare
  lower_value int default 100;
  upper_value int default 102;
  num_of_records int default 100000;
begin
  runstats_pkg.rs_start;
  for i in 1 .. num_of_records loop
    for x in ( select * from object_table where x between lower_value and
upper_value)
    loop
      null;
    end loop;
  end loop;
  runstats_pkg.rs_middle;
  for i in 1 .. num_of_records loop
    for x in ( select * from relational_table where x between lower_value and
upper_value)
    loop
      null;
    end loop;
  end loop;
  runstats_pkg.rs_stop(200);
end;
/
set echo off

---

The benchmark compares inserts and space taken. I did try
to compare selects but I compared only one scenario
using the primary key.

the results of the run are:
----
benchmark@apps> @benchmark
benchmark@apps> set echo on
benchmark@apps> declare
  2    num_of_records int default 100000;
  3  begin
  4    runstats_pkg.rs_start;
  5    insert into object_table
  6    select rownum, 'xxxyyy'||rownum, sysdate + 20
  7    from all_objects, all_objects
  8    where rownum <= num_of_records;
  9    runstats_pkg.rs_middle;
 10    insert into relational_table
 11    select rownum, 'xxxyyy'||rownum, sysdate + 20
 12    from all_objects, all_objects
 13    where rownum <= num_of_records;
 14    runstats_pkg.rs_stop(200);
 15  end;
 16  /
Run1 ran in 2784 hsecs
Run2 ran in 2258 hsecs
run 1 ran in 123.29% of the time

Name                                Run1      Run2      Diff
LATCH.cache buffers lru chain                455               229
-226
STAT...buffer is not pinned co           228,342           228,090
-252
STAT...change write time                     384               128
-256
LATCH.redo writing                           533               266
-267
STAT...recursive cpu usage                 2,599             2,264
-335
STAT...leaf node splits                      529               188
-341
STAT...calls to kcmgas                       657               267
-390
STAT...calls to kcmgcs                     1,176               601
-575
LATCH.messages                             1,236               660
-576
LATCH.session allocation                     641                21
-620
LATCH.shared pool                          1,216               595
-621
STAT...immediate (CURRENT) blo             1,046               372
-674
STAT...commit cleanouts                    1,143               431
-712
STAT...commit cleanouts succes             1,143               431
-712
LATCH.list of block allocation             1,207               430
-777
STAT...calls to get snapshot s             1,504               640
-864
LATCH.transaction allocation               1,749               635
-1,114
LATCH.row cache objects                   39,453            38,315
-1,138
STAT...consistent gets - exami           137,897           136,714
-1,183
STAT...recursive calls                     3,487             2,047
-1,440
LATCH.checkpoint queue latch               3,154             1,578
-1,576
STAT...free buffer requested               2,770             1,178
-1,592
LATCH.resmgr group change latc             3,852             2,046
-1,806
LATCH.resmgr:schema config                 3,852             2,046
-1,806
STAT...consistent gets                   427,249           425,244
-2,005
STAT...enqueue releases                    4,846             2,506
-2,340
STAT...enqueue requests                    4,847             2,507
-2,340
LATCH.undo global data                     5,198             1,922
-3,276
STAT...session uga memory                  8,472             4,116
-4,356
LATCH.enqueue hash chains                 10,148             5,138
-5,010
LATCH.library cache                       10,124             4,520
-5,604
STAT...redo entries                       11,697             5,534
-6,163
LATCH.redo allocation                     12,281             5,786
-6,495
LATCH.enqueues                            18,718             9,664
-9,054
STAT...db block gets                      17,610             7,723
-9,887
STAT...db block changes                   21,853            10,096
-11,757
STAT...session logical reads             444,859           432,967
-11,892
LATCH.cache buffers chains               795,051           748,061
-46,990
STAT...session pga memory max            248,424                 0
-248,424
STAT...session pga memory                249,136           -14,696
-263,832
STAT...redo size                      22,223,108         9,440,724
-12,782,384

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
909,403           822,203           -87,200 110.61%

PL/SQL procedure successfully completed.

Elapsed: 00:00:51.02
benchmark@apps> commit;

Commit complete.

Elapsed: 00:00:00.02
benchmark@apps> exec show_space ( 'RELATIONAL_TABLE' )
Free Blocks.............................               5
Total Blocks............................             436
Total Bytes.............................       3,571,712
Total MBytes............................               3
Unused Blocks...........................              20
Unused Bytes............................         163,840
Last Used Ext FileId....................               1
Last Used Ext BlockId...................          54,389
Last Used Block.........................             120

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.35
benchmark@apps> exec show_space ( 'OBJECT_TABLE' )
Free Blocks.............................               5
Total Blocks............................             957
Total Bytes.............................       7,839,744
Total MBytes............................               7
Unused Blocks...........................             305
Unused Bytes............................       2,498,560
Last Used Ext FileId....................               1
Last Used Ext BlockId...................          53,769
Last Used Block.........................              10

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.31
benchmark@apps> -- benchmarking queries (only with primary key based )
benchmark@apps> declare
  2    lower_value int default 100;
  3    upper_value int default 102;
  4    num_of_records int default 100000;
  5  begin
  6    runstats_pkg.rs_start;
  7    for i in 1 .. num_of_records loop
  8       for x in ( select * from object_table where x between lower_value and
upper_value)
  9       loop
 10         null;
 11       end loop;
 12    end loop;
 13    runstats_pkg.rs_middle;
 14    for i in 1 .. num_of_records loop
 15       for x in ( select * from relational_table where x between lower_value
and upper_value)
 16       loop
 17         null;
 18       end loop;
 19    end loop;
 20    runstats_pkg.rs_stop(200);
 21  end;
 22  /
Run1 ran in 5346 hsecs
Run2 ran in 5319 hsecs
run 1 ran in 100.51% of the time

Name                                Run1      Run2      Diff
LATCH.checkpoint queue latch               1,119               866
-253
LATCH.resmgr group change latc               975               586
-389
LATCH.resmgr:schema config                   975               586
-389
LATCH.redo allocation                      1,104               670
-434
LATCH.enqueue hash chains                  2,544             1,569
-975
LATCH.session allocation                   2,036               786
-1,250
LATCH.row cache objects                    1,906               331
-1,575
LATCH.enqueues                             4,598             2,846
-1,752
LATCH.library cache                      212,070           206,695
-5,375
LATCH.multiblock read objects              9,342                 0
-9,342
LATCH.cache buffers lru chain             32,471                10
-32,461
LATCH.cache buffers chains             1,613,024         1,505,997
-107,027

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
1,885,153         1,723,088          -162,065 109.41%

PL/SQL procedure successfully completed.

Elapsed: 00:01:47.23
benchmark@apps> set echo off
benchmark@apps> spool off

----

Following are my conclusions:
1.Insert into Object table took 23% more time (this %age was consistent for
different runs ranging from 20% to 30% )
2. Insert into Object table used 11% more latches (this %age was consistent for
different runs ranging from 11% to 15% )
3. Insert into object table took more than twice the amount of space as compared
to insert into relational table. This increase in space is due to internal
columns being generated by Oracle as shown in the select statement results below
(from Tom Kyte's book "Expert one on one Oracle".) As you can see for an object
table there are two additional columns SYS_NC_OID$ ( a system generated object
id of type raw(16)with a unique constraint and a unique index on it) and
SYS_NC_ROWINFO$ ( a virtual column used to get scalar elements as an object.)
This unique index explains the extra redo generated during the inserts as shown
in point "a" above for object table.
benchmark@apps> set echo on
benchmark@apps> @show_cols OBJECT_TABLE
benchmark@apps> select name, length
  2  from sys.col$
  3  where obj# =
  4  (
  5    select object_id
  6    from user_objects
  7    where object_name = '&1'
  8  );
old   7:   where object_name = '&1'
new   7:   where object_name = 'OBJECT_TABLE'
SYS_NC_OID$                            16
SYS_NC_ROWINFO$                         1
X                                      22
Y                                      50
Z                                       7

Elapsed: 00:00:00.01
benchmark@apps> @show_cols  RELATIONAL_TABLE
benchmark@apps> select name, length
  2  from sys.col$
  3  where obj# =
  4  (
  5    select object_id
  6    from user_objects
  7    where object_name = '&1'
  8  );
old   7:   where object_name = '&1'
new   7:   where object_name = 'RELATIONAL_TABLE'

X                                      22
Y                                      50
Z                                       7

4. There does not seem to be much difference in the select's run time. I
verified with tkprof where both of them generated the same plan and had the same

number of logical IOs. The differences in latches seems to be incidental - I ran
just the select portion multiple times - and the time taken as well as the
latches consumed were more or less the same for object table and relational
table. Anyways, the select test is definitely not comprehensive and needs to be
further tested with different scenarios to arrive at any conclusion.
5. In the end, we can conclude that in the above case
relational table is the way to go.

Tom, could you kindly give your comments on this benchmark
and my conclusions?

Also, I have one question:
Can you please explain a little more about what the hidden column
SYS_NC_ROWINFO$ is - how is it a "virtual" column and how exactly it gets used.

Thanx!
Menon:)

          Followup:

if you have my book expert one on one oracle -- read the chapter on object
stuff.

the answer is easy -- i never use object types to create persistently stored
objects.  I always use relational tables.  I use the object extensions only when
programming (in plsql)

      Rating: 5
thanx
      Tom  September 14, 2003
Reviewer:  Menon

Yes, I do have both your books and I know your philosophy
related to how you use object tables:)

I thought I would just run these results by you since
you have a sharp eye for mistakes and also it may be
useful for others.

Thanx for a great web site!

Menon:)

September 14, 2003 Pradeep -- Thanks for the question regarding
      "Segment Level Statistics", version 9.2.0.3

      You Asked
Could you please elaborate these with some examples ?

i. Segment level statistics - How they are used in tuning the queries ?

ii. PLAN_HASH_VALUE: How to use this feature with some examples ?

iii. DBMS_STAT: If you have any scripts to run this package ?

Thanks in advance

Pradeep

      and we said...
i) they aren't.  they are used to identify "hot" segments -- segments you might
consider moving in order to spread out IO.

ii) if you get my new book -- i use it in there actually.  I snapshot query text
and the plan hash value.  Then, after gathering stats or whatever and running
the queries again -- i compare the current plan hash value with the last --
generating a list of queries that was affected by the change in stats.  If the
system starts going "really slow" -- and one of these queries becomes the
"top-n" query in the system -- i can show that the plan changed and this query
deserves some real looking into.

it just helps you look at a single field to say "plan changed" as the hash for
two plans won't be the same (could be -- but chances are incredibly SMALL)

iii) they just begin with "begin".

it is a single stored procedure call?  no script needed.  most people use
dbms_jobs to schedule it to run on a recurring basis

one thing you might consider is to EXPORT your stats, gather stats.  That way,
if you come in tommorow and "things are bad", you can always put the stats that
were working fine yesterday back in a pinch.
Review &
      Followup

      Rating:
      3
DBMS_STATS  September 14, 2003
Reviewer:
      Pradeep Baliarsingh  from USA
Tom,
    Regarding the DBMS_STATS, my point was how to implement this for collecting
the statistics in a big OLTP Database of size around 200G.
i.  If I use GATHER_DATABASE_STATS procedure, whether it is safe to collect SYS
and SYSTEM schema statistics?

ii. In the Options parameter, what is your recommendation  for using either
GATHER AUTO or GATHER STALE?

iii. Could you please outline the steps for implementing this with a example ?

iv. Could you explain a bit about METHOD_OPT arguments partcularly about this
SIZE clause ?

 Thanks again for all your expertise.

          Followup:

i) in 9i, safe, ok, recommended.

TEST first -- obviously

ii) auto to start, stale after that.

it is only two commands, no examples really.

iii) see #2

iv) size has to do with histograms.  read about it in the performance guide
please

September 14, 2003 Ashwin -- Thanks for the question regarding
      "Query and transaction processing statistics", version 8.1.7.4

      You Asked
Hi Tom,

Thanks for recieving this question. Is there a way to determine the number of
records processed, no of records remaining to be processed, by an on-going
transaction/sql query. Also can we estimate the probability of whether or not a
given transaction/ sql query will result into a succesful operation.

Your response is much appreciated
Thanks,
Ash

      and we said...
you can look in v$session_longops for long running statements, it'll give you an
estimate.

as for the "probability", no way -- only you can do that.  we enforce your
constraints, we don't make them up -- so, whether a statement will succeed or
fail is generally up to you.
Review &
      Followup

      Rating:
      3
v$session_longops is inconsistent  September 13,
      2003
Reviewer:  Ashwin Karanth  from Richmond, VA.

Appreciate your response Tom.
We have often analyzed and found that the data populated into the
v$session_longops dynamic view is at times very inconsistent. It doesn't display
the true picture of the transactions processing.

As for the probability of a transaction / sql statement resulting into a
succesful operation, I feel there's got to a better control embbeded in the
database. What I mean here is something like this...

If there's a transaction being processed and which is running for around an
hour's time or even more, and which has acquired exclusive locks on a rollback
segment, then there's got to be certain statistics collected in the database
which upon analysis can say for sure as to whether the transaction will
complete succesfully or not. This would be a great help for the performance
analysts.

Thanks again for your response.
Ash

          Followup:

it displays only long running things -- things that can be measured by the
server.

we (oracle) have NO CLUE as to the scope of your transaction if it is a bunch of
tiny statements (in that case YOU should be instruementing your code with calls
to dbms_applicaiton_info.set_session_longops with meaningful information)

as for the last part -- i say "no way, not a chance"....

so tell us -- why would knowing what RBS is in use tell you anything?  what
information could we possibly glean from that????  most transactions do not fail
due to storage/database functions -- they fail due to human or data errors.

      Rating: 3
query
      statistics  September 14, 2003
Reviewer:  Ashwin
       from Richmond, VA
well, thanks for the response.....

I was just thinking about this issue and it occured to me that hey, this just
could be one of those added features that could be built into the database one
day.....I completely aggree with you when you say 'most transactions fail due to
human / data errors'. but what if there's a situation where user(s) / business
call(s) you up and say(s) 'Hey, I've submitted a job in the application and its
been running for quite some time now, is there anyway you could tell me as to
how much is the time remaining for this job to finish and whether the job will
complete succesfully or not?'...

to this I definetly can provide estimates about the time remaining and elapsed
time of the transaction / operation ..but I can't say for sure looking at the
statistics as to whether the job will result in success or failure.

Now, since there's so much of intellegince being built into the Oracle database,
making it as much self manageable as possible and into one of the most
sophisticated databases ever built, this could fit in somewhere. May be this is
sounding a bit too far fetched or futuristic, but I just wanted to share this
thought...

Thanks
Ash

          Followup:

tell me, give me an example -- just psuedo code, as to how this would be done.

a thought is great -- but make it a little more real.  give me a "for example,
Oracle could do this and that and tell us this" (maybe we already do!)

September 15, 2003 Ashwin -- Thanks for the question regarding
      "Right approach to monitoring sessions and concurrent transactions",
      version 8.1.7.4

      You Asked
Hi Tom,

Thanks for taking up my question. I have been working with oracle databases for
quite some time and now I am focussing my efforts on performance tunning and
optimization. I would like to ask you certain specific questions relating to
this area.

1. What is the most apt or rather better way of monitoring sessions, concurrent
processes, events and the general health of an Oracle database. I mean there are
people talking about the advantages / disadvantages of maintaining high buffer
cache hit ratios, keeping segments in very few extents and periodically
rebuilding the database. Some people discourage ratio based analysis and some
wait events. Would it be possible for you to highlight some of the cardinal
steps which one should follow in order to effectively manage and monitor an
Oracle Database (it would be great if you could discuss this in the perspective
of Oracle Applications..financials in particular).

Your response is much appreciated.
Thanks,
Ash

      and we said...
those three metrics/things are probably the biggest myths on the planet today.

high cache hit ratios indicate POORLY tuned systems just as frequently (and
maybe more often even) as they can be found on a well tuned system (note: i did
not say "indicate a well tuned", that they may be FOUND on a well tuned system)

they can indicate a poorly tuned system.
they can be found on a well tuned system.

high cache hit ratios can be a leading indicator that you are over doing nested
loops joins and re-reading the same data over and over and over at an very high
latching cost!

some well tuned systems happen to have high cache hit ratios.

Now, as for keeping segments in few extents, that is just a myth.
Always has been
Always will be (probably, i see nothing changing this)
Ask those people to prove scientifically that few extents = massive (or even
small, hey I'll take small) performance improvements.  scientifically -- not,
"well so and so says they heard about this system someone saw once where it made
a big difference"

and rebuilding indexes -- well, that can damage a system as easily as help it.
there are rare cases where a b*tree needs to be coalesced (not rebuilt, just
coalesced).  index rebuilding is most commonly needed in a data warehouse during
mass load operations.  but in most systems -- rebuilding is just a waste of
time, energy, resources and frequently incurrs "scheduled downtime" that could
be used for other productive, meaningful operations (or just cancelled all
together)

It is all about waits -- all about tuning to remove waits

It is all about designing things efficiently -- looking at a query and saying
"now, is it really reasonable that this query we execute 5 times a second to get
2 rows does 150 logical IO's???" (no, probably NOT, needs to be tuned).  Notice
that this query in this case isn't doing massive buffer gets -- "just" 150 but
given the number of returned rows and the frequency of execution -- this is the
"big guy".  That query that runs once a day at midnight and does 1,000,000
buffer gets -- you may well be able to ignore it.  Pick your fights carefully,
go after things that will really make a difference.

It is all about keeping a history.  How were we doing when things were fine.
That is the only way you can answer the question "so, things are going slow
today -- WHAT IS DIFFERENT".  Unless you have the history, you cannot answer
that question.  You need to know how you were performing a week ago, as much as
how you are not performing today -- in order to isolate some change and zero in
on that (else you end up having to tune the entire system which takes a while)

It is all about making it so you can "go back".  Before you make a change, test
your ability to "unmake that change".  Collect stats?  be able to uncollect them
(stats are a change, export them -- then gather.  neat thing is, if you keep
some of these exported stats for a while -- you'll be able to answer that age
old question "so, how has my data changed over the year" simply by importing the
stats in a test database with an empty schema.  you'll have blocks, num rows,
heights of indexes, distributions of data and so on)

It is most definitely not about cache hits.  if it were, just running this
stored procedure:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:528893984337#11933996240589
should make your system go faster (but, alas, it makes it run slower -- but boy,
that cache hit!!  awesome)

it is not about the number of extents.

it is not about index rebuilds

Review &
      Followup

      Rating:
      5
  September 13, 2003
Reviewer:  Mike
       from TX
"
high cache hit ratios can be a leading indicator that you are over doing nested
loops joins and re-reading the same data over and over and over at an very high
latching cost!
"

I might understand the high ratio because of the re-reading the same data over
and over. But for " at an very high latching cost? could you elaborate that (and
it will be help with examples)?

          Followup:

every block get (consistent get) requires a latch (lock) on a cache buffer chain
list.

a latch is a serialization device.

the more you latch, the less scalable you are.

      Rating: 4
Day vs.
      night  September 14, 2003
Reviewer:
      A reader
You talked about maintaining history so as to react when something is different.
I have a system where some extract jobs have started to take 5-6 times as long
as they used to. Nothing has changed in the system other than the fact that it
is taking on more load over the months. At the OS level (solaris), the machine
is fine, it is not strapped for IO, memory or CPU.

Its just that for this particular extract (create temp tables, populate them
using SQLs and dump the table), things take very long.

Long story short, when ever the script accesses this one particular table takes
a very long time. The table is about 32M (~300K rows). It is not being updated
when this happens.

For example, a statement like

create global temporary table t1 as select * from this_table;

takes less than a minute in the day, but at night, it takes about 5 minutes.

The stats are about the same, same LIO, same PIO.

How do I go about troubleshooting this? Why would the exact same set of SQLs
with the same plans and same data volume take drastically different execution
times? Thanks

          Followup:

trace the wait events, use the 10046 event (search for that number on this site
if you don't have the syntax/know about it to read about it) and see what it is
waiting on.

September 15, 2003 -- Thanks for the question regarding "Security",
      version 8.1.6

      You Asked
tom,

Can any kind of security be enforced thro Net8 ? Is there any
relevant material available online.

Bala

      and we said...
see

http://www.oracle.com/ip/solve/continuity/security/index.html?content.html
Oracle Advanced Security (ASO) is a feature of net8 to support
alternative signon methods (single sign on as well), on the wire
encryption, and so on.
Review & Followup

      Rating: 1
Security -
      Single Sign On  May 28, 2002
Reviewer:  Brad
      Anstey  from Perth, Australia
Pages referenced by links in the site (with regards to the subject 'Single Sign
On') are no longer valid/found - ie. raises a 404 Error.

          Followup:

http://www.oracle.com/ip/deploy/database/oracle9i/index.html?se_advsecurity.html
(went to products, products A-Z, Advanced Security on the www.oracle.com
website)

      Rating: 5
level of
      detail  May 28, 2002
Reviewer:  Harrison
      Picot  from Haymarket, VA "Ragweed Capital" USA
Tom, you said: "went to products, products A-Z, Advanced Security on the
www.oracle.com website". Your point being ... what? Should we be able to do
that? You make it look easy but parts of it are tricky, like how many thumbs did
you use?

Cheers

Harrison

      Rating: 3
Single
      Sign-on  September 13, 2003
Reviewer:  raghu

Hi Tom,

Can you please give  a detailed explanation of what Single Sign-on is all about.

can we have OPS$ feature with forms9i i.e authenticating the password from
windows username.

If yes can you please give the details about it.

Thank you.

          Followup:

sso

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96521/dba.htm#1064
windows authentication

http://download-west.oracle.com/docs/cd/B10501_01/win.920/a95492/toc.htm

      Rating: 2
OPS$ with
      forms9i  September 15, 2003
Reviewer:
      A reader
Hi Tom,

I think i am bit confused now.
I wanted to know whether i can have OPS% feature with FORMS9i.
Because Forms9i is web based implementation and not client server.

How can i achieve the client server OPS$ feature with FORMS9i applications. Is
it possible.

          Followup:

not sure if you can -- three tier tends to break that "trusted path" with a man
in the middle.

As I don't use forms myself, I'll have to refer you to otn.oracle.com ->
discussion forums.  There is a developer group there, they may will know the
answer.  sorry.

September 15, 2003 Tim -- Thanks for the question regarding "Audit
      on Trigger or Oracle Audit", version 9.2.0

      You Asked
Hello Tom,

We are planning to audit a table that we can log all the users
access/insert/delete/update a table.
My question is if I do this with a trigger and the table is access quite often,
will this causes performance issue? Will it help if I use Oracle Audit Trail?
Thanks in Advance.

Regards,
Tim.

      and we said...
if all you want to know is that someone inserts/updates/deletes from a table --
Oracle supplied auditing is the only "sensible" approach.  Less code to write,
less code to maintain, less dependencies, less to worry about.  Auditing is so
easy to enable.

In my latest book "Effective Oracle By Design" -- i used this as a test case
actually, in a section on using the database and its supplied functionality
instead of doing it yourself.  Using auditing is effective and efficient and the
correct approach if the information it gathers meets or more then meets your
needs.
September 15, 2003 Tony -- Thanks for the question regarding "Can I
      replace just the first occurrence of the string?", version 8.1.6

      You Asked
Hi Tom,
I want to replace only the first occurrence of the string, and I used replace
command as below:
select replace('EMPLOYER_TYPE,EMPLOYER_TYPE_DET','EMPLOYER_TYPE','-') from dual;
But it gave me the o/p:
REPLACE
-------
-,-_DET

I just want the o/p: -,EMPLOYER_TYPE_DET

Could you please show me how to do this?
Kind Regards,

      and we said...
we can use case or decode to figure out if we need to replace something and then
substr it in.  like this:

ops$tkyte@ORA920> select x,
  2         decode( nvl(instr(x,'EMPLOYER_TYPE'),0), 0, x,
  3                 substr(x,1,instr(x,'EMPLOYER_TYPE')-1) || '-'
  4                             ||
substr(x,instr(x,'EMPLOYER_TYPE')+length('EMPLOYER_TYPE')) ) r
  5    from t;

X                                        R
---------------------------------------- -----------------------------------
EMPLOYER_TYPE,EMPLOYER_TYPE_DET          -,EMPLOYER_TYPE_DET
how now brow cow                         how now brow cow

xxxx EMPLOYER_TYPE,EMPLOYER_TYPE_DET     xxxx -,EMPLOYER_TYPE_DET

Review &
      Followup

      Rating: 5
Thanks a
      lot!  September 15, 2003
Reviewer:
      A reader

September 15, 2003 mohammed -- Thanks for the question regarding
      "inserting data in lob columns", version 9.01

      You Asked
How do I insert data data in a table containing columns of clob,blob datatypes.
Hope that u will explain with a simple example.

      and we said...
Well, I'll refer you to the source:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96591/toc.htm
thats the application developers guide to large objects.  the "how" depends on
the "what".  It can be as simple as:

ops$tkyte@ORA920> create table t ( x clob );

Table created.

ops$tkyte@ORA920> insert into t values ( 'Hello World' );

1 row created.

or more complex

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          l_clob clob;
  3  begin
  4          insert into t values ( empty_clob() ) returning x into l_clob;
  5          for i in 1 .. 10
  6          loop
  7                  dbms_lob.writeAppend( l_clob, length('Hello World'), 'Hello
World' );
  8          end loop;
  9  end;
 10  /

PL/SQL procedure successfully completed.

or is can be done via dbms_lob.loadfromfile

for all of the options -- check out that doc, various language interfaces are
demonstrated as well.
September 15,
      2003 Lakshmi -- Thanks for the question
      regarding "Reg Nested Tables", version 8.1.6

      You Asked
Hi Tom,

Is it possible to see the data stored  by the Nested table thro Select * from
table_name.
The Table used by Nested table is also not stored in User_tables
Then how is it stored .Could you Explain

Thanks in Advance

      and we said...
If you have my book, I discuss how to do this in the chapter on tables.

Here is a short excerpt from that:

...
Previously I stated: 'We must always join, we cannot query the EMP data alone'
but then followed that up with a caveat: 'you can if you really need to'. It is
undocumented and not supported, so use it only as a last ditch method. Where it
will come in most handy is if you ever need to mass update the nested table
(remember, we would have to do that through the DEPT table with a join). There
is an undocumented hint, NESTED_TABLE_GET_REFS, used by EXP and IMP to deal with
nested tables. It will also be a way to see a little more about the physical
structure of the nested tables. This magic hint is easy to discover after you
export a table with a nested table. For example, I exported the table above in
order to get its 'larger' definition from IMP. After doing the export, I found
the following SQL in my shared pool (V$SQL table):

SELECT /*+NESTED_TABLE_GET_REFS+*/ NESTED_TABLE_ID,SYS_NC_ROWINFO$ FROM
"TKYTE"."EMPS_NT"

A simple query like 'select sql_text from v$sql where upper(sql_text) like
?%EMP%' found it for me. If you run this, you'll get some 'magic' results:

tkyte@TKYTE816> SELECT /*+NESTED_TABLE_GET_REFS+*/
  2         NESTED_TABLE_ID,SYS_NC_ROWINFO$
  3  FROM "TKYTE"."EMPS_NT"
  4  /

NESTED_TABLE_ID                  SYS_NC_ROWINFO$(EMPNO, ENAME,
-------------------------------- ------------------------------
9A39835005B149859735617476C9A80E EMP_TYPE(7782, 'CLARK',
                                 'MANAGER', 7839, '09-JUN-81',
                                 2450, 100)

9A39835005B149859735617476C9A80E EMP_TYPE(7839, 'KING',
                                 'PRESIDENT', NULL,
                                 '17-NOV-81', 5000, 100)

Well, this is somewhat surprising, if you describe this table:

tkyte@TKYTE816> desc emps_nt
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 EMPNO                                        NUMBER(4)
 ENAME                                        VARCHAR2(10)
 JOB                                          VARCHAR2(9)
 MGR                                          NUMBER(4)
 HIREDATE                                     DATE
 SAL                                          NUMBER(7,2)
 COMM                                         NUMBER(7,2)

These two columns don?t even show up. They are part of the hidden implementation
of nested tables. The NESTED_TABLE_ID is really a foreign key to the parent
table DEPT_AND_EMP. DEPT_AND_EMP actually has a hidden column in it that is used
to join to EMPS_NT. The SYS_NC_ROWINF$ 'column' is a magic column, it is more of
a function then a column. The nested table here is really an object table (it is
made of an object type) and SYS_NC_INFO$ is the internal way Oracle references
the row as an object, instead of referencing each of the scalar columns. Under
the covers, all Oracle has done for us is to implement a parent/child table with
system generated primary and foreign keys. If we dig a little further, we can
query the 'real' data dictionary to see all of the columns in the DEPT_AND_EMP
table:

tkyte@TKYTE816> select name
  2    from sys.col$
  3   where obj# = ( select object_id
  4                    from user_objects
  5                   where object_name = 'DEPT_AND_EMP' )
  6  /

NAME
------------------------------
DEPTNO
DNAME
LOC
EMPS
SYS_NC0000400005$

tkyte@TKYTE816> select SYS_NC0000400005$ from dept_and_emp;

SYS_NC0000400005$
--------------------------------
9A39835005B149859735617476C9A80E
A7140089B1954B39B73347EC20190D68
20D4AA0839FB49B0975FBDE367842E16
56350C866BA24ADE8CF9E47073C52296

The weird looking column name, SYS_NC0000400005$, is the system-generated key
placed into the DEPT_AND_EMP table. If you dig even further you will find that
Oracle has placed a unique index on this column. Unfortunately however, it
neglected to index the NESTED_TABLE_ID in EMPS_NT. This column really needs to
be indexed, as we are always joining FROM DEPT_AND_EMP to EMPS_NT. This is an
important thing to remember about nested tables if you use them with all of the
defaults as I did above ? always index the NESTED_TABLE_ID in the nested tables!

I've gotten off of the track though at this point. I was talking about how to
treat the nested table as if it were a real table. The NESTED_TABLE_GET_REFS
hint does that for us. We can use that like this:

tkyte@TKYTE816> select /*+ nested_table_get_refs */ empno, ename
  2  from emps_nt where ename like '%A%';

     EMPNO ENAME
---------- ----------
      7782 CLARK
      7876 ADAMS
      7499 ALLEN
      7521 WARD
      7654 MARTIN
      7698 BLAKE
      7900 JAMES
7 rows selected.

tkyte@TKYTE816> update /*+ nested_table_get_refs */ emps_nt
  2  set ename = initcap(ename);
14 rows updated.

tkyte@TKYTE816> select /*+ nested_table_get_refs */ empno, ename
  2  from emps_nt where ename like '%a%';

     EMPNO ENAME
---------- ----------
      7782 Clark
      7876 Adams
      7521 Ward
      7654 Martin
      7698 Blake
      7900 James
6 rows selected.

Again, this is not a documented supported feature. It may not work in all
environments. It has a specific functionality; for EXP and IMP to work. This is
the only environment it is assured to work in. Use it at your own risk. Use it
with caution though and do not put it into production code. Use it for one-off
fixes of data or to see what is in the nested table out of curiosity. The
supported way to report on the data is to unnest it like this:
......
Review
      & Followup

      Rating: 4
Reg Nested
      Tables  December 03, 2001
Reviewer:  Walt
      Borders  from Glasgow, DE
Great information. OO is a useful concept, but difficult to implement and
maintain in Oracle.  Any and better information is needed to make OO design
really useful. This article goes towards satisfying that requirment.

Thanks.

          Followup:

It's funny -- my article shows you how to subvert the Object relational concept
-- get around the limits of OBJECTS in general.  In a pure OO database (which we
are decidely most certainly NOT), you couldn't do this -- you would have to
message EACH object and ask it about its collection.

We are not OO, we are object relational -- at the end of the day, its all tables
and columns.

      Rating: 5
Reg Nested
      Tables  June 30, 2003
Reviewer:  Ovidiu
      Todoran  from Seattle, WA, USA
Clear and concise. Helpful to read the whole chapter from the book.

      Rating:
      5
NESTED_TABLE_GET_REFS seems to be
      documented  September 14, 2003
Reviewer:
      Menon
It is given in the sql reference document:

at the following link

http://st-doc.us.oracle.com/9.0/901/server.901/a90125/sql_elements7a.htm#8477
Best Regards

          Followup:

that'll be 9i doc -- book was 8i :)

things change... (i see it is listed but not really described...)

I would still say "undocumented" -- can you tell what it does from the docs?

      Rating: 5
re:
      NESTED_TABLE_GET_REFS seems to be documented  September 15,
      2003
Reviewer:  Menon
"that'll be 9i doc -- book was 8i :)"

Yup - realized that after posting it..Agree that it is
not documented as clearly as in your earlier book;) -
but at least one does not have to worry as much about
it being desupported as you would if it was not even
in the docs

          Followup:

yes, i think you do.  it was just mentioned in passing -- it is not
"documented", its behaviour is definitely not "defined"

caveat emptor

      Rating: 5
re:
      NESTED_TABLE_GET_REFS seems to be documented  September 15,
      2003
Reviewer:  Menon
Hi Tom
Turns out it is documented in the "Applications developer guide - object
relational features"

So it is actually documented in terms of usage at:.

http://st-doc.us.oracle.com/9.0/901/appdev.901/a88878/adobjdes.htm#451041
Quote from the documentation:
"
As a HINT during retrieval
A query can retrieve a collection as a locator by means of the hint
NESTED_TABLE_GET_REFS. Here is an example of retrieving the column b from the
table tab1 as a locator:

SELECT /*+ NESTED_TABLE_GET_REFS +*/ b
FROM tab1
WHERE a = 2;

Unlike with the RETURN LOCATOR clause, however, you cannot specify a particular
inner collection to return as a locator when using the hint. "

          Followup:

there we go, it is documented now.  ok, not a problem to use.

thanks!

September 15, 2003 shrenit -- Thanks for the question regarding
      "removing control characters from text", version

      You Asked
Is there a routine in Oracle that can test for and remove
unwanted characters in a text string, ie control characters?

      and we said...
Yes, we can use REPLACE and TRANSLATE to do this.  Lets say the
characters you wanted to remove where 'SAT' (to remove control
characters like TABS, CR, LF you would use chr(9) || chr(13) ||
chr(10).  CHR is a function that takes the ASCII code and
returns that character -- 9 = tab, 13 = CR and so on).  We could
then code:

ops$tkyte@8i> select username, replace( translate( username,
'SAT', 'S' ), 'S', '' )
  2    from     all_users
  3   where rownum < 5
  4  /

USERNAME                       REPLACE(TRANSLATE(USERNAME,'SA
------------------------------ ------------------------------
SYS                            Y
SYSTEM                         YEM
OUTLN                          OULN
DBSNMP                         DBNMP

So, that gets rid of SAT in all of the strings for us (and would
get rid of control characters just as easily.

Since this can be cumbersome to code and we would not really
want to put the string of control characters into all of our
queries, we can make this more generic as follows:

ops$tkyte@8i> variable undesirable varchar2(255)
ops$tkyte@8i> exec :undesirable := 'SAT'

PL/SQL procedure successfully completed.

ops$tkyte@8i> select username,
  2         replace(
  3          translate( username, :undesirable,
substr(:undesirable,1,1) ),
  4              substr(:undesirable,1,1), '' )
  5    from all_users
  6   where rownum < 5
  7  /

USERNAME                       REPLACE(TRANSLATE(USERNAME,:UN
------------------------------ ------------------------------
SYS                            Y
SYSTEM                         YEM
OUTLN                          OULN
DBSNMP                         DBNMP

So, here we get the same effect but 'SAT' and 'S' are not hard
coded into the query.  This is OK but still hard to code and we
would still have to code these variables into our applications.
We would prefer to control this from the database (in case the
set of control characters changes over time).  To achive this,
we create a view:

ops$tkyte@8i> create or replace view bad_chars
  2  as
  3  select 'SAT' allbad, 'S' onebad
  4    from dual
  5  /

View created.

And now we can code:

ops$tkyte@8i> select username, replace( translate( username,
allbad, onebad ), onebad, '' )
  2    from all_users, bad_chars
  3   where rownum < 5
  4  /

USERNAME                       REPLACE(TRANSLATE(USERNAME,ALL
------------------------------ ------------------------------
SYS                            Y
SYSTEM                         YEM
OUTLN                          OULN
DBSNMP                         DBNMP

That is easier but we can go one step further:

ops$tkyte@8i> create or replace function stripbad( p_str in
varchar2 ) return varchar2
  2  is
  3      undesirable varchar2(25) default 'SAT';
  4  begin
  5      return
  6         replace(
  7          translate( p_str, undesirable,
substr(undesirable,1,1) ),
  8              substr(undesirable,1,1), '' ) ;
  9  end;
 10  /

Function created.

ops$tkyte@8i> select username, stripbad( username ) stripped
  2    from all_users
  3   where rownum < 5;

USERNAME                       STRIPPED
------------------------------ --------------------
SYS                            Y
SYSTEM                         YEM
OUTLN                          OULN
DBSNMP                         DBNMP

So, now it is like we have a builtin function stripbad that gets
rid of our control characters.  If you select a couple of rows
at a time -- the PL/SQL function will perform quite adequately.
If you select thousands of records at a time, you will want to
use the view + translate/replace directly for performance.

Review &
      Followup

      Rating: 5
Suppose
      characters to strip are not known?  February 12, 2003

Reviewer:  Dave  from USA
Thanks, Tom, for the great example.

You were looking for strings that had "SAT" in them.  Suppose, that I do not
know the "bad" characters?  All I know is that the bad characters have ascci
values less than  32 and greater than 126. Does your method still work?  And for
my problem, I just need to identify the rows with these bad characters. I don't
need to replace them.

          Followup:

ops$tkyte@ORA920> create table bad ( str varchar2(255) primary key )
organization index;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          l_str varchar2(255);
  3  begin
  4          for i in 0 .. 31
  5          loop
  6                  l_str := l_str || chr(i);
  7          end loop;
  8          for i in 127..255
  9          loop
 10                  l_str := l_str || chr(i);
 11          end loop;
 12          insert into bad values ( l_str );
 13  end;
 14  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> drop table t;

Table dropped.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t ( data varchar2(20) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( 'Hello World' );

1 row created.

ops$tkyte@ORA920> insert into t values ( 'bad ' || chr(31) );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t where translate( data, (select * from bad),
chr(0) ) != data;

DATA
--------------------
bad

will do that -- just need to put into "bad" something that represents what you
are looking for.

      Rating: 5
can I do this
      for all columns of a number of tables in a schema  July 18,
      2003
Reviewer:  Ajeet Ojha  from OH
Hi Tom,
Can I find a particular set of special charactters in all the tables of a
schema,How can I change this script to do this.

Thanks
Ajeet

      Rating: 5
is this a good
      way --it can be make more generic  July 20, 2003

Reviewer:  Ajeet  from ohio
declare
l_cnt number := 0   ;
begin
for x in (select column_name,table_name from user_tab_columns where data_type in
 ('CHAR','VARCHAR2' )) loop
execute immediate 'select count(*)  from ' || x.table_name || ' where instr ('
|| x.column_name|| ',chr(0) ) > 0 '
into l_cnt ;
if l_cnt > 0 then
dbms_output.put_line(l_cnt||'~'||x.table_name||'~'||x.column_name) ;
end if ;
end loop ;
end ;
/

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5203164092530

      Rating: 5
You are
      unbelievable  July 20, 2003
Reviewer:  Ajeet
       from OH,USA
This is what I needed.How can you write such generic scripts..You are
unbelievable.

Thanks for your help again.
Ajeet

      Rating: 5
Thank you very
      much.  July 21, 2003
Reviewer:
      A reader
Just exactly what I needed.  The flat file generated additional lines when it
exceeded 255 chars or the next comma position in a comma-delimited file
whichever is latest.

Using this snippet, the control characters were removed.
Thanks.

      Rating: 5
Thank you very
      much.  July 21, 2003
Reviewer:  Oliver
      Dimalanta  from Philippines
Just exactly what I needed.  The flat file generated additional lines when it
exceeded 255 chars or the next comma position in a comma-delimited file
whichever is latest.

Using this snippet, the control characters were removed.
Thanks.

      Rating: 4
replace two
      strings  August 21, 2003
Reviewer:
      Pingu_SAN
Hi

How can we replace two strings without using twice replace(), for example

select
  replace(replace(value, '%s', sequence#), '%t', thread#)
  from v$log_history a, v$parameter b
where b.name='log_archive_format';

I have to use twice replace, is there a way (using decode or case) to replace
two or even more strings in a go?

Cheers

          Followup:

nope

      Rating: 5
mixed
      case  September 15, 2003
Reviewer:  Sandeep
       from Germany
8.1.7.4
need to look for a particular word in a string and change it to a particular
case...
like say the column we are working with is job_descrip for an emloyee....and
job_descrip has data in mixed case...
so we need to replace all occurences of word oracle (in any case ex: oRacle or
ORacLE etc. to OrACLE)...
How can I write as sql to do this....I was trying REPLACE...but didn't
work....cause have more than one case here and all the cases are not
known....too many combinations...

Thanks..

          Followup:

is there a reasonable max limit to the number of terms in the string to be
replaced you would expect ever??

      Rating: 3
Can it be done
      with using upper.  September 15, 2003
Reviewer:
      Shailandra  from NJ
Hi Tom,

Can the above question be done by using upper function rather than looking for
every possible combination of string.

SQL> select username, replace( translate( upper(username), 'SAT', 'S' ), 'S',
'' )
  2    from     all_users
  3   where rownum < 5
  4  /

          Followup:

yes, but that would destroy the case of everything else -- which I thought they
wanted to preserve.

September 16, 2003 Lilian -- Thanks for the question regarding "add
      a column and update the column with a big table", version 8.1.6

      You Asked
Hi Tom:

Thank you very much for your help as always.

I have a big table called client that currently has over 8 millions records, now
I need to add one more column called phone, and have to update phone number to
each row.  the table has one pk as personid, I created a temp table which
includs three colums personid, phone and ind(for check), wrote procedure
following as:

declare
v_nCount number(10,0);
v_nTotal number(10,0);
v_nNumber number(10,0);

begin
v_nCount := 0;

select count(*) into v_nTotal from Test_table;

while v_nTotal < v_nCount

Loop
begin

update main_table a
   set  a.phone = (select b.phone from test_table b where b.personid =
a.personid and ind is not null and rownum < 1000);

   v_nCount := sql%rowcount;

update test_table
   set ind = 1
   where rownum < 1000
   and ind is null;

   v_nCount := v_nCount + v_nNumber;

commit;

end;
end loop;

I really like to have your suggestion before i run this procedure, whether the
procedure will less impact our database performance, redo, archive log.
what is the best way to update the big table.

Thank you in advance,
an

      and we said...
To update 8 million rows and set an UNINDEXED column to some value -- I would
most certainly do it in a single update.

update ( select a.phone new_phone, b.phone set_to_phone
           from main_table a, test_table b
          where a.personid = b.person_id )
   set new_phone = set_to_phone;

And that is it.  Period, no complex logic.

Your logic appears flawed to me -- UNLESS there is exactly one row for each and
every row in main_table in test_table.  If there is one row more or less in
either table -- if the set of personid's is NOT EXACTLY the same -- you will
miss some updates.

Your counting is not making sense to me either -- you ALWAYS set v_nCount to
sql%rowcount (number of rows last updated)  and add v_nNumber to it -- but that
is never set.

Your update of main_table ALWAYS updates ALL ROWS in main_table!!!! It is most
definitely wrong.

Just update the join, skip the "fancy" logic, do it the right way.  Just make
sure to have sized your rollback large enough to do this operation.
Review &
      Followup

      Rating: 2
update 8
      million column  May 13, 2002
Reviewer:  an
       from DE USA
Thank you very much for your quick response.  Maybe my logic mislead.
Here is my problem, there is a table client that has about 8 millions records
stored already, now we need to add one column called phone to the table, and
load the phone to each row and match each client.  What is the best way to do
it?

Thank you very much,
an

          Followup:

You were not misleading in the question.  My answer is, was and will always be:

To update 8 million rows and set an UNINDEXED column to some value -- I would
most certainly do it in a single update.

update ( select a.phone new_phone, b.phone set_to_phone
           from main_table a, test_table b
          where a.personid = b.person_id )
   set new_phone = set_to_phone;

for this one.

Your logic in your approach is fundementally flawed and doesn't make sense.
Just do the update of 8 million rows in a single statement.

      Rating: 5
Only one thing
      to add...  May 13, 2002
Reviewer:  Connor
       from UK
For the original poster...

A similar experience (an update to a massive table) led our team to a slightly
different variant on the same solution.  We used rowid ranges (as derived by
probing the primary keys) so that we could parallelise the update even though
the table was not partitioned.

But the underlying philosophy Tom suggests is the same - use simple SQL to do
it.

      Rating: 5
how about
      indexed?  May 13, 2002
Reviewer:  Doug
       from Rye, NY USA
You've emphasized UNINDEXED column.  How about an indexed one?

          Followup:

drop the index, do the bulk update, build the index in parallel with nologging.

      Rating:
      5
thanks  May 13, 2002
Reviewer:  an
       from DE usa
Hi Tom:

Thank you millions!  I got your point later today, I never thought it was as
simple as you pointed.  I dived into very hard way at beginning.
Thanks again.

      Rating: 5
Good SQL ---but
      will it work if Table A has no PK ?  September 15, 2003

Reviewer:  Ajeet  from OH,USA
This is a good solution but seems it will not work if table
Main_Table (in the sql below) has no Primary key..

Is there a work around of this...

update ( select a.phone new_phone, b.phone set_to_phone
           from main_table a, test_table b
          where a.personid = b.person_id )
   set new_phone = set_to_phone;

Sometime back I tried and I get this error..i am missing something basic...can
anyone help me here.

Thanks

          Followup:

if there isn't a primary/unique key, you are back to a correlated subquery AND
there had better be uniqueness else the query will fail in the end no matter
what (as the results of the update would be ambigous)

my suggstion - add the unique constraint and update the join.

short of that

update main_table a
   set phone = (select phone from test_table b where b.person_id = a.person_id)
 where exists  (select phone from test_table b where b.person_id = a.person_id);

but remember, if person_id is not unique in B -- that'll not work, we'd have no
idea WHICH row to use (so the unique constraint needs to be there anyhow)

      Rating: 4
Error was
      this  September 15, 2003
Reviewer:
      A reader
ORA-01779: cannot modify a column which maps to a non key-preserved table...
sorry about this..forgot to put in my earlier post.

Thanks
Ajeet

      Rating: 5
Tom's update is
      most useful ... the rest? ... hmm, scary.  September 16,
      2003
Reviewer:  Gabriel  from Toronto
To Ajeet,

Come on man ... don't just stop and ask on first error message ... just a bit of
investigation would have shown:

/*
** For the “special” update method …
*/
update ( select a.phone new_phone, b.phone set_to_phone
         from   main_table a, test_table b
         where  a.person_id = b.person_id )
set new_phone = set_to_phone;

1. You get ORA-01779 when test_table doesn't have the PK on person_id (hey, this
is what the message for ORA-01779 is actually saying!)
2. If test_table has the PK on person_id, the update will work whether the PK on
main_table.person_id exists or not ... or data is unique by person_id!
3. Any method of enforcing uniqueness on test_table (person_id) will do … in
other words, an unique index would have been enough. Personally, I like to
declare and name things … hence I would have the unique or primary key
constraint declared (whichever is appropriate) … the constraint would get me the
unique index as well.

/*
** for the “classic” update method …
*/
update main_table a
set phone = (select phone from test_table b where b.person_id = a.person_id)
where exists  (select phone from test_table b where b.person_id = a.person_id);

This will work in all cases (constraints defined in both tables, constraints
defined in one only, constraints not defined in both) … except when data in
test_table is not unique by person_id. In this case one would get this message:

“ORA-01427: single-row subquery returns more than one row”

Note: I make distinction between having a unique constraint on a table (which
also guarantees uniqueness of data) and data being unique. Of course, when the
constraint is missing, data may be unique now (hence the update will work) … but
not tomorrow (when the update will fail). Anyone heard about the brilliant
application, which passed all QA tests (‘cause they had “nice”,
“it-will-always-be-like-this-take-my-GUI-APP-architect-word-for-it” data) … but
failed on first production day? Love those … they keep me employed.

Gabriel

September 16, 2003 chet -- Thanks for the question regarding "Delete
      Rule is "No Action" of FOREIGN KEY 's Constraint", version 9.2.0

      You Asked
Hi Tom,

ALTER TABLE PLANT ADD (
  CONSTRAINT PLAN_COMPANY_FK FOREIGN KEY (COMPANY_ID)
    REFERENCES COMPANY (COMPANY_ID));

Delete Rule is "No Action".

I deleted a record of the table "COMPANY", but I got a error message which is
"ORA-02292 integrity constraint (string.string) violated - child record found".

So , how to delete a record of "COMAPNY" and keep the records of "PLANT"?

Regards,
Chet

      and we said...
do you want to set the foreign key to NULL?  you can:

on delete set null

ops$tkyte@ORA920> create table t1 ( company_id int primary key );

Table created.

ops$tkyte@ORA920> create table t2 ( company_id references t1 ON DELETE SET NULL
);

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t1 values ( 1 );

1 row created.

ops$tkyte@ORA920> insert into t2 values ( 1 );

1 row created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select rownum, company_id from t2;

    ROWNUM COMPANY_ID
---------- ----------
         1          1

ops$tkyte@ORA920> delete from t1;

1 row deleted.

ops$tkyte@ORA920> select rownum, company_id from t2;

    ROWNUM COMPANY_ID
---------- ----------
         1

ops$tkyte@ORA920>
ops$tkyte@ORA920>

Review &
      Followup

      Rating:
      2
  September 15, 2003
Reviewer:  Chet

I don't want to set the foreign key to NULL.  Sometimes I want to keep the FK
value.  So, how to do this?

          Followup:

umm, lets see -- you deleted the parent row, with the primary key
and yet you want to retain the value in the child table.

the answer is easy -- you do not want a foreign key at all apparently?

Think about it -- you are saying

"i want to have values in the child table that are
a) not null
b) not in parent"

therefore, the parent is not the parent.  you do not have a need for a foreign
key at all.

      Rating: 5
What is the
      Poing of FK if you wilingly violate it.  September 15, 2003

Reviewer:  Ian Matyssik  from Japan, Tokyo

Chet,
so why would you need FK and willingly violate it? If you will leave the record
after parent is deleted you will get "orphant" record in the child table and how
would you expect Oracle to resolve conflict for you. I think you missed reading
Oracle Guide.

September 16, 2003 sush -- Thanks for the question regarding
      "Partitioning the table by range on a column and no index/key on this ?",
      version 8.1.7

      You Asked
Hi Tom ,

I want to partition by range on column partid (for each thread) on a table say
"xyz" as I'll be loading(inserting) this table with multiple threads(say 4).The
reason to partition this table is for restartability as it gives me flexibility
to truncate and reload if any of the threads fail.

My questions:

1)Is it necessary to have index/key on this column partid.
  I'll not be referring to this column partid in any of my queries.
2)Is it ok to analyze one partition while loading into another paritition?
3)Are local indexes on the table better or global indexes on the partitioned
table.

I always admire your answers.

      and we said...
1) no, not necessary

2) yes, but -- you'll WANT global statistics across all partitions after all is
said and done as well.  You want both partition level and TABLE level stats.
Pretty important to have both if you are not sure 100% of the queries will
always come down to a single partition (which it doesn't sound like they will)

3) if one were inheritly better then the other -- we would not have IMPLEMENTED
the other kind.

What you need to do -- this is important -- is understand the ins and outs of
the physics of partitions.

For example, you are using partitions to have a "restartable load".  Ok, fair
enough -- that is your stated goal.

Now, suppose you use local indexes on all -- well, when you query: "select *
from t where indexed_column = :x" -- unless indexed_column IS part of your
partition key (doesn't sound like it) you will have easily QUADRUPULED your IO
on your system as we have 4 index range scans to do.

So, suppose you use a global index on indexed_column -- now, the index IO is
down to normal -- but if you truncate a partition (as stated) the entire local
index will go "invalid" and have to be rebuilt.

So -- you will want to do further investigation, benchmarking, testing, and
understanding of the physics of partitioning -- before going further.

Partitions are a tool -- they can do many things, they can slow you down to a
crawl when used wrong, they can have no impact whatsoever in other cases and
from time to time can make you go faster.  Understand what and how they do it --
and you'll come along way to understanding how and where to apply them.

Sorry this is sort of "preachy" -- it is just that I see people "slamming"
partitioning because they deploy it and it works the way it was designed, not
the way they imagined ;)  Just want to make sure you don't get to that point.
Review &
      Followup

      Rating:
      5
  February 15, 2003
Reviewer:  sush
       from sj,ca
Thanks for the response.It's very useful.My earlier question perhaps was not
clear so I'm rewriting the questions.

question 2 : Can we do analyze of a partition while other partition is being
loaded(I mean simultaneously).I thought one of it will fail.

question 3 : Pls correct me if i'm wrong,In a datawarehouse we should go with
local indexes rather than with global indexes.

          Followup:

q2) the partitions are independent of eachother.

You should be able to do that.   You can analyze whilst others are even writing
to the same segment in fact.

q3) maybe yes -- maybe no -- maybe maybe.  All of the above, none of the below.
Sometimes, never, always.

They are all valid responses as I said -- understand the "physics" behind
partitioning and you'll understand what I mean.  Understand the motiviation
behind using partitions (your motivation) and implement accordingly.  Just
remember to consider all aspects of the system

o query
o administration
o load
o anything else you do

and make sure that what you do to optimize one does not hurt the others!

      Rating:
      5
  February 16, 2003
Reviewer:  sush
       from sj,ca
One more quick Q.I've one column in this partitioned table "xyz" say
code_type(has values say a,b,c,d,e,f,g,h)As I'm loading this table with multiple
threads , I'm thinking to create a bitmap index on this column.Is it ok ?Thanks
in advance.

          Followup:

After you load this table, go ahead and create the bitmap

It would be a totally KILLER to load into the table with a bitmapped index on it

      Rating: 4
what if that
      bitmap index is local on the partitions.  February 16, 2003

Reviewer:  Ajeet  from oh,usa
Tom --does the load timing will be impacted even the bitmap index is local to
partitions.?If yes -- then should we set those indexes unusable before the load
and then load .?

          Followup:

depends on how you load -- I'm guessing you are writing your own loader (just
have that impression).   If so  -- disable those bitmaps and rebuild at the end.

      Rating: 5
Very
      informative, as usual  February 17, 2003
Reviewer:
      Colin Davies  from Pasadena, CA USA
I think that this confirms what I have always used as guideline: if an index on
a range-partitioned table INCLUDES the partitioning key, then make it a local
index, otherwise make it a global index.   Is that correct?

          Followup:

well.... no.  not really

table partitioned by x
local index on y

select * from t where x = :x and y = :y;

for example -- local index is OK -- partition elmination kicks in (in this case
the partition key is implicitly in the index as well -- since the index is
equi-partitioned)

or -- I'm in a DW, doing parallel queries anyway -- parallel index range
scans....

It all depends -- there are no "rules".  Thats why I'm into the physics behind
partitioning.  Understand where and how the structures are -- and what your
popular queries will have to do in order to use them -- and make the decisions
based on that.

You might go for a locally partitioned index that you have to range scan N times
per query because the cost of maintaining a global index in your system is too
high.

You might go for a globally partitioned index that you have to range scan 1 time
per query because the cost of range scanning N indexes is much higher to you
then the maintanence of the global index.

      Rating: 5
Very
      informative  February 17, 2003
Reviewer:  Colin
      Davies  from Pasadena, CA USA
I agree about understanding the specifics in each case.  Here is a test I used
to verify conditions in a real database:

The DSSMART.PRODUCT_EVENTS table is range partitioned on START_DT.  All of the
indexes are currently LOCAL indexes.The index PRODUCT_EVENTS_IDX11 is on column
ORD_TYPE_CD.

Some examples:

SELECT COUNT(*) FROM DSSMART.PRODUCT_EVENTS WHERE START_DT > SYSDATE - 2;

Operation   Object Name Rows  Bytes Cost  Object Node In/Out   PStart   PStop

SELECT STATEMENT Hint=CHOOSE     1        600
 SORT AGGREGATE     1     8
  PARTITION RANGE ITERATOR                                      KEY   44
   PARTITION HASH ALL                                       1  4
    INDEX RANGE SCAN   PRODUCT_EVENTS_IDX1  1 M   12 M  600   KEY   176

This uses partition elimination as expected since START_DT is the partitioning
key and is also part of the primary key index.

Let's add another predicate:

SELECT COUNT(*) FROM DSSMART.PRODUCT_EVENTS WHERE START_DT > SYSDATE - 2 AND
ORD_TYPE_CD = 1;

Operation   Object Name Rows  Bytes Cost  Object Node In/Out   PStart   PStop

SELECT STATEMENT Hint=CHOOSE     1        600
 SORT AGGREGATE     1     12
  PARTITION RANGE ITERATOR                                      KEY   44
   PARTITION HASH ALL                                       1  4
    INDEX RANGE SCAN   PRODUCT_EVENTS_IDX1  124 K 1 M   600   KEY   176

So far, so good - including START_DT still gives us partition elimination.

Now let's lose the partitioning key from the query:

SELECT COUNT(*) FROM DSSMART.PRODUCT_EVENTS WHERE ORD_TYPE_CD = 1;

Operation   Object Name Rows  Bytes Cost  Object Node In/Out   PStart   PStop

SELECT STATEMENT Hint=CHOOSE     1        1251
 SORT AGGREGATE     1     4
  PARTITION RANGE ALL                                     1  44
   PARTITION HASH ALL                                       1  4
    INDEX FAST FULL SCAN  PRODUCT_EVENTS_IDX11 2 M   9 M   1251 1  176

We now have PARTITION RANGE ALL, which means that even though it can use index
PRODUCT_EVENTS_IDX11, it will still have to do an index scan on every partition
of this local index.

      Rating: 3
Single column
      bitmap on partition key  April 01, 2003
Reviewer:
      Peter Scott  from England
For our sins my group has "inherited" a DWH with loads of range partitioned
aggregate tables (8.1.7) Most data is parititioned by week, i.e. for week level
summaries each partition has a single date value.

We have found that the designers have created bitmap indexes on each of the
dimension columns of the fact summary INCLUDING the time column - surely this is
not needed as there will only be 1 value referenced in each index partition.
Unless any of you know otherwise ;-}

My thinking is that the data dictionary knows which partition contains which
week, and the bitmap index is not giving us any value even for date predicate
queries.

          Followup:

did they do it for star transformations?

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76994/schemas.htm#11292

      Rating: 3
Answer to
      Tom  April 01, 2003
Reviewer:  Peter Scott
       from England
Sadly, no
Star transforms are not enabled on this DB.

That is one area we are going to fix after a scheduled upgrade to 9.2

Are you saying that STAR transforms also require a bitmap index on the partition
keys

          Followup:

on the foreign keys to the dimension tables.

      Rating:
      5
  April 01, 2003
Reviewer:  Dave
       from Colorado
Star transforms were a little buggy in early 8.1.7 releases, but 8.1.7.4 was
good. you might like to give them a go prior the the 9i upgrade -- they gave us
a great performance boost.

Aside from the star tansformation issue, Oracle doesn't know for sure that there
is a single value per partition (aside from column histogram information), only
that any values fall within a particular range. So if you wanted to place a
predicate on that column that stated "where week_date ='01-jan-2003'" it would
still be beneficial to have that bitmap index there so that it could be
bitmap-AND'ed with other predicated colum bitmaps

      Rating: 5
Thanks Dave
      (& Tom, of course)  April 01, 2003
Reviewer:
      Peter Scott  from England
That was most helpful. The DB was devloped in 8.1.4 i.e before STAR Transforms
worked well.

We will certainly give this a try on our test system - just have to find some
time to put in the dimensions !

      Rating:
      3
  April 23, 2003
Reviewer:  vj
       from in
referring to this partition by range..can i partition with more than one
column...

say col1,col2 range x,y
col1, col2 range z,k
col1, col2 range f,h

          Followup:

yes, it is all documented for you actually.  the documentation would describe
this...

see also

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1200046826714

      Rating: 4
concatenated
      indexes on foreign keys for star query  May 27, 2003

Reviewer:  anonymous  from India
for the star transform the docs say:

There is no need to create many concatenated indexes where the different column
orders match different patterns of constrained dimensions in different queries.

so:

does this imply that for star queries without bitmap indexes, that we must have
many different concatenated indexes to match the different patterns of
constrained dimensions in the different queries?

      Rating:
      4
  September 16, 2003
Reviewer:
      Alvin  from Philippines
I've a partitioned table and i've created local partitions on some columns and
have indexed the partitioning key (TIME).

I've several similar queries that would benefit immensely if they'd use the
index on the partitioning key.

1. How would i tell oracle that this is the way to go ?? My queries are
dynamically created and i cannot readily change the front end program to make
use of the hints.

2. Would it be better to drop the index ??

3. how would i force oracle to full scan the partition instead ? I think block
selectivity  or block distribution of my data are evenly distributed hence the
benefit of full partition scans.

SELECT /*+INDEX (rtbetelcalldetailrecords,CDR_TIME) */ count(*)
FROM rtbetelephony.rtbetelcalldetailrecords
WHERE groupid = 2741
and (TIME BETWEEN 1062219600487 AND 1062305999487)
5  /

      COUNT(*)
--------------
         59679

1 row selected.

Elapsed: 00:03:209.62

Execution Plan
----------------------------------------------------------
   0
SELECT STATEMENT Optimizer=CHOOSE (Cost=68612 Card=1 Bytes=13)

   1    0
  SORT (AGGREGATE)

   2    1
    TABLE ACCESS (BY LOCAL INDEX ROWID) OF 'RTBETELCALLDETAILRECORDS' (Cost=6861
2 Card=1636 Bytes=21268)

   3    2
      INDEX (RANGE SCAN) OF 'CDR_TIME' (NON-UNIQUE) (Cost=268 Card=1636)

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

  1   SELECT count(*)
  2       FROM rtbetelephony.rtbetelcalldetailrecords
  3      WHERE groupid = 2741
  4*  and (TIME BETWEEN 1062219600487 AND 1062305999487)

      COUNT(*)
--------------
         59679

1 row selected.

Elapsed: 00:20:1251.90

Execution Plan
----------------------------------------------------------
   0
SELECT STATEMENT Optimizer=CHOOSE (Cost=33806 Card=1 Bytes=13)

   1    0
  SORT (AGGREGATE)

   2    1
    TABLE ACCESS (BY LOCAL INDEX ROWID) OF 'RTBETELCALLDETAILRECORDS' (Cost=3380
6 Card=1636 Bytes=21268)

   3    2
      INDEX (RANGE SCAN) OF 'CDR_PART_GRPID' (NON-UNIQUE) (Cost=248 Card=1636)

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

          Followup:

tell you what -- when I'm taking questions, this looks like a new one.  You see
-- one would need many more details in order to provide a meaningful answer.

you say you have other queries that would benefit from using this index.  I'd
need to see the plan we are generating and the plan you force to use the index
to see that this is so (and not just a guess) and we can work from there.

I'd need to understand the table/indexing strategy better.

I'd need to see the explain plan as show by $ORACLE_HOME/rdbms/admin/utlxpls.sql
so I can see the partition information as well as the CBO stats used...

all more then I want in a followup here.

I can say the above queries would benefit IMMENSELY if you added groupid to this
index.  If this is a "popular" type of query -- groupid should be in that index.

September 16, 2003 -- Thanks for the question regarding "quick
      question ", version 91

      You Asked
Tom:

sorry to bother you again.

I have a table (a int, b int)
sample data:

1,2
1,4
2,5
2,7
2,8
3,1
3,4
4,5

I want to add one more column to the table so that.
the added column reflect the order of column b regarding the column a.

1,2,1
1,4,2
2,5,1
2,7,2
2,8,3
3,1,1
3,4,2
4,5,1

can you tell me how to do that.

Thanks a lot

      and we said...
analytics rock and roll

ops$tkyte@ORA920> select a, b, row_number() over (partition by a order by b) c
  2    from t;

         A          B          C
---------- ---------- ----------
         1          2          1
         1          4          2
         2          5          1
         2          7          2
         2          8          3
         3          1          1
         3          4          2
         4          5          1

8 rows selected.

Review &
      Followup

      Rating: 5
analytics rock
      and roll  September 06, 2003
Reviewer:
      A reader
something new learned.
Great!!

      Rating:
      2
OK  September 09, 2003
Reviewer:  Mary
       from CT,USA
suppose if I want to have a column like diff_b whose result
set would be like
  diff_b
  -----
   b1-b2
   b2-b3
   b3-b4
   ....
 can analytic functions be made use of?Please do reply.

          Followup:

select b-lead(b) over ( partition by a order by b )
  from t;

lag and lead let you look backwards and forwards in the result set.

      Rating:
      3
Nice  September 10, 2003
Reviewer:
      Mary  from CT,USA
Thanks for your response.I have one more question for you.
It is
 1)Is there a query format like
     select 'select * from p' from q;
  If yes,Please cite some examples.

          Followup:

don't know what you are looking for -- that query will in fact run -- are you
looking for scalar subqueries:

select (select count(*) from emp)
  from dual
/

??

      Rating: 5
create folder
      and save file .fmb in database and getback agin  September
      10, 2003
Reviewer:  Nihad Saleh  from Yemen

hi
i want to know how  i can create folder by developer ? and i wnat to ask if i
can store file has .fmb or .doc in database and how i can get it back agin by
developer
please i have to know

      Rating: 5
create folder
      and save document file in database and restore it  September
      11, 2003
Reviewer:  Nihad Saleh  from Sana'a, Yemen

hi
 i want to know if i can create folder and delete folder ?
and if i can save document file or any type of files in database and restore it
again ?
thank you

          Followup:

er?

what is a folder?

but -- if this is about forms and such -- you might try otn.oracle.com
(discussion forums), I don't really use that particular tool myself.

      Rating: 5
saving document
      file from any partion to database and restore it  September
      12, 2003
Reviewer:  Niahd Saleh  from San'a, Yemen

hi
thank you for answer me , but i want to know how i can save file as document
file or any type of file into database and restore it again?
and thank you agin

          Followup:

using xdb (the xml database feature) or iFS, yes.

there is document foldering available in the database

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96620/toc.htm

      Rating: 5
folder mean
      directory   September 12, 2003
Reviewer:  Nihad
      Saleh  from San'a, Yemen
hi Tom,
i mean of create folder is a Directory as windows directory, but i want to
create empty directory on partion c:\ or any partion like nihad directory "
c:\nihad\ "
can i create it ?

      Rating: 5
what do you
      mean iFS   September 12, 2003
Reviewer:  Nihad
      Salej  from Sana'a, Yemen
hi ,
what do you mean of iFS , and i want to ask you if you can give me a simple
example to make the subject clear for me ?
and thank you again ?

          Followup:

ifs is the "internet file system", a file system in the database.  part of our
collaboration suite offering.

      Rating: 3
Help
      me...  September 13, 2003
Reviewer:  ahmad
      darwish  from Jordan
Hi tom.
I have many  file like ( word – excel – bdf – e.g ) .
And I have Oracle 8i data base .
How can save it In oracle data base and retrieve any time for update and save
again .
Thanks ,,,,

          Followup:

depends on your programming environment -- do you "web", do you "client/server",
what language do you do this in.  is this a one time load, is this something
your users will be using.

there is more missing information then information here.

      Rating: 5
HOW I CAN SAVE
      ANY TYPE OF FILE IN DB BY IFS?  September 16, 2003

Reviewer:  Nihad Saleh  from Saná, Yemen
HI TOM,
HOW I CAN SAVE ANY TYPE OF FILES SUCH AS DOCUMENT FILE INTO DATABASE BY IFS
FEATURES AND RESTORE THE FILE AGAIN ?
CAN YOU EXPLAIN THE SUBJECT MORE TIME ... !?
AND HOW I CAN FOUND IFS AND INSTALL IT ..?

THANK YOU ....

          Followup:

you don't have to shout :)   (all caps makes it feel like you are shouting)....

iFS makes the database look like a file system.  you can attach it as a windows
share for example.  so yes, any file at all can be stored in there.

iFS was part iAS and is now part of the collaboration suite.

http://technet.oracle.com/documentation/ifs.html

September 16, 2003 Nikhil -- Thanks for the question regarding
      "Nologging or Noarchive log , Archivelog Maintenace", version 8.1.7

      You Asked
Hi, Tom,

Got a chance after a long time,

We have PRODUCTION databases with HIGH OLTP kind, and so archive log generation
is much faster, we have some workaround/solution for maintaining the same.

1. Full RMAN Backup once a day and OnDemand for Archivelog only. (Maintenance
free)
2. BCV Configuration, split every 2-6 hours, but On Tape it goes once a week.
Need manual intervation to manage archives.
3. Manual intervation whenever archivelog filesystem over threshold. (Not easy
to maintain)

Now, the situation is, initially design filesystem can hold 1 day of
archivelogs, but after 6 months production every thing has got 3-4 fold
increase.

So, what could be the practical/real world scenario to manager archives.

1. Provide enough filesystem.
On peak duration, even 300GB is not enough, while normal 100GB is fine.
2. Turn some tables to nologging mode, personally don't prefer, as learn from
various posts on your site and metalink.
3. On peak duration, RMAN is not able handle the quantity. We have Veritas
NetBackup 4.5 with Tape Library StorageTek l700, per channel throughput best
300-400MB/Min worst <100MB/Min.
4. Turn DB to noarchivelog mode, increase REDO LOG size to hold atleast 4hrs
transaction, in the case where Data is possible to recreate and DATA LOSS is not
much crucial. (HARD to convince to Management)

So, what do you think ..?

Nikhil

      and we said...
I think #1 is the only reasonable answer -- provide sufficient diskspace.

#2 (nologging) isn't good because it won't even work!  nologging is done for
bulk operations like insert /*+ APPEND */, direct path loads, create table as
select, index builds.  It is doubtful you are doing those.

#3 is just a "statement"?

#4 means "our data isn't worth much.  we can afford to lose it all -- or at
least a days worth".  increasing your redo log size won't do anything for media
recovery (not in a SUPPORTED, 100%, will always work fashion in any case!).
Data loss that is not crucial?  I've yet to see that on a high end system.

You could always look for ways to reduce redo!! look for work you are doing that
isn't really necessary (you might be surprised at what you discover!).  look to
see if there are any batches that process row by row that could do it set based
(less redo and undo).  look for practices like "rebuild indexes on a schedule",
that can really increase the amount of redo you generate as the index needs to
split all over again.  find the causes of your redo generation and look to tune
them as you would anything else.
Review &
      Followup

      Rating: 5
is gzip (or
      compress) archive logs an option?  September 15, 2003

Reviewer:  A reader
Maybe you can gzip archived log files at a schedule (like once or twice an
hour). You will be surprised how much space can be saved. It puts some stress on
the CPU. If your CPU is powerful, maybe this is an option.

      Rating: 4
Can RMAN work
      with compressed Archived log files.  September 15, 2003

Reviewer:  Shailandra  from NJ
Hi Tom,

In our scripts for hot/cold/export backup we use pipe to compress the file while
taking the backup. When I use RMAN to take backup archive log files, if the
files are compressed, RMAN will not be able to find the archive log files and
will error out. Is there a way that If we compress the archive log files, RMAN
is still able to recognize the archivelog files and take its backup and then
delete the archivelog files as usual.

Shailandra

          Followup:

well, only if you name them back to the template name I think -- it is expecting
them to follow that naming convention.

that and rman actually tries to "read" those things, so that would probably not
work either.

I guess I would be relying on the hardware compression on my tape drives to take
care of that and just getting the archives off of the system ASAP.

      Rating:
      5
  September 15, 2003
Reviewer:
      A reader
Tom,

Consider a scenario

Rman used in nocatalog.
Entire database backed up with controlfiles and archivelogs.

Filesystem Corrupted or whatever reason entire file system gone bad.

Can i recover the database using RMAN.

Thanks..

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96566/rcmrecov.htm#1007319

      Rating: 3
Nologging or
      Noarchive log , Archivelog Maintenace  September 16, 2003

Reviewer:  Nikhil  from Mumbai,India
Your suggestion #1 is ok, but not always practical to ask maximum diskspace.
Since, it will be idle for 70% of time. Peak time is last week of the month.

Re : "DATA LOSS" , application has flatfiles as source, loaded --> processed -->
audited --> unwanted records deleted. So, flow is insert/process/delete, only
10-15% data remains. ie. (Fraud Management)

If we loss data, say upto 1 week, we may reprocess data from flatfiles.

#3 RMAN - can allocate maximum channels, with dedicated network bandwith, but
most of times it fails to handle the quantity during peak time.

Look for the ways to generate less redo -
-- we have online partitioning rebuild/purge, but they are in nologging mode.
-- Left with some code, but wrapped plsql packages, can't touch. Even vendor has
confirmed the optimal processing.

Thanks...
Nikhil

          Followup:

remember disk is cheap compared to

a) lost data
b) downtime
c) cpu
d) our salaries in the long term

reprocessing = downtime, has anyone figured out the cost of that (it is
generally HUGE)

      Rating:
      3
  September 16, 2003
Reviewer:
      Adrian  from Exeter, England
I think the two following sections, give a hint at the answer:

Tom:
"You could always look for ways to reduce redo!! look for work you are doing
that isn't really necessary (you might be surprised at what you discover!).
look to see if there are any batches that process row by row that could do it
set based (less redo and undo)."

Nikhil:
"Re : "DATA LOSS" , application has flatfiles as source, loaded --> processed
--> audited --> unwanted records deleted. So, flow is insert/process/delete,
only 10-15% data remains. ie. (Fraud Management)"

Change the way you load/process your data. Bulk load into no-logging tables,
during the process phase insert the 10-15% that you actually want to keep into
the real tables, then, when done truncate the no-logging tables. This way you
don't get redo associated with the initial load, or the delete.

      Rating: 4
External
      Tables  September 16, 2003
Reviewer:
      Shailandra  from NJ
What about using the external tables instead of bulk loading. May be compare the
result of data processing timeing using external tables or bulk loading the data
and processing.

Cheers

      Rating:
      4
Correction.  September 16, 2003
Reviewer:
      Shailandra  from NJ
I am sorry, he is using Oracle 8i which does not have external tables. So above
suggeston is of no use.

      Rating: 3
Nologging or
      Noarchive log , Archivelog Maintenace  September 16, 2003

Reviewer:  Nikhil  from Mumbai, India
Hi,
Thanks everyone for your inputs ..

I think ultimately TOM is right, we have to go for sufficient filesystem.

Adrian :
Yes, we are almost following the same things, loading into PARTITIONS, selecting
the 10-15%, insert into IMP tables, truncating PARTITIONS.

Anyway, it's scenario for Capacity Planning.

September 16, 2003 cindy -- Thanks for the question regarding
      "Foreign key", version 8.1.6

      You Asked
Hi Tom,

We have a new project provide by third party company.  This is
loan-mortgage application.  I found that all the loan level
tables(350) have a foreign key point to one table.  I am afraid
this design will degrade oerformance in the future.  for example

concurrent insert into child tables.  There will be at least 300
users to use this application concurrently.  Do I need to
concern this foreign key issue?

Thanks in advance

Cindy Miao

      and we said...
No, it will not be a performance issue (and think of the
converse -- lack of data integrity)....

You'll just want to ensure that

- you never update the parent tables primary key (its not the
primary key if you do)

- you do not delete from the parent table

--- OR ---

the foreign keys are indexed.

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:292016138754
for more info.

Review &
      Followup

      Rating: 3
Back to
      basics  September 16, 2003
Reviewer:  Vivek
       from UK
Tom,

When i am creating parent child relationship among tables, its only number and
datatypes that should match. Size of the column doesn't matter ?

I have tried the following, to prove the above, would you please confirm this.
---------------------
SQL> create table test1(col1 varchar2(10),
  2                     col2 varchar2(8),
  3       constraint pk_test1 PRIMARY KEY (col1));

Table created.

SQL> create table test2(col1 varchar2(8),
  2                     col2 varchar2(10),
  3       constraint pk_test2 primary key (col1),
  4*      constraint fk_test2_col1 foreign key (col1) references test1)
SQL> /

Table created.

SQL> alter table test2 modify col1 varchar2(12);

Table altered.
----------------------

Thanks

          Followup:

as demonstrated, correct.

the base types must match.
the sizes do not

however, I cannot imagine a case whereby it would make sense to have a child
tables column permit "larger" values then the parent -- smaller, maybe, larger
-- not a good practice.

      Rating:
      3
review  September 16, 2003
Reviewer:
      vivek  from UK
Tom,

your view about size of child column

"to have a child tables column permit "larger" values then the parent-- smaller,
maybe, larger -- not a good practice."

I agree larger doesn't make much sense but if i have smaller size than parent
than i have fairly good chances of hitting "insert value too large.." error. I
think thats why you have said "maybe".

Thanks

          Followup:

no, the maybe was there because it could be the parent has a larger domain of
values then you want that specific child to have.

it "might be" valid to have a smaller size.
it "cannot be" valid to have a larger size.

September 16, 2003 Om -- Thanks for the question regarding "Question
      regarding Redo log generation", version 8.1.7

      You Asked
Dear Tom,

Greetinhgs.

Just wanted to ask a Question on Redo generation for a database.

We have three Live (PRODUCTION database) 8.1.7 exisiting on
IBM AIX unix servers.The databases are in ARCHIVELOG mode.

Could you please let us know as

"How do you calculate the Redo generation for a database
in a particular day or for a week".

I think we should Query on v$archived_log to find the same but
really do not know as to how to write a Query ?

Could you please help us.

Best regards,
Om

      and we said...
  1  select trunc(completion_time,'w'),
round(sum(blocks*block_size/1024/1024/1024),2) total_size_in_gig
  2  from v$archived_log
  3* group by  trunc(completion_time,'w')
ops$tkyte@ORA9I> /

TRUNC(COM TOTAL_SIZE_IN_GIG
--------- -----------------
08-AUG-03              2.29
15-AUG-03             11.07
22-AUG-03             10.97
29-AUG-03              1.79
01-SEP-03              6.13
08-SEP-03              8.03
15-SEP-03               3.3

7 rows selected.

just use trunc(completion_time) for "day"
Review &
      Followup

      Rating: 5
Redo log
      generation calculation  September 17, 2003

Reviewer:  Om  from India
Hi Tom,

Simply excellent.It is very useful for us.

Many thanks.

September 16, 2003 John -- Thanks for the question regarding "Check
      for record existence in C++", version 9.2.0.3.21

      You Asked
We want to check for the existence of a table record in a pl/sql procedure.

We can do this several ways:

declare
empexists number;
begin
select count(*) into empexists
from emp;
if empexists then
processing...
end if;
end;
/

Or
declare
cursor empexists is
select * from emp;
emprec empexists%rowtype;
begin
open empexists;
fetch empexists into emprec;
if empexists%found then
processing...
end if;
end;
/

We tried some examples and tkprof shows the second is much quicker because it
does less (no) fetching. This is despite the explicit cursor. I guess if we
sorted out the syntax for doing this implicitly it would be even quicker.

I can't get my head around why this is the case.  Please could you explain it
for me?

I thought this is a question you would already have answered but I couldn't find
it when I searched. Please forgive me if you have already answered this
question.

Many thanks,

John

      and we said...
select count(*) into empexists
  from DUAL
 where exists ( select null from emp );

or

select count(*) into empexists
  from emp
 where rownum = 1;

rather then counting all records, just count the FIRST occurence.  that is all
you need.

or best yet --

  for x in ( select * from emp )
  loop
     processing....

code the logic such that NO CHECK IS NEEDED, just process....  (that is my
favorite, just do the process -- so what if there are no records to process,
what have you lost??)
Review & Followup

      Rating:
      4
  September 16, 2003
Reviewer:
      A reader

September 16, 2003 Colin -- Thanks for the question regarding "a
      procedure to rollback in-doubt transactions.", version

      You Asked
I am trying to create a script which will find any
In-Doubt transactions and then roll them back. I know what they
are and this methodology is OK.
However I cannot insert the variable correctly into the Rollback
or Purge commands.
The print statement work correctly. Please help!!

Here is my Script....

SET serveroutput on
VARIABLE my_trans_id1 VARCHAR2(22);
DECLARE
v_trans_id2 VARCHAR2(22);
BEGIN
SELECT local_tran_id
INTO v_trans_id2
FROM dba_2pc_pending;
DBMS_OUTPUT.PUT_LINE ('My Variable in the PL/SQL Block is: '||
(v_trans_id2));
:my_trans_id1 := v_trans_id2;
END;
/
print my_trans_id1;
ROLLBACK FORCE 'my_trans_id1';
EXECUTE dbms_tranaction.purge_lost_db_entry(my_trans_id1);

      and we said...
Well, you cannot use bind variables in DDL at all and
my_trans_id1 would be
a bind variable.  Here i would suggest using dbms_sql and
dynamic sql --
especially since you may have >1 transaction to process (and
youre select
into will start failing).  I'm thinking something like:

declare
   cursor_name     pls_integer default dbms_sql.open_cursor;
   ignore          pls_integer;

   procedure execute_immediate( p_sql in varchar2 )
   is
   BEGIN
      dbms_sql.parse(cursor_name, p_sql, dbms_sql.native);
      ignore := dbms_sql.execute(cursor_name);
      dbms_sql.close_cursor(cursor_name);
   END;

begin
   for x in ( select local_tran_id from dba_2pc_pending )
   loop
      dbms_output.put_line( 'My variable is ' || x.local_tran_id
);
      execute_immediate( 'rollback force ''' || x.local_tran_id
|| '''' );
      dbms_transaction.purge_lost_db_entry(x.local_tran_id);
   end loop;
end;
/

if you really want to do it as separate commands, you'll need to
use macro
variables -- not bind variables.  It might look like:

column local_tran_id new_value TRANS_ID
select local_tran_id from dba_2pc_pending;
rollback force '&TRANS_ID';
exec dbms_transaction.purge_lost_db_entry(&TRANS_ID);

Again -- that'll fail unless you don't have exactly 1 row in
dba_2pc_pending
when you run this (you could add a where ronum = 1 to the query
to let it
get one row at a time and just keeping running it until there is
no more
data)

Review &
      Followup

      Rating: 5
where will this
      occur  September 29, 2001
Reviewer:  Thank
      you  from USA
Give  me a scenario where I will have in doubt transaction and which I have to
rollback dynamically.

I have never encountered this kind of a situation , but believe this is very
useful.

Kindly illustrate with a scenario

          Followup:

The 2 phase commit protocol goes through 2 stages.

Before the commit you make updates to many sources -- say three databases.  One
of the databases is the coordinator.

When you issue COMMIT, the coordinator takes over.  It will go out to each of
the sites and ask them "are you ready to commit".  They will answer YES or NO.
This is the voting or PREPARE stage.  After they VOTE yes, they must be prepared
to commit.

When the coordinator collects the votes, it will determine if everyone said
"YES".  If they did -- the coordinator will commit itself and broadcast to the
other participants "COMMIT" (else it broadcasts rollback).

It is during the period of time between the VOTING (prepare) phase and the
broadcast that you may have a distributed in doubt transaction.  Say SITE1 was
the coordinator and SITE2 and SITE3 the participants.  If after SITE2 voted
"YES" to commit -- SITE1 crashed (or the network failed or whatever) -- SITE2
would be left hanging -- it would not know whether to commit or rollback the
transaction.  Data would be locked, rows would be "in doubt" as to their value.
If the outage was long in duration -- the DBA at SITE2 could phone the other DBA
 at SITE1 and ask them "how did this transaction end up".  The DBA at SITE1
could tell them it committed or rolled back and the DBA at SITE2 could force the
in doubt distributed transaction.

In the normal course of operation -- this will not happen.

      Rating:
      5
  September 15, 2003
Reviewer:
      A reader
Tom,

Can i force a commit in another user session if its not a  distributed database
(local database).

Thanks..

          Followup:

nope, you can force a "rollback" (kill em), but not a commit

(you have NO idea if they are ready to commit or not)

      Rating:
      5
  September 15, 2003
Reviewer:
      A reader
Thanks Tom

      Rating: 4
How to rollback
      ?  September 16, 2003
Reviewer:  Tony
       from India
Rollback segment status is 'Partialy Active' even after restarting database. I
think there are some pending transactions in the rollback segment. What should I
do to do rollback the transaction?

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96521/ds_txns.htm#991

      Rating: 5
In-doubt
      transactions locking up the database  October 10, 2003

Reviewer:  Arun Gupta  from Harrisburg, PA USA

Tom,
We have a heterogeneous system with one Oracle and one non-Oracle database. A
third party TPM initiates the 2pc transactions. Once the transactions enter the
prepare phase something happens and the TPM can neither commit or rollback. We
are investigating this issue. The problem is that on the Oracle side, we observe
that till we manually clear this in-doubt transaction, all the tables, even the
ones which do not participate in 2pc transaction, are locked for reads. Is this
expected behaviour? The administrator's guide says as:
=======================================================
Prepare Phase
The first phase in committing a distributed transaction is the prepare phase. In
this phase, Oracle does not actually commit or roll back the transaction.
Instead, all nodes referenced in a distributed transaction (except the commit
point site, described in the "Commit Point Site") are told to prepare to commit.
By preparing, a node:

Records information in the online redo logs so that it can subsequently either
commit or roll back the transaction, regardless of intervening failures

Places a distributed lock on modified tables, which prevents reads
========================================================
The last line says that only the modified tables are locked. I have read lot of
articles on Metalink, but cannot understand what is going on.
Thanks.

      Rating: 5
Please ignore
      the question  October 10, 2003
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
Tom,
Please ignore my post. It was a typical case of communication distortion when
going through a noisy channel.
Thanks.

September 16, 2003 -- Thanks for the question regarding "Query on
      multiple union all results into one ", version 8.0.6

      You Asked
Hi Tom,

I have to re-build a query which should display the results in three blocks in a
web page. The first block should contain the results related to sysdate only.
The second block should contain results between 1st of current month to sysdate
only . The third block should contain data from january 1st of current year to
sysdate. currently we are using 3 separate queries for each block of results
with union all clause. This is taking lots of time(30 minutes+) as each query is
doing a full scan of the table. Like, anyway the last block, i.e., from 1st of
the year to till date has the data for today & for the range from 1st of the
month to till date, so would like to know is there any way we can fetch the 1st
block data & 2nd block data from the third query?

We cant do this with the application as we were not supporting the
application.We were planning to do it with a temporary table.first we will query
the data between 1st if this year till today and place it in a temporary table
and query the temporary table for each block in the web page.is this the way to
do it?  I would like to know is there any other way this can be done from a
single SQL query?. Please explain with an example.

Here is the query that was being used till date

//Query to fetch data for today/current date ---------------------- 1

SELECT MD.PRODUCTTYPE,  'TOD' AS DURATION,SUM(FX.PROXYORDERINGCUSTOMERPAYAMT) AS
SUM , SYS.DAT AS CURRDATE
FROM
FXO2DEAL FX,  (SELECT DISTINCT AVTREFNUM, PRODUCTTYPE
FROM MODENAMISDEAL,  (SELECT MISNUMBER  FROM MODENAMISSTATETRANSITION
WHERE STATE='DealCompletes') MST WHERE  CUSTOMERTYPE='Financial Institution' AND
MODENAMISDEAL.MISNUMBER = MST.MISNUMBER) MD, (SELECT SYSDATE AS DAT FROM DUAL)
SYS
WHERE
DEALTS BETWEEN TO_DATE(TO_CHAR(SYS.DAT-1,'DD-MON-YYYY')||' '||'17:01:00',
'DD-MON-YYYY HH24:MI:SS')  AND TO_DATE(TO_CHAR(SYS.DAT,'DD-MON-YYYY')||'
'||'17:00:59', 'DD-MON-YYYY HH24:MI:SS')
AND FX.AVTREFNUM = MD.AVTREFNUM
GROUP BY MD.PRODUCTTYPE

UNION ALL

//Query to fetch data from 1st of the month & to till date -------- 2
SELECT MD.PRODUCTTYPE,  'MTD',SUM(FX.PROXYORDERINGCUSTOMERPAYAMT) AS SUM ,
SYS.DAT AS CURRDATE
FROM
FXO2DEAL FX, (SELECT DISTINCT AVTREFNUM, PRODUCTTYPE  FROM MODENAMISDEAL,
(SELECT MISNUMBER  FROM MODENAMISSTATETRANSITION  WHERE  STATE='DealCompletes')
MST
WHERE CUSTOMERTYPE='Financial Institution' AND  MODENAMISDEAL.MISNUMBER =
MST.MISNUMBER) MD,
(SELECT SYSDATE AS DAT FROM DUAL) SYS
WHERE
DEALTS BETWEEN
TO_DATE(TO_CHAR(LAST_DAY(ADD_MONTHS(SYS.DAT,-1)),'DD-MON-YYYY')||'
'||'17:01:00', 'DD-MON-YYYY HH24:MI:SS')  AND
TO_DATE(TO_CHAR(SYS.DAT,'DD-MON-YYYY')||' '||'17:00:59', 'DD-MON-YYYY
HH24:MI:SS')  AND FX.AVTREFNUM = MD.AVTREFNUM
GROUP BY MD.PRODUCTTYPE

UNION ALL

//Query to fetch data from 1st of the year to till date ------- 3
SELECT MD.PRODUCTTYPE,  'YTD',SUM(FX.PROXYORDERINGCUSTOMERPAYAMT) AS SUM ,
SYS.DAT AS CURRDATE
FROM
FXO2DEAL FX, (SELECT DISTINCT AVTREFNUM, PRODUCTTYPE FROM MODENAMISDEAL, (SELECT
MISNUMBER
FROM MODENAMISSTATETRANSITION WHERE  STATE='DealCompletes') MST  WHERE
CUSTOMERTYPE='Financial Institution' AND  MODENAMISDEAL.MISNUMBER =
MST.MISNUMBER) MD,
(SELECT SYSDATE AS DAT FROM DUAL) SYS
WHERE
DEALTS BETWEEN TO_DATE('31-Dec-'||(TO_CHAR(SYS.DAT,'YYYY')-1)||' '||'17:01:00',
'DD-MON-YYYY HH24:MI:SS')  AND TO_DATE(TO_CHAR(SYS.DAT,'DD-MON-YYYY')||'
'||'17:00:59', 'DD-MON-YYYY HH24:MI:SS')  AND FX.AVTREFNUM = MD.AVTREFNUM
GROUP BY MD.PRODUCTTYPE

regards,
Vijaya Chander V.S

      and we said...
I think i have the "sign" logic right -- we'll get all three values as columns.
we use the "big" between and then just sum decode's based on the date values.

some comments:

a) using "sum" as a column alias is a bad idea
b) using "sys" as a correlation name -- really bad
c) just use sysdate, not dual to get sysdate

basically the decode logic takes your "between a and b" value of "A" (the low
end) and subtracts DEALTS from it.  If that is negative (meaning dealts happens
ON or AFTER "A"), we sum that value -- else we do not. (note: i subtracted 1
second from "A" here)

SELECT MD.PRODUCTTYPE,
       sysdate,
       SUM(FX.PROXYORDERINGCUSTOMERPAYAMT) AS ytd_SUM,
       sum( decode(
sign(TO_DATE(TO_CHAR(LAST_DAY(ADD_MONTHS(sysdate,-1)),'DD-MON-YYYY')||'
'||'17:00:59', 'DD-MON-YYYY HH24:MI:SS')-dealts),
                    -1, FX.PROXYORDERINGCUSTOMERPAYAMT ) ) as mtd_sum,
       sum( decode( sign( TO_DATE(TO_CHAR(sysdate-1,'DD-MON-YYYY')||'
'||'17:00:59', 'DD-MON-YYYY HH24:MI:SS')-dealts),
                    -1, FX.PROXYORDERINGCUSTOMERPAYAMT ) ) as tod_sum
  FROM FXO2DEAL FX,
       (SELECT DISTINCT AVTREFNUM, PRODUCTTYPE
          FROM MODENAMISDEAL, (SELECT MISNUMBER
                                 FROM MODENAMISSTATETRANSITION
                                WHERE  STATE='DealCompletes') MST
         WHERE CUSTOMERTYPE='Financial Institution'
           AND  MODENAMISDEAL.MISNUMBER = MST.MISNUMBER) MD
 WHERE DEALTS BETWEEN TO_DATE('31-Dec-'||(TO_CHAR(sysdate,'YYYY')-1)||'
'||'17:01:00', 'DD-MON-YYYY HH24:MI:SS')                   AND
TO_DATE(TO_CHAR(sysdate,'DD-MON-YYYY')||' '||'17:00:59', 'DD-MON-YYYY
HH24:MI:SS')
   AND FX.AVTREFNUM = MD.AVTREFNUM
GROUP BY MD.PRODUCTTYPE

Review &
      Followup

      Rating: 3
coffee
      tales  September 16, 2003
Reviewer:  Gabriel
       from Toronto
My “2 cents” for Vijaya … deal with that full-table scan.

What follows is not that relevant (is just the coffee talking … or writing!) …

I’m reading: “I have to re-build a query which should display the results in
three blocks in a web page”
I’m thinking: “Good … (s)he has the liberty to re-write some query … (browse to
the query) … man, it is an ugly one … (s)he didn’t even bother to format it!”

I’m reading: <<the ugly query>>
I’m thinking: “Should I go have a coffee instead? … No … too many coffees
already!” … “So a Day is from 17:01:00 yesterday till 17:00:59 today … where is
that 1 minute after 5PM coming from? … doesn’t matter … but intriguing
nonetheless!”

I’m reading: “This is taking lots of time(30 minutes+) as each query is
doing a full scan of the table
I’m thinking: “So ... you need to improve the response time and not necessarily
re-write the query! Maybe an index would do it … if it runs in 30 seconds would
you bother to re-write the query?”

I’m reading: “so would like to know is there any way we can fetch the 1st block
data & 2nd block data from the third query?”
I’m thinking: “is (s)he suggesting to fetch all data for the year, by day, and
then filter and aggregate the YTD, MTD and DTD in the client? … re-inventing
database functionality in client code? … No! … Tom’s gonna have (her)him for
breakfast”

I’m reading: “We cant do this with the application as we were not supporting the
application”
I’m thinking: “    Hmmm … re-writing a query, probably embedded in the
application ==> (s)he is allowed to change the application … I’m confused now
about the leeway (s)he has on making changes”

I’m reading: <<something about a temporary table for the 3rd query, etc.>>
I’m thinking: “Oh, just an implementation path for the same theme … yes, one
could attempt to do something in PL/SQL and return a ref cursor … it could be
faster if those 3 FT-scans are replaced with just 1 … but then again, why don’t
deal with the FT-scan and then see if the SQL-only approach will do?”

I’m reading: “I would like to know is there any other way this can be done from
a single SQL query?”
I’m thinking: “Good … we have a reasonable question”

I’m reading: <<the ugly query>>
I’m thinking: “That coffee is tempting!”

I’m reading: “Vijaya Chander V.S”
I’m thinking: “What is ‘V.S’”

I’m reading: <<Tom’s answer>>
I’m thinking: <<Yep, good observations about SUM, SYS and the Cartesian product
with a 1-row table holding the SYSDATE … OK, he went for the decode, sign
combination with the YTD, MTD, DTD values returned as columns … but, he didn’t
format the SQL! … He just hacked (the good way) at the original ugly SQL!”

I’m reading: <<Tom’s SQL formatted>>
I’m thinking: “…||(TO_CHAR(sysdate,'YYYY')-1)||… that is, String minus Number …
OK, he just copied that from Vijaya … I don’t like implicit conversions … why
take the chances?”

I’m reading: <<Tom’s SQL formatted>>
I’m thinking: “Did he get the SIGN logic right? He wasn’t sure! … well, the
Amount is included in the SUM for MTD_SUM when DEALTS is greater than the lower
limit of the MTD interval … that is good. But why did he leave it like this
SUM(DECODE(SIGN(..),-1, Amount))? … the DECODE will return the Amount or NULL,
the SUM will ignore the NULLs … hence OK … but I would’ve put
DECODE(SIGN(..),-1, Amount, 0) just to be obvious (for whomever might read it).
Maybe I have a problem trying to be too obvious?”

I’m reading: <<Tom’s SQL formatted>>
I’m thinking: “Wait a minute … the original queries had BETWEEN some
date@17:01:00 and sysdate@17:00:59 … BETWEEN works on closed intervals (edges
included) … his SIGNs only dealt with –1, hence ‘grater than’ … hey, he doesn’t
include the Amount for records having DEALTS equal to the lower MTD limit! …
Stop, false Alarm! … He got himself covered, he didn’t just copy/paste the
LAST_DAY/ADD_MONTHS bit … he replaced the original 17:01:00 with 17:00:59 … the
man is crafty!! … How does a real programmer dude become a VP? … Now that is the
Million dollar question”

I’m reading: <<Tom’s SQL formatted>>
I’m thinking: “A question remains though … can Vijaya change the app for this
query? … this query will return all relevant PRODUCTTYPE based on the YTD
criteria … hence there may be plenty of PRODUCTTYPEs in the result set having
TOD_SUM or MTD_SUM set to 0 (actually NULL with Tom’s query). To avoid showing
irrelevant results the Day and Month-To-Date blocks should do some filtering in
the client code.”

I’m reading: <<The whole thing>>
I’m thinking: “Why don’t they say STATUS rather then STATETRANSITION … the OOD
guys and gals are pushing the science to its edges … just look at this one …
PROXYORDERINGCUSTOMERPAYAMT … mighty close to that 30 chars limit” … (Vijaya …
don’t take this personally … you are the good one … you looked for the answer in
the right place!)

I’m reading: <<My mind and my watch>>
I’m thinking: “I have too much hostility! … and so much work to do! ...  better
get that coffee”

Gabriel

September 16, 2003 Leon -- Thanks for the question regarding
      "monitor cpu and disk I/O usage and contention", version 7.3.4

      You Asked
Tom,

1) How can I monitor CPU and disk I/O usage or contention
for different user sessions, user application and process
from OS prompt (environment)?

2) Can I trace which SQL statement from a user seesion uses
the resources like CPU and disk I/O mostly?

3) How can I determine high water mark for a table? Can I
deallocate unused space below high water mark after some
rows are deleted for a table?

Thanks,

Leon,

      and we said...
1) well, that varies from os to os.  On Windows -- perform.  On
unix tools like top, sar, vmstat, and so on work.  Every OS has
their own "favorite" tool (glance on hp and so on).

2) cpu time, not really, disk -- yes.  The v$sqlarea view will
give you that information.

3) see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:231414051079
for the usage details of dbms_space. It'll do this for you.
 A high water mark is a permanent thing.  Once moved up -- it
never moves down.  Only a truncate or drop will reduce it for a
TABLE (or alter table MOVE in 8.1 and up).  An alter index
rebuild can reduce it for indexes in 7.x and 8.x -- and in 8i,
the index rebuild can be done online (no downtime)

Review &
      Followup

      Rating: 4
Will just
      analyzing table help in 8i   September 16, 2003

Reviewer:  Oracleo  from India
Hi Tom,

Will analyzing table using command 'Analyze table <table_name> compute
statistics;' reset the HWM in 8.1.7.

Thanks,
Oracleo

          Followup:

no, it will just let oracle now how much of the space under the HWM is actually
used.

in 9iR2 and before, the only way to reset the HWM is a rebuild.

      Rating:
      2
  September 16, 2003
Reviewer:  Sagi
       from India
So how do we do in 9i R2.

Thanx in advance.

Sagi

          Followup:

i said "in 9iR2 AND before".....

the only way to reset the HWM is to rebuild the segment -- alter table move,
export/import, create table as select, etc.

September 16, 2003 Neeraj -- Thanks for the question regarding
      "Underlying Algorithm For File Extention", version 8.1.7.3

      You Asked
Tom,

     Thanks Always for such a great website. My question is regarding
     the file extention of the locally managed tablespaces. If a
     tablespace has 10 files on 2 different devices, does oracle
     balance the size of each file while extending them. Can we
     predict which file would be extended next.

Thanks,
Neeraj

      and we said...
simple simulation -- shows that we do it round robin, we get to a file and try
to allocate an extent:

ops$tkyte@ORA920> REM drop table t;
ops$tkyte@ORA920> REM drop table space_usage;
ops$tkyte@ORA920> REM
ops$tkyte@ORA920> REM drop tablespace testing including contents;
ops$tkyte@ORA920> REM
ops$tkyte@ORA920> REM create tablespace testing
ops$tkyte@ORA920> REM datafile size 128k autoextend on next 64k,
ops$tkyte@ORA920> REM          size 256k autoextend on next 64k,
ops$tkyte@ORA920> REM          size 128k autoextend on next 64k,
ops$tkyte@ORA920> REM          size 256k autoextend on next 64k,
ops$tkyte@ORA920> REM          size 128k autoextend on next 64k,
ops$tkyte@ORA920> REM          size 256k autoextend on next 64k,
ops$tkyte@ORA920> REM          size 128k autoextend on next 64k,
ops$tkyte@ORA920> REM          size 256k autoextend on next 64k
ops$tkyte@ORA920> REM extent management local uniform size 64k
ops$tkyte@ORA920> REM /
ops$tkyte@ORA920> REM
ops$tkyte@ORA920> REM create table space_usage
ops$tkyte@ORA920> REM as
ops$tkyte@ORA920> REM select file_name, bytes/1024 kbytes, 0 num_rows
ops$tkyte@ORA920> REM   from dba_data_files
ops$tkyte@ORA920> REM  where tablespace_name = 'TESTING'
ops$tkyte@ORA920> REM /
ops$tkyte@ORA920> REM
ops$tkyte@ORA920> REM
ops$tkyte@ORA920> REM
ops$tkyte@ORA920> REM create table t ( x char(2000) ) tablespace testing;
ops$tkyte@ORA920> REM
ops$tkyte@ORA920> REM declare
ops$tkyte@ORA920> REM     l_last_size number;
ops$tkyte@ORA920> REM     l_curr_size number;
ops$tkyte@ORA920> REM     l_rows_at_a_time number := 10;
ops$tkyte@ORA920> REM begin
ops$tkyte@ORA920> REM     select sum(bytes) into l_last_size
ops$tkyte@ORA920> REM       from dba_data_files
ops$tkyte@ORA920> REM      where tablespace_name = 'TESTING';
ops$tkyte@ORA920> REM
ops$tkyte@ORA920> REM     for i in 1 .. 100
ops$tkyte@ORA920> REM     loop
ops$tkyte@ORA920> REM             insert into t
ops$tkyte@ORA920> REM             select 'x'
ops$tkyte@ORA920> REM               from all_users
ops$tkyte@ORA920> REM              where rownum <= l_rows_at_a_time;
ops$tkyte@ORA920> REM             commit;
ops$tkyte@ORA920> REM
ops$tkyte@ORA920> REM         select sum(bytes)
ops$tkyte@ORA920> REM               into l_curr_size
ops$tkyte@ORA920> REM           from dba_data_files
ops$tkyte@ORA920> REM          where tablespace_name = 'TESTING';
ops$tkyte@ORA920> REM
ops$tkyte@ORA920> REM             if ( l_curr_size <> l_last_size )
ops$tkyte@ORA920> REM             then
ops$tkyte@ORA920> REM                     insert into space_usage
ops$tkyte@ORA920> REM                     select file_name, bytes/1024,
i*l_rows_at_a_time
ops$tkyte@ORA920> REM                       from dba_data_files
ops$tkyte@ORA920> REM                      where tablespace_name = 'TESTING';
ops$tkyte@ORA920> REM                     l_last_size := l_curr_size;
ops$tkyte@ORA920> REM             end if;
ops$tkyte@ORA920> REM     end loop;
ops$tkyte@ORA920> REM end;
ops$tkyte@ORA920> REM /
ops$tkyte@ORA920> REM
ops$tkyte@ORA920>
ops$tkyte@ORA920> select file_name,
  2         max(decode(rn,1,kbytes)) kb,
  3         max(decode(rn,2,kbytes)) kb,
  4         max(decode(rn,3,kbytes)) kb,
  5         max(decode(rn,4,kbytes)) kb,
  6         max(decode(rn,5,kbytes)) kb,
  7         max(decode(rn,6,kbytes)) kb,
  8         max(decode(rn,7,kbytes)) kb,
  9         max(decode(rn,8,kbytes)) kb
 10    from (
 11  select file_name, row_number() over (partition by file_name order by
num_rows) rn, kbytes
 12    from space_usage
 13         )
 14   group by file_name
 15  /

FILE_NAME                        KB   KB   KB   KB   KB   KB   KB   KB
------------------------------ ---- ---- ---- ---- ---- ---- ---- ----
/d02/oradata/ora920/o1_mf_test  128  128  128  128  128  128  128  192
ing_zpgo4pyx_.dbf

/d02/oradata/ora920/o1_mf_test  256  256  256  256  256  256  256  256
ing_zpgo4pz0_.dbf

/d02/oradata/ora920/o1_mf_test  128  192  192  192  192  192  192  192
ing_zpgo4pz6_.dbf

/d02/oradata/ora920/o1_mf_test  256  256  320  320  320  320  320  320
ing_zpgo4q0z_.dbf

/d02/oradata/ora920/o1_mf_test  128  128  128  192  192  192  192  192
ing_zpgo4q1q_.dbf

/d02/oradata/ora920/o1_mf_test  256  256  256  256  320  320  320  320
ing_zpgo4q2d_.dbf

/d02/oradata/ora920/o1_mf_test  128  128  128  128  128  192  192  192
ing_zpgo4q2v_.dbf

/d02/oradata/ora920/o1_mf_test  256  256  256  256  256  256  320  320
ing_zpgo4q3j_.dbf

8 rows selected.

note the nice diagonal...
Review & Followup

      Rating: 5
Sorry:
      OT  September 16, 2003
Reviewer:  Martin
      Guillen  from Buenos Aires, Argentina.
I'm sorry about this OT but:
Tom, are you a mathematician?
I've seen high level of math skills in your responses.
This algebraic matrix remember me my college courses.

          Followup:

yes, I was a mathematician in college, abstract algebra (theorems, proofs, not
too many numbers after the first year) was my topic.

then, i found you really couldn't be employed by tons of people as a math guy --
so i touched the computer for the first time really after college....

      Rating:
      5
  September 16, 2003
Reviewer:
      A reader
I bet he is. I think mathematitions make excellent Oracle professionals. I read
in Jonathen Lewis's site that he was a Math teacher in his earlier life.

September 16, 2003 ALLAN -- Thanks for the question regarding "AUTO
      COMMIT", version 7

      You Asked
     Hi Sir,

       We have an application that executes stored procedure
       via VBA . Apparently ,it auto commits that statement.

       Is there a way to configure the oracle database to set
     the auto commit to off so that we have to issue "COMMIT"
     for every stored procedure we execute ?

     Thanks very much .

     Allan

      and we said...
Well, it is not an Oracle configuration issue -- the only method
Oracle operates in is "client tells us when to commit".  We do
not have an autocommit mode.  Various TOOLS and API's do (eg: I
can tell sqlplus to autocommit, that just means sqlplus will
issue a commit after each statement).

Microsofts various API's pretty much autocommit by default
(SQLServer hates locks, performance pretty much goes down the
drain, hence they love to commit for you -- at the expense of
things like transactional integrity but thats another story).

You'll have to dig around in the VBA api to find out the proper
set of calls to make.  I don't program VBA myself but if you are
using ODBC for example, the solution is:

...
In native ODBC, there are 2 API calls that affect transactions:
SQLSetConnectOption and SQLTransact. The SQLSetConnectOption is
used to set the transaction mode for that connection to read
write or read only and also to enable/disable the Autocommit
transaction mode.  The SQLTransact call is used to commit or
rollback a transaction.

Autocommit is enabled by default as defined by the Microsoft
ODBC
specification.  With Autocommit, a transaction is committed as
soon as it is executed.  While this increases user concurrency
in some databases, it does not allow the programmer to control
when a transaction is  committed nor does it allow a transaction
to be rolled back.  To disable Autocommit, call
SQLSetConnectOption with the SQL_AUTOCOMMIT_OFF qualifier.

Since there are native ODBC calls to handle transactions, the
ODBC driver must keep track of the transaction state at all
times.  For this reason, executing any statement that affects
transactions is not supported via SQLExecute or SQLExecDirect
calls.  This includes SET TRANSACTION, DECLARE TRANSACTION,
COMMIT, and ROLLBACK.

While this is not a problem for Native ODBC programmers, this
may be a problem for people who use 4GL development tools to
generate the native ODBC for them.  Many of these 4GLs do not
allow the developer to access the proper parameters to make
these calls successfully. These parameters are the connection
handle (HDBC) and the statement handle (HSTMT).

....

Hope this gets you pointed in the right direction.

Review &
      Followup

      Rating:
      5
autocommit  September 16, 2003
Reviewer:
      A reader
if autocommit is off - when I log off sqlplus - it
still commits. Is there a way to prevent this?
I want sqlplus to never commit unless I say so
explicitly.

thanx!!!!

          Followup:

autocommit ONLY affects statements

autocommit off:

insert
update
delete
commit

that is a single transaction.  autocommit on

insert
update
delete
commit

is now 4 transactions.

sqlplus always commits upon exit -- it is just they way it works.  exit and
commit.

      Rating:
      5
thanx!  September 17, 2003
Reviewer:
      A reader
that makes it clear!!

September 16, 2003 Raghava Rao -- Thanks for the question regarding
      "Displaying the maximum value for every record", version 8.1.6

      You Asked
Dear Tom,
I have a table with all columns as datatype as Numeric
eg:
Table Name :SAMPLE
col1 NUMERIC(4);
col2 NUMERIC(4);
col3 NUMERIC(4); etc
DATA
RECORD NO      COL1 COL2 COL3...
1               4    10    2
2               6     5    4

Required output should be the max value in every required.
eg RECORD NO     max value
     1              10
     2               6
my question is :I can get it by using the greatest built in if the no of colums
is fixed but at any point of time i can add a col to the table at any point of
time i need a query where it can still work inspite of adding a new column

Thanks in advance

K.Raghava Rao

      and we said...
you are looking for a view.

You change the base table -- and at the same time recreate the view so the
applications need not change.

create view my_view as
select record_no, greatest(c1,c2,c3) max_val
 from t;

and just replace that as needed.
Review &
      Followup

      Rating:
      5
  April 14, 2003
Reviewer:
      A reader

      Rating: 5
Query maximum-1
      Value  April 15, 2003
Reviewer:  Marc
       from Switzerland
Hi Tom

How can I query the "second max" value from a table ? My table has values like
1,3,5,7,9. I want see 7, because 7 is the highest value lower than max. Thanks
for your answer.

Kind Regards
Marc

          Followup:

I'll make the simplified assumption the column in question is unique.

ops$tkyte@ORA920> select * from t;

         X
----------
         1
         3
         5
         7
         9

ops$tkyte@ORA920>
ops$tkyte@ORA920> select x
  2    from ( select x, rownum r
  3             from ( select x from t order by x desc )
  4                    where rownum <= 2 )
  5   where r = 2;

         X
----------
         7

ops$tkyte@ORA920>
ops$tkyte@ORA920> select x
  2    from ( select x, row_number() over ( order by x desc ) r
  3             from t
  4             )
  5   where r = 2
  6  /

         X
----------
         7

ops$tkyte@ORA920>
ops$tkyte@ORA920> select *
  2    from t
  3   where (select count(*)
  4            from t t2
  5                   where t2.x > t.x ) = 1
  6  /

         X
----------
         7

ops$tkyte@ORA920>
ops$tkyte@ORA920> select max(x)
  2    from t
  3   where x < (select max(x) from t)
  4  /

    MAX(X)
----------
         7

are a couple of ways...

      Rating:
      5
  April 15, 2003
Reviewer:
      A reader

      Rating: 4
Good
      answer  April 15, 2003
Reviewer:  Marc
       from Switzerland
Hi Tom

Cool note, but my case is a little bit more complicate. I have a table with 25
columns which I have to select the second max value for each column, group by
business day (day is a date).

My table has values like:

c1 c2 c3 c...
1  8  3
2  6  9
3  4  6
4  2  0

The result should be

c1 c2 c3 c...
3  6  6

So if I use your approach, I get huge queries with 25 subqueries. Thanks in
advance for your feedback.

Kind Regards
Marc

          Followup:

ask a strange question, get a strange answer -- yes, you'll have 25 subqueries,
you are asking 25 separate questions -- each column would be "sorted" separately
-- 25 questions.

Views are really good for hiding this.

      Rating: 5
Second Max for
      each column and each date  April 15, 2003

Reviewer:  A reader
SQL> create table t1 (c1 number, c2 number, c3 number, d date);

Table created.

SQL> insert into t1 values (1, 8, 3,trunc(sysdate));

1 row created.

SQL> insert into t1 values (2, 6, 9,trunc(sysdate));

1 row created.

SQL> insert into t1 values (3, 4, 6,trunc(sysdate));

1 row created.

SQL> insert into t1 values (4, 2, 0,trunc(sysdate));

1 row created.

SQL>
SQL> insert into t1 values (5, 9, 4,trunc(sysdate - 1));

1 row created.

SQL> insert into t1 values (6, 7, 1,trunc(sysdate - 1));

1 row created.

SQL> insert into t1 values (7, 4, 6,trunc(sysdate - 1));

1 row created.

SQL> insert into t1 values (8, 1, 2,trunc(sysdate - 1));

1 row created.

SQL>
SQL> insert into t1 values (9, 15, 6,trunc(sysdate - 2));

1 row created.

SQL> insert into t1 values (10, 9, 9,trunc(sysdate - 2));

1 row created.

SQL> insert into t1 values (11, 12, 13,trunc(sysdate - 2));

1 row created.

SQL> insert into t1 values (12, 18, 1,trunc(sysdate - 2));

1 row created.

SQL>
SQL> insert into t1 values (13, 20, 3,trunc(sysdate - 3));

1 row created.

SQL> insert into t1 values (14, 15, 9,trunc(sysdate - 3));

1 row created.

SQL>
SQL> insert into t1 values (15, 8, 1,trunc(sysdate - 4));

1 row created.

SQL>
SQL> select x.d, x.c1, y.c2, z.c3 from
  2  (select c1, d from (select c1, d, row_number() over (partition by d order
by c1 desc) r from t1) where r = 2) x,
  3  (select c2, d from (select c2, d, row_number() over (partition by d order
by c2 desc) r from t1) where r = 2) y,
  4  (select c3, d from (select c3, d, row_number() over (partition by d order
by c3 desc) r from t1) where r = 2) z
  5  where x.d = y.d
  6  and x.d = z.d;

D                 C1         C2         C3
--------- ---------- ---------- ----------
12-APR-03         13         15          3
13-APR-03         11         15          9
14-APR-03          7          7          4
15-APR-03          3          6          6

      Rating: 4
Displaying the
      maximum value for every record  April 16, 2003

Reviewer:  K.Raghava Rao  from N.Delhi,India

Hai tom,
Thank u tom for the answer but i have a small doubt if i add some more columns
to my table eg col4,col5,col6 as per my understanding then i have to drop the
view and create the view with the query so that my application will not get
effect but is there any way of making the query more dynamic so that it avoids
me to create the view again and again when ever i add a column to my database.

          Followup:

look -- if you are going through the pain of adding columns to a table ( a
major, huge undertaking ) recreating the corresponding view is "small time".

Use the view, everything else is dynamic sql.  When dynamically discover what
you need to do every time you run a query - every day  -- over and over and over
-- when it is trivial to make it static.

funny that you are happy to add columns (big time, huge, MASSIVE change) but
cringe at the view...

don't drop the view CREATE OR REPLACE it.

      Rating: 3
Using dynamic
      SQL  April 16, 2003
Reviewer:  Jianhui
       from DC
Displaying the maximum value for every record  April 16, 2003
Reviewer:  K.Raghava Rao  from N.Delhi,India

Hai tom,
Thank u tom for the answer but i have a small doubt if i add some more columns
to my table eg col4,col5,col6 as per my understanding then i have to drop the
view and create the view with the query so that my application will not get
effect but is there any way of making the query more dynamic so that it avoids
me to create the view again and again when ever i add a column to my database

A quick method :

  FOR rec IN (SELECT * FROM user_tab_columns
              WHERE table_name='yourDynamicTable') LOOP
    -- construct your query string here using the
    -- columns in the data dictionary
    EXECUTE IMMIDIATE yourQueryString
  END LOOP;

Personally, i think it's a nasty design if you keep changing the defination of
the table quite often. You should review the design instead of spending time
coding to meet that not so good design. Anyway, if it happens occationally,
Tom's method is pretty neat!

      Rating: 5
Many thanks to
      all writers  April 16, 2003
Reviewer:  Marc
       from Switzerland

      Rating: 5
group
      by  August 21, 2003
Reviewer:  arjun
       from CA.USA
hi: tom

I have my queries like :

select count(*), id from t1 group by id;
select count(*), id from t2 group by id;
select count(*), id from t3 group by id;
...
select count(*), id from tn group by id;

each query returns fixed ids (1,2,3,4,5) with varying counts :
count(*), id
10      , 1
20      , 2
30      , 3
40      , 4
50      , 5

i have to insert the results in a table mytable as

table name  id1 id2  id3  id4  id5
t1         |10 | 20 | 30 | 40 | 50

how to achive this.

regards

arjun

          Followup:

select 't1',
       count( decode(id,1,1) ) id1,
       count( decode(id,2,2) ) id2,
       count( decode(id,3,3) ) id3,
       count( decode(id,4,4) ) id4,
       count( decode(id,5,5) ) id5
  from t1
 UNION ALL
...
select 'tn',
       count( decode(id,1,1) ) id1,
       count( decode(id,2,2) ) id2,
       count( decode(id,3,3) ) id3,
       count( decode(id,4,4) ) id4,
       count( decode(id,5,5) ) id5
  from tn

      Rating: 5
Awesome
      !!!  August 22, 2003
Reviewer:
      A reader
hi: Tom,

Thanks for the quick response. It was simplest and the best answer.I was working
on getting the solution through dynamic sql, just didn't considered the option
of decode.

thanks a zillion.

regards

      Rating: 5
Awesome
      !!!  August 22, 2003
Reviewer:  arjun

hi: Tom,

Thanks for the quick response. It was simplest and the best answer.I was working
on getting the solution through dynamic sql, just didn't considered the option
of decode.

thanks a zillion.

regards

      Rating: 5
Different
      situation  September 16, 2003
Reviewer:
      A reader  from California
hello tom,

thanks always.
please help me on this :

my query returns values like

id   name age
100  AA   20
100  BB   30
100  CC   40

....

I would like to dispaly as

id  name1  age1 name2 age2 name3 age3
100 AA     20   BB    30   CC    40

Please help.

regards

          Followup:

only can do that IF there is a maximum number of rows/id

select id,
       max( decode( rn, 1, name ) ) name1,
       max( decode( rn, 1, age  ) ) age1,
...
       max( decode( rn, N, name ) ) namen,
       max( decode( rn, N, age  ) ) agen
  from ( select id, name, age,
                row_number() over (partition by id order by name ) rn
           from t )
/


Subject | Article | Last Updated (East coast time)
Optimizer | article only | Sep 17, 2003 06:28 am
What to do in case Performance of a production database goes slow | article only | Sep 17, 2003 06:34 am
Why is cost based optimizer behaving poorly? | article only | Sep 17, 2003 06:57 am
sqlldr and loading a 4 byte date. | article only | Sep 17, 2003 07:05 am
how to traverse the tree in reverse direction? | article only | Sep 17, 2003 07:10 am
export/import from dictionary to local managed database | article only | Sep 17, 2003 11:51 am
Problem while executing a procedure | article only | Sep 17, 2003 04:32 pm
Tablespace Problem | article only | Sep 17, 2003 05:19 pm
Open cursors exceeded | article only | Sep 17, 2003 05:23 pm
Pl/SQL ETL | article only | Sep 17, 2003 05:32 pm
pinning packages | article only | Sep 17, 2003 05:36 pm
Can I query a LDAP Server using any of the supplied packages? | article only | Sep 17, 2003 05:49 pm
TABLES: Sizing for Locally Managed Tablespaces and Freelists | article only | Sep 17, 2003 05:55 pm
Dictionary manages Vs Locally Managed Tablespaces | article only | Sep 17, 2003 06:22 pm
How to supress Schema Name when using DBMS_METADATA | article only | Sep 18, 2003 08:42 am
Journal tables and DDL | article only | Sep 18, 2003 09:01 am
How to insert XML data which was generated using bind variable in query into table of type sys.xmltype ? | article only | Sep 18, 2003 09:22 am
What If I forget the name of SavePoint? | article only | Sep 18, 2003 10:05 am
How to use SQL*PLUS to generate a variable length file delimited by comma? | article only | Sep 18, 2003 11:07 am
EXECUTE IMMEDIATE - don`t know how many input variables | article only | Sep 18, 2003 11:14 am
Oracale 9i*plus SQL | article only | Sep 18, 2003 02:39 pm
question on dbms_sql | article only | Sep 18, 2003 02:43 pm
Update Table | article only | Sep 18, 2003 03:30 pm
SQL*Loader - How can I put header info on each record | article only | Sep 18, 2003 04:15 pm
WEB PLSQL | article only | Sep 18, 2003 06:03 pm
How Oracle Works | article only | Sep 18, 2003 09:22 pm
9i enhancements | article only | Sep 18, 2003 09:29 pm
how to keep 2 database in synch | article only | Sep 20, 2003 04:55 pm
utl_tcp usage | article only | Sep 20, 2003 04:57 pm
How to calculate memory, occupied by agrerate functions ? (ORA-01467: sort key too long) | article only | Sep 20, 2003 05:04 pm
PL/SQL Table and Sorting | article only | Sep 20, 2003 05:29 pm
How do I use bind variables with ODBC? | article only | Sep 20, 2003 05:31 pm
plsql array process and bulk collect | article only | Sep 21, 2003 09:39 am
Grants to Users | article only | Sep 21, 2003 09:46 am
Change tables' tablespace | article only | Sep 21, 2003 10:14 am
Application contexts | article only | Sep 21, 2003 10:38 am
SQL query | article only | Sep 22, 2003 07:40 am
reducing potential data loss in DR (for standby database) | article only | Sep 22, 2003 07:59 am
Copying Data from 1 table to Another | article only | Sep 22, 2003 08:08 am
Index size increased after IMPORT | article only | Sep 22, 2003 08:13 am
Materialized Views/snapshots -- getting them to refresh. | article only | Sep 22, 2003 08:23 am
Subtype Table Design | article only | Sep 22, 2003 09:21 am
can I rebuild an index online in oracle 8i | article only | Sep 22, 2003 09:24 am
Storing Reports in the database | article only | Sep 22, 2003 11:00 am
Hierarchical Tree | article only | Sep 22, 2003 11:01 am
Dictionary views - data on column constraints | article only | Sep 22, 2003 11:03 am
Comparing 9iAS/mod_plsql to other App Servers/web tools | article only | Sep 22, 2003 01:48 pm
Header space allocation for LMT tablespacesssue | article only | Sep 22, 2003 01:51 pm
Collection Variable Types | article only | Sep 22, 2003 01:53 pm
Checkpoint | article only | Sep 23, 2003 06:36 am
temporary tables | article only | Sep 23, 2003 06:39 am
oracle utl_smtp issue | article only | Sep 23, 2003 07:29 am
reading a delimited file into sqlldr | article only | Sep 23, 2003 07:54 am
Alpha and Numeric Sequence values | article only | Sep 23, 2003 11:08 am
Job Scheduling (Dbms_job vs Cron) | article only | Sep 23, 2003 11:17 am
text of my sql's since the start of my session | article only | Sep 23, 2003 11:40 am
JDBC variable binding | article only | Sep 23, 2003 03:06 pm
Can these queries be combined | article only | Sep 23, 2003 03:23 pm
Hiding the password | article only | Sep 23, 2003 05:38 pm
Pre-testing Integrity Constraints | article only | Sep 23, 2003 06:08 pm
Edit Stored Procedure | article only | Sep 23, 2003 06:23 pm
row(s) 1 - 61 of 61
September 17, 2003 -- Thanks for the question regarding "Optimizer", version 8.1.7
You Asked
This might border on Oracle's trade secret, but how exactly does the CBO
function? It actually determines and costs hundreds of plans and chooses the one
with the least cost all in a average of 300 milliseconds or so? How is this
possible? It has to look up the objects stats in the dictionary, perform
computations, etc. The recursive SQL alone would take more than a second to
perform!

How does this work? Thanks
and we said...
shared sql, it is all about shared sql.

look at the difference in parse times between a hard and a soft parse -- see the
difference between a query against a table that already has its dictionary
information cached vs not cached.

This is why bind variables are very important in a transactional system -- you
NEED to use the shared pool efficiently.  It is the only way to scale.
Review & Followup
Rating: 3
Count(Indexed Column) Vs Count(*) May 19, 2003
Reviewer: Vikas from INDIA
Hi Tom,

I was analysing the Count(*) Vs Count(Indexed Column) from a particulat table.
The results were like this :

Case - I : t11 is a table on which pk_t11 is a primary key based on object_id
column

Select /*+Index(t11 pk_t11) */ count(object_id) from  t11

call count  cpu  elapsed  disk query  current  rows
---- -----  ---- -------  ---- -----  -------  ----
Parse    1  0.00    0.00     0     0        0     0
Execute  1  0.00    0.00     0     0        0     0
Fetch    2  0.01    0.01     0   332        0     1
------- --  ----    ---- ----- -----  -------  ----
total    4  0.01    0.01     0   332        0     1

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=332 Card=1 Bytes=345280)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FULL SCAN) OF 'PK_T11' (UNIQUE) (Cost=332 Card=26560
Bytes=345280)

************************************************************

Case - II : t11 is a table

Select Count(object_id) from  t11

call count  cpu  elapsed  disk query  current  rows
---- -----  ---- -------  ---- -----  -------  ----
Parse    1  0.00    0.00     0     0        0     0
Execute  1  0.00    0.00     0     0        0     0
Fetch    2  0.01    0.40   650  1203        4     1
------- --  ----    ---- ----- -----  -------  ----
total    4  0.01    0.40   650  1203        4     1

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=183 Card=1 Bytes=345280)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'T11' (Cost=183 Card=26560 Bytes=345280)

I know Count(*) has to give the results based from the table Full Table Scan
because the Optimizer must not scan the index because it does not store NULLS &
thus can give wrong results. But since there is a Primary Key on t11 which
ensures that the Column Object_id can not contains NULLS why the Optimizer is
not choosing the access path as Index FULL Scan,instead of this it is choosing
the Full Table Scan.

The statistics have been computed for both tables and indexes before the queries
got executed.

Num of Rows in the table are : 26560 and the blocks : 1203
Num of Leaf blocks : 330

Since the no of blocks to scan are less for index it should have scanned Index
in order to give us the results as the overhead to scan the blocks are minimum
in case of an index.

The difference between the tkprof timings is the no. of LIO's in the case of
FULL TABLE SCAN (1203) with PIO's (650), where as in the case of Index Scan we
have only 332 LIO's.

Why in the IInd case, the CPU time differs with the Wall time (0.01 & 0.40) as
expectedly there should not be any wait events as this scenario was tested on a
database with only one session.

Please reply to it as it is quite amazing to know?

Followup:
You will really have to supply a FULL TEST CASE with all supporting and relevant
information.

you talk of count(*) but there are none in your example.

t11 seems to change between runs here -- first you say "t11 is a table with a
primary key", then you say "t11 is a table" -- well, does that second "t11" have
an index?  if so, I cannot see it using the plan you have there.

I cannot reproduce your findings at all.  I always provide end to end test cases
-- I can only ask that everyone else does as well.  Versions are in my
sqlprompts -- I can only ask that everyone supplies those as well.

As for the difference in CPU vs ELAPSED, look at the disk reads column -- should
be obvious after that.  You did physical IO, physical IO takes a while, you
waited for IO.  Wait events happen in single user systems, just as much as
multi-user systems.  Nothing special about a single user.

Here is my test case -- I would not expect it to use a INDEX SCAN at all --
rather an INDEX FAST FULL SCAN would be used.

ops$tkyte@ORA920> create table t as select * from all_objects;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table t add constraint t_pk primary key(object_id);

Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table t compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace on
ops$tkyte@ORA920> select count(object_id) from t;

COUNT(OBJECT_ID)
----------------
           30026

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=8 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FAST FULL SCAN) OF 'T_PK' (UNIQUE) (Cost=8 Card=30026)

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

ops$tkyte@ORA920> set autotrace off
ops$tkyte@ORA920>

also, I tried this in 920, 817, 815 -- the results were the same.

I had to go all of the way back to version 8.0 to get results like yours, so --
maybe you are using some really old software?  Anything could be happening with
that, lots of "smarts" added every year to the CBO.

Rating: 5
My Guess... May 19, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
I think the question should have been:
"Why do I need to give the INDEX hint to force the optimiser to use the INDEX
scan, given that the table has been analyzed?"

I think this might have to do with the way the table was analyzed. Like you
pointed out already, Since we do not see that ANALYZE command (or the DBMS_STATS
call), we can only GUESS...
Thanks,

Followup:
I'll guess they are on 8.0 or before myself ;)

Rating: 3
Is optimizer different for bind variable -- 9.2.0.3 May 19, 2003
Reviewer: Baqir Hussain from Emeryville, CA USA
Tom,
I am using OEM SQL Analyze to get the explain plan.  I am having difficulty in
understanding the following:

1.  Web based application is written in Java and one of the sql queries using
bind variable gives full table scan

SELECT c.*
    FROM client c
    WHERE c.client_id IN (SELECT ea.client_id
                              FROM email_address ea
                              WHERE LOWER(ea.email_address) LIKE LOWER(:1)
                                AND ea.is_active = 1)
    ORDER BY LOWER(c.fullname)

execution step               est cost  est row count
select statement                7756       13622
sort order by                   7756       13622
Hash join (semi)                7007       13622
table access (full) of client   3478       520500
(index row id) email_address    729        13623
(range scan) eamil_adrresss_I   24          1

2. On the other hand when I plug-in the value of bind variable as

SELECT c.*
    FROM client c
    WHERE c.client_id IN (SELECT ea.client_id
                              FROM email_address ea
                              WHERE LOWER(ea.email_address) LIKE LOWER('jeff%')
                                AND ea.is_active = 1)
    ORDER BY LOWER(c.fullname)

I get the results as follows:

select statement                       32    1
sort (order by)                        32    1
nested loops                           32    1
sort unique                            32    1
(index rowid) of email_address         5     1
(range scan)  of email_address_I_lower 3     1
(index rowid) of client                2     1
(unique scan) of client_I_unique       1     1

Questions:

1.  Sql query with bind variable gives us the different explain plan
than the actual one. I don't which one is true? We are not able to get the
results back by using this query through application.  Log file shows that bind
variable is replaced by the "jeff".  Is it some kind of JDBC or optimizer
issues?

2. Or you want me run some trace to get the real issues?

thanks

Rating: 4
shared sql May 19, 2003
Reviewer: A reader
ok so i guess the usual queries that CBO executes to get dict stats
(all_tab_columns, all_ind_columns, etc) are all shared using bind vars.

i can test this out but if this is true, the very first explain plan that is
done after the instance is started should do a hard parse of the dict queries
and should take a little longer than subsequent explain plans, right?

also, every time a explain plan  (or any query really) is done, the executions
count for the 'select .. from user_' in v$sql should go  up by one, right?

thanks

Followup:
not the queries -- the RESULTS of those queries are all shared, the dictionary
cache in the shared pool.

yes, the first queries out of the gate after a restart take long.

no, that query most likely WON'T be executed every time -- the results of that
query will be in the dictionary cache.

Rating: 3
Needs more explanation regarding Count(id) May 20, 2003
Reviewer: Vikas from INDIA
We have working on Version Oracle 8.0.5 and in one of the schema's a table
MAM_ASSETS exists where ID is the Primary Key.

SQL> Desc mam_assets;
 Name                            Null?    Type
 ------------------------------- -------- ----
 ID                              NOT NULL NUMBER(10)
 ACL_ID                          NOT NULL NUMBER(10)
 FIRST_VERSION_CREATED_BY                 NUMBER(10)
 ASSET_GROUP_XID                 NOT NULL NUMBER(10)
 NEW_VERSION_ASSET_ID                     NUMBER(10)
 CONTAINED_IN_COUNT              NOT NULL NUMBER(6)
 CONTAINER_OF_COUNT              NOT NULL NUMBER(6)
 RELATED_WITH_COUNT              NOT NULL NUMBER(6)
 ARCHIVE_STATUS                  NOT NULL NUMBER(1)
 IS_CURRENT_VERSION              NOT NULL NUMBER(1)
 HAS_HISTORY                     NOT NULL NUMBER(1)
 FAMILY_ID                       NOT NULL NUMBER(10)
 STORAGE_CHANGE_REQUEST          NOT NULL NUMBER(1)
 CHECKED_OUT_BY                           NUMBER(10)
 NAME                                     VARCHAR2(255)
 LC_NAME                                  VARCHAR2(255)
 CHECKOUT_LOCATION                        VARCHAR2(255)
 LC_CHECKOUT_LOCATION                     VARCHAR2(255)
 ASSET_SIZE                               NUMBER
 REVISION                                 NUMBER
 FILE_DATE_LAST_MODIFIED                  DATE
 FILE_EXTENSION                           VARCHAR2(255)
 FILE_DATE_CREATED                        DATE
 LC_FILE_EXTENSION                        VARCHAR2(255)
 DATE_CREATED                             DATE
 FILE_TYPE                                VARCHAR2(255)
 DATE_LAST_MODIFIED                       DATE
 LC_FILE_TYPE                             VARCHAR2(255)
 FILE_CREATOR_APPLICATION                 VARCHAR2(255)
 DATE_CHECKED_OUT                         DATE
 LC_FILE_CREATOR_APPLICATION              VARCHAR2(255)
 OWNER                                    NUMBER(10)
 CREATED_BY                               NUMBER(10)
 LAST_MODIFIED_BY                         NUMBER(10)
 DESTINATION_ID                           NUMBER(10)
 INDEX_SOURCE                             BLOB
 LAST_ACTIVITY_ID                         NUMBER(10)
 DATE_LAST_ACCESSED                       DATE
 PREVIOUS_REVISION                        NUMBER
 LAST_ACCESSED_BY                         NUMBER(10)
 FIRST_VERSION_DATE_CREATED               DATE

I will present two cases for the optimizer to work with.

RBO :-
In this case neither the table nor its indexes are analysed for statistics and
the optimizer_goal is set to CHOOSE.

SQL> Alter session set optimizer_goal = CHOOSE;
Session altered.

SQL> Select Count(*) from mam_assets;

 COUNT(*)
---------
   206916

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'MAM_ASSETS'

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

SQL> Select Count(Id) from mam_assets;

COUNT(ID)
---------
   206916

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'MAM_ASSETS'

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

In both the cases, the FULL TABLE SCAN is choosen, even if the Index does exist
on ID as the Primary Key.

CBO :-

Analysed the table and their indexes and populated the histograms also for the
indexes.

SQL> Analyze table mam_assets compute statistics for table for all indexes for
all indexed columns;

Table analyzed.

SQL> Select Count(*) from mam_assets;

 COUNT(*)
---------
   206916
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=16 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FULL SCAN) OF 'AST_ARCHIVE_STATUS_I' (NON-UNIQUE)
           (Cost=763 Card=206916)

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

SQL> Select Count(ID) from mam_assets;

COUNT(ID)
---------
   206916

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=16 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FULL SCAN) OF 'AST_ARCHIVE_STATUS_I' (NON-UNIQUE)
           (Cost=763 Card=206916)
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        763  consistent gets
          0  physical reads
          0  redo size
        578  bytes sent via SQL*Net to client
        666  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL>

Questions :

1. Since it is always felt that RBO optimisation is Index hungry why in the
above mentioned cases the Index was not used when it existed.

2.Can Count(*) of that table use any Index Scan OR it is just Count(ID) where ID
is the Indexed as is NOT NULL which could use the Index Full Scan in its plan as
the Statement internally is transformed as

Select Count(Id) from mam_assets ----> Select Count(*) from (Select Id from
mam_assets);

3.Even the Cost Optimizer is choosing another index to quantify the Count of the
table. Instead of Using the AST_PK index which is based on ID column, it is
using AST_ARCHIVE_STATUS_I Index which is another one on this table based on
Archive_status.

4.Count(*) on this table is also using the same index as it was used in earlier
case of Count(ID) but sometimes the Count(*) operation just does the FULL TABLE
SCAN.

5.Would be good to understand how Optimiser works in these two cases.

Looking for the answers, as they are making me quite confused.

Thanks in anticipation.

Followup:
1) because the RBO doesn't understand that an index on a NOT NULL COLUMN can be
used for counting.  The RBO assumes an index may not point to every value in the
table.  The RBO is not very smart. it has about 13 rules that dictate what it
does.

2) count(*) can only use an index on a column that is not nullable.

3) so?  ast_archive_status must be not null.  and being a number(1) (smaller
then number(10) data would probably be) it is probably smaller.  it did "good"

4) count(*) will not "sometimes" do one thing and then another.  not following
you here.

5) read the performance and tuning guide, it goes through the differences.

Rating: 4
optimizer with bind variable May 20, 2003
Reviewer: Baqir Hussain from Emeryville, CA USA
Tom,
I am using OEM SQL Analyze to get the explain plan.

Bind variable gives full table scan because CBO attemps to use column value
information to determine the optimal access path for the query.  Documents says
selectivity for bind variables with 'like' predicates defaults to 25%.

SELECT c.*
    FROM client c
    WHERE c.client_id IN (SELECT ea.client_id
                              FROM email_address ea
                              WHERE LOWER(ea.email_address) LIKE LOWER(:1)
                                AND ea.is_active = 1)
    ORDER BY LOWER(c.fullname)

execution step               est cost  est row count
select statement                7756       13622
sort order by                   7756       13622
Hash join (semi)                7007       13622
table access (full) of client   3478       520500
(index row id) email_address    729        13623
(range scan) eamil_adrresss_I   24          1

2. On the other hand CBO can determine the selectivity for the where clause
"LOWER(ea.email_address) LIKE LOWER('jeff%')" -- it uses the assumption that
values between the supplied value and the high value.

SELECT c.*
    FROM client c
    WHERE c.client_id IN (SELECT ea.client_id
                              FROM email_address ea
                              WHERE LOWER(ea.email_address) LIKE LOWER('jeff%')
                                AND ea.is_active = 1)
    ORDER BY LOWER(c.fullname)

I get the results as follows:

select statement                       32    1
sort (order by)                        32    1
nested loops                           32    1
sort unique                            32    1
(index rowid) of email_address         5     1
(range scan)  of email_address_I_lower 3     1
(index rowid) of client                2     1
(unique scan) of client_I_unique       1     1

Questions:

1. The bind variable will be replaced with different value on every execution
for this query.  Is there any way to get best execution plan with the said
situation?
Most of the time we do not get the results back due to full table scan.
Please advise. Thanks

Followup:
how big is this table?  I can scan millions of records on my laptop in seconds
-- what do you mean "we do not get the results back"??

but anyway, you can always /*+ FIRST ROWS */ hint it, that'll make it much more
"index happy".

Rating: 4
bind variable and optimizer -- 9.2.0.3 May 21, 2003
Reviewer: Baqir Hussain from Emeryville, CA USA
Thanks for the prompt reply.
We are experiencing this problem ONLY for this search.

client table has 527127 row
email_address has 548818

db_file_multiblock_read_count=8

Web based search never get the results back.  We definit missing some parameters
that lead to this situation.

Here is init file:

###########################################
# Cache and I/O
###########################################
db_block_size=8192
db_cache_size=1850000000
db_file_multiblock_read_count=8
###########################################
# Cursors and Library Cache
###########################################
open_cursors=900

###########################################
# Database Identification
###########################################
db_domain=""
db_name=bwalk01
service_names=bwalk01
###########################################
# Diagnostics and Statistics
###########################################
background_dump_dest=/u01/oracle9i/admin/bwalk01/bdump
core_dump_dest=/u01/oracle9i/admin/bwalk01/cdump
timed_statistics=TRUE
user_dump_dest=/u01/oracle9i/admin/bwalk01/udump

###########################################
# File Configuration
###########################################
control_files=("/u02/bwalk01/control01.ctl", "/u04/bwalk01/control02.ctl",
"/mp10/bwalk01/control03.ctl
")

###########################################
# Instance Identification
###########################################
instance_name=bwalk01

###########################################
# Job Queues
###########################################
##job_queue_processes=10

###########################################
# MTS
###########################################
dispatchers="(PROTOCOL=TCP) (SERVICE=bwalk01XDB)"

###########################################
# Miscellaneous
###########################################
aq_tm_processes=1
compatible=9.2.0.0.0

###########################################
# Optimizer
###########################################
hash_join_enabled=TRUE

###########################################
# Pools
###########################################
java_pool_size=15M
large_pool_size=116777216
shared_pool_size=400M
###########################################
# Processes and Sessions
###########################################
processes=950

###########################################
# Redo Log and Recovery
###########################################
fast_start_mttr_target=300

###########################################
# Security and Auditing
###########################################
remote_login_passwordfile=EXCLUSIVE

###########################################
# Sort, Hash Joins, Bitmap Indexes
###########################################
pga_aggregate_target=524288000
sort_area_size=0

###########################################
# System Managed Undo and Rollback Segments
###########################################
undo_management=AUTO
undo_retention=400
undo_tablespace=UNDOTBS1
#########################################
# index caching parameters
########################################

log_buffer=1024000
pre_page_sga=true
query_rewrite_integrity=trusted
query_rewrite_enabled=true
session_cached_cursors=400
log_archive_start = true
log_archive_dest_1 = "location=/mp9/bwalk01"
log_archive_dest_2 = "location=/u03/oracle92/archlog"
log_archive_format = "LOG%s_%t.ARC"

Would apprciate you help in advance.

Followup:
if you truly have a query that goes into lah-lah land and never returns, it
would be a sure sign of "it is time to log a tar with support"

Rating: 4
bind variable and optimizer -- 9.2.0.3 May 21, 2003
Reviewer: Baqir Hussain from Emeryville, CA USA
Tom,
Developers do not want to use hints into the Java application.  They want
database to handle this kind of issues.
I would like to get an environment where scan over millions of row is done in
seceonds.
Web based search query for email addresses run for 3 to 6 minutes and then times
out (what I mean is the results are not coming back ).
Please advise how to tkae of this issue other than hint.
Thanks a lot

Followup:
use first_rows -- that gives the database "information", something useful,
something it can use to optimize the query with.

Rating: 5
Like to know... May 22, 2003
Reviewer: Kamal Kishore from New Jersey, USA
I would like to know if the previous poster tried to run the problem query at
SQL*Plus prompt and see the behaviour?

Rating: 4
usage of optimizer related parameters September 16, 2003
Reviewer: reader
setting the parameters like OPTIMIZER_INDEX-CACHING and OPTIMIZER_INDEX_COST_ADJ
would influence CBO to use indexes rather than full table scan? Is this correct?
If so, we increase LIO and reading your discussion from another thread in
asktom.com, that may not be preferred as "full table scan is not evil". If this
is the case, why do we have to consider setting these two parameters? Thanks.

Followup:
or vice versa - to use scans instead of indexes.  depends on HOW you set them.

now, i don't think I ever said "using indexes will increase LIO".  I've said
"you know what, indexes are not 'good incarnate' and full scans are not 'pure
evil'".  You have to balance everything.

In an oltp system -- where response time is paramount -- indexes generally
(GENERALLY, not ALWAYS) rule.

this is a complex subject, if interested, i spent some amount on time on these
parameters (and many of the CBO related ones) as well as system statistics in my
latest book "Effective Oracle by Design" -- chapter 6 is all about cbo
parameters.

Rating: 5
Great Site December 29, 2003
Reviewer: Ego Kunna from KSA
Good Luck to Tom. You should be really the Guru of Oracle

September 17, 2003 Om -- Thanks for the question regarding "What to do in case Performance of a production database goes slow", version 8.1.6
You Asked
Hi Tom,

We have 6 production databases in our project.From the last 2 days
users of one of the production databases are complaining about
the response time of one of the database.

I usually carry out few checks as described below whenever I
receive such requests

1) Check the number of session which are Inactive
2) Check the ddcache and lib hit ratio
3) find out the locks status
4) query v$wait_event to see class and wait time

All these factors were fine with the database but still user
complains about the performance.

Could you please suggest what excatly and in what sequence we should
carry out the steps to exactly trace out the performance degradation
in a PRODUCTION DATABASE.

Is there any way to find out is there any big query or transaction
is getting executed ,if yes how to find out the SQL statement and
to what user it belongs ?

Your advise is very precious for me.Thanks in advance.

Best Regards,
Om
and we said...
You should be using statspack on a constant basis.

Every morning, you should take a snapshot, every afternoon another, every
evening, yet another.

Now you have a history.  You can compare a statspack from today (bad
performance) with last weeks at the same time (good performance) and look for
major differences.

Also, people must "quantify" things.  Eg: Screen 1 typically takes less then 1
second, today it is taking 60 seconds.  -- Ah ha, maybe we lost an index on some
of the tables surrounding screen 1, lets look at that.  Are there specific
components "going slow" or is the entire thing going slow.

Statspack will help you identify the top sql, the big wait events, contention
points, bad performance metric (eg: the soft parse ratio is my personal
favorite).

Also, attack this from two points - get the SA's looking at the machine,
network, disks, etc.

As it is now, if you don't have a history of what "good" looks like - it is
REALLY REALLY hard to figure out "badness".    You need to gather more
information, isolate the issue if possible and go from there.
Review & Followup
Rating: 5
How about a rogue query? April 13, 2002
Reviewer: Doug from CT, USA
Tom, even with three statspack snapshots a day, what if someone calls somewhere
inbetween and says "things are slow", and you also know that someone could have
kicked off an ad hoc query that could be slowing things down.  How would you go
about identifying it?

Followup:
I would use showsql.sql to see what sql's been running a long time (last_call_et
in v$session is useful for that)

OEM has a "top" monitor as do most all 3rd party tools like that.

I might even in unix use "top" at the OS level to id a rogue runaway process,
find it's pid and go into the database and see what that process is doing.  (on
NT I guess I would just reboot, thats the way to fix those systems right ;)  --
that was a joke....

Rating: 4
performance issues April 14, 2002
Reviewer: Om from India
Thanks Tom for your quich and very useful response.
The script showsql is really good and I was looking
for a script like this only.

But you have also mentioned to make use of statspack
to further analyse the problem.I ' am sorry as I should
have mentioned this before,my Production database version
is 7.3.3 and I think statspack will not work.

Could you please suggest any other alternative please.

Followup:
utlestat/bstat were the precursor to statspack.  Not nearly as information rich
but a good place to start.

You won't have the very nice ability to store the historical stats in database
tables like statspack (you'll need to keep a report on disk instead).

Yet another great reason to get to a SUPPORTED release of the database!

Rating: 3
PRODUCTION DATABASE April 14, 2002
Reviewer: Dave from US
hello
may I know what is meant by production database.

Followup:
A database running for real, with people using it -- in "production" as opposed
to "in development" or "in test".

the database behind asktom.oracle.com is "in production".

the database i use for examples is "in development/test"

Rating: 3
multiple databases and performance on a machine April 15, 2002
Reviewer: Krish R Ullur from Nashville, TN USA
Hi Tom,

We have about 10 datbases (3 of them really large with large number of users and
heavy weight sqls) on a large SUN E10K (16 cpus, 24GB ram and fast disks). There
are frequent occasions where users complain of slowness and the "top/mpstat"
shows some cpus loaded heavily. I suspect some heavy processes on specific cpu's
(background and/or client connections) could be a factor.
I would like to know if there is an easy way I could determine  which processes
run on which cpu's.
Forgive me my question is aimed at OS rather than Oracle.

Followup:
TOP shows you that on some OS's (hp/ux for sure, not sure about sun) but they'll
move around from cpu to cpu faster then you can keep track of them.

It's not specific CPU's (processes are not bound to a cpu), it's all of them --
seeing which ones on a CPU right now isn't relevant a second from now.

Rating: 5
Similar problem March 17, 2003
Reviewer: Rory Concepcion from Philippines
Hi TOm,

   Hope u dont mind. I have a problem with one of our production databases. I
dont have that extensive knowledge on interpreting the report of statspack. Can
u give me a nice document.

   And another one. Our production database is experiencing some problems where
the users are waiting too long for login. Even I am waiting too long when
logging in via sqlplus for windows or via telnet then sqlplus. It's only fast
when I use svrmgrl. It takes about 2 to 5 minutes for a login. My db is 8.1.7
and my server is IBM AIX. No prob with the server as I used TOPAS. But after one
long waiting for login, my next login would only be for 5 secs. Also thru
sqlplus. I'm going to paste my statspack report during that time. I have a prior
report that's run when there's no prob. I'll paste it if u'd like. SOrry for
this. Hope you have the time.

STATSPACK report for

DB Name        DB Id     Instance    Inst Num  Release     OPS   Host
----------  -----------  ----------  --------  ----------  ----  ----------
POINT         947966058  POINT              1  8.1.7.0.0   NO    CRM_PROD

                                                                Snap Length
Start Id    End Id       Start Time             End Time         (Minutes)
--------  --------  --------------------  --------------------  -----------
      98        99  12-Mar-03 10:51:13    12-Mar-03 11:37:39          46.43

Cache Sizes
~~~~~~~~~~~
           db_block_buffers:      250000
              db_block_size:        4096
                 log_buffer:      524288
           shared_pool_size:   800000000

Load Profile
~~~~~~~~~~~~
                                      Per Second      Per Transaction
                                  ---------------      ---------------
                  Redo size:             2,903.21             2,501.03
              Logical reads:             2,938.21             2,531.18
              Block changes:                15.64                13.47
             Physical reads:               655.77               564.93
            Physical writes:                11.47                 9.88
                 User calls:                74.02                63.77
                     Parses:                12.58                10.83
                Hard parses:                 0.15                 0.13
                      Sorts:                 2.69                 2.31
               Transactions:                 1.16

              Rows per Sort:      104.55
  Pct Blocks changed / Read:        0.53
         Recursive Call Pct:        5.49
 Rollback / transaction Pct:        1.89

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        Buffer Nowait Ratio:       96.13
        Buffer  Hit   Ratio:       77.68
        Library Hit   Ratio:       99.23
        Redo   NoWait Ratio:      100.00
       In-memory Sort Ratio:       99.96
           Soft Parse Ratio:       98.78
            Latch Hit Ratio:       98.65

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
db file sequential read                         1,547,030      167,785   68.61
buffer busy waits                                 316,992       40,103   16.40
latch free                                         17,757       17,784    7.27
db file parallel read                               6,995        8,257    3.38
db file scattered read                              9,969        4,945    2.02
         -------------------------------------------------------------
^LWait Events for DB: POINT  Instance: POINT  Snaps:      98 -     99
->cs - centisecond -  100th of a second
->ms - millisecond - 1000th of a second (unit often used for disk IO timings)

                                                                   Avg
                                                     Total Wait   wait  Waits
Event                               Waits   Timeouts  Time (cs)   (ms)   /txn
---------------------------- ------------ ---------- ----------- ----- ------
db file sequential read         1,547,030          0     167,785     1  478.4
buffer busy waits                 316,992          0      40,103     1   98.0
latch free                         17,757     11,226      17,784    10    5.5
db file parallel read               6,995          0       8,257    12    2.2
db file scattered read              9,969          0       4,945     5    3.1
log file sync                       3,237          0       2,058     6    1.0
log file parallel write             3,253          0         980     3    1.0
db file parallel write              1,097          0         659     6    0.3
PX Deq: Execute Reply                  84          0         387    46    0.0
control file parallel write           904          0         339     4    0.3
enqueue                                25          0         279   112    0.0
process startup                         5          1         221   442    0.0
file open                             162          0         207    13    0.1
PX Deq: Parse Reply                     3          0         142   473    0.0
library cache pin                       3          0         135   450    0.0
PX Deq: Execution Msg                 100          0         129    13    0.0
direct path read                      663          0          48     1    0.2
PX Deq: Signal ACK                      9          6          35    39    0.0
PX Deq: Join ACK                        5          0          22    44    0.0
SQL*Net more data to client         1,859          0          16     0    0.6
refresh controlfile command             5          0           4     8    0.0
control file sequential read           57          0           3     1    0.0
direct path write                       6          0           2     3    0.0
file identify                           2          0           2    10    0.0
SQL*Net break/reset to clien            2          0           1     5    0.0
db file single write                    1          0           0     0    0.0
SQL*Net message from client       205,557          0  14,238,554   693   63.6
virtual circuit status                 90         90     276,477 #####    0.0
PX Idle Wait                          836        830     170,455  2039    0.3
SQL*Net message to client         205,556          0          52     0   63.6
SQL*Net more data from clien          103          0           2     0    0.0
         -------------------------------------------------------------
^LBackground Wait Events for DB: POINT  Instance: POINT  Snaps:      98 -     99

                                                                   Avg
                                                     Total Wait   wait  Waits
Event                               Waits   Timeouts  Time (cs)   (ms)   /txn
---------------------------- ------------ ---------- ----------- ----- ------
log file parallel write             3,251          0         980     3    1.0
db file parallel write              1,097          0         659     6    0.3
control file parallel write           903          0         339     4    0.3
db file scattered read                 74          0          59     8    0.0
latch free                             14         11          19    14    0.0
db file sequential read                18          0          16     9    0.0
control file sequential read           19          0           2     1    0.0
rdbms ipc message                  25,995      5,382   2,126,644   818    8.0
pmon timer                          1,003        868     277,734  2769    0.3
smon timer                              9          9     276,458 #####    0.0
         -------------------------------------------------------------
^LSQL ordered by Gets for DB: POINT  Instance: POINT  Snaps:      98 -     99

                                Gets       % of
   Buffer Gets     Executes   per Exec    Total   Hash Value
-------------- ------------ ------------ ------ ------------
SQL statement
------------------------------------------------------------------------------
     2,529,602            4    632,400.5   30.9   1132031452
SELECT /*+ FIRST_ROWS */ Whendate__,PRIMARYKEY,Status,Priority,ForeignKey,When

     1,264,899            1  1,264,899.0   15.5   2069064965
SELECT /*+ FIRST_ROWS */ Whendate__,PRIMARYKEY,Status,Priority,ForeignKey,Type

       673,170            5    134,634.0    8.2   2251869451
SELECT /*+ FIRST_ROWS */ LINENUMBER,ACCTNUMBER,CUSTNUMBER,SERVCODE,ACTIVEDATE,

       578,968            8     72,371.0    7.1   2488240846
SELECT /*+ FIRST_ROWS */ CUS.RECORDTYPE,CUS.CUSTNUMBER,CUS.NAME,CUS.TYPE,CUS.C

       217,301          134      1,621.6    2.7    333582979
SELECT /*+ FIRST_ROWS */ Whendate__,PRIMARYKEY,Status,Priority,Type,Why,Whenda

       194,240        2,013         96.5    2.4   1371574077
SELECT /*+ FIRST_ROWS */ * FROM PCR_SYSDDB SDB WHERE FETCHID=(SELECT /*+ FIRST

       129,058            1    129,058.0    1.6   3507863592
SELECT /*+ FIRST_ROWS */ ACCTNUMBER,CUSTNUMBER,LINENUMBER,SERVCODE,ACTIVEDATE,

       123,911           27      4,589.3    1.5   2344561510
SELECT /*+ FIRST_ROWS */ CUS.RECORDTYPE,CUS.CUSTNUMBER,CUS.NAME,CUS.TYPE,CUS.C

       107,550            2     53,775.0    1.3    303765711
SELECT /*+ FIRST_ROWS */ Whendate__,PRIMARYKEY,Status,Priority,Type,Why,Whenda

        71,892            1     71,892.0     .9   1541492399
SELECT /*+ FIRST_ROWS */ * FROM TP_CUSTOMER CUS WHERE (UPPER(LASTNAME)=:1) AND

        53,794            2     26,897.0     .7   4274864533
SELECT /*+ FIRST_ROWS */ Whendate__,PRIMARYKEY,Status,Priority,Type,Why,Whenda

        53,775            1     53,775.0     .7    463364931
SELECT /*+ FIRST_ROWS */ Whendate__,PRIMARYKEY,Status,Priority,Type,Why,Whenda

Followup:
this is a no brainer.

You have queries doing hundreds of thousands, if not millions of buffer gets
(LIOs) per execution.

Unless you are a data warehouse -- you have some EXCESSIVELY low hanging fruit.

Find and fix those queries.

Rating: 5
statspack report March 17, 2003
Reviewer: Rory Concepcion from Philippines
Hi TOm,

     Sorry for this, but I dont understand what you meant. Did you mean that the
buffer gets of the queries in my database are "BAD". Are they that high? If i do
get a low cost from them using explain plan, would that be enough? Thanks for
your valuable time. And if you can direct me to a nice doc on statspack and
buffer gets. did u discuss them in your book? thanks.

Followup:
ignore the cost -- reduce the LIO's.

You do not need a book on statspack -- it is all about getting the queries to do
as little work as possible, it is all about reducing LIO's to their bare
minimums.

Unless you are a big old data warehouse, your queries are so far from where they
need to be.

Rating: 5
consistent gets or buffer gets etc. March 20, 2003
Reviewer: Rory Concepcion from Philippines
Hi Tom,

    If I'd like to see the plan of a query without really executing it, all I
need is to "explain plan" it. Is there a tool I can use to view the statistics
like consistent gets, physical reads, executions,etc, the information we see
when we set autotrace on for statistics without actually executing the sql
statement?

    Thanks.

Followup:
that would be neat -- "without actually running the query -- tell me how many
blocks it'll get from the cache, read from disk, recursive sql calls it'll
make".

If we could do that -- we could probably tell you the answer to the query as
well -- that would be really fast ;)

You have to actually EXECUTE the query to get things like sorts, buffer gets,
physical io's and the like.

Rating: 5
March 21, 2003
Reviewer: Mike from TX
I took a 75 min snap of statspack and found the most expensive
query:
Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
    259,672,818       25,108       10,342.2    55.5   2531437174
SELECT COUNT(*)   FROM MTL_ITEM_REVISIONS_INTERFACE  WHERE ((ORG
ANIZATION_ID = :b1  AND INVENTORY_ITEM_ID = :b2 ) OR (ORGANIZATI
ON_ID = :b1  AND ITEM_NUMBER = :b4 )) AND REVISION = :b5  AND SE
T_PROCESS_ID = :b6

apps@prod1> @plan
 QUERY_PLAN
------------------------------------------------------------
  SORT AGGREGATE
    CONCATENATION
      TABLE ACCESS BY INDEX ROWID MTL_ITEM_REVISIONS_INTERFACE
        INDEX RANGE SCAN MTL_ITEM_REVS_INTERFACE_N4
      TABLE ACCESS BY INDEX ROWID MTL_ITEM_REVISIONS_INTERFACE
        INDEX RANGE SCAN MTL_ITEM_REVS_INTERFACE_N2

6 rows selected.

Do you think the Gets per Exec = 10,342.2?

Thanks

Followup:
that stinks -- I hope the code isn't something silly like:

 select count(*) into l_cnt .....

 if ( l_cnt != 0 ) then....

tell me -- what does the code that invoke this count do with the results??

yes, 10k gets / query for a query that is executed over 5 times a SECOND is
horrible.

Rating: 5
March 21, 2003
Reviewer: Mike from TX
"
tell me -- what does the code that invoke this count do with the results??

yes, 10k gets / query for a query that is executed over 5 times a SECOND is
horrible.
"
It's Oracle APPS 11.5.7 and I am new to the site. Do not know what's code has
the query. I may find that later

Followup:
it looks like a query from PLSQL given the name of the binds -- you might try
looking in ALL_SOURCE for the code

Rating: 5
March 21, 2003
Reviewer: Mike from TX
"
it looks like a query from PLSQL given the name of the binds -- you might try
looking in ALL_SOURCE for the code
"
What's effective method to find the code name, I used

select distinct name from dba_source where text like
'%SELECT COUNT(*) FROM MTL_ITEM_REVISIONS_INTERFACE%'
/
It has not come back yet.

Followup:
well, typically -- select count(*) and from table will be on different lines.

I would be tempted to

select * from dba_source where
UPPER(text) like '% MTL_ITEM_REVISIONS_INTERFACE %'

Rating: 5
April 23, 2003
Reviewer: Rory from Philippines
Hi Tom,

How can I reduce LIO's. I mean, you can give me an example of a query that has
so much LIO then reduce it. Hope you can help me on this. This would help me in
resolving those
queries that could possibly have to much LIO's in our system. Thanks a lot.

Followup:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6749454952894
every query could possible have too many LIO's -- if they are doing more then
they should.

You can use techniques like IOT's, HASH clusters, B*Tree clusters, partitioning,
et al to physically organize the data to be more efficient

You can add an index sometimes.

You can remove an index sometimes.

Adding a column to an existing index can help sometimes.

rewriting the query can help sometimes.

NOT RUNNING THE QUERY AT ALL is the best approach (look for every query that has
"select count(*) from ..." and ask yourself "why, why am I doing that -- do I
really need to" -- almost always the answser is "no"

      Rating:
      5
  April 23, 2003
Reviewer:  Rory
       from Philippines
Hi again Tom,

I forgot too. Is there a way of determining the LIO'S of sql statements that
have been executed in the database. Can v$sqlarea show this (buffer_gets
column). Thanks.

          Followup:

buffer_gets in v$sql

try to avoid v$sqlarea, it is just an aggregation of v$sql and doesn't perform
quite as well.

      Rating: 5
Long login
      times  April 23, 2003
Reviewer:  Chris Major
       from Hungary
To Rory:
If only the first login is slow, then it could be a network problem. We had a
similar problem, where the server was trying to find the hostname via DNS, and
there was none. It takes the server a few minutes to give up, and look at the
hosts file.

Hope it helps.
Chris

      Rating:
      5
  April 24, 2003
Reviewer:  Rory
       from Philippines
To Chris:

Thanks for the advise. The problem is like this. There are times when the users
will complain that they can't login or database has simply hang. Now if that
happened when I am logged in (even as sys in sqlplus or svrmgrl), I would hang
too when I would execute a query. If I'm not logged in, I would not be able to
login and appear to hang. I checked the os for top processes and saw two
belonging to Oracle. I found out that they were p000 processes, meaning they're
for parallel query right? So I thought this might be the problem. Somebody might
be executing queries in parallel. But after 10 mins of hang and everything is
smooth again, I saw that the two oracle processes were still at the top of the
processes as seen in the os and still the same percentage playing from 95-102%.
Were could the problem be? Cant run statspack since I cant log in. Thanks for
anybody's help. Thanks Tom for the answers and they're really a big help.

          Followup:

just had a thought -- are you configured for shared server or dedicated server?

      Rating:
      5
  April 24, 2003
Reviewer:  Rory
       from Philippines
Hi Tom,

   Our database is configured only for dedicated connection. Shared server
connection or MTS is not configured for this specific database. I was also
thinking that there might be LIO's or waits... but why affect log in too? Cant
think of anything yet. My database is 8.1.7 running on IBM AIX. There are abt
200 users logged in with just a create session privilege and all the objects
they need to access have public synonyms. Security is done in the front end.
Still curious why it could affect even svrmgrl. By the way, I can log in via
server manager and the hang will take place when I query. Thanks a lot Tom for
the help.

      Rating:
      5
  April 25, 2003
Reviewer:  Rory
       from Philippines
Hi Tom,

It happened again this afternoon. Seems like the whole system is waiting for
something to finish. Just dont know how I could get that wait after the hang.
But what could that wait be that could cause even a query in server manager to
halt. Is iit possible for everyone to be locked out whenever a query is executed
by a certain user that could perhaps have heavy LIO. Reflecting on the knowledge
I have learned here, whenver a query is executed, it's looked at in the
shared_pool for shared resources then when it's ready, the data is searched
buffer cache using some function. I'm really curious on this one. It happens at
different times. It lasted for 15 minutes.
Here are some of the data I was able to gather after the hang. Dont know if they
are meaningful.

SVRMGR> select * from v$waitstat
     2> order by count asc;
CLASS              COUNT      TIME
------------------ ---------- ----------
sort block                  0          0
save undo block             0          0
save undo header            0          0
free list                   0          0
bitmap block                0          0
extent map                  0          0
system undo block           0          0
system undo header          0          0
bitmap index block          1          0
unused                     10         42
segment header             83        295
undo header               244       1572
undo block               1257        281
data block           54918547    8721332
14 rows selected.

I spooled the content of v$sesstat after the hang but did not paste it here.
Tell me if u need to see it and what particular statistic is need. I spooled by
individual sid. I didnt do any summing. I'm really curious on this one. Thanks a
lot.

P.S.
It's Saturday tomorrow so I may be able to see your response on Monday. But I'll
try to surf tomorrow. The machine after all is a production one. And I need to
solve this quickly.

          Followup:

start taking statspack snapshots -- in 15 or 30 minute intervals.

you can purge out ones that represent "ok gaps".  eg: if you take it at 9am,
9:30, 10am, .... and from 9am-noon all was well but at 12:10 you had a problem
-- you can purge the 9am, 9:30, .... 11am, 11:30am snapshots and just keep
12noon - 12:30.

A statspack report that started shortly before the issue and ended shortly after
would be very telling.

You do seem to have an issue there with waits on data blocks.  It is hard to
make sense of it though since that count is cumulative for the life of the
instance (so we don't know if you had 1000000 waits in 10 minutes or over 100
days).  statspack will normalize that out for us -- so we'll only see waits
during the period of observation -- it would tell us how many/how long the wait
was during that 15/30 minute window.

also consider opening a tar with support on this issue -- they'll collect
important things like your database version -- are you on a supported release
and such.

      Rating:
      5
  April 27, 2003
Reviewer:  Rory
       from Philippines
Hi Tom,

Yes I am in a supported version, 8.1.7. I'll try to get a statspack every 30
mins. That'll be continuous until I'm able to catch the problem. THen I'll post
here again. I'll try to post the statspack report here of when the problem
occured. Million thanks for your help.

          Followup:

817.4 right.

      Rating: 5
number ofblocks
      in cache  April 28, 2003
Reviewer:
      A reader
Hi

quoting one of your answers

"that would be neat -- "without actually running the query -- tell me how many
blocks it'll get from the cache, read from disk, recursive sql calls it'll make"

How do we know how many blocks Oracle will get from cache/buffer and disk???
Consistent gets seems out of question since it´s not the number of blocks.

Cheers

          Followup:

consistent gets IS the number of blocks Oracle will get from the cache.  that is
exactly what consistent gets is.

run the query with autotrace or with sql_trace and you'll see the PIO's as well.

      Rating: 5
but consistent
      gets changes  April 28, 2003
Reviewer:
      A reader
Hi

but everytime I change my array size consistent gets changes, does that mean
Oracle reads different number of blocks.... everytime?

          Followup:

It means Oracle latches (a serial process) more or less blocks.

Say you have 50 rows per block.

You ask for 10 at a time.  You ask for the entire table.

We'll have to get block 1 out of the buffer cache 5 times in order to satisfy
your read requests.  We'll do 5 logical IO's on block 1 in order to retrieve all
50 rows from it for you.

Now, you ask for 100 at a time.  You ask for the entire table.

We latch block 1 and get 50 rows, we latch block 2 and get 50 rows -- give you
back 100 rows.

We'll have latched block 1 once, reducing the logical IO's by a factor of 5.

      Rating: 4
just last
      question  April 28, 2003
Reviewer:
      A reader
Hi

I have a table with around 1 million rows, I ran your show_space proc and had
this result

exec show_space('CLDOM_DOMICILIOS', 'TGCGCT10')

Free Blocks.............................
Total Blocks............................18688
Total Bytes.............................153092096
Total MBytes............................146
Unused Blocks...........................256
Unused Bytes............................2097152
Last Used Ext FileId....................7
Last Used Ext BlockId...................41096
Last Used Block.........................768

I have 16MB DB Cache, I tried first ararysize of 15 then 200

tgcgct10@LNX920>select * from CLDOM_DOMICILIOS where CLENT_COD_CLIENTE = 2796;

100304 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=276 Bytes=28152)

   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'CLDOM_DOMICILIOS' (Cost=2 Card=276
Bytes=28152)

   2    1     INDEX (RANGE SCAN) OF 'CLDOM_DOMICILIOS_IDX1' (NON-UNIQUE) (Cost=1
Card=276)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      15701  consistent gets
       2501  physical reads
          0  redo size
   12747329  bytes sent via SQL*Net to client
      74045  bytes received via SQL*Net from client
       6688  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     100304  rows processed

tgcgct10@LNX920>set arraysize 200
tgcgct10@LNX920>select * from CLDOM_DOMICILIOS where CLENT_COD_CLIENTE = 2796;

100304 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=276 Bytes=28152)

   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'CLDOM_DOMICILIOS' (Cost=2 Card=276
Bytes=28152)

   2    1     INDEX (RANGE SCAN) OF 'CLDOM_DOMICILIOS_IDX1' (NON-UNIQUE) (Cost=1
Card=276)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       3493  consistent gets
       2495  physical reads
          0  redo size
   11998944  bytes sent via SQL*Net to client
       6010  bytes received via SQL*Net from client
        503  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     100304  rows processed

as you can see I have different consistent gets (15701 vs 3493), from these
results how can I know how many blocks my query actually read in the buffer....?

Thank you a lot for you advices :-D

          Followup:

one really read 15,701 and the other really read 3,493.

really - one did 15,701 buffer block gets from the buffer.  the other really did
3,493 from the buffer.

      Rating: 2
ok, I am
      totally confused now  April 29, 2003
Reviewer:
      A reader
Hi again

First thanks for your patience and replies.

Secondly, I am 100% confused now. In your previous reply you said

"consistent gets IS the number of blocks Oracle will get from the cache.  that
is exactly what consistent gets is"

Number of blocks == Consistent gets

So how come a query needs to read different number of blocks in the buffer even
the execution plan is exact....? I can see they read same number of blocks from
disk but from the buffer it does not.

Thirdly, i did a TKPROF and two questions came up (look the TKPROF paste)

select *
from
 CLDOM_DOMICILIOS where CLENT_COD_CLIENTE = 2796

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch    10032      1.68       2.63       1845      22272          0      100304
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    10034      1.68       2.63       1845      22272          0      100304

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 58

Rows     Row Source Operation
-------  ---------------------------------------------------
 100304  TABLE ACCESS BY INDEX ROWID CLDOM_DOMICILIOS
 100304   INDEX RANGE SCAN CLDOM_DOMICILIOS_IDX1 (object id 29805)

********************************************************************************

select *
from
 TGCGCT10.CLDOM_DOMICILIOS where CLENT_COD_CLIENTE = 2796

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     1005      2.031026955720.17       1838       4479          0
100304
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1007      2.031026955720.17       1838       4479          0
100304

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 58

My first concern is why in the second query TKPROF does not show

Rows     Row Source Operation
-------  -----------------------------------------------

like the first one

The second concern is what is the funny elapsed number 1026955720.17 in the
second query....?

I am using Oracle EE 9.2.0.1

Cheers

          Followup:

Well, I cannot address the "funny number" -- other then to say either

o your trace file is truncated and those results are suspect (this would answer
the "why there is not query plan for the second query" -- it is written last,
after the cursor is closed

o there is an issue on your platform with tracing that I'm not aware of...

The reasons you don't have a query plan for the second query are either

o see above -- trace file truncated
o or you did this in sqlplus or some other environment and you did not EXIT that
environment.  The stat records are written at the end.

The differnce (as seen right above in your other exmaple) is the array fetch
size.

Lets take a simplier exmaple.  A full scan.  Say you have a table with 100
blocks in it.  You have 50 rows per block as follows:

ops$tkyte@ORA817DEV> create table t ( x int );
Table created.

ops$tkyte@ORA817DEV> insert into t select 1 from all_objects where rownum <= 50;
50 rows created.

ops$tkyte@ORA817DEV> alter table t minimize records_per_block;
Table altered.

ops$tkyte@ORA817DEV> insert into t select 1 from all_objects where rownum <=
50*99;
4950 rows created.

ops$tkyte@ORA817DEV> select min(count(*)), max(count(*)), avg(count(*))
  2    from t
  3   group by dbms_rowid.rowid_block_number(rowid)
  4  /

MIN(COUNT(*)) MAX(COUNT(*)) AVG(COUNT(*))
------------- ------------- -------------
           50            50            50

ops$tkyte@ORA817DEV> select count(distinct dbms_rowid.rowid_block_number(rowid)
)
  2    from t
  3  /

COUNT(DISTINCTDBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID))
---------------------------------------------------
                                                100

so, 100 blocks, 50 rows per block.  Let's select * from this table -- but
fetch 10 rows at a time:

ops$tkyte@ORA817DEV> set autotrace traceonly
ops$tkyte@ORA817DEV> set arraysize 10
ops$tkyte@ORA817DEV> select * from t;

5000 rows selected.

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

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
        603  consistent gets
...
       5000  rows processed

Ok, so what happened?  600 logical IO's.  600 reads from the buffer cache...
how could that be?  well, this is what happened:

you asked for rows 1..10
server got block 1 from cache (latched it), copied it (unlatched it)
server gave you rows 1..10
you asked for rows 11..20
server got block 1 from cache AGAIN (2 logical ios now)...
you asked for rows 21..30
server got block 1 from cache AGAIN (3)
you asked for rows 31..40
server got block 1 from cache AGAIN (4)
you asked for rows 41..50
server got block 1 from cache AGAIN (5)
you asked for rows 51..60
server got block 1 from cache AGAIN (6)
found no data, got block 2          (7)
......

you read each and every block about 6 times.  6*100 = 600.

ops$tkyte@ORA817DEV> set arraysize 50
ops$tkyte@ORA817DEV> select * from t;

5000 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
        203  consistent gets
...
       5000  rows processed

Now, you can see what happened here -- rows 1..50 = block 1, 51..100 =
block1+block2 and so on...

ops$tkyte@ORA817DEV> set arraysize 100
ops$tkyte@ORA817DEV> select * from t;

5000 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
        153  consistent gets
....
       5000  rows processed

ops$tkyte@ORA817DEV> set autotrace off
ops$tkyte@ORA817DEV>

and so on.  So, the larger the array size, the less consistent gets we needed
to perform in order to answer your question.  The less logical IO's we did.  The
less latching we did

The main difference between your two queries above is the array size.  In one
example, you used an array size of 10.  You got and re-got the same block(s)
over and over.  In the second case, you used 100 -- you got a block once and
read the entire thing and never needed to get it again.

      Rating: 3
forgot to
      say  April 29, 2003
Reviewer:  A reader

the first query used arraysize 10 and the second arraysize 100

          Followup:

that was obvious ;)

      Rating: 3
hi
      again  April 29, 2003
Reviewer:
      A reader
Hi

I fixed the first issue regarding

Rows     Row Source Operation

I had to quit my session in order to get that part generated in my trace file,
or simply I had to issue another SQL stmt.

Cheers

      Rating: 5
ok! so it is
      like this  April 29, 2003
Reviewer:
      A reader
So I conclude

Number of blocks != Consistent gets

Guess we have to query v$bh to find the number of blocks read from buffer for a
query!

Cheers

          Followup:

No, no, no.

You are totally missing the point.

If you read block 1 500 times -- you have 500 block gets and it is not any
different in the amount of work we do to read 500 *different* blocks.

The number you are after is meaningless.  No more meaningful then if someone
asked me "how many football fields did you run around" vs "how many times did
you run around a football field".

You need to know the work performed.  How many unique, distinct blocks --
meaningless.

That and I'd love to see the query against v$bh that would tell you!

Your goal -- reduce LIO's.
Your number -- consistent gets.
That other number you seem bent on getting -- less then useless.

      Rating:
      4
showsql.sql  April 30, 2003
Reviewer:
      GM
Tom, where is this script located - I can't find it in $ORACLE_HOME\rdbms\admin?

Many thanks in advance

          Followup:

it is my script.

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:767025833873

      Rating:
      4
showsql.sql  April 30, 2003
Reviewer:
      GM
Thanks, prima!

      Rating:
      5
  April 30, 2003
Reviewer:
      A reader
Tom,

What do you recommend(size) for arraysize, should we keep 10 or 15 or 50 or 100
or depending on how many records we want to grab.

Thanks

          Followup:

i like the number 100.

      Rating: 4
count
      (*)?  April 30, 2003
Reviewer:  Kashif

Hi Tom,

You mentioned:

"that stinks -- I hope the code isn't something silly like:

 select count(*) into l_cnt .....

 if ( l_cnt != 0 ) then...."

If it happens to be that way, then how would you suggest changing it? I have
some procedures doing the same thing. Thanks in advance.

Kashif

          Followup:

either:

a) don't do the count -- ask yourself "why am I counting" -- why not just TRY
the operation and see if it finds any rows to work on.

eg:

  select count(*) into l_cnt from t where user = :x;
  if ( l_cnt != 0 )
  then
      for x in ( select *from t where user = :x  )
      loop
           ...

should just be the loop;

b) select count(*) into l_cnt from dual
     where exists (that query you used to count)

or

c) select count(*) into l_cnt from ......<query you had> AND ROWNUM = 1

b and c "short circut", stop when they find a row, they don't waste time
figuring out "oh there are 12432 of them".  They just tell you "yes there is" or
"no there isn't"

Funny thing is -- by the time the COUNT is done -- the answer may well have
changed!!  so your logic needs to be prepared for that (leading me to think (a)
is by far the best solution)

      Rating: 4
Followup to
      count(*)  April 30, 2003
Reviewer:
      A reader
Unless you actually need to no the number of
rows then there iwhy count them all?

The code Tom says stinks is quite common
when people want to check for existence of
any rows, they really aren't interested in how many
rows in total there are

Heres a quick example I found on tom's Site

select count(*)
  from dual
 where exists ( select NULL
                  from emp
                 where sal = 5000 );

that'll return 0 or 1.  It'll stop looking at the emp table the instant it finds

one row.

      Rating: 5
Count(*) - What
      if...  April 30, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Hi Tom,
Would you still recommend the use of "SELECT Count(*) FROM  DUAL ..." syntax if
you know that the query that you have will return no more than one row?
In such a case, should we use the "SELECT Count(*) FROM <table> WHERE
<condition>" syntax or still use the DUAL construct?
Or maybe, there is another alternative to figuring out if the given input
matches a row (The case I'm talking about is when the Input will match either
one row or not match at all).

          Followup:

i wouldn't count AT ALL -- that is my point.

just try the operation -- no data to process, skip it.  data to process -- do
it.

show me the case (all of the logic) where the count(*) is *needed*.

      Rating: 3
Oracle
      DBA  May 01, 2003
Reviewer:  Arash
       from Canada
Hi Tom,

 In the case that we want just to check whether there is any record in the Table
or not. I mean we are just looking for "YES" or "NO" answer not the number of
records in the table. You recommended useing of the following syntax:

SELECT count(*)
FROM table_name
WHERE condition
  AND rownum = 1;

I was wondering is there any other way to do it? Because our Software should
work on MSSQL & SQLAnywhere databases as well and these databases doesn't have
rownum ! I need a synatx which works for all three vendors.

Thanks in advance,
Arash Zaryoun

          Followup:

create table mytab ( x int primary key );
insert into mytab values(1);
analyze table mytab compute statistics;

select count(*) from mytab where exists (query);

      Rating:
      1
  May 01, 2003
Reviewer:  Arash

Thank you so much. I got it.

      Rating: 5
wondering about
      one of examples  May 03, 2003
Reviewer:
      A reader
hi

in one of recent responses you said

select count(*) into l_cnt from t where user = :x;
  if ( l_cnt != 0 )
  then
      for x in ( select *from t where user = :x  )
      loop
           ...

should just be the loop;

How do you change that code into a FOR loop...? I mean how do you put the count
part into the cursor?

          Followup:

you don't need the count -- the count was just to "see if we should do this
loop".

My point is -- just do the loop

for x in ( select * from t where user = :x )
loop
   .....

that is all you need -- you see, if you say "but I need l_cnt inside the loop"
then you have a bug.  The instant after you get l_cnt, it is stale, old, out of
date and possibly not reflecting reality....

      Rating:
      5
  June 18, 2003
Reviewer:
      A reader
Hi Tom,

One the guys in our group said that in Oracle 8.0.5 to use utlbstat/utlestat we
have to buy license for that..

How far is that true??

          Followup:

it is not true at all.

      Rating: 4
CONSISTENT
      GETS  August 04, 2003
Reviewer:  John French
       from Atlanta, GA
Tom,

     I got two databases on UAT and one PROD both the same size. In fact the UAT
was created from the Prod last week using Exp/Imp.

    I got a query that goes against two tables, one about 20 Mil rows and the
other 120 Mil rows. When I do a explain plan or a Autotrace or a Sql Trace, it
shows the exact same exact plan and cost for both.

    But when I execute the query, one shows a lot higher consistent gets. Mind
it is the same no. of rows. Any idea what is going on?

Thanks,

John.

Prod Output...

xecution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=26705 Card=1 Bytes=1
          74)

   1    0   FILTER*                                                    :Q360080
                                                                       01

   2    1     SORT* (GROUP BY) (Cost=26705 Card=1 Bytes=174)           :Q360080
                                                                       01

   3    2       NESTED LOOPS* (Cost=26696 Card=1 Bytes=174)            :Q360080
                                                                       00

   4    3         NESTED LOOPS* (Cost=26693 Card=1 Bytes=141)          :Q360080
                                                                       00

   5    4           NESTED LOOPS* (Cost=26691 Card=2 Bytes=238)        :Q360080
                                                                       00

   6    5             NESTED LOOPS* (Cost=26690 Card=1 Bytes=108)      :Q360080
                                                                       00

   7    6               NESTED LOOPS* (Cost=26687 Card=3 Bytes=258)    :Q360080
                                                                       00

   8    7                 TABLE ACCESS* (FULL) OF 'DSTS2_ORDER_HEADER' :Q360080
           (Cost=26678 Card=3 Bytes=198)                               00

   9    7                 INDEX* (RANGE SCAN) OF 'ORDEA_PK' (UNIQUE) ( :Q360080
          Cost=3 Card=21259213 Bytes=425184260)                        00

  10    6               TABLE ACCESS* (FULL) OF 'DSTS2_CBPF_FEATURE' ( :Q360080
          Cost=1 Card=95 Bytes=2090)                                   00

  11    5             INDEX* (FAST FULL SCAN) OF 'USOC_PK' (UNIQUE) (C :Q360080
          ost=1 Card=3253 Bytes=35783)                                 00

  12    4           TABLE ACCESS* (BY INDEX ROWID) OF 'DSTS2_CBPF' (Co :Q360080
          st=1 Card=6 Bytes=132)                                       00

  13   12             INDEX* (UNIQUE SCAN) OF 'XPKDSTS2_CBPF' (UNIQUE) :Q360080
                                                                       00

  14    3         TABLE ACCESS* (BY INDEX ROWID) OF 'DSTS2_ORDER_DETAI :Q360080
          L' (Cost=3 Card=108011197 Bytes=3564369501)                  00

  15   14           INDEX* (UNIQUE SCAN) OF 'ORDED_PK' (UNIQUE) (Cost= :Q360080
          2 Card=108011197)                                            00

   1 PARALLEL_COMBINED_WITH_CHILD
   2 PARALLEL_TO_SERIAL            SELECT A1.C0,A1.C1,A1.C2,A1.C3,A1.C4,A1.C5,A
                                   1.C6,A1.C7,A1.C8,A1.C9,A1.C10,A1.C11

   3 PARALLEL_TO_PARALLEL          SELECT /*+ ORDERED NO_EXPAND USE_NL(A2) INDE
                                   X(A2 "ORDED_PK") */ A1.C0 C0,A1.C13

   4 PARALLEL_COMBINED_WITH_PARENT
   5 PARALLEL_COMBINED_WITH_PARENT
   6 PARALLEL_COMBINED_WITH_PARENT
   7 PARALLEL_COMBINED_WITH_PARENT
   8 PARALLEL_COMBINED_WITH_PARENT
   9 PARALLEL_COMBINED_WITH_PARENT
  10 PARALLEL_COMBINED_WITH_PARENT
  11 PARALLEL_COMBINED_WITH_PARENT
  12 PARALLEL_COMBINED_WITH_PARENT
 13 PARALLEL_COMBINED_WITH_PARENT
  14 PARALLEL_COMBINED_WITH_PARENT
  15 PARALLEL_COMBINED_WITH_PARENT

Statistics
----------------------------------------------------------
         35  recursive calls
    4112372  db block gets
   23209109  consistent gets
     438179  physical reads
        692  redo size
      54709  bytes sent via SQL*Net to client
       3200  bytes received via SQL*Net from client
         27  SQL*Net roundtrips to/from client
         17  sorts (memory)
          0  sorts (disk)
        380  rows processed

UAT output......

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=13489 Card=1 Bytes=2
          25)

   1    0   FILTER*                                                    :Q268001
   2    1     SORT* (GROUP BY) (Cost=13489 Card=1 Bytes=225)           :Q268001
   3    2       SORT* (GROUP BY) (Cost=13489 Card=1 Bytes=225)         :Q268000
   4    3         NESTED LOOPS* (Cost=13464 Card=1 Bytes=225)          :Q268000
   5    4           NESTED LOOPS* (Cost=13462 Card=1 Bytes=192)        :Q268000
   6    5             NESTED LOOPS* (Cost=13461 Card=1 Bytes=181)      :Q268000
   7    6               NESTED LOOPS* (Cost=13460 Card=1 Bytes=142)    :Q268000
   8    7                 NESTED LOOPS* (Cost=13458 Card=3 Bytes=255)  :Q268000
   9    8                   TABLE ACCESS* (FULL) OF 'DSTS2_ORDER_HEADE :Q268000
          R' (Cost=13452 Card=3 Bytes=198)

  10    8                   INDEX* (RANGE SCAN) OF 'ORDEA_PK' (UNIQUE) :Q268000
           (Cost=3 Card=20635299 Bytes=392070681)

  11    7                 TABLE ACCESS* (FULL) OF 'DSTS2_CBPF_FEATURE' :Q268000
           (Cost=1 Card=164 Bytes=9348)

  12    6               TABLE ACCESS* (BY INDEX ROWID) OF 'DSTS2_CBPF' :Q268000
           (Cost=1 Card=164 Bytes=6396)

  13   12                 INDEX* (UNIQUE SCAN) OF 'XPKDSTS2_CBPF' (UNI :Q268000
          QUE)

  14    5             INDEX* (RANGE SCAN) OF 'USOC_PK' (UNIQUE) (Cost= :Q268000
          2 Card=3235 Bytes=35585)

  15    4           TABLE ACCESS* (BY INDEX ROWID) OF 'DSTS2_ORDER_DET :Q268000
          AIL' (Cost=3 Card=105300331 Bytes=3474910923)

  16   15             INDEX* (UNIQUE SCAN) OF 'ORDED_PK' (UNIQUE) (Cos :Q268000
          t=2 Card=105300331)

   1 PARALLEL_COMBINED_WITH_CHILD
   2 PARALLEL_TO_SERIAL            SELECT /*+ CIV_GB */ A1.C0,A1.C1,A1.C2,A1.C3
                                   ,A1.C4,A1.C5,A1.C6,A1.C7,A1.C8,A1.C9

   3 PARALLEL_TO_PARALLEL          SELECT /*+ PIV_GB */ A1.C0 C0,A1.C13 C1,A1.C
                                   22 C2,A1.C1 C3,A1.C9 C4,A1.C10 C5,A1

   4 PARALLEL_COMBINED_WITH_PARENT
   5 PARALLEL_COMBINED_WITH_PARENT
   6 PARALLEL_COMBINED_WITH_PARENT
   7 PARALLEL_COMBINED_WITH_PARENT
   8 PARALLEL_COMBINED_WITH_PARENT
   9 PARALLEL_COMBINED_WITH_PARENT
  10 PARALLEL_COMBINED_WITH_PARENT
  11 PARALLEL_COMBINED_WITH_PARENT
  12 PARALLEL_COMBINED_WITH_PARENT
  13 PARALLEL_COMBINED_WITH_PARENT
  14 PARALLEL_COMBINED_WITH_PARENT
  15 PARALLEL_COMBINED_WITH_PARENT
  16 PARALLEL_COMBINED_WITH_PARENT

Statistics
----------------------------------------------------------
         45  recursive calls
        125  db block gets
     218665  consistent gets
     212231  physical reads
        692  redo size
       1167  bytes sent via SQL*Net to client
        314  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
         27  sorts (memory)
          0  sorts (disk)
          0  rows processed

          Followup:

and how is temp set up on each?

      Rating:
      4
follow-up  August 04, 2003
Reviewer:
      John French  from Atlanta, GA
Also the Block Size in UAT is twice of Prodcution. Hence the difference in
Physical reads.

John.

          Followup:

not even going to touch that one -- shouldn't they "be the same"

      Rating:
      5
Temp  August 04, 2003
Reviewer:  Jon
      French.
Temp is setup temporary on UAT and regular permenant Files on Prod. But all
Sorts are in Memory as the stats show.  The time difference and consistent gets
is probably in the nested loop join as longops goes idle after 2 - 3 mins. No
Table or Index scans after that.

THANKS!!!

John.

          Followup:

plans are very different -- you said they were the same, start looking there.

xecution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=26705 Card=1 Bytes=1 74)
   1    0   FILTER*
   2    1     SORT* (GROUP BY) (Cost=26705 Card=1 Bytes=174)
   3    2       NESTED LOOPS* (Cost=26696 Card=1 Bytes=174)
   4    3         NESTED LOOPS* (Cost=26693 Card=1 Bytes=141)
   5    4           NESTED LOOPS* (Cost=26691 Card=2 Bytes=238)
   6    5             NESTED LOOPS* (Cost=26690 Card=1 Bytes=108)
   7    6               NESTED LOOPS* (Cost=26687 Card=3 Bytes=258)
   8    7                 TABLE ACCESS* (FULL) OF 'DSTS2_ORDER_HEADER'
   9    7                 INDEX* (RANGE SCAN) OF 'ORDEA_PK' (UNIQUE) (
  10    6               TABLE ACCESS* (FULL) OF 'DSTS2_CBPF_FEATURE' (
  11    5             INDEX* (FAST FULL SCAN) OF 'USOC_PK' (UNIQUE) (C
  12    4           TABLE ACCESS* (BY INDEX ROWID) OF 'DSTS2_CBPF' (Co
  13   12             INDEX* (UNIQUE SCAN) OF 'XPKDSTS2_CBPF' (UNIQUE)
  14    3         TABLE ACCESS* (BY INDEX ROWID) OF 'DSTS2_ORDER_DETAI
  15   14           INDEX* (UNIQUE SCAN) OF 'ORDED_PK' (UNIQUE) (Cost=

   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=13489 Card=1 Bytes=2 25)
   1    0   FILTER*
   2    1     SORT* (GROUP BY) (Cost=13489 Card=1 Bytes=225)
   3    2       SORT* (GROUP BY) (Cost=13489 Card=1 Bytes=225)
   4    3         NESTED LOOPS* (Cost=13464 Card=1 Bytes=225)
   5    4           NESTED LOOPS* (Cost=13462 Card=1 Bytes=192)
   6    5             NESTED LOOPS* (Cost=13461 Card=1 Bytes=181)
   7    6               NESTED LOOPS* (Cost=13460 Card=1 Bytes=142)
   8    7                 NESTED LOOPS* (Cost=13458 Card=3 Bytes=255)
   9    8                   TABLE ACCESS* (FULL) OF 'DSTS2_ORDER_HEADE
  10    8                   INDEX* (RANGE SCAN) OF 'ORDEA_PK' (UNIQUE)
  11    7                 TABLE ACCESS* (FULL) OF 'DSTS2_CBPF_FEATURE'
  12    6               TABLE ACCESS* (BY INDEX ROWID) OF 'DSTS2_CBPF'
  13   12                 INDEX* (UNIQUE SCAN) OF 'XPKDSTS2_CBPF' (UNI
  14    5             INDEX* (RANGE SCAN) OF 'USOC_PK' (UNIQUE) (Cost=
  15    4           TABLE ACCESS* (BY INDEX ROWID) OF 'DSTS2_ORDER_DET
  16   15             INDEX* (UNIQUE SCAN) OF 'ORDED_PK' (UNIQUE) (Cos

      Rating: 2
Query
      Plans  August 06, 2003
Reviewer:  John
      French  from GA
Tom,

     The Queries are different in one table being different
along the nested loop heirarchy.

     Anyway I managed to tweak the order of the Join using " ORDERED " Hint and
found alteast 3 combinations a lot faster than the Oracle decided way.  I might
be able to find several more if I test all combinations. Mind all the tables
used in this query have been analyzed compute with all indexes and indexed
columns.

     My question is why is Oracle not even choosing one of the better patha from
say the top 5 out of 20 - 30 possible combinations. Is The Optimizer not as
efficient as Oracle markets?

John.

          Followup:

given that you give me almost no data points to work with -- I cannot really
address anything.

You said "query plans the same" (they were not)...

But we don't get to see the queries.

No comments from you as to whether the estimated cardinality (which is a huge
driver) on the plans is even close.

versions -- well no ideas there...

don't really understand the last comment...

      Rating: 3
regarding this
      output..  August 19, 2003
Reviewer:  vj
       from in
tom,

Version : 9i Release 2 on Win-2k(this query is fired on the bean to get the
data)

recently we wrote a view that has link with 10 tables..from the tx screen we got
to show most of the information for the master, so we require desc for each
code..

our query return such a plan below...i require some understanding on why so many
nested loops there ?? This query takes long time to come!!! Why ? ..below the
plan i have pasted the query too..

PLAN_TABLE_OUTPUT

---------------------------------------------------------------------------------
------------------
| Id  | Operation                                   |  Name               | Rows
 | Bytes | Cost  |
---------------------------------------------------------------------------------
------------------
|   0 | SELECT STATEMENT                            |                     |
 |       |       |
|*  1 |  TABLE ACCESS BY INDEX ROWID                | T_POTENTIAL_AGENT   |
 |       |       |
|   2 |   NESTED LOOPS                              |                     |
 |       |       |
|   3 |    NESTED LOOPS                             |                     |
 |       |       |
|   4 |     NESTED LOOPS                            |                     |
 |       |       |
|   5 |      NESTED LOOPS                           |                     |
 |       |       |
|   6 |       NESTED LOOPS                          |                     |
 |       |       |
|   7 |        NESTED LOOPS                         |                     |
 |       |       |
|   8 |         NESTED LOOPS                        |                     |
 |       |       |
|   9 |          NESTED LOOPS                       |                     |
 |       |       |
|  10 |           NESTED LOOPS                      |                     |
 |       |       |
|  11 |            NESTED LOOPS                     |                     |
 |       |       |
|  12 |             NESTED LOOPS                    |                     |
 |       |       |
|  13 |              NESTED LOOPS                   |                     |
 |       |       |
|  14 |               NESTED LOOPS                  |                     |
 |       |       |
|  15 |                NESTED LOOPS                 |                     |
 |       |       |
|  16 |                 NESTED LOOPS                |                     |
 |       |       |
|  17 |                  TABLE ACCESS FULL          | T_EXAMSETTINGS      |
 |       |       |
|* 18 |                  TABLE ACCESS BY INDEX ROWID| T_AGENT_RANK        |
 |       |       |
|* 19 |                   INDEX RANGE SCAN          | PK_AGENT_RANK       |
 |       |       |
|  20 |                 TABLE ACCESS BY INDEX ROWID | T_AGENT_RANK        |
 |       |       |
|* 21 |                  INDEX RANGE SCAN           | PK_AGENT_RANK       |
 |       |       |
|  22 |                TABLE ACCESS BY INDEX ROWID  | T_AGENT_TIER        |
 |       |       |
|* 23 |                 INDEX RANGE SCAN            | PK_AGENT_TIER       |
 |       |       |
|  24 |               TABLE ACCESS BY INDEX ROWID   | T_TITLE             |
 |       |       |
|* 25 |                INDEX RANGE SCAN             | PK_TITLE            |
 |       |       |
|  26 |              TABLE ACCESS BY INDEX ROWID    | T_EXAM_TIMETABLE    |
 |       |       |
|* 27 |               INDEX RANGE SCAN              | PK_EXAM_TIMETABLE   |
 |       |       |
|  28 |             TABLE ACCESS BY INDEX ROWID     | T_LANGUAGE          |
 |       |       |
|* 29 |              INDEX RANGE SCAN               | PK_LANGUAGE         |
 |       |       |
|  30 |            TABLE ACCESS BY INDEX ROWID      | T_SERVICE_CENTRE    |
 |       |       |
|* 31 |             INDEX RANGE SCAN                | PK_SERVICE_CENTRE   |
 |       |       |
|* 32 |           TABLE ACCESS BY INDEX ROWID       | T_REG_AGENT         |
 |       |       |
|* 33 |            INDEX RANGE SCAN                 | PK_REG_AGENT        |
 |       |       |
|  34 |          TABLE ACCESS BY INDEX ROWID        | T_DEPARTMENT        |
 |       |       |
|* 35 |           INDEX RANGE SCAN                  | PK_DEPARTMENT       |
 |       |       |
|  36 |         TABLE ACCESS BY INDEX ROWID         | T_BANKBRANCH        |
 |       |       |
|* 37 |          INDEX RANGE SCAN                   | PK_BANKBRANCH       |
 |       |       |
|  38 |        TABLE ACCESS BY INDEX ROWID          | T_AGENT_CATEGORY    |
 |       |       |
|* 39 |         INDEX RANGE SCAN                    | PK_AGENT_CATEGORY   |
 |       |       |
|  40 |       TABLE ACCESS BY INDEX ROWID           | T_RELIGION          |
 |       |       |
|* 41 |        INDEX RANGE SCAN                     | PK_RELIGION         |
 |       |       |
|  42 |      TABLE ACCESS BY INDEX ROWID            | T_QUALIFICATION     |
 |       |       |
|* 43 |       INDEX RANGE SCAN                      | PK_QUALIFICATION    |
 |       |       |
|  44 |     TABLE ACCESS BY INDEX ROWID             | T_NATIONALITY       |
 |       |       |
|* 45 |      INDEX RANGE SCAN                       | PK_NATIONALITY      |
 |       |       |
|* 46 |    INDEX RANGE SCAN                         | PK_POTENTIAL_AGENT  |
 |       |       |
---------------------------------------------------------------------------------
------------------

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

   1 - filter("A"."AGENT_CENTER_CODE"="L"."CENTRE_CODE" AND
"A"."RECRUITED_BY"="A"."AGENT_CODE"
              AND "A"."DEPT_CODE"="J"."DEPT_CODE" AND
"A"."BRANCH_CODE"="I"."BRANCH_CODE" AND "A
              "."AGENT_CAT"="H"."AGENT_CAT" AND
"A"."AGENT_LEVEL"="A"."AGENT_LEVEL" AND "A"."AGE
              NT_RANK"="A"."AGENT_RANK" AND
"A"."RELIGION_CODE"="E"."RELIGION_CODE" AND "A"."QUA
              LIFICATION_CODE"="D"."QUALIFICATION_CODE" AND
"A"."RACE_CODE"="C"."RACE_CODE" AND
              "A"."TITLE_CODE"="B"."TITLE_CODE")
  18 - filter("B"."RECDL_YN"='Y')
  19 - access("B"."RECTYPE"='L')
  21 - access("A"."RECTYPE"='L')
  23 - access("A"."RECTYPE"="B"."RECTYPE" AND "A"."AGENT_TIER"="B"."AGENT_TIER")
       filter("A"."AGENT_TIER"="B"."AGENT_TIER")
  25 - access("B"."RECTYPE"='L')
       filter("B"."RECTYPE"="A"."RECTYPE")
  27 - access("P"."RECTYPE"="B"."RECTYPE" AND "M"."EXAM_DATE"="P"."EXAM_DATE")
       filter("M"."EXAM_DATE"="P"."EXAM_DATE")
  29 - access("B"."RECTYPE"="O"."RECTYPE" AND "M"."LANG_CODE"="O"."LANG_CODE")
       filter("M"."LANG_CODE"="O"."LANG_CODE")
  31 - access("B"."RECTYPE"="L"."RECTYPE")
  32 - filter("A"."AGENT_RANK"="B"."AGENT_RANK")
  33 - access("B"."RECTYPE"="A"."RECTYPE")
  35 - access("B"."RECTYPE"="J"."RECTYPE")
  37 - access("B"."RECTYPE"="I"."RECTYPE")
  39 - access("B"."RECTYPE"="H"."RECTYPE")
  41 - access("B"."RECTYPE"="E"."RECTYPE")
  43 - access("B"."RECTYPE"="D"."RECTYPE")
  45 - access("B"."RECTYPE"="C"."RECTYPE")
  46 - access("A"."RECTYPE"="M"."RECTYPE" AND "A"."AGENT_CODE"="M"."AGENT_CODE")
       filter("A"."AGENT_CODE"="M"."AGENT_CODE" AND "A"."RECTYPE"<>'H')

Note: rule based optimization

SELECT
    A.*,
    M.EXAM_DATE,
    M.EXAM_NUMBER,
    M.FEE_AMOUNT,
    M.LANG_CODE AS EXAM_LANG_CODE,
    M.LOCATION,
    M.PAY_REF AS EXAM_PAY_REF,
    M.RESIT_DATE,
    M.RESULT,
    B.TITLE_DESC,
    C.RACE_DESC,
    D.QUALIFICATION_DESC,
    E.RELIGION_DESC
    --,F.COUNTRY_DESC,F.STATE_DESC,F.REGION_DESC
    ,G.AGENT_DESC,
    G.TIER_DESC,
    H.CAT_DESC,
    I.BRANCH_DESC,
    J.DEPT_DESC,
    K.AGENT_NAME AS RECRUITER_NAME,
    K.RANK_DESC AS RECRUITER_RANK_NAME
    ,L.CENTRE_DESC,
    O.LANG_DESC,
    P.RESULT_DATE,'' AS AGE
    --N.CITY_DESC AS EXAM_CITY_DESC,
FROM
    T_POTENTIAL_AGENT A,
    T_TITLE B,
    T_NATIONALITY C,
    T_QUALIFICATION D,
    T_RELIGION E,
    VW_RANK_TIER G,
    T_AGENT_CATEGORY H,
    T_BANKBRANCH I,
    T_DEPARTMENT J,
    VW_AGENT_RANK K,
    T_SERVICE_CENTRE L,
    T_EXAMSETTINGS M,
    T_LANGUAGE O,
    T_EXAM_TIMETABLE P
    --,T_POSTCODE N,
    --,VW_LIVE_POSTCODE F
WHERE
    -- A TO M CONDITION
    A.AGENT_CODE=M.AGENT_CODE
    AND A.RECTYPE <> 'H'
    AND A.RECTYPE=M.RECTYPE
    -- A TO B CONDITION
    AND A.TITLE_CODE=B.TITLE_CODE
    AND B.RECTYPE='L'
    -- A TO C CONDITION
    AND A.RACE_CODE=C.RACE_CODE
    AND B.RECTYPE=C.RECTYPE
    -- A TO D CONDITION
    AND A.QUALIFICATION_CODE=D.QUALIFICATION_CODE
    AND B.RECTYPE=D.RECTYPE
    -- A TO E CONDITION
    AND A.RELIGION_CODE=E.RELIGION_CODE
    AND B.RECTYPE=E.RECTYPE
    -- A TO F CONDITION
    --AND A.POST_CODE=F.POST_CODE
    --AND A.COUNTRY_CODE=F.COUNTRY_CODE
    --AND F.RECTYPE=B.RECTYPE
    -- A TO G CONDITION
    AND A.AGENT_RANK=G.AGENT_RANK
    AND A.AGENT_LEVEL=G.AGENT_LEVEL
    AND B.RECTYPE=G.RECTYPE
    -- A TO H CONDITION
    AND A.AGENT_CAT=H.AGENT_CAT
    AND B.RECTYPE=H.RECTYPE
    -- A TO I CONDITION
    AND A.BRANCH_CODE=I.BRANCH_CODE
    AND B.RECTYPE=I.RECTYPE
    -- A TO J CONDITION
    AND A.DEPT_CODE=J.DEPT_CODE
    AND B.RECTYPE=J.RECTYPE
    -- A TO K CONDITION
    AND A.RECRUITED_BY=K.AGENT_CODE
    AND B.RECTYPE=K.RECTYPE
    -- A TO N CONDITION
    --AND N.RECTYPE=B.RECTYPE
    --AND M.LOCATION=N.POST_CODE
    -- A TO L CONDITION
    AND A.AGENT_CENTER_CODE=L.CENTRE_CODE
    AND B.RECTYPE=L.RECTYPE
    -- M TO O CONDITION
    AND M.LANG_CODE=O.LANG_CODE
    AND B.RECTYPE=O.RECTYPE
    -- A TO P CONDITION
    AND M.EXAM_DATE=P.EXAM_DATE
    AND P.RECTYPE=B.RECTYPE

Rgds

          Followup:

umm, why so many nested loops?

easy -- one per join there.

so tell us, how long is long, how much data does this return?

      Rating:
      3
  August 19, 2003
Reviewer:  vj
       from in
should be less than 5000 Rows TOM...

          Followup:

tkprof tkprof, show us a tkprof.

and bear in mind,

5,000 rows * 13 tables * 3 reads/index + 1 read /table access by rowid = 260,000
logical IO's which is HUGE HUGE HUGE

specially for a bean -- aren't those supposed to be "small", "transactional",
"tiny" things?

      Rating: 4
High
      parse/execute ratio  September 16, 2003
Reviewer:
      Bonnie Tang  from HK
Hi, Tom,

what parse/execute ratio is considered high ?

BT

          Followup:

the formula is:

round(100*(1-:prse/:exe),2)

the close to 100%, the better.  (lots more executes then parses)

in a web system with connection pooling -- 40-50% is normal.
client server (connected sessions) -- as high as possible is good

negative numbers indicate you PARSE more then you EXECUTE -- which is really
really bad.

September 17, 2003 Abhijit -- Thanks for the question regarding "Why
      is cost based optimizer behaving poorly?", version 8.1.7

      You Asked
I am working with Oracle 8.1.7 database (running on Solaris 2.8). I know that
Oracle recommends use of cost based optimizer in all future projects and agree
with the fact that cost based optimizer has come a long way from the time it was
originally released. I have seen it behave really intelligently in most
circumstances where database tables and indexes contain latest statistics.
Here is one example where I am finding that cost based optimizer is totally
outperformed by rule based optimizer. What surprises me even more - the query or
PL/SQL block in question is not complex at all.

Record count in ps_job ~ 65000
Record count in ps_sal_grade_tbl ~ 2500

I have an index (i1) on ps_sal_grade_tbl(setid,sal_admin_plan,grade,effdt)

When I execute the PL/SQL block in rule based mode, the execution time is little
more than 1 minute while in cost based mode it takes more than 5 minutes. While
running in "choose" I made sure that table & indexes in question are analyzed. I
generated the trace file and attempted to figure out the bottleneck. I am not
dumping the whole trace file here but I am including the section where SELECT
statement inside the FOR-LOOP is getting executed. You will notice that rule
based optimizer opts for index range scan while cost based goes for index FFS.
CPU time associated with fetch calls are radically different in two optimizer
modes. I don't want to switch the optimizer mode without understanding why this
is happening. Will you please help me out. The database in running on a decent
Sun machine with 4 processors. I am also attaching the init.ora file

SET SERVEROUTPUT ON SIZE 1000000
SET TIME ON
SET TIMING ON
DECLARE
   v_sal_grade_cnt INT ;
BEGIN
   FOR ps_job_rec IN (SELECT *
                      FROM   ps_job) LOOP
   BEGIN

      SELECT COUNT(*)
      INTO   v_sal_grade_cnt
      FROM   ps_sal_grade_tbl a
      WHERE  setid = ps_job_rec.setid_salary
      AND    sal_admin_plan = ps_job_rec.sal_admin_plan
      AND    grade = ps_job_rec.grade
      AND    effdt = (SELECT MAX(effdt)
                      FROM   ps_sal_grade_tbl
                      WHERE  setid = a.setid
                      AND    sal_admin_plan = a.sal_admin_plan
                      AND    grade = a.grade
                      AND    effdt <= ps_job_rec.effdt) ;
   END ;
   END LOOP;
END;
/

Trace File when optimizer is running in "Choose" mode
=====================================================

SELECT COUNT(*)
FROM
 PS_SAL_GRADE_TBL A  WHERE SETID = :b1  AND SAL_ADMIN_PLAN = :b2  AND GRADE =
  :b3  AND EFFDT =  (SELECT MAX(EFFDT)   FROM PS_SAL_GRADE_TBL  WHERE SETID =
  A.SETID  AND SAL_ADMIN_PLAN = A.SAL_ADMIN_PLAN  AND GRADE = A.GRADE  AND
  EFFDT <= :b4 )

call     count       cpu    elapsed       disk      query    current        row
s
------- ------  -------- ---------- ---------- ---------- ----------  ---------
-
Parse        1      0.00       0.00          0          0          0
0
Execute  63754      5.93       5.76          0          0          0
0
Fetch    63754    305.81     305.66          0     510028     510028       6375
4
------- ------  -------- ---------- ---------- ---------- ----------  ---------
-
total   127509    311.74     311.42          0     510028     510028       6375
4

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 22     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
  63754  SORT AGGREGATE
  63753   FILTER
 332885    INDEX FAST FULL SCAN (object id 167845)
 127506    SORT AGGREGATE
 176591     INDEX FAST FULL SCAN (object id 167845)

Trace File when optimizer is running in "Rule" mode
=====================================================

SELECT COUNT(*)
FROM
 PS_SAL_GRADE_TBL A  WHERE SETID = :b1  AND SAL_ADMIN_PLAN = :b2  AND GRADE =
  :b3  AND EFFDT =  (SELECT MAX(EFFDT)   FROM PS_SAL_GRADE_TBL  WHERE SETID =
  A.SETID  AND SAL_ADMIN_PLAN = A.SAL_ADMIN_PLAN  AND GRADE = A.GRADE  AND
  EFFDT <= :b4 )

call     count       cpu    elapsed       disk      query    current        row
s
------- ------  -------- ---------- ---------- ---------- ----------  ---------
-
Parse        1      0.01       0.01          0          0          0
0
Execute  63754      6.59       6.26          0          0          0
0
Fetch    63754     14.94      14.68          0     255314          0       6375
4
------- ------  -------- ---------- ---------- ---------- ----------  ---------
-
total   127509     21.54      20.95          0     255314          0       6375
4

Misses in library cache during parse: 1
Optimizer goal: RULE
Parsing user id: 22     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
  63754  SORT AGGREGATE
  63753   FILTER
 332885    INDEX RANGE SCAN (object id 167845)
 127506    SORT AGGREGATE
 176591     INDEX RANGE SCAN (object id 167845)

Init.ora
=========

timed_statistics                        = true
job_queue_interval                      = 30
job_queue_processes                     = 4
db_block_buffers                        = 102400
shared_pool_size                        = 104857600
large_pool_size                         = 104857600
buffer_pool_keep                       = (buffers:2560,lru_latches:4)
buffer_pool_recycle                    = (buffers:2560,lru_latches:4)
log_buffer                              = 1024000
sort_area_size                          = 2097152
sort_area_retained_size                 = 2097152
db_block_lru_latches                    = 24
dml_locks                               = 8192
enqueue_resources                       = 3000
open_cursors                            = 1000
processes                               = 100
optimizer_mode                          = choose
cursor_sharing                          = EXACT
query_rewrite_enabled                   = true
query_rewrite_integrity                 = stale_tolerated
star_transformation_enabled             = true
log_checkpoints_to_alert                = true
parallel_min_percent                    = 0
optimizer_percent_parallel              = 0
parallel_max_servers                    = 20
parallel_min_servers                    = 0
parallel_threads_per_cpu                = 4
parallel_automatic_tuning               = true
parallel_adaptive_multi_user            = true
fast_start_parallel_rollback            = HIGH
recovery_parallelism                    = 4

      and we said...
yuck, I hate SQL inside of for loops -- we should do that in a single query.
but more on that later.

Here is the deal -- the index fast full scan will use multi-block IO
(db_file_multi_block_read_count).  By default, the CBO costs physical IO and
logical IO the same.  It is not taking into consideration the fact that the
index range scan will be all logical IO and hence the fact that the fast full
scan uses multi-block IO doesn't count here.  Hence the cost it gives to the FFS
on the small index is artificially low (conversely, the cost of the index range
scan is artificially high).

So, how to change?  the two things that affect this most are:

o optimizer_index_caching - percentage of blocks expected to be found in the
buffer cache during an index hit. default of 0 implies that every (logical) LIO
is a (physical) PIO.

o optimizer_index_cost_adj - represents relative cost of PIO's for indexed
access vs full scan.  Default value of 100 indicates that an indexed access is
just as costly as a full access.

On many systems (most oltp casual query systems) -- the default values for these
parameters are totally wrong.  The default of 100 for cost adj is way too high.
The default of 0 for index caching is way too low.

I ran this simulation that achieved your same results:

create table ps_sal_grade_tbl( setid int, sal_admin_plan int, grade int, effdt
date );
create table ps_job( setid_salary int, sal_admin_plan int, grade int, effdt date
);

insert /*+ APPEND */ into ps_job
select rownum, rownum, rownum, created
  from all_objects
union all
select -rownum, -rownum, -rownum, created
  from all_objects
/
commit;

insert /*+ APPEND */ into ps_sal_grade_tbl
select setid_salary, sal_admin_plan, grade, effdt-r
  from (select ps_job.*, rownum r from ps_job  )
 where mod(r,26) = 0;
commit;

create index ps_idx1 on ps_sal_grade_tbl(setid,sal_admin_plan,grade,effdt);
analyze table ps_sal_grade_tbl compute statistics
for table for all indexes for all indexed columns;

analyze table ps_job compute statistics
for table for all indexes for all indexed columns;

alter session set sql_trace = true;

SET SERVEROUTPUT ON SIZE 1000000
SET TIME ON
SET TIMING ON
DECLARE
   v_sal_grade_cnt INT ;
BEGIN
   FOR ps_job_rec IN (SELECT *
                      FROM   ps_job) LOOP
   BEGIN

      SELECT COUNT(*)
      INTO   v_sal_grade_cnt
      FROM   ps_sal_grade_tbl a
      WHERE  setid = ps_job_rec.setid_salary
      AND    sal_admin_plan = ps_job_rec.sal_admin_plan
      AND    grade = ps_job_rec.grade
      AND    effdt = (SELECT MAX(effdt)
                      FROM   ps_sal_grade_tbl
                      WHERE  setid = a.setid
                      AND    sal_admin_plan = a.sal_admin_plan
                      AND    grade = a.grade
                      AND    effdt <= ps_job_rec.effdt) ;
   END ;
   exit;
   END LOOP;

END;
/

(note the exit is cause I have a short attention span and it took too long
otherwise ;)

So, by adding

alter session set optimizer_index_cost_adj=35;

the plan went FROM:

SELECT COUNT(*)
FROM
 PS_SAL_GRADE_TBL A  WHERE SETID = :b1  AND SAL_ADMIN_PLAN = :b2  AND GRADE =
  :b3  AND EFFDT =  (SELECT MAX(EFFDT)   FROM PS_SAL_GRADE_TBL  WHERE SETID =
  A.SETID  AND SAL_ADMIN_PLAN = A.SAL_ADMIN_PLAN  AND GRADE = A.GRADE  AND
  EFFDT <= :b4 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.01       0.01          0         10          4           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.01       0.02          0         10          4           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 594     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
      0   FILTER
      1    INDEX FAST FULL SCAN (object id 48844)
      0    SORT AGGREGATE
      0     INDEX FAST FULL SCAN (object id 48844)

To:
SELECT COUNT(*)
FROM
 PS_SAL_GRADE_TBL A  WHERE SETID = :b1  AND SAL_ADMIN_PLAN = :b2  AND GRADE =
  :b3  AND EFFDT =  (SELECT MAX(EFFDT)   FROM PS_SAL_GRADE_TBL  WHERE SETID =
  A.SETID  AND SAL_ADMIN_PLAN = A.SAL_ADMIN_PLAN  AND GRADE = A.GRADE  AND
  EFFDT <= :b4 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          2          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.01       0.01          0          2          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 594     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
      0   INDEX RANGE SCAN (object id 48844)
      0    SORT AGGREGATE
      0     FIRST ROW
      0      INDEX RANGE SCAN (MIN/MAX) (object id 48844)

You may well find that this setting asserts a positive overall effect on your
system -- you can use either (the cost adj is "more aggressive") and set it
permanently in your init.ora.

Suggest you check out:

http://www.evdbt.com/SearchIntelligenceCBO.doc
which is a pretty good paper on this topic.

Now, getting back to the other thing (my yuck comment).  I ran your full block
(with the cost adj) against my simulation and tkprof at the end said:
OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        4      0.06       0.10          0        505          0           0
Execute      5     15.68      17.02          0          0          0           4
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        9     15.74      17.12          0        505          0           4

Misses in library cache during parse: 4
Misses in library cache during execute: 1

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse      117      0.11       0.11          0         18          8           0
Execute  47486      4.11       4.24          0          0          0           0
Fetch    94987      5.05       4.41          0     146623         12       94894
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   142590      9.27       8.76          0     146641         20       94894

And then I ran my modified block (gives the same answer)
begin
    for x in ( select ps_job.*, nvl(to_number(substr(cnt,15)),0) cnt
                 from ps_job,
                      ( select setid, sal_admin_plan, grade,
max(to_char(effdt,'yyyymmddhh24miss')||cnt) cnt
                          from ( select setid, sal_admin_plan, grade, effdt,
count(*) cnt
                                   from ps_sal_grade_tbl
                                  group by setid, sal_admin_plan, grade, effdt )
                         group by setid, sal_admin_plan, grade ) b
               where ps_job.setid_salary = b.setid(+)
                 and ps_job.sal_admin_plan = b.sal_admin_plan(+)
                 and ps_job.grade = b.grade (+) )
    loop
      null;
    end loop;
end;

and tkprof said:

VERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        4      0.11       0.07          0          0          0           0
Execute      5      5.82       6.32          0          0          0           4
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        9      5.93       6.39          0          0          0           4

Misses in library cache during parse: 4
Misses in library cache during execute: 1

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse      108      0.07       0.13          0          0          8           0
Execute    150      0.06       0.01          0          0          0           0
Fetch    47651      2.49       4.09        190        967         28       47558
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    47909      2.62       4.23        190        967         36       47558

25 CPU seconds your way -- 9 if you do more work in SQL and less work yourself.

Something to consider -- look for ways to avoid doing sql inside of your loop by
rolling the sql into a single statement.

Review &
      Followup

      Rating: 4
Regarding
      referenced paper (and related questions)...  November 25,
      2002
Reviewer:  Matt  from Australia
The referenced paper identifies a query that can be used to identify the most
suitable value for optimizer_index_cost_adj. The query uses v$system_event which
requires the parameter timed_statistics to be set to true (to get timing info).

What is the performance cost of setting this value to true?

Additionally, when is it appropriate to set timed_os_statistics to true? The DB
reference (9.0.1) indicates that timed_os_statistics will have a large
performance hit, but I cannot find documentation to indicate where these stats
are stored or how they may be used.

          Followup:

The performance cost of setting timed_statistics to true is SO FAR OUTWEIGHED by
the postive return on having it on that I run all systems with it set to true.
You won't even notice it.

I've not really ever used timed_os_statistics (meaning, I haven't found that
occasion yet myself ;)

      Rating: 4
Why cost based
      optimizer is behaving poorly - comment on part 2 of the
      answer  November 30, 2002
Reviewer:  Abhijit
      Bhattacharya  from Redwood City,CA USA
Tom,
You advise about CBO really worked. PL/SQL which was taking more than 5 minutes
to execute with default values of optimizer_index_caching,
optimizer_index_cost_adj is now taking 40 seconds to complete. Thanks for the
excellent advice.
My comment pertains to section 2 of your reply (your "yuck" comment). The
modified block that you presented here may not produce the same answer as
original PL/SQL block. Here is the sample data -
DELETE FROM ps_job;
DELETE FROM ps_sal_grade_tbl ;
INSERT INTO ps_job VALUES (1,1,1,'1-Jan-1990') ;
INSERT INTO ps_sal_grade_tbl VALUES (1,1,1,'12-Dec-1991') ;

The original PL/SQL block will print value 0 (for COUNT(*)) since there is a
condition in the SELECT-INTO statement effdt <= ps_job.effdt

(SELECT MAX(effdt)
 FROM   ps_sal_grade_tbl
 WHERE  setid = a.setid
 AND    sal_admin_plan = a.sal_admin_plan
 AND    grade = a.grade
 AND    effdt <= ps_job_rec.effdt) <------

The modified PL/SQL block you presented will return 1 for COUNT(*) given the
above data set.
Do you have any other ideas by which I can rewrite the query for better
performance yet without changing its output? These tables with 'ps_' prefix came
from Peoplesoft. You are probably aware of the fact that records in large number
of Peoplesoft tables are effective dated forcing me to write "ugly" looking
SQL/PL-SQL blocks. Your suggestion will be really appreciated.

          Followup:

Try selecting a select.

select ...., (select count(*) .... )
  from ......

      Rating: 5
Default
      values  December 02, 2002
Reviewer:  Kalita
       from Bristol,UK
Is there any specific reason behind defaulting the value of
optimizer_index_caching to 0 and optimizer_index_cost_adj to 100?
The paper was interesting.

          Followup:

not that I'm aware of.

      Rating: 5
Perhaps Oracle
      Apps is the reason  December 12, 2002
Reviewer:
      Robert D. Ware  from St. Louis Mo. USA
Tom,

I was researching the possible use of these parameters on our 11i instances
until I read the following metalink article.

http://metalink.oracle.com/metalink/plsql/ml2_documents.showFrameDocument?p_database_id=NOT&p_id=169935.1

      Rating: 4
Setting of
      OPTIMIZER_INDEX_COST_ADJ   February 10, 2003

Reviewer:  Matt  from Australia
In the hotsos article it suggests using v$system event to calculate the value of
OPTIMIZER_INDEX_COST_ADJ.

This value represents the relative cost of physical IO for indexed access versus
physical IO for full table scans.

The default value of 100 indicates that an indexed access is just as costly
(100% of the cost ) as a full access.

SQL> l
  1  SELECT event, total_waits waits, total_timeouts timeouts,
  2  time_waited total_time, average_wait avg
  3  FROM V$SYSTEM_EVENT
  4  where event like 'db file s%'
  5* order by 4 desc
SQL> /

EVENT                           WAITS   TIMEOUTS TOTAL_TIME        AVG
-------------------------- ---------- ---------- ---------- ----------
db file sequential read       2153635          0    4258337          2
db file scattered read          72712          0     265319          4

In the above case:

(db file sequential read/db file scattered read)*100 = (2/4)*100 = 50% [=>
OPTIMIZER_INDEX_COST_ADJ = 50]

ie: On average physical IO for index reads takes half the time of physical IO
for full table scans. So, the cost of IO for index reads is half that for full
table scans and OPTIMIZER_INDEX_COST_ADJ should be set to 50.

Does all the above hang together?
Is 50 a typical value for an 80% DSS 20% OLTP system (currrently using RBO)?

          Followup:

Actually, 50 is where many people recommend starting and adjusting up or down as
you see fit on your system so -- sure.

      Rating: 3
Relative use of
      the two parameters  February 11, 2003
Reviewer:
      Matt  from Australia
From what I have seen in practice and read about these parameters
(optimizer_index_caching/optimizer_index_cost_adj)

The CBO is more sensitive to changes in optimizer_index_cost_adj. I plan to
leave this parameter well alone and only change optimizer_index_cost_adj.

Is this the usual way to go, or should both of these be tweaked, typically?

Is there a similar SQL query that could be used to highlight what the setting
should be for optimizer_index_caching on a system that has been running some
time? (or at least highlight the starting point for testing?

          Followup:

Actually, I go the other way on this.

index caching is subtle -- less obvious

index cost adj is like hitting it on the head with a hammer, a tad more
aggresive.

In reality if your system is running fine, you feel the queries are doing
about the right amount of work, setting these is not something you need to do.
 you do this in reaction to poorly performing situations.

I've found three values to be most useful in most cases for cost adj

o default
o 25'ish
o 10'ish

sort of like turning up the volume.

      Rating: 5
PL/SQL Cursor -
      select * from ps_job  February 11, 2003
Reviewer:
      Jerry  from Washington, DC USA
I don't do a lot of PL/SQL, but have worked quite a bit with PeopleSoft.
PS_JOB, at least in version 8, literally has hundreds of columns.  It also
typically has many thousands of rows for a medium to large company.  Our company
has 500k rows.

Would there be a benefit to using only the columns he needs from PS_JOB in the
cursor?

 FOR ps_job_rec IN (SELECT *
                      FROM   ps_job) LOOP

          Followup:

absolutely -- yes, 100%

      Rating: 5
our OLTP
      database shows a possible OPTIMIZER_INDEX_COST_ADJ > 100 !!!
        September 12, 2003
Reviewer:  William
      Brejnev  from St Louis
Our OLTP and non-OLTP databases, currently in RULE mode show the below numbers
for "db file sequential read" and "db file scattered read".  The ratio for the
OLTP one is 135 !!  Would it mean that when we move to CBO (this weekend !!) we
will need to set OPTIMIZER_INDEX_COST_ADJ to 135 ?  I'm suggesting our customer
to go with 10 for both OLTP and non-OLTP to start with.
Am I missing something ?  Do we need to run the query against the v$system_event
only AFTER moving to CBO (choose) ?  Would it give us different ratios to set
OPTIMIZER_INDEX_COST_ADJ ?

Tom, need your help here ...

Thanks !!

SELECT event, total_waits waits, total_timeouts timeouts,
time_waited total_time, average_wait avg
FROM V$SYSTEM_EVENT
where event like 'db file s%'
order by 4 desc  ;

(db file sequential read/db file scattered read)*100 = OPTIMIZER_INDEX_COST_ADJ
= 25

*********************************************************************************
*********
*********************************************************************************
*********
This is the OLTP database

SQL> connect system/######@cup1cl

EVENT                                     WAITS   TIMEOUTS           TOTAL_TIME
      AVG
-------------------------- -------------------- ---------- --------------------
----------
db file parallel read                    11,805          0                4,700
.398136383
db file parallel write                9,830,033        868           90,429,755
9.19933382
db file scattered read              217,910,796          0        6,196,974,557
28.4381255
db file sequential read           2,025,336,069          0       77,758,266,508
38.3927723
db file single write                    147,667          0               84,086
.569429866

(38/28)*100 = 135

*********************************************************************************
*********
*********************************************************************************
*********
This is not an OLTP database (keeps phone call detail records)

SQL> connect system/######@usp1cl
Connected.
SQL> /

EVENT                                     WAITS   TIMEOUTS           TOTAL_TIME
      AVG
-------------------------- -------------------- ---------- --------------------
----------
db file parallel read                     7,103          0                3,196
.449950725
db file parallel write                1,142,980         74           38,033,440
33.2756829
db file scattered read               95,004,952          0        1,714,552,258
18.0469778
db file sequential read              80,075,541          0           33,992,534
.42450583
db file single write                     51,036          0               58,247
1.14129242

(.4/18)*100 = 2

*********************************************************************************
*********
*********************************************************************************
*********
This is not an OLTP database (keeps phone call detail records)

SQL> connect system/######@usp2cl
Connected.
SQL> /

EVENT                                     WAITS   TIMEOUTS           TOTAL_TIME
      AVG
-------------------------- -------------------- ---------- --------------------
----------
db file parallel read                    12,218          0                4,774
.390734981
db file parallel write                1,730,443        138           31,969,755
18.4748963
db file scattered read               40,372,613          0        8,460,756,268
209.566725
db file sequential read              88,052,150          0           55,670,052
.632239553
db file single write                    137,113          0               70,884
.51697505

(.63/209)*100 = 0.3

*********************************************************************************
*********
*********************************************************************************
*********

          Followup:

you are moving to CBO this weekend and you are just trying to figure these
things out today?

you have a recipe for a really long week coming up -- I forecast lots of anger
and frustration.

any chance you can reschedule?  so you can

o set up a test environment
o run your stuff
o test it to SCALE
o have your performance tuning exercise performed in private, without end users
screaming at you

and then roll it out??

      Rating: 3
our OLTP
      database shows a possible OPTIMIZER_INDEX_COST_ADJ > 100 !!!
        September 17, 2003
Reviewer:  William
      Brejnev  from St Louis, MO
Tom, It seems we were able to reschedule it for 2 weeks from now.  We have done
tests against the application data layers to generate all the query plans (suing
CHOOSE) and compare them with the same query plans taken with the DB in RULE
mode.  We have seen that setting optimizer_ind_cost_adj to 10 gave us a good
response of queries making correct use of the indexes in lieu of full table
scans (note that this is mainly an OLTP DB).

Actually, my main doubts I sent you before were about :

(1) how could we have a factor of 135 for an OLTP DB ?
(2) should we run this magic query against v$system_event only after moving to
CBO or would it give the same results with RBO ?
(3) is this query against v$system_event reliable to find out what would be the
current/desired  optimizer_ind_cost_adj ?  Why it shows me 135 for an OLTP DB
and 2 for a batch DB ?
(4) and would it be better to have FIRST_ROWS with optimizer_ind_cost_adj set to
1 for an OLTP database ?  I know one big company who did it successfully.

I kept my suggestion to the customer to have CHOOSE and
optimizer_ind_cost_adj=10, for them to start their volume tests.  I don't have
means to do volume test on our local DB and we used the method of verifying the
query plans on our datalayers, as I mentioned on the top.

If there is one thing I want to avoid is the insatisfaction of our customer.
Hope our suggestion will work fine, I don't see why it wouldn't.
But I'm sure you would be able to tell me so.

Thanks again,
William Brejnev

          Followup:

1) i don't like ratios.  and as much as I hate ROT (rules of thumb) i was bold
enough to offer up two extremes in my latest book:

You should consider setting these to nondefault values for many systems, or at
least testing the two extremes:

o The default settings of OPTIMIZER_INDEX_CACHING = 0 and
OPTIMIZER_INDEX_COST_ADJ = 100. These are typically appropriate for many data
warehouse/reporting systems.

o The settings of OPTIMIZER_INDEX_CACHING = 90 and OPTIMIZER_INDEX_COST_ADJ =
25. These are typically appropriate for many transactional/OLTP systems.

2) that ratio could be used to give you a starting point (not even sure if you
have a valid ratio formula there, don't really use it).

3) i don't think so, could be, not sure, haven't really investigated it myself.

4) better?  after TESTING and being happy with the results -- it could be.

September 17, 2003 Sunil -- Thanks for the question regarding
      "sqlldr and loading a 4 byte date.", version

      You Asked
I am trying to load data in a table from external file
using sqlloader direct path method. The
file is a binary file which has date in
long format (4 bytes)

How do I specify in loader control file
the date format.

Sunil

      and we said...
If the number in the binary file is to remain a NUMBER in Oracle
(eg: it is some date you are managing in your own nmber field)
then:

LOAD DATA
REPLACE
INTO TABLE T
(a   position(1:4),
 b   position(5:8) INTEGER
)

will load it where the input datafile was generated by:

#include "stdio.h"

void main()
{
int i;

        for( i = 0; i < 10; i++ )
        {
                fwrite( "data", 4, 1, stdout );
                fwrite( &i, 4, 1, stdout );
                fwrite( "\n", 1, 1, stdout );
        }
}

for example.  If the number you are loading is in fact a NUMBER
that is the number of seconds since 1/1/1970 UTC and you want to
put it into an Oracle DATE datatype, you will not be able to use
the direct path loader since you need to apply a SQL function to
this data.  The control file for that would look like:

LOAD DATA
REPLACE
INTO TABLE T
(a   position(1:4),
 b   position(5:8) INTEGER "new_time(
to_date('01011970','ddmmyyyy')+1/24/60/60*:b,'GMT','EST')"
)

if you are in East Coast Time (adjust to your timezone if
needed).  Since that uses SQL, you cannot use the direct path
loader.

Another viable alternative is to write a small C program that
formats the data in a format SQLLDR likes (eg: converts the C
time into a string).  You can load from a named pipe like this:

$ mknod /tmp/sqlldr.dat p
$ your_c_program < input.dat > /tmp/sqlldr.dat &
$ sqlldr .... data=/tmp/sqlldr.dat direct=true

your_c_program would read input lines from stdin, format then
appropriately and print them out.  I've done this while loading
10's of millions of records with great success -- it works fast.

A last alternative (one I do not recommend due to the
complexity) is to use the new direct path loader API of OCI and
write your own loader that does whatever it needs with the data.

Review &
      Followup

      Rating: 4
loading binary
      data to oracle table  September 17, 2003
Reviewer:
      lura
Hi TOM
I met a problem to load the binary data to the Oracle table.

The file to be load is a COBOL output file.  In the file there is a binary field
like this :
       ˜¦x)
we knew the start and end position.

But we can not get the correct result, we have tried using
Integer and double (default integer length is 4 and double is 8 on my
machine--unix ), still does not work .
Do you think this is a SQL LOADER problem or how to load this binary data? (unix
and oracle 9i)
I can not find any information on thie internet.

Thank you!!!

          Followup:

so, what exactly is that binary data.

you said "we tried using integer and double" -- well they are as different as
night and day -- i cannot imagine trying both?

what is in the file and is it in ebcidic or ascii....

the devil is in the details.

September 17, 2003 Prince -- Thanks for the question regarding "how
      to traverse the tree in reverse direction? ", version 8.1.7.4

      You Asked
dear Tom,

In a hierarchical query how do I traverse the tree from the branch level to the
top?

Say in the emp->manager case, given the empno, I want to find his manager,
manager's manager etc.

thanks,

      and we said...
Just connect by differently.

select *
  from EMP
 start with EMPNO = :x
connect by prior MGR = EMPNO;

plug in the empno and there you go.
Review &
      Followup

      Rating: 5
Very good.
      Thanks.  July 23, 2002
Reviewer:  Prince

      Rating: 4
What about
      ordering?  September 15, 2003
Reviewer:
      Alla  from Boston, MA USA
Extra question on ordering.

If I run the query "as is", it'll show me the employee first, his manager next
etc

I would like to see the "big boss" first and my employee last

The only thing I could think of was "order by rownum desc"

Do you see any problems with this approach???

Thanks in advance for your help

Alla

          Followup:

i'd probably use "level" instead but yes, should work.  I'd use an inline view
to be safe about it:

select *
from (
select emp.* , rownum r
  from EMP
 start with EMPNO = :x
connect by prior MGR = EMPNO
)
order by r desc;

      Rating:
      4
Thanks  September 15, 2003
Reviewer:
      Alla  from Boston, MA USA
I would've used "level" as well, but unfortunately, there is no such a column in
the table (it was designed long before my time)

Thanks a lot!

          Followup:

level is a psuedo column associated with connect bys!  it is there like rownum
is.

      Rating:
      5
Thanks  September 15, 2003
Reviewer:
      Alla  from Boston, MA, USA
Damn it, I feel so stupid :-(

          Followup:

don't -- i learn something new about oracle each and every day -- honest.  It is
a really big topic

      Rating:
      4
Raghu  September 17, 2003
Reviewer:
      Raghu
Hi Tom,

Here is a query.

 SELECT node_id
         ,description
         ,parent_id
         ,level
     FROM tab_nodes
     START WITH node_id = 1
     CONNECT BY PRIOR node_id = parent_id
     ORDER  SIBLINGS BY description;

This query works fine with SQL*PLUS. I am using 9i database and forms9i.
But the same query gives error when i put it in forms.
Is it that ORDER SIBLINGS BY is not allowed within forms.

          Followup:

could be -- you can try using a record_group with "populate_group_from_query"
(make sure to bind -- set up a control block and dynamically execute

select .. from tab_nodes start with node_id = :block.item1 .....

then, you can access the result set in the record group.  or use EXEC_SQL.

that is what I do when the environment rejects some new sql syntax.

September 17, 2003 -- Thanks for the question regarding
      "export/import from dictionary to local managed database", version 8.1.7

      You Asked
Hi, Tom.

I want to reorganize the customer Oracle database from dictionary storage to
local managed TS with uniform size. The problem is that all users object are in
one TS and have a great difference in size. Most objects/tables are very small
(about 1000 objects < 1MB) and some are very large (20 objects > 100MB, one is
more than 1GB). This database was emailed to me as export file and I cannot
change anything in export file.
If I attempt to import this file in one local managed TS with uniform size I
have a problem to choose the extent size. When extent size to small (1MB),
Oracle cannot allocate by import enough extents for my large tables because of
maxextents 121 clause for this tables.
When extent size to big (10-20MB) to be able to allocate more than 1GB for the
largest table and not to offend against the 121 maxextents restrictions of this
table, Oracle allocates 10-20 MB for initial extent of each small table of only
few rows and have a problem with the size of tablespace: By extents size 20MB
Oracle needs more than 20GB for 1000 small objects.
To solve this problem I want to create two tablespace with different extent
size: one for small objects (uniform extent size 0,5MB) and another one for big
objects with 50MB uniform extent size.
My questions is:
1. How can I sort my tables in export file by size and than import them in two
different TS depend of the table size?
2. I have all users objects in original database in TS USERS. In my new database
I have created two TS BIGOBJECTS and SMALLOBJECTS. By import I want to avoid the
creations of USER TS in my new database and creation of any users objects in
system TS. How can I do this?
3 Is that right, that by import of full database Oracle first attempts to create
a tablespaces as in original database and put all users objects in the same TS
as in original database, than if Oracle can not create this TS it puts the user
objects in default TS of this user.
If I pre create user with default TS different from original database, how can I
avoid that full database import overwrite my user or make system to default TS
of this user when TS USERS not exist in new database.

In addition I have some more questions to understand my problems:

By import Oracle attempt to do the next (The export was made from dictionary
managed TS with COMPRESS=Y):

CREATE TABLE  PCTFREE 1" "0 PCTUSED 40 INITRANS 1 MAXTRANS 255 LOGGING
STORAGE (INITIAL 61440 NEXT 368" "64 MINEXTENTS 1 MAXEXTENTS 121 PCTINCREASE 50
FREELISTS 1 FREELIST GROUPS 1" " BUFFER_POOL DEFAULT) TABLESPACE "USERSX""

4:     What of this objects/TS storage parameters from old dictionary managed TS
(initial, next,         maxextents) are relevant for my new  LM TS?
5 . Where is MAXEXTENTS 121 clause from? Is this default of Oracle? It was not
define   maxextents 121 by creating of object in original database.
6 Is there any possibility to overwrite this maxetents clause from export in my
new  database?
7   Can different small tables share one extent in TS?
8   Would recommend to use autoalllocate clause to import objects of very
different size in one TS? What makes Oracle to allocate initial extent of 1 GB
(export with compress=Y) in those TS? Make it this one 1 GB too?

Olaf

      and we said...
1) you can do an

  imp userid=x/y full=y INDEXFILE=tables.sql

that'll just read the dmp file and create a ddl file for you.  You can get all
of the create tables from here.

Edit that file, delete the storage clauses and just change the tablespaces to be
one of the two you want.

Delete all other statements in there (create indexes and such, we just want the
create tables).

Now, run that DDL to pre-create the tables.

Now, you can run IMP userid=x/y full=y IGNORE=y

the ignore option lets the import continue on when an object it wanted to create
already exists.  It'll load right into your tables.

2) in order to avoid any stray objects getting into system -- you will:

a) revoke UNLIMITED TABLESPACE from your account (in case it has it, resource
and dba both grant this priv directly to you).

b) setup your default tablespace

c) import.  import will rewrite the create using your default tablespace.  This
works for everything EXCEPT multi-tablespace objects (tables with LOBS,
partitioned tables, index organized tables with OVERFLOW statements).  Those
objects must be created by hand first (but since you pre-created all of the
tables -- this is not an issue)

3) that is correct, it would try to put it back into the same tablespace
however, you've precreated the tables so that won't happen, it'll just use your
tables.

4) just delete the storage clauses from the create tables.  You don't want them
at all (well, maybe you want to retain pctfree/pctused).  Just use the LMT
storage allocation

5) maxextents is the old default based on an 2k blocksize (before we had
unlimited extents).  Objects in LMT's all have unlimited extents -- it is
ignored:

ops$tkyte@ORA717DEV.US.ORACLE.COM> create table t ( x int ) tablespace users
storage ( maxextents 5 );

Table created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> select table_name, max_extents from
user_tables
  2  where table_name = 'T';

TABLE_NAME                     MAX_EXTENTS
------------------------------ -----------
T                               2147483645

6) won't need to

7) if you CLUSTER them but it only makes sense to CLUSTER them in special cases.
 Read about clusters (i have a pretty good section on them in my book).  In your
case -- I doubt it.

8) i don't like autoallocate personally.  I only use uniform.  Oracle would look
at the 1gig initial request and allocate a couple of extents to satisfy that.
Again, I would suggest wacking the storage clause all together from the DDL and
just let it allocate uniformly sized extents as needed.

Review &
      Followup

      Rating: 5
A possibility
      for the poster...  October 16, 2001
Reviewer:
      Connor  from Glasgie
Rather then indexfile, it can sometimes be very useful to use 'strings' (unix)
on an "exp rows=n" dump file - mainly because the create commands are not broken
over multiple lines.  This makes removing the storage clause (with something
like sed 's/STORAGE(.*)//g' easier.

      Rating: 4
good
      tip  November 02, 2001
Reviewer:  Andrew
       from ca, usa
Connor - thanks for the useful tip

      Rating: 4
We can generate
      the script by using powerdesign tools  June 02, 2003

Reviewer:  jacky  from Shenzhen China
We can generate the script by using powerdesign tools, then import the data imp
userid=accela/aaaaa@orcl full=y ignore=y indexes=n

      Rating: 4
dmt to
      lmt  September 17, 2003
Reviewer:  karthik
       from india
please tell me what r the thing i should consider before importing data from a
dmt to lmt , is it compulsary to remove the storage clause.
thanks in advance
karthik

          Followup:

compulsary?
no

good idea?
probably

it'll take the storage clause, figure out HOW MUCH would have been allocated in
the DMT and allocate the same amount in the LMT.  if that is what you want --
thats OK, if not, remove the storage.

      Rating: 4
For
      CLOB  September 17, 2003
Reviewer:  Reader

Is there a link that I can refer to see how to import tables with CLOB datatype?
Thanks.

          Followup:

umm, it is just "imp"

nothing special for import.

the server Utilities guide documents import.

September 17, 2003 Asim Ahmed -- Thanks for the question regarding
      "Problem while executing a procedure", version 8.1.6

      You Asked
Hi Tom,

I'm executing the following procedure, which is fairly simple one but it gives
me the following error (see after code).

Structure of table 't'
----------------------

SQL> desc t
 Name                               Null?    Type
 --------------------------------- -------- ------------
 X                                  NOT NULL NUMBER
 NTN                                         VARCHAR2(20)

sample row from DUAL Table.

select
'PK-'||to_number(temp_seq.currval)||'-'||to_number(temp_seq.currval+100)||'-KHI'
from dual
SQL> /

'PK-'||TO_NUMBER(TEMP_SEQ.CURRVAL)||'-'||TO_NUMBER(TEMP_SEQ.CURRVAL+100)||'-KHI'
------------------------------
PK-500-600-KHI

create or replace procedure upd_table
is
total_rows number := 0;
begin
select to_number(temp_seq.currval) into total_rows from dual;
for count in 1..total_rows loop
update t set ntn =
'PK-'||to_number(temp_seq.currval)||'-'||to_number(temp_seq.currval+100)||'-KHI'
where x = count;
end loop;
end;
SQL> /

Procedure created.

SQL> exec upd_table;
BEGIN upd_table; END;

*
ERROR at line 1:
ORA-00934: group function is not allowed here
ORA-06512: at "ASIM.UPD_TABLE", line 7
ORA-06512: at line 1

this problem is sketched on a very small scale, infact this has to be run on
table with around 1 million rows (partitioned table with 80,000 rows/partition
having login/logout report of an ISP), so it can't be done manually.
thanks.

Asim Ahmed Khan.

      and we said...
ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2      for count in 1 .. 1
  3      loop
  4          update t set x = 1 where x = count;
  5      end loop;
  6  end;
  7  /
begin
*
ERROR at line 1:
ORA-00934: group function is not allowed here
ORA-06512: at line 4

ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2      for l_count in 1 .. 1
  3      loop
  4          update t set x = 1 where x = l_count;
  5      end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

count, being an aggregate GROUP function, is giving it some heartburn!

Review &
      Followup

      Rating: 3
returning
      clause  October 17, 2002
Reviewer:  Anuradha
       from India
Dear Tom,
   In the update statement I used returning clause to view the updated value.But
I get the following error

SQL> update emp set ename='aaa'
  2  where empno=7369 returning ename into :x;
update emp2 set ename='aaa'
*
ERROR at line 1:
ORA-00439: feature not enabled: RETURNING clause from this client type

Where should I enable it(which script should I execute to enale)?
TIA

P.S : My DB version is 8.1.7

          Followup:

PPS: Your version of sqlplus (the client) is really old.

Use a version of sqlplus that was written after Netscape was a product and
you'll be OK.  You are using the 7.3 client which doesn't understand this
construct at all.

      Rating: 3
common
      ignorance error  October 17, 2002
Reviewer:
      Asim Ahmed  from Karachi, Pakistan.
I found the response from Mr.Thomas Kyte quite useful becuase the choice of
variable names really doesn't bring one's attention to such kind of problems. as
in the problem, i used loop variable count, which is also a reserved word, but i
couldn't catch my mistake after reviewing my code numerous times(with wrath).
thanks tom.

      Rating: 5
update
      returning clause - how to get the old values  September 16,
      2003
Reviewer:  A reader
Hi Tom
Please see the following test:
t@ORA901> @test1
t@ORA901> drop table t1;

Table dropped.

t@ORA901> drop table t2;

Table dropped.

t@ORA901> create table t1 ( x varchar2(10) , y varchar2(30) );

Table created.

t@ORA901> create table t2 ( x varchar2(10) , y varchar2(30) );

Table created.

t@ORA901> insert into t1 values ( '1', 'xyz' );

1 row created.

t@ORA901> insert into t1 values ( '2', 'xyz' );

1 row created.

t@ORA901>
t@ORA901> declare
  2    l_old_x stringTable;
  3    l_new_x stringTable;
  4  begin
  5    update t1
  6     set x = x || 'new'
  7    returning x, x || 'new'
  8    bulk collect into l_old_x, l_new_x;
  9
 10    for i in 1..l_old_x.count
 11    loop
 12      dbms_output.put_line ( l_old_x (i) || ':' ||
 13        l_new_x (i) );
 14    end loop;
 15  end;
 16  /
1new:1newnew
2new:2newnew

PL/SQL procedure successfully completed.

t@ORA901> spool off
-----------

Here in l_old_x, how can I get the value before the update?

          Followup:

you cannot, we don't do that.

you would have access to the old values in a trigger only

      Rating: 5
re: previous
      question  September 16, 2003
Reviewer:
      A reader
Hi Tom
In the previous question, I need the old value
because after a set of such updates in a loop
in multiple tables - I need to insert into
another table as below (pseudo code):

for loop
  update ..
   set t1.a = t2.a
   returning t1.a, t2.a
    bulk collect into :old_a1, :new_a1;

 ...
insert into tt values (old_value, new_value)
all the old values and new values collected
 in the loop above

Thanx!

          Followup:

won't happen that way.

you'll

a) select for update the old value and rowids
b) update by rowid to the new value

if you need this sort of behaviour.

      Rating:
      5
thanx!  September 17, 2003
Reviewer:
      A reader
"won't happen that way.

you'll

a) select for update the old value and rowids
b) update by rowid to the new value

if you need this sort of behaviour"

I was thinking of the following:

for loop
  update ..
   set t1.a = t2.a
   returning t1.a
    bulk collect into :new_a1;

   other similar updates collecting
   ...the new values in different collections

At the end

insert into tt
< a select that uses the new values stored in the
  collections above to get the old values to get
  old_value, new_value>

end loop;

IS this a valid approach?

          Followup:

how do you get the old values after you updated?  maybe an autonomous
transaction that cannot see the changes?

      Rating: 5
sorry to
      complete the above post  September 17, 2003

Reviewer:  A reader
"a select that uses the new values stored in the
  collections above to get the old values to get
  old_value"

I do have the values in another table (old and new)

Thanx!

      Rating: 5
or a better way
      would be  September 17, 2003
Reviewer:
      A reader
to perform the insert first (to get one snapshot of
the old and new values) and then get
all the subsequent updates to refer to the
table inserted to get the old and new values.
)
for loop
   insert into table1
    select old_x, new_x
    from ...;

  update ( select involving the table1, t1 above to get
             new value and get the old value from t1)
   set t1.a = table1.new_value;

   other similar updates..

end loop;

      Rating: 5
thanx
      tom  September 17, 2003
Reviewer:
      A reader
"how do you get the old values after you updated?  maybe a autonomous
transaction that cannot see the changes? "

No - perhaps i should have been more clear in my question.

I have a view with old and new values
select old_value, new_value
from view1;

My logic is
begin
  update (join of table t1, view1)
  set t1.old_value = view1.new_value;

  similar update of t2, t3 etc.

  At the end I need to store the old and new values
  I have processed already in a table so that
  the next time, I dont process them again.

  insert into processed_table
  x.old_value, x.new_value;
end;

  Next time, the view will not select any of the above
  records as the view contains a join with processed_table
  to prevent this from happenning.

Now, I did not want to do a row by row processing,
that is why I did not code something like

for x in ( select old_value, new_value from view1)
loop
  update t1 set old_value = new_value
   ..
  insert into processed_table values ( old_value, new_value);

end loop;

The latest logic I have is:

  declare
    l_sysdate date default sysdate;
  begin
   insert into processed_table (
    select old_value, new_value, l_sysdate);

    update (join of table t1, processed_table )
     set t1.old_value = view1.new_value;..only for
                rows where processed_table.processed_date
                = l_sysdate
    ..
     other similar updates
  end;

This way I avoid processing row by row. I use
the insert into processed_table as a way to get
a snapshot of rows to be processed and then process
them only in all other updates.

Thanx!!!

September 17, 2003 atul -- Thanks for the question regarding
      "Tablespace Problem", version 8.0.4

      You Asked
Sir,

problem1)

I have one problem  at client site...they used exp/imp technique to get all the
tables and
now they are using sql-loader

I was called due to some error for sql-loader..
I saw error was sqlloader - 605 (which generally is a space related error)
When i put log in sqlloader command line i got real oracle error which was
saying that
quota exceeded for tablespace USERS..

The user which is doing loading has a default tablespace SBL_TABLESPACE1..

when I Saw at object level its tablespace was USERS..
i am confused here,

1)IF a default tablespace for a user is SBL_TABLESPACE1
  how objects for that user got created in USERS tablespace..
2)quota for SBL_TABLESPACE1  is unlimited for that user..
3)Why i can't see any rows from dba_ts_quotas?
4)What should i do now because some objects for that user are in USERS
TABLESPACE which is not its DEFAULT TABLESPACE AND
   Some objects are in it's default tablespace..

problem2)

I want to drop a datafile which we added in a USERS tablespace

Should following steps work
1)export all the objects from tablespace USERS
2)DROP TABLESPACE <tablespace name> INCLUDING CONTENTS;
3)Reimport all the objects to other tablespace

      and we said...
When you export something, exp creates a verbose CREATE TABLE statement.  The
create statment includes the tablespace.  hence, when you import, the object
will tend to be created in the original tablespace -- regardless of the default
tablespace for that user.

So, when they imported, the table was created and then exceeded their quota on
that tablespace ...

the default quota for a user is 0 -- if they do not have unlimited tablespace (a
privilege), they have an implied 0k quota.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:47812348053
for how to use exp/imp to move tables across tablespaces -- it directly
addresses your issue.

for problem 2, that'll work however if you read the link i just gave you --
it'll give you a script to use the alter table MOVE and alter index REBUILD
commands that can move these objects without using exp/imp -- much cleaner, much
"safer" -- but only available in 8i and up so it may not apply to you.
Review &
      Followup

      Rating:
      5
Thanks  July 23, 2002
Reviewer:
      atul  from India
Sir,

Thanks for your answers,
I got your script from the provided link...

As i want to move certail objects belongs to same user to default tablespace of
that user(some objects for that user
got created in TABLESPACE USER)

so i have to add where clause in your script...
like..
---------------------------------------------------------
set echo off

column order_col1 noprint
column order_col2 noprint

set heading off
set verify off
set feedback off
set echo off

spool tmp.sql

select decode( segment_type, 'TABLE',
                       segment_name, table_name ) order_col1,
       decode( segment_type, 'TABLE', 1, 2 ) order_col2,
      'alter ' || segment_type || ' ' || segment_name ||
      decode( segment_type, 'TABLE', ' move ', ' rebuild ' ) ||
      chr(10) ||
      ' tablespace &1 ' || chr(10) ||
      ' storage ( initial ' || initial_extent || ' next ' ||
        next_extent || chr(10) ||
      ' minextents ' || min_extents || ' maxextents ' ||
        max_extents || chr(10) ||
      ' pctincrease ' || pct_increase || ' freelists ' ||
        freelists || ');'
  from user_segments,
       (select table_name, index_name from user_indexes )
 where segment_type in ( 'TABLE', 'INDEX' )
   and segment_name = index_name (+) and
   TABLESPACE_NAME='USER'
 order by 1, 2
/

spool off

set heading on
set verify on
set feedback on
set echo on

REM UNCOMMENT TO AUTO RUN the generated commands
REM ELSE edit tmp.sql, modify as needed and run it
REM @tmp

 --- eof ----
------------------------------------------------------------

Is that ok for my requirement see the addition in BOLD letters..

also what priviledges i have to give to user so that he  will run the script
from his schema??

Thanks.
atul

          Followup:

Are you sure you can use that command -- alter table move is (as I said) new to
Oracle8i -- not in 8.0 as you say you have.

your tablespace_name = user looks just dandy -- run it and see -- it just
genreates sql -- it doe not run it.

The user just needs to have a quota on users - so they can actually create stuff
there.

      Rating:
      5
Tested  July 24, 2002
Reviewer:
      atul  from india
Sir,

Actually currently i am on oracle 8 but i wish to run this script where oracle
8i is installed..sorry for not clearing this..

I have tested the script and it's ok..
I got the output like

alter TABLE VPMS_MST move
 tablespace MASTER_L
 storage ( initial 1048576 next 1064960
 minextents 1 maxextents 99
 pctincrease 0 freelists 1);

So that i can move this table to tablespace MASTER_L

actually this user is having unlimited quota on MASTER_L it's default tablespace
and on USER tablespace where some objects are created..

so,
should i revoke unlimited quota of the user from USER TABLESPACE?? or should i
keep it as it is???

          Followup:

either or, it is up to you

      Rating: 4
oracle 9.2.0.3
      -- LONG and CLOB moving across tablespace   September 11,
      2003
Reviewer:  Baqir Hussain  from Emeryville, CA
      USA
It’s nice meeting you in the Oracle world.
We are running Oracle 9.2.0.3 on SUN e4500 with 8 CPU and 8G of RAM.
The message table contains text data and has more than ¾ of the space in the
schema.  We have over 200 tables under one tablesapce and physical size of the
datafile has grown to more than 40 G.  The message table needs to be moved to a
new tablespace.  The message table has both LONG and CLOB columns.  Alter table
move .. and/or CTAS does not work due to the LONG column and this table is a
part of the web based application.  By using method “rename” gives the following
errors when either trying to insert or copying the data:

 insert /*+ APPEND */ into message
(SUBJECT,BODY,CREATE_DATE,PERSONNEL_ID,CLIENT_ID,IS_OUTBOUND,MESSAGE_ID,TYPE,IS_B
OUNCED,CONTENT)
 select
SUBJECT,BODY,CREATE_DATE,PERSONNEL_ID,CLIENT_ID,IS_OUTBOUND,MESSAGE_ID,TYPE,IS_BO
UNCED
 ,CONTENT from message_X
SQL> /
SUBJECT,BODY,CREATE_DATE,PERSONNEL_ID,CLIENT_ID,IS_OUTBOUND,MESSAGE_ID,TYPE,IS_BO
UNCED
        *
ERROR at line 4:
ORA-00997: illegal use of LONG datatype

SQL> copy from bdr/qapass@bwalk02 to bdr/qapass@bwalk02 append message using
select * from message_X;

Array fetch/bind size is 15. (arraysize is 15)
Will commit when done. (copycommit is 0)
Maximum long size is 80. (long is 80)

CPY0012: Object datatypes cannot be copied

This table has more than 3.5 million of records and export/import takes more
than 12-15 hours.  We cannot afford 12-15 hours of down time.  Please advise how
can I achieve it with minimum down time???

SQL> desc message
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 SUBJECT                                   NOT NULL VARCHAR2(255)
 BODY                                      NOT NULL LONG
 CREATE_DATE                               NOT NULL DATE
 PERSONNEL_ID                              NOT NULL NUMBER(10)
 CLIENT_ID                                 NOT NULL NUMBER(10)
 IS_OUTBOUND                               NOT NULL NUMBER(1)
 MESSAGE_ID                                NOT NULL NUMBER(10)
 TYPE                                      NOT NULL NUMBER(1)
 IS_BOUNCED                                NOT NULL NUMBER(1)
 CONTENT                                            CLOB

          Followup:

are we allowed to fix that LONG column?

      Rating: 3
clob & long
      table move  September 11, 2003
Reviewer:  Baqir
      Hussain  from Emeryville, CA USA
Tom,
I do not understand what you mean by
"are we allowed to fix that LONG column? "
If you can fix it at database level it's okay.  But JAVA application code must
read it as LONG.  It is going to be replaced by CLOB in future some time??
Your help in this regard is highly appreciated.

          Followup:

fixing it means using a clob, can we fix it.

then we can move it.

until then, you cannot move it using fast bulk operations.  you can

o export/import it.
o write a program to do it.
o contact support to see why copy isn't working for it -- it should

      Rating: 4
clob & long
      table move   September 12, 2003
Reviewer:
      reader
Hi, You could use TO_LOB.

      Rating: 4
long table move
      across  September 12, 2003
Reviewer:  Baqir
      Hussain  from Emeryville, CA USA
Tom,
I would very much appreciate if you please show an example and/or a piece of
code that how can we achieve moving table with LONG to another tablespace with
programming.
Thanks in advance.

          Followup:

sure, i'll take the easy way, this is C

   system( "exp userid=scott/tiger tables=foo" );
   system( "imp userid=scott/tiger full=yes" );

:)

that'll be the easiest.  you would just write a program that dealt with longs
and lobs otherwise.  there are api's for both

      Rating: 3
long and clob
      move across tablespace  September 12, 2003

Reviewer:  Baqir Hussain  from Emeryville, CA USA

Tom,
thanks for the prompt reply.  As I mentioned before the table message has ~ 3.5
million of text data.  I would like to know which of the program is fast enough
to complete the job with mininum down time?

1. C ?
2. write a program that deals with long and clob ?
3. api ?

If possible please show a piece of code how to achieve # 2?
For api, where to find the info from?

          Followup:

you must have code that can read and write both longs and clobs (you have longs
and clobs, i HAVE to assume you have code that reads them and code that writes
them)

so, you already have #2.

guess I wouldn't move this object at all really.  not until you can fix the
datatype and we can just use a parallel create table as select anyway.

      Rating: 3
clob move
      across tablespaces  September 15, 2003
Reviewer:
      Baqir Hussain  from Emeryville, CA USA
Tom,
Thanks for the advise.  I spoke with the programmers and they will change Java
code to access column BODY as CLOB.

SQL> desc message
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 SUBJECT                                   NOT NULL VARCHAR2(255)
 BODY                                      NOT NULL LONG
 CREATE_DATE                               NOT NULL DATE
 PERSONNEL_ID                              NOT NULL NUMBER(10)
 CLIENT_ID                                 NOT NULL NUMBER(10)
 IS_OUTBOUND                               NOT NULL NUMBER(1)
 MESSAGE_ID                                NOT NULL NUMBER(10)
 TYPE                                      NOT NULL NUMBER(1)
 IS_BOUNCED                                NOT NULL NUMBER(1)
 CONTENT                                            CLOB

After the change has been made to Java code to access column BODY as CLOB then
What steps are required to make it work at database level?
Please explain. Thanks

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:510622111991

      Rating: 3
move CLOB
      across tablespace -- oracle 9.2  September 17, 2003

Reviewer:  Baqir Hussain  from Emeryville, CA USA

Tom,
SQL> desc message
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 SUBJECT                                   NOT NULL VARCHAR2(255)
 BODY                                      NOT NULL LONG
 CREATE_DATE                               NOT NULL DATE
 PERSONNEL_ID                              NOT NULL NUMBER(10)
 CLIENT_ID                                 NOT NULL NUMBER(10)
 IS_OUTBOUND                               NOT NULL NUMBER(1)
 MESSAGE_ID                                NOT NULL NUMBER(10)
 TYPE                                      NOT NULL NUMBER(1)
 IS_BOUNCED                                NOT NULL NUMBER(1)
 CONTENT                                            CLOB

The column (BODY  LONG) has been converted to CLOB by using the following SQL:

alter table message modify (BODY CLOB);

Now we have two CLOB in the message table:
SQL> desc message
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 SUBJECT                                   NOT NULL VARCHAR2(255)
 BODY                                      NOT NULL CLOB
 CREATE_DATE                               NOT NULL DATE
 PERSONNEL_ID                              NOT NULL NUMBER(10)
 CLIENT_ID                                 NOT NULL NUMBER(10)
 IS_OUTBOUND                               NOT NULL NUMBER(1)
 MESSAGE_ID                                NOT NULL NUMBER(10)
 TYPE                                      NOT NULL NUMBER(1)
 IS_BOUNCED                                NOT NULL NUMBER(1)
 CONTENT                                            CLOB
Now I would like to move this message table to another tablespace
message_test_TS as follows:

alter table message move tablespace message_test_TS
LOB (BODY, CONTENT) store as (tablespace message_test_TS);

1. Is it a right way to move table with two CLOBs to message_test_TS tablespace?
2. Why is the importance of LOB (...) when moving table
to another tablespace?

Thanks

          Followup:

ops$tkyte@ORA920> create table t ( x int, y clob, z clob );

Table created.

Elapsed: 00:00:00.09
ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table t move tablespace users
  2  lob(y) store as ( tablespace users )
  3  lob(z) store as ( tablespace users )
  4  /

Table altered.

the lob clause actually moves the out of line lobs -- else they stay just where
ever they were originally.

      Rating: 3
expediate clob
      mv across  September 17, 2003
Reviewer:  Baqir
      Hussain  from Emeryville, CA USA
Tom,
I tried the following on a test box to find out how long will it take to change
the column type first and then move it to another tablesspace.

The message table for this test had 141711 rows of text data

1. alter table message modify (BODY CLOB);

done in 25 minute

2. alter table message move tablespace message_test_TS
   lob(BODY)    store as (tablespace message_test_TS)
   lob(CONTENT) store as (tablespace message_test_TS);

took 1 hours and 10 minutes.

In the production system, the message table has over 3.5 million rows of text
data.  It will take several hours to change the column and then move it to
another tablespace. Several hours of production down time is not possible.
I would like to know if there is any way to get it done in few hours?
Please advise.
Thanks

September 17, 2003 Haris -- Thanks for the question regarding "Open
      cursors exceeded", version oracle 8

      You Asked
  Hai Tom,

         I have a oracle 8 production database . I am getting an
error like ' ora-01000 maximum open cursors exceeded' . I got a
solution from oracle docs.It suggests that I have to increase
the value of no.of open cursors parameter in init.ora file.
Since I am new to DBA role , I would like to ask the following
questions.

    1. Increasing the value will effect the performance

    2. Is it related to any other parameters in init.ora (for
example no. of processes etc).In other way ,is it a independant
parameter.

    3. estimated value for 'no.of open cursors ' parameter.

Best Regards,
Haris Jose

      and we said...
1) it will affect performance in that your applications will
work where right now they fail ;)   open_cursors will set a
maximum number of cursors a process can open.  If you set it to
1000 (the setting I use) Oracle will actually allocate cursors
64 at a time -- so setting this artificially high does not have
a negative impact on resource usage.

2) it is independent

3) I like to use around 500 to 1000 so I just don't have to
worry about it.  A program that leaks cursors will ultimately
hit this upper bound and you'll be able to identify those
programs that must be fixed versus those programs which need
more cursors then others.
Review & Followup

      Rating: 3
pl explain more
      on this  September 08, 2001
Reviewer:
      singireddy  from USA
I am also getting an error like ' ora-01000 maximum open cursors exceeded' How
to get rid of this error ...I am not a DBA but a aprogrammer..what to do to
avoid this error..How to stop this error..pl explain more...

          Followup:

search for

ora-01000

or

maximum open cursors exceeded

on my site here for more info.  Either open_cursors is set too low for your
application or you are not closing cursors in your application after you are
completely done using them.

      Rating: 3
Reply to follow
      up.  September 10, 2001
Reviewer:  Anthony Lai
      Cheuk Tung  from Hong Kong SAR.
If you write Java programs, you should remind yourself to open AND close the
result set and statment after finishing it. Otherwise, your program will consume
all the database cursor resources.

      Rating: 4
Spot
      on!  October 22, 2001
Reviewer:  Jeremy
      Nieuwoudt  from Melbourne, Australia
As a new DBA this sort of information is invaluable to me.  Thanks for
provinding this great service.

      Rating: 5
How about a
      case to set it all the way up?  July 08, 2002

Reviewer:  Doug  from Southbury, CT USA
Tom - I am getting an open cursors max error but it is coming from the gateway
with a ORA-2063.  This is a gateway to DB2.  Looking at document 198850.995 on
metalink, the gateway has it's very own open_cursors or hs_open_cursors
parameter that also defaults to 50 and can be set to 5000.
Very simply put, is there any reason not to set it sky high at 5000 if I am
certain there is no cursor leakage?  You choose 1000 to alert you of problems.
In this case, there are triggers being fired off in DB2 by a PL/SQL proc that
weren't there last week and I assume there's an open cursor opened for each DB2
trigger firing (I don't *know* that of course).   I'd like to set it to the
maximum rather than have to bump it up over and over again.  What do you think?

          Followup:

cursors are allocated in chunks (64 at a time) but the array itself that holds
the cursors is allocated once.  The array is relatively small (array of pointers
basically).  Say it is 8bytes at worst per entry -- the overhead is rather small
(unless it is a cursor leak that needs to be fixed and you don't find it. eg:
you end up allocating 4000 cursors before logging off -- you should have
allocated only 200 -- the additional 3800 just eat RAM whilst you are logged in)

It is a trade off.  I would increase to say 1000 and leave it at that.

      Rating: 3
Relation
      between open_cursors and session_cached_cursors  April 08,
      2003
Reviewer:  Juan Carlos Reyes P  from Bolivia

Hi Tom,
I still don't have very clear the relation between open_cursors and
session_cached_cursors
Could you please explain it.

          Followup:

open_cursor -- how many concurrently opened cursors you can have.

session_cached_cursors -- how many cached CLOSED cursors you can have.

it is hard to explain a relationship where none really exists...  The only
relationship they really have is -- they both have cursors at the end of their
name....

Consider:

ops$tkyte@ORA920> show parameter _cursors

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
open_cursors                         integer     50
session_cached_cursors               integer     100

that means, "you cannot have more then 50 open at the same time  - but we
might cache 100 of them for you off to the side..."

ops$tkyte@ORA920> @mystat cursor
ops$tkyte@ORA920> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  /
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('cursor')||'%'

NAME                                VALUE
------------------------------ ----------
opened cursors cumulative              26
opened cursors current                  9
session cursor cache hits               0
session cursor cache count             13
cursor authentications                  1

ops$tkyte@ORA920> declare
  2          type rc is ref cursor;
  3
  4          l_cursor rc;
  5  begin
  6          for i in 1 .. 100
  7          loop
  8                  for j in 1 .. 5
  9                  loop
 10                          open l_cursor for 'select * from dual xx' || i;
 11                          close l_cursor;
 12                  end loop;
 13          end loop;
 14  end;
 15  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> @mystat cursor
ops$tkyte@ORA920> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  /
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('cursor')||'%'

NAME                                VALUE
------------------------------ ----------
opened cursors cumulative             529
opened cursors current                  9
session cursor cache hits             400
session cursor cache count            100
cursor authentications                  1

that shows I've 100 cursors in my "cache" ready to be opened faster then
normal -- but I never exceeded my 50 open cursors at a time threshold.

      Rating:
      5
  April 09, 2003
Reviewer:
      A reader
:) Thanks Tom

      Rating:
      5
V$SESSION_CURSOR_CACHE  April 09, 2003

Reviewer:  Juan Carlos Reyes p  from Bolivia

As you told even not with this words is:
This statistics gives you the actual number of truely open cursors:
--For current session
select a.value, b.name
            from v$mystat a, v$statname b
           where a.statistic# = b.statistic#
             and b.name = 'opened cursors current', for current session

--For all sessions
select a.sid, a.value, b.name
      from v$sesstat a, v$statname b
     where a.statistic# = b.statistic#
       and b.name = 'opened cursors current'

Now the question is, in which way V$SESSION_CURSOR_CACHE can be used to evaluate
the parameter open_cursors?

Thanks

          Followup:

sorry -- i once again do not see the relationship between them.

They are different knobs.  This is like asking "how far to the left should I
turn the hot water knob on the sink given that the cold water knob is 25% to the
right?"

there is no sensible answer to that.  Unlike our relationship to Kevin Bacon --
there isn't necessary a relationship between every parameter.

      Rating:
      5
  April 09, 2003
Reviewer:
      A reader
Thanks

      Rating: 5
nice
      query  April 09, 2003
Reviewer:  Juan Carlos
      Reyes P  from Bolivia
Hi, A friend found a query in Metalink,
Which is your opinion about using it to evaluate the open_cursor and
session_cached_cursors value

select
  'session_cached_cursors'  parameter,
  lpad(value, 5)  value,
  decode(value, 0, '  n/a', to_char(100 * used / value, '990') || '%')  usage
from
  ( select
      max(s.value)  used
    from
      v$statname  n,
      v$sesstat  s
    where
      n.name = 'session cursor cache count' and
      s.statistic# = n.statistic#
  ),
  ( select
      value
    from
      v$parameter
    where
      name = 'session_cached_cursors'
  )
union all
select
  'open_cursors',
  lpad(value, 5),
  to_char(100 * used / value,  '990') || '%'
from
  ( select
      max(sum(s.value))  used
    from
      v$statname  n,
      v$sesstat  s
    where
      n.name in ('opened cursors current', 'session cursor cache count') and
      s.statistic# = n.statistic#
    group by
      s.sid
  ),
  ( select
      value
    from
      v$parameter
    where
      name = 'open_cursors'
  )
/

          Followup:

it reports back some ratios?  it tells you a fact, doesn't tell you anything
more.

No more useful then "your buffer cache hit ratio is 99%".

That is neither good, nor bad.  It is not relevant in itself.  There is one good
ratio, from statspack -- the soft parse ratio.  Not many other ratios mean
anything by themselves.

You set open cursors so as to not hit "max open cursors"

You set session cached cursors to help poorly written applications run a bit
better.

      Rating:
      5
  April 09, 2003
Reviewer:
      A reader
:) Thanks, personally I prefer the trigger, it gives more information.

          Followup:

a trigger?   and speaking of left field, thats where this comes from.  No idea
what logical connection a trigger has to this discussion...

      Rating: 4
cursors are
      allocated in chunks ?  May 26, 2003
Reviewer:
      A reader
Tom,

can you explain me about :

<quote>
cursors are allocated in chunks (64 at a time) but the array itself that holds
the cursors is allocated once.
</quote>

what array are you talking about?

Few more questions from what i observed when i tried to simulate your example

1) i dont see the number of cursor allocation in chunks - why?, see i started
with 9 (opened cursors cumulative) then gradually it went increased by 1....

2) in the below observation the number of open cursors (cumulative) have
exceeded 300 but i didnt get any ora-01000 maximum open cursors exceeded' error
- or is it opened cursors current i need to watch out for?

3) when will this  cursors get closed if i dont explicitly close the connection
in my program

4) i commented the close l_cursor - but still i didnt see any increase in opened
cursors current -

 please set me straight,

 Thanks for your time.

  ge$scott@workflow.us.oracle.com>select a.name, b.value from v$statname a,
v$mystat b   where a.statistic# = b.statistic#
    2  and lower(a.name) like '%' || lower('&1')||'%'
    3  ;
  Enter value for 1: cursor
  old   2: and lower(a.name) like '%' || lower('&1')||'%'
  new   2: and lower(a.name) like '%' || lower('cursor')||'%'

  NAME                                                                  VALUE
  ================================================================ ==========
  opened cursors cumulative                                                 9
  opened cursors current                                                    1
  session cursor cache hits                                                 0
  session cursor cache count                                                0
  cursor authentications                                                    3

  ge$scott@workflow.us.oracle.com>/
  Enter value for 1: cursor
  old   2: and lower(a.name) like '%' || lower('&1')||'%'
  new   2: and lower(a.name) like '%' || lower('cursor')||'%'

  NAME                                                                  VALUE
  ================================================================ ==========
  opened cursors cumulative                                                10
  opened cursors current                                                    1
  session cursor cache hits                                                 0
  session cursor cache count                                                0
  cursor authentications                                                    3

  ge$scott@workflow.us.oracle.com>/
  Enter value for 1: cursor
  old   2: and lower(a.name) like '%' || lower('&1')||'%'
  new   2: and lower(a.name) like '%' || lower('cursor')||'%'

  NAME                                                                  VALUE
  ================================================================ ==========
  opened cursors cumulative                                                11
  opened cursors current                                                    1
  session cursor cache hits                                                 0
  session cursor cache count                                                0
  cursor authentications                                                    3
  .
  .
  .
  .
  ge$scott@workflow.us.oracle.com>/
  Enter value for 1: cursor
  old   2: and lower(a.name) like '%' || lower('&1')||'%'
  new   2: and lower(a.name) like '%' || lower('cursor')||'%'

  NAME                                                                  VALUE
  ================================================================ ==========
  opened cursors cumulative                                               140
  opened cursors current                                                    1
  session cursor cache hits                                                 0
  session cursor cache count                                                0
  cursor authentications                                                   18

  ge$scott@workflow.us.oracle.com>/
  Enter value for 1:  cursor
  old   2: and lower(a.name) like '%' || lower('&1')||'%'
  new   2: and lower(a.name) like '%' || lower(' cursor')||'%'

  NAME                                                                  VALUE
  ================================================================ ==========
  opened cursors cumulative                                               176
  opened cursors current                                                    1
  session cursor cache hits                                                 0
  session cursor cache count                                                0
  .
  .
  .
  .

  ge$scott@workflow.us.oracle.com>/
  Enter value for 1: cursor
  old   2: and lower(a.name) like '%' || lower('&1')||'%'
  new   2: and lower(a.name) like '%' || lower('cursor')||'%'

  NAME                                                                  VALUE
  ================================================================ ==========
  opened cursors cumulative                                               381
  opened cursors current                                                    1

  ge$scott@workflow.us.oracle.com>sho parameter cursors

  NAME                                 TYPE    VALUE
  ==================================== ======= ==============================
  open_cursors                         integer 300
  session_cached_cursors               integer 0

ge$scott@workflow.us.oracle.com>

          Followup:

in your session, there there a fixed size array corresponding to the
open_cursors init.ora parameter.  an array of pointers.   When you open the
first cursor, we allocate 64 data structures for cursors.  when you open the
65th, we open 64 more and so on.

your OPEN cursors will increment by 1's always.  this 64 at a time is something
not readily observable by you.  It is there in order to allow you to set
open_cursors higher then you need with minimal overhead as far as ram allocation
is concerned.

in your example, you simply opened a single cursor 176 times.  you never had 176
concurrently opened cursors.  You had 1, you would never hit the limit at that
rate.

      Rating: 3
Troubleshooting
      Ora-01000  August 18, 2003
Reviewer:
      A reader
init.ora OPEN_CURSORS=512
8.1.7.4
I am troubleshoot a PRO/C program that is erring out with an ORA-01000 error. I
have reviewed the code and it looks to me that all cursors are being closed.
Open cursors is set to 512 on this system. When the program runs
--
select value from v$sesstat
where sid=7 -- this is the sid of the procedure
and statistic#=3
--
VALUE
-----
340 - this value will continue to grow until it reaches 512 then the program
errs out.
--
select
count(*)
from v$open_cursor
where sid=7
---
COUNT(*)
-------
17 -- This value does not increase as the program runs.
--
I have read many of your posts on why the 2 values are different and I think I
understand that, but I don't understand why I am getting the ORA-01000 when
v$open_cursor only returns count(*) of 17
--

          Followup:

do you have a small simulation -- one that errors out against the scott/tiger
tables in a loop?

      Rating:
      3
Reply  August 18, 2003
Reviewer:
      Doylef  from Des Moines, IA
Tom, I had opened up a tar on this, and just now received a response. Could you
comment on if this makes sense to you.

Resolution History

18-AUG-03 18:27:02 GMT

Can you easily recover from, bypass or work around the problem? = YES
Does your system or application continue normally after the problem occurs? =
YES
Are the standard features of the system or application still available; is the
loss of service minor? = YES

### Platform and O/S version, including patch set or service pack level? ###
Redhat Advanced Server 2.1

### What version and patch set level of the database are you running? ###
8.1.7.4.0

### Problem Description and its duration: ###
I am troubleshooting a C program that is erring out with an ORA-01000 error. I
have reviewed the code and it looks to me that all cursors are being closed.
Open cursors is set to 512 on this system. When the program runs
--
select value from v$sesstat
where sid=7 -- this is the sid of the procedure
and statistic#=3
--
VALUE
-----
340 - this value will continue to grow until it reaches 512 the the program
errs out.
--
select
count(*)
from v$open_cursor
where sid=7
---
COUNT(*)
-------
17 -- This value does not increase as the program runs.
--
My question is why are the 2 values different and how to I figure out what
cursors are not getting closed?
--

### Did the error generate a trace file? ###
Choose...

### Error can be generated if SQL statement is run in SQL*Plus/Server Mgr: ###
Choose...

### What is the frequency of the error? ###
Choose...

### What is the impact to your business because of this problem? ###
--

### Are you running any third-party applications? ###
--

### Has the third-party Vendor been contacted? ###
Choose...

Error : ORA-01000
Contact me via : Electronic support services

18-AUG-03 19:43:32 GMT

Hi,

You can view the open cursors from the view v$open_cursor.
select sql_text from v$open)_cursor will give you the
cursors which are open.

You would need to increase the open_cursors value in the init.ora file
to get around this problem.

Thanks,
Harish.

18-AUG-03 19:43:50 GMT

Email Update button has been pressed -- Sending email.

18-AUG-03 19:51:25 GMT

New info : OK,
v$open_cursor count does not increase.
( I repeat select count(*) from v$open_cursor for this SID always returns 17
rows)
value from v$sesstat continues to increase until it hits max open_cursors which
is 512, then the program ORA-01000's.
What am I missing?

18-AUG-03 20:11:57 GMT

Hi,

Would it be possible for you to upload the c code so that we could review it.

Thanks,
Harish.

18-AUG-03 20:12:14 GMT

Email Update button has been pressed -- Sending email.

18-AUG-03 20:39:21 GMT

Country: UNITED STATES
The customer has uploaded the following file via MetaLink:
\\Ciscodb\Projects\Judicial_Branch-State_of_Iowa\Protective_Orders\DA_line\Tar32
48051.996.zip

18-AUG-03 20:55:40 GMT

Working on this.

18-AUG-03 21:05:21 GMT

Hi,

There are two ways to workaround this ORA-01000 error. You can tune cursor
usage at the database level and at the application level.

1. Tuning at the DATABASE LEVEL

There is a parameter you can set in the init.ora that determines the number of
cursors a user can open in a session: OPEN_CURSORS.

To solve the ORA-01000 error, set the OPEN_CURSORS to a higher number . You may
need to set it to the maximum of the operating system limit.

Consequences to changing this parameter:
This parameter does not effect performance in any way but Oracle will now need
a little more memory to store the cursors.

2. Tuning at the APPLICATION LEVEL

There are three parameters that affect handling cursors at the application
level: RELEASE_CURSOR, HOLD_CURSOR, MAXOPENCURSORS. You should set these
parameters at the precompiler level.

HOLD_CURSOR by default is NO. This means that after Oracle executes a SQL
statement the links to the cursor cache, memory, and parse locks are released
and marked for reuse.

RELEASE_CURSOR by default is NO. This means that after Oracle executes a SQL
statement, the links to the cursor cache is maintained and not released.

To resolve the ORA-01000 error, you should set HOLD_CURSOR=NO and
RELEASE_CURSOR=YES. This way, after the cursors are used, Oracle will free up
the memory for other cursors.

          Followup:

can i see the test case?  send it to me.  bear in mind I'm on travel for the
next two days.

also -- if you have a teeny tiny program, that would be great.

      Rating:
      3
Sorry  August 18, 2003
Reviewer:
      doylef
I did not mean to paste the entire Tar into the document, just the last piece.

So if I understand correctly. A Pro/C program will not release cursors and value
in v$sesstat will continue to grow until it reaches OPEN_CURSORS and then
ORA-01000 out, unless you precompile with RELEASE_CURSORS=yes.

      Rating: 5
proc
      HOLD_CURSOR=YES generates "Context2: SQL-02122
      ..."  September 08, 2003
Reviewer:  Tatiana
       from 2 instances of Oracle 9.2.0.3.32
#include <stdio.h>
#include <stdlib.h>

EXEC SQL INCLUDE "sqlca.h";

static char *userToken="td123";
static char *passToken="td123";
static char *db1Name="Context1";
static char *db2Name="Context2";
static char *db1String="at1d";
static char *db2String="at2d";

void openDBContext(const char * user, const char *pass, const char *DBContext ,
const char *DBString)
{
     EXEC SQL CONNECT :user IDENTIFIED BY :pass AT :DBContext USING :DBString;
}

void isGood(const char *DBContext)
{
    EXEC SQL AT :DBContext SELECT 1 FROM DUAL;

    if (sqlca.sqlcode)
    {
        sqlca.sqlerrm.sqlerrmc[sqlca.sqlerrm.sqlerrml] = '\0';
        printf("%s: %s\n", DBContext, sqlca.sqlerrm.sqlerrmc);
    }
}

int main(int argc, char* argv[])
{
    openDBContext(userToken,passToken,db1Name,db1String); isGood(db1Name);
    openDBContext(userToken,passToken,db2Name,db2String); isGood(db2Name);
    return 0;
}

          Followup:

HOLD_CURSOR=YES and RELEASE_CURSOR=NO will keep the parsed
representation of the cursor in the SQL context area.
Hence in a multiple logon, after you disconnect one session, you
cannot connect using the same cursor name.  This is because disconnecting
causes the context area to no longer link with the SGA/PGA.
The precompiler, however, still thinks the parsed representation of
the cursor is in the cursor cache.

you need to either

a) not use this optimization, it is incompatible with what you are doing
b) use more then one cursor

      Rating: 4
closing
      cursors  September 08, 2003
Reviewer:  lenny
       from madison, wi
Okay so I have a java app running on Websphere against my oracle 8.1.7.4.1
database and I am seeing cursors left open.  This is a fairly new app and is
only in pilot with a small number of users(150) that are migrating from our C/S
app.  I wrote a quick vb.net app to parse every line of the java app and report
on unclosed connections, prepared statements, and resultsets.  The report is big
and my dev team is working on it.

I can't easily bounce the db and raise the open_cursors parameter and I would
rather not flush the shared pool.  Can you make any other short-term fix
recommendations to deal with the problem or close the cursors?

Thanks in advance.

          Followup:

you'll have to find that OTHER place you asked the same thing to see my
response.

      Rating: 4
sorry about
      that.; couldn't find it..thanks.  September 08, 2003

Reviewer:  A reader

      Rating: 5
open cursors
      doubt  September 17, 2003
Reviewer:
      A reader
If pl/sql caches some of the cursors for us then
would they be shown under "open cursor current".

In that case these open cursors are "false positives"
as far as trying to detect the "max open cursors"
problem since they are not really leaks.

Is this statement accurate!

          Followup:

ops$tkyte@ORA920> create or replace procedure p( p_more in boolean default false
)
  2  as
  3          l_x   number;
  4  begin
  5          select 1 into l_x from dual;
  6          select 2 into l_x from dual;
  7          select 3 into l_x from dual;
  8          select 4 into l_x from dual;
  9          select 5 into l_x from dual;
 10          if ( p_more )
 11          then
 12                  select 6 into l_x from dual;
 13                  select 7 into l_x from dual;
 14                  select 8 into l_x from dual;
 15                  select 9 into l_x from dual;
 16                  select 10 into l_x from dual;
 17          end if;
 18  end;
 19  /

Procedure created.

ops$tkyte@ORA920> connect /
Connected.
ops$tkyte@ORA920> @mystat cursor
ops$tkyte@ORA920> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  /
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('cursor')||'%'

NAME                                VALUE
------------------------------ ----------
opened cursors cumulative               9
opened cursors current                  1
session cursor cache hits               0
session cursor cache count              7
cursor authentications                  0

ops$tkyte@ORA920> exec p

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> @mystat cursor
ops$tkyte@ORA920> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  /
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('cursor')||'%'

NAME                                VALUE
------------------------------ ----------
opened cursors cumulative              17
opened cursors current                  6     <<<<==== +5
session cursor cache hits               1
session cursor cache count              7
cursor authentications                  5

ops$tkyte@ORA920> exec p( true )

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> @mystat cursor
ops$tkyte@ORA920> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  /
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('cursor')||'%'

NAME                                VALUE
------------------------------ ----------
opened cursors cumulative              24
opened cursors current                 11    <<<==== +5 more
session cursor cache hits               2
session cursor cache count              7
cursor authentications                 10

ops$tkyte@ORA920> exec p( true )

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> @mystat cursor
ops$tkyte@ORA920> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  /
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('cursor')||'%'

NAME                                VALUE
------------------------------ ----------
opened cursors cumulative              26
opened cursors current                 11    <<< === same +5
session cursor cache hits               3
session cursor cache count              7
cursor authentications                 11

yes, accurate

      Rating:
      5
thanx!  September 17, 2003
Reviewer:
      A reader

September 17, 2003 Raghu -- Thanks for the question regarding
      "Pl/SQL ETL ", version Oracle 9.2.0.2

      You Asked
Tom,
   Currently we  use the following structure for our ETL , Its taking  too long

    Cursor for Delta table
       Loop
            Insert  into dimension tables vaues ( cursor variables )
            insert into xref tables
            insert into fact tables
        end loop

     commit;

   The reason for this structure is to log all the errors in the process.
   Currently  its taking more than 16 hours for 140,000   records.
   It completes in about 1 minite for 300 records in delta table.

1. Ths insert statements  are variables This should not take that long ?
2. Is there anything fundamentally wrong  with the  procedure layout?
3. Does committing within the loop which you strongly oppose speed the process ?

Thanks in Advance
Raghu,J

      and we said...
I'll do this as a lifo queue:

3) NO -- in fact, it makes it go slower then it should.  Committing in a cursor
for loop for each insert does a couple of things:

  a) goes SLOW
  b) generates more REDO
  c) generates more UNDO
  d) stinks

Those are the cons, for the pros:

  NULL

cannot think of any personally...

2) yup, doesn't employ any bulk operations whatsoever.

1) do anything lots of times and it takes time.

The fastest way -- single statements all.  maybe it would be faster to validate
the data AND THEN load it.

At the very very teeny tiny least -- use bulk processing. see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3675533064673
Here I'll show a 9iR2 specific example, uses BULK_EXCEPTIONS and the ability to
insert/update from a record.  Refer to the above link for how to do this in 8i
and before:

ops$tkyte@ORA920> create table t
  2  as
  3  select *
  4    from all_objects
  5   order by object_name;

Table created.

Elapsed: 00:00:02.79
ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t2
  2  as
  3  select *
  4    from all_objects
  5   where 1=0;

Table created.

Elapsed: 00:00:00.10
ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table t2 add constraint check_object_id
  2  check ( object_id <= 31900 );

Table altered.

those are the tables to test with -- I've set it up so that 24 rows will get
rejected. Additionally -- I sorted the data by object name to make it so the
errors happen "randomly" throughout the load..

ops$tkyte@ORA920> declare
  2      l_cnt number := 0;
  3  begin
  4      for x in ( select * from t )
  5      loop
  6          begin
  7              insert into t2 values X;
  8          exception
  9              when others then dbms_output.put_line(sqlcode);
 10                               l_cnt := l_cnt+1;
 11          end;
 12          commit;
 13      end loop;
 14      dbms_output.put_line( l_cnt || ' total errors' );
 15  end;
 16  /
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
24 total errors

PL/SQL procedure successfully completed.

Elapsed: 00:00:58.69

thats you right now -- about 1 minute...

ops$tkyte@ORA920> truncate table t2;

Table truncated.

Elapsed: 00:00:01.48
ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      l_cnt number := 0;
  3  begin
  4      for x in ( select * from t )
  5      loop
  6          begin
  7              insert into t2 values X;
  8          exception
  9              when others then dbms_output.put_line(sqlcode);
 10                               l_cnt := l_cnt+1;
 11          end;
 12      end loop;
 13      commit;
 14      dbms_output.put_line( l_cnt || ' total errors' );
 15  end;
 16  /
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
-2290
24 total errors

PL/SQL procedure successfully completed.

Elapsed: 00:00:15.68

That's you with the commit in the proper location

ops$tkyte@ORA920> truncate table t2;

Table truncated.

Elapsed: 00:00:00.10
ops$tkyte@ORA920>
ops$tkyte@ORA920> DECLARE
  2      type array is table of t%rowtype index by binary_integer;
  3      data   array;
  4      errors NUMBER;
  5      dml_errors EXCEPTION;
  6      l_cnt number := 0;
  7      PRAGMA exception_init(dml_errors, -24381);
  8
  9      cursor c is select * from t;
 10  BEGIN
 11      open c;
 12      loop
 13          fetch c BULK COLLECT INTO data LIMIT 100;
 14          begin
 15             FORALL i IN 1 .. data.count SAVE EXCEPTIONS
 16                insert into t2 values data(i);
 17          EXCEPTION
 18              WHEN dml_errors THEN
 19                   errors := SQL%BULK_EXCEPTIONS.COUNT;
 20                   l_cnt := l_cnt + errors;
 21                   FOR i IN 1..errors LOOP
 22                       dbms_output.put_line
 23                       ('Error occurred during iteration ' ||
 24                         SQL%BULK_EXCEPTIONS(i).ERROR_INDEX ||
 25                        ' Oracle error is ' ||
 26                         SQL%BULK_EXCEPTIONS(i).ERROR_CODE );
 27                   end loop;
 28          end;
 29          exit when c%notfound;
 30       END LOOP;
 31       close c;
 32      dbms_output.put_line( l_cnt || ' total errors' );
 33  end;
 34  /
Error occurred during iteration 99 Oracle error is 2290
Error occurred during iteration 100 Oracle error is 2290
Error occurred during iteration 1 Oracle error is 2290
Error occurred during iteration 73 Oracle error is 2290
Error occurred during iteration 74 Oracle error is 2290
Error occurred during iteration 75 Oracle error is 2290
Error occurred during iteration 76 Oracle error is 2290
Error occurred during iteration 77 Oracle error is 2290
Error occurred during iteration 78 Oracle error is 2290
Error occurred during iteration 79 Oracle error is 2290
Error occurred during iteration 58 Oracle error is 2290
Error occurred during iteration 9 Oracle error is 2290
Error occurred during iteration 10 Oracle error is 2290
Error occurred during iteration 85 Oracle error is 2290
Error occurred during iteration 54 Oracle error is 2290
Error occurred during iteration 74 Oracle error is 2290
Error occurred during iteration 26 Oracle error is 2290
Error occurred during iteration 27 Oracle error is 2290
Error occurred during iteration 49 Oracle error is 2290
Error occurred during iteration 16 Oracle error is 2290
Error occurred during iteration 17 Oracle error is 2290
Error occurred during iteration 18 Oracle error is 2290
Error occurred during iteration 19 Oracle error is 2290
Error occurred during iteration 67 Oracle error is 2290
24 total errors

PL/SQL procedure successfully completed.

Elapsed: 00:00:01.45

and that is you with BULK processing...

ops$tkyte@ORA920> insert into t2 select * from t where object_id <= 31900;

29895 rows created.

Elapsed: 00:00:00.52

is where you could be if you can put your ETL validation into SQL itself

Looks promising for you ;)
Review & Followup

      Rating: 5
Using
      multi-table insert  March 24, 2003
Reviewer:
      Logan Palanisamy  from Santa Clara, CA USA
Tom,

How about using multi-table insert like

INSERT ALL
  INTO DIMENSION_T1 VALUES (DELTA_T.C1, .., ..)
  INTO DIMENSION_T2 VALUES (DELTA_T.Cn, .., ..)
  INTO XREF_T VALUES (DELTA_T.Cm, ..,..)
  INTO FACT_T VALUES (DELTA_T.Cr, .., ..)
SELECT * FROM DELTA_T;

C1, Cn, Cm, Cr are different columns in the DELTA_T table.
WHEN clause can be added optionally before INTO.

          Followup:

sure, even better.

      Rating:
      5
  March 24, 2003
Reviewer:  Georg
       from Germany
Hello Tom,

great example, as usual.

Is there any possibility to print out not the iteration counter but something
like the ID (primary key) of the "source row". Would be a great aid in the
search of the offending input data. Could it be used with a UPSERT (MERGE)
statement ? Thanks in advance.

Regards,
Georg

          Followup:

you can do that easily -- consider this code:

 12      loop
 13          fetch c BULK COLLECT INTO data LIMIT 100;
 14          begin
 15             FORALL i IN 1 .. data.count SAVE EXCEPTIONS
 16                insert into t2 values data(i);
 17          EXCEPTION
 18              WHEN dml_errors THEN
 19                   errors := SQL%BULK_EXCEPTIONS.COUNT;
 20                   l_cnt := l_cnt + errors;
 21                   FOR i IN 1..errors LOOP
 22                       dbms_output.put_line
 23                       ('Error occurred during iteration ' ||
 24                         SQL%BULK_EXCEPTIONS(i).ERROR_INDEX ||
 25                        ' Oracle error is ' ||
 26                         SQL%BULK_EXCEPTIONS(i).ERROR_CODE );
 27                   end loop;
 28          end;

On line 24 I am printing out the SUBSCRIPT into DATA of the offending record.

therefore:

 data( SQL%BULK_EXCEPTIONS(i).ERROR_INDEX )

contains the primary key - et.al. - of the "bad record" to do with what you
like.

I do not understand the tie in with MERGE?

      Rating: 3
ETL
        March 24, 2003
Reviewer:  Raghu  from
      NC
" How about using multi-table insert like

INSERT ALL
  INTO DIMENSION_T1 VALUES (DELTA_T.C1, .., ..)
  INTO DIMENSION_T2 VALUES (DELTA_T.Cn, .., ..)
  INTO XREF_T VALUES (DELTA_T.Cm, ..,..)
  INTO FACT_T VALUES (DELTA_T.Cr, .., ..)
SELECT * FROM DELTA_T;

C1, Cn, Cm, Cr are different columns in the DELTA_T table.
WHEN clause can be added optionally before INTO.

Followup:
sure, even better. "

what is DELTA_T ?

          Followup:

it is the delta table from the original question at the top

      Rating:
      5
DELTA_T  March 24, 2003
Reviewer:  Logan
      Palanisamy  from Santa Clara CA USA
Tom,

DELTA_T is the table in 'Cursor for Delta Table Loop' in the original posting.

      Rating: 4
Insert
      All  March 24, 2003
Reviewer:  Raghu
       from NC
So the syntax will be more like

1.  generate the Sequence values before you use insert all
  so you can use then ( Temp_id )

2. Insert all into
    dim_1(c1, c2, c3) values( c1, Dc2 , Dc3..,..)
    dim_2 ( ci1, ci2) values ( ci1, Dci2 ..... )
    select t.c1, t.ci1,
           Delta D

3. In the documentation ( 9i) Sequences cannot be used in the subquery.
         where D.Pk Column = t.Pk Column

          Followup:

so, they can be used in the values clause?

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6915127515933

      Rating: 5
Multi-table
      insert with sequence  March 24, 2003
Reviewer:
      A reader
Tom already has a solution for this. Look at

http://asktom.oracle.com/pls/ask/f?p=4950:8:175441911324638353::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:6915127515933,

      Rating: 4
How about multi
      DML in Bulk process?  March 25, 2003
Reviewer:
      Chen  from China
Hi Tom,
    Cool solution. But if I must do some other DML in the bulk process, and I
want to do something like following code I do.What can I coding with the bulk
method.
begin
   for i in somecur loop
    begin
       savepoint a;
       insert into someTable1(a,b) values (i.a,i.b);
       insert into someTable2(c,d) values (i.c,i.d);
       insert into someTable3(e,f) select e, f from souretable where col1= i.a
and col2 = i.b;
    exception
       when others then
         rollback to a;
         insert into errortable (a,b,c,d) values(i.a,i.b,i.c,i.d);
    end;
  end loop;
  commit;
end;

      Rating: 4
Another
      way  March 25, 2003
Reviewer:  Pete Scott
       from England
A way my team uses on our DWH systems is to define our data load table with all
of the required business logic (e.g product must have a valid product number).
Create an Exceptions table.
Then in our batch load:
Disable constraints.
Populate load table
Re-enable constraint with an 'Exceptions into" clause.
Process the exception rows (delete / report/ sideline or whatever - we park
ours)

Thats is it dataload done.

      Rating: 5
Pl/SQL ETL -
      Bulk MERGE  March 25, 2003
Reviewer:  Georg
       from Germany
Hi Tom,

what I meant with the MERGE: is it possible to use such a bulk mechanism for the
INSERT and UPDATE part of a MERGE command ?

Thanks in advance !

Regards,
Georg

          Followup:

there would be no need??

you take source table and merge with target table.  done.

      Rating: 4
Bulk processing
      in DW  March 25, 2003
Reviewer:  Kiro
       from BG
Hi Tom
Could you comment idea to use Bulk processing with option SAVE EXCEPTIONS to
load statistical tables?

T1 (with PK) will be loaded with data every night (app 60 000 to 100 000
records) using bulk processing from PRE_TABLE. T1 is suppose to grow up to 120
GB, and latest data will be deleted. T1 have 12 partitions (by month).
OS is Win2000 AS 4CPU 8GB.
What do you think about idea?

I was thinking for insert /+APPEND/ as select ..... but may be this way is
better !?
Thanks
kiro.

          Followup:

if you can do it in a single SQL statement -- DO IT THAT WAY.

Never write procedural code if you don't have to.

If you can do insert /*+ append */ as select, that would be the way to go.

      Rating: 4
Cursor with
      columns from different tables.  March 25, 2003

Reviewer:  kashif  from Houston, TX
Hi Tom,

In your response to the original poster, you explain how to use BULK processing
for data uploads. Frequently though I come across situations when the cursor I
need to use has columns selected from multiple tables. In those cases I am
unable to utilize the BULK processing provided, since the collection in which
the data is loaded can not be based on a cursor rowtype variable. Here's an
example of what I'm talking about (some of the errors are unrelated so please
ignore)...

create or replace
Procedure           TESTLOAD
IS
     cursor c is select * from table1, table2, dual;
     type array is table of c%rowtype index by binary_integer;
     data   array;
     errors NUMBER;
     dml_errors EXCEPTION;
     l_cnt number := 0;
     PRAGMA exception_init(dml_errors, -24381);
begin
      open c;
      loop
      fetch c BULK COLLECT INTO data LIMIT 100;
      begin
         FORALL i IN 1 .. data.count
              insert into t2 values data(i);
      end;
      end loop;
EXCEPTION
WHEN others THEN
   null;
END; -- Procedure TESTLOAD
/
sho err

13/7
PL/SQL: SQL Statement ignored

13/33
PLS-00597: expression 'DATA' in the INTO list is of wrong type  <<<--- This is
the error I am talking about.

15/19
PL/SQL: Statement ignored

16/15
PL/SQL: SQL Statement ignored

16/15
PLS-00435: DML statement without BULK In-BIND cannot be used inside FORALL

16/27
PLS-00201: identifier 'T2' must be declared

If you have any suggestions on workarounds, I would appreciate it. Thanks.

Kashif

          Followup:

ops$tkyte@ORA920> create or replace view v
  2  as
  3  select * from emp, dual;

View created.

ops$tkyte@ORA920> declare
  2       cursor c is select * from v;
  3       type array is table of v%rowtype index by binary_integer;
  4       data   array;
  5  begin
  6        open c;
  7        fetch c BULK COLLECT INTO data LIMIT 100;
  8  END;
  9  /

PL/SQL procedure successfully completed.

      Rating: 3
Restrictions on
      Bulk Collect  March 25, 2003
Reviewer:
      Craig  from Lowell, AR USA
Problem #1 is that I'm having trouble executing your block with the bulk
processing above (actually, none of your blocks above will run...odd).  I get an
ora-06550/ora-00906 "missing left parenthesis on the insert statment.

Problem #2 is that according to the link below, you can not bulk fetch from a
cursor into a collection of records.  But isn't that what your example above is
doing?

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96624/05_colls.htm#28396
Please lift the veil of my ignorance! What am I missing here?

          Followup:

probably you are using 8i -- refer to the 8i link i have there.... I'm using
quite a few new 9i features like insert a record and bulk_exceptions....

#2 where does it say that?  in 8i that would be true.  In 9i, you can.

      Rating: 4
Nope, 9i
      here  March 26, 2003
Reviewer:  Craig
       from Lowell, AR USA
Sorry about that link, Tom.  The docs I was reading were 8i docs, but the link I
gave you was for 9i.  Clearly, the link I sent you states no such restriction.
Chalk it up to sloppy browsing, I guess.

We're running 9.0.1.4.0 here.  Are those features you mentioned available on all
9i releases?

          Followup:

The bulk collect into records is new in 9iR2.... sorry about that.

      Rating: 4
Validation with
      regards to plsql bulk binds  March 26, 2003

Reviewer:  Errick  from Cincinnati, OH USA

This example is very good when looking at very simple ETL solutions, but I see a
case immediately where it may not be so quick. Typically when loading data (such
as customers) I frequently examine to see if a customer exists in the table I am
loading the data into, and determine whether or not it should be an update or an
insert (based on existence). In this case I would create two different plsql
tables indexed by binary integers. One for update and one for insert. However if
I have two "customers" in this case that are the same entity (ie same ssn) since
the object is not in the table, I would not be able to determine that one is for
update and one is for insert. I could scan through the entire set of records to
determine duplicates and order of DML, but there is a much simpler solution.
That is using the bulk bind api of Java, with its Hash Map api. Assuming that I
load the source in the database rather than the bytecode this would be much
faster for this rather typical ETL example. Just thought I would chime in on
what we though when we read the article.

          Followup:

well, have you benchmarked any of this (now that would be interesting) and why
wouldn't you just

a) use merge whenever possible
b) bulk update followed by bulk insert of leftovers otherwise?

ops$tkyte@ORA920> create table emp
  2  as
  3  select *
  4    from scott.emp
  5   where mod(empno,2) = 0;

Table created.

ops$tkyte@ORA920> update emp set sal = 0;

10 rows updated.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create type emp_rec as object (
  2   EMPNO     NUMBER(4),
  3   ENAME     VARCHAR2(10),
  4   JOB       VARCHAR2(9),
  5   MGR       NUMBER(4),
  6   HIREDATE  DATE,
  7   SAL       NUMBER(7,2),
  8   COMM      NUMBER(7,2),
  9   DEPTNO    NUMBER(2) )
 10  /

Type created.

ops$tkyte@ORA920> create type emp_array as table of emp_rec
  2  /

Type created.

ops$tkyte@ORA920> select ename, sal from emp;

ENAME             SAL
---------- ----------
JONES               0
MARTIN              0
BLAKE               0
CLARK               0
SCOTT               0
TURNER              0
ADAMS               0
JAMES               0
FORD                0
MILLER              0

10 rows selected.

ops$tkyte@ORA920> declare
  2          type    array is table of emp%rowtype index by binary_integer;
  3          l_rec   array;
  4          l_data  emp_array;
  5  begin
  6          select empno,ename,job,mgr,hiredate,sal,comm,deptno bulk collect
  7            into l_rec
  8            from scott.emp;
  9
 10          l_data := emp_array();
 11          l_data.extend( l_rec.count );
 12          for i in 1 .. l_rec.count
 13          loop
 14                  -- process record
 15                  l_data(i) := emp_rec( l_rec(i).empno, l_rec(i).ename,
l_rec(i).job,
 16                                        l_rec(i).mgr, l_rec(i).hiredate,
l_rec(i).sal,
 17                                                            l_rec(i).comm,
l_rec(i).deptno );
 18          end loop;
 19
 20          merge into EMP e1
 21          using (select * from TABLE(cast(l_data as emp_array)) ) e2
 22          on ( e2.empno = e1.empno )
 23          when matched then
 24          update set e1.sal = e2.sal
 25          when not matched then
 26          insert ( empno, ename, job, mgr, hiredate, sal, comm, deptno )
 27          values ( e2.empno, e2.ename, e2.job, e2.mgr, e2.hiredate, e2.sal,
e2.comm, e2.deptno );
 28  end;
 29  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select ename, sal from emp;

ENAME             SAL
---------- ----------
JONES            2975
MARTIN           1250
BLAKE            2850
CLARK            2450
SCOTT            3000
TURNER           1500
ADAMS            1100
JAMES             950
FORD             3000
MILLER           1300
KING             5000
WARD             1250
ALLEN            1600
SMITH             800

14 rows selected.

      Rating: 4
Why use
      collection with merge?  April 21, 2003
Reviewer:
      John  from San Jose, CA, USA
Hi Tom,

Not sure about this emp example. Why would you first load the scott.emp data
into a collection of records and then merge that collection with the local emp
table, when you can just merge scott.emp into the local emp table directly? One
single SQL is better than several procedural codes, you often say so, no?

          Followup:

yes, agreed.

but -- the prior thing seemed to be asking for "i need some procedural
processing inbetween"  that is what the procedural loop was for.

      Rating: 3
-- External
      File and End of Line --  June 02, 2003
Reviewer:
      Oracle 'User
Hi Tom ,

I have some problem about ETL again
I have Text file in format like this and it's end of line in difference position

<<TEST.TXT >>

3010401288400273472004 0000000000000000000000000000
3010401288400273477003 0000000000000000000000000000
3010401288400273478001 000009450                   TNSM07ZZ0
3010401288400273482003 000015000                   TBSM11ZZ0
3010401288400273527005 000011080                   RPZZ34BZ4
3010401288400273529001 000029650                   XPGC79ZZ0
3010401288400273534001 000016142                   TNMG38ZZ0
3010401288400273567001 000008478                   TNMG38ZZ0

I've External SQL like this

<< EXT. 1 >>

CREATE TABLE Ext_Gain_TEST (
GAIN_ORG                    varchar2    (3                ),
GAIN_LOGO                    varchar2    (3                ),
GAIN_ACCT_NMBR                    varchar2(16                ),
GAIN_INCOME                    number    (10            ),
GAIN_USR_ACCT_NBR               VARCHAR2(19),
GAIN_REF_NBR                    VARCHAR2(10)
 )
 ORGANIZATION EXTERNAL
(
   TYPE oracle_loader
   DEFAULT DIRECTORY dwh_data_dir
   ACCESS PARAMETERS
   (
      RECORDS DELIMITED BY NEWLINE
      BADFILE dwh_data_dir:'bad_Gain_ext'
      LOGFILE dwh_data_dir:'log_Gain_ext'
      FIELDS (
GAIN_ORG                    position(    1    :    3    ) char  ,
GAIN_LOGO                    position(    4    :    6    ) char  ,
GAIN_ACCT_NMBR                    position(    7    :    22    ) char
  ,
GAIN_INCOME                    position(    24    :    32    ) decimal external
,
GAIN_USR_ACCT_NBR            position(       33      :       51      ) char
NULLIF GAIN_USR_ACCT_NBR = '0000000000000000000',
GAIN_REF_NBR                  position(       52      :       60      ) char
              )
      )
 LOCATION ('TEST.TXT')
 ) REJECT LIMIT UNLIMITED;

 From this sql ,it'll return only 6 records

GAI GAI GAIN_ACCT_NMBR       GAIN_INCOME GAIN_USR_ACCT_NBR   GAIN_REF_N
--- --- ---------------- --------------- ------------------- ----------
301 040 1288400273478001            9450                     TNSM07ZZ0
301 040 1288400273482003           15000                     TBSM11ZZ0
301 040 1288400273527005           11080                     RPZZ34BZ4
301 040 1288400273529001           29650                     XPGC79ZZ0
301 040 1288400273534001           16142                     TNMG38ZZ0
301 040 1288400273567001            8478                     TNMG38ZZ0

6 rows selected.

This show that I'll loss 2 records ,which end of line on GAIN_USR_ACCT_NBR Only

But If I use others scripts

<< Ext.2 >>

CREATE TABLE Ext_Gain_TEST2 (
GAIN_ORG                    varchar2    (3                ),
GAIN_LOGO                    varchar2    (3                ),
GAIN_ACCT_NMBR                    varchar2(16                ),
GAIN_INCOME                    number    (10            ),
GAIN_USR_ACCT_NBR               VARCHAR2(19)
 )
 ORGANIZATION EXTERNAL
(
   TYPE oracle_loader
   DEFAULT DIRECTORY dwh_data_dir
   ACCESS PARAMETERS
   (
      RECORDS DELIMITED BY NEWLINE
      BADFILE dwh_data_dir:'bad_Gain_ext'
      LOGFILE dwh_data_dir:'log_Gain_ext'
      FIELDS (
GAIN_ORG                    position(    1    :    3    ) char  ,
GAIN_LOGO                    position(    4    :    6    ) char  ,
GAIN_ACCT_NMBR                    position(    7    :    22    ) char
  ,
GAIN_INCOME                    position(    24    :    32    ) decimal external
,
GAIN_USR_ACCT_NBR            position(       33      :       51      ) char
NULLIF GAIN_USR_ACCT_NBR = '0000000000000000000'
              )
      )
 LOCATION ('TEST.TXT')
 ) REJECT LIMIT UNLIMITED;

It'll return all 8 records but not have GAIN_REF_NBR

GAI GAI GAIN_ACCT_NMBR       GAIN_INCOME GAIN_USR_ACCT_NBR
--- --- ---------------- --------------- -------------------
301 040 1288400273472004               0
301 040 1288400273477003               0
301 040 1288400273478001            9450
301 040 1288400273482003           15000
301 040 1288400273527005           11080
301 040 1288400273529001           29650
301 040 1288400273534001           16142
301 040 1288400273567001            8478

8 rows selected.

How can I solves this problem ?

PS. In the previous SQL*Loader  ,not have any problem about end of line .

          Followup:

see line 17:

ops$tkyte@ORA920> CREATE TABLE Ext_Gain_TEST (
  2  GAIN_ORG                    varchar2(3),
  3  GAIN_LOGO                   varchar2(3),
  4  GAIN_ACCT_NMBR              varchar2(16),
  5  GAIN_INCOME                 number(10),
  6  GAIN_USR_ACCT_NBR           VARCHAR2(19),
  7  GAIN_REF_NBR                VARCHAR2(10)
  8  ) ORGANIZATION EXTERNAL
  9  (
 10     TYPE oracle_loader
 11     DEFAULT DIRECTORY dwh_data_dir
 12     ACCESS PARAMETERS
 13     (
 14        RECORDS DELIMITED BY NEWLINE
 15        BADFILE dwh_data_dir:'bad_Gain_ext'
 16        LOGFILE dwh_data_dir:'log_Gain_ext'
 17        FIELDS missing field values are null (
 18  GAIN_ORG           position( 1: 3) ,
 19  GAIN_LOGO          position( 4: 6) ,
 20  GAIN_ACCT_NMBR     position( 7:22) ,
 21  GAIN_INCOME        position(24:32) ,
 22  GAIN_USR_ACCT_NBR  position(33:51) NULLIF GAIN_USR_ACCT_NBR =
'0000000000000000000',
 23  GAIN_REF_NBR       position(52:60)
 24                )
 25      )
 26  LOCATION ('test.txt')
 27  ) REJECT LIMIT UNLIMITED;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from ext_gain_test;

GAI GAI GAIN_ACCT_NMBR   GAIN_INCOME GAIN_USR_ACCT_NBR   GAIN_REF_N
--- --- ---------------- ----------- ------------------- ----------
301 040 1288400273472004           0
301 040 1288400273477003           0
301 040 1288400273478001        9450                     TNSM07ZZ0
301 040 1288400273482003       15000                     TBSM11ZZ0
301 040 1288400273527005       11080                     RPZZ34BZ4
301 040 1288400273529001       29650                     XPGC79ZZ0
301 040 1288400273534001       16142                     TNMG38ZZ0
301 040 1288400273567001        8478                     TNMG38ZZ0

8 rows selected.

      Rating: 5
Thank so
      much  June 02, 2003
Reviewer:  Ora

for your consult ...

      Rating: 5
display
      data.empno  June 26, 2003
Reviewer:  Arjun
       from CA
hi: tom

is it possible to display data.empno or data.sal during forall processing
besides exception block?

12      loop
 13          fetch c BULK COLLECT INTO data LIMIT 100;
 14          begin
 15             FORALL i IN 1 .. data.count SAVE EXCEPTIONS
 16                insert into t2 values data(i);
                   ### display here / pass to function ###
 17          EXCEPTION
 18              WHEN dml_errors THEN
 19                   errors := SQL%BULK_EXCEPTIONS.COUNT;
 20                   l_cnt := l_cnt + errors;
 21                   FOR i IN 1..errors LOOP
 22                       dbms_output.put_line
 23                       ('Error occurred during iteration ' ||
 24                         SQL%BULK_EXCEPTIONS(i).ERROR_INDEX ||
 25                        ' Oracle error is ' ||
 26                         SQL%BULK_EXCEPTIONS(i).ERROR_CODE );
 27                   end loop;
 28          end;

On line 24 I am printing out the SUBSCRIPT into DATA of the offending record.

I suppose : a record type needs to be declared

type empnum is table of emp.empno%type;
empno_array empnum;

open...
fecth c bulk collect into empno_array
...

          Followup:

you have data which is a record...

you have the subscript

so.....

data( sql%bulk_exceptions(i).error_index ).empno would be the data no?

 22                       ('Error occurred during iteration ' ||
 23                         SQL%BULK_EXCEPTIONS(i).ERROR_INDEX ||
 24                        ' Oracle error is ' ||
 25                         SQL%BULK_EXCEPTIONS(i).ERROR_CODE );
 26                       dbms_output.put_line
 27                       ('bad data = ' || data(
sql%bulk_exceptions(i).error_index ).object_id );
 28                   end loop;
 29                          exit;
 30          end;
 31          exit when c%notfound;
 32       END LOOP;
 33       close c;
 34      dbms_output.put_line( l_cnt || ' total errors' );
 35  end;
 36  /
Error occurred during iteration 45 Oracle error is 2290
bad data = 33019

      Rating: 5
data.empo
      figured out  June 26, 2003
Reviewer:  arjun
       from ca
sorry tom :

this way it works :

for i in 1.. data.count
loop
 dbms_output.put_line(to_char(data(i).empno));
 ...
 pass_to_function_or_proc(data(i).empno))
 ...
end loop;

      Rating: 5
PLSQL/
      ETL  September 17, 2003
Reviewer:  RATAN
       from IND
Hi Tom,
I have bellow procedure to do insert and then update. The same I have to do for
different extractions. Could you please provide me the easy and effiecient way
to do?
Thank you very much.
procedure get_mtd_sales_amt(    p_cust_no
dwadmin.tb_sdw000_ge_gls_tmp.alt_customer_number%type,
                    p_bus_tier1 varchar2,
                    p_fm number,
                    p_fy number,
                    p_run_dt date)
    is
    begin

        get_fiscal_cal_details('C', p_run_dt, v_fw, v_fq, v_fh, v_fm, v_fy); --
current

        if v_fq = 1 then
            v_fq_s := 1;
            v_fq_e := 3;
        elsif v_fq = 2 then
            v_fq_s := 4;
            v_fq_e := 6;
        elsif v_fq = 3 then
            v_fq_s := 7;
            v_fq_e := 9;
        elsif v_fq = 4 then
            v_fq_s := 10;
            v_fq_e := 12;
        end if;

        if v_fh = 1 then
            v_fh_s := 1;
            v_fh_e := 6;
        else
            v_fh_s := 7;
            v_fh_e := 12;
        end if;

        insert into tb_sdw087_rpt_amer_wkly_sales
        (    sales_regn_cd,
            sales_regn_nam,
            cust_num, cust_nam,
            bus_team_tier1_dsc,
            product_line_dsc,
            curr_fiscal_wk_num,
            curr_fiscal_mth_num,
            curr_fiscal_qr_num,
            curr_fiscal_hy_num,
             curr_fiscal_yr_num,
            cy_mtd_sales_amt,
            cy_qtd_sales_amt,
            cy_htd_sales_amt,
            cy_ytd_sales_amt,
            py_mtd_sales_amt,
            py_qtd_sales_amt,
            py_htd_sales_amt,
            py_ytd_sales_amt
        )
        select    sales_regn_cd, sales_regn_nam,
            alt_customer_number,
            alt_customer_name,
            p_bus_tier1,
            'PLC',
            v_fw,
            v_fm,
            v_fq,
            v_fh,
            v_fy,
            sum(decode(fiscal_year,extract(year from
sysdate),nvl(daily_order_net_amount,0),0)),
            0,
            0,
            0,
            sum(decode(fiscal_year,extract(year from
sysdate),0,nvl(daily_order_net_amount,0),0)),
            0,
            0,
            0
        from    v_america_sales
        where    alt_customer_number    = p_cust_no
        and    bus_team_desc_tier1    = p_bus_tier1
        and    bus_team_desc_tier2    = 'PLC'
        and    (
            bus_team_desc_tier3    = 'Motion'
            OR
            bus_team_desc_tier3    IS NULL
            )
        and    sales_team_tier2    = 'AMERICA SALES'
        and    level_elim_flag_tier0    = 'N'
        and    bus_team_elim_flag    = 'N'
        and    fiscal_month        = p_fm
        and    fiscal_year        in (p_fy, (p_fy - 1))
        group by alt_customer_number, alt_customer_name,
        sales_regn_cd, sales_regn_nam;

        update    tb_sdw087_rpt_amer_wkly_sales
        set    (cy_qtd_sales_amt, py_qtd_sales_amt)    = (select
sum(decode(fiscal_year,extract(year from
sysdate),nvl(daily_order_net_amount,0),0)) cy,
sum(decode(fiscal_year,extract(year from
sysdate),0,nvl(daily_order_net_amount,0),0)) py
                            from    v_america_sales
                            where    alt_customer_number    = p_cust_no
                            and    bus_team_desc_tier1    = p_bus_tier1
                            and    bus_team_desc_tier2    = 'PLC'
                            and    (
                                bus_team_desc_tier3    = 'Motion'
                                OR
                                bus_team_desc_tier3    IS NULL
                                )
                            and    sales_team_tier2    = 'AMERICA SALES'
                            and    level_elim_flag_tier0    = 'N'
                            and    bus_team_elim_flag    = 'N'
                            and    fiscal_month        between v_fq_s and v_fq_e
                            and    fiscal_year        in (p_fy, (p_fy - 1))
                            group by alt_customer_number, alt_customer_name,
        sales_regn_cd, sales_regn_nam)
        where    cust_num = p_cust_no;

        update    tb_sdw087_rpt_amer_wkly_sales
        set    (cy_htd_sales_amt, py_htd_sales_amt)    = (select
sum(decode(fiscal_year,extract(year from
sysdate),nvl(daily_order_net_amount,0),0)) cy,
sum(decode(fiscal_year,extract(year from
sysdate),0,nvl(daily_order_net_amount,0),0)) py
                            from    v_america_sales
                            where    alt_customer_number    = p_cust_no
                            and    bus_team_desc_tier1    = p_bus_tier1
                            and    bus_team_desc_tier2    = 'PLC'
                            and    (
                                bus_team_desc_tier3    = 'Motion'
                                OR
                                bus_team_desc_tier3    IS NULL
                                )
                            and    sales_team_tier2    = 'AMERICA SALES'
                            and    level_elim_flag_tier0    = 'N'
                            and    bus_team_elim_flag    = 'N'
                            and    fiscal_month        between v_fh_s and v_fh_e
                            and    fiscal_year        in (p_fy, (p_fy - 1))
                            group by alt_customer_number, alt_customer_name,
        sales_regn_cd, sales_regn_nam)
        where    cust_num = p_cust_no;

        update    tb_sdw087_rpt_amer_wkly_sales
        set    (cy_ytd_sales_amt, py_ytd_sales_amt)    = (select
sum(decode(fiscal_year,extract(year from
sysdate),nvl(daily_order_net_amount,0),0)) cy,
sum(decode(fiscal_year,extract(year from
sysdate),0,nvl(daily_order_net_amount,0),0)) py
                            from    v_america_sales
                            where    alt_customer_number    = p_cust_no
                            and    bus_team_desc_tier1    = p_bus_tier1
                            and    bus_team_desc_tier2    = 'PLC'
                            and    (
                                bus_team_desc_tier3    = 'Motion'
                                OR
                                bus_team_desc_tier3    IS NULL
                                )
                            and    sales_team_tier2    = 'AMERICA SALES'
                            and    level_elim_flag_tier0    = 'N'
                            and    bus_team_elim_flag    = 'N'
                            and    fiscal_year        in (p_fy, (p_fy - 1))
                            group by alt_customer_number, alt_customer_name,
                            sales_regn_cd, sales_regn_nam)
        where    cust_num = p_cust_no;
    end get_mtd_sales_amt;

          Followup:

please, not the proper place for a new question -- that and "hey, rewrite my
code to go faster" isn't in the spirit of what I do here.

ask me "how" to do something.  ask me "how" something works.

      Rating: 5
Bulk
      processing  September 18, 2003
Reviewer:
      santhanam  from Bangalore India
Good example

      Rating: 5
Bulk
      processing  September 18, 2003
Reviewer:
      santhanam  from Bangalore India
Good example

September 17, 2003 Bob -- Thanks for the question regarding "pinning
      packages", version 8.1.6.1

      You Asked
Question on pinning packages using DBMS_SHARED_POOL.KEEP.

  A) Package A is pinned in memory at database startup (using a startup trigger)

  B) Package A is executed

  C) Package A is modified and recompiled

  Does Package A remain pinned after recompilation? If so, is the pinned code
the modified code or the original code? I've read some notes and forum postings
that indicate that the package does not
actually get pinned in the shared pool until it's compiled or executed. Is this
still true in 8.1.6.1. Please advise. Thanks in advance.

      and we said...
After a CREATE OR REPLACE -- its there, after a DROP and CREATE, no, it is not
pinned (not that we NEED to pin anything these days anyway.  pinning was used
before we had a large pool.  It is not something I recommmend doing in 8.x and
up, its just not necessary in most all cases)

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure p
  2  as
  3  begin
  4          null;
  5  end;
  6  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> column name format a20
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select owner, name, kept
  2    from v$db_object_cache
  3   where name = 'P';

OWNER      NAME                 KEP
---------- -------------------- ---
OPS$TKYTE  P                    NO

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec sys.dbms_shared_pool.keep( user || '.P',
'P' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select owner, name, kept
  2    from v$db_object_cache
  3   where name = 'P';

OWNER      NAME                 KEP
---------- -------------------- ---
OPS$TKYTE  P                    YES

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure p
  2  as
  3  begin
  4          null;
  5  end;
  6  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select owner, name, kept
  2    from v$db_object_cache
  3   where name = 'P';

OWNER      NAME                 KEP
---------- -------------------- ---
OPS$TKYTE  P                    YES

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> drop procedure p;

Procedure dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure p
  2  as
  3  begin
  4          null;
  5  end;
  6  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select owner, name, kept
  2    from v$db_object_cache
  3   where name = 'P';

OWNER      NAME                 KEP
---------- -------------------- ---
OPS$TKYTE  P                    NO

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

Review &
      Followup

      Rating: 4
Large Pool
      ???  February 03, 2002
Reviewer:
      A reader
Hi Tom,

Do u mean to ay that since the large pool takes much of the load off the Shared
pool the aging of the Pkgs and Procs withing the shared pool will be less and
hence there is no need to pin the packages.

Correct ??

Regards,
Ganesh R

          Followup:

I have this discussion in full in my book -- how memory is used.

Pinning was necessary in 7.x especially when using MTS (shared server) because
all memory for the UGA as well as the library cache came from the same place.
The shared pool (the variable size of the SGA).  Now, shared sql
(sql/plsql/library stuff) is managed in an LRU -- least recently used -- with
aging.  The older you are in the pool, the less you've been used -- out you go.
also, the shared pool was setup for small allocations, typically 4k in size or
so.

So, now we start using this shared pool for memory allocations that are NOT age
based -- our UGA memory -- out session memory.  Also, these allocations are
non-trivial in size (consider, sort_area_retained_size for example).  Now, we
have memory in the shared pool that is allocated "big" (needs big contigous
chunks) and it is needed for a duration of time -- it is not aged like library
cache data is.

This is where the problem came in.  We needed large contigous free chunks in the
shared pool - a memory pool where 4k chunks ruled AND we needed it for a fixed
period of time, not on an LRU schedule.

Well the contigous part was hard -- we would free up 4k chunks where possible
but would still have lots of 4k chunks out there.  You could have 50% of the
shared pool FREE but still get and ora-4031 trying to allocate 16k of ram (every
other 4k chunk of memory was free, every other chunk was allocated -- largest
free chunk was 4k).  The shared pool would get fragmented and flushing won't do
a thing (we need those other chunks, they aren't flushable right now).

The fixed length was bad as well.  Your session would come in an allocate 1m of
sort_area_retained_size in the shared pool, use it for a bit and then free it.
It's in an LRU so its freeable -- but not freed.  It is aged out on the LRU
method -- even though it will NEVER be used again (we are done with that result
set).  Your memory would sit there while other useful but older bits would be
aged out.

So, enter pinning as a temporary solution for some of these problems.  The
concept is pin all code in the "front" of the shared pool in a contigous fashion
(decreases the fragementation).  Also, make it non-flushable -- lets your UGA
memory flush faster.

Well, the better solution is to get that UGA memory which is big and not LRU
based into a pool that provides large allocations and does not use an LRU.
Enter the large pool.....  No need to pin as you are not mixing these allocation
types.

      Rating: 5
Nothing But
      Excellent  February 03, 2002
Reviewer:
      A reader
Thanks Tom.

Regards,
Ganesh R

      Rating: 5
what if I can't
      recompile the package?  March 14, 2002
Reviewer:
      Paul Druker  from Fairfax, VA USA
I have a situation when a package is always in use. So when I modify the package
and try to "create or replace" it, it's just sitting and waiting at SQL*Plus
prompt (I guess getting ORA-54 errors).

I guess I can solve this problem by flushing the shared pool, but it's not
appropriate for the Production system. What's the correct approach?

          Followup:

Well, updating ANYTHING in a production system that is never down is always a
problem isn't it.  Consider if this were the binary executable -- you wouldn't
be able to replace that as long as people are running it...

In 9i, we have an alter database quiesce available with the resource manager.
This'll let you "pause" the system (put people on hold in effect) so you can zap
the code.  You'll still have potential issues with "existing state of packages
has been discarded" but you'll be able to compile it.

The only think I can suggest is that you schedule downtime for upgrades in a
production system.  You would have a half hour scheduled downtime where you
shutdown, startup with restricted session, patch the application, test it, and
then remove the restricted session.  Much safer and more efficient then trying
to do a patch on a hot system.

      Rating:
      5
v$db_object_cache  May 14, 2003
Reviewer:
      A reader
Hi

My understanding of library cache is it contains parsed form of SQL, PL/SQL &
Java Classes code (and the documentation suggests the same). However my reading
about v$db_object_cache suggests me it contains more than that such as tables,
clusters, dimension etc. These are not SQL, PL/SQL nor Java Classes how can they
be in the library cache?

          Followup:

the shared pools contain lots of stuff -- it is a library cache.  they list some
"for examples" but preclude nothing from being in there.

      Rating: 5
but it
      specifically say library cache and not shared pool  May 14,
      2003
Reviewer:  A reader
Hi

The docu specifically explains about shared pool as dictionary cache and library
cache as follows (quoting)

Dictionary Cache Concepts
Information stored in the data dictionary cache includes usernames, segment
information, profile data, tablespace information, and sequence numbers. The
dictionary cache also stores descriptive information, or metadata, about schema
objects. Oracle uses this metadata when parsing SQL cursors or during the
compilation of PL/SQL programs.

Library Cache Concepts
The library cache holds executable forms of SQL cursors, PL/SQL programs, and
Java classes. This section focuses on tuning as it relates to cursors, PL/SQL
programs, and Java classes. These are collectively referred to as application
code.

The view v$db_object_cache also specifically says it's about library cache! If
it was shared pool I can understand we are probably talking about objects
metadata as well but no! It's only about library cache... Also v$db_object_cache
bases on x$kglob which is about library cache too!

      Rating:
      4
pinning  May 14, 2003
Reviewer:
      Reader  from Houston, TX
Please correct me. pinning a package means I have made sure that the compiled
version of the package is stored in shared pool so users can share. If i don't
pin, it might age out later as my other sqls and pl/sqls from the shared pool.
so why pinning is not required if i use version 8 onwards as you suggested.
also, could you clarify "No need to pin as you are not mixing these allocation
types." ok my uga will be in large pool (mts). do you mean to say that we always
can find spce for our pl/sqls in shared pool and pinning is not necessary.
Thanks.

          Followup:

the code will be loaded in 4k chunks.

pretty much every thing is.

If you use it -- it'll stay.
If you don't -- it'll go.

If you pin it -- it'll stay -- meaning, if you use it, it would stay anyway.  If
you DON'T use it, why did you pin it?

pinning was "useful" for big packages before the 4k pagesize stuff.
after that, not very useful

      Rating:
      3
v$db_object_cache  May 15, 2003
Reviewer:
      A reader
v$db_object_cache must be referring the shared pool in general and not library
cache otherwise it doesnt make sense at all because table, cluster, index etc
definitions are stored in dictionary cache and not lirary cache

          Followup:

dictionary objects are library objects -- think "generic term"

      Rating: 4
as someone
      already pointed out  May 15, 2003
Reviewer:
      A reader
Someone already referred from the documentation that the library cache holds
executable forms of SQL cursors, PL/SQL programs, and Java classes so
v$db_object_cache must be talking about the shared pool and not only library
cache!

Objects informations are stored in dictionary cache!

          Followup:

tell me, where is the dictionary cache.  they are all in the shared (variable
component) pool of the sga.

      Rating: 5
that;s my
      point!  May 16, 2003
Reviewer:
      A reader
yes my point is v$db_object_cache should be talking about shared pool and NOT
only library cache!

The reference guide says the view extracts informations in library cache but if
table, clusters, indexes etc informations are not in library cache then how on
earth can v$db_object_cache bases on library cache!

library cache ---> SQL, PL/SQL, Java

dictionary cache ---> objects, users, privileges etc informations

v$db_object_cache ---> contains info of SQL, PL/SQL, Java, objects, users,
privileges etc information so v$db_object_cache must be talking about SHARED
POOL and NOT only library cache as the documentation says!

          Followup:

it is a teeny tiny semantic and not very important distinction IMO.  It doesn't
really bother me, especially since the dictionary cache is just really a
subcache of the library cache since version 7.0 -- the ability to size and tune
the "dictionary cache" went away with the end of version 6

      Rating: 5
should I
      consider pinning packages ?  September 17, 2003

Reviewer:  a reader  from Brazil
You said :
"So, enter pinning as a temporary solution for some of these problems.  The
concept is pin all code in the "front" of the shared pool in a contigous
fashion(decreases the fragementation).  Also, make it non-flushable -- lets your
UGA memory flush faster.

Well, the better solution is to get that UGA memory which is big and not LRU
based into a pool that provides large allocations and does not use an LRU.

Enter the large pool.....  No need to pin as you are not mixing these allocation
types. "

Please consider this situation
u@j>  lsps -a
Physical Volume Size   %Used  Active  Auto  Type
hdisk0      1024MB      72     yes   yes    lv
hdisk5      1024MB      72     yes   yes    lv

u@j>  lsps -s
Total Paging Space   Percent Used
      2048MB              72%

I cannot increase memory, should I consider pinning packages ?

          Followup:

umm, no?

why do you think you should?

      Rating: 5
should I
      consider pinning packages ?  September 17, 2003

Reviewer:  a reader  from Brazil
You said :
"So, enter pinning as a temporary solution for some of these problems.  The
concept is pin all code in the "front" of the shared pool in a contigous
fashion(decreases the fragementation).  Also, make it non-flushable -- lets your
UGA memory flush faster.

Well, the better solution is to get that UGA memory which is big and not LRU
based into a pool that provides large allocations and does not use an LRU.

Enter the large pool.....  No need to pin as you are not mixing these allocation
types. "

Please consider this situation
u@j>  lsps -a
Physical Volume Size   %Used  Active  Auto  Type
hdisk0      1024MB      72     yes   yes    lv
hdisk5      1024MB      72     yes   yes    lv

u@j>  lsps -s
Total Paging Space   Percent Used
      2048MB              72%

I cannot increase memory, should I consider pinning packages ?

      Rating: 5
should I
      consider pinning packages ? (cont)  September 17, 2003

Reviewer:  a reader  from Brazil
We are using 8.1.7.4.0 with mts, and XA.

          Followup:

use the large pool, configure a shared pool

the shared pool is a nice LRU, if you use it, it'll stay.  if you do not, it'll
go.  if you pin it and don't use it, it'll just gobble up memory.

      Rating:
      5
Thanks  September 17, 2003
Reviewer:  a
      reader  from Brazil

September 17, 2003 -- Thanks for the question regarding "Can I query
      a LDAP Server using any of the supplied packages?", version 8.1.6

      You Asked
It is great that i can send mails using a PL/SQL package from within Oracle 8i.
But as my users mail-ids are in LDAP, I am wondering is there any PL/SQL package
that can retrieve data from LDAP?

Thank you

      and we said...
In Oracle8i release 3 (8.1.7) we added the DBMS_LDAP package.

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/addendum.817/a85455/packadde.htm#1020614
So, in 816 which you have -- no, 817 and up, yes...

In 816, you can use a java stored procedure to do this.
Review &
      Followup

      Rating:
      5
  January 29, 2003
Reviewer:  Don

I am tring to use the DBMS_LDAP package in 8.1.7.4.3.  I would like to query MS
Active Directory to pull in current email addresses.

The DBMS_LDAP package was not in my database.  I did find dbmsldap.sql and
pvrtldap.plb.   I ran these as internal but pvrtldap.plb returned a :

PLS-00201: identifier 'DBMS_LDAP_API_LIB' must be declared

Does anything else need to be installed?  How can I use DBMS_LDAP in 8.1.7.

Thanks

          Followup:

should have run

catldap

instead....

      Rating:
      4
  January 29, 2003
Reviewer:  Don

Ok I think I fixed the problem I had above.
I ran the following as SYS and all compiled correctly.

@c:\oracle\ora81\RDBMS\admin\prvtldh.plb
@c:\oracle\ora81\RDBMS\admin\dbmsldap.sql
@c:\oracle\ora81\RDBMS\admin\prvtldap.plb

Thanks

      Rating:
      5
  January 29, 2003
Reviewer:  Don

Tom,

Can you show me how to do a update via LDAP?

Here is what I have

table EMPLOYEE (
emp_id        number,
login_name     varchar2(25),
first_name     varchar2(50),
last_name      varchar2(50),
phone_number   varchar2(12),
smtp_email     varchar2(128));

I will need to query LDAP once a day to keep this table up to date.  It would
update records that currently exist and insert new records that do not exist.
The login_name is the distinct key in LDAP.  We use Emp_id in about twenty
different tables in our application.

Thanks for your time.

      Rating: 5
Using
      DBMS_LDAP.BIND_S  February 11, 2003
Reviewer:
      Randall
Tom,  Love your site.  Very good resource.

My question is this.  I am trying to use DBMS_LDAP to get employee information
from our MS LDAP Server.  My packing is working fine with using SIMPLE_BIND_S
but I need to use BIND_S.  I have not found any docs on the method parameter and
I will need to bind using method SSPI.

bind_s (    ld     IN SESSION,
            dn     IN VARCHAR2,
            passwd IN VARCHAR2,
            meth IN PLS_INTEGER )

I have been using a little tool called LDP to browse the LDAP server.   The bind
looks like this

res = ldap_bind_s(ld, NULL, &NtAuthIdentity, 1158); // v.3
    {NtAuthIdentity: User='NULL'; Pwd= <unavailable>; domain = 'NULL'.}
Authenticated as dn:'NULL'.

Can show how to use dbms_ldap.bind_s?

Thanks

          Followup:

I asked around and got this back:

...
Tom

In the spec of the dbms_ldap package ( $ORACLE_HOME/rdbms/admin/dbmsldap.sql )
the following constants are listed:

    /* authentication methods available */
    AUTH_NONE   CONSTANT NUMBER := 0;
    AUTH_SIMPLE CONSTANT NUMBER := 128; -- context specific + primitive
    AUTH_SASL   CONSTANT NUMBER := 163; -- v3 SASL

Hope this helps.

chris.
.............

looks like those are the types it supports.

      Rating: 4
ORA - 31202 :
      DBMS_LDAP : LDAP client/server error :No such object
        September 12, 2003
Reviewer:  Chin Ching
      Liang  from Malaysia
Dear Sir,
I am using Oracle 8.1.7 database running in Solaris and Netscape LDAP Server. I
am trying to using DBMS_LDAP package to verify the username and password of a
application user but facing the error ORA -31202 : DBMS_LDAP : LDAP
client/server error :No such object

The coding is as below :-
ldap_host := 'myldap.com.my' ;
ldap_port := '389';
ldap_user := 'chincl';
ldap_password := '123456';
my_session := DBMS_LDAP.init(ldap_host, ldap_port);
retval := DBMS_LDAP.simple_bind_s(my_session, ldap_user, ldap_passwd);

There is not problem for DBMS_LDAP.init as it is successfully return a session.
The problem now is when the DBMS_LDAP.simple_bind_s function is executed and the
password is not null (even it is a correct password) then I will get the error
msg but if the password is null then the function will return DBMS_LDAP.SUCCESS.

Could somebody tell me what is going wrong will my code or anything else that I
missed out?

Thanks in advanced.

Best Regards,
Chin Ching Liang

      Rating: 3
Same issue as
      Chin Ching  September 17, 2003
Reviewer:
      Raghu  from San Jose, CA
I am having the same problem as Chin Ching. When I pass any non-null password,
then I get an error. Why is that?

Also dbms_ldap should be able to read ANY LDAP server right? Not just the OID.
Can you confirm?

          Followup:

sorry -- i don't know, really haven't played with the ldap stuff seriously
myself.

      Rating:
      5
ldap_user  September 17, 2003
Reviewer:
      Mark A. Williams  from Indianapolis, IN USA
I could be way off here - don't have an ldap server at my disposal at the
moment... However, the ldap_user parameter to simple_bind_s should be a
distinguished name such as "cn=testuser,cn=users,dc=oracle,dc=com" or
"cn=orcladmin" or thereabouts.  It looks like the code here is just trying to
pass a "raw" user name...

Not sure if that is relevant or not.  May have to install OID just to find out
:)

- Mark

      Rating: 3
Try
      this  September 17, 2003
Reviewer:  andrew
       from ca, usa
First check the format of the "dn" for your test user in your LDAP using your
LDAP browsing tool (Netscape address book can show this info if you use that
client). I use a function similar to this to access Netscape LDAP:

FUNCTION ldap_login (
   p_user     IN   VARCHAR2,
   p_pass     IN   VARCHAR2,
   p_server   IN   VARCHAR2 DEFAULT 'myserver.xyz.com',
   p_port     IN   PLS_INTEGER DEFAULT 389
)
   RETURN NUMBER
IS
   v_sess   DBMS_LDAP.SESSION;
   v_dn     VARCHAR2 (80) := 'uid='||p_user||','||'ou=FINANCE,o=ACME Corp,c=us';
   retval   PLS_INTEGER;
   ignore   PLS_INTEGER;
   v_errm   VARCHAR2 (1000);
BEGIN
   DBMS_LDAP.use_exception := TRUE;
   v_sess                  := DBMS_LDAP.init (p_server, p_port);
   retval                  := DBMS_LDAP.simple_bind_s (v_sess, v_dn, p_pass);
   ignore                  := DBMS_LDAP.unbind_s (v_sess);
   RETURN retval; -- 0=success. check doc for other values returned
EXCEPTION
   WHEN OTHERS
   THEN
      v_errm    := SQLERRM;
      ignore    := DBMS_LDAP.unbind_s (v_sess);
      IF    v_errm LIKE '%Invalid credentials%' -- bad pass
         OR v_errm LIKE '%No such object%'      -- bad user or invalid base
      THEN
         RETURN 1;   -- try again
      ELSE
         RETURN 2;   -- see SQLERRM for more details
      END IF;
END;

See $ORACLE_HOME/ldap/demo/plsql dir for examples.

September 17, 2003 Yogeeraj -- Thanks for the question regarding
      "TABLES: Sizing for Locally Managed Tablespaces and Freelists", version
      8.1.7.4

      You Asked
Hello,

A. DMT/LMT
==========
We are currently in the process of creating new tables for a new system. We have
already created 2 Locally Managed tablespaces (1. Indexes and Primary keys. 2.
Data)

With Dictionary Managed Tablespaces, we created our tables with the following
set of parameters:
(example)
======================================================================
create table RFT860 (
                       STKCAT   varchar2(2) /*Stock Category */
                                constraint NN_RFT860_STKCAT
                                NOT NULL,
                       TRNTYP   varchar2(2) /* Transaction */
                                constraint NN_RFT860_TRNTYP
                                NOT NULL,
                       SEQCOD   number(2) /* Sequence */
                                constraint NN_RFT860_SEQCOD
                                NOT NULL,
                       STKDESC  varchar2(15), /*Stk description */
                       LCNCOD   varchar2(3), /* Unit */
                       LSTVAL   varchar2(6), /* last value*/
                       CHRCOD   varchar2(6), /* Character Generation*/
                       STATUS   varchar2(1), /* Active sts*/
                       RECDAT   date, /*Sysdate*/
                       RECID    varchar2(4), /* user id*/
                            constraint PK_RFT860
                            primary key (STKCAT,TRNTYP,SEQCOD)
                            Using Index Tablespace PFS_DMT_INDX_MEDIUM
                            Pctfree 5
                            Storage (Initial     24M
                                     Next        4M
                                     MaxExtents  240
                                     PctIncrease 0)
                )
Tablespace PFS_DMT_DATA_MEDIUM
Pctfree  5
Pctused  90
Initrans 1
Maxtrans 10
Storage ( Initial        112M
             Next        10M
             MaxExtents  240
             PctIncrease 0)
    ;
======================================================================

As in the example above, we have already sized our tables to contain data for
next two years (112M-Initial Extent). And we size the NEXT extent to be very
small so as reduce the probability of tablespace problems (unable to allocate
extents especially with tables with large initial extents).

Questions:
1. When sizing tables on Locally Managed Tablespaces, which parameters are no
longer taken into consideration?
2. What are the advantages of allowing tablespaces to autoextend?
3. How do you size your large tables? (do you always set Initial=Next?)

B. FREELIST
============
In Chapter 6, Page 200 to 202, you mention about "the usefulness of this
parameter".

1. How do i determine if my tables (with lots of concurent activity) are
suffering from the fact that not enough FREELISTs had be configured?
2. How do i "determine the maximum number of concurrent inserts or updates that
will require more space"?

Thank you for your patience and useful guidance

Best Regards
Yogeeraj

      and we said...
a1) they are all taken into consideration -- it'll use initial, next and
minextents to figure out how many extents to allocate initially to satisfy that.
 After the initial create, the storage parameters are ignored.

I would suggest you LEAVE THE STORAGE PARAMETERS OFF.  If you are going with a
simple "index tablespace" and "data tablespace" -- you might just as well go
with "a single tablespace" and put everything in it (no performance gains to be
had by separating the two structures -- none).  Then just use auto-allocation
and stripe the tablespace across as many devices as possible.

a2) you don't have to get that phone call in the middle of the night saying
"hey, I'm getting this error -- fix it"

a3) I'm lazy.  I put them in to "small", "medium" and "large" tablespaces based
on my guess as to how big or small they will end up.  Then, I let them grow.

b1) search this site for

freelist contention

b2) you use your intimate knowledge of your applications to determine that.
Review &
      Followup

      Rating: 4
block
      utilization parameters  July 15, 2002
Reviewer:
      A reader
are you concerned about these in LMT

          Followup:

if you mean pctfree and pctused -- yes, unless you are using the new 9i feature
of automatic segment space management, then only pctfree counts.

      Rating: 5
Thank you for
      the guidance.  July 16, 2002
Reviewer:
      Yogeeraj  from Mauritius
Hello,
Thank you for your guidance.

Please clear my last doubts.

A.
I have 5 new tables to create t1, t2, t3, t4 and t5 which i predict to take
about 15M, 84M, 200K, 54M and 40K of space over the next 2 years.

Will it be wise to create tables (t1,t2,t4) on the same tablespace
(tbs_lmt_medium - 1024K uniform extents) ignoring their sizes?

I will be modifying my scripts to look as follows:
===========================================================
create table RFT860 (
                       STKCAT   varchar2(2) /*Stock Category */
                                constraint NN_RFT860_STKCAT
                                NOT NULL,
                       TRNTYP   varchar2(2) /* Transaction */
                                constraint NN_RFT860_TRNTYP
                                NOT NULL,
                       SEQCOD   number(2) /* Sequence */
                                constraint NN_RFT860_SEQCOD
                                NOT NULL,
                       STKDESC  varchar2(15), /*Stk description */
                       LCNCOD   varchar2(3), /* Unit */
                       LSTVAL   varchar2(6), /* last value*/
                       CHRCOD   varchar2(6), /* Character Generation*/
                       STATUS   varchar2(1), /* Active sts*/
                       RECDAT   date, /*Sysdate*/
                       RECID    varchar2(4), /* user id*/
                            constraint PK_RFT860
                            primary key (STKCAT,TRNTYP,SEQCOD)
                            Using Index Tablespace PFS_DMT_DATA_MEDIUM
                            Pctfree 5
                )
Tablespace PFS_DMT_DATA_MEDIUM
Pctfree  5
Pctused  90
Initrans 1
Maxtrans 10
    ;
===========================================================

B.
How many tablespaces would you recommend on a typical OLTP?
------------------------------------------------------------
                 Uniform     Size    Autoextend   Striped
                 Extent              Maxsize      over disks
------------------------------------------------------------
tbs_lmt_small     64K        10M     50M          d1,d2,d3
tbs_lmt_medium    1024K      100M    1024M        d2,d3,d4
tbs_lmt_large     10M        1024M   2048M        d5,d6,d7
------------------------------------------------------------
I had 10 rather loosely connected Modules, can i review them to the following?
------------------------------------------------------------
                 Uniform     Size    Autoextend   Striped
                 Extent              Maxsize      over disks
------------------------------------------------------------
M1_tbs_lmt_small  64K        10M     50M          d1,d2,d3
M1_tbs_lmt_medium 1024K      100M    1024M        d2,d3,d4
M1_tbs_lmt_large  10M        1024M   2048M        d5,d6,d7
                    -----------
M2_tbs_lmt_small  64K        10M     50M          d5,d6,d7
M2_tbs_lmt_medium 1024K      100M    1024M        d2,d3,d4
M2_tbs_lmt_large  10M        1024M   2048M        d1,d2,d3
                    -----------
M3_tbs_lmt_small  64K        10M     50M          d2,d3,d4
M3_tbs_lmt_medium 1024K      100M    1024M        d5,d6,d7
M3_tbs_lmt_large  10M        1024M   2048M        d6,d2,d3
------------------------------------------------------------

Thank you for your precious time and guidance
Best Regards
Yogeeraj

          Followup:

a) sure

b) as many as you can afford. as many as you need.  If only OLTP meant exactly
the same thing to every person -- i mean the same amount of data, some number of
users, same number of transactions, same transaction size, etc

      Rating: 5
Thank you. Some
      people will be Delighted about this ;)  July 16, 2002

Reviewer:  Mauritius  from Mauritius
Thank you.

My collegue software developers will be delighted about this. They will be
relieved from the burden of spending hours calculating/sizing tables...

thanks a lot
Best Regards
Yogeeraj

      Rating: 3
Data and Index
      Tablespace  August 06, 2003
Reviewer:  Hien
      Nguyen  from Redmond, WA USA
Hi Tom,

You quote "I would suggest you LEAVE THE STORAGE PARAMETERS OFF.  If you are
going with a
simple "index tablespace" and "data tablespace" -- you might just as well go
with "a single tablespace" and put everything in it (no performance gains to be
had by separating the two structures -- none)."

My question is if data and index tablespace are on different spindles, is there
any performance gain at all? If so how much?

          Followup:

no, none.

your goal -- achieve even IO distribution.

your method -- up to you

index on one, data on another -- poor way to achieve it.
raid striping -- good way to achieve it.

The original goal of index on /dev1, data on /dev2 (since lost in the mists of
time, so now it is just a ritual of the religion that is "database") was to
SPREAD IO OUT.  "Hey, we read an index to read data.  If we have multiple users
-- we can get some reading indexes over there and others reading data over
here".

It never made sense in the manner of "it leaves the disk head where it was (that
'spindle' argument").  The heads never stayed there anyway -- that and an INDEX
is a data structure made up of single blocks scattered ALL OVER the place --
hence every index read moved the heads anyway.  Also, if you are using an index
to read a table -- IT TOO is using single block IO all over the place -- so, it
didn't leave the heads anywhere either.

      Rating: 4
OFA &
      Parallel Processing  August 11, 2003
Reviewer:
      Hien Nguyen  from Redmond, WA USA
So OFA is strictly for even IO distribution?  It has nothing to do with
"maximizing Oracle parallel processing architecture"?

Oracle does not parallel processing the index and data read?

Let raid controller does the parallel read/write by stripping?

If I have 4 disks, it better to do 0+1 and put both data and index on it than
have 2 mirrors and put data on one and index on the other?

If I have 8 disks, it better to do all 0+1 and put both data and index on it
than have 2 0+1 and put data on one and index on the other?

Sorry for too many questions, I don't have the tools to measure which is the
best configuration.  Maybe Oracle has some data that we can use as guideline?

          Followup:

using an index to access data is by its very nature a sequential process

step a) read index to find interesting leaf nodes
step b) take the rowid from leaf nodes and access table data

you will most likely get the most even IO distribution by simply striping the
disks -- rather then attempting to segregate data.

      Rating: 5
Dumb
      Question  August 11, 2003
Reviewer:
      A reader
Which system view tells us the type of tablespace whether LMT or DMT

          Followup:

ops$tkyte@ORA920> 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)

ops$tkyte@ORA920> select extent_management from dba_tablespaces;

EXTENT_MAN
----------
LOCAL
LOCAL
LOCAL
LOCAL
.....

      Rating: 5
dmt to
      lmt  September 17, 2003
Reviewer:  karthik
       from india
Please tell me segment_space_management column in dba_tablespaces;
karthik

          Followup:

tell you what?

      Rating: 4
seggregate date
      or stripping disks  September 17, 2003
Reviewer:
      A reader  from france
hi tom,

I thought it's more efficient to separate data from index using 2 disks
controllers in a environment with a lot of concurrent database access, even if
looking to index then getting data is a serial process. Do you mean this kind of
configuration is a "myth" as well?

pito

          Followup:

how about this -- stripe the two disks.  now you have achieved nirvana -- even
IO across your devices.

that is what "index over there" and "data over here" was an attempt to do --
balance io out.

striping would be infinitely better then separate index from data.

September 17, 2003 Pawan -- Thanks for the question regarding
      "Dictionary manages Vs Locally Managed Tablespaces", version 8.1.6

      You Asked
Tom,
    Thanks for yr help and this wonderful site. I have a very basic question. I
have read about Locally managed tablespaces but till now have worked only on
Dictionary managed tablespaces. I read about the advantages of Locally managed
tblespaces and asked my senior DBA if for the new Datawarehouse initiative (we
have started loading data) we could change tablespaces to Localy managed. He
says that there are performance issues with Locally managed tablespaces - The
more I read (including yr book) I am convinced that there seems to be no
performance issues with Locally managed tablespaces. You sure must have
experience working with both kinds and I would like to know from you experience
and vast knowledge - when should Locally managed tablespaces be used and when
not? When is it more beneficial ( performance or other wise)NOT to use Locally
managed tablespace but use Dictionary managed tablespace instead. A detailed
explanation from you will really help me understand things better.
Thanks is advance.

      and we said...
The performance issues are:

o they go faster then DMT's

there you go.

LMT's should be used everywhere. In fact a new 9i release 2 performance feature
is that for the first time SYSTEM will be a LMT as well.  In 9i R1 the DEFAULT
tablespace is LMT.  LMT's are the way to go.

There are no times when LMTs should be avoided, none.  If your Sr DBA says there
are "performance issues" please demand they quantify that with a test or provide
the location of a definitive paper that shows this.
Review &
      Followup

      Rating: 5
dispel
      myths  March 06, 2002
Reviewer:  JW
       from Colorado
Yet another "performance" myth, this time about LMTs.  DMTs can provide
opportunities for more work for the DBA.  Maybe your DBA sees more work as a
benefit.

I was told 9i R1 has a DELETE performance problem when using automatic freelist
tuning (LMT is needed for this feature).  You may want to benchmark before
turning this on.

      Rating:
      4
THanks  March 07, 2002
Reviewer:
      Pawan  from NJ
I have passed this reply to the right person and hope will be able to dispel the
myth. Thanks a lot.

      Rating: 5
Yes, but
      ....  March 12, 2002
Reviewer:  Dusan
      Valasek  from Slovakia
I am great fan of LMT, but I wish that guys from Oracle Designer (r4)
development team were such a fans too.

I got error like :
RME-02124: Failed to execute SQL statement: ALTER TABLE xt_sdd_files MODIFY
LOB (CONTENTS_BLOB) ( STORAGE ( NEXT 3M MAXEXTENTS unlimited PCTINCREASE 50
) )

I had to create traditional dictionary managed tablespace, set as default to
repository owner, move table to this tablespace and then repeate the action ...

Dusan

      Rating: 3
Disadvantages
      of LMT . Slower than DMT for INSERTs  September 17, 2003

Reviewer:  Ramasamy  from NC
LMT's should be used everywhere.
There are no times when LMTs should be avoided, none.

The above two lines are from your reply.
However, when i tried to convince my DBA's to move it to LMT
through test cases (I couldn't do it by theory/Paper/Book examples) i found
something strange.

For truncates LMT is more than 100 times faster.(about 1 million rows). But for
inserts + Commits it proved DMT is faster than LMT.

Here is the test cases. I tried with
1 (insert as select + COmmit)
2 (Bulk collect insert + Commit) -- to avoid RBS problem of above point.

Both times i see inserts are faster but commits are 1000 times slower than DMT.

When Bulk insert/Commit.

Results.
With DMT

[rthiruma@fdlxdv12 dkropman]$ cat spoolbi2.lst

PL/SQL procedure successfully completed.

Elapsed: 00:00:14.51

With LMT
[rthiruma@fdlxdv12 dkropman]$ cat spoolbilmt2.lst

PL/SQL procedure successfully completed.

Elapsed: 00:00:17.58 {LMT increaed 3 seconds to insert 180,000 rows).

When using insert as select / COmmit;

DMT BASED TEST

[rthiruma@fdlxdv12 dkropman]$ cat spoolins2out.lst
old   1: insert into pro_pos_data_tmp_6 select * from pro_pos_data_tmp_&1
new   1: insert into pro_pos_data_tmp_6 select * from pro_pos_data_tmp_2

180640 rows created.

Elapsed: 00:00:08.44

Commit complete.

Elapsed: 00:00:00.07

LMT BASED TEST

[rthiruma@fdlxdv12 dkropman]$ cat spoollmtins2out.lst
old   1: insert into lmt_pro_pos_data_tmp_6 select * from
lmt_pro_pos_data_tmp_&1
new   1: insert into lmt_pro_pos_data_tmp_6 select * from lmt_pro_pos_data_tmp_2

180640 rows created.

Elapsed: 00:00:03.55

Commit complete.

Elapsed: 00:00:01.19
[rthiruma@fdlxdv12 dkropman]$

There Insert is improved but Commit was 1000 times slower with DMT. I could not
put all my test cases. But over all
for insert it is talking more time than DMT.
We need to use BULk Collect approach because of RBS, which is proving LMT will
be Slower than DMT.

I am regular reader to your site and thanks for your help.
It will be really great if you could point me out why for COmmit it is slower.
We made sure we are working with right tablespaces (LMT / DMT) for test tables
and LMT is created with UNIFORM 1024K with initial allocation extents are same
to BOTH DMT and LMT tables.

Thank you ver much.
Ramasamy

          Followup:

me thinks your test is flawed.

perhaps you did bit DMT thing, followed by big LMT thing...  and you got hung up
on big log file sync's or checkpoint not completes.

the commit is not, will not be affected by the tablespace type.  the commit
doesn't do anything with that.

reverse the order of the test, run lmt/dmt.

I quite simply cannot reproduce your results at all -- unless i choke the log
files way back causing the lmt to wait.

      Rating: 4
Re: Yes, but
      .... March 12, 2002  September 18, 2003
Reviewer:
      Mark Gittins  from Yorkshire, UK
I too wish that Designer supported LMTs (its logged with Oracle as an
enhancement request), however, relating to Dusan's problems implementing LMT
objects via Designer-generated DDL; you can work around it by removing the
storage  definitions from the objects in the repository, then the DDL is
generated with no storage clause and at least will create okay in an LMT
tablespace.

hope this helps.

September 18, 2003 Pramod -- Thanks for the question regarding "How
      to supress Schema Name when using DBMS_METADATA ", version 9.X

      You Asked
While generating DDLS from DBMS_METADATA.get_ddl, schema User is appended to
object name. How to avoid Schema user being part of the object name.

CREATE TABLE scott.dept (dept number, dept_name varchar2(30) );

Required statement like below:

  CREATE TABLE dept (dept number, dept_name varchar2(30) );

thanks,

      and we said...
it cannot be done via the supplied "get_ddl" routine.  it will always append the
schema name.

you can use "replace" to get rid of it:

  1* select replace( dbms_metadata.get_ddl( 'TABLE', 'EMP' ), '"' || user ||
'".', '' ) from dual
ops$tkyte@ORA9I> /

REPLACE(DBMS_METADATA.GET_DDL('TABLE','EMP'),'"'||USER||'".','')
--------------------------------------------------------------------------------

  CREATE TABLE "EMP"
   (    "EMPNO" NUMBER(4,0) NOT NULL ENABLE,
        "ENAME" VARCHAR2(10),
        "JOB" VARCHAR2(9),
        "MGR" NUMBER(4,0),
        "HIREDATE" DATE,
        "SAL" NUMBER(7,2),
        "COMM" NUMBER(7,2),
        "DEPTNO" NUMBER(2,0)
   ) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 LOGGING
  STORAGE(INITIAL 524288 NEXT 524288 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCRE
ASE 0
  FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "USERS"

September 18, 2003 Darren -- Thanks for the question regarding
      "Journal tables and DDL", version 9.2.0

      You Asked
If I have a journal table (a copy of a source table which holds all previous
versions of records for auditing) is there a way to ensure any DDL performed on
the base table is automatically applied to the journal table? I realise that
good audit code would not "break" if the structure of the journal table was
different to the source but the journal woud be missing information.

TIA.

      and we said...
you could attempt to use ddl triggers and write lots of code....

or...

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96628/toc.htm
use the workspace manager and have it done for you...

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4529781014729
for a direct example.  something to consider.
September 18, 2003 Meena -- Thanks for the question regarding "How
      to insert XML data which was generated using bind variable in query into
      table of type sys.xmltype ?", version 9.0.1

      You Asked
1.How to insert XML data which was generated using bind variable in   query into
table of type sys.xmltype ?

  2. In order to use bindvariable should we go for CLOB?
  3. What is most recommened storage type for XML , Is it CLOB or Sys.XMLType
((I need to process the stored xml data later. hence i stored in XMLType than in
CLOB)
  4. How to view the whole data stored in CLOB.

I wanted to store xml data into table of which has column sys.xmltype.
This xmldata will be created based on the value which will be passed dynamically
.

create table t3 (id1 varchar2(4), result sys.xmltype);

For an example,
CREATE OR REPLACE PROCEDURE G10(P_WO_NO VARCHAR2)
IS
  VCTX  DBMS_XMLGEN.CTXHANDLE;
  RESULT sys.xmltype;
BEGIN

  VCTX := DBMS_XMLGEN.NEWCONTEXT(' SELECT
                                     WOHD.WO_NO,
                                     WOHD.CUST_ID,
                                     WOIM.VSL ,
                                     WOIM.VOY,
                                     TO_CHAR(WOIM.ETA_TI,''YYYYMMDD
HH24:MI:SS'') ETA_TI,
                                     TO_CHAR(WOHD.DEADLINE_DT,''YYYYMMDD
HH24:MI:SS'') DEADLINE_DT,
                                     WOHD.PO_NO,
                                     WOHD.CUST_REF,
                                     WOHD.ADD_REF_NO,
                                     SUBSTR(WOHD.CUST_INST,1,255) CUST_INST,
                                     WOHD.JOB_TYPE JOB_TYPE ,
                                   CAST (MULTISET (SELECT
                                                    WOPD.CNTR_NO,
                                                    WOFC.CNTR_TYPE,
                                                    WOFC.CNTR_SIZE,
                                                    WOPD.PROD_ID,
                                                    WOPD.QTY,
                                                    WOPD.UOM,
                                                    WOPD.PACKCONFIG,
                                                    WOPD.BATCH,
                                                    WOPD.STATUS,

TO_CHAR(WOPD.MFGDATE,''YYYYMMDD HH24:MI:SS'') MFGDATE,

TO_CHAR(WOPD.RECEIPTDATE,''YYYYMMDD HH24:MI:SS'') RECEIPTDATE,
                                                    WOPD.SOURCE,
                                                    WOPD.ASNNO,
                                                    WOPD.ID1,
                                                    WOPD.ID2,
                                                    WOPD.ID3,
                                                    WOPD.CIFVALUE
                                                  FROM
                                                    GBWOFCL WOFC,
                                                    GBWOPROD WOPD,
                                                    GBWOBASE WO
                                                  WHERE
                                                    WO.WO_NO = WOHD.WO_NO
                                                    AND WOPD.WO_NO(+) = WO.WO_NO
                                                    AND WOFC.WO_NO(+) =
WOPD.WO_NO
                                                    AND WOFC.CNTR_NO(+) =
WOPD.CNTR_NO )
                                   AS  IMP_PO_LIST_TYPE )
                                   FROM
                                     GBWOBASE WOHD,
                                     GBWOIMP WOIM
                                   WHERE
                                     WOHD.WO_NO = '''||P_WO_NO||'''
                                     AND WOIM.WO_NO(+) = WOHD.WO_NO ');

            RESULT := DBMS_XMLGEN.getXMLType(VCTX);

            INSERT INTO t3 (
                                            id1,result
      )
                         VALUES (
                                   1
                                 ,RESULT
                                 );

 COMMIT;
 END;
/

i'm getting the foll error when i compile
SQL> SELECT E.TRANS_REF_DATA.EXTRACT('/ROWSET/ROW/WO_NO/text()').getStringVal()
"Order No." from PNPTRANS E;
ERROR:
ORA-31011: XML parsing failed
ORA-19202: Error occurred in XML processing
LPX-00110: Warning: invalid QName
"CAST_x0028_MULTISET_x0028_SELECTWOPD.CNTR_NO_x002C_WOFC.CNTR_TYPE_x002C_WOFC.CNT
R_SIZE_x002C_WOPD.P
ROD_ID_x002C_WOPD.QTY_x002C_WOPD.UOM_x002C_WOPD.PACKCONFIG_x002C_WOPD.BATCH_x002C
_WOPD.STATUS_x002C_
TO_CHAR_x0028_WOP...
Error at line 13
ORA-06512: at "SYS.XMLTYPE", line 0
ORA-06512: at line 1

I would like to know ,
whether my approach is correct? how to view the whole content of "result"

Grateful if u could give some suggestion . Thanks a lot.

      and we said...
1) just like you would use a bind for any other input?

ops$tkyte@ORA920> create table xmldocs ( xmldoc xmltype );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> variable x varchar2(4000)
ops$tkyte@ORA920> exec :x := '<?xml version="1.0"?> <Record> <Type>VERIFY</Type>
<Source>SSL4.5</Source> <Instrument>ZZZZ.S</Instrument> <F> <N>1</N> <V>4022</V>
</F> <F> <N>2</N> <V>116</V> </F> </Record>'

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into xmldocs (xmldoc)
  2  values (xmltype.createxml(:x));

1 row created.

2) if the xml is larger then 4000 bytes, you'll definitely be looking at clobs,
yes.

3) there isn't a recommended way -- just alternatives (if there were a universal
"best", we would not provide alternates)

However, my good friend Sean Dillon recently tackled this question in Oracle
Magazine:

XML: To CLOB or Object?

http://otn.oracle.com/oramag/oracle/03-jul/o43xml.html
4) select x.result.getclobVal() from t3 x;

will return the entire thing as a clob.  make sure to "set long 50000" or
something to see the entire thing in sqlplus

September 18, 2003 -- Thanks for the question regarding "What If I
      forget the name of SavePoint?", version 9.2.0

      You Asked
Hi! I'm on the OCP track in South Korea and I'm learning Administraition
Fundamentals II right now. I'd appreciate it if you answer my question. So,
Here's my question.

When I was learning about controlling transaction, I learnt how to use
'SAVEPOINT' command. After I learnt the command, I practiced it by myself. See
the below..

SQL> UPDATE...
SQL> SAVEPOINT my_savepoint;
SQL> INSERT...
SQL> ROLLBACK TO ??????? <---- I forgot the name of the savepoint!

Do you see the problem? I tried to rollback to savepoint, but I forgot the name.
After that happened, I asked my instructor but he didn't know the solution. He
said he've never thought about that situation. So, I've been looking for the
answer on the internet, like OTN, Asktom, and so many Oracle related sites. But
I couldn't find the answer. And finally, here I am to ask you the question.
You're the last hope for me :)

Thanks for being helpful to many ORACLE beginner including me.

      and we said...
no i don't see the problem for I cannot fathom how you would "forget" the name
of the savepoint.

it wouldn't happen since you really don't write complex transactions by hand.
you have code that does them, and you would just "page up" in the code to see
what you had named it.

so no, I don't see the problem -- there isn't one.  this isn't something that
would "happen"
Review & Followup

      Rating: 1
is there any
      query that shows the Savepoint name   August 04, 2003

Reviewer:  Rahul k chaudhari  from India
if there R many savepoint i've created and i want to rollback up a particular
savepoint i know the particular transaction that i want to rollback upto but if
i forget the name of that particular transaction's savepoint name then what i
should do at that time ur say that go up-down and page-up-down this is not a
perfect anwser sir b'coz if have created  three savepoint and clear screen then
what should do there to know the name ?????

i just want to say that is there any query or table that stores the savepoint
name. i know that savepoints R loggical. but there must be some way to know the
name of savepoint.

          Followup:

what is "R" -- a new word?

like I said, if you forgot, you must not have wanted to get there.  give me a
real world case where this could happen.

tell me, what possible use would this view be.  Ok, here you go:

SP1
SP2
SP3
SP4
SP5

Now, you somehow "forgot" their names, you have then now.  now what?  I fail to
see how if you don't know their names, you know you want to go back to SP3 for
example.

There is a way to know -- it is called "common sense"

      Rating: 5
This is
      amnesia  August 04, 2003
Reviewer:
      A reader
Take medication this will solve the problem of savepoint.
Sorry but couldnt help it.
  Never heard of such a thing of forgetting names of objects or program
contructs in your Code.

      Rating: 5
This is a good
      one :-)  August 04, 2003
Reviewer:
      A reader

      Rating: 1
hmm....Looks
      like I asked a stupid question :(  August 04, 2003

Reviewer:  Z80  from Korea
 so...there is no way to forget the name of savepoint that you made.
Please Don't take it as I'm trying to tease you.
But it still looks possible to me....

Like you said, if you made statements on a texteditor like Notepad, there should
be no way to forget the name of savepoint. But, what if you made a statement
into SQL*PLUS directly?

Even if you still don't see the possibilty or reason that happen the thing, just
tell me if there is the way to query the name of existing savepoints like
querying data dictionary view. That's all I want to know.

Sorry for bothering you with stupid question again.

          Followup:

you would not make these statements into sqlplus directly -- that is, has been
and will be my point.

even if you did "scroll up" comes into mind.

there is no data dictionary view -- if there were, I would have said it.

      Rating: 5
The question
      is...  August 04, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
>>Sorry for bothering you with stupid question again.

The argument is not that the question is "stupid" but that such a situation is
not realistic.

Any such serious piece of code like this must be in a script or program file to
start with.

As you say that "after creating a savepoint if you cleared the SQL*Plus screen
and you forgot the name of savepoint that you created, now how to know the name"

The point is that the moment you clear the screen of SQL*Plus, it indicates that
you are NOT interested in any of the things (contents) displayed on the screen
(that is why you are clearing it, right?) and DO NOT NEED it or else why would
you clear the screen if you needed its contents.

So, it means that you are not interested in the savepoints you created either?

It is similar to saying that I ran a bunch of SQL*Plus commands at my SQL*Plus
prompt and then later cleared the screen. Now I want those commands back. Are
they stored somewhere for me to retreive?

      Rating: 5
spool and
      recall   August 05, 2003
Reviewer:
      A reader
You can spool the sqlplus session if you "R" so forgetfull.
 "R" you trying to hack into somebody's CODE ?I BET knowing the savepoint wont
help.
As someone said above
The only solution is open a TAR with ORACLE HEALTH SUPPORT
for errors : ORA-AMNESIA.
 If you are a novice/expert you should be reading more of concepts manual.Pls
get off this savepoint there are more interesting things in ORACLE.More to life
then fooling and savepointing
 This will be a SAVINGPOINT  for you

      Rating: 4
Good
      one!!!  August 05, 2003
Reviewer:  Suvarna

This was the best part in this thread. Atleast some solution.

      Rating: 3
a way
      out  August 05, 2003
Reviewer:  Dennis
       from Missouri, USA
If you really need to go back to a savepoint, but have forgotten the name of it,
maybe it would be best to just rollback.  You'll have to start over from the
last commit, but you are trying to 'lose' some part of the transaction anyway,
so maybe you aren't losing too much by going back (guess it depends on how many
savepoints you made and how far back you wanted to go on whether or not you're
'losing out' b by doing a full rollback).

I agree that if the transaction is so complex that you are making a lot of
savepoints, it will be in a script somewhere.  If not, then you should
definitely be spooling so you can script it later without having to reinvent the
wheel.  Also, adopting a standard naming scheme may be helpful (first savepoint
- savepointA for example - not as flashy as 'savept_insertx93' but the name of
the savepoint isn't what matters as long as you know where the savepoint is and
what it reflects).

Dennis

      Rating: 3
get savepoint
      names  September 02, 2003
Reviewer:  vidya
       from India
Hi all!

The following commands show how to get the names of the savepoints created.This
works even if you clear the screen before creating the second savepoint.

SQL> insert into emp (empno, ename)
       2  values (9995, 'vso');

     1 row created.

     SQL> savepoint pt1;

     Savepoint created.

     SQL> insert into emp (empno, ename)
       2  values (9994, 'vso');

     1 row created.

     SQL> savepoint pt2;

     Savepoint created.

     SQL> alter session set events
       2  'immediate trace name savepoints level 1';

     Session altered.

A trace file is generated in the user_dump_directory.  The
content of the trace file is included in the following:

Dump file /u04/app/oracle/admin/R805/udump/r805_ora_18763.trc
Oracle8 Enterprise Edition Release 8.0.5.2.1 - Production
With the Partitioning and Objects options
PL/SQL Release 8.0.5.2.0 - Production
ORACLE_HOME = /u04/app/oracle/product/8.0.5
System name:    SunOS
Node name:      rtcsol1
Release:        5.6
Version:        Generic_105181-17
Machine:        sun4u
Instance name: R805
Redo thread mounted by this instance: 1
Oracle process number: 8
Unix process pid: 18763, image: oracleR805

*** 2000.04.18.14.11.37.000
*** SESSION ID:(7.391) 2000.04.18.14.11.37.000
====================================================
SAVEPOINT FOR CURRENT PROCESS
------------------------------
   flag: 0x1
   name: PT2
   dba: 0x80020e, sequence #: 0, record #: 9, savepoint #: 131
   status: VALID, next: 10a0868
   name: PT1
   dba: 0x80020e, sequence #: 0, record #: 7, savepoint #: 109
   status: VALID, next: 0

regards,
Vidya

      Rating: 5
Great answer,
      Vidya  September 11, 2003
Reviewer:  Praveen
       from Bangalore
Thanks for that :)

Regards..

Praveen

      Rating: 3
is savepoint
      right approach  September 18, 2003
Reviewer:
      vj  from in
i use

Oracle9i Enterprise Edition Release 9.2.0.1.0..

We have a stored procedure that processes say 1 to 500 rows..For the records
that are not processed due to some business check WE FIRE A ROLLBACK TO THE
SAVEPOINT...This is to ensure the rest that are processed are to be committed...

LOOP
  SAVEPOINT XXXX;
  <<BUSINESS LOGIC>>
  <<ON CONDITION OF BUSINESS LOGIC>>
  <<DMLS>>
  ROLLBACK TO XXXX;
  <<BUSINESS LOGIC>>
  <DMLS>>
  <<ON CONDITION OF B.L>>
  ROLLBACK TO XXXX;
END LOOP;

Is this a right approach !! or there is any better approach available for this..

Your guidance is highly appreciated..

Rgds

          Followup:

that is what savepoints are for.  yes.

September 18, 2003 -- Thanks for the question regarding "How to use
      SQL*PLUS to generate a variable length file delimited by comma?", version
      8.1.6

      You Asked
Tom,

I am trying to use sql*plus to generate a file look like this:

Harry,Jia,410 York Mills Road,,M3A1B9,ON,CA
Daniel,Xiang,250 Yonge Street,18 Floor,M3B1W9,ON,CA

That is, every column can be shrinkable to the length of its value. If the value
is null, there is nothing output between the delimiters. I would appreciate if
you could provide some help on this.

Thanks,

Harry

      and we said...
see

http://osi.oracle.com/~tkyte/flat/index.html
...
Review
      & Followup

      Rating: 1
How to use
      SQL*PLUS to generate a variable length file delimited by
      comma?  January 30, 2002
Reviewer:  Stuart
       from Scotland
I don't know about your response - I thought Terry was asking how to generate a
comma delimited file - not import.  In SQL*PLUS the simplest way might be:-

set head off pages 0 feedback off
SELECT forename ||','||
       surname  ||','||
       address1 ||','||
       address2 ||','||
       etc ..
FROM   x
WHERE  y;

and spool this to wherever.  Other alternatives could involve PL-SQL
(DBMS_OUTPUT/UTL_FILE) which is not what was asked for

As I'm a newbie to "Ask Tom", I've probably misread the question - if so then
apologies

          Followup:

Stuart --

did you read the page I linked to?  It starts with:

<quote>
How Can I unload data to a flat file

Many times we are asked

    * "Does anyone know an easy way of dumping the data from an Oracle table
into a delimited(comma, tab etc) ascii file?"
    * "Does anyone know an easy way to unload data in a format for sqlldr to
reload later?"

SQLPlus can do this quite easily but it is a pain to have to write a
script/table.  What I've done is setup scripts for UNIX and NT that allow you
from the command line to execute things like:
</quote>
So, i don't mention IMP at all???  Rather I pointed him to a script that unloads
data using sqlplus in an automated fashion (so you don't have to write the query
you wrote above, I do that for you)

You didn't misread the question, but I believe you did misread the answser ;)

      Rating:
      3
D'Oh!!  January 30, 2002
Reviewer:
      Stuart  from Scotland
Sorry Tom - you're right (don't know why I doubted you) I went back and re-read
your answer and the penny dropped.  The way you built the ytmpy.sql script was
very inventive and will be sure to help me out in the future.

However, what if you don't want every column in the table or want to pull in
data from a couple (or more) tables ? I take it you would just have to write an
ad-hoc query in this case. Or do you have some more tricks up your sleeve?

Thanks for all your excellent responses and sorry for being a twat ( I can't
help it that I'm from the shallow end of the gene pool !)

      Rating: 4
Thank you very
      much for your response.  January 30, 2002

Reviewer:  Harry  from Toronto, ON CANADA
Hi Tom and Stuart,

I tried the ways you guys mentioned. Both of them worked very well. They are
really helpful for my work.

But another problem is that I want to extract over ten million records from a
table. Do you know which method I should use to get the higher speed, spool or
DBMS_OUTPUT or UTL_FILE.

Thanks a lot for your time and response again.

Harry

      Rating:
      3
  January 30, 2002
Reviewer:  Stuart
       from Scotland
I'm not sure what method would be quickest - if you choose spool (I would in
your situation) and it's the whole table you want outputted then use Tom's
utility - however I would modify it so that the ytmpy.sql script spools a file
to a location & name of your choosing - also I would 'set termout off' for when
ytmpy.sql is running - it should run faster without having to display all 10
million rows back to you - Hope this helps though it might be worth waiting for
Tom's answer

Cheers

          Followup:

For speed:

pro*c

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:459020243348
does that.

spool will be faster then utl_file UNLESS the sqlplus client is running on the
end of a very slow network in which case, utl_file might beat it.

      Rating: 3
links to sql
      loader  October 20, 2002
Reviewer:
      A reader
tom,

http://osi.oracle.com/~tkyte/flat/index.html
the link is pointing to sqlloader

can you give the new link to see the scripts that load data from tables to flat
files

          Followup:

He he -- thats the funniest way I've seen yet to ask for a rewrite of a script!

Doesn't exist yet, but I'll tell you what -- first one to write it and put it
here will get the credit and I'LL link back to you!

      Rating: 4
hide
      output  March 07, 2003
Reviewer:  mo

Tom:

I have a unix shell script that calls sqlplus and runs a sql*plus script.  The
script spools data to a file and has a statement like select 'update table
.....' from dual;

when i run the shell script it shows all the statements that are being spooled.

is there a way to hide the output in sql*plus while it is running select
statements in the background?

          Followup:

set termout off

      Rating: 4
termout
      off  March 07, 2003
Reviewer:  mo

Tom:

why does it still show the output?

SQL> set termout off
SQL> spool t1.txt
SQL> select 'update table x where .....' from dual;

'UPDATETABLEXWHERE.....'
--------------------------
update table x where ....

          Followup:

put it in a script

put:

set termout off
spool t1.txt
select ...
spool off
set termout on

in a script and run that.

      Rating: 5
Isn't the
      documentation just the handiest?  March 07, 2003

Reviewer:  Mark A. Williams  from Indianapolis, IN
      USA
From the "SQL*Plus User's Guide and Reference":

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a90842/toc.htm
SET TERM[OUT] {ON|OFF}
SET TERMOUT is not supported in iSQL*Plus

Controls the display of output generated by commands executed from a script. OFF
suppresses the display so that you can spool output from a script without seeing
the output on the screen. ON displays the output. TERMOUT OFF does not affect
output from commands you enter interactively.

Huh.  Right there it is.  Who knew they documented this stuff?

- Mark

          Followup:

vbg

      Rating: 4
spool file
      using SQL*Plus  March 10, 2003
Reviewer:
      anjum  from Michigan
Tom,

The Question from Harry was like this..
He wants to spool the data from a table into a file but without spaces and if
the column is NULL the the output should be just like ",,".

I don't know whether I did not undertand you reply.

Can you please explain again how to spool like this.

Harry,Jia,410 York Mills Road,,M3A1B9,ON,CA
Daniel,Xiang,250 Yonge Street,18 Floor,M3B1W9,ON,CA

          Followup:

read the link waaayyy up top.

It has three ways to do EXACTLY THAT.  all you need to do is -- click on the
LINK.

      Rating: 3
Can't start
      SQLPLUS...  March 10, 2003
Reviewer:  Robert
       from PA
Tom, Oracle is not set up properly on this QA box, is it?

First I get this:

asdprod1: /home/rchin > sqlplus
ksh: sqlplus:  not found.

so I cd to the bin directory, now I get this:

asdprod1: /u01/app/oracle/product/8.1.7/bin > sqlplus
Message file sp1<lang>.msb not found
Error 6 initializing SQL*Plus

But the .msb files are there:

dns: /u01/app/oracle/product/8.1.7/sqlplus/mesg > ls -f
.           cpyus.msb   sp2us.msb   sp1us.msg
..          sp1us.msb   cpyus.msg   sp2us.msg

What gives and what the heck are these files for anyway ?

Thanks

          Followup:

your oracle home is not set.

your ENVIRONMENT is incorrectly setup.  You do not have an ORACLE_HOME
environment variable, oracle_home/bin is not in your path.

those files are (as the error states) message files.  It cannot find the sqlplus
message files to turn error codes into meaningful text because you have
neglected to set up your environment.

      Rating: 5
For
      Robert...  March 10, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Robert,

I'm not Tom, but while you're waiting try:

echo $ORACLE_HOME and if it is not set, you should set it to the correct
location.  ie, export ORACLE_HOME=/u01/app/oracle/product/8.1.7

On a UNIX box, I would typically set:
ORACLE_BASE
ORACLE_HOME
ORAENV_ASK
ORACLE_SID
TNS_ADMIN

and call $ORACLE_HOME/bin/oraenv (for your ksh environment).

These are all documented here:

http://download-west.oracle.com/docs/html/A97297_01/ch1_admin.htm#sthref27
The .msb files are the "message binary" files and contain the message resources
used by Oracle.

Hope that helps, and pardon the interruption...

- Mark

      Rating: 3
SQLPLUS column
      separator  March 14, 2003
Reviewer:  tom
      reid  from Edinburgh, Scotland
Stuart, you dont need all your ||','|| in your SQPLUS script. Just use the handy
set colsep ',' command

      Rating: 3
truncating
      trailing spaces from lines  June 14, 2003

Reviewer:  Tim
Tom,

When I spool my query results to a file, it appends spaces at the end of the
line based on the linesize. Is there a way to truncate those trailing spaces ?

Thanks much.

          Followup:

set trimspool on

      Rating: 4
Thanks Tom for
      your immediate reply !!!  June 14, 2003
Reviewer:
      Tim
damn .. how couldn't I find the command :(

Now 1 more question :)

I am generating a comma separated file with around 45 columns using SQL*Plus and
||','||. Sorry, since I am on windoze 2000 & dont have access to the unix box,
cant use your famous (and my favourite) dump_csv function. So to get the proper
output, I have set heading off and pagesize 0. But to get the heading on 1st
line, I have done something like this :

select 'empno,ename,deptno,sal,hiredate'
from dual
union
select empno||','||ename||','||deptno||','||sal||','||hiredate
from emp
order by 1 desc
/

I was forced to use "order by 1 desc" because the first column being numeric,
the heading is displayed at the bottom. And there are almost 127,000 rows in the
output and I want to avoid using "order by" -- for performance and the empno are
generated in reverse order.

So is it possible to get the heading and empno in ascending order ?

Thanks again.

          Followup:

use prompt instead of a union (union by the way does a DISTINCT on the result
set, you probably meant "union all" in any case -- but you don't want EITHER)

scott@ORA920> @a
empno,ename,deptno,sal,hiredate
7369,SMITH,20,800,17-DEC-80
7499,ALLEN,30,1600,20-FEB-81
7521,WARD,30,1250,22-FEB-81
7566,JONES,20,2975,02-APR-81
7654,MARTIN,30,1250,28-SEP-81
7698,BLAKE,30,2850,01-MAY-81
7782,CLARK,10,2450,09-JUN-81
7788,SCOTT,20,3000,09-DEC-82
7839,KING,10,5000,17-NOV-81
7844,TURNER,30,1500,08-SEP-81
7876,ADAMS,20,1100,12-JAN-83
7900,JAMES,30,950,03-DEC-81
7902,FORD,20,3000,03-DEC-81
7934,MILLER,10,1300,23-JAN-82

scott@ORA920> !cat a.sql
set heading off
set embedded on
set feedback off
set pagesize 0
prompt empno,ename,deptno,sal,hiredate
select empno||','||ename||','||deptno||','||sal||','||hiredate
from emp
/

      Rating: 5
You are simply
      great !!  June 16, 2003
Reviewer:  Tim

Can I give you more than 5 stars ??? :-) So simple but works like a charm !!

Thanks a lot Tom !!!

      Rating: 3
Is it possible
      to make a comma separated string consisting of values of a single
      column?  September 17, 2003
Reviewer:
      Praveen  from Bangalore
Hi Tom,

It's possible to get such a string using a for-loop. ie

for rec in (select col from a_table) loop
   v_str := v_str || ',' || rec.col;
end loop;

But is it possible to make that using a single query ?

Tom, please dont give me the method now, if it is possible. Let me try to find
it by myself. But I would like to know if it is possible indeed (using a single
query) in Oracle.

Thanks for your answer

Praveen

          Followup:

if there is a reasonable maximum number of rows to concat -- yes, easily (and
there must be cause you are limited to varchar2(4000)!)

or, in 9i, you can do it with a user defined aggregate easily (i have that on
this site, you can search around for it if you like)

      Rating: 3
Multiple rows
      into a comma separated string  September 18, 2003

Reviewer:  Praveen  from Bangalore
Hi Tom,

1)I was unable to find the answer by myself, hence reffered to your answer (to a
similar question) at

http://asktom.oracle.com/pls/ask/f?p=4950:8:2871841274823210810::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:8682544392674
and made a query as below. It gives me unexpected result when EMPLOYEE table
contains less than 5 records.

SELECT E1 || ',' || E2 || ',' || E3 || ',' || E4 || ',' || E5 ECODE_SUM
FROM (
    SELECT MAX(DECODE(R, 1, ECODE)) E1,
           MAX(DECODE(R, 2, ECODE)) E2,
            MAX(DECODE(R, 3, ECODE)) E3,
            MAX(DECODE(R, 1, ECODE)) E4,
            MAX(DECODE(R, 1, ECODE)) E5
    FROM (SELECT ROWNUM R, ECODE FROM EMPLOYEE ORDER BY ROWNUM)
    WHERE R < 6
     )

2) As per your suggesion about user-defined aggregate I found the following
link, but the method given there is
infact using procedural statements, rather than single SQL queries. (but that
was an excellent feature of oracle and a brilliant explanation by you, indeed).

http://asktom.oracle.com/pls/ask/f?p=4950:8:2871841274823210810::NO::F4950_P8_DISPLAYID:2196162600402
Does this mean that using a single SQL query, we cannot combine the rows into a
single row, unless we know the maximum number of rows in that table and the
table should always contain that many number of records ? (I accept the
constraint of VARCHAR2(4000) bytes.)

Thanks for your answer

warm regards

Praveen

          Followup:

1) the decode uses 1,2,3,1,1???  but what is "unexpected"??

2) think about it this way.

MAX() is procedural code.  It is procedural code someone else wrote.

What is the difference between

a) stragg
b) max

stragg is a generic aggregation function that you write once and use everywhere.

It is no different then MAX() once installed.  It becomes part of SQL.

So, I would say "you can in a single sql query combine the rows"

September 18, 2003 Jan -- Thanks for the question regarding "EXECUTE
      IMMEDIATE - don`t know how many input variables", version 8.1.7

      You Asked
Dear Tom,

I have this question:

I have a table with Rules, each rule can have conditions which are checked
during the data processing. E.g.:

Rule_ID,   Conditions
-------------------------------------------------------------
1          ([AGE]>20 AND [SEX]='M') OR [STAFF_CATEGORY]='MGR'
2          null
3          [AREA] IN ('AA','AB','AC')
4          ([AGE]>5 AND [SEX]='F') OR [STAFF_CATEGORY]='DEPEND'

Each Rule can have 0 .. n  conditions.

When data are processed, the Attributes ([AGE],[AREA]) are replaced with current
values to find out if the Rule is applicable for particural person, e.g. for
Rule 1 and person Scott:

EXECUTE IMMEDIATE
 'BEGIN IF
 (15>20 AND ''M''=''M'')  OR ''EMP''=''MGR''  -- Dynamic condition string
 THEN :1=''Y''; END IF; END;'
   USING OUT v_is_applicable;

My question is, how can I avoid from NOT USING BIND VARIABLES? I cannot use  :1,
:2, :3 and USING var1, var2,var3 since I don`t know how many variables will be
there.

Thanks, Jan

      and we said...
Well, If I can assume you pass an array of "names" (like age, sex, etc) and an
array of values to this parsing routine -- we can do this (makes the parse
routine somewhat "trivial"

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace context my_ctx using
parse_procedure
  2  /
Context created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure
                             parse_procedure( p_names in dbms_sql.varchar2s,
  2                                           p_values in dbms_sql.varchar2s,
  3                                           p_rule in varchar2 )
  4  as
  5      l_rule  long;
  6      l_yn    varchar2(1) default 'N';
  7  begin
  8      for i in 1 .. p_names.count
  9      loop
 10          dbms_session.set_context( 'my_ctx', p_names(i), p_values(i) );
 11          dbms_output.put_line('set ' || p_names(i) || ' = '||p_values(i));
 12      end loop;
 13
 14      l_rule := replace( p_rule, '[', 'sys_context( ''my_ctx'', ''' );
 15      l_rule := replace( l_rule, ']', ''')' );
 16
 17      execute immediate 'begin if ' || l_rule ||
                           ' then :1 := ''Y''; end if; end;' using out l_yn;
 18
 19      dbms_output.put_line( 'answer is ' || l_yn );
 20      dbms_output.put_line( '-------------------' );
 21  end;
 22  /

Procedure created.

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( rule_id int, condition
varchar2(255) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1, '([AGE]>20 AND
[SEX]=''M'') OR [STAFF_CATEGORY]=''MGR'' ' );

1 row created.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_names dbms_sql.varchar2s;
  3      l_values dbms_sql.varchar2s;
  4  begin
  5      l_names(1) := 'age';
  6      l_values(1) := '15';
  7      l_names(2) := 'sex';
  8      l_values(2) := 'M';
  9      l_names(3) := 'staff_category';
 10      l_values(3) := 'MGR';
 11
 12      for x in ( select * from t )
 13      loop
 14          dbms_output.put_line( 'processing "' || x.condition || '"' );
 15          parse_procedure( l_names, l_values, x.condition );
 16
 17          l_values(3) := 'XX';
 18          parse_procedure( l_names, l_values, x.condition );
 19      end loop;
 20  end;
 21  /
processing "([AGE]>20 AND [SEX]='M') OR [STAFF_CATEGORY]='MGR' "
set age = 15
set sex = M
set staff_category = MGR
answer is Y
-------------------
set age = 15
set sex = M
set staff_category = XX
answer is N
-------------------

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating:
      4
Interesting  December 03, 2002
Reviewer:
      A reader

      Rating: 3
Sys_Context is
      Great but ...  December 03, 2002
Reviewer:
      Jan
Hi Tom,

your solution is OK if the ratio of Names and Values is 1:1.
But I cannot use it for conditions like [AREA] IN ('AA','BB','CC').

BTW, why CURSOR_SHARING=FORCE is not working here? It is for SQL only?

Thanks, Jan

          Followup:

why does that present a problem?  the only variant I see there is [AREA].

It will become:

  sys_context( 'my_ctx', 'area' ) in ( 'AA', 'BB', 'CC' )

you do not NEED (nor even truly desire) 'AA', 'BB', 'CC' to be bind variables!
They are constants -- everyone who executes that rule (and you have a finite set
of rules) will use the same exactly 3 constants.  You don't want or need to bind
them.

This is no different then saying "this solution doesn't work cause when I have

   [AGE] > 15

15 won't be bound".  Well, we don't want to bind 15, we want to bind [AGE].

So, the solution stands as it.

cursor sharing doesn't work for dynamically parsed plsql blocks, no.  this
sys_context trick does the job, does it well and makes your parsing routine
pretty trivial to implement!  (in fact, if you just changed the "rule"
syntax....  instead of putting [AGE] put sys_context( 'my_ctx', 'age' ) -- hmm..

      Rating: 3
need binds on
      both sides  December 04, 2002
Reviewer:  Jan
       from Singapore
"you do not NEED (nor even truly desire) 'AA', 'BB', 'CC' to be bind variables!
"

For the moment, I agree with you, but not forever, since the number of rules is
going to increase, there could be plenty of rules with the same "frame", but not
values, e.g.:

  [AREA] IN ('AA','BB','CC')
  [AREA] IN ('BB','AA','CC')
  [AREA] IN ('CC','DD','AA')

In this case, I would like to have in SQL_AREA just one statment.

Jan

          Followup:

So, what are you going to do about "[age] > 15" and anything else with "binds"

I suppose you could:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace context my_ctx using
parse_procedure
  2  /

Context created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure
  2                          parse_procedure( p_names in dbms_sql.varchar2s,
  3                                           p_values in dbms_sql.varchar2s,
  4                                           p_rule in varchar2 )
  5  as
  6      l_rule  long;
  7      l_yn    varchar2(1) default 'N';
  8  begin
  9      for i in 1 .. p_names.count
 10      loop
 11          dbms_session.set_context( 'my_ctx', p_names(i), p_values(i) );
 12          dbms_output.put_line('set ' || p_names(i) || ' = '||p_values(i));
 13      end loop;
 14
 15      l_rule := replace( p_rule, '[', 'sys_context( ''my_ctx'', ''' );
 16      l_rule := replace( l_rule, ']', ''')' );
 17
 18
 19          execute immediate 'select decode( count(*), 1, ''Y'', ''N'' )
 20                           from dual
 21                          where ' || l_rule into l_yn;
 22
 23      dbms_output.put_line( 'answer is ' || l_yn );
 24      dbms_output.put_line( '-------------------' );
 25  end;
 26  /

Procedure created.

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( rule_id int, condition
varchar2(255) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1, '([AGE]>20 AND
[SEX]=''M'') OR [STAFF_CATEGORY]=''MGR'' ' );

1 row created.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter system flush shared_pool;

System altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set cursor_sharing=force;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_names dbms_sql.varchar2s;
  3      l_values dbms_sql.varchar2s;
  4  begin
  5      l_names(1) := 'age';
  6      l_values(1) := '15';
  7      l_names(2) := 'sex';
  8      l_values(2) := 'M';
  9      l_names(3) := 'staff_category';
 10      l_values(3) := 'MGR';
 11
 12      for x in ( select * from t )
 13      loop
 14          dbms_output.put_line( 'processing "' || x.condition || '"' );
 15          parse_procedure( l_names, l_values, x.condition );
 16
 17          l_values(3) := 'XX';
 18          parse_procedure( l_names, l_values, x.condition );
 19      end loop;
 20  end;
 21  /
processing "([AGE]>20 AND [SEX]='M') OR [STAFF_CATEGORY]='MGR' "
set age = 15
set sex = M
set staff_category = MGR
answer is Y
-------------------
set age = 15
set sex = M
set staff_category = XX
answer is N
-------------------

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select sql_text from v$sql where sql_text
like 'select decode%';

SQL_TEXT
---------------------------------------------------------------------------------
--------------------------------------------------
select decode( count(*), :"SYS_B_00", :"SYS_B_01", :"SYS_B_02" )
         from dual                        where (
sys_context( :"SYS_B_03", :"SYS_B_04")>:"SYS_B_05" AND sys_context( :"SYS_B_06",
:"SYS_B_07")=:"SYS_B_08") OR sys_context( :"SYS_B_
09", :"SYS_B_10")=:"SYS_B_11"

obviates technically the need to use sys_context -- however, I'll betcha the
replace is lots easier then parsing and finding exact names and substituting in
the values! so you might keep sys_context anyhow

But -- bottom line -- you have a FINITE number of rules -- maybe 1000, maybe
more, but FINITE.  You can size a shared pool to hold them all.  The use of bind
variables is to prevent an INFINITE number of statements from flooding the
pool

      Rating: 5
one more
      academic question  December 05, 2002
Reviewer:
      Jan  from Singapore
Thanks Tom for your spent time,

yes, it will be finite number of rules, and by watching and estimating
statistics about parsed rules,
it will be not more then 100-200 unique rules, surely less then 1000. Then it is
propably not worth of such microtunning.

just one more academic question, propably out of the original one. I compared
SQL with PLSQL inside of EXECUTE IMMEDIATE:

----------------------
DECLARE
t      NUMBER:=dbms_utility.get_time;
v      VARCHAR2(1):='N';
v_con  VARCHAR2(10):='1=0';
BEGIN

FOR i IN 1 .. 100000 LOOP
EXECUTE IMMEDIATE
  'BEGIN IF '||v_con||' THEN :1:=''Y''; END IF;END;' USING OUT v;
END LOOP;

dbms_output.put_line('PL/SQL, Condition met? = '||v||' Time:
'||TO_CHAR(dbms_utility.get_time-t));
t:=dbms_utility.get_time;

FOR ii IN 1 .. 100000 LOOP
EXECUTE IMMEDIATE
  'SELECT DECODE(COUNT(*),1,''Y'',''N'') FROM DUAL WHERE '||v_con INTO v;
END LOOP;

dbms_output.put_line('SQL, Condition met? = '||v||' Time:
'||TO_CHAR(dbms_utility.get_time-t));
END;
-----------------------

PL/SQL, Condition met? = Y Time: 1088
SQL, Condition met? = Y Time: 1181
PL/SQL, Condition met?='N' =  Time: 1059
SQL, Condition met? = N Time: 878

I see here that SQL without I/O is faster (878:1059) then PL/SQL. Is it because
PL/SQL is slower
or because PL/SQL is used in Dynamic SQL (I mean - Dynamic Sql was born for
SQL)?

When I do the same test in Static PL/SQL block, result are totaly different
(because of switch between SQL and PL/SQL?):

----------------------
DECLARE
t      NUMBER:=dbms_utility.get_time;
v      VARCHAR2(1):='N';
v_con  VARCHAR2(10):='1=0';
BEGIN

FOR i IN 1 .. 100000 LOOP
IF 1=0 THEN v:='Y'; END IF;
END LOOP;

dbms_output.put_line('PL/SQL, Condition met? = '||v||' Time:
'||TO_CHAR(dbms_utility.get_time-t));
t:=dbms_utility.get_time;

FOR ii IN 1 .. 100000 LOOP
SELECT DECODE(COUNT(*),1,'Y','N') INTO v FROM DUAL WHERE  1=0;
END LOOP;
dbms_output.put_line('SQL, Condition met? = '||v||' Time:
'||TO_CHAR(dbms_utility.get_time-t));
END;
---------------------

PL/SQL, Condition met? = N Time: 7
SQL, Condition met? = N Time: 559

Thanks and Regards, Jan

          Followup:

SQL in general will be faster then procedural PLSQL ( i find this time and time
and time again ).  Here you have a PLSQL VM calling to dynamic stuff that has to
set up another PLSQL VM stack, run some interpreted code and return.  Compared
to a SQL query (and SQL is much less "generic" then PLSQL).

However, if you are already IN PLSQL -- then staying there is more efficient
then context switching to the SQL runtime.

      Rating: 3
Bind variables
      in exectue immediate  December 06, 2002
Reviewer:
      Marc  from Switzerland
Hi Tom

I prepare a dynamic insert as select statement with a few (0 - 150) bind
variables in the where clause. My code for execute statement look like this:

IF    i_bind_index =   1 THEN EXECUTE IMMEDIATE i_statement USING v(1);
ELSIF i_bind_index =   2 THEN EXECUTE IMMEDIATE i_statement USING v(1),v(2);
ELSIF i_bind_index =   3 THEN EXECUTE IMMEDIATE i_statement USING
v(1),v(2),v(3);
...
...
ELSIF i_bind_index = 149 THEN EXECUTE IMMEDIATE i_statement USING
v(1),v(2),v(3),v(4),v(5),v(6),v(7),v(8),v(9),v(10),v(11),v(12),v(13),v(14),v(15),
v(16),v(17),v(18),v(19),v(20),v(21),v(22),v(23),v(24),v(25),v(26),v(27),v(28),v(2
9),v(30),v(31),v(32),v(33),v(34),v(35),v(36),v(37),v(38),v(39),v(40),v(41),v(42),
v(43),v(44),v(45),v(46),v(47),v(48),v(49),v(50),v(51),v(52),v(53),v(54),v(55),v(5
6),v(57),v(58),v(59),v(60),v(61),v(62),v(63),v(64),v(65),v(66),v(67),v(68),v(69),
v(70),v(71),v(72),v(73),v(74),v(75),v(76),v(77),v(78),v(79),v(80),v(81),v(82),v(8
3),v(84),v(85),v(86),v(87),v(88),v(89),v(90),v(91),v(92),v(93),v(94),v(95),v(96),
v(97),v(98),v(99),v(100),v(101),v(102),v(103),v(104),v(105),v(106),v(107),v(108),
v(109),v(110),v(111),v(112),v(113),v(114),v(115),v(116),v(117),v(118),v(119),v(12
0),v(121),v(122),v(123),v(124),v(125),v(126),v(127),v(128),v(129),v(130),v(131),v
(132),v(133),v(134),v(135),v(136),v(137),v(138),v(139),v(140),v(141),v(142),v(143
),v(144),v(145),v(146),v(147),v(148),v(149);
ELSIF i_bind_index = 150 THEN EXECUTE IMMEDIATE i_statement USING
v(1),v(2),v(3),v(4),v(5),v(6),v(7),v(8),v(9),v(10),v(11),v(12),v(13),v(14),v(15),
v(16),v(17),v(18),v(19),v(20),v(21),v(22),v(23),v(24),v(25),v(26),v(27),v(28),v(2
9),v(30),v(31),v(32),v(33),v(34),v(35),v(36),v(37),v(38),v(39),v(40),v(41),v(42),
v(43),v(44),v(45),v(46),v(47),v(48),v(49),v(50),v(51),v(52),v(53),v(54),v(55),v(5
6),v(57),v(58),v(59),v(60),v(61),v(62),v(63),v(64),v(65),v(66),v(67),v(68),v(69),
v(70),v(71),v(72),v(73),v(74),v(75),v(76),v(77),v(78),v(79),v(80),v(81),v(82),v(8
3),v(84),v(85),v(86),v(87),v(88),v(89),v(90),v(91),v(92),v(93),v(94),v(95),v(96),
v(97),v(98),v(99),v(100),v(101),v(102),v(103),v(104),v(105),v(106),v(107),v(108),
v(109),v(110),v(111),v(112),v(113),v(114),v(115),v(116),v(117),v(118),v(119),v(12
0),v(121),v(122),v(123),v(124),v(125),v(126),v(127),v(128),v(129),v(130),v(131),v
(132),v(133),v(134),v(135),v(136),v(137),v(138),v(139),v(140),v(141),v(142),v(143
),v(144),v(145),v(146),v(147),v(148),v(149),v(150);

My code is pretty fast and all bind variables are perfect bound, but is there a
easier (and shorter) way to implement it. Many thanks for your answer.

Kind Regards
Marc

          Followup:

This is a perfect example of when you want to use DBMS_SQL and not native
dynamic sql:

ops$tkyte@ORA920> create or replace package dyn_insert
  2  as
  3      type array is table of varchar2(4000);
  4
  5      procedure do_insert( p_tname in varchar2,
  6                           p_cnames in array,
  7                           p_values in array );
  8
  9  end;
 10  /

Package created.

ops$tkyte@ORA920> create or replace package body dyn_insert
  2  as
  3
  4  g_last_stmt long;
  5  g_cursor    number := dbms_sql.open_cursor;
  6
  7  procedure p ( p_str in varchar2 )
  8  is
  9     l_str   long := p_str;
 10  begin
 11     loop
 12        exit when l_str is null;
 13        dbms_output.put_line( substr( l_str, 1, 250 ) );
 14        l_str := substr( l_str, 251 );
 15     end loop;
 16  end;
 17
 18  procedure do_insert( p_tname in varchar2,
 19                       p_cnames in array,
 20                       p_values in array )
 21  is
 22      l_stmt  long;
 23      l_rc    number;
 24  begin
 25      l_stmt := 'insert into ' || p_tname || ' ( ' || p_cnames(1);
 26      for i in 2 .. p_cnames.count
 27      loop
 28          l_stmt := l_stmt || ', ' || p_cnames(i);
 29      end loop;
 30      l_stmt := l_stmt || ') values ( :bv1';
 31      for i in 2 .. p_cnames.count
 32      loop
 33          l_stmt := l_stmt || ', :bv' || i;
 34      end loop;
 35      l_stmt := l_stmt || ')';
 36      if ( l_stmt <> g_last_stmt or g_last_stmt is null )
 37      then
 38          p( l_stmt );
 39          g_last_stmt := l_stmt;
 40          dbms_sql.parse( g_cursor, g_last_stmt, dbms_sql.native );
 41      end if;
 42
 43      for i in 1 .. p_values.count
 44      loop
 45          dbms_sql.bind_variable( g_cursor, ':bv' || i, p_values(i) );
 46      end loop;
 47      l_rc := dbms_sql.execute( g_cursor );
 48  end;
 49
 50  end;
 51  /

Package body created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> drop table t;

Table dropped.

ops$tkyte@ORA920> create table t ( x int, y int, z int );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dyn_insert.do_insert( 't', dyn_insert.array( 'x', 'y',
'z' ), dyn_insert.array( 1, 2, 3 ) );
insert into t ( x, y, z) values ( :bv1, :bv2, :bv3)

thats shows we built a new statment and parsed it..

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec dyn_insert.do_insert( 't', dyn_insert.array( 'x', 'y',
'z' ), dyn_insert.array( 4, 5, 6 ) );

PL/SQL procedure successfully completed.

the lack of output here (no insert statement) shows we are reusing that
cursor-- no parse, it'll be better when you have tons of inserts to do

ops$tkyte@ORA920> exec dyn_insert.do_insert( 't', dyn_insert.array( 'z', 'y',
'x' ), dyn_insert.array( 4, 5, 6 ) );
insert into t ( z, y, x) values ( :bv1, :bv2, :bv3)

different insert, so a parse for this one...

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from t;

         X          Y          Z
---------- ---------- ----------
         1          2          3
         4          5          6
         6          5          4

ops$tkyte@ORA920>

      Rating: 4
Open Cursor -
      don`t know how many input variables  December 06, 2002

Reviewer:  Marc  from Switzerland
Hi Tom

I do the same technique for open a cursor:

IF    i_bind_index =   1 THEN OPEN io_daten FOR i_statement USING v(1);
ELSIF i_bind_index =   2 THEN OPEN io_daten FOR i_statement USING v(1),v(2);
ELSIF i_bind_index =   3 THEN OPEN io_daten FOR i_statement USING
v(1),v(2),v(3);
...
ELSIF i_bind_index = 150 THEN OPEN io_daten FOR i_statement USING
v(1),v(2),v(3),v(4),v(5),v(6),v(7),v(8),v(9),v(10),v(11),v(12),v(13),v(14),v(15),
v(16),v(17),v(18),v(19),v(20),v(21),v(22),v(23),v(24),v(25),v(26),v(27),v(28),v(2
9),v(30),v(31),v(32),v(33),v(34),v(35),v(36),v(37),v(38),v(39),v(40),v(41),v(42),
v(43),v(44),v(45),v(46),v(47),v(48),v(49),v(50),v(51),v(52),v(53),v(54),v(55),v(5
6),v(57),v(58),v(59),v(60),v(61),v(62),v(63),v(64),v(65),v(66),v(67),v(68),v(69),
v(70),v(71),v(72),v(73),v(74),v(75),v(76),v(77),v(78),v(79),v(80),v(81),v(82),v(8
3),v(84),v(85),v(86),v(87),v(88),v(89),v(90),v(91),v(92),v(93),v(94),v(95),v(96),
v(97),v(98),v(99),v(100),v(101),v(102),v(103),v(104),v(105),v(106),v(107),v(108),
v(109),v(110),v(111),v(112),v(113),v(114),v(115),v(116),v(117),v(118),v(119),v(12
0),v(121),v(122),v(123),v(124),v(125),v(126),v(127),v(128),v(129),v(130),v(131),v
(132),v(133),v(134),v(135),v(136),v(137),v(138),v(139),v(140),v(141),v(142),v(143
),v(144),v(145),v(146),v(147),v(148),v(149),v(150);

Is DBMS_SQL also a good solution for open cursor ? Thanks for your answer.

Kind regards
Marc

          Followup:

It will be a DBMS_SQL cursor -- usable only by DBMS_SQL.

You can use this technique:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279
with ref cursors.

      Rating: 4
Dynamic query
      with the use of context is slower than Static Query. Your inputs
      please.  September 18, 2003
Reviewer:
      Muralee  from Boston, MA
Tom,

Thanks a lot for your earlier timely responses. Here is a problem we are facing
currently.

We are using Oracle 8i (8.1.7.0). It takes 1 min or more when we execute a query
( inside a package ) dynamically using bulk collect & context like the one given
below.

BEGIN
   EXECUTE IMMEDIATE REPLACE( 'BEGIN
   SELECT BULK COLLECT
     INTO :outtab
     FROM mytable
    WHERE col1 = SYS_CONTEXT( "myctx", "col1_value" );
END;',
                              '"',
                              ''''
                            )
      USING          OUT mycollection;
END;

But when we execute the same as STATIC query in PL/SQL like the one given below,
it takes 30 secs.

BEGIN
   SELECT BULK COLLECT
     INTO :outtab
     FROM mytable
    WHERE col1 = pl_sql_variable_value;
END;

The only difference I can see is we are using dynamic query & context in one
case, and in the other we are issuing it as static query.

Could you please help us with your inputs/comments/suggestions on this.

Thanks.. Muralee

          Followup:

give me a complete example -- or at least the tkprofs of the two runs.

September 18, 2003 Leroy -- Thanks for the question regarding
      "Oracale 9i*plus SQL", version 9i

      You Asked
Tom:

I am just learning Oracle SQL and I was wondering if you had a list of commands
used in Oracle 9i*plus sql.

Thanks,
Leroy

      and we said...
no, i don't but -- the documentation, well, the docs do!!!

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-SQL
all of the 9iR2 docs are right there -- that points to the sqlplus stuff.
Review &
      Followup

      Rating: 1
I wonder why
      these "help desk" questions even get posted.  September 18,
      2003
Reviewer:  A reader  from away from the
      winds
This site is a great resource.  This type of beginner question/response devalues
the site.

          Followup:

it is my way of posting a "gentle reminder" that the documentation does in fact
exist.

why should I be the only one to see these questions :)

hopefully lots of people see it and say "gee, wonder if a simple search on the
docs might be able to help me out"

that -- and many people don't know about OTN (the Oracle Technology Network) and
are not aware that all of the docs are online -- especially the beginners.  they
need a hand getting started.

if i don't mind answering....

      Rating: 5
Thanks
      Tom!!!  September 19, 2003
Reviewer:
      A reader
Thanks Tom ... really appreciate this reply.

September 18, 2003 Cleo -- Thanks for the question regarding
      "question on dbms_sql", version

      You Asked
Hi Tom,

I'm trying out Dynamic SQL and I'm using the DBMS_SQL package to

do this   but I am getting errors when executing the procedure.

Below is a simple version of the code and the errors given when
executing it.  Could you please give me some guidance as to
where
I am going wrong because I am having difficulty with it.

Thanks.

SQL> CREATE OR REPLACE PROCEDURE prTest (
2     p_tableName IN varchar2,
3     p_existingId IN number)
4  IS
5   v_cursorId  integer;
6   v_id   integer;
7   v_name    varchar2(30);
8   v_selectString  varchar2(1000);
9   v_dummy   integer;
10
11  BEGIN
12   -- Open the cursor for processing.
13   v_cursorId :=DBMS_SQL.OPEN_CURSOR;
14
15   -- Construct the select string.
16   v_selectString := 'select id,name into v_id,v_name from '
17                             || p_tableName || ' where id=
(:id1)';
18
19   -- Parse the query.
20   DBMS_SQL.PARSE(v_cursorId, v_selectString, DBMS_SQL.V7);
21
22   -- Bind the input variables.
23   DBMS_SQL.BIND_VARIABLE(v_cursorId, ':id1', p_existingId);
24
25   -- Define the output variables.
26   DBMS_SQL.DEFINE_COLUMN(v_cursorId, 1, v_id);
27   DBMS_SQL.DEFINE_COLUMN(v_cursorId, 1, v_name, 30);
28
29   -- Execute the statement.
30   v_dummy := DBMS_SQL.EXECUTE(v_cursorId);
31
32   -- Fetch loop for the records returned.
33   LOOP
34      IF DBMS_SQL.FETCH_ROWS(v_cursorId)=0 THEN
35    EXIT;
36      END IF;
37
38      -- Retrieve the rows from the buffer into variables.
39      DBMS_SQL.COLUMN_VALUE(v_cursorId, 1, v_id);
40      DBMS_SQL.COLUMN_VALUE(v_cursorId, 2, v_name);
41
42      insert into test2 (id,name) values (v_id,v_name);
43
44   END LOOP;
45
46   DBMS_SQL.CLOSE_CURSOR(v_cursorId);
47  END;
48
49  /

Procedure created.

SQL> exec prTest('test',1);
BEGIN prTest('test',1); END;

*
ERROR at line 1:
ORA-00905: missing keyword
ORA-06512: at "SYS.DBMS_SYS_SQL", line 487
ORA-06512: at "SYS.DBMS_SQL", line 32
ORA-06512: at "OXYGEN.PRTEST", line 20
ORA-06512: at line 1

SQL>

      and we said...
You do not SELECT ... INTO ... in dynamic sql. YOu just select
and bind the output columns.  Here is a small example:

create or replace function get_rows( p_tname in varchar2 )
return number
as
    l_theCursor        integer default dbms_sql.open_cursor;
    l_columnValue    number default NULL;
    l_status        integer;
begin
       dbms_sql.parse(  l_theCursor,
                    'select count(*) from ' || p_tname,
                     dbms_sql.native );

    dbms_sql.define_column( l_theCursor, 1, l_columnValue );
    l_status := dbms_sql.execute(l_theCursor);
    if ( dbms_sql.fetch_rows(l_theCursor) > 0 )
     then
           dbms_sql.column_value( l_theCursor, 1, l_columnValue
);
    end if;
    dbms_sql.close_cursor( l_theCursor );
    return l_columnValue;
end ;
/

it only gets one row but shows the point.  You don't select the
count(*) INTO anything -- you just select it.

Here is a larger, more generic routine.  takes any sql and dumps
it all to a flat file:

create or replace function  dump_csv( p_query     in varchar2,
                                      p_separator in varchar2
default ',',
                                      p_dir       in varchar2 ,
                                      p_filename  in varchar2 )
return number
is
    l_output        utl_file.file_type;
    l_theCursor     integer default dbms_sql.open_cursor;
    l_columnValue   varchar2(2000);
    l_status        integer;
    l_colCnt        number default 0;
    l_separator     varchar2(10) default '';
    l_cnt           number default 0;
begin
    l_output := utl_file.fopen( p_dir, p_filename, 'w' );

    dbms_sql.parse(  l_theCursor,  p_query, dbms_sql.native );

    for i in 1 .. 255 loop
        begin
            dbms_sql.define_column( l_theCursor, i,
l_columnValue, 2000 );
            l_colCnt := i;
        exception
            when others then
                if ( sqlcode = -1007 ) then exit;
                else
                    raise;
                end if;
        end;
    end loop;

    dbms_sql.define_column( l_theCursor, 1, l_columnValue, 2000
);

    l_status := dbms_sql.execute(l_theCursor);

    loop
        exit when ( dbms_sql.fetch_rows(l_theCursor) <= 0 );
        l_separator := '';
        for i in 1 .. l_colCnt loop
            dbms_sql.column_value( l_theCursor, i, l_columnValue
);
            utl_file.put( l_output, l_separator || l_columnValue
);
            l_separator := p_separator;
        end loop;
        utl_file.new_line( l_output );
        l_cnt := l_cnt+1;
    end loop;
    dbms_sql.close_cursor(l_theCursor);

    utl_file.fclose( l_output );
    return l_cnt;
end dump_csv;
/

create or replace procedure test_dump_csv
as
    l_rows  number;
begin
    l_rows := dump_csv( 'select * from all_users where rownum <
25', ',', '/tmp', 'test.dat' );
end;
/

Review &
      Followup

      Rating:
      5
dbms_sql  December 18, 2002
Reviewer:
      atul  from india
Sir,

I want to run some alter system commands from normal account...

I found package written by JPL from his site

script is

The script:
rem
rem    Script:        flush.sql
rem    Author:        Jonathan Lewis
rem    Dated:        25th April 1997
rem    Purpose:    Create packaged procedure to flush shared pool
rem
rem    Notes:        Script to be run by SYS or other user that has
rem            received the ALTER SYSTEM privilege directly and
rem            not through a role.
rem
create or replace package flush_pool as
    procedure flush_pool;
end;
/

create or replace package body flush_pool as
procedure flush_pool is
    flush_cursor     integer;
    m_junk        integer;
begin
    flush_cursor := dbms_sql.open_cursor;
    dbms_sql.parse (flush_cursor,
            'alter system flush shared_pool',
            dbms_sql.v7
    );
    m_junk := dbms_sql.execute(flush_cursor);
    dbms_sql.close_cursor(flush_cursor);
    exception
        when others then
            if dbms_sql.is_open(flush_cursor) then
                dbms_sql.close_cursor(flush_cursor);
            end if;
end    /* procedure */;
end    /* package */;
/

create public synonym flush_pool for sys.flush_pool;
grant execute on flush_pool to public;

I ran this and it worked well...
Only thing i want is...
This only gives output like "procedure executed successfully"

But also i want to know the output of the command
"alter system flush shared_pool"...

Also by this can we shutdown oracle from normal user?how?

Thanks.
atul

          Followup:

be careful with that axe eugene (obscure reference to a really old pink floyd
song)...

I hope you know what you are doing with that one -- flushing the shared pool is
NOT something you want people to be doing.  Anyway....

there is no output from a flush shared pool -- it just either

o succeeds (procedure executed successfully)
o fails (ORA-01031 issuficient privs or something)

there isn't any output to take a gander at.

You cannot shutdown from "normal user" (and boy oh boy -- you don't want to).
SYSDBA or SYSOPER accounts can shutdown.

      Rating:
      5
Timeliness  December 19, 2002
Reviewer:
      Connor McDonald  from UK
Atul,

You might want to take a look at the date on that script - its 1997.  Flushing
the shared pool might have been relevant back then when (pre8i) the shared pool
lists could get excessively long...

This was resolved in 816, so as Tom says, its unlikely you'll need this anyway

hth
connor

      Rating: 4
error executing
      dump_csv  December 19, 2002
Reviewer:  Anil
       from India
Hi Tom
Version : Oracle 8.1.7.2 on Solaris 2.8

I get an error when I try to run this dump_csv function, I have changed your
procedure to check if the file is opening or not. Please find
below the script.FYI , INIT.ORA UTL_FILE_DIR is set

create or replace function  dump_csv1( p_query     in varchar2,
                                      p_separator in varchar2 default ',',
                                      p_dir       in varchar2 ,
                                      p_filename  in varchar2 )
return number
is
    l_output        utl_file.file_type;
begin
    l_output := utl_file.fopen( p_dir, p_filename, 'w' );

    if utl_file.is_open(l_output) then
       utl_file.fclose( l_output );
       return 1;
    else
       return 0;
    end if;

exception
   when others then
      raise_application_error(-20001,sqlerrm);
end dump_csv1;

create table test(t number,tt number)

declare
begin
for i in 1..100
loop
  insert into t values(i,i);
end loop;
end;

exec :x:= dump_csv1( 'select * from test', ',','/tmp', 'test.dat' )

create or replace function  dump_csv1( p_query     in varchar2,
                                      p_separator in varchar2 default ',',
                                      p_dir       in varchar2 ,
                                      p_filename  in varchar2 )
return number
is
    l_output        utl_file.file_type;
begin
    l_output := utl_file.fopen( p_dir, p_filename, 'w' );

    if utl_file.is_open(l_output) then
       utl_file.fclose( l_output );
       return 1;
    else
       return 0;
    end if;

exception
   when others then
      raise_application_error(-20001,sqlerrm);
end dump_csv1;

ERROR at line 1:
ORA-20001: USER-Defined exception
ORA-06512: at "TEST.DUMP_CSV1", line 20
ORA-06512: at line 1

Kindly give me a pointer on what could be the possible reasons for this error.

Thanks & Regards,
Anil

          Followup:

your init.ora parameter is not set

or

it is set improperly (eg: not to /tmp)

or

the file test.dat exists and Oracle cannot open it for writing

suggestion: remove the exception handler so you can actually see what line is
flinging the exception -- you don't know if it is the fopen, is_open or fclose
right now.

      Rating: 4
error executing
      dump_csv  December 19, 2002
Reviewer:  Anil
       from India
Hi Tom:
Ignore the anonymous plsql block - I have changed it to table name test instead
of t and ran it. THe table is created and the dump_csv1 gives this error.

Thanks
Anil

      Rating: 4
error executing
      dump_csv  December 19, 2002
Reviewer:  Anil
       from India
Hi tom:
Thanks tom, tmp is the sub directory of the UTL_FILE_DIR, I was under the
impression that it can write to the sub directory.

Thanks & Regards,
Anil

          Followup:

no, only explicit paths.  no subdirectory traversal.

I am concerned that you put "/" in there -- you don't want to have people
writing to "/"

      Rating: 5
Can you please
      guide me on how can I re-write this function using
      DBMS_SQL  September 18, 2003
Reviewer:
      Steve  from new york
FUNCTION view_cmc_products (
      p_product            IN   VARCHAR2,
      p_dosage             IN   VARCHAR2,
      p_units              IN   VARCHAR2,
      p_uom                IN   VARCHAR2,
      p_code               IN   VARCHAR2,
      p_comments           IN   VARCHAR2,
      p_created_by         IN   VARCHAR2,
      p_create_date_from   IN   VARCHAR2,
      p_create_date_to     IN   VARCHAR2,
      p_category           IN   VARCHAR2,
      P_UPDATE             IN   VARCHAR2,
      p_order              IN   VARCHAR2,
      P_DESC               IN   VARCHAR2
   )
      --return varchar2
      RETURN cmc_products_cursortype
   IS
      v_log   cmc_products_cursortype;
      str     VARCHAR2 (3000);

   BEGIN
/********************************************************************
RETURNED FIELDS, PASS BACK THE SAME NUMBER TO ORDER BY THAT FIELD
   1 --ID
   2 --PRODUCT
   3 --DOSAGE
   4 --UNITS
   5 --UOM
   6 --CODE
   7 --CREATED BY
   8 --DATE CREATED
   9 --COMMENTS
   10--REASON_FOR_CHANGE
   11--CATEGORY
   12--UPDATE STATUS

   ******************************************************************/
      str    :=
         'SELECT /*+ RULE*/ ID,
   CMC_PRODUCTS_PKG.GET_COMPONENT_NAME(COMPONENT_FK_ID) PRODUCT,
   DOSAGE_PKG.GET_DOSAGE_DESC(DOSAGE_FK_ID) DOSAGE,
   UNITS ,
   UOM_PKG.GET_UOM(UOM_FK_ID) UOM,
   NVL(CODE,''&nbsp;'') CODE ,
    COLUMNS_JAVA_PKG.get_chemist_name_by_usid(CREATED_BY) CREATED_BY,
   TO_CHAR(DATE_CREATED,''DD-MON-YYYY'') DATE_CREATED,
   NVL(COMMENTS,''&nbsp;'') COMMENTS ,
   NVL(REASON_FOR_CHANGE,''&nbsp;'') REASON_FOR_CHANGE ,
   CATEGORY_PKG.get_category_desc(CATEGORY_FK_ID) CATEGORY,
   UPDATED UPDATE_STATUS
   FROM CMC_PRODUCTS
   WHERE  RECORD_STATUS<>''HISTORY''';

      IF P_UPDATE<>' ' THEN
      STR:=STR||' AND UPPER(CMC_PRODUCTS_PKG.CHECK_FOR_UPDATE(ID)) LIKE
UPPER('''||P_UPDATE||''')';
      END IF;
      IF P_CREATE_DATE_FROM<>' '  THEN

      STR:=STR||' AND
TO_CHAR(DATE_CREATED,''DD-MON-YYYY'')>='''||P_CREATE_DATE_FROM||'''';

      END IF;
      IF  P_CREATE_DATE_TO<>' ' THEN
            STR:=STR||' AND
TO_CHAR(DATE_CREATED,''DD-MON-YYYY'')<='''||P_CREATE_DATE_TO||'''';
      END IF;

      IF p_product <> ' '
      THEN
         str    :=
                str
             || ' AND
UPPER(CMC_PRODUCTS_PKG.GET_COMPONENT_NAME(COMPONENT_FK_ID)) LIKE UPPER(''%'
             || p_product
             || '%'')';
      END IF;

      IF p_dosage <> ' '
      THEN
         str    :=
                str
             || ' AND UPPER(DOSAGE_PKG.GET_DOSAGE_DESC(DOSAGE_FK_ID)) LIKE
UPPER(''%'
             || p_dosage
             || '%'')';
      END IF;

      IF p_units <> ' '
      THEN
         str    :=
                str || ' AND UPPER(UNITS) LIKE UPPER(''%' || p_units
                || '%'')';
      END IF;

      IF p_uom <> ' '
      THEN
         str    :=
                str
             || ' AND UPPER(UOM_PKG.GET_UOM(UOM_FK_ID)) LIKE UPPER(''%'
             || p_uom
             || '%'')';
      END IF;

      IF p_code <> ' '
      THEN
         str    :=
                  str || ' AND UPPER(CODE) LIKE UPPER(''%' || p_code
                  || '%'')';
      END IF;

      IF p_comments <> ' '
      THEN
         str    :=
                str
             || ' AND UPPER(COMMENTS) LIKE UPPER(''%'
             || p_comments
             || '%'')';
      END IF;

      IF p_created_by <> ' '
      THEN
         str    :=
                str
             || ' AND UPPER(
COLUMNS_JAVA_PKG.get_chemist_name_by_usid(CREATED_BY)) LIKE UPPER(''%'
             || p_created_by
             || '%'')';
      END IF;

      IF p_category <> ' '
      THEN
         str    :=
                str
             || ' AND UPPER(CATEGORY_PKG.get_category_desc(CATEGORY_FK_ID)) LIKE
UPPER(''%'
             || p_category
             || '%'')';
      END IF;

      IF p_order <> ' '
      THEN
         str    := str || ' ORDER BY UPPER(TRIM(' || p_order || '))
'||P_DESC||'';
      END IF;
      --return str;
      OPEN v_log FOR str;
      RETURN v_log;
      CLOSE v_log;

   END view_cmc_products;

          Followup:

you cannot use dbms_sql to open a ref cursor.

anyway, you want to use binds (i assume that is why you asked about dbms_sql)
so...

here is how to do that:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279
you do understand that last line of code -- close v_log -- never actually
happens right?  it is never executed (good thing -- else you would never get
your data!!!)

delete that line of code, it'll only confuse someone.

(all of those function calls to plsql in the predicate are pretty scary -- seems
like you could just use SQL probably - be a big increase in performance!)

      Rating:
      4
THANKS  September 18, 2003
Reviewer:
      A reader
THE REASON I WANTED TO USE DBMS_SQL IS BECAUSE YOU STATED IN YOUR NEW BOOK THAT
IF I AM GOING TO USE THE CODE ALL THE TIME
I SHOULD USE DBMS_SQL INSTEAD OF EXECUTE IMMEDIATE BECAUSE THE PERFORMANCE WILL
FASTER. ANYWAY, I JUST READ THE LINK AND I WAS WONDERING IF YOU HAVE SOMETHING
LIKE THIS USING DBMS_SQL.

THANKS

          Followup:

but you are dynamically building a query based on inputs (and you caps lock key
is stuck?)

you cannot use dbms_sql here.  the query changes from execution to execution.

but -- i also say you should use bind variables (over and over i say that). that
-- that NEEDS (as in MUST) be fixed....

i'd lose those function calls as much as possible as well.

      Rating:
      5
bravo!!!!!  September 18, 2003
Reviewer:
      A reader

September 18, 2003 Abhi -- Thanks for the question regarding "Update
      Table", version 8.1.5

      You Asked
hi ...

I am using 8.1.5 on Win NT.
what is the efficient way for updating column B in this scnario ..

 I want update column B incrementaly by  groupping  on columns A,B,C ...etc.  [
Ignoring ',' '*' ';' ',' '.' ]
 here I m groupping on only One Column..

SQL> select * from test;

A                             B
-------------------- ----------
First,second,third
First,second,third
First,second,third
First;second;third
First;second;third
First;second;third
First*second*third
First*second*third
First.second.third
First.second.third
Six,eight,nine
Six,eight,nine
Six.eight.nine
Six.eight.nine
Six.eight.nine
Six;eight;nine
Six;eight;nine
Six;eight;nine
Six*eight*nine
Six*eight*nine
Six*eight*nine

         --- Example ...

SQL> select * from test;

A                             B
-------------------- ----------
First,second,third            1
First,second,third            1
First,second,third            1
First;second;third            1
First;second;third            1
First;second;third            1
First*second*third            1
First*second*third            1
First.second.third            1
First.second.third            1
Six,eight,nine                2
Six,eight,nine                2
Six.eight.nine                2
Six.eight.nine                2
Six.eight.nine                2
Six;eight;nine                2
Six;eight;nine                2
Six;eight;nine                2
Six*eight*nine                2
Six*eight*nine                2
Six*eight*nine                2

21 rows selected.

      and we said...
in 815, without analytic functions, this is going to be virtually impossible to
do "efficiently"

In 816 and up, with analytics, we can easily:

ops$tkyte@ORA920> select a, dense_rank() over ( order by
translate(a,',*.;',',,,,') ) dr
  2   from t;

A                                      DR
------------------------------ ----------
First,second,third                      1
First,second,third                      1
First,second,third                      1
First;second;third                      1
First;second;third                      1
First;second;third                      1
First*second*third                      1
First*second*third                      1
First.second.third                      1
First.second.third                      1
Six,eight,nine                          2
Six,eight,nine                          2
Six.eight.nine                          2
Six.eight.nine                          2
Six.eight.nine                          2
Six;eight;nine                          2
Six;eight;nine                          2
Six;eight;nine                          2
Six*eight*nine                          2
Six*eight*nine                          2
Six*eight*nine                          2

21 rows selected.

we'd be able to use that to either

a) efficiently to a CREATE TABLE AS SELECT to "update" the value

b) array (bulk collect) the rowid and dense_rank() values in plsql 100-500 rows
at a time and then forall update column b by rowid

but in 815, you have issues just CREATING that value.  we can use a scratch
table to facilitate this:

ops$tkyte@ORA815> create table temp
  2  ( trans_a primary key, r )
  3  organization index
  4  as
  5  select trans_a, rownum r
  6    from (
  7  select distinct translate(a,',*.;',',,,,') trans_a
  8    from t
  9             )
 10  /

Table created.

ops$tkyte@ORA815>
ops$tkyte@ORA815> update t
  2     set b = ( select r from temp where trans_a =
translate(t.a,',*.;',',,,,') )
  3  /

21 rows updated.

ops$tkyte@ORA815> select * from t;

A                                       B
------------------------------ ----------
First,second,third                      1
First,second,third                      1
First,second,third                      1
First;second;third                      1
First;second;third                      1
First;second;third                      1
First*second*third                      1
First*second*third                      1
First.second.third                      1
First.second.third                      1
Six,eight,nine                          2
Six,eight,nine                          2
Six.eight.nine                          2
Six.eight.nine                          2
Six.eight.nine                          2
Six;eight;nine                          2
Six;eight;nine                          2
Six;eight;nine                          2
Six*eight*nine                          2
Six*eight*nine                          2
Six*eight*nine                          2

21 rows selected.

Review &
      Followup

      Rating: 4
Re: Update
      Table  September 16, 2003
Reviewer:  Abhi
      Malhotra  from india
Thanks Sir
           For your quick response.

Sir I was doing like this.

SQL> update a set b = rownum where
  2  rowid in ( select min(rowid) from a group by translate(a,'.,;*','....'))
  3  /

2 rows updated.

  1  update a tab set b = ( select b from a where b is not null and
  2                         translate(tab.a,'.,;*','....') =
translate(a,'.,;*','....')
  3*                      )
SQL> /

21 rows updated.

SQL> select * from a;

A                                  B
------------------------- ----------
First*second*third                 1
First*second*third                 1
First,second,third                 1
First,second,third                 1
First,second,third                 1
First.second.third                 1
First.second.third                 1
First;second;third                 1
First;second;third                 1
First;second;third                 1
Six*eight*nine                     2
Six*eight*nine                     2
Six*eight*nine                     2
Six,eight,nine                     2
Six,eight,nine                     2
Six.eight.nine                     2
Six.eight.nine                     2
Six.eight.nine                     2
Six;eight;nine                     2
Six;eight;nine                     2
Six;eight;nine                     2

21 rows selected.

          Followup:

do that on a big table and see how it scales (eg: it won't)

that second update full scans the original table for each row it updates unless
you create a function based index on b,translate(a,....)

      Rating: 2
Re: Update
      Table  September 17, 2003
Reviewer:  Rory R.
       from Omaha, NE
Am I missing something, or would the following work best for what he's asking
for?

SQL> create table x as select * from dba_tables;

Table created.

SQL> alter table x add (counter number);

Table altered.

SQL>
SQL> update x a1
  2  set counter = (select counter
  3                 from (select owner, tablespace_name, rownum counter
  4                       from (select distinct owner, tablespace_name
  5                             from x)) a2
  6                 where a1.owner = a2.owner
  7                 and nvl(a1.tablespace_name, '-1') = nvl(a2.tablespace_name,
-1))
  8  /

1044 rows updated.

SQL> select distinct owner, tablespace_name, counter
  2  from x
  3  /

OWNER              TABLESPACE_NAME         COUNTER
------------------ ----------------------- ----------
CTXSYS             DRSYS                            1
CTXSYS                                              2
HR                 EXAMPLE                          3
HR                                                  4
MDSYS              SYSTEM                           5
ODM                ODM                              6
<etc>

          Followup:

that doesn't do what he was doing?  no translate or anything?

the "fastest" way i believe is to create that temp table as i did and update --
if there are lots of rows

      Rating: 2
Re: Update
      Table  September 18, 2003
Reviewer:  Rory R.
       from Omaha, NE USA
Well, I didn't use the translate but the translate itself didn't appear to be
germane to the overall performance issue being discussed. Sorry, should have
kept the example similar.
And I notice that how you created your temp table is what I was trying to get
across by using an inline view.

Here's my question boiled down:
   Is it better to do as you did and create a temp table to query from
repeatedly, or would it be better to do as I did and keep the data resident
within an inline view?
   Is the answer dependent on the amount of data generated by the inline view?

          Followup:

its a correlated subquery, not an inline view though.  thats the problem.

that inline view is done "once per row" theoretically

my temp table is done "once per update"

September 18, 2003 Bob -- Thanks for the question regarding
      "SQL*Loader - How can I put header info on each record", version 9.2.0

      You Asked

Hi, I have a question regarding SQL*Loader. I am parsing a datafile that has a
header record that contains information that I need to store on each record
being inserted into the database. My data file looks something like:

BOF - 09/01/2003
ABCD   132.43   DJI
ABCD  2344.54   SJY
ABCD    50.51   IRJ

I would like to store the data in the table like:

----------  ----  -------   ---
09/01/2003  ABCD   132.43   DJI
09/01/2003  ABCD  2344.54   SJY
09/01/2003  ABCD    50.51   IRJ

So basically, I want to read the date from the first line and include it in each
of the following records that get loaded into the DB.

I've looked at using 'FILLER' datatypes a bit, but I'm not sure that this is the
correct solution.

Any help would be greatly appreciated.

Thanks, Bob.

***************************

It would be okay if I had to run SQL loader twice to get this to work.

      and we said...
Ok, so you have a pair of tables:

ops$tkyte@ORA920> create table t1 ( x date );
Table created.

ops$tkyte@ORA920> create table t2 ( c1 varchar2(4), c2 number, c3 varchar2(3),
c4 date );
Table created.

ops$tkyte@ORA920> !cat test.dat
BOF - 09/01/2003
ABCD   132.43   DJI
ABCD  2344.54   SJY
ABCD    50.51   IRJ

ops$tkyte@ORA920> !cat t1.ctl
LOAD DATA
infile test.dat
replace
INTO TABLE T1
FIELDS TERMINATED BY '-'
TRAILING NULLCOLS
( foo filler,
  x date "mm/dd/yyyy"
)

ops$tkyte@ORA920> !sqlldr / t1 load=1
SQL*Loader: Release 9.2.0.3.0 - Production on Thu Sep 18 16:16:14 2003
Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.
Commit point reached - logical record count 1

ops$tkyte@ORA920> select * from t1;

X
---------
01-SEP-03

ops$tkyte@ORA920> !cat t2.ctl
LOAD DATA
infile test.dat
replace
INTO TABLE T2
FIELDS TERMINATED BY whitespace
TRAILING NULLCOLS
( c1,
  c2,
  c3,
  c4 "(select x from t1)"
)

ops$tkyte@ORA920> !sqlldr / t2 skip=1

SQL*Loader: Release 9.2.0.3.0 - Production on Thu Sep 18 16:16:14 2003
Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.
Commit point reached - logical record count 4

ops$tkyte@ORA920> select * from t2;

C1           C2 C3  C4
---- ---------- --- ---------
ABCD     132.43 DJI 01-SEP-03
ABCD    2344.54 SJY 01-SEP-03
ABCD      50.51 IRJ 01-SEP-03

September 18, 2003 Sajid -- Thanks for the question regarding "WEB
      PLSQL", version 9.2.0

      You Asked
Hi Tom,

One easy question for you. I was configuring TESTDB DAD for SCOTT schema. In
that screen i found column for document path. I read somewhere on the NET that
is virtual path where you keep HTML and other documents. Usually it is set to
'doc'. But i dont understand when i set that to 'doc' where actually i have to
keep my html file for that DAD.I mean the actual location like 'D:/TESTDB/html'

Thanks in advance.

Regards,
Sajid Anwar
London

      and we said...
See

http://otn.oracle.com/docs/products/ias/doc_library/90200doc_otn/web.902/a92173/confmods.htm#1018106
it is NOT a directory on the filesystem at all -- it is a string your users
would use in a URL if you choose to use the file upload/download feature of
mod_plsql.
Review & Followup

      Rating: 5
Thanks a
      million  November 07, 2002
Reviewer:  Sajid
      Anwar  from London

      Rating:
      2
  September 18, 2003
Reviewer:  Mark
       from KC
Tom,  In older versions like OAS you could define Virtual Path that relate to a
Physical Path.

ie.  /docs/  D:\SOME_LONG_PATH_NAME\word_docs\
     /img/   D:\SOME_LONG_PATH_NAME\images\
     /pdf/   D:\SOME_LONG_PATH_NAME\pdf\

Can you still do this or do you HAVE to load all of these files in the database?

I am using Apache/MOD_PLSQL/Oracle 9.2.0.4

Thanks

          Followup:

you can still do that, the apache webserver serves up files quite handily

      Rating: 5
Virtual
      Path  September 18, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
To add an 'Alias' in the Oracle HTTP Server powered by Apache, point your
favorite text editor at $ORACLE_HOME/Apache/Apache/conf/httpd.conf and search on
the keyword 'Alias'.  Should be easy to sort out from there.

HTH,

Mark

      Rating: 4
Marvel is
      off....  September 18, 2003
Reviewer:
      robert  from ct
>>» Just FYI -- we are in the path of "hurricane isabel", there is a better then
good chance we'll be knocked off

looks like marvel.oracle.com just dropped off....
Isn't it hosted in your site ?

          Followup:

they took down many of the machines as a precaution.

we opted to keep a couple running -- this being one of them.  if the power goes
away, they'll have to shut these down too.

getting really windy out there....

September 18, 2003 Meyer -- Thanks for the question regarding "How
      Oracle Works", version 816-9ias

      You Asked
Tom,

I have always been amazed/fascinated with Oracle, and how insanely awesome it
was designed and works.

I have often been curious, what language(s) are used to create Oracle at the
lowest levels and up (and over time has the base language changed) , do those
developers use GUI tools?. Is there one brain that gets most things
done/delegates, small or huge teams of people? Are any of the FIRST developers
still making it better. Is the development centralized at HQ or spread out
around the world (internet style)?

Thanks,
Meyer

      and we said...
You want to read:

http://otn.oracle.com/oramag/oracle/01-nov/o61cover.html
Oracle was originally written in fortran and then redone in C, which it has been
written in ever since.  In fact, when we ported Oracle to the mainframe for the
first time we were faced with an issue -- do we rewrite Oracle in assembler
(there was no c compiler on the mainframe back then) or do we write a C
compiler?  We wrote a C compiler.

Yes, the developers use GUI tools for source code control, an IDE for
development/debugging.  It takes a team of literally hundreds of people -- there
is no single "brain" (thankfully, progress would be very slow otherwise).  Yes,
many of the first developers are still here (read the above article, they
interview one or two).  The development is (and has been for a very long time,
before "internet style" was cool even) very decentralized.  There is development
going on 24 hours a day pretty much from the east coast of the US (we have a new
england development center) to the west cost (portland and redwood shores) to
australia, india and moving into europe -- the UK and Ireland.
Review &
      Followup

      Rating:
      4
  August 31, 2002
Reviewer:  CG
       from Caracas - Venezuela

      Rating: 5
Great
      Information  August 31, 2002
Reviewer:
      Meyer
Thanks, this was very interesting.

Do you think it will go from C to java?

Thanks again!

Meyer

          Followup:

Hmm, let me think about that...

No.  doubt it.  Not this decade anyway.

      Rating: 5
OH working with
      MS?  September 01, 2002
Reviewer:  Sikandar
      Hayat Awan  from Pindi Bhattian - Pakistan
Hi TOM,
Thanks for the above info but let me know how you are working with Microsoft?
(MS C++) Unbelievable...as MS dominating strategies. As in Oracle Applications
you have to get MKS toolkit to integrate with windows.

          Followup:

I don't understand what you are asking?

      Rating: 4
Oracle History
      - Book Suggestion  September 02, 2002
Reviewer:
      Robert  from PA
If you're really interested in how Oracle grew from an (borrowed ?) idea from
the beginning to what it is today...the gory details...ups & downs ...anecdote ,
there's a good book "The Difference Between God and Larry Ellison", very
interesting.

      Rating: 5
Interesting
      information  May 06, 2003
Reviewer:  Ian

Just out of curiosity, I wanted to know, whether you were also involved in
development of the core database (since, you are pretty comfortable with C/C++).

          Followup:

Nope, never worked on the kernel - that, that would be a career path ;)

      Rating: 3
What about the
      designers & developers?  May 06, 2003

Reviewer:  A reader
Much publicity and respect is given to Oracle's best-known and successful "power
users" such as Tom, Jonathan Lewis, Anjo Kolk, Cary Millsap, Steve Adams and
Connor McDonald to name but a few. Their knowledge of the database and how to
use it is immense, granted, but what about the guys who design and develop the
database itself? I'd like to see MUCH more respect go out to those guys - they
must be awesome designers/programmers. Without them, there wouldn't be an Oak
Table.

          Followup:

they are all over the place.  goto otn.oracle.com -- today for example, Wim
Coekaerts is right there.  OracleWorld has the "meet the guru's" forum.  They
write for Oracle Magazine - and so on.

why do you think they are "not respected"

      Rating: 5
How about C to
      VC++ ?  May 06, 2003
Reviewer:  Ashq
      Shamsuden  from Trichy, Tamil Nadu , India
Tom ,

Why not give a try from C --> VC++?

          Followup:

why -- so I could be tied to an operating system that I don't even have running
on any computers??  VC++ doesn't run too well on my computers.

Even so, C++ -- yuck, I tried it -- never saw the point.  I've seen lots of
failed C++ projects, a few successful ones.

      Rating: 4
Expected the
      same answers  May 06, 2003
Reviewer:  Ashiq
      Shamsudeen  from Trichy, Tamil Nadu, India
I was expecting the same  answers Tom ;)

      Rating:
      5
  May 06, 2003
Reviewer:
      A reader
Hi Tom,
Your knowledge of the database is so great, that its actually hard to believe
that you didn't write anything for the database.
Infact, initially I used to think that in tkprof (tool kit profiler) tk stands
for "Thomas Kyte".

I think, you like C but not C++. Any specific reasons? C++ is also C with
Classes. I also did a few projects in C++ before migrating to databases. The
only thing that I discovered so far is that, in each technology everythings
boils down to the design /architecture and programming methodology.
Ultimately onus lies in the hands of the Developer

          Followup:

I just didn't like C++, found it was always trying to be "too cute" for its own
good.

C -- lean, mean, efficient, sparse, a great low level tool.

      Rating: 5
Very
      interesting  May 06, 2003
Reviewer:
      A reader  from London
Tom,

Do the oracle kernal programmers still use their own homegrown compiler? Have
they written their own compiler for each different target platform?

          Followup:

no, we use the system c compilers.  As i recall -- it was the mainframe that we
had a choice "write a C compiler or port Oracle to assembler".  We wrote a C
compiler -- but now we use the system C compiler.

      Rating: 4
Why not
      Java?  May 06, 2003
Reviewer:  Kashif
       from Houston, TX
Hi Tom,

Excuse my ignorance, but why would you say that Oracle will not be re-written in
Java? Is it too slow? Or is it just the sheer effort in re-writing the code? Or
something else altogether.

Kashif

P.S. On a side note, that TK comment by one of the posters cracked me up, I
nearly fell off my chair laughing...

          Followup:

I seriously doubt it -- i certainly *hope* not.

btw, the tk in tkprof is ToolKit as far as I know.

      Rating:
      4
  May 06, 2003
Reviewer:
      A reader  from CA, USA
With Oracle being such a large, complex and critical system, I always wonder how
you test changes. To always insure the integrity of the data you must do some
serious testing. How do you manage to test all the different options and
features that will be running in today’s large and complex computing
environments?

BTW speaking of recognizing GURUs it was sad to note the recent passing of Ted
Codd, the super GURU that started it all.

          Followup:

you use automated testing software with regression tests.  The oracle database
itself is probably undergoing tests 24/7 -- by the time they are done with one
set, a new build is there waiting to be tested again.

with your own applications, you need to have the same sort of facilities.
Unless of course you want to be down after every update (updates to your code,
the OS, the database -- whatever)

      Rating: 5
About
      Oracle  May 06, 2003
Reviewer:  Reaz
       from Dhaka, Bangladesh
Frankly, in this part of the world Oracle is still in a exploring stage. So one
can imagine the hard time  we are facing on letting people getting to know the
power of Oracle. Most of the IT guys still prefer to go on flat file system
because of lack of interest or unwillingness to learn RDBMS.

This discussion has definetely helped me to understand Oracle better and looking
forward to know more about it.

Reaz/Dhaka
Bangladesh

      Rating: 3
Very
      Interesting  May 07, 2003
Reviewer:  Kumar
       from India
Tom,
I found this particular thread very interesting.. Its hard to believe that you
did not have any role in kernal development.
And I fully agree with you on C vs C++. C is simple and powerful. C++ may reduce
development time in some cases but makes things complex..

      Rating: 3
Oracle
      Source  May 07, 2003
Reviewer:  Robert
       from PA
Tom,
a few people in his thread mentioned your deep knowledge
of the product and your background in C...
I am wondering if you ever have had actually a look at (portion of) the source
code.
Sort of "peek inside the gut of the monster" ?
Thanks

          Followup:

sure, snippets come up all of the time.  I've never been terribly interested in
looking at it in any sort of "depth".  It is just code after all (and lots of
it)

      Rating: 5
Very
      Interesting thread  May 07, 2003
Reviewer:
      A reader
I don't know whether Its the right forum, but I would also like to pay my
tribute to the Original Guru Ted Codd.

Tom, Did you have the priviledge of meeting him in person?

          Followup:

Nope.  read his books though.

      Rating: 5
C
      Compiler  May 13, 2003
Reviewer:  Jerry
       from Washington, DC USA
Interesting thread.  Love history.  When you say "system c compiler" are you
referring to the good ole "cc" compiler on Unix?

          Followup:

the officially supported compiler on that system...

gcc on linux
sparcworks on solaris
the optional ansi C compiler on hp/ux
visual C/C++ on windows
....

      Rating: 4
Smart SQL
      SERVER FoolHardy Oracle  May 13, 2003
Reviewer:
      A reader
I have seen that Whatever is lacking in Oracle, SQLSERVER brings that up in a
user-friendly tool.Oracle then realises its mistake ,wakes from its sleep and
then brings those features aboard.eg ROLLUP ,CUBE,CASE usage
Microsoft learns from Oracle and tries to beat it at its own game though
unsucessfully.But it is appealing for small DB customers.
I donot know why Oracle does things in a difficult manner whereas the same thing
is so easy to do in SQLSERVER gui tools.
Example to export or import a csv file you have to write code in Oracle ,
SQLSERVER has DTS for that.
I think Oracle works with the concept that if something is simple it cant be
good , so unnecessarily make it tough.
Any COmments.

          Followup:

what the heck are you talking about?

maybe you just never bothered to read the documentation or look at what is on
the CD?  (hint -- OEM)

      Rating: 5
ignorance is
      bliss  May 13, 2003
Reviewer:  Jim
       from Portland,OR USA
Guess you've never used sqlloader.

      Rating: 3
Have you ever
      used OEM you can do almost every thing as ms-visual
      tools  May 13, 2003
Reviewer:  A reader

      Rating: 3
Well
      Guys  May 13, 2003
Reviewer:  A reader

 OEM is not for developers. The DBA stick with it and always want to control
It.It requires more then devloper privs to use OEM.
Yes Have used SQLLDR but does it not require u to write a control file. Well
SQLSERVER DTS doesnot require any coding.

I feel there is  a  genuine need for ORacle to change its strategy, just learn
from Microsoft as Microsoft learns from Oracle. I am a diehard fan of ORacle
just complaining about missing links.

          Followup:

OEM is for developers.

Developers can in fact use it, the privs are settable.

I just connected with scott/tiger -- all they need is the ability to read the
needed dictionary views.

      Rating:
      5
  May 13, 2003
Reviewer:  Dave
       from Colorado
I read that PL/SQL was originally based on Ada. Do you know whether it inhereted
the robustness of Ada also (strong typing etc), and also whether subsequent
enhancements to PL/SQL have maintained the similarity to Ada? I looked up some
Ada examples on the web and they sure looked pretty familiar.

          Followup:

as an ex-ada programmer, i can say it inherited much of the ada language,
including the strong typing.

the two big things it did not have are

o generics
o tasking

      Rating: 4
More
      input...  May 13, 2003
Reviewer:  Brent
       from Salt Lake City, UT
One of the reasons Oracle is the way it is , is because of flexibilty.

Yes, Sql-loader is a command line tool that requires you to create a script.  It
also give you the ability to run sql-loader from an installation script (for
example, to populate a table from a flat file).  And there are also gui tools
that provide a front end...

I personally like having the flexibility of doing it either way.

      Rating: 3
SQLSERVER so
      easy....  May 13, 2003
Reviewer:  Robert
       from PA
>>I donot know why Oracle does things in a difficult manner whereas the same
thing is so easy to do in SQLSERVER gui tools.

Maybe because SQLSEVER runs on NOTHING but click-click-Windo$z ? "easy" things -
do not last.

      Rating: 4
Heck if the big
      deal is to have some trivial GUI  May 13, 2003

Reviewer:  Jim  from Portland, OR USA
The dts thing is a pretty trivial GUI.  If everything is aligned just right then
it will work, else you have to write code to move it around and massage it. (I
guess that's what tsql is for.)  One could do the same thing for SQLLoader.(just
gen the control file and run it.)

In short, the DTS thing is built to impress managers. (not knowledgeable ones)

      Rating: 4
managers are
      the one ......arent they...!!!  May 13, 2003

Reviewer:  reader  from timbakto
but managers are the one who buy that software......make money available
right!!!

      Rating:
      3
managers...  May 13, 2003
Reviewer:
      robert  from PA
>> but managers are the one who buy that software......make money available
right!!!

Well, they got a job to keep too, no ?
That's why we Oracle junkies have to drill into their styrofoam brain ;)...that
easy and pretty is no good, that
they should not bet their career on fluff, and what really counts is
performance, scalability etc...when the chips are down.

      Rating:
      5
  May 13, 2003
Reviewer:  Dave
       from Colorado
Just to continue my temporary obsession with Ada, let me put forward a scenario.

Suppose you had a team of database developers, all PL/SQL-experienced and
other-language-inexperienced, and you needed to standardize on a language that
will interface with the db from the server -- say for generating EDI files,
moving stuff about, simple command line interfacing etc.. Would you say that Ada
is a reasonable choice then, rather than C or java? Would you care to compare
them in terms of learnability, robustness etc. in such a case, or would the
general pervasiveness of C/Java rule it out?

          Followup:

Ada is sort of "dead" these days.  It would not be a good choice, since the DoD
abandoned it and the corporate world never picked it up....

      Rating:
      5
  May 13, 2003
Reviewer:  Dave
       from Colorado
Ah, so what you're saying is that the developers would be getting training on a
skill that they could use nowhere else? That would not make them more employable
somewhere else?

H'mmmmm. Verrrry interesting.

          Followup:

No, I'm saying that Ada compilers, Ada programmers, Support for the Ada language
is pretty much "not there"

Sort of like programming in APL.  Of great academic interest.

      Rating: 5
Please clear my
      understanding  May 14, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
Dear TOM,
I want to know that as you have mentioned above that you test the Oracle with
different softwares and 24/7. During evaluation of Oracle 9i Rel1 I came to know
that some bugs are removed in Rel 2 of 9i. I think before releasing Rel1 you had
tested it again and again then why these bugs specially in the new features
"Your response in the thread subjected, '9i dynamic SGA, pre-allocated?'".
Do you not think these minor things effect the reliability of the product. We
the DBA's try to convince the Management for new releases but when the
Management come to know that there are bugs in the main features/new features
then they say we are ok with the current one.
What you say about it?

Sorry for asking this question but we face it.

          Followup:

there are bugs in 9iR2, there are bugs in 8iR3.

I don't understand where you are going?

Yes, 9iR2 has fixed things that didn't work exactly right in 9iR1 -- at the same
time however, it released other new features that need a fix, maybe it even
"broke" things that used to work.

Install an OS patch to fix a bug and you've probably added a new "bug".

      Rating:
      5
  May 14, 2003
Reviewer:
      A reader
Hi Sikandar,
Since we as human beings are not completely debugged, there will be bugs in
whatever we do.
Oracle being developed by mere mortals like us is no exception to the rule.
I think the important point is look at the company's background and its future
roadmap.
Companies like Oracle and even Microsoft have brilliant support teams which
provide excellent solutions to the problems/bugs faced by users.
Will you work on a product, which is no longer supported?
(e.g Oracle 8i defect resolution will end on 31-Dec-03. Makes it much more
logical to migrate to 9i) Or will you work on Sybase/interbase, which no body is
sure, whether they will be there after 5 years.
I don't believe in perfection. Nothing is perfect. There is always a better
product. Best doesn't exist..........

PS: The last few threads have deviated from the original question about Oracle
internals/history to comparison between sql server, bugs etc. Let's get back to
the original question.

      Rating: 5
Interesting
      Topic  June 05, 2003
Reviewer:  Sue
       from Michigan
Many years ago, I asked an Oracle instructor what "tkprof" meant -- how did they
name that tool?.  The answer:  "trace kernel profile".

      Rating:
      2
OEM  July 27, 2003
Reviewer:
      A reader
Has any developer who visits this forum , used OEM  to write data to a flat
file.

Tom, in which world are you living. Developers being given access to data
dictionary views to enable them to write to flat files using OEM, is very
difficult happen.

I beg your pardon for saying the above.

It is very very strictly used as a DBA tool.

Do you have specific documentation which explains , how it can be configured for
developers?

          Followup:

just try to connect, it will tell you exactly what you need (that is all I did).

      Rating: 5
Cool
      Cool  July 28, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
I really appreciate Oracle Company, Support and Oracle Development. I am also
dealing only with Oracle Products and fan of Oracle. Now you know in every
company Oracle and MS are working parallel. So we have to face the MS guys. The
thread is "How Oracle Works" so I posted my query here.

Thanks to you Mr. Reader and TOM as well.

      Rating:
      2
OK  July 28, 2003
Reviewer:
      R.Chacravarthi  from Chennai,India
Dear Tom,
Tkprof stands for Trace Kernel Profile.I hope so.What is your response?
Thanks
Yours Sincerely,
R.Chacravarthi

          Followup:

sounds reasonable.

      Rating: 5
...and pl/sql
      ?  September 17, 2003
Reviewer:  ant
       from ny
With all of the improvements to pl/sql in 8i and 9i( like data cartridge
"interfaces" and object inheritance ), is pl/sql or the pl/sql "compiler"
written in c or c++ ?
Since 8i, pl/sql has more and more of an oo feel to it and i was wondering,
underneath it all, is it still just straight c ?

      Rating: 5
What about the
      regression test?  September 18, 2003
Reviewer:
      Cefers.br  from Brasil
Tom,

2 things:

* Could you please tell us a little about the Regression Tests? How exactly are
they applied to the new code?

* How does Oracle Corporation protect its source code? I mean, how do you avoid
that sombody (like a just fired employee) maliciously steals or distribute
pieces of code around the internet?

Thanks.

          Followup:

o you run a test that has known inputs and known outputs.

if outputs are not right, test failed.

most tests are built from "bugs" -- you build a test case that returns the
"wrong stuff".  if the wrong stuff comes out -- you know the bug is back.

o same way anyone protects any intellectual property.

September 18, 2003 Nag -- Thanks for the question regarding "9i
      enhancements", version 8.1.7

      You Asked
Tom

Hi, Iam back from my country after a good vacation. Nice to communicate with you
again.

Tom

I.

I was reading about 9i enhancements , and came to read that 9i has new olap
functions.

What are olap functions, are they the same as analytical functions in 8i.

II. 8i had execute immediate, authid , autonomous transactions , case as some of
the pl/sql enhancements. Can you tell a few 9i pl/sql enhancements.

III.With the advent of external tables hasnt sqlldr become absolete.

Thank you

Thank  you

      and we said...
I) not entirely -- there are many new analytical functions in 9i but they are
not the OLAP services. See:

http://download-west.oracle.com/otndoc/oracle9i/901_doc/nav/docindex.htm#index-OLA
for the OLAP services docs with new features and so on....

II) lots, see

http://download-west.oracle.com/otndoc/oracle9i/901_doc/appdev.901/a89856/whatsnew.htm#967650
III) sqlldr IS the external table!!  The external table has a control file in it
basically.  So, to say sqlldr is "obsolete" would probably be inaccurate -- it
is more accurate to say sqlldr is more easily accessed via an external table.

I can say that with:

o insert /*+ APPEND */ .... (direct path insert)
o MERGE (upsert command)
o multi-table inserts (like the multiple INTO clauses of sqlldr)
o parallel query on fixed length data files
o pipelined functions

the need to run sqlldr as an external process outside of the database is very
much marginalized.  SQLLDR as a separate process might be becoming obsolete.
Review &
      Followup

      Rating: 5
Tom, can you
      give examples and discuss  January 19, 2002

Reviewer:  Nag
the below features...

1.MERGE (upsert command)
2.multi-table inserts (like the multiple INTO clauses of sqlldr)

( BTW: is upsert a combination of update and insert)

          Followup:

Nag -- long term memory needs an update ;)

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1615330671789
This was mind blowing weeks ago.....

Multi-table inserts:

ops$tkyte@ORA9I.WORLD> @demo029
=================================================================

We will demonstrate first a multi-table insert.  we'll segregate
employees by department.  Leftover isn't really used here, just
shows you could do it.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> create table emp_10 as select * from scott.emp where 1=0;

Table created.

ops$tkyte@ORA9I.WORLD> create table emp_20 as select * from scott.emp where 1=0;

Table created.

ops$tkyte@ORA9I.WORLD> create table emp_30 as select * from scott.emp where 1=0;

Table created.

ops$tkyte@ORA9I.WORLD> create table leftover as select * from scott.emp where
1=0;

Table created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> set echo off
Enter to continue

=================================================================

Now we'll do the insert...

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> insert
  2    when (deptno=10) then
  3      into emp_10 ( empno, ename, job, mgr, hiredate, sal, comm, deptno )
  4               values ( empno, ename, job, mgr, hiredate, sal, comm, deptno )
  5    when (deptno=20) then
  6      into emp_20 ( empno, ename, job, mgr, hiredate, sal, comm, deptno )
  7               values ( empno, ename, job, mgr, hiredate, sal, comm, deptno )
  8    when (deptno=30) then
  9      into emp_30 ( empno, ename, job, mgr, hiredate, sal, comm, deptno )
 10               values ( empno, ename, job, mgr, hiredate, sal, comm, deptno )
 11    else
 12      into leftover ( empno, ename, job, mgr, hiredate, sal, comm, deptno )
 13               values ( empno, ename, job, mgr, hiredate, sal, comm, deptno )
 14  select * from scott.emp e
 15  /

14 rows created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> set echo off
Enter to continue

=================================================================

Next, we'll show how you might use this  feature to insert a single
input row into a table many times (pivot input data for example)
We start with a table has the data "in record"

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> create table t ( id int, day1 int, day2 int, day3 int,
day4 int );

Table created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> insert into t
  2  select rownum, rownum+1, rownum+2, rownum+3, rownum+4
  3  from all_objects where rownum <= 100;

100 rows created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> create table t2 ( id int, day varchar2(4), val int );

Table created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> set echo off
Enter to continue

=================================================================

Now, we want a row for each of day1, day2, day3, day4...

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> insert ALL
  2  into t2 ( id, day, val ) values ( id, 'DAY1', day1 )
  3  into t2 ( id, day, val ) values ( id, 'DAY2', day2 )
  4  into t2 ( id, day, val ) values ( id, 'DAY3', day3 )
  5  into t2 ( id, day, val ) values ( id, 'DAY4', day4 )
  6  select * from t;

400 rows created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> select * from t where id = 1;

        ID       DAY1       DAY2       DAY3       DAY4
---------- ---------- ---------- ---------- ----------
         1          2          3          4          5

ops$tkyte@ORA9I.WORLD> select * from t2 where id = 1;

        ID DAY         VAL
---------- ---- ----------
         1 DAY1          2
         1 DAY2          3
         1 DAY3          4
         1 DAY4          5

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> set echo off

      Rating: 5
It is still
      mind blowing , but   January 20, 2002
Reviewer:
      Nag- Tom's ultimate Fan
you have missed the point that I asked about multi table inserts , but not about
external tables.

After learning about external tables from you, I have already implemented in our
dev and test instance.

Further, you have not answered my question on

1.merge- upsert command.

2.Can youalso shed some light on  'parallel query on fixed length data files'.

Thank you

Thank you

          Followup:

1) the "minding blowing" example was the example of the MERGE command (read the
whole thing).  It completely demonstrates this new feature.  So, I did in fact
answer the MERGE command -- just follow the link.

2) if the flat file is fixed length (eg: each record is 100 bytes in length
exactly), then we can do parallel query on it since we can absolutely tell where
records 1 through N and N+1 ... 2*N and 2*N+1 .. 3*N and so on are physically
located in the file.  Hence, we can "chop" the file up into discrete sections
and do parallel reads on it without overlapping.

      Rating: 5
Restartable
      DML...  January 20, 2002
Reviewer:  Nag

Tom

Can you let us know with an example about restartable DML ... feature in 9i.

          Followup:

Ok, here is the scenario.  I want to create a table with a CTAS and it'll
require temp space.  I set up as such:

ops$tkyte@ORA9I.WORLD> create temporary tablespace temp2
  2  tempfile '/tmp/tmp2.dbf' size 256k
  3  extent management local uniform size 32k
  4  /

Tablespace created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> alter user ops$tkyte temporary tablespace temp2;

User altered.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> drop table t;
drop table t
           *
ERROR at line 1:
ORA-00942: table or view does not exist

ops$tkyte@ORA9I.WORLD> alter session set sort_area_size = 32765;

Session altered.

Now, that sort_area_size will ensure pretty much that I need disk based temp
space (and I don't have much of that).  Now I issue:

ops$tkyte@ORA9I.WORLD> create table t
  2  as
  3  select * from all_objects
  4  order by 1, 2, 3, 4, 5, 6, 7, 8
  5  /
select * from all_objects
              *
ERROR at line 3:
ORA-01652: unable to extend temp segment by 4 in tablespace TEMP2

and sure enough, it fails.  Ok, so knowing it might have failed -- I should have
issued:

ops$tkyte@ORA9I.WORLD> alter session enable resumable;

Session altered.

first -- now I do this:

ops$tkyte@ORA9I.WORLD> create table t
  2  as
  3  select * from all_objects
  4  order by 1, 2, 3, 4, 5, 6, 7, 8
  5  /

That statement just sort of sits there.  The DBA has been watching the ALERT log
or monitoring a V$ table or using the EVENT system to send themselves and EMAIL
or PAGE when a resumable statement gets suspended.  (in my case, the DBA was me
and I tailed the alert log).  I see the message:

...
Completed: create temporary tablespace temp2
tempfile '/tmp/t
ORA-1652: unable to extend temp segment by 4 in tablespace              TEMP2
statement in resumable session 'User OPS$TKYTE(107), Session 12, Instance 1' was
suspended due to
    ORA-01652: unable to extend temp segment by 4 in tablespace TEMP2

in the alert log and kick in to action.  I issue:

$ sqlplus /

SQL*Plus: Release 9.0.1.0.0 - Production on Mon Jan 21 09:29:14 2002
(c) Copyright 2001 Oracle Corporation.  All rights reserved.
Connected to:
Oracle9i Enterprise Edition Release 9.0.1.0.0 - Production
With the Partitioning option
JServer Release 9.0.1.0.0 - Production

ops$tkyte@ORA9I.WORLD> alter tablespace temp2 add tempfile '/tmp/tmp2_more.dbf'
size 5m;

Tablespace altered.

and notice in the other session that the CTAS finishes OK:

Table created.

ops$tkyte@ORA9I.WORLD>

thats it.  Thats resumable DDL, just change the CTAS to

ops$tkyte@ORA9I.WORLD> select count(*) from (
  2  select * from all_objects
  3  order by 1, 2, 3, 4, 5, 6, 7, 8
  4  )
  5  /

and you would see the same thing with resumabled DML

      Rating: 5
Flash back
      queries.  January 21, 2002
Reviewer:  Nag

Tom

1.Are flash back queries useful for the developer or the DBA.

2.I observe that one needs the SCN  number to do a flash back query. How can I
as a developer get to know the SCN number of a transaction.

3.A single transaction can have multiple deletes and a single SCN number
identifying all of these deletes. What if I want to flash back only a single
individual delete.

Can yu explain with an example of flashback query.

Thank you

          Followup:

Nag -- time to save the questions and ask them in the correct place.  Sorry.

      Rating: 5
Analytical
      Function in PL/SQL before 9i.  April 27, 2002

Reviewer:  Oscar Chacon  from Quebec, Canada

Hi Tom,

I have to use analytical functions in PL/SQL. I understand they are not
supported (within PL/SQL) until 9i so I'm trying to use EXECUTE IMMEDIATE
instead.

the problem I'm having is the following:

function()...

declare
  a  char(1);

BEGIN

  execute immediate '
     QUERY using variable a HERE
   ';

END

The variable 'a' does not seem to be defined within the execute immediate.

Any solution?

Thanks

Oscar.

          Followup:

execute immediate 'select ... from t where x = :bv' USING a;

use the bind variable syntax documented for execute immediate.

      Rating: 5
Forgot tem
      mention Oracle 8.1.7  April 27, 2002
Reviewer:
      Oscar Chacon  from Quebec, Canada
Hi again,

I forgot to mention in previous post that I am using Oracle version 8.1.7

      Rating: 5
9i
      Features  September 19, 2002
Reviewer:
      Barry  from India
Hi Tom,

Is it possible to handle OLAP queries in PL/SQL.

Regards,
B

          Followup:

yes

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-OLA

      Rating:
      4
  November 14, 2002
Reviewer:
      A reader
Hi Tom,
Are analytical functions available in Oracle 9.2.0 SE?
I am asking you this because the Oracle documentation says yes, but taking into
account that the Oracle 8i documentation also says yes for this feature in SE
and it is wrong, I was wondering if the 9i documentation is also wrong?

Thanks

          Followup:

Not sure -- I don't have any SE releases.  Anyone out there with SE installed
care to comment if they work -- or do I need to file a doc bug?

      Rating: 5
Analytical
      functions in 9i  November 15, 2002
Reviewer:
      Oleksandr Alesinskyy  from Germany
I have not 9.2SE on hand, but in 9.0.1 SE analytical functions works fine.

      Rating:
      3
analytical  July 31, 2003
Reviewer:
      vj  from in
i wanted to implement this in some analytical fn..

select deptno,max(sal) from scott.emp
/

i get the max salary for each department.

Now how can i get name also for that, using analytical function.

          Followup:

select ename, deptno, max(sal) over (partition by deptno)
  from emp;

      Rating:
      3
  August 01, 2003
Reviewer:  vj
       from in
i think i have not explained properly..

SELECT deptno, MAX(sal) max
--over (PARTITION BY deptno)
  FROM scott.emp
  GROUP BY deptno
/

DEPTNO    MAX
10    5000
20    3000
30    2850

i wanted the name of the guy who has the max salary in the same query

like

DEPTNO    MAX   NAME
10    5000  KING
20    3000  SCOTT    [IN THIS CASE THERE ARE TWO]
20      3000  FORD
30    2850  BLAKE

this using the analytical function is that possible..in the production
environment something similar is the requirement..the same thing can be
implemented using writing a inline query for the name, but prefer to use some
analytical fn if possible..

          Followup:

scott@ORA920LAP> select deptno, max_sal, ename
  2    from ( select deptno, max(sal) over (partition by deptno) max_sal, ename,
sal
  3             from emp )
  4   where max_sal = sal
  5  /

    DEPTNO    MAX_SAL ENAME
---------- ---------- ----------
        10       5000 KING
        20       3000 FORD
        20       3000 SCOTT
        30       2850 BLAKE

      Rating:
      3
  August 02, 2003
Reviewer:  vj
       from in
  1  select deptno, min(sal)
  2  from SCOTT.emp
  3* group by deptno
works@9.1.2> /

    DEPTNO   MIN(SAL)
---------- ----------
        10       1300
        20        800
        30        950

works@9.1.2> ED
Wrote file afiedt.buf

  1  select deptno, EMPNO,min(sal)
  2  from SCOTT.emp
  3* group by deptno
works@9.1.2> /
select deptno, EMPNO,min(sal)
               *
ERROR at line 1:
ORA-00979: not a GROUP BY expression

works@9.1.2> --SUPERGROUP
works@9.1.2> SELECT deptno,
  2      MIN(sal),
  3         MIN(EMPNO) KEEP ( dense_rank FIRST ORDER BY sal) empno,
  4         MIN(ENAME) KEEP ( dense_rank FIRST ORDER BY sal) empno
  5  FROM SCOTT.emp
  6  GROUP BY deptno
  7  /

    DEPTNO   MIN(SAL)      EMPNO EMPNO
---------- ---------- ---------- ----------
        10       1300       7934 MILLER
        20        800       7369 SMITH
        30        950       7900 JAMES

works@9.1.2>

      Rating: 5
/*append
      parallel */ and nologging  September 16, 2003

Reviewer:  George  from Chicago
Where to put the nologging clause?

insert /*+append*/
when (dept=10) then
    into emp1 values(xxx)
...
select * from emp /* nologging here??, still seems to be generating mucho redo
*/

Thanks a million.

Tom is the bomb

          Followup:

you do not put nologging on the insert

you alter table emp1 nologging;

then do the operation.  and remember -- INDEXES will ALWAYS generate redo. less
with append, but they will still generate redo (since we cannot "append" to an
index -- we have to "merge" the data into the index and we have to be able to
redo that -- as well as UNDO that)

      Rating: 5
Mult-table
      inserts and cursor sharing  September 18, 2003

Reviewer:  Jeff  from Atlanta
Tom, I noticed something and was hoping you could shed a little light:

Following your emp_10, emp_20 etc example from above, I changed it up a just a
little, making the comm column a literal.

 1  insert
  2       when (deptno=10) then
  3          into emp_10 ( empno, ename, job, mgr, hiredate, sal, comm, deptno )
  4                   values ( empno, ename, job, mgr, hiredate, sal, 100,
deptno )
  5        when (deptno=20) then
  6          into emp_20 ( empno, ename, job, mgr, hiredate, sal, comm, deptno )
  7                   values ( empno, ename, job, mgr, hiredate, sal, 200,
deptno )
  8        when (deptno=30) then
  9          into emp_30 ( empno, ename, job, mgr, hiredate, sal, comm, deptno )
 10                  values ( empno, ename, job, mgr, hiredate, sal, 300, deptno
)
 11*  select * from emp e
scott@UNITTST2>/

14 rows created.

scott@UNITTST2>select * from emp_10;

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
----------
      7782 CLARK      MANAGER         7839 09-JUN-81       2450        100
  10
      7839 KING       PRESIDENT            17-NOV-81       5000        100
  10
      7934 MILLER     CLERK           7782 23-JAN-82       1300        100
  10

scott@UNITTST2>truncate table emp_10;

Table truncated.

scott@UNITTST2>truncate table emp_20;

Table truncated.

scott@UNITTST2>truncate table emp_30;

Table truncated.

scott@UNITTST2>alter session set cursor_sharing=force;

Session altered.

scott@UNITTST2>edit
Wrote file afiedt.buf

  1  insert
  2       when (deptno=10) then
  3          into emp_10 ( empno, ename, job, mgr, hiredate, sal, comm, deptno )
  4                   values ( empno, ename, job, mgr, hiredate, sal, 100,
deptno )
  5        when (deptno=20) then
  6          into emp_20 ( empno, ename, job, mgr, hiredate, sal, comm, deptno )
  7                   values ( empno, ename, job, mgr, hiredate, sal, 200,
deptno )
  8        when (deptno=30) then
  9          into emp_30 ( empno, ename, job, mgr, hiredate, sal, comm, deptno )
 10                  values ( empno, ename, job, mgr, hiredate, sal, 300, deptno
)
 11*  select * from emp e
scott@UNITTST2>/

14 rows created.

scott@UNITTST2>select * from emp_10;

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
----------
      7782 CLARK      MANAGER         7839 09-JUN-81       2450
  10
      7839 KING       PRESIDENT            17-NOV-81       5000
  10
      7934 MILLER     CLERK           7782 23-JAN-82       1300
  10

Why does the statement insert null for the comm column? If you'd rather me
repost in a seperate thread, I can try. Thanks very much

          Followup:

does not reproduce in 9203...  whats your version.

      Rating: 5
Filed a
      TAR  September 22, 2003
Reviewer:  Jeff
       from Atlanta
I was able to reproduce the error fairly consistently and filed a TAR (emphasis
on WAS). They were able to reproduce the problem in house(So I'm not crazy). It
is similar to bug
3132071. Thanks Tom

September 20, 2003 LOUIS -- Thanks for the question regarding "how
      to keep 2 database in synch", version 8.1.7

      You Asked
Hi tom,

Thanks so much for your help in the past. My question
is: I have 2 databases one is the master and the other
is the slave. I want to keep the slave in synch with
the master. However here is the catch, the slave has
a few tables different than the master, I want
to keep the slave intact but but updated from the master.In
others words. I want the new information from the master to
come to the slave (sort of like incremental updates) but I would like the to
updates the slave  without losing the new information from
the master. Can this be done?

Louis

Are the tables in question (the few tables different from the master) YES  also
replicated data? NO  or are they totally different tables? YES

      and we said...
Then it is easy -- just use replication.

When you use replication, you don't have to replicate EVERY single table.  On
the "master" you'll add the tables you want to replicate.  On the copy site --
they will be replicated.  The copy site can have as many other tables as it
likes in addition to these replicated tables.

See

http://technet.oracle.com/doc/oracle8i_816/server.816/a76959/toc.htm
for details.
Review & Followup

      Rating:
      5
  October 01, 2001
Reviewer:
      A reader

      Rating:
      5
BUT...  October 01, 2001
Reviewer:
      A reader
but will replication refresh all the data
from the master db? want to be able to keep
the updated table and have the replication
append the new data to the slave.

L

          Followup:

replication will only refresh the tables you want to have refreshed.

I asked if the tables at the copy site were "different" -- not the same as --
the replicated tables -- you said they were.  Hence replication will work.

Lets say you have the sites MASTER and COPY.

Master has tables t1, t2, t3, t4, ..... tn

Copy replicates tables t1, t2, t3, t4 and has its own tables ta, tb, tc.
replication will maintain t1 ... t4 as copies of the data from master, ta, tb,
tc will be unaffected.

      Rating: 3
A typical
      scenario  September 17, 2002
Reviewer:  Abu
      Ahmadh  from Singapore
Hi Tom,

I have two sets A and B.  Initially, set B is a copy of set A.  Over time, set B
gets its own inserts, updates and deletes.  Now, I want to create a third set C
which will comprise of,

a) all rows from set A
b) all new rows from set B

and separate conflicting rows (may be a log generated), say,
i) rows in set A and set B are different (updated in set B)
ii) rows found in A but not in B (deleted in set B)

What is the best way to accomplish this?

Thanks Tom.

          Followup:

best to have two columns added to B and maintained via a trigger

o date_created
o date_modified

when you copy A to B_Table (yes, B_table, not B), add these two columns and an
insert trigger to maintain one and an update trigger for the other.

then, create a view B as select .... from b_Table (selecting everything but
date_created, date_modified).  Use the view B as if it were a table.

Now to find what you need is a series of simple queries.

      Rating:
      3
sub-set  September 17, 2002
Reviewer:
      A reader
Is there any way to get sub-set of data from the master database to the
replicated database.
Ex. Development instance has only small set of data from production
database..but still maintain the integrity across the tables.

          Followup:

Snapshots do that.

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76959/mview.htm#29854

      Rating: 3
Replication
      support different table structure?  September 18, 2002

Reviewer:  A reader
Can the structure of a table be different at replication site than the master.
Is there any way to replicate such tables?  For eg, table A at Master site has
col1,col2,col3,col4 and table A at replicated site has col1, col2, col5, col6.

          Followup:

no

      Rating: 3
Can this be
      accomplished with Replication / MView  September 19, 2002

Reviewer:  Abu Ahmadh  from Singapore
Tom, With respect to my query above, yes, it can be done via triggers if it is
small number of tables.  But, we have a few hundred tables.  Let's say, we have
two environments (DB) and they have the same set of configuration tables (few
hundreds) initially.  Over time, each DB has its own updates/deletes and
inserts.

What we want to do is, replicate set A -> set B with some exception.

1) If it is a new row in 'A', then replicate to 'B'
2) If it is an update, then do not replicate.  It should tell us that the row is
different. (i.e. compare all columns in a given row to find out if the
replicated row is updated)
3) If it is a deletion, then do not delete at the replicated site.  Rather, it
should tell us (may be by primary key) that the row is deleted in the master
site ('A').

In simple terms, we need to track changes done at both sides.  I hope you
understand our requirement.

Would appreciate your solution.

          Followup:

Sure, I understand that you'll need triggers on the tables in order to maintain
these fields so you can see whats new and whats changed.

Otherwise, you cannot tell the difference between a "new" row at A vs a
"modified" row at A when you goto compare.

1 table, 100 tables, 1000 tables -- what is the difference?  the trigger will
effectively be the same for all -- so, automate the building of the trigger (eg:
like we did with the "create snapshot log" command -- what does that do?  writes
a trigger for the table.  create your own create snapshot log command)

      Rating:
      4
  September 19, 2002
Reviewer:  Tianhua
      Wu  from Atlanta, GA
When you sue replication, you can use logic to use dbms_reputil.replication_off
in you application whenever you connect to the specific site, however you must
be careful, if you use other tools and forget to turn the replication off, data
will replicate to the other site.  Also there are some other issues you need
take care of, for example, primary key.

      Rating: 4
Can't be
      done!  September 19, 2002
Reviewer:
      A reader
Dear Tom,

You said, "1 table, 100 tables, 1000 tables -- what is the difference?  the
trigger will effectively be the same for all -- so, automate the building of the
trigger (eg: like we did with the "create snapshot log" command -- what does
that do?  writes a trigger for the table.  create your own create snapshot log
command)"

 i) Am I right to say that this cannot be done by using any of the existing
features in Oracle without manually writing triggers?

ii) Or, can it be partly accomplished with the use of replication to minimize
writing of triggers?  If yes, how to do that and what type of replication to
use?

Appreciate your reply.  Thanks.

          Followup:

i) you could use our snapshot log if you like.  It is virtually the same
concept, they just use another table.  for every insert/update/delete a log
entry will be made into a snapshot (AKA materialized view) log table.

ii) see i however that just *writes a trigger* for you (as I am suggesting you
do)

      Rating: 4
Oracle
      Utility  September 24, 2002
Reviewer:  Abu
      Ahmadh
Hi Tom,

Thanks for sharing your knowledge so far.  To achieve our requirement, we should
be able to do a "column by column" comparison for a given table across two
schemas to see if rows are different.  I heard my colleague saying, there is
such utility exists in SQL Server.  Just wondering if Oracle has one so that we
don't reinvent the wheel.

Thanks again.

          Followup:

See the DBMS_RECTIFIER_DIFF package found in the supplied packages guide.

      Rating: 5
Snapshots
      without delete  November 26, 2002
Reviewer:
      Harri  from Finland
Hi Tom!

I have almost same situation as original questioner here.

I must replicate few tables from master-db to archive-db
so that only new and updated rows are replicated.
When production db is cleaned(old rows deleted), archived rows in archive db
must stay.

Refreshing need is only once a day.

One important thing is that all replicated tables must be consistent - refresh
must be done in one transaction.
I think 'refresh group' is decision for that.

Is this possible to do with snapshots(MV)? Or is there better way?

-Harri

          Followup:

MV's and all forms of Oracle replication prior to Oracle9i Release 2 will not do
what you ask.

They will ALWAYS propagate the delete, no way to avoid it.

In 9iR2, using STREAMS, you can achieve your goal.  See

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-STR

      Rating:
      5
...  November 26, 2002
Reviewer:
      harri  from Finland
Thanks for the very quick response! It's appears especially amazing to me
because here the time is 2 AM!

What do You say about triggers in this case?

If I use triggers to insert and update rows to another table in master-db and
then do something like this in archive-db:

insert into arch_table
select * from trigger_dest_table@dblink
where last_modified>(select last_modified from arch_table);

          Followup:

You could use a snapshot log to capture inserts/updates and use the mlog tables
"manually" to do your own refresh by primary key

Might be a bit of a less burden on the source system that way.

      Rating: 4
Update data
      from 2 separate database  April 17, 2003
Reviewer:
      Zo A.  from Madagascar
Hi Tom,

Your response from the above question was great, Thanks a lot. Now, we have
Oracle 8.0.5 on a Server Unix in place 8A, and Personal Oracle 7.3 on a
standalone PC in place 7B. They are using the same application (same table ...)
and have their own data.

1) I need to update table A,B,C from 8A to 7B each time a new record has been
updated or inserted in 8A.

2) 7B needs to update table X,Y,Z in 8A without overwriting all records which
are created in 8A.

I'm thinking of extracting data into an ascii file and make a CTL format so that
I can retrieve data by the use of sqlloader. Exchanging those CTL file via MS
Outlook. What do you think ? How to achieve that ?

Thanks

Zo

          Followup:

sounds alot whole lot like an updatable snapshot -- read the replication guide
and give some SERIOUS consideration to using software that is supported and
supportable given you are still IN DEVELOPMENT.

      Rating: 4
Further
      clarification  April 17, 2003
Reviewer:  Rob
       from Pittsburgh, PA
Tom:

When you say:
>MV's and all forms of Oracle replication prior to Oracle9i >Release 2 will not
do what you ask.
>They will ALWAYS propagate the delete, no way to avoid it.

Does this mean that if the base table (the table with the MV log defined on it)
is truncated, that there is no way to avoid the having the truncate passed onto
the MV that is refreshed from that MV log.

We have a situation where a week's worth of records are processed in a work
table, then we want to refresh the MV so that it contains all of the work
table's rows, most of which are new but a few of which are updates, then
truncate the work table and start processing next weeks rows in the work table.
Obviously we don't want to truncate the MV.
Does it sound like streams are what I should be looking at? We are on 9.2.

          Followup:

streams, yes.

replication, no.

replication is by very definition "mirror image".  It would be an error for the
replicate NOT to look exactly like the primary.

streams allows you to capture "logical change records", and apply what you want
to from that (eg: apply inserts, updates, ignore all others)

      Rating: 5
Best way to
      replicate a schema  July 13, 2003
Reviewer:
      Tatiane
Tom,

I am working on a project where I have to design how to update a "mirror" site
for read-only access. It seems a simple configuration to me. I would like to
have your opinion on it. Maybe I am missing something. The environment looks
like this:

o We have a production database with Oracle 8.1.7 and HP-UX
o Our customer has the same version of Oracle and they have Solaris.
o We are connected through a WAN link (our bottlenet is something like 50 Mbps).
We are in different cities. So, this is not a reliable nor a fast link.
o The customer wants to have only one schema updaded where they would like to
have read-only access to build reports using their local mirrored database.
o So, we would not have to replicate all the production database, only that
specific schema.
o Staleness is tolerated. They want the mirrored site to be updated on a daily
basis, like during the night.
o Since the production tables are large, we would like to send them only the
updated data.
o The updated data is relatively small.

My solutions:

o I think I would have to do a nightly batch update on that site.
o Standby databases and applying redo logs do not seem to fit here, since I want
only one schema from a huge production database -- for querying purposes.
o I am new to Oracle replication, but from what I have read, updating an entire
schema online via a slow and unreliable link is not a nice solution. Or am I
wrong here and Oracle replication would be a fine solution ???
o What I have thought of as the best answer would be a solution that would
generate incremental exports from that schema and do a nightly batch update on
the mirror site.
o I would write a set of shell scripts to transport, test, re-trasmit, log and
notify the operations. But my feelings tell me this control would be a bit
cumbersome (do not reinvent the wheel, Tatiane!), and maybe something like
Oracle replication would have built-in features to deal with that. What do you
suggest here ???
o I have heard incremental exports are deprecated, right ???

What would you recommend is such a situation ?

Thanks again, Tom !

          Followup:

This sounds like read only snapshots - where each individual snapshot would be
refreshed independently (reducing the risk of you being almost complete
refreshing 100 tables and failing due to a network error).

incremental exports export the ENTIRE table if a single bit changed.  they do
not export just changed rows.

create snapshot logs on production, read only snapshots on the copy site (sans
RI -- you can have indexes and such but no foreign keys so you can refresh each
snapshot one by one)

      Rating: 5
Doubt on
      terms  July 14, 2003
Reviewer:  Gillian

Sorry, what do:

1) Sans
2) RI

mean above ?

Isn't "sans" a french word ?

          Followup:

sans means "without"

ri is referential integrity.

      Rating: 3
Sans
      RI  July 14, 2003
Reviewer:  Mike  from
      Mpls, MN
"Sans" means "without"
"RI" means "Referential Integrity" (via foreign key constraints, etc)

      Rating:
      5
RI  September 18, 2003
Reviewer:
      A reader
Hi Tom,
Can we have RI on the copy tables defined? If we take care to refresh in the
right order? OR if RI are defined, disable them and recreate them afterwards?
Can we have new RI defined on the copy table than the ones on the original.

I was also kinda wondering if we can use the REFRESH ON COMMIT, clause to
synchronously replicate? I know it's a bad idea, but is it possible?

Thanks

          Followup:

put the RI on with "deferrable initially immediate"

you can do this with deferrable constraints.

refresh on commit works in a single database, not over databases.

      Rating:
      5
Thanks  September 18, 2003
Reviewer:
      A reader
Dear Tom,
I was reading about establishing referential integrity over multiple databases,
in Jonathan’s  book. It seems the only possibility is either to seriously
compromise the concurrency or to leave behind a small window where RI can be
violated.

What’s the best way to establish RI over multiple databases? There cannot be
easy solutions as this has to work over network, but, do we have something in
the newer versions of Oracle 9.2 or 10G?

Thanks

          Followup:

over my dead body would they give us this dubious "feature".

I would state categorically that you want one database for these tables -- not
two.  All you would achieve with two is:

o infinitely decreased application availability.  The failure of either database
impacts the other database 100%.

o infinitely increased runtimes.  it would be what is known as "slow"

o infinitely increased resource utilization.

this screams "single database".

loosely coupled systems can live in an distributed environment.
tightly coupled -- never, not in a million years.

forget about concurrency and data integrity for a minute, there are much more
serious things to worry about.

      Rating:
      5
Thanks  September 22, 2003
Reviewer:
      A reader
Dear Tom,

Thanks for your very clear directions. The requirement we have is to integrate a
leagacy Oracle appliction with an additional application. The newer application
would be on a newer box. (the older one cannot take additional load)  The thing
to go for is a single database on a shared disk array, and let the old and the
new machine operate in a RAC style and share database. This seamed some radical
change in the environment, so some old style solution was being thought. Looks
like we are clear on this, now.

September 20, 2003 Reji -- Thanks for the question regarding
      "utl_tcp usage", version 8.1.6

      You Asked
Tom:
    I have written a Java Stored Procedure but what it does
    is to open a socket and send a msg to an external server.
    I have asked about this issue in the past and you suggested
    to use utl_tcp package.  Do you have samples how to use
    this?

    I have a stored procedure to create an record in the
database
    and at the end of that process I want to open a socket and
    send two msgs to the external process ( I know the ip
address
    and the port number of that process ), my first msg is a
    an authentication msg ( if the id/password is wrong then
    the server will close the socket ) and if the authentication
    is successful then I will send another msg with some data
    to the server.

    If you can help me with a sample how to use the utl_tcp
    pacjage that will be a great help.

    TIA
    Reji

      and we said...
Here is a really small example doing what you ask, I make a web
request but the concept is the same.

tkyte@THINK1.US.ORACLE.COM> DECLARE
  2          c utl_tcp.connection; -- TCP/IP connection to the
Web server
  3          n number;
  4  BEGIN
  5          c := utl_tcp.open_connection('aria.us.oracle.com',
                                    80); -- open connection

  6          n := utl_tcp.write_line(c, 'GET / HTTP/1.0');
  7          n := utl_tcp.write_line(c);
  8          BEGIN
  9          LOOP
 10                  dbms_output.put_line(
                      utl_tcp.get_line(c, TRUE)); -- read result
 11          END LOOP;
 12          EXCEPTION
 13                  WHEN utl_tcp.end_of_input THEN
 14                          NULL; -- end of input
 15          END;
 16          utl_tcp.close_connection(c);
 17  END;
 18  /

HTTP/1.0 200 OK
Date: Wed, 05 Jul 2000 17:20:57 GMT
Allow: GET, HEAD
Server: Oracle_Web_listener2.1/1.20in2
Content-Length: 299
Content-Type: text/html
Last-Modified: Wed, 10 Nov 1999 22:38:02 GMT
URI: <index.html>
<HTML>
<HEAD>
<TITLE>Oracle Service Industries</TITLE>
</HEAD>
<FRAMESET COLS="130,*" border=0>
<FRAME SRC="nav.html" NAME="sidebar" frameborder=0>
<FRAME SRC="
http://aria.us.oracle.com/sb/web$sb.folder.home
NAME="body" frameborder="0"
marginheight="0"
marginwidth="0">
</FRAMESET>
</BODY>
</HTML>
tkyte@THINK1.US.ORACLE.COM>
Review &
      Followup

      Rating: 1
TCPConnection
      does not exist!  November 29, 2001
Reviewer:
      Michael Madvig  from Denmark
please see below for details on error message - tried from several systems. same
result.

  1  DECLARE
  2  c utl_tcp.connection; -- TCP/IP connection to the
  3  n number;
  4  BEGIN
  5  c := utl_tcp.open_connection('www.madvig.dk',80); -- open connection
  6  n := utl_tcp.write_line(c, 'GET / HTTP/1.0');
  7  n := utl_tcp.write_line(c);
  8  BEGIN
  9  LOOP
 10  dbms_output.put_line(utl_tcp.get_line(c, TRUE)); -- read result
 11  END LOOP;
 12  EXCEPTION
 13  WHEN utl_tcp.end_of_input THEN
 14  NULL; -- end of input
 15  END;
 16  utl_tcp.close_connection(c);
 17* END;
 18  /
DECLARE
*
ERROR at line 1:
ORA-29540: class oracle/plsql/net/TCPConnection does not exist
ORA-06512: at "SYS.UTL_TCP", line 559
ORA-06512: at "SYS.UTL_TCP", line 552
ORA-06512: at line 5

Any ideas?

          Followup:

and a search for:

class oracle/plsql/net/TCPConnection does not exist

on this site will reveal the solution.  Just plug it into the search.

      Rating: 3
Not
      working  December 02, 2001
Reviewer:  ashraf

i try like
SQL> DECLARE
  2              c utl_tcp.connection; -- TCP/IP connection to the Web serve
  3              n number;
  4      BEGIN
  5              c := utl_tcp.open_connection('aria.us.oracle.com',
  6                                     80); -- open connection
  7            n := utl_tcp.write_line(c, 'GET / HTTP/1.0');
  8            n := utl_tcp.write_line(c);
  9              BEGIN
 10              LOOP
 11                     dbms_output.put_line(
 12                        utl_tcp.get_line(c, TRUE)); -- read result
 13             END LOOP;
 14             EXCEPTION
 15                     WHEN utl_tcp.end_of_input THEN
 16                             NULL; -- end of input
 17             END;
 18             utl_tcp.close_connection(c);
 19     END;
 20  /
DECLARE
*
ERROR at line 1:
ORA-20002: aria.us.oracle.com
ORA-06512: at "SYS.UTL_TCP", line 202
ORA-06512: at line 5

          Followup:

Umm, well since aria.us.oracle.com is my machine behind Oracle's firwall -- you
cannot connect to it.  Try a machine in your network that runs a webserver on
port 80 instead...

      Rating: 4
Getting
      response from the server  September 09, 2003

Reviewer:  vaibhav  from India
Dear TOM,

my code writes to the server rightly but getting a response sends the
application in a never ending loop. The code i have used is given below :-
DECLARE
  c  utl_tcp.connection;  -- TCP/IP connection to the Web server
  v integer ;
  d varchar2(1000);
  e varchar2(1000);
BEGIN
  c := utl_tcp.open_connection('10.0.23.137',6000);  -- open connection
  v := utl_tcp.write_text(c,'CON^`!XML^`!BankClient#END#',null);
  dbms_output.put_line('len -- '||v);  -- read result

  BEGIN
     LOOP
        dbms_output.put_line(utl_tcp.get_line(c,TRUE));  -- read result
     END LOOP ;
  EXCEPTION
    WHEN utl_tcp.end_of_input THEN
      NULL; -- end of input
  END;
  utl_tcp.close_connection(c);
END;

When i comment the utl_tcp.get_line part of code, the application runs properly
but to get a response from the server where i have written the message is not
possible. i even tried removing the loop and end loop assuming that maybe
utl_tcp does not throw a utl_tcp.end_of_input exception, but in vain.

Please advise...

          Followup:

umm, this just means you are never getting a response from your "server" or your
server is not closing the connection after it does.

you have a protocol issue here, you are not speaking the right "protocol" to
your server -- i've no idea what the proper protocol is (it is your server)
but....

use telnet

telnet 10.0.23.137 6000

and do it manually -- see what happens there.  if telnet doesn't exit right
after you type in that line of text, this process won't either.

      Rating: 4
utl_tcp
      response  September 19, 2003
Reviewer:
      vaibhav  from India
Dear All,
It is happening exactly as you stated. the server is not closing the connection
after it receives my message.
i had a discussion with the team handling the server side application and they
told me that keeping the connection open was the practice they followed.
opening and closing the connections immediately would lead ot a lot of overhead
which they want to avoid.
our application basically writes SMS messages to this server which then actually
sends it to the mobile phones. the protocol they follow is :-
1. open a connection
2. keep sending messages
3. send a dummy message to keep the connection open, if no  messages are to be
sent presently.
4. close the connection (if a long pause is reqd)

In this case, how do i use utl_tcp to achieve my goal? I am able to write to the
port, the server also sends the response, but my application hangs waiting
indefinitely for a response.

Please help...

Regards,
Vaibhav.

          Followup:

you just read from the server until you get an "end of response" character --
whatever that may be (probably, a newline).

then you close the connection.

standard protocol stuff -- you have to be able to recognize "end of message".
if there is no way to recognize end of message, you are hosed (out of luck).
but I betcha, an end of line is "end of message" (or something like that)

September 20, 2003 Michael -- Thanks for the question regarding "How
      to calculate memory, occupied by agrerate functions ? (ORA-01467: sort key
      too long)", version Oracle 8.0.5

      You Asked
I have ORA-01467: sort key too long
in query with a lot of max() functions ( about 70 max() ).

I know that there is a limit on the memory, occupied by agrerate
functions :  "the group-by expression and all of the
non-distinct  aggregates (e.g., sum, avg) need to fit within a
single database block."

I think if I will shrink table names and field names
this occupied space will be less for expressions like  this
Max(decode(ANALYSIS.ANALYSIS_NAME,'CRASH',RESULT.AVG_VALUE)),

But when I renamed the tables and the fields and have now :
 Before                             Now
 -----------------------           -----
 ANALYSIS.ANALYSIS_NAME            A.N
 RESULT.AVG_VALUE                  R.AVG
Max(decode(ANALYSIS.ANALYSIS_NAME,
'CRASH',RESULT.AVG_VALUE))
Max(decode(A.N,'CRASH',R.AVG))

the same ORA-01467 after the same number of max occured...

Question :
How to calculate the memory, occupied by agrerate functions ?
------------------------------------------------------------
Regards, Michael.

P.S.
Here is my SQL:

Select ITEM.ITEM_NAME,
Max(decode(ANALYSIS.ANALYSIS_NAME,'CRASH',RESULT.AVG_VALUE)),
Max(decode(ANALYSIS.ANALYSIS_NAME,'CRASH',RESULT.MIN_VALUE)),
Max(decode(ANALYSIS.ANALYSIS_NAME,'CRASH',RESULT.MAX_VALUE)),

Max(decode(ANALYSIS.ANALYSIS_NAME,'SPEED',RESULT.AVG_VALUE)),
Max(decode(ANALYSIS.ANALYSIS_NAME,'SPEED',RESULT.MIN_VALUE)),
Max(decode(ANALYSIS.ANALYSIS_NAME,'SPEED',RESULT.MAX_VALUE)) ,

Max(decode(ANALYSIS.ANALYSIS_NAME,'BREAK',RESULT.AVG_VALUE)),
Max(decode(ANALYSIS.ANALYSIS_NAME,'BREAK',RESULT.MIN_VALUE)),
Max(decode(ANALYSIS.ANALYSIS_NAME,'BREAK',RESULT.MAX_VALUE))
...
Max(...)
...

from ITEM, ANALYSIS, RESULT
where RESULT.ITEM_ID=ITEM.ITEM_ID
and  RESULT.ANALYSIS_ID=ANALYSIS.ANALYSIS_ID
Group by  ITEM.ITEM_NAME

      and we said...
It is based on the data values -- not their names.  Consider:

ops$tkyte@8i> create table t ( x varchar2(4000), y
varchar2(4000), z varchar2(4000), a int );

Table created.

A sort key including all 4 fields here could be very large
indeed -- 12,000 bytes in x,y,z alone and upto 22 bytes for the
number

ops$tkyte@8i> insert into t values ( 'x', 'y', 'z', 1 );
1 row created.

ops$tkyte@8i> select x, y, z, sum(a)
  2    from t
  3   group by x, y, z
  4  /

X          Y          Z              SUM(A)
---------- ---------- ---------- ----------
x          y          z                   1

But it is not necessarily a problem -- not until we get a
really large row in there anyway:

ops$tkyte@8i> insert into t
  2  values
  3  ( rpad('*',4000,'*'),
  4    rpad('*',4000,'*'),
  5    rpad('*',4000,'*'), 2
  6  );

1 row created.

ops$tkyte@8i> select x, y, z, sum(a)
  2    from t
  3   group by x, y, z
  4  /
  from t
       *
ERROR at line 2:
ORA-01467: sort key too long

So, you see - its a function of the ACTUAL data that is in
there -- not the names of the tables or columns or their maximum
POTENTIAL length.  Its 100% a function of their current, actual
length
Review & Followup

      Rating: 4
OraDoc
      missleading  April 25, 2002
Reviewer:  Marc
      Blum  from Aachen, Germany
Oradoc 8.1.7 states:

ORA-01467 sort key too long
Cause: A DISTINCT, GROUP BY, ORDER BY, or SET operation requires a sort key
longer than that supported by Oracle. Either too many columns or too many group
functions were specified in the SELECT statement.
Action: Reduce the number of columns or group functions involved in the
operation.

If I compare that to your response, I would say, the documentation is
missleading und not really helpful.

      Rating: 1
ORA-01467: sort
      key too long  April 25, 2002
Reviewer:
      Kenneth  from HK
Though we know it only depends on the data size but not the column name, how to
tackle it is not mentioned.  If we resize the database block size, say from now
8k to 16k, we have to re-gen the database and the impact is very big.  Is there
any work around?

          Followup:

In 9i, you can have databases with more then one block size.  So, one solution
is to use 9i.  There you can have 2, 4, 8, 16 even 32k blocksizes in the same
database.

Other then that -- the max sort key is a FUNCTION of the block size -- period.
The only way to get a bigger max sort key is to get a bigger block size.

      Rating: 5
what the term
      SORT KEY mean  June 11, 2002
Reviewer:  ajar
      bajar
in the context it has been used here..

is this because when group by multiple columns, we also sort using the
concatanation of all those columns.

Why are we talking about the block size here..

          Followup:

As they provided in the question themselves:

...
I know that there is a limit on the memory, occupied by agrerate functions :
"the group-by expression and all of the non-distinct  aggregates (e.g., sum,
avg) need to fit within a single database block."
.......

need to fit on a single block (actually, a fraction of the block).  that is the
sort key here.

      Rating:
      4
Reader  June 11, 2002
Reviewer:
      A reader
Tom,

Is this similar to sort_write_buffer_size in earlier
versions which is a tunable parameter

Thanks

          Followup:

No, not really.

      Rating: 5
Let me sum it
      up for your critique...  June 12, 2002
Reviewer:
      ajar bajar
So when I execute a sql like

select a,b,c,b
from x,z
where conditions
group by abc

then columns a,b,c are concatanated and then grouped by , is that it ?

and such concatanation should fit within one oracle block, for example if the
block size is 16k then the concatanation of a,b,c should be less than or equal
to 16k

other wise it is going to return usethe ora-01467, is that what you are saying ?

          Followup:

yes and no, you have the group by columns, but not the aggregates.. all of the
columns in the group by, plus the aggregates themselves (and they had 70 some of
them)

      Rating: 5
never read
      about this limitation  June 12, 2002
Reviewer:
      A reader
so groupby columns + aggregrates should not exceed block size.

Why is grouby columns + aggregates  called the sort key. Actually only the
grooup by columns together  should be the sort key . How does the aggregate get
included in the sort key? Doesnt make much meaning..

You aggregate values based on the sort key which is a concatenation of the
groupby columns  . Isnt it ?

Can you give the link giving the documentation for the sort key too long
limitation.

Thx

          Followup:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76999/e900.htm#1656
and search for 1467

ORA-01467 sort key too long

    Cause: A DISTINCT, GROUP BY, ORDER BY, or SET operation requires a sort key
longer than that supported by Oracle. Either too many columns or too many group
functions were specified in the SELECT statement.

    Action: Reduce the number of columns or group functions involved in the
operation.

      Rating: 3
Sort Key too
      long  February 13, 2003
Reviewer:  Ik
       from BG
Tom,
Iam getting the error - Sort key too long and when i reduce the number of
columns in the query, the error does not appear.

My situation is like this -
I want to generate output in a cross tab format. I take in a comma separated
list of values and generate cross tab data. The query is done such that it takes
in a max of 20 comma separated values. I have 20 columns per record and it is
populated based on the number of values i have in my list.
If i gave 'a,b,c,d', i populate just the first four columns per record. The
remaining columns have NULL throughout. Yet, i get sort key too long error and
when i remove 10 columns or so from the query this error disappears. Since you
said that this error is a function of the data, iam wondering why removing NULL
columns could make a difference.
FYI - Iam grouping by just one column which was a varchar and i changed that to
a smaller key. Still the error persists.

Any info will be greatly appreciated. Thanks as always.

          Followup:

it is a function of the max the data could be...

it is a function of your blocksize (about 40% of the block size is the maximum
sort key)

so -- it computes the max it *could possibly hit* and that is the sort key
length it uses.

If you hit it -- there is no "workaround", short of using a bigger block.

      Rating: 5
Thank
      You  February 13, 2003
Reviewer:  Ik
       from BG
Tom,

woooh!...that was very fast. Thanks.

So would doing a SUBSTR on the columns help? Would that influence MAX sizing.

          Followup:

yes, it could -- as long as cursor_sharing was exact (so the db knows how big
the substr is)

      Rating: 3
Sort Key Too
      long  September 19, 2003
Reviewer:  Girish
       from India
This solution provided by Tom was helpful for us.But he should have given
alternate ways of doing the things to eschew errors
In our case we have no scope of doing any changes to database.

          Followup:

i can only give the alternate of "don't run that query" as there is no
alternative if you have a sort key that is too long!

September 20, 2003 Nilesh -- Thanks for the question regarding
      "PL/SQL Table and Sorting", version 8.1.7

      You Asked
Tom,

As always I am very thankful for all your help to my Oracle related problems in
past.
Today I am having an issue with PL/SQL table and sorting.

Hardware/Software:
          Oracle - 8.1.7.2.0 Installed on Sun - OS 5.8

I am working on a PL/SQL code in which I am using PL/SQL tables to form
information in the form
of a 2D table. First I defined TABLE types and then declared PL/SQL table
variables as below

TYPE t_batch_id is table of appintgmgr.process_log.batch_id%type  index by
binary_integer;

TYPE t_src_int_node is table of
appintgmgr.process_log.source_interface_node%type index by binary_integer;

TYPE t_dest_int_node is table of
appintgmgr.process_log.destination_interface_node%type index by binary_integer;

TYPE t_int_type is table of appintgmgr.process_log.interface_type%type index by
binary_integer;

TYPE t_int_type is table of appintgmgr.process_log.interface_type%type index by
binary_integer;

TYPE t_process_dt is table of varchar2(50) index by binary_integer;

batch_id                    t_batch_id;
source_interface_node       t_src_int_node;
destination_interface_node  t_dest_int_node;
interface_type              t_int_type;
pdt                         t_process_dt;

After populating all these tables in a same loop, I have information in these
PL/SQL tables in 2D form as follows

batch_id   source_         Destination       Interface
          interface_node  interface_node    type            pdt
---------------------------------------------------------------------------------
--------
    62746   MANUDRP-MLB-GLB   MAPS-AHG-LCLN-MFG  PLA     08-Apr-2003 05:06:43 am
    62425   MANUDRP-MLB-GLB   MAPS-AHG-LCLN-MFG  PLA     08-Apr-2003 04:45:00 am
    62029   MANUDRP-MLB-GLB   MAPS-AHG-LCLN-MFG  PLA     07-Apr-2003 04:45:00 am
    62026   MANUDRP-MLB-GLB   MAPS-AHG-LCLN-MFG  PLA     05-Apr-2003 02:12:19 pm
    62023   MANUDRP-MLB-GLB   ALL              PLA     05-Apr-2003 10:01:55 am
    62463   MANUDRP-MLB-GLB   MAPS-AHG-LOLN-MFG  PLA     08-Apr-2003 12:09:48 am
    62142   MANUDRP-MLB-GLB   MAPS-AHG-LOLN-MFG  PLA     07-Apr-2003 11:36:00 pm
    61983   MANUDRP-MLB-GLB   MAPS-AHG-LOLN-MFG  PLA     06-Apr-2003 09:30:00 pm
    62452   MANUDRP-MLB-GLB   MAPS-MLB-LATN-MFG  PLA     07-Apr-2003 01:38:55 pm
    62110   MANUDRP-MLB-GLB   MAPS-PPG-BKLN-MFG  PLA     07-Apr-2003 05:32:00 am
    62065   MANUDRP-MLB-GLB   MAPS-PPG-BKLN-MFG  PLA     06-Apr-2003 09:32:00 am

As you can see that date column information is not sorted.

Now my sorting requirements is that once this information is generated I need to
sort this in Descending Order of PDT column.
Is there any Oracle 8+ PL/SQL table tricks that can help me to sort this
information?
Or do I need to write my own small sorting routine to sort this information?

Thanks for your help in advance!

Regards
Nilesh

      and we said...
if you do this instead:

create type myScalarType as object (
batch_id number, source_interface_node varchar2, ..... );
/

create type myArraytype as table of MyScalarType
/

declare
   l_data myArrayType := myArrayType();
begin
   for i in 1 .. n loop
     l_data.extend;
     l_data(i) := myScalarType( .... );
   end loop;

   for x in (select * from TABLE( cast(l_data as myArrayType) ) order by ... )
   loop

it'll work -- else, you will be writing your own sort.

Important notes:

o CREATE TYPE -- in sql, not in sql
o No, the type cannot be in a package, it must be a sql type

Review &
      Followup

      Rating:
      4
Confusion  April 09, 2003
Reviewer:
      Sajid Anwar  from London
Hi Tom,
>>>>>
Important notes:
o CREATE TYPE -- in sql, not in sql
o No, the type cannot be in a package, it must be a sql type
<<<<<<

what does this mean in sql, not in sql?
I am a bit confused. Could you please clear my confusion?

Thanks in advance.

Regards,
Sajid Anwar
London

          Followup:

should have been NOT IN PLSQL

      Rating: 4
its
      plsql  April 09, 2003
Reviewer:  swapnil
       from London
ITs just a typo ,
read it like
o CREATE TYPE -- in sql, not in pl/sql

      Rating: 3
This is
      neat!!  April 09, 2003
Reviewer:
      A reader
Tx a lot Tom for good idea!!

      Rating: 4
very good
      solution TOM...  July 30, 2003
Reviewer:
      srinivasa rao bachina  from bangalore
Hello Tom ,
 i am having one more doubt regarding sorting....
my question is
from EMP table i am taking 3 groups based on dept no(10,20,30) in the
groups(g1,g2,g3) i am ordering the results by empno,sal,ename respectively.
now i am having rows
g11,g12,g13....
g21,g22,g23...
g31,g32,g33.....
now i want to give priority to the above rows as follws
  i will take g11,g21,g23 and give g21 as first priority based on
(empno,sal).now i will take g11,g22,g31 and agian find the least(empno,sal) from
the three and give the priority 2.and next time i will take the row from which
group we have given priority..in this way i have to give the priority to all the
rows in g1,g2,g3...
which way is better to do the problem...
can you give me the solution
waiting for your solution
thanks in advance

          Followup:

not making sense to me.

      Rating: 4
Is simple but
      what about performance  September 19, 2003

Reviewer:  Chris Wones  from Cincinnati, OH USA

Tom,

I appreciate all of the information you provide and I was hoping for a little
more.  Earlier in this thread, you suggest to sort using:

for x in (select * from TABLE( cast(l_data as myArrayType) ) order by ... )
   loop

I have found this to be very slow.  In my application, there are many benefits
to using the types but "selecting" data from them is painful in terms of
performance.  Are there any mechanisms for sorting the data in the types that is
faster than the TABLE() option?  I ask this because looping through the list is
very fast if it happens to be in the right order.

          Followup:

how big exactly is this list if that is "slow"??????

what is "slow"?

give me a "for example"

September 20, 2003 Kevin -- Thanks for the question regarding "How
      do I use bind variables with ODBC?", version 8.1.7

      You Asked
I realize you may not address VB and ODBC issues, but I was trying to see if
there was a way to use bind variables in an ODBC connection from an Excel
spreadsheet through VB to an Oracle database.  I've been reading the discussions
you had in some of the threads and thought it might be advantages to not have to
reparse complex queries, some of which are more complicated than listed below.
I've already realized a 10 times increase in performance having Oracle calculate
these statistics instead of the spreadsheet or VB, and thought I'd try to help
the database by not making it have to reparse these nasty things.

If I knew how to change the station variable to a bind variable and pass it to
the ODBC, I'd be in great shape, but I can't find a source of information
describing how to do this.

Private Sub StatisticsButton_Click()
   Dim conn_str As String
   Dim sql_str As String
   Dim station As String

   'set the sql connection string
   conn_str = "ODBC;DSN=DEVL;UID=PUB;PWD=PUB;DBQ=DEVL;ASY=OFF;"

sql_str = ""
sql_str =           "SELECT POWER(CORR(a.value,
TO_NUMBER(DECODE(b.value,-999,NULL,-902,NULL,-901,NULL,b.value))), 2) AS
"R-squared", "
sql_str = sql_str & "       POWER( SUM( POWER( (a.value -
TO_NUMBER(DECODE(b.value,-999,NULL,-902,NULL,-901,NULL,b.value) ) ),2)) / "
sql_str = sql_str & "       (COUNT(b.value)-1),.5) AS RMSE, "
sql_str = sql_str & "       SUM(a.value -
TO_NUMBER(DECODE(b.value,-999,NULL,-902,NULL,-901,NULL,
b.value)))/count(b.value) AS BIAS, "
sql_str = sql_str & "       (SUM(POWER( (a.value - (SELECT AVG(z.value)  FROM
dm_daily_data z  WHERE z.dbkey = a.dbkey)),2)) - "
sql_str = sql_str & "       SUM(POWER( (TO_NUMBER(decode(b.value,-999,NULL,
-902,NULL, -901,NULL, b.value))-a.value),2)))/ "
sql_str = sql_str & "       SUM(POWER( (a.value - (SELECT AVG(z.value) FROM
dm_daily_data z  WHERE z.dbkey = a.dbkey)),2)) AS Efficiency, "
sql_str = sql_str & "       COUNT(b.value) AS BCOUNT "
sql_str = sql_str & "  FROM dm_daily_data a, "
sql_str = sql_str & "       hsm_weekly_data b, "
sql_str = sql_str & "       hsm_stations c "
sql_str = sql_str & " WHERE a.dbkey = c.dbkey "
sql_str = sql_str & "   AND c.hsm = b.hsm_station "
sql_str = sql_str & "   AND a.daily_date = b.weekly_date "
sql_str = sql_str & "   AND b.hsm_station = '" & station & "' "
sql_str = sql_str & "   AND a.value IS NOT NULL "
sql_str = sql_str & "   AND TO_NUMBER(DECODE(b.value,-999,NULL, -902,NULL,
-901,NULL,b.value)) IS NOT NULL"

    With ActiveSheet.QueryTables.Add(Connection:=conn_str,
Destination:=ActiveSheet.Range("AF3"), Sql:=sql_str)
        .RefreshStyle = xlOverwriteCells
        .Refresh
    End With

End Sub

      and we said...
Tell you what -- I'll publish this and hopefully a VB programmer will give a
better answer.

Quick fix for you, add a statement:

  alter session set cursor_sharing=force;
  run query
  alter session set cursor_sharing=exact;

that'll "autobind" for you.

But -- hopefully a VB programmer can help us out here.
Review &
      Followup

      Rating: 3
ODBC and ADO
      support parameters for queries  February 25, 2003

Reviewer:  Mike  from Minneapolis, USA
I've worked in both VB and Delphi with ODBC and ADO.
There are query objects for retrieving datasets or
sending commands to the database.  Both contain mechnisms
for "parameterization" of queries.  Usually this takes
the form of:

     MYQUERY.PARAMETER[ 'XYZ' ] := MYVALUE

The actual SQL looks like:

     SELECT * FROM MYTABLE WHERE MYCOLUMN = :XYZ

Hope this helps.

      Rating: 5
Avoid Extra
      Parsing  February 25, 2003
Reviewer:  Kevin
      Perez  from IL USA
Don't forget that if your program will be issuing this call numerous times, you
should keep the cursor open to avoid extra soft parses.

I know this isn't directly related to Binding Variable question but it is
related enough that it should be kept in mind when creating these VB and/or
Delphi procedures.

Thanks Tom ;)

      Rating: 4
Not
      sure  February 25, 2003
Reviewer:  Alex
       from New York
Kevin,

I am not sure that this will help . ( I am not VB dev myself ). There is a note
on Metalink with title "Using Bind variables with RDO, ODBC in SQL Statements".
The note id is: 111845.1.

      Rating:
      2
ME?!?  February 26, 2003
Reviewer:  Not
      the other Kevin  from IL, USA
Shouldn't have that been directed to ummm, the other Kevin?
;)

      Rating:
      5
Multi-disciplined Database Programmers  February 27,
      2003
Reviewer:  Kevin Rodberg  from West Palm Beach,
      FL, USA
Thank you all for the comments and suggestions.  The MetaLink note 111845.1 was
exactly what I was looking for.  I'll still look into how you keep the cursor
open since I'm not sure how this works with ODBC.

It's nice to be able to post a request for information and get assistance form
such a diverse group of backgrounds, with a common area of interest.

      Rating: 4
Who develop
      ODBC for Oracle?  September 14, 2003
Reviewer:
      TH  from Manama, BAH
I'm sure Oracle is Developing ODBC to connect to Oracle. Does third pary also
develop ODBC for Oracle? (I heared some native ODBC drivers related to Delphi.)

Thanx,
Thaha

          Followup:

yes, many people have made odbc adapters.

      Rating: 2
Oracle Best
      Practices (VB/VBA)  September 14, 2003
Reviewer:
      Mike Wilson  from Los Angeles, CA
I do a lot of VB/VBA development (and I'm a DBA, weird isn't it?).

A few thoughts:
1) Don't use ODBC!
Use ADO instead, it's faster (uses OLE DB, circumventing the Jet layer), has
better error handling (Errors collection), allows for disconnected recordsets,
and client side cursors.  Did mention it's way faster?  It will require you to
download the latest MDAC from MS.  I recommend MDAC 2.7 RTM.

2) Don't embed SQL in your VB code.
It's messy, hard to maintain, and a royal pain to debug.  It's much "cleaner" if
you seperate your SQL into a view on the database side.  You can embed the query
during development but I *highly* recommend you move it to a view once you get
the recordset you require.  Work with your DBA on this.  If you *must* code a
query into your VB code then move the complex query into a view and reference it
as "select * from my_view where fld = ?".

3) Use bind variables wherever possible.
If you can't use bind variables easily (you probably shouldn't be doing what you
are doing) then consider calling out through ADO and "alter session set
cursor_sharing=force".  See below for an simple example of using a bind variable
with ADO.  Note, Excel will only use a ODBC query for External Data Tables.

4) Use the ADODB.Errors collection for error handling.
Every VB routine that references the DB should use the ADODB.Errors collection.
No funtion/sub is too small to avoid this.

5) Have Oracle calculate as much as possible.
It's far more efficient at handling large datasets and almost any function you
can think of in Excel can probably be coded as a PL/SQL function or field in a
view.  Again, work with your DBA on this.  You can flesh out your query locally
but it will be much faster in the DB.  This will be particularly true in the
upcomming 10g with it's new MODEL clause.  {nifty examples BTW Tom at
OracleWorld}

6) Use BeginTrans and EndTrans to batch update.
Where possible use the BeginTrans and Commit/Rollback methods of the Connection
object when updating large #'s of records.  It's *much* faster than commiting on
every change.  You can also use the UpdateBatch method of the Recordset object.
Again, this is ADO, not ODBC.

7) Let Excel to it for you.
Excel can handle your parameters natively without needing to code all of this up
like you have done.  None of the code you have shown is required if you simply
wish to generate a filtered table in Excel.

Here's how I do it:
  a) Move your query into a view in Oracle.  Let it return all of the data.  You
will filter when you build the query or with AutoFiltering in Excel.
  b) Insert the view in a worksheet (Tools/External Data) and in the Query
Builder create your filter such that "fld = [my_param1]" (use the brackets to
indicate a query parameter).
  b) Return the data to Excel.  When the query runs you will be asked to provide
values for the parameters that have [] around them.  This will allow Oracle to
filter your view.
  c) Once you get the data into Excel, if you right-click on the table you can
use the Parameters... option to set the values a) for all time, b) for one run,
or c) referencing a set of cells in your spreadsheet.

  I use this technique to help track ticket assignments in our Helpdesk system
in a spreadsheet.  It allows them to type their name in as the ASSIGNED cell of
the workbook, which Excel uses as a parameter for the table. I also set the
table to auto-update every 15 minutes.  To ease load I created this as a
Materialized View also.  This is the best of all worlds, speed, filtering, and
views from Oracle, ease of use from Excel.

Hope this helps.

Mike

Sub ParameterExample()
   Dim cmd As New ADODB.Command
   Dim rs As New ADODB.Recordset
   Dim prm As ADODB.Parameter

   ' Set the command's connection using a connection string.
   cmd.ActiveConnection = Provider=OraOLEDB.Oracle;User
ID=scott;Password=tiger;Data Source=oraDSN;
   ' Set the command's text, and specify that it
   ' is an SQL statement.
   cmd.CommandText = "Select empno from emp where deptno = ?"
   cmd.CommandType = adCmdText

   ' Set up a new parameter for the stored procedure.
   Set prm = cmd.CreateParameter("deptno", adInteger, adParamInput, , 10)
   cmd.Parameters.Append prm

   ' Create a recordset by executing the command.
   Set rs = cmd.Execute

   ' Loop through the recordset and print the first field.
   Do While Not rs.EOF
      Debug.Print rs(0)
      rs.MoveNext
   Loop

   ' Close the recordset.
   rs.Close

End Sub

Oracle Overview of OleDB (with Code Samples):

http://download-west.oracle.com/docs/cd/B10501_01/win.920/a95498/intro.htm
Good Overview of MS Data Technologies:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsqlmag02/html/TheEvolutionofData-AccessTechnologies.asp

          Followup:

thanks, really appreciate that.

gotta love #3! :)

      Rating: 5
Use
      oo4o  September 14, 2003
Reviewer:  Jim
      Kennedy  from Portland, OR, USA
I use Oracle's Ole Objects.  Very fast, nice parameter interface, supports all
the Oracle datatypes.  It is especially nice to define your queries with
parameters as globals and just do a refresh and execute on the query. eg

create the parameter x
create dynaset with parameter x
at a later time set the value of the parameter x.value = ...
 dyneset.refresh.
Very fast.  Also it has a nice array interface (on a particular system took 2.5
seconds to insert 30,000 rows in a small narrow table)

      Rating: 4
OO4O - my 2
      cents  September 15, 2003
Reviewer:  robert
       from CT
Yes, OO4O on client and plsql on Oracle --- I have yet to
have hear arguments that this is not the most efficient & flexible way of doing
VB/Oracle programming.

ADO ? --- much ADO about nothing....it's unfortunate enough
that one is tied down to VB/Windoz, why thro in another bucket of @#$% ?

Have VB do as little as possible --- just gather parameters & pass on to Oracle
in the form of stored procedure call.

      Rating:
      5
Tatiane  September 18, 2003
Reviewer:
      Tatiane
Tom, you said:

>alter session set cursor_sharing=force;
>run query
>alter session set cursor_sharing=exact;

1) I know how cursor_sharing works, but what exactly was your intention here ?

2) Any caveats to using cursor_sharing=safe, instead of force ?

          Followup:

1) to get bind variables into their system....

2) safe?  do you mean similar?  similar was added in 9i, didn't exist at the
release they are using.  similar can be used, yes.

      Rating:
      4
cursor_sharing  September 18, 2003

Reviewer:  Tatiane
Tom, continuing from above...

>to get bind variables into their system

... it's obvious, I know

But why didn't you simply set it to force, instead of setting to force, running
the query and then setting to exact ? What is the reasoning behind that ?

What I would normally do:

o alter system set cursor_sharing=force (or similar)
o alter system flush shared_pool (so there won't be old statements with literals
in the shared pool)
o let them system run

          Followup:

i don't like setting to force for the entire session.  it could have side
effects elsewhere.

i want them to FIX the code, not bandage it.  cursor sharing is a big bandaid to
me, i don't like it.

      Rating: 4
Side effects
        September 18, 2003
Reviewer:  Tatiane

I agree cursor_sharing is a band-aid.

What kind of side-effects ?

The only one I am aware of is additional soft-parse overhead...

Can you point me to some discussion on it ? I was looking for it but I could not
find.

Thanks

          Followup:

chapter 10 in my book "Expert one on one Oracle" has a discussion.

it can change query plans (for the better or worse -- bottom line is "things can
change")

it will change the returned default widths of many columns.  your query that
was:

select ..., substr( x, 1, 5 ) x from t

was KNOWN to return a varchar2(5).  Now it is:

select ..., substr( x, :sys_bv_0, :sys_bv_1 ) x from t;

it is no longer KNOWN what width that will be.  can you imagine the havoc that
might play with a report generator??  all of a sudden the stuff that printed
just dandy yesterday won't fit on the page -- even in landscape mode...

      Rating: 4
No to
      OO4O/ODBC/ADO yes to www  September 19, 2003

Reviewer:  Johan Snyman  from JHB SA
I recently solved this kind of problem by using a PL/SQL package to generate the
required results in html format via the web sql toolkit and mod_plsql.

Then you use Excel's web query facility to access the appropriate URL's and
supply the appropriate parameters (either prompted or taken from a spreadsheet
cell or from a spreadsheet cell populated from a VBA drop down list, with a
range of cells that are auto-updated via webquery from the Oracle database when
the spreadsheet is opened as the date source).

This way all the user require is MS Excel and network access to the Oracle HTTP
server. No client side install (you can even have them download the spreadsheet
from the web site). No oracle net client software to install and configure. No
ODBC driver installation and setup. No OO4O install and setup. Very similar to a
purely browser based approach.

The only downside is some additional soft parsing, due to the statelessness
among calls, but the pros far outweigh the cons. It is like using Excel as a
slightly more sophisticated web browser (in this case it was mostly done in
order to enable use of Excel's extensive charting capabilities, and to allow
users to easily analyse the data in an environment they are already familiar
with.)

      Rating: 5
cursor
      sharing  September 19, 2003
Reviewer:
      Douglas
Guys, I have used cursor_sharing=force for quite a while now ( 2+ years ) and
have never found any problems with it.
I have only had benefits ! My soft parse ratios are always cool and I have never
noticed any side effects.

          Followup:

you still have a bug in your code that needs to be fixed.

your parse to execute ratio should be improved as well -- you are parsing more
then you should.

it does have side effects
you do need to fully test a system before just flinging it on.

September 21, 2003 ted -- Thanks for the question regarding "plsql
      array process and bulk collect", version 8.1.7

      You Asked
Thomas, Questions related to following posting:
1. I want to have plsql record instead of a single field number type.

    type mytype is record (id number :=0,
                          myname varchar(30) := null);
    type mytyp is table of mytype index by binary index.

   what changes need to be made to your code if I want
    to bulk collect into this type and forall delete ?

2. empty numtab for initialization ?
3. why loop command is placed right after begin statement ?

thnx in advance
ted chyn

====================================
declare
       type numTab is table of number(4) index by
binary_integer;
        data numTab;
           empty numtab;
    begin
            loop
                    data := empty;
                    select user_id BULK COLLECT into data from
t where rownum <= 1000;
                   exit when data.count = 0;
                   forall i in 1 .. data.count
                          delete from T where user_id =
data(i);
           end loop;
   end;
   /

      and we said...
1) you cannot bulk collect into a record type, only into SCALAR array types.
You could use an Object type like this:

ops$tkyte@DEV816> create or replace type myScalarType
  2  as object
  3  ( id number,
  4    myname varchar2(30)
  5  )
  6  /
Type created.

ops$tkyte@DEV816> create or replace type myTableType
  2  as table of myScalarType;
  3  /
Type created.

ops$tkyte@DEV816> create table t
  2  as
  3  select user_id, username from all_users;
Table created.

ops$tkyte@DEV816> declare
  2      data myTableType;
  3  begin
  4
  5      select cast( multiset( select user_id , username
  6                               from all_users
  7                              where rownum < 1000 )
  8                   AS myTableType )
  9        into data
 10        from dual;
 11
 12      dbms_output.put_line( data.count || ' rows fetched' );
 13
 14      delete from t
 15       where user_id in
 16          ( select x.id
 17              from TABLE( cast (data as myTableType) ) x
 18          );
 19
 20      dbms_output.put_line( sql%rowcount || ' rows deleted' );
 21  end;
 22  /
160 rows fetched
160 rows deleted

PL/SQL procedure successfully completed.

2) empty would be used to quickly reset data.

    data := empty;

would be like iterating through data and deleting each element, only easier.

 followup to comment
3) The loop is needed when T contains more then 1,000 rows.  We "array" fetch
1,000 rows, Array delete 1,000 rows and continue on --exiting the loop when
there is nothing left in T.  Put 1001 rows into 1 and the loop will execute two
times.
Review
      & Followup

      Rating: 5
plsql array
      process and bulk collect  April 08, 2001
Reviewer:
      ted chyn  from dallas/tx usa
thomas, my third question related to your script is, why using loop and end loop
at all, since bulk collect an for all only execute once. I comment out loop/end
loop/exit command and got the same result.
thnx ted

      Rating: 5
Limit clause
      with select into  September 20, 2003
Reviewer:
      A reader
How to use limit clause with
begin
select * bulk collect into pltable from emp;
end;

          Followup:

you cannot -- you can use "rownum" if you are just interested in the first N
rows.

In order to bulk collect with limit, you must use an explicit cursor

  open c;

   fetch c limit N;
   fetch c limit N;

  close c;

September 21, 2003 BharathKumar -- Thanks for the question regarding
      "Grants to Users", version 8.1.6

      You Asked
I have an application which has its backend data on one schema in the database.
Should I use the same username password combination for all clients or should I
create individual ID's and make the users access the schema objects using public
synonyms ?

If I use the synonym method, do I have a way of granting all access permissions
on all objects to all the users using a single command ? (I know I can do this
using a PL/SQL procedure using Dynamic SQL, but I want to know a single
command). If I do it once during installation of the application, how do I
ensure the grant of the similar rights to objects created later on the schema as
part of the maintenance/upgrade of the application ?

Expecting a detailed reply.....

Thanks and Regards

Bharath

      and we said...
see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1344402741490
for a very very similar discussion and suggested solution.

You should either

o let everyone log in as themselves (i like this myself, total accountability)

o let people log in as various "roles" -- have many different schemas
representing differing levels of functionality in your application (i've never
seen an application where EVERYONE has the same rights and priveleges).

You do not need and I would not recommend public synonyms in any case.

Your application will have many roles associated with it -- lets for example use
just two

- end_user
- admin_user

Your application setup will grant the appropriate privileges to each role.
Perhaps table T1 is SELECTable by end_user and SELECTable and INSERTable by
admin_user.  You would:

o grant select on t to end_user_role;
o grant select, insert on t to admin_user_role;

You would do this for each object, granting the MINIMUM privelege the prople
needed for the given role.  Never over grant (else end_user will find some way
to delete all of your data someday and it'll be your fault when they do).

Now, if you go the path of everyone logs in as themselves -- you simply grant
the appropriate role to each user as you create their account.  If the set of
privs needs to change over time, change the set of privs the role has and you
are done, all users are updated.

If you got the path of everyone logs in as some set of users, then you will
grant each role needed to each of those users.

As for the synonyms -- don't use them.  Make the first line of your application
after connecting be (you could even use a database  LOGIN trigger to effect this
if you always want the default schema to be your application) :

  alter session set current_schema = YOUR_APPLICATION_SCHEMA;

Now, if they issue:

   select * from T;

instead of us interpreting that as:

  select * from CURRENT_USERNAME.T;

we will execute:

  select * from YOUR_APPLICATION_SCHEMA.T;

this command does not change your privilege set, it only alters the default
schema we use to resolve non fully qualified references.

Again, I like a user/schema in that way I can tell exactly who did what and when
via auditing (either my own auditing or using the system auditing capabilities).

Also, i would suggest using stored procedures to implement your logic and only
granting EXECUTE on procedures and perhaps SELECT on tables (for reporting) --
never granting INSERT, UPDATE, or DELETE on any table at any time.  In that
fashion, you are gauranteed that only YOUR application can modify the tables --
end users will not be able to use some odbc tool to "accidently" destroy your
data.

detailed enough?

Review &
      Followup

      Rating: 4
VERY
      USEFUL  June 26, 2002
Reviewer:  ARUN KHANNA
       from India
HI TOM
        WHICH PRIVILIGES CAN NOT BE GRANTED TO ROLES.

THANKX IN ADVANCE

          Followup:

unlimited tablespace

is the only one.

      Rating: 4
Yes...
      "detailed enough"  June 26, 2002
Reviewer:
      Robert  from Memphis
Tom,

I've never heard of 'set current_schema', this is very good.

I particularly appriciate your sharing your philosophy of design, etc.

Can you please give some information, why you don't like public synonyms?

Thanks,

Robert.

          Followup:

There are performance implications

Most importantly there are namespace clashes.  I'm a big fan of "stick as many
apps in my database as you can".  If they all want a public synonym "S", we are
in a heap of trouble.

      Rating: 3
Having problems
      with public synonym  November 26, 2002
Reviewer:
      Ken  from toronto, cdn.
hi Tom,
We've a data warehouse using Informatica as the ETL tool where we are trying to
truncate a table in the STAGE schema from the EDR schema without prefixing the
table with its schema name(STAGE), but it is not allowed in Oracle:

STAGE>Create table ken_test(id number)
/

STAGE>grant select, update, alter, delete on ken_test to EDR
/

STAGE> Create public synonym ken_test for STAGE.KEN_TEST
/

DBA> grant drop any table to EDR
/

EDR> truncate table ken_test
/
ORA-00942: table or view does not exist

EDR> truncate table STAGE.ken_test
/
Statement processed in 0.161sec

Because the truncate option is provided in Informatica and we can't alter its
SQL, we couldn't prefix the schema name there.

We could do this in a store procedure, but just want to hear your opinion?

Thanks as always Ken.

          Followup:

Yah, you won't be able to do that.

truncate will not work on a synonym.
truncate will not work on a view.

it is that way by design.  you must be explicit with ddl.

ops$tkyte@ORA920.US.ORACLE.COM> create synonym x for scott.emp;

Synonym created.

ops$tkyte@ORA920.US.ORACLE.COM> select * from x;

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

14 rows selected.

ops$tkyte@ORA920.US.ORACLE.COM> drop table x;
drop table x
           *
ERROR at line 1:
ORA-00942: table or view does not exist

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

Table dropped.

it would be the cause of much heartburn otherwise. "whoops, I didn't know it was
THAT table".

stored procedure will work.

      Rating:
      3
  November 26, 2002
Reviewer:
      SUBBARAO
Hi Tom,
The discussion on this article is very interesting. In my current system, I have
nearly 700-800 application users accessing my database. all these users fall
into 4 categories and the access rights are given at the application leve. When
the user logins, based on his login id they will have access to only some
screens in the application.So they cant insert or update the data in other
module tables.

This has been done already and system is running fine and will not change in
this security.

If we were planned to handle this from backend, how we should exactly go about.

My Thinking in this regard is like this: Grant priviliges to the roles. So that
i will create 4 roles. Now the issue is how to grant these roles to the users.
As my users are 700+, do i need to create 700+ users in the database and then
assign the roles to them. Iam not clear in this, can u explain.

SUBBARAO

          Followup:

If you were to use database user accounts and roles, yes, you would create 700+
users and grant to them.

Then, you have the ability to know exactly who did what, when and where.

      Rating: 4
Very Very
      Useful  November 26, 2002
Reviewer:  abhijit
       from Mumbai
Good and useful.
Could you tell me if there is a way to set the current_schema by default to some
other schema whenever the user logs in?

Thanks,
Abhijit

          Followup:

Using a ON LOGON schema trigger would work.  See the appllication developers
guide for details on that.

      Rating:
      3
  November 27, 2002
Reviewer:
      SUBBARAO
hi Tom,

U told "Then, you have the ability to know exactly who did what, when and
where."

Will this be any performance drain, if it exceeds a certain number of users.
What can be the maximum number of users.(this is just for enthusiasm). Can u
tell me any example in any live systems with this kind of setup. And for
tracking who did what and when, we need to enable auditing right? will this also
be the performance issue. If we want some thing we need to scarifice some
things, we can bare the little performance drain in this auditing. Please
comment.

          Followup:

Nothing is a performance issue if it is a requirement of your system.  I always
love it when people say "auditing is a performance issue".  No, no it is not.
Auditing is either something you need to do (like putting an order in an order
table is something you NEED to do in an order entry system -- it is NOT a
performance issue that you have to insert) or something you don't need to do.
It is not a performance issue.

Auditing takes more resources then not auditing.  However, if auditing is
something you have to do -- then you have to do it.

metalink has 6 figures worth of database users (i remember when we had to
upgrade it from 7.3 which only supported 64k users to 8.0 with 2billion max
users -- there was a while we could not add new metalink accounts).

My own internal database at Oracle that I run has

ops$tkyte@ORA9I.WORLD> select count(*) from all_users;

  COUNT(*)
----------
     49800

runs just dandy.

      Rating: 4
A few
      disagreements  November 29, 2002
Reviewer:
      Tony  from India
I have a few doubts here though your approach is interesting.

1. As per your suggestion, we have to create as many db users as we want to
access a single main schema(user) where we have all our application tables. We
have to use
alter session set current_schema = YOUR_APPLICATION_SCHEMA to change the default
schema whenever the users log in. In this approach, the users will not be able
to access their own tables/objects, if they have any, without
<schema>.<username> syntax. How to overcome this problem?

2. For client server applications like D2K, this approach will be good as D2K
checks the privilage before executing any query/DML and throws proper error
message. What if we use VB or other web tools ASP, JSP, etc.? How to write easy
logic to disable/ enable buttons or links in web pages based on their
privilage?. Is it feasible first of all?

3. In my view, web applications have single schema and application users are
created at the application level. Roles (who to access , what links(menu) to
access) are also created at the application level and assigned to the users. So,
access control is done at the application level. This seems more flexible and
maintainable and logical. In your approach, is it possible to do so? will be
happy if you give an example.

4. Is it easy to grant selectivily on each object to each user when the schema
has thousands of tables and new tables are getting added frequently?

5. What if a databas has many main schemas to have tables/objects for many
applications and these schema are accessed by thousands of users. How to
differentiate the users from one app to other apps in case if I want to check
all these from my front-end tool.

6. Is there any licence issues on number of users we create in oracle database?

Expecting your valuable comments.

          Followup:

1) not sure what I'm comparing this to?  Having a single schema -- if so, they
wouldn't have any of their own tables.  Having public synonyms for this other
data?  if so, confusion abounds.  If they (the user) creates a table as the same
name as the public synonym -- what then?

Me, personally, I've never had an issue with using SCHEMA names.  Tools that
generate queries have NO PROBLEMS using the schema name.

To me, this is much ado about nothing.  Users don't log into sqlplus and start
querying - -they use tools, they use stored queries (views).  this is mostly
hidden from them.

2) web tools like jsp's, servlets, etc are just as capable as client server
things as far as security goes.  The same things d2k would do -- you would do in
asp.  I see no differences there.

3) More flexible to you, maybe.

Heinous, terrible, "hate it" to me.

So, do you think this web thing is the last application development paradigm???
It is the end right -- there will be nothing else.

Tell me -- how is is more flexible?  Lets see, database has "create user"
command - you can write code to do a "create user" in your applicaiton.
Database has create role and grant, application has to code create role and
grant.  Database does auditing, application has to code auditing.  (you see
where I am going -- you are emulating the database functionality you already
bought).

I guess, if by flexible you mean "It lets me write more code", you are right.
If by flexible you mean "I get more functionality" -- i have yet to see it.  You
are developing YET ANOTHER authentication/authorization method.  Do you do
single sign on?  Database can.  Do you do advanced authentication methods like
x509 PKI?  Database can. Do you have so many spare cycles that writing
identification and authorization and auditing and access control is something
you can do?  Great -- I don't.

Flexible?  3 years from now the next great programming paradigm comes along.  Oh
sorry you say -- cannot do it cause all of the security is tied up in the
application and none of it is by the data.  You can only access the data via my
old fashioned application - -you cannot build new apps on top of the data using
this new paradigm.  All of a sudden,  your cool web app is as appealing as a
3278 Green screen application from a mainframe.  You know, the green screen apps

o did identification
o authorization
o access controls
o auditing

all tied up in the code.... Hmmm, guess what was really hard to migrate to
client server and then web and then <next really cool programming method here>

Can I use an ad-hoc query tool against your data?  What -- I cannot???  Bummer,
seems to me that any time I put up a database application -- the end users say
"cool, but what I really need to see is X".  Well, if I have I&A, access control
-- everything tied up in the application -- i cannot let them have at the data
now can i?  Guess it is really good for job security -- need more data, new
reports?  I'll be the only one that can do it for you.

Web applications are not special, they are not different, it is the same old
thing on three tiers.  You can make the same mistakes that were made with host
based and client server systems all over again.

4) And it is easier in your application HOW???  If you have thousands of
tables/functions -- it is easier in your "flexible" environment HOW??

5) Read about n-tier proxy authentication, read about fine grained access
control.  That does exactly that.  Secure application roles plus security
policies in place by application.

6) 2 billion.

      Rating:
      5
Disagreements  November 29, 2002
Reviewer:
      Tony Andrews  from London, UK
> 3) More flexible to you, maybe.
>
> Heinous, terrible, "hate it" to me.

You are so right!  This is a message that really needs to be hammered home to
the web / OO community.  They get so wrapped up in how cool their tools are,
they seem to think a database is just a bucket to put their data in while the
app isn't running.  The only DBMS features they want to use are INSERT, UPDATE,
SELECT and DELETE - they don't even like database integrity much, let alone
security.  Gets in the way of doing it "their way", which of course being web/OO
based is assumed to be far superior.

Web-based apps are just the latest way of providing a user interface to the
data.  Managing the data is still (should be) the job of the DBMS.

      Rating:
      4
  February 13, 2003
Reviewer:
      A reader
But what about the overhead of opening individual sessions every time a user
access data? To establish a connection, it takes few seconds and that is
considered toomuch on web applications. That is why connection pooling and
application servers are invented. I don't understand how the two (ie. connection
pooling and having individual database users) can go together. Can you please
explain?

          Followup:

search the documentation for

proxy authentication

there is a "set user" capability in the database.  Also, mod_plsql and many
other things work very hard to reuse a physical circut for you.

      Rating: 4
Compilation
      over database link with this method  March 13, 2003

Reviewer:  A Reader  from UK
Tom,

This was very useful.  However, I have a problem whereby I try to compile a
package and get:

PLS-00201: identifier 'ABCDEF' must be declared

where ABCDEF is a SYNONYM pointing to a TABLE in a remote database (over a
database link).

I used the above method of ALTER SESSION SET CURRENT_SCHEMA in a LOGON trigger
(at Schema level) at the other end of the database link.  The user I connect to
only has CREATE SESSION, ALTER SESSION and SELECT on the schema owner tables.
The trigger was created in this user's schema by SYS.

My question is - is SELECT enough for the compilation as:

USER A, DB A      USER B, DB B            SCHEMA OWNER, DB B
my pkg ---------> Trigger, alters schema ---> ultimate table
        (DB LINK) (Select, Create Session)

If I switch the DB link to be direct to the Schema owner, it compiles but I
don't want this as for security I don't want to have full access to the schema,
only select.

Please advise.

Regards,

Paul

          Followup:

It would be normal to create synonyms on the remote site -- never tried set
current_schema over a dblink.

      Rating: 5
Some more
      help  September 19, 2003
Reviewer:  Sanjaya
      Balasuriya  from Columbo, Sri Lanka
Tom,

Say I have a user X who owns a schema with set of tables and a set of packages.
I have a another user Y, who has only CREATE SESSION.

I want to let Y to modify X's tables and packages. For tables, I can give ALTER
TABLE privilege on X's tables.
But what can I do about packages ? How can I let Y to modify X's packages ?

          Followup:

well, I would just let y log in as x, whats the difference here - not much you
will be stopping Y from doing.

what do you mean by "modify" - do you mean "recompile" or "supply brand new code
for it"

      Rating: 3
Revoking
      privileges  September 21, 2003
Reviewer:  Asim
      Naveed  from Pakistan
Hi,

This is very usefull for me. I have a problem.

I installed Oracle 9i Rel. 2 database , created several
users with most of the default options. These users
have created many tables/indexes in there own user. And
they also use to ALTER them occasionaly.

Now I want that all users can only SELECT, UPDATE,
DELETE, INSERT there existing tables,
but they should NOT CREATE or ALTER anything. CREATE
and ALTER will only be done by SYS.

How can I do this?

Thanks in advance for your help.

Thank

How can I do this

          Followup:

create is easy, just revoke the privileges from them.  just give them CREATE
SESSION and nothing more.  they can modify their existing tables (you might have
to adjust their quotas to allow these tables to grow) but they will not be able
to create more.

ALTER is trickier, you can use a "before alter" "on schema" trigger (you need to
do a before alter or drop probably)

sort of like this example:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4111470834766

      Rating: 4
Lots of Oracle
      Database knowledge  October 02, 2003
Reviewer:
      Carlos Gongora  from Cochabamba, Bolivia
I read your document, I am almost new in this topics.
They are helping me a lot to build an application right now.

THANK YOU

September 21, 2003 Huy -- Thanks for the question regarding "Change
      tables' tablespace", version 9.2

      You Asked
Hi Tom

A schema has tables and indices that have been created in a number of different
tablespaces (a table an its indices might not be in the same tablespace). The
tables are small, a few thousands rows. What is the best way to move all the
tables to a single tabe space, T, and all the indices to a single tablespace, I.

Is there a better way than "Export the data, drop & recreate the tables &
indices in the correct table spaces, import the data"?

Thank you very much for your time in dealing with this question.

Huy

      and we said...
absolutely a better way:

alter table T1 move tablespace T;
alter index idx1_on_t1 rebuild tablespace I;
alter index idx2_on_t1 rebuild tablespace I;
........

that way, you never stand a chance of losing a grant, etc etc etc...
Review &
      Followup

      Rating: 5
Great solution,
      thank you very much.  September 22, 2003
Reviewer:
      Huy Duong  from UK
Hi Tom

Thank you very much. The solution is great.

Best regards

Huy

      Rating: 5
tables in
      tablespaces  September 22, 2003
Reviewer:  vijay
      kaushik  from India,chennai
Tom is asusual great with  his answers .

September 21, 2003 Sanjai -- Thanks for the question regarding
      "Application contexts", version 9.1.0.2

      You Asked
Hi Tom,

IS it possible to create the context using user.package_name.procedure _name.

SQL> /
create or replace context MY_CTX using SCOTT.MY_PACAKAGE.MY_PROCEDURE
                                                        *
ERROR at line 1:
ORA-00933: SQL command not properly ended

Thanks for your help.

      and we said...
it is done at the object level, in this case -- the object is the package.

so no, you cannot specify a single procedure within a package, just the package
itself.

and then any procedure/function in there may set the context.
September 22, 2003 Steve -- Thanks for the question regarding "SQL
      query", version 9.2.0

      You Asked
Hi Tom

Let's say I have a table t, that records the lottery results on a week by week
basis.  The lottery picks 6 balls numbered from 1 to 49 inc. so I have data that
looks like:

draw_date   ball1 ball2 ball3 ball4 ball5 ball6 bonus_ball
1/1/2003      5    39     6     45    23   9      37
1/8/2003      11   21     6     43    19   21     1
etc etc

What I'd like to do is construct a query that, given an input of 6 numbers
between 1 and 49 would compute the number of times that combination would've
been successful at picking 3 or more numbers.  So, for example I choose the
number 1,2,3,4,5,6 the result of the query would be:
3_balls  4_balls  5_balls  6_balls
   8        3       2        0
Obviously not based on the above data!!  Is that possible in one sqlk statement?
 Or would it need some processing in pl/sql?

Thanks

Steve

      and we said...
Almost everything is possible in a single sql statment :)  this is for sure:

ops$tkyte@ORA920> create table t2 as select rownum r from all_objects where
rownum <50;

Table created.

ops$tkyte@ORA920> create table t ( draw_date date, b1 int, b2 int, b3 int, b4
int, b5 int, b6 int, bb int );

Table created.

ops$tkyte@ORA920> create or replace trigger t_trigger
  2  before insert on t for each row
  3  declare
  4          type numArray is table of number;
  5          l_data numArray;
  6  begin
  7          select r bulk collect into l_data
  8            from (select r from t2 order by dbms_random.random)
  9           where rownum <= 7;
 10
 11          :new.b1 := l_data(1);
 12          :new.b2 := l_data(2);
 13          :new.b3 := l_data(3);
 14          :new.b4 := l_data(4);
 15          :new.b5 := l_data(5);
 16          :new.b6 := l_data(6);
 17          :new.bb := l_data(7);
 18  end;
 19  /

Trigger created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t (draw_date) select sysdate-rownum from
all_users;

48 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> variable b1 number
ops$tkyte@ORA920> variable b2 number
ops$tkyte@ORA920> variable b3 number
ops$tkyte@ORA920> variable b4 number
ops$tkyte@ORA920> variable b5 number
ops$tkyte@ORA920> variable b6 number
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec :b1 := 1; :b2 := 2; :b3 := 3; :b4 := 4; :b5 := 5; :b6 :=
6;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select tot_hits, count(*)
  2    from (
  3  select b1_hit+b2_hit+b3_hit+b4_hit+b5_hit+b6_hit+bb_hit tot_hits
  4    from (
  5  select case when b1 in (:b1,:b2,:b3,:b4,:b5,:b6) then 1 else 0 end b1_hit,
  6         case when b2 in (:b1,:b2,:b3,:b4,:b5,:b6) then 1 else 0 end b2_hit,
  7         case when b3 in (:b1,:b2,:b3,:b4,:b5,:b6) then 1 else 0 end b3_hit,
  8         case when b4 in (:b1,:b2,:b3,:b4,:b5,:b6) then 1 else 0 end b4_hit,
  9         case when b5 in (:b1,:b2,:b3,:b4,:b5,:b6) then 1 else 0 end b5_hit,
 10         case when b6 in (:b1,:b2,:b3,:b4,:b5,:b6) then 1 else 0 end b6_hit,
 11         case when bb in (:b1,:b2,:b3,:b4,:b5,:b6) then 1 else 0 end bb_hit
 12    from t
 13         )
 14             )
 15   group by tot_hits
 16   order by tot_hits
 17  /

  TOT_HITS   COUNT(*)
---------- ----------
         0         22
         1         17
         2          7
         3          2

Review &
      Followup

      Rating:
      3
Nice  September 21, 2003
Reviewer:
      P.Srikanth  from Hyderabad,AP,India
Dear Sir,
fine and expect the same from you.I searched for "all" and
"any" keywords but found nothing.Could you please specify some queries which
make use of "any,all and some" keywords.Please do reply.
Thanks.

          Followup:

they would be stop words in my search (eg: they are so common as to not be
indexed)

any all and some are just set operators.

any and some are synonymous -- no difference between them.

select * from t1 where x >= any ( select y from t2 )

says "give me the rows from t1 where x is larger than or equal to ANY value of y
in t2".  it is more commonly expressed as:

select * from t1 where x >= ( select min(y) from t2 )

all is like any but does what it says:

select * from t1 where x >= all ( select y from t2 )

says to get the rows in t1 such that x is larger than or equal to ALL values of
y in T2.  It is commonly expressed as BUT NOT EQUIVALENT to

select * from t1 where x >= (select max(y) from t2);

the reason it is not the same has to do with NULLS.  Consider:

ops$tkyte@ORA920> create table t1 ( x int );

Table created.

ops$tkyte@ORA920> create table t2 ( y int );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t1 values ( 1 );

1 row created.

ops$tkyte@ORA920> insert into t2 values ( null );

1 row created.

ops$tkyte@ORA920> insert into t2 values ( 0 );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t1 where x >= any(select y from t2);

         X
----------
         1

ops$tkyte@ORA920> select * from t1 where x >= (select min(y) from t2);

         X
----------
         1

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t1 where x >= all(select y from t2);

no rows selected

ops$tkyte@ORA920> select * from t1 where x >= (select max(y) from t2);

         X
----------
         1

      Rating:
      4
Nice  September 22, 2003
Reviewer:
      P.Srikanth  from Hyderabad,India
Dear Sir,
Thanks for your instant response.I have a question to ask.It
is
 "Is it possible to obtain the permutations and combinations
of character sequences of a string?For example if i have a
string like "abc",the other possible sequences can be "acb,bca,cab,cba".Is it
possible to write a procedure that takes a string and return its various
combinations.?"
This is the question.Please do reply.
Thanks in advance.

          Followup:

it is possible to write a function to do most anything.  it is just code after
all.

      Rating: 4
Lottery
      Analytical Function  September 22, 2003
Reviewer:
      Richard  from Oxfordshire, UK
When can we expect a Winning_Balls_for_Next_Lottery function?

It would be of great help ;)

      Rating:
      2
OK  September 23, 2003
Reviewer:
      P.Srikanth  from Hyderabad,India
Dear Sir,
Regarding the permutations and combinations of character
sequences of a string,I *need * your help.Could you please
some hints how it can be done?Expecting your reply.
With Regards,
P.Srikanth

September 22, 2003 Shitanshu -- Thanks for the question regarding
      "reducing potential data loss in DR (for standby database)", version 8.1.7

      You Asked
Hi,
In a typical standby database scenario, is there an optimal time interval which
should be targetted for transfer of logs to the standby site?

One of the requirements that I am dealing with, is to ensure that in standby DR
solution, the data loss is not more than 5-10 minutes. This means that the
transfer of log must occur every 10 minutes or less.

Will forcing the transfer of log at a reduced time interval create any potential
impact on the primary site's performance?  Are there any other considerations ?

Thanks.

      and we said...
the best thing for you to do would be to utilize Oracle9i's data guard whereby
LGWR sends the redo as it is generated to the standby site.  Given that Oracle8i
ends defect resolution support at the end of this year....

short of that, yes, you'll have to flip logs every N minutes, yes this can
impact performance.  probably won't be noticed in most systems
September 22, 2003 Gautam -- Thanks for the question regarding
      "Copying Data from 1 table to Another", version 9.2.0.1

      You Asked
Dear Mr. Tom :

I need to copy/update data from 1 view V1 to another table t2. This is a weekly
update. Both view and table contains same structure. Now I have to insert new
raws from V1 to t2 and update raws which are modified. I can write script ,which
can insert data but I am stuck in updating modified raw. (View is based on
external table)
What is the best technique so that I can give maximum performance as the tables
are very large and contains 500000 raws.
Please advise.

Thanking you

Gautam

      and we said...
it is all about MERGE

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/statements_915a.htm#2080942
it is a single command that will

o insert new records
o update existing records

for you.
Review
      & Followup

      Rating:
      4
  September 23, 2003
Reviewer:
      A reader
Thanks a Lot

September 22, 2003 Sachin -- Thanks for the question regarding
      "Index size increased after IMPORT", version 8.1.7

      You Asked
Hi,

I exported a big database and imported it into another.

On a very big table (190 Million records), there are 3 indexes. Index size
before export was 13 GB.

But after import into new database, index size has increased to 20 GB.

I checked, indexes are correct; number of records in new DB is correct.

Please help.
Cheers,
Sachin

      and we said...
index was rebuilt using default pctfree.

existing index must have been "compact"  -- all blocks full.
new index is not "compact", it has white space.

same thing would happen if you rebuild the existing index -- it would get
"bigger" (counter to the "common knowledge" of course that rebuilding an index
'reclaims space' -- it can just as easily make the index larger

Also, it could be due to the extent sizing on new database -- but now I'm
guessing (lack of details).....

September 22, 2003 Lisa -- Thanks for the question regarding
      "Materialized Views/snapshots -- getting them to refresh.", version
      version 8.1.5.0 (8i)

      You Asked
My question is how do I set up the views to refresh on a daily
basis (i.e. midnight).
I have tried this by including the following in the create
snapshot stmt:
....
refresh complete next trunc(sysdate)+24/24
as....

I am creating complex snapshots.
Thanks,

Lisa

Additional Info:
No error msg, snapshot just not being refreshed...
Nothing appears in alert.log.
Job queue, don't have anything set up relating to this....any
help would be great...
Lisa

      and we said...
Snapshots are refreshed via the job queues (or manually via the
dbms_snapshot package).

In order to allow for automatic snapshot refresh, you must set
the init.ora parameters

JOB_QUEUE_PROCESSES

JOB_QUEUE_PROCESSES specifies the number of SNPn background
processes per instance, where n is 0 to 9 followed by A to Z.
Job queue processes are used to process requests created by
DBMS_JOB.  Some job queue requests are created automatically; an
example is refresh support for table snapshots. If you wish to
have your table snapshots updated automatically, you must set
JOB_QUEUE_PROCESSES to a value of one or higher.

JOB_QUEUE_INTERVAL

JOB_QUEUE_INTERVAL specifies the interval between wake-ups for
the SNPn background processes of the instance.  Jobs will only
be run every N seconds (where N is the setting for this
parameter).  So, if you set job_queue_interval = 60 but you have
asked that a job be run every 30 seconds -- it will only run
once a minute as the queues are only inspected for jobs every 60
seconds.  A setting of 1-5 minutes is usually sufficient for
this parameter.

Review &
      Followup

      Rating:
      5
JOB  September 26, 2002
Reviewer:
      Bharath  from USA
I want run the JOB on demand if i run the command dbms_job.submit(I didnt
specify any time interval ..dependent on JOB_QUEUE_INTERVAL)  found the  entry
in user_jobs.My question is will those entry will be wiped off once the JOB is
done

          Followup:

to run a job on demand -- just execute the "WHAT" -- it makes no sense to use a
job if you are going to just call dbms_job.run, just execute whatever "what"
was.

      Rating: 5
snapshot
      refresh  November 01, 2002
Reviewer:  sm
       from usa
O.K.
Hava a table based on a snapshot (some columns in table has to be updateable..so
that's why making a table based on a snapshot)
The snapshot will be refreshed multiple times during the day...
I was thinking about
having a trigger on the snapshot table..so as it refreshes..it will compare the
table with the snapshot table...and make changes...
I don't know how this is going to play..have never dealt with snapshots that
have to be refreshed hourly...
An application is going to be using this table (table based on the
snapshot)...so as snapshot is being refreshed..and throwing changes at the
table...won't we get locks?
What's the best way to do this?

          Followup:

not following you here. sorry. don't understand.

      Rating: 5
DBMS_SNAPSHOT
      DBMS_REFRESH  April 17, 2003
Reviewer:
      Sachin  from Germany
Tom,
We have about 30 snapshots for getting data from two different systems.
They are all complete refreshes.
Most of them are the kind that have time inside the snapshot..like "start with
sysdate next...." statement inside the create snapshot script...
all these will create a job automatically which will have
"what" as "dbms_refresh.refresh"  and a refresh group.
Lately we wanted a snapshot to put some data into another table right after it
refresh....so I created that snapshot as "On Demand"...and created a procedure
that use
DBMS_SNAPSHOT.REFRESH.....
and insert data into another table after it refreshes..

Questions:
1. Currently all of our snapshots have a separate job...
I want to create a procedure and list all of them one after
the other....like
DBMS_SNAPSHOT.REFRESH(SNAPSHOT_1);
DBMS_SNAPSHOT.REFRESH(SNAPSHOT_2);
....
and then submit just one job...and track their success
failure through dba_snapshots.
Of course...I will have to make them on demand first...
is this approach O.K.
2. What is the difference between DBMS_REFRESH AND DBMS_SNAPSHOT

Thanks...

          Followup:

1) as long as it is OK with you to have the refresh serially, one after other,
sure -- its oK

2) dbms_refresh is all about creating and manipulating refresh GROUPS.
dbms_snapshot/mview is about individual snapshots/mv's

      Rating: 4
snapshot
      refresh  September 04, 2003
Reviewer:
      Florin  from The Netherlands
Hi,

I'm using Oracle 8.1.7.4.

Lately I've created 4 snapshots setting the inerval (next) to 10 min.
The snapshots are refreshing without any problem however the parameters in my
init.ora are:

job_queue_interval=60
job_queue_processes=1

How do you expalain this? I thought that by having job_queue_processes=1 no
snapshot will be refreshed. The snapshot is refreshed every 10 min as I defined
it (alter snapshot billing_account_snap refresh fast start with sysdate next
sysdate + 1/144 ;) so it means that the parameter job_queue_interval=60 is
ignored?

Many many thanks in advance.

Sincerely,

Florin

          Followup:

job_queue_processes=0 would mean no auto refresh

with it at 1, they are all refreshed one after the other.

if you set it to 4, they would be refreshed at the same time in parallel.

the interval is not ignored??? i don't know why you think that.  that just means
the job queues will wake up every 60 seconds and say "anything for me to do?"
once out of every ten times -- the answer is "yes, there are 4 snapshots to
refresh"

      Rating: 5
snapshot
      refresh group  September 18, 2003
Reviewer:
      atul  from India
Hi,

1)What view we can use to see snapshot refresh group and
which snapshots are included in refresh group??

2)Can we add updatable snapshots in refresh groups?
3)My snapshot is not refreshing properly where should i see
the errors??

Thanks.
atul

          Followup:

1) all|dba|user_refresh  and _refresh_children

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:8145042756282#12383145941393
2) yes

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96567/repoverview.htm#15398
3) we've got a chapter on that topic

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96567/reptrouble.htm#8520
(monitor your alert logs)

      Rating: 5
Broken
      Job  September 19, 2003
Reviewer:  atul
       from India
Hi,

Thanks for your help..

I am getting output of the query like this

select RNAME,REFGROUP,JOB,broken from dba_refresh;

RNAME              REFGROUP        JOB B
------------------------------ ---------- ---------- - -
UPD_EQMGROUP              3         23 Y

So i can see my job is broken..

what method i can use to reshedule it???
Or what i have to do next??

Thanks.
atul

          Followup:

read about dbms_job -- there are routines to unbreak a job.

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96612/d_job.htm#999107

      Rating:
      5
Query  September 22, 2003
Reviewer:
      atul  from India
Hi,

I have gone through the link..
Just i want to confirm

should i use,
DBMS_JOB.RUN(job#)

To reshedule the broken job..

Thanks.
atul

          Followup:

well, you can "unbreak" it as well using dbms_job.broken

if you use run, you will have to sit there and wait for it to run to completion.
 it is not asyncronous

September 22, 2003 Reader -- Thanks for the question regarding
      "Subtype Table Design", version 8.1.7

      You Asked
Dear Tom,
I have a problem in data model for OLTP system.
There is a ORDER entity which has product details (mandatory info), customer
info and finance info. We do not have separate customer or finance entity. The
customer and finance info in order are non mandatory. So I have taken the
approach of SUBTYPE and separated the ORDER entity into 3 tables having same
primary key.
Now review people are saying there will be performance degradation.
DELETE/INSERT/UPDATE will triple and extra joins in SELECT.
I said customer fields around 25 may be null in some of the records, if we keep
in single table. Same to finance.

Please suggest,

1.   If we should keep it in single table or not? (High performance is required)

2.   If we keep it together, some orders may have NULLs in arround 50 fields. Is
there any problem having NULLs in many columns of the records of a table?

Also can you guide me to any location (Net or document) having such scenario.

-San

      and we said...
1) sounds like a single table design to me.  I do not factor out 1:1 optional
relationships unless a table is "really really wide" -- really wide being into 3
digits.

put the most likely to be null columns at the end of the create table and when
they are NULL, they will consume NO space.

2) nope.

just benchmark it.  you would always have to join to pick up this optional
information (2 or 3 LIO's at least per row retrieved for each optional set of
data) vs an extra 50 bytes of flags saying "this is null".  I would go for the
extra 50 bytes in a row that will be accessed via an index rather then incurring
2/3 LIO's to read an index to access another table.
Review &
      Followup

      Rating: 2
too
      precise?!?!  September 15, 2003
Reviewer:
      Gabriel  from Toronto
Tom,

I believe your answers were, as always, precise and correct … as long as the
original question was “is splitting a wide table (with plenty of nullable
columns) into smaller (less columns) tables going to improve performance of
select operations?” … Actually, this is what the questions really boiled down
to!

But where is “sounds like a single table design to me” coming from?

In many cases I’ve seen someone ask you a question and submitting some SQL code
… and you answered the question but also “went above the call of duty” and
warned (since you could only extrapolate that all/most of the code is like that)
about not using bind variables in SQL statements. I see a similar case here …
the bigger problem is the potentially bad design (unless 3NF is not good for
OLTP) … if Order, Order Item, Product, Customer, Price, Tax, Order Status, etc.
are OK to be denormalized (or even non-existent) and implemented as one single
table then lots of those Cobol legacy mainframe applications are state of the
art. Of course, what San is trying to do is not “SUBTYPE”ing … and he is not
going to win the argument with the review people by suggesting simply to split
some table.

My suggestions to you San:
1.    Let the review people know what the problems are … data quality, difficult
to integrate with other systems, undocumented business rules,  … or whatever
your pain and observations are … let them find the solutions. If no problems …
why fix it?
2.    Get them to buy Tom’s upcoming book “Effective Oracle by Design” … or at
least read the first 3 chapters online.

Gabriel

          Followup:

umm, this seemed really really really simple.

but you are right -- there is an argument for some normalization in a real
design.  the customer data -- should be entered once (that would make it a M:1
relationship from order to customer)

the finance data -- not sure about that -- but customer might be a candidate for
a foreign key to existing data.

      Rating: 4
It's
      clear  September 22, 2003
Reviewer:
      Santhosh  from India
Thanks to Tom and Gabriel.

All the information about customer and finance are entered every time a order is
created. So I feel, what Tom suggested is right approach (Having single table).

How will it help having Nullable columns at the end of table? Also please guide
me to any of your document, having design tips as this.

-Santhosh

          Followup:

if the last column in a table is null, we record ZERO bytes on the block for it.
 if the last two are null, zero.  if the last three, zero and so on.

so, put the columns most likely to be null at the "end" of the table create and
you might save a byte or two.

      Rating: 5
Yes it was
      helpful  September 22, 2003
Reviewer:  Shrek
       from DC Metro
Tom:

Agreed that the single table approach was the best choice for San's situation
and has the least LIO's, no arguments there.

I have a question on clustered indexes. If we do split it up into 3 tables, and
have an Oracle clustered index, will this help and result in lesser LIO's? is
there any overhead involved? Under what circumstances would you recommend a
clustered index?

Thanks,
Shrek

          Followup:

using a b*tree cluster -- we can store the data from the 3 tables on the same
block -- yes.

It might not result in less LIO's (it could but it won't assure it), but it will
result in less PIO's and a better buffer cache efficiency -- we'll read that
block N times -- but it'll be the same block, it won't be N blocks to be read
from disk, it won't be N blocks in the buffer cache, it will be a single PIO, a
single block to buffer.

but we would still need to read it logically for each table during the join.

      Rating: 2
much ado about
      (relatively) nothing  September 23, 2003
Reviewer:
      Gabriel  from Toronto
<Santosh>
“All the information about customer and finance are entered every time a order
is created. So I feel, what Tom suggested is right approach (Having single
table).”
</Santosh>

Of course Tom’s answer was appropriate … but, you asked a very _narrow_ question
… something to the effect of: … “I have a very wide ORDER table with plenty of
optional columns … is splitting this table into 3 tables (having the same PK)
going to degrade performance?”

First of all, I had understood that this is an existing application in need of
some fixes (performance, integration, whatever) … I’m now reading that it is a
brand new modeling effort. It is an important distinction since making changes
to a production system has an “impact analysis” side to it … the choice of what
is worth doing and which solutions (since there may be multiple ways to solve a
problem) should be employed are critical to patching a system (maximum benefits
with minimal disruption/effort).

If this is a brand new modeling effort, it is all the more important to get the
model right (or, better said … to not get the model obviously wrong) … and this
is because you HAVE the time and opportiunity to do it! In my opinion the bigger
issue you have is why the ORDER table is the way it is … in other words, you may
not even have to think about splitting the table if you had the model right!

Now, I may be totally off base here … maybe this is indeed your pet project for
tinkering with Oracle features … or maybe this is a very, very simplistic
business model for capturing orders. If this is the case, then, we collectively
have already spent more time talking about it than it is worth doing.

But, if this model is to resemble in any way any real business case out there
then you have a poor implementation for the Order entity. Here is just one
question: “can a customer order more than 1 product type?” … if yes … how many?
… a single table design would have repeating columns for storing product type
details (a FINITE  number of repeating groups) … would you like to tell you
customer that he can only order 5 product types at once?

Just think of you grocery receipt … the whole thing is the Order … the products
are listed in Order Lines … some Order Lines are for Product Discounts, Coupons
… an Order Line has a Quantity, an Amount, may have a Tax Exemption indicator,
etc. … at the bottom there is the subtotal plus lines for the applicable taxes
(federal, state, whatever) and a grand total. The Order is for a customer … the
customer may be anonymous (grocery store) or maybe known (Oracle knows who buys
their software) … or maybe is known indirectly through some Loyalty Card. There
is also a Method of Payment for the order … your company may accept cash and
Visa … in the future may accept other Payment Method Types (MasterCard, AMEX,
cheques, etc.). So, any skeleton of a robust data model would have some of these
entities: Order, Order Line, Product Type and/or Service Type, Order Status,
Customer, Payment Method Type, Currency, Tax Type, Discount, etc.

Maybe, you business case is indeed very simplistic and you feel a collapsed
Order entity is OK … well, are there any other processes requiring this data?
Once captured the orders may have to be fulfilled, may be used for marketing or
to manage the supply chain (or maybe there is an army of employees continuously
looking for empty shelves?!?!?) … what do you think is easier and less prone to
error: to write a SQL joining ORDER, ORDER_ITEM and PRODUCT_TYPE on
ORDER.ORDER_ID = ORDER_ITEM.ORDER_ID and ORDER_ITEM.PRODUCT_TYPE_ID =
PRODUCT_TYPE.PRODUCT_TYPE_ID? … or something that queries one table only … but
has to deal with the repeating product groups on each order record?

Bottom line … I think you may be going in the wrong direction with your
application … asking a narrow (in scope) question and getting the correct answer
even from a guru like Tom, does not, in any way, validate the larger (in scope)
modeling approach. I fail to understand this obsession with IOs … we’re talking,
in the case of a SELECT (for a row by PK), about _improving_ the response time
from 2 milliseconds to 1 (or whatever) … yes, huge (relative) improvement, but
is it worth it? is the user going to notice? … next to your supper fast SELECT,
you’ll have to write self outer joins and plenty of other hard to tune SQL. You
know, when one gets an OLTP model reasonably right then everything seems to flow
nicely after that (now major bottlenecks, no catastrophic SQLs, etc.) … I see
Tom diligently answer questions regarding monstrous SQL  … and thinking “if only
I were the boss of the guy/gal who designed that thing” … but then I realize it
is hopeless and change the thinking to “… or I could be the consultant that
fixes that … I could make a living out of it!”.

So, here you have it … but don’t take my word for it … just pick any modeling
book out there … it will surely have a chapter on Order Capturing/Processing …
just start from there.

As for …
<Shrek>
“Agreed that the single table approach was the best choice for San's situation
and has the least LIO's, no arguments there.”
<Shrek>

I’m not sure what “situation” you’re talking about … if reducing IO on SELECT
were the only consideration than all systems out there would consist of one
single table … hey Tom, we don’t need the join syntax … hey, it is one table
only … we don’t need Oracle either … a file will do just fine. Sorry Shrek … I
don’t mean to be nasty … I just hate the fact that you nudged Santosh along the
wrong path (without so much of a warning).

<Gabriel>
“… but you asked a very _narrow_ question …”
</Gabriel>

Yes, he did … and he got the answer he deserved … so, why am I blabbing here
for? Just go away and everyone will be happy.

September 22, 2003 -- Thanks for the question regarding "can I
      rebuild an index online in oracle 8i", version 8.1.7.4

      You Asked
In 8i can I rebuild an index online?  Are there any restictions on others using
the table at the same time?  I am not using a partitioned table or parallel
server.

      and we said...
ops$tkyte@ORA817DEV> alter index EMP_PK rebuild online;

Index altered.

yes you can...

are their restrictions?  well -- depends on how you define that work.  here are
some things to consider:

o there is a brief need for a lock at the beginning of this command.  the
rebuild online will wait for all outstanding transactions to complete and then
it'll start.  so, it would be best to wait for a time of "low use"

o it'll take 2x or thereabouts the space PLUS room to hold the modifications so
we can sync the two indexes for after a bit.

Review &
      Followup

      Rating: 2
is this only
      for enterprise edition?  September 22, 2003

Reviewer:  Tina McAllsiter  from heathrow, fl

I ran and got error ORA-00439:  feature not enabled:  Online Index Build.  So is
this a feature for enterprise edition only?  We have standard edition.

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96531/ch5_edit.htm#89333
it is EE and PE (enterprise/personal)

September 22, 2003 Sampath -- Thanks for the question regarding
      "Storing Reports in the database", version 8.1.6

      You Asked
Hi Tom!
      Thanks for taking my question . I have a web application developed with
Oracle8.1.7/WebDB2.2 . I have some reports coded in Web Tookit . They run over
tables with 5 - 10 million records and
retrive 1000 to 3000 records .Users have to wait 5 - 10 minutes
 to see the output . We refresh our database every morning and
system is used only for reporting after that .
      Is there any way I can run most freuently used reports with batch job ,
save them  and give the links to users ? The reports are 1MB to 3MG in size.
a) Can I save the html output in the database table ? A small example to save it
in the database would be great .

b)  Or do you recomend saving them in file system ?

There are some permission levels for users to access these reports .
Which one (a or b) do you recomend to run them in batch mode and
allow the access based on their roles ?

Thanks in advance,
Sampath

      and we said...
You can use the procedure:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:347617533333
to dump the output of a mod_plsql procedure to a file.  You would schedule this
to run via DBMS_JOB or as part of your load.

then the users would just pull up these monsterous reports from the file system
-- 1 to 3mb, glad I don't have to read them.  my attention span is far too short
for that.

You can setup security in the file system using the webserver as well -- put the
reports into different directories and use the web server to protect each
directory with a different group.

alternatively, you could insert the "lines of the report" into a table with a
"sequence" to order the rows (instead of using utl_file to write to a file).
Then you would dump the rows from the table using a procedure on demand if you
don't like using the OS files.
Review &
      Followup

      Rating:
      4
  October 09, 2002
Reviewer:  Sampath

Tom,
    Thank you so much .
Sampath

      Rating: 4
loop with a
      range  March 04, 2003
Reviewer:  an
       from de
Hi Tom:

our web has written a stand alone Application that will do the validation of the
emails addresses.
It takes as an input a start clientid and an end clientid (e.g. 10000 to 20000)
as a range to processes in a batch run.
So, we need help generating a list of Start clientids and End clientids so we
can validate 10,000 records per night where email_state is null.
would you show me how i can generate those records to a file?
thank you as always,
an

          Followup:

er?  not making sense.  You are not asking me how to write a query to get
records with clientid between 10000 and 20000 are you?

      Rating: 4
How
      About  March 05, 2003
Reviewer:  Steve
      Cooper  from Blackburn, UK
Sampath,

Couldn't you use MV's to speed up the underlying query, build them once every
morning when you refresh your data and then just use query rewrite?.....Isn't
this what MV's are all about?

      Rating: 4
re: loop
      range  March 05, 2003
Reviewer:  an
       from de. usa
sorry, Tom
there are about 500k validate records in the client table.   We are going to run
batches of 10,000 clientids each night.
We need to generate a list of Start and End clientids for every 10,000 records
in database where the EMAIL_STATE is NULL.
my problem here is i don't know how i could write a procedure that will generate
a list, first loop from 10000 to 20000, then other 20000 to 30000, and go on
until reach 500k.

thank you,

          Followup:

really -- seems like a straight forward enough process, if you know your data.

if the clientids are sequential -- this is, as they say, trivial (you have to be
able to generate (10000, 20000), (20001, 30000) and so on -- thats pretty "darn
easy"

If they are nonsequential, it is not that hard - just take the min/max clientid,
assume an even distribution, do a little division and there you go.

This is just "math"

      Rating: 4
max &
      min  March 05, 2003
Reviewer:  an

thank you, Tom,

here is what i did, but why my max and min only showed for first two, the rest
15 did show.

declare

   v_max            number;
   v_min            number;
   v_max2            number;
   v_ncount            number;
   v_total            number;

begin

select count(*) into v_total
from temp_client
where emailstate is null;

v_ncount := 0;

while v_ncount < v_total  LOOP

    if v_ncount=0 then
        select max(clientid) into v_max
        from temp_client
        where emailstate is null
        and rownum < 10001;

        select min(id) into v_min
        from temp_client
        where emailstate is null
        and rownum < 10001;

        dbms_output.put_line('SET1:  max=' || v_max || 'min=' || v_min);
       dbms_output.put_line('Counter = '|| v_ncount);

    else

        select max(clientid) into v_max
        from temp_client
        where emailstate is null
        and clientid > v_max2
        and rownum < 10001;

        select min(clientid) into v_min
        from temp_client
        where emailstate is null
        and clientid > v_max2
        and rownum < 10001;

        dbms_output.put_line('SET:  max=' || v_max || 'min=' || v_min);
        dbms_output.put_line('Counter = '|| v_ncount);

    end if;

    v_max2:= v_max;
    v_ncount := v_ncount + 10000;

end loop;
end;

SET1:  max=6575238min=400004
Counter = 0
SET:  max=456789565min=6774835
Counter = 10000
SET:  max=min=
Counter = 20000
SET:  max=min=
Counter = 30000
SET:  max=min=
Counter = 40000
SET:  max=min=
Counter = 50000
SET:  max=min=
Counter = 60000
SET:  max=min=
Counter = 70000
SET:  max=min=
Counter = 80000
SET:  max=min=
Counter = 90000
SET:  max=min=
Counter = 100000
SET:  max=min=
Counter = 110000
SET:  max=min=
Counter = 120000
SET:  max=min=
Counter = 130000
SET:  max=min=
Counter = 140000
SET:  max=min=
Counter = 150000
SET:  max=min=
Counter = 160000
SET:  max=min=
Counter = 170000

          Followup:

Never mind, just do this:

  1  select nt, min(object_id), max(object_id)
  2    from ( select object_id, ntile(10) over ( order by object_id ) nt
  3             from all_objects
  4         )
  5*  group by nt
ops$tkyte@ORA920> /

        NT MIN(OBJECT_ID) MAX(OBJECT_ID)
---------- -------------- --------------
         1              3           3120
         2           3121           6282
         3           6283           9335
         4           9336          12388
         5          12389          15441
         6          15442          18494
         7          18495          21547
         8          21548          24617
         9          24618          28443
        10          28444          44320

10 rows selected.

replace all_objects with your table.
replace object_id with clientid

      Rating: 3
similar report
      question but on query statement  March 06, 2003

Reviewer:  Godwin  from Ghana
Hi Tom,
I need to generate this report to select all staff of the university who have
not retired or resigned from employment. Am picking this record from 3 tables:
employee details table
i.e employee(emp_id,name,staff_type)
employee_job_history table(emp_id,start_date,end_date,status_id),
The employee_job_history table does not have a primary since it stores an
employee more that once in order to keep track of previous service record of
each staff.
post_status_tab table
(status_id,status_desc)
the post_status_tab stores record like
01,RETIRED
02,RESIGNED
03,PROMOTED
04,VACATED POST
etc.
Now the problem with my report is that after i run the report it still includes
staff who had retired or resigned from the University.
Here is the code:
SELECT  emp_id,name
FROM employee a
,employee_job_history b
,post_status_tab c
WHERE a.emp_id= b.emp_id
 and   b.status_id=c.status_id
 AND   b.start_date =(SELECT MAX(j.start_date)
                   FROM employee_job_history j
                   WHERE j.emp_id=a.emp_id
                   AND j.status_id not in ('01','02','04'))
order by name asc
Pls help!!!
Thanks.

          Followup:

WHERE a.emp_id= b.emp_id
 and   b.status_id=c.status_id
 AND   b.start_date =(SELECT MAX(j.start_date)
                   FROM employee_job_history j
                   WHERE j.emp_id=a.emp_id)
 AND b.status_id not in ('01','02','04'))
order by name asc

you need to get the record with the max date BUT ONLY if it is not a retired
record.  you had the predicate for "retired" in the wrong level there.

      Rating: 5
How to save a
      Report in Database ?  September 22, 2003
Reviewer:
      suresh  from India
  hi, tom

     i created one report(.rdf) by Using ReportsBuilder (Reports6i). How to save
this report in Database ?
  ( Save my report as database report how to do this).

  pls reply immd .... very Urgent...

  ok, tom bye.

          Followup:

don't even know if you can -- that is, don't know if reports will read it from
the database.

try otn.oracle.com -> discussion forums -> developer.  I've never actually used
reports myself

September 22, 2003 Randy -- Thanks for the question regarding
      "Hierarchical Tree ", version 8.0.5.1.0

      You Asked
I have a table:

SQL> desc emp;
 Name                            Null?    Type
 ------------------------------- -------- ----
 EMP_ID                                   NUMBER
 NAME                                     VARCHAR2(10)
 PARENT_ID                                NUMBER
 ORG_LEVEL                                NUMBER

With the following data:

SQL> select * from emp;

    EMP_ID NAME        PARENT_ID  ORG_LEVEL
---------- ---------- ---------- ----------
         1 KING                           1
         2 SMITH               1          2
         3 JONES               1          2
         4 BALL                1          2
         5 HOBBS               2          3
         6 BLACK               2          3
         7 WHITE               3          3
         8 MARSH               3          3
         9 CHANG               3          3
        10 STEWART             4          3
        11 DAILY               4          3
        12 GREEN               4          3
        13 BELL               10          4
        14 HARRIS             10          4

14 rows selected.

I need to build an output file based upon the hierarchy in the
organization.  I can partially accomplish that with the
following SQL statement:

select level, substr(lpad(' ',level*2)||name,1,20) name
from emp
start with org_level = 1
connect by prior emp_id = parent_id;

     LEVEL NAME
---------- --------------------
         1   KING
         2     SMITH
         3       HOBBS
         3       BLACK
         2     JONES
         3       WHITE
         3       MARSH
         3       CHANG
         2     BALL
         3       STEWART
         4         BELL
         4         HARRIS
         3       DAILY
         3       GREEN

14 rows selected.

The problem/trick is:

I need to format the output with XML tags which OPEN and CLOSE
each managers' division.  For example:

<BLOCK="KING">
    <BLOCK="SMITH">
        <DETAIL="HOBBS">
        </DETAIL>
        <DETAIL="BLACK">
        </BLACK>
    </BLOCK>
    <BLOCK="JONES">
        <DETAIL="WHITE">
        </DETAIL>
        <DETAIL="MARSH">
        </DETAIL>
        <DETAIL="CHANG">
        </DETAIL>
    </BLOCK>
    </BLOCK="BALL">
        <BLOCK="STEWART">
            <DETAIL="BELL">
            </DETAIL>
            <DETAIL="HARRIS">
            </DETAIL>
        </BLOCK>
        <DETAIL="DAILY">
        </DETAIL>
        </DETAIL="GREEN">
    </BLOCK>
</BLOCK>

It's obvious, but, BLOCKS are "PARENT" employees, DETAIL are
employees who don't have anyone reporting to them.

Where I'm running into problems, there is no limit to how many
levels deep the chart may go.  How do I know where an employee's
hierarchy ends if he/she has an employee who has employee's
reporting to him/her?

I need to do this in PL/SQL for future maintenance UNLESS it
can't be done.  Then I need to do it in something that can do
it.

Oracle8 Release 8.0.5.1.0 - Production
PL/SQL Release 8.0.5.1.0 - Production
CORE Version 4.0.5.0.0 - Production
TNS for Solaris: Version 8.0.5.0.0 - Production
NLSRTL Version 3.3.2.0.0 - Production

Partitioning = FALSE
Objects = FALSE
Parallel Server = FALSE
Advanced replication = FALSE
Bit-mapped indexes = FALSE
Connection multiplexing = TRUE
Connection pooling = TRUE
Database queuing = FALSE
Incremental backup and recover = FALSE
Instead-of triggers = FALSE
Parallel backup and recovery = FALSE
Parallel execution = FALSE
Parallel load = FALSE
Point-in-time tablespace recov = FALSE

      and we said...
With a tiny bit of PLSQL -- I believe we can do this in SQL.
The query is a bit tricky but workable.  I did my example on th
SCOTT.EMP table which is very much like your example.  Here it
is:

scott@ORA806.WORLD> create or replace package state_pkg
  2  as
  3      g_last_level    number;
  4
  5      function get_previous_level( p_my_level in number )
  6      return number;
  7      pragma restrict_references( get_previous_level,
  8                                  wnds, rnds );
  9
 10
 11      pragma restrict_references( state_pkg, wnds, rnds );
 12  end;
 13  /

Package created.

scott@ORA806.WORLD>
scott@ORA806.WORLD>
scott@ORA806.WORLD> create or replace package body state_pkg
  2  as
  3
  4  function get_previous_level( p_my_level in number )
  5  return number
  6  is
  7      l_last_level number default g_last_level;
  8  begin
  9      g_last_level := p_my_level;
 10      return l_last_level;
 11  end;
 12
 13  end;
 14  /

Package body created.

This state pkg is callable from SQL.  We'll use it to figure
out what the level of our previous row in the result was.  If
the previous row had a level GREATER then us, we need to
generate some </block> tags.  Thats what this is for

It'll take us 2 queries to get the entire result set.  I'll run
them once so you can see what they do and then again with th
appropriate sqlplus settings to get it to generate the entire
answer as we desire it....

scott@ORA806.WORLD> column data format a40
scott@ORA806.WORLD>
scott@ORA806.WORLD> select lpad( ' ',curr_level*2) ||
  2         decode( sign( prev_level- curr_level ),
  3                    1, replace( lpad('*',
  4                               prev_level-curr_level,'*'),
  5                                '*', '</block>' ) ,
  6                       null)  ||
  7         decode( y.mgr,
  8                  NULL, '<detail="'||x.ename||'"></detail>',
  9                        '<block="'||x.ename||'">' ) data
 10    from ( select rownum r, level curr_level, ename, empno,
 11            state_pkg.get_previous_level(level) prev_level
 12             from emp a
 13            start with mgr is null
 14          connect by prior empno = mgr ) X,
 15         ( select distinct mgr
 16             from emp ) Y
 17   where x.empno = y.mgr (+)
 18   order by r
 19  /

DATA
----------------------------------------
  <block="KING">
    <block="JONES">
      <block="SCOTT">
        <detail="ADAMS"></detail>
      </block><block="FORD">
        <detail="SMITH"></detail>
    </block></block><block="BLAKE">
      <detail="ALLEN"></detail>
      <detail="WARD"></detail>
      <detail="MARTIN"></detail>
      <detail="TURNER"></detail>
      <detail="JAMES"></detail>
    </block><block="CLARK">
      <detail="MILLER"></detail>

14 rows selected.

That query did 99% of the work.  To break it down:

o  select rownum r, level curr_level, ename, empno,
                state_pkg.get_previous_level(level) prev_level
           from emp a
          start with mgr is null
        connect by prior empno = mgr

created a set we can order to preserve the hierarchy later.  we
added rownum to force this inline view to be resolved as a
temporary table.  This inline view has the level of this record
as well as the PREVIOUS level of the record that came right
before it.

o select distinct mgr
           from emp

created a set of EMPNOs who are managers....

o we outer joined these two together.  If, after we join, the
MGR is NULL then this person manages NO ONE.  If the MGR is not
null, they managed at least one person....

o order by r

puts the hierarchy back into the right order after the join....

o  decode( sign( prev_level- curr_level ),
                  1, replace( lpad('*',
                             prev_level-curr_level,'*'),
                              '*', '</block>' ) ,
                     null)

that decode closes blocks for us.  It looks at the current rows
level and the prev_level.  If the PREVIOUS level was GREATER
then our level -- the sign() will be +1 (else if they are equal
it'll be zero, if prev is less then, it'll be -1 ).  If the
prev_level is greater then the curr_level, we generate a set of
close block tags using lpad with lots of *'s and replace the *'s
with </block>.

o decode( y.mgr,
                  NULL, '<detail="'||x.ename||'"></detail>',
                        '<block="'||x.ename||'">' )

simply decides whether the ename should be wrapped in <detail>
or <block> tags...

All that is left now is the final </block> tags...

select replace(
         lpad('*',(state_pkg.get_previous_level(null)-1),'*'),
         '*',
         '</block>' ) data
  from dual
/

DATA
----------------------------------------
</block></block>

1 row selected.

And they get them...  Now we show how to generate the entire
result set in sqlplus:

scott@ORA806.WORLD>
scott@ORA806.WORLD> set heading off
scott@ORA806.WORLD> set feedback off
scott@ORA806.WORLD> set echo off
I ran both queries right here

  <block="KING">
    <block="JONES">
      <block="SCOTT">
        <detail="ADAMS"></detail>
      </block><block="FORD">
        <detail="SMITH"></detail>
    </block></block><block="BLAKE">
      <detail="ALLEN"></detail>
      <detail="WARD"></detail>
      <detail="MARTIN"></detail>
      <detail="TURNER"></detail>
      <detail="JAMES"></detail>
    </block><block="CLARK">
      <detail="MILLER"></detail>

</block></block>

scott@ORA806.WORLD>
Review & Followup

      Rating: 4
Pure sql
      solution  November 15, 2001
Reviewer:  Mikito
      Harakiri
The query below works as Tom's PL/SQL procedure

select n2.l, substr(lpad(' ',n2.l*2)||n2.ename,1,20), n2.l-n1.l drop_level from
  (select level l,  ename, rownum rn from emp
  start with ename = 'KING'
  connect by prior empno = mgr) n1,
  (select level l,  ename, rownum rn from emp
  start with ename = 'KING'
  connect by prior empno = mgr) n2
where n1.rn+1 = n2.rn

      Rating: 3
copy the tree
        May 02, 2002
Reviewer:  Charanjiv
       from Delhi India
Hi
In the same tree structure, can you tell us how can we copy a part of the tree
with new node ids to another part of the tree.
we have a table which stores Folder_Id and Parent_folder_Id
we want to copy a folder with its subfolder to another location.

Thanks
Charanjiv

      Rating: 5
Folder
      Tree  September 22, 2003
Reviewer:  David
      Schwartz  from NJ. USA
This example is great. I'm using it for a folder tree.
Is it possible to indent the child fodlers accoring to their level?
In the above code the chidren are indented according to the level column. I just
have an ID & a parent ID.

          Followup:

you can glue in an rpad() based on the level, yes.

      Rating: 5
Folder
      Tree  September 22, 2003
Reviewer:  David
      Schwartz  from NJ, USA
rpad based on level?

I don't understand.
In my case there is no [Level] column. My table has a list of folders with ID &
Parent_ID. There is no predefined level.

          Followup:

in a connect by -- there is the psuedo column LEVEL you have access to -- I used
it in my examples above

September 22, 2003 Jack -- Thanks for the question regarding
      "Dictionary views - data on column constraints", version pl/sql 2.3

      You Asked
I am interested in finding out the constraints on table and view columns (eg, if
they are primary keys, foreign keys and what table/columns are referenced, any
default values, if they are constants, any check constraints for the column, and
if they reference a v. 8.0 nested column).  Though the dictionary views
USER_TABLES, _TAB_COLUMNS AND _CONS_COLUMNS give some information on column
constraints (eg, if null, data type and size), I could not find any views for
the particular information I needed and have listed above.  Could you tell me
where I might find this information ?

Thanks, Jack

      and we said...
1) primary keys on a table:

scott@TKYTE816> select a.constraint_name, b.column_name
  2  from user_constraints a, user_cons_columns b
  3  where a.constraint_type = 'P' and a.constraint_name = b.constraint_name
  4  and a.table_name = 'EMP'
  5  order by b.position
  6  /

CONSTRAINT_NAME                COLUMN_NAME
------------------------------ ------------------------------
EMP_PK                         EMPNO

scott@TKYTE816> 4
  4* and a.table_name = 'EMP'
scott@TKYTE816> c/EMP/DEPT
  4* and a.table_name = 'DEPT'
scott@TKYTE816> /

CONSTRAINT_NAME                COLUMN_NAME
------------------------------ ------------------------------
DEPT_PK                        DEPTNO

2) foreign keys -- this query is overkill for you but I had it lying around.  It
generates the foreign key statements given a parent table (i use it to drop and
rebuild constraints for a truncate or something like that)

scott@TKYTE816> @cons emp
scott@TKYTE816> column fkey format a80 word_wrapped
scott@TKYTE816> select
  2  'alter table "' || child_tname || '"' || chr(10) ||
  3  'add constraint "' || child_cons_name || '"' || chr(10) ||
  4  'foreign key ( ' || child_columns || ' ) ' || chr(10) ||
  5  'references "' || parent_tname || '" ( ' || parent_columns || ');' fkey
  6  from
  7  ( select a.table_name child_tname, a.constraint_name child_cons_name,
  8           b.r_constraint_name parent_cons_name,
  9           max(decode(position, 1,     '"'||
 10                  substr(column_name,1,30)||'"',NULL)) ||
 11           max(decode(position, 2,', '||'"'||
 12                  substr(column_name,1,30)||'"',NULL)) ||
 13           max(decode(position, 3,', '||'"'||
 14                  substr(column_name,1,30)||'"',NULL)) ||
 15           max(decode(position, 4,', '||'"'||
 16                  substr(column_name,1,30)||'"',NULL)) ||
 17           max(decode(position, 5,', '||'"'||
 18                  substr(column_name,1,30)||'"',NULL)) ||
 19           max(decode(position, 6,', '||'"'||
 20                  substr(column_name,1,30)||'"',NULL)) ||
 21           max(decode(position, 7,', '||'"'||
 22                  substr(column_name,1,30)||'"',NULL)) ||
 23           max(decode(position, 8,', '||'"'||
 24                  substr(column_name,1,30)||'"',NULL)) ||
 25           max(decode(position, 9,', '||'"'||
 26                  substr(column_name,1,30)||'"',NULL)) ||
 27           max(decode(position,10,', '||'"'||
 28                  substr(column_name,1,30)||'"',NULL)) ||
 29           max(decode(position,11,', '||'"'||
 30                  substr(column_name,1,30)||'"',NULL)) ||
 31           max(decode(position,12,', '||'"'||
 32                  substr(column_name,1,30)||'"',NULL)) ||
 33           max(decode(position,13,', '||'"'||
 34                  substr(column_name,1,30)||'"',NULL)) ||
 35           max(decode(position,14,', '||'"'||
 36                  substr(column_name,1,30)||'"',NULL)) ||
 37           max(decode(position,15,', '||'"'||
 38                  substr(column_name,1,30)||'"',NULL)) ||
 39           max(decode(position,16,', '||'"'||
 40                  substr(column_name,1,30)||'"',NULL))
 41              child_columns
 42      from user_cons_columns a, user_constraints b
 43     where a.constraint_name = b.constraint_name
 44       and b.constraint_type = 'R'
 45     group by a.table_name, a.constraint_name, b.r_constraint_name ) child,
 46  ( select a.constraint_name parent_cons_name, a.table_name parent_tname,
 47           max(decode(position, 1,     '"'||
 48                  substr(column_name,1,30)||'"',NULL)) ||
 49           max(decode(position, 2,', '||'"'||
 50                  substr(column_name,1,30)||'"',NULL)) ||
 51           max(decode(position, 3,', '||'"'||
 52                  substr(column_name,1,30)||'"',NULL)) ||
 53           max(decode(position, 4,', '||'"'||
 54                  substr(column_name,1,30)||'"',NULL)) ||
 55           max(decode(position, 5,', '||'"'||
 56                  substr(column_name,1,30)||'"',NULL)) ||
 57           max(decode(position, 6,', '||'"'||
 58                  substr(column_name,1,30)||'"',NULL)) ||
 59           max(decode(position, 7,', '||'"'||
 60                  substr(column_name,1,30)||'"',NULL)) ||
 61           max(decode(position, 8,', '||'"'||
 62                  substr(column_name,1,30)||'"',NULL)) ||
 63           max(decode(position, 9,', '||'"'||
 64                  substr(column_name,1,30)||'"',NULL)) ||
 65           max(decode(position,10,', '||'"'||
 66                  substr(column_name,1,30)||'"',NULL)) ||
 67           max(decode(position,11,', '||'"'||
 68                  substr(column_name,1,30)||'"',NULL)) ||
 69           max(decode(position,12,', '||'"'||
 70                  substr(column_name,1,30)||'"',NULL)) ||
 71           max(decode(position,13,', '||'"'||
 72                  substr(column_name,1,30)||'"',NULL)) ||
 73           max(decode(position,14,', '||'"'||
 74                  substr(column_name,1,30)||'"',NULL)) ||
 75           max(decode(position,15,', '||'"'||
 76                  substr(column_name,1,30)||'"',NULL)) ||
 77           max(decode(position,16,', '||'"'||
 78                  substr(column_name,1,30)||'"',NULL))
 79              parent_columns
 80      from user_cons_columns a, user_constraints b
 81     where a.constraint_name = b.constraint_name
 82       and b.constraint_type in ( 'P', 'U' )
 83     group by a.table_name, a.constraint_name ) parent
 84  where child.parent_cons_name = parent.parent_cons_name
 85    and parent.parent_tname = upper('&1')
 86  /
old  85:   and parent.parent_tname = upper('&1')
new  85:   and parent.parent_tname = upper('emp')

FKEY
--------------------------------------------------------------------------------
alter table "EMP"
add constraint "EMP_FK_EMP"
foreign key ( "MGR" )
references "EMP" ( "EMPNO");

3) default values:

scott@TKYTE816> select column_name, data_default
  2  from user_tab_columns
  3  where table_name = 'EMP'
  4  and data_default is not null;

no rows selected

scott@TKYTE816> alter table emp modify sal default 100;

Table altered.

scott@TKYTE816> select column_name, data_default
  2  from user_tab_columns
  3  where table_name = 'EMP'
  4  and data_default is not null;

COLUMN_NAME
------------------------------
DATA_DEFAULT
------------------------------------------------------------
SAL
100

4) constraints:

scott@TKYTE816> select constraint_name, constraint_type, search_condition
  2  from user_constraints
  3  where table_name = 'EMP'
  4  /

CONSTRAINT_NAME                C SEARCH_CONDITION
------------------------------ - ------------------------------
SYS_C003382                    C "EMPNO" IS NOT NULL
EMP_PK                         P
EMP_FK_DEPT                    R
EMP_FK_EMP                     R
SAL_CHK                        C sal < 10000

you could add where constraint_type = 'C' just to get check constraints.

Review &
      Followup

      Rating: 4
Table names
      based upon FK relationship  April 20, 2003

Reviewer:  A reader
Hi Tom,
How can I find the table names from dictionary views based upon FK relationship?
For example, I have a hierarchy of area to region to state etc. Area has FK to
region and region has FK to state and to.... If I now table name region, I want
to get table name area, and if I know table name state, I want to get table
names region and area. The depth of hierarchies is different for different set
of hierarchies (as there are lots of hierarchies - time, customer, salesrep
etc.). However, there are FKs to PKs existing for all hierarchies.
Thanks

          Followup:

you would write queries against user_constraints.  it is all in there --
constraint_type = 'R' is foreign keys.

select * from user_constraints where constraint_type= 'R' and table_name = 'X'

gives you the fkeys for a table, do a join of that

select
  from user_constraints a, user_constraints b
 where a.constraint_type = 'R'
   and a.table_name = 'X'
   and a.r_constraint_name = b.constraint_name

and you have the fkeys from X to whatever table (and the table they point to)

If you are lucky enough to be on 9i where you can connect by a join, you can
even do the hierarchy thing.

ops$tkyte@ORA920> create table t0 ( x int constraint t0_pk primary key );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t1 ( x int constraint t1_pk primary key, y
references t0 );

Table created.

ops$tkyte@ORA920> create table t2 ( x int constraint t2_pk primary key, y
references t1 );

Table created.

ops$tkyte@ORA920> create table t3 ( x int constraint t3_pk primary key, y
references t2 );

Table created.

ops$tkyte@ORA920> create table t4 ( x int constraint t4_pk primary key, y
references t2 );

Table created.

ops$tkyte@ORA920> create table t5 ( x int constraint t5_pk primary key, y
references t4 );

Table created.

ops$tkyte@ORA920> create table t6 ( x int constraint t6_pk primary key, y
references t5 );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> column table_name format a30
ops$tkyte@ORA920> select rpad( '*', 2*level, '*' ) || table_name table_name
  2    from (select a.table_name,
  3                 a.constraint_name,
  4                             a.r_constraint_name,
  5                             a.constraint_type,
  6                 b.constraint_name primary_key
  7                    from user_constraints a, user_constraints b
  8                   where a.constraint_type in ('P', 'R' )
  9                     and b.constraint_type = 'P'
 10                     and a.table_name = b.table_name )
 11   start with ( table_name = 'T0' and constraint_type = 'P' )
 12  connect by prior primary_key = r_constraint_name
 13  /

TABLE_NAME
------------------------------
**T0
****T1
******T2
********T3
********T4
**********T5
************T6

7 rows selected.

      Rating: 5
Thanks a
      lot!  April 20, 2003
Reviewer:
      A reader
Tom,
I was quite lucky to get reply with in 10 minutes of posting a question, you are
really like a "superman".
Questions:
1. I have seen that normally you use in-line views instead of self referencing
tables. But in this reply (first query) you did not use in-line views. Is there
any specific reason???
2. We are on 8.1.7.3. Is there any alternative approach to second query, as this
is what exactly I am looking for.
Thanks

          Followup:

1) don't understand what you mean by "self referencing tables" in this context.

I used inline views all over the place on this page -- when I needed to.  which
query exactly did you think would benefit from inline views over what I did?

2) You'll have to two step it.

create global temporary table TEMP
as
select a.table_name,
                 a.constraint_name,
                             a.r_constraint_name,
                             a.constraint_type,
                 b.constraint_name primary_key
                    from user_constraints a, user_constraints b
where 1=0;

do that once and then:

insert into temp
select a.table_name,
                 a.constraint_name,
                             a.r_constraint_name,
                             a.constraint_type,
                 b.constraint_name primary_key
                    from user_constraints a, user_constraints b
                   where a.constraint_type in ('P', 'R' )
                     and b.constraint_type = 'P'
                     and a.table_name = b.table_name ;

 select rpad( '*', 2*level, '*' ) || table_name table_name
    from temp
   start with ( table_name = 'T0' and constraint_type = 'P' )
  connect by prior primary_key = r_constraint_name
;

      Rating: 5
Why I am
      getting this error..  April 20, 2003
Reviewer:
      A reader
Hi Tom,
Could you please help me in resolving this error?
SQL>  create or replace procedure test1_proc
  2   as
  3   begin
  4   EXECUTE IMMEDIATE 'create global temporary table temp_constraints
  5      as select a.table_name,
  6              a.constraint_name pkey_constraint,
  7                  b.constraint_name fkey_constraint,
  8                  b.r_constraint_name
  9         from user_constraints a, user_constraints b
 10        where 1=0';
 11  end;
 12  /

Procedure created.

SQL> exec test1_proc
BEGIN test1_proc; END;

*
ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "user.TEST1_PROC", line 4
ORA-06512: at line 1

The user created the procedure is same as user executing the procedure.
Kind Regards,

          Followup:

well -- #1 question from me is "why would you do that"

a global temporary table is like "EMP", you create it ONCE per database, no
more, no less.  It is really something that doesn't belong in a stored
procedure.  My fear is you are in the current mindset that
"sqlserver/informix/whatever makes me create a temp table in the procedure, use
it and then they drop it -- that's what I need to do in Oracle".  It doesn't
work that way.

You create the gtt ONCE -- not once per use (it would be deadly to create it
over and over and over)....

That aside, read

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html
for why you are getting the 1031.  but - after reading that -- change your
approach anyway and don't do this.

      Rating: 4
Procedure
      works  April 21, 2003
Reviewer:
      A reader
Hi,
Sorry to bother you, the procedure works by giving explicit privileges to the
user.
Regards

          Followup:

but DON'T USE IT!

      Rating: 5
Thanks for
      advice.  April 21, 2003
Reviewer:
      A reader
Tom,
Actually, the procedure will run once a week for loading data into DW. That was
the reason I was thinking of building temporary table through the same procedure
which will load data into DW.
Regards,

          Followup:

just build it once, leave it there forever.

      Rating: 4
Keys referenced
      by other users  September 22, 2003
Reviewer:
      Sri  from India
Dear Tom,

  The script that you have given for finding foreign keys works well within one
schema.  What if I have given a 'Grant Select, References on Emp to AnotherUser'
and the Emp table gets referenced by a table created in AnotherUser's schema.  I
want to get this information also from the script.  How can I get this (if
possible, without using dba_cons_columns since my user doesn't have dba
privileges).  I am using Oracle 8.1.5

Thank you.

          Followup:

if you don't have access, that will be a problem.

you can try the ALL_ views.  they will show you what you are allowed to see.

September 22, 2003 Ed -- Thanks for the question regarding
      "Comparing 9iAS/mod_plsql to other App Servers/web tools", version 8.1.7

      You Asked
Tom,

I enjoyed the recent thread "Oracle compared to other databases" (
http://asktom.oracle.com/pls/ask/f?p=4950:8:1625470
.

I have often had to defend Oracle, and this is a great argument.  However,
increasingly I find myself in a similar argument trying to defend the use of
Oracle's web development tools against other stuff, such as ASP, PHP etc.

So, assuming we're talking about an environment where we're just using Oracle
databases, could you outline the arguments for using Oracle tools rather than
anything else?

Really there are 2 agruments here ...
1) Looking just at mod_plsql and the PL/SQL toolkit
2) Looking at 9iAS and all it's myriad features

Thanks

Ed

      and we said...
I hate "defending" things but.. anyway.

The pro's and con's of development methodologies are the things of religious
wars.

Java is best.  No wait, PHP is really superior.  Well, I find Perl to be the end
all be all of web programming.  No wait, CFM is the way to go.  But I've heard
that ASP's are technically superior to everything.  PLSQL is the only way to do
it.

and the list goes on and on and on and on and on and on and on (and so on).
Gets to be quite borrrrinnnnggg after a while.

Here is my philosophy.  You need more then one tool in your tool chest.

Sometimes Java/J2EE is right.
Sometimes it is like using an atom bomb to crack a walnut.

Sometimes PLSQL via mod_plsql is right.
Sometimes it isn't (cause you have NO plsql programmers for example or there is
an excellent chance you want to run this on something else and they do not have
PLSQL)

If all you have is a hammer.... everything is a nail...

Development has always been like this.

o Modular coding will save the day
o no wait, it is AI
o no wait, it is objects
o no wait, it is 4gls (anyone besides me remember IBM in the late 80's with
their CSP language?  it was going to be the be all, end all.  Man was it nasty)
o no wait, it is CASE
o no wait, it is Java
o no wait, it is web services
o no wait, it is.......

Your COMPANY (not individuals, not individual organizations) should have a short
list of accepted and acceptable technologies for projects (no technology
du-jour).  You should have guidelines for when you would consider implementation
A over implementation B.

If you don't have any Java skills but have lots of PLSQL -- guess which one
looks more reasonable?

If you don't have any PLSQL skills but you have legions of PHP developers --
guess which one might have an edge?

Picking a suite of development tools/methodologies is something a company should
and standardize on.

Sorry -- thats the most "non answer" answer I could give.  I have a feeling
there will be some amount of followups on this one though for some reason ;)

Review &
      Followup

      Rating: 4
I'm glad you
      restisted bashing the competition this time  November 21,
      2002
Reviewer:  Andrew  from ca, usa
Tom
Glad to see that can be objective about non-Oracle products and technologies.
Reading one of the Oracle .vs. other databases threads, I got the feeling that
many Oracle "fans" run other products down without knowing a thing about them.
Oracle has had their share of rotten products and white elephants just like
anyone else. The pre-decessors to 9iAS come to mind... Who would have thought
that Oracle would have chosen Apache to base 9iAS on!

      Rating: 4
answering my
      own question  November 22, 2002
Reviewer:
      Ed  from Cheshire, UK
Tom,

Well I suppose I phrased my question to appeal to a broader audience rather than
just my particular situation.

At my organisation, they had developed web applications using OAS which they
migrated onto 9iAS.  Nothing fancy, no Java, no applets, didn't use Portal - All
applications self-contained (no SSO for example), just rendered the HTML using
the PL/SQL web toolkit with embedded HTML & Javascript in the code.

Now before I joined, they reviewed their web development approach, and decided
to drop Oracle for future web developments and use PHP instead, despite having
considerable experience in PL/SQL (icluding web development) and none in PHP.

The arguments (in addition to cost saving) for changing approach ran along the
lines of:

1. Development using PL/SQL can be slow and code is difficult to maintain (e.g.
HTML, JavaScript and PL/SQL are mixed in the same file)
2. No debugging features in 9iAS
3. PL/SQL was not designed for web development
4. PL/SQL requires recompilation, even for trivial changes
5. No separation of business logic and presentation
6. Each web request requires a call to the Oracle RDBMS

Now I would say 1 applies to anything, 2 is plain wrong, 3 and 4 are laughable,
5 is due to ignorance and 6 is irrelevant.

On the other side I could argue that developing in PHP (or ASP or any other such
language) means that you are splitting the logic / code between end (Oracle) and
middle (PHP) tiers.  This obviously can increase the complexity of any web
application, and the boundary of what logic should be handled by Oracle and what
by PHP may be hard to define.  Furthermore, any functional change is likely to
require changes to both PHP and Oracle (which is likely to mean more than one
developer).  Developing in PL/SQL means that all the logic can be held on the
database, and can be coded by an individual.

I suppose to a certain extent, I've answered my own question, but I think I was
really looking for some reassurance that I wasn't conning myself just because of
my familiarity with the Oracle tools.

I hope I'm fairly objective, but I find it difficult to defend the approach
taken.

Thanks (yet again) for your input.

Ed

          Followup:

1) and php isn't?

2) they must have missed that chapter -- there is, has been for quite a while,
debugging of PLSQL

3) so?  neither was java, perl, C, VB, etc etc etc etc etc.  I didn't know we
needed a language developed for each problem?  plsql was developed to deal with
data -- are you building applications that deal with data?

4) umm, php doesn't?  hopefully they knew to use packages, meaning ONE package
body gets changed and nothing else does...

5) and php does?

6) point being?  don't 99.9999% of the pages need data from the database?

Hey -- i just read your paragraph AFTER typing in that list.  Seems we see eye
to eye on that one.

      Rating: 5
J2EE vs Marvel
      approach  March 08, 2003
Reviewer:  Ray
       from Ottawa,On,CA
Tom It seems you don't like this subject much but ....
I have a problem
Our shop has been exclusively PLSQL- HTML
We have been developing an approach very similar to Marvel. I just got wind of P
Marvel this past week. .. But at any rate. Its a meta data driven approach to
generating PLSQL HTML apps.
Recently the company has contracted a new resource. The background of this new
resource is Java J2EE. The next thing we know ... The fear of the "L" is being
but into management that our ModPLSQL based architecture is not an enterprise
solution ... scalability issue.

I don't think there is an issue. I agree with using a database centric approach.
 Unfortunately common sense and business sense dont seem to be the driving
factors.

IT and love of a specific J2EE AS vendor seem to be more of the driving force.
Fun stuff to deal with in the middle of a development project.

Can you give me a hand in comparing the scale up / scale out
advantages/disadvantages of these 2 architectures. I need some ammunition, to
defend our approach.

Using our approach we ask the DB server to do just about everything. Our
sw/hardware architecture for production will be a dual processor 9i as http
server (4gb ram) for mod plsql and a quad (1.6 mhz) 8gb ram  9.2 db server. for
the internet and the same for the intranet. (win2kas)

we expect 3000 proposals a year. each proposal is 40 web forms. I would not
suspect > 10 concurrent db connections.

The application is to submit proposals on the internet and to evaluate proposals
on the intranet.

The  proposal to evaluate is entered over many days transfered from internet db
to the intranet db upon submission.

The intranet has significantly more funcitonality, nothing that requires
combining data on a form from outside oracle, more data and potentially more
concurrent users... say 50 at the very most.

Each form will average 20k of data
Assuming the the data access is tuned....

Can you recommend something?

          Followup:

...
Unfortunately common sense and business sense dont seem to be the driving
factors. ....

ug.....

It would seem you've already got your case together.  "we need one dual cpu
machine".  Now, ask the other guys how many machineS with a capital S they will
be wanting and how many CPU's....

I don't know what to tell you here -- Maybe this helps.  asktom, all plsql,
250,000 to 500,000 page views a week (just asktom).  lots of searching, lots of
modifications.  Oh yeah, this server also services other websites like:

# DevTrends (Cameron O'Rourke's Online Forum)
# ProMed(Program for Monitoring Emerging Diseases)
# ISID (International Society for Infectious Diseases)
# Oracle Academic Initiative
# Oracle Workforce Development Program
# NASA

(goto osi.oracle.com for the urls).  How many databases?  1.  How many app
servers running mod_plsql?  1.

What is the equipment?  A single 6 year old (yes, 6 years old) sun e450....

is it running out of steam?  nope, doesn't even break a sweat...

is it in need of java to scale are?  nope, that would make us actually have to
buy more hardware.

We wrote a web based calendar internally -- did database authentication,
serviced a couple thousand concurrent users -- palm sync -- the whole nine
yeards.  Over 100,000 page views a day.  The hardware -- same as osi.oracle.com,
it was a cheap sun e450 when we rolled it out.

In order to "integrate" it with the other systems -- they had to change the
login procedure -- no more database authentication, gotta go through a java
login routine.  Ok -- you know what?  we have more hardware running java login
servers then we have running the application ;)

But as your question started out:

...
Unfortunately common sense and business sense dont seem to be the driving
factors. ....

totally frustrating and I don't know what to do about it.

      Rating: 4
re:
      frustrating  March 09, 2003
Reviewer:  Ray
       from Ottaw,ON,CA
Thanks for the feedback. I appreciate your thoughts

I suppose Enterprise to some is departmental to others as well. I like the
numbers you gave me. This is not a business into making money see my email
address. .. So the chance of their business balooning to have to handle 100,000
users is nil. From what I have investigated we should never have a scale issue.

You said your db server hosts several web sites. You wouldnt have the total
stats on concurrent users, pages/minute .... would you?

thanks again Tom. You da man

          Followup:

about 300-500,000 page views a week combined.

70 concurrent database sessions maximum.

      Rating: 5
9iAS Release 2
      for over 10,000 concurrent users  May 04, 2003

Reviewer:  Sean  from Atlanta
Could you tell where I can find the sizing and configuration of 9iAS R2? We are
looking into usage of 9IAS R2 for our web application. We have been told there
will be over 1,000,000 accounts and the max of concurrent user > 10,000. We are
going to prototype with 2-cluster, which have 2 webcaches, 2 oc4js and the
infrastructure on a different host. By briefly how can 9ias handle the huge
concurrent users? As 10,000 on the online, how many sessions will actually raid
in the DB? Thanks tom. We really have hard time to find the doc to assistance
the capacity planning in the 3-tier configuration.

          Followup:

10,000 users accessing a simple "select * from dual" application with little
work versus 10,000 users accessing a fast fourier transformation engine (a tad
more cpu /compute intensive) would require slight different architectures.

10,000 users using a pure stateless model vs 10,000 users using a stated model
in the middle tier would require slightly different architectures.

and so on....

The only "sizing guidelines" that work (in my experience) are those for an
already developed application.  That is -- if you buy Oracle HR (or SAP or
PeopleSoft -- whatever), you can size that with some degree of comformt.  If you
buy core technology - to build arbitrary application with arbitrary
inefficiencies (mixed in with some really good code of course) -- sizing is
something a benchmark will answer for you.

Did you find in the documentation how to size a single (host based) or client
server setup for arbitrary application?  It is not really possible.  There are
thousands of implementations possible using the same software -- turn on feature
"X" and the performance characteristics will be utterly different then if you
turn on feature "Y".

I'll bet you cannot even really define "10,000 concurrent users"
Or what their transactions entail exactly...

The prototype will tell you what you need to know (isn't a prototypes goal after
all at least partially to do this?)

And rememeber -- you and you along have the opportunity to make this scale or
not.  The software can -- but it doesn't have to.  Do it "wrong" -- make bad
decisisions and stick to them early on -- and it won't matter how much hardware
you have.  Education, knowledge of what is possible and benchmarking MANY
alternatives are the name of the game.

      Rating:
      5
  May 04, 2003
Reviewer:  Sean
       from Atlanta
We will press the customer for the specifications. And speed up to mastering
9iAS architechture.

      Rating:
      4
  September 22, 2003
Reviewer:  Mark
       from KC
Tom,  (or anyone else)

What do you use to develop applications when using the mod_plsql?

Thanks

          Followup:

project marvel -- soon to be Oracle 10g's HTML DB feature.

http://otn.oracle.com/oramag/oracle/03-sep/o53asktom.html

September 22, 2003 Alex -- Thanks for the question regarding "Header
      space allocation for LMT tablespacesssue", version 9.0.1

      You Asked
Hi Tom,
we are in the process of upgrading our databases from 8i to 9i and one of my
tasks is to implement a better space management strategy that the existing one.
We will be using LMT with uniform extent sizes. I have done some testing and
found out that when I create the tablespaces, Oracle allocates 1 extent (no
matter which size it is) to the header for each datafile. I've created 3
tablespaces with 128K, 4M and 128M extent sizes with 5 datafiles (512M) for each
tablespace. When I looked at the space allocation in OEM and SQL*Plus, I saw
that for 128K tablespace Oracle used 640K for the header (128K*5), for 4M
tablespace Oracle used 20M for the header, and for 128M tablespace Oracle used
640M for the header. Then I did another test - I created a small tablespace with
128K extent size and 1 datafile (4M). In 4M you can fit 32 128K extents, so I
created a table in that tablespace and filled it up until I got an error "Unable
to extend". Then I looked at the space allocation and saw that Oracle used 31
extents for the segment (my table). Is this how it suppose to be? I've read
somewhere that Oracle only allocates 1 data block for the header (in my case it
should be 8K).

Thank you
Alex.

      and we said...
No, it allocates 64k for itself.

You'll find if you use 1meg uniform extents, you'll want to have a file that is

100m + 64k -- in order to have 100 free extents.

Just resize your files 64k bigger and they will all get that last extent!

Review &
      Followup

      Rating:
      5
  August 04, 2003
Reviewer:  Alex
       from Chicago, IL
Thanks, Tom.
I've done some more testing and found out that when I create tablespaces with
datafile sizes divisible by tablespace's extent size + 64K then Oracle uses 64K
for the overhead, but when the datafile sizes are not divisible by tablespace's
extent size + 64K, then Oracle uses 1st extent for the overhead. In addition,
the theory of creating datafiles with sizes that are multiples of extent size +
DB block size did not work (at least in my case).
Thanks for your quick and correct response.

          Followup:

no, it does not.  it uses 64k for the overhead and then reports back how many
extents there are available.  since that last extent is now 64k short -- it does
not show it.

just adding 64k to the END of the file will give you that last extent back!

      Rating:
      5
  August 11, 2003
Reviewer:  Alex
       from Chicago, IL
Thank you.
I have 1 more question:

we have Online Redo Logs of 150M each. Since we will be  using different sizes
for datafiles, would it be a good idea to size Redo Logs with the same method,
i.e. if the datafile size is 512M, then Redo Logs should be 512, 256 or 128M?
The reason why I ask is because we have a disk farm, so even though we have
drive letters assigned to the tablespace or Redo Logs, they may be on the same
disk and I would want to avoid fragmentation.
Thanks,
Alex.

          Followup:

I cannot imagine any correlation between the size of datafiles and redo logs.

      Rating: 5
Header space
      allocation for LMT   September 16, 2003
Reviewer:
      A reader  from orlando,FL
Tom,
Please help correct my understanding:

If the LMT stores its space mgmt info in a bitmap(each bit has 1 block info?)
in its 64K overhead area,  we get 524288 bits ( 64 * 1024 * 8 ). So a LMT
tablespace *datafile* can be only as big as
524288 * TBS block_size
giving us  4 GB per file in a 8K block size env??

Thanks as always...

          Followup:

forgetting that an extent is what the bitmap manages and an extent consists of
many blocks....

no, the files are not limited to 4gig -- in 9iR2 and before, somewhere between
0g and 128g is the limit, depending on OS and other support.

      Rating: 5
Thanks for
      clearing up the blindspot  September 16, 2003

Reviewer:  A reader  from orlando,FL

      Rating:
      5
  September 22, 2003
Reviewer:
      A reader  from orlando,FL
Tom,

Each extent has a one bit representation in the 64K LMT header.Does oracle check
this bit to find out whether there is space in the extent or not?

Thanks as always..

          Followup:

nope, we use either freelists to manage freespace or ASSM (automatic segment
space mgmt) which uses it's own bitmap in the segment, not the same bitmap as
the tablespace uses.

      Rating: 5
It is said
      learning is a continuous process.True. From you.  September
      22, 2003
Reviewer:  A reader  from orlando,FL

Tom,
can u pls throw some light on ASSM's bitmap for space mgmt and *its* overhead?
How much it would eat into the objects allocation?
pls ignore this question if it is covered in your new book as my copy is in the
mail.

Thanks as always...

September 22, 2003 Munzer -- Thanks for the question regarding
      "Collection Variable Types", version 8.1.7

      You Asked
Tom:

What is really the difference and when you would use each of oracle datatypes:

1.  Index-by-tables
2.  Nested Tables
3.  Varrays

My understanding is that index by tables are for data of same type that is
stored in memory.

For nested tables you can store the variables values in oracle table.

Varrays are same as nested except they are confined to a certain number.

AM I correct?

Would you use a nested table for two tables like a PO table and items table
iinstead of referring to two tables.

Thank you,

      and we said...
The major difference between:

(index by tables) and (nested tables/varrays)

is that index by tables are only available in PLSQL, nested tables/varrays are
avaialable in both PLSQL *and* SQL.

Index by tables are basically "sparse" arrays that need no allocation.  For
example:

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      type array is table of number index by binary_integer;
  3      data array;
  4  begin
  5      data(1000) := 1;
  6  end;
  7  /

PL/SQL procedure successfully completed.

Here plsql gladly accepts an entry in the 1,000'th slot -- without doing
anything else.  There is nothing in slots 1, 2, ... 999 (or 1001 or -1, -2,....
)

That array has allocated space for 1 element in the 1,000th position.  Nested
tables/varrays do not behave that way:

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      type array is table of number ;
  3      data array := array();
  4  begin
  5      data(1000) := 1;
  6  end;
  7  /
declare
*
ERROR at line 1:
ORA-06533: Subscript beyond count
ORA-06512: at line 5

Here it is saying "you haven't allocated me any space up there...".  So, we use
the .extend attribute:

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      type array is table of number ;
  3      data array := array();
  4  begin
  5          data.extend(1000);
  6      data(1000) := 1;
  7  end;
  8  /

PL/SQL procedure successfully completed.

and we can do so.

Note that we do have to allocate 1,000 entries:

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      type array is table of number ;
  3      data array := array();
  4  begin
  5          data.extend(1);
  6      data(1000) := 1;
  7  end;
  8  /
declare
*
ERROR at line 1:
ORA-06533: Subscript beyond count
ORA-06512: at line 6

the nested table/varray type isn't "sparse" like the index by table.

Day to day, in plsql code, i generally use index by tables exclusively.  They
are a little faster, a little more flexible.  It is when I need to use the table
type in SQL that I use a nested table (see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:110612348061
for an example of what I mean by that)....

There are other differences between varrays and nested tables when you use them
for storage in a database table.  For example -- varrays maintain their "order".
 Nested tables do not.  Varrays are stored in lobs -- nested tables in separate
tables.  There are certain DML operations available for nested tables that are
not avaialable for varrays.  See

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76976/toc.htm
for more details.

Review &
      Followup

      Rating:
      5
  March 28, 2002
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating:
      5
  March 28, 2002
Reviewer:  Arun
      Panchal  from Rahway, NJ

      Rating: 3
Need
      Help!!!  August 03, 2003
Reviewer:  Suwarna
       from Manchester, UK
Hi Tom,

 Sorry for not following your procedures/policies. Was really needing your help
and hence added my query to Collection types question.

I have three tables. Structure of each is:
Cash: This table holds all payments made by customers by date by
account_reference. Columns are:
cash_id number(22) Primary Key,
receipt_date date,
account_reference_number varchar2(13),
cash_amount number(22,2)

Charge: This table holds all the charges raised against the products(Water,
Sewerage, etc) provided by Water system to the customers. Columns are:
charge_id number(22) Primary Key,
activity_date date,
account_reference_number varchar2(13),
billable_amount number(22,2)
charge_type number(22)

Cash_Application: This table holds combination of cash_id and charge_id for an
account_reference with the cash_amount received for each charge. Columns are:
cash_application_id number(22) Primary Key,
fk_cash_id number(22),
fk_charge_id number(22),
account_reference_number varchar2(13),
applied_amount number(22,2)

Now, Cash_Application table needs to be loaded using Cash and Charge tables
recursively. There are some rules to do cash allocation for each charge.

1. The earliest cash will be allocated to the earliest charge

2. If there are more than one charge records on the same date, then cash will be
allocated in proportionally to each charge.Thus if cash_amount is x and there
are three charges a, b and c, cash applied to each charge will be x*a/(a+b+c),
x*b/(a+b+c), x*c/(a+b+c), respectively.
There are two possibilities ....
i.billable_amount for each charge will become zero after allocating the cash
ii. cash will be insufficient for sum(billable_amount) for an account_reference
on a particular date.
In first case, if some more charges exist for the account, cash will be applied
to those charges
In second case, new cash record will be brought in from cash table and it will
be allocated proportionally to remaining charges

3. Any cash that remains after fully allocating all the charges on the account
for all days, will remain un-allocated

Please guide on how this can be done using PL/SQL.

Thanks a ton!!!

Regards,
Suwarna.

          Followup:

"hey, please code my system for me -- here are the requirements, go"

if you have a focused question, about how the technologies work, or about how
plsql can do something, or how to write a tricky query or something -- I'm here.

"code my system".... no, not really.

sounds like you have solid specs there.   turning specs like that into code is
something programmers do.

      Rating: 2
I will do the
      coding as I am here for that ....  August 03, 2003

Reviewer:  A reader
Hi,

 Thanks for going through the question. I really want to do the coding ... but
not really knowing from where to start. Some pointers on this will be really
appreciated. Have tried doing this using varray and using normal cursors. But
getting messed up. Thought you can help me out (not very good of me expecting
such a thing, but still thought!!!)

Regards,
Suwarna

          Followup:

just sounds like you want to bulk collect the data into arrays of records from
both tables and "bump and grind" the data procedurally according to your rules.

      Rating: 3
One example
      please!!!  August 05, 2003
Reviewer:  Reader

Hi Tom,

 Will you please give an example for 'bulk collect the data into arrays of
records from a table'?

          Followup:

ops$tkyte@ORA920> declare
  2          type empArray is table of emp%rowtype index by binary_integer;
  3
  4          l_data  empArray;
  5          n       number := 10;
  6
  7          cursor c is select * from emp;
  8  begin
  9          open c;
 10          fetch c bulk collect into l_data limit N;
 11          close c;
 12  end;
 13  /

PL/SQL procedure successfully completed.

      Rating: 4
I am gettin ORA
      error  August 07, 2003
Reviewer:  Reader
       from UK
Hi Tom,

 I tried the above example by creating a record type and then table of records.
Following is the code and error messages ....

SQL> ed
Wrote file afiedt.buf

  1  declare
  2      type cash_Rec is record (account_reference_number char(13), cash_id
number(22), cash_amount decimal(22,2));
  3      type cash_Tab is table of cash_Rec index by binary_integer;
  4      l_cashTab cash_Tab;
  5      cursor cashCursor is
  6              select acc_ref_num, pk_cash_id, amount
  7              from cash
  8              order by acc_ref_num, rcpt_dt;
  9      l_counter integer := 10;
 10   begin
 11      open cashCursor;
 12      fetch cashCursor bulk collect into l_cashTab limit  l_counter;
 13   close cashCursor;
 14*  end;
SQL> /
    fetch cashCursor bulk collect into l_cashTab, l_cashTab, l_cashTab  limit
l_counter;
                                       *
ERROR at line 12:
ORA-06550: line 12, column 40:
PLS-00597: expression 'L_CASHTAB' in the INTO list is of wrong type
ORA-06550: line 12, column 5:
PL/SQL: SQL Statement ignored

Where am I going wrong????

          Followup:

ops$tkyte@ORA920> declare
  2          cursor cashCursor is
  3          select empno, ename, hiredate from emp;
  4
  5          type cash_tab is table of cashCursor%rowtype index by
binary_integer;
  6
  7          l_cashTab cash_tab;
  8          l_counter number := 10;
  9  begin
 10          open cashCursor;
 11          fetch cashCursor bulk collect into l_cashTab limit l_counter;
 12          close cashCursor;
 13  end;
 14  /

PL/SQL procedure successfully completed.

why do you have three tables in your into clause? you only need one.

      Rating: 4
Corrections
      ....  August 08, 2003
Reviewer:
      A reader  from Manchester, UK
Some corrections to above code ....:))

SQL> ed
Wrote file afiedt.buf

  1  declare
  2      type cash_Rec is record (account_reference_number char(13), cash_id
number(22), cash_amount decimal(22,2));
  3      type cash_Tab is table of cash_Rec index by binary_integer;
  4      l_cashTab cash_Tab;
  5      cursor cashCursor is
  6              select acc_ref_num, pk_cash_id, amount
  7              from cash
  8              order by acc_ref_num, rcpt_dt;
  9      l_counter integer := 10;
 10   begin
 11      open cashCursor;
 12      fetch cashCursor bulk collect into l_cashTab limit  l_counter;
 13   close cashCursor;
 14*  end;
SQL> /
    fetch cashCursor bulk collect into l_cashTab limit
l_counter;
                                       *
ERROR at line 12:
ORA-06550: line 12, column 40:
PLS-00597: expression 'L_CASHTAB' in the INTO list is of wrong type
ORA-06550: line 12, column 5:
PL/SQL: SQL Statement ignored

Where am I going wrong????

          Followup:

guess you are using 8i which did not support bulk collects into array of
records, only into records of arrays.

      Rating: 2
Using 9.2
      Oracle version  August 11, 2003
Reviewer:
      Su  from Manchester, UK
Hi Tom,

 Oracle version is 9.2. Are some added privileges required to create arrays in
the schema?

Regards.

          Followup:

ops$tkyte@ORA920> create user a identified by a;

User created.

ops$tkyte@ORA920> grant create session, create type to a;

Grant succeeded.

ops$tkyte@ORA920>
ops$tkyte@ORA920> @connect a/a

a@ORA920>
a@ORA920> create type myArray as table of varchar2(250)
  2  /

Type created.

      Rating: 4
Example for
      sequentially accessing records in an Array  August 11, 2003

Reviewer:  Reader
Hi Tom,

 Will you please give an example of how the records loaded by above example can
be fetched or read sequentially and some manipulations done on the data.

Thanks and regards,
A reader

          Followup:

A reader -- you do not know how to loop over an array?

  for i in 1 .. array_variable.count loop
     ......

      Rating: 4
Upper limit on
      collection types  September 02, 2003
Reviewer:
      BK  from NJ USA
Very good explanation, I still have one more doubt in my mind. What's the upper
limit on different collection types? If dependent on hardware then what would be
upper limit on a server with 1GB of RAM. I'm considering collecting around 50K
records into a table type and passing this table to a procedure for dumping into
a file. Would this approach be faster than writing to the file directly. Your
insight is greatly appreciated.

Thanks,

          Followup:

that would be about 50 times more then I would personally feel comfortable with.

do 1000, write 1000, do 1000 more, write 1000 more.

no reason you have to "get them all", "write them all"

modularize your code and "get a few", "write a few", "get a few more", "write a
few more"

      Rating: 3
need more
      explanation on upper limit of collection type  September 18,
      2003
Reviewer:  BK  from NJ
Tom,
Could you elaborate on why you would not be comfortable with this approach. We
have taken this approach keeping 4-5 thousand records per day in mind. Now
program has to handle Initial load of 350K records, which was not anticipated.
We have modularize our code and all our interfaces creates table type and one
program takes care of writing to the file. Now if we want to follow 'Get few'
and 'write few' approach, we are missing the last records. Here is the eg.

declare
TYPE varchar_table_type IS TABLE OF VARCHAR2(32767)
  INDEX BY BINARY_INTEGER;
v_pac_table varchar_table_type;
filedir varchar2(100) := '/u226/appl/aicdappl/11.5.0/data/outbound';
v_filename  varchar2(50) := 'append_file.txt';
x_status     varchar2(240);
x_msg         varchar2(240);
cursor c1
    is
select rownum||'-'||owner||'.'||object_name rec
  from all_objects
 where owner = 'AR'
   and object_type = 'TABLE'
   and rownum < 53;
begin
for x in c1 loop
 v_pac_table(v_pac_table.count+1) := x.rec;
 dbms_output.put_line('Count : '||v_pac_table.count);
 IF (MOD(v_pac_table.count,10) = 0) Then
    interface_util.create_file(filedir, v_filename, v_pac_table, x_status,
x_msg);
    v_pac_table.delete;
 END IF;

end loop;
dbms_output.put_line('X_Status: '||x_status);
dbms_output.put_line('X_msg: '||x_msg);
end;

create file procedure opens file in an 'A' mode and writes data to the file. In
this case how do we write the last two records ? Also I would greatly appreciate
your input on optimum number of records each collection type can handle. Thanks,

          Followup:

because of "unanticipated things"

just put outside of the loop a call to interface_util.create_file if
v_pac_table.count > 0 ??

      Rating: 4
Please provide
      more information  September 22, 2003
Reviewer:
      BK  from NJ, USA
Hi Tom, I wish I could reply before you could read my previous question. Tried
for two days but couldn't access the site. It was very 'stupid' to ask, because
it was so simple, I wasn't thinking right and I apologize for wasting your time.
Your replys to the questions asked have greatly influenced my design and coding
approach. I vist this site atleast once a day. Great job Tom. As you have always
done could you also please explain what 'unanticipated' things can happen and
why. How do I find out optimum number of records each collection type can
handle, I think Oracle community will better understand when to use what
collection type. Thanks,

          Followup:

you used the term unanticipated!

Could you elaborate on why you would not be comfortable with this approach. ....
which was not anticipated. .....

that is why i'm not comfortable with the "fill up an array without any bounds"
approach -- the un-anticipated things in life will kill you.

the "optimum number" is purely a function of RAM here.  I am not comfortable
with more then a couple hundred, maybe few thousands, of entries "in memory".
not very scalable after a while.

September 23, 2003 Ganesh -- Thanks for the question regarding
      "Checkpoint", version 8.1.7

      You Asked
Hi Tom,

I think i am Bit confused on this after reading some Papers on the 2 pass redo
reading at the time of recovery in 9i.

I know that during recovery Oracle Reads Redo Entries only from the point of
checkpoint and not before that.

Now Let us say i updated a 10 Mill Row table and during the time of update there
were a 2 checckpoints and the Dirty Buffers also got written to the disk.

At this Point my DB Crashed.

At the time of Crash recovery if oracle is going to read from the last
checkpoint then what happens to all the dirty buffers that have been written
before the previous checkpoint.

How are they Rolledback ?

In the Case of 9i, i read that we write more information into the redo stream to
identify if a Particular block has been written into the disk or not so that we
dont read it into memory. Where is this info written. All this while my
understanding was that redo is written when the datablock is changed. Am i right
in assuming this ???

Thanks for your time.

Regards,
Ganesh R

      and we said...
we recover rollback just like "data"

therefore, we roll forward from the logs -- this gets the db back exactly to
where it was before the crash.

then, using rollback (which was recovered) we undo any outstanding -- not yet
committed -- work.

Review &
      Followup

      Rating: 4
Two Pass
      Redo  September 22, 2003
Reviewer:  Ganesh
      Raja  from Singapore
Hi Tom,

Thanks for that. yes I understand that oracle will be doing redo data apply and
then will recover the rollback data and then undo the uncommited transactions.

From what point will the redo be read. Will it be from the last checkpoint. If
so comming back to my previous question what will happen to the data blocks that
have been written before the last checkpoint and have not been commited.

Thanks for your time once again.

Regards,
Ganesh R

          Followup:

we make sure the undo for them is flushed first, so their undo exists - the undo
we need to "undo these changes" either in the rbs OR in the redo that will be
turned into undo upon its application.

September 23, 2003 Mo -- Thanks for the question regarding
      "temporary tables ", version 8.1.7

      You Asked
Tom:

1.  When you are converting informix  procedures that have SQL statements using
temp tables, you can convert those into oracle using temporary tables or ref
cursors?  Is there really a need to create and drop those temporary tables since
you can get the result set using a ref cursor?

2.  Would temporary tables create a problem when you are using a web application
when several users run the same procedure with different parameters that will
create different result sets but same temporary table name? since we are using
one DAD login to database?

3.  In ref cursor when you define a query can u use IN parameters in the query
like

open cursor for 'select * from table where state=:p_state'

p_state is an IN parameter to the stored procedure

      and we said...
why in the year 2002 would you be converting from one obsolete database to an
obsolete version of another???? 9iR2 is what you SHOULD be converting to.
Anyway.

1) temporary tables are crutches that other databases that couldn't handle more
then 2 or 3 tables at a time needed to use.

We grudgingly added them in 8i.  I've used them in exactly one application -- a
palm sync module I wrote for our web calendar.  The palm conduit stuffs the
change records from the palm into a pre-defined global temporary table and then
calls a stored procedure to process the data.  I've never found a use for them
in reporting personally.  Inline views, select (select), analytic functions --
they all obviate the need for them.

Just do it in a single query.  check out

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3092495541617
for a complex example.

2) temporary tables are always session specific, there are no concurrency, no
"opps I see your data" type of issues across sessions (and you'll have a session
per page so no worries)

3) yes, it would look like:

procedure p( p_state in varchar2, p_cursor in out ref_cursor_type )
is
begin
    open p_cursor for select * from table where state = P_STATE;
end;

would be the way to do that (don't use dynamic sql unless you HAVE to use
dynamic sql.  If you have to, it would have been:

    open p_cursor for 'select * from table where state = :x' using p_state;

Review &
      Followup

      Rating: 5
temp
      tables  August 30, 2002
Reviewer:  mo
       from Vienna, VA
Tom:

if you have the following informix code:

If type = 'A' then

  insert into  #temp1 (query 1=select from two tables);
  insert into #temp2 (query 2);
ELSE
  insert into #temp1 (query 1);
  insert into #temp2 (query 2);
END IF;

select sum(col1- col2) into late_days from temp1;
select count(*) into cnt_temp1 from temp1;
select count(*) into cnt_temp2 from temp2;
select count(*) into cnt_noship from temp2 where ....;
late_ship:=late_days/cnt_temp1;

end;

to convert this into oracle:
1. do you need to use temp tables. my guess is not.
2. would you use implicit cursor or ref cursor for this.
3.  I was thinking of doing for each query that inserts into temp table:

  cnt_temp1:=0
  FOR x in (query 1)
  LOOP
  cnt_temp1:=cnt_temp1+1;
  END LOOP;

would you do this ot can you use cursor%rowcount.

4. Actually do I really need to use a cursor at all?

can i just do:

select count(*) into cnt_temp1 from table1,table2 where (query1);
select count(*) into cnt_temp1 from table1, table2
where (query2)

5.  If I want to outpu the cnt_temp1 and others do I declare them as OUT
parameters in the header.

6.  can i pass those output counts into other procedure or bascially I call this
procedure in the other one and I can use the output parameters there.

THank you

          Followup:

Now -- I don't see the difference between when type = 'A' and when it doesn't?

Lets assume they are different query 1 and query 2's so the code is really:

  if ( type = 'A' ) then
      insert into #temp1 QueryA;
      insert into #temp2 QueryB;
  else
      insert into #temp1 QueryC;
      insert into #temp2 QueryD;
  end if;

I would code in Oracle then:

  if ( p_Type = 'A' )
  then
      select sum(col1-col2),
             count(*),
             decode(count(*),0,to_number(null),sum(col1-col2)/count(*) )
        into last_days, cnt_temp1, late_ship
        from ( QueryA );

      Select count(*), sum( case (when <condition> then 1 else 0 end ) )
        into cnt_temp2, cnt_noship
        from ( QueryB );
  else
     ... same block of code, differen queries in the FROM clause ...
  end if;

In general -- where ever you have #tempN in a query -- replace it with the query
you used to insert into #tempN in the first place.  Also use techniques like I
did above with decode and or CASE to remove the need to query a table 2, 3, 4
times.

If you wanted to have the called have access to the cnt_temp1, etc variables --
yes, you would have formal out parameters:

 procedure p( p_cnt_temp1 out number, p_cnt_temp2 out number, ..... )

Yes, you can do that with parameters (#6).  PLSQL is very much like any other
3gl language you have programmed with and has most all of the same constructs.

      Rating: 5
temporary
      tables  August 31, 2002
Reviewer:  mo
       from Vienna, VA
Tom:

Thanks for your great answer.  I hope you run for president one day as you are
the only one I vote for.

As a followup,

1.  Did you use decode statement to avoid division by a 0 in case the count was
null or for other purpose.

2.  Why do you say " to avoid querying table 2,3 , or 4 times.  How can i query
tabe several time wiht one SQL statement.

3.  Are you showing the use of the CASE statement as an alternative to use
decode. Does this exist in 8i though?

4.  You always use cursors when you need to access values in individual
rows?Correct.
For frouop functions like here you do need any cursor.

5.  Why does informix encourage the use of temp tables.  It seems all their
procedures have that feature.

Thank you,

          Followup:

I'd make a terrible president.  I'm not very political.

1) to avoid divide by zero.

2) I took your 2 INSERTS and 4 queries:

insert into temp1 select queryA
insert into temp2 select queryB

select sum(col1- col2) into late_days from temp1;
select count(*) into cnt_temp1 from temp1;
select count(*) into cnt_temp2 from temp2;
select count(*) into cnt_noship from temp2 where ....;

and turned them into 2 queries -- period.  Instead of scanning temp1 two times
-- for the two selects, (to get sum(col1-col2) and count(*)) -- I did it in a
single query (full scan ONCE, not TWICE).  I used CASE for temp2 so I could do
the two queries in one statement.

3) yes it does.  In order to use it in plsql however you must use dynamic sql

execute immediate 'select case when 1=1 then 1 else 0 end from dual' INTO
host_variable;

4) I only use cursors when I cannot possibly do it in a single statement.
Cursors imply procedural processing which will be slower then a single
statement.

EG: Never code
   for x in (select ... )
   loop
      insert into t values ( x.c1, x.c2, .. );
   end loop;

always code

   insert into t select ....;

5) because it cannot do what we can I suppose.  With inline views, select
(select), analytic functions, etc etc etc -- I've never actually USED a temp
table except to stage data to be processed.

      Rating: 5
temporary
      tables  August 31, 2002
Reviewer:  mo

Tom:

I forgot about this:

1.  You say if I want to have access to the cnt_temp1 I would use an OUT
parameter.

DO i assign the value of my out parameter to the variables as

p_cnt_temp1:=cnt_temp1;

2. or do i just forget about variables and use write directly to parameters. can
i do that? as:

select sum(col) into p_cnt_temp1 from table;

          Followup:

1) just select into it.  Just use p_cnt_temp1, you don't need cnt_temp1;

2) correct.

      Rating: 5
temprary
      tables  August 31, 2002
Reviewer:  mo
       from Vienna, VA
Tom:

1. Are you saying that
SELECT count(*),sum(col1-col2) from (select * from table  where state='CA')

will scan table only once?

2.  Why do not I just do this rather than inline views.

select count(*),sum(col1_col2) from table
where state='CA'

3.  Let us say I want to find a count and a sum for records for two states.  Is
there a way to do it in one table scan, or you have to do two select statements
(two full scans).

4.  Are you using inline views because it is faster rather than joining tables.
My assuption is that when you join oracle does a full table scan of both tables
and then does filter.

With inline views you filter on each table first and then you do the join on
each query result set which makes a lot of difference in speed.

5. you say you only use temp tables when you want to stage data.  What do you
mean by that? to create other sub tables for production.  I thought temp tables
get cleaned after session ends?

AM I correct?

          Followup:

1) yes.

2) sure.  not a problem -- it wasn't the question but yes, not a problem (they
are in fact 100% the same)

3) look at the decode/case examples !!!!

select sum(decode(state,'CA',1,0)) ca_cnt, sum(decode(state,'VA',1,0)) va_cnt
  from t;

4) No, I'm using them because the generic nature of the question dictated that I
must.  I don't know what queryA, queryB, queryC, queryD involved -- you might be
able to merge them directly into the query, you might have to use an inline
view.

YOU however can code it in whatever style works ;)

5) There is one time in my entire career I've used a temporary table.  That was
during the coding of a Palm sync application.  I had the palm conduit on the PC
insert the changed records from the palm into a global temporary table on the
server (staged the data) -- ran a stored procedure to do the sync -- read the
change recs back from the temp table and applied them on the palm.  I do a
commit and wah-lah -- the staged data disappears just like I wanted it to.

      Rating: 5
temporary
      tables  September 03, 2002
Reviewer:  mo
       from reston, USA
TOm:

When I try to write to OUT parameter and test with SQL PLUS I got the following
example:

PROCEDURE TEST ( p_one   IN VARCHAR2,
             p_two     OUT integer)
   IS

BEGIN
 p_two   := 0;
    select '5' into p_two from dual;

END;

SQL> execute test('A',c);
BEGIN test('A',c); END;

               *
ERROR at line 1:
ORA-06550: line 1, column 16:
PLS-00201: identifier 'C' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

Do you know why?

          Followup:

yah, what is C?  You just made it up, it doesn't exist.

SQL> variable c number
SQL> exec test( 'A', :c );
SQL> print c

      Rating: 5
temporary
      tables  September 03, 2002
Reviewer:  mo
       from vienna, VA
Tom:

1. Well C is the name of the variable that I am passing to write the value of
p_two. Do you always have to use ":C" when substituting an OUT parameter in
calling a procedure?

2.  The procedure I have have 5 OUT parameters I am wiring to.  I am trying to
check the answer in SQL*PLUS.  I am running it as above using
execute('1234',A,B,C,D,E);

Am i not supposed to see the output of the procedure which is the values of
those OUT parameters?

3.  Do I have to do what you did using print to see the results? Can I use it
for several parameters?

Thank you,

          Followup:

1) and where did you define this C?  You didn't.

You could:

declare
   c int;
begin
   test( 'A', c );
end;

or you can use a sqlplus bind variable with :c like I did.  Upto you -- either
way, you've gotta define it.

2) I showed you how.

variable a number
variable b number
...
exec test( 'A', :a, :b, :c, .. )
print a
print b

(or set autoprint on and a, b, c, ... will just be printed for you)

3) set autoprint on

      Rating: 5
out
      parameter  September 03, 2002
Reviewer:  mo
       from vienna, VA
Tom:

Great answer.

WHen I did

 declare
d int;
e int;
begin
test('A',d,e);
end;

I had to use dbms_output.put_line(d);
to see the values. Correct?

2.  So everytime you want to check values of OUT parameter you have to define a
variable type for the passed parameter like you did?

Thank you,

          Followup:

1) yup

2) yup

      Rating: 5
informix
      c  September 07, 2002
Reviewer:  ko
       from mclean,pa
Tom:

1.  Can you do multiple sums in one scan of a table for different criteria like

select sum(total) from table where state='CA';
select sum(total) from table where state='CA' and city='San Diego';

2.  If the informix database uses some C proecdures do you recomend converting
those into PL/SQL or just call those from pl/sql and what do you do to execute
them?

Thank you,

          Followup:

1) absolutely:

select sum(total),
       sum(case when city = 'San Diego' then total else 0 end)
  from table
 where state = 'CA'

(see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3027089372477
before trying to use CASE in sql in plsql in Oracle8i -- but don't do anything
special in 9i and up)

or you can

select sum(total), sum( decode( city, 'San Diego', total, 0 ) )
  from table
 where state = 'CA'

in all versions with no worries.

2) You can do either.  We support the concept of external procedures. See the
application developers guide for details on that.

      Rating:
      5
sql  September 08, 2002
Reviewer:
      ko  from ca
Tom:

This is great:

select sum(total), sum( decode( city, 'San Diego', total, 0 ) )
  from table
 where state = 'CA'

However let us say  i want to compute total sum for records in "CA" and total
sum of records in "Boston". Would replaceing "San Diego" with "Boston" work. My
guess is that it would not becayse you are filtering on state="CA"? DO you have
to do multiple selects (scans) in this case?

2.  Are you lcoated in Boston BTW.

Thank you

          Followup:

Simple continuation of the fundementals here.

Think about it -- see what I did for city?  Just continue on:

select sum( decode( state, 'CA', total, 0 ) ) CA_total,
       sum( decode( state, 'MA', decode( city, 'Boston', total, 0 ), 0 ) B_total
  from table
 where state in ( 'CA', 'MA' ) -- this is not necessary but may dramatically
                               -- affect performance for obvious reasons

Using case, it is easier I think:

select sum( case when state = 'CA' then total else 0 end ),
       sum( case when state = 'MA' and city = 'Boston' then total else 0 end )
  from table
 where state in ( 'CA', 'MA' )

Now that you know of the existence of DECODE and CASE -- writing ANY query in
this style should become second nature.

2) No, I'm in Leesburg VA working out of the Reston VA Oracle campus (or my
personal Leesburg branch office ;)

      Rating:
      5
sql  September 08, 2002
Reviewer:
      mo  from vienna, va
Tom:

Yes this is great. I can apply this to any query rather than creating multiple
queries. I guess basically any query can be done using on table scan rather than
multiple selects which saves a lot of time when you have millions of records.
WHen you index a column is there a way to see the data in the index (select from
it)?

2.  What was more shocking is that you are only a few miles away from me.  I bet
you work in ORACLE off Sunset Hills Drive. Please feel free to email me whenever
you are free and I will invite you to lunch or dinner to your favorite
restaurant in the area.  I know you must be very busy man but I owe you a lot.

          Followup:

If you create an index and your query can be answered solely from the index, the
optimizer will choose an INDEX FAST FULL SCAN or INDEX FULL SCAN to answer it.
It just happens.

      Rating:
      5
OUT  February 11, 2003
Reviewer:  mo

Tom:

If a and b are arrays of type varchar2 how do you define in SQL*PLUS or pl/sql t
osee the result of the our parameter.

variable a number
variable b number
...
exec test( 'A', :a, :b, :c, .. )
print a
print b

          Followup:

you don't

declare
   a   <tabletype>;
   b   <tabletype>;
begin
   test ( 'A', a, b, :c, .... );

   for i in 1 .. a.count loop dbms_output.put_line( a(i) ); end loop;
   for i in 1 .. b.count loop dbms_output.put_line( b(i) ); end loop;
end;
/
print c

      Rating:
      5
OUT  February 11, 2003
Reviewer:  mo

Tom:

Do you know why I can not see the output.

PACKAGE EMPLOYEE_PKG AS
TYPE tblname_out   IS  TABLE OF varchar(50) INDEX BY BINARY_INTEGER;
TYPE tbladdress1_out IS TABLE OF varchar2(50) INDEX BY BINARY_INTEGER;
TYPE tblcity_out   IS  TABLE OF varchar2(30) INDEX BY BINARY_INTEGER;
TYPE tblstate_out  IS  TABLE OF varchar2(15) INDEX BY BINARY_INTEGER;
TYPE tblzip_out    IS  TABLE OF varchar2(10) INDEX BY BINARY_INTEGER;
PROCEDURE EmployeeSearch
   (the_name IN VARCHAR2,
 name_out     OUT tblname_out,
 address1_out OUT tbladdress1_out,
 city_out     OUT tblcity_out,
 state_out    OUT tblstate_out,
 zip_out      OUT tblzip_out);
END Employee_Pkg;

PACKAGE BODY EMPLOYEE_PKG AS
PROCEDURE EmployeeSearch
   (the_name IN VARCHAR2,
 name_out     OUT tblname_out,
 address1_out OUT tbladdress1_out,
 city_out     OUT tblcity_out,
 state_out    OUT tblstate_out,
 zip_out      OUT tblzip_out)

IS
CURSOR cur_employee (curName VARCHAR2) IS
select * from org where orgtype = curName;

RecordCount NUMBER DEFAULT 0;

BEGIN
      FOR curRecEmployee IN cur_employee(the_name) LOOP
         RecordCount:= RecordCount + 1;
 name_out(RecordCount)     := curRecEmployee.name;
 address1_out(RecordCount) := curRecEmployee.addr1;
 city_out(RecordCount)     := curRecEmployee.city;
 state_out(RecordCount)    := curRecEmployee.state;
 zip_out(RecordCount)      := curRecEmployee.zip;

      END LOOP;
   END EmployeeSearch;
END Employee_Pkg;

  1   declare
  2  a1  employee_pkg.tblname_out;
  3  a2  employee_pkg.tbladdress1_out;
  4  a3 employee_pkg.tblcity_out;
  5  a4  employee_pkg.tblstate_out;
  6  a5  employee_pkg.tblzip_out;
  7  begin
  8  employee_pkg.employeesearch('MSC',a1,a2,a3,a4,a5);
  9* end;
 10  /

PL/SQL procedure successfully completed.

SQL> print a1
SP2-0625: Error printing variable "a1"
SQL> print a2;
SP2-0625: Error printing variable "a2"

      Rating: 5
OUT
      parameter  February 11, 2003
Reviewer:  mo

Tom:

it works thanks, but:

1.test ( 'A', a, b, :c, .... );

what is :c.  I have a and b as OUT parameters only.

Can you run it through SQL*PLUS or only pl/sql program?

2.  One informix guy says that in informix they use temp tables because they
save time on the query. He says that you create a temp table then you do all
queries on it instead of creating the query everytime and join the tables. he
says if the query takes 100 secs and insert takes 10 secs and sum/count takes 10
secs you always save the 100 secs each time you query.  My thinking is that the
join in oracle will not take any time and I think the insert is what is really
time consuming? What do you think?

Thank you

          Followup:

1) you have to answer "what is :c", you used it first:

...
If a and b are arrays of type varchar2 how do you define in SQL*PLUS or pl/sql t

osee the result of the our parameter.

variable a number
variable b number
...
exec test( 'A', :a, :b,  :c, .. )
print a
print b
......

I just copied your example.  my example was an anonymous block that could be
submitted via sqlplus or any environment actually that can execute plsql.

2) That may well be why informix as an entity no longer exists ;)

I think I could not follow the "query takes 100 secs" "insert takes 10 secs" and
"sum/count" takes 10 seco" you save 10 secs each time you query.

I would ask why they query more then once?  That is the part I'm not getting --
show me an example

      Rating: 4
Please Do Not
      Take Back GTT, thanks  February 12, 2003
Reviewer:
      Robert  from PA
>>...temporary tables are crutches....We grudgingly added them in 8i.

You really mean this, Tom ?
Gee, I hope you guys don't take it back.
I mean for "bad" data model GTT seems invaluable to me as a
session-owned temp holding place so one does not have to come up with a single
complex SQL - assuming that's possible - that no one can understand 2 days
after...

          Followup:

and undocumented complex code is any harder to understand then an elegant query?

give me a query anyday -- rather unravel that then scads of code to do the same
;)

Yes, they are are a crutch for things that shouldn't be (that's what crutches
are for I guess)....

      Rating: 5
temp
      tables  February 12, 2003
Reviewer:  mo

Tom:

1. what I mean is that  I ran the package/procedure using pl/sql like this and
it worked. But how do you do it using sql*plus prompt.  how do you declare a
variable of type array t for output and then print it.

  1  declare
  2      t1  employee_pkg.tblname_out;
  3      t2  employee_pkg.tbladdress1_out;
  4      t3 employee_pkg.tblcity_out;
  5      t4  employee_pkg.tblstate_out;
  6      t5  employee_pkg.tblzip_out;
  7      begin
  8      employee_pkg.employeesearch('ABC',t1,t2,t3,t4,t5);
  9      for i in 1..t1.count
 10      loop
 11      dbms_output.put_line(t1(i));
 12      dbms_output.put_line(t2(i));
 13      dbms_output.put_line(t3(i));
 14      dbms_output.put_line(t4(i));
 15      dbms_output.put_line(t5(i));
 16      end loop;
 17*    end;

2.  I do not have an example.  But I conclude you are against using temp tables
and using inline views and a query instead. Is there a benefit to this? Does it
make the SQL more complex?
What the informix guy is saying: let us say you have one PL/SQL program where
you have 5 queries on tables a , b ,c.
First query runs on tables and create a temp table. then queries 2-5 run on the
temp table (result set). what he says is that if he runs the last 4 queries on
the temp table he save time instead of running it on tables a , b anc c because
he has to do a join every time.

I am not sure if this is correct but if I am correct the join is not time
consuming as an insert (write data blocks)?

Thanks,

          Followup:

1) asked and answered:

...
If a and b are arrays of type varchar2 how do you define in SQL*PLUS or pl/sql t

osee the result of the our parameter.

variable a number
variable b number
...
exec test( 'A', :a, :b, :c, .. )
print a
print b

Followup from Tom:

you don't

declare
   a   <tabletype>;
......

2) does it make the sql more complex?  I don't know, is:

   insert into t
   SELECT_QUERY_HERE

   select .... from ...., T where .......

more complex, easier, harder, the same as:

   select .... from ...., (SELECT_QUERY_HERE) where .......

?????

And again -- I sort of need the real world case where I need to:

a) create some result set
b) to run 2-5 other queries against it

If you have a verifiable valid case that this is "so", then yes, there can be an
advantage to using a temporary *sometimes*.  I myself might look at the
processing that requires the same temp table to be used in 2-5 subsequent
queries and see if I don't have that proverbial "single SQL statement" instead
of 5....

I can show you joins that blow away inserts for speed
I can show you the converse
I can show you most any case -- since almost everything is possible.

There are no blacks, no whites, no absolutes here -- everything is shades of
grey.

No, you cannot say "a join is faster then an insert" -- I'm not really even sure
what that "means".  it is comparing apples to toaster ovens.

      Rating: 4
Some more
      questions regarding temporary tables  July 15, 2003

Reviewer:  Suwarna Patki  from Manchester, UK

Hi Tom,

 I have some quick questions:

1. Can temporary tables be loaded using external tools like Informatica?
2. How are these tables referenced in stored procedures? Please give some
examples.

Thanks in advance!!!

Regards,
Suwarna

          Followup:

1) question for informatica.  I would guess "yes but so what".  temporary tables
disappear upon commit or session end so while informatica could load it -- so
what, they'll disappear.

2) just like any other table -- not a single bit of difference.

      Rating: 5
temporary table
      for multiple selection  September 19, 2003

Reviewer:  surya  from singapore
can temporary tables be used for multiple selection?
to elaborate,

say i have table tab1 with col1, col2, etc..
have index on col1

now, i need to display the data from tab1 based on value passed for col1.

if only single value is passed then my output refcursor would be

open o_ref_cur for select * from tab1 where col1 = i_col1;

i need to accept multiple values for col1 as well. so, decided pass comma
seperated as "1,2,3,4,5"

then can i use a temporary table to parse this and populate it into a temporary
table say "temp" and give the output
like below to make use of index on col1

open o_ref_cur for select * from tab1 where col1 in (select col1 from temp)?

is that good way? i never used temporary tables, please guide?

          Followup:

yes, you can use temp tables for this.

      Rating: 5
temporary table
      for multiple selection   September 21, 2003

Reviewer:  surya
thanks.it works fine.

now, when i don't select anything, then i don't pass anything for col1. in that
case "temp" temporary table doesn't have any rows. so my query

open o_ref_cur for select * from tab1 where col1 in (select col1 from temp)

doesn't return anything, but i need to return everything(i.e. all the rows from
tab1). is that possible?

thanks

          Followup:

  if ( i_didnt_put_anything_in_temp )
  then
     open o_ref_cur for select * from tab1;
  else
     open o_ref_cur for select * from tab1 where col1 in ( select col1 from
temp);
  end if;

      Rating: 4
temporary table
      for multiple selection -- for multiple columns  September
      23, 2003
Reviewer:  surya
yeah, i can give that if condition but there are lots of columns like col1. i.e.
multiple selection is possible on more than one column.so, it is becoming little
difficult to give so many conditions. is there any work around to write in the
single sql and at the same time utilize the indices on the columns

          Followup:

not really

      Rating: 5
Tom for
      president ?  September 23, 2003
Reviewer:
      Neil  from UK
"I'd make a terrible president.  I'm not very political."

Exactly why you should get the job!

September 23, 2003 -- Thanks for the question regarding "oracle
      utl_smtp issue", version 8.1.7

      You Asked
Dear Tom:
    I am testing with utl_smtp, my platform is oracle 8.1.7.0.0 + Windows Server
2000, my windows server is a member of dc, in our dc, we have exchange server as
mail server, I issue the following command within sqlplus, it can work,

declare
v_connection utl_smtp.connection;
begin
v_connection :=utl_smtp.open_connection('mailsc2.test.com',25);
utl_smtp.helo(v_connection,'mailsc2.test.com');
utl_smtp.mail(v_connection,'jiangzx@test.com');
utl_smtp.rcpt(v_connection,'test@test.com');
utl_smtp.data(v_connection,'This mail is come from Oracle pl/sql');
utl_smtp.quit(v_connection);
end;
/

But when I try to send a mail to other mailbox not in our dc,
declare
v_connection utl_smtp.connection;
begin
v_connection :=utl_smtp.open_connection('mailsc2.test.com',25);
utl_smtp.helo(v_connection,'mailsc2.test.com');
utl_smtp.mail(v_connection,'jiangzx@test.com');
utl_smtp.rcpt(v_connection,'solaris350@sohu.com');
utl_smtp.data(v_connection,'This mail is come from Oracle pl/sql');
utl_smtp.quit(v_connection);
end;
/

System give me one error message as follow:
ORA-20002: 550 5.7.1 Unable to relay for solaris350@sohu.com
ORA-06512: at "SYS.UTL_SMTP", line 86
ORA-06512: at "SYS.UTL_SMTP", line 223
ORA-06512: at line 7

I wonder that maybe there are something wrong with the auth login, but I don't
know how to deal with this issue, tks for you help in advance!

      and we said...
you need to talk to your mail admin.

they have configured the smtp server so as to NOT relay (this has nothing to do
with oracle's utl_smtp, you would be able to replicate this using simple TELNET
or any email package that uses smtp to send mail -- like netscape or mozilla).

this is commonly done to prevent spammers from using your machine to send mail.

it is a configuration issue on the smtp side.
Review &
      Followup

      Rating: 3
smtp auth
      login  September 23, 2003
Reviewer:  Jackie

Also I want to know whethere there is a function in utl_smtp to authentic the
related mail account and password?
if there is, can you give me a example?

Tks a lot!

          Followup:

you would have to look up the SMTP protocol and either use the utl_smtp command
call or just "write_data" to talk to smtp directly.

I don't have an smtp server that requires authentication, so I cannot test.

but smtp is really "simple" as the name suggests.  you can use TELNET to talk to
your server.  for example, in the following -- the bold is what I typed in:

[tkyte@localhost tkyte]$ telnet aria.us.oracle.com 25
Trying 138.2.5.51...
Connected to aria.us.oracle.com.
Escape character is '^]'.
220 aria.us.oracle.com ESMTP Sendmail 8.10.2+Sun/8.10.2; Tue, 23 Sep 2003
07:32:51 -0400 (EDT)
HELO aria
250 aria.us.oracle.com Hello
dhcp-amer-vpn-gw2-west-141-144-79-157.vpn.oracle.com [141.144.79.157], pleased
to meet you
HELP
214-2.0.0 This is sendmail version 8.10.2+Sun
214-2.0.0 Topics:
214-2.0.0       HELO    EHLO    MAIL    RCPT    DATA
214-2.0.0       RSET    NOOP    QUIT    HELP    VRFY
214-2.0.0       EXPN    VERB    ETRN    DSN
214-2.0.0 For more info use "HELP <topic>".
214-2.0.0 To report bugs in the implementation contact Sun Microsystems
214-2.0.0 Technical Support.
214-2.0.0 For local information send email to Postmaster at your site.
214 2.0.0 End of HELP info
HELP EXPN
214-2.0.0 EXPN <recipient>
214-2.0.0       Expand an address.  If the address indicates a mailing
214-2.0.0       list, return the contents of that list.
214 2.0.0 End of HELP info
QUIT
221 2.0.0 aria.us.oracle.com closing connection
Connection closed by foreign host.
[tkyte@localhost tkyte]$

You can telnet in and see what commands your server accepts and use the command
api accordingly.

      Rating: 4
tks a
      lot!  September 23, 2003
Reviewer:  Jackie

Tks a lot, so I must adjust my smtp server to make it has the relay function to
assure that I can send mail through oracle db to outside mailbox!

September 23, 2003 Lisa -- Thanks for the question regarding
      "reading a delimited file into sqlldr", version

      You Asked
I am trying to read a tab delimited file in SQLloader
is this possible

      and we said...
LOAD DATA
INFILE *
REPLACE
INTO TABLE DEPT
FIELDS TERMINATED BY X'9' OPTIONALLY ENCLOSED BY '"'
(DEPTNO, DNAME, LOC)
BEGINDATA
12  RESEARCH    SARATOGA
10  ACCOUNTING  CLEVELAND
11  ART SALEM
13  FINANCE BOSTON
21  SALES   PHILA.
22  SALES   ROCHESTER
42  INT'L   SAN FRAN

is an example.  there are tabs (chr(9), hex number '9')
separating the fields after the BEGINDATA

Review &
      Followup

      Rating: 3
error in
      loading of data  September 22, 2003
Reviewer:
      Deepak Gupta  from Delhi, India
Dear Mr. Tom,

I am using  following steps to load data through sqlldr
But I am getting following error

Record 1: Rejected - Error on table MEDI_TEMP1, column MEDI_OPEN.
ORA-01722: invalid number

I tried different options like
instead of "char" I put  decimal external / integer external and
field terminated by '$' option
( only those record got inserted without decimal value )

control.file
+++++++++
load data
infile 'medi_new1.txt'
INSERT into table
medi_temp
(
 emp POSITION(1:4) char,
 medi_open POSITION(17:22) CHAR,
 medi_amt POSITION(33:38) CHAR)

medi_new1.txt
+++++++++
C008    $    32191.2    $    29000
C030    $    310    $    5810
C031    $    23172.3    $    6260
C035    $    1641    $    6130
C040    $    174.5    $    3875
C049    $    0    $    8460
C051    $    495.52    $    4170
C062    $    0    $    11565
C065    $    0    $    11070

Thanks + Regards

          Followup:

looks like you have tabs in there.

when I cut and paste it -- pos(17:22)

C008    $    32191.2    $    29000
123456789012345678901234567890
                ^^^^^^

doesn't line up.  check your input data.

what I do to test -- would be to create a temp table "T" with the same number of
columns but make all of the datatypes varchar2(4000).  then load.  then you can
"see" what data is loaded into which columns and figure out where you went
wrong.

      Rating: 4
How to load
      data from tab input fiel  September 23, 2003

Reviewer:  Deepak Gupta  from Delhi, India

Dear Mr. Tom,

     Hi,

You rightly said , there is tab in my data file, but how to handle it ?

I tried as per your  advice but data is not coming in any logical order in the
table columns
++++++++++++++++++++
SQL> desc t3
 Name                            Null?    Type
 ------------------------------- -------- ----
 EMP                                      VARCHAR2(5)
 MEDI_OPEN                                VARCHAR2(4000)
 MEDI_AMT                                 VARCHAR2(4000)

SQL> set ARRAYSIZE 1
SQL> select * from t3;

EMP
-----
MEDI_OPEN
---------------------------------------------
MEDI_AMT
---------------------------------------------
C008
        32191.2
        $       29000

C030
        310     $       5
810

C031
        23172.3
        $       6260

C035
        1641    $
6130

++++++++++

Thanks + Regards

          Followup:

if you use delimited by whitespace instead of POS()

then:

C008    $    32191.2    $    29000

will map to 5 fields -- you can "filler" the two dollar signs out

September 23, 2003 Russell -- Thanks for the question regarding
      "Alpha and Numeric Sequence values", version 8.1.7

      You Asked
I have a requirement that an alpha/numeric sequence value is created for
x-reference purposes to a legacy system;

eg. B21

The alpha value is between A-Z and the numeric is between 0-99.

I have created two sequences, one between 65 and 90 to enable the use of the CHR
function to control the alpha side on the sequence and another sequence between
0-99 to control the numeric side.

The alpha/numeric sequence will be created by a trigger on insert to another
table.

As the sequence, ideally has to be gap free, I can't use the NEXTVAL of the
alpha side of things, so will be storing the sequence value in a lookup table
until the numeric sequence recycles, at which point I will then get the NEXTVAL
of the alpha side.

As it is also possible that the sequence value may have been used, as this is
inherited data from legacy system, before I have wrapped the  code in a WHILE
loop to try and prevent duplicates;

BEGIN
   DECLARE
      l_alpha_seq   lookup.code%TYPE;
      l_prefix      lookup.code%TYPE;
      l_suffix      PLS_INTEGER;
   BEGIN
      BEGIN
         SELECT code, sequence02.NEXTVAL
           INTO l_prefix, l_suffix
           FROM lookup;
      EXCEPTION
         WHEN NO_DATA_FOUND THEN
            SELECT sequence01.NEXTVAL
              INTO l_suffix
              FROM DUAL;
      END;

      WHILE (SQL%ROWCOUNT = 0) LOOP
         IF (   l_suffix = 1
             OR NVL (l_prefix, 0) = 0) THEN
            SELECT CHR (sequence01.NEXTVAL)
              INTO l_prefix
              FROM DUAL;

            UPDATE lookup
            SET code = l_prefix;

            IF SQL%NOTFOUND THEN
               INSERT INTO lookup
                           (code)
                    VALUES (l_prefix);
            END IF;
         END IF;

         l_alpha_seq := RTRIM (LTRIM (l_prefix)) || TO_CHAR (l_suffix, '00');

         INSERT INTO xref
                     (prd_pdh_key, prd_pdd_key, lng_code, code)
            SELECT :NEW.prd_pdh_key, :NEW.prd_pdd_key, 'UK', l_alpha_seq
              FROM DUAL
             WHERE NOT EXISTS (SELECT 1
                                 FROM xref
                                WHERE code = l_alpha_seq);

         SELECT sequence01.NEXTVAL
           INTO l_suffix
           FROM DUAL;
      END LOOP;
   END;
END;

Is there anyway to do this just using Oracle sequences or removing the need to
maintain the lookup table ?

P.S. This is great site and I read your posting daily in enhance my knowledge of
Oracle.

      and we said...
I would be tempted to just create a table:

create table legacy_seq( x varchar2(3) );

and load it up with all 2,600 values A00 ... A99, B00, ...B99, .... Z98, Z99.

ops$tkyte@ORA920.US.ORACLE.COM> drop table legacy_seq;

Table dropped.

ops$tkyte@ORA920.US.ORACLE.COM> create table legacy_seq ( x varchar2(3) primary
key ) organization index;

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> begin
  2          for i in ascii('A')..ascii('Z')
  3          loop
  4                  for j in 0..99
  5                  loop
  6                          insert into legacy_seq values ( chr(i) ||
to_char(j,'fm00') );
  7                  end loop;
  8          end loop;
  9  end;
 10  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> select count(*) from legacy_seq;

  COUNT(*)
----------
      2600

and then to grab a sequence, just code:

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace function get_legacy_seq return
varchar2
  2  is
  3          l_val legacy_seq.x%type;
  4  begin
  5          delete from legacy_seq where x >= 'A00' and rownum = 1 returning x
into l_val;
  6          return l_val;
  7  end;
  8  /

Function created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> exec dbms_output.put_line( get_legacy_seq );
A00

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> exec dbms_output.put_line( get_legacy_seq );
A01

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> exec dbms_output.put_line( get_legacy_seq );
A02

PL/SQL procedure successfully completed.

Now, if you can/need, you can use an autonomous transaction to make this more
concurrent -- but frankly with only 2600 values, concurrency isn't probably a
big issue.  The drawback to this would be if a session calls get_legacy_seq and
then ROLLSBACK -- you'll have "lost a sequence", which with 2600 of them could
be an issue

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace function get_legacy_seq return
varchar2
  2  is
  3          pragma autonomous_transaction;
  4          l_val legacy_seq.x%type;
  5  begin
  6          delete from legacy_seq where x >= 'A00' and rownum = 1 returning x
into l_val;
  7          commit;
  8          return l_val;
  9  end;
 10  /

Function created.

Review &
      Followup

      Rating: 5
Very
      nice!  December 07, 2002
Reviewer:  John
       from US

      Rating: 5
Alphanumeric
      Sequence  June 04, 2003
Reviewer:
      A reader

      Rating: 3
Does this
      violate relational thoery?  June 07, 2003

Reviewer:  David  from USA
Does this function depend on the order in which rows are deleted?  Does Oracle
guarantee the order that the rows will be deleted in?  That seems to be risky in
an active database in which tablespaces are constantly being re-organized.  What
effect would chaining or migrating rows have on this function?  (I realize that
that shouldn't be a problem in your example, but is this ordered behavior
guaranteed by the rdbms?)

      Rating: 3
Seemingly
      contradictory answer  June 25, 2003
Reviewer:
      DH  from USA
In
http://asktom.oracle.com/pls/ask/f?p=4950:8:350732801489822338::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:10294619924214,
you stated that:
"It is not only "generally said", it is most EMPHATICALLY STATED -- never rely
on the order of rows in a result set unless you have an ORDER BY.

But in this post, your code,

delete from legacy_seq where x >= 'A00' and rownum = 1 returning x ,

seems to depend on the order that the rows are deleted in.  Is rownum enough to
insure that the rows will be deleted in the appropriate order?

          Followup:

I cheated, it was an IOT which when full scanned will return data sorted by
primary key.

      Rating:
      5
Excellent  June 26, 2003
Reviewer:
      A reader

      Rating: 5
alphanumeric
      and Numeric value  September 23, 2003
Reviewer:
      atul  from India
Hi,

I have a table of which one column contains values as follows:

select sea from test;
AOO67
12345
ABCDE
AB12E

means table conatins numeric values,Numeric + alpha numeric and alphanumeric
values..

How to find out numeric + alpha numeric value from a column

ie i want to find out only AB12E,AOO67 ie value contains number and character..

Thanks.
atul

          Followup:

ops$tkyte@ORA920LAP> select x, replace( translate( x, '01234567890','0000000000'
), '0', '' )
  2    from t
  3   where length(replace( translate( x, '01234567890','0000000000' ), '0', ''
)) != length(x)
  4  /

X     REPLA
----- -----
AOO67 AOO
AB12E ABE

works by turning numbers into "nothing".  if resulting string is

a) not null
b) a different length

then it must have some non-numbers and numbers...

September 23, 2003 Alan -- Thanks for the question regarding "Job
      Scheduling (Dbms_job vs Cron)", version 8.1.7.

      You Asked
Hi Tom,

I'm having some discussion with my dba with regards to Dbms_job. Currently we
are using CRON to do some job scheduling like running some "clean-up" routines
for our tables, analyzing tables, etc, all in all, all jobs are database
dependent. But with the advent of DBMS_JOB, I just told him if we could move our
job scheduling stuff to use oracle's job scheduling facility, but he came back
to me with the following reason why we should not use dbms_job

1. If the database is down when the job is scheduled to run then not only does
the job not run but the schedule is broken. (2) It is the responsibility of the
job to set the schedule for the next run so it is not a true scheduling tool it
is only a one-shot run like 'at' in unix (3) If the job fails at any time for
any reason the schedule is broken and must be restablished manually.

Can you shed light on these problems with DBMS_JOB as per my DBA findings?

Thank you very much.

      and we said...
1) well, that actually seems like a postive thing to me (that the job should not
run) -- but the "broken schedule" -- where does that come from?

I want a job to run at 3am every day.  Great, the interval should be:

  trunc(sysdate)+1+3/24

Now, regardless of WHEN that job runs -- its NEXT_DATE will be 3am tomorrow --
no matter what (just like cron).

If I don't want the job to run after 6am and before (eg: if it starts between
3am and 6am -- it is OK, any other time -- just wait to the next run) I would
put:

  begin
      if ( to_char(sysdate,'hh24') NOT between 3 and 6 )
      then
             return;
      end if;
      .........

So, me thinks #1 is not valid at all -- not a bit...

2) huh?  what do they mean by that?  makes no sense at all.  You can submit a
job with a "start with" and "interval".  It is for recurring jobs.

3) see #1 -- the schedule is not something you need to fix manually.  You want
something to run on the hour every hour?  great:

  interval => trunc(sysdate,'hh')+1/24

regardless of WHEN that job runs -- the NEXT_DATE will be on the top of the
hour.  You want something to run every Saturday at 7am?

  interval => next_day( trunc(sysdate), 'SAT' ) + 7/24

once again, regardless of when the job runs, it next runs on the next sat at 7am

The schedule "doesn't get broken" -- they are really confusing me with that.

Review &
      Followup

      Rating: 5
Also, to the
      original poster...  February 03, 2003
Reviewer:
      Kashif  from Houston, TX
I'm not much of a DBA, but it seems to me that the kinds of jobs you're looking
to do, i.e. analyze and clean tables and so forth, would require the database to
be up in any event, so #1 on your DBA's list seems to be a moot point. The only
advantage I can think of in scheduling a job at the cron level is to have other
routines/handlers execute in the event that the database is in fact down, which
dbms_job will not be able to do since it's dependent on the db being up.
Correct, Tom? Thanks.

Kashif

P.S. I think they have one of those rare DBAs in the world who actually took the
time to present some arguments trying to validate their viewpoint, and not just
dismiss it by saying, "thats how we do it..." or something along those lines.

          Followup:

I think the DBA's point about #1 with the db up or down is -- if at 3am the
database is down and at 7am they bring it up

a) cron will have run the job at 3am, cron won't run it again
b) dbms_job will not have run the job at 3am, dbms_job WILL run it and you may
well not want it to run at 7am during business.  Hence my "if between" logic if
that is what was relevant....

      Rating: 4
My DBA's
      reasoning  February 04, 2003
Reviewer:  Paul
       from UK
My DBA insists that all jobs are scheduled using cron, so that the Unix
administrator can manage the "big picture" of job scheduling (which in our case
involves lots of FTP jobs that copy data files for loading using SQL*Loader -
we're on 8.1.7).

I couldn't argue with his rationale, but I find it very frustrating not being
able to use the full features of Oracle.  Obviously though, this hinders
portability if management decide to go to NT or any other OS.

          Followup:

well, if you are using FTP and SQLLDR -- cron is the logical choice.

If you are checking the "up" or "downess" of your database, cron is again -- the
logical choice.

If you are cleaning up tables, analyzing tables, all jobs are database dependent
-- cron becomes less logical of a choice.

What is the unix administrator doing running database loads though ;)

      Rating:
      3
  May 27, 2003
Reviewer:  reader
       from Michigan
if in this case, dbas have issues on whether to use cron or dbms_job, should the
dbas have issues if the application group would be the one scheduling thru
dbms_job? Our application team wants to use it to schedule datafixes.

          Followup:

ask them exactly what their issues are.  No, I don't see why they should have
"issues".

It would actually give them BETTER control if everything was done via jobs.

      Rating:
      3
  May 27, 2003
Reviewer:  reader
       from Michigan
If its a choice between dbms_job and a third party scheduling software like
autosys, is dbms_job still an advantage. The issue that was raised by another
dba was related to performance impact on onlines or batch if developers can
schedule datafixes anytime. We had an experience where the datafix script
scheduled via dbms_job included an analyze of a table before update.

          Followup:

so, developers cannot use "at" and schedule a sqlplus job anytime right now????
on any platform (inlcluding windows which has such a command...)

if you have an single, enterprise scheduling tool you use for everything --
everything -- by all means go for it.

CRON is not that "single, enterprise scheduling tool"

      Rating: 4
dbms_job's
      unexpected (or 100% expected?) behavior  May 28, 2003

Reviewer:  A reader
If not scheduled and monitored carefully, jobs scheduled with dbms_job can
behave undesirably. Say if you want a job to run 8am and 8pm everyday, and you
set

next_date=>trunc(sysdate)+8/24,
interval=>'trunc(sysdate,''HH'')+0.5'

If, for example at 2pm in the afternoon you need to "alter system set
job_queue_processes=0" and then reset it to no-zero (dynamically disable and
enable job queue), then the above schedule will change: it will execute 2pm and
2am everyday. The reason is that once the job queue is re-enabled, every
non-broken job in the queue will run regardless of the existing next_date.

          Followup:

you are correct, when incorrectly programmed, programs behave incorrectly.

when you use relative dates/times like that (add a half day), it would be a
mistake.

You need to use a function that always returns 8am or 8pm regardless of what
time it is invoked.  CASE and DECODE are very useful there.

      Rating: 5
Any Substitute
      for AutoSys in the Oracle universe?  September 23, 2003

Reviewer:  A reader
(Currently on 8.1.7.0.0 Enterprise Edition;
 Moving towards 9i soon)

Tom,

Is it possible to replace a job-scheduling system currently implemented using
AutoSys with tools/components exclusively from the Oracle universe?

The current system is used to schedule SQL*Plus scripts, SQL*Loader scripts,
stored programs, Reports, Forms and o/s batch programs.

The DBMS_JOB package lets us handle one piece of the puzzle (scheduling of
stored programs). Is there any combination of utilities within Oracle which will
let us handle this whole set-up without using AutoSys?

Thank you for your time and valuable insight!

          Followup:

OEM does that out of the box.  (enterprise manager)

September 23, 2003 sj -- Thanks for the question regarding "text of
      my sql's since the start of my session", version 8.6

      You Asked
Tom

I want to query all the sql's I wrote since the start of my session from
v$sqlarea. I want to know how should I write this query and how should I format
it so that I will have a well formatted list of query list,

and I also want the buffer hits, disk hits for this query from v$sqlarea.

which views should I join for the above purpose.

and what kind of details does mystats give, does it give details by query or by
session.

How many queries will be in the v$sqlarea, i.e. what is the criteria for pushing
a query out of v$sqlarea.

Thank you

      and we said...
You cannot do that, that information is not tracked in the SGA.  All of the
queries you ran since you session began might not even be in the shared pool and
we definitely don't remember which ones YOU executed and the buffer counts YOU
encountered.

This is what SQL_TRACE, TIMED_STATISTICS and TKPROF are useful for:

http://asktom.oracle.com/~tkyte/tkprof.html

v$mystats is session level statistics.

As for how many queries are in the shared pool -- that is a direct function of
the complexity of the query (size it takes to store it) and the size of your
shared pool.  Could be few queries, could be lots.

Queries get pushed out on an LRU basis when more space is needed for new queries.
Review &
      Followup

      Rating:
      4
Clarify  September 27, 2001
Reviewer:
      A reader
So where do the sql's found in v$sqlarea, come from.
How long do they stay in this view. What is the criteria for them to stay in the
view.

IN

http://asktom.oracle.com/pls/ask/f?p=4950:8:129035::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:497421739750,%7BSET%7D%20and%20%7BSERVEROUTPUT%7D%20and%20%7BON%7D
You do state that the active sql can be captured.

So do you mean that only active sql which is currently executing in different
sessions is only found in V$sqlarea.

Once again..

What is the actual criteria to put a query into v$sqlarea, and to kick it out of
the v$sqlarea.

number of sql's in shared pool = no of sql's in v$sqlarea
is this statement true

          Followup:

The sql you see there is what is currently in the shared pool.  All of the sql
you executed since the beginning of your session MAY or MAY NOT be in there
still.  Even if it all IS -- the fact that YOU executed is NOT tracked.

There will be one copy of "select * from emp" typically in the shared pool.
1,000,000 sessions may each have executed it 5 times.  We do not track who
executed and what sort of buffer reads they have.

If you are currently executing a statement -- it will be in the shared pool.
Hence we can get the ACTIVE sql -- but not sql you executed 5 minutes ago.

v$sql and related views are just peeking at the memory of the SGA.  The criteria
for being in there is someone parsed you.  The criteria for getting out is that
no one has used you for a while.

v$sqlarea is an aggregate view, v$sql is not.  duplicates (child cursors) are
removed in v$$sqlarea (you should use v$sql for casual queries for this reason
-- more efficient)

It is not really true that count(*) in v$sqlarea is count of queries in the
shared pool, its the count of the distinct query texts in the shared pool.  That
is

"select * FROM T"

might be in the shared pool two times because the user mike, who owns mike.t,
submitted and the user tom, who owns tom.t, submitted it and its really TWO
different queries with the same text.

      Rating: 4
A
      Reader  September 28, 2001
Reviewer:  A
      Reader  from USA
<quote>
v$sqlarea is an aggregate view, v$sql is not.  duplicates (child cursors) are
removed in v$$sqlarea (you should use v$sql for casual queries for this reason
-- more efficient)
<quote>

Could you give an example of *duplicates (child cursors)"

Thanks

          Followup:

Sure, I have two users OPS$TKYTE and SCOTT.  they both have EMP tables.  They
both ran some queries.  When I look in v$sqlarea:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select sql_text, version_count from v$sqlarea
where sql_text like 'select * from emp%';

SQL_TEXT                                 VERSION_COUNT
---------------------------------------- -------------
select * from emp                                    1
select * from emp e1                                 2
select * from emp e2                                 2
select * from emp, dept where emp.deptno             1
 = dept.deptno

select * from emp@ora815.us.oracle.com               1

I see 2 versions of select * from emp e1, select * from emp e2. I only see the
query once but I know there are two versions.  So, I goto v$sql:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select sql_text, parsing_user_id,
parsing_schema_id from v$sql where sql_text like 'select * from emp%';

SQL_TEXT                                 PARSING_USER_ID PARSING_SCHEMA_ID
---------------------------------------- --------------- -----------------
select * from emp                                      0                 0
select * from emp, dept where emp.deptno              28                28
 = dept.deptno

select * from emp e2                                  28                28
select * from emp e2                                   0                 0
select * from emp@ora815.us.oracle.com                28                28
select * from emp e1                                  28                28
select * from emp e1                                   0                 0

7 rows selected.

And I can see why.  The select * from emp e2 queries where parsed by different
users, with different effective (parsing schema id) ids.  They are the same text
-- totally different queries.

      Rating:
      5
READER  September 28, 2001
Reviewer:
      Reader  from USA
Given "PARSING_USER_ID PARSING_SCHEMA_ID" be the same
and ivalidations = 0, what other causes are for multiple versions

Thanks

          Followup:

session level settings play into this hugely

o different optimizer goals
o different sort area sizes
o different optimizer_* parameters
o and so on.

each of those will cause a different plan to be generated (hence child cursors)

      Rating: 5
how to see
      actual bind variable values  March 25, 2003

Reviewer:  P  from AL
hi tom,
is there a way to see actual values for bind variables instead of :1 :2 in
v$sqlarea/v$sql views?

          Followup:

no, there is not.  they are in the UGA of the session which is normally in the
PGA memory only.

remember -- v$sql/sqlsarea represent SHARED stuff -- there could be thousands of
:1 values for any single statement in there.

      Rating:
      5
Excellent!  March 25, 2003
Reviewer:  A
      Reader
My unserstanding is that each session in dedicated server architecture will have
its own UGA/PGA and it is not part of SGA. My question related to your comment
above "no, there is not.  they are in the UGA of the session which is normally
in the PGA memory only." If the values for bind variables are in UGA, why can't
a current session see the value of bind variables that are used in that session.
Is there a way to see it at all? Thanks.

          Followup:

a current session can, v$sql_bind_data, v$sql_bind_metadata, session specific v$
views.

      Rating: 5
Truncated
      SQL_TEXT  August 13, 2003
Reviewer:  Fred
      Shimaya  from Irvine, CA
Tom,

I am interested in looking at very long statements in v$sql, but most of those
are truncated.  Thus I can't see if bind variables were being used.  Is there a
way to see the whole statement?

Fred

          Followup:

v$SQLTEXT_WITH_NEWLINES

      Rating: 5
thanks a
      mucho!  August 13, 2003
Reviewer:  Fred
      Shimaya  from irvine, ca
Senor El Caro,

Gracias por la ayuda!

      Rating: 4
Auditing
      SQL  August 14, 2003
Reviewer:  Praveen
       from India
Hi Tom,

Version : Oracle 8.1.7

  I am developing an audit trail for our warehouse system. I want to trap the
sql statements if user deletes, updates or truncates the table. How to trap the
sql's that user is executing. I tried using the following query but always got
no rows found.

select distinct substr(sql_text,1,100) IN_TRIGGER into v_sql_text
        from v$sql
        where address in (select sql_address from v$session
                          where osuser = sys_context('userenv','os_user')
                          and   status = 'ACTIVE'
                         )
        and sql_text not like '%IN_TRIGGER%'

          Followup:

you cannot get the text of the query in 8i.

you can in 9i using a trigger -- there is a function provided for doing so.

      Rating: 4
When was the
      last time shared pool object was flushed?  September 22,
      2003
Reviewer:  Sai  from Mountain View, CA

Hi Tom,

I would like to know "when was the last time shared pool flushed/aged out any
object from the pool".

If I have a job set up to collect snapshot of v$sql every 30 mins, is it safe to
assume that none of the sql statements were aged out or flushed out from the
shared pool if the first_load_time is less than last 30 mins?

Thanks

          Followup:

not safe, the entire entry could "disappear" - the thing being flushed itself
could "go away" entirely.

      Rating: 3
When was the
      last time shared pool object was flushed?  September 22,
      2003
Reviewer:  Sai  from Mountain View, CA

Hi Tom,

I request you to help me with this question. Why would Oracle flush any object
out of shared pool if there are no new sql statements being executed, unless
somebody flush out entire shared pool. With this assumption, I thought, Oracle
didn't yet flushed any objects out since max(to_date(first_load_time,......')),
in other words, the last time Oracle flushed any object could be <=
max(to_date(first_load_time,'.....)).   Please explain.

Thanks.

          Followup:

ops$tkyte@ORA920LAP> create table t ( x int );
Table created.

ops$tkyte@ORA920LAP> select * from t test;
no rows selected

ops$tkyte@ORA920LAP> select first_load_time from v$sql where sql_text = 'select
* from t test';

FIRST_LOAD_TIME
-------------------
2003-09-22/21:42:38

ops$tkyte@ORA920LAP> alter table t add y int;
Table altered.

ops$tkyte@ORA920LAP> select * from t test;
no rows selected

ops$tkyte@ORA920LAP> select first_load_time from v$sql where sql_text = 'select
* from t test';

FIRST_LOAD_TIME
-------------------
2003-09-22/21:42:38

Ok, so what does this show?  well, nothing directly but -- obviously, "select *
from t test" was invalidated and then rebuilt -- but the load time didn't change
(and I type fast, but not that fast)...

so, what if "select * from t test" took 5 times the amount of space?  something
might have to go.

it could be that the statements are in v$sql -- but is invalidated -- so not
really in the shared pool truly.  So, they actually get parsed and loaded up --
flushing other stuff out as well.  (statements do not fall out of v$sql when
invalidated...)

      Rating:
      5
  September 23, 2003
Reviewer:
      A reader

      Rating: 5
Bind variables
      etc.  September 23, 2003
Reviewer:  Neil
       from UK
The trace analyzer will give you the values of your bind variables. Only
retrospectivly, though. Get TRCA.zip from technet...

      Rating: 5
For
      Neil  September 23, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Lahore, Pakistan
Neil !

Can you please paste here the link for trca.zip ?

Thanks

          Followup:

it is actually on metalink.oracle.com, just search for trca.zip and it pops
right up

September 23, 2003 -- Thanks for the question regarding "JDBC
      variable binding", version 10.0.0

      You Asked
Hi, Tom

Phillip Greenspun has this comment in his blog that
"Java is the SUV of programming languages".

Here's the link:

http://blogs.law.harvard.edu/philg/2003/09/20#a1762
Here's google cache:

http://www.google.com/search?q=cache:http://blogs.law.harvard.edu/philg/2003/09/20
Do you have any comments on this issue?

I know you are more Oracle guy than Java, but since
Phillip's comment targets specifically in coding
JDBC variable binding. What's your thought on that?
You always said use bind variables, but if binding
is so hard in JAVA, what to do about it?
Do you have a good coding practice that would somehow
make life easier for our good old Phillip?

Thanks.

      and we said...
I agree with the crux/premise of his statment (copied from the above link)

<quote>
A project done in Java will cost 5 times as much, take twice as long, and be
harder to maintain than a project done in a scripting language such as PHP or
Perl.  People who are serious about getting the job done on time and under
budget will use tools such as Visual Basic (controlled all the machines that
decoded the human genome).  Note from Tom: he forgot the most efficient
scripting language on the planet if you are building a database applicaiotn --
PLSQL -- end note But the programmers and managers using Java will feel good
about themselves because they are using a tool that, in theory, has a lot of
power for handling problems of tremendous complexity.  Just like the suburbanite
who drives his SUV to the 7-11 on a paved road but feels good because in theory
he could climb a 45-degree dirt slope.  If a programmer is attacking a truly
difficult problem he or she will generally have to use a language with systems
programming and dynamic type extension capability, such as Lisp.  This
corresponds to the situation in which my friend, the proud owner of an
original-style Hummer, got stuck in the sand on his first off-road excursion; an
SUV can't handle a true off-road adventure for which a tracked vehicle is
required.

</quote>

I cannot fix java -- but woe to those that do not use prepared statements and
bind!!!  (yes, I had to add chapters to my latest book on this -- i used java as
the example to try and end once and for all the "statement vs prepared
statement")

warning -- opinion coming up.  no proof for this one:

I too believe that java has a place, only it is not "all places".  it is
correctly used in maybe 10% the times it is used.

When I was a programmer -- first starting out -- i always had a choice.  I could
use any of

o SAS
o PL/I (the *coolest* 3gl ever invented in my opinion)
o REXX
o JCL  (yes, jcl, i would write entire programs in jcl)
o C
o SQL
o syncsort

I used them all interchangeably.  I would have pl/i calling syncsort, one day I
would write the code in rexx, the next day it was pl/i.  C for the pc (no pl/i
compilers -- although I did have rexx on there, that was cool)

There was, is, will not be "a best language for everything"
Review &
      Followup

      Rating: 4
Java and JDBC
      complexity  September 23, 2003
Reviewer:  Jer
      Smith  from Salt Lake CIty, UT
One thing that I'd strongly recommend for people writing applications that
connect to Oracle is to consider an alternative to writing fancy O/R code like
JDO or entity beans or to writing raw JDBC is to use JDBC wrapper utilities to
make life easier.  This mitigates many of the problems with using JDBC.  JDBC is
really too low-level to work with for those who are really trying to concentrate
on application functionality.  Just make sure that your wrapper classes use best
practices in working with the database.

Rod Johnson, in his _J2EE_ Expert One-on-One Book (a good companion to Tom's,
really) provides a set of wrapper classes that handle the stuff that people
usually screw up and having to type over and over again. You can find the
javadoc at
http://www.springframework.org/docs/api/overview-summary.html
Another option is to use SQLJ to call the Oracle stored procedures.

You'll never be successful at Java if you try to build things from scratch.
What attracts people to Microsoft and Oracle languages is that the _bulk_ of the
utilities and tools are produced by the company itself.  That's not a bad thing,
necessarily, but it's a different world than Java, Perl, or Python, where the
base product is pretty limited, but thousands of other solutions are available
out there.

September 23, 2003 -- Thanks for the question regarding "Can these
      queries be combined", version 8.1.7

      You Asked
Tom,

is there a way to combine these queries ?

accesslog is 160million records partitioned by TIMESTAMP (41 partitions)
visit is 110 million records partitioned by FIRSTACCESS(41 partitions)
There are indexes on timestamp and firstacess (local prefixed)
view_page is 310,000 records non partitioned.

select    sum(1) OFPAGEVI,count(distinct a22.ID) OFVISITS,count(distinct
a22.HTTPUSERID) OFUSERS
from    accesslog a21, visit a22, view_page a24
where    a21.VISITID = a22.ID and
a21.HTTPRESOURCEID = a24.ID and
a21.SERVERID = a24.SITEID
and a22.FIRSTACCESS between To_Date('2003-08-31 00:00:00', 'YYYY-MM-DD
HH24:MI:SS') and To_Date('2003-09-06 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
and a21.TIMESTAMP between To_Date('2003-08-31 00:00:00', 'YYYY-MM-DD
HH24:MI:SS') and To_Date('2003-09-06 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
and    a24.L1DIRID in (49703, 56113, 56106);

select    sum(1) OFPAGEVI,count(distinct a22.ID) OFVISITS,count(distinct
a22.HTTPUSERID) OFUSERS
from    accesslog a21, visit a22, view_page a24
where    a21.VISITID = a22.ID and
a21.HTTPRESOURCEID = a24.ID and
a21.SERVERID = a24.SITEID
and a22.FIRSTACCESS between To_Date('2003-08-31 00:00:00', 'YYYY-MM-DD
HH24:MI:SS') and To_Date('2003-09-06 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
and a21.TIMESTAMP between To_Date('2003-08-31 00:00:00', 'YYYY-MM-DD
HH24:MI:SS') and To_Date('2003-09-06 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
and    a24.L1DIRID in (56113);

select    sum(1) OFPAGEVI,count(distinct a22.ID) OFVISITS,count(distinct
a22.HTTPUSERID) OFUSERS
from    accesslog a21, visit a22, view_page a24
where    a21.VISITID = a22.ID and
a21.HTTPRESOURCEID = a24.ID and
a21.SERVERID = a24.SITEID
and a22.FIRSTACCESS between To_Date('2003-08-31 00:00:00', 'YYYY-MM-DD
HH24:MI:SS') and To_Date('2003-09-06 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
and a21.TIMESTAMP between To_Date('2003-08-31 00:00:00', 'YYYY-MM-DD
HH24:MI:SS') and To_Date('2003-09-06 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
 and    a24.L1DIRID in (8079, 10274, 49332, 58137);

select    sum(1) OFPAGEVI,count(distinct a22.ID) OFVISITS,count(distinct
a22.HTTPUSERID) OFUSERS
from    accesslog a21, visit a22, view_page a24
where    a21.VISITID = a22.ID and
a21.HTTPRESOURCEID = a24.ID and
a21.SERVERID = a24.SITEID
and a22.FIRSTACCESS between To_Date('2003-08-31 00:00:00', 'YYYY-MM-DD
HH24:MI:SS') and To_Date('2003-09-06 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
and a21.TIMESTAMP between To_Date('2003-08-31 00:00:00', 'YYYY-MM-DD
HH24:MI:SS') and To_Date('2003-09-06 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
and    a24.L2DIRID in (57880, 51323, 52061);

select    sum(1) OFPAGEVI,count(distinct a22.ID) OFVISITS,count(distinct
a22.HTTPUSERID) OFUSERS
from    accesslog a21, visit a22, view_page a24
where    a21.VISITID = a22.ID and
a21.HTTPRESOURCEID = a24.ID and
a21.SERVERID = a24.SITEID
and a22.FIRSTACCESS between To_Date('2003-08-31 00:00:00', 'YYYY-MM-DD
HH24:MI:SS') and To_Date('2003-09-06 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
and a21.TIMESTAMP between To_Date('2003-08-31 00:00:00', 'YYYY-MM-DD
HH24:MI:SS') and To_Date('2003-09-06 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
and    a24.L2DIRID in (94069, 67777, 51416, 83903, 50171, 50165, 82043, 50168,
51316, 94244);

select    sum(1) OFPAGEVI,count(distinct a22.ID) OFVISITS,count(distinct
a22.HTTPUSERID) OFUSERS
from    accesslog a21, visit a22, view_page a24
where    a21.VISITID = a22.ID and
a21.HTTPRESOURCEID = a24.ID and
a21.SERVERID = a24.SITEID
and a22.FIRSTACCESS between To_Date('2003-08-31 00:00:00', 'YYYY-MM-DD
HH24:MI:SS') and To_Date('2003-09-06 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
and a21.TIMESTAMP between To_Date('2003-08-31 00:00:00', 'YYYY-MM-DD
HH24:MI:SS') and To_Date('2003-09-06 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
and(a24.L1DIRID in (56106)
and not a24.L2DIRID in (67276));

Many thanks

      and we said...
select sum(case when a24.l1dirid in ( 49703, 56113, 56106) then 1
                else null end) c1,
       count( distinct case when a24.l1dirid in ( 49703, 56113, 56106)
                       then a22.id else null end) c2,
       count( distinct case when a24.l1dirid in ( 49703, 56113, 56106)
                       then a22.httpuserid else null end) c3,

       sum(case when a24.l1dirid in ( 56113) then 1 else null end) c4,
       count( distinct case when a24.l1dirid in ( 56113) then a22.id
                            else null end) c5,
       count( distinct case when a24.l1dirid in ( 56113)
                            then a22.httpuserid else null end) c6,
       .....
  from accesslog a21, visit a22, view_page a24
 where a21.VISITID = a22.ID
   and a21.HTTPRESOURCEID = a24.ID
   and a21.SERVERID = a24.SITEID
   and a22.FIRSTACCESS between To_Date('2003-08-31 00:00:00', 'YYYY-MM-DD
HH24:MI:SS')
                           and To_Date('2003-09-06 23:59:59', 'YYYY-MM-DD
HH24:MI:SS')
   and a21.TIMESTAMP between To_Date('2003-08-31 00:00:00', 'YYYY-MM-DD
HH24:MI:SS')
                         and To_Date('2003-09-06 23:59:59', 'YYYY-MM-DD
HH24:MI:SS')

just move the variable part of the predicate into the SELECT list using CASE and
have a set of 3 columns per query you had before....

Review &
      Followup

      Rating:
      4
Excellent  September 23, 2003
Reviewer:
      Sanjay  from USA
Will surely need this type of query in future

      Rating:
      5
Brilliant  September 24, 2003
Reviewer:
      A reader

      Rating: 5
Not sure if
      it's necessary...  September 24, 2003
Reviewer:
      Kashif  from Reston, VA
But you might want to add an 'IN' predicate to Tom's query, to limit the results
to just the L1DIRIDs you want, as you had done in your individual queries. Only
now you'll add all the L1DIRIDs you're interested in. Something like:

<Tom's query goes here>
and a24.L1DIRID in (49703, 56113, 56106, 56113, 8079, 10274, 49332, 58137,
57880, 51323, 52061, 94069, 67777, 51416, 83903, 50171, 50165, 82043, 50168,
51316, 94244, 56106);

Kashif

September 23, 2003 Jason -- Thanks for the question regarding
      "Hiding the password", version

      You Asked
I usually write shell scripts that
invoke SQL*Plus and friends from the
command line.  I put the username and
password on the command line since that
info is kept in an environment variable,
since it can potentially change.  However,
I don't like the fact that the "ps"
command can display the command line of
the process to other users, since it
will reveal the username and password
for this account!  How can I mask this
information from "ps" without having
to resort to keeping the username and
password in the SQL script?

      and we said...
Well, my favorite solution to this is to use an "identified externally" account.
 For example, I've set:

NAME                           TYPE    VALUE
------------------------------ ------- --------------------
os_authent_prefix              string  ops$

in my init.ora.  I then:

create user ops$tkyte identified externally;

This lets me:

$ id
uid=12997(tkyte) gid=1(other)

$ sqlplus /
SQL*Plus: Release 8.1.5.0.0 - Production on Fri Mar 10 19:28:46 2000
(c) Copyright 1999 Oracle Corporation.  All rights reserved.
Connected to:
Oracle8i Enterprise Edition Release 8.1.5.0.0 - Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.0.0 - Production

ops$tkyte@8i> show user
USER is "OPS$TKYTE"
ops$tkyte@8i>

I do not need a username password anymore (i can still use them but I can always
use / to log in as my).  This is perfect for cron jobs, at jobs and the like.
You have to be logged into unix to become that account.

see
http://www.peachnet.edu/oiit/support/oracle/General/Unix/HideArgs.html
for another solution.

Review &
      Followup

      Rating: 2
How about for
      NT?  April 05, 2001
Reviewer:  Victor
      Lobaton  from The Hague, The Netherlands
I was happy to know that there is one already for UNIX but how about for NT?
Could you help me how to do this in NT?  Since we use a lot of logfiles in our
batch jobs the password is always dynamically coded in the logfiles as well as
the scripts.  Thanks in advance and more power!

      Rating: 3
Other
      tricks...  August 30, 2001
Reviewer:  andrew
       from CA, USA
http://www.orafaq.com/faqunix.htm#HIDEPSW

      Rating: 2
Need more
      help  June 14, 2002
Reviewer:  Nitin
       from India
The solution which u have given is not of more use for me because I am working
in a production server and I can not make the user as identified externlly also
I need to know how can I prevent to echo the password while taking EXPORT not
connectiong thru SQLPLUS. If u give me a solution then I will be really very
thankfut to you.

          Followup:

Yes you can -- and yes you should.

The solution -- os authenticated accounts, period.  We've emailed -- so I know
you are on unix -- it is trivial, it is very very secure (way more secure then
your approach of storing passwords in files), and it matters not if you are on
production, test or development.  You have a problem "passwords available via
ps", the solution is "use os authenticated accounts -- no more passwords on
command lines"

      Rating: 5
ksh
      options  June 14, 2002
Reviewer:  Connor
       from UK
on korn shell you can issue

print PASS | oracletool

where 'oracletool' is most of the common oracle ones - exp, imp, sqlplus etc

          Followup:

care to expand on that?  give a full example?  (i couldn't get it to work -- I'm
a csh guy so maybe just more info is needed...)

      Rating: 5
I think I can
      help elaborate on the ksh options  June 15, 2002

Reviewer:  Doug  from Danbury, CT USA
Something like this seems to work:
print systempassword | exp full=Y userid=system

An export takes place and with the ps command the password is not visible.

          Followup:

yup, that appears to work

I still like the os authenticated accounts as you never need to store a password
anywhere.  then it just becomes

exe userid=/ full=y

no muss, no fuss -- more secure, easier to manage.

thanks for the followup

      Rating: 3
Another way to
      hide password on UNIX  June 15, 2002
Reviewer:
      TJ Struckus  from USA
The following shows a way to hide sqlplus and sqlldr username password from the
PS command.  Granted you still need to have the username and password somewhere
because it is not externally identified.

Let this be you env variables.
---------VARIABLES --------------
export APP_USER=something
export APP_PASS=somepass
export APP_SID=sid

Here is the script with a execute permission.

--------------SCRIPT TO RUN SQL----------
#!/usr/ksh
sqlplus << END_OF_SQL
$APP_USER/$APP_PASS@APP_SID

select * from dual;
END_OF_SQL
exit $?
----------END SCRIPT----------

Here is another for SQLLOAD steps
--------------SCRIPT TO SQLLDR----------
#!/usr/ksh
sqlldr control=contril.ctl data=data.dat << END_OF_SQLLDR
$APP_USER/$APP_PASS@APP_SID
END_OF_SQLLDR
----------END SCRIPT----------

          Followup:

well, if you are on solaris, fire up your script and then go:

/usr/ucb/ps -auxwwee | grep APP_PASS

and see what you see ;)

ps can dump the ENVIRONMENT as well.  (very handy trick to know, can be useful
to see a processes environment -- but for you -- it exposes the password)

EXTERNALLY AUTHENTICATE ACCOUNTS!!!!!

      Rating: 4
OPS$ accounts
      and REMOTE_OS_AUTHENT secure?   September 30, 2002

Reviewer:  Cynthia  from Denver, USA
If I have OPS$ accounts and enable REMOTE_OS_AUTHENT,
is there a security vulnerability?

Do I need REMOTE_OS_AUTHENT enabled to use db links
from another database, where the db link does not specify
the user/password?

          Followup:

YES.

say you have ops$cynthia setup and it has DBA.  I'm on your network with my
linux laptop and (having root) create an OS user cynthia.  Hmmm, am I in or am I
in?

You do not need remote_os_authent (and in fact that in general WOULDN'T work --
the OS process connecting is typically owned by ORACLE, not by you!), you do
need password based authentication however.

      Rating: 4
But what
      about......  October 01, 2002
Reviewer:
      Robert  from Memphis, USA
Tom,

I see that using the externally identified account is a great thing. But how can
we connect via a client app with these. Don't you have to be on the server in
order to use these externally identified accounts?

Thanks,

Robert

          Followup:

Typically yes.  I do not use them for "end users" personally (mostly I guess
cause I haven't used anything client server for many many many many years).

I use them for ADMINS who are running on the machine.

In windoze -- you can use this safely on a network -- see the NT OS admin guide
for details.

      Rating:
      5
  October 01, 2002
Reviewer:
      A reader
"say you have ops$cynthia setup and it has DBA.  I'm on your network with my
linux laptop and (having root) create an OS user cynthia.  Hmmm, am I in or am I
in?"

Please, would you elaborate this? I tried to test connect this way and I
couldn't. Can you please demonstrate what you say here? Thanks a lot.

          Followup:

if you have remote_os_authent set to true (says to trust the os credentials
forwarded from the remote host, sort of like the r* programs -- rlogin, rsh,
rexec)

and you are in a unix environment

and you drop a machine into that environment upon which you have root

and you create a user that matches one in the remote database in your OS -- by
definition you are able to remotely login in using OS authentication to that
host.

      Rating:
      2
  October 01, 2002
Reviewer:
      A reader  from Hungary
$sqlplus /nolog
SQL>connect username/passwd@connect_string
$ps -ef |grep sqlplus
end than

          Followup:

so tell me "reader".

How does that answer the question:

"How can I mask this
information from "ps" without having
to resort to keeping the username and
password in the SQL script?"

(rhetorical question, it doesn't -- your approach implies encoding the
username/password into a script something they say "we don't want to do")

OS authentication is the RIGHT CHOICE for automated scripts that run on the
server like this.

      Rating: 5
Followup for
      "ksh options" (Jun 2002)  October 01, 2002

Reviewer:  Connor  from UK
In korn shell, you can use (for example):

print my_pass | exp userid=system file=...

or

print my_pass | sqlplus system @my_script

Or for my lengthy scripts

print "
connect user/pass
select ...
exit" | sqlplus /nolog

etc etc

Cheers
Connor

      Rating:
      5
AskTom.unixguru.com  October 08, 2002

Reviewer:  Juan Guascarancas Pena  from Mexico

<quote>
  /usr/ucb/ps -auxwwee
</quote>

You are da man Tom!!!
Almost 4 years as a Unix sysadmin and I didn't know this...

      Rating: 4
question to
      implemenet remote security  March 10, 2003

Reviewer:  A reader
Hi

My English is not very good.... so I dont understand very much what you said
about remote_os_authent... is it secure?

I have a question, we have an DBA Admin database which is a centralized
repository for all our production instances (over 60 production instances), what
we normally do is run shell scripts from the host where this admin db resides
and script loops a instances.ini file which contains all production instances
SID and for each database it runs several SQL scripts and spool the results to
plain text files then load the results into the admin db. The password is set in
shell variables inside shell scripts (we have a .env file which loads the
variables at beggining of each shell script, all KSH btw), I feel this is not
very secure, the unix admin can see my files and therefore my passwords which I
am not happy so I am thinking of two approaches

1. store password encrypted in a table using dbms_obfuscation but this has a
problem, I still need to store the seed somewhere so in my scripts can use it to
unencrypt the password and use it

2. use remote_os_authent but I have never used this and wondering if it has any
security issues, you have explained a couple of examples but I dont fully
understand :(

          Followup:

remote_os_authent is not a very secure setting, no.

or 3) use database links instead of connecting directly to the remote database.

create a schema for each remote database.
create a database link from that user to the remote database.
create synonyms for the remote tables.

      Rating: 5
How many
      external users?  March 26, 2003
Reviewer:
      Marcio  from Brazil
Tom, I did not understand this stuff enough.

1. Have I got to set up init parameter os_authent_prefix such 'ops$' always for
use externally authentication? or any other like 'bla$' or 'bla' either.

2. how many end users with authentication externally could be exists? And, all
of them are going to use os_authent_prefix, so I gotta set their names like
bla<username>?

cheers,
Marcio

          Followup:

1) no, you don't have to.  I think you should, but it is not mandatory.

2) 2 Billion.  and yes, if you user bla as the os_authent_prefix, you would need
to add that to their username.

      Rating: 4
Thanks for the
      idea  September 22, 2003
Reviewer:  Sanjaya
      Balasuriya  from Colombo, Sri Lanka
And Tom,

With OS authentication how can you login to a database as different users ?
Say I want to login as appuser@mydb as well as system@mydb.
How can I work out such a situation with OS authentication ?

          Followup:

you can either:

o use a username/password (eg: i can either log in as tkyte to the OS and

connect /

or i can log in as whomever and

connect ops$tkyte/password

since I created the user identified by password and set up the ops$ to signify
OS authentication.  you can do both)

o log into the OS as the right person

      Rating: 4
Passing ops$
      password on command line  September 23, 2003

Reviewer:  Jerry  from Washington, DC USA
If I have user mary identified by her pass, and user ops$oracle identified
externally, and by mypass, mary seems to be able to login by passing her
password on the command line, but ops$oracle doesn't seem to be able to.

Is this a restriction placed on externally identified accouunts?

oracle@hp:~> sqlplus mary/herpass

SQL*Plus: Release 9.2.0.1.0 - Production on Tue Sep 23 09:48:08 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

SQL> quit
Disconnected from Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
oracle@hp:~> sqlplus ops$oracle/mypass

SQL*Plus: Release 9.2.0.1.0 - Production on Tue Sep 23 09:48:19 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

ERROR:
ORA-01017: invalid username/password; logon denied

Enter user-name: ops$oracle
Enter password:

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

SQL>

          Followup:

alter user ops$oracle identified by mypass;

it'll be both.

      Rating: 4
Can't Get It To
      Work on Linux  September 23, 2003
Reviewer:
      Jerry  from Washington, DC USA
It's not real important, but for some reason, I can't get this to work as I had
expected on SuSE Linux 8.2 and 9iR2.

oracle@hp:~> sqlplus /

SQL*Plus: Release 9.2.0.1.0 - Production on Tue Sep 23 11:53:02 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

SQL> alter user ops$oracle identified by mypass;

User altered.

SQL> quit
Disconnected from Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
oracle@hp:~> sqlplus ops$oracle/mypass

SQL*Plus: Release 9.2.0.1.0 - Production on Tue Sep 23 11:53:28 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

ERROR:
ORA-01017: invalid username/password; logon denied

Enter user-name: ops$oracle
Enter password:

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

SQL>
SQL> show parameter os_authent

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
os_authent_prefix                    string      ops$
remote_os_authent                    boolean     FALSE

          Followup:

think about what $oracle means in "shell" :)

try

sqlplus 'ops$oracle/mypass'

else, you are just getting

sqlplus ops$oracle/mypass -> sqlplus ops/mypass

      Rating: 5
Is there
      anything you don't know?  September 23, 2003

Reviewer:  Jerry  from Washington, DC USA

          Followup:

i don't know...

i guess that means -- yes, there is.

I learn something new about Oracle every day.  I learn something new about stuff
every day....

September 23, 2003 Tony -- Thanks for the question regarding
      "Pre-testing Integrity Constraints", version 8.1.7

      You Asked
A programmer on my team is working on an application that manages versions of a
customer's resume.  When the user goes to update/revise the resume, the app
currently copies the entire resume to a new version (involving several tables)
in order to "preserve" prior versions that recruiters and companies may have
linked to (ensured through FK constraints).  During a code review, it became
obvious that a "new" version of the resume was only required if someone had
linked to the current one, otherwise, the change could be made without
re-versioning. Instead of issuing several SQL calls to check if any dependent
tables were linked to the current resume version, is there a way to *test* the
foreign key constraints instead, something like,

- delete <current resume>
- if (ORA-02292: integrity constraint violated)
- then
-   create a new version of resume
- else
-   rollback;
-   allow edit of current version
- end if;

There is probably a simple way to do this that I just can't see...

      and we said...
You can use savepoints to do some work and rollback JUST that work:

scott@ORA9I.WORLD> create table p ( x int primary key );
Table created.

scott@ORA9I.WORLD> create table c ( fk references p );
Table created.

scott@ORA9I.WORLD> create sequence p_seq;
Sequence created.

scott@ORA9I.WORLD> insert into p values ( p_seq.nextval );
1 row created.

scott@ORA9I.WORLD> insert into c values ( p_seq.currval );
1 row created.

scott@ORA9I.WORLD> insert into p values ( p_seq.nextval );
1 row created.

scott@ORA9I.WORLD> create or replace function get_new_resume_id( p_old_resume_id
in number ) return number
  2  as
  3      fk_violation exception;
  4      pragma exception_init( fk_violation, -2292 );
  5      l_new_resume_id number;
  6  begin
  7      savepoint resume_savepoint;
  8      delete from p where x = p_old_resume_id;
  9      rollback to resume_savepoint;
 10
 11      return p_old_resume_id;
 12  exception
 13      when fk_violation
 14      then
 15          insert into p values ( p_seq.nextval ) returning x into
l_new_resume_id;
 16          return l_new_resume_id;
 17  end;
 18  /

Function created.

scott@ORA9I.WORLD>
scott@ORA9I.WORLD>
scott@ORA9I.WORLD> begin
  2      for x in ( select x from p )
  3      loop
  4          dbms_output.put_line( 'Going to version id = ' || x.x );
  5          dbms_output.put_line( 'Version id = ' || get_new_resume_id(x.x) );
  6          dbms_output.put_line( '----------------' );
  7      end loop;
  8  end;
  9  /
Going to version id = 1
Version id = 3
----------------
Going to version id = 2
Version id = 2
----------------

PL/SQL procedure successfully completed.

scott@ORA9I.WORLD>
scott@ORA9I.WORLD> select * from p;

         X
----------
         1
         2
         3

scott@ORA9I.WORLD> select * from c;

        FK
----------
         1

scott@ORA9I.WORLD>

Review &
      Followup

      Rating: 5
Great
      Solution!  December 12, 2001
Reviewer:  Tony
       from Philadelphia
This is exactly what I wanted - this has numerous application throughout our
system.  I really like the fact that this automatically accomodates any
additional constraints that might be added in the future... Thanks!

      Rating: 4
Unique
      Constraint and Unique Index  December 13, 2001

Reviewer:  Sen  from Chennai, TN, INDIA
What is the Relationship Between UNIQUE Index and UNIQUE CONSTRAINT? How does
UNIQUE INDEX work on a COLUMN ( having no UNIQUE  CONSTRAINT)?
NOTE,I AM NOT ABLE TO POST A QUESTION! WHAT SHOULD I DO?

          Followup:

well, i plugged:

unique constraint index

into my search screen

and found prior discussions on this very topic.  The one with the subject:
Index Clarificatin. -- why primary key | unique, why not a unique index.

is of great relevance (its basically the same question)

Sorry if this message that you got when you attempted to ask a question was not
clear:

Sorry, I am not accepting new questions right now.

Here is my policy/procedure regarding questions:

    * I am one person, there is really an askTom. I am not a team, not a group
of people. From time to time, I do request assistance from others but I receive
each and every question.

    * On a typical day I take between 20 to 40 questions. Over the last 2 years,
I've averaged 18 a day, every day, 365 days a year. Some days I take more, some
less, some none at all (I too take vacations).

    * I take questions in batches of 10. That is, when accepting questions, I
accept upto 10 of them and then stop. I do this so I can provide timely answers
to questions (within a couple of minutes or hours)..

    * I typically do a batch in the morning, after lunch, and before I go home.
If I have time and no one will play pool with me, I'll do another batch at
night. I live on the east coast of the United States -- so the "best" times to
try are 9am, 1pm, 5pm, and 10pm (all times in GMT-5)

Please don't email me directly. That will only prevent me from answering
questions here in this forum, where many people can benefit from the answer. If
I answer you in email -- that answer will be seen by you and you alone. I'd
rather answer the question once here, not for everyone who has it.

Also -- try the search. At least 1/3 of the questions I do answer are answered
with "a search for X Y Z found this link...." where X Y Z comes right out of the
question. The search is run using interMedia and the results are ordered by
relevancy -- its fast and accurate. Out of the 13,000 plus questions I have --
over 5,000 of them are published here (the ones that are answered with "see this
link" I don't publish).

I don't know any other way to say it -- I take X number of questions and then
STOP.

I wish more people would try the search -- i answer many of YOUR questions that
way myself!

      Rating: 5
Pre-testing
      Integrity Constraints (what about cascade
      delete?)  September 23, 2003
Reviewer:  Kurt
       from Springfield, VA USA
Tom,
I like your suggestion of issuing a statement within a savepoint, and I can
imagine several places where I can use that approach - thanks!

However, would your approach to this problem work if all FK constraints used the
"on delete cascade" clause? I believe then that the delete statement would not
raise the exception, and that SQL%ROWCOUNT would equal just 1 even if multiple
child records were deleted. So how would you know if child records existed or
not?

Thanks, Kurt

          Followup:

you are right, with on delete cascade, it would not work.  good point, good eye.

      Rating: 5
A similar
      approach for other constraints?  September 23, 2003

Reviewer:  Kurt  from Springfield, VA USA
Tom,
In the same way that Tony is trying to catch FK violations, we're trying to
catch unique constraint violations, and may also wish to catch check constraint
violations at a later time. When I say catch, I mean detect that a violation
occurred (or would occur?) and return a *meaningful* error message/code.

Suppose I have a personnel table with a sequence-driven PK as well as a unique
constraint on the SSN column. If a client attempts to insert a new record which
includes a duplicate SSN, a DUP_VAL_ON_INDEX exception is raised. But the same
exception is also raised if (by error) the client attempts to insert a record
which includes a duplicate PK value. Rather than return a message/code stating
"Duplicate value, but I don't know which column.", I'd like to return "Duplicate
PK value" or "Duplicate SSN value", depending on the constraint that was
actually violated.

One approach (the "pre-test" or "pro-active" approach) is to first issue a query
looking for the potentially offending values, and if no record is found, issue
the insert statement. But I don't like that approach much because it's doing
twice the work for a presumably rare occurrence.

I've used another ("re-active") approach where the DUP_VAL_ON_INDEX handler
block searches SQLERRM for the literal name of the unique constraint - something
like:

EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
   IF INSTR(SQLERRM,'T_PK')>0 THEN
      RAISE_APPLICATION_ERROR(-20000,'Duplicate PK value');
   ELSIF INSTR(SQLERRM,'T_UNQ_SSN')>0 THEN
      RAISE_APPLICATION_ERROR(-20001,'Duplicate SSN value');
   ELSE
      RAISE;
   END IF;

As long as we can control the constraint names at the time of DDL execution (the
names must be unambiguous such that the instr() call won't return false
positives), this approach works fine, but other developers are resistant to
hardcoding object names in the code.

Can you recommend an alternate approach that is more elegant than this one?

Thanks, Kurt

          Followup:

no, that is a sound approach on your part, using the constraint names.

      Rating: 2
there is
      something there  September 24, 2003
Reviewer:
      Gabriel  from Toronto
Hey Kurt,

I’ll get a stab at this one  … <description>Table with primary and unique
constraints … on insert, would like to report on the columns violating the
unique constraints.</description>

First of all some observations regarding the context:
1. You describe a scenario where one record is inserted at a time … for batch
operations ( “INSERT INTO … SELECT FROM …” or BULK inserts) it would be rather
complicated! So we won’t go there.

2. You said <quote>sequence driven PK</quote> … well, there is no point in
having a sequence for a synthetic primary key if some people use it and some
don’t … I would say a violation of the PK is a bug (cause somebody coded an
insert without using the sequence or did an INSERT … SEQ.NEXTVAL and then INSERT
… SEQ.CURRVAL … hey, anything is possible out there!) … where a violation of the
unique constraint is likely an application error to be expected in a multi-user
environment. I like bugs to literally explode in somebody’s face during
development or QA … I program for the application errors since they are part of
normal processing. It all depends on how you _publish_ those ORA-20000 and
ORA-20001 in your code snippet. If the caller captures both Oracle errors and
pops-up a window with the error text then you, in fact, have buried a bug even
deeper. The end-user will think “I probably inserted something that conflicted
with an existent record … hence, try again or later or never or whatever” … but
the user’s view of the record does not include the PK value (the PK is
synthetic, artificial, made up …) … the real payload of the record is probably
OK but it cannot go in because of the PK bug.

3. Uniqueness is enforced by unique indexes (physical structures) … the Oracle
primary key and unique constraints are just declarations (on top of unique
indexes of course). I’m just saying that the existence of a unique constraint
implies the existence of a unique index with the same name … but the reverse is
not true.

In the light of #2 and #3 above I’ll rephrase the problem (still relevant) as …
<description>Table with multiple unique indexes … on insert (or update) would
like to name the columns with the offending values</description>

4. Regarding the approach … you called them “pro-active” (when the steps are:
verify and maybe insert) and “re-active” (when the steps are: insert and maybe
deal with errors); I prefer to call them the “pessimist” and “optimist”
approaches (“pro-active” usually has this positive connotation to it which I
don’t think is granted in this case … or maybe is just my poor command of the
English language!). In any case, as you said about the “pro-active” approach:
<quote> I don't like that approach much because it's doing twice the work for a
presumably rare occurrence</quote>. Actually, it is more than that … you still
have to code the exception part … the fact that you passed the _verification_
part does not guarantee the insert will go through without unique violations.
Consider this: table with two numeric columns, unique indexes on each, table has
record (1,1) … one session inserts (2,2) but doesn’t commit yet … another
session verifies for (2,2) doesn’t find it and proceeds to execute the insert,
it hangs till the other session commits and then fails with ORA-00001. Even if
you have channeled all inserts through a small transaction block doing
select-to-verify and insert and commit … two sessions executing the same block
to insert the same value will race each other … they’ll both pass the
_verification_ part, but the one getting blocked out will fail with ORA-00001.
Remote possibility … but nonetheless a possibility. Of course, if the select and
insert and commit are not tightly packed (like when they are separate SQL calls
from a client over network … or with other code in between) the likelihood of
the conflict increases.

Now, let us go to your actual question regarding the snippet of code for the
exception part of the “re-active” approach.

Regarding this <quote> As long as we can control the constraint names at the
time of DDL execution (the names must be unambiguous such that the instr() call
won't return false positives), this approach works fine</quote>. Well, the false
positive can be dealt with fairly easy … consider the pattern of the error:
“ORA-00001: unique constraint (GABE.UK) violated” … here “GABE” is the owner and
“UK” is the name of the unique index  … rather than doing INSTR(SQLERRM, ‘UK’) …
do INSTR(SQLERRM,’.UK)’ … that is, include the dot and the bracket.  This will
eliminate the false positive when one has 2 unique indexes named ‘UK’ and ‘UK2’.

Pass this issue though , I would say I would be on the side of those resistant
to hardcoding index names in the code … of course, one could not be “too
opposed” since it would imply never using hints referencing an index.
Nonetheless, I would try not to hardcode if at all possible (hey, it is)!

Here are my observations:
A. Kudos for not forgetting that “ELSE RAISE;”

B. If your record has values violating both unique indexes then you’ll still
report just on one index (after all the effort to pinpoint the offending
column)! Which one? Well, on my tests it seemed Oracle verifies in the order in
which the unique indexes were created. Maybe Tom knows the internal workings …
but then again, is not that relevant after all.

C. You are already hardcoding (hence know) the object you’re inserting into …
hence you could query the dictionary for UNIQUE indexes on it (ALL_INDEXES,
ALL_IND_COLUMNS) … hence you could eliminate the hardcoding of the index names.
More, by looping through the indexes you eliminate the need for future code
maintenance when yet another unique index gets added. Of course, it gets a bit
more complicated if the object you’re referencing is not a table your schema
owns, or it is a view or synonym for a remote object … this is why I said
ALL_INDEXES and not USER_INDEXES … you may even have to get the synonym
definition and access the remote dictionary, etc … the fun part.

D. You may want to include the actual values together with the column names in
the error messages … the end-user may not recognize the column names since the
GUI may label them differently … the values could help along.

Well, this is it … hope it makes sense.

Gabriel

      Rating: 5
Follow-on to
      Gabriel's helpful feedback  September 24, 2003

Reviewer:  Kurt  from Springfield, VA USA
Gabriel,
Thanks for the great feedback - it must have taken you a long time to analyze
the issue and formulate a response! I've paraphrased your comments below, and
added some follow-on information.

1. "Approach is not appropriate for bulk inserts." Yes - this approach is only
useful for single record inserts and updates. In support of a database-driven
web application, I'm keying on single record operations, and bulk operations
would need to handle errors in a different fashion.

2. "PK violation is a bug." Absolutely - encountering a PK violation is
something that should only happen during initial development, or as the result
of a faulty change to existing code. The goal of the approach is to provide
sufficient diagnostic information such that the user or support staff can figure
out what to do next. If a PK violation occurs during production use, then the
user is powerless to fix it once they receive a "Duplicate PK value" error
message. On the other hand, if it's a user-entered data issue, then the user can
change the data they're entering and try again, or abort completely.

3. "We should refer to this as a unique index issue rather than a unique
constraint issue." Actually (according to this site), unique constraints can be
enforced by non-unique indexes, but that might not matter here. We're explicitly
issuing a statement like "alter table personnel add constraint personnel_unq1
unique(ssn);". As long as that constraint creation statement succeeds, I *think*
that Oracle will mention that constraint name in SQLERRM when that constraint is
violated, no matter how the constraint was implemented under the covers. But you
raise a good point, and I should test what Oracle puts in SQLERRM when a
constraint does "hijack" an existing index rather than creating a new index.

4. "Exception handler must be coded the same whether optimistic or pessimistic
approaches are used in order to handle a race condition." As you suggest, the
proper way to code a pessimistic approach is to tightly pack the 2 statements
together into a single PL/SQL routine. (Although we never commit in PL/SQL -
that's the client's responsibility.) But if I were to code a pessimistic
approach, I'd consider the risk of a race condition to be so minimal that I'd
just let the exception bubble up in its raw form. I'm not saying the exception
would be masked, or hidden, or ignored - just not massaged in any way for the
benefit of the user. It's only in the optimistic approach where I'd expend
effort to provide a context-sensitive error message.

5. "Use '.<constraint_name>)' as the target pattern for instr()." That's a good
suggestion. Our syntax for a unique constraint name is actually
<table_name>_unq<sequence> where <sequence> is a 1-digit base-1 incrementing
number. If we use this syntax, and the specified exception handler, a false
positive will certainly occur if we have more than 9 unique constraints,
constraint PERSONNEL_UNQ10 was raised, and we searched SQLERRM for
"PERSONNEL_UNQ1" before searching for "PERSONNEL_UNQ10". Your target pattern
syntax is better, so I'll switch to that. It will work as long as the format of
SQLERRM doesn't change!

6. "Avoid hardcoding object names in strings." I agree. On the other hand, we're
*in* PL/SQL referencing specific objects names already, so it's not that much of
a stretch to reference an object name in a string. The real issue is "tight"
versus "loose" coupling - there are now 2 places in the code that must always be
kept synchronized. If the constraint name is changed at the DDL level, it will
negatively impact the exception handler until that block of code is changed to
reflect the new name. See #8 below for avoiding using a hardcoded name at all.

7. "Observation B: Only one violation out of potentially many can be reported by
Oracle." I agree. In this case, that's acceptable. The user can change that one
value and resubmit, and then the second error message will be displayed. This
looping can occur until the user submits completely valid data, or gets
frustrated and aborts.

8. "Observation C: Query the data dictionary at runtime to loop through the
names of constraints that might be violated." Unfortunately, this approach fails
to satisfy the primary goal of linking a specific constraint name with a
specific error message/code. It's not enough to simply inform the user/support
which constraint is being violated since the constraint name is likely to be
user-unfriendly. It's *meta data* that we need at the client level rather than
the raw object name - it's the mapping of constraint name to form field-specific
message that is required.

9. "Observation D: Include submitted values in the error message." Yes, that is
very helpful information, and we do provide that information to the user. The
error message examples are just placeholders for this thread. ("Synchronization
of labels visible in the GUI with the error messages presented to the user" is a
complete topic unto itself.)

Thanks again for spending the time to help me work through this issue!

Kurt

      Rating:
      5
  September 24, 2003
Reviewer:
      Gabriel  from Toronto
Kurt,

1.
2.
3. <quote>Actually (according to this site), unique constraints can be enforced
by non-unique indexes</quote>
You're quite right. Thank you.
4. I wouldn't code the pessimistic approach or implying one should.
5.
6. I was suggesting not to hardcode the constraint/index name in the INSTR
function ... rather loop through the unique constraints for the given object(may
have to go after unique indexes as well) ... that is, go through
ALL_CONSTRAINTS, ALL_INDEXES and from there through ALL_CONS_COLUMNS and
ALL_IND_COLUMNS to get the names of the columns. Of course, this code can get
quite involved in order to become generic ... so yes, one should consider if the
effort is really worth it. It is an “alternate approach” though … didn’t even
consider the “elegant” part since … well, how is elegance being measured?
7.
8. Wasn't aware of that “primary goal” … or suggesting to stop at
constraint/index names (ORA-00001 has that information already) ... but go from
there to the actual column names (didn't realize PK and SSN are also used as GUI
lables). But yes, if you want to report based on the GUI labels rather than the
column names then, indeed, separate custom error messages are needed ... unless
one has the mapping of GUI labels to column names in a database table and has
decided to implement the above mentioned, quite heavy, dynamic method.
9.

Gabriel

      Rating:
      5
  October 12, 2003
Reviewer:
      A reader

September 23, 2003 Munzer -- Thanks for the question regarding "Edit
      Stored Procedure", version 8.1.7

      You Asked
Tom:

1.  I am using SQL*PLUS for windows to query data on a unix oracle database
connecting through net8.

Is there a way for me to update a stored procedure in the database.  How would
you retrieve it for editing.

2.  Is there a way in SQL*PLUS to run a statement that backs up all of the
stored procedures into a floppy disk?

Thank you,

      and we said...
1) I use a script called getcode:

set feedback off
set heading off
set termout off
set linesize 1000
set trimspool on
set verify off
spool &1..sql
prompt set define off
select decode( type||'-'||to_char(line,'fm99999'),
               'PACKAGE BODY-1', '/'||chr(10),
                null) ||
       decode(line,1,'create or replace ', '' ) ||
       text text
  from user_source
 where name = upper('&&1')
 order by type, line;
prompt /
prompt set define on
spool off
set feedback on
set heading on
set termout on
set linesize 100

Just run:

SQL> @getcode foo
SQL> edit foo
SQL> @foo

and you'll have retrieved a procedure/package/function named foo, edited it, and
updated it in the database.....

2) I have a script, getallcode:

set termout off
set heading off
set feedback off
set linesize 50
spool xtmpx.sql
select '@getcode ' || object_name
from user_objects
where object_type in ( 'PROCEDURE', 'FUNCTION', 'PACKAGE' )
/
spool off
spool getallcode_INSTALL
select '@' || object_name
from user_objects
where object_type in ( 'PROCEDURE', 'FUNCTION', 'PACKAGE' )
/
spool off
set heading on
set feedback on
set linesize 130
set termout on
@xtmpx.sql
---------------------------------------------------------------

that will put into the CURRENT WORKING DIRECTORY all of your code in separate
files with one extra script getallcode_INSTALL that will rerun the code.

Review &
      Followup

      Rating:
      3
  January 19, 2002
Reviewer:  Mark
       from switzerland
But how does getallcode_INSTALL install successfully into a new schema if there
are inter-dependencies between any of the functions/procedures/packages?

          Followup:

I've never ever worried about that (and often wonder why people are so obsessed
with this) since they will compile all by themselves the very next time.

If you would like a bullet proof way to make sure they compile "cleanly", see my
book -- in there I have a routine that'll do a "compile schema" without any
problems in all circumstances.  Also, dbms_utility has a compile schema which
works most of the time.

I jsut never worry or even think about it.

      Rating:
      5
SQL*PLUS  January 19, 2002
Reviewer:
      Munzer  from Reston, VA
Tom:

Also, do you know why when I do SETLINESIZE 1500 in case i have a large record
to see a columnar list of all columns , SQL*PLUS sometimes cuts it out at the
right even though it did not display 1500 characters yet.

Any ideas?

          Followup:

is sqlplus "cutting it off" or is your terminal emulator.  Give me some method
to reproduce (i've heard of issues with some versions of the "gui" sqlplusw.exe
on windows but as I never use it -- always use sqlplus.exe -- i haven't hit it).

      Rating:
      5
SQL*PLUS  January 20, 2002
Reviewer:
      Munzer  from Reston, VA
Tom:

Bascially, I have a large table of 20 columns and 2 columns are varchar2(500).
when i do select i do select * from tab rather than typing all column names and
using substr in some varchar2 columns.

Thanks,

          Followup:

yes, well that is a statement of fact.

do you mean that this is what causes some truncation?  If so, what exactly are
the other circumstances like....

o OS
o versions
o executable name (eg: sqlplusw.exe sqlplus.exe sqlplus)
o your linesize setting
o the steps I could take to reproduce this exactly.

details details details -- i need them all.

      Rating: 5
GETALLCODE
      script  February 04, 2002
Reviewer:  Munz
       from Reston, VA
Tom:

I tried running you getallcode script and it does not do anything.  Does it take
a long time to run. I waited about 10 minutes and nothing happens on the screen.

          Followup:

depends on the amount of code you have.  did you see that if it created the
xtmpx.sql script or not?  did you look from another session to see if you were
idle or not?

Its always worked for me.

      Rating: 5
edit store
      procedure  February 05, 2002
Reviewer:  dan
      malumphy  from cleveland, oh
I found this very usefull.  I also use the set pause off.  Perhaps that is why
the previous fellow had a problem

      Rating: 2
When real
      coding is to be done...  February 05, 2002

Reviewer:  A reader  from ca, usa
Very useful scripts for editing one or two bits of code, but when real coding is
neeeded, most people I know use one particular green tool reviewed here:

http://www.orafaq.com/tools/index.htm

          Followup:

geez -- I guess i don't do real coding then....  I use these pretty much all of
the time and since I work in lots of places -- I know they always work (i always
have these and vi no matter where I go -- pretty much all one needs)...

      Rating: 5
RE: When real
      coding is to be done...  February 05, 2002

Reviewer:  Mark A. Williams  from Indianapolis, IN
      USA
I find it remarkably amusing that when someone gives a 1 or 2 star rating they
are almost always named "A reader".  It just seems like heckling from a dark
corner in the back of the theater to me....

I wonder, how is "real" coding measured?  By the number of lines of code?  Is
longer better?  I can always tell when someone is using that little green tool,
because I run my databases with o7_dictionary_accessibility=false.  It doesn't
take long before I get that phone call...

The guys in our "intranet group" are always telling me I need to get a gui tool
-- for what I say?  All I need is veritas, oracle, & sun (telnet and sqlplus
included all for free thank you very much).  We had one consultant in who
couldn't start a database without using OEM (literally) - he lasted one-half
day.  Maybe it is just the Windows generation, but since when is "point & click"
a developer and/or DBA requirement?

OK, I'm done with my rant now.

- Mark

      Rating: 5
SQL*PLUS
      scripts  February 06, 2002
Reviewer:  Munz
       from Reston, VA
Tom:

it is working nicely now.  My SQL*PLUS session was hanging.

      Rating: 5
get
      code  October 31, 2002
Reviewer:  mo

Tom:

I modified the getallcode script to get "triggers" too but it is not working.
it is giving me a file with set define off statement only.

do you know why.

select '@getcode ' || object_name
from user_objects
where object_type in ( 'PROCEDURE', 'FUNCTION', 'PACKAGE','TRIGGER' )
/
spool off
spool getallcode_INSTALL
select '@' || object_name
from user_objects
where object_type in ( 'PROCEDURE', 'FUNCTION', 'PACKAGE','TRIGGER' )

2.  Do I have to get package body to get new code for packages.

3.  How can I modify this to get todays changes only.  like gettodaycode and
then recomile the changes into other instances.

4.  How would you best manage the table column changes between development and
test and production.  Would you just run the samq "alter" statement or you drop
the table and export/import. However this may result in screwing up the table
constraints. correct?

          Followup:

1)
see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:803098416492
for how I extract triggers. triggers are not stored in user_source like
procedures and such.

2) huh?

3) filter on LAST_DDL_TIME (where last_ddl_time >= trunc(sysdate) for example)

4) Well, changes need to move from DEV to TEST to PROD.  PROD cannot be fixed
with "exp/imp" from TEST (maybe test could from dev but PROD -- never)

So, TEST *needs* to supply TEST with a valid UPGRADE script that does alters to
add/drop columns and fix data.  TEST needs to have been backed up before running
such a script so TEST can be restored when said script *fails* (as it will the
first time -- murphy rules).  When DEV gives TEST a working upgrade script, TEST
will check that into the configuration mgmt tool you are using (different from
the CM tool your developers are using and they are using one right...) and then
release that to PROD for implementation.

      Rating:
      5
getcode  October 31, 2002
Reviewer:
      mo
Tom:

1.  What i meant is do you need to add 'PACKAGE BODY' or the type package will
get that automatically.

Also, can i add 'VIEW' here.

THanks

          Followup:

If you run it - you would see that by getting the NAME of the package and then
running GETCODE on that name -- I pick up both the spec and body.

You could, but it won't work.

search for

getaview

on this site (or -- better yet, based on the sheer VOLUME of questions you ask
-- you better have my book "Expert one on one".  I have this all documented in
the chapter on export import, you can read all about it -- getting code,
triggers, views out of the database to move to another database)

      Rating: 4
coding in
      vi.....  November 01, 2002
Reviewer:  Robert
       from PA
>>...and vi no matter where I go -- pretty much all one >>needs

you ARE my hero !

      Rating:
      5
print_table  September 23, 2003
Reviewer:
      mo
Tom:

How do you get this to work:

exec print_table(select * from table where name=''Mike'' ');

It does not like the single or double quotes in the search name.

Thanks

          Followup:

exec print_table( 'select * from table where name = ''Mike'' ' );

works fine

ops$tkyte@ORA920LAP> exec print_table( 'select * from dual where dummy = ''X'' '
);
DUMMY                         : X
-----------------

PL/SQL procedure successfully completed.


Subject | Article | Last Updated (East coast time)
Freelists/Apps DBA | article only | Sep 24, 2003 09:37 am
Padded Hierarchy | article only | Sep 24, 2003 09:43 am
nls_date_format | article only | Sep 24, 2003 09:46 am
Materialized Views | article only | Sep 24, 2003 12:16 pm
Top 10 Performance Indicators | article only | Sep 25, 2003 04:56 am
SYSDATE behavior in SQL and PL/SQL | article only | Sep 25, 2003 05:00 am
redo for inserting one at a time vs. many at a time | article only | Sep 25, 2003 05:12 am
7 stages a query goes through before retrieving the rows | article only | Sep 25, 2003 05:14 am
Bulk operations. jdbc using batch option VS jdbc calling stored procedure | article only | Sep 25, 2003 08:44 am
Using XML... | article only | Sep 25, 2003 09:33 am
How to extract Oracle data into a XML formatted file | article only | Sep 25, 2003 10:18 am
sub query | article only | Sep 25, 2003 11:18 pm
Function to concatenate output | article only | Sep 25, 2003 11:25 pm
Recursive Query - heirarchical relationship | article only | Sep 25, 2003 11:28 pm
Shared Memory Problem (unable to allocate shared memory ...) | article only | Sep 25, 2003 11:32 pm
Error Trapping - Reg | article only | Sep 25, 2003 11:33 pm
Auditing | article only | Sep 26, 2003 10:40 am
Update the Records in Table2 from Table1 if there is a change in any column Table1 and Insert the Records from Table1 toTable2 if it not there in Table2. | article only | Sep 26, 2003 10:41 am
How to send hungarian special characters in an email from a database? | article only | Sep 26, 2003 12:39 pm
Generate sequence numbers inside a select | article only | Sep 26, 2003 02:39 pm
Invalid Objects | article only | Sep 26, 2003 02:49 pm
Oracle connect restrictions | article only | Sep 26, 2003 02:55 pm
Update SQL Performance | article only | Sep 26, 2003 03:02 pm
Export problem | article only | Sep 26, 2003 08:06 pm
Sybase to Oracle | article only | Sep 26, 2003 08:08 pm
internal error when using autonomous transactions | article only | Sep 26, 2003 08:10 pm
Mechanism to perform the product of a single column in a multirow table | article only | Sep 26, 2003 08:16 pm
Calling A procedure dynamically which has weak refcursor has out parameter | article only | Sep 27, 2003 11:32 am
OEM of 10G | article only | Sep 27, 2003 11:40 am
Different levels of dynamic sql | article only | Sep 27, 2003 01:10 pm
How to avoid using the qualifier infront of the object name?? | article only | Sep 27, 2003 01:49 pm
How can I get this result only executing one query? | article only | Sep 27, 2003 02:22 pm
Java Stored Procedures loaded in Database | article only | Sep 27, 2003 04:33 pm
Parse out columns of a weakly typed ref cursor | article only | Sep 28, 2003 09:27 am
using oracle text function contains in anymous block with binding variable | article only | Sep 28, 2003 09:57 am
Error:PLS 00906 compilation is not possible | article only | Sep 28, 2003 10:51 am
audit trail questionc | article only | Sep 29, 2003 07:10 am
Storing Chinese Characters | article only | Sep 29, 2003 07:15 am
Issues with rebuilding indexes | article only | Sep 29, 2003 07:27 am
Serializable transaction | article only | Sep 29, 2003 07:40 am
Automating standby databases | article only | Sep 29, 2003 12:38 pm
more info about rowids... | article only | Sep 29, 2003 12:42 pm
opinion about new auto segment management | article only | Sep 29, 2003 01:30 pm
QA database performance testing using stats from Production DB | article only | Sep 29, 2003 02:32 pm
Transactions and Isolation Level Confusion | article only | Sep 29, 2003 04:32 pm
How to calculate the actual size of a table? | article only | Sep 29, 2003 04:52 pm
Questions on Pro*C multi-threaded programming | article only | Sep 30, 2003 07:04 am
asktom's Architecture. | article only | Sep 30, 2003 07:08 am
FORALL Update - Updating multiple columns | article only | Sep 30, 2003 07:32 am
Creative Auditing | article only | Sep 30, 2003 08:05 am
Outer Join - row existence decision | article only | Sep 30, 2003 08:10 am
Recommendations for using default and automatic column values | article only | Sep 30, 2003 08:15 am
oracle_smon process running as top process | article only | Sep 30, 2003 10:02 am
Nologging | article only | Sep 30, 2003 10:47 am
Indexing a path string | article only | Sep 30, 2003 01:50 pm
Summing up within a single VARRAY | article only | Sep 30, 2003 02:19 pm
normalize and surrogate key in oltp | article only | Sep 30, 2003 04:14 pm
row(s) 1 - 57 of 57
September 24, 2003 -- Thanks for the question regarding "Freelists/Apps DBA", version 8.1.7
You Asked
Q1)How do you know that a given table will need more than one freelist?
How do you decide the no. of freelists required on a table that has a lot of
concurrent inserts.

Q2)Can you suggest any books for Oracle Applications DBA?  What more will one be
looking at as an Oracle Applications DBA compared to an Oracle DBA?
and we said...
q1) Well, in 9i -- you don't have to worry about this anymore, you can use
automatic segment space management, no more freelists / freelist groups.

In 8i -- you will know based on the application itself.  You need some domain
knowledge of the application, how it works, what it does.  You can (should be
able) identify what tables will suffer the most from concurrent inserts and
updates.  These tables and their indexes are the ones to look at.

q2) No, I cannot -- I haven't read any (and I don't recommend anything I haven't
actually used/read).  Anyone out there want to recommend something here?
Review & Followup
Rating: 5
Freelists June 06, 2002
Reviewer: Greg from San Francisco, CA USA
If you only have one freelist on a table and this is not enough, the lack of
enough freelists shows up as BUFFER BUSY WAITS.  This happens because concurrent
insert processes must compete for access to the header block of the table, which
contains the freelist.

Try running the response_time_breakdown.sql script available from
www.ixora.com.au to see if buffer busy waits are a significant problem on your
system.

If so, query v$waitstat and see if there are significant waits for "data block."
 There are different classes of buffers.  v$waitstat shows buffer busy waits
broken down by class of buffer.  Lack of enough freelists will show up as buffer
busy waits on the "data block" class of buffer.  One of the most common causes
for excessive waits on this class of buffer is too few freelists on one or more
tables.

If these quick tests reveal (1) you have significant buffer busy waits, and (2)
v$waitstat shows they are "data block" waits, you now need to continue
investigating, to verify it's a freelist problem and determine which table(s)
need more freelists.  For details on doing this, search google for Anjo Kolk's
1999 YAPP paper.

Once you determine that a specific table needs more freelists, for a suggestion
of how many freelists to create, try running the script table_access_paths.sql
(which is available at www.ixora.com.au) for the table in question.

If you already have an idea of which table(s) might be burdened by two or more
processes doing concurrent inserts and the table not having enough freelists,
you can skip the preliminaries and just run table_access_path.sql for the
table(s) in question, and check whether the script recommends you increase the
number of freelists.

Rating: 5
Freelists January 14, 2003
Reviewer: atul from India
Sir,

Our web server is located at other destination that database server both
communicates through fibre line..

at max 100 clients connects to web server and some calculation get done and some
records get add/delete from one same table accordingly..

so in this case for this table what number for freelist you suggest to start
with???

Followup:
don't know.  do you even have freelist contention. have you

a) identified an issue
b) diagnosed the severity of the issue

Rating: 5
free blocks in freelists January 15, 2003
Reviewer: A reader
Hi

I am wondering if blocks NEVER used is managed by freelists too? Or freelists
manage only those blocks which have been used?

For example I have a table, size 100MB, with high water mark at 90MB, empty
table by the way so with dbms_space it would show me there are 90MB free blocks
and 10MB unused blocks. The freelist that manages this segment manages all 100MB
blocks? Or just the 90MB?

Followup:
blocks above the HWM are not on the freelists.  only blocks that have been used
to hold data will be there.

in your example, at most 90mb of blocks could be on the freelists, the 10mb
above the HWM would never be there.

Rating: 5
Freelist associated with db buffer cache April 26, 2003
Reviewer: Saminathan from CA,USA
Tom,
Is there onemore freelist to maintain "list of free blocks avaiable" associated
with db buffer cache?
If so that is nothing to do with the one we are talking about in segment level.

Kindly clear my doubt. TIA

Followup:
i don't understand the question.

segments have freelists, segments may have blocks buffered in the buffer cache.

Rating: 5
Sorry- Got confused with LRU Lists April 27, 2003
Reviewer: Saminathan from CA,USA
Dear Tom, I'm really sorry. I got confused with "LRU Lists" which is associated
with SGA. I should go thru the docs/manuals before i ask you anything.
Sorry again

Rating: 5
buffer busy waits September 24, 2003
Reviewer: A reader
Hi

We have tons of buffer busy waits in our database, I dont know where to look, I
checked v$session_wait and this is what I see

   SID user             event                                    waiting state
    wait time/sec         P1         P2         P3
------ ---------------- ----------------------------------------
------------------- ------------- ---------- ---------- ----------
    50 TTD$ORACLE7      buffer busy waits                        WAITING
                0         44     219059          0
    56 T_NEG_OWN        buffer busy waits                        WAITING
                0         44     219059          0
    46 T_NEG_OWN        db file sequential read                  WAITING
                0         44     219059          1
     8 TTD$ORACLE7      SQL*Net message from client              WAITING
               72 1650815315          1          0
     9 T_NEG_OWN        SQL*Net message from client              WAITING
                0 1413697619          1          0
    14 TTD$ORACLE7      SQL*Net message from client              WAITED SHORT
TIME               0 1650815315          1          0

   SID user             event                                    waiting state
    wait time/sec         P1         P2         P3
------ ---------------- ----------------------------------------
------------------- ------------- ---------- ---------- ----------
    46 T_NEG_OWN        buffer busy waits                        WAITING
                0         43     158135          0
    56 T_NEG_OWN        buffer busy waits                        WAITING
                0         43     158135          0
    50 TTD$ORACLE7      db file sequential read                  WAITING
                0         43     158135          1
     8 TTD$ORACLE7      SQL*Net message from client              WAITING
              240 1650815315          1          0

I dont understand this, why the sessions waits for the session with db file
sequential read waits?

Followup:
the reason code of 0 in p3 indicates they are waiting for the block to be read -
which in this case makes sense for sid = 46 is actually in the process of
READING the block that sids 50 and 56 want.

so, you have three sessions going after the same block.
that block is not in the buffer cache.
only one of them needs to do the physical IO to read that block.
so, two of them are on buffer busy wait and the other on an IO wait.

September 24, 2003 Matt -- Thanks for the question regarding "Padded Hierarchy ", version 8.1.7
You Asked
Tom,

I have spent the last 6 hours playing with analytics to try and find an answer
to my problem.

I have a hierarchy on a department table, on 8i (so no sys_connect_by_path) and
I am trying to create a view which will give me the denormalised view to an
arbitrary number of levels... in this case 5.

The hierarchy is a standard hierarchy based on dept_id, and parent_dept_id,
within the table.

For example the hierarchy:

1
  2
    3
    4
    5
6 7 8
    9

I would want it appear to 5 levels as:

12333
12444
12555
67888
67999

Which effectively takes the node to the n'th level. (Hardcoded).

Is there any way to do this using analytics ?

Here are the object creation scripts I have used with some example data.

DROP TABLE REF_DEPT_HIERARCHY CASCADE CONSTRAINTS ;
CREATE TABLE REF_DEPT_HIERARCHY (
DEPT_ID NUMBER NOT NULL,
PARENT_DEPT_ID NUMBER,
MAX_TIER NUMBER,
PERIOD_ID NUMBER,
PRIMARY KEY ( DEPT_ID ) ) ;
INSERT INTO REF_DEPT_HIERARCHY ( DEPT_ID, PARENT_DEPT_ID, MAX_TIER, PERIOD_ID )
VALUES (
1, -1, NULL, 200211);
INSERT INTO REF_DEPT_HIERARCHY ( DEPT_ID, PARENT_DEPT_ID, MAX_TIER, PERIOD_ID )
VALUES (
2, -1, NULL, 200211);
INSERT INTO REF_DEPT_HIERARCHY ( DEPT_ID, PARENT_DEPT_ID, MAX_TIER, PERIOD_ID )
VALUES (
3, -1, NULL, 200211);
INSERT INTO REF_DEPT_HIERARCHY ( DEPT_ID, PARENT_DEPT_ID, MAX_TIER, PERIOD_ID )
VALUES (
4, 3, NULL, 200211);
INSERT INTO REF_DEPT_HIERARCHY ( DEPT_ID, PARENT_DEPT_ID, MAX_TIER, PERIOD_ID )
VALUES (
5, 4, NULL, 200211);
INSERT INTO REF_DEPT_HIERARCHY ( DEPT_ID, PARENT_DEPT_ID, MAX_TIER, PERIOD_ID )
VALUES (
6, 2, NULL, 200211);
INSERT INTO REF_DEPT_HIERARCHY ( DEPT_ID, PARENT_DEPT_ID, MAX_TIER, PERIOD_ID )
VALUES (
7, 2, NULL, 200211);
INSERT INTO REF_DEPT_HIERARCHY ( DEPT_ID, PARENT_DEPT_ID, MAX_TIER, PERIOD_ID )
VALUES (
8, 7, NULL, 200211);
INSERT INTO REF_DEPT_HIERARCHY ( DEPT_ID, PARENT_DEPT_ID, MAX_TIER, PERIOD_ID )
VALUES (
9, 3, NULL, 200211);
INSERT INTO REF_DEPT_HIERARCHY ( DEPT_ID, PARENT_DEPT_ID, MAX_TIER, PERIOD_ID )
VALUES (
10, 9, NULL, 200211);
INSERT INTO REF_DEPT_HIERARCHY ( DEPT_ID, PARENT_DEPT_ID, MAX_TIER, PERIOD_ID )
VALUES (
11, 10, NULL, 200211);

Here is the code that I have attempted .....

select Row_Pop,
       Max( decode( curr_level, 1, dept_id ) ) Level_1,
       Max( decode( curr_level, 2, dept_id ) ) Level_2,
       Max( decode( curr_level, 3, dept_id ) ) Level_3,
       Max( decode( curr_level, 4, dept_id ) ) Level_4,
       Max( decode( curr_level, 5, dept_id ) ) Level_5
from
(
select dept_id,
       curr_level,
       row_num,
       node,
       max(node) over ( order by row_num ) row_pop
from (
select dept_id,
       curr_level,
       lead( curr_level, 1 ) over ( order by row_num ),
       row_num,
       case when lag( curr_level, 1 ) over (order by row_num ) < curr_level then
       null
        else
        dept_id
       end node
from (
select dept_id,
           period_id,
           level curr_level,
         rownum row_num
from ref_dept_hierarchy
  where period_id = 200211
  connect by dept_id = prior parent_dept_id
  and period_id = prior period_id
  start with ( period_id,dept_id ) in (
            select period_id,
                   dept_id
            from (
            select dept_id,
                   period_id,
                   case when lead( curr_level, 1 ) over (order by row_num ) >
curr_level then
                   'N'
                   else
                   'Y'
                   end node
            from
            ( select dept_id,
                       period_id,
                       level curr_level,
                     rownum row_num
              from ref_dept_hierarchy
              where period_id = 200211
              start with parent_dept_id = -1
              connect by prior dept_id = parent_dept_id
              and prior period_id = period_id
            ) )
            where node = 'Y'
      ) )
      )
       )
        Group By Row_Pop

Output:

1
5    4    3
6    2
8    7    2
11    10    9    3

The problem I have is that I want everything shifted along from the left, so I
end up with ....

1  1  1  1  1
5  5  5  4  3
6  6  6  6  2
8  8  8  7  2
11 11 10 9  3

The hierarchy structure will never be of depth greater than 5.
and we said...
ps$tkyte@ORA817DEV> select nvl(to_number(substr( syscbp, 41, 10 )),dept_id) c1,
  2         nvl(to_number(substr( syscbp, 31, 10 )),dept_id) c2,
  3         nvl(to_number(substr( syscbp, 21, 10 )),dept_id) c3,
  4         nvl(to_number(substr( syscbp, 11, 10 )),dept_id) c4,
  5         nvl(to_number(substr( syscbp,  1, 10 )),dept_id) c5
  6    from (
  7  select dept_id,
  8         rtrim( ( select
  9         max(to_char( decode( rownum, 5, dept_id, null ),'fm0000000000' )) ||
 10         max(to_char( decode( rownum, 4, dept_id, null ),'fm0000000000' )) ||
 11         max(to_char( decode( rownum, 3, dept_id, null ),'fm0000000000' )) ||
 12         max(to_char( decode( rownum, 2, dept_id, null ),'fm0000000000' )) ||
 13         max(to_char( decode( rownum, 1, dept_id, null ),'fm0000000000' ))
 14                    from t x
 15                   start with dept_id = y.dept_id
 16                 connect by prior parent_dept_id = dept_id ), '/' ) syscbp
 17    from t y
 18   where dept_id not in ( select parent_dept_id from t )
 19         )
 20  /

        C1         C2         C3         C4         C5
---------- ---------- ---------- ---------- ----------
         1          1          1          1          1
         5          5          5          4          3
         6          6          6          6          2
         8          8          8          7          2
        11         11         10          9          3

beware -- i'm assuming you are using the CBO and that parent_dept_id and
dept id are NOT NULL to permit a nice fast "NOT IN", if that is wrong, you might
need to use not exists or something else to make the query:

select dept_id
  from t
 where dept_id not in ( select parent_dept_id from t )

"fast enough".  using CBO with not-null columns should result in a nice fast
anti-join (search this site for hash_aj for examples)
Review & Followup
Rating: 5
Great Technique April 01, 2003
Reviewer: Matt
Very nice technique Tom, definitely one to store away
for future use.

Thanks, and I owe you a pint next time you visit the UK.

Rating: 4
Very cool April 08, 2003
Reviewer: Winston from Canada
It took me some time to understand this query but I found it very useful.

I wonder why you need that rtrim(...., '/'), I tried to remove it and it return
the same result.

select nvl(to_number(substr( syscbp, 41, 10 )),dept_id) c1,
           nvl(to_number(substr( syscbp, 31, 10 )),dept_id) c2,
           nvl(to_number(substr( syscbp, 21, 10 )),dept_id) c3,
           nvl(to_number(substr( syscbp, 11, 10 )),dept_id) c4,
           nvl(to_number(substr( syscbp,  1, 10 )),dept_id) c5
      from (
    select dept_id,
           rtrim( ( select
           max(to_char( decode( rownum, 5, dept_id, null ),'fm0000000000' )) ||
          max(to_char( decode( rownum, 4, dept_id, null ),'fm0000000000' )) ||
          max(to_char( decode( rownum, 3, dept_id, null ),'fm0000000000' )) ||
          max(to_char( decode( rownum, 2, dept_id, null ),'fm0000000000' )) ||
          max(to_char( decode( rownum, 1, dept_id, null ),'fm0000000000' ))
                     from t x
                    start with dept_id = y.dept_id
                  connect by prior parent_dept_id = dept_id ) ) syscbp
     from t y
    where dept_id not in ( select parent_dept_id from t )
          )

        C1         C2         C3         C4         C5
---------- ---------- ---------- ---------- ----------
         5          5          5          4          3
         6          6          6          6          2
         8          8          8          7          2
        11         11         10          9          3

Followup:
ahh, a remanent of a prior attempt where instead of using fixed length decoded
fields, I was using delimited -- but I foun that too hard to substr so I went
fixe width instead.

Rating: 5
Sort data in CONNECT BY PRIOR September 24, 2003
Reviewer: Raghu
Hi Tom,

my data base is 8i , so there is no ORDER SIBLINGS BY child_field as in 9i.

How can i achieve the same results in 8i, i.e to sort data at each LEVEL.
Can you give me an example using the emp table.

Followup:
you cannot do this in any reliable fashion whatsoever.

read
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:9212348049

Rating: 5
not really having a question ... September 24, 2003
Reviewer: Gabriel from Toronto
Tom,

Regarding this <quote>beware -- I’m assuming you are using the CBO and that
parent_dept_id and dept id are NOT NULL to permit a nice fast "NOT IN"</quote>.

The way I look at this is: hierarchy implemented as a self-referencing foreign
key ==> one should have the FK constraint actually implemented … one should also
have the before insert or update row trigger to ensure dept_id and
parent_dept_id are not equal (in order to avoid “ORA-01436: CONNECT BY loop in
user data” from occurring all together) ==> parent_dept_id has to be nullable …
otherwise we’ll never be able to insert the root of the hierarchy.

Of course, some people prefer to have the mandatory parent_dept_id at the
expense of the foreign key constraint … well, sure.

My point … if I were to make a choice, irrespective whether the parent_dept_id
is (right now) implemented as optional or mandatory … I would’ve gone for the
<quote> … might need to use not exists or something else to make the query
…</quote> rather than NOT IN.

But … I didn’t have to make a choice … so here you have it, I’m just blabbing.
The SQL is otherwise great.

Gabriel

September 24, 2003 Bill -- Thanks for the question regarding "nls_date_format", version 8.1.0
You Asked
hi Tom,

i saw your FAQ regarding this but couldn't find the answer.

my initORCL.ora's nls_date_format is being ignored!

is there a way to adjust sqlplus so it uses a certain date
format always - other than by using a sql statement or a trigger
to set it automatically on login? i want to use ANSI
"YYYY-MM-DD"

my nls_lang is set to american - not sure if that's relevant.
you mentioned on windows this could be achieved by editing the
client's registry entries - is there somthing similar for linux?
i didn't see any config settings for sqlplus.

i was able to use the alter session statement. how would i
implement the trigger solution if editing the registry isn't an
option?

thanks!
Bill
and we said...
The faq reference is
http://osi.oracle.com/~tkyte/Misc/NLSDateFormat.html
Its not being ignored, its being overridden.  If you set *any*
NLS parameter -- they all default.

On 8.1, using an ON-LOGON trigger would be my preferred method
if you want this to take effect for absolutely every client that
connects.  In this fashion, regardless of the client
environemnt, you are assured the date format will be what you
want it to be until the client application itself changes it.

short of that, you can set an environment variable as you did
for NLS_LANG above.  The environment in unix is in place of the
registry in NT.  Here is an example. I started with an 8i
instance with the nls_date_format set to dd-mon-rrrr in the
init.ora.  My environment has NO nls_* parameters to start:

$ sqlplus scott/tiger

SQL*Plus: Release 8.1.5.0.0 - Production on Mon Jun 26 06:55:19
2000
(c) Copyright 1999 Oracle Corporation.  All rights reserved.
Connected to:
Oracle8i Enterprise Edition Release 8.1.5.1.0 - Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.1.0 - Production

scott@ORACLE8I> select sysdate from dual;

SYSDATE
-----------
26-jun-2000

So, here the nls_date_format from the init.ora is being used.
Lets add NLS_LANG to the environment:

scott@ORACLE8I> Disconnected from Oracle8i Enterprise Edition
Release 8.1.5.1.0 - Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.1.0 - Production

$ setenv NLS_LANG american

$ sqlplus scott/tiger

SQL*Plus: Release 8.1.5.0.0 - Production on Mon Jun 26 06:55:48
2000

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Enterprise Edition Release 8.1.5.1.0 - Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.1.0 - Production

scott@ORACLE8I> select sysdate from dual;

SYSDATE
---------
26-JUN-00

That shows we are back to the default DD-MON-YY format.  Lets
fix it:

scott@ORACLE8I> exit
Disconnected from Oracle8i Enterprise Edition Release 8.1.5.1.0
- Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.1.0 - Production

$ setenv NLS_DATE_FORMAT "dd-mon-yyyy hh24:mi:ss"

$ sqlplus scott/tiger

SQL*Plus: Release 8.1.5.0.0 - Production on Mon Jun 26 06:56:33
2000

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Enterprise Edition Release 8.1.5.1.0 - Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.1.0 - Production

scott@ORACLE8I> select sysdate from dual;

SYSDATE
--------------------
26-jun-2000 06:56:37

scott@ORACLE8I>

And thats it - the environment variable fixes the default date
format.

Again -- i'm partial to the on-logon trigger myself.
Review & Followup
Rating: 3
default nls_date_format June 04, 2002
Reviewer: A reader
Hi Tom,
Referring to the comment:
"That shows we are back to the default DD-MON-YY format"
Should it not be DD-MON-RR? If not, how did you know that it is DD-MON-YY?
Thanks

Followup:
yes, it is rr in 8i and up, yy in 8.0 and before

Rating: 5
Reader June 06, 2002
Reviewer: A reader
Tom,

The default format for Oracle is DD-MON-RR for Oracle 8i
SQL>select sysdate from x$dual;
06-JUN-02
SQL>set time on
11:39:30 SQL>

When I try to do
SQL>recover database until time '30-MAY-02 11:00:00' ;
I ger ORA-00351

However when I subsequently do

SQL>alter session set nls_date_format = 'yyyy-mm-dd hh24:mi:ss';
SQL>recover database until time '2002-05-30 11:00:00';
It is successful

How to find what does Oracle use for nls_format when doing recovery

Thanks

Followup:
The sqlplus guide documents the format used for the recover command:

UNTIL TIME date

Specifies an incomplete, time-based recovery. Use single quotes, and the
following format:
’YYYY-MM-DD:HH24:MI:SS’

Rating: 4
Reader June 06, 2002
Reviewer: A reader
SQL> alter session set nls_date_format = 'mm/dd/yyyy hh24:mi:ss';

SQL> recover database until time '05/24/2002 10:49:30' using
     backup controlfile;
ORA-01843: not a valid month

The following format works although not the documented format:
--------------------------------------------------------------
SQL> alter session set nls_date_format = 'mon/dd/yyyy hh24:mi:ss';

Session altered.

SQL> recover database until time 'may/29/2002 17:04:23'
     using backup controlfile;

recovery complete

Followup:
and did you try just

recover database until time ’YYYY-MM-DD:HH24:MI:SS’ ....

as documented?

Rating: 4
Reader June 07, 2002
Reviewer: A reader
I tried first

recover database until time ’YYYY-MM-DD:HH24:MI:SS’ ....
It works as well

Thanks

Followup:
Yes, we are happy with a : or a space in there when the date format is set to:

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set nls_date_format =
'yyyy-mm-dd hh24:mi:ss';

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select to_date( '2001-01-01 00:00:00' ) from
dual;

TO_DATE('2001-01-01
-------------------
2001-01-01 00:00:00

ops$tkyte@ORA817DEV.US.ORACLE.COM> c/1 0/1:0
  1* select to_date( '2001-01-01:00:00:00' ) from dual
ops$tkyte@ORA817DEV.US.ORACLE.COM> /

TO_DATE('2001-01-01
-------------------
2001-01-01 00:00:00

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

See -- it can (or cannot) have a : in there when the format is set to that.
Your setting of the nls_date_format has nothing to do with the recover command -
there is but one format (the one documented) -- it just happens both strings
work with it.

Rating: 5
Reader June 07, 2002
Reviewer: A reader
Tom,

Thanks for the clarification

Rating: 5
ORA-12801: error signaled in parallel query server P002 July 25, 2002
Reviewer: Sikandar Hayat Awan from Pindi Bhattian - Pakistan
I am getting the following error on one table in the same session of sql/plus .

ERROR:
ORA-12801: error signaled in parallel query server P002
ORA-01843: not a valid month

My test is as below,

Oracle 8.1.7 on WIN-NT4 SP5

scott > select sysdate from dual;

SYSDATE
----------
26/07/2002

scott > select * from emp_barcode where prod_date = '15/07/2002';
ERROR:
ORA-12801: error signaled in parallel query server P000
ORA-01843: not a valid month

no rows selected

scott > select * from emp_barcode where scan_date = '15/07/2002'
scott > /
ERROR:
ORA-12801: error signaled in parallel query server P001
ORA-01843: not a valid month

no rows selected

scott > select * from emp_incentives where prod_date  = '15/07/2002'
<<<<<<<<<<< displays records>>>>>>>>>>>>>>>>>>

scott > select * from emp_barcode where prod_date =
to_date('15/07/2002','dd/mm/yyyy');
<<<<<<<<<<< displays records>>>>>>>>>>>>>>>>>>

scott >  alter session set nls_date_format = 'DD/MM/YYYY';

Session altered.

scott > select * from emp_barcode where prod_date = '15/07/2002';
<<<<<<<<<<< displays records>>>>>>>>>>>>>>>>>>

I have checked Oracle DOCS and while searching here I found this thread so
posting here.

My questions are,
In the same session nls_date settings should be same?
Is there any problem with this table?
I was not getting this error earlier and I have not changed anything on the
server or client?

Followup:
Word of advice.

compare strings to strings.

dates to dates

numbers to numbers

Always -- forever and always.  Never compare a date to a string, a number to a
string, and so on.  Implicit conversions will *kill you* as they are here.

You know what to do here, use to_date and never compare a date to a string
again.

Rating: 5
My problem is different July 26, 2002
Reviewer: Sikandar Hayat Awan from Pindi Bhattian - Pakistan
Before posting I was thinking that what will be your answer and today found the
same answer. I know that I should use date = date and I am using it. Here my
question is this whats wrong that in one query it is giving me results while in
other an error. I am not using date = string in my applications but some time we
have to check the tables data from sql/plus so it is time consuming to write
to_date(...) while oracle will take no time to convert as it is one time query.

Here my question is that is there any problem with the table or it is an
abnormal situation as this is first time I am getting this error.

You are talking about performance but I am worried about the DB configuration or
any side effects.

Why oracle is unable to convert string to date on this table while doing on
others?

>> I know it will work date=date.

Followup:
sorry, time consuming or not -- your problem *is no different* (other then you
are willing to say "its my fault, it is too much work to type in the correct
query"

type in the correct and proper query.  period.

I don't know how many times in this lifetime I will type in:

compare

dates to dates
strings to strings
numbers to numbers

But it's in the thousands already (from way BEFORE doing SQL actually -- back in
my PL/I mainframe programming days!)

I'm not talking about performance -- I'm talking *ALWAYS* about getting the
right answer.

Rating: 2
NLS_DATE_FORMAT ANSWER? April 20, 2003
Reviewer: Nigel Evenden from United Kingdom
I followed the suggested answer to the letter, but was still unsuccessful,
having changed the Registry and changed my init.ora file.  The only thing that
seems to work is the ALTER SESSION method - not good enough!

Any more thoughts?

Thanks

Nigel

Followup:
you did something wrong -- you fixed the "wrong" NLS_DATE format.  you might
find you have many of them -- or you might find whatever tool you are using
issues an ALTER SESSION itself.

the alter session should not only be good enough -- it is far superior then
relying on a registry setting that could change on you.  If your application
needs a date format -- it should set it.  thats the only way to be sure of
anything.

Rating: 5
Set NLS_DATE_FORMAT env. variable in your Windows environment... April 20, 2003
Reviewer: Kamal Kishore from New Jersey, USA
No need to edit the registry. All you need is to create a System variable
NLS_DATE_FORMAT in your Windows environment.

For Windows2000, Right click on "My Computer" icon and click on properties. Then
click on Advanced tab and then click "Environment Variables" to create
NLS_DATE_FORMAT env. variable.

See the following example to see the effect of changing the environment variable
inside the MS-DOS session:

=========================================================
C:\>echo %NLS_DATE_FORMAT%
DD-MON-YYYY

C:\>sqlplus scott/tiger

SQL*Plus: Release 9.2.0.1.0 - Production on Sun Apr 20 22:05:05 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

SQL> select sysdate from dual ;

SYSDATE
-----------
20-APR-2003

SQL> exit
Disconnected from Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

C:\>set NLS_DATE_FORMAT=YYYY-MM-DD

C:\>sqlplus scott/tiger

SQL*Plus: Release 9.2.0.1.0 - Production on Sun Apr 20 22:05:27 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

SQL> select sysdate from dual ;

SYSDATE
----------
2003-04-20

SQL> exit
Disconnected from Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

C:\>
=========================================================

Rating: 4
July 06, 2003
Reviewer: mubeen from Kuwait
you said,
"On 8.1, using an ON-LOGON trigger would be my preferred method if you want this
to take effect for absolutely every client that connects.  In this fashion,
regardless of the client environemnt, you are assured the date format will be
what you want it to be until the client application itself changes it."

Can you please give an example of ON-LOGON trigger for that pupose.  And is
there any other environment variables can be set using ON-LOGON trigger?

Thanks

Followup:
just click on the link I provided in the answer above.

Rating: 5
what about webserver September 10, 2003
Reviewer: Susan from Watertown, MA
Default PC installation sets NLS_LANG=AMERICAN_AMERICA.WE8ISO8859P1 which
defaults to a DD-MON-YY date format.  If I change the registry setting on the
Webserver will that overide the date format being set b/c of client NLS_LANG
setting?  Thanks.

Followup:

if you are using a webserver to access the database, the client settings are
"meaningless", the client IS the webserver -- the client is NOT the pc.

Rating: 4
ORA - 01843, ORA - 01839 September 24, 2003
Reviewer: Abdul from Kuwait
hai,
I have a table

pisterm
-------
milit_no       number(38)
term_date      number(28)
......

sample data
miltno   term_date
-------------------
1000714  80449198
1000822  80109698
1000992  80138998

I need to insert/ update  term_date into some other table.
I used
TO_DATE(TO_NUMBER(99999999-TERM_DATE),'YYYYMMDD') to convert.  It updated some
records and then it is giving ORA-01843,ORA-01839 errors. I can display records
using query...

 SELECT TO_DATE(TO_NUMBER(99999999-TERM_DATE),'YYYYMMDD')
  FROM aisha.PISTERM_COUNT WHERE MILITARY_NO > 1012800

output
---------
14-SEP-87
25-JUN-94
10-JUN-90
01-SEP-94
05-DEC-95
01-AUG-92
30-JUL-86
30-AUG-97
08-MAY-76
18-NOV-97..
.........

but I cannot update using

update pisterm
set date_type_col = TO_DATE(TO_NUMBER(99999999-TERM_DATE),'YYYYMMDD')
where MILITARY_NO > 1012800;

ORA - 01843 NOT A VALID MONTH

PLEASE HELP ME

Followup:
seems pretty clear to me:

[tkyte@localhost tkyte]$ oerr ora 1843
01843, 00000, "not a valid month"
// *Cause:
// *Action:
[tkyte@localhost tkyte]$ oerr ora 1839
01839, 00000, "date not valid for month specified"
// *Cause:
// *Action:

you have some values of term_date in there such that when you do your math --
you are getting months that are not between 1..12 and days that are outside the
range for the month.

you have "bad data"

Rating: 5
Ask Tom "nls_date_format" October 14, 2003
Reviewer: curtis keller from Houston, Tx, USA
Great information.   We often forget other countries have other date formats -
and sometimes the user of the SQLPLUS - did not set the defaults.

And we failed to "convert / compare dates to dates"

September 24, 2003 Sam -- Thanks for the question regarding "Materialized Views ", version 8.1.5
You Asked
Hello Tom:

What I now so far about Materialized Views is "Materialized
views are schema objects that can be used to summarize,
precompute, replicate, and distribute data".
1. In data warehouses, materialized views are used to precompute
and store aggregated data such as sums and averages.
2. In distributed environments, materialized views (also called
snapshots) are used to replicate data at distributed sites and
synchronize updates done at several sites with conflict
resolution methods.
3. In mobile computing environments, materialized views are used
to download a subset of data from central servers to mobile
clients, with periodic refreshes from the central servers and
propagation of updates by clients back to the central servers.

Basically, what I am looking for:
1. Create materialized view is a SYNONYM for create snapshot,
then why create snapshot SQL is there?
2. Can you give me an example for:
a. How I can use a materialized view, instead of snapshot for
basic replication?
b. How I can use a materialized view in data ware housing
environment.
3. Where I can find more detail about materialized view.

Best Regards,
Sam
and we said...
1) because CREATE SNAPSHOT is the syntax that has been in use
since version 7.0 in 1993 of Oracle.  With 8i, the functionality
was greatly enhanced to encompass the materialized view concept.
 They are renaming the feature from snapshot to materialized
view.  For backwards compatibility, snapshot is and will be
supported.

2a) the create snapshot and create materialized view are
synonomous, just use create materialized view and you have done
a create snapshot and vice versa.

2b) you would create a MV that aggregates some info, for
example:

create materialized view emp_rollback
enable query rewrite
as
select deptno, sum(sal) sal
  from emp
 group by deptno;

Now, given that all the necessary settings have been done (see
the data warehousing guide for a comprehensive example) your end
users can query:

  select deptno, sum(sal) from emp where deptno in ( 10, 20)
group by deptno;

and the database engine will rewrite the query to go against the
precomputed rollup, not the details -- giving you the answer in
a fraction of the time it would normally take.

3)
http://technet.oracle.com/doc/oracle8i_816/server.816/a76994/toc.htm
has it all.
Review & Followup
Rating: 4
Materialzed Views March 11, 2001
Reviewer: Sam from CA, USA

Rating: 2
Only part of the example worked March 25, 2002
Reviewer: Pranav Shah from CT,USA
I created a materialized view, but when I write a select statement with a where
clause it looks at the table rather than the materialized view.

Followup:
And your example is 100% missing so I have no clue what you tried, what you did.
 Sorry.

Rating: 2
Materialiased view March 26, 2002
Reviewer: kit from england
When u say it would give u results quicker do you mean as the snapshot was taken
during off peak hours

If yes, if the user wanted to include the latest info how would that work.

I mean how can it know to look at the snapshot or direct table

many thanks

Followup:
you is a word, not a letter (does anyone else find it really hard to read text
with "u" instead of you?  I sure do.... anyway...)

I mean it is quicker to query against:

create materialized view emp_rollback
enable query rewrite
as
select deptno, sum(sal) sal
  from emp
 group by deptno;

then the plain emp table if your query is:

select deptno, sum(sal) from emp where deptno in ( 10, 20) group by deptno;

That is what i meant.  MV's are designed for data warehouses where the data is
relatively static.  The currency of data issue rarely comes up.  When it does
you can sometimes use "on commit refresh" to maintain the MV like you would an
index (automagically) or you would not use the MV to answer the question.

Rating: 5
RE: does anyone else find it really hard... March 26, 2002
Reviewer: Lester Burnham from Orlando, FL
A resounding, "YES!"

It drives me round the bend when I have to wade through a pool of "u", "ur", and
other such "abbreviations".  Maybe this is why the search facility on AskTom (or
is that AT?) is so under-utilized?  If someone can't be bothered to type a
complete word, then how can they be bothered to go through all the trouble of
clicking on the "Search/Archives" tab and typing in a search phrase.  I love
reading AskTom, but if I have to read through one more "I upgraded to 9i, didn't
read the documentation, and now I can't find svrmgrl, is this a bug in Oracle?"
type question again...  OK, moment over.

Rating: 4
Quick comment - not really related ... March 27, 2002
Reviewer: Chris Slattery from Dublin, Ireland

This abbreviated comments u=you ur = your is a terrible habit we are getting due
to the widespread usage of GSM SMS messaging . Hasn't caught on in USA yet...
Just wait !

Rating: 3
materiliased view March 27, 2002
Reviewer: kit from england
Sorry for the U word.

Rating: 5
Distinct values August 07, 2003
Reviewer: Alvin from Philippines
Because of faulty designs and Developers staunchily defending their designs i
need a work around for a certain query.

I need to query all distinct values for a certain table;

Create table t as
select substr(object_name,1,1) a ,1 b from all_objects;

Insert into t as select substr(object_name,1,1), 2 b from all_objects;

Insert into t as select substr(object_name,1,1), 3 b from all_objects;

create materialized view mv_t
enable query rewrite
as select distinct(a),b from t;

select distinct(a)
from t
where b=3;

I know that MV_T can answer the above query but oracle seems to ignore this
view. Dimensions don't help because there is no relationship between columns A
and B.

It full scans table t.

I think of Indexes as Small virtual tables with rowid's so..

create index i_t on t ( b,a ) compress 1;

works much much faster but it has as many rows as the base table and i know that
mv_t has much much less rows and should be able to answer the above query.

1 .  Is index really my solution or its possible for me to make use of
Materialized views in this case?

Is it because Distinct is not considered an aggregate by oracle ?

Followup:
I added a data column to T so as to make it a tad "wider" (could be yours was
too "narrow" and the cost of full scanning it wasn't very high)

Also, I did the necessary query rewrite settings (don't know if you did)

Also, I supplied my version :) which is Oracle9iR2

ps$tkyte@ORA920> Create table t as
  2  select substr(object_name,1,1) a , b, rpad('x',50,'x') data
  3    from all_objects, (select 1 b from dual union all select 2 b from dual
union all select 3 b from dual );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create materialized view mv_t
  2  enable query rewrite
  3  as select distinct a,b from t;

Materialized view created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter session set query_rewrite_enabled=true;

Session altered.

ops$tkyte@ORA920> alter session set query_rewrite_integrity=trusted;

Session altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA920> analyze table mv_t compute statistics;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select distinct a
  2    from t
  3   where b = 3;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=29 Bytes=87)
   1    0   SORT (UNIQUE) (Cost=4 Card=29 Bytes=87)
   2    1     TABLE ACCESS (FULL) OF 'MV_T' (Cost=3 Card=41 Bytes=123)

ops$tkyte@ORA920> set autotrace off

and it worked... It worked with your extra/unneeded () about A.  Distinct is a
set operator -- not a function!

I did this in 817 as well -- I've got a feeling you are missing the alter
sessions (that you would normally put as init.ora's)?

Rating: 3
September 22, 2003
Reviewer: Vipin from NYC
Hi tom,

First of all sorry for not posting this query in a new thread, but I was waiting
for very long and was not able to grab a new thread till now.

I have a very peculiar requirement here. I know that the way it is designed to
operate is a little weird but I don't have a choice here (we have a lot of
webservices savy guys around;)).

We have two applications :-

System  A - One which is supposed to collect some personal data of employees
i.e. Emergency contacts
System  B - Other which has to use this emergency contact number from the System
A for its own processing. This System  will call a webservice which will fetch
the emergency contact from System  A and give us the results. This emergency
contact can be modified in System  A as well as System  B. Upon modification of
this data in System  B the webservices will update this data in System  A(so we
need not bother about this.)

Now the problematic area is when the reporting occurs in System  B. For
reporting purpose we have materialized view on this emergency contact data in
System  A at System  B, but any newly modified data will not be available in
System  B through materialized view until the next refresh unless otherwise the
materialized view from System  A to System  B is refreshed ON COMMIT. But I
think this will slow down the commit process and hence will slow down the DML on
Emergency contact data in System  A.

So the idea which I suggested is to make the materialized view refresh (FAST
refresh) ON DEMAND. The DML which modifies the data at System A would call
DBMS_JOB which will submit a job for refreshing materialized view to System B,
using DBMS_MVIEW.REFRESH, hence this process would be asynchronous. So even if
the modification is due to System A users or System B users(using web services.)
we would have all the latest data in MVIEW.

Please correct me if this is the wrong way to approach this problem. Would you
prefer some other way, if so please advise.

Followup:
you cannot refresh on commit over databases -- only IN a database.

why not (in order of "smartness" or "best practice")

a) combine these two databases.  you only need one.

b) use a dblink and just query the data, don't copy it.

distributed complexity -- is just that -- distributed complexity.

Rating: 4
September 22, 2003
Reviewer: Vipin
Exactly what I suggested them at first short, but I was cut short beacuse many
non technical, policy wise issues associated ;)

Sorry to constrain you like this, but If doors are closed for those two
approachs what ever you said, would you nod for my approach.

Thanks for your advise.

Followup:
i would not want to submit a job for every row update, no.

just refresh on a "fast enough" cycle.

Rating: 4
September 22, 2003
Reviewer: Vipin
Thanks Tom,

But could you please explain why you advise not to refresh asynchronously using
SUBMIT for each update?

Followup:
you update 5 rows either AT the same time or at ABOUT the same time.

you would queue 5 jobs.

only one of which needs to run, the other 4 just eat resources.

Rating: 4
September 22, 2003
Reviewer: A reader
Dear Tom,

I know this is very vague of asking this question, but could you please tell
something about this. How many bytes of data is safe to be refreshed (without
affecting performance)between any two databases, if the refresh interval is say
about 15 mins in a fast refresh?

Followup:
you are right

it is vague.

"safe" is not quantifiable

"without affecting performance" isn't totally clear either.

you would want to benchmark to see the effect on YOUR hardware with your loads
using your data under your working conditions.

anything else is, well, hypothetical and not very useful

Rating: 4
Create snapshot & query rewrite September 22, 2003
Reviewer: LCLN from Brasil
Hi, Mr. Kyte.

Is it possible to use "enable query rewrite" in a "create snapshot" command?
And yes, it's hard to read when someone uses 'u' instead of 'you'. Another
annoying thing is the lack of punctuation.

Thanks in advance.
A reader from Brazil.

Followup:
ops$tkyte@ORA920LAP> create table t as select * from scott.emp;

Table created.

ops$tkyte@ORA920LAP> alter table t add constraint t_pk primary key(empno);

Table altered.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create snapshot t_snap
  2  enable query rewrite
  3  as
  4  select job, count(*) cnt from t group by job;

Materialized view created.

snapshots are just "mv's" in disguise.  query rewrite only works on a single
database of course (the base object must exist in the same database as the
snapshot)

Rating: 4
Create snapshot & query rewrite September 22, 2003
Reviewer: LCLN from Brasil
Hi, Mr.Tom.

Please do not consider my last question.

Best regards.

Rating: 4
Use materialized views or table September 23, 2003
Reviewer: Michael from San Diego, CA
Hello Tom,

I have two tables SAMPLE_RECEIPT(ID,QTY) and
SAMPLE_USAGE(SAMPLE_RECEIPT_ID,QTY). I have two methods for computing remaining
quantity

1.
Create MV for both tables summing QTY by ID.
Create view to determine difference in QTY by ID.

2.
Create a new table SAMPLE_REMAINING(SAMPLE_RECEIPT_ID,QTY)
If a new sample is received, insert record into this table.
Otherwise, perform math on this table accordingly for the sample.

What do you think is a better solution? I am using refresh on commit for MV's.

Thank you.

Followup:
what is wrong with using sample_receipt and sample_usage and just joining when
you need to know?

Rating: 3
Materialize Views September 24, 2003
Reviewer: Michael from San Diego, CA
Here is a sample data.

create table sample_receipt(id number, qty number);
create table sample_usage(sample_receipt_id number, qty number);

insert into sample_receipt values (1,10);
insert into sample_receipt values (1,15);
insert into sample_receipt values (2,20);
insert into sample_usage values (1,5);
insert into sample_usage values (1,2);
insert into sample_usage values (1,1);
commit;

MDINH@test> select a.id, nvl(asum-bsum,0) qty
  2  from
  3    (select id, sum(qty) asum
  4    from sample_receipt
  5    group by id) a,
  6    (select sample_receipt_id, sum(qty) bsum
  7    from sample_usage
  8    group by sample_receipt_id) b
  9  where a.id=b.sample_receipt_id(+);

        ID        QTY
---------- ----------
         1         17
         2          0

MDINH@test>

Is this what you mean by why not just create a query?

How is performance affected if there are millions of records in the tables since
the calculation is done every time?

I am a firm believer of not storing results in the database when it can be
calculated on the fly. But in this case I am not sure.

Thank you.

Followup:
do you plan querying all of the rows every time -- or do you

how many qty's go into a sum on average (eg: how many records might you avoid
scanning if you pre-aggregated)

Rating: 3
My query was wrong September 24, 2003
Reviewer: Michael from San Diego, CA
select a.id, decode(bsum,null,asum,asum-bsum)
from (select id, sum(qty) asum
from sample_receipt
group by id) a,
(select sample_receipt_id, sum(qty) bsum
from sample_usage
group by sample_receipt_id) b
where a.id=b.sample_receipt_id(+);

Rating: 5
September 25, 2003
Reviewer: A reader

September 25, 2003 Alan -- Thanks for the question regarding "Top 10 Performance Indicators", version 817
You Asked
Hi Tom,

I would like to set up an automated performance monitoring system for my 20 or
so Oracle 817 databases.  I suspect that if I could monitor about 10 or so
statistics and keep them in line (ie. DB Buffer Hit rate of +90%) then I will be
in pretty good shape.

In your opinion what would the the most imporant performance statistics that I
should be watching (obviously, your answer doesn't have to be exactly 10)?

Thanks in advance,

Alan
and we said...
There is only one performance indicator I care about.

Is the thing running sufficiently fast, is it meeting or exceeding its designed
response times.

If so, I could really care less if the buffer hit ratio was 0%.

All of these numbers everyone looks for -- are mostly meaningless unless taken
in context.  In many high end OLTP systems -- a cache hit of 90% would be
unachievable, consider how you process.  You insert orders into a table.  You
read the lookup tables but thats about it.  You read a customer record once and
then not again for a very very long time.  Re-reading the same information isn't
likely to happen.

In many high end DSS (warehouse) systems -- a cache hit of 90% would be
unachievable, again considering how you process....

So in these cases -- cache hit would be a lousy indicator of performance (but
the rule of thumb is "cache hit is the single most predicative indicator of
performance"....)

I suppose the one ratio that I would pay attention to is the hard/soft parse
ratio.  in an oltp system the percentage of hard parses should rapidly goto zero
so that you are not hard parsing at all.  On a warehouse, this may or may not be
true depending on how skewed the data is and whether you need different plans
given different criteria.  In 9i however I would expect this ratio to go away as
you should use bind variables even in datawarehouses there since the optimizer
will "peek" at the binds when picking a plan.

Probably not the answer you were looking for but in my opinion -- in order to
gauge if your databases are performing OK, you need to have custom metrics --
specific to the instance and applications running therein -- that you measure
to.  What is good for one instance will be terrible for another.  You should
measure response time, not all of the knobs and dials (they are what you might
look at when you discover you have a problem...)
Review & Followup
Rating: 5
peek the bind variables December 30, 2001
Reviewer: A reader
hi tom

in 8i there are restirction using histograms, query rewrites with bind
variables, is this because the execution plan is generated before the variables
are bind? How does 9i overcome this?

And ratio doesnt mean anything these days! Waits/Latch statistics are probably
much better indicators for perfomance problems

Followup:
in 8i and before, binds are not taken into consideration when producing a query
plan.  In 9i, the CBO will peek at binds when developing the query plan and the
plan can change.

Start with this schema:

ops$tkyte@ORA9I.WORLD> create table t
  2  as
  3  select rownum x, rpad( '*', 150, '*' ) y from all_objects;

Table created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> insert /*+ append */ into t select -1, y from t;

18895 rows created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> create index t_idx on t(x);

Index created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> analyze table t compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns
  5  /

Table analyzed.

then, run a script like this:
variable x_one number
variable x_neg_one number

exec :x_one := 1
exec :x_neg_one := -1

set termout off
alter session set sql_trace=true;
select * from t where x = :x_one;
select * from t where x = :x_neg_one;

the resulting tkprof shows:

select *
from
 t where x = :x_one

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          4          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.01          0          4          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 103

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID T
      1   INDEX RANGE SCAN (object id 20780)

********************************************************************************

select *
from
 t where x = :x_neg_one

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     1261      0.27       0.20        118       2110         12       18895
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1263      0.27       0.20        118       2110         12       18895

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 103

Rows     Row Source Operation
-------  ---------------------------------------------------
  18895  TABLE ACCESS FULL T

different plans for the nearly identical query......

As for waits and latches, you cannot look at them in ISOLATION either.  When
someone says to me "hey, I got XXXX latches, how do I fix that" -- i always say,
"well, I had to stop at XXXX red lights on my last trip, was that bad?"

If XXXX is large, they say "bad", but then I say "well I drove from the east
coast of the US to the west coast, it was really pretty good".

If they say "good", I say "but I only drove a mile and it took me hours"....

In order to see if 1,000,000 latches is "bad" -- you need to consider what you
were doing, what the period of observation was (if it is weeks -- who cares, if
it was minutes, you might have a problem), if it is unavoidable due to the
nature of your problem -- it might be ok).....

Rating: 2
Yes, but ... December 30, 2001
Reviewer: Alan from San Diego, CA
Hi Tom,

I understand your answer even though it wasn't what I was hoping for.
Certainly, if the customers are happy with the performance then you probably
shouldn't get heavily into an academic tuning exercise (fun, though it may be).
However, let me take another stab at what I was trying to get at with an
example:

Let's say my pickup truck is advertised to get 20 miles to the gallon.
Obviously, that will vary with driving patterns, load and age of the truck.  Now
if the truck continues to perform it's job but I discover that it is only
getting 3 miles to the gallon shouldn't that raise a red flag?  Maybe I'm
carrying heavy loads up steep hills or maybe I need a tune up.

My Oracle goal is that I would like to set up a monitoring methodology that
would allow me to watch the basic health of the database.  I would like to do
things like:

     - Check availability of machines and databases.
     - Check available space.
     - Check for O/S swapping or paging.
     - Check performance of some key, custom, long
       running reports and processes.
     - Check some key Oracle statistics like buffer hits,
       dictionary hits, sorts to memory/disk, ect.

What do you think?

Thanks,

Alan

Followup:
Your analogy is good  (excellent actually) -- but just change "miles per gallon"
into transactions/unit of time.  Now you have MY analogy.  You should have setup
some measurable performance metric (like miles/gallon) beforehand -- then you
can report performance.

Your analogy is perfect actually since different vehichles get different MPG --
and we expect it.  A tractor trailer will be very different from a pickup, from
an SUV from a BMW, from a Toyota -- and we expect them to be different.  For
this reason, reporting back simple ratios/metrics are pretty much meaningless.

You should be measuring things like "my response to a query like this should be
0.5 seconds or less -- if it is not, something is wrong".  You now have
something you can in fact measure and report on.  Just like "i should be getting
25 MPG".

Once you find the query doesn't return in 0.5 seconds -- then you need to look
at the system and figure out whether you a) need a tune up or b) have been
carrying heavy loads up steep hills.

To check for the things you listed there (most of which are not ratios) tools
like OEM (Oracle Enterprise Manager), statspack, or even just the SNMP MIBs for
use with any monitoring tool would be appropriate.  That'll cover availability
of databases, available space and metrics like buffer hits/dictionary hits/sorts
to memory/disk.

To monitor your OWN reports/processes, we have DBMS_APPLICATION_INFO and the
ability to measure long runnging processes in v$session_longops (i have a fairly
extensive chapter on this in my book, search for dbms_application_info on this
site for other examples).

To monitor the OS, you would be looking at some system management tool or
writing your own with sar/vmstat/iostat on unix or perfmon on NT.
This is very different from getting the top N performance indicators though.
You should instrument your applications so that you monitor your apps
performance over time.  This is the KEY factor in monitoring your system. The
rest of the information is just NUMBERS, measuring your response time (compare
actual against expected) thats the ONLY thing that counts.  For example on
asktom.oracle.com, I measure every single request and have a screen that easily
tells me stuff like (for this sunday) in the last 24 hours:

I generated 2,761 pages
This is an average of 0.03 pages/second
From 355 different ip addresses
each ip address viewed about 8 pages.
There were 449 USERS (different browser sessions) that viewed about 6pages/each

The average elapsed time per page was 0.35 seconds with the slowest page
taking 2.18 seconds and the fastest taking 0.13

Now, Sunday is a little slow -- but during the week I check this page from time
to time to see how we are doing.  If the avg/max times start to go up -- i have
a problem.  I have this information stored historically and can tell you exactly
how I was doing performance wise yesterday -- two months ago -- one year ago.
All of the applications I work on have this "feature".  This is something I
think every application should add -- sure, the extra insert adds a little
overhead but hey -- the wealth of information I have makes pinpointing
performance issues TRIVIAL and allows me proactively monitor my system.  None of
the metrics I report back to myself however have anything to do with the
database itself -- those are things I would start to look at AFTER finding there
was an issue.

Rating: 4
How do I draw the Application Performance Metric? May 14, 2002
Reviewer: shivaswamy from Dallas, TX
Tom,

It was a very nice topic and article.

I manage a big (800+ G)database for a small shop. Local clients. DSS nature.
Long running jobs. New, unstabilized(changing) custom application. When do I
say, the application is doing good..?I keep tab of the top 10 written and read
datafiles with their response time on a day-to-day basis and store it. Anything
else would you care to suggest..? I plan to update 500,000 rows on a test table
to measure the performance, daily to give me an yardstick to measure..

Lookforward to your invaluable input.
Regards,
Shivaswamy

Rating: 5
memory issue July 10, 2003
Reviewer: Mike from TX
I used top on Solaris:

Memory: 10G real, 175M free, 15G swap in use, 858M swap free

does it imply the machine still has 858M 'memory' to be allocated or just 175 M?

TIA

Followup:
impossible to say since solaris steals as much ram as it wants to do OS file
system buffering and gives it back when applications need it.

So, you might very very well have 9.9gig free

Rating: 5
In my database, "plans for the nearly identical query" are the same July 10, 2003
Reviewer: Paul Druker from Fairfax, VA USA
Tom,

I ran your example in our 9.2.0.3.0 database, and, in fact, both plans were
identical.

select *
from
 t where x = :x_one

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          4          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.00          0          4          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 50  (PDRUKER)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID T (cr=4 r=0 w=0 time=146 us)
      1   INDEX RANGE SCAN T_IDX (cr=3 r=0 w=0 time=102 us)(object id 60991)

********************************************************************************
select *
from
 t where x = :x_neg_one

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     1407      0.34       0.34          0       3324          0       21088
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1409      0.34       0.34          0       3324          0       21088

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 50  (PDRUKER)

Rows     Row Source Operation
-------  ---------------------------------------------------
  21088  TABLE ACCESS BY INDEX ROWID T (cr=3324 r=0 w=0 time=209221 us)
  21088   INDEX RANGE SCAN T_IDX (cr=1449 r=0 w=0 time=97363 us)(object id
60991)

********************************************************************************

Now, according to the manual, "The CBO peeks at the values of user-defined bind
variables on the first invocation
of a cursor. This feature lets the optimizer determine the selectivity of any
WHERE clause condition, as well as if literals have been used instead of bind
variables. On
subsequent invocations of the cursor, no peeking takes place, and the cursor is
shared, based on the standard cursor-sharing criteria, even if subsequent
invocations use different bind values."

So, actually, I'd expect plans to be the same. Or, possibly, have different plan
for both queries if
select * from t where x = :x_neg_one
statement is executed first and it's not in the library cache yet.

Can you explain why plans are the same in my database? Is is possible because of
difference in some init.ora parameters? cursor_sharing =TRUE in my database.

Regards,
Paul

Rating: 5
Stats Gathering September 19, 2003
Reviewer: Steven Cooper from England
Tom,

Any chance of seeing how you implement your stats gathering?

I'm thinking about implementing this in a new application that I'm writing and
was wondering how you have done it.

Kind Regards
Steven

Followup:
depends.  for many things

- setting all tables to monitoring
- using AUTO with dbms_stats

so, basically, two calls to dbms_stats, one to set all tables to monitoring and
the other to gather with auto.

is great.  for really big or complex (large partitioned tables, loads into DW's,
etc) -- you think about it much more.  you would probably do it segment by
segment

Rating: 5
Interesting September 22, 2003
Reviewer: Steven Cooper from England
Tom,

Not quite what I had in mind, my question was a little ambiguous though.  What i
meant was you mention above how you gather info (stats) about your applications
performance over time i.e. this screen took 1 minute to return some data usually
its 10 seconds etc..  I wanted to know how you implement this.  The way I'm
thinking i.e. Keep it Simple , is to have three tables,
APPS (which holds which application the stats are for and a description of the
App ),
STATS (which holds which stat and a description of that stat) and
APP_STAT (which holds App_Id,stat_Id,user,date,start_time,end_time)

In the application I would have two calls to a stat_package , one to get the
starting time and populate a pl/sql table with all the other info except
end_time.  The second call would get the end_time and then insert and commit
(autonmous transaction) into the app_stat table.

How do you implement yours?

Kind regards
Steven

Followup:
page starts with an insert/commit
page ends with an update/commit

record logged is simply:

flows_010400@ASKUS> desc WWV_FLOW_ACTIVITY_LOG1$
 Name                                                  Null?    Type
 ----------------------------------------------------- -------- --------------
 TIME_STAMP                                            NOT NULL DATE
 COMPONENT_TYPE                                                 VARCHAR2(255)
 COMPONENT_NAME                                                 VARCHAR2(255)
 COMPONENT_ATTRIBUTE                                            VARCHAR2(4000)
 INFORMATION                                                    VARCHAR2(4000)
 ELAP                                                           NUMBER
 NUM_ROWS                                                       NUMBER
 USERID                                                         VARCHAR2(255)
 IP_ADDRESS                                                     VARCHAR2(4000)
 USER_AGENT                                                     VARCHAR2(4000)
 FLOW_ID                                                        NUMBER
 STEP_ID                                                        NUMBER
 SESSION_ID                                                     NUMBER
 SECURITY_GROUP_ID                                     NOT NULL NUMBER

some pages do their own auditing as well - for example, the search page records:

ask_tom@ASKUS> desc WWC_ASK_QUESTION_SEARCH_LOG$
 Name                                                  Null?    Type
 ----------------------------------------------------- -------- ---------------
 ID                                                    NOT NULL NUMBER
 SEARCH_CRITERIA                                                VARCHAR2(1000)
 EMAIL                                                          VARCHAR2(255)
 ACCESS_DATE                                                    DATE
 REMOTE_ADDR                                                    VARCHAR2(255)
 HTTP_USER_AGENT                                                VARCHAR2(255)
 HTTP_REFERER                                                   VARCHAR2(255)
 HOST_NAME                                                      VARCHAR2(255)

so I can see what people are querying for and what pages are submitting searches
to me.  The RSS page is another one with an audit trail

Rating: 5
one question September 22, 2003
Reviewer: A reader
"flows_010400@ASKUS> desc WWV_FLOW_ACTIVITY_LOG1$
 Name                                                  Null?    Type
 ----------------------------------------------------- -------- --------------
...
ELAP                                                           NUMBER"

Is the "elap" this the elapsed time since the last timestamp from the previous
record?

Followup:
it is the amount of time the page took to generate

when we start, we call dbms_utility.get_time
do the audit insert
do the page
get the time again
update that audit trail record with (STOP-START) -- 1/100th of seconds..

Rating: 5
thanx! September 22, 2003
Reviewer: A reader

Rating: 3
How would you mesure this? September 23, 2003
Reviewer: msc
Tom,

your solution is nice and simple but it seems limited in use.

Let look rather typical [reporting] case in our system.

Setup:

-- performance log table...
create table LOG (A varchar2(200));

-- package containing report
create or replace package TEST_CUR is
  type TRefCur is ref cursor;

  procedure Open_cur(cur OUT TRefCur);
end;
/

create or replace package body TEST_CUR is

  procedure Open_cur(cur OUT TRefCur) is
  begin
    insert into LOG values ('Start time: ' || to_char( sysdate, 'YYYY-MM-DD
HH24:MI' ));
    commit;

    open cur for
      select OBJECT_NAME
      from ALL_OBJECTS
      where ROWNUM < 50;

    insert into LOG values ('End time: ' || to_char( sysdate, 'YYYY-MM-DD
HH24:MI' ));
    commit;
  end;
end;
/

Now lets test:

var curResult REFCURSOR

set arraysize 1

alter session set SQL_TRACE = TRUE;

begin
  TEST_CUR.Open_cur(:curResult );
end;
/

print curResult

alter session set SQL_TRACE = FALSE;

Now lets examine trace file (only partial):
=====================
PARSING IN CURSOR #7 len=45 dep=0 uid=59 oct=47 lid=59 tim=183619990
hv=420908503 ad='411cfc8'
begin
  TEST_CUR.Open_cur(:curResult );
end;
END OF STMT

...

PARSING IN CURSOR #8 len=76 dep=1 uid=59 oct=2 lid=59 tim=183619995
hv=4020378713 ad='31e88d0'
INSERT INTO LOG VALUES ( 'Start time: ' || TO_CHAR(SYSDATE,'YYYY-MM-DD')   )
END OF STMT
PARSE #8:c=1,e=5,p=0,cr=3,cu=0,mis=1,r=0,dep=1,og=4,tim=183619995
EXEC #8:c=0,e=0,p=0,cr=1,cu=8,mis=0,r=1,dep=1,og=4,tim=183619995

...

--> HERE is ref cursor opened.

PARSING IN CURSOR #9 len=56 dep=1 uid=59 oct=3 lid=59 tim=183620014
hv=2527982697 ad='32a0838'
SELECT OBJECT_NAME   FROM ALL_OBJECTS  WHERE ROWNUM < 50
END OF STMT
PARSE #9:c=12,e=18,p=2,cr=18,cu=0,mis=1,r=0,dep=1,og=4,tim=183620014
EXEC #9:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=183620014

...

--> HERE we update final time

PARSING IN CURSOR #11 len=74 dep=1 uid=59 oct=2 lid=59 tim=183620014
hv=749481542 ad='31e4244'
INSERT INTO LOG VALUES ( 'End time: ' || TO_CHAR(SYSDATE,'YYYY-MM-DD')   )
END OF STMT
PARSE #11:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=1,og=4,tim=183620014
EXEC #11:c=0,e=0,p=0,cr=1,cu=3,mis=0,r=1,dep=1,og=4,tim=183620014

...

--> HERE the report data is fetched (actual work)

FETCH #9:c=0,e=3,p=4,cr=7,cu=4,mis=0,r=1,dep=0,og=4,tim=183620092
...
FETCH #9:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=183621284

--> THIS would be right point to mesure end time
=====================

This example would measure only parse and execution time(approx time to get the
first row) but should measure time to get the *last* row in report.

When first page of report is displayed on user computer screen then [s]he is
probably pouring coffee and will print report later. And we cannot measure time
in client application as there is user time included.

Is there any means to measure automatically time spent in database (except
trace)? Or any other way to get reliable data on reporting performance if using
ref cursors?

Followup:
so, why did you do it that way??? i wouldn't have.

i use mod_plsql.

everything, everything is done in plsql.

I would have timed the open, fetch and close.

I would never time the open -- that isn't relevant.

you have to INSTRUMENT THE CLIENT.

in my case, the client is (smartly) PLSQL, so it is all very easy

if your client is not plsql, you'll have to move the instrumentation OUT there.

Rating: 3
It is client-server application September 24, 2003
Reviewer: msc
Tom,

it is client-server application not web app (no mod_plsql, no html page to
construct). We have all SQL in database packages (open cur for ...) no single
SQL query in client executable. We just call the procedure and fetch ref cursor.

Measuring at client side would be ok but... this is non-modal (MDI) application
and user can do other things (even another report) before printing complete
report out.

Followup:
then that is STILL where the timing/instruementation needs to take place -- as
well as in the database.

as you noted "open" = "fast"

"fetch" = "where the work is done"

if you want response time metrics, your client will be doing that, it is the
only one that can.

September 25, 2003 Dmitry -- Thanks for the question regarding "SYSDATE behavior in SQL and PL/SQL", version 8.1.7
You Asked
Hello,

My quess: there are two different SYSDATE functions – one defined in STANDARD
package and another one somewhere ‘inside’ Oracle.

Example:

SQL> select * from dual;

D
-
X

SQL> select sysdate from user_objects where rownum=1;

SYSDATE
---------
15-OCT-01

SQL> delete from dual;

1 row deleted.

SQL> select * from dual;

no rows selected

SQL> select sysdate from user_objects where rownum=1;

SYSDATE
---------
15-OCT-01 - it still returned the date !

Which means that sysdate did not read dual, Oracle uses its ‘inside’ function in
SQL statements.

Another example – (this is one of your examples, I found it on your site in one
of the threads regarding sysdate):

SQL> declare
2 v_date date := sysdate;
3 begin
4 null;
5 end;
6 /

PL/SQL procedure successfully completed.

SQL> delete from dual;

1 row deleted.

SQL> declare
2 v_date date := sysdate;
3 begin
4 null;
5 end;
6 /
declare
*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at "SYS.STANDARD", line 586
ORA-06512: at line 2

So, now oracle tries to read from dual – which means it uses standard.sysdate
function in PL/SQL constructions.

But the conclusion about two SYSDATE functions is just my guess.

Could you explain so different behavior of sysdate function in SQL and PL/SQL?

Thank you,
and we said...
Actually -- what you just demonstrated is that there is ONE sysdate function and
its in SQL.

declare
  x date := sysdate;
begin
   ....

is really:

declare
   x date;
begin
   select sysdate into x from dual;
   ....

We must GOTO the database to get the sysdate, plsql doesn't have its "own"
sysdate, it gets it from the database (same for USER as well).

Needless to say -- don't do that to DUAL -- you are just asking for trouble!
Review & Followup
Rating: 4
October 17, 2001
Reviewer: A reader
Tom

If we have to use sysdate numerous number of times in our program, how do you
suggest we do it.

Why is it expensive to select it from dual, every time we need it.

Thank you

Followup:
Well it depends (how you use it in your app).  In my web based stuff (plsql htp
pages, like here on asktom), we typically code:

create package my_pkg
as
   g_sysdate date default sysdate;
   ....
end;
/

and use g_sysdate everywhere so the date/timestamp we use is ALWAYS the same for
all inserts/updates and such.  So, rather then:

   insert into questions .... values ( sysdate, ..... )

we
   insert into questions .... values ( g_sysdate, ..... )

and when we reference the current date in the code, we reference g_sysdate.
This provides two things for us:

1) its a little faster (more on that in a minute)

2) we get the SAME date on all inserts.  We make each page fast, but they can
execute across 2 seconds (from 12:01:01.85 to 12:01:02.15 for example) and I'd
like all timestamps related to that page to be the same 12:01:01.

On a client server app -- this does not work as well since you might be
connected for hours or days even.  A static date doesn't fit.  So, it depends on
your needs.

As for the expense...

Every time you go from PLSQL to SQL there is a context switch.  You go from the
PLSQL runtime to the database.  No network or anything, just a context switch
from one part of code to another.  It is just a relatively expensive operation
-- something to be avoid if and when you can.

Rating: 5
Some options for faster SYSDATE October 17, 2001
Reviewer: Connor from UK
1) Replace DUAL with an IOT or a view on X$DUAL - you should get approval from
Oracle support before doing this.  This makes the select-from-dual slightly
faster.

2) See
http://www.oracledba.co.uk/tips/quick_date.htm
for a "sysdate" that does not use the SQL engine

3) Probably most effective is to have a look at why you are getting sysdate so
frequently.

Followup:
Not to be a contrarian HOWEVER (i'm going to be...)

1) you'll never get that approval.  DUAL is too embedded all over the place.
Strange things could happen (no, i have no examples -- just past experience
tells me making a seemingly innocent change like this can lead to all kinds of
bad things).

2) that sysdate, in addition to having the wrap problem every 248 days (you
don't need a LONG session, you just need to start your appliation on that 248'th
day -- once a year, you have a terrible BUG in your application as the number
will wrap and your dates will be WAY..... off.  Thats like having a Y2k bug once
or twice a year every year for the rest of your life!) has a problem in that the
dbms_utility.get_time clock is NOT syncronized with the system clock.

Hence, using the sysda.te function we'll find:

ops$tkyte@ORA717DEV.US.ORACLE.COM> begin
  2          for i in 1 .. 10000
  3          loop
  4                  if ( sysdate <> sysda.te )
  5                  then
  6                          dbms_output.put_line( 'found one ' || i );
  7                          exit;
  8                  end if;
  9      end loop;
 10  end;
 11  /
found one 3479

PL/SQL procedure successfully completed.

ops$tkyte@ORA717DEV.US.ORACLE.COM> /
found one 4077

PL/SQL procedure successfully completed.

The sysda.te and sysdate function will differ by upto 1 second from eachother as
the get_time function rolls seconds on a different beat then sysdate does.  This
could cause issues for some applications that rely on sysdate increasing (if
they use a mix of sysda.te and sysdate as most will)

3) best answser. I ran the tests at the above referenced page and found:

a) sysdate: 12.14 seconds
b) sysda.te: 10.74 seconds
c) NO TRIGGER, use insert into stamp (x,date_col) select rownum, sysdate....
   0.64 seconds

If performance were the overreaching goal here, you would not have SQL call
PLSQL in the form of a trigger to do something that is more easily done in the
SQL in the first place.

Rating: 4
Reader October 17, 2001
Reviewer: Reader from USA
Tom,

What is the theory behind the following occurrance
SQL> insert into dual select * from dual;

1 row created.

SQL> commit;

Commit complete.

SQL> select * from dual;

D
-
X

SQL> select count(*) from dual;

  COUNT(*)
----------
     2

Thanks

Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1562813956388
....

Rating: 5
Better in 9i October 17, 2001
Reviewer: Connor from Scotland
In 8.1.7

SQL> select * from dual;

Statistics
----------------------------------------------------------
          4  db block gets
          1  consistent gets

In 9.0.1

SQL> select * from dual;

Statistics
----------------------------------------------------------
          2  db block gets
          1  consistent gets

Always nice to see those good folks at Oracle doing things for us :-)

Rating: 5
Difference between function and select March 04, 2002
Reviewer: jan van mourik from Freezing Houston, Texas
Tom,

To see if there's a performance difference, I ran these pl/sqls:

declare
  t_date date;
  t_n    number;
begin
  t_n := dbms_utility.get_time;
  for i in 1..10000
  loop
     select sysdate into t_date from dual;
  end loop;
  dbms_output.put_line(dbms_utility.get_time - t_n);
end;

(Output between 73 and 81)

call     count       cpu    elapsed
------- ------  -------- ----------
Parse        1      0.00       0.01
Execute      1      1.07       1.30
Fetch        0      0.00       0.00
------- ------  -------- ----------
total        2      1.07       1.31

declare
  t_date date;
  t_n    number;
begin
  t_n := dbms_utility.get_time;
  --
  for i in 1..10000
  loop
     t_date := sysdate;
  end loop;
  dbms_output.put_line(dbms_utility.get_time - t_n);
  --
end;

(Output between 21 and 24)

call     count       cpu    elapsed       disk
------- ------  -------- ---------- ----------
Parse        1      0.00       0.00          0
Execute      1      0.22       0.21          0
Fetch        0      0.00       0.00          0
------- ------  -------- ---------- ----------
total        2      0.22       0.21          0

Using "t_date := sysdate;" is consistently faster. And I don't see the "SELECT
SYSDATE FROM DUAL" in the tkprof output. What exactly is Oracle doing?

Followup:
It is just using internalized, optimized code.  It knows you want sysdate (it
cannot see that from the first query really) and just gets it that much faster.
As demonstrated above, it is running a query against dual -- just more
"optimallly" (more internalized)

Rating: 4
things changed in 8.1.7.4? December 27, 2002
Reviewer: Richard Ji from New York City, NY USA
It seems that starting from 8.1.7.4, assigning sysdate to
a variable no longer does a "select sysdate into ...".
In stdbody.sql, sysdate will only do "select sysdate into.."
if function pessdt returns null.  And that's a C call.

Rating: 4
Sys.Standard April 10, 2003
Reviewer: Som from India
Hi,

This is Som from India.
I was just going through the Standard Package provided by Oracle.
The last function is quit interesting and not understandable to me.
Could you please explain this to me, by giving an example?
The function is :

FUNCTION || RETURNS VARCHAR2
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 LEFT                           VARCHAR2                IN
 RIGHT                          VARCHAR2                IN

I will be waiting for your positive answer.
Thanks a lot.

Followup:
it lets you code:

  x := a || b;

it is the concatentation operator.

Rating: 4
Interesting... April 11, 2003
Reviewer: Jon from Sydney
If || were a "standard" function call, we would use it like:

x := ||(a,b);

but a||b doesn't look much like a function.

1) Why is it declared in Standard at all - doesn't the parser know what to do
with it without reference to a package?

2) Or is a||b some obscure valid syntax for a function?

Thanks
Jon

Followup:
it just "is".  don't have a really good answer for this beyond "it just is"....

Rating: 5
All operators are functions. April 11, 2003
Reviewer: Richard from VA
|| is a function just like +, -, *, etc. etc.

Take a closer look at standard.  They are all there.

In fact some languages (C++ and Ada to name two) will let you overload these
operators.  For example if you have defined an object of widgets and you want to
be able to add them.

Rating: 5
back to SYSDATE April 11, 2003
Reviewer: A reader
Oracle sysdate is completely dependent on the operating system date. They are
the same in value. Tested with Oracle 9i on linux: if setting the OS date using
"date -s" to an arbitrary date, the oracle sysdate will have that value, and
ticking alone with it...

Rating: 4
SYSDATE faster from your own DUAL April 12, 2003
Reviewer: Peter from Cumbria, England
A bit of a long-winded example to prove that getting the date from your own
table is consistently much faster than getting it from dual.

SQL>  create table mydual  (c1 number)  storage (initial 8192) pctfree 0 pctused
99;

Table created.

SQL> insert into mydual values(1);

1 row created.

SQL> commit;

Commit complete.

SQL> alter table mydual add constraint pk_mydual primary key (c1) using index
tablespace user_indexes;

Table altered.

SQL> analyze  table mydual compute statistics;

Table analyzed.

SQL> set autot on
SQL> select sysdate from dual;

SYSDATE
---------
12-APR-03

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

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

SQL> select sysdate from mydual;

SYSDATE
---------
12-APR-03

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1)
   1    0   INDEX (FULL SCAN) OF 'PK_MYDUAL' (UNIQUE) (Cost=1 Card=1)

Statistics
----------------------------------------------------------
         39  recursive calls
          0  db block gets
          9  consistent gets
          0  physical reads
          0  redo size
        207  bytes sent via SQL*Net to client
        124  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed

(Plenty of recursive calls first time)

SQL> /

SYSDATE
---------
12-APR-03

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1)
   1    0   INDEX (FULL SCAN) OF 'PK_MYDUAL' (UNIQUE) (Cost=1 Card=1)

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

(No recursive calls or block gets)
SQL> /

SYSDATE
---------
12-APR-03

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1)
   1    0   INDEX (FULL SCAN) OF 'PK_MYDUAL' (UNIQUE) (Cost=1 Card=1)

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

SQL> set timing on
SQL>  declare a date;
  2     begin
  3     for i in 1..100000  loop
  4    select sysdate into a from mydual;
  5   end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:11.08
SQL> /

PL/SQL procedure successfully completed.

Elapsed: 00:00:11.08
SQL> /

PL/SQL procedure successfully completed.

Elapsed: 00:00:11.09
SQL>  declare a date;
  2     begin
  3     for i in 1..100000  loop
  4    select sysdate into a from dual;
  5    end loop;
  6   end;
  7  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:15.05
SQL> /

PL/SQL procedure successfully completed.

Elapsed: 00:00:16.00
SQL> /

PL/SQL procedure successfully completed.

Elapsed: 00:00:15.05
SQL>

mydual is always faster - much.

Rating: 4
Is sysdate the same value for each row June 11, 2003
Reviewer: Sam To from San Mateo, CA
Tom,

If I have the following SQL:
SELECT to_char(sysdate, 'DD/MM/YYYY HH24:MI:SS'), ... FROM my_view a WHERE a.val
= :1

Suppose this returns many rows and takes several minutes to run.  Will the
sysdate value selected be the same for every row?  My tests indicates it does
return the same value.  I just want to find out if it is documented behavior.

Thank you very much.

Followup:
sysdate will be the same within a query like that, yes.

not sure of the documentation explicity "says that" -- but sysdate will behave
that way

Rating: 4
Conor's follow up June 11, 2003
Reviewer: umesh from bangalore india
Tom
I could not understand what Connor wanted to convey here. Please explain

Followup:
you don't say which item -- he said 2 things, assuming you meant:

In 8.1.7

SQL> select * from dual;

Statistics
----------------------------------------------------------
          4  db block gets
          1  consistent gets

In 9.0.1

SQL> select * from dual;

Statistics
----------------------------------------------------------
          2  db block gets
          1  consistent gets

Always nice to see those good folks at Oracle doing things for us :-)

....

Look at the db block gets -- they went down.  In fact, on my machines

817, 4 db block gets, 1 consistent gets (5 logical IO's)
901, 0 db block gets, 3 consistent gets (3 logical IO's)

meaning a significant reduction in the amount of work it takes to process dual

Rating: 5
9ir2 gives the same result 0 db block gets, 3 consistent gets (3 logical IO's) June 12, 2003
Reviewer: A reader from MN, USA

Rating: 5
How to get the current time from the client machine using web forms? September 24, 2003
Reviewer: A reader
I do not know if this question fits in here, but how can I get the current time
from the client machine through a web form?

SYSDATE gives the current date/time from the database. What gies the current
date/time from the client?

Thanks!

Followup:
i don't know that you can.  i haven't myself used forms since 1995 -- the
current time on the client seems sort of "not relevant in the least" as it is
almost certainly "not right"

you can try the discussion forums on otn.oracle.com -- there is a developer
forum, someone there might be able to tell you if you can or not.

Rating: 4
Getting the Client machine time in WebForms October 10, 2003
Reviewer: Joris Struyve from Daknam
> I do not know if this question fits in here, but how can I
> get the current time
> from the client machine through a web form?

A JavaBean will do this for you. You can download the web util package from otn.
It contains heaps of examples.

September 25, 2003 Winston -- Thanks for the question regarding "redo for inserting one at a time vs. many at a time", version 9i
You Asked
Hi Tom,

On page 168 of 'Expert one-one-one', it says 'An insert behaves a little
differently. It generates slightly more redo for single row insrts which is
reasonable, given that it must organize data on the block differently when
inserting one at a time, versus many at a time( it does slightly more work).'

Would you please share some lights on 'organize data on the block differently'
and ' it does slightly more work'?

Thank you in advance,
Winston
and we said...
every time you visit the block to insert, we do work.  this work generates a
"start redo, redo, end redo" sort of messsage.

if you insert 100 rows -- 1 at a time, you get 100 times the "overhead" for a
redo message.

versus inserting 100 rows all at once, where you get 1/100th or thereabouts the
redo.

it is very easy to measure.

ops$tkyte@ORA920> create table t as select * from all_objects where 1=0;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec runstats_pkg.rs_start;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t select * from all_objects;

31731 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec runstats_pkg.rs_middle;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2          for x in ( select * from all_objects )
  3          loop
  4                  insert into t values X;
  5          end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runstats_pkg.rs_stop;
Run1 ran in 352 hsecs
Run2 ran in 2312 hsecs
run 1 ran in 15.22% of the time

Name                                  Run1        Run2        Diff
...
STAT...redo size                 3,591,356  11,352,092   7,760,736

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
464,734     904,478     439,744     51.38%

PL/SQL procedure successfully completed.
Review & Followup
Rating: 5
message size, log_buffer latches September 21, 2003
Reviewer: Alberto Dell'Era from Milan, Italy
> this work generates a "start redo, redo, end redo" sort of message.

So the overhead is about 240 bytes:
(diff in redo)/(num rows) = 7760736 / 31731 = 244.5

Is the message you are speaking of the one put in the log buffer - hence in your
example we get the redo copy latch 31731 times for the 31731 single-row inserts,
and only one (or probably one for each inserted block) for the bulk insert ?

Rating: 5
Why cann't see the difference in logminer? September 24, 2003
Reviewer: Winston
Thank you Tom for your answer! Just for curiosity I did a similar test and used
logminer to see what's in the redo, but I couldn't see much difference.

wzhg@9iR2> create table tredo(c1 number);

Table created.

wzhg@9iR2> alter session set nls_date_format='yyyy-mm-dd hh24:mi:ss';

Session altered.

wzhg@9iR2> select sysdate from dual;

SYSDATE
-------------------
2003-09-24 16:09:23

wzhg@9iR2>
wzhg@9iR2>
wzhg@9iR2>
wzhg@9iR2> insert into tredo select rownum from all_objects where rownum<10;

9 rows created.

wzhg@9iR2>
wzhg@9iR2>
wzhg@9iR2>  begin
  2     for x in (select rownum from all_objects where rownum<10) loop
  3        insert into tredo values X;
  4      end loop;
  5      end;
  6      /

PL/SQL procedure successfully completed.

wzhg@9iR2>
wzhg@9iR2>
wzhg@9iR2>
wzhg@9iR2>  commit;

Commit complete.

wzhg@9iR2>
wzhg@9iR2>
wzhg@9iR2>
wzhg@9iR2>  select sysdate from dual;

SYSDATE
-------------------
2003-09-24 16:09:25

wzhg@9iR2> exec sys.dbms_logmnr.start_logmnr(-
>   dictfilename=>'C:    mpdictionary.ora',-
>  starttime=>to_date('2003-09-24 16:09:23','yyyy-mm-dd hh24:mi:ss'),-
>  endtime=>to_date('2003-09-24 16:09:25','yyyy-mm-dd hh24:mi:ss'));

wzhg@9iR2>
wzhg@9iR2> SELECT (XIDUSN || '.' || XIDSLT || '.' || XIDSQN) AS XID,
  2  USERNAME AS USR,
  3  SQL_REDO AS SQL_REDO
  4  FROM V$LOGMNR_CONTENTS
  5  /
XID        USR        SQL_REDO
---------- ---------- ------------------------------
4.24.58514 WZHANG     set transaction read write;
4.24.58514 WZHANG     insert into "WZHANG"."TREDO"("
                      C1") values ('1');

4.24.58514 WZHANG     insert into "WZHANG"."TREDO"("
                      C1") values ('2');

4.24.58514 WZHANG     insert into "WZHANG"."TREDO"("
                      C1") values ('3');

4.24.58514 WZHANG     insert into "WZHANG"."TREDO"("
                      C1") values ('4');

4.24.58514 WZHANG     insert into "WZHANG"."TREDO"("
                      C1") values ('5');

4.24.58514 WZHANG     insert into "WZHANG"."TREDO"("
                      C1") values ('6');

4.24.58514 WZHANG     insert into "WZHANG"."TREDO"("
                      C1") values ('7');

4.24.58514 WZHANG     insert into "WZHANG"."TREDO"("
                      C1") values ('8');

4.24.58514 WZHANG     insert into "WZHANG"."TREDO"("
                      C1") values ('9');

4.37.58513 WZHANG     set transaction read write;
4.37.58513 WZHANG     insert into "SYS"."IDL_SB4$"("
                      OBJ#","PART","VERSION","PIECE#
                      ","LENGTH","PIECE") values ('3
                      3244','0','153092096','0','14'
                      ,Unsupported Type);

4.37.58513 WZHANG
4.37.58513 WZHANG     insert into "SYS"."IDL_SB4$"("
                      OBJ#","PART","VERSION","PIECE#
                      ","LENGTH","PIECE") values ('3
                      3244','0','153092096','1','7',
                      Unsupported Type);

4.37.58513 WZHANG
4.37.58513 WZHANG     insert into "SYS"."IDL_UB1$"("
                      OBJ#","PART","VERSION","PIECE#
                      ","LENGTH","PIECE") values ('3
                      3244','0','153092096','2','3',
                      HEXTORAW('000000'));

4.37.58513 WZHANG
4.37.58513 WZHANG     insert into "SYS"."IDL_CHAR$"(
                      "OBJ#","PART","VERSION","PIECE
                      #","LENGTH","PIECE") values ('
                      33244','0','153092096','3','43
                      ','"DEFINE"TREDO"C1"NUMBER"ORG
                      ANIZATION"HEAP""');

4.37.58513 WZHANG
4.37.58513 WZHANG     insert into "SYS"."IDL_UB2$"("
                      OBJ#","PART","VERSION","PIECE#
                      ","LENGTH","PIECE") values ('3
                      3244','0','153092096','4','143
                      ',Unsupported Type);

4.37.58513 WZHANG
4.37.58513 WZHANG     insert into "SYS"."IDL_UB2$"("
                      OBJ#","PART","VERSION","PIECE#
                      ","LENGTH","PIECE") values ('3
                      3244','0','153092096','5','14'
                      ,Unsupported Type);

4.37.58513 WZHANG
4.37.58513 WZHANG     update "SYS"."OBJ$" set "OBJ#"
                       = '33244', "DATAOBJ#" = '3324
                      4', "TYPE#" = '2', "CTIME" = T
                      O_DATE('2003-09-24 16:39:53',
                      'yyyy-mm-dd hh24:mi:ss'), "MTI
                      ME" = TO_DATE('2003-09-24 16:3
                      9:53', 'yyyy-mm-dd hh24:mi:ss'
                      ), "STIME" = TO_DATE('2003-09-
                      24 16:39:53', 'yyyy-mm-dd hh24
                      :mi:ss'), "STATUS" = '1', "FLA
                      GS" = '0', "OID$" = NULL, "SPA
                      RE1" = '6', "SPARE2" = '1' whe
                      re "OBJ#" = '33244' and "DATAO
                      BJ#" = '33244' and "TYPE#" = '
                      2' and "CTIME" = TO_DATE('2003
                      -09-24 16:39:53', 'yyyy-mm-dd
                      hh24:mi:ss') and "MTIME" = TO_
                      DATE('2003-09-24 16:39:53', 'y
                      yyy-mm-dd hh24:mi:ss') and "ST
                      IME" = TO_DATE('2003-09-24 16:
                      39:53', 'yyyy-mm-dd hh24:mi:ss
                      ') and "STATUS" = '1' and "FLA
                      GS" = '0' and "OID$" IS NULL a
                      nd "SPARE1" = '6' and "SPARE2"
                       = '1' and ROWID = 'AAAAASAABA
                      AAHYFAA9';

4.37.58513 WZHANG     commit;
4.24.58514 WZHANG     insert into "WZHANG"."TREDO"("
                      C1") values ('1');

4.24.58514 WZHANG     insert into "WZHANG"."TREDO"("
                      C1") values ('2');

4.24.58514 WZHANG     insert into "WZHANG"."TREDO"("
                      C1") values ('3');

4.24.58514 WZHANG     insert into "WZHANG"."TREDO"("
                      C1") values ('4');

4.24.58514 WZHANG     insert into "WZHANG"."TREDO"("
                      C1") values ('5');

4.24.58514 WZHANG     insert into "WZHANG"."TREDO"("
                      C1") values ('6');

4.24.58514 WZHANG     insert into "WZHANG"."TREDO"("
                      C1") values ('7');

4.24.58514 WZHANG     insert into "WZHANG"."TREDO"("
                      C1") values ('8');

4.24.58514 WZHANG     insert into "WZHANG"."TREDO"("
                      C1") values ('9');

4.24.58514 WZHANG     commit;

35 rows selected.

Followup:
because logminer is just showing you row by row changes, it is simply a UI to
dump the redo in human readable format.

you would have to actually DUMP the redo to trace files and inspect that.  I
don't believe it is worth it (you KNOW more is generated, you know HOW to get
less generated -- WE have everything we need to know :)

September 25, 2003 Omer -- Thanks for the question regarding "7 stages a query goes through before retrieving the rows", version 8.1.7
You Asked
Tom

I heard that there are 7 stages a query goes through before retrieving the rows.
Can you explain to us what are those 7 stages.
I can think of parsing the query, fetching the rows etc.

can you discuss this with us.

Omer
and we said...
Never heard of "7 stages".

there are 8 STEPS i can think of (this is would you would do in pure dynamic sql
to execute a query -- similar to what SQLPlus goes through)

1) prepare
2) optional describe
3) bind INPUTS (if any)
4) optional describe outputs
5) define OUTPUTS
6) open
7) fetch
8) close
Review & Followup
Rating: 5
exciting September 08, 2001
Reviewer: A reader
Tom

Can you put  in a few sentences for each of the stages you have described.(if
you can take an example and explain that would be wonderful)

Followup:
Sure,  we'll work with the query "select ename from emp where empno = :empno"

1) prepare.  That parses/compiles the query.  If the query was syntactically
invalid, you did not have access to the objects, etc -- it would fail.

2) describe.  In languages like Pro*C this would fill in a data structure that
describes all of the INPUTS to the query.  In this case, the data structure
would say "I see :empno as a bind variable".  It would tell us of one bind
variable

3) bind INPUTS.  In this step -- you would provide to us the value of :empno

4) describe outputs.  In this step, you ask us "what output might this query
result in".  In this case we would say "there is one column, it is named ENAME,
it is a varchar2(10)".

5) define outputs.  Here you would tell us "where" to put the value of ename and
what datatype you would like to use.  In the prior step we told you this was a
varchar2(10) -- you tell us what datatype you would like to fetch this into.
This makes more sense when you fetch a number/date -- you can tell us to put the
results into a STRING type instead and we'll convert the number/date into a
string for you.

6) open.  This "executes" the query.  It just opens the result set.  In most
cases -- this is instantaneous (we do not answer the query here -- that happens
in the FETCH phase)

7) fetch.  Get the data, fetch the rows

8) close. finish up, close the cursor (if you won't be using it again in this
application).  releases all resources.

Rating: 5
MY rating -- **********************, absolutely useful September 09, 2001
Reviewer: A reader
Have you covered this anywhere in your book.

Followup:
Chapter 16, Dynamic SQL.

I cover only PLSQL dynamic sql which does not support step #2 below (describe
the INPUTS -- bind variables ).  Otherwise all of the steps are covered in
detail there.

In order to get the bind variable names (inputs) from a query in PLSQL you must
parse the query text yourself.

function get_binds( p_stmt  in varchar2 ) return  dbms_sql.varchar2_table
as
    l_binds      dbms_sql.varchar2_table;
    l_in_literal boolean default false;
    l_ch         varchar2(1);
    i            number default 1;
    l_str        varchar2(255);
    l_add        boolean;
begin
    loop
        exit when ( i > length(p_stmt) );
        l_ch := substr( p_stmt, i, 1 );

        if ( l_ch = '''' )
        then
            l_in_literal := NOT l_in_literal;
        elsif ( l_ch = '-' AND substr(p_stmt,i+1,1) = '-' )
        then
            i := nvl(instr( p_stmt, chr(10), i ),0);
            if ( i = 0 ) then i := length(p_stmt); end if;
        elsif ( l_ch = '/' AND substr(p_stmt,i+1,1) = '*' )
        then
            i := nvl(instr( p_stmt, '*/', i ),0);
            if ( i = 0 ) then i := length(p_stmt); end if;
        end if;

        if ( l_ch = ':' and NOT l_in_literal )
        then
            l_str := ':';
            for j in i+1 .. length(p_stmt)
            loop
                l_ch := upper(substr( p_stmt, j, 1 ));
                exit when l_ch not between '0' and '9'
                     AND  l_ch not between 'A' and 'Z'
                     AND  l_ch <> '_';
                l_str := l_str || upper(l_ch);
                i := j;
            end loop;
            l_add := (length(l_str) > 1);
            for k in 1 .. l_binds.count
            loop
                if ( l_binds(k) = l_str )
                then
                    l_add := false;
                    exit;
                end if;
            end loop;
            if ( l_add )
            then
                l_binds(l_binds.count+1) := l_str;
            end if;
        end if;
        i := i+1;
    end loop;

    return l_binds;
end get_binds;

Rating: 4
Omer September 09, 2001
Reviewer: Omer
where do the above 8 stages occur in the following layers.
can you kindly put a few sentences for each of the above layer and explain to us
in simple terms.

1.The oracle call interface
2.the user program interface
3.The oracle program interface
4.The compilation layer
5.The execution layer
6.The distributed execution layer
7.The network execution layer
8.The security layer
9.The query layer
10.The recursive program interface
11.The access layer
12.The data layer
13.The transaction layer
14.The cache layer
15.The services layer
16.The lock management layer
17.The generic layer
18.The operating system dependencies

(Is understanding the above layers very important to understand oracle)

Followup:
It doesn't make sense to map the phases of procedurally executing a query with
the above layers.  I'll make a couple of comments

- OCI, isn't really a layer, it is an API.  You could use OCI to prepare, bind,
define, open, fetch, etc -- but it is not a "layer".  Its like ODBC or JDBC -
just an API

- UPI & OPI, user/oracle program interface, are more APIs -- they are the lowest
level API but just API's none the less.

Many of the remaining items in your list will apply -- perhaps at many times.  A
statement will be compiled during a parse -- but that statement might contain a
call to a plsql function that does dynamic selects itself.  So, a FETCH from
that statement might hit the compile "layer" as well.  Normally parse = compile.

There is no straightforward, one to one mapping.  In my opinion, it is not very
important in day to day operations to understand these layers.

Rating: 5
Bhagat Singh September 10, 2001
Reviewer: Bhagat Singh from INDIA
Knowing better the software helps in getting better results

Rating: 4
Thanks a lot; September 10, 2001
Reviewer: Ashraf Jebreel from Riyahd, Saudi Arabia.
This is exactly what I was looking for. Today I am browsing just to submit the
same question;

Thanks a lot.

Rating: 3
I was talking of the below 7 steps September 10, 2001
Reviewer: Omer
Does your 8 steps fall under these 7 steps

( the below info is provided by oracle itself, and by the way what is
"heuristics")

Query processing can be divided into 7 phases:
     [1] Syntactic                - checks the syntax of the query
     [2] Semantic                 - checks that all objects exist and are
accessible
     [3] View Merging             - rewrites query as join on base tables as
                                    opposed to using views
     [4] Statement Transformation - rewrites query transforming some complex
constructs into simpler ones where appropriate (e.g. subquery merging, in/or
                                    transformation)
     [5] Optimization             - determines the optimal access path for the
                                    query to take. With the Rule Based Optimizer
(RBO) it uses a set of heuristics
to determine access path. With the Cost Based Optimizer (CBO) we use statistics
to analyze the relative costs of accessing objects.
     [6] QEP Generation
     [7] QEP Execution
(QEP = Query Evaluation Plan)

Steps [1]-[6] are handled by the parser.
Step [7] is the execution of the statement.

( can you give an example for point number 4 , and discuss)

Followup:
You know, if you have the seven steps -- it would be helpful to put them in in
the first place (less guessing on my part).

1, 2, 3, 4, 5, 6 are all part of PARSE.
7 is part of fetch or execute (execute is what you do to an UPDATE for example)
You led me to believe with your question that you had "heard there were 7
stages" but didn't know what they were.

the dictionary definition of heuristic is:

Main Entry: heu·ris·tic
Pronunciation: hyu-'ris-tik
Function: adjective

Etymology: German heuristisch, from New Latin heuristicus, from Greek heuriskein
to discover; akin to Old Irish fo-fúair he found
Date: 1821

: involving or serving as an aid to learning, discovery, or problem-solving by
experimental and especially trial-and-error methods <heuristic techniques> <a
heuristic assumption>; also : of or relating to exploratory problem-solving
techniques that utilize self-educating techniques (as the evaluation of
feedback) to improve performance <a heuristic computer program>
................

Simply put, it is the set of rules followed by the software to optimize your
query.  RBO uses rules (heuristics) built into it.  The CBO does not.

An example of [4] would be the fact that:

select * from t where x in ( 1, 2, 3 )

is actually rewritten as

select * from t where ( x = 1 or x = 2 or x = 3 )

before being processed.  A much mnore complex case could be a materialized view,
such as:

create materialized view emp_rollup
enable query rewrite
as
select deptno, sum(sal) sal
  from emp
 group by deptno;

Now, given that all the necessary settings have been done (see
the data warehousing guide for a comprehensive example) your end
users can query:

  select deptno, sum(sal) from emp where deptno in ( 10, 20)
group by deptno;

and the database engine will rewrite the query to go against the
precomputed rollup, not the details -- giving you the answer in
a fraction of the time it would normally take (depending on the volume of data
of course).

Rating: 5
Learning something on every thread September 10, 2001
Reviewer: J from MA
Sorry to extend this further but ::

You said
"
An example of [4] would be the fact that:

select * from t where x in ( 1, 2, 3 )

is actually rewritten as

select * from t where ( x = 1 or x = 2 or x = 3 )

before being processed.
"

Would "in ( 1, 2, 3 )" be better written "( x = 1 or x = 2 or x = 3 )" as far as
processing overhead goes?
Is this something to even consider?

Followup:
No, I would not consider this.  The rewrite happens on a hard parse only and
happens so fast -- I would not even consider it.  In fact, depending on the
optimizer mode, the rewrite might not take place (i should have mentioned that
in the last comment).  For example (assuming an index on empno)

scott@ORA817DEV.US.ORACLE.COM> select empno from emp where empno in ( 1,2,3,4 );

Execution Plan
----------------------------------------------------------
SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=4 Bytes=12)
  INDEX (FAST FULL SCAN) OF 'EMP_PK' (UNIQUE) (Cost=1 Card=4 Bytes=12)

scott@ORA817DEV.US.ORACLE.COM> select /*+ FIRST_ROWS */ empno from emp where
empno in ( 1,2,3,4 );

Execution Plan
----------------------------------------------------------
SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=8 Card=4 Bytes=12)
  INLIST ITERATOR
    INDEX (RANGE SCAN) OF 'EMP_PK' (UNIQUE) (Cost=8 Card=4 Bytes=12)

Here the query was NOT rewritten with the CBO, the CBO instead tends to use the
INLIST ITERATOR to "loop over" the values in an IN LIST

Use "where x in ( ..... )" when you have a list.  If need be, it'll be
rewritten.

Rating: 4
The get_binds breaks if I use double quote to specify bind variables September 24, 2003
Reviewer: Hui Li from CA, USA
Please see the following example

select * from ams_test_char where msg = : " this is a test "

If I use DBMS_SQL to parse it, it is fine but your API can not work with this
query

Followup:
"fix it"

should not be really hard.

September 25, 2003 Lior -- Thanks for the question regarding "Bulk operations. jdbc using batch option VS jdbc calling stored procedure", version 8.1.5
You Asked
Hi Tom !

I have a question regarding bulk operations (inserts/updates and
deletes). I need to insert a large amount of records into a
table. Performance wise what is the preferred way. Using JDBC
with the Batch option or implement a JDBC call to a stored
procedure using array processing where I can take advantage of
the FORALL option or other Oracle options.
What is the best way ? what are the pros and the cons of each
way ?

Thanks in advance,
   Lior.
and we said...
I think (but have not tested, a simple benchmark would bear this
out) that a JDBC batch (set the execute batch to a hundred or
so) would outperform PLSQL in this particular case.

The reason is:

we will go from JDBC ->
   single network call with 100 records ->
     database kernel

versus

JDBC ->
   single network call with 100 record ->
      PLSQL array ->
        database kernel

In this particular case, PLSQL would add a layer to the process
versus just sending 100 rows directly there.   I would probably
opt for JDBC in this case unless I needed to use a stored
procedure to "hide" access to the base tables (eg: I did not
want to grant INSERT on the table but granting EXECUTE on this
one procedure was OK).
Review & Followup
Rating: 1
Elobrate in detail August 29, 2001
Reviewer: RAVICHANDRAN V from CHENNAI , INDIA
Not upto my expectations please give me more information regarding this topic.

Rating: 4
DBMS JVM JDBC Java stored procedure vs PLSQL? August 03, 2003
Reviewer: Tom Starr from Seattle
Can one similarly avoid granting inserts by granting EXECUTE on a DBMS JVM JDBC
Java stored procedure, in this case even if the Java method is Public?

I was expecting one would secure the Java, and it's PLSQL call spec, and
possibly the related trigger, hoping Oracle privileges would secure Java object
execution, despite the necessary (in this case) public static Java method.

Followup:
yes, as you call java in the db via a plsql wrapper and you need execute on the
wrapper and the wrapper runs with the privs of the definer by default....

Rating: 5
Excellent Review August 04, 2003
Reviewer: Tom Starr from Seattle
Thank-you for clarifying that only the PLSQL wrapper, and neither the Java it
calls, nor the trigger which calls it need execute protection.

May I ask if it should work in a unix environment for that execute privilege to
be granted to an ops$<oracle_user> mapped to the owner of a setuid executable?

Or could it be preferrable to use OS_ROLES?

Followup:
believe you meant "execute permission" -- they are "protected"

I do not understand your implied relationship to a setuid executable?

Rating: 4
Clarification August 04, 2003
Reviewer: Tom Starr from Seattle
A Solaris app executes menu options as the invoker, a new menu option needs
Oracle 8.1.7 authentication, and unless our DBAs prefer OS_ROLES, our plan is to
have the menu pick invoke a setuid program, which we hope will authenticate
against an externally identified Oracle user associated with the unix owner of
the setuid program, which Oracle user will also have EXECUTE privilege on any
required Oracle objects. The main concern is whether Oracle will accept the
effective UID associated with the setuid program,for purposes of external
authentication.

Followup:
we seem to be using getuid() not geteuid() for os authentication...

I copied sqlplus to sqlplus_setuid and set uid on it:

[ora920@tkyte-pc-isdn bin]$ cp sqlplus sqlplus_setuid
[ora920@tkyte-pc-isdn bin]$ chmod a+s sqlplus_setuid
[ora920@tkyte-pc-isdn bin]$ ls -l sqlplus_setuid
-rwsr-s--x    1 ora920   ora920     664980 Aug  4 12:34 sqlplus_setuid
[ora920@tkyte-pc-isdn bin]$

sqlplus_setuid /

SQL*Plus: Release 9.2.0.3.0 - Production on Mon Aug 4 12:41:50 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production

ops$tkyte@ORA920> select user from dual;

USER
------------------------------
OPS$TKYTE

ops$tkyte@ORA920> !ps -auxww | grep sqlplus_setuid
ora920   26651  0.2  1.0 13468 5456 pts/3    S    12:41   0:00 sqlplus_setuid

so no, i don't believe that will work for you.

Rating: 3
A Bit Surprised August 04, 2003
Reviewer: Steve from USA
Tom,
Aren't you the guy who said,
"I firmly believe the best java programs are those that have ZERO
"selects/inserts/updates/deletes" in them....."

Are you making an exception here?

Followup:
this is a bulk loader...

nothing is ever black and white.  for a loader, rules are bent, broken and
disregarded sometimes.

Rating: 5
some jdbc benchmarks August 23, 2003
Reviewer: Menon
OK - this is not directly related to the above
benchmark that Tom suggested for the original
question but this evaluates the impact of batch
size when using dml statements.
Tom, others - it would be great if you can just
take a look at if I am making any mistakes
in the benchmark itself or not!:)

The reason I ran these benchmarks are:
1. Wanted to see how different batch sizes impact
   and how much impact batch sizing has in terms
   of response time and resources consumed.
2. Wanted to evaluate the guidelines from Oracle
   documentation in the context of my simple benchmark
   - The guideline is in Oracle's JDBC document
     at
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/java.920/a96654/oraperf.htm#1056502
     in section "Oracle Update Batching
     Characteristics and Limitations"
     Specifically the guideline that says:
     "Batch values between 5 and 30 tend to be the most effective. Setting a
very high value might even have a negative effect. It is worth trying different
values to verify the effectiveness for your particular application. "

I wanted to see how important it is to evaluate this
guideline (of not going beyond 30) on a case by case
basis.

OK.

I used following utilities supplied already by Tom
1. DbmsOutput.java at
http://asktom.oracle.com/pls/ask/f?p=4950:8:5639806495088258994::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:433029981484,
    Note to Tom: Two minor errors in this class
    in the original posting:
    1. iimport in the first line instead of import
    2. the line "public void disable() throws SQLException"
       is repeated.
2. the runstats utility at
http://asktom.oracle.com/~tkyte/runstats.html
3. My own test program in TestBatchUpdates.java
   which is given below at the end of the message.
   It essentially compares inserts of 10000 records
   (can be changed by editing a constant defined)
   in a table t1 defined as follows:

   create table t1 ( x varchar2(100) );

   It can be invoked with two parameters
      the first run's batch size and the second run's
      batch size as follows:
   java TestBatchUpdates 1 20

   would compare inserting 10000 records - Run1 would
   be for batch size of 1 and Run2 would be for batch
   size of 20.

The results summarized for me are given below:
----------------------------------------------

Batch1    Batch2    Time1  Time2    %age (Time)  %age Latches
               (100th of sec)
1    20    1870    140    1336%         1380%
20    30    273    129    212%         131%
30    50    264    88    300%         137%
50    100    236    87    271%         134%
100    200    226    74    305%         142%
200    500    160    194    83%         108%
500    1000    263    69    381%         97%

So the run for 1 batchsize took 1336% of time it took
to run with a batch of 20 and consumed 1380% of the resources of arun with 20
batches.

Conclusions:
1. Batching makes a *huge* difference (see the first run
   result)
2. As the size of batch increases the differences
   between different runs seems to go down in terms of
   time and resources consumed.
3. I had a case (with runs of inserting 1000 records)
   where for batch size of 500 records the results were
   consistently worse than both batch size of 200 and
   1000! The point is you have to benchmark for your
   operations to estimate a good batch size.
4. So the batch size of 30 - specified in the  Oracle doc
   - at least in my case does not seem to be the optimal
    batch size. But then they say clearly ask you to run
    your own benchmarks:)
5. Benchmark every thing!

----------------------------------------------
---- TestBatchUpdates.java begins
import java.sql.*;

import oracle.jdbc.driver.*;

public class TestBatchUpdates
{

  public static void main(String[] args) throws SQLException
  {
    DriverManager.registerDriver( new OracleDriver());
    _checkUsage( args );
    _setBatchValues ( args );

    OracleConnection connection = _getOracleConnection();
    _prepareBenchmarkStatements ( connection );

    // we are going to benchmark the following statement
    String stmtString = "insert into t1 values ( ? )";

    OraclePreparedStatement stmt = (OraclePreparedStatement)
      connection.prepareStatement ( stmtString );

    long updateCount = 0;

    _benchmarkStatementArray[BENCHMARK_START_INDEX].execute();

    // for each batch size being tested

    for( int k = 0; k < UPDATE_BATCH_SIZES_BEING_TESTED.length; k++)
    {
      stmt.setExecuteBatch ( UPDATE_BATCH_SIZES_BEING_TESTED[k] );

      // benchmark starts
      long startTime = System.currentTimeMillis();
      updateCount = 0;
      for( int i=0; i < NUMBER_OF_RECORDS_INSERTED; i++)
      {
        stmt.setString(1, "hi" + i);

        updateCount += stmt.executeUpdate();
      }
      updateCount += stmt.sendBatch(); // remaining data if any
      long endTime = System.currentTimeMillis();

      if( k == 0 ) // set the middle point only in first iteration
        _benchmarkStatementArray[BENCHMARK_MIDDLE_INDEX].execute();

      // benchmark ends
      long timeTaken = (endTime - startTime);
      benchmarkTimingResults[k] = timeTaken;
    }
    _benchmarkStatementArray[BENCHMARK_STOP_INDEX].execute();

    _printBenchmarkResults ();

    connection.commit();

    System.out.println( "----Java Timer based results begin\n" );
    for (int i=0; i < UPDATE_BATCH_SIZES_BEING_TESTED.length; i++)
    {
      System.out.println( "Time taken for inserting " +
        NUMBER_OF_RECORDS_INSERTED +
        " with a batch size of " +
        UPDATE_BATCH_SIZES_BEING_TESTED[i] +
       " = " + benchmarkTimingResults[i] );
      System.out.flush();
    }

    System.out.println( "----Java Timer based results end\n" );
    stmt.close();
    _closeBenchmarkStatements ( connection );
    connection.close();
  }

  private static void _closeBenchmarkStatements (
    OracleConnection connection )  throws SQLException
  {
    for( int i=0; i < _benchmarkStatementArray.length; i++)
    {
      _benchmarkStatementArray[i].close();
    }
  }

  private static void _prepareBenchmarkStatements (
    OracleConnection connection ) throws SQLException
  {
    _benchmarkStatementArray[BENCHMARK_START_INDEX]=
      (OracleCallableStatement) connection.prepareCall( BENCHMARK_START );

    _benchmarkStatementArray[BENCHMARK_MIDDLE_INDEX]=
      (OracleCallableStatement) connection.prepareCall( BENCHMARK_MIDDLE );

    _benchmarkStatementArray[BENCHMARK_STOP_INDEX]=
      (OracleCallableStatement) connection.prepareCall( BENCHMARK_STOP );
    _benchmarkStatementArray[BENCHMARK_STOP_INDEX].setInt(1,
      BENCHMARK_DIFFERENCE_THRESHOLD);
    _dbmsOutput = new DbmsOutput ( connection );
    _dbmsOutput.enable ( DBMS_OUTPUT_BUFFER_SIZE );
  }

  private static void _checkUsage (String[] args)
  {
    int argc = args.length;
    if( argc != 2 )
    {
      System.out.println( "usage: java TestBatchUpdates <batch1> <batch2>");
      System.out.flush();
      System.exit(1);
    }
  }

  private static OracleConnection _getOracleConnection ()
    throws SQLException
  {
    String connectionURL = "jdbc:oracle:thin:@lothar:1521:apps";
    OracleConnection connection = (OracleConnection)
      DriverManager.getConnection ( connectionURL, "benchmark", "benchmark");

    connection.setAutoCommit ( false) ;
    return connection;
  }

  private static void _setBatchValues (String[] args)
  {
    int firstBatchValue = Integer.parseInt( args[0] );
    int secondBatchValue = Integer.parseInt( args[1] );

    UPDATE_BATCH_SIZES_BEING_TESTED[0] = firstBatchValue;
    UPDATE_BATCH_SIZES_BEING_TESTED[1] = secondBatchValue;

    System.out.println( "First batch value = " +
      UPDATE_BATCH_SIZES_BEING_TESTED[0]);System.out.flush();
    System.out.println( "Second batch value = " +
      UPDATE_BATCH_SIZES_BEING_TESTED[1]);System.out.flush();
  }

  private static void _printBenchmarkResults()
    throws SQLException
  {
    _dbmsOutput.show();
    _dbmsOutput.close();
  }

  private static int[] UPDATE_BATCH_SIZES_BEING_TESTED = new int[2];

  private static long[] benchmarkTimingResults = new
long[UPDATE_BATCH_SIZES_BEING_TESTED.length];

  private static String BENCHMARK_START = "begin runstats_pkg.rs_start; end;";
  private static String BENCHMARK_MIDDLE = "begin runstats_pkg.rs_middle; end;";
  private static String BENCHMARK_STOP = "begin runstats_pkg.rs_stop(?); end;";

  private static OracleCallableStatement[] _benchmarkStatementArray = new
OracleCallableStatement[3];

  private static DbmsOutput _dbmsOutput;
  private static final int DBMS_OUTPUT_BUFFER_SIZE = 1000000;

  private static final int BENCHMARK_START_INDEX = 0;
  private static final int BENCHMARK_MIDDLE_INDEX = 1;
  private static final int BENCHMARK_STOP_INDEX = 2;
  private static final int BENCHMARK_DIFFERENCE_THRESHOLD = 200;
  private static final int NUMBER_OF_RECORDS_INSERTED = 10000;
}
---- TestBatchUpdates.java ends

Followup:
all i can say is "bravo"

<quote>
Conclusions:
1. Batching makes a *huge* difference (see the first run
   result)
2. As the size of batch increases the differences
   between different runs seems to go down in terms of
   time and resources consumed.
3. I had a case (with runs of inserting 1000 records)
   where for batch size of 500 records the results were
   consistently worse than both batch size of 200 and
   1000! The point is you have to benchmark for your
   operations to estimate a good batch size.
4. So the batch size of 30 - specified in the  Oracle doc
   - at least in my case does not seem to be the optimal
    batch size. But then they say clearly ask you to run
    your own benchmarks:)
5. Benchmark every thing!
</quote>

excellent.  I agree 110%.

add #6

6. parameterize so that you can change this array size externally -- so when
things change, you can change easily with them

Rating: 5
thanx! August 23, 2003
Reviewer: A reader
A "bravo" from Tom Kyte means a lot to me!:)
Thanx for point # 6. Just in case you missed it - a minor
point I made is:
"Note to Tom: Two minor errors in this class
    in the original posting:
    1. iimport in the first line instead of import
    2. the line "public void disable() throws SQLException"
       is repeated."

You may want to correct it - although anyone who
has written even a java program to print "Hello World "
should be able to correct those errors:)

If anyone else can test those results and let me know
if there are any bugs in it - I would be really
grateful!

Thanx!

Followup:
corrected

Rating: 5
Here is another JDBC benchmark August 24, 2003
Reviewer: A reader
Not to get carried away or anything - but since I ran
this one and shared it with my team - might as well share
it on this forum.

Tom
Let me know whether this thread was the right one
for this benchmark!

Question:
  Is the index version of getColumn() faster than the
  name version of it, i.e. resultSet.getColumn( int
  columnIndex) faster than resultSet.getColumn( String
  columnName)?

Answer:
   I had heard a lot that the index version would be faster
   hence I wrote a simple benchmark to find out.
   (ran it on 9.0.1.0)using Oracle jdbc drivers.
   Here we don't need to look at latches etc - just
   need to find times from the JDBC client.

The program is given below:

The result:

Approach Taken                                  Time taken
                                            (milliseconds)
resultSet.getColumn() based on column index    12,773
resultSet.getColumn() based on column name    10,503
%age Difference    20%

So it turns out that the the getColumn() by name is around 20% faster than
getColumn() by index.

--the test program -TestGetColumnNameOrIndex.java begins--
import java.sql.*;

import oracle.jdbc.driver.*;

public class TestGetColumnNameOrIndex
{

  public static void main(String[] args) throws SQLException
  {
    String columnName = "object_name";
    String testQuery = "select object_name from t1";
    DriverManager.registerDriver( new OracleDriver());
    _checkUsage( args );

    OracleConnection connection = _getOracleConnection();
    Statement stmt = connection.createStatement();

    ResultSet rset = stmt.executeQuery(  testQuery );

    // benchmark - first case - using index

    long startTime = System.currentTimeMillis();

    int indexOfColumn = rset.findColumn ( columnName );

    int i=0;
    while( rset != null && rset.next() )
    {
      String objectName = rset.getString( indexOfColumn );
      i++;
    }
    long endTime = System.currentTimeMillis();
    //System.out.println( "total rows found = " + i);
    System.out.println( "Time taken using column index " +
       "for selecting " + i + " rows " + ( endTime - startTime ) );

    rset.close();

    rset = stmt.executeQuery(  testQuery );

    // benchmark - second case - using column name

    startTime = System.currentTimeMillis();

    i=0;
    while( rset != null && rset.next() )
    {
      String objectName = rset.getString( columnName );
      i++;
    }
    endTime = System.currentTimeMillis();
    //System.out.println( "total rows found = " + i);

    System.out.println( "Time taken using column name " +
       "for selecting " + i + " rows " + ( endTime - startTime ) );
    rset.close();
    stmt.close();
    connection.close();
  }

  private static void _checkUsage (String[] args)
  {
    int argc = args.length;
    if( argc != 0 )
    {
      System.out.println( "usage: java TestGetColumnNameOrIndex");
      System.out.flush();
      System.exit(1);
    }
  }

  private static OracleConnection _getOracleConnection ()
    throws SQLException
  {
    String connectionURL = "jdbc:oracle:thin:@lothar:1521:apps";
    OracleConnection connection = (OracleConnection)
      DriverManager.getConnection ( connectionURL, "benchmark",
        "benchmark");

    connection.setAutoCommit ( false) ;
    return connection;
  }

}

-- the test program ends

Regards
Menon:)

Followup:
try it with a 20 column query -- be more "real world".

see what happens to get 20 columns by index
and then by name.

post that!

Rating: 5
thanx Tom! August 25, 2003
Reviewer: Menon
OK - here you go - the results are different - thanx for
your tip! (Just to make sure people can run the benchmarks
easily - I am posting the 2 versions of same Java programs
that I ran here (I could have modified the same Java program to work for all
cases but was too lazy!

Sooo - first I created a table.

drop table lots_of_columns;
create table lots_of_columns
as select rownum a,
          a.object_name b,
          a.object_type c,
          b.object_name d,
          b.object_type e,
          a.object_name f,
          a.object_type g,
          b.object_name h,
          b.object_type i,
          a.object_name j,
          a.object_type k,
          b.object_name l,
          b.object_type m,
          a.object_name n,
          b.object_type o,
          b.object_name p,
          a.object_type q,
          b.object_name r,
          b.object_type s,
          a.object_name t
from all_objects a, all_objects b
where rownum <= 100000;
alter table lots_of_columns add constraint lots_of_columns_pk primary key (a);

Then I ran the benchmark by selecting 10 columns.

Following is the modified Java program
------ TestGetColumnNameOrIndex.java (*10* columns)
import java.sql.*;

import oracle.jdbc.driver.*;

public class TestGetColumnNameOrIndex
{

  public static void main(String[] args) throws SQLException
  {
    String columnName = "object_name";
    String testQuery = "select a, b, c, d, e, f, g, h,i, j, k, " +
                               "l, m, n,o, p, q, r, s, t " +
                               " from lots_of_columns";
    DriverManager.registerDriver( new OracleDriver());
    _checkUsage( args );

    OracleConnection connection = _getOracleConnection();
    Statement stmt = connection.createStatement();

    ResultSet rset = stmt.executeQuery(  testQuery );

    // benchmark - first case - using index

    long startTime = System.currentTimeMillis();

    int index1 = rset.findColumn ( "a" );
    int index2 = rset.findColumn ( "b" );
    int index3 = rset.findColumn ( "c" );
    int index4 = rset.findColumn ( "d" );
    int index5 = rset.findColumn ( "e" );
    int index6 = rset.findColumn ( "f" );
    int index7 = rset.findColumn ( "g" );
    int index8 = rset.findColumn ( "h" );
    int index9 = rset.findColumn ( "i" );
    int index10 = rset.findColumn ( "j" );
    int index11 = rset.findColumn ( "k" );
    int index12 = rset.findColumn ( "l" );
    int index13 = rset.findColumn ( "m" );
    int index14 = rset.findColumn ( "n" );
    int index15 = rset.findColumn ( "o" );
    int index16 = rset.findColumn ( "p" );
    int index17 = rset.findColumn ( "q" );
    int index18 = rset.findColumn ( "r" );
    int index19 = rset.findColumn ( "s" );
    int index20 = rset.findColumn ( "t" );

    int x=0;
    while( rset != null && rset.next() )
    {
      int a = rset.getInt( index1 );
      String b = rset.getString( index2 );
      String c = rset.getString( index3 );
      String d = rset.getString( index4 );
      String e = rset.getString( index5 );
      String f = rset.getString( index6 );
      String g = rset.getString( index7 );
      String h = rset.getString( index8 );
      String i = rset.getString( index9 );
      String j = rset.getString( index10 );
      if ( x == 0)
      {
/*
        System.out.println(a + "," +
                           b + "," +
                           c + "," +
                           d + "," +
                           e + "," +
                           f + "," +
                           i + "," +
                           j );System.out.flush();
*/
      }
      x++;
    }
    long endTime = System.currentTimeMillis();
    //System.out.println( "total rows found = " + x);
    System.out.println( "Time taken using column index " +
       "for selecting " + x + " rows " + ( endTime - startTime ) );

    rset.close();

    rset = stmt.executeQuery(  testQuery );

    // benchmark - second case - using column name

    startTime = System.currentTimeMillis();

    x=0;
    while( rset != null && rset.next() )
    {
      int a = rset.getInt( "a" );
      String b = rset.getString( "b" );
      String c = rset.getString( "c" );
      String d = rset.getString( "d" );
      String e = rset.getString( "e" );
      String f = rset.getString( "f" );
      String g = rset.getString( "g" );
      String h = rset.getString( "h" );
      String i = rset.getString( "i" );
      String j = rset.getString( "j" );
      if ( x == 0)
      {
/*
        System.out.println(a + "," +
                           b + "," +
                           c + "," +
                           d + "," +
                           e + "," +
                           f + "," +
                           i + "," +
                           j );System.out.flush();
*/
      }
      x++;
    }
    endTime = System.currentTimeMillis();
    //System.out.println( "total rows found = " + x);

    System.out.println( "Time taken using column name " +
       "for selecting " + x + " rows " + ( endTime - startTime ) );
    rset.close();
    stmt.close();
    connection.close();
  }

  private static void _checkUsage (String[] args)
  {
    int argc = args.length;
    if( argc != 0 )
    {
      System.out.println( "usage: java TestGetColumnNameOrIndex");
      System.out.flush();
      System.exit(1);
    }
  }

  private static OracleConnection _getOracleConnection ()
    throws SQLException
  {
    String connectionURL = "jdbc:oracle:thin:@lothar:1521:apps";
    OracleConnection connection = (OracleConnection)
      DriverManager.getConnection ( connectionURL, "benchmark",
        "benchmark");

    connection.setAutoCommit ( false) ;
    return connection;
  }

}

----- program ends (*10 columns)

I ran this 10 times (Yeah I noticed that I did a findColumn for 20 columns above
- but don't think it
matters a lot since the next loop is the main one
(going for 100000 times) - Did nt bother to test with
this minor correction. - would not have changed
the results materially.

--- my 10 runs-- (for 10 columns)
Time taken using column index for selecting 100000 rows 31353
Time taken using column name for selecting 100000 rows 32655

Time taken using column index for selecting 100000 rows 31041
Time taken using column name for selecting 100000 rows 32912

Time taken using column index for selecting 100000 rows 30922
Time taken using column name for selecting 100000 rows 32163

Time taken using column index for selecting 100000 rows 31128
Time taken using column name for selecting 100000 rows 32265

Time taken using column index for selecting 100000 rows 30977
Time taken using column name for selecting 100000 rows 32280

Time taken using column index for selecting 100000 rows 30668
Time taken using column name for selecting 100000 rows 32735

Time taken using column index for selecting 100000 rows 30783
Time taken using column name for selecting 100000 rows 31853

Time taken using column index for selecting 100000 rows 30754
Time taken using column name for selecting 100000 rows 32348

Time taken using column index for selecting 100000 rows 31993
Time taken using column name for selecting 100000 rows 32756

Time taken using column index for selecting 100000 rows 30981
Time taken using column name for selecting 100000 rows 32014
---

So the average time taken was pretty much the same - in
fact selection using getColumn() by index was slightly (1%)
faster.

Then I used the following program (on the same table)
to run the benchmark for 20 columns.

-----TestGetColumnNameOrIndex.java (*20* columns)
import java.sql.*;

import oracle.jdbc.driver.*;

public class TestGetColumnNameOrIndex
{

  public static void main(String[] args) throws SQLException
  {
    String testQuery = "select a, b, c, d, e, f, g, h,i, j, k, " +
                               "l, m, n,o, p, q, r, s, t " +
                               " from lots_of_columns";
    DriverManager.registerDriver( new OracleDriver());
    _checkUsage( args );

    OracleConnection connection = _getOracleConnection();
    Statement stmt = connection.createStatement();

    ResultSet rset = stmt.executeQuery(  testQuery );

    // benchmark - first case - using index

    long startTime = System.currentTimeMillis();

    int index1 = rset.findColumn ( "a" );
    int index2 = rset.findColumn ( "b" );
    int index3 = rset.findColumn ( "c" );
    int index4 = rset.findColumn ( "d" );
    int index5 = rset.findColumn ( "e" );
    int index6 = rset.findColumn ( "f" );
    int index7 = rset.findColumn ( "g" );
    int index8 = rset.findColumn ( "h" );
    int index9 = rset.findColumn ( "i" );
    int index10 = rset.findColumn ( "j" );
    int index11 = rset.findColumn ( "k" );
    int index12 = rset.findColumn ( "l" );
    int index13 = rset.findColumn ( "m" );
    int index14 = rset.findColumn ( "n" );
    int index15 = rset.findColumn ( "o" );
    int index16 = rset.findColumn ( "p" );
    int index17 = rset.findColumn ( "q" );
    int index18 = rset.findColumn ( "r" );
    int index19 = rset.findColumn ( "s" );
    int index20 = rset.findColumn ( "t" );

    int x=0;
    while( rset != null && rset.next() )
    {
      int a = rset.getInt( index1 );
      String b = rset.getString( index2 );
      String c = rset.getString( index3 );
      String d = rset.getString( index4 );
      String e = rset.getString( index5 );
      String f = rset.getString( index6 );
      String g = rset.getString( index7 );
      String h = rset.getString( index8 );
      String i = rset.getString( index9 );
      String j = rset.getString( index10 );
      String k = rset.getString( index11 );
      String l = rset.getString( index12 );
      String m = rset.getString( index13 );
      String n = rset.getString( index14 );
      String o = rset.getString( index15 );
      String p = rset.getString( index16 );
      String q = rset.getString( index17 );
      String r = rset.getString( index18 );
      String s = rset.getString( index19 );
      String t = rset.getString( index20 );
      if ( x == 0)
      {
/*
        System.out.println(a + "," +
                           b + "," +
                           c + "," +
                           d + "," +
                           e + "," +
                           f + "," +
                           i + "," +
                           j + "," +
                           k + "," +
                           l + "," +
                           m + "," +
                           n + "," +
                           o + "," +
                           p + "," +
                           q + "," +
                           r + "," +
                           s + "," +
                           t );System.out.flush();
*/
      }
      x++;
    }
    long endTime = System.currentTimeMillis();
    //System.out.println( "total rows found = " + x);
    System.out.println( "Time taken using column index " +
       "for selecting " + x + " rows " + ( endTime - startTime ) );

    rset.close();

    rset = stmt.executeQuery(  testQuery );

    // benchmark - second case - using column name

    startTime = System.currentTimeMillis();

    x=0;
    while( rset != null && rset.next() )
    {
      int a = rset.getInt( "a" );
      String b = rset.getString( "b" );
      String c = rset.getString( "c" );
      String d = rset.getString( "d" );
      String e = rset.getString( "e" );
      String f = rset.getString( "f" );
      String g = rset.getString( "g" );
      String h = rset.getString( "h" );
      String i = rset.getString( "i" );
      String j = rset.getString( "j" );
      String k = rset.getString( "k" );
      String l = rset.getString( "l" );
      String m = rset.getString( "m" );
      String n = rset.getString( "n" );
      String o = rset.getString( "o" );
      String p = rset.getString( "p" );
      String q = rset.getString( "q" );
      String r = rset.getString( "r" );
      String s = rset.getString( "s" );
      String t = rset.getString( "t" );
      if ( x == 0)
      {
/*
        System.out.println(a + "," +
                           b + "," +
                           c + "," +
                           d + "," +
                           e + "," +
                           f + "," +
                           i + "," +
                           j + "," +
                           k + "," +
                           l + "," +
                           m + "," +
                           n + "," +
                           o + "," +
                           p + "," +
                           q + "," +
                           r + "," +
                           s + "," +
                           t );System.out.flush();
*/
      }
      x++;
    }
    endTime = System.currentTimeMillis();
    //System.out.println( "total rows found = " + x);

    System.out.println( "Time taken using column name " +
       "for selecting " + x + " rows " + ( endTime - startTime ) );
    rset.close();
    stmt.close();
    connection.close();
  }

  private static void _checkUsage (String[] args)
  {
    int argc = args.length;
    if( argc != 0 )
    {
      System.out.println( "usage: java TestGetColumnNameOrIndex");
      System.out.flush();
      System.exit(1);
    }
  }

  private static OracleConnection _getOracleConnection ()
    throws SQLException
  {
    String connectionURL = "jdbc:oracle:thin:@lothar:1521:apps";
    OracleConnection connection = (OracleConnection)
      DriverManager.getConnection ( connectionURL, "benchmark",
        "benchmark");

    connection.setAutoCommit ( false) ;
    return connection;
  }

}

--- program ends

the results of 10 runs follows
-- 10 runs (for selecting 20 columns)--
Time taken using column index for selecting 100000 rows 35996
Time taken using column name for selecting 100000 rows 48999

Time taken using column index for selecting 100000 rows 35770
Time taken using column name for selecting 100000 rows 49516

Time taken using column index for selecting 100000 rows 36501
Time taken using column name for selecting 100000 rows 49337

Time taken using column index for selecting 100000 rows 36454
Time taken using column name for selecting 100000 rows 50061

Time taken using column index for selecting 100000 rows 36331
Time taken using column name for selecting 100000 rows 49259

Time taken using column index for selecting 100000 rows 35157
Time taken using column name for selecting 100000 rows 47626

Time taken using column index for selecting 100000 rows 36589
Time taken using column name for selecting 100000 rows 50654
Time taken using column index for selecting 100000 rows 36518
Time taken using column name for selecting 100000 rows 49734
Time taken using column index for selecting 100000 rows 36254
Time taken using column name for selecting 100000 rows 49710
Time taken using column index for selecting 100000 rows 36106
Time taken using column name for selecting 100000 rows 50773
-- 10 runs end

Here we see that on average the getColumn() by index is
37% faster.

So my conclusion is:
If you select 1 column, resultSet.getColumn() by name is around 20% faster as
compared to resultSet.getColumn() by index.
If you select 10 columns, resultSet.getColumn() by name is around the same as
compared to resultSet.getColumn() by index.
If you select 20 columns, resultSet.getColumn() by index is around 37% faster as
compared to resultSet.getColumn() by name.

Overall, if you select many columns, use resultSet.getColumn() by index. If you
select few columns ( <= 5 to 10) then use resultSet.getColumn() by name.

Thanx!
Menon:)
PS: I love your service, your book and am waiting eagerly
for the next book - have put order on your new book
and Cary Milsap's book (his first chapter available online is really
interesting)

Followup:
yes, Cary is an interesting speaker/writer.  You should check him out at the
hotsos conference next march if you can.  it is well worth it.

Rating: 5
re: thanx! August 25, 2003
Reviewer: Menon
"yes, Cary is an interesting speaker/writer.  You should check him out at the
hotsos conference next march if you can.  it is well worth it.
"

Yes - I am really interested in attending the conference -
not just for Cary but to listen to you and JL too!
I had sent a request for re-imbursement to my boss awhile
back - I hope it goes through!

Rating: 5
direct insert VS stored procedure August 26, 2003
Reviewer: Edgar Chupit from Latvia
Hello Tom,

Using the benchmark that was givven above, I have found out that direct
inserting into table (with batch size 100) is almost 60 times faster than
inserting from a stored procedure, and changing batch size have almost no effect
to execution performance of stored procedure (I think it's because you have to
switch from SQL engine to PL/SQL). Are there some approaches to tune procedure
calls?

To benchmark stored procedure in TestBatchUpdates.java I have changed line:
OraclePreparedStatement stmt = (OraclePreparedStatement)
connection.prepareStatement ( stmtString );
to
OracleCallableStatement stmt = (OracleCallableStatement) connection.prepareCall
( stmtString );
and changed stmtString to:
String stmtString = "begin tt1.doInsert(?,?); end;";

Thank you!

Followup:
well, you could save up 100 rows -- send them to a procedure and forall BULK
insert them.  it would be comparable.

Rating: 5
you are right August 26, 2003
Reviewer: Edgar Chupit from Latvia
Thank you! it is working faster, not as fast as direct insert, but way more
faster than simple 'one in a time' pl/sql procedure call.

Rating: 5
To Edgar August 26, 2003
Reviewer: Menon
Hi Edgar
Could you post the code here ?

Thanx!
Menon:)

Rating: 5
code for array processing August 27, 2003
Reviewer: Edgar Chupit from Latvia
Hello,

I'm not real Java programmer, so I guess that this code can be optimized, but my
point was to change original benchmark code, that was kindly provided by Menon.

Results:

Batch1    Batch2    Time1 (hsec)    Time2 (hsec)    %age (time)    %age
(latches)
1    20    1231    70    1758,57%    1852,53%
20    30    92    60    153,33%    148,12%
30    50    67    40    167,50%    155,52%
50    100    45    25    180%    171,01%
100    200    30    17    176%    162,06%
200    500    24    11    218%    148,17%
500    1000    17    11    155%    104,95%
1000    1500    16    11    145,50%    109,24%
1500    2000    16    10    160%    104,73%

SQL*Plus:

SQL> create table tt (id integer, time timestamp default systimestamp, code
integer, val integer);

Table created

SQL> create type integerarray as table of integer;

Type created

SQL> create or replace package tt1 is
  2
  3     procedure doInsert2( aCode integer, aVal integer );
  4     procedure doInsert( aCode integerArray, aVal integerArray );
  5
  6  end tt1;
  7  /

Package created

SQL> create or replace package body tt1 is
  2
  3     procedure doInsert( aCode integerArray, aVal integerArray ) is
  4     begin
  5        forall i in aCode.first .. aCode.last
  6          insert into tt (id,code,val) values (1,aCode(i),aVal(i));
  7     end;
  8
  9     procedure doInsert2( aCode integer, aVal integer ) is
 10     begin
 11          insert into tt (id,code,val) values (1,aCode,aVal);
 12     end;
 13
 14  end tt1;
 15  /

Package body created

SQL> show errors;
No errors for PACKAGE BODY TT.TT1

Java:

---- TestBatchUpdates2.java begins
public class TestBatchUpdates2
{
  public static void main(String[] args) throws SQLException
  {
    DriverManager.registerDriver( new OracleDriver());
    _checkUsage( args );
    _setBatchValues ( args );
    OracleConnection connection = _getOracleConnection();
    _prepareBenchmarkStatements ( connection );

    // we are going to benchmark the following statement
    String stmtString = "begin tt1.doInsert(?,?); end;";

    OracleCallableStatement stmt = (OracleCallableStatement)
connection.prepareCall ( stmtString );
    long updateCount = 0;
    _benchmarkStatementArray[BENCHMARK_START_INDEX].execute();

    ArrayDescriptor desc = ArrayDescriptor.createDescriptor( "TT.INTEGERARRAY",
connection );

    // for each batch size being tested
    for( int k = 0; k < UPDATE_BATCH_SIZES_BEING_TESTED.length; k++)
    {
      // benchmark starts
      long startTime = System.currentTimeMillis();
      updateCount = 0;
      int[] aCode = new int [ UPDATE_BATCH_SIZES_BEING_TESTED[k] ];
      int[] aVal = new int [ UPDATE_BATCH_SIZES_BEING_TESTED[k] ];

      for( int i=0; i < NUMBER_OF_RECORDS_INSERTED; i++)
      {
    if( i%UPDATE_BATCH_SIZES_BEING_TESTED[k] == 0 && i > 0 )
        {
           stmt.setArray(1,new ARRAY (desc, connection, aCode) );
           stmt.setArray(2,new ARRAY (desc, connection, aVal) );
           stmt.execute();

           // if NUMBER_OF_RECORDS_INSERTED%UPDATE_BATCH_SIZES_BEING_TESTED[k]
!= 0 than
           //   last portion of data will be smaller than
UPDATE_BATCH_SIZES_BEING_TESTED[k]
           if( (NUMBER_OF_RECORDS_INSERTED-i > 0) &&
(UPDATE_BATCH_SIZES_BEING_TESTED[k] > NUMBER_OF_RECORDS_INSERTED-i) )
           {
              aCode = new int [ NUMBER_OF_RECORDS_INSERTED-i ];
              aVal = new int [ NUMBER_OF_RECORDS_INSERTED-i ];
           }
        }
        aCode[(i%UPDATE_BATCH_SIZES_BEING_TESTED[k])] = i;
        aVal[(i%UPDATE_BATCH_SIZES_BEING_TESTED[k])] = i*i*3;
      }
      stmt.setArray(1,new ARRAY (desc, connection, aCode) );
      stmt.setArray(2,new ARRAY (desc, connection, aVal) );
      stmt.execute();
      connection.commit();

      long endTime = System.currentTimeMillis();
      if( k == 0 ) // set the middle point only in first iteration
        _benchmarkStatementArray[BENCHMARK_MIDDLE_INDEX].execute();
      // benchmark ends
      long timeTaken = (endTime - startTime);
      benchmarkTimingResults[k] = timeTaken;
    }
    _benchmarkStatementArray[BENCHMARK_STOP_INDEX].execute();
    _printBenchmarkResults ();
    connection.commit();
    System.out.println( "----Java Timer based results begin\n" );
    for (int i=0; i < UPDATE_BATCH_SIZES_BEING_TESTED.length; i++)
    {
      System.out.println( "Time taken for inserting " +
        NUMBER_OF_RECORDS_INSERTED +
        " with a batch size of " +
        UPDATE_BATCH_SIZES_BEING_TESTED[i] +
       " = " + benchmarkTimingResults[i] );
      System.out.flush();
    }
    System.out.println( "----Java Timer based results end\n" );
    stmt.close();
    _closeBenchmarkStatements ( connection );
    connection.close();
  }
  private static void _closeBenchmarkStatements (
    OracleConnection connection )  throws SQLException
  {
    for( int i=0; i < _benchmarkStatementArray.length; i++)
    {
      _benchmarkStatementArray[i].close();
    }
  }
  private static void _prepareBenchmarkStatements (
    OracleConnection connection ) throws SQLException
  {
    _benchmarkStatementArray[BENCHMARK_START_INDEX]=
      (OracleCallableStatement) connection.prepareCall( BENCHMARK_START );
    _benchmarkStatementArray[BENCHMARK_MIDDLE_INDEX]=
      (OracleCallableStatement) connection.prepareCall( BENCHMARK_MIDDLE );
    _benchmarkStatementArray[BENCHMARK_STOP_INDEX]=
      (OracleCallableStatement) connection.prepareCall( BENCHMARK_STOP );
    _benchmarkStatementArray[BENCHMARK_STOP_INDEX].setInt(1,
      BENCHMARK_DIFFERENCE_THRESHOLD);
    _dbmsOutput = new DbmsOutput ( connection );
    _dbmsOutput.enable ( DBMS_OUTPUT_BUFFER_SIZE );
  }
  private static void _checkUsage (String[] args)
  {
    int argc = args.length;
    if( argc != 2 )
    {
      System.out.println( "usage: java TestBatchUpdates3 <batch1> <batch2>");
      System.out.flush();
      System.exit(1);
    }
  }
  private static OracleConnection _getOracleConnection ()
    throws SQLException
  {
    String connectionURL = "jdbc:oracle:thin:@127.0.0.1:1521:TEST01";
    OracleConnection connection = (OracleConnection)
      DriverManager.getConnection ( connectionURL, "tt", "tt");
    connection.setAutoCommit ( false) ;
    return connection;
  }
  private static void _setBatchValues (String[] args)
  {
    int firstBatchValue = Integer.parseInt( args[0] );
    int secondBatchValue = Integer.parseInt( args[1] );
    UPDATE_BATCH_SIZES_BEING_TESTED[0] = firstBatchValue;
    UPDATE_BATCH_SIZES_BEING_TESTED[1] = secondBatchValue;
    System.out.println( "First batch value = " +
      UPDATE_BATCH_SIZES_BEING_TESTED[0]);System.out.flush();
    System.out.println( "Second batch value = " +
      UPDATE_BATCH_SIZES_BEING_TESTED[1]);System.out.flush();
  }
  private static void _printBenchmarkResults()
    throws SQLException
  {
    _dbmsOutput.show();
    _dbmsOutput.close();
  }

  private static int[] UPDATE_BATCH_SIZES_BEING_TESTED = new int[2];
  private static long[] benchmarkTimingResults = new
long[UPDATE_BATCH_SIZES_BEING_TESTED.length];
  private static String BENCHMARK_START = "begin runstats_pkg.rs_start; end;";
  private static String BENCHMARK_MIDDLE = "begin runstats_pkg.rs_middle; end;";
  private static String BENCHMARK_STOP = "begin runstats_pkg.rs_stop(?); end;";
  private static OracleCallableStatement[] _benchmarkStatementArray = new
OracleCallableStatement[3];

  private static DbmsOutput _dbmsOutput;
  private static final int DBMS_OUTPUT_BUFFER_SIZE = 1000000;
  private static final int BENCHMARK_START_INDEX = 0;
  private static final int BENCHMARK_MIDDLE_INDEX = 1;
  private static final int BENCHMARK_STOP_INDEX = 2;
  private static final int BENCHMARK_DIFFERENCE_THRESHOLD = 200;
  private static final int NUMBER_OF_RECORDS_INSERTED = 10000;
}
---- TestBatchUpdates2.java ends

Rating: 5
thanx Edgar! August 27, 2003
Reviewer: Menon

Rating: 4
view/table information to see java stored procedure September 25, 2003
Reviewer: A reader
Tom,

If i want to know, what are all the java stored procedure that are created in my
schema - which view/table gives me that.

Please let me know

Thanks

Followup:
select * from user_objects where object_type like 'JAVA%'

Rating: 5
Thanks a lot September 25, 2003
Reviewer: A reader

September 25, 2003 Atul -- Thanks for the question regarding "Using XML...", version 8i
You Asked
Hi Tom,

    I want to create a file (it's a spool from a table) in XML layout. What is
the best way to do it. The only way I can think of is

select '<name>'||name||'<name/>, '<address>'||address||'<address/>
 from T

   Can you suggest anything better than this. Also Oracle provides an utility to
create XML. What for is that?

Atul
and we said...
I asked Sean Dillon, our local XML guy to take a look at this. He says...

If you haven't already, you need to download and install the latest Oracle XML
Developer's Kits (XDK's). What you are trying to do is relatively simple, and
with the XDK, you can generate XML from SQL queries using PL/SQL, Java, C or
C++. The following command on the command line generates an XML document
containing the first 4 rows of the SCOTT.EMP table:

$ java OracleXML getXML -user "scott/tiger" "select * from emp where rownum < 5"
> emp.xml

$ cat emp.xml
<?xml version = '1.0'?>
<ROWSET>
   <ROW num="1">
      <EMPNO>7369</EMPNO>
      <ENAME>SMITH</ENAME>
      <JOB>CLERK</JOB>
      <MGR>7902</MGR>
      <HIREDATE>12/17/1980 0:0:0</HIREDATE>
      <SAL>800</SAL>
      <DEPTNO>20</DEPTNO>
   </ROW>
   <ROW num="2">
      <EMPNO>7499</EMPNO>
      <ENAME>ALLEN</ENAME>
      <JOB>SALESMAN</JOB>
      <MGR>7698</MGR>
      <HIREDATE>2/20/1981 0:0:0</HIREDATE>
      <SAL>1600</SAL>
      <COMM>300</COMM>
      <DEPTNO>30</DEPTNO>
   </ROW>
   <ROW num="3">
      <EMPNO>7521</EMPNO>
      <ENAME>WARD</ENAME>
      <JOB>SALESMAN</JOB>
      <MGR>7698</MGR>
      <HIREDATE>2/22/1981 0:0:0</HIREDATE>
      <SAL>1250</SAL>
      <COMM>500</COMM>
      <DEPTNO>30</DEPTNO>
   </ROW>
   <ROW num="4">
      <EMPNO>7566</EMPNO>
      <ENAME>JONES</ENAME>
      <JOB>MANAGER</JOB>
      <MGR>7839</MGR>
      <HIREDATE>4/2/1981 0:0:0</HIREDATE>
      <SAL>2975</SAL>
      <DEPTNO>20</DEPTNO>
   </ROW>
</ROWSET>

There are a variety of options with this Java class (OracleXML) as well...

$ java OracleXML
OracleXML-Error:wrong argument list
Call with

OracleXML getXML
 [-user "username/password"]    -- the user name and password
 [-conn "JDBC_connect_string"]  -- JDBC connect string
 [-withDTD | -withSchema]       -- generate the DTD/Schema
 [-rowsetTag <rowsetTag>]       -- document tag name
 [-rowTag    <rowTag>]          -- row element tag name
 [-rowIdAttr <attrName>]        -- row-id attribute name
 [-rowIdColumn <column_name>]   -- db-column to use for the row id
 [-collectionIdAttr <attrName>] -- collection element-id attribute
 [-useTypeForCollElemTag]       -- use type name for coll-elem tag
 [-useNullAttrId]               -- use a null attribute
 [-styleSheet <URI>]            -- stylesheet processing instruction header
 [-styleSheetType <type>]       -- stylesheet header type (e.g.text/xsl)
 [-setXSLT <URI>]               -- XSLT to apply to XML doc
 [-setXSLTRef <URI>]            -- XSLT external entity reference
 [-useLowerCase| -useUpperCase] -- the case of the tag names
 [-withEscaping]                -- if necessary do SQL-XML name escaping
 [-errorTag <errorTagName>]     -- error tag name
 [-raiseException]              -- raise exceptions for errors
 [-raiseNoRowsException]        -- raise exception if no returned
 [-maxRows   <maxRows>]         -- maximum rows in output
 [-skipRows  <skipRows>]        -- rows to skip in output
 [-encoding <encoding_name>]    -- encoding to be used
 [-dateFormat <date format>]    -- date format to be used
 (<query>| -fileName <sqlfile>) -- SQL query | file containing the query

 -- OR --

OracleXML putXML
 [-user "username/password"]    -- the user name and password
 [-conn "JDBC_connect_string"]  -- JDBC connect string
 [-batchSize <size>]            -- number of inserts executed at a time
 [-commitBatch <size>]          -- number of inserts commited at a time
 [-rowTag <rowTagName>]         -- the name for row elements
 [-dateFormat <format>]         -- the format of date elements
 [-withEscaping]                -- if necessary do SQL-XML name escaping
 [-ignoreCase]                  -- ignore the case of the tag namess
 [-setXSLT <URI>]               -- XSLT to apply to XML doc
 [-setXSLTRef <URI>]            -- external entity reference for XSLT doc
 [-fileName fileName |          -- the XML document file name  or
  -URL url |                     - URL   or
  -xmlDoc <XMLDocumentString>]   - XML string
 <tableName>                    -- the table name to put into

This is Java, however, and it might not fit your needs at all. Alternatively,
you could use the PL/SQL equivalent functionality to generate XML from a SQL
query using the package DBMS_XMLQUERY:

SQL> var my_xml clob
SQL> set autoprint on
SQL> declare
  2  --  l_clob clob;
  3    l_ctx  dbms_xmlgen.ctxHandle;
  4    l_sql  varchar2(400);
  5  begin
  6    l_sql := 'select *
  7                from emp
  8               where rownum < 5';
  9
 10    l_ctx := dbms_xmlgen.newContext(l_sql);
 11    :my_xml := dbms_xmlgen.getXml(l_ctx); -- or you could assign it to l_clob
 12  end;
 13  /

PL/SQL procedure successfully completed.

MY_XML
----------------------------------------------
<?xml version="1.0"?>
<ROWSET>
 <ROW>
  <EMPNO>7369</EMPNO>
  <ENAME>SMITH</ENAME>
  <JOB>CLERK</JOB>
  <MGR>7902</MGR>
  <HIREDATE>17-DEC-80</HIREDATE>
  <SAL>800</SAL>
  <DEPTNO>20</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7499</EMPNO>
  <ENAME>ALLEN</ENAME>
  <JOB>SALESMAN</JOB>
  <MGR>7698</MGR>
  <HIREDATE>20-FEB-81</HIREDATE>
  <SAL>1600</SAL>
  <COMM>300</COMM>
  <DEPTNO>30</DEPTNO>
 </ROW>
 <ROW>
 <EMPNO>7521</EMPNO>
  <ENAME>WARD</ENAME>
  <JOB>SALESMAN</JOB>
  <MGR>7698</MGR>
  <HIREDATE>22-FEB-81</HIREDATE>
  <SAL>1250</SAL>
  <COMM>500</COMM>
  <DEPTNO>30</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7566</EMPNO>
  <ENAME>JONES</ENAME>
  <JOB>MANAGER</JOB>
  <MGR>7839</MGR>
  <HIREDATE>02-APR-81</HIREDATE>
  <SAL>2975</SAL>
  <DEPTNO>20</DEPTNO>
 </ROW>
</ROWSET>

SQL> set autoprint off

The DBMS_XMLQUERY package has a GETXML function that takes in a SQL query and
returns a CLOB without having to create a context and go into the overhead, but
I recommend using the context handle in the PL/SQL block; it gives you an
enormous amount of functionality (most of which is absolutely necessary in a
production application) such as binding variables to your query, generating a
DTD or Schema for your XML, modifying the names of the ROWSET and ROW elements,
limiting the number of rows returned, etc.

You can find the latest copies of the XML Developer's Kits on the Oracle
Technology Network's XML pages...
http://technet.oracle.com/tech/xml/content.html
Hope that helps!
Review & Followup
Rating: 4
Can we do for HTML also? January 11, 2002
Reviewer: AB from UK
Is there an HTML equivalent? I have no Oracle Web tools and won't be able to
have either.

I currently have my intranet set up to monitor db activity. I have a UNIX box
with a webserver daemon and a PC with a browser. Currently I call an HTML script
on the UNIX box which calls a shell script. This shell script executes a
package.procedure. The procedure "DBMS_OUTPUTs" a combination of html tags and
the resultset from cursors. The shell script pipes this back to the browser. As
dynamic as I can get with my set up, so an "html-generator" like the XML one
above would be great.

Followup:
AB,

Oracle8.1.7 comes with an Apache web listener and MOD_PLSQL, an Apache add-on
that lets you write PL/SQL procedures to produce web pages. If you have access
to an 8.1.7 database, the code you need to install and configure the PL/SQL Web
Toolkit is located in the $ORACLE_HOME/Apache/modplsql directory.

The PL/SQL Web Toolkit let's you spool data to an HTML buffer instead of the
DBMS_OUTPUT buffer. The toolkit contains packages (with procedures and
functions) for creating html. Using this approach, you could do something like
this:

connect scott/tiger

create or replace package xml_reports as
  procedure employees;
  procedure departments;
end xml_reports;
/

create or replace package body xml_reports as

  procedure to_html(p_xml in out nocopy clob) is
    l_xml  varchar2(30000);
    l_line varchar2(2000);
    l_amt  pls_integer := 30000;
    l_idx  pls_integer := 1;
  begin
    for i in 1 .. ceil(dbms_lob.getlength(p_xml)/l_amt) loop
      l_xml := dbms_lob.substr(p_xml, l_amt, l_idx);
      loop
        exit when l_xml is null;
        l_line := substr(l_xml, 1, instr(l_xml, chr(10))-1);
        -- THIS IS THE SQLPLUS OUTPUT STYLE
        --dbms_output.put_line('|' || l_line);
        -- THIS IS THE HTML OUTPUT STYLE
        htp.put_line(l_line);
        l_xml := substr(l_xml, instr(l_xml, chr(10))+1);
      end loop;
    end loop;
  end to_html;

  procedure employees
  is
    l_clob clob;
    l_ctx  dbms_xmlgen.ctxHandle;
    l_sql  varchar2( 400 );
  begin
    l_sql := 'select *
                from emp';
    l_ctx := dbms_xmlgen.newContext( l_sql );
    l_clob := dbms_xmlgen.getXml( l_ctx );
    to_html( l_clob );
  end employees;

  procedure departments
  is
    l_clob clob;
    l_ctx  dbms_xmlgen.ctxHandle;
    l_sql  varchar2( 400 );
  begin
    l_sql := 'select *
                from dept';
    l_ctx := dbms_xmlgen.newContext( l_sql );
    l_clob := dbms_xmlgen.getXml( l_ctx );
    to_html( l_clob );
  end departments;
end xml_reports;
/
show error

With this package procedure, I could put this in my browser:
http://servername:port/pls/dbconfig/xml_reports.employees
...and get the following results in my browser:

<?xml version="1.0"?>
<ROWSET>
 <ROW>
  <EMPNO>7369</EMPNO>
  <ENAME>SMITH</ENAME>
  <JOB>CLERK</JOB>
  <MGR>7902</MGR>
  <HIREDATE>17-DEC-80</HIREDATE>
  <SAL>800</SAL>
  <DEPTNO>20</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7499</EMPNO>
  <ENAME>ALLEN</ENAME>
  <JOB>SALESMAN</JOB>
  <MGR>7698</MGR>
  <HIREDATE>20-FEB-81</HIREDATE>
  <SAL>1600</SAL>
  <COMM>300</COMM>
  <DEPTNO>30</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7521</EMPNO>
  <ENAME>WARD</ENAME>
  <JOB>SALESMAN</JOB>
  <MGR>7698</MGR>
  <HIREDATE>22-FEB-81</HIREDATE>
  <SAL>1250</SAL>
  <COMM>500</COMM>
  <DEPTNO>30</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7566</EMPNO>
  <ENAME>JONES</ENAME>
  <JOB>MANAGER</JOB>
  <MGR>7839</MGR>
  <HIREDATE>02-APR-81</HIREDATE>
  <SAL>2975</SAL>
  <DEPTNO>20</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7654</EMPNO>
  <ENAME>MARTIN</ENAME>
  <JOB>SALESMAN</JOB>
  <MGR>7698</MGR>
  <HIREDATE>28-SEP-81</HIREDATE>
  <SAL>1250</SAL>
  <COMM>1400</COMM>
  <DEPTNO>30</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7698</EMPNO>
  <ENAME>BLAKE</ENAME>
  <JOB>MANAGER</JOB>
  <MGR>7839</MGR>
  <HIREDATE>01-MAY-81</HIREDATE>
  <SAL>2850</SAL>
  <DEPTNO>30</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7782</EMPNO>
  <ENAME>CLARK</ENAME>
  <JOB>MANAGER</JOB>
  <MGR>7839</MGR>
  <HIREDATE>09-JUN-81</HIREDATE>
  <SAL>2450</SAL>
  <DEPTNO>10</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7788</EMPNO>
  <ENAME>SCOTT</ENAME>
  <JOB>ANALYST</JOB>
  <MGR>7566</MGR>
  <HIREDATE>19-APR-87</HIREDATE>
  <SAL>3000</SAL>
  <DEPTNO>20</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7839</EMPNO>
  <ENAME>KING</ENAME>
  <JOB>PRESIDENT</JOB>
  <HIREDATE>17-NOV-81</HIREDATE>
  <SAL>5000</SAL>
  <DEPTNO>10</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7844</EMPNO>
  <ENAME>TURNER</ENAME>
  <JOB>SALESMAN</JOB>
  <MGR>7698</MGR>
  <HIREDATE>08-SEP-81</HIREDATE>
  <SAL>1500</SAL>
  <COMM>0</COMM>
  <DEPTNO>30</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7876</EMPNO>
  <ENAME>ADAMS</ENAME>
  <JOB>CLERK</JOB>
  <MGR>7788</MGR>
  <HIREDATE>23-MAY-87</HIREDATE>
  <SAL>1100</SAL>
  <DEPTNO>20</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7900</EMPNO>
  <ENAME>JAMES</ENAME>
  <JOB>CLERK</JOB>
  <MGR>7698</MGR>
  <HIREDATE>03-DEC-81</HIREDATE>
  <SAL>950</SAL>
  <DEPTNO>30</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7902</EMPNO>
  <ENAME>FORD</ENAME>
  <JOB>ANALYST</JOB>
  <MGR>7566</MGR>
  <HIREDATE>03-DEC-81</HIREDATE>
  <SAL>3000</SAL>
  <DEPTNO>20</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7934</EMPNO>
  <ENAME>MILLER</ENAME>
  <JOB>CLERK</JOB>
  <MGR>7782</MGR>
  <HIREDATE>23-JAN-82</HIREDATE>
  <SAL>1300</SAL>
  <DEPTNO>10</DEPTNO>
 </ROW>
</ROWSET>

For more information on the packages that make up the package, look here:
http://db.lib.vt.edu/ows-adoc/psqlwtlk.htm
I hope that helps!

-SEAN- ;-)

Rating: 5
PL/SQL to HTML January 11, 2002
Reviewer: AB from UK
Tom,

Thankyou very much. This was exactly what I was looking for. I just need to work
out how to format the output into grids etc now.

BTW, you are THE Oracle resource on the www. If I had as many people praising me
as they do you, I'd have the ego the size of a rock star !!!

While I'm at it, your book is AMAZING also. Possibly the best actually. We don't
need continual syntax manuals, we need APPROACH manuals, and you sure are
changing the way I approach my applications.

Thanks again.
AB

Rating: 4
XMLGEN and Cursors January 15, 2002
Reviewer: Edgar from Latvia
Hello Tom and Sean!

This is all great, that we can use dbms_xmlgen to generate xml directrly from
PL/SQL, but as I can see in manuals dbms_xmlgen.newContext() accepts parameter
as simple static sql, from this site we all know that static sql is evil and
that we have to use binding capabilities. Is there a way to generate xml from
select passing as argument cursor or something like this?

Followup:
Hi Edgar Sean here...

Yes good point, I prolly should have included the bind variables as a part of
the example... There are a couple of ways to handle this. Luckily, in
DBMS_XMLQUERY we have the SETBINDVALUE procedure, so you could code it this way:

SQL> create or replace function get_emps_in_dept( p_deptno in number )
  2    return clob
  3  is
  4    l_ctx dbms_xmlquery.ctxType;
  5    l_sql varchar2(100);
  6    l_xml clob;
  7  begin
  8    l_sql := 'select * from emp where deptno = :deptno';
  9    dbms_lob.createtemporary( l_xml, true, dbms_lob.session );
 10    l_ctx := dbms_xmlquery.newContext( l_sql );
 11
 12    dbms_xmlquery.setbindvalue( l_ctx, 'deptno', p_deptno );
 13
 14    dbms_xmlquery.setRaiseNoRowsException( l_ctx,false );
 15
 16    -- get the xml fm the context
 17    l_xml := dbms_xmlquery.getXML( l_ctx );
 18
 19    dbms_xmlquery.closeContext(l_ctx);
 20    return l_xml;
 21  exception
 22    when others then
 23      dbms_xmlquery.closeContext(l_ctx);
 24      raise;
 25  end get_emps_in_dept;
 26  /
Function created.

Then, if we test it, we can look in the v$sqlarea for matches:

SQL> declare
  2    l_clob clob;
  3  begin
  4    l_clob := get_emps_in_dept( 10 );
  5  end;
  6  /

PL/SQL procedure successfully completed.

SQL> select count(*), executions
  2    from v$sqlarea
  3   where sql_text like 'select * from emp where deptno =%'
  4   group by executions;

  COUNT(*) EXECUTIONS
---------- ----------
         1          1

If we run it a whole bunch of times, we get the same response:

SQL> declare
  2    l_clob clob;
  3  begin
  4    for i in 1 .. 100 loop
  5      l_clob := get_emps_in_dept( i );
  6    end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

SQL>
SQL> select count(*), executions
  2    from v$sqlarea
  3   where sql_text like 'select * from emp where deptno =%'
  4   group by executions;

  COUNT(*) EXECUTIONS
---------- ----------
         1        101
In the case where an API doesn't have the ability to set bind variables, there
are a couple of approaches you can use. Cursor sharing is the way to tell the
database to do this for us:

execute immediate 'alter session set cursor_sharing = force';
then call the API
execute immediate 'alter session set cursor_sharing = exact';

will do it in 8.1.6 and up (an autobinder feature -- good for wierd cases like
this)

As well, you can use the technique outlined on:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279
using an application context.  You would use dbms_session.set_context to "bind"
instead of dbms_sql.bind_variable.

Rating: 3
Using XML April 26, 2003
Reviewer: reader
SQL>desc employees ;
 Name                                                  Null?    Type
 ----------------------------------------------------- -----
 ID                                                             NUMBER
 FNAME                                                          VARCHAR2(100)
 LNAME                                                          VARCHAR2(100)

SQL>SELECT XMLELEMENT ( "Emp", XMLATTRIBUTES (e.id,e.fname ||' ' || e.lname AS
"name")) AS "result" FROM employees e;
/

result()
============================================================
XMLTYPE()
XMLTYPE()
XMLTYPE()

what is wrong in this, i am not getting the expected output. please clarify.

Thanks

Followup:
works for me --

scott@ORA920LAP> sELECT XMLELEMENT ( "Emp", XMLATTRIBUTES (e.empno,e.ename ||' '
|| e.job AS
  2  "name")) AS "result" FROM emp e;

result
---------------------------------------------------------------------------------
--------------------------------------------------
<Emp EMPNO="7369" name="SMITH CLERK"></Emp>
<Emp EMPNO="7499" name="ALLEN SALESMAN"></Emp>
<Emp EMPNO="7521" name="WARD SALESMAN"></Emp>
<Emp EMPNO="7566" name="JONES MANAGER"></Emp>
<Emp EMPNO="7654" name="MARTIN SALESMAN"></Emp>
<Emp EMPNO="7698" name="BLAKE MANAGER"></Emp>
<Emp EMPNO="7782" name="CLARK MANAGER"></Emp>
<Emp EMPNO="7788" name="SCOTT ANALYST"></Emp>
<Emp EMPNO="7839" name="KING PRESIDENT"></Emp>
<Emp EMPNO="7844" name="TURNER SALESMAN"></Emp>
<Emp EMPNO="7876" name="ADAMS CLERK"></Emp>
<Emp EMPNO="7900" name="JAMES CLERK"></Emp>
<Emp EMPNO="7902" name="FORD ANALYST"></Emp>
<Emp EMPNO="7934" name="MILLER CLERK"></Emp>

14 rows selected.

Rating: 2
Using XML April 26, 2003
Reviewer: reader
SQL>select xmlelement("Employees",
  2               xmlagg(
  3                 xmlelement(
  4                   "Employee",
  5                   xmlforest(empno, ename, sal)))) as "result"
  6        from emp
  7  ;

result()
============================================================
XMLTYPE()

I am stuck here,.. not able to proceed. can it be because of any settings that
needs to be changed. remember it was working earlier..

Followup:
it works for me.

I do not remember that it was working earlier -- i've no clue who you are.

You are not cutting and pasting from sqlplus -- result() wouldn't appear there,
it would be result and we use ---- not ======= so I can only guess you are using
toad or something else and they are punting when it comes to displaying an
object type.

Rating: 3
April 26, 2003
Reviewer: A reader
Tom,

trust me - It didnt work...

i restarted and put the same query - it worked.

i have been out for dinner, after 45 minutes, i read your reply, thought i would
say its working now - but again the query failed.

the sqlplus which was working - i still have it opened. opened a new session,
its failing now. !

SQL>       select xmlelement("Employees",
  2                         xmlagg(
  3                           xmlelement(
  4                             "Employee",
  5                             xmlforest(empno, ename, sal)))) as "result"
  6                  from emp
  7            ;

result()
============================================================
XMLTYPE()

the other window which is working :

SQL>        select xmlelement("Employees",
  2                         xmlagg(
  3                           xmlelement(
  4                             "Employee",
  5                             xmlforest(empno, ename, sal)))) as "result"
  6                  from emp
  7            ;

result
--------------------------------------------------------------------------------
<Employees>
  <Employee>
    <EMPNO>7369</EMPNO>
    <ENAME>SMITH</ENAME>
    <SAL>800</SAL>
  </Employee>
  <Employee>
    <EMPNO>7499</EMPNO>
    <ENAME>ALLEN</ENAME>
    <SAL>1600</SAL>
  </Employee>

result
--------------------------------------------------------------------------------
  <Employee>
    <EMPNO>7521</EMPNO>

.... continues

    <SAL>1300</SAL>
  </Employee>
</Employees>

may be i need to restart and test it again.

I would never waste your time, that you dedicate for us. i daily visit your site
and learn a lot.

Thanks

Rating: 3
May 21, 2003
Reviewer: Shailesh from India
I created package xml_reports and two procedures as given
in the example. However when i place the link
http://172.17.19.140/xml_reports.employees
I get error
Page cannot be found.
Also there is a message given
HTTP-404 - File not found.
My apache server is running on
SQL> host
$ ps -ef|grep apache
     www  3363  1914  0  Apr 11  ?         0:00 /opt/apache/bin/httpd
     www  1926  1914  0  Apr 10  ?         0:00 /opt/apache/bin/httpd
    root  1914     1  0  Apr 10  ?         2:15 /opt/apache/bin/httpd
     www  1927  1914  0  Apr 10  ?         0:00 /opt/apache/bin/httpd
     www  1928  1914  0  Apr 10  ?         0:00 /opt/apache/bin/httpd
     www  1929  1914  0  Apr 10  ?         0:00 /opt/apache/bin/httpd
     www  1930  1914  0  Apr 10  ?         0:00 /opt/apache/bin/httpd
     www 11484  1914  0  Apr 24  ?         0:00 /opt/apache/bin/httpd
     www 17274  1914  0  Apr 17  ?         0:00 /opt/apache/bin/httpd
     www 20836  1914  0  Apr 30  ?         0:00 /opt/apache/bin/httpd
oracle9i 17638 16418  0 16:37:13 pts/tk    0:00 grep apache

The OS is HP-UX 11i and db version is 9.0.1.4.
please help

Regards
Shailesh

Rating: 5
How can we sort the extract() results? July 16, 2003
Reviewer: A Reader from USA
We have a query like the following:

select * from (select extract(xmldetail, '/XMLDetail/Detail/text()')
from t_with_xmltype) order by 1
       *
We get the following error:
ERROR at line 1:
ORA-22950: cannot ORDER objects without MAP or ORDER method

How can we sort the result set returned from an XML query?
Thank you very much for your help.

Followup:
well, that is an "object", an XML document, being returned.

if the detail is a scalar, perhaps you meant to use extractvalue?

Rating: 3
Generate XML from Type as Table of Object July 25, 2003
Reviewer: JK from Boston MA USA
Hi Tom,

Can you please suggest what is wrong in this?
 CREATE OR REPLACE TYPE datastack AS OBJECT (
               ATTRIB_NAME  VARCHAR2(40),  --This should be the Tag
               ATTRIB_VALUE  VARCHAR2(40)); -- This is the value
 Inside my Procedure I have
  TYPE datacontext is TABLE OF datastack;
  dump_data_stack datacontext     := datacontext();

  The dump_data_stack is updated with data. This is something similar to a
call_stack but will be populated with data from within the procedures. If there
is an exception in the procedure, this data_stack will be dumped into error_log
table as a XML.

 Here comes my problem.
 l_ctx := dbms_xmlquery.newContext(
        'select * from THE ( select cast( dump_data_stack as datacontext )from
dual ) a');

 l_xml_payload := dbms_xmlquery.getXml(l_ctx);

 throws
 "XML IS: ***<?xml version = '1.0'?>
<ERROR>oracle.xml.sql.OracleXMLSQLException:
ORA-00902: invalid datatype" this error.

 I couldnt resolve it. So, i tried this:
 FOR stack_index IN 1 .. top_of_data_stack
 LOOP
  l_attrib_name := '"' || dump_data_stack (stack_index).attrib_name || '"';
  SELECT XMLElement (l_attrib_name,
          dump_data_stack (stack_index).attrib_value )
  INTO l_xml
  FROM DUAL;
  l_xml_payload := l_xml.getClobVal();
  l_call_stack_text := l_call_stack_text || l_xml_payload;

 This prints as
"<L_ATTRIB_NAME>234567899</L_ATTRIB_NAME><L_ATTRIB_NAME>Mary M
Beth</L_ATTRIB_NAME><L_ATTRIB_NAME>Margin Account</L_ATTRIB_NAME> ..."

 I expected/require it to be something like this:
"<SSN>234567899</SSN><NAME>Mary M Beth</NAME> <ACCOUNT>Margin Account</ACCOUNT>
..."

 Can you please suggest some option? Also is there a better way to dump data log
to the table? I update the stack through the course of the procedure and then
plan to dump it.

Thanks & Regards
JK

Followup:
datacontext must be defined at the sql level, it is not visible to SQL when it
is defined in that procedure.

Rating: 5
How to separate the xml results September 03, 2003
Reviewer: Yong Wu from CA, USA
I ran the following query and expect results like

Administrative
Business

But the results are always concatenated. How to fix the problem?

Thanks,

SQL> l
  1  begin
  2   for c in (select
x.content.extract('/School//Category/@CodeName').getStringVal()
  3  Category
  4  from xml_data x) loop
  5  dbms_output.put_line(c.category);
  6  end loop;
  7* end ;
SQL> /
AdministrativeBusiness

Followup:
hmm, no table xml_data in my database

no xml to peek at.

sort of hard to say anything without a full, yet concise and simple, test case
(which I find I frequently find my own mistakes in when putting it together)

Rating: 5
Here is test data September 04, 2003
Reviewer: Yong Wu from CA, USA
Tom,

Here is the test data. Thanks for your help.

create table xml_data
  (id number,
   name varchar2(60),
   status varchar2(20),
   created_date date default sysdate,
   content xmltype);

insert into xml_data values(1,'EDU Program','New',sysdate,
'<School codekey="3028" Name="Vatterott College">
     <QualificationMajor CodeName=”Undergraduate” DisplayName="Undergraduate"
Description=”Undergraduate”>
         <QualificationMinor CodeName=”Associate” DisplayName="Associate"
Description="Associate">
         <QualificationMinor CodeName=”Bachelor” DisplayName="Bachelor"
Description=” Bachelor">
     </Qualification>
     <QualificationMajor CodeName="Graduate"  DisplayName="Graduate"
Description=”Undergraduate”>
         <QualificationMinor CodeName="Bachelor" DisplayName="Bachelor"
Description="Bachelor">
         <QualificationMinor CodeName="Doctorate" DisplayName="Doctorate"
Description="Doctorate">
     </Qualification>

     <Campus codekey="302072" Name="Cleveland">
       <Category CodeName="Administrative" DisplayName="Administrative">
         <SubCategory CodeName="BUSINESSADMINISTRATIVEASSISTANT(1OF2)"
DisplayName="Business Administrative Assistant">
           <Program CodeName="210AASD" DisplayName="Administrative Applications
Specialist" AlternativeName="210AASD" />
       <Program CodeName="210AOSA" DisplayName="Administrative Office
Specialist" AlternativeName="210AOSA" />
       <Program CodeName="210COAD" DisplayName="Computer Office Assistant"
AlternativeName="210COAD" />
     </SubCategory>
       </Category>
       <Category CodeName="Business" DisplayName="Business">
     <SubCategory CodeName="ACCOUNTING(1OF2)" DisplayName="Accounting">
           <Program CodeName="210ACCA" DisplayName="Accounting "
AlternativeName="210ACCA" />
         </SubCategory>
       </Category>
     </Campus>
</School>');

SQL> l
  1  begin
  2   for c in (select
x.content.extract('/School//Category/@CodeName').getStringVal()
  3  Category
  4  from xml_data x) loop
  5  dbms_output.put_line(c.category);
  6  end loop;
  7* end ;
SQL> /
AdministrativeBusiness

Followup:
sorry -- this example is so far away from even slightly working.....

you have an xmltype, you are inserting char

you have a <QualificationMajor> tag that is ended with /Qualification

you have QualificationMinor tags that are not even ended....

please, make sure the stuff at least *runs*

Rating: 5
September 05, 2003
Reviewer: Yong Wu from CA, USA
I am realy sorry for posting wrong sql. Here is the query.

insert into xml_data values(1,'EDU Program','New',sysdate,
xmltype('<School codekey="3028" Name="Vatterott College">
     <QualificationMajor CodeName="Undergraduate" DisplayName="Undergraduate"
Description="Undergraduate">
         <QualificationMinor CodeName="Associate" DisplayName="Associate"
Description="Associate" />
         <QualificationMinor CodeName="Bachelor" DisplayName="Bachelor"
Description="Bachelor" />
     </QualificationMajor>
     <QualificationMajor CodeName="Graduate"  DisplayName="Graduate"
Description="Undergraduate">
         <QualificationMinor CodeName="Bachelor" DisplayName="Bachelor"
Description="Bachelor" />
         <QualificationMinor CodeName="Doctorate" DisplayName="Doctorate"
Description="Doctorate" />
     </QualificationMajor>
     <Campus codekey="302072" Name="Cleveland">
       <Category CodeName="Administrative" DisplayName="Administrative">
         <SubCategory CodeName="BUSINESSADMINISTRATIVEASSISTANT(1OF2)"
DisplayName="Business Administrative Assistant">
           <Program CodeName="210AASD" DisplayName="Administrative Applications
Specialist" AlternativeName="210AASD" />
       <Program CodeName="210AOSA" DisplayName="Administrative Office
Specialist" AlternativeName="210AOSA" />
       <Program CodeName="210COAD" DisplayName="Computer Office Assistant"
AlternativeName="210COAD" />
     </SubCategory>
       </Category>
       <Category CodeName="Business" DisplayName="Business">
     <SubCategory CodeName="ACCOUNTING(1OF2)" DisplayName="Accounting">
           <Program CodeName="210ACCA" DisplayName="Accounting "
AlternativeName="210ACCA" />
         </SubCategory>
       </Category>
     </Campus>
</School>'));

SQL> select x.content.extract('/School//Category/@CodeName').getStringVal()
  2  Category
from xml_data x;
  3
CATEGORY
--------------------------------------------------------------------------------
AdministrativeBusiness

SQL> select x.content.extract('/School//SubCategory/@CodeName').getStringVal()
  2  SubCategory
  3  from xml_data x;

SUBCATEGORY
--------------------------------------------------------------------------------
BUSINESSADMINISTRATIVEASSISTANT(1OF2)ACCOUNTING(1OF2)

Thanks for your help

Rating: 5
September 09, 2003
Reviewer: Yong Wu from CA, USA
I tried another query it returns the same

SQL> select extract(content,'/School//SubCategory/@CodeName').getStringVal()
from xml_data;

EXTRACT(CONTENT,'/SCHOOL//SUBCATEGORY/@CODENAME').GETSTRINGVAL()
--------------------------------------------------------------------------------
BUSINESSADMINISTRATIVEASSISTANT(1OF2)ACCOUNTING(1OF2)

I would like to know how to spit the results like

BUSINESSADMINISTRATIVEASSISTANT(1OF2)
ACCOUNTING(1OF2)

Thanks,

Followup:
Hi Yong, Sean Dillon here.  You want to use an operator in Oracle9iR2 called
XMLSequence.  You can read all about XMLSequence in the documentation (Oracle
XML DB Developer's Handbook).  XMLSequence turns an extract result into a set of
XMLType documents instead of a single XMLType result set.  You then use the
TABLE function to turn that set of XMLType documents into a source from which
you can query:

SQL> select extractvalue(value(d), '/SubCategory/@CodeName')
  2    from xml_data x,
  3         table( xmlsequence( extract(x.content, '/School//SubCategory') ) ) d
  4  /

EXTRACTVALUE(VALUE(D),'/SUBCATEGORY/@CODENAME')
--------------------------------------------------------------------------------
BUSINESSADMINISTRATIVEASSISTANT(1OF2)
ACCOUNTING(1OF2)

Hope that helps!

Sean Dillon - Principal Technologist, Oracle Corporation
Author "Beginning Oracle Programming" www.amazon.com/exec/obidos/ASIN/186100690X

Rating: 5
September 10, 2003
Reviewer: Yong Wu from CA, USA
Sean,

It works.
I follow Oracle on-line document Oracle9i XML Database Developer's Guide and
didn't reach far enough to find a good example.

Great! It helpe me a lot.

Thanks,

Rating: 5
Error in creating a xml schema September 10, 2003
Reviewer: Yong Wu from CA, USA
Sean,

I tried to create/register a xml schema and got an error.
Would you please let me know what is wrong with this error or some setup
problem?

create table po_tab(
   id number,
   po sys.XMLType
 )
   xmltype column po
     XMLSCHEMA "
http://www.oracle.com/PO.xsd
     element "PurchaseOrder";
create table po_tab(
*
ERROR at line 1:
ORA-01001: invalid cursor
ORA-21700: object does not exist or is marked for delete

Thanks

Followup:
thats not a valid url

---

Hi Sean here...  in the SQL reference, look up the create table statement.
Under the XML Schema explanation, it states if you specify an XMLSCHEMA for your
column storage, you must have already registered the XML Schema with the
database.  Page 15-42:

"If you do specify an XMLSchema URL, you must already have registered the
XMLSchema using the DBMS_XMLSCHEMA package."

You can lookup DBMS_XMLSCHEMA in the "Supplied PL/SQL Packages and Types
Reference."  Basically the REGISTER_SCHEMA procedure tells Oracle you will be
storing XML according to a particular schema, and creates the underlying storage
mechanism for that schema.  For more information about this, you can read
Chapter 5, Structured Mapping of XMLType of the "XML Database Developer's
Guide".  Hope that helps!

September 25, 2003 Rene -- Thanks for the question regarding "How to extract Oracle data into a XML formatted file", version 9.2.0
You Asked
Hi Tom,

On our project, we need to extract data from our Oracle9i database to generate a
XML formatted file, and use the XML file as portable media then import data from
the XML data file to either a MS SQL Server database or an Oracle database on
our client site. Is there an Oracle utility/tool available to accomplish the
task? Any idea or advice would be greatly appreciated.

Thanks in advance and happy holiday!

Rene Zhao

DFI International
and we said...
You mean like this:

ops$tkyte@ORA920> select dbms_xmlgen.getxml( 'select * from scott.emp' ) from
dual;

DBMS_XMLGEN.GETXML('SELECT*FROMSCOTT.EMP')
--------------------------------------------------------------------------------
<?xml version="1.0"?>
<ROWSET>
 <ROW>
  <EMPNO>7369</EMPNO>
  <ENAME>SMITH</ENAME>
  <JOB>CLERK</JOB>
  <MGR>7902</MGR>
  <HIREDATE>17-DEC-80</HIREDATE>
  <SAL>800</SAL>
  <DEPTNO>20</DEPTNO>
 </ROW>
 <ROW>
  <EMPNO>7499</EMPNO>
  <ENAME>ALLEN</ENAME>
  <JOB>SALESMAN</JOB>
  <MGR>7698</MGR>
  <HIREDATE>20-FEB-81</HIREDATE>
  <SAL>1600</SAL>
  <COMM>300</COMM>
  <DEPTNO>30</DEPTNO>
 </ROW>
......

You want to read:
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-XML
the xml developers guide -- you'll find the XML support to be pretty
comprehensive....
Review & Followup
Rating: 4
XML generation in Oracle8i December 21, 2002
Reviewer: Anil from India
Hi Tom:
I tried using the xmlgen package in 8i but it failed with this error. Can you
please give me a list of steps(checklist) which I need to check. I donot want to
blindly increase the size of my shared pool. I just want to findout why this has
occured - so I need your help it actually finding the source of the problem.

select xmlgen.getxml('select * from tags') from dual
       *
ERROR at line 1:
ORA-04031: unable to allocate 4032 bytes of shared memory ("shared
pool","OracleXMLStoreSYS","joxlod: in ehe","ioc_allocate_pal")
ORA-06512: at "SYS.XMLGEN", line 487
ORA-06512: at "SYS.XMLGEN", line 477
ORA-06512: at line 1

Thanks for your time.

Best Regards,
Anil

Followup:
make sure your java pool and shared pool are set large enough (they are not
right now)

Rating: 5
June 27, 2003
Reviewer: VINI from CANADA
Hi,

I too have a ORA-04031.

I did the "alter system flush shared pool". but still has the problem.

our people doesn't want to change existing sql (for bind variables), and or
shutdown the database.

so my problem is :
 Is there any other way to refresh the shared memory ?

Followup:
how big is your shared pool

and why won't the developers fix the bugs in their code?  make fun of them,
maybe they'll get it.

have you tried cursor_sharing?

Rating: 4
ORA-04031: unable to allocate 4032 bytes of shared memory ("shared June 27, 2003
Reviewer: Yong from CA, USA
I encoutered the similar problem on Oracle8i before. The real problem was that
the pl/sql package was locked. For example, the package is running and you try
to re-compile the package from other tool, then you will get this error. You may
find the process to kill the compiling process. You may not be able
drop/re-create the package. There is a undocument way to break the lock.

Followup:
umm, no, that won't do that.  you'll get a timeout, not an "out of memory"

Rating: 4
CHop off June 27, 2003
Reviewer: A reader
Hi, Tom,

I tried you code, but only got some of the output, the others are chopped of:
DBMS_XMLGEN.GETXML('SELECT*FROMSCOTT.EMP')
------------------------------------------------------------
<?xml version="1.0"?>
<ROWSET>
 <ROW>
  <EMPNO>7369</EMPNO>
  <ENAME>SMITH</ENAM
                        <--------Missing parts
1 row selected.
Only one partial row? Or do I need to do somthing here?

How to get XML data back to the DB?

THanks

Followup:
SQL> set long 500000

or some big number.  just a sqlplus setting.

search for

xmlsave

on this site.

Rating: 4
how can we insert data into oracle from XML file July 28, 2003
Reviewer: A Reader from INDIA
hi Tom ...
need ur help Again ...

how can we insert data into oracle from XML file

Thanx

Followup:
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-XML

      Rating:
      2
Re:  July 29, 2003
Reviewer:  a
      reader  from india
hii ... Tom ..

we are using 8.1.5 ...

          Followup:

see otn.oracle.com, goto the xml technology corner.  much of the stuff can be
loaded into 8i, some of it must run outside of the database.

      Rating: 4
Why null in
      fields?  August 18, 2003
Reviewer:  Sasa
       from Belgrade, Serbia
Hi Tom,

I created test case(hope will help).

(it is Oracle 9.2.03)

create table xmltest (id number, xmlpayload xmltype);

Table created.

insert into xmltest(id, xmlpayload)
values(1, xmltype(
'<?xml version="1.0"?>
<ROWSET>
 <ROW>
  <IDFOBETSTATE>A</IDFOBETSTATE>
  <ISACTIVE>-1</ISACTIVE>
  <INTERNALORDER>1</INTERNALORDER>
  <INTERNALDESCRIPTION>Bet attempted (not open/valid)</INTERNALDESCRIPTION>
  <NAME>Attempted</NAME>
 </ROW>
 <ROW>
  <IDFOBETSTATE>O</IDFOBETSTATE>
  <ISACTIVE>-1</ISACTIVE>
  <INTERNALORDER>2</INTERNALORDER>
  <INTERNALDESCRIPTION>Bet opened</INTERNALDESCRIPTION>
  <NAME>Open</NAME>
 </ROW>
 <ROW>
  <IDFOBETSTATE>C</IDFOBETSTATE>
  <ISACTIVE>-1</ISACTIVE>
  <INTERNALORDER>3</INTERNALORDER>
  <INTERNALDESCRIPTION>Bet closed(All legs closed - not
settled)</INTERNALDESCRIPTION>
  <NAME>Closed</NAME>
 </ROW>
 <ROW>
  <IDFOBETSTATE>S</IDFOBETSTATE>
  <ISACTIVE>-1</ISACTIVE>
  <INTERNALORDER>4</INTERNALORDER>
  <INTERNALDESCRIPTION>Bet settled(Bet is paid out)</INTERNALDESCRIPTION>
  <NAME>Settled</NAME>
 </ROW>
 <ROW>
  <IDFOBETSTATE>?</IDFOBETSTATE>
  <ISACTIVE>-1</ISACTIVE>
  <INTERNALORDER>5</INTERNALORDER>
  <INTERNALDESCRIPTION>Bet undefined(whatever it means)</INTERNALDESCRIPTION>
  <NAME>Unknown</NAME>
 </ROW>
</ROWSET>'));

1 row created.

commit;

Commit complete.

SELECT
extract(value(d),'ROWSET/ROW/IDFOBETSTATE/text()').getstringVal() as
IDFOBETSTATE,
extract(value(d),'ROWSET/ROW/INTERNALORDER/text()').getnumberVal() as
INTERNALORDER
FROM xmltest t,
TABLE(XMLSequence(t.xmlpayload.extract('ROWSET/ROW'))) d;

This query shows me correctly 5 records but with the null in the fields.

What am I missing?

Please, help.

          Followup:

because the rowset is gone by then... always helpful to just dump the query out
and see what it is giving you back.  I started with "select *" but backed off to
just select column_value for showing you what is there:

ops$tkyte@ORA920> SELECT         column_value
  2  FROM xmltest t,
  3  TABLE(XMLSequence(t.xmlpayload.extract('ROWSET/ROW'))) d;

COLUMN_VALUE
---------------------------------------------------------------------------------
--------------------------------------------------
<ROW>
  <IDFOBETSTATE>A</IDFOBETSTATE>
  <ISACTIVE>-1</ISACTIVE>
  <INTERNALORDER>1</INTERNALORDER>
  <INTERNALDESCRIPTION>Bet attempted (not open/valid)</INTERNALDESCRIPTION>
  <NAME>Attempted</NAME>
</ROW>

<ROW>
  <IDFOBETSTATE>O</IDFOBETSTATE>
  <ISACTIVE>-1</ISACTIVE>
  <INTERNALORDER>2</INTERNALORDER>
  <INTERNALDESCRIPTION>Bet opened</INTERNALDESCRIPTION>
  <NAME>Open</NAME>
</ROW>

<ROW>
  <IDFOBETSTATE>C</IDFOBETSTATE>
  <ISACTIVE>-1</ISACTIVE>
  <INTERNALORDER>3</INTERNALORDER>
  <INTERNALDESCRIPTION>Bet closed(All legs closed - not
settled)</INTERNALDESCRIPTION>
  <NAME>Closed</NAME>
</ROW>

<ROW>
  <IDFOBETSTATE>S</IDFOBETSTATE>
  <ISACTIVE>-1</ISACTIVE>
  <INTERNALORDER>4</INTERNALORDER>
  <INTERNALDESCRIPTION>Bet settled(Bet is paid out)</INTERNALDESCRIPTION>
  <NAME>Settled</NAME>
</ROW>

<ROW>
  <IDFOBETSTATE>?</IDFOBETSTATE>
  <ISACTIVE>-1</ISACTIVE>
  <INTERNALORDER>5</INTERNALORDER>
  <INTERNALDESCRIPTION>Bet undefined(whatever it means)</INTERNALDESCRIPTION>
  <NAME>Unknown</NAME>
</ROW>

so, what you want is:

ops$tkyte@ORA920>
ops$tkyte@ORA920> SELECT
  2  extract(value(d),'/ROW/IDFOBETSTATE/text()').getstringVal() as
  3  IDFOBETSTATE,
  4  extract(value(d),'/ROW/INTERNALORDER/text()').getnumberVal() as
  5  INTERNALORDER
  6  FROM xmltest t,
  7  TABLE(XMLSequence(t.xmlpayload.extract('ROWSET/ROW'))) d;

IDFOBETSTATE    INTERNALORDER
--------------- -------------
A                           1
O                           2
C                           3
S                           4
?                           5

ops$tkyte@ORA920>

      Rating: 5
It works
      now  August 18, 2003
Reviewer:  Sasa
       from Belgrade, Serbia
Thanks Tom,

Now works.

      Rating: 4
How to extract
      Oracle data into a XML formatted file, ver 9.0.1  August 21,
      2003
Reviewer:  Raj
Hi Tom,
I have the following problem....

SQL> select dbms_xmlgen.getxml( 'select * from scott.emp' ) from
  2  dual;
ERROR:
ORA-01722: invalid number

no rows selected

Could you know what's wrong?

Raj

          Followup:

I asked Sean Dillon, our local XML guy, to take a look at this and here's what
he had to say:
-------

Hiya Raj,

I'm not sure why you're getting this error, but a couple things you can try:

1.  Make sure you have the appropriate SELECT grant on SCOTT.EMP.
2.  Try the same statement but specify the columns instead of "*". For example,
"select empno, ename, job from scott.emp".  See what that gives you.
3.  Make sure you can use DBMS_XMLGEN.GETXML on a simple table you create:

SQL> create table t( txt varchar2(50), num number );
Table created.

SQL> insert into t values ( 'One', 1 );
1 row created.

SQL> insert into t values ( 'Two', 2 );
1 row created.

SQL> insert into t values ( 'Three', 3 );
1 row created.

SQL> insert into t values ( 'Four', 4 );
1 row created.

SQL> select dbms_xmlgen.getxml( 'select * from t' )
  2    from dual;

DBMS_XMLGEN.GETXML('SELECT*FROMT')
--------------------------------------------------------------------------------
<?xml version="1.0"?>
<ROWSET>
 <ROW>
  <TXT>One</TXT>
  <NUM>1</NUM>
 </ROW>
 <ROW>
  <TXT>Two</TXT>
  <NUM>2</NUM>
 </ROW>
 <ROW>
  <TXT>Three</TXT>
  <NUM>3</NUM>
 </ROW>
 <ROW>
  <TXT>Four</TXT>
  <NUM>4</NUM>
 </ROW>
</ROWSET>

If you have the appropriate privileges (you can just "select * from scott.emp"
logged in as the same user...), and the test above works just dandy, let me
know.

      Rating: 5
CLOB or
      XMLType  August 26, 2003
Reviewer:
      A reader
Tom,
Which is the better option to store XML data
1)CLOB data type
2)XMLType

When writing(inserting) data to a table, the options I know we have in oracle
are
dbms_xmlSave.InsertXML and
dbms_lob.loadfromfile
Which option is the better option to go with?
Thank you

          Followup:

depends on what you want to do with it (as always)

if you want to have xml functionality -- xmltype makes sense.

if it is just a bit bucket, clob might be good enough.

September 25, 2003 gabby -- Thanks for the question regarding "sub
      query", version 8.0.6

      You Asked
Hi

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

thanks
gabby

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

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

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

      Rating: 3
subqueries
      II  April 24, 2002
Reviewer:  J?rund Vier
      Skriubakken  from Norway
Hi.

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

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

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

          Followup:

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

correlated subqueries -- once per row.

      Rating: 3
On a similar
      subject  April 25, 2002
Reviewer:  Martin
       from England
Hiya Tom,

This is on a similar subject. Consider this :

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

Table created.

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

Table created.

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

1 row created.

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

1 row created.

SQL> set autotrace traceonly explain

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

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

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

Thanks in advance

          Followup:

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

      Rating: 5
tune
      sub-queries with analytic functions  August 19, 2002

Reviewer:  A reader
hi tom

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

like

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

which implies two index/table scan in emp.

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

          Followup:

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

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

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

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

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

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

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

Table dropped.

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

Table created.

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

PL/SQL procedure successfully completed.

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

Index created.

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

Table analyzed

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

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

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

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

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

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

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

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

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

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

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

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

      Rating: 5
pls share the
      procedure - exec gen_data( 'EMP', 10000 )  August 19, 2002

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

          Followup:

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

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

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

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

      Rating: 5
difference
      between these two queries  September 25, 2003

Reviewer:  A reader
Hi

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

I came up with these two queries

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

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

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

many many thanks

          Followup:

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

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

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

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

      Rating:
      3
Good  September 25, 2003
Reviewer:
      Peter  from Boulder,USA
Dear Tom,
Fine and expect the same from you.What is the use of the
"with" keyword?Does it relate to query possiblities like
"Merge" statement?Please provide a sample explanation.

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4423923392083
nothing to do with merge...

      Rating:
      5
  September 25, 2003
Reviewer:  Sam

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

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

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

I rewrote the above query as follows to avoid correlated subqueries

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

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

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

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

Thanks & Regards
Sam

      Rating:
      4
  September 26, 2003
Reviewer:  Sam

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

Thanks  & Regards
Sam

September 25, 2003 Arun -- Thanks for the question regarding
      "Function to concatenate output", version 8i

      You Asked
Hi,

Is there some way to concatenate the output of a select query to
a single record.

for ex. in SQL Anywhere there is a function LIST

select list(tname) from tab;

would give a comma seprate list of all the values in tname
column.

is there something quivalent in oracle or some way around to do
it.

      and we said...
there are a couple of ways to do this in Oracle.

If you know the range of values for the column you wish to
"list", you should use DECODE().  An example of using decode to
do this is at:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:124812348063
Now, I will show 2 more ways to do it by calling PLSQL from sql.
 The first example uses static sql and hence you would need to
code a routine per column/table you wished to "list" on.  The
second, more generic example, may be used on any table/column
pair as it uses dynamic sql:

First we will set up a demo table.  We have 2 columns A and B.
 We desire output in the format:

A-value1            b-value1 b-value2 b-value3 ....
A-value2            b-value1 b-value2 ....

ask_tom@OSI1.WORLD> column t format a30
ask_tom@OSI1.WORLD> drop table t;
Table dropped.

ask_tom@OSI1.WORLD> create table t
  2  ( a varchar2(25),
  3    b varchar2(25)
  4  );

Table created.

ask_tom@OSI1.WORLD> insert into t values ( '210','5000' );
ask_tom@OSI1.WORLD> insert into t values ( '210','5001' );
ask_tom@OSI1.WORLD> insert into t values ( '210','5002' );
ask_tom@OSI1.WORLD> insert into t values ( '220','6001' );
ask_tom@OSI1.WORLD> insert into t values ( '220','6002' );
ask_tom@OSI1.WORLD> commit;

Commit complete.

Our first implementation simply uses static sql to select all
of the values for B from T for a given A and string them
together:

ask_tom@OSI1.WORLD> create or replace
  2  function get_transposed( p_a in varchar2 )
  3  return varchar2
  4  is
  5      l_str  varchar2(2000) default null;
  6      l_sep  varchar2(1) default null;
  7  begin
  8      for x in ( select b from t where a = p_a ) loop
  9          l_str := l_str || l_sep || x.b;
 10          l_sep := '-';
 11      end loop;
 12      return l_str;
 13  end;
 14  /

Function created.

ask_tom@OSI1.WORLD>
ask_tom@OSI1.WORLD> select a, get_transposed( a ) t
  2    from t
  3   group by a
  4  /

A                         T
------------------------- ------------------------------
210                       5000-5001-5002
220                       6001-6002

Our next example is more complex.  We will pass in the name of
the 'key' column (the column to pivot on), a value for that
column, the name of the column to actually select out and string
together and finally the table to select from:

ask_tom@OSI1.WORLD> create or replace
  2  function transpose( p_key_name in varchar2,
  3                      p_key_val  in varchar2,
  4                      p_other_col_name in varchar2,
  5                      p_tname     in varchar2 )
  6  return varchar2
  7  as
  8      type rc is ref cursor;
  9      l_str    varchar2(4000);
 10      l_sep    varchar2(1);
 11      l_val    varchar2(4000);
 12
 13      l_cur    rc;
 14  begin
 15
 16      open l_cur for 'select '||p_other_col_name||'
 17                        from '|| p_tname || '
 18                       where ' || p_key_name || ' = :x '
 19                  using p_key_val;
 20
 21      loop
 22          fetch l_cur into l_val;
 23          exit when l_cur%notfound;
 24          l_str := l_str || l_sep || l_val;
 25          l_sep := '-';
 26      end loop;
 27      close l_cur;
 28
 29      return l_str;
 30  end;
 31  /

Function created.

ask_tom@OSI1.WORLD>
ask_tom@OSI1.WORLD> REM List the values of "B" for a given value
ask_tom@OSI1.WORLD> REM of "A" in the table "T"
ask_tom@OSI1.WORLD>
ask_tom@OSI1.WORLD> select a, transpose( 'a', a, 'b', 't' ) t
  2    from t
  3   group by a
  4  /

A                         T
------------------------- ------------------------------
210                       5000-5001-5002
220                       6001-6002

Review &
      Followup

      Rating: 5
3 more
      methods  November 28, 2001
Reviewer:  Mikito
      Harakiri
1.
http://www.oracle.com/oramag/code/tips2001/index.html?052001.html
Pitfalls: slower than 2 and 3

2.
http://www.oracle.com/oramag/code/tips2001/index.html?102801.html
Essentially, this technique can be reduced to writing a package with a variable
that accumulates the aggregated value ordering the records correctly and firing
a method that calculates the aggregate. No need in ROLLUP or GROUPING.

3. 9i:

SELECT deptno, CURSOR(SELECT * FROM emp where deptno=dept.deptno) nested_TABLE
  FROM dept;

-- User defined agregate through pipelined function
CREATE or replace FUNCTION CONCAT_LIST( cur SYS_REFCURSOR )
  RETURN  VARCHAR2 IS
    ret VARCHAR2(32000);
    tmp VARCHAR2(4000);
BEGIN
    loop
        fetch cur into tmp;
        exit when cur%NOTFOUND;
            ret := ret || ',' || tmp;
    end loop;
    RETURN ret;
END;
/

SELECT distinct
    deptno,
    SUBSTR(CONCAT_LIST(CURSOR(
        SELECT ename FROM emp ee WHERE e.deptno = ee.deptno
    )),1,40) employees
FROM emp e;

          Followup:

Or, even better, in 9i -- use a user defined aggregate so you don't have run the
concat_list function once per row and then through out the dups (it would be
better to write:

  select deptno, SUBSTR(CONCAT_LIST(CURSOR(
        SELECT ename FROM emp ee WHERE e.deptno = ee.deptno
    )),1,40) employees
    from ( select distinct deptno from emp );

)

But a good general purpose solution is:

scott@ORA9I.WORLD> create or replace type StringAggType as object
  2  (
  3     theString varchar2(4000),
  4
  5     static function
  6          ODCIAggregateInitialize(sctx IN OUT StringAggType )
  7          return number,
  8
  9     member function
 10          ODCIAggregateIterate(self IN OUT StringAggType ,
 11                               value IN varchar2 )
 12          return number,
 13
 14     member function
 15          ODCIAggregateTerminate(self IN StringAggType,
 16                                 returnValue OUT  varchar2,
 17                                 flags IN number)
 18          return number,
 19
 20     member function
 21          ODCIAggregateMerge(self IN OUT StringAggType,
 22                             ctx2 IN StringAggType)
 23          return number
 24  );
 25  /

Type created.

scott@ORA9I.WORLD>
scott@ORA9I.WORLD> create or replace type body StringAggType
  2  is
  3
  4  static function ODCIAggregateInitialize(sctx IN OUT StringAggType)
  5  return number
  6  is
  7  begin
  8      sctx := StringAggType( null );
  9      return ODCIConst.Success;
 10  end;
 11
 12  member function ODCIAggregateIterate(self IN OUT StringAggType,
 13                                       value IN varchar2 )
 14  return number
 15  is
 16  begin
 17      self.theString := self.theString || ',' || value;
 18      return ODCIConst.Success;
 19  end;
 20
 21  member function ODCIAggregateTerminate(self IN StringAggType,
 22                                         returnValue OUT varchar2,
 23                                         flags IN number)
 24  return number
 25  is
 26  begin
 27      returnValue := rtrim( ltrim( self.theString, ',' ), ',' );
 28      return ODCIConst.Success;
 29  end;
 30
 31  member function ODCIAggregateMerge(self IN OUT StringAggType,
 32                                     ctx2 IN StringAggType)
 33  return number
 34  is
 35  begin
 36      self.theString := self.theString || ',' || ctx2.theString;
 37      return ODCIConst.Success;
 38  end;
 39
 40
 41  end;
 42  /

Type body created.

scott@ORA9I.WORLD>
scott@ORA9I.WORLD> CREATE or replace
  2  FUNCTION stringAgg(input varchar2 )
  3  RETURN varchar2
  4  PARALLEL_ENABLE AGGREGATE USING StringAggType;
  5  /

Function created.

scott@ORA9I.WORLD>
scott@ORA9I.WORLD> column enames format a30
scott@ORA9I.WORLD> select deptno, stringAgg(ename) enames
  2    from emp
  3   group by deptno
  4  /

    DEPTNO ENAMES
---------- ------------------------------
        10 CLARK,KING,MILLER
        20 SMITH,FORD,ADAMS,SCOTT,JONES
        30 ALLEN,BLAKE,MARTIN,TURNER,JAME
           S,WARD

scott@ORA9I.WORLD>

      Rating: 4
Final Question
      About transposing  March 07, 2002
Reviewer:
      Edgars  from Latvia
Hello Tom!

This article is very useful, but I think there is one thing that could be added
to it. Is there a way to fully transpose table? For example suppose I have a
table like this:

create table ts1( tm date, val float, descr varchar(5) );

And values like:

insert into ts1 values ( to_date( '01.01.2001.', 'DD.MM.YYYY.' ), -1, 'P1' );
insert into ts1 values ( to_date( '01.01.2001.', 'DD.MM.YYYY.' ), -2, 'P2' );
insert into ts1 values ( to_date( '01.01.2001.', 'DD.MM.YYYY.' ), -3, 'P3' );
insert into ts1 values ( to_date( '02.01.2001.', 'DD.MM.YYYY.' ), -1, 'P1' );
insert into ts1 values ( to_date( '02.01.2001.', 'DD.MM.YYYY.' ), -2, 'P2' );
insert into ts1 values ( to_date( '02.01.2001.', 'DD.MM.YYYY.' ), -3, 'P3' );
insert into ts1 values ( to_date( '03.01.2001.', 'DD.MM.YYYY.' ), -1, 'P1' );
insert into ts1 values ( to_date( '03.01.2001.', 'DD.MM.YYYY.' ), -2, 'P3' );
insert into ts1 values ( to_date( '04.01.2001.', 'DD.MM.YYYY.' ), -1, 'P1' );
insert into ts1 values ( to_date( '05.01.2001.', 'DD.MM.YYYY.' ), -1, 'P1' );
insert into ts1 values ( to_date( '05.01.2001.', 'DD.MM.YYYY.' ), -2, 'P2' );
insert into ts1 values ( to_date( '05.01.2001.', 'DD.MM.YYYY.' ), -3, 'P3' );

All I need is to get a table like this:
TM              P1          P2            P3
01.01.2001.     -1          -2            -3
02.01.2001.     -1          -2            -3
03.01.2001.     -1          NULL          -3
04.01.2001.     -1          NULL          NULL
05.01.2001.     -1          -2            -3

Can this be done internally in oracle? For now I use a crosstable to achieve
this. Thank you.

          Followup:

See

...
If you know the range of values for the column you wish to "list", you should
use DECODE().  An example of using decode to do this is at:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:124812348063
Now, I will show 2 more ways to do it by calling PLSQL from sql.  The ......

That shows (given you know p1, p2, p3) how to use decode to do that.

      Rating: 4
Followup to my
      previous comment  March 07, 2002
Reviewer:
      Edgars  from Latvia
Hello Tom!

What I need in my comment can be achieved with decode if I know exectly values
for descr (distinct descr). But what should I do when I don't know what distinct
values are there?
Thank you!

          Followup:

A SQL query *must* have a fixed number of columns (its one of the rules).

So, if you want to transpose in SQL, you need to know the columns in advance.
If you didn't know the domain of descr was (P1, P2, P3) -- you would not know
the number of columns -- hence you could not write the SQL (you could return a
list like above, you could return a cursor for each row or you could use a
collection type and return a single column that was in fact an array of values,
eg:

scott@ORA817DEV.US.ORACLE.COM> select tm, CURSOR( select descr, val from ts1 x
where x.tm  = ts1.tm )
  2    from ( select distinct tm
  3             from ts1 ) ts1
  4  /

TM        CURSOR(SELECTDESCR,V
--------- --------------------
01-JAN-01 CURSOR STATEMENT : 2

CURSOR STATEMENT : 2

DESCR        VAL
----- ----------
P1            -1
P2            -2
P3            -3

02-JAN-01 CURSOR STATEMENT : 2

CURSOR STATEMENT : 2

DESCR        VAL
----- ----------
P1            -1
P2            -2
P3            -3

03-JAN-01 CURSOR STATEMENT : 2

CURSOR STATEMENT : 2

DESCR        VAL
----- ----------
P1            -1
P3            -2

04-JAN-01 CURSOR STATEMENT : 2

CURSOR STATEMENT : 2

DESCR        VAL
----- ----------
P1            -1

05-JAN-01 CURSOR STATEMENT : 2

CURSOR STATEMENT : 2

DESCR        VAL
----- ----------
P1            -1
P2            -2
P3            -3

scott@ORA817DEV.US.ORACLE.COM>

Here you get a cursor for each row -- so there were only 5 rows returned -- each
with a result set contained therein.

Also, if you have my book -- I have a routine in there that dynamically builds a
query that does the transpose without having to know the domain up front.  The
stored procedure returns a ref cursor (result set) that is the transposed data.

      Rating: 5
Function to
      concatenate output  October 23, 2002
Reviewer:
      A reader
hi tom,

  I don't know if this question will fit in here
because the question was for 8i and then 9i discussion started up.

 but I want to know in the 8.1.7 db.

 I have a pl/sql record consist of 5 field
and I have declared a type

  v_rec new_data%rowtype;

I can access the elements by v_rec.field1..

but I want to convert the v_rece obj. in a string.

varchar2(2000) v_rec_str := fn (v_rec);

should move
 v_rec.field1||v_rec.field2||.....

 do we have any type casing function that will do this
or Ihave to manually
   concatinate all the fields
   convert to_cher if numeber
 and assign to a varchar variable ?

          Followup:

manually concatenate, yes.

      Rating: 5
very
      helpful..thanks Tom.  November 07, 2002
Reviewer:
      A reader

      Rating: 5
Aggregate
      Function in Package  June 26, 2003
Reviewer:
      Jan  from Europe
Hi Tom,

I want to create my agg. function, it works for a standalone function, but not
for the packaged one. What is the syntax?

Thanks

          Followup:

It does not appear to be supported -- the aggregate map is a simple standalong
function only.

      Rating:
      5
  August 01, 2003
Reviewer:  Carsten
      Laube  from Germany
Hi Tom,

the stringaggtype is really smart. You can handle the stringagg-function like
sum() or count().

Thanks a lot!

      Rating: 3
XMLTransform
      and XMLAgg [mis]use :-)  September 25, 2003

Reviewer:  msc
What a wonderful world - Oracle 9.2 includes XMLAgg() and XMLTransform():

select rtrim( extractvalue
       ( XMLTransform
         ( XMLElement( "tables", XMLAgg( XMLElement( "table", TABLE_NAME)))
         , XMLTYPE( '<xsl:stylesheet version="1.0" xmlns:xsl="
http://www.w3.org/1999/XSL/Transform
>
                     <xsl:template
match="tables"><result><xsl:apply-templates/></result></xsl:template>
                     <xsl:template match="table"><xsl:apply-templates/>,
</xsl:template>
                     </xsl:stylesheet>  ')), '/result' ),', ')
from USER_TABLES
where rownum < 10;

PS 2 Tom: is it possible to disable URL transformation into hyperlink in review
text? At least preview shows xmlns:xsl URL trsansformed to active hyperlink...

          Followup:

put a space in the http -- make it be h ttp or something.  i just look for
http://

September 25, 2003 TAPAS -- Thanks for the question regarding
      "Recursive Query - heirarchical relationship ", version Oracle8i(8.1.7)

      You Asked
There is a little correction in the tables. They look like:

TABLE: emp              |  TABLE: mgr
empno    mgr_key        |  mgr_key       empno
-----    -------        |  -------       -----
1         k1            |   k1             2
2         k2            |   k1             3
3         k3            |   k3             4
4         k4            |

-----------------------------------------------------------------

The existing schema is on DB2 and I want to convert it to
Oracle.
They have modelled the heirarchical relationship in two tables
rather than one. The emp table can serve as example. The
relationship between employees and their managers are modelled
in the same table 'emp' as follows:

TABLE: emp

empno    mgr
-----    ---
 1
 2        1
 3        1
 4        3

This table could be queried using CONNECT BY PRIOR to report the
heirarchical relationship.

But at the client site I'm working this is modelled as two
tables:

TABLE: emp            |  TABLE: mgr
empno                 |  mgr       empno
-----                 |  ---       -----
1                     |   1         2
2                     |   1         3
3                     |   3         4
4                     |

As CONNECT BY PRIOR is not allowed on a table join, I cannot
query the heirarchical relationship out of these two tables. I
suggested to change the design but it is not possible due to
many reasons. I solved this broblem by creating another table
out of the results of the join and then using CONNECT BY PRIOR
on the new table. But it takes huge amount of space and also
adds the overhead of populating the new table every time the
base table data changes. In DB2, it's possible to do the
recursive query with one SQL statement. It has 'WITH' clause
which allows the same query to refer to itself iteratively.

Is there another better solution available in Oracle than what
I'm doing?

Thanks and regards

Tapas

      and we said...
This is a perfect time to use a function based index then.  We
can create a function that converts the "mgr_key" into the
empno.  We can index that function.  Then, we can write a query
like:

tkyte@TKYTE816> select *
  2    from emp
  3  start with mgr_lookup(mgr_key) = -1
  4  connect by prior empno = mgr_lookup(mgr_key)
  5  /

where mgr_lookup is the function to convert a mgr_key into an
empno and have it run very efficiently.

See

http://osi.oracle.com/~tkyte/article1/index.html
for details on function based indexes and how to use them.  Here
is a small example showing this at work:

tkyte@TKYTE816> create table emp
  2  as
  3  select ename, empno, 'mgr_' || mgr mgr_key
  4    from scott.emp;

Table created.

tkyte@TKYTE816> create table mgr
  2  as
  3  select distinct 'mgr_' || mgr mgr_key, mgr empno
  4    from scott.emp
  5   where mgr is not null;

Table created.

So, that gives me a table similar to your structure.  The
MGR_KEY in emp is a foreign key to the MGR table which stores
the mapping between a mgr_key and the real empno

tkyte@TKYTE816> create or replace
     function mgr_lookup( p_mgr_key in varchar2 ) return number
  2  deterministic
  3  as
  4      l_empno  mgr.empno%type;
  5  begin
  6      select empno
  7        into l_empno
  8        from mgr
  9       where mgr_key = p_mgr_key;
 10
 11      return l_empno;
 12  exception
 13    when no_data_found then return -1;
 14  end;
 15  /

Function created.

There is the function to convert a mgr_key into an EMPNO.
Notice how I use -1 NOT a NULL in this function.  I NEVER want
this to return NULL as NULLS are not indexed in btrees and I
really want to use the index as much as possible.  this may or
may not be relevant in your case.  Given the scott.emp table --
it was.  I wanted to start with "king" who has no manager and
hence would normally say "start with mgr is null" but that would
have to full scan emp and I wanted to avoid that so I do "start
with mgr = -1"

tkyte@TKYTE816> create index mgr_lookup_idx on
emp(mgr_lookup(mgr_key))
  2  /

Index created.

Now, I create an index on that.  this index will be maintained
for me by the database.  I do not have to rebuild any temporary
tables or anything when the structure changes -- this does it
for me

tkyte@TKYTE816> alter session set QUERY_REWRITE_ENABLED=TRUE;

Session altered.

tkyte@TKYTE816> alter session set
QUERY_REWRITE_INTEGRITY=TRUSTED;

Session altered.

These settings would probably be done at the database level in
the init.ora, not at the session level.  See the paper
referenced above for details on this.

tkyte@TKYTE816> set autotrace on explain
tkyte@TKYTE816>
tkyte@TKYTE816> select /*+ first_rows */
  2         rpad( '*', (level-1)*2, '*' ) || ename ename,
                  empno, mgr_key
  3    from emp
  4   start with mgr_lookup(mgr_key) = -1
  5  connect by prior empno = mgr_lookup(mgr_key)
  6  /

ENAME                EMPNO MGR_KEY
--------------- ---------- --------
KING                  7839 mgr_
**JONES               7566 mgr_7839
****SCOTT             7788 mgr_7566
******ADAMS           7876 mgr_7788
****FORD              7902 mgr_7566
******SMITH           7369 mgr_7902
**BLAKE               7698 mgr_7839
****ALLEN             7499 mgr_7698
****WARD              7521 mgr_7698
****MARTIN            7654 mgr_7698
****TURNER            7844 mgr_7698
****JAMES             7900 mgr_7698
**CLARK               7782 mgr_7839
****MILLER            7934 mgr_7782

14 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=1
   1    0   CONNECT BY
   2    1     INDEX (RANGE SCAN) OF 'MGR_LOOKUP_IDX' (NON-
   3    1     TABLE ACCESS (BY USER ROWID) OF 'EMP'
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=1
   5    4       INDEX (RANGE SCAN) OF 'MGR_LOOKUP_IDX' (NON-

The query plan shows we

o used the index to find the start with rows
o used the index to connect by

So the runtime performance of this will be excellent.  it will
not be actually invoking the PLSQL routine when we query.  Only
when we modify the (update or insert) the MGR_KEY column in the
EMP table will this function be invoked.  Since we typically
insert/update a single row at a time, the overhead of this
function based index typically will not be noticed by an
existing application.

Review &
      Followup

      Rating: 4
New feature in
      Oracle9i  June 28, 2001
Reviewer:  TAPAS
      SAHOO  from TX, USA
Tom,
I want to share this important information on the above question. I was happy to
know that Oracle has introduced the 'WITH' clause in release 9i that will solve
the problem I was facing as described above. This feature was available in DB2
and I was wondering why Oracle doesn't have it.

      Rating: 4
Recursive
      call  December 07, 2001
Reviewer:  sivababu
       from INDIA
Hello TOM,
    I searched in the archive and looked on this example. But i'm not able to
solve this problem.
 look this example,

I have this table.

create table t(parent varchar(10), child varchar(10));

values are:
insert into t values('100','200');
insert into t values('200','300');
insert into t values('300','100');
If i will give the following query i got error.

select parent, child from t start with parent=100 connect by prior parent= child

ERROR:
ORA-01436: CONNECT BY loop in user data

This is correct. I made a recursive loop for raising the error. But it is not
working correctly. I couldn't get where i made mistake. Look this procedure.

 CREATE OR REPLACE PROCEDURE CHECKloop ( p_parent varchar2,
   p_child     varchar2 )
 as
      Looped exception;
      strChild varchar2(100);
      cursor cSqlRec is
  select child from t  where child=p_parent;
 begin
      if p_parent  = p_child     then
           raise Looped ;
      else
  open cSqlRec;
  loop
      fetch cSqlRec Into strchild;
        exit when cSqlRec%notfound;
          If p_Parent=p_Child then
               close cSqlRec;
           raise Looped ;
          end if;
     CHECKloop  ( strchild, p_child );
  end loop;
         close cSqlRec;
      end if;
      exception
        when Looped  then
           raise_application_error( -20001, 'Loop data' );
 end;
/

exec checkloop('100','200');

expecting your reply,

regards,
sivababu

      Rating: 4
SQL to detect
      recursive loop  September 23, 2003
Reviewer:
      Ma$e
Tom:

I was wondering if you can help me with a SQL to detect recursive loop condition
that might exist in a table having two columns (group_id, member_id) where two
groups are members of each other.

Can such a situation be prevented with proper referential integrity or
constraints or some other means ?

Thanks
Ma$e

          Followup:

i'm not aware of any RI that can deal with this.

generally, you have to actually run the connect by and walk the tree in order to
do this.  it is very hard to do so in a multi-user situation, really hard. you
would have to serialize on the inserts to do it properly.

      Rating: 4
Here is my
      solution...is there a better way?  September 23, 2003

Reviewer:  Ma$e
Hi Tom:

Thanks for the prompt response.

On the issue of detecting a looping situation, here is what I came up with. Is
this an optimal solution ?

SQL> connect scott/tiger
Connected.
SQL>
SQL>
SQL>
SQL> desc gm;
 Name                                                              Null?    Type
 ----------------------------------------------------------------- --------
------------------------
 G
NUMBER
 M
NUMBER

SQL> select * from gm;

         G          M
---------- ----------
       123        777
       777        123
       123        345
       123        678
       777        779
       777        998

6 rows selected.

SQL> get gm.sql
  1* select a.g from gm a where a.m in (select distinct b.g from gm b where
b.m=a.g)
SQL>
SQL> @gm

         G
----------
       123
       777

SQL>

Thanks once again for your assistance.

          Followup:

1 3
3 2
2 1

1->3->2->1->3->......

ops$tkyte@ORA920LAP> create table gm ( g int, m int );

Table created.

ops$tkyte@ORA920LAP> insert into gm values ( 1, 3 );

1 row created.

ops$tkyte@ORA920LAP> insert into gm values ( 3, 2 );

1 row created.

ops$tkyte@ORA920LAP> insert into gm values ( 2, 1 );

1 row created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select a.g from gm a where a.m in (select distinct b.g from
gm b where
  2  b.m=a.g);

no rows selected

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select * from gm start with g = 1 connect by prior m = g;
ERROR:
ORA-01436: CONNECT BY loop in user data

no rows selected

you have to actually walk the tree.

      Rating: 4
What
      if....  September 24, 2003
Reviewer:  Ma$e

Hi Tom:

Thanks for the response.

Does the situation change if I had another table that would differentiate
between members and groups. I think I agree with you that prevention of a
looping situation at run-time would be difficult, however if I can find a way to
detect it and report it, I'll be a happy camper.

Have you covered the issue of "walking the tree" in one of the discussions ? If
so, can you please provide me with the link.

Thanks

Ma$e

          Followup:

i don't know what a "member" and a "group" means in your context.

the only real way i know to do it is:

lock the table
do the insert
do a connect by using the newly inserted row as root row and walk down the tree
do a connect by using the newly inserted row as root row and walk up the tree
commit

      Rating: 4
My
      definition  September 24, 2003
Reviewer:
      Ma$e
Hi Tom:

Thanks for the response.

I think in terms of users and groups. A user is an end point, but a group can
contain users and other groups.

We track the users and groups in another table via a type columns: users = 1 and
groups = 4. This table contains all the users and groups defined in the system.

Ma$e

      Rating: 5
generating
      hierarchical test data  September 24, 2003

Reviewer:  A reader
Hi Tom
Is there a sql way of generating hierarchical data
to create a big table with hierarchies running to
say a depth of 5-10 or so?  This is for testing
purposes?

Thanx!

      Rating: 5
performance of
      hierarchical queries  September 25, 2003
Reviewer:
      A reader
consider if the hierarchy depth is large (5-10) and the number of records we are
weeding through is also large (say in 50K range).
How do hierarchical queries perform? Do you ever consider
maintaining a flattened version of data to improve performance?
thanx!

          Followup:

they perform fine.  weeding through 50k records -- hope that is a batch process,
an end user couldn't possibly use that.

September 25, 2003 Khalid -- Thanks for the question regarding
      "Shared Memory Problem (unable to allocate shared memory ...)", version
      8.1.5

      You Asked
Tom:

My Development Environment:
I have a NT development box with 256MB on it. There are two oracle databases on
it. At least 2 identical schema owners on each of them. Each scema has on an
average about 10 pl/sql packages of about 1000 lines each. I have separated two
databases as D (Development) and Q (For our own internal testing). Also there is
a JRUN server, and a Netscape Enterprise Server running on the same box.

My SGA:
Total System Global Area   24899532 bytes
Fixed Size                    65484 bytes
Variable Size               7983104 bytes
Database Buffers           16777216 bytes
Redo Buffers                  73728 bytes

Some of the pfile Parameters:
db_block_size                        integer 8192
db_block_buffers                     integer 2048
shared_pool_size                     string  5000000
shared_pool_reserved_size            string  250000
shared_pool_size                     string  5000000

Problems Encountered:
    Our developers are using servlet applications, and our testers are banging
against the database pretty hard.
    I have to restart the server at least once or twice every day due to shared
memory errors such as the one below. Our QC and Production environments will be
dedicated Oracle servers running on Solaris boxes with much better resource
allocations.

Any help on how to eliminate/mitigate this problem will be greatly appreciated.

Thanks,
  Khalid

Sample Errors:
***********************************************
Error: SQLException java.sql.SQLException: ORA-04031: unable to allocate 4096
bytes of shared memory ("shared pool","GF","PL/SQL
MPCODE","BAMIMA: Bam Buffer") ORA-06508: PL/SQL: could not find program unit
being called ORA-06512: at line 1 begin :1 := gfx.insrt_coach('Ron
Jennings',5,'04172001','','','',''); end;

Error: SQLException java.sql.SQLException: ORA-00604: error occurred at
recursive SQL level 1 ORA-04031: unable to allocate 4216 bytes of shared memory
("shared pool","select con#,obj#,rcon#,enabl...","sga heap","library cache")
INSERT INTO gfx_suggestion (suggestion, suggestion_id, timestamp,
suggestion_type_fl,
name, email, business_unit_key) select 'test suggestion. RJ 04/19/01',
max(suggestion_id)+1, sysdate, 'T', 'Ron Jennings' , 'rj@rwd.com', '5' from
gf_suggestion
*******************************************************

      and we said...
#1 -- you are NOT USING BIND VARIABLES, for example I clearly see:

INSERT INTO gfx_suggestion (suggestion, suggestion_id, timestamp,
suggestion_type_fl,
name, email, business_unit_key) select 'test suggestion. RJ 04/19/01',
max(suggestion_id)+1, sysdate, 'T', 'Ron Jennings' , 'rj@rwd.com', '5' from
gf_suggestion

that MUST BE rewritten as:

INSERT INTO gfx_suggestion (suggestion, suggestion_id, timestamp,
suggestion_type_fl,
name, email, business_unit_key) select :1, max(suggestion_id)+1, sysdate, :2,
:3, :4, :5 from gf_suggestion

Or you will not go ANYWHERE with this application.  Bind variables are SO
MASSIVELY important -- I cannot in any way shape or form OVERSTATE their
importance.

Same with the PLSQL call I see there:

begin :1 := gfx.insrt_coach('Ron
Jennings',5,'04172001','','','','');  end;

That MUST be coded as:

begin :1 := gfx.insrt_coach(:2,:3,:4,:5,:6,:7,:8);

If you do not fix this, your application is doomed to utter and total failure
from day one.

#2 -- I see the query:

INSERT INTO gfx_suggestion (suggestion, suggestion_id, timestamp,
suggestion_type_fl,
name, email, business_unit_key) select 'test suggestion. RJ 04/19/01',
max(suggestion_id)+1, sysdate, 'T', 'Ron Jennings' , 'rj@rwd.com', '5' from
gf_suggestion

and shudder - that is frightening!!! What happens when two people insert at
about the same time?  --- answer -- both get the SAME suggestion_id.  That is a
horrible programming practice your "database" developers have (i quote
"database" because I don't think they are database developers, I think they are
java programmers trying to use a database-- these code snippets must just be the
tiny tip of a really big iceberg).

They NEED to read about sequences.  That insert should be inserting:

...  SUGGESTION_SEQ.NEXTVAL, ....

A sequence is a highly scalable, non-blocking ID generator.

Java supports bind variables, your developers must start using prepared
statements and bind inputs into it. If you want your system to ultimately scale
beyond say about 3 or 4 users -- you will do this right now (fix the code).  It
is not something to think about, it is something you MUST do.  A side effect of
this - your shared pool problems will pretty much disappear.  That is the root
cause.

If I was to write a book on how to build 'non scalable
applications in Oracle', this would be the first and last
chapter.  This is a major cause of performance issues and a
major inhibitor of scalability in Oracle.  The way the Oracle
shared pool (a very important shared memory data structure)
operates is predicated on developers using bind variables.  If
you want to make Oracle run slowly, even grind to a total halt 
just refuse to use them.

For those that do not know, a bind variable is a placeholder in
a query.  For example, to retrieve the record for employee 1234,
I can either query:

SELECT * FROM EMP WHERE EMPNO = 1234;

Or I can query:

SELECT * FROM EMP WHERE EMPNO = :empno;

And supply the value for :empno at query execution time.  The
difference between the two is huge, dramatic even.  In a typical
system, you would query up employee 1234 maybe once and then
never again.  Later, you would query up employee 456, then 789
and so on.  If you use literal (constants) in the query  each
and every query is a brand new query, never before seen by the
database.  It will have to be parsed, qualified (names
resolved), security checked, optimized and so on.  In short, it
will be compiled.  Every unique statement you execute will have
to be compiled every time.  This would be like shipping your
customers Java source code and before calling a method in a
class  you would invoke the Java compiler, compile the class,
run the method and then throw away the byte code.  The next time
you wanted to execute the same exact method, you would do the
same thing  compile it, run it and throw it away.  Executing
SQL statements without bind variables is very much the same
thing as compiling a subroutine before each and every call.  You
would never consider doing that in your application  you should
never consider doing that to your database either.

Not only will parsing a statement like that (also called a HARD
parse) consume many more resources and time then reusing an
already parsed query plan found in the shared pool (called a
SOFT parse), it will limit your scalability.  We can see it will
obviously take longer, what is not obvious is that it will
reduce the amount of users your system can support.  This is due
in part to the increased resource consumption but mainly to the
latching mechanisms for the library cache where these plans are
stored after they are compiled.  When you hard parse a query, we
will spend more time holding certain low level serialization
devices called latches.  These latches protect the data
structures in the shared memory of Oracle from concurrent
modifications by two sessions (else Oracle would end up with
corrupt data structures) and from someone reading a data
structure while it is being modified.  The longer and more often
we have to latch these data structures, the longer the queue to
get these latches will become.  Similar to the MTS architecture
issue described above, we will start to monopolize scarce
resources.  Your machine may appear to be underutilized at times
 but yet everyone in the database is running very slowly.  This
will be because someone is holding one of these serialization
mechanisms and a line is forming.  You are not able to run at
top speed.

The second query above on the other hand, the one with :empno,
is compiled once and stored in the shared pool (the library
cache).  Everyone who submits the same exact query that
references the same object will use that compiled plan (the SOFT
parse).  You will compile your subroutine once and use it over
and over again.  This is very efficient and the way the database
is intending you will do your work.  Not only will you use less
resources (a SOFT parse is much less resource intensive), but
you will hold latches for less time and need them less
frequently.  This increases your performance and greatly
increases your scalability.

Just to give you a tiny idea of how huge of a difference this
can make performance wise, you only need to run a very small
test:

tkyte@TKYTE816> alter system flush shared_pool;
System altered.

tkyte@TKYTE816> declare
  2      type rc is ref cursor;
  3      l_rc rc;
  4      l_dummy all_objects.object_name%type;
  5      l_start number default dbms_utility.get_time;
  6  begin
  7      for i in 1 .. 1000
  8      loop
  9          open l_rc for
 10          'select object_name
 11             from all_objects
 12            where object_id = ' || i;
 13          fetch l_rc into l_dummy;
 14          close l_rc;
 15      end loop;
 16      dbms_output.put_line
 17      ( round( (dbms_utility.get_time-l_start)/100, 2 ) ||
 18        ' seconds...' );
 19  end;
 20  /
14.86 seconds...

PL/SQL procedure successfully completed.

tkyte@TKYTE816> declare
  2      type rc is ref cursor;
  3      l_rc rc;
  4      l_dummy all_objects.object_name%type;
  5      l_start number default dbms_utility.get_time;
  6  begin
  7      for i in 1 .. 1000
  8      loop
  9          open l_rc for
 10          'select object_name
 11             from all_objects
 12            where object_id = :x'
 13          using i;
 14          fetch l_rc into l_dummy;
 15          close l_rc;
 16      end loop;
 17      dbms_output.put_line
 18      ( round( (dbms_utility.get_time-l_start)/100, 2 ) ||
 19        ' seconds...' );
 20  end;
 21  /
1.27 seconds...

PL/SQL procedure successfully completed.

That is pretty dramatic.  The fact is that not only does this
execute much faster (we spent more time PARSING our queries then
actually EXECUTING them!) it will let more users use your system
simultaneously.

Review &
      Followup

      Rating:
      5
  April 21, 2001
Reviewer:  Sean Bu
       from Atlanta, GA

      Rating:
      5
  April 21, 2001
Reviewer:  K
       from Ellicott City, MD

      Rating:
      5
  April 21, 2001
Reviewer:  Pauline
       from U.S.A

      Rating: 5
very
      explicit...  April 23, 2001
Reviewer:  Marc
       from Belgium

      Rating:
      5
  April 23, 2001
Reviewer:
      A reader  from USA

      Rating: 5
Just
      excellent  April 24, 2001
Reviewer:  Hakan
      Dombekci  from Istanbul, TURKEY

      Rating:
      3
Helpful  June 07, 2001
Reviewer:
      David  from Harahan, LA

      Rating: 5
Importance of
      using bind variable  February 15, 2002
Reviewer:
      Kalpesh  from NJ 07024 , USA
Tom,
This is excellent. Very useful for developers as well as for DBAs to find out
the performance bottlenecks.
Simply Great!

      Rating:
      5
  February 27, 2002
Reviewer:
      A reader

      Rating: 5
Thank you so
      much  April 19, 2002
Reviewer:  Giga
       from Germany
Now i really know where our problems are.
I have the exact same problems and looking at the
sql code of our application(s), makes me shudder after i read your answer.

      Rating: 4
Funny I've been
      trying to tell some of our developers the same
      thing......  June 20, 2002
Reviewer:  DBA
       from UK
We have been hitting this error on one of our systems, our software developers
have suggested everything under the sun that could be the issue......

.......I looked into V$SQLAREA and couldn't believe what I saw. I was well
impressed with your response if only I could have been so brutally honest!!!

          Followup:

And to automagically see the problem statements see:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1163635055580
(and lower the 100 in the last query to say 10 or so over time, as you correct
the problem)

      Rating: 3
dynamic queries
      will cause ORA-04031 ?  August 21, 2002
Reviewer:
      I Singh  from Kansas City, MO
Tom,
Am i understanding it correct that if dynamic queries are executed on the
database then it will eventually give me a ORA-04031 ? Is there a threshold
number of objects that oracle ages from shared pool (based on LRU principle)
before it determines that it cannot fit the new object in?

Thanks in advance.

          Followup:

No, you misunderstand.

If you use bind variables -- as suggested, there will never be a problem.

If you do not use bind variables and you flood the server with
hundreds/thousands of unique queries you will

o run dog slow
o consume a ton of RAM (and maybe run out)
o not scale beyond a handful of users, if that

among other really bad side effects.

All sql in Oracle is dynamic sql under the covers.  The question here is "did
you do it right with bind variables or not".  These guys (above) did it wrong

      Rating: 4
do java
      PreparedStatements work like queries with bind
      variables  October 09, 2002
Reviewer:  SC
       from Colombia
If I have a query in a PreparedStatement that reads
String sql = "select a,b,c from table where d=? and e=?";
and create a PreparedStatement with it and subsequently fill the ? with the
setXXX() method. Does it work like a query with bind variables or do I need to
write the query in a yet different way?

          Followup:

that is how to do bind variables in JDBC, the ? are bind placeholders.

So yes, this is all you need to do to use binds in JDBC

      Rating:
      5
  May 29, 2003
Reviewer:
      A reader
I have a question related to this issue.

One think I couldn’t understand is

1.    When Oracle needs to parse new SQL it required memory in shared pool. I
will check for a free chuck.
2.    If it won’t find a free space it will free up the occupied space using LRU
method.
Then why are we getting this ORA-04031 error? I have a database with 250 MB
shared pool. The database is running in dedicated server mode.

If oracle couldn’t find a 4k continues block then it can free up an unused
memory.

          Followup:

because there weren't enough freeable chunks in the amount of space it decided
to look through.

bind variables, bind variables, its all about bind variables.

      Rating: 5
Thanks for you
      comments  May 29, 2003
Reviewer:
      A reader
When oracle is going to release these objects? After the end of each
transaction?

I have a job, which consists a sequence of call to 10 packages.

Begin
Call package 1;
...
Call package 10;
End;

In this scenario, is oracle going to release locks and free up the memory after
completion of each package call?

How to find out whether the problem is with not using “bind variables” or in
sufficient shared pool or setting?

          Followup:

what "objects"? what "locks"?

      Rating:
      5
  May 29, 2003
Reviewer:
      A reader
I wrote the following test case to simulate the problem (ORA-04031)

SQL> declare
  2  aSql varchar2(1000);
  3  mycount number;
  4  begin
  5      for i in 1..100000 loop
  6              aSql := 'select count(*) from t1 where id = ' || i;
  7              execute immediate aSql into mycount;
  8      end loop;
  9  end;
 10  /

PL/SQL procedure successfully completed.

Elapsed: 00:04:45.00

I thought it is going to fail with Ora-04031 error, but it didn’t. I observed
v$sql while the program was going on. V$sql table got flushed all the selected
statements until I=80000. If this is the case why Oracle is not able to flush
the statements on my production db.

          Followup:

because an execute immediate is

open
execute
close <<<====

on your production system, people are actually still using the things see.  so,
they are not freeable.  it is because people are using it and on production you
have lots of people.

maybe something like:

create or replace procedure recursive( p_n in number default 1 )
as
    type rc is ref cursor;
    l_cursor rc;
begin
    open l_cursor for 'select * from all_objects where object_id = ' || p_n;
    recursive(p_n+1);
exception
    when others then
        dbms_output.put_line( 'error ' || p_n );
        dbms_output.put_line( sqlerrm );
        return;
end;
/
exec recursive

you might get some other error other then 4031 (like cannot find called unit or
max cursors or something else) as it is quite a disasterous thing to do

(warning -- don't run this on your real systems!)

      Rating: 5
RA-04031: unabl
      e to allocate 64 bytes of shared memory ("large pool"  June
      12, 2003
Reviewer:  A reader  from ca

hi tom

I am getting the following error on our system:

-------
2003-06-12 04:13:33,120 [Job Dispatcher Thread] ERROR conn.ConnectionServic
e
 verifyRepositoryEx.324 - Invalid Connection Pool. ERROR = ORA-04031: unable
 to allocate 64 bytes of shared memory ("large pool","unknown object","sess
i
on heap","trigger condition node")"

what do various components of this error message
mean? (e.g. trigger condition node)?

My doubt is whether this indicates a "bind variable problem"? I was skeptical as
it says "large pool" which
I thought is "used by MTS for session memory, by
Parallel Execution for message buffers.." from your
book.
I would think that if it had menioned "shared pool" then
it would most likely indicate a bind variable issue..

What do you think the problem could be?

Thanx!

          Followup:

this is likely happening because your database is being connect to via shared
server (MTS) and we are unable to allocate the initial UGA portion (large pool
isn't big enough)

large pool is insufficiently sized for the number of users you desire to
connect.

      Rating: 4
the database is
      not using MTS  June 12, 2003
Reviewer:
      A reader  from ca
I found this out by doing an sqlplus and
running
ps-ef|grep oracleora901

Every session creates a new process.

which implies dedicated server mode.

(There must be an easier way to detect dedicated/shared mode)

          Followup:

in your application

select server from v$session where sid = ( select sid from v$mystat where
rownum=1)

or -- well -- ask your DBA how it is setup.  They are the ones that need to be
looking at this issue in the first place with you.

      Rating:
      5
thanx!  June 12, 2003
Reviewer:
      A reader  from ca

      Rating:
      5
  June 30, 2003
Reviewer:  MG
       from Germany
Hi Tom,

I have called, some of pl/sql packages from my application. It works fine for
long time.

Then recently, I transfer this database to a another network.

Then after some time I get following error:
 ORA-04031

I am confused, If this is because not properly using bind variables, then why is
this not arise in earlier (before tranfering the database)?

I use same init.ora file without changing sizes of memory allocations.

So could you please tell me, is there any other reason can be caused for this?
or why is this happens?

          Followup:

more users, different load -- i would guess, since we can only guess given the
amount of information.

      Rating: 4
bind
      variables  June 30, 2003
Reviewer:
      Santhanam  from bangalore india
Excellent

      Rating: 5
Excellent
      Analysis  July 01, 2003
Reviewer:  Lakshmi
      Jeedigunta  from MI, USA

      Rating: 4
Excellent
      Explanations  July 10, 2003
Reviewer:  Srini
       from Chennai, Tamilnadu, India

      Rating: 5
large pool and
      dedicated server mode  July 10, 2003
Reviewer:
      A reader
if we are in dedicated server mode, how can we get
"ORA-04031: unable
 to allocate 64 bytes of shared memory ("large pool"..
thought large pool was relevant only for MTS mode.
How does dedicated mode use this piece of memory.
(assuming we are not running parallel queries.)

Thank you!!

          Followup:

tell me what you were doing -- was it logging on.

      Rating: 5
no an app was
      running   July 11, 2003
Reviewer:
      A reader
It occurs every now and then in our application.

this occurs in 9.0.1.0. The solution apprently in
our group is to move to 9.0.1.4. Actually I posted
this problem in the same thread (June 12).. The error
message is:

"2003-06-12 04:13:33,120 [Job Dispatcher Thread] ERROR conn.ConnectionService
verifyRepositoryEx.324 - Invalid Connection Pool. ERROR = ORA-04031: unable
 to allocate 64 bytes of shared memory ("large pool","unknown object","session
heap","trigger condition node")"

I am sure this is dedicated server mode - then how
come large pool is getting used. Also, could you kindly
explain what the various components of the above error
mean (e.g. what does "trigger condition node" mean?

Thanx!

          Followup:

the tags in there just represent where in the code it was, they are not
meaningful to you and me, only to support.

you can use lsnrctl status or lsnrctl services to see if your doing shared or
dedicated (or just query SERVER from v$session)

      Rating: 5
thanx it is in
      dedicated mode!  July 11, 2003
Reviewer:
      A reader
I ran the select...

SQL> set echo on
SQL> select server from v$session where sid=(select sid from v$mystat where
rownum=1);

DEDICATED

SQL> spool off

          Followup:

not so fast

that shows a single dedicated server session.

select server, count(*) from v$session group by server;

      Rating: 5
Sort Area
      size  July 11, 2003
Reviewer:  George
      Frederick  from NYC
I have been an Oracle DBA for many years. Tuning is a complex science (or art),
indeed.

But if you asked me: "If you had to improve the performance of a database (not
the application) by tuning only one thing, what would you do ?"

I would answer "Increase the sort_area_size (or the PAT in 9i)". On the very
whole, it seeems to me the most constraining factor of the databases I have
touched. Especially because of lots of implicit sorts carried out dynamically
due to inefficient design. Some systems speed up thereafter.

I might expect a slam from you, but it's my experience, Tom.

          Followup:

well, in my next book I write:

Note: Most implementations simply leave SORT_AREA_SIZE and HASH_AREA_SIZE at the
defaults, which is generally in the 64KB range. This is far too small in most
cases. In general, a value of 512KB to 1MB should be considered. If you review a
Statspack report and see many sorts to disk, reevaluate your sort area size.

As of Oracle9i, I personally consider these parameters obsolete when using
dedicated server.. You should be using WORKAREA_SIZE_POLICY = AUTO and
PGA_AGGREGATE_TARGET settings instead, and forget about fine-tuning and tweaking
these parameters

So I concurr -- however, increasing the sort area size will not do anything to
fix lack of bind variables ;)

      Rating: 5
here you
      go  July 11, 2003
Reviewer:  A reader

"not so fast that shows a single dedicated server session..."

whoa - now i am even more confused!!

SQL> set echo on
SQL> select server, count(*) from v$session group by server;

DEDICATED         15
NONE               8

SQL> spool off

          Followup:

8 of those are shared servers that are currently inactive.....

      Rating: 5
thanx
      Tom!  July 11, 2003
Reviewer:  A reader

"8 of those are shared servers that are currently inactive....."...
I take it that "NONE" implies shared server connection.

That is strange - This is my development environment.
A database that I installed (Enterprise Edition 9.0.1.0)
. I did not do anything specific to have those
shared server connections (unless our applications
middle tier connection pooling has anything to do
with it - I dont think so!).
I installed a 9i and started it up normally...
Any ideas where these shared server connections
are coming from!!

Thank you!!!

          Followup:

mts/shared server is typically configured by default -- the database registers
itself with the listener and broadcasts the fact that these connections are
available

      Rating:
      5
Excellent  July 11, 2003
Reviewer:
      Brian
I think I'm finally getting it.

Bind Variables are an issue when writing dynamic SQL such as Native Dynamic SQL
(Execute Immediate statements) or  using DBMS_SQL.  The predicate (WHERE
statement) should always be a bind variable and declared as (e.g.,
:variable_name) in native dynamic SQL.

In SQL*PLUS, I can optionally declare a bind varable using:

variable [variable_name] [type]

before my statements.

In dynamic sql, I have to follow up the open for statement such as:

open l_cursor for 'select . . . where x = :variable_name' using variable;

with 'using . . .' where the 'using variable' is one of the parameters of the
function or procedure.

However, if I am just writing static sql such as:

create or replace procedure test(input number)
as
begin
for x in (select . . . where x = input)
loop
...
end loop;
end;

PL/SQL will automatically bind my variables.

I hope I have finally figured this out.

And this allows me to better scale my applications by preventing execessive
parsing.

Right?

          Followup:

exactly -- and static sql in plsql is the very best way to ensure that.  it has

o least parses
o most binds
o tightest code path.

      Rating: 5
shared pool/
      bind variables  July 11, 2003
Reviewer:  DS

Tom, little confused about the original error message in this post and your
repsonse.  I thought that if the application used no bind variables then the
shared poll will get filled up with non-reusable queries so performance will go
down (understandbale).  So when the shared poll becomes full it will just age
out the oldest query, so why were they getting that error - was it because all
available ram on the machine was being used or because so many queries were
filling up the shared pool that it couldn't quickly get more in

hope that makes sense

Thanks

          Followup:

the bigger the shared pool, the harder it is to manage and the more fragmented
it can become.  the algorithms don't even attempt to compact the entire thing
when searching for memory (sort of a serialization issue there).  when not using
binds, a smaller shared pool is actually nicer then a big fat one.

but, the answer is -- use binds.

      Rating:
      5
thanx!  July 14, 2003
Reviewer:
      A reader
"mts/shared server is typically configured by default -- the database registers
itself with the listener and broadcasts the fact that these connections are
available "

Thanx but the Oracle installation guide says this:

"General Purpose configuration: If you select this option with an Enterprise
Edition or Standard Edition installation, the Database Configuration Assistant
creates a preconfigured, ready-to-use, multipurpose database with the following:

Default initialization parameters using server parameter file and Automatic Undo
Management

Automatic installation and configuration of Oracle Options and interMedia

Advanced Replication capabilities

****Database configuration of dedicated server mode ***

Archiving mode of NOARCHIVELOG "

Since I chose General purpose, I was suprised to see
that in addition to dedicated servers, I also had some
mts servers configured. Is there anyway, I can get rid
of the mts server connections?

thanx!

          Followup:

unset the mts_ init.ora parameters

      Rating: 5
getting rid of
      mts..  July 14, 2003
Reviewer:
      A reader
actually after the machine got rebooted (due to some other reasn)- I ran the
same query again - now it shows only
dedicated sessions.

14:41:25 test@ORA901> /

DEDICATED         18

14:41:26 test@ORA901> spool off

thanx!

          Followup:

No, that just means no one connected via mts at the point in time you looked is
all...

      Rating:
      5
mts?  July 15, 2003
Reviewer:
      A reader
"unset the mts_ init.ora parameters "

could not find any "mts" parameters (searched for
them in the initora901.ora)

For the time being let us justigmore the topic - If i
run into this again, I can let you know..

thanx for the all the help and insight!!

      Rating: 5
("shared
      pool","unknown object","sga heap(1,0)",  July 15, 2003

Reviewer:  Saradha Bavanadam  from NJ, USA

Tom,

We have migrated to Oracle9i instance on this weekend
and the database is up and running from Sunday and the
application users are working on. Today (Tuesday)
we received shared memory problem and unable to logon to
the database.

ORA-04031: unable to allocate 26200 bytes of shared memory ("shared
pool","unknown object","sga heap(1,0)",

I used the following query to find out the SQLs that are not using the bind
variables. And I did find many dynamic SQLs coming from Java are not using bind
variables.

But my question is, these dynamic SQLs are there before and
we did not get this problem before the 9i migration.

Is there anything changed in 9i or is any parameter got
changed that we have to take care of during the migration
that is relevent to this problem.

SELECT substr(sql_text,1,30) "SQL",
count(*) ,
sum(executions) "TotExecs"
FROM v$sqlarea
WHERE executions < 5
GROUP BY substr(sql_text,1,30)
HAVING count(*) > 30
ORDER BY 2
/

Thanks
Saradha

          Followup:

do you still have your 8i instance on your test machines to check against?

no, we do not tend to change init.oras on you during the migration -- we might
suggest some, but we do not change them ....

that sql isn't good for finding non-bind variable statements at all.

when you tested the applications -- before upgrading your production box -- what
happened in that environment?

      Rating: 5
but why after
      the migration ?  July 15, 2003
Reviewer:
      Saradha Bavanandam  from NJ, USA
Tom,

I do have production support instances which are not
yet migated to 9i and still running in 8i but the parameter
size are different as compare to the Production machine.

I identified the SQLs that are not using the BIND variables
using different queries and conveyed to the correspondig people to take care of
this.

They are asking me, these dynamic SQLs did not create problem before and are
working fine for the last one year in Oracle8i instance and Why all of a sudden
after the migration this problem is comming.

They did agree to change those dynamic SQLs but they are still looking for the
answer, why it is now.

Thanks
Saradha

          Followup:

without a controlled reproducible environment to test with - all i can say is "i
don't know", insufficient data to diagnose.

      Rating: 5
ORA-06508:
      PL/SQL: could not find program unit being call  August 18,
      2003
Reviewer:  Pushparaj Arulappan  from Edison, NJ

Tom,

I am receiving the following error from my session whenever a particular
dependent program is re-compiled.

ORA-06508: PL/SQL: could not find program unit being called.

I have a package (PACK1) which calls another package (PACK2). The two packages
are dependent on many other stored programs and packages also.

Assume initially all the programs are compiled and valid.

I open a SQLPLUS session (session 1) and execute the PACK1 package and it
executes successfully.

While this session (session 1) is still open, I open another SQLPLUS session
(session 2) and re-compile PACK2.

Now I go back to the (session 1) and try to execute the PACK1 pacakge again and
this time it raises this error.

ORA-06508: PL/SQL: could not find program unit being called.

But If I logout and re-connect session 1 then it is not a problem.  I could not
repeat this error with any other test programs something very similar to this.
This is happening only to this program. These two programs are relatively large
programs 2000 lines each program and these two call many other programs also.

The package PACK1 does call another package say
PACK3 and re-compiling PACK3 is not producing any error.

We have this situation in our environment where the WEB application server
initially opens a minimum number of sessions and keeps it in the connection
pool. So whenever we recompile this program, we will have to re-start the web
server in order to have a fresh connection.

Please give us some clue.

Thanks
Pushparaj

          Followup:

pack1 maintains a state doesn't it.

      Rating: 5
Is there any
      work around ?  August 19, 2003
Reviewer:
      Pushparaj Arulappan  from NJ, USA
Tom,

Just for testing I did the following.
After I re-compiled package PACK2 in session 2,
I have executed DBMS_SESSION.RESET_PACKAGE in session 1
and then I executed PACK1 package from session 1 and
the problem did not occur.

I may not want to implement this work around in production
but why I am not able to reproduce this error with other
similar test cases. Also, this problem does not occur if I
recompile some other dependent program of PACK1 package.

Thanks
Pushparaj

          Followup:

because the package HAS A STATE.

you recompile it -  and we blow away this particular session state that YOUR
session is dependent on.

using dbms_session.reset_package -- resets ALL PACKAGE states, as if you had
just logged in.

this is not an error, it would be an error to silently wipe out your package
variables and not tell you about it!

      Rating:
      3
  August 19, 2003
Reviewer:
      A reader
Very interesting scenario,

Tom could you please shed some light over this.

          Followup:

put a light over your monitor!

      Rating:
      1
  August 20, 2003
Reviewer:
      A reader
But Tom,

Why is this not occuring in case of PACK3 in the above person's case. He says
when PACK3 is compiled and he returns back to session where in he was using
PACK1 this problem is not occuring.

          Followup:

because the compilation of pack3's body does not invalidate pack1 (that is the
beauty of packages) and pack3 apparently does not have any state to maintain.

packages MAY have a state.
packages do not HAVE to have a state.

      Rating: 5
ora
      4031  August 20, 2003
Reviewer:
      A reader
Tom, why am I supposed to get an ora-4031 ? I thought the worst that would
happen would be my statements aging out. How do they "crowd" the shared pool to
the extent of creating an out-of-space situation ? Wouldn't the LRU statements
simply age out ?

PS: Where is that script of yours that checks for candidates for binds ? I
cannot find it.

Thanks

          Followup:

search this site for

remove_constants

      Rating: 3
Shared Memory
      Problem (unable to allocate shared memory ...)  August 21,
      2003
Reviewer:  Cong Tang  from Vienna, Austria

I experienced the same problem with Oracle8i 8.1.5. It was shared pool latch
contention due to literal/unshared SQL. Modifying existing applications to use
bind variables is expensive, especially with complex applications. Flushing the
shared pool every hour and pinning packages could be a valid workaround.
Upgrading to Oracle9i and setting cursor_sharing = SIMIMILAR will help.

          Followup:

what is expensive is doing it wrong in the first place no?

think about the massive amounts of hardware you could have saved had you a
single developer who understood or had experience with oracle?

think about the massive amounts of time you could have saved....

      Rating:
      1
answer  August 21, 2003
Reviewer:  Bargh
      Kara  from NYC
dear tom, you seem to have forgotten to answer the reader asking about "ora
4031" above. Could you answer it please ?

      Rating: 5
I AM STILL
      TRYING TO UNDERSTAND THIS BIND VARIABLES  August 21, 2003

Reviewer:  Paul  from Indiana,USA
Tom can you tell me how to bind variables in VB , say i have follwoing select
statement

select * from coil_status where
coil_nbr ='" & input_coil_nbr & "'

is that the way you bind a statement , can you tell me how to bind statement
during updates too
thanks

          Followup:

that is not a bind variable. that results in a unique query for each execution.

you have queries like

select * from coil_status where coil_nbr = '55';
select * from coil_status where coil_nbr = '56';
select * from coil_status where coil_nbr = '57';

and so on.  bad that you quoted a number, worse that you didn't bind.

the only VB i have is here:

http://asktom.oracle.com/~tkyte/ResultSets/index.html
can someone who programs vb post a small snippet using binds?  it keeps coming
up over and over

when I tried to compile vb on my linux desktops it keeps giving me an error
"sorry, only open systems code allowed, try again"

      Rating: 5
Using Binds in
      VC  August 22, 2003
Reviewer:  Pinto
       from Bangalore
Sorry, i don't know VB but i have seen lot of queries as to how its done in VC.

common code
HSTMT stmtHandle;
SQLRETURN retcode;
CDatabase *pDB = new CDatabase;
pDB->OpenEx(...);
char* SQLStmt = new char[2000];
SQLTCHAR szTaskID[10] = "Hello";
SQLINTEGER iTaskTimeStamp = k ;
SQLINTEGER ivalSizeChar = SQL_NTS;
SQLINTEGER ivalSizeInt = 0;
int nTaskIDLength= strlen((const char*)szTaskID);
SQLAllocStmt(pDB->m_hdbc,&stmtHandle);
/*****************
  CODE FOR SQLs
*****************/

sprintf(SQLStmt,"select c1 from t1 where c1 = ?");
retcode = SQLBindParameter(stmtHandle,
1,SQL_PARAM_INPUT,SQL_C_TCHAR,SQL_CHAR,nTaskIDLength,0,szTaskID,nTaskIDLength,&iv
alSizeChar);

retcode = SQLExecDirect(stmtHandle,(unsigned char*)SQLStmt,SQL_NTS);

/**************************
Code for Stored procedures
***************************/
strcpy(SQLStmt,"call ins_proc(?)");

retcode =
SQLBindParameter(stmtHandle,1,SQL_PARAM_INPUT,SQL_C_TCHAR,SQL_CHAR,nTaskIDLength,
0,szTaskID,nTaskIDLength,&ivalSizeChar);
retcode = SQLExecDirect(stmtHandle,(unsigned char*)SQLStmt,SQL_NTS);

/**************************
Code for functions
**************************/

strcpy(SQLStmt,"begin ?:=ins_fun(?); end;");

retcode =
SQLBindParameter(stmtHandle,1,SQL_PARAM_OUTPUT,SQL_C_SLONG,SQL_INTEGER,0,0,&iTask
TimeStamp,0,&ivalSizeInt);
retcode = SQLBindParameter(stmtHandle,2, SQL_PARAM_INPUT, SQL_C_SLONG,
SQL_INTEGER,0, 0, &k, 0,&ivalSizeInt);
retcode = SQLExecDirect(stmtHandle,(unsigned char*)SQLStmt,SQL_NTS);

      Rating: 5
Auto
      generation  August 22, 2003
Reviewer:
      Ismail  from India
During Autogeneration of no, if I use sequence, i am facing the following
problem.

- If finally the updation fails (becoz of some reason or other), data will be
rolled back, but sequence increment is not getting rolled back. Hence number
skipping is happening.

- As you said, max +1 logic is also not working properly.

Pl suggest me best method.

          Followup:

use the SEQUENCE and ignore the fact that they are non-sequential.  so what?

      Rating: 4
unable to
      determine the problem  August 22, 2003
Reviewer:
      Anurag  from INDIA
Tom I've a testing database oracle 8i on PIII 500, win-nt 128 mb ram, After a
normal startup the system becomes damn slow even no activity is performed. I
calculated the hit ratio  of library cache, it was 90.67. I increased
shared_pool_size by 1 MB and then rebound the datbase. The database shows great
improvement with hit ratio considerabley increasing from 97.89 to 99.99. Later
after an hour with no activity performed, the hit ratio was again 92.34. I am
not performing any activity. Still i see the changes in ratios. What is the
mystery?? and how to get rid of it.

thanks in adv.

          Followup:

lets see, running a database on a machine with just about enough memory to run
the operating system.

thinking that cache hit ratios are the important thing...

adding a measly 1mb to the shared pool...

not really describing what is happening in the system, what the system needs to
do, how many users it needs to support, how it is currently using resources....

128m of ram is less ram then you need to run windoze and word at the same time -
let alone windows and a database.

cache hit ratios are not meaningful in themselves (see hotsos.com and get "are
you still using cache hit ratios")

If you want your cache hit ratio to go waaaayyy up - i can fix that easily.
Here see:

ops$tkyte@ORA920> SELECT round( (1 -(phy.value / (cur.value +
con.value)))*100,2) "CACHE HIT RATIO"
  2    FROM v$sysstat cur, v$sysstat con, v$sysstat phy
  3   WHERE cur.name = 'db block gets'
  4     AND con.name = 'consistent gets'
  5     AND phy.name = 'physical reads'
  6  /

CACHE HIT RATIO
---------------
          90.03

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace procedure set_cache_hit_ratio_to( p_ratio in
number )
  2  as
  3      l_cache_hit number;
  4  begin
  5      loop
  6          SELECT round( (1 -(phy.value / (cur.value + con.value)))*100,2)
"CACHE HIT RATIO"
  7            into l_cache_hit
  8            FROM v$sysstat cur, v$sysstat con, v$sysstat phy
  9           WHERE cur.name = 'db block gets'
 10             AND con.name = 'consistent gets'
 11             AND phy.name = 'physical reads';
 12
 13          exit when ( l_cache_hit >= p_ratio );
 14
 15          for i in 1 .. 100
 16          loop
 17              for x in ( select d1.dummy from dual d1, dual, dual )
 18              loop
 19                  null;
 20              end loop;
 21          end loop;
 22      end loop;
 23  end;
 24  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec set_cache_hit_ratio_to( 92 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> SELECT round( (1 -(phy.value / (cur.value +
con.value)))*100,2) "CACHE HIT RATIO"
  2    FROM v$sysstat cur, v$sysstat con, v$sysstat phy
  3   WHERE cur.name = 'db block gets'
  4     AND con.name = 'consistent gets'
  5     AND phy.name = 'physical reads'
  6  /

CACHE HIT RATIO
---------------
             92

ops$tkyte@ORA920>

whoo-hoo.  we could get close to 100% if we like.

bummer that my CPU was pegged to do it, but my cache hit - perfection!

you probably have insufficient ram to run excel and word at the same time with
good performance, let along a database.

if your SGA is over say 50-60meg, you are paging like wild.  look and see if
your disk drive light isn't blinking like crazy.

      Rating: 5
How they've to
      pass parameter  August 22, 2003
Reviewer:  Ashiq
      Shamsudeen  from Trichy, Tamil Nadu, India
Hi Tom,

   Can you show me small example how to pass parameter to the stored procedure
from the front end(say front end be java or asp) , which will be using bind
variable and the code which is not using bind variable.
  I doesn't have idea about Java ,so if front end(or java) guys they code
wrongly (in the sense not using bind variables) .I can ask them to change the
code which use bind variable.

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1101233460502

      Rating: 5
Auto gen -
      sequence  August 22, 2003
Reviewer:  Ismail
       from India
"Auto generation no" should be sequence, because
- "financial transactions, the nos MUST be in sequence, otherwise auditors will
question the accuracy and there will not be any control for the users to
maintain no. of transactions. (if no skipping happens)

Hence sequencial order is very much required.

          Followup:

and in the paper based days, what did you do eh?

when you spilt coffee on the forms and had to throw away some?

why, you voided them.

if you want 100% sequential numbers with no gaps nowhere -- you will serialize.
plain, simple, to the point.  you will scale not at all.

serialization, one at a time -- and even then, I'll betcha you get gaps.

to maintain the number of transactions is as simple as "count(*)"

if you like, just issue "update t set transid = rownum" every night.  where
transid is some magic column you keep for "auditors" to make them happy.  It
would be as relevant and meaningful and accurate as serialization all day
long!!!

      Rating: 5
RE: can someone
      who programs vb post a small snippet using binds?  August
      26, 2003
Reviewer:  Mark A. Williams  from
      Indianapolis, IN USA
Tom,

I am not a VB programmer, but here is a small snippet I managed to cobble
together.  See embedded comments.  Also, wrapping of the text posted may be an
issue.

Thanks,

Mark

==========

Module VBOraBind
  Sub Main()
    '// This is a very simple example that illustrates how to use a bind
variable in
    '// a Visual Basic .NET program with Oracle.  I am not a VB programmer, so I
offer
    '// not guarantee that this is the best way to use a bind variable in VB,
only that
    '// it works on my test system.
    '// As you can see it has no error handling code or other features that
would be
    '// included in real production code.

    '// In my opinion, a well-architected Oracle application would be using
packages
    '// in the server, and the client would simply be making calls to those
packages,
    '// and not submitting SQL statements as we do in this example.

    '// Created with Visual Studio .NET (but the concepts are the same in Visual
Studio 6)
    '// Requires a reference to the ADODB library
    '// version 2.7 was used in this example

    '// Uses the 'HR' sample schema (employees table) supplied with the Oracle9i
database

    '// Declare and instantiate a connection object
    Dim cnOracle As ADODB.Connection
    cnOracle = New ADODB.Connection()

    '// Declare and instantiate a command object
    Dim cmdOracle As ADODB.Command
    cmdOracle = New ADODB.Command()

    '// Declare and instantiate a parameter object
    Dim prmOracle As ADODB.Parameter
    prmOracle = New ADODB.Parameter()

    '// Declare and instantiate a recordset object
    Dim rsOracle As ADODB.Recordset
    rsOracle = New ADODB.Recordset()

    '// Open a DSN-less connection
    '// You must adjust values for your environment
    cnOracle.Open("Driver=Oracle in OraHome92;server=cds9i;uid=hr;pwd=demo")

    '// This is the SQL statement for the example
    '// The '?' is the placeholder for the bind variable
    cmdOracle.CommandText = "select first_name || ' ' || last_name from
employees where employee_id = ?"
    cmdOracle.CommandType = ADODB.CommandTypeEnum.adCmdText

    '// Create the parameter object, assign it a value, and append it to the
    '// parameters collection of the command object
    '// This is the bind variable that will be passed to Oracle
    prmOracle = cmdOracle.CreateParameter("employee_id",
ADODB.DataTypeEnum.adInteger, ADODB.ParameterDirectionEnum.adParamInput)
    prmOracle.Value = 200
    cmdOracle.Parameters.Append(prmOracle)

    '// Assign the connection object to which this command object belongs
    cmdOracle.ActiveConnection = cnOracle

    '// Execute the command and assign the recordset
    rsOracle = cmdOracle.Execute

    '// Simply print out the value from the recordset
    While Not (rsOracle.EOF)
      Debug.WriteLine(rsOracle.Fields(0).Value)
      rsOracle.MoveNext()
    End While

    '// Explicitly close objects
    rsOracle.Close()
    cnOracle.Close()

    '// Release objects
    prmOracle = Nothing
    rsOracle = Nothing
    cmdOracle = Nothing
    cnOracle = Nothing
  End Sub
End Module

          Followup:

thanks, that was perfect.

      Rating: 4
Ora-04031 on
      Dedicated Server  August 27, 2003
Reviewer:
      Vivek Sharma  from Bombay, India
Dear Tom,

I really appreciate the solutions provided by you related to each and every
oracle related problems. Also the solution for ora-04031 was excellent. But I
have a question. I have a dedicated server configuration with a shared pool size
of 324 MB and shared pool reserved size is default to 5% (not set in init.ora)
about 17 MB. The database version is 8.1.6.3. I have checked the statspack
report and the soft parse ratio is well above 98%. Then why am i facing a
problem of ora-04031. The error which I receive is

Tue Aug 26 02:41:20 2003
Errors in file /depdb/oracle/DEPDB/dump/bdump/depdb_snp0_1775.trc:
ORA-00604: error occurred at recursive SQL level 1
ORA-04031: unable to allocate 512 bytes of shared memory ("shared
pool","STANDARDSYS","PL/SQL DIANA","PAR.C:parapt:Page")

We have not kept any PL/SQL in shared pool reserved area but it seems that
oracle by default keep some of them in the reserved area. I have checked in 2-3
databases and found around 25 of them in reserved area.

What do you recommend. The application is using Bind Variables. What should I do
?

A) Increase the shared pool size by around 50MB (We have already increased it
from 250 to 320 MB when we last faced this error).

or

B) Keep some objects in reserved area

or

C) Reduce the size of Share pool reserve area to 0.

What are your recommendations ? Please help me resolve this issus as it is
production 24 x 7 database and when we face this error, we need to bounce the
database else the users are not able to connect to the DB.

Regards

          Followup:

first -- not sure what you are saying about the reserved area.  basically that
is an area reserved for large allocations -- 512bytes won't go there.

how about option d) get onto supported versions

you can have a soft parse ratio of 98% and still be hard parsing like made.
that means 2 out of every 100 parses is "hard".

have you tried flushing the shared pool versus killing the server?

      Rating: 4
Shared pool
      allocation  August 27, 2003
Reviewer:  Krish
      Ullur  from Nashville, TN
I read (somewhere) that shared pool memory is allocated in chunks of contiguous
4K bytes.
But an 'exec dbms_shared_pool.sizes(0);' shows objects with 1(K) as well. Could
you please throw light on this unit of memory allocation in shared pool?

Thx,

KU

          Followup:

smaller then 4k -- not just 4k, smaller then 4k

      Rating: 5
Is 4k the
      minimum allocation in shared pool?  August 27, 2003

Reviewer:  Reader
Tom, How does oracle allocate memory for "small" objects? What does it mean by
4k chunks? Thanks.

          Followup:

4k is not the minimum.

it is a size that plsql tries to chunk code into so rather then allocation 125k
of space, it would try to allocate ceil(125/4) - 4k chunks.  it helps prevent
fragement of the shared pool where a large object would not be able to find
sufficient contigous space.  it is easier to find 50-4k chunks then it is to
find a single 200k contigous one.

      Rating: 5
Can this happen
      when bind variable is not used?  September 25, 2003

Reviewer:  Mike Costa  from NJ, USA
We had this happen twice on our Oracle DB.
The oracle DB simply becomes not responsive at all.
Cannot even log on as sysdba.   We sent Oracle support
the traces and was told nothing special was found.

Could this be the result of lack of bind variables?  We do
have queries that don't use bind variables and will be fixed
soon.  But wonder if that could be the possible cause of
the crash.

          Followup:

no and support should not (in my experience would not) have stopped there.

this doesn't sound like a "crash", you had a hang, they should have been able to
help you diagnose this.

September 25, 2003 Patibandla -- Thanks for the question regarding
      "Error Trapping - Reg", version 8.1.6.0

      You Asked
In our database we are having around 200/300 procedures/packages.
whenenver there is an error
in the error trapping table we want to insert a row with the following contents.

1. Procedure Name
2. Line No of the procedure at which exception has been raised.
3. Error No.
4. Error Message

Is it possible to catch procedure name/line no.

Yr reply would be very much appreciated.

Regards

Koshal

      and we said...
It depends on how you write your code.  If you coded the client -- the client
can catch this and perform this sort of logic.

Otherwise -- you have to put your exception blocks around the statements that
can cause exceptions, for example:

    begin
       insert into t values ( ..... );
    exception
       when others log_error;
    end;

    begin
        update y set ....;
    exception
        when others log_error;
    end;

where log_error and supporting routines would look like:

ops$tkyte@ORA8I.WORLD> create or replace function whence return varchar2
  2  as
  3      call_stack  varchar2(4096) default dbms_utility.format_call_stack ||
chr(10);
  4      n           number;
  5      found_stack BOOLEAN default FALSE;
  6      line        varchar2(255);
  7      cnt         number := 0;
  8  begin
  9      loop
 10          n := instr( call_stack, chr(10) );
 11          exit when ( cnt = 4 or n is NULL or n = 0 );
 12
 13          line := substr( call_stack, 1, n-1 );
 14          call_stack := substr( call_stack, n+1 );
 15
 16          if ( NOT found_stack ) then
 17              if ( line like '%handle%number%name%' ) then
 18                  found_stack := TRUE;
 19              end if;
 20          else
 21              cnt := cnt + 1;
 22          end if;
 23      end loop;
 24      return line;
 25  end;
 26  /

Function created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> set define on
ops$tkyte@ORA8I.WORLD> drop table error_table;

Table dropped.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> create table error_table
  2  ( timestamp  date,
  3    whence     varchar2(1024),
  4    msg        varchar2(1024),
  5    code       number
  6  )
  7  /

Table created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> create or replace procedure log_error
  2  as
  3      pragma autonomous_transaction;
  4
  5      l_whence varchar2(1024);
  6      l_msg    varchar2(1020) default sqlerrm;
  7      l_code   number default sqlcode;
  8  begin
  9      l_whence := whence;
 10      insert into error_table
 11      ( timestamp, whence, msg, code )
 12      values
 13      ( sysdate, whence, l_msg, l_code );
 14
 15      commit;
 16  exception
 17      when others then
 18          rollback;
 19          raise;
 20  end;
 21  /

Procedure created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> drop table t;

Table dropped.

ops$tkyte@ORA8I.WORLD> create table t ( x int primary key );

Table created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> begin
  2      begin
  3          insert into t values ( 1 );
  4      exception when others then log_error; raise; end;
  5
  6      begin
  7          insert into t values ( 1 );
  8      exception when others then log_error; raise; end;
  9  end;
 10  /
begin
*
ERROR at line 1:
ORA-00001: unique constraint (OPS$TKYTE.SYS_C0038883) violated
ORA-06512: at line 8

ops$tkyte@ORA8I.WORLD> select * from error_table;

TIMESTAMP WHENCE                         MSG                        CODE
--------- ------------------------------ -------------------- ----------
30-MAR-01 88bfd870         8  anonymous  ORA-00001: unique            -1
          block                          constraint
                                         (OPS$TKYTE.SYS_C0038
                                         883) violated

1 row selected.

Review &
      Followup

      Rating:
      5
  May 09, 2001
Reviewer:  Robert
      Petuker  from Germany
it is a very useful peace of code

thanks

      Rating: 5
It helped me a
      lot  October 08, 2001
Reviewer:  Garima
       from USA
Thanks for a wonderful piece of code

      Rating: 2
Great but what
      about context....  October 04, 2002
Reviewer:
      A reader
Hi,

I whole heartedly agree that knowing exactly which line of code errored and the
error it got it of a huge help.  But often I have found that you need the
context of the error for investigation of the problem.  So in your example while
you know that you violated the PK on a table, you don't know why you violated it
(in your example you tried to insert the value 1)

I've tried various models but it seems Oracle will never be able to give me both
pieces of information, as I would have to trap the error to set context Info
(perhaps parameters for an "insert into select" statement and then lose the line
that actually caused me to jump into the exception.

Is there a way around this?  The only idea I have had is to continually keep up
to date some context information for the operation you are to perform next, so
if you error, you know the value in this is context of the operation that
failed.  I was thinking of storeing this with application_info

However I'm worried about the overhead of this.  I see some possible view
points, I wondered if you subscribed to one of them or a seperate one.

1.  I don't think you need that context info.  the context info of the caller
should be enough
2.  Yeah, it's an overhead and tough.  if it makes the application too slow
maintaining the context, you have a bad design or either the application or your
context model.
3.  errr, that's it.

Thanks as always,

Mike.

          Followup:

search this site for

dbms_trace

      Rating: 5
Function
      returns NULL  October 04, 2002
Reviewer:  A
      reader  from BG
Tom,
   I tried executing your function whence independently as a PL/SQL Block. The
return value (line) has the NULL value.

regards

          Followup:

did you trying debugging it a bit to see whats up ?  you know, a little
dbms_output.put_line here and there?

(its what I would have to do if you gave me
o your version
o your os
o your example

but you could do it for us and post the fix)

      Rating: 4
what's cnt = 4
      ?  October 04, 2002
Reviewer:  Robert C
       from PA
>> function whence
>> 11          exit when ( cnt = 4 or n is NULL or n = 0 );

Tom ,  You provided a procedure variation of the above function (think you
called it "who_called_me")
you had "exit when ( cnt = 3 or n is NULL or n = 0 ); "
and you commented:
  -- cnt = 1 is ME
  -- cnt = 2 is MY Caller
  -- cnt = 3 is Their Caller

so what's cnt = 4 in the function whence then ? Is this as high as cnt will get
?
Thanks

          Followup:

when it is 4, it is their callers caller of course.  But, if it is 4, we've sort
of MISSED THE BOAT and I'm breaking out of the infinite loop

      Rating: 4
RE what's cnt =
      4 ?   October 04, 2002
Reviewer:  Robert
       from pa
>> what's cnt = 4 ?
never mind my original question...it's just one of the slice in the stack.
Thanks

      Rating: 4
How about this
      for context.........  October 04, 2002
Reviewer:
      Robert  from Memphis, USA
Tom,

I would like to offer a suggestion for getting context and I hope you or others
might comment on it.

I would add 2 columns to the 'error_table' (e.g. CONTEXT_LOCATION and
CONTEXT_ID) and add add the same 2 input parameters to the log_error procedure.
All you have to do to use it are these simple steps.
1) Define 2 local variables in your program corresponding to the 2 log_error
parameters (l_context_location, l_context_id)
2) Before each sql statement, load L_CONTEXT_ID with enough info to be able to
find the offending row (e.g. primary key info)
3) Additionally, you can also load L_CONTEXT_LOCATION with info to tell you
where you are in your program, just in case the context_id info is not enough.
4) If there is an error, since these local variables are being passed to the
log_error procedure, this info will be automatically get loaded into the error
table.

create table error_table
  ( timestamp        date,
    whence           varchar2(1024),
    msg              varchar2(1024),
    code             number,
    context_location varchar2(1024),
    context_id       varchar2(1024)
  );

create or replace procedure log_error ( p_context_location varchar2,
p_context_id varchar2)
as
    pragma autonomous_transaction;

    l_whence varchar2(1024);
    l_msg    varchar2(1020) default sqlerrm;
    l_code   number default sqlcode;
begin
    l_whence := whence;
    insert into error_table
    ( timestamp, whence, msg, code, context_location, context_id )
    values
    ( sysdate, whence, l_msg, l_code, p_context_location, p_context_id );
    commit;
exception
    when others then
        rollback;
        raise;
end;
/

Then your code would look like this, for example

declare
....
begin
....
   L_CONTEXT_LOCATION := 'before main loop...';
   for r1 in c1 loop
      L_CONTEXT_ID := 'account_id:' || to_char(r1.account_id) || ':';
      update t
      set amount=r1.amount
      where account_id = r1.account_id;
   end loop
exception
   when others then
   log_error(L_CONTEXT_LOCATION, L_CONTEXT_ID);
   raise;
end;

What do you think?

Thanks,

Robert.

          Followup:

I would rather use

dbms_application_info

cause then I can "see" this data from another session in real time (eg: I can
use it to monitor the process from another session easily in real time).

I'm a big fan of that.  You can set 3 columns in v$session with this API and
your other code can easily retrieve this data using dbms_application_info.

Same sort of thing, just adds the bonus that "hey, I can watch what my procedure
is doing from afar".  Great for debugging!

      Rating: 3
How to take
      error stack trace into a variable.  January 22, 2003

Reviewer:  Chandra S.Reddy  from Hyderabad.India

declare
    err_msg varchar2(1024);
      begin
          insert into t values ( 1 );
exception
when dup_val_on_index then
-- modified log_error procedure, in order to return the whence
log_error(err_msg);

raise;
dbms_output.put_line('err_msg'||err_msg);
when others then
log_error(err_msg);
dbms_output.put_line('err_msg'||err_msg);
raise;
end;

My requirement is, Instead of inserting error stack messages into a table in
procedure log_error, I need to pass the Error Stack trace to Java application.
When I try to do so, the out string contains no values.

Please suggest me how can be done this.

          Followup:

er?  don't get it.

what "out string".

If java is to get the error stack - just remove the exception block all
together.  Java will get it.

      Rating:
      2
logging  January 22, 2003
Reviewer:
      Jack  from Columbus, OH USA
Tom,
Can you comment on Log4j for PL/SQL. (it looks like you can lot of options with
this).
How good is this (performance point of view and usability etc..).
thanks
Jack

          Followup:

not familar with log4j

      Rating: 3
How to get
      Errors from nested SP calls.  January 27, 2003

Reviewer:  Chandra S.Reddy  from India
Tom,
I have a requirement like below.
I have a main procedure say proc1. In this I'll call other procedures and
functions. At the end I need to send all the errors as a out parameter( say
err_desc out varchar2) occurred at the child procedures and functions along with
the procedure/function name. The main Procedure Proc1 will be called from java
application. At java end they will display the message as it is. Since, message
is having name of the SP/Func and type of error, the debugging can be done
easily.
Un fortunately, I should not go for any additional tables to resolve this issue.
Will this be solved with packaged variables.

My code looks like this.

create procedure Proc1(err_desc out varchar2)
begin

--

--- nested procedure calls.
proc2; -- is a child procedure.
select fun1('xx') into yy from dual; -- here is a function call.

---

end;
/

And, When I tried your example as it is, no entries were inserted for
error_table.whence filed.

          Followup:

what kind of code do you have that

a) a procedure can fail

b) the rest of the procedure "just doesn't care"

???

anyway -- sounds totally bizzare (and really bad) but -- I don't see any
exception blocks to call log_error.  did you try putting in a little debug code
to see why it isn't working for you?  works for me in all releases I use however
-- maybe the call stack format is a little different for you.... debug it, make
it work.

      Rating: 3
How to catch
      errors in nested SP calling.  January 27, 2003

Reviewer:  Chandra S.Reddy  from India
Hi Tom,
My actual code looks like this.
-- child program.
FUNCTION SP_Poc_UserRoles_FNC(in_nUserId IN NUMBER) RETURN VARCHAR2 IS

    CURSOR v_cvRoleDesc(UserId VARCHAR2) IS SELECT ROLE_ID FROM T_Poc_USER_ROLE
WHERE USER_ID = UserId ;
    v_Description    VARCHAR2(150);
    v_TempDesc    VARCHAR2(50);
BEGIN
    FOR INDX IN v_cvRoleDesc(in_nUserId)
    LOOP
        SELECT INITCAP(ROLE_NAME) INTO v_TempDesc FROM T_Poc_ROLE WHERE ROLE_ID
= INDX.ROLE_ID ;
        v_Description := v_TempDesc||', '||v_Description;
    END LOOP;
    v_Description := SUBSTR(v_Description,0,(LENGTH(v_Description)-2));
    RETURN v_Description;
    EXCEPTION
        WHEN OTHERS THEN
                RETURN NULL;

END ;
The above function is called in the SP below at line no 12.

-- main program.
PROCEDURE SP_Poc_SysAdmin_UserDetails(in_nRoleId IN NUMBER,in_nOperationCenterId
IN NUMBER,out_nStatusCode OUT NUMBER,out_nWipAccountNo OUT NUMBER,out_cvGenric
OUT PKG_Poc_CommDefi.Gencurtyp,out_cErrorDesc OUT VARCHAR2) IS
                   v_cUserRole    VARCHAR2(20) ;
                   v_cSqlString    VARCHAR2(1000) ;
                   v_nSysAdmin    NUMBER ;
                   v_nSuper        NUMBER ;
BEGIN -- mian blcok begins
        v_nSysAdmin := PKG_Poc_CommDefi.v_SysAdmin ;
        v_nSuper    := PKG_Poc_CommDefi.v_Super ;
        out_nStatusCode := 0 ;
          v_cSqlString := 'SELECT  US.USER_NAME,
            US.MODIFIED_BY ,
            SP_Poc_UserRoles_FNC(US.USER_ID) User_Roles, ------**** here is a
function call.
            TO_CHAR(US.MODIFY_DATE ,''MM/DD/YYYY-HH24:MI'') ,
            ROLE_NAME,
            US.USER_ID
        FROM    T_Poc_USER US,
            T_Poc_USER_ROLE USRROLE,
            T_Poc_ROLE ROLE
        WHERE    USRROLE.USER_ID = US.USER_ID AND
            USRROLE.ROLE_ID = ROLE.ROLE_ID AND
            USRROLE.ROLE_ID !=  :SUPER AND
            PRIMARY_ROLE_FLAG = ''Y'' AND
            ACTIVE_FLAG = ''Y'' ;

    -- Here v_nSysAdmin will be 2
      IF in_nRoleId = v_nSysAdmin THEN

        BEGIN

            OPEN out_cvGenric FOR v_cSqlString ||'AND US.OPERATION_CENTER_ID =
:OperationCenterId '
            USING v_nSuper,in_nOperationCenterId ;

        EXCEPTION WHEN OTHERS THEN
                          out_nStatusCode := SQLCODE;   -- Send fail status
back.
                out_cErrorDesc := 'Error occurred while retrieving User Details.
' || SQLERRM;
                   END ;
    -- Here v_nSysAdmin will be 1
      ELSIF in_nRoleId = v_nSuper THEN
        BEGIN
            OPEN out_cvGenric FOR v_cSqlString ||' AND ROLE.ROLE_ID = :UserRole
'
            USING v_nSuper, v_nSysAdmin ;

        EXCEPTION WHEN OTHERS THEN
                out_nStatusCode := SQLCODE;   -- Send fail status back.
                out_cErrorDesc := 'Error occurred while retrieving User Details.
' || SQLERRM;
                 END ;
        END IF;

EXCEPTION WHEN OTHERS THEN
             out_nStatusCode := SQLCODE;   -- Send fail status back.
        out_cErrorDesc := 'Error occurred while retrieving User Details. ' ||
SQLERRM;
END SP_Poc_SysAdmin_UserDetails;
/
show err;

I(we) the child function will never raise errors. Because it is simple select
statement.
But I still need to show the client, that, we are logging function name and
error message in case of nested SP calling cases.

          Followup:

I'll betcha you wrote lots of sqlserver code in a former/current life ;)

the one function should just be:

FUNCTION SP_Poc_UserRoles_FNC(in_nUserId IN NUMBER) RETURN VARCHAR2
IS
    v_Description VARCHAR2(4000);
BEGIN
    for x in ( select initcap( t_poc_role.role_name ) role_name
                 from t_poc_user_role, t_poc_role
                where t_poc_user_role.user_id = in_nUserid
                  and t_poc_role.role_id = t_poc_user_role.role_id )
    loop
        v_description := v_description || ', ' || x.role_name;
    end loop;
    return substr( v_description, 3 );
end;

Note -- if that flings ANY exception there has been a heinous, totally
unrecoverable, cannot fix sort of error.  So, we will not "hide" the error as
you are (when I see a when others that is not followed by a RAISE, 999 time out
of 1000 I am looking at a bug in the code)....

Anyway -- I guess you are sort of on your own here.  You are basically coding
--very explicitly I might add-- ignore all errors, ignore all errors, errors
are OK, ignore them

when others, not followed by a raise = bug in your code....

See -- there is no error stack, no whence, as you are canceling the error.  Just
remove the when others, let the error propagate to someone who can deal with
it, do something about it.  do not catch them and ignore them.

Especially in this code.  The first function you have cannot deal with any
errors -- there are NO errors I can even imagine that would be "recoverable"
from there.  If that routine flings an error -- let it propagate.

Same with the second routine.  If the open cursor FAILS, the client should just
get an ERROR back and use *traditional* error handling in order to recover.  As
it is -- the client calls the procedure, you hide the error, they have to
inspect a return code -- well, all of that can EASILY be done for you just by
letting the error propagate out in the first place! it is the natural way

In my opinion the only correct way to write that second procedure is:

PROCEDURE SP_Poc_SysAdmin_UserDetails
(in_nRoleId IN NUMBER,
 in_nOperationCenterId IN NUMBER,
 out_nWipAccountNo OUT NUMBER,
 out_cvGenric OUT PKG_Poc_CommDefi.Gencurtyp )
IS
BEGIN
  if ( in_nRoleId = PKG_Poc_Commdefi.v_SysAdmin )
  then
    open out_cvGeneric for
                   SELECT  US.USER_NAME, US.MODIFIED_BY ,
                           SP_Poc_UserRoles_FNC(US.USER_ID) User_Roles,
                           TO_CHAR(US.MODIFY_DATE ,'MM/DD/YYYY-HH24:MI') ,
                           ROLE_NAME,
                           US.USER_ID
                     FROM  T_Poc_USER US,
                           T_Poc_USER_ROLE USRROLE,
                           T_Poc_ROLE ROLE
                    WHERE  USRROLE.USER_ID = US.USER_ID AND
                           USRROLE.ROLE_ID = ROLE.ROLE_ID AND
                           USRROLE.ROLE_ID !=  PKG_Poc_CommDefi.v_Super AND
                           PRIMARY_ROLE_FLAG = 'Y' AND
                           ACTIVE_FLAG = 'Y' AND
                           US.OPERATION_CENTER_ID = in_nOperationCenterId;
  ELSIF in_nRoleId = v_nSuper THEN
    open out_cvGeneric for
                   SELECT  US.USER_NAME, US.MODIFIED_BY ,
                           SP_Poc_UserRoles_FNC(US.USER_ID) User_Roles,
                           TO_CHAR(US.MODIFY_DATE ,'MM/DD/YYYY-HH24:MI') ,
                           ROLE_NAME,
                           US.USER_ID
                     FROM  T_Poc_USER US,
                           T_Poc_USER_ROLE USRROLE,
                           T_Poc_ROLE ROLE
                    WHERE  USRROLE.USER_ID = US.USER_ID AND
                           USRROLE.ROLE_ID = ROLE.ROLE_ID AND
                           USRROLE.ROLE_ID !=  PKG_Poc_CommDefi.v_Super AND
                           PRIMARY_ROLE_FLAG = 'Y' AND
                           ACTIVE_FLAG = 'Y' AND
                           ROLE.ROLE_ID = PKG_Poc_Commdefi.v_SysAdmin;
  END IF;
END SP_Poc_SysAdmin_UserDetails;

Now, the ONLY errors that can occur are the totally heinous "we cannot recover"
type of errors since we are using static sql.   The ONLY exceptions that would
be raised here are ones that you would not be able to deal with AT ALL.

      Rating: 5
insert 0
      records  January 29, 2003
Reviewer:  mo

Tom:

If an insert did not work and it did 0 records would it raise any exception
handler in pl/sql.

I have three tables and each insert is dependent on the previous one. Instead of
creating a lot of IF SQL%rowcount > 0 after each insert I want to rollback if
first one fails.

I was thinking of assigning
var1:=sql%rowcount  after each insert and on the last statement do:

If (var1 > 0 and var2 > 0 And var3 > 0) then
  commit;
else
  rollback;
end if;

any suggestions?

          Followup:

the insert did work.  it sucessfully inserted 0 records.  there is nothing wrong
or erroneous with that.

if you believe it to be an error, you would inspect sql%rowcount and treat it as
such.

      Rating: 3
Error Trapping
      - Reg  May 01, 2003
Reviewer:  Andrew Fenton
       from UK
I found the article most useful.

When testing the "whence" function I found that the error stack may vary in
length, hence if the error stack did not contain 4 rows of data then a zero
length (null) string would be inserted in to the "whence" column of the
error_table.

I have rewritten the function to extract the last line of the call stack no
matter the size.

FUNCTION whence RETURN VARCHAR2
AS
        v_call_stack  VARCHAR2(4096) DEFAULT DBMS_UTILITY.FORMAT_CALL_STACK; --
call stack buffer
        v_len         PLS_INTEGER DEFAULT 0; -- length of the call stack
        v_pos         PLS_INTEGER DEFAULT 0; -- position of line feed character
        v_next_pos    PLS_INTEGER DEFAULT 0; -- next position of line feed
character
BEGIN
        -- get the length of the call stack buffer
        v_len := LENGTH(v_call_stack);

        LOOP
                -- find line feed
                v_next_pos := INSTR(v_call_stack, CHR(10), v_pos+1);
                -- find next line feed (look ahead - exit loop early as we
required second to last line feed char.)
                v_next_pos := INSTR(v_call_stack, CHR(10), v_next_pos+1);

                -- exit when no more line feed characters found or length of
buffer exceeded
                EXIT WHEN (v_next_pos IS NULL OR v_next_pos = 0 OR v_next_pos >=
v_len);

                -- set the current position to the last one found plus one
                v_pos := INSTR(v_call_stack, CHR(10), v_next_pos+1);
        END LOOP;

        -- return the last line in the call stack (+2 to allow for line feed
character)
        RETURN SUBSTR(v_call_stack, v_pos+2, v_len-(v_pos+2));
END;

      Rating: 3
Andrew
      Fenton  May 01, 2003
Reviewer:  Andrew
      Fenton  from UK
Please disregard my submission as the code does not work correctly.  I have gone
back to using Tom's code for the "whence" function.

      Rating: 3
WHEN
      OTHERS  July 14, 2003
Reviewer:  Fred
      Shimaya  from Irvine, CA
In page 473 of your book (almost half way done), you share one of your
experiences where only WHEN OTHERS was used in the exceptions block of a deeply
nested stored procedure.  You mentioned that it was used in such a way that it
"catches and ignores all exceptions" and that it "should never be used.."  I
didn't quite understand what you meant by "catches and ignores."  To me, that
seems contradictory.  I'm sure you don't mean only using WHEN OTHERS in the
exceptions block, as you did in your sample code above.  Could you elaborate?
Demonstrate what you mean please.

          Followup:

I meant the developer coded something along the lines of:

   .....
exception
   when others then
        maybe write out a log message or something
end;

there was NO raise right after, the invoking routines had no clue there was an
error, they continued on as if nothing happened.

A when others, that is not followed by RAISE, is probably a BUG in the code.

      Rating:
      5
Ahhh...  July 14, 2003
Reviewer:
      Fred  from Irvine, CA
Ok I see.  Makes sense now.  Thanks

      Rating: 5
java **JDBC
      Thin ** + oracle package output  July 24, 2003

Reviewer:  A reader
{
....
...

CallableStatement procnone = con.prepareCall (
"begin dbms_ouput.put_line('Hello Tom'); end;");

procnone.execute ();

....
}

as we can execute the java PL/SQL anonymous block
from java stored proc., where is the output
Hello Tom stored ?

if i m doing this on sql plus

SQL> begin dbms_output.put_line('Hello Tom'); end;
  2  /
Hello Tom

PL/SQL procedure successfully completed.

if I use the same anonymous block in
what java object/property will I receive

1.) Hello Tom
2.) PL/SQL procedure successfully completed.

outputs ? I need it.

Thank you very much...

          Followup:

Here is a java class for dbms_output support I have.  as for "PL/SQL procedure
successfull completed", well, it should be obvious that if you don't recieve any
errors -- it successfully completed!

import java.sql.*;

class DbmsOutput
{
/*
 * our instance variables. It is always best to
 * use callable or prepared statements and prepare (parse)
 * them once per program execution, rather then one per
 * execution in the program.  The cost of reparsing is
 * very high.  Also -- make sure to use BIND VARIABLES!
 *
 * we use three statments in this class. One to enable
 * dbms_output - equivalent to SET SERVEROUTPUT on in SQL*PLUS.
 * another to disable it -- like SET SERVEROUTPUT OFF.
 * the last is to "dump" or display the results from dbms_output
 * using system.out
 *
 */
private CallableStatement enable_stmt;
private CallableStatement disable_stmt;
private CallableStatement show_stmt;

/*
 * our constructor simply prepares the three
 * statements we plan on executing.
 *
 * the statement we prepare for SHOW is a block of
 * code to return a String of dbms_output output.  Normally,
 * you might bind to a PLSQL table type but the jdbc drivers
 * don't support PLSQL table types -- hence we get the output
 * and concatenate it into a string.  We will retrieve at least
 * one line of output -- so we may exceed your MAXBYTES parameter
 * below. If you set MAXBYTES to 10 and the first line is 100
 * bytes long, you will get the 100 bytes.  MAXBYTES will stop us
 * from getting yet another line but it will not chunk up a line.
 *
 */
public DbmsOutput( Connection conn ) throws SQLException
{
    enable_stmt  = conn.prepareCall( "begin dbms_output.enable(:1); end;" );
    disable_stmt = conn.prepareCall( "begin dbms_output.disable; end;" );

    show_stmt = conn.prepareCall(
          "declare " +
          "    l_line varchar2(255); " +
          "    l_done number; " +
          "    l_buffer long; " +
          "begin " +
          "  loop " +
          "    exit when length(l_buffer)+255 > :maxbytes OR l_done = 1; " +
          "    dbms_output.get_line( l_line, l_done ); " +
          "    l_buffer := l_buffer || l_line || chr(10); " +
          "  end loop; " +
          " :done := l_done; " +
          " :buffer := l_buffer; " +
          "end;" );
}

/*
 * enable simply sets your size and executes
 * the dbms_output.enable call
 *
 */
public void enable( int size ) throws SQLException
{
    enable_stmt.setInt( 1, size );
    enable_stmt.executeUpdate();
}

/*
 * disable only has to execute the dbms_output.disable call
 */
public void disable() throws SQLException
{
    disable_stmt.executeUpdate();
}

/*
 * show does most of the work.  It loops over
 * all of the dbms_output data, fetching it in this
 * case 32,000 bytes at a time (give or take 255 bytes).
 * It will print this output on stdout by default (just
 * reset what System.out is to change or redirect this
 * output).
 */

public void show() throws SQLException
{
int               done = 0;

    show_stmt.registerOutParameter( 2, java.sql.Types.INTEGER );
    show_stmt.registerOutParameter( 3, java.sql.Types.VARCHAR );

    for(;;)
    {
        show_stmt.setInt( 1, 32000 );
        show_stmt.executeUpdate();
        System.out.print( show_stmt.getString(3) );
        if ( (done = show_stmt.getInt(2)) == 1 ) break;
    }
}

/*
 * close closes the callable statements associated with
 * the DbmsOutput class. Call this if you allocate a DbmsOutput
 * statement on the stack and it is going to go out of scope --
 * just as you would with any callable statement, result set
 * and so on.
 */
public void close() throws SQLException
{
    enable_stmt.close();
    disable_stmt.close();
    show_stmt.close();
}
}

      Rating: 4
can you
      simplify this for me  July 25, 2003
Reviewer:
      A reader
Hi tom Thanks for your reply.

 Still can you please simplify this for me !!

I just need only one statment in that block

"begin " +
  " dbms_output.put_line('Hello Tom 1'); " +
  " dbms_output.put_line('Hello Tom 2'); " +
"end;" ;

I tried to do that and it gave me lots of error
I am a newbee in java so please just
tellme on "WHAT VARIABLE " will I get the output of
dbms_output statment

 do I have to bind that ?
also if you can show me that you ran that and output of that
it will be great ! ( I tried it since moring)

Thanks..

          Followup:

no variable -- dbms_output buffers the data internally -- it is availble to you
via the get_line/get_lines API calls in that package.

see the entire java package above, it does it

      Rating:
      5
dbms_output  July 25, 2003
Reviewer:
      A reader
so Oracle don't have dbms_output " put_line " implemantation
 for java/jdbc is that right ?

1.) plese show me how will you run that code
2.)if my user passes the pl/sql anynymos block/procedure with "
dbms_output.put_line " , do i " have to " parese it and change it to get_line ?
will you do that (if u konw, please give me solution.)
3.) let's say in the block there are more then one
   dbms_output.put_line staments how to retirve them after execution?
4.) "PL/SQL procedure successfully completed."
   " 39 rows selected." are those SQL*PLUS messages
   and  those are not returned by server ?
   ** so does sqlplus counts them and then displays ?
   **for counting doesn't it retrive "all" the rows first/*+All_rows */  or
looping or something ..?

/*************************************************/

import java.sql.*;

class DbmsOutput
{

private CallableStatement enable_stmt;
private CallableStatement disable_stmt;
private CallableStatement show_stmt;

public DbmsOutput( Connection conn ) throws SQLException
{
    enable_stmt  = conn.prepareCall( "begin dbms_output.enable(:1); end;" );
    disable_stmt = conn.prepareCall( "begin dbms_output.disable; end;" );

    String str = "declare msg varchar2(50); begin
dbms_output.get_line('******Hello******',msg); end;";

//    String str = "begin dbms_output.put_line('************Hello****'); end;";

/*
 String str ="declare " +
              "    l_line varchar2(255); " +
              "    l_done number; numx number := 0;" +
              "    l_buffer long; myvar number := null;" +
              "begin " +
              " dbms_output.put_line( to_char(:maxbytes) ); numx := numx + 1;
    " +
//              "    dbms_output.get_line(:maxbytes,myvar); " +
              "    dbms_output.get_line( l_line, l_done ); " +
              "    dbms_output.put_line( ': **** Hello 1*****' ); " +
              "    dbms_output.put_line( ': **** Hello 2*****' ); " +
              "    l_buffer := l_buffer || l_line || chr(10); " +
              " :done := l_done; " +
              " :buffer := l_buffer; " +
          "end;" ;
*/
    show_stmt = conn.prepareCall(str );

         System.out.println(str);

for(int xm=32000 ; ;xm++ )
{

         System.out.println("enabling output size :" + xm);
         enable(xm);

/*        System.out.println("executing stmt");
        show_stmt.execute ();
*/
        System.out.println("showing output");
         show();
/*
        System.out.println("disabling output");
         disable();
*/

}

}

public void enable( int size ) throws SQLException
{
    enable_stmt.setInt( 1, size );
    enable_stmt.executeUpdate();
}

public void disable() throws SQLException
{
    disable_stmt.executeUpdate();
}

public void show() throws SQLException
{
int done = 0;
    System.out.println("Trying to show output 1");

//    show_stmt.registerOutParameter( 2, java.sql.Types.INTEGER );
//    show_stmt.registerOutParameter( 3, java.sql.Types.VARCHAR );
    show_stmt.registerOutParameter( 1, java.sql.Types.VARCHAR );
    System.out.println("Trying to show output 2");

//        show_stmt.setInt( 1, 32000 );
        show_stmt.executeUpdate();
        System.out.println("Trying to show output 3");

        System.out.print(" Here is the output : " + show_stmt.getString(1) );

            System.out.println("\n **Trying to show output 4");

//        if ( (done = show_stmt.getInt(2)) == 1 ) ;
        System.out.println(" \n **Trying to show output 5");

}

public void close() throws SQLException
{
    enable_stmt.close();
    disable_stmt.close();
    show_stmt.close();
}
  public static void main( String args[] )
    throws SQLException
  {

    try{
        DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());

       System.out.println( "connection - " + "jdbc:oracle:thin:@"+
"myserver:1521" +":"+"myinstance" );
       Connection con = DriverManager.getConnection ("jdbc:oracle:thin:@"+
"myserver:1521" +":"+"myinstance","xyz","xki+=-ldkdi");

       new DbmsOutput(con);  // calling constructor

    }catch(Exception e){
            System.out.println( "Can't connect - " + e.getMessage());
        }

  }//end of main()

}
/*********************************************/

          Followup:

umm, given that dbms_output is PLSQL, no, not really.  java has
system.out.println as its equivalent.

1) you need to have a java program, then you can run this -- it should be pretty
"obvious".  You enable dbms_output.  you run something in the db that does
dbms_output.  you call the show routine

2) do you know how to call PLSQL from java??

that is all you need to do

3)  just call 'show' it prints it all out

4) they are pretty little messages sqlplus prints out.  the "success" is obvious
-- you don't get an error, you were successful.  there are attributes of the
statement objects in jdbc to give you row counts of affected rows and then you
can print them out.

      Rating: 5
Thanks,
      tom  August 19, 2003
Reviewer:
      A reader

today I understood what you were saying..
I got the output !!

thanks,

      Rating: 5
Why does show
      error doesnt work in the CASE II  September 11, 2003

Reviewer:  A reader
I.

SQL> CREATE OR REPLACE PACKAGE TEST5
  2  AS
  3  PROCEDURE ERROR_TEST;
  4  END;
  5  /

Package created.

  1  CREATE OR REPLACE PACKAGE BODY TEST5
  2  AS
  3   PROCEDURE ERROR_TEST
  4      IS
  5      ET_VAR NUMBER(1);
  6      BEGIN
  7      ET_VAR_VAR := 'ABCDEF';
  8      END;
  9* END;
 10  /

Warning: Package Body created with compilation errors.

SQL> SHO ERR
Errors for PACKAGE BODY TEST5:

LINE/COL ERROR
-------- -----------------------------------------------------------------
7/5      PLS-00201: identifier 'ET_VAR_VAR' must be declared
7/5      PL/SQL: Statement ignored
SQL>

*********************************************************************************
************************************************
II.

SQL> ed
Wrote file afiedt.buf

  1  /* Formatted by PL/Formatter v2.0.8.0 on 2000/02/16 20:30  (08:30 PM) */
  2  CREATE OR REPLACE PACKAGE pk_genutilitypkg
  3  AS
  4     /*
  5     Generic String Parser: provide a delimiter and it returns an
  6     index-by table of the individual elements of the string that are
  7     separated by the specified delimiter.
  8     Author: "GAUTHAM CHANDRASEKARAN" <gautha@hotmail.com>
  9     */
 10     TYPE t_string IS TABLE OF VARCHAR2(2000)
 11        INDEX BY BINARY_INTEGER;
 12     m_ctr NUMBER(5);
 13     m_pos NUMBER(5);
 14     PROCEDURE sp_parsestring (
 15        p_string IN VARCHAR2,
 16        delimiter IN VARCHAR2,
 17        p_t_string OUT t_string);
 18  END pk_genutilitypkg;
 19  /
 20  CREATE OR REPLACE PACKAGE BODY pk_genutilitypkg
 21  AS
 22     PROCEDURE sp_parsestring (
 23        p_string IN VARCHAR2,
 24        delimiter IN VARCHAR2,
 25        p_t_string OUT t_string)
 26     IS
 27        m_string VARCHAR2(4000);
 28     BEGIN
 29        /* Raise a Error if the length of the delimiter is not 1 */
 30        IF LENGTH (delimiter) != 1
 31        THEN
 32           raise_application_error (-20001,
 33               'Delimiter should be of only one character');
 34           RETURN;
 35        END IF;
 36        m_string := p_string;
 37        m_ctr := 1;
 38        LOOP
 39           m_pos := INSTR (m_string, delimiter);
 40           IF m_pos > 1
 41           THEN
 42              p_t_string (m_ctr) := SUBSTR (m_string, 1, m_pos - 1);
 43              IF (m_pos < LENGTH (m_string))
 44              THEN
 45                 m_string := SUBSTR (
 46                                m_string,
 47                                m_pos + 1,
 48                                LENGTH (m_string) - m_pos
 49                             );
 50              ELSIF m_pos = LENGTH (m_string)
 51              THEN
 52                 m_ctr := m_ctr + 1;
 53                 p_t_string (m_ctr) := NULL;
 54                 EXIT;
 55              END IF;
 56           ELSIF m_pos = 1
 57           THEN
 58              p_t_string (m_ctr) := NULL;
 59              IF m_pos < LENGTH (m_string)
 60              THEN
 61                 m_string := SUBSTR (
 62                                m_string,
 63                                m_pos + 1,
 64                                LENGTH (m_string) - m_pos
 65                             );
 66              ELSIF m_pos = LENGTH (m_string)
 67              THEN
 68                 m_ctr := m_ctr + 1;
 69                 p_t_string (m_ctr) := NULL;
 70                 EXIT;
 71              END IF;
 72           ELSIF m_pos = 0
 73           THEN
 74              p_t_string (m_ctr) := m_string;
 75              EXIT;
 76           END IF;
 77           m_ctr := m_ctr + 1;
 78        END LOOP;
 79     END;
 80* END pk_genutilitypkg;
 81  /

Warning: Package created with compilation errors.

SQL> sho err
Usage: SHOW ERRORS [{ FUNCTION | PROCEDURE | PACKAGE |
   PACKAGE BODY | TRIGGER | VIEW
   | TYPE | TYPE BODY
   | JAVA SOURCE | JAVA CLASS } [schema.]name]
SQL>

In the second case I have to query all_errors to find the details of the error,
where as in the case I it immedietly returns the errors on show error command.

Please explain.

          Followup:

that comment is getting in the way -- sqlplus isn't seeing that you are creating
a package named pk_genutilitypkg.

so, it is not able to fill in the blanks for you.

      Rating: 5
how come user
      errors show the errors  September 25, 2003

Reviewer:  A reader
SQL> ED
Wrote file afiedt.buf

  1  CREATE OR REPLACE PROCEDURE "P"
  2  AS
  3  X NUMBER;
  4  BEGIN
  5  X =123;
  6* END;
SQL> /

Warning: Procedure created with compilation errors.

SQL> SHO ERR
No errors.

SQL> select * from user_errors;
P                              PROCEDURE             1          5          4
PLS-00103: Encountered the symbol "=" when expecting one of the following:

   := . ( @ % ;
The symbol ":= was inserted before "=" to continue.

1 row selected.

SQL> CREATE OR REPLACE PROCEDURE P
  2   AS
  3   X NUMBER;
  4   BEGIN
  5   X =123;
  6   END;
  7  /

Warning: Procedure created with compilation errors.

SQL> SHO ERR
Errors for PROCEDURE P:
5/4      PLS-00103: Encountered the symbol "=" when expecting one of the
         following:
         := . ( @ % ;
         The symbol ":= was inserted before "=" to continue.

Why is it that, when we use double quotes around procedure name, though the
procedure has compilation errors, we cannot see them on show errors. This is in
sqlplus.

If we go by the answer you gave for the above posting, then how come user_erros
will contain the error information, in the first case.

          Followup:

the quoted identifer will throw it off

show errors procedure "P"

will work.

September 26, 2003 Umesh -- Thanks for the question regarding "
      Auditing", version 8.1.5.0

      You Asked
Ho Tom

platform Windows NT

I set
 audit_trial =DB in init.ora

startup the database

svrmgr >connect internal/<password>

svrmgr> audit user;

svrmgr > connect scott/tiger

svrmgr > create table emp123 ( n number);

svrmgr > connect sys/change_on_install

svrmgr > select * from aud$;

I see  no rows selected

How do I proceed

Thanks in advance
Umesh

      and we said...
Audit USER audits "CREATE USER", "ALTER USER", "DROP USER" commands.  You are
auditing the USER command.

You need to review the SQL Reference manual with a list of things in mind you
want to audit and use the appropriate audit command to enable it.

For example:

sys@TKYTE816> select * from aud$;

no rows selected

sys@TKYTE816> audit table;

Audit succeeded.

sys@TKYTE816> connect scott/tiger
Connected.
scott@TKYTE816> create table abc ( x int );

Table created.

scott@TKYTE816> connect sys/manager
Connected.

sys@TKYTE816> select count(*) from aud$;

  COUNT(*)
----------
         1

Review &
      Followup

      Rating:
      4
audting  March 24, 2001
Reviewer:  Umesh
      Kasturi  from Bangalore India
It has helped greatly for my review

      Rating: 4
audit
      select  July 24, 2002
Reviewer:  Charlie

Hi Tom,

How can I audit specific user selecting on one of my tables?

I tried:

AUDIT SELECT ON T1 BY access by user1;

which doesn't work. And I don't want to use
"AUDIT SELECT TABLE BY user1 BY ACCESS WHENEVER SUCCESSFUL".  Since it will
audit every SINGLE table, which is too much.

Thanks,

          Followup:

that doesn't work cause it isn't valid sql.

you can audit select on T1 by SESSION or by ACCESS -- not at the user level.
You either either selects on the table -- or not.

In 9i, there is Fine Grained Access Control that gives you this finer grained
control

      Rating: 5
Turn
      off  August 20, 2003
Reviewer:  Marcio
       from Br
Tom, I read this on manual:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96521/audit.htm#1633
Caution:
The NOAUDIT statement only specifies auditing options; it does not disable
auditing as a whole. To turn auditing off and stop Oracle from generating audit
records, set the initialization parameter AUDIT_TRAIL in the database's
initialization parameter file as described in "Enabling and Disabling Auditing".

Well, if I issued NOAUDIT any... what was being audit -- What do I do? I mean,
Oracle keeps generating records on database? I thought the audid was turned off.

thks, Marcio

          Followup:

they are just saying "you want auditing totally off -- just set the init.ora
parameter"  noaudit can be used to reduce (to zero if you desire) what is
audited but the audit code is still being executed (statements are looked at to
see "hmm, do I need to audit this -- no -- ok, continue on).

      Rating: 3
third party
      tool generating sql -want to understand  August 23, 2003

Reviewer:  A reader
Tom,

i wanted to post this query in a separate thread - but i am not geetting chance
for the past 1 week.. here it goes.

06:22:31.734 SQL  t@2200 select UPPER(value) from nls_database_parameters where
parameter=UPPER('NLS_CHARACTERSET')
06:22:31.750 SQL  t@2200 select * from mxSchema
06:22:31.750 SQL  t@2200 -->select
06:22:31.750 SQL  t@2200 -->select
06:22:31.750 SQL  t@2200 -->select
06:22:31.750 SQL  t@2200 select * from mxVer6 where mxOid = :va
06:22:31.765 SQL  t@2200    :va=1
06:22:31.765 SQL  t@2200 -->select
06:22:31.765 SQL  t@2200 select * from mxAdmin where mxType = :va
06:22:31.765 SQL  t@2200    :va=5
06:22:31.765 SQL  t@2200 -->select
06:22:31.781 SQL  t@2200 select * from mxVer6 where mxOid = :va
06:22:31.781 SQL  t@2200    :va=1
06:22:58.984 SQL  t@2200 -->select
06:22:58.984 SQL  t@2200 select * from V_Prod_Code@TSTEENWL
V_Prod_Code,V_Order_DocQuote4@TSTEENWL V_Order_DocQuote4 where
V_Order_DocQuote4.order_row_id = :va and (V_Prod_Code.sub_requisition_num =
V_Order_DocQuote4.sub_requisition_num)
06:22:58.984 SQL  t@2200     :va15L-6DQ
06:23:02.953 SQL  t@2200 --select
06:23:02.968 SQL  t@2200 select * from lxRO_4de5d474 where lxToLat = :va and
lxToId = :vb
06:23:02.968 SQL  t@2200    :va=1306907764
06:23:02.968 SQL  t@2200    :vb=117440512
06:23:02.984 SQL  t@2200 -->select
06:23:02.984 SQL  t@2200 select * from lxRO_4de5d474 where lxOid in (:va)
06:23:02.984 SQL  t@2200    :va=201326592
06:23:02.984 SQL  t@2200 -->select
06:23:03.078 SQL  t@2200 select * from lxBO_4de5d474 where lxOid = :va
06:23:03.093 SQL  t@2200    :va=33554432
06:23:03.093 SQL  t@2200 -->select
06:23:03.093 SQL  t@2200 select * from V_Order_DocQuote4@TSTEENWL where
order_row_id = :va
06:23:03.093 SQL  t@2200     :va15L-6DQ
06:23:04.140 SQL  t@2200 --select
06:23:04.156 SQL  t@2200 select * from lxBO_4de5d474 where lxOid = :va
06:23:04.156 SQL  t@2200    :va=16777216
06:23:04.156 SQL  t@2200 -->select
06:23:04.156 SQL  t@2200 select * from V_Prod_Code@TSTEENWL where r1 = :va
06:23:04.156 SQL  t@2200     :va20288-281-11407-T
06:23:06.937 SQL  t@2200 --select
06:23:06.937 SQL  t@2200 select mxOid,mxFlags,mxParent from mxBusType where
mxName = :va
06:23:06.937 SQL  t@2200    :va=LINKS Line Item
06:23:06.937 SQL  t@2200 -->select
06:23:06.953 SQL  t@2200 select * from mxPolicy where mxName = :va
06:23:06.953 SQL  t@2200    :va=LINKS Line Item
06:23:06.953 SQL  t@2200 -->select

the above lines are generated by one of the tool which we are using ...not sure
how it does the search..I understand the select staements and bind variables
(one of your favourite!!)  but dont know how they are holding the resultset of
each query and processing the whole process ...and getting the output.
this is just a search operation on an object.
Can you throw some light on this, so i can understand better

Thanks for your time and patience.

          Followup:

umm, not really.  all we are seeing is the sql they are executing.  their logic,
that is well hidden away from you.

if you are using a tool, what you really need to know is:

a) how to use the tool
b) that the tool performs well for you
c) that the tool gets the right answers

      Rating: 4
stop auditing
      for all client  August 27, 2003
Reviewer:
      Linden  from VA, US
Tom,

Short of change the init parameter for audit and bounce the database, how can I
stop auditing for any client? I can stop auditing on all other users except when
the user_name is any client. I tried NOAUDIT delete table by any client, of
course it returned no such user.

SQL> select * from sys.dba_stmt_audit_opts;

USER_NAME                      PROXY_NAME                     AUDIT_OPTION
                      SUCCESS
------------------------------ ------------------------------
--------------------------------------
ANY CLIENT                                                    SELECT TABLE
                      BY SESSION
ANY CLIENT                                                    INSERT TABLE
                      BY SESSION
ANY CLIENT                                                    DELETE TABLE
                      BY SESSION
ANY CLIENT                                                    EXECUTE PROCEDURE
                      BY SESSION
ANY CLIENT                                                    UPDATE TABLE
                      BY SESSION

Thanks.

          Followup:

what version and how did you enable auditing for 'ANY CLIENT'

      Rating: 4
My follow up
        August 28, 2003
Reviewer:  Linden
       from VA,US
The version is Oracle 8.1.7.4.1. To be honest, I don't remember how I enabled
that. Since sys.dba_stmt_audit_opts is just a view, I found those records were
actually in SYS.AUDIT$ table, so I went in to delete them. Now I kind of stopped
all auditing actions except it still generating auditing records for SYSTEM, I
guess that is built in.

My further question regarding AUDIT is that based on this audit trail if I can
built a monitoring procedure that keeps track of all users of their actions
(actually all select statements on one schema, since this is a warehouse, that's
the only thing they can do), capture those statements, get timing statistics and
get an EXPLAIN PLAN for those statements. The purpose of it would be 1) to track
the usage; and 2) to monitor performance. Is this practical or doable, if not,
what is the better approach?

I would appreciate greatly your thought on this.

          Followup:

you deleted from sys.audit$???

everything you want to do is done automagically for you in 9i.  In 8i, you will
not be able to do this effectively.

      Rating: 4
AUDIT &
      FGAC  September 26, 2003
Reviewer:  A reader
       from Brasil
HI, TOM!

We have a production application which uses more than 400 tables. We are willing
to have a better access control and allow auditing at the user level in a near
future. Tell me, taking into account this number of tables, should we use Fine
Grained Access Control? Or would it be better to develop another application to
control access to other applications (transformed into modules)?

Thanks again.
Best regards.

          Followup:

FGAC is not about auditing...

FGAC is about access control and whether it is 1 table, 400 tables, 40,000
tables whatever -- it is an appropriate technology to consider in order to
implement access control.

DBMS_FGA (fine grained auditing) might be useful to look at for auditing

      Rating:
      4
  September 30, 2003
Reviewer:
      A reader  from washington dc USA

September 26, 2003 Bijay -- Thanks for the question regarding
      "Update the Records in Table2 from Table1 if there is a change in any
      column Table1 and Insert the Records from Table1 toTable2 if it not there
      in Table2.", version 9.0.2

      You Asked
Hi I am having 2 questions at a time as both of them are related to one problem
and it is very hard to find u's availavility also.

Q1.I have Table T1 with
PFCNTR                                             NUMBER(6)
BRAND                                              VARCHAR2(2)
CUSTOMER_NUMBER                                    NUMBER(7)
CITY                                               VARCHAR2(24)
ZIP1                                               VARCHAR2(6)
ZIP2                                               VARCHAR2(4)
...............
...............
...............

Another Table T2
SHOP_SK                                   NOT NULL NUMBER(6)
PFCNTR                                             NUMBER(6)
BRAND                                              VARCHAR2(2)
CUSTOMER_NUMBER                                    NUMBER(7)
CITY                                               VARCHAR2(24)
ZIP1                                               VARCHAR2(6)
ZIP2                                               VARCHAR2(4)
...............
...............
...............
Staus                                              char(1)
=========
No constraints in Tables T1, But the Records are unique on composite column i.e.
(PFCNTR,BRAND)
In Table T2 SHOP_SK is the Primary key/Surogate key.

T2 is the target table and the T1 is the Source Table.
Initial status of T2 is ='N'; means not updated
SHOP_SK is the Surigate key which is sequence.NEXTVAL inserted for 1st time load
into T2 along with other columns from T1

My requirement is on daily basis load the Table T2 from T1 with following
conditions
=================================================================================
=========
Next Load  onwards I would like to update the record in Table T2 if there is
change in any column of the T1 along with Staus in T2 with 'Y' meas this record
is Updated.

and  Insert the new records from T1 into T2 if it the record is not there in the
T2 with status as 'N' means record is new and not updated.

Not necessary to do anything on those records, if there is no change  in any of
the existing records in T1 which is there is T2

I would appreciate you's answers.
Thanks
--Bijay

      and we said...
Merge will do that.

ops$tkyte@ORA920> create table t1
  2  ( pfcntr number,
  3    brand varchar2(2),
  4    customer_number number,
  5    city varchar2(24),
  6    zip1 number );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t1
  2  select rownum, substr(object_name,1,2), object_id,
substr(object_name,1,24), object_id
  3    from all_objects
  4   where rownum <= 10;

10 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create sequence shop_sk_seq;

Sequence created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t2
  2  ( pfcntr number,
  3    shop_sk number,
  4    brand varchar2(2),
  5    customer_number number,
  6    city varchar2(24),
  7    zip1 number, status varchar2(1) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t2;

no rows selected

ops$tkyte@ORA920>
ops$tkyte@ORA920> merge into t2
  2  using ( select pfcntr, brand, customer_number, city, zip1 from t1
  3          MINUS
  4          select pfcntr, brand, customer_number, city, zip1 from t2 ) t1
  5     on ( t2.pfcntr = t1.pfcntr )
  6   when matched
  7   then update
  8    set t2.brand = t1.brand,
  9        t2.customer_number = t1.customer_number,
 10            t2.city = t1.city, t2.zip1 = t1.zip1, t2.status = 'Y'
 11   when not matched
 12   then insert ( shop_sk, pfcntr, brand, customer_number,
 13                 city, zip1, status )
 14            values ( shop_sk_seq.nextval, t1.pfcntr, t1.brand,
 15                     t1.customer_number, t1.city, t1.zip1, 'N' );

10 rows merged.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t2;

    PFCNTR    SHOP_SK BR CUSTOMER_NUMBER CITY                           ZIP1 S
---------- ---------- -- --------------- ------------------------ ---------- -
         5          1 /1           22346 /1048734f_DefaultFolder       22346 N
         8          2 /1           22798 /106ba0a5_ArrayEnumerati      22798 N
         3          3 /1           22543 /10297c91_SAXAttrList         22543 N
         1          4 /1           17287 /1005bd30_LnkdConstant        17287 N
         2          5 /1            7559 /10076b23_OraCustomDatum       7559 N
        10          6 /1           11261 /10744837_ObjectStreamCl      11261 N
         6          7 /1           10173 /10501902_BasicFileChoos      10173 N
         7          8 /1           22748 /105072e7_HttpSessionBin      22748 N
         4          9 /1           13615 /103a2e73_DefaultEditorK      13615 N
         9         10 /1            9849 /106faabc_BasicTreeUIKey       9849 N

10 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> update t1 set brand = 'xx' where rownum <= 5;

5 rows updated.

ops$tkyte@ORA920> insert into t1
  2  select rownum+1000, substr(object_name,1,2), object_id,
substr(object_name,1,24), object_id
  3    from all_objects
  4   where rownum <= 5;

5 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> merge into t2
  2  using ( select pfcntr, brand, customer_number, city, zip1 from t1
  3          MINUS
  4          select pfcntr, brand, customer_number, city, zip1 from t2 ) t1
  5     on ( t2.pfcntr = t1.pfcntr )
  6   when matched
  7   then update
  8    set t2.brand = t1.brand,
  9        t2.customer_number = t1.customer_number,
 10            t2.city = t1.city, t2.zip1 = t1.zip1, t2.status = 'Y'
 11   when not matched
 12   then insert ( shop_sk, pfcntr, brand, customer_number,
 13                 city, zip1, status )
 14            values ( shop_sk_seq.nextval, t1.pfcntr, t1.brand,
 15                     t1.customer_number, t1.city, t1.zip1, 'N' );

10 rows merged.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t2;

    PFCNTR    SHOP_SK BR CUSTOMER_NUMBER CITY                           ZIP1 S
---------- ---------- -- --------------- ------------------------ ---------- -
         5          1 xx           22346 /1048734f_DefaultFolder       22346 Y
         8          2 /1           22798 /106ba0a5_ArrayEnumerati      22798 N
         3          3 xx           22543 /10297c91_SAXAttrList         22543 Y
         1          4 xx           17287 /1005bd30_LnkdConstant        17287 Y
         2          5 xx            7559 /10076b23_OraCustomDatum       7559 Y
        10          6 /1           11261 /10744837_ObjectStreamCl      11261 N
         6          7 /1           10173 /10501902_BasicFileChoos      10173 N
         7          8 /1           22748 /105072e7_HttpSessionBin      22748 N
         4          9 xx           13615 /103a2e73_DefaultEditorK      13615 Y
         9         10 /1            9849 /106faabc_BasicTreeUIKey       9849 N
      1001         16 /1           17287 /1005bd30_LnkdConstant        17287 N
      1005         17 /1           22346 /1048734f_DefaultFolder       22346 N
      1002         18 /1            7559 /10076b23_OraCustomDatum       7559 N
      1004         19 /1           13615 /103a2e73_DefaultEditorK      13615 N
      1003         20 /1           22543 /10297c91_SAXAttrList         22543 N

15 rows selected.

Review &
      Followup

      Rating:
      5
Crikey!  March 06, 2003
Reviewer:  Steve
      Irwin
Crikey, I didn't know you could do that
That merge is bewdy..
Steve

      Rating: 5
Title
      tooooooooooooooooooooooooooo long  March 06, 2003

Reviewer:  David
Bijay, couldn't you think of a longer title ? ;)

Tom, you could limit, truncate or wrap the title so we wouldn't have to scroll
through a 200+ character wide screen.

          Followup:

got, chomped at 70 characters on the main screen.

      Rating: 5
I have a very
      similar requirement but on 8i  March 06, 2003

Reviewer:  umesh  from bangalore India
I have used cursor for the same . can that be done with SQL stmts. PLease help
Thanks for your patience

          Followup:

well a

MERGE INTO t1
USING T2

in 8i would be:

  for x in ( select * from t2 )
  loop
      update t1 set ....
      if (sql%rowcount = 0)
      then
          insert into t1 ....
      end if;
  end loop

OR

  delete from t1 where (primary key) in (select primary key from t2);
  insert into t2 select * from t2;

OR

  update t1 set (columns) = (select columns from t2 where t2.pk = t1.pk )
   where exists ( select columns from t2 where t2.pk = t1.pk )
  insert into t1
  select * from t2 where t2.pk not in ( select pk from t1 );

amongst other approaches.

      Rating:
      4
  March 07, 2003
Reviewer:  Scott
       from Chicago, IL, USA
You meant:

  delete from t1 where (primary key) in (select primary key from t2);
  insert into t1 select * from t2;

of course...

Scott

          Followup:

of course ;)

just wanted to make sure people actually read the stuff.... (not, it was a typo)

      Rating: 5
Oh, they read
      it...  March 07, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Oh yeah, they read it and take it all literally - how many times have you had
someone say "I ran ur code and I get error on aria-dev.  Can u fix it?  Is this
a bug in Oracle?"  Or some such statement...

- Mark

      Rating:
      4
ok  March 07, 2003
Reviewer:  bijay
       from usa
Hi Tom
I am testing with small set of columns it works okay
but if there are larger no of columns and some columns are null then while using
the merge statment it updates all existing records to 'Y'

I have all the stmts as file how can i send it to u.

Please do let me know.
Thanks
Bijay

          Followup:

don't send me anything.

you are mistaken -- the number of columns affects nothing.  I tested my example
with NULLS, it does the same thing as when they are "not null".

Tell you what -- take my example, make it reproduce your issue.  If it happens
with 100 columns, you can make it happen with 5.

Small test case, like mine, to prove your point.

NULL or NOT NULL -- won't matter, that is why i used MINUS actually cause MINUS
is very nice about nullality.

      Rating: 4
Cool
      Demo  March 07, 2003
Reviewer:  vj
       from CA,USA
This is wonderful!!! I have a whole bunch of scripts that could use some retouch
based on the above demo.

Thanks Tom!!!

      Rating:
      4
FYI  March 10, 2003
Reviewer:  Scott
       from Perth, Western Australia
G'day

For those who are a little bewildered by the first follow up by Steve Irwin, and
don't realise - he is a rare, mutated form of Australian also known as "The
Crocodile Hunter"
Until now, I wasn't aware he was also an Oracle programmer.

Crikey!

      Rating: 4
New Changes
        April 29, 2003
Reviewer:  bijay

Hi Tom
I have a change request it was initially  like the table t2 is from table t1
with merge insert..update.
Now what I want is
When there will be a chnge in any column in t1 the same record has to be
recorded in T2 and the current flag of existing row in T2 has to be 'Y'.
I mean we would like to have a history of t1 when it changes and all in t2 with
latest records whose current flag='N'
I would appreciate your help.

          Followup:

new question....

      Rating: 5
snapshot based
      tables  September 23, 2003
Reviewer:
      Sandeep  from Germany
8.1.7.4
Suppose there is a snapshot based table EMP..
and we create another table OUR_EMP and populate OUR_EMP every morning as you
suggested (update existing records and insert new ones.
EMP snapshot has EMPLID as unique column...(which is a primary key in the parent
database for the snapshot table)
and OUR_EMP also EMPLID column..which we have as a primary key..and pass it to
other tables as a foreign key.

Now this everyday load will update existing/insert new
but does not do any thing to the orphan EMPLIDS that can arise in OUR_EMP
table...and these orphans might have childeren in our database..so we can not
quickly update them in our database during the morning load
One solution is to do a monthly cleanup process or weekly or every day for
OUR_EMP table...

Is there another way to work around this issue?

          Followup:

not enough facts here.  i'm confused as to the existence of emp and our_emp on
what appear to be the same machine?

how can you let someone delete the parent of child records like that?

(this is a new question tho, bigger then a bread box)

      Rating:
      5
Snapshot  September 23, 2003
Reviewer:
      Sandeep  from Germany
EMP is a snapshot based on a table in Database A
OUR_EMP is a table in Database B..and it is updated/inserted from EMP every
morning....confusing as it sounds..we do have this scenerio in our existing
database (Database B)...and I am finding lot of orphan Emplids in our
database....since there is no connection between two databases..except for the
EMP snapshot....

Thanks

          Followup:

so, what i'm not understanding here is, why do you have OUR_EMP

EMP is already there in B right?  what is the purpose of OUR_EMP

      Rating:
      5
OUR_EMP  September 24, 2003
Reviewer:
      Sandeep  from Germany
OUR_EMP is there in database B so that we can attach some other information,
columns to it...those columns will be maintained by Database B...
Also...A snapshot table can not have a primary key on it..that can be passed as
foreign key...so OUR_EMP can have EMPLID as a primary key..that can be passed to
other tables as a foreign key...

          Followup:

a snapshot table can have a primary key on it -- make it a deferrable
constraint.  anyway...

our_emp should just have emplid and the extra columns

"real_emp" should be a join between emp and our_emp

then -- the deleted rows just appear to "disappear".

use EMP as the parent, our_emp as supplemental data.

      Rating:
      5
Real_emp  September 26, 2003
Reviewer:
      Sandeep  from Germany
"a snapshot table can have a primary key on it -- make it a deferrable
constraint.  anyway..."

/*Good..then we can pass this as foreign key to our_emp correct?*/

"our_emp should just have emplid and the extra columns"
/*Yes it does*/

""real_emp" should be a join between emp and our_emp"
/*Are you saying whenever we query..join these two?*/

then -- the deleted rows just appear to "disappear".

use EMP as the parent, our_emp as supplemental data

          Followup:

yes...  make the fkeys deferrable as well.

just join, yes.

September 26, 2003 Zoltán -- Thanks for the question regarding "How
      to send hungarian special characters in an email from a database? ",
      version 8.1.7.4.0

      You Asked
I installs several codes in a database but none of them supports national
language characters however the database supports.
For example:éáíóuúo

      and we said...
by default, smtp is 7bit ascii.

See

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96612/u_smtp.htm#1000697
it describes this fact and offers a solution -- using RAW.  you would
utl_raw.cast_to_raw in your hungarian varchar2 variable and use write_raw_data.
Review &
      Followup

      Rating: 4
OTN UTL_SMTP
      Code Examples  September 26, 2003
Reviewer:
      Robert Pang  from Redwood Shores, CA USA
Also check out the UTL_SMTP code examples on OTN. There is one on how to send
multibyte emails.

http://otn.oracle.com/sample_code/tech/pl_sql/htdocs/Utl_Smtp_Sample.html

September 26, 2003
      Carlos -- Thanks for the question regarding
      "Generate sequence numbers inside a select", version 9i

      You Asked
This question is really about a SQL query and not Forms.

¿Is it possible to generate a sequence while making a query?

Example:

select sale_date, generated_sequence
where  :condition_a
and    date = :user_give_date
order  by sales_date;

I am displaying the data of this query inside a data_block
in a form. The field sequence (or position) is not a column
of the table. For my requirement is not useful to add a column
with sequences to the table.

The data displayed in the data_block has the same sequence or
order than the resulting query, so I need some kind of counter
inside the select so this counter stops when the condition
is satisfied. This counter will give me the number of record
to display in the data block. The user wants to show a record
for a given date.

Right now I am doing this in a Procedure, where I declare a cursor based in the
query. And with the help of aditional variables
inside a loop, I get the number of record  I need to show. But
this is slow.

I think if it is possible to build a query with that counter, I
will get the position and show that record directly in the data block
without aditional loops or procedures which make the
process very slow. I can not filter the data inside the data_block because the
user wants to see what is happening with the data before and after the select
record, so I must always show the whole query,
but the current record pointing to the selected by the user.

Sorry for my long explanation.

THANK YOU FOR YOUR HARD WORK FOR EVERYONE.

Carlos

      and we said...
select a.*, rownum seq
  from ( select .... where ... order by ... ) a
/

does that, for example:

  1  select a.*, rownum seq
  2  from ( select ename, sal from emp where job = 'CLERK'
  3* order by ename ) a
scott@ORA920> /

ENAME             SAL        SEQ
---------- ---------- ----------
ADAMS            1100          1
JAMES             950          2
MILLER           1300          3
SMITH             800          4

Review &
      Followup

      Rating: 3
Thank
      you  September 26, 2003
Reviewer:  Carlos
      Gongora  from Cochabamba, Bolivia
Thank you for your input.

      Rating: 4
Nick
      Trick!!  September 26, 2003
Reviewer:
      A reader

      Rating: 5
generate seq
      numbers by dept  September 27, 2003
Reviewer:
      A reader
Tom
In a query where we are displaying deptno and ename,
if I wanted to display sequence numbers by deptno, how can we do it.

For example

deptno  ename   seq

10      a       1
10      b       2
20      c       1
20      d       2

Thanks

      Rating: 3
answer to:
      "generate seq numbers by dept"  September 28, 2003

Reviewer:  Freek  from Belgium
You can use analytic functions for this.

select deptno, empno, ename, row_number() over (partition by deptno order by
empno) ordening
from emp
order by deptno, empno;

this will give you the offset of a row in an ordered group.
(here the group is based on the deptno, while the rows within the group are
ordered by empno)

greetings

Freek

September 26, 2003 Jeremy -- Thanks for the question regarding
      "Invalid Objects", version 8.0.6

      You Asked
I've detected a few invalid objects in one of our databases.  How do I make them
'VALID'?

      and we said...
They will fix themselves as they are executed or accessed.  I never worry about
some invalid objects -- you'll almost always have some somewhere.  You could
create a full time job fixing them -- or just let the database do it.

In any case:

exec dbms_utility.compile_schema( 'SCOTT' )

would compile all of the invalid objects owned by SCOTT (note: must use UPPER
case on the schema name).

If some objects are still invalid -- that means there is some error with them --
use the "show errors " command in sqlplus to diagnose.

Review &
      Followup

      Rating: 3
Thanks
      Tom  December 10, 2001
Reviewer:  Jeremy
      Nieuwoudt  from Melbourne, Australia
The reason I would like to get rid of invalid objects is I'd like to find any
problems before the end-users do.

      Rating: 2
Unable to
      understand the behaviour  April 15, 2002
Reviewer:
      murali  from Hyderabad - India
Sir,

On 8.1.7,first, I connected as the user accounts with invalid objects and gave
the command,
SQL> select object_type,count(*) from user_objects where status = 'INVALID'
  2  group by object_type;
OBJECT_TYPE          COUNT(*)
------------------ ----------
PACKAGE BODY                4
PROCEDURE                  61
VIEW                        3

next, as sys
exec dbms_utility.compile_schema( 'ACCOUNTS' );
PL/SQL procedure successfully completed.

then,
connect accounts
SQL> select object_type,count(*) from user_objects where status = 'INVALID'
group by object_type;

OBJECT_TYPE          COUNT(*)
------------------ ----------
PACKAGE BODY                1
PROCEDURE                 182
VIEW                        3

Without changing any code, i manually compiled the invalid objects using alter
procedure proc_name compile;

SQL> select object_type,count(*) from user_objects where status = 'INVALID'
  2  group by object_type;

OBJECT_TYPE          COUNT(*)
------------------ ----------
PACKAGE BODY                1
PROCEDURE                  49
VIEW                        1

Could you please explain this behaviour.

thanks and regards

          Followup:

Yes, because when you compile procedure X, and there is a procedure Y that calls
procedure X, procedure Y will go invalid again -- even if it was valid before.
Y needs to be compiled again.

select * from user_errors to see if in fact there are ERRORS.  If not, no
problems.

In my book if you have it -- there is a procedure that compiles ALL code in a
schema cleanly (if possible) under all circumstances.

      Rating: 3
Compiling
      Invalid Objects  April 16, 2002
Reviewer:
      Vishwas Kulkarni  from Singapore
Hi ALL

You can also use $ORACLE_HOME/rdbms/admin/utlrp.sql

which will compile all invalid objects thru SVRMGRL.

      Rating: 4
Invalid
      objects  October 04, 2002
Reviewer:  Leigh
      Gold  from BC, Canada
Tom,

I have noticed that using Powerbuilder application to access an invalid object
does not automatically recompile the object, it gave a ora-20003 error instead.
After I compiled it manually, the application ran fine.

Why is this so?

          Followup:

cause Sybase doesn't know how to program against Oracle?

Seriously, it is because they are trying to describe an invalid object, when
they encounter the 20003, they should simply PARSE:

  begin OBJECT; end;

regardless of what object is -- that will auto-compile it (a describe will not).
 They could then describe it successfully.

You could file an enhancement request with them to have them do it.  Its really
quite simple.

      Rating: 4
exception
      during recompile  February 21, 2003
Reviewer:
      June  from MD
Tom,

I tried to understand what's happening in here:

I use one dynamic script to recompile all invalid pkg/fn/procedure under one dba
account, and interesting thing is, one of procedure in schema1 turned to invalid
after same name of procedure in schema2 didn't recompiled successfully due to
less privilege on common tables in schema3. (procedure in schema1 will be valid
if recompiled individually). after I granted privilege from schema3, both
procedures under schema1/2 recompiled successfully. However these two schemas
didn't call each other, other than they access to same table in schema3.

have any idea why is that? It really shouldn't. Is it a good exercise to
recompile invalid objects under dba accounts for all schema?

thanks in advance for any input.

          Followup:

I don't bother compiling objects, no -- they recompile themselves when invoked.

But if you give me a test case with create users, etc etc etc that shows the
issue you hit, I'll explain why.

What you have above is insufficient to reproduce (it doesn't work the way you
describe).... there is something missing in the details there.

      Rating: 3
Valid status in
      Oracle 8.0.5 on NT  June 12, 2003
Reviewer:
      A reader  from arash_zar@hotmail.com
Hi Tom,

  The issue that I have is, there is function F1 which is using table T1,T2,T3.
After I modified the structure of table T1, the function F1 should become
invalid. But it doesn't ! and it shows 'valid' status for that. But when I want
to use it in application which is PB 8 , it fails and I have to recompile it
manually, then it works.

Here are my questions:
1- Why oracle shows Valid status for an invalid object?

2- I didn't understand your comments about PB
"begin OBJECT; end;

regardless of what object is -- that will auto-compile it (a describe will not).

 They could then describe it successfully.
"

What is describe ? What do you mean by "begin object end" ?

Thank you in advance for your valuable time.

Regards,
Arash

          Followup:

you would need to supply a testcase that backs that up:

ops$tkyte@ORA806> create table t1 ( x int );

Table created.

ops$tkyte@ORA806> create table t2 ( y int );

Table created.

ops$tkyte@ORA806> create table t3 ( z int );

Table created.

ops$tkyte@ORA806>
ops$tkyte@ORA806> create or replace procedure p
  2  as
  3  begin
  4          for x in ( select * from t1, t2, t3 )
  5          loop
  6                  null;
  7          end loop;
  8  end;
  9  /

Procedure created.

ops$tkyte@ORA806>
ops$tkyte@ORA806> @invalid
ops$tkyte@ORA806> break on object_type skip 1
ops$tkyte@ORA806> column status format a10
ops$tkyte@ORA806> select object_type, object_name, status
  2  from user_objects
  3  where status = 'INVALID'
  4  order by object_type, object_name
  5  /

no rows selected

ops$tkyte@ORA806> alter table t1 add a int;

Table altered.

ops$tkyte@ORA806> @invalid
ops$tkyte@ORA806> break on object_type skip 1
ops$tkyte@ORA806> column status format a10
ops$tkyte@ORA806> select object_type, object_name, status
  2  from user_objects
  3  where status = 'INVALID'
  4  order by object_type, object_name
  5  /

OBJECT_TYPE     OBJECT_NAME                    STATUS
--------------- ------------------------------ ----------
PROCEDURE       P                              INVALID

i cannot reproduce

and by "begin OBJECT; end;" what I mean is -- all PB needs to do is parse a
string 'begin p; end;' since P is the "object" in question here.  the block I
generally use it:

begin if (1=0) then p; end if; end;

that'll cause p to compile (if it can) but most certainly would never actually
execute it by accident.

      Rating: 5
Similar problem
      with Microsoft OLEDB driver for Oracle...  June 12, 2003

Reviewer:  Kamal Kishore  from New Jersey, USA

Hi Tom,
When a package goes invalid because of dependencies, some of our applications
that call this package procedures (and connect to the Oracle database using
Microsoft OLEDB Driver for Oracle) start to report errors that "can not execute;
package state is invalid" ....

When we go to SQL*Plus and compile manually the package, everything than is
fine. The application runs correctly.

Is this again a limitation of the OLEDB driver (as said before for the
PowerBuilder app) that it fails to identify this and take corrective measures?

Is there something we could do different (apart from asking Microsoft to update
its OLEDB driver !!!), which could circumvent this problem?

I keep telling everyone that there is no need to manually compile the invalid
objects as Oracle automatically takes care of this, but when we see that package
calls are failing through OLEDB driver, I'm asked always, "Now, why does Oracle
not automatically compile this? Why must I manually compile the package before
it starts to work again?"

Is it safe to point fingers at the OLEDB driver and put it as a limitation (or
would you call it a bug?) of the driver implementation?
Thanks,

          Followup:

they are trying to describe the object in order to discover its inputs and
outputs.

you cannot describe an invalidate object.

you must either:

o compile it
o cause it to be compiled -- parse -- begin if (1=0) then p; end if; end; and
  it'll compile itself

so, I guess a solution would be to catch the error in PB, dynamically parse that
block and retry the operation.

      Rating: 3
PB working with
      Oracle  June 12, 2003
Reviewer:
      A reader
Tom,

 So it's the PB issue and I have to recompile it manually ! Am I right?

Thanks again,
Arash

      Rating: 4
I have a
      similar problem - Package body compiled successfully But status is
      Invalid  September 26, 2003
Reviewer:  Balaji
      CN  from India Chennai
Hi
I have a strange problem - I was trying to create a Package .
The package header and body compiled successfully But status is Invalid for the
body alone.
I dont know how this is possible.
SQL> @D:\BalajiCN\CCR\Dev\ppdadmin.ph

Package created.

SQL> show err
No errors.
SQL> @D:\BalajiCN\CCR\Dev\ppdadmin.pb

Package body created.

SQL> show err
No errors.
SQL> select distinct name  from user_errors;

NAME
------------------------------
COMMENTS

SQL>  SELECT OBJECT_NAME , OBJECT_TYPE , STATUS FROM USER_OBJECTS WHERE
OBJECT_NAME = 'PPDADMIN';

OBJECT_NAME
---------------------------------------------------------------------------------
-------------------
OBJECT_TYPE        STATUS
------------------ -------
PPDADMIN
PACKAGE            VALID

PPDADMIN
PACKAGE BODY       INVALID

i DONT KNOW HOW THIS HAPPENS??
can you please help me??

I am calling procedure in an another database via a public db link and accessing
the proc thro a public synonym .
and the grants everything are proper..

i am also able to execute the proc.

but second time it said , unable to find the pl sql unit being callled..

any help welcome.

regards
balaji

          Followup:

I have no idea what package was created in those scripts.

hows about looking at LAST_DDL_TIME to see when those guys where last modified
as well.

September 26, 2003 Pedro -- Thanks for the question regarding
      "Oracle connect restrictions", version 8.1.6

      You Asked
Is there a way to grant access to the Oracle database ONLY
through certain applications (example: Forms Runtime), and
restrict access from other applications (examples: sqlplus,
odbc)?

      and we said...
No.  It is impossible.  It is upto the application to tell us
"who" they are.

Lets say you could.  You said "myapp.exe" is allowed in.  Ok,
I'll just copy sqlplus.exe to myapp.exe.

Unfortunately, there is no such thing as a "signed" application
-- we have no method to verify an application is who they say
they are.

for this reason, I like to put the bulk of my application (well
actually all of it) in the database.  In that fashion, you can
access my data only through my application and I don't really
care if you run my application from sqlplus or the gui I built
for you.

You might consider a password protected role -- but see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1445204371337
to read about some of the issues before going down that
path.
Review
      & Followup

      Rating:
      4
clear  June 15, 2003
Reviewer:
      Peter  from Spain

      Rating: 3
But what about
      this  September 26, 2003
Reviewer:  sanjay
       from NJ, USA
Tom,
I have a related question. In our organization the Development ,Test and
Production servers are on the network. User has access to databases on all the
three servers. So users can log into one db(say development) and sqlplus in the
production DB. Is there anyway we can restrict this. We want that to access the
Production/Test/Development DB (which are on three different Unix boxes) the
user should either Telent into the unix box and then connect to the DB on that
box only or when using a client be able to connect to a single DB and not be
able to connect to development DB and then from there SQLPLUS into the
PRoduction DB.
IS there a way to do it.
Is it possible to restrict certain IP addresses to connect to a DB?

Thanks

          Followup:

in 8i, see the protocol.ora file definition.

in 9i, see

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/network.920/a96581/sqlnet.htm#496732
the sqlnet.ora file

September 26, 2003 Dilip -- Thanks for the question regarding
      "Update SQL Performance", version 8.1.7

      You Asked
Hello Tom,

I have an update statement on a table t1.

update t1
set (amount, enter_date) =
   (select amount, trunc(enter_date)
    from t2
    where t2.id = t1.id
    and t2.code in (select code from t3 where t3.sign = 1)
    and t2.enter_date = (select max(enter_date) from t2
                         where t2.id = t1.id
                         and t2.code in (select code from t3
                                       where t3.sign = 1)));

Table t1 has around 100K rows and t2 has around 2 million rows.
I am trying to get latest transaction amount and date information for all id's
in t1 from t2 (transaction table). t3 table (transaction code) dictates what
transactions should be included.

This particular update statement is taking quite long time.  What would be your
recommendation to improve the performance of this particular update statement.

Thanks,
Dilip

      and we said...
Well, that update -- given proper indexes and using the CBO -- should run
"fairly fast".

On my desktop PC, it took under 2 minutes -- is that too long?

Any, I set up a simulation:

create table t1 ( id int, amount int, enter_date date );

insert into t1
select rownum, object_id, sysdate
  from big_table.big_table
 where rownum <= 100000;

alter table t1 add constraint t1_pk primary key(id);

analyze table t1 compute statistics
for table
for all indexes
for all indexed columns;

create table t2 ( id int, amount int, enter_date date, code int );

begin
    for i in 1 .. 20
    loop
        insert /*+ APPEND */ into t2
        select id, amount-i, enter_date-i, mod(i,2)
          from t1;
        commit;
    end loop;
end;
/
create index t2_idx on t2(id,enter_date);

analyze table t2 compute statistics
for table
for all indexes
for all indexed columns;

create table t3 ( code int, sign int primary key);
insert into t3
select rownum, rownum
  from all_users;

analyze table t3 compute statistics
for table
for all indexes
for all indexed columns;

So, I too have 100,000 rows, 2,000,000 rows and a code lookup.  I created this
view:

create or replace view v
as
select rid, amount, trunc(enter_date) enter_date
  from ( select t1.rowid rid, t2.amount, t2.enter_date,
                max(t2.enter_date) over (partition by t2.id) max_enter_date
           from t1, (select amount, enter_date, id
                       from t2
                      where code in (select code from t3 where sign = 1)) t2
          where t1.id = t2.id (+))
 where (enter_date = max_enter_date)
    or (enter_date is null and max_enter_date is null)
/

And tried it procedurally:

declare
    type ridArray is table of rowid index by binary_integer;
    type numArray is table of number index by binary_integer;
    type datArray is table of date index by binary_integer;
    l_rid ridArray;
    l_amt numArray;
    l_ed  datArray;
    l_array_size number := 100;
    l_cnt        number := 0;
    cursor c is select rid, amount, enter_date from v;
begin

    open c;
    loop
        fetch c bulk collect
         into l_rid, l_amt, l_ed
        limit l_array_size;

        forall i in 1 .. l_rid.count
            update t1 set amount = l_amt(i), enter_date = l_ed(i)
             where rowid = l_rid(i);

        l_cnt := l_cnt + l_rid.count;
        dbms_application_info.set_client_info( 'updated ' || l_cnt || ' rows' );

        exit when c%notfound;
    end loop;
    close c;
end;
/

Then I tried it via the update:

update t1
set (amount, enter_date) =
   (select amount, trunc(enter_date)
    from t2
    where t2.id = t1.id
    and t2.code in (select code from t3 where t3.sign = 1)
    and t2.enter_date = (select max(enter_date) from t2
                         where t2.id = t1.id
                         and t2.code in (select code from t3
                                       where t3.sign = 1)));

Now, the PLSQL block took

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.03          0          0          1           0
Execute      1     71.28      80.10      15610     106393     101914           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2     71.30      80.14      15610     106393     101915           1

about 80 seconds, the update:

update t1
set (amount, enter_date) =
   (select amount, trunc(enter_date)
    from t2
    where t2.id = t1.id
    and t2.code in (select code from t3 where t3.sign = 1)
    and t2.enter_date = (select max(enter_date) from t2
                         where t2.id = t1.id
                         and t2.code in (select code from t3
                                       where t3.sign = 1)))

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      1     62.90     108.38      13665    2728542     204504      100000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2     62.90     108.39      13665    2728542     204504      100000

a little more....

So, you can try the procedural access, but if you have the right indexes,
updating 100,000 rows shouldn't be too arduous.

Review &
      Followup

      Rating: 3
Why SQL slower
      than PL/SQL  June 03, 2003
Reviewer:  Jeff
       from Sydney
Tom, what are the features of this example which make it an exception to your
rule of thumb: a single statement will be faster and consume less resources than
doing it procedurally?

Thanks.

          Followup:

because the nature of the update is such that it has lots of correlated
subqueries in order to find that "max enter_date" thing.  That makes it very
hard to optimize using anything other then nested loops (the correlated subquery
does that).

What I'd really like to do would be:

UPDATE(
select *
  from ( select t1.amount t1_amt, t1.enter_date t1_ed,
                t2.amount t2_amt, t2.enter_date t2_ed,
                max(t2.enter_date) over (partition by t2.id) max_enter_date
           from t1, (select amount, enter_date, id
                       from t2
                      where code in (select code from t3 where sign = 1)) t2
          where t1.id = t2.id (+))
 where (enter_date = max_enter_date)
    or (enter_date is null and max_enter_date is null)
)
set t1_amt = t2_amt, t1_ed = t2_ed;

But -- because of the lack of key preservation and the analytics (which imply
"order") -- we cannot.  I want this query to select the rows cause it does nice,
big, bulk operations (faster for many rows) rather then 100,000 nested loops
type of operations.

So, in order to "update this query", I need to unfortunately (in 8i) use a query
+ update.  Doing so in bulk...

In 9i, we've got MERGE to the rescue:

merge into t1
using (
select rid, amount, trunc(enter_date) enter_date
  from ( select t1.rowid rid, t2.amount, t2.enter_date,
                max(t2.enter_date) over (partition by t2.id) max_enter_date
           from t1, (select amount, enter_date, id
                       from t2
                      where code in (select code from t3 where sign = 1)) t2
          where t1.id = t2.id (+))
 where (enter_date = max_enter_date)
    or (enter_date is null and max_enter_date is null)
) t2
on ( t1.rowid = t2.rid )
when matched then
    update set t1.amount = t2.amount, t1.enter_date = t2.enter_date
when not matched then
    insert (id) values ( null )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      2     43.03      53.45      16486       7558     102809      100000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3     43.05      53.46      16486       7558     102809      100000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 137

Rows     Row Source Operation
-------  ---------------------------------------------------
      2  MERGE  (cr=7558 r=16486 w=10807 time=53444549 us)
 100000   VIEW  (cr=7531 r=16477 w=10807 time=49308344 us)
 100000    HASH JOIN OUTER (cr=7531 r=16477 w=10807 time=48646484 us)
 100000     VIEW  (cr=7187 r=16139 w=10807 time=46776991 us)
1000000      WINDOW SORT (cr=7187 r=16139 w=10807 time=45026596 us)
1000000       HASH JOIN OUTER (cr=7187 r=5331 w=0 time=27551523 us)
 100000        INDEX FAST FULL SCAN OBJ#(37458) (cr=215 r=210 w=0 time=235332
us)(object id 37458)
1000000        VIEW  (cr=6972 r=5121 w=0 time=18959603 us)
1000000         HASH JOIN  (cr=6972 r=5121 w=0 time=15955318 us)
      1          TABLE ACCESS BY INDEX ROWID OBJ#(37461) (cr=2 r=0 w=0 time=97
us)
      1           INDEX UNIQUE SCAN OBJ#(37462) (cr=1 r=0 w=0 time=58 us)(object
id 37462)
2000000          TABLE ACCESS FULL OBJ#(37459) (cr=6970 r=5121 w=0 time=4275638
us)
 100000     TABLE ACCESS FULL OBJ#(37457) (cr=344 r=338 w=0 time=376606 us)

It does the same thing my PLSQL code did, just a little more efficiently.

      Rating:
      3
Sorry  June 03, 2003
Reviewer:  Jeff
       from Sydney
Should've read more thoroughly.  I thought you created the view using the exact
select used as the inline view in the update. Now I understand you rewrote the
select, but in such a way that it couldn't be used in the update. Thanks.

      Rating: 5
Similar
      problem, would appreciate help  June 03, 2003

Reviewer:  Jon Waterhouse  from Newfoundland, Canada

I have been trying to understand why an update query I have is taking multiple
_hours_ to run when if I separate it into two components it runs in seconds. It
may have to do with the self-join -- I'm not sure.

My problem was that I originally had a file that ignored the possibility of same
sex twins. It has an CLIID on file number, sex and date of birth. In a file that
has client names I just wanted to mark possible duplicates (some of whom will be
twins, some who will just have a name spelt wrong), within each CLIID. File is
about 150,000 records with about 10,000 possible duplicate records.

I wrote the following statement:

update testchild tcx set pd = (select "rank" from
(select cliid,id,fname, rank() over (partition by cliid order by fname) "rank"
from testchild tc2 where exists
  (select * from testchild tc3 where tc3.cliid=tc2.cliid and cliid > 1000000000
group by cliid having count(*) > 1)) x
where x.id = tcx.id)

Plan Table
---------------------------------------------------------------------------------
---------
| Operation                 |  Name              |  Rows | Bytes|  Cost  |
Pstart| Pstop |
---------------------------------------------------------------------------------
---------
| UPDATE STATEMENT          |                    |   152K|  745K|    284 |
|       |
|  UPDATE                   |TESTCHILD           |       |      |        |
|       |
|   TABLE ACCESS FULL       |TESTCHILD           |   152K|  745K|    284 |
|       |
|   VIEW                    |                    |     7K|  193K|    319 |
|       |
|    WINDOW SORT            |                    |     7K|  149K|    319 |
|       |
|     FILTER                |                    |       |      |        |
|       |
|      TABLE ACCESS FULL    |TESTCHILD           |     7K|  149K|    284 |
|       |
|      FILTER               |                    |       |      |        |
|       |
|       SORT GROUP BY NOSORT|                    |     1 |    9 |      1 |
|       |

Plan Table
---------------------------------------------------------------------------------
---------
|        FILTER             |                    |       |      |        |
|       |
|         INDEX RANGE SCAN  |TC_CLIID            |     1 |    9 |      1 |
|       |
---------------------------------------------------------------------------------
---------
(I hope that plan comes out OK).

The query itself takes about 6 seconds to run. If I do a CTAS and then update
the testchild table from the "temporary" table it takes less than a minute. An
index on ID for the temporary table doesn't seem to make much difference.
Running the full update written here; well it started about 11am yesterday
morning and was still running at 9 this morning. Consistent gets were in the
billions. This is 9i running on a PC.

Any idea what is going on?

Many thanks.

          Followup:

same exact thing here.

That 6 second query is run 152k times (stats above indicate 152k rows in the
table to update)

The same techniques as above make it so you can do that one 6 second query IN
BULK (not row by row by row by row by row) and update with it.

      Rating: 5
Works
      perfectly; CBO reports horrendous statistics  June 03, 2003

Reviewer:  Jon Waterhouse  from Newfoundland, Canada

merge into testchild tcx using (select tc2.rowid rid,cliid,id,fname, rank() over
(partition by cliid order by fname) "rank"
  from testchild tc2 where exists
  (select * from testchild tc3 where tc3.cliid=tc2.cliid and cliid > 1000000000
group by cliid having count(*) > 1)) x
on (tcx.id=x.id)
when matched then
  update set pd=x."rank"
when not matched then
  insert(tcx.id,tcx.cliid,tcx.pd) values(x.id,x.cliid,x."rank")

was my recoded statement (though there shouldn't ever be any inserts). Ran in
just a few seconds. CBO reported Cost = 2169227; Card=1166483017. I guess I
still don't really understand why the original update statement results in the
same query result set being generated once for every row being changed (or is it
just the tiny result set for the one cliid?) but it sure is nice to know how to
avoid it.

      Rating: 5
Excellent
      solution  June 03, 2003
Reviewer:  Dilip
       from PA USA
Thank you Tom.

I tried procedure solution provided by you and it is taking around 60 seconds
now.  Analytical functions seems to be amazing (glanced over the chapter in your
book but will read in detail now).

I had one problem though.  Instead of creating view, I tried to create my cursor
as

CURSOR last_payment_cur IS
select rid, amount, trunc(enter_date) enter_date
  from ( select t1.rowid rid, t2.amount, t2.enter_date,
                max(t2.enter_date) over (partition by t2.id) max_enter_date
           from t1, (select amount, enter_date, id
                       from t2
                      where code in (select code from t3 where sign = 1)) t2
          where t1.id = t2.id (+))
 where (enter_date = max_enter_date)
    or (enter_date is null and max_enter_date is null);

This fails with following message:

LINE/COL ERROR
51/32    PLS-00103: Encountered the symbol "(" when expecting one of the
following: , from

But if I put same query in SQL window, it works fine.  I would appreciate any
input why cursor declaration is failing.

          Followup:

I did that on purpose (the view) in reaction to "817"

see the chapter on analytics. page 586

      Rating: 4
inline views
      seem to be IT but ...  September 25, 2003

Reviewer:  bobspa  from PHILs.
Tom,
  i've been repeatedly reading your solutions here but can find a worthwhile
approach with my performance problem on update. hope you can help me on this...
    I'm trying to optimize a package which runs for more than an hour of exec
CPU-time (based on tkprof output).
     ENVIRONMENT
      -- 8.1.6 data warehouse on solaris  with 6 cpu's ,archiving
      -- CURR_TXN  table is truncated at the start of the process ; gets
populated (about 2.7M rows) by querying a range-partitioned table and other
lookups; no indices ; insert /*+ APPEND */ took 17 CPU-minutes
      -- ACCOUNTS_HIST contains about 30M rows, non-partitioned, has a
non-unique composite index on (type_cd,id_number,<2 other columns>),
un-analyzed,tkprof shows an index-range scan; type_cd is char(4), id_number is
number(28); put an index hint to use another non-uniq composite index on
(id_number,<another column>,type_cd,<another column>) hoping that the range scan
would be lessened, but LIOs worsened
      -- BUS_ADDR has about 500K rows, unique index on acct_no

     The update statements below takes about 18 CPU minutes to finish.

UPDATE curr_txn t1
   SET    acct_no =
       (SELECT MIN(t2.acct_no)
        FROM   accounts_hist t2
        WHERE  t2.id_number      = t1.idnum
        AND    t2.item_code      = :item_id  -- <== sqlplus variable
        AND    t2.type_cd   = t1.type_cd
        )
    ;
commit;
 UPDATE curr_txn t1
    SET    origin_cd =
          (SELECT postal_code
           FROM   bus_addr t4
           WHERE  t4.acct_no     = t1.acct_no
          )
    WHERE   acct_no IS NOT NULL;

To get rid of the double pass (FTS) on table curr_txn, i'm considering on
combining the 2 UPDATES to:

update curr_txn t1
 set (acct_no, origin_code) =
    (SELECT MIN(t2.acct_no) min_acct_no, t4.postal_code
          /* do a single-row cartesian join */
        FROM   accounts_hist t2,  bus_addr t4
        WHERE  t2.id_number      = t1.idnum
        AND    t2.item_code      = :item_id
        AND    t2.type_cd        = t1.type_cd
        and    t4.acct_no        = t1.acct_no
     )
where exists  (SELECT /*+ FIRST_ROWS */ acct_no
        FROM   accounts_hist t2
        WHERE  t2.id_number      = t1.idnum
        AND    t2.item_code      = :item_id
        AND    t2.type_cd        = t1.type_cd
        )

Qs:
1. Will the new approach run faster? I can't benchmark it as i don't have access
to the system right now :).
1.1. will revising the line "where exists  (SELECT acct_no" to
     "where exists  (SELECT min(acct_no)" execute the same query twice? can the
hint PUSH_SUBQ be of any help here?
2. will a global temporary table (based on accounts_hist) then defining a
composite index of (id_number,item_code,type_cd) be just a waste of storage?
3. altering table CURR_TXN to nologging mode and plus an APPEND hint reduced the
CPU time by almost 3 minutes for the 2 updates
4. Can you suggest a better approach to speed-up the updates OR is this a
dead-end?

thanks

          Followup:

you will need to benchmark and I'll add a 3rd case to benchmark

create a global temporary table with a primary key on id_number, type_cd,
acct_no as

SELECT MIN(t2.acct_no) min_acct_no, t4.postal_code
          /* do a single-row cartesian join */
        FROM   accounts_hist t2,  bus_addr t4
        WHERE  t2.id_number      = t1.idnum
        AND    t2.item_code      = :item_id
        AND    t2.type_cd        = t1.type_cd
        and    t4.acct_no        = t1.acct_no
and 1=0

then, insert /*+ append */ into this gtt the results of

select min(......
 from ...
where t2.item_code = :item_id
group by idnum, type_cd, acct_no

and then update the join of t1 with this gtt

      Rating: 5
Great
      asusual  September 26, 2003
Reviewer:  B rao
       from Austin,TX
Tom has a great approach towards the problems.Excellent

September 26, 2003 jahanzeb -- Thanks for the question regarding
      "Export problem", version oracle 7.3

      You Asked
Hi,

I am having a problem regarding export.
I want to export  sets of tables repeatedly, but following error
is occuring.

ORA-00604 error occurred at recursive SQL level num

Actually I have to export various tables in the form of  sets.
Each set contains  42 selected tables.When I export any set the
above error occurs.
But, when I export in sets of 6  tables  , no error occurs.

More over, this error only occurs only when I perform export on
a server called 'PCV'.And when i perform the same action on
server called 'BACKUP',no error occur in any case. Actually PCV
and BACKUP ,both are copies of each other,with slight
differences in their init.ora file.The parameters which differ
with each other are,

PCV --parameters

1.db_files=20
2.db_block_buffers=200
3.shared_pool_size=6500000
4.open_cursors=100
5.log_buffer=8192

BACKUP ---parameters

1.db_files=35
2.db_block_buffers=3200
3.shared_pool_size=12000000
4.open_cursors=300
5.log_buffer=32768

There  may be problem with the parameter difference.

Please help me in this regard.

Thanks.

Jahazneb

      and we said...
Well, that is not the entire message stack.  the ora-604 is:

00604, 00000, "error occurred at recursive SQL level %s"
 An error occurred while processing a recursive SQL statement
 (a statement applying to internal dictionary tables).

  If the situation described in the next error on the stack
  can be corrected, do so; otherwise contact Oracle Support.

There is a "next error on the stack" that describes the real
failure.  I'm going to guess here based on the init.ora
parameters above -- it is ORA-01000 "maximum open cursors
exceeded" exceeded.

100 is way too small.  the smallest I use is 500 but I prefer
1000 myself in all cases.  Adjust and retry the operation after
changing and bouncing the instance (shutdown+startup).

If you are worried that setting too high will "waste" stuff, see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:288216032261
It'll only allocate them as it needs them (and if it needs
them -- it isn't wasting anything, it needs them)

Review &
      Followup

      Rating: 4
still don't
      understand why export gets ORA-01000  September 25, 2003

Reviewer:  Beth  from Tahiti (well in Tahiti at least
      in my mind)
Hi Tom,
I have been receiving the same ORA-00604, ORA-01000 stack, as in this question,
during a nightly export.

I have been reading all about open cursors on your site and still have a few
questions.

1)  Why exactly does the export get max open cursors exceeded?  Does the exp
utility open one cursor per table?  I can't tell if there is a correlation,
because I don't know how far the export gets before it fails.  We have
open_cursors = 220, and there are 454 tables in the entire database.  (This is a
full export that it is failing on.)

2)  I understand you stress to set open_cursors to at least 500, preferably
1000, because there is no harm done in allocating an array of 1000 pointers (not
yet eating up memory) and it solves the bigger pain-in-the-butt problem of
having to repeatedly bounce the database to fix this error, if you only
increment it by a small amount each time, right?

a) You also said, if there is a memory leak in some code, you will find it then
(how? I am not exactly sure, but if it can suck up the bulk of 1000 cursors I
guess you can find out what in v$open_cursor and find out who in v$sesstat...)
Regardless, if this bad code got implemented in a prod db, wouldn't it then
start to allocate memory for those 1000 array pointers, and depending on the amt
of memory available for Oracle on your server, couldn't that be a bad thing, to
let it run wild?

b)  or do you just have to keep in mind the 'OS limit for open cursors'?   Is
there one?  This was a phrase from some TAR. If it exists, how do you know what
that would be?  The Ref manual says 4294967295 is the max, but that is number of
cursors...would I need to find out the size of each cursor and compare to RAM on
the box?

3)  Lastly, or perhaps repeating myself, if there is a threshold of max cursors
being set too high....how do you proactively figure it out?

Thanks as always!

          Followup:

what version of the database/exp are we talking about here.

      Rating:
      4
version  September 26, 2003
Reviewer:
      Beth
8.1.7.0.0

      Rating:
      4
version  September 26, 2003
Reviewer:
      Beth
817 for both the database *and* the exp utility

          Followup:

contact support and reference:

Hdr: 2194182 8.1.7.2 RDBMS 8.1.7.2 SPACE PRODID-5 PORTID-59 ORA-604
Abstract: ORA-604 FOLLOWED BY ORA-1000 DURING SELECTS ON THE DD VIEWS

patched in 8173 and 8174

      Rating:
      5
Thanks!  September 26, 2003
Reviewer:
      Beth
Wow, thanks!  Sorry to bother you with a bug.  I inherited the unpatched db, and
know i need to fix it, but didn't think this was a bug!

But anyway, for my understanding...what about 2a, b?

          Followup:

2a) you would find that in TEST would you not?  if you say no, then prod is
error prone anyway...

2b) it is a function of RAM available

September 26, 2003 shelly -- Thanks for the question regarding
      "Sybase to Oracle", version 8.1.6

      You Asked
Hello,Tom
      My company plan to migrate from "Sybase SQL Anywhere 5.0" to "Oracle
8.16". Do you know any tools we can do this? Where can
I download for testing purpose?

Thanks always.
Shelly

      and we said...
You'll want to get familar with

http://otn.oracle.com/
The oracle technology network.

There you will find

o software to download
o documentation to read
o migration toolkits to help you migration from Sybase to Oracle
Review &
      Followup

      Rating: 3
A more specific
      link would be helpful  May 30, 2002
Reviewer:
      Daryl  from Carrollton, Texas
Try
http://otn.oracle.com/tech/migration/toolkits/content.html
The original poster is going from Sybase SQL Anywhere 5.0, which isn't quite the
same thing as "big" Sybase (System 10/11; Adaptive Server Enterprise). The link
I list above indicates there's a toolkit for migrating from Watcom SQL to
Oracle. Watcom SQL is the product that became SQL Anywhere, so the 4.x stuff is
probably helpful.

      Rating: 3
Sybase vs.
      Oracle, your opinion please  August 18, 2003

Reviewer:  juancarlosreyesp@yahoo.com  from Bolivia

Hi Tom, could you please give your opinion about
this points I recently read in a searchdatabase newsletter.
:) Thank you.

"
Following my call last week for feedback about the merits and future of the
Sybase DBMS, dozens of you wrote in, effusive with your praise of the product.
Here is a compilation of your thoughts. In a nutshell:

A high-performance data warehouse can be built with Sybase for a tenth of the
cost of anybody else.
Sybase is the fastest DBMS: ASE 12.5.0.3 recently scored the best benchmarks for
a Linux based RDBMS, ever.
Sybase is much easier to learn, install and administer than Oracle.
Sybase is very efficient in terms of utilizing operating system resources and so
requires less hardware.
Sybase beats Oracle in terms of Total Cost of Ownership.
Sybase is very reliable: multiple people said that they've been running Sybase
for years without any downtime.
Selling SQL Server to Microsoft was a big mistake, although Sybase ASE is today
a far better product than MS SQL Server.
Sybase is not only not in decline, it's growing: 600+ new ASE customers have
been won over in the last year.
More great comments here
The only negative that fans could muster is that despite having brilliant
engineers and technical people, Sybase has the "lousiest marketing and sales
teams among the leading RDBMS vendors
"

          Followup:

do they really still make a database, wow, it has been so long since I've run
into them.  really -- MS, IBM -- all over the place.  Sybase?  Not even in NY.
Not for new stuff anyway.

o A high-performance data warehouse can be built with Sybase for a tenth of the
cost of anybody else.

Huh?  how so? Where is the proof points?  when I look at the tpc-h results -- I
surely don't see them as 1/10 the cost???  for example...

o what benchmark would that be?

o b-s, excuse my french.  what is easy is what you know.  If you know sybase it
is easier then making toast too but so what?

o less resources?  really.  how?

Sybase lost the database wars years ago.  Religion reigns supreme in the
database world.  The fanantics will never go away.

Please don't forget what "church" I go to either -- take it all with a grain of
salt.

      Rating: 5
sql anywhere
      5.0  August 18, 2003
Reviewer:  a dba
       from USA
I routinely have to convert Oracle and MSS to Sybase SQL Anywhere 5.0 for the
purposes of a demonstration unit.
I have found the Sybase SQL Anywhere 5 engine ( i'm not talking about ASE 11x +
) to be so antiquated and difficult to work with, i usually wait until the stars
are aligned and full moon to start my conversion.
Converting LONGs and BLOBs are my favorite part. I usually go through 3 or 4
monitors and several keyboards before the job is done.  I can buy bandages in
bulk at Costco before the conversion process.
Going from Sql Anywhere 5.0 to Oracle 8i ( not sure why not go right to 9.2 ) is
like going from moped ( embarrassed to ride - only do it at night ) to Mercedes.
Of course, just my opinion. Your results may vary.

      Rating: 5
Thanks
      :)  August 18, 2003
Reviewer:  A reader

      Rating: 4
Recent
      Benchmark from serachdatabase  September 26, 2003

Reviewer:  Kan  from USA
A recent searchdatabase.com article polled customers on their opinions of Sybase
and its products. Here are some of the responses as summarized on
searchdatabase.com.

A high-performance data warehouse can be built with Sybase for a tenth of the
cost of anybody else.
Sybase is the fastest DBMS: ASE 12.5.0.3 recently scored the best benchmarks for
a Linux based RDBMS, ever.
Sybase is much easier to learn, install and administer than Oracle.
Sybase is very efficient in terms of utilizing operating system resources and so
requires less hardware.
Sybase beats Oracle in terms of Total Cost of Ownership.
Sybase is very reliable: multiple people said that they've been running Sybase
for years without any downtime.

          Followup:

and a poll of people who like product X will return -- Hey, we like product X.

so, show me the PROOF POINTS (just hit page up and see what I already wrote
about this in the past)

September 26, 2003 Rico -- Thanks for the question regarding
      "internal error when using autonomous transactions", version 8.1.6

      You Asked
Hi,

i have a problem using autonomous transactions and dynamic SQL.
I have two procedures, p1 makes some database changes inside a
loop at the local database and on an other database via dblink.
At start of loop every time it calls a procedure p2 wich is
created with PRAGMA AUTONOMOUS_TRANSACTION. This procedure p2
updates (an commit after update) simple a local table with the
loop counter, so that i can see progress of p1.

Now the internal error ORA-00600 [18186] occurs at the commit
call in p2 after p1 has made a dynamic sql call (no dynamic sql
-> no error).

what can i do? (samplecode is available if you need)

Rico

      and we said...
Sorry but it is an undocumented feature that autonomous
transactions do not function with database links.  They will in
8.2 I believe but they do not now and it is not documented.

This is documenation issue #692232

Thats the bad news.

The good news is there is a much better way to do what you want
in any case.  You should simply call:

    dbms_application_info.set_client_info
    ( 'some piece of interesting data' );

or

   dbms_application_info.set_module
    ( 'some piece of interesting data',
      'some more interesting data'  );

Then you can query the client_info, module and action columns in
v$session to watch your programs progress.  These apis
dynamically update these columns in this virtual table -- very
handy...

Review &
      Followup

      Rating: 3
Similar
      error  September 26, 2003
Reviewer:  Sudha
      Bhagavatula  from Buffalo,NY
I'm trying to run this procedure and get the error 0ra-00600 [PITL1]

CREATE OR REPLACE PROCEDURE PRC_UHC_UPD_RX_TIER_PLAN_HIST(start_mnth IN NUMBER,
                                                          end_mnth   IN NUMBER)
IS

/* ************************************************************************ */
/* NAME:        PRC_UPD_RX_TIER_PLAN_HIST.prc                               */
/* LOCATION:    V:\BHAGAVAT_S\FCT_CLAIMS                                    */
/* DESCRIPTION: Procedure to populate tier plan dim id of the               */
/*              T_PHARMACY_CLAIM_FACT table                                 */
/*                                                                          */
/* ********************************************************                 */
/* Parameters in: start_mnth PAID MONTH                                     */
/*                end_mnth   PAID MONTH                                     */
/* ********************************************************                 */
/*                                                                          */
/* REVISION HISTORY:                                                        */
/* -----------------                                                        */
/* Date                       User                       Action             */
/* -----------                -------------------        ------------       */
/* 09/16/2003                 Sudha Bhagavatula          Created            */
/*                                                                          */
/* ************************************************************************ */

   v_tier_code       DW.T_UHC_EMP_GRP_TIER_JUNCTION.TIER_CODE%TYPE;
   v_subr_spouse_cnt NUMBER;
   v_child_cnt       UNIV.T_CONTRACT_DIMENSION.CHILD_CNT%TYPE;
   v_tier_dim_id     UNIV.T_TIER_PLAN_DIMENSION.TIER_PLAN_DIM_ID%TYPE;
   v_start_mnth      NUMBER;

BEGIN

  DBMS_APPLICATION_INFO.SET_MODULE('STARTING PRC_UHC_RETAG_PHARM_CLAIMS', ' ');

  v_start_mnth := start_mnth;

  WHILE  end_mnth >= v_start_mnth LOOP -- month while loop

    FOR rx_clm_rec IN (SELECT   CONTR_DIM_ID,
                                EMP_GRP_DIM_ID,
                                FIRST_SERV_DATE_DIM_ID,
                                PHARMACY_CLM_FACT_ID
                         FROM   UNIV.T_PHARMACY_CLAIM_FACT F,
                                UNIV.T_DATE_DIMENSION D
                        WHERE   F.PAID_DATE_DIM_ID = D.DATE_DIM_ID
                          AND   D.MONTH = v_start_mnth)
    LOOP

      begin

        SELECT   J.TIER_CODE
          INTO   v_tier_code
          FROM   UNIV.T_PHARMACY_CLAIM_FACT P ,
                 UNIV.T_EMPLOYER_GROUP_DIMENSION E,
                 DW.T_UHC_EMP_GRP_TIER_JUNCTION J
         WHERE   E.EMP_GRP_DIM_ID = rx_clm_rec.EMP_GRP_DIM_ID
           AND   P.EMP_GRP_DIM_ID = E.EMP_GRP_DIM_ID
           AND   E.EMP_GRP_ID = J.EMP_GRP_ID
           AND   TO_DATE(TO_CHAR(FIRST_SERV_DATE_DIM_ID),'YYYYMMDD') BETWEEN
J.EFF_DATE
                                                                         AND
J.END_DATE;

      exception

        WHEN NO_DATA_FOUND THEN

          v_tier_code := '*M*';

       WHEN TOO_MANY_ROWS THEN

          v_tier_code := '*M*';

      end;

      begin

        SELECT   SUM(SUBR_CNT) + SUM(SPOUSE_CNT),
                 SUM(CHILD_CNT)
          INTO   v_subr_spouse_cnt,
                 v_child_cnt
          FROM   UNIV.T_CONTRACT_DIMENSION
         WHERE   SUBR_ID = (SELECT SUBR_ID
                              FROM UNIV.T_CONTRACT_DIMENSION
                             WHERE CONTR_DIM_ID = rx_clm_rec.CONTR_DIM_ID)
           AND   TO_DATE(TO_CHAR(rx_clm_rec.FIRST_SERV_DATE_DIM_ID),'YYYYMMDD')
BETWEEN EFF_DATE

   AND END_DATE;

      end;

      begin

        SELECT   TIER_PLAN_DIM_ID
          INTO   v_tier_dim_id
          FROM   UNIV.T_TIER_PLAN_DIMENSION T
         WHERE   TIER_CODE = v_tier_code
           AND   SUBR_SPOUSE_CNT = v_subr_spouse_cnt
           AND   CHILD_CNT = v_child_cnt;

      exception

        WHEN NO_DATA_FOUND THEN

          v_tier_dim_id := -99;

      end;

      UPDATE   UNIV.T_PHARMACY_CLAIM_FACT
         SET   TIER_PLAN_DIM_ID = v_tier_dim_id,
               UPDATE_DATE = SYSDATE
       WHERE   PHARMACY_CLM_FACT_ID = rx_clm_rec.PHARMACY_CLM_FACT_ID;

    END LOOP;
    COMMIT;

    v_start_mnth := v_start_mnth + 1;

  END LOOP;

END PRC_UHC_UPD_RX_TIER_PLAN_HIST;

          Followup:

unfortunately, when I run it -- i get "no such objects"

suggestion -- create the SMALLEST possible test case -- that is COMPLETE and
concise.

then, you have the perfect thing to file a tar with!

September 26, 2003 -- Thanks for the question regarding "Mechanism
      to perform the product of a single column in a multirow table", version
      8.1.7

      You Asked
Hi Tom,
I want to perform the product of a single field for a multi row query

table is
Table Name - TRY
FieldA     FieldB
1          10
2          100
3          20
4          10
5          10

My requirement is I want a output as (10 * 100 * 20 * 10 * 10) using a single
query (Joins statement are acceptable)
This can be performed using the PL/SQL by reading each row and storing it in a
variable.The next row read can be * with the previous value.

      and we said...
see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2196162600402
in 8i, we don't have a "product" aggregate, in 9i you can write one.  In 8i, we
would use one of the solutions offered by me using a PLSQL function, for
example:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace
  2  function product( p_key_name in varchar2,
  3                    p_key_val  in varchar2,
  4                    p_other_col_name in varchar2,
  5                    p_tname     in varchar2 )
  6  return number
  7  as
  8      type rc is ref cursor;
  9      l_num    number;
 10      l_val    number;
 11      l_cur    rc;
 12  begin
 13
 14      open l_cur for 'select '||p_other_col_name||'
 15                        from '|| p_tname || '
 16                       where '|| p_key_name || ' = :x
 17                         and '||p_other_col_name|| ' is not null'
 18                  using p_key_val;
 19
 20      loop
 21          fetch l_cur into l_val;
 22          exit when l_cur%notfound;
 23                  l_num := nvl(l_num,1) * l_val;
 24
 25      end loop;
 26      close l_cur;
 27
 28      return l_num;
 29  end;
 30  /

Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select deptno, product( 'deptno', deptno,
'sal', 'emp' ), cnt
  2    from (select deptno, count(*) cnt
  3                    from dept
  4                   group by deptno )
  5  /

    DEPTNO PRODUCT('DEPTNO',DEPTNO,'SAL','EMP')        CNT
---------- ------------------------------------ ----------
        10                           1.5925E+10          1
        20                           2.3562E+16          1
        30                           1.0153E+19          1
        40                                               1

In 9i, we could use a user defined aggregate:

scott@ORA9I.WORLD> create or replace type product_type as object
  2  (
  3     total number,
  4
  5     static function
  6          ODCIAggregateInitialize(sctx IN OUT product_type )
  7          return number,
  8
  9     member function
 10          ODCIAggregateIterate(self IN OUT product_type ,
 11                               value IN number )
 12          return number,
 13
 14     member function
 15          ODCIAggregateTerminate(self IN product_type,
 16                                 returnValue OUT  number,
 17                                 flags IN number)
 18          return number,
 19
 20     member function
 21          ODCIAggregateMerge(self IN OUT product_type,
 22                             ctx2 IN product_type)
 23          return number
 24  );
 25  /

Type created.

scott@ORA9I.WORLD>
scott@ORA9I.WORLD> create or replace type body product_type
  2  is
  3
  4  static function ODCIAggregateInitialize(sctx IN OUT product_type)
  5  return number
  6  is
  7  begin
  8      sctx := product_type( null );
  9      return ODCIConst.Success;
 10  end;
 11
 12  member function ODCIAggregateIterate(self IN OUT product_type,
 13                                       value IN number )
 14  return number
 15  is
 16  begin
 17      self.total := nvl(self.total,1) * value;
 18      return ODCIConst.Success;
 19  end;
 20
 21  member function ODCIAggregateTerminate(self IN product_type,
 22                                         returnValue OUT number,
 23                                         flags IN number)
 24  return number
 25  is
 26  begin
 27      returnValue := self.total;
 28      return ODCIConst.Success;
 29  end;
 30
 31  member function ODCIAggregateMerge(self IN OUT product_type,
 32                                     ctx2 IN product_type)
 33  return number
 34  is
 35  begin
 36      self.total := nvl(self.total,1) * nvl(ctx2.total,1);
 37      return ODCIConst.Success;
 38  end;
 39
 40
 41  end;
 42  /

Type body created.

scott@ORA9I.WORLD>
scott@ORA9I.WORLD> CREATE or replace
  2  FUNCTION product(input number )
  3  RETURN number
  4  PARALLEL_ENABLE AGGREGATE USING product_type;
  5  /

Function created.

scott@ORA9I.WORLD>
scott@ORA9I.WORLD> select deptno, product(sal)
  2    from emp
  3   group by deptno
  4  /

    DEPTNO PRODUCT(SAL)
---------- ------------
        10   1.5925E+10
        20   2.3562E+16
        30   1.0153E+19

scott@ORA9I.WORLD>
Review & Followup

      Rating: 5
I heard that 9i
      supports object inheritance...  December 12, 2001

Reviewer:  Scott Sowers  from Chicago
Tom,

I've seen you define a few 9i aggregate object types.  Is object inheritance in
9i sufficiently robust to allow us to just define an interface/abstract
class/concrete class with those ODCI... members?

Thank you much.

          Followup:

Not following you on this question.

The aggregates use a "template" construct -- similar to an interface, very
similar, but not identical.  You create a type with various well defined entry
points.  You register (associate) this type with a function.  The database calls
the appropriate type methods in your type at runtime with the correct inputs.

So, this is alot like implementing an interface...

I don't see where inheritance would come into play here at all?  Why would you
want to use that construct, how would it be useful?

      Rating: 5
A little
      mathematics...  December 12, 2001
Reviewer:
      Connor  from London
select exp(sum(ln(col))
from table

is a way for doing products.

          Followup:

very good -- i knew there must have been a way to do this, very nice...

      Rating: 5
Not really
      mathematics, just calculus...  December 13, 2001

Reviewer:  Sergei  from The Netherlands
Conner right, but it works for positive numbers only (it's easy to make it
working on the whole NUMBER range tough).

I was just wondering isn't it would be more efficient to use package variable?
Something like:
exec my_product.reset_product; -- we need to reset each time
select test_package.show_product, count(my_product.product(x)) from my_table;
--discarding results from count
select my_product.show_product from dual; -- this could be used either
I have that feeling that with dicent data samples ln(x) will eat all the cpu
resources.

Cheers

          Followup:

Benchmark it!  It depends on the approach.  My approach where you hide the group
by in an inline view and call product AFTER The grouping is pretty efficient
(only calls plsql one for each OUTPUT row).  Calling PLSQL once for every row as
you have would be much slower then LN().  Cursory tests do show that calling
product against the inline view to be faster then calling exp(ln()) for each row
-- however, if you do not have the ability to write a function for whatever
reason (draconian rules where you work for example), exp(ln()) is a nice
solution.

That and you cannot group by in your solution....

So, lets fix that negative number problem:

tkyte@TKYTE816> create table t ( x int );
Table created.

tkyte@TKYTE816> insert into t values ( 100 );
1 row created.

tkyte@TKYTE816> insert into t values ( -100 );
1 row created.

tkyte@TKYTE816> select exp(sum(ln(abs(x))))*decode(mod(
count(decode(sign(x),-1,1,null)),2),1,-1,1)
  2  from t
  3  /

EXP(SUM(LN(ABS(X))))*DECODE(MOD(COUNT(DECODE(SIGN(X),-1,1,NULL)),2),1,-1,1)
---------------------------------------------------------------------------
                                                                     -10000

tkyte@TKYTE816>
tkyte@TKYTE816> insert into t values ( -1 );

1 row created.

tkyte@TKYTE816>
tkyte@TKYTE816> select exp(sum(ln(abs(x))))*decode(mod(
count(decode(sign(x),-1,1,null)),2),1,-1,1)
  2  from t
  3  /

EXP(SUM(LN(ABS(X))))*DECODE(MOD(COUNT(DECODE(SIGN(X),-1,1,NULL)),2),1,-1,1)
---------------------------------------------------------------------------
                                                                      10000

All i did here was count the number of negative entries -- if ODD then mulitply
by -1, else multiply by +1....

Still that won't handle 0, so we can:

tkyte@TKYTE816> select exp(sum(ln(abs(decode(x,0,1,x)))))*
  2         decode(mod( count(decode(sign(x),-1,1,null)),2),1,-1,1)*
  3             nvl(max(decode(x,0,0,null)),1) prod
  4  from t
  5  /

      PROD
----------
     10000

tkyte@TKYTE816> insert into t values ( 0 );

1 row created.

tkyte@TKYTE816>
tkyte@TKYTE816> select exp(sum(ln(abs(decode(x,0,1,x)))))*
  2         decode(mod( count(decode(sign(x),-1,1,null)),2),1,-1,1)*
  3             nvl(max(decode(x,0,0,null)),1) prod
  4  from t
  5  /

      PROD
----------
         0

that'll multiply the product by either 0 (if we detect a zero in the group) or
by 1 otherwise....

In 9i, it is interesting to note that enhancements to the engine make the PLSQL
solution better:

ops$tkyte@ORA9I.WORLD> create table t as select mod(object_id,5)+1 x, owner from
all_objects
  2  where owner not in ( 'PUBLIC', 'SYS', 'SYSTEM' );

Table created.

Elapsed: 00:00:00.35
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> insert into t select -x, owner from t;

294 rows created.

Elapsed: 00:00:00.02
ops$tkyte@ORA9I.WORLD> select count(*) from t;

  COUNT(*)
----------
       588

Elapsed: 00:00:00.00
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> set timing on
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> select owner,
  2         exp(sum(ln(abs(decode(x,0,1,x)))))*
  3         decode(mod( count(decode(sign(x),-1,1,null)),2),1,-1,1)*
  4         nvl(max(decode(x,0,0,null)),1) prod
  5  from t
  6  group by owner
  7  /

OWNER                                PROD
------------------------------ ----------
A                                     -25
BENCH                                3600
BOOK                           7464960000
CLBECK                              -2025
DEMO11                         1.7199E+15
DEMO2                                  -9
DEMO20                               -625
DEMO21                                 -4
DEMO4                                1600
DEMO5                                  -1
DEMO6                                  25
FORUM                          3.8698E+19
HR                             2.0543E+32
HR1                            1.2839E+27
LOW_PRIORITY                           -1
OPS$CLBECK                     7.1547E+48
OPS$ORACLE9I                            9
OPS$SDILLON                       -360000
OPS$TKYTE                      -1.548E+16
OUTLN                            -1440000
SCOTT                          -1.548E+20
SH                             -4.778E+17
TEST                                   -4
TEST1                                 144
TMC                                    64
TMC1                                   -1
TYPES                                 144

27 rows selected.

Elapsed: 00:00:00.21
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> select owner, product(x)
  2  from t
  3  group by owner
  4  /

OWNER                          PRODUCT(X)
------------------------------ ----------
A                                     -25
BENCH                                3600
BOOK                           7464960000
CLBECK                              -2025
DEMO11                         1.7199E+15
DEMO2                                  -9
DEMO20                               -625
DEMO21                                 -4
DEMO4                                1600
DEMO5                                  -1
DEMO6                                  25
FORUM                          3.8698E+19
HR                             2.0543E+32
HR1                            1.2839E+27
LOW_PRIORITY                           -1
OPS$CLBECK                     7.1547E+48
OPS$ORACLE9I                            9
OPS$SDILLON                       -360000
OPS$TKYTE                      -1.548E+16
OUTLN                            -1440000
SCOTT                          -1.548E+20
SH                             -4.778E+17
TEST                                   -4
TEST1                                 144
TMC                                    64
TMC1                                   -1
TYPES                                 144

27 rows selected.

Elapsed: 00:00:00.14

and easier to read by far ;)

      Rating: 3
comment about
      string aggregate package  December 13, 2001

Reviewer:  Mikito Harakiri
First, it's better to move hardcoded comma outside of the aggregate function
implementation and concatenate with comma in the query itself like this:

select deptno, ltrim((stragg(','||ename)),',')
   from emp
   group by deptno
/

Second, in my hierarchical query I want to show the path to the employees like
this:

KING.JONES.SCOTT

(Because of the dot see the comment#1).

Now if I perform corellated scalar subquery like this:

select
 (select stragg(ename||'.') from emp ee
  connect by prior mgr=empno
  start with ee.empno=e.empno)
from emp e
connect by prior empno=mgr
start with ename = 'KING';

then I need to flag to the aggregate function that I want to concatenate in
reverse order. How do I do this with the package?

          Followup:

well, thats your opinion -- sure.  I think moving it out obscures the simplicity
but hey -- its up to you.  (stragg is in a totally different question as well).

In the connect by, why not just use the sys_connect_by_path

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1014464646711
...

To "pass" things to a user defined aggregate, I suppose you could use a package
- put globals into, (like separator, reverse_flag) and let people set those
values -- your user defined aggregate would look to the values in the package to
decide how to work.

      Rating: 3
minor
      correction  December 13, 2001
Reviewer:  Mikito
      Harakiri
outer "connect by" is unecessary, of course:

select
 (select stragg(ename||'.') from emp ee
  connect by prior mgr=empno
  start with ee.empno=e.empno)
from emp e

      Rating:
      4
sys_connect_by_path  December 13, 2001

Reviewer:  Mikito Harakiri
What if my concatenation needs to take "level" into account as well?

KING./..SMITH../...JONES

Moralizing: All other concerns beeing ignored (performance, etc), it is better
to have subquery than a black box function.

Sorry, for writing into the wrong thread.

          Followup:

If we could ignore all other concerns (performance, et. al.) we would just be
using sed, grep and awk.

You've brought this up before with analytic functions -- where you don't like
them for some reason simply because you can do it in sql (even though the
analytic functions are literally thousands of times faster -- many thousands of
times).

Performance is always important to me, I don't answer any questions here without
considering performance -- I'll many times give two or three answers (slow --
but what you asked for, fast and almost what you wanted -- maybe you can live
with it).

I can think of better ways to do what you ask -- using the sys_connect_by_path
-- that would perform much better then a correlated subquery that redoes the
work we just did (over and over and over)....

I'll moralize:

All other concerns aside -- it is better to do things simply and to use out of
the box functionality -- rather then writing somewhat obscure implementations
that are harder to maintain and harder to tune.

      Rating: 5
About
      Accuracy  September 25, 2003
Reviewer:  Asim
      Naveed
Regarding the Product solution earlier in this
thread.

select exp(sum(ln(col))
from table

Wouldnt it gives an inaccurate product?
Becuase LN is being used.

The above solution cannot be used when accuracy is
required, agree???

      Rating: 3
am i
      wrong  September 26, 2003
Reviewer:  Asim
      Naveed  from Pakistan
Am I wrong?????

          Followup:

no

September 27, 2003 Jagan -- Thanks for the question regarding
      "Calling A procedure dynamically which has weak refcursor has out
      parameter", version 8.1.7

      You Asked
Hi Tom
i have the following problem
i have a procedure
PROCEDURE get_parameterOptions_pr(
                    i_parameterName IN  NUMBER
                   , o_optionList OUT refCursorType
               )

when i try to call this in dynamic SQL  as below

v_procCallStr := 'BEGIN '||v_procName||'(:1, :2); END;';
EXECUTE IMMEDIATE v_procCallStr
    USING  IN i_parameterName, OUT o_optionList;

I get error
Line: 387 Column: 36  Error: PLS-00457: expressions have to be of SQL
types

is there any work around for this,
Jagan

      and we said...
you will not be successful in dynamically invoking a procedure that uses a ref
cursor.  there is no way to do it.

dynamic sql -- while it makes for generic code -- should truly be avoided.  It
would be infinitely preferable to have a procedure with a big if then elsif
block to do this (and that is the only way to do this with a ref cursor)

if you stick with "dynamic stuff" (not recommended if it can be avoided), your
options will be:

a) a collection type -- return an "array" instead.  that is a SQL type and is
supported.

b) use a DBMS_SQL "cursor" which is just a number.
September 27, 2003 Tony -- Thanks for the question regarding "OEM of
      10G", version 8.1.6

      You Asked
Hi Tom,
Recently, I attended launch of 10g. I found that OEM with 10g is really cool.
Will the statspack still come as separate component or it will be part of OEM in
10g? Also, could you please point me to a link for sql model clause on which you
gave presentation in the OracleWorld.
btw:- I bought your NEW book, and started reading it.

Regards,

      and we said...
statspack will be somewhat "obsolete" with 10g, it is still there -- but its
functionality is burned into the database itself and visualized with 10g EM
(enterprise manager).  the smarts are in the database, EM is used to "see it".
The database will collect everything it needs, it incoporates the logic to
analyze the gathered information.

statspack is still there however.

http://otn.oracle.com/ow2003/ow_all.html
has the OracleWorld 2003 presentations.
September 27, 2003 -- Thanks for the question regarding "Different
      levels of dynamic sql", version 8.1.7.3

      You Asked
Tom, what are the different levels of dynamic sql. My colleagues assert that
dyanmic sql is classified by the level of dynamism i.e.

1.passing  select column list  dynamically
2.passing From table list dynamically
3.passing the where clause dynamically

Please discuss and clarify

Secondly, can you give me the link to a thread on your web site where you
discussed, the order in which which dynamic sql is executed i.e
1.Parse
2.open cursor
3.Execute
3.fetch rows
4.close cursor

Actually, I'm looking for an example with an explanation of the above 4, with
reference to the example query.

Please illustrate.

      and we said...
this dates from the days of "Pro*C" with dynamic sql "method 1", "method 2",
"method 3" and "method 4".

I am not familar with their method of classification -- it is "valid" but not
universal.

SQL is either statically (precompiled) executed
or
SQL is dynamically executed

thats about it.

In order to go through how a statement is processed -- it takes a chapter in a
book (i know, i've done it three times now -- in 3 different books).

My new book Effective Oracle By Design has the latest incarnation.  Here is an
extract from an earlier book:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2588723819082
it only talks about step 1.  In order to do all of the steps, it took pages
242-264 in my last book to effectively describe whats going on.

this is a nice overview as well:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96533/optimops.htm#30169

Review &
      Followup

      Rating: 3
method 4
      ?  September 27, 2003
Reviewer:
      A reader
1.passing  select column list  dynamically  - method 1
2.passing From table list dynamically       - method 2
3.passing the where clause dynamically     - method 3

method 4 ?

          Followup:

those are not the 4 methods....  as i said:

...
I am not familar with their method of classification -- it is "valid" but not
universal.
......

the only "classificiation" i know of is the four found in the pro*c stuff.

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a97269/pc_13dyn.htm#688

September 27, 2003 Sateesh Kumar -- Thanks for the question
      regarding "How to avoid using the qualifier infront of the object name??",
      version 8.1.5

      You Asked
Hi Tom,
   I have a object created in one user and i have granted an
appropriate privelege on the object created to another user. Is
there any way that i can access the created object without using
the qualifier.

For ex:

 CREATE OR REPLACE TYPE typ_number AS TABLE OF NUMBER(17,4);
 Grant execute on typ_number to user1;

 logging as user1:
if i declare a variable in a pl/sql block
 var1 typ_number;

 --returns an error .

 var1 user.typ_number;

  will not return an error.

Is there any way that i can just use the object without the
qualifier.

Thanks
SAteesh

      and we said...
No there is not -- you cannot use synonyms with types.

One thing you can do is to use a package like this:

scott@DEV816> create or replace package types
  2  as
  3     typ_number   ops$tkyte.typ_number;
  4  end;
  5  /

Package created.

and then others can declare variables like this:

scott@DEV816> declare
  2  x   types.typ_number%type;
  3  begin
  4       null;
  5  end;
  6  /

PL/SQL procedure successfully completed.

In that fashion, if the OWNER of the type changes -- you are
protected.
Review & Followup

      Rating: 5
grant on
      object  January 29, 2003
Reviewer:  Bala
      Subramanian
Hi Tom,

I have the following problem..

create or replace type STR_ARRAY as table of varchar2(20);

create or replace package userManagement_pkg as
procedure remove_user_proc(
p_userid_array    IN STR_ARRAY,
p_message    OUT varchar2);

end userManagement_pkg;
/

create or replace package body userManagement_pkg as

procedure remove_user_proc(
p_userid_array    IN bin.STR_ARRAY,
p_message    OUT varchar2)

is

begin
    for i in 1..p_userid_array.count
    loop
        delete from tbluser_profile
                where up_userid = p_userid_array(i);
    end loop;
        p_message := 'removed';
exception
    when others then raise;
end remove_user_proc;
end userManagement_pkg;

create user appl identified by appl;

grant execute on userManagement_pkg to appl;
grant execute on str_array to appl with grant option;

now if i call this remove_user_proc from jsp page
as user appl

i am getting
"ORA-04043: object APPL.STR_ARRAY does not exist "

if i execute as owner then there is no problem.

I am using oci8 driver.

what am i missing here?
(oracle 8.1.7)

Thanks
Bala.

          Followup:

APPL.STR_ARRAY doesn't exist.

but someother <SCHEMANAME>.str_array does.

so, put the right schemaname in there when you define the ARRAY  in java.

      Rating: 5
Thank you so
      much  January 30, 2003
Reviewer:  Bala
      Subramanian
That was it.
Thank you so much ...

Bala

      Rating: 5
Problem in
      passing array to pl/sql with JNDI connection
      pooling  February 17, 2003
Reviewer:  Bala
      Subramanian
Hi Tom
As soon as i started to use Tomcat JNDI connection pooling
array passing to stored procedures are not working.

According to your thread

http://asktom.oracle.com/pls/ask/f?p=4950:8:2083425::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1014031146145,%7Barray%7D%20and%20%7Bconnection%7D%20and%20%7Bpool%7D
we can not pass arrays to database using a pooled connection?.

Is there a work around.

Thanks
Bala.

          Followup:

who said that in that thread?  no where do I see that.

but -- use our jdbc drivers or read the documentation for the jdbc drivers you
are using.

with Oracles jdbc drivers -- I can tell you how to do it.

With theirs -- I cannot tell you if you can even do it.

But you can use ANY jdbc driver you want.

      Rating: 3
Using Oracle
      JDBC thin driver passing array in Websphere  September 27,
      2003
Reviewer:  karthik  from NJ
When I try to use oracle.jdbc.pool.OracleConnectionPoolDataSource
I get a class cast exception. I want to know if there is any driver that
supports passing in an array to a stored procedure in oracle using connection
pool that works in websphere.

          Followup:

try over at devtrends.oracle.com

i know not too much about Java.  If websphere is standards based, there is zero
reason it would not work if you have all of the necessary jar files available.

September 27, 2003 David -- Thanks for the question regarding "How
      can I get this result only executing one query?", version 9.2

      You Asked
Hi Tom,
I'm building a "search form" where user can search by any word. This word can be
found in items (records) of various topics.
The result of query should be:

Your results ...

   + Topic 1: 55 items
   + Topic 2: 12 items
   + Topic 3: 190 items

Here, there is one group by topic. Now, when the user expand this tree to find
the items, I execute another query similar to "select item from T where topic =
selected for user". This select uses Oracle text and it has a lot of predicates;
now I execute the query twice:

First:

Select count(*), topic
 from T
 where contains ............. and ...

and second (when expand tree)

Select Item
 from T
 where "the same where that the first query" and
       topic = <selected>

This is a web application.
Exists any way to get this result only executing one select ??

Thanks Tom.

Atententament
David Pujol

      and we said...
Here is one approach:

ops$tkyte@ORA920> /*
DOC>drop table t;
DOC>
DOC>create table t ( topic varchar2(10), item varchar2(10), text varchar2(4000)
);
DOC>
DOC>insert into t
DOC>select 't-' || mod(rownum,5),
DOC>       'i-' || rownum,
DOC>       object_name || ' ' || object_type || ' ' || owner
DOC>  from all_objects
DOC>/
DOC>
DOC>create index t_idx on t(text) indextype is ctxsys.context;
DOC>*/
ops$tkyte@ORA920>
ops$tkyte@ORA920> select topic, count(*)
  2    from t
  3   where contains( text, 'SCOTT' ) > 0
  4   group by topic;

TOPIC        COUNT(*)
---------- ----------
t-0                 2
t-1                 2
t-2                 2
t-3                 1
t-4                 1

ops$tkyte@ORA920>

ops$tkyte@ORA920> select decode( grouping(item),
  2                 1, 'Total ' || decode( grouping(topic),
  3                                        1, 'Hits',
  4                                           'For Topic'),
  5                 'Detail' ),
  6         topic,
  7         item,
  8         count(*)
  9    from t
 10   where contains( text, 'SCOTT' ) > 0
 11   group by rollup( topic, item )
 12   order by grouping(item) desc, grouping(topic) desc, topic, item
 13  /

DECODE(GROUPING TOPIC      ITEM         COUNT(*)
--------------- ---------- ---------- ----------
Total Hits                                     8
Total For Topic t-0                            2
Total For Topic t-1                            2
Total For Topic t-2                            2
Total For Topic t-3                            1
Total For Topic t-4                            1
Detail          t-0        i-31565             1
Detail          t-0        i-31570             1
Detail          t-1        i-31566             1
Detail          t-1        i-31571             1
Detail          t-2        i-31567             1
Detail          t-2        i-31572             1
Detail          t-3        i-31568             1
Detail          t-4        i-31569             1

14 rows selected.

Review &
      Followup

      Rating:
      5
  September 27, 2003
Reviewer:
      A reader

      Rating:
      5
  September 27, 2003
Reviewer:  David
      Pujol  from Barcelona, Catalonia

September 27, 2003 vinay -- Thanks for the question regarding "Java
      Stored Procedures loaded in Database", version 8.1.7

      You Asked
Hi Tom,
I have specific question related to security when Using java stored procedures
loaded into database.
In my situation, I have a Java program that interacts with OS to run system
commands and shell scripts.
I am loading(using loadjava utility) this Java class into database. Assuming, I
make use of Oracle provided Aurora's SecurityManager and ensure that my class is
secured against users from other schemas.

My specific question here is how does calling this stored procedure(Call Specs)
from database, access OS file system?

Is it using sockets/RPC calls(as in the case external procedures), are there any
security concerns?

I understand, calling external procedures and java stored procedures from
database is documented feature in Oracle. I feel most of the corporate security
policies, would recommend to block all sorts connections to/from database, other
than standard options(Net8, OCI, JDBC etc). In which case, these features can
not be used.

I would appreciate your thoughts.

Thanks and regards,
Vinay

      and we said...
it does it in the same address space as the Oracle database kernel itself.  It
runs as the same exact account that runs the database software.

You would use dbms_java to grant in a very fine grained manner exactly what it
can and cannot do -- for example when using sockets -- you (as the dba) can
specify what host and even what ports on that host -- the java engine is allowed
to connect to.

So, in order to all java running in the database to accept or make a connection
-- you must EXPLICITLY grant that privilege.
September 28, 2003 Les -- Thanks for the question regarding "Parse
      out columns of a weakly typed ref cursor", version 8.1.7.0.0

      You Asked
Good day Tom,

Many thanks for receiving my question regarding ref cursors.

I am working on an application that uses a generic weakly typed ref cursor to
return rows to an active server page that uses vbscript to format a web page. I
would like to be able to return that same weakly typed ref cursor to a plsql
procedure that would display the rows on a web page.

For example, the following creates a table of output with an update and delete
link. Active server pages just executes this and receives the data in a record
set.

   Procedure get_parm_groups
   (p_group_name in varchar2 default null,
    p_result_set out types.cursor_type)
   is

   Begin
      Open p_result_set for
      select pg.group_name,
             pg.group_desc,
             '<A HREF="parm_group_parms.asp?theRowID='||
                      urlencode(rowidtochar(pg.rowid))||
                                         '&Refresh=No'||
             decode(rec_count.parms_by_group(pg.rowid),0,'">Parameters</A>',
                                      '">Parameters ('||
                   rec_count.parms_by_group(pg.rowid) ||
                                                ')</A>') "Detail Link",
              '<A HREF="parm_group_updt.asp?theRowID='||
                      urlencode(rowidtochar(pg.rowid))||
                                         '&Refresh=No'||
                                        '">Update</A>' "Update Link",
             decode(nvl(rec_count.parms_by_group(pg.rowid),0),0,
           ('<A HREF="parm_group_delete.asp?theRowID='||
                      urlencode(rowidtochar(pg.rowid))||
                                         '&Refresh=No'||
                                        '">Delete</A>'),
                                                   ' ') "Delete Link",
             '<a href="parm_group_insert.asp?ReFresh=No">New Group</a>' "New
Link"
        from parm_group pg
       where nvl(p_group_name,pg.group_desc) = pg.group_desc
       order by lower(pg.group_name);

In active server pages the following happens:

        set rs = Server.CreateObject("ADODB.Recordset")
        Set rs = cmdStoredProc.Execute

        'Fomat the output - Note the Add new Group is in the header
        if (not rs.EOF) then
          Response.Write ("<h2>Parameter Groups  - " & rs (5) & "</h2>" &
vbCrLf)
          emptyTable = "No"
          Response.Write ("<table border = 1>" & vbCrLf)
          Response.Write ("<tr>" & vbCrLf)
          Response.Write ("<th>Group Name       </th>" & vbCrLf)
          Response.Write ("<th>Group Desc       </th>" & vbCrLf)
          Response.Write ("<th>Detail           </th>" & vbCrLf)
          Response.Write ("<th>Update           </th>" & vbCrLf)
          Response.Write ("<th>Delete           </th>" & vbCrLf)
          Response.Write ("</tr>" & vbCrLf)
          While (Not rs.EOF)
                Response.Write ("<tr>" & vbCrLf)
                Response.Write ("<td>" & rs (0) & "</td>" & vbCrLf)
                Response.Write ("<td>" & rs (1) & "</td>" & vbCrLf)
                Response.Write ("<td>" & rs (2) & "</td>" & vbCrLf)
                Response.Write ("<td>" & rs (3) & "</td>" & vbCrLf)
                Response.Write ("<td>" & rs (4) & "</td>" & vbCrLf)
                Response.Write ("</tr>" & vbCrLf)
                rs.MoveNext
          Wend
          Response.Write ("</table>" & vbCrLf)
        else
          Response.Write ("<h2>No Parameter Groups Currently Defined</h2>"  &
vbCrLf)

Please correct me if I am wrong (fingers crossed, as I hope I am getting
corrected), but from what I have learned by searching your site amoung other
things, is that the out parameter would need to change to an in out parameter
and a record type would need to be created to recieve the data in another plsql
procedure.

What I am hoping you will tell me is that there is a way to interrogate the ref
cursor at run time similiar to the following for dynamic sql (after opening a
cursor and doing a parse of a select statement):

dbms_sql.describe_columns(theCursor,v_column_count,v_describe_table);

...or maybe there is a way to extract the select statement from the ref cursor,
and re-parse and describe...Or maybe the only way is to copy that select
statement out ( Hmmm... 2 things to change in 2 places when something changes)
of that procedure and use the dbms_sql package to parse the statement...

I am open to any suggestion that would allow both active server pages to use the
procedure, as well as plsql, with a minimum of change.

Again, many thanks for accepting my question and assistance you may offer.

Les Davidner

      and we said...
You cannot do this with a ref cursor -- not in plsql.  You would have to use
DBMS_SQL to open the query -- not a ref cursor.

If you do not know the number and types of output elements in the cursor at
compile time -- there is simply NO way for plsql to execute the "fetch
ref_cursor into <unknown list of variables>".

you need to use the procedural API for dyanmic sql in this case.

The only suggestion I have is to put the procedure in a package (to isolate the
code) and create overloaded procedures -- on that returns a ref cursor and one
that returns a dbms_sql cursor.

Looking on the bright side - I really truly thing you want to recode that
procedure anyway-- that trick you are using:

where nvl(p_group_name,pg.group_desc) = pg.group_desc

is a KILLER -- it is a pretty bad thing.  It obviates indexes, forces a full
scan.  What you should code is:

if ( p_group_name is not NULL )
then
 Open p_result_set for
      select pg.group_name,
             pg.group_desc,
             '<A HREF="parm_group_parms.asp?theRowID='||
                      urlencode(rowidtochar(pg.rowid))||
                                         '&Refresh=No'||
             decode(rec_count.parms_by_group(pg.rowid),0,'">Parameters</A>',
                                      '">Parameters ('||
                   rec_count.parms_by_group(pg.rowid) ||
                                                ')</A>') "Detail Link",
              '<A HREF="parm_group_updt.asp?theRowID='||
                      urlencode(rowidtochar(pg.rowid))||
                                         '&Refresh=No'||
                                        '">Update</A>' "Update Link",
             decode(nvl(rec_count.parms_by_group(pg.rowid),0),0,
           ('<A HREF="parm_group_delete.asp?theRowID='||
                      urlencode(rowidtochar(pg.rowid))||
                                         '&Refresh=No'||
                                        '">Delete</A>'),
                                                   ' ') "Delete Link",
             '<a href="parm_group_insert.asp?ReFresh=No">New Group</a>' "New
Link"
        from parm_group pg
       where p_group_name = pg.group_desc
       order by lower(pg.group_name);
else
 Open p_result_set for
      select pg.group_name,
             pg.group_desc,
             '<A HREF="parm_group_parms.asp?theRowID='||
                      urlencode(rowidtochar(pg.rowid))||
                                         '&Refresh=No'||
             decode(rec_count.parms_by_group(pg.rowid),0,'">Parameters</A>',
                                      '">Parameters ('||
                   rec_count.parms_by_group(pg.rowid) ||
                                                ')</A>') "Detail Link",
              '<A HREF="parm_group_updt.asp?theRowID='||
                      urlencode(rowidtochar(pg.rowid))||
                                         '&Refresh=No'||
                                        '">Update</A>' "Update Link",
             decode(nvl(rec_count.parms_by_group(pg.rowid),0),0,
           ('<A HREF="parm_group_delete.asp?theRowID='||
                      urlencode(rowidtochar(pg.rowid))||
                                         '&Refresh=No'||
                                        '">Delete</A>'),
                                                   ' ') "Delete Link",
             '<a href="parm_group_insert.asp?ReFresh=No">New Group</a>' "New
Link"
        from parm_group pg
       order by lower(pg.group_name);
end if;

You'll get the best performance that way.
Review &
      Followup

      Rating: 3
Anyway to get
      the count from Ref Cursor ?  July 26, 2002

Reviewer:  Ken Chiu  from toronto, on, canada.

Hi tom, thanks in advance for many useful insights.

As the title says, without using a type for the ref cursor to fetch into, is it
possible to get the count of records from a ref cursor ?

Can you elaborate more on the APIs ?  Do you mean replacing the ref cursor with
DBMS_SQL ?

thanks,
ken.

          Followup:

The answer is the same for ALL CURSORS -- ref or otherwise.

You can find out the number of rows you have fetched so far with C%rowcount
where C is the cursor name/variable.

Until you fetch the last row, no one -- including Oracle -- knows how many rows
will be the results.  We tend to not pre-answer queries (consider "select * from
one_trillion_row_table", we don't count up the answer first and then return
rows, we just return rows until we are done)

Yes, I mean -- replace the use of the ref cursor with calls to DBMS_SQL.  search
this site for dbms_sql for tons of examples.  search for dump_csv for a very
generic example.

      Rating: 4
Adding values
      to ref-cursor  September 26, 2003
Reviewer:
      B  from India
Hi Tom,

Is their any other way to load data into a refcursor other than the following

CREATE OR REPLACE PROCEDURE return_many_rows_ref(RSET IN OUT test_pack.mycur)
IS
BEGIN
     OPEN RSET FOR SELECT * FROM dept;
END;

Say, if I want to do it in a loop like the following, it has only the last value
in the table of record as the previous ones gets overwritten (which is valid as
I keep on opening itb again)..

 FOR I IN table_of_rec.first..table_of_rec.last Loop
     open a_ref_cursor for
                select b(I).p_price_list as   p_price_list,
                        '2'             as  p_currency_code,
                       from dual;
   dbms_output.put_line(b(I).p_price_list);
  end loop;

Is there a way to this achieve this functionality and retining all the data..

Regards,
Bharath

          Followup:

i don't understand the logic here.

a ref cursor is opened via a query -- period.  the query can be "select * from
PLSQL_FUNCTION" or select * from tables -- but it'll be a query.

i don't understand the psuedo code logic if your loop there at all.

      Rating: 4
Adding data to
      ref-cursor  September 28, 2003
Reviewer:
      Bharath  from India
Hi Tom,

Sorry for not being clear in my previous posting..

I have a table of record.. I go through the data in the table of record in a
loop.. And what I want to know is
while going through the loop I want to keep adding the data one-by-one  to a
ref-cursor.. i.e all the data in the table of record should be available in the
refcursor..

When I tried, the ref-cursor has only the last row in the table of record(i.e.
data fetched by the last loop) as the previous ones gets overwritten, which is
valid as I keep on opening the ref-cursor again and again..

I wanted to find out is their anyway the above functionality can be achieved..

And anyways you have said the only way to add data to a ref-cursor is through a
query.. which can either select from a PL/SQL Function or a table..

Thanks

          Followup:

once a row is fetched, it is "fetched", gone, processed, finished.

you would just open two ref cursors with the same defining query.

you might consider changing your logic so as to NOT NEED to read and re-read the
same row(s) over and over and over (that would be my choice)

if you are more clear as to your processing needs, I might be able to be more
clear as to how to most efficiently approach the problem.

      Rating:
      4
Ref-Cursor  September 30, 2003
Reviewer:
      Bharath  from India
Tom,
Thanks.. This was required by my friend.. Even he was not sure why his PM wanted
it this way.. Anyways he has dropped the idea of getting the data into a
ref-cursor.. Again thanks a lot..

September 28, 2003 -- Thanks for the question regarding "using
      oracle text function contains in anymous block with binding variable",
      version 8.1.7

      You Asked
hi tom,

i can easily query an oracle text query with binding through NDS

declare
myval varchar2(253);
begin
myval:= '!google';
execute immediate 'declare ct  number; begin  SELECT count(*) into ct FROM table
WHERE CONTAINS
(col,:x,1)>0 ;dbms_output.put_line(''the count: ''||ct);end;' using myval;

end;

could you please provide a example a oracle text query using contains function
with dbms_sql package?

thanks for your advice.

georges

      and we said...
it is not any different then using a bind with ANY query in dbms_sql.

ops$tkyte@ORA920> /*
DOC>drop table t;
DOC>create table t ( x varchar2(4000) );
DOC>insert into t
DOC>select object_name || ' ' || object_type || ' ' || owner from all_objects;
DOC>create index t_idx on t(x) indextype is ctxsys.context;
DOC>*/
ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      l_theCursor     integer default dbms_sql.open_cursor;
  3      l_columnValue   varchar2(4000);
  4      l_status        integer;
  5      l_query         varchar2(1000) default 'select x from t where contains(
x, :x ) > 0';
  6  begin
  7      dbms_sql.parse(  l_theCursor,  l_query, dbms_sql.native );
  8      dbms_sql.bind_variable( l_theCursor, ':x', 'SCOTT' );
  9      dbms_sql.define_column( l_theCursor, 1, l_columnValue, 4000 );
 10
 11      l_status := dbms_sql.execute(l_theCursor);
 12
 13      while ( dbms_sql.fetch_rows(l_theCursor) > 0 )
 14          loop
 15          dbms_sql.column_value( l_theCursor, 1, l_columnValue );
 16                  dbms_output.put_line( l_columnValue );
 17      end loop;
 18
 19      dbms_sql.close_cursor(l_theCursor);
 20  end ;
 21  /
SALGRADE TABLE SCOTT
EMP_PK INDEX SCOTT
EMP_IND1 INDEX SCOTT
EMP TABLE SCOTT
DUMMY TABLE SCOTT
DEPT_PK INDEX SCOTT
DEPT TABLE SCOTT
BONUS TABLE SCOTT

PL/SQL procedure successfully completed.

September 28, 2003 Marta -- Thanks for the question regarding
      "Error:PLS 00906 compilation is not possible", version 8.0.5.1

      You Asked
PLS 00906    compilation is not possible
Cause    The PL/SQL compiler cannot run properly because
        its operating environment is corrupted.
        For example, its error message file might be
inaccessible.

Action    Check the PL/SQL operating environment,
        making sure that all files required by the compiler
        are accessible.

This message occurs when trying to create a procedure with some
errors.
DBA has reinstalled database, but the problem persists.
Similar installation with the same environment and version of
database
on the same platform works properly.
Could you advice me what files have to be checked or what to do?
( operating system: VMS)
Thank you very much.

      and we said...
found a vms specific problem with 8.0.5.1 with respect to this:

Problem:
========

You create a stored procedure and receive the following errors
when you compile:

  ORA-06540 PL/SQL compilation error
  ORA-06553 PLS-906 compilation is not possible

Solution:
=========

1. $ SET DEFAULT ORA_UTIL    (This is where the ORAUSER.COM file
is located.)

2. Edit ORAUSER.COM and modify the order so that RDBMSUSER.COM
is called *before* PROGINTUSER.COM.

3. Shutdown the database.

4. Exit to the OpenVMS "$" prompt.

5. $ SET DEFAULT ORA_DB       (This is where the
ORAUSER_<sidname>.COM  file is located.)

6. Run the ORAUSER_<sidname>.COM file.  This file calls
ORAUSER.COM.

7. Startup the database.

Once you startup the database, rerun the script that creates the
procedure. Now you receive more descriptive error messages that
can help you debug your procedure.

Explanation:
============

The compilation errors are returned when the message file is
accessed, even though the message file is present.  This happens
due to the way the  products are installed and linked which
determines the order in which  <product>USER.COM is added to the
ORAUSER.COM file.  When you modify the order, the message file
can be successfully accessed and report the actual (and more
useful) error messages.

Review &
      Followup

      Rating:
      5
  September 10, 2003
Reviewer:
      A reader
Hi,

When I try to execute a package the header file if created. But the body (around
8200 lines) not created, and gives the following error.

ORA-06553: PLS-:
ORA-06553: PLS-:
ORA-06553: PLS-:
ORA-06553: PLS-:
ORA-06553: PLS-215: Beschränkungen der Zeichenfolgelä
ORA-06553: PLS-215: Beschränkungen der Zeichenfolgelänge nur im Bereich (1 ..
32767)
ORA-06553: PLS-215: Beschränkungen der Zeichenfolgelänge nur im Bereich (1 ..
32767)
ORA-06553: PLS-215: Beschränkungen der Zeichenfolgelänge nur im Bereich (1 ..
32767)
ORA-06553: PLS-215: Beschränkungen der Zeichenfolgelänge nur im Bereich (1 ..
32767)
ORA-06553: PLS-215: Beschränkungen der Zeiche

could you please tell why I get this error, and how do I solve this?

Thank you

          Followup:

ops$tkyte@ORA920LAP> declare
  2  x varchar2;
  3  begin
  4  null;
  5  end;
  6  /
x varchar2;
  *
ERROR at line 2:
ORA-06550: line 2, column 3:
PLS-00215: String length constraints must be in range (1 .. 32767)

looks like you might have a variable defined that has no size associated with
it.

      Rating:
      5
  September 10, 2003
Reviewer:
      A reader
I am sorry, I have to add more information on above question.

The Operating system : Windows 2000
Database version : 9i

Thank you.

      Rating:
      5
  September 11, 2003
Reviewer:
      A reader
Hi Tom,
But Tom, this script is working fine in Oracle 8i version. Only in Oracle 9i
gets the problems.

I checked the undefined lengths for the variable. But all of the variables are
defined well.

Tom, Is there any other reasons to occure this error?

Thank you very much for your valuable help.

          Followup:

i cannot comment at all without a full example.

(a small, clear, concise, simple -- yet complete example that I can run on my
own machine to reproduce the issue)

      Rating:
      5
  September 11, 2003
Reviewer:
      A reader
Hi Tom,

I have comment of some of code and see which of them are giving me a problem.
Then I found, some inserts are giving this problem.

ex:
INSERT INTO VERMOEGENSUEBERSICHT_W_ORIG
  SELECT X030_FONDNR AS FONDSNR,
         X030_DATUM AS DATUM,
         'WPV' AS VERM_ART_ID,
         'WPI' AS KAT_ID,
         'AKT' AS POS_ID,
         0 AS KURSWERT
   FROM FFFRV.XS030_V0@VIEE.PRODUKTION.UNIX_RZ.TBT
   WHERE (X030_FONDNR IN (SELECT FONDSNR FROM BEWERTUNGSFONDS_W_ORIG)) AND
X030_DATUM = DDATUM
   GROUP BY X030_FONDNR, X030_DATUM;

This statement is in one of procedure in my package.

When I comment above stmt, then my package is successfully creating. else gives
the error.

Could you please tell what could be the problem?

Thanks

          Followup:

my car would not start this morning.

do you know why?

no, i will not tell you any symptoms
i will not tell you if i put gas in it
i will not tell you what message i see on the dashboard.

so, whats wrong with my car?

(sarcasm, biting sarcasm there.....   no error messages, no nothing.  what else
can i say???)

      Rating:
      5
  September 11, 2003
Reviewer:
      A reader
Hi Tom,

You are not only a expert, you are nice person too. Yor are right.
Here I'll try to give full details.

The procedure(CREATE_VMUAKT) in my package as follows    ---> In No : 1
The error message, what I get, after executing.          ---> in No : 2

I am sorry, I couldn't give you a small example to reproduce this situation.
Thas because,  I don't know what exactly gives me this problem.

Sofar Only thing what I found was, when I comment following Insert statements,
then the package is creating. ---> In No : 3

So Tom, could please guess what has happen. If still info. is missing, I would
like to bite more sarcasm.

Thank You.

(1).
PROCEDURE CREATE_VMUAKT(DDATUM DATE) IS
-- ***********************************************************************
-- Geändert:    13.07.1999
-- Bearbeiter:  OCT
-- Aufruf:      execute dbms_fondsvermoegen_w.create_aib(to_date(sysdate));
--
-- Die Prozedur erzeugt die Daten für den Abschnitt 'Aktien Inland Branchen'
-- für die Strukturanalyse
-- ***********************************************************************

CURSOR CUR_WPI IS (SELECT F080_FONDSNR, SUM(F080_KURSWERTBW)
                          FROM FFFRV.V3_FS080_V5@VIEE.PRODUKTION.UNIX_RZ.TBT,
                            FFFRV.V3_FS110_V0@VIEE.PRODUKTION.UNIX_RZ.TBT,
                             V3COPY_GW_VIP_WPART_W_VIEW
                      WHERE F110_WPKNR = F080_WPKNR
                        AND F110_WPART = GW_VWA_REF
                        AND F080_FONDSNR IN (SELECT FONDSNR
                                               FROM BEWERTUNGSFONDS_W_ORIG)
                        AND TRIM(F110_KZLAND) IN (SELECT TRIM(LAND_KZ)
                                                    FROM EUROLAENDER_W_ORIG
                                                   WHERE EURO = 1)
                        AND GW_WPART_KEY IN (SELECT WPART_KEY
                                               FROM WERTPAPIERARTEN_W_VIEW
                                              WHERE WPART = 'AKT')
                        AND F080_DATUM = DDATUM
                      GROUP BY
                            F080_FONDSNR);

CURSOR CUR_WPA IS (SELECT F080_FONDSNR, SUM(F080_KURSWERTBW)
                          FROM FFFRV.V3_FS080_V5@VIEE.PRODUKTION.UNIX_RZ.TBT,
                            FFFRV.V3_FS110_V0@VIEE.PRODUKTION.UNIX_RZ.TBT,
                             V3COPY_GW_VIP_WPART_W_VIEW
                      WHERE F110_WPKNR = F080_WPKNR
                        AND F110_WPART = GW_VWA_REF
                        AND F080_FONDSNR IN (SELECT FONDSNR
                                               FROM BEWERTUNGSFONDS_W_ORIG)
                        AND TRIM(F110_KZLAND) NOT IN (SELECT TRIM(LAND_KZ)
                                                    FROM EUROLAENDER_W_ORIG
                                                   WHERE EURO = 1)
                        AND GW_WPART_KEY IN (SELECT WPART_KEY
                                               FROM WERTPAPIERARTEN_W_VIEW
                                              WHERE WPART = 'AKT')
                        AND F080_DATUM = DDATUM
                      GROUP BY
                            F080_FONDSNR);

IFONDSNR NUMBER(9) := 0;
IKURSWERT NUMBER(24,2) := 0;

BEGIN

  -- Löscht alle Daten, die älter als 3 Monate aber nicht Ultimo sind
  DELETE
    FROM VERMOEGENSUEBERSICHT_W_ORIG
   WHERE POS_ID = 'AKT'
     AND VERM_ART_ID = 'WPV'
     AND CHECK_ULTIMO_W(DATUM) <> 1
     AND DATUM <= (DDATUM - 95);
  COMMIT;

  DBMS_FONDSVERMOEGEN_W.CREATE_LOG('S', 'Vermögensübersicht Aktien', 'D',
'Löschen alter Daten (vor ' || to_char(dDATUM - 95) || ') Daten zu : Aktien',
SQL%ROWCOUNT);
  COMMIT;

/** Dieser Eintrag wurde probeweise entfernt, weil er Probleme verursacht;
Birth, 6.6.2002; wieder zugefügt am 7.6.2002; **/

  -- Löscht alle Daten der aktuellen Bewertung
  DELETE
    FROM VERMOEGENSUEBERSICHT_W_ORIG
    WHERE POS_ID = 'AKT'
    AND VERM_ART_ID = 'WPV'
    AND DATUM = DDATUM;
  COMMIT;

  DBMS_FONDSVERMOEGEN_W.CREATE_LOG('S', 'Vermögensübersicht Aktien', 'D',
'Löschen aktueller Daten zu : Aktien', SQL%ROWCOUNT);
  COMMIT;

 -- Einfügen eines Datensatzes mit Kurswert 0, um alle Kategorien in der Tabelle
anzulegen
--!!  INSERT
--!!    INTO VERMOEGENSUEBERSICHT_W_ORIG
--!!  SELECT X030_FONDNR AS FONDSNR,
--!!         X030_DATUM AS DATUM,
--!!         'WPV' AS VERM_ART_ID,
--!!         'WPI' AS KAT_ID,
--!!         'AKT' AS POS_ID,
--!!         0 AS KURSWERT
--!!    FROM FFFRV.V3_XS030_V0@VIEE.PRODUKTION.UNIX_RZ.TBT
--!!   WHERE (X030_FONDNR IN (SELECT FONDSNR
--!!                            FROM BEWERTUNGSFONDS_W_ORIG))
--!!     AND X030_DATUM = DDATUM
--!!   GROUP BY
--!!         X030_FONDNR,
--!!         X030_DATUM;

  COMMIT;

-- Fügt die Daten für den Abschnitt 'Aktien Inland' der Tabelle
(geändert:23.11.00:MD)
-- VERMOEGENSUEBERSICHT_W hinzu
--  INSERT
--    INTO VERMOEGENSUEBERSICHT_W_ORIG
--  SELECT F080_FONDSNR AS FONDSNR,
--         F080_DATUM AS DATUM,
--       'WPV' AS VERM_ART_ID,
--         'WPI' AS KAT_ID,
--         'AKT' AS POS_ID,
--         SUM(F080_KURSWERTBW) AS KURSWERT
--    FROM FFFRV.V3_FS080_V5@VIEE.PRODUKTION.UNIX_RZ.TBT,
--         FFFRV.V3_FS110_V0@VIEE.PRODUKTION.UNIX_RZ.TBT,
--         FFFRV.GWVIP_WPART@VIEE.PRODUKTION.UNIX_RZ.TBT
--   WHERE ((F110_WPKNR = F080_WPKNR)
--     AND (F110_WPART = GW_VWA_REF))
--     AND (F080_KURSWERTBW <> 0)
--     AND (F080_FONDSNR IN (SELECT FONDSNR
--                               FROM BEWERTUNGSFONDS_W_ORIG))
--     AND (RTRIM(F110_KZLAND) IN (SELECT LAND_KZ
--                                       FROM EUROLAENDER_W_ORIG
--                                      WHERE EURO = 1))
--     AND ((GW_VWA_AKRELIST || GW_VWG_WPARTGR_KZ) IN (SELECT WPARTKZ ||
WPGRUPPE
--                                                       FROM
WERTPAPIERARTEN_W_ORIG
--                                                      WHERE WPART = 'AKT'))
--     AND F080_DATUM = DDATUM
--   GROUP BY
--         F080_FONDSNR,
--         F080_DATUM;
--  COMMIT;

  OPEN CUR_WPI;
  LOOP
    FETCH CUR_WPI INTO IFONDSNR, IKURSWERT;
    EXIT WHEN CUR_WPI%NOTFOUND;
  -- Update des vorher eingefügten Datensatzes, übernahme des Kurswertes; falls
vorhanden
    UPDATE VERMOEGENSUEBERSICHT_W_ORIG
       SET KURSWERT = IKURSWERT
     WHERE FONDSNR = IFONDSNR
       AND DATUM = DDATUM
       AND POS_ID = 'AKT'
       AND VERM_ART_ID = 'WPV'
       AND KAT_ID = 'WPI';
     COMMIT;
  END LOOP;
  CLOSE CUR_WPI;

  DBMS_FONDSVERMOEGEN_W.CREATE_LOG('S', 'Vermögensübersicht Aktien Ausl', 'I',
'Erstellen aktueller Daten zu : Aktien Ausland', SQL%ROWCOUNT);
  COMMIT;

  -- Fügt die Daten für den Abschnitt 'Aktien Ausland' der Tabelle
(geändert:23.11.00:MD)
  -- VERMOEGENSUEBERSICHT_W hinzu
--  INSERT
--    INTO VERMOEGENSUEBERSICHT_W_ORIG
--  SELECT F080_FONDSNR AS FONDSNR,
--         F080_DATUM AS DATUM,
--       'WPV' AS VERM_ART_ID,
--         'WPA' AS KAT_ID,
--         'AKT' AS POS_ID,
--         SUM(F080_KURSWERTBW) AS KURSWERT
--    FROM FFFRV.V3_FS080_V5@VIEE.PRODUKTION.UNIX_RZ.TBT,
--         FFFRV.V3_FS110_V0@VIEE.PRODUKTION.UNIX_RZ.TBT,
--         FFFRV.GWVIP_WPART@VIEE.PRODUKTION.UNIX_RZ.TBT
--   WHERE ((F110_WPKNR = F080_WPKNR)
--     AND (F110_WPART = GW_VWA_REF))
--     AND (F080_KURSWERTBW <> 0)
--     AND (F080_FONDSNR IN (SELECT FONDSNR
--                               FROM BEWERTUNGSFONDS_W_ORIG))
--     AND (RTRIM(F110_KZLAND) NOT IN (SELECT LAND_KZ
--                                       FROM EUROLAENDER_W_ORIG
--                                      WHERE EURO = 1))
--     AND ((GW_VWA_AKRELIST || GW_VWG_WPARTGR_KZ) IN (SELECT WPARTKZ ||
WPGRUPPE
--                                                       FROM
WERTPAPIERARTEN_W_ORIG
--                                                      WHERE WPART = 'AKT'))
--     AND F080_DATUM = DDATUM
--   GROUP BY
--         F080_FONDSNR,
--         F080_DATUM;
--  COMMIT;

-- Einfügen eines Datensatzes mit Kurswert 0, um alle Kategorien in der Tabelle
anzulegen
--!!  INSERT
--!!   INTO VERMOEGENSUEBERSICHT_W_ORIG
--!!  SELECT X030_FONDNR AS FONDSNR,
--!!         X030_DATUM AS DATUM,
--!!         'WPV' AS VERM_ART_ID,
--!!         'WPA' AS KAT_ID,
--!!         'AKT' AS POS_ID,
--!!         0 AS KURSWERT
--!!    FROM FFFRV.V3_XS030_V0@VIEE.PRODUKTION.UNIX_RZ.TBT
--!!   WHERE (X030_FONDNR IN (SELECT FONDSNR
--!!                            FROM BEWERTUNGSFONDS_W_ORIG))
--!!     AND X030_DATUM = DDATUM
--!!   GROUP BY
--!!         X030_FONDNR,
--!!         X030_DATUM;

-- Update des vorher eingefügten Datensatzes, übernahme des Kurswertes; falls
vorhanden
  OPEN CUR_WPA;
  LOOP
    FETCH CUR_WPA INTO IFONDSNR, IKURSWERT;
    EXIT WHEN CUR_WPA%NOTFOUND;
  -- Update des vorher eingefügten Datensatzes, übernahme des Kurswertes; falls
vorhanden
    UPDATE VERMOEGENSUEBERSICHT_W_ORIG
       SET KURSWERT = IKURSWERT
     WHERE FONDSNR = IFONDSNR
       AND DATUM = DDATUM
       AND POS_ID = 'AKT'
       AND VERM_ART_ID = 'WPV'
       AND KAT_ID = 'WPA';
     COMMIT;
  END LOOP;
  CLOSE CUR_WPA;

  DBMS_FONDSVERMOEGEN_W.CREATE_LOG('S', 'Vermögensübersicht Aktien Inl', 'I',
'Erstellen aktueller Daten zu : Aktien Inland', SQL%ROWCOUNT);
  COMMIT;

END CREATE_VMUAKT;

-----------------
(2).
ORA-06553: PLS-:
ORA-06553: PLS-:
ORA-06553: PLS-:
ORA-06553: PLS-:
ORA-06553: PLS-215: Beschränkungen der Zeichenfolgelä
ORA-06553: PLS-215: Beschränkungen der Zeichenfolgelänge nur im Bereich (1
..32767)
ORA-06553: PLS-215: Beschränkungen der Zeichenfolgelänge nur im Bereich (1
..32767)
ORA-06553: PLS-215: Beschränkungen der Zeichenfolgelänge nur im Bereich (1
..32767)
ORA-06553: PLS-215: Beschränkungen der Zeichenfolgelänge nur im Bereich (1
..32767)
ORA-06553: PLS-215: Beschränkungen der Zeiche

-------------
(3).
--!!  INSERT
--!!    INTO VERMOEGENSUEBERSICHT_W_ORIG
--!!  SELECT X030_FONDNR AS FONDSNR,
--!!         X030_DATUM AS DATUM,
--!!         'WPV' AS VERM_ART_ID,
--!!         'WPI' AS KAT_ID,
--!!         'AKT' AS POS_ID,
--!!         0 AS KURSWERT
--!!    FROM FFFRV.V3_XS030_V0@VIEE.PRODUKTION.UNIX_RZ.TBT
--!!   WHERE (X030_FONDNR IN (SELECT FONDSNR
--!!                            FROM BEWERTUNGSFONDS_W_ORIG))
--!!     AND X030_DATUM = DDATUM
--!!   GROUP BY
--!!         X030_FONDNR,
--!!         X030_DATUM;

          Followup:

unless i have something I can run myself -- with create tables -- the whole
thing -- i cannot really comment.

you should be able to get this down to "just the insert" shouldn't you?

      Rating: 3
To the
      "reader"  September 12, 2003
Reviewer:
      eugen
Hi,

Maybe you're hitting the Bug-2526424. It's reported for Solaris, but you never
know...

Regards,
Eugen

          Followup:

looks like a good candidate -- but to be sure, we would actually need that tiny
reproducible test case.  we've no idea what the remote object is or looks like.

(test cases are oh so important...)

      Rating: 5
form
      compilation error  September 27, 2003
Reviewer:
      atul  from India
Hi,

While compiling a form we are getting following error

0509-036 cannot load program f45convert because of following errors     0509-025
the /usr/lib/libdl.a is not executable or not in                 correct xcoff
format
0509-022 cannot load library libdl.a
0509-026 system error cannot run a file that does not have a valid format

Could you kindly tell me cause of error..
Is anything missing in PATH???
we are using AIX

Thanks,
Atul

          Followup:

goto metalink.oracle.com, log in and see support note:

Article-ID:         <Note:1048338.6>

      Rating:
      5
Thanks..  September 28, 2003
Reviewer:
      atul  from India
Hi Tom,

I searched metalink for the mentioned site..

It said:
1. Change locations to the "<product>/lib" directory         % cd
$ORACLE_HOME/<product>/lib
 2. Relink the product         % make -f ins_<product>.mk install

1)I want to know what is "product" there and how i know what i have to use
there..

2)Also to relink should i use root passowrd or through oracle software owner..

3)Also should i have to give LIBPATH and LD_LIBRARY_PATH in PATH varaibale..

4)If my database on sepatare node and if i installed forms 4.5 on separate
node..should i run f45convert on the node where forms is installed without
having linking to database?

Thanks,
Atul

          Followup:

please contact support for this sort of question (you were just there!)

it is their support note.  there are some caveats list about developer stating
that relinking might not be possible and that putting the proper libdl.a back is
the correct approach.

I don't admin any AIX boxes and don't run forms myself and this is such an old
version of forms... Please follow through with support on this one

September 29, 2003 potential -- Thanks for the question regarding
      "audit trail questionc", version 8.1.7

      You Asked
Tom,

Another question regarding Java/Oracle application.

The application needs to have some high level audit trail on top of table level
auditing implemented by trigger.

So I created an audit_trail table:

create table audit_trail
  ( change_id number,
    change_type number,
    change_subtype number,
    user varchar2(40),
    change_date date,
    change_sys_description varchar2(300),
    change_user_comment varchar2(300),
    change_status char(1),
    ip_address varchar2(15)
  );

A Java function updateProdStatus which calls stored proc p_updateProdStatus, I
want to write an audit function which will insert a record into audit_trail
table whether it's successful or not. The change_sys_description field needs to
populated as 'item x changed from a to b and item y changed from c to d'.

1) I am not sure where to put this audit function, in the Java layer or stored
proc layer? User and ip_address are obtained in the Java layer.

2) What is the best approach to get the initial value during the update? I need
to record the final value as well as the initial one.

3) If the updating ProdStatus is successful, inserting to the audit_trail table
seems to be straightforward. If it fails, I don't know how the failed change is
logged to the audit table. Kinda of thinking autonomous transaction can do
something here. But not sure how to implement.

Could you explain this with an example?

      and we said...
This should be a stored procedure.

procedure do_update( p_user, p_ip_address, other data..... )
is
begin
    update ....;
    log_success
exception
    when others then
        log_failure
        RAISE;
end;

and log_failure would be coded as an autonomous transaction so it can log the
error and commit without destroying the callers transactional context.

procedure log_failure( .... )
is
   pragma autonomous_transaction;
begin
   insert into error log;
   commit;
end;
/

Review &
      Followup

      Rating:
      5
  September 28, 2003
Reviewer:
      potential
Excellent answer!

What's your take on "The change_sys_description field needs to populated as
'item x changed from a to b and item y changed from c to d'. What is the best
approach to get the initial value during the update? I need to record the final
value as well as the initial one."

Thanks again.

          Followup:

guess that would be a trigger -- only other option is to actually select for
update all of the data, then do an update "returning".

the trigger can put into a plsql package variable the string you want to record.

September 29, 2003 Pushparaj -- Thanks for the question regarding
      "Storing Chinese Characters", version 9.2.0

      You Asked
Tom,

We have a new requirement where we need to store Chinese Characters
in the database, diplay it and print it. This affects only one table
in the database. Only one table will store the chinese characters.

Currently our NLS_CHARACTERSET is set to WE8ISO8859P1.
The chinese is a MULTIBYTE character and only UTF8 can handle this.

My question is:
Is it possible to keep the NLS_CHARACTERSET=WE8ISO8859P1 as it is now
and only set the NLS_NCHAR_CHARACTERSET to handle the mulibyte
and store the chinese characters in a NCLOB or NVARCHAR2 data types. Currently
we do not have any other NCHAR datatype columns in our database.
Or
Do we need to create a separate instance with UTF8 characterset for
this purpose.

Following is current setup in our environment.

SQL> select * from nls_database_parameters;

PARAMETER                      VALUE
------------------------------ ----------------------------------------
NLS_LANGUAGE                   AMERICAN
NLS_TERRITORY                  AMERICA
NLS_CURRENCY                   $
NLS_ISO_CURRENCY               AMERICA
NLS_NUMERIC_CHARACTERS         .,
NLS_CALENDAR                   GREGORIAN
NLS_DATE_FORMAT                yyyy-mm-dd.hh24.mi.ss
NLS_DATE_LANGUAGE              AMERICAN
NLS_CHARACTERSET               WE8ISO8859P1
NLS_SORT                       BINARY
NLS_NCHAR_CHARACTERSET         AL16UTF16
NLS_TIME_FORMAT                HH.MI.SSXFF AM
NLS_TIMESTAMP_FORMAT           DD-MON-RR HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT             HH.MI.SSXFF AM TZH:TZM
NLS_TIMESTAMP_TZ_FORMAT        DD-MON-RR HH.MI.SSXFF AM TZH:TZM
NLS_TIME_FORMAT                HH.MI.SSXFF AM
NLS_COMP                       BINARY
NLS_TIMESTAMP_FORMAT           DD-MON-RR HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT             HH.MI.SSXFF AM TZH:TZM
NLS_TIMESTAMP_TZ_FORMAT        DD-MON-RR HH.MI.SSXFF AM TZH:TZM
NLS_LENGTH_SEMANTICS           BYTE
NLS_NCHAR_CONV_EXCP            FALSE
NLS_RDBMS_VERSION              9.2.0.3.0
NLS_SAVED_NCHAR_CS             WE8ISO8859P1

Please give us your suggestion.

Thanks
Pushparaj

      and we said...
thats what the nchar stuff is for, yes.  so you can store two in one.

have you read through:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-GLO
it'll be useful to you.
Review & Followup

      Rating: 4
can do
      it  June 06, 2003
Reviewer:  Reader

You already have NLS_NCHAR_CHARACTERSET  is set as AL16UTF16 as can be seen from
the parameters that you have displayed. Now you can create a table with datatype
NCHAR or NVARCHAR in the same instance and will be able to store foreign
characters provided your client termional is configured to enter chinese
characters.

      Rating: 3
Chinese
      character experience  June 06, 2003
Reviewer:
      Ya-Ping  from Plainsboro, NJ USA
When creating the database using the UTF8 character set, the Chinese character
can be stored in and extracted from the varchar2 data type column.

      Rating: 4
Can't load
      Chinese characters  September 27, 2003
Reviewer:
      Sanjay Jha  from Toronto
Tom,
I need your urgent help.

SQL>  SELECT * FROM NLS_DATABASE_PARAMETERS WHERE PARAMETER LIKE
'%CHARACTERSET';

PARAMETER                      VALUE
------------------------------ ----------------------------------------
NLS_CHARACTERSET               UTF8
NLS_NCHAR_CHARACTERSET         UTF8

SQL> drop table test
  2  ;

Table dropped.

SQL> create table test (a varchar2(20), b varchar2(1000), c varchar2(10));

Table created.

SQL> select * from test;

no rows selected

I have an excel spreadsheet with few columns having Chinese characters mixed
with English. I saved the file as Unicode text file. When I load, it loads
garbage in the database.

I have taken a sample from that and tried to load in this table.
Here is what it looks in my data file to be loaded using SQL*Loader, (when
looked in Word or Internet Browser):
Sanjay,CBV - &#29305;&#35768;&#19994;&#21153;&#20272;&#20540;&#24072;,tch

(Wow! I am surprised here! When I typing this,it looks Chinese Character, but
when I preview, it looks like what you are seeing there...what is that all
about?)

I load them using the SQL*Loader:
LOAD DATA
INFILE 'C:\test.txt'
BADFILE 'C:\test.bad'
DISCARDFILE 'C:\test.dsc'

INTO TABLE "TEST"
INSERT

FIELDS TERMINATED BY ','

  (A
,
   B
,
   C
)
SQL*Loader: Release 8.1.7.0.0 - Production on Sat Sep 27 01:41:45 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Control File:   C:\test.ctl
Data File:      C:\test.txt
  Bad File:     C:\test.bad
  Discard File: C:\test.dsc
 (Allow all discards)

Number to load: ALL
Number to skip: 0
Errors allowed: 50
Bind array:     64 rows, maximum of 65536 bytes
Continuation:    none specified
Path used:      Conventional

Table "TEST", loaded from every logical record.
Insert option in effect for this table: INSERT

   Column Name                  Position   Len  Term Encl Datatype
------------------------------ ---------- ----- ---- ---- ---------------------
A                                   FIRST     *   ,       CHARACTER
B                                    NEXT     *   ,       CHARACTER
C                                    NEXT     *   ,       CHARACTER

Record 2: Rejected - Error on table "TEST", column B.
Column not found before end of logical record (use TRAILING NULLCOLS)

Table "TEST":
  1 Row successfully loaded.
  1 Row not loaded due to data errors.
  0 Rows not loaded because all WHEN clauses were failed.
  0 Rows not loaded because all fields were null.

Space allocated for bind array:                  49536 bytes(64 rows)
Space allocated for memory besides bind array:        0 bytes

Total logical records skipped:          0
Total logical records read:             2
Total logical records rejected:         1
Total logical records discarded:        0

Run began on Sat Sep 27 01:41:45 2003
Run ended on Sat Sep 27 01:41:48 2003

Elapsed time was:     00:00:02.36
CPU time was:         00:00:00.03

Question: Why it says that it rejected  one row, when there was only one row in
the file? Also why there is some garbage before the first column value "Sanjay"?

SQL> col a format a15
SQL> col b format a30
SQL> select * from test;

A               B                              C
--------------- ------------------------------ ----------
ÿþS a n j a y    C B V   -   yr¸¿N¡R0O<P^     t c h

SQL>

Then I look using non-graphical SQL*Plus, I set NLS_LANG to .ZHT16BIG :
C:\>SET NLS_LANG=.ZHT16BIG5

C:\>SQLPLUS CI_USER/CI_USER@CICA

SQL*Plus: Release 8.1.7.0.0 - Production on Sat Sep 27 01:48:16 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to:
Personal Oracle8i Release 8.1.7.0.0 - Production
With the Partitioning option
JServer Release 8.1.7.0.0 - Production

SQL> SELECT * FROM TEST;

A
----------------------------------------
B
--------------------------------------------------------------------------------

C
--------------------
íHíHS a n j a y
 C B V   -   yríHíH&#8594;NíHR0O<^
 t c h

SQL>

I have refered almost all the documents and help available on internet, metalink
and your site as well, but no help.
The closest document for this on metalink is 143783.1, but it did not help.
Must mention that using Java Program through browser we can insert and retrieve
correctly the Chinese characters in this database, but when I viewed the
inserted data above, it shows garbage.
Where is the problem? Please help.

Regards

          Followup:

it is all about fonts and what charactersets the clients have.  thats what
happened with the chinese characters here -- my browser would not have been able
to render chinese at all.  my database cannot store it (i use an 8 bit
characterset)

your client probably had the wrong characterset -- you can specificy it in the
environment (registry if you use windows) or even in the sqlldr control file if
you like (there is a section on loading unicode data in the server utilities
guide).

java by default sets the characterset of the client to a unicode one.

so, check the NLS_LANG of your client.

      Rating: 2
Tom, what is
      this OTN doc saying?  September 28, 2003
Reviewer:
      A reader
Tom,
While I am still struggling with the problem, I forgot to mention in my question
that I am using Windows 2000 Professional. So came across the OTN Doc at :

http://otn.oracle.com/products/oracle8i/htdocs/faq_combined.htm#characterset10
************************************************************
How do I load multilingual data into an Oracle UTF-8 database 8.1.6, from
Windows 2000 client?
Unicode has two forms: fixed-width UCS-2 form (or its superset UTF16) and
variable-width UTF-8 form. Windows 2000 support UCS-2. Oracle supports UTF-8 in
all components and UCS-2 in OCI, Pro*C OLE/DB and ODBC.
You have three options:

1. Prepare your data in UCS-2 under Windows 2000 and use a custom or third party
converter to convert UCS-2 to UTF-8. Then you may use SQL*Plus or SQL*Loader to
load the data, provided you set NLS_LANG in Registry to AMERICAN_AMERICA.UTF8.

2. Prepare your data in UCS-2 under Windows 2000 and write your own loader using
OCI, Pro*C or ODBC (>=8.1.5.5). See documentation.

3. Use Oracle Forms, if you want to enter data manually. If you set NLS_LANG to
UTF8, Oracle Forms will automatically convert UCS-2 (coming from Windows) to
UTF-8 (sent to RDBMS).

***********************************************************
My questions in this context are:
1. Is it true that on W2K platform I will not be able to load Chinese characters
in a straightforward manner, using sqlldr?
2. Can you tell me any ucs-2 to utf8 conversion tool that you know of?
3. Could you help me in writing a loader program using OCI, Pro*C or ODBC
(>=8.1.5.5) ?
4. Also I was reading at
http://www.unicode.org/iuc/iuc18/papers/b1.ppt
where they mention about New Unicode features in Oracle9i
and mention following-

Character semantics support for text column
Reliable Unicode datatype as NCHAR
UTF-16 support for Oracle Call Interface(OCI)
Complete Unicode support for ODBC/OLEDB /JDBC
Unicode and ISO14651 based multilingual sort
Unicode enabled Oracle utilities such as SQL*Loader
Unicode based locale builder for locale customization

My question is, does Oracle 8i SQL*Loader not have Unicode enabled Oracle
utilities?

Thanks for your help, much appreciated.

Regards

          Followup:

I never dealt with either of Windows or Chinese characters myself.

I do know that sqlldr (written in OCI) is capable of loading chinese.  It is all
about how you set your client character set.

September 29, 2003 Ted -- Thanks for the question regarding "Issues
      with rebuilding indexes", version 8.1.7.2

      You Asked
Tom,

Is there or have there been problems with the Oracle index "rebuild"?  I started
to use one of your methods to insert data into a reporting instance (see link
below).  The DBA staff has said that the index rebuild is not a reliable
feature.  Some of the issues I have been told about index rebuild are: do not
always rebuild correctly, sometimes recreated not in the same tablespace, index
extent sizes changed, requires 2 times the space to recreate and may run out of
tablespace, takes longer to rebuild than creating an index.  Were there any
issues or are there issues with rebuild?

http://asktom.oracle.com/pls/ask/f?p=4950:8:383677::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1669403535933,%7Brebuild%7D%20and%20%7Bnologging%7D
Thanks in advance.
Ted

      and we said...
1) tell me of a case where they do not rebuild correctly?

2) tell me of a case where the tablespace for an index gets switched (and that
its "arbitrary", sometimes it does, sometimes it doesn't)?  One of the FEATURES
of the rebuild is that you can move an indexes tablespace.  In any case, just do
an "alter index idxname rebuild tablespace tablespace_name"

3) your DBA should be using locally managed tablespaces with uniform extent
sizes -- hence the size of extents is totally not relevant.  They would be
ignored.  Even so -- if they insist for some reason on using dictionary managed
tablespaces, they can:

  alter index idxname rebuild tablespace tspace_name storage ( .... )

and we can even query all we need from the data dictionary itself to build the
statement on the fly.

4) two times the space is not 100% accurate -- you are adding more data and that
data will never be in the original index.  True, as you rebuild an index, we'll
have space for the OLD and the NEW momentarily, then the old goes away.  If you
don't have the extra room for this -- your out of space anyway aren't you (you
would be cutting it a bit close).

You do not need 2x the size of your INDEX tablespace -- you only need enough
space to hold all of your indexes (no kidding) PLUS enough extra space to hold
one copy of your LARGEST index.  It is not 2x<index space>, it is 1x<index
space> Plus size of biggest index.

5) never heard of it taking longer.  same amount in this case would be expected
as it cannot really use the unusable index to rebuild with.

Hey  -- in the end, its up to them.  If they want to drop and recreate -- go for
it.  Me, I'm a paranoid guy.  I know what a missing index will do to my system.
I've seen it.  I don't want it.  I'd rather have apps break due to the unusable
indexes (due to a failed reload) then to have then run without indexes for a
while (bring everything else to a grinding halt)

Here is an example.  Shows that they tend to rebuild in their proper tablespaces
(my default is users which I have as locally managed, i used SYSTEM which is
dictionary managed) with normal extents and take about the same amount of time
as a total recreate:

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

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx1 on t(object_id)
tablespace system;

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx2 on t(object_name)
tablespace system;

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx3 on t(owner, object_name,
object_id ) tablespace system;

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select index_name, tablespace_name,
initial_extent, next_extent from user_indexes where index_name like 'T_IDX_';

INDEX_NAME                     TABLESPACE_NAME                INITIAL_EXTENT
NEXT_EXTENT
------------------------------ ------------------------------ --------------
-----------
T_IDX1                         SYSTEM                                  16384
  65536
T_IDX2                         SYSTEM                                  16384
 122880
T_IDX3                         SYSTEM                                  16384
 155648

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter index t_idx1 unusable;

Index altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter index t_idx2 unusable;

Index altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter index t_idx3 unusable;

Index altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set skip_unusable_indexes=true;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from
all_objects;

17222 rows created.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from
all_objects;

17222 rows created.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> variable n number
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec :n := dbms_utility.get_time

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter index t_idx1 rebuild nologging;

Index altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter index t_idx2 rebuild nologging;

Index altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter index t_idx3 rebuild nologging;

Index altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line(
(dbms_utility.get_time-:n) || ' hsecs to rebuild' );
1514 hsecs to rebuild

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select index_name, tablespace_name,
initial_extent, next_extent from user_indexes where index_name like 'T_IDX_';

INDEX_NAME                     TABLESPACE_NAME                INITIAL_EXTENT
NEXT_EXTENT
------------------------------ ------------------------------ --------------
-----------
T_IDX1                         SYSTEM                                  16384
 155648
T_IDX2                         SYSTEM                                  16384
 270336
T_IDX3                         SYSTEM                                  16384
 335872

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> drop index t_idx1;

Index dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop index t_idx2;

Index dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop index t_idx3;

Index dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> variable n number
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec :n := dbms_utility.get_time

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx1 on t(object_id) nologging
tablespace system;

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx2 on t(object_name)
nologging tablespace system;

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx3 on t(owner, object_name,
object_id ) nologging tablespace system;

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line(
(dbms_utility.get_time-:n) || ' hsecs to rebuild' );
1529 hsecs to rebuild

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select index_name, tablespace_name,
initial_extent, next_extent from user_indexes where index_name like 'T_IDX_';

INDEX_NAME                     TABLESPACE_NAME                INITIAL_EXTENT
NEXT_EXTENT
------------------------------ ------------------------------ --------------
-----------
T_IDX1                         SYSTEM                                  16384
 155648
T_IDX2                         SYSTEM                                  16384
 270336
T_IDX3                         SYSTEM                                  16384
 335872

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

Review &
      Followup

      Rating: 5
Send the DBAs
      on courses or get news ones !  January 09, 2002

Reviewer:  Jim  from australia
Ted,
    Sounds like your DBAs need some training or you need
new ones. Tom made a VERY important note about
losing indexes.

One of the most common performance problems I fix for clients is a result of
missing indexes.

I have seen indexes go missing during a drop and create by many DBAs but I have
never had any go missing using the rebuild option.

Missing indexes can cause dramas and VERY VERY unhappy
users and management ... although it makes me look
really good when I can resolve a performance problem
very rapidly whilst their DBAs run for cover

      Rating: 3
Issues with
      rebuilding   January 09, 2002
Reviewer:
      Tony  from Australia
This is regarding point 4.
I started using rebuilding indexes since 8.0.4 (NO LMTS). But reserving space
for old and new became a big problem for me. The reason was that my PL/SQL
package (with number of procedures) was automatically dropping the oldest
partition and creating a new partition (rolling window), as such all global
indexes became UNUSABLE, which is understandable. But when I started rebuilding
them in the same package (different procedure - get all UNUSABLE indexes one by
one and rebuild), the script start falling apart because of space issues. On
following the problem with Oracle support, the answer was that in the same
session though the new index is built, the space to be released by old index is
not released till you come out of the session. Is it still true for 8i?

          Followup:

It is not true for the SESSION:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t as select * from all_objects;
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from t;
17220 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from t;
34440 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from t;
68880 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop tablespace test_idx;
Tablespace dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> host rm -f /tmp/test_idx.dbf

ops$tkyte@ORA817DEV.US.ORACLE.COM> create tablespace test_idx
  2  datafile '/tmp/test_idx.dbf' size 512k
  3  autoextend on
  4  next 512k
  5  extent management local uniform size 64k;
Tablespace created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx1 on
t(owner,object_name,object_type,timestamp,status,created,last_ddl_time)
  2  nologging tablespace test_idx;
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx2 on
t(object_name,owner,object_type,timestamp,status,created,last_ddl_time)
  2  nologging tablespace test_idx;
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx3 on
t(object_name,object_type,owner,timestamp,status,created,last_ddl_time)
  2  nologging tablespace test_idx;
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx4 on
t(object_name,object_type,timestamp,owner,status,created,last_ddl_time)
  2  nologging tablespace test_idx;
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx5 on
t(object_name,object_type,timestamp,status,owner,created,last_ddl_time)
  2  nologging tablespace test_idx;
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> @free 1
ops$tkyte@ORA817DEV.US.ORACLE.COM> -- free.sql
ops$tkyte@ORA817DEV.US.ORACLE.COM> --
ops$tkyte@ORA817DEV.US.ORACLE.COM> -- This SQL Plus script lists freespace
....

             %
                                                             %
MaxPoss    Max
Tablespace Name        KBytes         Used         Free   Used      Largest

---------------- ------------ ------------ ------------ ------ ------------
...
*TEST_IDX              72,192       72,064          128   99.8          128
.....
14 rows selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter index t_idx1 rebuild nologging;
Index altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter index t_idx2 rebuild nologging;
Index altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter index t_idx3 rebuild nologging;
Index altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter index t_idx4 rebuild nologging;
Index altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter index t_idx5 rebuild nologging;
Index altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> @free 1
                                                             %
MaxPoss    Max
Tablespace Name        KBytes         Used         Free   Used      Largest
 ---------------- ------------ ------------ ------------ ------ ------------
*TEST_IDX              86,528       72,064       14,464   83.3       14,400

so, there the tablespace did not double.

Now, if I do it in a single call, eg:

begin
   execute immediate 'alter index t_idx1 rebuild ...';
   ...
   execute immediate 'alter index t_idx5 rebuild ...';
end;
/

then yes, you are correct, the space is not available to be reused until the
completion of the call.

      Rating: 5
Early
      iterations of "REBUILD ONLINE"  January 10, 2002

Reviewer:  Connor  from Battersea
The DBA in question may have been scared off by early versions of 'rebuild
online' which could be a little flakey (Metalnk Note:125149.1 being an example).

But I've never seen issues with plain ol' rebuild.

hth
Connor

      Rating:
      4
review  October 07, 2002
Reviewer:
      Irfan  from Kuwait
I am using Locally managed tablespace oracle 9i. Recently
we inserted massive amount of data in the tables. We have
indexes associated with lot of tables index and tables are in the same
tablespace.

Question 1. Is it nessary to rebuild indexes every time we
             insert large amount of data?
Question 2. What is the full syntex of the command to rebuild the indexes?

Thanks

          Followup:

question 1) NO

question 2) believe it or not, we actually document this stuff:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/statements_18a.htm#2086990
in addition to this command, you'll find the full syntax for all commands!

      Rating:
      4
Review  October 07, 2002
Reviewer:
      Irfan  from Kuwait
Thanks for reply, I hope that I am not irritating you. One last question.
So, Index rebuild is not required after a large amount of
insert or delete. Then in what circumstances do we actually
rebuild or recreate the indexes.

Thanks and regards.

          Followup:

Bitmap indexes -- more so then b*tree.

B*tree -- so very infrequently.....  Lots less them many people might say (me, I
have bigger fish to fry then rebuilding indexes on a recurring basis)...

I use it mostly to MOVE them from place to place.

      Rating: 5
rebuild index
      bypass cache?  October 08, 2002
Reviewer:
      A reader
Hi

When we rebuild index do we use buffer cache at all? If we do if we rebuild with
nologging option will we bypass buffer cache and write them directly to
datafiles like direct load?

          Followup:

the index is rebuilt to temporary extents which will be written directly after
being cached for a bit (sort areas and such).

it'll not "bypass" the cache, it'll read from the cache when necessary but the
index itself is written to disk directly regardless of logging/nologging.

      Rating: 4
rebuilding
      indexes after massive load, dropping indexes  October 09,
      2002
Reviewer:  Andrew  from Calgary, AB, Canada

Tom,

but when one loads a lot of data in the hevaily indexed table... isn't it
sometimes advantageous to drop all indexes, load the data and recreate indexes
again? because inserting a record in an indexed table will require the
modification to an index as wwell and if there are millions of records then the
overhead becomes bigger then rebuilding an index.

and of course the missing index is probably the single most common reason for
performance troubles... But indexes don't disappear on its own. DBA has to be
meticulous, have to run monitoring scripts daily, have to READ those reports
daily, etc.

          Followup:

sure it is. it can be much faster (in fact, i demonstrated how to load into the
table without maintaining the indexes -- they would need a rebuild then).  that
was much faster then maintaining the indexes during the load.

if you use the "unusable" trick -- you don't need to look for what is NOT THERE
(hey, if you aren't here, raise your hand).  But you can easily look for things
that are there but UNUSABLE

but -- you do not necessarily need to rebuild after a load into an indexed
table.

      Rating: 4
Index
      rebuild  October 11, 2002
Reviewer:  kiro

Hi Tom
Great job, but I have still one missing point.
Q1)When rebuild index extra space for new index is taken from INDEX TABLESPACE
or from TEMP?
Q2)And for partitioned(local and global) index - if we rebuild entire index when
system releases old space after every index partition was rebulid or when entire
index was rebuild?
thanks
kiro

          Followup:

q1) from the tablespace the index will ultimately reside in.  We allocate a temp
segment in that tablespace -- put the index into it -- when done turn that
"temp" segment into an "index" segment (better then building the thing in TEMP
and them moving temp to "real", less IO and you need the space in the real
tablespace anyway.

q2) after the entire process is done -- unless you rebuild the individual
partitions that is

      Rating:
      5
  October 18, 2002
Reviewer:  kiro

Thanks Tom!

      Rating: 5
please expand
      on statement  December 04, 2002
Reviewer:
      David  from Nashville
Tom,

Great thread.  In your followup to "Andrew, from Calgary" you stated, "but --
you do not necessarily need to rebuild after a load into an indexed table".
Could you expand on this (i.e. when would you not?).

Inserting a million rows into our 70 mill row table took 22 seconds (!!!) when
dropping indexes before loading.  It used to take 5 hours.  However, I'm not
clear on the best process to use (drop/recreate or unusable/rebuild).

          Followup:

You would not rebuild if you never disabled them.  You need not disable indexes
in order to load.

You might do a direct path load -- it'll maintain indexes for us at the end.  No
rebuild needed.

You might be adding 100,000 rows to a 10,000,000 row table.  I would not disable
indexes for that.  No rebuild needed.  Adding 1,000,000 rows to a 10,000,000 row
table -- I would give it some serious thought (to disable, load, enable)

I like the unusable/rebuild for reasons stated above (the gist of the question
was this point exactly)

      Rating: 5
further
      clarification  December 04, 2002
Reviewer:
      David  from Nashville
So, there are two good ways to approach a big load:

1. Mark index unusable
2. insert /*+ append */ into big_table
3. alter index rebuild? or mark index usable?  If I don't rebuild will it have
the new rows indexed?

*OR*

1. Drop index
2. insert /*+ append */ into big_table
3. Recreate index
This is what I am doing now, but the index creation takes almost three hours.

Am I on track?

          Followup:

there are an infinite number of good ways.  two is way too limiting.  And there
are SO many things to consider that I cannot even begin to tell you which way to
go without knowing your circumstances.  Anyway...

if you set the index unusable -- you'll need to rebuild it (else they stay
unusable -- not maintained -- forever)

the other one is the one I don't recommend for obvious reasons

But -- look, the rebuild will take 3 hours as well.  Consider this -- it would
take many times LONGER to load it with the index on see.

You can speed up the index rebuild/create via:

o parallel
o nologging (in an archive log mode database)

      Rating: 5
more
      detail  December 04, 2002
Reviewer:  David
       from Nashville
I am doing parallel and nologging.  It seems that the slow part is a sort that
takes place as the unique index is being built.  I'm thinking this because when
I monitor v$session_longops I see a message of "sort output x blocks of [about]
425,000 blocks".  Also, v$session_waits shows a "db file scattered read" for
this SID - sometimes as high as 15 seconds.  Any thoughts on speeding this part
up?

          Followup:

increase sort_area_size if not already.

increase db_file_multi_block_read_count, if not at OS limits already.

make sure the data you are reading is spread out across multiple devices (else
your PQ might just be introducing massive disk contention).

speed up your disk access -- db file scattered read is sequential IO (believe it
or not).  we are doing big reads and are being made to wait for them.  15
seconds -- that must be cumulative?  I cannot imagine waiting 15 seconds for a
disk read to return unless the disks are totally botched.

      Rating: 5
how the inded
      get missed during index rebuild  December 05, 2002

Reviewer:  Ashiq  from Trichy, TN,India
Hello Tom,

 From ur comment ,ie "I know what a missing index will do to my system"

 How are index get missed up during rebuild.What is the reasong for that and
what problem it'll cause for our application???

          Followup:

You missed the point.

If I use rebuild (meaning i set them unusable), I cannot lose them.  I cannot
accidently have one "go missing".  People will call and say "hey, I'm getting a
strange error about index such and such being unusable -- whats up with that".

If I use DROP / CREATE -- what will happen if the DROP succeeds, but the create
fails for whatever reason (script bombs out, out of temp, whatever).  Index has
gone missing -- no one checks the script log -- and then you spend the next day
or two trying to figure out why performance has gone down the tubes ("oh they
say the next day, we 'lost' an index").

So, if you use ususable/rebuild -- you'll not lose an index, you'll be told your
script bombed, you fix it and the system runs smoothing (except for that minor
annoyed person who hit your error)

If you use drop/create -- you'll lose an index someday (maybe many days).
You'll not be told your script bombed.  Your users will just experience slower
response times (if they get the answer back at all), you'll be flooded with
calls about "slowness" and then hopefully you can actually track down the index
that has gone missing this time.

      Rating:
      5
Temp  December 05, 2002
Reviewer:
      Martin Guillen  from Argentina.
Hi tom:
Recalling from this thread : ..."We allocate a temp
segment in that tablespace -- put the index into it -- when done turn that
"temp" segment into an "index" segment (better then building the thing in TEMP
and them moving temp to "real"...

Actually temp space is used (for sorts or something like that, I think):

PEPE> create table u (a int, b varchar2(100));

Table created.

  1  begin
  2  for i in 1..1000000 loop
  3  insert into u values (i, '*');
  4  end loop;
  5* end;
PEPE> /

PL/SQL procedure successfully completed.

PEPE> desc u;
 Name   Null?    Type
 ------ -------- --------------------------------------------
 A               NUMBER(38)
 B               VARCHAR2(100)

PEPE> create unique index u_idx_uni on u(a);

Index created.

SQL> create temporary tablespace TEMP_TINY tempfile
'/opt/oracle/oradata/dbstart/TEMP_TINY.DBF' size 2M autoextend off;

Tablespace created.

SQL> select file_name, bytes/1024/1024 "MB" from dba_temp_files;

FILE_NAME                                   MB
------------------------------------------- ----------
/opt/oracle/oradata/dbstart/TEMP.DBF        100
/opt/oracle/oradata/dbstart/TEMP_TINY.DBF   2

SQL> alter user pepe temporary tablespace temp_tiny;

User altered.

PEPE> select count(*) from u;

  COUNT(*)
----------
   1000000

PEPE> alter index u_idx_uni rebuild;
alter index u_idx_uni rebuild
*
ERROR at line 1:
ORA-01652: unable to extend temp segment by 128 in tablespace TEMP_TINY

PEPE> drop index u_idx_uni;

Index dropped.

PEPE> create index u_idx_uni on u(a);
create index u_idx_uni on u(a)
                          *
ERROR at line 1:
ORA-01652: unable to extend temp segment by 128 in tablespace TEMP_TINY

SQL> alter user pepe temporary tablespace temp;

User altered.

PEPE> create unique index u_idx_uni on u(a);

Index created.

My question is: How much space in temp is needed for index rebuild/create?
Thank you,
Martin.

          Followup:

sure, it depends on your sort_area_size totally.

      Rating: 5
wait
      time  December 11, 2002
Reviewer:  David
       from Nashville
Regarding your followup of my question above...

The seconds_in_wait column of v$session_wait had 15 seconds for my SID.  Is this
a cumulative value or an individual wait?  We are using raid 5.

          Followup:

look at wait_time for the last wait time

      Rating: 3
Fragmentaion of
      index in LMT  March 04, 2003
Reviewer:
      A reader
Hi Tom,
In our database all the index and data tablespaces are LMT tablespaces. We are
aware that the LMT tablespaces doesnt have fragmentation. But I have read some
article, stating that the index objects will have holes while there are frequent
deletions in data, and hence they are fragmented and the only way to correct
them is to rebuild the indexes. Even the indexes are in the LMT this will
happen.

So just correct me whether my understanding is correct or not.

As my indexes are in LMT, so if I rebuild them, will there be any use out of
that?

          Followup:

read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6601312252730
it is the extremely rare index that needs to be rebuilt.  Ignore the myths that:

o index space is not reused (it is all of the time)
o indexes on sequences are always 50% empty (they are not, you will find them to
be very full actually)
o indexes need to be rebuilt on a schedule (not)

      Rating: 4
Multi-Org
      Environments  September 04, 2003
Reviewer:
      Matt  from UK
Tom,

We are running Oracle Apps multi-org with fine grained access control  - a
decision has been made for some of our batch routines to partition the load
tables on organization.

Problem is we currently have global indexes (based on the initial
pre-partitioned tables) which become unusable when truncate of the partition
occurs. Obviously if we are truly multi-org ( which actually is unlikely ) and
want to have multiple batch processes running at the same time we want to avoid
this.

Should we be considering converting all the global indexes to local - bearing in
mind we'll have sys_context appended to all queries anyway.

Cheers,

Matt

          Followup:

depends on how you use the indexes -- if you are 100% sure that the local
indexes would ALWAYS partition prune -- local is OK.  if not, global is sort of
needed to avoid having to range scan N index partitions for each query.

9i allows you to do partition operations and maintain global indexes (they don't
have to go unusable)

      Rating: 5
Rebuild all
      indexes of a table  September 28, 2003
Reviewer:
      A reader
Hi tom,

How could I rebuild all indexes for a table? Why there is no such command as
"alter table X rebuild indexes"?

          Followup:

begin
  for x in ( select index_name from user_indexes where table_name = 'T' )
  loop
     execute immediate 'alter index ' || x.index_name || ' rebuild';
  end loop;
end;
/

September 29, 2003 deniz -- Thanks for the question regarding
      "Serializable transaction", version 8.1.6

      You Asked
Hello,

My question is about serializable transaction. Does it
have a bad performance effect on system performance and
how is it related to the rollback segments. I mean
if I keep a session's isolation level in serializable mode
for a long time does it cause rollback segments to grow ?

Thank you,
Deniz ATA

      and we said...
It will not affect the overall system performance.

It does not cause RBS to grow anymore then READ COMMITTED will.

RBS is used by selects -- RBS is never extended on behalf of a SELECT.  Only
inserts/updates/deletes cause rollback to grow.
Review &
      Followup

      Rating: 3
Clarification
      ?  September 29, 2003
Reviewer:  Anil Pant
       from Hyderabad, India
What dou mean by "RBS is used by selects -- RBS is never extended on behalf of a
SELECT.  Only inserts/updates/deletes cause rollback to grow. "

          Followup:

hmm, three statements

a) RBS is used by selects (we use rollback data to construct read consistent
views of data.  if we hit data that was changed since the beginning of your
query, we use rollback data to "unchange it")

b) RBS is never extended on behalf of a select (meaning, a rollback segment will
not extend because a read only SELECT query is running and just "might" need
some of the existing data.  the rollback segment will wrap around if it can,
overwriting old rollback data -- that that query might need)

c) only inserts/updates/deletes cause rollback to grow (to grow = to extend.
only a modification can cause rollback to extend and only then if it needs to
extend.  the modification will just wrap around and reuse space that a select
might need if it can)

hope that helps.

September 29, 2003 Andre -- Thanks for the question regarding
      "Automating standby databases", version 8.1.6

      You Asked
Tom,

I am planning to set up a site with a primary and a standby
database. I am working on Solaris. I read your overview on
standby databases in Oracle7 at:

http://osi.oracle.com/wa/ask/owa/ask_tom_pkg.display?p_dispid=199412348073&p_search_str={STANDBY}
The problem is: I have to configure this standby database to
failover with minimal downtime and minimal manual intervention
-- or totally automated if possible. I know Oracle8i has the
improvemement over Oracle7 of sustained recovery mode, which
automates the process of transferring the archive log files.

Questions:

1) Apart from the failover situation itself, it seems to me a
major management problem is keeping the controlfile updated,
which can render the standby database useless if, for example,
the DBA simply forgets that a tablespace has been added in the
primary db. This worries me because in the daily hurry it is not
such an implausible accident. Do you have some hint as to
keeping the controlfile updated ? Maybe disk sharing or some
kind of automatic replication ? I thought of running a script
that polls the controlfile for changes and warns you in case of
modifications (it's at least some additional security).

2) In the case above, is there any workaround for not having an
updated controlfile in the standby database in the case of a
crash ? Perhaps traditional manual recovery procedures might
allow you to at least put the standby db to work, provided you
can have access to the files of the crashed primary db.

3) For the failover situation itself, I would like the standby
db to be "aware" of the failing of the primary db. Couldn't I
just run a script (maybe shell) in the standby machine that
"probes" the primary db and, in case it fails, the standby
machine would be put to work ? What OS and DB issues (events)
would I have to tackle in such a script ?

4) Another issue that seems to come up is avoiding unrecoverable
(nologging) operations in the primary db, in which case I would
have to backup and transfer the datafiles again, in addition to
re-configuring the stanby db. Are there any special (maybe
undocumented) operations I should be aware of that are
unrecoverable, in addition to the traditional nologging creates,
rebuilds, loads and inserts ? Maybe I should pay attention to
the application which might generate nologging operations
without my knowing it (this seems to be a real problem) ?

5) Unarchived redo logs in the primary db also seem to be a
problem. How can I avoid losing them and applying them to the
standby db if they are still available in the primary site ? If
I am using sustained recovery mode, is there any special
technique to do that ?

6) If you could just remember any additional items you think are
relevant for setting up this "automated" scenario, I would be
most grateful.

Thanks !

      and we said...
1) you cannot share a control file between the standby and
primary -- each has its own.  If you add a datafile/tablespace
to the PRIMARY, you must add it to the standby manually.

In an environment, where you have such stringent uptime demands
(you strive to be highly available), there can be no such thing
as "the daily hurry".  You have must have concepts of operation,
standard operating procedures, rules and regulations to follow.
You are a professionally managed data center with highly trained
staff.

Just as you can do unrecoverable operations in your database
RIGHT NOW (no standby) in the "day to day hurry" and render your
backups not 100% useful (eg: I do an unrecoverable direct path
load, allow people to modify the data and then the system fails.
 I have to RELOAD the data and lose those updates unless I took
a backup after the load since the load itself was not logged.
This would be an in-excusable error on the part of the
maintainers of the system.)

A standby database has rules you must follow (suggest you read
from cover to cover:

http://technet.oracle.com/doc/oracle8i_816/server.816/a76995/toc.htm
and understand the ramifications.  Beyond creating a tablespace,
adding a datafile, there are other operations such as a direct
path loads/unrecoverable operations, changing an init.ora
parameter, using open resetlogs and so on will affect the
operation of the standby database.  This is unavoidable and will
have to necessarily become part of your concepts of operations.

2) refer to 1.  It is a situation you cannot let yourself ever
get into in your environment.  Highly available systems demand
some attention to detail.  This is just one of the details you
cannot overlook.

3) I don't like having the standby probe the primary.  What if
the error exists in the connection between the primary and
standby BUT the primary is doing great?  You do not want the
standby to automatically take over - you could really mess
things up if the primary is actually OK and still processing
work but new connections are "failed" over the standby.  Now you
have 2 databases.  You can have a script that a human, once the
determination to fail over is made, runs to automate the
failover but having the standby make the decision on its own
would not be a good idea.  There are conditions that are easily
corrected on the primary that should be corrected on the primary
rather then failing over (eg: failed to connect due to "too many
processes" -- increase processes and restart -- don't failover).
 A human may need to be involved in this process.

4) see 1.  See the standby database documentation.  It covers in
some detail how this all works.  All operations you need be
aware of are documented in the section in chapter 4, "Monitoring
Events That Affect the Standby Database"

5) unarchived typically means they were

o active (they were the current online redo log)
o in the process of being archived

if you can "get them" you can apply them before activating the
standby.  If you cannot "get them" and activate the standby, the
data contained therein is gone.  You may be able to use log
miner after the fact to generate the SQL to redo the
transactions (but maybe not).  You can limit your exposure to
this by limiting the size of your online redo log files and/or
using a job to switch and archive all redo on a timed basis.

6) see the document referenced above.  it is much more
comprehensive then the notes for 7.3.
Review &
      Followup

      Rating: 5
Error during
      Activating  December 11, 2002
Reviewer:
      Sikandar Hayat Awan  from Pindi Bhattian - Pakistan

Dear TOM I want to implement standby database but during testing it I found the
following difficulties,

I have Oracle 8.1.7 on Win2000 box.

1- Before activating the standby db I have applied all the archived log files
even current log file but how I will get online log files physically on the
standby box. I have to create groups and member manually? When I query v$logfile
it is showing logs but as you know physically they are/were on production server
but Control file is reflecting there paths and names.
Please guide me what to do with online red logs?

2- During a test of Standby db I got following error may be due to online log
files or any other issue,

SVRMGR> recover standby database;
ORA-00279: change 12842892 generated at 12/10/2002 04:15:09 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15496.ARC
ORA-00280: change 12842892 for thread 1 is in sequence #15496
Log applied.
ORA-00279: change 12842919 generated at 12/10/2002 05:17:20 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15497.ARC
ORA-00280: change 12842919 for thread 1 is in sequence #15497
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15496.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12843640 generated at 12/10/2002 07:08:50 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15498.ARC
ORA-00280: change 12843640 for thread 1 is in sequence #15498
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15497.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12844320 generated at 12/10/2002 08:03:34 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15499.ARC
ORA-00280: change 12844320 for thread 1 is in sequence #15499
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15498.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12845279 generated at 12/10/2002 08:22:56 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15500.ARC
ORA-00280: change 12845279 for thread 1 is in sequence #15500
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15499.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12846172 generated at 12/10/2002 08:33:07 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15501.ARC
ORA-00280: change 12846172 for thread 1 is in sequence #15501
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15500.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12848592 generated at 12/10/2002 09:51:55 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15502.ARC
ORA-00280: change 12848592 for thread 1 is in sequence #15502
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15501.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12851862 generated at 12/10/2002 10:52:30 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15503.ARC
ORA-00280: change 12851862 for thread 1 is in sequence #15503
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15502.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12852748 generated at 12/10/2002 11:00:56 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15504.ARC
ORA-00280: change 12852748 for thread 1 is in sequence #15504
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15503.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12853577 generated at 12/10/2002 11:12:58 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15505.ARC
ORA-00280: change 12853577 for thread 1 is in sequence #15505
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15504.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12854413 generated at 12/10/2002 11:24:44 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15506.ARC
ORA-00280: change 12854413 for thread 1 is in sequence #15506
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15505.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12856117 generated at 12/10/2002 11:48:22 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15507.ARC
ORA-00280: change 12856117 for thread 1 is in sequence #15507
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15506.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12858100 generated at 12/10/2002 12:30:35 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15508.ARC
ORA-00280: change 12858100 for thread 1 is in sequence #15508
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15507.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12859141 generated at 12/10/2002 12:47:02 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15509.ARC
ORA-00280: change 12859141 for thread 1 is in sequence #15509
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15508.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12860028 generated at 12/10/2002 12:58:16 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15510.ARC
ORA-00280: change 12860028 for thread 1 is in sequence #15510
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15509.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12862493 generated at 12/10/2002 13:54:48 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15511.ARC
ORA-00280: change 12862493 for thread 1 is in sequence #15511
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15510.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12864870 generated at 12/10/2002 14:42:03 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15512.ARC
ORA-00280: change 12864870 for thread 1 is in sequence #15512
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15511.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12867776 generated at 12/10/2002 16:37:02 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15513.ARC
ORA-00280: change 12867776 for thread 1 is in sequence #15513
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15512.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12870199 generated at 12/10/2002 17:38:31 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15514.ARC
ORA-00280: change 12870199 for thread 1 is in sequence #15514
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15513.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12872126 generated at 12/10/2002 18:04:48 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15515.ARC
ORA-00280: change 12872126 for thread 1 is in sequence #15515
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15514.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12873246 generated at 12/10/2002 18:17:23 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15516.ARC
ORA-00280: change 12873246 for thread 1 is in sequence #15516
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15515.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12873960 generated at 12/10/2002 18:23:03 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15517.ARC
ORA-00280: change 12873960 for thread 1 is in sequence #15517
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15516.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12874968 generated at 12/10/2002 18:42:42 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15518.ARC
ORA-00280: change 12874968 for thread 1 is in sequence #15518
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15517.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12875767 generated at 12/10/2002 18:50:55 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15519.ARC
ORA-00280: change 12875767 for thread 1 is in sequence #15519
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15518.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12876630 generated at 12/10/2002 19:03:00 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15520.ARC
ORA-00280: change 12876630 for thread 1 is in sequence #15520
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15519.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12877433 generated at 12/10/2002 19:14:09 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15521.ARC
ORA-00280: change 12877433 for thread 1 is in sequence #15521
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15520.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12878246 generated at 12/10/2002 19:25:41 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15522.ARC
ORA-00280: change 12878246 for thread 1 is in sequence #15522
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15521.ARC' no longer needed for this
recovery
Log applied.
ORA-00279: change 12879179 generated at 12/10/2002 19:37:55 needed for thread 1
ORA-00289: suggestion : D:\CGLD\ARCHIVE\CGLD_15523.ARC
ORA-00280: change 12879179 for thread 1 is in sequence #15523
ORA-00278: log file 'D:\CGLD\ARCHIVE\CGLD_15522.ARC' no longer needed for this
recovery
ORA-00308: cannot open archived log 'D:\CGLD\ARCHIVE\CGLD_15523.ARC'
ORA-27041: unable to open file
OSD-04002: unable to open file
O/S-Error: (OS 2) The system cannot find the file specified.
ORA-01547: warning: RECOVER succeeded but OPEN RESETLOGS would get error below
ORA-01152: file 1 was not restored from a sufficiently old backup
ORA-01110: data file 1: 'D:\CGLD\SYSTEM01.DBF'
SVRMGR> alter database activate;
alter database activate
                       *
ORA-00905: missing keyword
SVRMGR> alter database activate;
alter database activate
                       *
ORA-00905: missing keyword
SVRMGR> alter database activate standby database;
alter database activate standby database
*
ORA-01152: file 1 was not restored from a sufficiently old backup
ORA-01110: data file 1: 'D:\CGLD\SYSTEM01.DBF'
SVRMGR>

what may be wrong with SYSTEM01.DBF?

      Rating: 5
Addition to the
      above posting  December 11, 2002
Reviewer:
      Sikandar Hayat Awan  from Pindi Bhattian - Pakistan

I overcome the error in the above post I have applied more archived files and
now when I am trying to activate the db the following error is displaying,

SVRMGR> alter database activate standby database;
alter database activate standby database
*
ORA-00344: unable to re-create online log 'E:\CGLD\REDO05.LOG'
ORA-27040: skgfrcre: create error, unable to create file
OSD-04002: unable to open file
O/S-Error: (OS 3) The system cannot find the path specified.
SVRMGR>

What should I do just drop and recreate the online red log files or any other
solution?
I hope now my problem will be quite clear to you.

          Followup:

do you have an e:\cgld?

have you read

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76995/toc.htm

      Rating: 5
More
      Detail  December 12, 2002
Reviewer:  Sikandar
      Hayat Awan  from Pindi Bhattian - Pakistan
No as I have mentioned that I have not copied online redo to the standby box so
let me know if I don't want to copy or lost on line although they are duplex.
Only names of online are in the standby setup due to standby copy of contol
file. My question is,
1- I have to copy online if available from the prod?
2- I have to create online manually but what will happen to the names coming
from the control file with v$logfile of prod online. I have to drop them first
or what to do?

I have read from the link and also standby article from Oracle Magazine Nov, Dec
2002.

Thanks for your reply.

          Followup:

if you want the last and final updates -- all of the committed work you would
copy them yes.

else, you will open the database at a prior point in time.  that is the answer
to #1

for #2, I'm assuming the PATHS are different, you don't have an e: drive or
whatever.  Read:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76995/standby.htm#23603

      Rating: 5
One more
      question  December 13, 2002
Reviewer:  Sikandar
      Hayat Awan  from Pindi Bhattian - Pakistan
I have 3 groups with 2 members each on different HD.

The question is when to create/copy online redo to the standby at the time of
activation? I am assuming that before activation the standby is without online
redo and when I want to activate I will create/copy online redo.

Let me know if I am wrong.

          Followup:

You would copy the online redo log to the standby in the event of a catastrophic
failure of the production instance -- if the disks are still available and you
want to save that data.

In 9i, LGWR is the guy responsible for shipping the redo in more "real time"
mode, hence this step isn't necessary -- it is only necessary if ARCH is
shipping the redo and ARCH hasn't shipped something.

      Rating:
      4
Reader  December 15, 2002
Reviewer:
      A reader
If you want the information in the online redo of the
PRIMARY database, Do a unix copy of the redo log to
the NEXT archive log sequence name. ( Example if
the last archived log is arch1_500.log, copy the
redo log to arch1_501.log) Ship the new archived log
to standby and apply it. I have tested it. It works

      Rating: 5
Applying the
      redo on Standby Database   May 07, 2003
Reviewer:
      Venki  from Singapore
Tom , this is what you have to say about unarchived log files on Primary Db .
(Pointer # 5)
unarchived typically means they were
active (they were the current online redo log)
in the process of being archived
if you can "get them" you can apply them before activating the standby.
************************************************************
In the event of my Primary Db going down and I still can access the last online
redo log on disk (now how do I do that when there are obviously more than one
group ,maybe timestamp) , do you think it is safe to copy it to the achive dest
on Standby and apply the same after renaming it to the next archived logname ?
This has been suggested by one person earlier in this discussion .Even if I have
small sized logs ,but they are unarchived (means current) I would still loose
data in case of a Prod database crash .How do u address this situation ? Please
share your thoughts .

      Rating: 5
Slow
      application of archive to standby  June 20, 2003

Reviewer:  Rory Concepcion  from Philippines

Hi Tom,

   I'm currently working for a new employer so I'll have a new email here. One
of our databases is operating with a standby database. Sizes of archive logs are
2gig each. They are applied to the standby database manually. But the
application of each archive log to the standby database takes more than 10(ten)
minutes (about 16mins). My database versions are 8.1.7.4. Is there a way to make
this faster to about 6 to 8 mins? Thanks.

          Followup:

2 gig logs + 8i standby = not quite the right approach.

2gig is too big, you will lose 2gig or more of redo information in a failure.
That's a TON of work.

My solution would be -- make them smaller, apply them more often -- reduce the
risk of data loss.

      Rating:
      5
  June 22, 2003
Reviewer:  Rory
      Concepcion  from Philippines
Thanks Tom. I'll try to find out more information. I think the 2gig size of redo
were approved by on site support of Oracle here in the Philippines. I agreee
with you but since I'm new here, dont know if I could convince my 2 colleagues
about making the archives smaller. By the way, the 2gig archives are produced
every 10mins. So much transactions in the db, I know. Well this is a database of
prepaid transactions. We're a mobile company servicing around 8 million
subscribers. Thanks again.

      Rating:
      5
  June 22, 2003
Reviewer:  Rory
      Concepcion  from Philippines
I was able to speak with the head of support here. And they said that it is not
really advisable but we dont have a choice. Because of the large volume of
transactions, logs are generated in less than 10mins. So archives are really
2gig in size. It might even go up to 4gig next year. Looks like we dont have a
choice. There really is a large volume of transaction. It's a very very very big
database.

          Followup:

well, how is the IO on the standby machine, same nice fast disks?

      Rating:
      5
  June 22, 2003
Reviewer:  Rory
      Concepcion  from Philippines
There isnt any problem with the disks. The company was more than generous with
it's budget so management has given the best for this system. I think the
storage device here is EMC.

          Followup:

see support notes:

Article-ID:         <Note:240875.1>
Article-ID:         <Note:240874.1>

on metalink.oracle.com.  they say "9i" but apply in part or whole to 8i
standbys.  Some features such as parallel recovery on the standby are *new* with
9i and won't be available to you.

it may be worthwhile to take a peek at 9i data guard (and 9i in general on such
a high volume system).  data guard has alot more options then the 8i standby
did.

      Rating: 3
could be the
      network?  June 23, 2003
Reviewer:  Shiva
       from UK
Rhory, log shipping 2gb across to standby is a lot.
may be a high speed network should help. fibre optic etc.
one of our primary database is shipping logs over our internal public network.
speed not being very critical we haven't taken any action. but we know that's
the cause.

cheers,
Shiv

      Rating:
      5
  June 23, 2003
Reviewer:  Rory
      Concepcion  from Philippines
Thanks Tom. We are actually in the testing of the migration of this system into
9i. It will happen this year. This is a very critical system for us. A long down
time for this and we surely will be seen in the papers. It is a system that is
revenue earning. Though the system is not operating in RAC, the database has 2
more copies in the same location but on different machines via EMC technology.
And there is an additional copy located in another location. That database copy
is using standby. But I do believe we might use the RAC. Just dont know when.
Although recently, we encountered a bug in the undo which caused one of our
newly migrated 9i databases to crash. US Oracle Support's suggestions did not
work. And the local support assissted us with whatever the US support told us to
do. The DB would open but only sys could query. Then after a while, smon would
shutdown the instance. Local support's top man was unable to resolve it too and
all we could do was recreate the DB and import the data back. I'll try to send
you the errors received in the alert. You might be interested coz it's a new bug
they say. And of course its part of the ora 600 but different code. This event
that happened yesterday was in a way a sigh to us coz at least only management
would be unable to use the newly migrated database. But what if we were already
using this with our Prepaid card system. Would it happen again? So now they are
having second thoughts using the undo management feature of 9i. thanks for the
time TOm.

      Rating: 5
standby
      database in Standard Edition of Oracle  September 29, 2003

Reviewer:  Himnish Narang  from Chandigarh, India

Hi Tom,

I have read in one of your queries that the source and target database should be
of some version, reside on the same OS. I have one query :

I am using Oracle Standard Edition (SE) 8.1.5 on Win NT. I have tried all the
steps required to implement the Stand By database. The setup was OK, but it
didn't work.

I tried the same setup on my Enterprise edition of Oracle 8.1.7 on AIX. It
worked there.

Please tell me is there any difference in implementation of Stand by database in
SE and EE. I want to automate the standby database in WIN NT environment.

Please help me.  Thanx for your time

          Followup:

automated standby databases are a feature of ENTERPRISE edition, not standard.

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76962/ch4.htm#74665

September 29, 2003 Jonathan -- Thanks for the question regarding
      "more info about rowids...", version 8.1.5

      You Asked
You told me recently :

"1) you have a partitioned table, this table has ENABLE ROW
MOVEMENT defined on it.  If you update the partition key that
causes the row to become a member of another partition, a new
rowid will be assigned to it.

2) you have an index organized table.  the rowid (really a
urowid at that point) is a function of the primary key value.
If you change the primary key, causing the row to move in the
index, the urowid will change.

I would suggest that you use the PRIMARY KEY of the table, not a

rowid.  Over time there may be other cases where the rowid will
change."

In the first case you described this is caused by some user or
administrator command. So the dba will be aware of this rowid
modification. Am I right?

In the seconc case it's only for the index table and not the
table itself. Is that it?

So apparently I will be able to know if there is a modification
in the rowids by asking to the DBA.

Anyway is there a place where I can find this information (the
modification of a data physical loction)?
in a system view, in the log via logminer?...

      and we said...
No, if I create a partitioned table and enable row movement --
the rowid of any row can change at any time in response to a
simple UPDATE from an application.

the rowids in a partitioned table with row movement are not
static.  An update will change them.

the DBA will NOT know if the rowids have changed.

On those two table types, you should not rely on a rowid
remaining static. You should use a PRIMARY KEY in any case.  You
cannot get this information reliably from log miner, there is no
view.

I would strongly encourage you to use the PRIMARY KEY.  A rowid
is effective only in a session.

Review &
      Followup

      Rating: 5
where should we
      put rowid's to use  September 25, 2001
Reviewer:
      A reader
Recently I and my colleague had a conversation on use or rowid's. We were not
very sure as to what is the exact use of rowid's

According to my colleague

"rowid is the fastest mechanism for accessing a row in a table, even faster than
a primary key. To perform bulk deletes or updates, use rowid if at all possible.
"

I disagree with him , because as the user keep inserting rows and deleting rows,
the rowid can change , isnt it?, the only time we can use rowid , is may be when
we have be delete all the rows, in which case we might as well truncate the
table.

But your statement below "A rowid is effective
only in a session.", though it comes after a strong recommendation to use
primary key, vindicates my colleagues stand that , when you select a group of
rows in a cursor and delete them by rowid , it will be valid.

Kindly discuss this aspect, and beat the issue to death.

what do you acutally mean by 'A rowid is effective
only in a session'.

          Followup:

The way I would use a rowid would be like this.

At time T1 an application issues a query "select rowid r, a.* from a".  This
fetches the data and the rowid from the table.

At time T2 that application decides it would like to modify the "n'th" row it
fetched.  BEFORE it does this it will issue:

    select *
      from a
     where rowid = :fetched_rowid
       and c1 = :fetched_c1
       and c2 = :fetched_c2
       ...
       and cN = :fetched_cn
    FOR UPDATE NOWAIT;

That is -- we go back and RE-READ the row using the OLD values (:fetched_c1...)
and the rowid.  The rowid gets it FAST and the rest just verify that between the
time we opened our query and the time we decided we wanted to modify it -- the
data did not change

If that select gets 0 rows -- someone changed the row, we cannot update it -- we
would suffer from a LOST UPDATE if we did (an update that overwrites someone
elses changes without ever looking at their changes).

If that select gets an ORA-00054, someone else has the row locked, we shouldn't
even try to update it.

If that select returns a row -- the row was not changed and is now LOCKED by us
(the update will not block).

This is, for example, what Oracle forms does for you automagically.

By across sessions I meant -- you should never store a rowid in a table.  The
rowid of a row can change in 8i (never used to be able to change prior to that).
 IOTs and PARTITIONED tables can have rowids change on your unexpectedly.

      Rating: 5
You did not say
      across sessions you said  September 25, 2001

Reviewer:  A reader
'A rowid is effective only in a session'.

why?

          Followup:

Which is the inverse of across sessions.  My point is you should use them in a
session -- not across sessions.

Ok, let me rephrase....

when I said "only in a session" i mean you should not use them across sessions.
you should never store a rowid in a table.  The rowid of a row can change in 8i
(never used to be able to change prior to that). IOTs and PARTITIONED tables can
have rowids change on your unexpectedly.

      Rating: 4
You have
      substantiate and elaborate the below statemet  September 25,
      2001
Reviewer:  Joe
developers always believed the contrary

"The rowid of a row can change in 8i (never used to be able to change prior to
that). "

Yes , they can change with IOT's etc, but they never used to before... are you
sure..

when you update or delete a row, doesnt a rowid change.

( well, you might have to explain us some basics, about rowid's, what is it made
off  , and why it doesnt change)

          Followup:

Well, I don't have to substantiate -- its a fact, in 8.0 and before a rowid was
immutable, never changing.  Belief in the contrary isn't relevant, the fact is
the rowid -- once assigned to a row -- never changed.

When you DELETE a row, its rowid doesn't change by definition -- its deleted,
gone, doesn't exist (it no longer has a rowid).

When you UPDATED a row, its rowid never USED to change (8.0 and before).  The
row might "migrate", but the rowid never changed (we left the head piece of the
row where it used to be and just leave behind another pointer to the new
location).

Starting with 8i and the ability to create secondary indexes on IOTS (causing
their urowid to change if you update the primary key) and the ability to ENABLE
ROW MOVEMENT in a partition table changed that rule.

I recommend the server concepts manual as the definitive source of data on what
a rowid is, what it is made of.

See

http://technet.oracle.com/doc/server.804/a58227/ch5.htm#2848
and page up once to "rowids of row pieces".  this shows in 8.0 the rowid did not
change.  they confuse the issue with "exp/imp" however since the act of
IMPORTING means you are creating a new row -- it should be somewhat obvious that
a new rowid will be assigned.

Well, now that you made me look it up -- I discover they did NOT update the
concepts guide in 8i/9i to reflect that a rowid can change (same paragraph as in
8.0 is there).  I'll file a bug on that one.

      Rating:
      4
contradictory  September 26, 2001

Reviewer:  ray
"I would strongly encourage you to use the PRIMARY KEY.  A rowid is effective
only in a session."

and then you say , that within a session you can use rowid, to do deletes and
updates.

Can I do

Update t

set t.x =(select t1.y from t1
          where t.rowid=t.rowid
             and t1.z='blah')

This corerelated update will be much faster , because Iam using the rowid, aint
this true.

I dont care if changes are being made in other sessions. When my process runs I
update all the rows which meet a particular criteria.

Kindly clear this for us , because the use of rowid all these days , has been
minimum, if it is beneficial why not use it.

          Followup:

It is not contradictory.  I say "strongly encourage", "you can (if you want
to)".  One is advice (strongly encourage), the other a fact.  I would recommend
you use a primary key but you can use the rowid.

As for your update

update t set x = decode( z, 'blah', y, null );

would be the most efficient way to do that.  It is 100% equivalent to your
correlated subquery.  I cannot think of a reason to self join like that by rowid
in an update ever.

      Rating: 4
sorry , there
      was a typo in the sql   September 26, 2001

Reviewer:  ray
Update t

set t.x =(select t1.y from t1
          where t1.rowid=t.rowid
             and t1.z='blah')

I guess using rowid, in this way would be much make the sql execute much faster
then using the primary key to join t and t1.

What can be the reasons, or situations or possible disadvantages  when we use
the rowid

          Followup:

that won't work at all -- the rowid from t cannot be compared to the rowid in
t1.  That query won't update anything, rather it will get:

update t
       *
ERROR at line 1:
ORA-01410: invalid ROWID

(well, there is ONE special case where the update WON'T get that error -- here
is a challenge for everyone -- post the full example here of the above update
succeeding (and T1 is NOT a view of T!!! that would be too easy) with the
create's and all ;)

      Rating: 5
Response to
      challenge  September 26, 2001
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Tom:

How about something like this for your challenge?  It isn't pretty, but I think
it shows what you were looking for...

SQL> disconnect
Disconnected from Oracle9i Enterprise Edition Release 9.0.1.1.0 - 64bit
Production
With the Partitioning option
JServer Release 9.0.1.1.0 - Production

SQL> connect scott@o9it
Enter password:
Connected.

SQL> create cluster tom_test
  2  (deptno number);

Cluster created.

SQL> create table emp2
  2  (empno number primary key,
  3  deptno number not null)
  4  cluster tom_test (deptno);

Table created.

SQL> create table dept2
  2  (deptno number,
  3  dname varchar2(32))
  4  cluster tom_test (deptno);

Table created.

SQL> CREATE INDEX idx_tom_test ON CLUSTER tom_test;

Index created.

SQL> insert into emp2 values (1000, 100);

1 row created.

SQL> insert into dept2 values (100, 'Test Dept');

1 row created.

SQL> update emp2
  2  set emp2.deptno = (select dept2.deptno from dept2
  3                     where dept2.rowid = emp2.rowid
  4                     and dept2.dname = 'Test Dept');

1 row updated.

SQL>

- Mark

          Followup:

Rats -- that was it.  (anyone who read my book and the chapter on clusters
should have been able to come up with that).

didn't think it would be answered so fast.  That just proves that a rowid is
unique to a TABLE -- not unique within a database.  In that cluster -- the row
int he EMP table and the DEPT table actually have the SAME rowid.

      Rating:
      5
challenge  September 26, 2001
Reviewer:
      Mark A. Williams  from Indianapolis, IN USA
Tom:

Don't think of it as being answered so fast...  think of it as your book is
excellent and I spend too much time on 'AskTom'.  :)

- Mark

      Rating:
      4
  December 27, 2002
Reviewer:  Paul
      Heely  from Groton, CT USA
Tom,
You said:
"Well, now that you made me look it up -- I discover they did NOT update the
concepts guide in 8i/9i to reflect that a rowid can change (same paragraph as in

8.0 is there).  I'll file a bug on that one."

I looked on metalink for documentation about this being missing from the
concepts manual, but could not find anyting.  Is there a bug number or metalink
doc that we can reference to document the incorrect information in the 9i
concepts manual?  I need to document this problem in some code that we have and
an "official" Oracle document would be helpful

Thanks.
--
Paul

          Followup:

Not that I'm aware of -- although there is this page.... You could file a TAR
with support to get a support note written up on the topic.

      Rating: 4
Example to show
      changing rowid.  December 31, 2002
Reviewer:
      Paul Heely  from Groton, CT USA
Tom,
Do you have any sample code that will illustrate a rowid changing inside an IOT?

Thanks.
--
Paul

          Followup:

ops$tkyte@ORA920> create table t (x int primary key ) organization index;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( 1 );

1 row created.

ops$tkyte@ORA920> select rowid from t;

ROWID
-----------------------------------------
*BAJAAQwCwQL+

ops$tkyte@ORA920> update t set x = 2;

1 row updated.

ops$tkyte@ORA920> select rowid from t;

ROWID
-----------------------------------------
*BAJAAQwCwQP+

      Rating:
      5
Thanks!  December 31, 2002
Reviewer:
      Paul  from Groton, CT USA
Thanks Tom.

My own attempts at showing this were WAY to complicated, and didn't work.
Your's is perfect.

--
Paul

      Rating: 5
questions on
      rowid   May 26, 2003
Reviewer:  Tanmoy Datta
      Choudhury  from india
hi tom ,

  recently i have got one requirment to print the two table in sql as it is ...

like if my two table is t1 and t2 having one col each ..
t1                      t2
2                       5
                        6
1                       0
3
and i have to print the two table in sql as it is

i wrote one query like this
select t1.col1,t2.col2 from t1,t2
where substr(t1.rowid,18,14)= substr(t2.rowid,18,14);

which gives me the desired output ..but m little bit confused here becos u see
there is no join condition in the tables but using substring m joining..(as
after examining the rowids of two tables i have got some common char in it ..not
so efficient think i suppose but i couldnt get any other hints to solve the
problem )will i get the same result in any case (except IOT,cluster and index )
...
and one more thing ...

as i have gone through your articel on rowid here ...can i say like this ..

rowid will be unique in the database (except IOT,cluster and index issues )

waiting for your advice ..

          Followup:

rowid is NOT UNIQUE in the database.
rowid is unique within a table

but in any case, I've no idea what you are really trying to do here -- but I can
say that using rowid like that is a recipe for disaster.  Are you trying to just
take each row from t1 and join it with some row from t2 and output?

ops$tkyte@ORA920> select * from t1;

         X
----------
         2
         1
         3

ops$tkyte@ORA920> select * from t2;

         X
----------
         5
         6
         0

ops$tkyte@ORA920> select        t1.x, t2.x
  2    from (select rownum r1, x from t1 ) t1,
  3         (select rownum r2, x from t2 ) t2
  4   where r1 = r2
  5  /

         X          X
---------- ----------
         2          5
         1          6
         3          0

Now, if t1 and t2 have different cardinalities -- you'll need to outer join
them.

      Rating:
      4
Question  May 29, 2003
Reviewer:
      Christo Kutovsky  from Ottawa, ON Canada
Is it possible to receive and "Invalid rowid" under any circumstance from the
following statement:

delete from TAB where rowid in
(select Y.rowid from TAB x, TAB y
 where Y.col1 = X.col1
 and   Y.col2 != X.col2
)

In our case, the table is HASH partioned.

          Followup:

it would appear not.  but i'm guessing you have experienced otherwise?

      Rating: 5
Followup to
      Question - Christo Kutovsky  May 29, 2003

Reviewer:  Jim  from Australia
I'm going to say yes it is possible.

I remember replicating something similar
and it was due to a Bug with view merging,
am pretty sure it is in 8.1.7

I'll see if I can find out exactly what is was.
the fix was changing the value of an init.ora parameter
that controls complex view merging

Regards
Jim

      Rating: 4
Followup to
      Tom  June 01, 2003
Reviewer:  Christo
      Kutrovsky  from Ottawa, ON Canada
It seems that yes. According to the developers, they've got this error in the
application, and according to the logs it was this query.

Before saying that it's impossible I decided to check for sure.

It should not matter even if the table is partitioned and has row movement on ?
After all it's 1 query and read consistency is guarenteed.

          Followup:

it should not, I cannot think of the case where it would but I haven't tried too
hard to simulate it.

The obvious solution would be to use a primary key rather then rowid.

      Rating:
      4
Follow-up  June 01, 2003
Reviewer:
      Christo Kutrovsky  from Ottawa, ON Canada
Yes, that's what we suggested.

We also did "analyze TABLE validate structure" on the table, and also
select count(*) from
 (select 1 from TABLE where rowid in
   (select rowid from TABLE)
 )

just to verify for any possible inconsistencies.

It's either the wrong SQL statement or a misterious bug in Oracle 9.2.0.2
(Solaris).

P.S.
Jim, I appreciate your help.

      Rating:
      5
Followup  June 01, 2003
Reviewer:
      Jim
Sorry still looking for my notes on this.
I remember it still occured even when there was no
explicit reference to rowid. It was all to do with
how Oracle was merging views

      Rating: 5
Constancy of
      ROWID within a session  July 24, 2003
Reviewer:
      B. Robinson  from Hartford, CT
What are the conditions that will ensure in Oracle8i and 9i that a session will
see the same ROWID for the same row even if it is updated?

So far the following have been noted:
1. It must not be a partitioned table
2. It must not be an index organized table
3. Deleting will lose the ROWID (i.e. if you insert it back with the same data
expect a new ROWID)
4. Do not expect it to be the same for another session

What else is necessary to guarantee the ROWID will remain the same within a
session?  Or is it that there is no guarantee at all?  Are the conditions
different for 8i vs. 9i?

Thanks.

          Followup:

there are NONE......

#3 PRECLUDES that.

You need to run a query like:

select * from t where rowid = :r and PRIMARY_KEY = :pk;

to be sure it is the "same row"

      Rating: 3
Thanks ... but
      let me explain  July 24, 2003
Reviewer:  B.
      Robinson  from Hartford, CT
What I meant was -- are there some things that you can do and NOT do to
guarantee that ROWID will be the same?

If I do updates on a table but don't violate any of the conditions 1-4 (ie. I
DON'T delete, don't use IOT or partitions, etc.), will that guarantee that ROWID
will be the same?  If not, are there any other things that can be done to ensure
it will remain the same within a session, while still allowing updates?

Basically, I am maintaining a large system developed over several years that
makes use of ROWID in a number of places, and I need to analyze which areas of
it are at immediate risk of producing erroneous results due to mutating ROWIDs
(which wasn't an issue when the system was being developed on earlier versions
of Oracle). If I can determine which conditions would make ROWID usage "safe", I
can then prioritize any necessary changes and focus on the most shaky parts.

          Followup:

the rowid will stay the same for a row as long as it exists if you do not enable
row movement and you do not use IOT's

      Rating:
      4
rowid  August 23, 2003
Reviewer:
      Reader
rowid is unique within a table not in the database is true because of extended
rowid that includes objectid as a component in rowid. Is it right? Thanks.

          Followup:

no, even in version 7.3 (before extended rowids blah blah blah) rowid was unique
in a TABLE, not in a DATABASE.

The rowid is applied to a table, not a database.

      Rating: 5
rowid
        August 23, 2003
Reviewer:  Reader

From the link,

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96540/sql_elements6a.htm#19762
'Usually, a rowid value uniquely identifies a row in the database. However, rows
in different tables that are stored together in the same cluster can have the
same rowid."

May be I am not interpreting your statement properly. I always thought rowid is
unique to the database. Thanks.

          Followup:

it is not.  it is unique in a table, not in a database.

clusters make this possible.  it is easy to have two rows in the same database
in different tables have the same rowid.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:433278366052
for an exmaple

      Rating: 5
uniqueness of
      rowid  August 24, 2003
Reviewer:  Reader

tom, only for clusters, the rowid is unique within a table. However, for heap
tables still rowid is uique within the database. Is it correct? Thanks.

          Followup:

does it really matter?

the bottom line is this "a rowid is unique in a table.  it cannot be used to
identify a table.  any application that believes otherwise has a bug just
waiting to happen.  a rowid is unique in a table -- period"

that is might be unique across heap tables, or iots or whatever -- not really
"relevant"

(but yes, across heaps, it'll be unique)

      Rating:
      5
  August 25, 2003
Reviewer:  Helena
      Marková  from Bratislava, Slovakia

      Rating: 5
DML using rowid
      vs PK  September 09, 2003
Reviewer:
      A reader
Hi

I am wondering when we use PL/SQL to make some DML changes to tables (for
example using cursors) what is faster, using rowid in WHERE clause or the
PRIMARY KEY?

          Followup:

well, how did you get the rowid in the first place?

this is not an apples and oranges comparision at all.

      Rating: 4
Max
      rowid  September 24, 2003
Reviewer:  Phil

On the subject of rowid..and sorry if this is a basic question but

What exactly does max(rowid) mean , would it be the last updated/inserted row?

Regards
Phil

          Followup:

no, it just means the biggest rowid in the table.

the biggest rowid could be the newest row in the table
it could be the oldest row
it could by any row in between.

nothing can be ascertained by "max(rowid)" other then "i've got the biggest
rowid"

      Rating:
      5
Rowid  September 24, 2003
Reviewer:
      Phil
Thank you very much for that, as I had an asumpation that max(rowid) was
actually very specific ie the last row updated/inserted.

In that case...and please forgive me for straying (as i thought max(rowid) would
help here)

if I had a table of

account          balance2   balance date
-------         --------    ------------
1               200         01/Jan/2003
1               300         01/Jan/2003
1               -400        01/Jan/2003
2               500         01/Jan/2003

how can I get the last balance for the account, if it is a rolling balance. In
the above table the balance for account 1 is currently -400.

I was under the impression (incorrectly) selecting the value with the max(rowid)
would do the trick !

Thanks

          Followup:

select *
  from ( select account, balance, balance_date, max(balance_date) over
(partition by account) max_dt
           from t )
where balance_date = max_dt;

is but one way....

if you need it just for an account:

select *
  from ( select * from t where account = :x order by balance_date DESC )
where rownum = 1;

is another.

      Rating: 5
thank
      you  September 24, 2003
Reviewer:  phil
       from london
regards
phil

      Rating:
      4
  September 29, 2003
Reviewer:  phil

Sorry to drag out, as this has now little to do with rowid, but would the query
you provided not return all rows for account 1 as they all have the same
date/time value ?

This seems to be happening for me. I am trying to make sure I get the last row
that was inserted as this would be the last balance.

this is the query I am using:

 select *  from (
select account_fk,balance_date_time,booked_balance,available_balance,
       max(a.balance_date_time) over (partition by account_fk) max_date
  from accountbalance a
 where  account_fk in ( 1,2,3,4 )
 ) b
 where balance_date_time = b.max_date

thanks

          Followup:

there is no such thing as "a last row inserted" unless you have some field there
to tell you which row is "last".

I have to assume the DATE field would be the date of insertion, in which case,
it gives you the last row "inserted"

if not, you don't have a chance of getting the last row inserted.

      Rating: 4
to add to the
      above  September 29, 2003
Reviewer:  phil

...when I say get the last row inserted I mean the last row inserted for each
account with the max balance date/time for each account

thanks

          Followup:

select *
  from ( select account, balance, balance_date, max(balance_date) over
(partition by account) max_dt
           from t )
where balance_date = max_dt;

does that.  max_dt is set to the max_dt for each account.

you keep the row with the max_dt for each account.

      Rating: 3
would this do
      regarding above  September 29, 2003
Reviewer:
      phil
if pk is a sequence number ( i am only inserting to the accountbalance table )
....

select * from (
select b.*,
  max(pk) over (partition by account_fk) max_pk
from (
 select pk,account_fk,balance_date_time,booked_balance,available_balance,
       max(balance_date_time) over (partition by account_fk) max_date
  from accountbalance
 where  account_fk in ( 1,2,3,4 )
 ) b
 where balance_date_time = max_date
 ) where pk = max_pk

September 29, 2003 -- Thanks for the question regarding "opinion
      about new auto segment management", version 9.0.1

      You Asked
Hi tom

What is your opinion on 9i's new auto segment management feature? I think it's
not good at all since if we now distribute inserts on more blocks it may be good
for DMLs but for queries it would have a negative impact isnt it? Since we would
have to read more blocks (and waste buffer cache)

What do you think?

cheers

      and we said...
Auto segment management just automates the process by which you used to have to
manually tune using multiple freelists.  In the past, you would have had to of
determine the number of concurrent modifiers of data (inserts/updates that
happen exactly at about the same time) and configure multiple freelists in order
to satisfy these concurrent requests in a timely fashion.

With auto management you don't have to do this anymore.  It'll not distribute
across multiple blocks any more then multiple freelists would have in the past
-- only you need not waste your time and energy attempting to determine the
exact correct number of freelists to have.

This new feature is more efficient then multiple freelists:

<quote>
Free lists have been the traditional method of managing free space within
segments. Bitmaps, however, provide a simpler and more efficient way of managing
segment space. They provide better space utilization and completely eliminate
any need to specify and tune the PCTUSED, FREELISTS, and FREELISTS GROUPS
attributes
for segments created in the tablespace. If such attributes should be specified,
they are ignored.
</quote>

a very informal, quick and dirty test I've done shows that automatic segment
space mgmt gives us almost all of the benfit of finely tuned freelists --
without the pain of finely tuning freelists (something that is virtually
impossible to do in the "real world" anyway).

Go for it -- on a heavy OLTP type table -- this is very beneficial.
Review &
      Followup

      Rating: 5
What more about
      9i?  March 04, 2002
Reviewer:  Yogeeraj
       from Mauritius
Hello,

..more about 9i

I have am currently moving my database from Oracle 7.2.3 to Oracle 8.1.7 (9i was
not there when i started). I have currently a performance gain of about 10 fold
(well, my reports runs much faster!!) without any major change in my application
design. (I am happy except that i once had a data block corruption with one of
my Index Tablespaces)

I have the possibility of replacing my 8i with 9i before the completion of my
full migration. (I will rearranging my storage internal disks to a new RAID BOX
and at the same time converting my tablespaces to LMT). Should i go for 9i?

I run a 24x7 OLTP application which is very critical.

Thank you for your precious time
Best Regards
Yogeeraj

          Followup:

I would go with 9i if you are going through the upgrade process right now.  Why
upgrade to software that will be obsoleted before other software that exists
right now?

      Rating:
      3
  March 05, 2002
Reviewer:
      A reader  from usa
if it´s very critical app dont use 9i, too buggy or at least wait till release 2
but I doubt it would be stable neither... nowdays us the users are the official
free beta testers for software companies sigh

          Followup:

awk, sputter, gag,

WHAT.  You're KIDDING right.

Really big SIGH from me.  Beta tester indeed.  This is not me being "corporate",
this is me as an official long time user of 9i.

You know -- if it were too buggy, you wouldn't even be able to file a bug
against it since the entire bug db (which is a pretty large thing as it has
everything in it for many many years and is used by thousands of people) has
been running on 9i.  Same with emailing me -- you wouldn't be able to send or
receive mail to/from me since it's been running on 9i as well (and that is a
LARGE database with 10's of thousands of users using the most advanced features
of 9i).

Ok, if you say it's "too buggy", give me that bug number or description of the
show stopping bug.   Once you do that, I'll give you a bug number for 817, when
9iR2 comes out -- I'll give you one for that release.  Point being -- there be
some bug ("product feature") in every release probably, as to whether it effects
you or not remains to be seen.

I don't usually say this but ... you -- you can just stay away from here if you
like.  Or -- don't hide behind "a reader from usa", tell us who you are and what
exactly you've run into.

sigh indeed.  well, at least my blood pressure is up for the day, gets me going
in the morning anyway.

      Rating: 4
auto segment
      management  April 24, 2003
Reviewer:  A
      reader  from Houston, TX
Tom, if I have segment management auto clause, how the pctfree, pctused are
managed internally? Is it like, the bitmaps will track of how "full" the block
is, is the block 1/2 or 1/3 full and depending on how much it is full, the
system will chose the block for inserts. Could you please explain how it is
happening in Oracle at the bitmap level. My understaning is the each bit in
segment header corresponds to a block. With in the bit how Oracle manages or
knows how full the block is. you are the best in explaining concepts. I
appreciate your time.

          Followup:

the algorithm is complex, not fully documented, and subject to radical change.
In short

o we remember if a block is 75% or more empty, 50%, 25% or full.

o we remember if a block is formatted or not (been used or not been used)

o we spread the data out to reduce contention as it is being added (so we don't
always goto the first block available as a list would, we hash around and use
space "haphazzardly" but in a controlled fashion so it only appear haphazzard)

      Rating:
      5
  April 24, 2003
Reviewer:
      A reader
So I would guess you would not use this everywhere.  For example, Large tables
that do not get updated.  We have three denormilize tables in our OLTP
application used for reporting.  We have PCTFREE set to 0 on the table and
indexes to try and limit the block reads.

Can you give a kinda rule-of-thumb when you would and when you would not use
this?

Thanks.

          Followup:

well, the pctfree is still used here on the intial load up (it is pctused that
is obsoleted totally)....

I would use this on my oltp style tables.

I don't see the advantage to using this on a warehouse type table (don't
necessary see the disadvantage either).

      Rating: 5
See also
      Metalink  April 24, 2003
Reviewer:  Jan van
      Mourik  from Houston, TX USA
Do a search on Metalink for "assm". There's an interesting thread with subject
"Re : automatic segment space management in 9.2"> Scroll down and read what
Richard Foote has to say.

There is something strange with assm and deletes though. I played around with
assm a while ago. I created a table in a lmt with assm and inserted some rows
until I had some full blocks. Then I started deleted them, and looked at the
bitmap. It seems that the bitmap won't be updated until there is '25-50% free'
space. It doesn't go from 'FULL' to '0-25% free'! Even when doing updates I
didn't see it being changed to '0-25% free', it will change to '25-50% free'
though. Did anybody else notice this?

      Rating: 5
Segment dump
      output  April 25, 2003
Reviewer:  Jan van
      Mourik  from Houston, TX USA
To clarify my comment above, this is what I ran:

Db_block_size is 8192.
create table bmb (id number, col1 char(2000), col2 char(1)) pctfree 0 tablespace
users01;

begin
execute immediate 'truncate table bmb';
sys.dbms_space_admin.segment_dump('USERS01',2, 475);
insert into bmb values (1, '1', '1');
commit;
sys.dbms_space_admin.segment_dump('USERS01',2, 475);
insert into bmb values (2, '2', '1');
commit;
sys.dbms_space_admin.segment_dump('USERS01',2, 475);
insert into bmb values (3, '3', '1');
commit;
sys.dbms_space_admin.segment_dump('USERS01',2, 475);
insert into bmb values (4, '4', '1');
commit;
sys.dbms_space_admin.segment_dump('USERS01',2, 475);
insert into bmb values (5, '5', '1');
commit;
sys.dbms_space_admin.segment_dump('USERS01',2, 475);
insert into bmb values (6, '6', '1');
commit;
sys.dbms_space_admin.segment_dump('USERS01',2, 475);
insert into bmb values (7, '7', '1');
commit;
sys.dbms_space_admin.segment_dump('USERS01',2, 475);
insert into bmb values (8, '8', '1');
commit;
sys.dbms_space_admin.segment_dump('USERS01',2, 475);
insert into bmb values (9, '9', '1');
commit;
sys.dbms_space_admin.segment_dump('USERS01',2, 475);
insert into bmb values (10, '10', '1');
commit;
sys.dbms_space_admin.segment_dump('USERS01',2, 475);

delete from jvmo.bmb where id = 10;
commit;
sys.dbms_space_admin.segment_dump('USERS01',2, 475);
delete from jvmo.bmb where id = 9;
commit;
sys.dbms_space_admin.segment_dump('USERS01',2, 475);
delete from jvmo.bmb where id = 8;
commit;
sys.dbms_space_admin.segment_dump('USERS01',2, 475);
delete from jvmo.bmb where id = 7;
commit;
sys.dbms_space_admin.segment_dump('USERS01',2, 475);
delete from jvmo.bmb where id = 6;
commit;
sys.dbms_space_admin.segment_dump('USERS01',2, 475);
delete from jvmo.bmb where id = 5;
commit;
sys.dbms_space_admin.segment_dump('USERS01',2, 475);
delete from jvmo.bmb where id = 4;
commit;
sys.dbms_space_admin.segment_dump('USERS01',2, 475);
delete from jvmo.bmb where id = 3;
commit;
sys.dbms_space_admin.segment_dump('USERS01',2, 475);
delete from jvmo.bmb where id = 2;
commit;
sys.dbms_space_admin.segment_dump('USERS01',2, 475);
delete from jvmo.bmb where id = 1;
commit;
sys.dbms_space_admin.segment_dump('USERS01',2, 475);

execute immediate 'truncate table bmb';
commit;
sys.dbms_space_admin.segment_dump('USERS01',2, 475);

end;
/

This is extracted from the dump file:

execute immediate 'truncate table bmb';
0:Metadata   1:Metadata   2:Metadata   3:unformatted
4:unformatted   5:unformatted   6:unformatted   7:unformatted
8:unformatted   9:unformatted   10:unformatted   11:unformatted
12:unformatted   13:unformatted   14:unformatted   15:unformatted

insert into bmb values (1, '1', '1');
0:Metadata   1:Metadata   2:Metadata   3:75-100% free
4:75-100% free   5:75-100% free   6:75-100% free   7:75-100% free
8:75-100% free   9:75-100% free   10:75-100% free   11:75-100% free
12:50-75% free   13:75-100% free   14:75-100% free   15:75-100% free

insert into bmb values (2, '2', '1');
12:25-50% free   13:75-100% free   14:75-100% free   15:75-100% free

insert into bmb values (3, '3', '1');
12:0-25% free   13:75-100% free   14:75-100% free   15:75-100% free

insert into bmb values (4, '4', '1');
12:0-25% free   13:75-100% free   14:75-100% free   15:75-100% free

insert into bmb values (5, '5', '1');
12:FULL   13:50-75% free   14:75-100% free   15:75-100% free

insert into bmb values (6, '6', '1');
12:FULL   13:25-50% free   14:75-100% free   15:75-100% free

insert into bmb values (7, '7', '1');
12:FULL   13:0-25% free   14:75-100% free   15:75-100% free

insert into bmb values (8, '8', '1');
12:FULL   13:0-25% free   14:75-100% free   15:75-100% free

insert into bmb values (9, '9', '1');
12:FULL   13:FULL   14:50-75% free   15:75-100% free

insert into bmb values (10, '10', '1');
12:FULL   13:FULL   14:25-50% free   15:75-100% free

delete from jvmo.bmb where id = 10;
12:FULL   13:FULL   14:50-75% free   15:75-100% free

delete from jvmo.bmb where id = 9;
12:FULL   13:FULL   14:75-100% free   15:75-100% free

delete from jvmo.bmb where id = 8;
12:FULL   13:FULL   14:75-100% free   15:75-100% free

delete from jvmo.bmb where id = 7;
12:FULL   13:25-50% free   14:75-100% free   15:75-100% free

delete from jvmo.bmb where id = 6;
12:FULL   13:50-75% free   14:75-100% free   15:75-100% free

delete from jvmo.bmb where id = 5;
12:FULL   13:75-100% free   14:75-100% free   15:75-100% free

delete from jvmo.bmb where id = 4;
12:FULL   13:75-100% free   14:75-100% free   15:75-100% free

delete from jvmo.bmb where id = 3;
12:25-50% free   13:75-100% free   14:75-100% free   15:75-100% free

delete from jvmo.bmb where id = 2;
12:50-75% free   13:75-100% free   14:75-100% free   15:75-100% free

delete from jvmo.bmb where id = 1;
12:75-100% free   13:75-100% free   14:75-100% free   15:75-100% free

execute immediate 'truncate table bmb';
0:Metadata   1:Metadata   2:Metadata   3:unformatted
4:unformatted   5:unformatted   6:unformatted   7:unformatted
8:unformatted   9:unformatted   10:unformatted   11:unformatted
12:unformatted   13:unformatted   14:unformatted   15:unformatted

oracle@houhpt24=> grep 'Freeness Status:' ids9i_ora_2444.trc
truncate   Freeness Status:  nf1 0      nf2 0      nf3 0      nf4 0
insert 1   Freeness Status:  nf1 0      nf2 0      nf3 1      nf4 12
insert 2   Freeness Status:  nf1 0      nf2 1      nf3 0      nf4 12
insert 3   Freeness Status:  nf1 1      nf2 0      nf3 0      nf4 12
insert 4   Freeness Status:  nf1 1      nf2 0      nf3 0      nf4 12
insert 5   Freeness Status:  nf1 0      nf2 0      nf3 1      nf4 11
insert 6   Freeness Status:  nf1 0      nf2 1      nf3 0      nf4 11
insert 7   Freeness Status:  nf1 1      nf2 0      nf3 0      nf4 11
insert 8   Freeness Status:  nf1 1      nf2 0      nf3 0      nf4 11
insert 9   Freeness Status:  nf1 0      nf2 0      nf3 1      nf4 10
insert 10  Freeness Status:  nf1 0      nf2 1      nf3 0      nf4 10
delete 10  Freeness Status:  nf1 0      nf2 0      nf3 1      nf4 10
delete 9   Freeness Status:  nf1 0      nf2 0      nf3 0      nf4 11
delete 8   Freeness Status:  nf1 0      nf2 0      nf3 0      nf4 11
delete 7   Freeness Status:  nf1 0      nf2 1      nf3 0      nf4 11
delete 6   Freeness Status:  nf1 0      nf2 0      nf3 1      nf4 11
delete 5   Freeness Status:  nf1 0      nf2 0      nf3 0      nf4 12
delete 4   Freeness Status:  nf1 0      nf2 0      nf3 0      nf4 12
delete 3   Freeness Status:  nf1 0      nf2 1      nf3 0      nf4 12
delete 2   Freeness Status:  nf1 0      nf2 0      nf3 1      nf4 12
delete 1   Freeness Status:  nf1 0      nf2 0      nf3 0      nf4 13
truncate   Freeness Status:  nf1 0      nf2 0      nf3 0      nf4 0

Notice how after the deletes start, the status doesn't go from "FULL" to "0-25%
free". It does't get updated until there's "25-50% free"!

      Rating: 2
Space
      Management in Locally Managed Temporary Tablespaces  May 17,
      2003
Reviewer:  A Reader of AskTom  from Pakistan

Hi Tom!

The introduction of LMTs looks really interesting in new releases of Oracle. It
has atleast minimized the space management burden of the DBAs. But I have
another experience... I have created a Locally Managed Temporary Tablespace (1
gig initially) and now its size is more than 3 gig and I am losing free space on
my disk. With the Oacle8i.. it was very easy in case of Dictionary Managed
Temporary Tablespaces, we took temporary tablespace offline and then took it
online to free the used blocks. Isn't there any method to release the used space
by the Locally Managed Temporary Tablespaces in Oracle9i Release 2 ? Please tell
us in detail if there is any way. Thanks...

          Followup:

(new meaning the last 5 releases of course ;)

you know, it is just going to grow again.  why did you get into this predicament
in the first place.

do you remember how many hours that took to shrink with DMT's (could have been
days actually).  do you remember how hot SMON got during that?

do this

create new temp -- making sure to disable autoextend this time as you obviously
don't want it;  this'll take a fraction of a second regardless of the size of
temp.

alter users to new temp;

drop old temp;

      Rating: 2
No way to
      release the used space..?  May 17, 2003
Reviewer:
      A Reader  from Pakistan
Well Boss! It means there is no way to release the used space in LM temporary
tablespaces until the latest release of Oracle.. Isn't?

          Followup:

well boss?

no, it doesn't mean that at all -- does it.

I told you how to do it after all first.

Second, the space need not, i would go as far to say -- should not -- be
"released".  you are just going to get it again.

The space IS in fact released -- it is dedicated to temp.    It is just a waste
of cycles on your part and the systems part to "release" it.

      Rating: 3
opinion about
      new auto temp segment management  May 20, 2003

Reviewer:  A Reader  from Pakistan
Boss is just to give you honour as i am learning from you :)
I am really thankful to you becoz of ur help in understanding the logic of auto
temporary segment management in Oracle.

      Rating: 5
Potential
      issues with ASSM and LMT  July 25, 2003
Reviewer:
      Peter Tran  from Houston, TX USA
Tom,

Have you encountered this problem with LMT and ASSM?

When using "ANALYZE TABLE <table_name> with estimate statistics;", you can
potentially get a ORA-00600 [KCBGTCR_12].

See
http://metalink.oracle.com/metalink/plsql/ml2_documents.showFrameDocument?p_database_id=BUG&p_id=2619867
The work around is to either use compute statistics or the DBMS_STATS package.

I wonder if you've encountered this problem.  I ran into it when I did a
tablespace reorg moving tables and indexes from 60+ tablespaces into 6.

Thanks,
-Peter

          Followup:

whether i've "run into it" or not doesn't seem particularly "relevant" (what
would it mean that I've hit it or not?)

dbms_stats is the method we are basically saying is "the way to go" so the
workaround is pretty good as it gets you there.

      Rating:
      5
  September 29, 2003
Reviewer:
      A reader  from orlando,FL
Tom,
Your explanation of freelist in your new book was the best. The concept was
explained without complicating things. Great teacher. The water cooler example
*and*  the diagram finally drove home the point for me. Now,

a) when we create a LMT using ASSM, oracle does the freelist management for us.
What is the algorithm - does it create freelists or freelist groups?

b) In page 229 you mention that buffer busy waits(bbw) is *one* indication of
free list contention. what are the *other* situations ?

c)Can we derive:
*all* freelist contention should show up as bbw,  but all bbw are not freelist
contentions

d) Am i correct in assuming multiple freelist groups is nothing but just many
master freelists

Thanks as always

          Followup:

a) trick question -- neither.  it uses bitmaps stored in blocks to manage space.
 no freelists at all.

b) other causes of buffer busy waits could be -- you are waiting for the block
to be read, someone else has the buffer and makes you wait, we are waiting for
some other session to read the block in for us...

c) yes

d) more or less, yes.

      Rating: 3
JP Lewis's
      Comments - Taken from Metalink  October 14, 2003

Reviewer:  JP Lewis's Comments  from USA
From: J P Lewis 31-Jan-02 13:26
Subject: Re : Re : Connection pool much slower in 9i than in 8i

Very interesting.

Thanks for the feedback. I had noticed that
the number of bitmap blocks allocated for
Automatic Segment Space management was rather
high. Typical figures seem to be 1 bitmap block
per 64 data blocks for segment sizes up to 64MB,
and one bitmap block per 256 data blocks thereafter
up to the next limit (wherever that is).

For large databases with partitioned objects this
seems to suggest that you could have a very large
fraction of the db_block_buffers dedicated to bitmap
blocks, and some of the side effects of having a large
number of concurrent processes include a very wide
scattering of I/O - including sparsely populated data
blocks - across the table. My most 'noticeable' experiment
(WHICH SHOULD NOT BE EXTRAPOLATED to a full size system)
was to discover a single row insert to a newly created
table resulting in a 16 block tablescan.

I suspect that there may be some significant strategy,
or implementation, changes pretty soon for this feature.

Jonathan Lewis
Author: Practical Oracle 8i

Now running intensive seminars

http://www.jlcomp.demon.co.uk/seminar.html
Hosting the Oracle usenet FAQ
www.jlcomp.demon.co.uk/faq/ind_faq.html

September 29, 2003 Ganesh -- Thanks for the question regarding "QA
      database performance testing using stats from Production DB", version
8.1.7

      You Asked
Tom,
  Copying stats from one database to another (without copying the data) to
simulate the source environment .. is this something you recommend as regular
practice? This means fiddling with objects owned by SYS and I was little
surprised when this was a standard practice with a major client here. Appreciate
your thoughts as usual.

Regards,
Ganesh.

      and we said...
it is done naturally via dbms_stats.

it does not involve fiddling with anything in SYS.

There are reasons to export/import stats

but in this case, it is a meaningless practice.  So what -- you have the same
query plan.  What can you tell about that?  Nothing, nothing at all, nada.  You
have a system with little or no data.  you run a query with the same plan as a
really big database.  It runs fast.  so what?  can you extrapolate that up to
the big database?  not a chance.

If you want to test, you need representative data, period.
Review &
      Followup

      Rating: 4
To the
      point  September 29, 2003
Reviewer:  Ganesh
       from San Jose

September 29, 2003 Kevin -- Thanks for the question regarding
      "Transactions and Isolation Level Confusion", version 8.1.7

      You Asked
Hi, Tom.

I have some questions about transactions and isolation levels that come from my
reading of your books and answers in this forum, as well as the Oracle Database
Concepts.

1.  You've said that using serialazable isolation level does not negatively
affect performance overall:

http://asktom.oracle.com/pls/ask/f?p=4950:8:2084716298042783235::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:776821414494,
However, the Concepts manual seems to indicate there is a performance tradeoff
between serializable and read committed:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/c21cnsis.htm#2799
Quoting from the section, "Read Committed Isolation":

"Read committed isolation can provide considerably more concurrency with a
somewhat increased risk of inconsistent results due to phantoms and
non-repeatable reads for some transactions."

"Many high-performance environments with high transaction arrival rates require
more throughput and faster response times than can be achieved with serializable
isolation."

Is this just a difference in scale?

2.  If there is not a significant performance difference (in the context of a
particular application, of course) it seems to me that, in general, one would
set the isolation mode to serializable instead of read committed so that one
would have repeatable reads, etc.

Are there criteria you use to determine what you should set your isolation level
to?  I'm mostly interested in OLTP applications.  I can't think of a good reason
to set it to anything but serializable in my application, but I think I'd
benefit from wider experience.

3.  Also, in the following link you seem to indicate that a session isn't always
in a transaction in read committed mode (assuming that some SQL has been
issued):

http://asktom.oracle.com/pls/ask/f?p=4950:8:2084716298042783235::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:8913646338893,
You make a similar statement in Expert One-on-One, chapter 4, "Transaction
Control Statements":  "A transaction implicitly begins with the first statement
that modifies data ...".

However, in the Concepts Guide, it seems to state clearly that one is always in
a transaction if SQL has been executed:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/c01_02intro.htm#54005
Can you resolve my confusion, here?

Thanks!

--Kevin

      and we said...
1) i disagree with their conclusions.

We run our TPC-C's in serializable (it is a requirement of that benchmark).  I
would in fact say the opposite is many times true -- that serializable is one
way to achieve high throughput and faster response times -- with less code.

2) no, no you wouldn't.  serializable will increase the risk of transaction
failure as the duration of the transaction increases.  It is appropriate for
exactly those high transaction arrival rates -- with small, fast, quick in out
transactions.  It is not in fact appropriate for many systems.

I use read committed almost exclusively.  Serializable is only when I need
multi-statement read consistency which is rare -- and even then, it is mostly
with regards to reports and then I would use read only.

3) a select does not begin a transaction in read committed mode.  a modification
will.  In serializable the "set" command marks the beginning of the transaction
(IT would be the first statement)

Consider:

ops$tkyte@ORA920> select * from dual;

D
-
X

ops$tkyte@ORA920> set transaction isolation level  serializable;

Transaction set.

If select marked the beginning, the set transaction would have FAILED like it
does in the following:

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920> update emp set empno = 1 where rownum = 1;

1 row updated.

ops$tkyte@ORA920>  set transaction isolation level  serializable;
 set transaction isolation level  serializable
*
ERROR at line 1:
ORA-01453: SET TRANSACTION must be first statement of transaction

and the following shows the first statment of the transction is the SET
command:

ops$tkyte@ORA920> set transaction isolation level serializable;

Transaction set.

ops$tkyte@ORA920> declare
  2  pragma autonomous_transaction;
  3  begin
  4  delete from emp;
  5  commit;
  6  end;
  7  /

PL/SQL procedure successfully completed.

Now, we'll query emp -- if we see data, then the transaction was already begun
and the data is as of the point in time we did the set transaction

ops$tkyte@ORA920> select empno from emp;

     EMPNO
----------
         1
      7499
      7521
      7566
      7654
      7698
      7782
      7788
      7839
      7844
      7876
      7900
      7902
      7934

14 rows selected.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920> select empno from emp;

no rows selected

that shows that SET was the first statment of the transaction

The only statements that really begin transactions, that serve as start points,
are modification statements and set transaction statements.

a select could if it was a select for update, but a select for update is really
just a modification statement in disguise.
Review &
      Followup

      Rating:
      3
TPC-C  September 29, 2003
Reviewer:
      malcolm
> We run our TPC-C's in serializable (it is a requirement
> of that benchmark).

But I thought Oracle (and possibly mainframe DB2) is the only database to
support serializable isolation level. I didn't think that any other databases
had the mutli-version read consistancy which is required. So how do they publish
TPC-C results without meeting the requirements? Or am I a victim of Oracle
propaganda? ;-)

          Followup:

serializable does not require multi-versioning (and DB2 surely doesn't do
multi-versioning).

serializable can be accomplished via read locks -- at the cost of concurrency.

      Rating: 3
Just to
      clarify  September 29, 2003
Reviewer:
      A reader
Multi-versioning can not help in the support of the serializable isolation
level. So mayby you are a victim of Oracle propaganda :)

          Followup:

umm, our serializable model is entirely built on the premise of multi-versioning
actually.

Oracles approach to serializable transactions is multi-versioning.
The others use locks.

no victims here...

      Rating: 5
RE: So mayby
      you are a victim of Oracle propaganda  September 29, 2003

Reviewer:  Mark A. Williams  from Indianapolis, IN
      USA
> So mayby you are a victim of Oracle propaganda

In what way?  What elaborations can you make in regard to that statement?

- Mark

      Rating: 5
Victim of some
      kind...  September 30, 2003
Reviewer:
      malcolm
Well, perhaps I'm a victim of some kind... I've been working solely with Oracle
for so long that I've completely forgotten about the existence of 'read locks'.
:-)

September 29, 2003 Reji -- Thanks for the question regarding "How to
      calculate the actual size of a table?", version 8.1.5 & 8.1.6

      You Asked
Tom:
    How to calculate the actual size of a table?
    I was looking at one of your previous listing about
    vsize and that may be one way of doing column by column
    but if there is a tool to do at once that will be a great
    help.

    TIA
    Reji

      and we said...
see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:231414051079
for a discussion of the use of ANALYZE and DBMS_SPACE to this
end.  Basically -- you can find out how many blocks are
allocated to the table (whether used or NOT), how many blocks
have never been used (subtract them from above) and on the
blocks that are used -- the average free space.

For example, in the example from above that I linked to -- we
see:

ops$tkyte@DEV8I.WORLD> create table t ( x int,
                                 y char(2000) default '*' )
  2  storage ( initial 40k next 40k minextents 5 )
  3  tablespace system;

Table created.

A table that will create ~2k rows for each row inserted.
makes it easy to do the math

ops$tkyte@DEV8I.WORLD> insert into t (x) values ( 1 );
1 row created.

ops$tkyte@DEV8I.WORLD> analyze table t compute statistics;
Table analyzed.

ops$tkyte@DEV8I.WORLD> compute sum of blocks on report
ops$tkyte@DEV8I.WORLD> break on report
ops$tkyte@DEV8I.WORLD> select extent_id, bytes, blocks
  2    from user_extents
  3   where segment_name = 'T'
  4     and segment_type = 'TABLE'
  5  /

 EXTENT_ID      BYTES     BLOCKS
---------- ---------- ----------
         2      81920         10
         3     122880         15
         4     163840         20
         0      40960          5
         1      40960          5
                      ----------
sum                           55

ops$tkyte@DEV8I.WORLD> clear breaks
ops$tkyte@DEV8I.WORLD> select blocks, empty_blocks,
  2         avg_space, num_freelist_blocks
  3   from user_tables
  4  where table_name = 'T'
  5  /

    BLOCKS EMPTY_BLOCKS  AVG_SPACE NUM_FREELIST_BLOCKS
---------- ------------ ---------- -------------------
         1           53       6091                   1

Ok, the above shows us:

o we have 55 blocks allocated to the table
o 53 blocks are totally empty (above the HWM)
o 1 block contains data (the other block is used by the system)
o we have an average of about 6k free on each block used.

Therefore, our table

o consumes 1 block
o of which  1block * 8k blocksize - 1 block * 6k free = 2k is
used for our data.

Now, lets put more stuff in there...

ops$tkyte@DEV8I.WORLD> insert into t (x)
  2  select rownum
  3    from all_users
  4   where rownum < 50
  5  /
49 rows created.

ops$tkyte@DEV8I.WORLD> analyze table t compute statistics;
Table analyzed.

ops$tkyte@DEV8I.WORLD> compute sum of blocks on report
ops$tkyte@DEV8I.WORLD> break on report
ops$tkyte@DEV8I.WORLD> select extent_id, bytes, blocks
  2    from user_extents
  3   where segment_name = 'T'
  4     and segment_type = 'TABLE'
  5  /

 EXTENT_ID      BYTES     BLOCKS
---------- ---------- ----------
         2      81920         10
         3     122880         15
         4     163840         20
         0      40960          5
         1      40960          5
                      ----------
sum                           55

ops$tkyte@DEV8I.WORLD> clear breaks
ops$tkyte@DEV8I.WORLD> select blocks, empty_blocks,
  2         avg_space, num_freelist_blocks
  3    from user_tables
  4   where table_name = 'T'
  5  /

    BLOCKS EMPTY_BLOCKS  AVG_SPACE NUM_FREELIST_BLOCKS
---------- ------------ ---------- -------------------
        19           35       2810                   3

Ok, the above shows us:

o we have 55 blocks allocated to the table (still)
o 35 blocks are totally empty (above the HWM)
o 19 blocks contains data (the other block is used by the
system)
o we have an average of about 2.8k free on each block used.

Therefore, our table

o consumes 19 blocks of storage in total.
o of which  19 blocks * 8k blocksize - 19 block * 2.8k free =
98k is used for our data.

Given our rowsize, this is exactly what we expected.

Review &
      Followup

      Rating: 5
Well described,
      thank you tom.  January 30, 2002
Reviewer:
      Gururaj Kulkarni  from Norcross, GA

      Rating: 4
avg_row_len vs
      vsize  August 05, 2002
Reviewer:  Prince

Tom,

In the following example "avg_row_len" from all_tables shows "1009", while the
row_size calculated based on vsize is 1002. Why is the difference?

Does avg_row_len include rowid?

Also, does avg_row_len is the number of characters as we see them or the bytes
needed for the physical storage?

ex. vsize(100000) = 2.
 Is the avg_row_len in this case is 2 or 6?.

create table t ( col1 number, col2 char(1000) default '*' );

insert into t (col1) select rownum from all_tables where rownum < 50;

analyze table t compute statistics ;

select NUM_ROWS, BLOCKS, EMPTY_BLOCKS, AVG_SPACE, AVG_ROW_LEN, NUM_ROWS,
NUM_FREELIST_BLOCKS
from all_tables where owner = 'DBUSR' and table_name = 'T';

  NUM_ROWS     BLOCKS EMPTY_BLOCKS  AVG_SPACE AVG_ROW_LEN   NUM_ROWS
NUM_FREELIST_BLOCKS
---------- ---------- ------------ ---------- ----------- ----------
-------------------
        49         20           11       1530        1009         49
      4

select sum(vsize(rowid)), sum(vsize(col1)), sum(vsize(col2)), avg(vsize(col1)),
avg(vsize(col2)) from t ;

 SUM(VSIZE(COL1)) SUM(VSIZE(COL2)) AVG(VSIZE(COL1)) AVG(VSIZE(COL2))
 ---------------- ---------------- ---------------- ----------------
               98            49000                2             1000

          Followup:

vsize is not taking into consideration the null/not null flags and the leading
byte count on numbers/chars.

it is not the rowid (rowid is the ADDRESS of the row -- the file, block and slot
on the block).  The rowid is INFERRED data, not data physically present on the
row.  The rows location is it's rowid.  The rowid is not stored with the row but
rather derived from its location.

      Rating:
      5
  August 16, 2002
Reviewer:
      Thorsten  from Bremerhaven, Germany, Europe
This is the first time I seek for information here ...
and I'm impressed !
Perfect. You have the ability to guide people through
the answers to their questions.
Thanks.

      Rating: 5
Calculate total
      size of "keep" buffer pool  September 29, 2003

Reviewer:  A reader  from NJ
Tom, how can I calculate the current consumed size of the keep buffer pool?
(This is in order to determine if I should increase the size of my keep buffer
pool). Thanks.

          Followup:

well, i don't see how the currently consumed size will tell you that?  We keep
consistent read versions of blocks in there so the same block could be there
over and over -- some of them never to actually be used.  So, you could be
"full" but only really use 5% of the data in there.  Would you add more??

but you can look at v$buffer_pool_statistics to sort of calculate a "hit ratio",
not that a hit ratio will be meaningful (our goal is actually to REDUCE LIO's --
my tuning approach actually LOWERS the cache hit!!)

you could use v$bh to count blocks as well -- there is an objd - the database
object number, you can find all of the blocks for the objects in the keep pool

      Rating: 4
I used this
      based on your earlier solution  September 29, 2003

Reviewer:  A reader  from NJ
I used this SQL based on your earlier solution:
select sum(bytes), sum(user_extents.blocks)
from user_extents, user_tables
where segment_name=table_name and buffer_pool='KEEP';

BTW, if I alter storage of certain tables to buffer_pool keep, is there a way to
keep automatically move all the indexes on these tables to keep pool as well?
Or, do I have to "alter index" buffer_pool keep for each index?

Thanks.

          Followup:

???  you should let a cache cache.  it looks like you are trying to allocate
enough space to hold "all data" -- that'll never work (multi-versioning).

anyway... remember logical IO is darn expensive, hugely so.  don't look to the
cache as "fast=true".  look to reduce IO across the board.

you assign segments to the buffer pools, like you assign segments to
tablespaces.

September 30, 2003 Shane -- Thanks for the question regarding
      "Questions on Pro*C multi-threaded programming", version 8.1.7

      You Asked
Hi Tom,

I am working on a project using Pro*C. I do find out lots useful info on this
website. Here are my questions:

1) Where is the header file in which the sql_context is defined?

2) I want to created a number of threads. Can I create a dedicated database
connection for each thread by mallocing a context in the beginning of the thread
routine?

3) I am confused by the positional EXEC SQL CONTEXT USE. In my thread function
call, several functions containing DML statements will be called in a sequence.
Should I use only one EXEC SQL CONTEXT USE in the beginning just before the
calls, or I have to put one EXEC SQL CONTEXT USE inside each of the functions.

Thanks a lot!

Shane

      and we said...
1) it is not, it is burned into the code when you precompile:

/* Result Sets Interface */
#ifndef SQL_CRSR
#  define SQL_CRSR
  struct sql_cursor
  {
    unsigned int curocn;
    void *ptr1;
    void *ptr2;
    unsigned long magic;
  };
  typedef struct sql_cursor sql_cursor;
  typedef struct sql_cursor SQL_CURSOR;
#endif /* SQL_CRSR */

/* Thread Safety */
typedef void * sql_context;
typedef void * SQL_CONTEXT;

It is a transparent type to you -- just a pointer -- the definition of what it
points to is neither relevant nor available.

2) you do not malloc a context, you use the EXEC SQL commands to allocate it:

sql_context do_connect( char * userid )
{
sql_context ctx;

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();
    exec sql context allocate :ctx;
    EXEC SQL CONTEXT USE :ctx;
    exec sql connect :userid;
    return ctx;
}

main()
{
sql_context ctx[2];
sql_context curr_ctx;
char        user[31];

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();
    ctx[0] = do_connect( "scott/tiger" );
    ctx[1] = do_connect( "tkyte/xxxx" );

    curr_ctx = ctx[0];
    EXEC SQL CONTEXT USE :curr_ctx;
    EXEC SQL select user into :user from dual;
    printf( "I am %s\n", user );

    curr_ctx = ctx[1];
    EXEC SQL CONTEXT USE :curr_ctx;
    EXEC SQL select user into :user from dual;
    printf( "I am %s\n", user );
}

for example uses an array to have 2 connections.  You might malloc/realloc an
ARRAY of sql_context pointers.

3) to be safe, I would put the context use before each EXEC SQL statement --
that way you KNOW what context is in use at all times.  The context use clause
generates NO CODE, so its overhead is zero.

Review &
      Followup

      Rating: 5
Great answers!
        January 27, 2002
Reviewer:  Shane Hu
       from NYC, USA
Thanks for your prompt response. It is of great help to me. You said that you
prefer to using Pro*C instead of OCI if the job can be done with Proc*C. But do
you agree that OCI is a bit more convenient for multithreaded application?

I am reading your book now, which is very well written. As for Pro*C and OCI, I
have not found any books other than the manuals. The Oracle University does not
provide training on these two topics, which makes me wondering that Pro*C and
OCI is out-of-date.

Thanks again.

          Followup:

I like pro*c cause pro*c is simple.  I can code:

  exec sql connect :userid;
  exec sql select count(*) into :cnt from t where x = :y;

Much much easier then I can code the equivalent OCI (or jdbc, or odbc, or ANY
3gl call level interface).

I don't see how using pro*c for multi-threaded apps is any easier or harder for
that matter then coding pro*c for non-multi-threaded apps.  Use a context and
there you go -- its done.  So, I would say that using pro*c for multi-threaded
apps is still easier then coding the equivalent in pro*c.

I would not say that pro*c or oci are "out of date".  Rather (this is my
personal opinion here) that something like pro*c is actually pretty
straightforward and once you've read the pro*c manuals -- there isn't too much
left to do in a book/class.  It is also a matter of supply and demand, there
just isn't a large demand for a "pro*c" class.

      Rating: 5
how to do a
      global commit?  April 16, 2002
Reviewer:  Shane
      Hu  from NYC
Tom,

A follow up question: I have a number of threads which have dedicated
connections. How can I do a commit across all of the threads (sessions). Thanks
again.

          Followup:

You must commit in each thread, it would be DISASTEROUS to commit another
threads work without having that thread do the commit.

If the work is all related -- it should be a SINGLE transaction, not different
ones.

      Rating: 4
Thanks Tom for
      your help.  April 16, 2002
Reviewer:  Firas
      Adel  from NC USA
Every time i come here I learn alot of things. Just one more question on the
subject please: Does the Oracle parallel server has to be runing to use
multithreading? I am runing an application and I am using threads.h but every
ime i try to spawn more than one thread, the process dies.

Thanks,
Firas Adel

          Followup:

NO, OPS is a way to have oracle run on a cluster, it has nothing to do with
multi-threading a client.

You need to use the proper libraries and such to do multi-threaded clients (not
just a .h file, the proper oracle libs) but it is supported in OCI, Pro*c and
Java.  You make no mention of any environment or version so all I can say is "it
works, its supported -- when done properly as documented"

      Rating: 5
What is
      OCI  April 16, 2002
Reviewer:  A reader

Tom, how do you program in OCI, when you say OCI  are you talking about a
programming languagae or programming languagae (s).

Can you give an example or some examples of OCI programming.

          Followup:

OCI = Oracle Call Interface.

It is a C api.

You program in C, we have functions you call.

See the documentation -- tons of OCI stuff in the OCI manual.

      Rating: 4
A
      reader  April 16, 2002
Reviewer:  A reader
       from USA
I have a demon written in pro*c .I have MTS parameter
in a init.ora.Recently i commented those parameter.
(because i have only 50-100 connetions)
After commenting ,i noticed my one demon fails once in a while.I asked the
person who wrote that,whether it uses any
mutlithread feature,and the reply was no.
After that i had no option other then enabling those feature
again.After enabling it works fine .What could be the problem ??
Your comment please

          Followup:

MTS is a connection feature -- it has NO effect on the client.  Using or not
using it will not "crash" the client.

You are seeing a false "cause and effect" in my opinion.  The effect is real "my
app crashed".  It was (in my opinion) a coincidence that it crashed during the
time you had the MTS commented out.  There was something else at work.

To test, you can set up another TNSNAMES.ORA entry that uses server=dedicated to
bypass the use of MTS (a connection option, NOT a client option).

You also don't mention at all what "fails once in a while" -- so further
diagnosing on my part would be further guessing on my part.

      Rating: 4
Questions on
      Pro*C multi-threaded programming  April 17, 2002

Reviewer:  Amit Tiwari  from India
dear tom,
You gave a code to use two different context. What i understand from that code
is that if i use two different contexts there are two live connections and i
have a choice of using any one of the connection. But when i tried to do the
same i.e. I COUNTED THE NUMBER OF SESSIONS AT EACH STEP AND FOUND OUT THAT THE
LIVE CONNECTION FORMED IS JUST ONE EVEN IF I CONNECT TO THE SECOND DB USER. i
again used the first context saying EXEC SQL CONTEXT USE :CTX[0] and tried to
fetch the user name and found out that it has maintained the last connection
i.e. "TKYTE".

          Followup:

I do not understand what you are saying.

There will be a SESSION per connection.

Each connection, if they use different usernames, will retrieve different values
for USER.

I do not understand your points.  Perhaps a test case, modelled after the way I
do mine (small, concise and complete -- so they fit here and ANYONE can run
them) along with expected results would clear this up.

      Rating: 1
Questions on
      Pro*C multi-threaded programming  April 17, 2002

Reviewer:  Amit Tiwari  from India
I'm attaching a code. When I connect I have 24 connection i one user while 21 in
other. Now I expect it to remain same for a context depending on the context i
use. I want two live connections , one each for a thread.

The code is tested on solaris 5.6

#include <stdio.h>
#include <pthread.h>

EXEC SQL INCLUDE SQLCA;

void logon(sql_context ctx, char * uid);
void *t1(sql_context *);
void *t2(sql_context *);
main()
{
    sql_context ctx[2], ctx2;
    pthread_t    t_id[2];
    int i;
    char uid[2][100];
    strcpy(uid[0],"rstest/xxxxxx@rstest");
    strcpy(uid[1],"iiss_test/yyyyyy@esstest");
    printf("%s    %s\n",uid[0], uid[1]);

    EXEC SQL ENABLE THREADS;

    for (i=0; i<2; i++)
    {
        EXEC SQL CONTEXT ALLOCATE :ctx[i];
        logon(ctx[i], uid[i]);
    }
    if (pthread_create(&t_id[0], NULL, t1, (void *)&ctx[0]))
        printf("cant create thread 111\n");
    if (pthread_create(&t_id[1], NULL, t2, &ctx[1]))
        printf("cant create thread 222\n");
    for (i=0; i<2; i++)
        pthread_join(t_id[i],NULL);
}
void  *t1(sql_context *ctx)
{
    int l_conn,i=0;
    printf("Thread 11\n");
    exec sql context use :ctx;
    while (i!=10)
    {
    exec sql select count(*) into l_conn from v$session;
    printf("T111 Connections = %d ERROR = %d  i= %d \n", l_conn,
sqlca.sqlcode, i);
    i++;
    }
    return ;
}
void  *t2(sql_context *ctx)
{
    int l_conn, i=0;
    printf("Thread 22\n");
    exec sql context use :ctx;
    while (i!=10)
    {
    exec sql select count(*) into l_conn from v$session;
    printf("T222 Connections = %d ERROR = %d i = %d\n", l_conn,
sqlca.sqlcode, i);
    i++;
    }
}
void logon(sql_context ctx, char *uid)
{
    int l_conn;
    exec sql context use :ctx;
    exec sql connect :uid;
    exec sql select count(*) into l_conn from v$session;
    printf("Connections = %d  ERROR=%d\n", l_conn, sqlca.sqlcode);
}

Output:
rstest/rstest@rstest    iiss_test/testdata@esstest
Connections = 24  ERROR=0
Connections = 21  ERROR=0
Thread 11
Thread 22
T111 Connections = 0 ERROR = 0  i= 0
T222 Connections = 21 ERROR = 0 i = 0
T222 Connections = 21 ERROR = 0 i = 1
T111 Connections = 0 ERROR = 0  i= 1
T222 Connections = 21 ERROR = 0 i = 2
T222 Connections = 21 ERROR = 0 i = 3
T111 Connections = 0 ERROR = 0  i= 2
T222 Connections = 21 ERROR = 0 i = 4
T222 Connections = 21 ERROR = 0 i = 5
T111 Connections = 0 ERROR = 0  i= 3
T222 Connections = 21 ERROR = 0 i = 6
T222 Connections = 21 ERROR = 0 i = 7
T111 Connections = 0 ERROR = 0  i= 4
T222 Connections = 21 ERROR = 0 i = 8
T222 Connections = 21 ERROR = 0 i = 9
T111 Connections = 0 ERROR = 0  i= 5
T111 Connections = 21 ERROR = 0  i= 6
T111 Connections = 21 ERROR = 0  i= 7
T111 Connections = 21 ERROR = 0  i= 8
T111 Connections = 21 ERROR = 0  i= 9

          Followup:

you are connecting to two different databases. why would you expect them to be
the same.

You have a huge bug -- you are sharing a sqlca between two threads.  you best
start declaring local sqlca structures -- I will *guess* that you are actually
hitting an error since 0 connections (count(*) in v$session) is impossile.

      Rating: 4
Questions on
      Pro*C multi-threaded programming  April 18, 2002

Reviewer:  Amit Tiwari  from India
I'm sorry for not being very clear about my problem. Yeah I realize that there
was a bug in the code. But I'll try to explain the poblem again.
My problem is that when I run the code:

#include <stdio.h>
EXEC SQL INCLUDE SQLCA;

sql_context do_connect(char * uid);
main()
{
sql_context ctx[2];
sql_context curr_ctx;
char        user[31];

/*    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();*/
    ctx[0] = do_connect( "rstest/rstest@rstest" );
    ctx[1] = do_connect( "iiss_test/testdata@esstest" );

    curr_ctx = ctx[0];
    EXEC SQL CONTEXT USE :curr_ctx;
    EXEC SQL select user into :user from dual;
    printf( "I am %s\n", user );

    curr_ctx = ctx[1];
    EXEC SQL CONTEXT USE :curr_ctx;
    EXEC SQL select user into :user from dual;
    printf( "I am %s\n", user );
}
sql_context do_connect(char * uid)
{
    char        user[31];
    sql_context ctx;
    exec sql context use :ctx;
    exec sql connect :uid;

    strcpy(user,"");
        EXEC SQL select user into :user from dual;
        printf( "connected to  %s\n", user );
    return ctx;
}

The output is:

connected to  RSTEST
connected to  IISS_TEST
I am IISS_TEST
I am IISS_TEST

The problem is that the program maintains the last connection it had made,
whereas I want two live connections.
I expect the output to be :

connected to  RSTEST
connected to  IISS_TEST
I am RSTEST
I am IISS_TEST

Sorry again from wasting your valuable time by not putting the problem clearly.
I hope this time the problem is understandable.

Thanks and Regards

Amit Tiwari

          Followup:

ADD ERROR HANDLING.  YOU have none.

Your program, as coded above, cannot be producing that output at all.  Just
can't.

You are missing the exec sql context allocate :ctx;

Your output is not from the program you posted (arg!  that's a waste of time,
the only conclusion I can come to is that the code posted is not the code
executed).

Please -- only post that which you actually used (without that statement, your
program either:

o seg faults immediately
o prints out garbage

As soon as I put the statement in:

connected to  SCOTT
connected to  OPS$TKYTE
I am SCOTT
I am OPS$TKYTE

was my output)......

      Rating: 4
Multi-thread
      programming in PL/SQL  May 08, 2002
Reviewer:
      Pushparaj Arulappan  from NJ,USA
Tom,

I have compiled and executed your example pro*c program
for Multi-thread programming.
Is it possible to achieve the multi-thread concept in a
PL/SQL programming. ?

Thanks
Pushparaj

          Followup:

Not truly.  You can achieve the equivalent of a "fork()/exec()" in C using
DBMS_JOB.  That will background a stored procedure to be executed in another
session separate from yours.  A totally new session is created for that.

      Rating: 3
PL/SQL vs
      Pro*C  September 02, 2003
Reviewer:  Jeff
       from Seattle
Tom,

I remember you mentioning which gives better performance with reasons but cannot
find the message.

Will I get better performance with a pl/sql procedure/package over a pro*c
script? How to decide which one to pick, if given a choice?

Thanks in advance!

          Followup:

you want to unload 1,000,000 rows to a flat file in the operating system?

pro*c will toast plsql.

you want to loop over the rows in a table and perform some little tweaking of
the data and update another table

plsql will toast pro*c.

I tend to pick plsql -- until it is not right, then I pick pro*c.  OS related
things (create me a big file) -- pro*c.  DB related things (process this data)
-- plsql.

However, I did have to compute a nasty string function once -- in plsql it would
have taken 5 days -- we could have gotten down to like a day in parallel.

Doing it in pro*c -- where I could do the calculation (it was intense) 2 orders
of magnitude faster -- we did it in 3 hours.

So, i look at the problem, prototype it in plsql, see "will it be better then
good enough" and if not code in C.

      Rating:
      4
  September 02, 2003
Reviewer:  Jeff
       from Seattle
Thanks Tom!

Since I am reading from a big monster table and checking for certain values in
various other tables and finally updating a target table, I guess my best bet
would be to use pl/sql.

Anyway, as you said, I will first try it in pl/sql.

Thanks again.

          Followup:

no no no!!!!

first try it in SQL and SQL alone!!!!!!

my mantra:

a) do it in a single sql statement
b) use a tiny bit of plsql if you cannot
c) drop down to java stored procedure if plsql isn't "able" to do it (eg: an
ftp)
d) if you feel the need for speed after b & c -- try C

      Rating: 5
Commit
      once  September 29, 2003
Reviewer:
      A reader
Hi Tom,

You say "If the work is all related -- it should be a SINGLE transaction, not
different ones. ". Does it mean that we cannot have a transaction spaning
multiple threads? Say I want to update a partition table, each partition update
being done in it's own thread, and commit once in the end? If there is a error
in one thread rollback all, and try later, may be? This is to avoid adding
restart features/ code in the program?

I know in Pl/SQL parallel DML this is achieved through implicit 2PC accross
parallel threads (running in their own sessions?). How can this be done when
programming in Pro*C multi-threading? And, also if there is an error in one
thread, how can it make all threads to abort.

Thanks

          Followup:

you would accomplish the above NOT by writing a ton of code but by simply
issuing "update"

there would be no sensible reason to do it yourself.

You can have a transaction span multiple threads.  what you want tho isn't that,
you want a transaction to span multiple sessions since in a session only one
statement is going to happen at a time (hence you need more then one session in
order to achieve your stated goal).  You would need to use something like the XA
interface and an external resource manager.

In other words, it gets really ugly, really fast.

      Rating:
      5
Thanks  September 29, 2003
Reviewer:
      A reader
Thanks Tom,
Please confirm my understanding
1. A transaction can span multiple threads, this can happen only when the
threads do not use their own separate sql contexts. OR sql context has to be
shared between the threads.
2. Otherwise, if a transaction has to span multiple threads, XA interface and
external resource manager has to be used. This external resource manager can be
a TP monitor like BEA tuxedo. And, this kind of programming is conplex and
should be avoided.

          Followup:

the problem is that "threads" has nothing to do with "transactions" really.

the threads you are refering to are a client "thing", they mean nothing at all
to the Database.  The database has NO CLUE whether you are running with threads
or not -- nor does it care.

The crux of the matter comes down to how many sessions you have.  One session
(one sql context) or many sessions -- many contexts.

A transaction should be done in a single session.
Trying to do it otherwise is really ugly.

September 30, 2003 TomBoy -- Thanks for the question regarding
      "asktom's Architecture.", version 8.1.7

      You Asked
Hello Sir :

I know that , i don't have rights to ask this questions to you.But still i am
trying. Sir ...Can u please explain me the Architecture of your site. It means
the Work-Flow , how u are getting different questions from various people , how
u are giving the reply & storing all question on site as a Archivival & Some
under HOT ARTICLE & some under Most Popular.

Your application using which technology as a FRONT-END. what Application/Web
Server the site is using.

Sir .. Please provide the detailed description of same , if possible.

Thaks a lot for ur Time.

Warm Regadrs
- TomBoy.

      and we said...
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1995355238285
covers most of that.

The "work flow" piece is very simple.

I control the max number of submitted but never read questions (usually 10).
This is done via a trigger.  I have a flag in my questions table that tells me:

o not answered
o read/more info supplied
o read/answered -- do not publish
o read/answered -- published
o read/awaiting more info
o deferred

Questions in the first two categories are the ones I'm interested in -- they are
the ones that I have to answer.  The others are what I display on the website
(when published).

Hot article/most popular -- those are just queries.  when you read a document,
we increment a counter on it.  So, finding the recently hot items and the
overall most popular is a simple sort.

Archival -- again, that's easy.  When I touch an article (update it), the
timestamp is updated.  Simple query.

Review &
      Followup

      Rating: 3
Search in
      asktom  August 26, 2002
Reviewer:  Irfan
       from Kuwait
Hi Tom,
Recently that if we search in your site;
The number of records found does not match the records displayed. For example if
I do a search for dbms_flashback, the header says, Approximately 17 records
found, but only 3 questions are displayed.

Thanks

          Followup:

It has always said approximately, just like google says "Results 1 - 10 of
about 6,510,000. " when you search for Oracle.

It goes right along with my belief that you should NEVER do a count(*) before
running a query to tell the user how many rows will result (that is an utter
waste of time).  We approximate and only give a page forward (notice how google
lets you access the first 10 pages only -- there is no "last page", it would
take google FOREVER to get to the page with hit 6,000,000 on it!)

Also, on google, sometimes it says "showing 1-10 of about 100" and if you click
on page ten it says "whoops -- we'll just drop you off at page 7 as we've just
discovered there are really only 72 hits!"

approximately.....

If you want see this effect on google, run this link:
http://www.google.com/search?q=%22active+noise+cancellation%22+virginia+northern&hl=en&lr=&ie=UTF-8&oe=UTF-8&start=0&sa=N
it should say (as of Aug 26 2002 at 8am EDT anyway) results 1-10 of about 47.
Now, click on page 4 and see what happens (you'll be on page three looking at
results 21-23, there are only 23)

      Rating: 3
Sentence
      search  August 26, 2002
Reviewer:  Martin
       from Amsterdam, NL
Tom,

Is it Possible to search for an exact sentence on your site?
Like you would search for "exact sentence" (quoted string) on google? It doesn't
seem to work that way on your site, or does it?

Tia,

Martin

          Followup:

Your wish has been granted.  It is goole-esque.

"online operations"

will find only things with that phrase

online operations

will find all with online AND operations somewhere.

You can mix them up, eg:

this is a test of "the emergency broadcast system" had this been a real test

works as expected (well, as I expect it to work ;)

Interested in the software behind ASKTOM, the stuff used to develop the
screens?  Well, it's going "public".  Stop by:

http://asktom.oracle.com/pls/ask/z?p_url=http%3A%2F%2Fotn.oracle.com%2Fhosted_dev%2Fcontent.html&p_cat=MARVEL&p_company=10
the Oracle Technical Network (OTN) and read about it (click on Project Marvel
and then "Tell me more" or "Quick Tour"

      Rating: 4
problem with
      project Marvel link  August 26, 2002
Reviewer:
      Doiug  from CT, USA
Tom.. when I try to click on that project marvel link I get asked for
authentication to "FORMS" on the asktom site.
I've tried it in IE and Mozilla.

          Followup:

WHOOPS, fixed that.  sorry.

      Rating: 4
sorry.. I mean
      FLOWS from asktom  August 26, 2002
Reviewer:
      Doug  from CT, USA

      Rating: 5
Thanks for
      fixing the search function  August 27, 2002

Reviewer:  Martin  from Amsterdam, NL
Thanks Tom,

It's good to see even you make coding errors sometimes ;)

          Followup:

Hey now, that was no coding error - it was the intended behaviour :)

What you proposed is known as an "enhancement request".  I changed the
formatting of the Oracle Text query.  Before when you entered:

"how now" brown cow

we stripped out lots of stuff (special characters), turning that into

how now brow cow

and then

{how} and {now} and {brown} and {cow}

I enhanced the routine to look for balanced quotes and upon finding them --
treat that as the word.  So, now the query is

{how now} and {brown} and {cow}

      Rating: 5
Estimating the
      number of query results....  August 27, 2002

Reviewer:  Mark J. Bobak  from Dearborn Heights, MI

Hi Tom,

When you estimate or approximate the number of query
results, how do you do that?

What coding mechanism or technique do you use to do that?

Thanks!

          Followup:

I use ctx_query.count_hits

      Rating: 4
Whoops
        August 27, 2002
Reviewer:  Martin
       from Amsterdam, NL
Sorry to have doubted your coding, Tom ;)

Keep up the good work, I really enjoy reading your site and soon i'll be
enjoying your book as well

      Rating: 5
approximate
      results  August 27, 2002
Reviewer:  Dan
       from NY
You wrote: "It goes right along with my belief that you should NEVER do a
count(*) before running a query to tell the user how many rows will result (that
is an utter waste of time).  We approximate and only give a page forward..."

This is an intruiging statement to me. Tom, how do you apply this to a typical
select statement in a typical web application? That is, how do you determine the
size of the result set rows in order to let users page through results if not by
a count(*)? (I'm guessing intermedia and google both have additional data
structures that allow them to efficiently over-estimate the number of results.)

          Followup:

I don't -- goto my search page and you'll see.

goto that google link above -- they don't either (they "guess" and when wrong,
don't really admit it -- just sort of "fix it up" on the fly)

intermedia uses its index to get a guesstimate and google most likely has their
own algorithm.

In most cases, I give you simply this information:

a) your search ran
b) here are the first 10 hits
c) if there are more then 10 hits (eg: that 11th fetch that I didn't print
returned with data) I'll give you a NEXT button

And that is all.  I don't tell them "hey you got N records".  Sometimes I even
do this:

select *
  from ( THEIR QUERY HERE  and rownum <= 500 )
 order by whatever;

just to limit it to 500 hits (knowing full well that NO one is going to hit page
down 50 times and if they do -- too bad for them -- their search was not
selective enough)

      Rating: 5
ignore
      last  August 27, 2002
Reviewer:  Dan
       from NY
duh... missed the answer above.

      Rating: 3
Sentence
      search  August 28, 2002
Reviewer:  Paul
       from UK
Tom,

I like you enhancement a lot - I was using it to look for "function-based
indexes", in fact.

However, I notice that it's not quite perfect.

Searching for <"how now" brown cow> just seems to do a search for {how now},
though <"how now" brown "cow"> works fine.  It seems to be ignoring any
non-quote-enclosed strings after the last occurrence of a quote-enclosed string,
or something like that.

It's interesting to see how many entries include the word "cow" - this site must
be used by the farming community a lot :-)

          Followup:

He he, amazing what a difference ONE line of code can make sometimes ;)

It is nearer to perfection now...

      Rating:
      5
askus???  September 10, 2002
Reviewer:
      Fenng  from China
hi,Tom ,I know there is a site that called "askus" ,
very similar to asktom ,
I want to know what is the relation about the 2 site?

          Followup:

we run a couple copies of this -- devtrends.oracle.com for example is another
"askus" piece of software (same application, different guy answering the
questions)

      Rating: 5
I mean
      here:  September 10, 2002
Reviewer:  Fenng
       from china
http://osi.oracle.com/pls/askus/f?p=4951:1

          Followup:

That is help for the Project Marvel piece of software which is what was used to
write askus (aka asktom)

      Rating: 5
query on
      asktom  January 16, 2003
Reviewer:  mo

Tom:

When I search on your site for "CURSOR" it will tell me there are 1132 records
found and display two pages with about 30 -40 records.

None of those had the article I was looking for labeled "CURSOR". I had to go to
the weekly list to get to it. I think maybe something is not right with search
utility here.

THanks,

          Followup:

if you would like to understand the algorithm used:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/inter.817/a77063/ascore.htm#272
it is not a simple "search the subject" -- it is a function of how often things
are referenced in the document.  "cursor" -- very generic.  goto google and
search for "cursor" -- over 3MILLION hits.  It is a tad vague -don't you think?

Amazingly -- due to the sheer NUMBER of comments you made -- a search for

cursor mo

turns it right up....  You need to be a tad more specific.  search isn't
"broken", search terms were way too broad.

      Rating: 5
PLSQL
      Pages  January 16, 2003
Reviewer:  Christophe
      Parmentier  from France
Nice to learn your site works.

Is this a pl/sql generated webpage like i've read in the Oracle Doc? I had doubt
regarding performances but il looks pretty fast ;)

          Followup:

all of it is plsql, yes.

      Rating: 5
Still more
      questions!  January 17, 2003
Reviewer:
      Christophe Parmentier  from France
Wow, i may get a look at these plsql webpages then ;)

FMI, could we know on what's your Oracle Server running to get these
performances with such a big DB ?

Thx!

          Followup:

search for

Interested in the software behind ASKTOM

on this page to see the tool (marvel) we used.

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1995355238285
describes the hardware environment and what else is running on here.

      Rating: 5
Top 5 Search
      Terms  January 26, 2003
Reviewer:  Jerry
       from Washington, DC USA
Any chance that you'll consider publishing something like the "Top 5" search
terms, either cumulatively, or for a weekly period?

I am curious as to what people are working on or confused about.

I remember you published them once and I found it very interesting.

          Followup:

always more to do, i'll put it in the queue ;)

      Rating: 3
URL not
      found  February 09, 2003
Reviewer:
      A reader
Hi Tom

I have marked following url adress of your useful site:

http://asktom.oracle.com/pls/ask/f?p=4950:8:2013863::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:7644437779866,
This link is not found. Could you get me the new link for this topic.
Best Regards.

          Followup:

In the future use the "book mark this page" link to bookmark.

but for now, just remove the sessionid from the URL -- 2013863, it'll start a
new session for you.

http://asktom.oracle.com/pls/ask/f?p=4950:8:::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:7644437779866,

      Rating: 5
Many thanks
      Tom  February 10, 2003
Reviewer:
      A reader

      Rating: 3
Bug in search
      feature?  February 24, 2003
Reviewer:
      Martin  from The netherlands
Tom,

Something might be wrong with your search feature. When searching for "The New
Ask Tom" which is an existing question title nothing is found. I can still see
it under the your questions-tab and according to that list it should be
published?

          Followup:

got it -- thanks for the example, its fixed now.

      Rating: 3
While your at
      it....  February 24, 2003
Reviewer:  Jonas
       from Berkeley, CA
Can you please fix the back button after you click through to an article from a
search or frontpage? The previous page expires almost immediately. By the time
read the article, you have to click back, click refresh, then click a dialogue
button to repost the data. Its a tad painful :)

          Followup:

seems to me that would be a function of your browser?

I use mozilla, I've never had this happen.

I just tried IE 50, cannot get it to happen.

questions:

is it happening to others?
if so, what browser are you using and what are your cache settings?

      Rating: 3
brower type and
      cache settings  February 24, 2003
Reviewer:
      Jonas
IE 6 on XP. cache lookup is set to "automatic" with plenty of disk.

Tested in Mozilla and it worked fine.

      Rating: 5
9ias' database
      connection pooling  May 11, 2003
Reviewer:
      Sean  from Atlanta
I understood that asktom using the 9ias. I am learning the 9ias r2 architecture.
There is data-sources.xml in the j2ee home, which is for config of the database
connectivity. How do you manage asktome.com database sessions? If you use OC4J,
how do you set the parameters?:
max-connections, min-connections, and inactivity-timeout.
If you do not use J2EE, then what's your database connection pooling scheme? I
assume you are not using the dedicated server.

Thanks.

          Followup:

I use plsql, short of a java procedure to send emails using the java mail api
(and that happens in the background via dbms_job), there is no java involved in
this site.

we use mod_plsql exclusively.  It scales well beyond my needs, is simple to use,
have very few "moving parts" that need to be coordinated to accomplish
something.

      Rating: 5
MOD_PLSQL or
      MOD_OC4J  May 11, 2003
Reviewer:  Sean
       from Atlanta
Thanks for the answer.
A question might not be intellege, but I have had it in my mind for a while.
Oracle is big on OC4J for 9ias R2. For you, what knids of applications you may
want to use a lot of MOD_OC4J and what are they you may rather use a lot of
MOD_PLSQL. Do decissions base on the web functionalities, or performance, such
users populations?
Thanks.

          Followup:

I've written one "big" application in Java, it was a palm sync conduit.  I
choose java because the other choice was C++ and Java in this case just made
more sense. (even then, the java palm conduit pretty much just inserted into a
global temporary table the changes from the palm and then a plsql stored
procedure actually did the work -- since it was all about data, data in the
database -- and sent the changes back to the palm for java to apply locally)

Everything else I write is in PLSQL and if PLSQL isn't capable of doing it I
might do a small java stored procedure.  If that cannot do it, a little C covers
it.

So, all of my applications are written in PLSQL.

Oracle is big on OC4J for 9iAS for those people who have choosen java as their
predominant application development language.  If you picked a language -- java
-- we feel our j2ee contains are outstanding (vs a BEA or IBM solution for
example).

Many people have choosen the language and j2ee standard as their way of
implementing applications.  For those people, mod_oc4j is the way to go.  there
is mod_perl, php, plsql, etc etc etc etc -- and they are all valid and workable
solutions as well.

      Rating: 4
Marvel Engine,
      what is it ?  May 19, 2003
Reviewer:  Robert
       from PA
>>we use mod_plsql exclusively.

Tom,
I took the Project Marvel "QuickTour" -
Is the "Marvel Engine" composed of a bunch of pl/sql packages ?

Thanks

      Rating: 5
For the most
      part  May 22, 2003
Reviewer:  Tyler D. Muth
       from Reston, VA
Thes are the objects that make up Marvel:

flows_010306@lap> select object_type,count(object_type) cnt
  2  from user_objects
  3  group by object_type;

OBJECT_TYPE               CNT
------------------ ----------
FUNCTION                    9
INDEX                     356
JAVA CLASS                  3
JAVA SOURCE                 3
LOB                        84
PACKAGE                   129
PACKAGE BODY              123
PROCEDURE                  38
SEQUENCE                    6
TABLE                     202
TRIGGER                   225
TYPE                        6
VIEW                       22

13 rows selected.

      Rating: 4
COUNT_HITS -
      but what about other criteria  September 10, 2003

Reviewer:  Tom Best  from PA
With regard to the COUNT_HITS recommendation for estimating number of hits, what
if you have additional criteria e.g.

where a = 10 and contains(idx,'string to find') > 0

Do I have to go back to using count(*) for this?  It seems to me there is no
other way.

Thanks.

          Followup:

i just say "approximately" and let them shoot me if I'm wrong.

I dont' think its a big deal.  I've never done it.  I won't.  I take it out of
every application I get access to.  It is the worst thing to do.

      Rating: 4
Display by
      Topic  September 11, 2003
Reviewer:  Mohan
       from Bangalore
Hi Tom,

It will be better if you can add one more flag for each question. The name of
the flag is "topic" or "category". each question can be a memmber of one or more
categories.

backup&recovery->export, exp
backup&recovery->import, imp
backup&recovery->rman, "recovery manager"
backup&recovery-> "online backup", "cold backup"
Query->sql
query->sql->join
query->sql->Top N
query->sql->DML->insert
query->sql->DML->"bulk insert"
query->sql->DML->update
query->sql->DML->delete
query->sql->DDL
query->pl/sql

The list can be more exhaustive. The advantage is that if is possible to display
questions category and subcategory wise. Search using a key word may not fetch
all questions.

suppose I want all questions related to export. There are questions where only
exp is used instead of export. Also imp for import.

Mohan

      Rating: 4
How to access
      project marvel?  September 30, 2003
Reviewer:
      Jap Boon Churn  from Malaysia
Hi Tom,

I followed one of the link you provide in this page to the project marvel. But i
was directed to
http://otn.oracle.com/hosted_dev/index.html
How can i view the details on project marvel from here?

Please advise.
Thanks.

          Followup:

project marvel is now "feature HTML DB" of the database.

see html db

September 30, 2003 Adam -- Thanks for the question regarding "FORALL
      Update - Updating multiple columns", version 8i

      You Asked
Sorry for the confusion.

profile_cur has 19 columns and has 1-2 million rows.  An UPDATE in the cursor
loop will update 1 row and 19 columns at once.  The Update is dependent on
results of 19 procedures which are quite complex.  I can not do a one hit update
outside of the cursor loop.

        FOR profile_rec IN profile_cur
        LOOP
            QuoToPurDays( profile_rec.personid
                        , profile_rec.QuoToPurDays
                        , changedFlag );
            ...

            IF changedFlag = TRUE THEN
                UPDATE profile
                    SET quotopurdays = profile_rec.QuoToPurDays,
                        ...,
                        ...,
                        ...
                    WHERE personid = profile_rec.personid;
               changedFlag  := FALSE;
            END IF;
        END LOOP;

I tried to use the CURSOR loop to populate a PL table of profilePeople_rec.  I
tried to use this PL table of records to so a FORALL update etc..

        OPEN  profile_cur FOR sqlStmt;
        LOOP
            FETCH profile_cur INTO profile_rec;
            EXIT WHEN profile_cur%NOTFOUND;

            QuoToPurDays( profile_rec.personid
                        , profile_rec.QuoToPurDays
                        , changedFlag );
            ...

            IF changedFlag = TRUE THEN
               people_tab(x).personid     := profile_rec.personid;
               people_tab(x).quotopurdays := profile_rec.quotopurdays;
               ...;
               ...;
               x  := x +1;
               changedFlag  := FALSE;
            END IF;
        END LOOP;
        CLOSE profile_cur;

        FORALL bulkNum IN 1..people_tab.COUNT
            UPDATE profile
            SET    quotopurdays = people_tab(bulkNum).QuoToPurDays,
            ...
            WHERE  personid     = people_tab(bulkNum);
        COMMIT;

Oracle will not allow a FORALL update to use a PL table of records in the update
as above etc..

My method of using FORALL to update many columns is by splitting a PL table of a
string using SUBSTR in fixed places. This seems a messy way of getting around
the problem.

        FORALL bulkNum IN 1..people_tab.COUNT
            UPDATE profile
            SET quotopurdays = LTRIM(SUBSTR(people_tab(bulkNum),0,6)),
                maildays     = LTRIM(SUBSTR(people_tab(bulkNum),7,9)),
                ...
            WHERE personid = LTRIM(SUBSTR(people_tab((bulkNum),50,9);

How can you use FORALL to update many rows using a PL table, Collection etc?  Am
I taking the wrong approach?

Do you agree that FORALL is quicker than an update in the cursor loop?

Thanks

      and we said...
Now it makes sense.  Ok.  What we (well, you) want to do is ARRAY fetch N
records, do your thing to them, and ARRAY update N records (or less).  We can do
that using the following logic.  (i don't like that substr trick, just adds
overhead).  With just 19 columns, you'll want a table/column.

The following example shows how to accomplish this.  If you wanted, you could
make the L_RECORD variable a package GLOBAL (define it in the body, not in a
procedure in the body) and just pass the INDEX around from subroutine to
subroutine.  That way, all of the subroutines in this package could access the
i'th element of the RECORD of TABLES we have defined.  It would make argument
passing trivial and avoid that substr trick alltogether.  Alternatively, you
could code it as I have using IN OUT NOCOPY parameters.

So, we'll start with a table to update:

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

and a package spec, we'll definitely be using a package here

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package do_update
  2  as
  3      procedure do_it;
  4  end;
  5  /

Package created.

now, we can define a record that represents a row in our table.  In this case,
it really represents an ARRAY of rows in our table.  Note that we are fetching
out the ROWID for updating (faster) and we are saving a changed flag as well

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package body do_update
  2  as
  3      type rowidArray is table of rowid index by binary_integer;
  4
  5      type empRec is record
  6      (
  7        empno     dbms_sql.number_table,
  8        ename     dbms_sql.varchar2_table,
  9        job       dbms_sql.varchar2_table,
 10        mgr        dbms_sql.number_table,
 11        hiredate  dbms_sql.date_table,
 12        sal       dbms_sql.number_table,
 13        comm      dbms_sql.number_table,
 14        deptno    dbms_sql.number_table,
 15        rowid     rowidArray,
 16        changed   dbms_sql.varchar2_table
 17      );
 18
 19

this little routine just mimicks your setup.  We update any empno with an ODD
empno

 20  procedure QuoToPurDays( p_empno in number, p_ename in out NOCOPY varchar2,
p_changed in out NOCOPY varchar2 )
 21  is
 22  begin
 23      if (mod(p_empno,2) = 1 )
 24      then
 25          p_ename := initcap( p_ename );
 26          p_changed := 'Y';
 27      end if;
 28  end;
 29

now for the meat of the routine.  Here we Array fetch N records (set
l_array_size to the number of records you want to process.  Use numbers between
say 100 and 1000 to see what works best for you.  Don't go overboard!!)

 30  procedure do_it
 31  is
 32      l_record     empRec;
 33      l_array_size number default 5;
 34      l_done       boolean;
 35      l_cnt        number default 1;
 36
 37      cursor c is select emp.*, rowid, 'N' from emp;
 38  begin
 39
 40      open c;
 41      loop

you might want to use dbms_application_info.SET_SESSION_LONGOPS (if you have
my book, i have a good example of the use in there).  The advantage to that is
various tools use that table to show progress -- as well, it will estimate the
time to completion for you when possible.  Just setting client info though gives
you the ability from another session to "see" where you are in your processing,
I'll show you the query later

 42          dbms_application_info.set_client_info
 43          ( 'processing ' || l_cnt || ' thru ' || (l_cnt+l_array_size-1) );
 44

Ok, here we bulk fetch N records into our arrays -- including the rowids and
an array of changed flags -- all 'N' to begin with:

 45          fetch c bulk collect into l_record.empno, l_record.ename,
l_record.job,
 46                                    l_record.mgr, l_record.hiredate,
 47                                    l_record.sal, l_record.comm,
l_record.deptno,
 48                                    l_record.rowid, l_record.changed
 49          LIMIT l_array_size;
 50          l_done := c%notfound;

we need to save this boolean, so we can exit the loop at the end.  We might
ask for 100 rows -- our last fetch will get say 55 rows -- notfound will be set
but we need to process those last 55 FIRST before we exit!

 51
 52          for i in 1 .. l_record.empno.count
 53          loop
 54              QuoToPurDays( l_record.empno(i), l_record.ename(i),
l_record.changed(i) );
 55              /* ..... */
 56          end loop;

there we just do your 19 complex functions.... and then:

 57
 58          forall i in 1 .. l_record.empno.count
 59              update emp
 60                 set empno    = l_record.empno(i),
 61                     ename    = l_record.ename(i),
 62                     job      = l_record.job(i),
 63                     mgr      = l_record.mgr(i),
 64                     hiredate = l_record.hiredate(i),
 65                     sal      = l_record.sal(i),
 66                     comm     = l_record.comm(i),
 67                     deptno   = l_record.deptno(i)
 68               where rowid    = l_record.rowid(i)
 69                 and l_record.changed(i) = 'Y';
 70

we update the records you need to have updated by rowid.  We only update those
records where the changed flag = 'Y'.

 71          exit when (l_done);
 72          l_cnt := l_cnt + l_array_size;
 73      end loop;
 74  end;
 75
 76
 77  end;
 78  /

Package body created.

and that is it.  Here is an example of it running:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select empno, mod(empno,2), ename from emp;

     EMPNO MOD(EMPNO,2) ENAME
---------- ------------ ----------
      7369            1 SMITH
      7499            1 ALLEN
      7521            1 WARD
      7566            0 JONES
      7654            0 MARTIN
      7698            0 BLAKE
      7782            0 CLARK
      7788            0 SCOTT
      7839            1 KING
      7844            0 TURNER
      7876            0 ADAMS
      7900            0 JAMES
      7902            0 FORD
      7934            0 MILLER

14 rows selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec do_update.do_it

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select empno, mod(empno,2), ename from emp;

     EMPNO MOD(EMPNO,2) ENAME
---------- ------------ ----------
      7369            1 Smith
      7499            1 Allen
      7521            1 Ward
      7566            0 JONES
      7654            0 MARTIN
      7698            0 BLAKE
      7782            0 CLARK
      7788            0 SCOTT
      7839            1 King
      7844            0 TURNER
      7876            0 ADAMS
      7900            0 JAMES
      7902            0 FORD
      7934            0 MILLER

14 rows selected.

only the records we wanted updated are...

ops$tkyte@ORA817DEV.US.ORACLE.COM> select username, client_info
  2    from v$session
  3   where username = user;

USERNAME
------------------------------
CLIENT_INFO
--------------------------------------------------
OPS$TKYTE
processing 11 thru 15

 and that shows how to see "where" you are in the processing - -you can run
this query repeatedly from another session whilst this procedure runs.

Note: you need 816 or update for the LIMIT.
Review &
      Followup

      Rating: 4
Just
      kibitzing  January 04, 2002
Reviewer:
      A reader  from Chicago, IL, USA
Swell example of just how bulk collects, collections and bulk insert/updates
ought to interact.  For maintainability, and since I'm a lazy typist, I'd make a
couple of changes to the code pro-forma.

In lieu of your record of dbms_sql.*_table's I'd use a PL/SQL table (or perhaps
varray?) but most importantly declare it based on the structure of the cursor
for ease of maintenance down the road:

  5      type empRec is record
  6      (
  7        empno     dbms_sql.number_table,
  8        ename     dbms_sql.varchar2_table,
  9        job       dbms_sql.varchar2_table,
 10        mgr        dbms_sql.number_table,
 11        hiredate  dbms_sql.date_table,
 12        sal       dbms_sql.number_table,
 13        comm      dbms_sql.number_table,
 14        deptno    dbms_sql.number_table,
 15        rowid     rowidArray,
 16        changed   dbms_sql.varchar2_table
 17      );

becomes:

TYPE empTab IS TABLE OF c%ROWTYPE;
l_records  empTab;

And then change the fetch:

fetch c bulk collect into l_record.empno, l_record.ename, l_record.job,
 46                                    l_record.mgr, l_record.hiredate,
 47                                    l_record.sal, l_record.comm,
l_record.deptno,
 48                                    l_record.rowid, l_record.changed

to

FETCH c BULK COLLECT INTO l_records
...

cheers,
Scott

          Followup:

Oh, that you could.... but you cannot....

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2          type empTab is table of emp%rowtype index by binary_integer;
  3          l_records empTab;
  4          cursor c is select * from emp;
  5  begin
  6          open c;
  7          fetch c bulk collect into l_records;
  8          close c;
  9  end;
 10  /
        fetch c bulk collect into l_records;
                                  *
ERROR at line 7:
ORA-06550: line 7, column 28:
PLS-00597: expression 'L_RECORDS' in the INTO list is of wrong type
ORA-06550: line 7, column 2:
PL/SQL: SQL Statement ignored

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2          type empTab is table of number index by binary_integer;
  3          l_records empTab;
  4          cursor c is select empno from emp;
  5  begin
  6          open c;
  7          fetch c bulk collect into l_records;
  8          close c;
  9  end;
 10  /

PL/SQL procedure successfully completed.

You cannot bulk collect into an ARRAY OF RECORDS.  You can into a RECORD OF
ARRAYS.....

      Rating: 5
Another Great
      Explanation  January 04, 2002
Reviewer:  A
      Thankful DBA  from St. Louis Missouri
Tom,

I plan on sharing this with all of our developers.

Thanks!

      Rating:
      5
Great  January 08, 2002
Reviewer:
      Sasa
Hi Tom,

Thanks for this explanation.I've tried several times to manage this but
couldn't.
Could you please just explain
dbms_sql.number_table .I presume it is declaration of pl/sql table in record
type?

          Followup:

Instead of defining my own types -- I borrowed the definitions from dbms_sql:

  -- Bulk SQL Types
  --
  type Number_Table   is table of number         index by binary_integer;
  type Varchar2_Table is table of varchar2(2000) index by binary_integer;
  type Date_Table     is table of date           index by binary_integer;

....

they are just plsql table types -- you could define your OWN if you want.

      Rating: 5
Just a
      doubt...  January 08, 2002
Reviewer:  Neeti
       from TX,USA
  I was always fascinated by the bulk methods but never used
  it because I never saw an example of FOR ALL clause with
  END LOOP. The example I have seen show only one line
  command after FOR ALL so its good to know that FOR ALL
  can handle a multi line transaction with END LOOP.

  My question is related to the same in this thread
  where user wants to collect in bulk and process the data.

  Why are using an extra step to process the array of
  records and then updating the data in next step.
  Why can't we do the processing in FOR ALL block.

  Thanks,

          Followup:

The FOR ALL is just a way to bulk execute a single statement. The END LOOP is
*not* associated with it at all.  Lets look at the code without the comments:

 40      open c;
 41      loop
 42          dbms_application_info.set_client_info
 43          ( 'processing ' || l_cnt || ' thru ' || (l_cnt+l_array_size-1) );
 44
 45          fetch c bulk collect into l_record.empno, l_record.ename, ....

 50          l_done := c%notfound;
 51
 52          for i in 1 .. l_record.empno.count
 53          loop
 54              QuoToPurDays( l_record.empno(i), l_record.ename(i), ....
 55              /* ..... */
 56          end loop;

 57
 58          forall i in 1 .. l_record.empno.count
 59              update emp
 60                 set empno    = l_record.empno(i),
 61                     ename    = l_record.ename(i),
 62                     job      = l_record.job(i),
 63                     mgr      = l_record.mgr(i),
 64                     hiredate = l_record.hiredate(i),
 65                     sal      = l_record.sal(i),
 66                     comm     = l_record.comm(i),
 67                     deptno   = l_record.deptno(i)
 68               where rowid    = l_record.rowid(i)
 69                 and l_record.changed(i) = 'Y';
 70
 71          exit when (l_done);
 72          l_cnt := l_cnt + l_array_size;
 73      end loop;
 74  end;

 So, in psuedo code:

loop
    fetch N records

    loop N times, processing N records

    for all i in 1 .. N update ...
end loop

the end loop is not associated with the FOR ALL.  FOR ALL is a single statement
executed atomically.

      Rating: 5
Excellent
      explanation   April 09, 2002
Reviewer:  She
       from US
Never knew one could do this using Oracle.
Just great !

      Rating: 5
bulk
      update  August 23, 2002
Reviewer:
      A reader
Hi Tom

I have a procedure which takes 10 hours to process 1 million rows, I am not sure
if it can be optimized with forall array processing.
The code is something like

DECLARE
   CURSOR c1
   IS SELECT cust_id FROM x_temp;
   l_custid          NUMBER;
   l_varob           NUMBER;
   l_cnt             NUMBER := 0;
   l_objid           NUMBER;
BEGIN
   for i in c1
   loop
      begin
        l_cnt := l_cnt + 1;
        select blah blah
          into l_custid
          from x_cust
         where cust_id = c1.cust_id;
        update x_cust blah blah;
      exception
        when
        no_data_found
        then
           begin
           select obj_id
           into l_objid
           from x_obj
           where blah blah;
           update x_cust;
           exception
           when no_data_found then blah blah;
           end;
        when others then update x_temp;
      end;
      if l_cnt = 5000 then commit; l_cnt := 0; end if;
   end loop;
END;
/

I have to process row by row and there are 1 million rows. Can this kind of
process use array processing? Or conventional cursor is the only bet?

          Followup:

better idea.

lose the commit.
lose the procedure.

Lets see the blah blah blahs (try to put it in a question, not in a review).
I'll betcha we can avoid a ton of the row by row stuff.

that will be array processing on steroids.

      Rating:
      5
Great  January 09, 2003
Reviewer:
      SHD  from Austria
!!!

      Rating: 3
Why this gives
      error?  May 13, 2003
Reviewer:  Shaji
       from IN
Hi Tom,

I just replicated your do_it procedure in this way

  1  DECLARE
  2    TYPE EMPREC IS RECORD (P_EMPNO T_EMP.EMPNO%TYPE,
  3                      P_EMPNM T_EMP.EMPNM%TYPE,
  4                      P_DEPT T_EMP.DEPT%TYPE,
  5                      P_SALARY T_EMP.SALARY%TYPE,
  6                      P_COMM T_EMP.COMM%TYPE);
  7    VREC EMPREC;
  8    CURSOR C1 IS SELECT EMPNO,EMPNM,DEPT,SALARY,COMM FROM T_EMP;
  9  BEGIN
 10     OPEN C1;
 11     LOOP
 12     FETCH C1 BULK COLLECT INTO
VREC.P_EMPNO,VREC.P_EMPNM,VREC.P_DEPT,VREC.P_SALARY,VREC.P_COMM;
 13       EXIT WHEN C1%NOTFOUND;
 14     END LOOP;
 15    DBMS_OUTPUT.PUT_LINE('STATUS IS OK');
 16* END;
 17  /
   FETCH C1 BULK COLLECT INTO
VREC.P_EMPNO,VREC.P_EMPNM,VREC.P_DEPT,VREC.P_SALARY,VREC.P_COMM;
   *
ERROR at line 12:
ORA-06550: line 12, column 4:
PLS-00497: cannot mix between single row and multi-row (BULK) in INTO list
ORA-06550: line 12, column 4:
PL/SQL: SQL Statement ignored

. Why this gave such error and in your case not?

          Followup:

you have an array of records.

I had a record of array's

totally different data structures.  9i permits bulk collection into arrays of
records, 8i did not.

      Rating: 5
Difference
      between record of arrays and array of records  May 13, 2003

Reviewer:  A reader
Tom,

If you could, can you highlight the differences between an "record of arrays"
and "array of records" with some simple examples. And when one might use what?

          Followup:

sorry -- but seems obvious to me?

an array of records

record(1)
record(2)
...
record(n)

vs

a record of arrays

a single record with attributes that happen to be arrays.

One would use a record of arrays in 8i to facilitate bulk collects whilst using
a record.

      Rating: 5
Bulk append to
      a collection  May 13, 2003
Reviewer:  John
       from San Jose, CA
Hi Tom,

Is it possible to bulk append to a collection. Say I have a collection x that
already has 5 elements populated. Now I want to add 10 elements to it. Is there
a way to avoid, in the case of nested table, doing
loop
x.extend;
x(i):=something;
end loop;

Obviouly if I do a select BULK COLLECTION INTO x again, all the 5 existing
elements will be over written.

- John

          Followup:

No, not really.  You can optimize the extend by extending all N at once but then
you would be doing singleton assignments to move the second collection into the
first.

      Rating:
      5
Overisight  May 14, 2003
Reviewer:
      Shaji  from IN
Hi Tom,
I just overlooked your above emprec definition. Thanks a lot for the
clarification.

      Rating:
      5
Oversight!  May 14, 2003
Reviewer:
      Shaji  from IN
Hi Tom,
I just overlooked the emprec definition. Thanx for the clarification.

      Rating: 4
Simultaneous
      Updates on different columns  May 15, 2003

Reviewer:  rama  from ct, us
tom,

 This is my question.

Table A
ID -- Primary key
col1
col2
col3
col4
col5

Now Session 1:
select 1 into var1 where id = 1 for update;
-- can we say select 1 into var2 where id = 1 for update of col1, col2 ? And
does this resolve the lock issue?
update a set col = xxx, col2=yyyy where id = 1;

Session 2:
select 1 into var1 where id = 1 for update;
update a set col4=zzzz, col5=qqqq where id = 1;

Now, when these are executed simultaneouly, though on the same row for DIFFERENT
COLUMNS, how to resolve this locking issue. We have these update statements in
our batch jobs that get kicked off every night at same time. The values for the
columns come from different sources.

Your help would be much appreciated. thanks much.

          Followup:

ROWS are locked, not columns.

two sessions cannot select the same row for update.  the "of col1, col2" is used
to removed ambiguities in the event the query references more then one table. it
does not lock columns.

      Rating: 4
Thanks
      !  May 15, 2003
Reviewer:  rama  from
      ct, us
Thanks Tom. I thought so, but just want to confirm. thanks again.

      Rating: 5
Bulk fetch into
      array of records (objects)  May 15, 2003
Reviewer:
      Piotr Jarmuz  from Braunschweig, Germany
<quote>
totally different data structures.  9i permits bulk collection into arrays of
records, 8i did not.
</quote>

Could you please give a small example?
I tried this in 9iR2:

create table rec as select object_id id, object_name name from all_objects where
rownum<=100

declare
type record_t is record
(
id number,
name varchar2(30)
);

type arr_t is table of record_t;
arr record_t;
begin
     select * bulk collect into arr from rec;
     for i in 1..100 loop
          dbms_output.put_line('id=' || arr(i).id || ' name=' || arr(i).name);
     end loop;
end;

The following error has occurred:

ORA-06550: line 11, column 3:
PLS-00497: cannot mix between single row and multi-row (BULK) in INTO list
ORA-06550: line 11, column 34:
PL/SQL: ORA-00904: : invalid identifier
ORA-06550: line 11, column 3:
PL/SQL: SQL Statement ignored
ORA-06550: line 13, column 16:
PLS-00222: no function with name 'ARR' exists in this scope
ORA-06550: line 13, column 5:
PL/SQL: Statement ignored

Cheers

          Followup:

ops$tkyte@ORA920LAP> create table rec as select object_id id, object_name name
from all_objects where rownum<=100
  2  /

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> declare
  2  type record_t is record
  3  (
  4  id number,
  5  name varchar2(30)
  6  );
  7
  8  type arr_t is table of record_t;
  9  arr ARR_T;
 10  begin
 11       select * bulk collect into arr from rec;
 12       for i in 1..100 loop
 13            dbms_output.put_line('id=' || arr(i).id || ' name=' ||
arr(i).name);
 14       end loop;
 15  end;
 16  /
id=17287 name=/1005bd30_LnkdConstant
id=7559 name=/10076b23_OraCustomDatumClosur
....

your code was trying to bulk collect into a RECORD, not an array of records ;)

      Rating: 5
It
      works  May 15, 2003
Reviewer:  Piotr Jarmuz
       from Braunschweig, Germany
Sorry...

It works! It was my typo:

should be:
arr arr_t;

instead of:
arr record_t;

Regards

      Rating: 4
Bulk
      updates  May 15, 2003
Reviewer:  lakshmi
       from Bangalore India
Excellent

      Rating: 4
Completion Time
      with Bulk Fetch and Single Fetch  September 30, 2003

Reviewer:  Vivek Sharma  from Bombay, India

Dear Tom,

One of my daily night process takes around 12 hours to completes which processes
around 6 Lakhs of rows. The logic built was that the number of rows to be
processed are inserted into a table which is first truncated. This is a simple
insert (insert into <tablename> select * from <another_tablename>. The logic
built by the application tea was

declare
define a cursor
begin
for cursor
loop
processing.... (calling some procedures / functions)
inserting into another table
end loop;

It processes 6 lakhs records. I wanted to use bulk collect feature using records
as shown by you in the top. But when compared to single processing the tieme
taken is same in both the cases. i.e single record processing takes same amount
of time as buld processing. Based on the logic developed by our developer, I
have created a package with both the options (Bulk Fetch and Single Fetch).
The package is as under :

create or replace package vivek
as
  procedure do_it;
  procedure do_it_single;
  function  check_odd_even(objid in number) return varchar2;
  function  check_prime(objid in number) return varchar2;
end;
/

create or replace package body vivek
as
type rowidArray is table of rowid index by binary_integer;

type vivekRec is record
(
t_owner         dbms_sql.varchar2_table,
t_object_name         dbms_sql.varchar2_table,
t_object_id        dbms_sql.number_table,
t_odd_even        dbms_sql.varchar2_table,
t_prime            dbms_sql.varchar2_table,
t_rowid            rowidArray
);

function check_prime(objid in number)
   return varchar2
is
l_value        number default 0;
l_cnt        number default 2;
Begin
    while l_cnt<objid
    loop
    l_value:=mod(objid,l_cnt);
         if l_value=0 then
         l_cnt:=objid;
         end if;
        l_cnt:=l_cnt+1;
    end loop;
if l_value = 0 then
return 'Not a Prime No.';
else
return 'Prime No.';
end if;
end;
-- end of prime function

function check_odd_even(objid in number) return varchar2
is
l_value    number;
begin
l_value:=mod(objid,2);
if l_value = 0 then
return 'Even';
else
return 'Odd';
end if;
end;
---------------- end of odd_even function

procedure do_it is
l_record     vivekRec;
l_array_size    number default 1000;
l_cnt        number default 1;
l_start        number;
l_run        number;
cursor main is
select rowid, owner, object_name, object_id
from vivek_test
where rownum<=20000;
begin
-- execute immediate ('truncate table vivek_completion_time');
update vivek_completion_time
      set time_taken=null
where processing_for='Bulk Processing';
l_start:=dbms_utility.get_time;
open main;
loop
   dbms_application_info.set_client_info
     ('processing '||l_cnt||' thru '||(l_cnt+l_array_size-1));

   fetch main bulk collect into l_record.t_rowid, l_record.t_owner,
l_record.t_object_name,
                 l_record.t_object_id limit l_array_size;

   for i in 1..l_record.t_owner.count
   loop
-- to check whether the object_id is prime number;
    l_record.t_prime(i):= check_prime(l_record.t_object_id(i));
-- check whether the object_id is odd number
    l_record.t_odd_even(i) := check_odd_even(l_record.t_object_id(i));
   end loop;

   forall i in 1..l_record.t_owner.count

--     if l_record.t_prime='Prime No.' and l_record.t_odd_even='Odd' then
    insert into vivek_test2_prime(owner, object_name, subobject_name, object_id,

               created, last_ddl_time, status)
        select l_record.t_owner(i), l_record.t_object_name(i),
l_record.t_prime(i),
                l_record.t_object_id(i), sysdate, sysdate,
l_record.t_odd_even(i)
        from vivek_test
        where rowid = l_record.t_rowid(i)
        and   l_record.t_prime(i)='Prime No.';
--        and   l_record.t_odd_even(i)='Odd';

   forall i in 1..l_record.t_owner.count
    insert into vivek_test2_odd(owner, object_name, subobject_name, object_id,
               created, last_ddl_time, status)
        select l_record.t_owner(i), l_record.t_object_name(i),
l_record.t_prime(i),
                l_record.t_object_id(i), sysdate, sysdate,
l_record.t_odd_even(i)
        from vivek_test
    where rowid = l_record.t_rowid(i)
    and   l_record.t_odd_even(i)='Odd';

--      elsif l_record.t_prime='Prime No.' and l_record.t_odd_even='Even' then

   forall i in 1..l_record.t_owner.count
    insert into vivek_test2_even(owner, object_name, subobject_name, object_id,
               created, last_ddl_time, status)
        select l_record.t_owner(i), l_record.t_object_name(i),
l_record.t_prime(i),
                l_record.t_object_id(i), sysdate, sysdate,
l_record.t_odd_even(i)
        from vivek_test
    where rowid = l_record.t_rowid(i)
    and   l_record.t_odd_even(i)='Even';

--     elsif l_record.t_prime='Not a Prime No.' and l_record.t_odd_even='Even'
then
   forall i in 1..l_record.t_owner.count
    insert into vivek_test2_prime(owner, object_name, subobject_name, object_id,

               created, last_ddl_time, status)
        select l_record.t_owner(i), l_record.t_object_name(i),
l_record.t_prime(i),
                l_record.t_object_id(i), sysdate, sysdate,
l_record.t_odd_even(i)
        from vivek_test
    where rowid = l_record.t_rowid(i)
        and   l_record.t_prime(i)='Not a Prime No.';
--    and   l_record.t_odd_even(i)='Even';

     forall i in 1..l_record.t_owner.count
    insert into vivek_test2(owner, object_name, subobject_name, object_id,
               created, last_ddl_time, status)
        values (l_record.t_owner(i), l_record.t_object_name(i),
l_record.t_prime(i),
                l_record.t_object_id(i), sysdate, sysdate,
l_record.t_odd_even(i));
exit when main%notfound;
l_cnt:=l_cnt+l_array_size;

end loop;
close main;
commit;
l_run:=dbms_utility.get_time-l_start;
update vivek_completion_time
set time_taken=ltrim(rtrim(to_char(l_run)))||' hsecs.'
where processing_for='Bulk Processing';
-- insert into vivek_completion_time values(l_run||' hsecs.');
-- dbms_output.put_line('Time Taken '||l_run||' secs.');
commit;
end;
-- end of do_it procedure

procedure do_it_single is
t_owner        vivek_test.owner%type;
t_object_name    vivek_test.object_name%type;
t_object_id    vivek_test.object_id%type;
l_cnt        number default 1;
l_start        number;
l_run        number;
t_prime        varchar2(30);
t_odd_even    varchar2(30);
l_array_size    number:=20000;

cursor main is
select owner, object_name, object_id
from vivek_test
where rownum<=l_array_size;
begin

update vivek_completion_time
      set time_taken=null
where processing_for='Single Processing';
l_start:=dbms_utility.get_time;
open main;
loop
   dbms_application_info.set_client_info
     ('processing '||l_cnt||' thru '||(l_array_size));
   fetch main into t_owner, t_object_name,
                 t_object_id;
    t_prime:= check_prime(t_object_id);
    t_odd_even := check_odd_even(t_object_id);

     if t_prime='Prime No.' and t_odd_even='Odd' then
    insert into vivek_test2_prime(owner, object_name, subobject_name, object_id,

               created, last_ddl_time, status)
        values(t_owner, t_object_name, t_prime,
                t_object_id, sysdate, sysdate, t_odd_even);

    insert into vivek_test2_odd(owner, object_name, subobject_name, object_id,
               created, last_ddl_time, status)
        values (t_owner, t_object_name, t_prime,
                t_object_id, sysdate, sysdate, t_odd_even);
      elsif t_prime='Prime No.' and t_odd_even='Even' then

    insert into vivek_test2_prime(owner, object_name, subobject_name, object_id,

               created, last_ddl_time, status)
        values(t_owner, t_object_name, t_prime,
                t_object_id, sysdate, sysdate, t_odd_even);
    insert into vivek_test2_even(owner, object_name, subobject_name, object_id,
               created, last_ddl_time, status)
        values (t_owner, t_object_name, t_prime,
                t_object_id, sysdate, sysdate, t_odd_even);

   elsif t_prime='Not a Prime No.'  and t_odd_even='Odd' then
-- and l_record.t_odd_even='Even' then
    insert into vivek_test2_prime(owner, object_name, subobject_name, object_id,

               created, last_ddl_time, status)
        values(t_owner, t_object_name, t_prime,
                t_object_id, sysdate, sysdate, t_odd_even);
    insert into vivek_test2_odd(owner, object_name, subobject_name, object_id,
               created, last_ddl_time, status)
        values (t_owner, t_object_name, t_prime,
                t_object_id, sysdate, sysdate, t_odd_even);
   else
    insert into vivek_test2_prime(owner, object_name, subobject_name, object_id,

               created, last_ddl_time, status)
        values(t_owner, t_object_name, t_prime,
                t_object_id, sysdate, sysdate, t_odd_even);
    insert into vivek_test2_even(owner, object_name, subobject_name, object_id,
               created, last_ddl_time, status)
        values (t_owner, t_object_name, t_prime,
                t_object_id, sysdate, sysdate, t_odd_even);
end if;
    insert into vivek_test2(owner, object_name, subobject_name, object_id,
               created, last_ddl_time, status)
        values (t_owner, t_object_name, t_prime,
                t_object_id, sysdate, sysdate, t_odd_even);
exit when main%notfound;
l_cnt:=l_cnt+1;
end loop;
close main;
commit;
l_run:=dbms_utility.get_time-l_start;
update vivek_completion_time
set time_taken=ltrim(rtrim(to_char(l_run)))||' hsecs.'
where processing_for='Single Processing';
-- insert into vivek_completion_time values(l_run||' hsecs.');
-- dbms_output.put_line('Time Taken '||l_run||' secs.');
commit;

end;
-- end of do_it_single procedure

end; -- end of package
/

The procedure do_it uses bulk processing with 1000 rows as limit and
do_it_single processes single row at a time. the time taken by both the
processes is inserted into another table after completion of this process. The
check was made for 20000 rows.

TIME_TAKEN           PROCESSING_FOR
-------------------- --------------------
50973 hsecs.         Bulk Processing
51774 hsecs.         Single Processing

In do_it procedure, everytime the object_id is checked for prime number check
and odd_even check. Then it gets inserted using forall. There are no indexes on
the main table which is vivek_test.

What I assume is that since I am calling the functions in a loop using for loop
and this loop is being executed total no. of records time hence there can be no
improvement. Is there any way that I can pass bulk parameters to functions
instead of running the loop 20000 times. How do I improve the performance of
do_it procedure over do_it_single procedure. I have also tried to skip the for
loop and modify all the forall's as
    insert into vivek_test2_odd(owner, object_name, subobject_name, object_id,
               created, last_ddl_time, status)
        select l_record.t_owner(i), l_record.t_object_name(i),
check_prime(l_record.t_object_id(i)),
                l_record.t_object_id(i), sysdate, sysdate,
check_odd_even(l_record.t_object_id(i))
        from vivek_test
    where rowid = l_record.t_rowid(i)
    and   check_odd_even(l_record.t_object_id(i))='Odd';

But it took more time than the previous.
Please help. This will help me enhance my knowledge towards application tuning.
There are 3-4 similar processes which I need to improve in terms of response
time.

Regards
Vivek

          Followup:

hmm, thats a ton of code.

if you have my book Expert One on One Oracle -- read chapter 10, learn how to
use the tools there.  Also refer to the chapter on dbms_profiler -- once you
have the SQL perfectly fast.

I cannot read the above code and "tune it" for you -- well, maybe I could but
thats not what this is about.

I'd be looking to REMOVE ALL procedural code, period.

September 30, 2003 Kevin -- Thanks for the question regarding
      "Creative Auditing", version 9.2

      You Asked
I'm using a commercial application that, in some cruel idea of a joke,
implements its UPDATE logic by deleting the row, then inserting a row with the
same primary key and modified values. I would like to perform auditing on this
table as if it was being updated [tracking changes to the ":new" and ":old"
values], but haven't figured out how.
Any ideas?

      and we said...
you cannot -- you have two distinct events, delete and then insert.

Best you can do is audit the delete and the insert and then from the AUDIT TRAIL
-- deduce that it was an update after the fact based on seeing the same primary
key in the same transaction (you can get the transaction id from
dbms_transaction.local_transaction_id in your trigger so you can easily group
statements in your audit trail by transaction)
Review &
      Followup

      Rating:
      5
Thanks  October 01, 2003
Reviewer:
      Kevin  from Reston, VA
The "best you can do" was exactly what I was looking for.

September 30, 2003 Tom -- Thanks for the question regarding "Outer
      Join - row existence decision", version 9.2.0

      You Asked
I have a table that has a FK into another table that contains internationalized
strings for the parent table.  So, build_id is the PK of the parent table, and
there are 0 through N entries in the i18n table that have strings for each
language for that build ID.  If I have a string for English, and one for French,
then there are 2 rows for that build_id, and one having lang_cd = 'en', and one
for lang_cd 'fr'.  (build_id, lang_cd) is the PK in this table.

My app needs to display the string depending on the current setting of the user,
but, if there is no entry for this build_id, for the chosen language, I need it
to default to find the 'en' string.

Is there a way, as part of a single query, to outer join into the i18n table and
get the language of choice "if it is there", but then get the 'en' row
otherwise.  And, I say outer join, because there may not even be an entry at all
(even for 'en').

This will be in a stored proc, so I know I could do it procedurally, but I'd
love to have it happen in a single query, if that's faster.

      and we said...
scalar subqueries can be very cool for this:

ops$tkyte@ORA920> create table t1 ( build_id int primary key );

Table created.

ops$tkyte@ORA920> create table t2 ( lang_cd varchar2(5), build_id references t1,
data varchar2(5) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t1 values ( 1 );

1 row created.

ops$tkyte@ORA920> insert into t1 values ( 2 );

1 row created.

ops$tkyte@ORA920> insert into t1 values ( 3 );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t2 values ( 'en', 1, 'EN' );

1 row created.

ops$tkyte@ORA920> insert into t2 values ( 'en', 2, 'EN' );

1 row created.

ops$tkyte@ORA920> insert into t2 values ( 'fr', 2, 'FR' );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select t1.build_id,
  2         nvl( (select data from t2 where lang_cd = 'fr' and build_id =
t1.build_id),
  3              (select data from t2 where lang_cd = 'en' and build_id =
t1.build_id) ) data
  4    from t1
  5  /

  BUILD_ID DATA
---------- -----
         1 EN
         2 FR
         3

ops$tkyte@ORA920>

Review &
      Followup

      Rating: 5
Thanks
      again  September 30, 2003
Reviewer:  Tom
      Best
eggs cellent.

(an aside... ever think of having optional free registration at asktom, so we
don't have to fill in our info each time?).

      Rating: 3
Another way -
      might be faster  October 01, 2003
Reviewer:
      msc
These are not real tables (no indexes etc) but there may exists other performant
ways.

Original:

set autotrace on stat

SQL> select t1.build_id
  2       , nvl( (select data from t2 where lang_cd = 'fr' and build_id =
t1.build_id),
  3              (select data from t2 where lang_cd = 'en' and build_id =
t1.build_id) ) data
  4  from t1;

  BUILD_ID DATA
---------- -----
         1 EN
         2 FR
         3

Statistics
----------------------------------------------------------
         28  db block gets
          8  consistent gets

another way:

SQL> select t1.build_id, nvl( fr.data, en.data) as data
  2  from t1
  3     , (select build_id, data from t2 where lang_cd = 'fr' ) fr
  4     , (select build_id, data from t2 where lang_cd = 'en' ) en
  5  where t1.build_id = fr.build_id (+)
  6    and t1.build_id = en.build_id (+);

  BUILD_ID DATA
---------- -----
         1 EN
         2 FR
         3

Statistics
----------------------------------------------------------
         12  db block gets
          3  consistent gets

or:

SQL> select t1.build_id, nvl( fr_data, en_data) as data
  2  from t1
  3     , ( select build_id
  4              , max(decode(lang_cd, 'fr', data, NULL)) as fr_data
  5              , max(decode(lang_cd, 'en', data, NULL)) as en_data
  6         from t2
  7         where lang_cd in ('fr','en')
  8         group by build_id ) x
  9  where t1.build_id = x.build_id (+)
 10  ;

  BUILD_ID DATA
---------- -----
         1 EN
         2 FR
         3

Statistics
----------------------------------------------------------
          8  db block gets
          2  consistent gets

Or if empty row 3 is not wanted:

SQL> select build_id
  2       , nvl( max(decode(lang_cd, 'fr', data, NULL))
  3            ,  max(decode(lang_cd, 'en', data, NULL))) as DATA
  4  from t2
  5  where lang_cd in ('fr','en')
  6  group by build_id;

  BUILD_ID DATA
---------- -----
         1 EN
         2 FR

Statistics
----------------------------------------------------------
          4  db block gets
          1  consistent gets

September 30, 2003 Terry -- Thanks for the question regarding
      "Recommendations for using default and automatic column values", version
      9.2.0

      You Asked
Try as I might and after searching this site, checking the Oracle docs, several
books (three from Tom Kyte), the sample Oracle schemas, I was unable to find
good examples of how to set default values and calculate automatic columns. Here
are some of the defaults that I was able to do:

Literal (okay this is easy)
  ex: region varchar2 (4) default 'CO'
Current Date (again, pretty easy)
  ex: date_entered date default sysdate
Current Day of week (month, year, etc.)
  ex: current_day to_char(sysdate, 'Day')
Current Hour
  ex: current_hour numeric(1) default to_number(to_char(sysdate,'HH'))
Current Timestamp
  ex: rec_timestamp timestamp default systimestamp
Next sequence (uset the following PL/SQL code within a trigger:
   ex: select seqno.nextval into :new.seqno from dual;

Are there any more good ones that I'm missing?  Expecially ones using DECODE or
more complex expressions.

And what's the best practice for calculating the tot_amt from price and qty
columns and updating the table via an insert trigger since the following is
invalid because defaults can not reference other columns?

column price numeric(10)
column qty numeric (10)
column tot_amt numeric (10) default price*qty /* bad syntax */

And what's the best practice for setting a default column in an insert trigger
by using the value of an entered column to lookup a value from another table?

Many thanks.

Terry O'Brien

      and we said...
Defaults are like "beauty" i guess - all in the eye of the beholder.  I don't
see them as being "very interesting".  I don't have any other examples then you
do and in fact, I would find your list "too long" really.

They are as simple as they look to me -- literals, dates, complex values via
triggers (like a sequence).

In your example -- tot_amt would never be stored -- it would be ALMOST as bad as
storing "age" and "date of birth".  AGE must be computed upon retrieval.  Here,
tot_amt must be computed upon retrieval.  If you needed to search on tot_amt,
you can certainly index:

create index t_idx on t(price*qty);

and create a view that appears to have tot_amt in it:

create view v as select ..., price, qty, price*qty tot_amt from t;

and then select * from t where tot_amt > 500000;

could use an index if appropriate.

As for the question:

...
And what's the best practice for setting a default column in an insert trigger
by using the value of an entered column to lookup a value from another table?
......

that sounds like a foreign key -- so the best practice would be

a) lose the trigger
b) lose the column
c) join to get the value at query time

Review &
      Followup

      Rating: 1
Correct
      information but does not address the questions
      directly  September 30, 2003
Reviewer:  Terry
      O'Brien  from Boulder, CO USA
Thanks for the response.  However, I was hoping for list of other column default
examples as it gets short attention in the Oracle docs and the general Oracle
publications.

I'll take your respones to indicate that you have not come across any other
defaults that would be useful to know about.

As to the rest of the response, I used the simplist example possible to
illustrate the concept required and I was looking for a trigger example such as:

begin
  select seqno.nextval into :new.seqno from dual;
  :new.tot_amt := :new.price * :new.qty;
end

Although your answer is correct that derived values can be easily be done via a
function based index, there are times when generating the derived value and
storing it in the table and indexing as part of a composite index can yield
staggering performance gains.

As to the lookup, again there are times for performance reasons where this type
of lookup is a much better than the one you describe.  In my particular case,
the lookup is to categorize records into buckets and then indexed with the
tot_amt into a composite index.

I was looking for any better approach than doing a select from the tigger as
follows:

declare
  Vto number;
begin
  select col1 from table1 into Vto where .....;
   :new.column1 = Vto;
 ......
end;

So it doesn't appear there is anything more in Oracle 9 for setting defaults and
calculating values automatically on an insert beyond what I've already
indicated.

Thanks again for the response and I'm enjoying your latest Oracle by Design
book.

September 30, 2003 Thiruvaiyaru -- Thanks for the question regarding
      "oracle_smon process running as top process", version 8.1.6

      You Asked
Tom,

I often see the oracle background process -- oracle_smon_prod running as the top
process on the production database box. This happens quite often. Can you tell
me why this process runs as the top process  and how I could get around the
problem.

Here is the process :-
 1   ?   15625 oracle   235 20 27972K   756K run    292:33 14.19 14.16
ora_smon_prod

Also, can you give me a checklist of what needs to be done/checked
if the load on the database box is high. It has suddenly gone up
in the last 2 days (15 minute load on the box is around 4.0), I tried indexing
columns on tables that were missing indexes,but still didn't help.

Thanks in Advance
Sai

      and we said...
here is a short extract from my book (to get all of the details, get the book)

SMON ¡V The System Monitor

SMON is the process that gets to do all of the jobs no one else wants. It is
sort of the 'garbage collector' for the database. Some of the jobs it is
responsible for include:

ƒá Temporary space clean up ¡V With the advent of 'true' temporary tablespaces,
this job has lessened, but has not gone away. For example, when building an
index, the extents allocated for the index during the creation are marked as
TEMPORARY. If the CREATE INDEX session is aborted for some reason, SMON is
responsible for cleaning them up. There are other operations that create
temporary extents that SMON would be responsible for as well.

ƒá Crash recovery ¡V SMON is responsible for performing crash recovery of a
failed instance, upon restart.

ƒá Coalescing free space  ¡V If you are using dictionary-managed tablespaces
SMON is responsible for taking extents that are free in a tablespace and
contiguous with respect to each other, and coalescing them into one 'bigger'
free extent. This occurs only on dictionary managed tablespace with a default
storage clause that has pctincrease set to a non-zero value.

ƒá Recovering transactions active against unavailable files ¡V This is similar
to its role during database startup. Here SMON recovers failed transactions that
were skipped during instance/crash recovery due to a file(s) not being available
to recover.  For example, the file may have been on a disk that was unavailable
or not mounted.   When the file does become available, SMON will recover it.

ƒá Instance recovery of failed node in OPS ¡V In an Oracle Parallel Server
configuration, when a node in the cluster goes down (the machine fails), some
other node in the instance will open that failed node's redo log files, and
perform a recovery of all data for that failed node.

ƒá Cleans up OBJ$ ¡V OBJ$ is a low-level data dictionary table  that contains an
entry for most every object (table, index, trigger, view, and so on) in the
database. There are many times entries in here that represent deleted objects,
or objects that represent 'not there' objects, used in Oracle's dependency
mechanism. SMON is the process that removes these no longer needed rows.

ƒá Shrinks rollback segments ¡V SMON is the process that will perform the
automatic shrinking of a rollback segment to its optimal size, if it is set.

ƒá 'Offlines' rollback segments ¡V It is possible for your DBA to 'offline' or
make unavailable, a rollback segment that has active transactions. It may be
possible that active transactions are using this offlined rollback segment. In
this case, the rollback is not really offlined; it is marked as 'pending
offline'. SMON will periodically try to 'really' offline it in the background
until it can.

That should give you a flavor of what SMON does (there is more). As evidenced by
the ps listing of processes I introduced above, SMON can accumulate quite a lot
of CPU over time (the instance that ps was taken from was an active instance
that had been up for well over a month). SMON periodically wakes up (or is woken
up by the other backgrounds) to perform these housekeeping chores.

..................................

SMON is supposed to be busy, it is normal for it to have lots of CPU

As for why your system load is 4 -- cannot really help you unless you've been
running statspack all along and have historical reports to compare with (if you
have no history -- its very very hard to say "whats gone up")

A load of 4 is not necessarily bad -- my e450 with 4 cpus runs between 3 and 5
all day long but it still has plenty of idle CPU.

I suggest you start using statspack, generate a history of what you do in a
typical day so you can compare this to what you are currently doing when you
suspect something is changing.

Review &
      Followup

      Rating: 5
good
      answe  August 09, 2001
Reviewer:  ashraf
      gamal  from kuwait

      Rating: 5
Fantastic
      Answer  August 11, 2001
Reviewer:  Rajesh
      Dayal  from Muscat, Oman
The explanation is real good. I don't think you have missed anything about SMON.

      Rating: 5
GREAT
      !!!  August 11, 2001
Reviewer:  Ganesh R
       from India
Excellent is the Only word to Describe this article.

      Rating: 4
Growing value
      for SMON  September 30, 2003
Reviewer:
      Nathan  from England
Hi,
we have a round-robin database ( Buggy Version 9.0.1.2 ) , we add 32 million
rows in a day in one the tables. There are are few tables which get loaded
everyday. In some tables partiions are added and a oldest partion dropped . We
use some java and external tables.  The DB is 24X7 and everytime after bouncing
the db the performance falls by 30 minutes a day ( based on logs from report
tables population and generation).  I found a pattern ..by looking at SMON it
increases by 5 megs a day . Is there any way i can reset smon without restarting
the DB ?

( Patching is not immediately feasible as we cannot bring the DB down for the
next few months due to management issues )

Is there any other solution ?

Thanks
Nathan

          Followup:

what do you mean by "smon increases by 5 megs a day"

have you looked at what smon is doing?

what does it mean to have "performance fall by 30 minutes a day"?

but -- the real confusing thing is:

a) 24x7
b) yet you bounce (else you would not have an issue)
c) but yet you cannot bounce to patch.

hmmm?

      Rating: 3
SMON
      Growth!  September 30, 2003
Reviewer:
      Nathan  from UK
Tom, I agree I could have been more clearer rather than being stingy with my
details,

I tried executing the following query for the past one week

SELECT  sess.sid , sess.username
, substr(nvl(sess.program,p.program),-20) prog
, to_char(sum(decode(name.name , 'session pga memory'
       , value,0)),'999,999,999,999')curr_pga
, to_char(sum(decode(name.name,'session pga memory max'
     ,value,0)),'999,999,999,999')max_pga
   FROM v$session sess, v$sesstat stat
   , v$statname name ,v$process p
WHERE sess.sid = stat.sid
 AND stat.statistic# = name.statistic#
   AND name.name like 'session pga memory%'
and sess.paddr = p.addr
group by
    sess.sid, sess.username,
        substr(nvl(sess.program,p.program),-20)
This query has returns a various process of which SMON continously seems to
increase and never releases the memory back. I was monitoring the DB for a
performance issue which slowly increases to intolerable levels after a week.

We had to bring down the db for a system maintenance shutdown (for an hour ) to
change a essential component after a six month wait. ( The management
unfortunately does not act on the changes that easily )and make a fuss about it.
We had to bring down for the second time with a shorted notice in less than
three months as Sun forced us for a shutdown to change some component which they
discovered was faulty with the particualr model. Now I have been pushing the
management for a patch, we were not given enough downtime for patching the two
databases on the server.

The DB was slowing down with report generation and dataload every day by half
hour ( reports are generated 30 minutes later than the previous day ) everyday.
I have the statspack details ... however I'm puzzled bcos the same amount of
data is loaded every day and reports generated we poll our network devices and
collect statistics , there is very little increase in data almost neglible(
.0001% increase in a day ). The DB is used by our 24X7 customer support who
depend heavily on our DB to diagnose the problems so we are unable to bring the
DB down as often as we would please to. I would eventually patch it.. in the
next two months time... but wondered if there was a different way of sorting the
memory leak ( Reference metalink - Bug reference 2209289 ). Just looking for a
temporary work around. Hope you would understand the practical difficulties in a
DBA's world.

Regards
Nathan

I can post three spreports , I failed to deduce much as my knowledge is limited
and i'm still learning from you. :)
Thank you!

          Followup:

I understand the practical difficulties, i just couldn't understand what you
meant by certain things.  this is much clearer.

for that bug, you will need to patch.  there are no workarounds, it takes a
binary patch to correct the SMON issue.

September 30, 2003 Kurian -- Thanks for the question regarding
      "Nologging", version 8.1.6

      You Asked
hi tom,

I am a bit confused about nologging.
I have a couple of object in nologging ,after a full database recovery
, I found a couple of block curroption.
investigation on the same revealed  that those objects were created using
nologging.

Now for my confusion
when a table/index is created using nologging,
Is the redo information for create tab/index generated ?
if yes then the block curroption is not justifiable.

I understand all the DML operations on nologging objects will generate only 40%
redo or even less, in that case how do i refresh my standby database containing
these objects?

Pl. explain the concept of nologging and also the scenario listed below.

Regards
Kurian

      and we said...
the entire point of nologging is NOT TO GENERATE REDO for many operations --
these objects are by definition "not recoverable" after these operations until
you back them up.

When you create a table or index in nologging -- the initial create is *not
logged*.  Hence, if you restore a backup from BEFORE the time of the create and
you restore that backup -- all of the blocks that never had redo -- are missing!
(not corrupt -- missing, purposely -- you told us to make them go away).

when using a standby database for disaster recovery -- you cannot perform
nologging operations without performing additional, manual work (in fact, in 9i,
we can set a flag that says "ignore the request for nologging -- log it anyway"
on the database to avoid this issue alltogether).

Please read the standby database documention -- it does explain this in some
detail:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76995/toc.htm
and in particular

http://otn.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76995/standbys.htm#28477

Review &
      Followup

      Rating: 5
Bulk data
      loading  May 02, 2003
Reviewer:
      A reader
We load a data monthly using sql*loader, our database run in archive log mode.
This operation generate a lot of redo logs during data loading. So, I change the
tables and indexes involved in loading to nologging and when I loading the data
the database still generate a lot of logs (Its seems to be same). Then I change
the tablespace to nologging but still same (for nologging tablespace: is the
redo log about space which is segments?)
Did I miss some parameter in sql*loader or I miss something else?

          Followup:

a DIRECT path load can bypass redo generation for the *table* data - but not the
indexes.

if this is the goal -- minimize redo -- then you

o set tables nologging
o direct path
o use skip_index_maintenance=true
o rebuild indexes in nologging mode after the load

then back it all up.

But -- you know what -- if you are doing your load in more then a reasonable
amount of time....(maybe what you are currently doing is just fine)

      Rating:
      4
Nologging  June 10, 2003
Reviewer:
      kurian  from India
Hi Tom,
I am asking this question after a long time. Pl. forgive me for that.

could you pl. explain what exactly you meant by *Initial Create is not logged?.

does it mean , that my create table statement will not generate redo for the
data dictionary. As i understand all DDLs will generate redo.

kindly throw some light on it

Regards
Kurian

          Followup:

the data put into the table you are creating is not logged.

the changes to the data dictionary are logged.

there will be some nominal amount of redo generated, this redo is used to
protect the data dictionary.

      Rating:
      4
Nologging  June 11, 2003
Reviewer:
      kurian  from India
Hi Tom,
Thanks for the clarification.
Yesterday, i carried out an an activity
1. Created a table T1 in nologging
2. then took a cold backup
3. Performed direct path insert on table T1
4. Once the cold backup was over, I opened the database
created another table T2 in nolooging
5. Performed some direct path insert on table T2.
6. Then performed a recovery , I got back all the data in Table T1, and no data
on table T2.

Now for my question , if the amount of redo generation in low while doing a
direct path insert ,
then how did oracle recovered the whole data in table t1 ?

regards
Kurian

          Followup:

your steps are confusing

1) ok
2) ok
3) ????  you haven't opened yet
4) you open here

one would need the real set of steps.  My guesses:

a) you backed up after the direct path inserts on t1 or
b) t1 wasn't really in nologging mode

      Rating:
      3
Nologging  June 11, 2003
Reviewer:
      Kurian  from India
Hi Tom,

Let me explain the steps once again
1. Created a Table in nologging and performed direct Path insert
2. Backed up the database
3. Then opened the database
4. Created another Table T2 , performed a direct path insert on T1 and T2
5. Performed a complete recovery.
6. Table T1 gave back all the data, and only the structure of T2.

I understood why the data was lost from T2, but couldnt understand how oracle
recovered all the data from T1

Regards
Kurian

          Followup:

sorry, you did something "wrong" (i don't see exact commands, so I cannot say
"what you did wrong".

Here is my test case that shows what would happen.  I used Oracle Managed Files,
if you are using 8i, you would have to use a "more complete" create tablespace
command:

ops$tkyte@ORA920> create tablespace test_logging;
Tablespace created.

ops$tkyte@ORA920> column file_name new_val f
ops$tkyte@ORA920> select file_name from dba_data_files where tablespace_name =
'TEST_LOGGING';

FILE_NAME
------------------------------
/usr/oracle/ora920/OraHome1/or
adata/ora920/o1_mf_test_log_zg
h7ddvj_.dbf

ops$tkyte@ORA920> create table t1 TABLESPACE TEST_LOGGING as select * from
all_objects where 1=0;
Table created.

ops$tkyte@ORA920> alter table t1 nologging;
Table altered.

ops$tkyte@ORA920> insert /*+ append */ into t1 select * from all_objects;
31662 rows created.

ops$tkyte@ORA920> commit;
Commit complete.

there, we have 31662 rows...

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter tablespace test_logging begin backup;

Tablespace altered.

ops$tkyte@ORA920> !cp &F /tmp/backup.dmp

ops$tkyte@ORA920> alter tablespace test_logging end backup;

Tablespace altered.

backed up that datafile at this point in time...

ops$tkyte@ORA920> create table t2 TABLESPACE TEST_LOGGING as select * from
all_objects where 1=0;
Table created.

ops$tkyte@ORA920> alter table t2 nologging;
Table altered.

ops$tkyte@ORA920> insert /*+ append */ into t2 select * from all_objects;
31663 rows created.

ops$tkyte@ORA920> commit;
Commit complete.

ops$tkyte@ORA920> insert /*+ append */ into t1 select * from all_objects;
31663 rows created.

ops$tkyte@ORA920> commit;
Commit complete.

same thing for T2 at this point in time and added rows to T1 as well...

ops$tkyte@ORA920> alter tablespace test_logging OFFLINE;
Tablespace altered.

ops$tkyte@ORA920> !rm -f &F

simulated "file gone away"

ops$tkyte@ORA920> alter tablespace test_logging ONLINE;
alter tablespace test_logging ONLINE
*
ERROR at line 1:
ORA-01157: cannot identify/lock data file 15 - see DBWR trace file
ORA-01110: data file 15:
'/usr/oracle/ora920/OraHome1/oradata/ora920/o1_mf_test_log_zgh7ddvj_.dbf'

ops$tkyte@ORA920> !cp /tmp/backup.dmp &F

ops$tkyte@ORA920> alter tablespace test_logging ONLINE;
alter tablespace test_logging ONLINE
*
ERROR at line 1:
ORA-01113: file 15 needs media recovery
ORA-01110: data file 15:
'/usr/oracle/ora920/OraHome1/oradata/ora920/o1_mf_test_log_zgh7ddvj_.dbf'

ops$tkyte@ORA920> alter database recover datafile '&F' ;
old   1: alter database recover datafile '&F'
new   1: alter database recover datafile
'/usr/oracle/ora920/OraHome1/oradata/ora920/o1_mf_test_log_zgh7ddvj_.dbf'

Database altered.

restored from backup and:

ops$tkyte@ORA920> alter tablespace test_logging ONLINE;

Tablespace altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select count(*) from t1;
select count(*) from t1
                     *
ERROR at line 1:
ORA-01578: ORACLE data block corrupted (file # 15, block # 440)
ORA-01110: data file 15:
'/usr/oracle/ora920/OraHome1/oradata/ora920/o1_mf_test_log_zgh7ddvj_.dbf'
ORA-26040: Data block was loaded using the NOLOGGING option

ops$tkyte@ORA920> select count(*) from t2;
select count(*) from t2
                     *
ERROR at line 1:
ORA-01578: ORACLE data block corrupted (file # 15, block # 522)
ORA-01110: data file 15:
'/usr/oracle/ora920/OraHome1/oradata/ora920/o1_mf_test_log_zgh7ddvj_.dbf'
ORA-26040: Data block was loaded using the NOLOGGING option

both tables are nuked....

So, I have a feeling T1 was "logging" in your example -- but now you have a very
simple test case that doesn't involve startup/shutdown to play with

      Rating: 5
create table is
      logged?  June 11, 2003
Reviewer:  Reader

<quote> the data put into the table you are creating is not logged.
the changes to the data dictionary are logged.
<quote>

meaning create table action is logged but the data is not logged. correct? If i
have not backed up after loading, if i had to recover from an earlier backup,
does oracle create the table structure with no data in it?

          Followup:

correct.

      Rating: 3
tablespace vs.
      object  September 30, 2003
Reviewer:
      A reader
What, if any, is the relationship between logging|nologging on the tablespace vs
logging|nologging on the table itself?  Does one supercede the other?

Thanks.

          Followup:

on the tablespace level, it just defaults the attribute of newly created
segments.  It is like changing a default.

for example, you set "initial" at the tablespace level, that is the default
initial size for all newly created objects in that tablespace -- it doesn't
affect any existing objects.  It will only affect segments that do not otherwise
say "this is my initial"

So, you need look at the segment itself to say "it will or will not log during
that bulk operation"

...
logging_clause

Specify the default logging attributes of all tables, indexes, materialized
views, materialized view logs, and partitions within the tablespace. LOGGING is
the default.

The tablespace-level logging attribute can be overridden by logging
specifications at the table, index, materialized view, materialized view log,
and partition levels.
.........

Consider:

ops$tkyte@ORA920> create tablespace log_test
  2  datafile size 1m
  3  NOLOGGING;

Tablespace created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t1 ( x int ) tablespace log_test;

Table created.

ops$tkyte@ORA920> create table t2 ( x int ) tablespace log_test logging;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select table_name, logging
  2    from user_tables
  3   where tablespace_name = 'LOG_TEST';

TABLE_NAME                     LOG
------------------------------ ---
T1                             NO
T2                             YES

so, a logging clause on T2 overrides the DEFAULT setting inherited from the
tablespace.  further:

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter tablespace log_test logging;

Tablespace altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t3 ( x int ) tablespace log_test;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select table_name, logging
  2    from user_tables
  3   where tablespace_name = 'LOG_TEST';

TABLE_NAME                     LOG
------------------------------ ---
T1                             NO
T2                             YES
T3                             YES

the logging attribute only affects objects created AFTER the change -- T1 is
still "no logging"

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> drop tablespace log_test including contents;

Tablespace dropped.

September 30, 2003 Mike -- Thanks for the question regarding
      "Indexing a path string", version 9.2.0

      You Asked
Many of our applications require storing the path to an file based asset in the
database as a single field.  These are primarily legacy applications.  So we
have tables with millions of rows of information basically of this structure.

create table asset (pk integer primary key,
  str_show_id varchar2(32),
  path varchar2(1024));

Subsequently, we have many applications that query these tables like:
select * from asset where path like '%pex%';

In these applications we don't really know what portion of the path will be
useful for what application and the paths tend to be very long.  Basically we
keep the path and filename to every file on our network in the database (and
every version also gets an row).

Many of the paths are similar to a point:
  /shots/home/pex/model/sz02/pix/out/.../sz02_blah1.jpg
  /shots/home/pex/model/sz02/pix/out/.../sz02_blah2.jpg
  /shots/home/pex/model/sz03/pix/out/.../sz03_foo1.jpg
  ... {4 million rows here}
  /shots/home/sl2/bin/al06/tfva/al06_bin.exe

I have done my best to index this column and performance for selects is just
barely ok.  My worry as as we grow these tables select performance will slowly
degrade to the point that the response time to the user will be unacceptable.

What type of index would you recommend for this type of searching?  Would any of
the more exotic Oracle technologies (IOT, partitions, table compression, etc)
help with performance?  Is there some type of functional index, possibly at the
"/" boundries that might be of use?

For future applications I have toyed with the idea of breaking each directory
level into it's own record and using connect by and SYS_CONNECT_BY to get the
paths.  Additionally, I would expect I could partition the data by the
str_show_id field.  For legacy applications though I would appreciate your input
to maximize searching this specific type of text string.

Additional information:
  Most of the queries are for complete text strings between the "/".  There can
be some that look for %odl% as in "model" but in general the querys are looking
for "%model%"

      and we said...
Oracle Text is what you are looking for.  It'll parse each string around the '/'
for you and set up a text index.  For "full terms", this'll be fast.  ONLY use
the '%' when you HAVE to.

Consider:

ops$tkyte@ORA920> create table t ( x varchar2(100) );

Table created.

ops$tkyte@ORA920> insert into t values (
'/shots/home/pex/model/sz02/pix/out/.../sz02_blah1.jpg' );

1 row created.

ops$tkyte@ORA920> insert into t values (
'/shots/home/pex/model/sz02/pix/out/.../sz02_blah2.jpg' );

1 row created.

ops$tkyte@ORA920> insert into t values (
'/shots/home/pex/model/sz03/pix/out/.../sz03_foo1.jpg' );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index t_idx on t(x) indextype is ctxsys.context;

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t where contains( x, 'model' ) > 0;

X
---------------------------------------------------------------------------------
-------------------
/shots/home/pex/model/sz03/pix/out/.../sz03_foo1.jpg
/shots/home/pex/model/sz02/pix/out/.../sz02_blah2.jpg
/shots/home/pex/model/sz02/pix/out/.../sz02_blah1.jpg

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t where contains( x, '%03' ) > 0;

X
---------------------------------------------------------------------------------
-------------------
/shots/home/pex/model/sz03/pix/out/.../sz03_foo1.jpg

when we used model, it was very efficient.  when we use %03, less efficient --
but still works.

See

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-TEX
for details.

Review &
      Followup

      Rating: 3
alternative
      approaches  September 30, 2003
Reviewer:  Mikito
      harakiri
Oracle text might be a big hammer that can be applied to this problem too.
Although I would be skeptical about the following:
1. Throughput. Generally, the more exotic index is, the more overhead it
stupulates for inserts/updates. For example, unlike standard B-tree indexes,
bitmap and materialized views fair not quite well from OLTP perspective. Check
what locking schema for inverted index oracle uses.
2. Query optimization. Generally, the more complex domain is, the more mature
query optimization is. How realistic  selectivity/cardinality estimation for
text and objects would you expect when there are still bugs filed against
estimated selectivity of simple predicate "like 'abcde%'"?

If your problem is graph traversal, then, yes, "connect by" might be an
appropriate answer. But your queries have to be formulated in graph terms ("find
all the children of all the nodes named 'model'") instead of text ("find all the
rows that has 'model' substring").

Also, you may check nested intervals approach

http://www.dbazine.com/tropashko4.html
It uses standard SQL technique, so you might expect less surprises there.

          Followup:

benchmark em next time.  prove your points.

1) the index updates are actually deferred, they will affect your performance
during insertion not much at all.  I use this technology all over the place.  It
works well.

This is NOT a big hammer, this is the way to do it.

2) so, go ahead, ignore everything more complex then the number "1" -- other
numbers are harder then "1".  ???

there will be no surprises using a mature technology like TEXT.

sorry -- but for something so simple as this, Text is the right approach.  Its
in the database, the code is written.  To answer the queries they say they need
-- thats it.

I'd love to see the code you would have to write to implement the other
techniquies you describe.  Both insert and retrieval.  Ugh -- it'll be "not
small"

      Rating: 4
Thanks Tom (and
      others)  September 30, 2003
Reviewer:  Mike
      Wilson  from Los Angeless
Yes, I am aware of the nested path approach and had actually thought of playing
with it.  I have some excellent data to test both approaches as I have several
million records that need to be indexed efficiently in this manner.

      Rating: 3
how to update
      nested intervals  September 30, 2003
Reviewer:
      Mikito harakiri
http://www.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=Fs58b.22%24927.175%40news.oracle.com

      Rating: 5
Mikito - it is
      not a math, it is a database!  October 03, 2003

Reviewer:  msc
I do not understand why should somebody want to use "nested intervals approach"
when having Oracle at hand :-/ (or "materialized paths" - what a fancy name for
trick used back in 1995 in Paradox and never-ever needed after going with
Oracle).

Lets take article:
http://www.dbazine.com/tropashko4.html
There is a function definition:
  function distance( .. ) ..

And then there is a query:

<quote>
All the ancestors of FORD, excluding himself:

select h2.name from hierarchy h1, hierarchy h2
where h1.name = 'FORD'
and distance(h1.numer, h1.denom,
             h2.numer, h2.denom)>0;
</quote>

It is not hard to see that function distance() is used to implement join between
h1 and h2. Given that table hierarchy has "millions of rows of information" (as
original question states) it is obvious that function distance() will be
executed millions of times... and this is SLOW (no need to go for trace for
that). Don't even bother to think impact of recalculating distances for few
millions of rows.

If you really do not like Oracle Text (why not?) then you can take cowboy way
and implement it yourself ;-)

Use three tables:

asset -< asset_blongs_to_folder >- folder_name

create table folder_name
( name varchar
, folder_id ...
, constraint fn_pk primary key (name)
) organization index including folder_id;

name, folder_id
shots, 1
home, 2
pex, 3
model, 4
sz02, 5
pix, 6
out, 7
sz02_blah1.jpg, 8
sz02_blah2.jpg, 9
sz03_foo1.jpg, 10
...

create table asset_blongs_to_folder
( asset_id ...
, folder_id ...
, constraint abf_pk primary key (folder_id, asset_id)
) organization index ;

For sz02_blah1.jpg it would make:
asset_id, folder_id
1, 1
1, 2
1, 3
1, 4
1, 5
1, 6
1, 7
1, 8

For sz02_blah2.jpg it would be:
asset_id, folder_id
2, 1
2, 2
2, 3
2, 4
2, 5
2, 6
2, 7
2, 9

And then query:

select a.*
from assets a
   , asset_blongs_to_folder abf
   , folder f
where a.asset_id = abf.asset_id
  and abf.folder_id = f.folder_id
  and f.name = 'pix'  -- or f.name like '%pix%

and let the Oracle do the all hard work for you. All index lookups (or index
scans if using like operator). And no few millions function calls to kill your
database.

Of course you have to create few triggers (insert, update,delete) on table asset
to split path into tokens and insert/delete rows in folder and
asset_blongs_to_folder. Convert folder names to uppercase in trigger to
implement case-insensitive search. But this code is much-much easier than to
scale and shift distances (no self updates, no mutating table problems, far less
rows updated).

Oracle is a database. Use it like a database.

September 30, 2003 Art -- Thanks for the question regarding "Summing
      up within a single VARRAY", version 8.1.7

      You Asked
Hi, Tom.

I have a VARRAY representing a week's worth of time, and I am looking for a
simple way to sum up the hours of time for that VARRAY into a local variable.

SQL> CREATE OR REPLACE TYPE workday AS OBJECT (
  2      my_day        DATE
  3  ,   my_hours      NUMBER
  4  );
  5  /

Type created.

SQL> CREATE OR REPLACE TYPE workweek AS VARRAY(7) OF workday;
  2  /

Type created.

SQL> DECLARE
  2      l_week        workweek;
  3      l_tot_hrs     NUMBER;
  4  BEGIN
  5      -- Load the VARRAY with a week of dates and hours
  6      SELECT(CAST(MULTISET(SELECT
  7                               SYSDATE + ROWNUM - 1
  8                           ,   (MOD(TRUNC(ABS(DBMS_RANDOM.RANDOM)),16)+29)/4
  9                           FROM all_objects
 10                           WHERE ROWNUM <= 7)
 11             AS workweek))
 12      INTO l_week
 13      FROM DUAL;
 14      FOR i IN 1..l_week.COUNT LOOP
 15          DBMS_OUTPUT.PUT_LINE('Day: ' || l_week(i).my_day || ', Hours: ' ||
l_week(i).my_hours);
 16      END LOOP;
 17      --SELECT SUM(l_week.my_hours) INTO l_tot_hrs FROM TABLE(l_week);
 18      DBMS_OUTPUT.PUT_LINE('Total for week: ' || l_tot_hrs);
 19  END;
 20  /
Day: 20-AUG-02, Hours: 10.5
Day: 21-AUG-02, Hours: 10.25
Day: 22-AUG-02, Hours: 8
Day: 23-AUG-02, Hours: 8
Day: 24-AUG-02, Hours: 8
Day: 25-AUG-02, Hours: 10.5
Day: 26-AUG-02, Hours: 8.25
Total for week:

PL/SQL procedure successfully completed.

I gave it a shot, but when I uncomment the line above to attempt to populate
l_tot_hrs, I get:

SQL> /
DECLARE
*
ERROR at line 1:
ORA-06550: line 17, column 5:
PLS-00801: internal error [22914]
ORA-06550: line 17, column 5:
PL/SQL: SQL Statement ignored

Is there a way to do this within SQL, or should I just fall back on iterating
through and maintaining a tally on the PL/SQL side?

As always, Tom, thank you for your assistance.

Art.

      and we said...
ops$tkyte@ORA817DEV.US.ORACLE.COM> DECLARE
  2      l_week        workweek;
  3      l_tot_hrs     NUMBER;
  4  BEGIN
  5      -- Load the VARRAY with a week of dates and hours
  6      SELECT(CAST(MULTISET(SELECT
  7                               SYSDATE + ROWNUM - 1
  8                           ,   (MOD(TRUNC(ABS(DBMS_RANDOM.RANDOM)),16)+29)/4
  9                           FROM all_objects
 10                           WHERE ROWNUM <= 7)
 11             AS workweek))
 12      INTO l_week
 13      FROM DUAL;
 14      FOR i IN 1..l_week.COUNT LOOP
 15          DBMS_OUTPUT.PUT_LINE('Day: ' || l_week(i).my_day || ', Hours: ' ||
l_week(i).my_hours);
 16      END LOOP;
 17      select sum(my_hours) into l_tot_hrs
 18        from TABLE( cast( l_week as workWeek ) );
 19      DBMS_OUTPUT.PUT_LINE('Total for week: ' || l_tot_hrs);
 20  END;
 21  /
Day: 20-AUG-02, Hours: 9.25
Day: 21-AUG-02, Hours: 9
Day: 22-AUG-02, Hours: 7.25
Day: 23-AUG-02, Hours: 11
Day: 24-AUG-02, Hours: 9.5
Day: 25-AUG-02, Hours: 8.25
Day: 26-AUG-02, Hours: 9
Total for week: 63.25

PL/SQL procedure successfully completed.
Review &
      Followup

      Rating: 5
Thanks for
      straightening that out for me  August 20, 2002

Reviewer:  Art  from St. Paul, MN

      Rating:
      5
  August 21, 2002
Reviewer:  kiro

Just Great!

      Rating: 4
Did not work
      for me.  September 30, 2003
Reviewer:  Jay
      Shi  from Seminole, FL USA
I got the same PLS-00801: internal error [22914] when trying to group by data
from nested table. I have 2 sets of data from the same table, and group by
different columns, then I need to combine them into one dataset, and last use
max(decode(...)) to convert it to a pivot table before send back to Crystal. I
have tried to put results into object table, like the example you have for
weekly total hours, but the same error still there when compile. Any idea ?

The top comment lines are the types defined outside proc.
/*
create or replace type ProbeTranType  As Object (CallDate     varchar2(11),
                              Calltype     varchar2(15),
                              GroupCol     varchar2(40),
                              Counter      number);
create or replace TYPE PTransTblType AS TABLE OF ProbeTranType;

create or replace type OfferSumType As Object (CallDate     varchar2(15),
                                               Calltype     varchar2(15),
                                               GroupCol     varchar2(15),
                                               OFFER_1      Number,
                                               OFFER_2      Number,
                                               OFFER_3      Number,
                                               OFFER_D      Number,
                                               OFFER_X      Number,
                                               OFFER_N      Number,

                                               OFFER_P      number);
/
create or replace TYPE OfferSumTblType AS TABLE OF OfferSumType;

*/

create or replace procedure CE_probeTran_proc
as
c_cur                          CE_CLIC_PKG.CE_CLIC_TYP;
c_result                       CE_CLIC_PKG.CE_CLIC_TYP;
Transfer1                      ProbeTranType;
MyTransfers                    PTransTblType;
OfferSum                       OfferSumType;
OfferSumTbl                    OfferSumTblType;

begin

MyTransfers := PTransTblType();
OfferSumTbl := OfferSumTblType();
OPEN c_cur for
select CALLDATE, CALLTYPE, to_char(OfferNum) as GROUPCOL, count(*) AS COUNTER
from (
select TO_CHAR(CallDate, 'dd-mon-yyyy') AS CALLDATE, offerName, CallType,
asset_id, count(Trans_id) as OfferNum
From offer_dtl
group by CallDate, offerName, CallType, asset_id
) group by CallDate, CallType, to_char(OfferNum);

LOOP
   BEGIN
   FETCH c_cur INTO Transfer1;
   EXIT WHEN c_cur%NOTFOUND;
    MyTransfers.extend;
    MyTransfers(MyTransfers.last) := Transfer1;
    END;
END LOOP;
CLOSE c_cur;

OPEN c_cur for
select TO_CHAR(CallDate, 'dd-mon-yyyy') AS CallDate, CallType, Response_cd as
groupcol, count(Response_cd) as Counter
From (
select Asset_id,
       Trans_id,
       CallDate,
       CALLTYPE,
       offerName,
       RESPONSE_CD from offer_dtl
) group by CallDate, CallType, Response_cd ;
LOOP
   BEGIN
   FETCH c_cur INTO Transfer1;
   EXIT WHEN c_cur%NOTFOUND;
    MyTransfers.extend;
    MyTransfers(MyTransfers.last) := Transfer1;
    END;
END LOOP;
CLOSE c_cur;

Open c_result for   -- compile error here.
select CallDate, Calltype,
       MAX( DECODE(GROUPCOL, '1', COUNTER, 0))                  AS OFFER_1,
       MAX( DECODE(GROUPCOL, '2', COUNTER, 0))                  AS OFFER_2,
       MAX( DECODE(GROUPCOL, '3', COUNTER, 0))                  AS OFFER_3,
       MAX( DECODE(GROUPCOL, 'D', COUNTER, 0))                  AS OFFER_D,
       MAX( DECODE(GROUPCOL, 'X', COUNTER, 0))                  AS OFFER_X,
       MAX( DECODE(GROUPCOL, 'N', COUNTER, 0))                  AS OFFER_N,
       MAX( DECODE(GROUPCOL, 'P', COUNTER, 0))                  AS OFFER_P
from   TABLE(cast(MyTransfers as PTransTblType ))
group by CallDate, Calltype;
--
--LOOP
--   BEGIN
--   FETCH c_result INTO OfferSum;
--   dbms_output.put_line('====');
--   dbms_output.put_line(OfferSum.calldate);
--   dbms_output.put_line(OfferSum.Calltype);
--   dbms_output.put_line(OfferSum.GroupCol);
--   dbms_output.put_line(OfferSum.OFFER_1);
--   EXIT WHEN c_result%NOTFOUND;
--   OfferSumTbl.Extend;
--   OfferSumTbl(OfferSumTbl.last) := OfferSum;
--   END;
--END LOOP;
CLOSE c_result;

end;
/
Compile Error on line :"Open c_result for" near end of file.
Thanks!

          Followup:

I'm curious as to why there is any procedural code at all here?  All I see is
what should be a SINGLE query.

do your pivot on

from ( select CALLDATE, CALLTYPE, to_char(OfferNum) as GROUPCOL, count(*) AS
COUNTER
from (
select TO_CHAR(CallDate, 'dd-mon-yyyy') AS CALLDATE, offerName, CallType,
asset_id, count(Trans_id) as OfferNum
From offer_dtl
group by CallDate, offerName, CallType, asset_id
) group by CallDate, CallType, to_char(OfferNum)
UNION ALL
select TO_CHAR(CallDate, 'dd-mon-yyyy') AS CallDate, CallType, Response_cd as
groupcol, count(Response_cd) as Counter
From (
select Asset_id,
       Trans_id,
       CallDate,
       CALLTYPE,
       offerName,
       RESPONSE_CD from offer_dtl
) group by CallDate, CallType, Response_cd );

remove the procedural code and done.

      Rating: 4
Thanks Tom,
      But....  September 30, 2003
Reviewer:  Jay
      Shi  from Seminole, FL USA
Thanks Tom. I did that too early today before submitting to this site, but the
DBA here does not want query go through database twice, do not like use "union
all" and encourage use temp tables. I have not got my single query approved yet.
Because got rejected before for those kind of union, I did this one while
waiting approval. For my example, data cache should take care of running the
same inner query (from offer_dtl) twice, and my result before union is less then
50 rows. The single query as you said should do the job, also my chois, but I am
a contractor here. (table in this example offer_dtl, is my testing one, the real
one is a query of 4 tables join returning for average 80K rows).
I do want to find out why nested table is not working. And we suppose to report
to Oracle for error PLS-00801. I also had trouble with object type with date
attribute, record type with date attribute works good with fetch cursor, but I
have to convert date to char to make this one working.

Thanks.

          Followup:

HUH???????

what is with "going through the database twice" - i just took YOUR code and
removed the code?  same number of "queries" going on.

"union all" -- what is wrong with that?  your DBA is picking between

a) run two queries, use single row fetches to retrieve data into plsql, send
data BACK to SQL (eg: perform union all in plsql by your self), run yet another
query

versus

b) run a single query that most efficiently and effectively returns the required
data using the least amount of resources on the server

I cannot believe what I'm hearing here.  It doesn't make sense.

September 30, 2003 -- Thanks for the question regarding "normalize
      and surrogate key in oltp", version 8.1.6

      You Asked
Tom:

we are OLTP.
when table are fully normalized and some surrogate key is introduced,
normally , it introduce a lot of join. is that all right?
without introduce surrogate key, we can reduce lot of join.

      and we said...
The surrogate key has NOTHING to do with the number of joins.  A surrogate key
is simply a single column replacement for the NATURAL key of the table.

Look at EMP and DEPT.

It can be said that DEPTNO in DEPT is a surrogate key for DNAME.  The addition
(or lack thereof) of this surrogate key will not impact the number of joins --
it only impacts the columns by which you join in the first place!

Sure, using denormalization can reduce the number of JOINS but consider -- if we
moved DNAME, LOC into EMP from DEPT - we now have hundreds or thousands of
records to update in order to change the LOC of a departement.  We have
redundant data, we have data integrity issues.  If I need a poplist of
departments to pick from -- I have to query the much larger EMP table and
distinct the results, rather then querying the smaller, already distinct DEPT
table.

DE-Normalization is OK for read mostly/query systems (data warehouses).
Normalization is pretty much a must in OLTP (with some exceptions on a case by
case basis)

Joins are NOT evil -- we are sort of built to do them.
Review &
      Followup

      Rating:
      3
  August 23, 2001
Reviewer:
      A reader
deptno is different.because it is meanful.
if user always know deptno , then they can go directly to
emp table and do the query, but if they only know deptname,
they will need to go through dept table.

sometimes we may add some meaningless surrogate key and user don't know it
actually. so they need to go through some other table to get the key first, so
it take one more join.

when i post my question at dbasupport, somebody tell me that
they want to use surrogate key as much as possible.
what's your opinion?

          Followup:

deptno was just an example.  think outside the box for a moment -- pretend
deptno was really named "ID".  You think deptno is "different" because deptno=10
means something to you.  It doesn't mean anything to me though, its just a
number.

As for:

"sometimes we may add some meaningless surrogate key and user don't know it
actually. so they need to go through some other table to get the key first, so
it take one more join."

I will maintain once again that you have not imposed an EXTRA join on anyone.
You have changed the key they will use to join -- but you have NOT made an extra
join here.  If you removed the surrogate key from the table and used the natural
key -- you would have the SAME situation, the same number of joins.

The use of a surrogate key makes it generally EASIER to join.  People use a
single surrogate key column in place of the natural key which may be many
columns "wide" (its easier to say "where a.id = b.id" then "where a.c1 = b.c1
and a.c2 = b.c2 and a.c3 = b.c3" and so on).

I personally use surrogate keys all of the time.  Look up in the URL above,
there is a big "display_id" number up there (a combination of a sequence, time,
and random number.  The reason in this case is because the base table didn't
have a natural key (your question doesn't have a key really).

Even in tables that appear to have a natural key, I use surrogates.  This is to
protect myself when people decide the primary key value needs to be changed (and
they always do).  The only imutable column in the table is really the surrogate
key -- the rest are subject to modification.  Since I don't want to have to
cascade that update to dozens of tables -- i use the surrogate.

      Rating:
      4
Excellent.  August 24, 2001
Reviewer:
      Pritish Kulkarni  from Mumbai, India.

      Rating: 5
unique
      index  September 24, 2003
Reviewer:
      A reader
If we are using surrogate keys, then we will have to use an unique index on
columns which would have made up the natural key of the table.

Is the above statement true.

For example, in the department table if you use a surrrogate key  dept_id, then
you have to use a unique index on deptno, as there cannot be two departments
with the same deptno.

Thank you

          Followup:

the above statement is false.

the correct statement is "we will have to use a unique CONSTRAINT on the columns
that would have made up the natural key"

not an index, a constraint....

they are different.  constraints are what you use to enforce uniqueness -- and
they may use a unique or non-unique index as appropriate to help them do their
job.

      Rating: 5
Unique
      Constraint Vs Unique index  September 25, 2003

Reviewer:  A reader
Tom please distinguish between

1.Unique Constraint Vs Unique index

"they are different.  constraints are what you use to enforce uniqueness -- and
they may use a unique or non-unique index as appropriate to help them do their
job.
"

2.How can we enforce a unique constraint using a non unique index. Please
explain.

          Followup:

you do not enforce unique constraints

Oracle does.

and all it needs is an index -- any index.  say you have a unique constraint on
table T column X, any of the indexes following will suffice:

create index i on t(x);
create unique index i on t(x);
create index i on t(x,y,z);
create unique index i on t(x,y,z);

oracle only needs an index.  not a unique one.  in fact, if you:

create table t ( x int unique DEFERRABLE );

you'll find we create a NON-unique index by default there.

      Rating: 3
Ensuring
      uniqueness   September 26, 2003
Reviewer:
      Gabriel  from Toronto
<Tom>
create table t ( x int unique DEFERRABLE );
you'll find we create a unique index by default there.
</Tom>
… you have it correct in other threads ... non-unique.

<Tom>
and all it needs is an index -- any index.
</Tom>
… any B*Tree index … bitmap won’t do for sure :)

Sorry man, I know this was a low blow … only wanted to prove context is
important … since on this case …

<A reader>
If we are using surrogate keys, then we will have to use an unique index on
columns which would have made up the natural key of the table.
</A Reader>
… you said …
<Tom>
the above statement is false.
the correct statement is "we will have to use a unique CONSTRAINT on the columns
that would have made up the natural key"
</Tom>

… without really saying why is it false (the fact you claim yours is true
doesn’t necessarily make the other false).

Let us ignore the “have to” part, which would make both statements false (nobody
is twisting arms … although, personally I would recommended it … if a natural
key is not enforced, preferably with an unique constraint), and replace it with
“could”.

So what was the context? … the problem being addressed? Well, my reading of the
thread is something like “An Entity has a natural key (hence ensuring uniqueness
is important) … when implementing the table out of this entity we decide to have
a surrogate primary key … what is to happen with the natural key?” … and not
“what is the best way to go about implementing the natural key”! The better way?
… yes, the unique constraint! But surely I can create an unique index on a
column without having declared an unique constraint on it … and uniqueness is
safeguarded! The best way? … imho, unique constraint + mandatory column(s) …
I’ll state my case if you think is necessary.

In fact, you “provoked” the reaction by stating, right at the beginning:
<Tom>
A surrogate key is simply a single column replacement for the NATURAL key of the
table.
</Tom>
… “replacement” wasn’t the best choice, was it? … “in addition to” or anything
that doesn’t suggest an abandonment of the natural key would’ve been better.

Since I’m here … I have a real question (not just opinions :)) ..

SQL*Plus: Release 9.2.0.1.0 - Production on Fri Sep 26 23:09:32 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Enter password:

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

SQL> create table a ( x int primary key );

Table created.

SQL> select constraint_name, constraint_type,index_name from user_constraints
where table_name='A';

CONSTRAINT_NAME                C INDEX_NAME
------------------------------ - ------------------------------
SYS_C003040                    P SYS_C003040

SQL> desc a
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 X                                         NOT NULL NUMBER(38)

Why is not the NOT NULL constraint showing in USER_CONSTRAINTS? Where is
DESCRIBE getting its info from?

Gabriel

          Followup:

thats whats i meant "non-unique", forgot the word non, fixed it.

it seemed simple enough to me though on the rest of the stuff.

<statement made>
If we are using surrogate keys, then we will have to use an unique index on
columns which would have made up the natural key of the table.

Is the above statement true.
</statement>

answer supplied:

false, statement is not true.

we do not need a unique index, we need a unique CONSTRAINT.

      Rating: 5
and is all for
      a good cause ...  September 28, 2003
Reviewer:
      Gabriel  from Toronto
The other issue aside, I was hoping you would have a word or two on ...

create table a ( x int primary key );

... after which DESCRIBE shows a NOT NULL check constraint on X but
USER_CONSTRAINTS doesn't.

Gabriel

          Followup:

a primary key is known to imply "not null".  the primary key is the NOT NULL
constraint!  there is no need for a NOT NULL constraint, it is the very
definition of what a primary key is.

      Rating: 5
semantics
      ...  September 28, 2003
Reviewer:  Gabriel
       from Toronto
No question ... the primary key column is/has to be constrained against NULL
values ...

Concepts, Chapter 21, Section: Primary Key Constraints and Indexes
<quote>
Oracle enforces all PRIMARY KEY constraints using indexes. In Figure 21-5, the
primary key constraint created for the department_id column is enforced by the
implicit creation of:
A unique index on that column
A NOT NULL constraint for that column
</quote>

if ... <Tom>the primary key is the NOT NULL constraint</Tom> then I should be
able to re-state the above as "... the primary key constraint ... is enforced by
the implicit creation of a unique index and a primary key constraint ..." and
expect it to be correct.

To me, that <Concepts> statement is misleading ... but, yes, it may be my
problem after all.

Disclaimer: never doubted the value of the advice you provided in this thread,
just the way you sometimes did it (as in "choice of words").

In the grand scheme of things, all these semantics are not worth 1 penny ... and
so I'll stop before being told "get off my site you vagabond".

Gabriel

      Rating: 3
was this really
      necessary?  September 30, 2003
Reviewer:
      Gabriel  from Toronto
Tom,

Following …

http://asktom.oracle.com/pls/ask/f?p=4950:8:2534945178675822451::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:12678058160686,
I decided to insist in arguing …
<statement to be proven>
In Oracle, one could implement the natural key of an entity with
A.    an unique index
B.    an unique constraint
are either both TRUE of both FALSE.
</statement to be proven>

I’ll only do the “both FALSE” part to keep it short.

If you argue the above statement does not reflect what has been stated so far in
this thread then I’ll agree to disagree (since we are totally disconnected) and
call it quits.

You valuated A as being FALSE and B as being TRUE … and the only obvious
objective criteria you could have used is B is “more complete” than A.

If you disagree with this “criteria” then let me know what was your criteria …
if it is “because I said so” or “Oracle says so” than I’ll call it quits.

Based on this valuation system I could come up with option C and argue C is the
“most complete” … hence B is FALSE; or I could prove B cannot be TRUE (without
having to even state C … I can show there has to be something more than B
states).

When it comes to ER modeling, Oracle provides plenty of features (it is an RDBMS
after all) … for the “primary key” ER concept, Oracle provides the “primary key
constraint” construct … for the “natural key” ER concept, Oracle does not
provide a construct (I’m not aware of any “natural/candidate/alternate key
constraint” … and, as I am out to prove, the “unique constraint” construct is
not it either).

So, what is the difference between the “primary key” and “natural key” ER
concepts? … none whatsoever except for an arbitrary choice of which one should
be the “primary” … that is the one that usually gets migrated to related
entities (in theory, both can be migrated to different dependent entities). The
“primary” and “natural” words are really used to provide nuances to essentially
the same thing … a “key of an Entity” … which specifies that each instance of
the Entity can be uniquely identified by the values in the key. In fact, they
don’t talk about “natural keys”, but “candidate keys” which, after the selection
process, become “primary” and “alternate key” … but we can stick with the
“natural”.

Let us assume, we have an entity E=(x1,x2,x3,x4,…) with both K1=(x1,x2) and
K2=(x3,x4) as ER keys … they are also “natural” because we didn’t artificially
create X1, X2, X3, X4 or derived them from other attributes … they are all
natural attribute of the entity E. Is it conceivable such entity exists in real
life? I would say, Yes.

Having identified K1 and K2 let me decide on the “primary” … well the candidate
keys have already been established … the only thing I’m doing now is toss a coin
… the decision of which one is “primary” doesn’t have anything to do with the
existence or fabric of the keys. Let us assume we pick K2 as the “primary”.

We implement E as Oracle table T, K1 as an Oracle unique constraint and K2 as an
Oracle primary key constraint.

… insert into T (x1,x2,x3,x4,…) values (NULL,v2,v3,v4,…) … where v3, v4, v5 are
NOT NULL.

Question: can I now reverse my toss of coin and pick K1 as the “primary”? … NO …
a totally arbitrary event has introduced differences between the candidate keys.

Of course, that NULL into x1 did it … which goes to prove, when implementing a
candidate/natural key, one also has to say something about the nullability of
the attributes making up the key. The “unique constraint” Oracle construct, as
opposed to the “primary key” one … does not make any provisions regarding the
nullability of the columns included in its definition. Hence there has to be
more than what B states in order to be complete. Hence, based on the
“completeness” valuation system, B is FALSE … in fact, both A and B are FALSE.

End.

If you find flaws in my little Boolean excursion please point them out. If you
think all of this has no value whatsoever then yes, let us call it quits.

Opinion coming …

So what should that option C be in order to evaluate to TRUE? Well, in Oracle,
in order to preserve this little bit of logic above (and if no flaws in it, of
course), one should implement a candidate key of an entity with an Oracle unique
constraint and NOT NULL constraints for all component columns … or as a primary
key constraint of course (but only one such PK constraint can exist for a
table).

I find this a bit strong since the only problem posed by NULLs when implementing
a “key of an Entity” is NULL-tuples. All values of type (v1,v2), (v1,NULL),
(NULL,v2) can uniquely identify each instance of an Entity …. the only problem
is the (NULL,NULL) pair. One could then argue, that including the NOT NOLL
constraints in the Oracle primary key construct is too strong.

But this is an opinion … things are and will stay the way they are. And that’s
perfectly OK with me. Practicality always wins.

Gabriel

          Followup:

umm, not really sure what the point here is

you are putting words into my mouth -- your statement to be proven isn't really
a point I was making.

one would not IMPLEMENT a natural key with either of those (if you want to parse
words).

one could ENFORCE a natural key with a unique constraint (and not null
constraints).

let me just say this.  i'm rather pragmatic in my approach to things.  Ask me a
question -- I'll give you my answer and try to back it up with examples.

Argue esoteric nuances -- and well, i don't get all of the way through the
reading.  Especially when the discussion focuses around a "mysterious point C
that need not even be listed since it is known to exist".

So, i guess I'm saying "i didn't get it, not following WHY we are having this
discussion"


Subject | Article | Last Updated (East coast time)
Dependency of stored procedure | article only | Oct 01, 2003 08:26 am
Cuncurrent execution of procedures | article only | Oct 01, 2003 08:37 am
The best insert performance... | article only | Oct 01, 2003 08:41 am
Upgrade Documentation from 7.x to 9i | article only | Oct 01, 2003 08:42 am
Introduction to Shared Server Performance | article only | Oct 01, 2003 09:36 am
Constraints | article only | Oct 01, 2003 09:38 am
adding a millisecond to a date | article only | Oct 01, 2003 01:41 pm
Users and Roles | article only | Oct 01, 2003 01:52 pm
Difference Between ROLLUP and CUBE | article only | Oct 01, 2003 02:32 pm
How to log in using a cookie safely | article only | Oct 02, 2003 07:35 am
RMAN backup strategy!. | article only | Oct 02, 2003 07:46 am
Second Highest Sal | article only | Oct 02, 2003 07:59 am
Having problems while validating XML doc in Database based on XSD doc. | article only | Oct 02, 2003 09:43 am
performance tuning | article only | Oct 02, 2003 09:48 am
Why "application server" ? | article only | Oct 02, 2003 09:50 am
Flashback Question. | article only | Oct 02, 2003 10:32 am
How to dynamically add "missing" data | article only | Oct 02, 2003 03:52 pm
Questions about how to create users in PL/SQL and assign database roles | article only | Oct 02, 2003 04:43 pm
Oracle Label Security vs Fine Grained Access Control | article only | Oct 02, 2003 06:52 pm
How about the technology underneath asktom? | article only | Oct 02, 2003 10:28 pm
Wildcards with a between? | article only | Oct 03, 2003 08:02 am
dbms_space.free_space | article only | Oct 03, 2003 08:05 am
export tablespace | article only | Oct 03, 2003 09:29 am
how to display selective record twice in the query? | article only | Oct 03, 2003 10:24 am
How to pass a Pl/SQL Table? | article only | Oct 03, 2003 10:25 am
Multiple Inserts | article only | Oct 03, 2003 11:03 am
extend in varrays and performance | article only | Oct 03, 2003 12:02 pm
Using Interval Datatype | article only | Oct 03, 2003 01:33 pm
Top 5 Salaries by Department. | article only | Oct 03, 2003 01:33 pm
DBMS_SQL | article only | Oct 03, 2003 03:41 pm
Return values from a PL/SQL table into a ref cursor? | article only | Oct 04, 2003 09:40 am
Prepared statement Vs regular statement in JDBC | article only | Oct 04, 2003 09:45 am
Using dbms_rowid.rowid_row_number(rowid) in queries | article only | Oct 04, 2003 05:32 pm
How to convert a table with long_row data into a CLOB data type table | article only | Oct 04, 2003 07:24 pm
Progessive slow down when inserting into a table with a clob | article only | Oct 04, 2003 07:26 pm
Datatype coversion | article only | Oct 04, 2003 07:58 pm
RMAN backup to disk | article only | Oct 05, 2003 09:22 am
Pl/SQL web cartridge | article only | Oct 05, 2003 09:23 am
On-line re-org | article only | Oct 05, 2003 09:53 am
JDBC driver | article only | Oct 05, 2003 06:04 pm
v$object_usage | article only | Oct 05, 2003 06:27 pm
Context and Packages | article only | Oct 05, 2003 06:28 pm
Backup Strategy | article only | Oct 06, 2003 07:54 am
How to search record having Clob field | article only | Oct 06, 2003 08:04 am
how to fetch database to list item | article only | Oct 06, 2003 08:10 am
Modeling graphs inside a relational database | article only | Oct 06, 2003 08:12 am
Where can I find info on writing callable database routines (APIs) using Pro*C and embedded SQL? | article only | Oct 06, 2003 09:33 am
Performance and Creation Time for unique vs nonunique indexes | article only | Oct 06, 2003 10:31 am
Synonym problems | article only | Oct 06, 2003 10:33 am
kill session and process | article only | Oct 06, 2003 11:04 am
ora-00164 AUTONOMOUS TRANSACTION DISALLOWED WITH DISTRIBUTED TRANSACTION | article only | Oct 06, 2003 11:34 am
sql%rowcount = 0 for an update | article only | Oct 06, 2003 09:25 pm
How to get the port | article only | Oct 07, 2003 07:52 am
Flush shared pool | article only | Oct 07, 2003 08:06 am
Is materialized view synchronization cheap? | article only | Oct 07, 2003 09:26 am
NOT IN in SQL | article only | Oct 07, 2003 09:44 am
Names for Database Links | article only | Oct 07, 2003 10:10 am
unable to connect using database link | article only | Oct 07, 2003 10:55 am
Create private synonym for another user | article only | Oct 07, 2003 10:56 am
Passing an ARRAY from Java to PL/SQL | article only | Oct 07, 2003 02:17 pm
IN (vs) EXISTS and NOT IN (vs) NOT EXISTS | article only | Oct 07, 2003 06:18 pm
row(s) 1 - 61 of 61
October 01, 2003 Jesse -- Thanks for the question regarding "Dependency of stored procedure", version 8.1.7
You Asked
Hi Tom,
  Is there a way to check the dependency of stored procedures(procedure,
function, trigger). If I compile a stored procedure, other stored procedures
which call this sp will becomes invalid. I need to recompile the others too. Is
there a table which stores the dependency of sp? Thanks.
and we said...
Yes there is HOWEVER

o they will recompile themselves (so you need not waste your time/energy)
o you shouldn't use procedures and functions (standalone) in a real database!!!

Read:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:286816015990
and use PACKAGES.  Avoid the issue in the first place!  You'll be glad you did.

(*_DEPENDENCIES where * = all, user, dba)
Review & Followup
Rating: 4
Sometimes an automatic re-compile is unavoidable May 13, 2002
Reviewer: AB from UK
Tom

Even when using packages an automatic re-compile of dependants is unavoidable.
For example, my PKG_TYPES of shared user-defined types is referenced by several
others. Now when someone needs to add a new shared type they hadn't thought of
at original version time to PKG_TYPES, the spec needs to be re-compiled so all
becomes invalidated. I guess it's something we have to live with.

Followup:
sometimes -- but not often.  I still encourage you to use packages exclusively
in any real system.  avoid standalone procedures and functions!

Rating: 3
Procedures dont ALWAYS recompile themselves. May 13, 2002
Reviewer: IS from MO
Tom on 8.1.7 we use describe <procedure_name> to find out what the field sizes
are before calling the procedure. If a procedure is INVALID the describe gets an
error and the procedure does not get automagically compiled. (The proedure does
get maually recompiled without any compilation errors).
Also a manual recompile of a stored procedure sometimes changes the order of the
parameters. Is there a fix for that? We just replace the procedures when they
become invalid.

Followup:
A manual compile cannot in any way shape or form change the order of
parameters

Only you, by writing NEW code with the parms in a different oracle can do that.

What I do in this case is:

  attempt to describe
  if error due to invalid
      parse begin procedure_name; end;  <<=== this might fail, no parameters!
                                              might even be a function.  It'll
                                              VALIDATE the procedure/function
tho
      attempt to describe
      if error then it reall is broken, not going to run it
  end if

I do it with code like this:

    for i in 1 .. 2 loop
        begin
            dbms_describe.describe_procedure( :ObjectName,
                                   NULL, NULL,
                                   l_overload, l_position,
                                   l_level, l_argument_name,
                                   l_datatype, l_default_value,
                                   l_in_out, l_length, l_precision,
                                   l_scale, l_radix, l_spare );
            exit;
        exception
            when others then
                if ( i = 1 ) then
                    begin
                        exec_cursor := dbms_sql.open_cursor;
                        dbms_sql.parse(exec_cursor,
                 'begin if (1=0) then ' || :ObjectName || ';end if; end;',
                         dbms_sql.native );
                        dbms_sql.close_cursor( exec_cursor );
                    exception
                        when others then
                        if dbms_sql.is_open(exec_cursor) then
                            dbms_sql.close_cursor(exec_cursor);
                        end if;
                    end;
                else
                    raise;
                end if;
        end;
    end loop;

Rating: 3
package invalid caused database hang May 13, 2002
Reviewer: Windy from USA
Tom,
We use packages in the database. There are a lot of dependency among packages in
our database. Sometimes, when developers push a new version of a core package
(changed its spec), it will invalid some other packages. Since we are an online
retailer, we constantly have customers activities that need call packages. And
sometimes, oracle was unable to recompile invalid package in time. Then the
database is in a deadlock. One session has a library cache lock and wait for
library cache pin, while some other sessions hold library cache pin waiting for
library cache lock, and the session tries to recompile packages will never
finish its job. As soon as we block further customer connections, kill all
waiting sessions and recompiled invalid packages, we are fine. But this is not a
solution we like. Could you please give me some suggestion on this situation?

Thank you very much.

Followup:
It is not in deadlock -- we would detect and roll that back.

It is locked up.  You cannot compile a package until the dependencies are not
being used -- you cannot use the package you are attempting to compile until
after it has been compiled.  Classic traffic jam.

In 9i, we have a "quiesce" function -- that effectively "stops" what is going
on.  It is a cleaner way -- but still involves downtime.  Code upgrades do and
will always involve some amount of downtime.

For now, the best approach is to schedule downtime -- get people out -- do the
upgrade and let them back in.

Rating: 4
...cannot in any way shape or form change the order of parameters... May 14, 2002
Reviewer: Padders from UK
Funnily enough there was bug: 782989 in earlier versions of Oracle 8 and 8i
that used to do exactly that during compilation. I believe fix was to flush
shared pool, although I don't know why. Suspect this MUST have been fixed
by now, however!

Rating: 2
Unix code management is easier November 26, 2002
Reviewer: Paul Kelley from Clearwater, Florida
It took a while for the Unix contingent at my shop to convince me about this,
but from an ease of management perspective, Oracle has a lot to learn.  Shutting
down the database, shutting out users, etc.  prior to code upgrades seems crude
if you compare it with what is involved in the same sort of operation in Unix.

A rudimentary example:

cc hello.c
mv -f a.out my.executable
my.executable &

<the background process prints "hello" every 5 seconds

cc goodby.c
mv -f my.executable my.executable.bak
mv -f a.out my.executable
my.executable &

There are now two background processes, one printing
"hello" every 5 seconds, one printing "goodbye" every 5 seconds.  The inodes are
preserved, the directory entries are changed, both new and old can execute at
the same time, I don't have to kill processes running the old version, etc.

Followup:
boy oh boy -- you've been lucky in the past, haven't you.

I see "page fault" written all over your future.

You are scaring me actually.  If you really believe the OS keeps each and every
text file open, forever.  Or that it even opens all files at the same time.  Or
that programs are all trivial.  Please -- don't you ever do a database upgrade
(don't laugh -- I've had people do OS upgrades, while the database is running
and wonder "why did it crash all of a sudden with strange errors?".  Well, you
CHANGED THE OS on us)

what about the program that uses two dynamic libraries?  So you start the
process "a.out" and it loads version 1.0 of lib1.  You decide to upgrade to
version 2.  Now a.out (v1) is still "running" (you are getting lucky) and now
calls something that invokes functionality in lib2 for the first time.  a.out
(v1) now loads library code for v2.  You have a mis-mash.

most unixes have this concept of SHARED TEXT as well.  Yikes on that one...

Many unixes (eg: AIX i believe, HP I'm almost positive) protect you from this
and say "text busy" if you try to recompile an already running program.

Even windows got this right.

I can say, if this is your approach to system upgrades -- I'm glad I don't work
on your systems.  I'd be ripping my hair out.  I'm really hoping you are not an
SA.

Perhaps the "lot to learn" thing is a two way street???

Rating: 5
To Be Or Not To Be November 26, 2002
Reviewer: Mac from Bloomington, IL
Besides, why would one want two versions of the same executable to be running
simultaneously and doing different things? If you change a program, you do so
because you need different behavior. Wouldn't you want *all* runs of this
changed program to produce the new behavior?

Rating: 3
Unix has a better idea? hmm November 26, 2002
Reviewer: Paul Kelley
Don't be scared, we have all of our hair, I'm not an SA.  I wasn't talking about
OS or Oracle product upgrades.  By code upgrade I meant our homegrown apps, not
Oracle or Os.  Most of our homegrown apps are statically linked, and the
multi-versioning thing actually works, according to the Unix/Oracle guys who
have been torturing me.  I forwarded your response to them.  Perhaps they will
feel like responding to you, or perhaps they will stop asking me why Oracle
doesn't work the way they think it should.   Your dynamic libraries example was
the kind of response that should interest them.

BTW I am one of the guys who told you years ago you should write a book and I
would buy it.  You did and I did.

Followup:
but by extension -- I should be able to according to you -- it is so simple? I
mean, if it works on a single binary, why not the world?

(i have a chapter in that book that addresses that "it works the way it works --
the way it works is documented -- it does not work the way you feel that it
should -- cause the guy next to you thinks it should work differently then you
do and it can only work ONE way ;)

And unix systems in general won't let you do this "text busy" is the message you
get.

we'll have to agree to disagree on this point.

Rating: 3
to be or not to be November 26, 2002
Reviewer: Paul Kelley
Pl/sql aside, for the moment,  assume we're talking about a system with hundreds
of users with many reports, some of which run for hours.  Users can submit a
report request at any time, it gets put in the queue for execution.   Assume I
have a new version of a report to install.  The new version has some
enhancements requested by the user community, it is better than the old report,
but the old report has been ok for years and it happens to be running at the
moment in 3 or 4 databases.    Should I have to keep checking each of those
databases to find just the right moment to install?   Should I have to go into
the report request system and postpone execution of this report in all databases
until I get the new version installed?   Wouldn't it be better if I could just
do the installation and let the processes that are running the report just
finish up?

Followup:
yes you should, yes you should with your binaries on the OS as well.

Consider that this report calls 5 packages to process.  Consider that (for
example) this report takes 1 hour to run.  Consider that the first 10 minutes is
in package 1, then package 2, .... and so on.  Consider you "upgrade the code
during minute 30.  So -- would you want NEW package 4 and 5 to execute or the
NON-EXISTENT old package 4 and 5 to execute -- given that you've already used
old packages 3 and 4.

Wouldn't you be really mad if every user loaded their own copy of the code?
(thats what shared text is all about).  We used to do that -- we called it
version 6 of Oracle.  No shared pool, every sql statement compiled into the pga
and was lost forever when you disconnected.  That would be a huge step
backwards.

Or, incorporate a naming convention that lets you do this (eg: the way, umm the
way unix does with .....so.1 .....so.2 and so on)

Still scaring me.

Rating: 4
How much rope? November 26, 2002
Reviewer: Paul
Not sure why you're scared.  Oracle protects me from myself, doesn't it?  And
I'm only playing Unix advocate.  Anyhow, speaking on behalf of some Unix/Oracle
developers in my shop, I would want executing programs (or queries retrieving
data from views) to continue to use the versions of the referenced programs that
were in scope at the start of the session.  I wouldn't want every user to load
his own version,  I'd want the system to manage the versioning and do the usual
Oracle paging bits with the versions that should be in scope for the given
session.

We have several flavors of Unix and at least one version of Linux, all of which
give us enough rope to do this type of installation with Unix apps, many of
which are Pro*C programs.  I have only done this with a trivial program on
Sequent Dynix/PTX.  I'm told it also works in-house on Solaris and Linux.
Apparently we have some staff members who are knowledgeable about this at the
dll level, and who admit that you have to be very careful with dynamic linking.

I'll admit that to me, the dynamic linking scenario seems complex even for a
simple case.   With many sessions and hundreds of programs it would certainly be
daunting.   I'm not sure why a group of people smarter than myself and with
enough time couldn't make it work, though.  Then again there's the question of
how much rope do you want to give me, and of course, what's in it for Oracle.

I'll have to finish with this - In the face of continued mild abuse from the
Unixians, I have maintained all along that we may have to settle for 24X6, 23X1
availability in some of our databases.  I have been told that this is painful
when you need 24 hour web access and ( possibly more to the point in this case )
when you have to log in at odd hours or else hire more staff to manage the off
hours installations.   This is the manageability point that they have tried to
make to me.

In any case congratulations on a great web site and an excellent book.

Rating: 5
to be or not to be November 26, 2002
Reviewer: Jim
Paul,
     I find the thread you started interesting.
I see your point of view and concerns
however, I agree with Tom on this one.
Things are just waaaaay to prone to something going
wrong

Rating: 5
To Be Or Not To Be November 27, 2002
Reviewer: Mac from Bloomington, IL
I see Paul's point regarding long-running reports. But consider another
situation. Suppose we are dealing with transactions that modify data. Suppose we
create a new version of a program to fix the current version which creates
faulty data. In this case, wouldn't we want the new version to be used by
everybody (and specifically prevent the use of old version for the sake of data
integrity)? Thus we would have 2 classes of programs -- tolerant and
non-tolerant -- to manage.

But, as Tom said, it works the way it works.

Thank you Tom for proividing a stimulating and rich environment full of
treasures!

Rating: 5
What if invalid code still works? March 26, 2003
Reviewer: Mark Schifferli from Cambridge, MA
Hi Tom,

Thanks for your wonderful expertise. We have a situation where the package body
is invalid, yet is still in use, much like Windy from USA describes above.
However, the invalid package still seems to be executing fine, sort of. From a
SQLPLUS session, any attempt to execute or recompile the package just hangs:

14:44:36 LISTENER> alter package q compile;
alter package q compile
*
ERROR at line 1:
ORA-04021: timeout occurred while waiting to lock object LISTENER.Q

For the most part, this package is an interface to DBMS_JOB, and the dbms_jobs
started via this package call a procedure in the package to check for things to
do. If there is nothing to do, they wait and check again in a little bit. As far
as I can tell, these jobs are running fine, no invalidation problems at all. How
can this be? It appears that there is invalid code for the sqlplus prompt, and
working code (therefore valid code?) for DBMS_JOB.

We're using 8.1.7.0.0.

Thanks for your help,

Mark

Followup:
a package body cannot be invalid yet in use.  that doesn't make sense.

Rating: 5
What if invalid code still works? (2) March 26, 2003
Reviewer: Mark Schifferli from Cambridge, MA USA
I agree, it doesn't make sense to me either. Perhaps I'm misinterpreting the
situation? Here are the facts that lead me to that conclusion:

* The package body is invalid, though the specs are valid.
* I can't get a lock on the package to recompile it.
* the only sessions that can use the package are those in DBMS_JOBS (some of
which are running now)

17:06:00 LISTENER> select object_type, status from user_objects where
object_name = 'Q';

OBJECT_TYPE        STATUS
------------------ -------
PACKAGE            VALID
PACKAGE BODY       INVALID

17:06:07 LISTENER> alter package q compile;
alter package q compile
*
ERROR at line 1:
ORA-04021: timeout occurred while waiting to lock object LISTENER.Q

17:31:25 TDM.LISTENER> select uj.this_date, uj.this_sec, uj.failures, uj.what
from dba_jobs_running djr, user_jobs uj where uj.job = djr.job;

THIS_DATE THIS_SEC   FAILURES WHAT
--------- -------- ---------- --------------------------------------------------
26-MAR-03 08:35:51            BEGIN q.listen(1099); /* Q1099--*/ END;
26-MAR-03 08:34:53            BEGIN q.listen(204); /* Q204--*/ END;
26-MAR-03 08:34:53            BEGIN q.listen(301); /* Q301--*/ END;
26-MAR-03 08:34:53            BEGIN q.listen(302); /* Q302--*/ END;
26-MAR-03 08:34:53            BEGIN q.listen(200); /* Q200--*/ END;

Any insight in how to interpret this situation is welcome.

Thanks

P.S. Your book is awesome

Followup:
ahh -- ok, the spec has some variables or types in it.

Others are using those variables or types.

The body is not being executed....

can you alter package q compile BODY;

Rating: 5
What if invalid code still works? (3) March 26, 2003
Reviewer: Mark Schifferli from Cambridge MA USA
Thanks for your response. Unfortunately, it didn't work.

alter package q compile BODY
*
ERROR at line 1:
ORA-04021: timeout occurred while waiting to lock object LISTENER.Q

Followup:
see page 119 of expert one on one oracle and query up dba_ddl_locks to see whose
got it.

Rating: 5
What if invalid code still works? (4) March 27, 2003
Reviewer: Mark Schifferli from Cambridge MA USA
Done (yet another reason to appreciate your book).

So from dba_ddl_locks, I get both these for sessions 7-36
LISTENER Q Table/Procedure/Type Null None
LISTENER Q Body                 Null None

And from a version of your showsql script (thanks again) each of these sessions
is doing something like:

DECLARE job BINARY_INTEGER := :job; next_date DATE := :mydate; broken BOOLEAN :=
FALSE; BEGIN
BEGIN q.listen(303); /* Q303--*/ END;
:mydate := next_date; IF broken THEN :b := 1; ELSE :b := 0; END IF; END;

Which to me looks like the syntax of a DBMS_JOB (thanks again, ref. page 1069).
Each job is calling a procedure in the package that is invalid, q.listen. Just
in case the particulars make a difference, that procedure essentially it queries
a table to find if there is a task to execute. If it finds one it hosts out via
java, and if it doesn't find one it sleeps for 10 seconds.

I still can't help but think that these sessions are executing this procedure,
even though the package body is invalid. Querying dba_jobs_running shows
this_date is not null, which implies they are executing, doesn't it?

Thanks again.

Mark

Followup:
yup -- it would appear the package is valid, but the view is "wrong".

I'll have to refer you to support on that one, I haven't seen that myself.

Rating: 5
What if invalid code still works? (5) March 27, 2003
Reviewer: Mark Schifferli from Cambridge MA USA
Thanks for all your help

Rating: 3
mode_held is null in dba_ddl_locks July 01, 2003
Reviewer: Vijay from India
Hi Tom,
      Good Day to you, I have gone through your suggestion of dba_ddl_locks what
i see is like this
session_id owner name        type
13         test  proc_test   Table/procedure/Type
mode_held mode
null      none

there are four session id's other than mine which are having records in this
view and using the object but mode_held is null for all.

But when i try to compile the package I still get the
ORA-04021: timeout occurred while waiting to lock object

what could be the reason and how do i resolve it, as always thanks to you.

Followup:
that means one of them was "using" it while you were trying to rebuild it.

Rating: 4
Thanks Tom for your quick reply July 02, 2003
Reviewer: Vijay from India
Hi Tom,
      Good day sorry to ask you about this again but I am a bit confussed here,
does rows in dba_ddl_locks mean all those users are using the object, please
help.

Thanks as always.

Regards,
Vijay

Followup:
it means they "have at least used", they might not be currently using but at the
time you tried the operation -- they were.

remember it shows you "right now", you got the error "back then".

Rating: 3
Regarding "Dependency of Stpred procedure" July 02, 2003
Reviewer: Raj Jamadagni from Connecticut, USA
Vijay,

instead of ddl_locks ... I prefer to use the procedure "who_is_using" supplied
by Oracle and available on metalink.

Have your DBA install it and give you rights on it, as it needs to be installed
as sys. Run that with the name of your package in question and it will give you
a list of sessions (sid only) that are actively executing the code.

HTH
Raj

Rating: 4
July 29, 2003
Reviewer: Yogesh from Pune
One more question related to this ... One of the member from testing team was
testing one procedure using Debug of TOAD ... I tried making changes in the same
procedure same time ... and when I compiled it it gave me

ORA-04021: timeout occurred while waiting to lock object TEST.PKG_MIGRATE

I could understand this .. But when testing user finished his debug and came
out, I tried once more .. still was getting the same error....  not only that
even after One hour same error ... I even killed all the active and inactive
sessions of test user using TOAD.. but of no use ..  what could be the reason ?

There was one session which was showing as killed but was still coming in list
even hour after .. was this session blocking that Pkg ?

what are ways to get rid of this error ?

Followup:
use dba_ddl_locks to see what sessions might be pointing to it.

Rating: 5
Over 10,000 packages become invalid September 30, 2003
Reviewer: A reader
11.5.7 Apps, 8.1.7.4 database.

It has been 2nd time. There were 10,000 APPS owned package became invalid.
Recompiled all of them, and no problem.

Question, what could triggered such number invalid? no, there have been no
upgrades and patches applied. If there was small number invalid storage objects,
I might figure the roots out. Not such large number.

What could be?

Thanks In Advance

Followup:
someone could have revoked something from PUBLIC -- thats a sure fire way to
invalidate a database.

someone could have revoked something from the owner of the packages.

it is most likely a grant being revoked that did it.  you could enable auditing
to capture the culprit in the future.

Rating: 5
great input !!!! October 01, 2003
Reviewer: A reader

October 01, 2003 Ramasubbu -- Thanks for the question regarding "Cuncurrent execution of procedures", version Oracle8i Enterprise Edition Release 8.1.6.3.0 - Production
You Asked
Hi Tom,

Platform:Win NT,Oracle 8i,R-3

     My scenario is like there are three DT's clubbed into a single
procedure,say

       create or replace procedure club_proc is
       begin
            p1;
            p2;
            p3;
       exception
            when others then null;
       end;
Now execution is happening squentially one by one procedure,from p1 to p3.
I want to execute three procedures cuncurrently since there is no dependency on
each procedure.
How do I achieve it?.

Awaiting ur reply.
muthu
and we said...
first -- the code:

exception
  when others then null;
end;

is a bug -- it is always a bug, if you actually have code that looks like this,
you have a bug.

You can configure the job queues and use the dbms_job package to submit each:

declare
   l_job number;
begin
   dbms_job.submit( l_job, 'p1;' );
   dbms_job.submit( l_job, 'p2;' );
   dbms_job.submit( l_job, 'p3;' );
   commit;
end;
/

Now, if you have job_queue_processes set to three or above, you have a chance
that all three will run concurrently, in the background.  Note that if the job
queues are already processing stuff -- your jobs will be in the queue and will
run as soon as a job queue process is free.

In 8i, you can have 36 job queue processes, in 9i, 1,000.
Review & Followup
Rating: 5
How could someone know so much about Oracle? November 14, 2001
Reviewer: Eric Worthy from Littleton, CO USA
Kinda like he isn't human.....

Rating: 5
A Java and OS approach November 14, 2001
Reviewer: Andre Whittick Nasser from Rio de Janeiro, Brazil
You can execute concurrently at OS level. Unfortunately this won't run on NT,
which I think is your case, since DOS won't take parallel execution as simple as
UNIX.

It is important to notice that it is a different alternative to Tom's
suggestion. His is simpler for what the reader wants.

But this code is more powerful in the sense you can run things in the OS
environment.

Use this code tip to publish the "shellcmd" PL/SQL function:
http://www.oracle.com/oramag/code/tips2001/index.html?021201.html
Or take a look at Oracle Magazine:
http://www.oracle.com/oramag/oracle/00-Mar/index.html?o20java.html
Then you have something like:

CREATE OR REPLACE PROCEDURE club_proc
IS
   r1 NUMBER;
   r2 NUMBER;
   r3 NUMBER;
BEGIN
   r1=shellcmd(<os_cmd1>);
   r2=shellcmd(<os_cmd2>);
   r3=shellcmd(<os_cmd3>);
END;

Where each <os_cmd> command will execute serially.

If you want to run OS commands "concurrently", use the UNIX ampersand (&)
suntax.

So, if you want to run your procedures, run SQLPLUS starting them, just like:

CREATE OR REPLACE PROCEDURE club_proc
IS
   r1 NUMBER;
   r2 NUMBER;
   r3 NUMBER;
BEGIN
   r1=shellcmd("$ORACLE_HOME/bin/sqlplus scott/tiger@host1 @p1.sql &");
   r2=shellcmd("$ORACLE_HOME/bin/sqlplus scott/tiger@host2 @p2.sql &");
   r3=shellcmd("$ORACLE_HOME/bin/sqlplus scott/tiger@host3 @p3.sql &");
END;

Notice you can run whichever OS command you like, including starting SQLPLUS
sessions in other machines, like in host1, host2 and host3.

Just take care:

- Beware of Java privileges in Oracle -- they changed from 8.1.5 to 8.1.6,
differently from the second article I mentioned (8.1.5), you should use "direct
Java privileges" (look it up).

- Your username and password would be visible, so you could use the wrap utility
to "hide" your code. Run:

    $ wrap iname=p1.sql

Even so, some of your data is visible in the wrapped code.

Hope this helps !

Rating: 5
November 15, 2001
Reviewer: Helena Markova from Bratislava, Slovakia

Rating: 3
Cuncurrent execution of procedures November 15, 2001
Reviewer: Michael from HK
Why there is alwayas a bug in
<<
exception
  when others then null;
end;
>>?

I can only see that it will miss some error or it is difficult to debug.

Followup:
That is EXACTLY why there is a bug here -- because

"it will miss some error"

that IS the bug.  I see it time and time again.  Someone says "well, I ran the
procedure and got "PLSQL procedure successfully completed" but it didn't work.
Whats wrong with Oracle?  why isn't it saving my data properly?  It only did
half the job!

When in fact, the programmer purposely caught and ignored the error -- causing
the database to preserve half of the work.

Consider the difference between these two blocks:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2      insert into t values ( 1 );
  3      RAISE program_error;
  4      insert into t values ( 1 );
  5  end;
  6  /
begin
*
ERROR at line 1:
ORA-06501: PL/SQL: program error
ORA-06512: at line 3

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;
no rows selected

ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2      insert into t values ( 1 );
  3      RAISE program_error;
  4      insert into t values ( 1 );
  5  exception
  6      when others then
  7          null;
  8          -- silently ignore the error
  9  end;
 10  /
PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

         X
----------
         1

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

One, the one that lets the error propagate, saves NO data -- the statement is
considered atomic, since it failed -- it failed entirely.  On the other hand,
the second block CAUGHT the error, ignored it and "succeeded".  therefore the
partial work it did was saved.

This is the way exceptions are explicitly designed to work.  In general, it
works very nicely (recovering from that DUP_VAL_ON_INDEX exception for example,
or the SELECT INTO that returns no_data_found) but when you catch just ANY error
and basically ignore it -- you have a BUG.  I can assure you of that, 100%.  A
tablespace fills up -- error -- partial work, no clue that it happened.  A
constraint that should not be violated gets violated -- error -- partial work,
no clue that it happened.  (you get the picture)

Rating: 5
The Man March 15, 2002
Reviewer: David from Nashville, TN United States of America
Tom,

Where have you been all of my [Oracle] life?

Thanks for your efforts.

Rating: 5
Eye opening April 17, 2002
Reviewer: Gabriel from Montreal

Rating: 4
Sort of on a related note... September 30, 2003
Reviewer: Mike from England
Hi,

I'm currently trying to move an application away from the coding standard of...

when others then
  call_logging_procedure;
  raise;
end;

to having nothing and allowing errors to propogate up and out of Oracle to the
caller, who can then log the full error stack including the line of text that
raised the exception...

One of the other things I'm trying to remove is a job scheduler written in PRO
C.  it pretty good, but does cause some problems, and provides little that
DBMS_JOB does not...

however one of the things it does provide is the ability to log error messages
for the PL/SQL it executes.

I've set up a simple DBMS_JOB that will fail due to a PK violation and it does
fail...

However DBMS_JOB will keep on trying it (fair enough) and I can't seem to find
any trace of the error anywhere...

Ideally I would want the job to fail, not to be retried and the error to go
somewhere.  If i attempt to do any of this in PL/SQL I must catch the excpetion,
which will cause me to lose the main thing I'm after.

Am I not looking in the right places or is DBMS_JOB not appropriate?

Thanks,

Mike.

Followup:
for the job -- instead of submitting:

dbms_job.submit( :job, 'proc;' );

you would submit:

dbms_job.submit( :job, 'begin proc; exception when others then LOGIT; COMMIT;
<optionally re-raise if that is appropriate>; end;' );

(looks familar!  its the when others then log, raise trick :)

your errors right now are in the alert log on the server.

Rating: 5
As usual... October 01, 2003
Reviewer: Franco from Denmark
... every your word is so useful... Thanks for this wonderful site, and your
books that i just received two minutes ago :-))

Rating: 4
OK then... October 01, 2003
Reviewer: Mike from England.
Hi Tom,

Thanks for that, but putting a when others then log is what I am trying to get
away from, as it means I'll lose the error stack... A NDF is really useful if
you also know the line number, but if it's one of 20,000 lines of code, it's a
lot less helpful...

I'll have to have a hard think about sticking with the pro c or not, but as much
for my own knowledge a couple of questions?

Is there anyway to get the exceptions raised by DBMS_JOB to go somewhere other
than the alert log? even if another file.

Is it possible to stop DBMS_JOB retrying failed jobs?

Thanks,

Mike.

Followup:

the errors go into the alert log.

you could use dbms_trace to great effect as well -- search this site for
dbms_trace to see what it can do for you.

the job would have to succeed in order for dbms_job to "stop"  -- so, you can
use dbms_trace to capture the details you want and the when others to make it so
it does'nt retry.

October 01, 2003 Tansel -- Thanks for the question regarding "The best insert performance...", version 8.1.5
You Asked
Hi Tom,

Thanks again for your valuable help to the Oracle developer
community:

We will develop an application where very large number of
inserts will take place. We will be inserting records into our
database as we read them from our data source. There will be
only one application inserting records. The table has only a few
columns. Our goal is to perform 15,000-20,000 inserts a second.

If we use PL/SQL bulk inserts we get the best performance but
this option require more coding. My question is:

How can we optimize the insert performance of our database for a
table as described above?

Thanks,

Tansel
and we said...
Use OCI or Pro*C.  Compiled C code will be faster then PL/SQL.

Array processing is the way to go.  The amount of code needed
for bulk binds versus non-bulk binds is very little (especially
when compared to the PERFORMANCE increase you get).

Instead of coding:

tkyte@TKYTE816> begin
  2      for j in 1 .. 5000 loop
  3          insert into insert_into_table values ( j );
  4      end loop;
  5      commit;
  6  end;
  7  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:03.74

you code:

tkyte@TKYTE816> declare
  2      type numTab is table of number(4) index by
binary_integer;
  3      data numTab;
  4      empty numTab;
  5  begin
  6      for j in 1 .. 5000 loop
  7          data(data.count+1) := j;
  8
  9          if ( mod(data.count,1000) = 0 )
 10          then
 11             forall i in 1 .. data.count
 12                insert into insert_into_table values
                         ( data(i) );
 13             data := empty;
 14          end if;
 15
 16      end loop;
 17      if ( data.count is not null )
 18      then
 19          forall i in 1 .. data.count
 20                insert into insert_into_table values
                          ( data(i) );
 21      end if;
 22      commit;
 23  end;
 24  /

PL/SQL procedure successfully completed.
Elapsed: 00:00:00.40

you replace the insert with:

  7          data(data.count+1) := j;

and add:

  9          if ( mod(data.count,1000) = 0 )
 10          then
 11             forall i in 1 .. data.count
 12                insert into insert_into_table values
                         ( data(i) );
 13             data := empty;
 14          end if;

after the assignments to insert every 1k rows for example.  It
does not add significantly to the code OR the logic at all
(doesn't affect the logic really -- unless you expect a high
number of REJECTS, then you need lots of error handling logic
but you would need that anyway....)

Given that it takes it from 3+ seconds to 0.40 seconds -- it is
well worth it.

OCI and Pro*C would be a little faster in my opinion in this
case.
Review & Followup
Rating: 5
Good to find a statistic June 26, 2001
Reviewer: Steve Pilgrim from UK
I am about to optimise an update and this is the sort of performance improvement
I would like.

I am using Pro*C with an update of 20 - 30 attributes per customer and the
forall looks the way to go

Rating: 3
how do we go about in case table is having around 20-25 fields April 11, 2002
Reviewer: Sushil Vyas from India
Hi Tom,

The example you have provided is good as usual.
I am in the process of writing a procedure which has to push around 0.1 million
records over the Lan using a DB link at an interval of 30mins from 20 diff
tables.
This is a sort of replication based on snapshot logs but due to security reasons
I have to push the data from the propagator DB instead of allowing receiver DB
to pull the data.

The no of fields in the tables are around 20 on an average.
As of now my flow is such
Open a cursor .Go into for loop.
Fetching fields value on the basis of rowid into variables.
Insert into into a table using DBlink.
Returning to Fetch.
Committing outside the for loop.

Time taken is 21 mins for 128000 records.

Can u help me in making it perform better. I tried to follow what u have shown
by creating PL/SQL types for each table but then that did not work with my
understanding.

Would be grateful if u provide a example.

Tks
Sushil

Followup:
use insert into select from

don't use a cursor for what can easily be done in a single statement.

Rating: 1
But i have to check for existence of records if any April 11, 2002
Reviewer: Sushil Vyas from India
Hi Tom,

Sorry for I did not communicated that there is also a possibility that the
record already exists in the DB.Therefore I have to use a cursor and in case the
record exists I got for updating that record.

Tks
Sushil.

Followup:
A snapshot style refresh is easiest done with two commands:

delete from remote_table where rowid in ( select rowid from snapshot_log where
dml_type in ( 'U', 'D' ) );
insert into remote_table
select * from local_table
where rowid_pointer_column in ( select rowid from snapshot_log where dml_type in
( 'U', 'I' ) );

Don't even try to update, just DELETE all modified/deleted records.  Insert all
new ones.

Rating: 3
Tuning insert into ... select * from ... April 11, 2002
Reviewer: Marc from Switzerland
Hi Tom

Your page is very very good. Thanks for your work. I have a problem in tuning
large inserts. I am preparing all my data within a global temporary table, and
put them into the big "live" table. The table is partitioned. For a view million
rows this tooks a while.

For better performace I have tried the append hint for the insert statement. But
I have get a dead lock error when running for parallel processes on the same
table.

How can I speed up a insert into ... select * from ... ? Thanks for your answer.

Followup:
the /*+ append */ hint doesn't make sense really for a global temporary table --
it already has most of those attributes.

A select into temp table will go as fast as

a) the query
b) your disks

use TKPROF to see where the bottleneck resides
http://asktom.oracle.com/~tkyte/tkprof.html

      Rating: 4
What about
      forms?  June 07, 2002
Reviewer:  Steve
      Kiteley  from Worcester, UK
What version of PL/SQL does this require. I tried to use the forall command from
within a forms trigger and it would not compile. I tried Forms 6i (PL/SQL v
8.0.6) and Forms 5 (PL/SQL v 2.3.4).

          Followup:

forall was added to the 8i release of the database -- you need the 8.1 version
of plsql or better.

      Rating: 3
Why do insert
      /*+ append */ lock the table ?  July 23, 2002

Reviewer:  A reader  from Switzerland

Hi Tom

If I do an insert /*+ append */ into a table and wait with the commit, and start
a second session doing the same insert a lock is occured. See my sample:

create table test as select * from dual;
insert /*+ append */ into test select * from test;

now start the insert a second time in a separate session:
insert /*+ append */ into test select * from test;

and you have to wait for commit.

If run the same insert without the append hint, no lock will happen. Is this a
feature or a bug ? Thanks for your answer.

          Followup:

designed behavior.  To do a parallel direct path load using SQL -- you use
parallel query in the original insert.

Just like doing a parallel direct path load.

      Rating: 4
This can work
      for update too ?  July 24, 2002
Reviewer:
      Ruben  from Mexico
Tom , we want to delete a thousands of records from
different tables, but it takes 1 hour 20 mins. to delete
850,000 records, can Pro*c, OCI to help us to minimize
the time for this purpose

          Followup:

I don't know.  Depends on the logic used to delete, the indexes in place and
many other considerations.

      Rating:
      5
  March 24, 2003
Reviewer:  Shahrouz
      DARVISHZADEH  from Austria
Tom, in case of bulk insert, is there a difference if I use NOLOGGING option? is
there a difference if I use temporary tables? concrete case:
-insert without BULK INSERT:

Elapsed: 00:00:10.05
-Insert with BULK INSERT:

Elapsed: 00:00:03.02
- BULK INSERT with table in NOLOGGING mode:

Elapsed: 00:00:02.05
- BULK INSERT in a GLOBAL TEMPORARY TABLE:

Elapsed: 00:00:06.02

Why is the insert in temporary table so slowly. It has to be faster, hasn't it?

Shahrouz

          Followup:

that is as concrete as this:

1
23432
232
2235432532

I have 4 numbers without an example....

NOLOGGING affects only one type of bulk modification -- direct path inserts such
as those achieved with the direct path loader, CTAS, insert /*+ append */, or
the direct path API in OCI.

It does NOT affect conventional path inserts at all.

A global temporary table can avoid some amount of REDO generation.  It does
however generate UNDO (has to) and that UNDO does generate REDO.  Further, a GTT
is just a table that truncates itself post commit or session - otherwise it is a
table -- indexes must be maintained, blocks have to be formatted.

No reproducible example, no explanation from me.

In this case -- I would say you are probably testing on a system with other
users and the flucuations in response time are due to them using the system at
the same time you are -- or your redo logs are tiny and you are waiting for
checkpoints and if you kept running the examples - you would get very spikey
response times, sometimes things would take a second, sometimes ten -- because
you are waiting on IO.

      Rating: 5
Good info!
      ......Snapshot type refresh method.  March 24, 2003

Reviewer:  Robert  from Memphis, USA
Good information on snapshot style refresh in 2 steps...(delete, insert), vs. 1
step (update).

      Rating:
      5
  March 25, 2003
Reviewer:  Shahrouz
      DARVISHZADEH  from Austria
I didn't want to bother you with my insert procedure which is about 1400 rows,
therefore I wrote only the times statistics. Hier only the insert part:
loop
 fetch  x_cursor_var bulk collect into  l_record.r_STRUCTUREPRIV_ID
 LIMIT l_array_size;
 l_done:=x_cursor_var%NOTFOUND;
          forall i in 1..l_record.r_STRUCTUREPRIV_ID.count
           insert into sis_structurepriv_user(id, structure_id, user_id,
structurepriv_id, admin_option, cuserid, cdate, version)
             values(seq_sis_structurepriv_user.nextval,
l_record.r_STRUCTUREPRIV_ID(i), R.user_id, x_structpriv_select, 'NO', 3,
sysdate, 1);
.....
 exit when (l_done);
 l_cnt := l_cnt + l_array_size;
 end loop;
 close x_cursor_var;

If I understand you properly there is no different if I use NOLOGGING or not in
this case (but I swear IT IS faster with NOLOGGING). Elapsed time statistics
which I sent you are reproducable everytime. I am the only user on a local
machine.
Temporary tables are exactly the thing which we need in this case. We delete
everything in this table and insert the values everytime new. The content of the
table is not usable for other users. But GTTs are the slowest thing in my
inserts as you can see. I read the capital in your book about GTTs and didn't
find much about how I can tune these sort of tables. Thus my question: am I
doing something wrong?

Thank you in advance
Shahrouz DARVISHZADEH

      Rating: 4
Logging in bulk
      operations  October 01, 2003
Reviewer:
      Sagar  from India
Hi Tom,
     If I am doing any bulk insert/update/delete, will there be logging? If yes,
can I change the behaviour to no-logging at the statement level itself? (for eg.
in FOR ALL INSERT)

Regards,
Sagar

          Followup:

update and delete = ALWAYS logging (and you are mistaken if you think you don't
want it)

INSERT /*+ APPEND */ as select can skip logging.

INSERT with a VALUES clause = ALWAYS logging -- even with "forall", it is a
single row insert and those are not done in "direct" mode.

October 01, 2003 Sridevi -- Thanks for the question regarding
      "Upgrade Documentation from 7.x to 9i", version 9i

      You Asked
Hello Tom,
Where would I be able to find a document that would help me upgrade from Oracle
7.x to Oracle 9iDB.(Covering features of 8.x and 9i)

And also from Developer 2000 to developer 9i suite.

Thanks in Advance.

      and we said...
Surprisingly, with every release of the DB we publish a migration guide:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96530/toc.htm
that covers just such issues!!  For new features, we have a new features guide:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-NEW

Same with developer:

http://technet.oracle.com/docs/products/forms/content.html
All of these were found by simply going to otn.oracle.com and clicking on
"documentation".  Its all there!
Review &
      Followup

      Rating: 3
9i
      Documentation  October 01, 2003
Reviewer:
      Sandeep  from India
tom i want the link of main index of oracle9i documentaion
so that i can read the documntation online
as i searched it but i m unable to find that link
i know u can help me
thanx in advance

          Followup:

otn.oracle.com

big "documentation" link on that page. (button, right up on top)

October 01, 2003 Winston -- Thanks for the question regarding
      "Introduction to Shared Server Performance", version 9iR2

      You Asked
Hello Tom,

Here is the quote from

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/features.htm#22618
<quote>
Performance of certain database features can improve when a shared server
architecture is used, and performance of certain database features can degrade
slightly when a shared server architecture is used. For example, a session can
be prevented from migrating to another shared server while parallel execution is
active.

A session can remain nonmigratable even after a request from the client has been
processed, because not all the user information has been stored in the UGA. If a
server were to process the request from the client, then the part of the user
state that was not stored in the UGA would be inaccessible. To avoid this,
individual shared servers often need to remain bound to a user session.
</quote>

What's the quickest way to experiment session migration between shared servers?
and could you please explain why 'A session can remain nonmigratable even after
a request from the client has been processed, because not all the user
information has been stored in the UGA.'?

Many thanks in advance,
Winston

      and we said...
I guess the quickest way to see a migration would be to open two shared server
sessions -- one each in a window.

Making sure you have max shared servers greater then one, run this query in
session 1:

  1  select server, paddr,
  2         (select name from v$shared_server c where c.paddr = b.paddr ),
  3         (select 'Me' from v$mystat where sid = b.sid and rownum=1)
  4  from v$session b
  5* where b.username = user
scott@ORA920> /

SERVER    PADDR    (SEL (S
--------- -------- ---- --
SHARED    57124F48 S000 Me
NONE      571252F8

That shows you have two sessions -- one is active and flagged as "me".  It is
using shared server S000 right now.  Now, in the second session -- leaving this
one idle -- execute:

scott@ORA920> exec dbms_lock.sleep(30)

That'll grab a shared server and it'll tend to grab S000.  It'll hold it for
30 seconds.  go back to session 1 and hit slash and you might see

scott@ORA920> /

SERVER    PADDR    (SEL (S
--------- -------- ---- --
SHARED    57125E08 S001 Me
SHARED    57124F48 S000

there, you have a "migrated session"

Now, there are some operations -- like a hash join for example -- that prevent
migration.  It used to be that these operations were not permitted via shared
server -- but now they are.

If anything remains in the PGA after a call -- that session will be
non-migratable, we need THAT specific shared server.  If the remaining bits of
data are safely in the UGA, that'll be in the SGA and we can migrate.

It is for that reason for example that PGA_AGGREGATE_TARGET is *not* used with
shared server in 9i with shared server, sort_area_size/hash_area_size are,
regardless of the workarea size policy setting.
Review &
      Followup

      Rating:
      5
Excellent...  July 06, 2003
Reviewer:
      Kamal Kishore  from New Jersey, USA
Hi Tom,
In your reply above you said:
<quote>
It used to be that these operations were not permitted via shared
server -- but now they are.
</quote>

Which version(s) of Oracle dd not permit this kind of operation(s)?

Was this restriction removed in Oracle9i? (meaning that up untill Oracle8i this
was an issue with shared server config).

What used to happen when these operations were not permitted? Was it generating
runtime error or it was silently ignored and query proceeded anyway.

Are there any other issuses like this that we may still have to lookout for even
with Oracle9i?
Thanks,

          Followup:

bits and pieces, here and there, over time.   The optimizer would not use that
technique if the client was a shared server (eg: you can and still will get
different plans for the same exact query based on part on the fact you have a
shared server connect versus a dedicated server)

      Rating:
      5
Strategy?  July 06, 2003
Reviewer:
      Kamal Kishore  from New Jersey, USA
Hi Tom,
At our client site we are currently running databases in DEDICATED server mode.
The client site is planning to switch over to SHARED server mode.

Based upon your response above, it looks like that this seemingly simple switch
might cause our query plans to suddenly change. What should be our strategy
after the switch to the SHARED server mode? How should we make sure that our
query performance is similar or better compared to before?

Would you equate this change to the database version upgrade? Would you apply
the same rules as applied when a database version upgrade is carried out?
Or, there are alternate strategies to consider?
Thanks,

          Followup:

It'll mostly only affect large DW type queries.

I would equate this with a db upgrade, a patch, a tweak, a change.  You should
test.  Do you have adequate shared servers configured?  Do you need shared
server?  those would be more relevant probably then the query plan changes.

      Rating: 5
simply
      fabulous  July 07, 2003
Reviewer:  Vijay'S
       from India
Hey Tom,
       Good day, once again simple and clear explanation.

Best Regards,
Vijay.

      Rating: 5
dedicated vs
      shared server  September 30, 2003
Reviewer:
      A reader
Tom,
In chap. 2 of you book 'Effective Oracle by Design', the section 'How Do
Dedicated Server Connections Work'
<quote>..Every dedicated server connection has a dedicated server allocated
soley to it.  This does not mean every single session has a dedicated
server....it is possible for a single connection(dedicated server) to have many
sessions concurrently active....In general....there is a one-to-one relationship
between a session and a dedicated server.</quote>
q1)In a dedicated server connection, why dosen't it mean that every single
connection has a dedicated server?
q2) You mention both the following
<quote>
it is possible for a single connection(dedicated server) to have many sessions
concurrently active.....and also ..there is a one-to-one relationship between a
session and a dedicated server</quote>
How can both of them be possible, can you please explain?

In the section 'Understanding Shared Server vs Dedicated Server Connections'
<quote>...that is time to consider switching over to shared servers...clients
don't have control over this....</quote>
1) Can't the client change from a shared server connection to a dedicated server
connection by modifying the TNSNames.ora file?
2) If the client dosen't have a TNSNames.ora, what parameter or entry decides
shared server or dedicated server connection?
3)If an entry is made for the database in the DNS server.  What configuration
parameters tells yoy that the connection is configured either for a dedicated
server or shared server
4) Is there a default mode(dedicated vs shared server) of
connection/configuration, if nothing is specified?

          Followup:

I think you meant chapter 3.

a1) because a single client process may open more then one session at a time.
There is a CONNECTION, then within connections you create sessions -- as many as
you need.  typically -- this is "one session"

but you changed my words when you say:

<quote>
In a dedicated server connection, why dosen't it mean that every single
connection has a dedicated server?
</quote>

in a dedicated server connect, every CONNECTION does have a dedicated server,
however, each SESSION need not have one.

a2) they are not possible simultaneously.  you've taken some things out of
context.  Allow me to reproduce the text in question:

<quote>
So, in dedicated server mode, there is a one-to-one mapping between processes in
the database and a client process. Every dedicated server connection has a
dedicated server allocated solely to it.  That does not mean every single
session has a dedicated server, for it is possible for a single application,
using a single physical connection (dedicated server) to have many sessions
concurrently active.  In general however, when in dedicated server mode, there
is a one to one relationship between a session and a dedicated server.
</quote>

you left out the "IN GENERAL HOWEVER" that time, a key modifier -- in general,
there is a one to one relationship.  What MOST people will observe in practice
is a one to one relationship.  However, there need not be a one to one
relationship.

1) that is not a client decision -- the database must be setup for it as well.
I do not consider rewriting configuration files to be a "client decision".  The
database must be setup for shared server

2) the database, when it registers itself with the listener, decides how the
service will work.

3) the listener and database deal with that.

4) dedicated is default out of the box (always there)

      Rating: 5
dedicated vs
      shared server   October 01, 2003
Reviewer:
      A reader
Tom,
How are connections and sessions different?
If I need 2 SQLPlus sessions, I will need 2 connections.  Can you give an
example where for a single application, using a single physical connection
(dedicated server) to have many sessions concurrently active.

Thank you

          Followup:

sqlplus is an example.

when you set autotrace on with statistics -- sqlplus will actually use TWO
sessions -- one you interact with and run the query in and another that watches
your statistics (so as to not have the watching of the statistics INFLUENCE the
statistics themselves!)

so, there you have a case where by you have one connection (physical circut from
client to server) but more then one session.

forms does it -- for debugging.

app servers do it, for connection pooling.

      Rating: 5
dedicated vs
      shared server   October 01, 2003
Reviewer:
      A reader
Thank you.

October 01, 2003 Brian -- Thanks for the question regarding
      "Constraints", version 8.16.

      You Asked
Tom,

Are the following a full list of all possible user constraint_types

P - primary key?
C - check?
R - foreign key?
U - unique ?

And what are their full meanings?
Are the dab constraint_types the same?

Thanks

Brian

      and we said...
there is:

c - check
p - primary key
u - unique
r - references (fkey)
v - view with check option
o - read only on a view

this is documented in the Oracle8i Reference

The DBA_CONSTRAINTS view is the same as ALL_CONSTRAINTS/USER_CONSTRAINTS with
the exception that it shows ALL constraints in the database regardless of
whether you have the privilege to "see" them or not.

Review
      & Followup

      Rating: 5
What about not
      null  September 25, 2001
Reviewer:
      A reader
Tom

I guess you forgot to mention 'NOT NULL' constraint.

can you throw some more light on

v - view with check option
o - read only on a view

          Followup:

err -- no, i didn't mention "not null" for the same reason I didn't mention
"check (x > 1)" -- its just a specific instance of a check constraint:

ops$tkyte@ORA8I.WORLD> create table t ( x int not null );
Table created.

ops$tkyte@ORA8I.WORLD> select constraint_name, constraint_type from
user_constraints;

CONSTRAINT_NAME                C
------------------------------ -
SYS_C0044420                   C

it is of type C.

This is the check constraint on a view:

ops$tkyte@ORA8I.WORLD> create view v as select * from t where x > 0 WITH CHECK
OPTION;

View created.

ops$tkyte@ORA8I.WORLD> insert into v values ( 1 );

1 row created.

ops$tkyte@ORA8I.WORLD> insert into v values (-1);
insert into v values (-1)
            *
ERROR at line 1:
ORA-01402: view WITH CHECK OPTION where-clause violation

If the row you insert/update would not be selected by the view -- it violates
the check option on the view and it rejected..

As for read only -- thats just a read only view:

ops$tkyte@ORA8I.WORLD> create view v2 as select * from t with read only;

View created.

ops$tkyte@ORA8I.WORLD> select constraint_name, constraint_type from
user_constraints;

CONSTRAINT_NAME                C
------------------------------ -
SYS_C0044420                   C
SYS_C0044421                   V
SYS_C0044422                   O

It reads a view that cannot be updated.

      Rating: 3
Check
      constraint VS not null - any difference in
      performance?  September 25, 2001
Reviewer:
      Andrew  from CA, USA
SQL> create table T (col1 number not null, col2 number, col3 number);

Table created.

SQL> alter table T modify (col2 constraint t_col2_nn NoT NuLl);

Table altered.

SQL> alter table T add constraint t_col3_nn check (Col3 is Not Null /*my custom
criteria*/);

Table altered.

SQL> column search_condition format a40
SQL> select constraint_type, constraint_name, search_condition
  2  from user_constraints
  3  where table_name = 'T';

C CONSTRAINT_NAME                SEARCH_CONDITION
- ------------------------------ ----------------------------------------
C SYS_C00112155                  COL1 IS NOT NULL
C T_COL2_NN                      COL2 IS NOT NULL
C T_COL3_NN                      Col3 is Not Null /*my custom criteria*/

      Rating:
      5
  February 19, 2003
Reviewer:
      Harold  from Managua, Nicaragua
If a use this kind of constraints:

SYS_C00887                     P ENABLED

on a table with 800,000 rows it will be any performance problems??? or it will
be better to create a PK on that table??

Thanks

          Followup:

that is a primary key.

If that is coming from user_constraints and the P is the constraint type -- that
IS a primary key.

      Rating:
      5
  February 19, 2003
Reviewer:
      Harold  from Managua, Nicaragua
i know, but if i do this:

SYS_C00887                     P ENABLED

alter table INISER.ACREENCIA drop primary key;/*this one is SYS_C00887*/

and create a new one with this:

create unique index INISER.ACREENCIA_I_NUMACRE on INISER.ACREENCIA (NUMACRE)
tablespace INISER
storage (initial 11800 K next 2960 K );

Will i have a better performance??

          Followup:

maybe if you used 11801 on the initial (that, is a joke).

No, you use a primary key for primary key.

You use a unique constraint for unique columns.

You lose valuable metadata if you do not.

Under the covers, you will find an index in support of that primary key already.

      Rating: 5
primary key and
      not null  March 06, 2003
Reviewer:
      A reader
Hi tom,

 pleaes help me here..

/*****************************************/

12:45:34 SQL> create table t (id number not null primary key )
12:45:54   2  ;

Table created.

12:45:56 SQL>  SELECT
CONSTRAINT_NAME,CONSTRAINT_TYPE,TABLE_NAME,SEARCH_CONDITION
12:46:00   2   from USER_CONSTRAINTS WHERE TABLE_NAME = 'T';

CONSTRAINT_NAME                C TABLE_NAME                     SEARCH_CONDITION
------------------------------ - ------------------------------
--------------------------------
SYS_C0025737                   C T                              "ID" IS NOT NULL
SYS_C0025738                   P T

Elapsed: 00:00:00.00
12:46:01 SQL> desc t
 Name                                                              Null?    Type
 ----------------------------------------------------------------- --------
--------------------
 ID                                                                NOT NULL
NUMBER

12:46:07 SQL> alter table t drop CONSTRAINT SYS_C0025737;

Table altered.

Elapsed: 00:00:00.00
12:46:25 SQL> desc t
 Name                                                              Null?    Type
 ----------------------------------------------------------------- --------
--------------------
 ID                                                                NOT NULL
NUMBER

12:46:28 SQL> drop table t;

Table dropped.

Elapsed: 00:00:00.00
12:46:37 SQL> create table t (id number primary key);

Table created.

Elapsed: 00:00:00.00
12:46:51 SQL> desc t
 Name                                                              Null?    Type
 ----------------------------------------------------------------- --------
--------------------
 ID                                                                NOT NULL
NUMBER

12:46:53 SQL>  SELECT
CONSTRAINT_NAME,CONSTRAINT_TYPE,TABLE_NAME,SEARCH_CONDITION
12:46:57   2   from USER_CONSTRAINTS WHERE TABLE_NAME = 'T';

CONSTRAINT_NAME                C TABLE_NAME                     SEARCH_CONDITION
------------------------------ - ------------------------------
--------------------------------
SYS_C0025739                   P T

then,,

Q: what is the use of non null constraint on the column
   which paritcipates in the primary key ???

Thanks

          Followup:

all columns in a primary key are by definition "not nullable" -- it is the very
definition of primary keyness.

      Rating: 5
so here is it
      overhead ?  March 06, 2003
Reviewer:
      A reader

so in the 1st case I have put both the constraints on the
column  where as only primary key is needed..

 so now is it an over head to check for not null even if it primary key or
oracle is intelligent to know that and check it only for once ?

          Followup:

it'll do one or the either, it would not be good practice to do both by you --
just use primary key.  that is after all all that is needed.

      Rating: 5
Make View Read
      Only   April 30, 2003
Reviewer:  George
      Fernandes  from Portugal
Tom,

    I've two tables say T1 and T2. Both of them have views V1 and V2 on them.
The view V1 is created with 'WITH READ ONLY' (hence a record exist in
DBA_CONSTRAINTS with CONSTRAINT_TYPE='O') where are the view V2 has the same
defination as V1 but is without 'WITH READ ONLY'. How can I alter view V2
without dropping so that it is similar to V1('WITH READ ONLY') and thereby a
record exist in DBA_CONSTRAINTS for V2 ?

Please Help.

          Followup:

CREATE OR REPLACE view v2.....

      Rating: 5
New column,
      what should be the constraint  October 01, 2003

Reviewer:  A reader
Tom, We need to add a new column to a table, the column is like a flag, the
possible values are 'Y' or 'N'. The table is loans , the new column will be to
lock a loan once it has been approved.

Should the column be declared as not null or null( we have existing data in this
column, and on update database trigger as well).

          Followup:

tell me, is NULL a valid value for this column?

if not, it should be NOT NULL
if so, it should be NULL

October 01, 2003 Nagaraju -- Thanks for the question regarding
      "adding a millisecond to a date", version 8.1.6

      You Asked
how do i add a millisecond to a date.
i only have calculate(24/(60 * 60 *1000) and add) or is there any other way?

thanks
nagaraju

      and we said...
you cannot.  dates only have seconds.  adding a millisecond will not affect what
is stored.  It would be like adding .001 to an integer - -nothing.
Review &
      Followup

      Rating: 5
How do I get
      the ONLY ' the Millisec ' from MY_TIMESTAMP  February 20,
      2002
Reviewer:  Pascal  from HH,Germany
Hi Tom,
Thanks for  My_Timestamp  JSP.

I have tried to Exract only the Millisec part , but you can  see  from the Query
below  that there's some LAG ...now , how do i Extract only the Millisec without
this LAG.

column my_timestamp format  a30
column  millisec format  a30
select  substr(my_timestamp,instr(my_timestamp,'.')+1 ) millisec , my_timestamp
from dual;

MILLISEC                       MY_TIMESTAMP
------------------------------ -----------------------------
594                            2002-02-20 19:34:26.596

Thanks in advance

Best Regards

Pascal

          Followup:

ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE or replace JAVA SOURCE
  2  NAMED "MyTimestamp"
  3  AS
  4  import java.lang.String;
  5  import java.sql.Timestamp;
  6
  7  public class MyTimestamp
  8  {
  9      public static String getTimestamp()
 10      {
 11        return (new
 12           Timestamp(System.currentTimeMillis())).toString();
 13      }
 14  };
 15  /

Java created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function my_timestamp
return varchar2
  2  AS LANGUAGE JAVA
  3  NAME 'MyTimestamp.getTimestamp() return java.lang.String';
  4  /

Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> column my_timestamp format  a30
ops$tkyte@ORA817DEV.US.ORACLE.COM> column       millisec format  a30
ops$tkyte@ORA817DEV.US.ORACLE.COM> column       ts format  a30

ops$tkyte@ORA817DEV.US.ORACLE.COM> select
substr(my_timestamp,instr(my_timestamp,'.')+1 ) millisec , my_timestamp
  2  from dual;

MILLISEC                       MY_TIMESTAMP
------------------------------ ------------------------------
148                            2002-02-22 10:10:42.152

ops$tkyte@ORA817DEV.US.ORACLE.COM> select substr(ts,instr(ts,'.')+1 ) millisec ,
ts
  2  from (select my_timestamp ts, rownum r from dual);

MILLISEC                       TS
------------------------------ ------------------------------
166                            2002-02-22 10:10:42.166

using rownum in the inline view is a "trick", we evaluate the inline view once
and then apply the outer select to it -- hence my_timestamp is called only once
and its results are used twice.

      Rating:
      5
  August 30, 2002
Reviewer:
      A reader
Hi Tom,

 In 9i R2 oracle has new datatype Timestamp.
 Can it store millisecond?

 Can you explain with example?

 Thanks,

          Followup:

It is 9i Release 1 that added this feature.

ops$tkyte@ORA920.US.ORACLE.COM> create table t
  2  ( x timestamp,
  3    y timestamp with time zone );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> insert into t values ( systimestamp,
systimestamp );
1 row created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select * from t;

X
---------------------------------------------------------------------------
Y
---------------------------------------------------------------------------
30-AUG-02 08.15.53.195414 PM
30-AUG-02 08.15.53.195414 PM -04:00

supports not only fractional seconds but timezones as well if you like.

      Rating:
      2
limited  September 30, 2003
Reviewer:
      Christo Kutrovsky  from Ottawa, ON Canada
Hi Tom,

I was trying to use timestamp, to gradually replace some of my DATE types, due
to more precision and easily used with the interval datatype (timetsamp -
timestamp = interval).

However, I find that it is pretty limited, in terms of provided built-in
functions.

trunc(timestamp) To get the day only does not work (for group by for example)

round(timestamp) also does not work.

The same applies to the interval datatype. I intended to replace my NUMBER
fields which holded a number of "seconds" with the new INTERVAL DAY TO SECOND
datatype, but then I loose the ability to round to a minute for example. (which
is as easy as round(seconds/60))

Also converting from INTERVAL DAY TO SECOND to get for example "55 hours , 33
minutes and 13 seconds" instead of "2 days, 7 hours, 33 minutes and 13 seconds"
requires lot's of code.

Are any of those issues addressed in the upcoming Oracle 10G ?

          Followup:

ops$tkyte@ORA920> l
  1*  select localtimestamp, trunc(cast( localtimestamp as date)) from dual
ops$tkyte@ORA920> /

LOCALTIMESTAMP               TRUNC(CAST(LOCALTIME
---------------------------- --------------------
01-OCT-03 07.41.03.387167 AM 01-oct-2003 00:00:00

  1* select localtimestamp, round(cast( localtimestamp as date)) from dual
ops$tkyte@ORA920> /

LOCALTIMESTAMP               ROUND(CAST(LOCALTIME
---------------------------- --------------------
01-OCT-03 07.41.20.393160 AM 01-oct-2003 00:00:00

as for the interval type -- how it works, behaves, is dictated by ANSI -- we
just implemented the "standard"

      Rating:
      4
Intersting.  October 01, 2003
Reviewer:
      Christo Kutrovsky  from Ottawa, ON Canada
Interesting.

Does the cast involve any perfomance issues ?

It will be so more convenient if trunc/round supported (and transperently)
converted timestamp.

Are there plans to extend the interval "ansi" datatype?

          Followup:

well, without cast, it doesn't work :)

so performance aside -- we cannot really compare cast vs no cast

i'm not aware of any changes -- but there is alot of stuff to look at still in
10

October 01, 2003 Meyer -- Thanks for the question regarding "Users
      and Roles", version 8.16 9ias

      You Asked
Tom, in your apps

1)Do you normally have an Oracle user for each application user...or a table you
create of the users for your app. I was creating real Oracle users w/forms and
now use my own tables of users for web enabled apps...but I noticed that Oracle
supports DB validation for web apps.

2)Do you normally code your application permissions to use Oracle roles, then
have the GUI query the roles tables or some "table of permissions" to see what
options to give the user...I was using actual Roles granted to the users
w/forms, but now create my own tables of permissions. (and grant everything to
the DAD).

3)Does a DB w/a huge amount of roles/grants/users execute sql noticeably slower
then a DB with a few or normal amount. Do large tables of permisions/users slow
down Oracle's permision verification phase when executing SQL? If yes, at what
ball park range is this noticeable.

Thanks,
Meyer

      and we said...
1) I create real, honest, auditable -- grantable -- users in the database.

I've never created a table of users and did it myself.  I lose too much builtin
functionality that way.

2) yup.  I use roles and query that stuff -- that is my preferred method.  there
is a school of thought that runs counter to that.  My opinion is "hey, they
already got all of these tables, I don't need my own AND if someday I want to
have something else access the data -- I don't need to teach that something else
how to use MY security -- they are forced to use Oracles security and cannot get
around it"

3) no, no it doesn't.  we have databases with over 100k users.  my own has 40k+
and grows.
Review & Followup

      Rating: 1
Not good for
      N-Tier Architecture  October 18, 2002
Reviewer:
      Gowrishankar Holalkere  from Boston, MA
Tom
I agree with your suggestion if
1)You have handfull of users to manage or
2)You really dont care about the number of concurent connections and resource
reusability and connection effieciency.

If you have a 3/N-tier architecture, using single oracle account and managing
access/roles in application layer is more effeicient and scalable. Also most of
the comercial class applications use LDAP kind of service for autentication and
user account management.
So the decision would be based on the requirement. Long term, Short Term,
Scalable or Quick and Dirty and ...

Here are my questions
1)Please do let me know if you you how your 1 account per user would work in a N
tier env, other then making a dedicated connection for each user.
2)Is there a way to make the db connection independent of the user
autentication. Something where the application layer can attach/pass the user
credentials with every call it makes to the database thr a common connection??
Am I making any sense??

Thanks

          Followup:

Well, I run a system with 40k users.

It is n-tier.

I *do care* somewhat about those things (trust me, i do).

I argue it is NEITHER more efficient NOR more scalable.

I argue that what it does it lock the data into your application, limiting its
reused.  N-tier is just "todays" architecture.  Just like host based was last
centuries and client server was after that.  There will be some other
architecture.

By doing these crucial things in the APPLICATION ITSELF, you are locking
yourself into an architecture for ever (many of the client server and host based
systems are out there not cause people want them -- but because they cannot move
OFF of them -- the logic is all wrapped around itself in the application -- you
cannot access the data safely/securely WITHOUT the app).

See

http://otn.oracle.com/docs/products/ias/doc_library/90200doc_otn/core.902/a90146/fundamen.htm#1006392
for a description of proxy authentication.

It lets you setup a PHYSICAL connection to the database and then "set user"
within that connection.  The physical connection -- slow.  The "set user" is
just setting up a session state (something YOU MUST do anyway with a connection
pool -- you cannot let bits of a previous state hang out across connections)

#2 not only makes sense -- we do it.

      Rating: 5
Additional
      Questions based on your Answer  October 19, 2002

Reviewer:  Meyer Tollen  from Baltimore, MD

Tom you mentioned

"1) I create real, honest, auditable -- grantable -- users in the database."

Assuming a mod/PLSQL application... say an online service application where you
want as many members of the "public" to join/pay as posible...would you have
code the creates the Oracle user, that gets executed after they fill out the
sign up form, maybe via a package?

When users logon to your service (web site) and you authenticate their password
and your app writes a "logged on" cookie (since I am thinking you would have
them connect via the DAD)...how do you validate that they entered a correct
ORACLE username/password combination....w/out a connection attempt from their
account (if you are doing this)?

I realize this is making assumtions based on your answer...let me know if this
type of application is not what you had in mind....Regardless, I am still
curious about the specific way to do oracle validation via the DAD.

Thanks,
Meyer

          Followup:

I would have a public dad which everyone can use and when you login you get
switched to a dad where the username password is not stored with the dad so you
are asked for your username/password and mod_plsql uses that to authenticate
you.

      Rating: 5
Your
      followup.....?'s  October 28, 2002
Reviewer:
      Meyer  from Baltimore, MD
Tom you said this at the bottom:

<QUOTE>
I would have a public dad which everyone can use and when you login you get
switched to a dad where the username password is not stored with the dad so you
are asked for your username/password and mod_plsql uses that to authenticate
you.
</QUOTE>

I tried it and it works well. I have some followup questions since this is WAY
different that I have done in the past. But am going to implement this way. 1
and 2 are most important. 3-4 are because I am curious if you can explain.

1)When an employee leaves, but you still need to access their related data (and
be able to reference who they were). Do you drop the user?? Change their
password and keep old emps as users...or what.

2)If I want to reference the current user inside my code written in plsq
packages should I use this w/each call:
sys_context( 'userenv', 'current_user' );

3)Is it possible to create a custom HTML logon page (other then the
IE-does-it-w-a-POPUP logon screen) with this no-password DAD model? If yes, How
would your custom logon form authenticate/logon the blank DAD username/pword
(psydo-sql if posible)?

4)When the DAD is logged on, I do not see a cookie recorded in my browser, but
it behaves that way? How does the DAD/browser know whos is logged on? I have
managed this in the past w/my own "you-are-logged-on-cookies".

Thanks,
Meyer

          Followup:

1) drop the user -- your "data" won't go away.  the records you have about them
are safely tucked in your table.  There is no correlation between their database
account and "their records"

2) or just

   x := USER;

3) nope.  basic authentication is handled by the browser for us.  it is totally
browser driven

4) the browser does the work -- we need no cookies, it just happens.  See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:822026909613
for how this protocol works under the covers.

      Rating: 5
Great, just
      clarify one point  October 28, 2002
Reviewer:
      Meyer  from Baltimore, MD
<quote>
1) drop the user -- your "data" won't go away.  the records you have about them
are safely tucked in your table.  There is no correlation between their database

account and "their records"
</quote>

I understand that...but not sure how you are doing this:

Say you create an application that only has 1 table of transactions for users
(say hours they worked). And you were going to create reports that show
transactions by user.

1)Based on your logic, would your applications transaction table have a column
similar to "username" or "user_id" ?(or both), where does this column get its
value and where does it join to? (maybe USERNAME or USER_ID from DBA_USERS?)

I am trying to figure out how you handle droping a user who quits and not
creating another w/the same account later or making it so it does not matter
(Jen Smith, John Smith)= jsmith.
Or
if you use USER_ID (from DBA_USERS)...and if so do you keep their "name" w/the
data OR in another table your application manages outside of DBA_USERS?

As always, THANKS!

Meyer

          Followup:

I learned the hard way many years ago -- DO NOT USE THE "USERID" as a key to
anything.  Why?

o people get married (go figure), they change their names and get quite upset
when their user name is their maiden name

o people get divorced (see #1 above)

o people from foreign countries "americanize" their names sometimes (happens
more often then I can count now)

usernames are not imutable.  You can record their username but you need a "real
key" for them -- employee id, system generated number, something.

When Oracle flattened their domain (eg: my email WAS tkyte@us.oracle.com, now it
is just tkyte@oracle.com), we had this issue in spades.  do not use a username
as a key -- it is functionally dependent data and nothing more or less.

      Rating: 5
Found the way
      (Expert One on One)  November 18, 2002
Reviewer:
      Meyer  from Baltimore, MD
Tom,

I found an implementation to bridge the oracle-DB accounts with the
my-own-app-user-info-table, and keep them in sync programmatically.

Example in your book
chapter 15: Autonomous Transactions
Page: 666, Performing DDL in Trigger

I have learned so much from your book, THANKS.

Meyer

      Rating: 3
what for all
      these users?  March 13, 2003
Reviewer:
      reader  from india
Tom,
after the installation of my database using Database configuration assistant, i
logged in to see many users...version 9.2.0.1.0. as follows...
ge$kumar@devadw.world>select username from all_users ;
 USERNAME
==============================
SYS
SYSTEM
DBSNMP
OUTLN
WMSYS
WKSYS
ORDSYS
ORDPLUGINS
MDSYS
CTXSYS
XDB
ANONYMOUS
ODM
ODM_MTR
WKPROXY
QS_ADM
HR
OE
PM
SH
RMAN
QS
QS_WS
QS_ES
QS_OS
QS_CBADM
QS_CB
QS_CS
SCOTT
MATRIX
WASCNFG
EJSADMIN

1)Could you please tell me what for all these users and when these users are
used?. matrix,wascnfg,ejsadmin were created by me. i do know few users from this
but majority of these users are new to me.

2)if i dont want any of these users, how to avoid creating that? say i doesnt
want rman to be created, how to stop that at the time of creation.

3)I see tablespaces like XDB, ODM, DRSYS what are these used for and again if i
dont want to create those tablespaces, how should i tell database configuration
assistant to, NOT to create those tablespace. is it possible?

ge$kumar@devadw.world>select tablespace_name from dba_tablespaces;
 TABLESPACE_NAME
==============================
SYSTEM
UNDOTBS1
TEMP
DRSYS
EXAMPLE
INDX
ODM
TOOLS
USERS
XDB

Thanks ...

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:636465813226

      Rating: 3
USERNAME
      Mystery  June 05, 2003
Reviewer:  Aamir
      Abbas  from Karachi, Pakistan
Dear Tom,
Please solve this mystery....

SQL> select * From all_users;

USERNAME                         USER_ID CREATED
------------------------------ --------- ---------
SYS                                    0 04-SEP-03
SYSTEM                                 5 04-SEP-03
...
s_aman                                96 04-JUN-03
AAMIR_ABBAS                           97 05-JUN-03

56 rows selected.

A user named "s_aman" appears in lower-case...
All application users were created using 9i Enterprise Console.

we cannot login using this user.

SQL> connect aamir_abbas/aamir@psl
Connected.

SQL> connect s_aman/s_aman@psl
ERROR:
ORA-01017: invalid username/password; logon denied

SQL> alter user s_aman identified by s_aman;
alter user s_aman identified by s_aman
           *
ERROR at line 1:
ORA-01918: user 'S_AMAN' does not exist

SQL> drop user s_aman;
drop user s_aman
          *
ERROR at line 1:
ORA-01918: user 'S_AMAN' does not exist

Now, I created the same user using SQL*Plus...
and it says...

SQL> select * From all_users;

USERNAME                         USER_ID CREATED
------------------------------ --------- ---------
SYS                                    0 04-SEP-03
SYSTEM                                 5 04-SEP-03
...
s_aman                                96 04-JUN-03
AAMIR_ABBAS                           97 05-JUN-03
S_AMAN                                98 05-JUN-03

57 rows selected.

What does it mean?
What's wrong in my db????

          Followup:

someone has:

create user "s_aman" ......

using quoted identifiers.

alter user "s_aman" identified by foo;

      Rating: 4
connect using
      Forms..  June 06, 2003
Reviewer:  Aamir
      Abbas  from Karachi, Pakistan
Dear Tom,
Thanks. It helped...

But how will Oracle Forms will distinguish both of these?
How will user connect to "s_aman" using Forms.? because if the user will type
his id on login screen, it will always connect to S_AMAN....

Thanks again for your kind help.

          Followup:

don't use quoted identifiers, you are only asking for trouble if you do.  tools
are not "expecting it" and may or may not support it.

      Rating: 4
roles
      identified externally  June 06, 2003
Reviewer:
      Geo  from Atlanta, GA
What does it mean by

create role role_name
identified externally;

and what is its usage in real world?

I understand creating a user identified externally in relation to ops$ accounts
in unix. I have never used role in that respect. if you could clarify this with
a demo if possible, it would be helpful. Thanks in advance.

          Followup:

it means you would have the role if you belonged to the unix group the role was
named after.

[tkyte@tkyte-pc-isdn tkyte]$ groups
tkyte apache ora920 dba

[tkyte@tkyte-pc-isdn tkyte]$ plus

SQL*Plus: Release 9.2.0.3.0 - Production on Fri Jun 6 17:06:08 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production

ops$tkyte@ORA920> select * from session_roles;

ROLE
------------------------------
CONNECT
DBA
SELECT_CATALOG_ROLE
HS_ADMIN_ROLE
EXECUTE_CATALOG_ROLE
DELETE_CATALOG_ROLE
EXP_FULL_DATABASE
IMP_FULL_DATABASE
GATHER_SYSTEM_STATISTICS
WM_ADMIN_ROLE
JAVA_ADMIN
JAVA_DEPLOY
XDBADMIN
OLAP_DBA

14 rows selected.

no apache, but

ops$tkyte@ORA920> create role apache identified externally;
Role created.

ops$tkyte@ORA920> Disconnected from Oracle9i Enterprise Edition Release
9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production
[tkyte@tkyte-pc-isdn tkyte]$ plus

SQL*Plus: Release 9.2.0.3.0 - Production on Fri Jun 6 17:06:23 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production

ops$tkyte@ORA920> select * from session_roles;

ROLE
------------------------------
CONNECT
DBA
SELECT_CATALOG_ROLE
HS_ADMIN_ROLE
EXECUTE_CATALOG_ROLE
DELETE_CATALOG_ROLE
EXP_FULL_DATABASE
IMP_FULL_DATABASE
GATHER_SYSTEM_STATISTICS
WM_ADMIN_ROLE
JAVA_ADMIN
JAVA_DEPLOY
XDBADMIN
OLAP_DBA
APACHE

15 rows selected.

now there is.

and other users are not in it, unless they are in the unix group:

[ora920@tkyte-pc-isdn ora920]$ id
uid=501(ora920) gid=501(ora920) groups=501(ora920),502(dba)
[ora920@tkyte-pc-isdn ora920]$ plus

SQL*Plus: Release 9.2.0.3.0 - Production on Fri Jun 6 17:07:56 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production

ops$ora920@ORA920> select * from session_roles;

ROLE
------------------------------
DBA
SELECT_CATALOG_ROLE
HS_ADMIN_ROLE
EXECUTE_CATALOG_ROLE
DELETE_CATALOG_ROLE
EXP_FULL_DATABASE
IMP_FULL_DATABASE
GATHER_SYSTEM_STATISTICS
WM_ADMIN_ROLE
JAVA_ADMIN
JAVA_DEPLOY
XDBADMIN
OLAP_DBA

13 rows selected.

      Rating: 4
is it still a
      group?  June 06, 2003
Reviewer:  Reader

In your example, is 'apache' a group created at the os level just like a 'dba'
group? As a os user if I am included in the group 'apache', I can have it
represented in the database as a role? What privs the 'apache' role/group(?)
contain in your example?

          Followup:

yes (that was the point of "groups" to show that apache was a unix group.

in my example, apache the group/role contained no privs -- only because I didn't
grant anything to apache.  grant something to it and I would have had that priv.

      Rating:
      2
Good  October 01, 2003
Reviewer:
      R.Chacravarthi  from Chennai,India
Dear sir,
well and wish the same from you.
When we have a role, is it possible to find out what are the
privileges that make up the role?
Thanks in advance.

          Followup:

dba_tab_privs
dba_sys_privs
dba_role_privs

where grantee = 'role name'

October 01, 2003 Puneet -- Thanks for the question regarding
      "Difference Between ROLLUP and CUBE", version 8.1.5.0.0

      You Asked
Hi Tom,
How you doing there???? Well I wsih to know what's the problem
between ROLLUP and CUBE functions when used in GROUP BY, as I
used both of them but the output I got was same.
Please state with an example.

Regards
Puneet Mathur

      and we said...
rollup will create subtotals, much like a CONTROL-BREAK report
would.  If you group by rollup(a,b,c), it'll produce subtotals
by A,B and A.  So, you get the results equivalent to:

select a,b,c, <aggregates>
  from t
 group by a,b,c
UNION ALL
select a,b,NULL,<aggregates>
  from t
 group by a,b,NULL
UNION ALL
select a,NULL,NULL,<aggregates>
  from t
 group by a,NULL,NULL
UNION ALL
select NULL,NULL,NULL,<aggregates>
  from t
 group by NULL,NULL,NULL
/

but they will be nicely ordered (as demonstrated below with the
EMP table)

cube will create every combination -- you'll get the aggregates
by:

a,b,c
a,b
a,c
b,c
a
b
c
<null>

Again, ordered nicely.  Here is an example:

scott@ORA8I.WORLD> select decode(grouping(mgr),0,null,'summed')
mgr_agg,
  2             decode(grouping(job),0,null,'summed') job_agg,
  3             mgr, job, sum(sal)
  4  from emp
  5  group by rollup(mgr,job)
  6  /

MGR_AG JOB_AG        MGR JOB         SUM(SAL)
------ ------ ---------- --------- ----------
                    7566 ANALYST         6000
       summed       7566                 6000
                    7698 CLERK            950
                    7698 SALESMAN        5600
       summed       7698                 6550
                    7782 CLERK           1300
       summed       7782                 1300
                    7788 CLERK           1100
       summed       7788                 1100
                    7839 MANAGER         8275
       summed       7839                 8275
                    7902 CLERK            800
       summed       7902                  800
                         PRESIDENT       5000
                                            5
       summed                            5005
summed summed                           29030

17 rows selected.

scott@ORA8I.WORLD> select decode(grouping(mgr),0,null,'summed')
mgr_agg,
  2             decode(grouping(job),0,null,'summed') job_agg,
  3             mgr, job, sum(sal)
  4  from emp
  5  group by cube(mgr,job)
  6  /

MGR_AG JOB_AG        MGR JOB         SUM(SAL)
------ ------ ---------- --------- ----------
                    7566 ANALYST         6000
       summed       7566                 6000
                    7698 CLERK            950
                    7698 SALESMAN        5600
       summed       7698                 6550
                    7782 CLERK           1300
       summed       7782                 1300
                    7788 CLERK           1100
       summed       7788                 1100
                    7839 MANAGER         8275
       summed       7839                 8275
                    7902 CLERK            800
       summed       7902                  800
summed                   ANALYST         6000
summed                   CLERK           4150
summed                   MANAGER         8275
                         PRESIDENT       5000
summed                   PRESIDENT       5000
summed                   SALESMAN        5600
                                            5
       summed                            5005
summed                                      5
summed summed                           29030

23 rows selected.

scott@ORA8I.WORLD>
Review & Followup

      Rating: 5
can you explain
      the grouping clause  January 13, 2002
Reviewer:
      A reader
Tom what does the grouping clause do?

          Followup:

well, its a documented function:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a85397/function.htm#77500
In short, if you look at this exmaple:

scott@ORA8I.WORLD> select decode(grouping(mgr),0,null,'summed') mgr_agg,
  2             decode(grouping(job),0,null,'summed') job_agg,
  3             mgr, job, sum(sal)
  4  from emp
  5  group by rollup(mgr,job)
  6  /

MGR_AG JOB_AG        MGR JOB         SUM(SAL)
------ ------ ---------- --------- ----------
                    7566 ANALYST         6000
       summed       7566                 6000
                    7698 CLERK            950
                    7698 SALESMAN        5600
       summed       7698                 6550
....
....
summed summed                           29030

we used the grouping function to determine what rows represented ROLLUPS -- when
the columns we summed over.  Here you see the row in bold with sum(sal) of 6550
-- we know thats a summary row -- summed OVER job for that manager.  The last
row in the result set is the total sum over both MGR and JOB -- and the grouping
functions lets us identify it as such.

      Rating: 5
Ordering of
      grouping aggregate information  May 06, 2003

Reviewer:  Martin  from UK
Hi Tom,

thanks for a great Oracle resource.

I'm using 9.2.0.3.0 on win2000, and was wondering about the differences in the
position of the CUBE and ROLLUP "totals".

Here's my table :

SQL> DESC t;
 Name
                                  Null?    Type

---------------------------------------------------------------------------------
-------------------------------- -------- ----------
 A
                                           NUMBER
 B
                                           NUMBER

SQL> SELECT * FROM T;

         A          B
---------- ----------
         1         10
         2         20
         3         30
         4         40
         1         20

5 rows selected.

Now, the ROLLUP "totals" are (as you say), "nicely ordered" :

SQL> SELECT
  2  DECODE(GROUPING(A), 1, 'SUM OF B FOR ALL A') title1,
  3  DECODE(GROUPING(B), 1, 'SUM OF B FOR A = ' || a) title2,
  4  a, b, SUM(b)
  5  FROM t
  6  GROUP BY ROLLUP(A,B);

TITLE1             TITLE2
     A          B     SUM(B)
------------------ ---------------------------------------------------------
---------- ---------- ----------

     1         10         10

     1         20         20
                   SUM OF B FOR A = 1
     1                    30

     2         20         20
                   SUM OF B FOR A = 2
     2                    20

     3         30         30
                   SUM OF B FOR A = 3
     3                    30

     4         40         40
                   SUM OF B FOR A = 4
     4                    40
SUM OF B FOR ALL A SUM OF B FOR A =
                         120

10 rows selected.

But the CUBE totals are inverted :

SQL> SELECT
  2  DECODE(GROUPING(A), 1, 'SUM OF B FOR ALL A') title1,
  3  DECODE(GROUPING(B), 1, 'SUM OF B FOR A = ' || a) title2,
  4  a, b, SUM(b)
  5  FROM t
  6   GROUP BY CUBE(a,b);

TITLE1             TITLE2
     A          B     SUM(B)
------------------ ---------------------------------------------------------
---------- ---------- ----------
SUM OF B FOR ALL A SUM OF B FOR A =
                         120
SUM OF B FOR ALL A
               10         10
SUM OF B FOR ALL A
               20         40
SUM OF B FOR ALL A
               30         30
SUM OF B FOR ALL A
               40         40
                   SUM OF B FOR A = 1
     1                    30

     1         10         10

     1         20         20
                   SUM OF B FOR A = 2
     2                    20

     2         20         20
                   SUM OF B FOR A = 3
     3                    30

     3         30         30
                   SUM OF B FOR A = 4
     4                    40

     4         40         40

14 rows selected.

Does this imply, that you cannot guarantee the ORDER, like the selection of
data, and have to use an ORDER BY to get the totals at the end?

Thanks in advance

          Followup:

In a cube -- there is no "end".

rollup is sort of like "control break processing".  it does a nice report.

cube -- it computes all aggregate combinations possible -- there is no concept
of being "ordered" there in a report sense.

If you want a report -- rollup is natural.

If you need all aggregate combo's -- cube is natural and the grouping function
will tell you in your program what each row represents.  You can certainly order
by it -- but you'll never get a "nice report looking report" out of it.

      Rating: 2
ROLLUP AND
      ORDERING  May 17, 2003
Reviewer:  Asim
      Naveed  from Pakistan
Hi,
I have read in oracle documentation that ORACLE does not
guarantee on the order of rows produced by a
 SELECT statement until you use the ORDER BY CLAUSE.
So how can we rely on the order of rows produced by
ROLLUP without using ORDER BY.
for e.g

SELECT year, quarter, month, sum(sales)
FROM time
GROUP BY rollup(year,quarter,month)

Now I want rows to be returned by this query in the
natural hierarchy with totals in the right places.
will this be guaranteed without using ORDER BY. and if
we will have to use order by then please give the
order by clause for the above query.

Thanks.

          Followup:

the aggregate rows will appear where they belong --rollup does that.  If you put
an order by on it, the aggregated rows will not appear where they belong unless
you take care to do so using the GROUPING() functions yourself.

read through the "sql for aggregation" chapter in the data warehousing guide for
complete details on these functions.

      Rating: 5
... very
      interesting ...  May 20, 2003
Reviewer:  Helena
      Marková  from Bratislava, Slovakia

      Rating: 3
rollup and
      order  May 24, 2003
Reviewer:  Asim Naveed
       from Pakistan
I have read the sql for aggregation topic in oracle manuals.
There is not explicitly written that rollup will order
properly. Although all examples show the results
correctly ordered

But in SQL referrence manual its explicitly written
that ORACLE does not guarantee any particular order of
rows until you use ORDER BY clause. A query without
ORDER BY clause executed several times will/may
return rows in different orders.

          Followup:

yes, but the subtotal rows will be in a predicable location -- at the group
breaks.  the order by can "destroy" this grouping, or you can use the grouping
functions with order by to ensure that

a) the groups are returned to you in sorted order
b) the data within the groups are sorted
c) the subtotals remain in the correct location.

      Rating: 5
Group by
      nulls  July 27, 2003
Reviewer:
      A reader
SQL> ed
Wrote file afiedt.buf

  1  select job,null,sum(sal)
  2  from emp1
  3  where job='ANALYST'
  4  group by job
  5  union
  6  select job,null,sum(sal)
  7  from emp1
  8  where job='SALESMAN'
  9* group by job
SQL> /

JOB       N   SUM(SAL)
--------- - ----------
ANALYST           6000
SALESMAN          5600

SQL> ed
Wrote file afiedt.buf

  1  select job,null,sum(sal)
  2  from emp1
  3  where job='ANALYST'
  4  group by job,null
  5  union
  6  select job,null,sum(sal)
  7  from emp1
  8  where job='SALESMAN'
  9* group by job
SQL> /

JOB       N   SUM(SAL)
--------- - ----------
ANALYST           6000
SALESMAN          5600

Tom

in the example you have given above I dont think is is necessary to group by
null.
select a,b,NULL,<aggregates>
  from t
 group by a,b,NULL

In you see my example, in the second select I removed null from the group by ,
but it did not fail the select.

Is it only for clarify that you have grouped by even nulls.

In addition to this sometime in group by's you see hard coded values with alias
column names. For example...

select job,null,'H',sum(sal)
from emp1
where job='ANALYST'
group by job,null
union
select job,null,NULL,sum(sal)
from emp1
where job='SALESMAN'
group by job

Why is it not necessary to give the said 'H' status column ( what are these type
of columns called) in the group by.

          Followup:

you don't need to group by constants -- it knows there is only "one" value.

      Rating: 5
Why the
      difference using an inline view?  October 01, 2003

Reviewer:  Scott
Tom,

Can you explain what is happening here.  The first query returns 1 row however
when I wrap that inside another select the query returns 4 rows.   ( I am using
9.2 )

scott@SWLOCAL> select 1,2,sum(1) from dual group by cube(1,2);

         1          2     SUM(1)
---------- ---------- ----------
         1          2          1

scott@SWLOCAL> select * from (select 1,2,sum(1) from dual group by cube(1,2));

         1          2     SUM(1)
---------- ---------- ----------
         1          2          1
         1          2          1
         1          2          1
         1          2          1

Thanks

          Followup:

it is a strange effect of the optimzer "knowing" dual should have one row.  it
was not expecting the group by cube to explode the data.

      Rating: 5
Funny, because
      under TOAD (Oracle 9.2.0.4)...  October 02, 2003

Reviewer:  Franco  from Denmark
... the two queries give the same result: 4 rows. (?!?!)

      Rating: 5
Yeah, I
      wondered about that too....  November 21, 2003

Reviewer:  Martin  from UK
Tom,

at what point does the "optimizer / dual" thing happen?

Since, even the entries in v$sqltext are the same for a TOAD-issued query, than
for a SQL*Plus issued query....

SQL> select sql_text
  2  from v$sqltext
  3  where sql_text like '%testsql%'
  4  /

SQL_TEXT
----------------------------------------------------------------------
select /* testsql from SQL*Plus */ 1 from dual group by cube (1,2,3)
select /* testsql from TOAD */ 1 from dual group by cube (1,2,3)

Regards
Martin

October 02, 2003 KK -- Thanks for the question regarding "How to
      log in using a cookie safely", version 4.0.8

      You Asked
I've created serveral web pages in the OAS.  When the user
enters the first page, hs is prompted to input his
username and password.  Then I will use the package "OWA_COOKIE"
to store the username in the cookie file.

Then, the second page has some links which calls different
reports generated in the WebDB.  Different users have different
priviledges to browse the report.  Therefore, when the user
clicks the link, I will get his username from the cookie and
execute a procedure to check whether he can browse the report
or not.

Problem: if another user copies the cookie file from the others
and skip the first login page(directly type the URL of the
second page), then he can bypass the
authentication step and browse any report he likes.

What can I do with OAS in order to resolve the problem?

      and we said...
Well, this really has nothing to do with OAS -- this has to do
with the way you are generating cookies yourself.  Every
webserver/database/browser configuration in the world would
exhibit the above behaviour given the way you have coded this
application.

The best method for doing this with WebDB is to use its native
database authentication.  In this fashion, the browser will
prompt you for a username and password.  There are no cookies
anywhere.  In your application, you can simply look at the USER
psuedo variable (eg: select user from dual) and that'll tell you
who is who.  Then you can even use things like VIEWS, Fine
Grained Access Control, GRANTS and ROLES to control who can see
what data (without coding it all yourself).

If you cannot do that, then you'll need to do a couple of things

1) do not use persistent cookies (cookies with an expiration
date/time) as you are now.  In that fashion, they (the cookie)
will never be stored on disk.

2) obfusicate the cookie, do not put the username into it and
encode your own timeout into it.

A common method is to use a sessionid that changes frequently
and it randomly generated.  You store the user session state in
a database table and that table is keyed by the session id.  For
example -- you might have a table like:

create table session_table
( session_id    number primay key,
  timestamp     date,
  username      varchar2(30),
  <other state variables you need>
)
/

Your login routine would do something like:

procedure login( p_username in varchar2, p_password in varchar2)
as
   l_session_id number;
begin
   -- validate the username/password is correct here...

   if ( it is correct )
   then
       select session_seq.nextval ||
              to_char(random.rand,'fm00000') into l_session_id
         from dual;

      insert into session_table values
      ( l_session_id, SYSDATE, p_username, .... );

      -- set the cookie to be l_session_id
      -- do whatever
   else
      -- tell them it failed....
   end if;
end;

So, here the cookie is NOT the usernam (very very trivial to
guess) it is a unique number that is randomized (pretty hard to
guess, you would have to guess).  See

http://osi.oracle.com/~tkyte/Misc/Random.html
for info on getting a random number in plsql if you do not know
how.

Now, your other routines would all call a common routine to
"authenticate" the user.  It would look like this:

function authenticated_userame return varchar2
is
  l_rec session_table%rowtype;
  l_id  number;
  l_username varchar2(30);
begin

   -- get the cookie value into l_id

   update session_table
      set timestamp = SYSDATE
    where session_id = l_id
      AND timestamp > sysdate - 1/24
   returning username into L_USERNAME;  -- expire the session
                                        -- after one hour of
                                        -- inactivity

   if ( sql%rowcount = 0 ) then
       -- tell them the login either hasn't happened or
       -- has timed out
       return NULL;
   else
       return l_username;
   end if;
end;

This routine will expire inactive sessions after 1 hour and will
either return a valid usename or NULL in the event the user is
not logged in.
Review & Followup

      Rating: 4
Cookie not
      validated  February 20, 2003
Reviewer:  Dave
       from NH
When using the above methodology as described below, I've had some problems with
the browser recognizing the cookie:

-setting up a login
-hashing the user/pass against the database
-setting up a cookie with a randomly generated session id
with a timeout etc.

After I set the cookie with the session id after the login I have the user
directed to another procedure based on the login. Although, this does not
happen. Due to error checking in the procedure the user is being directed
to(authenticate user)if there is not a valid cookie with the appropriate info
the user is then prompted for a username and password again at a login prompt.

This shouldnt happen, the user based on their initial login should be directed
to the appropriate page. Essentially after establishing the cookie the web
browser is not seeing it. But after they login a second time the program works
fine.

Any insights or workarounds for this would be great!

D

          Followup:

i don't know what you mean by "problems with the browser recognizing the cookie"

You have a bug in your code -- cause this is what thousands and thousands of
sites do (perhaps millions even).  If you put up the most SIMPLE and SMALLEST of
test cases possible -- stripping out every non-relevant bit of code and just
getting it down to "login procedure" and "first page of application", we can
take a look.

You do know you cannot "read" a cookie in your procedure after setting it right?
 I mean -- the cookie set is just writing a http header.  you don't "see that"
in your procedure.  But it is OK you cannot "see it" cause you SET IT and hence
you KNOW IT.

I don't understand the references to the browser here, don't see where that fits
in as the browser just holds and sends cookies, it doesn't really "read" and
"use" them.

      Rating: 5
Thank you and
      thank you again.  March 12, 2003
Reviewer:  Todd
      Smith  from Tallahassee,Fl
Cleared all the fuzzy areas I was having with cookies and oas.

      Rating: 3
What about
      other applications  October 01, 2003
Reviewer:
      Raghu  from San Jose CA
Hi Tom,

Expanding the example a bit further. Say I have two applications - one using the
pl/sql iAS and the other is a asp/IIS front end.
What's the 'best practice' to allow a single user signon?

Can you crtique this plan - Create a single USER table with usernames/encrypted
passwords and have a single web interface to it that verifies that the correct
user/pass are entered and if yes, then writes a cookie to the user's computer.

This way whether the user enters via the ias or iis  - or some future third
method - each application's step 1 is to check for a cookie and if it does not
exist, then hive off to this single web interface I spoke about above. Once it
exists, the user can leave this app and go to the other app - whose step 1 again
is to also check for a cookie. It now exists and she can come through.

The cookie works only to say - yes, this user is valid. Roles and what each user
can or cannot see is left to each app. Expiry times are also left to each app to
manage.

This sort of mimicks LDAP/Passport. Does it sounds like a good plan? What are
the pitfalls?

          Followup:

don't encrypt passwords -- hash them.  no need to encrypt/decrypt ever -- you
only need to verify.

encrypt the cookie -- sure, but not the persistently stored password.

i would not let expiry times up to the app -- since they share the cookie, it
should be done consistently (eg: they all enforce the exact same policy)

the pitfall is -- how do you manage the key to decrypt the cookie without giving
the key away to everyone.  once everyone has the key -- you cannot easily change
the key and once the key is discovered anyone can easily become anyone else.

What some systems do is:

a) there is a master cookie -- lets call this "M"
b) each app has its OWN cookie, lets call this "A1", "A2", ... and so on

(1) you goto application -- it looks for its cookie A1.  if A1 exists,
application decrypts it (application knows the key -- how we'll see in a moment)
and all goes well.

If A1 does not exist, application looks for M.  If M does not exist, then
application redirects user to login server site to get M set.  We would go back
to step (1) above after logging in.

If M does exist -- that means the user has been authenticated but hasn't used A1
as yet.  So, A1 makes a web services like call to the login server -- sending
the encrypted cookie M (which A1 cannot decrypt -- no key).  A1 sends the cookie
M and a "secret key".  The login server will decrypt M (it knows the key) and
recrypt it with A1's secret and send it back to A1.  A1 now has the users
identity and can set the cookie A1.

Now, each application has its own secret key which can be changed at any time.

A little stronger -- note also that talk between A1 and login server would use
SSL so as to not expose the key.

October 02, 2003 -- Thanks for the question regarding "RMAN backup
      strategy!.", version 8.1.7

      You Asked
Thanks.... sorry I hit update previously without updating the question. Here are
the details u wanted:

1. Two databases to start with
2. This is where we need help as well. How many backups to keep? How to decide
that? Suppose we have taken full database backup level 0 on Sunday and we take
level 1 backups on every other day. Upto what point should we keep the previous
backups. Should it be a week so that when we reach next Sunday , we have a level
0 backup and here we drop the previous all backups.
3. 40 GB database right now.
4. Yes ..we plan to manage some more databases in near future.
Many thanks...

Irshad.

Hi,

We are in the process of developing a hot backup strategy for our productions
sytem using either RMAN or OS scripts. My questions:

1. RMAN or OS scripts...which one and why....point me to some references. I have
done a reading of RMAN manuals though.

2. If we use RMAN...full backup of database (including controlfile and
archivelog) on Saturday evening and the incremental backups of level 1 every day
(evening). Is that sound enough? At what point should we drop/delete the old
backups from the tape or disk? Should it be weekly?

3. Due to advent of E-business apps and high availability requirements, do u
think RMAN is suitable as it is not easy to master even for an experienced DBA.
I guess EM console of RMAN does not contain all the functionality of RMAN as
such. U have to know all the RMAN command line...right??

Many thanks.

Irshad.

      and we said...
Off the cuff -- I would say RMAN is the proper tool.  I base this on

o we have 2 databases and will have more.
o we are thinking of using incremental backups (only available via rman)

you need at LEAST 2 backups.  You were thinking along the lines of:

1)Sunday - level 0
2)monday-wed-fre - level 1
3)Sunday - level 0
4)monday-wed-fre - level 1
5)Sunday - level 0  and then erase 1)

so you need space for three backups typically.  I've seen cases where a disk
goes bad in the middle of a backup and the people were overwriting their last
backup -- they were left with NOTHING.  You want at least 2 backups at all time
in the event one is not readable for some reason.

You should know that an INCREMENTAL backup takes longer then a FULL backup -- it
just reduces time to recover (less archives to roll forward through).  It
consumes less storage then a full backup would as well.

RMAN is basically a big book-keeper for you.  If you plan on managing a couple
of databases backups, you'll want this tool.

Review &
      Followup

      Rating: 5
Incrementals
      may take longer.  April 24, 2003
Reviewer:
      Brent  from Salt Lake City, UT
---
You should know that an INCREMENTAL backup takes longer
--

Depends on the configuration...if you backing up over a wan, then you could be
bandwidth limited, and in this case incrementals can actaully be quite a bit
faster.

Like you always say, test it first.

          Followup:

never speak in absolutes - except when you are talking about speaking in
absolutes.

Yes, I should have said "incrementals will most likely take longer...." :)

      Rating:
      4
  May 02, 2003
Reviewer:  prudent
       from CA
Tom,

I have read in most of the Oracle documentation that RMAN incremental backups
are slow.  But I am not sure why? Can you please explain with an example.

Thanks for your time

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:879828062602#9549956940800

      Rating: 4
Error in my
      previous posting  May 02, 2003
Reviewer:
      prudent  from CA
Tom,

I am sorry there was a typo in my previous post, my post should be read as "why
incremental backups using RMAN takes longer".

Thanks

          Followup:

same link.

      Rating: 5
Recovery
      question  May 02, 2003
Reviewer:  Alexandre
       from Rio de Janeiro - Brasil
Tom,

I was moving a database from one machine to another using RMAN.
I did a full database online backup, "backup controlfile to <ctlfile>",
used this <ctlfile> on the new machine plus archived logs of the old
machine to restore and recover the database...

Oracle Version: 8.1.7.4.1
OS version:     Windows 2000 Advanced Server SP3

Both machines has the same directory structure, so I submitted
the script bellow...

run     {
    allocate channel dev1 type disk;
    allocate channel dev2 type disk;
    allocate channel dev3 type disk;
    restore database;
    recover database;
        alter database open;
    }

RMAN-08030: allocated channel: dev1
RMAN-08500: channel dev1: sid=37 devtype=DISK

RMAN-03022: compiling command: allocate
RMAN-03023: executing command: allocate
RMAN-08030: allocated channel: dev2
RMAN-08500: channel dev2: sid=38 devtype=DISK

RMAN-03022: compiling command: allocate
RMAN-03023: executing command: allocate
RMAN-08030: allocated channel: dev3
RMAN-08500: channel dev3: sid=39 devtype=DISK

RMAN-03022: compiling command: restore

RMAN-03022: compiling command: IRESTORE
RMAN-03023: executing command: IRESTORE
RMAN-08016: channel dev1: starting datafile backupset restore
RMAN-08502: set_count=4364 set_stamp=492707528 creation_time=30-APR-03
RMAN-08089: channel dev1: specifying datafile(s) to restore from backup set
RMAN-08523: restoring datafile 00003 to
F:\ORACLE\ORADATA\DBSISBE\TABLESPACE\USERS01.DBF
RMAN-08523: restoring datafile 00004 to
F:\ORACLE\ORADATA\DBSISBE\TABLESPACE\TOOLS01.DBF
RMAN-08523: restoring datafile 00006 to
F:\ORACLE\ORADATA\DBSISBE\TABLESPACE\INDX01.DBF
RMAN-08523: restoring datafile 00009 to
F:\ORACLE\ORADATA\DBSISBE\TABLESPACE\AUDINDICES01.ORA
RMAN-08523: restoring datafile 00012 to
H:\ORACLE\ORADATA\DBSISBE\TABLESPACE\SISBE01.ORA
RMAN-08523: restoring datafile 00013 to
F:\ORACLE\ORADATA\DBSISBE\TABLESPACE\TS_PATROL_DATA1.ORA
RMAN-08016: channel dev2: starting datafile backupset restore
RMAN-08502: set_count=4365 set_stamp=492707528 creation_time=30-APR-03
RMAN-08089: channel dev2: specifying datafile(s) to restore from backup set
RMAN-08523: restoring datafile 00001 to
F:\ORACLE\ORADATA\DBSISBE\TABLESPACE\SYSTEM01.DBF
RMAN-08523: restoring datafile 00002 to
F:\ORACLE\ORADATA\DBSISBE\TABLESPACE\RBS01.DBF
RMAN-08523: restoring datafile 00007 to
F:\ORACLE\ORADATA\DBSISBE\TABLESPACE\DR01.DBF
RMAN-08523: restoring datafile 00010 to
E:\ORACLE\ORADATA\DBSISBE\TABLESPACE\AUDITORIA01.ORA
RMAN-08523: restoring datafile 00011 to
G:\ORACLE\ORADATA\DBSISBE\TABLESPACE\SISBEIND1.ORA
RMAN-08016: channel dev3: starting datafile backupset restore
RMAN-08502: set_count=4366 set_stamp=492707529 creation_time=30-APR-03
RMAN-08089: channel dev3: specifying datafile(s) to restore from backup set
RMAN-08523: restoring datafile 00005 to
F:\ORACLE\ORADATA\DBSISBE\TABLESPACE\SISBEIND2.ORA
RMAN-08523: restoring datafile 00008 to
F:\ORACLE\ORADATA\DBSISBE\TABLESPACE\AUDAUX1.ORA
RMAN-08523: restoring datafile 00014 to
G:\ORACLE\ORADATA\DBSISBE\TABLESPACE\SNAPDATA1.ORA
RMAN-08523: restoring datafile 00016 to
H:\ORACLE\ORADATA\DBSISBE\TABLESPACE\SNAPIND1.ORA
RMAN-08023: channel dev3: restored backup piece 1
RMAN-08511: piece
handle=P:\BACKUP_ORACLE\DBSISBE\ARQUIVOS\RCM\DBSISBE_ONLINE_4366 tag=null
params=NULL
RMAN-08024: channel dev3: restore complete
RMAN-08023: channel dev1: restored backup piece 1
RMAN-08511: piece
handle=P:\BACKUP_ORACLE\DBSISBE\ARQUIVOS\RCM\DBSISBE_ONLINE_4364 tag=null
params=NULL
RMAN-08024: channel dev1: restore complete
RMAN-08023: channel dev2: restored backup piece 1
RMAN-08511: piece
handle=P:\BACKUP_ORACLE\DBSISBE\ARQUIVOS\RCM\DBSISBE_ONLINE_4365 tag=null
params=NULL
RMAN-08024: channel dev2: restore complete

RMAN-03022: compiling command: recover

RMAN-03022: compiling command: recover(1)

RMAN-03022: compiling command: recover(2)

RMAN-03022: compiling command: recover(3)
RMAN-03023: executing command: recover(3)
RMAN-08054: starting media recovery

RMAN-03022: compiling command: recover(4)
RMAN-06050: archivelog thread 1 sequence 4778 is already on disk
as file F:\ORACLE\ORADATA\DBSISBE\ARCHIVE\DBSISBE04778.ARC
RMAN-06050: archivelog thread 1 sequence 4779 is already on disk
as file F:\ORACLE\ORADATA\DBSISBE\ARCHIVE\DBSISBE04779.ARC
...
...
...
RMAN-06050: archivelog thread 1 sequence 4809 is already on disk
as file F:\ORACLE\ORADATA\DBSISBE\ARCHIVE\DBSISBE04809.ARC
RMAN-06050: archivelog thread 1 sequence 4810 is already on disk
as file F:\ORACLE\ORADATA\DBSISBE\ARCHIVE\DBSISBE04810.ARC
RMAN-06050: archivelog thread 1 sequence 4811 is already on disk
as file F:\ORACLE\ORADATA\DBSISBE\ARCHIVE\DBSISBE04811.ARC
RMAN-03023: executing command: recover(4)
RMAN-08017: channel dev1: starting archivelog restore to default destination
RMAN-08022: channel dev1: restoring archivelog
RMAN-08510: archivelog thread=1 sequence=4776
RMAN-08022: channel dev1: restoring archivelog
RMAN-08510: archivelog thread=1 sequence=4777
RMAN-08023: channel dev1: restored backup piece 1
RMAN-08511: piece
handle=P:\BACKUP_ORACLE\DBSISBE\ARQUIVOS\RCM\DBSISBE_ARCHIVE_4368 tag=null
params=NULL
RMAN-08024: channel dev1: restore complete
RMAN-08515: archivelog
filename=F:\ORACLE\ORADATA\DBSISBE\ARCHIVE\DBSISBE04776.ARC thread=1
sequence=4776
RMAN-08515: archivelog
filename=F:\ORACLE\ORADATA\DBSISBE\ARCHIVE\DBSISBE04777.ARC thread=1
sequence=4777
RMAN-08515: archivelog
filename=F:\ORACLE\ORADATA\DBSISBE\ARCHIVE\DBSISBE04778.ARC thread=1
sequence=4778
RMAN-08515: archivelog
filename=F:\ORACLE\ORADATA\DBSISBE\ARCHIVE\DBSISBE04779.ARC thread=1
sequence=4779
...
...
...
RMAN-08515: archivelog
filename=F:\ORACLE\ORADATA\DBSISBE\ARCHIVE\DBSISBE04802.ARC thread=1
sequence=4802
RMAN-08515: archivelog
filename=F:\ORACLE\ORADATA\DBSISBE\ARCHIVE\DBSISBE04803.ARC thread=1
sequence=4803
RMAN-08515: archivelog
filename=F:\ORACLE\ORADATA\DBSISBE\ARCHIVE\DBSISBE04804.ARC thread=1
sequence=4804
RMAN-03026: error recovery releasing channel resources
RMAN-08031: released channel: dev1
RMAN-08031: released channel: dev2
RMAN-08031: released channel: dev3
RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-03002: failure during compilation of command
RMAN-03013: command type: recover
RMAN-03006: non-retryable error occurred during execution of command: recover(4)

RMAN-07004: unhandled exception during command execution on channel default
RMAN-10032: unhandled exception during execution of job step 3: ORA-00283:
recovery
session canceled due to errors
RMAN-11003: failure during parse/execution of SQL statement: alter database
recover
logfile 'F:\ORACLE\ORADATA\DBSISBE\ARCHIVE\DBSISBE04804.ARC'
RMAN-11001: Oracle Error: ORA-00283: recovery session canceled due to errors
ORA-00313: open failed for members of log group 5 of thread 1
ORA-00312: online log 5 thread 1:
'F:\ORACLE\ORADATA\DBSISBE\REDOLOG\REDOLOG05B.ORA'
ORA-27041: unable to open file
OSD-04002: unable to open file
O/S-Error: (OS 2) The system cannot find the file specified.
ORA-00312: online log 5 thread 1:
'D:\ORACLE\ORADATA\DBSISBE\REDOLOG\REDOLOG05A.ORA'
ORA-27041: unable to open file
OSD-04002: unable to open file
O/S-Error: (OS 2) The system cannot find th

-- ###########################################################################
-- # At this point I had to copy all the eight redologs from the old machine #
-- # to the new machine to turn the recovery action possible...              #
-- #                                                                         #
-- # My question is why Oracle did not use the archived redo logs that were  #
-- # ready to use on the disc... why did Oracle search for redologs ???      #
-- ###########################################################################

RMAN>
RMAN>
RMAN>
RMAN>
RMAN> run       {
2>      allocate channel dev1 type disk;
3>      allocate channel dev2 type disk;
4>      allocate channel dev3 type disk;
5>      recover database;
6>         alter database open;
7>      }

RMAN-03022: compiling command: allocate
RMAN-03023: executing command: allocate
RMAN-08030: allocated channel: dev1
RMAN-08500: channel dev1: sid=37 devtype=DISK

RMAN-03022: compiling command: allocate
RMAN-03023: executing command: allocate
RMAN-08030: allocated channel: dev2
RMAN-08500: channel dev2: sid=38 devtype=DISK

RMAN-03022: compiling command: allocate
RMAN-03023: executing command: allocate
RMAN-08030: allocated channel: dev3
RMAN-08500: channel dev3: sid=39 devtype=DISK

RMAN-03022: compiling command: recover

RMAN-03022: compiling command: recover(1)

RMAN-03022: compiling command: recover(2)

RMAN-03022: compiling command: recover(3)
RMAN-03023: executing command: recover(3)
RMAN-08054: starting media recovery
RMAN-08055: media recovery complete

RMAN-03022: compiling command: recover(4)

RMAN-03022: compiling command: alter db
RMAN-06400: database opened
RMAN-08031: released channel: dev1
RMAN-08031: released channel: dev2
RMAN-08031: released channel: dev3

RMAN> exit

          Followup:

either put it in as a question some day, or file a tar with support.  not really
relevant to the established thread here.

      Rating:
      3
  September 18, 2003
Reviewer:
      A reader  from Florida
Tom,
   I'm new to RMAN and i would like to implement it. How can i go about doing
it? Can you suggest any books?

Thanks,
Sriram

          Followup:

well, assuming you've already read:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-REC
....

I've browsed Freemans book -- looked pretty good, lots of examples, well
written.  it is out with Oracle Press.

      Rating: 5
How to start
      Recovery Manager (RMAN)  October 01, 2003

Reviewer:  Dulal  from Bangladesh
Hi Tom,
I have been testing the Recovery Manager on Oracle Server
8.1.6. on Windows 2000. I had read some articles but dit not start rman i.e
cann't connect the target. Can you help me with providing a SCRIPT upto connect.
Thanks in advance.

          Followup:

a script upto connect??

c:\> rman

I guess -- not sure what you are looking for really.

      Rating: 5
Re: How to
      start Recovery Manager (RMAN)  October 01, 2003

Reviewer:  A reader
First, upgrade to 9.2 on Linux. :-)

      Rating: 5
How to start
      Recovery Manager (RMAN)   October 02, 2003

Reviewer:  dulal  from Bangladesh
Hi Tom,
I'm sorry for incomplete question. Actually I want a sample SCRIPT from starting
i.e.
1. creating a user and schema for the recovery catalog and grant the necessary
privileges
2. registering the target database in the recovery catalog
3. create or store rman script
4. backup a database
5. etc. which are necessary for first time use.

N.B. Is there any option which can backup a total objects of a USER like as EXP
tools.

Thanks in advance.

          Followup:

and you have read the Recovery Manager User's Guide (which sort of does that
stuff) right?

you do not backup "users", you backup files or tablespaces or databases.

      Rating: 5
quick
      start  October 02, 2003
Reviewer:
      A reader
There's also quick start guides to RMAN on Metalink. Step by step.

      Rating: 4
well there is a
      beauty - in Technet  December 04, 2003
Reviewer:
      dharma  from CA, USA
Check out Oracle by examples..in technet, thats the best bet,covers everything.
But do read the rman manual or you may not know what you are doing.

http://otn.oracle.com/products/oracle9i/htdocs/9iober2/obe9ir2/obe-ha/html/rman/rman.htm
thats all you need :)

October 02, 2003 Bipin -- Thanks for the question regarding
      "Second Highest Sal", version 8.1.7

      You Asked
Hello Tom,
How are you,
After long time i visited the site and able to find the button.
Ok
Here is my question how can i get from sql second highest salary record from the
table but with deparment wise

Ram 10  1000
Jai 10  2000
San 20  3000
Das 20  1500
Has 30  4000

I should get
------------
Ram 10 1000
Das 20 1500
Has 30 4000

      and we said...
  1  select *
  2    from (select deptno,sal,
  3                 dense_rank() over
  4                   (partition by deptno order by sal desc ) dr
  5            from emp )
  6*  where dr = 2
scott@ORA920LAP> /

    DEPTNO        SAL         DR
---------- ---------- ----------
        10       2450          2
        20       2975          2
        30       1600          2

scott@ORA920LAP>

analytics rock, analytics roll
Review &
      Followup

      Rating:
      5
  October 01, 2003
Reviewer:
      A reader
Cool...

      Rating: 5
How to write
      this query?  October 01, 2003
Reviewer:  a
      reader  from UK
acc_no    a    b
======    ==    ==
100    1    20
100    2    30
100    3    40
200    3    20
200    4    50

I want to get something like this

acc_no    c            (where c is the derived col)
======    ==
100    15
200    10
..
..

for each distinct acc_no in the table I want to generate a derived col
c=sum(b)/sum(a)

that is for acc_no =100
sum(a)=1+2+3=6
sum(b)=20+30+40=90
so the value in dervied col c =90/6=15

Could you help me how to write this using analytics

          Followup:

no analytics, just

select acc_no, suM(b)/sum(a) from t group by acc_no

if sum(a) could be zero,

select acc_no, decode(sum(a), 0, to_number(null), sum(b)/sum(a) ) from t group
by acc_no

      Rating: 4
Query Re.
      Second highest..  October 02, 2003
Reviewer:
      Swap  from UK
Hi!
In the first question Bipin asked about second highest record for the following
data

-------------
Ram 10  1000
Jai 10  2000
San 20  3000
Das 20  1500
Has 30  4000

I should get
------------
Ram 10 1000
Das 20 1500
Has 30 4000

Following query will return only 2 records as there is only one record for dept
= 30 .

1  select *
  2    from (select deptno,sal,
  3                 dense_rank() over
  4                   (partition by deptno order by sal desc ) dr
  5            from emp )
  6*  where dr = 2
scott@ORA920LAP> /

    DEPTNO        SAL         DR
---------- ---------- ----------
        10       2450          2
        20       2975          2
        30       1600          2 <==== This record wil not be there using above
mentioned set of data , instead if there are more than one row for deptno=30
then it will return the data as above...
Am I right?

          Followup:

that is correct -- i answered his text question, ignored the "sample" output.

October 02, 2003 Arvind -- Thanks for the question regarding
      "Having problems while validating XML doc in Database based on XSD doc.",
      version 9.2.0

      You Asked
Hi Tom,
After 2 months at last I got the chance to ask my question. Question is about
Xml validation based on XSD schema definition:

Both documents are stored in different tables in XMLType fields.
I have a trigger on table valid_xml_schemas that fires whenever I update the
VXS_SCHEMA field in table. I validate all the XML documents in the
subset_family_details table which are having foreign key value pointing to the
schema that I am updating in table valid_xml_schemas.

My problem is that if I take the :new.vxs_schema value and insert it into a
temporary table and use the inserted value to validate the XML documents then it
works and validates the documents for a valid schema but if instead of inserting
into temporary table, I validate the XML documents against :new.vxs_schema then
it fails the validation.

Here in this case you might have seen that schema is same with the difference of
only populating from database and direct in coming value. Is it changing the
value somewhere in between or we can not use any value other than database XSD
value to validate the XML document ?

Codes:
-- stores XSD documents
PROMPT Creating Table 'VALID_XML_SCHEMAS'
CREATE TABLE VALID_XML_SCHEMAS
 (VXS_ID NUMBER(4) NOT NULL
 ,VXS_NAME VARCHAR2(30) NOT NULL
 ,VXS_DESCRIPTION VARCHAR2(255)
 ,VXS_SCHEMA XMLTYPE NOT NULL
 ,VXS_PROG_ALIAS VARCHAR2(30) NOT NULL
 ,VXS_DISPLAY_SEQUENCE NUMBER(3) NOT NULL
 ,VXS_STATUS VARCHAR2(1) DEFAULT 'Y' NOT NULL
 ,VXS_STATUS_DATE DATE NOT NULL
 ,VXS_USER_CREATED VARCHAR2(30) NOT NULL
 ,VXS_DATE_CREATED DATE NOT NULL
 ,VXS_USER_MODIFIED VARCHAR2(30)
 ,VXS_DATE_MODIFIED DATE
 )
/
-- stores XML documents
PROMPT Creating Table 'SUBSET_FAMILY_DETAILS'
CREATE TABLE SUBSET_FAMILY_DETAILS
 (SFD_ID NUMBER(9) NOT NULL
 ,VSF_ID NUMBER(4) NOT NULL
 ,VXS_ID NUMBER(4) NOT NULL
 ,SFD_XML_DOC XMLTYPE NOT NULL
 ,SFD_USER_CREATED VARCHAR2(30) NOT NULL
 ,SFD_DATE_CREATED DATE NOT NULL
 ,SFD_USER_MODIFIED VARCHAR2(30)
 ,SFD_DATE_MODIFIED DATE
 )
/

CREATE TABLE TEST( XSD XMLTYPE);

Java code:

package oracle.util;

import oracle.xml.parser.schema.XSDBuilder;
import oracle.xml.parser.schema.XMLSchema;
import oracle.xml.parser.v2.DOMParser;
import oracle.xml.parser.v2.XMLParser;
import oracle.xml.parser.v2.XMLParseException;
import java.io.ByteArrayInputStream;
import java.io.StringWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.sql.SQLException;
import java.io.IOException;
import oracle.sql.CLOB;

public class SchemaUtil{
        public static String validation( CLOB xmlClob, CLOB xsdClob)throws
Exception{
                StringWriter sw = new StringWriter();
                try
                {
                        //Build Schema Object
                        String xml = getStringForClob(xmlClob);
                        String xsd = getStringForClob(xsdClob);
                        XSDBuilder builder = new XSDBuilder();
                        byte [] docbytes = xsd.getBytes();
                        ByteArrayInputStream in = new
ByteArrayInputStream(docbytes);
                        XMLSchema schemadoc = (XMLSchema)builder.build(in,null);

                        //Parse the input XML document with Schema Validation
                        docbytes = xml.getBytes();

                        in = new ByteArrayInputStream(docbytes);
                        DOMParser dp = new DOMParser();

                        // Set Schema Object for Validation
                        dp.setXMLSchema(schemadoc);
                        dp.setValidationMode(XMLParser.SCHEMA_VALIDATION);

                        dp.setPreserveWhitespace (true);

                        dp.setErrorStream (new PrintWriter(sw));
                        dp.parse (in);

                        sw.write("WELLFORMED");
                }
                catch (XMLParseException pe)
                {
                        sw.write("Parser Exception: " + pe.getMessage());
                }
                catch (Exception e)
                {
                        sw.write("NonParserException: " + e.getMessage());
                }
                return sw.toString();
        }

        public static String getStringForClob(CLOB xmlObj) throws SQLException,
IOException{
                Reader clobStream = xmlObj.getCharacterStream ();
                StringBuffer suggestions = new StringBuffer();
                int nchars = 0;
                char[] buffer = new char[1000];
                while((nchars = clobStream.read(buffer)) != -1)
                suggestions.append(buffer, 0, nchars);
                clobStream.close();
                return suggestions.toString();
  }
}

Note: please create a Jar file for above class and load in database using( I
know you know this but still I am wrinting :) ):

loadjava -resolve -verbose -user user/pwd@connectstring filename.jar

CREATE OR REPLACE PROCEDURE TEST_P(XSD XMLTYPE) IS
PRAGMA autonomous_transaction;
BEGIN
         DELETE FROM TEST;
         INSERT INTO TEST VALUES(XSD);
         COMMIT;
END TEST_P;

CREATE OR REPLACE function schemavalidation(xml in clob, xsd in clob)
return varchar2
as language java name
   'oracle.util.SchemaUtil.validation(oracle.sql.CLOB,oracle.sql.CLOB) returns
java.lang.String';

CREATE OR REPLACE TRIGGER PQDTVXS
 BEFORE UPDATE OF VXS_SCHEMA
 ON VALID_XML_SCHEMAS
 FOR EACH ROW
DECLARE
XML_not_wellformed  Exception;
newline                         varchar2(1) := chr(10);
lv_out                  varchar2(32000);
lv_vxs_schemas          Clob;
lv_vxs_id                       valid_xml_schemas.vxs_id%type;
lv_sfd_xml_doc          Clob;

Type tp_clob is table of clob index by binary_integer;
sfd_xml_doc_tab tp_clob;

Cursor cur_xml_doc(in_vxs_id in valid_xml_schemas.vxs_id%type) is
         select sfd.SFD_XML_DOC.getClobVal()
         from   subset_family_details sfd
         where  vxs_id = in_vxs_id;
BEGIN
          -- case1: works:
         TEST_P(:new.VXS_SCHEMA);  -- works
         SELECT X.XSD.getClobVal() INTO lv_vxs_schemas FROM TEST X;

         -- case2 does not work...
         --lv_vxs_schemas := :new.VXS_SCHEMA.getClobVal();

         open cur_xml_doc(:new.vxs_id);
         fetch cur_xml_doc bulk collect into sfd_xml_doc_tab;
         if (sfd_xml_doc_tab.count > 0) then
           for indx in sfd_xml_doc_tab.first..sfd_xml_doc_tab.last loop
                 lv_sfd_xml_doc := sfd_xml_doc_tab(indx);
                 lv_out := SchemaValidation(lv_sfd_xml_doc, lv_vxs_schemas);
                 lv_out := replace(lv_out,newline,'');
                 if ( substr(lv_out,1,1) != newline ) then
                        if ( substr(lv_out,1,10) != 'WELLFORMED') THEN
                           raise XML_not_wellformed;
                        end if;
                 else
                        raise XML_not_wellformed;
         end if;
           end loop;
         end if;
     EXCEPTION
                WHEN XML_not_wellformed then
                         RAISE_APPLICATION_ERROR(-20010,'New Xsd document does
not validate existing XML documents...'

 ||'delete XML documents first and then insert/update XSD again');

        WHEN OTHERS THEN
                 RAISE;
END pqdtvxs;

Please let me know if you need any other information. Any help is appreciated.

Thanks,
Arvind K Sharma

      and we said...
I asked Sean Dillon, our local XML Technologist, to take a look at this and
here's what he had to say...
--

Hi Arvind,

Just so you know, Well-formedness is different from VALID XML according to an
XML Schema.  If you simply need to check for well-formed XML Schemas, you're
already doing that by using the XMLType datatype.  XMLType values can't be
instantiated unless they are well-formed.

I tried reproducing your problem and couldn't boil it down to a test case that
BROKE :-).  I used some of the code on OTN for doing PLSQL-based XML Schema
validation, and then added a trigger much the same as your above... and it
worked fine.  Here's what I came up with:

(all my source files are here:
http://asktom.oracle.com/~sdillon/asktom/plsqlschema/
@DBSchema.sql
@DBData.sql
loadjava -resolve -verbose -user sdillon/sdillon SchemaUtil.class
@SchemaUtil.sql
@printbuffer.sql
@dbvalid.sql

-- trig.sql:
create or replace trigger schema_tab_bifer
  before update
  on schema_tab
  for each row
declare
  l_out      varchar2(4000);
begin
  for i in (select xml from xml_tab) loop
    l_out := schemavalidation(xmltype.extract(i.xml,'/').getStringVal(),
                              xmltype.extract(:new.xsd,'/').getStringVal());
    printbufferout(l_out);
  end loop;
end schema_tab_bifer;
/
show errors

-- test.sql:
@test.sql

SQL> update schema_tab
  2     set xsd = xsd
  3  /

| The input XML parsed without errors.
|
| <Line 1, Column 211>: XSD-2023: (Error) Invalid value of attribute:
'1999-11-31'
| <Line 20, Column 18>: XSD-2105: (Error) Identity constraint validation error:
'Key sequence not found in key reference
'
| | Parser Exception: Invalid value of attribute: '1999-11-31'

1 row updated.

What this shows me is that the trigger fired and executed the XML Schema
validation... I never had to insert the value into a temporary table and it
works fine:

    l_out := schemavalidation(xmltype.extract(i.xml,'/').getStringVal(),
                              xmltype.extract(:new.xsd,'/').getStringVal());

If you can create a slimmed down reproduceable test case I'd be more than happy
to take a look at why it's not working.  Hope that helps.
Review &
      Followup

      Rating:
      3
xmlType.toObject doesnt work  October 02, 2003

Reviewer:  Oleg Oleander  from Hungary
Dear Sean,

I have a nice and well working SQL object hierarchy in my DB, and I want to
expose it thru XML to the client. I registered an XMLSchema and created XML-type
views, and those work well. However I need to convert the schema-validated XML I
got back from the client to SQL object in stored proc. to perform some actions,
but I always got this internal error.
Q1: Could you please tell me the cause of this internal error, and a passible
workaround.

My SQL object has an object hierarchy. I could register a schema that represents
this extension of properties, but I would like to have a single view and a
single table under it to present and store all descendant object of the root
object.
Q2: Could you please tell me, that is it feasable and how to do it?

for Q1:
SQL> create or replace type emp_t as object (
  2          empno number(4),
  3          ename varchar2(10),
  4          job   varchar2(9),
  5          mgr   number(4),
  6          hiredate date,
  7          sal   number(7,2),
  8          comm  number(7,2)
  9  );
 10  /

A típus létrejött.

SQL>
SQL> create or replace type emplist_t as table of emp_t;
  2  /

A típus létrejött.

SQL>
SQL> create or replace type dept_t as object (
  2          deptno number(2),
  3          dname  varchar2(14),
  4          loc    varchar2(13),
  5          emps   emplist_t
  6  ) not final;
  7  /

A típus létrejött.

SQL>
SQL> declare
  2           xsd xmlType := xmlType(
  3  ' <schema targetNamespace="
http://www.oracle.com/dept.xsd
 '||
  4  '               xmlns:my="
http://www.oracle.com/dept.xsd
 '||
  5  '               xmlns="
http://www.w3.org/2001/XMLSchema
 '||
  6  '               xmlns:xdb="
http://xmlns.oracle.com/xdb
'||
  7  '               elementFormDefault="qualified" > '||
  8  '    <element name="DEPT_T" type="my:DEPT_TType" xdb:SQLSchema="XT"
xdb:SQLType="DEPT_T" xdb:defaultTable="DEPARTMENT"/> '||
  9  '    <complexType name="DEPT_TType"> '||
 10  '     <sequence> '||
 11  '      <element name="DEPTNO" type="double" xdb:SQLName="DEPTNO"
xdb:SQLType="NUMBER"/> '||
 12  '      <element name="DNAME" type="string" xdb:SQLName="DNAME"
xdb:SQLType="VARCHAR2"/> '||
 13  '      <element name="LOC" type="string" xdb:SQLName="LOC"
xdb:SQLType="VARCHAR2"/> '||
 14  '      <element name="EMPS" type="my:EMPS_Type" maxOccurs="unbounded"
minOccurs="0" xdb:SQLName="EMPS" xdb:SQLType="EMP_T" xdb:SQLCollType="EMPLIST_T"
xdb:SQLSchema="XT" /> '||
 15  '     </sequence> '||
 16  '    </complexType> '||
 17  '    <complexType name="EMPS_Type"> '||
 18  '     <sequence> '||
 19  '      <element name="EMPNO" type="double" xdb:SQLName="EMPNO"
xdb:SQLType="NUMBER"/> '||
 20  '      <element name="ENAME" type="string" xdb:SQLName="ENAME"
xdb:SQLType="VARCHAR2"/> '||
 21  '      <element name="JOB" type="string" xdb:SQLName="JOB"
xdb:SQLType="VARCHAR2"/> '||
 22  '      <element name="MGR" type="double" xdb:SQLName="MGR"
xdb:SQLType="NUMBER"/> '||
 23  '      <element name="HIREDATE" type="date" xdb:SQLName="HIREDATE"
xdb:SQLType="DATE"/> '||
 24  '      <element name="SAL" type="double" xdb:SQLName="SAL"
xdb:SQLType="NUMBER"/> '||
 25  '      <element name="COMM" type="double" xdb:SQLName="COMM"
xdb:SQLType="NUMBER"/> '||
 26  '     </sequence> '||
 27  '    </complexType> '||
 28  ' </schema> ');
 29  begin
 30            dbms_xmlschema.REGISTERSCHEMA('
http://www.oracle.com/dept.xsd',xsd,true,false,false,false
;
 31  end;
 32  /

A PL/SQL eljárás sikeresen befejez&#337;dött.

SQL>
SQL> create or replace view dept_xml of xmltype
  2   xmlschema "
http://www.oracle.com/dept.xsd
element "DEPT_T"
  3   with object ID (extractValue(sys_nc_rowinfo$,'/DEPT_T/DEPTNO'))
  4   as
  5   select dept_t(d.deptno, d.dname, d.loc,
  6                  CAST(MULTISET(
  7                       select emp_t(e.empno, e.ename, e.job, e.mgr,
e.hiredate, e.sal, e.comm)
  8                        from scott.emp e
  9                        where e.deptno = d.deptno)
 10                  as emplist_t))
 11    from scott.dept d;

A nézet létrejött.

SQL>
SQL> select v.sys_nc_rowinfo$.getClobVal()
  2    from dept_xml v
  3    where rownum = 1;

V.SYS_NC_ROWINFO$.GETCLOBVAL()
--------------------------------------------------------------------------------
<DEPT_T xmlns="
">http://www.oracle.com/dept.xsd">

  <DEPTNO>10</DEPTNO>
  <DNAME>A

SQL>
SQL> declare
  2          d dept_t;
  3          x xmltype;
  4  begin
  5   select sys_nc_rowinfo$
  6    into x
  7    from dept_xml v
  8    where rownum = 1;
  9
 10    xmltype.toObject(x,d,'
http://www.oracle.com/dept.xsd','DEPT_T'
;
 11  end;
 12  /
declare
*
Hiba a(z) 1. sorban:
ORA-00600: bels&#337; hiba kód, argumentumai: [qmxiCreColl9], [], [], [], [],
[],
[], []
ORA-06512: a(z) "SYS.XMLTYPE", helyen a(z) 0. sornál
ORA-06512: a(z) helyen a(z) 10. sornál

SQL>
SQL> declare
  2          d dept_t;
  3          x xmltype;
  4  begin
  5   select sys_nc_rowinfo$
  6    into x
  7    from dept_xml v
  8    where rownum = 1;
  9
 10    xmltype.toObject(x,d);
 11  end;
 12  /
declare
*
Hiba a(z) 1. sorban:
ORA-00600: bels&#337; hiba kód, argumentumai: [qmxiCreColl9], [], [], [], [],
[],
[], []
ORA-06512: a(z) "SYS.XMLTYPE", helyen a(z) 0. sornál
ORA-06512: a(z) helyen a(z) 10. sornál

SQL> exit;

          Followup:

there could be more then one cause -- you'll want to contact support (as with
all ora-600's) to diagnose.

October 02, 2003 josh -- Thanks for the question regarding
      "performance tuning ", version 9.2

      You Asked
Greetings Tom,

I have two questions.

1. Using event 10046 level 8 trace, I am having difficulty proving that my
timing math is correct. I add up all the elapsed time values from the waits and
the parse|fetch|exec and I do not have the same amount of time as the "set
timing on" figure that was displayed when the query that was being traced
finished. I would like to arrive at a method of ensuring that the time figures I
produce out of a trace file match the real world. How can I validate the elapsed
time in the trace file? What figures in the trace file should I add up to reach
a figure that matches the elapsed time that is displayed by "set timing on"?

2. optimizer_index_cost_adj and optimizer_index_caching. What is the lowdown on
these parameters? I have read the default values for these in 9iR2 are not
applicable to most database systems. I have played with them and have gotten the
optimizer to produce plans with far lower costs. However, these lower costs are
often associated with increased index use, which might be bad given the recent
emphasis on lowering lio to reduce serialized latches. What are your thoughts on
these two parameters?

Thanks for your time!

      and we said...
1) why not just use tkprof???

anyway -- the trace file has only what happened on the server.  "set timing on",
well there you are timing:

o the servers performance
o the network performance
o the performance of SQLPlus (which is quite bad actually)
o the performance of the client OS

So,

trace file = server
timing on  = lots and lots of noise and garbage.

me, i would just stick with tkprof -- digging into a trace file only to find
binds (especially now that tkprof added waits to the report itself)

2) see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6601251003901
they are set wrong for most "general" systems.

Review &
      Followup

      Rating: 3
why not just
      use TKPROF  December 31, 2002
Reviewer:
      A reader
I would like to aggregate things to arrive at a clearer picture of bottlenecks
on the system. That way I can prove that bottlenecks aren't the server but are
actually the client or the network. I am trying to create a report that depicts
the difference between response time (end to end on the client) and service time
(time spent processing the work by the db server).  Aren't the SQL*plus wait
events reflective of the network traffic btw client and the db server? Aren't
there a series of wait events that reflect the "noise and garbage time" that you
speak of?

If I can show that there is a great difference between the response time (as
viewed from the client end) and the elapsed time to perform the work (in the
trace file) then we can focus our efforts where they are appropriate. Hence my
desire to validate the elapsed time in the trace file vs the elapsed time shown
by "Set timing on".

          Followup:

But those wait events aren't in the trace files -- they are in the "system".

I think you might want to look at statspack instead then.  System level -- not
an individual trace.

      Rating:
      2
waits  December 31, 2002
Reviewer:
      Josh
Aren't the SQL*plus wait
events reflective of the network traffic btw client and the db server?

I am interested in comparing the end to end response time as seen from the
client to the end to end service time for a particular query or set of query.

Isn't a trace file the best place to get amount of time that is consumed by the
db processing a query?

here is what I envision:

start trace
client hits enter start timer
client receives results end timer
end trace

elapsed time on client timer = Y
parse trace for time used to serve query = X

Z= (Y-X)
Z= network, OS, presentation time.

if Z/Y (ratio of total response time to non-service time)is greater than a
particular threshold I will know that the place for me to concentrate my efforts
is not in the DB, but client side.

          Followup:

What "sqlplus wait events" --

the trace file shows the amount of time consumed by the db processing a query
yes.

the elapsed time at the client shows the "service" time.

I still think you just want to use tkprof.  Client supplies Y.  Tkprof supplies
X (you don't need to add up waits -- just the timings in there.  The waits help
explain why ELAPS time -- which is what you sum up exceed CPU time).

      Rating:
      3
what?  December 31, 2002
Reviewer:
      Josh
Could you explain this a bit more

The waits help
explain why ELAPS time -- which is what you sum up exceed CPU time.

what kinda crazy talk is this? :)

Thanks very much for your help, you do all of us a great service with your time
and smrt!

          Followup:

tkprof shows you things like:

---------------------------------------------------------
update emp set ename = ename

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.01      39.04          0         19         16          14
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.01      39.04          0         19         16          14

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 241

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  UPDATE
     14   TABLE ACCESS FULL OBJ#(36120)

so apparently, that took 0.01 cpu seconds yet it took 39.04 seconds by the
wall clock.  The end user waited AT LEAST 39.04 seconds for that result to be
returned -- maybe more (that is your Y above -- 39.04, you need the X to compute
whether the server or client is the bottleneck)

Now, since I have 9i we get the wait events in the tkprof as well, they are
separate and distinct from ELAPSED times:

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  enqueue                                        14        2.99         39.03
  SQL*Net message to client                       1        0.00          0.00
  SQL*Net message from client                     1        0.00          0.00
********************************************************************************

here we see the database, during the processing of this statement waited on an
enqueue (a LOCK -- i locked all rows in emp in another session first).  It timed
out 14 times and went back and waited again.  That cumulative time -- 39.03
seconds -- accounts for the difference between the CPU time above and the
ELAPSED time.  If we wait for nothing -- cpu and elapsed are the same, if we
wait for some resource - they will differ.

You will see this with selects and waits for "db file xxxx read" and such.  The
more physical IO, the more you wait...

      Rating: 5
* * * * * -- as
      always  December 31, 2002
Reviewer:  Jonathan
      Reinhart

      Rating: 5
Performance
      tuninh  October 02, 2003
Reviewer:
      A reader
Your first response on this question
<quote>
2) see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6601251003901
they are set wrong for most "general" systems.
</quote>

Question
they are set wrong for most "general" systems. - Is this documented anywhere?
The fact that you said that they are set wrong for most "general" systems is
more than good enough for me.  But if I want my DBA to change these parameters,
he would not do it without checking in the oracle documentation.  Just wondering
if you can point me the oracle documentation URL

Thank you

          Followup:

ok, so the parameter "processes" defaults to some number right?

does the DBA need to see that processes is set wrong for most systems in order
to change it?  (it is too small for most 'general' systems)

same thing here.  These parameters have defaults (as all do).  You change
parameters in order to achieve desired functionality.

This is no different.  What you would do is say

"look, i do the alter session to set these to the defaults and this happens.
Now, we've tested these other settings and get this response.  Isn't that
better"

sort_area_size is generally set too small on 8i and before systems as well --
would the DBA need to see in writing that this is a fact -- or would they
perhaps study the effects of setting a larger value, appreciate that less sorts
to disk occur and set it larger overall?

October 02, 2003 david -- Thanks for the question regarding "Why
      "application server" ?", version 9

      You Asked
Why/when do we need an application server? Can't we just have a database server
and create application using, let's say, Microsoft Visual Interdev?

      and we said...
The main #1 purpose of an application server is to avoid having to deploy an
application to the desktop.

the major failing of client server is that after I build an application, anyone
who wants to use it must install it.  Take your example above, just use MS
visual interdev.  Well, to have an Oracle application, you also need the Oracle
client.  So, now you have to install your app and make sure the Oracle client is
there.  That desktop probably needs to run other applications other then yours
as well.  They might need Oracle8.0 client support, you might need Oracle8i
client support.  In fact, you might need version 8.1.6.2 and some other
application needs 8.1.5.  That client might end up with 2, 3, 4 or more installs
of Oracle software -- each with their own configuration files and so on.  Couple
that with the fact that each machine in this PC environment is a "unique"
machine.  Each will experience a unique glitch, a DLL conflict, something.

Now, think about doing that on 100 machines, or 1,000, or 10,000 or more.  Not
very pleasant.  Especially when you discover a bug in your developed
application.  Now you have to track down the 1,000 users who have installed your
application and tell them about it.  They have to get the patch and upgrade it.

with an application server, you host your application there.  people run it on
that machine.  You install it on typically one and maybe a couple of machines.
If you find a bug, you fix it -- thats it.  There are no DLL conflicts, no
install glitches, nothing like that at all.

That is the #1 motivation behind an application server. It is not performance
related at all -- it is 100% "manageability".  I remember within Oracle we tried
to roll out some sales force automation software in 1994 (right before the web).
 It was an utter failure.  Try to get 10,000 sales reps to install and configure
a complex piece of software and then support them on it.  By the time the 3rd
patch was shipped out, many had abandoned it already.  Tired of spending more
time installing and configuring then selling.  Along comes the web and now the
applications "just work".  You goto the site and there they are.  No muss, no
fuss.  I get a new PC and my applications are all still there, no reinstall, no
hassle.

A note to Denise below...

Agreed, but I never mentioned the name of a product -- just the technology.  The
point is "use an app server".  Use anyone you would like, use the one that has
the feature set you need.  You'll achieve to goal of not having 10,000 desktops
to deploy to.

I will point out however that you might want iAS built on Apache or any app
server OTHER then Microsofts if you believe that there is a chance that in 5
years, Windows won't be what it is today.  Portability and choice of OS is
something to be considered in a huge way.  5 years ago, no one ran windows for
real.  5 years before that, Unix was just making headway.  5 years before that
VMS was doing what only mainframes did before.  5 years before that......
Review &
      Followup

      Rating:
      5
  March 19, 2001
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating:
      3
  March 19, 2001
Reviewer:  david
       from INDONESIA

      Rating: 4
But, why
      Oracle's Application Server?  March 19, 2001

Reviewer:  Denise Overstreet  from Tallahassee,
      Florida, US
Microsoft has their own application server.  You could install MS's app server
and develop in MS Visual InterDev and deploy as web-enabled and do all the
things you have listed.

Why would you choose Oracle's Application Server?  Let's talk about connection
to the database, integration of tools, etc.

Thanks.

      Rating: 4
Good Questuion
      and very useful answer.  March 19, 2001
Reviewer:
      Gururaj  from VA USA
I am surprised to see 3 stars from David. Was he expecting
something else ?

      Rating: 5
Now iAS is
      finally getting mature !  March 19, 2001
Reviewer:
      Andre Whittick Nasser  from Rio de Janeiro, Brazil
I used to work for a big bank, and we didn't choose OAS (app. server's old
version) because it was not "mature" enough. It seems Oracle's focus was too
much on the RDBMS. I suppose 9i's integrated RDBMS/iAS support is finally
offering a very reliable and consolidated product after all.

      Rating: 5
very
      Useful  January 12, 2002
Reviewer:  Pertisth
       from Delhi,India
I Think this is better ,effective and best application server for deploying Web
based application.. and this has long long future.

      Rating: 5
8i
      IAS  November 12, 2002
Reviewer:  mo

Tom:

For an oracle web app developed using stored procedures and using mod_plsql and
8iIAS:

1.  A manager says that we are not fully using the capabilities of the J2EE
platform such as Session Management, Security, Persistence. Is it possible to
use these here for a pl/sql app? Are not those only for java based apps.

2. Is it possible to avoid passing the too many parameters from one procedure to
another through submit or URL.

3.Is it possible to place the PL/SQL code on the app server rather than the
database? If yes, is there an advantage  to it?

4. Would PSP eliminate the need to pass parameters and allow to use J2EE
functionality?

5. Would JSP be a better choice to fully use the app server capabilities?
Would you still be doing submits and parameter passing when you use JSP same
way.

6.  Is it true that when you use J2EE app server you create a session and all
information is traced/saved in that session so you do not need to keep passing
infor from one page to another?

THank you

          Followup:

1) you have session managment, security and persistence already.  J2ee is just a
different way.

My question to them would be "so?"

2) does not compute. there is no such thing as too many or too few - there is
only "just right" and just right is the set of parameters necessary for your
application to function

3) no, no and no

4) no -- you are comparing apples and toaster ovens.

5) no, yes, maybe, sort of, not really, sometimes.

6) and you can easily do the same with PLSQL.  It is true that with J2ee you'll
be buying tons more hardward, app server farms and have more moving pieces
(complexity) as well.  Now what?

asktom is built using plsql -- you can see my pages -- I do not pass hundreds of
things from page to page -- I use this thing called a database to store a
session state.  databases are built to store and retrieve data, we are pretty
good at it.

      Rating: 5
8i
      IAS  November 13, 2002
Reviewer:  mo

Tom:

As a followup:

1.  Where do you have session managment, security, and
persistence using PL/sql? is it in database or pl/sql web toolkit (i.e.
owa_sec)?

2.  Is this correct?
    It seems J2EE is only for java applications (servlets, JSP, beans), and you
can only use the 9iAS J2EE features if you are developing using those languages?

3.  how are you handling parameter passing in asktom? when you have a form and
you submit you still have to pass it to an insert procedure? how can you avoid
that using a table?
do you have any examples anywhere on doing this?

4.  Is it true that industry is heading toward J2EE development because it is an
open standard.  You keep saying oracle power is it is portable and you do not
have to tie yourself to an O/S (I agree), but also is not the same true for JAVA
apps.  It is portable to any O/S and any database while PL/sql will need an
oracle database?

Thank you,

          Followup:

1) the database is all about persistance -- it is WHAT THEY DO (think about it
-- define persistance for me).

security -- that is what we do -- think about it -- "grant", "revoke", fine
grained access control etc etc etc.

session managment -- it is called a database table by some (that is what I use,
simple table with state and a session id).  Others use the stateful model
provided with mod_plsql -- i never do and do not recommend it (scales not as
well, scales like j2ee does -- meaning "more hardware"

2) J2ee does stand for "java" -- servlets, jsp's, beans are all part of j2ee.
So, umm, yes, if you want to use j2ee -- you use java (thats the language) the
other things are "techniques" or implementations if you will.

3) don't know what you mean.

4) no comment

      Rating: 5
8i
      IAS  November 13, 2002
Reviewer:  mo

Tom:

Thanks for the great clarification:

1.  What I meant by previous 3 is that, How do you handle the parameter passing
the the asktom app.  You said you save it to a table rather than pass it.  I am
trying to do the same because I have so many procedure passing a lot of
parameters.  If I have an HTML form and user submits it to another procedure I
pass all parameters to 2nd procedure and I isnert it into table. I can not think
of how to do it without declaring all these parameters in the 2nd procedure.
How do you do it?

2.  Surprised you did not comment on J2EE versus pl/sql.  I was trying to get a
convincing answer when someone suggests in a meeting that all apps should be
developed using J2EE because it he heard it is the future and the way to go?

Thank you,

          Followup:

1) read

http://technet.oracle.com/docs/products/ias/doc_library/90200doc_otn/web.902/a90855/feature.htm#1005765

2) thats just the stupidest reason for making a technical decision don't you
think "I heard it is the future and the way to go..."

It is just too broad of a topic to get into the religous war it would turn into
on this forum.  Ask me about the database -- i'll answser that.  Ask me what
technology you should use to build your applications -- I'll take a pass

Hmm, some things to consider though

do you have a core of experienced j2ee programmers sitting with you? (it takes a
while to learn you know)

Does your company have any training or experience with it?
Can you articulate the explicit benefits you will derive from it?
will it cost you less to develop with it?
do you need something as big as it is?

      Rating: 4
Train
      themself  November 13, 2002
Reviewer:
      A reader
Hi, Tom,

I agree with you, basically, the people choose the most
"advanced/or popular" language is ready for train themself
and benefit from the project development. I don't think
they are really good at the new tech but only to
get some experences on that for the new job in case
some future needs. This is just like how many good programmers out there who are
really good at combining JDEV 9.0.3 and BI beans etc.? Look the oracle forum and
then you will see. It needs time to fix the bugs itself and experience
themselves to get familiar with the new tech.
But get into the new tech is always a good thing to
everybody, period.

Thanks

          Followup:

"But get into the new tech is always a good thing to
everybody, period."

Hah.  disagree... maybe good for resume padding, maybe good for computer science
experiments.  Good to get something done now, today, fast?  nah.

      Rating: 5
application
      server  November 13, 2002
Reviewer:  mo

TOm:

1. can you run pl/sql web app using other application servers like websphere or
weblogic or not?

2. For J2EE development, is not all you need to know how to code in JAVA?

THank you,

          Followup:

1) sure, if you write mod_plsql for them.

2) no, you need to know a ton of stuff.  That would be like saying "for Oracle
development -- all you need to know is SQL" (you have my book -- you tell me
whats wrong with that statement -- what other knowledge might you think you
would need to have in order to program CORRECTLY against oracle)

      Rating:
      5
Excellent  November 13, 2002
Reviewer:
      Jim

      Rating:
      5
IAS  November 14, 2002
Reviewer:  mo

Tom:

1.  For oracle programming, you need to know SQL and plsql.
It sure helps to know the packages oracle provides and the
architecture of the database but it is not a must. FOr J2EE development, you may
just need to know JSP and you can develop a web app according to J2EE standards.

2. From your discussion the only advantage I am seeing out of J2EE development
is that the application is portable to all O/S and works with all J2EE compliant
application servers. You can have the app work with sql server, informix, sybase
and windows/unix/linux.

PL/SQL web app (my preference) is fast and easy but you are stuck with oracle
application server and oracle database.

Correct?

3.  Why in your book you do not talk about oracle web technologies?

Thank you,

          Followup:

1) read J2EE books -- then report back.  See you next year

2) Hmm, lets see, if the database is Oracle and you develop in the database
guess what?  You are portable to even MORE OS's...

stuck is a relative term mo.

3) cause it was a book on databases -- did you count the pages?  (you don't have
to, just goto the last one).  1265 of em, had to draw the line somewhere
don'tcha think.

      Rating: 4
all application
      server java developer ever need is tomcat  November 14, 2002

Reviewer:  Mikito Harakiri
I never felt I need anything beyond tomcat and JDBC connection to the database.
All these Enterprise Beans, eXtremely Messy Language, SOAP, etc are for SQL
challenged developers only.

      Rating: 5
app
      server  November 16, 2002
Reviewer:  mo

Tom:

let us say I went to a company who uses oracle but use a websphere or weblogic
or IIS application server.  A lot of them do.

Now do I have to let them give up theri app server to develop a PL/SQL based app
or they can just continue on and I can install mod_plsql on same or other server
and it will work.

THanks,

          Followup:

if they have oracle, they have mod_plsql running under apache (it is right there
with the database) -- that is what they would use.

      Rating:
      5
ias  November 16, 2002
Reviewer:  mo

1. OK so they do not need to buy oracle 9i or 8i IAS and they can continue to
use their websphere or other app server for all other apps without a problem.

am i correct?

2. I always wondered where do all the packages for the web toolkit come from?
They say it is installed with the applicatin server but it is stored in the
database?  Does it get installed in the database whe nyou install IAS?

Thank you

          Followup:

1) they need mod_plsql -- they need apache in order to run mod_plsql.

2) 817 and up -- in the db, with the db, at db install time.  before that with
ias, oas

      Rating:
      5
IAS  November 16, 2002
Reviewer:  mo

Tom:

1. If I am correct, Apache is an http web server (listener) and is not an
application server (more softwware to host apps).  I think this is the standard
web server used by websphere and weblogic anyway.

So reagrdless what app server they use the listener will forward the URL request
to mod_plsql and run the procedure.

          Followup:

tell me -- is a web server an application server?
does an application server have a web server?
what is the line between the two?

      Rating:
      5
IAS  November 17, 2002
Reviewer:  mo

Tom:

Well Here is my understanding of the two:

1.  A web server is not an app server.  It is basically an http listener that
forwards browser requests to appropriate destination.  You can have a static web
site with static html files on a directory and all you need is a web server.

2.  An app server includes a web server and other modules to host an a web
application.  Usually you need one when you generate dynamic content out of
database.

          Followup:

what about modules?  what are they?  part of a web server or not.

I refuse to split hairs.  most web servers today contain many/most of the
attributes of an app server.  I don't even bother trying to differentiate.

      Rating: 5
web
      server  November 17, 2002
Reviewer:  mo

well are not web servers free while app servers are not.

If I do not need to host servlets, JSP, beans why do I need to buy oracle IAS.
all I need is the free apache listener and the mod_plsql which is provided free
by 8i database.

If I decided to build my app with servlets, then I MUST buy an app server.

      Rating: 5
Pay/Free has
      nothing to do with it.  November 17, 2002

Reviewer:  Jer Smith  from Salt Lake City, UT

JBoss, Orion, Tomcat (if all you want is servlets/JSP), etc, are all free.
Oracle 9iAS includes Orion (plus a lot of other stuff--check it out).  And of
course, if you want to be silly, there are plenty of places that'll still sell
you a webserver.
:)  You just need to pick your feature set and what it's worth to you.

      Rating: 5
J2EE (JSP ) vs
      ASP vs Mod Pl/sql vs PSP  November 18, 2002

Reviewer:  Hrishy  from United Kingdom
Hi Tom

This is a very interesting discussion..I am a DBA from the client server ERA and
feel comfortable with pl/sql...but would love to get my hands dirty with web
applications...i feel given my background i would like to narrow down my choice
to mod-plsql and psp...which one is the easiest to pick up ..

b)Can PSP provide the same functionality as ASP or mod pl/sql (please excuse me
if i am comparing modpl/sql oranges with apples psp)

regards
Hrishy

          Followup:

PSP uses mod_plsql.  mod_plsql is needed to run a plsql server page.

PSP is just like ASP or JSP but the procedural language is PLSQL instead of
VB/Java

      Rating: 5
PSP and
      PL/SQL  November 18, 2002
Reviewer:  reader
       from usa
http://otn.oracle.com/tech/pl_sql/pdf/PSP_Best_Practices.pdf
read here for.  toc looks like:

WHAT IS PSP
WHEN TO USE PSP
Writing PL/SQL stored procedures directly
Writing PSP pages
HOW TO STRUCTURE A PSP-BASED APPLICATION?
Isolation of application logic.
Framework for your PSP pages
Using procedure invocation to include “pagelets
Exception handling
REUSE FUNCTIONALITY IN EXISTING PL/SQL WEB-BASED APPLICATIONS

somewhere in the regular docs for the pl/sql web toolkit it covers which way to
go based on your situation also

I say go with pl/sql and if you really don't want to be bothered with coding up
your html as a stored procedure, use one of the many tools that already do that,
like webalchemy or even psp to create the procedure for you, and then take the
source of that procedure and put it into your packages.

I didn't like PSP because it only allowed creation of separate procedures, not
packages, which we know leads to all the dependency issues with procedures.
Maybe I just didn't dig hard enough, but I think that is still the case.

psp is just a crutch for html challenged folks that want to use dreamweaver.
psp authors will defeat the purpose by including app logic in their pages
anyway.

been reading j2ee books for awhile and still don't have all the architecture
down, let alone how to build the kind of apps I can easily build in pl/sql and I
know java already.

      Rating:
      5
j2ee  November 18, 2002
Reviewer:  Stu
      Charlton  from Toronto ON Canada
J2EE is a set of APIs for abstracting core services like databases, web
application servers, messaging, etc.  Nothing more...  Note I said *abstracting*
not *ignoring*.  If there's anything Tom's book has taught us, is that you can't
ignore the details of what you're using, contrary to popular belief.   Every
server & platform is a bit different - J2EE provides a common set of interfaces
to think about it, but it's only a starting point.

There's something to be said for abstraction over transparency.  One enables
evolvable systems that minimize baked-in-dependencies, the other is a pipe
dream.

The way I tend to look at it is, do you prefer PL/SQL (Ada :) or Java as your
language?  Does it have the expressive power to handle the complexity of what
you're trying to do?  Java has polymorphism, and PL/SQL only recently added it.
There are also a number of very interesting tools in the Java space for
refactoring, etc.  Both can do any job, but the question is -- which is more
maintainable, or leads to higher quality software?  This is a difficult question
because "maintainable" and "quality" mean different things to different people.

      Rating: 5
Very
      interesting thread  November 18, 2002
Reviewer:
      Doug  from CT, USA

      Rating: 4
Is Apps server
      a must for Web Applications?  November 19, 2002

Reviewer:  Tony
Is Applications server a must for web applications?
Is web server alone not enough? When to use/not use Appliaction server for web
applications? We are planning to develop web application using JSP, iPlanet and
Oracle. Do I have to use any apps server? Some people say App servers will slow
down the performance. Please explain.

          Followup:

depends on what/how you define an app server.

I clump web servers right in there (never really see app servers WITHOUT a web
server).

In order to run a JSP -- you have components of j2ee in there -- people would
say "well, thats an app server".

"some people say app servers will slow down the performance" -- that's a really
comical statment don't you think?  ask them "well, what'll run the application
then?"

      Rating: 4
Is Apps server
      a must for Web Applications?  November 19, 2002

Reviewer:  Tony
Is Applications server a must for web applications?
Is web server alone not enough? When to use/not use Appliaction server for web
applications? We are planning to develop web application using JSP, iPlanet and
Oracle. Do I have to use any apps server? Some people say App servers will slow
down the performance. Please explain.

      Rating: 5
Future of the
      OS  November 19, 2002
Reviewer:  Tarry
       from The Netherlands
Tom,
Pretty rightly said about the come's and go's of hte OS's but why aren't
companies/consumers willing to pick linux(say) as an OS?
I have heard that oracle will be penetrating the top and the middle market
segment with linux solutions, so what stopping them even though it cost's
nothing?

          Followup:

who says they are not?

I work in Government Education and Healthcare here at Oracle.

Govt is adopting linux -- the FAA runs some fairly big stuff using 9iRAC on
linux clusters for example.  Lots of places in govt.

Education -- they are all over it.

Healthcare -- a little more conservative but starting to look at it.

heck the entire country of Japan is considering moving from windows to linux

http://biz.yahoo.com/rc/021115/tech_japan_windows_1.html
see

http://news.com.com/2100-1001-965843.html
for a recent convert.

I see it literally *everywhere i go*

      Rating: 5
Nice
      discussion!  November 19, 2002
Reviewer:
      Yogeeraj  from Mauritius
On there server side there is not doubt that Linux is invading the market!

However, on the desktop side there is still a long way to go....but it will
come.

Regards

      Rating: 5
Attaboy/girl
      linux!!!!  November 19, 2002
Reviewer:
      Tarry  from The Netherlands

      Rating:
      5
linux  November 19, 2002
Reviewer:
      mo
Tom:

What is the advantage of using Linux over unix? Is it also a server and desktop
O/S?

          Followup:

it runs on commidity hardware nicely.  It is running on my laptop very nicely
and my two desktops.

yes, there are server and desktop releases -- there is SO MUCH stuff about linux
out there -- suggest you take a look around.

      Rating:
      5
  November 20, 2002
Reviewer:
      Farnaz  from Iran

      Rating:
      5
Tom  November 20, 2002
Reviewer:
      Bill  from SoCal
do you have a favorite linux distro you prefer for running Oracle
I have run them under SuSE and been pretty happy

          Followup:

I've only used RH so to say I have a "preference" would be misleading as I don't
have a wide variety from which to compare ;)

      Rating:
      5
linux  November 20, 2002
Reviewer:
      mo
Tom:

What is commidity hardware??

Does it run on all servers (Sun, HP, IBM, DELL, DEC)?

          Followup:

stuff you can buy in a store, cheap -- interchangeable.

      Rating: 5
Great initial
      answer and discussion!  November 20, 2002

Reviewer:  Robert  from Memphis, USA
Thanks Tom!

      Rating: 5
For
      Mo...  November 21, 2002
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
From the Merriam-Webster online dictionary located at

http://www.m-w.com/
Commodity:

b) a mass-produced unspecialized product

- Mark

          Followup:

now thats cheating -- thats my line ;)

      Rating: 3
hosting vb.net
      application on oracle application servers  November 22, 2002

Reviewer:  ash  from in
Dear tom,
      I wanted to know that will the oracle 9i IAS suuport a vb.net application.
If yes then are there any imcompatibllity features?

Reagards,
Ash

          Followup:

see

http://technet.oracle.com/tech/windows/content.html
for lots of information in that regards.

      Rating:
      5
  November 22, 2002
Reviewer:
      A reader
It is a very informative thread.

Can you please give your openion about which platform you would prefer for the
application server? We are considering Report server and 9iAS on Unix and NT. We
have Unix as well as NT resources. We have an existing report server on NT and
so some people are interested to keep it that way. I want to know of there is
any advantage of Unix which I can point out to them (like appache web server
runs better on that etc etc.). If you could say a couple of lines on this I'll
appreciate it. Thanks.

          Followup:

sorry -- platform agnostic there.

I have my personal preferences (things that end in x) but that is primary
because I know that environment much more intensely then the windows one.

There is alot more choice with li|unux running on the same hardware as windows
would as you can easily move about to other hardware platforms that windows just
won't even touch.

      Rating: 5
Why do we need
      an expensive App Server  January 21, 2003

Reviewer:  Srimal  from Trenton, US
Hello Mr Tom,

With 8.1.7 and now the newer releases of 9i, is it not acceptable to put in a
good amount of Business Logic in the Database itself (rather than an App Server)
and have a Web/App server just plainly host the pages for data input and
display?

The reason for this question is the fact that these versions of Oracle are
pretty powerful as to what they can do and if the application is database
intensive, it would be rather prudent to have the work done by Oracle and just
worry about Oracle rather than have to worry about Oracle in addition to an
expensive App Server (the product may be free, but one needs an expensive
specialist to maintain them).

Am I right in using this argument for a Cost effective solution where the
database has already been selected?

From the forum, I get a feeling that
1. Whatever can be done by the database should be done by the database
2. The improvement to Packages is primarily to handle Business Logic.
3. Security for the database is best handled by the database itself.

Thanks

          Followup:

you would not get a ton of pushback from me on that line of thinking...

There are systems of a scale to necessitate application server farms.
Many systems can be done more simply then they are.

      Rating: 5
Thanks very
      much  January 21, 2003
Reviewer:  Srimal
       from Trenton, US
Hello Mr Tom,

Thank you very much for a fast response...

      Rating: 4
Multi-tier
      architecture  January 23, 2003
Reviewer:
      Samson
It seems you are not much in favour of much hyped multi-tier architecture.
Where does a middle tier,encapsulating the business logic
fit in your design methodology?
Personally, I myself am more comfortable in putting business logics in database
(which to me is the middle tier, but not for many experts/purist). But there is
so much hype about adding another middle tier to hold business logic, that it
adds to confusion. There are so many vendors supporting it (Microsoft in
particular). Many of these claim that datbase is not the most scalable piece of
software and the only scalable solution is to add another layer, which carries
out mundane tasks like for example, in a loan based application, repayment
schedule of a customer can be prepared in middle tier, in lieu of database doing
it and on click of Save button, its saved to database.
What's your view on this?

          Followup:

The fact is

o you can do ntier on a single computer.
o you can do ntier on multiple computers.

ntier is just "hey, lets break this UI from the LOGIC from the DATA" -- I can (I
did) do that with PL/I on an IBM mainframe running VM/CMS or MVS using VSAM or
SQL/DS years ago.  It is called "good coding style" (which along with modular
coding, path of least resistance, common sense -- sometimes seems to be out of
vogue with many camps)

My view -- I'm lazy.  I want to admin as few things as possible.  I'm easily
confused as well -- I'd rather have as few moving pieces as possible.  Do I
really need a browser to talk http to a servlet that will do some EJB thing to a
bean that was built using CMP for persistence to update a row in a table?

I find when I build an app against the database -- guess what?  its all about
the data.  How can that middle tier app work out a repayment schedule in the
middle tier *without sucking all of the data out of the database and pretending
to be a database itself*?

Therein lies my real problem with some of these technologies.  Take CMP for
example.  Here we move transactions, persistence, concurrency - -everything OUT
OF THE DATABASE and put it in something else.  Well, what happens pray tell in 3
years when "next great programming paradigm" comes along (as they always do).
Oh -- bummer, you cannot really use the data in the database with "new great way
to do things" cause if you actually try to access the data in the database
(bypassing that great middle tier cache) -- what then?  oh, bummer data
corruption, you see unless you use the app to access the data -- the data is
hosed.  Never mind the fact that you need java programmer to read code to tell
you what the rules regarding this data are.  seems to be totally missing the
design goal of the RDBMS in the first place doesn't it.

Oh, my favorite one -- security.  We'll do it in the middle tier.  Ok, so tell
me, when I bypass your middle tier and just go after the data - what then?  Oh,
no auditing, no access control, no nothing -- yippee...

Is there a time when you need the distributed complexity?  Sure, if I was
building something on the scale of amazon.com - yahoo.com, sure.
Asktom.oracle.com?  Nah -- the universe for that is relatively confined.  I can
more then handle the load - probably on my LAPTOP (actually, my desktop computer
today is bigger then my sparc box from 96' that I'm running on).  so, would I
need 15 tiers to handle the load?  Nope.  I would bet that there are lots of
applications like that -- more then the other kind (only so many amazon's)

      Rating:
      5
  January 23, 2003
Reviewer:  Samson

      Rating: 5
http
      server?  May 19, 2003
Reviewer:  Fahad
       from Saudi Arabia
Dear Tom,
  I need to know  few things:
1.My company has an application built in V.B which we want to deploy on internet
using http server on windows.The database ofcourse is oracle.We have 9i
database.Is this possible?
2.Does this Http server comes with 9i database?.If it does can i just install
the http server from it .Or does it comes with 9iAS only?.. Or do I need to buy
9iAS?
  If i can install just the http server, what about licensing?.Do we need to pay
something for it.
  Tom, to be more clear, just tell me what technical steps should we take to
deploy v.b application on net using http server of oracle.(all the details)Your
help is highly required.

thank you

          Followup:

1) i don't know, our stuff all works "internet wise".  V.B. (virus basic) would
be the thing you need to look at.

2) there is an apache server with 9i.

You would need to ask MS how to get their own language up and going on the
internet.

      Rating: 5
Oracle needs an
      "Ask Mr. AS"  May 19, 2003
Reviewer:  Steven
       from Saint Louis, MO USA
Excellent discussion as always.  We just made the committment to purchase 9iAS -
it would be great of there was an "Ask Mr. AS" with the depth of knowledge and
clarity of presentation that one finds on AskTom!

Steven Healey

      Rating: 5
app server
        May 19, 2003
Reviewer:  Sushant Prabhu
       from Mumbai,India
Hi Tom

 Going through this thread i undertand you handle almost everything(session
management, security etc ) as mentioned above in the database.
What part or functionality of an application server do you use or you dont use
application server and its functionality at all.

if yes do ellaborate a bit on how do you bring the app server to use.
do we need an app server at all or can we handle everything in the database

I reckon when  mod_plsql is used , an app server will come into picture.

Kindly clarify

Regards
Sushant

      Rating:
      5
n-Tier  May 19, 2003
Reviewer:
      Wendy  from Mason, OH
Great Discussion!

      Rating: 5
My 2 cents on
      several of the reviews above  May 19, 2003

Reviewer:  Tyler D. Muth  from Reston, VA
First, I am a colleague of Tom's, so my opinions will be Oracle centric.
Second, someone asked about a "Mr. AS" site; check out
http://devtrends.oracle.com/
 It is a sister-site
of AskTom and a good resource.  I actually work on the tool behind AskTom and
DevTrends, but more on that in a minute.

Now on to the meat.  Several people asked about building applications using the
PL/SQL web toolkit.  My response is that while it is possible, and not really
all that hard for simple 1-5 page apps, it's a very tedious process to build a
large interactive application.  (Warning, gratuitious plug ahead)...

I propose trying Project Marvel (soon to be renamed) if you are comfortable with
SQL and PL/SQL.  It's declaritive, completely contained within the datbase so
you don't need a middle tier, and I would challenge you to find a technology
that can build a web app faster.  Yes, since there is no middle tier, your
scalibility is limited to one machine (for now), but AskTom is an excellent
example of how little hardware you actually need.  You can read about it from
the link on the AskTom home page, or try it yourself for free at
http://marvel.oracle.com/
(did I
mention it's FREE).

Now on to iAS.  Keep in mind I am NOT a Java developer and do not claim to be an
expert on it.  iAS is a good choice if you have developers that know Java.
There are a lot of talented developers out there.  Every CS major coming out of
college today will know Java.  Do you think they teach PL/SQL at many
Univerities? (not likely)  In theory, J2EE apps should scale well, as you can
expand the number of App servers and eventuallu cluster the database using RAC.
Unfortunately (and, yes, I know I am likely preaching to the choir here) too
many Java developers know nothing about the database.  Tom will (and likely has
in other threads) go on for hours about this.  How many long running if's or
for-loops (of the non-infinate variation) have you run into?  How about long
running queries?  Exactly.  The database does the work, and your developers
don't know how to exploit it, then your app is doomed to be slow.  I've actually
seen developers retrieve hundreds of thousands of records into an array and sort
that array in the middle tier.  Last time I checked, the datbase (any database
for that matter) has been pretty good at sorting for a long time.

Someone else posted about being database independant.  Tom has a good section in
his first book about this, so I wont get into it, but the gist of it is...
Desing an app to be database independant is a recipe for failure.

To clear up one more PL/SQL question, the database (817+)comes with Apache and
mod_plsql.  It is included in the license for the database.

      Rating: 3
A question
      about 9ias  May 19, 2003
Reviewer:  A reader
       from China
We have got two problems.

1) One of our form is hanging when we run thro' the internet using 9ias/oracle
8i using UTF as character set. Even i think it is not reaching the preform
trigger. The message in the preform is not coming ! Any ideas from anyone. Its
very difficult to trace the problem.

2) the font size showing in client server is different from 9ias so sometimes it
is hiding the data. What can we do to solve this problem ??? Is there anyway to
solve this without changing the font size because the font size 9(MS Sans Serif)
is standard for us.

      Rating: 5
apache
      server  May 20, 2003
Reviewer:  Fahad
       from Saudi Arabia
Dear tom,
   First of all thanx for the prompt reply.Atleast i came to know that license
for apache server come along with 9i database.
   Actually we have an application in Virus Basic ;),we use odbc to connect to
the 9i database for data.In order to use this application on the internet is it
possible to connect to the database from v.b 'THROUGH' apache server on the
database?
 Can u provide me with some link where i can find the technical details.
 Thanks once again for replying my question.

          Followup:

time to goto askbill.microsoft.com

no, vb cannot connect "through" apache.  you would be using asp's or some other
"technology".

      Rating: 5
VB and
      Oracle  May 20, 2003
Reviewer:  Tyler D.
      Muth  from Reston, VA
First, if you are using ODBC, you should switch to the "Oracle Provider for
OLEDB".  Think of ODBC as a least-common-denominator technology; it's the worst
of all worlds.  The performance is slow and it's impossible to exploit some of
Oracle's best features such as packages and stored procedures.  Which brings me
to my next point...

Move all of your business logic to PL/SQL packages.  You might expect this
answer from an Oracle employee (which I am), but think of the flexibility.  I
challenge you to name a front-end technology that cannot call a package: JSPs,
ASPs (and C#, VB, etc.), Cold Fusion, Adobe Graphics Server, PHP, and the list
goes on.  I was working on a project with a very large organization in which we
built the primary front end using Project Marvel calling packages for all DML
operations.  When another group within the org. wanted to "try" to build a
mobile solution using .Net, we simply allowed them to call our packages.  We
didn't have to worry about them putting bad data into our system as all of the
validation was done either at the table level or in packages.  They only had
select privs on tables, not insert/update/delete, so the only way they could
change the data is through the packages.  And they did not have to re-write any
business logic.  Packages also have another benefit, in that you can query the
user_dependencies view to see what code is dependant on a table.  Thus, if I
need to change a table structure, I can get a report of all of the code this
will affect as well.

Finally, to answer your VB question, you are basically out of luck.  There may
be technologies out there to automatically transform your VB into a web app, but
I guarantee you will not be happy with the appearance or performance.  Client
server and the web are just too different.  I can't tell you how many times I
have seen people converting forms or VB apps to Project Marvel and try and do it
screen for screen, only to end up with a web page that scrolls vertically AND
horizontally for miles.  Your best bet is to cut your losses, pick a web
technology stack and start re-writing.

Below are some links related to my response:

http://marvel.oracle.com/
http://otn.oracle.com/sample_code/tech/windows/ole_db/oledb92/content.html
http://www.macromedia.com/devnet/server_archive/articles/cf_best_practices_oracle.html
http://www.adobe.com/products/server/graphics/main.html
http://www.phpbuilder.com/manual/ref.oracle.php

      Rating: 5
Excellent
      Discussion.  May 20, 2003
Reviewer:  shan

That was an excellent, reassuring and lucid explanation Tylor. Thank you
verymuch.

Tom suggested that may be on a much higher scale (like for amazon / yahoo) he
would probably go with putting business logic in middle tier.. (Tom, is that
what you meant? Or did I misread you..?) It would be great if you could discuss
why is it so. Now that with 9i RAC Oracle can achieve near "leanear
sclalbility", why can't we still have the same 'project-marvel' model
applications extend to truly big systems? I think with the size and traffic, a
site like Amazon, will find it more difficult to handle the transactions in the
middle-tier than inside the database. And I am sure it will take a ton of
hardware to do it. I feel that the practice of using heavy loaded middletier has
to do with the notion that database servers cannot scale up well.

Very curious to hear your (Tom/Tylor) take on the big-site-architecture. Thanks
for this great site!!

Regards,

Shan,
NewYork

          Followup:

there will be times when you exceed the limits of what you can do entirely in
the database -- when you need layers upon layers of what I call distributed
complexity.  It takes a really huge system to get there.  I can scale out in a
huge way with an app server farm, run the cpu/compute intensive stuff there and
let the database just do security and transactions.  That is what I mean.
transactions -- i would never put them in the middle -- the logic of drawing a
page -- perhaps, but only when I know I cannot afford the cycles in my database.

      Rating: 5
RAC with
      Project Marvel  May 20, 2003
Reviewer:  Tyler D.
      Muth  from Reston, VA
Shan.  I'm embarrassed to say this, but we have never combined RAC and Project
Marvel in a production system.  I therefore would not recommend that as a
solution until I'm personally involved in a project that does so.  It's not to
say that I don't trust RAC, as I know of a number of customers that are very
happy with it, I just don't know how the session state model of Project Marvel
will interact with RAC in production.  As our beta period draws to a close, this
is one of the top items on our list.  I will post a followup when we have tested
it.

I'm actually not embarrassed we haven't needed RAC; I would say it's more of a
sense of pride.  The machine running AskTom is a 6 year old Sun e450, has 7
PRODUCTION applications running on it, AskTom alone gets over 300,000 distinct
page views per week and the average page time is .5 seconds (a little slow, but
I'll look into that later, though the top 5 hit pages are under .4).

I cannot speak to iAS at any level as I have never implemented a production
system on iAS and am therefore not qualified to do so.

Someone else made the statement that they thought they need iAS to use
mod_plsql.  That is not correct.  All you need is: 1. The database 2. Apache 3.
mod_plsql.  That is the exact architecture of AskTom.  Project Marvel must be
included in this list for AskTom, but it is nothing more than a bunch of PL/SQL
and tables that hold meta-data which live 100% inside the database.  If you look
at the URL of this page:
  -f is a public synonym to a PL/SQL package which retrieves the metadata that
make up this page.
  -p is a parameter of f
  -4950 is the flow or application number
  -8 is the page number
  -the big number after that is the session...

      Rating:
      5
N-tier  May 20, 2003
Reviewer:
      Darren  from London, UK
Hi Tyler,

 >I propose trying Project Marvel (soon to be renamed) if you are comfortable
with
 >SQL and PL/SQL.  It's declaritive, completely contained within the datbase so
 >you don't need a middle tier, and I would challenge you to find a technology
 >that can build a web app faster.  Yes, since there is no middle tier, your
 >scalibility is limited to one machine (for now), but  AskTom is an excellent
 >example of how little hardware you actually need.  You can read about it from
 >the link on the AskTom home page, or try it yourself for free at .........

If there is no middle tier, why is it limited to one machine? Oracle can be
clustered, can it not?

If i have pl/sql packages to handle presentation only, pl/sql packages to handle
business logic only and of course the data store itself. Is this not an example
of 3-tier design?

Also, some developers, who are keen to get into Java, where I work are pushing
for us to move ALL of our pl/sql presentation and business logic code out into
servlets/jsps and ejb's, basically turning the Database into a bit-bucket. Im
strongly against moving the business logic part out of the database for
performance and data integrity reasons. They are pushing it mainly because its
the new "stategic" architecture etc. What are your views on ejb's vs stored
procs for a medium sized DB (our db is about 3GB in size, runs happily on one
sparc box and receives a few thousand web based hits per day).

Daz.

          Followup:

yes, it can run in a clustered environment.

I actually like to call it modular coding myself -- you know, all of these
technologies like "Object Oriented programming" and "N-Tier" are just
implementations of good old 'modular code'.  Good code was always 'object
oriented' as good code always separated things like this out.  Good code was
always ntier.

Not only performance and data integrity but REUSE goes out the window doing what
they want to do.  Tell me, is java the last language? The last programming
paradigm?  HAH -- I've seen too many "silver bullets, we have found nirvana" in
my career.  Not a chance.  3 years from now, we'll be doing it a bit different
than today.

I would no more go with massive distributed complexity when I didn't need it
then stick a needle in my eye for no reason.  Keep it small, keep it simple and
you won't be sorry.  Make it so you need a professional java programer, database
developer and DBA to cobble together their bits and pieces and make everything
work and then maintain it over time and, well, you see where I'm going.

      Rating: 5
great
      discussion  May 20, 2003
Reviewer:  a reader

Change is not permanent, but change is - as we cycle through languages (
currently java ) and complexity ( getting more and more complex - there is a new
Jxxx acronym everyday ) i feel we will follow the cycle back to less complexity
in our systems.
In our purchasing of systems, we not only need to buy database servers, but
servers to host "middle tier".
More developers are needed to chase the EJBs and J2EE application architecture,
and they have to pick and choose what container or app server to deploy to.
Weblogic, Websphere, JBoss, iAS - they all seem to have their hooks in one way
or another.
If I want my mod-plsql app moved to windows - I move it. If i want it on Linux -
i move it. Easy.  I don't have to worry about Ant or someother tool to deploy -
then manage that as well.
I won't even get into deployed usernames/passwords etc in XML files and having
to now get my Net Admin involved to lock down my server.
Our developers spend hours and hours developing applications that seem to be as
thick as the old client server apps - loading up objects in the middle tier -
using object query language to get that data - letting another machine worry
about state, resources - connections - security : when it has been there in
front of them all along.  Oh, now with all this middle tier stuff, i don't need
to be concerned with the schema - it's abstracted - and the sql calls don't
matter either.  (sarcasm)
Then they blame oracle for being "slow".
Its only time before it goes back to client/server - or some other new
technology.
But the database remains - through thick and thin

          Followup:

i agree.

It is funny how things cycle like this.  I remember in 1994/95 client server
ruled.  client server was getting infinitely complex however.  trying to manage
that stuff was horrible.  along comes this web thing.

it was so simple back then.  remember, it was all just HTML - simple HTML.  tiny
amounts of javascript, but pretty much HTML.  our major formatting tool -- html
tables.

enter DHTML, VRML (remember VRML?), blah blah blah.  Now we have 500meg web
pages with some much complexity that many don't work with my browser of choice
or demand version x.y.z of IE and so on.

We've taken the most simple, open, flexible protocols and made them hard,
complex, closed....

I wonder what is next myself.  I cannot wait (but we'll make it really hard all
over again)

      Rating: 5
Confused on
      Project Marvel  May 21, 2003
Reviewer:
      Steven  from Saint Louis, MO USA
OK, now you've got me really confused with Project Marvel.  Perhaps sitting
through three months of presentations on how 9iAS is the Swiss Army Knife for
all data presentation needs has addled my brain, but could someone (perhaps
Tyler) explain exactly what Project Marvel does and how it differs from 9iAS?  I
have read what is available without committing to the beta program, and I must
confess I am still lost.

Enlightentment would be appreciated!

sPh

          Followup:

marvel is a mod_plsql application generator.

mod_plsql is needed, that runs in ias but all it does is convert a url into a
stored procedure call.

marvel is the stored procedure.  Much like you would in Oracle forms, you layout
a screen, define behavior for the screen (what is the next screen, what happens
before this screen appears, right after, etc etc etc).  You write snippets of
code (like triggers in Oracle FORMS) that marvel executes in reaction to various
events and so on.

Your application is stored as data in tables -- marvel is the runtime engine
then that interprets this data and executes your application.

It is just an application generation environment -- for thos of us who don't
want to code alot of java to do something very easy.

      Rating:
      3
over-glorification of mod_plsql  May 21, 2003

Reviewer:  Matthew  from Canberra, Australia

I agree with Tom & many of the respondants that physical n-tier architectures &
EJBs are often overkill.... but I don't think anybody has really looked at where
the "simple" solution falls down.

I work on an pure PL/SQL production application with some 100,000 lines of code,
written by dozens of people over the last six years (with 6 fulltime maintenance
programmers).  The data in this system will near the terabyte range in the next
couple of years....
 => It's not a "trivial" app.

Poorly written applications are not unique to the J2EE space - this application
is horrible to maintain and it's 100% PL/SQL.   Why?  because of poor
abstractions and low code reuse.   (oh, and the fact that nobody tried
separating logical layers - presentation, control, logic/model).

I find PL/SQL an excellent language for quickly manipulating data - beats the
heck out of acquiring Connection, {Prepared}Statement & ResultSet objects from a
JDBC driver and then iterating through it...  however, it frustrates the heck
out of me.

Why?  Because it's a procedural language - and a not fully developed one at
that.   I find OO languages achieve far better code re-use because they lend
themselves to more elegant and simple designs to breakdown the same problem.

Many argue OO has no benefit & everything can be done in procedural
languages....  unfortunately, many of these solutions rely on features not
available in PL/SQL.... like function pointers :)  As much as OO is another
implementation of 'modular code', the way it is implemented is IMHO far superior
to other implementations.

I know I am stepping into a lion's den of PL/SQL proponents, but really there
are limits on what the language is capable of.

Pick the right tool for the job.   As this application I work on is being
redesigned, we're going to keep the database doing some of the things it's good
at:  referential integrity, validation/security checks in triggers,
*transactions*, batched fetches/updates, sorting & searching.

However, we're getting rid of the things it's not good at - the "VC" out of MVC
:)   Good bye "htp.p" & spaghetti code, hello Struts Framework.   I personally
believe you can't truly appreciate the value of good frameworks and tools until
you have tried to maintain an application without 'em... and I am sold.
(mind you, "Marvel" looks interesting in this regard - I'm off to look at it
very soon)

The plan is to utilise Oracle as a "transaction API" - you give it large pieces
of work like "remove this 'thing' from the system", and it cleans up all the
dependant records, does logging, checks security, copies data to the archive
tables etc etc.

A few specific points:
* "App Server vs Web Server" - it does become hair-splitting when you consider
something like "mod_perl" :)

But, to nit-pick - I have seen "application servers" without web servers.
Consider a Java Swing GUI that talks to an EJB container direct (no http
involved).

* The J2EE world has some nice ready-made facilities like JAAS, layered
request/response filters & session management.  AFAIK, the PL/SQL web toolkit
does not provide such niceties.  Depends if you want them ready-made, or if you
want to implement/ignore them yourself.

* Like Tom, I agree that the J2EE framework is a more complex set of
technologies and has a significant learning curve.  That's the price you pay for
something that can do nearly everything :P   However, I find some of my J2EE
apps are just as good by the "now, today, fast" criteria as PL/SQL (or similar)
web applications - when they're built right!!   Sometimes, even better because
my J2EE apps seem to lend themselves better to reuse... (in *my* experience).

* "Middle Tier" - 'here here' for Tom: you can do n-tier on one box or multiple.
 It doesn't matter.

Some things I can't stand to let go from "A Reader"
* "deployed usernames/passwords etc in XML files".  haha - checked your
"wdbsvr.app" file lately?  Exactly the same thing.

* "worry about Ant or someother tool".   So, how do you take a new version of
code from the test environment & put it into production?   You have to extract,
copy & run it, ergo you need/use a tool.   We wrote our own Unix shell scripts
to do it - Ant is just a tool to help build, manage and move source code (and a
thousand other things).

Everything is IMHO, YMMV.   My experience with PL/SQL is in 8i, so I'm itching
to see what Oracle has done with collections & opaque functions in 9i.
"Objects" in 8i are a joke - even Turbo Pascal 6 did it better (circa 1991).  At
least 9i claims it has "polymorphism" - shame it lacks either multiple
inheritence or "interfaces".   Still doesn't seem to support "constructors" yet
either :/

Pick the best tool for the job.  PL/SQL has its place, but it seems maybe not
for building every single component of large, complex web applications.  J2EE
has its place, but I think we all share the opinion that there are some things
that are still done better by the RDBMS.

matthew

          Followup:

Marvel is struts on steroids.

The app servers without webservers are virtually useless in the environments I
see most -- its all about http and browsers....

o the j2ee world.... yes, that is what marvel is about.

o that is the most important part -- when they are built right.

Here is my gripe.  When I drive my car onto a bridge, you know what -- I have a
very reasonable chance of getting over the bridge safely and off the other side.
 Why -- because a certified, responsible, honest ENGINEER designed, built,
tested (sounds a little like the perfect software cycle doesn't it).  They even
come back from time to time to do maintenance and make sure the thing isn't
falling apart -- BEFORE it falls apart.

While writing a book -- I came to fear the "file/save" option.  Or just the
fancy set of keystrokes I might hit.  I swear, I hit the save button at least
every paragraph.  (hint to would be authors, don't go over about 90 pages in
word -- trust me -- break it up way before then)

I think we agree on tons more then disagree.  I've said myself "pick the best
tool for the job".  On a large complex web app -- it (my beloved plsql) might
not be the best thing (i said as much).  However for most (many at least), full
blown j2ee is a tad overdone.  something in between is the way to go.

(btw: static member functions in the object types ARE constructors!)
(btw2: i still don't like OO languages, too much happens "by side effect" or "by
accident".  I still believe if I didn't write the class hierarchy -- I won't be
able to exploit it - my most reusable code has always been --- well, plsql :)

But I agree -- best tool for the job, more then one tool exists.

      Rating: 5
Well
      Said  May 22, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Matthew,

I would like to congratulate you on making a lucid post that actually used
complete words and complex sentences.  Those type of feedbacks seem to be in
short supply for whatever reason.  While I don't agree with absolutely
everything you have said (and I realise that this matters only to me), I did
want to say it was nice to read a well-formed and intelligently worded feedback.

Regards,

Mark

      Rating: 5
Real good
      discussion  May 22, 2003
Reviewer:  Raj
       from Los Angeles, CA USA

      Rating: 5
app
      server  May 22, 2003
Reviewer:  mo

Matthew:

I just wanted to say that part of your problem is probably not related to PL/SQL
at all. You said you have 100,000 line of code developed by 12 programmers and
maintained by 6 full time people.  I have developed three oracle web
applications using pl/sql web toolkit with about 70,000 lines of code and I
maintain it myself supporting about 50 users for now.  I do the database design,
development, testing and training.  You get more people and you get yourself
into real mess sometimes.

When you move to OO language or J2EE you will see how complex it is.  Also, you
better find the right people for it because many people claim to know it when in
reality there are very few J2EE good programmers out there.

The risk here is that the conversion will take a lot of money and time. By the
time you reach the end you may realize that PL/sql was a whole lot faster and
easier. It would be too late though because you are stuck with J2EE after all
that money spent.

      Rating: 5
A few more
      comments...  May 22, 2003
Reviewer:  Tyler D.
      Muth  from Reston, VA
First off, well written Matthew.  I will try to keep this brief, as Tom has been
giving me a hard time for my lengthy posts...

I was not suggesting you write your whole app in PL/SQL, only the transactional
APIs.  By this I mean don't insert, update, or delete any records except from a
procedure within a PL/SQL package.  I would even go as far as effectively
disabling those operations except from with your APIs (possibly using a trigger
that fires before any dml and looks for a global to be set which you only do
through your packages).  There are several advantages to doing all DML through
packages:
1. You can keep complex security closer to the data.  The closer it is to the
data the better.  What you can't do in constraints or triggers, do in your APIs.
 Then all front ends will leverage the same security code.
2. When business logic changes, you change it one and ONLY one place.
3. The more code you have in packages, the less you have in your front end.  As
Tom said, J2EE will not be around forever, nor will Marvel.  How long has PL/SQL
been around?  How long do think PL/SQL will be supported in the database?  I
can't talk about the new features in the next database release yet, but I think
(read: hope) I can say that <b><strong><really strong>SIGNIFICANT</really
strong></strong></b> (yes, I know tags don't work here) work went into the
PL/SQL engine.  Might that be yet another clue that PL/SQL will be around long
after whatever front end anyone is using now?
4. I know I said it before, but there's nothing like user_dependencies to tell
you what code screwed up your data.  Especially in situations where data is
really sensitive, such as law enforcement, financial, and health care
organizations.
5. Code portability.  Yes, Java gives you that too, but stop and think of how
many platforms the database runs on!  I am a developer of Project Marvel, and
because the code is nothing more than a lot of PL/SQL (not just a procedure as
was stated above) and tables, we have to port exactly 0 (zero) lines of code!

What is Marvel?  Well, it's a declaritive way to build apps for those who like
SQL and PL/SQL.  It handles:
- Session state mgt: create an "item", reference it's value any where else in
the app
- User interface: HTML templates that you can customize or create from scratch
to like like any HTML UI you wish
- Authentication: Built in support for generic LDAP, database, and Portal SSO
security.  Allows you to write your own auth in PL/SQL as well.
- Authorization: create a security function like "select 1 1 from emp where
ename = :USER" (can be much more complex if you like), then declaritively apply
that to any object in your app, like a single item (textbox, radiogroup, etc), a
group of items that we call a "Region", a page, or a whole application.
- Too many more features to mention (SVG Charts, pagination, report sorting,
etc...)

It generates nothing but html and javascript, so no plug-ins, and you could
build an entire app without writing a single line of HTML.

Here is a link to a viewlet demonstrating "Inline Error Messages" for
validation:

http://marvel.oracle.com/i/dp/viewlets/inlinevalid1.swf
Keep in mind Marvel has matured a lot since this made, but it will give you an
idea.  BTW, you can see these in action by not entering a rating when tryin to
post a review.  Alse, textarea with spell-check is one of Marvel's 70+ built in
item types (calendar controls, combobox with submit, popup list of values,
etc...) which you can just use when creating an app.

Sorry Tom, I did it again :(

      Rating: 3
Replacement For
      Portal  May 23, 2003
Reviewer:  Fred Shimaya
       from Irvine, CA
Tyler,

Very interesting!  Is Project Marvel (its incarnation) intended to be the
replacement of Portal as Oracle's web development tool?  What is the difference
between the two?  Will it allow more flexibility than Portal with regards to
custom javascript code?  With Portal, you are limited to what you can do with
javascript.  Finally, is the architecture pretty much the same as Portal's?  One
of my biggest gripes with Portal is how it has to query certain tables just to
build the html, which slows down rendering.  For instance, if you create a
Report By Query and run it, it may take 15 seconds for the page to appear.  You
run the exact SQL statement in SQLPLUS, and it takes maybe 6 seconds.  You
realize that there is nothing in the statement that would cause the slow
rendering.  What is happening is that Portal queries certain tables that contain
HTML tags to build the page.  That query alone is what's causing the slowness.
So, you build a Dynamic Page, include all the HTML tags within the SQL
statement, and when you compile and run the report, it takes 7 seconds as
opposed to 15 seconds.  Will Marvel be just that (another Portal in disguise)?
Or will it use JSP's to build the HTML as it should?  Another reason why one
shouldn't make an entire web app out of a database package!

Thanks

Fred

          Followup:

they are orthogonal products.

marvel builds apps.
portal integrates apps.

they are complimentary, not interchangeable at all.

      Rating:
      5
  May 23, 2003
Reviewer:  Sasa
       from Belgrade, Serbia
Finally someone has explained "Why AS?".
Thanks Tom for that.

      Rating: 5
Reply to Fred's
      Questions  May 23, 2003
Reviewer:  Tyler D.
      Muth  from Reston, VA
Fred, you ask a lot of really valid questions, we get these a lot.  Project
Marvel is NOT another incarnation of Portal, nor is it a replacement for Portal.
 Portal is really good at agregating content to build corporate "My Something"
sites.  We have MyOracle.com.  It ties all of the corporate apps I use together.
 Portal is also very good at Single Sign On.  You can literally use it to manage
the authentication of all of your applications.  We think it's so good at these
tasks we built no real content agregation components into Marvel except pulling
from web services, and we built-in support for connect to Portal's Single Sign
On (or straight LDAP, depending on your needs).  Portal is NOT good at building
applications.  You obviously know this since you have tried.  Yes, you can build
simple 1 or 2 page apps, but you quickly hit a wall.  It's a square peg in a
round hole problem, it just wasn't intended for that.

It's architecture is quite different from Portals, but it does store everything
in tables and it IS a whole web app out of database packages.  Let me use the
AskTom home page as an example.  The page is nothing more than a row in the
pages table.  There are around 10 regions on that page such as "Search",
"Results", "Messages", "Status", "Timing", etc. which are all rows in the
regions table, which has a foreign key that points to the pages table.  Then you
have items on that page, such as search and the go button: you guessed it, rows
in an items table.   Then there are the templates, 1 page template, a differnet
region template for each region (all distinct queries), item templates, a report
template; again all rows in different tables.  I would estimate that no less
than 30 queries were executed just to generate that page, Tom's queries are in
addition to the ~30.

Do you think it's slow Fred?  Ranged between .32 and .38 for me this morning.
This is old hardware.  Marvel gets a lot faster on machines with really fast
processors.  The average overhead per page excluding your queries, which you are
free to write as slow or as fast as you like, is between .12 and .22 seconds per
page.  I don't consider that slow at all.  The advantages over having all of
your code in a bunch of files could take up a whole separtate post.  There is no
saving and compiling here, just save your changes (inserts, updates and deletes
in meta data tables) and run the page.  Period.  We regularly test a baseline of
our code before a new feature is introduced.  If the feature slows things down,
it's re-written until it doesn't or it doesn't make the cut.

We actually have Tom to thank for a lot of our speed.  We work on the same
floor, so my version of AskTom usually involves his white-board (yes, I know I'm
lucky).  Tom has written several major components of Marvel, if for no other
reason than he his really good solving a problem in the quickest, simplest way.
Hope that clears things up Fred.

      Rating: 4
Thanks Tom
      & Tyler...  May 23, 2003
Reviewer:  Fred
      Shimaya  from Irvine, CA
Your explanations help a lot.  Now I know the difference, and look forward to
Project Marvel.  Tyler, I didn't mean to sound sarcastic.  If I did, I
apologize.  I just have two more questions for you:

1.  Have you tried creating reports in Portal and witnessing a major difference
between Dynamic Page and Report By Query, using basically the same SQL statement
(except the former hard codes the HTML tags)?  You'll see a difference if the
result is expected to be over 100 records (we have users who like having mucho
rows displayed on the same page).

2.  What happens in Marvel when the query returns over 100 records?   If the
rendering is slow (not the retrieval), what does that indicate?  Again, let's
assume the users want reports that show all desired records on one page.

Again, I'm not trying to be a wiseacre.  I am just curious.

Fred

      Rating: 5
2 Answers for
      Fred  May 23, 2003
Reviewer:  Tyler D. Muth
       from Reston, VA
I enjoy a little sarcasim every now and then in case you can't tell from my own
posts ;)

1. I am not Portal developer, so I'm not really able to answer this one.

2. If ther rendering WERE slow, that would be a bad thing, but it's not.  There
is a performance impact if you choose certain types of pagination that loop of
over all rows returned, but we do have a pagination scheme that does not do this
for that specific reason.  The following is a link to 2 reports I wrote.  This
link will likely go away eventually (months from now), so my apologies in
advance:

http://marvel.oracle.com/pls/otn/f?p=948:1
There are 2 reports on all objects.  Play with the "Num Rows" on the first one.
I get about .07 seconds for 100 records and .62 for 1000.  Not bad in my book.
The second uses pagination, another built in feature that I simply turned on.
This is a more pracitcal implementation of retruning 1000 records.  Hope this
helps Fred.

      Rating: 5
Hehe... Very
      Nice!  May 23, 2003
Reviewer:  Fred Shimaya
       from Irvine, CA
Yeah!  I like it...  That's screaming fast!  You got me convinced.  A little
sarcasm adds spice to life ;)

Thanks.

Fred

      Rating: 5
That was a
      great demo of your point. Thanks Tylor.  May 23, 2003

Reviewer:  A reader

      Rating: 5
6.00 stars. It
      is marvelous  May 29, 2003
Reviewer:  frequent
      reader  from USA
May be not short but sweet (rephrasing Tom's expression).
Go to this site for almost all questions and sometimes spend hours just reading.
I don?t smile so much at any site as here reading Tom?s ?follow up? and ?we
said?.
I envy people who work with you when you are in a good mood (not sure about
opposite).

"Pick the best tool for the job" is only part of the approach. What about "pick
the best
people for the job" and they will do the first part. Did you see Oracle triggers
Cobol-VB-style-coded and
having 4000-5000 rows?  Did you see hierarchy of objects when one would work as
dandy, or twenty modules of copy-and-paste code where few
procedures is required?  Did not you see developers with 8 years of experience
who tried to update dba views?  And what about database design?
Very few should go to production at all, because it is easier to redesign then
support.
List is endless to validate the statement and prove that it is not at all
?PL/SQL vs Java? or ?need vs don?t? AS thing.

Thanks a lot for all you tremendous work, non human patience and politeness.

      Rating: 5
5
      years  September 17, 2003
Reviewer:  Wayne
       from San Antonio, Texas
Tom,

  I cannot help but notice that you did not mention that 5 years ago (almost
before "windows"), oracle was there.
  And 5 years before that, when Unix began to make progress there was Oracle.
  5 years before that in the VMS days (before they joined compaq) there was
oracle.
  5 years before that in the "Mainframe" days there was that little company out
of San Francisco just barely starting up (Actually that was about 5 more years
back in 1977) called Oracle ...

      Rating: 5
app server
      architecture  September 21, 2003
Reviewer:
      mo
Tom:

1. Can you tell me if this is architecture is feasible or not.

Currently we have server "A" and 8i database and we run everything using
mod/plsql as stored procedures.
System admin wants to put our web app on server "B" which has 9i database.
However they block web access on this server and we have to use an app server
(mod/plsql) installed on server "C".  can it be done and you think there is any
security justification for this?

          Followup:

why would the sys admin put an app server on a machine that does not permit any
web access?

seems sort of "self defeating", what is the purpose?

i must be missing something somewhere.  it does not seem logical.

yes, mod_plsql (and apache) can run anywhere in the network, it need not be on
the same machine -- but since mod_plsql doesn't really do anything seriously
hard and apache is pretty efficient, there isn't really a good reason for
splitting them up necessarily either.

      Rating: 5
app
      server  September 21, 2003
Reviewer:  mo

Tom:

SErver "C" is the public server.  All external users log into it.  Server "B" is
private and contains the database.  IT looks like they do not want people log in
directly to the web app from "B".

However, if my thinking is correct, even if users log in to server "C" you need
to code hyperlinks that point  to server "B" and use port 7776. Then they weould
be able to see the other server anyway?

          Followup:

if apache/mod_plsql are running on "C" and the database is on "B", then
mod_plsql would use sql net from C to B

no hyperlinks to B, just a tns connect string to let C log into the database on
B

      Rating: 5
Database and
      Oracle HTTP Server on same machine  September 22, 2003

Reviewer:  Jens  from Germany
Hi Tom,

somewhere in the Oracle documentation I found the recommendation that we should
use the Oracle HTTP-Server and the database on the same machine only for
development purposes.

What Do you think? I think, that I could use it also for a production
environment. The peerformance ist great, why 9ias?

Bye,

Jens

          Followup:

they were probably assuming "java", not mod_plsql

      Rating: 5
app
      server  September 22, 2003
Reviewer:  mo

Tom:

On your second answer you mention there will be no hyperlinks to server "B".
Everything will be to C and only DAD will be pointing to "B".  Does not that
answer your question "seems sort of "self defeating", what is the purpose?".

Server "B" will be sort of hidden to the public. Some security people may think
this is good security procedure?

          Followup:

I thought this:

System admin wants to put our web app on server "B" which has 9i database.

was saying the app server would be on "B", that would be self defeating.  I read
it wrong.

      Rating: 4
8i vs
      9i  October 01, 2003
Reviewer:  Denise
       from Virginia,USA
Tom

thanks for the clarification regarding 9i application server technology and its
benefits.

we are in the process of trying to evaluate the pros and
cons as to whether we should upgrade to 8i EE or wait
to migrate to 9i in January 2004.

Our software vendor is trying to push us to upgrade
to 8iEE (we currently use 8.1.7 SE), at a cost of $16,000, while telling us we
will be migrating to 9i in January.

After reading your comparison on application server technology and client/server
deployments it definitely serves our interest and benefit to wait for 9i.

Can you give any more pros and cons regarding 8iEE vs 9i.

thanks

          Followup:

it does not seem to make "sense" to move to EE and test now -- only to do it all
over again in Jan

I'd be asking the vendor what the pro's of doing this intrusive thing right now
is?

      Rating: 3
8iEE vs
      8iSE  October 02, 2003
Reviewer:  Denise
       from Virginia, USA
Tom

thanks for response.

The reasoning for getting us upgraded to 8iEE before 9i
is that if we remain with 8i(817)SE when we upgrade to 9i
it will still be the SE and not EE.

I have been pushing for the EE whether it be 8i or 9i so
that I can implement partitioning features to tables and
use Analytic Functions...which I do not have available
with SE.

We are also told it will not affect us, cost wise, when
we finally migrate to 9i...so we are pondering whether
to go ahead and invest now in the 8iEE...which will give
me a couple months of "play time" with the new features
while we wait for 9i implementation.

again..your thoughts,comments are eagerly awaited.
thanks.

          Followup:

seems intrusive to me.

what i would be more tempted to do is get 9iEE, and on a test system -- do the
january upgrade now -- to "play", "test", "learn"

Not sure that 2 months with 8iEE would buy you much, given the amount of
possible effort it could take to get there (it would be like doing an upgrade
right now, you would be changing the software, nothing "should" go wrong but
something could)

October 02, 2003 Ramasamy -- Thanks for the question regarding
      "Flashback Question.", version 9.2.0.1

      You Asked
Tom ,

Could you please mention what privilege i need to get.

Documentation says flashback against data dictionary view are allowed as below.

You cannot retrieve past data from a V$ view in the data dictionary.
Performing a flashback query on such a view just returns
 the current data.
You can perform flashback queries on other views
in the data dictionary, such as USER_TABLES.

SQL> select * from t1 as of timestamp (sysdate -1);

        N1         N2
---------- ----------
                    2

SQL> select text from user_source as of timestamp (sysdate -1)
  2  where name like 'SP_CREATE_BOOKING_TRAN%'
  3  /
select text from user_source as of timestamp (sysdate -1)
                 *
ERROR at line 1:
ORA-01031: insufficient privileges

SQL> select * from V$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
PL/SQL Release 9.2.0.1.0 - Production
CORE    9.2.0.1.0       Production
TNS for Linux: Version 9.2.0.1.0 - Production
NLSRTL Version 9.2.0.1.0 - Production

SQL>

I made sure i have DBA privilege. What do i need to get to have
query against user_source. As you can see it is working for normal
tables.

Thank you very much.

Ramasamy T

      and we said...
this is related to the O7_DICTIONARY_ACCESSIBILITY init.ora parameter.

Very quickly, if you log in as SYSDBA and:

sys@ORA920> grant flashback on user_source to ops$tkyte;
Grant succeeded.

then you will observe:

  1* select 'Yes' from user_source as of timestamp(sysdate-1) where rownum = 1
ops$tkyte@ORA920> /

'YE
---
Yes

ops$tkyte@ORA920> select 'Yes' from dual as of timestamp(sysdate-1) ;
select 'Yes' from dual as of timestamp(sysdate-1)
                  *
ERROR at line 1:
ORA-01031: insufficient privileges

(dual is a dictionary object as well).  This happens because the dictionary (sys
owned things) are prevented from being accessed by the "general" grants like
"flashback any table", "select any table".  This is for security.

This is the default when:

ops$tkyte@ORA920> show parameter o7

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
O7_DICTIONARY_ACCESSIBILITY          boolean     FALSE

the default setting is in place.  If we change it:

ops$tkyte@ORA920> show parameter o7

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
O7_DICTIONARY_ACCESSIBILITY          boolean     TRUE
ops$tkyte@ORA920> select * from dual as of timestamp(sysdate-1);

D
-
X

We have full unfettered access to the data dictionary at the cost of much less
security!!! i do not recommend this second approach at all!

October 02, 2003 Richard -- Thanks for the question regarding "How
      to dynamically add "missing" data", version 8.1.7

      You Asked
We have a transaction summary of customer orders by year and month.  We need to
create a summary that will have 12 rows for each customer with a current
year-to-date and prior year-to-date order total, regardless of whether the
customer had orders in all 12 months.  I've been able to generate the
year-to-date amounts for the current and prior year by customer using sum() over
(partition by ...) syntax, but getting the additional months is troublesome.
For example, if a customer purchased items in months 1-4 and in 7, and 11, we
need to fill in periods 5-6 with the year-to-date amount of period 4, periods
8-10 from period 7 and period 12 from period 11.  Is there a way to do this
automagically in sql?

      and we said...
what you need to do is generate a set of CUSTOMER_NAME + DATE where customer
name is the set of all customers you have in this report and date is jan-1,
feb-1, etc for that year.

So, if you had 2 customers -- this set would have 24 rows (2*12).  For example,
suppose you had:

ops$tkyte@ORA920> select * from customer;

NAME                                  AMT DT
------------------------------ ---------- ---------
tom                                   100 17-JAN-02
tom                                   200 17-FEB-02
tom                                   300 17-APR-02
tom                                    50 17-JUN-02
sue                                  1231 17-MAR-02
sue                                  2321 17-APR-02
sue                                  3122 17-SEP-02
sue                                    59 17-OCT-02

8 rows selected.

Then:

ops$tkyte@ORA920> select real_name, real_dt, nvl(amt,0), nvl(sum(amt) over
(partition by real_name order by real_dt ),0) cum_amt
  2    from ( select name, trunc(dt,'mm') dt, sum(amt) amt
  3             from customer
  4            group by name, trunc(dt,'mm')
  5             ) A,
  6             ( select real_name, real_dt
  7                 from ( select distinct name real_name
  8                          from customer ),
  9                      ( select add_months( to_date('01-jan-2002'), rownum-1 )
real_dt
 10                          from all_objects
 11                             where rownum <= 12 )
 12            ) B
 13   where b.real_dt = a.dt (+)
 14     and b.real_name = a.name (+)
 15   order by real_name, real_dt
 16  /

REAL_NAME  REAL_DT   NVL(AMT,0)    CUM_AMT
---------- --------- ---------- ----------
sue        01-JAN-02          0          0
sue        01-FEB-02          0          0
sue        01-MAR-02       1231       1231
sue        01-APR-02       2321       3552
sue        01-MAY-02          0       3552
sue        01-JUN-02          0       3552
sue        01-JUL-02          0       3552
sue        01-AUG-02          0       3552
sue        01-SEP-02       3122       6674
sue        01-OCT-02         59       6733
sue        01-NOV-02          0       6733
sue        01-DEC-02          0       6733
tom        01-JAN-02        100        100
tom        01-FEB-02        200        300
tom        01-MAR-02          0        300
tom        01-APR-02        300        600
tom        01-MAY-02          0        600
tom        01-JUN-02         50        650
tom        01-JUL-02          0        650
tom        01-AUG-02          0        650
tom        01-SEP-02          0        650
tom        01-OCT-02          0        650
tom        01-NOV-02          0        650
tom        01-DEC-02          0        650

24 rows selected.

Would do that -- suggest you run each inline view so you can understand what
they are -- then you'll see B is just the set of all customers by month -- you
outer join to that to pick up the amts and get your running total...

Review &
      Followup

      Rating: 5
Perfect as
      always!  March 30, 2003
Reviewer:  Richard
      Smith  from Bensenville, IL
Exactly what I needed.

      Rating: 5
Fill missing
      data with values of previous row   August 20, 2003

Reviewer:  Edgar Chupit  from Latvia
Hello Tom!

Once again great article, but can we extend a little bit. What if I need to fill
"missing data" with values from previous (first not missing) row?

Suppose I have:

SQL> create table t2 (val number, indx number);

Table created

SQL> insert into t2 values ( 1, 1 );

1 row inserted

SQL> insert into t2 values ( 13, 10 );

1 row inserted

SQL> insert into t2 values ( 12, 14 );

1 row inserted

SQL> insert into t2 values ( 11, 20 );

1 row inserted

Now we have a select with "missing data":

SQL> with all_values as (
  2       select rownum as num
  3       from all_objects
  4       where rownum < 30 )
  5  select t2.*, av.num
  6  from t2, all_values av
  7  where ( t2.indx(+) = av.num )
  8  order by av.num
  9  /

VAL INDX NUM
--- ---- ---
  1    1   1
           2
           3
           4
           5
           6
           7
           8
           9
 13   10  10
          11
          12
          13
 12   14  14
          15
          16
          17
          18
          19
 11   20  20
          21
          22
          23
          24
          25
          26
          27
          28
          29

29 rows selected

and we want to fill "missing data" with values of last row that had data, in
pl/sql this can be done easily like this:

SQL> declare
  2    cursor c is
  3       with all_values as (
  4          select rownum as num
  5          from all_objects
  6          where rownum < 30 )
  7       select val, indx, av.num
  8       from t2, all_values av
  9       where ( t2.indx(+) = av.num )
 10       order by av.num;
 11    val number;
 12    indx number;
 13  begin
 14    dbms_output.put_line( 'VAL   INDX   NUM  ' );
 15    dbms_output.put_line( '----- ------ -----' );
 16    for r in c loop
 17        if( r.val is not null ) then
 18            val := r.val;
 19            indx := r.indx;
 20        end if;
 21        dbms_output.put_line( to_char(nvl(r.val,val),'00')||'
'||to_char(nvl(r.indx,indx),'00')||'   '||to_char(r.num,'00') );
 22    end loop;
 23  end;
 24  /
VAL   INDX   NUM
----- ------ -----
 01    01    01
 01    01    02
 01    01    03
 01    01    04
 01    01    05
 01    01    06
 01    01    07
 01    01    08
 01    01    09
 13    10    10
 13    10    11
 13    10    12
 13    10    13
 12    14    14
 12    14    15
 12    14    16
 12    14    17
 12    14    18
 12    14    19
 11    20    20
 11    20    21
 11    20    22
 11    20    23
 11    20    24
 11    20    25
 11    20    26
 11    20    27
 11    20    28
 11    20    29

PL/SQL procedure successfully completed

Can  this  be done in pure SQL (so we can add further data processing) or this
kinds of data manipulations is possible only on pl/sql?

Thank you!

          Followup:

qed :)

ops$tkyte@ORA920> with all_values as (
  2       select rownum as num
  3       from all_objects
  4       where rownum < 30 )
  5  select t2.*, av.num,
  6       substr( max( decode( t2.val, null, null,
                                 to_char(av.num,'fm00000000') || t2.val ))
  7                 over (order by av.num), 9 ) t2_val,
  8         substr( max( decode( t2.val, null, null,
                                 to_char(av.num,'fm00000000') || t2.indx))
  9                 over (order by av.num), 9 ) t2_indx
 10  from t2, all_values av
 11  where ( t2.indx(+) = av.num )
 12  order by av.num
 13  /

       VAL       INDX        NUM T2_VAL     T2_INDX
---------- ---------- ---------- ---------- ----------
         1          1          1 1          1
                               2 1          1
                               3 1          1
                               4 1          1
                               5 1          1
                               6 1          1
                               7 1          1
                               8 1          1
                               9 1          1
        13         10         10 13         10
                              11 13         10
                              12 13         10
                              13 13         10
        12         14         14 12         14
                              15 12         14
                              16 12         14
                              17 12         14
                              18 12         14
                              19 12         14
        11         20         20 11         20
                              21 11         20
                              22 11         20
                              23 11         20
                              24 11         20
                              25 11         20
                              26 11         20
                              27 11         20
                              28 11         20
                              29 11         20

29 rows selected.

      Rating: 5
similar
      approach  October 02, 2003
Reviewer:  Joe
       from PA
Tom,

This is a similar requirement, but easier.
I need to ensure that all 24 hours are included in a report, even when there is
no data for them.  I need to know the number of occurences in an hour for a
paritcular value.

create table master (pk number);
create table detail (fk number, value number);
insert into master values (1);
insert into master values (2);
insert into master values (3);
insert into master values (4);
insert into detail value (1, 10);
insert into detail value (1, 10);
insert into detail value (1, 10);
insert into detail value (1, 10);
insert into detail value (2, 10);

from SQL I can do this like:

SELECT master.pk, detail.event_count
FROM master,
   (SELECT detail.fk,count(*) event_count from detail
    WHERE value=:param
    GROUP by key
    ) detail
where master.key=detail.key(+);

Can this be done without the subquery?  Oracle Discoverer has some limitations
that make this impossible unless I can re-write it without a subquery.  (ie, I
think I have a legitimate reason for asking for something that would otherwise
not make sense).  I read enough of your stuff to understand the frustration with
"answer my question, but don't give me this answer" that you often get.

          Followup:

ops$tkyte@ORA920> variable param number
ops$tkyte@ORA920> exec :param := 10

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> SELECT master.pk, detail.event_count
  2  FROM master,
  3     (SELECT detail.fk,count(*) event_count from detail
  4      WHERE value=:param
  5      GROUP by fk
  6      ) detail
  7  where master.pk=detail.fk(+);

        PK EVENT_COUNT
---------- -----------
         1           4
         2           1
         3
         4

ops$tkyte@ORA920>
ops$tkyte@ORA920> select master.pk, count(detail.fk)
  2    from master, detail
  3   where :param = detail.value(+)
  4     and master.pk = detail.fk(+)
  5   group by master.pk;

        PK COUNT(DETAIL.FK)
---------- ----------------
         1                4
         2                1
         3                0
         4                0

ops$tkyte@ORA920>

October 02, 2003 Carlos -- Thanks for the question regarding
      "Questions about how to create users in PL/SQL and assign database roles",
      version 9i

      You Asked
Hello, I am building a form to create and maintain database users, so I need to
perform ddls in the database like alter user, create user, grant roles, privs
etc.

I can use forms_ddl built-in inside a program unit or I can create a database
package to call procedures.

1.- ¿ Which is better talking about performance ?

2.- ¿ How do I implement this ddl's (alter user, create user, etc) inside a
package procedure? (I read of doing with oracle supplied package dbms_sql but
don't know how to do it.)

3.- I will be keeping database/application users in a table, they will be
inserted from a Form, with login field as PK. When I insert a user in this
table, then I have to create it in the database.
¿ What is the best way to maintain consistency so I don't have users in my table
and due to some failure not having success while creating them in the database?

4.- I will be giving privileges to users with database roles, besides grant
"connect" and specific app roles, what roles should I grant to users. ¿ What
about "resource" role, what is this role for?

5.- What is the difference between "with admin option" and "with grant option".

I expect a detailed answer because I am new in this kind of proceses with forms
and pl/sql.

Thank you.

Carlos

      and we said...
1) it doesn't really matter in this case.  you are doing DDL, either or will
suffice.

2)
begin
  EXECUTE IMMEDIATE 'create user foo identified by bar';
end;

3) You would use DBMS_JOB (well, at least I would).  I would add a column to my
"user" table called job_id.  The trigger would look like this:

begin
   dbms_job.submit( :new.job_id, 'procedure_to_create_user(JOB);' );
end;

that will fill in the job_id for us.  Now, when you commit -- the system will
run your job SHORTLY after.  Your procedure will get the job id and can simply:

  select * into l_record from user_table where job_id = P_JOB_ID;

to retrieve the necessary information to create the user.

4) i would strongly suggest you do not use connect, resource, dba.  You would
create your OWN application role.  You would grant to it the MINIMUM set of
privs possible.  You would grant this role to your users.

Especially if you don't know what a role is for -- you should NOT be using it!!!

5)

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/statements_912a.htm#2083760
describes the "with admin option" -- what it conveys.  it applies to system
privs like "create table"

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/statements_912a.htm#2077700
describes the "with grant option".  It applies to OBJECT privs - like "select"

You will also want to read

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html
to learn why the OWNER of the package you are going to create will need the
grants that allow them to create users and such granted directly to them.
Review &
      Followup

      Rating: 3
Thank
      you  October 02, 2003
Reviewer:  Carlos
      Gongora  from Cochabamba, Bolivia
Thank you, I will read them in detail and prepare a review later.

      Rating:
      4
Feedback  October 02, 2003
Reviewer:
      Carlos Gongora  from Cochabamba, Bolivia
>1) it doesn't really matter in this case.  you are doing >DDL, either or will
suffice.

-o- I was asking this because I read that while executing forms_ddl I will also
perform an implicit commit in the databse This maybe will confuse the Form...
So  I guess that to avoid this I will better create and execute procedures as
you stated  in point 2... using execute immmediate.."

>2)
>begin
>  EXECUTE IMMEDIATE 'create user foo identified by bar';
>end;

-o-I think I will use this procedures instead of forms_ddl to create users and
grant roles.

>3) You would use DBMS_JOB (well, at least I would).  I >would add a column to
my "user" table called job_id.  The >trigger would look like >this:
>
>begin
>   dbms_job.submit( :new.job_id, 'procedure_to_create_user
>(JOB);' );
>end;

>that will fill in the job_id for us.  Now, when you >commit -- the system will
run your job SHORTLY after.  >Your procedure will get the job id and can simply:

-o- Sorry, but I don't get your point here. If I execute procedures as
recomended in point 2, to create users and assign grants then what is this
dbms_job.submit used for?
Please elaborate...

> select * into l_record from user_table where job_id = >P_JOB_ID;
>
>to retrieve the necessary information to create the user.

¿¿What information do I need to create the user?? Sorry I don't get this...
Please explain.

>4) I would strongly suggest you do not use connect, >resource, dba.  You would
create your OWN application >role.  You would grant to it the MINIMUM set of
>privs possible.  You would grant this role to your users.

OK, thank you. I just read oracle docs and I see that the only priv. (besides my
specific app roles) to grant to a user is Create Session. Please tell me if I am
correct. My users will use only Oracle Forms to log in the Database
to select/execute database tables, views and procedures.

Please do not get mad with this two Aditional questions :

1.-From the point of view of creating a database user. I have read that you have
to do it with user SYS. Is this a requirement or is it enough that the user has
Create Users privileged assigned.

2.-¿What is the difference between sys and system default Oracle users?

THANK YOU FOR ALL...

          Followup:

Doing ddl will do an implicit commit period.

dbms_job is used to run the DDL in the background.  DDL commits -- you want to
make sure that if the row goes in AND gets commited -- then the user is created.
 using dbms_job is a way to do this.  Otherwise, you cannot maintain
transactional integrity.

your procedure that creates a user -- it needs INPUTS, the user information (so
it knows for example what the username to be is)

Create session is all you want.

(do i get mad?)

you do not have to do it with sys

you should in fact not use sys for anything (in 9i, very hard to even log in as
sys!)

sys and system are "special", they are our internal accounts.  You shouldn't use
them.  you should use "as sysdba" or "as sysoper" to startup and shutdown - but
using your own username/password.

      Rating: 5
Simple and
      direct  October 02, 2003
Reviewer:  Carlos
      Gongora  from Cochabamba, Bolivia
Thanks Thomas. I am learnign lots... Don't think all I do is ask, I read lots
and lots... I got  a copy of Expert One-On-One...

If books could handle some questions, as simple as why is this or why not...
Then learning will be easier and faster.

The process of learning is like the process of getting rich, the more money you
have the more money you earn...

Thank you very much.

Carlos Gongora

October 02, 2003 Johan -- Thanks for the question regarding
      "Oracle Label Security vs Fine Grained Access Control", version 8.1.7
      & later

      You Asked
Tom,

I recently read an article on Oracle Label Security and from the relatively
limited information provided in the article, Label Security seem very similar to
Fine Grained Access Control, maybe just a bit more limited than FGAC.

Why do both these technologies exist and how would one choose between the two
when it comes to the implementation of row-level security in an application ?

      and we said...
Label Security is actually built on top of FGAC.

Label Security is an "out of the box" experience.  It is already implemented --
you just use it.

FGAC is a toolkit, you could use it to build Label Security (and our developers
did just that)

You might browse this chapter:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/network.920/a96578/intro.htm#1008265
to get some more details on it.
Review &
      Followup

      Rating:
      5
  October 02, 2003
Reviewer:
      A reader
Isn't label security the new name for what used to be known as Trusted Oracle in
Oracle 7? Was Trusted Oracle implementation based on the RLS model(which I think
is an 8i feature) or was it different? Essentially could you please tell what
are the differences between the Trusted Oracle implementation and Label
Security? Thanks.

          Followup:

Trusted Oracle (i cut my teeth on that product way way way back when -- i was
one of the first customers) is much "bigger" then Label Security.

Trusted Oracle was a b1 multi-level secure database that relied on a b1
multi-level secure operating system.  It was "big time".  it used the OS to
enforce the "row level security"

Label Security has many of the salient features of Trusted -- but is not so
onerous.  No trusted OS is needed, but you have some (not all) of the qualities
of that b1 multi-level secure OS (so secure it was, that people couldn't really
'use' it day to day)

Trusted Oracle is "dead", Label Security is what we are producing now.

      Rating: 3
Memories of
      Trusted Oracle...  October 02, 2003
Reviewer:
      Mark Wooldridge  from Vienna, VA.
I also was thrown into Trusted Oracle back in 95.  I had alot of interesting
days working with the product and integrated webservers (back before they were
application servers) onto trusted solaris, dec mls, sco's mls and hp cmw.

Replication was very intersting when Trusted Oracle was involved.

OLS, previously Military security is also an interesting product.  The key piece
missing is the multilevel listener to allow OLS to work on Trusted Solaris.

OLS is a good product but is designed after the Trusted Oracle model with
levels, and compartments (also now inverse compartments as a result of my beta
testing), and a new feature groups.  If your data fits this model, then OLS is
the product for you.

October 02, 2003 Doug -- Thanks for the question regarding "How
      about the technology underneath asktom?", version 8.1.7

      You Asked
Hey Tom.. maybe this is off-topic.  I thought it might be fun for you to share
some of the technology underneath your own site.  Are you using 9i?  Are our
questions and your answers CLOBS?  If I'd built a website on top of Oracle I'd
have better questions.  Your using intermedia.. any other cartridges?  Seperate
servers?  Using 9ias?

Simply curiousity...

      and we said...
See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:350017644883
for how the questions are organized pretty much.

As of November 17, 2001 we use:

Oracle 8.1.7.2 EE.
interMedia for the text search.
Every page is generated out of the database using PL/SQL.
We use iAS and mod_plsql for that.

A homegrown development environment that lets us put together plsql apps fast (a
framework we built for page nagivation and look and feel).

Materialized views (this is for my benefit, so I can rapidly see how many
questions are asked, waiting, deferred, answered, etc).

Thats pretty much it -- its a very simple application actually.  Soon it will be
9i -- when we update the existing instance.  Just like everyone else, our
production instances are upgraded after a bit of testing and such.  We host many
things on this one machine (eg: if you nslookup www.promedmail.org, you'll find
its the same as osi.oracle.com = asktom.oracle.com = store.thinknic.com =
academy.oracleicenter.com = oai.oracle.com = govt.oracle.com = www.isid.org.  We
host a couple of sites on this one box which happens to be a 4 cpu Sparc e450
with 2 gig of ram and 100gig of storage.  Not bad for such a small box -- and
everything is in PLSQL or uses Portal or iStore (apps))

Review &
      Followup

      Rating: 5
That's
      interesting  November 17, 2001
Reviewer:
      Doug  from CT, USA
Thanks Tom that's interesting, I just got curious.  How big is your db?

          Followup:

This is just the "asktom" part.

Its about 400meg in data + index.

There are (as of Nov 18th) 12,773 Q&As.

The biggest Q&A is 33k, the average is about 1.5k.

I've gotten 3,657 "reviews/followups" where the biggest was 10k and the avg is
263 bytes.

I've followed up 927 of them (well, thats actually hard to tell cause we
implemented the actual "followup" function well after the review function, I
used to just followup in the question itself but that got confusing after a
bit).  My biggest followup was 19k and the avg is 777bytes (guess I'm much
longer winded then you guys are ;)

It started on Jan 2000, so this represents almost 2 years of data collection.
asktom recieves about 20,000 page views (not hits, pages generated) a day
mon-fri (sat and sun drop to 4,000 and 2,000 respectively).  I see about 1,300
different IP addresses on any given day (firewalls mask many people as "one" so
its somewhat higher)

      Rating: 3
some question
      about askTom  November 19, 2001
Reviewer:
      Michael  from HK
Tom,

I am always curious about the hosting of "askTom".

Are you paid for that "job"?
How many people working on it?
Is there anyone helping you answering question?

Indeed, I do thank for your kindly help and admire your knowledge on Oracle!

          Followup:

In as much as Oracle provides me my paycheck, yes, I am compensated.

I used to write the software that runs it, someone else maintains that now on a
very part time basis (not much needs to be done to keep it going).

I answer most all of the questions, I send some out, but the vast majority (well
over 95%) are me.

      Rating: 5
Good
      Job  November 19, 2001
Reviewer:  srimal
       from Trenton, USA
Hello Mr Tom,

This is a wonderful and a very useful site for Oracle questions and the answers
are always what I am looking for..

Thank you for all your help and a great support!

      Rating:
      4
  November 19, 2001
Reviewer:
      A reader
Thanks Tom ! I guess, everybody might be wondering about the environment behind
asktom.oracle.com !

Are you addicted to this, like some folks are to email or discussion boards ?
Like, do you keep checking for new questions all the time; say, on weekends &
holidays ? Or like, check what are the latest questions whenever & wherever you
get an internet access ???

          Followup:

Addicted -- no, i wouldn't say that (thanksgiving is coming up for me here --
I'm going to shutdown for a couple of days).

I tend to answer in batches -- morning, afternoon, evening.  I strictly control
the number of questions....

      Rating: 5
Have a nice
      break  November 19, 2001
Reviewer:  REader
       from USA
Hi, Tom,

I strongly agree with you ---- have a nice break and happy thanksgiving day.

I'll say thanks----Tom for your all supportive and profesional answers.

Reader

      Rating: 5
virtual
      teleconf with u.  November 20, 2001
Reviewer:
      Ramasubbu Muthuraj  from Silicon Valley of India
Hi Tom,
      Especially regarding this question and answer,am feeling virtually
watching some interview on television giving by u.
great!!!knowledge sharing!!!.
Thanks tom.
reg
muthu

      Rating: 5
where to start
      with 9iAS  November 20, 2001
Reviewer:  Igor
       from France
For someone who should start with 9iAS, where is possible to
find simple examples to start ?

          Followup:

See

http://otn.oracle.com/sample_code/content.html
for some.

      Rating: 4
about f?p= ...
      (in the asktom URL)   August 26, 2002
Reviewer:
      Vikas Sharma  from Delhi India
Hi Tom ,

Can you please tell me in url
…ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1995355238285 what is this f. I believe
you r calling  a procedure f  and passing parameters in p= 4950:8:::::. Are you
dynamically executing the others packaged procedures or function depending upon
the parameter you passed by using execute immediate.

I know you are not in favor of dynamic executes whenever somthing can be done by
statically. But if you are doing it that probably for the security reasons. I am
very curious to know about  f?p=

Thanks

Regards,
Vikas Sharma

          Followup:

No, it is not dynamically calling other procedures.  To see and understand the
software behind asktom read:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3489618933902
It is an application develop environment we've been working on for a bit and
have made "public".  There is a link to OTN there that takes you to the software
in question.

F is a static function -- it is our "runtime engine" (and that engine just
happens to live in the database, written in plsql)

      Rating: 4
Realsing
      features Vs Using features  August 26, 2002

Reviewer:  A reader
Tom,
Beyond doubt, Oracle is leader in RDBMS technology to release the new features.
But these features are of no use, if people are not using it. Most of the new
features are better way of doing things; and sometimes it takes time to change
the mind set of people, whenever there is new feature usage idea proposed in
developing applications. There is always a more confidence in adopting the
previous WORKED approach for doing things which has been done in the past, BUT
this may not be the best approach today. Atleast for technology lovers people,
this site is one of the aspiring site with strong encouragement, and definitely
helps a lot in using the new features. The information is trustworthy, it is not
only the questions which are of high standard but the approach for answering
questions is beyond comparison. I don't think there is even a single *BOOK* can
replace this. The response time for getting reply is great. When a site has
become so rich in information, and travelled so far, and the information is
being applied in the real world, I DON’T THINK there is a time for thanksgiving
by Tom. More appropriately, it is the time for OTHER SOFTWARE VENDORS to adopt
this model to help their customers/users.
Once again thanks Tom!

      Rating: 3
OTN
      Link  August 27, 2002
Reviewer:  Vikas
      Sharma  from Delhi India
Hi tom,

--There is a link to OTN there that takes you to the software in question.  --

I shall be grateful if you provide me the OTN link. I searched OTN but could not
get.

There was link (interested in software behind asktom) on you site some time back
which is not seen now is that is the same link you are talking about.

many thanks in advance.

Vikas

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3489618933902

      Rating: 3
how to call
      funtion from mod_plsql  September 07, 2002

Reviewer:  Vikas Sharma  from Delhi India
Hi tom,

This is regarding a review post by you. you said :
"F is a static function -- it is our "runtime engine" (and that engine just
appens to live in the database, written in plsql "

If 'F' is a function then can you please tell me how to call functions from
mod_pl/sql. for eg
http://host/pls/dad/function_name
 does not work. AS far as i know we cam only call
package.procedure or standalone procedures.

I shall be very thankful if you can elaborate your review ("F is a static
function -- it is our "runtime engine" (and that engine just appens to live in
the database, written in plsql " )  more.

Thanks,

Regards,

Vikas Sharma

          Followup:

sorry, F is a static "procedure".

You cannot call a function from mod_plsql (nor would it make sense -- what would
happen with the return value?).

      Rating: 2
how to call
      funtion from mod_plsql...Thanks  September 07, 2002

Reviewer:  vikas Sharma  from Delhi India
Thanks..

      Rating: 3
page layout
      change  February 20, 2003
Reviewer:  robert
       from pa
hi, tom,

Was it your idea to switch the message/annoucement section to the bottom ? why
the change ? because of user-feedback or something ?

          Followup:

user feedback -- since I moved the search to the home page, it didn't look right
to have it(search) in the middle.

      Rating: 3
What's that
      tiny little....  February 21, 2003
Reviewer:
      robert  from PA
What's that tiny little number like "1.42" at the far bottom left corner of the
(home) page ? it changes with each refresh.

          Followup:

it is how long it took to generate the page -- I am working with the marvel team
to get that back to to like 0.20 ;)

tiny "feature" in their new reporting engine we are working on...

      Rating: 4
Feature: Your
      Questions (a suggestion)  February 22, 2003

Reviewer:  Robert  from PA
This new feature is great ! Let's one know how much
one's been bugging you :) or time to bug you again....

Now the result list is not sorted in some way at all, is it ?
It'd be nice to have it sorted by the date column.

          Followup:

Here is the query we run:

select lnk, how, description, to_char(status_ts,'Dy Mon dd, yyyy' )
  from (
select '<a href="f?p=4950:61:'||:FLOW_SESSION||'::::P61_ID:'||a.id||'">' ||
a.subject ||'</a>' lnk,
       'posted by you' how,
       description,
       status_ts
  from wwc_ask_submitted_questions$ a, wwc_ask_posting_status$ b
 where a.status = b.id
   and a.email = lower(:P60_EMAIL)
 union all
select '<a href="f?p=4950:61:'||:FLOW_SESSION||'::::P61_ID:'||a.id||'">' ||
a.subject ||'</a>' lnk,
       'reviewed by you' how,
       description,
       status_ts
  from wwc_ask_submitted_questions$ a, wwc_ask_posting_status$ b
 where a.status = b.id
   and displayid in ( select displayid from wwc_ask_question_reviews$ where
email = lower(:P60_EMAIL) )
       )
 order by status_ts desc

so it is sorted by the date associated with it (date of last modification -- not
the SUBMITTED date - the date of last mod)

      Rating: 4
Yes, YOUR
      QUESTIONS list is indeed sorted  February 22, 2003

Reviewer:  robert  from PA
Woops, never my my "suggestion", the list is indeed sorted
by date desc.
It's just the text got wrapped...

      Rating:
      5
  February 22, 2003
Reviewer:  Mike
       from TX
"it is how long it took to generate the page -- I am working with the marvel
team
    to get that back to to like 0.20 ;)"

it has been always in 1.2 for me, and accessing ASKTOM.COM

I use the cable modem to access internet.

          Followup:

we just did an upgrade, it went from 0.2 to 1.2 due to a issue with their
reporting engine, instead of just getting the first 10, they are getting the
first 500 rows and just displaying the first 10.  they are working on it.

      Rating:
      5
  February 23, 2003
Reviewer:  Mike
       from TX
"..wwc_ask_submitted_questions$ a, wwc_ask_posting_status$.."

are these _$ tables/views your custom stuffers or from some Oracle apss/9ias...?

see we become nosy...

          Followup:

they are mine, part of "ask tom"

      Rating: 5
_$ for
      ?  February 23, 2003
Reviewer:  Mike
       from TX
In your convention, _$ is a table or view? or might have some other meaning?

          Followup:

the guys working on it use $ at the end of every tablename... (or view as a view
is really just a table as far as anyone should be concerned)

      Rating: 3
Search Results
      of Ask Tom now  February 23, 2003
Reviewer:
      Vikas Sharma  from Delhi India
Hi Tom,

Since when the new look has been introduced. The search on you web site are
giving the exact result found. So your team must be doing must be doing select
count(*) from... to get the exact no. of rows then dividing it into the paging
set of 10 and then select for getting the actual rows using pg_min_row=11,
&pg_max_rows=10, pg_rows_fetched=10

I know you strong beliver that one should NOT do select count(*) from ... just
for this reason. As earlier you use to do it with the intermedia's
approximations. May i have your comment on this? or this is the viewer request
to show the excat no. of result.

          Followup:

Yes, that is a BUG they put into the software without me knowing about it --
there is an OFFICIAL bug filed against this.  It is KILLING my performance.  I'm
pretty upset about it -- they are fixing it and adding the "tom pagination"
feature back in.

the "feature" that is killing my performance, limiting my scalability, slowing
everything down -- it is going away....

      Rating:
      4
Search  February 23, 2003
Reviewer:
      Vikas Sharma  from Delhi India
Hi Tom,

Thanks for immediate reply.

I was sure that you would not accept this bug.

I am also the fan of "tom pagination" methodology on which i already had
discussion with you in a thread and implemented it in one of my web application.

      Rating: 3
Pointing at
      your book GIF...  February 24, 2003
Reviewer:
      Robert  from PA
It says "Arrived in June..."...just splitting hair....

      Rating:
      5
  March 12, 2003
Reviewer:  Mike
       from TX
"
it is how long it took to generate the page -- I am working with the marvel team

to get that back to to like 0.20 ;)

tiny "feature" in their new reporting engine we are working on...
"
It has been improved from 1.2 to 0.6
How your team did that? Please just outline the methods/'tricks' used to get
what's now

many thanks

          Followup:

It is between .2 and .3.

I fixed the pagination (i have a message saying ignore that "1-10 of 11" thing,
that is going away.

      Rating: 2
Query from
      Internet Search   March 14, 2003
Reviewer:
      Nurlan  from Kazakhstan
Hello again Tom,
Just wondered why your answer pages are not available from internet search
services? Like yahoo or google? I do not remember that I had a hit to your site
from querying these services,

          Followup:

look at the URLS -- every page is a new experience.  they cannot walk my site.
i turn them away.

      Rating:
      3
ORA-0602  March 14, 2003
Reviewer:
      Eddie
report error:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small

Got this error when first logged on to asktom.oracle.com
The search feature did not work either-giving the same
error.
So I passed :7: inplace of :1: in the url and got the
listing of question. There on Search worked as well.
Some maint. work going on?

      Rating: 3
Did you have to
      flush your shared pool today?  March 14, 2003

Reviewer:  Tianhua Wu  from Atlanta, GA
I saw ora-6502 this morning from this site.  I have very similar problem in a
8.1.7.3 database. We had to flush the shared pool when this happens.  Is this
the same problem?

          Followup:

No, not at all -- it was a self induced "programmer made a change and didn't
check his work" error.  I was away for the day and he never checked his "fix"

If you are getting a 6502 -- unless you are hitting a bug (which I've not seen),
flushing the shared pool would not fix it.  Either you are experiencing false
causality (the effect of the 6502 going away is unrelated to the flush shared
pool -- it is just a coincidence) or you have hit a bug and opening a tar with
support would be called for.

      Rating: 5
Ask Tom vs
      search engines  March 14, 2003
Reviewer:
      A reader
>look at the URLS -- every page is a new experience.  they
>cannot walk my site.
>i turn them away.

Could you explain that in more detail ? What do you mean by "experience" ? I
have always been curious how these search engines work. How do you turn them
away ? Thanks !

          Followup:

Look at my urls -- you have a session in there.  even if they walked the site
(which I don't permit, I don't allow web wackers either -- I turn them away, it
is based on user agents and in extreme cases where people persist, I'll block an
ip address) it wouldn't be useful as the page is a one time experience.  it is
for your session.

most intelligent indexes stop all by themselves right there.

      Rating: 5
PL/SQL web
      toolkit  April 18, 2003
Reviewer:  Robert Xuequn
      Xu
It's a nice toolkit for developing web enabled database applications, and I can
see that this site is a live example. However, there are two questions (or
concerns):

1) Would you rather use straight hpt.print() to print out plain html, or use
those htp API calls? Which way is more efficient? To me, those htp APIs
(htp.title, htp.htmlopen, etc...) are just re-inventing the wheel. If one can
just htp.print anything, way bother using those API calls? There must be a
reason for their existence. What do you use inside asktom site?

2) Is there an "API" (a really useful one) to generate web graphics (dynamic JPG
or PNG diagrams)? I know some Perl modules can do that - generate diagrams on
the fly in JPG format, based on data retrieved from the database. I wonder if
the PL/SQL web toolkit has the same capability.

Thanks!

          Followup:

1) why code in C when you can code in assembler?  I mean, C is just an
abstraction, an inefficient layer on top of assembler.  Going further -- isn't
assembler just a layer on object code?  All we need therefore is a binary editor
and a good grasp of octal/hex and binary code and we'd be done :)

We use a mixture of both.  we use the htf functions alot in SQL -- select
htf.anchor( a, b ) .... for example.  Depends on

o which is most convienent
o which makes most sense.

htp.p() sometimes.
htp.bold() others
htp.pre() frequently (i'm a big fan of courier as you can tell!)

2) PLSQL no, its not really good for that.  Java -- yes and you can even load
them right into the database as a java stored procedure in many cases (not a
full AWT implementation in there but generally "full" enough)

      Rating:
      5
  May 10, 2003
Reviewer:
      A reader
"
it is how long it took to generate the page -- I am working with the marvel team

to get that back to to like 0.20 ;)

tiny "feature" in their new reporting engine we are working on...
"

You said: it is how long it took to generate the page.
I would know what sorts of work related to generating the page. It is most by
iAS or the backend DB, or both?

          Followup:

in my case, since the page is generated by plsql, 100% the DB.

      Rating:
      5
  May 11, 2003
Reviewer:  Sean
       from Atlanta
Thanks for answing.
I have just click asktom.oracle.com home, it was 0.45 shown at left of the
bottem. How much of it was database cost and how much was from the mid-tier iAS
server?
Thanks.

          Followup:

100% from the db -- the "start timing" is in plsql, the code to generate the
page is in plsql, the "end timing" is in plsql.

all the app server is doing is translating a URL into a stored procedure call,
calling the procedure and returning the page.

      Rating: 5
Great site but
      a bug  May 30, 2003
Reviewer:  Gabriel
       from Montreal, Canada
Hello Tom,

I used to serach sites in this oreder: tahiti.oracle, metalink, tom. After 1
year is tom, tahiti, metalink. All this to say this is an excellent site, thanks
for keeping up.
I think there is a bug on the web site. Often after I do a serach, it says we
found 90 results but after the first page of 10 results there is no link to the
next pages. The link does not reapear if I reexecute the query.....

Thanks

          Followup:

it says "approximately"

I use google as the "gold standard" for search engine excellence.  Anything they
do, I do.

They say approximately, so do I.

They are often way wrong, so am I.

I am all about performance, so are they.

I refuse to count exactly that there are "123 results" when in all probability
you'll look at 10 of them and no more.  What a waste that would be (i hate that
technique.  when I get a system, first thing i do is remove those exact counts
-- and the links to "every single silly page" in the result set and the link to
the last page).

See what google does and follow its lead.  They ain't broken over there.

goto google and search for oracle.

note that they DO NOT offer to take you to the last page -- or even page 22.
You have to work hard to get to page 20.  Now, just try to get to the last page
-- what, they actually have the nerve to cut you off at a reasonable limit? (you
cannot get past page 70!  and as you page through, they take longer for each
page)

Now, search for

"tom kyte oracle"

on google, they'll say "showing you 1-10 of about 18" (as of May 30, 2003
anyway).  But wait --- where is that NEXT button?  Hey, it isn't there.  Even if
you use the "show redundant ones", there are not 18 for that exact string.

No bug, working as designed ;)

      Rating: 4
utl_tcp and
      your page  August 21, 2003
Reviewer:  robert
       from PA
Tom, how can I get YOUR home page with this code below ?

DECLARE
    c utl_tcp.connection;
    ret_val pls_integer;
BEGIN
    p.set_linelen(200);
    p.set_prefix(NULL);
    c := utl_tcp.open_connection(remote_host => 'asktom.oracle.com',
                                 remote_port => 80,
                                 charset => 'US7ASCII'); -- open connection
    ret_val := utl_tcp.write_line(c, 'GET / HTTP/1.0'); -- send HTTP request
    ret_val := utl_tcp.write_line(c);
  BEGIN
    LOOP
       dbms_output.put_line(utl_tcp.get_line(c, TRUE)); -- read result
    END LOOP;
  EXCEPTION
  WHEN utl_tcp.end_of_input THEN
       NULL; -- end of input
  END;
utl_tcp.close_connection(c);
END;

      Rating: 4
How Asktom
      content is being output?  September 30, 2003

Reviewer:  Jap Boon Churn  from Malaysia
Hi Tom,

I understand that AskTom is built using PL/SQL.
So, when your engine output the content from your AskTom site, most of the
content is dynamic right?

I would like to know (if you don't mind), inside your stored procedures, do you
htp each and every line of html codes? or you have some techniques that can
segregate between static template html that can be pumped in with dynamic data
at run time?

I understand PLSQL Server Pages can do that, but i don't think AskTom is using
that.

So, would appreciate your sharing on how can i do that without sacrifying the
output time if i don't use htp.p to directly my web application content. Or
maybe you have a reusable component that we can use.

Please advise.
Thanks.

          Followup:

all of the content is dynamic, yes.

we use HTML DB, i don't really "htp.p" much of anything (but it does).

see

http://otn.oracle.com/ow2003/ow_all.html
there is a paper/presentation on html db there.

      Rating:
      4
link  September 30, 2003
Reviewer:
      Mirjana  from Barcelona
Hi Tom,
I followed this OW link to get HTML DB presentation,
but how come there's no "Whitepaper and/or Presentation"
link for "Accelerated Web Development with Oracle HTML DB" session?

          Followup:

i don't know, I don't manage otn content, you would have to ping them over
there.
-------------------------
Mirjana,

Try the OTN forum on HTML DB at:

http://forums.oracle.com/forums/forum.jsp?forum=137
If you can't find what you are looking for or need some information on HTML DB,
you can get in touch with me at (I know I'm going to regret this on such a broad
forum) tyler.muth@oracle.com

Thanks,
Tyler
HTML DB Development Team

      Rating: 5
Thank
      You.  September 30, 2003
Reviewer:  Sandeep s
      Bajwa  from Erie, PA USA
I really love this site.  Please do not make it a pay site or part of metalink.
Keep up the good work.

October 03, 2003 Renee -- Thanks for the question regarding
      "Wildcards with a between?", version 8i

      You Asked
Can wildcards be used with BETWEEN?  I think the answer is 'yes' but my manager
says "NO WAY!".  However, I ran the query below and it got results which seem
accurate - although I'd need exclude '1ST ST'. If this functionality of using
wildcards with a BETWEEN is valid, can you explain how Oracle evaluated the
data?

select distinct addr1_str_hse
from hse_base
where addr1_str_hse between '10%' and '20%'

addr1_str_hse
-------------
10TH ST
11TH ST
12TH ST
13TH ST
14TH ST
1ST ST

Thanks!

      and we said...
Your manager wins:

ops$tkyte@ORA8I.WORLD> create table t ( x varchar2(10) );

Table created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> insert into t values ( '10$' );

1 row created.

ops$tkyte@ORA8I.WORLD> insert into t values ( '10' );

1 row created.

ops$tkyte@ORA8I.WORLD> insert into t values ( '10TH ST' );

1 row created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select * from t where x between '10%' and '11%';

X
----------
10TH ST

It was done character by character.  A '%' is ascii 37.  A '$' is ascii 36.  $ <
'%' hence it was not in between.

The 10 by itself likewise is less than 10%

Now, 10T, since 'T' > '%' character wise -- was fund....
Review &
      Followup

      Rating: 3
Concatenating %
      wildcard symbol  October 02, 2003
Reviewer:
      Fred  from Irvine, CA
Tom,

I have a simple procedure where it takes the input search parameter (in this
case, a user enters a name in a field and clicks submit), and then adds a %
wildcard symbol at the end of the parameter.  It then performs a query, with the
resultset stored in a cursor.

It works, but it is too slow.  If I then hardcode the name and the % character
in the USING clause, it works much, much faster.  I don't understand why this is
happening.  Could you explain?

Here is a sample of the code:

  PROCEDURE retrieve_customer_selection  (p_snam IN VARCHAR2 := NULL, p_schema
IN VARCHAR2, p_cursor IN OUT retr_custselect_rc)
  IS

    l_district CHAR(3)      := RPAD(SUBSTR(p_schema, 9, 2), 3, ' '); -- Extract
the District portion from the string.
    l_snam     VARCHAR2(20) := UPPER(LTRIM(RTRIM(p_snam))) || '%';

  BEGIN

    IF p_ssn IS NOT NULL
       THEN

         OPEN   p_cursor

         FOR 'SELECT    ad.address_name
    name,
                        lcd.search_name
    search_name,
                        ad.street_address_1
    address,
                        ca.customer_account
    account,
                        LPAD(lcd.ssn_or_tax_id, 9, ''0'')
    tax_id,
                        DECODE(ca.account_status, ''AC'', ''ACTIVE'',
                                              ''AP'', ''ACT/PEND FINAL BILL'',
                                              ''DE'', ''**No Description** '',
                                              ''NF'', ''NONACT/ FINAL BILLED'',
                                              ''NT'', ''NONACTIVE/TERMINATED'',
                                              ''NW'', ''NONACTIVE/WRITE OFF'')
    description,
                        ca.bill_ar_ind
    ar_ind
               FROM   ' || SUBSTR(p_schema, 1, 8) ||'.leads_customer_pointer
    lcd,
                      ' || SUBSTR(p_schema, 1, 8) ||'.customer_accounts
    ca,
                      ' || SUBSTR(p_schema, 1, 8) ||'.addresses
    ad
               WHERE    lcd.search_name           LIKE :b1
               AND      lcd.district                 = :b2
               AND      ca.customer_account          = lcd.customer_account
               AND      ca.district                  = lcd.district
               AND      ad.customer_account          = lcd.customer_account
               AND      ad.district                  = lcd.district
               AND      ad.rec_seq_no                = 1
               AND      ad.address_name IS NOT NULL  '
               USING    l_snam,
                        l_district;
       END IF;

    EXCEPTION
      WHEN NO_DATA_FOUND THEN
        RAISE_APPLICATION_ERROR (-20100, 'Record with Search Parameter of
'||p_snam||' not found.');
  END;

Now if I replace:

               USING    l_snam,
                        l_district;

With:

               USING    'FRED%',
                        l_district;

It is much faster.

Thanks.

          Followup:

we need to see the query plans and tkprof output from the two different
approaches in order to say anything

      Rating: 3
Not much
      different  October 02, 2003
Reviewer:  fred
       from irvine, ca
I don't get it.  They appear identical to me.  The first one is without
hardcoding the % in the USING clause:

TKPROF: Release 9.2.0.4.0 - Production on Thu Oct 2 17:53:26 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Trace file: prsmprod_ora_2796.trc
Sort options: default

********************************************************************************
count    = number of times OCI procedure was executed
cpu      = cpu time in seconds executing
elapsed  = elapsed time in seconds executing
disk     = number of physical reads of buffers from disk
query    = number of buffers gotten for consistent read
current  = number of buffers gotten in current mode (usually for update)
rows     = number of rows processed by the fetch or execute call
********************************************************************************

alter session set sql_trace=true

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        1      0.00       0.00          0          0          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 63
********************************************************************************

begin
prism01.retrieve_customer_selection('fred');
end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.14          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.14          0          0          0           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 63
********************************************************************************

select user#
from
 sys.user$ where name = 'OUTLN'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          2          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          2          0           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 2)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID USER$ (cr=2 r=0 w=0 time=42 us)
      1   INDEX UNIQUE SCAN I_USER1 (cr=1 r=0 w=0 time=18 us)(object id 44)

********************************************************************************

SELECT    ad.address_name                                              name,
                        lcd.search_name
    search_name,
                        ad.street_address_1
    address,
                        ca.customer_account
    account,
                        LPAD(lcd.ssn_or_tax_id, 9, '0')
  tax_id,
                        DECODE(ca.account_status, 'AC', 'ACTIVE',
                                              'AP', 'ACT/PEND FINAL BILL',
                                              'DE', '**No Description** ',
                                              'NF', 'NONACT/ FINAL BILLED',
                                              'NT', 'NONACTIVE/TERMINATED',
                                              'NW', 'NONACTIVE/WRITE OFF')
description,
                        ca.bill_ar_ind
    ar_ind,
                        t1.record_count
               FROM   prismlao.leads_customer_pointer         lcd,
                      prismlao.customer_accounts              ca,
                      prismlao.addresses                      ad,
                       (SELECT  COUNT(*)          record_count
                        FROM  prismlao.leads_customer_pointer lcd,
                              prismlao.addresses              ad
                        WHERE   lcd.search_name           LIKE :b1
                        AND     lcd.district                 = :b2
                        AND     ad.customer_account          =
lcd.customer_account
                        AND     ad.district                  = lcd.district
                        AND     ad.rec_seq_no                = 1
                        AND     ad.address_name IS NOT NULL ) t1
               WHERE    lcd.search_name           LIKE :b1
               AND      lcd.district                 = :b2
               AND      ca.customer_account          = lcd.customer_account
               AND      ca.district                  = lcd.district
               AND      ad.customer_account          = lcd.customer_account
               AND      ad.district                  = lcd.district
               AND      ad.rec_seq_no                = 1
               AND      ad.address_name IS NOT NULL

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.02          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.02          0          0          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 76     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS BY INDEX ROWID CUSTOMER_ACCOUNTS (cr=0 r=0 w=0 time=0 us)
      0   NESTED LOOPS  (cr=0 r=0 w=0 time=0 us)
      0    NESTED LOOPS  (cr=0 r=0 w=0 time=0 us)
      0     MERGE JOIN CARTESIAN (cr=0 r=0 w=0 time=0 us)
      0      VIEW  (cr=0 r=0 w=0 time=0 us)
      0       SORT AGGREGATE (cr=0 r=0 w=0 time=0 us)
      0        TABLE ACCESS BY INDEX ROWID ADDRESSES (cr=0 r=0 w=0 time=0 us)
      0         NESTED LOOPS  (cr=0 r=0 w=0 time=0 us)
      0          TABLE ACCESS BY INDEX ROWID LEADS_CUSTOMER_POINTER (cr=0 r=0
w=0 time=0 us)
      0           INDEX RANGE SCAN LEADS_CUSTOMER_POINTER_IDX2 (cr=0 r=0 w=0
time=0 us)(object id 35240)
      0          INDEX RANGE SCAN ADDRESSES_IDX1 (cr=0 r=0 w=0 time=0 us)(object
id 32435)
      0      TABLE ACCESS BY INDEX ROWID LEADS_CUSTOMER_POINTER (cr=0 r=0 w=0
time=0 us)
      0       INDEX RANGE SCAN LEADS_CUSTOMER_POINTER_IDX2 (cr=0 r=0 w=0 time=0
us)(object id 35240)
      0     TABLE ACCESS BY INDEX ROWID ADDRESSES (cr=0 r=0 w=0 time=0 us)
      0      INDEX RANGE SCAN ADDRESSES_IDX1 (cr=0 r=0 w=0 time=0 us)(object id
32435)
      0    INDEX RANGE SCAN CUSTOMER_ACCOUNTS_IDX1 (cr=0 r=0 w=0 time=0
us)(object id 32432)

This next one is with the one that's hardcoded in the USING clause:

TKPROF: Release 9.2.0.4.0 - Production on Thu Oct 2 17:58:40 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Trace file: prsmprod_ora_2796.trc
Sort options: default

********************************************************************************
count    = number of times OCI procedure was executed
cpu      = cpu time in seconds executing
elapsed  = elapsed time in seconds executing
disk     = number of physical reads of buffers from disk
query    = number of buffers gotten for consistent read
current  = number of buffers gotten in current mode (usually for update)
rows     = number of rows processed by the fetch or execute call
********************************************************************************

alter session set sql_trace=true

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      3      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          0          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 63
********************************************************************************

begin
prism01.retrieve_customer_selection('fred');
end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      2      0.00       0.15          0          0          0           2
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.15          0          0          0           2

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 63
********************************************************************************

select user#
from
 sys.user$ where name = 'OUTLN'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          4          0           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        6      0.00       0.00          0          4          0           2

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 2)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID USER$ (cr=2 r=0 w=0 time=42 us)
      1   INDEX UNIQUE SCAN I_USER1 (cr=1 r=0 w=0 time=18 us)(object id 44)

********************************************************************************

SELECT    ad.address_name                                              name,
                        lcd.search_name
    search_name,
                        ad.street_address_1
    address,
                        ca.customer_account
    account,
                        LPAD(lcd.ssn_or_tax_id, 9, '0')
  tax_id,
                        DECODE(ca.account_status, 'AC', 'ACTIVE',
                                              'AP', 'ACT/PEND FINAL BILL',
                                              'DE', '**No Description** ',
                                              'NF', 'NONACT/ FINAL BILLED',
                                              'NT', 'NONACTIVE/TERMINATED',
                                              'NW', 'NONACTIVE/WRITE OFF')
description,
                        ca.bill_ar_ind
    ar_ind,
                        t1.record_count
               FROM   prismlao.leads_customer_pointer         lcd,
                      prismlao.customer_accounts              ca,
                      prismlao.addresses                      ad,
                       (SELECT  COUNT(*)          record_count
                        FROM  prismlao.leads_customer_pointer lcd,
                              prismlao.addresses              ad
                        WHERE   lcd.search_name           LIKE :b1
                        AND     lcd.district                 = :b2
                        AND     ad.customer_account          =
lcd.customer_account
                        AND     ad.district                  = lcd.district
                        AND     ad.rec_seq_no                = 1
                        AND     ad.address_name IS NOT NULL ) t1
               WHERE    lcd.search_name           LIKE :b1
               AND      lcd.district                 = :b2
               AND      ca.customer_account          = lcd.customer_account
               AND      ca.district                  = lcd.district
               AND      ad.customer_account          = lcd.customer_account
               AND      ad.district                  = lcd.district
               AND      ad.rec_seq_no                = 1
               AND      ad.address_name IS NOT NULL

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.02          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.02          0          0          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 76     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS BY INDEX ROWID CUSTOMER_ACCOUNTS (cr=0 r=0 w=0 time=0 us)
      0   NESTED LOOPS  (cr=0 r=0 w=0 time=0 us)
      0    NESTED LOOPS  (cr=0 r=0 w=0 time=0 us)
      0     MERGE JOIN CARTESIAN (cr=0 r=0 w=0 time=0 us)
      0      VIEW  (cr=0 r=0 w=0 time=0 us)
      0       SORT AGGREGATE (cr=0 r=0 w=0 time=0 us)
      0        TABLE ACCESS BY INDEX ROWID ADDRESSES (cr=0 r=0 w=0 time=0 us)
      0         NESTED LOOPS  (cr=0 r=0 w=0 time=0 us)
      0          TABLE ACCESS BY INDEX ROWID LEADS_CUSTOMER_POINTER (cr=0 r=0
w=0 time=0 us)
      0           INDEX RANGE SCAN LEADS_CUSTOMER_POINTER_IDX2 (cr=0 r=0 w=0
time=0 us)(object id 35240)
      0          INDEX RANGE SCAN ADDRESSES_IDX1 (cr=0 r=0 w=0 time=0 us)(object
id 32435)
      0      TABLE ACCESS BY INDEX ROWID LEADS_CUSTOMER_POINTER (cr=0 r=0 w=0
time=0 us)
      0       INDEX RANGE SCAN LEADS_CUSTOMER_POINTER_IDX2 (cr=0 r=0 w=0 time=0
us)(object id 35240)
      0     TABLE ACCESS BY INDEX ROWID ADDRESSES (cr=0 r=0 w=0 time=0 us)
      0      INDEX RANGE SCAN ADDRESSES_IDX1 (cr=0 r=0 w=0 time=0 us)(object id
32435)
      0    INDEX RANGE SCAN CUSTOMER_ACCOUNTS_IDX1 (cr=0 r=0 w=0 time=0
us)(object id 32432)

********************************************************************************

When I execute the one with the hardcoded value in the USING clause, it runs
much faster.  Try this simple test by creating a similar procedure and execute
from JSP to see if you will run into the same outcome.

Fred

          Followup:

there are no fetch lines?

you need to actually "use" the ref cursor.  you need to FETCH the entire result
set in order for this to be meaningful.

October 03, 2003 sam -- Thanks for the question regarding
      "dbms_space.free_space", version 8.1.5

      You Asked
   What is the use of this stored proc?
It tells you how many blocks have free space for updates, right
?
But it does not tell you how much free space in each block. We
can
get free space info. from dba_free_space.
Can you show how this proc can be of value to us?
   Another procs in this package is unused_space.
If it reports 35 blocks. Does it mean 35 blocks have never
had data in it ?
   It seems that it doesn't report any empty blocks above
the high water mark, does it?
   How can we make use of this info ? Can you give some examples
that we can use these procedures to help manage space.

Thanks, Tom.

      and we said...
Here is an example showing how to use dbms_space and how to
interpret the output.  Basically between the 2 procedures free
blocks and unused space, we'll be able to get:

Free Blocks......  Number of blocks on the freelist
Total Blocks.....  Total blocks allocated to the table
Total Bytes......  Total bytes allocated to the table
Unused Blocks....  Blocks that have never contained data
Unused Bytes.....  The above in bytes

It does not tell you how many blocks have free space for
updates.  We can tell you how many blocks are candidates for
INSERTS (they are on the freelist) and blocks on the freelist
have space for updates -- but -- there are blocks in the table
that have space for updates but that are not on the
freelist.  We cannot see them in any report.

It does not tell you how much space is free in each block
(nothing does, typically there are thousands or hundreds of
thousands of blocks in a table -- an analysis of the free space
block by block is not practical.  We can get an average free
space but not block by block).

This report does show blocks above the high water mark.  Unused
Blocks are exactly the block above the high water mark.

You can get most of the information supplied by this package by
analyzing the table and using queries against user_tables and
user_segments.  The freelist analysis is more detailed using
this package as you can look at each freelist independently.

Below is a procedure you can use to make using dbms_space a
little easier.  After that I create a table and show how space
is being used in it after various operations.  Comments in bold
explain the output.

ops$tkyte@8i> create or replace
  2  procedure show_space
  3  ( p_segname in varchar2,
  4    p_owner   in varchar2 default user,
  5    p_type    in varchar2 default 'TABLE' )
  6  as
  7      l_free_blks                 number;
  8
  9      l_total_blocks              number;
 10      l_total_bytes               number;
 11      l_unused_blocks             number;
 12      l_unused_bytes              number;
 13      l_LastUsedExtFileId         number;
 14      l_LastUsedExtBlockId        number;
 15      l_LAST_USED_BLOCK           number;
 16      procedure p( p_label in varchar2, p_num in number )
 17      is
 18      begin
 19          dbms_output.put_line( rpad(p_label,40,'.') ||
 20                                p_num );
 21      end;
 22  begin
 23      dbms_space.free_blocks
 24      ( segment_owner     => p_owner,
 25        segment_name      => p_segname,
 26        segment_type      => p_type,
 27        freelist_group_id => 0,
 28        free_blks         => l_free_blks );
 29
 30      dbms_space.unused_space
 31      ( segment_owner     => p_owner,
 32        segment_name      => p_segname,
 33        segment_type      => p_type,
 34        total_blocks      => l_total_blocks,
 35        total_bytes       => l_total_bytes,
 36        unused_blocks     => l_unused_blocks,
 37        unused_bytes      => l_unused_bytes,
 38        LAST_USED_EXTENT_FILE_ID => l_LastUsedExtFileId,
 39        LAST_USED_EXTENT_BLOCK_ID => l_LastUsedExtBlockId,
 40        LAST_USED_BLOCK => l_LAST_USED_BLOCK );
 41
 42      p( 'Free Blocks', l_free_blks );
 43      p( 'Total Blocks', l_total_blocks );
 44      p( 'Total Bytes', l_total_bytes );
 45      p( 'Unused Blocks', l_unused_blocks );
 46      p( 'Unused Bytes', l_unused_bytes );
 47      p( 'Last Used Ext FileId', l_LastUsedExtFileId );
 48      p( 'Last Used Ext BlockId', l_LastUsedExtBlockId );
 49      p( 'Last Used Block', l_LAST_USED_BLOCK );
 50  end;
 51  /

Procedure created.

ops$tkyte@8i>
ops$tkyte@8i> create table t ( x int, y char(2000) default '*' )
  2  storage ( initial 40k next 40k minextents 5 )
  3  tablespace system;

Table created.

I create a table with >1 extent to make it interesting.  I
also put a char(2000) in there to make the minimum row length be
2000 bytes (chars always take their max space right away).  This
just makes my rows "big"

ops$tkyte@8i> insert into t (x) values ( 1 );
1 row created.

I create one row just to use a little space in the table

ops$tkyte@8i> analyze table t compute statistics;
Table analyzed.

ops$tkyte@8i> compute sum of blocks on report
ops$tkyte@8i> break on report
ops$tkyte@8i> select extent_id, bytes, blocks
  2    from user_extents
  3   where segment_name = 'T'
  4     and segment_type = 'TABLE'
  5  /

 EXTENT_ID      BYTES     BLOCKS
---------- ---------- ----------
         2      40960          5
         3      81920         10
         4      57344          7
         0      40960          5
         1      40960          5
                      ----------
sum                           32

This shows that there are 32 blocks allocated in 5 extents to
this table (as expected)

ops$tkyte@8i> clear breaks
ops$tkyte@8i> select blocks, empty_blocks,
  2         avg_space, num_freelist_blocks
  3    from user_tables
  4   where table_name = 'T'
  5  /

    BLOCKS EMPTY_BLOCKS  AVG_SPACE NUM_FREELIST_BLOCKS
---------- ------------ ---------- -------------------
         1           30       6091                   1

Since I analyzed the table, I have acccess to the above
information.  You'll find that it maps exactly to the data
below.  There are a total of 32 blocks allocated to the table
(below and as confirmed by user_extents above).  There are 30
EMPTY_BLOCKS (above)/ UNUSED_BLOCKS (below).  These are blocks
above the HWM.   This leaves 2 blocks unaccounted for -- 1 block
has data in it, the other has the extent map for the table (the
first block of each table is used by the system itself).

ops$tkyte@8i> exec show_space( 'T' )
Free Blocks.............................1
Total Blocks............................32
Total Bytes.............................262144
Unused Blocks...........................30
Unused Bytes............................245760
Last Used Ext FileId....................1
Last Used Ext BlockId...................64816
Last Used Block.........................2

PL/SQL procedure successfully completed.

ops$tkyte@8i> insert into t (x)
  2  select rownum
  3    from all_users
  4   where rownum < 50
  5  /

49 rows created.

ops$tkyte@8i> commit;
Commit complete.

So now we have 50 rows with 2k each -- I'm using a 8k
blocksize so I expect about 3 rows / block.  That means about 18
blocks of data plus 1 for the system = about 19 blocks should be
"used" now.  Below I see that I have

o 3 blocks on the freelist.  they have more space for new
inserts (they have not hit their pctused yet and may be inserted
into)

o 12 unused blocks, leaving 20 blocks "used".  Since I have 3 on
the freelist -- we probably used a little more then the 18 for
data -- we used 19 for the 50 rows.  We have one for the system
-- all accounted for.

ops$tkyte@8i> exec show_space( 'T' )
Free Blocks.............................3
Total Blocks............................32
Total Bytes.............................262144
Unused Blocks...........................12
Unused Bytes............................98304
Last Used Ext FileId....................1
Last Used Ext BlockId...................64681
Last Used Block.........................5

PL/SQL procedure successfully completed.

ops$tkyte@8i> delete from t;
50 rows deleted.

ops$tkyte@8i> commit;
Commit complete.

Now we can see what a delete does to our utilization.

ops$tkyte@8i> exec show_space( 'T' )
Free Blocks.............................19
Total Blocks............................32
Total Bytes.............................262144
Unused Blocks...........................12
Unused Bytes............................98304
Last Used Ext FileId....................1
Last Used Ext BlockId...................64681
Last Used Block.........................5

PL/SQL procedure successfully completed.

The above shows that the delete simply put all of our blocks
on the free list.  We have 19 blocks on the free list + 12
unused blocks + 1 system block = 32 blocks.  All accounted for.
Note that the HWM stayed the same -- we don't have 31 unused
blocks -- we have 12 as before.  The HWM for a table will never
decrease unless we.....

ops$tkyte@8i> truncate table t;
Table truncated.

ops$tkyte@8i> exec show_space( 'T' )
Free Blocks.............................0
Total Blocks............................32
Total Bytes.............................262144
Unused Blocks...........................31
Unused Bytes............................253952
Last Used Ext FileId....................1
Last Used Ext BlockId...................64816
Last Used Block.........................1

PL/SQL procedure successfully completed.

Truncate it.  That puts all of the blocks below the HWM.  Now
we have 31 unused blocks + 1 system block = 32 blocks total.
None on the free list since none of them have any data.
Review &
      Followup

      Rating: 5
Table space
      used for a particular table  March 17, 2001

Reviewer:  spmurthy  from Singapore
Hi Tom,
                Thanks for your reply it is more useful to me to know the table
space.

Regards

      Rating: 5
dbms_space
      usage  March 22, 2001
Reviewer:  B.N.Sarma
       from USA
Tom,

Excellent , nothing less.

It would have been nice had you shown a select statement with autot on  doing
FTS upto hwm, even if you have delted all the rows and the same with truncate.
It would have become a good notes.

Your explanation with examples makes things very clear.

Why don't you write a book :-)

Regards
BN

      Rating:
      5
  March 23, 2001
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating:
      4
dbms_space.free_space  May 09, 2001

Reviewer:  D.C.L.  from Seattle, USA
Right on. Awesome grip of the subject matter.

      Rating:
      5
  May 10, 2001
Reviewer:  Vikram
       from Delhi, India
Excellent

      Rating:
      4
  August 21, 2001
Reviewer:
      k.v.s.Raju  from Sydney, Australia
Its excellent

      Rating: 5
dbms_space
        September 19, 2001
Reviewer:  Jim
       from MA
Very, Very Good!!!

      Rating: 3
Errors in
      show_space  September 20, 2001
Reviewer:
      A reader
Tom, tried using your show_space procedure. It compiled successfully but on
using it I get following errors:

SQL> exec show_space('T')
BEGIN show_space('T'); END;

*
ERROR at line 1:
ORA-00942: table or view does not exist
ORA-06512: at "SYS.DBMS_SPACE", line 55
ORA-06512: at "TOM.SHOW_SPACE", line 22
ORA-06512: at line 1

the table T exists under schema TOM and the show_space procedure was compiled
under user TOM.

DBMSUTIL/PRVTUTIL etc. have all been run. What am I missing ?

          Followup:

Something must be wrong -- give me a full example like this (that shows it
works)

ops$tkyte@ORA817DEV.US.ORACLE.COM> create user a identified by a;
User created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> grant create session, create procedure,
create table to a;
Grant succeeded.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter user a quota unlimited on users;
User altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> connect a/a
Connected.

a@ORA817DEV.US.ORACLE.COM> @showspace
Procedure created.

a@ORA817DEV.US.ORACLE.COM> create table t ( x int ) tablespace users;
Table created.

a@ORA817DEV.US.ORACLE.COM> exec show_space( 'T' )
PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set serveroutput on
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec show_space( 'T' );
Free Blocks.............................0
Total Blocks............................64
Total Bytes.............................524288
Unused Blocks...........................63
Unused Bytes............................516096
Last Used Ext FileId....................7
Last Used Ext BlockId...................4809
Last Used Block.........................1

PL/SQL procedure successfully completed.

so, do the whole create user/install the procedure/run the test and see if it
reproduces.  If not, either you were not logged in as TOM, TOM did not own T,
etc... (is T a view or synonym in your case??)

      Rating: 3
A little
      question  October 18, 2001
Reviewer:  Igor
       from France
I don't understand how you knew it would be 32 blocks
for one row of 2000 chars and number ?

          Followup:

the 5 extents were expected.  the 32 blocks just happened.

      Rating: 4
5 extents 40k
      each (8k block size) why not 5X5=25 blocks?  October 22,
      2001
Reviewer:  YK LIU  from CA, USA

      Rating: 5
Free
      Space  October 30, 2001
Reviewer:  an
       from DE
it's excellent!

      Rating: 5
ORA-14107:
      partition specification is required for a partitioned
      object  November 19, 2001
Reviewer:
      A reader
SQL> CREATE TABLE T (X VARCHAR2(20));

Table created.

SQL> EXEC SHOW_SPACE('T');

PL/SQL procedure successfully completed.

SQL> set serveroutput on
SQL>  EXEC SHOW_SPACE('T');
Free Blocks.............................0
Total Blocks............................10
Total Bytes.............................81920
Unused Blocks...........................9
Unused Bytes............................73728
Last Used Ext FileId....................5
Last Used Ext BlockId...................126659
Last Used Block.........................1

PL/SQL procedure successfully completed.

T_P -- is a partitioned table

SQL> EXEC SHOW_SPACE('T_P');
BEGIN SHOW_SPACE('T_P'); END;

*
ERROR at line 1:
ORA-14107: partition specification is required for a partitioned object
ORA-06512: at "SYS.DBMS_SPACE", line 55
ORA-06512: at "myschema.SHOW_SPACE", line 22
ORA-06512: at line 1

Why Iam I running into this error while trying to use show_space on a
partitioned table.

          Followup:

Ok, time for an update of this utility!  I had this sitting around already -- it
does two things

1) adds partition support
2) makes it so this runs in SQL for anything...  gives a result set instead of
printing. You can easily make it dbms_output.put_line if you want...

First we start with the types:

create or replace type show_space_type
as object
( owner                 varchar2(30),
  segment_name          varchar2(30),
  partition_name        varchar2(30),
  segment_type          varchar2(30),
  free_blocks           number,
  total_blocks          number,
  unused_blocks         number,
  last_used_ext_fileid  number,
  last_used_ext_blockid number,
  last_used_block       number
)
/
create or replace type show_space_table_type
as table of show_space_type
/

And then the function:

create or replace
function show_space_for
( p_segname   in varchar2,
  p_owner     in varchar2 default user,
  p_type      in varchar2 default 'TABLE',
  p_partition in varchar2 default NULL )
return show_space_table_type
authid CURRENT_USER
as
    pragma autonomous_transaction;
    type rc is ref cursor;
    l_cursor rc;

    l_free_blks                 number;
    l_total_blocks              number;
    l_total_bytes               number;
    l_unused_blocks             number;
    l_unused_bytes              number;
    l_LastUsedExtFileId         number;
    l_LastUsedExtBlockId        number;
    l_last_used_block           number;
    l_sql                       long;
    l_conj                      varchar2(7) default ' where ';
    l_data                      show_space_table_type :=
show_space_table_type();
    l_owner varchar2(30);
    l_segment_name varchar2(30);
    l_segment_type varchar2(30);
    l_partition_name varchar2(30);

    procedure add_predicate( p_name in varchar2, p_value in varchar2 )
    as
    begin
        if ( instr( p_value, '%' ) > 0 )
        then
            l_sql := l_sql || l_conj || p_name ||
                            ' like ''' || upper(p_value) || '''';
            l_conj := ' and ';
        elsif ( p_value is not null )
        then
            l_sql := l_sql || l_conj || p_name ||
                            ' = ''' || upper(p_value) || '''';
            l_conj := ' and ';
        end if;
    end;
begin
    l_sql := 'select owner, segment_name, segment_type, partition_name
                from dba_segments ';

    add_predicate( 'segment_name', p_segname );
    add_predicate( 'owner', p_owner );
    add_predicate( 'segment_type', p_type );
    add_predicate( 'partition', p_partition );

    execute immediate 'alter session set cursor_sharing=force';
    open l_cursor for l_sql;
    execute immediate 'alter session set cursor_sharing=exact';

    loop
        fetch l_cursor into l_owner, l_segment_name, l_segment_type,
l_partition_name;
        exit when l_cursor%notfound;
        begin
        dbms_space.free_blocks
        ( segment_owner     => l_owner,
          segment_name      => l_segment_name,
          segment_type      => l_segment_type,
          partition_name    => l_partition_name,
          freelist_group_id => 0,
          free_blks         => l_free_blks );

        dbms_space.unused_space
        ( segment_owner     => l_owner,
          segment_name      => l_segment_name,
          segment_type      => l_segment_type,
          partition_name    => l_partition_name,
          total_blocks      => l_total_blocks,
          total_bytes       => l_total_bytes,
          unused_blocks     => l_unused_blocks,
          unused_bytes      => l_unused_bytes,
          LAST_USED_EXTENT_FILE_ID => l_LastUsedExtFileId,
          LAST_USED_EXTENT_BLOCK_ID => l_LastUsedExtBlockId,
          LAST_USED_BLOCK => l_LAST_USED_BLOCK );

        l_data.extend;
        l_data(l_data.count) :=
               show_space_type( l_owner, l_segment_name, l_partition_name,
                  l_segment_type, l_free_blks, l_total_blocks, l_unused_blocks,
                  l_lastUsedExtFileId, l_LastUsedExtBlockId, l_last_used_block
);
        exception
            when others then null;
        end;
    end loop;
    close l_cursor;

    return l_data;
end;
/

Then we can:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select SEGMENT_NAME, PARTITION_NAME
SEGMENT_TYPE, FREE_BLOCKS,TOTAL_BLOCKS,UNUSED_BLOCKS
  2    from table( cast( show_space_for( 'HASHED',user,'%' ) as
show_space_table_type ) )
  3  /

SEGMENT_NA SEGMENT_TYPE      FREE_BLOCKS TOTAL_BLOCKS UNUSED_BLOCKS
---------- ----------------- ----------- ------------ -------------
HASHED     PART_2                      1           64            62
HASHED     PART_3                      1           64            62
HASHED     PART_4                      1           64            62
HASHED     PART_1                      1           64            62

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

And in 9i, we'd change the function to be pipelined:

ops$tkyte@ORA9I.WORLD> create or replace
  2  function show_space_for
  3  ( p_segname   in varchar2,
  4    p_owner     in varchar2 default user,
  5    p_type      in varchar2 default 'TABLE',
  6    p_partition in varchar2 default NULL )
  7  return show_space_table_type
  8  authid CURRENT_USER
  9  PIPELINED
 10  as
 11      pragma autonomous_transaction;
 12      type rc is ref cursor;
 13      l_cursor rc;
 14
 15      l_free_blks                 number;
 16      l_total_blocks              number;
 17      l_total_bytes               number;
 18      l_unused_blocks             number;
 19      l_unused_bytes              number;
 20      l_LastUsedExtFileId         number;
 21      l_LastUsedExtBlockId        number;
 22      l_last_used_block           number;
 23      l_sql                       long;
 24      l_conj                       varchar2(7) default ' where ';
 25      l_owner varchar2(30);
 26      l_segment_name varchar2(30);
 27      l_segment_type varchar2(30);
 28      l_partition_name varchar2(30);
 29
 30      procedure add_predicate( p_name in varchar2, p_value in varchar2 )
 31      as
 32      begin
 33          if ( instr( p_value, '%' ) > 0 )
 34          then
 35              l_sql := l_sql || l_conj || p_name || ' like ''' ||
upper(p_value) || '''';
 36              l_conj := ' and ';
 37          elsif ( p_value is not null )
 38          then
 39              l_sql := l_sql || l_conj || p_name || ' = ''' || upper(p_value)
|| '''';
 40              l_conj := ' and ';
 41          end if;
 42      end;
 43  begin
 44      l_sql := 'select owner, segment_name, segment_type, partition_name
 45                  from dba_segments ';
 46
 47      add_predicate( 'segment_name', p_segname );
 48      add_predicate( 'owner', p_owner );
 49      add_predicate( 'segment_type', p_type );
 50      add_predicate( 'partition', p_partition );
 51
 52      execute immediate 'alter session set cursor_sharing=force';
 53      open l_cursor for l_sql;
 54      execute immediate 'alter session set cursor_sharing=exact';
 55
 56      loop
 57          fetch l_cursor into l_owner, l_segment_name, l_segment_type,
l_partition_name;
 58                  dbms_output.put_line( l_segment_name || ',' ||
l_segment_type );
 59          exit when l_cursor%notfound;
 60          begin
 61          dbms_space.free_blocks
 62          ( segment_owner     => l_owner,
 63              segment_name      => l_segment_name,
 64              segment_type      => l_segment_type,
 65              partition_name    => l_partition_name,
 66              freelist_group_id => 0,
 67              free_blks         => l_free_blks );
 68
 69          dbms_space.unused_space
 70          ( segment_owner     => l_owner,
 71              segment_name      => l_segment_name,
 72              segment_type      => l_segment_type,
 73              partition_name    => l_partition_name,
 74              total_blocks      => l_total_blocks,
 75              total_bytes       => l_total_bytes,
 76              unused_blocks     => l_unused_blocks,
 77              unused_bytes      => l_unused_bytes,
 78              LAST_USED_EXTENT_FILE_ID => l_LastUsedExtFileId,
 79              LAST_USED_EXTENT_BLOCK_ID => l_LastUsedExtBlockId,
 80              LAST_USED_BLOCK => l_LAST_USED_BLOCK );
 81
 82          pipe row ( show_space_type( l_owner, l_segment_name,
l_partition_name,
 83                     l_segment_type, l_free_blks, l_total_blocks,
l_unused_blocks,
 84                      l_lastUsedExtFileId, l_LastUsedExtBlockId,
l_last_used_block ) );
 85          exception
 86              when others then null;
 87          end;
 88      end loop;
 89      close l_cursor;
 90
 91      return;
 92  end;
 93  /

Function created.

ops$tkyte@ORA9I.WORLD> set arraysize 1

ops$tkyte@ORA9I.WORLD> select SEGMENT_NAME, SEGMENT_TYPE,
FREE_BLOCKS,TOTAL_BLOCKS,UNUSED_BLOCKS
  2    from table( show_space_for( '%',user,'%' ) )
  3  /

SEGMENT_NAME    SEGMENT_TYPE      FREE_BLOCKS TOTAL_BLOCKS UNUSED_BLOCKS
--------------- ----------------- ----------- ------------ -------------
KEEP_SCN        TABLE                       1           64            62
EMPLOYEES       TABLE                       0           64            63
STINKY          TABLE                       0           64            63
OBJECT_TABLE    TABLE                       1           64            62
RUN_STATS       TABLE                       2           64            53
EMP             TABLE                       0           64            62
PROJ            TABLE                       0           64            62
X               TABLE                       1           64            62
WORDS           TABLE                       0           64            63
DOCS            TABLE                       0           64            63
KEYWORDS        TABLE                       0           64            63
DEPT            TABLE                       2           64            61
C               TABLE                       1           64            62
DSINVLINES      TABLE                       1           64            62
NUM_STR         TABLE                       1           64            23
T               TABLE                       4           64            28
T1              TABLE                       0           64            63
T2              TABLE                       0           64            63
BOM             TABLE                       1           64            62
PARTS           TABLE                       1           64            62
SYS_C001371     INDEX                       0           64            62
SYS_C001372     INDEX                       0           64            62
SYS_C001574     INDEX                       0           64            62
SYS_C001694     INDEX                       0           64            62
SYS_C001695     INDEX                       0           64            62
BOM_PK          INDEX                       0           64            62
PARTS_PK        INDEX                       0           64            62

27 rows selected.

      Rating:
      5
Reader  December 26, 2001
Reviewer:
      Reader  from USA
Tom,

Could you clarify, why the *first block* of a datafile
being used for the OS., 2nd for segment header, 3rd... for
data.

I created a table ts1 in tablespace ts1 , db_block_size 8k

Results:

SQL> set serveroutput on size 1000000
SQL> exec show_space('TS1','SYS','TABLE');
Free Blocks.............................1
Total Blocks............................2
Total Bytes.............................16384
Unused Blocks...........................0
Unused Bytes............................0
Last Used Ext FileId....................2
Last Used Ext BlockId...................2
Last Used Block.........................2

PL/SQL procedure successfully completed.

SQL> select lpad(file_name,40), bytes/1024/8 BLOCKS , TABLESPACE_NAME from
     dba_data_files where TABLESPACE_NAME = 'TS1';

LPAD(FILE_NAME,40)                           BLOCKS TABLESPACE_NAME
---------------------------------------- ---------- -----------------------
               /u07/oradata/iiim/ts1.dbf          3 TS1

SQL> select SEGMENT_NAME,TABLESPACE_NAME,BLOCK_ID,BLOCKS, extent_id from
     dba_extents where SEGMENT_NAME = 'TS1';

SEGM TABLESPACE_NAME                  BLOCK_ID     BLOCKS  EXTENT_ID
---- ------------------------------ ---------- ---------- ----------
TS1  TS1                                     2          2          0

SQL> select TABLE_NAME,INITIAL_EXTENT,NEXT_EXTENT from dba_tables
     where TABLE_NAME = 'TS1';

TABLE_NAME                     INITIAL_EXTENT NEXT_EXTENT
------------------------------ -------------- -----------
TS1                                     16384        8192

SQL> alter table ts1 allocate extent (size 8192K);
alter table ts1 allocate extent (size 8192K)
*
ERROR at line 1:
ORA-01653: unable to extend table SYS.TS1 by 1024 in tablespace TS1

The datafile has 3 blocks, segment has used 2 blocks (including segment header).
When I try to allocate one
extent = 1 block, get ora-1653

Thanks

          Followup:

I never said the first block would be used by the OS.  In any case, you are
asking for 8192k (8 MEG) of space, not 8k.

      Rating:
      5
Reader  December 26, 2001
Reviewer:
      Reader  from USA
Tom,

I am sorry, I did try 8k and got this ora-1653

SQL> alter table ts1 allocate extent (size 8k);
alter table ts1 allocate extent (size 8k)
*
ERROR at line 1:
ORA-01653: unable to extend table SYS.TS1 by 1 in tablespace TS1

I have found the 1st block seemed to have been used
by OS, not sure if this is platform specific (Silicon Graphics)

Thanks

          Followup:

what is the CREATE TABLESPACE command you used (and why are we losing sleep over
1 block)

      Rating:
      5
Reader  December 26, 2001
Reviewer:
      Reader  from USA
Tom,

Create Tablespace command:

Create tablespace TS1 datafile '/u07/oradata/iiim/ts1.dbf' size 24k;
Create table ts1 storage(initial 8k next 8k pctincrease 0)
 tablespace ts1;

This is purely of academic interest. Ofcourse, we do not need to spend
too much time on this. I agree

Although, if the datafile is for example 1000M and the
segment in the tablespace is initial 500M next 500M,
since 1 block (8/1024M) is used for whatever reason other than
database EXTENTS, there is fragmentation induced; 500M that can
be allocated for segments and 499.99M gets unusable; unless
size the datafile to be 1001M to start with.

Thanks

          Followup:

Yes, the first block of a file in a DICTIONARY managed tablespace is used by the
system (us, Oracle)

Just like the first 64k of a LOCALLY managed tablespaces.

Additionally, on my system we allocated 32k for the datafiles -- not 24k.  The
following shows what you ask for -- what you get and how much is usable by you
(i would highly recomment LMT's btw -- avoid DMT's):

ops$tkyte@ORA817DEV.US.ORACLE.COM> Create tablespace TS1 datafile '/tmp/ts1.dbf'
size 24k reuse;

Tablespace created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> host ls -l /tmp/ts1.dbf
-rw-r-----   1 ora817      32768 Dec 26 13:33 /tmp/ts1.dbf

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

             MaxPoss    Max
Tablespace Name   KBytes       Used      Free   Used Largest   Kbytes   Used
---------------- ------- ---------- --------- ------ ------- -------- ------
.....
TS1                   24          8        16   33.3      16        0     .0
                 ------- ---------- ---------
sum            3,818,848  1,605,144 2,213,704

13 rows selected.

see, 24k in size -- 8 is used, 16 free...

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter tablespace ts1 add datafile
'/tmp/ts2.dbf' size 24k reuse;

Tablespace altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> host ls -l /tmp/ts2.dbf
-rw-r-----   1 ora817      32768 Dec 26 13:33 /tmp/ts2.dbf

ops$tkyte@ORA817DEV.US.ORACLE.COM> @free 1
MaxPoss    Max
Tablespace Name   KBytes      Used      Free   Used  Largest   Kbytes   Used
---------------- ------- --------- --------- ------ -------- -------- ------
TS1                   48        16        32   33.3       16        0     .0
                 ------- --------- ---------
sum            3,818,872 1,605,152 2,213,720

13 rows selected.

now, 48k (24*2), 16k used (1 block / file )

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

      Rating: 5
Extremely
      Useful  December 26, 2001
Reviewer:  Reader
       from USA
Tom,

Thanks very much
I do plan on using LMT in new databases

      Rating:
      5
  December 27, 2001
Reviewer:  Dixit
       from NJ,USA
Very Good

      Rating: 5
Wow
      !  March 20, 2002
Reviewer:  Mini  from
      OH
Exactly what I was looking for.

Thank you so much Tom

      Rating: 5
How the extents
      are allocated in bytes  April 30, 2002
Reviewer:
      Santosh Jadhav  from India
it was a very good explanation TOM.  but i have one doubt

ops$tkyte@8i> select extent_id, bytes, blocks
  2    from user_extents
  3   where segment_name = 'T'
  4     and segment_type = 'TABLE'
  5  /

 EXTENT_ID      BYTES     BLOCKS
---------- ---------- ----------
         2      40960          5
         3      81920         10
         4      57344          7
         0      40960          5
         1      40960          5
                      ----------
sum                           32

this is what after creating table T with 40k initial and next extent.  so why it
is 81920 (3rd extent) and 57344(4th extent). And why 7 and 10 blocks are
allocated respectively

          Followup:

It is because I created the table in a dictionary managed tablespace and we
allocate things to within +- 5 blocks (to avoid fragmenting the tablespace
freespace totally)

Read

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/c02block.htm#2846
it covers the algorithm.

      Rating: 5
good
      stuff  September 16, 2002
Reviewer:  Doug
       from CT, USA

      Rating: 5
System block is
      always 1 ?  January 09, 2003
Reviewer:  Tony
       from India
Does Oracle allocate only one system block even for very big table?

          Followup:

at least one, it might get more later.  depends on block size and number of
extents.

      Rating: 5
What AUTO
      SEGMENT SPACE MANAGEMENT ts's?  March 20, 2003

Reviewer:  Matt  from Australia
What should the value of freelist_group_id be in each call to
dbms_space.free_blocks? You default your value to 1, should this be 1 for tables
in all cases?

I just executed dbms_space.free_blocks for a segment in a LOCALLY managed TS
with AUTO SEGMENT SPACE MANAGEMENT
and got the following error:

10618, 00000, "Operation not allowed on this segment"
// *Cause:  This DBMS_SPACE operation is not permitted on segments in
//          tablespaces with AUTO SEGMENT SPACE MANAGEMENT
// *Action:  Recheck the segment name and type and re-issue the statement

How do you identify free blocks in this case?

          Followup:

I just assumed one freelist.  if you have more, this routine is not
"sophisticated enough"

there is a dbms_space.space_usage routine for ASSM

      Rating: 5
your show_space
      procedure and partitoned tables  May 06, 2003

Reviewer:  PINGU
Hi

I am trying to use your show_space procedure but it seems that it does not work
with partitioned tables?

I think the cursor

for x in ( select tablespace_name
             from dba_tablespaces
            where tablespace_name = ( select tablespace_name
                                        from dba_segments
                                       where segment_type = p_type
                                         and segment_name = p_segname
                                         and SEGMENT_SPACE_MANAGEMENT <> 'AUTO'
)

the subquery should we add distinct?

          Followup:

go for it.  I adjust it as I need.

      Rating: 5
after a closer
      look...  May 06, 2003
Reviewer:  PINGU

Hi

I had a closer look and I dont understand why we need the for loop

for x in ( select tablespace_name
                 from dba_tablespaces
                where tablespace_name in ( select distinct tablespace_name
                                            from dba_segments
                                           where segment_type = p_type
                                             and segment_name = p_segname
                                  and SEGMENT_SPACE_MANAGEMENT <> 'AUTO' )
             )
    loop
    dbms_space.free_blocks
    ( segment_owner     => p_owner,
      segment_name      => p_segname,
      segment_type      => p_type,
      partition_name    => p_partition,
      freelist_group_id => 0,
      free_blks         => l_free_blks );
    end loop;

I think we dont use anything from the loop.....? Or iterating for anything

          Followup:

it only calls free blocks IF the segment space management is not AUTO and the
object exists.

just add a "and rownum = 1" to the query.  Lose the distinct, it is not
relevant.

      Rating:
      5
  May 19, 2003
Reviewer:
      A reader
Hi Tom,

 I am getting error when i use show_space procedure

SQL> exec show_space('T1');
Error ORA-10618: Operation not allowed on this segment -10618
BEGIN show_space('T1'); END;

*
ERROR at line 1:
ORA-10618: Operation not allowed on this segment
ORA-06512: at "GAURANG.SHOW_SPACE", line 49
ORA-06512: at line 1

Thanks

          Followup:

having no clue what t1 is, i have no comment.

      Rating:
      5
  May 20, 2003
Reviewer:
      A reader
T1 IS TABLE .

I am using oracle 9iR2

          Followup:

sorry, maybe if you show us the entire thing -- are you using the script that
does auto segment space management "show space", etc...

have you read about the dbms_* packages I use (they are documented).  It is
pretty straightforward stuff.

works for me in 9i

ops$tkyte@ORA920> l
  1  create or replace procedure show_space
  2  ( p_segname in varchar2,
  3    p_owner   in varchar2 default user,
  4    p_type    in varchar2 default 'TABLE',
  5    p_partition in varchar2 default NULL )
  6  authid current_user
  7  as
  8      l_free_blks                 number;
  9
 10      l_total_blocks              number;
 11      l_total_bytes               number;
 12      l_unused_blocks             number;
 13      l_unused_bytes              number;
 14      l_LastUsedExtFileId         number;
 15      l_LastUsedExtBlockId        number;
 16      l_LAST_USED_BLOCK           number;
 17      procedure p( p_label in varchar2, p_num in number )
 18      is
 19      begin
 20          dbms_output.put_line( rpad(p_label,40,'.') ||
 21                                p_num );
 22      end;
 23  begin
 24      for x in ( select tablespace_name
 25                   from dba_tablespaces
 26                  where tablespace_name = ( select tablespace_name
 27                                              from dba_segments
 28                                             where segment_type = p_type
 29                                               and segment_name = p_segname
 30
        and owner = p_owner
 31                                               and SEGMENT_SPACE_MANAGEMENT
<> 'AUTO' )
 32               )
 33      loop
 34      dbms_space.free_blocks
 35      ( segment_owner     => p_owner,
 36        segment_name      => p_segname,
 37        segment_type      => p_type,
 38       partition_name    => p_partition,
 39        freelist_group_id => 0,
 40        free_blks         => l_free_blks );
 41      dbms_output.put_line( 'Old fashioned' );
 42      end loop;
 43
 44      dbms_space.unused_space
 45      ( segment_owner     => p_owner,
 46        segment_name      => p_segname,
 47        segment_type      => p_type,
 48       partition_name    => p_partition,
 49        total_blocks      => l_total_blocks,
 50        total_bytes       => l_total_bytes,
 51        unused_blocks     => l_unused_blocks,
 52        unused_bytes      => l_unused_bytes,
 53        LAST_USED_EXTENT_FILE_ID => l_LastUsedExtFileId,
 54        LAST_USED_EXTENT_BLOCK_ID => l_LastUsedExtBlockId,
 55        LAST_USED_BLOCK => l_LAST_USED_BLOCK );
 56
 57      p( 'Free Blocks', l_free_blks );
 58      p( 'Total Blocks', l_total_blocks );
 59      p( 'Total Bytes', l_total_bytes );
 60      p( 'Total MBytes', trunc(l_total_bytes/1024/1024) );
 61      p( 'Unused Blocks', l_unused_blocks );
 62      p( 'Unused Bytes', l_unused_bytes );
 63      p( 'Last Used Ext FileId', l_LastUsedExtFileId );
 64      p( 'Last Used Ext BlockId', l_LastUsedExtBlockId );
 65      p( 'Last Used Block', l_LAST_USED_BLOCK );
 66* end;
ops$tkyte@ORA920> drop table t;

Table dropped.

ops$tkyte@ORA920> create table t ( x int );

Table created.

ops$tkyte@ORA920> exec show_space('T')
Free Blocks.............................
Total Blocks............................8
Total Bytes.............................65536
Total MBytes............................0
Unused Blocks...........................5
Unused Bytes............................40960
Last Used Ext FileId....................9
Last Used Ext BlockId...................48
Last Used Block.........................3

PL/SQL procedure successfully completed.

      Rating: 4
FREE BLOCK Vs
      PERFORMANCE  June 12, 2003
Reviewer:  Raghu
       from Milwaukee, WI USA
Hi Tom

What is the impact of the Large High water mark or FREE Blocks or UNUSED block
on the table performacne (INSERT/UPDATE/SELECT). I have a case in my environment
where a batch takes 9 minutes in one instance (EDEV) amd ot takes 2 hrs in
another (EPROD). The outputs from yr procedure and from ALL_TABLES is given
below for both the instance.

EDEV

Free Blocks  4
Total Blocks  1984
Total Bytes  16252928
Unused Blocks  1
Unused Bytes  8192
Last Used Ext FileId 112
Last Used Ext BlockId  5193
Last Used Block  15

EPROD

Free Blocks  20
Total Blocks  2352
Total Bytes  19267584
Unused Blocks  11
Unused Bytes  90112
Last Used Ext FileId 346
Last Used Ext BlockId  3065
Last Used Block  5

BLOCKS    EMPTY_BLOCKS    AVG_SPACE    NUM_FREELIST_BLOCKS
2340    6             4462            2103

select blocks, empty_blocks,
           avg_space, num_freelist_blocks
      from user_tables
     where table_name = 'BACKLOG_ITEM'

BLOCKS EMPTY_BLOCKS AVG_SPACE NUM_FREELIST_BLOCKS
3956   11            4570     2004

2. If my process does not prevent me to issue a TRUNCATE statement, but my batch
involves lots of DELETES and INSERT, then how can I prevent large values for
high water mark?. Do we have any utility/script that we can run to  reduce the
high water mark of a table.

          Followup:

1) both of those tables are so small, If I had a "batch" that took 9 minutes to
process 20meg of data, I'd be concerned about that in itself.  That is probably
8 minutes and 30 seconds longer then it should be.

Use TKPROF and SQL_TRACE to find your low hanging fruit.

2) you have a very very small table, I think you are looking in the wrong place
here.  You have most likely a bad algorithm.  You need to diagnose where the
problem is, then fix it.  Not "fix something" and see if it was the right thing.

      Rating:
      3
Thanks  June 13, 2003
Reviewer:
      Raghu  from Milwaukee, WI USA
Hi Tom,

Thanks for your response. I absolutely agree with you that the performance of
the batch I had given in my example could be improved by changing the algorithm.
I am sorry that my example diverted you from the "real" reason for my comment.

I was wondering how I can make use of the output of your query in my DB
maintenenace. For instance. Is there a co-relation between the number of Free
blocks and performance.

In my original example, Can I attribute the difference in batch performance
between two instance to number of Free blocks?(Again I accept that the
performance could be improved further).

Another example, I have a table that has around 6 million rows. The average Row
length is 1250 bytes. We do lots of "Delete/Insert/Update" on this table. We
recently re-partitioned this table (using Export, CREATE Table and Import). We
noticed a huge performance changes for SELECTS..Over the period of time, it
performance is going back to the "Normal". Can I attribute this to FREE Blocks
and Do you think that we should constantly look for such blocks and "clean it"
and how can we do it?.
Note: At this time, I am trying to find the Root cause for the performance
changes. When I read this article of yours I felt that I might have discovered
"one" of the casues and wanted a confirmation from you.  I also like to point
out that the DB are very similar in terms of size, parameter etc. The code is
also same.

          Followup:

there could be -- but in this case, the numbers are far too small. I thought it
was clear that I do not think that the small number of blocks you have on
freelists here would have any material affect on anything....

No, you cannot attribute anything to anything here.  There is insufficient data
to say anything reasonable.

You don't say which way the performance change was, what type of operations you
do on this table, nothing.  Again, totally insufficient data to say anything
about anything (except hypothesize, which I don't like doing too much).  Now, if
you had some metrics (tkprofs!!!  statspacks!!!) - hard numerical,
incontravertible data points that would provide information as to the number of
consistent gets, query plans, inputs, etc over time -- then, well, then we'd be
cooking with gas...

(but, the performance of queries is unaffected by the number of blocks on the
freelist -- I could set pctfree/pctused so that every block is on the freelist.
I could set the pctfree/pctused so that NO block is on the freelist.  I could
have BOTH of these tables -- one with every block on and other with every block
off -- organized in such a way that block for block they are in effect "the
same".  They would perform identically.  It is nothing to do with how many
blocks are on the freelist (although you may be able to use that as an indicator
if you understand how pctfree/pctused are set and how they are used).  It has
everything to do with how many blocks Oracle must process in order to answer
your queries.

You need a TKPROF.  You need a TKPROF of this "batch" on dev, of this "batch" on
prod.  You need to compare them.  That'll get you 90% of your way along in your
root cause analysis.

      Rating: 5
Dear Tom, what
      could be wrong here?  July 24, 2003
Reviewer:
      Saminathan Seerangan  from NJ,USA
Tom,

I am not able to use show_space procedure. Please help me out.

SQL> create user a identified by a;
User created.

SQL> grant create session, create procedure,
  2  create table to a;
Grant succeeded.

SQL> alter user a quota unlimited on users;
User altered.

SQL> connect a/a
Connected.

SQL> @D:\share\oracle\asktom\show_space.sql
 51  /
Procedure created.

SQL> create table t ( x int ) tablespace users;
Table created.

SQL> exec show_space( 'T' )
BEGIN show_space( 'T' ); END;

*
ERROR at line 1:
ORA-10618: Operation not allowed on this segment
ORA-06512: at "SYS.DBMS_SPACE", line 74
ORA-06512: at "A.SHOW_SPACE", line 22
ORA-06512: at line 1

SQL> desc t
 Name                                      Null?    Type
 ------------------------------------- -------- --------
 X                                       NUMBER(38)

SQL> insert into t values(100);
1 row created.

SQL> desc dbms_space
PROCEDURE FREE_BLOCKS
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 SEGMENT_OWNER                  VARCHAR2                IN
 SEGMENT_NAME                   VARCHAR2                IN
 SEGMENT_TYPE                   VARCHAR2                IN
 FREELIST_GROUP_ID              NUMBER                  IN
 FREE_BLKS                      NUMBER                  OUT
 SCAN_LIMIT                     NUMBER                  IN     DEFAULT
 PARTITION_NAME                 VARCHAR2                IN     DEFAULT
PROCEDURE SPACE_USAGE
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 SEGMENT_OWNER                  VARCHAR2                IN
 SEGMENT_NAME                   VARCHAR2                IN
 SEGMENT_TYPE                   VARCHAR2                IN
 UNFORMATTED_BLOCKS             NUMBER                  OUT
 UNFORMATTED_BYTES              NUMBER                  OUT
 FS1_BLOCKS                     NUMBER                  OUT
 FS1_BYTES                      NUMBER                  OUT
 FS2_BLOCKS                     NUMBER                  OUT
 FS2_BYTES                      NUMBER                  OUT
 FS3_BLOCKS                     NUMBER                  OUT
 FS3_BYTES                      NUMBER                  OUT
 FS4_BLOCKS                     NUMBER                  OUT
 FS4_BYTES                      NUMBER                  OUT
 FULL_BLOCKS                    NUMBER                  OUT
 FULL_BYTES                     NUMBER                  OUT
 PARTITION_NAME                 VARCHAR2                IN     DEFAULT
PROCEDURE UNUSED_SPACE
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 SEGMENT_OWNER                  VARCHAR2                IN
 SEGMENT_NAME                   VARCHAR2                IN
 SEGMENT_TYPE                   VARCHAR2                IN
 TOTAL_BLOCKS                   NUMBER                  OUT
 TOTAL_BYTES                    NUMBER                  OUT
 UNUSED_BLOCKS                  NUMBER                  OUT
 UNUSED_BYTES                   NUMBER                  OUT
 LAST_USED_EXTENT_FILE_ID       NUMBER                  OUT
 LAST_USED_EXTENT_BLOCK_ID      NUMBER                  OUT
 LAST_USED_BLOCK                NUMBER                  OUT
 PARTITION_NAME                 VARCHAR2                IN     DEFAULT

SQL>

          Followup:

create or replace procedure show_space
( p_segname in varchar2,
  p_owner   in varchar2 default user,
  p_type    in varchar2 default 'TABLE',
  p_partition in varchar2 default NULL )
-- this procedure uses authid current user so it can query DBA_*
-- views using privileges from a ROLE and so it can be installed
-- once per database, instead of once per user that wanted to use it
authid current_user
as
    l_free_blks                 number;
    l_total_blocks              number;
    l_total_bytes               number;
    l_unused_blocks             number;
    l_unused_bytes              number;
    l_LastUsedExtFileId         number;
    l_LastUsedExtBlockId        number;
    l_LAST_USED_BLOCK           number;
    l_segment_space_mgmt        varchar2(255);
    l_unformatted_blocks number;
    l_unformatted_bytes number;
    l_fs1_blocks number; l_fs1_bytes number;
    l_fs2_blocks number; l_fs2_bytes number;
    l_fs3_blocks number; l_fs3_bytes number;
    l_fs4_blocks number; l_fs4_bytes number;
    l_full_blocks number; l_full_bytes number;

    -- inline procedure to print out numbers nicely formatted
    -- with a simple label
    procedure p( p_label in varchar2, p_num in number )
    is
    begin
        dbms_output.put_line( rpad(p_label,40,'.') ||
                              to_char(p_num,'999,999,999,999') );
    end;
begin
   -- this query is executed dynamically in order to allow this procedure
   -- to be created by a user who has access to DBA_SEGMENTS/TABLESPACES
   -- via a role as is customary.
   -- NOTE: at runtime, the invoker MUST have access to these two
   -- views!
   -- this query determines if the object is a ASSM object or not
   begin
      execute immediate
          'select ts.segment_space_management
             from dba_segments seg, dba_tablespaces ts
            where seg.segment_name      = :p_segname
              and (:p_partition is null or
                  seg.partition_name = :p_partition)
              and seg.owner = :p_owner
              and seg.tablespace_name = ts.tablespace_name'
             into l_segment_space_mgmt
            using p_segname, p_partition, p_partition, p_owner;
   exception
       when too_many_rows then
          dbms_output.put_line
          ( 'This must be a partitioned table, use p_partition => ');
          return;
   end;

   -- if the object is in an ASSM tablespace, we must use this API
   -- call to get space information, else we use the FREE_BLOCKS
   -- API for the user managed segments
   if l_segment_space_mgmt = 'AUTO'
   then
     dbms_space.space_usage
     ( p_owner, p_segname, p_type, l_unformatted_blocks,
       l_unformatted_bytes, l_fs1_blocks, l_fs1_bytes,
       l_fs2_blocks, l_fs2_bytes, l_fs3_blocks, l_fs3_bytes,
       l_fs4_blocks, l_fs4_bytes, l_full_blocks, l_full_bytes, p_partition);

     p( 'Unformatted Blocks ', l_unformatted_blocks );
     p( 'FS1 Blocks (0-25)  ', l_fs1_blocks );
     p( 'FS2 Blocks (25-50) ', l_fs2_blocks );
     p( 'FS3 Blocks (50-75) ', l_fs3_blocks );
     p( 'FS4 Blocks (75-100)', l_fs4_blocks );
     p( 'Full Blocks        ', l_full_blocks );
  else
     dbms_space.free_blocks(
       segment_owner     => p_owner,
       segment_name      => p_segname,
       segment_type      => p_type,
       freelist_group_id => 0,
       free_blks         => l_free_blks);

     p( 'Free Blocks', l_free_blks );
  end if;

  -- and then the unused space API call to get the rest of the
  -- information
  dbms_space.unused_space
  ( segment_owner     => p_owner,
    segment_name      => p_segname,
    segment_type      => p_type,
    partition_name    => p_partition,
    total_blocks      => l_total_blocks,
    total_bytes       => l_total_bytes,
    unused_blocks     => l_unused_blocks,
    unused_bytes      => l_unused_bytes,
    LAST_USED_EXTENT_FILE_ID => l_LastUsedExtFileId,
    LAST_USED_EXTENT_BLOCK_ID => l_LastUsedExtBlockId,
    LAST_USED_BLOCK => l_LAST_USED_BLOCK );

    p( 'Total Blocks', l_total_blocks );
    p( 'Total Bytes', l_total_bytes );
    p( 'Total MBytes', trunc(l_total_bytes/1024/1024) );
    p( 'Unused Blocks', l_unused_blocks );
    p( 'Unused Bytes', l_unused_bytes );
    p( 'Last Used Ext FileId', l_LastUsedExtFileId );
    p( 'Last Used Ext BlockId', l_LastUsedExtBlockId );
    p( 'Last Used Block', l_LAST_USED_BLOCK );
end;
/

try that one, you must be in 9i using ASSM with the older version from 8i --
assm does not have freelists

      Rating: 5
Thank you so
      much  July 24, 2003
Reviewer:  Saminathan
      Seerangan  from NJ,USA
Small typo in the above procedure
Line # 46 'select ts.segment_space_management appears 2 times.

          Followup:

thanks -- i fixed that up

      Rating: 3
Access to dba_*
      views needed  September 17, 2003
Reviewer:
      Dusan  from Czech republic
Well, this is a bit frustrating! I used to use this excellent utility
(show_space) at many client sites (8i), to investigate space issues. Basically,
every schema might use it! Now, on 9i, I have to ask access for those views.
Isn't it possible to work around this?

          Followup:

instead of me querying the dba_views, you can pass in that information (what
type of tablespace is it in)

use the "8i" like version, just parameterize it to call the proper API

      Rating: 2
Seems it does
      not work for partitioned table  September 17, 2003

Reviewer:  Dusan  from Czech rep.
Problems appeared when I tried to use this utility for partitioned table.

  1  select TABLE_OWNER, PARTITION_NAME
  2  from all_tab_partitions
  3  where table_owner='VALASEKD'
  4* and table_name='DV_ALL_OBJECTS_PART'
sys@DV9IR2.US.ORACLE.COM> /

TABLE_OWNER                    PARTITION_NAME
------------------------------ ----------------
VALASEKD                       P030915
VALASEKD                       P030916
VALASEKD                       PMAX

  1  select ts.segment_space_management
  2    from dba_segments seg, dba_tablespaces ts
  3   where seg.segment_name      = 'DV_ALL_OBJECTS_PART'
  4     and ('PMAX' is null or
  5         seg.partition_name = 'PMAX')
  6     and seg.owner = 'VALASEKD'
  7*    and seg.tablespace_name = ts.tablespace_name
sys@DV9IR2.US.ORACLE.COM> /

SEGMEN
------
AUTO

sys@DV9IR2.US.ORACLE.COM> ed
Zapsáno soubor afiedt.buf

  1* exec show_space('DV_ALL_OBJECTS_PART', 'VALASEKD','TABLE','PMAX');
sys@DV9IR2.US.ORACLE.COM> exec show_space('DV_ALL_OBJECTS_PART',
'VALASEKD','TABLE','PMAX');
BEGIN show_space('DV_ALL_OBJECTS_PART', 'VALASEKD','TABLE','PMAX'); END;

*
ERROR na øádku 1:
ORA-03204: zadání typu segmentu by mìlo vyznaèovat dìlení na partition
ORA-06512: na "SYS.DBMS_SPACE", line 97
ORA-06512: na "SYS.SHOW_SPACE", line 64
ORA-06512: na line 1

      Rating: 4
DIfferent
      Extent Sizes  October 02, 2003
Reviewer:
      Kailash  from East Brunswick, NJ
Thanks for a wonderful response. However, in response to your first review, I am
not clear as to why the DBA_EXTENTS shows different extent sizes (ie. the number
of blocks in the extent are 5,5,5,10,7 a total of 32 extents). In this example,
we did not specify any PCTINCREASE. So my understanding is that all extents must
be of equal size and 1 of the extents must have less space compared to other
extents because of the 1 insert. However, the query shows a different result. I
would appreciate if you could explain me how DBA_EXTENTS calculated the above
mentioned extent sizes.

          Followup:

did you read the followup I posted with a link to the docs?

when you ask for 40k in a dictionary managed tablespace and we find a free
extent that is within 5 blocks of your request (eg: 40k = 5 * 8k blocks --
anything that is free and has 5, 6, 7, 8, 9, 10 blocks will be used).  This is
to avoid the situation whereby you have a free extent of say 7 blocks -- you
asked for 5, so we take 5 leaving "2 totally useless blocks" hanging out there.

If the free extent was 11 blocks -- we would have given you your 5 and left 6
out there for the next guy.

Otherwise you would end up with lots of 1/2/3 block free extents that couldn't
be used for anything.

October 03, 2003 -- Thanks for the question regarding "export
      tablespace", version 9i

      You Asked
Tom,
if we export a tablespace, and a table only has one partition in the tablespace,
other partitions are seperate tablespace, what will be in the dump file? will it
export the one partition?

thanks,

cathy

      and we said...
ops$tkyte@ORA920> create tablespace test datafile size 1m;
Tablespace created.

  1  create table t ( x int, data char(255) )
  2  partition by range(x)
  3  ( partition p1 values less than (100) tablespace test,
  4    partition p2 values less than (200) tablespace users
  5* )
ops$tkyte@ORA920> /
Table created.

ops$tkyte@ORA920> insert into t select rownum,'x' from all_objects where rownum
< 200;

199 rows created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920> Disconnected from Oracle9i Enterprise Edition Release
9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production

[tkyte@tkyte-pc-isdn tkyte]$ exp userid=/ tablespaces=test

Export: Release 9.2.0.3.0 - Production on Fri Oct 3 09:26:33 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to: Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production
Export done in US7ASCII character set and AL16UTF16 NCHAR character set
server uses WE8ISO8859P1 character set (possible charset conversion)

About to export selected tablespaces ...
For tablespace TEST ...
. exporting cluster definitions
. exporting table definitions
. . exporting table                              T
. . exporting partition                             P1         99 rows exported
. . exporting partition                             P2        100 rows exported
. exporting referential integrity constraints
. exporting triggers
Export terminated successfully without warnings.

as you can see - it got both partitions, the definition from the docs
"implies" this is what happens:

TABLESPACES

Default: none

The TABLESPACES parameter specifies that all tables in the tablespace be
exported to the Export dump file. This includes all tables contained in the list
of tablespaces and all tables that have a partition located in the list of
tablespaces. Indexes are exported with their tables, regardless of where the
index is stored. You must have the EXP_FULL_DATABASE role to use TABLESPACES to
export all tables in the tablespace. When TABLESPACES is used in conjunction
with TRANSPORT_TABLESPACE=y, you can specify a limited list of tablespaces to be
exported from the database to the export file.
October 03, 2003 Muhammad Ahmad -- Thanks for the question
      regarding "how to display selective record twice in the query?", version
      8.1.7

      You Asked
Hello Tom,

Actually I want to display all the records from a table but alongwith I want to
display every record that is divisible by 40 comes twice

I mean if there are 100 records then after using order by the 40th,80th record
comes twice and total rows return is 102.

Is This possible in a single query or use some pl

My query is:

select trans.tdate,DESCRIPTION,case when in_out_type = 'INCOME' then amount end
debit,
                         case when in_out_type = 'EXPENDITURE' then amount end
credit,bal
from trans,balance where trans.id = balance.bid
--and trans.id > 480
ORDER BY ID

thanks

      and we said...
I'll use 4 instead of 40 and 10 instead of 100.

The query on lines 4..6 is your current query.  We add "rownum" so we can see
what rows 4 and 8 are.

We cartesian product this to a 2 row result set

we always output the "first" row
we output the second row when mod(rownum,4) is 0 -- when it is divisible by 4

ops$tkyte@ORA920> select a.*, b.*,
  2         decode( b.x, 2, '<<<<====' )
  3    from
  4  ( select rownum r, object_name
  5     from all_objects
  6    where rownum <= 10) a,
  7  ( select 1 x from dual
  8     union all
  9    select 2 x from dual ) b
 10    where b.x = 1
 11       or (b.x = 2 and mod(a.r,4) = 0)
 12   order by a.r
 13  /

         R OBJECT_NAME                             X DECODE(B
---------- ------------------------------ ---------- --------
         1 /1005bd30_LnkdConstant                  1
         2 /10076b23_OraCustomDatumClosur          1
         3 /10297c91_SAXAttrList                   1
         4 /103a2e73_DefaultEditorKitEndP          1
         4 /103a2e73_DefaultEditorKitEndP          2 <<<<====
         5 /1048734f_DefaultFolder                 1
         6 /10501902_BasicFileChooserUINe          1
         7 /105072e7_HttpSessionBindingEv          1
         8 /106ba0a5_ArrayEnumeration              1
         8 /106ba0a5_ArrayEnumeration              2 <<<<====
         9 /106faabc_BasicTreeUIKeyHandle          1
        10 /10744837_ObjectStreamClass2            1

12 rows selected.

Review &
      Followup

      Rating: 4
and this
      ?!!  October 01, 2003
Reviewer:  huss

nice answer  ,
but help me in this,
how i make a query that retuen more than one row while not depend on a db table
(ie select from dual)

 example :
  select ...........
  from dual

and returns  some thing like
 id          name
--------------------
 1            n1
 2            n2
 3            n3

3 row selected !!

          Followup:

create a pipelined function in 9i -- you can select * from PLSQL_FUNCTION

search for pipelined

      Rating: 5
Is it possible
      in 8i?  October 01, 2003
Reviewer:  Murali
       from India
Tom,

In an Interview, I was asked to print 1 to 50 using dual table.  They said, I
should not use UNION.  Is it possible in 8i?

Thanks
Murali

          Followup:

maybe it was a trick question:

select 1, 2, 3, 4, 5, .... 50 from dual;

that prints 1 to 50...

      Rating:
      5
  October 01, 2003
Reviewer:  Ian
      Matyssik  from Japan
select rownum from ( select 1,2,3,4,5,6,7,8,9,10 from dual group by cube
(5,6,7,8,9,10)) where rownum <=50;

          Followup:

cool -- didn't think about that !

      Rating: 4
Excellent Ian
      Matyssik  October 01, 2003
Reviewer:  Pawan
       from USA
Tom,
Can you explain the use of "cube" by Ian.

          Followup:

group by cube (or rollup) produces (injects) extra rows into the result set.
The rows are the aggregated each and every way -- consider this query:

scott@ORA920> select ename, job, sum(sal),
  2         grouping(ename),
  3             grouping(job)
  4    from emp
  5   group by cube(ename,job)
  6  /

that says:

show me sal by:

ename, job
ename, <over job>
job, <over ename>
<over both job and ename>

we can use the builtin "grouping" function to see what rows mean what:

ENAME      JOB         SUM(SAL) GROUPING(ENAME) GROUPING(JOB)
---------- --------- ---------- --------------- -------------
                          29025               1             1

both ename and job were "grouped over", this is select sum(sal) from emp"

           CLERK           4150               1             0
           ANALYST         6000               1             0
           MANAGER         8275               1             0
           SALESMAN        5600               1             0
           PRESIDENT       5000               1             0

there was sum(sal) by job -- over ename

FORD                       3000               0             1

FORD       ANALYST         3000               0             0
there is a row of sum(sal) by ename/job

KING                       5000               0             1
and a row of sum(sal) by ename -- over job

KING       PRESIDENT       5000               0             0
WARD                       1250               0             1
WARD       SALESMAN        1250               0             0
ADAMS                      1100               0             1
ADAMS      CLERK           1100               0             0
ALLEN                      1600               0             1
ALLEN      SALESMAN        1600               0             0
BLAKE                      2850               0             1
BLAKE      MANAGER         2850               0             0
CLARK                      2450               0             1
CLARK      MANAGER         2450               0             0
JAMES                       950               0             1
JAMES      CLERK            950               0             0
JONES                      2975               0             1
JONES      MANAGER         2975               0             0
SCOTT                      3000               0             1
SCOTT      ANALYST         3000               0             0
SMITH                       800               0             1
SMITH      CLERK            800               0             0
MARTIN                     1250               0             1
MARTIN     SALESMAN        1250               0             0
MILLER                     1300               0             1
MILLER     CLERK           1300               0             0
TURNER                     1500               0             1
TURNER     SALESMAN        1500               0             0

34 rows selected.

So, instead of 14 rows -- we get 34 with the aggregate sum(sal) computed each
and every way.

He just used a single row in dual with enough "columns" to trick us into giving
enough rows out.

      Rating: 4
Got
      it!!  October 01, 2003
Reviewer:  Pawan
       from USA
Tom,
Even the following query will give the same result -
select rownum from ( select 1 from dual group by cube (1,1,1,1,1,1))
where rownum <51

Now I think I understand what CUBE is doing ( just 2^n where n=6 in this case)

Every time I learn something at this site
Thanks

          Followup:

guess what -- me too :)

every day, something new

      Rating:
      4
interesting  October 01, 2003
Reviewer:
      Dennis  from Missouri, USA
Ok,

I still don't really know what cube does but I am intrigued as to why the first
query,
select rownum from ( select 1,2,3,4,5,6,7,8,9,10 from dual
group by cube (5,6,7,8,9,10)) where rownum <=50

is more performant than the second one.   Is it because the second one has to go
up by 1s?

Thanks,
Dennis

      Rating: 5
Please
      explain.  October 01, 2003
Reviewer:  Sai
       from Mountain View, CA
Hi Tom,

Can you help me understand the behaviour of "cube" when the column name repeats
in expression. For example:
"select username,username from (select username from dba_users where rownum<=2)
group by cube (username,username)" is giving null row output for some
combinations of groups, while the following query giving no null row output
except for final summary:
"select username,user_id from (select username,user_id from dba_users where
rownum<=2) group by cube (username,user_id)".

Please explain -- Thanks.

          Followup:

it would be sort of "ambigous" to do that

but it doesn't happen for me:

ops$tkyte@ORA920> set null ****
ops$tkyte@ORA920> /

USERNAME                       USERNAME
------------------------------ ------------------------------
SYS                            SYS
SYSTEM                         SYSTEM
SYS                            SYS
SYSTEM                         SYSTEM
SYS                            SYS
SYSTEM                         SYSTEM
****                           ****

7 rows selected.

it doesn't really "make sense"

      Rating: 3
Could it be a
      bug?  October 01, 2003
Reviewer:  Sai
       from Mountain View, CA
Hi Tom,

I am using 8.1.7.4 on Windows 2000 professional box. Here is the output I got
for the same query you ran.

SQL> select username,username from (select username from dba_users where rownum<
=2)
  2  group by cube (username,username);

USERNAME                       USERNAME
------------------------------ ------------------------------
SYS                            SYS
SYS                            SYS
SYSTEM                         SYSTEM
SYSTEM                         SYSTEM

6 rows selected.

You got 7 rows selected, I got 6 rows selected. Is it a bug??? -- Thanks.

          Followup:

looks like it, yes

      Rating: 3
I cannot get my
      head around this ?  October 02, 2003
Reviewer:
      A reader
select rownum from ( select 1 from dual group by cube (1,1,1,1,1,1)) where
rownum <51

I just cannot see how this works, despite it being such a simple looking query.
Any chance you could break it down ?
Sorry for being so dumb !

          Followup:

does this make more sense to you:

select rownum from ( select 1 from dual group by cube
('a','b','c','d','e','f')) where
rownum <51
/

or maybe:

select rownum
  from ( select c1, c2, c3, c4, c5, c6, count(*)
           from (select 'a' c1, 'b' c2, 'c' c3, 'd' c4, 'e' c5, 'f' c6
                   from dual )
          group by cube(c1,c2,c3,c4,c5,c6)
       )
 where rownum <51
/

they are all functionally equivalent.  group by cube(1,1,1,1,1,1) is just short
hand for that -- instead of having "real" columns -- it is grouping by literals.

      Rating:
      5
  October 02, 2003
Reviewer:  Ian
      Matyssik  from Japan
I do not know what is so confusing about CUBE. All it does is displaying every
possible combination of the given variables( in our case literals ). So if you
go CUBE(1,2,3) it will give you the same row (1,2,3) in every possible order =
2^3. So that is as simple as that. So the quick hack to get more then 1 row from
dual is create some literals instead of columns and group them by CUBE. I by
misstake put 10 literals in the first query beacuse it is more confusing to the
reader what I could do is 2^6 = 64
SQL> select power(2,6) from dual;

POWER(2,6)
----------
        64

so we need total of 50 rows that is closest (64) and just select from that query
ROWNUM which will give you sequencial numbers. And that is all trick. So if you
do cube of 7 columns and one row you get 128 ans so on.

      Rating: 5
To answer
      Dennis from Missouri, USA   October 02, 2003

Reviewer:  Ian Matyssik  from Japan
Here is a output of autotrace

SQL>
SQL> select rownum from ( select 1 from dual group by cube (1,1,1,1,1,1))
where rownum <51  2
  3  ;

    ROWNUM
----------
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11

    ROWNUM
----------
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22

    ROWNUM
----------
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33

    ROWNUM
----------
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44

    ROWNUM
----------
        45
        46
        47
        48
        49
        50

50 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   COUNT (STOPKEY)
   2    1     VIEW
   3    4         RECURSIVE EXECUTION OF 'SYS_LE_4_0'
   4    2       TEMP TABLE TRANSFORMATION
   5    4         VIEW
   6    5           VIEW
   7    6             UNION-ALL
   8    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

   9    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  10    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  11    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  12    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  13    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  14    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  15    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  16    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  17    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  18    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  19    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  20    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  21    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  22    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  23    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  24    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  25    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  26    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  27    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  28    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  29    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  30    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  31    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  32    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  33    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  34    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  35    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  36    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  37    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  38    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  39    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  40    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  41    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  42    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  43    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  44    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  45    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  46    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  47    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  48    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  49    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  50    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  51    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  52    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  53    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  54    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  55    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  56    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  57    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  58    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  59    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  60    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  61    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  62    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  63    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  64    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  65    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  66    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  67    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  68    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  69    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  70    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6606_360
          3CB5'

  71    7               TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6607_360
          3CB5'

Statistics
----------------------------------------------------------
        133  recursive calls
         14  db block gets
        185  consistent gets
          1  physical reads
       2080  redo size
       1059  bytes sent via SQL*Net to client
        536  bytes received via SQL*Net from client
         10  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         50  rows processed

SQL> select rownum from ( select 1,2,3,4,5,6 from dual group by cube (
1,2,3,4,5,6 ) );

    ROWNUM
----------
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11

    ROWNUM
----------
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22

    ROWNUM
----------
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33

    ROWNUM
----------
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44

    ROWNUM
----------
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55

    ROWNUM
----------
        56
        57
        58
        59
        60
        61
        62
        63
        64

64 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   COUNT
   2    1     VIEW
   3    2       SORT (GROUP BY)
   4    3         GENERATE (CUBE)
   5    4           SORT (GROUP BY)
   6    5             TABLE ACCESS (FULL) OF 'DUAL'

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

This is an output it gave me so as you can see execution was handeled
differently. As to why I would ask Tom to help here.

          Followup:

putting rownum INSIDE the inline view changes the semantics of queries greatly.

In my new book, Effective Oracle By Design, I show how to use that to great
effect in tuning some cases.

Here, putting rownum into the inline view would not be appropriate -- it has to
generate the entire cube and then select from it.  it was like an "all rows"
optimization.

The second query allowed us to feed back rows as they were generated -- they
didn't need to have a rownum assigned to them  -- it was like "first_rows"

      Rating: 5
Question to
      Tom.  October 02, 2003
Reviewer:  Ian
      Matyssik  from Japan
But Tom, I see that the rownum was put outside in both queries and the only
distinction I see in the first query that only one column was selected and cube
was generated based on that single column/literal (CUBE(1,1,1,1,1,1)). So my
guess would be that Oracle chose to do something different then CUBEing by
distinct values. So I still would like to know what exectly Oracle does when you
cube on the same value, or could it be the optimiser doing something wrong?

          Followup:

sorry -- it was very hard to read and see the queries with all of the stuff in
between -- i read it wrong.

don't know.  as both queries are really "nonsense" queries -- they would never
actually be used in real life -- not going to worry to much about it.

      Rating: 4
Ok, I'm still
      confused.  October 02, 2003
Reviewer:  Mark J.
      Bobak  from Ann Arbor, MI
I (think I) conceptually understand CUBE().  Thanks to Tom
and Ian for countless examples.

Here's what's baffling me:
select rownum from(select 1 from dual group by cube(1,2,3,4,5,6));
will return 1-64.  Ok, fine, 2^6 = 64.

But then, why:
select 1 from dual cube(1,2,3,4,5,6);
Will only return 1??

Shouldn't it return 64 lines?  If we wrap:
select rownum from (....); around it, we get 64
rows, so, why only one when the inner query is
executed???

Sorry, perhaps I'm being a little thick, but I don't
get it.

          Followup:

dual is special, optmizer "knows" dual does a single row.  Watch this (but don't
feel compelled to do this on your system and for heavens sake ROLLBACK if you do
-- do not commit!!!)

ops$tkyte@ORA920> insert into dual values ( 'x' );

1 row created.

ops$tkyte@ORA920> select * from dual;

D
-
X

where is 'x'???

ops$tkyte@ORA920> select count(*) from dual;

  COUNT(*)
----------
         2

should be there...

ops$tkyte@ORA920> select * from dual where dummy = 'x';

D
-
x

and it is but the optimizer "knew" dual had a single row.  wait'll you see
dual in 10g :)

      Rating: 4
Because
      ...  October 02, 2003
Reviewer:  TOM_PARTII
       from UK
it is a strange effect of the optimzer "knowing" dual should have one row.  it
was not expecting the group by cube to explode the data.

      Rating:
      3
Clarification  October 02, 2003
Reviewer:
      Richard  from Oxfordshire, UK
I found the following helpful, with regard to understanding how CUBE works:

====
SQL:
====

select *
  from ( select c1, c2, c3, c4, c5, c6
           from (select 'a' c1, 'b' c2, 'c' c3, 'd' c4, 'e' c5, 'f' c6
                   from dual )
          group by cube(c1,c2,c3,c4,c5,c6)
       );

=======
Result:
=======

C C C C C C
- - - - - -
a b c d e f
a b c d e
a b c d   f
a b c d
a b c   e f
a b c   e
a b c     f
a b c
a b   d e f
a b   d e
a b   d   f
a b   d
a b     e f
a b     e
a b       f
a b
a   c d e f
a   c d e
a   c d   f
a   c d
a   c   e f
a   c   e
a   c     f
a   c
a     d e f
a     d e
a     d   f
a     d
a       e f
a       e
a         f
a
  b c d e f
  b c d e
  b c d   f
  b c d
  b c   e f
  b c   e
  b c     f
  b c
  b   d e f
  b   d e
  b   d   f
  b   d
  b     e f
  b     e
  b       f
  b
    c d e f
    c d e
    c d   f
    c d
    c   e f
    c   e
    c     f
    c
      d e f
      d e
      d   f
      d
        e f
        e
          f

64 rows selected.
==================================

The SQL is just Tom's earlier SQL, with * replacing 1.
As can be seen there are indeed 64 combinations!

      Rating: 5
to
      Mark  October 02, 2003
Reviewer:  Freek
      D'Hooge  from Belgium
Mark,

you can see this when you do:

==> this will give you 64 rows
select 1
from ( select 1
       from user_tables
       where rownum = 1
     )
group by cube (1,2,3,4,5,6);

==> this will give you 1 row
select 1
from dual
group by cube (1,2,3,4,5,6);

      Rating:
      4
Thanks  October 02, 2003
Reviewer:
      Dennis  from Missouri, USA
Thanks Ian.

It does seem like a result of selecting the one value instead of selecting a
large enough piece that it could just work with as part of the cube.

Thanks,
Dennis

      Rating: 3
One More thing
      in CUBE?  October 02, 2003
Reviewer:  Muhammad
      Ahmad Imran  from Faisalabad, pakistan
select rownum from (select 1 from dual group by cube(1,1,1,1,1,1)) where
rownum<=50;

also prints the 50 numbers

      Rating: 2
To Original
      Question  October 02, 2003
Reviewer:  Muhammad
      Ahmad Imran  from Faisalabad, Pakistan
  1  select a.*, b.*,
  2  decode( b.x, 2, '<<<<====' )
  3    from
  4  ( select rownum r, ename,hiredate
  5     from emp
  6    where rownum <= 10) a,
  7  ( select 1 x from dual
  8     union all
  9    select 2 x from dual) b
 10    where b.x = 1
 11       or (b.x = 2 and mod(a.r,4) = 0)
 12*  order by a.hiredate
SQL> /

         R ENAME      HIREDATE           X DECODE(B
---------- ---------- --------- ---------- --------
         1 SMITH      17-DEC-80          1
         2 ALLEN      20-FEB-81          1
         3 WARD       22-FEB-81          1
         4 JONES      02-APR-81          1
         4 JONES      02-APR-81          2 <<<<====
         6 BLAKE      01-MAY-81          1
         7 CLARK      09-JUN-81          1
        10 TURNER     08-SEP-81          1
         5 MARTIN     28-SEP-81          1
         9 KING       17-NOV-81          1
         8 SCOTT      19-APR-87          1
         8 SCOTT      19-APR-87          2 <<<<====

12 rows selected.

SQL>

So I actually want to be duplicate records by hiredate then what to do?

          Followup:

sorr, not following you -- what do you mean "by hiredate"

      Rating:
      1
  October 03, 2003
Reviewer:  Muhammad
      Ahmad Imran  from Faisalabad, Pakistan
In the last line I put a clause of order by a.hiredate then ruesults are
disturbed

************************
Look this is what I want
*************************

         R ENAME      HIREDATE           X DECODE(B
---------- ---------- --------- ---------- --------
         1 SMITH      17-DEC-80          1
         2 ALLEN      20-FEB-81          1
         3 WARD       22-FEB-81          1
         4 JONES      02-APR-81          1
         4 JONES      02-APR-81          2 <<<<====
         6 BLAKE      01-MAY-81          1
         7 CLARK      09-JUN-81          1
        10 TURNER     08-SEP-81          1
         5 MARTIN     28-SEP-81          1
         5 MARTIN     28-SEP-81          2 <<<<====
         9 KING       17-NOV-81          1
         8 SCOTT      19-APR-87          1

12 rows selected.

so I want results displaying twice order by hiredate not by rownum

          Followup:

it helps to be "clear".  I see lots of information every day -- its really hard
to "guess" what people mean.

you post a query (that looks reasonable), a result set that looks fine, and say
simply

"So I actually want to be duplicate records by hiredate then what to do? "

which isn't really a sentence.... makes it sort of "hard".

you need to assign the rownum to the EMP data AFTER sorting

scott@ORA920> select a.*, b.*,
  2  decode( b.x, 2, '<<<<====' )
  3    from
  4  ( select rownum r,
  5           ename,
  6           hiredate
  7      from ( select ename, hiredate
  8               from emp
  9              order by hiredate
 10           )
 11    where rownum <= 10) a,
 12  ( select 1 x from dual
 13     union all
 14    select 2 x from dual) b
 15    where b.x = 1
 16       or (b.x = 2 and mod(a.r,4) = 0)
 17   order by a.hiredate
 18  /

         R ENAME      HIREDATE           X DECODE(B
---------- ---------- --------- ---------- --------
         1 SMITH      17-DEC-80          1
         2 ALLEN      20-FEB-81          1
         3 WARD       22-FEB-81          1
         4 JONES      02-APR-81          1
         4 JONES      02-APR-81          2 <<<<====
         5 BLAKE      01-MAY-81          1
         6 CLARK      09-JUN-81          1
         7 TURNER     08-SEP-81          1
         8 MARTIN     28-SEP-81          1
         8 MARTIN     28-SEP-81          2 <<<<====
         9 KING       17-NOV-81          1
        10 JAMES      03-DEC-81          1

12 rows selected.

      Rating: 3
1 Row
      ??  October 03, 2003
Reviewer:  Matt
       from UK
I understand 64 rows however:

SELECT 1 FROM dual GROUP BY cube (1,1,1,1,1,1)

Returns 25 Rows in my database 8.1.7.4

Why ???

          Followup:

dual, magic, special, different, many special things shown about it above.

      Rating: 4
TOAD
      !  October 03, 2003
Reviewer:  Matt
       from UK
Oops it seems it's TOAD thats causing 25 errors - run in SQL*Plus I see one as
expected.

Like to know the explanation - but i'll ask the Toad mail list.

      Rating: 5
Excellent
        October 04, 2003
Reviewer:  Muhammad Ahmad
      Imran  from Faisalabad, Pakistan
That's great. Thanks a lot

      Rating: 5
ARRAYSIZE is
      the source of this unexpected behavior  November 03, 2003

Reviewer:  Art Metzer  from Minnesota, USA

To those who've noticed less than POWER(2,n) rows when you perform SELECT NULL
FROM DUAL GROUP BY CUBE (1,1,1,1,...) [n 1's], ARRAYSIZE is the culprit, but
*ONLY* if you select directly from DUAL.  If you wrap the SELECT NULL FROM DUAL
GROUP BY CUBE(1,1,1,...) in an in-line view, the expected behavior results.

SQL> SHOW ARRAYSIZE
arraysize 15
SQL> SELECT NULL
  2  FROM DUAL
  3  GROUP BY CUBE(1,1,1,1,1) -- 32 rows expected
  4  /

N
-

15 rows selected.

SQL> SET ARRAYSIZE 50
SQL> SELECT NULL
  2  FROM DUAL
  3  GROUP BY CUBE(1,1,1,1,1)
  4  /

N
-

32 rows selected.

SQL> SET ARRAYSIZE 15
SQL> SELECT ROWNUM
  2  FROM
  3  (SELECT NULL
  4  FROM DUAL
  5  GROUP BY CUBE(1,1,1,1,1)
  6  ) -- this one wrapped in an in-line view
  7  /

    ROWNUM
----------
         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

32 rows selected.

SQL>

Does anyone have any idea why ARRAYSIZE influences GROUP BY CUBE in this manner?

Thanks, and HTH,

Art.

      Rating:
      2
CUBE  November 12, 2003
Reviewer:
      Narendar  from India
This was good but we seldom use this case in real, Just a brief clear
description of the CUBE might have resolved all long conversations and the
example of Mr.Richard was good for others to understand how the cube works.

October 03, 2003 Omar -- Thanks for the question regarding "How to
      pass a Pl/SQL Table?", version 8.1.0

      You Asked
I want to pass a PL/SQL table from one procedure in a package as an input
parameter to another function in another  package to process the information
stored in this PL/SQL table is it possible?

      and we said...
yes, you just "do it".

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package pkg1
  2  as
  3          type array is table of number index by binary_integer;
  4
  5          procedure p;
  6  end;
  7  /

Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package pkg2
  2  as
  3          procedure p( p_data in pkg1.array );
  4  end;
  5  /

Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package body pkg1
  2  as
  3
  4  procedure p
  5  is
  6          l_data array;
  7  begin
  8          for i in 1 .. 10 loop
  9                  l_data(i) := i;
 10          end loop;
 11
 12          pkg2.p( l_data );
 13  end;
 14
 15  end;
 16  /

Package body created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
Review &
      Followup

      Rating:
      3
  March 09, 2002
Reviewer:  Peter

Is it possible to pass a table of records or a record of tables as a parameter?

          Followup:

both -- yes.

      Rating: 4
Thanks
      Tom  March 10, 2002
Reviewer:  Omar Foda

Your answer was clear and useful for me, It is the first time I ask and thanks a
lot for the kind response.

      Rating: 4
How to pass a
      Table of Records(TOR) using Execute Immediate  October 03,
      2003
Reviewer:  Martin van Donselaar  from The
      Netherlands
Tom,

Thanks, you're reply is very usefull as usual. But I have a sligthly different
problem:

I want to pass a TOR using execute Immediate, like this:

l_exec := 'Begin ecaesar.'||r_mapping.MAPPING||'.main(:X); end;';
EXECUTE IMMEDIATE l_exec USING MY_TOR;

But i get: PLS-00457: expressions have to be of SQL types

How would i solve this Problem?

          Followup:

you would have to use a collection of SQL objects

create type myScalarType as object
( x int,
  y date,
  z varchar2(25)
)
/
create type myTableType as table of myScalarType
/

do that in sql and then you can use myTableType in your code and dynamically
pass it.

Short of that, you'll have to change all of your routines to access a package
global variable -- eg:

create package get_your_inputs_here
as
   g_global_data_everyone_can_see  my_tor_type;
end;
/

and you would:

    get_your_inputs_here.my_tor_type := my_tor;
    execute immediate 'begin ecaesar.' || r_mapping.mapping || '.main; end;';
    my_tor := get_your.... (if they are OUT parameters)

for, as the error states, only SQL (not plsql) types work with native dynamic
SQL.

October 03, 2003 Raymond -- Thanks for the question regarding
      "Multiple Inserts", version 8.1.7

      You Asked
The problem is that when l sum the result is not considering my ah.. columns
which is (where ah.. like '% African Bank%')
Thats why  my result is not balancing. Column ah1386 maybe African bank while
ah2386 is ABC. So if its not african bank for the bsps it should not add the
column. Thats where my problem is. l don't know how to write the code in such a
way that
it should look at (AH1386 if the value in the column is African bank and the
value is $$ ,
AH2386 if the value in the column is African bank and the value is $$ ,
AH3386 if the value in the column is ABC and the value is $$, my script should
add up all the values with ah.. like african bank then subtract from the
capital.
The ten statements as requested.

CREATE OR REPLACE PROCEDURE MIS_PERSAL_PROC
       (v_start_date DATE, v_end_date DATE)

IS
   v_cnt   NUMBER;
BEGIN

--  BSPs  ----------------------------------------------------------------------
--  BSPs no. 1  --
   INSERT INTO MIS_PERSAL
               (branch, total, loan_count, summary_date, change_date)
      (SELECT   brn386, SUM (da1386), COUNT (da1386), SYSDATE, v_start_date
           FROM PW38600P
          WHERE bro386 NOT IN ('SBSA',
                               'KIOSKS',
                               'HMC',
                               'CREDIT',
                               'ESP',
                               'PSA',
                               'AIN',
                               'HOSPERSA',
                               'REGIONAL',
                               'COVERHOLD',
                               'WACC',
                               'NATU',
                               'ACC CARE',
                               'TAF BROK',
                               'AB CC'
                              )
            AND lof386 NOT IN
                    ('153', '1114', '97', '4843', '4849', '1157', 'E0080',
'J0196')
            AND ah1386 NOT LIKE '%AFRICAN BANK%'
            AND did386 BETWEEN v_start_date AND v_end_date
       GROUP BY brn386);

--  BSPs no. 2  --
   INSERT INTO MIS_PERSAL
               (branch, total, loan_count, summary_date, change_date)
      (SELECT   brn386, SUM (da2386), COUNT (da2386), SYSDATE, v_start_date
           FROM PW38600P
          WHERE bro386 NOT IN ('SBSA',
                               'KIOSKS',
                               'HMC',
                               'CREDIT',
                               'ESP',
                               'PSA',
                               'AIN',
                               'HOSPERSA',
                               'REGIONAL',
                               'COVERHOLD',
                               'WACC',
                               'NATU',
                               'ACC CARE',
                               'TAF BROK',
                               'AB CC'
                              )
            AND lof386 NOT IN
                    ('153', '1114', '97', '4843', '4849', '1157', 'E0080',
'J0196')
            AND ah2386 NOT LIKE '%AFRICAN BANK%'
            AND did386 BETWEEN v_start_date AND v_end_date
       GROUP BY brn386);

--  BSPs no. 3  --
   INSERT INTO MIS_PERSAL
               (branch, total, loan_count, summary_date, change_date)
      (SELECT   brn386, SUM (da3386), COUNT (da3386), SYSDATE, v_start_date
           FROM PW38600P
          WHERE bro386 NOT IN ('SBSA',
                               'KIOSKS',
                               'HMC',
                               'CREDIT',
                               'ESP',
                               'PSA',
                               'AIN',
                               'HOSPERSA',
                               'REGIONAL',
                               'COVERHOLD',
                               'WACC',
                               'NATU',
                               'ACC CARE',
                               'TAF BROK',
                               'AB CC'
                              )
            AND lof386 NOT IN
                    ('153', '1114', '97', '4843', '4849', '1157', 'E0080',
'J0196')
            AND ah3386 NOT LIKE '%AFRICAN BANK%'
            AND did386 BETWEEN v_start_date AND v_end_date
       GROUP BY brn386);

--  BSPs no. 4  --
   INSERT INTO MIS_PERSAL
               (branch, total, loan_count, summary_date, change_date)
      (SELECT   brn386, SUM (da4386), COUNT (da4386), SYSDATE, v_start_date
           FROM PW38600P
          WHERE bro386 NOT IN ('SBSA',
                               'KIOSKS',
                               'HMC',
                               'CREDIT',
                               'ESP',
                               'PSA',
                               'AIN',
                               'HOSPERSA',
                               'REGIONAL',
                               'COVERHOLD',
                               'WACC',
                               'NATU',
                               'ACC CARE',
                               'TAF BROK',
                               'AB CC'
                              )
            AND lof386 NOT IN
                    ('153', '1114', '97', '4843', '4849', '1157', 'E0080',
'J0196')
            AND ah4386 NOT LIKE '%AFRICAN BANK%'
            AND did386 BETWEEN v_start_date AND v_end_date
       GROUP BY brn386);

--  BSPs no. 5  --
   INSERT INTO MIS_PERSAL
               (branch, total, loan_count, summary_date, change_date)
      (SELECT   brn386, SUM (da5386), COUNT (da5386), SYSDATE, v_start_date
           FROM PW38600P
          WHERE bro386 NOT IN ('SBSA',
                               'KIOSKS',
                               'HMC',
                               'CREDIT',
                               'ESP',
                               'PSA',
                               'AIN',
                               'HOSPERSA',
                               'REGIONAL',
                               'COVERHOLD',
                               'WACC',
                               'NATU',
                               'ACC CARE',
                               'TAF BROK',
                               'AB CC'
                              )
            AND lof386 NOT IN
                    ('153', '1114', '97', '4843', '4849', '1157', 'E0080',
'J0196')
            AND ah5386 NOT LIKE '%AFRICAN BANK%'
            AND did386 BETWEEN v_start_date AND v_end_date
       GROUP BY brn386);

--  BSPs no. 6  --
   INSERT INTO MIS_PERSAL
               (branch, total, loan_count, summary_date, change_date)
      (SELECT   brn386, SUM (da6386), COUNT (da6386), SYSDATE, v_start_date
           FROM PW38600P
          WHERE bro386 NOT IN ('SBSA',
                               'KIOSKS',
                               'HMC',
                               'CREDIT',
                               'ESP',
                               'PSA',
                               'AIN',
                               'HOSPERSA',
                               'REGIONAL',
                               'COVERHOLD',
                               'WACC',
                               'NATU',
                               'ACC CARE',
                               'TAF BROK',
                               'AB CC'
                              )
            AND lof386 NOT IN
                    ('153', '1114', '97', '4843', '4849', '1157', 'E0080',
'J0196')
            AND ah6386 NOT LIKE '%AFRICAN BANK%'
            AND did386 BETWEEN v_start_date AND v_end_date
       GROUP BY brn386);

--  BSPs no. 7  --
   INSERT INTO MIS_PERSAL
               (branch, total, loan_count, summary_date, change_date)
      (SELECT   brn386, SUM (da7386), COUNT (da7386), SYSDATE, v_start_date
           FROM PW38600P
          WHERE bro386 NOT IN ('SBSA',
                               'KIOSKS',
                               'HMC',
                               'CREDIT',
                               'ESP',
                               'PSA',
                               'AIN',
                               'HOSPERSA',
                               'REGIONAL',
                               'COVERHOLD',
                               'WACC',
                               'NATU',
                               'ACC CARE',
                               'TAF BROK',
                               'AB CC'
                              )
            AND lof386 NOT IN
                    ('153', '1114', '97', '4843', '4849', '1157', 'E0080',
'J0196')
            AND ah7386 NOT LIKE '%AFRICAN BANK%'
            AND did386 BETWEEN v_start_date AND v_end_date
       GROUP BY brn386);

--  BSPs no. 8  --
   INSERT INTO MIS_PERSAL
               (branch, total, loan_count, summary_date, change_date)
      (SELECT   brn386, SUM (da8386), COUNT (da8386), SYSDATE, v_start_date
           FROM PW38600P
          WHERE bro386 NOT IN ('SBSA',
                               'KIOSKS',
                               'HMC',
                               'CREDIT',
                               'ESP',
                               'PSA',
                               'AIN',
                               'HOSPERSA',
                               'REGIONAL',
                               'COVERHOLD',
                               'WACC',
                               'NATU',
                               'ACC CARE',
                               'TAF BROK',
                               'AB CC'
                              )
            AND lof386 NOT IN
                    ('153', '1114', '97', '4843', '4849', '1157', 'E0080',
'J0196')
            AND ah8386 NOT LIKE '%AFRICAN BANK%'
            AND did386 BETWEEN v_start_date AND v_end_date
       GROUP BY brn386);

--  BSPs no. 9  --
   INSERT INTO MIS_PERSAL
               (branch, total, loan_count, summary_date, change_date)
      (SELECT   brn386, SUM (da9386), COUNT (da9386), SYSDATE, v_start_date
           FROM PW38600P
          WHERE bro386 NOT IN ('SBSA',
                               'KIOSKS',
                               'HMC',
                               'CREDIT',
                               'ESP',
                               'PSA',
                               'AIN',
                               'HOSPERSA',
                               'REGIONAL',
                               'COVERHOLD',
                               'WACC',
                               'NATU',
                               'ACC CARE',
                               'TAF BROK',
                               'AB CC'
                              )
            AND lof386 NOT IN
                    ('153', '1114', '97', '4843', '4849', '1157', 'E0080',
'J0196')
            AND ah9386 NOT LIKE '%AFRICAN BANK%'
            AND did386 BETWEEN v_start_date AND v_end_date
       GROUP BY brn386);

--  BSPs no. 0  --
   INSERT INTO MIS_PERSAL
               (branch, total, loan_count, summary_date, change_date)
      (SELECT   brn386, SUM (da0386), COUNT (da0386), SYSDATE, v_start_date
           FROM PW38600P
          WHERE bro386 NOT IN ('SBSA',
                               'KIOSKS',
                               'HMC',
                               'CREDIT',
                               'ESP',
                               'PSA',
                               'AIN',
                               'HOSPERSA',
                               'REGIONAL',
                               'COVERHOLD',
                               'WACC',
                               'NATU',
                               'ACC CARE',
                               'TAF BROK',
                               'AB CC'
                              )
            AND lof386 NOT IN
                    ('153', '1114', '97', '4843', '4849', '1157', 'E0080',
'J0196')
            AND ah0386 NOT LIKE '%AFRICAN BANK%'
            AND did386 BETWEEN v_start_date AND v_end_date
       GROUP BY brn386);

----------------------------------------------------------------------  BSPs  --

--  HMCs  ----------------------------------------------------------------------
--  HMCs no. 1  --
   INSERT INTO MIS_PERSAL
               (branch, brocker, total, loan_count, summary_date, change_date)
      (SELECT   brn386, bro386, SUM (da1386), COUNT (da1386), SYSDATE,
                v_start_date
           FROM PW38600P
          WHERE bro386 = 'HMC'
            AND ah1386 NOT LIKE '%AFRICAN BANK%'
            AND did386 BETWEEN v_start_date AND v_end_date
       GROUP BY brn386, bro386);

--  HMCs no. 2  --
   INSERT INTO MIS_PERSAL
               (branch, brocker, total, loan_count, summary_date, change_date)
      (SELECT   brn386, bro386, SUM (da2386), COUNT (da2386), SYSDATE,
                v_start_date
           FROM PW38600P
          WHERE bro386 = 'HMC'
            AND ah2386 NOT LIKE '%AFRICAN BANK%'
            AND did386 BETWEEN v_start_date AND v_end_date
       GROUP BY brn386, bro386);

--  HMCs no. 3  --
   INSERT INTO MIS_PERSAL
               (branch, brocker, total, loan_count, summary_date, change_date)
      (SELECT   brn386, bro386, SUM (da3386), COUNT (da3386), SYSDATE,
                v_start_date
           FROM PW38600P
          WHERE bro386 = 'HMC'
            AND ah3386 NOT LIKE '%AFRICAN BANK%'
            AND did386 BETWEEN v_start_date AND v_end_date
       GROUP BY brn386, bro386);

--  HMCs no. 4  --
   INSERT INTO MIS_PERSAL
               (branch, brocker, total, loan_count, summary_date, change_date)
      (SELECT   brn386, bro386, SUM (da4386), COUNT (da4386), SYSDATE,
                v_start_date
           FROM PW38600P
          WHERE bro386 = 'HMC'
            AND ah4386 NOT LIKE '%AFRICAN BANK%'
            AND did386 BETWEEN v_start_date AND v_end_date
       GROUP BY brn386, bro386);

--  HMCs no. 5  --
   INSERT INTO MIS_PERSAL
               (branch, brocker, total, loan_count, summary_date, change_date)
      (SELECT   brn386, bro386, SUM (da5386), COUNT (da5386), SYSDATE,
                v_start_date
           FROM PW38600P
          WHERE bro386 = 'HMC'
            AND ah5386 NOT LIKE '%AFRICAN BANK%'
            AND did386 BETWEEN v_start_date AND v_end_date
       GROUP BY brn386, bro386);

--  HMCs no. 6  --
   INSERT INTO MIS_PERSAL
               (branch, brocker, total, loan_count, summary_date, change_date)
      (SELECT   brn386, bro386, SUM (da6386), COUNT (da6386), SYSDATE,
                v_start_date
           FROM PW38600P
          WHERE bro386 = 'HMC'
            AND ah6386 NOT LIKE '%AFRICAN BANK%'
            AND did386 BETWEEN v_start_date AND v_end_date
       GROUP BY brn386, bro386);

--  HMCs no. 7  --
   INSERT INTO MIS_PERSAL
               (branch, brocker, total, loan_count, summary_date, change_date)
      (SELECT   brn386, bro386, SUM (da7386), COUNT (da7386), SYSDATE,
                v_start_date
           FROM PW38600P
          WHERE bro386 = 'HMC'
            AND ah7386 NOT LIKE '%AFRICAN BANK%'
            AND did386 BETWEEN v_start_date AND v_end_date
       GROUP BY brn386, bro386);

--  HMCs no. 8  --
   INSERT INTO MIS_PERSAL
               (branch, brocker, total, loan_count, summary_date, change_date)
      (SELECT   brn386, bro386, SUM (da8386), COUNT (da8386), SYSDATE,
                v_start_date
           FROM PW38600P
          WHERE bro386 = 'HMC'
            AND ah8386 NOT LIKE '%AFRICAN BANK%'
            AND did386 BETWEEN v_start_date AND v_end_date
       GROUP BY brn386, bro386);

--  HMCs no. 9  --
   INSERT INTO MIS_PERSAL
               (branch, brocker, total, loan_count, summary_date, change_date)
      (SELECT   brn386, bro386, SUM (da9386), COUNT (da9386), SYSDATE,
                v_start_date
           FROM PW38600P
          WHERE bro386 = 'HMC'
            AND ah9386 NOT LIKE '%AFRICAN BANK%'
            AND did386 BETWEEN v_start_date AND v_end_date
       GROUP BY brn386, bro386);

--  HMCs no. 0  --
   INSERT INTO MIS_PERSAL
               (branch, brocker, total, loan_count, summary_date, change_date)
      (SELECT   brn386, bro386, SUM (da0386), COUNT (da0386), SYSDATE,
                v_start_date
           FROM PW38600P
          WHERE bro386 = 'HMC'
            AND ah0386 NOT LIKE '%AFRICAN BANK%'
            AND did386 BETWEEN v_start_date AND v_end_date
       GROUP BY brn386, bro386);

----------------------------------------------------------------
--- Check for Duplicates Before Inserting into History Table --
----------------------------------------------------------------

   v_cnt := 0;

   SELECT COUNT (*)
     INTO v_cnt
     FROM MIS_PERSAL_HIST
    WHERE TO_DATE(v_start_date,'yyyy-mm-dd') =
                                           TO_DATE(change_date,'yyyy-mm-dd')
      AND TO_DATE(SYSDATE,'yyyy-mm-dd') =
                                          TO_DATE(summary_date,'yyyy-mm-dd');

   IF v_cnt = 0
   THEN
      INSERT INTO MIS_PERSAL_HIST
                  (branch, brocker, sbrockercd, total, loan_count, change_date,
                   summary_date)
         SELECT branch, brocker, sbrockercd, total, loan_count, change_date,
                summary_date
           FROM MIS_PERSAL;
   END IF;

   COMMIT;
END;
/

      and we said...
what about this then:

declare
   type array is table of varchar2(30);

   l_columns array := array( 'da1386', 'da2386', .... 'da0386' );
begin
   for i in 1 .. l_columns.count
   loop
     execute immediate
     'INSERT INTO MIS_PERSAL
      (branch, total, loan_count, summary_date, change_date)
      SELECT   brn386, SUM (' || l_columns(i) || '),
                     COUNT (' || l_columns(i) || '), SYSDATE, :v_start_date
        FROM PW38600P
       WHERE bro386 NOT IN (''SBSA'', ''KIOSKS'', ''HMC'', ''CREDIT'',
                            ''ESP'', ''PSA'', ''AIN'', ''HOSPERSA'',
                            ''REGIONAL'', ''COVERHOLD'', ''WACC'',
                            ''NATU'', ''ACC CARE'', ''TAF BROK'', ''AB CC'')
        AND lof386 NOT IN (''153'', ''1114'', ''97'', ''4843'', ''4849'',
                           ''1157'', ''E0080'', ''J0196'')
        AND ah0386 NOT LIKE ''%AFRICAN BANK%''
        AND did386 BETWEEN :v_start_date AND :v_end_date
      GROUP BY brn386'
      using v_start_date, v_start_date, v_end_date;
   end loop;

....

I'd go a step further and create a view:

create view V
as
 SELECT *
   FROM PW38600P
  WHERE bro386 NOT IN ('SBSA', 'KIOSKS', 'HMC', 'CREDIT',
                       'ESP', 'PSA', 'AIN', 'HOSPERSA',
                       'REGIONAL', 'COVERHOLD', 'WACC',
                       'NATU', 'ACC CARE', 'TAF BROK', 'AB CC')
   AND lof386 NOT IN ('153', '1114', '97', '4843', '4849',
                      '1157', 'E0080', 'J0196')
   AND ah0386 NOT LIKE '%AFRICAN BANK%'

and the code becomes:

declare
   type array is table of varchar2(30);

   l_columns array := array( 'da1386', 'da2386', .... 'da0386' );
begin
   for i in 1 .. l_columns.count
   loop
     execute immediate
     'INSERT INTO MIS_PERSAL
      (branch, total, loan_count, summary_date, change_date)
      SELECT   brn386, SUM (' || l_columns(i) || '),
                     COUNT (' || l_columns(i) || '), SYSDATE, :v_start_date
        FROM V
       WHERE did386 BETWEEN :v_start_date AND :v_end_date
      GROUP BY brn386'
      using v_start_date, v_start_date, v_end_date;
   end loop;

Review &
      Followup

      Rating:
      5
  March 11, 2002
Reviewer:
      A reader
Thank very much Tom it works.You have a special way when approaching problems
and l've learnt quite a lot form your responses and other problems that you've
solved for other people.

      Rating:
      5
  March 11, 2002
Reviewer:  Raymond
       from south Africa
Thank very much Tom it works.You have a special way when approaching problems
and l've learnt quite a lot form your responses and other problems that you've
solved for other people.

      Rating: 4
Multiple
      Inserts  January 06, 2003
Reviewer:  Steve
       from NYC, USA
Hi Tom,

I have 2 inserts in my sqlplus script and in the second insert it referense the
table in the first Insert.

alter session force parallel query parallel 12

truncate table A

insert into A (C1,c2,c3,c4)
select ...
from ...
where

truncate table B

insert into B (c5,c6,c7...)
select ....
from A, C, D
where...

My question is that if first insert take 10 min to finish,
how to force second insert wait until the first insert is done.
It seems that 2nd insert begins running while 1st insert is still running
because I got 0 rows inserted. if I manually run 1st insert , wait to complete
and then run 2nd insert
there is no problem. Is this because of parallel server environment?

Thank you!

Steve

          Followup:

if that is what you have in a sqlplus script -- then they are running serially,
no differently then when you run them "by hand".  SQLPLUs is not parallelizing
across statements, the database is parallelizing within statements only.

Something else is happening here -- you need to diagnose this further.  It is
not because of parallelism!

      Rating: 5
multiple
      insert  January 06, 2003
Reviewer:  Steve

Hi Tom,

Your quote"
if that is what you have in a sqlplus script -- then they are running serially"

I am sorry I forgot mention the following command in the sql script:

ALTER SESSION ENABLE PARALLEL DML;

Does that cause parallel insert operation?

Thanks!

Steve

          Followup:

Each INSERT could be paralellized but SQLPLUS will run each insert one after the
other.

It is as if you typed them in -- nothing more, nothing less.  Typing it in will
give no different effect than running the script.

      Rating: 3
Parallelizing
      insert/update with cursors  April 17, 2003

Reviewer:  vj
Hi tom,

I am new to the concept of parallelizing insert/update etc., I have a question
about parallelizing insert/update with cursors. Can we use the parallel hint
when we have a structure like this and would it be of any use.

--
begin
cursor c1 is select * from t1;

for i in c1 loop
update /*+ PARALLEL(t2,4) */
set t2.fld1=i.fld1
end loop;
end;
-----

          Followup:

read this:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96524/c20paral.htm#365
there is more to pdml.

      Rating:
      4
  June 27, 2003
Reviewer:
      A reader

      Rating: 4
Passing array
      to another stored proc?  June 27, 2003
Reviewer:
      ht  from California
How can I implement this solution to avoid multiple explicit inserts?
For example, PSP procA uses your pl/sql table technique to create a matrix with
day of the week on the x-axis and time of the day on the y-axis.  Within each
cell, the user has the ability to select pager/email/both/none from a dropdown
list.  Instead of having to hard-code each value selected, I would like PSP
procB (below) to read it dynamically.
Can this be done?
Thanks.

declare
   type dayarray is table of varchar2(20);
   lc_daylist dayarray := dayarray
('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday');
   type timearray is table of varchar2(20);
   lc_timelist timearray := timearray (
'12am','1am','2am','3am','4am','5am','6am','7am','9am','10am','11am',
   '12pm','1pm','2pm','3pm','4pm','5pm','6pm','7pm','8pm','9pm','10pm','11pm');
begin
   for i in 1 .. lc_daylist.count
   loop
   for j in 1 .. lc_timelist.count
   loop
insert into notification (day,time,type)
values(lc_daylist(i),lc_timelist(j),<notification_type_passed_into_proc>) ...
...
   end loop;
   end loop;
end;

          Followup:

does not compute, don't understand the issue.

      Rating: 3
How does one
      pass values from multiple dropdown lists to a stored
      proc?  June 28, 2003
Reviewer:  ht
       from California
Tom,
I have 3 PSP stored procs.
The first one generates a page that allows a user to select how he wants to be
notified depending on the day and time.
The second one receives each selection for each iteration of day/time.
The third one inserts the selection/time/day into a db table.
I understand how to accomplish this with a db lookup table and hard-coded
selection parameters but I would like to use a pl/sql table and array lists so I
won't have to store each day of the week and time of day in a db table.
Additionally, my code will be more readable if I don't have to have an insert
for each day and time (at least 84 insert statements).
Below is my code:
proc1:
...
<%!
type dayarray is table of varchar2(20);
        lc_daylist dayarray := dayarray
('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday'
,'Saturday');
type timearray is table of varchar2(20);
        lc_timelist timearray := timearray (
'12am','1am','2am','3am','4am','5am','6am','7am','8am',
'9am','10am','11am','12pm','1pm','2pm','3pm','4pm','5pm','6pm','7pm','8pm','9pm',
'10pm','11pm');
%>
...
<table>
<%
begin
%>
        <td class="yaxiscblabel1">
<%
        for i in 1 .. lc_timelist.count loop
%>
                <td class="yaxiscblabel1"><%=lc_timelist(i)%>
<%
        end loop;
         for j in 1 .. lc_daylist.count loop
%>
                <tr> <td class="xaxiscblabel1"><%=lc_daylist(j)%>
<%
        for i in 1 .. lc_timelist.count loop
%>
        <td>
 --->>>>>>>>              <select class="text1"
name="psp_<%=lc_daylist(j)%><%=lc_timelist(i)%>">
                        <option value=1 selected>Email
                        <option value=2>Pager
                        <option value=3>Email/Pager
                        <option value=4>None
                </select>
<%
        end loop;
        end loop;
exception when others then htp.init; <error in proc1>);
end;
%>

</table>
...
proc2:
...
<%@ plsql parameter="psp_Sunday12am" type="number" %>
<%@ plsql parameter="psp_Sunday1am" type="number" %>
<%@ plsql parameter="psp_Sunday2am" type="number" %>
<%@ plsql parameter="psp_Sunday3am" type="number" %>
...
<%!
type dayarray is table of varchar2(20);
        lc_daylist dayarray := dayarray
('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday'
,'Saturday');
type timearray is table of varchar2(20);
        lc_timelist timearray := timearray (
'12am','1am','2am','3am','4am','5am','6am','7am','8am',
'9am','10am','11am','12pm','1pm','2pm','3pm','4pm','5pm','6pm','7pm','8pm','9pm',
'10pm','11pm');
psp_gnotification_type varchar2(25);
%>
...
<%
begin
        for j in 1 .. lc_daylist.count loop
        for i in 1 .. lc_timelist.count loop
  proc3(
-->>>>>>>        ' || psp_gnotification_type || ',
         '||lc_daylist(j)||',
          to_date('||lc_timelist(i)||','hhpm'),
          to_date('||lc_timelist(i)||','hhpm'
)
                        );
        end loop;
        end loop;
end;
        exception when others then
                htp.init;<error in proc2>);
%>

</table>
...
proc3:  1 insert statement that loops through for each day/time/selection.

Thanks,
ht

          Followup:

sorry, I'm not getting it.  I don't see what the problem is

a) you don't have 84 inserts anywhere and I cannot imagine why you believe you
need them

b) you seem to have it table driven already.

      Rating: 3
More
      info  June 28, 2003
Reviewer:  ht  from
      California
I didn't include this piece of code for proc2 - which is critical for your
troubleshooting:
                psp_gnotification_type := 'psp_'||lc_daylist(j)||lc_timelist(i);

It concatenates a string that represents the notification type selected in
proc1.

Thanks again.

      Rating: 3
More info on
      how to prevent multiple insert statements in PSP.  June 29,
      2003
Reviewer:  ht  from California
Tom,
Sorry for the confusion.

Instead of these 2 stored procedures:

PSP Proc 1a (enables the user to select a notification type for each day of the
week and hour of the day):

...<table>
        <%--Loop through each hour of the day, starting with 12am to print
yaxis.--%>
        <td class="yaxiscblabel1">
        <% for tod in c2 loop %>
                <td class="yaxiscblabel1"><%=tod.timeofday%>
        <% end loop; %>
        <%--Loop through each day of the week, starting with Sunday to print
xaxis.--%>
        <% for dow in c1 loop %>
                <tr>
                <td class="xaxiscblabel1"><%=dow.dayofweek%>
        <%--Display the notification type dropdown list.--%>
        <% for tod in c2 loop %>
        <td>
                <select class="text1"
name="psp_<%=dow.dayofweek%><%=tod.timeofday%>">
                        <option value=1 selected>Email
                        <option value=2>Pager
                        <option value=3>Email/Pager
                        <option value=4>None
                </select>
        <% end loop; %>
        <% end loop; %>
</table>...

PSP Proc 2a (inserts the notification type for each day of the week and hour of
the day):

...<%
begin
gnotification_ins2(psp_Sunday12am,...);
gnotification_ins2(psp_Sunday1am,...);
gnotification_ins2(psp_Sunday2am,...);
...
gnotification_ins2(psp_Saturday10pm,...);
gnotification_ins2(psp_Saturday11pm,...);
...
end
%>...

can you help me construct something like this?:

PSP Proc 1b (enables the user to select a notification type for each day of the
week and hour of the day):
...<table>

        <%--Loop through each hour of the day, starting with 12am to print
yaxis.--%>
        <td class="yaxiscblabel1">
        <% for tod in c2 loop %>
                <td class="yaxiscblabel1"><%=tod.timeofday%>
        <% end loop; %>
        <%--Loop through each day of the week, starting with Sunday to print
xaxis.--%>
        <% for dow in c1 loop %>
                <tr>
                <td class="xaxiscblabel1"><%=dow.dayofweek%>
        <%--Display the notification type dropdown list.--%>
        <% for tod in c2 loop %>
        <td>
                <select class="text1"
name="psp_<%=dow.dayofweek%><%=tod.timeofday%>">
                        <option value=1 selected>Email
                        <option value=2>Pager
                        <option value=3>Email/Pager
                        <option value=4>None
                </select>
        <% end loop; %>
        <% end loop; %>

</table> ...

PSP Proc 2b (inserts the notification type for each day of the week and hour of
the day):
...<%
begin
        for j in 1 .. lc_daylist.count loop
%>
<%
        for i in 1 .. lc_timelist.count loop
                psp_gnotification_type := 'psp_'||lc_daylist(j)||lc_timelist(i);
%>
<%
gnotification_ins2(' || ***psp_gnotification_type || ' ...);
*** Can this parameter be reconstructed and passed from Proc 1b instead of
having to perform inserts similar to Proc 2a?
        end loop;
        end loop;
end;
%> ...

Hopefully I've provided enough info this time but please let me know if what I'm
trying to accomplish is still not clear.

Thanks,
ht

          Followup:

you can pass arrays easily from page to page (i've always found this "asp like"
stuff virtually impossible to read myself, asp, jsp, psp, ug -- ugly)

I'm not going to be able to answer this here cause it is just not making any
sense.

I see all of the code, but I'm unable to relate it to anything.  You seem to be
passing arrays, you seem to be able to loop over them, I don't see what
parameter would need to be "reconstructed"

If I'm taking questions -- feel free to try there, we can have a good back and
forth, this is not the right place.

      Rating: 3
One more
      try.  June 29, 2003
Reviewer:  ht  from
      California
Tom,
Thanks for your assistance.  I'll try to ask you a question when you're
available but thought maybe I would try again in case I'm over-complicating the
question.
In it's simplest form, I'm trying to retrieve the value selected in a dropdown
list so I can insert it into a table.
This works:
proc1:
  psp_Sunday12am = Notify Me via Pager
  psp_Monday3pm = Notify Me via Email
  psp_Saturday5pm = Notify Me via Pager
proc2:
  insert into notification(notification_type)values(psp_Sunday12am);
  insert into notification(notification_type)values(psp_Monday3pm);
  insert into notification(notification_type)values(psp_Saturday5pm);

My question:
Is there a way I can change proc2 to a loop that will prevent 3 explicitly
defined inserts?  In other words, substitute "psp_Monday3pm" with
"psp_<item1.x>||<item2.x>"?

Thanks again for your persistence (especially on a weekend!) and I apologize for
not being able to explain this clearly.

ht

      Rating: 5
Not inserting
      .. why ?  October 03, 2003
Reviewer:
      A reader
I have a table in my local schema 'myschema' 'T', a table with the same name
exists in an other schema  'otherschema'.
There is this package which is used to insert into Table 'T', the package is
created under the schema  'otherschema'. 'myschema' has
full privileges over the said package.

Now interestingly when I do a insert from 'myschema' using the said package, the
rows are not getting insertinto my myschema.T.

Can you please illustrate the reasons behind this.

          Followup:

packages (procedures/functions) run as "definer" by default -- this is the
correct and proper way for them to run in general.

When "otherschema" created a package that did:

   insert into t values (.....)

that was implicitly turned into

   insert into otherschema.t values (.....)

The package will then run with the definers rights.  It is just the (very smart)
way it was designed to work.

It allows "otherschema" to set up code that manipulates tables "otherschema"
owns or has access to -- and then grant execute on that code to anyone else --
giving them the ability to manipulate those tables WITHOUT being able to
directly ACCESS those tables.

      Rating: 4
Another
      option.  October 03, 2003
Reviewer:  Kashif
       from Reston, VA
In 9i the original poster's problem could be solved using 1 or 2 multi-table
pivoting insert statements, something similar to the following:

INSERT ALL
INTO MIS_PERSAL
(branch, total, loan_count, summary_date, change_date)
values (brn386, sum_da1386, cnt_da1386, sys_date, v_start_date)
INTO MIS_PERSAL
(branch, total, loan_count, summary_date, change_date)
values (brn386, sum_da2386, cnt_da2386, sys_date, v_start_date)
      (SELECT brn386, SUM (da1386) sum_da1386, COUNT (da1386) cnt_da1386, SUM
(da2386) sum_da2386, COUNT (da2386) cnt_da2386, SYSDATE sys_date, v_start_date
           FROM PW38600P
          WHERE bro386 NOT IN ('SBSA',
                               'KIOSKS',
                               'HMC',
                               'CREDIT',
                               'ESP',
                               'PSA',
                               'AIN',
                               'HOSPERSA',
                               'REGIONAL',
                               'COVERHOLD',
                               'WACC',
                               'NATU',
                               'ACC CARE',
                               'TAF BROK',
                               'AB CC'
                              )
            AND lof386 NOT IN
                    ('153', '1114', '97', '4843', '4849', '1157', 'E0080',
'J0196')
            AND ah1386 NOT LIKE '%AFRICAN BANK%'
            AND did386 BETWEEN v_start_date AND v_end_date
       GROUP BY brn386);

Kashif

October 03, 2003 Ravi -- Thanks for the question regarding "extend
      in varrays and performance", version 8.1.5

      You Asked
Hi Tom,
I am using varrays in one of my stored procedure.
I am extending it dynamically using .extend.
When I analyzed the performance of it using dbms_profiler, I have seen that most
of the time is taken by extend of varrays.
    What should I do to reduce the time taken by it.
Do I need to set any parameter or any other methodology ?

Thanks
Ravi

      and we said...
how much time are we talking about.  Even in the best tuned code -- there will
be something that takes the most time.  Make sure you aren't trying to tune
something thats already pretty fast.

I did a quick and dirty test using varray's, nested tables and index by tables.
On the varray's and nested tables I allocated space two ways -- one time I did
an entry at a time, the other I did a "mass allocate" of all needed slots.  The
code is at the bottom but the profiler results are:
edi

Percentage of time in each module, summarized across runs

UNIT_OWNER  UNIT_NAME                       SECS PERCENTAG
----------- ------------------------------ ----- ---------
OPS$TKYTE   VARRAY_TEST                      .57     25.22
OPS$TKYTE   NESTEDTABLE_TEST                 .57     25.12
OPS$TKYTE   NESTEDTABLE_TEST2                .40     17.58
OPS$TKYTE   VARRAY_TEST2                     .39     17.44
OPS$TKYTE   INDEXBYTABLE_TEST                .33     14.58
<anonymous> <anonymous>                      .00       .04
SYS         DBMS_OUTPUT                      .00       .02
SYS         DBMS_PROFILER                    .00       .01

8 rows selected.

VARRAY_TEST was a varray with singleton extends (everytime I need a new slot I
would extend).

NESTEDTABLE_TEST was the same but for a nested table type.

in the "2" tests -- I used "array.extend(N)" with n = 512 to allocate as many
slots as I needed.

The indexbyTable_test used a PLSQL index by table (no extend, just assign to any
old slot).

The results speak for themselves -- there are efficiencies to be gained by using
index by tables (at least in Oracle8i release 3, YMMV)

Here is the test code I used:

create or replace procedure varray_test
as
    type arrayType is varray(512) of number;

    l_data arrayType;
begin
    for i in 1 .. 100
    loop
        l_data := arrayType();
        for i in 1 .. 512
        loop
            l_data.extend;
            l_data(i) := i;
        end loop;
    end loop;
end;
/

create or replace procedure varray_test2
as
    type arrayType is varray(512) of number;

    l_data arrayType;
begin
    for i in 1 .. 100
    loop
        l_data := arrayType();
        l_data.extend(512);
        for i in 1 .. 512
        loop
            l_data(i) := i;
        end loop;
    end loop;
end;
/

create or replace procedure nestedTable_test
as
    type arrayType is table of number;

    l_data arrayType;
begin
    for i in 1 .. 100
    loop
        l_data := arrayType();
        for i in 1 .. 512
        loop
            l_data.extend;
            l_data(i) := i;
        end loop;
    end loop;
end;
/

create or replace procedure nestedTable_test2
as
    type arrayType is table of number;

    l_data arrayType;
begin
    for i in 1 .. 100
    loop
        l_data := arrayType();
        l_data.extend(512);
        for i in 1 .. 512
        loop
            l_data(i) := i;
        end loop;
    end loop;
end;
/

create or replace procedure indexByTable_test
as
    type arrayType is table of number index by binary_integer;

    l_data arrayType;
    l_empty arrayType;
begin
    for i in 1 .. 100
    loop
        l_data := l_empty;
        for i in 1 .. 512
        loop
            l_data(i) := i;
        end loop;
    end loop;
end;
/

Review &
      Followup

      Rating: 5
arrays as I/O
      for stored procs  March 07, 2002
Reviewer:  Leon
      Rzhemovskiy  from Vancouver, BC Canada
Hi Tom,

I am using arrays extensively for about 1 year. It provides everything you need
in order to centralize business logic in database instead of statement execution
on a client/app server side, provide security, keep code consistent with
database changes, tune up etc. etc.

For some reason Oracle is not supporting idea of stored procedures as well as it
was before. Java, Java beans are not even close to benefits of stored
procedures, but it is a big push to implement Oracle applications using EJB
instead of stored procs with arrays as I/O.

Unfortunately arrays/ pl/sql tables are not truly integrated with client side
programming.
Even if pl/sql tables are faster and easier to program I have to use
1)    INDEXBYTABLE arrays for internal to database stored procedures, OCI and
asp/odbc clients. Please note that only Microsoft odbc driver supports PL/SQL
tables as input/output parameters. Oracle ODBC does not support and as far as I
found does not plan to support it
2)    VARARRAY with extend overhead looks like single way to do business with
java (jdbc) clients

Please correct me if I am wrong.

Thanks
Leon

          Followup:

index by tables with JDBC as of 817 as well...  Oracles OLEDB implementation
supports it.

But, I would not be using arrays to RETURN information to clients, I would be
using REF CURSORS personally.

      Rating: 4
Use of extend
      for Table and Varrays  March 08, 2002
Reviewer:
      Ravi Sharma  from Banglore, India
Hi Tom,
When I allocate the space for my table(TABLE OF NUMBER(17,4)) array using
extend, my stored procedure takes more time but when I don't use extend, it
takes less time.

Is it compulsary to use extend to allocate space ?
If Yes or No then Why ?
Can you list the Advantages and Disadvantages .
Thanks
Ravi

          Followup:

It depends on the type of collection (as demonstrated above).

NESTED TABLES, VARRAYS => you MUST allocate space using extend:

ops$tkyte@ORA8I.WORLD> declare
  2          type array is table of number(17,4);
  3
  4          l_data array := array();
  5  begin
  6          l_data(1) := 1;
  7  end;
  8  /
declare
*
ERROR at line 1:
ORA-06533: Subscript beyond count
ORA-06512: at line 6

PLSQL index by tables => you do not have too, in fact, you CANNOT use extend

ops$tkyte@ORA8I.WORLD> declare
  2          type array is table of number(17,4) INDEX BY BINARY_INTEGER;
  3
  4          l_data array;
  5  begin
  6          l_data(1) := 1;
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD> declare
  2          type array is table of number(17,4) INDEX BY BINARY_INTEGER;
  3
  4          l_data array;
  5  begin
  6          l_data.extend;
  7  end;
  8  /
        l_data.extend;
        *
ERROR at line 6:
ORA-06550: line 6, column 2:
PLS-00306: wrong number or types of arguments in call to 'EXTEND'
ORA-06550: line 6, column 2:
PL/SQL: Statement ignored

      Rating:
      5
cursor<->array  March 11, 2002

Reviewer:  Leon Rzhemovskiy  from Vancouver, BC,
      Canada
Hi Tom,

Thanks for your reply and 8.1.7  info.

My experience make me think that using arrays much more flexible and efficient.

1)    Inside of a database
Calling 1 stored proc from another with output as an array of values/cursor was
twice faster with array.
2)    Outside
Calling array guaranties that Oracle would not allocate resources to fetch
records for a client. Idea is: ASP is too slow to fetch records from cursor. It
is better to return everything in 1 call.

Flexibility:
Using cursor make you work with Oracle database in similar fashion as Microsoft
SQL Server. Stored procedure is container for single sql query. This query could
join number of tables and become difficult to optimize.

Arrays are giving ability
1)    Utilize true procedural approach (use functions and programming logic)
2)    Customizing (you can return sqlerrm with every row for example)

Your feedback about benefits of cursors over arrays is most welcome.

Thanks
Leon

      Rating: 5
performance of
      extend versuss insert into the table  June 13, 2003

Reviewer:  A reader  from ca
we had a scenario in which we were storing our
intermediate result into a sql object type.
After the entire operation was over, we transfered
the result into a table via insert statement. The performance improved
dramatically when we replaced
the code for extending and adding an entry (which was
in a loop) to a direct insert statement in the
table itself. What could have been the main reason
for this? One reason could be that we were consuming
unnecessary memory - but still why is it that the
insert into a table is much faster (about 5-10 times)
than insert
into the plsql variable itself. Or is it that in the
combination of <insert into sql type object +
final insert statement inserting all rows into
the table>, the final insert was the bottleneck?
I guess I should use dbms_profiler for this - but
may be you can answer from your vast experience!:)
Thanx!:)

          Followup:

you are doing tons less work.

you removed procedural code.

you did things in bulk.

All of those add up.  look at this:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:10390945632850
same concept.

remove procedural code.
do things in bulk.

20 times faster. (in that case, your mileage may vary)

      Rating: 4
did not quite
      do things in bulk  June 13, 2003
Reviewer:
      A reader  from ca
hi tom
"you are doing tons less work. you removed procedural code.
you did things in bulk."

I guess the procedural code and the loop in which
we were performing inserts are still there.
Except inside the loop, we do a direct insert
instead of
inserting into the sql table and
doing a final insert.

So,  the number of steps has not changed
(except for the final step where in one
case there is one final insert into db table)
only what we did in the loop changed..

          Followup:

do it in bulk -- it'll be amazingly even faster.

sounds like this routine should be

insert into ... select .....;

      Rating: 4
Bulk, extend
      and For All   June 23, 2003
Reviewer:  phil
       from
I am doing a bulk collect , then validating each record and putting the record
into one of 2 arrays... insert or updates (using extend to expand the arrays)...
when i am done I do a FORALL for the inserts and also for the updates.

can this be done more efficiently if extend has any kind of performance impact ?

I think i am doing far too much work here (code below)?

regards
phil

CREATE OR REPLACE PROCEDURE ImportAccountsXX IS

 TYPE l_pk IS TABLE OF accountimport.pk%TYPE;
 TYPE l_servicinginst_bic IS TABLE OF
accountimport.servicinginstitution_bic%TYPE;
 TYPE l_accountnumber IS TABLE OF accountimport.accountnumber%TYPE;
 TYPE l_accountowner_bic IS TABLE OF accountimport.accountowner_bic%TYPE;
 TYPE l_accountname IS TABLE OF accountimport.accountname%TYPE;
 TYPE l_currency IS TABLE OF accountimport.currency%TYPE;
 TYPE l_exportflag IS TABLE OF accountimport.exportflag%TYPE;
 TYPE l_datasource IS TABLE OF accountimport.datasource%TYPE;
 TYPE l_timestamp IS TABLE OF accountimport.timestamp%TYPE;

 TYPE l_a_sendstatus_fk IS TABLE OF account.sendstatus_fk%TYPE;

 l_pk_array l_pk ;
 l_servicinginst_bic_array l_servicinginst_bic;
 l_accountnumber_array l_accountnumber;
 l_accountowner_bic_array l_accountowner_bic;
 l_accountname_array l_accountname;
 l_currency_array l_currency;
 l_exportflag_array l_exportflag;
 l_datasource_array l_datasource;
 l_timestamp_array l_timestamp;

 currentAccount PLS_INTEGER;

 l_u_pk_array l_pk := l_pk();

 l_i_pk_array l_pk := l_pk();
 l_i_servicinginst_bic_array l_servicinginst_bic := l_servicinginst_bic() ;
 l_i_accountnumber_array l_accountnumber := l_accountnumber();
 l_i_accountowner_bic_array l_accountowner_bic  := l_accountowner_bic();
 l_i_accountname_array l_accountname := l_accountname();
 l_i_currency_array l_currency := l_currency();
 l_i_datasource_array l_datasource := l_datasource() ;
 l_i_timestamp_array l_timestamp := l_timestamp();
 l_i_sendstatus_array l_a_sendstatus_fk := l_a_sendstatus_fk();
 l_i_exportflag_array l_exportflag := l_exportflag();

 n pls_integer;
 j pls_integer :=0;
 k pls_integer :=0;

 l_nErrorCode pls_integer;
 laccountpk pls_integer;

 CURSOR c1 IS
    SELECT  pk,servicinginstitution_bic, accountnumber,
       accountowner_bic,accountname,currency,exportflag,datasource,timestamp
    FROM accountimport;

BEGIN

   open c1;
   loop
       fetch c1 bulk collect into
                             l_pk_array, l_servicinginst_bic_array,
                         l_accountnumber_array,l_accountowner_bic_array,
l_accountname_array,

l_currency_array,l_exportflag_array,l_datasource_array,l_timestamp_array
                         LIMIT 500;

       for i in l_pk_array.first .. l_pk_array.last
       loop
      declare
           validation_error   EXCEPTION;
          begin

            currentAccount := i;
          laccountpk := null;

          /* ...do some validation here

          /* ... determine wether we are inserting or updating

           /*
          we need to extend the arrays
          and determine if we are updating or inserting
          populate the inserts with the inserts and updates with the
          updates
          */
          if laccountpk IS NULL then
           /* we will be inserting*/
           j := j +1;
           l_i_pk_array.extend;
           l_i_servicinginst_bic_array.extend;
              l_i_accountnumber_array.extend;
              l_i_accountowner_bic_array.extend;
           l_i_accountname_array.extend;
           l_i_currency_array.extend;
           l_i_datasource_array.extend;
                  l_i_timestamp_array.extend;
           l_i_sendstatus_array.extend;
               l_i_exportflag_array.extend;

           l_i_pk_array(j) := l_pk_array(i);
           l_i_servicinginst_bic_array(j) := l_servicinginst_bic_array(i);
           l_i_accountnumber_array(j) := l_accountnumber_array(i);
           l_i_accountowner_bic_array(j) := l_accountowner_bic_array(i);
           l_i_accountname_array(j) := l_accountname_array(i);
           l_i_currency_array(j) := l_currency_array(i);
           l_i_datasource_array(j) := l_datasource_array(i);
           l_i_timestamp_array(j) := l_timestamp_array(i);

          else
           /* we will be updating */
             k := k +1;
           l_u_pk_array.extend;
                  l_u_pk_array(j) := l_pk_array(i);
          end if;

      exception
        when validation_error
            then
         dbms_output.put_line( 'error,' ||   l_pk_array(currentaccount) );
         /* we have an error so update the error code */
         update accountimport
           set errorcode_fk = l_nerrorcode,
               validationstatus = 'E'
           where pk = l_pk_array(i);

    end;
       end loop;

       n := l_i_pk_array.count;
       -- insert any accounts
       forall i in 1 .. l_i_pk_array.count
              insert into account (pk) values ( l_i_pk_array(i));
          -- needs all fields

       -- update any accounts
       forall i in 1 .. l_u_pk_array.count
              update account set pk =  l_u_pk_array(i);
              -- needs all fields

     exit when c1%notfound;
   end loop;
   close c1;
END ImportAccountsXX;

          Followup:

not a version to be found... bummer.

      Rating: 4
re:Bulk, extend
      and For All   June 23, 2003
Reviewer:  phil
       from sydney
version

oracle ... 9.2.0.2.1

sorry !

any help would be appreciated here as I am getting my self into a pickle I think
!

to empty arrays do i just .delete ?

          Followup:

search this site for

"save exceptions"

use bulk processing.

Just set the array to an empty one (like you did to initialize them in the first
place)

      Rating: 4
Overhead for
      making an overly large array?  July 30, 2003

Reviewer:  Doug  from CT, USA
Tom - if the memory used for the aforementioned types is only allocated on an
extend, is there any problem with defining a larger than needed array type to
safely handle isolated occurances of larger than "usual" arrays? Is there a
memory hit for example? What is the overhead of defining an array of 200,000 if
you usually extend only 4 or 500 but every now and then there may be 20,000
entries?

          Followup:

why would you allocate 200k slots at once?  I would just allocate either

one at a time as needed.

a couple dozen/hundred at a time if I thought I'd be extending frequently
otherwise.

no need to go 200k all at once since they are dynamically extendable.

      Rating: 4
not extending
      all at once.. just defining all at once  July 30, 2003

Reviewer:  Doug  from CT, USA
I don't mean extending more than I need.
I mean defining it overly large to begin with.
I mean like -
 CREATE TYPE quitelarge AS VARRAY(500000) OF WHATEVER;
If I'm only going to extend it to 20,000 most of the time.

          Followup:

i never use varrays, so no practical experience here.

I use plsql table types
I use nested table collections

I've never really used a varray

      Rating:
      5
  September 10, 2003
Reviewer:
      Anita  from Pittsburgh, PA
Hi Tom,
why am I getting subscript beyond count though I used ENTEND... in the following
code, please let me know.

Thanks in advance.....Anita.

  DECLARE
        TYPE TYP_MEASURES_REC IS RECORD ( measure_data VARCHAR2(100)
                                         ,pfactor fsb_accum_data.pfactor%TYPE
                                         ,xdtindvol
fsb_accum_data.xdtindvol%TYPE);

--   Maintain the Order as : week_code, geo_seq, iri_store, var_seq
       TYPE TYP_WEEK_CODES IS TABLE OF TYP_MEASURES_REC;
       TYPE TYP_GEOS IS TABLE OF TYP_WEEK_CODES;
       TYPE TYP_IRI_STORES IS TABLE OF TYP_GEOS;
       TYPE TYP_VAR_SEQS IS TABLE OF TYP_IRI_STORES;

       arr_accum_data TYP_VAR_SEQS :=
TYP_VAR_SEQS(TYP_IRI_STORES(TYP_GEOS(TYP_WEEK_CODES())));
       v_extend pls_integer;
BEGIN
       ---Get the max of var_seq
          SELECT MAX(var_seq)
           INTO v_extend
          FROM fsb_variables;

                arr_accum_data.EXTEND(v_extend);

                FOR REC2 IN (SELECT
                    week_code
                   ,iri_store_num
                   ,xdtindvol
                   ,pfactor
                   ,NVL(TO_CHAR(measure_data),'NULL') measure_data
                   ,geo_seq
                   ,var_seq
                  FROM fsb_accum_data
                  WHERE week_code BETWEEN 1250 AND 1250
                    AND (5 = 0 OR geo_seq = 5)
                 ) LOOP

arr_accum_data(rec2.var_seq)(rec2.iri_store_num)(rec2.geo_seq)(rec2.week_code).me
asure_data := rec2.measure_data;

arr_accum_data(rec2.var_seq)(rec2.iri_store_num)(rec2.geo_seq)(rec2.week_code).pf
actor := rec2.pfactor;

arr_accum_data(rec2.var_seq)(rec2.iri_store_num)(rec2.geo_seq)(rec2.week_code).xd
tindvol := rec2.xdtindvol;
        END LOOP;
        END;

          Followup:

so, you extended one dimension.

but you have 4.

this is an utterly bad idea I see here, I would not use a 4 dimensional array
and ESPECIALLY i would never use "select max()" -- ugh....

this looks like a really bad idea.

      Rating:
      4
Max  October 03, 2003
Reviewer:
      A reader
what might you use instead of select max() in this case?

          Followup:

i wouldn't use anything -- i wouldn't go down this path.

October 03, 2003 Carol -- Thanks for the question regarding "Using
      Interval Datatype", version 9i

      You Asked
I have the following query where TS_END and TS_START are DATE columns.  I used
to run this query to give me the average response time in seconds:

SELECT COUNT(*) system_process_count,
    TRUNC(AVG(lgrt.ts_end - lgrt.ts_start)*24*60*60, 1)
FROM MD_LOG_RESPONSE_TIMES lgrt
WHERE lgrt.ts_start IS NOT NULL
  AND lgrt.ts_end IS NOT NULL ;

TS_END and TS_START were just changed to TIMESTAMP columns so now my query
generates an error.  When 2 TIMESTAMPs are subtracted you get an INTERVAL.  I
can't figure out how to convert an interval to the  number of seconds.  Help!

Thank you.

      and we said...
Either of:

ops$tkyte@ORA920> select avg( extract( day from (x-y) )*24*60*60+
  2         extract( hour from (x-y) )*60*60+
  3         extract( minute from (x-y) )*60+
  4         extract( second from (x-y)) )  avg_secs from t;

  AVG_SECS
----------
  -1657063

ops$tkyte@ORA920>
ops$tkyte@ORA920> select numtodsinterval(
  2           avg( extract( day from (x-y) )*24*60*60+
  3                extract( hour from (x-y) )*60*60+
  4                extract( minute from (x-y) )*60+
  5                extract( second from (x-y)) ), 'second')  avg_int from t;

AVG_INT
---------------------------------------------------------------------------
-000000019 04:17:43.000000000

will work.
Review & Followup

      Rating: 3
Using stored
      interval to compute new date  July 16, 2003

Reviewer:  nolan tucker  from Harrisburg, PA USA

I'd like to store the time of day a report is to run.  I'm considering using
interval day(0) to second(0), since the only parts I want are the hour:minute.
I am having trouble with the syntax of secifying the interval value:
DECLARE
intvl interval day(0) to second(0) := '05:30';
newdate date;
begin
select trunc(sysdate) + intvl into newdate from dual;
dbms_output.put_line('newdate is '||newdate);
end;
/
declare
*
ERROR at line 1:
ORA-01867: the interval is invalid
ORA-06512: at line 2

SQL}EVEDW@dev1:evedb2>declare
  2  intvl interval day(0) to second(0) := 5hours 30minutes;
  3  newdate date;
  4  begin
  5  select trunc(sysdate) + intvl into newdate from dual;
  6  dbms_output.put_line('newdate is '||newdate);
  7  end;
  8  /
intvl interval day(0) to second(0) := 5hours 30minutes;
                                       *
ERROR at line 2:
ORA-06550: line 2, column 40:
PLS-00103: Encountered the symbol "HOURS" when expecting one of the following:
* & = - + ; < / > at in is mod not rem <an exponent (**)>
<> or != or ~= >= <= <> and or like between ||

I want to be able to maintain this starttime in an easily understood format like
HH24:MI.

Thanks for your help.  This is a super site.

          Followup:

two ways -- first, write a tiny function:

ops$tkyte@ORA920LAP> create or replace function hh24mi_to_interval( p_string in
varchar2 ) return interval day to second
  2  as
  3  begin
  4          return to_dsinterval( '0 ' || p_string || ':00' );
  5  end;
  6  /

Function created.

and use that:

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> declare
  2          ival interval day(0) to second(0) := hh24mi_to_interval('05:30');
  3  begin
  4          dbms_output.put_line( ival );
  5  end;
  6  /
+0 05:30:00

PL/SQL procedure successfully completed.

or, use "dual"

ops$tkyte@ORA920LAP> declare
  2          ival interval day(0) to second(0);
  3  begin
  4          select interval '05:30' hour to minute into ival from dual;
  5          dbms_output.put_line( ival );
  6  end;
  7  /
+0 05:30:00

PL/SQL procedure successfully completed.

since plsql won't take the hour to minute syntax...

      Rating:
      4
  July 17, 2003
Reviewer:  Nolan
      Tucker
Thanks Tom.

      Rating: 5
Timestamp and
      Interval  July 25, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
Name Data Type
TS, TS1 TIMESTAMP
TSZ TIMESTAMP WITH TIME ZONE
TLZ TIMESTAMP WITH LOCAL TIME ZONE
IYM INTERVAL YEAR TO MONTH
IDS INTERVAL DAY TO SECOND

What operation we can perform on these datatypes. It is just confusing.

A. IDS* 2
B. TS + IYM
C. TS -TS1
D. IDS - TS
E. IDS + IYM

          Followup:

see

http://asktom.oracle.com/~tkyte/9iTrain/img12.html

      Rating: 5
My
      test  July 26, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
1 Datetime +/- Interval = Datetime
2 Datetime - Datetime = Interval
3 Interval +/- Interval = Interval
4 Interval * Number = Interval
5 Interval / Number = Interval

SQL> desc l
 Name                  Type
 -------------------- ------------------------------
 USERNAME              VARCHAR2(10)
 TIMEIN                TIMESTAMP(3)
 TIMEOUT               TIMESTAMP(3)
 YM                    INTERVAL YEAR(2) TO MONTH
 DS                    INTERVAL DAY(2) TO SECOND(6)

column name format a15 not works with desc?

1
SQL> select timein - ym, timein + ym from l;
Ok
2
Ok
but datetime + datetime not allowed

3
SQL> select ym - ds from l;
select ym - ds from l
          *
ERROR at line 1:
ORA-30081: invalid data type for datetime/interval arithmetic

SQL>

4 ok
5 ok

I have tried to subtract interval - datetime and there will be an error.

Your comments.

          Followup:

set linesize works with desc

2) what does it means ""add jan 1'st 2004 to july 22cnd 2003"????  no, adding
datetimes doesn't make sense

3) they were at different scales.  they are different types of intervals.

Tell me -- what would "8 hours - january 1st 2003" be exactly?  again, like
date+date it makes *no* sense

      Rating: 3
sum() with
      Interval  October 03, 2003
Reviewer:  Christo
      Kutrovsky  from Ottawa, ON Canada
Hello Tom,

How can we use the aggregate function sum with the interval datatype?

SQL> create table int_table(a interval DAY(1) TO SECOND(2));

Table created

SQL> insert into int_table values (interval '3' hour);

1 row inserted

SQL> insert into int_table values (interval '1' hour);

1 row inserted

SQL> commit;

Commit complete

SQL> select sum(a) from int_table;

select sum(a) from int_table

ORA-00932: inconsistent datatypes: expected NUMBER got INTERVAL

SQL>

Since we can ADD intervals, we should be able to sum() them.

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5269744394385

      Rating:
      5
Oops..  October 03, 2003
Reviewer:
      Christo Kutrovsky  from Ottawa, ON Canada.
Ooops, i guess i did not search well enough.

Thanks.

October 03, 2003 Gautam -- Thanks for the question regarding "Top
      5 Salaries by Department.", version 8.1.5

      You Asked
Hi..

i want to display the top 5 salaries for each department using single SQL !!! i
don't want to use the PL/SQL !!!!

how can i achieve....?.

Your responce will be highly appriciated.

Sorry!!!.

What i want is Top 5 salaries in each department. if department has
repetative  salararies [ department no 10 has 8 records having same salary than
display only once.

I hope now You may understand my questation.

Your responce will be highly appriciated.

Thanks.

      and we said...
Ok, given the clarification I can show you a way to get this in 815 and before
(slow on a big table) and in 816 and up (fast with analytic functions!)

ops$tkyte@ORA817.US.ORACLE.COM> select * from t;

    DEPTNO        SAL
---------- ----------
        10        100
        10        100
        10        200
        10        300
        10        400
        10        500
        20        100
        20        200
        20        300
        20        400
        20        500
        20        600
        20        700
        20        700
        30        100
        30        200
        30        300
        30        400

18 rows selected.

Thats my sample data.  Now to get it in 815 and before:

ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> select distinct *
  2    from t t1
  3   where 5 >= ( select count(distinct t2.sal)
  4                  from t t2
  5                 where t2.deptno = t1.deptno
  6                   and t2.sal >= t1.sal )
  7  /

    DEPTNO        SAL
---------- ----------
        10        100
        10        200
        10        300
        10        400
        10        500
        20        300
        20        400
        20        500
        20        600
        20        700
        30        100
        30        200
        30        300
        30        400

14 rows selected.

And now using a new feature of 816:

ops$tkyte@ORA817.US.ORACLE.COM> select distinct *
  2    from ( select deptno, sal,
  3           dense_rank() over ( partition by deptno order by sal desc ) rank
  4             from t )
  5   where rank <= 5
  6  /

    DEPTNO        SAL       RANK
---------- ---------- ----------
        10        100          5
        10        200          4
        10        300          3
        10        400          2
        10        500          1
        20        300          5
        20        400          4
        20        500          3
        20        600          2
        20        700          1
        30        100          4
        30        200          3
        30        300          2
        30        400          1

14 rows selected.
Review & Followup

      Rating: 4
More
      info  August 24, 2001
Reviewer:  Benny Ching
       from Los Angeles, CA USA
Can you explain this new feature or point me to some
documentation on it.  Thanks!

          Followup:

Well, I know this book:

http://www.amazon.com/exec/obidos/ASIN/1861004826
has a great chapter on this feature in it ;)  Also:

http://technet.oracle.com/doc/oracle8i_816/server.816/a76994/analysis.htm#11237
http://technet.oracle.com/doc/oracle8i_816/server.816/a76989/functio2.htm#81409

      Rating: 5
salaries >
      5  August 26, 2001
Reviewer:  sree
       from india
hi tom,

i am a java professional. i am new to oracle. can u please explain how the
5>subquery work. like sub query gives only count.
can u suggest good site/book for queries.
i have more doubts.\
1. how to work with tkprof. the command is not working from command prompt.
should i set any thing in classpath or path.

2.when i try to create a database i get the following errors. i have followed
oracle documentation.
when i try to create database i get the following error and the session
terminates.i can find datafiles created in the disk.
 ORA-00603: oracke server session terminated by fatal error.

 and in alert log file the following error appears

 ORA-00376: file 1 cannot be read at this time
 ORA-01110: data file 1: 'E:\ORANT\LEASE\LSYS01.ORA'

 Can u help me?

thanks
sree

          Followup:

This query:

ops$tkyte@ORA817.US.ORACLE.COM> select distinct *
  2    from t t1
  3   where 5 >= ( select count(distinct t2.sal)
  4                  from t t2
  5                 where t2.deptno = t1.deptno
  6                   and t2.sal >= t1.sal )
  7  /

is conceptually processed like this:

  for x in ( select * from t t1 )
  loop
      count the number of distinct salaries in t such that the deptno is equal
      to X.DEPTNO and the salary is greater then or equal to X.SAL.

      if ( that count <= 5 )
      then
          keep the record
      end if;
  end loop

That subquery is technically called a "correlated subquery" and is effectively
run once per row in the outer query to get a value.

As for TKPROF, it is a simple command line tool.  You need to have your
ORACLE_HOME set and ORACLE_HOME/bin should be in the path (not classpath --
tkprof is C).  As you didn't put an error message -- i cannot guess what the
error might be.

As for creating a database, I suggest you use the database configuration
assistant and let it do the work.  Again, without a tad more information --
answering your specific request isn't really possible.

      Rating: 3
Fantastic
      explantion   August 26, 2001
Reviewer:
      umesh  from Bangalore India
Good Tom U always remember things for me which I forget down the line ..
Tom I know I should not ask a Question Here
But Still Is there any Indian Edition of your book coming uo soon ( You know I
am talking about Expert 1 on 1  !!)

          Followup:

If by Indian Edition you mean a translated version -- not that I am aware of.

      Rating: 5
The Article was
      excellent.  August 27, 2001
Reviewer:  Ganesh.
      R  from Dubai, UAE
Hi Tom,

This regarding the answer you have given to the last comment by umesh from
Banglore.

By Indian Edition we mean a Reprit as the book if got as a US edition Costs us
2000 odd Rs which is too high [ I know that nothing is too much for Tom's book
but still... ] So we people in India are eagerly awaiting a Reprint of the book
so that we can get from our local book store.

So it would be great if you would pull some strings with wrox for the reprint.

Regards,
Ganesh R

      Rating: 5
To Find the Nth
      Max or Min   August 27, 2001
Reviewer:
      Kamal.P  from Singapore
It is most useful to find out the "N"th max or min,exact n value from the given
query,please let me know the following result is the same as  Mr.TOM's answer..
select a.dept_no,a.salary from t a where 5<=(select count(*) from t
where a.salary<salary );

          Followup:

No, that would be different than:

ops$tkyte@ORA817.US.ORACLE.COM> select distinct *
  2    from t t1
  3   where 5 >= ( select count(distinct t2.sal)
  4                  from t t2
  5                 where t2.deptno = t1.deptno
  6                   and t2.sal >= t1.sal )
  7  /

Yours is very different.  It finds the count of all records with salaries  > the
row from the outer query.  My subquery finds the number of DISTINCT ( remove
dups ) salaries in the CURRENT DEPTNO that are larger.

      Rating: 4
book not
      available  August 27, 2001
Reviewer:  Harish
       from Bangalore,India
yup,your book is not available in India

      Rating: 5
top salary in
      each department but very big emp table  August 30, 2002

Reviewer:  A reader
hi tom

if we have 100000 rows in table emp then the query (example)

select ename, deptno
from emp a
where sal = (select max(sal) from emp b
             where a.deptno = b.deptno
             and b.deptno in (10, 30))

would be very very slow, is there a way to rewrite this query?

          Followup:

Why pre-suppose things like that?  Consider:

ops$tkyte@ORA920.US.ORACLE.COM> /*
DOC>drop table emp;
DOC>create table emp as select * from scott.emp where 1=0;
DOC>
DOC>exec gen_data( 'EMP', 100000 );
DOC>
DOC>drop index dept_index;
DOC>create index dept_index on emp( deptno, sal );
DOC>analyze table emp compute statistics for table for all indexes for all
indexed columns;
DOC>*/
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select deptno, count(*)
  2    from emp
  3   group by deptno;

DEPTNO   COUNT(*)
------ ----------
     1        503
     2       1000
     3        983
     4        984
.....
    97       1002
    98        989
    99        523

99 rows selected.

Elapsed: 00:00:00.13

ops$tkyte@ORA920.US.ORACLE.COM> select ename, deptno
  2  from emp a
  3  where sal = (select max(sal) from emp b
  4               where a.deptno = b.deptno
  5               and b.deptno in (10, 30))
  6  /

ENAME      DEPTNO
---------- ------
xnkHKxWxyr     10
zqZDsVmPjF     30

Elapsed: 00:00:00.00

That ain't very very slow, is it?

ops$tkyte@ORA920.US.ORACLE.COM> set autotrace on
ops$tkyte@ORA920.US.ORACLE.COM> select ename, deptno
  2  from emp a
  3  where sal = (select max(sal) from emp b
  4               where a.deptno = b.deptno
  5               and b.deptno in (10, 30))
  6  /

ENAME      DEPTNO
---------- ------
xnkHKxWxyr     10
zqZDsVmPjF     30

Elapsed: 00:00:00.13

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=9 Card=1 Bytes=40)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=2 Card=1 Bytes=14)
   2    1     NESTED LOOPS (Cost=9 Card=1 Bytes=40)
   3    2       VIEW OF 'VW_SQ_1' (Cost=5 Card=2 Bytes=52)
   4    3         SORT (GROUP BY) (Cost=5 Card=2 Bytes=14)
   5    4           INLIST ITERATOR
   6    5             INDEX (RANGE SCAN) OF 'DEPT_INDEX' (NON-UNIQUE) (Cost=5
Card=1333 By
          tes=9331)

   7    2       INDEX (RANGE SCAN) OF 'DEPT_INDEX' (NON-UNIQUE) (Cost=1 Card=1)

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

ops$tkyte@ORA920.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA920.US.ORACLE.COM>

It in fact is pretty darn fast

      Rating: 5
analytic
      functions instead of correlated query  August 30, 2002

Reviewer:  A reader
Hi

I have follwing correlated query

Select co_id, ch_seqno, ch_status, userlastmod, entdate
       From   contract_history ch1
       Where  (ch1.ch_seqno = ( Select Max(ch_seqno)
                               From   contract_history ch2
                               Where  ch1.co_id = ch2.co_id
                               And    trunc(entdate) <= trunc(sysdate - 1))
       or    ( ch1.ch_seqno = 1 and trunc(entdate) <= trunc(sysdate - 1)))
       order by co_id, ch_seqno;

The plan is contrct_history_pk (compositi index cod_id and cod_seqno) range scan
then contract_history full table scan (or the other way round, I dont have the
plan handy and cant get it now)

I changed it into

select co_id, ch_seqno, ch_status, userlastmod, entdate
                 from (select co_id, ch_seqno, ch_status, userlastmod,
entdate,
                        dense_rank() over (partition by co_id order by
ch_seqno desc) rango
                         from contract_history
                        where co_id < 11
                          and trunc(entdate) <= trunc(sysdate - 1)) iv_ch
                where rango = 1
                   or (ch_seqno = 1 and trunc(entdate) <= trunc(sysdate - 1))
                order by co_id, ch_seqno

The plan is simply a full table scan and window sort

I think the second one is better although it runs just 1 minute faster comparing
with previous correlated query for 50000 rows. The consistent gets are lower
with analytic function as well

Table contract history has around 13 million rows

Do you think I am in the right direction :?

          Followup:

If you are using the cost based optimizer -- you are.

      Rating: 4
Hi
      Umesh  August 30, 2002
Reviewer:  Reddy
       from Bangalore, India
I am Reddy here,

Yesterday I got book from Gangarams(after Mr. Tom Suggestion). Now only one copy
is available from Gangarams. Please get it now it self,with out fail.

Reddy

      Rating: 4
oops the second
      query should be this  August 30, 2002
Reviewer:
      A reader
select co_id, ch_seqno, ch_status, userlastmod, entdate
                 from (select co_id, ch_seqno, ch_status, userlastmod, entdate,
                        dense_rank() over (partition by co_id order by ch_seqno
desc) rango
                         from contract_history
                        where trunc(entdate) <= trunc(sysdate - 1)) iv_ch
                where rango = 1
                   or (ch_seqno = 1 and trunc(entdate) <= trunc(sysdate - 1))
                order by co_id, ch_seqno

cod_id < 11 was redundant

cheers

      Rating:
      3
  August 30, 2002
Reviewer:  sreekanth
      gadhiraju  from tampa, fl usa
following will also give top five salaries

select empno, ename, -minus_sal sal from
   (
      select minus_sal, empno, ename, rownum top from
      (
         select -sal minus_sal, empno, ename from emp
         union
         select to_number(null), to_number(null), null from dual
         minus
         select to_number(null), to_number(null), null from dual
      )
   ) where top <= 5

      Rating: 5
how to do this
      ???  February 07, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Islamabad, Pakistan
Hello Tom !

consider:

Table : t
Columns:

seller_Regno char(13)
invoice_no  varchar2(20)
...........
..........

I want top 15 seller_regno's( and their counts) who have the greatest no. of
records in table t.

For this i did:

select * from (select count(1)cnt,seller_regno form t
group by seller_regno
order by count(1) desc)
where cnt<16;

It gave me correct results but its too much slow. Can analytic function help me
for this ???

I am using 8.1.7 EE on Win2000 machine.

Riaz

          Followup:

that query doesn't look right

select *
  from (select count(*) cnt,seller_regno
          from t
         group by seller_regno
         order by 1 desc)
  where ROWNUM < 16;

but basically -- your question involves scanning and aggregating the entire
table.  An index on seller_regno and a NOT NULL constraint would help it
greatly.

      Rating: 4
To find the 3rd
      maximum  April 28, 2003
Reviewer:  Dani
       from India
Hi The great,
I have Written a query to find the emp_id, who's salary is the 3 rd maximum in
the company.

Table Name : Emp

Emp_Id    number(10)
Salary    number(9,2)

create table emp(Emp_id number(10), salary number(9));

insert into emp values(1,3000);
insert into emp values(2,1000);
insert into emp values(3,5000);
insert into emp values(4,2400);

The Query is
----------------
Select emp_id from emp
    Where
        Salary    = (select min(Salary) from (select distinct salary from emp
order by

salary desc) where rownum <=3)

-------------------
It is working perfectly.

But my boss is told that you can give better query than the previous one. Is
there any other

efficient way to find this 3rd maximum.

Please reply to me

Thanks in advance

Dani

          Followup:

and you could not figure out how to do this given the above examples?

      Rating: 4
Top N
      query  June 13, 2003
Reviewer:  Mohan K
       from Bangalore, India
Another query can be

select b.rnum, a.deptno, b.sal from
(select deptno, min(rnum) minrnum from(select rownum rnum, deptno, sal from
(select deptno, sal from dept order by deptno, sal))  group by deptno) a,
(select rownum rnum, deptno, sal from (select deptno, sal from dept order by
deptno, sal)) b
where a.deptno=b.deptno and b.rnum>=a.minrnum and b.rnum<(a.minrnum+5)
/

      Rating: 4
Query to find
      row wise maximum  June 25, 2003
Reviewer:
      Praveen  from Mumbai
Hi tom,

    Learned a lot from your site. I am regular reader of your site. I have one
small question to ask. suppose I have a students table as described below

SQL> desc students
Name Type         Nullable Default
---- ------------ -------- -------
NAME VARCHAR2(30) Y
PHY  NUMBER(3)    Y
CHEM NUMBER(3)    Y
MATH NUMBER(3)    Y
BIO  NUMBER(3)    Y

In this table I store all the marks of the students in physics, chemistry, maths
and biology. Now I have data as below:
SQL> select * from students;

NAME                            PHY CHEM MATH  BIO
------------------------------ ---- ---- ---- ----
Jack                             45   50   67   35
Jill                             50   76   68   66
Jane                             35   78   78   56
Bill                             56   35   98  100

4 rows selected

I want to display the student wise subjects that he/she is scoring most. From
the above example I want to display the result as

NAME        SUBJECT
--------------- ---------------
Jack        MATH
Jill        CHEM
Jane        CHEM, MATH
Bill        BIO

can I achieve this in a single SQL statement? If so then can you explain the
method...

          Followup:

no version.  bummer.

ok, using the technique at

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2196162600402
pretend DEPTNO = NAME
pretend SAL=grade
pretend ENAME = subject...

then

ops$tkyte@ORA920LAP> select deptno, stragg(ename) ename
  2    from (select deptno, ename, dense_rank() over (partition by deptno order
by sal DESC) dr
  3            from emp)
  4   where dr = 1
  5   group by deptno;

    DEPTNO ENAME
---------- ------------------------------
        10 KING
        20 SCOTT,FORD
        30 BLAKE

      Rating:
      3
Nice  September 24, 2003
Reviewer:
      Ferdinand Maer  from CT,USA
Dear Sir,
I have some doubts regarding queries.They are as follows
1)Can a column value be tested in queries? For example.
 How to make this query work?
  select deptno,sal,hiredate from emp where 'KING' = (..TEST
 condition );
 How to do this?I tried but it throws errors.Could you please tell how to do
this?
2)I want to get rows from EMP table who donot belong to
deptno = 10 using * not exists* .The query needs to be like
 "select * from emp where not exists(...subquery which tests
  for deptno ->10)" .Could you please help?
Thanks a lot in advance.

          Followup:

1) having no idea what ( ...TEST condition ) means -- i cannot say anything
about this one....

2) why would you want to do that????  but you could

  1* select ename, deptno from emp where not exists ( select * from dual where
deptno = 10 )
scott@ORA920LAP> /

ENAME          DEPTNO
---------- ----------
SMITH              20
ALLEN              30
WARD               30
JONES              20
MARTIN             30
BLAKE              30
SCOTT              20
TURNER             30
ADAMS              20
JAMES              30
FORD               20

11 rows selected.

it would be about the wrongest way to do it, but you could

      Rating:
      3
Nice  September 25, 2003
Reviewer:
      Ferdinand Maer  from CT,USA
Dear Sir,
Regarding the first question ,I tried to get the ename which must match
'KING'.To be clear My intention was
" select deptno,sal from emp where 'KING' = (select ename
from emp).I want the subquery to return the ename as 'KING'
Is this possible?
I have some other doubts also involving queries.
1)What is this "column_value"?I searched in your website but
donot find anything.
2)You sometime use "select max(decode(col_name...))"
Could you please explain why you are doing so with an example?
Thanks in advance

          Followup:

why?  it would be "wrong"

but, it would be;

select deptno, sal from emp where 'KING' = ( select ename from DUAL );

but again - it is wrong wrong wrong, wrong wrong wrong. not anything you want to
do.

1) column_value is the default name assigned to the single column of a nested
table type.

ops$tkyte@ORA920LAP> create or replace type numArray as table of number
  2  /

Type created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create table t
  2  ( x int,
  3    y numArray )
  4  nested table y store as y_tab;

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> insert into t values ( 1, numArray(1,2,3) );

1 row created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select y.* from t x, TABLE(x.y) y
  2  /

COLUMN_VALUE
------------
           1
           2
           3

ops$tkyte@ORA920LAP>

2) you'll have to show me a FULL example as there are possibly and infinite set
of responses to that question.

      Rating:
      3
Nice  September 25, 2003
Reviewer:
      Ferdinanand Maer  from CT,USA
Dear Sir,
Thanks for you response.I need a query that returns the max.
among the count of jobs.I tried the one like
 "select job,count(*),max(count(*)) from job group by job"
But got an error stating that not a single - group group
function.How to correct this?Please specify other formats of
the query.
P.S)-> Can the "select max(decode(job,..." example used here?
Please do reply.
Thanks a lot in advance.

          Followup:

scott@ORA920LAP> select job, count(*), max(count(*)) over () from emp group by
job;

JOB         COUNT(*) MAX(COUNT(*))OVER()
--------- ---------- -------------------
ANALYST            2                   4
CLERK              4                   4
MANAGER            3                   4
PRESIDENT          1                   4
SALESMAN           4                   4

      Rating:
      4
Nice  September 26, 2003
Reviewer:
      Ferdinand Maer  from CT,USA
Dear Sir,
Thanks for your response.I have a question for you.
1) Can we have a glimpse of the first question which you have received when this
website was started?

          Followup:

i forget the exact first question, one of them that was sort of the genisys of
it all was:

http://www.oracle.com/oramag/oracle/00-May/index.html?o30tom.html
starting to show it's age by now but it was the first print article I had and
that question was among the first few.

      Rating:
      3
NIce  September 27, 2003
Reviewer:
      Ferdinand Maer  from CT,USA
Dear Sir,
Thanks for your response.I have some questions regarding
queries.They are
1)In a query,can a where clause contain a select stmt.like
 "select * from dept where(select ...) = .."
2)suppose if I want to add the sal of first five rows regard
-less of deptno. how can I do it?Can analytic functions be
used here?
3)Can a select like this possible ?
 'select(select * from a) from b'
Please provide some examples.
Thanks in advance.
Bye!

          Followup:

1) that is a statement of fact, not a question...

2) select sum(sal) from ( select sal from t where rownum <= 5 )

3) yes.

select ( select * from dual ) from dual;

      Rating: 2
Predicate out
      of scope  October 02, 2003
Reviewer:  Marcio
       from br
Is it possible?

ops$t_mp00@MRP9I1> select x.*, rank() over ( order by sum_sal ) rank
  2    from (
  3  select job, sum(sal) sum_sal
  4    from emp
  5   group by job
  6         ) x
  7  /

JOB          SUM_SAL       RANK
--------- ---------- ----------
CLERK           4150          1
PRESIDENT       5000          2
SALESMAN        5600          3
ANALYST         6000          4
MANAGER         8275          5
DBA            10000          6

6 rows selected.

Now I would like selection dynamic with hiredate predicate. I know that query
below doesn't work, just ilustrate what I want.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> select x.*, rank() over ( order by sum_sal ) rank
  2    from (
  3  select job, sum(sal) sum_sal
  4    from emp
  5   group by job
  6         ) x
  7   where hiredate between to_date('01/01/80', 'dd/mm/yy') and
  8                          to_date('01/01/99', 'dd/mm/yy')
  9  /
 where hiredate between to_date('01/01/80', 'dd/mm/yy') and
       *
ERROR at line 7:
ORA-00904: "HIREDATE": invalid identifier

Any idea?

          Followup:

using an illustration that doesn't make any sense to me won't help.  I don't
know what you want really.

is it as easy as putting the predicate in the inline view where it will work.

      Rating: 2
View with rank
      dynamic  October 03, 2003
Reviewer:  Marcio
       from br
I'm helping with BO (Business Inteligence Tool) and need a view that brings to
me a rank of sum(sal). The view predicate is -- where hiredate between a range.
I know if I put predicate into inline view this will work fine, but how can I
create a view to accept a range of hiredate and rank with sum()?

          Followup:

tell me what you can do in BO -- can you make a stored procedure call before
running a query? (we can parameterize a view with an application context).  Can
you call a stored procedure that returns a ref cursor?  what can we do -- i
don't know this tool.

      Rating:
      4
View  October 03, 2003
Reviewer:
      Marcio  from br
1) can you make a stored procedure call before running a query?
No. This was the first choice offer.

2) Can you call a stored procedure that returns a ref cursor?
No.

We can do view in BO.

          Followup:

sorry, no go then.

you cannot push the predicate down into the view with analytics.  it changes the
semantic meaning of the query.  if you create a view like:

create view v
as
select empno, sal, hiredate, rank() over (order by sal desc) rnk
  from emp;

when you query:

select * from v where hiredate between A and B;

that will be like:

select * from (select empno, sal, hiredate, rank() over (order by sal desc) rnk
  from emp ) where hiredate between A and B;

the inline view would be materialized fully AND THEN queried -- for the simple
reason that

select empno, sal, hiredate, rank() over (order by sal desc) rnk
  from emp
 where hiredate between A and B

would return a totally different answer.

You'll have to put the query itself into BO.

      Rating: 4
Complt. last
      follup  October 03, 2003
Reviewer:  Marcio
       from br
In BO we can select * from view... just it, just queries.

      Rating:
      5
BO  October 03, 2003
Reviewer:
      A reader
In BO (thick client) you can use stored procedures.  You can also use analytical
functions in BO Universe Design.  For using analytical function go to thier web
site and search for "tips and tricks". You will find the document under BO
Universe Design.

          Followup:

thanks so much for the input -- appreciate it!

      Rating: 5
thanks
      "reader"  October 03, 2003
Reviewer:  Marcio
       from br
Thanks, but according my colleague, who is responsible to implement that problem
with BO, is -- web intelligence doesn't allow any stored procedure or queries
with inline views YET.
Anyway, thank you so much!

      Rating:
      4
Awesome  October 30, 2003
Reviewer:
      Jeffrey  from Seattle, WA
Hello Tom,

I have learned more on Oracle from this site than any other resource.  Thank
you.

I have a followup question for this case; and I also wanted your readers to know
how well this works.  I timed these and the second example was approximately 25%
the time of the first.

I started with the following query: (where T1 has (id number, value number,
value2 number, and id and value are relevant, value2 is not)

select * from t1 where (id, value) in (
select i1, v1 from (
select id i1, value v1 from t1 T
where id in (select distinct id from t1)
and value = (select max(value) from t1 where id = T.id)
) group by i1, v1 having count(*) = 1
)

the goal here was to find the id,value pairs with the greatest value for a given
ID, but only when the case with that value has only one instance.  if there are
>1  cases with the highest 'value', it should not be included in the result.

I am far from a SQL expert, so the first question is how could I improve that
query without analytical functions?

I then turned that into the following:

select * from t1 where (id, value) in (
select i1, v1 from
(select id i1, value v1,
 dense_rank() over ( partition by id order by value desc ) rank
from t1 T
where id in (select distinct id from t1)
) where rank = 1
group by i1, v1
having count(*) = 1)

The inner part here returns the ranks, when is selected only the #1 ranks
(equivalent to the MAX above), then selecting cases where count=1 to reject the
ones where there is more than one case.

2) 2nd question is how could I improve this query?

October 03, 2003 Richard -- Thanks for the question regarding
      "DBMS_SQL", version 8.1.6

      You Asked
    The following is a code snippet obtained from a much larger stored procedure
within a package.  The error I am receiving both in this code snippet and when
running the same code in the stored procedure is as follows:
DECLARE
*
ERROR at line 1:
ORA-01007: variable not in select list
ORA-06512: at "SYS.DBMS_SYS_SQL", line 1099
ORA-06512: at "SYS.DBMS_SQL", line 333
ORA-06512: at line 19

    Unless I am missing the obvious, I see no reason why this occurring.  If you
make the dynamic query SELECT into a static SELECT-INTO, removing the additional
single quotes needed for the interpretation of the string to include a single
quote, the query works.  I also have additonal dynamic queries within other
stored procedures within the package needing the additional single quotes which
work so that should not be the issue.  Below is the code snippet when run
produces the error.  The database user against which this was run has the
necessary SELECT privileges on the necessary V_$ views.  Also, since this is
taking place in an Oracle8i I have hard-coded the vIncludePoolColumn variable to
be TRUE as the V_$SGASTAT.POOL column exists.

DECLARE
  vDummy               NUMBER(38);
  vDynamicCursor       NUMBER(38);
  vIncludePoolColumn   BOOLEAN := TRUE;
  vSharedPoolAvailable NUMBER(38);
  vSharedPoolFree      NUMBER(38);
BEGIN
    vDynamicCursor := DBMS_SQL.OPEN_CURSOR;

    IF (vIncludePoolColumn) THEN
      DBMS_SQL.PARSE(vDynamicCursor,'SELECT
DECODE(UPPER(SUBSTR(LTRIM(RTRIM(VP.VALUE)),LENGTH(LTRIM(RTRIM(VP.VALUE))),1)),''K
'',TO_NUMBER(SUBSTR(LTRIM(RTRIM(VP.VALUE)),1,LENGTH(LTRIM(RTRIM(VP.VALUE))) -
1)) *
(1024),''M'',TO_NUMBER(SUBSTR(LTRIM(RTRIM(VP.VALUE)),1,LENGTH(LTRIM(RTRIM(VP.VALU
E))) - 1)) * (1024 * 1024),TO_NUMBER(LTRIM(RTRIM(VP.VALUE)))),VS.BYTES FROM
SYS.V_$SGASTAT VS,SYS.V_$PARAMETER VP WHERE UPPER(LTRIM(RTRIM(VS.POOL))) =
''SHARED POOL'' AND UPPER(LTRIM(RTRIM(VS.NAME))) = ''FREE MEMORY'' AND
UPPER(LTRIM(RTRIM(VP.NAME))) = ''SHARED_POOL_SIZE''' || ' ',DBMS_SQL.NATIVE);
    ELSE
      DBMS_SQL.PARSE(vDynamicCursor,'SELECT
DECODE(UPPER(SUBSTR(LTRIM(RTRIM(VP.VALUE)),LENGTH(LTRIM(RTRIM(VP.VALUE))),1)),''K
'',TO_NUMBER(SUBSTR(LTRIM(RTRIM(VP.VALUE)),1,LENGTH(LTRIM(RTRIM(VP.VALUE))) -
1)) *
(1024),''M'',TO_NUMBER(SUBSTR(LTRIM(RTRIM(VP.VALUE)),1,LENGTH(LTRIM(RTRIM(VP.VALU
E))) - 1)) * (1024 * 1024),TO_NUMBER(LTRIM(RTRIM(VP.VALUE)))),VS.BYTES FROM
SYS.V_$SGASTAT VS,SYS.V_$PARAMETER VP WHERE UPPER(LTRIM(RTRIM(VS.NAME))) =
''FREE MEMORY'' AND UPPER(LTRIM(RTRIM(VP.NAME))) = ''SHARED_POOL_SIZE''' || '
',DBMS_SQL.NATIVE);
    END IF;

    vDummy := DBMS_SQL.EXECUTE(vDynamicCursor);

    WHILE (DBMS_SQL.FETCH_ROWS(vDynamicCursor) > 0) LOOP
      DBMS_SQL.COLUMN_VALUE(vDynamicCursor,1,vSharedPoolAvailable);
      DBMS_SQL.COLUMN_VALUE(vDynamicCursor,2,vSharedPoolFree);
    END LOOP;

    DBMS_SQL.CLOSE_CURSOR(vDynamicCursor);
END;
/

      and we said...
You are missing the necessary call to dbms_sql.define_column.

You must DEFINE all of your outputs -- tell us what datatypes you would like to
fetch them into.

Here is a small example.  In the first procedure, i comment out the call to
dbms_sql.define_column.  It fails with your ORA-1007.  Simply uncomment the
needed line and it works fine:

ops$tkyte@ORA8I.WORLD> create or replace
  2  function get_row_cnts( p_tname in varchar2 ) return number
  3  as
  4      l_theCursor     integer default dbms_sql.open_cursor;
  5      l_columnValue   number  default NULL;
  6      l_status        integer;
  7  begin
  8      dbms_sql.parse(  l_theCursor,
  9                      'select count(*) from ' || p_tname,
 10                       dbms_sql.native );
 11
 12      -- dbms_sql.define_column( l_theCursor, 1, l_columnValue );
 13      l_status := dbms_sql.execute(l_theCursor);
 14      if ( dbms_sql.fetch_rows(l_theCursor) > 0 )
 15      then
 16          dbms_sql.column_value( l_theCursor, 1, l_columnValue );
 17      end if;
 18      dbms_sql.close_cursor( l_theCursor );
 19      return l_columnValue;
 20  end;
 21  /

Function created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> begin
  2     dbms_output.put_line('Emp has this many rows ' ||
  3                           get_row_cnts('emp'));
  4  end;
  5  /
begin
*
ERROR at line 1:
ORA-01007: variable not in select list
ORA-06512: at "SYS.DBMS_SYS_SQL", line 1099
ORA-06512: at "SYS.DBMS_SQL", line 333
ORA-06512: at "OPS$TKYTE.GET_ROW_CNTS", line 15
ORA-06512: at line 2

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> create or replace
  2  function get_row_cnts( p_tname in varchar2 ) return number
  3  as
  4      l_theCursor     integer default dbms_sql.open_cursor;
  5      l_columnValue   number  default NULL;
  6      l_status        integer;
  7  begin
  8      dbms_sql.parse(  l_theCursor,
  9                      'select count(*) from ' || p_tname,
 10                       dbms_sql.native );
 11
 12      dbms_sql.define_column( l_theCursor, 1, l_columnValue );
 13      l_status := dbms_sql.execute(l_theCursor);
 14      if ( dbms_sql.fetch_rows(l_theCursor) > 0 )
 15      then
 16          dbms_sql.column_value( l_theCursor, 1, l_columnValue );
 17      end if;
 18      dbms_sql.close_cursor( l_theCursor );
 19      return l_columnValue;
 20  end;
 21  /

Function created.

ops$tkyte@ORA8I.WORLD> begin
  2     dbms_output.put_line('Emp has this many rows ' ||
  3                           get_row_cnts('emp'));
  4  end;
  5  /
Emp has this many rows 15

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating: 5
dynamic query
      doesn't return any rows  July 08, 2003
Reviewer:
      Leonardo Zacché  from Rio de Janeiro, RJ, Brazil
Tom, can you ever imagine WHY the exact same query returns 10 rows (perfect
result) if I run it from SQL*Plus prompt, but if I run it as a (pseudo-)Dynamic
query, the fetch_rows returns always 0 (zero)? I mean pseudo-Dynamic because I
just put the select statement in a string and run it. It seems to be no
mis-written, it parses ok, runs ok, but return zero rows. Can you imagine why?
Thanks!

          Followup:

hmm, without the code, no -- I cannot see your issue from here.  perhaps you are
doing dynamic sql wrong.

it should look like this:

ops$tkyte@ORA920> select ename from emp;

ENAME
----------
SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
KING
TURNER
ADAMS
JAMES
FORD
MILLER

14 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          type rc is ref cursor;
  3          l_cursor rc;
  4          l_ename  emp.ename%type;
  5  begin
  6          open l_cursor for 'select ename from emp';
  7          loop
  8                  fetch l_cursor into l_ename;
  9                  exit when l_cursor%notfound;
 10                  dbms_output.put_line( l_ename );
 11          end loop;
 12          close l_cursor;
 13  end;
 14  /
SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
KING
TURNER
ADAMS
JAMES
FORD
MILLER

PL/SQL procedure successfully completed.

      Rating: 5
DBMS_SQL vs
      Native Dynamic SQL?  October 03, 2003
Reviewer:
      Kamal Kishore  from New Jersey, USA
Hi Tom,
At the following link:

http://www.devx.com/gethelpon/10MinuteSolution/16610
The author indicates that:
<quote>
According to Oracle, Native Dynamic SQL provides 30 to 60 percent performance
improvements over DBMS_SQL.
</quote>

Do you agree with this assertion?
Is the performance difference so significant so as to prompt the programmers to
start re-writing already running code (which uses DBMS_SQL) to make use of the
native dynamic sql features?

To me, it seems to suggest that DBMS_SQL should be used ONLY and ONLY in such
extreme cases where Native Dynamic SQL can not perform what you are trying to
acomplish.
What is your viewpoint?
Thanks,

          Followup:

for a single statment -- executed once, never to be executed again

yes.

for loading 100,000 rows into a table whose name you didn't know until run time?

no.

If you are going to execute the same statement over and over with different
inputs -- dbms_sql.  You can keep the statement open and parse it ONCE per
session instead of ONCE per execution (as NDS would have you do)

If you are going to execute a statment once in your session, NDS is dandy.

But then again, if you just do it once, well -- 30%-60% of an already pretty
small number is not very much so I probably would not rip code apart just for
that.

      Rating: 5
Thank
      You  October 05, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Thanks Tom.
As always, great insight and to the point response.
Thanks,

October 04, 2003 Arindom -- Thanks for the question regarding
      "Return values from a PL/SQL table into a ref cursor?", version 8.1.6

      You Asked
How do I return the values from a PL/SQL table
(indexed by BINARY_INTEGER) into a ref cursor?

The contents of the PL/SQL table are NOT returnable  by a single

SQL statement. AS it is a PL/SQL table ; I can't do a standard
select (This doesn't work -open TunnelCrs for
                           'select gw1, gw2 from a')

Currently, to create the ref. cursor am currently doing
thefollowing:
   open TunnelCrs for
   'select '||to_char(a(1).gw1)||','||
   to_char(a(1).gw1)||' from dual';

If there are multiple rows ; I am using an 'union all' .

The following is my type and PL/SQL table definitions:
TYPE gw_ttn is record (
        gw_id1 INTEGER,
        gw_id2 INTEGER
);
TYPE gw_tn is table of gw_ttn index by binary_integer;
TYPE TunnelCursor IS REF CURSOR;
a       gw_tn;

Is there a more elegant solution?

      and we said...
the proper way to do this is to NOT use a PLSQL table type but
to use a SQL Object Type instead.  It would look like this:

tkyte@OSI1.WORLD> create or replace type myScalarType as object
  2  (   x     int,
  3      y    date,
  4      z    varchar2(25)
  5  )
  6  /
Type created.

tkyte@OSI1.WORLD> create or replace type myTableType as table of
myScalarType;
  2  /
Type created.

tkyte@OSI1.WORLD> create or replace
  2  function demo_proc( p_start_row in number,
  3                      p_end_row in number )
  4  return myTableType
  5  as
  6      l_data             myTableType := myTableType();
  7      l_cnt              number default 0;
  8  begin
  9      for x in ( select * from emp order by sal desc )
 10      loop
 11          l_cnt := l_cnt + 1;
 12          if ( l_cnt >= p_start_row )
 13          then
 14              l_data.extend;
 15              l_data(l_data.count) :=
 16                      myScalarType( x.empno,
 17                                    x.hiredate,
 18                                    x.ename );
 19          end if;
 20          exit when l_cnt = p_end_row;
 21      end loop;
 22
 23      return l_data;
 24  end;
 25  /
Function created.

tkyte@OSI1.WORLD> select *
  2  from the ( select cast( demo_proc(2,6) as mytableType )
  3               from dual ) a
  4  /

         X Y         Z
---------- --------- -------------------------
      7788 09-DEC-82 SCOTT
      7902 03-DEC-81 FORD
      7566 02-APR-81 JONES
      7698 01-MAY-81 BLAKE
      7782 09-JUN-81 CLARK

tkyte@OSI1.WORLD>

So, I am recommending you use a SQL type -- not a plsql table
type (they work very much the same with the notable exception
that the SQL Nested table demands you use .EXTEND to allocate
space whereas the plsql table type just "makes room" as needed.

By using the SQL Type, you can select from the table easily.
Your ref cursor example would be:

tkyte@OSI1.WORLD> create or replace package my_pkg
  2  as
  3      type rc is ref cursor;
  4
  5      procedure p( p_cursor in out rc );
  6  end;
  7  /

Package created.

tkyte@OSI1.WORLD> create or replace package body my_pkg
  2  as
  3
  4  procedure p( P_cursor in out rc )
  5  is
  6      l_data  myTableType := myTableType();
  7  begin
  8      for i in 1 .. 3 loop
  9          l_data.extend;
 10          l_data(i) :=
 11             myScalarType( i, sysdate+i, i || ' data');
 12      end loop;
 13
 14      open p_cursor for
 15      select *
 16        from TABLE ( cast ( l_data as myTableType) );
 17  end;
 18
 19  end;
 20  /

Package body created.

tkyte@OSI1.WORLD> set autoprint on
tkyte@OSI1.WORLD> variable x refcursor
tkyte@OSI1.WORLD> exec my_pkg.p(:x)

PL/SQL procedure successfully completed.

         X Y         Z
---------- --------- -------------------------
         1 27-MAY-00 1 data
         2 28-MAY-00 2 data
         3 29-MAY-00 3 data

tkyte@OSI1.WORLD>

Review &
      Followup

      Rating:
      4
  April 15, 2001
Reviewer:
      A reader

      Rating:
      4
  April 30, 2001
Reviewer:
      A reader

      Rating:
      5
  May 20, 2001
Reviewer:
      A reader

      Rating:
      3
  July 23, 2001
Reviewer:  Faris

      Rating:
      5
  August 16, 2001
Reviewer:  krotov
      sergey  from russia
the great;the best; in Oracle Forms

      Rating:
      5
Super!!  August 29, 2001
Reviewer:
      Maeve  from Dublin, Ireland
This is EXACTLY what I was looking for. I have to say that I am WELL impressed
with asktom.oracle.com the answers to the questions are easy to understand and
the practical examples are brilliant.

      Rating: 3
Is returning
      RefCursor better than returning the table itself?  August
      30, 2001
Reviewer:  Niranjan Ramakrishnan  from
      Sunnyvale, CA USA
Is there a performance reason why one would want to return
a ref cursor as opposed to the table itself (and access it
from JDBC using ARRAYs or CustomDatums)?

          Followup:

Yes, it is better (to return the ref cursor).

If I was to return an array -- i would have to fetch all of the rows on the
PLSQL side.  With a ref cursor -- the client gets to get at the data right away
(don't have to fetch the last row before they see the first row).  The
application would appear to be faster to the end user.

Additionally, if I was to return an array -- I would have to fetch all of the
rows, allocate storage in plsql for them, copy the data there and then send that
data back over the network in one big chunk where the client would have to
allocate storage to hold the whole thing in memory.  With a ref cursor -- i send
back a pointer (don't have the overhead of allocating the storage in plsql and
copying all of the data into it).  I send rows in chunks -- not all at once (10
rows at a time by default -- you control the size).  I don't have to allocate
memory to hold the entire thing in the client -- just a slice of it.

A ref cursor is infintely more scalable then sending a data structure back and
forth for these reasons.

Not only that but the java code to access a result set is trivial as compared to
accessing an ARRAY or CustomDatum type.

      Rating: 3
forgive me, for
      i have doubts ;-)  August 31, 2001
Reviewer:
      frank  from nl
I think the downside of your approach is that you have to define your SQL-Types
apart from your pck. this means, that when you -not unlikely- have to change
your table/object definition, you have to change the package (to fill the
object) as well as the SQLtype.
Can't it all be wrapped _within_ a package ?

(btw GREAT book !)

          Followup:

You must use a SQL type -- you want to use it in SQL right?  You want to be able
to select from it.  In order to be able to select from it -- you must use a SQL
type that is registered in the data dictionary.  Otherwise SQL will have no idea
about it.

      Rating: 5
Very
      good  October 24, 2001
Reviewer:  Kamlesh
      Patel  from USA
This is what I was looking for. Thanks a lot.

      Rating: 5
I'm a happy
      puppy...  December 02, 2001
Reviewer:  Greg
      Sullivan  from Dhahran, Saudi Arabia
This is exactly what I was looking for...

I'm abstracting specific queries with stored procedures for some applications,
due to a possible migration of portions of my application to SAP R/3.  Our
release date is not in sync with the migration of these tables to the R/3
environment, meaning that, to be prudent, we SHOULD put a layer between our
application (which is currently written in PowerBuilder and C++) and R/3.

While knowing that this is this philosophy is the "right thing to do" (hey, I'm
the one recommending it!  ;-) ) and that "there ought to be a way to force a
result set in, regardless of what occurs on the other end", it's certainly
comforting to know that I can use a brute force method if need be, while
maintaining an elegant reference cursor/SQL interface to the data as long as
possible.

In short, thanks.  My comfort level has gone up astronomically, thanks to this
answer.

      Rating: 5
Great
      Solution!!!  March 07, 2002
Reviewer:  R
       from USA
Tom,
Based on your great solution to the main question above, How would object types
work in a multi user environment?? Suppose more than one users pass some
parameters and execute the 'my_pkg' package above, is it going to return the ref
cursor result set specific to that user only ??
Another question, Do we have to empty out the object type once the processing is
done or does it store data in memory only?? I request you to tell me asap before
I use this solution because we have a multi user environment.

      Rating:
      5
  March 11, 2002
Reviewer:  Peter

Is there any way to make the following declarations within a PL/SQL package?
thanks

tkyte@OSI1.WORLD> create or replace type myScalarType as object
  2  (   x     int,
  3      y    date,
  4      z    varchar2(25)
  5  )
  6  /
Type created.

tkyte@OSI1.WORLD> create or replace type myTableType as table of myScalarType;
  2  /
Type created.

          Followup:

if you want to use them in SQL, as demonstrated, they must be defined at the SQL
layer (as standalone types).

if you don't want to use them in SQL, just want a table of records, we would use
a table of records in PLSQL (eg: type array is table of emp%rowtype index by
binary_integer; )

      Rating: 5
Thanks Tom,
      great  March 12, 2002
Reviewer:
      A reader  from Switzerland

      Rating:
      5
Followup  March 14, 2002
Reviewer:  Tim
      Glasgow  from Belfast
1.Are these permanent once created or do they only last for the session?
2.    Are they like arrays?
3.    When using ref cursors is it better to point to the data set in these
objects or a regular database table?
4.    Can these objects be created using execute immediate?

          Followup:

1) is WHAT permanent?  The contents of a collection or the collection types
themselves?

the contents -- session specific.
the TYPE itself -- it is there until you drop the type.  Consider them a VIEW of
sorts.

2) yes, it is fair to think of them as similar to arrays.

3) does not make sense to me.

4) yes but that would be a truly bad idea.   You do not want to be doing DDL
all over the place.  They should be created ONCE per database just like your
code is.  They should be created at installation time -- period.

      Rating: 5
Mr Kyte strikes
      again  April 04, 2002
Reviewer:  Phenoracle
       from UK
Again another reader friendly explanation by Mr Kyte.
Time and time again we as techis look for answers to questions, and it is so
refreshing that today there are so many people willing to share their knowledge.
My thoughts are I.T. will evolve at a greater rate due to this.

Many Thanks (Great Book)

Phenoracle

      Rating:
      5
  April 09, 2002
Reviewer:
      A reader  from China
Very cute method to do this ,thank you!

      Rating: 3
What about
      Pipe?  April 11, 2002
Reviewer:  Joshua
      Perry  from Las Vegas
What are the performance characteristics of using this REF Cursor method or say
pipelined return value PIPE ROW(ROW_TYPE)?  Or, are the two methods not
related/interchangable?

          Followup:

You cannot compare them?

You could use a ref cursor to return a pipelined result.
You could use a ref cursor to return the above results (fill the variable,
return the variable)

a pipelined function allows the first row of the "collection" to be returned to
the client well before the last.  A function as above must wait until the last
row of the collection is assembled before returning a single byte.  So,
pipelining allows for more "client/server interaction" where as the above method
is "client calls server, server builds entire result and returns it".  depends
on what you are doing as to which will be more appropriate.

      Rating:
      5
  July 25, 2002
Reviewer:  Edric
       from ph

      Rating: 4
Cons of using
      Ref Cursor  July 30, 2002
Reviewer:  Ken
      Chiu  from toronto, on, canada.
hi Tom,

I agree with all the pros of using a Ref Cursor you mentioned, like not doing
the fetch into a pl/sql table.  But every request from user means we'll execute
the SQL again right ?  In our shop we've a store proc where 15 tables are joined
and union agained, we'll tune the SQL eventually, but I wonder in case like this
and we've many, what is more expensive: the pl/sql table is more expensive or
the executing the SQL again on request ?  Actually now after typing it I'll do
some tests, but please share your opinions.

Thanks as usual.

          Followup:

SQL is what the database was written to do.

SQL is what the database does best.

See

http://groups.google.com/groups?selm=a1g2kv02r69%40drn.newsguy.com&oe=UTF-8&output=gplain
for another take on this.

      Rating: 5
You are right,
      letting the DB do it is faster.  July 31, 2002

Reviewer:  Ken Chiu  from toronto, on canada

I did my test on comparing the time on fetching into pl/sql table vs. fetching
into a SQL type and then CAST it, and yes the SQL type option is much faster,28%
on average, in a 30,000 records resultset.

Thanks,
Ken.

      Rating: 4
Fetching from
      RefCursor into a SQL Type ?  August 10, 2002

Reviewer:  Ken Chiu  from toronto, on, cdn.

hi tom,

I need to retrieve 10 records within the RefCursor, the code sample above works
great, but now I have to  separate the Select data logic from the return data
logic, basically I'll have 1 function that returns the RefCursor, and a
procedure to return 10 records from that RefCursor.

But the code below is giving an incosistent type error )ORA-00932):

create or replace type date_obj as object (myDate   Date)
/
create or replace type date_obj_tbl as table of date_obj
/

create or replace function rtn_refCursor
Return Std_Utl.generic_refcursor_Typ
Is
l_refCur        Std_Utl.generic_refcursor_Typ;
begin
open l_refCur for select sysdate from dual ;

return l_refCur ;
end;
/

declare

l_data      date_obj_tbl := date_obj_tbl();
l_refCur    Std_Utl.generic_refcursor_Typ ;
begin
    l_refCur := rtn_refCursor();

    loop
       l_data.extend ;
       fetch l_refCur into l_data(l_data.count);
       Exit when l_refCur%NOTFOUND or l_refCur%NOTFOUND Is  NULL ;
    end loop ;
   Close l_refCur ;
exception when others then
dbms_output.put_line('Sqlerrm=> ' || Sqlerrm );
end ;
/

Why the datatype error ?  I'm only fetching a date into a date variable here ?

I tried replacing the fetch line by expliciting giving it the object's member
name:
 fetch l_refCur into l_data(l_data.count).myDate ;

But the error is then:
ORA-06530: Reference to uninitialized composite.

I'm not sure what I did wrong here.

One last question, instead of returning RefCursor from a Function, I set the
refCursor as an OUT parameter, but I don't seem to be able to get data from the
RefCursor, is RefCursor not suppose to be used like this ?

Thanks a lot !

          Followup:

The datatype error is that l_data(l_data.count) is not a DATE, it is a
"DATE_OBJ".

declare

l_data      date_obj_tbl := date_obj_tbl();
l_refCur    Std_Utl.generic_refcursor_Typ ;
l_tmp       date;
begin
    l_refCur := rtn_refCursor();

    loop
       l_data.extend ;
       fetch l_refCur into l_tmp;
       l_data(l_data.count) := date_obj(l_tmp);
       Exit when l_refCur%NOTFOUND or l_refCur%NOTFOUND Is  NULL ;

will work. or

declare

l_data      date_obj_tbl := date_obj_tbl();
l_refCur    Std_Utl.generic_refcursor_Typ ;
begin
    l_refCur := rtn_refCursor();

    loop
       l_data.extend ;
       l_data(l_data.count) := date_obj(null);
       fetch l_refCur into l_data(l_data.count).myDate;
       Exit when l_refCur%NOTFOUND or l_refCur%NOTFOUND Is  NULL ;

ref cursors most certainly work as OUTS

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure rtn_refCursor(
p_refCur in out std_utl.generic_refcursor_typ )
  2  Is
  3  begin
  4  open p_refCur for select sysdate from dual ;
  5  end;
  6  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2
  3  l_data      date_obj_tbl := date_obj_tbl();
  4  l_refCur    Std_Utl.generic_refcursor_Typ ;
  5  begin
  6      rtn_refCursor(l_refCur);
  7
  8      loop
  9         l_data.extend ;
 10             l_data(l_data.count) := date_obj(null);
 11         fetch l_refCur into l_data(l_data.count).myDate;
 12         Exit when l_refCur%NOTFOUND or l_refCur%NOTFOUND Is  NULL ;
 13      end loop ;
 14     Close l_refCur ;
 15  end ;
 16  /

PL/SQL procedure successfully completed.

      Rating: 4
Excellent
      Way  August 21, 2002
Reviewer:  Bhaggs
       from UK
I used this to call a cursor object from VB using oracle ole provider..excellent
example

      Rating: 3
Yes, but it's
      not exactly what I was looking for  August 22, 2002

Reviewer:  Carlos  from Guatemala
I was needing help to create a Stored Procedure returning a Table which will be
the datasource for a Forms block.
That's due to the limitation of Ref Cursor in disallowing DML when using it as
datasource...
The problem: If I declare de type in the Procedure as a PL/SQL Table of Records,
the trigger query-Procedure wich will populate the block doesn't recognize it;
and if I declare the table as a SQL Type, compiling the query-procedure trigger
says that RPCs cannot use variables with schema level user-defined type in this
release.  I'm using Forms Developer 6i and Oracle 8i...

          Followup:

why do you believe this table of records would not suffer from the same dml
"limitations"?

      Rating: 4
Very helpful
      but how due you maipulate an object table  October 17, 2002

Reviewer:  Mark McLauchlan  from England
I have created my object table and can select from it
but is it possible to delete from the object table where conditions are met by
joining to a database table.

Something like

DELETE from "myObjectTable" test
WHERE EXISTS
(SELECT 'X'
FROM table a
WHERE a."column_name" = test."objectAttribute")

          Followup:

You cannot use INSERT/UPDATE/DELETE DML on a "variable" -- only on tables (real
tables, stored on disk)

You procedurally process the variable, just like an array.  There are functions
like .DELETE you can apply to each element.

      Rating: 3
Dunno if its
      related but....  October 17, 2002
Reviewer:  A
      Reader  from BG
Tom,
  I have a situation where i need to reuse the result of a query. These are
expensive queries and consume a lot of resources. So, is there a way so that i
can avoid firing them again?

If i have a database function which returns a refcursor can i have the resultset
cached such that i can reuse that.
Initially thought of having a PL/SQL table in the database.

regards

          Followup:

global temporary tables.

create global temporary table gtt ( .... ) on commit PRESERVE|DELETE rows;

do that once in your database for each "expensive query type" and then in your
code, you will

   insert into gtt <select complex query here>;

and now you just use gtt where ever you would have used complex query.

      Rating:
      4
  October 18, 2002
Reviewer:  Mark
      McLauchlan  from England
What I am trying to do is return a cursor that includes only the records in the
object table that don't don't exist in my physical table(allocated_panel_periods
-millions of rows). Looking to select a small subset from the object table using
a join condition that removes all the records that exist on the system from the
result set.

The problem is it always seem to return all records in the object table. Any
help would be much appreicated??

FOR c_import_upsite IN
 (SELECT *
  FROM TABLE (CAST (
  gl_import_upsite_tab AS importUpsiteTableType)) import_upsite WHERE NOT EXISTS
     (SELECT 'x'
     FROM allocated_panel_periods app
     WHERE app.contractor_code =   import_upsite.contractor_code
             AND      app.district_code = import_upsite.district_code
             AND      app.TOWN_CODE = import_upsite.town_code
             AND      app.SITE_CODE = import_upsite.site_code
             AND      app.PANEL_CODE = import_upsite.panel_code
             AND       app.package_id = p_package_id)
             ) LOOP

 DBMS_OUTPUT.put_line ('output1 - ' || _import_upsite.site_code );

        END LOOP;

          Followup:

ops$tkyte@ORA920.US.ORACLE.COM> create or replace type myScalarType as object
  2  ( username varchar2(30),
  3    created date,
  4    user_id number
  5  )
  6  /

Type created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace type myArrayType as table of
myScalarType
  2  /

Type created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> declare
  2      l_data myArrayType := myArrayType( myScalarType( null, null, null ),
  3                                         myScalarType( 'Hi', sysdate, 55 ) );
  4  begin
  5      select username, created, user_id
  6        into l_data(1).username, l_data(1).created, l_data(1).user_id
  7        from all_users
  8       where username = USER;
  9
 10      for x in
 11      ( select *
 12          from TABLE( cast( l_data as myArrayType ) ) import_upsite
 13         where NOT EXISTS ( select null
 14                              from all_users
 15                             where all_users.username =
import_upsite.username
 16                               and all_users.created = import_upsite.created
 17                               and all_users.user_id = import_upsite.user_id
)
 18      )
 19      loop
 20          dbms_output.put_line( x.username || ',' || x.created || ',' ||
x.user_id );
 21      end loop;
 22  end;
 23  /
Hi,18-OCT-02,55

PL/SQL procedure successfully completed.

worked for me (817 on up) -- perhaps P_PACKAGE_ID is not set or not set
properly??

      Rating: 3
Tried
      this?  October 22, 2002
Reviewer:  Mark
      McLauchlan  from England
Tom,

I am using Oracle 8.1.7.0.0

Tried your example and NOT EXISTS does not return anything.

When I change it to EXISTS it returns everything in the object.

SQL> l
  1  declare
  2          l_data myArrayType := myArrayType( myScalarType( null, null, null
),
  3                                            myScalarType( 'Hi', sysdate, 55 )
);
  4      begin
  5          select username, created, user_id
  6            into l_data(1).username, l_data(1).created, l_data(1).user_id
  7            from all_users
  8           where username = USER;
  9         dbms_output.put_line('These are the lines in the array');
 10         dbms_output.put_line( l_data(1).username || ',' ||
l_data(1).created || ',' || l_data(1).use
 11         dbms_output.put_line( l_data(2).username || ',' ||
l_data(2).created || ',' || l_data(2).use
 12         dbms_output.put_line('----------------------------');
 13         dbms_output.put_line('Now select the data required');
 14         for x in
 15         ( select *
 16             from TABLE( cast( l_data as myArrayType ) ) import_upsite
 17            where NOT EXISTS ( select null
 18                                 from all_users
 19                                where all_users.username =
 20  import_upsite.username
 21                                  and all_users.created =
import_upsite.created
 22                                  and all_users.user_id =
import_upsite.user_id
 23  )
 24         )
 25         loop
 26             dbms_output.put_line( x.username || ',' || x.created || ',' ||
 27  x.user_id );
 28         end loop;
 29*    end;
SQL> /
These are the lines in the array
MARK_DEV_DATA,19-JUN-02,74
Hi,22-OCT-02,55
----------------------------
Now select the data required

PL/SQL procedure successfully completed.

SQL>

          Followup:

Suggest you look into patching up to 8174 -- the supported release.  It would
appear to be an issue with 8170 given that I know it works on 8174.

      Rating: 4
selecting
      object via view  November 06, 2002
Reviewer:
      Mahomed  from U.K
I have created an object type in a package and return it via a view as follows:

Create or Replace Type Shm_Row_Type
As Object (
     shm_id         number(5),
     shm_val        number(1),
     shm_dat        date
          );
/
show errors;

Create or Replace Type Shm_Type Is Table Of Shm_Row_Type;
/
show errors;

create or replace package pkg_shm
as

  procedure init_shm ;

  function ret_shm return Shm_Type;

  Shm_Tab   Shm_Type;

end;
/
show errors;

create or replace package body pkg_shm
as

procedure init_shm
is
BEGIN

  pkg_shm.Shm_Tab := Shm_Type();
  pkg_shm.Shm_Tab.Extend;
  pkg_shm.Shm_Tab( 1 ) := Shm_Row_Type( 1, 9, sysdate );
  pkg_shm.Shm_Tab.Extend;
  pkg_shm.Shm_Tab( 2 ) := Shm_Row_Type( 2, 8, sysdate + 1);
  pkg_shm.Shm_Tab.Extend;
  pkg_shm.shm_Tab( 3 ) := Shm_Row_Type( 3, 7, sysdate + 2);

END;

function ret_shm return Shm_Type
is
--
--
BEGIN

   return pkg_shm.Shm_Tab ;

END;

end;
/
show errors;

create or replace view vwshm
 as Select * From Table( Cast(pkg_shm.ret_shm() As Shm_Type ) )
/

If I run sqlplus in one session, then exec pkg_shm.init_shm,
I can subsequently select * from vwshm and get 3 rows.
If I leave this session running and run sqlplus in another session and select *
from vwshm, I get no rows. My question is how can I get another session to see
this object? Is this a valid operation in the first place?

          Followup:

Each session has its own "data segment".  It would be a really bad thing if that
other session could "see" that sessions data (meaning it could change that
sessions data -- meaning that package global variables would be virtually
USELESS)

If you want all sessions to see that data -- put it in a real table and commit
it.

      Rating: 5
Select specific
      value  November 06, 2002
Reviewer:  Ken
       from Canada
How do I select a specific value..say May-28-00 from this table type?

          Followup:

don't understand the question?

      Rating: 3
Where clause
      for cast  November 06, 2002
Reviewer:
      A reader
I think, Ken want to know how a where clause is applied for cast.

          Followup:

Just like anything else -- where....

 14      open p_cursor for
 15      select *
 16        from TABLE ( cast ( l_data as myTableType) );
          where column_value = .....
 17  end;

(column_value is the name given to single column tabletypes built on scalars)

      Rating: 5
SELECT from Ref
      cursor  February 12, 2003
Reviewer:  Edgar
      Chupit  from Riga, Latvia
Hello Tom,

Is there a posibility to select from function that returns ref cursor. For
example:

create or replace package my_pkg
  as
      type rc is ref cursor;

      function p return rc;
  end;

create or replace package body my_pkg
    as

    function p return rc
    is
        p_cursor rc;
        l_data  myTableType := myTableType();
    begin
        for i in 1 .. 3 loop
            l_data.extend;
           l_data(i) :=
              myScalarType( i, sysdate+i, i || ' data');
       end loop;

       open p_cursor for
       select *
         from TABLE ( cast ( l_data as myTableType) );

       return p_cursor;
   end;

   end;

Can I now do something like

select * from ( cast ( my_pkg.p ) as ??? )

          Followup:

No, not really.  You would have to write a pipelined function in 9i that fetched
the rows and piped them from the ref cursor (so sort of, but not really)

      Rating: 4
Great idea, but
      I cann't get Oracle Report to work on the Ref
      Cursor  February 14, 2003
Reviewer:  Michael
       from Australia
Hi Tom

I follow your initial example and made a little modification to it so that I can
pass a STRONG ref cursor to Oracle Report for getting the column name (required
for building the layout).  However, whenever I run the report, it hangs, never
return and I have to terminate it (in window 'end task').  Could you please help
me out?

The reason I did this test is that I have project that has an existing procedure
that takes in a plsql table A and return anther plsql table B. And I want to be
able to run report to format and print out the content of plsql table B.

I am on Oracle 8.1.7.4, running Oracle Report Builder 6.0.8.13.1 on Window NT
4.0

The code is as follow:

On SqlPlus Release 8.1.7.0.0 I did this

SQL*Plus: Release 8.1.7.0.0 - Production on Fri Feb 14 16:41:01 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production

SQL> create or replace type myScalarType as object
  2  (   x     int,
  3      y    date,
  4      z    varchar2(25)
  5  )
  6  /

Type created.

SQL> create or replace type myTabType as table of myScalarType;
  2  /

Type created.

SQL> create or replace package my_pkg
  2   as
  3   type rc is ref cursor;
  4
  5   type rec_type is record
  6   (  x     int,
  7      y    date,
  8      z    varchar2(25)
  9   );
 10
 11   type rc_rec is ref cursor return rec_type;
 12
 13   procedure p( p_cursor in out rc_rec );
 14  end;
 15  /

Package created.

SQL> create or replace package body my_pkg
  2   as
  3  procedure p( P_cursor in out rc_rec )
  4  is
  5      l_data  myTabType := myTabType();
  6  begin
  7      for i in 1 .. 3 loop
  8          l_data.extend;
  9          l_data(i) :=
 10             myScalarType( i, sysdate+i, i || ' data');
 11      end loop;
 12
 13      open p_cursor for
 14      select *
 15        from TABLE ( cast ( l_data as myTabType) );
 16  end;
 17
 18  end;
 19  /

Package body created.

The reason that I use rc_rec is that it is a strong ref cursor, so that in the
Oracle report I can get the columns and build the layout.  That the only
modification!

In Oracle Report, the Query PL/SQL is:

function QR_1RefCurDS return my_pkg.rc_rec is
  x  my_pkg.rc_rec;
begin
  my_pkg.p(x);
  return x;
end;

As far as I know this is the standard way Oracle Report based its data from a
Ref cursor.  I can see all the columns BUT when I run the reports it hangs! no
response at all and eventually I had to terminate Report builder!

I went back to sqlplus and tested the package once again.  It works from
sqlplus!

SQL> set autoprint on
SQL> variable x refcursor
SQL> exec my_pkg.p(:x)

PL/SQL procedure successfully completed.

        X Y         Z
--------- --------- -------------------------
        1 15-FEB-03 1 data
        2 16-FEB-03 2 data
        3 17-FEB-03 3 data

Could you please help!
Thanks a million.

          Followup:

I've never used reports in my life -- have to refer you (in this order) to

o support

o otn.oracle.com -> discussion forums -> developer to see if anyone there has an
idea.

but - this is clearly a support issue.

      Rating: 4
How to "fetch
      into" dynamically set of columns?  April 23, 2003

Reviewer:  A reader
Tom,
I am opening a ref cursor based upon argument of table name as:
....
OPEN cv FOR 'SELECT * '||' FROM '||p_tname;
...
1. How I do issue FETCH INTO .....?
as number of columns are different for different tables. If you could also
please show me the reference to these INTO variables for further processing.
2. Is it possible to use BULK PROCESSING with ref cursors?
Thanks

          Followup:

you use DBMS_SQL cursors for this -- they can be programatically described,
bound to, and fetched from.

yes you can bulk collect with ref cursors.
no you cannot in your case.

but that is OK, dbms_sql allows for an array bind -- if you have my book "Expert
one on one Oracle" -- lots of examples in there.

      Rating: 5
Exactly what I
      was looking for!!!  June 02, 2003
Reviewer:
      D.Marcil  from Montreal, Quebec, Canada
Thanks a lot! I was searching everywhere I knew for the answer!
But your site gave me the exact way to do it! (it is not the first time!!!!)

I also wanted to sort that SQL table. I finaly succeeded by using «order by
3,1,5» just like an ORDER BY clause in a UNION select clause.

Thanks !!!

      Rating: 4
How to convert
      local PL/SQL table to ref cursor  August 12, 2003

Reviewer:  Sachin  from India
since all the mentioned solutions were for PL/SQL types defined using CREATE
TYPE, what abt the PL/SQL type created locally witin a package as shown below -
compiles with error

Package       TEST_PKG
  IS

   type ref_cur_type  is  ref cursor;
   type record_type is record(col1 varchar2(50),col2 varchar2(50));
   type table_type is table of record_type index by binary_integer;
   PROCEDURE ref_proc
     ( param2  OUT ref_cur_type);

  FUNCTION ref_function
     RETURN  table_type;

END; -- Package spec

Package Body       TEST_PKG
IS

   PROCEDURE ref_proc
     ( param2  OUT ref_cur_type)is
     x table_type ;
   begin
    x := ref_function;
    open param2 for select * from table(cast(x as table_type)) ;

   end;

  FUNCTION ref_function
     RETURN  table_type
     is
     x table_type;
   begin
    x(1).col1 := '1';
    x(1).col2 := '2';
    x(2).col1 := '3';
    x(2).col2 := '4';
    return x;
   end;

END;

error at the table-cast stmt
----
(1):PLS-00382: expression is of wrong type
(2):PL/SQL: SQL Statement ignored

          Followup:

if you want SQL to see the TYPE -- you need to create the TYPE in SQL -- not in
plsql.

a plsql table type is just that - PLSQL only.

PLSQL is "above" SQL, PLSQL can see all things PLSQL and SQL.

SQL is "below" PLSQL.  SQL can see all things SQL.

      Rating: 5
SQL functions
      on a single-column table type  October 03, 2003

Reviewer:  B. Robinson
I've found SQL objects and nested table types to be extremely useful for
handling and passing collections of data that are derived from a PL/SQL
application (i.e. cannot be selected from a database table).

But I have a slight problem with this table of NUMBER.

SQL> create type numbertable as table of number;
  2  /

I can use TABLE and CAST to SELECT * from it.

But I also want to do things like SELECT SUM(), STDDEV() and other functions on
it.  The trouble is that I don't know name what to put within the parentheses.
Will I be forced to create an Object type which holds nothing but a single
NUMBER field, or is there a way to use SQL functions on the single nameless
NUMBER column as defined above?

          Followup:

COLUMN_VALUE

is the name of the "magical" column you are looking for.  It's documented -- but
the way I found it was to use the fact that a view will expose it for me.

ops$tkyte@ORA920> create or replace type myTblType as table of Number
  2  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace view v
  2  as
  3  select * from TABLE( myTblType(1,2,3,4) );

View created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> desc v
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 COLUMN_VALUE                                 NUMBER

ops$tkyte@ORA920> select sum(column_value) from table( myTblType(1,2,3,4) );

SUM(COLUMN_VALUE)
-----------------
               10

October 04, 2003 Ram -- Thanks for the question regarding
      "Prepared statement Vs regular statement in JDBC", version 8.1.6

      You Asked
Tom:

I'm not sure whether to use a PreparedStatement or just the regular Statement if
a query is going to be executed only once before the statement is closed.

I have read through some documentation regarding this topic and still not
exactly clear on which way is better for the following scenario.

I have a EJB(stateless session bean) that needs to retrieve a record from the
database and returns an Object (Object created using the resultset getXXX
methods..).

If I use a PreparedStatement,  connection.prepareStatement(sql) would have to
dip to the database to see if a cached statement is available or need to
precompile this 'sql'. And another dip to the database to execute this prepared
statement. Once I have constructed my Object using the resultset, both the
resultset and the prepared statement are closed (connection is returned to the
pool). The same steps happen for every request to the EJB (prepareStatement(),
execute(), close()). Is there any benefit in using preparedStatement for this
scenario ?

In contrast, if I used the regular Statement, my guess would be the
connection.createStatement() just returns a new object reference(instead of the
database dip in the case of preparedstatement). My execute will dip to the
database. The only problem i see with this approach is the sql statement has to
be parsed everytime before execution (or will this be cached ???). Is there some
guidelines on when to use PreparedStatement Vs Statement?

Thanks,
Ram.

      and we said...
The use of a Statement in JDBC should be 100% localized to being used for DDL
(ALTER, CREATE, GRANT, etc) as these are the only statement types that cannot
accept BIND VARIABLES.

PreparedStatements or CallableStatements should be used for EVERY OTHER type of
statement (DML, Queries).  As these are the statement types that accept bind
variables.

This is a fact, a rule, a law -- use prepared statements EVERYWHERE.  Use
STATEMENTS almost no where.

You say:

"If I use a PreparedStatement,  connection.prepareStatement(sql) would have to
dip to the database to see if a cached statement is available or need to
precompile this 'sql'"

Well, change PreparedStatement to Statement and the SAME is true!!  Except with
the Statement, you've probably made a totally unique statement that never
existed -- we won't find it in the cache and you'll do a hard parse.  You'll
discover you probably spend more time PARSING then executing statements!!!  If
you submit statements like:

   select * from t where x = 5;
   select * from t where x = 56;
   select * from t where x = 32;

instead of

   select * from t where x = ?

I can assure you that:

o your app will not scale
o your app will run slow
o your app will fail eventually

So, there is HUGE benefit from using a prepared statement here (if you get my
book, I go over this again and again and again and again).

Your guess on the statement would be incorrect.  A prepared statement is simply
a statement that can also do bind variables.  There is a statement under the
covers!

So the guidelines:

o never use statements.
o always use prepared or callable statements.

Review &
      Followup

      Rating:
      4
  March 21, 2002
Reviewer:  Ron
       from York UK
Tom

I have your book. Im working with a java application that
dynamically generates sql against views using Statements
rather that PreparedStatements as in the example below.
I've used the cursor_sharing=force parameter to try and get some cursor sharing
benefits.
SELECT
  glnk_link_id_n,
  glnk_description_c,
  gloc_from_location_id_n,
  gloc_from_location_c,
  gloc_to_location_id_n,
  gloc_to_location_c,
  glin_line_full_name_c
FROM
  vlnk_link_vw
WHERE
  gloc_from_location_id_n = :"SYS_B_0" AND
  gloc_to_location_id_n = :"SYS_B_1"
ORDER BY glin_line_full_name_c

Looking at the parse to execute ratio for the above
it is 120 to 120 or 1:1. No benefit from the cursor sharing
=force? Is this a feature of views or java Statements being dynamically
generated? Am I wasting my time with cursor_sharing=force in this type of
environment ?

          Followup:

That is because you are parsing the statement 120 times ( i point this out in my
book as well -- I DON'T LIKE cursor sharing.  it only gets you PART WAY there
and is NOT the end solution, just a temporary bandaid till you write your code
properly-- using bind variables).

At least 119 of the 120 are soft parses (not as bad as a hard parse but not
NEARLY as good as NO parse).  Use tkprof and look at the misses in library cache
or use statspack to see the soft parse ratio (covered in chapter 10)

Are you wasting your time?  Yes -- if you could be using this time to fix your
code to use a prepared statement that you parse ONCE per session and reuse over
and over again.

      Rating:
      5
Parsing+Prepared Statement  August 01, 2003

Reviewer:  Ashok  from USA
I am using prepared statement to insert multiple rows(39731 rows)the way i am
doing is like this

Parse
loop
 bind
 execute
end loop

But when i looked into TKPROF i have got 4598 parses and 4598 executes.I was
excepting one parses and mutilple executes.I am assming majority of the parses
are soft parses.i assumed that if i keep my cursor open(i.e Prepare statement
only once) oracle will excute with going for soft parse or softer softer parse.

TKPROF Output:

INSERT INTO tr_ap_ishare_curr
values
(:1,:2,:3,:4)

call     count       cpu    elapsed    rows
----     ------  -------- ----------  ---------
Parse     4598      1.29       1.25    0
Execute   4598      2.25       5.33    39731
Fetch        0      0.00       0.00    0
------- ------  -------- ----------   ----------
total     9196      3.54       6.58    39731

Misses in library cache during parse: 1
Optimizer goal: RULE
Parsing user id: 472

Thx
Ashok

          Followup:

language?  code looks like what?

      Rating: 4
also might try
      array interface  August 01, 2003
Reviewer:
      Jim  from Portland, OR
A lot of the apis to interface with Oracle (oci, oo4o, etc.) allow you to insert
multiple rows at one time.  Usually this is under the documentation concerning
the array interface.  I have seen excellent throughput when using the array
interface for retrieving or inserting multiple rows at a time.

      Rating:
      5
Parsing+Prepared statement  August 01, 2003

Reviewer:  Ashok  from USA
COde In Java:
 while(lrs_dest.next())
                {
                   if ( lstmt_ins_source == null )
                        {
                          lstmt_ins_source  =
lcon_source.prepareStatement(ls_insinv);
                         }

                              lstmt_ins_source.setInt(1,lrs_dest.getInt(1));
                              lstmt_ins_source.setInt(2,lrs_dest.getInt(2));
                              lstmt_ins_source.setFloat(3,lrs_dest.getFloat(3));
                              lstmt_ins_source.setFloat(4,lrs_dest.getFloat(4));
                              /*DO it in batch so we can  avoid context
switching*/
                              lstmt_ins_source.addBatch();

                }
                              lstmt_ins_source.executeBatch();
                             lstmt_ins_source.clearBatch();

          }

          Followup:

I don't like that implementation -- just use a normal prepared statment and
this:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/java.920/a96654/oraperf.htm#1056502
with that addBatch, it tends to over parse since the generic implementation
could have EACH statement in the batch be totally different then the prior one.

      Rating:
      5
Parsing+prepared statement  August 01, 2003

Reviewer:  Ashok  from USA
>>I don't like that implementation -- just use a normal >>prepared statment and
this:

What is wrong in the code.What do you meant by normal prepared statement.Can you
please explain it .

          Followup:

it is the implementation of the addBatch -- each and every statement added by an
addBatch could be different -- therefore, they parse it each time when you
batch.

the code is acting as it should (which is unfortunate).  using the other method
will achieve better results.

      Rating:
      5
Parsing+PreparedStatement ;-Great Tom  August 01,
      2003
Reviewer:  Ashok  from USA
Great that worked as expected,one parse and 3695 executes
I didn't tought that Addbatch was culprit there.

Tom can you please explain why AddBatch Increase the parse count.

          Followup:

because you can:

  declare a prepared statement

  parse stmt1 with it
  add it to the batch
  parse a different stmt2 with it
  add it to the batch
  and so on

EVERY statement batched could be a totally different statement

      Rating: 5
Prepared
      Statement+Parsing  August 01, 2003
Reviewer:
      Ashok  from USA
If i use Oracle Prepared statement + Batch Updates, will the parse count will be
one (i.e "One parse" and Many executes)

I typed previous  reviews without reading your feeback on my first review.Thanks
for addbatch response.

          Followup:

it is all about tkprof.... use it and you'll know ;)

      Rating: 5
Using generic
      PreparedStatement is ok  October 03, 2003

Reviewer:  Peter Tran  from Houston, TX USA

Tom,

Correct me if I'm wrong, but using the generic PreparedStatement should be ok
and result in only one parse (hard if it's a new SQL).

E.g.  This is one from something you helped me with earlier.  My logic here is:

get connection
create PS
while ()
{
   ps.setXXX();
   ps.addBatch()
}
call ps.executeBatch();
ps.close();
close connection;

--------------

UPDATE
   fe_exm_observation_values  SET    UnconstrainedValue=:1
WHERE
    DepthPos=:2   AND OBS_ID=(OBS_ID   FROM     fe_exm_observations
  WHERE     LeafKey=:3     AND RowPos=:4     AND ColPos=:5   )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.49       0.66         17       2753        412         400
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.49       0.66         17       2753        412         400
Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 70

Rows     Row Source Operation
-------  ---------------------------------------------------
    400  UPDATE  (cr=1153 r=5 w=0 time=73985 us)
    400   PARTITION HASH SINGLE PARTITION: KEY KEY (cr=1151 r=3 w=0 time=43456
us)
    400    INDEX UNIQUE SCAN FE_EXM_OBSERVATION_VALUES_PK PARTITION: KEY KEY
(cr=1151 r=3 w=0 time=42621 us)(object id 150243)

Note, I only do one soft parse, but I update 400 rows, because I call addBatch()
400 times with different bind values.  This is using the 9.2.0.3 Oracle driver.

BTW, can you check that link above?  I takes me to the documentation for all
documents.

Thanks,
-Peter

          Followup:

clear indication that things change over time.  apparently the newest drivers
have been optimized somewhat.

the "this" i referring to, before they moved the documentation yet again on me,
was to set the "batch" size on a prepared statement -- you can say "you know,
only really execute every N times or upon commit, please say N executes up
before sending them over to the server"

It is in the java developers guide.

October 04, 2003 praveen -- Thanks for the question regarding
      "Using dbms_rowid.rowid_row_number(rowid) in queries", version 8.1.7.0.0

      You Asked
Hello Tom,
I want to fetch records from a table in batches of n. The query which I used
gave me correct results with emp table but is giving erroneous results (fetching
more records than expected) with other tables which have got thousands of
records. Can you please explain why it is showing this behaviour?
This is the query I’ve used.

SQL>  select empno,ename
  2   from emp
  3   where dbms_rowid.rowid_row_number(rowid) between (1-1) and
  4  (5-1);

     EMPNO ENAME
---------- ----------
      7369 SMITH
      7499 ALLEN
      7521 WARD
      7566 JONES
      7654 MARTIN

SQL>  select empno,ename
  2   from emp
  3   where dbms_rowid.rowid_row_number(rowid) between (6-1) and (10-1);

     EMPNO ENAME
---------- ----------
      7698 BLAKE
      7782 CLARK
      7788 SCOTT
      7839 KING
      7844 TURNER
As shown above the query works fine in fetching records in batches of 5.When I
tried this query with a table having more than 3000 records with a change in the
where clause as below..

......where dbms_rowid.rowid_row_number(rowid)=1;...it gave me more than one
record. Is rowid unique only within a block? I searched in Oracle docs but didnt
get my doubt cleared..
Can I use this dbms_rowid.rowid_row_number(rowid)function safely in my web
application where I need to fetch records in batches.

-Praveen from India

      and we said...
rowids are unique within a table -- but they are made up of many fields.

there is a file/block/rownumber (slot on block)

your technique is getting rows N thru M from each block (every block has a row 0
after all)

select *
  from ( select a.*, rownum r
           from (YOUR_QUERY_HERE) A
          where rownum <= :MAX_ROW )
 where r >= :MIN_ROW;

is the technique you might be looking for.
Review &
      Followup

      Rating: 4
Still some
      doubt left..  April 30, 2003
Reviewer:  praveen
       from India
Hello Tom,
Thanks a lot for your quick response..Your advices are extremely invaluabe to a
beginner like me who is stepping into the world of Oracle.Tom, I have still some
doubts left ..
1) You said "rowids are unique within a table -- but they are made up of many
fields". If they are unique why it fetches multiple records for the same rowid
in my table?
2)The query you gave will definitely serve my purpose. But will it throw
performance issues when my table grows each day by the order of thousands..My
doubt is, will the temporary table within the query "( select a.*, rownum r
           from (YOUR_QUERY_HERE) A
          where rownum <= :MAX_ROW )" pose a threat to the system memory when
the data is loaded to the data buffer of SGA, each time the query is executed?
 Thanks in advance.
-Praveen from India

          Followup:

1) it is not fetching multiple records per rowid.  You are taking a single piece
of a rowid (the row number on the block) and getting every record with that row
number.  there are alot of records with row number "0"

if you use file/block/slot (row number) -- you would get one.

2) memory -- no.  performance probably not either -- as long as you get the
'first chunks'.  Think of it like a web search -- this is the query I use to
paginate through a result set with.  It is very efficient given a query that
gets the first rows fast.   it'll be "slower" obviously for the "last rows"

      Rating:
      5
Reader  October 04, 2003
Reviewer:
      A reader
From the Oracle Manual:

SELECT ROWID,
       SUBSTR(ROWID,1,6) "OBJECT",
       SUBSTR(ROWID,7,3) "FIL",
       SUBSTR(ROWID,10,6) "BLOCK",
       SUBSTR(ROWID,16,3) "ROW"
       FROM products;

ROWID               OBJECT  FIL  BLOCK   ROW
------------------  ------  ---  ------  ----
AAAA8mAALAAAAQkAAA  AAAA8m  AAL  AAAAQk  AAA
AAAA8mAALAAAAQkAAF  AAAA8m  AAL  AAAAQk  AAF
AAAA8mAALAAAAQkAAI  AAAA8m  AAL  AAAAQk  AAI

Or you can use SUBSTR to break a restricted rowid into its three components
(block, row, and file):

SELECT ROWID, SUBSTR(ROWID,15,4) "FILE",
       SUBSTR(ROWID,1,8) "BLOCK",
       SUBSTR(ROWID,10,4) "ROW"
       FROM products;

ROWID               FILE  BLOCK     ROW
------------------  ----  --------  ----
00000DD5.0000.0001  0001  00000DD5  0000
00000DD5.0001.0001  0001  00000DD5  0001
00000DD5.0002.0001  0001  00000DD5  0002

Tom, is there way to convert row# AAF from extended rowid
to conform to corresponding row# 0001 in the restricted
rowid

          Followup:

use dbms_rowid to get bits and pieces of a rowid -- much easier.

October 04, 2003 Sue -- Thanks for the question regarding "How to
      convert a table with long_row data into a CLOB data type table", version
      8.1.6

      You Asked
Developer was asking a question about how to convert a table with
long_row data into a CLOB data type table? Does Oracle provide
any utility to do it?

      and we said...
Create table new_table
as
select c1, c2, c3, to_lob(c4) c4
from old_table
/

In Oracle9i -- there is a very cool "alter table t modify long_col CLOB" to do
this as well....

Review &
      Followup

      Rating: 3
long raw to
      blob..... same kind of question.   August 30, 2001

Reviewer:  anshula  from india
columns in test_long_raw_tab  are id (number) and text_long_raw (long raw)
columns in test_blob_tab  are id( number) and text_blob (blob).

Can I use this to insert data from test_long_raw to test_blob
if  test_long_raw_tab  contains image? it working file for text files.

 declare
 cursor c1 is
 select text_long_raw
 from test_long_raw_tab
 where id  = 200000020;
 begin
 for c In c1 loop
 insert into test_blob_tab  values (1,c.text_long_raw);
 commit;
 end loop;
 end;
 /

          Followup:

It only works for LONGS and LONG RAWS that are 32k and less.

An INFINITELY more efficient method would be simply:

insert into test_blob_tab
select 1, to_lob(text_long_raw)
  from text_long_raw_table
where id = .......;

just do it in a single statement.

      Rating:
      3
  September 04, 2001
Reviewer:
      A reader
Thanks for answering my question.
Using create table X as select ... is only for same tablespace; but our CLOB are
created in defferent tablespace. How do we solve this problem ?

          Followup:

Perhaps the easiest method is to create the table first and then use INSERT INTO
SELECT ... to_lob() instead of CREATE TABLE as SELECT ....

You can do it in the CTAS however:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t2 ( x, y )
  2  tablespace users
  3  lob (y) store as ( tablespace drsys disable storage in row )
  4  as
  5  select x, to_lob(y)
  6  from t;

Table created.

      Rating: 4
long to clob
      conversion in 9.2  February 10, 2003
Reviewer:
      Baqir Hussain  from Emeryville,CA USA
Tom,
It's really cool to change long into clob using the following sql query
alter table journal modify body clob;
The table has over 4 million records and would take more than 2 hours to convert
from long to clob.

Is there any other way to expediate this procedure?
thanks and oblige.

          Followup:

CTAS with parallel nologging followed by
reindex (which you have to do after an alter table modify as well)
grant
constrain
rename of table.

      Rating: 5
update a
      long  October 04, 2003
Reviewer:  Kumar
       from Houston, TX
Tom, I have an urgent requirement at work to update a long column that contains
a two page document for every row in a table. I have been asked to update two
sentences in the long col doc in such a way that If I find these two sentences
in each long col, update them to different information. Please help me how i can
do it? Thanks much.

          Followup:

what is the size of the longest long?

and what is your favorite programming language in case you say "a number bigger
then 32k"

      Rating: 5
update a long
      col  October 04, 2003
Reviewer:  Kumar
       from Houston
what is the size of the longest long?

It is about 20k. The database version is 8.1.7.3.
Thanks.

          Followup:

declare
   temp_col long;
begin
   for x in ( select rowid rid, t.* from t )
   loop
      if ( x.long_column like '%strings you are interested in%' )
      then
          temp_col := ..... whatever you want to fix string up to be, use
                      substr/instr, whatever to fix it up....
          update t set long_column = temp_col where rowid = x.rid;
      end if;
   end loop;
end;
/

      Rating: 5
Thanks so
      much.  October 04, 2003
Reviewer:  Kumar
       from Houston, USA

October 04, 2003 Erik -- Thanks for the question regarding
      "Progessive slow down when inserting into a table with a clob", version
      8.1.7

      You Asked
Tom,

I'm using PL/SQL to load a new table that is going to contain an XML document
stored in a CLOB.  I have to build the XML document from data in an existing
table in the same database, basically each column gets its own tag.  The rate
that new rows are being inserted into the table keeps dropping and I haven't
been able to figure out why.  When the table was empty and we started the load,
about 2200 rows an hour were being inserted.  The rate kept dropping though
until only 100 rows an hour were being inserted.  I stopped to procedure and
restarted it with the primary key on.  It seemed to speed up initially.  It took
5 minutes to do the first 100, but then 15 minutes to do the next 100, and
progressively slower.

The tables look something like this (but with more columns):

create table src(
doc_id number,
a      varchar2(20),
b      varchar2(30),
c      clob);

create table dest(
doc_id number,
doc    clob);

CREATE OR REPLACE PROCEDURE load_docs
as
v_clob      clob := empty_clob();
v_counter   number := 0;
v_tmp       varchar2(1000);
cursor my_cursor is
   select doc_id, a, b, c from src;

begin

  FOR my_rec
  IN my_cursor
  LOOP
    insert into dest(doc_id, doc)
    values (my_rec.doc_id, EMPTY_CLOB())
    returning doc into v_clob;

    v_tmp := '<DOCUMENT>';
    dbms_lob.writeAppend(v_clob,length(v_tmp),v_tmp);

    v_tmp := '<A_TAG>' || my_rec.a || '</A_TAG>';
    dbms_lob.writeAppend(v_clob,length(v_tmp),v_tmp);

    v_tmp := '<B_TAG>' || my_rec.b || '</B_TAG>';
    dbms_lob.writeAppend(v_clob,length(v_tmp),v_tmp);

    v_tmp := '<C_TAG>';
    dbms_lob.writeAppend(v_clob,length(v_tmp),v_tmp);

    dbms_lob.append(v_clob,my_rec.c);

    v_tmp := '</C_TAG></DOCUMENT>';
    dbms_lob.writeAppend(v_clob,length(v_tmp),v_tmp);

    if v_counter = 100
      then
        commit;
        v_counter = 0;
    end if;
  end loop;

  commit;

end load_docs;
/

One thought we had was that the returning clause on the insert needed a PK to
avoid doing a full table scan (which would explain the progressively slower
behavior).  We added a PK after we stopped the load and then restarted, but got
the same behavior.

The disk the DB is on is raid 5, which accounts for some slowness on writes but
I would think that that should be a constant.

I need to run this for about 2 million records, and with this speed it's going
to take a LONG time.

Do you have any idea what could be accounting for this progressive slowdown?  I
would appreciate any thoughts you would have on what might be causing this.  Is
it the procedure or is something not tuned correctly?

Thanks,

Erik

      and we said...
Well, you are seeing the effect on the raid 5 and buffering.

When you start (after you pause for example), there are lots of log buffers/data
buffers just sitting their.  Now, you start going full bore.  The log
buffers/data buffers start filling up, you run out of them and we have to flush
them (you make the problem worse by committing frequently -- worst thing you can
do -- ESPECIALLY since your load doesn't appear to be restartable at all -- you
cannot restart this process, you would have to truncate and do it over).

Make SURE your logs are not on raid 5.

Use statspack and/or tkprof (if you have my book read chapter 10) to find out
what your major waits are for (log file sync I'll guess).

Avoid the undue overhead you incurr by going back and forth and back and forth.

My suggestion is as follows:

IF the result of the string concatenation will be 4000 characters or less, use
this approach:

ops$tkyte@ORA817DEV.US.ORACLE.COM> set timing on
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int, y clob );

Table created.

Elapsed: 00:00:00.10
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t NOLOGGING;

Table altered.

Elapsed: 00:00:00.03
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ APPEND */ into t
  2  select object_id,
  3            '<document><a_tag>' || object_id || '</a_tag>'   ||
  4            '<b_tag>' || object_name || '</b_tag>' ||
  5            '<c_tag>' || object_type || '</c_tag>' ||
  6            '<d_tag>' || owner || '<d_tag><document>' data
  7       from all_objects;

22916 rows created.

Elapsed: 00:00:07.70
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t logging;

Table altered.

Do it in a single, nologged SQL insert into statement using the direct path
hint (append).  This will be far far superior to all other methods.  Fastest
approach you can take.

If not, at least do as much as you can in SQL and if the XML document will be
32k or less use this approach:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure load_docs
  2  as
  3      type array is table of varchar2(4000) index by binary_integer;
  4
  5      l_object_id   array;
  6      l_data        array;
  7      l_empty       array;
  8
  9      l_string      varchar2(31000);
 10
 11      cursor my_cursor is select object_id,
 12                                 '<a_tag>' || object_id || '</a_tag>'   ||
 13                                 '<b_tag>' || object_name || '</b_tag>' ||
 14                                 '<c_tag>' || object_type || '</c_tag>' ||
 15                                 '<d_tag>' || owner || '<d_tag>' data
 16                            from all_objects;

use as many "data" columns as you need.  put together as many columns as you
can safely without exceeding 4000 characters/column. Later, when we assemble
l_string, we'll do that final concatenation

 17
 18  begin
 19      open my_cursor;
 20      loop
 21          fetch my_cursor BULK COLLECT into l_object_id, l_data LIMIT 500;
 22

bulk collect the data, avoid single row processing...

 23          for i in 1 .. l_object_id.count
 24          loop
 25              l_string := '<document>' || l_data(i) ||  '</document>';
 26              insert into t (x,y) values ( l_object_id(i), l_string );
 27          end loop;
 28

unfortunately, we cannot bulk insert into a clob -- but we can at least cut
way down on the round trips (dbms_lob.writeappends, returning clauses and
such)

 29          exit when MY_CURSOR%NOTFOUND;
 30          l_object_id := l_empty;
 31      end loop;
 32      close my_cursor;
 33      commit;

Please, commit when you are done -- not in the middle!!! frequent commits will
achieve this for you:

o you will run slower
o you will generate more overall redo log
o you will generate more overall rollback

 34  end;
 35  /

Procedure created.

Elapsed: 00:00:00.24
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec load_docs

PL/SQL procedure successfully completed.

Elapsed: 00:00:16.15
ops$tkyte@ORA817DEV.US.ORACLE.COM>

As you can see.... the insert in a single statement will be your preferred
method.  Followed by more optimized plsql.  If you can go with the insert append
in a single statement, you can massively reduce the amount of IO you will do.
If not, you will need to TUNE your logs (i'll guess with 80% certainty that they
are your bottleneck here -- the progressive slow down is indicative of this and
over time you are starting to wait even on dbwr as the buffer cache is filled
and you get buffer busy waits).

Use statspack to find out what the biggy waits are.
Review &
      Followup

      Rating: 4
Really helpful,
      as usual  January 24, 2003
Reviewer:
      Heraldo  from Spain
But I still have a little doubt about this,
In your example above you wrote:
insert /*+ APPEND */ into t
select object_id,
            '<document><a_tag>' || object_id|| '</a_tag>' ||
           '<b_tag>' || object_name || '</b_tag>' ||
           '<c_tag>' || object_type || '</c_tag>' ||
          '<d_tag>' || owner || '<d_tag><document>' data
from all_objects;
with  t ( x int, y clob );
Acording Oracle it's not possible to do an direct-load over a table with a LOB
column. If I'm rigth, we only could make a nologging insert, but not a
direct-load. Am I wrong in my conclusions? If yes, could you please tell me why?
Thanks in advance.
Heraldo

          Followup:

you are right -- in 8i, the append didn't do anything:

ops$tkyte@ORA817DEV> create table t ( x int, y clob );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> alter table t nologging;

Table altered.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert /*+ APPEND */ into t
  2  select object_id,
  3        '<document><a_tag>' || object_id || '</a_tag>'   ||
  4        '<b_tag>' || object_name || '</b_tag>' ||
  5        '<c_tag>' || object_type || '</c_tag>' ||
  6        '<d_tag>' || owner || '<d_tag><document>' data
  7  from all_objects
  8  where rownum = 1;

1 row created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select * from t where rownum = 1;

         X Y
----------
--------------------------------------------------------------------------------
     30791
<document><a_tag>30791</a_tag><b_tag>/1001a851_ConstantDefImpl</b_tag><c_tag>JAV
           A CLASS</c_tag><d_tag>SYS<d_tag><document>

In 9iR2 -- it will:

ops$tkyte@ORA920> insert /*+ APPEND */ into t
  2  select object_id,
  3        '<document><a_tag>' || object_id || '</a_tag>'   ||
  4        '<b_tag>' || object_name || '</b_tag>' ||
  5        '<c_tag>' || object_type || '</c_tag>' ||
  6        '<d_tag>' || owner || '<d_tag><document>' data
  7  from all_objects
  8  where rownum = 1;

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t where rownum = 1;
select * from t where rownum = 1
              *
ERROR at line 1:
ORA-12838: cannot read/modify an object after modifying it in parallel

that last bit shows we did the direct path stuff

but you could use a nocache/nologging CLOB to force a direct write of the clob
data if it exceeds 4k or is always stored out of line.

      Rating: 5
what to do when
      there is lots of 'direct path write (lob)' wait?  October
      01, 2003
Reviewer:  Winston
Hi Tom,

I adopted your strategy on 8.1.7.4.0.. alter table <> nologging; and then do
insert /*+ append */ select  from a table.

But I still see a lot of 'direct path write (lob)' events.

   1     SELECT    event, state,  wait_time, p1, p2, p3
  2              FROM     v$session_wait
  3              WHERE   event= 'direct path write (lob)'
  4*             and sid=45

EVENT           STATE       WAIT_TIME         P1         P2         P3
--------------- ---------- ---------- ---------- ---------- ----------
direct path wri WAITING             0         16     771984          1
te (lob)

  1                     SELECT segment_name, segment_type
  2          FROM   dba_extents
  3          WHERE  file_id =16
  4*         AND     771984 BETWEEN block_id AND block_id + blocks -1

SEGMENT_NAME                   SEGMENT_TYPE
------------------------------ --------------------
SYS_LOB0000030826C00006$$      LOB PARTITION

Thanks a lot!

          Followup:

and you expected not to?  not sure what your question would be here.

You appear to have a LOB (guess on my part, incomplete data)

a direct path insert into a nologging table would avoid redo/undo generation --
but why would you expect it to stop writing the LOB for you?

      Rating: 5
more
      information provided  October 02, 2003
Reviewer:
      Winston
I need to convert non-partitioned table1 to partitioned table2 for easy
management.
Table1 and table2 have similar structure and they both contain CLOB field.
Table1 contains  1.2M rows. I created table2 and set it to nologging, then
insert /*+ append */ into table2 select * from table1.
It took very long time to move data over (more than 15 hours now but still
doing).

I used the above SQL statements to see what that session was waiting on and I
found out it was 'direct path write (lob)'. I wonder if there is anything we can
do to reduce the waits on 'direct path write (lob)'

          Followup:

how *big* is the table itself.  1.2 m rows could be anywhere between about 2meg
and infinity.  How much data are we talking about.

how good are your disks?  the direct path write on the lob is simply the writing
of the lob data to disk.  you must be using non-cached lobs -- they are not
hitting the buffer cache.  You may find using a cached lob to be "faster" as
DBWR will be doing the writes in the background.

      Rating: 5
Result of
      show_space after analyzing the table   October 02, 2003

Reviewer:  Winston
winstonz@WZHG> exec show_space('TABLE1');
Free Blocks.............................11616
Total Blocks............................397440
Total Bytes.............................3255828480
Unused Blocks...........................6207
Unused Bytes............................50847744
Last Used Ext FileId....................19
Last Used Ext BlockId...................743561
Last Used Block.........................1985

PL/SQL procedure successfully completed.

winstonz@WZHG> SELECT (397440-11616-6207)*8/1024 FROM DUAL;

(397440-11616-6207)*8/1024
--------------------------
                2965.75781

almost 3GB..

Please tell me how to measure 'how good the disks are' so that we can judge
whether it should take so long time to move the data over?

          Followup:

thats 3gig of table data.

Now, hows about the LOB data which is probably stored OUT OF LINE in a lob
segment :)

and the lob index, which is stored separately as well.

you bought the disks -- are they "good fast, and lots of them" or "big slow
single disk that was cheap".

if you monitor IO at the OS level, are you hitting or getting near the
throughput for your disks

      Rating: 5
This is getting
      more interesting  October 02, 2003
Reviewer:
      Winston
I am sure that most rows' LOB data were stored in line because most of them are
smaller than 4K.
and I don't have any index on the partitioned target table (the table we're
writing to), but I noticed Oracle create one for me. I don't know how to size
this index since Oracle is still writing to using /*+ APPEND */
winstonz@WZHG>  select index_name, partitioned,
  2   num_rows, sample_size from user_indexes   where table_name='TABLE2';

INDEX_NAME                     PAR   NUM_ROWS SAMPLE_SIZE
------------------------------ --- ---------- -----------
SYS_IL0000030826C00006$$       YES

I ran the OS commands in  doc id 1005636.6
and surprisingly I found out that my diskS are not busy at all...

 sar -b 5 5

SunOS sedm1065p 5.8 Generic_108528-13 sun4u    10/02/03

23:16:56 bread/s lread/s %rcache bwrit/s lwrit/s %wcache pread/s pwrit/s
23:17:02       0      45     100       2       3      40       1       0
23:17:07       0      27     100       0       0       0       0       0
23:17:12       0     114     100       1       1       0       0       0
23:17:17       0      13     100       0       0     100       0       0
23:17:22       0      25     100       0       1      67       0       0

Average        0      45     100       1       1      38       0       0

iostat -d 5 5
    sd6           sd15          sd52          sd53
kps tps serv  kps tps serv  kps tps serv  kps tps serv
  0   0    0    0   0    0    0   0    0    0   0    0
  0   0    0    0   0    0    0   0    0    0   0    0
  0   0    0    0   0    0    0   0    0    0   0    0
  0   0    0    0   0    0    0   0    0    0   0    0
  0   0    0    0   0    0    0   0    0    0   0    0

sar -d 5 5  showed me that all the devices' busy% is lower than 5% (most of them
are 0 )

My question is what can we check further to see why writing into a table
containing CLOB is so slow?

Finally I got your new book, it's very well-written!  but it didnt' cover the
question above:)

Thank you Tom for your time, I really appreciate it!

          Followup:

do a 10046 level 12 trace -- do the insert append with just a couple thousand
rows (where rownum < N)

also, do an exp of the "new" table with rows=n followed by an imp of that with
show=y and post the DDL for the table you are adding to.

      Rating: 5
10046 trace
        October 03, 2003
Reviewer:  Winston

insert /*+ APPEND */ into table2
select MESSAGE_QUEUE_ID   ,
MESSAGE_TIMESTAMP       ,
RECORD_INSERT_TIME      ,
nvl(SUCCESS_FLAG,:"SYS_B_0") as success_flag             ,
RETRY                    ,
XML_MESSAGE    from message_queue
where rownum<:"SYS_B_1"

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1     38.48      78.77       1154       6405       3496        2999
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2     38.48      78.77       1154       6405       3496        2999

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 55

Details in trace file
=====================
PARSING IN CURSOR #4 len=119 dep=1 uid=0 oct=6 lid=0 tim=3440971332
hv=1425443843 ad='a3fe25d8'
update seq$ set
increment$=:2,minvalue=:3,maxvalue=:4,cycle#=:5,order$=:6,cache=:7,highwater=:8,a
udit$=:9 wher
e obj#=:1
END OF STMT
PARSE #4:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=3440971332
BINDS #4:
 bind 0: dty=2 mxl=22(02) mal=00 scl=00 pre=00 oacflg=18 oacfl2=1 size=24
offset=0
   bfp=101ecddd8 bln=24 avl=02 flg=05
   value=50
 bind 1: dty=2 mxl=22(02) mal=00 scl=00 pre=00 oacflg=18 oacfl2=1 size=24
offset=0
   bfp=101ecdda8 bln=24 avl=02 flg=05
   value=1
 bind 2: dty=2 mxl=22(15) mal=00 scl=00 pre=00 oacflg=18 oacfl2=1 size=24
offset=0
   bfp=101ecdd78 bln=24 avl=15 flg=05
   value=999999999999999999999999999
 bind 3: dty=2 mxl=22(22) mal=00 scl=00 pre=00 oacflg=08 oacfl2=1 size=24
offset=0
   bfp=101ecdd48 bln=24 avl=01 flg=05
   value=0
 bind 4: dty=2 mxl=22(22) mal=00 scl=00 pre=00 oacflg=08 oacfl2=1 size=24
offset=0
   bfp=101ecdd08 bln=24 avl=01 flg=05
   value=0
bind 5: dty=2 mxl=22(02) mal=00 scl=00 pre=00 oacflg=18 oacfl2=1 size=24
offset=0
   bfp=101ecdb48 bln=24 avl=02 flg=05
   value=20
 bind 6: dty=2 mxl=22(05) mal=00 scl=00 pre=00 oacflg=18 oacfl2=1 size=24
offset=0
   bfp=101ecdb18 bln=24 avl=05 flg=05
   value=7206151
 bind 7: dty=1 mxl=32(32) mal=00 scl=00 pre=00 oacflg=18 oacfl2=1 size=32
offset=0
   bfp=101ecdae0 bln=32 avl=32 flg=05
   value="--------------------------------"
 bind 8: dty=2 mxl=22(22) mal=00 scl=00 pre=00 oacflg=08 oacfl2=1 size=24
offset=0
   bfp=101ecde08 bln=22 avl=03 flg=05
   value=373
EXEC #4:c=0,e=0,p=0,cr=1,cu=3,mis=0,r=1,dep=1,og=4,tim=3440971332
STAT #4 id=1 cnt=1 pid=0 pos=0 obj=0 op='UPDATE SEQ$ '
STAT #4 id=2 cnt=1 pid=1 pos=1 obj=99 op='INDEX UNIQUE SCAN '
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40475 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40476 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40477 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40478 p3=1
WAIT #3: nam='db file scattered read' ela= 0 p1=19 p2=40479 p3=16
WAIT #3: nam='direct path read (lob) ' ela= 0 p1=6 p2=112616 p3=1
WAIT #3: nam='log buffer space' ela= 1 p1=0 p2=0 p3=0
WAIT #3: nam='direct path write (lob)' ela= 200 p1=16 p2=850779 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40495 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40496 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40497 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40498 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40499 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40500 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40501 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40502 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40503 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40504 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40505 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40506 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40507 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40508 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40511 p3=1
WAIT #3: nam='db file scattered read' ela= 0 p1=19 p2=40509 p3=2
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40512 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40513 p3=1
WAIT #3: nam='db file scattered read' ela= 0 p1=19 p2=40514 p3=2
WAIT #3: nam='direct path read (lob) ' ela= 1 p1=19 p2=265234 p3=1
WAIT #3: nam='direct path read (lob) ' ela= 30 p1=6 p2=69911 p3=1
WAIT #3: nam='direct path read (lob) ' ela= 0 p1=19 p2=216040 p3=1
WAIT #3: nam='direct path read (lob) ' ela= 0 p1=20 p2=270015 p3=1
WAIT #3: nam='direct path read (lob) ' ela= 0 p1=19 p2=265217 p3=1
WAIT #3: nam='direct path read (lob) ' ela= 0 p1=19 p2=265233 p3=1
WAIT #3: nam='direct path write (lob)' ela= 223 p1=6 p2=750596 p3=5
WAIT #3: nam='direct path write (lob)' ela= 0 p1=16 p2=850785 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40518 p3=1
WAIT #3: nam='direct path read (lob) ' ela= 0 p1=19 p2=265232 p3=1
WAIT #3: nam='direct path write (lob)' ela= 124 p1=16 p2=850780 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40521 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40522 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40523 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40525 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40524 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40526 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40527 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40528 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40529 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40530 p3=1
WAIT #3: nam='db file scattered read' ela= 0 p1=19 p2=40531 p3=16
WAIT #3: nam='latch free' ela= 2 p1=15032453248 p2=95 p3=0
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40547 p3=1
WAIT #3: nam='db file scattered read' ela= 0 p1=19 p2=40548 p3=16
WAIT #3: nam='log buffer space' ela= 2 p1=0 p2=0 p3=0
WAIT #3: nam='db file sequential read' ela= 0 p1=19 p2=40564 p3=1
WAIT #3: nam='db file scattered read' ela= 0 p1=19 p2=40565 p3=16
WAIT #3: nam='log buffer space' ela= 0 p1=0 p2=0 p3=0
WAIT #3: nam='log buffer space' ela= 0 p1=0 p2=0 p3=0
WAIT #3: nam='log buffer space' ela= 0 p1=0 p2=0 p3=0
WAIT #3: nam='db file scattered read' ela= 0 p1=19 p2=40581 p3=4
WAIT #3: nam='log buffer space' ela= 1 p1=0 p2=0 p3=0
WAIT #3: nam='db file sequential read' ela= 123 p1=20 p2=45065 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45066 p3=1
WAIT #3: nam='db file scattered read' ela= 0 p1=20 p2=45067 p3=2
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45070 p3=1
WAIT #3: nam='direct path read (lob) ' ela= 0 p1=6 p2=112606 p3=1
WAIT #3: nam='direct path write (lob)' ela= 133 p1=16 p2=850781 p3=1
WAIT #3: nam='db file sequential read' ela= 16 p1=20 p2=45072 p3=1
WAIT #3: nam='log buffer space' ela= 1 p1=0 p2=0 p3=0
WAIT #3: nam='db file sequential read' ela= 1 p1=20 p2=45073 p3=1
WAIT #3: nam='log buffer space' ela= 1 p1=0 p2=0 p3=0
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45074 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45075 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45081 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45077 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45079 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45078 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45080 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45082 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45083 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45085 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45084 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45086 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45087 p3=1
WAIT #3: nam='log buffer space' ela= 0 p1=0 p2=0 p3=0
WAIT #3: nam='direct path read (lob) ' ela= 0 p1=19 p2=362422 p3=1
WAIT #3: nam='direct path write (lob)' ela= 131 p1=16 p2=850782 p3=1
WAIT #3: nam='direct path read (lob) ' ela= 0 p1=19 p2=362474 p3=1
WAIT #3: nam='direct path write (lob)' ela= 141 p1=16 p2=850783 p3=1
WAIT #3: nam='db file sequential read' ela= 15 p1=20 p2=45091 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45090 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45093 p3=1
WAIT #3: nam='db file sequential read' ela= 1 p1=20 p2=45092 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45094 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45096 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45095 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45098 p3=1
WAIT #3: nam='log buffer space' ela= 0 p1=0 p2=0 p3=0
WAIT #3: nam='log buffer space' ela= 3 p1=0 p2=0 p3=0
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45099 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45097 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45100 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45101 p3=1
WAIT #3: nam='log buffer space' ela= 0 p1=0 p2=0 p3=0
WAIT #3: nam='db file sequential read' ela= 134 p1=20 p2=45105 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45104 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45107 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45106 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45108 p3=1
WAIT #3: nam='db file scattered read' ela= 0 p1=20 p2=45109 p3=16
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45125 p3=1
WAIT #3: nam='direct path read (lob) ' ela= 103 p1=6 p2=105494 p3=1
WAIT #3: nam='direct path write (lob)' ela= 116 p1=16 p2=850784 p3=1
WAIT #3: nam='direct path read (lob) ' ela= 0 p1=19 p2=265224 p3=1
WAIT #3: nam='direct path write (lob)' ela= 125 p1=16 p2=850801 p3=1
WAIT #3: nam='db file sequential read' ela= 130 p1=20 p2=45129 p3=1
WAIT #3: nam='db file sequential read' ela= 2 p1=20 p2=45130 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850887 p3=1
WAIT #3: nam='db file scattered read' ela= 133 p1=20 p2=45131 p3=16
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850888 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850889 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850890 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850891 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850892 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850893 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850894 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850895 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850896 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850897 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850898 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850899 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850900 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850901 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850902 p3=1
WAIT #3: nam='db file sequential read' ela= 138 p1=20 p2=45148 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850903 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45147 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45149 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850904 p3=1
WAIT #3: nam='latch free' ela= 0 p1=15546021344 p2=106 p3=0
WAIT #3: nam='latch free' ela= 0 p1=15546021344 p2=106 p3=1
WAIT #3: nam='latch free' ela= 0 p1=15546021344 p2=106 p3=2
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45150 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850905 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850906 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850907 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45151 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850908 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45154 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850909 p3=1
WAIT #3: nam='db file scattered read' ela= 0 p1=20 p2=45152 p3=2
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850910 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45156 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850911 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=20 p2=45155 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850912 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850913 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850914 p3=1
WAIT #3: nam='db file scattered read' ela= 0 p1=20 p2=45157 p3=11
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850915 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850916 p3=1
WAIT #3: nam='direct path read (lob) ' ela= 105 p1=6 p2=105485 p3=1
WAIT #3: nam='direct path write (lob)' ela= 121 p1=16 p2=850802 p3=1
WAIT #3: nam='db file sequential read' ela= 0 p1=16 p2=850917 p3=1
=====================
Summary:

Event                                           Times     Count   Max.  Total
waited on                                      Waited Zero Time   Wait Waited
--------------------------------------------- ------- --------- ------ ------
direct path write (lob)......................    31         1   2.23   40.50
db file sequential read (single block scan)..   533       488   1.42   21.80
direct path read (lob) ......................    35        26   1.36   6.17
log buffer space.............................    38        16   0.94   2.24
db file scattered read (multiblock full scan)    63        53   1.33   1.65
latch free (095 row cache objects)...........     5         0   0.02   0.07
file open....................................     4         4   0.00   0.00
latch free (106 library cache)...............     6         6   0.00   0.00
SQL*Net message from client (idle)...........     1         0   0.03   0.03
SQL*Net message to client (idle).............     1         1   0.00   0.00
--------------------------------------------- ----- --------- ------ ------
total........................................   717       595   2.23  72.46
non-idle waits...............................   715       594   2.23  72.43
idle waits...................................     2         1   0.03   0.03

          Followup:

ok, since we do direct read/writes on out of line lobs -- I have a feeling your
lobs are "bigger" then you think.

what is the output of:

select
avg( dbms_lob.getlength( XML_MESSAGE ) ),
min( dbms_lob.getlength( XML_MESSAGE ) ),
max( dbms_lob.getlength( XML_MESSAGE ) ),
count( case when dbms_log.getlength(xml_message) > 4000 then 1 else null end ),
count( xml_message )
   from message_queue
where rownum< 3000;

you might consider "cache" on the lobs -- you can "uncache" them later.

did a small test with a 5000 byte lob, here is NOCACHE:

insert /*+ append */ into t2
select * from t

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1     38.61     290.85      32768     100184     170857       32768
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2     38.61     290.85      32768     100184     170857       32768

with biggest waits on direct path read (lob) direct path write (lob).  with
cache:

insert /*+ append */ into t2
select * from t

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1     25.53     177.82      33044     100231     203544       32768
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2     25.53     177.82      33044     100231     203544       32768

with no direct path write (lob) waits.

      Rating:
      5
DDL  October 03, 2003
Reviewer:
      Winston
"CREATE TABLE "TABLE2" ("MESSAGE_QUEUE_ID" NUMBER(38, 0) NOT NULL ENAB"
 "LE, "MESSAGE_TIMESTAMP" DATE, "RECORD_INSERT_TIME" DATE, "SUCCESS_FLAG" NUM"
 "BER(3, 0), "RETRY" NUMBER(3, 0), "XML_MESSAGE" CLOB)  PCTFREE 10 PCTUSED 40"
 " INITRANS 1 MAXTRANS 255 NOLOGGING TABLESPACE "SWSPDA_01" LOB (XML_MESSAGE)"
 " STORE AS  (ENABLE STORAGE IN ROW CHUNK 8192 PCTVERSION 10 NOCACHE  STORAGE"
 "( BUFFER_POOL DEFAULT)) PARTITION BY RANGE ("SUCCESS_FLAG" )  (PARTITION "P"
 "0" VALUES LESS THAN (1)  PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 STOR"
 "AGE(INITIAL 65536) TABLESPACE "SWSPDA_01" NOLOGGING LOB (XML_MESSAGE) STORE"
 " AS  (TABLESPACE "SWSPDA_01" ENABLE STORAGE IN ROW CHUNK 8192 PCTVERSION 10"
 " NOCACHE  STORAGE(INITIAL 65536)), PARTITION "P1" VALUES LESS THAN (10)  PC"
 "TFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 STORAGE(INITIAL 65536) TABLESPA"
 "CE "SWSPDA_01" NOLOGGING LOB (XML_MESSAGE) STORE AS  (TABLESPACE "SWSPDA_01"
 "" ENABLE STORAGE IN ROW CHUNK 8192 PCTVERSION 10 NOCACHE  STORAGE(INITIAL 6"
 "5536)), PARTITION "P2" VALUES LESS THAN (40)  PCTFREE 10 PCTUSED 40 INITRAN"
 "S 1 MAXTRANS 255 STORAGE(INITIAL 65536) TABLESPACE "SWSPDA_01" NOLOGGING LO"
 "B (XML_MESSAGE) STORE AS  (TABLESPACE "SWSPDA_01" ENABLE STORAGE IN ROW CHU"
 "NK 8192 PCTVERSION 10 NOCACHE  STORAGE(INITIAL 65536)), PARTITION "P3" VALU"
 "ES LESS THAN (70)  PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 STORAGE(IN"
 "ITIAL 65536) TABLESPACE "SWSPDA_01" NOLOGGING LOB (XML_MESSAGE) STORE AS  ("
 "TABLESPACE "SWSPDA_01" ENABLE STORAGE IN ROW CHUNK 8192 PCTVERSION 10 NOCAC"
 "HE  STORAGE(INITIAL 65536)), PARTITION "P4" VALUES LESS THAN (80)  PCTFREE "
 "10 PCTUSED 40 INITRANS 1 MAXTRANS 255 STORAGE(INITIAL 65536) TABLESPACE "SW"
 "SPDA_01" NOLOGGING LOB (XML_MESSAGE) STORE AS  (TABLESPACE "SWSPDA_01" ENAB"
 "LE STORAGE IN ROW CHUNK 8192 PCTVERSION 10 NOCACHE  STORAGE(INITIAL 65536))"
 ", PARTITION "P5" VALUES LESS THAN (90)  PCTFREE 10 PCTUSED 40 INITRANS 1 MA"
 "XTRANS 255 STORAGE(INITIAL 65536) TABLESPACE "SWSPDA_01" NOLOGGING LOB (XML"
 "_MESSAGE) STORE AS  (TABLESPACE "SWSPDA_01" ENABLE STORAGE IN ROW CHUNK 819"
 "2 PCTVERSION 10 NOCACHE  STORAGE(INITIAL 65536)), PARTITION "P6" VALUES LES"
 "S THAN (100)  PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 STORAGE(INITIAL"
 " 65536) TABLESPACE "SWSPDA_01" NOLOGGING LOB (XML_MESSAGE) STORE AS  (TABLE"
 "SPACE "SWSPDA_01" ENABLE STORAGE IN ROW CHUNK 8192 PCTVERSION 10 NOCACHE  S"
 "TORAGE(INITIAL 65536)), PARTITION "P7" VALUES LESS THAN (110)  PCTFREE 10 P"
 "CTUSED 40 INITRANS 1 MAXTRANS 255 STORAGE(INITIAL 65536) TABLESPACE "SWSPDA"
 "_01" NOLOGGING LOB (XML_MESSAGE) STORE AS  (TABLESPACE "SWSPDA_01" ENABLE S"
 "TORAGE IN ROW CHUNK 8192 PCTVERSION 10 NOCACHE  STORAGE(INITIAL 65536)), PA"
 "RTITION "P8" VALUES LESS THAN (120)  PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTR"
 "ANS 255 STORAGE(INITIAL 65536) TABLESPACE "SWSPDA_01" NOLOGGING LOB (XML_ME"
 "SSAGE) STORE AS  (TABLESPACE "SWSPDA_01" ENABLE STORAGE IN ROW CHUNK 8192 P"
 "CTVERSION 10 NOCACHE  STORAGE(INITIAL 65536)), PARTITION "P9" VALUES LESS T"
 "HAN (130)  PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 STORAGE(INITIAL 65"
 "536) TABLESPACE "SWSPDA_01" NOLOGGING LOB (XML_MESSAGE) STORE AS  (TABLESPA"
 "CE "SWSPDA_01" ENABLE STORAGE IN ROW CHUNK 8192 PCTVERSION 10 NOCACHE  STOR"
 "AGE(INITIAL 65536)), PARTITION "P10" VALUES LESS THAN (140)  PCTFREE 10 PCT"
 "USED 40 INITRANS 1 MAXTRANS 255 STORAGE(INITIAL 65536) TABLESPACE "SWSPDA_0"
 "1" NOLOGGING LOB (XML_MESSAGE) STORE AS  (TABLESPACE "SWSPDA_01" ENABLE STO"
 "RAGE IN ROW CHUNK 8192 PCTVERSION 10 NOCACHE  STORAGE(INITIAL 65536)), PART"
 "ITION "P11" VALUES LESS THAN (150)  PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRA"
 "NS 255 STORAGE(INITIAL 65536) TABLESPACE "SWSPDA_01" NOLOGGING LOB (XML_MES"
 "SAGE) STORE AS  (TABLESPACE "SWSPDA_01" ENABLE STORAGE IN ROW CHUNK 8192 PC"
 "TVERSION 10 NOCACHE  STORAGE(INITIAL 65536)), PARTITION "P12" VALUES LESS T"
 "HAN (160)  PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 STORAGE(INITIAL 65"
 "536) TABLESPACE "SWSPDA_01" NOLOGGING LOB (XML_MESSAGE) STORE AS  (TABLESPA"
 "CE "SWSPDA_01" ENABLE STORAGE IN ROW CHUNK 8192 PCTVERSION 10 NOCACHE  STOR"
 "AGE(INITIAL 65536)), PARTITION "P13" VALUES LESS THAN (MAXVALUE)  PCTFREE 1"
 "0 PCTUSED 40 INITRANS 1 MAXTRANS 255 STORAGE(INITIAL 65536) TABLESPACE "SWS"
 "PDA_01" NOLOGGING LOB (XML_MESSAGE) STORE AS  (TABLESPACE "SWSPDA_01" ENABL"
 "E STORAGE IN ROW CHUNK 8192 PCTVERSION 10 NOCACHE  STORAGE(INITIAL 65536)) "
 ")"

      Rating: 5
output of the
      query   October 03, 2003
Reviewer:  Winston

winstonz@WZHG> select
  2  avg( dbms_lob.getlength( XML_MESSAGE ) ) as avglength,
  3  min( dbms_lob.getlength( XML_MESSAGE ) ) as minlength ,
  4  max( dbms_lob.getlength( XML_MESSAGE ) ) as maxlength ,
  5  count( case when dbms_lob.getlength(xml_message) > 4000 then 1 else null
end ) as numlarger ,
  6  count( xml_message ) as totalmsg
  7     from message_queue
  8  where rownum< 3000;
count( xml_message ) as totalmsg
                      *
ERROR at line 6:
ORA-00932: inconsistent datatypes

winstonz@WZHG> select
  2  avg( dbms_lob.getlength( XML_MESSAGE ) ) as avglength,
  3  min( dbms_lob.getlength( XML_MESSAGE ) ) as minlength ,
  4  max( dbms_lob.getlength( XML_MESSAGE ) ) as maxlength ,
  5  count( case when dbms_lob.getlength(xml_message) > 4000 then 1 else null
end ) as numlarger ,
  6  count( * ) as totalmsg
  7     from message_queue
  8  where rownum< 3000;

AVGLENGTH MINLENGTH MAXLENGTH NUMLARGER TOTALMSG
--------- --------- --------- --------- --------
 1,878.13  1,343.00 #########        30    2,999

winstonz@WZHG> column maxlength format 9,999,999,999
winstonz@WZHG> /

AVGLENGTH MINLENGTH      MAXLENGTH NUMLARGER TOTALMSG
--------- --------- -------------- --------- --------
 1,878.13  1,343.00         42,602        30    2,999

winstonz@WZHG> ed
Wrote file afiedt.buf

  1  select
  2  avg( dbms_lob.getlength( XML_MESSAGE ) ) as avglength,
  3  min( dbms_lob.getlength( XML_MESSAGE ) ) as minlength ,
  4  max( dbms_lob.getlength( XML_MESSAGE ) ) as maxlength ,
  5  count( case when dbms_lob.getlength(xml_message) > 4000 then 1 else null
end ) as numlarger ,
  6  count( * ) as totalmsg
  7     from message_queue
  8* where rownum< 6000
winstonz@WZHG> /

AVGLENGTH MINLENGTH      MAXLENGTH NUMLARGER TOTALMSG
--------- --------- -------------- --------- --------
 1,864.09  1,163.00         42,602        65    5,999

winstonz@WZHG> ed
Wrote file afiedt.buf

  1  select
  2  avg( dbms_lob.getlength( XML_MESSAGE ) ) as avglength,
  3  min( dbms_lob.getlength( XML_MESSAGE ) ) as minlength ,
  4  max( dbms_lob.getlength( XML_MESSAGE ) ) as maxlength ,
  5  count( case when dbms_lob.getlength(xml_message) > 4000 then 1 else null
end ) as numlarger ,
  6  count( * ) as totalmsg
  7     from message_queue
  8* where rownum< 12000
winstonz@WZHG> /

AVGLENGTH MINLENGTH      MAXLENGTH NUMLARGER TOTALMSG
--------- --------- -------------- --------- --------
 1,913.62  1,158.00         42,602       241   ######

winstonz@WZHG>
so only about 1~2% rows contain bigger LOB.

      Rating: 5
direct path
      write (lob) is totally gone  October 03, 2003

Reviewer:  Winston
Thank you very much, Tom!

Now the tkprof changed to

insert /*+ APPEND */ into table3
select MESSAGE_QUEUE_ID   ,
MESSAGE_TIMESTAMP       ,
RECORD_INSERT_TIME      ,
nvl(SUCCESS_FLAG,:"SYS_B_0") as success_flag             ,
RETRY                    ,
XML_MESSAGE    from message_queue
where rownum<:"SYS_B_1"

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.02          0          0          0           0
Execute      1      1.25      30.79        914       6389       5876        2999
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      1.28      30.81        914       6389       5876        2999

and the top waits are on
db file sequential read (single block scan) --19.68
and direct path read (lob) --9.57

One thing I don't understand is why in your small test case you have biggest
waits on 'direct path read (lob)' but mine is on 'db file sequential read'?

          Followup:

I made 100% of my lobs incurr a direct path read.

you did not.

so, I had lots of them and you had fewer.

      Rating: 5
You book did
      mention about 'direct path write'  October 04, 2003

Reviewer:  Winston
I read your book Chapter 2 again and I found out that you did mention uncached
LOBs can cause the direct path write on page 151.

I wonder how you made 100% of your lobs incurr a direct path read? And in your
test case there is huge difference between cpu and elapsed, how can you bring it
down?

Thanks a lot!

          Followup:

... did a small test with a 5000 byte lob, here is NOCACHE: ....

5000 byte lobs will be stored OUT OF LINE, anything out of line in a nocache lob
will be "direct path read"

to descrease the cpu/elapsed -- I would buy faster disks -- this was a simple,
small desktop pc, not a "server" -- single ATA drive, nothing fancy.

October 04, 2003 Keith -- Thanks for the question regarding
      "Datatype coversion", version Oracle8i 8.1.5 & 6 & 7

      You Asked
Tom,

In working with my developers(Java), I have come across the
topic of datatype conversion. They have given me a script which
creates tables using INTEGER and FLOAT as datatypes for some of
the columns. I questioned why these dayatypes were being used
instead of NUMBER. I was made aware of the fact that Oracle has
its own proprietory datatypes. If the application we are
building wants to insert into or select from a column values
which are externally defined as FLOAT or INTEGER, should I
create this column with the external or internal datatype? And
if I use an internal datatype, where does the conversion occur?
I have read through the Oracle documentation but still don't
quite understand. They are also questioning how they/I can use
external datatypes BIGINT, DOUBLE, and TIMESTAMP. As always your
words of wisdom are greatly appreciated.

Keith M Cutler

      and we said...
All numbers in Oracle are stored as numbers.  Float, integer,
etc -- they are all synonyms for numbers.

tkyte@TKYTE816> create table t
  2  ( a int, b float(5), c decimal(5,2),
          d number(5), e number, f float );

Table created.

tkyte@TKYTE816> desc t
 Name                    Null?    Type
 ----------------------- -------- ----------------
 A                                NUMBER(38)
 B                                NUMBER(5)
 C                                NUMBER(5,2)
 D                                NUMBER(5)
 E                                NUMBER
 F                                FLOAT(126)

We sometimes fake a float for you (but not ALWAYS -- see above,
b float(5)) in the data dictionary views -- if you look at the
text of USER_TAB_COLUMNS, you would find:

decode(c.scale,
          null, decode(c.precision#, null, 'NUMBER', 'FLOAT'),
         'NUMBER'),

so, floats are fake, integers are fake -- they are ALL numbers.

You, as a creator of tables, never need to worry about external
datatypes -- they are only of concern to the developers.

They, as developers using JDBC, need not concern themselves
either -- they use JDBC and it is all "hidden" underneath.  They
just use the type they want.  Just make sure to use a type that
has enough precision to retain the number (eg: Oracle supports a
number with 38 digits of precision, Java won't hold a number
with that much precision in an Int or Double type -- neither
will C or Ada or any 3gl pretty much).

Review &
      Followup

      Rating:
      3
FLOAT(126)  December 17, 2001
Reviewer:
      A reader  from UK
Tom,

Why doesn't oracle store and display the datatype as number rather than
float(126) ? What does the 126 mean ?

Thanks in advance, chris

          Followup:

They are stored as NUMBER, as I said:

We sometimes fake a float for you (but not ALWAYS -- see above, b float(5)) in
the data dictionary views -- if you look at the text of USER_TAB_COLUMNS, you
would find:

decode(c.scale,
          null, decode(c.precision#, null, 'NUMBER', 'FLOAT'),
         'NUMBER'),

so, floats are fake, integers are fake -- they are ALL numbers.

      Rating: 3
Clunky
      Date&Time to string?  December 17, 2001

Reviewer:  A reader  from ca, usa
Tom
Java developers I worked with had trouble handling DATE datatypes as in and out
parms to and from my procs/functions. My workaround was to convert my Date&Time
to strings for them (as you do in
http://asktom.oracle.com/pls/ask/f?p=4950:8:338756::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:494221681963,
). It seems a bit clunky. I read that
SQLJ hasn't taken off widely although it simplifies JDBC/Oracle interface. Can
Date&Time be left in it's native datatype by using SQLJ?

          Followup:

they will work seamlessly with java.sql.Timestamp types as well.

      Rating:
      5
FLOAT  August 25, 2003
Reviewer:
      SANDEEP  from GERMANY
Tom,
I converted a 8.1.7.4 database to 9.2.0.3 (Both on HP Unix Box)
After converting I noticed that....when I did describe on tables that had NUMBER
data type....showed as FLOAT(0) in
SQLwORKSHEET (ORACLE enterprise manager 1.6.0)
but showed as NUMBER in SQL *PLUS worksheet (ORACLE enterprise manager
2.2.0.0.0)
Is everything O.K. with the conversion..or is it just an old version of OEM
that's causing it.

Thanks

          Followup:

it is OK

      Rating: 5
Is this
      datatype conversion necessary  October 04, 2003

Reviewer:  A reader
I'm modifying some existing code.

I see that when concatanating some text to database columns of number datatype,
the columns are being converted to char.

Example:
ln_amt is a database column with number datatype.
ln_amt number(8);

'Loan amount' ||to_char(ln_amt)

I dont think it is necessary to convert ln_amt to char before concatanating to
the text string. In my opinion oracle will take care of the internal conversion.

What are the pros and cons , do doing and not doing the conversions of the above
type.

          Followup:

Explicit is good.

Implicit is not as good.

It is 100% clear what is happening above -- ln_amt is some non-string being
converted to a string to be concatenated on.

While not technically necessary - a nice touch.  It can help in some cases:

  x:= 'x' || a-b || 'y';

there, you need it (the to_char()) on a-b due to the order of operator
precendence.

      Rating: 5
operator
      precedence ?  October 04, 2003
Reviewer:
      A reader
I understand your point
(SQL> var x varchar2(20);
SQL>
SQL> exec :x := 'a' || 5-4 || 'b' ;
BEGIN :x := 'a' || 5-4 || 'b' ; END;

*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at line 1

SQL> exec :x := 'a' || to_char(5-4) || 'b' ;

PL/SQL procedure successfully completed.

SQL> print :x ;

X
--------------------------------
a1b

)

But what do you mean by  "due to the order of operator
precendence" what is operator precedence.

          Followup:

the || happens before the - without a function call (or parens, we could go
"implicit" again with

'x' || (a-b) || 'y'

but the to_char is "nice"

      Rating: 5
Please explain
      the basics  October 04, 2003
Reviewer:
      A reader
Why is (a-b) , the parentehsis making the difference?

          Followup:

 a || b-c || d

is like

(a||b) - (c||d)

because the - happens "before" the ||

using

a || (b-c) || d

makes the - happens so we have:

a || BC || d

where BC is the result of (b-c).  it changes the order of operation

like the difference between

a*b-c*d

and

a*(b-c)*d

October 05, 2003 Yogeeraj -- Thanks for the question regarding
      "RMAN backup to disk", version 8.1.7.4

      You Asked
Hello,

I have a question concerning RMAN backup to disk. I was doing my Database backup
using RMAN (Full backup everyday) to disk until recently when we got a new
application which required about 2 GB of additional disk space. With this my
RMAN backup is starting to Fail!

My current database usage is as follows:
Total:  17,239,646,208
Free:    8,048,582,656
Used:    9,191,063,552
(bytes)

The error message i am getting is:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
RMAN-00571: ==========================================================
RMAN-00569: ============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ==========================================================
RMAN-03007: retryable error occurred during execution of command: backup
RMAN-07004: unhandled exception during command execution on channel fs2b
RMAN-10035: exception raised in RPC: ORA-19502: write error on file
"/backup/rman/full/fs2/full_idecnd2s.2", blockno 67585 (blocksize=1024)
ORA-27072: skgfdisp: I/O error
HP-UX Error: 2: No such file or directory
Additional information: 67585
RMAN-10031: ORA-19624 occurred during call to
DBMS_BACKUP_RESTORE.BACKUPPIECECREATE
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
which i believe is related to insufficient disk space being available in file
system /backup.

Fortunately, i have another file system /backup01 (which is reserved for RMAN
Archived Redolog backup) with lots of free space.

hence, i have 2 file systems of different sizes available.
SLX1:>bdf /backup /backup01
Filesystem                kbytes     used   avail %used Mounted on
/dev/vgexport/lv_export 31449088 26019860 5357748   83% /backup
/dev/vg00/lv_backup01   13312000  4704940 8338134   36% /backup01

My last successful FUll RMAN backup took 14GB (fitted in /backup => ).

My backup script is as follows:
1. Delete old backup
2. Do new backup
(NB. the disk backup is then to sent to tape by another scheduled process)

=====================================================================
#!/bin/sh
######################################################################
#Script name: rman_full.sh
#Description: RMAN full backup script
#last Modified: YD-10/01/2002
######################################################################
#Generate Script & Execute it
/prod/global/admin/bckup/generate_deletefull_script.sh >
/prod/global/admin/bckup/rman_deletefull.sh
chmod +x /prod/global/admin/bckup/rman_deletefull.sh
/prod/global/admin/bckup/rman_deletefull.sh
######################################################################
messagelog="/backup/rman/logfiles/rman_full-`date +%d%m%y-%H%M`"
rman target sys/change_on_install@pfs rcvcat rman_dbslx1/rman_dbslx1@dev msglog
$messagelog <<EOF >/dev/null
run {
allocate channel fs1b type disk format='/backup/rman/full/fs1/full_%u.%p';
allocate channel fs2b type disk format='/backup/rman/full/fs2/full_%u.%p';
allocate channel fs3b type disk format='/backup/rman/full/fs3/full_%u.%p';
allocate channel fs4b type disk format='/backup/rman/full/fs4/full_%u.%p';
allocate channel fs5b type disk format='/backup/rman/full/fs5/full_%u.%p';
allocate channel fs6b type disk format='/backup/rman/full/fs6/full_%u.%p';
allocate channel fs7b type disk format='/backup/rman/full/fs7/full_%u.%p';

set limit channel fs1b kbytes=750000;
set limit channel fs2b kbytes=750000;
set limit channel fs3b kbytes=750000;
set limit channel fs4b kbytes=750000;
set limit channel fs5b kbytes=750000;
set limit channel fs6b kbytes=750000;
set limit channel fs7b kbytes=750000;
backup database include current controlfile;}
exit;
EOF
subject="RMAN-Backup Log (full) on `date`"
emailadd1=xxxx@xxx.mu
#
/usr/bin/mailx -s "$subject" $emailadd1 < $messagelog
#
=====================================================================

generate_deletefull_script.sh is as follows:
=====================================================================
#!/bin/sh
echo "rman target sys/change_on_install@pfs rcvcat rman_dbslx1/rman_dbslx1@dev m
sglog /backup/rman/logfiles/rman_full_delete-`date +%d%m%y-%H%M`.log <<EOF >/dev
/null"
echo "allocate channel for delete type disk;"
for i in `ls -1 /backup*/rman/full/fs*/* `
do
echo "change backuppiece '$i' delete; "
done
echo "release channel;"
echo "exit;"
echo EOF
=====================================================================

Question:
- How do i determine how much space will be required by my FULL rman backup?

- Based on the size of the file systems, how do i allocate my channels so that
we make the best use of the file systems?

Thank you for your patience and precious guidance.

Best Regards
Yogeeraj

      and we said...
...
My backup script is as follows:
1. Delete old backup
2. Do new backup
......

NO, please tell me that is NOT true.  You need to keep at least two and
preferably 3 or more of your last backups.  ELSE YOU WILL LOSE ALL OF YOUR DATA
SOMEDAY.

Consider:

a) you delete old backup
b) you start new backup
c) DISK FAILS NOW

Don't laugh -- I've not only seen it happen, I've seen it happen more then
once  and it will happen to you.

You need to buy a tape drive or some other storage device if you are somewhat
serious about backup and recovery.  You need to do that today.

As for the questions:

o I would use the size of my database as a starting point, and make sure I have
1.5 to 2x that much storage available for my current backup set.  Why?  Because
my database could be that big and it'll grow and I want to have space to stuff
it.

o that is up to you -- I would just stripe them all together into one big one
and deal with it that way.  My goal is to get that backup off to tape actually
-- onto some other storage.  away from my machines....

Review &
      Followup

      Rating: 5
great rman
      stuff - Tom  January 13, 2003
Reviewer:
      Jonathan

      Rating: 5
Thank you for
      the reply...  January 14, 2003
Reviewer:
      Yogeeraj  from Mauritius
hello,

YES. this is not true. In fact, i can't forget your words "I've seen it happen
more than once and it will happen to you" which you have repeated on most of
your replies to backup related questions ;)

The backup files being deleted are already on tape beforehand. In fact, we are
keeping 6 copies of our last backups on high-speed tape (HP-Ultrium).

In fact, my script is as follows:
 Repeat
  1. Delete old backup
  2. Do new backup
  3. Send backup to Daily backup tape.
 Everyday except Early Monday morning where you should append to the tape.
 if monday,wednesday: Send backup tape to Phoenix head office.

I have yet to implement the restore of the backup files on my development server
- As you have mentioned here - "onto some other storage"!

<quote>
I would use the size of my database as a starting point, and make sure I have
1.5 to 2x that much storage available for my current backup set.  Why?  Because
my database could be that big and it'll grow and I want to have space to stuff
it.
</quote>
Do you mean "1.5 to 2x" amount of storage on the SAME File System? I hope no! So
how do i specify how much of the backup should go to which file system?

Thank you for your patience.

Best Regards
Yogeeraj

          Followup:

stripe it. make it easy for you -- just have one big old disk.

      Rating: 4
rman
      errors  June 19, 2003
Reviewer:  Reader

Tom, When I use RMAN to recover a tablespace, using OEM interface, if there are
any errors, where RMAN logs those errors? In command line mode rman shows the
errors on the screen. Thanks. I appreciate your time.

      Rating: 4
restore with
      RMAN  June 28, 2003
Reviewer:  Reader

If I wanted to use a specific backup, how do i tell RMAN? RMAN, my understanding
is that it uses the most appropriate backup to restore from. Suppose, I have
taken two full backups of my database, Sun and Mon. If I let RMAN to restore
from, it would restore from MON backup. However, If I wanted to restore from SUN
backup specifically, What command should I use? Thanks.

      Rating: 5
configure
      channel  July 09, 2003
Reviewer:  Reader

Tom, is it possible to configure channels using CONFIGURE command for device
type 'disk' for multiple locations? I understand that I can do it by manuaaly
allocating channels and then i can specify format for each channel in RUN block.
similarly, can i do it with CONFIGURE command? Thanks for your time.

          Followup:

you could configure multiple channels, each with different settings, yes.

consider

RMAN> configure channel 1 device type disk format '/d01/bkup1/%U.bak';

using target database controlfile instead of recovery catalog
old RMAN configuration parameters:
CONFIGURE CHANNEL 1 DEVICE TYPE DISK FORMAT   '/d01/bkup1/%U.bak';
new RMAN configuration parameters:
CONFIGURE CHANNEL 1 DEVICE TYPE DISK FORMAT   '/d01/bkup1/%U.bak';
new RMAN configuration parameters are successfully stored

RMAN> configure channel 2 device type disk format '/d01/bkup2/%U.bak';

old RMAN configuration parameters:
CONFIGURE CHANNEL 2 DEVICE TYPE DISK FORMAT   '/d02/bkup2/%U.bak';
new RMAN configuration parameters:
CONFIGURE CHANNEL 2 DEVICE TYPE DISK FORMAT   '/d01/bkup2/%U.bak';
new RMAN configuration parameters are successfully stored

RMAN> CONFIGURE DEVICE TYPE DISK PARALLELISM 2;

old RMAN configuration parameters:
CONFIGURE DEVICE TYPE DISK PARALLELISM 2;
new RMAN configuration parameters:
CONFIGURE DEVICE TYPE DISK PARALLELISM 2;
new RMAN configuration parameters are successfully stored

RMAN> configure default device type to disk;

old RMAN configuration parameters:
CONFIGURE DEFAULT DEVICE TYPE TO DISK;
new RMAN configuration parameters:
CONFIGURE DEFAULT DEVICE TYPE TO DISK;
new RMAN configuration parameters are successfully stored

RMAN> backup database;

Starting backup at 09-JUL-03
using target database controlfile instead of recovery catalog
allocated channel: ORA_DISK_1
channel ORA_DISK_1: sid=12 devtype=DISK
allocated channel: ORA_DISK_2
channel ORA_DISK_2: sid=13 devtype=DISK
channel ORA_DISK_1: starting full datafile backupset
channel ORA_DISK_1: specifying datafile(s) in backupset
including current SPFILE in backupset
including current controlfile in backupset
input datafile fno=00011
name=/usr/oracle/ora920/OraHome1/oradata/ora920/o1_mf_undo_zhy26jk5_.dbf
input datafile fno=00001
name=/usr/oracle/ora920/OraHome1/oradata/ora920/system.dbf
input datafile fno=00005
name=/usr/oracle/ora920/OraHome1/oradata/ora920/example01.dbf
input datafile fno=00003
name=/usr/oracle/ora920/OraHome1/oradata/ora920/cwmlite01.dbf
input datafile fno=00004
name=/usr/oracle/ora920/OraHome1/oradata/ora920/drsys01.dbf
channel ORA_DISK_1: starting piece 1 at 09-JUL-03
channel ORA_DISK_2: starting full datafile backupset
channel ORA_DISK_2: specifying datafile(s) in backupset
input datafile fno=00009
name=/usr/oracle/ora920/OraHome1/oradata/ora920/users01.dbf
input datafile fno=00006
name=/usr/oracle/ora920/OraHome1/oradata/ora920/o1_mf_big_tabl_zjbvrkt3_.dbf
input datafile fno=00008
name=/usr/oracle/ora920/OraHome1/oradata/ora920/tools01.dbf
input datafile fno=00010
name=/usr/oracle/ora920/OraHome1/oradata/ora920/xdb01.dbf
input datafile fno=00007
name=/usr/oracle/ora920/OraHome1/oradata/ora920/odm01.dbf
input datafile fno=00002
name=/usr/oracle/ora920/OraHome1/oradata/ora920/tools02.db
channel ORA_DISK_2: starting piece 1 at 09-JUL-03
channel ORA_DISK_1: finished piece 1 at 09-JUL-03
piece handle=/d01/bkup1/01erqjs5_1_1.bak comment=NONE
channel ORA_DISK_1: backup set complete, elapsed time: 00:06:07
channel ORA_DISK_2: finished piece 1 at 09-JUL-03
piece handle=/d01/bkup2/02erqjs6_1_1.bak comment=NONE
channel ORA_DISK_2: backup set complete, elapsed time: 00:06:06
Finished backup at 09-JUL-03

RMAN> exit

Recovery Manager complete.
[tkyte@tkyte-pc-isdn 9iR2]$ ls -l /d01/bkup?
/d01/bkup1:
total 1287428
-rw-rw----    1 ora920   ora920   1317036032 Jul  9 20:11 01erqjs5_1_1.bak

/d01/bkup2:
total 1286260
-rw-rw----    1 ora920   ora920   1315840000 Jul  9 20:11 02erqjs6_1_1.bak
[tkyte@tkyte-pc-isdn 9iR2]$

      Rating:
      5
maxpiecesize  October 05, 2003
Reviewer:
      Shankar  from Salt Lake City, UT
I am trying to understand the usage of maxpiecesize usage.
Assume that D: drive capacity is 50M and C: drive capacity is 50m and my users
tablespace size is 100m.
If I run the following run block in RMAN, does channel c1 create a piece of size
50M of users tablespace in D: path and another piece in the specified c: path?
Is my understanding correct? does it mean that the backupset is spread in two
locations with a piece in each location? thanks.

RMAN> run
 {
 allocate channel c1 type disk maxpiecesize=50m
format='D:\rmanbackup\df_%U.bak';
 allocate channel c2 type disk maxpiecesize=50m
format='C:\oracle\ora92\database\df_%U.bak';
 backup tablespace users;
 }

          Followup:

no, it sets the maximum size of a backup piece, it does not control the max data
written to a channel.

it'll use channel c1 in this case:

RMAN> run { allocate channel c1 type disk maxpiecesize=60m format =
'/usr/oracle/ora920/OraHome1/oradata/bkup/d1/df_%U.bak';
allocate channel c2 type disk maxpiecesize=60m format =
'/usr/oracle/ora920/OraHome1/oradata/bkup/d2/df_%U.bak';
backup tablespace example;
}
2> 3> 4>

allocated channel: c1
channel c1: sid=15 devtype=DISK

allocated channel: c2
channel c2: sid=17 devtype=DISK

Starting backup at 05-OCT-03
channel c1: starting full datafile backupset
channel c1: specifying datafile(s) in backupset
input datafile fno=00005
name=/usr/oracle/ora920/OraHome1/oradata/ora920/example01.dbf
channel c1: starting piece 1 at 05-OCT-03

channel c1: finished piece 1 at 05-OCT-03
piece handle=/usr/oracle/ora920/OraHome1/oradata/bkup/d1/df_04f343j5_1_1.bak
comment=NONE
channel c1: starting piece 2 at 05-OCT-03
channel c1: finished piece 2 at 05-OCT-03
piece handle=/usr/oracle/ora920/OraHome1/oradata/bkup/d1/df_04f343j5_2_1.bak
comment=NONE
channel c1: backup set complete, elapsed time: 00:00:15
Finished backup at 05-OCT-03
released channel: c1
released channel: c2

      Rating: 5
Thanks. It
      helped.  October 05, 2003
Reviewer:  Shankar
       from Salt Lake City

      Rating: 4
Use 9x and
      backup to tape from disk  October 05, 2003

Reviewer:  Dana  from Phoenix, AZ USA
The big failure point the backup from disk to tape.
You are depending on a process rman does not know about to get your backuppieces
from one media to another. Use Oracle 9, get a media management layer for rman,
and have rman do the backup from disk to tape.
Not only will you have more security, it is transparent to recover. Rman will
know which pieces it needs to fetch from tape, and which from disk.

October 05, 2003 Anitha -- Thanks for the question regarding
      "Pl/SQL web cartridge", version 8.1.7

      You Asked
Hi,

I am using PL/SQL web toolkit to open documents in a browser.
The documents are stored as BLOB data types. I am using the below procedure
"viewer.sql" to open the documents based on the mime types.

My question: When I try to save the opened document, I see that the file name is
that of the procedure name in the URL and not the actual filename.

I have the filename stored in the database. Is there a way to change the
filename?

I saw something like CGI variables, in which the path_info parameter contains
the actual procedure name. Is that got to do with this?

Actual code....

CREATE OR REPLACE PROCEDURE VIEWER (DOC_LOC IN NUMBER,D_ID IN NUMBER) AS

V_file_ext VARCHAR2(5)
V_blob     BLOB;
buffer raw(32767);
buffer_size integer := 32767;
offset integer := 1;
length number;

BEGIN

THEN
       BEGIN
         SELECT FILE_EXT,ATTACHMENT
         INTO V_file_ext,V_blob
         FROM RBCMS_INBOX
         WHERE MAIL_ID= D_ID;
       EXCEPTION
         WHEN NO_DATA_FOUND THEN
          htp.p('Documents not found');
       END;
    ELSIF doc_loc = 2 THEN
       BEGIN
         SELECT DOC_EXT,DOCUMENT INTO V_file_ext,V_blob FROM RBCMS_DOCUMENTS
         WHERE DOC_ID = D_ID;
       EXCEPTION
         WHEN NO_DATA_FOUND THEN
          htp.p('Documents not found');
       END;
    ELSE
       NULL;
    END IF;
/*********** Determines the mime type for the document to be
displayed***********/

       IF UPPER(V_FILE_EXT)='PDF' THEN
         owa_util.mime_header('application/pdf');
       ELSIF UPPER(V_FILE_EXT)='DOC' THEN
         owa_util.mime_header('application/msword');
       ELSIF UPPER(V_FILE_EXT)='XLS' THEN
         owa_util.mime_header('application/msexcel');
       ELSIF UPPER(V_FILE_EXT)='GIF' THEN
         owa_util.mime_header('image/gif');
       ELSIF UPPER(V_FILE_EXT)='JPG' THEN
         owa_util.mime_header('image/jpeg');
       ELSIF UPPER(V_FILE_EXT)='TIF' THEN
         owa_util.mime_header('image/tiff');
       ELSE
         null;
         --owa_util.mime_header('text/plain');
       END IF;
      length := dbms_lob.getlength(v_blob);

      while offset < length loop
        dbms_lob.read(v_blob, buffer_size, offset, buffer);

        htp.prn(utl_raw.cast_to_varchar2(buffer));

        offset := offset + buffer_size;

      end loop;

      IF length = 0 THEN
       htp.p('Documents Not Available');
      END IF;
EXCEPTION
   when others then
   htp.p(sqlerrm);
END;

      and we said...
if you use the native capabilities of mod_plsq, you can put the filename in the
URL and the browser (which is what names files) will name the file after the URL

you'll have lots less code to maintain too!

http://download-west.oracle.com/docs/cd/A97329_03/web.902/a90855/feature.htm#1006058

Review &
      Followup

      Rating:
      4
  October 05, 2003
Reviewer:  Ahmad
       from Dubai, UAE
Hello Tom,

Thanks for this wonderful site, your comments and replies are very useful to us.

Tom, I am facing the same problem, which is when trying to download the
document, the file name will be overwritten by the procedure name.

Is there any other way to keep the actual file name without
using the native capabilities of mod_plsql (putting the filename in the URL and
the browser )

Best Regards,

          Followup:

you can try the "filename=whatever" in the mime header -- you'll have to "google
around" for that format.

October 05, 2003 Tony -- Thanks for the question regarding
      "On-line re-org", version 8.1.6

      You Asked
Hi Tom,
Couple of weeks back I was in one of the Oracle conference. There was a paper
presented on behalf of one of the leading consulting company. The presenation
was focussed on on-line space management features in Oracle 9i. One of the
question came up about re-org of the table which is being replicated.
Unfortunately, he was not able to answer. I know, I can not find a better place
then this to ask this question. Could you please through some light on this?
Regards

      and we said...
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96521/tables.htm#12855
contains a list of restrictions, including "tables with MV's and MV logs cannot
be online redefined"

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96567/repmview.htm#25394
indicates that a MASTER table that is not having any MV's or MV logs can in fact
be online redefed.

So, the answer is "it depends"

October 05, 2003 Vladimir -- Thanks for the question regarding
      "JDBC driver", version 8i

      You Asked
Hi Tom,

I would like to ask you :
1. Which JDBC driver is better to connect to the Database to
generate report through Java Servlet, I means what I must to use
:
a) jdbc:oracle:thin:@database:port_no:SID
b) jdbc:oracle:oci8@databse_name
and why? or is doesn't matter which kind of jdbc (oci or thin)?
, and what is the advantage again the other?

thanks for your help

      and we said...
It is a matter of opinion.

You might find the thick drivers to be a little more performant
and flexible.

You'll find the thin drivers to be easier to setup (less
software to install) and more portable.

I myself tend to use the thin drivers as it does not require me
to install the net8 client on all of the machines that need to
use my program.  This applies when I am developing code that
will be run on someones desktop.

When building something to run on a server, I tend towards the
thick drivers.  I most likely already have net8 setup there, i
have the software installed -- so that is not an issue.  You are
not prevented from using the thin drivers on the server, Its
just that if net8 and all are installed, I'll probably use the
thick drivers as they are 90% C instead of Java and tend to run
a little faster for many operations.
Review &
      Followup

      Rating: 2
Not what I'm
      seeing  September 18, 2001
Reviewer:  Joe
      Gohlke  from Orlando, FL
There are differences between the two drivers, for example the thin driver does
not support OS Authentication.  These differences may be important to your
application.  The OCI8 driver does require a client install. Achieving a stable
PC configuration for applications requiring multiple versions of Oracle can be
challenging.

Also, although Oracle promotes the OCI8 driver as being faster, in many real
world applications it is slower.  We conducted some testing using NT clients and
Sun servers.  We've run the application in client-server mode and locally on the
Sun.  We used regular one-at-a-time inserts and have used the "addBatch" method.
 In every situation we tested, the OCI8 driver took about twice as long to
perform the work.  We wrapped timings around the different portions of the code
and extracted the time for connection, binding, batching and execution.
Connection is very slow using OCI8, but is only done once.  The execution time
is 80%+ of the remaining time.

If it is true that the OCI8 driver is faster, I would like to see an example
and/or explanation.  I can provide the code from out testing if requested (it's
a short java program).

All applications are written in Java 1.2.  Oracle version is 8.1.7.

          Followup:

Ok, I waited a couple of days to follow up on this one.  I wanted to get the
definitive answer to back up my premise.  I have that now.

There are definite differences, I never said there were none.  I think I did
state that you would find the OCI8 drivers "more flexible" (they offer more
options).  There are many advanced features only available with them -- OS
authentication being one of them for sure.

I stand by my advice which is in a nutshell:

o when you deploy to a client machine (of which there are hundreds or thousands)
target the THIN drivers for ease of maintaining your application.

o when you deploy to an app server (of which there are just a few and they are
professionally managed in a data center), target the OCI8 drivers for
performance and flexibility (increased functionality)

So, lets get to the performance stuff.  I myself find time and time and time
again that OCI8 connects much faster (2x typically) then the THIN drivers.  I
cannot find a counter case to this.  It may be that you have your tnsnames.ora
or sqlnet.ora setup in such a way that all connections except thin are penalized
(if that is the case, we can fix that) -- but I cannot find a case where thin
beats thick on connections.

As for the rest, we have found the THIN to be only "as fast" but many times
"slower" than the THICK drivers.  I ask Mark Piermarini, our resident Java Guru
here in OSI, to come up with a couple of simple benchmarks to demonstrate this.
Mark wrote back:

For the first part of the performance analysis, both tests were run on the same
Sun workstation. I wanted to run the programs on the same machine where the
database resides in an attempt to mitigate network latencies. I also wanted to
run the tests over a 10-base-T network so I used a Windows 2000 machine as the
client and the Ultra 60 as the server. Here are some brief specs for the
machines.

    Sun Ultra 60
    2 450 MHz CPU's
    512 MB RAM
    Solaris 8
    Oracle 8.1.7
    JDK 1.3.1_01

    Dell Workstation
    1 500 MHz Pentium 3
    384 MB RAM
    Windows 2000 Workstation
    JDK 1.3.1_01

I asked Mark to do two tests for me.  One involved reading/writing LOBS.  This
is just one area where I know the OCI8 drivers will toast the THIN drivers.  He
wrote back on that:

The write test times how fast a blob (from memory) can be written to a new
database row 10 times. The blob I write is a memory-based byte array
materialized as an InputStream. I did not want to use a file since that would
subject me to more uncertainty. I've broken the times out and have averaged the
insertion time after the 10 iterations. I explicitly turned of garbage
collection and gave the JVM 32MB of memory in an attempt to remove any potential
variables.

The read test times how fast a blob can be read from a database row. Each test
was executed using the thin and OCI8 drivers 10 times for 4 different blob
sizes. The blob is just read -- nothing is done with the contents. I gave the
JVM 32MB of memory and disabled garbage collection. Here is how the table was
initially populated.

SQL> select id, dbms_lob.getlength(data) from lobtest order by id;

        ID DBMS_LOB.GETLENGTH(DATA)
---------- ------------------------
        10                    10240
       100                   102400
      1000                  1024000
     10000                 10240000

Here are the results of Solaris - to - Solaris and then NT to Solaris WRITE
tests:

                     THIN   OCI8       THIN  OCI8      THIN OCI8     THIN OCI8
                     10000K 10000K     1000K 1000K     100K 100K     10K  10K
                     ====== ======     ===== =====     ==== ====     ==== ====
Time to connect:       1348    869      1257   694     1032  612      969  610
Statement prepare:       56     57        55    54       54   55       54   56
Iteration 0:          40420  38178      3793  3461      652  442      276  108
Iteration 1:          34219  32587      3324  3777      844  554      221  102
Iteration 2:          34294  31800      3490  2977      436  321      197   78
Iteration 3:          33398  32080      3315  3382      690  295      302   77
Iteration 4:          36505  32897      3626  3174      439  338      185  180
Iteration 5:          38692  35153      3559  3324      450  340      185   84
Iteration 6:          33768  36229      3443  3115      486  599      173   78
Iteration 7:          33637  35574      3657  3211      692  373      174   77
Iteration 8:          38866  31775      4343  4266      444  342      178   72
Iteration 9:          37030  32676      3614  3148      457  302      173   72
Avg. time:            36082  33894      3616  3383      559  390      206   92
Total time:          362241 339889     37485 34593     6683 4582     3096 1602

                     THIN   OCI8       THIN  OCI8      THIN OCI8     THIN OCI8
                     10000K 10000K     1000K 1000K     100K 100K     10K  10K
                     ====== ======     ===== =====     ==== ====     ==== ====
Time to connect:       2483   1663     3815  1472      2653 1462     2424 1442
Statement prepare:      131    130      141   120       131  120      130  120
Iteration 0:          40348  40338     4186  3796       620  451      260  140
Iteration 1:          40248  45695     4025  4296       481  421       90  100
Iteration 2:          42120  38366     4217  3806       461  621      110  100
Iteration 3:          40208  41840     4075  3815       701  430      101  110
Iteration 4:          42932  39296     4297  4837       430  421      120   91
Iteration 5:          42220  38696     4015  4196       451  450      100  100
Iteration 6:          42401  40979     3996  3795       481  621      120   90
Iteration 7:          43473  41089     4396  3816       591  391      230   90
Iteration 8:          43062  39006     4056  3976       671  401      100   90
Iteration 9:          42441  39176     4006  3895       450  400       91   90
Avg. time:            41945  40448     4126  4022       533  460      132  100
Total time:          422107 406284    45295 41910      8121 6199     3886 2573

As you can see, the THICK drivers are always faster (code supplied below) then
the THIN drivers in the LOB test -- both the connect and the actual write
operation.  Moving onto the solaris to solaris and then nt to solaris READ tests
we find:

                     THIN   OCI8       THIN  OCI8      THIN OCI8     THIN OCI8
                     10000K 10000K     1000K 1000K     100K 100K     10K  10K
                     ====== ======     ===== =====     ==== ====     ==== ====
Time to connect:       1152    804      1153   804     1172  825     1161  824
Statement prepare:       80     89        80    87       81   89       83   86
Iteration 0:           2699   1362       487   149      190   49      127   39
Iteration 1:           2406   1174       265    85       51   10       20    3
Iteration 2:           2493   1222       240    92       46    9       39    3
Iteration 3:           2325   1133       222    85       39    9       11    3
Iteration 4:           2789   1091       267    87       35    9       11    3
Iteration 5:           2237   1092       237    85       32    9       24    3
Iteration 6:           2274   1092       206    93       33    9       10    3
Iteration 7:           2257   1164       243    94       54    9       10    3
Iteration 8:           2362   1103       209    91       31    9       11    3
Iteration 9:           2310   1153       195    85       30    9       11    3
Avg. time:             2415   1158       257    94       54   13       27    6
Total time:           25397  12491      3815  1847     1806 1061     1533  986

                     THIN   OCI8       THIN  OCI8      THIN OCI8     THIN OCI8
                     10000K 10000K     1000K 1000K     100K 100K     10K  10K
                     ====== ======     ===== =====     ==== ====     ==== ====
Time to connect:       2664   1753      2664  1732     2644 1732     2624 1703
Statement prepare:      170    170       171   170      170  170      170  160
Iteration 0:          13239  13019      1682  1482      351  161      220   70
Iteration 1:          14791  13259      1352  1322      170  170       30   10
Iteration 2:          14651  13309      1482  1352      190  150       30   20
Iteration 3:          14521  13459      1462  1332      181  120       30   10
Iteration 4:          14631  13280      1572  1312      220  140       20   10
Iteration 5:          14601  13319      1603  1312      150  121       30   20
Iteration 6:          14551  13419      1372  1292      130  120       20   10
Iteration 7:          14571  13379      1452  1332      181  150       31   20
Iteration 8:          14551  13450      1442  1372      160  130       20   10
Iteration 9:          14521  13429      1512  1422      170  130       30   20
Avg. time:            14462  13332      1493  1353      190  139       46   20
Total time:          147492 135255     17766 15452     4717 3304     3255 2083

Once again, it is clear that the THICK drivers are faster in this case as well.

I also asked Mark to do regular "selects" and "updates" as well which he did
against the scott/tiger EMP table.  The solaris to solaris and then nt to
solaris results where:

                     THIN  OCI8     THIN  OCI8     THIN   OCI8
                     BS 1  BS 1     BS 5  BS 5     BS 10  BS 10
                     ===== =====    ===== =====    =====  =====
Time to connect:     1165  798      1176  800      1167   799
Statement prepare:   44    43       43    44       45     43
Avg. time:           3886  3304     2693  3262     2765   2412
Total time:          12088 9699     9154  8707     8969   8272

                     THIN   OCI8     THIN   OCI8
                     BS 20  BS 20    BS 40  BS 40
                     =====  =====    =====  =====
Time to connect:     1171   797      1194   806
Statement prepare:   44     44       45     44
Avg. time:           4165   2645     2862   2486
Total time:          10997  8564     9030   8510

and NT-Solaris:

                     THIN  OCI8     THIN  OCI8     THIN   OCI8
                     BS 1  BS 1     BS 5  BS 5     BS 10  BS 10
                     ===== =====    ===== =====    =====  =====
Time to connect:     2574  1703     2654  1672     2564   1672
Statement prepare:   110   110      110   111      110    110
Avg. time:           4649  4721     4616  2713     2977   2659
Total time:          15322 13480    12768 9944     10035  9824

                     THIN   OCI8     THIN   OCI8
                     BS 20  BS 20    BS 40  BS 40
                     =====  =====    =====  =====
Time to connect:     2544   1673     2554   1673
Statement prepare:   110    110      100    110
Avg. time:           2890   2526     2716   2826
Total time:          10175  9414     10175  9103

Mark's summary conclusion was:

...
I have attached the Java programs used in the testing. I compiled them using the
"-O" option. They are:

    lob_read_test.java
    lob_write_test.java
    emp_rw_test.java

From all the tests, I see that the OCI8 driver is faster. Sometimes by a lot,
sometimes by a little. This is true when the client is either Solaris or NT.
Additionally, the connect times are faster for OCI8 versus the thin drivers. For
the EMP read/write tests, I noticed that having a batch size less than 10 shows
a definite difference between the thin and OCI8 driver but batches of 10 or more
usually show relatively insignificant timing differences.

the code he used is simply:

LOB-WRITE:

import java.io.*;
import java.lang.*;
import java.sql.*;
import java.util.*;
import java.text.NumberFormat;
import oracle.jdbc.driver.*;
import oracle.sql.BLOB;

public class lob_write_test {
   static String sql = "begin insert into lobtest(data) values(empty_blob())
return data into ?; end;";

   public static void main(String[] argv) {
      try {
         // Setup program parameters and input blob
         int lobsize = Integer.parseInt(argv[0]);
         int iter = Integer.parseInt(argv[1]);
         byte[] data = new byte[lobsize * 1024];
         Arrays.fill(data, (byte) 64);

         // Setup connection and record time
         long start = System.currentTimeMillis();
         DriverManager.registerDriver(new OracleDriver());
         Connection conn = DriverManager.getConnection(argv[2], "scott",
"tiger");
         conn.setAutoCommit(false);
         System.out.println(System.currentTimeMillis() - start);

         long prepTime = System.currentTimeMillis();
         OracleCallableStatement ocs = (OracleCallableStatement)
conn.prepareCall(sql);
         ocs.registerOutParameter(1, OracleTypes.BLOB);
         System.out.println(System.currentTimeMillis() - prepTime);

         long totalIterTime = 0;

         for (int i = 0; i < iter; i++) {
           long iterStartTime = System.currentTimeMillis();
           InputStream is = new ByteArrayInputStream(data);
           ocs.executeUpdate();
           BLOB blob = ocs.getBLOB(1);
           OutputStream os = blob.getBinaryOutputStream();

           int bufSize = blob.getBufferSize();
           byte buffer[] = new byte[bufSize];
           int length;

           while ((length = is.read(buffer, 0, bufSize)) != -1) {
              os.write(buffer, 0, length);
           }

           is.close();
           os.close();
           conn.commit();
           long iterTime = System.currentTimeMillis() - iterStartTime;
           totalIterTime += iterTime;
           System.out.println(iterTime);
         }

         ocs.close();
         conn.close();
         System.out.println(totalIterTime/iter);
         System.out.println(System.currentTimeMillis() - start);
         System.exit(0);
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}

LOB-READ

import java.io.*;
import java.lang.*;
import java.sql.*;
import java.util.*;
import oracle.jdbc.driver.*;
import oracle.sql.*;

public class lob_read_test {
   static String sql = "select data from lobtest where ID = ? and rownum < 2";

   public static void main(String[] argv) {
      try {
         // Setup program parameters
         int lobsize = Integer.parseInt(argv[0]);
         int iter = Integer.parseInt(argv[1]);

         // Setup connection and record time
         long start = System.currentTimeMillis();
         DriverManager.registerDriver(new OracleDriver());
         Connection conn = DriverManager.getConnection(argv[2], "scott",
"tiger");
         conn.setAutoCommit(false);
         System.out.println(System.currentTimeMillis() - start);

         long prepTime = System.currentTimeMillis();
         OraclePreparedStatement ops = (OraclePreparedStatement)
conn.prepareStatement(sql);
         ops.setNUMBER(1, new NUMBER(lobsize));
         System.out.println(System.currentTimeMillis() - prepTime);

         long totalIterTime = 0;

         for (int i = 0; i < iter; i++) {
           long iterStartTime = System.currentTimeMillis();
           OracleResultSet rs = (OracleResultSet) ops.executeQuery();

           while (rs.next()) {
             BLOB blob = rs.getBLOB(1);
             InputStream is = blob.getBinaryStream();

             int bufSize = blob.getBufferSize();
             byte buffer[] = new byte[bufSize];
             int length;

             while ((length = is.read(buffer, 0, bufSize)) != -1);

             is.close();
             long iterTime = System.currentTimeMillis() - iterStartTime;
             totalIterTime += iterTime;
             System.out.println(iterTime);
           }

           rs.close();
         }

         ops.close();
         conn.close();
         System.out.println(totalIterTime/iter);
         System.out.println(System.currentTimeMillis() - start);
         System.exit(0);
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}

EMP-RW

import java.io.*;
import java.lang.*;
import java.sql.*;
import java.util.*;
import oracle.jdbc.driver.*;
import oracle.sql.*;

public class emp_rw_test {
   static String sqlQuery = "select * from emp";
   static String sqlUpdate = "update emp set hiredate=?, sal=?, job=? where
empno=?";

   public static void main(String[] argv) {
      try {
         // Setup connection and record time
         int iter = Integer.parseInt(argv[0]);
         int batchSize = Integer.parseInt(argv[1]);
         long start = System.currentTimeMillis();
         DriverManager.registerDriver(new OracleDriver());
         OracleConnection conn = (OracleConnection)
DriverManager.getConnection(argv[2], "scott", "tiger");
         conn.setAutoCommit(false);
         conn.setDefaultExecuteBatch(batchSize);
         System.out.println(System.currentTimeMillis() - start);

         long prepTime = System.currentTimeMillis();
         OraclePreparedStatement opsQuery = (OraclePreparedStatement)
conn.prepareStatement(sqlQuery);
         OraclePreparedStatement opsUpdate = (OraclePreparedStatement)
conn.prepareStatement(sqlUpdate);
         System.out.println(System.currentTimeMillis() - prepTime);

         long totalIterTime = 0;

         for (int i = 0; i < iter; i++) {
           long iterStartTime = System.currentTimeMillis();
           OracleResultSet rs = (OracleResultSet) opsQuery.executeQuery();

           while (rs.next()) {
             rs.getNUMBER(1);
             rs.getString(2);
             rs.getString(3);
             rs.getNUMBER(4);
             rs.getDATE(5);
             rs.getNUMBER(6);
             rs.getNUMBER(7);
             rs.getNUMBER(8);

             for (int x = 0; x < 20; x++) {
               opsUpdate.setDATE(1, rs.getDATE(5));
               opsUpdate.setNUMBER(2, rs.getNUMBER(6));
               opsUpdate.setString(3, rs.getString(3));
               opsUpdate.setNUMBER(4, rs.getNUMBER(1));
               opsUpdate.executeUpdate();
             }

             long iterTime = System.currentTimeMillis() - iterStartTime;
             totalIterTime += iterTime;
           }

           rs.close();
         }

         opsQuery.close();
         opsUpdate.close();
         conn.commit();
         conn.close();
         System.out.println(totalIterTime/iter);
         System.out.println(System.currentTimeMillis() - start);
         System.exit(0);
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}

Mark -- thanks for the support.

      Rating: 5
Interesting
      data - thanks for the update  September 25, 2001

Reviewer:  Joseph Gohlke  from Orlando, FL

I found the numbers and the code interesting and useful.  I appreciate the
completeness of the response.

We were actually testing different things.  Our code doesn't use the Oracle jdbc
extensions, we use the standard jdbc calls only.  We are reviewing our options
and may recommend a move to the Oracle jdbc extensions.  I'd like to see a
similar comparison between standard jdbc and Oracle's jdbc.

During further testing, we found that the OCI8 driver is often faster than the
thin driver, but not always.  The easiest, recreateable example of this is to
insert data into the database using standard jdbc addBatch and executeBatch (we
used batches of 1000).  In our testing, the OCI8 driver was consistently slower
than the thin driver (by a factor of 10 or more).

We feel more comfortable with the OCI8 driver, but will continue to construct
test scenarios to ensure we are getting the results we expect.  Sometimes, these
results suprise us because they don't meet our expectations. For example,
batching doesn't seem to buy much in terms of performance.

We've learned that subtle differences in the code can make big differences in
performance.  We've also learned that performance improvements are not "one size
fits all".

Thanks for the time and effort.

      Rating:
      5
  February 28, 2003
Reviewer:  Ron
      Chennells  from Newcastle UK
Tom once again excellent answer.
Is the increase in peformance due to the fact that the thin
driver uses repeated calls to the dbms_lob package whereas
the OCI driver uses some other method to manipulate LOBs?

          Followup:

pretty much.

the OCI drivers use native OCI calls, the thin use dbms_lob.

      Rating:
      4
Penalization?  March 07, 2003
Reviewer:
      Jonas  from Berkeley, CA
> It may be that you have your tnsnames.ora or sqlnet.ora
> setup in such a way that all connections except thin are
> penalized

I'm curious, what are the configuration setting(s) that can significantly
degrade the performance of the OCI drivers?

          Followup:

just connections in general that use tnsnames.ora and sqlnet.

You could have a dns issue -- you were using hostnames in the tnsnames.ora, ip
addresses in the thin

you could be on windows and have some nt domain checking going on from the
sqlnet.ora - really slow on certain setups -- but never done with thin

just plain old "configuration issues" -- that was the point.

      Rating: 5
THIN driver in
      MTS environment  April 29, 2003
Reviewer:
      Saminathan  from CA,USA
Dear Tom, The response is awesome.

I have a question about THIN driver in MTS environment.

Our DB is configured as MTS(Shared Server) and would like to get a DEDICATED
connection for my JDBC client program which uses THIN driver. Is it possible?

The reason for looking at
a)THIN Driver is that client  program needs portability.
b)DEDICATED connection is that the client JDBC program will process lengthy
transactions.

In case of OCI driver,have option to specify (SERVER=DEDICATED) in tnsnames.ora
file and using that tnsnames.ora entry in the program.

Tom, Kindly provide your comments on this.

          Followup:

lass test
{

public static void main (String args [])
throws SQLException, ClassNotFoundException
{

   DriverManager.registerDriver
        (new oracle.jdbc.driver.OracleDriver());

   String tns =
   "(DESCRIPTION="+
      "(ADDRESS_LIST="+
         "(ADDRESS="+
            "(PROTOCOL=TCP)"+
            "(HOST=aria-dev.us.oracle.com)"+
            "(PORT=1541))"+
         ")"+
      "(CONNECT_DATA="+
         "(SERVICE_NAME=ora817dev)"+
         "(SERVER=dedicated)"+
      ")"+
   ")";

   Connection conn=
       DriverManager.getConnection
       ("jdbc:oracle:thin:@" + tns, "scott", "tiger");

.....

      Rating: 5
Many Thanks
      Tom  April 30, 2003
Reviewer:  Saminathan
       from CA,USA

      Rating: 4
THIN driver for
      failover in OPS env  October 05, 2003
Reviewer:
      Saminathan Seerangan  from NJ,USA
Hi Tom,

Version: Oracle 8.1.7.4, OPS(2 node)
Can I use THIN driver for failover in OPS environment?
I mean specifying all the TNS failover configuration in the  Java program
itself.

Thanks
Sami

          Followup:

TAF requires the thick drivers.

http://download-west.oracle.com/docs/cd/B10501_01/java.920/a96654/oci_func.htm#1009011

October 05, 2003 -- Thanks for the question regarding
      "v$object_usage", version 9i

      You Asked
Tom,
When we use
alter index xxx monitoring usage
to start the process of monitoring and collecting index utilization, then we
query v$object_usage to see if the index has been used.

What kind of DDL or DML will generate 'YES' in the 'USED' column in the view?

In another words, will 'select' statement be considered using that index? or
only update....

thanks,

Cathy

      and we said...
the column will be YES when the index is used to ACCESS the table -- be it due
to an update, merge, delete or SELECT statement.

this column is not modified just because we "modified" an index entry -- that
would happen with each and every insert.

this column is set when we USE the index to access the table.

Consider:

ops$tkyte@ORA920.US.ORACLE.COM> create table t ( x int,
  2                   constraint t_pk primary key(x) );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> insert into t
  2  select rownum
  3    from all_objects
  4   where rownum <= 100
  5  /

100 rows created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

We'll enable monitoring on the index now

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select
index_name,monitoring,used,start_monitoring,end_monitoring
  2    from v$object_usage;

no rows selected

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> alter index t_pk monitoring usage
  2  /

Index altered.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select
index_name,monitoring,used,start_monitoring,end_monitoring
  2    from v$object_usage;

INDEX_NAME   MONITORING USED START_MONITORING    END_MONITORING
------------ ---------- ---- ------------------- -------------------
T_PK         YES        NO   10/05/2003 18:29:16

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

Using RBO as we are by default, a count(*) won't use the index

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set autotrace on explain
ops$tkyte@ORA920.US.ORACLE.COM> select count(*) from t;

  COUNT(*)
----------
       100

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'T'

ops$tkyte@ORA920.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select
index_name,monitoring,used,start_monitoring,end_monitoring
  2    from v$object_usage;

INDEX_NAME   MONITORING USED START_MONITORING    END_MONITORING
------------ ---------- ---- ------------------- -------------------
T_PK         YES        NO   10/05/2003 18:29:16

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

An insert won't either

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> insert into t values (-1);

1 row created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select
index_name,monitoring,used,start_monitoring,end_monitoring
  2    from v$object_usage;

INDEX_NAME   MONITORING USED START_MONITORING    END_MONITORING
------------ ---------- ---- ------------------- -------------------
T_PK         YES        NO   10/05/2003 18:29:16

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

But if we analyze the table, a count(*) will use the index
and we would have a record of it

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> analyze table t compute statistics
  2  /

Table analyzed.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set autotrace on explain
ops$tkyte@ORA920.US.ORACLE.COM> select count(*) from t;

  COUNT(*)
----------
       101

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FULL SCAN) OF 'T_PK' (UNIQUE) (Cost=2 Card=101)

ops$tkyte@ORA920.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select
index_name,monitoring,used,start_monitoring,end_monitoring
  2    from v$object_usage;

INDEX_NAME   MONITORING USED START_MONITORING    END_MONITORING
------------ ---------- ---- ------------------- -------------------
T_PK         YES        YES  10/05/2003 18:29:16

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

October 05, 2003 Deanna -- Thanks for the question regarding
      "Context and Packages", version 8.1.7

      You Asked
Hello Tom,

I am using context for the first time with Oracle packages.  I have used them
before with procedures and it has worked fine.

Is the syntax still: create or replace context MY_CTX using MY_PROCEDURE

I have been getting insufficient privileges errors

I also tried: create or replace context MY_CTX using
MY_PACKAGE_NAME.MY_PROCEDURE

Thanks for having this site!
Deanna

      and we said...
you use

create or replace context my_ctx using MY_PACKAGE_NAME;

you associate them (context's) with top level schema objects -- things you see
in "USER_OBJECTS" -- any procedure/function in the package can set the context.
October 06, 2003 bhabani -- Thanks for the question regarding
      "Backup Strategy", version 8.1.7

      You Asked
Tom

I would like to know how to frame a backup strategy for a 24/7 DB

Thank You

      and we said...
very very simple answer:

hot backups using RMAN.

why?  hot = always up.  rman = i can limit the speed at which blocks are read so
I can make it so my backup doesn't overwhelm my system (9i).

see

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/index.htm
and read all of the backup and recovery docs -- well worth the time as this is
the ONLY thing you as a DBA are not allowed to mess up.  Almost everything else
can be fixed in time, but if you mess up backup and recovery -- you lose the
data and then -- you lose your job ;)

Review &
      Followup

      Rating:
      5
  March 01, 2003
Reviewer:  bhabani
       from India
Thank you Tom

Yes proper backup is of utmost importance.

My databases are from 5 gb to 35 gb. I know there are a lot of advantages of
using RMAN like - donot have to worry for RECOVERY, skipping unused blocks, FAST
and less space.

Still, can I do hot backup tablespace by tablespace using ALTER
TABLESPACE.........BEGIN.......------>OS COPY OF FILES --------> END BACKUP ?

One more thing: since export is not at all a backup(i believe it is required 1]
when we port the DB to different OS and hardware 2] we can have good scripts
using ROWS=N), why it is considerd as a part of the backup strategy?

Thanks and Regards

          Followup:

if you have more then one database, rmans recovery catalog is something you need
as well.

of course you can do it the old way with alter tablespace begin/end backup --
but rman does the book-keeping for you and makes it harder to do backups wrong.
DIY makes it easy to do it wrong -- or just plain not know what you have going.

I listed some of the reasons why export can be considered part of an overall
backup strategy

o it exercises the data dictionary for logical consistency
o it can be used to recover that accidently dropped table sometimes -- or code
-- without having to do a database recovery
o it tests the logical integrity of your tables -- does lots of full scans,
reads the data.  finds issues before your end users do.

      Rating:
      5
  March 01, 2003
Reviewer:  bhabani
       from india
thanks Tom

      Rating: 4
more
      doubts  March 01, 2003
Reviewer:  Amitabh

Tom

what do you mean by 'it exercises the datadictionary.....'

Regds

          Followup:

it reads the entire data dictionary -- triggers, table definitions, etc.  It
runs tons of queries against the data dictionary, exercising it.  If there is a
logical integrity issue OR physical problem with the data dictionary -- exp
typically finds it.

      Rating: 4
Export -
      Usefulness  October 05, 2003
Reviewer:
      Richard  from Oxfordshire, UK
Re:

***
o it exercises the data dictionary for logical consistency
o it can be used to recover that accidently dropped table sometimes -- or code
-- without having to do a database recovery
o it tests the logical integrity of your tables -- does lots of full scans,
reads the data.  finds issues before your end users do.
***

Is it necessary to actually export the data for all this to happen?

          Followup:

"that accidently dropped table" -- yes, you must export the rows

"does lots of full scans, reads the data" -- yes, you must export the rows.

I would say "yes", you need to actualy export the data.

Note: RMAN is even a *better* way to do that last part, testing the logical
integrity of your data.

      Rating: 4
Commercial
      RMAN-enabled Backup Products  October 05, 2003

Reviewer:  Jer Smith  from Salt Lake City, UT

Any comments about the commercial products such as Veritas, Legato, or
Brightstor, that integrate with RMAN?  I'll be implementing a Veritas NetBackup
system in the next few months here.  Do they effectively take advantage of RMAN
features or are there areas where they fall down on the job?
RMAN is nice but since most environments have more than just Oracle to them, I
get the feeling once needs a bit...more.

Ideally, I'd like to leverage a product like this into point-of-time recovery
for OS, application server, Oracle, other databases--as all of these are part of
a consistent environment.  Even if I can do amazing things with the Oracle
backup software, if the problem is, say, recovering from an install of lousy
software, then I need something that handles the whole environment.

          Followup:

they integrate nicely and allow you to take advantage of the RMAN features.

October 06, 2003 Them -- Thanks for the question regarding "How to
      search record having Clob field", version 8.1.7

      You Asked
Dir Sir !
I like your web very much.
In next time , I want make my web, it will connect to Oracle Database. It is
writen by JSP (Jdeveloper 3.1.2)
 In that I must find some keywords in some questions . The questions are writen
in text (notepad, word,html..)
I will store them in my database in Clob to search. They like your web. some
months ago I look your web, you told to search use contain comand, but now I
can't search that question.
Can you tell me how can I design database to search keyword in questions ?
Can you tell me how do you do to find (search) question by keywords?

  Thank you !

      and we said...
You are looking for "interMedia"

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/inter.817/a77063/toc.htm
it provides the CONTAINS operator.  Search this site for CTXSYS for examples.
Review &
      Followup

      Rating: 3
Thank you very
      much  March 17, 2002
Reviewer:  Them
       from Viet Nam
Thank you very much. This is first time I can ask you question.Today I'm really
happy  when you reply for my question.I only access internet in the morning and
afternoon (when you have tonight because I live in Viet Nam). I hope you usually
check question this time.
 I will ask you some question in some days.
 Thank you very much.

      Rating: 3
Search a
      word  October 02, 2003
Reviewer:  apl
       from india
I am designing a database for  web. i want to search a word from a group of
words which is stored in a particular column.
1.Is this the proper way of design.or any differnet way of design so that we
will ge the resulting words faster.

2.How we will get the results faster in sites which contain bulk data?

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-TEX
Oracle Text is the way to do this -- it is what I use here on this site.

      Rating: 3
not getting the
      link  October 06, 2003
Reviewer:  apl

Thank You tom. But i am not getting the link. It is going to the otn
documentation windows.

          Followup:

they moved the docs.

goto the docs and look for the oracle text documentation.

October 06, 2003 milroy -- Thanks for the question regarding "how
      to fetch database to list item", version developer 6

      You Asked
Hi!
I need to know how to display table column data using list item
in the developer form.please send a mail.
regards,
milroy.

      and we said...
Well, you can simply populate the list elements in the designer
using the "list elements" property in the property sheet or you
can use a trigger at runtime (eg: in a when-new-form-instance or
something) to populate the list item from a record group.

For example, I created a default block on DEPT.  I wanted the
deptno to be a drop down list of the existing deptno's in the
dept table.  I changed the deptno type to LIST and added one
list element (value = 0, label = 0) to allow the form to
compile.  Then I added a when-new-form-instance-trigger:

declare
    rg recordGroup;
    status number;
begin
    rg := Create_Group_From_Query( 'list_rg',
         'select to_char(deptno), to_char(deptno) from dept' );
    status := populate_group( 'list_rg' );
    populate_list( 'dept.deptno', 'list_rg' );
end;

That filled in the list at run time with the values in the dept
table for me.
Review & Followup

      Rating: 3
multi-record
      data block with more than one related list items  July 24,
      2002
Reviewer:  Abdal-Rahman Agha  from KSA

Hi Tom,
  That's what you did is simple. Now if I have three tables:

States(state_code number(2) primary key,
       state_name varchar2(30));

provinces(prov_code number(2),
          state_code number(2) references states,
          prov_name varchar2(30),
          constraint pk1 primary key(state_code,prov_code)
          );

people(no number, name varchar2(50), state_code number(2),
       prov_code number(2));

I Created multi-record database block from "people" and I converted the columns
"state_code" and "prov_code" to list items.

In when-new-form-instance trigger I called your procedure to populate my list
with the "state names".

In When-List-Changed trigger at the "state_code" item I called your procedure to
populate the "provinces" list based on the value selected at the "state_code"
item.

Now, I saw that any selection on the state list changed all the lists elements
in all the other displayed records in the multi-record block. WHY???

Thanks.

      Rating: 2
Record group
      taking too long to populate  January 18, 2003

Reviewer:  Godwin  from Ghana, West-Africa

Hi Tom,
I have about 30000 records in a table linked to other tables in a database. I
wrote a query attached to a record group to populate some record in forms6i.
The problem is that after i have run the form,it takes about 10 minutes before
the recordgroup is populated. What do you think is the reason for such a delay.
I does the population using WHEN-NEW-FORM INSTANCE OR WHEN-NEW-BLOCK INSTANCE
with this script:
declare
p pls_integer;
begin
 p := populate_group('course');
  populate_list('one.my_desc','course');
end;
pls help!!!!!
thanks.

          Followup:

your network is slow perhaps?

i've no idea.  I cannot even see the query.  use tools like sql_trace,
timed_statistics, tkprof to see if the problem is a poorly running query -- or
your network.

30,000 records in a form or a poplist is a tad "large" don't you think.

      Rating:
      1
Suggestion  January 19, 2003
Reviewer:
      Riaz Shahid  from PRAL, Islamabad, Pakistan
I think you Mr.Godwin should opt LOV instead of list item for 30,000 records
(and put validation upon it)...the list item casn't be filered (as in LOV using
find button). So choosing a specific value from list item of 30,000 values is
tadious.

Regards

Riaz

      Rating: 2
list item lable
        October 04, 2003
Reviewer:  huss
       from egypt
Tom ,
 how i got the list item lable in run time ?
 i mean when the user select from the list, then list item carry the value of
the slelected lable BUT if i need that lable for any thing how i get it.
note that the list populate by some query so i dont know the list item indexes

          Followup:

i don't do forms, haven't done forms in about 8 years now... sorry -- try
otn.oracle.com -> discussion forums.

      Rating: 1
are u intend to
      do that ?   October 04, 2003
Reviewer:  huss
       from egypt
Tom that is my second or 3rd question u not answer
is it cause some thing in me or my nation or what ?? !!

          Followup:

umm, no -- nothing to do with the country.

However, it has everything to do with the fact that -- hey, you asked about
forms.  I don't know forms, haven't used forms for over 8 years, don't know the
answer.

I did however do the next best thing -- I told you where to go to perhaps get an
answer if one exists!

and in all fairness here, I see 5 "reviews" in my system made by you.

This one, which I said "i don't know, but go here -- they might"

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:12718072439781#12755915001593
which was in fact "answered"

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1264000472584#12711993364713
which was answered with -- oh wait, the same exact thing I said here "sorry,
don't know, goto otn and try there please"

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:580828234131#12710224005667
which was answered.... (it was about the database)

      Rating:
      2
!!!  October 05, 2003
Reviewer:
      Muhammad Ibrahim  from India
Dear Mr.huss,

  Tom is a very gentle person. He is meant for software. I am seeing him in this
forum more than a year or so. He is doing a great job for people like you and
me. Please dont take it personally.

Regards,
Ibrahim.

      Rating: 4
how to fetch
      database to list item  October 05, 2003
Reviewer:
      Jamil Shaibani  from Saudi
Dear Sir
I have used the same instruction that you have giving in your answer, but I am
getting this error message when I run the form FRM-30188 NO INITIAL VALUE GIVEN
AND OTHER VALUES ARE NOT ALLOWED (item DEPT.DEPTNO). LIST DEPTNO BLOCK:DEPT
What values do I have to put to the initial value field in the deptno property
list.
In the property list of the Deptno , I have  changed the item
Type to LIST item and in the elements in list I click in it and in the
list elements I add 0 and in the list item value I add 0 also and press ok. But
still it is giving the above Frm-30188 message
Thank you very much for your valuable answer.
Best regards
Jamil

          Followup:

sorry -- i just don't have forms installed anywhere to test with.

the docs say

Error:  FRM 30188
Text:   Warning! no default value given and other values are not allowed.
-------------------------------------------------------------------------------
Cause:  No default value given for item.
Action: Provide a default value for item.

so maybe by providing a default value?

      Rating: 4
how to fetch
      database to list item  October 06, 2003
Reviewer:
      Jamil  from Saudi
Dear Sir
Would you please explain this
declare
rg recordGroup;
status number;
 begin
 rg := Create_Group_From_Query( 'list_rg', 'select to_char(deptno),
to_char(deptno) from dept' );
status := populate_group( 'list_rg' ); populate_list( 'dept.deptno', 'list_rg'
);
end;

If I want to select both department name and deptno who can I do it , I try
doing this it return deptno and department name
declare
    rg recordGroup;
    status number;
begin
rg := Create_Group_From_Query( 'list_rg',
         'select to_char(deptno)||DNAME, to_char(deptno) from dept' );
       status := populate_group( 'list_rg' );
        populate_list( 'dept.deptno', 'list_rg' );
end;

But if I want to include space between depart no and department name it return 0
 like this

declare
    rg recordGroup;
    status number;
begin
    rg := Create_Group_From_Query( 'list_rg',
         'select to_char(deptno)||'-'||DNAME, to_char(deptno) from dept' );
    status := populate_group( 'list_rg' );
    populate_list( 'dept.deptno', 'list_rg' );
end;
it return 0

Thank you very much for your valuable help.

Best regards
Jamil

          Followup:

i don't know what you mean by "it return 0"

      Rating: 3
Ibrahim,
      india  October 07, 2003
Reviewer:  Muhammad
      Ibrahim  from China
Dear Jamil,

'select to_char(deptno)||'-'||DNAME, to_char(deptno) from dept' );

'select to_char(deptno)||'-' DNAME, to_char(deptno) from dept' );

This should work!

Regards,
Ibrahim.

October 06, 2003 Aru -- Thanks for the question regarding
      "Modeling graphs inside a relational database", version 9i2

      You Asked
Tom,

We have a debate on how to model graphs in a relational database. Our customers
unwilling to  use the object-relational features of Oracle.

APPLICATION:  Participants submit data indicating how much energy they can
sell/buy and for what price.  This data is submitted in the form of
piecewise-linear curve with 20 segments:  First 10 MW at  $10, next 30 MW at
$11, and so on up to 20 such quantity, price points.   The data is submmitted
and utilized as a whole curve.  Users do not query or modify individual linear
segments of the  curve.

We now define columns X1,Y1,...X20,Y20 and enter the curve in a single row of
the table.  This is convenient for the applications which need to read this data
and use it in their linear-program optimization calculations.

The DBAs on our client's side consider this a violation of normal forms and are
asking us to create a child table called PriceCurves that has three new columns:
SegmentNo, X, Y and then create the curve as twenty rows.

Since individual segments of the curve are never accessed or modified,  our
model is not a violation of any of the normal forms.  Besides there is some
performance advantage to not having a child table and having data in a single
table in a single row.

I have suggested we switch to using ObjectRelational features and embed the
entire curve as a single object in the parent table.  But they are not ready for
O-R as yet.

What do you think?

      and we said...
I think normalization is overrated.

I think performance is key.
I think ease of use if paramount.
I think common sense must reign supreme.

I think the model IS normalized.  normalization does not mean "have as many rows
in the database as possible"

Tell me -- what rule of normalization are you in violation of?  They are
thinking this should be modelled as a parent child relationship.  I see it as an
entity with 40 attributes.  X1, Y1 is not any different then "ename", "job" to
me in this context.

What sort of dramatic performance decrease would you see if you did what they
THINK they want?

What sort of storage increase would you see?

I would not use an object for persistently stored data personally.  I use the OR
stuff in my PL/SQL code all of the time in order to make it a more productive
programming language, but when it comes to storing bits and bytes on disk -- i
stick with well designed, performant tables.

Do that which

o makes the most sense
o is fast
o is efficient
o is easy for the applications that consume the data to manipulate

Review &
      Followup

      Rating: 3
Limitations of
      RDBMS  September 28, 2003
Reviewer:  Vikas
      Khanna  from INDIA
Hi Tom,

Recently in a conference a questions was posed to the participants :

What are the 3 limitations of RDBMS in context to O-RDBMS. In what situations we
should use RDBMS and in what situations O-RDBMS is helpful.

Can you please give us a detailed explanation of the above.

Regards,

          Followup:

I know of no limitations -- since O-RDBMS's are effectively built on top of
RDBMS's....

I've never felt limited by the rdbms.

      Rating: 2
Ranting 'bout
      normalization...  September 29, 2003
Reviewer:
      Marc Blum  from Aachen, Germany
Hi,

first a quote from one of Tom's presentations:

<TOMQUOTE>
Statements that should raise your eyebrows:
It is my opinion...
I claim...
I think...
I feel...
</TOMQUOTE>

;-)

My 2 cts:

(1)
Every developer should have a solid understanding of what normalization *means*.
Any occurence of de-normalization should raise his eyebrows: "Gosh, what happend
here to the datamodel? I'll find out if there's some strong reason for it." If
there's a reason, fine.

(2)
Follow the "Single best place"-Dogma for your datamodel

(3)
Avoid redundancies (in the first order). They are errorprone because developers
tend to overlook them; or the staff changes and noone tells the newbies how the
datamodel is to be understood (sad, but look around you, that's the way it is).
So, when intruducing redundancies, make their maintenance bullet-proof and
undependend from changing application code.

(4)
The above example is typical for a "technical" type of application. Lots of
nearly equal attributes. I agree with Tom here. The only point is that every new
price segment means a structural change to the database.

Greetings from old europe
Marc

      Rating: 2
I have 2 cents
      as well ...  September 29, 2003
Reviewer:
      Gabriel  from Toronto
Aru,

Do you think there is any remote chance they will move from 20-segments curves?
Do you think they will ever consider offering buying/selling on multiple types
of segments curves at the same time? (if "Yes" on the second, and if implemented
now with 20 pairs of columns ... I would be willing to bet, they will invent
another SEGMENT_TYPE and, if required, add some more columns to this table)

From your description, I gather you represent the contractor who builds a
solution for a client (and their DBA has raised the issue). Have you advised
them what would be involved in moving from 20-segments to 21-segments curves? …
you better get them to sign on it (what am I talking here, maybe you did that
already).

<quote>The data is submitted and utilized as a whole curve. Users do not query
or modify individual linear segments of the curve.
</quote>
Do you ever see this changing? I can certainly see the benefit of asking a
question like “Get the best price plan available for my average consumption of
300MW per month” … you may have to come back to Tom and ask for the best SQL one
could have (the man is a miracle, he can take ANY data model and squeeze any
ounce of performance out of it). After he gives his answer contemplate a change
from 20 to 10-segments curves … would his query “scale” (as in, be resilient to
the change)?

<quote>
We now define columns X1,Y1,...X20,Y20 and enter the curve in a single row of
the table.  This is convenient for the applications which need to read this data
and use it in their linear-program optimization calculations.
</quote>
So, the whole curve is inserted in one gulp and served as one … nothing more,
nothing less. Check it out Tom, they are using the database as a dumping ground
(+backup, +whatever). The question that I mentioned above is probably being
served by that “linear-program optimization calculation” engine … maybe that
application did not hardcode the 20 limit! Do you see your client ever using
that data for anything else but the calculation engine? Which modeling approach
to you think is more resilient to change?

Bottom line: for the situation you described and based on the hints you gave
regarding the architecture of the system and its known intended usage … yes,
store the whole curve (segments and all) as one record. If you are mandated or
have an Oracle database around (hopefully employed for a better use) then use it
… otherwise you can get your functionality (merge curves in a file and serve
them whole) for, virtually, $0. And, yes, you’re bound to cause a stir by asking
“how to model graphs inside a relational database” when all you need is a file.

Tom,

<quote>X1, Y1 is not any different then "ename", "job" to me in this
context.</quote>
Why don’t you consider X1=100MW, X2=20MW, X3=30MW and “ename”, “job”? …
The only thing ename and job have in common is “they are attributes of the same
Employee” … X1, X2, X3 are attributes of the same curve but there is more: the
20MW represents the segment between 100MW and 120MW. Graph the points (Xi,Yi)
with i=1,2,3 in any order and you’ll get the same 3 dots … start connecting the
dots and the order in which you do that will give you different shapes (as in
“meaning”).
But you are absolutely correct; “in this context” … the app does not need, right
now (I may add), to model the segments of the curve. It is questionable whether
they need a relational database at all (except for its non-relational features).

<quote>
Tell me -- what rule of normalization are you in violation of?
</quote>
How about repeating groups.
I remember working for a company building a commercial ETL tool and having to
implement migrations from Mainframe/Cobol to Unix/Oracle … as in, from “closed”
to “open” systems (steady business … Oracle sure rode that wave) … guess Oracle
was considered “open” because it was/is offering facilities for implementing
relational data models. I remember having to deal with Cobol metadata that said
entity E has attributes A, B, C and data coming in as <record header
info>,A,(B1,B2, … Bn), C. People were moving to open systems partly because
querying (as in “making use”) of data stored as A,B1,B2, …   ,Bn,C was
inefficient and prone to error. Extracting the whole record in one gulp was fast
and easy … anything else wasn’t.

Bottom line: as in many other cases, your practicality gets you scot-free; but
“normalization is overrated” is in the same league as “my query is not
performing”.

Personally I approach “normalization” very much like “surrogate keys”:
Not “should I use a surrogate key?” but “why shouldn’t I use surrogate keys?” …
hence, not “should I normalize?” but “why shouldn’t I normalize?”. In both cases
the advantages, long term, are overwhelming. My conclusion may be questionable
... but, hey, I don't really have a reputation to protect.

On a different note … this site is like a magnet … certainly the best marketing
effort Oracle has undertaken to wining mind-share. And we know who the Caesar
is, don’t we? :)

Gabriel

          Followup:

ok, think about first_name, last_name, middle_name.   3 dots -- that when put
together give you a name.

think about street_addr, city, state, zip.  4 dots.....

and so on.  they are to me as meaningful as ename, job....

If you view it like that, no issues with a single, efficient, effective record.
Since the application itself that visualizes this is "20", no worries there.  to
add or take away would require fixes there as well.

normalization IS overrated.  I see it taken to illogical extremes -- literally
killing performance.  common sense -- that is what is not overrated.

And what is wrong with "my query is not performing"?  as long as you have the
supporting information to go with it?

I don't approach surrogates your way.  I do it the other way -- should I use
one?  Not, why shouldn't i use one.

      Rating: 5
RE:
      Caesar  September 29, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
> And we know who the Caesar is, don’t we? :)

The salad?

- Mark

      Rating: 4
Regarding
      comments from Gabriel and Marc  September 29, 2003

Reviewer:  Aru Arunasalam  from Santa Clara, CA

It is possible that the customer may choose to have more than 20 segements in
the future.  But the change won't come about without long deliberation.  As it
is 20 is much more than what most market participants want.

I don't know if the folks who created the normals forms (Codd?) allowed for such
anomalies as embedding price-curves in relational databases.

The basic theme to normalization seems to be: make sure you can manipulate
stored data without losing data integrity.  Clearly, if one were to change one
of the segments independantly, the entire price curve will be messed.  But the
same possible even if the data was organized in twenty rows instead of in one
row.

      Rating: 3
"column
      exploded" design is ridiculos  September 29, 2003

Reviewer:  Mikito Harakiri
http://www.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=hnXQ6.4605%24rn5.237912%40www.newsranger.com

          Followup:

gee, great argument, thanks for the pointer to a big create table.  now what?

did you look at

a) what they need to do?
b) what the application needs to do?
c) the stated constraints

hmmm?

      Rating: 3
I thought
      similarity is obvious.  September 30, 2003

Reviewer:  Mikito Harakiri
If you look more closely, one of the comments was "I would enjoy quering this
table". It applies to both cases,isn't it?

The problem stated "How do I model graphs", which is not quite what it really
means. It should read "How do I model a  function?" Now, (one argument) function
is a set of pairs.

Now, consider

create table Funct1 (
    valueAt0 integer,
    valueAt1 integer,
    valueAt2 integer,
    ...
)

Admitedly, it's slightly less ridiculos than the design with varchar2 in the
link, but close.

          Followup:

we'll have to agree to disagree.

since the application needs 20 "points", getting them in a SINGLE row is great,
that is exactly what the application needs.

I would even say that on pointed to table -- yes, I would enjoy querying that
table in many cases.  Lots better then a parent child!  I can see use cases
where that model is the only common sense approach.  I can see use cases where
that model stinks.  Nothing but nothing is black or white.

      Rating: 2
long
      ranting  September 30, 2003
Reviewer:
      Gabriel  from Toronto
Aru,

Here is my final “2 cents” for your original concern:
<quote>We have a debate on how to model graphs in a relational database. Our
customers unwilling to use the object-relational features of Oracle.
</quote>
I would avoid the one table design, primarily because of that 20 limit. If
necessary, you can put together a little process to denormalize the data and
store and sync that image into another table (if it is convenient to serve the
calculation engine this way) … so you can get the best of both worlds … all the
features you need are there in Oracle. As for O-R, you can invent your own or
you can use Oracle Spatial (all about points and lines and polygons). The “meat”
of Spatial though revolves around polygons, which you don’t seem to have … so,
just look/read at what they do in Spatial for lines, if you are curious about an
O-R approach.

Tom,

Entity with PK=(first name, middle_name, last_name) … I can assemble the full
name in any order I or anyone pleases and I’m not losing any meaning … it still
represents the same physical person (modeled by the Entity uniquely identified
by the said PK … I had to mention this so I don’t get the “but we cannot
identify all people in the world just by their name” … for anyone jumpy on that
one, think my Entity models “My Kids”).

Curve with PK=(dot1,dot2,dot3) … I cannot connect the dots except in a precise
order … (dot1,dot2,dot3) is different than (dot2,dot1,dot3) … if I’m not
concerned with “connecting the dots” then I’m not modeling a “curve” but a
triplet of dots.

Just assume for one minute we normalize a bit here:

Entity 1: Curve_Type (PK=curve_type_id, AK=name, max_no_of_segments, etc.)
Entity 2: Curve (PK=curve_id, FK=curve_type_id, create_date, etc.)
Entity 3: Curve_Segment (PK=?, MW_Amt, Dollar_Amt)

What do you say the natural key for Curve_Segment would be? (let us ignore
surrogate keys in this case) … well, it cannot be PK=(FK=curve_id, MW_Amt,
Dollar_Amt) since segment 1 and 3 may both be (100MW,$10) … but is
PK=(FK=curve_id, segment_order) … where the segment_order can only be an integer
between 1 and the related max_no_of_segments. Have I captured more semantics in
or very close to the model? (notice I did not say “all semantics”) In the one
entity design the ability to deal with all curves doesn’t exist … also the
semantics of the segment_order attribute are somewhere buried in the Oracle
metadata (not a good place! … talk about giving water to the mill of those who
argue one should get the business logic out of the database and into some upper
layer in order to be database independent). Is this little bit of normalization
taken to an “illogical extreme”? … YES, I say, if and only if, the only
requirement is and will ever be “model 20 pairs of (MW_Amt,Dollar_Amt)”. Do you
think I’m taking abstraction to “illogical extremes”?

From the little Aru describes, the bit (s)he’s building doesn’t have any
“understanding” of curves (I guess, “pricing plans”) … might as well store pairs
of (miles, gallons) … data in, data out … hence which is the best design for
this sole requirement? … overwhelmingly the one entity.

The problem may be (invariably, is), the curve-meaning would still be out in the
user community … your business guy/gal may soon say “I have those price plans in
the database to feed my pricing engine … let me get some more juice out of it” …
which model do you think looks appropriate now? The business user won’t see any
obligation to pay for any re-factoring of the model. The developers will rush to
fill in the gap between what the model actually is and what their business
owners thinks it is … based on their own understanding of how things should be …
is anybody’s guess where those extra semantics will now end up: in the model,
very close to the data in a PL/SQL trigger or package … or scattered around in
various client applications … or some upper layers. If the original architect
had any suspicion that data may be used for something else, then (s)he may have
missed the opportunity to keep those business rules in the model or very close
to it (which is what you recommend, isn’t it? … but after the fact, in many
instances, it is too late!).

Now, why am I so against a statement like “normalization is overrated”? In
principle I am not, because I can “guess” your reasons and more than willing to
give you plenty of leeway on it (you are absolutely correct with the vast
majority of your answers after all) … I would agree with you that plenty of
projects do not require a big modeling approach … in many cases I just put
together some DDL in a script and start from there … but, the fact that I’m
writing the DDL directly with all the denormalization I deem appropriate already
in, does not mean that I’m discrediting the value of normalization. Check out
your very first 5 words of the original response … you already set the stage for
what’s to follow … whether your intention or not, you have already associated
this big negative connotation to “normalization”. Your, maybe unqualified,
“follower” out there may take that to heart to mean “don’t bother to normalize
at all” … the critic may have just been given ammunition to challenge you.

Let us talk a bit about normalization and abstraction:

When we embark in this normalization process we can go between 2 extremes: fully
normalized and fully denormalized (everything lumped together into one entity).
Anything in between can be considered good or bad, depending on circumstances
(which also change over time).
One cannot say “normalization is overrated” (and expect not to be challenged)
since a valuation of what is appropriate (name it “common sense” if you like)
for a subject being modeled cannot be made unless the subject and its context is
actually described, in sufficient details, at that particular time. In other
words, one cannot just say “little bit more or little bit less normalization is
appropriate” … you also have to describe the subject being modeled and its
context. This is akin to saying “my query is not performing” … it just doesn’t
make sense unless you specify the actual query (“the subject”) and its context …
the same query executing in 30 seconds may be appropriate for a reporting
application but may not be for another OLTP application.  I remember reading one
of your papers and you making essentially the same point: client calling you in
and saying, “my system is not performing” … and you saying “Aha … and?” …
followed by inquiries about the context: type of application, suspect SQL
statements, history of recent changes, what is considered an appropriate
response time, etc. What did you just do? You used context to establish a
valuation system … after that, talk about common sense, this is worse, this is
better, this and that. Someone throwing at you just “my system is not
performing” coveys as much value as someone throwing at me just “normalization
is overrated”. Of course, you are totally entitled to express your opinions
since people come at this site exactly for this reason, to get them (and keep
coming back because they are overwhelmingly valuable) … but do not expect not to
be challenged if your opinions are generic or suspect. On a case by case basis I
have no doubt you can give plenty of examples where too much normalization
literally killed performance … but “case by case” is what I am getting at … the
“case” should come before the assessment if you are to be fair and objective.

Since you got me going … here is another thread where, I try to prove, you were
incorrect …

http://asktom.oracle.com/pls/ask/f?p=4950:8:2534945178675822451::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1235228851558,
Anyone traumatized by the commoner daring to question his/her idol … check out
this thread where I made and recognized a blunder … you could twist the knife
even more if that helps with your trauma …

http://asktom.oracle.com/pls/ask/f?p=4950:8:2534945178675822451::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:2200739488307,
One can say “abstraction is overrated” (and you certainly question the real
value of so called “universal models” … and I agree with you). Why can one
express this opinion though? … surely there are degrees of abstraction as there
are degrees of normalization. Because one of the extremes when abstracting is
always known and it just doesn’t make sense in any real life scenario … one
could abstract everything in this world until all is left is an entity named
“Thing” … it models and represents … well, anything. This is akin to OOM …
within the same namespace, the root of any class hierarchy is a class called
Object (Object Id) … but that is OK since the class is ABSTRACT … it doesn’t get
implemented in real-life. Hence, when one says “abstraction is overrated”
without giving much of a context I CANNOT DISAGREE because there is at least one
case where that is true: abstracting all the way to Thing just doesn’t make
sense … normalizing to 3NF may or may not make sense.

So normalization and abstraction are kind of orthogonal … I can fully normalize
a model and then continue to abstract it until I see blue in my face … or I can
strike a balance between the level of normalization and performance (enjoy that
for a moment) and then abstract till blue again. Conclusion: I don’t necessarily
“see blue” because I take a data model to a certain level of denormalization …
the evil is not in normalization.

All this talk about context and valuation systems (being able to compare things)
… let us have a real example and use what you mentioned: street addr, city,
state, zip … better yet, unit#, street#, street_name, street direction (we in
North America have Eglinton East and Eglinton West … talk about grids :)),
municipality, state, zip, country.

What is appropriate for the Mom&Pop B&B web-reservation system in “old Europe”?
They use all this stuff to mail a welcome package to whoever makes a reservation
… so, if all they care is to use the address information to print on envelopes,
what level of normalization is appropriate? Well, make your choice and I’ll keep
agreeing with you. Are they going to start offering on their website some sort
of map of the flight path between your address and theirs? Are they going to
feed this data to their own reporting system or even warehouse to get some
exotic intelligence out of their data? Remote possibilities … but to be sure
I’ll ask them. So, yes, having all those attributes denormalized in addr1,
addr2, addr3 is not going to raise any of my eyebrows. Of course, if they decide
not to build a custom app but rather rent a service, the company offering that
service may decide to offer those features … what is now appropriate? Well,
something more than addr1, addr2, addr3 may be better. A more extreme example
against the Mom&Pop requirements would be that of a organization conducting
elections … their business IS addresses and electors … for them an address has
at least two roles: mailing address and residential address. One would be well
advised to get giggly with normalization for those residential addresses. The
electoral address itself is “contained” within some parcel of land … the
distribution/density of people determines the boundaries of electoral polls, the
density of poll locations, the staffing required to run those polls, etc.
Comparing 2 addresses (which is critical in this case in order to avoid
duplications) is pretty complex … having those attributes denormalized is
definitely better than addr1, addr2, addr3. More to it, street, municipality,
state now have extra properties … they have associated polygons to it … is it
better to always have to parse out the municipality from the addr fields in
order to identify the associated polygon?
So we can compare and decide what is appropriate because we know what we are
talking about. No absolute full normalization is good, full normalization is
bad.

<quote>I don't approach surrogates your way.  I do it the other way -- should I
use one?  Not, why shouldn't i use one.</quote>
I said that is my approach … I did not presume to imply I approach surrogates
the same way you do … since I cannot read your mind. One could draw any
conclusion from this (in another thread)…

<quote>
I personally use surrogate keys all of the time.  Look up in the URL above,
there is a big "display_id" number up there (a combination of a sequence, time,
and random number.  The reason in this case is because the base table didn't
have a natural key (your question doesn't have a key really).

Even in tables that appear to have a natural key, I use surrogates.  This is to
protect myself when people decide the primary key value needs to be changed (and
they always do).  The only imutable column in the table is really the surrogate
key -- the rest are subject to modification.  Since I don't want to have to
cascade that update to dozens of tables -- i use the surrogate.
</quote>

You use surrogates for entities without a natural key … you even use them for
entities that have no natural key. Guess I’m getting at the same result … just
thinking about it differently.

Sorry this has gotten so long … I prefer to justify my points and sometimes it
takes more than 2 words (check out Marc bellow) to be rigorous when there is no
exact science about it. I’m not out to prove you wrong for the sake of it … just
following your advice of challenging “so called” experts. In my books, you are
not “so called” … you are THE expert … but not THE ABSOLUTE one. I’ll take
humans over any deity any time. And I’m learning more from you than you’ll ever
learn (if anything) from me.

To sarcastic Mark from Indianapolis, IN USA

I don’t know what to make of your 2 words insight? But take another guess … is
not the salad and is not Tom either.

Gabriel

          Followup:

You are passionate, I'll say that much.

given what I read in the question, i think a single table makes sense.  there is
no need to increase the LIO on this system some 20 fold potentially when what
they got "works".

      Rating:
      5
Gabriel  September 30, 2003
Reviewer:
      Mark A. Williams  from Indianapolis, IN USA
> To sarcastic Mark from Indianapolis, IN USA

I was neither guessing nor was I attempting to be sarcastic.

- Mark

      Rating:
      5
  September 30, 2003
Reviewer:  Chuck
      Jolley  from OKC, OK USA
Tell them they can have their child table...
IF they will maintain a lookup table of valid curve segment values.
Otherwise there is no "relationship" involved in their idea.
Many times informing the user of the true nature of what they are asking for
will demonstrate why it is less efficient.
Assuming that it realy is less efficient of course ;)

      Rating:
      5
Marc  September 30, 2003
Reviewer:
      Gabriel  from Toronto
Marc,

It certainly felt sarcastic to me … since it hinted to what I’m arguing against
… ambiguity. My only excuse … it was deliberate.

It just goes to show, that given the slightest level of ambiguity, whether
deliberate (conscious denomalization in ER modeling) or not, will invariably
generate different semantics to different observers.

Given a chance (new code, new requirements, new assumptions, new anything), the
actual semantics of a data model will deviate from its origins. This deviation
manifests itself through: performance degradation, logical bugs or through
legacy-type symptoms (difficult to change, missing or incomplete documentation,
etc.). Most of what we do in our business is try to keep this continuous
deviation in check. Tom’s approach is pragmatic: deal with the known problems as
they come … it is a deterministic path … he can always make it better … since
performance is a good indicator of how much deviation is there, he also
concludes one should design for performance (which I agree with ... since he's
really arguing for solid programming). I, on the other hand, cannot always make
a bad system perform … hence, whenever I get a chance, I try to control those
run-away semantics close to the model and as soon as possible (worried more
about being able to stand up longer rather than running faster) … I find
normalization a good tool for that … the downside? … having to convince people
(who sometime don’t give a monkey about it) about potential problems …
non-deterministic path … exposed to being labeled dogmatic, alarmist, obsessive,
trouble maker, whatever. Everyone remembers the fix for the big problem … not
having the problem at all? … a non-event. But I’m learning … Tom’s book is
finally being delivered … hey, we are slower here in Canada.

Again … sorry man for labeling you.

Gabriel

      Rating:
      5
Gabriel  September 30, 2003
Reviewer:
      Mark A. Williams  from Indianapolis, IN USA
Don't worry, I don't feel "labeled" :)

My intent was to have a tiny bit of fun (though unoriginal I realize), and to
give the "simple/obvious/easy" answer.  Certainly it was not intended to offend,
and I hope that it did not.  Though there *are* things you can read into the
fact that it was a "simple/obvious/easy" response if you are so inclined (just
not too much, ok?) - as you observe - "different semantics to different
observers".

I understand and respect your viewpoint, and agree with Tom that you have, if
nothing else, a full supply of passion.

"Tom’s book is finally being delivered" - I hope that you enjoy the book as much
as I have and find it a worthwhile addition to your library.

"we are slower here in Canada" - you need to make the ice colder then :)  and I
am sure that is not the case.

Thanks,

Mark

      Rating: 5
Great
      Debate!  October 01, 2003
Reviewer:
      A reader
Great Debate Friends,
Good to know some of the expert opinions here. I have another similar situation
here:
A database to be designed to keep the values against the bookmarks of a word
document/ template.
Two approaches:
1. Create a Normalized Table
Template_id, field/bookmark_id, field/bookmark_value

2. Create a De-normalized Table
Template_id, field/bookmark_id_1, field/bookmark_value_1, field/bookmark_id_2,
field/bookmark_value_2 …

The second approach certainly provides performance, at the related cost probably
when a field/bookmark is added/ deleted. My thoughts are the first one though
elegant would be limited in its scalability due to very high LIO. The
modifications required when a field/ bookmark is added/ deleted would roughly be
the same in both the approaches.

Can I request your opinion here?
Thanks

          Followup:

here, two tables would be called for -- you do not have a documented upper limit
on the number of bookmarks and it does not seem to me that there would be one.

You can reduce the LIO's via the smart use of an IOT (index organized table).
It'll clump the book marks for a given document together on a block or two --
instead of scattered all over the place (same technique could be used in the
above question as well obviously)

      Rating: 5
Just for the
      Fun of it …  October 02, 2003
Reviewer:
      Gabriel  from Toronto
Hi Tom,

With your permission, I’ll play a bit more with our topic here (and promise not
to use any of the “Entity”, “Attribute” words).

First of all … I think we are all in agreement that the one table implementation
is appropriate when the only stated need is to stage data prior to feeding some
consumer. Who cares what is the content when the only thing I want is a small
container that moves fast?

But then, we’ve all been duped by the talk of price plans and graphs (the crazy
cousins of trees?) and O-R and … ! OK … all with, maybe, one exception.

Let us assume one would model this, in Oracle, when interested in MWs and
Dollars. We’ll also keep it simple and not think of things like comparing price
plans submitted by sellers and buyers. So, here it is … “give me the cheapest
energy plan for my average consumption of 42MW” … or the reverse “give me the
best energy plan for my $400 budget”.

The one table implementation (4-segment plans for simplicity):

create table epp_in1
( id             integer       not null
 ,name           varchar2(10)  not null
 ,segm_offset_1  integer       not null
 ,unit_price_1   integer       not null
 ,segm_offset_2  integer       not null
 ,unit_price_2   integer       not null
 ,segm_offset_3  integer       not null
 ,unit_price_3   integer       not null
 ,segm_offset_4  integer       not null
 ,unit_price_4   integer       not null
 ,constraint epp_in1_pk  primary key (id)
 ,constraint epp_in1_uk  unique      (name)
);

insert into epp_in1 values (1,'Plan A',10,10,30,15,20,20,140,24);
insert into epp_in1 values (2,'Plan B',20, 8,25,15,25,22,130,24);
commit;

First 10MW @ $10, next 30MW @ $15, next 20MW @ $20, the rest till 200 @ @24 …
and so on.

One would have to always scan the entire table to evaluate the best match. One
would also have the code lots of PL/SQL (or wrappers to external) procedures to
do all these CPU intensive searches … here is such a skeleton (doesn’t do
anything worthwhile) …

create or replace package math_pkg as

  function fn_dummy
           ( an_x              in integer
            ,an_segm_offset_1  in epp_in1.segm_offset_1%type
            ,an_unit_price_1   in epp_in1.unit_price_1 %type
            ,an_segm_offset_2  in epp_in1.segm_offset_2%type
            ,an_unit_price_2   in epp_in1.unit_price_2 %type
            ,an_segm_offset_3  in epp_in1.segm_offset_3%type
            ,an_unit_price_3   in epp_in1.unit_price_3 %type
            ,an_segm_offset_4  in epp_in1.segm_offset_4%type
            ,an_unit_price_4   in epp_in1.unit_price_4 %type
           ) return integer;
  pragma restrict_references(fn_dummy, WNDS, WNPS, RNDS, RNPS);
end;
/
show errors

create or replace package body math_pkg as

  function fn_dummy
           ( an_x              in integer
            ,an_segm_offset_1  in epp_in1.segm_offset_1%type
            ,an_unit_price_1   in epp_in1.unit_price_1 %type
            ,an_segm_offset_2  in epp_in1.segm_offset_2%type
            ,an_unit_price_2   in epp_in1.unit_price_2 %type
            ,an_segm_offset_3  in epp_in1.segm_offset_3%type
            ,an_unit_price_3   in epp_in1.unit_price_3 %type
            ,an_segm_offset_4  in epp_in1.segm_offset_4%type
            ,an_unit_price_4   in epp_in1.unit_price_4 %type
           ) return integer is
  begin
    -- complex stuff here
    return an_segm_offset_1 * an_unit_price_1 +
           an_segm_offset_2 * an_unit_price_2 +
           an_segm_offset_3 * an_unit_price_3 +
           an_segm_offset_4 * an_unit_price_4 ;

  end;
end;
/
show errors

After this a query like …

select math_pkg.fn_dummy(42,segm_offset_1,unit_price_1
                           ,segm_offset_2,unit_price_2
                           ,segm_offset_3,unit_price_3
                           ,segm_offset_4,unit_price_4
                        ) dummy_result
from   epp_in1
;

and then DENSE_RANK the results.

Now, this may be appropriate and even faster than the 2 table design … but there
are some disadvantages … I have to write all these routines, I may need to load
the parameters into PL/SQL arrays before being able to iterate through them
(should I need to, of course) … in fact, the only use of Oracle’s analytics is
that DENSE_RANK call outside.

The big ?advantage? of the 2-table implementation is that it already has the
data organized vertically … is ready for heavy querying as is.

Consider …

create table epp
( id     integer       not null
 ,name   varchar2(10)  not null
 ,constraint epp_pk  primary key (id)
 ,constraint epp_uk  unique      (name)
);

create table epp_segm
( epp_id       integer       not null
 ,segm_seq     integer       not null
 ,segm_offset  integer       not null
 ,unit_price   integer       not null
 ,constraint epp_segm_pk  primary key (epp_id, segm_seq)
 ,constraint epp_segm_fk  foreign key (epp_id) references epp (id)
 ,constraint epp_segm_chk check       (segm_seq between 1 and 4)
);

insert into epp values (1,'Plan A');
insert into epp_segm values (1,1, 10,10);
insert into epp_segm values (1,2, 30,15);
insert into epp_segm values (1,3, 20,20);
insert into epp_segm values (1,4,140,24);

insert into epp values (2,'Plan B');
insert into epp_segm values (2,1, 20, 8);
insert into epp_segm values (2,2, 25,15);
insert into epp_segm values (2,3, 25,22);
insert into epp_segm values (2,4,130,24);

commit;

If I look at the data with this …

select epp_id
      ,segm_seq
      ,segm_offset
      ,unit_price
      ,sum(segm_offset) over (partition by epp_id order by segm_seq)
as x
      ,sum(segm_offset*unit_price) over (partition by epp_id order by segm_seq)
as y
from   epp_segm
;

    EPP_ID   SEGM_SEQ SEGM_OFFSET UNIT_PRICE          X          Y
---------- ---------- ----------- ---------- ---------- ----------
         1          1          10         10         10        100
         1          2          30         15         40        550
         1          3          20         20         60        950
         1          4         140         24        200       4310
         2          1          20          8         20        160
         2          2          25         15         45        535
         2          3          25         22         70       1085
         2          4         130         24        200       4205

I can now almost see my graphs …
With MW on the x-axis and $price on the y-axis I can plot these:

Plan 1: (0,0) , (10,100) , (40,550) , (60, 950) , (200,4310)
Plan 2: (0,0) , (20,160) , (45,535) , (70,1085) , (200,4205)

The 2 queries I listed above are now equivalent with:
A.    finding the intersection points, with the graphs, for the line given by
x=42MW … the one with the lowest y is the cheapest price.
B.    finding the intersection points, with the graphs, for the line given by
y=400$ … the one with the highest x is the bigger MW volume for the budget.

One handy with trigonometry can even generalize the linear function for price
plans (needs to get into COS, SIN, TAN, etc.) and use that for the PL/SQL
function mentioned in the 1-table implementation.

With SQL I can do (maybe not the most elegant!) …

select epp_id
      ,to_char(max(decode(sign(x-42),-1,0,y-(x-42)*unit_price)),'999.99') price
      ,dense_rank() over (order by
max(decode(sign(x-42),-1,0,y-(x-42)*unit_price))) as rank_by_price
from
(
select epp_id
      ,segm_seq
      ,segm_offset
      ,unit_price
      ,sum(segm_offset) over (partition by epp_id order by segm_seq)
as x
      ,sum(segm_offset*unit_price) over (partition by epp_id order by segm_seq)
as y
from   epp_segm
) t
group by epp_id
order by epp_id
;

    EPP_ID PRICE   RANK_BY_PRICE
---------- ------- -------------
         1  590.00             2
         2  490.00             1

So, I have all the Oracle analytics to R&R.

I don’t know which approach is faster (your opinion would be welcomed) … the
tradeoff seems to be between build-in vs. build-out functionality rather than
normalization. In fact, the “normalization” is not even an issue since this is
now very much a calculation, derivation work-horse (it would be like arguing for
3NF for a DW) … the queries will get faster the more I pre-calculate and store.
The insert/update OLTP side definitely pales in comparison with the rest.

Now … what is the mentioned “linear-program optimization calculation” engine
doing then? Well, probably this and more  (kind of curios who’s salesman got
there first). Hence the whole thing may have just been about staging some data
after all.

Hence, I feel I owe you an apology … if nothing else, about my long ranting
being “long”.

Given the same conjecture I would still come strong against “normalization is
overrated”. Why? Because “normalization is rated” also leaves room for the
illogical extremes but has this positive connotation to it (excluding any other
considerations, the same model normalized vs. denormalized looks to me like a C
function with vs. without ASSERTs … hence, at least something is always better).
I see it a bit like “positive reinforcement” … I want my kids to learn music …
but, while they are young and vulnerable, I rather their teacher don’t use Pink
Floyd’s “ … I don’t need no education …” as class material. Well, this is it.

I rather start concentrating to my current real assignment (… no performance
problems, but huge data quality ones … and they come fast … surprise, surprise
the model is over denormalized and code is all over the place) … the client
wants me to “Analyze This” … better do it or no “Analyze That” sequel will
follow :)

Thank you.

Gabriel out …

      Rating:
      5
Thanks  October 03, 2003
Reviewer:
      A reader
Gabriel,
A worthwhile presentation. The crux of the matter is “database goes beyond the
requirements of current application being build”. A database is being considered
an organizational asset, the what and how of the data to be stored, decided by
what it represents – a recognition as organizations migrate to organization wide
data model thus decided. Typically such a data model would also have taken into
account the possible future use of this data – not to be forgotten here is the
fact that applications come and go while the data always remains.

The two possible future scenarios that you’ve pointed out (cheapest energy plan
for an average consumption, and, best energy plan for an budget) are just the
kind of requirements that could not be best catered for with de-normalized
data-model.

Tom’s advise on the other hand has been very practical, avoid taking
de-normalization to extreme, and an extraordinary way of physically keeping the
normalized data items together by using IOTs to keep the LIOs minimum.

And, if I can request Tom to confirm if it would be okay to make a third table
to keep the “attributes for a table in word template” in the word template
example above, it would be great. It is expected that there would be 15 to 55
bookmarks/fields in a word template, about 80 templates and a few templates
having a grid (word table).

Thanks and regards,

          Followup:

i don't have a clear enough picture to answer the last part.  i don't know what
the 3rd table is about?

      Rating: 5
No
      question.  October 03, 2003
Reviewer:
      Gabriel  from Toronto
To “A Reader”,

Thank you for your support ... if nothing else, for at least acknowledging that
I may have a point. In my mind, it wasn’t really about all future uses of the
data … that would be way more speculation that even I could handle, given the
little actual facts that we were presented with.

--------------

The fundamental difference between the 1 vs. 2-table design is where the
semantics of that “Segment Sequence” attribute are defined: in the data
(2-table) or in the metadata (1-table).

As any pragmatic would’ve been able to easily point out, the answers to the
simple questions I listed could be easily obtained from the 1-table design
without PL/SQL functions or trigonometric SQL functions (you may need them when
searching for a mathematical generalization of those graphs … and that is
abstraction that is not really required) … one CAN get the same answers with a
CASE statement (or derivatives) and a bit of elementary math ( + - * / )… and,
if one would want to also use the built-in Oracle analytics, getting the data
vertically in a temp table is also trivial.

The real issue is the fact that the CASE statement would’ve hard-coded EXACTLY
20 branches … going to 19 would require taking one out, going to 21 would
require adding one in, hence changes everywhere in the code … or to be precise,
any impact analysis effort would’ve had a wider area to investigate, hence
higher chances to miss something … with the 2-table design, the change/search is
localized: the little code that handles insert/update/delete in them. It is
about ENCAPSULATION … and I don’t even program C++ or Java for my living … I’m
SOOOOO “obsolete” :-( ! Quality software like Oracle and cheap CPUs/Memory/Disks
can get my data vertical from horizontal or vice-versa faster than I could
notice the difference. LONG TERM, PERFORMANCE IS NOT MY PRIMARY CONCERN. The
container (software and hardware) for the model gets better all the time … the
model doesn’t without re-factoring!

Is it likely the enterprise has hard-coded that “20” somewhere else in their
processes? Absolutely. I cannot really stop global deforestation single-handed …
but I can protect the trees at least on my side of the woods.

So, it all boils down to how important that “20” really is. Well, to use Tom’s
own metaphor he uses in the Introduction to his latest book (I just got it … and
is fantastic) … it is about how holistic the “Preventative medicine doctor”
should get. Or differently … how important is year 2000 for the programmer of
the ‘80s? … it is about only 2 digits after all :-) ! With the 1-table design,
the genie is out of the bottle … and the milestone is coming nonetheless ... the
only escape is if the company goes bust before it … or just spend the money
and/or pray for luck.

The morale of the story: when modeling graphs in a relational database, first
think Y2K.

As Tom explained, “Nothing but nothing is black or white”… it is about using the
gray (matter) when it really matters (sic!), in your particular context.

BEYOND the fact that the little bit that is pragmatic in the original question
has already been pragmatically answered … For the rest, “modeling graphs in a
relational database” (and using them), how obvious does the obvious has to get?
Draw your own conclusions everyone.

To Mark A Williams: sorry man about the “volume of words” …I know ”I Did It
Again”, but … simple things do evolve some time in controversy. As you may know
(just trying to make the point) … Fermat’s Last Theorem took over 350 years to
solve … and could be expressed in less than 20 “words” … the search for the FLT
proof has nonetheless spurred the development of most modern algebra (where is
this Relational Theory coming from?). Some things, IMHO, are worth defending …
even to the point of having to explain the totally obvious.

Gabriel … totally out … (of words) …

      Rating: 5
My
      question  October 06, 2003
Reviewer:
      A reader
Dear Gabriel,

If the requirements that you list are explicitly stated, it alone would derive a
two table (or normalized) design. It is many times, the requirements remain
hidden or evolve over time, that do not get adequately handled by the data
model. It is the experience of the business analysts and the data modelers to
see through these things. There are definite benefits of normalization, and, as
you say the container (software and hardware) for the model gets better all the
time, it can be coarsely said that, when in doubt normalize.

However, I see no clash in your’s and Tom’s viewpoints. You state the benefits
of normalization (rather brilliantly on the above problem) and academics would
endorse this as well.

While Tom, breaks our shackles, as in this case by saying a de-normalized table
might just be okay. And, he does it on a person to person (read case to case)
basis. If Tom sees there is a prejudice to normalize, taking it to excessive
limits, he says normalization is overrated, and , if he sees a poorly
de-normalized tables he advises for normalization. Now, reconciling the two or
extracting excerpts for critical examination appears a little gratuitous,
without the contexts in which these were said. The context is what Tom derives,
by attaching himself to the person, situation and problem, and, advises
accordingly - the best that the person, situation or the problem deserves.
That’s why he makes us understand like nobody else. That’s his USP, that’s why
so many of us visits him.

So, what this thread tells us is de-normalization is not a vice, and, in certain
cases (like performance) may be a demand too. Now for my scenario:

Dear Tom,

A template would look like:

<Reviewer_Name>
<Address_Field_1>
<Address_Field_2>

Dear <Reviewer_name>,

Please find enclosed the details of the documents still outstanding for review/
forward.

<A Word Table>

Sl. No.    Document ID.  Document Description    Status
<Sl_No>    <Document_Id> <Document_Description>    <Status>
...

</A Word Table>

Yours truly,
<Consultant>

The word table can have variable number of records. Like this there are many
templates.

Now table one would hold:
<template_id> <field_name> <field_type>
t1, reviewer_name    <field>
t1, address_field_1  <field>
t1, grid_1           <grid>
...
table two to hold
<template_id> <grid_id> <field_name>
t1             grid_1    sl_no
t1             grid_1    document_id
...

Table two can be a Nested Table, but what would be best?

Thanks for your time.

          Followup:

i only use nested tables as a programming construct in plsql -- i never use them
to persistently store data on disk.  physically -- they are just a parent/child
table implemented for you.  You can do it more efficiently yourself and if you
like the O-R paradigm, you can certainly use an O-R view to make it appear "as
if" it were a nested table.

October 06, 2003 Subhash -- Thanks for the question regarding
      "Where can I find info on writing callable database routines (APIs) using
      Pro*C and embedded SQL?", version 9.2.2

      You Asked
Hi Tom,

I'm planning to write my own layer of Oracle database callable routines/APIs
using Pro*C and embedded SQL. All I want to do is to write callable routines
which I plan to call from C code and should be able to manage tables and data
(insert/update/delete) in our application Oracle database. I'm working with
Oracle 9.2.2. I know we can use OCI instead, but the requirement is such that I
need to write my own API layer. Could you please point me to documentation on
writing Oracle database API? What I'm looking at is some instructions and / or
specifications, some document which tells me how do I go about writing those
APIs.

Thanks in advance,
Subhash.

      and we said...
why?

it is really hard to

a) do this in a manner that is easier then the existing api
b) supports all of the features you need to support -- such as BIND VARIABLES

The API you are looking for is called OCI, period, plain and simple.

To build your "own api" on top of that -- well, there can be no
documentation/specifications.  They would be your own idea, your own API, all
about you.

Our API is called OCI.  It is what your applications should code to.  anything
else will just be yet another layer that prevents people from having full access
to the functionality of the database and adds processing overhead and adds
inefficiencies and makes it harder to debug

In short -- doesn't add anything of "value", only detracts from the "value" of
what you have.

I believe it is time to "rethink" your requirement in terms of

a) cost to develop (given you don't really know what you want to do)
b) availability of off of the shelf API's (don't like OCI?  fine, use odbc.
done like ODBC? fine, buy something -- there are tons of them out there)
c) cost to maintain
d) what it is exactly you think you would gain by doing this.
Review &
      Followup

      Rating:
      4
Thanks!  October 07, 2003
Reviewer:
      Subhash
Thanks Tom, for your expertise and time. I'm giving second thought to the points
you raised.

Thanks again!
Subhash.

October 06, 2003 Bill -- Thanks for the question regarding
      "Performance and Creation Time for unique vs nonunique indexes", version
      9.0.1

      You Asked
I've also posted this on the OTN forum.

Searched metalink and OTN...and even Google.

Looking for a white paper on the creation timing and performance timing of a
unique vs a nonunique index on a unique column of a large (260M rows) table.

Thanks
Bill

      and we said...
well, the only difference between a unique and non-unique index is whether the
rowid is considered part of the key or not.

In a non-unique index, the rowid is part of the key (since there are only unique
indexes really!)

In a unique index, it is not....

So... they are more or less the same.  I did load up a table with:

big_table@ORA920> select count(*) from tall_but_skinny;

  COUNT(*)
----------
 258564672

the numbers 1..258,564,672 are in there.  Then created unique and non-unique
indexes on that and tkprof says:

create index tall_but_skinny_2 on tall_but_skinny(r)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.16          0          2          0           0
Execute      1   2000.30    3375.34    1175887     415226       3636           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2   2000.30    3375.51    1175887     415228       3636           0

create UNIQUE index tall_but_skinny_3 on tall_but_skinny(r)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.24          0          2          0           0
Execute      1   1969.09    2857.14    1175908     415221       3567           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2   1969.10    2857.39    1175908     415223       3567           0

they are roughly the same.  This was done "serially" (no parallel) with logging
(generated tons of logs...) so your mileage may vary - but basically -- you have
very similar structures at the end of the day.

Review &
      Followup

      Rating:
      5
Appreciation  October 03, 2003
Reviewer:
      Bill Pry  from San Jose CA
Tom, thanks for your most excellent work and description of the difference in
indexes.

You are a champ!

Thanks and regards,
Bill

      Rating:
      3
Confused  October 03, 2003
Reviewer:
      H  from USA
You said:
"In a non-unique index, the rowid is part of the key (since there are only
unique indexes really!)"
Question:
As per my understanding the rowids are unique, hence how would a rowid make a
unique index to a non unique index. can you please give an example.

I know that i am missing something here, thanks for helping me clear up the
confusion.

          Followup:

an index has two parts

a) key (your columns)
b) rowid (points to the row)

In reality, all indexes are really unique indexes.

Hence, if you create a "non-unique" index, Oracle will consider the ROWID as
being part of the KEY.  That'll make sure the KEY is unique in the b*tree.

If you create a "unique" index -- Oracle will only consider the KEY as being
part of the KEY.  That'll make sure YOUR key data is unique in the b*tree.

      Rating: 3
One
      Quest  October 06, 2003
Reviewer:  Anil Pant
       from Hyderabad, India
Hi,
Then it means creating an non-unique index is always an overhead as rowid will
be stored. The overhead in terms of storing an extra data and fetching it when
accessed. This Ive concluded from ur response.

"Hence, if you create a "non-unique" index, Oracle will consider the ROWID as
being part of the KEY.  That'll make sure the KEY is unique in the b*tree.
If you create a "unique" index -- Oracle will only consider the KEY as being
part of the KEY.  That'll make sure YOUR key data is unique in the b*tree. "

Pls correct me if Im wrong.

          Followup:

no no no

a rowid is ALWAYS stored in the index (how else do you think we get back to the
row????)

unique index implies ROWID IS NOT PART OF THE "key", the rowid is stored, just
not as part of the unique key in the b*tree

non-unique index implies ROWID IS PART OF THE "key",  the rowid is stored and is
part of the unique key in the b*tree.

both indexes have

a) the columns you asked to be indexed
b) the rowid

the only difference between them is whether the rowid is considered a "key"
value for uniqueness.

      Rating: 4
so many things
      to learn  October 06, 2003
Reviewer:  reader
       from India
Hi Tom,
      Good day to you, please correct me in case I am wrong what I got from the
point here is,for example rowid is XXXX and value of column to be indexed is AA
then

1. "unique index implies ROWID IS NOT PART OF THE "key", the rowid is stored,
just not as part of the unique key in the b*tree"

    Internal representation key AA rowid XXXX

2. "non-unique index implies ROWID IS PART OF THE "key",  the rowid is stored
and is part of the unique key in the b*tree."

    Internal representation key XXXXAA rowid XXXX

Thanks as always for your help.

Best Regards.

          Followup:

no.  think of the index as having

a) key
b) data

in a unique index for a row with a column value AA and rowid XXX -- the index
would be:

a) key = AA
b) data = XXX

in a non-unique index for that row we would have:

a) key = AAXXX
b) data = (null)

      Rating: 5
You are the
      best.  October 07, 2003
Reviewer:  Tom's fan
       from India
Thanks for the help.

Best Regards.

October 06, 2003 -- Thanks for the question regarding "Synonym
      problems", version 9.0.1

      You Asked
Hi Tom,

We have two different applications to access two databases. End users use
different applications to access different databases. Each application accesses
database through a set of public synonyms. Now we need to integrate these two
databases into one, but we are struggling to find the best way to solve the
public synonym problem, because the names of some public synonyms used in two
databases are same. And application code modification is our least choice.
Would you please advise if we have better way to solve these conflicts with
minimum code modification?

Any thought and comment will be greatly appreciated.

      and we said...
See -- this is why I don't like public synonyms AT ALL.  Just publishing this
one because we've had some recent threads on this.  Public Synonyms -- just
don't use them!

anyway, you maybe be able to get away with

ALTER SESSION SET CURRENT_SCHEMA=<application schema>

If the objects in your application are owned by a specific schema, this will
work, just make that part of the application, after you log in.
Review &
      Followup

      Rating:
      5
  January 29, 2003
Reviewer:  Vivian
       from US
Thanks Tom for your quick response. We also have been considering dropping
public synonyms and using set current_schema instead, but we have the following
questions:

1. If end user only need to access one application schema like batch loading
user, then the easiest way to set current_schema is to be done by using "after
logon database trigger". Is this true?
2. If end user need to access different application schemas through different
application modules, like web module, power builder, Oracle Forms/Reports. The
"after logon database trigger" will be unable to handle this situation. Do you
think that creating a stored procedure, let's say 'pr_set_schemaid', and execute
in each application module is the only way to go?  Because we have hundreds of
forms and reports, where is the best place the procedure called by Oracle
Forms/Reports? Do we need to include this set in every *.fmt or *rpt?
3. How to handle the scheduled jobs in job queue? Do you prefer to modify PL/SQL
procedure using schema.object format or using "alter session set
current_schema"?

Again, thank you for your time.

          Followup:

1) easiest is in the eye of the beholder.  If you have a rule that says "no on
logon triggers" in your shop, that would be very hard...

It is certainly workable, yes.

2) If each app needs a schema, the app should set the schema, yes.  You would
need to do this EVERYWHERE you need to have the schema be different then the
default schema.  Generally -- right after you logon i would suspect.

you can use the logon trigger to set the "default schema" and each app could
override that as needed.

3) they will use real schema names -- a stored procedure is statically bound to
an object *at compile time* so setting the current schema in a procedure would
not affect the procedure at runtime. (well, there are invokers rights routines
but that is a different story)

      Rating: 5
Set
      current_schema does not work in Oracle form6i  February 12,
      2003
Reviewer:  Vivian  from US
Dear Tom,

Here is a new problem I have while eliminating public synonym. Let’s say, I have
user SCOTT and TEST, and the following is what I have done:
Connected to Oracle9i Enterprise Edition Release 9.0.1.0.0
Connected as SCOTT
SQL> create role access_scott;
Role created
SQL> grant select on emp to access_scott;
Grant succeeded
SQL> grant access_scott to test;
Grant succeeded
SQL> GRANT CREATE SESSION TO TEST;
Grant succeeded
SQL> GRANT ALTER SESSION TO TEST;
Grant succeeded
SQL> CREATE OR REPLACE TRIGGER set_scott_schema AFTER logon ON DATABASE
   BEGIN
if user = 'TEST' then
execute immediate 'alter session set current_schema = SCOTT';
end if;
END;
/
Trigger created

SQL> connect test/xxxxx;
Connected to Oracle9i Enterprise Edition Release 9.0.1.0.0
Connected as test
SQL> select count(*) from emp;
  COUNT(*)
----------
        14
SQL>

So far, everything works fine. However, when I create a simple Oracle form which
has only a button together with a text item using Oracle Form6i. The button in
the form has a WHEN-BUTTON-PRESSED trigger using embedded the following PL/SQL
statement:
Begin

    Select count(*) into :totalnumber
    From   emp;
End;

Login test to compile the form, always got error ‘identifier EMP must be
declare’, and I login SQL plus check the schema for the test, it shows the test
in scott schema, shown as following:

SQL> SELECT USERNAME,SCHEMANAME FROM V$SESSION;

USERNAME                       SCHEMANAME
------------------------------ ------------------------------
                               SYS
                               SYS
                               SYS
                               SYS
                               SYS
                               SYS
                               SYS

TEST                           SCOTT

Any idea and solution? Thanks in advance.

          Followup:

I've not used forms since 1995 myself.

A temporary solution since TEST is obviously a developer and NOT and end user
(they are compiling forms) would be for test to either

o have a public synonym
o have a view

in order to compile -- regular users would not notice this.

      Rating: 3
Aditional
      Requirement with multiple schemas  October 06, 2003

Reviewer:  Carlos Gongora  from Cochabamba, Bolivia

I am finishing an application for an Electrical Regulation Company composed of 3
schemas:

1.- Schema A: Objects for Electrical Network (tables, views etc for the network
elements, etc)
2.- Schema B:  Proceses objects  (tables, etc for processes that are applied
over the electrical networks they built )
3.- Schema C: Tables for menus, app specific messages, app parameters, error
logs, etc

How can I perform, "alter session set current_schema = APP_SCHEMA" if I need one
user connected and accesing objects of the 3 schemas (A,B,C) defined in the
database.

I think the only way to solve it (we are doing this now in the development
schema) is to use public synonyms...

Thank you very much. I expect some comments or an alternate solution, becase I
think the only way to solve this is with public synonyms. Even though you
recommend not to use them.

Carlos Gongora

          Followup:

you cannot.

there is NOTHING wrong with using schema names in your queries -- nothing at all
wrong with that.

using schema names -- like "APP$EN" for electrical network and APP$PO for
processes objects and APP$CONFIG for the rest -- would work well.

October 06, 2003 -- Thanks for the question regarding "kill
      session and process", version 8.1.7.4

      You Asked
Hi Tom

When we do alter system kill 'sid, serial#'. Are we killing the user process or
the server process? Because sometimes i see that after the session is killed the
query is still running in the sessions even shows KILLED sometimes takes days to
disappear! Sometimes we got some problems with large batch jobs in testing
enviroment, we killed from sql plus but the job keeps on going!
What happens if we kill the server process with kill -9 instead of issueing
alter system kill?

cheers
cheers

      and we said...
see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1058832409881

the job isn't "going on" it is actually "going back" -- it is rolling back.

If the job took an hour to get where it is, it might take even LONGER to "undo"
what its done.

You can monitor how the killed session is doing as far as rolling back is
concerned in v$transaction via the used_ublk column.

Consider this example.  In a schema "big_table" i started a delete against a
1,000,000 row indexed table.  Partway through -- I killed it.  This is what
you'll see (you need to be more selective on the query against v$transaction of
course, I just had one active DML session going)

ops$tkyte@ORA817DEV.US.ORACLE.COM> select used_ublk from v$transaction;

 USED_UBLK
----------
      5750

1 row selected.

it generated that much undo so far...

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

USERNAME        SID_SERIAL      STATUS     MODULE          ACTION
--------------- --------------- ---------- --------------- ---------------
OPS$TKYTE       '7,665'         ACTIVE     01@ showsql.sql

BIG_TABLE       '8,1082'        ACTIVE     SQL*Plus

2 rows selected.

BIG_TABLE(8,1082) ospid = 13903 command = 7 program = sqlplus@aria-dev (TNS
V1-V3) dedicated server=13904
Monday    10:24  Monday    10:38 last et = 822
delete from big_table

I kill it and get the message "it is marked for kill".  That means the server
heard us and is in the process of rolling back but it'll take a while

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter system kill session '8,1082';
alter system kill session '8,1082'
*
ERROR at line 1:
ORA-00031: session marked for kill

ops$tkyte@ORA817DEV.US.ORACLE.COM> select username, status from v$session where
username = 'BIG_TABLE';

USERNAME             STATUS
-------------------- ----------
BIG_TABLE            KILLED

1 row selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select used_ublk from v$transaction;

 USED_UBLK
----------
      5327

1 row selected.

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

 USED_UBLK
----------
      5202

1 row selected.

we can watch the used_ublk shrink and even use that to estimate when it'll be
done

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

 USED_UBLK
----------
      4470

1 row selected.

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

 USED_UBLK
----------
      2914

1 row selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select username, status from v$session where
username = 'BIG_TABLE';

USERNAME             STATUS
-------------------- ----------
BIG_TABLE            KILLED

1 row selected.

session is still there...

ops$tkyte@ORA817DEV.US.ORACLE.COM> select used_ublk from v$transaction;

 USED_UBLK
----------
      2262

1 row selected.

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

 USED_UBLK
----------
      1430

1 row selected.

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

 USED_UBLK
----------
      1103

1 row selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select username, status from v$session where
username = 'BIG_TABLE';

USERNAME             STATUS
-------------------- ----------
BIG_TABLE            KILLED

1 row selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select used_ublk from v$transaction;

 USED_UBLK
----------
      1048

1 row selected.

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

 USED_UBLK
----------
       489

1 row selected.

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

no rows selected

now we are done rolling back and...

ops$tkyte@ORA817DEV.US.ORACLE.COM> select username, status from v$session where
username = 'BIG_TABLE';

no rows selected

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

the session is gone

If you kill -9, it might appear that the session is gone "faster" but not
really.  We still have to roll back (obviously).  We might be doing on demand
recovery of the blocks which makes it "appear" faster but we still have to
recover the transaction.  kill -9 is not something I recommend, especially if
you use the above technique to see it rolling back.

Review &
      Followup

      Rating: 4
server
      process  August 12, 2002
Reviewer:
      A reader
Hi

So when we kill a session we are actually killing an user process and the server
process will be rolled back by PMON and dies away, am I correct?

We had a problem with a scheduled job with dbms_jobs, scheduled to run every 5
minutes however someone killed it 10 days ago and in v$session we still see the
session is marked for kill (however process and paddr shows null) and it shows
up in dba_jobs_running however if I query v$process there is no process
associated to it (expected) but due to this zombie session the job has not run
since it was killed 10 days ago! And I really really dont understand why. The
way I fixed it is by set the job status to broken, ran the job manually, after
job is finished I set BROKEN to N again by using dbms_job

But I really dont understand why a zombie session blocked a scheduled job (and
the job shows as it was running for 10 days in dba_jobs_Running)

          Followup:

No, when you kill a session, you kill a session.  The process will stay there
(that process could have OTHER sessions in it)

A session is not a process, a process is not a session.

A session can use more then one process in its life (shared server, PQ for
example)
A process can service more then one session (shared server, connection
multi-plexing for example)

Now however, you've changed the entire scope of the problem from a simple "user
process" to "we killed the job queue".

You killed the job queue process, it does not end its process, you broke the job
queue (snpN) process.  It was unable to perform its function in life, running
jobs, updating the dba_jobs_running base tables and such.

you should have tried something like:

alter system set job_queue_processes=0;
alter system set job_queue_processes=N;

to restart the snpN processes.

      Rating: 3
why my
      condition is not same as what you said  September 09, 2002

Reviewer:  nintyuui  from china
SQL> select used_ublk from v$transaction;

 USED_UBLK
----------
         1

after 20 mins

SQL> select used_ublk from v$transaction;

 USED_UBLK
----------
         1

why the rollback cannot be completed?

          Followup:

It was rolled back.  I've seen it be off by one or two blocks (even go negative
sometimes).

      Rating: 4
Estimated Time
      to Rollback  June 10, 2003
Reviewer:  Vivek
      Sharma  from Bombay, India
Dear Tom,

I had read the column in ORACLE Magazine wherein you had answered a query from a
user regarding estimated time of Rollback. I have certain query :
1. I noted that the used_ublk was 2861 and after 60 seconds it was 2840. Thus as
per the calculation provided by you, it should take approx.135 Minutes to
rollback and thus clear the session. Am I right ?

2. Also the query select to_char(flag,'0000000X') from v$transaction gives an
error as

ORA-01481: invalid number format model

What could be the problem ?

Thanks and regards
Vivek

          Followup:

1) as long as it keeps rolling back at the really slow rate of 20 per minute
(which seems really slow)

2) you are on a really old version of Oracle that didn't support "x" as a format
perhaps.

      Rating:
      4
  June 10, 2003
Reviewer:  muhammad
       from nj usa
How i will know from the v$transaction table. which sid it belong

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7143624535091#7144005967893

      Rating: 1
Close, but not
      quite on the mark  June 25, 2003
Reviewer:  Evan
       from Georgia
My problem is a oracle session marked "KILLED" for several days that will not go
away. Restarting is not an option. I know I have seen an answer. It is just a
mater of finding it. Keep up the good work!

          Followup:

Umm, it'll just sit there.

You can kill the thread/process at the OS level but -- so what?  it is not like
it is consuming gobs of resources really.  It has no locks or anything like
that.  the process is idle and in a total state of "wait" so it'll be paged out.
 If it were a shared server connection, there is not even that to concern
yourself with.

Be very very very very careful if you go the kill route, or you may be going for
that unscheduled shutdown if you kill the wrong thing "by accident"

      Rating: 5
killed session
      still holding librarycache locks  June 26, 2003

Reviewer:  Angus McTavish aged 75  from Cumbria,
      England
We've had a situation crop up a few times where a killed session has somehow
retained library cache locks (as indicated by looking at x$kgllk) and is
blocking other sessions and slowly but surely grinding the database to a halt.
The only way we've been able to clear it up has been to 'kill -9' the process at
the OS level. Then the session disappears from v$session and the locks disappear
and everything flips back to normal.
It has only ever happened in these exact circumstances:
A user has been running queries in TOAD and the sql contains a long inlist. Once
the inlist goes beyond a certain size the session hangs and 'alter system kill
session...' kills the session but does not free the librarycache locks. Other
users are blocked, system slows down, kill -9, problem solved. But I haven't
been able to get to the bottom of it...
Doesn't happen in Sqlplus, just TOAD

      Rating: 5
Unable to clear
      lock on table   October 02, 2003
Reviewer:
      Brao  from Austin,TX
I have a stored procedure  which updates a error table ERROR during
inserts/updates.
Since it took more than 7 hours , we were forced to kill the session.
But I got only Session marked to kill.
But we queried
select used_ublk from v$transaction;
 to select the used blocks in the current transaction
it got rolled back.

But when I try to access (insert /delete / drop ) into ERROR I am getting the
following

ORA-00054 resource busy and acquired with NOWAIT specified

What could be the reason

Hope I made the points clear.

Thanks
B rao

          Followup:

and insert or delete wouldn't return that?  can you cut and paste an example
from sqlplus of that happening?

      Rating: 4
Shutdown
      Abort  October 06, 2003
Reviewer:
      A reader  from CT, USA
Why does shutdown abort and re-open seem to "speed" the rollback along somehow.

          Followup:

when you are rolling back in an "up" database -- it is done "nicely" so as to
not consume the entire machine (it would appear to be a loss of service
otherwise)

when you startup, you sort of want everything to get done asap, so the database
can be up -- so it is done with a bit more "vigor".

Also, you can have it do selective block recovery as well -- meaning that the
database opens right after the rollforward and as processes hit locked blocks --
the are rolled back on demand -- something we only do during the recovery
processing during startup.  It gives the appearance of "everything is ready to
go" even though it is not.

October 06, 2003 LOUIS -- Thanks for the question regarding
      "ora-00164 AUTONOMOUS TRANSACTION DISALLOWED WITH DISTRIBUTED
      TRANSACTION", version 8.1.6

      You Asked
Hi there,

thanks before hand for any help. I am getting this
error message when I am trying to insert a record
thru db link. ORA-00164 AUTONOMOUS TRANSACTION
DISALLOWED WITH DISTRIBUTED TRANSACTION. The
environment is os/390.

L. Lugo

      and we said...
The error message says it all.

Autonomous transactions are not supported, do not work, in distributed
transactions.

There was an issue in 815 where an autonomous transaction under certain
conditions was accidently allowed to execute but usually it would fail with an
internal error.  In 816, they are always detected and failed immediately.
Review &
      Followup

      Rating: 3
Erros comes
      Even while not instering ..?  July 08, 2002

Reviewer:  Sulinder Singh  from S'pore
Hi,
Tom I get a similar error when fetching records from a distrubuted database and
inserting in the the calling DB, i am using Oracle 9.0.1

          Followup:

let us see the example.

Your followup is somewhat confusing.  Subject says "error comes even while NOT
inserting" but the body says "... and INSERTING in the calling DB".  Which is
it?

      Rating: 2
Fetchign
      records  July 10, 2002
Reviewer:  Sulinder
      Singh
Hi Tom,
Sorry about the previous insufficient question,
I get the error after fetching the records also, i had a look in some places, it
says even if there is a select on db_link it becomes a distirbuted transaction.

To give u the entire picture i am using some selectes to fetch the data in job
from some remote db using db link, after fetching i explicity commit the
transactions and then start a autonomous transaction in a another function.

eg
procedure a
fetch data from dblink
commit ;
sp_reinstatejob(v_return)
commit/rollback depending on v_returns values

procedure sp_reinstatejob
v_jobno number
pragma autnomous transaction
begin

dbms_job.submit(v_jobno,'a;',future date,null);
commit ;
execption
rollback ;
end ;

While analysing the error that occured in job the
exeution stopped when it reaches sp_resintate_jobs
and give ora-164.

how can i stop the finish the transaction where i have accessed dblink and start
a fresh one with autonomoys,
givign a commit/rollback after the select on dblink should have worked.
can u tell me what have i done wrong here, dont consider the sytax erros with
the script i written above, coz what i hv is a really logn script didn't want to
paste the entire thing here.

          Followup:

Well, without the entire real thing i won't comment (i won't tell you how many
times people post "extracts" that look and do NOTHING like the real one does).

try execute immediate 'alter session close database link ...' after the commit,
if in fact there is a commit.

      Rating: 5
ORA-00164 my
      user is getting I am not why?  August 13, 2002

Reviewer:  P from LA  from LA
hi tom
we have a package which calls some functions (autonomous) to create or alter
users. functions are autonomous because they execute DDL statements.
now when I call this procedure which inturn calls these functions my user gets
ORA-00164 error
If I do the same I dont why?????
--------
I tried copying my code here but it gives error so cannot copy my code here

          Followup:

> oerr ora 164
00164, 00000, "autonomous transaction disallowed within distributed transaction"
// *Cause:  A request was made by the application to start an autonomous
//          transaction when the application was already in a distributed
//          transaction.
// *Action: Roll back or commit the current distributed transaction first.

are they in a distributed transaction per chance?

what do you mean "but it gives error"?

      Rating: 5
Error
      Handling...  June 27, 2003
Reviewer:  Kamal
      Kishore  from New JErsey, USA
Hi Tom,
We were thinking that we could use this autonomous transaction feature to log
errors/warnings even if the main transaction commits or rolls back. But we get
this error when the transaction spans more than one database.

We currently use the mechanism like this to do logging:
1). PL/SQL package procedure runs in DBMS_JOB queue listening for log messages
on DBMS_PIPE.
2). Applications interested to log messages call another procedure inside this
package to log the error.
3). This procedure sends the message to same pipe via DBMS_PIPE.
4). The procedure in DBMS_JOB queue reads the log message, saves it (commit) and
returns success response back into the reply pipe.
4). The calling application then reads the response sent back to see if the
logging was successful.

We were trying to replace this mechanism with the use of AUTONOMOUS TRANSACTION
but it does not appear to work with distributed transactions.

Is there another way to be able to do error/warning logging without using
AUTONOMOUS TRANSACTION in this case?

(using either C or JAVA is not an option at the client side).
Thanks,

          Followup:

did you consider utl_file?

      Rating: 5
quick subject
      change  June 27, 2003
Reviewer:  John
       from MA
Whatever happened to aricles II - V on autonomous transactions?

http://asktom.oracle.com/~tkyte/

          Followup:

they killed the online magazine I was writing for "ideveloper" at the time (it
was 2000, dot bomb) and I wrote a book instead

      Rating: 5
did you
      consider utl_file?   June 28, 2003
Reviewer:
      Kamal Kishore  from New Jersey, USA
Hi Tom,
We have a set of web based status screns that read data from these error tables
to show the status and messages (errors/warnings/success) generated by the jobs
running on a particular day for a given application.

If we use UTL_FILE the error/warning/message logging will become easier but
these web based status screens will no longer work.

I was thinking that since we are already moving to Oracle9i, maybe we could use
UTL_FILE to log messages such that they are in the format required by the status
screens and then using the EXTERNAL TABLES features, provide this data to the
status screens as usual.

The problem will be, these EXTERNAL TABLES will be very limiting for such status
queries (no indexes and such), so could we create MVs of these and the status
screens will first initiate a REFRESH and then query the status to show the
status to the managers.

What is your take on this?

Thanks,

          Followup:

you could move your error handling to the top level, run the process, catch the
error, rollback, log it, commit it, and re-raise it.

      Rating: 5
Is ORA-00164
      lifted in 9.2.0.3  October 06, 2003
Reviewer:
      Pushparaj Arulappan  from NJ, USA
Tom,

Does the "ORA-00164" restriction is gone in Oracle9.2.0.3
version. I am not receiving this error when I did the following test which
involves a distributed transaction.

In one instance DB1,

DB1: SQL> CREATE TABLE test_auto (col1 number, col2 number)
/
Table Created.

DB1: SQL> CREATE OR REPLACE PROCEDURE s_auto
(nCol1 IN NUMBER, nCol2 IN NUMBER)
IS
    PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
    INSERT INTO test_auto VALUES (nCol1,nCol2);
    COMMIT;
END;
/

Procedure Created.

In another instance DB2,

DB2: SQL> CREATE SYNONYM s_auto FOR s_auto@DB1.ORACLEDB.COM
/
Synonym created.

DB2: SQL> EXEC s_auto (11,11);

PL/SQL procedure successfully completed.

I am planning to implement the autonomous transaction feature in our application
which involves distributed transactions and can you please confirm the
autonomous transaction is now available in a distributed environment.

Thanks
Pushparaj

          Followup:

they removed the restriction in 9.0.2

October 06, 2003 Pat -- Thanks for the question regarding
      "sql%rowcount = 0 for an update ", version 8.1.7

      You Asked
Hi Tom,
    I have a question: I need to update a row matching a certain ID(its
uniqueness is enforced) in a table within a procedure. Normally you'd have to
    begin
        check if a row with that id exists
        if yes then
            update
    exception
        no_data_found exception raised if no such id
    end;

    I did it this way:
    begin
        update row where id = theid#;

        -- since update statement will not raise no_data_found exception
        -- this is Oracle8i so I don't have the merge feature
        if sql%rowcount = 0 then
            insert the new row into table
    exception
        catch other exceptions
    end;

Do you see any problem with the 2nd approach? If yes, why is it?

Thanks for your help.

      and we said...
the second way is the only correct way to do it!

Never "select to see if it is there to update", just "update and see if it found
something"
Review & Followup

      Rating: 5
Any reason why
      the second approach is the right approach  October 06, 2003

Reviewer:  Bala  from USA
Thanks

          Followup:

why

a) look to see if it exists and incurr the work involved just to
b) update it

if you can

a) update it

???

      Rating: 5
Thanks for the
      answer  October 07, 2003
Reviewer:  Bala
       from USA

      Rating: 4
Tom is always
      the best!  October 08, 2003
Reviewer:  Pat

October 07, 2003 Ranjan -- Thanks for the question regarding "How
      to get the port", version 8.1.7

      You Asked
Hi Tom,

Oracle database is running on some particular unix box.  From the database can
we get the port number. From v$instance i can get the hostname and instance
name. The purpose of getting the port number is to create a database link
dynamicaly using the instance and port number so that we don't have to
configured the TNS entry on the client  machine.
Thanks
Ranjan

      and we said...
You cannot get the port -- the port is not necessarily known to the database.

The listener need not be running on the same machine with the database, a single
listener might be servicing many databases.

A database may have many listeners servicing it.

You might be interested in Oracle Names -- a names server that can run to serve
up this information to interested clients.

see

http://technet.oracle.com/doc/oracle8i_816/network.816/a76933/products.htm#439800
...
Review
      & Followup

      Rating: 3
How can I
      change Oracle 9i port number in Windows 2k  January 24, 2003

Reviewer:  Susie Maxwell  from TX
My oracle port number default to 80, my IIS5 port number is 81, I want to
disable Oracle port number, then change IIS5 port to 80, how can I do that?

          Followup:

port 80, sounds more like http then Oracle net?

anyway, no idea what software you are running on port 80, oas, ias, just apache.
 assuming apache -- shutdown the service, edit the apache config files change 80
to whatever and restart.

      Rating: 4
where can I
      find Apache config file  January 24, 2003

Reviewer:  Susie Maxwell  from TX
Would you please tell me where is Apache config file? Thanks!

          Followup:

anyone have a windows install about?  i don't....

on my unix boxes it is in the Apache/Apache/conf/httpd.conf file.

      Rating:
      5
Suggestion  January 25, 2003
Reviewer:
      Riaz Shahid  from Pakistan
You can disable running apache server from services of windows....just disable
the apache server to be run and that port will be free.

Riaz

      Rating: 5
I know how to
      change Apache config  January 27, 2003
Reviewer:
      A reader
Thanks very much! It works!
In Windows 2000, the standard config files are located in the
c:\oracle\ora90\apache\apache\conf\httpd.conf. You can change port from here.
And also you can start up and shut down the server from the Start menu. Click
Start | Programs|Oracle - OraHome90 | Oracle HTTP Server and choose the
appropriate shortcut. You can also manage the HTTP service from teh Service
applet.

      Rating:
      3
Vague  May 06, 2003
Reviewer:
      Victor  from Russia
I can't understand , if listener is on one mashine and database on the other,
how do they communicate?
And I've read that PMON asks listener port,
if port is unknown what does PMON ask ?
May be I mislead ?
Thanks.

          Followup:

we communicate over a network.

PMON uses  the default 1521 address on the local machine, if the listener is not
there, it does not register itself.

If 1521 on the local machine is "wrong", you use the init.ora parameter
local_listner to tell it where it really is.

      Rating: 4
is this still
      true for 9.2?  July 01, 2003
Reviewer:  Mark
      Rem  from Ashburn, VA USA
Thanks for your initial response to this question.  However, is it still true in
9.2 there is no place in the data dictionary where Oracle stores the port number
users have connected via?

I find it hard to imagine how Oracle knows where to send responses.  In a
dedicated server environment, when Oracle wants to send data to a server
process, how does Oracle know where the server process is ((which port it is
waiting on)?
-Mark

          Followup:

the client is connected to the dedicated server -- they both "know" the port.

The dedicated server attaches the SGA, reads datafiles.  That is how the pieces
talk.  tcp is between the client and the dedicated server.

In shared server, the client connects to a dispatcher on a port.  They both
"know" that port.  The dispatcher and shared server use a queue in the SGA to
communicate.

"oracle" as a whole doesn't need to know.

      Rating: 3
Need to
      know  July 01, 2003
Reviewer:  Richard
       from Reston VA
I see your point that oracle 'doesnt need to know' however if my customer needs
to know, how can I record the port number which was used in the connection?  Are
you saying Oracle does not record any port specific info in the data dict?  It
seems like a good piece of info to have if you deploy an application that uses
different SSL and non-SSL ports and needed to monitor how users are connecting.

          Followup:

if they use dedicated server, it is easy -- they use the port the listener is
on.

if they use shared server, they connect to a dispatcher.  dispatchers can be
told what ports to use or they optimally use wildcard ports.

I don't understand where the "different ssl and non-ssl" ports come into play at
all.

If you have a real world scenario, something you are trying to accomplish, feel
free to try and ask that -- seems like we are dancing around here.  Why would
they care what port i used last week?  of what use is that information?

If you need to see how users are connecting to the database (ssl, non-ssl,
whatever) you certainly do not need to see the port.  We can tell you how they
connected (audting does that)

      Rating: 5
Could you
      demonstrate how?  July 01, 2003
Reviewer:
      Reader
Tom,

"If you need to see how users are connecting to the database (ssl, non-ssl,
whatever) you certainly do not need to see the port.  We can tell you how they
connected (audting does that)"

Could you please let us know how the above could be accomplished? Thanks.

          Followup:

SQL> audit connect;

and then the comment$text in sys.aud$ will have the protocol (and apparently the
client PORT as well....)

sys@ORA920> /

COMMENT$TEXT
---------------------------------------------------------------------------------
--------------------------------------------------
Authenticated by: DATABASE; Client address:
(ADDRESS=(PROTOCOL=tcp)(HOST=138.1.120.255)(PORT=35837))
Authenticated by: OS
Authenticated by: DATABASE; Client address:
(ADDRESS=(PROTOCOL=tcp)(HOST=138.1.120.255)(PORT=35838))
Authenticated by: DATABASE; Client address:
(ADDRESS=(PROTOCOL=tcp)(HOST=138.2.5.52)(PORT=60004))
Authenticated by: DATABASE
Authenticated by: OS
Authenticated by: DATABASE; Client address:
(ADDRESS=(PROTOCOL=tcp)(HOST=138.2.5.52)(PORT=60037))
Authenticated by: DATABASE

8 rows selected.

On the server, they were all connected to 1521 when using tcp (the protocol is
what would be of interest to you)

      Rating: 4
Good response
      and follow q&a.  July 02, 2003
Reviewer:
      MarkRem  from Ashburn
thanks TK, et. al. for your insights.  You've answered all my questions.

For Richard, here's my 2 cents on getting the port number for audit purposes.
When a user's request hits the listener, only the listener's log file records
which port the user's request came in on.  Any port info known to the database
would be the redirect port, which is of no use in an audit of user activity.
However, you could use package utl_file to read the contents of file
listener.log into a dummy audit table, and parse it from there using pl/sql.
Should be very easy to do.

      Rating: 3
RE: Need to
      know  July 03, 2003
Reviewer:  Richard
       from Reston, VA
Currently we are running a two node RAC cluster with a listenr on each node
supporting two ports SSL and non-SSL.  We have to audit everything, even what
ports are used for each connection.  We had to lock down our non-SSL port with
IPSEC since the portal we are using only supported ODBC via 1521 (non SSL).
Even with all this security we still have to monitor what ports 'specific' users
are coming in on for auditing purposes.  We are also using Selective Audit,
which does not monitor what port is being used so that is suprising.  I see your
point, just making the comment that it would be nice if port info was recorded
in DD somewhere such as V$session.

Mark Rem - Good advice, I seemed to remember hearing that somewhere else . .

      Rating: 5
Follow-up on
      port  October 06, 2003
Reviewer:  Rory
      Concepcion  from Philippines
Hi Tom,

 Congrats on your new book. Havent gotten it yet coz it has
not arrived the Philippines yet. But I've already ordered
thru some relatives in the US.

  How do I determine if the port for httpd which is "8001"
is listening? I forgot the linux command that shows if that
port is open. Should there be an error when starting the
services if that port is down? thanks

          Followup:

well, you can just

telnet hostname 8001

or you can use netstat to see whats on what.

October 07, 2003 Tony -- Thanks for the question regarding "Flush
      shared pool", version 8.1.7

      You Asked
Hi,
   1. I use Auto Trace/ Explain Plan to tune SQLs. During this process, Do I
have to flush out the shared pool evertime before executing my query to find the
correct response time?
( Alter system flush shared_pool )? what will be the impact if I don't do so?

   2. My production database is in dedicated  mode and has hundreds of
concurrent users. Will the peroformance improve if its changed to MTS mode?

      and we said...
1) no, in the real world -- queries WILL be soft parsed -- never hard parsed
once the system is running.

In fact, you need to run the query once without tracing to get the accurate
response time -- the real world one!

2) doubt it -- if you have my book "Expert one on one Oracle" -- all of the
details are there.  Basically MTS is *slower* then dedicated server.  The code
path is alot longer.  MTS is used when you run out of resources (processes, ram,
whatever) on the machine and can no longer get people logged in.  Then you drop
down to MTS to let more people get connected at the same time.
Review &
      Followup

      Rating: 3
What about the
      data? Is it fetched afresh everytime?  November 07, 2002

Reviewer:  Tony  from India
When I first execute a query, data for the query is fetched and kept in the
buffer. Now I slightly modifiy the same query and run. This time the query
appears fast as the required data is already kept in the buffer and no need to
fetch afresh from the disk. Hence, to know the correct performance of a query,
we need to flush out the data from the buffer. I think you'll agree with my
argument.

          Followup:

Hence in the real world you'll have many users and data will be buffered or not.

Your goal is to make the query

o use as little cpu time
o do as few consistent gets

as possible -- beyond that, it doesn't matter for testing if the data is
buffered or not buffered.  In the real world -- it may well (or may well not) be
buffered.  If you minimized the cpu and consistent gets (logical io's), there
you go, you have it tuned.

(besides, flushing the shared pool would have no effect on physical io with
respect to the query in question -- it would just make you hard parse again)

      Rating:
      5
flush  December 12, 2002
Reviewer:
      mo
TOm:

Do I need to log in as a system manger to flush. I am using a developer account?

SQL> alter system flush shared_pool;
alter system flush shared_pool
*
ERROR at line 1:
ORA-01031: insufficient privileges

          Followup:

you need to use an account that has the ALTER SYSTEM privilege, yes.  I never
use system...

      Rating: 5
alter
      system  December 13, 2002
Reviewer:  mo

Tom:

would this privilege be risky for a developer. It seems dba is worried about it.
 can you make it only for flushing pool in a development instance?

          Followup:

DBA is correct to be worried.

Developer should be using this only on their own personal test instance they
have running on their own personal machine (desktop).

This command should not be used on a system others are using -- no point in
doing that.

      Rating:
      5
flush  December 13, 2002
Reviewer:
      mo
Tom:

well how do you do query optimization and tuning if you can not flush.  Let us
say as in your book you want to time a pl/sql program with bind variables and
without bind variables.  You run the query, which puts it in shared pool. so
second time your timing test would not be accurate because it is running it from
cache(soft parse) rather than doing a hard parse which is what happens first
time they use it in a production environemnt.

          Followup:

You do it on your OWN personal machine is how.

You do not do destructive testing like that on a shared machine.

It is as I said:

Developer should be using this only on their own personal test instance they
have running on their own personal machine (desktop).

      Rating:
      5
flush  December 13, 2002
Reviewer:
      mo
Tom:

You misunderstood me.  I want to learn from your book and apply the examples in
real life systems.

What I am saying is that let us say I have a query in real-production system
that is taking too long. So I set timing on in my development instance to
exactly measure how long it will take the user first time he runs it. I can not
do it unless I flush the shared pool because all subsequent tests all would be
soft parse?

BTW, I do not know why you use "SET TIMING ON" and "dbms_utility.get_time" to
get two time counts?

Thanks,

          Followup:

so, the hard parse will be a small, irrelvant fraction of the total runtime of
the query.

Besides, in the real world, your end user is also doing a soft parse.  In fact,
you should run the query -- ignore the results and then run it again.  That
second run is what you want to work with.

You, the developer (anyone really) shouldn't be flushing the shared pool on a
shared machine.  flushing the shared pool isn't necessary here -- ESPECIALLY in
the case you describe.  It would introduce artificial work that quite simply is
not being done in the real world (that guys shared pool in production -- it is
full of all kinds of useful stuff, if you empty yours, you have just influenced
the test in the wrong way)

The examples in the book -- all done on single user machines.  99% of them do
not have a flush shared pool.  The ones that do -- showing you the effect of
parsing thousands of literal sql statements without binds.

I generally do not set both timing on and use dbms_utility get time.  get_time
returns timing in hsecs which is nice.  TKPROF is the only source of truth for
timing though.

      Rating:
      5
flushing  December 13, 2002
Reviewer:
      mo
Tom:
<the hard parse will be a small, irrelvant fraction of the total runtime of the
query>

If this is correct why then use bind variables. I thought you always said to use
bind variables to get better performance. Is it irrelevant?

<Besides, in the real world, your end user is also doing a soft parse.  In fact,
you should run the query -- ignore the results and then run it again.  That
second run is what you want to work with>

Not true if you are not using bind variables.  Query could be hardcoded so you
are always hard parsing.  Why also not worry about the first run since user will
run it first time. The shared pool can also get fulshed by starting the instance
or oracle processes, so user may be running it for first time several occasions?

thanks,

          Followup:

sigh, you haven't read the book then...

or, I didn't explain it very well.

don't know which one.

Think about it -- look at your question.  You said  "I have a query in
real-production system that is taking too long."...  Too long to me means more
then 1 second.  A query should parse in less then 1 second, your query is taking
many seconds.  As a pct of the run time -- that hard parse is NOTHING in your
case (that is simple math, just think it thru.  simple math)....

Now, parsing -- especially hard parsing -- takes LATCHES on the library cache,
the shared pool (you know, those things I keep pointing out time and time again
with runstats).  Latches are locks, locks are serialization devices,
serialization devices inhibit scalability....   Ok.

In the real world -- your end user IS doing a soft parse -- why?  because you
read my book, you followed the cardinal rule "use bind variables" -- hence, your
user IS doing a soft parse -- ok?....

Don't mince words.  Just remember this:

o you can flush the shared pool on your own personal instance running on your
own personal machine.

o you cannot flush the shared pool on a shared instance, why -- because i said
so.  that is something for the DBA to do and I cannot even imagine why they
would.  You flush the shared pool to do runstats type testing but that CAN ONLY
be done on a single user machine -- else all of the other users influence the
latch counts and the test is USELESS

o in an system where you run the same query over and over and response times for
that query are small (eg: 99% of all systems -- you are OLTP'ish) -- you WILL
USE BIND VARIABLES.  How many articles on this site have you personally read mo
that start with "my system is going really bad, lots of latching, ora-4031's, i
have to flush the shared pool 15 times an hour, whats wrong".  The answer is
always, constantly, consistently BIND VARIABLES. so, if you run many queries per
second -- BIND VARIABLE = YOUR ONLY CHANCE OF SUCCESS.

o if you are building a data warehouse where you take MANY SECONDS per QUERY
(the total REVERSE) you don't have to and may not WANT to use bind variables.
Here you have used histograms and other features and you want the optimizer to
come up with different plans for the query:

  select * from t where x = 5;

then for

  select * from t where x = 6;

one should use an index, the other a full scan.  if you use bind variables, that
isn't going to happen.

      Rating: 4
Hard Parse/Soft
      Parse  December 14, 2002
Reviewer:  Sormi
       from Australia
Would you please explain Hard Parse and Soft Parse?

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2588723819082

      Rating: 3
Is it
      practical?  December 14, 2002
Reviewer:
      Tony  from India
Tom, you say that we need to use personal, single user database to tune queries.
Here, in our company, around 50 developers are working on a project. Is it
advisable/feasible to install oracle in all 50 machines?. what if centralized
development database and client machines for developers have different
configuration? Difference in configuration won't give different result? just
curious to know.

          Followup:

Ok, let me be a little more clear.

I have a database on every computer I work on.  It is a scratch database.  If it
gets toasted, big deal.  I run DBCA and create a new one.  This database is what
I use to test different approaches on (mini snippets of code).  I use things
like runstats

http://asktom.oracle.com/~tkyte/runstats.html

on it (something that you CANNOT use on a multi-user machine).  I do destructive
things on it that I would not want to do on a development instance (the "what
if's" of the world).  I feel free to play with any system setting I want.  I can
do whatever.

(as a side note, I've been taking polls at seminars.  did you know -- informally
-- almost every DBA started as a developer.  Wonder how they got to know the
workings of Oracle if they didn't run Oracle - just a thought for the developers
aspiring to be DBA's some day, how much of a leg up would it be if you actually
ran the database, even in this modest fashion....)

Do I develop on this machine?  Typically no.  I use it to test ideas,
sometimes I export the system I'm working on and import it locally to work on it
or tune some really gnarly queries (you know -- if you can get them fast on your
laptop -- they generally go even faster on your big box).  But at the end of
the day, the developed code does exist until it is running on the development
box with all of the other code written by the other people in that
environment.

Then there is the TEST box which must be upgrade from the upgrade scripts that
are provided from the DEV box.  Then there is PROD that uses these fully tested
upgrade scripts....

So, the point I think most are missing in this thread is the database on the
developers machine doesn't count, it is just there for learning, testing,
trying out different approaches, finding out what works - what doesn't and
breeding the next generation of DBA's

      Rating: 3
Licence
      issue  December 15, 2002
Reviewer:  Tony
       from India
I agree with your argument Tom, I've some practical issues.

1. My company doesn't allow developers to install Oracle in all developers'
machine due to licence issues. Is there really any licence issue for Oracle used
for development purpose?

2. All databases in all devepoers' machine should be in sync with centralized
development database as large number of objects are added frequently.

          Followup:

If you goto otn.oracle.com and click on download/database and read the license
(as you must before you download), you'll find:

<quote>
License Rights
We grant you a nonexclusive, nontransferable limited license to use the programs
only for purposes of developing and prototyping your applications, and not for
any other purpose.  If you use the applications you develop under this license
for any internal data processing or for any commercial or production purposes,
or you want to use the programs for any purpose other than as permitted under
this agreement, you must contact us, or an Oracle reseller, to obtain the
appropriate license.  We may audit your use of the programs.  Program
documentation is either shipped with the programs, or documentation may accessed
online at
http://otn.oracle.com/docs
Ownership and Restrictions
We retain all ownership and intellectual property rights in the programs.  The
programs may be installed on one computer only, and used by one person in the
operating environment identified by us.  You may make one copy of the programs
for backup purposes.

You may not:
·use the programs for your own internal data processing or for any commercial or
production purposes, or use the programs for any purpose except the development
and prototyping of your applications;
·use the applications you develop with the programs for any internal data
processing or commercial or production purposes without securing an appropriate
license from us;
·remove or modify any program markings or any notice of our proprietary rights;
·make the programs available in any manner to any third party;
·use the programs to provide third party training;
·assign this agreement or give or transfer the programs or an interest in them
to another individual or entity;
·cause or permit reverse engineering or decompilation of the programs;
·disclose results of any program benchmark tests without our prior consent; or,
·use any Oracle name, trademark or logo.
</quote>

grab it, use it.  just don't deploy with it.

2) the database on the developers machine would NOT be the database to do your
primary development on -- it is there to test with, play with, do things like
runstats with.  Sure, you'll export DEV objects every now and again to test/add
new functionality -- but the code isn't code until it is checked into the
development environment.

      Rating:
      5
FIRST_LOAD_TIME  July 16, 2003
Reviewer:
      Pushparaj Arulappan  from NJ, USA
Tom,

We have flushed the shared pool today early morning
at 3.00am when there is no activity on the database.

But today I have queried the V$SQLAREA view and found that
many SQLS had first_load_time from the earlier dates.

I had thought the "alter system flush shared_pool", will erase all the SQLs from
the shared pool and hence V$SQLAREA view will only show SQLS that are loaded
after the flush.

SELECT DISTINCT FIRST_LOAD_TIME FROM V$SQLAREA
/
2003-07-13/15:11:41
...
2003-07-14/07:40:49
...
2003-07-15/07:40:49
...
2003-07-16/07:40:49

Please correct me.

          Followup:

it cannot get rid of everything.  flush flushes what it can.  sql being used --
stays.

      Rating: 4
flush shared
      pool  October 07, 2003
Reviewer:  Venkat
       from India
Tom,

When v$sqlarea will get truncated? How about the growth of this table in 24x7
enviornment? When the instance has been running for  quite a long time, how to
find out the latest sql statements from this table?

Regards,
Venkat

          Followup:

it never gets "truncated", it is not even a "real table" -- it is a data
structure, it is the shared pool!

suggest you use v$sql -- v$sqlarea is just a (slower performing) aggregation of
v$sql.

v$sql has a first_load_time you might use to find "latest"

October 07, 2003 Mikito -- Thanks for the question regarding "Is
      materialized view synchronization cheap?", version 9i

      You Asked
Oracle Manual emphasizes the analogy between materialized views and indexes. I
wonder how cheap are materialized view updates? Oracle has a patented algorithm
for materialized view fast refresh. But is it adviseable to use materialized
views outside of Data Warehousing scope?

      and we said...
I would say that there are three cases:

1) MV's that refresh on commit.  These are NOT candidates for OLTP systems.
Why? Predominant one is concurrency.  An MV is usually a rollup.  Say you have
that "sum(sal) from emp" MV.  Upon the update of the EMP table -- every
transaction will SERIALIZE on the refresh of the MV.  It would be a big choke
point.

2) MV's that refresh FULLY on a timed basis for reporting purposes.  these ARE
candidates for use in an OLTP system.  Why?  Because this adds no overhead to
the OLTP system (no MV logs to maintain) and reduces the number of big queries
against the OLTP data making the OLTP system even faster.

3) MV's that refresh FAST on a timed basis.  These PROBABLY are not candidates
for use in an OLTP system.  Why?  Because the MV logs add overhead and OLTP is
characterized by high volume, high speed, short transactions.  It depends on
your needs however with this one.

When we say they are like indexes -- that is just an analogy.  Just as a unique
index on t(x) can speed up "select * from t where x = 5", an MV can speed up
"select count(*) from t" (assume t is a 5 billion row table).

Now indexes can speed up OLTP but, improperly used, they can bring it to a halt
as well (too much maintenance).

Review &
      Followup

      Rating: 4
indexes and
      MVs  September 11, 2001
Reviewer:  D@B.A
       from SF, CA
Wouldn't users see phantoms in case 2? As a consequense, if their transactions
rely upon the data returned by an MV that  is outside of the scope of database
transaction isolation, it could damage their semantics. Allowing MVs into OLTP
means that the transaction scope must include MVs as well (exactly the same as
for indexes).

IMHO, materialised views and indexes are indistinguisheable in case 1. Both
speed up reading access for a single user. Both reduce the total system
throughput, because of additional locking imposed upon indexing structures.

Summarizing, materialized views are just relational indexing structures.

          Followup:

I was refering to the use of MV's in the scope of a reporting system that might
not need "the most current information" so a time based refreshed would be
applicable.

You would not get phantoms - you would get "stale" data "as of a point in time".

Of course if you need the most current information -- you need to use the "real
tables" or a refresh on commit MV.

      Rating: 2
Please Provide
      abbrevation   September 12, 2001
Reviewer:
      mtk  from india
Please Provide abbrevation for all Short term used by u.
I can able to understand this question and answer, if i know
expansion for OLTB.

      Rating: 2
Please Provide
      abbrevation   September 12, 2001
Reviewer:
      mtk  from india
Please Provide abbrevation for all Short term used by u.
I can able to understand this question and answer, if i know
expansion for OLTB.

      Rating:
      1
  September 12, 2001
Reviewer:  Sonali
      kelkar  from Newton MA
OLTP means Online transaction Processing.. you may read about it in oracle book.
MV is Materialized Views..

      Rating: 2
MVs versus
      User-created Aggregate Tables.  July 08, 2003

Reviewer:  Richard  from Oxfordshire, United Kingdom

Hi,

We have a Decision Support System (DSS) that uses our Oracle tables; we are on
version 8.1.5.

For the DSS, we repopulate our fact tables from various data repositories, and
then build aggregate tables, indexes etc. The data in the DSS-related tables
(fact and otherwise) then remain static throughout the working day (barring any
emergency reloads) until the following night's data load process.

The rebuild of the aggregate tables is the most time-consuming part of our
nightly processes, and I wondered if we are missing a trick here by not
employing MVs?

Is this an ideal case for MVs? Are there any quirks we should be aware of? I do
have a copy of "Expert One on One" and have read the chapter on MVs, but would
still be very grateful for any feedback.

Thanks in advance for any help!

          Followup:

if you are creating the aggregates via "create table as select ..." or
populating them via

truncate
disable indexes
insert /*+ APPEND */
rebuild indexes

they'll be "as fast" as an MV, since you are in effect rebuild the database.

If you have the horsepower, employ parallel processing, either in the form of
parallel query OR in the form of "we'll do as much concurrent work as we can --
we'll build 5 of these aggregates at the same time"

      Rating: 4
Complete
      refresh of MV slower than table  July 08, 2003

Reviewer:  Rob  from Pittsburgh, PA
I have found

truncate
disable indexes
insert /*+ APPEND */
rebuild indexes

on a large (300 + M rows) table to be faster than a complete MV refresh because
of the fact that the first thing DBMS_MVIEW.REFRESH does is
truncate the underlying table. The truncate causes any index that you have made
unusable to become usable. This means that you are maintaining any index on the
MV on the fly as opposed to doing a parallel nologging rebuild at the end of the
load.

      Rating: 5
aggregate
      tables vs. MVs  July 08, 2003
Reviewer:
      A reader
Rebuilding the aggregate tables alone does not cause query re-write. You have to
have MVs for query rewrite. You can rebuild the aggreaget table either using a
CTAS or sqlldr and then create an MV on top of it by using the "ON PREBUILT
TABLE" option. This option does not take any time as it will only mark the table
as an MV in the database catalog. However, you will not be able to do DML
operations on that table after the MV is created on it. We have to drop the MV,
update the table and then recreate the MV using pre-built option. Tom, is there
any other shortcut we can use for this?

          Followup:

umm, why would you do DML on it?

      Rating: 4
MVs versus
      Aggregate Tables.  July 08, 2003
Reviewer:
      Richard  from Oxfordshire, United Kingdom
Thanks for the feedback Tom.

We do use parallel processors and rebuild the aggregates as you say.

I don't think we can do much more to speed things up, with the resources that we
have.

      Rating: 5
limits between
      distinct kind of MV  August 13, 2003
Reviewer:
      juancarlosreyesp@yahoo.com
Hi Tom,
As is very difficult to set when a table is in OLTP or DSS.
Please could you give a limit in transactions per minute, in which you suggest
to use all the 3 kind of MV you described.

For example MV on commit, are suggested if you have less than X transactions por
minute.

:) Thank you

          Followup:

not possible to ROT (rule of thumb) something that is so dependent on

o size/spread of data in mv (refresh on commit for a SELECT COUNT(*) FROM T --
where everyone serializes on commit to update the count is very very different
from select deptno, sum(sal) from dept where all serialize at the DEPTNO level)

o size/speed of machine

o complexity of the mv

for example

it is all about BENCHMARKING your situation (you know, *simulation*)

      Rating: 4
MV's that
      refresh on commit  August 13, 2003
Reviewer:
      michael  from San Diego, CA USA
Hello Tom,

You stated:
1) MV's that refresh on commit.  These are NOT candidates for OLTP systems.
Why? Predominant one is concurrency.  An MV is usually a rollup.  Say you have
that "sum(sal) from emp" MV.  Upon the update of the EMP table -- every
transaction will SERIALIZE on the refresh of the MV.  It would be a big choke
point.

My question:
How do you obtain real time data? For example, total number of items as items
are being added? Other than refresh on commit, what method would you suggest to
get accurrate data?

Thanks.

          Followup:

a query?

that is as real time as you get.

      Rating: 5
Thank you
      :)  August 13, 2003
Reviewer:  A reader

      Rating: 4
So Why Use
      MVS?  October 07, 2003
Reviewer:  Richard
       from Oxfordshire, UK
Hi,

A while back, I asked a question in this thread (MVs versus User-created
Aggregate Tables).

Assuming the scenario I described in that question, is it definitely the case
that aggregate tables will perform as well as MVs with similar purpose? I only
ask, as I have read elsewhere that MVs will out-perform aggregate tables.

As ever, thank you for your help.

          Followup:

At the end of the day -- bit for bit, byte for byte -- the would be exactly the
same on disk.

Where MV's are BETTER then DIY's (do it yourself's) is in the transparency.

Pretend for moment that after creating an index -- you had to go out and change
application code to take advantage of it.  You had to train the developes to use
it directly in future code.  You had to train end users to be aware of it in
ad-hoc queries.  Pretend further that next week you created an infinitely BETTER
index and now you have to go recode, retrain, reimplement.

It would not be workable, no one would stand for it.  Indexes should be
transparent -- the best one used at the right times.

Well, MV's are the indexes of your data warehouse/reporting system.  They should
be transparent to the application.  Application queries "details", system
automagically rewrites queries to use summaries.  Add a new summary and wa-lah,
applications start taking advantage of it *transparently*.  Just like an index.
Additionally, just like an index -- you have dependencies set up in the data
dictionary -- you can see what the MV relies on, is dependent on.  You can see
if it is STALE or FRESH.  The system maintains it.

The advantages of MV's over DIY's are numerous -- from transparent performance
benefit (no recode, no train/retrain), to maintainability, to documentation...

October 07, 2003 Dan -- Thanks for the question regarding "NOT IN
      in SQL", version 8.1.7

      You Asked
Tom,

    How bad will be the performance of query if NOT IN is used.Is there any
other way of writing a query not using NOT IN.

Thanks,
Dan

      and we said...
Well, it depends.

If you are one of those people "afraid" of the big bad CBO, it could be very
bad.  If you are not -- you will see the best of all possible performance using
it.  Take this script:

create table t1 as select * from all_objects where rownum <= 10000;
create table t2 as select * from all_objects where rownum <= 9950;
create index t2_idx on t2(object_id);

alter session set sql_trace=true;
select count(*)
  from t1 rbo
 where object_id not in ( select object_id from t2 );

select count(*)
  from t1 rbo
 where NOT EXISTS (select null from t2 where t2.object_id = rbo.object_id )
/
select count(*)
  from t1, t2 rbo
 where t1.object_id = rbo.object_id(+)
   and rbo.object_id IS NULL
/

alter session set sql_trace=false;
analyze table t1 compute statistics
for table
for all indexes
for all indexed columns;
analyze table t2 compute statistics
for table
for all indexes
for all indexed columns;
alter session set sql_trace=true;
select count(*)
  from t1 cbo
 where object_id not in ( select object_id from t2 );

select count(*)
  from t1 cbo
 where NOT EXISTS (select null from t2 where t2.object_id = cbo.object_id )
/

select count(*)
  from t1, t2 cbo
 where t1.object_id = cbo.object_id(+)
   and cbo.object_id IS NULL
/

now, lets look at the NOT IN performance of RBO vs CBO:

select count(*) from t1 rbo where object_id not in ( select object_id from t2 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2     17.55      18.69        134     735899          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4     17.56      18.69        134     735899          0           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=735899 r=134 w=0 time=18696501 us)
     50   FILTER  (cr=735899 r=134 w=0 time=18696326 us)
  10000    TABLE ACCESS FULL T1 (cr=138 r=134 w=0 time=69425 us)
   9950    TABLE ACCESS FULL T2 (cr=735761 r=0 w=0 time=18393649 us)
********************************************************************************
select count(*) from t1 cbo where object_id not in ( select object_id from t2 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.13       0.14          0        161          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.13       0.15          0        161          0           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=161 r=0 w=0 time=146502 us)
     50   HASH JOIN ANTI (cr=161 r=0 w=0 time=146422 us)
  10000    TABLE ACCESS FULL T1 (cr=138 r=0 w=0 time=16401 us)
   9950    INDEX FULL SCAN T2_IDX (cr=23 r=0 w=0 time=16219 us)(object id 34305)
********************************************************************************

Well, that is startling -- isn't it?  The RBO full scanned the T2 table
100,000 times!  The CBO, will, once.  It was slightly faster... Looking at other
ways you MIGHT be able to write a NOT IN:

select count(*) from t1 rbo
 where NOT EXISTS (select null from t2 where t2.object_id = rbo.object_id )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.22       0.21         22      20138          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.22       0.21         22      20138          0           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=20138 r=22 w=0 time=217043 us)
     50   FILTER  (cr=20138 r=22 w=0 time=216954 us)
  10000    TABLE ACCESS FULL T1 (cr=138 r=0 w=0 time=24927 us)
   9950    INDEX RANGE SCAN T2_IDX (cr=20000 r=22 w=0 time=109100 us)(object id
34305)
********************************************************************************
select count(*) from t1 cbo
 where NOT EXISTS (select null from t2 where t2.object_id = cbo.object_id )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.03          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.19       0.19          0      20138          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.19       0.22          0      20138          0           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=20138 r=0 w=0 time=192747 us)
     50   FILTER  (cr=20138 r=0 w=0 time=192643 us)
  10000    TABLE ACCESS FULL T1 (cr=138 r=0 w=0 time=22106 us)
   9950    INDEX RANGE SCAN T2_IDX (cr=20000 r=0 w=0 time=94977 us)(object id
34305)

that is a tie -- but NOTE that this is NOT the same as "not in".  To see why
figure out why this two queries return different answers:

scott@ORA920> select count(*) from emp where empno not in ( select mgr from emp
);

  COUNT(*)
----------
         0

scott@ORA920> select count(*) from emp
  2  where not exists ( select null from emp e2 where e2.mgr = emp.empno );

  COUNT(*)
----------
         8

and think about NULLS..

And a third way:

select count(*)
  from t1, t2 rbo
 where t1.object_id = rbo.object_id(+)
   and rbo.object_id IS NULL

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.20       0.23          0      10161          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.20       0.23          0      10161          0           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=10161 r=0 w=0 time=231584 us)
     50   FILTER  (cr=10161 r=0 w=0 time=231495 us)
  10000    NESTED LOOPS OUTER (cr=10161 r=0 w=0 time=216922 us)
  10000     TABLE ACCESS FULL T1 (cr=138 r=0 w=0 time=23132 us)
   9950     INDEX RANGE SCAN T2_IDX (cr=10023 r=0 w=0 time=122286 us)(object id
34305)
********************************************************************************
select count(*)
  from t1, t2 cbo
 where t1.object_id = cbo.object_id(+)
   and cbo.object_id IS NULL

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.16       0.16          0        161          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.16       0.17          0        161          0           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=161 r=0 w=0 time=168176 us)
     50   FILTER  (cr=161 r=0 w=0 time=168094 us)
  10000    HASH JOIN OUTER (cr=161 r=0 w=0 time=154291 us)
  10000     TABLE ACCESS FULL OBJ#(34303) (cr=138 r=0 w=0 time=15475 us)
   9950     INDEX FULL SCAN OBJ#(34305) (cr=23 r=0 w=0 time=16363 us)(object id
34305)

here again, the CBO rules -- but note that this is really just emulating the
first NOT IN and is not nearly as clear to the reader what it is doing....

So, not in is not something to be afraid of....
Review &
      Followup

      Rating: 3
Not
      clear...  January 29, 2003
Reviewer:  Kashif
       from Houston, TX
Hi Tom,

I'm not sure how you concluded that t2 will be full scanned 100,000 times. Do
you mean 10,000? Also, how are you able to tell looking at the tkprof that t2
was full-scanned more than once? I would have thought that both t1 and t2 would
be full table scanned once, and then their results compared. Is that not true?
If there are any questions on your forum you might have previously answered
explaining this or similar questions, please provide me with the link to that
question. Thanks in advance.

Kashif

          Followup:

yes, 10,000 not 100,000 sorry about the extra zero.

the query plan tells me so...

the consistent gets backs it up -- the ONLY way they could be that high is to
full scan that table over and over.

That was my point -- the RBO is very rudimentary here.  It full scans T1 and
applied a FILTER on T2 by full scanning T2 to see if the value was "not in
t2"....

      Rating:
      3
  January 30, 2003
Reviewer:  Mike
       from TX
We have seen HASH join very expensive on hugh tables (>1,000,000 rows) as
comparison to the NL

          Followup:

ever the contraryian, suggest you read:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6749454952894
there you will see the evil NL join literally killing your performance (but,
giving you a very good cache hit ratio if you like that sort of stuff)

and tell you what -- take this example, extrapolate the data upto 1+ million
rows and give it a whirl....

      Rating: 5
Different
      conclusion...  October 06, 2003
Reviewer:
      Kashif  from Reston, VA
Hi Tom,

I happened to revisit this question and tried out the test you had presented in
your answer. I wanted to ascertain whether the drop in consistent gets when
using the CBO (in your second query) was as a result of the reduced number of
full scans of table t2. It turns out, however, that the drop in consistent gets
was as a result of the index that was added; table t2 was scanned the same of
number of times in both the RBO and CBO environments, per my testing. Here are
my results:

/* My script: I did not include the exists and outer join
alternatives to NOT IN */

create table t1 as select * from all_objects where rownum <= 10000;
create table t2 as select * from all_objects where rownum <= 9950;
--create index t2_idx on t2(object_id); --Excluded the index creation for now...

alter session set sql_trace=true;
select count(*)
  from t1 rbo
 where object_id not in ( select object_id from t2 );

alter session set sql_trace=false;
analyze table t1 compute statistics
for table
for all indexes
for all indexed columns;
analyze table t2 compute statistics
for table
for all indexes
for all indexed columns;
alter session set sql_trace=true;
select count(*)
  from t1 cbo
 where object_id not in ( select object_id from t2 );
create index t2_idx on t2(object_id); --Added the index over here.
analyze table t2 compute statistics
for table
for all indexes
for all indexed columns;
select count(*)
from t1 cbo2
where object_id not in ( select object_id from t2 );

Results from tkprof:

Without the index, RBO:

select count(*)
  from t1 rbo
 where object_id not in ( select object_id from t2 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00    1007332    1492552     212229           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00    1007332    1492552     212229           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 1885

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
     50   FILTER
  10001    TABLE ACCESS FULL T1
  10000    TABLE ACCESS FULL T2

Without the index, CBO:

select count(*)
  from t1 cbo
 where object_id not in ( select object_id from t2 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0    1492552     212229           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0    1492552     212229           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 1885

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
     50   FILTER
  10001    TABLE ACCESS FULL T1
  10000    TABLE ACCESS FULL T2

The consistent gets for the query in RBO and CBO modes is the same without the
index, which tells me that the difference in your results was a result of the
added index, and NOT because of the number of times t2 is full-scanned is
reduced. I also threw the indexed query in at the end (I added the index and
then reanalyzed table t2, and ran the query):

select count(*)
from t1 cbo2
where object_id not in ( select object_id from t2 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00        283      20294         33           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00        283      20294         33           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 1885

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
     50   FILTER
  10001    TABLE ACCESS FULL T1
  10000    INDEX RANGE SCAN (object id 298547)

The consistent gets do go down, but the t2 query still gets executed 10,000
times, as proven by this test:

kkashif@DEV> exec dbms_application_info.set_client_info( 0 )

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.40
kkashif@DEV> create or replace function client_info return varchar2
  2  as
  3  begin
  4     dbms_application_info.set_client_info( userenv('client_info')+1 );
  5  --   return userenv('client_info');
  6     return ('A');
  7  end;
  8  /

Function created.

Elapsed: 00:00:00.41
kkashif@DEV> ed
Wrote file afiedt.buf

  1  select count(*)
  2  from t1 cbo3
  3* where object_id not in ( select object_id from t2 where client_info = 'A')
kkashif@DEV> /

  COUNT(*)
----------
        50

Elapsed: 00:00:02.33
kkashif@DEV> select userenv ('client_info') from dual
  2  /

USERENV('CLIENT_INFO')
----------------------------------------------------------------
10000

Elapsed: 00:00:00.50
kkashif@DEV>

So the results indicate that the NOT IN clause does in fact execute the same
number of times using the RBO and CBO. Am I missing something? Thanks.

Kashif

          Followup:

that is NOT a cbo plan you have there -- no cost, no card!

the cbo plan has a hash anti join:

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=161 r=0 w=0 time=146502 us)
     50   HASH JOIN ANTI (cr=161 r=0 w=0 time=146422 us)
  10000    TABLE ACCESS FULL T1 (cr=138 r=0 w=0 time=16401 us)
   9950    INDEX FULL SCAN T2_IDX (cr=23 r=0 w=0 time=16219 us)(object id 34305)

that scans 1 time!  you are looking at the wrong plan.

      Rating:
      4
Interesting...  October 06, 2003
Reviewer:
      Kashif  from Reston, VA
Hi Tom,

The same query in the same exact environment with the same statistics etc.
produces the following plan and statistics, when executed after turning
autotrace on instead of using sql_trace:

kkashif@DEV> set autotrace traceonly
kkashif@DEV> select count(*)
  2  from t1 cbo5
  3  where object_id not in ( select object_id from t2 )
  4  /

Elapsed: 00:00:00.21

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=44 Card=1 Bytes=5)
   1    0   SORT (AGGREGATE)
   2    1     FILTER
   3    2       TABLE ACCESS (FULL) OF 'T1' (Cost=44 Card=500 Bytes=2500)

   4    2       INDEX (RANGE SCAN) OF 'T2_IDX' (NON-UNIQUE) (Cost=1 Card=1
Bytes=5)

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

kkashif@DEV>

As you can see, the query shows the cost, cards etc. in the plan. So I guess now
I have different questions:

Why would there be a difference in the plans shown by autotrace and sql_trace?
Is there a setting in sql_trace that will cause the cost, cards etc. to show up
that I have not set? Thanks.

Kashif

          Followup:

are you actually observing two different plans?

you ran this with sql_trace and get a hash anti join and then run with autotrace
and get a different plan?

I cannot reproduce that behaviour -- but -- is that what you are seeing?

      Rating:
      4
  October 06, 2003
Reviewer:  Kashif
       from Reston, VA
Hi Tom,

Actually, I get the same plan in both the autotrace and sql_trace traces, but
the autotrace produces a plan with the card and cost information, whereas the
sql_trace produces the same plan without the cost and card information:

In SQL_TRACE:

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
     50   FILTER
  10001    TABLE ACCESS FULL T1
  10000    INDEX RANGE SCAN (object id 298547)

In Autotrace:

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=44 Card=1 Bytes=5)
   1    0   SORT (AGGREGATE)
   2    1     FILTER
   3    2       TABLE ACCESS (FULL) OF 'T1' (Cost=44 Card=500 Bytes=2500)

   4    2       INDEX (RANGE SCAN) OF 'T2_IDX' (NON-UNIQUE) (Cost=1 Card=1
Bytes=5)

However, I checked the setting on the always_anti_join parameter, and it was set
as NESTED_LOOPS. So I changed it to HASH and executed the query again. This
time, the plan is similar to your plan (i.e. with the hash anti join), but still
no cost/card info in the trace file!

select count(*)
from t1 cbo5
where object_id not in ( select object_id from t2)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00        275        341         39           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00        275        341         39           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 1885

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
     50   HASH JOIN ANTI
  10000    TABLE ACCESS FULL T1
   9950    VIEW VW_NSO_1
   9950     INDEX FAST FULL SCAN (object id 298547)

The autotrace plan though, again, shows the cost and card info:

kkashif@DEV> select count(*)
  2  from t1 cbo5
  3  where object_id not in ( select object_id from t2 )
  4  /

Elapsed: 00:00:00.31

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=114 Card=1 Bytes=18)
   1    0   SORT (AGGREGATE)
   2    1     HASH JOIN (ANTI) (Cost=114 Card=50 Bytes=900.00000000000
          1)

   3    2       TABLE ACCESS (FULL) OF 'T1' (Cost=44 Card=10000 Bytes=
          50000)

   4    2       VIEW OF 'VW_NSO_1' (Cost=8 Card=9950 Bytes=129350)
   5    4         INDEX (FAST FULL SCAN) OF 'T2_IDX' (NON-UNIQUE) (Cos
          t=8 Card=9950 Bytes=49750)

Strange... If this is becoming too long, I'll wait for when you are accepting
questions and post it as a new question. Thanks!

Kashif

          Followup:

thats accurate -- my "wrong" above.  the STAT records in the trace file won't
have the cost/card -- sorry about that.

only if you use explain=u/p will you get the cost/card in the trace.

In anycase, you discovered "the problem"

      Rating: 5
Got
      it.  October 07, 2003
Reviewer:  Kashif
       from Reston, VA
Hi Tom,

Yep, I now understand why the query was being executed multiple times even in
the CBO environment (always_anti_join was set to NESTED_LOOPS). Though I wonder
why Oracle set the default value for the always_anti_join parameter to
NESTED_LOOPS. Curious to know if you have any explanation/insight. Thanks again
for your insight.

Kashif

          Followup:

in current releases of the software, this is a hidden parameter and has a new
value "choose" meaning it can do what it wants.

in older releases, it defaulted to the way it was first implemented for
backwards compatibility.

October 07, 2003 David -- Thanks for the question regarding "Names
      for Database Links", version 8.0.5.0.0.

      You Asked
Hi Tom,
    I only discovered today that in Oracle8 a database link must
have the same name as the database it is accessing. Is this a
new feature (read limitation) that came in with Oracle8? I’m
positive you always used to be able to give the link any name
you wanted. How do you operate this if you wanted to have two
links to the same database with different access privileges –
say OPERATOR and SUPERVISOR? Firstly, can you tell me I haven’t
gone crazy and that is how it used to be and, secondly, can you
explain the reasoning behind imposing this condition?

Regards,

David.

      and we said...
It has been this way and it depends on the database or session
setting of GLOBAL_NAMES.  (and it really truely is a feature,
not a limitation)...

If global_names is true -- then the database link must be named
AFTER the global name of the database it connects to.  This
prevents ambiguity in a replication environment (which must have
global_names=true) and is in general a good thing to do.  You
can still have many dblinks to the same database -- as long as
they all incorporate the global name (see second point below)

So, what can you do:

o alter session set global_names=false;
  in your session, dblinks no longer have to be the global_name.
  You can use your existing naming convention.  Not recommended.

o leave global_names on but use @ in the dblink name.  Put the
  global name first in the dblink name and then extra stuff
  after an @

For example, I have a database whose global name is ora8i.world.
 I'll create three db links to it -- one won't work since it
doesn't use the global_name, the other 2 will -- they use the
global name and then extra qualifiers after the global name to
distinguish between them -- allowing me to have >1 link to the
same database with different authorizations:

scott@ORA816> create database link wont_work
  2  using 'ora8i.world'
  3  /

Database link created.

scott@ORA816> create database link ora8i.world@current_user
  2  using 'ora8i.world'
  3  /

Database link created.

scott@ORA816> create database link ora8i.world@scott_user
  2  connect to scott
  3  identified by tiger
  4  using 'ora8i.world'
  5  /

Database link created.

scott@ORA816> select * from dual@wont_work
  2  /
select * from dual@wont_work
                   *
ERROR at line 1:
ORA-02085: database link WONT_WORK.US.ORACLE.COM connects to
ORA8I.WORLD

scott@ORA816> select * from dual@ora8i.world@current_user
  2  /

D
-
X

scott@ORA816> select * from dual@ora8i.world@scott_user
  2  /

D
-
X

Review &
      Followup

      Rating: 5
Names for
      Database Links  April 18, 2001
Reviewer:  Tyler
      D. Muth  from Columbus, OH USA
Nice answer Tom.  This one was puzzling Joel and I.

      Rating: 4
Referring DB
      Link inside a procedure  June 26, 2002
Reviewer:
      Janna  from Bangalore - India
Hi,

How to refer DB link inside a procedure ?

insert into emp select * from emp@aaa@test;
is working from SQL PLUS where aaa@test is my dblink

But procedure

create or replace procedure proc_ins_emp
as
begin
 insert into emp select * from emp@aaa@test;
end;
/

Is created with errors mentioning

LINE/COL ERROR
-------- -----------------------------------------------------------------
4/2      PL/SQL: SQL Statement ignored
4/32     PLS-00201: identifier 'EMPAAA@TEST' must be declared

Thanks for your help.

Regards,
Janna

          Followup:

you must need a role for that then.  Roles are not enabled during the
compilation of a stored procedure.  See

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html
for how to test for that (to see that it's the issue)

      Rating:
      4
  July 19, 2002
Reviewer:  RN
       from NJ
From your original response:

<QUOTE>

scott@ORA816> create database link wont_work
  2  using 'ora8i.world'
  3  /

Database link created.

scott@ORA816> select * from dual@wont_work
  2  /
select * from dual@wont_work
                   *
ERROR at line 1:
ORA-02085: database link WONT_WORK.US.ORACLE.COM connects to ORA8I.WORLD

</QUOTE>.

Your 'CREATE DATABASE LINK WONT_WORK ....' commad created a database link named
WONT_WORK.US.ORACLE.COM. Where did the US.ORACLE.COM came from? Is it the
default domain name from sqlnet.ora? Why would that be appended to the database
link name? Is there anyway to create the database link without this appending?

I have a situation like this: I created a dblink (named RASD) pointing to
another server (with db_name and service_name RASD.). It does not, however, work
(gettin ora-2085) as the dblink created on my db has a name RASD.world (world is
my default domain name) and that is not equl to the service name of the remote
DB. The DBA of the remote DB is not ready to change the service name to
RASD.world. And, I am not allowed to change the global_names setting on this db.
What can I do here? Thank you for your valuable response.

RN.

          Followup:

what is the GLOBAL NAME of the remote database.  The service name has nothing to
do with it.

      Rating:
      4
  July 19, 2002
Reviewer:  RN

Global_name=false in remote DB and =true in the db where I want to create the
dblink.

          Followup:

Fine, what is the global name of the remote db.  YOUR DB wants global naming to
be observed.  That is what counts here.

      Rating: 5
db
      links  July 19, 2002
Reviewer:  munz
       from Reston, USA
Tom:

I an using 8.1.7 database and have no problem in creating database links with
any name.  I did not see this limitation.

Create database link anyname
connect to scott identified by tiger
using 'emsd';

can you explain  how am i not limited?

2.  Would there be a difference if a developer account created a link rather
than "PUBLIC" owner.

thanks,

          Followup:

you do not have global_names=true set perhaps.

global naming is what affects this.

      Rating: 5
db
      links  July 20, 2002
Reviewer:  Munz
       from REston, USA
TOm:

1. what about whether is there a difference if a developer created a link or a
dba.

one is owned by "public" and the other by "dev".  would it affect anything?

          Followup:

one can be seen by the developer, the other can be seen by all.

just like a private synonym vs public synonym

      Rating:
      4
  July 20, 2002
Reviewer:  RN
       from Rutherford, NJ
Sorry I confused global_name and global_names!

The global_name of the remote DB is RASD. And I am trying to create a dblink
called RASD. But Oracle won't let me do that - it instead creates a dblink
called RASD.world. Can you tell me why? Since RASD != RASD.world, I am getting
the error.

          Followup:

You can either:

o disable global naming on your database
o give the other database a domain -- so its name is rasd.something.something

once you have a domain, that domain is "there".  Your databases global name has
.world in it.  By default every dblink you create will have .world (or whatever
you  specify when you create the dblink)

      Rating:
      4
  July 20, 2002
Reviewer:  RN
       from NJ
So this means: If you have global_names=true, you can not create database links
pointing outside your database domain? Did I understand it right?

I don't know how I can get around this. The DBA of the remote database is not
willing to change anything - he does not want to touch his 'perfectly running'
database. And my DBA is also reluctant for changing the global_names=true
setting (eventhough he didn't put it..It seems to be there historically....!).
We have several database links in this db (all pointing to databases within our
database domain). Can you think of any negative effect of setting
global_names=false?

Thank you for being there for us even on holidays. We greatly appreciate your
help.

rgds,

RN

          Followup:

sure you can -- your problem is

a) you have a database without a domain
b) you have a database with a domain
c) in the database that has a domain, when you create a link it'll get that
domain by default
d) hence B cannot talk to A with global names since A doesn't have a domain and
B will create a link with a domain

solution:  give A a domain
solution: don't use global names

alter session set global_names = FALSE;

and you are done.

      Rating: 5
db
      links  July 20, 2002
Reviewer:  Munz
       from Reston, USA
Tom:

When you have a web application as stored procedure in the database, how is the
user connecting to the database.

Is it the account defined in the DAD which is either developer or public?

2. If the DAD username is public account then he can not run the database links
created by developer. Is this true?

Thanks

          Followup:

you have to tell me.  I cannot tell you how they connected, only that they are.

no, that is not true.  if the private database link is a "connect to username
identified by password" type (NOT a proxy account), it'll work just dandy

      Rating: 5
db
      links  July 20, 2002
Reviewer:  munz
       from Reston, USA
Tom:

As a followup to the previous message, the user runs a login procedure where he
enters username/password.

However that means he has already logged in since he ran the procedure.  I am
assuming you always loggin in with the userid/password defined in the DAD.
Correct?

          Followup:

yes

      Rating:
      4
  September 06, 2003
Reviewer:  Deepak
      Gupta  from Delhi, India
Hi Tom,

scott@ORA816> create database link ora8i.world@scott_user
  2  connect to scott
  3  identified by tiger
  4  using 'ora8i.world'
  5  /

scott@ORA816> select * from dual@ora8i.world@scott_user
  2  /

D
-
X

base on above example

How the entry will start in tnsnames.ora of server

for example

scott_user.WORLD=(DESCRIPTION=
            (ADDRESS_LIST =(
                ADDRESS = (COMMUNITY = NMP.world)
                    (PROTOCOL = NMP)(SERVER = 192.9.200.250)
               (PIPE = ORAPIPE))(ADDRESS = (PROTOCOL = TCP)
               (Host = 192.9.200.250)(Port = 1521))(ADDRESS = (PROTOCOL = TCP)
               (Host = 192.9.200.250)(Port = 1526))(ADDRESS = (PROTOCOL = TCP)
               (Host = 127.0.0.1)(Port = 1521))
               (ADDRESS = (PROTOCOL = SPX)(Service = host_doc_lsnr)))
               (CONNECT_DATA=(SID=ORCL)
               (SERVER=DEDICATED)
  )
)

is this right (scott_user.WORLD ) ?

          Followup:

ora8i.world

is the tns entry required.

      Rating:
      3
  September 09, 2003
Reviewer:  Deepak
      Gupta  from Delhi,India
Hi tom,

I have tried following way still I am getting error

SQL> select substr(name,1,20) name ,value from V$parameter
  2  where name like 'global_name%'
  3  /

NAME                 VALUE
-------------------- -----------------------------------------------
global_names         TRUE

SQL> set  ARRAYSIZE 1
SQL> select * from global_name;
GLOBAL_NAME
---------------------------------------------------
ORACLE.WORLD

SQL> CREATE DATABASE LINK ORACLE.WORLD@DP
  2  CONNECT TO DPMS
  3  IDENTIFIED BY DPMS
  4  USING 'ORACLE.WORLD'
  5  /

Database link created.

SQL> select * from dba_db_links;

OWNER      DB_LINK              USERNAME   HOST                 CREATED
---------- -------------------- ---------- -------------------- ---------
NS         ABC.WORLD            DPMS       ABC                  06-SEP-03
NS         DLF.WORLD            DPMS       dlf                  13-MAR-02
NS         ORACLE.WORLD@DP      DPMS       ORACLE.WORLD         09-SEP-03
PAYROLL    DLF.WORLD            PAYROLL    dlf                  13-MAR-02

SQL> select * from dual@ORACLE.WORLD@DP;
select * from dual@ORACLE.WORLD@DP
                   *
ERROR at line 1:
ORA-02085: database link ORACLE.WORLD@DP connects to ORCL

Regards + Thanks

Deepak

          Followup:

the global name of the remote database you are referencing via 'oracle.world' is
ORCL, not ORACLE.

so,

create database link orcl@dp connect to .....

and select * from dual@orcl@dp;

      Rating: 4
thanks a lot
      !!!  September 10, 2003
Reviewer:
      A reader

      Rating: 3
DBLink from
      local database (ver 8.1.7) to remote database (ver
      8.1.6)  October 07, 2003
Reviewer:  Praveen
       from Bangalore
Hi Tom,

As you have said above I created a database link from the local database (Oracle
version is 8.1.7) to a remote database (Oracle version is 8.1.6).

CREATE DATABASE LINK service_name@scott_tiger
CONNECT TO scott
IDENTIFIED BY tiger
USING 'service_name';

Here service_name is the remote database service name which I use to connect
directly to that database. (like connect scott/tiger@service_name)

Now

SELECT * from dual@service_name@scott_tiger;

gives me the error:

ORA-02085: database link servive_name.US.ORACLE.COM connects to service_name

Then I obtained the global database name to the remote database from the
listene.ora file (ie remote_dbname.world)
and created another database link as

CREATE DATABASE LINK remote_dbname.world@scott_tiger
CONNECT TO scott
IDENTIFIED BY tiger
USING 'remote_dbname.world';

Any attempt to executed a

SELECT * from dual@remote_dbname.world@scott_tiger;

is resulting in a ORA-12154.

1)Is that because of any version difference ?

2)Or is it not possible to get data from a remote database
from within a local instance using just the service_name?

3)Where did I went wrong?

Thanks for your valuble answer

Praveen

          Followup:

try

CREATE DATABASE LINK remote_dbname.world@scott_tiger
CONNECT TO scott
IDENTIFIED BY tiger
USING 'service_name';

you want to name the dblink after the GLOBAL NAME

you want to use a tns connect string that actually works.

put the two together and wa-lah, you'll connect.

October 07, 2003 kamal -- Thanks for the question regarding
      "unable to connect using database link", version 8.1.5

      You Asked
DEAR TOM,
I CREATED A DATABASE LINK ON MY LOCAL DATABASE USING THE
FOLLOWING COMMANDS.
SQL> CREATE DATABASE LINK RP
  2  CONNECT TO PRINCE
  3  IDENTIFIED BY PRINCE
  4  USING 'ORB';

Database link created.

SQL> SELECT COUNT(*) FROM DUAL@RP;
SELECT COUNT(*) FROM DUAL@RP
                          *
ERROR at line 1:
ORA-02085: database link RP.LPU.ST.NM.US connects to ORB

I am unable to view the data.
Pls hlp me in this matter
THANKS
KAMAL

      and we said...
You have global names set to TRUE which means the database link
has to be named after the global name of the other database.

So, you can do one of the following:

o alter session|system set global_names=false
o create the database link with the name ORB instead of RP
o rename the global name of ORB to RP
Review &
      Followup

      Rating: 2
Database
      link  October 07, 2003
Reviewer:  Vivek
       from UK
Tom,
I am confused while creating and using database link. So need you advise on
that. I have two databases with following setting

              LOCAL     REMOTE
db_name        mass       orcl
db_domain      world      world
global_name    true       true

Now if I am creating a database link like

afm@MASS.WORLD> create database link orcl.world@albert connect to fsa identified
by fsa using 'ASPECT.WORLD’;

Database link created.
In one of the thread so have said add some extra stuff after @, so i have added
my server name.

afm@MASS.WORLD> select count(*) from em@orcl.WORLD@albert;
select count(*) from em@orcl.WORLD@albert
                        *
ERROR at line 1:
ORA-02085: database link ORCL.WORLD@ALBERT connects to ORACLE.WORLD

Why its trying to connect to ORACLE.WORLD?

But if alter the session for global_name it works

afm@MASS.WORLD> alter session set global_names=false;

Session altered.

afm@MASS.WORLD> select count(*) from em@orcl.WORLD;

  COUNT(*)
----------
      2852

Why I need to set the flag to FALSE, when I have created the DB link same as
database name?

I thought if i have created DB link with Database name then global_name
parametre is not relevant.

Thanks

Vivek

          Followup:

you need to use the GLOBAL_NAME for local and remote -- not the "db_name", the
GLOBAL NAME

select * from global_name;

thats the name you want to use.

when global_name is true, the name of the db link must match the global name of
the remote instance.

October 07, 2003 Jay -- Thanks for the question regarding "Create
      private synonym for another user", version 9.0.1

      You Asked
Tom

The simple question is this:
Can I create a private synonym for another user using dynamic SQL from within a
packaged procedure.

The reason for the question is this:
I have a system that has field (or column) level security, i.e. some users can
see all of the columns in table a, whilst others can only see some of the
columns in table a. I keep track of the security on columns simply with a table:
column_security(table_name VARCHAR2(30)
               ,column_name VARCHAR2(30)
               ,restriction_level NUMBER(1))
I have a package that generates views on every table for each level of security,
returning nulls for each restricted column.
I have a role for each level of security and the package grants select on the
views for each appropriate role.
Actual users (application users) are granted the appropriate role.
I have a user for each level or security as well, for the sole purpose or
resolving naming. Each of these users has private synonyms named as the original
table, but resolving to the views at their level of access.
Then there is an on logon trigger that looks at the user's granted roles and
changes the current_schema to the user with the correct synonyms.

Right, I wanted to be able to regenerate the entire structure easily if I add a
row or change a row in the column_security table. Everything is do-able from
within a packaged proc apart from the creation of the private synonyms, because
they are owned by the diffent schema users. Is there way of doing this within
the same package, owned and run by the table/view owner? If not can you think of
another neat way of doing it?

Cheers
Jay

      and we said...
Yes, just grant create any synonym to the owner of the procedure doing the
dynamic sql (make sure you UNDERSTAND the ramification of doing so.  that user
can overwrite anyones synonyms in the system)

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop user test cascade;

User dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop user admin cascade;

User dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create user test identified by test;

User created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create user admin identified by admin;

User created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> grant create session to test;

Grant succeeded.

ops$tkyte@ORA817DEV.US.ORACLE.COM> grant create session, create any synonym,
create view to admin;

Grant succeeded.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> @connect admin/admin
ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout off
admin@ORA817DEV.US.ORACLE.COM> GET afiedt.buf NOLIST
admin@ORA817DEV.US.ORACLE.COM> set termout on
admin@ORA817DEV.US.ORACLE.COM>
admin@ORA817DEV.US.ORACLE.COM> create or replace view v as select * from dual;

View created.

admin@ORA817DEV.US.ORACLE.COM> grant select on v to test;

Grant succeeded.

admin@ORA817DEV.US.ORACLE.COM>
admin@ORA817DEV.US.ORACLE.COM> create synonym test.v for admin.v;

Synonym created.

admin@ORA817DEV.US.ORACLE.COM>
admin@ORA817DEV.US.ORACLE.COM> @connect test/test
admin@ORA817DEV.US.ORACLE.COM> set termout off
test@ORA817DEV.US.ORACLE.COM> GET afiedt.buf NOLIST
test@ORA817DEV.US.ORACLE.COM> set termout on
test@ORA817DEV.US.ORACLE.COM>
test@ORA817DEV.US.ORACLE.COM> select * from v;

D
-
X

test@ORA817DEV.US.ORACLE.COM>

That shows that admin can create test's synonym for them just by qualifying the
create synonym name.
Review & Followup

      Rating: 5
So
      simple  February 28, 2002
Reviewer:  Jay
       from Scotland
Brilliant. Exactly what I was looking for. My only problem is: why didn't any of
the documentation that I looked at have this in it?

Anyway - thanks.

          Followup:

You didn't read the SQL reference manual then?

The syntax for create synonym is:

create [PUBLIC] synonym [SCHEMA.]synonym FOR [SCHEMA.]object[@dblink]

Additionally the create synonym "prereqs" state:

Prerequisites

To create a private synonym in your own schema, you must have CREATE SYNONYM
system privilege.

To create a private synonym in another user?s schema, you must have CREATE ANY
SYNONYM system privilege.

Read:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a85397/statem2d.htm#2060937
to verify that this is so....

      Rating: 1
Yeah,
      yeah  February 28, 2002
Reviewer:  Jay
       from Scotland
Tom

I didn't really expect a follow up to that. I revisited the manuals armed with
the answer and reverse engineered my interpretation of the command explanation.
With that I realised my error. But had I found the answer on first pass of the
manuals then I wouldn't have asked the question and consequently all your other
readers would not have the potential benefit of your solution.

Cheers!

      Rating: 3
Private synonym
      Performance  July 09, 2002
Reviewer:  nvssk
       from India
Hi Tom,
What is the impact on the performance, if we use private synonyms instead of
schema.table ?

I have an oracle server with single instance having multiple schemas.
Distributed option is enabled on this server.

If i want to access the tables in the same way as i do in my application,
(schema.table name) from the other database server what options do i have?

Assumption is Many application instances(connected through database users) will
have access to the second database server.

Can i achive the same through creating private synonyms for each user? OR any
other options are there ?

          Followup:

You can use private synonyms -- the overhead for them is marginal at best.
Translation during parse, but not much else at all.

I sort of like views as well as they import the meta-data from the remote site
to the local site, populating user_tab_columns and such.

      Rating: 5
Views vs
      private synonyms  July 10, 2002
Reviewer:
      nvssk  from India
Hi Tom,
Can you put light into these words.
"I sort of like views as well as they import the meta-data from the remote site
to the local site, populating user_tab_columns and such. "

How can we achieve import data from remote to local online?
Is there an option available ?
Also our requirement is that we should maintain data at one place only, but
should access from other server.

          Followup:

It imports the "meta data" about the remote object (not the data itself)

if you go:

create synonym foo for foo@remote_site;
select * from user_tab_columns where table_name = 'FOO';

you'll get no rows.  but if you

create view foo as select * from foo@remote_site;
select * from user_tab_columns where table_name = 'FOO';

you'll get rows (and rows in the user_tab_columns and such as well).

That is what I meant.

To actually "copy" or import the data, just

create table local as select * from foo@remote;

but that is not what you wanted.  So, this is just the difference between a
synonym and a view -- a view imports the META data, a synonym does not.

      Rating: 3
which is best
      view of synonym  September 01, 2003
Reviewer:
      Habibulla  from India
To refer tables of another schema, which is best?
VIEW of SYNONYM

Is there any difference in performance point of view.
(I cannot user alter.. currentschema, since multiple tables referred in queries
from multilple schemas)

Pl advise.

          Followup:

trick question -- you did not include the obvious answer -- use the schema name.

I prefer views myself when forced.

      Rating: 4
I have the same
      situation - can you help please ?  October 03, 2003

Reviewer:  Sonali  from waltham, ma
We have 2 schema's (we are trying to integrate these 2 application schemas) both
have tables, triggers, procedures etc.. I cannot use alter schema, as we need to
access tables from both, as you suggested many times synonynms are bad, we would
use views with create view as schema.obj_name.. This should be fine except when
I have following situation...

Say
I have 4 schemas on one database, they are called prod, prodback, dev, devback.

I have some triggers on prod schema, which have hardcoded references to prodback
schema. But then I backup and restore prod to dev schema. After I do this, the
dev schema now has hardcoded triggers with references to prodback instead of
devback.
To fix this I have to manually change all these hardcoded values in the dev
schema to point to devback.

I will have same issues with views and procedures...

Is there a function I can write which will return a shema name ( I can hard code
it here, but it will be only one place).. which then I call in all these
triggers, views, procedures to get the schema name as varaible and
then do something like this -

insert into fun_devback_name.work values(x,y,z);
instead of manually changing it all over ?

Thanks
Sonali

          Followup:

you need 2 databases -- you cannot run prod and dev in the same instance -- that
would be what one might term "a really utterly hugely bad idea".

      Rating: 4
Yes,
      agreed  October 03, 2003
Reviewer:  sonali
       from waltham, ma
Suppose I have 2 databases, but still one can have different schema names in
both.. then whaT ? How do I cross reference 2 schemas ? I have written this as
an example of what the customers can do and have... I wanted to have simple
export import process for this and not ask them to manually edit all the
triggers, views and tables in both schemas that are cross referenced.. this can
be a disaster if they don't do it right...Can I use sys context ? Can I write a
function to get the schema name and use it in all of these, so they will have to
change the schema name only in this function..

Thanks
Sonali

          Followup:

you would not have different schema names

or you would be forced to use synonyms.

so, i guess you are forced to either use VIEWS or SYNONYMS that you point to the
right objects from each schema.

      Rating: 4
Is there any
      routine I can write  October 07, 2003
Reviewer:
      sonali  from waltham ma
Okay I get, I need to use views.. that would be best then..
Is there any script I can write that will check for the schema name in say
dba_objects and replace the hard coded values for the schema references ?

Thanks
Sonali

          Followup:

not that i am aware of.

what you can do is write your installation scripts to be parameterized

create view v
as
select * from &owner1..table1, &owner2..table2
where.....

so they prompt you for the schema names upon install.

October 07, 2003 Venkat -- Thanks for the question regarding
      "Passing an ARRAY from Java to PL/SQL", version 8.1.7

      You Asked
Hi Tom,

   I need to Pass String array from Java to PL/SQL and also return
array from PL/SQL. I refered your book and arrived at the below code.

CREATE OR REPLACE TYPE STRARRAY AS TABLE OF VARCHAR2 (255)
/

CREATE OR REPLACE PACKAGE DEMO_PASSING_PKG
AS
   -- Varchar2's are most easily mapped to the java String type
   PROCEDURE PASS (
      P_IN IN VARCHAR2,
      P_OUT OUT VARCHAR2)
   AS
      LANGUAGE JAVA
         NAME 'demo_passing_pkg.pass( java.lang.String,
                                      java.lang.String[] )';

     PROCEDURE PASS (
        P_IN IN STRARRAY,
        P_OUT OUT STRARRAY)
     AS
        LANGUAGE JAVA
           NAME 'demo_passing_pkg.pass_str_array( oracle.sql.ARRAY,
                                               oracle.sql.ARRAY[] )';

   FUNCTION RETURN_STRING
      RETURN VARCHAR2
   AS
      LANGUAGE JAVA
         NAME 'demo_passing_pkg.return_string() return
           java.lang.String';
END DEMO_PASSING_PKG;
/

SET define off

CREATE OR REPLACE AND COMPILE
JAVA SOURCE NAMED "demo_passing_pkg"
AS
import java.io.*;
import java.sql.*;
import java.math.*;
import oracle.sql.*;
import oracle.jdbc.driver.*;

public class demo_passing_pkg extends Object{

public static void pass( java.lang.String p_in,
                         java.lang.String[] p_out ){

    /*
     * the simplest of datatypes -- the String.  If you remember
     * the C version with 6 formal parameters, null indicators,
     * strlen's, strcpy's and so on -- this is trivial in
     * comparision
     */

    if ( p_in != null ){

    System.out.println
        ( "The first parameter is " + p_in.toString() );

        p_out[0] = p_in.toUpperCase();

        System.out.println
        ( "Set out parameter to " + p_out[0].toString() );
    }
}

private static void show_array_info( oracle.sql.ARRAY p_in )
throws SQLException{

    System.out.println( "Array is of type      " +
                         p_in.getSQLTypeName() );
    System.out.println( "Array is of type code " +
                         p_in.getBaseType() );
    System.out.println( "Array is of length    " +
                         p_in.length() );
}

public static void
pass_str_array( oracle.sql.ARRAY p_in, oracle.sql.ARRAY[] p_out )
throws java.sql.SQLException,IOException{

    show_array_info( p_in );
    String[] values = (String[])p_in.getArray();

    for( int i = 0; i < p_in.length(); i++ )
        System.out.println( "p_in["+i+"] = " + values[i] );

    Connection conn = new OracleDriver().defaultConnection();
    ArrayDescriptor descriptor =
       ArrayDescriptor.createDescriptor( p_in.getSQLTypeName(), conn );

    p_out[0] = new ARRAY( descriptor, conn, values );

}

public static String return_string(){
    return "Hello World";
}

}

SET serveroutput on size 1000000
EXEC dbms_java.set_output( 1000000 )

DECLARE
   L_IN                          STRARRAY := STRARRAY ();
   L_OUT                         STRARRAY := STRARRAY ();
BEGIN
   FOR I IN 1 .. 5
   LOOP
      L_IN.EXTEND;
      L_IN (I) := 'Element ' || I;
   END LOOP;

   DEMO_PASSING_PKG.PASS (L_IN, L_OUT);

   FOR I IN 1 .. L_OUT.COUNT
   LOOP
      DBMS_OUTPUT.PUT_LINE ('l_out('|| I || ') = ' || L_OUT (I) );
   END LOOP;
END;
/

It worked, But I did not understand the flow of the code, Can you please help me
here.

Thanks in Advance,
Venkat

      and we said...
Umm, at a loss here.  It is pretty straightforward stuff and I did document the
flow in the book (very heavily I thought).

I'll try again.

o STRARRAY is simply our Oracle type that represents the array (collection) we
want to pass.

o DEMO_PASSING_PKG is our "binding" to the java.  Maps the SQL types to the Java
types.

o when you run the PLSQL block at the bottom of your question, we are calling
the code:

public static void
pass_str_array( oracle.sql.ARRAY p_in, oracle.sql.ARRAY[] p_out )
throws java.sql.SQLException,IOException{

    show_array_info( p_in );
    String[] values = (String[])p_in.getArray();

    for( int i = 0; i < p_in.length(); i++ )
        System.out.println( "p_in["+i+"] = " + values[i] );

    Connection conn = new OracleDriver().defaultConnection();
    ArrayDescriptor descriptor =
       ArrayDescriptor.createDescriptor( p_in.getSQLTypeName(), conn );

    p_out[0] = new ARRAY( descriptor, conn, values );

}

That code just

1) dumps the array meta data -- type name, length and so on.
2) gets the array of java strings from the parameter (p_in.getArray())
3) prints out each string in turn (system.out.println)
4) creates a new array to be returned (first half of example shows how to PASS
IN, second half shows how to pass OUT).
5) Then, it copies the values in the array we want to return into the OUT
parameter.

Review &
      Followup

      Rating: 4
Passing an
      ARRAY from Java to PL/SQL  May 07, 2002
Reviewer:
      Venkatesh  from Hyderabad
Thanks again Tom for your help.

Venkat

      Rating: 4
A
      Reader  July 31, 2002
Reviewer:  Rahul
       from India
Hi Tom

I have a small doubt, please clear it.
I have a proecdure which takes the parameter values from a form filled by a user
and insert it into a table.
That form has 15 to 20 parameters and right now I am using one IN parameter for
each value.
Please tell me, I should use the same approach or use the array for that..which
one is better and optimize approach??

Thanks.

          Followup:

I cannot say -- it depends on the code in the client doesn't it?

Is dealing with the data in an array more natural?
Is dealing with each parameter as a formal named (and typed) parameter more
natural?

Performance wise, it'll be 6 one way, 1/2 dozen the other way.

      Rating:
      3
  August 07, 2002
Reviewer:  peter

I think the confusion perhaps lies/lay in why we have two overloaded pass
routines and whether that really necessary.

Useful never the less.

      Rating: 3
What about a
      more comple array?  June 09, 2003
Reviewer:
      Carol  from Canada
I understand your example, but I am unable to understand how to extend it to
allow an array containing something like (last_name, first_name, address) to be
returned from the java to the pl/sql (I am not querying the database to retrieve
the information, I am building the array manually).  I cannot find an example on
this.  Does one exist?

Thank you

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:10271519764319
for how I would tackle this.

      Rating: 3
It is still not
      clear to me  June 10, 2003
Reviewer:  Carol
       from Canada
I'm sorry, but the example does not make it clear to me.  If I declare a
structure like:

create or replace type testemp as object (
    fname            varchar2(20),
    lname            varchar2(20))
/

create or replace type testtableemp as table of testemp
/

in PL/SQL.  What is the related structure I would declare in my java?  I want to
populate this structure manually with my java code and pass it back to PL/SQL.

Thanks again.

          Followup:

you can use jpublisher (way beyond where I'm going...) to map these object types
to java classes.

Me, I would juse

create global temporary table gtt ( fname varchar2(20), lname varchar2(20) )
on commit delete rows;

and have the java app BATCH insert into this and then call the procedure, the
procedure just uses that tables data as its inputs.

that, in my experience, is the least amount of code between "me" and "being
finished"

      Rating:
      5
JPublisher  June 10, 2003
Reviewer:
      bob  from texas
Tom,

No need to go there (JPub that is)..  (that is what OTN is for!)

http://otn.oracle.com/sample_code/tech/java/sqlj_jdbc/files/advanced/advanced.htm
Objects can be accessed either using oracle.sql.STRUCT or by defining custom
Java Classes to represent the Oracle Object Type. This sample illustrates access
of an Object Type using a java class generated by JPublisher, while retrieval
using weakly typed objects (oracle.sql.STRUCT) is illustrated in Object Oracle
Sample. Updated on 20-May-2003.

      Rating: 5
Using this
      example  August 21, 2003
Reviewer:  Carol
       from BC
Hi Tom,

I used the 'passing the array' portion of this example and it worked great.
When I try to run the example as a user that is not the owner of the
packages/java and type, I cannot get it to work.  Is it possible or am I doing
something wrong?

I changed the code to just pass strings (and not arrays so I didn't need the
type strarray) and when I run it as a different user it works fine.

Any ideas?

Thanks.

          Followup:

not working is 'too vague'

was it "type not known" type of error?  did you grant execute on it?

what was the error?

      Rating: 5
Passing a Java
      String array from Java to PL/SQL  August 28, 2003

Reviewer:  olerag  from Virginia
First example of code I've seen that clearly demos
passing a Java String array to a PL/SQL stored function
that is implemented with a PL/SQL block.

Other examples only showed a Java class that passed an
array to a PL/SQL block only to return it to the same
Java class.  This did not provide the "key" to the issue,
specifically the return type as depicted in the PL/SQL
packaged function, "oracle.sql.ARRAY[]".

The reference, however, to JDeveloper was, I suppose, an
attempt to demonstate the capaiblities of passing
multi-dimensional arrays.  I wonder how one might return
this type of object (String[][]) to a PL/SQL packaged
function in a pure PL/SQL environment??

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:8908169959941

      Rating: 5
Passing a List
      Java Collection Type from Java to PL-SQL Java Stored
      Procedure  October 07, 2003
Reviewer:  pasko
       from HH,Germany
Hi Tom,

I have just finished reading the above Great example..
but still couldn't figure out how to pass a List Java Collection Type from java
to PL-SQL using java stored Procedure.

For example in java code there's  a declaration like :

List  results = new  ArrayList() ;

I was thinking of copying this List type to an array of String , but may be
there's a  1-to-1 mapped Type from List  to  Oracle jdbc Types .

So , what mapped type should i use in the java stored Procedure.

I would really appreciate your Response on this .

Thanks in advance.

          Followup:

you have to use the collection type, as above.  you need to map to a SQL type.

October 07, 2003 Madhusudhana Rao -- Thanks for the question
      regarding "IN (vs) EXISTS and NOT IN (vs) NOT EXISTS", version Oracle
7.3.4

      You Asked
Hi Tom,

     Can you pls explain the diff between IN and EXISTS and NOT IN
and NOT EXISTS. Because I have read that EXISTS will work better than
IN and NOT EXISTS will work better than NOT IN (read this is Oracle
server tunning).

Regards,
Madhusudhana Rao.P

      and we said...
see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:953229842074

It truly depends on the query and the data as to which is BEST.

Note that in general, NOT IN and NOT EXISTS are NOT the same!!!

SQL> select count(*) from emp where empno not in ( select mgr from emp );

  COUNT(*)
----------
         0

apparently there are NO rows such that an employee is not a mgr -- everyone is
a mgr (or are they)

SQL> select count(*) from emp T1
  2  where not exists ( select null from emp T2 where t2.mgr = t1.empno );

  COUNT(*)
----------
         9

Ahh, but now there are 9 people who are not managers.  Beware the NULL value and
NOT IN!!  (also the reason why NOT IN is sometimes avoided).

NOT IN can be just as efficient as NOT EXISTS -- many orders of magnitude BETTER
even -- if an "anti-join" can be used (if the subquery is known to not return
nulls)

Review &
      Followup

      Rating:
      5
  April 03, 2001
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating: 5
phrase
      it  September 26, 2001
Reviewer:
      A reader
Kindly phrase the following query for us

select count(*) from emp where empno not in ( select mgr from emp );

      Rating:
      4
Reader  September 30, 2002
Reviewer:
      A reader
Tom,

Instead of

SQL> select count(*) from emp T1
  2  where not exists ( select null from emp T2 where t2.mgr = t1.empno );

you could have used

SQL>  select count(*) from emp T1
  2  where not exists ( select mgr  from emp T2 where t2.mgr = t1.empno );

Could you tell what circumstances do we use "select null"
instead of "select <value>". Are there any advantages

          Followup:

why select mgr?

I find select null to be semantically more meaningful.  You are NOT selecting
anything really -- so say that.

      Rating: 5
Why problem if
      NULL is there   October 02, 2002
Reviewer:
      Tony  from India
Hi Tom,
       Your answer is superb. Can you tell us why there is no record selected
for NOT IN when there is NULL?

          Followup:

Because NULL means -- gee, I don't know.  (litterally, null means Unknown)

So, the predicate

where x not in ( NULL )

evaluates to neither TRUE, nor FALSE

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from dual where dummy not in ( NULL
);

no rows selected

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from dual where NOT( dummy not in
(NULL) );

no rows selected

(you would think one of the two queries would return a row -- but there is a
third state for a boolean expression in sql -- "I don't know what the answer
is")

      Rating: 3
select rows
      using exists  March 31, 2003
Reviewer:  Madhuri
      Bondre  from India
Your solution is very useful

but i have one query

i have two tables

first table with item_cd, sub_item_cd (both the columns are in primary key)

second table with sub_item_cd (no primary key)

i want to filter only those records from first table where all sub_item_cd
exists in second table

ex.

rows of first table

item_cd     sub_item_cd

1010        A
1010            B
1010        C
1011        A
1011        D
1012        A

rows of second table

sub_item_cd

A
A
A
B
B
C
B
C

i need output as

item_cd

1010
1012

how can I get it in one select ?

can exists will solve it?

thanks in advance

          Followup:

ps$tkyte@ORA920> select t1.item_cd, t1.sub_item_cd, decode( t2.sub_item_cd,
NULL, 1, 0 )
  2    from t1, (select distinct sub_item_cd from t2 ) t2
  3   where t1.sub_item_cd = t2.sub_item_cd(+)
  4  /

   ITEM_CD S DECODE(T2.SUB_ITEM_CD,NULL,1,0)
---------- - -------------------------------
      1010 a                               0
      1011 a                               0
      1012 a                               0
      1010 b                               0
      1010 c                               0
      1011 d                               1

6 rows selected.

ops$tkyte@ORA920> select t1.item_cd
  2    from t1, (select distinct sub_item_cd from t2 ) t2
  3   where t1.sub_item_cd = t2.sub_item_cd(+)
  4   group by t1.item_cd
  5  having max(decode( t2.sub_item_cd, NULL, 1, 0 )) = 0
  6  /

   ITEM_CD
----------
      1010
      1012

ops$tkyte@ORA920>

      Rating:
      4
Thanks  April 01, 2003
Reviewer:
      Madhuri bondre  from India
you are great !!!!

      Rating:
      5
  May 05, 2003
Reviewer:  Mirjana
       from Barcelona

      Rating: 4
NOT IN or NOT
      EXISTS or  May 24, 2003
Reviewer:  Gautam
       from USA
Hi Tom, Your points are very helping, however, in case of having 20 Mill rows in
outer table and Mill in inner table none is working, both are too slow. Is there
a way to avoid the subquery being executed for retrieval of every row from the
outer query.

          Followup:

give me a "for example" and define "too slow".

on my pc, to full scan a 1,000,000 row table (a copy of all_objects -- the WIDTH
* HEIGHT of a table is relevant -- not just the height.  I have 1,000,000 tables
that consume lots less space then a 10,000 row one) takes about 7 seconds.  a
20million -- 20 times as long.

As long as you are using the CBO, and the tables are analyzed, it will use a
hash anti join and be very fast.

      Rating:
      4
  May 27, 2003
Reviewer:  Gautam
       from USA
Hi Tom, Thanks for your reply.

I have following observations :

Query:

SELECT ind_id, lst_ord_ss, dec_sc_is, lst_ord_is,
       ss_group, dec_sc_ss, list_pri, cont_lvl,
       site_upd_dt, source_cd, sd_custtyp, site_id,
       ss_subgrp
 FROM multi_mv
WHERE country_cd = :sys_b_00
  AND dsf_index IN (:sys_b_01, :sys_b_02)
  AND mail_cont IN (:sys_b_03, :sys_b_04)
  AND mail_site = :sys_b_05
  AND ss_group IN
     (:sys_b_06, :sys_b_07, :sys_b_08, :sys_b_09, :sys_b_10)
  AND NOT ind_id IN (SELECT DISTINCT ind_id
                             FROM ssw.promo
                             WHERE drop_cd = :sys_b_11)

1. The plan of above query. NL's

SELECT STATEMENT   Cost = 193604
  FILTER
    TABLE ACCESS FULL MULTI_MV
    TABLE ACCESS BY INDEX ROWID PROMO
      INDEX RANGE SCAN PROM_DROP_CD

2. Adding "ORDER BY int_id"into above Query changes
   the plan to seems like MERGE_AJ

SELECT STATEMENT   Cost = 118609
  TABLE ACCESS BY INDEX ROWID MULTI_MV
    INDEX FULL SCAN MV_MULTI_IND_ID
      TABLE ACCESS BY INDEX ROWID PROMO
        INDEX RANGE SCAN PROM_DROP_CD

3. Changing "NOT IN" to "NOT EXISTS"

SELECT STATEMENT   Cost = 198944
  SORT ORDER BY
    HASH JOIN ANTI
      TABLE ACCESS FULL MULTI_MV
      TABLE ACCESS FULL PROMO

Why is the CBO choosing Anti join only in case of NOT EXISTS ?

Internally the Anti Hash Join retrives the full row or only the join column ?

The approx time for the query to finish in first 2 cases is 5 days.

I would appreciate any comments on this ?.

          Followup:

obviously NO BIND variables on your system eh.

I'll guess (in light of no creates, no knowledge of indexes, no
dbms_stats.set_table_stats to let me know how big/how many rows things are, etc
etc etc)

Is ind_id in ssw.promo NULLABLE

and if so, what happens when you say:

  AND NOT ind_id IN (SELECT DISTINCT ind_id
                             FROM ssw.promo
                             WHERE drop_cd = :sys_b_11
                               and IND_ID IS NOT NULL )

instead (you do know of course that NOT EXISTS and NOT IN are *not*
interchangeable, they'll give different results!)

      Rating: 3
Not In Vs Not
      Exists !  May 27, 2003
Reviewer:  ramki
       from atl,ga
Tom,

I have 2 tables. Both tables have around 8000 records.

Table 1:

ID
parentID
col1
col2
col3

Table1: Primary key: ID

Table2

Module
ID
processDate
ParentID
dataCol1
dataCol2
source_column
source_fg

Table2 Primary key: Module, Id, processDate

Table 1 is a superset of all Ids that Table 2 has.
ParentIds for Ids in Table1 and Table2 would be same for same Id.

Now, I need to pick those Ids from Table1 that are

 -  not in Table2
 - have same ParentID from Table2.
 - for records that have source_column in table2 is not null
 - for records in Table2 that have source_fg as null

Table1:

ID     ParentId    Col1    col2    col3
101    111        10    20    30
102    111        20    40    60
103    112        10    10    10
104    113        10    20    30
105    113        30    40    50
106    114        40    40    50
107    113        50    50    50

Table2:

Module    ID    ParentId    ProcessDate    datacol1    datacol2    source_column
source_fg
1    101    111        27-MAY-2003    1000        1000        2000
1    103    112        27-MAY-2003    2000        2000        2000        Y
1    104    113        27-MAY-2003    3000        3000        3000
1    106    114        27-MAY-2003    2300        2300        3500

Now, I need the out to be:

102
105
107

Select t1.Id
from Table1 t1, Table2 t2
where not Exists (select null from Table2 where table2.module=1 and
table2.id = t1.id and table2.processDate = sysdate)
and t1.parentId = t2.parentId
and t2.Module = 1
and t2.processDate = sysdate
and t2.source_column is not null
and t2.source_fg is null

Actually, I need to insert these Ids in Table2 and replicate data from their
parentIDs in Table2.
so, New records would be

Module    ID    ParentId    ProcessDate    datacol1    datacol2    source_column
source_fg
1    102    111        27-MAY-2003    1000        1000        2000
1    105    113        27-MAY-2003    3000        3000        3000
1    107    113        27-MAY-2003    3000        3000        3000

I am trying to use, Insert into table2
select ......

Which one would be better: (using RBO)

Select t1.Id, t2.datacol1, t2.datacol2..........
from Table1 t1, Table2 t2
where not Exists (select null from Table2 where table2.module=1 and
table2.id = t1.id and table2.processDate = sysdate)
and t1.parentId = t2.parentId
and t2.Module = 1
and t2.processDate = sysdate
and t2.source_column is not null
and t2.source_fg is null

or

Select t1.Id, t2.datacol1, t2.datacol2..........
from Table1 t1, Table2 t2
where t1.Id not in (select Id from Table2 where table2.module=1 and
            table2.processDate = sysdate)
and t1.parentId = t2.parentId
and t2.Module = 1
and t2.processDate = sysdate
and t2.source_column is not null
and t2.source_fg is null

Please advice. Thanks

          Followup:

neither, rbo is the wrong technology.

In anycase, it seems you have the test data, benchmark it?  it is what I would
have to do in order to show you.

rbo would be wrong.  rbo would use the least efficient plans for these types of
queries.  cbo would do it most efficiently.

      Rating: 3
Problem with my
      NOT EXISTS statement  May 30, 2003
Reviewer:
      Godwin  from Ghana
Hi Tom,
Its difficult getting you meanwhile this question is similar what is being
discussed and I will be grateful if you will mail the reply to
godwinofoe@yahoo.com. Please don't ignore reading this.I need an urgent reply to
be able to get this report to management.

The problem is in generating a report in Report6i from a University HR database
Oracle7.3.2 Server.
The problem is like this:
In a University, staff who retired are taken on contract to work for some time.
Also staff who resigned from the university and want to come back are
re-engaged.
Now management requested for a report on all active staff (i.e to exclude
retirees and include staff re-engaged).
When I wrote a query to produce such report I was not getting the correct
result. The query failed to exclude the retired staff. The logic i was using is
that the subquery should select all staff who retired and resigned from the
university. At that instance of creating their service record, their start date
is the same as their end date so the subquery would select by using the
condition where the start date equals the end date. When such records of staff
are found by the subquery,the outer query would compare its result with the
inner query and then exclude any matching records found in the inner query
result. I hope i made myself clear.Here is the detail of my query:

SELECT  distinct id,name
,INITCAP(LOWER(dname))
,disc,descrip
,startdate,endate
from staff a
,dept b
,jobhist c
,dictator d
,postab e
WHERE a.id= c.stid
 AND   a.deptno= b.deptno
 AND   c.indic = d.code
 AND   c.jobtitle = e.postid
AND NOT EXISTS
             (SELECT 1
              FROM jobhist j
              WHERE j.stid=a.id
              AND j.startdate=(SELECT MAX(NVL   k.endate,k.startdate))
                             FROM jobhist k,jobhist n
                             WHERE k.stid = a.id
                             AND k.stid=n.stid
                             AND c.indic IN('01','02','04')
                                            )
                 )

ORDER BY   name asc

This is the result got from the above query:
DeptFile# NameService Indicator    PostStart Date    End Date
Operations    10003    Mana    NEW APPOINTMENT    LECTURER    1-Dec-87
20-Jan-99
Operations    10003    Mana    re-enganged    SENIOR LECTURER    1-Jun-01
Research    10001    Ofoe    NEW APPOINTMENT    LECTURER    15-Aug-02
15-May-03
Research    10001    Ofoe    PROMOTION    SENIOR LECTURER    15-May-03
Sales    10002    John    NEW APPOINTMENT    Junior Admin Asst    14-Jun-75
15-May-88
Sales    10002    John    PROMOTION    LECTURER    16-May-90    11-Jun-00
Sales    10002    John    PROMOTION    SENIOR ADMIN    15-May-88    30-May-90
Sales    10002    John    PROMOTION    SENIOR LECTURER    13-Jun-00    30-Jul-02

The correct output should have excluded staff called John since he is retired.

Here are the records stored in the various tables:

Jobhist Table
File# Ser.Indicator Postcode StartDate End Date
10001    A1    5    15-Aug-02    15-May-03
10001    A2    6    15-May-03
10002    A3    7    14-Jun-75    15-May-88
10002    A3    6    15-May-88    30-May-90
10002    A1    6    16-May-90    11-Jun-00
10002    A2    6    13-Jun-00    30-Jul-02
10002    A2    1    30-May-03    30-May-03
10003    A1    5    1-Dec-87    20-Jan-99
10003    A2    4    1-Jul-00    1-Jul-00
10003    A2    3    1-Jun-01

Staff table:
File#  Name    Hiredate     StaffType Deptno    Birthdate
10001    Ofoe    12-Aug-02    3    20
10004    Kofi    1-Jun-75    3    10    15-Jun-40
10002    John    12-May-14    3    30    12-Jun-70
10003    Mana    12-Sep-87    2    40    17-Jul-56

Service Indicator Table(dictator):
Code    Description
1    retired
2    contract
3    re-enganged
4    resigned
5    NEW APPOINTMENT
6    PROMOTION

Is there a mistake I made, is there a different way of achieving the result or
is SQL incapable of achieving the result? I need your help please. Thanks for
your help.

          Followup:

well, first - i don't read every review, most but not all.  Also, I read them as
I have time -- not on any sort of set schedule

second, I'm not emailing people on individual followups.  I'd spend the other
half of my life doing that (leaving no half for me)

third, the example you put forth is really really really hard to follow.  Your
sample tables for example haven't many columns at all that match the columns in
your query.  I don't understand your datamodel (as you should).  So, I'll make a
simple guess

sounds like you want STAFF records such that their last (most recent) record in
JOBHIST does not have an indic code in ( 1, 2, 4 )

so, I think we can ignore enddate all togther, just find the max startdate for
each person.  we can start with:

select file#, max( to_char(startdate,'yyyymmddhh24miss') || indic )
  from jobhist
 group by file#

Now, that will give us a set of file# (which I assume is the key into STAFF) and
their max startdate value ALONG WITH THE INDIC code for that max date.

We can then:

select file#
  from (
select file#, max( to_char(startdate,'yyyymmddhh24miss') || indic ) data
  from jobhist
 group by file#
       )
 where substr( data, 15 ) not in ( '01', '02', '04' )
/

That'll be the set of ACTIVE STAFF file#'s.

Now you can just

select *
  from staff
 where file# in ( <that query> );

      Rating: 2
You got what i
      mean but not getting the actual result I want  June 02, 2003

Reviewer:  Godwin  from Ghana
Hi Tom,
Thanks for the response.
With reference to your understanding "sounds like you want STAFF records such
that their last (most recent) record in
JOBHIST does not have an indic code in ( 1, 2, 4 )"

What i mean is that staff who resign(4) or retired(1)
shouldn't have their names or previous service records in the JOBHIST table
appeared in the report at all.
Take it as that the STAFF TABLE is the MASTER Table with the primary key fileNo
and the JOBHIST table is the DETAIL TABLE with FileNo as the foreign Key.
Your solutions you gave produced the names and previous service records of staff
excluding their most recent records that has indic code in (1,2,4).
We don't the records of those whose most recent records that has indic code in
(1,2,4) appeared.
Can't there be away of achieving that?
Thanks for your help.

          Followup:

you say

"we don't the records ... with 1,2,4 appeared"

If I read that to mean "why don't" -- then it is because I filtered them:

select file#
  from (
select file#, max( to_char(startdate,'yyyymmddhh24miss') || indic ) data
  from jobhist
 group by file#
       )
 where substr( data, 15 ) not in ( '01', '02', '04' )
/

Look at the concept there.  (you should be able to fix this, it is after all
your set of tables???)

That should -- if you've explained things accurately -- return the set of
primary keys from jobhist for joining to the other table.  The set of records
this returns is the MAX startdate record for everyone in jobhist (their LAST
ENTRY) if and only if their indic code isn't in 1, 2, 4

Take the technique, verify the results -- apply the technique to your problem.

Since I
a) don't know your tables
b) don't know your keys
c) am having a hard time following your explaination

this is the best I can do.

      Rating: 5
It was
      wonderful  June 02, 2003
Reviewer:  Godwin
       from Ghana
Hi Tom,
It is working perfectly.
Thanks for your patience although my questions were not explanatory enough,your
guess were correct.I made some mistakes in implementing the solution.
Just as you said,I will now fine tune the query so i can get the records from
more than one tables. I will do my best to get accurate result but please bear
with me if i should get back to you for additional help.
God bless!!

      Rating: 4
avoid
      correlated subquery  August 10, 2003
Reviewer:
      umesh  from blore india
SELECT
  count(*)
FROM
  AER A,
  AER_PRODUCT B ,
  AER_PRODUCT_APPROVAL_NO P ,
  AER_REACT E
WHERE
  A.AER_DELETED = '0'  AND
  B.RECORD_DELETED != '1' AND
  E.RECORD_DELETED != '1'  AND
  (B.NOT_RELEVANT IS NULL) AND
  (E.NOT_RELEVANT IS NULL)   AND
  B.TRADE_NAME LIKE :1 AND
  (
     ( B.PRODUCT_TYPE_MULTI = '0'
       AND
      (B.PRODUCT_TYPE = '1' OR B.PRODUCT_TYPE = '2' OR B.PRODUCT_TYPE = '4'  )
     )
     OR
     ( B.PRODUCT_TYPE_MULTI = '1'
     AND
      ( ( P.PRODUCT_TYPE = '1' OR P.PRODUCT_TYPE = '2' OR P.PRODUCT_TYPE = '4' )

       AND P.COUNTRY_CODE = NVL(A.AER_COUNTRY, '078')
         AND B.SEQ_PRODUCT = P.SEQ_PRODUCT  AND B.AER_ID = P.AER_ID
     )
     OR
    (NOT EXISTS
        ( SELECT Y.APPROVAL_NO FROM AER_PRODUCT_APPROVAL_NO Y
          WHERE Y.COUNTRY_CODE  = NVL(A.AER_COUNTRY, '078')
      AND B.SEQ_PRODUCT = Y.SEQ_PRODUCT  AND  B.AER_ID = Y.AER_ID
    )
    AND
    B.PRODUCT_TYPE != '3'
 ))
  )
  AND
  (B.PRODUCT_FLAG = '1'  OR B.PRODUCT_FLAG = '4')  AND
  B.AER_ID = A.AER_ID  AND
  E.AER_ID = A.AER_ID  AND
  E.EVENT_TYPE  != '2'
ORDER BY A.AER_NO, A.COMPOSITE_VERSION_NO DESC , A.AER_ID, E.AUTO_RANK,
B.AUTO_RANK

Tables                       P.KEY
AER                          AER_ID
AER_PRODUCT                  AER_ID,SEQ_PRODUCT
AER_APPROVAL_NO
                      AER_ID,SEQ_PRODUCT,SEQ_APPROVAL_NUMBER

i WANT TO ELIMINATE THE CORRELATED SUBQRY
PLEASE HELP

      Rating:
      5
  September 04, 2003
Reviewer:
      A reader
Tom,

Can you please take a look and advice on this query

select     map,
    todmdunit,
    todmdgroup,
    todfuloc,
    tomodel,
        fromdmdunit,
    fromdmdgroup,
    fromdfuloc,
    frommodel,
    eff
from    stsc.dfumap a
where      scen = 0
and        not exists
    (    select *
        from   stsc.dfu b
        where  scen = 0
        and    a.fromdmdunit = b.dmdunit
        and    a.fromdmdgroup = b.dmdgroup
                and    a.fromdfuloc = b.loc
                and    a.frommodel = b.model
    )
/

no rows selected

Elapsed: 00:00:30.29

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=59587 Bytes=3634807)
   1    0   FILTER
   2    1     INDEX (FAST FULL SCAN) OF 'DFUMAP1' (NON-UNIQUE) (Cost=2
Card=59587 Bytes=36
          34807)

   3    1     INDEX (UNIQUE SCAN) OF 'DFU1' (UNIQUE) (Cost=2 Card=1 Bytes=23)

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

SQL> desc dfu
 Name                                                                    Null?
 Type
 -----------------------------------------------------------------------
-------- ----------------------------
 DMDUNIT                                                                 NOT
NULL VARCHAR2(30)
 DMDGROUP                                                                NOT
NULL VARCHAR2(10)
 LOC                                                                     NOT
NULL VARCHAR2(10)
 HISTSTART                                                               NOT
NULL DATE
 EFF                                                                     NOT
NULL DATE
 DISC                                                                    NOT
NULL DATE
 FCSTHOR                                                                 NOT
NULL NUMBER(38)
 DMDCAL                                                                  NOT
NULL VARCHAR2(18)
 DMDPOSTDATE                                                             NOT
NULL DATE
 MODELDATE                                                               NOT
NULL DATE
 STATMSE                                                                 NOT
NULL FLOAT(126)
 MAXHIST                                                                 NOT
NULL NUMBER(38)
 TOTFCSTLOCK                                                             NOT
NULL NUMBER(38)
 LOCKDUR                                                                 NOT
NULL NUMBER(38)
 REFITDATE                                                               NOT
NULL DATE
 MASK                                                                    NOT
NULL VARCHAR2(18)
 SCEN                                                                    NOT
NULL NUMBER(38)
 SCENBITS                                                                NOT
NULL NUMBER(38)
 MAPUSED                                                                 NOT
NULL VARCHAR2(18)
 NETFCSTMSE                                                              NOT
NULL FLOAT(126)
 NETFCSTMSESMCONST                                                       NOT
NULL FLOAT(126)
 NETFCSTERROR                                                            NOT
NULL FLOAT(126)
 NEGFCSTSW                                                               NOT
NULL NUMBER(38)
 AUTOADJFACTOR                                                           NOT
NULL FLOAT(126)
 MODEL                                                                   NOT
NULL VARCHAR2(18)
 AUTOADJMIN                                                              NOT
NULL FLOAT(126)
 AUTOADJMAX                                                              NOT
NULL FLOAT(126)
 PUBLISHDATE                                                             NOT
NULL DATE
 UDC_DFU_HIERARCHY
 VARCHAR2(50)
 UDC_MODELNUM                                                            NOT
NULL NUMBER(38)
 UDC_ABCINDICATOR
 VARCHAR2(50)
 UDC_DELETERECSW                                                         NOT
NULL NUMBER(38)
 UDC_IMPORTSW
 NUMBER(38)
 UDC_NEWAUTOGENSW                                                        NOT
NULL NUMBER(38)

SQL>desc dfumap
 Name                                                                    Null?
 Type
 -----------------------------------------------------------------------
-------- ----------------------------
 MAP                                                                     NOT
NULL VARCHAR2(18)
 TODMDUNIT                                                               NOT
NULL VARCHAR2(30)
 TODMDGROUP                                                              NOT
NULL VARCHAR2(10)
 TODFULOC                                                                NOT
NULL VARCHAR2(10)
 FROMDMDUNIT                                                             NOT
NULL VARCHAR2(30)
 FROMDMDGROUP                                                            NOT
NULL VARCHAR2(10)
 FROMDFULOC                                                              NOT
NULL VARCHAR2(10)
 EFF                                                                     NOT
NULL DATE
 DISC                                                                    NOT
NULL DATE
 FACTOR                                                                  NOT
NULL FLOAT(126)
 LEVELNUM                                                                NOT
NULL NUMBER(38)
 FCSTTYPE                                                                NOT
NULL NUMBER(38)
 HISTTYPE                                                                NOT
NULL NUMBER(38)
 SCEN                                                                    NOT
NULL NUMBER(38)
 SCENBITS                                                                NOT
NULL NUMBER(38)
 CONVFACTOR                                                              NOT
NULL FLOAT(126)
 LOCKSTART                                                               NOT
NULL DATE
 LOCKEND                                                                 NOT
NULL DATE
 TOMODEL                                                                 NOT
NULL VARCHAR2(18)
 FROMMODEL                                                               NOT
NULL VARCHAR2(18)
 UDC_NEWAUTOGENSW                                                        NOT
NULL NUMBER(38)

SQL>select count(*) from dfu;
 COUNT(*)
----------
    770946

SQL>select count(*) from dfumap;
  COUNT(*)
----------
   1191740

parameters for
optimizer_index_caching              integer 80
optimizer_index_cost_adj             integer 25

          Followup:

try using NOT IN instead of EXISTS

from    stsc.dfumap a
where      scen = 0
and    (fromdmdunit, ... ) not in
    (    select b.dmdunit, b.dmdgroup, b.loc, b.model
        from   stsc.dfu b
        where  scen = 0
    )

      Rating: 3
Sometimes not
      exists does not work!!!  October 06, 2003

Reviewer:  Aldo bravo  from Ottawa, ON, Canada

Hi Tom,

See this example:

SQL> drop table t1;
drop table t1
           *
ERROR at line 1:
ORA-00942: table or view does not exist

SQL>
SQL> create table t1 (f1 varchar(10),
  2  f2 varchar(10),
  3  f3 varchar(10));

Table created.

SQL>
SQL> insert into t1 values ('1','5','3');

1 row created.

SQL> insert into t1 values ('4','4','6');

1 row created.

SQL> commit;

Commit complete.

SQL> SELECT f1,f2,f3
  2  FROM t1 a
  3  WHERE a.f2 <= '5'
  4  AND NOT EXISTS (SELECT '1'
  5  FROM t1 b
  6  WHERE b.f2 <= '5'
  7  AND b.f2 > a.f2);

F1         F2         F3
---------- ---------- ----------
1          5          3

SQL> analyze table t1 compute statistics;

Table analyzed.

SQL> SELECT f1,f2,f3
  2  FROM t1 a
  3  WHERE a.f2 <= '5'
  4  AND NOT EXISTS (SELECT '1'
  5  FROM t1 b
  6  WHERE b.f2 <= '5'
  7  AND b.f2 > a.f2);

no rows selected

What can you say about it?
If I delete the stats it works:
SQL> exec dbms_stats.delete_table_stats(tabname=> 'T1',ownname=>'ESCMANAGER');

PL/SQL procedure successfully completed.

SQL> /

F1         F2         F3
---------- ---------- ----------
1          5          3

It is a 9.2.0.4 database on Windows 2003.

Thanks,

          Followup:

first -- thank you thank you thank you for such a nice simple testcase.  bravo

Second, I filed Bug No: 3178622 on this -- obviously, a problem.

      Rating: 4
implicit
      cast?  October 07, 2003
Reviewer:  freek
       from Belgium
Tom,

Could the problem above be explained by an implicit cast?
Where the optimizer is after the analyze aware of the fact the the datatype of
the column is varchar2 and does not convert the literal to a number?

freek@DEV01> select (case when to_char(x) > to_char(y) then 'X bigger' else 'Y
bigger' end)
  2  from ( select 2 x, 10 y
  3         from dual
  4       );

(CASEWHE
--------
X bigger

Elapsed: 00:00:00.00

if you modify the example to use different values, you get following result

freek@DEV01> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
PL/SQL Release 9.2.0.1.0 - Production
CORE    9.2.0.1.0    Production
TNS for 32-bit Windows: Version 9.2.0.1.0 - Production
NLSRTL Version 9.2.0.1.0 - Production

Elapsed: 00:00:00.00
freek@DEV01> drop table t;

Table dropped.

Elapsed: 00:00:00.00
freek@DEV01> create table t1
  2  ( f1 varchar(10),
  3    f2 varchar(10),
  4    f3 varchar(10)
  5  );

Table created.

Elapsed: 00:00:00.00
freek@DEV01> insert into t1 values ('1','2','3');

1 row created.

Elapsed: 00:00:00.00
freek@DEV01> insert into t1 values ('4','10','6');

1 row created.

Elapsed: 00:00:00.00
freek@DEV01> commit;

Commit complete.

Elapsed: 00:00:00.00
freek@DEV01> select f1,f2,f3
  2  from t1 a
  3  where a.f2 <= '20'
  4  and not exists ( select '1'
  5                   from t1 b
  6                   where b.f2 <= '20'
  7                         and b.f2 > a.f2
  8                 );

F1         F2         F3
---------- ---------- ----------
1          2          3

Elapsed: 00:00:00.00
freek@DEV01> analyze table t1 compute statistics;

Table analyzed.

Elapsed: 00:00:00.00
freek@DEV01> select f1,f2,f3
  2  from t1 a
  3  where a.f2 <= '20'
  4        and not exists ( select '1'
  5                         from t1 b
  6                         where b.f2 <= '20'
  7                         and b.f2 > a.f2
  8                       );

F1         F2         F3
---------- ---------- ----------
1          2          3

Elapsed: 00:00:00.00

now if you change the query to use an to_number

freek@DEV01> select f1,f2,f3
  2  from t1 a
  3  where to_number(a.f2) <= 20
  4  and not exists ( select 1
  5                   from t1 b
  6                   where to_number(b.f2) <= 20
  7                         and to_number(b.f2) > to_number(a.f2)
  8                 );

F1         F2         F3
---------- ---------- ----------
4          10         6

          Followup:

nope, it is a bug - if the SAME query against the SAME data returns DIFFERENT
answers based solely on the plan developed -- that is a bug.

      Rating: 5
RE: sometimes
      not exists does not work...  October 07, 2003

Reviewer:  Mark A. Williams  from Indianapolis, IN
      USA
Tom,

Is there some environmental/config setting that could make a difference?  I have
9.2.0.4.0 on a WinXP laptop and can not duplicate...  Is this behavior specific
to Windows Server 2003?  I could not duplicate on 9.2.0.4.0 on Solaris, AIX, or
Linux as well...  (I am going to install Windows Server 2003 and test on that
tomorrow)

SQL> select * from v$version;

BANNER
----------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
PL/SQL Release 9.2.0.4.0 - Production
CORE    9.2.0.3.0       Production
TNS for 32-bit Windows: Version 9.2.0.4.0 - Production
NLSRTL Version 9.2.0.4.0 - Production

5 rows selected.

SQL> create table t1
  2  (
  3    f1 varchar(10),
  4    f2 varchar(10),
  5    f3 varchar(10)
  6  );

Table created.

SQL> insert into t1 values ('1','5','3');

1 row created.

SQL> insert into t1 values ('4','4','6');

1 row created.

SQL> commit;

Commit complete.

SQL> SELECT   f1,
  2           f2,
  3           f3
  4  FROM     t1 a
  5  WHERE    a.f2 <= '5'
  6  AND NOT EXISTS (SELECT   '1'
  7                  FROM     t1 b
  8                  WHERE    b.f2 <= '5'
  9                  AND      b.f2 > a.f2);

F1         F2         F3
---------- ---------- ----------
1          5          3

1 row selected.

SQL> analyze table t1 compute statistics;

Table analyzed.

SQL> SELECT   f1,
  2           f2,
  3           f3
  4  FROM     t1 a
  5  WHERE    a.f2 <= '5'
  6  AND NOT EXISTS (SELECT   '1'
  7                  FROM     t1 b
  8                  WHERE    b.f2 <= '5'
  9                  AND      b.f2 > a.f2);

F1         F2         F3
---------- ---------- ----------
1          5          3

1 row selected.

SQL>

Thanks,

Mark

          Followup:

absolutely -- any non-default optimizer related parameters will affect this.
It's all about the plan that was generated and many things will affect that.

      Rating: 5
I can replicate
      on Windows 2000 as well  October 08, 2003

Reviewer:  Martin  from UK

      Rating:
      5
Workaround  October 08, 2003
Reviewer:
      Aldo Bravo  from Ottawa, ON, Canada
If you use /*+ nounnest +/ hint or if you set the parameter
_unnest_subquery=false in the spfile the problem is solved.

There are many bugs related to this issue (2739068-2700474-3085399-2829723) and
in one of them I found the workaround.

Thanks!!


Subject | Article | Last Updated (East coast time)
cost | article only | Oct 08, 2003 06:33 am
Using temporary tables in stored procedure | article only | Oct 08, 2003 10:57 am
rollback segment | article only | Oct 09, 2003 03:27 pm
Loading data using XML files | article only | Oct 09, 2003 03:41 pm
Creating dll for executing external procedure('c' language) | article only | Oct 09, 2003 03:42 pm
Analytic..Function | article only | Oct 09, 2003 03:46 pm
replication direction | article only | Oct 09, 2003 03:51 pm
server details & input output | article only | Oct 09, 2003 03:55 pm
Using user function in SELECT,WHERE and ORDER BY | article only | Oct 09, 2003 04:14 pm
how to get user information? | article only | Oct 09, 2003 04:37 pm
Processing correlated rows | article only | Oct 09, 2003 05:15 pm
Dynamic SQL -Debugging | article only | Oct 09, 2003 05:24 pm
How to refresh Materialized View when the changes are made in the B tables ! | article only | Oct 09, 2003 05:43 pm
EBCDIC to ASCII Conversion | article only | Oct 09, 2003 07:09 pm
Oracle Web Server and HTP/HTF packages | article only | Oct 09, 2003 08:02 pm
Pivoting a result set | article only | Oct 09, 2003 08:23 pm
FGAC and web applications | article only | Oct 10, 2003 10:28 am
Synonyms and Dependency Management | article only | Oct 10, 2003 10:54 am
Performance using TAPIs | article only | Oct 10, 2003 10:58 am
logon trigger doesnot on guys with dba role | article only | Oct 10, 2003 10:59 am
Ref Cursor parameter | article only | Oct 10, 2003 11:07 am
SQL vs. PL/SQL (Cursors) | article only | Oct 10, 2003 12:30 pm
returning a series of values based on a start and end value using SQL | article only | Oct 10, 2003 12:53 pm
Delete Cascade if foreign keys exists with different delete rule | article only | Oct 10, 2003 04:23 pm
jdbc/plsql tables and also preparedStatements | article only | Oct 10, 2003 06:09 pm
freelists higher than 'sessions concurrently inserting' | article only | Oct 11, 2003 10:13 am
What happens in core database when I make one hot backup? | article only | Oct 11, 2003 03:48 pm
Avoiding redo while Deleting | article only | Oct 11, 2003 04:05 pm
Error: In Creating the Trigger | article only | Oct 11, 2003 04:13 pm
LOB compression | article only | Oct 11, 2003 04:38 pm
Can you think of reasons why indexes are ignored when using RAWs? | article only | Oct 12, 2003 09:49 am
Question on Statspack Report | article only | Oct 12, 2003 09:59 am
foreign key syntax issue - table level / column level? | article only | Oct 12, 2003 03:32 pm
table partitioning | article only | Oct 13, 2003 07:07 am
Password File | article only | Oct 13, 2003 07:18 am
Complex Unique constraint | article only | Oct 13, 2003 07:28 am
Functions Handling in SQL Queries. | article only | Oct 13, 2003 07:34 am
about the RR format | article only | Oct 13, 2003 11:56 am
Is it a good idea moving towards XMLType CLOB based model | article only | Oct 13, 2003 11:59 am
Stored Procedure | article only | Oct 13, 2003 12:00 pm
Materialized View | article only | Oct 13, 2003 12:55 pm
MATERIALIZED VIEW and Optimization | article only | Oct 13, 2003 07:59 pm
Instrumentation overhead | article only | Oct 13, 2003 08:00 pm
Exactly what triggers 'maximum open cursors exceeded' error ? | article only | Oct 14, 2003 06:27 am
wrap | article only | Oct 14, 2003 07:12 am
insert but discarding some unwanted/erroneous rows | article only | Oct 14, 2003 08:08 am
generating html with utl_file package | article only | Oct 14, 2003 10:48 am
How do you subtract the current row with the following row? | article only | Oct 14, 2003 11:04 am
Retrieving ARBITRARY Numbers with a select statement from dual | article only | Oct 14, 2003 11:05 am
when other than exception, and raise application error | article only | Oct 14, 2003 05:03 pm
CLOB's - lower and UPPER | article only | Oct 14, 2003 05:14 pm
PL/SQl server pages | article only | Oct 14, 2003 07:02 pm
row(s) 1 - 52 of 52
October 08, 2003 Anil -- Thanks for the question regarding "cost", version 8.1.5
You Asked
Hi tom ,
 I read your answer which U have given to somebody.After reading

i got some doubts...please clarify...
Here is your answer abt cost

You cannot compare the cost of 2 queries with eachother.  They
are simply not comparable.

When we get a query, we come up with lots of plans. Each step of

the plan is assigned some relative cost.  At the end, we apply a

function to derive the total cost of that query for each plan.
These costs (which are ONLY visible to the optimizer) can be
compared as they are for the same exact SQL using the same exact

environment.

If we get another query -- another similar query -- we go
through the same steps, build lots of plans, assign a cost, pick

the one with the lowest cost from that set.

We cannot however take those 2 queries and compare the relative
cost of each to the other.  Those costs are specific to those
queries.

Many things influence the "cost" of a query.  The "cost" is just

some artificial number we arrived at to select a query given a
certain environment.  Take a query and get its cost.  Now, add a

hint that makes the query run faster and check out its cost --
it might be higher, might be lower then the first.  Why?
Because the environment with the hint had us adjust the internal

numbers we assign to various operations -- in an attempt to make

your hint "look like" the best way to go.  It might have
adjusted the over cost for the plan you wanted to be lower then
the plan we did without the hint, or it may have made the plan
we did without the hint extremely expensive (artificially
inflated its cost).

Don't compare them, you cannot -- they might as well be random
numbers.
             Now My question.

1)Cost is just some artificial number we arrived at to select a
query given a certain environment...if So then the value of cost
doesn't say that the perticular query is good to be used or Not
?? please clarify...

2) what i have understood so far is ,if the cost is less..query
is good and if it is high it's not good to use.

3) If my second point is correct then how after adding the hint
 ,query cost might go up(as u said above)...but still work
faster.

4) For cost based...table should be analyzed as we know...and
for rule based Not.which One is Best way to find out query
efficiency in all the aspect.

regards
 Anil
and we said...
1) the optimizer is given a query.  This query could be solved
in one of possibly hundreds of ways.  The optimizer generates
many of these plans and assigns to each step of the plan a COST
based on the environment (hints will affect the cost, init.ora
parameters will affect the cost, statistics will affect the
costs, etc etc etc).  For each of these plans -- an aggregate
"cost" is computed.  The plan with the lowest cost wins.

The cost is used to find the "winning" query plan for a query.
The cost has no bearing as to whether or not it is the BEST plan
or whether it will take a long time to run or a short time to
run.  The cost is just a number computed by the software
according to an algorithm and is influenced heavily by the
envioronment.  The WORST plan that will take hours may come back
with a cost of "1" due to a HINT you gave the query or out of
date statistics.  The BEST plan for that query -- in light of
the stats or hints -- may have been assigned a cost of 100000.
The best plan will lose in that environment.  Remove the hint or
update the stats and the WORST plans cost will go up and the
best plans cost may go down and then it will win.

The cost is just a number.  It does not mean the plan is good,
bad or indifferent.  Its just part of an algorithm.

2) no, not at all.  I can, via the use of hints, make the WORST
plan look like the BEST plan by artificially lower the cost
(thats how hints work) of certain steps.  A lower cost query
plan may be many orders of magnitude WORSE then a higher cost.
It depends on the environment -- thats why having good stats is
important and having the right settings for sort_area_size and
so on and so on.  They all influence the cost of various steps.
If they are set up wrong -- you'll get the "wrong" plan because
it will appear to the software that it is less expensive.

3) after adding the hint, the cost of plans associated with
using the step requested by the hint will be REDUCED with
respect to other plans generated during that parse.  One cannot
say they will go up or down or sideways -- just that plans that
use the step suggested by the hint will look more appealing then
plans that do not.

4) in 8.0 and up it is recommended to use the CBO as many dozens
of features are only available via the CBO (interMedia, bitmaps,
function based, star transformations, etc etc etc)
Review & Followup
Rating: 5
Great explanation March 22, 2002
Reviewer: Anindya Roy from Mumbai, India
Very Precise Tom.

 But I have a question regarding this. From my personal experience I have seen ,
the optimiser reduced the cost of a particular plan of a query when a new index
was created on the table. This new plan started using the new index. But the use
of the new index actually made the query perform worse. No Hints were put at any
time. So why was not the optimiser able to distinguish which would be the best
plan. The table and indexed were analyzed before the query was re-run.

Is there a way which can prevent such surprises.

Followup:
because it is SOFTWARE and software is written by imperfect humans.

In all seriousness, you should never be surprised because:

a) adding an index is a big deal, it is a huge change

b) you should only be doing this in a test environment where "surprises" are OK.
 If you do this willy nilly on your production system -- expect to be constantly
surprised for your entire career (as well as black and blue and somewhat
bloodied by your user community)

c) The cost of two queries in different environments cannot be reasonably
compared.  the cost of a query has no bearing on the duration of the query.

The optimizer took into consideration the FACTS present in the database (the
statistics).  Based on that, it used a predicable algorithm to compute the
access plan.  It could be any number of factors that caused this to happen (but
it can and does happen with the RBO as well).  This is exactly why we have test
systems and production systems -- so there are NO surprises.

Rating: 4
Reader March 25, 2002
Reviewer: A reader
Tom,
"
The WORST plan that will take hours may come back with a cost of
"1" due to a HINT you gave the query or out of date statistics.  The BEST plan
for that query -- in light of the stats or hints -- may have been assigned a
cost of 100000.
"

Questions:

1. Does COST in any way, shape or form represent the
   number of datablocks to be accessed ( physical or
   logical ) directly or indirectly. I've seen people
   correlate calculation of COST to the the number of
   blocks to be accessed by a query to satisfy the results.
   Is this a mythe or any truth to it

Thanks

Followup:
1) no, no it does not - sort of.

The individual costs within a query plan (for each step) can have a relationship
to the size of the data.  suggest Jonathan Lewis's book practical Oracle8i.  He
talks about this in various places, its a very good book overall.  You won't be
sorry you got it.

The final cost of the plan, for anything more then a very simple query, will not
really be related to the amount of data to be read.  It is a function of all of
the costs and some costs just aren't related to the size of the data.

Rating: 4
Reader March 26, 2002
Reviewer: A reader
Tom,

Thanks for the prompt response

"
some costs just aren't related to the size of the data
"

Could you clarify this statement to us so that we can understand, what other
things go into COST. Because, as
you say I have seen query with more cost run slower than
one with less cost. IF, if at all possible any example
may help

Thanks

Followup:
If size were the only factor, all plans would have the same cost since the size
is fixed.

There are many variables -- too many to mention.

You already have your example.  You have seen queries with different costs for
the same tables.  If the COST inferred the amount of data, that would not quite
be possible would it.

Rating: 4
Reader March 26, 2002
Reviewer: A reader
Tom,

If I can guess correctly, the variables that you are referring
to, are initialization parameters like sort_area_size, hash_area_size,
multi_block_read_count etc.

Given that there are no sorts, no write to temporary tables or merge
joins, and the execution plan is purely of Nested Loop joins of a
complicated query, will the COST be number of blocks to read + any
other cost ? If so what would be this additional COST, be a function of.

Thanks

Followup:
no.  don't read too much into the cost.  I personally do not find it generally
useful -- don't get me wrong, there are times when I use it however at those
times I'm way down in the weeds, way way down there.

day to day, I don't use it.

Rating: 3
is cost proportional to execution time? March 26, 2002
Reviewer: Mikito Harakiri
Cost must be proportional to the execution time, otherwise optimizer has no way
to compare plans. Every time optimizer comes up with suboptimal plan, the reason
is just a wrong assumption in cost estimate. Every such case is essentially a
bug.

This is a theory, of course. In practice, there is a lot of uncertainty to make
correct cost estimation next to impossible. For example if there is a skew on a
join index, no histogram would help. Likewise, there is no way to estimate what
a sample of a join is by sampling each of 2 joined relations. In simple cases,
however, cost is a very good approximation of execution time.

Tom's explanation about hints is not quite correct: hints don't lower the cost,
but rather eliminate some potential plans from optimizer's consideration. For
example, when you give "ordered" hint, the optimiser would consider joining the
table just in the order that they are enumerated in the "where" clause. Even if
the join order is restricted, the optimizer still have a choice to choose either
NL, HJ, or MJ on each join, and also, whether to use indexes or not. It would
cost each those alternatives exactly as if they were considered in the case
without hint. The gross effect, however, is that the cost of the plan with a
hint would necessarily be higher.

Followup:
Cost is not proportional to execution time ACROSS QUERIES.

Cost is "somewhat, vaguely" proportional to execution time WITHIN A QUERY, in
the SAME ENVIROMNENT.

What I mean is -- you have two queries q1, q2.   Q1 has a cost of 1, Q2 has a
cost of 5.  Q2 may very well execute faster then Q1.  You cannot look at the
costs of Q1 and Q2 and determine which will run faster, you cannot look at the
costs and say "it will run this long".

Now, when optimizing Q1, the cost based optimizer will have generated N plans.
Each of the N plans is assigned a cost.  The plan generated for THAT query in
THAT environment with the lowest cost is assumed to be the plan that will
execute the fastest in that environment for that query.  You cannot make any
assumption about HOW LONG -- only that it is the most likely candidate for fast
execution in that environment.  If a plan in that same set has a cost that is 2
times the cost -- you cannot make any claim about HOW MUCH longer it will take
-- only that it is assumed it will take longer.  So the cost really isn't truly
proportional to the time.  Its just that things with lower costs (are assumed
to) run faster then things with a higher cost.  No implication of
runtime/execution time can be made.

Rating: 3
more about cost being proportional to the time March 26, 2002
Reviewer: Mikito Harakiri
Cost is supposed to be proportional to the time in the strong sense:

cost for Index Unique scan is: cost = BLEVEL+1;
cost for FTS is: BLOCKS/DB_FILE_MULTIBLOCK_READ_COUNT;
etc.

All these formulas clearly demonstrate that the intention is to make a cost to
be an approximation to the run-time performance of the query. Now, multiple
unknown factors complicate the matter, of course. Say, if a significant portion
of a table is cached, then the optimiser disc-access based formula become
somewhat pessimistic. But, in general, there is nothing in the formulas
themselves that distinquish one query upon the other, as you suggested.

In principle, you might be correct, because, a "really advanced" optimizer would
take into account other factors, like load, memory usage, etc. On the other
hand, taking into account volatile factors, like current system load, would make
the job of query performance analyst a nightmare, as the plan would mutate from
time to time...

Followup:
The costs are influenced by outside factors.  Factors OTHER THEN THE SIZE OF THE
DATA ITSELF.  These outside influences make it impossible to associate a "time"
with a "cost" or to infer even that query1 in environment1 with a cost of X is
better then query1 in environment2 with a cost of Y.

Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t as select * from all_objects;
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx1 on t(owner);
Index created.

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

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set optimizer_index_cost_adj =
100;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where owner = user;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=7 Card=636 Bytes=61056)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=7 Card=636 Bytes=61056)

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set optimizer_index_cost_adj =
35;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where owner = user;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=636 Bytes=61056)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=5 Card=636 Bytes=61056)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX1' (NON-UNIQUE) (Cost=2 Card=636)

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off

Now, given that all i did was influence the cost of an index -- something
"tweakable" -- does that mean anything with regards to runtime?

I hope your answer is "no", you cannot infer that the cost of 5 must be faster
then the cost of 7.  You just cannot.

In practice, I believe I am correct. (not just in principle)

(btw, in 9i, the optimizer can take those factors into consideration)

Rating: 3
Can you give a quick example or two? April 09, 2003
Reviewer: Robert from Memphis, USA
Tom,

Concerning CBO 'cost', you said......

"...don't get me wrong, there are times when I use it however at those times I'm
way down in the weeds, way way down there."

Many of us find ourselves way down in the weeds from time to time!

Can you please give an example or two, how you use the CBO 'cost'?

Thanks,

Robert.

Followup:
I'm looking at the output of a 10053 trace and trying to figure out why it did
what it did.

thats it.  that is as weedy as you get.

Rating: 4
I would like to provide info. April 09, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
Hello all.

For those who would like to dig in to get more details about the meaning of the
cost, I would recomend the floowing web site:
http://www.centrexcc.com/papers.html
Especially:
http://www.centrexcc.com/A%20Look%20under%20the%20Hood%20of%20CBO%20-%20the%2010053%20Event.pdf
It has a simple case, with maximum details, with the exact numbers used on each
step.

I hope Tom wont mind the aditional info.

Rating: 5
April 09, 2003
Reviewer: A reader from ca, usa
Tom, what's the best way to learn all the information described in this
discussion thread? Is there any good documentation out there that would show us
how to do sql tuning? thanks.

Followup:
have you read the performance guide that comes free with the database?  It is
the place to start to understand the optimizer.

add a couple of years of real experience..

a touch of intuition..

a heaping helping of "thinking in sets, forget row at a time thoughts"....

see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:8764517459743
for details.

Rating: 4
Cost influencing parameters April 09, 2003
Reviewer: Eric D Peterson from Bellevue, WA
As a developer and not a DBA, how can I look at the various parameters that will
influence my "cost" of a query?  How many of these can I manipulate?  Where can
I learn more about what the values in each of the parameters mean and how they
influence my session and the system?

Thanks much.

Followup:
see right above -- the "cost" isn't meaningful to you and me...

What is possible -- that is.
What techniques you know -- that is.

thats what that book will start you down the path on.

Rating: 5
Cost of CPU October 06, 2003
Reviewer: A reader
This is a bit of the topic.

If I save so many CPU seconds by tuning a query,
is there a way to translate the savings in terms of
dollars and cents to show to the customers

Followup:
get Cary's book:
http://www.amazon.com/exec/obidos/tg/detail/-/059600527X/
while it won't put a $ and cents on the answer, it'll tell you how to quantify
this, what you do want to look at.

For example -- you'll learn to think like this:

Hmm, I just cut the CPU time of that query in half.  It uses 30 cpu seconds to
run, not 60.  Can I put a dollar/cents number on that?  Of course not, we have
insufficient data.  What data might be missing?  Well for starters -- maybe that
query ran once a day, at 3am, with no one waiting for the results (it was a
batch job).  If it took me 5 hours to tune that query -- the dollars/cents might
actually be negative here (no savings, 100% cost) and the cost would be my
hourly rate times 5 -- for all I've done is improve a query no one needed
improving on.
.......

You need to take a more 'Holistic' approach.  You have to find out where the
pain is, figure out what the cost of that pain is, tune the pain away..

Cary has lots of good examples that discuss this -- no dollars and cents -- but
lots of sense!  you'll look at things differently.

Rating: 5
Reader October 06, 2003
Reviewer: A reader
Thanks. I was acually meaning to ask you, how to quantify cost of database
downtime in dollars/cents

I am planning on getting Cary Millsap's book also.

Followup:
that is easier/harder :)

sometimes it is easy:

"order entry system down"

a) we lost x$$ in orders based on our past order average for the duration of the
downtime.

thats easy (other things might contribute, but thats the big one).  It gets
harder with customer service types of applications.

Tell me -- what is the cost of a database the controls the entry/exit of people
in/out of the country?  Suppose you are not allowed to stop the entry/exit if
the system is down.  Now the cost is partly "cost to fix it" but could include
the cost of letting the bad guy in or out of the country.  How do you compute
that cost?

The system is a benefits system.  The cost of being down for a day is -- what?
What is the impact to the people who got their benefits a day (or more -- there
is a backlog to get over) late?  They pay their bills late, maybe someone
doesn't get something they really needed and they get sick/die.  Whats the cost
there?

It is very hard, there is no formula.  You have to look at the system, what it
does, what it means for it to be down.

If asktom went "down", the cost is ZERO (or is it?).  I mean, it is not mission
criticial, no one dies, no one can expect it to be here -- its free.  whats the
cost of that?

If metalink went "down", the cost is really high.  It is a for fee service,
there is an expectation that it will be there. The cost isn't so much in
measurable dollars and cents but in the lost trust, the bad press, the missed
opportunity costs -- that is not directly measurable except by educated guesses.

Rating: 4
Cost Vs Runtime October 07, 2003
Reviewer: Santosh Kumar from NJ USA
I am little bit confused about comparing cost of two queries. If I have a query
and cost is let's say 10000 and I add few more conditions and cost comes down to
300.  Does this mean both queries are separate and not comparable to each other
??

Followup:
pretty much -- you are by definition comparing apples to toaster ovens there
(they have different predicates, return different answers)

Rating: 3
Cost October 07, 2003
Reviewer: Santosh Kumar from NJ, USA
Thanks a lot Tom.  That clears the doubt. I have another question though and
could not resist to post it here. Why repeating join predicate multiple time
reduces cost of query and increases performance ??   In the explain plan the
Hash Joins were replaced with nested loops.

Followup:
which part of this page are you referring to?

October 08, 2003 Karl -- Thanks for the question regarding "Using temporary tables in stored procedure", version v7.3
You Asked
Hi Tom,

I am used to use MS SQL Server or Sybase to create stored procedures for
reporting.  There, it is very convenient to use temporary tables in the stored
procedure to manipulate complex logic.  However, in ORACLE, it seems difficult
to use.  Let say I want to

1)  create temp_1 to store the results from "Select col1, col2 from table1,
table2 where ..."

2)  create temp_2 to store the results from another query

3)  Join temp_1 and temp_2 with other tables to get the final results.  The
results are multiple rows.  The application software will call the stored
procedure to get the results set.

4)  temp_1 and temp_2 should be dropped automatically after running the stored
procedure.

How can I accomplish these requirement in a stored procedure?  You will be
highly appreciated if you could send me a sample stored procedure so that I can
learn from it.

Thanks!

Karl Huang
and we said...
You should simply:

open ref_cursor for
select *
 from ( query you used to put into temp_1 ),
      ( query you used to put into temp_2 )
where join_conditions

in your procedure.

You'll find that Oracle is much better at complex queries involving dozens (yes
more then 16) tables -- without any issues whatsoever.

The temporary table crutch which ismandatory in SQLServer is something you need
not use in Oracle -- we are very different -- Oracle and SQLServer are as
different as you can get.

See
http://osi.oracle.com/~tkyte/ResultSets/index.html
for getting result sets from stored procedures.

In this case, I would recommend you totally skip the temporary tables and their
unneccesary IO and overhead and go right for the answer.

We support temporary tables, but in a different fashion, see:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:48812348054
for a comparision.  DO NOT dynamically create them, DO NOT dynamically create
them, please -- do NOT dynamically create them.

followup to comment one below

see
http://osi.oracle.com/~tkyte/ResultSets/index.html
for examples of ref cursors and their usage.

Anytime you can rewrite in a single query what you are currently doing
procedurally or in many steps -- you will be the better for it.

In the example above, they were catering to a database engine that did not
perform optimization very well with lots of tables ( very common in that
database, that is why temp tables are so overused ).  In Oracle -- even with
"real temp tables" in 8i -- this would be the wrong way to approach this
problem.

In their case, using a ref cursor is easy, straight forward and cuts out the
temp table all together.  It is important to note however that even with the
temp table -- they would still need to use a ref cursor (as that is the way to
return result sets from stored procedures)

Whether or not you can make use of them will have to be determined by you
ultimately.

follow to comment two

Karl -- don't know exactly what you are asking but... here is an example.
Suppose in SQLserver you had coded something like:

begin
   select a,b,c into #t1
     from t1, t2, t3
     where t1.x = t2.y and t2.y = t3.z and t1.c1 = 2;

    select a,d,e,f into #t2
      from t4, t5, t6
     where t4.d = t5.e and t5.e = t6.f and t6.c2 = 55;

    select a.a, a.b, a.c, b.d, b.e, b.f
      from #t1 a, #t2 b
     where a.a = b.a;
end;

In Oracle we would just code:

begin
    open ref_cursor_variable for
    select a.a, a.b, a.c, b.d, b.e, b.f
      from (select a,b,c into #t1
              from t1, t2, t3
              where t1.x = t2.y and t2.y = t3.z and t1.c1 = 2) a,
           (select a,d,e,f into #t2
              from t4, t5, t6
             where t4.d = t5.e and t5.e = t6.f and t6.c2 = 55) b
      where a.a = b.a;
end;

and that is it -- where you used temp tables, we'll use INLINE views.  Hope that
clears it up, if not, feel free to followup.

followup to comment three

No, you are missing the point.

You don't open a REF CURSOR / temporary table from SQLServer -- you use an
INLINE view / temp table.

For example:

scott@ORA8I.WORLD> variable result_set refcursor
scott@ORA8I.WORLD>
scott@ORA8I.WORLD> begin
  2    open :result_set for
  3          select a.ename, b.dname
  4            from ( select * from emp ) a,
  5                     ( select * from dept ) b
  6           where a.deptno = b.deptno;
  7  end;
  8  /

PL/SQL procedure successfully completed.

scott@ORA8I.WORLD>
scott@ORA8I.WORLD> print result_set

ENAME      DNAME
---------- --------------
SMITH      RESEARCH
ALLEN      SALES
WARD       SALES
JONES      RESEARCH
MARTIN     SALES
BLAKE      SALES
CLARK      ACCOUNTING
SCOTT      RESEARCH
KING       ACCOUNTING
TURNER     SALES
ADAMS      RESEARCH
JAMES      SALES
FORD       RESEARCH
miller     ACCOUNTING

14 rows selected.

Here "select * from emp" and "select * from dept" represent your "temp table
queries" from sqlserver.

If a third table (or fourth or 20th, whatever) needs to be involved -- so be it,
just put it in the from clause with the rest of them.

As you can see -- that result_set has >1 row!

followup to comment 4

Karl -- back to my original point here -- we are not sqlserver.

fears like: "I am afraid that the performance would be low because there are
many tables opened simultaneously." would be valid there, they are not here in
Oracle.

One suggestion -- just try it.  Take a look at my free.sql query:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:285415955510
inline views that are joined and are the results of many table joins themselves
(the DBA_* views are horribly complex).  Don't be afraid.

follow up to Randy "Always good but... "

I never said you could not use this syntax in SQLSserver -- What I'm saying is
that you probably won't use this syntax in SQLServer but you should use it in
Oracle.

We have temp tables, you can use them (i have 1 application I developed that
uses one).  They are useful -- but not as a crutch to help the sql engine digest
smaller queries (not in Oracle anyway).
Review & Followup
Rating: 3
So are you saying that Ref_cursor is better than temp table August 15, 2001
Reviewer: Sonali Kelkar from Newton USA
In our cases we have real tables as temp (not dynamic which are created inside
procedure) and we delete records at the end. We insert values in these tables
with complex join queries and then do lot of processing using these inserted
values, like insert, update, deletes, some cursors , procedure calls etc. And at
the end we delete records for that transaction. But it is a pain maintaining
these procedures, I am not also sure if this the fastest way of doing it,
performance wise. Tom, do you have a more descriptive example of REF_cursor, I
will try to change one of the procedure to use it. We use Oracle 8.0.6 and 8.1.7
but we also support Oracle 7.3 do you think this will work with all these
versions ?

Rating: 4
Need example using ref_cursor in joining other tables August 15, 2001
Reviewer: Karl Huang from CA
Hi Tom,

I appreciate your definite answer.  What you suggest is to use ref_cursor to
store the temporary results.  That way we do not need to physicall create
'temporary' permanent tables which are urgly because otherwise when moving the
stored procedure from test server to QA to Production, we have to move those
'temporary' tables as well.

Now the question I have is that: after create these ref_cursors, how do they
participate in joinning other tables to get the final results set?  Are there
any good examples showing how the ref_cursor is used in joinning?

Thank again!

Karl

Rating: 4
Join ref_cursor1, ref_cursor2 with a table and return a result set August 15, 2001
Reviewer: Karl Huang from CA
Hi Thomas,

The example you showed me (see "follow to comment two") is assuming that the
temporary table has only one row. The temporary table I am using has multiple
rows.  Let me address my question again:

I need to create temp_table1 and temp_table2, each contains multiple rows.  As
suggested by Tom, it should use ref_cursor to create temp table.  That way, I
have ref_cursor1 and ref_cursor2 instead of temp_table1 and temp_table2.  Now I
need to join ref_cursor1 and ref_cursor2 with a real table, say My_Table and
have join conditions to associated ref_cursor1, ref_cursor2, and My_Table.  The
join will result in a results set, that is, it has multiple rows.  How can I do?

Again, an example will be greate help.

Thank you in advance!

Karl

Rating: 4
August 15, 2001
Reviewer: Karl from CA
Thanks a lot, Tom, I got your point now.  But it is still somewhat different
from what I need:

I want to have:

1. cursor_1 for select * from table_1 (join other tables)
   cursor_2 for select * from table_2 (join other tables)

2. May need cursor_1 or cursor_2 to join other tables to get
   cursor_3

3. Finally I want to have cursor_1, cursor_2, cursor_3 participate in join many
tables, say 11 tables, to get the final results set.

If I put everything (including cursor_1, 2, 3,..., and other tables) together to
get the final results, it is impossible (usually a report contains many logic
which need to be solved indivisually).  Even it is possible, I am afraid that
the performance would be low because there are many tables opened
simultaneously.

Again, please give me an example showing what I need.

Thanks,

Karl

Rating: 4
August 16, 2001
Reviewer: Karl from CA
Thanks a lot, Tom, I got your point now.  But it is still somewhat different
from what I need:

I want to have:

1. cursor_1 for select * from table_1 (join other tables)
   cursor_2 for select * from table_2 (join other tables)

2. May need cursor_1 or cursor_2 to join other tables to get
   cursor_3

3. Finally I want to have cursor_1, cursor_2, cursor_3 participate in join many
tables, say 11 tables, to get the final results set.

If I put everything (including cursor_1, 2, 3,..., and other tables) together to
get the final results, it is impossible (usually a report contains many logic
which need to be solved indivisually).  Even it is possible, I am afraid that
the performance would be low because there are many tables opened
simultaneously.

Again, please give me an example showing what I need.

Thanks,

Karl

Rating: 4
Always good but... August 16, 2001
Reviewer: Randy from TX
I'm not sure if the name "inline view" is an Oracle term, but the concept is not
particular to Oracle, is it?  As long the SQL flavor is ANSI (which SQLServer
is), you should be able to do an "inline view".   I've been using that syntax
for a while in SQL Server AND Oracle, but not too many temp tables in SQL Server
(though I have to admit that they are nice).  I'm in agreement that I'd rather
have Oracle opening many tables simultaneously than SQL Server.

Rating: 5
temporary tables August 30, 2002
Reviewer: mo from Reston, VA
TOm:

You wrote this:

In Oracle we would just code:

begin
    open ref_cursor_variable for
    select a.a, a.b, a.c, b.d, b.e, b.f
      from (select a,b,c into #t1
              from t1, t2, t3
              where t1.x = t2.y and t2.y = t3.z and t1.c1 = 2) a,
           (select a,d,e,f into #t2
              from t4, t5, t6
             where t4.d = t5.e and t5.e = t6.f and t6.c2 = 55) b
      where a.a = b.a;
end;

Is this correct oracle syntax "into #t1".  what does this do.

Followup:
No, you just drop the proprietary "into #t1" part and it would be:

begin
    open ref_cursor_variable for
    select a.a, a.b, a.c, b.d, b.e, b.f
      from (select a,b,c
              from t1, t2, t3
              where t1.x = t2.y and t2.y = t3.z and t1.c1 = 2) a,
           (select a,d,e,f
              from t4, t5, t6
             where t4.d = t5.e and t5.e = t6.f and t6.c2 = 55) b
      where a.a = b.a;
end;

Rating: 5
temporary tables August 31, 2002
Reviewer: Mo from Vienna,VA
TOm:

This is what I thought. I never saw this as a valid oracle syntax "into #t1".

Thanks,

Rating: 5
temporary tables August 31, 2002
Reviewer: Mo from Vienna,VA
TOm:

You can also get the same result by johning the set of 5 tables together.  Is
using inline views better in some ways?

Followup:
for this trivial example, yes, you could just join the SIX tables together --
the exmaple assumes the queries in the inline views are non-trivial and include
things like aggregates perhaps and such which would preclude a simple join.

Rating: 1
Your whole site sucks badly October 12, 2002
Reviewer: Oracle appears to be an expensive piece of junk from Columbus, OH USA
1) Stop using persistent cookies, you claim to be in software business.
2) "follow to comment two" Where the hell is "comment two"? Should I count all
the comments?

Followup:
1) get a grip.  have you been to amazon?  yahoo?  any site of any relevance?

It is OK to block cookies on this site, we just do it to make it easier.  Our
sessions are maintained via the sessionid in the URL you see (we we track you
anyway -- cookie or no).  The cookie is only to make it so you don't have to
enter you email and other data each time you ask a question, period.

(try disabling cookies on some other sites and see what happens to them
sometime)

2) what, they forgot to teach you how to count?  That's too bad -- so sorry for
you, allow me to try and help you.  Comment two would follow right after comment
one.  comment one is the first comment you see in the reviews section.    Got
it?

What you are seeing on this page is :

a) we had Q&A's at one point, no follow ups.
b) we decided to let people "review"
c) we discovered that many times we wanted to follow up the review
d) before we added the code to do that, i would follow up in the answer and just
"forward reference" it.
e) we added the code so I would just follow up to the review

We did that because people actually seemed to find the Q&A with followups
useful.

Sorry you find this not worth your while, hope you don't come back.

Rating: 3
Complex queries - without any issues whatsoever? October 12, 2002
Reviewer: Sybase user from LA, CA
Tom writes: "You'll find that Oracle is much better at complex queries involving
dozens (yes more then 16) tables -- without any issues whatsoever."

I would question that it will be “without any issues whatsoever”. AFAIK, the
reason why Sybase limits (or used to limit) the number of tables in the join
is/was because mathematically it is not that simple to determine the best join
order when many tables are involved. Besides, if the table/index statistics is a
bit off (and it’s always a bit off when there is activity on the db) optimizer
is more likely to make an error when determining the best join order. This
optimizer error will be more and more expensive the more tables are involved.
Oracle might be better (or not!) than orher dbs when it comes to complex queries
but I’m pretty sure that the sequence

BEGIN
SELECT * INTO #temp1 FROM b WHERE ….

UPDATE #temp1 SET … FROM b1 WHERE …
….
UPDATE #temp1 SET … FROM b20 WHERE

SELECT * FROM #temp1
END

will perform BETTER than SELECT that joins 21 tables.

So, if one replaces “SELECT * INTO #temp1” with a cursor, how does one handle
those “UPDATE #temp1” in Oracle (providing that he does not want to join 20+
table in one select)?

Thanks

P.S. Author of the ‘comment one’ bring a valid point and it is extremely easy to
do this in other dbs.

Followup:
In Sybase -- you would be correct.  Oracle (fortunately) is not sybase.

In Oracle the above sequence would perform more slowly then a single query -- I
see it over and over and over.

Do not project their limitation on us.

Rating: 4
My apologies, your site is great October 13, 2002
Reviewer: A reader
I have to apologize for my previous posting.  Tom, your site is very helpful and
works well with session cookies. I think the issue I had was with OTN, I believe
it requires permanent cookies (or maybe it simply it does not work for me).  I
was trying to get to
http://technet.oracle.com/doc/workbench/C5.htm
Anyway, my apologies, thanks for your help.

Rating: 4
My apologies, your site is great October 13, 2002
Reviewer: A reader
I have to apologize for my previous posting.  Tom, your site is very helpful and
works well with session cookies. I think the issue I had was with OTN, I believe
it requires permanent cookies (or maybe it simply it does not work for me).  I
was trying to get to
http://technet.oracle.com/doc/workbench/C5.htm
Anyway, my apologies, thanks for your help.

Rating: 4
My apologies, your site is great October 13, 2002
Reviewer: A reader
I have to apologize for my previous posting.  Tom, your site is very helpful and
works well with session cookies. I think the issue I had was with OTN, I believe
it requires permanent cookies (or maybe it simply it does not work for me).  I
was trying to get to
http://technet.oracle.com/doc/workbench/C5.htm
Anyway, my apologies, thanks for your help.

Rating: 4
Complex logic without temp tables November 28, 2002
Reviewer: A reader
Hi Tom,

Very interesting thread indeed. Based on previous discussion it appears that
general recommendation is to use cursor as an alternative to (Sybase) temp
tables. However, in some cases the logic is so complex that it simply cannot be
expressed in a single SELECT. With temp tables handling of such complex cases is
easy, one would simply run multiple insert/delete/update on temp table(s) as
necessary. How would one handle the same situation in Oracle, without temp
tables?

Thanks in advance

Followup:
give me an example of logic so complex that one is forced -- totally forced --
to use a temporary table vs just working with the real data itself.

Rating: 4
Complex logic without temp tables November 29, 2002
Reviewer: A reader
> give me an example of logic so complex that one is
> forced -- totally forced –
> to use a temporary table vs just working with the real
> data itself.

Hi Tom,

I could definitely give an example where implementing the query logic with one
SELECT would be a very difficult task. You’re right though, good SQL developers
can be very inventive and, on a couple of occasions, I’ve seen queries about 200
lines long that did the job with one SELECT. In my experience, that kind of
queries is difficult to understand (in some cases due to heavy use of
characteristic functions) and maintain.

Anyway, my point is that there are situations when implementing the query logic
with one SELECT is not possible or practical. How would one handle this in
Oracle?

Thanks in advance

Followup:
INLINE view -- whereever you felt tempted to use a temporary table.

very readable, very understandable, better then procedural code.

I would like to see that example -- of "not possible" but made possible by
temporary tables without procedurally processing the results.

Rating: 4
Temporary tables in Oracle January 31, 2003
Reviewer: Praveen from Bangalore
Hi Tom,

Tom, I am really messed up with the temporary table concept of Sybase. I started
re-writing a big Sybase procedure into Oracle PL/SQL and used a lot of temp
tables in the process.
Now I am unable to compile the procedure and was completely stuck until I
thought of going through your site again. Now I understand where the problem is
but it is now too late to re-write the whole procedures using cursor variables.
I get the error

"identifier 'temp_table' must be declared"

at the line where I tried to insert into the temporary table which I created
dynamically. (ie using EXECUTE IMMEDIATE "CREATE GLOBAL TEMPORARY TABLE
temp_table....").
The reason is obvious. ie at compile time there is no such object as
'temp_table'.

1) How should I overcome this
problem? (ofcourse with the temporary tables are still created dynamically).

2) Another problem is with the update done on these temporary tables. ie in
Sybase they write it as

Ex1: UPDATE temp_table1 t1
    SET t1.a1 = t2.a1
    FROM temp_table1 t1, another_table2 t2
    WHERE t1.pk = t2.pk;

How can I write a similar Update statement in Oracle. ie without using any
cursor loops. ?

I saw your answer for a similar question as:

UPDATE (SELECT t1.a FROM temp_table1 t1, another_table2 t2 WHERE t1.pk = t2.pk)

Does this statement gives the same result as the Ex1.

Your valuble suggesion is highly appreciated.

Thanks and regards

Praveen

Followup:
we do not create temp tables dynamically.

There is no way in the world you even want to consider that.  I don't care
how far along you are.  stop, do it right.

1) by creating the temporary table OUTSIDE of the procedure, ONCE when you
install the procedure.  Of course temporary tables should NOT be created
dynamically.

2) see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:273215737113
for syntax that will work in all databases...

Yes, you update a join  update (select....) set ....

Rating: 4
May 01, 2003
Reviewer: Steve from nyc, usa
Hi tom,

Inline view is very convenient because it is easy to query data based on other
query's result.

My question is that how can I query the data from the result set generated by a
stored procedure as an inline view.

e.g.  select col1, col2, ...
      from  (
                a result set from a stored procedure
            )
      where ....

The stored procedure above is not a single sql, so I can't just copy the sql of
stored procedure as inline view.

Thanks!

Steve

Followup:
search for

pipelined

on this site.

Rating: 5
temporary tables May 02, 2003
Reviewer: Steve from NYC, USA
Hi Tom,

Could you please show us how to dump a ref cursor's data (result set)into a
temporary table?

Thanks!

Steve

Followup:
open it
loop
    fetch
    exit when not found
    insert
end
close

that of course would be the wrong approach -- right approach would be:

  insert into table select ....;

(eg: the ref cursor is neither necessary nor desired in this case)

Rating: 4
Temp Tables / In-Line view May 02, 2003
Reviewer: Dilip Dandekar from Pune, India
After checking at this site I have started using in line views and these are
very effective.

Rating: 3
Programmer/Analyst July 03, 2003
Reviewer: Vishal Sharma from USA
I am already using refcursor in procedures to return the results for reporting
purposes. It works fine when I run them in sqlplus to verify the results. But
the procedures are supposed to support on web.
There are 2 problems I am facing.
1. As the results (a text file) get printed on a network drive, which is read on
the web. Procedure completes very fast, but some times it takes 3-4 hours for
the file to finish. How can we improve that?
2. Since the procedure is already completed. If i see on the schema, it shows
that proc is no more running. While on web (Crystal Report Front Supported) ,it
says running because the file is still not over. HOW CAN I see all the
refcursors which are still printing ??
We are using {REPORT} user for all those procs.

Thanks in advance.

Followup:
1) if the procedure runs fast -- where is the 3-4 hours coming from?  missing
something here.

2) ahh, now I see.  opening a cursor is instantaneous.  actually deriving the
result set and fetching the result -- that could take a long time.

consider:

 open ref_cursor for select * from a_one_BILLION_row_table;

that'll open in a nanosecond.
it might take a while to fetch it all.

use TKPROF and see if you cannot tune your query.

Rating: 3
Why should the temp table not created dynamically any specific reason July 04, 2003
Reviewer: A reader

Followup:
because DDL is expensive, very expensive, to execute.

because you would have to "drop the table" yourself.

because you would have to uniquely name it.

because your stored procedures would have to use dynamic (slower) sql to access
it

because all sql accessing this gtt would be hard parsed -- since the object
never existed and won't exist for long.

because it is messy and buggy to do so (your code is harder to write, will have
more runtime errors, will run slower).

because there is no upside to doing so.

Rating: 4
Programmer?Analyst July 07, 2003
Reviewer: Vishal Sharma from USA
So When The cursor is fetching and printing the results which actually is taking
time, Can I query from from database which all refcursors are executing at any
perticlar time ???

Followup:
v$open_cursor shows you open cursors.

Rating: 5
DDL in PL/SQL July 23, 2003
Reviewer: Andy from Germany
Tom, I appreciate your point about there being better ways (inline views etc.)
to deal with issues that could be dealt with by creating temp. tables from
within a stored procedure, but why is DDL not permitted in PL/SLQ in the first
place? I came across this quote in the 9i Concepts manual:

"Additionally, you can issue DML or DDL statements using dynamic SQL. This helps
solve the problem of not being able to statically embed DDL statements in
PL/SQL." (section 14-20)

and was curious as to the reason.

Followup:
well, it doesn't make sense mostly.

PLSQL is statically compiled.  when you compile it -- all references are
resolved, permissions checked, etc -- at that time.  If you had code like:

create procedure p
as
begin
   for x in ( select * from emp ) loop ....
end;

the reference to EMP is "fixed" at compile time.  Now, if you tried:

create procedure p
as
begin
   create table emp ( x int );
   for x in ( select * from emp ) loop ....
   drop table emp;
end;

well, obviously at compile time EMP does not exist, so that could never compile.

All you would have achieved had it compiled is some really BAD horrible code
(the create/drop commit -- only one person at a time could run this without
error since only one EMP table can ever exist -- if you had an error after the
create, before the drop, you would have to manually clean up the emp table and
so on)

Rating: 3
temporary tables August 12, 2003
Reviewer: vivek from UK
Tom,

I am creating temporary tables dynamically in one of mine procedure and then
read this thread where you have mentioned

"DO NOT dynamically create them, DO NOT dynamically create
them, please -- do NOT dynamically create them."

Could you please suggest best way to handle this situation.

My client is going to create Work orders and these workorders will be created by
their vendors based on 'Contract number' issued to them.

Now vendors are approx. 150 in number and contract number issued per financial
year could be in range of 500-600.
What i am doing is creating temporary table based on contract number (which will
be unique) and then process the result. Temporary tables are dropped firat and
then re-created each time procedure is executed

Now after reading this thread, i am not sure, is this the correct method for
this situation.

could you please advise us on this.

thanks

Vivek

Followup:
permantly create them?

Just CREATE the global temporary table when you install your code.  then, use it
in your procedures.

done.

Rating: 3
temporary tables August 12, 2003
Reviewer: vivek from uk
Thanks Tom.

apologies for posting thrice now..

Rating: 2
temporary tables September 17, 2003
Reviewer: Roy Garita L. from San José, Costa Rica
Hi... my name is Roy Garita and looking for some information about TEMPORARY
TABLES in Oracle. What I need, is to know how they work, for what I should use
them, in which cases will be useful and not. How to build them. What are the
performances reached among others procedures.

EJM: SELECT a,b,c
     FROM   (SELECT x,x,y
             FROM   table1
             WHERE  ...)
I'll be so happy to find lots of information about it.

Regards.

Followup:
use them only when nothing else will work at all...

they work just like tables.  except they lose all data when you commit or your
session ends -- depending on how they are declared.

you see only your data, no locking/concurrency issues.

inline views are the way to go whenever possible.

i don't really use them very much myself -- except maybe as a big "in list"

Rating: 4
Repeated use of some data September 17, 2003
Reviewer: Alla from Boston, MA USA
Tom;

You say to use temp tables when nothing else works.

My question though is if there is a set of data that I need to use often in a
procedure in different statements, would not it make more sense to insert this
data into temp table and then use it in all other statements, rather then
including this inline view in each and every statement, thus selecting the same
data over and over again

Thanks

Rating: 1
Why aren't you answering??? September 22, 2003
Reviewer: Alla from Boston, MA USA
Hi Tom;

Did this question somehow escaped your attention?

I am still curious what you think about this situation

I would appreciate a response

Thanks

Alla

Followup:
i don't see each and every review/followup -- no.  the UI shows me reviews in
chronological order and I try to remember which one i last looked at.

i would only use a temp table if the result set you need is very hard to
manufacture (takes a long time to materialize)

Rating: 5
Thank you September 22, 2003
Reviewer: Alla from Boston, MA USA

Rating: 2
need explanation on temp table usage October 08, 2003
Reviewer: aish from texas
hello,
Here iam trying to insert values to to table child from table master.
Every day this job/procedure is going to run.
This will delete all the contents of child and get a new info from master.
For this purpose iam trying to create some temp table in my pl/sql procedure.
but i have no idea like how to create it .
I dont understand your answer for the abovce question.
How can i use ref cursor in this example.
Thanks
aish

Followup:
umm, why do you think you need a temp table???

just "insert into select from" -- no temp tables needed, or desired.

October 09, 2003 vrishali -- Thanks for the question regarding "rollback segment", version 8.1.6.2.0
You Asked
Hi Tom,

I have created a rollback segemnt's tablespace with pctincrease
parameter 50 but all rollback segments are created with
pctincrease 0.
   Will pctincrease parameter of tablespace affect on performace
of rollback segemnts which are actually created with
pctincrease=0?

Thanks
Vrishali Vekhande
and we said...
rollback segments never have a pctincrease, it is not allowed.
they should have initial=next=some fixed size.

ALL SEGMENTS should ALWAYS have pctincrease=0 if you ask me.

Since you are in 8.1.6, you should use locally managed
tablespaces with uniform extent sizes -- they are faster then
dictionary managed and storage parameters like pctincrease are
just ignored.
http://asktom.oracle.com/pls/ask/f?p=4950:7:::::F4950_P7_SEARCH_CRITERIA:locally+managed
for more info on locally managed tablespaces...
Review & Followup
Rating: 5
Insert slow after switch to LMT October 08, 2003
Reviewer: Vera from D.C.
Hi,Tom,
We just switched on LMT and I as soon as I started testing our application I saw
that performance became horrible. For example, we have a simple INSERT/SELECT
statement that used to take 1-1.5 minutes (about 50 mg)and now it takes forever.

SELECT itsef works fine and does not take time at all. I am not a DBA, but my
common sense and everything I know about database tell me that it could be RBS
problem. I checked new settings and found out that while tablespaces for tables
and indexes are on LMT, RBS is still in the Dictionary. Our DBAs when I asked
them said RBS should not be on LMT, but I cannot buy it and I think it is wrong
and causes us problems. Tom, what do you think?
Thank you

Followup:
10046 level 12 trace it and tkprof it -- if you have 9i, you'll see the waits in
tkprof report -- else you'll have to dig into the trace file

I'm pretty sure you'll find that the LMT is not a factor in this (RBS's should
definitely be LMT, temp should, data should, everything should)

October 09, 2003 Dhananjaya -- Thanks for the question regarding "Loading data using XML files", version 8i (8.1.6)
You Asked
Hi Tim,

     Can you give me brief discription about loading data to oracle table when
the data is given in XML files.
and we said...
Well there are quite a few ways to do it.
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1405403846139
shows how to load the XML straight in

You could also read the XML in using a BFILE and use either java or plsql to
parse it and insert it.  See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:441019930865

search for XML on my site and you'll see a couple more examples/questions.
Review & Followup
Rating: 3
reading files with attributes and just frontslash (/) as the element terminator June 01, 2002
Reviewer: Nihal singh from S'pore
Hi tom,
can u give an example or a link to the example where in a xml file with elements
and Attributes is read.
for eg lets say i have the following xml file ,i can view it through I.E. but
loading it through a normal stored procedure gives error i guess it because of
the attributes and just frontslash (/) as the terminator,
now i need to load it without any change to the way xml file has been generated.
---------------
- <Common>
  <Client ClientID="98092" ClientName="DEBORAH WALL AGENCY"
IRDNumber="26-969-212" DOB="00/00/0000" TactAccountNo="TACT1" />
  <Client ClientID="405631" ClientName="MR MURRAY JOHN HAYTERIAN B And THELMA M
HAYTER AS TRUSTEES" IRDNumber="13-449-187" DOB="00/00/0000"
TactAccountNo="405631" />
  <Client ClientID="417186" ClientName="VALDA SPIERS" IRDNumber="19-040-763"
DOB="00/00/0000" TactAccountNo="417186" />
  </Common>

-------------------------------------------------
I based my stored procedure on the eg given where in u can load the xml into
clob and then into the table, but that didnt work.

Thanks

Nihal Singh

Followup:
I asked Sean Dillon, our local XML guru, to take a look at this and here's what
he had to say:
--

If you want to use the XML SQL Utility to "auto-load" your XML into relational
tables, you need to transform your XML into Oracle's "canonical XML format".
The XML you have listed above isn't valid in unto itself, you need an XML
prolog, i.e.:

<?xml version="1.0">

Using XSLT in the database, you need to convert the XML document you store in
your CLOB column:

<?xml version="1.0"?>
<Common>
  <Client ClientID="98092"
          ClientName="DEBORAH WALL AGENCY"
          IRDNumber="26-969-212"
          DOB="00/00/0000"
          TactAccountNo="TACT1" />
  <Client ClientID="405631"
          ClientName="MR MURRAY JOHN HAYTERIAN B And THELMA M HAYTER AS
TRUSTEES"
          IRDNumber="13-449-187"
          DOB="00/00/0000"
          TactAccountNo="405631" />
  <Client ClientID="417186"
          ClientName="VALDA SPIERS"
          IRDNumber="19-040-763"
          DOB="00/00/0000"
          TactAccountNo="417186" />
</Common>

...into Oracle's canonical XML format:

<?xml version="1.0"?>
<ROWSET>
  <ROW>
    <ClientID>"98092"</ClientID>
    <ClientName>"DEBORAH WALL AGENCY"</ClientName>
    <IRDNumber>="26-969-212"</IRDNumber>
    <DOB>="00/00/0000"</DOB>
    <TactAccountNo>"TACT1"</TactAccountNo>
  </ROW>
  <ROW>
    <ClientID>"405631"</ClientID>
    <ClientName>"MR MURRAY JOHN HAYTERIAN B And THELMA M HAYTER AS
TRUSTEES"</ClientName>
    <IRDNumber>="13-449-187"</IRDNumber>
    <DOB>"00/00/0000"</DOB>
    <TactAccountNo>="405631"</TactAccountNo>
  </ROW>
  <ROW>
    <ClientID>"417186"</ClientID>
    <ClientName>"VALDA SPIERS"</ClientName>
    <IRDNumber>"19-040-763"</IRDNumber>
    <DOB>"00/00/0000"</DOB>
    <TactAccountNo>"417186"</TactAccountNo>
  </ROW>
</ROWSET>

This can be done using an XSLT stylesheet like this:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="
http://www.w3.org/1999/XSL/Transform
xmlns:fo="
">http://www.w3.org/1999/XSL/Format">
  <xsl:template match="Common">
    <ROWSET>
      <xsl:for-each select="Client">
        <ROW>
          <CLIENTID><xsl:value-of select="@ClientID"/></CLIENTID>
          <CLIENTNAME><xsl:value-of select="@ClientName"/></CLIENTNAME>
          <IRDNUMBER><xsl:value-of select="@IRDNumber"/></IRDNUMBER>
          <DOB><xsl:value-of select="@DOB"/></DOB>
          <TACTACCOUNTNO><xsl:value-of select="@TactAccountNo"/></TACTACCOUNTNO>
        </ROW>
      </xsl:for-each>
    </ROWSET>
  </xsl:template>
</xsl:stylesheet>

For an example of transforming XML using XSLT inside the database, see:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1827993798549
Once the original document is converted into the canonical XML format, it can
then be inserted into a table using the XML SQL Utility.  For an example of
doing this, see:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4061080732051
Hope that helps!

_____________________________________________________________________
Sean Dillon - Senior Technologist, Oracle Corporation
Author "Beginning Oracle Programming"
http://www.amazon.com/exec/obidos/ASIN/186100690X

      Rating: 5
Open Protocol
      Support  July 27, 2002
Reviewer:  Sean
      Dillon  from Reston, VA
Another way I'd mention that's NOT presented here is loading XML into the XML
Repository via Oracle's new open protocol support in 9i Release 2.  HTTP, FTP
and WebDAV are all available protocols for browsing and interacting with the
database.

For instance, I might load an XML Schema into the database for my CD collection.
 The XML Schema can use Oracle's object-relational storage capabilities to
designate how XML files should be stored under the covers.  Then, I can use any
WebDAV or FTP tool to insert XML documents into my database.

So yes, instead of using a PL/SQL or Java proc, I simply open up my handy-dandy
FTP tool and drag over my 250,000 xml documents into the database.  Now you can
get to that data using open protocols, SQL, PL/SQL, Java, etc.

Oh and also, you can use SQL*Loader to load XMLType columns now in 9iR2 :-).
Good luck w/ your XML endeavors!

      Rating: 5
Suggestion
        October 08, 2003
Reviewer:  A reader

Hi, i have went through many of the documents given in the OTN and also in this
site. But could not really get the exact answer related to my problem as i am
new to this topic. so can you kinldy help me out.

Here is my xml DTD.

<!------------------------ Start DTD----------------------->

<!ELEMENT MEPOutput (XMLInputOrder?,General?, MEPInputData?, MEPOutputData?)>
<!ELEMENT XMLInputOrder (OrderID)>

<!ELEMENT General (General.Column+)>

<!ELEMENT MEPInputData (UsedDatas,UsedDevices)>

<!-- UsedDatas -->
<!ELEMENT UsedDatas (UsedData)*>
<!ELEMENT UsedData (UsedInpDatum+)>
<!-- UsedDatas -->

<!-- UsedDevices -->
<!ELEMENT UsedDevices (Device)*>
<!ELEMENT Device (Name?,ViewName?,Konfiguration?)>
<!ELEMENT Konfiguration (KonfigFile*)>
<!-- UsedDevices -->

<!ELEMENT MEPOutputData (Sequences?)>
<!-- MEPOutputData hat Typaten und Sequences Daten -->

<!-- Sequences -->
<!ELEMENT Sequences (Sequence*)>
<!-- Eine Mess Sequence hat STDUP und Messpunkte (Lastpunkte) -->
<!ELEMENT Sequence (Name,ViewName? , (MeasPoint? | STDUP?)*)>
<!-- Sequences -->

<!-- STDUP -->
<!ELEMENT STDUP (Name?,ViewName?,STDUPSequenceData?, STDUPMeasData)*>
<!ELEMENT STDUPMeasData (STDUPMeasData.Column*)>
<!ELEMENT STDUPSequenceData (STDUPSequenceData.Column*)>
<!-- STDUP -->

<!-- MeasPoint -->

<!ELEMENT MeasPoint (Name?,ViewName?,MeasPointSteps*)>
<!ELEMENT MeasPointSteps (MeasPointStep)*>

<!ELEMENT MeasPointStep (Name?,ViewName?,StatusSequence?,
StepGeneral?,StepGeneralMeas?,SetPhase?,WaitPhase?,MeasPhase?)>
<!ELEMENT StepGeneral (StepGeneral.Column)*>
<!ELEMENT StepGeneralMeas (StepGeneralMeas.Column)*>

<!ELEMENT SetPhase (Beginphase, SetVar+)>
<!ELEMENT SetVar (Name?,ViewName?, VarDescription?, VarDevice?, DisplayFormat?,
ValueTransform?, VarSequData?,VarSetValues+)>
<!ELEMENT VarSequData (VarSequData.Column)*>

<!ELEMENT VarSetValues (VarSetValue)*>
<!ELEMENT VarSetValue (VarSet ,VarMeas?)>

<!ELEMENT MeasPhase (Beginphase, MeasVar+)>
<!ELEMENT MeasVar (Name?,ViewName?, VarDescription?,VarDevice?,DisplayFormat?,
ValueTransform?, VarSequData?,VarMeasValues+)>

<!ELEMENT VarMeasValues (VarMeasValue)*>
<!ELEMENT VarMeasValue (VarMeas+)>

<!ELEMENT WaitPhase (Beginphase, WaitVar+)>
<!ELEMENT WaitVar (Name?,ViewName?, VarDescription?,VarDevice?, DisplayFormat?,
ValueTransform?,VarSequData?,VarMeasValues+)>

<!ELEMENT VarSet ((SetValue? | ExtSetFile? ) ,SetState?,SetTime?)*>
<!ELEMENT VarMeas ((MeasValue? |ExtMeasFile? ),MeasState?,MeasTime?)*>
<!ELEMENT ValueTransform ( FourPoint | Straight )>

<!-- MeasPoint -->

<!-- einzelne Elemente  -->

<!ELEMENT OrderID (#PCDATA)>
<!ELEMENT UsedInpDatum (#PCDATA)>
<!ELEMENT Name (#PCDATA)>
<!ELEMENT ViewName (#PCDATA)>
<!ELEMENT KonfigFile (#PCDATA)>

<!ELEMENT SetValue (#PCDATA)>
<!ELEMENT MeasValue (#PCDATA)>
<!ELEMENT ExtMeasFile (#PCDATA)>
<!ELEMENT ExtSetFile (#PCDATA)>
<!ELEMENT MeasState (#PCDATA)>
<!ELEMENT SetState (#PCDATA)>
<!ELEMENT SetTime (#PCDATA)>
<!ELEMENT MeasTime (#PCDATA)>
<!ELEMENT StatusSequence (#PCDATA)>
<!ELEMENT VarDevice (#PCDATA)>
<!ELEMENT STDUPSequenceData.Column (#PCDATA)>
<!ELEMENT STDUPMeasData.Column (#PCDATA)>
<!ELEMENT General.Column (#PCDATA)>
<!ELEMENT StepGeneralMeas.Column (#PCDATA)>
<!ELEMENT StepGeneral.Column (#PCDATA)>
<!ELEMENT VarSequData.Column (#PCDATA)>
<!ELEMENT Beginphase (#PCDATA)>
<!ELEMENT VarDescription (#PCDATA)>
<!ELEMENT DisplayFormat (#PCDATA)>

<!ELEMENT FourPoint (#PCDATA)>
<!ELEMENT Straight (#PCDATA)>

<!-- einzelne Elemente  -->

<!-- Hier kommen die Attribute und ENTITies-->
<!ENTITY % ColumnAttributes "
    id CDATA #REQUIRED
    View CDATA #IMPLIED
    Unit CDATA #IMPLIED
    DataType (double | integer | text | KonfigFile) 'double'
    Format CDATA #IMPLIED
    ViewValue CDATA #IMPLIED
">

<!ENTITY % VarAttributes "
    id CDATA #REQUIRED
    Unit CDATA #IMPLIED
    DataType (double | integer | text) 'double'
">

<!ENTITY % VarTime "
    TimeBase (msec | sec )  #IMPLIED
    TimeTrigger (BeginSetPhase|BeginMeasPhase) #IMPLIED
">

<!ENTITY % MEPDataVersionid "   Model     CDATA #IMPLIED
                            Version   CDATA #IMPLIED
">

<!ENTITY % UsedDataTypes " (Typdata |Sequdata | ExtSequSetValue |RegistryKonfig)
#REQUIRED">

<!ENTITY % MeasMeasQuality "(Last | Average | Single | ExternFile) #IMPLIED">

<!ENTITY % KonfigFileTyp
"(ISO-CAN|Newport|DDE-Loaddatei|LeCroy|Inca-Descr|Inca-Hex|ASAP-Konf|MEPRegler)
#REQUIRED ">

<!-- comments -->

<!ATTLIST STDUP id ID #REQUIRED>
<!ATTLIST MeasPoint id ID #REQUIRED
                  Typ (Normal | Steps  | Revolution) #IMPLIED>

<!ATTLIST MeasPointStep id CDATA #REQUIRED
                   Nr CDATA #IMPLIED >

<!ATTLIST Section.Column %ColumnAttributes; >
<!ATTLIST STDUPSequenceData.Column %ColumnAttributes; >
<!ATTLIST STDUPMeasData.Column %ColumnAttributes; >
<!ATTLIST StepGeneral.Column %ColumnAttributes; >
<!ATTLIST StepGeneralMeas.Column %ColumnAttributes; >
<!ATTLIST VarSequData.Column %ColumnAttributes; >

<!ATTLIST SetVar %VarAttributes; >
<!ATTLIST MeasVar %VarAttributes; >
<!ATTLIST WaitVar %VarAttributes; >

<!ATTLIST MeasTime %VarTime; >
<!ATTLIST SetTime %VarTime; >

<!ATTLIST MEPOutput Version CDATA #IMPLIED>

<!ATTLIST Sequence id ID #IMPLIED>
<!ATTLIST General.Column id CDATA #REQUIRED>
<!ATTLIST Device DeviceID ID #REQUIRED
         Version  CDATA  #IMPLIED
>

<!ATTLIST KonfigFile Typ %KonfigFileTyp;
             Version CDATA #IMPLIED
>

<!ATTLIST MEPInputData %MEPDataVersionid; >
<!ATTLIST MEPOutputData %MEPDataVersionid; >

<!ATTLIST UsedData UsedDataType %UsedDataTypes; >

<!ATTLIST UsedInpDatum Typ %UsedDataTypes;
            Version CDATA #IMPLIED
            LastUpdate CDATA #IMPLIED
            LastTimeStamp CDATA #IMPLIED
>

<!ATTLIST VarSetValue Step CDATA #IMPLIED>
<!ATTLIST VarSetValues SetTyp (Unique|Steps) #IMPLIED
            MeasQuality %MeasMeasQuality;
>

<!ATTLIST VarMeasValue Step CDATA #IMPLIED>
<!ATTLIST VarMeasValues MeasTyp (Unique|Steps) #IMPLIED
            MeasQuality %MeasMeasQuality;
>
<!ATTLIST VarMeas Name CDATA #IMPLIED
          Typ (ExternFile)  #IMPLIED>

<!ATTLIST VarSet Name CDATA #IMPLIED
             Typ (ExternFile)  #IMPLIED>

<!ATTLIST ValueTransform  Typ (FourPoint | Straight) #REQUIRED >

<!ATTLIST FourPoint    X0 CDATA #REQUIRED
                  X1 CDATA #REQUIRED
            Y0 CDATA #REQUIRED
            Y1 CDATA #REQUIRED
>
<!ATTLIST Straight    Gradient CDATA #REQUIRED
            Offset   CDATA #REQUIRED
>

<!--------------------- END DTD---------------------------->

If i  have such a DTD and i need to load the XML file into the database, what
would be the method i should use.

After the file is loaded i should be able to display the information to the user
and also allow the user to query on it.
I am using 9iR2 database and my GUI application will be in Forms9i.
Also i 8i i tried parsing the XML files in pl/sql using XMLDOM parser but it
gives error when the size of the document is too large. So is this problem still
exists in 9iR2.

you response will be greatly appreciated.

          Followup:

I asked Sean Dillon, our local XML Technologist, to take a look at this and
here's what he had to say:
--

DTD's are what I like to call "the old".  XML Schemas are "the new".  In
Oracle8i and Oracle9i, we have support for DTD validation and generation.  In
Oracle9iR2, we introduced new functionality called XML DB, which is an XML
Schema-based approach to storing, querying, manipulating, securing, accessing,
etc. your XML documents.  The first question I would ask you would be... can you
upgrade to an XML Schema from the above listed DTD?  There are tools that help
do this (Oracle doesn't offer them, they're 3d party) if converting was an
issue.  With an XML Schema as your documents validation point, Oracle's
abilities become exponentially better.

That being said... you can certainly store and query DTD-based XML documents.
Since you're using 9iR2, you would use the XMLType to store and query those
docs.  XMLType is a new datatype introduced in Oracle9iR1.  What you want to do
is familiarize yourself with using XMLType columns or tables.  There are a
couple of ways to do this, ideally you'd use the documentation and Oracle's OTN.
 A few URLS:

http://otn.oracle.com/tech/xml/index.html
http://otn.oracle.com/tech/xml/learner.html
http://otn.oracle.com/tech/xml/htdocs/XDBDemo1.html
The last one, the XDBDemo1.html, is going to give you practical examples of what
you are looking for.

I can't tell you how to do this all through Forms9i, I don't really use our
Developer Suite.  One question for you, are you looking to display the XML
document or the contents of the XML document?  This is the difference between
stuff like:

<employee id="21342">
  <fname>Sean</fname>
  <lname>Dillon</lname>
  <hobby>SQL</hobby>
</employee>

...and...

 Employee#: 21342
First Name: Sean
 Last Name: Dillon
     Hobby: SQL

(just an example)

On the OTN site above, you will find *many* XMLDOM and DBMS_XMLDOM (the db
supplied package you SHOULD be using w/ 9i+ databases) code examples.  Without
the error you received due to a "too large" document, I'd like to see the error
itself.

I hope that helps!

      Rating:
      4
  October 08, 2003
Reviewer:  Raghu
       from India
Hi Sean,

Thanks for the fast response.
I can shift to XML Schema based implementation by converting the DTD's to
Schemas.

The data i want to display to the user is the second way as mentioned by you. I
want to store the data from the xml files and display to the end user in
relational way.

I tried to access the demo link without success. some problem with the link.

I am familiar with XMLTYPE column datatype of 9iR2.
Can you please give me an example with schema based approach w.r.t to my
problem.

Your response will be highly appreciated.

          Followup:

Your example, EXACTLY, can be found HERE:
http://otn.oracle.com/tech/xml/xmldb/index.html
If you can't get to that page, you need to register an account on OTN (free
registration).  Once you've got a registered account, go to that page and click
on the "Oracle XML DB Downloadable Demonstration Source" link.  This is a
download for a good sized demo that shows you all the basics of XML DB use...
This is a list of just some of the things you do in the demonstration:

- Load an XML schema into Oracle XML DB
- Register the schema with the database
- FTP a few hundred XML documents into the database (via the XML Repository)
- Query an object/relational (the PURCHASEORDERS) table, see a few hundred
records (the loaded xml docs)
- Create views that looks like a relational query against the xml documents
- Query those views, do joins on those views, etc.

Hope that helps!

_smd_

      Rating: 3
External files
      in xmltype-table with sqlldr  December 10, 2003

Reviewer:  Ralf  from Dresden, Germany
Hi, Tom

I'm looking for a way to fill a xmltype-table (create table t of xmltype) with
external xml-files via sqlldr. I found a way to do it, if it is a normal table
with xmltype-column, but this solved not my problem.  9iR2

Thanks for your help

Ralf

October 09, 2003 Ramasubbu -- Thanks for the question regarding
      "Creating dll for executing external procedure('c' language)", version
      Version 8i

      You Asked
Hi Tom,
      I am using Wint NT,Oracle 8i(server) and C language.
      My goal is calling 'c' routine thru stored procedure.
      For that I had made neccesary steps.I had modified
Tnsnames and listener entry as follows.

      Tnsnames entry
------------------------
extproc_connection_data =
   (DESCRIPTION =
       (ADDRESS =
          (PROTOCOL=IPC)
      (KEY=extproc_key) )
      (CONNECT_DATA =
          (SID = extproc_agent)
     )

Listener entry
-------------------
EXTERNAL_PROCEDURE_LISTENER =

(ADDRESS_LIST =
    (ADDRESS = (PROTOCOL=ipc)
               (KEY=extproc_key)
     )
)

SID_LIST_EXTERNAL_PROCEDURE_LISTENER =

(SID_LIST =
     (SID_DESC = (SID_NAME=extproc_agent)
                 (ORACLE_HOME=d:\oracle8i)
                 (PROGRAM=extproc)
      )
)

Then I want to create the dll.Here I have a doubt how to use the
dll.Any my 'C' routine is
/*This program will gives square the number*/
/*square.c*/
#include <stdio.h>
main(int argc,char *argv[])
{
  int no;
  no = argv[1];
  return no*no;
}
My next question is "Would I need to include any OCI statements
inside of 'C' routines?".If say yes,Please tell me how to I do
it?.

      and we said...
You do not create a DDL with a main.  You create an EXE with a
main.

You do not *need* to have OCI in your extproc.  Many (most) do
however -- to handle errors and such.

Look in $ORACLE_HOME\plsql\demo for ext*.*.  There is a full
example of an extproc routine there including the C code for you
to review.

It is recommended to test the installation of external
procedures with the demonstration programs.  The reason for this
is twofold

ƒá Oracle support is aware of and can help setup/configure the
demonstration program.  If we work with an example they know
about ¡V we¡¦ll be able to resolve any issues that much faster

ƒá The supplied demonstration program will illustrate the
correct approach for compiling and linking on your platform

The demonstration program is found in your
$ORACLE_HOME/plsql/demo directory on all releases of Oracle8i.
The steps we should follow to make the demonstration are:

Compile the extproc.c code into a DLL or .so/.sl/.a file.

The process for doing that on NT is to simply cd to the
ORACLE_HOME\plsql\demo directory and type make (they have
supplied a Make.BAT file in that directory):

C:\oracle\plsql\demo>make
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version
10.00.5270 for 80x86
Copyright (C) Microsoft Corp 1984-1995. All rights reserved.

extproc.c
Microsoft (R) 32-Bit Incremental Linker Version 3.00.5270
Copyright (C) Microsoft Corp 1992-1995. All rights reserved.

/out:extproc.dll
/dll
/implib:extproc.lib
/debug
..\..\oci\lib\msvc\oci.lib
msvcrt.lib
/nod:libcmt
/DLL
/EXPORT:UpdateSalary
/EXPORT:PercentComm
/EXPORT:PercentComm_ByRef
/EXPORT:EmpExp
/EXPORT:CheckEmpName
/EXPORT:LobDemo
extproc.obj
C:\oracle\plsql\demo>

In Unix, you will do much the same but the command to compile is
different.  There it will look like:

$ make -f demo_plsql.mk extproc.so
/usr/ccs/bin/make -f
/export/home/ora816/rdbms/demo/demo_rdbms.mk extproc_callback
SHARED_LIBNAME=extproc.so OBJS="extproc.o"
¡K..

After that command completes, you will have a .DLL file on NT or
a .so/.sl/.a file on Unix (extension depends on platform.  For
example, Solaris is .so, HP/UX will be .sl).

Set up the scott/tiger account

For this demonstration program to work correct, we will need a
scott/tiger demo account.  If your database does not have a
scott/tiger account, you can set one up by issuing:

   SQL> grant connect, resource to scott identified by tiger;

That will create the scott user and give it the ability to
connect to the database and create objects like tables and
packages.  You will most like want to assign this use a default
tablespace other then SYSTEM and a temporary tablespace as well.

Given that we have the scott/tiger account, we must provide it
with one additional grant before proceeding.  The SCOTT user
will need the ¡§CREATE LIBRARY¡¨ privilege.  This privilege will
allow SCOTT to issue the create library statement needed for
external procedures (more on that in a moment).  As this is a
fairly powerful privilege ¡V you will want to consider revoking
it from SCOTT after running the example.  To accomplish this you
will:

   SQL> grant create library to SCOTT;

When connected as someone who has the CREATE LIBRARY privilege
with the ADMIN option themselves (eg: SYSTEM or some DBA
account).

Lastly, you will want to ensure the EMP/DEPT demo tables are in
the scott schema and populated with data.  You can verify this
via:

SQL> select count(*) from emp;

  COUNT(*)
----------
        14

SQL> select count(*) from dept;

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

If these tables do not exist or do not have any data ¡V you can
rebuild them by executing demodrop.sql (to remove them) and
demobld.sql (to create and populate them).  These scripts are
found in $ORACLE_HOME/sqlplus/demo and are meant to be executed
via sqlplus when logged in as SCOTT.

Create the demolib LIBRARY

The next step in the demonstration is to create the library
object in Oracle.  This object is simply a mapping of a library
name (some 30 character name you choose) to a physical operating
system file.  This OS file is your compiled binary we made in
the first step.  The user who issues the create library
statement must have the CREATE LIBRARY privilege granted to them
either via a ROLE or directly.  This privilege is considered a
fairly powerful one and should be given out only to those
accounts you trust with the privilege.  It will allow them to
execute any arbitrary C code they wish on your server machine
using the account the extproc service executes with.  This is
one of the reasons you would want to configure the extproc
service to execute under some account other then the Oracle
software owner (to avoid the inadvertent or malicious
overwriting of your System tablespace for example).

In order to accomplish this step, you use SQLPlus and execute:

SQL> connect scott/tiger
Connected.
SQL> create or replace library demolib as
'c:\oracle\plsql\demo\extproc.dll';
  2  /

Library created.

The name DEMOLIB is what the developers of the demo chose as the
name of their library ¡V you must use DEMOLIB.  The file name
c:\oracle\plsql\demo\extproc.dll may be different for you ¡V I
built the example directly in the demo directory of my
Oracle_Home.  You may have a different Oracle_Home then I do or
you might have built the demo in some other directory entirely
¡V you should use the actual path name of the extproc.dll you
built in the first step.

Installing and running  the demo packages

The last step in the demo is to install the PLSQL code that maps
to routines in the demolib library.  We are not interested at
this point in what they look like as much as we are what they
output ¡V we are using this demo to test external procedures,
we¡¦ll look at how we code them in a bit.

Now we will simply execute:

SQL> connect scott/tiger
Connected.
SQL> @extproc

When in the $ORACLE_HOME/plsql/demo directory.  What we expect
to see is:

SQL> @extproc

Package created.

No errors.

Package body created.

No errors.
ENAME      : ALLEN
JOB        : SALESMAN
SALARY     : 1600
COMMISSION : 300
Percent Commission : 18.75
ENAME      : MARTIN
JOB        : SALESMAN
SALARY     : 1250
COMMISSION : 1400
Percent Commission : 112
Return value from CheckEmpName : 0
old_ename value on return      : ANIL
ENAME      : 7369
HIREDATE   : 17-DEC-80
Employee Experience Test Passed.
***************************************

PL/SQL procedure successfully completed.
¡K. (other feedback would be here as well)¡K.

That shows that external procedures are correctly configured and
ready to be used on our system ¡V the first procedure executes
many of the routines in the extproc.dll we created.  This shows
conclusively that all is configured correctly.

In the event of an incorrectly configured system, you would
expect to see:

SQL> @extproc

Package created.

No errors.

Package body created.

No errors.
BEGIN demopack.demo_procedure; END;

*
ERROR at line 1:
ORA-28575: unable to open RPC connection to external procedure
agent
ORA-06512: at "SCOTT.DEMOPACK", line 61
ORA-06512: at "SCOTT.DEMOPACK", line 103
ORA-06512: at line 1

That implies that it is time to revisit your tnsnames and
listener.ora configuration.

Review &
      Followup

      Rating:
      5
  November 21, 2001
Reviewer:
      A reader
hi tom

i don't have make.bat in
%oracle_home%\plsql\demo directoty
it's prcmake.bat

i read the text in side and it is telling
-- -----------------
Rem Use this script to create executables for the demo scripts with PL/SQL
Rem blocks embedded in C code.  To create all executables, at the prompt type:

 if (%1) == () goto usage

Rem MSVCDir is defined by running a batch file, vcvars32.bat,
Rem in Microsoft Visual Studio directory.  You can find it using
Rem Windows Explorer.
Rem Environment Check:
 if (%MSVCDir%) == () goto msvcdir_error
-- ---------
i don't have visual studio

I am running personal oracle 8.1.7 on windows 98

i want to call executable from pl/sql at my work.
so please respond as soon as possible

          Followup:

If you don't have MAKE.BAT you don't have a version of Oracle with extprocs.
prcmake.bat doesn't make the extprocs.  you need make.bat.

if you

dir ext*

in that directory -- you should see:

 Directory of C:\oracle\plsql\demo

02/29/2000  02:03p              17,853 extproc.c
02/29/2000  02:03p              15,779 extproc.sql

if not, you don't have the extproc stuff.

I've never installed Personal Oracle -- I suppose there is a chance its "not
there".

try downloading

http://asktom.oracle.com/~tkyte/extproc.tar
I've tarred up (winzip will extract it) the source and make.bat.  Lets start
there and see what happens.

      Rating:
      5
  November 21, 2001
Reviewer:
      A reader
Tom
So FAST REPLY!!!!!!!!!!!!!!!!!!!!!!!!!!!

IT REALLY HELPS ME A LOT
CAN'T BELIEVE IT

I REALLY APPRICIATE IT

YOU ARE REALLY BOON TO DEVLOPER COMMUNITY

HAVE NICE THANKSGIVING

      Rating:
      5
  November 21, 2001
Reviewer:
      A reader
Another question.

External procedure in C is better
OR java stored procedure is better
(Consider Resources and perfomance of database)

          Followup:

It is a double edged sword.

C will be faster, its closer to the machine.

Java will be a little slower but it is approaching the speed of C with regards
to performance when NCOMP'ed (natively compiled)

C will run outside the address space of the server -- it will take another
process.  Java runs inside the database, in the same address space.  No extra
process.

Calling a java stored procedure for the first time in a session takes a bit of
time (jvm startup).  Its getting better with each release but its still rather
large the first time.  A C extproc is not.  This mostly counts in a stateless
environment where "for the first time in a session" happens very often.

My mantra in general is:

o if you can do it in SQL, do it
o if not, do it in PLSQL, if you cannot do it there (eg: ftp'ing a file, writing
a binary file, etc)
o do it in Java in a simple stored procedure
o if Java won't do it (you have a 3rd party api, existing C code) do it in C as
an extproc.

      Rating: 5
make.bat and MS
      .net  June 18, 2003
Reviewer:  Jeff Yuan
       from AZ USA
First of all, thank you very much for your excellent coverings on extproc in
your book and here.

I believe the existing make.bat (from Oracle 9.0.1 installation) works with MS
Visual Studio. Now in my box, I have only MS Visual Studio .NET installed,
including MS Visual C++ .NET. I can not have MS Visaul C++ in same box.(?)

When I try to run make.bat, I doesn't work. I don't know much about .NET. Could
you please look into this? Is some modification on the make.bat necessary to
find the C++ compiler?

Regards,
Jeff
~~~~~~~~~~~~~~
C:\oracle\plsql\demo>make.bat
.
Environment variable MSVCDIR must be set before running this batch file.
Please run vcvars32.bat from MS Visual Studio directory.
.

C:\>cd program files\microsoft visual studio .net

C:\Program Files\Microsoft Visual Studio .NET>cd vc7\bin

C:\Program Files\Microsoft Visual Studio .NET\Vc7\bin>vcvars32.bat

C:\Program Files\Microsoft Visual Studio .NET\Vc7\bin>"C:\Program Files\Microsof
t Visual Studio .NET\Common7\Tools\"vsvars32.bat
Setting environment for using Microsoft Visual Studio .NET tools.
(If you also have Visual C++ 6.0 installed and wish to use its tools
from the command line, run vcvars32.bat for Visual C++ 6.0.)

C:\>cd oracle\plsql\demo

C:\oracle\plsql\demo>make
Files\Microsoft was unexpected at this time.

          Followup:

looks like there might be an issue with spaces in the filenames.

maybe you can use the 8.3 filenames instead.  I don't have access to any windows
machines, sorry.

      Rating:
      3
vc  September 17, 2003
Reviewer:
      Aladdin  from india
sir how could i use external dll files not  made by c language but with visual c
++ or vb

          Followup:

you just need to know the "c prototype" they use.  then you can map to it.

      Rating: 4
Need a
      clarification  October 08, 2003
Reviewer:
      Sanjay Jha  from Toronto
For using External Proc..do we need any additional piece of software? I am not
able to run make.bat. It complains:

C:\oracle\ora81\plsql\demo>make
'cl' is not recognized as an internal or external command,
operable program or batch file.

I referred metalink doc id (70110.1)and it says, I need to run vcvars32.bat
before running make.bat:
**********************************************************
3. Use the make.bat available in the %ORACLE_HOME%\rdbms\extproc directory.  You
   need to run vcvars32.bat file before running this batch file.  This will
create a dll file.
**********************************************************

However, I did not see that step mentioned in your book, nor do I see a mention
of the fact that I need any extra piece of software than Oracle RDBMS. Do I need
Visual Studio to be installed, since I did not see vcvars32.bat, cl.exe etc.
anywhere in my Oracle folder?

          Followup:

you need a C compiler, yes.  In order to compile C, you need a C compiler.

You do not need a C compiler if you have a DLL already.

You do need a C compiler to turn C into a DLL on windows.

October 09, 2003 Shivaswamy -- Thanks for the question regarding
      "Analytic..Function", version 817

      You Asked
Tom,
I have a table to monitor my portfolio.
16:14:49 swamy@TEST9.US.ORACLE.COM>  desc trans;
 Name                                                                     Null?
  Type
 ------------------------------------------------------------------------
-------- -----------------
 DT
  DATE
 TICKER
  VARCHAR2(5)
 RATE
  NUMBER(10,4)
 QTY
  NUMBER
 TRANS
  CHAR(1)

16:15:05 swamy@TEST9.US.ORACLE.COM>

Trans will have either 'B' for bought or 'S' for sold.
I want to have a report of all the transactions I have made and the
corresponding net result for all the sold transactions mentioned. I am looking
for a report like this:

DT        TICKER ACTION       RATE        QTY        AMT    P/L
--------- ------ ------ ---------- ---------- ----------    ---
29-MAY-03 ADSK   BOUGHT     15.269        125    1908.63
06-JUN-03        SOLD       16.211        125    2026.38

03-JUN-03 AMGN   BOUGHT      63.53         54    3430.62
05-JUN-03        SOLD        65.54         54    3539.16

But I want net profit/loss for each ticker/transaction.(I may have multiple
transaction for the same ticker). Can you please help me? My efforts with Lag
did not give me desired results.

Shivaswamy

      and we said...
Analytics rock, Analytics roll... all you needed was "sum()"

ops$tkyte@ORA920> create table t ( dt date, ticker varchar2(5), rate number, qty
number, trans varchar2(1) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( sysdate, 'ABC', 15, 2000, 'B' );

1 row created.

ops$tkyte@ORA920> insert into t values ( sysdate, 'XYZ', 15, 2000, 'B' );

1 row created.

ops$tkyte@ORA920> insert into t
  2  select sysdate+rownum,
  3         decode( mod(rownum,2), 0, 'ABC',  'XYZ' ),
  4         round(dbms_random.value( 12, 20 ),2),
  5             trunc(dbms_random.value( 100, 500 )),
  6             decode( round(dbms_random.value(1,2)), 1, 'B', 'S' )
  7    from all_users
  8
ops$tkyte@ORA920> /

43 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> break on ticker skip 1
ops$tkyte@ORA920>
ops$tkyte@ORA920> select ticker,
  2         dt,
  3             to_char(rate, '999,999.99' ),
  4             abs(qty) qty,
  5             to_char(rate*qty,'999,999.99pr') amt,
  6             sum(rate*qty) over (partition by ticker order by dt)
running_total
  7    from (
  8  select dt, ticker, rate, decode( trans, 'B', +1, -1 )*qty qty
  9    from t
 10         )
 11   order by ticker, dt
 12  /

TICKE DT        TO_CHAR(RAT        QTY AMT          RUNNING_TOTAL
----- --------- ----------- ---------- ------------ -------------
ABC   15-JUN-03       15.00       2000   30,000.00          30000
      17-JUN-03       14.46        379   <5,480.34>      24519.66
      19-JUN-03       17.32        457    7,915.24        32434.9
      21-JUN-03       13.76        320    4,403.20        36838.1
      23-JUN-03       12.51        444   <5,554.44>      31283.66
      25-JUN-03       12.87        168    2,162.16       33445.82
      27-JUN-03       14.78        410    6,059.80       39505.62
      29-JUN-03       14.12        139    1,962.68        41468.3
      01-JUL-03       13.02        132    1,718.64       43186.94
      03-JUL-03       12.94        466    6,030.04       49216.98
      05-JUL-03       13.58        266   <3,612.28>       45604.7
      07-JUL-03       19.05        105   <2,000.25>      43604.45
      09-JUL-03       16.74        293   <4,904.82>      38699.63
      11-JUL-03       18.60        176   <3,273.60>      35426.03
      13-JUL-03       12.43        190    2,361.70       37787.73
      15-JUL-03       12.79        318    4,067.22       41854.95
      17-JUL-03       15.78        158    2,493.24       44348.19
      19-JUL-03       15.29        387   <5,917.23>      38430.96
      21-JUL-03       14.58        477    6,954.66       45385.62
      23-JUL-03       19.77        200   <3,954.00>      41431.62
      25-JUL-03       12.35        492   <6,076.20>      35355.42
      27-JUL-03       13.09        125   <1,636.25>      33719.17

XYZ   15-JUN-03       15.00       2000   30,000.00          30000
      16-JUN-03       14.82        319    4,727.58       34727.58
      18-JUN-03       16.81        311   <5,227.91>      29499.67
      20-JUN-03       17.21        156   <2,684.76>      26814.91
      22-JUN-03       17.07        127   <2,167.89>      24647.02
      24-JUN-03       19.67        248    4,878.16       29525.18
      26-JUN-03       14.08        140    1,971.20       31496.38
      28-JUN-03       12.79        126   <1,611.54>      29884.84
      30-JUN-03       16.46        189    3,110.94       32995.78
      02-JUL-03       13.90        372    5,170.80       38166.58
      04-JUL-03       12.08        224   <2,705.92>      35460.66
      06-JUL-03       19.74        444   <8,764.56>       26696.1
      08-JUL-03       13.40        399    5,346.60        32042.7
      10-JUL-03       17.38        176    3,058.88       35101.58
      12-JUL-03       17.92        178    3,189.76       38291.34
      14-JUL-03       13.60        208    2,828.80       41120.14
      16-JUL-03       19.18        126   <2,416.68>      38703.46
      18-JUL-03       12.33        393   <4,845.69>      33857.77
      20-JUL-03       18.25        464   <8,468.00>      25389.77
      22-JUL-03       12.18        400   <4,872.00>      20517.77
      24-JUL-03       19.64        167    3,279.88       23797.65
      26-JUL-03       18.89        129   <2,436.81>      21360.84
      28-JUL-03       18.12        385   <6,976.20>      14384.64

45 rows selected.

Review &
      Followup

      Rating: 4
generate A-Z
      randomly  October 07, 2003
Reviewer:  Saradha
      Bavanandam  from NJ, USA
Tom,

I need to generate a letter A-Z randomly and I am
thinking to use DBMS_RANDOM.VALUE for this.

SELECT chr(trunc(DBMS_RANDOM.VALUE(65,90)) from dual
/

Can you please advice me if this is the right approach
or is there anything else that you can suggest me.

Thanks
Saradha

          Followup:

value returns a number x such that

low <= x < hi

so, you'll want to use 91, not 90 but otherwise, looks good.  gets a nice
spread:

ops$tkyte@ORA920> create table t
  2  as
  3  select chr(trunc(DBMS_RANDOM.VALUE(65,91))) letter
  4    from all_objects;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select letter, count(*)
  2   from t
  3  group by letter;

L   COUNT(*)
- ----------
A       1176
B       1183
C       1137
D       1096
E       1130
F       1139
G       1149
H       1176
I       1187
J       1200
K       1142
L       1208
M       1144
N       1121
O       1141
P       1201
Q       1178
R       1150
S       1153
T       1166
U       1206
V       1194
W       1205
X       1135
Y       1143
Z       1146

26 rows selected.

      Rating: 5
Thank
      You!!!  October 07, 2003
Reviewer:  Saradha
      Bavanandam  from NJ, USA
Thank You very much Tom....

      Rating: 5
An interesting
      value generation  October 08, 2003
Reviewer:
      Saradha Bavanandam  from NJ, USA
Tom,

I need to generate values randomly without duplicates
for ACC_NUM CHAR(7) and then generate values for another
column ACC_NUM_SUB based on ACC_NUM column.

The column size and the value of each position of the
columns are defined.

acc_num       CHAR(7)

acc_num_sub       CHAR(10)

ACC_NUM
---------------
The ACC_NUM is a 7 byte character, the first two positions are letters.
(example.. AA, AB, ... ZZ)
And the next five positions are running 5 digit sequence number. (example..
00001, 00002, 99999) within each two position characters.

Selecting the 2 characters is the random but within that the 5 digit numbers are
sequential.

For Example, the ACC_NUM will look like the following
-----------------------------------------------------

Prefix   ||  Suffix   =  ACC_NUM
--------------------------------
AA       ||  00001    = AA00001
AA       ||  00002    = AA00002
..       ..  .....      .......
AA       ||  99999    = AA99999
AB       ||  00001    = AB00001
...          .....      .......
ZZ       ||  99999    = ZZ99999

At one day, the total number of values generated will be (676x99999)=67,599,324.

I will create a control table and will manually insert the two char prefix with
(AA-ZZ) and 5 char suffix with 0. So there will (26x26 = 676) rows will be
inserted and maintained to find the next sequence number within each two letter
prefix each time.

Create table t_acc_num_gen
(
Prefix_val  CHAR(2)
Suffix_val  NUMBER(5)
)
/

Prefix_val  Suffix_val
----------  ----------
AA                   0
AB                   0
..                   .
BA                   0
..                   .
ZZ                   0

676 rows.

All the suffix_val initially inserted with 0 and whenever
a acc_num need to be generated, the suffix_val will be incremented by 1 and the
suffix_val in the table will be updated for that prefix.

A sample stored procedure to generate the acc_num.
--------------------------------------------------

CREATE OR REPLACE PROCEDURE get_next_acc_num
                           (accnum_param OUT CHAR)
IS

/** Since the acc_num generation should not have
    affect the pending transaction of that session,
    I want to have this is as an autonomous transaction
**/
PRAGMA AUTONOMOUS_TRANSACTION;
prfx_val     CHAR(2);

BEGIN

    /** Randomly select the first two letter prefix **/
    prfx_val := chr(trunc(dbms_random.value(65,91)))||
                chr(trunc(dbms_random.value(65,91)));

  /* Since this procedure will be called simlutaneously
     from the Front-End application, the row will be
     selected for update
  */
  SELECT prefix_val||LPAD(to_char(suffix_val+1),5,'0')
  INTO   accnum_param
  FROM   t_acc_num_gen
  WHERE  prefix_val = prfx_val
  FOR UPDATE;

  UPDATE t_acc_num_gen
  SET    suffix_val = suffix_val+1
  WHERE  prefix_val = prfx_val;

  COMMIT;
END;
/

Get the acc_num
----------------
Call the stored procedure and get the ACC_NUM.

DECLARE
  acc_num_val char(7);
BEGIN
  get_next_acc_num (acc_num_val);
END;
/

So finally the control table will look like the following
and by then we already generated 67,599,324 acc_num
and that is more than enough.

Prefix_val  Suffix_val
----------  ----------
AA               99999
AB               99999
..               99999
BA               99999
..               99999
ZZ               99999

676 rows.

With this approach, the first two letters are picked up randomly but the numbers
within each prefix is incremented sequentially and that is what we want.

Now the toughest part comes.
----------------------------------------

ACC_NUM_SUB     CHAR(10)
-------------------------

The ACC_NUM_SUB is a 10 byte character and it needs to
be generated within each ACC_NUM. So Each ACC_NUM will have
many ACC_NUM_SUB numbers created.

It is like parent and child relationship.
The ACC_NUM is the parent and the ACC_NUM_SUB is the child.

The first 7 positions are the actual ACC_NUM. (AA00001)
The 8th position is a letter . (A-Z)
The 9th and 10th positions are running sequence numbers.(01..99)

So for the ACC_NUM_SUB, the prefix is the ACC_NUM that has been generated
already and the suffix is the running 3 characters (01A, 02A, ..99A,01B..).

For Example

Prefix        Suffix    =  acc_num_sub
---------     ------       -----------
AA00001       A01       =  AA00001A01
AA00001       A02       =  AA00001A02
.......       ...          ..........
AA00002       A99       =  AA00002A99
.......       ...          ..........
ZZ99999       Z99    =  ZZ99999Z99

The ACC_NUM will be generated first and then many ACC_NUM_SUB will be created
within each ACC_NUM.

The requester of the ACC_NUM_SUB will provide any ACC_NUM and we need to
generate the last 3 charaters and attach it to the 7 byte acc_num to make it as
a 10 byte ACC_NUM_SUB.

My biggest questions are:

1. How do I generate the 3 character suffix for ACC_NUM_SUB within each ACC_NUM.

2. How do I find out the highest suffix within a ACC_NUM each time to generate
the next sequence.

If I have to keep this in a table, then there will be millions of rows just to
find out the next sequence of the last three bytes for each given ACC_NUM.

How do I resolve this..
Please help.

          Followup:

uh huh.  and this has to do what exactly with the previous question on this
page?

the other random question -- small, so i looked at it.

this is like a specification for an entire subsystem!

      Rating:
      2
  October 08, 2003
Reviewer:  Saradha
      Bavanandam  from NJ, USA
Tom,
Sorry If I had asked you too much. I had expected that
somebody may have asked you of this similar scenario.

Thanks
Saradha

      Rating:
      4
DBMS_RANDOM.STRING  October 08, 2003

Reviewer:  A reader  from USA
tom

to select a letter randomly, instead of doing
chr(trunc(DBMS_RANDOM.VALUE(65,91))), can the
DBMS_RANDOM.STRING function be used ?.

select UPPER(dbms_random.string('A',1)) from dual
/
B

thx

          Followup:

that should work as well -- as long as they stick the definition of an "ALPHA
string" as being only A-Z in the dbms_random package.

October 09, 2003 shahid -- Thanks for the question regarding
      "replication direction", version 9i

      You Asked
Hello Tom,

My question is not a new one as I have found so many articles on your website
which discuss the rechnical issues of replication.

However I am here asking your opinion rather architecture decision on what to
choose.

We have a situation where we have two discrete databases on different
servers servicing two different applications; one for Debit cards management and
other for credit cards management.
Both applications are vendor specific and belong to diff vendors and have diff
applications.

We have a situation where we have a few tables which are common to both
databases business wise. For example customer table where customer no is same
but other columsn are different.

What we want is to replicate the info from Database-A (for table-A)
across Database-B (Table-B) with certain where clauses
and update columsn in database-B.

The same holds true for reverse case.

Moreover we also have some specifc tables where infact the strusture is even
same and we want replication both ways from databaseA to B and vice versa.

We have two groups one says we should have a middle ware that deals with this
situation so we dont have trans loss.
They say replication is for simple stuff for drc, for reporting etc
and not TO BE USED as a tool for Application-Integration.
Becuase after deploung replication there will be issues or handling excpetions
like reconiliation of tran in case of failues of one machine etc. Replication
should not be used for mission critical online system in this manner - they say.

Other says why dont we use oracle replication to accomplish this.

Can we employ adv replication while we have diff table structure?
While we have same table structyre but we want to put where clauses?

What is 'Oracle Stream' some one mention that we can utilise this to fulfil all
our reqirments?

What do you have to say all about this? I mean the technical parts come later
but would you suggest we should go for replication??

cheers

      and we said...
well, typical replication is not appropriate here (what I call bits and bytes
replication)

but... not for the reasons expounded by these people -- not at all.  but, anyway

you are going to be limited by the off the shelf applications.  You cannot do
table level replication here -- you will HAVE to use their API's (and hopefully
they have them) to apply changes.  writing directly to their tables would be a
disaster (ask them if it is OK first -- ask them what happens when you call for
support)

Oracle Streams is the most appropriate technology here.  It'll capture the
changes, turn them into logical change records (messages) which you can
transform/whatever and then apply using the vendors API to the target tables.
It is middle ware like -- a big message queue.

See

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-STR

Review &
      Followup

      Rating:
      4
Replication  October 08, 2003
Reviewer:
      A reader
Tom,

I have the following situation: I have database A (DA) which is the database is
charge to support all the oltp applications and a couple of batch processes. I
also have database B (DB) which is in charge of support the 95% of the batch
processing.
The applications/batch processes on DA use some of the tables in DB (from now on
tables_B) and not only they read from tables_B but also they can
update/insert/delete from those tables_B. I also have ad hoc queries in both
databases.

I want to move all the batch processes and ad hoc queries to a different server,
so I will have DA in server A (SA) and DB in server B (SB). In this way  the
oltp applications will not be affected by the batch processing/ad hoc queries
activity which are running all day long and doing a lot of used of cpu and heavy
load.
My idea is to use replication for those tables which are being modified, in
order to maintain all the tables up to date at any moment in both databases (DA
and DB). So I will have some tables on DB which will be replicated to DA, and
some tables on DA which will be replicated to DB.
Would you mind giving me your opinion about my idea? Do you think I should use
any other alternative to do what I need to do?

Thanks in advance for any help.

          Followup:

I would consider a 2 node RAC cluster before replication.

Or the resource manager to control how much of the CPU/lios the batch processes
can consume when the OLTP guys want it.

Either would give you the ability to put each resource consumer "into their own
little box"

Distributed complexity, coupled with replication gets really ugly, really fast.

October 09, 2003 Rakesh -- Thanks for the question regarding
      "server details & input output", version 8.1.7

      You Asked
Hi Tom

  1) Like userenv gives us all details about the client.is there anyway i can
get server details , name of the terminal where oracle database is installed, th
IP, which OS its installed on.
 While searching , i got a java program which gives the server IP.
But does Oracle store these details somewhere where we can capture at the SQl
prompt.

   2)I have to do a file copy operation from 1 directory to another. Currently i
am using dbms_java & my problem is solved. But is there a way to do it in
PL/Sql.

  3)What is your  approach while  sql queries . Could you suggest how to make
oneself very strong in solving sql queries.

Bye
Rakesh

      and we said...
1)

ops$tkyte@ORA920> select dbms_utility.port_string, host_name from v$instance;

PORT_STRING
---------------------------------------------------------------------------------
--------------------------------------------------
HOST_NAME
----------------------------------------------------------------
Linuxi386/Linux-2.0.34-8.1.0
tkyte-pc-isdn.us.oracle.com

2) in 9i, utl_file can copy.  utl_file.fcopy.  In 8i if the file is TEXT, you
can:

a) open new file for write, old file for read
b) read old file, write new file

3) learn everything that is available -- how access paths work, what indexing
strategies are there, what functions you have and practice.
Review &
      Followup

      Rating: 3
FCOPY with
      binary  October 08, 2003
Reviewer:  Vikas
      Sharma  from Delhi India
Hi Tom,

I would like to know that ,does fcopy works with binary files also.

I have a routine which copy a file on server from one location to another
loaction everythingworks fine with text file but it does not work with doc or
gif or pdf files. It creates the file but the file is corrupt. For eg cannot see
any thing when i try toopen gif file into browser or cannot open a pdf fle in
acrobat.

Thanks And Regards,

Vikas Sharma

          Followup:

do a binary compare and tell us what is materially different.  if you see lots
of end of lines turned into carriage return/linefeed pairs, then it must be
using TEXT semantics on the file (and you must be on an OS that for whatever
reason picked \r\n as the end of line instead of just \n like the rest -- eg:
windows)

October 09, 2003 Steve -- Thanks for the question regarding "Using
      user function in SELECT,WHERE and ORDER BY", version 9.0.1.1.1

      You Asked
When using a user function in a query, why must I repeat the call to the
function in the select, where and order by in PL/SQL?  Why won't PL/SQL allow
this?  I get an ORA-00904 Invalid column name on the INSP_DT.

Want to do this:

SELECT STOCK_NUM,
       LOT_NUM
       QUANTITY,
       LOCATION,
       UTILS.NEXT_INSP_DT(STOCK_NUM,LOT_NUM) AS INSP_DT
 FROM STOCKPILE
WHERE INSP_DT > LAST_DAY(SYSDATE)
ORDER BY INSP_DT

But must do this:

SELECT STOCK_NUM,
       LOT_NUM
       QUANTITY,
       LOCATION,
       UTILS.NEXT_INSP_DT(STOCK_NUM,LOT_NUM) AS INSP_DT
FROM STOCKPILE
WHERE UTILS.NEXT_INSP_DT(STOCK_NUM,LOT_NUM)> LAST_DAY(SYSDATE)
ORDER BY UTILS.NEXT_INSP_DT(STOCK_NUM,LOT_NUM)

      and we said...
It is not PLSQL -- it is the very definition of SQL that prevents this

ops$tkyte@ORA920.LOCALHOST> select dummy as foo
  2    from dual
  3   where foo = 'X';
 where foo = 'X'
       *
ERROR at line 3:
ORA-00904: "FOO": invalid identifier

The selection is the very last thing done -- the "as insp_dt" hasn't happened in
the where clause, not in the order by.

You can use an inline view like this:

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> select *
  2    from ( select dummy as foo
  3          from dual
  4          )
  5   where foo = 'X';

F
-
X

ops$tkyte@ORA920.LOCALHOST>

However, performance will be BEST if you can avoid calling plsql from sql
alltogether -- something that is increasingly more often something that can be
done with analytic functions or select (select).
Review &
      Followup

      Rating: 4
Description
      Lookups - Joins versus Functions  August 27, 2002

Reviewer:  Gary Myers  from Sydney, Australia

Tom,

Regarding the comment :

"However, performance will be BEST if you can avoid calling plsql from sql
altogether -- something that is increasingly more often something that can be
done with analytic functions or select (select). "

and some previously stated comments that complex queries can often usefully be
hidden in a view, I'd like to here your opinion of this situation.

CREATE VIEW trans_view AS
select    c.client_id,
    c.client_type,
    c1.desc client_type_desc,
    c.client_status,
    c2.desc client_status_desc,
    a.account_id,
    a.account_type,
    a1.desc account_type_desc,
    a.account_action,
    a2.desc account_action_desc
    t.transaction_id,
    t.transaction_currency,
    t1.desc trans_curr_desc,
    t.transation_date,
from    clients c,
    accounts a,
    transactions t,
    ref_codes c1,
    ref_codes c2,
    ref_codes a1,
    ref_codes a2
    ref_codes t1
where    a.client_id = c.client_id
and    t.account_id = a.account_id
and    c1.code_type = 'CLIENT TYPE'
and    c2.code_type = 'CLIENT STATUS'
and    a1.code_type = 'ACCOUNT TYPE'
and    a2.code_type = 'ACCOUNT ACTION'
and    t1.code_type = 'CURRENCY TYPE'
and    c1.code = c.client_type
and    c2.code = c.client_status
and    a1.code = a.account_type
and    a2.code = a.account_action
and    t1.code = t.transaction_currency

against

CREATE FUNCTION get_desc (p_desc_type IN VARCHAR2, p_code IN VARCHAR2) return
VARCHAR2 IS
  v_return ref_codes.desc%TYPE;
BEGIN
  SELECT desc
  INTO    v_return
  FROM    ref_codes
  WHERE    code_type = p_desc_type
  AND    code = p_code;
  RETURN v_return;
END;

CREATE VIEW trans_view AS
select    c.client_id,
    c.client_type,
    get_desc('CLIENT TYPE',c.client_type) client_type_desc,
    c.client_status,
    get_desc('CLIENT STATUS',c.client_status),
    a.account_id,
    a.account_type,
    get_desc('ACCOUNT TYPE',a.account_type) account_type_desc,
    a.account_action,
    get_desc('ACCOUNT ACTION',a.account_action) account_action_desc
    t.transaction_id,
    t.transaction_currency,
    get_desc('CURRENCY TYPE',t.tranaction_currency) trans_curr_desc,
    t.transation_date
from    clients c,
    accounts a,
    transactions t
where    a.client_id = c.client_id
and    t.account_id = a.account_id

From tests I've run (against Oracle 8.1.7), in the second case the function
get_desc will only be run if the column is selected, whereas in the first case
Oracle must perform all the joins even if the relevant column isn't selected (as
it needs to know how many rows the join returns).

As such, I'd expect a performance trade off with the first view working faster
if most/all of the descriptions are selected, and the second option working
faster where fewer (zero or one maybe) of the descriptions are in the select
list.

To work out the 'sweet spot' of which is better, I think it will be necessary to
weigh up the time taken for get_desc to run and the number of times it's run,
against the percentage of queries where descriptions are selected. Is that right
?

PS. I'm assuming code_type and code make up a unique key on ref_codes, and that
all the expected matches are there.

          Followup:

How about this -- even better don't you think:

CREATE VIEW trans_view AS
select    c.client_id,
    c.client_type,
    ( SELECT desc
        FROM ref_codes
       WHERE code_type = 'CLIENT_TYPE'
         AND code = c.client_type ) client)_type_desc,
    c.client_status,
    .....

that is what I meant by "select (select)"

even better then dropping down to PL/SQL -- less context switching going on.

      Rating: 5
suggested
      solution is a big improvement  August 28, 2002

Reviewer:  Gary Myers  from Sydney, Australia

Excellent information,

I'd read about this left correlation, but it seemed to be concerned with nested
tables, and I didn't see that it could have performance benefits in thes sort of
circumstances.

One niggle is that the explain plan in 8.1.7 didn't show the access of the
ref_codes table whether the description was selected or not. However, I created
a 'hamstrung' version of the view that wouldn't use the index, and it was
obvious from the performance difference that, without the description selected,
the unnecessary select/join wasn't performed.

      Rating: 4
SELECT (SELECT)
      info hard to come by  October 24, 2002
Reviewer:
      Adrian Billington  from UK
Tom

Sorry to follow this up with a question (which you will possibly choose not to
answer) about SELECT..SELECT as Metalink came up with nothing that I could find.

I am trying to bulk collect from an inline view. The inline view is performing a
select key from nested table MINUS select (select ... ) from normal table, where
the select (select... ) returns one row.

In 8.1.6.3 (unfortunately the version I must develop for),  the SQL statement
(i.e. bulk collect bit commented out) works fine, but to run it in PL/SQL gives
me a runtime error saying it wasn't expecting the "(SELECT" after the SELECT
keyword. But it runs OK in 9.2.0.1. Using the SELECT SELECT is already a
workaround to a discrepancy in our model, so I'm wondering if you have any bug
or workaround knowledge for using it with bulk collect in PL/SQL.

Examples in 8i and 9i:-

816> create table x as select object_id, object_name, object_type from
user_objects;

Table created.

816> create table y as select rownum as surr_key from user_objects;

Table created.

816> create table z as select rownum as surr_key, object_id, object_name,
object_type from user_objects;

Table created.

816> delete from y where rownum <= 10;

10 rows deleted.

816>
816> declare
  2     type typ_nt_v2_4000 is table of varchar2(4000);
  3     nt   typ_nt_v2_4000;
  4  begin
  5     select object_id
  6     bulk collect into nt
  7     from (
  8           select object_id
  9           from x
 10           minus
 11           select (select object_id from z where surr_key = y.surr_key) as
object_id
 12           from y
 13          );
 14    dbms_output.put_line(nt.count||' fetched.');
 15  end;
 16  /
         select (select object_id from z where surr_key = y.surr_key) as
object_id
                 *
ERROR at line 11:
ORA-06550: line 11, column 18:
PLS-00103: Encountered the symbol "SELECT" when expecting one of the following:
( - + mod not null others <an identifier>
<a double-quoted delimited-identifier> <a bind variable> avg
count current exists max min prior sql stddev sum variance
execute forall time timestamp interval date
<a string literal with character set specification>
<a number> <a single-quoted SQL string>
ORA-06550: line 11, column 71:
PLS-00103: Encountered the symbol "AS" when expecting one of the following:
; return returning and or

816>
816> select count(*)
  2  from  (
  3         select object_id
  4         from x
  5         minus
  6         select (select object_id from z where surr_key = y.surr_key) as
object_id
  7         from y
  8        );

  COUNT(*)
----------
        10

816>

****************************

920> create table x as select object_id, object_name, object_type from
user_objects;

Table created.

920> create table y as select rownum as surr_key from user_objects;

Table created.

920> create table z as select rownum as surr_key, object_id, object_name,
object_type from user_objects;

Table created.

920> delete from y where rownum <= 10;

10 rows deleted.

920>
920> declare
  2     type typ_nt_v2_4000 is table of varchar2(4000);
  3     nt   typ_nt_v2_4000;
  4  begin
  5     select object_id
  6     bulk collect into nt
  7     from (
  8           select object_id
  9           from x
 10           minus
 11           select (select object_id from z where surr_key = y.surr_key) as
object_id
 12           from y
 13          );
 14    dbms_output.put_line(nt.count||' fetched.');
 15  end;
 16  /
10 fetched.

PL/SQL procedure successfully completed.

920>
920> select count(*)
  2  from  (
  3         select object_id
  4         from x
  5         minus
  6         select (select object_id from z where surr_key = y.surr_key) as
object_id
  7         from y
  8        );

  COUNT(*)
----------
        10

920>

Any ideas other than upgrade ? ;)

Thanks

Adrian

          Followup:

You cannot bulk collect from a dynamically open query -- you need to use a
dynamically opened query to use select (select ).....

you can hide the construct in a VIEW and query the view without any issues.

      Rating:
      5
8.1.7.4?  October 25, 2002
Reviewer:
      Adrian Billington  from UK
Tom

Thanks for responding. You confirmed my worst fears... I've not heard the term
"dynamically opened query" before. Don't suppose you can tell me if it works in
8.1.7.4 as it does in 9i?

8.1.7 is the only potential upgrade path I have at present (we are constrained
by third-party tools - don't you just love it).

I'll change your view recommendation to a function to return the value I'm
after. This SELECT (SELECT...) is a workaround to a problem I have whereby I'm
referencing a whole long list of tables and their "key columns" from a parameter
table to drive a dynamic SQL version of the demo I gave you.  The parameter
table's value for KEY_COLUMN feeds the subtractor in the MINUS SQL and are
consistent and existent across all but one table. So for this one annoying table
that doesn't have a corresponding key column, instead of storing the column name
in the parameter table, I stored a SELECT (SELECT) instead. About which I was
feeling quite pleased with myself, until I hit the 8.1.6.3 limitation. So a
function name in my table instead of the SELECT (SELECT) will have to be my
workaround...

Thanks again.

Adrian

          Followup:

In 9i you can

o bulk collect from a dynamically opened ref cursor
o select (select ... ) from t  directly in plsql

In 8i -- you cannot.

      Rating: 4
Parameters to
      Views !  October 25, 2002
Reviewer:  Ram
       from ct, USA
Tom,

Pardon my ignorance. But is there a way that a view can be called with
parameters and those parameters are used in the underlying query ?

EX: Create View (dno)
as
select * from emp where deptno = dno;

Appreciarte your help.

          Followup:

A view is just like a table -- so, no.

You would use a stored procedure that returns a ref cursor for this.  See

http://asktom.oracle.com/~tkyte/ResultSets/index.html

      Rating: 4
Returning
      multiple columns using select (select ...)  May 12, 2003

Reviewer:  A reader
Tom, how to achieve this using sql if I want to get multiple columns using
select (select ...) ? Say, I want

select col1, col2, (select col3, col4, col5 ...)

Is the above possible ? Also, where can I read more about using "select (select
...)" ?

Thanks in advance !

          Followup:

you don't really need to "read about it".  it is called a scalar subquery and is
basically allows a query that returns a single column and either ZERO or ONE
rows to be used where ever a literal could have been.  Eg:

select * from emp where ename = 'KING'

could be

select * from emp where ename = ( select 'KING' from dual );

That is all there is to know about it.

In order to get "multiple columns", you have to be willing to work a little.
What I do is this (say col3 is a varchar2, col4 is a positive number(5) and col5
is a date):

select col1,
       col2,
       substr( data, 1, 20 ) data1,
       to_number(substr( data, 21, 5 ) ) data2,
       to_date(substr( data, 26, 14),'yyyymmddhh24miss') data3
  from ( select col1, col2,
               (select rpad(col3,20)||
                       to_char(col4,'fm00000')||
                       to_char(col5,'yyyymmddhh24miss')
                  from ... ) DATA
            from .... )

and use to_number/to_date where and when appropriate.  Also *beware* of nulls --
you may have to NVL in there to preserve the fixed width nature of DATA.

      Rating: 4
Forgot to
      mention db version  May 12, 2003
Reviewer:
      A reader
For the above question, I am using Oracle EE 8.1.7.

Thanks !

      Rating: 5
Thanks a lot
      for immediate useful answer !!!  May 12, 2003

Reviewer:  A reader

      Rating: 4
Context
      switching example  October 01, 2003
Reviewer:
      bala  from Kuala Lumpur
Can you please give an example to see which database statistics as to know that
context switching is adding considerable overhead for me to move my pl/sql into
the sql itself. I am calling a function in an sql statement and have coded this
way to keep the code distinct and easy to understand.

          Followup:

use a VIEW -- it keeps the code "distinct" and "easy to understand" in the same
way calling PLSQL from SQL does.

you would just run the query with and without the function (with the function
and again without the function but inclusive of the SQL needed to perform the
function) and benchmark it.

      Rating:
      5
Benchmark  October 06, 2003
Reviewer:
      Bala  from Kuala Lumpur
here are the benchmark results(it was just an example, actually  we dont need
that function at all) What I really wanted to see was the effect of context
switching, what parameter shows me the effect of context switching both in
autotrace or tkprof.

10:39:56 scott@dybqr11>set autotrace traceonly;
10:39:56 scott@dybqr11>set arraysize 5000;
10:39:56 scott@dybqr11>alter session set sql_trace=true;

Session altered.

10:39:56 scott@dybqr11>select ename,sal,dept_max_sal(deptno) dep_max_sal from
emps;

14999 rows selected.

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

Statistics
----------------------------------------------------------
      15006  recursive calls
     180000  db block gets
    1455005  consistent gets
          0  physical reads
          0  redo size
     731708  bytes sent via SQL*Net to client
        457  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      14999  rows processed

10:43:19 scott@dybqr11>select ename,sal,(select max(sal) from emps where
deptno=e.deptno) dep_max_sal from emps e;

14999 rows selected.

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

Statistics
----------------------------------------------------------
          0  recursive calls
        132  db block gets
       1070  consistent gets
          0  physical reads
          0  redo size
     731743  bytes sent via SQL*Net to client
        457  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      14999  rows processed

10:43:32 scott@dybqr11>alter session set sql_trace=false;

TKPROF OUTPUT
-----------------

1) First SQL with Function call

select ename,sal,dept_max_sal(deptno) dep_max_sal
from
 emps

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.19          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        4      2.00       3.73          0        100         12       14999
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        6      2.00       3.92          0        100         12       14999

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 209
********************************************************************************

SELECT MAX(SAL)
FROM
 EMPS  WHERE DEPTNO = :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute  14999      0.42       0.43          0          0          0           0
Fetch    14999    152.09     192.66          0    1454903     179988       14999
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    29999    152.51     193.09          0    1454903     179988       14999

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 209     (recursive depth: 1)

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.19          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        4      2.00       3.73          0        100         12       14999
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        6      2.00       3.92          0        100         12       14999

Misses in library cache during parse: 1

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute  15000      0.42       0.44          0          0          0           0
Fetch    15000    152.09     192.66          0    1454905     179988       15000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    30002    152.51     193.10          0    1454905     179988       15000

Misses in library cache during parse: 0

    2  user  SQL statements in session.
    1  internal SQL statements in session.
    3  SQL statements in session.

************************************************************
Second SQL without the function call
************************************************************
select ename,sal,(select max(sal)
from
 emps where deptno=e.deptno) dep_max_sal from emps e

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        4      0.22       4.19          0       1070        132       14999
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        6      0.23       4.20          0       1070        132       14999

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 209

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  DELETE PLAN_TABLE
      3   TABLE ACCESS FULL PLAN_TABLE

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        4      0.22       4.19          0       1070        132       14999
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        6      0.23       4.20          0       1070        132       14999

Misses in library cache during parse: 1

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      0      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        0      0.00       0.00          0          0          0           0

Misses in library cache during parse: 0

    1  user  SQL statements in session.
    0  internal SQL statements in session.
    1  SQL statements in session.

      Rating: 5
Tom my previous
      post was a question  October 08, 2003
Reviewer:
      Bala  from Kuala Lumpur
I need to know how context switching is affecting me. I can see the difference
in the elapsed time and the consistent gets and executions, but I want to know
what exactly(which statistic) tells me here the problem with context swithing

          Followup:

umm, elapsed time, increased cpu time, consistent gets -- they all do

context switching manifests itself in increased elapsed times, increased cpu
times and less efficient processing of data (consistent gets)

The number that tells you "how many" will be incorporated in recursive calls
(see your autotrace output)

      Rating: 5
Not clear
      still  October 08, 2003
Reviewer:  Bala
       from Kuala Lumpur
what i am not clear about is
i) all the statistics i have gathered(cpu time, consistent gets etc) are to do
with sql processing, arent they? i mean cpu time,consistent gets and elapsed
time can go up by writing bad sql also, not necessarily by context switching.
ii) i want to see the time spent(elapsed and cpu) switching between sql engine
and plsql engine -- somehow this is not clear to me
iii)yes, the "how many" thing in the recursive calls tells me the number of
switches

          Followup:

i) of course.  but -- look at the cpu time with and without.  look at the
elapsed time with and without.

That is the overhead in this case of doing it wrong (calling plsql from sql when
you didn't need to) and doing it right.  That includes all of the overhead in
this case of doing the context switch plus all of the extra work.

ii) write a dummy "return NULL" function if you just want to measure the context
switch overhead.  not sure WHAT that will buy you exactly for it is a comparison
of "doing it in plsql called from sql vs doing it in sql alone" that is relevant
here.  It didn't really matter if the context switch was 10% of those values
(the differences) or 90%.  The fact is -- doing in plsql would could (should)
have been done in SQL took X more resources.

Here is a quick and dirty "do nothing" example:

ops$tkyte@ORA920> create or replace function f return number
  2  as
  3  begin
  4          return null;
  5  end;
  6  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec runStats_pkg.rs_start

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> prompt select null from all_objects;
select null from all_objects
ops$tkyte@ORA920> set termout off
ops$tkyte@ORA920> exec runStats_pkg.rs_middle

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> prompt select f() from all_objects;
select f() from all_objects
ops$tkyte@ORA920> set termout off
ops$tkyte@ORA920> exec runStats_pkg.rs_stop(50)
Run1 ran in 267 hsecs
Run2 ran in 394 hsecs
run 1 ran in 67.77% of the time

Name                                  Run1        Run2        Diff
LATCH.row cache enqueue latch       58,918      58,978          60
LATCH.row cache objects             88,377      88,437          60
LATCH.SQL memory manager worka          67         134          67
LATCH.cache buffers chains         298,631     298,701          70
LATCH.library cache pin alloca          24         102          78
STAT...CPU used by this sessio         206         297          91
STAT...CPU used when call star         206         297          91
STAT...Elapsed Time                    273         397         124
STAT...redo size                    64,980      65,132         152
LATCH.simulator hash latch           3,456       3,200        -256
LATCH.library cache pin                 54       4,381       4,327
LATCH.shared pool                       54       6,556       6,502
LATCH.library cache                    100       8,894       8,794
STAT...session pga memory                0      65,536      65,536

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
454,727     474,555      19,828     95.82%

PL/SQL procedure successfully completed.

October 09, 2003 -- Thanks for the question regarding "how to get
      user information?", version 8.1.6

      You Asked
Tom:

select username,server,osuser,machine,terminal from v$session.

it gives me lots of line like this:

username    server    osuser        machine        terminal
USER1        NONE    jdbcuser    jdbcclient    unknown

this is because lots of program are running by user user1 under jdbc.
how can i know where those program run from like machine name or ip address?

i know sys_context('userenv','ip_address') can provide such kind of information,
but in this case, how should i use it?

if i have sid,serial#, can i know which ip each session come from instead of
only jdbcclient and jdbcuser, and terminal unknown?

      and we said...
Well, the machine is the host they are on -- that should be translatable to an
IP Address.

You would need to enable auditing and audit connects in order to see the ip
address.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:526822275627
...
Review &
      Followup

      Rating: 4
3 Tier OSUSER
      ?  October 09, 2003
Reviewer:  Matt
       from UK
We are migrating to Oracle 11i - 3 tier.

We developed an old audit system (DB Version 7) which used triggers and inserted
osuser from v$session.

The problem we now have in 3 tier ( DB 8.1.7.4 ) is that the osuser is a
constant based on the mid tier connection.

Will switching to the new Audit functionality ( audit_trail=true ) allow us to
view the 'real' client user ? Is there another way to do this maybe using Java ?
Any help appreciated - we need to keep our auditors happy.

          Followup:

the "real" client is the middle tier -- that is the "real" client.

You'll want to ask someone who knows alot more about Oracle Apps then I do to
see what you might be able to get access to (sorry)...

October 09, 2003 Dale -- Thanks for the question regarding
      "Processing correlated rows", version 9.2.0

      You Asked
We have a table event, with three columns, event_id as number, event_gmt as
date, event_type_id as number.

We want to get from the event table only the first row of event_type_id 1 for
each day, and match this with the first row of event_type_id 2 that has an
event_gmt later than that of the event_type_id 1 row.

Given this dataset:

event_id, event_gmt, event_type_id
1, 1 Jan 2003 9am, 1
2, 1 Jan 2003 10am, 2
3, 1 Jan 2003 11am, 1
4, 1 Jan 2003 12pm, 2
5, 2 Jan 2003, 7am, 2
6, 2 Jan 2003 8am, 1
7, 2 Jan 2003 9am, 2
8, 2 Jan 2003 10am, 1
9, 2 Jan 2003 11am, 2
10, 3 Jan 2003 8am, 2

We want a result set like:

Start, Stop
1 Jan 2003 9am, 1 Jan 2003 10am
2 Jan 2003 8am, 2 Jan 2003 9am

Can this be done in SQL? Currently we loop through the events using procedural
code.

thanks

Dale

      and we said...
ops$tkyte@ORA920> select event_id, event_gmt,
  2             (select min(event_gmt)
  3                    from t
  4                   where event_type_id = 2
  5                     and event_gmt > A.event_gmt )
  6    from (
  7  select event_id, event_gmt, event_type_id,
  8             row_number() over
                  ( partition by trunc(event_gmt) order by event_gmt ) rn
  9    from t
 10   where event_type_id = 1
 11             ) A
 12   where rn = 1
 13  /

  EVENT_ID EVENT_GMT        (SELECTMIN(EVENT
---------- ---------------- ----------------
         1 01 jan 2003 09am 01 jan 2003 10am
         6 02 jan 2003 08am 02 jan 2003 09am

Is one approach.....
Review & Followup

      Rating: 3
Certainly does
      the job but performance is not so good  October 07, 2003

Reviewer:  Dale Ogilvie  from Christchurch, New
      Zealand
Thanks for that, your query answers the question but it appears to be much
harder on the database (9.2.0.4) than a straight query that just gets the rows
within the range. If this kind of query cannot be closer in impact to just
retrieving the rows for procedural processing, then I think we are better off
sticking with looping over the rows in our business logic, as opposed to getting
Oracle to answer the question for us. Here's what I did, together with the
autotrace output.

nccdb@DALEUSD> SELECT outgoing_event_msg_id, event_gmt AS start_gmt,
  2         (SELECT MIN (event_gmt)
  3            FROM outgoing_event_msg
  4           WHERE event_id = 94 AND event_gmt > a.event_gmt) AS stop_gmt
  5    FROM (SELECT outgoing_event_msg_id, event_gmt, event_id,
  6                 ROW_NUMBER () OVER (PARTITION BY TRUNC (event_gmt) ORDER BY
event_gmt)
  7
rn
  8            FROM outgoing_event_msg
  9           WHERE event_id = 93
 10             AND tracker_id = 3000029
 11             AND event_gmt BETWEEN TO_DATE ('07/01/2003', 'MM/DD/YYYY')
 12                               AND TO_DATE ('08/01/2003', 'MM/DD/YYYY')) a
 13   WHERE rn = 1;

OUTGOING_EVENT_MSG_ID START_GMT           STOP_GMT
--------------------- ------------------- -------------------
              2763426 07/17/2003 01:46:33 07/17/2003 02:04:03
              2780425 07/21/2003 01:38:44 07/21/2003 01:56:14
              2887121 07/28/2003 16:25:40 07/28/2003 16:30:40

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=1 Bytes=35)
   1    0   SORT (AGGREGATE)
   2    1     VIEW OF 'index$_join$_001' (Cost=98 Card=432 Bytes=4752)
   3    2       HASH JOIN
   4    3         INDEX (RANGE SCAN) OF 'IF_OUT_EVENT_MSG_TRACKER_EVENT'
(NON-UNIQUE) (Cost=187 Card=432 Bytes
          =4752)

   5    3         INDEX (RANGE SCAN) OF 'AI_OUTGOING_EVENT_MSG_EVENTGMT'
(NON-UNIQUE) (Cost=187 Card=432 Bytes
          =4752)

   6    0   VIEW (Cost=6 Card=1 Bytes=35)
   7    6     WINDOW (SORT PUSHED RANK) (Cost=6 Card=1 Bytes=23)
   8    7       TABLE ACCESS (BY INDEX ROWID) OF 'OUTGOING_EVENT_MSG' (Cost=4
Card=1 Bytes=23)
   9    8         INDEX (RANGE SCAN) OF 'AI_OEM_TRKR_DT_PORT' (NON-UNIQUE)
(Cost=3 Card=3)

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

nccdb@DALEUSD> SELECT   oem.outgoing_event_msg_id, oem.event_id, oem.event_gmt
  2      FROM outgoing_event_msg oem
  3     WHERE (oem.event_gmt BETWEEN TO_DATE ('07/01/2003', 'MM/DD/YYYY')
  4                              AND TO_DATE ('08/01/2003', 'MM/DD/YYYY')
  5           )
  6       AND (oem.event_id IN (93, 94))
  7       AND (oem.tracker_id = 3000029)
  8  ORDER BY oem.event_gmt, oem.outgoing_event_msg_id;

OUTGOING_EVENT_MSG_ID   EVENT_ID EVENT_GMT
--------------------- ---------- -------------------
              2763426         93 07/17/2003 01:46:33
              2763452         94 07/17/2003 02:04:03
              2763458         93 07/17/2003 02:05:08
              2763492         94 07/17/2003 02:22:38
<snip for brevity>
              2887361         93 07/28/2003 22:06:11
              2887372         94 07/28/2003 22:11:11

188 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=1 Bytes=23)
   1    0   SORT (ORDER BY) (Cost=6 Card=1 Bytes=23)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'OUTGOING_EVENT_MSG' (Cost=4
Card=1 Bytes=23)
   3    2       INDEX (RANGE SCAN) OF 'AI_OEM_TRKR_DT_PORT' (NON-UNIQUE) (Cost=3
Card=3)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         76  consistent gets
          0  physical reads
          0  redo size
       5486  bytes sent via SQL*Net to client
        635  bytes received via SQL*Net from client
         14  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
        188  rows processed

76 versus 1500 block reads is quite a big price to pay. Can this be lessened? I
have had to translate your suggested solution over to the reality of our db
schema, so I guess it's possible that I've scuppered the query somehow. Also,
even if I run the first query again in the same session I still get large
numbers of physical reads reported. Why is this?

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

          Followup:

can you push the predicate into the inline view -- not outside of it?

      Rating: 5
Oh
      yeah!  October 07, 2003
Reviewer:  Dale
      Ogilvie  from Christchurch, New Zealand
Thanks for the hint. Yep, I scuppered the query by neglecting to add one
important bit to the inline view, our event table has lots of rows in it, but
not so many for each tracker. Adding "AND tracker_id = 3000029" makes a lot of
difference. Time to look at killing off some business logic I think.

nccdb@DALEUSD> SELECT outgoing_event_msg_id, event_gmt AS start_gmt,
  2         (SELECT MIN (event_gmt)
  3            FROM outgoing_event_msg
  4           WHERE event_id = 94 AND tracker_id = 3000029 AND event_gmt >
a.event_gmt) AS stop_gmt
  5    FROM (SELECT outgoing_event_msg_id, event_gmt, event_id,
  6                 ROW_NUMBER () OVER (PARTITION BY TRUNC (event_gmt) ORDER BY
event_gmt)
  7
rn
  8            FROM outgoing_event_msg
  9           WHERE event_id = 93
 10             AND tracker_id = 3000029
 11             AND event_gmt BETWEEN TO_DATE ('07/01/2003', 'MM/DD/YYYY')
 12                               AND TO_DATE ('08/01/2003', 'MM/DD/YYYY')) a
 13   WHERE rn = 1;

OUTGOING_EVENT_MSG_ID START_GMT           STOP_GMT
--------------------- ------------------- -------------------
              2763426 07/17/2003 01:46:33 07/17/2003 02:04:03
              2780425 07/21/2003 01:38:44 07/21/2003 01:56:14
              2887121 07/28/2003 16:25:40 07/28/2003 16:30:40

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=1 Bytes=35)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'OUTGOING_EVENT_MSG' (Cost=4
Card=1 Bytes=17)
   3    2       INDEX (RANGE SCAN) OF 'AI_OEM_TRKR_DT_PORT' (NON-UNIQUE) (Cost=3
Card=1)
   4    0   VIEW (Cost=6 Card=1 Bytes=35)
   5    4     WINDOW (SORT PUSHED RANK) (Cost=6 Card=1 Bytes=23)
   6    5       TABLE ACCESS (BY INDEX ROWID) OF 'OUTGOING_EVENT_MSG' (Cost=4
Card=1 Bytes=23)
   7    6         INDEX (RANGE SCAN) OF 'AI_OEM_TRKR_DT_PORT' (NON-UNIQUE)
(Cost=3 Card=3)

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

      Rating: 4
Similar
      requirement  October 08, 2003
Reviewer:
      Balaji  from India Chennai
Hi
I have a table Y with columns
Idy
Idx -- FK to ID of X
Type
Col1
Col2

I have another table X with columns
Idx

X is the parent of Y.
ie for 1 row in x , there could be say 3 rows in Y. for each type say type1 ,
type2 , type3.

The data is like this

X

IDX

1

Y

IDY IDX  TYPE  COL1 Col2
1   1    TYPE1 100  100
2   1    TYPE2 200  200
3   1    TYPE3 300  300

I need the output like this

IDX IDYtype1 idytype2 idytype3  TYPE1 TYPE2 TYPE3 TYPE1COL1 TYPE1COL2 type2col1
type2col2 type3col1 type3col3

1 1 2 3 type1 type2 type2 100 100 200 200 300 300

ie i want the 3 rows to be transformed into a single row,

how can i do this using sql.

is using the alias 3 times the only option.

          Followup:

A query has a FIXED number of columns.

therefore you would have to know at query parse time "there will be three rows
found in Y for this X column"

then and only then you could build a query to do that using column aliases.

      Rating: 3
Rows to
      columns  October 08, 2003
Reviewer:  Balaji
       from Chennai India
Hi
I have only 6 types. type 1 to 6.
So in my query for getting the type details i would have 12 columns 2 for each
type.

how can i form the query??

          Followup:

select x.idx,
       max( decode( rownum, 1, type ) ) type1,
       max( decode( rownum, 2, type ) ) type2,
       .....
       max( decode( rownum, 6, type ) ) type6,
       max( decode( rownum, 1, col1 ) ) col1_1,
       ....
       max( decode( rownum, 6, col1 ) ) col1_6,
       max( decode( rownum, 1, col2 ) ) col2_1,
       ....
       max( decode( rownum, 6, col2 ) ) col2_6
  from x, y
 where x.idx = :n
   and y.idx = x.idx
 group by x.idx;

works if you are getting A row from X, this:

select idx,
       max( decode( rn, 1, type ) ) type1,
       max( decode( rn, 2, type ) ) type2,
       .....
       max( decode( rn, 6, type ) ) type6,
       max( decode( rn, 1, col1 ) ) col1_1,
       ....
       max( decode( rn, 6, col1 ) ) col1_6,
       max( decode( rn, 1, col2 ) ) col2_1,
       ....
       max( decode( rn, 6, col2 ) ) col2_6
  from (select x.idx, y.type, y.col1, y.col2,
               row_number() over (partition by x.idx order by y.ydx ) rn
          from x, y
         where x.idx = y.idx
           AND <whatever else you want> )
 group by idx;

      Rating: 4
Need another
      column  October 09, 2003
Reviewer:  Dale
      Ogilvie  from Christchurch, New Zealand
It turns out we need more information from the query than just the datetime of
the following row. To expand my original test case with a "val" column, given:

event_id, event_gmt, event_type_id, val
1, 1 Jan 2003 9am, 1, A
2, 1 Jan 2003 10am, 2, B
3, 1 Jan 2003 11am, 1, C
4, 1 Jan 2003 12pm, 2, D
5, 2 Jan 2003, 7am, 2, E
6, 2 Jan 2003 8am, 1, F
7, 2 Jan 2003 9am, 2, G
8, 2 Jan 2003 10am, 1, H
9, 2 Jan 2003 11am, 2, I
10, 3 Jan 2003 8am, 2, J

We want a result set like:

Start, Stop, Start Val, Stop Val
1 Jan 2003 9am, 1 Jan 2003 10am, A, B
2 Jan 2003 8am, 2 Jan 2003 9am, F, G

I got as far as:

select a.event_gmt as start, b.event_gmt as stop, a.val as aval, b.val as bval
from
( select
   event_gmt,
   trunc(event_gmt) as event_date
   val,
   row_number() over ( partition by trunc(event_gmt) order by event_gmt ) rn
  from event
  where event_type_id = 1
) a,
( select
   event_gmt,
   trunc(event_gmt) as event_date
   val,
  from event
  where event_type_id = 2
) b
where a.event_date = b.event_date
and a.rn = 1
and b.event_gmt > a.event_gmt
and b.event_gmt < a.event_date + 1;

But this returns a row for all event_type_id 2 rows for the given event_date,
when I only want the first one later than a.event_gmt.

1 Jan 2003 9am, 1 Jan 2003 10am, A, B
1 Jan 2003 9am, 1 Jan 2003 12pm, A, D
2 Jan 2003 8am, 2 Jan 2003 9am, F, G
2 Jan 2003 8am, 2 Jan 2003 11am, F, I

How can I only get one type 2 row here?

thanks

Dale

          Followup:

QED using a trick (how to get an aggregate and the related row info -- i use
this "encoding" trick all of the time.  See "Effective Oracle by Design" for
other techniques of doing the same)

ops$tkyte@ORA920> select event_id, event_gmt,
  2         to_date( substr( data, 1, 14 ), 'yyyymmddhh24miss' ) event_stop,
  3             val val_start,
  4             substr( data, 15 ) val_stop
  5    from (
  6  select event_id, event_gmt, val,
  7             (select min( to_char( event_gmt, 'yyyymmddhh24miss' ) || val )
  8                    from t
  9                   where event_type_id = 2
 10                     and event_gmt > A.event_gmt ) data
 11    from (
 12  select event_id, event_gmt, event_type_id, val,
 13             row_number() over
 14               ( partition by trunc(event_gmt) order by event_gmt ) rn
 15    from t
 16   where event_type_id = 1
 17             ) A
 18   where rn = 1
 19         )
 20  /

  EVENT_ID EVENT_GMT        EVENT_STOP       V V
---------- ---------------- ---------------- - -
         1 01 jan 2003 09am 01 jan 2003 10am A B
         6 02 jan 2003 08am 02 jan 2003 09am F G

      Rating: 5
Analytical
      query  October 10, 2003
Reviewer:  Santhanam
       from India
Good Example

October 09, 2003 SubbaRao -- Thanks for the question regarding
      "Dynamic SQL -Debugging", version 8.1.6

      You Asked
Hi Tom,
1.I am trying to write a stored procedure which will have a Native Dynamic SQL
call and wanted to return the output to a REF CURSOR variable. Is it possible?
2.Is there anyway to debug the dynamic sql query while executing.I used
dbms_output.put_line to pring the query and it is raising the overflow buffer
exception.
3.Are there any specific rules while using QUOTES(') for different parameters in
WHERE clause section? It gives ORA-01756: quoted string not properly
terminated.It is becoming little tough to debug directly. I am using iterative
process to debug one ' problem.
4.I wanted to build the where clause dynamically based on the parameters passed.
I have seen an article on your site which explains about how to handle this
using CONTEXT scenarios(dbms_session). I can also build the WHERE clause using
NATIVE DYNAMIC SQL or DBMS_SQL based on the IN parameters. Which one is a
suggested approach. CONTEXT approach or DYNAMIC SQL approach?

Here are the two procedures I am trying to make them working but both of them
are giving their own errors. The 1st one is giving the following errors.

**************************************************************
SQL>  exec prpack_common.Search_Archives('PR',null,null);
Starting
1st Stage
2nd Stage
3rd Stage
4th Stage
5th Stage
BEGIN prpack_common.Search_Archives('PR',null,null); END;

*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SYS.DBMS_OUTPUT", line 64
ORA-06512: at "PRDEV.PRPACK_COMMON", line 543
ORA-06550: line 1, column 690:
PLS-00103: Encountered the symbol "ARCHIVED" when expecting one of the
following:
. ( * @ % & - + ; / at for mod rem <an exponent (**)> and or
group having intersect minus order start union where connect
||
The symbol "." was substituted for "ARCHIVED" to continue.
ORA-06550: line 1, column 747:
PLS-00103: Encountered the symbol "" when expecting one of the following:
. ( , * @ % & - + ; / at for mod rem <an exponent (**)> asc
desc || indicator
The symbol "," was substituted for "" to continue.
ORA-06512: at line 1
***************************************************************
The Second one is giving the following errors:
SQL> exec prpack_common.Search_Archive_Docs('PR',null,null);
ORA-01756: quoted string not properly terminated

PL/SQL procedure successfully completed.
****************************************************************
My Intetion is if this case works, I can enhance with all the possible IN
parameters subsequently.

/********************************************************************************
**/
PROCEDURE Search_Archives(p_content_type_id    IN
PR_CONTENT.CONTENT_TYPE_ID%TYPE,
              p_sort_type    IN VARCHAR2,
                  p_sort_by    IN VARCHAR2
             ) IS

    TYPE cur_type IS REF CURSOR;
    c        cur_type;

    query_str         VARCHAR2(2000);
    l_content_id         NUMBER;
    l_est_pub_dt        DATE;
    l_content_type_id    VARCHAR2(50);
    l_last_name        VARCHAR2(50);
    l_first_name        VARCHAR2(50);
    l_style_value        VARCHAR2(256);
    l_cat_desc        VARCHAR2(50);

    BEGIN

    dbms_output.put_line('Starting');

    query_str := 'DECLARE' ||
              ' l_content_type_id    VARCHAR2(50):=:1; '||
              ' l_sort_type        VARCHAR2(25):=:2; '||
              ' l_sort_by        VARCHAR2(5):=:3; '||
              ' BEGIN ';
    dbms_output.put_line('1st Stage');
    query_str :=query_str||' SELECT PC.CONTENT_ID,'||
                ' PTX.ESTIMATED_PUBLISH_DT,'||
                ' PC.CONTENT_TYPE_ID,'||
                ' PU.LAST_NAME,'||
                ' PU.FIRST_NAME,'||
                ' PCB.STYLE_VALUE,'||
                ' PCAT.CATEGORY_DESC'||
            ' FROM       PR_CONTENT PC,'||
                ' PR_TARGETSITE_XREF PTX,'||
                ' PR_CONTENT_BODY PCB,'||
                ' PR_CATEGORY_XREF PCX,'||
                ' PR_CATEGORY PCAT,'||
                ' PR_CON_USRROLE_XREF PCUX,'||
                ' PR_USER PU'||
                ' WHERE   PC.CONTENT_ID=PTX.CONTENT_ID'||
            ' AND    PC.CONTENT_ID=PCUX.CONTENT_ID ';
    dbms_output.put_line('2nd Stage');
    query_str:=query_str||' AND PCUX.USERROLE_ID=''PRIMARY CONTACT';
    dbms_output.put_line('3rd Stage');
    query_str:=query_str ||    'AND    PCUX.USER_ID=PU.USER_ID '||
            ' AND    PC.CONTENT_ID=PCB.CONTENT_ID'||
            ' AND    PCB.STYLE_ID=1'||
            ' AND    PC.CONTENT_ID=PCX.CONTENT_ID'||
            ' AND    PCX.CATEGORY_ID=PCAT.CATEGORY_ID'||
            ' AND    PCX.SEQUENCE_NO=1'||
            ' AND    PC.STATUS =''ARCHIVED';
    dbms_output.put_line('4th Stage');

    IF p_content_type_id IS NOT NULL THEN
    query_str := query_str ||' AND PC.CONTENT_TYPE_ID=:1';
    END IF;

    query_str := query_str||' ORDER BY PC.STATUS,:2' ||' :3; END;';

    dbms_output.put_line('5th Stage');

    OPEN c FOR query_str USING p_content_type_id,p_sort_type,p_sort_by;

    LOOP
    dbms_output.put_line('Inside Loop');
        FETCH c INTO
l_content_id,l_est_pub_dt,l_content_type_id,l_last_name,l_first_name,
        l_style_value,l_cat_desc;

        EXIT WHEN c%NOTFOUND;
    END LOOP;
    CLOSE c;
    EXCEPTION

        WHEN no_data_found THEN
            RAISE;
        WHEN others THEN
        dbms_output.put_line(SQLERRM);

    END Search_Archives;
/********************************************************************************
**/
PROCEDURE Search_Archive_Docs(p_content_type_id    IN
PR_CONTENT.CONTENT_TYPE_ID%TYPE,
              p_sort_type    IN VARCHAR2,
                  p_sort_by    IN VARCHAR2
             ) IS

    query_str         VARCHAR2(4000);
    l_cursorid        INTEGER;
    l_dummy                INTEGER;
    l_content_id         NUMBER(10);
    l_est_pub_dt        DATE;
    l_content_type_id    VARCHAR2(50);
    l_last_name        VARCHAR2(50);
    l_first_name        VARCHAR2(50);
    l_style_value        VARCHAR2(256);
    l_cat_desc        VARCHAR2(50);

    BEGIN

    --Open the cursor for Processing.
             l_cursorid := dbms_sql.open_cursor;

    --Create the query string.

    query_str :=        ' SELECT PC.CONTENT_ID,
                 PTX.ESTIMATED_PUBLISH_DT,
                 PC.CONTENT_TYPE_ID,
                 PU.LAST_NAME,
                 PU.FIRST_NAME,
                 PCB.STYLE_VALUE,
                 PCAT.CATEGORY_DESC
             FROM    PR_CONTENT PC,
                 PR_TARGETSITE_XREF PTX,
                 PR_CONTENT_BODY PCB,
                 PR_CATEGORY_XREF PCX,
                 PR_CATEGORY PCAT,
                 PR_CON_USRROLE_XREF PCUX,
                 PR_USER PU
             WHERE   PC.CONTENT_ID=PTX.CONTENT_ID
             AND     PC.CONTENT_ID=PCUX.CONTENT_ID
             AND     PCUX.USERROLE_ID=''PRIMARY CONTACT';

    /*
    query_str:=query_str||' AND PCUX.USERROLE_ID=''PRIMARY CONTACT';

    query_str:=query_str ||' AND    PCUX.USER_ID=PU.USER_ID
             AND    PC.CONTENT_ID=PCB.CONTENT_ID
             AND    PCB.STYLE_ID=1
             AND    PC.CONTENT_ID=PCX.CONTENT_ID
             AND    PCX.CATEGORY_ID=PCAT.CATEGORY_ID
             AND    PCX.SEQUENCE_NO=1
             AND    PC.STATUS =''ARCHIVED';
    */

    /*
    IF p_content_type_id IS NOT NULL THEN
    query_str := query_str ||' AND
PC.CONTENT_TYPE_ID='''||p_content_type_id||'''';
    END IF;

    query_str := query_str||' ORDER BY PC.STATUS,'||p_sort_type || p_sort_by;

    */
    --Parse the query.
         dbms_sql.parse(l_cursorid,query_str,dbms_sql.native);

        -- Define the output variables.
        dbms_sql.define_column(l_cursorid,1,l_content_id,10);
        dbms_sql.define_column(l_cursorid,2,l_est_pub_dt);
        dbms_sql.define_column(l_cursorid,3,l_content_type_id,50);
        dbms_sql.define_column(l_cursorid,4,l_last_name,50);
        dbms_sql.define_column(l_cursorid,5,l_first_name,50);
        dbms_sql.define_column(l_cursorid,6,l_style_value,256);
        dbms_sql.define_column(l_cursorid,7,l_cat_desc,50);

    -- Execute the statment. We don't care about the return value,
    -- but we do need to declare a variable for it.

            l_dummy  := dbms_sql.execute(l_cursorid);

    --This is the fetch Loop.
    LOOP
        --Fetch the rown into the buffer, and also chech for the exit
        --condition from the loop.
        IF dbms_sql.fetch_rows(l_cursorid) = 0 THEN
           EXIT;
        END IF;
    dbms_output.put_line('Inside the Fetch Loop');
       -- Retrieve the rows from the buffor into PL/SQL variables.
        dbms_sql.column_value(l_cursorid,1,l_content_id);
        dbms_sql.column_value(l_cursorid,2,l_est_pub_dt);
        dbms_sql.column_value(l_cursorid,3,l_content_type_id);
        dbms_sql.column_value(l_cursorid,4,l_last_name);
        dbms_sql.column_value(l_cursorid,5,l_first_name);
        dbms_sql.column_value(l_cursorid,6,l_style_value);
        dbms_sql.column_value(l_cursorid,7,l_cat_desc);

    dbms_output.put_line('Content ID is ' ||l_content_id);
    dbms_output.put_line('Est Pub Date is ' ||l_est_pub_dt);
    dbms_output.put_line('Last Name is ' ||l_last_name);
    dbms_output.put_line('First Name is ' ||l_first_name);

    END LOOP;
    -- close the cursor.
       dbms_sql.close_cursor(l_cursorid);

    EXCEPTION

        WHEN no_data_found THEN
            RAISE;
        WHEN others THEN

                IF dbms_sql.is_open(l_cursorid) THEN
                   dbms_sql.close_cursor(l_cursorid);
                END IF;
        dbms_output.put_line(SQLERRM);

    END Search_Archive_Docs;

      and we said...
1) yes,

...
  open ref_cursor_variable for 'string here' using bind_variable1,
bind_variable2;
....

2) I use a small procedure like this:

create or replace procedure p ( p_str in varchar2 )
is
   l_str   long := p_str;
begin
   loop
      exit when l_str is null;
      dbms_output.put_line( substr( l_str, 1, 250 ) );
      l_str := substr( l_str, 251 );
   end loop;
end;
/

for cases like that.

3) all quotes must be ''.  thats the rule.  Here is a trick you can use.

Instead of using '', use ".  Eg:

  l_stmt := 'select *
               from emp
              where ename like :x
                and job = "CLERK"';

  open l_ref_cursor for replace( l_stmt, '"', '''' ) using bind_variable;

That way, it's easier to read and you are not in double quote $#@.  Make sure to
use bind variables on the portions of the query that can change -- search for:

sys_context ref cursor

to see two different ways to use bind variables if you do not know how many you
will have until run time (eg: in the above example, I knew there was one bind
variable -- if you are building the query on the fly, you might not know how
many there will be -- use sys_context or cursor_sharing as demonstrated by the
above search links)

4) you use the sys_context /dbms_session with native dynamic sql.  If you use
dbms_sql, you'll be using dbms_sql.bind_variable instead.  Either approach is
doable.  I find native dynamic sql a tad easier.

Your coding issues surround missing quotes and using too long of a string with
dbms_output.  P will fix that, using " will fix the other.

Also, i see this:

query_str := query_str||' ORDER BY PC.STATUS,:2' ||' :3; END;';

you cannot BIND order by values.  you have to put them in there.  No binding for
order by (the query plan needs to be able to change as the columns to order by
change -- you need a different query for each set of order by's).

If you do "bind" in the value, eg:

  open ref_cursor for 'select * from emp order by :x' using 'ENAME';

that is the same as:

select * from emp order by 'ENAME';

that will order by the CONSTANT ename.  You have to:

  open ref_cursor for 'select * from emp order by ' || 'ENAME';

Review &
      Followup

      Rating: 4
Dynamic
      SQL  April 09, 2002
Reviewer:  SubbaRao
       from Detroit
Tom,
Your response is too good and the answers were highly helpful.
However, I have the following issue:
1.I am creating the procedure like this.

PROCEDURE Search_Archives(p_content_type_id    IN
PR_CONTENT.CONTENT_TYPE_ID%TYPE,
                  SDTLS        IN OUT Get_Inprocess_DocumentsPKG.IPRESULTS
             ) IS
2.I am trying to execute the Dynamic SQL using
"OPEN SDTLS FOR query_str USING p_content_type_id;"
It gives the following error:
520/7    PLS-00455: cursor 'SDTLS' cannot be used in dynamic SQL OPEN  statement

My Dynamic SQL returns a list of values and I wanted them to be part of my REF
CURSOR OUT variable SDTLS. Do I need to create that ref cursor within this
procedure to make this working?

          Followup:

You need to use a weakly typed ref cursor.  Goto the definition of IPRESULTS and
make it be like this:

TYPE cur_type IS REF CURSOR;

you have it "strongly typed" (where ever you have it defined, I don't see it)
meaning it has a well defined structure.  Dynamically opened ref cursors cannot
be "well defined"

      Rating:
      4
  May 16, 2002
Reviewer:
      A reader
Using Oracle 8.1.7.3

Is there a size limit for a column in a ref cursor? I.E

PROCEDURE TEST (P_CUR OUT MY_REFCUR)
   IS

BEGIN

open P_CUR for
select lpad('test',8000,'X') from dual;
end;

it return fine but sql/plus only shows 4000.

Could I use a clob to and pass that back in if ref cursor?

Thanks.

          Followup:

ref cursor or not (any cursor) strings are limited to 4000 bytes.

  1* select length( lpad('test',8000,'X') ) from dual
ops$tkyte@ORA817DEV.US.ORACLE.COM> /

LENGTH(LPAD('TEST',8000,'X'))
-----------------------------
                         4000

it is a sql limit.

You can use a clob.

      Rating: 3
A
      Reader  July 24, 2002
Reviewer:  Raj
       from India
Hi Tom

BEGIN SP_COPYAUDITDATA_V1; END;

*
ERROR at line 1:
ORA-20000: ORU-10028: line length overflow, limit of 255 bytes per line
ORA-06512: at "SYS.DBMS_OUTPUT", line 99
ORA-06512: at "SYS.DBMS_OUTPUT", line 65
ORA-06512: at "FPIMLVER103.SP_COPYAUDITDATA_V1", line 112
ORA-06512: at line 1

This error is coming when I try to print something in variable but value is vaey
large...
what is the SET cmd to overcome this prob.

          Followup:

sigh, there is no SET, the error is very stringent.

Use this:

create or replace procedure p ( p_str in varchar2 )
is
   l_str   long := p_str;
begin
   loop
      exit when l_str is null;
      dbms_output.put_line( substr( l_str, 1, 250 ) );
      l_str := substr( l_str, 251 );
   end loop;
end;
/

instead of dbms_output.put_line

      Rating: 5
is there really
      a 4000 bytes limit for a ref cursor?  August 20, 2002

Reviewer:  Paul Druker  from Fairfax, VA USA

You mentioned that "ref cursor or not (any cursor) strings are limited to 4000
bytes"

I'm using "open p_refcursor for l_sql" statement, and I don't seem to hit the
4000 bytes limit if l_sql is declared as long, not as varchar2(4000).

Does it mean that with l_sql as long limit is now pushed to 32,760?

Is there maximum length for a SQL statement?

          Followup:

the COLUMNS selected in the sql query are limited to 4000 bytes,  the text of
the query itself with a ref cursor is 32k.  Sorry for the confusion -- the
following shows what I meant:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x varchar2(4000), y char(1)
);

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( rpad('*',4000,'*'),
'x' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> variable x refcursor
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2          open :x for select x || y from t;
  3  end;
  4  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> print x
ERROR:
ORA-01489: result of string concatenation is too long

no rows selected

      Rating: 5
Extremely
      Helpful  August 20, 2002
Reviewer:
      A reader
Tom,
Your clarifications are highly helpful for me.
Thanks a Ton!.

      Rating: 2
A
      Tip  August 20, 2002
Reviewer:
      A reader
For qualifying single quotes in a dynamic string One more way is just use
chr(39)
so
l_stmt := 'select *
               from emp
              where ename like :x
                and job = "CLERK"';
could be
l_stmt := 'select *
               from emp
              where ename like :x
                and job = '||chr(39)||'CLERK'||chr(39);
Here we avoid using replace.

I think the simplest thing to do is whenever you need to add a single quote
inside a string just use it two times
like ''  .
so
l_stmt := 'select *
               from emp
              where ename like :x
                and job = ''CLERK'' ';
  I do not undertand why so many people get sooo confused with this Quotes.
There is no Rocket science  here.You just have to understand it for once and
carry it on for your lifetime.
 Any suggestions Tom?

      Rating: 4
A
      Tip  August 20, 2002
Reviewer:
      A reader

      Rating: 4
Using BULK
      Collect with Dynamic Ref Cursor  September 12, 2002

Reviewer:  Ik  from Bangalore
Tom,
   When using Bulk Collect with Dynamic RefCursor iam getting the following
error.

set serveroutput on
DECLARE
    TYPE rc IS REF CURSOR;
    rc2 rc;
    TYPE var IS TABLE OF NUMBER;
    vt var;
BEGIN
    OPEN rc2 FOR 'SELECT CASE WHEN (1=0) THEN 1 ELSE 0 END FROM DUAL';
    LOOP
        FETCH rc2 BULK COLLECT INTO vt;
    EXIT WHEN rc2%NOTFOUND;
    END LOOP;
END;
/

SQL> @98
Input truncated to 1 characters
DECLARE
*
ERROR at line 1:
ORA-01001: invalid cursor
ORA-06512: at line 9

Can you please tell me what could be possibly wrong here.

regards
ik

          Followup:

You cannot bulk collect from a dynamically opened ref cursor until 9i.

      Rating: 4
One more
      Question  September 12, 2002
Reviewer:  Ik
       from Bangalore
Tom,
    Thanks for the response.
    Please tell me what is the default arraysize used by BULK COLLECT(If LIMIT
is not specified) and FORALL.

regards
Ik

          Followup:

Infinity for bulk collect (well, limited in reality to available ram)

forall -- you have to tell forall, there is no "default"

      Rating: 3
Quote Problem
      after using CHR(39)  November 13, 2002
Reviewer:
      Abhijit  from Mumbai
I am trying to form the following query.
Its causing some problem owing to a quote.
Where am I going wrong?

        l_empnames := ' select a.tablename from tabledefinition
a,tablefielddefinition b,tablefieldindexerfieldmaptable c '
    || ' where c.country='|| chr(39) || in_country || chr(39)|| 'And
c.city='||chr(39)|| in_city ||chr(39)
    || ' and c.product='||chr(39) || in_product ||chr(39)
    || ' And c.pickfield = '|| chr(39)||l_indlabelname|| chr(39)
    || ' And b.fieldid=c.fieldid and b.tableid=c.tableid    and
a.tableid=c.tableid';

Cheers!
Abhijit

          Followup:

well, stop doing it that way.  The query should be (must be if you ask me)

l_empnames := 'select ... from ... where c.country = :bv1 and c.city = :bv2 ...'

and you BIND the values in (not even going to consider helping you "fix" your
query as it is 100% the absolute wrong way to do it!)

No quotes needed!!!!

      Rating: 5
Using
      Bind  November 14, 2002
Reviewer:  Abhijit
       from Mumbai
Hi Tom,
Thanks a lot for your solution. However to use bind variables I will have to
open the cursor instead of assigning these values to a string.

Is this assumption correct?

Cheers!
Abhijit

          Followup:

what good is a query in a string unless you open it???  you ulimately have to
open it.

If you do not use bind variables -- your program will be one of those programs I
despise, make fun of, etc.  It'll ultimately fail to scale, in short "it will
stink of a really bad smell"

Binds are EASIER to use then no bind (download my AOTC slides on
asktom.oracle.com as of Nov 2002 -- if you are reading this in the future, they
might not be there anymore).  there I show clearly

o binds = faster
o binds = scalable
o binds = EASIER TO CODE even

      Rating:
      4
dynamic  October 09, 2003
Reviewer:
      phil  from london
can you suggest a more efficient way of doing this dynamic sql ... would I be
better of with 2 seperate statments and 2 cursors. If so how would I switch
between cursors ?

thanks

....

  ssql :=   ' select t.pk, upper(t.accountnumber), ' ||
  '     upper(t.servicinginstitution_bic), ' ||
  '     t.servicinginstitutionreference, upper(t.txntypecode), ' ||
  '    t.txnstatus, t.txnstatusdatetime, t.amount, ' ||
  '     t.valuedatetime, t.entrydatetime, t.originator, ' ||
  '     t.relatedreference, t.beneficiary, ' ||
  '     t.supplementaryinfo, t.informationtoaccountowner, ' ||
  '     t.statement_fk, nvl(t.statementorder,0), t.swifttext, ' ||
  '     t.swiftmessagetyperef, t.txnreference, ' ||
  '     t.accountownerref, nvl(t.isunmatchable,0), t.datasource, ' ||
  '     t.timestamp, t.validationstatus, t.errorcode_fk, ' ||
  '     t.statuscount, t.correlationid, ' ||
  '     t.sql_errormessage, t.ack_status, ' ||
  '       NULL, NULL, NULL, NULL, NULL, NULL,NULL, NULL, NULL,' ||
  '       :l_i_accountfk,:l_i_currencyfk, ' ||
  '       :l_i_accountowner,:l_i_servinstfk, ' ||
   (case when (l_isMulti = false) then
                       ' :statementseq, '
    else ' s.statementseq, ' end ) ||
  ' NULL,NULL,-1,seq_messageinstance.nextval' ||
  ' from txnimport t ' ||
   (case when (l_isMulti = false)
    then
   ' where t.statement_fk = :l_o_statementfk '
    else '  ,statementimport s where t.statement_fk = s.pk ' ||
   ' and s.statementno = :la_statementno ' ||
   ' and s.accountnumber = :la_accountnumber ' ||
   ' and s.servicinginstitution_bic = :la_servicinginstitution_bic ' ||
   ' and s.statementyear = :la_statementyear ' ||
   ' and s.swiftmessagetyperef = :la_swiftmessagetyperef' end );

   if (l_isMulti = false) then
       open c for ssql using
l_i_accountfk,l_i_currencyfk,l_i_accountowner,l_i_servinstfk,
        p_statements.la_statementseq(l_i_currst),l_o_statementfk;
   else
       open c for ssql using
l_i_accountfk,l_i_currencyfk,l_i_accountowner,l_i_servinstfk,
        p_statements.la_statementno(l_i_currst),
        p_statements.la_accountnumber(l_i_currst),
        p_statements.la_servicinginstitution_bic(l_i_currst),
        p_statements.la_statementyear(l_i_currst),
        p_statements.la_swiftmessagetyperef(l_i_currst);
   end if;

      loop

        fetch c  bulk collect into .....

          Followup:

You can use static sql:

 if ( l_isMulti = false )
 then
     open c for select ........;
 else
     open c for select ........;
 end if;

No need for dynamic sql here.

      Rating: 5
Thank
      you  October 13, 2003
Reviewer:  phil

October 09, 2003 OP -- Thanks for the question regarding "How to
      refresh Materialized View when the changes are made in the B tables !",
      version 8.1.6

      You Asked
Dear Tom,

Hi !
How are you doing !

Thanks very much for your Kind help again and again.
Sometimes I wonder if you were not there what would
have happened to my role.

My sincere thanks and Regards to you.

Ques) This Quest is based on Materialized  View .When
we create a Materialized view and after that if the
Changes are made in the Base tables,the changes are not
reflected in the Materialized view.There is a need to
drop it and recreate it.

For exmaple I am creating Materialized View like this :

create materialized view MV_AREA_WRK_PLC
    tablespace test storage ( initial 128K )
    build immediate
    using index tablespace test
as
(
select
    (area.WRK_PLC_AREA_CD || '00000000') as PLC_MERGED_CD,
    area.WRK_PLC_AREA_NM as PLC_MERGED_NM,
    area.WRK_PLC_AREA_CD as WRK_PLC_AREA_CD,
    area.WRK_PLC_AREA_NM as WRK_PLC_AREA_NM,
    area.DISP_ORDER as WRK_PLC_AREA_DISP_ORDER,
    null as WRK_PLC_CD,
    null as WRK_PLC_NM,
    '0000' as WRK_PLC_DISP_ORDER
from
    MT_WRK_PLC_AREA                area
where
    area.del_f = '0'
)
union all
(
select
    (area.WRK_PLC_AREA_CD || plc.WRK_PLC_CD) as PLC_MERGED_CD,
    plc.WRK_PLC_NM as PLC_MERGED_NM,
    area.WRK_PLC_AREA_CD as WRK_PLC_AREA_CD,
    area.WRK_PLC_AREA_NM as WRK_PLC_AREA_NM,
    area.DISP_ORDER as WRK_PLC_AREA_DISP_ORDER,
    plc.WRK_PLC_CD as WRK_PLC_CD,
    plc.WRK_PLC_NM as WRK_PLC_NM,
    plc.DISP_ORDER as WRK_PLC_DISP_ORDER
from
    MT_WRK_PLC_AREA                area,
    MT_WRK_PLC                    plc
where
    plc.WRK_PLC_AREA_CD  = area.WRK_PLC_AREA_CD and
    area.del_f = '0' and
    plc.del_f = '0'
)
order by 4, 7
;

Whenever any change is made in the value for Tables

MT_WRK_PLC_AREA
MT_WRK_PLC

there is a need to drop it and recreate it.

1) Is there any Keyword which can be used while creation
of materialized view so that the Materialized view gets
refresed automatically after some appropriate duration
of time.

2) How Materialized view is different from View ?

Kindly answer !

Thanks & Regards
OP

      and we said...
You need to use the REFRESH clause of the create materialized
view command.  It lets you specify a NEXT clause.  For example:

CREATE MATERIALIZED VIEW T
REFRESH FAST START WITH SYSDATE NEXT trunc(SYSDATE) + 1
AS SELECT .....

Would create a changed only MV that will refresh nightly at
midnight.  In some cases you can even specify "refresh on
commit" meaning apply the changes to the MV as soon as you
commit.

See

http://technet.oracle.com/doc/oracle8i_816/server.816/a76994/mv.htm#721
for more details.

A view is simply a stored query, it consumes no space in the
database other then its data dictionary entries.  A materialized
view on the other hand is a real table -- we've already computed
the view and queries will go against the results of the MV.  If
you were using a regular view -- we just substitute in the view
text in your query.  With an MV, we query the materialized view
data.

Review &
      Followup

      Rating: 4
Materialzed
      View  June 27, 2002
Reviewer:  Bharath
       from India
Hi Tom,
In your reply you had mentioned that an option can be specified during the
creation of MV to reflect the changes made in the base table. Will this work for
only  What

If a MV is based on say more than one table for e.g. 5 tables and has a number
of columns from each base table.
What would happen if one of the column is updated in the base table. Will the
change be reflected in the MV or the
"refresh on commit"  would work for MV based on a single table.

Thanks,
Bharath

          Followup:

see

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76994/mv.htm#32933
ON COMMIT

Refresh occurs automatically when a transaction that modified one of the
materialized view's fact tables commits. Can be used with materialized views on
single table aggregates and materialized views containing joins only.

      Rating: 4
Sun from
      HK  July 16, 2002
Reviewer:  chen sun
       from Hong Kong
Hi Tom,
    Can Materialized View be used in Oracle Discover? and If I want to create a
MV, what should I set in the machine?
thanks ........

          Followup:

A MV works like an index -- the application doesn't have to be aware of it, only
the optimizer.

the goal of an MV is to be totally transparent to the client - making them
available to ANY and ALL clients from sqlplus to discoverer to brio to crystal
and so on.

You'll want to read the chapter on MV's in the data warehousing guide and if you
have my book -- I have a chapter in there explaining what they are, what privs
you need, how to set up and so on.

Lots of examples on this site as well if you use the SEARCH

      Rating: 5
How about set
      operators in a mview..  October 10, 2002
Reviewer:
      Worker Bee  from Kalamazoo, MI
Can set operators like "union all", "minus" etc.. be used in a materialized view
definition?

I was under the impression that it was not allowed (in either 8i or 9i) but
looks like the person with the initial question was able to do it.

Thank you.

          Followup:

you can use them, sure -- they are just not FAST refreshable.

In 9iR2, union ALL views are fast refreshable.

The union, minus -- that would be truly hard cause they remove DUPS, there isn't
any sort of one to one mapping from MV row back to a source row.

      Rating:
      4
Aladdin  September 26, 2003
Reviewer:
      Aladdin  from india
thanks ,
i wanna know is these materialized view could be refreshed on commit if the view
is a join between two tables
on on my bdatabase and the other on another database(borland interbase) using a
database link

          Followup:

no.

      Rating: 1
just
      no  September 27, 2003
Reviewer:  Aladdin

how it could be

          Followup:

because that is the answer??

did you want me to make something up?

you cannot refresh on commit over a dblink, period.

you cannot even capture a "commit" action from borland interbase in Oracle -
think about it.

Sometimes the answer is "no" and nothing else.

      Rating: 5
What additional
      privs?  September 28, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Hi Tom,
I have SELECT, INSERT, UPDATE, DELETE privs. on the SCOTT.EMP table and SELECT
on MVIEW LOG table granted. When trying to create a Materialized View (REFRESH
ON DEMAND) it works but fails when specified REFRESH ON COMMIT. What additional
privs. are required for REFRESH ON COMMIT?

SQL> create materialized view mview_emp
  2  refresh fast on demand
  3  as
  4  select * from scott.emp
  5  /

Materialized view created.

SQL>
SQL> drop materialized view mview_emp
  2  /

Materialized view dropped.

SQL>
SQL> create materialized view mview_emp
  2  refresh fast on commit
  3  as
  4  select * from scott.emp
  5  /
select * from scott.emp
                    *
ERROR at line 4:
ORA-01031: insufficient privileges

SQL>
SQL> disconnect
Disconnected from Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production
SQL>

          Followup:

Most all of the SQL commands include their "pre-reqs" in the SQL Reference:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/statements_63a.htm#2080852

      Rating: 3
joins on
      MV  October 09, 2003
Reviewer:  aish
       from tx
can we use multiple joins on MV's
REFRESH complete WITH ROWID
START WITH SYSDATE NEXT trunc(SYSDATE) + 1
AS
SELECT so.urefitem, fc.CREFITEM1 , Count(oi.prefitem) ItemCount
      FROM tab1 so INNER JOIN tab2 oi ON
      so.sorefitem = oi.sorefitem INNER JOIN tab3 pc ON
      oi.prefitem = pc.prefitem INNER JOIN  tab4 fc ON
      pc.flatiD = fc.FLATID
     WHERE so.ostatus = -1 AND so.osubstatus = -1 AND fc.CREFITEM1   <> 1
     GROUP BY so.urefitem, fc.CREFITEM1  ;
like the above
iam getting ORA-00904 error

          Followup:

[tkyte@tkyte-pc-isdn tkyte]$ oerr ora 904
00904, 00000, "%s: invalid identifier"
// *Cause:
// *Action:

your query is "bad"

if you

SQL> set autotrace traceonly explain
SQL> select .....

does the query even parse?

October 09, 2003 Steve -- Thanks for the question regarding
      "EBCDIC to ASCII Conversion", version 8.1.5

      You Asked
I will be retrieving data from a text data file generated by an
IBM AS/400 using PL/SQL...
My question is would I be able to use the 'convert(...)'
function
to convert the EBCDIC characters to ASCII and which character
set, if any within Oracle, would I use as the 'source_char_set'
argument?

      and we said...
Solution Description
--------------------

Within PL/SQL, use the Built-in SQL 'CONVERT' function:

   Syntax;  CONVERT(stringin, dest_set[, source_set])

This function converts the character string 'stringin' from the
character set identified by source_set to the character set
indentified by dest_set.

I believe:

convert( some_variable, 'US7ASCII', 'WE8EBCDIC500' )

is one way HOWEVER, the data would have to be loaded RAW and
"cast" into a varchar2 using utl_raw.cast_to_varchar2 I believe
(i don't have any ebcdic data sitting around to test with)....

Another option is to convert on load using sqlldr:

 You should use a SQL*Loader control-file setup like:

options (direct=true,errors=9999999,rows=1000,bindsize=999000)
UNRECOVERABLE
LOAD DATA
CHARACTERSET WE8EBCDIC500
INFILE '<file-name>' "fix <your-record-length> buffers 1024"
REPLACE
INTO TABLE  <your-table-name>
( <your-field-specs>
)

- the CHARACTERSET bit takes care of EBCDIC->ASCII conversion
for characer fields.

- the "fix <recordlength>" tells loader not to look voor
embedded new-lines (x'0a') to determine logical records, but use
the given fixed length instead;
x'0a' is not workable because that can also be found as the
contents of binary-numeric data fields!

Review &
      Followup

      Rating: 3
ASCII to EBCDIC
      conversion  October 09, 2003
Reviewer:  Max
      Yao  from Toronto, Canada
Tom,

I am working on an Oracle data extract project. My output file will send to a
DB2 database and a mainframe application. In my file, some fields' type is
COMP-3. I use Oracle build-in function convert () to convert to EBCDIC. This
works fine in SQL*PLUS. But when I use it in my PL/SQL program, I got
"ORA-06502: PL/SQL: numeric or value error: character to number conversion
error". Here is my program.

FUNCTION get_vd_pro_norm_mth_amt(p_account_rec IN  ACCOUNT_ROW) return varchar2
as

        v_scaled_amount            varchar2(15);
BEGIN
        select convert(a.scaled_amount, 'WE8EBCDIC500','US7ASCII')
        into v_scaled_amount
        from pin61_02.rate_bal_impacts_t a, pin61_02.rate_plan_t b
        where b.poid_id0 = a.obj_id0 and
            b.account_obj_db = p_account_rec.poid_db and
            b.account_obj_id0 = p_account_rec.poid_id0 and
            b.account_obj_type = p_account_rec.poid_type and
            b.account_obj_rev = p_account_rec.poid_type;
    return v_scaled_amount;

    EXCEPTION
        WHEN OTHERS THEN
            RETURN NULL;

END get_vd_pro_norm_mth_amt;

I guess the wrong data type of my variable v_scaled_amount generated the
problem. I do not know which data type should I use to store EBCDIC data.

Thanks a lot!

Max

          Followup:

hard to say i don't know what the real type of a.scaled_amount is in the db --
but you might try

create view v as select convert ....

and a describe on that to see what the SQL engine believes you need as
datatypes, eg:

ops$tkyte@ORA920> create or replace view v
  2  as
  3  select ename, convert( ename,  'WE8EBCDIC500','US7ASCII')  ename_ebcdic
  4    from emp
  5   where rownum <= 5 ;

View created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> desc v
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 ENAME                                              VARCHAR2(10)
 ENAME_EBCDIC                                       VARCHAR2(50)

ops$tkyte@ORA920>

      Rating: 3
I find the
      problem  October 09, 2003
Reviewer:  Max Yao
       from Toronto, Canada
Tom,

Sorry to waste your time.

I find the reason. It is nothing related this function. It is because of wrong
data type of a variable in the calling procedure.

Thanks any way.

Max

October 09, 2003 Jim -- Thanks for the question regarding "Oracle
      Web Server and HTP/HTF packages", version 8.1.7

      You Asked
With earlier versions of the Oracle database a small webserver product was
included that enabled one to write html with the htp and htf packages. Now it
seems like Apache is included with the database but for use by the OEM product.
Is there a way with the Oracle Server Enterprise Edition 8.1.7 to setup a simple
webserver environment where I can write dynamic html using the htp/htf packages
without having to purchase the oracle application server?

Additionally, how are DADs created without Application Server?

      and we said...
that small webserver product was called OWS/OAS and it shipped with 7.3 of the
database for a very short period of time (version 1.0 did).  Back then it was
called Oracle Internet Server (ois).  It evolved into OWS, and then OAS and now
iAS.

In 8.1.7 you can actually do this without a webserver at all -- it can all be
embedded straight into the database.  I have not worked through all of this
myself yet -- but if you look at:

http://download-west.oracle.com/docs/cd/A87860_01/doc/java.817/a83720/toc.htm
it has the information you need.  including how to create DADs without ias.

Review &
      Followup

      Rating:
      5
  March 26, 2002
Reviewer:  Ken
      Thurston  from Denver, Colorado USA

      Rating: 1
Ask
      Tom  October 09, 2003
Reviewer:  David
      Terwilliger  from NV USA
Don't bother.  The link Tom provided is, as with many Oracle products and
services, no longer supported. Even this review submittal doesn't work.

          Followup:

hmmm, wonder how it is I got to see this then if it didn't work?

Hmmm

(ps, i went ahead and updated the link anyhow -- links do tend to move from time
to time.  sorry about that)

gotta wonder who let the dogs out -- woof woof.

      Rating:
      3
OSE?  October 09, 2003
Reviewer:  eric
      givler  from Hbg, PA USA
I thought what he was referring to was the built in pl/sql toolkit that is
included with the database itself which also has an apache listener?  I know
when I navigate to the db machine via a URL and port, I can get to an admin page
and configure a DAD from there.  I use this to test all of your sample code for
mod_plsql against a DAD setup for SCOTT/TIGER.

The URL you provided for OSE does work.

          Followup:

(i updated the link for him)

      Rating: 5
Try HTML DB
      Instead  October 09, 2003
Reviewer:  Tyler D.
      Muth  from Reston, VA
The architecture you are asking about is as follows:
Web Browser > Apache (comes with 8i and up) > mod_plsql (comes built into Apache
from Oracle) > db > PL/SQL Web Toolkit (comes with db).  Yes, you can write your
own custom PL/SQL packages to spit out dynamic html, but it's tedious and not
very productive given the alternatives...

HTML DB is a new feature of the 10g database and is supported back to 9iR2.  It
uses the exact architecture above, but is infinately more productive.  It takes
care of the details, like session state management, report pagination, security,
etc., so you can focus on the impoert aspects of your app, like business logic,
user interface, and navigation.

Check it out at
http://htmldb.oracle.com,/
I doubt you'll be disappointed.  You can also search this
site for "html db" and see what you come up with.  Speaking of this site, did I
mention it's written in HTML DB?

      Rating: 5
Answer to your
      DAD question  October 09, 2003
Reviewer:  Tyler
      D. Muth  from Reston, VA
The process of creating a DAD with mod_plsql is simple.  You can either do it
through the web interface at yourserver.com/pls/ or modify the
$ORACLE_HOME/Apache/modplsql/conf/wdbsvr.app and copy one of the example DADs.

AskTom uses Apacha and mod_plsql.  If you look at the URL above:  .../pls/ tells
Apache to use mod_plsql, "ask" is the DAD, f?p=4950:8 is HTML DB syntax (f is
package with a procedure called p, 4950 is the application number, and 8 is the
page number, just in case you were interested)

      Rating: 4
Yo,
      Tyler....small correction to your statement...  October 31,
      2003
Reviewer:  Robert  from CT
> AskTom...."ask" is the DAD, f?p=4950:8 is HTML DB syntax
>(f is package with a procedure called p, 4950 is the
> application number, and 8 is the
> page number, just in case you were interested)

Heh Tyler,  maybe you're a bit too deep into HTML DB to realize this...but I am
still in the puddle here :)

There's no "f" package...f is a procedure, p is the first parameter...

October 09, 2003 -- Thanks for the question regarding "Pivoting a
      result set", version 8.1.7

      You Asked
Tom,
From this query:

select g.ichangerequesttype cr_type,
COUNT (case when p.completion_time>=(sysdate-7) then
g.ichangerequesttype else null end) wk1,
COUNT (case when p.completion_time>=(sysdate-14) then
g.ichangerequesttype else null end) wk2,
COUNT (case when p.completion_time>=(sysdate-21) then
g.ichangerequesttype else null end) wk3,
COUNT (case when p.completion_time>=(sysdate-28) then
g.ichangerequesttype else null end) wk4,
COUNT (case when p.completion_time>=(sysdate-56) then
g.ichangerequesttype else null end) wk8,
COUNT (case when p.completion_time>=(sysdate-84) then
g.ichangerequesttype else null end) wk12,
COUNT (case when p.completion_time>=(sysdate-112) then
g.ichangerequesttype else null end) wk16,
COUNT (case when p.completion_time>=(sysdate-140) then
g.ichangerequesttype else null end) wk20,
COUNT (case when p.completion_time>=(sysdate-168) then
g.ichangerequesttype else null end) wk24
FROM giocm g, process p, process_defn pd
WHERE g.fk_process = p.pk_process
AND p.fk_process_def=pd.pk_process_def
AND upper(pd.process_name) IN ('PROC_1', 'PROC_2')
group by g.ichangerequesttype

I get the following result:

CR_TYPE    WK1    WK2    WK3    WK4    WK8    WK12    WK16    WK20    WK24
Cd    1    1    1    1    1    1    1    1    1
Co    0    0    0    0    0    0    0    2    2
Ed    0    0    0    0    0    0    0    4    6
Eo    1    1    1    1    1    1    1    3    7
Sd    0    1    1    1    1    1    1    2    4
So    1    3    3    3    3    3    3    4    23
Tr    0    0    0    0    0    0    0    10    10

that I want to pivot such that it looks like:

PERIOD    EO    ED    SO    SD    CO    CD    TR
wk1     1    0    1    0    0    1    0
wk2     1    0    3    1    0    1    0
wk3     1    0    3    1    0    1    0
wk4     1    0    3    1    0    1    0
wk8     1    0    3    1    0    1    0
wk12     1    0    3    1    0    1    0
wk16     1    0    3    1    0    1    0
wk20     3    4    4    2    2    1    10
wk24     7    6    23    4    2    1    10

What is the best approach to the solution given that I have no column to anchor
my pivot query?

Thanks for your response

Jos

      and we said...
You have something to pivot on -- PERIOD.  Do the same thing sort of:

select period, sum(eo), sum(ed), ..., sum(tr)
  from (
select case when p.completion_time >= sysdate-7 then 'wk1'
            when p.completion_time >= sysdate-14 then 'wk2'
            ...
            when p.completion_time >= sysdate-168 then 'wk24'
        end period,
       ( decode( g.ichangerequesttype, 'EO', 1, 0 ) ) 'EO',
       ( decode( g.ichangerequesttype, 'ED', 1, 0 ) ) 'ED',
       ...
       ( decode( g.ichangerequesttype, 'TR', 1, 0 ) ) 'TR'
  from giocm g, process p, process_defn pd
 WHERE g.fk_process = p.pk_process
   AND p.fk_process_def=pd.pk_process_def
   AND upper(pd.process_name) IN ('PROC_1', 'PROC_2')
   and p.completition_time >= sysdate-168
       )
 group by period;

should be close -- fill in the dots...
Review &
      Followup

      Rating: 5
Unexpected
      results  February 28, 2003
Reviewer:  Jos
       from Seattle, WA
After filling in the blanks and executing the query, I receive an unexpected
result set from the CASE statement:
It returns values for wk1, wk2 then skips to wk20 and wk24. I was expecting a
cumulative count such that even if there was no activity was completed within
the week of (sysdate-21), then wk3 = wk2. However, when I select distinct values
with the CASE statement there are only the PERIODs (wk01, wk02, wk20, wk24).

Any idea why that should happen? Looking at the query it shouldn't behave that
way

          Followup:

do you have any data for wk03 .. wk16???

If not, we'll just need to outer join to a set of weeks you want:

select a.period, b.e0, b.ed, ....
  from ( YOUR_QUERY_FROM_ABOVE ) b,
       ( select 'wk01' period from DUAL union all
         select 'wk02' ....
         union all
         select 'wk24' period from dual ) a
 where a.period = b.period (+)
/

      Rating: 3
Problem
      restatement  March 04, 2003
Reviewer:  Jos
       from Seattle, WA
Tom,
When you look at the initially posted SQL query, you will notice that the count
is cummulative over the 24 week period such that by week 24, we are seeing all
the records grouped by CR_type completed within the past 24 weeks. That is the
result set I wanted to pivot

The approach you suggested groups the records by the week they were completed,
such that it is not cumulative over time. I hope that the problem statement is
clear enough.

Thanks for all the good work Tom.

          Followup:

just got off a plane but -- what does:

select period, sum(eo) over (order by period) eo,
               sum(ed) over (order by period) ed,
               ....
from (
select period, sum(eo) eo, sum(ed) ed, ..., sum(tr) tr
  from (
select case when p.completion_time >= sysdate-7 then   'wk01'
            when p.completion_time >= sysdate-14 then  'wk02'
            ...
            when p.completion_time >= sysdate-168 then 'wk24'
        end period,
       ( decode( g.ichangerequesttype, 'EO', 1, 0 ) ) 'EO',
       ( decode( g.ichangerequesttype, 'ED', 1, 0 ) ) 'ED',
       ...
       ( decode( g.ichangerequesttype, 'TR', 1, 0 ) ) 'TR'
  from giocm g, process p, process_defn pd
 WHERE g.fk_process = p.pk_process
   AND p.fk_process_def=pd.pk_process_def
   AND upper(pd.process_name) IN ('PROC_1', 'PROC_2')
   and p.completition_time >= sysdate-168
       )
 group by period)
order by period;

do -- it should build the "un-cumulative" matrix and then create running totals
(note wk01 -- not wk1 anymore, need them to be "sortable")

      Rating: 3
How to pivot
      ename to "my emps"?  October 09, 2003
Reviewer:
      ht  from california
Tom,
How could I get this output:

mgr mgr_employees
13  jones,blake,clark

from this data?

>select ename from emp where mgr=7839;

ENAME
------------------------------
JONES
BLAKE
CLARK

3 rows selected.

Thanks.

          Followup:

search this site for stragg

October 10, 2003 Freek -- Thanks for the question regarding "FGAC
      and web applications", version 8.1.7

      You Asked
Hi,

We have a website that uses an oracle database for storing individual data
(names, addresses, companies, …) as well as content (articles, headlines,…) and
navigation (menu’s). All the tables we use for this are in the same schema.

We use php (4.1.1) and apache (1.3.22) for the website (oracle version is
8.1.7). The database and the web server are running on the same machine, but
they will be put on different machines in the near future.

The php connects to the server with user holding the schema, when a user has to
log in (to change his address for instance) his login and password is checked in
a table -> no oracle user is being made for a person.

The team that developed this has put all of security logic in the php
(E.g. a manager can only change an article belonging to his department/country).

I would like to change this and use FGAC to control the security.

But I have noticed that the database connections are not closed. If I kill a
session and then try to go to the website I sometimes get an “invalid session
id” error, I guess this is because apache is maintaining a pool of http sessions
which are holding the oracle sessions open.
As far as I understand FGAC (I have never used it before) you set some
application context, which will define the predicate returned.

But if the session will be reassigned after the client disconnects, would it not
be possible that the new client uses the same values in the application context
(when for instance a direct link to a part of the site is being used) as the
client who had the session before him?

I could alter the site to create oracle users (+- 6000 registrations), but I
still would have the problem with the connection pool.

Can you give some explanation of how normally the security is being implemented
on a website.

Thanks

Freek D’Hooge

      and we said...
It depends on how the connections are used.

In any case, a tiny modification to the php will solve the problem.

For EVERY request, they must inspect the identity and verify it is correct.
When the user is "first seen", they goto a table and verify the user/password.
They probably assign an encrypted cookie at that point in time so subsequent
requests can just look at the cookie.

Very well -- the code that checks the user for the first time AND the code that
verifies the cookie on each call will simply execute a stored procedure passing
the actual identity of the user.  This stored procedure will setup the entire
application context for this user.

That is how it would work in this environment.
Review &
      Followup

      Rating: 4
Thank you for
      the quick answer  April 22, 2002
Reviewer:
      Freek D'Hooge  from Belgium
Now there is only left the difficult task to make them see that it is worth the
effort to change the security from application level to database level.

thanks again

Freek D'Hooge

      Rating: 4
Performance
      Implications?  June 30, 2002
Reviewer:  Jonas
      Edgeworth  from Berkeley, CA USA
Because the webapp is having to call a stored proc in order to set the
application context for *every* request, I'm curious if there is a measurable
performance hit incurred by having to make this additional call for each
request?

          Followup:

Setting the context, via dbms_session, takes a trivial amount of time.

Now, if the code preceding the dbms_session code takes a long time... thats a
different story (that is your code of course)

In 9i, we've introduced the concept of a statefull context -- one that can
retain its values across sessions.  All you need is a sessionid to rejoin your
context in progress.  If your code takes a bit of time to initialize the
context, this can save you that time on every page after the first.

      Rating: 4
statefull
      context  July 02, 2002
Reviewer:  Prince.

Dear Tom,

Can you please, point me to the URL (doc) which has info on "stateful context"?

Thanks,

          Followup:

see

http://206.204.21.139/otndoc/oracle9i/901_doc/server.901/a90125/statements_53.htm#2060929
and read about the 'accessed globally' option

      Rating: 4
encrypted
      cookie?  July 03, 2002
Reviewer:  Vikas
      Sharma
Hi,
In my application(usong mod_pl/sql) after verification from of user
name/password i create a normal cookie and i have a routine which checks for it,
if found then it passes, otherwise the the page is not displayed. This routine
get executed for all the pages. I want to know that instead of the normal cookie
i should always create a encrypted cookie( may be by using hash value when
creating it). If yes then why? pl explain.

          Followup:

What is in your cookie?  What does your cookie look like, what is contained in
it.

      Rating: 3
encrypted
      Cookies  July 03, 2002
Reviewer:  Vikas
      Sharma  from Delhi - India
Hi tom,
I am sorry for not providing you full details.

My cookie has the user name and the organization id which he belongs. Both are
used to check the other authorizations like some one is authorized to view and
edit an employee record. the page which is used to edit the employee record
checks the user name, if he is authorized then the page is shown otherwise it
redirected to no_auth page.

Authorization are assigned to user in an another table, my auth check routine
check the authrization as per this table records.

          Followup:

You better encrypt that then.  Add to the data other stuff like WHEN the cookie
was  last accessed (so the cookie is not only encrypted but EVER CHANGING).
don't accept cookies that are "older then some threshold" -- else someone might
"kidnap" them.  I would also consider a cookie that consists of a sessionid and
a timestamp -- and turn the sessionid into a username/id on the SERVER instead
of passing that around.  Else, I can just

o edit my cookies file
o type in any name/id I want (since it is just in clear text)
o fire up my browser
o I'm IN

it is really just that easy.

      Rating: 4
encrypted
      cookie  July 05, 2002
Reviewer:  Vikas
      Sharma  from Delhi India
Hi  Tom,

The cookies i creating expires when the browser window is closed.(not a text
file cookies) it remain into browser memory till the window is activated (who
created it) ie i am not given any expire date to cookie when creating. Then also
i need to encrypt it. kindly let me know. As far as i know, there is no way to
find the name/value of the cookie with any other
program/language. am i right? Kindly let me know.

Thanks

Regards,
Vikas Sharma

          Followup:

....
Else, I can just

o edit my cookies file
.........

it is that simple.  YOU might be using a session based cookie.  I can create
that cookie.  I can just use TELNET and set any cookie I want at any time.

cookies are passed in clear text, they are trivial to discover.  In fact, my
browser makes it soooo easy to discover the cookies in effect by a very simple
GUI.

YOU NEED to encrypt the data.  It would take me about 30seconds to defeat your
security as it exists now (only cause GUI's slow you down, it could take less
time)

      Rating:
      4
Thanks  July 05, 2002
Reviewer:  Vikas
      Sharma  from Delhi India
Thanks Tom. For providing me this vital information.
Regards
Vikas Sharma

      Rating: 3
Server side
      cookies   October 09, 2003
Reviewer:  Vikas
      Sharma  from Delhi India
Hi Tom,

There is any way to create the server side cookies. I have a situation that my
client says that, they dont want to enable the cookies on the client machine's
browser.

My existing application stores a session id(generated when user logon) into the
cookie, which is retirve in to a routine which check the existance of the
session id and retrive all other variable from a table which maintain session
state against that session id.

I could have change the logic in my application and store the session into a
hidden item or pass it thr url to every page BUT for that now i have to change
my all pages which will take a long time so i would not like to do that.

Now i am thinking to create a server side cookie. But i am not sure how can i
create a server side cookies and retrives its value in to my security routine,
and how to assign a unique name to that cookie.

Kindly suggest me. If there is any other way to achive it kindly let me know.

Thanks And Regards,

Vikas Sharma

          Followup:

in a word "no"

your client is overly paranoid perhaps.  cookies are not evil, they are not bad,
they are virtually necessary for any "non toy application"

On asktom, we use a sessionid in the URL (look up, see that ugly number).  We
try to avoid use of cookies but -- it does require the application to pass the
sessionid from page to page itself.

      Rating: 3
Server side
      Cookies  October 09, 2003
Reviewer:  Vikas
       from Delhi India
Hi Tom

Thanks,

We will try again with our client again.

Thanks,

Vikas Sharma

          Followup:

Tyler, friend of Tom here,

Why not create a really long, very random, globally unique id for every user and
store that in 1. The cookie and 2. A table in the database along with thier
usrename and any other info.  You can then read the cookie and look up who it
belongs to in the table.

This is almost exactly how we do it HTML DB (htmldb.oracle.com).  We have some
other security checks going on too, but I can't give away all of our secrets ;)

If you are interested, HTML DB has built-in support for VPD.  I don't have a
link to our new doc yet with the example, but I can find it if you are
interested.

      Rating:
      3
  October 10, 2003
Reviewer:  Vikas
      Sharma  from Delhi India
Hi Tyler,

Thanks For showing your interest.

My application is doing the same thing am storing the encrypted long randomly
generated sessionid in a cookies(client side) and store the same in to table
with other variable into the table, then my security routine check the cookie
first and get the session id stored in the cookie.
and i check the session id into table and get the other details from the table.

But now the problem is My Client says that they dont want it to do by Cookies.
On there network none of the PC browser will have the cookies enable. Now i am
not sure how to do it? What i can do is just passing the session id to every
page in the hidden item using POST or thr URL. But for that i have to change my
application pages to accept that parameter. Which is a long job and it may take
months to do that as my application has lot of pages.

I am not able to convence my client, cookies are not threat to security. As They
are very much required to impliment the security. If we are not using the
cookies and sombody just do copy and paste of the URL on a new window then he is
in..Or else sombody can note the URL and type it on some other PC and he is in.

***********************
There is any way to create and read a client side file Using Mod_plsql. If it is
possible than i can have cookies like behaviour.
***************************

Pl Suggest me..

Thanks to you and Tom for helping Oracle developers.

Regards

Vikas Sharma

          Followup:

Tyler here again,

You could take the cookie security a step further by also storing their ip
address and exact browser version (owa_util.get_cgi_env('REMOTE_ADDR'),
owa_util.get_cgi_env('HTTP_USER_AGENT')) in your session table.  In this case
someone would have to 1. guess a really long number 2. Spoof the IP of the
machine that still has that exact session open, and 3. do it from the exact
browser version of the person who owns that exsiting session.  Furthe, if you
store a time stamp with you session and update that each time a page is viewed,
it's very easy to determine old sessions.  Now you can have a dbms_job that runs
every 8 hours or so and purges all sessions that have been in-active for 8+
hours.  You can apply this same model to your idea of only using session numbers
in the URL and hidden items on the page.  This way copying ther URL (session id
and all) from one machine to another will NOT work unless they have the same IP
and browser version.

I think your client needs to re-evalutate their security policy.  While I'm NOT
a security expert, I don't feel that cookies present a security risk by
themselves.  The real threat comes from sites that save sensitive information in
cookies.  This is still minimal, especially if you employ a browser that is
historically less likely to be hacked, such as Mozilla (or any of it's
variants).

The only way for mod_plsql to create and read a file on the client side is
through cookies.

I suspect this was not the exact answer you were looking for, but I hope it
helps,
Tyler

October 10, 2003 Chris -- Thanks for the question regarding
      "Synonyms and Dependency Management", version 8i

      You Asked
I'm struggling with a concept here.

I understand through the Oracle Concepts documentation that
"Only dependent objects can be invalid; tables, sequences, and synonyms are
always valid."

I understand that tables and sequences do not make reference to other database
objects, so they are independent - and always valid.  However a synonym, by
definition, makes reference to another database object.  So I would suspect that
a synonym, by its nature, would be a dependent object.

I accept the documentation as accurate and true.  But the behavior makes no
sense to me.

Is there a rationale for this seemingly counterintuitive quality of ever-valid
synonyms?
Chris Gates
cg

      and we said...
A synonym is just a "symbolic link" if you are familar with Unix.  It does not
even have to point to something real:

ops$tkyte@ORA8I.WORLD> desc NOT_AN_OBJECT
ERROR:
ORA-04043: object NOT_AN_OBJECT does not exist

ops$tkyte@ORA8I.WORLD> create synonym x for NOT_AN_OBJECT;

Synonym created.

A synonym is just a pointer.  It cannot become invalid.  It is not dependent on
the object it points to.  They don't even have to point to any existing objects.
Review &
      Followup

      Rating: 5
how can we
      check for synonym validation  October 09, 2003

Reviewer:  A reader
Hi

Is there a way to check if a synonym is invalid, i.e has no parents?

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:227413938857#12933421732980

      Rating: 5
what is synonym
      parent is not table?  October 10, 2003
Reviewer:
      A reader
hi

that works for tables, what can we do with PL/SQL stored codes, synonym of
synonyms, sequences

          Followup:

I guess one could query the data dictionary to see if the object exists or not.

or just add a when others to ignore other compilation errors.

ops$tkyte@ORA920> create or replace procedure p
  2  as
  3  begin
  4  null;
  5  end;
  6  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create synonym p_syn for p;

Synonym created.

ops$tkyte@ORA920> create synonym totally_bogus for not_here;

Synonym created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      synonym_bogus EXCEPTION;
  3          pragma exception_init( synonym_bogus, -980 );
  4  begin
  5     for i in (select synonym_name from user_synonyms)
  6     loop
  7     null;
  8               begin
  9                      execute immediate
 10                        'create or replace view v
 11                           as select * from ' || i.synonym_name;
 12                   exception
 13                      when synonym_bogus then
 14                              dbms_output.put_line( i.synonym_name );
 15                                      when others then null;
 16                   end;
 17     end loop;
 18  end;
 19  /
TOTALLY_BOGUS

      Rating:
      5
null;  October 10, 2003
Reviewer:
      A reader
Hi

What is the use of null; in the code...?

          Followup:

just a remnant

it need not be there.  I was having a problem -- wanted to isolate it so I put
null in the loop and commented out the body to locate it.  then i forgot to take
null out

      Rating: 5
Why Oracle
      allows to create trigger on Synonym ?  October 10, 2003

Reviewer:  Ashiq Shamsudeen  from Trichy, Tamil Nadu,
      India
Tom,

  From your quote "A synonym is just a "symbolic link" ..." . But why oracle
allows us to create  a trigger on synonym. I believe even in manuals ,I haven't
seen ,saying that "you can create triggers on synonyms" .

scott@ASHIQ> create synonym a_syn for emp;

Synonym created.

scott@ASHIQ>create or replace trigger a_syn_trg
2  after insert on a_syn
3  for each row
4  begin
5  null;
6* end
7  /

Trigger created.

Is any reason behind this ??

Oralce8i(817) on W2k professional

          Followup:

let me turn the question around, is there any reason why not?

it just "does"

some ddl permits it:

ops$tkyte@ORA920> create index t_idx on emp_syn(empno);

Index created.

some ddl does not:

ops$tkyte@ORA920> alter table emp_syn add x number;
alter table emp_syn add x number
*
ERROR at line 1:
ORA-00942: table or view does not exist

October 10, 2003 Eric -- Thanks for the question regarding
      "Performance using TAPIs", version 8.1.6

      You Asked
Hi Tom,
  I work in n ecommerce company that only develops multi tier application. We
don't generate anything except data models.

  For some reason the lead architect decided to use Designer’s Table API on all
the tables of all of our databases. He does 3 things with it:  Log the database
username in audit fields, maintain journaling on a couple of tables and validate
cg_ref_codes.

  This causes lots of headaches for developers because when the "nice" triggers
validate cg_ref_codes they don't give EJB friendly messages (they crap out with
the unhandled cg$eror exception).  I’ve been trying for 6 months the get him to
remove them by showing him that inserts take at least twice as long to execute
with TAPI compared to small user writen triggers and by showing him the ugly
erors caused by the triggers but nothing seems to work.

  In your opinion, when - why should the TAPIs be used?  What kind of arguments
could I use?

      and we said...
Well, you won't get supporting arguments from me actually.

TAPI's -- they encapsulate the logic, they are generated for you, what you are
trading off is some performance for ease of development.  All the lead architect
must do is push a button and his code is done (how about you?  how much of your
code is hand written, how much generated??)

I'm a big fan of not writing code unless I have to.  If performance is fine
(acceptable or more then acceptable), and you don't have a large amount of
developed code to maintain, enhance, fix, track, etc -- I would stick with his
approach.

I don't know what a "nice" EJB friendly message would look like?  You see, I'm a
huge fan of NOT putting a line of sql into java -- only plsql.  If you are
putting PLSQL, you can catch and do whatever you want with the "ugly" errors:

  begin
       procedure( inputs )
  exception
       when this_exception then
            raise_application_error( -20001, 'This happened' );
       when that_other_exception then
            raise_application_error( -20002, 'This other thingy happened' );
  end;

instead of just

   begin procedure( inputs ); end;

Review &
      Followup

      Rating:
      3
TAPI  December 28, 2001
Reviewer:  Eric
      Veilleux  from Montreal, Canada
Actually since all that's being done with the TAPI are the audit fields (logging
JDBC user) and validating cg_ref_codes (the journaling really is an exception on
3 or 4 tables) I wrote a stored proc that generates the triggers that could do
it all (using the data dictionary).  We don't incorporate logic at all in the
TAPI since the logic is in our beans and rarely in stored procs... never in
Designer.

unfortunately we don't have much plsql in our EJB because the simple ones are
container managed and the rest just do straight inserts or updates in the
necessary tables (Unless they use data access objects).  Since the inserts
aren't done in a plsql block it's impossible for us to trap the user defined
exception that gets thrown by the TAPI's triggers (cg$errors).  So a "nice"
error message would be something like this:

ERROR at line 1:
ORA-20001: Invalid value hghghsg in domain BATCHES_STAT_TYP for column
BATCHES.STAT_TYP

instead of something like this:

ERROR at line 1:
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at "FPA.CG$ERRORS", line 40
ORA-06512: at "FPA.CG$BATCHES", line 283
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-01403: no data found
ORA-06512: at "FPA.CG$BIR_BATCHES", line 45
ORA-04088: error during execution of trigger 'FPA.CG$BIR_BATCHES'
ORA-06512: at line 3

      Rating: 5
TAPI - or how
      to keep the code under control  January 07, 2002

Reviewer:  Dushan  from Czech
I totaly agree with Tom - I am too lazy to type (or copy&paste) similar code
(for e.g. procedures, triggers for cascade update, journalling,
sequence-in-parent for child table etc) for various tables, having headache when
tables are changed. I am probably not better programmer then the guys who
developed TAPI in Oracle Designer. I like to generate the code instead of coding
manually. Thats what the CASE tool is for. Why to reinvent the wheel again and
again?
I hate copy-paste programming.

      Rating: 3
Not using bind
      variables  October 10, 2003
Reviewer:
      A reader
I was intrigued by this Designer 6i feature. I tried out a few TAPI generations,
and was suprised to see bind variables were not used. Is there a reason for
this? Or am I missing something

Here is a sample:

IF cg$sel_rec.the_rowid is null THEN
       SELECT    DESCRIPTION
       ,         WG_ID
       ,         NAME
       ,         DATE_CREATED
       ,         FRENCH_DESCRIPTION
       ,         FRENCH_NAME
       , rowid
       INTO      cg$sel_rec.DESCRIPTION
       ,         cg$sel_rec.WG_ID
       ,         cg$sel_rec.NAME
       ,         cg$sel_rec.DATE_CREATED
       ,         cg$sel_rec.FRENCH_DESCRIPTION
       ,         cg$sel_rec.FRENCH_NAME
       ,cg$sel_rec.the_rowid
       FROM   WORKING_GROUPS
       WHERE        WG_ID = cg$sel_rec.WG_ID;
    ELSE
       SELECT    DESCRIPTION
       ,         WG_ID
       ,         NAME
       ,         DATE_CREATED
       ,         FRENCH_DESCRIPTION
       ,         FRENCH_NAME
       , rowid
       INTO      cg$sel_rec.DESCRIPTION
       ,         cg$sel_rec.WG_ID
       ,         cg$sel_rec.NAME
       ,         cg$sel_rec.DATE_CREATED
       ,         cg$sel_rec.FRENCH_DESCRIPTION
       ,         cg$sel_rec.FRENCH_NAME
       ,cg$sel_rec.the_rowid
       FROM   WORKING_GROUPS
       WHERE  rowid = cg$sel_rec.the_rowid;
    END IF;

"WHERE        WG_ID = cg$sel_rec.WG_ID;" If my understanding of bind variables
is correct, each time "cg$sel_rec.WG_ID" changes, Oracle will hard-parse the
statement, eventually eating up the SGA etc...

          Followup:

in plsql -- all plsql variables referenced in SQL are in fact bind variables.

That is the sheer and utter BEAUTY of PLSQL -- it is so so hard NOT to use bind
variables properly.  You have to go out of your way and use dynamic sql in order
to NOT use binds.

consider:

ops$tkyte@ORA920> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA920> declare
  2          l_empno number := 1234;
  3  begin
  4          for x in ( select * from emp LOOK_FOR_ME_IN_THE_TRACE where empno =
l_empno )
  5          loop
  6                  null;
  7          end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

select *
from
 emp LOOK_FOR_ME_IN_THE_TRACE where empno = :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.02          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          1          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.02          0          1          0           0

Look at that -- nicely bound, all magically, no work, very easy.  Reason number
2143321 that you should use PLSQL heavily if you want performance (as many
developers in VB and JDBC are loathe for whatever misinformed reasons to DO IT
RIGHT)

      Rating: 5
You learn
      something everyday  October 10, 2003
Reviewer:
      A reader
Thanks for the info. I was actually doing extra work for nothing, recoding
similar statements using Native Dynamic SQL was overkill, since as you pointed
out, PL/SQL binds them automatically.

This will definately serve me well in my next project.

October 10, 2003 zhu -- Thanks for the question regarding "logon
      trigger doesnot on guys with dba role", version 8.1.7

      You Asked
hi, tom:
  It is really difficult to get a chance to ask you a question: your site is
always so busy:)
  I want to create a trigger to prevent user from logon database during some
time(or from some ip address)
  the source of the trigger is:
create or replace trigger logon_time after logon on database
declare dba number;
begin
    if to_char(sysdate,'HH24') between 9 and 24
    --or to_char(sysdate,'HH24') between 0 and 6
    then
        raise_application_error(-20001,'Not allowed to logon database during
this time');
    end if;
end
/
This trigger works for every body except the ones with dba role.
I also tried your proc_role('DBA') But cannot find a solution.Users with dba
role can still logon.
The trigger body like ;
if to_char(sysdate,'HH24') between 9 and 24  and proc_role('DBA')
then
raise_application_error();
  How can i make all users unable to logon, including those with dba roles?
  Thanks very much!

      and we said...
This is the correct behavior and is designed to make it so your database doesn't
get into a state whereby NO ONE can log in (815 can get to that state).  It is a
feature.

My suggestion, don't use the DBA role.  Create your own ROLE (create many of
them in fact) YOUR_DBA and use that instead.  DBA is just a role (but a special
one).  If you don't use "DBA", you will be able to treat your DBA users as if
they were any ordinary user.
Review &
      Followup

      Rating: 4
Nice
      approach!!  November 01, 2001
Reviewer:  Sanjay
      Raj  from NJ, USA
Tom,
    Can the YOUR_DBA have all the privileges of the DBA role?

Just my 2 cents. I think if I know the IP addresses that I want to restrict then
I can use the following to restrict logon

Create a file named protocol.ora as follows.
tcp.excluded_nodes=(XXX.XY1.XY2.Y, XXX.XY2.XY3.Z)
tcp.validnode_checking=yes
Suppose I want to Disallow connection to a user  with an IP addresse of
192.168.11.20 or 192.168.10.12 then I can  simply configure the protocol.ora
file as follows:
tcp.excluded_nodes=(192.168.11.20, 192.168.10.12)
tcp.validnode_checking=yes
The destination of this file would be same as that of sqlnet.ora and
tnsnames.ora. The listener will required to be stopped and restarted.
There are certain bugs in this method, so be sure to check with Oracle support
or Metalink for the specific platform and version.
For example, On Unix and Oracle version 8.1.x, I had  to rename protocol.ora to
.protocol.ora in the network/admin directory.

Thanks for all yr help

          Followup:

Yes, your_dba can be just like a dba...

      Rating:
      4
Risky  November 02, 2001
Reviewer:
      A reader
Let's hope nothing goes wrong between 9 and 24 ;-)

      Rating: 3
Tom Has There
      Been a Change  December 12, 2001
Reviewer:
      A reader
Hi Tom,

i am using

Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production

Now the After Logon trigger Fires for even users with DBA Roles Enabled.

What is the change ... The same trigger did not fire for me in 8.1.6

Comments pls....

Regards,
Ganesh R

          Followup:

The triggers FIRE (always did) for users with DBA roles.

The triggers do not fire for INTERNAL or SYSDBA/SYSOPER

The logon triggers (816 and up) do not FAIL for DBA's -- they will not prevent a
DBA from logging on but they EXECUTE.

tkyte@TKYTE816> drop table t;

Table dropped.

tkyte@TKYTE816> create table t ( msg varchar2(50) );

Table created.

tkyte@TKYTE816>
tkyte@TKYTE816> create or replace trigger logon_trigger
  2  after logon on database
  3  declare
  4          pragma autonomous_transaction;
  5  begin
  6          insert into t values ( 'logon by ' || user || ' at ' ||
  7                                  to_char(sysdate,'dd-mon-yyyy hh24:mi:ss' )
);
  8          commit;
  9  end;
 10  /

Trigger created.

tkyte@TKYTE816> connect tkyte/tkyte
Connected.
tkyte@TKYTE816> select * from t;

MSG
--------------------------------------------------
logon by TKYTE at 13-dec-2001 08:55:37

tkyte@TKYTE816> select * from session_roles;

ROLE
------------------------------
PLUSTRACE
CONNECT
DBA
SELECT_CATALOG_ROLE
HS_ADMIN_ROLE
EXECUTE_CATALOG_ROLE
DELETE_CATALOG_ROLE
EXP_FULL_DATABASE
IMP_FULL_DATABASE
JAVA_ADMIN

10 rows selected.

tkyte@TKYTE816> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle8i Enterprise Edition Release 8.1.6.0.0 - Production
PL/SQL Release 8.1.6.0.0 - Production
CORE    8.1.6.0.0       Production
TNS for 32-bit Windows: Version 8.1.6.0.0 - Production
NLSRTL Version 3.4.1.0.0 - Production

      Rating: 2
What about
      this.  December 14, 2001
Reviewer:
      A reader
Hi,

<quote>
This trigger works for every body except the ones with dba role.

And we Said...

This is the correct behavior and is designed to make it so your database doesn't

get into a state whereby NO ONE can log in (815 can get to that state).  It is a

feature.
</quote>

U Had said that it is the correct behaviour and do not use the DBA Role but use
a Different Role.

Your comments Pls.

Regards,
Ganesh R

          Followup:

The trigger FIRES.  The trigger did not have the desired effect in their
environment.  The trigger did not prevent DBA's from logging on (people with the
DBA role). The trigger executed, the error however does NOT prevent (by design)
DBA's from logging on.

In order to NOT have this be the case (to be allowed to prevent DBA's from
logging on) my recomendation was to *not use the DBA ROLE*.  It gives way too
much power anyway.  A secure system would not use connect, resource, dba -- they
would make their own specialized roles and would have many DBA roles (jr_dba,
sr_dba, nightime_dba, etc).  It is only when you use a role named DBA that this
ignoring of the error takes place.

      Rating: 5
ora-0604 when
      logon trigger fires  January 22, 2002
Reviewer:
      A reader
Hi

we have a logon trigger to restrict number of session with  TOAD, however this
trigger does not work fully.
We always get these errors

ORA-00604: error occurred at recursive SQL level 1
ORA-20002: 'Maximum connection exceeded'
ORA-06512: at line 19

the code is

create or replace trigger trg_test
after logon on database
declare
  l_prog varchar2(36);
  l_cnt number;
  l_max number := 1;
begin
  select substr(program, 1, 4)
  into l_prog
  from sys.v_$session
  where audsid=(select userenv('sessionid') from dual);
  if l_prog = 'TOAD'
  then
  select count(*)
  into l_cnt
  from sys.v_$session
  where program like l_prog||'%';
    if l_cnt >= l_max
    then
    raise_application_error(-20002, 'Maximum connection exceeded');
    end if;
  end if;
end;
/

it works but why we get three errors? if the user has DBA role then these errors
reflect in the alert....

          Followup:

You get the error stack, you are not getting three errors, you get one -- with
three lines of text.

the actual error is "the recursive SQL failed" the logon trigger.

That would be meaningless to you so we include "Max connection exceeded" (your
message).

An on logon trigger in 816 and up will not prevent a DBA account from logging
in, the failure will be recorded in the alert log however.

      Rating: 5
so 604 is not
      avoidable?  January 22, 2002
Reviewer:
      A reader
Hi Tom

So we cannot avoid that 604 error?
The problem you see is this, when we want to restrict access of an ODBC
application then the ODBC only returs ORA-0604 to the users and not the error
-20002 as we intended and they complain that Oracle is not working, so the raise
application error message seems useless in this case! Is there any workaround?

          Followup:

the raise application error is NOT useless.

The problem is this -- you are NOT calling the "logon" trigger directly.  If you
put that code into a procedure and you called that code -- you would get the
20002.  YOU are not calling this code.  It is called by the database -- you call
CONNECT(), they call this code.  This code fails -- thus the connect fails --
thus the connect returns a connect error (along with information regarding the
underlying cause of the failure).

There is no way you would get a 20002 from a connect - it is not an error that a
connect can return.

      Rating: 4
DBA
      Roles  January 23, 2002
Reviewer:  Dilip
      Dandekar  from Pune, India
Tom,

Your suggestion of jr_dba, sr_dba, night_dba roles is very good.

Can you please suggest sys priviledges to be assigned to these respective roles.

          Followup:

not really -- thats something YOU need to decide based on YOUR site.

For example, what if I recommended "ALTER USER" for the SR_DBA -- well, that
would give them the ability to become ANY user at will, effectively giving them
100% access to anything.

You need to analyze your needs, your level of security and grant appropriately.
Depending on your paranoia level -- you would never give a grant without fully
understanding the implications.

Giving someone a list of suggested privs lends nothing towards understanding WHY
they need these privs or WHAT these privs might imply.

      Rating: 5
How to stop an
      IP using logon trigger ?  January 24, 2002

Reviewer:  Randy  from Chicago.IL
I know that you can use sys_context('userenv,'ip_address) to find the IP if you
are logging to the database from any tcp client application. But it fails to do
so if I telnet into the server from a workstaion and try sqlplus. It appears
blank. Any suggestion ?

Thanks,

          Followup:

Yes, because you are not connecting over net8 -- it only works with network
connections, not local connections.  If the ip_address is "blank", the
ip_address should be obvious -- its the server.  You can get your hostname from
"select host_name from v$instance" for logging purposes.

      Rating: 5
Followup on DBA
      role  April 16, 2003
Reviewer:  Donna Drawe
       from Houston , TX USA
I was surprised to find that when I modified logon and logoff triggers, I WAS
able to execute the triggers from IDs with the DBA role (for example, mine).

Is there a difference in 8.1.7.4 (the version I am currently testing on)?  Or
was it because DBA was not the only role granted to the IDs?

My current trigger is:
CREATE OR REPLACE TRIGGER  GASAUDIT.TR_GASLOGONAUDIT
AFTER
LOGON
ON DATABASE
DECLARE
   machinename VARCHAR2(64);
   osuserid VARCHAR2(30);
   programname VARCHAR2(48);
      CURSOR c1
   IS
      SELECT osuser, machine,program
      FROM v$session
      WHERE audsid = USERENV('sessionid');
begin
   OPEN c1;
   FETCH c1 INTO osuserid, machinename,programname;
   INSERT INTO gasaudit.gaslogonaudit
   VALUES('LOGON', SYSDATE, USER, osuserid, machinename,programname);
   CLOSE c1;
   COMMIT;
END;

BTW, we originially tried to create the triggers for a specific schema but only
the owner could execute the triggers?

          Followup:

they *fire*

they cannot prevent you from logging it -- even if they fail.

not that you asked BUT

insert into gasaudit.gaslogonaudit
select 'LOGON', sysdate, user, osuserid, machinename, programname
  from v$session
 where audsid = userenv('sessionid')

would be the proper way to code that -- NEVER use an explicit cursor when an
implicit one will do.  less code -- less work -- easier.

dont know what you mean by the last question....

      Rating:
      5
clarification...  April 16, 2003
Reviewer:
      Donna  from Houston, TX USA
Tom,

Thanks for the response.

We originally created the trigger to be

...
AFTER
LOGON
ON GAS
...

In this instance only logins / logoffs as 'GAS' were captured.  If I logged in
as me, there was nothing in the table.

Thanks again for your VERY invaluable help!

          Followup:

well, that is sort of the definition of what "after logon on gas" is about.  You
were looking for "after logon on database"

      Rating: 2
Why Use Logon
      Trigger ?  April 17, 2003
Reviewer:  Randy
       from Chicago,US
I would not use logon trigger for that. It's much more conveient to use the
profile for the useres to restrict the logon in certain time.

          Followup:

coming in from left field on this one.  how would you use a profile to restrict
the times during which a user can login exactly?

      Rating: 2
that's not the
      purpose...  April 17, 2003
Reviewer:  Donna
       from Houston, TX USA
Randy, My purpose in these triggers is to capture when users logon / logoff the
database and which program they are using to access the database (application
.exes, msaccess, artisan, etc.).

I have an audit requirement to be able to track user access to the database and
provide information on users who are accessing from outside the application
code.

This was the method my DBA found doing research on the Oracle site (before I
found AskTom).

Tom, thanks for the response... one last followup.  Please excuse my lack of
knowledge, but I've seen examples where you have logon triggers ON xxxxx.SCHEMA.
 How would that differ from ON DATABASE and what would you use them for?

Donna

          Followup:

hopefully you do not RELY on the programname information.

xcopy msaccess.exe sqlplus.exe
xcopy sqlplus.exe your_custom_name.exe

Hmmm, it is trivial to spoof the name.

AUDIT CONNECT is much much much (like alot) easier still

      Rating: 5
the best as
      usual  May 29, 2003
Reviewer:  Gabriel
       from Montreal, Canada

      Rating: 5
logon trigger
      not firing  October 09, 2003
Reviewer:  amit
      poddar  from New Haven, Ct USA
Hi,
I tried the code you supplied above

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production

SQL> drop table t;

Table dropped.

SQL> create table t ( msg varchar2(50) );

Table created.

SQL> SQL> create or replace trigger logon_trigger
  2  after logon on database
  3  declare
  4  pragma autonomous_transaction;
  5  begin
  6  insert into t values ( 'logon by ' || user || ' at ' ||
  7  to_char(sysdate,'dd-mon-yyyy hh24:mi:ss' ));
  8  commit;
  9  end;
 10  /

Trigger created.

SQL> connect system
Enter password:
Connected.
SQL> select * from t;

no rows selected

SQL> connect apps
Enter password:
Connected.
SQL> connect systemm
Enter password:
Connected.
SQL> select * from t;

no rows selected

SQL>

Why is the trigger not firing ??
Is it a bug.

Thanks in advance

          Followup:

look in your alert log on the server --

and make sure _system_trig_enabled is NOT set in your init.ora!

      Rating: 4
You were right
      !!  October 10, 2003
Reviewer:  amit poddar
       from New Haven, CT
Hi,

You were right we had _system_trigger_enabled=false in the init.ora file.

Could you please clarify which triggers won't fire because of this parameter.
Since I tested all the other triggers and they were firing correctly.  By other
triggers I mean the DML triggers on the tables.

          Followup:

only system event triggers won't fire.  triggers on various DDL or database
events are disabled.

      Rating: 5
Excellent...But
      one clarification   October 10, 2003
Reviewer:
      Riaz Shahid  from PRAL, Lahore, Pakistan
Tom !

I am running Oracle 8.1.7 on windows 2000 server box.

Please refer to first quote by Sanjay Raj

<Quote>
Tom,
    Can the YOUR_DBA have all the privileges of the DBA role?

Just my 2 cents. I think if I know the IP addresses that I want to restrict then

I can use the following to restrict logon

Create a file named protocol.ora as follows.
tcp.excluded_nodes=(XXX.XY1.XY2.Y, XXX.XY2.XY3.Z)
tcp.validnode_checking=yes
Suppose I want to Disallow connection to a user  with an IP addresse of
192.168.11.20 or 192.168.10.12 then I can  simply configure the protocol.ora
file as follows:
tcp.excluded_nodes=(192.168.11.20, 192.168.10.12)
tcp.validnode_checking=yes
The destination of this file would be same as that of sqlnet.ora and
tnsnames.ora. The listener will required to be stopped and restarted.
There are certain bugs in this method, so be sure to check with Oracle support
or Metalink for the specific platform and version.
For example, On Unix and Oracle version 8.1.x, I had  to rename protocol.ora to
.protocol.ora in the network/admin directory.
<Quote>

I want to allow specific IPs to access my database (like 196.0.x etc.). How can
i do that using above ? I think i will have to add
tcp.included_nodes=(196.0.x)
instead of
tcp.excluded_nodes=(192.168.11.20, 192.168.10.12)

Is that true ?

Moreover, I can't find protocol.ora file in my Oracle home. where it is located
?(documentation says it should be in $$Oracle_Home$$/network/admin directory).

Please suggest ...

Riaz

          Followup:

the protocol.ora file is an optional file -- if none is there, just create it.

You should see the documentation for all of the proper use techniques.  All of
the docs are online at otn.oracle.com.  This goody is in the net admin guide
(along with lots of other good stuff)

October 10, 2003 Yogesh -- Thanks for the question regarding "Ref
      Cursor parameter ", version 8.1.7

      You Asked
I'm back with one more REF CURSOR question. I have a package in X schema with
name PKG1 ... I have a Procedure which returns a REF CURSOR. Now I have to use
this parameter as a input parameter in other procedure which is not a part of
this package / schema . i.e.

The procedure where I need to use this REF CURSOR belongs to a different schema
lets say 'Y' .... is it possible ? if yes can you please give a piece of code,
if not can you please give me some solution for this ?

Can I simply create a same type in 'Y' schema and refer it ? Please help ...

Thanks

      and we said...
Either, Y will use X's type or X will use a type defined by Y

Both techniques follow.  It is literally no different then if you use a single
schema.

ops$tkyte@ORA817DEV> create user x identified by x;

User created.

ops$tkyte@ORA817DEV> create user y identified by y;

User created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> grant create session, create procedure to x;

Grant succeeded.

ops$tkyte@ORA817DEV> grant create session, create procedure to y;

Grant succeeded.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> @connect x/x
ops$tkyte@ORA817DEV> set termout off
x@ORA817DEV> REM GET afiedt.buf NOLIST
x@ORA817DEV> set termout on
x@ORA817DEV>
x@ORA817DEV> create package pkg1
  2  as
  3     type x_rc is ref cursor;
  4
  5     procedure p( p_cur in out x_rc );
  6  end;
  7  /

Package created.

x@ORA817DEV>
x@ORA817DEV> create package body pkg1
  2  as
  3     procedure p( p_cur in out x_rc )
  4     is
  5     begin
  6      open p_cur for select * from dual;
  7     end;
  8  end;
  9  /

Package body created.

x@ORA817DEV>
x@ORA817DEV> grant execute on pkg1 to y;

Grant succeeded.

x@ORA817DEV>
x@ORA817DEV> @connect y/y
x@ORA817DEV> set termout off
y@ORA817DEV> REM GET afiedt.buf NOLIST
y@ORA817DEV> set termout on
y@ORA817DEV>
y@ORA817DEV> create package demo_pkg
  2  as
  3      type y_rc is ref cursor;
  4
  5      procedure p_called_with_x_type( p_cursor in x.pkg1.x_rc );
  6      procedure p_called_with_y_type( p_cursor in y_rc );
  7  end;
  8  /

Package created.

y@ORA817DEV> show err
No errors.
y@ORA817DEV> create package body demo_pkg
  2  as
  3      procedure p_called_with_x_type( p_cursor in x.pkg1.x_rc )
  4      is
  5          l_rec dual%rowtype;
  6      begin
  7          fetch p_cursor into l_rec;
  8          dbms_output.put_line( 'row = ' || l_rec.dummy );
  9          close p_cursor;
 10      end;
 11
 12      procedure p_called_with_y_type( p_cursor in y_rc )
 13      is
 14          l_rec dual%rowtype;
 15      begin
 16          fetch p_cursor into l_rec;
 17          dbms_output.put_line( 'row = ' || l_rec.dummy );
 18          close p_cursor;
 19      end;
 20  end;
 21  /

Package body created.

y@ORA817DEV> grant execute on demo_pkg to x;

Grant succeeded.

y@ORA817DEV>
y@ORA817DEV> @connect x/x
y@ORA817DEV> set termout off
x@ORA817DEV> REM GET afiedt.buf NOLIST
x@ORA817DEV> set termout on
x@ORA817DEV>
x@ORA817DEV> declare
  2      l_cursor pkg1.x_rc;
  3  begin
  4      pkg1.p( l_cursor );
  5      y.demo_pkg.p_called_with_x_type(l_cursor);
  6  end;
  7  /
row = X

PL/SQL procedure successfully completed.

x@ORA817DEV>
x@ORA817DEV>
x@ORA817DEV> declare
  2      l_cursor y.demo_pkg.y_rc;
  3  begin
  4      open l_cursor for select * from dual;
  5      y.demo_pkg.p_called_with_Y_type(l_cursor);
  6  end;
  7  /
row = X

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating:
      4
Excellent!!!!!  June 12, 2003
Reviewer:
      A reader

      Rating: 5
Cursor details
      not known   June 12, 2003
Reviewer:  Yogesh
       from Pune India
It was very useful reply ... one more subquestion

If I don't know what type of ref cursor is going to be returned .. i.e. I don't
know in which package it is defined ...(so I can't define variable type as
...%TYPE)  Is it still possible to to refer it in other schema?

          Followup:

if you don't know the type of cursor being returned, you cannot possible pass it
onto something else.

You need to get your code "under control".  How many weak ref cursor types do
you need in your system?  one.

In 9i, you would just use SYS_REFCURSOR, until then, you just have to
standardize your code.

      Rating: 5
Thanks a
      ton  June 12, 2003
Reviewer:  Yogesh
       from Pune India
Thanks for valuable reply. But I have 8.1.7 in production, so need to go with
"Package Control" Thanks once again

          Followup:

hold on, backup the bus.  looks like ref cursors (weak ones at least) are
"special", different from other variable types!  They are the exception to the
rule (and proof that yes, we all learn new stuff every day, i did not expect
this myself).

This example uses 3 separate types, no worries.  If you use a STRONGLY typed
cursor:

type rc is ref cursor return dual%rowtype;

it will NOT work, but weak ref cursors do just fine:

ops$tkyte@ORA817DEV> create user x identified by x;
User created.

ops$tkyte@ORA817DEV> create user y identified by y;
User created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> grant create session, create procedure to x;

Grant succeeded.

ops$tkyte@ORA817DEV> grant create session, create procedure to y;

Grant succeeded.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> @connect x/x
ops$tkyte@ORA817DEV> set termout off
x@ORA817DEV> REM GET afiedt.buf NOLIST
x@ORA817DEV> set termout on
x@ORA817DEV>
x@ORA817DEV> create or replace package foo
  2  as
  3          type rc is ref cursor;
  4          procedure p ( p_cursor out rc );
  5  end;
  6  /

Package created.

x@ORA817DEV> create or replace package body foo
  2  as
  3          procedure p ( p_cursor out rc )
  4          is
  5          begin
  6                   open p_cursor for select * from dual;
  7          end;
  8  end;
  9  /

Package body created.

x@ORA817DEV> grant execute on foo to public;

Grant succeeded.

x@ORA817DEV>
x@ORA817DEV> @connect y/y
x@ORA817DEV> set termout off
y@ORA817DEV> REM GET afiedt.buf NOLIST
y@ORA817DEV> set termout on
y@ORA817DEV>
y@ORA817DEV> create or replace package bar
  2  as
  3          type rc is ref cursor;
  4          procedure p ( p_cursor in rc );
  5  end;
  6  /

Package created.

y@ORA817DEV> create or replace package body bar
  2  as
  3          procedure p ( p_cursor in rc )
  4          is
  5                  l_rec dual%rowtype;
  6          begin
  7                  fetch p_cursor into l_rec;
  8                  dbms_output.put_line( l_rec.dummy );
  9                  close p_cursor;
 10          end;
 11  end;
 12  /

Package body created.

y@ORA817DEV> grant execute on bar to public;

Grant succeeded.

y@ORA817DEV>
y@ORA817DEV> @connect /
y@ORA817DEV> set termout off
ops$tkyte@ORA817DEV> REM GET afiedt.buf NOLIST
ops$tkyte@ORA817DEV> set termout on
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> declare
  2          type rc is ref cursor;
  3          l_cursor rc;
  4  begin
  5          x.foo.p( l_cursor );
  6          y.bar.p( l_cursor );
  7  end;
  8  /
X

PL/SQL procedure successfully completed.

      Rating:
      5
Amazing  June 13, 2003
Reviewer:  Yogesh
       from Pune India
Thats really Amazing .... so one don't need to worry about the incoming ref
cursor type .... so no worry ... thanks once again ....

      Rating: 5
Another problem
      with cursor   June 19, 2003
Reviewer:  Yogesh
       from Pune India
I have written following code .. it is incomplete .. but I'm stuck at one point

CREATE OR REPLACE PROCEDURE PopVirtualTab
(
       p_Id                                           VARCHAR2,
    p_SchemaName                                VARCHAR2,
    p_VirtualTab                                VARCHAR2,
    p_LocSeqId                                    Locationref.Locseqid%TYPE,
    p_StatusCode               OUT              NUMBER,
    p_StatusMessage            OUT              VARCHAR2
 )
 IS
   v_ErrorFlag                NUMBER :=0;
   v_Termtable                  Termrecloc.Termtable%TYPE;
   v_Termcolnm                  Termrecloc.Termcolnm%TYPE;

   v_Id                            VARCHAR2(8);
   v_SelectString              VARCHAR2(1000);
   v_InsertString              VARCHAR2(1000);

   TYPE REF_CURSOR              IS REF CURSOR;
   v_PopCursor                  REF_CURSOR;
   v_AccessTabCursor          REF_CURSOR;

   CURSOR v_ColumnCursor(v_TabName VARCHAR2) IS
   SELECT Column_Name FROM User_Tab_Columns WHERE
   Table_name = v_TabName;

BEGIN

    v_Id := '00'||p_Id;

    v_InsertString := 'INSERT INTO '|| p_VirtualTab ||
                         '(p_Tab,p_Col,p_Val) VALUES (:1,:2,:3)';

    OPEN v_AccessTabCursor FOR
    SELECT Termtable,Termcolnm FROM Termrecloc
    WHERE Locseqid = p_LocSeqId;

v_ErrorFlag :=1;

    LOOP
        FETCH v_AccessTabCursor INTO v_Termtable,v_Termcolnm;
        EXIT WHEN v_AccessTabCursor%NOTFOUND;

        v_SelectString:='SELECT * FROM '||p_SchemaName||'.'||v_Termtable ||
                        ' WHERE '||v_Termcolnm||' = '||v_Id;

v_ErrorFlag :=2;

        OPEN v_PopCursor FOR v_SelectString;

        LOOP
            FETCH v_PopCursor INTO ......;
            EXIT WHEN v_PopCursor%NOTFOUND;

v_ErrorFlag :=3;

            FOR v_ColumnRec IN v_ColumnCursor (v_TableName)
            LOOP
                EXECUTE IMMEDIATE v_InsertString USING
v_TableName,v_ColumnRec,...
            END LOOP;

v_ErrorFlag :=4;

        END LOOP;

        CLOSE v_PopCursor;

END LOOP;
CLOSE v_AccessTabCursor;

EXCEPTION

WHEN NO_DATA_FOUND THEN
    p_StatusCode    := -1;
    p_StatusMessage := 'Error in PopVirtualTab At :'||v_ErrorFlag ||
SUBSTR(SQLERRM,1,1000);
WHEN OTHERS THEN
    p_StatusCode    := -2;
    p_StatusMessage := 'Error in PopVirtualTab At :'||v_ErrorFlag ||
SUBSTR(SQLERRM,1,1000);
END;

Here in this statement

    v_SelectString:='SELECT * FROM '||p_SchemaName||'.'||v_Termtable ||
' WHERE '||v_Termcolnm||' = '||v_Id;

It returns me the actual name of the columns and its values ... I want to open
this cursor using cursor for loop as the table might contain upto 200 columns
... I don't want to .. rather I can't define so many variables ... This is for
one schema .. there are multiple schemas where table sturctures of same
table(name) is different ... is it possible to use for loop in this case .. if
not what could be the option in this case ?

or in other way .. is there any way where we manually open cursor and then use
it in for cursor loop .. I could not do this .. I tried ..

or
can I use for loop like this

for x in (some dynamic sql)
loop

end loop

I can't define ROWTYPEs as there are man yschemas and many tables in it ..

Please give me some solution

          Followup:

ugh, no bind variables -- how horrible is that.\

so, tell me why this isn't just "insert into .... select ... from "????  why do
you have any procedural processing row by row in there at ALL?

      Rating: 5
Table Type
        July 10, 2003
Reviewer:  Yogesh  from
      Pune India
On the parallel lines what you explained for ref cursor ... can we even access
table types from one package which is in one database .. in other database in
some package ?

          Followup:

object types are not "distributed" in 8i, in 9i you can do some things
distributed with them.

      Rating: 5
Whats the way
      out  July 10, 2003
Reviewer:  Yogesh
       from Pune India
Thanks for the reply ... what is the way out then in 8i ? do I have to get
control of package ? can you please give a small piece of code .. or some link
where I can find that ..

          Followup:

way out of what exactly?

      Rating: 5
Way
      out  July 10, 2003
Reviewer:  yogesh
       from Pune India
If I have a package in Database db1 ...which creates a table type .. now I want
to refer it in another package which is in another database .. can I do it ?

          Followup:

if the type is a plsql type:

create package foo
as
  type array is table of varchar2(25) index by binary_integer;
end;
/

then the remote database can:

create synonym foo for foo@remote;

create package bar
as
   g_data foo.array;
end;
/

you could use the dblink in the package itself, but this is one place where I
like to use synonyms to hide that dblink.

      Rating: 5
Thanks a
      lot  July 11, 2003
Reviewer:  Yogesh
       from Pune India
Thanks a lot once again ... I was not sure if I could do that ... you cleared by
doubt

      Rating: 5
Collection and
      dblink  July 15, 2003
Reviewer:  Yogesh
       from Pune India
Suppose I have defined a collection
Create or replace TYPE DummyT  AS OBJECT
(
    Feature VARCHAR2(50),
    FeatureVal VARCHAR2(50)
);

and a table of collection

create type TblType as table of DummyT;

my first question is can we define this inside a package ? I get PLS-00540:
object not supported in this context error .. am I defining something wrong ?

Secondly if I define above types in database A and now I want to refer it in
database B can I define my procedure parameter as

 v_param  IN TblType@remote  ?

if not whats the alternative ?

          Followup:

you would create a plsql table type of records - not objects - in plsql

then, we are right back to where we were before right above..

      Rating: 5
Need more
      help  July 15, 2003
Reviewer:  Yogesh
       from Pune India
I want to create a composit type

  Feature VARCHAR2(50),
  FeatureVal VARCHAR2(50) how would I Do that ?

and How I'll be able to handle that in my code ? as there will be two elements
per row ?

          Followup:

time for someone to read:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96624/toc.htm
it'll tell you how to create record (composite) types and tables there of

      Rating: 5
Object
      Scope  July 15, 2003
Reviewer:  Yogesh
       from Pune India
If I create an composit object type in DB1 added 10 records.. Can I pass it to
other database using dblink ?

          Followup:

don't you have a pair of machines to test some ideas on ?

read above, i think i answered this

      Rating:
      5
  July 15, 2003
Reviewer:  Yogesh
       from Pune India
I don't have access to any of the boxes .... right now .. so can't experiment
much ....

          Followup:

use your local machine with a loopback link

      Rating:
      5
  July 15, 2003
Reviewer:  Yogesh
       from Pune India
First

CREATE TYPE DummyT AS OBJECT
(
   project_no NUMBER(2),
   title      VARCHAR2(35),
   cost       NUMBER(7,2)
);

and then

create type TblType as table of DummyT;

which I can't define in Pl / Sql

Secondly

  TYPE EmpTabTyp IS TABLE OF emp%ROWTYPE INDEX BY BINARY_INTEGER;
  emp_tab EmpTabTyp;

So in this case .. I need to create a table of two columns and
then define a tab of that type ......

Third

   TYPE AnEntry IS RECORD
   (
      term    VARCHAR2(20),
      meaning VARCHAR2(200)
   );

  TYPE Glossary IS VARRAY(250) OF AnEntry;

No If I want to use any one of these on DBlink in a package
which once should I use

          Followup:

*the last* -- really, that is what I said above -- you have to use a plsql
record.  You have to use a plsql table type.  then you can use a dblink.

      Rating: 5
Thanks once
      again   July 15, 2003
Reviewer:  yogesh
       from pune India
Thanks for clearing my doubts and your valuable time ... are there any
performance issues which may crop up due to this ?  as this procedure may be
called frequently .. may be 1-5 times in 1 min ... and with a resultset of 100
rows everytime ..

          Followup:

time to BENCHMARK

1 to 5 times in a minute is pretty "small"

      Rating: 5
OPEN and
      FETCH  July 16, 2003
Reviewer:  Arun Gupta
       from Harrisburg, PA USA
Tom
When does the query associated with a ref cursor actually executes?

I created an example as:

SQL> ed
Wrote file afiedt.buf

  1  create or replace package pkg_test as
  2  type rc is ref cursor;
  3  procedure usp_test(ref_cur out rc);
  4* end;
  5  /

Package created.

SQL> ed
Wrote file afiedt.buf

  1  create or replace package body pkg_test as
  2  procedure usp_test (ref_cur out rc) is
  3  begin
  4  open ref_cur for <a bad SQL which takes 5 minutes to return results>;
  5  end;
  6* end;
  7  /

Package body created.

SQL> variable r_cur refcursor;
SQL> execute pkg_test.usp_test(:r_cur);

PL/SQL procedure successfully completed.

This step completed in a fraction of second. If the ref was executed, this step
should have taken 5 minutes. However, the next step, which prints the

          Followup:

in general queries are not "executed" until you ask for the first row back, they
are just setup to be executed.  so parsing is "fast", fetching can be "slow"

      Rating: 5
Cursor
      Farm  October 08, 2003
Reviewer:  Neil
       from UK
I was pondering whether to separate all my cursors from my business logic by
putting them into packages.
The benefits are fairly obvious, but what sort of trouble could I expect?

          Followup:

well, the benefits are not obvious to me.

what benefits do you see?

I like implicit cursors in a for loop first.

A locally declared cursor second (in the local stack of the procedure/function)

A globally declared cursor in the PACKAGE BODY third (don't really like it at
all tho)

Putting them in a specification however, that wouldn't be my approach AT ALL.

Here is a preview of next months column -

My personal preference is, has been, and will always be to use implicit cursors
in most cases; that is, do not explicitly define a cursor at all! Like this for
example:

is
 ...
begin
  ....
  for x in ( select * from emp )
  loop

This technique works exceptionally well for result sets of under say 50-100
rows.  I prefer this technique over explicit cursors for these reasons:
o It is marginally more efficient CPU-wise than using an explicit cursor.
o I can glance at the code and easily see what data I'm dealing with. The query
is staring me in the face.
o It makes me use views when queries get big. I still see what data I'm querying
in the view, but the complexity of the view is hidden. Rather then hide it in a
cursor, I "hide it" in a view.

Now, there are times you have to use an explicitly defined cursor; the most
common one being when you have larger result sets to process and you need to use
BULK COLLECT during the FETCH phase for performance. When I have to use an
explicitly defined cursor, I would opt for a local one, defined inside the
procedure itself (not even in the package body, but right inside of the
procedure in the body). Why? Well,

o It is marginally more efficient CPU-wise than a global cursor (which is
defined in the spec).
o I can still glance at the code and easily see what I'm dealing with.
o It makes me use views since the query is in my procedure and I don't want it
to obscure the rest of the code.
o It is clear who the cursor belongs to.

Do you see a reason pattern repeating here? Also, a cursor with a local scope
(in a procedures STACK) automagically cleans up after itself. There is no if
cursor%isopen junk littering my code. (None of my code ever has used that
'feature' of isopen.) I don't have to worry that "oh, you know procedure P1 uses
cursor_x, and I use cursor_x, and since they are the same cursor_x, will we
stomp on each other?" So I don't have that problem people always have with
global variables. I view cursors in the package specification or body (not
defined in a procedure, but having global scope) with the same level of distrust
I would have of a global variable; the possibility of side effects from multiple
procedures accessing these global variables is too high. I use global variables
only when no other choice exists.

So, to wrap up, in order of preference:

a) no cursors (select into, for x in ( select..... )

b) locally declared cursors, when forced into it for whatever reason, such as
   - bulk binds that need to use the LIMIT clause
   - ref cursors

I recommend that you avoid globally declared cursors, in a package spec, for
these reasons:

o You lose the nice features of encapsulation.  The cursors are globally
accessible and exposed to anyone that can access the package.
o You experience marginally slower performance (I stress marginally, and this is
not the main point).
o You reduce the readability of the package body.
o They are globals, and globals are, in general, a bad coding practice to be
avoided whenever possible.

      Rating: 5
That'll be a
      "NO", then!  October 09, 2003
Reviewer:
      Neil  from UK
Thanks, Tom - informative as ever! I'm shortly off on a new build and was
mulling over the possibilities of exercising some sort of control over the
cursors in the system. Having them all in one place seemed an attractive
proposition. It would appear that the pros are outweighed by the cons, though...
Rgds,
Neil

      Rating: 4
Ref Cursor
      parameter   October 09, 2003
Reviewer:  Jamil
      Shaibani  from Saudi
Dear Sir
I am getting this error message when I try
to use this code ,and also always if I create a user
I could not connect even if I give grant connect to to the user like this
SQL> CREATE USER X IDENTIFIED BY X;

User created.

SQL> CREATE USER Y IDENTIFIED BY Y;

User created.

SQL> GRANT CREATE SESSION,CREATE PROCEDURE TO X;

Grant succeeded.

SQL> GRANT CREATE SESSION,CREATE PROCEDURE TO Y;

Grant succeeded.

SQL> @connect x/x
unable to open file "connect.sql"

SQL> GRANT CONNECT TO X
  2  /

Grant succeeded.

SQL> connect x/x
ERROR:
ORA-04045: errors during recompilation/revalidation of TEST.LOGON_TRIGGER
ORA-01031: insufficient privileges

Warning: You are no longer connected to ORACLE.
moste of the time if I create the user I am getting error to connect to the new
created user al

so I will not be able to connect to the new user if I am not giving
GRANT DBA TO THE NEW USER

Best Regards
Jamil

          Followup:

umm, that has nothing to do with ref cursors as far as I can see???

someone has put an on logon trigger.

this trigger is called LOGON_TRIGGER
it is owned by the schema TEST

It is invalid

It cannot be recompiled due to this.

Your DBA account you are using to create the users is not affected by this (DBA
accounts are not affected by the failure of a login trigger) but your normal
user X is.

Nothing to do with ref cursors.

Everything to do with an object in the TEST schema.  Fix it.

      Rating: 5
this sentences
      can not work in oracle,why?  October 09, 2003

Reviewer:  peter.liu  from ShangHai.CHINA
SQL> variable r_cur refcursor;
SQL> execute pkg_test.usp_test(:r_cur);

          Followup:

why can't they indeed?  they work for me -- assuming of course I have a package
named pkg_test (or schema named pkg_test) with a procedure usp_test in it that
has an out parameter of a ref cursor type.

you'll have to be a little more "clear" as to why it is not working for you (eg:
a test case like i supply all of the time with everything needed to reproduce
the issue)

      Rating: 5
REFCURSOR not
      supported  October 09, 2003
Reviewer:
      peter.liu  from shanghai.china
when i run this two command,it appear this error:REFCURSOR not supported,why? i
have created the package and package body,and compile successfully.
SQL> variable r_cur refcursor;
SQL> execute pkg_test.usp_test(:r_cur);

          Followup:

you'll need to give the FULL example and CUT AND PASTE the exact error message.

      Rating: 5
Re: That'll be
      a "NO" then!  October 09, 2003
Reviewer:
      Neil  from UK
Hold on a sec there, Tom! I've just come back to this thread, and re-read your
answer. You're absolutley right, of course - but this is a thread about
ref_cursors. Would it still be a no-brainer to encapsulate the cursors in a few
packages and have them exposed via functions returning a ref_cursor? You could
still do bulk operations and use the "for x in" construct, couldn't you?

          Followup:

it is not just about for x in

it is about encapsulation and hiding of variables.

A cursor is just like a variable -- putting it into a spec would make it a
global.  In my world, globals are to be avoided at all costs.

I never said you could not do any of the above

- return refcursor
- bulk operations
- for x in

on packaged cursors -- it was all about dependencies and cascading invalidations
(tell me what happens when that package spec that you have with 50 queries in it
used by 40 packages gets ONE query added or ONE query fixed???  cascading
invalidations all over the place)

What happens when function Foo called by function Bar decides to reuse cursor X
-- which Bar is already using?

ops$tkyte@ORA920> create or replace package demo_pkg
  2  as
  3          cursor c is select * from dual;
  4  end;
  5  /

Package created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace procedure bar
  2  as
  3  begin
  4          for x in demo_pkg.C
  5          loop
  6                  null;
  7          end loop;
  8  end;
  9  /

Procedure created.

ops$tkyte@ORA920> create or replace procedure foo
  2  as
  3  begin
  4          for x in demo_pkg.C
  5          loop
  6                  bar;
  7          end loop;
  8  end;
  9  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec foo
BEGIN foo; END;

*
ERROR at line 1:
ORA-06511: PL/SQL: cursor already open
ORA-06512: at "OPS$TKYTE.DEMO_PKG", line 3
ORA-06512: at "OPS$TKYTE.BAR", line 4
ORA-06512: at "OPS$TKYTE.FOO", line 6
ORA-06512: at line 1

A global is a global -- and stinks as bad -- by any other name...

      Rating: 5
Hi, this is a
      full example,pls help me.  October 09, 2003

Reviewer:  peter.liu  from Shanghai,CHINA
-------------package------
create or replace package pkg_test
 as
 type cur_test is ref cursor;
 procedure p_test ( v_cur out pkg_test.cur_test ) ;
end pkg_test;
-------------package body------
create or replace package body pkg_test is
  procedure p_test ( v_cur  out pkg_test.cur_test )
  as
  v_sql varchar2(100);
  begin
    v_sql := 'select * from dept';
    OPEN v_cur FOR v_sql;
  end p_test;
end;
--then i input command-----
SQL> variable r_cur refcursor;
REFCURSOR not supported
----end----
pls help me thx!

          Followup:

what tool are you using???

is that sqlplus?

      Rating: 5
Re: That'll be
      a "NO" then!  October 10, 2003
Reviewer:
      Neil  from UK
You must have been wondering what on earth I was taking about! What I had in
mind was something like this:

  lRefCur := pk_cursor_farm.f_get_certain_info();
  for x in lRefCur
  loop
    p_process(x.column);
  end loop;

But it's not possible! You have to pass a pointer that is already associated
with the data using the "OPEN FOR" statement. I tried it. I should have tried it
BEFORE I posted my question, but...

          Followup:

correct - and due to the way ref cursors MUST be treated (parsed during each
open, instead of once per session like all other static SQL in plsql) I recomend
using a ref cursor ONLY WHEN YOU MUST return a result set, rather then as a
rule.  avoid them until you actually *need* them.

      Rating: 5
It's only not
      possible due to the FOR loop though, isn't it?  October 10,
      2003
Reviewer:  A reader
Hi Tom,

Not that i'm extolling the virtues of this approach, of course, but the problem
with not being able to do this cursor farm approach is down to the FOR looping
isn't it? You can still FETCH and CLOSE the REF CURSOR from a FUNCTION based
approach

SQL> CREATE OR REPLACE PACKAGE farm
  2  AS
  3    TYPE l_ref IS REF CURSOR;
  4    FUNCTION get_cursor RETURN l_ref;
  5  END farm;
  6  /

Package created.

SQL> CREATE OR REPLACE PACKAGE BODY farm
  2  AS
  3    FUNCTION get_cursor RETURN l_ref
  4    AS
  5      refc   l_ref;
  6    BEGIN
  7      OPEN refc FOR SELECT dummy FROM dual;
  8      RETURN refc;
  9    END get_cursor;
 10  END farm;
 11  /

Package body created.

SQL> DECLARE
  2    refc  farm.l_ref;
  3    l_dummy   VARCHAR2(1);
  4  BEGIN
  5    refc := farm.get_cursor;
  6    FETCH refc INTO l_dummy;
  7    CLOSE refc;
  8    DBMS_OUTPUT.PUT_LINE(l_dummy);
  9  END;
 10  /
X

PL/SQL procedure successfully completed.

Regards

          Followup:

absolutely -- that works but... you don't want to build a system that way as you
pointed out.

October 10, 2003 v -- Thanks for the question regarding "SQL vs.
      PL/SQL (Cursors)", version 8.1.7

      You Asked
Hi Tom,

We have a task at hand and that is to extract data from some tables in a flat
file delimited. Me and my colleagues were having a discussion on using select
statements and spooling the data into the flat file vs. using cursors and
looping through the records and writing into the file using UTL_FILE package.

There is no modification of data happening from selecting to writing into the
flat file.

Can you throw some light as to which approach is better and few points
explaining the same?

Thanks
vj.

      and we said...
go here

http://asktom.oracle.com/~tkyte/flat/index.html
there you will find out of the box:

plsql
sqlplus
pro*c

solutions for this.  download them and benchmark them in your environment.  see
which does the job

o adequately
o in the best fashion that fits in with your environment

for example, suppose sqlplus was faster BUT the goal was to do this unload at
3am to a file on the server?  well, then utl_file + dbms_job would be the
answer.

Review &
      Followup

      Rating: 3
SQL vs. PL/SQL
      (Cursors)  January 18, 2003
Reviewer:  vj
       from CA,USA
Our O.S is Unix , so we can write some shell script to call the sql file with
the spool and selects in it. The shell script can be scheduled to run at 3 a.m
or whatever time it has to run.
Our obvious choice would be to save time even if we have to go through the above
described path.

Your thoughts?

          Followup:

My obvious choice (differs from your obvious choice) would be to use plsql +
utl_file if it wrote the data "fast enough".

I prefer to use dbms_job over cron.

I have a single place to look in the morning to see if all is "well".
The jobs only run if the db was in fact running.
It is portable across all unixes and all windoze and even mainframes.
it is in keeping with my "do it in the db if I can" philosophy..

but if the need for speed is there -- pro*c is going to blow away either of the
above.

      Rating:
      3
  January 18, 2003
Reviewer:
      A reader
Spooling the output of an SQL file would be the easiest and most natural
solution here. And surely, it will be faster than the pl/sql+utl_file solution.
Pro*C may be the fastest, but it may not be worth if you are not doing large
volumes repetedly (You will spend lot of time to compile and make it work
right!).

Not to criticize you Tom, but I was surprised at your answer to this one. This
might be in tune with your 'do it all in the database' philosophy, but it is
against your own 'do it in sql if you can' and 'keep it simple' philosophies.
they said they are not doing any processing - they just want a plain dump on a
flat file. The easy, simple and straightforward approach to me is to spool to a
file and schedule it using cron. I don't understand your reasoning for using
dbms_job over cron at all. Cron is a powerful and easy tool and it is most
appropriate for the task in hand.

          Followup:

easiest?   most natural?  i disagree. (and how is it against the do it in sql??
or keep it simple??)

the pro*c will compile in nanoseconds and (hey I wrote it myself) work out of
the box.

I gave them ALL OF THE CODE FOR ALL THREE SOLUTIONS.  So, the choice of what to
implement it in does not rely on how hard it would be to code pro*c, or how
"natural" it would be in sqlplus.

doing it in plsql is as easy as proc as easy as sqlplus from a simple "run it"
perspective.  that entire discussion is moot in this case.

Now, looking at the long term, when talking about the solution:

o is it portable
o is it easy to manage
o is it the best solution to my problem

I came to a different conclusion then you.  Sure, if I had NOTHING to start with
(no choices), sqlplus from a development perspective would be the path of least
resistance (maybe, depends if I knew the magic incarnation of sqlplus'ism to get
a flat file without headers, footers, feeback, prompts, inital blank line, etc).

Given that I have off the shelf solutions for all three -- EACH of which is as
easy other the other to "run from the command line", I am free to think about
the rest of it.

I find plus to be about 2/3x the speed of utl_file (on a 1.8 million row result
set)...

I find pro*c to be about 4/5 x the speed of plus...

I would still

o use plsql if it were "faster then fast enough"
o pro*c if it weren't (cause speed must be the issue then)

      Rating: 5
Follow Up - A
      Reader  January 19, 2003
Reviewer:  Jim

I understand your surprise at Tom's answer
but I'm with Tom on this one.

Tom has already posted a response
however I disagree with you about cron being
easy. The first problem is access..
I have contracted for 7 years as a DBA and never
had direct access to cron. I have always had to
contact sysadmin and many times this caused delays
for a cron tab entry that I requested to get actioned.

As the DBA I have direct control over DBMS_JOB.

      Rating: 3
Building Oracle
      Reports from result set in CRON.  October 10, 2003

Reviewer:  Tommy  from Canada
Is it possible and if there's any example in getting a resultset from a sql call
(sqlplus) and building the request for an oracle report in a cron job?

          Followup:

what does cron have to do with anything?

cron just runs a job at a time, as if you typed it on the command line.

So, anything you can type on the command line, you can cron.  cron isn't
relevant here.

What is the question then?  Not sure what you are asking to do?

      Rating: 3
Building Oracle
      Reports from result set in CRON  October 10, 2003

Reviewer:  Tommy  from Canada
Basically, I am building up the Oracle Report command from the data in a SQL
table, then schedule it to run in cron.

          Followup:

then just set up a shell script that sets the environment (oracle home, sid,
path, etc) and make it so the script runs from the command line.

then schedule script

October 10, 2003 Kari -- Thanks for the question regarding
      "returning a series of values based on a start and end value using SQL",
      version 9.0.2

      You Asked
Hi,

is it possible to generate the following using sql: given e.g. two dates, sql
would return one date for each month.

So, given 1-1-2000 and 31-12-2000 the query would return

1-1-2000
1-2-2000
1-3-2000
....
1-11-2000
1-12-2000

Using numbers, the same could be : given 1 and 10 the query would return
1,2,3,4,5,6,7,8,9,10.

TIA,
Kari

      and we said...
ops$tkyte@ORA920> select add_months( to_date(:a,'dd-mm-yyyy'), rownum-1)
  2    from all_objects
  3   where rownum <=
  4   months_between(to_date(:b,'dd-mm-yyyy'),to_date(:a,'dd-mm-yyyy'))+1
  5  /

ADD_MONTH
---------
01-JAN-00
01-FEB-00
01-MAR-00
01-APR-00
01-MAY-00
01-JUN-00
01-JUL-00
01-AUG-00
01-SEP-00
01-OCT-00
01-NOV-00
01-DEC-00

12 rows selected.

ops$tkyte@ORA920>

Review &
      Followup

      Rating: 5
Pipelined
      Function  October 06, 2003
Reviewer:
      A reader
You can try Pipelined Function in Oracle 9i. It's very convenient.

      Rating: 2
Generate week
      days  October 08, 2003
Reviewer:  Anil Pant
       from Hyderabad, India
If I wish to generate all the week days (mon to fri) one way is to by querying
user_objects. Some like this

select to_char(to_date('01-jan-2003') + rownum,'day') , to_date('01-jan-2003') +
rownum from user_objects
where rownum <= 365
and to_char(to_date('01-jan-2003') + rownum,'day') not in ('sunday', 'saturday')

Is there any other way to get the result without using user_objects or any
intermediate table ?

          Followup:

search this site for

pipelined

and see how you can write a pipelined function to do this.  You'll query a plsql
function as if it were a table

However, if you want to do it "right" (eg: easy, fast enough, simple), you'll
use all_objects or some related table.

      Rating:
      3
  October 09, 2003
Reviewer:  Mikito
      Harakiri
But all_objects is not a table, it's a obscenely complicated view!

Pipelined function or parametrized view is the clean way to solve this.

          Followup:

hey mikitto -- so, define and demonstrate for us this "parametrized view"
concept?

(i do believe I said "some related table", all_objects works for many things
just dandy)

      Rating: 3
Alternative to
      all_objects  October 09, 2003
Reviewer:
      Gary  from Sydney, Australia
In a simple test in Oracle9i, using a type constructor seems to work a lot
quicker than using all_objects.
It also looks 'tidier' to me. [I don't want anything from all_objects, so I
don't want to reference it]

create type year IS varray(366) of number(3) not null;

select sysdate+rownum
from
 all_objects

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1    300.00    1953.13          0          0          0           0
Execute      1      0.00       3.76          0          0          0           0
Fetch      381  19800.00   18687.22          0     117861          0        5699
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      383  20100.00   20644.11          0     117861          0        5699

select sysdate+rownum
from
table(year(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))
a,

table(year(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))
b,
                        table(year(1,2,3,4,5,6,7,8,9)) c

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1    100.00      54.76          0          0          0           0
Execute      1      0.00       1.21          0          0          0           0
Fetch      376    200.00     446.62          0          0          0        5625
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      378    300.00     502.59          0          0          0        5625

          Followup:

gosh -- I hope you are using 8i tkprof on 9i traces :)  else those runtimes are
horrible.

but concurr -- that'll perform better.

      Rating:
      3
  October 09, 2003
Reviewer:  Mikito
      Harakiri
Parametrized view is a synomym for table function.

As far as the performance concerned this is not even a competition:

SQL> CREATE TYPE IntSet AS TABLE OF Integer;
  2  /

SQL> CREATE or replace FUNCTION UNSAFE
  2    RETURN  IntSet PIPELINED IS
  3  BEGIN
  4      loop
  5         PIPE ROW(1);
  6      end loop;
  7  END;
  8  /

Function created.

SQL> set autotrace traceonly;
SQL> set timing on;
SQL> select count(1) from table(unsafe)
where rownum < 1000000;

  COUNT(1)
----------
    999999

Elapsed: 00:00:01.08

         95  consistent gets
          2  physical reads

SQL> select count(1) from all_objects where rownum < 1000000;

  COUNT(1)
----------
    322153

Elapsed: 00:03:21.02

     608806  consistent gets
       7645  physical reads

2 problems with the actual view:
1. It is unable to give me the full list of 1000000 numbers, as there are only
322K objects in the database.
2. You can't possibly beat 95 buffer gets for table fiunction.

If there were no sorting involved, that is if I just wanted the list of million
numbers back, the difference would be even more dramatic.

          Followup:

parameterize view might be YOUR synonym for that but I've never heard anyone
else refer to it that way. To me a parameterize view is:

create or replace view V
as
select ....
where c1 = sys_context( 'my_ctx', 'c1' )
  and c2 like sys_context( 'my_ctx', 'c2' )

then you use dbms_session.set_context to set the parameters to the view and
select from it.

In any case, you'll find the approach one up from here to be many times more
efficient then piping for doing this.

And remember -- we are hardly ever (like I mean never) using this techinque for
thousands of records.  Look at the original question and don't lose sight of
that.  People are asking for dozens of rows -- not millions, not hundreds of
thousands, in these cases.

      Rating:
      4
  October 10, 2003
Reviewer:  Mikito
      Harakiri
They are the synomyms. When googling "parameterized view table function" I see
that the context is mostly SQL Server.

Of course you realize how goofy passing parameters to a view through the context
is. Table function allows embedding the view and pass parameters into the view
definition as host variables.

From engineer perspective there is a difference between 10 and 10000. From
matematician's there isn't.

          Followup:

hmm, goofy.

I'll show you how goofy it is when it makes a view run 50,000 times faster then
without.  its not goofy, its very very useful at times.  sometimes you need a
predicate pushed down into the view to get the right semantics and performance.

you can call it what you want.

      Rating: 4
Totally agree
      with Tom  October 10, 2003
Reviewer:  gs

We have a few views doing excelent using this technique. They could also be used
as, sort of VPD, in the SE.

October 10, 2003 Prince -- Thanks for the question regarding
      "Delete Cascade if foreign keys exists with different delete rule",
      version 8.1.7.2

      You Asked
Tom,

Q1:
----
Suppose I have tables t1, t2, t3 and having the relation t1 -> t2 -> t3, where
t1 is the root level parent and t3 is the child.

Suppose, if there exists a relation between t1 and t2 with delete cascade,t2 and
t3 with delete cascade and t1 and t3 with delete restrict.

t1 --> t2 --> t3   (say delete cascade)
t1 --> t3          (say delete restrict)

Now, how does oracle determines, whether to delete the records from "t3" or not?

If it chooses the path of t1 --> t2, t2 --> t3 then, it deletes the record and
t1 --> t3 may find no record to delete.
On the other hand, if it chooses t1 -- t3, it fails with "child record found".
Now my question is what strategy oracle uses to choose the delete path.

Q2:
----
is it possible to UNION a LONG and VARCHAR2 column? If so how?

Thanks,

      and we said...
q1) it would appear we cascade the delete and then check:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t1 ( x int primary key );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t2 ( y int primary key, x
references t1 on delete cascade );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t3 ( y references t2 on delete
cascade, x references t1 );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t1 values ( 1 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t2 values ( 1 , 1 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t3 values ( 1 , 1 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from t1;

1 row deleted.

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

(weird model if you ask me)...

q2) no, especially with a UNION since that implies a sort/distinct at the end
and longs cannot be sorted nor distincted...

Review &
      Followup

      Rating: 3
I agree with u
      it being a wierd model. I was just trying that.  March 21,
      2002
Reviewer:  Prince
Thanks Tom, for the prompt answer.

I know its a weird model. I just had a thought and was experimenting it. But, I
couldn't confirm whether it always uses a certain path are it chooses different
path based on the optimizer's path selection.

And, regarding the Q2,
If that is the case shouldn't "UNION ALL" work. It seems the union is not
allowed as the data types are incompatible. Can a long be converted to varchar2
easily?

          Followup:

No, a long cannot be converted to a varchar2 "easily".   See

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:55212348054
for a method you can use to return the first 4000 characters of a long as long
as the overall long is <= 32k in length using a PLSQL function.

      Rating: 5
Oracle 9.2 HR
      demo schema  May 05, 2003
Reviewer:  pingu

Hi

I am working with Oracle 9.2 Human Resurces demo schema, I was trying to delete
some records in Employees and it complained about

ORA-02292: integrity constraint (HR.DEPT_MGR_FK) violated - child record found

So, I went to departments table and tried to delete the child records, but
obviously departments is also employees parent table!!!! So when I issue a
delete statement I got

ORA-02292: integrity constraint (HR.EMP_DEPT_FK) violated - child record found

How can we delete in such situations where two tables are child and parent of
each other....?

          Followup:

update one of the fkeys to null -- now you can delete the other parent.

      Rating: 4
just
      wondering  May 05, 2003
Reviewer:  PINGU

Hi

I am just wondering because I am not very efficient in RDBMS theory. Is this
kind of model quite common? Tables are parent and child of each other

Thx

          Followup:

common? no, not very common but definitely they happen and they exist.

consider EMP:

create table emp ( empno number primary key,
                   ...
                   mgr   references emp
                 );

there the single table is both parent and child.

      Rating:
      3
  May 06, 2003
Reviewer:
      A reader
I would consider DEFERRED constraints to be a good choice in such a case. are
there any reasons NOT to use them HERE?

          Followup:

i don't think they are necessary unless you want to do an update cascade
operation (which means you have an ill designed model in the first place as you
are updating a primary key -- meaning it is not really a primary key at all).

no reason 'not to', but then again no reason 'to'

      Rating: 5
on delete
      cascade  October 10, 2003
Reviewer:  Junnior
       from VA, USA
Tom:

Why people not use "on delete cascade" so often?
What is the disadvetage to use it?
What is the syntax to modify the exist tables to be on delete cascade?

Thanks.

          Followup:

I don't like it personally -- I'd rather have an error when I delete a parent
that has child records.  Doesn't "seem" right to have that stuff just disappear.

It is useful in limited cases.

You can only have on delete cascade when creating the constraint (so you drop
and recreate the constraint)

      Rating: 5
It's all about
      business logic  October 10, 2003
Reviewer:
      Michael
> Why people not use "on delete cascade" so often?
> What is the disadvetage to use it?

We use "on delete cascade", where the desired behaviour is to eliminate the
connected data and don't use it, where we want to avoid the cascading deletes of
the data.

Lets say you have a customer table and a referencing address table. If you
theoretically would delete the customer, the address isn't needed anymore, so we
can "on delete cascade" the address. But if this customers has made a purchase
and we have a bill for him, we can't delete him anymore, so we can't use "on
delete cascade" for the referencing purchases.

October 10, 2003 Duncan -- Thanks for the question regarding
      "jdbc/plsql tables and also preparedStatements", version 8.1.7.4

      You Asked
Tom

Great website and great book.
i have two questions for you:

Question 1

We have recently been looking into the possibility of passing plsql tables to
and from stored procedures.  There are some confusing posts on the internet
about this issue.  Please can you clarify this once and for all.  We have an
Apache Tomcat linux server talking to an 8.1.7.4 database using (I believe) JDBC
driver 1.2 (classes 12).
Now am I right in saying that:

a)   The Oracle JDBC OCI driver for 8.1.7 databases DOES support PL/SQL index-by
tables of scalar datatypes.  It seems that OraclePreparedStatement and
OracleCallableStatement classes define the additional methods needed to handle
these datatypes??

b ) Also only PLSQL can send tables of RECORDS to and from itself. Nothing else
is able to do this.  This can only be achieved using collection of object types,
is that correct?

So if I have a pl/sql table declared in a procedure which is based upon eg a 5
field table with say 10 rows then this CANNOT as it stands be passed to jdbc?  I
should instead load the data into an object type which mirrors this pl/sql
index-by integer table??

Question 2

We are trying to migrate the java code to use preparedStatements to take
advantage of bind variables.  The problem is that the java class which handles
the database connection and query submission is also used to connect to
SQLServer databases and MYSQL databases. We have found that only some drivers
don't support the use of preparedStatements do you know if there is a definitive
list of drivers to use or indeed any other issues we may come across when moving
to preparedStatements??

Many thanks

      and we said...
a) correct

b) correct -- you would have to use COLLECTIONS of OBJECTS (sql types, not plsql
types) to pass "arrays of records" to and fro from plsql to java to plsql

All drivers should support prepared statements -- they are MANDATORY, statement,
preparedStatement, callableStatement -- if they do not support these, they are
NOT jdbc drivers.

All of those databases support this.  As for drivers, no i don't have a list as
I work with -- well, Oracle ;)

but it is funny cause the support for "plsql tables" isn't in those other
databases either?

October 11, 2003 Alberto -- Thanks for the question regarding
      "freelists higher than 'sessions concurrently inserting'", version
      8.1.7/9.0.1

      You Asked
Hi Tom,

I have investigated the freelist machinery recently, and have found some results
that you may find interesting, and I would be happy if you could comment on it.
I know you're very busy at the present time - so pretend this question was
submitted anytime in the future weeks ...

1) After having read Metalink notes 157250.1 (very well written) and 1029850.6
(very detailed), i noticed that the association between a process/session and
the (process)freelist it uses while inserting is static, and it is

freelist# = 1 + (V$PROCESS.PID % FREELISTS)

So sessions with the same value for (V$PROCESS.PID % FREELISTS) will contend on
the same freelist.

I have verified it (in 9.0.1) by running a test case, similar to your one on
page 200 of "Expert one-on-one", and setting FREELISTS=2, using two sessions
with the same value for (v$PROCESS.PID % 2); i got the same results (same values
for the wait times in v$waitstat) that i get when I set FREELISTS=1.

Of course, if the values for (v$PROCESS.PID % 2) are different, I get zero wait
times.

2) So, if we have 2 sessions concurrently inserting, it is not enough to set
FREELISTS = 2 to eradicate the contention; the probability that the 2 sessions
will contend is 1/FREELISTS, so a surprising 50% for FREELIST=2. Much better
then the 100% for FREELISTS=1, but well above the near-zero I expected.

If we e.g. set a goal of a 10% contention probability, we would have to set
FREELISTS=10 - a value much higher than I expected indeed.

3) Now, from the same Metalink notes, I understand that the blocks are
transferred on the (process) freelists (from the master freelist, the
transaction freelists, or from the new space got by advancing the High Water
Mark) in batch of 5 (max), so we can expect to have statistically an average of
2.5 blocks "wasted" on each freelist if FREELISTS > 1.

So, let's call it a 2.5 * FREELISTS "space price".

In tablespace space "wasted" - it will never be a problem in these days of cheap
disks.

IMHO the only real problem may arise for the blocks that come from bumping the
hwm, since the performance of full table/index scan would be negatively
affected; for the blocks coming from deletion/updates, the space is not wasted
at all (whatever the setting of FREELISTS, they are there - just distributed
differently).

4) Conclusion of 1+2+3:

It is a good idea to consider first thing a value much higher the just the
'number of sessions concurrently inserting', since it will greatly reduce the
contention, and it seems to be quite cheap given the math in (3). As always,
this does not mean that one has to forget about the drawbacks (eg the longer
full scans) and be lazy on the analysis ...

5) Now, all these ruminations of mine where motivated by a table of ours that

- it's big (110,000 blocks of 8k size);
- gets an average of 6,000,000 inserts/day (and we have big peak hours);
- it is always accessed through the pk (never full scanned) [regular OLTP];
- we can't partition (it belongs to a third-party product);
- had been installed by the third-party company with FREELISTS=1;
- wait time is a very bad thing since the users are impatient web users that
don't like to wait.

Needless to mention, we got 'buffer busy waits' (20 min/hour total), contention
on 'data block' and 'undo header'.

For a table like this, i would say that only the sky is the limit for FREELISTS
... say we set FREELISTS=20; the space wasted would be about

20 * 2.5 / 110,000 = 0.05%

and i don't see any problem even if all the additional space come straight from
bumping the hwm (we never full scan, and the scan would be only 0.05% longer
even if we did).

Would you buy it ?

6) I know the existence of ASSM, and for learning purposes I will investigate it
soon - but unfortunately all our databases (including the one hosting the table
in (5)) are 8.1.7.4 and are going to be for a long long time ...

Many thanks in advance
Alberto

      and we said...
then yes, you can use many freelists to help spread it out at the expense of
"space utilization" to a degree.

freelist GROUPS are a bottleneck as well -- you might add more of then, even in
a single instance.  it'll tend to use more space but is more highly concurrent.

anytime two processes get onto the same freelist, you'll have this sort of
concurrent access contention -- it is not possible to remove it 100% but we can
spread it out with lots of freelists and freelist groups.

In my new book "effective oracle by design", i go into this in more detail.
Here is short blurb explaining why freelist groups might be useful here:

(3)Multiple Freelist Groups

A freelist group is a collection of freelists. You may choose to create a table
with two freelist groups of five freelists each. This is typically used in a
Real Application Clusters (RAC) or Oracle Parallel Server (OPS) environment in
order to give each instance its own set of freelists to manage and avoid
cross-instance contention.

Freelist groups also can be useful for increasing concurrency in single-instance
configurations. The reason for this has to do with the way free space is managed
when there are multiple freelists. When a session modifies a block so that the
block will be placed onto a freelist, it goes onto a master freelist for the
segment. As space is needed, the blocks are moved in small amounts from the
master freelist onto the individual freelists for the segment called process
free lists. This master freelist itself can be a point of contention if sessions
are consuming free blocks at a high rate of speed. (called process free lists)
Now, if you have multiple freelist groups, you'll spread the points of
contention out over many master freelists.

As an analogy, suppose you have a water cooler and a room full of thirsty
people. The water cooler represents the single freelist, and the people in the
room represent sessions that want to insert into a table. With a single water
cooler from which to dispense water (free space), everyone will need to get in
line and wait for the people in front of them to get their water. That is, there
will be a line, or contention, for this single resource, as illustrated in
Figure 4-1.

So, you add more water coolers to the room, say ten of them. Now, people can get
in one of ten lines in order to get a drink, so things go much faster. This is
analogous to having multiple freelists. But there is still a bottleneck in this
system. As the water coolers are used, they run out of water and must be
refilled. At this point, you have only one person capable of refilling the water
coolers. This single person represents your single freelist group. Now, this one
person cannot refill more than one water cooler at a time, so whenever more than
one watercooler needs refilling, people need to wait. This is where the multiple
freelist groups come into play. You could create the segment with ten freelists
in two freelist groups, as illustrated in Figure 4-2. Now, you have two people
who can refill any one of their five assigned water coolers. Those thirsty
people get their drinks faster than ever before.

One thing to consider with freelist groups, however, is that space usage can be
a real concern here. With a single freelist group and multiple freelists, there
can be some small amount of wasted space, but with freelist groups, the amount
of wasted space may appear to be huge, depending on how the table is used.

Consider a table where one process is continually inserting into the table and
another process is continually processing and deleting rows from that same table
(sort of like a queue). It is very easy to get into a situation where the table
appears to grow and never reuse space. This is because the process that is
inserting data may pull blocks from one freelist group, whereas the other
process that is deleting and freeing space places these blocks onto a separate
freelist group. Hence, the session freeing space will never make a block
available for the session requesting space. This is generally an atypical
situation in regular single-instance Oracle, since it can happen with only truly
long-running processes that get assigned to separate freelists in different
freelist groups. Normally, if the processes come and go, they will tend to
balance out over time.

In an environment with multiple Oracle instances, such as you find with OPS or
RAC, the amount of wasted space associated with multiple freelist groups can be
a problem. If a process inserts into a table on one instance and another
instance is responsible for processing the row and deleting it, you may find
that table grows without bounds. This is because the freelist groups will be
assigned to each instance, and the space freed by instance A will not be usable
by instance B. We'll discuss in a moment how ASSM can alleviate this issue in an
RAC environment.
.....
Review
      & Followup

      Rating: 5
max five blocks
      per freelist ?  September 06, 2003
Reviewer:
      Alberto Dell'Era  from Milan, Italy
So I have just to wait for your new book to arrive (it's flying over the
Atlantic Ocean now) to know how the story develops into the next episode, "ASSM"
;-).

Just a clarification: is it accurate to say that the space wasted on each
freelist (whether in multiple groups or not), is at most 5 blocks, so 2.5 on
average ? That's to quantify the adjective "small" in your excerpt ...

Many thanks again.
Alberto

          Followup:

it should stay at 5.  we can sort of see this in this simulation.  My goal with
the loop was to find out how many blocks would 80% fill up a block (so I can
delete a row from each of 4 or 5 separate blocks).  I wanted to fill the table
up.  manually put 5 blocks onto the free list and then insert a row that would
not fit nicely on any of the freelist blocks -- causing us to extend the HWM.
but -- those blocks are not on the freelist.  consider (dictionary managed
tablespace in 8i for this example)

ops$tkyte@ORA817DEV> create table t ( x int, y varchar2(4000) ) storage( initial
1k next 1k pctincrease 0) pctfree 20 pctused 79;

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> declare
  2          l_last_cnt number := 0;
  3  begin
  4          for i in 1 .. 453
  5          loop
  6                  insert into t values ( i, rpad('*',200,'*') );
  7                  commit;
  8                  for x in ( select count(distinct
dbms_rowid.rowid_block_number(rowid)) cnt from t )
  9                  loop
 10                          if ( l_last_cnt <> x.cnt )
 11                          then
 12                                  dbms_output.put_line( i || ' ' || x.cnt );
 13                                  l_last_cnt := x.cnt;
 14                          end if;
 15                  end loop;
 16          end loop;
 17  end;
 18  /

this shows us the threshold at which the rows cause us to goto the next block.
 about 31 rows/block.  If we delete rows 1, 32, 63, 94 -- we know we deleted a
row on block 1, 2, 3, 4...

1 1
32 2
63 3
94 4
124 5
154 6
184 7
214 8
244 9
274 10
304 11
334 12
364 13
394 14
424 15

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> commit;

Commit complete.

ops$tkyte@ORA817DEV> exec show_space( 'T' )
Free Blocks.............................1
Total Blocks............................64
Total Bytes.............................524288
Unused Blocks...........................48
Unused Bytes............................393216
Last Used Ext FileId....................7
Last Used Ext BlockId...................192905
Last Used Block.........................16

PL/SQL procedure successfully completed.

so, one block on the freelist  -- 48 UNUSED blocks above the HWM...

ops$tkyte@ORA817DEV> delete from t where x in ( 1 ,32, 63 ,94 );

4 rows deleted.

ops$tkyte@ORA817DEV> commit;

Commit complete.

ops$tkyte@ORA817DEV> exec show_space( 'T' )
Free Blocks.............................5
Total Blocks............................64
Total Bytes.............................524288
Unused Blocks...........................48
Unused Bytes............................393216
Last Used Ext FileId....................7
Last Used Ext BlockId...................192905
Last Used Block.........................16

PL/SQL procedure successfully completed.

now, we just added blocks 1, 2, 3, 4 of the table onto the freelist here.
they dropped down in utilization and can be reused -- but, when we try to put a
row onto them that would take them back OVER their pctfree -- we see:

ops$tkyte@ORA817DEV> insert into t values ( 1000, rpad('*',400,'*') );

1 row created.

ops$tkyte@ORA817DEV> commit;

Commit complete.

ops$tkyte@ORA817DEV> exec show_space( 'T' )
Free Blocks.............................5
Total Blocks............................64
Total Bytes.............................524288
Unused Blocks...........................43
Unused Bytes............................352256
Last Used Ext FileId....................7
Last Used Ext BlockId...................192905
Last Used Block.........................21

PL/SQL procedure successfully completed.

still 5 blocks ON the freelist -- but now there are only 43 UNUSED blocks.
Where did the other 4 go??  they are on that master freelist -- and one of them
contains the new row...

      Rating: 5
measuring free
      blocks while concurrently inserting  September 06, 2003

Reviewer:  Alberto Dell'Era  from Milano, Italy

Oh yes! I should have thought myself to use dbms_space to verify the number of
free blocks ...

And so, I have expanded your test case on page 200 of "expert-one-on-one"
integrating it with your latest followup, by setting up the following test case:

create table spread (x int)
  storage (freelists ... initial 100k next 100k pctincrease 0);

create or replace procedure insert_rows (p_delta number)
is
  l_free_blks     number;
  l_max_free_blks number := 0;
  l_freelists     number;
begin
  select freelists
    into l_freelists
    from user_tables
   where table_name = 'SPREAD';

  for i in 1..100000 loop

    insert into spread values (i + p_delta);
    commit;

    dbms_space.free_blocks (
      segment_owner     => user,
      segment_name      => 'SPREAD',
      segment_type      => 'TABLE',
      freelist_group_id => 0,
      free_blks         => l_free_blks );

    if (l_free_blks > l_max_free_blks) then
      l_max_free_blks := l_free_blks;
    end if;

  end loop;

  dbms_output.put_line ('max free blocks = ' || l_max_free_blks ||
                        ', free blocks/freelist = ' || l_max_free_blks /
l_freelists);
end;
/

I have concurrently run insert_rows in two sessions (in 9i, should be the same
in 8i). Results:

FREELISTS=1
  max free blocks = 5, free blocks/freelist = 5

FREELISTS=2 (this case is point (1) of the original question)
  1) using two sessions using different freelists
     (i.e. different V$PROCESS.PID % FREELISTS)
       max free blocks = 10, free blocks/freelist = 5

  2) using two sessions colliding on the same freelist
     (i.e. same V$PROCESS.PID % FREELISTS)
       max free blocks = 5 , free blocks/freelist = 2.5

So, we have the final answer - the number of free blocks per freelist never goes
above 5.

That was pure fun - thanks!
Alberto

      Rating: 5
5 block
      boundary ...  October 10, 2003
Reviewer:
      Shankar  from Houston, TX
Tom, is it a fair statement to say that oracle moves the HWM by 5 blocks at a
time if there is no free block that was found below HWM for inserts? Thanks.

          Followup:

using a non-ASSM tablespace, yes.

October 11, 2003 David -- Thanks for the question regarding "What
      happens in core database when I make one hot backup?", version 9.2.0

      You Asked
When I make a hot backup (sometimes I use rman) via alter tablespace xxx begin
backup, what happens in this tablespace?. Other dba's say me that datafile is
locked, but I don't believe it, I've tested this behavior and I think that
Oracle not lock datafile. I've inserted, deleted, updated tables that are inside
of backup tablespace and all is correct, I always have can do this operation. I
don't unserstand where oracle keeps these dirty blocks if the datafile is
locked. Is the DBWR writing this blocks in the datafile?.

Thanks in advance
Atentament
David Pujol

      and we said...
well, when you use RMAN, you don't even "alter tablespace begin backup", you
just backup. RMAN doesn't need the extra "begin backup" processing.

Files -- all of them - are CONSTANTLY written to during a hot backup (else it
would not be "hot", it would be "cold")

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:271815712711

October 11, 2003 jignesh -- Thanks for the question regarding
      "Avoiding redo while Deleting", version 8.1.7

      You Asked
Hi Tom,

We can avoid genaration of redo while inserting using /*+ append */ hint.  Is
there any way we can avoid generation of redo while deleting? ? ?

if we do
alter table abc nologging ...
delete from abc
alter table abc logging ...
will it do?

changing logging and nologging will help in normal dmls or it is useful while
sqlloader direct path load only?

Thanks in advance

Jignesh

      and we said...
no, and there isn't a chance in the world you would want to.

why?  because a simple failure would corrupt your data -- poof, gone.  Not much
use.

We can avoid logging on a direct path insert since we load ABOVE the high water
mark.

Below the high water mark -- you are modifying a complex, sophisticated data
structure that needs protection.

If you are deleting MOST of the rows -- and hence are concerned about redo --
you want to explore other bulk ways to do it

a) use partitions with sliding windows so you are able to easily "age" data if
that is your goal.  you can drop old partitions very fast.

b) use CREATE TABLE TEMP as select data to keep -- index, grant on that.  that
can be done 100% unrecoverable and even in parallel.  Then drop old table,
rename new table

are just two ideas.

NOLOGGING works on BULK operations only (alter table move, direct path loads,
create's etc)
Review & Followup

      Rating: 4
Deleting
      without generating of redo logs  October 11, 2003

Reviewer:  Hussain Kena  from Bahrain
I think that Jignesh has the following situation: a table contains thousands of
records, So when he delete the data, this will generate alot of redo records.
So, his solution is first: to change the structure of the table to be nologging,
then: deleting records form the table, and finaly: return the structure of the
table back to be in the logging mode.

So, I think his solution is the right, but with replacing of "DELETE FROM abc"
with "TRUNCATE TABLE abc", because this will reset the water mark level.

Another solution, is to make that table a temporary table. Temporary tables are
by default in nologging mode.

Thanks.

          Followup:

there would be no need to set the table nologging for a truncate, truncates do
not log.

truncate would be appropriate if and only if the table was to be totally
emptied. it would be applicable in that case.

temporary tables generate redo -- the undo generated by a temporary table
operation is logged.  their most salient features are:

a) they empty themselves
b) there are no concurrency issues with them (eg: they can have unique indexes
and you can insert the same unique values I insert without collisions)

      Rating: 3
Clear with
      NOLOGGING option  October 12, 2003
Reviewer:
      jignesh karia  from india

October 11, 2003 Gayathri -- Thanks for the question regarding
      "Error: In Creating the Trigger ", version 8.0

      You Asked
I need to create a trigger for the following..

I have a table with the following fields

tablename - acty
---------
entrydate date,
intime    date,
outtime   date,
activity  varchar2(10),
shft      varchar2(10),

While i was inserting a row into the table,i need to check the system time. if
the system time is between 12.00 am and 5.00 am then the entry date should have
the previous date and shft
should be 's3'.

for eg. if i enter data at 11/10/2003 12:01 am,
the entry date must have the previous date and shft='s3'.
i.e.,
 insert into acty(entrydate,intime,outtime,activity,shft)
 values('11/10/2003','11/10/2003 12:00:00 am','','test')

The result i should get is

entrydate    intime            outtime    activity    shft
---------    ------            -------    --------    ----
10/10/2003    11/10/2003 12:00:00 am     {null}  test        S3

I created the trigger as:

SQL> CREATE OR REPLACE TRIGGER acty_bi
  2  BEFORE INSERT ON TIMESHEET_DATA
  3  DECLARE
  4  l_hr_num NUMBER(4);
  5  BEGIN
  6  SELECT TO_NUMBER(TO_CHAR(sysdate,'hh24mi'))
  7  INTO l_hr_num
  8  FROM dual;
  9    IF l_hr_num >= 0 and l_hr_num < 500 THEN
 10        :new.act_date := :new.entry_date - 1;
 11        :new.shift = 'S3';
 12     END IF;
 13  END;
 14  /
CREATE OR REPLACE TRIGGER acty_bi
                          *
ERROR at line 1:
ORA-04082: NEW or OLD references not allowed in table level triggers

But error is coming like this.. can u suggest me the solution..

Thanks
gaya3

      and we said...
it would be

create or replace trigger acty_bi
before insert on timesheet_data FOR EACH ROW
begin
  if ( to_number( to_char( sysdate,'hh24mi') ) between 0 and 500 )
  then
     :new.act_date := :new.entry_date - 1;
     :new.shift    := 'S3';
  end if;
end;
/

never select something from dual unless you have to!! (eg: you want to use
decode and decode cannot be called right from plsql)

Review &
      Followup

      Rating: 4
Purpose of "FOR
      EACH ROW"  October 11, 2003
Reviewer:  Hussain
      Kena  from Bahrain
Tom,

Don't you think that "FOR EACH ROW" is a default clause?

and, what is the effect of this clause?

Thanks

          Followup:

umm, no

there are 4 firing times:

a) before the statement does anything
b) before each row is "really" changed (you can change the row here)
c) after each row is "really" changed (you cannot change the row anymore)
d) after the statement finished all work

there are NO defaults -- you have to tell us:

before
before for each row
after for each row
after

      Rating: 5
Very very
      clear  December 23, 2003
Reviewer:  Sanjaya
      Balasuriya  from Colombo, Sri Lanka
Thanks Tom,

I got the exact answer for the problem I wa having.

October 11, 2003 Piotr -- Thanks for the question regarding "LOB
      compression", version 9.2.0.1.0

      You Asked
Hello,

I have a table with BLOBs and they are actually character logs generated by fax
transmission software. They are about 4k-16k big and pretty redundant, they
compress with zlib to just about 1k-2k. And I want to store them compressed in
DB. Now question what is better in your opinion?
To use server side compressor lib in on insert triggers for storing (they will
never be updated) and decompressing views? Or maybe you would code
compressor/decompressor logic on the client side.

I wrote compressor/decompressor as Java SP and use first approach thus there
have to be no compressor specific client side code (Perl, Java and C++) but the
drawback is that there is more network traffic than necessary.

Any comments?

Thank you in advance

Regards,
Piotr

      and we said...
I would do the compression in the database -- easier to manage and the amount of
network traffic probably won't be an issue (they trickle in all day long
right...)

In 10g, the package utl_compress will be of keen interest to you to replace all
of the custom code you have.

make sure to enable inline row storage -- else the lobs will be stored on a
block all by itself (eg: a 1k lob stored out of line will consume at least ONE
block all by itself, stored inline, in the row, it'll just take its size)
Review &
      Followup

      Rating: 5
Enabling inline
      row storage  October 11, 2003
Reviewer:  Piotr
      Jarmuz  from Poznan, Poland
I agree. Interesting comments.

Especially this enable inline row storage hint. I will do that. Can I enable it
post factum with alter and/or rebuild or do I have to re-create the table?

Seems like 10g will render obsolete most of my custom Java code in Oracle DB
(compression, regex).

If you want I can post my compression code (Java and PLSQL wrappers) so that
other people might use it in 8i or 9i.

Thanks a lot.

Regards,
Piotr

          Followup:

believe you must rebuild...

if the code is "small", post it.

      Rating: 5
It is
      small...  October 11, 2003
Reviewer:  Piotr
      Jarmuz  from Poznan, Poland
...and self-explanatory.

Java part:

create or replace and compile java source named asco."LobCompressor" as
import java.lang.*;
import oracle.sql.*;
import java.io.*;
import java.util.zip.InflaterInputStream;
import java.util.zip.DeflaterOutputStream;

/**
 * A simple class for LOB compression and decompression in Oracle Database. Will
work in 8i and better.
 *
 * @author <a href="mailto:pjarmuz@poczta.onet.pl">Piotr Jarmuz</a>
 */
public class LobCompressor {
/**
 * Compresses the CLOB into BLOB
 *
 * @param clob the source CLOB (plain text)
 * @param blob the target BLOB (will hold compressed binary data) it should be
an empty BLOB retrieved for example with dbms_lob.createtemporary(l_blob,true);
 * @throws Exception mostly I/O exception if ever
 */
    public static void compress(CLOB clob, BLOB blob) throws Exception {
        InputStream in=clob.getAsciiStream();
        DeflaterOutputStream z=new
DeflaterOutputStream(blob.getBinaryOutputStream());
        byte[] buffer=new byte[clob.getBufferSize()];

        int cnt;
        while ((cnt=in.read(buffer))!=-1) {
            z.write(buffer,0,cnt);
        }
        in.close();
        z.close();
    }

/**
 * Decompresses the BLOB into CLOB
 *
 * @param blob the source BLOB (compressed binary data)
 * @param clob the target CLOB (will hold plain text) it should be an empty CLOB
retrieved for example with dbms_lob.createtemporary(l_clob,true);
 * @throws Exception mostly I/O exception if ever
 */
    public static void decompress(BLOB blob, CLOB clob) throws Exception {
        OutputStream out=clob.getAsciiOutputStream();
        InflaterInputStream z=new InflaterInputStream(blob.getBinaryStream());
        byte[] buffer=new byte[blob.getBufferSize()];

        int cnt;
        while ((cnt=z.read(buffer))!=-1) {
            out.write(buffer,0,cnt);
        }
        z.close();
        out.close();
    }

/**
 * Compresses the BLOB into BLOB
 *
 * @param slob the source BLOB (plain binary data)
 * @param blob the target BLOB (will hold compressed binary data) it should be
an empty BLOB retrieved for example with dbms_lob.createtemporary(l_blob,true);
 * @throws Exception mostly I/O exception if ever
 */
    public static void compress(BLOB slob, BLOB blob) throws Exception {
        InputStream in=slob.getBinaryStream();
        DeflaterOutputStream z=new
DeflaterOutputStream(blob.getBinaryOutputStream());
        byte[] buffer=new byte[slob.getBufferSize()];

        int cnt;
        while ((cnt=in.read(buffer))!=-1) {
            z.write(buffer,0,cnt);
        }
        in.close();
        z.close();
    }

/**
 * Decompresses the BLOB into CLOB
 *
 * @param blob the source BLOB (compressed binary data)
 * @param slob the target CLOB (will hold plain binary data) it should be an
empty CLOB retrieved for example with dbms_lob.createtemporary(l_blob,true);
 * @throws Exception mostly I/O exception if ever
 */
    public static void decompress(BLOB blob, BLOB slob) throws Exception {
        OutputStream out=slob.getBinaryOutputStream();
        InflaterInputStream z=new InflaterInputStream(blob.getBinaryStream());
        byte[] buffer=new byte[blob.getBufferSize()];

        int cnt;
        while ((cnt=z.read(buffer))!=-1) {
            out.write(buffer,0,cnt);
        }
        z.close();
        out.close();
    }
};
/

PL/SQL package specification:

create or replace package compressor is
    function clob_compress(p_clob clob) return blob;
    function clob_decompress(p_blob blob) return clob;
    function blob_compress(p_blob blob) return blob;
    function blob_decompress(p_blob blob) return blob;
end;
/

And PL/SQL package implementation:

create or replace package body compressor is

procedure clob_decompress(p_blob blob, p_clob clob)
as language java
name 'LobCompressor.decompress(oracle.sql.BLOB, oracle.sql.CLOB)';

procedure clob_compress(p_clob clob, p_blob blob)
as language java
name 'LobCompressor.compress(oracle.sql.CLOB, oracle.sql.BLOB)';

procedure blob_decompress(p_slob blob, p_blob blob)
as language java
name 'LobCompressor.decompress(oracle.sql.BLOB, oracle.sql.BLOB)';

procedure blob_compress(p_slob blob, p_blob blob)
as language java
name 'LobCompressor.compress(oracle.sql.BLOB, oracle.sql.BLOB)';

function clob_compress(p_clob clob) return blob is
l_blob blob;
begin
     if p_clob is null then
         return null;
     end if;
     dbms_lob.createtemporary(l_blob,true);
     clob_compress(p_clob,l_blob);
     return l_blob;
end;

function clob_decompress(p_blob blob) return clob is
l_clob clob;
begin
     if p_blob is null then
         return null;
     end if;
     dbms_lob.createtemporary(l_clob,true);
     clob_decompress(p_blob,l_clob);
     return l_clob;
end;

function blob_compress(p_blob blob) return blob is
l_blob blob;
begin
     if p_blob is null then
         return null;
     end if;
     dbms_lob.createtemporary(l_blob,true);
     blob_compress(p_blob,l_blob);
     return l_blob;
end;

function blob_decompress(p_blob blob) return blob is
l_blob blob;
begin
     if p_blob is null then
         return null;
     end if;
     dbms_lob.createtemporary(l_blob,true);
     blob_decompress(p_blob,l_blob);
     return l_blob;
end;

end;
/

I made PL/SQL wrappers functions so that they can be called from SQL layer e.g.
views hiding (de)compression

Regards,
Piotr

October 12, 2003 Mark -- Thanks for the question regarding "Can
      you think of reasons why indexes are ignored when using RAWs?", version
      8.1.7.4

      You Asked
I have table with 8 columns and 14 million rows.  It's my only table.  I have a
few indexes on this table, including one where columns 4 and 5 comprise a
composite unique index.  My basic query is "select * from mytable where col4 =
:val4 and col5 = :val5;" which I think should be perfect fit for the index.
But, no index is ever used - Oracle always does FTS, even if I use hints and
tune init.ora params.  I've employed all suggestions from your books, this site,
and other sources to no avail; indexes are still not used.  TAR is open on
MetaLink.

My basic query (listed above) takes 3.5 seconds, but since the query runs
150,000 times per day I've got a real problem.  145 hours run time!  My hardware
includes quad 900 MHz Xenon CPUs, a 128 MB shared_pool, and an 800 MB
db_buffer_cache.

It seems the problem is col4 has data type RAW.  If I drop/recreate the table to
replace the RAW column with a VARCHAR2 column, then Oracle starts using my
index.

Thoughts?

      and we said...
sounds like you are NOT binding a RAW to the column!  You are binding a varchar2
to the column and hence the query is really:

select * from t where rawtohex(col4) = :v4 and col5 = :v5
                      ^^^^^^^^^^^^^^

Consider:

ops$tkyte@ORA817DEV> create table t ( col4 raw(4), col5 int, data char(20),
constraint t_pk primary key(col4,col5) );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA817DEV> declare
  2      l_num  number := 5;
  3      l_vc   varchar2(8) := 'AABBCCDD';
  4      l_raw  raw(4) := l_vc;
  5  begin
  6      for x in ( select * from t vc_bind where col4 = l_vc and col5 = l_num )
  7      loop null; end loop;
  8
  9      for x in ( select * from t vc_bind_with_cast where col4 =
hextoraw(l_vc) and col5 = l_num )
 10      loop null; end loop;
 11
 12      for x in ( select * from t raw_bind where col4 = l_raw and col5 = l_num
)
 13      loop null; end loop;
 14  end;
 15  /

PL/SQL procedure successfully completed.

Now, TKPROF shows us:

SELECT *   FROM T VC_BIND  WHERE COL4 = :b1  AND COL5 = :b2

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS FULL T

--------------------------------------------------------------------------------

SELECT *   FROM
 T VC_BIND_WITH_CAST  WHERE COL4 = HEXTORAW(:b1)  AND COL5 = :b2

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS BY INDEX ROWID T
      1   INDEX UNIQUE SCAN (object id 57663)

--------------------------------------------------------------------------------

SELECT *   FROM
 T RAW_BIND  WHERE COL4 = :b1  AND COL5 = :b2

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS BY INDEX ROWID T
      1   INDEX UNIQUE SCAN (object id 57663)

So, you either want to use HEXTORAW to avoid the implicit RAWTOHEX that is
happening now (precluding the index)  OR you should bind a RAW OR you can use a
FUNCTION BASED INDEX:

ops$tkyte@ORA817DEV> alter session set query_rewrite_enabled=true;

Session altered.

ops$tkyte@ORA817DEV> create unique index t_idx2 on t ( rawtohex(col4), col5 );

Index created.

ops$tkyte@ORA817DEV> exec dbms_stats.set_table_stats( user, 'T', numrows =>
10000, numblks => 1000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> set autotrace on
ops$tkyte@ORA817DEV> select * from t where col4 = 'AABBCCDD' and col5 = 5;

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=39)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=1 Bytes=39)
   2    1     INDEX (UNIQUE SCAN) OF 'T_IDX2' (UNIQUE) (Cost=1 Card=1)

that mandates the use of the CBO as FBI's are only visible to the CBO

Review &
      Followup

      Rating:
      5
Perfect!  October 14, 2003
Reviewer:
      Mark  from Ashburn, VA
My query's run time is down from 3.5 seconds to 0.5 seconds.  And now that the
basic problem has been oversome I can probably tune it some more.

      Rating:
      5
Cool  October 16, 2003
Reviewer:
      Christo Kutrovsky  from Ottawa, ON Canada
That's usefull to know.

October 12, 2003 Kuo-Chun -- Thanks for the question regarding
      "Question on Statspack Report", version 8.1.6

      You Asked
Tom:

This is a great site. Every time I have question or problem, this site is always
the first site I search for help.

In the statspack report for 1 of my production databases, one thing caught my
eyes and I couldn't figure out whether it is reasonable or, if not, how to
resolve it. Following is the excerpts from the report on the data sampled in 2
hours span:

SQL ordered by Gets for DB: MAXPIP2  Instance: maxpip2  Snaps:      11 -     1

                                Gets       % of
   Buffer Gets     Executes   per Exec    Total   Hash Value
-------------- ------------ ------------ ------ ------------
SQL statement
------------------------------------------------------------------------------
    20,469,462           52    393,643.5   34.4   3859560219
DECLARE job BINARY_INTEGER := :job; next_date DATE := :mydate;  broken BOOLEAN

In this database, there are about 10 jobs. Most of them are scheduled running
once every 1 or 2 minutes. To me 400K gets per exec is too high. Is this number
reasonable? If not how do I reduce it?

Thank you for your help.

      and we said...
well, you can use that HASH VALUE to find the full sql text so you can see what
job that is.

Basically -- that is the master plsql block running your job.  That means you
executed your procedure 52 times -- and the SUM OF ALL OF THE SQL RUN by your
job was 400k.

PLSQL reports back the sum of the "gets" for all of the SQL executed by it.
This is an aggregate report of the sum of the work performed by YOUR JOB (your
procedure).  If you just run your procedure in sqlplus, it'll do about 400k
logical IO's

To "reduce" it, you need to

a) find out which job it is
b) tkprof the job
c) to find the low hanging fruit
d) so you can tune those queries/algorithms.
Review &
      Followup

      Rating:
      5
  October 12, 2003
Reviewer:
      A reader

      Rating: 5
How To Refresh
      the Cache  October 15, 2003
Reviewer:
      Kuo-Chun  from Buchanan, NY
Tom:

Thanks for the help. Following your advise I was able to find the SQL statement
that took about 30 seconds to complete. On the development instance, I was able
to decrease the execution time to 0.02 second after I created a function index,
analyzed the table and all indexes, modified the SQL statement, and set
query_rewrite_enabled to true. In the production instance, I added the same
function index, analyzed the table and indexes, modified the SQL statement in
the package and re-compiled the package. I re-ran the statspack after all have
been done and found nothing has changed. I figure that I need to refresh the SQL
statement in the cache. Question is how to do it? Thank you for your help.

      Rating:
      5
Thanks  October 15, 2003
Reviewer:
      Kuo-Chun  from Buchanan, NY
Tom:

Found the problem after viewing the information in
http://osi.oracle.com/~tkyte/article1/index.html
I did alter system set
query_rewrite_enabled=true and alter system set query_rewrite_integrity=
trusted. After that I did 2 snaps and the original sql statement didn't show up
in the report. Thank you very much.

October 12, 2003 Doug -- Thanks for the question regarding
      "foreign key syntax issue - table level / column level?", version 9201

      You Asked
Tom - given the following setup

create table junk1
(  CB integer not null primary key ) ;

The following CREATE TABLE fails on the foreign key clause.
ORA-02253 constraint specification not allowed here

CREATE TABLE JUNK2
( CA Integer not null constraint junk2_pk primary key,
  CB Integer not null constraint junk2_fk
                        foreign key references junk1,
  CC integer not null);

The following statement succeeds..

CREATE TABLE JUNK2
( CA integer not null,
  CB integer not null,
  CC integer not null,
  constraint junk1_pk primary key (CA),
  constraint junk3_pk foreign key (CB)
                 references junk1);

Is this significant, or just a syntax issue?  I would guess one answer would be
that constraints have to be at the end of the table?
I've heard it goes back to theory and the difference between a column level and
a table level constraint.  Is this so, or not?  What is the difference?

      and we said...
ops$tkyte@ORA920> create table junk1
  2  (  CB integer not null primary key ) ;

Table created.

ops$tkyte@ORA920> CREATE TABLE JUNK2
  2  ( CA Integer constraint junk2_pk primary key,
  3    CB         NOT NULL
  4               constraint junk2_fk
  5               references junk1,
  6    CC integer not null);

Table created.

at the column level, you just say "references", the foreign key is "implied"

Also, primary keys already are NOT NULL -- don't want that on CA.

And just skip the datatype on fkeys -- let us pick it up from the parent table.
Less error prone that way.
Review & Followup

      Rating: 4
Uniqueness vs
      Primary key and References vs foreign key  October 12, 2003

Reviewer:  Doug  from CT, USA
If I do the following:
 create table junk1
 (c1 integer primary key,
 c2 integer not null unique,
 c3 integer not null,
 c4 integer not null)

I can create a foreign key to a unique column like this:
  1  create table junk2
  2  (
  3  ca integer not null,
  4  c2 integer not null,
  5* constraint junk2_fk foreign key (c2) references junk1)
SQL> /

Table created.

Elapsed: 00:00:00.05

I can do this is junk1.c1 is not deliberately made not null either.
So is there a difference between primary key and unique?
Primary key allows no nulls, whereas unique allows me one null.  I can create a
foreign key to both. What is the reason for this?

I can do this too..
create table junk2
(ca integer not null,
c2 integer not null constraint junk3_ref
          references junk1(c2));

table created

OR

  1  create table junk2
  2  (ca integer not null,
  3  c1 integer not null constraint junk3_ref
  4* references junk1(c1))
SQL> /

Table created.

So I can reference a primary or unique constraint.  I can make a foreign key to
a primary or unique constraint.

Why these options?

Thanks,
D

          Followup:

a primary key is a unique constraint with the addition of "you may not be null".
 that is what it is by definition.  Thats the difference between the two
technically.

You can create a fkey to either because the only requirement for fkeys is the
uniqueness rule.  Since both are "unique" - either will do.

      Rating: 5
Great Example
      right there Tom...  October 13, 2003
Reviewer:
      pasko  from HH,Germany
Hi Tom,

Thanks for a great Tip above..i had never used the implied- column-Data-Type
syntax before...never new it works...

Just to make everything "Named" ,
even double column constraints do seem to work !

check this out :

DROP TABLE JUNK2 CASCADE CONSTRAINTS ;
DROP TABLE JUNK1 CASCADE CONSTRAINTS ;

CREATE TABLE JUNK1
   (  CB INTEGER CONSTRAINT cst_cb_nn NOT NULL
                 CONSTRAINT pk_junk1  PRIMARY KEY
) ;

CREATE TABLE JUNK2
    ( CA INTEGER CONSTRAINT junk2_pk PRIMARY KEY,
      CB         CONSTRAINT cb_nn  NOT NULL
                 CONSTRAINT junk2_fk
                 REFERENCES JUNK1,
      CC INTEGER CONSTRAINT cc_nn NOT NULL
);

October 13, 2003 Charlie -- Thanks for the question regarding
      "table partitioning", version 9.2.0.3

      You Asked
Hi Tom,

I have a table T as follows,

create table t(id number, name varchar2(30), weight number(7,2));

Can I partition this table by different columns? For example,

1) create a hash-partition on ID column
2) then, create a range-partition on NAME column, value from A to Z
3) then, create a range-partition on WEIGHT column, value from 100 to 800

So can a table have multiple partitions based on different columns?

thanks,

      and we said...
you can have two levels of partitioning.

The top level will be a RANGE
The lower level can be either HASH or LIST

So, you can

range on NAME, hash on ID
range on NAME, list on WEIGHT

in general:

range on (c1,c2,c3) -- ranges do vectors
hash on (cN) or list on (cN)

this is known as composite partitioning:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/c12parti.htm#459754

Review &
      Followup

      Rating: 5
Table size for
      partitioning  October 12, 2003
Reviewer:
      A reader
Hi Tom,

In your opinion, tables above what size should be partitioned? I have a table of
10M rows of 200M disk space. Should it be partitioned?

Thanks.

          Followup:

it is not about size.

it is about what you want to accomplish.

o you can have ease of administration (sliding windows for fast purge, small
bite size chunks for administering)

o you can have higher availability (partition elimination can make it so that
the unavailability of a single partition does not shutdown your entire system)

o you have have higher performance (partition elimination speeding a full scan,
more partitions may mean less contention in OLTP)

you generally cannot have all three simultaneously :)

you have to design to achieve any of the three

and note that size wasn't really a factor here.  You can partition a 10m table
just as readily as a 10gig table.  A 10gig table doesn't have to be partitioned
but there could well be a 1gig table that must be.

It is about what you need to accomplish more then anything else.

October 13, 2003 Sagi -- Thanks for the question regarding
      "Password File", version 8x

      You Asked
Hi Tom !

Happy XMas and New Year 2002 to you.

1. At the time of creating a password file you specify the maximum number of
distinct DBA and OPERs. But say already this step had been taken care and now I
want to see in the exsisting password file the max. no of entries specified.
Which data dictionary or anyother place where we can get this info.

2. Is SVRMGRL removed from 9i onwards. If so then how do we connect for SYSDBA
or INTERNAL accounts.

3. Can we rename a column. I am aware of a VIEW as an alternate for this.

Thanx in advance for your help.

Regards,
Sagi.

      and we said...
1) take the size of the password file, subtract 1024 and divide by 128.  Eg:

$ ls -lag xxx yyy zzz
-rwSr-----   1 tkyte    other        3584 Dec 27 11:25 xxx
-rwSr-----   1 tkyte    other        6144 Dec 27 11:25 yyy

3584 -> 2560 -> 20, xxx has 20 entries
6144 -> 5120 -> 40, yyy has 40 entries

2) same way you can in 8i, sqlplus.

$ sqlplus "/ as sysdba"

or

$ sqlplus /nolog

SQL*Plus: Release 8.1.7.0.0 - Production on Thu Dec 27 11:31:18 2001

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

idle> connect / as sysdba;
Connected.

(and INTERNAL -- thats gone in 9i as well)

3) nope, a view is the way to do it.  In 9i, you can do an online reorg that can
rename a column as well.

Review &
      Followup

      Rating: 5
Used or
      allowed  December 27, 2001
Reviewer:  Randy
       from Austin, TX
Is that the number of password entries allowed or actually used?

          Followup:

number allowed.  number used = select count(*) from v$pwfile_users

      Rating: 5
Good
      one  December 27, 2001
Reviewer:  Randy

Oh yeah--completely forgot about the dictionary view for that.  I should have
those things memorized by now!!

-Thanks

      Rating: 4
How to do this
      in NT?  December 28, 2001
Reviewer:  Lisa
       from TX
The answer 1, how do I come up the command if my password file is reside in NT
box?
Thanks.

          Followup:

use dir instead of ls, the math is the same.

      Rating:
      3
  December 31, 2001
Reviewer:
      Nirmal  from Qatar
Oracle 817 on WinNT4.

c:\> orawpd file=c:\orarep.ora password=oracle entries=2

The file was created and it's size is 2k.

now the calculation:
2k = 2*1024 = 2048
=> 2048 - 1024 = 1024
=> 1024/128 = 8.

So it's meaning that i can keep maxinum 8 user entries in the passwordword. It's
not 2, since i created the file entries=2?.

Can you pls clear out this.

          Followup:

do this:

C:\>orapwd file=c:\orarep.ora password=oracle entries=2

C:\>dir orarep.ora
 Volume in drive C has no label.
 Volume Serial Number is F455-B3C3

 Directory of C:\

12/31/2001  08:42a               1,536 orarep.ora
               1 File(s)          1,536 bytes
               0 Dir(s)   7,823,032,320 bytes free

for me, its not 2k using 816 and 901 (and I seriously doubt 817 would be any
different).

      Rating: 5
8.1.7 Password
      File Size  December 31, 2001
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Tom:

Here it is on 8.1.7...

C:\Temp>c:\oracle\8.1\bin\orapwd file=orarep.ora password=oracle entries=2

C:\Temp>dir orarep.ora
 Volume in drive C has no label.
 Volume Serial Number is 6056-B9DA

 Directory of C:\Temp

12/31/2001  10:23a               1,536 orarep.ora
               1 File(s)          1,536 bytes
               0 Dir(s)   8,502,665,216 bytes free

Shock!  Horror!  It is the same.  Luckily I was sitting down.  Now, of course,
if you just look at it from Windows Explorer it will say '2K' because it rounds
in the display...

- Mark

      Rating: 2
Password File
      Size Discussions  February 16, 2002
Reviewer:
      Vikas Khanna  from INDIA
Hello Tom,
Even if you specified the size tobe 1536 bytes if the no. of enteries are 2 in
the password file, then also by applying your formala the result is coming to be
4 entries which is not the right case??
1536 - 1024 =  512 bytes
512/128 gives u 4 instead of 2 entries.

Where is the problem??
In some cases it is coming to be true as per your formula. Why it is so
differentiating ??

          Followup:

It is right -- you ask for 2, 3 or 4 and we give you 4.  It is rounded up.

      Rating: 4
Pls
      Clarify  May 20, 2003
Reviewer:  Anirudh
       from New Delhi, INDIA
Hi Tom,
could you please clarify this:
"It is right -- you ask for 2, 3 or 4 and we give you 4.  It is rounded up. "
Thanks

          Followup:

it means regardless of whether you asked for 1, 2, 3 or 4 entries, we allocate 4
of them at a time.

      Rating: 4
password file
      creation in oracle 9i  October 13, 2003
Reviewer:
      Mujib Kasem  from Bangladesh
In the oracle8i we create pwd file by using ORADIM utility before database
creation. But in oracle 9i how can we do that thing .

          Followup:

you always used orapwd -- not oradim.

http://download-west.oracle.com/docs/cd/A87861_01/NT817EE/index.htm
(thats from 8i).  oradim managed 'services', orapwd the password file

same in 9i

October 13, 2003 Michael -- Thanks for the question regarding
      "Complex Unique constraint", version 8.1.7

      You Asked
Hi Tom,

i want to create a complex unique constraint.
Assume this is the table i have:

create table t
(
    pk         integer          not null,
    value      varchar2(10)     not null,
    validfrom  date             not null,
    validto    date             not null,
    primary key (pk)
)

What i want is, that value is unique in the period of validfrom to validto.
Overlapping of periodes is must not be allowed.

What all this is for:
Atm, we have a address table, where we store the address of our customers. A
customer is only allowed to have one regular billing address at a given time,
but more than one shipping address at the same time. Because the address can
change and we need all the old addresses, we "invalidate" the address by setting
validto and we insert a new address with a new validfrom and validto. We have a
business rule, that says, that an address, which has no real validto, defaults
to to_date('31.12.2099 23:59:59', 'DD.MM.YYYY HH24:MI:SS')". Now i want to
enforce a unique constraint on the table, so it isn't possible that a customer
has more than one valid billing address at the same time, but can have more than
one valid shipping addresses at the same time. How can i do that?

Our address table looks like this (irrelevant columns for unique constraint left
out):
create table address
(
    pk_addressid             integer          not null,
    fk_customerid            integer          not null,
    addresstype              varchar2(8)      not null
       check (addresstype in ('SHIPPING', 'BILLING')),
    validfrom                date             not null,
    validto                  date             default to_date('31.12.2099
23:59:59', 'DD.MM.YYYY HH24:MI:SS')   not null,
    primary key (pk_addressid),
    foreign key (fk_customerid)
       references customer (pk_customerid)
)
/

Thanks in advance, Michael

      and we said...
Ok, this is easy using a function based index.

create UNIQUE INDEX
on ( case when addressType = 'BILLING'
          and validTo = to_date('31.12.2099 23:59:59', 'DD.MM.YYYY HH24:MI:SS')
     THEN FK_CUSTOMERID
     else NULL );

that'll ensure there is AT MOST one record for a CUSTOMERID that has the validto
of the default value and addresstype = 'BILLING'

We use the fact that entire NULL entries are never made into a b*tree index  -
so, this'll index only those records where billing/dec 31 2099 is true...

If you want all of the details - I go over this in great detail in my new book
Effective Oracle by Design as well.

Review &
      Followup

      Rating:
      5
  October 12, 2003
Reviewer:
      A reader

      Rating: 4
Great! But
      still not totally solved. ;)  October 12, 2003

Reviewer:  Michael
Thanks very much Tom.

you tought me new things again, very good stuff.
It's almost what i needed, but it'd still be possible, that there was more than
one billing address valid at the same time in the past or that there's more than
one billing address valid at the moment, if a non-default validto is chosen. Is
there a way to add a unique constraint, which checks if a "fk_customerid,
'BILLING', validfrom, validto"-combination would conflict with any other
existing address?

Thanks again, Michael

          Followup:

so basically -- you have the overlap issue I asked about.

So, why don't you have a constraint that just ensures that

a) validto = that default OR
b) validto = some date less then sysdate

since the only "valid" business address should be one with validto = default.
All others SHOULD represent history - no?

Everything else requires nastly procedural logic.  you have to lock all the
address records for that customer (serialization is key here) and then "look".
ugly.

better if you can just have the above business rule.

      Rating: 5
search for
      avoiding overlap ..  October 13, 2003
Reviewer:
      pasko  from HH,Germany
Hi Tom,

I think Michael could also use your avoiding-overlap trigger Logic
implementation to solve the problem.

http://asktom.oracle.com/pls/ask/f?p=4950:8:2401685892367151463::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:474221407101,

          Followup:

yeah, it gets ugly.

October 13, 2003 patibandla -- Thanks for the question regarding
      "Functions Handling in SQL Queries. ", version 8.1.6.0

      You Asked
I am writing sql which  extensively uses user defined functions.

The Usedefined functions basically consists
  1. Arithemetic operations intensive (No Select Statements)
  2. SQL  Intensive (Selection based on the parameters)

1.  select * from table where ar_operation(column) and
    sql_operations(column)
or

2.   select * from table where all_operations(column)

which of the above is better
1. when u have more than 3 or more  sql statements in a
functions and around 100 lines of ar_operations code.

2. when u have les than 3  sql statements in a functions and
around 20/30  lines of ar_operations code.

Thanks in advance

Regards

      and we said...
Well, if SOME of the functionality is deterministic -- that is:

f(x,y,z)

always returns the same value given the same values for X, Y,
and Z (eg:  f(1,2,3) always returns 5.  No matter what values I
pass to F, a deterministic value is returned) I would break my
functions into:

o deterministic
o non-deterministic.

For the deterministic functions, we can create an index on them
and massively speed up processing.  For exmaple, UPPER() is
deterministic.  I can create an index:

 create index emp_idx on emp(upper(ename));

Now, when I search:

  select * from emp where upper(ename) = 'KING';

that query will NEVER actually call the the upper() function,
rather it will take the value 'KING' and goto the index and find
the hits.

So, if you have some complex function f that takes some inputs
-- you could:

 create index t_idx on t(f(x,y,z));

and later when you query:

  select * from t where f(x,y,z) between 1 and 20;

it'll never actually execute f, it will use the index.  In this
way you can:

  select *
    from T
   where DETERMINISTIC_FUNCTION ....
     and NON_DETERMINISTIC_FUNCTION

and the optimizer will use the index when possible, avoiding
calling PLSQL for those values.

See

http://osi.oracle.com/~tkyte/article1/index.html
for details.

Review &
      Followup

      Rating: 5
limitations of
      calling functions from sql  January 04, 2002

Reviewer:  Kulguru
Tom

when we call functions from sql

1.We cannot return a date
2.we cannot pass out and inout parameters

Why is this so .. and

what are the other limitations.

          Followup:

1) is false.  you can so return a date

ops$tkyte@ORA817DEV.US.ORACLE.COM> create function foo return date
  2  as
  3  begin
  4  return sysdate;
  5  end;
  6  /

Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select foo from dual;

FOO
---------
04-JAN-02

2) it doesn't MAKE SENSE to have OUT parameters.  I mean, what would it mean to:

select foo(dummy) from dual;

where the parameter to foo was an OUT parameter?  What would it change
(rhetorical question -- it wouldn't change anything).

This is not a limitation, rather its more like common sense.  There is no place
to return that OUT parameter TO..

      Rating:
      5
Correction  January 04, 2002
Reviewer:
      Kulguru
A function called in a sql cannot return a boolean. This was my doubt. Why is it
so.

          Followup:

because you cannot:

create table t ( x boolean );

boolean is not a SQL type.  SQL can only deal with SQL types.  The function can
return ANY datatype that you can use to define a database column with -- no
more, no less (this includes UDT's -- object types, they can return those)

      Rating: 5
Where can I get
      a list of sql types and pl/sql types  January 05, 2002

Reviewer:  Kulguru
Tom

Can you give the link to the documents where I can find a list of sql types and
pl/sql types.

Thanks

          Followup:

concepts guide (the MOST UNDER-READ document in the oracle doc set.  if you read
this from cover to cover and retain 20% of it, you'll know more then 80% of the
rest of the Oracle world!)

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/c10datyp.htm#629
for plsql types, lo and behold -- the PLSQL manual has a section titled
"datatypes"

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a77069/02_funds.htm#10531
....

      Rating: 4
What could be
      the effect when it is in RBO mode  March 24, 2002

Reviewer:  lakshmi narasimhan  from Dubai, UAE

Tom

Great.  In fact i am having the same kind of problem while using User defined
functions.

I am just cut and paste my query.
our DB is RULE (ver 8.1.7)

Due to some requirement, i need to call function in the where clause
as row value as parameter.  the column i am sending is one of the
column in the unique index along with some other parameter

Wrote file afiedt.buf

  1        SELECT   A.tarnum, A.cardsgcod, orgloctyp,orgloccod orgctycod,
  2        dstloctyp,dstloccod dstctycod,
  3        A.fabcod, jnytyp, faramt, farcurcod, tvleffdat, tvldisdat,a.crltyp,
  4        A.rounum, A.rulcod, fntcod,A.datsou,A.lnknum,A.seqnum
  5        FROM fapfarhdr a
  6        WHERE Datsou='0'
  7        and Cardsgcod='EK'
  8        and (1 = check_location(orgloccod,orgloctyp))
  9        and (1 = check_location(dstloccod,dstloctyp))
 10        AND fabcod = 'Y'
 11*       AND crltyp = '3'

to facilitate the user to enter what ever the type of or group of location in a
single
row. ( they will file air lines Fares from set of cities to set of cities)

Now my performance is going for all time low.

with out the function it is taking around 2 mts for more than 1000 plus records,
with function it is taking more than 10 mts.  is it calling a function from the
Query in caseof RBO is the reason?

          Followup:

create a function based index on

    check_location(orgloccod,orgloctyp),
    check_location(dstloccod,dstloctyp),
    datsou,
    cardsgcod,
    fabcod,
    crltyp

and see what happens.  I don't know what "mts" is (is that some sort of
abbreviation for "minutes"??) but it would be 100% a function of how long it
takes to execute your function.  If it takes a quarter of a second or
thereabouts (a little less actually) to run YOUR code, then easily it would take
an extra 8 minutes as  8*60 = 1000*2*.24 for example.

      Rating: 3
But It is Rule
      Based  March 24, 2002
Reviewer:
      A reader
Hi Tom,

Just thought of highlighting this. The Previous Poster is using a Rule Based
Optimiser so What is the use of him creating a Function Based Index.

Your comments pls.

Regards,
Ganesh R

          Followup:

I ignored that -- when they read about fbi's they will discover that you need to
use the CBO and will use it if they want the phenomenal performance gains that
can be achieved by that optimizer (and access to the host of other functionality
that is not otherwise available).

You can always use a HINT as well, especially with FBI's.

      Rating: 4
But i am using
      Rule Base  March 25, 2002
Reviewer:  Lakshmi
      narasimhan R  from Dubai, UAE
Hi tom

thanks for immediate response

But i cannot create function based since i am
using RBO.  Is there any efficient workaround in RBO
itself.

          Followup:

Don't use PLSQL then.  Incorporate the logic inline.  I find that 99.99% of the
time, the logic in the plsql routine can be done using SQL with decodes and/or
CASE statements.

You can ALWAYS hint the query as well - to tell it to use the index (which will
invoke the CBO and will use the function based index)

You should use the CBO.  Think outside the box.  Use the tools available.

      Rating:
      5
  March 25, 2002
Reviewer:
      A reader
hi tom.

Can you explain more about o deterministic and
o non-deterministic ?

What are these?

you told :
f(x,y,z)

always returns the same value given the same values for X, Y, and Z (eg:
f(1,2,3) always returns 5.  No matter what values I pass to F, a deterministic
value is returned) .

What do you mean always return 5 ?

          Followup:

deterministic means that given the SAME inputs, the function will return the
same OUTPUT.

take:

x := f(1,2,3);
y := f(1,2,3);

if F is deterministic, that can be written as:

x := f(1,2,3);
y := x;

if F is NOT deterministic, it cannot.

      Rating:
      4
Review  March 26, 2002
Reviewer:
      Momen  from Riyadh
Hi Tom,

  Is the following function a Deterministic or a Non-Deterministic function ???

Create or Replace Function Det_or_Not(p_emp_no in emp.emp_no%type) return
emp.emp_name%type is
  v_emp_name emp.emp_name%type;
begin
  select emp_name into v_emp_name
    from emp
   where emp_no = p_emp_no;

  return v_emp_name;
end;
/

  The above query returns emp_name based on emp_no, what if I delete the row or
even update the name ???

2. How do I find all of your articles from osi
  i.e.
http://osi.oracle.com/~tkyte/
Thanks

          Followup:

It is non-deterministic.

A simple "update emp set emp_name = 'foo' " will change the return value for a
given p_emp_no.  Given the input 12345 -- this function may return a different
value at two different points in time.

as for "how to find all fo the articles on osi" -- you have the link right
there.  they are all listed there.

      Rating: 3
function take
      longer ?   March 26, 2002
Reviewer:  Charanjiv
       from India
you said - "but it would be 100% a function of how long it
takes to execute your function.  If it takes a quarter of a second or
thereabouts (a little less actually) to run YOUR code, then easily it would take
an extra 8 minutes as  8*60 = 1000*2*.24 for example."
Could you elaborate? esp in the context if i have 1000 lines of pl/sql code in a
procedure and i take out some code and put then in a function. does this hold
true then also?.

          Followup:

It is simple math here.  Very simple math

You fetch 1,000 records.
You call a function 2 times for each record.
Hence you call a function 2,000 times.

If that function takes N units of time to execute, you will consume 2,000*N
units of time AT LEAST during the processing of your query.  If your function
takes 0.24 seconds to execute on average, you will consume 480 seconds of
runtime in 2,000 calls.  That is 8 minutes.

Replacing 100 lines of code in a procedure that started with 1000 with a
function that has 100 lines of code will result in -- 1,000 lines of code.
Functions are good for programmers, they do nothing to increase performance,
only code reuse (in fact, a compiler optimization is to "inline code" to remove
functions since a function itself has overhead in the calling of it).

      Rating: 4
calling the
      same function twice  July 07, 2003
Reviewer:
      john
Tom,

Question on the answer to last followup(Charanjiv's question)

if i use function two times in a query with same parameters will CBO calls the
function twice?

say for example:

select your_bonus(salary) from emp where your_bonus(salary) > 0;

so, your_bonus(salary) is being called twice?

I want this to be called only once, is there any hint with which i can make that
to happen?

          Followup:

It may, it may not.  It might never be called, it might be called once for each
row and then again for each row that satisfied the predicate.  It might be
called once for each row.

It depends.  Consider:

ops$tkyte@ORA920> create table emp ( empno number primary key, comm number )
organization index;
Table created.

ops$tkyte@ORA920> insert into emp
  2  select object_id, decode( mod(rownum,2), 0, object_id, null )
  3    from all_objects
  4   where rownum <= 1000;
1000 rows created.

ops$tkyte@ORA920> analyze table emp compute statistics;
Table analyzed.

so, emp is a table with 1,000 rows and every other COMM is NULL -- so the
query will return 500 out of 1,000 rows

ops$tkyte@ORA920> create or replace function your_bonus( p_empno in number )
return number
  2  deterministic
  3  as
  4          l_comm number;
  5  begin
  6          dbms_application_info.set_client_info( sys_context( 'userenv',
'client_info')+1 );
  7
  8          select comm
  9            into l_comm
 10            from emp
 11           where empno = p_empno;
 12
 13          return l_comm;
 14  exception
 15          when no_data_found then
 16                  raise program_error;
 17  end;
 18  /
Function created.

that function will let us easily count how many times it's called due to the
dbms_application_info stuff...

ops$tkyte@ORA920> exec dbms_application_info.set_client_info(0);
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> set autotrace traceonly
ops$tkyte@ORA920> select your_bonus(empno) b from emp;
1000 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1000 Bytes=3000)
   1    0   INDEX (FAST FULL SCAN) OF 'SYS_IOT_TOP_38955' (UNIQUE) (Cost=2
Card=1000 Bytes=3000)

ops$tkyte@ORA920> set autotrace off
ops$tkyte@ORA920> select sys_context( 'userenv', 'client_info') sys_ctx from
dual;

SYS_CTX
--------------------
1000

that is to be expected -- 1,000 rows -- 1,000 calls

ops$tkyte@ORA920> exec dbms_application_info.set_client_info(0);
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> set autotrace traceonly
ops$tkyte@ORA920> select your_bonus(empno) b from emp where your_bonus(empno) >
0;

500 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=50 Bytes=150)
   1    0   INDEX (FAST FULL SCAN) OF 'SYS_IOT_TOP_38955' (UNIQUE) (Cost=2
Card=50 Bytes=150)

ops$tkyte@ORA920> set autotrace off
ops$tkyte@ORA920> select sys_context( 'userenv', 'client_info') sys_ctx from
dual;

SYS_CTX
--------------------
1500

that shows that in this case, the function was called ONCE in the predicate
for each of the 1,000 rows and then again in the select list for the 500 rows
that actually met the criteria.

ops$tkyte@ORA920> exec dbms_application_info.set_client_info(0);
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> set autotrace traceonly
ops$tkyte@ORA920> select b
  2    from (select your_bonus(empno) b
  3            from emp
  4                   where rownum > 0 ) EMP
  5   where b > 0;

500 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1000 Bytes=13000)
   1    0   VIEW (Cost=2 Card=1000 Bytes=13000)
   2    1     COUNT
   3    2       FILTER
   4    3         INDEX (FAST FULL SCAN) OF 'SYS_IOT_TOP_38955' (UNIQUE) (Cost=2
Card=1000 Bytes=3000)

ops$tkyte@ORA920> set autotrace off
ops$tkyte@ORA920> select sys_context( 'userenv', 'client_info') sys_ctx from
dual;

SYS_CTX
--------------------
1068

rownum is very handy for helping us out there.  The optimizer cannot merge the
predicate "where b > 0" into the subquery due to that. So, your_bonus(empno) is
materialized into "temp" in effect and reused.  Now you might wonder why 1,068?
well, if you

ops$tkyte@ORA920> set arraysize 1000
ops$tkyte@ORA920> exec dbms_application_info.set_client_info(0);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> set autotrace traceonly
ops$tkyte@ORA920> select b
  2    from (select your_bonus(empno) b
  3            from emp
  4                   where rownum > 0 ) EMP
  5   where b > 0;

500 rows selected.

ops$tkyte@ORA920> set autotrace off
ops$tkyte@ORA920> select sys_context( 'userenv', 'client_info') sys_ctx from
dual;

SYS_CTX
--------------------
1002

You can see it is a function of the number of times we fetched from this result
set.  Larger array fetches will reduce this number

Now for the "best" perhaps

ops$tkyte@ORA920> create index bonus_idx on emp(your_bonus(empno));
Index created.

ops$tkyte@ORA920> alter session set query_rewrite_enabled=true;
Session altered.

ops$tkyte@ORA920> alter session set query_rewrite_integrity=trusted;
Session altered.

ops$tkyte@ORA920> exec dbms_application_info.set_client_info(0);
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> set autotrace traceonly
ops$tkyte@ORA920> select your_bonus(empno) b from emp where your_bonus(empno) >
0;

500 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=50 Bytes=150)
   1    0   INDEX (RANGE SCAN) OF 'BONUS_IDX' (NON-UNIQUE) (Cost=2 Card=50
Bytes=150)

ops$tkyte@ORA920> set autotrace off
ops$tkyte@ORA920> select sys_context( 'userenv', 'client_info') sys_ctx from
dual;

SYS_CTX
--------------------
0

ops$tkyte@ORA920>

you cannot beat 0 times

      Rating: 4
CHAR Datatype
      question  July 07, 2003
Reviewer:
      A reader  from MN, USA
The following is taken from the Oracle Documentation.
I am unable to understand the third point.
<quote>
CHAR Datatype
The CHAR datatype stores fixed-length character strings. When you create a table
with a CHAR column, you must specify a string length (in bytes, not characters)
between 1 and 2000 for the CHAR column width. The default is 1. Oracle then
guarantees that:

==1) When you insert or update a row in the table, the value for the CHAR column
has the fixed length.

==2) If you give a shorter value, then the value is blank-padded to the fixed
length.

==3) If you give a longer value with trailing blanks, then blanks are trimmed
from the value to the fixed length.

==4) If a value is too large, Oracle returns an error.

Oracle compares CHAR values using blank-padded comparison semantics.

</quote>

It says if the value is longer than the defined size and has trailing blanks
then these are trimmed and the value is inserted or updated in the table. I
tried to do this but it gives me an error. Is this a doc error or am I
misinterpreting the documentation?

RKPD01> drop table t1;

Table dropped.

RKPD01> create table t1(x char(5));

Table created.

RKPD01> insert into t1 values(rpad('A', 6));
insert into t1 values(rpad('A', 6))
                      *
ERROR at line 1:
ORA-01401: inserted value too large for column

 I am inseting a longer value with trailing blanks. But it throws an error as
mentioned in point 4. Point 3 and 4 seem to contradict each other can you
enlighten me about the same.

Thanks

          Followup:

seems to be a doc bug, I've logged on on it as of reading this.

      Rating: 5
calling the
      same function twice  July 09, 2003
Reviewer:
      John
Tom, thanks for the followup.

an extract from your followup:

"
The optimizer cannot merge the
predicate "where b > 0" into the subquery due to that. So, your_bonus(empno) is
materialized into "temp" in effect and reused."

so,i have a query like this:

select sum(alias1),
sum(decode(alias2,0,0,alias1))

(

select function1 alias1, function2 alias2 from
<tables> where <where clause>

)

in the above query's inner query also do i need to use rownum > 1?

          Followup:

if you want the function called a minimum number of times, quite possibly.

test it and see -- I showed how to use dbms_application_info to easily benchmark
these situations.

      Rating:
      3
OK  August 06, 2003
Reviewer:
      Chakravarthi  from Chennai,India
Dear sir,
Well and wish the same from you.I have a question for you related to function.
1)I tried to create a function like
  create or replace function f(arg in number,sqr out number,
    cube out number)
  return number is
 begin
  ...code
   I tried to out the parameters sqr and cube but the compiler is throwing
errors.How to correct this and how to invoke that function after it gets
created.
Whether I have to use another pl/sql block or use execute
immediate involving returning clauses.Please help me with code.
Thanks,
Yours sincerely
R.Chakravarthi

          Followup:

ops$tkyte@ORA920> create or replace function f ( arg in number, sqr out number,
cube out number ) return number
  2  is
  3  begin
  4          sqr := arg*arg;
  5          cube := sqr*arg;
  6          return arg;
  7  end;
  8  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          l_cube number;
  3          l_sqr  number;
  4  begin
  5          dbms_output.put( 'the square and cube of ' ||
  6                            f( 5, l_sqr, l_cube ) );
  7      dbms_output.put_line( ' is ' || l_sqr || ',' || l_cube );
  8  end;
  9  /
the square and cube of 5 is 25,125

PL/SQL procedure successfully completed.

      Rating: 5
Question on the
      function return varchar2 length  September 25, 2003

Reviewer:  Frank  from US
A question along the long of using functions.  If I define a function with
return data type of VARCHAR2, I always get VARCHAR2(4000).  Is there any way to
restrict the length of the return string?

Thanks!

          Followup:

other then using SUBSTR, no

      Rating: 5
function based
      index in rule  September 29, 2003
Reviewer:
      A reader  from Buffalo, NY
Tom,

I understand your article when it states "Use the Cost Based Optimizer. Function
based indexes are only visible to the Cost Based Optimizer and will not be used
by the Rule Based Optimizer ever.".

However, we are a software house and have some clients who will be using RULE
and some using CBO. In our software upgrade, we are introducing function-based
indexes. I would prefer to always create the index for all of our clients
regardless of their optimizer mode. However, I don't want to create negative
performance results for those using RULE. Is there a performance degradation in
maintaining function-based indexes if no statisics exist and RULE is the
optimizer mode?

Thanks for your reply.

          Followup:

I don't understand -- why are you not DICTATING the optmizer mode be CBO?

You own the code
You own the queries
You have to support it
I would assume they call you for performance issues.

I don't see how you could possibly do the last two things without using EITHER
OR, but not both?????

your application has a requirement to have its tables analyzed, so be it.

      Rating: 3
Q on Functions
      used in Select   October 09, 2003
Reviewer:
      Anil Pant  from Hyderabad, India
Hello,
I want to know why its not possible to perform DML operations in functions which
are called in select statements on tables which are not used in select
statement.
In the example below I can understand if function foo tries to update table
akp1. But what's the reason it does not allow to do any DML operation on akp2 or
any other table even when the select is not using that table.

Here is the example
create table akp1 (col1 number(5), col2 number(5))

create table akp2 (col1 number(5), col2 number(5))

create or replace function foo (pcol number) return number  is
begin
update akp2 set col2 = pcol;
return pcol*10;
end;
/

insert into akp1 values(1,1)

insert into akp2 values(1,0)

SQL> select col1, col2, foo(col2) from akp1;
select col1, col2, foo(col2) from akp1
                   *
ERROR at line 1:
ORA-14551: cannot perform a DML operation inside a query
ORA-06512: at "ISV310_DEV.FOO", line 3

          Followup:

Tell me -- in a 100% deterministic fashion -- given that akp1 has the following
data:

COL1    COL2
-----   ------
1       2
2       1

what is the value of col2 in akp2 after that query runs?  1 or 2.  No matter
what you say, i'll show you it is the other value.

It is what is termed "not safe", the results are ambigous.

Tell me, can you tell (give that table T has 100 rows in it) how many times the
function F would be called:

select * from t where x = f();

no matter what you answer, i'll show a different answer.  It is ambigous --
hence, prevented.

You can "get around" it by using an autonomous tranaction but that is so not
recommended.

      Rating: 2
Could not get
      you  October 09, 2003
Reviewer:  Naina
       from New York
Sorry Tom I cud not get you? Could you be more specific. What did you mean by
this

"select * from t where x = f();
no matter what you answer, i'll show a different answer.  It is ambigous --
hence, prevented."

          Followup:

if f() did modifications in the database -- what would the end result of
invoking that sql query be?

You CANNOT answer that because you cannot answer the question "how many times
will f() be called".  I can make F() be called a different number of times with
that query using the SAME exact table with the SAME exact data.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t ( cnt int );

Table created.

ops$tkyte@ORA920> insert into t values (0);

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t2 ( x int );

Table created.

ops$tkyte@ORA920> insert into t2 select rownum from all_objects where rownum <=
100;

100 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace function f return number
  2  as
  3          pragma autonomous_transaction;
  4  begin
  5          update t set cnt = cnt+1;
  6          commit;
  7          return 55;
  8  end;
  9  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t2 where x = f();

         X
----------
        55

ops$tkyte@ORA920> select * from t;

       CNT
----------
       100

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index t2_idx on t2(x);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t2 where x = f();

         X
----------
        55

ops$tkyte@ORA920> select * from t;

       CNT
----------
       102

ops$tkyte@ORA920>

see how the addition of a simple index, or a change in a query plan or because
it is raining on tuesday -- changes how many times F() is called, hence makes
the result of F() updating data "ambigous"

Also consider if F() was passed some data -- and the order in which F() was
called "mattered".  ambiguities ABOUND.  This is a really bad idea.

      Rating: 3
Pls
      clarify  October 13, 2003
Reviewer:  Naina
       from New York
Going by your example here's what I've done.

Case 1 : First without creating the index ran the query

SQL> select * from t2 where x = f();

         X
----------
        55

SQL> select * from t;

       CNT
----------
       100

SQL> select * from t2 where x = f();

         X
----------
        55

SQL> select * from t;

       CNT
----------
       200

SQL> select * from t2 where x = f();

         X
----------
        55

SQL> select * from t;

       CNT
----------
       300

All the rows of the table t2 was scanned and table t was updated everytime.
So, select * from t2 where x = f();
would update 100 to table t.
You can see the increments by 100 everytime

Case 2 : Created Index

Now I dropped the tables and created again. Also created an index. Now the
values were different everytime I ran the query.

SQL> select * from t2 where x = f();

         X
----------
        55

SQL> select * from t;

       CNT
----------
       100

SQL> create index t2_idx on t2(x);

Index created.

SQL> select * from t2 where x = f();

         X
----------
        55

SQL> select * from t;

       CNT
----------
       102

SQL>  select * from t2 where x = f();

         X
----------
        55

SQL> select * from t;

       CNT
----------
       104

SQL> select * from t2 where x = f();

         X
----------
        55

SQL> select * from t;

       CNT
----------
       106

Now the table t was updated by 2 everytime. So what's happening and how its
connected to Functions in SQL

          Followup:

When you full scan -- we are:

 for C in ( select * from t )
 loop
     if C.x = f() then output
 end loop

we call f() once per row.

When we index scan -- we are processing:

   temp := f()
   do an index lookup to find rows with temp and output them

(please don't ask why we call it 2 times, i've no idea -- technically it seems
once should do but it does it twice).

My point is -- you and I have NO CONTROL over how or when f() is called -- not
even how many times f() is called and it can change from day to day -- hence it
is "ambigous" and "totally unsafe" to have f() be part of a larger transaction.
It would be like using dbms_random to update your salary.

October 13, 2003 aman -- Thanks for the question regarding "about
      the RR format", version 9i

      You Asked
sir !!
i am trying to use the RR format as given in the oracle 9i..this format is used
to interpret the centuary and then on the basis of thisto find out the correct
year of the date !! according the format of RR if the current year and the
specified year , both are coming in the range of 0-49 , the date will be in the
current centuary ..that means if we have the current year as 2003 and the
specified date as 27-oct-17 according to the RR it will be in the year as 2017
and according to the YYYY format it wil be 1917 ..sir my question is as follows
Q1. if we have a date entered in the year 1940 and then after 63 years in 2003
we migrated to 9i , then we have the option of RR to use  in the To_char funtion
..then if some user is going to use the YYYY format in the to_char function it
will show the date as 1940 but if someone tries it with the RR format it will be
2040  as both the current year and the specified year they are in the range of
0-49.am i right ?this value display is wrong then how we can say that RR is
helping the users in the correct interpration of the date values ?there will be
the 2 displays for the same date .then how this is going to explain the actual
date value !!
please remove my confusion
thanx alot in advance !!

      and we said...
it would be 2017 with both RR and YY (it would be year 17 with YYYY).

ops$tkyte@ORA920> select to_char( to_date( '27-oct-17', 'dd-mon-yy' ), 'YYYY' ),
  2         to_char( to_date( '27-oct-17', 'dd-mon-rr' ), 'YYYY' ),
  3         to_char( to_date( '27-oct-17', 'dd-mon-yyyy' ), 'YYYY' )
  4    from dual
  5  /

TO_C TO_C TO_C
---- ---- ----
2017 2017 0017

q1) the ONLY correct, proper, professional, smart, intelligent thing to do is to
do what we ALL learned in 1999 the hard way.

It takes 4 characters to properly input a year.  Period.  User interface should
accept ony 4 digits -- no more, no less.

But -- in answer to your question.  If you take data away -- data is lost.  If
you demand on using 2 characters to display something that takes a minimum of 4
-- you WILL LOSE data, you will corrupt it at some point.  No avoiding that --
you get what you do, thats all.

USE YYYY and don't worry about it!
Review &
      Followup

      Rating: 4
thanx alot for
      the quick responce!!  October 12, 2003
Reviewer:
      aman  from india
sir!!
thanx alotfor the quick responce!!you are amazing!!
sir,its true it will show 2017 with both the RR and YY format if the date is
entered in the  2003 year.sir i am  confused about the point that if someone had
entered the date in the year 1940 (20 centuary) and now someone else is trying
to look at it in 2003(21 centuary) then what will be shown to him?as last digits
of both the current year (2003 21st centaury) and specified year(1940 20th
centaury) lie in the range of 0-49 ,according to the RR concepts this would show
year  2040 as it says that "the return date will be current centaury means 21st
centaury" and if uwe use it will be 1940 ..correct me if i am wrong.i tried to
do to the practical of it by changing the system date from the Control
Panel->Regional Options ->date =1939 but then database stopped working giving
the error that the system date is in correct , i changed thedate from the Task
Bar to 2099 , i was not able to connect with any of the Sys or System login and
i was getting the error that hosdef extension not found ....so i was unable to
check my doubt practically ...please explain ..
thanx alot sir

          Followup:

you are confusing DATES with FORMATS.

A date is a 7 byte binary piece of data always -- it stores

YYYY
MM
DD
HH24
MI
SS

So, if in 1940 some inserted "to_date( '01-jan-17', 'dd-mon-yy' )" that would be
1917.

In the year 2040 -- that will STILL be 1917
It will be 1917 FOREVER

It is like the Number "5".  you insert 5, you get 5 out forever.

If you insert 1917, it'll be 1917 forever.

Now, in the year 2040, if you:

select to_char( dt, 'dd-mon-yy' )

you'll get back 01-jan-17 which is basically a MEANINGLESS character string and
points out why the only "good" thing is to use YYYY on INPUT and OUTPUT to avoid
ambiguities.

      Rating: 5
downside of
      using 'YY'  October 12, 2003
Reviewer:
      A reader
Tom

Can you show an example where using 'YY' format instead of rr or rrrr , proves
fatal.

If I insert using yy format, and query using a to_char of yyyy, then I will
still be o.k....

SQL>  insert into t select to_date('01-01-17','dd-mm-yy') from dual
  2  ;

1 row created.

SQL> commit;

Commit complete.

SQL> select x from t;

X
---------
01-JAN-17

SQL> select to_char(x,'mm/dd/yy') from t;

TO_CHAR(
--------
01/01/17

SQL> select to_char(x,'mm/dd/yyyy') from t;

TO_CHAR(X,
----------
01/01/2017

What is the issue here.

          Followup:

here:

03

What year is that please?  No matter what you say -- you are wrong.

Now:

2003

what year is that please?  ahh -- it takes 4 characters to show a year -- i get
it!

SQL> select to_char(x,'mm/dd/yy') from t;

TO_CHAR(
--------
01/01/17

is that 1917, 2017, 1817 - what???????  you don't know -- that is the issue.

Worse, end user gives you "40".

Is that 1940, 2040 -- what????? no clue, no idea.  not safe.

something we all should have learned 3 years ago big time?

      Rating:
      5
excellent  October 13, 2003
Reviewer:
      aman  from india
thanx alot sir . sothat means if the person has entered the date in 1940(20
century) and he sees it in the 2003 (21st centuary) he will see it as 1940
FOREVER..one last doubt please forgive me for the stupid question but then what
does itmean by the statement that if the current year and specfied year they are
in the range of 0-49 RR will show the date in the current centuary ?does it mean
that whatever the date is like 1940 it will be reurned in the 21 st centaruy as
like  1940 but internally with the centuary of 20th one?or as i thought that it
will be returned as like 2040 if we go by the word to word description of RR
format ?
thanx alot

          Followup:

RR is an "input" format.

it means if you enter to_date( '01-jan-40', 'dd-mon-rr' ) Oracle will slide
around the date based on the current year.

In 1999 and 2001 -- that would be the year 2040

As opposed to yy -- where the century is based on the current date.  meaning

to_date( '01-jan-40', 'dd-mon-yy' )

is 1940 on December 31'st 1999 at 11:59:59 and 2040 on January 1st 2000 at
00:00:00

It just changes the sliding window.

I will strongly reiterate "i hope this shows you why it would be a truly utterly
horrible idea to use yy or rr -- YYYY is "right"'

      Rating: 5
excellent as
      always  October 13, 2003
Reviewer:  aman
       from india
thanx alot sir , thts what i was thinking too !!finally the conclusion goes that
if we are putting somthing wrong we wil get the output wrong too
thanx alot for the excellent explanation

October 13, 2003 -- Thanks for the question regarding "Is it a
      good idea moving towards XMLType CLOB based model", version 9iR2

      You Asked
Hi Tom,

Thanks for all your help.

I got a question for you.

Ours is a web based Ordering system like shopping cart model.
As of now, our database is purlely relational. Due to lot many different
services, the no. of tables keep growing in the database.

Due to too many joins in a query, the performance is also getting bad.

So, our team is just thinking putting the whole shopping cart from relational
model to non-relational model like storing in XMLType CLOB based approach. This
way, when we retrieve the shopping cart, the query does not involve lot of
joins.

Yes, we do store Order related essential information in other admin tables for
tracking purpose.

By the way, later we may do change/disconnect/modify/supp activities on the
shopping cart orders.

Could you advice, what would be your approach using this CLOB based model
instead of relational model.

Could you point out any performance/issues if any in CLOB model approach.

Thanks for your time and suggestions.

      and we said...
databases WERE BORN TO JOIN
totally
100%
it is what they do best.

when designed properly.   I cannot imagine why a "shopping cart" would be hard
to query.  You should use simple structures for simple things and a shopping
cart just sounds pretty darn "simple".

I've been know to say "normalization is overrated" and perhaps you've taken it
to an extreme.  a shopping cart is a temporary, short lived thing.  no reason
why you would not (should not) COPY the functionally dependent data into it when
someone says "I want to buy this".  I mean - what are the chances the dependent
data will change (some where between negative infinity and zero i think and even
if it does -- you'll find it out when they go to check out).

It is basically what you propose but in an ugly XML document.  Just use a nice
fat row.  It is OK, you don't have to go to the 30th normal form for everything

Only publishing this cause I think there will be two people coming to say "there
he goes again with overrated and normalization" :)

Given the amount of data I have here in this question -- thats all I can say. I
cannot IMAGINE why a shopping cart would impose such a strain on a system (maybe
you can elaborate...)
Review & Followup

      Rating: 3
There he goes
      again with overrated and normalization!  October 13, 2003

Reviewer:  Tim Hall  from UK
There he goes again with overrated and normalization!

Just thought I'd be the first :)

      Rating: 3
Normalization
      is a good thing  October 13, 2003
Reviewer:
      Mike  from Minnesota
I've had the opportunity to design 3 pretty complex databases over the last 15
years.  I always start by
having them be completely normalized.
Add to that proper definitions of primary keys, foreign keys, and the database
functions very well.
The key to me is doing proper load testing.  You will then
find out where bottle necks and performance problems occur
where there is alot of data.  Then, I denormalize in the
few cases where it is necessary for better performance.  This
approach has always worked well for me and I'll continue
to use it in the future...

          Followup:

so, you in fact are saying "yes tom, I agree.  denormalization makes sense in
certain cases - we don't have to always have a physical structure that maps to
our pretty diagrams.  sometimes performance is more important then a perfect
logical model".....

      Rating:
      3
Thanks  October 13, 2003
Reviewer:
      A reader

October 13, 2003 -- Thanks for the question regarding "Stored
      Procedure", version 817

      You Asked
Hi Tom
I have a question on a stored procedure Im trying to create as follows . I have
tables x,y,z .
My procedure deletes all the tables and then populates each of them as follows:

Create or Replace procedure Ins as
Begin
Delete from x;
Insert into x
select * from tmp;

Delete from y;
Insert into y
select * from tmp2;

Delete from z;
Insert into z
select * from tmp3;

end;

Could you please help me in answering the following questions.

1) How should I error handle if I get errors at each insert stmt. Would the
procedure fail at the first error or just give me an error and proceed to the
other inserts. I have seen an error at the 3rd insert, the other 2 inserts run
with success.

2) How should I incorporate the Exception handling, what kind of exceptions
should I take care in this code. I have added :
EXCEPTION
When Others Then
ErrMsg:=SQLERRM;
RAISE_APPLICATION_ERROR (-20016,ErrMsg);
END;
Is this good enough?
3) Should I put Commit statements after each Insert?

Another question on dbms_job:

Is there any way I can put the job scheduling statement in a script and when my
user runs it the script asks him at run time the time he wants to schedule
it.Can I do the following:

dbms_job.submit(:jobno, 'package_name;', &a,&b)

But then how would I interpret his values as following if he enters 12:00am in
the following stmt.

dbms_job.submit(:jobno, 'package_name;',
trunc(sysdate) + 1 + 0/24, 'trunc(sysdate) + 1 + 0/24');

How can I interpret the trunc(sysdate) + 1 + 0/24 AND
'trunc(sysdate) + 1 + 0/24' in the above stmt

Thanks

      and we said...
If you want to make sure all three succeed or fail (regardless of what the
CALLER does), you should use a savepoint.  To understand what I mean and why I
say that -- a simple example will help.  You might have to read this two or
three times to appreciate it (and if you are interested, I have more examples
and more detail in my book in the chapter on transactions).  We'll start with
the tables:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table x ( a int );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table y ( a int );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table z ( a int check (a<0) );
Table created.

I put a check constraint on Z so the insert into Z will always fail in my
examples...

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table tmp as select 1 a from dual;
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table tmp2 as select 2 a from dual;
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table tmp3 as select 3 a from dual;
Table created.

tmp, tmp2, tmp3 each have 1 row.  Now we do your procedure as it exists:

ops$tkyte@ORA817DEV.US.ORACLE.COM> Create or Replace procedure Ins
  2  as
  3  Begin
  4      Delete from x;
  5      Insert into x
  6      select * from tmp;
  7
  8      Delete from y;
  9      Insert into y
 10      select * from tmp2;
 11
 12      Delete from z;
 13      Insert into z
 14      select * from tmp3;
 15  end;
 16  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec ins
BEGIN ins; END;

*
ERROR at line 1:
ORA-02290: check constraint (OPS$TKYTE.SYS_C00873) violated
ORA-06512: at "OPS$TKYTE.INS", line 13
ORA-06512: at line 1

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from
  2  ( select * from x union all select * from y union all select * from z );

  COUNT(*)
----------
         0

that shows if the statement is executed AND the error is not handled (allowed
to propagate back to the client) -- NONE of the inserts "sticks".  That is
because Oracle treated that statement (the procedure call) as an ATOMIC
operation.  It silently executed the code like this:

    begin
        savepoint X;
        ins;
    exception
         when others then rollback to savepoint X; RAISE;
    end;

so, when ins failed -- it rolled back ALL work done by ins (but only that work
done by INS -- any prior outstanding work is NOT rolled back!!

Now, how could someone cause the outcome to be different?  easy:

ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2      ins;
  3  exception
  4      when others then NULL;
  5  end;
  6  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from
  2  ( select * from x union all select * from y union all select * from z );

  COUNT(*)
----------
         2

See -- here the first two inserts "stuck" (they are not committed yet, a
rollback makes them go away as we see below but they are there).  The third
insert that failed -- its not there.

This happened because as far as Oracle was concerned INS did not fail this time.
 The caller caught and ignored the error!!.  So, lets rollback and see how we
can prevent this behavior.

ops$tkyte@ORA817DEV.US.ORACLE.COM> rollback;
Rollback complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from
  2  ( select * from x union all select * from y union all select * from z );

  COUNT(*)
----------
         0

ops$tkyte@ORA817DEV.US.ORACLE.COM> Create or Replace procedure Ins
  2  as
  3  Begin
  4      SAVEPOINT ins_sp;
  5
  6      Delete from x;
  7      Insert into x
  8      select * from tmp;
  9
 10      Delete from y;
 11      Insert into y
 12      select * from tmp2;
 13
 14      Delete from z;
 15      Insert into z
 16      select * from tmp3;
 17  exception
 18      when others then
 19          rollback to savepoint ins_sp;
 20          RAISE;
 21  end;
 22  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec ins
BEGIN ins; END;

*
ERROR at line 1:
ORA-02290: check constraint (OPS$TKYTE.SYS_C00873) violated
ORA-06512: at "OPS$TKYTE.INS", line 20
ORA-06512: at line 1

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from
  2  ( select * from x union all select * from y union all select * from z );

  COUNT(*)
----------
         0

here the behavior is of course unchanged (the line the error appears to be on
is different that is all -- its coming from the exception block now -- not the
line with the insert....

More importantly -- the other example:

ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2      ins;
  3  exception
  4      when others then NULL;
  5  end;
  6  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from
  2  ( select * from x union all select * from y union all select * from z );

  COUNT(*)
----------
         0

doesn't have the inserts either!  Now, any prior outstanding work
(inserts/updates/deletes you did before the call to INS that had yet to be
committed) would still be pending -- we did not roll them back -- we only rolled
back the work done in INS....

2) see above

3) NO, never, don't do it.  You should commit when your business transaction is
DONE.  Never commit before that.  Commit when your logical unit of work is
complete and not a second before  (again, I beat this topic to death in my
chapter on transactions with more examples...)

As for the DBMS_JOB question -- you would have to write a routine that took 12am
and figured out what that meant and then it would call dbms_job to schedule the
job.  That is, write a procedure "schedule_job( p_what, p_when )"

you would put into your script then:

exec schedule_job( 'package_name;', '&a' )

they would enter 12am -- your procedure schedule_job would get the string '12am'
and would figure out using logic you write that that means "trunc(sysdate)+1"
and call dbms_job appropriately.

Review &
      Followup

      Rating:
      3
  August 31, 2001
Reviewer:
      A reader
Tom
So where should I put the commit. At the end, above the transaction?

          Followup:

My personally -- i like to let the CALLER do the commit.  What if one day --
your INS procedure becomes part of a larger transaction??  In that case --
calling you would prematurely commit their transaction.

I believe transaction control should be in the hands of the caller.  Your call
would be something like:

begin
   ins;
   commit;
end;

      Rating:
      3
  August 31, 2001
Reviewer:
      A reader
Hi Tom
Can I put in the Exception block a method to record the error msg in a table if
my proc fails as follows:

EXCEPTION
  When Others Then
 ErrMsg:=SQLERRM;
  RAISE_APPLICATION_ERROR (-20016,ErrMsg);
  insert into log(dt,msg)
  values(sysdate,errmsg);
End;

But When the procedure fails it does not get recorded in the log table. what am
I doing wrong.
Thanks

          Followup:

Well, in your example -- the insert it never gotten to! you raise application
error before you get there.

You can only do this if you have Oracle8i or up -- you need an autonomous
transaction to do this properly.  You would code:

create procedure log_error( p_msg in varchar2 )
as
   pragma AUTONOMOUS_TRANSACTION;
begin
   insert into log ( dt, msg ) values ( sysdate, p_msg );
   COMMIT; -- commits only the work in this procedure!!!

   raise_application_error( -20016, p_msg );
end;
/

and your exception block would look like:

exception
   when others then log_error( sqlerrm );
end;

See
http://osi.oracle.com/~tkyte/autonomous/index.html
for info on autonomous transactions (and I have a bigger chapter on
this in my book as well)

      Rating:
      4
  August 31, 2001
Reviewer:
      A reader
When a procedure failes it gives a big error message
including the line in the procedure where it failed .

 begin
*
ERROR at line 1:
ORA-20016: Invalid number inserted in table
ORA-06512: at "ls.Proc", line 44
ORA-06512: at line 2

Is there a way to just show them the
ORA-20016: Invalid number inserted in table

which I raise as follows:

exception
   when others then
if (SQLCODE = -1722) then
 RAISE_APPLICATION_ERROR (-20016,'Invalid number inserted in table');
 end if;
end;

          Followup:

The client would be responsible for doing that.  For example, in sqlplus you
could:

begin
   ls.proc;
exception
   when others then
      rollback;
      dbms_output.put_line( 'ERROR: ' || sqlerrm );
end;

      Rating: 4
While on the
      topic...  August 31, 2001
Reviewer:  Andrew
       from USA
Sometimes you want more error messages to get to the underlying cause. If so use
"TRUE" as below.

SQL> declare
  2  x number;
  3  begin
  4  x := to_number('abc');
  5  exception
  6  when others then
  7  raise_application_error(-20501, 'Whoops - anonomous PL/SQL failed!',
FALSE);
  8  end;
  9  /
declare
*
ERROR at line 1:
ORA-20501: Whoops - anonomous PL/SQL failed!
ORA-06512: at line 7

SQL> declare
  2  x number;
  3  begin
  4  x := to_number('abc');
  5  exception
  6  when others then
  7  raise_application_error(-20501, 'Whoops - anonomous PL/SQL failed!', TRUE);
  8  end;
  9  /
declare
*
ERROR at line 1:
ORA-20501: Whoops - anonomous PL/SQL failed!
ORA-06512: at line 7
ORA-06502: PL/SQL: numeric or value error

SQL>

      Rating: 5
explanation was
      good  May 24, 2003
Reviewer:  srinivasa rao
      bachina  from Bangalore
Hi tom
one more doubt i am having regarding autonomous transactions
my requirement is
i am selecting a row with FOR UPDATE,and processing that row and creating some
rows in the other tables,if any error comes during this process ,i have to
update stauts field of the selected record (selected earlier with lock) then i
will role back all the changes.for this i created a procedure to update the
field ,as an autonomous transaction and giving the commit in that.if i start
processing the row i am getting ora err -60.if i comment the FOR UPDATE clause
it is working fine...
Can u Help me

          Followup:

the proper way to do that is:

   select for update the row
   SAVEPOINT FOO;
   process......
   on error
      ROLLBACK TO FOO;
      update locked row
      COMMIT;
   on success
      whatever

      Rating: 5
very good
      solution  May 27, 2003
Reviewer:  srinivasa rao
      bachina  from bangalore
so in that case there is no need of using atx(autonomous transaction,right?

          Followup:

an autonomous transaction would be totally inappropriate here, yes.

      Rating: 5
Thank You for u
      r quick response  May 27, 2003
Reviewer:
      srinivasa rao bachina  from bangalore
One more problem i am getting
  select rows with update(table a)
    process----->
in process: validating the records(tab a) if any error i am inserting into
error_log table other wise i will process the records and create records (in
b,c).i am having more than 10 validations.i may get that many erros all those i
have to insert into error_log irrespective of the result of processing.so , i
created the atx for creating the error_log transctions and given commit in
that..

 if i run first time i got 3 errors, first record is inserted into log  but the
2,3 are not inserted and the error is agian ORA-00060: deadlock detected while
waiting for resource.if i run second time 2 and 3 records are inserting and for
first one same error is comming.

what is the problem with the method?
waiting for your Response

          Followup:

why commit?

process should be:

   open cursor;
   loop
      fetch bulk collect 100 records
      for i in 1 .. fetched
      loop
           if validated move into valid array
           else         move into error array;
      end loop
      forall i in 1 .. valid array count insert into t1;
      forall i in 1 .. error array count insert into t2;
   end loop;
   commit;

I don't see the point or logic in commiting error records independent.  If you
have a serious error that prevents you from getting to the place where you
SHOULD commit -- you'll reprocess those records again later anyway.

      Rating:
      4
  June 18, 2003
Reviewer:
      A reader
Hi tom,
I wanted to know, if I create a stored procedure with only one select statement,
then executing a stored procedure or executing that select statement in SQL*PLUS
is more useful.

          Followup:

i don't know, "is more useful" then what?

      Rating: 4
Used this
      method, but another problem  June 20, 2003

Reviewer:  Riyaz  from Chennai, South India

I used log_error procedure in my application to trap the error.(added one more
column for storing option name). But now i have another 2 problems.

1. Whenever the user (seesion) gets this error, it is going into log table.
Hence log table is getting accessed all the time, the transaction is taking MORE
time to savd. How to resolve this problem?

2. I am using GUI application, I want to show full text message for the oracle
thrown errors, hence what logic I can use to fetch the error from log table and
show it to the user from Front end application (ex VB). (If I am throwing the
exception,  truncated messages shown - full message not shown)
(meaning that, if i have trigger a, that calls proc b, proc b calls proc b1. In
this case, if proc b1 throws exception, proc-b throws, then finally trigger a
throws full error msg, which is "truncated one", ie "not full", hence difficult
to fix the error by reading half message).

Now suggest me the good method to resolve the above issues.

          Followup:

1) i don't know, i don't even know what that problem is...

2) i'm not a vb programmer, I don't have the docs, but I would look in there and
see what apis they have.  We are not truncating the error message, they are.

      Rating: 4
ATX or not in
      logging error  June 20, 2003
Reviewer:
      Henry  from New York, USA
Tom, you said:

> I don't see the point or logic in commiting error records independent.  If you

> have a serious error that prevents you from getting to the place where you
> SHOULD commit -- you'll reprocess those records again later anyway.

Assume that the original intention is to log any validation
failure no matter whether subsequent processing succeeds or
not, which makes sense if the business requirements want to
log the failed attempts on some particular operations.

With the solution you gave, if the processing of one record
fails, no prior validation failures will be logged.
Seems to me ATX would be needed.  Actually I remember you
used error logging as example for ATX.

So please explain why you think ATX is not appriopriate
for this.  Is it just an overkill or simply incorrect?

Thanks!

          Followup:

In a data load, I frankly do not see the point -- no.  You'll simply reprocess
those later.

the example I gave was auditing that cannot be rolled back for example -- in a
TRIGGER audit that someone tried to insert "such and such data" -- commit that
and then raise an error that causes the insert to "un-happen".

But for logging validation records during a data load?  nope, I don't see the
value or benefit or need.

      Rating: 5
Yes. It is a
      limitation in the GUI application  June 21, 2003

Reviewer:  Riyaz  from Chennai, India
Yes Tom. You are right. The limitation is in "Front End" application. Because of
that only, errors are getting truncaated.

      Rating: 5
Is there a
      better way?  October 13, 2003
Reviewer:  Thaha
      Hussain  from Manama, BAH
Dear Tom,

Please see this:-

CREATE OR REPLACE FUNCTION -----
----------------------
-------------------------
------------

SELECT COUNT(*) INTO v_count FROM Pre_requisites WHERE
    for_subject_id = v_subject_id;
  IF (v_count= 0) THEN
   RETURN 1;
  END IF;

----------------
-----------------

Can we Return 1 without using a variable(v_count)? I mean the condition in IF
statement itself ;-)

Or in other words,
IF (there is now row satisfying the condition) THEN ---> RETURN 1

Regards,
Thaha Hussain

          Followup:

why are you doing this ???? why why why?

I hate the "see if a row exists and then do something" logic.  what a waste of
cpu cycles.

so so so much better to "do something and if nothing exists upon which to do
something, then return"

So, there is a better way.  don't do it in the first place.

      Rating: 4
which is best
      way, then??  October 13, 2003
Reviewer:  Ashiq
      Shamsudeen A  from Trichy, Tamil Nadu, India
Tom,

Your quote " do something and if nothing exists upon which to do something, then
return"

  There some conditions checks ,we've to do before returning some values or
inserting into a tables.

  In this case,what is the best way you're following.

          Followup:

in that case -- i'm not following what you are saying.

October 13, 2003 Manoj -- Thanks for the question regarding
      "Materialized View", version 8.1.6

      You Asked
Sir,

I have one doubt regarding Materialized view & Snapshot.

Is there any difference between them?.

Which is preferable & why.

Thanks in advance.

Regards

Manoj

      and we said...
They are the same.  A materialized view is a greatly enhanced snapshot.  When
they added features like query rewrite, on commit refresh and such -- they
renamed snapshots.
Review & Followup

      Rating: 4
ON COMMIT
      refreshes  June 03, 2002
Reviewer:  Andre
       from Brazil
Thanks for helping me on my question in review:
http://asktom.oracle.com/pls/ask/f?p=4950:8:889881::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:547622750348,%7Bvoting%7D
about MVs.

You pointed out ON COMMIT refreshes are valid for join conditions only in the
where clause. But it didn't work even so. I have two join conditions. I created
mv logs on both AVISO and HISTORICO with rowid.

These are the base tables:
==========================

MOD_REDE@UNI2OPE> desc AVISO
 Nome                                      Nulo?    Tipo
 ----------------------------------------- -------- --------------
 ID                                        NOT NULL NUMBER(11)
 REDE_AFETADA                                       VARCHAR2(4000)
 IDENTIFICACAO_AFETADA                              VARCHAR2(4000)
 LOCAL_AFETADO                                      VARCHAR2(4000)
 PROBLEMA                                           VARCHAR2(4000)
 ABRANGENCIA                                        VARCHAR2(4000)
 ID_HISTORICO_PRIMEIRO                              NUMBER(11)
 ID_HISTORICO_ULTIMO                                NUMBER(11)
 ID_HISTORICO_AVISO                                 NUMBER(11)

MOD_REDE@UNI2OPE> desc HISTORICO
 Nome                                      Nulo?    Tipo
 ----------------------------------------- -------- --------------
 ID                                        NOT NULL NUMBER(11)
 ID_TICKET                                          NUMBER(11)
 DATA_EVENTO                                        DATE
 STATUS                                             VARCHAR2(4000)
 CAUSA                                              VARCHAR2(4000)
 LOCAL_OFENSOR                                      VARCHAR2(4000)
 IDENTIFICACAO_OFENSOR                              VARCHAR2(4000)
 PREVISAO_RETORNO                                   NUMBER(11)
 OBSERVACAO                                         VARCHAR2(4000)
 RESPONSAVEL                                        VARCHAR2(4000)
 DATA_REGISTRO                                      DATE
 AVISO                                              NUMBER(1)
 ACAO                                               VARCHAR2(4000)

This is the MV I want:
======================

MOD_REDE@UNI2OPE> CREATE MATERIALIZED VIEW MV_TEST
  2  BUILD IMMEDIATE
  3  REFRESH FAST ON COMMIT
  4  AS
  5  SELECT
  6  AVISO.ID AS ID,
  7  HIST2.DATA_REGISTRO AS DATA_REGISTRO,
  8  HIST2.STATUS AS STATUS,
  9  AVISO.REDE_AFETADA AS REDE_AFETADA,
 10  AVISO.IDENTIFICACAO_AFETADA AS IDENTIFICACAO_AFETADA,
 11  AVISO.LOCAL_AFETADO AS LOCAL_AFETADO,
 12  HIST2.DATA_EVENTO AS DATA_EVENTO,
 13  AVISO.PROBLEMA AS PROBLEMA,
 14  HIST2.CAUSA AS CAUSA,
 15  HIST2.IDENTIFICACAO_OFENSOR AS IDENTIFICACAO_OFENSOR,
 16  HIST2.LOCAL_OFENSOR AS LOCAL_OFENSOR,
 17  AVISO.ABRANGENCIA AS ABRANGENCIA,
 18  HIST2.PREVISAO_RETORNO AS PREVISAO_RETORNO,
 19  HIST2.OBSERVACAO AS OBSERVACAO,
 20  HIST2.RESPONSAVEL AS RESPONSAVEL,
 21  HIST2.ACAO AS ACAO,
 22  HIST1.AVISO AS AVISO_PRIMEIRO,
 23  HIST2.AVISO AS AVISO,
 24  HIST2.ID ID_HISTORICO_ULTIMO,
 25  HIST1.ID ID_HISTORICO_PRIMEIRO,
 26  HIST1.DATA_EVENTO DATA_EVENTO_PRIMEIRO,
 27  AVISO.ROWID AVISO_RID, HIST1.ROWID HIST1_RID, HIST2.ROWID HIST3_RID
 28  FROM AVISO, HISTORICO HIST1, HISTORICO HIST2
 29  WHERE AVISO.ID_HISTORICO_PRIMEIRO = HIST1.ID and AVISO.ID_HISTORICO_ULTIMO
= HIST2.ID
 30  /
FROM AVISO, HISTORICO HIST1, HISTORICO HIST2
     *
ERRO na linha 28:
ORA-12054: cannot set the ON COMMIT refresh attribute for the materialized view

This is a test with ON DEMAND instead of ON COMMIT:
===================================================

MOD_REDE@UNI2OPE> CREATE MATERIALIZED VIEW MV_TEST
  2  BUILD IMMEDIATE
  3  REFRESH FAST ON DEMAND
  4  AS
  5  SELECT
  6  AVISO.ID AS ID,
  7  HIST2.DATA_REGISTRO AS DATA_REGISTRO,
  8  HIST2.STATUS AS STATUS,
  9  AVISO.REDE_AFETADA AS REDE_AFETADA,
 10  AVISO.IDENTIFICACAO_AFETADA AS IDENTIFICACAO_AFETADA,
 11  AVISO.LOCAL_AFETADO AS LOCAL_AFETADO,
 12  HIST2.DATA_EVENTO AS DATA_EVENTO,
 13  AVISO.PROBLEMA AS PROBLEMA,
 14  HIST2.CAUSA AS CAUSA,
 15  HIST2.IDENTIFICACAO_OFENSOR AS IDENTIFICACAO_OFENSOR,
 16  HIST2.LOCAL_OFENSOR AS LOCAL_OFENSOR,
 17  AVISO.ABRANGENCIA AS ABRANGENCIA,
 18  HIST2.PREVISAO_RETORNO AS PREVISAO_RETORNO,
 19  HIST2.OBSERVACAO AS OBSERVACAO,
 20  HIST2.RESPONSAVEL AS RESPONSAVEL,
 21  HIST2.ACAO AS ACAO,
 22  HIST1.AVISO AS AVISO_PRIMEIRO,
 23  HIST2.AVISO AS AVISO,
 24  HIST2.ID ID_HISTORICO_ULTIMO,
 25  HIST1.ID ID_HISTORICO_PRIMEIRO,
 26  HIST1.DATA_EVENTO DATA_EVENTO_PRIMEIRO,
 27  AVISO.ROWID AVISO_RID, HIST1.ROWID HIST1_RID, HIST2.ROWID HIST3_RID
 28  FROM AVISO, HISTORICO HIST1, HISTORICO HIST2
 29  WHERE AVISO.ID_HISTORICO_PRIMEIRO = HIST1.ID and AVISO.ID_HISTORICO_ULTIMO
= HIST2.ID
 30  /
FROM AVISO, HISTORICO HIST1, HISTORICO HIST2
     *
ERRO na linha 28:
ORA-12015: cannot create a fast refresh snapshot from a complex query

This is what I was able to get to work, with FORCE:
=======================================

MOD_REDE@UNI2OPE> CREATE MATERIALIZED VIEW MV_TEST
  2  BUILD IMMEDIATE
  3  REFRESH FORCE ON DEMAND
  4  AS
  5  SELECT
  6  AVISO.ID AS ID,
  7  HIST2.DATA_REGISTRO AS DATA_REGISTRO,
  8  HIST2.STATUS AS STATUS,
  9  AVISO.REDE_AFETADA AS REDE_AFETADA,
 10  AVISO.IDENTIFICACAO_AFETADA AS IDENTIFICACAO_AFETADA,
 11  AVISO.LOCAL_AFETADO AS LOCAL_AFETADO,
 12  HIST2.DATA_EVENTO AS DATA_EVENTO,
 13  AVISO.PROBLEMA AS PROBLEMA,
 14  HIST2.CAUSA AS CAUSA,
 15  HIST2.IDENTIFICACAO_OFENSOR AS IDENTIFICACAO_OFENSOR,
 16  HIST2.LOCAL_OFENSOR AS LOCAL_OFENSOR,
 17  AVISO.ABRANGENCIA AS ABRANGENCIA,
 18  HIST2.PREVISAO_RETORNO AS PREVISAO_RETORNO,
 19  HIST2.OBSERVACAO AS OBSERVACAO,
 20  HIST2.RESPONSAVEL AS RESPONSAVEL,
 21  HIST2.ACAO AS ACAO,
 22  HIST1.AVISO AS AVISO_PRIMEIRO,
 23  HIST2.AVISO AS AVISO,
 24  HIST2.ID ID_HISTORICO_ULTIMO,
 25  HIST1.ID ID_HISTORICO_PRIMEIRO,
 26  HIST1.DATA_EVENTO DATA_EVENTO_PRIMEIRO,
 27  AVISO.ROWID AVISO_RID, HIST1.ROWID HIST1_RID, HIST2.ROWID HIST3_RID
 28  FROM AVISO, HISTORICO HIST1, HISTORICO HIST2
 29  WHERE AVISO.ID_HISTORICO_PRIMEIRO = HIST1.ID and AVISO.ID_HISTORICO_ULTIMO
= HIST2.ID
 30  /

View materializada criada.

This last one doesn't serve my needs, because it seems FORCE is forcing a
complete, not a fast refresh. Besides it makes me use DBMS_MVIEW.REFRESH, whilst
what I really need is an ON COMMIT refresh.

Thanks a lot again !

          Followup:

You won't be able to refresh that incrementally --

you are joining  AVISO with HISTORICO two times  -- that is a complex view, we
cannot incrementally refresh a complex view.  A documentation bug regarding this
is filed (and it should be updated)

      Rating: 4
not able to
      drop mview  July 23, 2003
Reviewer:  umesh
       from blore india
ag312>drop
  2  materialized view sm_meddra_term_mapping;
drop
*
ERROR at line 1:
ORA-12003: snapshot "AG312"."SM_MEDDRA_TERM_MAPPING" does not exist

i try creating it again and dropping it becoz
system>drop user ag312 cascade;
drop user ag312 cascade
*
ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 1
ORA-12083: must use DROP MATERIALIZED VIEW to drop
"AG312"."SM_MEDDRA_TERM_MAPPING"

ag312>create materialized view sm_meddra_term_mapping
  2   build immediate
  3    refresh force on commit
  4      as
  5      select deptno,  count(*) sum_sal
  6      from dept
  7      group by deptno;
    from dept
         *
ERROR at line 6:
ORA-00955: name is already used by an existing object

i get this error
please advice

          Followup:

contact support please.

      Rating: 3
how to let
      oracle know when to use MV?  August 01, 2003

Reviewer:  jianhui  from DC
Tom,
i have a fact table like this
create table fact
 (tm date,
  value number)

also have materialized view defined like this

create materialized view mv_year_fact
BUILD IMMEDIATE
REFRESH ON COMMIT
ENABLE QUERY REWRITE
as
select trunc(tm,'YYYY') tm, sum(value)
from fact
group by trunc(tm,'YYYY')

When i use query 1)
select /*+ all_rows */
trunc(tm,'YYYY') tm, sum(value)
from fact
group by trunc(tm,'YYYY'),
the execution plan shows the mv_year_fact is used.

But when i use query 2)
select /*+ all_rows */
trunc(tm,'YYYY') tm, sum(value)
from fact
WHERE tm < to_date('20030101','YYYYMMDD')
group by trunc(tm,'YYYY'),
the execution plan shows the mv_year_fact is NOT used.

Why cant oracle use the MV in the query 2)? If i change the literal value
20030101 to 20030405, i can understand that the MV doesnot have enough
information to return the result. But since the literal value is 20030101, so
the Oracle should know MV can return the result, because the defination of MV
uses trunc(tm,'YYYY') which truncates time to the first day of the year. Same
problems happen when I define the MVs at the monthly, dayly, hourly basis.

I dont want to define extra columns like YEAR, MONTH, DAY, HOUR and then define
the hierachy of the dimensions, Is it possible to use trunc(tm,FORMAT) to do
this?

          Followup:

you are asking a bit too much don't you think?

the MV is built on trunc(tm,'YYYY')  -- think of that as if the MV was built on
f(x) -- some black box.

you'll have to use

where trunc(tm,'YYYY') < to_date( ..... )

ops$tkyte@ORA920> create table fact( tm date, value number, data char(50) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create materialized view fact_mv
  2  build immediate
  3  enable query rewrite
  4  as
  5  select trunc(tm,'yyyy') tm, sum(value)
  6    from fact
  7   group by trunc(tm,'yyyy')
  8  /

Materialized view created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_stats.set_table_stats( user, 'FACT', numrows =>
10000000, numblks => 1000000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec dbms_stats.set_table_stats( user, 'FACT_MV', numrows =>
100, numblks => 10 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select trunc(tm,'yyyy') tm, sum(value)
  2    from fact
  3   group by trunc(tm,'yyyy')
  4  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=100 Bytes=2200)
   1    0   TABLE ACCESS (FULL) OF 'FACT_MV' (Cost=4 Card=100 Bytes=2200)

ops$tkyte@ORA920> select trunc(tm,'yyyy') tm, sum(value)
  2    from fact
  3   where trunc(tm,'yyyy') < to_date( '20030101','YYYYMMDD' )
  4   group by trunc(tm,'yyyy')
  5  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=5 Bytes=110)
   1    0   TABLE ACCESS (FULL) OF 'FACT_MV' (Cost=4 Card=5 Bytes=110)

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace off

      Rating: 3
oops, i've got
      it worked  August 01, 2003
Reviewer:
      jianhui  from DC
changed the query to
where trunc(tm,format) < to_date(...),
and it worked.
Anyway, if you have better solutions like to share, i'll appreciate very much.

      Rating: 4
Cannot create
      materialized view !  August 22, 2003
Reviewer:
      Haratsaris, Alexandre  from Rio de Janeiro,RJ BRAZIL

Hi Tom,
I have this materialized view issue.

CREATE materialized view AR.RA_CUSTOMER_TRX_LINES_ALL_SNAP
 REFRESH COMPLETE WITH ROWID -- The aud table doesn´t have primary_key
 START WITH SYSDATE
 NEXT SYSDATE + 1
 AS  select * from ar.ra_customer_trx_lines_all_a aud
 where aud.audit_timestamp >= trunc(sysdate) - 30
 and  aud.audit_user_name not in
( select a.user_name
from                                              applsys.fnd_user a,
                           applsys.fnd_profile_options b,
         applsys.fnd_profile_option_values c
where                                                   a.user_id=c.level_value
and b.profile_option_id=c.profile_option_id
and b.profile_option_name = 'XFND_USER_AUDIT_PROFILE'
and c.profile_option_value = 'No' )

ORA-00942: table "applsys.fnd_profile_option_values" does not exist

It's strange ´cause the table, grants,synonyms and all other stuff is OK  as
well as the select statment is.
I know that fast refresh snapshots has problems with complex queries, but that´s
not the case.

Any idea ?
Thanks a lot !

          Followup:

if you "set role none", is "applsys.fnd_profile_option_values" still available?

      Rating: 4
Cannot create
      materialized view !  August 25, 2003
Reviewer:
      Haratsaris, Alexandre  from Rio de Janeiro,RJ BRAZIL

Yes, even if I set role to none I can select from table without problems :

SQL> set role none
SQL> select count(*) from applsys.fnd_profile_option_values;

COUNT(*)
----------
4338

I suspect that I cannot have snapshots without base table primary keys ( WITH
ROWID ) and subqueries together...am I wrong ?

          Followup:

well, you know what would be helpful here?

things like

o the error you are getting
o the command you are using
o a description of what fnd_profile_option_values is (i'm not an apps guy bear
in mind)

      Rating: 4
Cannot create
      materialized view !   August 26, 2003
Reviewer:
      Haratsaris, Alexandre  from Rio de Janeiro,RJ BRAZIL

OK, sorry by the lack of information.
Anyway, that's solved... it was privilege stuff, you know :

CREATE materialized view AR.RA_CUSTOMER_TRX_LINES_ALL_SNAP
 REFRESH COMPLETE WITH ROWID -- The aud table doesn´t have primary_key
 START WITH SYSDATE
 NEXT SYSDATE + 1
 AS  select * from ar.ra_customer_trx_lines_all_a aud
 where aud.audit_timestamp >= trunc(sysdate) - 30
 and  aud.audit_user_name not in
( select a.user_name
from                                              applsys.fnd_user a,

                           applsys.fnd_profile_options b,

         applsys.fnd_profile_option_values c
where                                                   a.user_id=c.level_value
and b.profile_option_id=c.profile_option_id
and b.profile_option_name = 'XFND_USER_AUDIT_PROFILE'
and c.profile_option_value = 'No' )

ORA-00942: table "applsys.fnd_profile_option_values" does not exist

It happens 'cause I'm using a dba to create the MV on another schema. That means
, the DBA has the grants on "applsys.fnd_profile_option_values" table ( that's
why I can query on it) but the AR USER (AR.RA_CUSTOMER_TRX_LINES_ALL_SNAP)
don't, so I can't be owner of something that someones gives to me if I'm a
outlaw ;-)! That's the obvious I couldn't see!

Thanks a lot Tom !

      Rating: 5
Can we create
      indexes on MVs?  October 13, 2003
Reviewer:
      A reader
to speed up the queries that go directly against the MV?
I think that is not usually the case perhaps - but..

Thank you for your great service!

          Followup:

absolutely you can -- 100%, yes.

      Rating: 5
awesome -
      thanx!  October 13, 2003
Reviewer:
      A reader

October 13, 2003 San -- Thanks for the question regarding
      "MATERIALIZED VIEW and Optimization", version 8.1.5.0.0

      You Asked
Hello Tom,
Greetings!!

Thanks for your previous help, I am getting confused with some
of conceptual thing:

1. As I know a materialized view can be used to replicate data
(If I am not correct make me correct), which was formerly
achieved by using the CREATE SNAPSHOT statement. And I thing
CREATE MATERIALIZED VIEW is a synonym for CREATE SNAPSHOT. Now
since still we have both option available in 8i, I am getting
confused here, when to use SNAPSHOT and when to use materialized
view and What is the difference (If any) between snapshot and
materialzed views?

2. I have seen one of your article, says we can use same
sequence for multiple tables. I know How to use sequence for one
table but do not know how to use same sequence for multiple
tables. Could you please explained how I can use same sequence
for multiple table.

3. I am trying to set optimizer mode for my database, but when I
tried:
svrmgr>show parameter optimizer_mode
NAME            TYPE            VALUE
------------------------------------------------------
OPTIMIZER_MODE        string            CHOOSE

a. I don't inderstand why I am getting this value though. Since
there is nothing specified for OPTIMIZER_MODE in init.ora.
b. Now If I want to set OPTIMIZER_MODE, do I need to just
specify OPTIMIZER_MODE in init.ora or I have to do anything
else.
c. Is there any big impact on database after changing
OPTIMIZER_MODE.

really appriciated your help.
Regards,
San

      and we said...
A snapshot = materialized view.  Consider them synonomous
(because they are).  They renamed them in 8i when they added
addition functionality such as "refresh on commit".

Since you select a sequence or insert it as a psuedo column AND
you can select/insert from/to any table -- it is simple to use
it for >1 table -- they are never tied to a table.

Simply "use" it in many inserts or user it in many triggers.
Now you are using it for many tables.

The DEFAULT optimizer mode is CHOOSE.  Thats why that shows up.

You shouldn't change the default -- it is perfect.  If all of
the tables in a query are NOT analyzed, the optimizer will be
rule based.  If any are analyzed, the optimizer will be cost
based.  So, if you want to use CBO, analyze.  If you want to use
RBO, delete the statistics.

To change it at the session level to play with it, simply:

alter session set optimizer_goal =
              choose|rule|first_rows|all_rows

You can test it in individual queries via hints, such as:

/*+  RULE */
/*+  ALL_ROWS */

and so on.

There can be a huge impact on the database if you change the
optimizer mode.  If you set it to first_rows and haven't
analyzed anything -- the optimizer won't have any data with
which to optimize queries.  My guess is performance would be
less then stellar in that case.
Review &
      Followup

      Rating: 5
Performance in
      materialized view  February 10, 2003
Reviewer:
      Juan Carlos Reyes  from Bolivia
Hi Tom
1)
I created the following materialized view, and I don't know if there is a way to
optimize it.
Without changing the ON COMMIT option.

But the problem is that when I executed a delete in a table related to this
materialized view It took 80 seconds in commit.

This tables are small.
The query used to create the materialized view
  take 3s in execute.
  8 s in fetch all records.
  The delete only affect less than 0.5% of the query.
  that has only ~31,000 records.

Do you see something wrong or something that could be optimized in this
materialized view, to increase speed of commit in deletes?
Thanks.

DELETE FROM TRANSAC
WHERE TRC_EMPRESAGESTION_GESTION||TRC_TRANSAC
IN
(
SELECT DTR_EMPRESAGESTION_GESTION||DTR_TRANSAC
FROM ADM.DEFTRANSAC_ME WHERE TRUNC(DTR_FECHA) = TO_DATE('02012002','DDMMYYYY'))
/
296 rows in 0,9 s.
COMMIT;
83.05 s.

CREATE MATERIALIZED VIEW "ADM"."CTAS_SALDOS_COMPROBACION"
CACHE
BUILD IMMEDIATE
REFRESH FORCE
ON COMMIT
AS
SELECT TRC_EMPRESAGESTION CSL_EMPRESAGESTION,
       TRC_EMPRESAGESTION_GESTION CSL_GESTION,
       TRUNC( DTR_FECHA ) CSL_FECHA,
       TRC_CUENTA CSL_CUENTA,
       CTS_TRATAMIENTO CSL_TRATAMIENTO,
       CTS_NIVEL CSL_NIVEL,
       CTS_SIGNO CSL_SIGNO,
       CTS_CUENTA_MAYOR CSL_CUENTA_MAYOR,
       SUM( CASE WHEN TRC_OPERACION = 'D' THEN TRC_MONTO_ORIGEN ELSE 0 END)
CSL_DEBITOS_OM ,
       SUM( CASE WHEN TRC_OPERACION = 'D' THEN 0 ELSE TRC_MONTO_ORIGEN END)
CSL_CREDITOS_OM,
       SUM( CASE WHEN TRC_OPERACION = 'D' THEN TRC_MONTO_BOB ELSE 0 END)
CSL_DEBITOS_BOB,
       SUM( CASE WHEN TRC_OPERACION = 'D' THEN 0 ELSE TRC_MONTO_BOB END)
CSL_CREDITOS_BOB,
       SUM( CASE WHEN TRC_OPERACION = 'D' THEN TRC_MONTO_USD ELSE 0 END)
CSL_DEBITOS_USD,
       SUM( CASE WHEN TRC_OPERACION = 'D' THEN 0 ELSE TRC_MONTO_USD END)
CSL_CREDITOS_USD,
       SUM( CASE WHEN CTS_MONEDA = 'DMV' THEN CASE WHEN TRC_OPERACION = 'D' THEN
TRC_MONTO_BOB ELSE 0 END ELSE 0 END) CSL_DEBITOS_MV,
       SUM( CASE WHEN CTS_MONEDA = 'DMV' THEN CASE WHEN TRC_OPERACION = 'D' THEN
0 ELSE TRC_MONTO_BOB END ELSE 0 END) CSL_CREDITOS_MV,
       SUM( CASE WHEN NOT CTS_MONEDA IN ( 'DMV','BOB') THEN CASE WHEN
TRC_OPERACION = 'D' THEN TRC_MONTO_BOB ELSE 0 END ELSE 0 END) CSL_DEBITOS_ME,
       SUM( CASE WHEN NOT CTS_MONEDA IN ( 'DMV','BOB') THEN CASE WHEN
TRC_OPERACION = 'D' THEN 0 ELSE TRC_MONTO_BOB END ELSE 0 END) CSL_CREDITOS_ME,
       SUM( CASE WHEN CTS_MONEDA = 'BOB' THEN CASE WHEN TRC_OPERACION = 'D' THEN
TRC_MONTO_BOB ELSE 0 END ELSE 0 END) CSL_DEBITOS_MN,
       SUM( CASE WHEN CTS_MONEDA = 'BOB' THEN CASE WHEN TRC_OPERACION = 'D' THEN
0 ELSE TRC_MONTO_BOB END ELSE 0 END) CSL_CREDITOS_MN
FROM ADM.CUENTAS_ME, ADM.TRANSAC_ME, ADM.DEFTRANSAC_ME
WHERE
    CTS_CUENTA                  = TRC_CUENTA
AND CTS_EMPRESAGESTION          = TRC_EMPRESAGESTION
AND CTS_EMPRESAGESTION_GESTION  = TRC_EMPRESAGESTION_GESTION
AND DTR_TRANSAC                 = TRC_TRANSAC
AND DTR_STATUS = '0'
AND DTR_EMPRESAGESTION          = TRC_EMPRESAGESTION
AND DTR_EMPRESAGESTION_GESTION  = TRC_EMPRESAGESTION_GESTION

GROUP BY
       TRC_EMPRESAGESTION ,
       TRC_EMPRESAGESTION_GESTION ,
       TRUNC( DTR_FECHA ) ,
       TRC_CUENTA ,
       CTS_TRATAMIENTO ,
       CTS_NIVEL ,
       CTS_SIGNO ,
       CTS_CUENTA_MAYOR
ORDER BY
       TRC_EMPRESAGESTION ,
       TRC_EMPRESAGESTION_GESTION ,
       TRUNC( DTR_FECHA ) ,
       TRC_CUENTA

/
BEGIN
   DBMS_STATS.GATHER_TABLE_STATS(
     ownname => 'ADM',
     tabname => 'CTAS_SALDOS_COMPROBACION');
END;

2)

Could you explain more clearly what the documentations try to says with this.

prebuilt_table_clause
The ON PREBUILT TABLE clause lets you register an existing table as a
preinitialized materialized view. This is particularly useful for registering
large
materialized views in a data warehousing environment. The table must have the
same name and be in the same schema as the resulting materialized view.
If the materialized view is dropped, the preexisting table reverts to its
identity as a
table.
Caution: This clause assumes that the table object reflects the
materialization of a subquery. Oracle Corporation strongly
recommends that you ensure that this assumption is true in order
to ensure that the materialized view correctly reflects the data in its
master tables.

:) Thanks

          Followup:

1) trace it and see what it is doing.

also why would you

where a||b in ( select c||d from t)

it should always be

where (a,b) in ( select c, d from t)

2) it is saying:

you can materialize the data into a table yourself (eg: create table as select
.... <your big query from above>...)

you can then turn your table into a MV

We "trust" that you have done everything correctly AND that the data is current.
 If not -- caveat emptor!

      Rating:
      5
  February 11, 2003
Reviewer:
      A reader
:) Thanks

      Rating: 5
caveat
      emptor!  June 30, 2003
Reviewer:
      A reader
What is caveat emptor? Is it same as

exception
 when others
  null;

Just kidding...!

      Rating: 4
"MATERIALIZED
      VIEW and Optimization", version 9.0.1  July 24, 2003

Reviewer:  Raja
Hi Tom,
We are using RBO.  However, we want to use materialized views (MVs) to summarize
a transaction table consisting of millions of rows.  Do you recommend doing
this.  If not, then please explain why?  Are there any implications of doing
this? Will the refreshes to the MVs be slower, query response slower....etc.

Thanks in advance!

          Followup:

not going to happen very effectively.

you'll have to query the MV directly.  no query rewrite with the brain dead RBO.

it is that the transparent attribute of MV's (that they act like INDEXES do
normally) will be 100% unavailable to you.

      Rating: 3
subquery in
      MV  October 13, 2003
Reviewer:  aish
       from tx
i want to create a MV like this
but iam getting error says subquery is not allowed here .

CREATE MATERIALIZED VIEW TreeLevel_MV
AS
SELECT os.UREFITEM, os.UOWNERREF,
(SELECT Count(*) FROM tab1 WHERE UREFITEM = os.UOWNERREF ) as TLevel
     FROM tab2 os

i tried to change the query as

CREATE MATERIALIZED VIEW TreeLevel_MV
AS
with cnt as
(SELECT Count(*) TLevel
              FROM tabl ow,tab2 os
             WHERE ow.UREFITEM = os.UOWNERREF)
SELECT os.UREFITEM, os.UOWNERREF,TLevel
             FROM tab2 os ,cnt

 but the results vary
 am i missing something in my modified query ?

          Followup:

this is just a "parent child" aggregate -- either of these would do it:

ops$tkyte@ORA920> create materialized view test
  2  as
  3  select dept.dname, dept.deptno, count(*) cnt
  4    from dept, emp
  5   where dept.deptno = emp.deptno(+)
  6   group by dept.deptno, dept.dname
  7  /

Materialized view created.

ops$tkyte@ORA920> drop materialized view test;

Materialized view dropped.

ops$tkyte@ORA920> create materialized view test
  2  as
  3  select dept.dname, dept.deptno, cnt
  4    from dept, (select deptno, count(*) cnt
  5                  from emp
  6                 group by deptno) emp
  7   where dept.deptno = emp.deptno(+)
  8  /

Materialized view created.

Only use the OUTER JOIN if necessary..

      Rating: 3
iam sorry
        October 13, 2003
Reviewer:  aish
       from tx
iam sorry .my question is totally misleading
there is only one table not two tables
instead of tab1 , tab2 it should be tab1
iam sorry again
CREATE MATERIALIZED VIEW TreeLevel_MV
AS
SELECT os.UREFITEM, os.UOWNERREF,
(SELECT Count(*) FROM tab1 WHERE UREFITEM = os.UOWNERREF ) as TLevel
     FROM tab1 os

          Followup:

see the second example -- join to an inline view grouped by UREFITEM

      Rating: 4
thanks
        October 14, 2003
Reviewer:  aish

October 13, 2003 Peter -- Thanks for the question regarding
      "Instrumentation overhead", version 9.2.0.3

      You Asked
Hi Tom,

I am curious, but how much overhead does all of Oracle's instrumentation add?

Of course, I realize that all that information is extrememly valuable and aids
in tuning the database, and in no way am I suggesting that it should ever be
remove.

Lets say you have an application that is totally at one with the database - that
is the database is perfectly tuned for this application.  And somehow all
Oracle's instrumentation code was temporarily disabled, how much faster would
the database engine run?

Thanks,
-Peter

      and we said...
I don't even go there.

I have an entire presentation (that can take from 1 hour to 3 days depending on
how long you are willing to listen) who's premise is

"auditing is not overhead
 Instrumentation adds NOTHING"

without v$ tables...
the event system....
sql_trace=true.....

we would be entirely and utterly lost, totally.

So, I propose there is 0% overhead, since overhead is something you can get rid
of and not lose any material, immediate benefit.

INSTRUMENTATION is vital.

Once upon a time ago, I had this piece of code.  It was a replacement for what
is now known as mod_plsql (mod_plsql incorporated it).  I let people download
it.  By the hundreds/thousands they did and they used it.

I had a bug or two in the code.

It was OK though, since 50% of the code was instrumentation.  -- Just turn on
"debugModules=all" I would say and email me the trace file.  I could debug
everything....  Never had to telnet into anyones site to debug.

So, I refuse to answer this on the grounds that I refuse to categorize it as
overhead.  It is totally necessary stuff -- sort of like anti-lock brakes,
catalytic converters, mufflers, air bags, etc on cars.

(oh, and if you set it off, it is skipped over, not very much "overhead" at all
when it isn't on)
Review & Followup

      Rating: 4
is there a URL
      for that presentation   June 16, 2003
Reviewer:
      umesh  from bangalore india
"I have an entire presentation (that can take from 1 hour to 3 days depending on

how long you are willing to listen) "

Thanks

          Followup:

bits and pieces are -- see the homepage, click on tidbits and at the bottom are
a series of presentations.

      Rating: 5
Nice
      Definition  June 16, 2003
Reviewer:  Jim

Tom
I liked this
"So, I propose there is 0% overhead, since overhead is something you can get rid
of and not lose any material, immediate benefit.  "

I think instrumentation is a bit like seat belts in a car.
There is some extra requirement of having to put it on
before driving, but you really need to have it on

      Rating: 4
My 2.5
      cents...  June 16, 2003
Reviewer:  Kashif

With the advent of the profiler in Oracle (DBMS_PROFILER), I wouldn't think
instrumentation in the code is as necessary as it used to be. The question then
becomes of how to best utilize the profiler for one's needs.

Kashif

          Followup:

nope, disagree vehemently.

instrumentation does many things -- debug (imagine if you will Oracle without
the event subsystem -- or the copious dumps upon an ora-600) for example...

and that gets what?  plsql.  now how about the other 14 tiers of software you
have glued together out there?

I use the profiler for fine tuning..
I rely on my instruemented code to tell me whats going on.

      Rating: 4
Instrumentation
      Overhead  June 16, 2003
Reviewer:  Raj
       from CT, USA
Tom,

Is that hourlong presentation available anywhere? I am interested in evaluating
different options on the techniques. I have some experience in implementing
instrumented code using  dbms_pipe. But a full presentation from you would be
good for my learning, and useful to convince the whole department to consider
it.

Thanks
Raj

          Followup:

it's the "whats wrong" one on the home page (6/17/2003) or
asktom.oracle.com/~tkyte sometime after that date.

      Rating: 3
A tad bit of a
      sweeping generalization  June 16, 2003
Reviewer:
      Jonas  from Berkeley, CA
Granted instrumentation is invaluable, but poor usage of instrumentation can
lead to poorly performing code. At a previous company I worked for I found that
almost 20% of the code executed was string concatenations in debug statements.
By adding a simple "if" check to each log statement, there was a significant
speed up:

// Excuse blatent psuedo-code here
if (Log.level(Log.DEBUG)) {
   Log.debug("Here's the object: " +
               someBeanWithALotOfProperties.toString());
}

Are there any gotchas like this within Oracle's tools?

          Followup:

If you do not use common sense in coding your applications, yes, they will
perform poorly.  I would hope any coder would find certain "efficiencies" in
that regards.  That is programming 101 sort of stuff.

      Rating:
      3
WhatsWrong  June 17, 2003
Reviewer:
      Helen  from UK
Anybody else having trouble running WhatsWrong.ppt.  Power point tries to open
it and then bombs without any errors.

Thanks.

          Followup:

I just downloaded and opened in ppt 2000, worked for me.  maybe a version issue?

      Rating:
      3
Helen...  June 17, 2003
Reviewer:
      Kashif  from Houston, TX
Try saving the target by right clicking on the link and selecting "Save Target
As". Once saved, you should be able to open the saved file... HTH.

Kashif

          Followup:

the file on the home page is a "tgz" file -- a TAR gzipped file.

winzip can deal with it.

      Rating: 5
Re Problem with
      WhatsWrong.ppt  June 18, 2003
Reviewer:
      Helen  from UK
Tom

PowerZip seems to be the problem.  When copy to unix, gunzip, tar extract, copy
back and all is fine.  Thanks.

      Rating: 1
where it
      is?  June 19, 2003
Reviewer:  A reader

> it's the "whats wrong" one on the home page (6/17/2003)
> or asktom.oracle.com/~tkyte sometime after that date.

I do not find this one... Is it missing or is some cache/proxy server making fun
on me :-(

          Followup:

page down -- it is there, has been there (on 6/19/2003)

look for:

Material from NOUG cruise The "whats wrong" slides from the NOUG cruise on June
11th are here:.....

      Rating: 5
Thanks - I got
      it!  June 19, 2003
Reviewer:  A reader

I was looking at
http://asktom.oracle.com/~tkyte/
page...

      Rating: 5
hi
      Tom  August 28, 2003
Reviewer:
      A reader
"if (Log.level(Log.DEBUG)) {
   Log.debug("Here's the object: " +
               someBeanWithALotOfProperties.toString());
}
"

Tom
That example is very relevant for Java certainly since
in Java strings are immutable and so string concatenation
is costly. How does this compare to plsql? In your instrumentation code - do you
generate the string to
be displayed conditionally? (It makes the code kinda
cumbersome - you have to have an if wherever
you are having a debug statement being used. Of course
if it is required there is no alternative.)

Also, how could I easilly measure this? (figure out
increase in memory - uga/pga wherever) with and
without an if - is that a reliable test?)

          Followup:

the strings would be built each time.

v$sesstat measures all things.

      Rating: 5
string cost in
      plsql  August 29, 2003
Reviewer:
      A reader
"the strings would be built each time.
v$sesstat measures all things.
"
Q1. You mean it is  like Java where strings are immutable
and so a = a + b => does not change a but creates a new object (with other
objects being created along the way)?

Q2. In your code, do you put an if around your debug.f's?

          Followup:

q1) correct, there would be a new temporary

q2) no, i do not.

      Rating:
      5
thanx!  August 29, 2003
Reviewer:
      A reader
"correct, there would be a new temporary"

Not to question your knowledge or anything - but how could I verify that (unless
it is documented or not)?

And i will try to benchmark the impact of plsql string concatenation sometime in
the near future.

Thanx!!!

          Followup:

umm, how else could it be.

 p( x || y );

there must be a temporary "foo" that is x||y.  it cannot modify X, it cannot
modify Y yet it must send a variable "foo" that is the result of x||y

      Rating:
      5
ok  August 29, 2003
Reviewer:
      A reader
"umm, how else could it be.
 p( x || y );
there must be a temporary "foo" that is x||y.  it cannot modify X, it cannot
modify Y yet it must send a variable "foo" that is the result of x||y"

Right:) I guess i was thinking more of the case

x := x || y.

In java the equivalent does not change the x and add y to
the end - it creates a 3rd, totally new x||y and assigns
it to the x. StringBuffer does it in a different way
(actually appends y to x in memory) (I am sure you know this)

My q was is that how it works in plsql? I do see that
it perhaps is not very relevant to the debug.f() statements though as pointed
out by you...

          Followup:

oh, there -- there wouldn't necessarily be a temporary created, it would just
write to X directly. it would not "new" a temporary in that case if x and y were
of the same types (no implicit temporary conversions and all).

      Rating: 5
plsql
      strings  August 29, 2003
Reviewer:
      A reader
"oh, there -- there wouldn't necessarily be a temporary created, it would just
write to X directly. it would not "new" a temporary in that case if x and y were

of the same types (no implicit temporary conversions and all).
"
thanx - actually - the above would impact the string generation- whenever there
is
debug.f ( a || b || c)

in plsql you would create only one temporary.

In java a + b + c would create more than one
intermediate strings.

So the fact that you can change a string in plsql
means that it is not as important to enclose the
debug.f() in quotes as in Java.

This is what I wanted to verify in plsql.
Will a+b+c only create one temporary (I think it would
but how can I verify that)

Thanx!

          Followup:

cannot think of a good way to "verify it" myself.

      Rating:
      5
thanx!  August 29, 2003
Reviewer:
      A reader
"cannot think of a good way to "verify it" myself. "
If I find one - I will post it here.

      Rating: 5
RE: How to
      verify it...  August 29, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
WARNING:

This is not a documented or supported activity.  I would not normally post
undocumented or unsupported techniques, but I was intrigued by this question.

I do not offer this is as a valid method for determining the answer to the
question at hand.  Nor am I sure my interpretation of the output is correct.  In
any case, I used event 10928 to enable pl/sql tracing and here are the results:

SQL> connect testuser
Enter password:
Connected.
SQL> variable a varchar2(1)
SQL> variable b varchar2(1)
SQL> variable c varchar2(1)
SQL> alter session set events '10928 trace name context forever, level 1';

Session altered.

SQL> begin
  2    :a := 'A';
  3    :b := 'B';
  4    :c := 'C';
  5    dbms_output.put_line(:a || :b || :c);
  6  end;
  7  /

PL/SQL procedure successfully completed.

SQL> alter session set events '10928 trace name context off';

Session altered.

SQL> disconnect

TRACE FILE CONTENTS:

[header snip]

*** 2003-08-29 12:39:03.000
*** SESSION ID:(10.256) 2003-08-29 12:39:03.000
Entry #1
00001: ENTER      196, 0
00007: INFR       DS[0]+44
  Frame Desc Version = 2, Size = 35
    # of bind proxies = 3
    TC_VCHARi(OUT): FP+8, d=FP+76, ubn(mxl)=1, (byte) CS_IMPLICIT
    TC_VCHARi(OUT): FP+28, d=FP+116, ubn(mxl)=2, (byte) CS_IMPLICIT
    TC_VCHARi(OUT): FP+48, d=FP+156, ubn(mxl)=3, (byte) CS_IMPLICIT
<source not available>
00012: MOVC       HS+0='A'V=, FP+8
00017: SBVAR      SQLT_CHR(1), 1, FP+8
<source not available>
00024: MOVC       HS+28='B'V=, FP+28
00029: SBVAR      SQLT_CHR(1), 2, FP+28
<source not available>
00036: MOVC       HS+56='C'V=, FP+48
00041: SBVAR      SQLT_CHR(1), 3, FP+48
<source not available>
00048: GBVAR      SQLT_CHR(1), 1, FP+8
00055: GBVAR      SQLT_CHR(1), 2, FP+28
00062: CONC3      FP+8, FP+28, FP+72
00069: GBVAR      SQLT_CHR(1), 3, FP+48
00076: CONC3      FP[72], FP+48, FP+68
00083: MOVA       FP[68], FP+4
00088: XCAL       4, 5

[content snip]

00093: RLSM
00094: RET

At line 00062 it appears that the concatenation of :a and :b is performed and
stored at offset FP+72.

At line 00076 it appears that the concatenation of :c with FP+72 is performed
then stored at offset FP+68.

It appears that the final result (FP+68) is then moved to offset FP+4.

Again, this could be a wholly incorrect interpretation, and is one reason why I
do not normally engage in undocumented activities.  Caveat Emptor.

- Mark

      Rating: 3
And in Java it
      is...  August 29, 2003
Reviewer:  Piotr
      Jarmuz  from Braunschweig, Germany
In case someone would like to know:

A Java class like this:

public class Test {
    public void foo() {
        String a="A";
        String b="B";
        String c="C";
        System.out.println(a+b+c);
    }
}

Compiled:
$> javac Test.java

And disassembled:
$> javap -c Test

Compiled from Test.java
public class Test extends java.lang.Object {
    public Test();
    public void foo();
}

Method Test()
   0 aload_0
   1 invokespecial #1 <Method java.lang.Object()>
   4 return

Method void foo()
   0 ldc #2 <String "A">
   2 astore_1
   3 ldc #3 <String "B">
   5 astore_2
   6 ldc #4 <String "C">
   8 astore_3
   9 getstatic #5 <Field java.io.PrintStream out>
  12 new #6 <Class java.lang.StringBuffer>
  15 dup
  16 invokespecial #7 <Method java.lang.StringBuffer()>
  19 aload_1
  20 invokevirtual #8 <Method java.lang.StringBuffer append(java.lang.String)>
  23 aload_2
  24 invokevirtual #8 <Method java.lang.StringBuffer append(java.lang.String)>
  27 aload_3
  28 invokevirtual #8 <Method java.lang.StringBuffer append(java.lang.String)>
  31 invokevirtual #9 <Method java.lang.String toString()>
  34 invokevirtual #10 <Method void println(java.lang.String)>
  37 return

------------------------------------------------------------
So it uses one temporary variable but it is not a String instance at all rather
StringBuffer (which is mutable). It is constructed via successive append() calls
and at the end of expression code creates one String instance via toString()
call which ultimately is passed onto System.out.println

It's Interesting to note how the compiler optimized (exchanged) calls to
StringBuffer.append() and push bytecode of System.out (class variable) before on
the stack, so that when it is ready computing temporary string it already has
instance (reference to it) of java.io.PrintStream on the stack. An then all that
is left is to call virtual println()

Regards

      Rating: 5
thanx Mark and
      Piotr  August 29, 2003
Reviewer:
      A reader
Mark,
you seem to imply that plsql perhaps creates more than one
intermediate variable (caveat emptor noted)

Piotr - what you say (with proof) perhaps seems to go against the commonly
accepted norm that multiple
intermediate strings would be created in this example.
It may also be that the compiler you use has this
optimization built in. Is it standard Sun's compiler?

Thanx guys!

      Rating: 3
Yes, it is a
      standard Sun compiler  August 29, 2003
Reviewer:
      Piotr Jarmuz  from Braunschweig, Germany
Since javac command is just a binary wrapper to embedded Java shared libs and
Sun's compiler is written in Java (just bunch of "normal " java classes found in
$JAVA_HONE/lib/tools.jar)

>$ java -version
java version "1.4.1_01"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1_01-b01)
Java HotSpot(TM) Client VM (build 1.4.1_01-b01, mixed mode)

Regards.

      Rating: 3
And I also
      tested it with gcj  August 29, 2003
Reviewer:
      Piotr Jarmuz  from Braunschweig, Germany
And if you have GNU gcj

Compile:

$> gcj -C Test.java

Decompile ditto...

But for this small class it produces EXACTLY the same bytecode (opcode for
opcode).

One thing I REALLY like about gcj is that it runs almost 2 (yes!!! 2) orders of
magnitude faster then Sun's compiler.

GNU:
$> time gcj -C Test.java
real    0m0.029s
user)   0m0.010s
sys     0m0.010s

Sun's
$> time javac Test.java
real    0m2.247s
user    0m1.130s
sys     0m0.020s

On a 1.7 GHz AMD Athlon with 1 GByte RAM

But it had problems with some complicated classes especially those with embedded
local and anonymous classes (but this might be already fixed I have a build from
April 2003).

It is basically compatible with Java 1.1 so not an appealing solution for
state-of-the-art killer-apps ;)

Regards.

      Rating: 5
Thanx
      Piotr!  August 29, 2003
Reviewer:
      A reader
Interesting comments!
Tom, so far the conclusion seems to be opposite of what
I thought.

1. pl/sql seems to be creating more than one temporary
   variables for concatenating a, b, c
2. the sun's standard compiler seems to be opimizing
   by using StringBuffer underneath.

Any comments?

Thanx and have a great labor day weekend - all of you!

      Rating: 3
How to switch
      on / off debugging  September 17, 2003
Reviewer:
      msc
Tom,

how do you switch on/off debugging? Do you programm on/off switch info
application and set some package variable? Or do you use some kind "remote"
debugger to monitor other session (using pipe,alert,locks,table for on/off
switch).

Usually I do not want that debugging code is switched on at prod server and
would use it seldom - but sometimes it would be more convinient to monitor
remotely the end user session [than to get dumb-user to switch on debugging].

      Rating: 3
??? Log4PLSQL
      ???  October 12, 2003
Reviewer:  j

hi tom,

yesterday I've got your new book (had to pay 1.3 times the amount in EURs that
one has to pay in USD in America!!!) and started reading chapter 1. Now I wonder
whether you didn't mention Log4PLSQL on purpose or not (
http://log4plsql.sourceforge.net/
.

I use Log4Perl and Log4J on client side and did just some small tests with
Log4PLSQL. Do you have any reasons to NOT USE that tool for database
instrumentation?

          Followup:

i guess because I do not use it?

i only write about that which I actually use, i've never used it, hence I did
not write about it.

It was not a "bash" at log4plsql, I've just never used log4plsql -- it could be
the greatest thing since sliced bread but if i haven't used it?

I don't have any reason not to use it other then "i just haven't had a use to
use it".

      Rating: 4
... and would
      you mind to give it a try?  October 13, 2003

Reviewer:  j.
(just to be curious)

          Followup:

well, the framework I use is 100% instrumented -- HTML DB (aka project marvel)

I currently have no logging needs above and beyond.

      Rating: 5
This thread is
      getting hijacked, but what the heck...  October 14, 2003

Reviewer:  Peter Tran  from Houston, TX USA

Piotr,

The SUN java compilers takes some time to "warm-up".  Go figure...

/cygdrive/d/prosrm/peter> time javac Test.java

real    0m2.721s
user    0m0.031s
sys     0m0.046s

/cygdrive/d/prosrm/peter> time gcj -C Test.java

real    0m0.285s
user    0m0.060s
sys     0m0.139s

/cygdrive/d/prosrm/peter> time gcj -C Test.java

real    0m0.114s
user    0m0.076s
sys     0m0.091s

/cygdrive/d/prosrm/peter> time javac Test.java

real    0m0.714s
user    0m0.015s
sys     0m0.031s

Yes, the SUN compiler is still slower but not as bad as your test initially
shows.

Like you said, it's nice to have a fast compiler but it's not very useful if it
doesn't support the latest JDK features and it's always going to fall behind in
the bug fixes.

Thanks,
-Peter

      Rating: 4
Hijacked by
      interesting :-)  October 16, 2003
Reviewer:
      Piotr Jarmuz  from Poznan, Poland
I too, have figured that out... (warm-up)

Yes, but I can see you use Cygwin so it must be windoze so it is slower than
under native Unix (Linux) where I had done my tests. And your SUN java is most
probably native windows while GNU gcj is Cygwin program.

Cygwin is a great software but it uses Posix emulation layer which slows things
down. So lets say it is not 2 orders of magnitude but "just" 1.

But there are ways to keep SUN compiler up and running all the time... Various
IDEs: Forte, JBuilder, Eclipse (I personally don't use them) or something like a
"local compilation server" which runs in the background and accepts "compilation
requests" from clients using say Unix domain sockets. Then it may work "really
fast" (fast in java parlance of course ;).

Regards,
Piotr

      Rating: 5
To Piotr and
      Mark  October 29, 2003
Reviewer:  Menon

"you seem to imply that plsql perhaps creates more than one
intermediate variable (caveat emptor noted)"

Confirmed that in 10G this problem (of creating intermediate strings) does not
exist and
has been optimized at the pl/sql compiler level (which
also has lots of other optimizations.)
(Asked an Oracle pl/sql compiler guy )

October 14, 2003 Simon -- Thanks for the question regarding
      "Exactly what triggers 'maximum open cursors exceeded' error ?", version
      7.3.4

      You Asked
Tom

My client has been getting the 'maximum open cursors exceeded' error message
upto three times a day on their production system.  The front end is a Progress
GUI linked to a 7.3 database.  Upon further investigation I noticed that

1) OPEN_CURSORS was set to 512

2) The v$sqlarea view was reporting many thousands of literals within SQL
commands.   All of these literals originated from our PL/SQL packages.

3) The Progress GUI was behaving in that it generated SQL on the server that
used bind variables in all cases.

To solve the problem I did the following :-

1) Increased OPEN_CURORS to 1024

2) Replaced all literal SQL statements with statements using bind variables.

This has solved the problem, we no longer have to reboot the server.

However I am unsure which of the above actions solved the problem, is the
problem still lurking because the number of OPEN_CURSORS has doubled, or has
removing all literal SQL statements solved the problem?

I guess I'm basically trying to understand what the "point of no return" is for
the "maximum open cursors exceeded" error message is - what limit is reached to
trigger it off.

Regards
Simon.

      and we said...
Well, getting rid of the literals is definitely a very good, positive thing
indeed.  Its the best thing you can do for your database!

In PLSQL, it will attempt to cache cursors in the hopes that you will reuse
again and again.  In 7.3 and before the way this was done is different from
later releases and at times could lead to a max open cursors even when you've
been careful.  The init.ora parameter (since obsoleted)

close_cached_open_cursors

would help mitigate this.

My guess is -- setting open_cursors to 1024 fixed it.  Since you must have been
using DBMS_SQL to generate the sql with literals and we won't cache those (maybe
you inadvertantly fixed a cursor leak as well?  by adding a
dbms_sql.close_cursor somewhere?) fixing the bind problem would not have
decreased the cursor usage.

You can query v$open_cursor by SID to see what queries are remaining opened
(ignore any static SQL from PLSQL routines -- its cached on purpose).
Review &
      Followup

      Rating: 4
what kind of
      cursors shown in v_$Open-Cursor view.?  April 03, 2003

Reviewer:  CSR  from India.
Tom,
"(ignore any static SQL from PLSQL routines -- its cached on purpose)."
What is meant by static SQL statements in your followup above.

My client is complaining that, they are getting "maximum cursors exceeded"
error.

At what scenarios, all the opened cursors gets closed.Will this closing of
opened cursors depends on time too.

My Database is Oracle: 8.1.7
Apllication Server: Tomcat.

Am sure at all places in java, they are closing resultsetsa and connections
properly.
At SP level we are not closing refcursors.Data extraction and closing of
ResultsSet is doing at Java level.

When I tried with the following example, I did not get the complete picture
about the 'Opened Cursors" issue.

Please look at the examples.

conn SWIPTEST2/SWIPTEST2

create or replace procedure sptest3 is
n1 NUMBER;
BEGIN
select count(*) into n1 from tab;
insert into test_tab values(32);
commit;
end ;
/

SQL>exec sptest3;

SQL> conn sys/sysdba

SQL>select sql_text from v_$open_cursor where user_name = 'SWIPTEST2' ;

SQL_TEXT
------------------------------------------------------------
 select /*+ CHOOSE */ a1.constraint_name,   c1.column_name,
INSERT INTO TEST_TAB VALUES ( 32  )
Select /*+ CHOOSE */ cols.column_name as Name, nullable,  da
Select /*+ CHOOSE */ object_name, object_type, status from
SELECT COUNT(*)   FROM TAB
BEGIN sptest3; END;
SELECT rowid, "SWPTEST2"."T_WIPS_INVOICE_CHARGE_CODE".* FRO

SQL>7 rows selected.

when I query the v_$open_cursor, it is giving the above results.

v_$open_cursor view will give all kind of cursor list(implcit, explicit,
refcursors).?

"INSERT INTO TEST_TAB VALUES ( 32  )" is this a static sql.?
In the v_$Opnen_Cursor count the above statement also counted.?
We set our open_cursors to 200. Is above statement also consuming a cursor from
the above 200 value.

Please give me your suggestion to get rid of this problem.

thanks in advance.

          Followup:

begin
   select count(*) into l_cnt from dual;  <<<== static sql
   execute immediate
   'select count(*) from dual' into l_cnt; <<<=== dynamic sql
end;
/

If, I had but $1 for everytime I've heard:

...
Am sure at all places in java, they are closing resultsetsa and connections
properly.....

ref cursors are closed by the client that fetched from them, so it is good that
plsql doesn't close them if java is doing the fetching...

suggestion:  you don't say what open-cursors is set to.  perhaps you do close
everything but -- it is actually set to small for the number of cursors you use
concurrently.

suggestion:  search for

v$open_cursor java

on this site.  add some code to the application to monitor its actual cursor
usage -- find out where you are leaking cursors in the application (if you are).

      Rating: 3
What is the
      ideal value for the Open_Cursor parameter.  April 03, 2003

Reviewer:  CSR  from India
Tom,
Thank you very much for your reply.

please let me know, what will be the recomended value for the open_cursor
parameter to avoid this kind of errors for the following environment.

Environment details:
Database:Oracle 8.1.7 on Solaris, Sun sparc.
App Server: Tomcat.
Drivers: Oracle 8i Thin.
In the production, 75 users will be accessing the application concurrently via
browser.

Currently client's production server is set with open_cursor = 200.
And he recomended us the cursor limit should not exceed 120 at any given point
in time. Since kernel process will be using some cursors. So he recomended the
above figure.

In considering the above scenarios, what could be the suggested /ideal value for
the open_cursor.

Will it degrades performance or leads to any db server crashes, If I run under
high open_cursor value. ?

Thanks in advance.

          Followup:

somewhere between 0 and 10,000

open cursor will allocate an array in the session space (smallish).

then, as cursors are needed, we'll grab 64 at a time (so the first cursor really
sets up for 64 of them, the 65th sets up for 128 and so on).

200 would be fine for most, i regularly run with 500/1000

      Rating: 4
Getting open
      cursors exceed with statements being closed?  July 09, 2003

Reviewer:  Doug  from CT, USA
Tom - during a small stress test where connection pooling makes use of about 10
connections to the database, the running scripts got a maximum open cursors
exceeded error.  I bumped it to 1000 and watched it.  There appeared to be a
small "leak" where the cursors just went up and up and up.   When I did a select
sql_text, count(1) from v$open_cursor where sid = 11 group by sql_text (the
maxed sid).. I saw that one statement dwarfed the others by about 600:1.  I had
the developers look to see if they are closing that prepared statement and they
said that yes.. they were.  Then they hand the connection back to the pool.
Any idea what else could be going on?  Would the lack of an explicit commit keep
the cursors from being handed back?  Thanks.

          Followup:

they have a cursor leak, they are NOT closing the cursor.  perhaps they have an
try catch block that flies over the close.

      Rating: 5
open
      cursors  October 13, 2003
Reviewer:  Reader

Tom, If I issue a select * from emp from my session, I use a cursor slot in the
pga that will cache my statement, hash  value for the statement and also a
pointer to shared pool to point to the execution plan for that statement. Is it
correct? Then, If I issue the same statement in my session, will it use the same
cursor slot in the pga or a different slot? Thanks.

          Followup:

fundementally correct -- when you open a cursor, we'll allocate a slot for it
and it'll be in your CGA which may or may not be in the PGA (dedicated vs shared
server configuration).

If you close that cursor -- and the slot is free, it may use the same slot again
but that would be by accident

there is also session cached cursors -- that simply changes the semantics of
"close" to "close but remember" -- leading to a softer soft parse next time
around (makes that reuse you talk of more real)

October 14, 2003 wallacel -- Thanks for the question regarding
      "wrap", version 8.1.6

      You Asked
Tom:

I saw your definition of wrap
-- overwriting data the system no longer needs
to perform a rollback with but that your batch job might need to

perform a consistent read.

but i don't know how oracle measure that, since rollback segment
is used in robin mode, it alway overwrite something, how
oracle know the one being overwritten might need to perform
a consistent read? if oracle knows that, why not allocate one
more block instead of overwrite since it will needed by some
batch job.

i run some following statement:
set transaction use transaction rbs1
begin
for i in 1..1000000 loop
insert into atable values (i);
end loop;
end;

then i
select extents,wraps from v$rollstat where usn=3; periodly,
each time extents and wraps add exactly same number;
let say for first query i got
296 for extents, 302 for wraps
next time i got
320 for extents, 326 for wraps,

looks like each time extents expands, it got one wraps.

i don't understand that. i think it didn't overwrite anything
just allocate more extents. why "wraps" increase as well?

      and we said...
When we wrap we are overwriting data the system no longer needs
to perform a rollback.  However, your BATCH job *might* need it
to perform a consistent read and since we overwrote it will get
an ORA-1555 -- this is the cause of the ORA-1555.

Oracle does not measure that -- that is the point.  Oracle does
not know that rollback it just overwrote might be needed for a
consistent read.  That is why it does not allocate a new extent
-- it has no idea that you might need that later.  That is why
we must size rollback correctly.

The WRAPS column indicate the number of times the rollback
segment wrapped to another extent to serve the transaction.  If
this number is significant then you need to increase the extent
size of the rollback segment.  WRAPS is sort of a "bad" name as
it implies a total wrap around but it is the measure of how many
times we SPANNED extents.

Review &
      Followup

      Rating: 5
Wrapping
      concept not clear  October 03, 2001
Reviewer:
      Andre Nasser  from Brazil
Tom, something that is not clear to me yet is:
In case a transaction "walks" completely around a RBS and returns to the first
extent in which it still "exists", and space can be used in this first extent
(maybe due to other transactions committing or rolling back), will Oracle
allocate a new extent even so ? (because in principle this extent is in use... )
Thanks

          Followup:

We will never wrap into a RBS extent that is in use by anyone, including
ourselves.

We would extend the rbs in this case since we are still using the first extent.

      Rating: 5
why oracle cant
      wrap extents  September 14, 2002
Reviewer:
      A reader
hi tom

I was reading concept huide about rollback segments and read something sort of
confusing.

In Concept guide 8.1.7 chapter 4 there is a example with figures of how rollback
segments works, it says that an extent can have more than one active transaction
but if the transactions has to extend it will never use an extent with active
transactions. So I guess basically is

Rollback segment extents can be used by several transactions
But if transactions has to extend it has to use a brand new extents with no
active transactions

My question is if I have 4 transactions going in extent 3, transaction 1 extends
to extent 4 which is free of transactions, if transaction 2 later on has to
extend will it able to use extent 4 since there is now an active transaction
(transaction 1)? Or it will has to acquire another brand new extent with no
active transaction?

Also is there any theory why when extending extents in RBS Oracle needs a
transaction-free extent when normally an extent can accomodate more than one
transaction?

          Followup:

I don't see that "but if the transactions has to extend it will never use an
extent with active transactions."

When a transaction needs more space in a rollback segment and the current extent
it was working on is full -- it'll look at the NEXT extent in the rollback
segment (think of the extents in an RBS like a circular buffer -- there is a
loop of them in a circle).

If the very NEXT extent is still being used, it'll allocate a NEW extent and
insert it into the "loop" at that point.  We cannot advance into an extent that
contains an active transaction -- just like we cannot advance into an online
redo log that is still protecting data in the buffer cache (gotta wait for that
checkpoint to complete before overwriting the redo entries)

It is not "if a transaction has to extend" but rather "a rollback segment WILL
allocate a new extent whenever the next extent in the circular chain of extents
it has, has an active transaction"

As for the question -- yes, transaction 2 will use extent 4.

It is not the "transaction" but the "rollback segment".  When a rollback segment
extends itself..... Not a transaction.

the "theory" is that we cannot overwrite/reuse that which we still need -- we
need the rollback generated by that active transaction.

      Rating: 4
what if next
      extent is 99% free?  September 14, 2002
Reviewer:
      A reader
Hi

If you said that we need to protect the rollback of an active transaction
condier this situation

transaction 1 wraps (needs an extra extent), it is currently in extent 1, if
extent 2 has an active transaction but very small transaction that barely uses
1% of extent size transaction cannot used extent 2?

it is a bit confusing because if extents can be used by several transactions at
same time why when a transaction wraps it cannot use next extent which has an
active transaction? What can go wrong? Cant Oracle just used the parts of that
extent 2 which is not used by the active transaction for my transaction 1?

I can understand if extent is full of information of the active transaction but
otherwise I dont see the point...

          Followup:

Correct -- rollback is used like a circular buffer.  We cannot wrap around into
an active extent -- just like online redo logs. there might be just one block in
the buffer cache protected by that redo log -- hence we cannot reused that redo
log (which is likewise a circular buffer) until we are no longer protecting that
block.

Rollback is not managed as a "heap" (which is hard and slow to manage) but
rather as a high speed circular buffer which is very easy to manage.

      Rating: 3
I still dont
      get it  September 15, 2002
Reviewer:
      A reader
Hi again Tom

if I have two transactions which needs to wrap cant they use the same next
extent? Since if first transactions that wraps to next extent then the other
wont be able to use it since it would be an extent with active transactions?

so I guess we better create RBS with small extent sizes to avoid wasting
space???

I dont see the point, I mean if my extent size is 1MB and one transaction only
uses 100K then it would be wasting 900K since no transaction can use it

          Followup:

*transactions* don't need to wrap.

Rollback segments need to wrap.

I think that is your conceptual issue, don't thing TRANSACTION, thing "rollback
segment" as an entity.

The rollback segment is servicing lots of people, it is the keeper of the space.
 The rollback segment is asked by SOMEONE "give me X blocks of space please".
The rollback segment looks at its circular queue (say it currently has 20
extents and it is in extent #20, the last one).  It finds there are less then X
blocks of space and hence has to advance into another extent.  If extent #1 has
NO ACTIVE work in it, the rollback segment will simply roll into extent #1 and
give that someone their space (and anyone else that asks for space will get it
from extent #1.

If on the other hand, extent #1 was still active -- the ROLLBACK SEGMENT (not
the transaction, not the somebody) will allocate a new extent #21 and add it
into the circle.  The rollback segment will advance into this new extent and
give that somebody and in fact anybody that requests space blocks from this
newly added extent #21.

Regardless of WHAT happens (advance into existing extent, advance into new
extent) MORE THAN ONE transaction can use it.

So, your example of 1mb extent with 900k "not used" is false.  the other 900k
would be available to any other transaction in the system cause the ROLLBACK
SEGMENT is managing the space, not the transaction!

      Rating: 4
blocking
      sessions  September 15, 2002
Reviewer:
      A reader
Hi

from your explanation

"If on the other hand, extent #1 was still active -- the ROLLBACK SEGMENT (not
the transaction, not the somebody) will allocate a new extent #21 and add it
into the circle.  The rollback segment will advance into this new extent and
give that somebody and in fact anybody that requests space blocks from this
newly added extent #21."

If extent #1 has only 100K (of 1MB) occupied we would still need extent #21??? I
dont understand why RBS has to extend if next extent has free space for more
transactions.

This is cause of blocking sessions from oracle university admin guide:

"When an extent in a rollback segment is full, the Oracle server attempts to
reuse the next extent in the segment. If this new extent contains one active
entry—that is, an
entry by a transaction that is still active—it cannot be used. In these cases, a
rollback segment allocates an additional extent. The transaction cannot skip an
extent in the ring and continue writing to a subsequent extent. A transaction
that has made only a few changes, but has been idle for a long time, could cause
rollback segments to grow
even though there are many free extents. In this situation, a lot of space is
wasted and a database administrator may need to intervene to avoid excessive
rollback segment
growth."

It is basically saying if one transaction (even it's tiny) is not committed or
rollback it can cause RBS to grow since the extent which the transaction's is
recorded cannot be used by others! And that's the thing I dont understand. Why
it cannot be used by others?

          Followup:

If extent #1 is being used by any active transaction, we cannot advance into it.
 Circular buffer concept here -- you cannot advance forward if something is in
the way.

Yes, an active transaction that is consuming 100k of 1m of extent #1 will
prevent us from advancing into it UNTIL that transction commits or rolls back.

It cannot be used by others because of the basic premise of a circular buffer.
We would get "stuck" because a rollback segment is just used in a big circle and
all of a sudden there is this "active set of blocks" in our way that we cannot
overwrite.

Managing a circular buffer is very fast, very efficient, requires very little
overhead (we only need remember were we are in the circle after all).

Managing a "heap" (which is what you would be asking for) is hard, it would be
less performant, it would be less concurrent due to that, it would inhibit
scalability.  It is not the way we do it.

      Rating: 2
Contradiction
      Detected, Pls verify answer....  September 18, 2002

Reviewer:  Robert Chin  from PA
Tom, don't mean to beat it to death, but I am reading contradictions in your
answers in this thread,
so just some clarifications (PLEASE!) :

A)This one line in followup to 9/14 review:

"... yes, transaction 2 will use extent 4"

Do you really mean it Tom ?

But Tran #1 - an ongoing active transaction, just spanned into Ext #4 as "A
Reader" describes and you keep stressing that "We will never wrap into a RBS
extent that is in use by anyone...", "We can not advance into an extent that
contains an active tran..."

If you really mean it, that RBS will let Tran #2 span into Ext #4,
If Yes, HOW does this fall in line with your statements I quited above ???

B) "If Ext #1 is being used by any active transaction, we cannot advance into
it. Circular buffer concept here...."(9/15)
Question: so RBS will NOT "span/advance/wrap" ANOTHER
EXISTING, ONGOING transaction into Ext #1, regardless of space availability, BUT
will stick a NEW transaction into Ext #1, Is my conclusion correct ?

          Followup:

read the review RIGHT above that one

*transactions* don't extend.

*rollback segments* extend.

when a ROLLBACK segment needs to get more space, to advance, it will look at the
next extent in its circle.  If no active transaction exist in there, the
ROLLBACK segment will advance into it (note: I have not said the transaction
will advance, the RBS will advance).  Now that the RBS has advanced into this
extent -- it is free to be used by ALL active transctions using that RBS.

Following your logic -- we could have but one transaction / RBS cause the first
time a transaction needed space -- you would call that an "extend" but it is
NOT.  It is a transaction requesting space from this thing we call an RBS.  The
RBS is the thing that extends, not the transaction.

I see nothing in conflict here.  Just drop all thoughts of "transactions"
extending -- they do not.  RBS's do.

An RBS manages space.  A transaction requests it.

An RBS manages space in a circular buffer.  As it moves through the extents, it
gives space to transactions to use.  When it has moved through all of the
extents it had allocated (1,2,3,4 in this example) and needs to move on out of
extent 4 -- it will first look at extent 1.  If extent 1 is not being used by
any transaction, the RBS (not the transactions) will move into extent 1 and
start giving out space as needed (to ANYONE that needs it).  if there was a
single active transaction in extent 1 the RBS will instead allocate extent 5,
add it to the circle, advance into it and start handing out space to anyone that
asks for it.

      Rating: 4
Still have
      small doubt  September 18, 2002
Reviewer:
      Robert  from Memphis, TN, USA
Tom,

2 points, please...

(1) I think I understand this 99% but aren't you being a little bit 'overly
precise' about the "*transactions* don't extend. *rollback segments* extend."
distinction. When we are saying the *transaction* extends, we are meaning the
transaction *invokes*, or *triggers*, or *causes* the rollback segment to
extend... if there were no transaction that needed more rollback space, the
rollback segment would not extend.

(2) If you will grant the above statement... will you please put to rest the
observation by Robert C. regarding your statement: "... yes, transaction 2 will
use extent 4"
Based on my understanding of your very good explanations transaction 2 would not
be using extent 4, since transaction 1 is active and is currently using extent
4. I understand that the RBS would extend to extent 5 and put transaction 2
there. Correct?

Thanks,

Robert

          Followup:

1) overly precise -- no, I was trying to get you away of thinking of the
transaction as the thing that extends into the new extent.  It does not (it is
the catalyst).  The RBS does.

2) ARG!  I don't know how to say it.

Transaction 2 is NOT extending the RBS into extent 4 or 1 or anything.  the RBS
is already *there*.  The transaction DOES NOT EXTEND, the RBS does and it
already did.

So no, i'm not being nearly precise.

Forget about transactions - just forget them.  Just thing RBS, extents and
whether an extent is still being used.  Just think those three things.

RBS has 4 extents.

RBS is currently idle.

Someone asks RBS for space, RBS is in extent 1.
Time goes on, others ask for space, RBS fills extent 1.
RBS now needs to leave extent 1, it looks at the next extent in the circle.
This is extent 2.  Extent 2 is totally "inactive", no one is using it for
anything.  RBS extends into extent 2.

The same happens over time for 3 and 4 (they were empty after all, we just
brough this RBS online for example).

NOW, extent 4 fills.  Someone asks the RBS for more space.  RBS looks in the
circle -- looking at extent 1.

Is extend 1 still used?  Yes -- then get a new extent 5 and put it in the circle
and advance into there.  No -- then use extent 1 and advance into there.

(there -- I explained it without transactions -- they are not meaningful here
for this discussion.  Think RBS, extents and "still being used, not being used"
period...

      Rating: 5
Got it !...it's
      that RBS "guy"  September 18, 2002
Reviewer:
      Robert C  from PA
Thanks Tom ! you'r patience is appreciated....
I got it, I just re-read your emphasis on RBS and image RBS being a person
pacing around in a "circle" of "KrispyKreme" shops, he skips those with people
still gorging in there and pops into one with no customer and start handing them
out free.....

      Rating: 4
Thanks!...
      but...  September 18, 2002
Reviewer:  Robert
       from Memphis
Tom,

Please, Are these true statements:

(1) The RBS will not extend into a extent that is currently being used by a
[uncommitted] transaction.
(2) Only one [uncommitted] transaction can be in an RBS extent at a time.

Thanks you for your patience and precision!

Robert.

          Followup:

1) true
2) very false

      Rating: 4
Oh, I can
      answer that.  September 18, 2002
Reviewer:
      Kevin  from Reston, VA
1. Yes.
2. No. In fact, Tom says:
"Regardless of WHAT happens (advance into existing extent, advance into new
extent) MORE THAN ONE transaction can use it."

      Rating: 5
Please clearify
      the definitions of the wraps and the extends of
      v$rollstat  September 18, 2002
Reviewer:  Mike
       from TX
Please clearify the definitions of wraps and extends of v$rollstat

Tom,
From yours:
"
An RBS manages space in a circular buffer.  As it moves through the extents, it
    gives space to transactions to use.  When it has moved through all of the
    extents it had allocated (1,2,3,4 in this example) and needs to move on out
of
    extent 4 -- it will first look at extent 1.  If extent 1 is not being used
by
    any transaction, the RBS (not the transactions) will move into extent 1 and
    start giving out space as needed (to ANYONE that needs it).  if there was a
    single active transaction in extent 1 the RBS will instead allocate extent
5,
    add it to the circle, advance into it and start handing out space to anyone
that
    asks for it.

"
a WAPS occured as ?
"
When it has moved through all of the
    extents it had allocated (1,2,3,4 in this example) and needs to move on out
of
    extent 4 -- it will first look at extent 1.  If extent 1 is not being used
by
    any transaction, the RBS (not the transactions) will move into extent 1 and
    start giving out space as needed (to ANYONE that needs it).
"

and an EXTENDS occured as?

"
if there was a
    single active transaction in extent 1 the RBS will instead allocate extent
5,
    add it to the circle, advance into it and start handing out space to anyone
that
    asks for it.
"

          Followup:

wrap = reuse extent 1, we wrap the circle around
extend = we couldn't wrap, so we added another extent to the circle.

      Rating: 5
also the
      definition of hwmsize (highwater marks) of
      v$rollstat  September 18, 2002
Reviewer:
      Mike  from TX

      Rating:
      5
TRANSACTIONS_PER_ROLLBACK_SEGMENT  September 19,
      2002
Reviewer:  Pasko  from Hamburg, Germany

Hi Tom,

Thanks for your Great Contribution to the Discussion above.

I would  just like to ask what effect does the init File parameter
"TRANSACTIONS_PER_ROLLBACK_SEGMENT" have on this circular Buffer .

Is it the max number of Transactions that a Rollback Segment can accomodate ?

In the init File Comment  , they say 'number of Active Transactions per Rollback
Segment.

Thanks in Advance.

          Followup:

Nothing, as the transaction would never get assigned to this rbs if it was
already at max transactions.

      Rating:
      4
  September 19, 2002
Reviewer:  Sagi
       from India
Hi Tom !

It was really very well explained.

I just wanted to summarize and see if my understanding with the above is
correct.

Say our extents are of 1mb

Say the RBS is currently using #4 extent (Used Sofar 600k space). At this point
User A's transaction requests for some space of 200k. Now RBS checks that still
there is space of 424K still in extent #4. So it gives User-A's transaction
extent #4. After a few Seconds User-B reqests for space of 500K for his
transactions and now since 224K is left in extent #4. At this point it checks to
see if extent #1.

extent #1 - No Active Transactions then allocate it.
extent #1 - Active Transaction (Could be using space 1k or greate does not
matter as RBS works in Circular Fasion), So extent #5 is created.

Now if any other user's transactions requests for more space same checks as
above. Right?

Q1. Is my understanding correct.
Q2. If there is active transactions in extent #1. and now since user-b requested
some space. Would it give 224K of extent #4 and 226K from extent #5 or extent
#5.

Thanx for your patience and explanation.

We all are luck to have you and this site.

Regards,
Sagi

          Followup:

q1) i think so
q2) it would fill extent 4 and then move into extent 5

      Rating: 5
I think I've
      got it...I'm I trying your patience yet? Thank
      You.  September 19, 2002
Reviewer:  Robert
       from Memphis, USA
Tom,

RBS has 4 extents
RBS allocates space in extent 1 for transaction A.
RBS allocates space in extent 1 for transaction B.
RBS allocates space in extent 1 for transaction C.
RBS needs to allocate space for transaction D.
    RBS sees extent 1 is fully allocated
    so RBS allocates space in extent 2 for transaction D.
RBS needs to allocate space for transaction E.
    RBS sees extent 2 is fully allocated
    so RBS allocates space in extent 3 for transaction E.
RBS needs to allocate space for transaction F.
    RBS sees extent 3 is fully allocated
    so RBS allocates space in extent 4 for transaction F.
Transactions A and C (in extent 1) commit.
RBS needs to allocate space for transaction G.
    RBS sees extent 4 is fully allocated
    RBS sees extent 1 has an active transaction (B).
    so RBS creates and allocates space in extent 5 for transaction G.
Transaction B (in extent 1) commits.
RBS needs to allocate space for transaction H.
    RBS sees extent 5 is fully allocated.
    RBS sees there are no active transactions in extent 1
    so RBS allocates space in extent 1 for transaction H.

Is this scenario accurate?

Thanks,

Robert.

          Followup:

yes (and would be just as accurate without any mention of "for transaction
A,...H"

      Rating: 5
One minor
      thing....  September 19, 2002
Reviewer:  Robert
      C  from PA
>>so RBS allocates space in extent 2 for transaction D.
>>so RBS allocates space in extent 3 for transaction E.
>>so RBS allocates space in extent 4 for transaction F.

Bob, your RBS forgot to check if those extents have active transactions before
diving in.....just splitting hairs ;)

      Rating: 5
Thanks
      Tom!  September 19, 2002
Reviewer:  Robert
       from Memphis, USA
Now I will go to the Oracle documentation and attempt to completely digest your
comment and previous emphasis

<quote>
(and would be just as accurate without any mention of "for transaction A,...H"
<quote>

...I sort of understand, but I want to fully understand this concept you are
emphasizing of the RBS not allocating space *for* the transaction.

[Yes, Robert C., I that was implied! ;)]

Thanks again,

Robert.

          Followup:

I'm just pointing out that the cause of misunderstanding here is the
"transaction" part.

The transaction is the catalyst, the "end user", the client -- the "thing that
wants space".

The RBS is the "server", the maker of space.

      Rating: 5
Will RBS keep
      growing  September 19, 2002
Reviewer:  Logan
      Palanisamy  from Santa Clara CA 95051
Tom,

Let's say,

We start with RBS1 having 4 initial extents.
Transactions fill up extenets 1 to 4.
One of the transactions that uses extent 1 doesn't commit for a long time.
All the transactions using extents 2 and 3 have committed.

Does it mean, the rollback segment will keep grabbing new extents 5, 6, 7 and so
on, till the transaction using extent 1 commits? Will it keep growing till it
reaches max extents or runs out of room, if one rogue transaction doesn't
release extent 1, even if all the other extents after extent 1 are freed up?

Thanks

          Followup:

Exactly.

      Rating: 5
How-to monitor
      wrap and do appropriate corrections?  October 30, 2002

Reviewer:  Yogeeraj  from Mauritius
hi,

<quote>
The WRAPS column indicate the number of times the rollback segment wrapped to
another extent to serve the transaction.  If this number is significant then you

need to increase the extent size of the rollback segment.  WRAPS is sort of a
"bad" name as it implies a total wrap around but it is the measure of how many
times we SPANNED extents.
</quote>

I have have gathered some statistics from my Oracle Database that will be soon
be moved to a server with more resources(disk, memory, cpu). I need to analyze
whether i  am currently having rollback segment problems. Please advise.

========================================================
SQL>
  1  select substr(owner,1,10) own, substr(segment_name,1,10) SEG,
sum(bytes/1024/1024) mb
  2    from dba_segments
  3   where tablespace_name = 'RBS'
  4* group by owner,segment_name

OWN        SEG                MB
---------- ---------- ----------
SYS        RBS0                4
SYS        RBS1                4
SYS        RBS10               4
SYS        RBS11               4
SYS        RBS12               4
SYS        RBS13               4
SYS        RBS14               4
SYS        RBS15               4
SYS        RBS16               4
SYS        RBS17               4
SYS        RBS18               4
SYS        RBS19               4
SYS        RBS2                4
SYS        RBS20               4
SYS        RBS21               4
SYS        RBS22               4
SYS        RBS23               4
SYS        RBS24               4
SYS        RBS25               4
SYS        RBS26               4
SYS        RBS27               4
SYS        RBS28               4
SYS        RBS3                4
SYS        RBS4                4
SYS        RBS5                4
SYS        RBS6                4
SYS        RBS7                4
SYS        RBS8                4
SYS        RBS9                4

29 rows selected.

USN        WRAPS
    16:00    17:35    Difference
0    0    0      0
1    58    58     0
2    46    48     2
3    45    46     1
4    52    53     1
5    62    64     2
6    54    55     1
7    39    39     0
8    45    46     1
9    46    47     1
10    63    63     0
11    39    40     1
12    101    102    1
13    57    58     1
14    51    51     0
15    57    57     0
16    45    45     0
17    45    46     1
18    44    46     2
19    67    67     0
20    35    35     0
21    40    41     1
22    47    48     1
23    47    48     1
24    42    42     0
25    46    47     1
26    49    50     1
27    61    61     0
28    78    79     1
29    46    46     0
====================================
Regards
Yogeeraj

      Rating:
      4
  March 12, 2003
Reviewer:  Mike
       from TX
Tom,
The database has been up 2 two days.
I have the WRAPS in 700 for each rollback segs (total 7), no shrinks. Do you
think I shall increase the extent?
Thanks

          Followup:

did you have any 1555's?

wraps are not bad, they are normal.

      Rating: 5
Snapshot too
      old  March 13, 2003
Reviewer:  Vivek Sharma
       from India
Dear Tom,

Very Good Input. Your Inputs have cleared many questions about rollback
segments. Good work.

You say that wraps are not bad, they are normal. But Wraps can also cause
Snapshot too old error. Am I correct ? So shall I avoid rollback segment to wrap
and to do so, do i need to create more rollback segments or Increase the extent
size of the rollback segments.
When do I consider increasing the no. of rollback segments ?
and when do i consider increasing the extent size.

Thanks in advance and Regards
Vivek

          Followup:

every rollback segment is going to wrap -- unless they are infinite in size.

wraps do not cause 1555's

1555's are caused by having rbs's that are too small.  When they are too small
they wrap faster then your longest running query takes to run.

You cannot avoid them wrapping.

You want to make sure they don't wrap TOO FAST.

You do that by permanently allocating then large enough so they take longer to
wrap around then your queries do to execute. (minextents = more)

adding rbs's -- that would be another way (more rbs's to use, wrap less fast)
but usually you add rbs's to reduce rbs contention (undo header gets and such).

      Rating: 5
Snapshot too
      old  March 13, 2003
Reviewer:  Vivek Sharma
       from India
Sorry Tom,

I have asked u the question for which u have already answered. To avoid
wrapping, I need to increase the extent size.

So I have got the answer for this, but when should i consider increasing the no.
of rollback segments.

My apologies.

Thanks and Regards
Vivek Sharma

      Rating: 4
Is the same
      true for Locally Managed UNDO Tablespaces ?(9i)  March 14,
      2003
Reviewer:  Devanshi Parikh  from NJ USA

By using UNDO_MANAGEMENT as AUTO and UNDO tablespace, we do do not control the
size or the number of segments. How would Oracle figure out how many segments
should be created and how many max extents should it have ? If it is not
possible to extend(we truly reached a 100%), will it wrap into the other
segment's extent ?
Could you please explain a little ?

Thanks,

          Followup:

It figures out how many by watching concurrency -- as it goes up, it'll
automagically add more, goes down -- takes them away.

As for how big -- the retention period you specify controls that -- it'll grow
them until

o the retention period is satisfied
o you run out of space

which ever comes first.

      Rating: 5
I think I can
      answer devanshi parikh's question  March 14, 2003

Reviewer:  Vivek Sharma  from India
UNDO_RETENTION parameter takes care about for how much time the information be
kept in the rollback segment.

Please correct if I am wrong

Vivek

      Rating: 5
Hats off to you
      Tom  June 19, 2003
Reviewer:  A reader
       from MN, USA
Hi Tom,
  I must sincerely appreciate the patience you have. With all the technical
brilliance dont you at times think of all us users as *silly*. I mean this
particular thread had so much of confusion created and all that was becuase we
readers were making a fuss. You have answered all of them with such accurate
reasoning and great patience. I really feel you are a boon to the ORACLE user
community.
   Thanks for all the help you extend.

Regards
A well-wisher

      Rating: 5
Space utilized
      by rollback segment  October 09, 2003
Reviewer:
      atul  from India
Hi,

if following query give me output like :
select extents,wraps from v$rollstat where usn=3;

296 for extents, 302 for wraps

Only one rollback segment other than system is online.
Initial/next = 10M maxextents=1000

From output how can i know space utilized by rollback segment..
If we can't from this query then how to know that?

Thank,
Atul

          Followup:

look at dba_segments to see the space used by a rbs.

      Rating:
      5
optsize  October 14, 2003
Reviewer:
      Atul  from India
Hi,

I read on this site that its not good to use optsize
parameter in rollback segment.

If i create rollback segment without optsize then also
i can shrink it back to whatever size i want??
So what are benifits by using optsize we get??

Why you don't use it,any reason??

Thank & Regards,
Atul

          Followup:

in 9i, i just use AUM (automatic undo mgmt)

in 8i, i put every rbs in its own autoextend tablespace.  when i wanted to
shrink one -- i would simply drop it, shrink the datafile in that tablespace and
recreate it.

I don't use it cause it kicks in when the system is busy and expires undo that
others may well need.  rather do it on saturday when the system wasn't busy and
when people wouldn't need it as much.

it would be rare to actually shrink it -- i like to let them grow to be as big
as they want to be for my system.

October 14, 2003 -- Thanks for the question regarding "insert but
      discarding some unwanted/erroneous rows", version 8.1.7.1

      You Asked
Hi Tom

I was wondering if with bulk collect in pl/sql is it possible to mass insert
many rows but discarding those rows which would be discarded under circumstances
for example:

insert... select ....

some rows of one column of the origin table has longer width than destination
table, if we use SQL*Loader it will be discarded automatically. Is it possible
to simulate this functionality with pl/sql? With a simple cursor and exception
we could do it but it iterates row by row (slow) so I was wondering if bulk
collect can be used

cheers

      and we said...
Yes, you can simulate this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x varchar2(10) NOT NULL );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      type array is table of varchar2(255);
  3
  4      l_data array := array( 'works 1', 'works 2',
  5                             'too long, much too long',
  6                             'works 4', NULL, 'works 6' );
  7      l_start number := 1;
  8  begin
  9      loop
 10          begin
 11              forall i in l_start .. l_data.count
 12                  insert into t values ( l_data(i) );
 13              EXIT;
 14          exception
 15              when others then
 16                  dbms_output.put_line( 'Bad row index = ' ||
(l_start+sql%rowcount) ||
 17                                        ' ' || sqlerrm );
 18                  l_start := l_start + sql%rowcount + 1;
 19          end;
 20      end loop;
 21  end;
 22  /
Bad row index = 3 ORA-01401: inserted value too large for column
Bad row index = 5 ORA-01400: cannot insert NULL into ("OPS$TKYTE"."T"."X")

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

X
----------
works 1
works 2
works 4
works 6

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

It inserts all of the data that is "good" stopping on each "bad" record.

Review &
      Followup

      Rating: 5
for insert...
      select  May 04, 2002
Reviewer:
      A reader
Hi

For insert select how can we do it? How do we plug forall for select :?

          Followup:

insert select either WORKS or it does not WORK.  Either all rows or no rows.

You could (in this example)

insert into t
select x from t2
where ( LENGTH(x) <= 10 and x is not null and <other constraints here> )

and do that for each column.  With 9i, it is nice cause you can have a
multi-table insert so that rows that cannot be inserted into t could be logged
into a "bad" table.

      Rating: 4
using a
      cursor?  May 04, 2002
Reviewer:
      A reader
sorry i should have said not a plain insert select, rather using a cursor so
instead of

forall i in l_start .. l_data.count

can I use

forall i in (select * from myTable)

?

          Followup:

Sorry it was not clear.

INSERT INTO T
select ... from ..... where ....

either WORKS or it does not WORK.  There is no "forall" filtering (that involves
bringing the data BACK to the client and then sending it back to the server)

What I tried to show was a way to avoid getting the "bad" records in the first
place:

insert into t
select x from t2
where ( LENGTH(x) <= 10 and x is not null and <other constraints here> )

      Rating:
      5
  May 04, 2002
Reviewer:
      A reader
from your code

  9      loop
 10          begin
 11              forall i in l_start .. l_data.count
 12                  insert into t values ( l_data(i) );
 13              EXIT;

What is EXIT for?
Are we fetching row by row? Looks like it, if we are what is the use of forall
here? how many rows is forall fetching each iteration?

          Followup:

Line 9 is the beginning of an infinite loop.  We need a way out.  the EXIT is
the way out.  When we do an insert that flings no exceptions -- leave the loop.

We are not fetching at all in this example.  We are bulk inserting as many rows
as we can and will insert all rows in the array UPTO a bad row.

The forall is inserting UPTO (l_data.count-l_start+1) rows -- if one of the rows
in the array cannot be inserted, it will STOP inserting and return.

      Rating:
      5
locks  August 28, 2002
Reviewer:
      A reader
Hi Tom

I have folowing pl/sql block to insert 10000 rows to emp

declare
  type array is table of number;
  l_array array;
  l_array_element number;
begin
  l_array := array(); -- I dont know what does this do, I only know without it
this doesnt work
  for i in 1..9999
  loop
    l_array.extend;
    l_array(l_array.last) := i;
  end loop;
    l_array_element := l_array.first;
    dbms_output.put_line(l_array_element);
    loop
      begin
        forall i in l_array_element..l_array.last
        insert into lsc.emp(empno) values(l_array(i));
        exit;
      exception
      when others
      then l_array_element := l_array_element + sql%rowcount +1;
      end;
    end loop;
end;
/

I ran twice and I killed my database :-(
I see these locks in v$lock

HW - Space management operations on a specific segment
TT - Temporary table enqueue

They switch between each other, now TT next second HW next second goes back to
TT and so on

what´s going on...?

          Followup:

You did not "kill" your database - whatever that means is beyond me.

l_array := array();

that sets l_array to an EMPTY array, before l_array was NULL and trying to
"extend" NULL would fail.  You needed to "un-null" it before using it.

works great for me:

ops$tkyte@ORA920.LOCALHOST> drop table emp;

Table dropped.

ops$tkyte@ORA920.LOCALHOST> create table emp ( empno int );

Table created.

ops$tkyte@ORA920.LOCALHOST> declare
  2    type array is table of number;
  3    l_array array;
  4    l_array_element number;
  5  begin
  6    l_array := array();
  7    for i in 1..9999
  8    loop
  9       l_array.extend;
 10       l_array(l_array.last) := i;
 11    end loop;
 12
 13    l_array_element := l_array.first;
 14    dbms_output.put_line(l_array_element);
 15    loop
 16         begin
 17           forall i in l_array_element..l_array.last
 18           insert into emp(empno) values(l_array(i));
 19           exit;
 20         exception
 21         when others
 22             then l_array_element := l_array_element + sql%rowcount +1;
 23         end;
 24       end loop;
 25  end;
 26  /
1

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.LOCALHOST> spool off

without more info beyond "it killed my database" which is meaningless -- i
cannot say anything more.

      Rating: 5
100% CPU
      usage  August 28, 2002
Reviewer:
      A reader
Hi sorry for my bad language, I meant the server CPU usage is spinning at 100%.
I queried v$lock I see

ADDR     KADDR           SID TY        ID1        ID2      LMODE    REQUEST
CTIME      BLOCK
-------- -------- ---------- -- ---------- ---------- ---------- ----------
---------- ----------
7982C33C 7982C448          9 TX     589861        291          6          0
 2736          1
79F442A0 79F442B4          9 TM      30585          0          3          0
 2736          0
79F4421C 79F44230          9 TM      30372          0          3          0
 2736          0
7A6CD9D4 7A6CD9E4          9 HW          1    8388745          6          0
    0          0
7A6CD988 7A6CD998          9 TT          1         16          4          0
    0          0

sid 9 is my insert session, what I did was ran the pl/sql block twice, one after
another without commit or rollback, I killed the session 30 minutes ago but
these locks still appears in v$lock

In my code, if I comment

  for i in 1..9999
  loop
    l_array.extend;
    -- l_array(l_array.last) := i;
  end loop;

I get this error back (I commented out the exception part to see the error)

ERROR at line 1:
ORA-01400: cannot insert NULL into ("LSC"."DEPT"."DEPTNO")
ORA-06512: at line 16

:-0

          Followup:

so, give us the entire test case from start to finish (include the create table
and all)

I just did it -- works great -- two times in a row.

Versions down to 4 digits are always appreciated
Operating system type as well.

One thing I note right away is the error message complains about DEPTNO, but you
never filled in DEPTNO so I don't think you are giving us the WHOLE story here.
Do a cut and paste -- from beginning to END, just like I do!

      Rating: 5
it is the
      entire code!!!  August 28, 2002
Reviewer:
      A reader
declare
  type array is table of number;
  l_array array;
  l_array_element number;
begin
  l_array := array(); -- I dont know what does this do, I only know without it
this doesnt work
  for i in 1..9999
  loop
    l_array.extend;
    l_array(l_array.last) := i;
  end loop;
    l_array_element := l_array.first;
    dbms_output.put_line(l_array_element);
    loop
      begin
        forall i in l_array_element..l_array.last
        insert into lsc.emp(empno) values(l_array(i));
        exit;
      exception
      when others
      then l_array_element := l_array_element + sql%rowcount +1;
      end;
    end loop;
end;
/

this is the entire code!!!!

declare
  type array is table of number;
  l_array array;
  l_array_element number;
begin
  l_array := array(); -- I dont know what does this do, I only know without it
this doesnt work
  for i in 1..9999
  loop
    l_array.extend;
    -- l_array(l_array.last) := i;
  end loop;
    l_array_element := l_array.first;
    dbms_output.put_line(l_array_element);
    loop
      begin
        forall i in l_array_element..l_array.last
        insert into lsc.emp(empno) values(l_array(i));
        exit;
      -- exception
      -- when others
      -- then l_array_element := l_array_element + sql%rowcount +1;
      end;
    end loop;
end;
/

this the code that gives me deptno error, the table is the dept table that comes
with demobld.sql in $ORACLE/HOME/sqlplud/demo  :-(

      Rating: 5
forgot version
      and OS  August 28, 2002
Reviewer:
      A reader
sorry, the version is 9.2.0.1 and OS win2k

      Rating: 5
sorry the first
      works when I try in another server  August 28, 2002

Reviewer:  A reader
sorry the first pl/sql works, i dont understand what happened in other server
that gave those waits which I havent seen before

the second does not

SQL>   declare
  2      type array is table of number;
  3      l_array array;
  4      l_array_element number;
  5    begin
  6      l_array := array(); -- I dont know what does this do, I only know
without it this doesnt work
  7      for i in 1..9999
  8      loop
  9        l_array.extend;
 10    --    l_array(l_array.last) := i;
 11      end loop;
 12        l_array_element := l_array.first;
 13        dbms_output.put_line(l_array_element);
 14        loop
 15          begin
 16            forall i in l_array_element..l_array.last
 17            insert into lsc.emp(empno) values(l_array(i));
 18            exit;
 19   --       exception
 20   --       when others
 21   --       then l_array_element := l_array_element + sql%rowcount +1;
 22          end;
 23        end loop;
 24    end;
 25  /
  declare
*
ERROR at line 1:
ORA-01400: cannot insert NULL into ("LSC"."EMP"."EMPNO")
ORA-06512: at line 16

The deptno was another error, very sorry :-(

      Rating:
      4
  August 28, 2002
Reviewer:  Bhagat
      Singh  from India
Hi Tom,

It was great example can this type of situation handled via pro*c code if yes
then can you show some sample of the same so that it can help us to do bulk
loading of the data.

Regards
Bhagat Singh

          Followup:

It is in fact virtually the same as the PLSQL except you have to increment a
host variable pointer for us.  Consider this piece of code:

void process()
{
exec sql begin declare section;
int   int_data[] = { 1,2,33,4,5,66,7,8,99,0 };
int   * int_hv;

typedef char asciiz [20];
EXEC SQL TYPE asciiz is STRING(20);
asciiz  char_data[] = { "one", "two", "three", "four", "five",
                          "six", "seven", "eight", "nine", "ten" };
asciiz  * * char_hv;
int   n;

int      test_number;
asciiz   test_string;
EXEC SQL END DECLARE SECTION;

    EXEC SQL WHENEVER SQLERROR CONTINUE;
    EXEC SQL drop table t;

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();
    exec sql create table t ( x number(1), y varchar2(20) );
    EXEC SQL WHENEVER SQLERROR CONTINUE;

    for( int_hv = int_data, char_hv = char_data,
         n = sizeof(int_data)/sizeof(int_data[0]); ; )
    {
        exec sql for :n insert into t values ( :int_hv, :char_hv );
        printf( "sqlcode = %d, sqlerrd[2] = %d\n",
                 sqlca.sqlcode, sqlca.sqlerrd[2] );
        if ( sqlca.sqlcode )
        {
            int_hv = int_hv + (sqlca.sqlerrd[2]+1);
            char_hv = (char *)char_hv + (sqlca.sqlerrd[2]+1) *
sizeof(char_data[0]);            n -= sqlca.sqlerrd[2]+1;
        }
        else
        {
            break;
        }
    }

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();
    exec sql whenever NOTFOUND DO BREAK;
    exec sql declare c cursor for select * from t;
    exec sql open c;
    for(;;)
    {
        exec sql fetch c into :test_number, :test_string;
        printf( "%d, %s\n", test_number, test_string );
    }
    exec sql whenever notfound continue;
    exec sql close c;

}

when run, it generated this output:

Connected to ORACLE as user: /

sqlcode = -1438, sqlerrd[2] = 2
sqlcode = -1438, sqlerrd[2] = 2
sqlcode = -1438, sqlerrd[2] = 2
sqlcode = 0, sqlerrd[2] = 1
1, one
2, two
4, four
5, five
7, seven
8, eight
0, ten

showing the numbers 33, 66, 99 failed and we just skipped them.

      Rating: 5
Hey!
      Unidentified reader!!!  August 29, 2002
Reviewer:
      Unidentified also  from ma
  8      loop
  9        l_array.extend;
 10    --    l_array(l_array.last) := i;
 11      end loop;
 12        l_array_element := l_array.first;
 13        dbms_output.put_line(l_array_element);
 14        loop
 15          begin
 16            forall i in l_array_element..l_array.last
 17            insert into lsc.emp(empno) values(l_array(i));

You rem'd out line 10 thus extending but never initializing l_array, inserting
into a (assumed) required field.

      Rating: 5
Built in
      exception handler with Bulk collects ?  September 06, 2002

Reviewer:  Ram  from India
Thanks for the fantastic example !

I have a couple of questions regarding exception handling when using bulk
collects.
a) Is there a built in exception array into which you can populate error
messages as and when they occur and loop thru them at the end and take necessary
action?
Reason  i am asking is that i saw some code using bulk collects that had a 'Save
Exceptions'  clause added to forall.
b) if the answer to the above is yes, is it specific to some version ?

thanks in advance.
your book is GREAT ! looking forward to your
next one :-)

          Followup:

Yes, this is new in Oracle9i release 1 and up.  Here is the exmaple from the
book:

ops$tkyte@ORA9I.WORLD> create table emp ( sal int );

Table created.

ops$tkyte@ORA9I.WORLD> insert into emp values ( 1000 );

1 row created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> DECLARE
  2     TYPE NumList IS TABLE OF NUMBER;
  3     num_tab NumList := NumList(10,0,11,12,30,0,20,199,2,0,9,1);
  4     errors NUMBER;
  5     dml_errors EXCEPTION;
  6     PRAGMA exception_init(dml_errors, -24381);
  7  BEGIN
  8     FORALL i IN num_tab.FIRST..num_tab.LAST SAVE EXCEPTIONS
  9        DELETE FROM emp WHERE sal > 500000/num_tab(i);
 10  EXCEPTION
 11     WHEN dml_errors THEN
 12        errors := SQL%BULK_EXCEPTIONS.COUNT;
 13        dbms_output.put_line('Number of errors is ' || errors);
 14            FOR i IN 1..errors LOOP
 15                  dbms_output.put_line('Error ' || i || ' occurred during '||
 16             'iteration ' || SQL%BULK_EXCEPTIONS(i).ERROR_INDEX);
 17          dbms_output.put_line('Oracle error is ' ||
SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE));
 18            END LOOP;
 19  end;
 20  /
Number of errors is 3
Error 1 occurred during iteration 2
Oracle error is ORA-01476: divisor is equal to zero
Error 2 occurred during iteration 6
Oracle error is ORA-01476: divisor is equal to zero
Error 3 occurred during iteration 10
Oracle error is ORA-01476: divisor is equal to zero

PL/SQL procedure successfully completed.

As you can see, it greatly simplified this entire process.

      Rating: 5
insert
      fail  February 06, 2003
Reviewer:  mo

Tom:

Is there a way when an insert fails in SQL/PL-SQL on a table to tell which
column exactly could not accept the data or I have to examine each column in a
large table manually.

Thanks,

          Followup:

depends on the error:

Bad row index = 3 ORA-01401: inserted value too large for column
Bad row index = 5 ORA-01400: cannot insert NULL into ("OPS$TKYTE"."T"."X")

      Rating: 5
fail
      insert  February 07, 2003
Reviewer:  mo

TOm:

how did you get the row index value in SQL or PL/SQL? Is this the troublesome
column?

Here is what I mean. How do I let oracle tell me which column has the problem
rather than me trying to figure it out in a large table?

SQL>   insert into emp1(empno,ename,job,mgr) values (222,'michael
dell','MGR','abc')
                                                                        *
ERROR at line 1:
ORA-01401: inserted value too large for column

SQL> insert into emp1(empno,ename,job,mgr) values (222,'mike dell','MGR','abc')
SQL> /
 insert into emp1(empno,ename,job,mgr) values (222,'mike dell','MGR','abc')
                                                                     *
ERROR at line 1:
ORA-01722: invalid number

          Followup:

I told you -- depends on the ERROR.  1401, no joy. others --maybe.

the row index was the index form the array insert.

      Rating: 5
insert
      fail  February 07, 2003
Reviewer:  mo

Tom:

You did not tell me how you got this:

I am not seeing this in SQL. All I get is the ORA-01401 message. Do i have to
create an index on the column or do some other kind of setup for it?

Also, in PL/SQL can you report that index value back to the user on a web page
(by assigning it to a variable)?

<
Bad row index = 3 ORA-01401: inserted value too large for column
Bad row index = 5 ORA-01400: cannot insert NULL into ("OPS$TKYTE"."T"."X")
>

          Followup:

All i get is the 1401 too -- are you seeing something I'm not??????

the index is NOT the column number.
the index is printed by me, an index into an ARRAY of rows I was inserting.

that says "row 3 had a problem"

      Rating: 4
Trapping
      multiple errors  April 23, 2003
Reviewer:
      A reader
Hi Tom,
Is it possible to find multiple errors (if any) in the data, while
inserting/updating? Like; the error could be because of NULL value, PK and/or FK
violation etc. This could be useful to fix all errors in one go rather then
running the program again and again. The DB version I am using is 8.1.7.3 on
HP-UNIX 11i
Thanks

          Followup:

no, once an error is found, the statement is terminated.

      Rating: 4
bulk
      processing  May 15, 2003
Reviewer:  lakshmi
       from Bangalore India
Excellent

      Rating:
      4
Excellent  June 27, 2003
Reviewer:
      A reader  from Aus
what if i only want to insert those rows/records that have passed some
validation previous to the forall.

do i have to create a second set of arrays of valid values, and insert that set?

I saw a technique used by yourself for an update where you set a changed flag
for each record.

thanks

          Followup:

same concept:

ops$tkyte@ORA920> create table t ( data number );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2     type array is table of varchar2(20);
  3     l_data array := array( '1','2','3','d','4','5' );
  4     l_flags array := array();
  5  begin
  6          l_flags.extend(l_data.count);
  7          for i in 1 .. l_data.count
  8          loop
  9                  if ( l_data(i) != 'd' )
 10                  then
 11                          l_flags(i) := 'Y';
 12                  else
 13                          l_flags(i) := 'N';
 14                  end if;
 15          end loop;
 16
 17          forall i in 1 .. l_data.count
 18                  insert into t ( data)
 19                  select l_data(i)
 20                    from dual
 21                   where l_flags(i) = 'Y';
 22  end;
 23  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from t;

      DATA
----------
         1
         2
         3
         4
         5

ops$tkyte@ORA920>

      Rating: 5
I am not worthy
      !!!  June 28, 2003
Reviewer:  phil

thanks

      Rating:
      5
  August 08, 2003
Reviewer:  Su Baba
       from Ca, USA
Hi Tom,

I have a situation where I need to do a bulk insert, but I want
to get rid of unwanted rows based on a function applied to
a column. It seems like the entire operation stops as soon as
it hits the first exception. Is there any way to continue the
process like the one in your reply to the original question?

Here's my code to simulate the situation:

SQL> CREATE OR REPLACE FUNCTION func (x IN NUMBER)
  2  RETURN NUMBER IS
  3     l_ret_val          NUMBER;
  4     e_custom_exception EXCEPTION;
  5     PRAGMA EXCEPTION_INIT(e_custom_exception, -1000);
  6
  7  BEGIN
  8     l_ret_val := x * 100;
  9
 10     IF (l_ret_val > 100000) THEN
 11        RAISE e_custom_exception;
 12     END IF;
 13
 14     RETURN l_ret_val;
 15
 16     EXCEPTION
 17        WHEN e_custom_exception THEN
 18           dbms_output.put_line('Error occurred...');
 19           l_ret_val := 0;
 20  END;
 21  /

Function created.

SQL> CREATE TABLE x (
  2  col1 NUMBER
  3  );

Table created.

SQL>
SQL> INSERT INTO x VALUES (1);

1 row created.

SQL> INSERT INTO x VALUES (100);

1 row created.

SQL> INSERT INTO x VALUES (10000);

1 row created.

SQL> INSERT INTO x VALUES (200);

1 row created.

SQL> INSERT INTO x VALUES (20000);

1 row created.

SQL> commit;

Commit complete.

SQL>
SQL> CREATE TABLE z (
  2  col1 NUMBER
  3  );

Table created.

SQL> DECLARE
  2      type number_tbl_type is table of NUMBER index by binary_integer;
  3      l_col1_tbl    number_tbl_type;
  4      l_start NUMBER := 1;
  5
  6      CURSOR c IS
  7         SELECT col1 FROM x;
  8
  9  BEGIN
 10      OPEN c;
 11
 12      LOOP
 13          FETCH c BULK COLLECT INTO l_col1_tbl
 14             LIMIT 100;
 15
 16          BEGIN
 17             FORALL i IN 1..l_col1_tbl.COUNT
 18                INSERT INTO z (col1) VALUES
 19                (func(l_col1_tbl(i)));
 20
 21             EXCEPTION
 22                WHEN others THEN
 23                   dbms_output.put_line('Error at ' ||
 24                      (l_start + sql%rowcount));
 25
 26                   l_start := l_start + sql%rowcount + 1;
 27          END;
 28
 29          EXIT WHEN c%NOTFOUND;
 30      END LOOP;
 31
 32      CLOSE c;
 33  END;
 34  /
Error occurred...
Error at 3

PL/SQL procedure successfully completed.

SQL> select * from z;

      COL1
----------
       100
     10000

SQL> show error
No errors.

          Followup:

make the function return a 0 or 1 (success or failure) instead and either

select col1 from x where f(col1) = 0

to filter then on the way OUT of the database or less efficiently

insert into z ( col1 ) select l_col1_tbl(i) from dual where f(l_col1_tbl(i)) = 0

or select them all out and move them into another array to be inserted:

   fetch bulk collect...

   l_data_array := l_empty_array;
   for i in 1 .. l_col1_tbl.count loop
        if ( f(l_col1_tbl(i)) = 0 ) then l_data_array(l_data_array.count+1) :=
l_col1_tbl(i);
        end if;
   end loop
   insert

or use the SAVE EXCEPTIONS (search for "save exceptions" on this site in quotes
to read about that) if you are using 9i

Me, I would probably do the "select them all, move them into an array, insert
them" to reduce the number of context switches from sql to plsql which can be
expensive.

      Rating:
      4
  August 10, 2003
Reviewer:  Su Baba
       from Ca, USA
Hi Tom,

Datababase version  : 8.1.7
Number of Rows in x : 1,000,000

If I make the function to return either 0 or 1, how do I get the return value
computed by the function?

I modified the above bulk collect/insert code into the following. Would it work
this way?

thanks

DECLARE
   type number_tbl_type is table of NUMBER index by binary_integer;
   l_col1_tbl    number_tbl_type;
   l_data_array  number_tbl_type;
   l_empty_array number_tbl_type;
   l_start NUMBER := 1;

   CURSOR c IS
      SELECT col1 FROM x;

BEGIN
   OPEN c;

   LOOP
      FETCH c BULK COLLECT INTO l_col1_tbl
         LIMIT 1000;

      l_data_array := l_empty_array;

      FOR i IN 1..l_col1_tbl.COUNT LOOP
         IF (func(l_col1_tbl(i)) = 0) THEN
            l_data_array (l_data_array.COUNT + 1) := l_col1_tbl(i);

         ELSE
            -- "dbms_output" the exception message
         END IF;

         FORALL i IN 1..l_data_array.COUNT
            INSERT INTO z (col1) VALUES l_data_array(i);

      EXIT WHEN c%NOTFOUND;
   END LOOP;

   CLOSE c;
END;
/

          Followup:

use my preferred option (do it in the code) and leave the exception flinging the
way it is I guess

or have the function return "some obviously invalid value" like
999999999999999999999999 on error.

I would

   fetch bulk collect
   for i in 1 ...
   loop
       begin
          another_array(another_array.count+1) := f(data(i));
       exception
           when your_exception then do whatever you want
       end;
   end loop
   forall insert another_array;

      Rating:
      4
Fanstastic!  August 14, 2003
Reviewer:
      Kashif  from Reston, VA
Kashif

      Rating: 3
Your "when
      others exception" example using
      "dbms_output.put_line"  August 25, 2003
Reviewer:
      ht  from California
Tom,
I haven't been successful in running your sample code so I tried the basic
sample below.  All I'm trying to do is print the Oracle error message and error
code in a pl/sql session.

What am I doing wrong?

Thanks,
ht
  1  declare
  2          x date;
  3  begin
  4          select sysdatex into x from dual;
  5          exception when others then
  6                  dbms_output.put_line('error code:'||sqlcode);
  7                  dbms_output.put_line('error message:'||sqlerrm);
  8* end;
>/
        select sysdatex into x from dual;
               *
ERROR at line 4:
ORA-06550: line 4, column 16:
PL/SQL: ORA-00904: "SYSDATEX": invalid identifier
ORA-06550: line 4, column 9:
PL/SQL: SQL Statement ignored

          Followup:

you have to experience a runtime error, not a compile time error.

sysdatex is NOT a column
hence this block of code cannot be compiled
hence it never actually runs

if you do something like

ops$tkyte@ORA920> declare
  2          x date;
  3  begin
  4          x := 'a';
  5          exception when others then
  6                  dbms_output.put_line('error code:'||sqlcode);
  7                  dbms_output.put_line('error message:'||sqlerrm);
  8  end;
  9  /
error code:-1858
error message:ORA-01858: a non-numeric character was found where a numeric was
expected

PL/SQL procedure successfully completed.

that'll generate a RUNTIME error for you.

      Rating:
      5
  August 26, 2003
Reviewer:
      A reader

      Rating: 5
save exceptions
      and so on in Oracle <= 9.0  August 28, 2003

Reviewer:  Matthias Rogel  from Kaiserslautern,
      Germany
Hallo Tom,

U often mention the new pl/sql-feature "save exceptions" introduced in Oracle 9
Release 1.

however, I think it's worth telling the audience that U can achieve everything
introduced by this feature also in prior versions.

Take your example on

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3675533064673#5303751976529
"from the book" (I don't read books @all, sorry 4 that!)

here's how I do the same example in Oracle 9.0 (would also work in Oracle 8, I'd
say, cannot test it 4 the moment):
SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.0.1.0.0 - Production
PL/SQL Release 9.0.1.0.0 - Production
CORE    9.0.1.0.0       Production
TNS for Linux: Version 9.0.1.0.0 - Production
NLSRTL Version 9.0.1.0.0 - Production

Abgelaufen: 00:00:00.00
SQL> create table emp ( sal int );

Tabelle wurde angelegt.

Abgelaufen: 00:00:00.00
SQL> insert into emp values ( 1000 );

1 Zeile wurde erstellt.

Abgelaufen: 00:00:00.00
SQL>
SQL>
SQL> declare
  2     TYPE NumList IS TABLE OF NUMBER;
  3     num_tab NumList := NumList(10,0,11,12,30,0,20,199,2,0,9,1);
  4     num_tab_ dbms_sql.number_table;
  5     errors number;
  6     bulk_cursor integer;
  7     arr_sqlerrm dbms_sql.varchar2_table;
  8     dummy integer;
  9     i integer;
 10  begin
 11     bulk_cursor := dbms_sql.open_cursor;
 12     dbms_sql.parse(bulk_cursor,
 13           'begin' || chr(10) ||
 14           '   delete from emp where sal > 500000/:num;'|| chr(10) ||
 15           '   exception when others then :vsqlerrm := sqlerrm;'|| chr(10) ||
 16           'end;',
 17           dbms_sql.native);
 18     for c in num_tab.first .. num_tab.last loop
 19        num_tab_(num_tab_.count+1) := num_tab(c);
 20     end loop;
 21     dbms_sql.bind_array(bulk_cursor, ':num', num_tab_);
 22     dbms_sql.bind_array(bulk_cursor, ':vsqlerrm', arr_sqlerrm);
 23     dummy := dbms_sql.execute(bulk_cursor);
 24     dbms_sql.variable_value(bulk_cursor, ':VSQLERRM', ARR_SQLERRM);
 25     dbms_sql.close_cursor(bulk_cursor);
 26     dbms_output.put_line('Number of errors is ' || arr_sqlerrm.count);
 27     i := arr_sqlerrm.first;
 28     while i is not null loop
 29        if arr_sqlerrm(i) is not null then
 30           dbms_output.put_line('Oracle error is ' ||
 31                                 arr_sqlerrm(i));
 32        end if;
 33        i := arr_sqlerrm.next(i);
 34     end loop;
 35  end;
 36  /
Number of errors is 3
Oracle error is ORA-01476: Divisor ist Null
Oracle error is ORA-01476: Divisor ist Null
Oracle error is ORA-01476: Divisor ist Null

PL/SQL-Prozedur wurde erfolgreich abgeschlossen.

Abgelaufen: 00:00:00.03

If you also need the "SQL%BULK_EXCEPTIONS(i).ERROR_INDEX"
you had to go like
SQL> declare
  2     TYPE NumList IS TABLE OF NUMBER;
  3     num_tab NumList := NumList(10,0,11,12,30,0,20,199,2,0,9,1);
  4     num_tab_ dbms_sql.number_table;
  5     errors number;
  6     bulk_cursor integer;
  7     arr_sqlerrm dbms_sql.varchar2_table;
  8     arr_it dbms_sql.number_table;
  9     dummy integer;
 10     i integer;
 11  begin
 12     bulk_cursor := dbms_sql.open_cursor;
 13     dbms_sql.parse(bulk_cursor,
 14           'begin' || chr(10) ||
 15           '   delete from emp where sal > 500000/:num;'|| chr(10) ||
 16           '   :vsqlerrm := null;'|| chr(10) ||
 17           '   exception when others then :vsqlerrm := sqlerrm;'|| chr(10) ||
 18           'end;',
 19           dbms_sql.native);
 20     for c in num_tab.first .. num_tab.last loop
 21        num_tab_(num_tab_.count+1) := num_tab(c);
 22        arr_it(arr_it.count+1) := c;
 23     end loop;
 24     dbms_sql.bind_array(bulk_cursor, ':num', num_tab_);
 25     dbms_sql.bind_array(bulk_cursor, ':vsqlerrm', arr_sqlerrm);
 26     dummy := dbms_sql.execute(bulk_cursor);
 27     dbms_sql.variable_value(bulk_cursor, ':VSQLERRM', ARR_SQLERRM);
 28     dbms_sql.close_cursor(bulk_cursor);
 29     i := arr_sqlerrm.first;
 30     while i is not null loop
 31        if arr_sqlerrm(i) is not null then
 32           dbms_output.put_line('Error occurred during '||
 33                               'iteration ' || arr_it(i));
 34           dbms_output.put_line('Oracle error is ' ||
 35                                 arr_sqlerrm(i));
 36        end if;
 37        i := arr_sqlerrm.next(i);
 38     end loop;
 39  end;
 40  /
Error occurred during iteration 2
Oracle error is ORA-01476: Divisor ist Null
Error occurred during iteration 6
Oracle error is ORA-01476: Divisor ist Null
Error occurred during iteration 10
Oracle error is ORA-01476: Divisor ist Null

or so.

Of course, your way is
1. easier to code
2. faster (I know, execute immediate and forall and this stuff is faster than
dbms_sql, however, I don't know why)

But my way is still pretty performant

best
regards
matthias

      Rating: 2
Sorry, but ....
      "save exceptions" and instead-of-trigger  September 01, 2003

Reviewer:  A reader
sorry for that bad example.

just saw, that Tom's method (of course) also works
in 9.0.1.

BUT:
I came to it with a sample where "save exceptions"
don't work, in 9.0 AND in 9.2

in my opinion, it seems to be an Oracle Bug - just
don't know whether in the engine or in the documentation.

here it is

SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
PL/SQL Release 9.2.0.1.0 - Production
CORE    9.2.0.1.0       Production
TNS for 32-bit Windows: Version 9.2.0.1.0 - Production
NLSRTL Version 9.2.0.1.0 - Production

SQL> create table test(n number);

Tabelle wurde angelegt.

SQL> create view test_ as select * from test;

View wurde angelegt.

SQL> create trigger tr_test
  2  instead of insert on test_
  3  for each row
  4  begin
  5     if :new.n > 5 then raise_application_error(-20001, 'only values<=5
allowed'); end if;
  6     insert into test values(:new.n);
  7  end;
  8  /

SQL> insert into test_ values(6);
insert into test_ values(6)
            *
FEHLER in Zeile 1:
ORA-20001: only values<=5 allowed
ORA-06512: in "DP.TR_TEST", Zeile 2
ORA-04088: Fehler bei der Ausf³hrung von Trigger 'DP.TR_TEST'

SQL> insert into test_ values(4);

1 Zeile wurde erstellt.

SQL> select * from test;

         N
----------
         4

REM ********************************************
REM so far, so good
REM now, let's take Tom's approach to bulk-insert
REM into test_ ....
REM ********************************************

SQL> DECLARE
  2    TYPE NumList IS TABLE OF NUMBER;
  3    num_tab NumList := NumList(10,0,11,12,30,0,20,199,2,0,9,1);
  4    errors NUMBER;
  5    dml_errors EXCEPTION;
  6    PRAGMA exception_init(dml_errors, -24381);
  7    BEGIN
  8    FORALL i IN num_tab.FIRST..num_tab.LAST SAVE EXCEPTIONS
  9    insert into test_ values(num_tab(i));
 10   EXCEPTION
 11   WHEN dml_errors THEN
 12   errors := SQL%BULK_EXCEPTIONS.COUNT;
 13   dbms_output.put_line('Number of errors is ' || errors);
 14   FOR i IN 1..errors LOOP
 15   dbms_output.put_line('Error ' || i || ' occurred during '||
 16   'iteration ' || SQL%BULK_EXCEPTIONS(i).ERROR_INDEX);
 17   dbms_output.put_line('Oracle error is ' ||
 18  SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE));
 19   END LOOP;
 20   end;
 21   /
DECLARE
*
FEHLER in Zeile 1:
ORA-20001: only values<=5 allowed
ORA-06512: in "DP.TR_TEST", Zeile 2
ORA-04088: Fehler bei der Ausf³hrung von Trigger 'DP.TR_TEST'
ORA-06512: in Zeile 8

REM ********************************************
REM sorry, what's that ?
REM NO exception was saved ?
REM It seems that when U want
REM to do bulk-dml on a view with
REM instead-of-triggers
REM using save exceptions
REM the only way U are able to do this is
REM dbms_sql
REM ********************************************

Am I missing sth. here ?

best regards,
matthias rogel

          Followup:

i concurr -- the appearance of an instead of trigger causes the save exceptions
to not work.  I've filed a bug on it.

      Rating:
      4
  September 18, 2003
Reviewer:
      Peacemonger  from Vacationland
In presenting the PL/SQL code for bulk inserting rows of data, you say

     It inserts all of the data that is "good" stopping on each "bad" record.

Does this mean that every "stop" entails a context switch to complete the
forall...insert statement before continuing the LOOP?

If so, then it follows that the number of context switches is directly
proportional to the number of rows provoking an EXCEPTION.  Correct?

          Followup:

if you do it without SAVE EXCEPTIONS -- it context switches on each error.

if you do it with SAVE EXCEPTIONS -- it does not.

save exceptions is a new 9i feature.

      Rating:
      5
Intriguing  September 18, 2003
Reviewer:
      Peacemonger  from Vacationland
I'll definitely want to explore that when upgrading to 9i

      Rating: 4
Inserting
      Single row v/s bulk of rows  October 14, 2003

Reviewer:  Vivek Sharma
Dear Tom,

In the code which you have mentioned above, what difference would it have made
if you insert the rows into a table in the for loop after the IF statement check
for !='d'

declare
type array is table of varchar2(20);
l_data array := array( '1','2','3','d','4','5' );
l_flags array := array();
begin
        l_flags.extend(l_data.count);
        for i in 1 .. l_data.count
        loop
                if ( l_data(i) != 'd' )
                then
                        l_flags(i) := 'Y';
                else
                        l_flags(i) := 'N';
                end if;
        end loop;
        forall i in 1 .. l_data.count
                insert into t ( data)
                select l_data(i)
                  from dual
                where l_flags(i) = 'Y';
end;

I modified this plsql block to check the difference when
1. I Bulk fetch 1000 records and based on mod function set the flag. Then insert
into table t using forall for flag='Y'.
2. I Bulk fetch 1000 records and based on mod function insert a record in t
table.
3. Normaul processining of single record and based on mod function inserts into
t table.

The modified block is as under :

insert into t1 select rownum from all_objects;

17166 rows created.
commit;

set serveroutput on
declare
type array is table of varchar2(20);
l_flags array := array();
l_start    number;
l_x    t.x%type;
cursor main is
select * from t1;
begin
l_start := dbms_utility.get_time;
open main ;
loop
fetch main bulk collect into l_data limit 1000;
        l_flags.extend(l_data.count);
        for i in 1 .. l_data.count
        loop
                if ( mod(l_data(i),100) != 0 )
                then
                        l_flags(i) := 'Y';
                else
                        l_flags(i) := 'N';
                end if;
        end loop;
        forall i in 1 .. l_data.count
                insert into t
                select l_data(i)
                  from dual
                where l_flags(i) = 'Y';
  exit when main%notfound;
end loop;
close main;
dbms_output.put_line('Time taken '||to_char(dbms_utility.get_time-l_start)||'
hsecs');
l_start:=dbms_utility.get_time;
open main;
loop
fetch main bulk collect into l_data limit 1000;

        for i in 1 .. l_data.count
        loop
        if ( mod(l_data(i),100) != 0 )
                then
                        insert into t
                        values(l_data(i));
                end if;
        end loop;
exit when main%notfound;
end loop;
close main;
dbms_output.put_line('Time taken '||to_char(dbms_utility.get_time-l_start)||'
hsecs');
l_start:=dbms_utility.get_time;
open main;
loop
    fetch main into l_x;
    exit when main%notfound;
        if ( mod(l_x,100) != 0 )
                then
                    insert into t
                    values(l_x);
        end if;
end loop;
close main;
dbms_output.put_line('Time taken '||to_char(dbms_utility.get_time-l_start)||'
hsecs');
end;
/

Input truncated to 1 characters
Time taken 630 hsecs
Time taken 696 hsecs
Time taken 736 hsecs

PL/SQL procedure successfully completed.

Which is better according to you ? I have my application wherein I need to
process 6 lakhs records. What is your opinion ? Will first approach improve the
performance because I could not see much of the difference when running for
17000 odd records.

Also I can understand that that 3rd approach will be slower but what is the
difference between first 2 in terms of Performance.

Thanks in Advance.

Regards
Vivek Sharma

          Followup:

well, you need to be a little more "real world"  you probably don't have a
single column table like that do you?

Here we'll use a 40 byte row with an indexed value.  Using runstats, we can see
what is really going on:

ops$tkyte@ORA920LAP> rem drop table t;
ops$tkyte@ORA920LAP> rem drop table t1;
ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> rem create table t ( x int primary key, data char(40) );
ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> rem create table t1
ops$tkyte@ORA920LAP> rem as
ops$tkyte@ORA920LAP> rem select rownum x from all_objects;
ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> exec runstats_pkg.rs_start

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> truncate table t;

Table truncated.

ops$tkyte@ORA920LAP> declare
  2     type array is table of varchar2(20);
  3     l_flags array := array();
  4     l_data  array;
  5     l_start    number;
  6     l_x    t.x%type;
  7     cursor main is
  8        select * from t1;
  9  begin
 10     open main ;
 11     loop
 12        fetch main bulk collect into l_data limit 1000;
 13        l_flags.extend(l_data.count);
 14        for i in 1 .. l_data.count
 15        loop
 16           if ( mod(l_data(i),100) != 0 )
 17           then
 18              l_flags(i) := 'Y';
 19           else
 20              l_flags(i) := 'N';
 21           end if;
 22         end loop;
 23         forall i in 1 .. l_data.count
 24             insert into t
 25             select l_data(i), 'x' from dual
 26              where l_flags(i) = 'Y';
 27         exit when main%notfound;
 28     end loop;
 29     close main;
 30  end;
 31  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec runstats_pkg.rs_middle

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> truncate table t;

Table truncated.

ops$tkyte@ORA920LAP> declare
  2     type array is table of varchar2(20);
  3     l_flags array := array();
  4     l_data  array;
  5     l_start    number;
  6     l_x    t.x%type;
  7     cursor main is
  8        select * from t1;
  9  begin
 10      open main;
 11      loop
 12          fetch main bulk collect into l_data limit 1000;
 13
 14          for i in 1 .. l_data.count
 15          loop
 16          if ( mod(l_data(i),100) != 0 )
 17          then
 18             insert into t values(l_data(i), 'x' );
 19          end if;
 20          end loop;
 21          exit when main%notfound;
 22      end loop;
 23      close main;
 24  end;
 25  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec runstats_pkg.rs_stop(30000)
Run1 ran in 591 hsecs
Run2 ran in 714 hsecs
run 1 ran in 82.77% of the time

so the bulk inserts are measurably faster -- the more data you insert, the
more indexes you have -- the faster they will be as opposed to single rows

Name                                  Run1        Run2        Diff
STAT...recursive calls               1,093      31,406      30,313
STAT...execute count                   129      30,462      30,333
LATCH.shared pool                      722      31,066      30,344
STAT...buffer is not pinned co      30,643         278     -30,365
STAT...table scans (short tabl      30,369           4     -30,365
STAT...table scan rows gotten       61,108      30,743     -30,365
STAT...table scan blocks gotte      30,446          81     -30,365
STAT...no work - consistent re      30,544         179     -30,365
LATCH.library cache                  1,379      62,015      60,636
LATCH.library cache pin                915      61,560      60,645
STAT...calls to get snapshot s      91,726      30,688     -61,038
STAT...session pga memory                0      65,536      65,536
STAT...consistent gets              91,830         740     -91,090
STAT...session logical reads       186,862      95,736     -91,126
LATCH.cache buffers chains         596,611     415,418    -181,193
STAT...redo size                15,580,544  15,989,964     409,420

look at the redo difference here as well

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
677,178     649,052     -28,126    104.33%

PL/SQL procedure successfully completed.

the bulk inserts are better

October 14, 2003 Sam -- Thanks for the question regarding
      "generating html with utl_file package", version 8.0.4

      You Asked
Hi Tom!
would you be kind enough to give me step-by-step aproaches to
writing an util_file package that generates an html file, not
just html file but one with an image like gif or jpg.
If it is possible, also, can you explain to me how an utl_file
stored proc is tested.
Lastly, a detailed sample code would be highly appreciated.
Thank you,
sam

      and we said...
Well, the step by step for getting using UTL_FILE to write a
file full of html is at:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:347617533333
Now, that depended on having HTP/HTF (the OAS web toolkit
installed).  You don't have to do that -- you can just call
"putline" with whatever HTML you want.  It just shows how to use
utl_file to create an OS file -- after that -- its up to you
what you want to write.

To have an html file with an image, you'll just:

  utl_file.put( l_output, '<img src=foo.gif>' );

you don't put an image into HTML, you link to it -- the browser
pulls them together.

As for testing a utl_file stored procedure -- I just run them in
sqlplus and see what they do...

You can search for UTL_FILE in my search area for more UTL_FILE
examples -- i have lots of them.
Review &
      Followup

      Rating: 5
Image from
      Database  October 14, 2003
Reviewer:
      A reader
Hi Tom,
If the image too is stored in database, raw or blob, what would are options be,
to include this image in the HTML page?
If an image would have to be extracted to an OS file, would you still recommend
to store these in the database or to keep them in the OS files?
Thanks

          Followup:

the options depend on your tools and what you are running.

eg: mod_plsql -- as simple as a stored procedure that returns a blob.

does the file need to be extracted to the OS on the client, yes database storage
is the way to go (you need to move it anyway)

does the file need to be extracted to the OS on the server -- we'd need to have
more information such as

o why
o how often

October 14, 2003 Carlos -- Thanks for the question regarding "How
      do you subtract the current row with the following row?", version 8.1.7

      You Asked
Hi TOM:

I have a problem, because I have a table like this:
    situation           date
  ------------        --------
       S1             12-JAN-02
       S2             14-JAN-02
       S3             15-JAN-02
       S4             20-JAN-02
       .                  .
       .                  .
       .                  .

and I need to obtain a result like this

     situation         subtract(following_date - current_date)
   ------------       ----------------------------------------
       S1                        2
       S2                        1.23
       S3                        5.2
       ...
and so on. That's why I made a query following and example that I saw in the
magazine called "ORACLE" March/Abril, where you wrote an article with the title
"On Blocks, Messages Rows, and Queues" and inside it, I found something about
analytical functions, but It didn't work for my case, because I used a simple
subtraction instead of an analytical funtion, but I need something like that...

Could you help me please??

Thanks in advance...

      and we said...
You just need the LEAD function.  Analytic functions ROCK:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( s varchar2(2), d date );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set nls_date_format =
'dd-mon-yy hham';

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 's1', '12-jan-02' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 's2', '14-jan-02' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 's3', '15-jan-02 6am'
);

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 's4', '20-jan-02 11am'
);

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select s, round( lead(d) over ( order by d) -
d, 2 ) diff
  2    from t
  3  /

S        DIFF
-- ----------
s1          2
s2       1.25
s3       5.21
s4

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

Review &
      Followup

      Rating: 5
Absolutly
      great, thanks Tom  July 21, 2002
Reviewer:
      Marc  from Switzerland

      Rating: 3
It was useful,
      thanks so much  July 21, 2002
Reviewer:  Carlos
      Camargo  from PERU
I'm going to learn more about analytical functions..

Thanks..

      Rating: 3
I have a
      problem and I don't know how to solve it...  July 21, 2002

Reviewer:  Carlos Camargo  from PERU
When I used the analytical function, I got this error:

ORA-00439: feature not enabled: OLAP Window Functions

How can I enable this feature?

Thanks in advance..

          Followup:

You need Oracle Enterprise Edition.

      Rating: 4
What about
      this?  July 22, 2002
Reviewer:  Ed
       from UK
Tom,

Wouldn't this work, or is 'order by in an inline view' only available in
Enterprise Edition?

SELECT q1.s,
       q2.d-q1.d diff
  FROM (SELECT s,
               d,
               rownum r
          FROM t
      ORDER BY d) q1,
       (SELECT s,
               d,
               rownum r
          FROM t
      ORDER BY d) q2
 WHERE q1.r = q2.r(+)-1
/

          Followup:

The "concept" works (your syntax wont -- explain in a moment).

It'll be really slow of course.

You need to order by AND THEN assign a rownum, so, it'll be more like:

select q1.s, q2.d - q1.d diff
  from ( select t.*, rownum r1 from ( select * from t order by d ) t ) q1,
       ( select t.*, rownum r2 from ( select * from t order by d ) t ) q2
 where q1.r = q2.r(+)-1
/

      Rating: 4
How to make it
      work within a PL/SQL block  July 22, 2002

Reviewer:  Subhro  from Mumbai, India
Tom,

I was trying to execute the analytical function lead within a PL/SQL block, but
could not make it work the way it did as an Single SQL statement

SQL> create table emp(emp_no Number, Name VARCHAR2(20), hiredate date);

Table created.

SQL> desc emp;
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 EMP_NO                                             NUMBER
 NAME                                               VARCHAR2(20)
 HIREDATE                                           DATE

SQL> insert into emp values(1000, 'Michael Owen', '01-MAR-1999');

1 row created.

SQL> insert into emp values(1001, 'David Beckham', '25-MAY-1999');

1 row created.

SQL> insert into emp values(1002, 'Ronaldinho', '03-MAR-2000');

1 row created.

SQL> commit;

Commit complete.

SQL> select * from emp;

    EMP_NO NAME                                     HIREDATE
---------- ---------------------------------------- ---------
      1000 Michael Owen                             01-MAR-99
      1001 David Beckham                            25-MAY-99
      1002 Ronaldinho                               03-MAR-00

SQL> select emp_no, (lead(hiredate) over(order by emp_no) - hiredate) days from
emp;

    EMP_NO       DAYS
---------- ----------
      1000         85
      1001        283
      1002

SQL> begin
  2  for x in (select emp_no, (lead(hiredate) over(order by emp_no) - hiredate)
days from emp)
  3  loop
  4     dbms_output.put_line(x.emp_no||'  '||x.days);
  5  end loop;
  6  end;
  7  /
for x in (select emp_no, (lead(hiredate) over(order by emp_no) - hiredate) days
from emp)
                                         *
ERROR at line 2:
ORA-06550: line 2, column 42:
PLS-00103: Encountered the symbol "OVER" when expecting one of the following:
. ( ) , * % & | = - + < / > at in mod not range rem => ..
<an exponent (**)> <> or != or ~= >= <= <> and or like
between is null is not || is dangling

What could be the possible cause of this error...

          Followup:

read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3027089372477

      Rating: 5
Analytics rock
      (and so do guitars - but first you have to learn to
      play!)  October 14, 2003
Reviewer:  Neil
       from UK
Tom,
I have a set of rows like this:

LOG_DATE  LOG_LINE LOG_MODULE LOG_DESC
--------- -------- ---------- ----------------------------
01-OCT-03        1 CBP12600   Started  01-OCT-2003 09:50:34
01-OCT-03        2 CBP12600   Finished 01-OCT-2003 09:54:23
02-OCT-03        1 CBP12600   Started  02-OCT-2003 09:18:07
02-OCT-03        2 CBP12600   Finished 02-OCT-2003 09:20:08
06-OCT-03        1 CBP12600   Started  06-OCT-2003 14:47:55
06-OCT-03        2 CBP12600   Finished 06-OCT-2003 14:47:58

From which I wish to create a set of rows like this:

LOG_DATE   LOG_MODULE DURATION
---------  ---------- --------
01-OCT-03  CBP12600   3:49     i.e. (09:54:23 - 09:50:34)
02-OCT-03  CBP12600   2:01          (09:20:08 - 09:18:07)
06-OCT-03  CBP12600   0:03          (14:47:58 - 14:47:55)

This would enable me to present the performance over time of any given batch
module as a graph using Discoverer. My current effort involves creating a view
from a pl/sql function viz:
select * from TABLE(f_log_report()), and then querying the view for the
required module.
Could this reasonably be achieved using analytics? I haven't
had much luck! The log_date column contains the time portion, so it's not
necessary to go extracting it from log_desc. I'm using 9.2.0.3.0

          Followup:

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> alter session set nls_date_format='dd-mon-yy hh24:mi:ss';

Session altered.

ops$tkyte@ORA920LAP> select *
  2    from (
  3  select trunc(log_date) log_date,
  4         log_module,
  5             log_date start_log_date,
  6             lead(log_date) over (partition by log_module,trunc(log_date)
  7                                  order by log_line) end_log_date
  8    from t
  9         )
 10   where end_log_date is not null
 11  /

LOG_DATE           LOG_MODU START_LOG_DATE     END_LOG_DATE
------------------ -------- ------------------ ------------------
01-oct-03 00:00:00 CBP12600 01-oct-03 09:50:34 01-oct-03 09:54:23
02-oct-03 00:00:00 CBP12600 02-oct-03 09:18:07 02-oct-03 09:20:08
06-oct-03 00:00:00 CBP12600 06-oct-03 14:47:55 06-oct-03 14:47:58

      Rating:
      5
Great!  October 15, 2003
Reviewer:
      Neil  from UK
After all the problems I had with this, the solution is surprisingly simple!

October 14, 2003 Jose -- Thanks for the question regarding
      "Retrieving ARBITRARY Numbers with a select statement from dual", version
      8.1.7

      You Asked
Why it happens ?

SQL> SELECT 1,2,3 FROM DUAL
  2  GROUP BY CUBE(1,2);

         1          2          3
---------- ---------- ----------
         1          2          3

SQL> SELECT * FROM
  2  (SELECT 1,2,3 FROM DUAL
  3   GROUP BY CUBE(1,2,3)
  4  );

         1          2          3
---------- ---------- ----------
         1          2          3
         1          2          3
         1          2          3
         1          2          3
         1          2          3
         1          2          3
         1          2          3
         1          2          3

8 ROWS SELECTED

and then

SQL> select rownum from
  2  (select 1,2,3 from dual
  3   group by cube(1,2,3)
  4  );

    ROWNUM
----------
         1
         2
         3
         4
         5
         6
         7
         8

8 rows selected.

      and we said...
I think you are seeing a side effect of DUAL -- the optimizer realizes dual is a
"magic" special table and does things different.  The second and third cases are
what you should expect.

Consider the following examples (WARNING, if you try this on your machine MAKE
SURE THAT DUAL ENDS UP WITH 1 ROW!!!!!! when you are done)

ops$tkyte@DEV816> create table t ( x int );
Table created.

ops$tkyte@DEV816> insert into t values ( 1 );
1 row created.

ops$tkyte@DEV816> select 1,2,3 from t group by cube( 1,2,3)
  2  /

         1          2          3
---------- ---------- ----------
         1          2          3
         1          2          3
         1          2          3
         1          2          3
         1          2          3
         1          2          3
         1          2          3
         1          2          3

8 rows selected.

ops$tkyte@DEV816> select * from ( select 1 a,2 b,3 c from t group by cube(
1,2,3) )
  2  /

         A          B          C
---------- ---------- ----------
         1          2          3
         1          2          3
         1          2          3
         1          2          3
         1          2          3
         1          2          3
         1          2          3
         1          2          3

8 rows selected.

ops$tkyte@DEV816> select 1,2,3 from dual group by cube( 1,2,3)
  2  /

         1          2          3
---------- ---------- ----------
         1          2          3

This is happening because the optimizer saw DUAL at the top level as the only
table and said "i know this is going to be one row"

ops$tkyte@DEV816> select * from ( select 1 a,2 b,3 c from dual group by cube(
1,2,3) )
  2  /

         A          B          C
---------- ---------- ----------
         1          2          3
         1          2          3
         1          2          3
         1          2          3
         1          2          3
         1          2          3
         1          2          3
         1          2          3

8 rows selected.

That happened because DUAL was not in the top level AND the inline view had to
be materialized -- it could not be merged -- hence we got the 8 rows

Now for the "trick" -- lets do stupid tricks with DUAL.  Beware -- you wan
dual to have exactly 1 row when you are DONE!

ops$tkyte@DEV816> insert into dual values ( 'x' );
1 row created.

ops$tkyte@DEV816> select count(*) from dual;

  COUNT(*)
----------
         2

count says two but...

ops$tkyte@DEV816> select rownum, dummy from dual;

    ROWNUM D
---------- -
         1 X

Select says one

ops$tkyte@DEV816> select * from ( select dummy from dual );

D
-
X

Select from inline view says 1 as well -- thats because this inline view was
"mergable".  So, lets make it NOT be mergable:

ops$tkyte@DEV816> select * from ( select rownum, dummy from dual );

    ROWNUM D
---------- -
         1 X
         2 x

Ahh, there they are -- two rows in DUAL.  The addition of ROWNUM (or an order
by, group by, etc) made this inline view "non-mergable".  It was materialized
and then selected from.

ops$tkyte@DEV816> declare
  2          x date := sysdate;
  3  begin
  4          null;
  5  end;
  6  /
declare
*
ERROR at line 1:
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at "SYS.STANDARD", line 1014
ORA-06512: at line 2

But some things will still see >1 row sometimes. That shows that defaults like
SYSDATE and USER are really done via a "select SYSDATE into from dual" under the
covers...

ops$tkyte@DEV816> begin
  2          for x in ( select * from dual )
  3          loop
  4                  dbms_output.put_line( x.dummy );
  5          end loop;
  6  end;
  7  /
X

PL/SQL procedure successfully completed.

but other things still only see 1 row...

ops$tkyte@DEV816>
ops$tkyte@DEV816> rollback;

Rollback complete.

rollback -- thats IMPORTANT!

Review &
      Followup

      Rating:
      2
Ok  October 12, 2003
Reviewer:  Mary
       from Boulder,USA
Dear Sir,
1)Can we use dual table to print numbers from 1 to 10 using
static sql statement?
2)for x in (select * from emp) loop
   dbms_output.put_line(x.*) -- Is this possible?
  end loop;
 Thanks in advance.

          Followup:

1) yup

  1  select rownum
  2  from ( select 'a' from (select * from dual where rownum=1) group by
cube(1,2,3,4) )
  3* where rownum <= 10
ops$tkyte@ORA920> /

    ROWNUM
----------
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10

10 rows selected.

using nested inline views like that.

2) nope.

      Rating:
      4
Nice  October 14, 2003
Reviewer:
      Mary  from Boulder,USA
Dear Sir,
Thanks for your response.For the first question Do you have
a simpler way to do that? since it contains some KLOC(Kilo
LinesOfCode).For the second question I want to print the
entire row like
 for x in (select * from emp) loop
  Dbms_output.put_line(-- I want entire row to be printed)
   -- Do I have to list the column names to do that?Is there
any other way to do it?
Thanks in advance.

          Followup:

you can use dbms_sql to procedually access the columns

search this site for

dump_csv

for example.....(dumps to file but same concept)

October 14, 2003 sj -- Thanks for the question regarding "when
      other than exception, and raise application error", version 8.1.7

      You Asked
Tom
1.
I have a procedure, where in I drop partitions every day and add partitions.

My question is about the exception section.

Do I have to give specific exception conditions to handle the erros which are
possible while dropping and adding partitions, or should I leave it by just
giving the when others then exception, or should I leave it alone by not giving
the exception section at all.

what are the possible erros which can occur while trying to create a partition
and trying to drop a partition.

2.
The use of pragma exception init is very clear, but Why do you have to use a
raise application error. Can I just use a regular exception, and raise it when I
require to. I dont see the specific use of raise application error.

what is the advantage of raise application error over a regular exception which
you declare in the exception section and use it.
(the only advantage seems to be that you can assign a number(20001 to 20999) to
the error.)

correct me if Iam wrong.

      and we said...
1)
A when others is almost always a BUG unless it is immediately followed by a
RAISE.

The point of an exception block is to catch exceptional conditions you are
EXPECTING, handle them gracefully and continue.

For example, lets say you have a procedure that will either INSERT a new record
or UPDATE an existing one depending on whether or not it exists.  You could
code:

   begin
      insert into t ( columns.... ) values ( values ..... );
   exception
      when dup_val_on_index then -- record already exists, lets update it
           update t set .... = .... where ....;
   end;

Now, if that was coded:

   begin
      insert into t ( columns.... ) values ( values ..... );
   exception
      when dup_val_on_index then -- record already exists, lets update it
           update t set .... = .... where ....;
      when others then
           null;
   end;

that would be a bug.  The when others would fire upon some spurious --
un-expected error and the record would be neither added nor updated.  It would
be skipped.

Same with your routines, if you have a when others -- and don't do anything
meaningful in it (eg: email yourself a notification that it failed, log a
message using utl_file or an autonomous transaction, etc -- it is an error that
will go undetected.

I truly wish we didn't even support WHEN OTHERS.

You should only catch the exceptions you are expecting and can do something
about.  Let the others propagate out so you can detect them (so you see them)

2) the use of pragma exeception init and raise_application_error are not
dependent on each other whatsoever.

I use raise_application_error much much more then RAISE <named exception>
myself.  I like to see:

ops$tkyte@ORA8I.WORLD> begin
  2  raise_application_error( -20001, 'Something really bad happened, here is
some text about it' );
  3  end;
  4  /
begin
*
ERROR at line 1:
ORA-20001: Something really bad happened, here is some text about it
ORA-06512: at line 2

over:

ops$tkyte@ORA8I.WORLD> declare
  2    something_really_bad exception;
  3  begin
  4    raise something_really_bad;
  5  end;
  6  /
declare
*
ERROR at line 1:
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at line 4

myself anyday.

The advantage of raise application error is not only the code (which is huge)
but the message you specify as well.  Much more meaningful then

ORA-06510: PL/SQL: unhandled user-defined exception

So -- if the error is going to propagate OUT OF plsql (back to a client) i like
to catch it and re-raise it with raise_application_error (for a good example of
this, see my section on UTL_FILE in my book in the appendix on necessary
supplied packages.  UTL_FILE raises about 5 or 6 different "user defined
exceptions" -- i use raise_application_error to turn them into meaningful error
messages.

If the error is going to be caught and handled by PLSQL -- leave it as an
exception.

followup to comment one

What I'm trying to say there is - if the error is raised, caught and handled
(not re-raised to the client) by PLSQL -- and the error was a USER DEFINED
EXCEPTION (like the ones thrown by UTL_FILE for example) -- there is obviously
no need to use raise_application_error.

On the other hand, if the error is going to be propagated BACK to the client --
I prefer to catch the USER DEFINED EXCEPTIONS by name and use raise application
error to turn them into something MEANINGFUL for the client, for example:

exception
    when utl_file.invalid_path then
        raise_application_error(-20001,
            'INVALID_PATH: File location or filename was invalid.');
    when utl_file.invalid_mode then
        raise_application_error(-20002,
            'INVALID_MODE: The open_mode parameter in FOPEN was invalid.');
    when utl_file.invalid_filehandle then
        raise_application_error(-20002,
            'INVALID_FILEHANDLE: The file handle was invalid.');
    when utl_file.invalid_operation then
        raise_application_error(-20003,
            'INVALID_OPERATION: The file could not be opened or
        operated on as requested.');
    when utl_file.read_error then
        raise_application_error(-20004,
            'READ_ERROR: An operating system error occurred during
        the read operation.');
    when utl_file.write_error then
        raise_application_error(-20005,
            'WRITE_ERROR: An operating system error occurred during
        the write operation.');
    when utl_file.internal_error then
        raise_application_error(-20006,
            'INTERNAL_ERROR: An unspecified error in PL/SQL.');
end;

is a block I use in all of my utl_file code.  That gives the CLIENT application
something useful (error code/message) not just:

ORA-06510: PL/SQL: unhandled user-defined exception

Review &
      Followup

      Rating: 4
good answer,
      but   August 12, 2001
Reviewer:
      A reader
"If the error is going to be caught and handled by PLSQL -- leave it as an
exception."

what do you mean, by caught and handled by plsql , leave it as an exception,
all errors are captured by pl/sql, and we use  user defined exceptions or raise
application error ,
correct me

      Rating:
      3
MGB  August 13, 2001
Reviewer:
      Manoj  from Singapore
Tom,
 I was just thinking what if we code these kind of general exception handling
blocks as individual functions , as they will be used in many places , in ur
case wherever UTL_FILE is used , and pass the exception as an argument to the
function and use this function to display the User friendly Error message.

May be u can categorize this into silly suggestion (:

Thanks

      Rating: 4
Sound's like a
      good idea...  July 12, 2002
Reviewer:
      Robert  from USA
Tom,

What do you think of Manoj's suggestion.
I have tried to think of a way to do something like this myself.

Thanks,

Robert.

          Followup:

I didn't really follow what he was getting at:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function foo ( x in
exception ) return varchar2
  2  as
  3  begin
  4          return 'x';
  5  end;
  6  /

Warning: Function created with compilation errors.

ops$tkyte@ORA817DEV.US.ORACLE.COM> show err
Errors for FUNCTION FOO:

LINE/COL ERROR
-------- -----------------------------------------------------------------
1/21     PLS-00103: Encountered the symbol "EXCEPTION" when expecting one
         of the following:
         out <an identifier> <a double-quoted delimited-identifier>
         LONG_ double ref char time timestamp interval date binary
         national character nchar
         The symbol "<an identifier> was inserted before "EXCEPTION" to
         continue.

you cannot "pass" an exception to a function.

      Rating: 5
Could this be
      it?  June 19, 2003
Reviewer:  Scott
       from Perth, WA
G'day

I think Manoj was trying to say that if someone has many programs that need the
same exception handling for utl_file, instead of copying the same exception code
to all the different programs, is there a way to have a generic piece of code to
capture the exceptions and treat them the same in each different program.

Capeche?

      Rating: 5
Is this
      adequate..  October 13, 2003
Reviewer:
      A reader
Tom, we are creating a bunch of database triggers. We are planning the following
common exception section in all the triggers. Please opine if it is adequate.

Exception

when others then
raise_application_error( -20001,'Error Encountered'||sqlcode||'-'||sqlerrm)

Is there anything else to make it more efficient.

          Followup:

sure, mine would be:

<this space intentionally left blank>

that "common sense" trigger just seems to be extra, confusing, information
hiding keystrokes.  I would never consider it ever.

      Rating:
      3
  October 14, 2003
Reviewer:
      A reader
Are you suggesting that there should be no exception section at all? Can you
please support your recommendation with an illustration.

          Followup:

that is exactly what I'm saying -- not even suggesting -- just plain out SAYING.

I hate when others, wish we didn't have it.

      Rating:
      2
  October 14, 2003
Reviewer:
      A reader
In that case how can we capture errors occurring in the databaset triggers in
nice manner..

          Followup:

how is that "nice"?

and -- how often do you expect the trigger to fail?

and -- for what reasons?

you use exceptions to catch ERRORS YOU EXPECT (eg: no_data_found, you might
catch that, deal with it and ignore it

Tell me, which looks "nicer" to you?

ops$tkyte@ORA920LAP> create table t ( x int primary key );

Table created.

ops$tkyte@ORA920LAP> insert into t values ( 1 );

1 row created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create table t1 ( x int );

Table created.

ops$tkyte@ORA920LAP> create table t2 ( x int );

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create trigger t1_trigger
  2  before insert on t1 for each row
  3  begin
  4     insert into t values (:new.x);
  5  end;
  6  /

Trigger created.

ops$tkyte@ORA920LAP> create trigger t2_trigger
  2  before insert on t2 for each row
  3  begin
  4     insert into t values (:new.x);
  5  exception
  6  when others then
  7  raise_application_error( -20001,'Error
Encountered'||sqlcode||'-'||sqlerrm);
  8  end;
  9  /

Trigger created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> insert into t1 values ( 1 );
insert into t1 values ( 1 )
            *
ERROR at line 1:
ORA-00001: unique constraint (OPS$TKYTE.SYS_C006890) violated
ORA-06512: at "OPS$TKYTE.T1_TRIGGER", line 2
ORA-04088: error during execution of trigger 'OPS$TKYTE.T1_TRIGGER'

ops$tkyte@ORA920LAP> insert into t2 values ( 1 );
insert into t2 values ( 1 )
            *
ERROR at line 1:
ORA-20001: Error Encountered-1-ORA-00001: unique constraint
(OPS$TKYTE.SYS_C006890) violated
ORA-06512: at "OPS$TKYTE.T2_TRIGGER", line 5
ORA-04088: error during execution of trigger 'OPS$TKYTE.T2_TRIGGER'

I, as a developer, would INFINITELY prefer getting ORA-00001 -- that MEANS
something. You would give me ORA-20001 -- what the HECK is that.  I'd have to
parse the error message to figure it out.

don't use when others.

      Rating:
      5
  October 14, 2003
Reviewer:
      A reader
Excellent !!

      Rating: 4
Framework and
      Exception  October 14, 2003
Reviewer:  Alex
      V  from Montreal, Canada
Tom,

I would agree with you points against WHEN OTHERS and
NOT catching basic Oracle's exceptions in your examples.
Still, for high-level components, it make sence. For example, OrderManager
component asks OrderShipmentManager
component to send an Order. I do not want getting ORA-00001, I want 'ORA-30004,
Order was not sent, reason 4, shipping is on strike :-)'. And considering
posible bugs in complex OrderShipmentManager, I would put WHEN-OTHERS
to orderly rollback and inform client that there is some
temporary unavailablilities...

In short, my points: basic exception - on low-level, user-defined exception and
WHEN_OTHERS - for high-level domain and client communication. (and mix in
between).

Have a nice day,

Alex V.

          Followup:

Absolutely -- as coded above -- everything became a MEANINGLESS 20001

I would not use a when others in your example -- i would catch errors i expected
logically might happen.  if the error was ora-1234 -- would you still tell them
"strike"???

If you have a good solid reason for capturing an error you logically think you
might get and can either

a) deal with it (eg: not an error, just an exception that can be handled)
b) map it to an infinitely more meaningful error message (or just log it and re
RAISE; it )

go for it.  otherwise, leave it be.

I wish we didn't have when others not because it should never be used but
because it is SO ABUSED that I'd rather not have it at all (at the expense of
the times it would be a logical construct to have).

      Rating:
      4
  October 15, 2003
Reviewer:  Alex
      V.  from Montreal Canada
<quote>
I would not use a when others in your example -- i would catch errors i expected

logically might happen.  if the error was ora-1234 -- would you still tell them
"strike"???
</quote>

The fact of life is that OrderShipmentManager
(for example) was coded by another person and not well
documented. Or, ora-doc for this componend discribes 25
exception and I can re-submit (recover) only 3 of them
and all others (cut by when_other) will be "service temporary unavailable"
for a client. The same message will be for possible bugs
(we are in real world, aren't we :-)). Logging is
obligatory with no questions.

In another words, WHEN OTHER is appropriate when you can not trust somebody's
else work (ex: high-level complex business domain components) and have ability
to post
"service temporary unavailable" to client. I see these points as a big
difference between low-level
pl/sql fragments around database and coding big domain components.

Of course, nothing is 100% and implementation depends on situation in hand. :-)

Alex V.

Thank you for "Effective Oracle by design"!

October 14, 2003 Adam -- Thanks for the question regarding "CLOB's
      - lower and UPPER", version 9.0.1

      You Asked
Apart from some annoying looping techniques how can I get around the lack or
lower or UPPER functions when looking through CLOBS?

Any clever ideas would be much appreciated.

Thanks

      and we said...
Hey, you are in 9i -- use it!

ops$tkyte@ORA9I.WORLD> drop table t;

Table dropped.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> create table t ( x clob );

Table created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> insert into t values ( 'Hello World' );

1 row created.

ops$tkyte@ORA9I.WORLD> insert into t values ( 'Goodbye to you' );

1 row created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> column x format a20
ops$tkyte@ORA9I.WORLD> select upper(x) x, lower(x) x, substr(x,5,2) x,
initcap(x) x
  2    from t
  3  /

X                    X                    X                    X
-------------------- -------------------- --------------------
--------------------
HELLO WORLD          hello world          o                    Hello World
GOODBYE TO YOU       goodbye to you       by                   Goodbye To You

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> select *
  2    from t
  3   where upper(x) like '%TO%'
  4  /

X
--------------------
Goodbye to you

ops$tkyte@ORA9I.WORLD>

9i has improved lob integration.  This'll work dandy for lobs of reasonable
sizes (I'd say 64k or less, much less really, else performance will obviously
just go down the tubes).

Above that -- I would be (and I do, here on asktom) using Oracle Text.  Very
fast keyword and fancier searching.
Review &
      Followup

      Rating:
      5
Thanks  September 04, 2002
Reviewer:
      A reader
How annoying.. I didn't realise.  We only went to 9.0.1 in the last 2 weeks :(

I can rewrite all this 8i legacy code now in a far more pleasant manner.

Many Thanks

          Followup:

check out

http://asktom.oracle.com/~tkyte/9iTrain/index.html
for a "brief" overview of 9iR1 and R2 new features (bummer that months after
9iR2 went production -- you are developing on 9iR2!)

      Rating: 5
A Related
      question  October 13, 2003
Reviewer:  Nasir
      Sarwar  from Houston, TX, USA
Sir,
I created a table having clob as below. Then ran the following query. The query
returns the result is 00.00.06 seconds. However, it took 3 minutes to returned
to for SQL> prompt. Please advise.
Here is my query to create the myclob table:
create table myclob
(object_id number not null,
attribute_id number not null,
abstract clob)
STORAGE
(INITIAL 100k
NEXT 100K
PCTINCREASE 0
MAXEXTENTS 100
)
TABLESPACE TABLE6_TBS
lob (ABSTRACT) store as ABSTRACT
(tablespace TABLE6_TBS storage
(initial 30K next 30K pctincrease 30 maxextents 3)
index (tablespace INDEX1_TBS storage
(initial 40K next 40K pctincrease 40 maxextents 4)))
/
This how I inserted data from longs table to myclob table:
insert into myclob select * from longs
/
Now myclob has 1856431 row.
Following query returns data in 00.00.06 seconds but it take 3 minutes to
display sql*plus prompt:
SQL> select * from myclob where abstract like '%CHANGED 6 OCTOBER 1967%';
output:
OBJECT_ID ATTRIBUTE_ID ABSTRACT
--------- ------------ ------------------------
511154 844 CHANGED 6 OCTOBER 1967
511340 844 CHANGED 6 OCTOBER 1967
511713 844 CHANGED 6 OCTOBER 1967
Elapsed: 00:03:21.08

          Followup:

how do you know that query takes 0.06 seconds?????

      Rating: 3
RE: A Related
      question   October 13, 2003
Reviewer:  Nasir
      Sarwar  from Houston, TX, USA
Tom, thanks for a quick response.
SQL query on myclob table displayed  three rows in 00:00:00:06 seconds. At the
same time OEM show that it is doing a full table scan. After 3 minutes, when the
table scan is done, the query is done and SQL prompt is displayed. On the other
hand same query on 'longs' table has elapse time around 00:00:00:09 seconds. I
though the elapse time on myclob table would be less than elapse time on 'longs'
table. I have been searching on askTom for a few days to find out a similar
case. But so far no luck. Your input on this issue will be grately appriciated.

          Followup:

I still have no idea how you are timing this down to this level of detail.

use TKPROF and make your point with that data.

      Rating: 3
RE: Related
      Question  October 14, 2003
Reviewer:  Nasir
      Sarwar  from Houston, TX , USA
TKPROF (both for 'longs' and 'myclob') show the following. It negates what I
claimed earlier. It is amazing that data from 'myclob' table was displayed in no
time (less than a second) though query finished in a few minutes. On the other
hand data is displayed and query is finished in same amount of time (less than a
second) for 'longs' table. Do I have to create an index on 'abstract' column
which is type clob? Yes, I know clob has its own index and handle. What am I
missing here?
TKPROF when query against 'myclob' table:
select *
from
 myclob where abstract like '%CHANGED 6 OCTOBER 1967%'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.25       0.28          1          1          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        4    202.71     214.58      24629    3737406          0           3
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        6    202.96     214.86      24630    3737407          0           3

Misses in library cache during parse: 1
Optimizer goal: RULE
Parsing user id: 27

Rows     Row Source Operation
-------  ---------------------------------------------------
      3  TABLE ACCESS FULL MYCLOB

********************************************************************************
TKPROF report when query against 'longs' table:
********************************************************************************

select *
from
 longs where abstract like '%CHANGED 6 OCTOBER 1967%'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.29       0.28          0          0          0           0
Execute      1      0.01       0.00          0          0          0           0
Fetch        2      0.92       2.75      15706      15717          0           3
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      1.23       3.04      15706      15717          0           3

Misses in library cache during parse: 1
Optimizer goal: RULE
Parsing user id: 27

Rows     Row Source Operation
-------  ---------------------------------------------------
      3  TABLE ACCESS FULL LONGS

          Followup:

ummm, you searched through 237 TIMES the data!

there be more "clob" data then long data.

      Rating: 3
How is it
      possible?  October 14, 2003
Reviewer:  Nasir
      Sarwar  from Houston, TX, USA
<<there be more "clob" data then long data>>
How is it possible?
Please note that I created myclob table table using the definition mentioned
above. Also this is how I inserted data in myclob table:
insert into myclob select * from longs;
Both 'longs' and 'myclob' table have same number of rows ie, select count(*)
returns 1856431 rows on both tables.
Seems like clob is not working well in this case. I don't know why.

          Followup:

i don't think the long query ever finished perhaps -- but anyway, I'd need a way
to reproduce your test.

given that a long is a long thing, one would certainly expect more then 15,717
consistent gets to scan 1.8 million rows!!!

that would imply about 118 rows per block.
meaning each row is like 50 bytes or so!

does that seem right to YOU

      Rating: 5
Can I send you
      test data?  October 14, 2003
Reviewer:  Nasir
      Sarwar  from Houston, TX, USA
<<I'd need a way to reproduce your test>>
Can I email you export dump file of longs table? It is just 15K zip file. I have
created a test table (named longs with just 4561 records in it :)

          Followup:

if your export is just 15k?  what could be in it?

sure, email it.  tkyte@oracle.com

(no, that is not an invite to the world to send me statspacks or traces!!!)

      Rating:
      5
  October 15, 2003
Reviewer:
      A reader
Tom,
In Nasir Sarwar's issue, the optimizer goal is showing as RULE. Also, there is
no analyze table... command. Would these make a difference?
Thanks

October 14, 2003 Munzer -- Thanks for the question regarding
      "PL/SQl server pages", version 8.1.7

      You Asked
Tom:

I want to ask about the advantages of PL/SQL server pages (PSP).

I have an application that uses stored procedures (htp) to create html pages.
Is there any advantage of converting this to PSP or not.

Can you convert stored procedure to PSP automatically and is it worth doing.
Would it make things easier to edit, faster etc..

Thank you,

      and we said...
I'm not a fan (personally) of mixing procedural code with HTML.  I prefer
(opinion) to generate HTML from procedural code.  I don't find it intuitive to
mix them together -- i can read procedural code, I cannot read a mixture of HTML
and plsql or java or VB.

other people love the asp/jsp/psp concept.  It is a matter of taste I support.
I see no advantage or disadvantage here since both end up being a stored
procedure at the end of the day.
Review &
      Followup

      Rating: 5
pl/sql server
      pages  March 14, 2002
Reviewer:  munz
       from reston, usa
TOm:

Thanks. But is it true that if you develop with PSP you can control development
effort bette by checking in files into version control software and checking it
out before anone can edit it.

By using stored procedures is there any way to track changes for the code if 3
developers are editing the same procedure.

Thanks,

          Followup:

Umm -- the same way you would with a PSP (a psp after all just generates a
stored procedure, nothing stopping developer X from reading out the source code
of a PSP in the database and modifying it).

You would store your code in some version control system. (just like a psp)

Your developers would check out the code from this system (just like a psp)

You would "outlaw" the editing of code directly in the database (just like you
would with a psp)

They could not change the same piece of code since its until version control.

      Rating:
      5
PSP  October 14, 2003
Reviewer:  mo

Tom:

I want to create an pl/sql application.  Since some screens are hard to do in
HTML manually we purchased Dreamweaver. If I am correct this makes it more
appropriate to use PSP.

But I am trying to envision how the PSP process will work differently than the
PL/SQL WEB TOOLKIT which I am used to.
Is this the correct flow/actions involved?

1.  I create the HTML file using dreamweaver on windows PC and name it with a
"filename.psp" extension.

2.  Now I add the plsql code I need to the file and save it.  Here I will not be
able to tell if my code is valid or not since I have not compiled it to a pl/sql
stored procedure.

3.  I ftp the "filename.psp" to a unix server where the database resides.  I run
"Loadpsp filename.psp" which creates a stored procedure.

4.  I find errors in my procedure.  I run "SQL navigator" on windows to debug
the pl/sql code and fix it?

5.  Now I have a stored procedure that is different than my original PSP file.
Do I have to manually update my psp file everytime I change a line of code? DO I
do that using Dreamweaver? It seems to me the synchronization between the psp
file and stored procedure is tedious process.

6.  Is it possible to do "Load psp" from windows prompt or SQL*PLUS or do I
always have to ftp the file to unix?

7.  Is it correct to say that with WEB pl/sql toolkit (htp) I can build many
variants of one HTML form by using IF condition to wite HTML tags or not.  So I
can hide/display a field based on a value of a parameter.

 Using PSP it seems all html code is startic at the top and all plsql code is at
the bottom assigning values for
variables.  But I can't hide an html line based on the value of input
parameters?

          Followup:

you should iterate over 3/4 to avoid 5.

you can loadpsp from windows if you have loadpsp installed -- it is doable over
the network.

7) sure, you procedurally decide what tags to print or not.

yes you can -- psp's have plsql anywhere you want - top, bottom, middle,
whatever.

      Rating:
      5
PSP  October 14, 2003
Reviewer:  mo

Tom:

1.  DO you know where I can get loadpsp to install on windows.  is it part of
oracle client software?

2.  It seems to me (if I am correct) it is pretty tedious to keep editing the
pls/sql code in the PSP and compile again everytime you have any kind of error.
So I would not be using any PL/sql debugger in this case as SQL navigator.

Is there any tools that are special for debugging pl/sql in the psp file.

3.  Do you personally favor using web toolkit (htp) over psp?  or
creating/editing the html in dreamweaver and then converting it using webalchemy
to htp statements.

Thank you,

          Followup:

1) if it is, it'll be in $oracle_home/bin.  if you have a client install (you
must) and it is not there, it must not be part of the client install.

2) up to you.  you should be putting teeny tiny snippets of plsql in mostly html
with psp's.  You can call packaged procedures and such -- which can be debugged
standalone.  Just use proper (modular) implementation techniques and you'll be
just dandy.

3) I use HTML DB.  Look around -- this site, it is as pretty as it gets.  I go
no fancier.  If only you could see my browser -- you might laugh.  I've blocked
all popups (i won't use sites that use them).  I've disabled animated gifs
(makes me dizzy).  My /etc/hosts file has a couple of hundred entries like this:

127.0.0.1   us.a1.yimg.com
127.0.0.1   us.a1.yimg.com
127.0.0.1   207-87-18-203.wsmg.digex.net
127.0.0.1   Garden.ngadcenter.net
127.0.0.1   Ogilvy.ngadcenter.net
127.0.0.1   ad.ca.doubleclick.net
127.0.0.1   ad.de.doubleclick.net
127.0.0.1   ad.doubleclick.net
127.0.0.1   ad.fr.doubleclick.net
127.0.0.1   ad.jp.doubleclick.net
.......

(those are all ad servers -- i don't get ANYTHING from them).  I make liberal
use of the Mozilla "block images from this server" to kill ads dead as I run
into them.  I have scripts "flashon" and "flashoff" (sort of like the guy in the
fantastic 4 -- johnny storm aka the human torch -- "flameon" and "flameoff").
My default is flashoff to avoid stupid moving flash screens (which are 99.99%
ads) and to "flashon" to put the plugins back when I want to see something.

Guess what I'm saying is -- I don't see the purpose of a HTML page sooooo
complex and fancy dancy that you need the likes of Dreamweaver to build it.
Sort of means to me that "the content on that page is so fluffy and non-relevant
that we must dress it up pretty to make it look important"

Just my 2cents, but then again, you see my favorite font is apparently 12 point
Courier :)

      Rating:
      5
PSP  October 14, 2003
Reviewer:  mo

Tom:

Thanks a lot for the answers but:

1.  Loadpsp is not in oracle_home/bin on my pc.  Database is on unix but I
hardly use unix.  I run sql*plus for windows and sql*navigator for windows.  In
this case, do I have to get this file from an oracle database installation on
windows server or oracle provides these utilities somewhere for download?

2.  What is HTML DB you mentioned? is this oracle portal? is it something I buy
from oracle or free? Does it create the HTML tags for you automatically?

Thank you,

          Followup:

1) they would be on install cds.  probably only come with "server".

2) html db is a cool new feature of 10g, it'll be available for 9iR2 as well.
see htmldb.oracle.com


Subject | Article | Last Updated (East coast time)
Enclosing special characters inside the WHERE clause | article only | Oct 15, 2003 07:42 am
How to drop a datafile ? | article only | Oct 15, 2003 07:51 am
How to move tables from one schema to another ? | article only | Oct 15, 2003 06:25 pm
root block, branch block , and leaf block within an index | article only | Oct 15, 2003 06:38 pm
Formatting numeric data using to_char | article only | Oct 15, 2003 06:48 pm
Which Indexes are being used by our Application? | article only | Oct 15, 2003 06:49 pm
BLOB arrays | article only | Oct 16, 2003 10:16 am
DML statement on a table | article only | Oct 16, 2003 10:19 am
Processing records in a specific sequence | article only | Oct 16, 2003 10:32 am
help with internationalization | article only | Oct 16, 2003 10:34 am
Your suggestions on OLAP technology | article only | Oct 16, 2003 10:36 am
How to find physical read/Writes made per second | article only | Oct 16, 2003 10:36 am
Can two sort operations share the same temp. extent? | article only | Oct 16, 2003 10:45 am
UNDO RETENTION | article only | Oct 16, 2003 10:49 am
What is a SID, how to change it, how to find out what it is. | article only | Oct 16, 2003 10:58 am
'Reverse' transposition SQL optimisation | article only | Oct 16, 2003 03:08 pm
Send data to MS Office 2000 (Excel) | article only | Oct 16, 2003 03:12 pm
SQL*Loader into multiple tables depending on column being NULL | article only | Oct 16, 2003 05:17 pm
Perl scripts | article only | Oct 16, 2003 05:28 pm
File Exists which is not a BFILE | article only | Oct 16, 2003 05:32 pm
Returning recordset from package | article only | Oct 16, 2003 09:00 pm
RE: Setting a default ORACLE_SID | article only | Oct 17, 2003 09:38 am
SGA question regarding the size | article only | Oct 17, 2003 09:51 am
Unable to Use Oracle 8.1.6 Features | article only | Oct 17, 2003 09:52 am
DBMS_JOB -- how to pass parameters to the job | article only | Oct 17, 2003 12:42 pm
Is there a bug in min/max when using decode? | article only | Oct 17, 2003 03:59 pm
Oracle Text - handling plurals | article only | Oct 19, 2003 06:19 pm
OS Authentication | article only | Oct 19, 2003 07:08 pm
Oracle 9i generates statistics automatically? | article only | Oct 20, 2003 08:18 am
Export the report to Excel format in OAS | article only | Oct 20, 2003 08:31 am
Advanced Queuing & PL/SQL Notification | article only | Oct 20, 2003 10:56 am
Question on Trigger's ! | article only | Oct 20, 2003 11:23 am
difference between count(1) and count(*) | article only | Oct 20, 2003 08:12 pm
No of occurences of a string within another string | article only | Oct 20, 2003 09:45 pm
Creating Temporary Table | article only | Oct 21, 2003 07:14 am
J2EE and Oracle's stored procedures | article only | Oct 21, 2003 07:19 am
Owa_sylk utility | article only | Oct 21, 2003 07:41 am
Seeing dbms_output buffer from another session? | article only | Oct 21, 2003 07:50 am
Limiting the number of returned rows | article only | Oct 21, 2003 04:55 pm
Best way to check for dbms_job completion? | article only | Oct 21, 2003 04:55 pm
Performance poor after analyzeing table | article only | Oct 21, 2003 05:08 pm
Creating custom filter | article only | Oct 21, 2003 05:47 pm
Checkpoint&logswitch -- what is their relationship. | article only | Oct 21, 2003 05:52 pm
Execute procedures concurently in a procedure | article only | Oct 21, 2003 09:51 pm
convert database server sysdate to GMT date and daylight saving aware | article only | Oct 21, 2003 09:52 pm
Column order in Index | article only | Oct 21, 2003 09:53 pm
row(s) 1 - 46 of 46
October 15, 2003 Bong -- Thanks for the question regarding "Enclosing special characters inside the WHERE clause", version version 7.3.2.1.1
You Asked
Dear Tom,

I would show an example on my problem and I hope you do have a
solution for this:

NAME IS "INT'L"

SELECT ENAME FROM LOCATION
WHERE L_NAME IS 'INT'L';

This command usually have an error in L_NAME because of the
apostrophe between INT and L. In my database INT'L is already
posted but I cannot make an inquiry on this because of error in
syntax.Do you have a special functions to treat this as a whole
word. The data cannot be changed to INTL for some special
instructions. If thats applicable it would be easy for me to
change.

I tried this : WHERE L_NAME IS "INT'L" substituting apostrophe
to quotation marks but error occurs too. I know in some language
has special function or characters used for this kind of
situation. Does sqlplus has too?

I am hoping of an immediate reply on my inquiry immediately.

Thank you and more power.

Bong
and we said...
If you must use a character string constant, it would be:

select * from T where name = 'INT''L';

two quotes = 1 quote in a character string constant.  You should
REALLY use bind variables in queries however -- not character
string constants.  So the above query should be:

select * from T where name = :x

and you would bind INT'L in for :x at runtime.  This is
infinitely more efficient.
Review & Followup
Rating: 5
October 18, 2001
Reviewer: A reader

Rating: 5
What about bind variables using web application ? October 15, 2003
Reviewer: Amit from Ohio, USA
Hi Tom,

is there a way I can get the following to work through JDBC ?

I will run stmt.executeUpdate() on the following :

String updateQuery=" execute immediate 'update s_uda_basic_info set " +
     " Name =:1"+","+
     " Address1 =:2"+","+
     " Address2 =:3"+","+
    " using "+
     (String) req.getParameterValues("Name")[0].trim()+","+
     (String) req.getParameterValues("Address1")[0].trim()+","+
     (String) req.getParameterValues("Address2")[0].trim()+","+
user_id;

Where the address field can have a ",", in which case there will be more 'using'
fields ( eg : ... using xyz,add1,more_add1,add2) .

My problem arose because if i don't use bind variables and a field comes in with
apostrophe ('), my update query fails.

Normally what is done in web based applications to take care of these special
characters ?

Thanks,
Amit

Followup:

umm, you use bind variables for performance and because anyother way is a bug in
your code!!!!!

but in jdbc, you just use prepared statements for this.

you would prepare a statement "update t set c1 = ?, c2 = ?, c3 = ?"

you would bind to it

you would execute it.

something like this:

  static void doPreparedStatement (Connection con, int count)
  throws Exception
  {
    long start = new Date().getTime();
    PreparedStatement ps =
       con.prepareStatement
       ("insert into testxxxperf " +
        "(id, code, descr, insert_user, insert_date)"
        + " values (?,?,?, user, sysdate)");

    for (int i = 0; i < count; i++)
    {
      ps.setInt(1,i);
      ps.setString(2,"PS - code" + i);
      ps.setString(3,"PS - desc" + i);
      ps.executeUpdate();
    }
    long end = new Date().getTime();
    con.commit();
    System.out.println
    ("pstatement " + count + " times in " + (end - start) + " milli seconds");
  }

Rating: 5
October 15, 2003
Reviewer: A reader

October 15, 2003 Frédéric -- Thanks for the question regarding "How to drop a datafile ?", version 7.3.4.4
You Asked
I have a tablespace with a few datafiles.
I have droped lots of tables in this tablespace and i just want
to keep only one datafile on this tablespace.
I know the name of the only datafile witch contains datas so i
no more need the others datafiles.
How can i do ?

I try to regenerate the controlfiles, it works but i can see
some lines with "MISSINGXXX" in the v$datafile view so i look
for another way(s) to do that because it doesn't seem very
"clean".

I know that it's possible to do that with Import/Export (Export,
Drop Tablespace, Import) but it's a big manipulation.

Do you have a solution to drop an empty datafile easily and
properly ?
and we said...
No, datafiles are not meant to be dropped once added.  Here is a
support note with some methods you can use.  (Doing an alter
database OFFLINE DROP is not a method either).

Before we start with detailed explanations of the process
involved, please note that Oracle does not provide an interface
for dropping datafiles in the same way that you could drop a
schema object such as a table, a view, a user, etc.  Once you
make a datafile part of a tablespace, the datafile CANNOT be
removed, although we can use some workarounds.

Article-ID:         <Note:111316.1>
Circulation:        PUBLISHED (EXTERNAL)
Folder:             server.Rdbms.DBA.Admin
Topic:              Managing Controlfiles and Datafiles
Title:              How to 'DROP' a Datafile from a Tablespace
Document-Type:      BULLETIN
How to 'DROP' a Datafile from a Tablespace
==========================================

PURPOSE
-------

This note explains how a datafile can be removed from a
database.

Since there can be confusion as to how a datafile can be dropped
because of the ALTER DATABASE DATAFILE OFFLINE DROP command,
this note explains what steps are needed to delete a datafile
and in contrast, what the OFFLINE DROP command is for.

SCOPE & APPLICATION
-------------------

There are two situations where people may want to 'remove' a
datafile from a tablespace:

1.  You've just mistakenly added a file to a tablespace, or
perhaps you made the file much larger than intended and now want
to remove it.

2.  You are involved in a recovery scenario and the database
won't startbecause a datafile is missing.

This article is meant to discuss circumstance 1 above.  There
are other articles that discuss recovery scenarios where a
database cannot be brought online due to missing datafiles.
Please see the 'Related Documents' section at the bottom of this
article.

How to 'Drop' a Datafile
-----------------------------

Before we start with detailed explanations of the process
involved, please note that Oracle does not provide an interface
for dropping datafiles in the same way that you could drop a
schema object such as a table, a view, a user, etc.  Once you
make a datafile part of a tablespace, the datafile CANNOT be
removed, although we can use some workarounds.

Before performing certain operations such as taking
tablespaces/datafiles offline, and trying to drop them, ensure
you have a full backup.

If the datafile you wish to remove is the only datafile in that
tablespace, simply drop the entire tablespace using:

DROP TABLESPACE <tablespace name> INCLUDING CONTENTS;

You can confirm how many datafiles make up a tablespace by
running the following query:

select file_name, tablespace_name
from dba_data_files
where tablespace_name ='<name of tablespace>';

The DROP TABLESPACE command removes the tablespace, the
datafile, and the tablespace's contents from the data
dictionary.  Oracle will no longer have
access to ANY object that was contained in this tablespace.  The
physical datafile must then be removed using an operating system
command (Oracle NEVER physically removes any datafiles).
Depending on which platform you  try this on, you may not be
able to physically delete the datafile until Oracle is
completely shut down (For example, on Windows NT, you may have
to shutdown Oracle AND stop the associated service before the
operating system will allow you to delete the file - in some
cases, file locks are still held by Oracle)

If you have more than one datafile in the tablespace, and you do
NOT need the information contained in that tablespace, or if you
can easily recreate the information in this tablespace, then use
the same command as above:

DROP TABLESPACE <tablespace name> INCLUDING CONTENTS;

Again, this will remove the tablespace, the datafiles, and the
tablespace's contents from the data dictionary.  Oracle will no
longer have access to ANY object that was contained in this
tablespace.  You can then use CREATE TABLESPACE and re-import
the appropriate objects back into the tablespace.

If you have more than one datafile in the tablespace and you
wish to keep the objects that reside in the other datafile(s)
which are part of this tablespace, then you must export all the
objects  inside the affected tablespace.  Gather information on
the current datafiles within the tablespace by running this
query:

select file_name, tablespace_name
from dba_data_files
where tablespace_name ='<name of tablespace>';

Make sure you specify the tablespace name in capital letters.

In order to allow you to identify which objects are inside the
affected tablespace for the purposes of running your export, use
the following query:

select owner,segment_name,segment_type
from dba_segments
where tablespace_name='<name of tablespace>'

Now, export all the objects that you wish to keep.

Once the export is done, issue the DROP TABLESPACE tablespace
INCLUDING CONTENTS.

Note that this PERMANENTLY removes all objects in this
tablespace.Delete the datafiles belonging to this tablespace
using the operating system (see the comment above about possible
problems in doing this)Recreate the tablespace with the
datafile(s) desired, then import the objects into that
tablespace.  (This may have to be done at the table
level,depending on how the tablespace was organized.)

NOTE:
The ALTER DATABASE DATAFILE <datafile name> OFFLINE DROP
command, is not meant to allow you to remove a datafile. What
the command really means is that you are offlining the datafile
with the intention of dropping the tablespace.

If you are running in archivelog mode, you can also use:

ALTER DATABASE DATAFILE <datafile name> OFFLINE; instead of
OFFLINE DROP.Once the datafile is offline, Oracle no longer
attempts to access it, but it is still considered part of that
tablespace.  This datafile is marked only as offline in the
controlfile and there is no SCN comparison done between
the controlfile and the datafile during startup (This also
allows you to startup a database with a non-critical datafile
missing).  The entry for that datafile is not deleted from the
controlfile to give us the opportunity to recover that datafile.

If you do not wish to follow any of these procedures, there are
other things that can be done besides dropping the tablespace.

--If the reason you wanted to drop the file is because you
mistakenly created the file of the wrong size, then consider
using the RESIZE command.
  See 'Related Documents' below.

--If you really added the datafile by mistake, and Oracle has
not yet allocated any space within this datafile, then you can
use ALTER DATABASE DATAFILE <filename> RESIZE; command to make
the file smaller than 5 Oracle  blocks.  If the datafile is
resized to smaller than 5 oracle blocks, then it will never be
considered for extent allocation. At some later date, the
tablespace can be rebuilt to exclude the incorrect datafile.
Review & Followup
Rating: 4
March 30, 2001
Reviewer: Prashant Jhaveri from FL.USA

Rating: 5
How to drop a datafile ? July 19, 2001
Reviewer: Alison Tonner from Scotland - Glasgow
This was a very helpful document, I have just performed the procedure to merge
two very large datafiles (98% fragmented and only a small % used) into one
smaller datafile.

Worked a treat....

Rating: 4
Good information July 19, 2001
Reviewer: Andre Araujo from Manaus,Brazil
This information was very useful to me , I really solved my problem and doubt.

Thank You,
Andre

Rating: 5
How to drop a datafile July 23, 2001
Reviewer: Ana from UK
Very helpful

Rating: 5
Dropping datafiles August 23, 2001
Reviewer: Anil from India
Very good information. Cleared lot of doubts

Rating: 4
August 28, 2001
Reviewer: Jase from Australia
Excellent, fixed my problem

Rating: 1
Usefull??? November 24, 2001
Reviewer: Petr Bily, Josef Vesely from Czech Rep.
It has not been useful for us, we still can't drop the datafile. We can not
undestand how other people can tell that this is very useful for them. Why do we
have to drop our all other fine datafiles in tablespace if one is demaged???

Followup:
If one file is damaged -- you must restore it from a backup.  What use would be
there in dropping a damaged file?  It contains bits and pieces from perhaps many
tables.

Once added, a file is part of a tablespace forever until you drop the
tablespace.  If it becomes "damaged" that is where backup and recovery kicks in.
 Consider a tablespace to be a data structure -- that file is part of a larger
data structure.  You know, like if you have a linked list in a program -- you
cannot just "drop" the middle of the list -- the two ends point to garbage.
Same with dropping a datafile.

If one file is damaged -- all of the other files in your tablespace are in fact
NOT FINE, they rely on that file just as much as that file relied on them.

Rating: 1
follow up "Usefull?" November 24, 2001
Reviewer: Petr Bily, Josef Vesely from Czech Rep.
Let us illustrate why it could be useful to have ability to drop datafile: We
have lost 1 datafile that cannot be restored from backup. This file contains
partitions that are spread accross many other tablespaces. We cann't drop a
datafile. DROP TABLESPACE returned en error ORA-14404. ALTER TABLE DROP
PARTITION on partitions in demaged datafile returned error ORA-00376. The
suggested command on Metalink for this situation ALTER TABLE ECHANGE PARTITION
failed as well. The result is that due to 1 datafile lost we loose all database!
Now we are in the position that we have to export all database without one
demaged datafile and drop all objects/tablespaces and recreate it insted of
importing only one tablespace (optimally datafile).

Followup:
No, you will not lose the entire database.  Think about what you must do.

You have a tablespace that is HOPELESSLY unrecoverable because you lost a
datafile in it and YOU have no backup.

What we must do is remove this tablespace.  You will be responsible for
recovering any lost data (we cannot help you, you don't have any backups).  We
can cut out this partition easily.  You only need to drop it.

Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create tablespace will_be_corrupted
  2  datafile '/tmp/will_be_corrupted.dbf' size 1m
  3  /
Tablespace created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE TABLE t
  2  ( x  int, y varchar2(5), z date )
  3  PARTITION BY RANGE (x) (
  4    PARTITION PART_95 VALUES LESS THAN (1996) tablespace users,
  5    PARTITION PART_96 VALUES LESS THAN (1997) tablespace users,
  6    PARTITION PART_97 VALUES LESS THAN (1998) tablespace users,
  7    PARTITION PART_98 VALUES LESS THAN (1999) tablespace will_be_corrupted
  8  )
  9  /
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1995, 'aaa', sysdate
);
1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1996, 'bbb', sysdate
);
1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1997, 'ccc', sysdate
);
1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1998, 'ddd', sysdate
);
1 row created.

Ok, so I have a partitioned table with one partition in a tablespace we will
"mess up".  So, I connect as sysdba and kill the instance:

ops$tkyte@ORA817DEV.US.ORACLE.COM> connect / as sysdba
Connected.

ops$tkyte@ORA817DEV.US.ORACLE.COM> shutdown abort
ORACLE instance shut down.

ops$tkyte@ORA817DEV.US.ORACLE.COM> !rm -f /tmp/will_be_corrupted.dbf

that assures us the file will be "corrupted", its gone.

ops$tkyte@ORA817DEV.US.ORACLE.COM> startup
ORACLE instance started.

Total System Global Area  238829728 bytes
Fixed Size                    73888 bytes
Variable Size             142336000 bytes
Database Buffers           90112000 bytes
Redo Buffers                6307840 bytes
Database mounted.
ORA-01157: cannot identify/lock data file 11 - see DBWR trace file
ORA-01110: data file 11: '/tmp/will_be_corrupted.dbf'

ok, now what -- well, since you said you have no backup, we can simply:

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter database datafile
'/tmp/will_be_corrupted.dbf' offline drop;
Database altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter database open;
Database altered.

database is up, but we still have a problem -- the table isn't 100% yet

ops$tkyte@ORA817DEV.US.ORACLE.COM> connect /
Connected.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t drop partition part_98;
Table altered.

very easy, remove the affected partition...

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop tablespace will_be_corrupted;
Tablespace dropped.

tablespace drops easily...

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

         X Y     Z
---------- ----- ---------
      1995 aaa   25-NOV-01
      1996 bbb   25-NOV-01
      1997 ccc   25-NOV-01

and all of the data we can save is in fact saved.  That tablespace is gone --
the entire thing (regardless of how many files you had in it -- the entire
tablespace is wiped out) but we don't lose the database by any means

Rating: 5
Dam Good Ans. November 25, 2001
Reviewer: Parag Mehta from India

Rating: 5
follow up (Usefull?) November 26, 2001
Reviewer: Petr Bily, Josef Vesely from Czech Rep.
Your testcase works well as is. But if you have an index on partitioned table
(CREATE INDEX T_1 on T(x) LOCAL TABLESPACE ind) you receive the error ORA-00376
as we did. Before issuing ALTER TABLE DROP PARTITION you have to drop all
indexes on it (bug?). You are right, whole database is not lost. Thanks a lot.

Rating: 3
database segments. March 04, 2002
Reviewer: Venkateswara Reddy from Bangalore, India.
Hi Tom

Thanks for your continuous support. I have successfully solved my problem. Here
some doubt regarding support note issue ( some part).

You have mentioned before droping tablespace get the information about the
objects which is stored in the particulare tablespace data in segments.

I have issued a query in my database
select file_name, tablespace_name
   from dba_data_files
   where tablespace_name ='HRMSDEMO';

I got only 25 Rows ( Tables). But I am sure my HRMSTEST user  i have mapped to
HRMSDEMO tablespace. If Issue
Select * from tab on HRMSTEST.
I got 112 objects including views. Wheere are my tables are stored. I am
littelbit confuse. Pls clarify.

Thanks in advance.

Reddy

Followup:
select * from user_segments where tablespace_name = 'HRMSDEMO';

you got the list of files, you wanted the list of segments (tables, indexes,
etc).

Rating: 3
DataFiles / Control Files March 04, 2002
Reviewer: Santhosh from India - The land of Beauty !
Was just going thru your answers...

Have a doubt...

1. Can the DataFiles or Control Files in a Database can be edited using some
Text editors / some other editors ? (If the user has access to them)
2. Can these changes be saved ? If Yes or No, explain me why.
3. Also, what security is Oracle providing to safeguard these Data & Control
Files (Other than the usual back-ups) ?

Thanks.

Followup:
1) NO

2) NO, because you cannot edit these files.

3) You need to read the concepts guide from start to finish:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/toc.htm
do that and remember say about 10% of it and you'll already know more then most
people do about Oracle.

Rating: 5
Adding datafiles OPS March 05, 2002
Reviewer: Irfan from Kuwait
Hi Tom,
This solution was very nice and descriptive. We have OPS 7.3.4. We would like to
add data file (raw device) to one of our tablespace to increase the size. Can
you suggest this is what exactly I have to use

ALTER TABLESPACE TBSPNAME ADD DATAFILE '/dev/rdsk/c3t3dds1' SIZE 400M.

Is it necessary to bring the tablespace offline before issuing this command.
Please advise any other alternate solution if.

Thanks

Followup:
Refer to your OS guide for specific details on raw partitions on your platform.
You may have to add some device overhead (typically 2 blocks or so) to the raw
partition.

Yes, that would be the command --given the device is properly configured.  No,
the tablespace need not be offline.

As OPS in 73 requires really good DBA interaction to run smoothly -- I would
hope there is an experience DBA around that would be able to answer this as
well.

Rating: 1
dropping datafile. April 24, 2003
Reviewer: Rajeev Grover from INDIA
How to exclude a missing file entry from a control file for which no physical
datafile or its backup is present.

Followup:
did you read the backup and recovery guide which is where I would go for such an
in depth question in order to try and find out for myself first?
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96572/recoscenarios.htm#11689
did you read through the above stuff?

your question is vague enough as to be unanswerable in a "safe" manner.

Rating: 3
dropping datafile April 25, 2003
Reviewer: Rajiv from INdia
Thanks Tom for reference. It was useful.I conclude that,  shall I create a new
file belonging to that tablespace and rename  the missing one to new one.

Rating: 4
why is it not possible to drop datafile? April 26, 2003
Reviewer: P from birmingham
hi tom,
i know that it is not possible to drop a datafile once added...could you tell us
why i cannot drop it...what is the technical reason...what is going on behind
the screen which doesnot allow us to drop it?
thank you for all your help!
P

Followup:
it becomes an integral part of the tablespace.  In order to drop it -- you need
to get rid of that entire tablespace.

Say a tablespace is a glass.
Datafiles are water.

You add a cup of water to the glass.

Later you add another cup of water.  Now -- why can't you remove that last cup
of water?  They are all mixed together, all the same.

You can shrink the amount of water ( like you can resize a file within limits)
but you cannot get rid of the "first cup" of water or the "second cup".

Rating: 3
:) April 27, 2003
Reviewer: Sravan from Boston, MA
... interesting answer :-)

Rating: 5
coool April 30, 2003
Reviewer: Abdul Wahab Ansari from jed,Saudi Arabia
The way you cleared the concept of datafiles and control files was absolutely
excellent and helpful.

Rating: 5
Help, Help, Help May 08, 2003
Reviewer: Riaz Shahid from PRAL, Islamabad, Pakistan
Dear Tom !

I am running my database in NonArchivelog mode. One of datafiles of tablespace
RBS got corrupted (as shown below). So i want to drop the rbs tablespace and
re-create it but i am unable to do so as shown below:

C:\>svrmgrl

Oracle Server Manager Release 3.1.7.0.0 - Production

Copyright (c) 1997, 1999, Oracle Corporation.  All Rights Reserved.

Oracle8i Enterprise Edition Release 8.1.7.2.1 - Production
With the Partitioning option
JServer Release 8.1.7.2.1 - Production

SVRMGR> connect / as sysdba
Connected.
SVRMGR> shutdown abort;
ORACLE instance shut down.
SVRMGR> startup
ORACLE instance started.
Total System Global Area                 178436124 bytes
Fixed Size                               75804 bytes
Variable Size                            104882176 bytes
Database Buffers                         73400320 bytes
Redo Buffers                             77824 bytes
Database mounted.
Database opened.
SVRMGR> select count(*) from cr.gstt05;
COUNT(*)
----------
ORA-00376: file 2 cannot be read at this time
ORA-01110: data file 2: 'E:\ORACLE\ORADATA\STARR\RBS01.DBF'
SVRMGR>  alter database datafile  'E:\ORACLE\ORADATA\STARR\RBS01.DBF' offline;
Statement processed.
SVRMGR> alter tablespace rbs offline;
alter tablespace rbs offline
*
ORA-01539: tablespace 'RBS' is not online
SVRMGR> drop tablespace rbs including contents;
drop tablespace rbs including contents
*
ORA-01548: active rollback segment 'RBS0' found, terminate dropping tablespace
SVRMGR> alter tablespace rbs online
     2> ;
alter tablespace rbs online
*
ORA-01113: file 2 needs media recovery
ORA-01110: data file 2: 'E:\ORACLE\ORADATA\STARR\RBS01.DBF'
SVRMGR> alter rollback segment rbs0 offline;
alter rollback segment rbs0 offline
*
ORA-01598: rollback segment 'RBS0' is not online

Note that i tried the media recovery but it asks for archived logs (that are not
available since database was running in Noarchivelog mode). Please advise what
should o do in order to re-create the tablespace RBS (and its datafiles). I have
no physical backup for this database. However, i've logical backup for this
database.

(I am using 8.1.7.2.1 on Windows 2000 Server)

Waiting for your expert advice...

Riaz

Rating: 4
Datafile Size 8.1.7.4 October 14, 2003
Reviewer: Sriram from Florida
Tom,
    Is there a limit on the size of a single Datafile in a tablespace on Windows
2000 Server? I have read somewhere that it is 2GB but i'm not sure about it. If
so, what the reason for the limit?

Thanks,
Sriram

Followup:
there is a limit, it goes beyond 2gig - depends on your os support.  they impose
the limit here.  generally it is around 64/128 gig depending on OS and all.

metalink has the supported configurations somewhere...

Rating: 4
Resize the datafile October 15, 2003
Reviewer: S.Darshan from Singapore
Dear Mr.Tom,

Thanks for your example regarding Tablespaces and Datafiles, Particularly

"Say a tablespace is a glass.
Datafiles are water."

         I have a 6GB size of Tablespace called Archive with Datafiles
Archive01.dbf(2GB), Archive02.dbf(2GB) and Archive03.dbf(2GB). I have a table1,
table2, table3 in Archive Tablespace. After some time we truncated the table2&
table3. Now free space for Archive tablespace is 4GB. I want to resize the
tablespace size to 2GB or 2.5 GB by using
ALTER DATABASE DATAFILE '/u01/data/Archive03.dbf' RESIZE 1024M;

But I can not able to resize the datafile.  How can I resize ? Can you help me?

With best regards
S.Darshan

Followup:
well -- the files can only be shrunk to their "current high water mark"

In each of those files, you'll find bits of Table1 -- its extents.  Suppose the
three files look like this:

(from 0m to 2gig in size -- f is "free" and U is used by table1)

0m        512m       1024m      1536m       2048m
ffffUUUffffUffffffffffffUffffffffffffffffffffffff     file 1
fffffffffffffffffffffffffffffffffUUffffffffffffff     file 2
ffffffffffffffffffffffffffffffffffffffffffffffffU     file 3

Now, file 1 can be shrunk to 1gig in size -- there is nothing used beyond 1gig
in that file.

file 2 can only be shrunk to 1.5 gig -- there is data at the 1.5 gig mark in
that file.  Even though that file only has 2 used bits -- they are way in the
middle

file 3, with the least data, will be the biggest file.  It cannot be shrunk at
all in this example.

Now, if you are using LMTS -- you can try "alter table table1 MOVE" -- that'll
tend to reorg the table and put all of the bits at the "front" of a file.  Then
you can shrink perhaps (remember to rebuild indexes after moving a table like
that!)

If you search for

maxshrink

on this site you find a script that will tell you how small ever datafile in
your database can currently be shrunk to

Rating: 4
How to drop a datafile October 15, 2003
Reviewer: Amit J from Mumbai, India
The answere to the question regd. Data file where too good. It has improved my
knoledge regarding the relation between Tablespace & Datafile to a very high
extent.

Rating: 5
October 15, 2003
Reviewer: A reader

Rating: 4
Great helpful January 05, 2004
Reviewer: Raghunath from INDIA
Really very informative. cleard my doubts.

October 15, 2003 Prakash -- Thanks for the question regarding "How to move tables from one schema to another ?", version 8.1.5/81.6
You Asked
Tom,

My basic requirement is like this :  I need to take a dump of a schema.  But I
don't want to export certain tables which are really huge and I don't want them
in the dump.  Since there is no way to exclude tables in the export command, I
created a new schema and moved these not-necessary tables into this new schema.
 Then I took the export of the original schema.  It seems to work fine, except
for the time.

Problem :  Each of these tables that are not required are really huge with 50-80
million records.   So, in order to move the table from one schema to another, I
have (as far as I know) just two options.

1. Create table schema2.t1 as select * from schema1.t1;

2. Export these tables from schema 1 and use import with from_user=schema1 and
to_user=schema2.

Currently I have used method 2.  But because of these huge tables, it is taking
really a long time and also huge disk space to store these dump files
temporarily (though I am not too worried about the diskspace).

So, what I would like to know is that, is there way in Oracle 8i and above,
wherein we can just move the table from one schema to another.  I am looking for
some command like "Rename t1 to t2" but in a different schema.

Please advise.

Thanks
Prakash.
and we said...
you could just:

create table t1 unrecoverable as select * from other_schema.t1;

for each table you want (you could write a query to write all of these queries
and delete the tables you DON'T want to copy)

Then:

exp userid=other_schema owner=other_schema ROWS=N
imp userid=new_schema fromuser=other_schema touser=new_schema IGNORE=Y

that'll get all of the indexes, constraints, triggers, packages, etc -- but no
data.

follow up to comment

No, there is not.  If the volume of data is large and you want it to appear as
if the table is in the other schema, you should use a view.  The actual
ownership of the table would remain with USER1 but user2 would appear to "own"
the table as well.
Review & Followup
Rating: 1
Creating the tables does take a long time.... June 05, 2001
Reviewer: Prakash from Bangalore
create table t1 unrecoverable as select * from other_schema.t1;

The above statement takes quite a lot of time, because the volume of data that I
am dealing with runs to 50-100 million records.

I wanted to know, if there is any command which will just change the ownership
of the table, without having to move across the data.

Rating: 4
Large Table Suggestion June 06, 2001
Reviewer: Matt from Phila, Pa
It sounds like the tables you are working with are fairly large.  I have used
the create table unrecoverable fairly successfully with tables that are in the
6-8GB range and have found it loads fast, typically only takes about 20Minutes
on 6GB tables(Note: on 4 Processor HP K Class Server with EMC Array).    I would
suggest the following possible enhancements to the create table unrecoverable:
1)Use parallel query (i.e) create table x parallel (degree 5) as select * from
y.  Note:  remember that each parallel query server will require the inital
extent in the tablespace so be careful to size the initial extent resonably..
Probably should use locally managed tablespace..
2)If at all possible try to distribute the I/O accros mulitle controllers.
3)Create the indexes unrecoverable with parallel query.
4)Remember if your database is in archive log mode you can not recover the new
table until your next Hot/Cold backup. Might want to do an export of the table
after creating it.

Rating: 3
June 06, 2001
Reviewer: Sriram from New York
If your basic objective is to selectively export tables

You might do two exports
exp username/password rows=n
/* to export the user with just the structures and objects */

Followed by exp username/password tables=(tbl1, tbl2 ..)
/*Spool output from user_tables.table_name
 delete your large table entries ,
cut and paste to get your list */

 Import the files into schema 2. Use the ignore=y option in the second import.
The structures for those large tables may get into the second schema but not the
data.

Rating: 4
What if the "Source" table and "Target" table exist in same schema December 19, 2001
Reviewer: Subra from CA USA
You have absolutely fantastic way of explaining the hard concepts.  Thanks
always for your tips.
Here is a slightly different requirement, with lot of constraints.
I have a "Source" table with about 20 million rows of data, that I need to copy
(100%) into "Target" table.  "Target" table is empty at the begining of the
process.
If I was a dba or have "CREATE" privelege, I could have

CREATE TARGET UNRECOVERABLE
   AS SELECT * FROM SOURCE ;

Or if I had export / import priv, then I would use that technic.

Since I have no "CREATE" privelege,  I wrote a program to open a cursor (on
source.rowid) on "Source" and insert the data into "Target" a row at a time.  It
works but takes a good 2 hours to complete the process.

Please review and suggest any better way in view of my constraints.

Thanks
Subra

Followup:
insert /*+ append */ into target select * from source;

that'll do a direct path insert into target (no rollback/redo if you don't have
any indexes on target).  It'll be as fast as you can get.  You can even do it in
parallel, see the server concepts guide for details.

Rating: 3
I have used transportable tablespace feature for those kind of move December 19, 2001
Reviewer: David Shi from Wilmington, DE USA
If your large tables reside in separate tablespaces ( I image you would with the
size of those tables ), you can use transportable tablespace feature to export
tablespace definition, drop them, and then import them back into a separate
schema. The time needed is very minimal with this approach. Make sure to test it
out in your test database just in case.

Rating: 4
You may use database link December 19, 2001
Reviewer: Ash from Australia
You can simply use database link to transfer tables from one schema to other.
Once you have created database link, you can use create table xyz as select *
from xyz@database_link_name.

Rating: 2
another possibility December 20, 2001
Reviewer: Andrei Sokolenko from Germany
To quickly move big tables between schemas
I use EXCHANGE PARTITION feature of Oracle 8i.

for example:

SQL> connect as user "A"

SQL> create table large_table
     (
       a number,
       b char,
       c date
     )

-- just for this example only. :)
SQL> grant ALL on large_table to "B";

SQL> connect as user "B"

SQL> create table large_table
     (
       a number,
       b char,
       c date
     )
     partition by range (a)
     (
       partition dummy values less than (maxvalue)
     )

Then you can use the following command to quickly move
"A.large_table" to "B.large_table"

SQL> connect as user "B";

SQL> alter table large_table exchange partition dummy
     with table A.large_table;

And return it back to schema A:

SQL> alter table large_table exchange partition dummy
     with table A.large_table;
-- of course, it is the same SQL command

Followup:
very nice, cool idea.

Rating: 4
Difference in the Execution Plan Oracle 8.1..7.2 July 31, 2002
Reviewer: Ron from OH USA
Tom,
We exported schema1 to schema2(its a complete export/Import) on the same
instance.I have a SQL running in schema1 hitting different instance(joins 3
tables from the schema1 and 3 tables from the instance2(remote)), and it is
returning the results within 2 sec and the plan is also looks good.Now I am
trying the same SQL from the schema2(which is exact replica of schema1) and it
is taking 2hours.I checked the plan and it is completely different.We analysed
all the tables in the SQL and all the indexes are there, everything looks same
as the schema1 could you tell me what could be the reason for the difference in
execution plan?
Thanks a lot
Ron

Followup:
how is the dblink created -- using a static proxy account or "connect as current
user".  are you querying the same three tables at the remote site?

What are the plans and what are the queries and make SURE all of the same
structures are there (indexes and all)

Rating: 4
Difference in the Execution Plan Oracle 8.1..7.2 August 05, 2002
Reviewer: Ron from OH USA
Tom,
I checked everything and we have all the indexes and we also analyzed the
tables.We are using the public DB Links.
The Script we used to create this link is
"CREATE PUBLIC DATABASE LINK REMOTE_INSTANCE_NAME
USING 'REMOTE_INSTANCE_NAME'". we are using the same tables and it is same SQL
also.

Followup:
Well, the users are going to be different -- are you *sure* they are the same
tables and the users have the *same* privs and all.

The users connecting to the remote database are different since you do not have
"connect to identified by" in the create database link.

What are the plans -- do a TKPROF and show us that (as the "good" user and as
the "bad" user).  My crystal ball is in the shop this week.

Rating: 4
August 07, 2002
Reviewer: Milind bhavsar from India ,Pune
Hi Tom ,
I have been reading this column for quite some time
and it is simply marvelous
for this problem of moving between schema
Suppose you do following to change ownership of EMP table from SCOTT to ADAMS
what do you think about this Is it safe enough to be done on production

SYS@RSI-PUN-MILINDB> select username, user_id from dba_users ;

USERNAME                          USER_ID
------------------------------ ----------
SYS                                     0
SYSTEM                                  5
OUTLN                                  11
SCOTT                                  19
DBSNMP                                 16
ADAMS                                  20
JONES                                  21
CLARK                                  22
BLAKE                                  23
EM_REPO                                29
TEST                                   25
REPO                                   26
GEKO                                   28
TEST1                                  30
TEST2                                  31

15 rows selected.

SYS@RSI-PUN-MILINDB> select table_name from all_tables where owner = 'SCOTT' ;

TABLE_NAME
------------------------------
A
BONUS
DEPT
EMP
EMP2
PLAN_TABLE
SALGRADE
TMP1
TMP2
USERPWD

10 rows selected.

SYS@RSI-PUN-MILINDB> select table_name from all_tables where owner = 'ADAMS' ;

no rows selected

SYS@RSI-PUN-MILINDB> select OBJ#, OWNER# from obj$ where name like 'EMP' ;

      OBJ#     OWNER#
---------- ----------
      3174         19

SYS@RSI-PUN-MILINDB> UPDATE OBJ$ SET OWNER# = 20 WHERE OBJ#  = 3174 ;

1 row updated.

SYS@RSI-PUN-MILINDB> select table_name from all_tables where owner = 'ADAMS' ;

TABLE_NAME
------------------------------
EMP

SYS@RSI-PUN-MILINDB> select table_name from all_tables where owner = 'SCOTT' ;

TABLE_NAME
------------------------------
A
BONUS
DEPT
EMP2
PLAN_TABLE
SALGRADE
TMP1
TMP2
USERPWD

9 rows selected.

Followup:
that is the worst thing you can do.

You lose, game over player one.

That is the best way to totally corrupt your database.

Never, not under any circumstances -- without support telling you "do this
update" -- will you EVER update the data dictionary.

It is the worst thing you can do  (besides not using bind variables).

Don't do that, EVER.  Period.  Anytime you are tempted to update a SYS. table --
stop, don't.

Rating: 5
Milind August 07, 2002
Reviewer: Johan from JHB SA
Milind, you have just broken your database and invalidated any support from
Oracle by directly updating the data dictionary. Just think - who knows how many
other data dictionary tables relate to this table and also need to be updated to
maintain consistency ? Only Oracle know the answer to that - therefore, you can
not update the data dictionary directly. Only use oracle supplied commands to
indirectly update the data dictionary and in this case use the good advice
supplied by Tom and other contributors.

Rating: 3
Was this a simpler question? August 07, 2002
Reviewer: Shrek from MD
Tom may have probably overlooked the beginning part of your question.

You said:
"Since there is no way to exclude tables in the export command"
and then you went on and asked a more difficult question.

I guess the way to do it is a table level export? Am I missing something here?

Followup:
If you use a table level export, you miss all of the other schema objects that
might be also interesting like views, sequences, procedures, packages, etc.

Rating: 5
January 30, 2003
Reviewer: p001
What is the difference between unrecoverable and nologging?

create table t1 unrecoverable as select * from t2;

create table t1 nologging as select * from t2;

Followup:
they both inhibit redo generation for certain operations -- unrecoverable is no
longer documented and considered "obsolete" -- there is a subtle difference
however:

ops$tkyte@ORA920> @mystat "redo size"
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('redo size')||'%'

NAME                                VALUE
------------------------------ ----------
redo size                          155196

ops$tkyte@ORA920> create table t1 UNRECOVERABLE as select * from all_objects;

Table created.

ops$tkyte@ORA920> @mystat "redo size"
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('redo size')||'%'

NAME                                VALUE
------------------------------ ----------
redo size                          227724

ops$tkyte@ORA920> create table t2 NOLOGGING as select * from all_objects;

Table created.

ops$tkyte@ORA920> @mystat "redo size"
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('redo size')||'%'

NAME                                VALUE
------------------------------ ----------
redo size                          296140

ops$tkyte@ORA920> select table_name, logging from user_tables where table_name
like 'T_';

TABLE_NAME                     LOG
------------------------------ ---
T1                             YES
T2                             NO

they generated about the same amount of redo (this is in archive log mode,
normally, this would have generated 3.4 meg of redo), but the LOGGING mode is
set differently

Rating: 5
after import to other schema, how about the constraints October 15, 2003
Reviewer: Junior from VA, USA
Tom:

After import tables to others schemas. What should I do to get the constrains
work?
1.
export schema1.t1, schema.t2.
export schema1.t3, and t3 is child of t1.

2.
import t1, t2 to be schema2
import t3 still under schema1

3.
the foreign key for t1 and t3 is gone.

What should I do to get it back instead of running scripts to recreate them.

Thank you!

Followup:

you should export t1, t2, t3 as a set!

if you want to move things about like this -- from schema to schema -- you are
going to be doing manual changes.  You've totally just moved t1 - we cannot
"guess" what t1 might be or where it might be - there could be 10,000 t1's to
choose from and even if there were just 1 t1, how could we assume it was the
"right one"?

October 15, 2003 parker -- Thanks for the question regarding "root block, branch block , and leaf block within an index", version 8.1.7
You Asked
Hi Tom
I was waiting for the last one week to post this question(s).
Thanks in advance.

Iam a newbie to internals of an index.

I want to know as to what are root block, branch block , and leaf block within
an index. When a query is issued using an indexed column what happens within the
index.

Why do you have to drop a index and rebuild it. How will you decide when to drop
an index.

parker
and we said...
Sorry, I am but one person.  I can take about 30-40 questions per day (for every
one question you see published on the web site, there are 3/4 more that I chose
not to publish and just answer).  I can only answer so many...

Thanks for waiting though (and not just emailing me directly).... I appreciate
that.

An index is a data structure, similar to a binary tree.  A root block is the
"top of the tree".  The searching of an index starts there.  Branch blocks are
interior nodes of the tree.  We use them to navigate the tree -- they tell us to
branch to the right or left depending on the value we need to get to.  Leaf
blocks are the nodes in the tree that are at the "bottom".  They contain the
actual index entries.  This is where the index data is.
http://technet.oracle.com/doc/oracle8i_816/server.816/a76965/c08schem.htm#3353
depicts this graphically.  Also:
http://technet.oracle.com/doc/oracle8i_816/server.816/a76965/c08schem.htm#3239
talks about indexes, how they used and processed.

The last question "why do you have to drop an index and rebuild it" -- well I
always turn that around and ask the people who (mistakenly) believe you have to
do this.  It is actually the rare case where this is needed -- i would ask them
to justify to me why they feel the need to rebuild.

Actually, i would never drop and rebuild -- i would just:

alter index NAME rebuild online;

no muss, no fuss.
Review & Followup
Rating: 3
you are so different May 03, 2001
Reviewer: parker from usa
It is not that much about the technical answers you give, which are more often
than not are excellent, but what I really like, is the way you answer..you make
people feel so good about you .. there is something very personal in your
answers....

you want people to be successfull and take the maximum from your knowledge .. I
really respect you for that.

It could have been more useful , if you have compared
drop and rebuild index and
alter index NAME rebuild online;

Rating: 4
Blevel >3 September 02, 2001
Reviewer: Mike from GA, USA
I have one index in our porduction database has the BLEVEL = 4, after I checked
with
Alter index ... validate structure;

Is it a condidate for rebuild?

Rating: 5
blevel = 0 December 26, 2002
Reviewer: Sam from San Mateo, CA
Tom,

I selected the blevel of an index from all_indexes and it showed 0.  How is that
possible?  Should it not be at least one since there should be a root block?

I am using 9iR2 and I collected statistics on the index before I checked the
blevel.

Followup:
Not until it fans out (grows branches), consider

ops$tkyte@ORA920> create table t ( x int );
Table created.

ops$tkyte@ORA920> create index t_idx on t(x);
Index created.

ops$tkyte@ORA920> analyze index t_idx compute statistics;
Index analyzed.

ops$tkyte@ORA920> select index_name, blevel from user_indexes where index_name=
'T_IDX';

INDEX_NAME                         BLEVEL
------------------------------ ----------
T_IDX                                   0

ops$tkyte@ORA920> insert into t values ( 1 );
1 row created.

ops$tkyte@ORA920> analyze index t_idx compute statistics;
Index analyzed.

ops$tkyte@ORA920> select index_name, blevel from user_indexes where index_name=
'T_IDX';

INDEX_NAME                         BLEVEL
------------------------------ ----------
T_IDX                                   0

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          l_blevel number;
  3  begin
  4          loop
  5                  insert into t values ( 1 );
  6                  execute immediate 'analyze index t_idx compute statistics';
  7                  select blevel into l_blevel
  8                    from user_indexes
  9                   where index_name= 'T_IDX';
 10                  exit when l_blevel != 0;
 11          end loop;
 12  end;
 13  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select count(*) from t;

  COUNT(*)
----------
       572

it took 572 inserts of 1 to split it and cause it to branch out... blevel was
zero until then

Rating: 5
blevel October 15, 2003
Reviewer: Shankar from Houston, TX
Is there any correlation between blevel and time to rebuild? is there any
guideline that if blevel is greater than certain value, then rebuild an index?
Does blevel depend on the number if index entries? Thanks.

Followup:
nope.  blevel 4 might be perfectly acceptable for a big table with lots of rows

yes, blevel is related to the number of entries in the index structure.

a high amount of deleted leaf rows (browned index leaves if you like) might
imply you want to COALESCE, but a rebuild -- rare beast indeed for a b*tree

October 15, 2003 -- Thanks for the question regarding "Formatting numeric data using to_char", version 8.1.5
You Asked
What does to_char return an extra space after formatting ?
In the following example:

select length(to_char(8,'09')), length(8) from dual
returns 3 and 1 respectively.

Why is there a leading space even though
the format string has 2 positions?

Thanks.
and we said...
What is the number was -8.  Then it would make more sense.  It
is saving space for the sign.

If you want to lose that space, it is easiest to query:

select '"' || to_char(8,'09') || '"',
       '"' || to_char(8, 'fm09') || '"'
  from dual;

The fm will trim the spaces for you.
Review & Followup
Rating: 5
formatting November 01, 2001
Reviewer: A reader
Tom

How should we remove the formatting given for a particular column of a query  in
sqlplus.
and
How should I remove the formatting for all the columns for the given query

is there any thing like

unformat column column name

and

 BBB
---- -
   3
   3
   3
   3
   3
   4
   4
   4
   4
   3
   3
how can I make the values print from the center , and from the left

thank you

Followup:
scott@ORA717DEV.US.ORACLE.COM> column empno format 99999.99
scott@ORA717DEV.US.ORACLE.COM> select empno from emp where rownum = 1;

    EMPNO
---------
  7369.00

scott@ORA717DEV.US.ORACLE.COM> column empno clear
scott@ORA717DEV.US.ORACLE.COM> /

     EMPNO
----------
      7369

There is no format to "center", you can use lpad:

scott@ORA717DEV.US.ORACLE.COM> column empno format a20
scott@ORA717DEV.US.ORACLE.COM> l
  1* select lpad( empno, (20 - length(empno))/2 ) empno from emp
scott@ORA717DEV.US.ORACLE.COM> /

EMPNO
--------------------
    7369
    7499
    7521
    7566
    7654
    7698
    7782
    7788
    7839
    7844
    7876
    7900
    7902
    7934

14 rows selected.

same with left/right (although if you convert a number to a string, it'll be on
the left -- leave as a number, on the right)

Rating: 4
That explains that, but how about this? October 15, 2003
Reviewer: Jon from Hartford, CT
Tom, I used the search feature and found this response, but I am still confused
by a result I am getting.  When I use LPAD(TRIM, my results fit in 34
characters.  When I omit the LPAD, it doesn't fit, but goes over onto a second
line, even though both evaluate to a length of 12.  Here is my output, and I
will repeat the script afterward so you don't have to strip out the junk to test
for yourself:

sql> SET TERM OFF;
sql> SET SPACE 0;
sql> SET ARRAYSIZE 5;
sql> SET NEWPAGE 0;
sql> SET PAGESIZE 0;
sql> SET ECHO OFF;
sql> SET RECSEP OFF;
sql> SET FEEDBACK OFF;
sql> SET HEADING OFF;
sql> CLEAR COLUMNS;
columns cleared
sql> CLEAR BREAKS;
breaks cleared
sql> SET LINESIZE 34;
sql> SELECT LENGTH(LPAD(TRIM(TO_CHAR(345678901234, '099999999999')), 12)) FROM
DUAL;
                                12
sql> SELECT '1234567890123456789012',
  2          LPAD(TRIM(TO_CHAR(345678901234, '099999999999')), 12) RCRD_CN
  3  FROM DUAL;
1234567890123456789012345678901234
sql> SELECT LENGTH(TRIM(TO_CHAR(345678901234, '099999999999'))) FROM DUAL;
                                12
sql> SELECT '1234567890123456789012',
  2          TRIM(TO_CHAR(345678901234, '099999999999')) RCRD_CN
  3  FROM DUAL;
1234567890123456789012
345678901234
sql>

====================================================
SET TERM OFF;
SET SPACE 0;
SET ARRAYSIZE 5;
SET NEWPAGE 0;
SET PAGESIZE 0;
SET ECHO OFF;
SET RECSEP OFF;
SET FEEDBACK OFF;
SET HEADING OFF;
CLEAR COLUMNS;
CLEAR BREAKS;
SET LINESIZE 34;
SELECT LENGTH(LPAD(TRIM(TO_CHAR(345678901234, '099999999999')), 12)) FROM DUAL;
SELECT '1234567890123456789012',
        LPAD(TRIM(TO_CHAR(345678901234, '099999999999')), 12) RCRD_CN
FROM DUAL;
SELECT LENGTH(TRIM(TO_CHAR(345678901234, '099999999999'))) FROM DUAL;
SELECT '1234567890123456789012',
        TRIM(TO_CHAR(345678901234, '099999999999')) RCRD_CN
FROM DUAL;

Followup:
the lpad changed what was thought to be the MAX length of the column -- forget
the real length, plus is formatting based on MAX lengths.

you should use substr( ...., 1, MAX_LENGTH_YOU_WANT ) to avoid this in the
future.

October 15, 2003 Kev -- Thanks for the question regarding "Which Indexes are being used by our Application?", version 8.0.6.1.0
You Asked
We have just gone live and I have noticed alarming growth in the indexes.
Closer investigation shows several large, heavy transactional tables with a
large number of indexes.  I believe we are duplicating indexes and that some of
the could be removed.

Does anybody know of a way to find out which indexes are being used by an
application?

Ps: and I don't want to set tracing on at the DB level and trawl through a huge
TKprof output either!!!

Thanks for any help you can give.

Kev McKelvey
DBA
Lex Vehicle Leasing
and we said...
Not on 8.0.6 -- not easily anyway.  In 8i, we can use stored outlines as a trick
to get this information and in 9i, there will be a built in piece of
functionality.

I can suggest you set up a tablespace for each of the indexes on a given table.
Then, you can monitor IO on these tablespaces.  If the reads are very close to
the writes -- then the index is probably only being read to be updated.  If the
reads are much larger then the writes -- the index is actively used.

Bear in mind that the FACT that an index is being used does not imply that it
needs to be there.  Consider:

create index i1 on t(a);
create index i2 on t(a,b);
create index i3 on t(a,b,c);

The optimizer might use ALL three but only i3 is needed by the system.  i1 and
i2 are redudant (unless one of them is UNIQUE) and in most cases can be dropped.
 You could look for this condition using a uery like this:

tkyte@TKYTE816> select  *
  2  from
  3  ( select index_name,
  4           max(decode(p, 1,     c,NULL)) ||
  5           max(decode(p, 2,', '||c,NULL)) ||
  6           max(decode(p, 3,', '||c,NULL)) ||
  7           max(decode(p, 4,', '||c,NULL)) ||
  8           max(decode(p, 5,', '||c,NULL)) ||
  9           max(decode(p, 6,', '||c,NULL)) ||
 10           max(decode(p, 7,', '||c,NULL)) ||
 11           max(decode(p, 8,', '||c,NULL)) ||
 12           max(decode(p, 9,', '||c,NULL)) ||
 13           max(decode(p,10,', '||c,NULL)) ||
 14           max(decode(p,11,', '||c,NULL)) ||
 15           max(decode(p,12,', '||c,NULL)) ||
 16           max(decode(p,13,', '||c,NULL)) ||
 17           max(decode(p,14,', '||c,NULL)) ||
 18           max(decode(p,15,', '||c,NULL)) ||
 19           max(decode(p,16,', '||c,NULL)) index_cols
 20      from (select index_name, substr(column_name,1,30) c, column_position p
 21                 from user_ind_columns )
 22     group by index_name  ) A,
 23  ( select index_name,
 24           max(decode(p, 1,     c,NULL)) ||
 25           max(decode(p, 2,', '||c,NULL)) ||
 26           max(decode(p, 3,', '||c,NULL)) ||
 27           max(decode(p, 4,', '||c,NULL)) ||
 28           max(decode(p, 5,', '||c,NULL)) ||
 29           max(decode(p, 6,', '||c,NULL)) ||
 30           max(decode(p, 7,', '||c,NULL)) ||
 31           max(decode(p, 8,', '||c,NULL)) ||
 32           max(decode(p, 9,', '||c,NULL)) ||
 33           max(decode(p,10,', '||c,NULL)) ||
 34           max(decode(p,11,', '||c,NULL)) ||
 35           max(decode(p,12,', '||c,NULL)) ||
 36           max(decode(p,13,', '||c,NULL)) ||
 37           max(decode(p,14,', '||c,NULL)) ||
 38           max(decode(p,15,', '||c,NULL)) ||
 39           max(decode(p,16,', '||c,NULL)) index_cols
 40      from (select index_name, substr(column_name,1,30) c, column_position p
 41                 from user_ind_columns )
 42     group by index_name  ) B
 43  where a.index_name <> b.index_name
 44    and a.index_cols like b.index_cols || '%'
 45
tkyte@TKYTE816> /

INDEX_NAME INDEX_COLS                     INDEX_NAME INDEX_COLS
---------- ------------------------------ ---------- --------------------------
I2         A, B                           I1         A
I3         A, B, C                        I1         A
I3         A, B, C                        I2         A, B

That tells you I2 can be used where I1 is.  I3 can be used where I1 is.  I3 can
be used where I2 is (hence keep I3)
Review & Followup
Rating: 5
James September 25, 2001
Reviewer: A reader from Oz
Good Stuff... another addition to my bag of tricks

Rating: 5
May 26, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
Good Info. Above you said:

"Not on 8.0.6 -- not easily anyway.  In 8i, we can use stored outlines as a
trick
to get this information and in 9i, there will be a built in piece of
functionality."

Can you elaborate how this would be easier to figure out with Oracle9i?
Thanks,

Followup:
you can enable index monitoring.

start with our sample schema.  We want to see if the T_PK index is used
for any queries

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create table t ( x int,
  2                   constraint t_pk primary key(x) );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> insert into t
  2  select rownum
  3    from all_objects
  4   where rownum <= 100
  5  /

100 rows created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

We'll enable monitoring on the index now

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select
index_name,monitoring,used,start_monitoring,end_monitoring
  2    from v$object_usage;

no rows selected

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> alter index t_pk monitoring usage
  2  /

Index altered.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select
index_name,monitoring,used,start_monitoring,end_monitoring
  2    from v$object_usage;

INDEX_NAME   MONITORING USED START_MONITORING    END_MONITORING
------------ ---------- ---- ------------------- -------------------
T_PK         YES        NO   05/26/2003 10:06:32

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

Using RBO as we are by default, a count(*) won't use the index

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set autotrace on explain
ops$tkyte@ORA920.US.ORACLE.COM> select count(*) from t;

  COUNT(*)
----------
       100

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'T'

ops$tkyte@ORA920.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select
index_name,monitoring,used,start_monitoring,end_monitoring
  2    from v$object_usage;

INDEX_NAME   MONITORING USED START_MONITORING    END_MONITORING
------------ ---------- ---- ------------------- -------------------
T_PK         YES        NO   05/26/2003 10:06:32

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

An insert won't either

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> insert into t values (-1);

1 row created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select
index_name,monitoring,used,start_monitoring,end_monitoring
  2    from v$object_usage;

INDEX_NAME   MONITORING USED START_MONITORING    END_MONITORING
------------ ---------- ---- ------------------- -------------------
T_PK         YES        NO   05/26/2003 10:06:32

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

But if we analyze the table, a count(*) will use the index
and we would have a record of it

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> analyze table t compute statistics
  2  /

Table analyzed.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set autotrace on explain
ops$tkyte@ORA920.US.ORACLE.COM> select count(*) from t;

  COUNT(*)
----------
       101

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FULL SCAN) OF 'T_PK' (UNIQUE) (Cost=1 Card=101)

ops$tkyte@ORA920.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select
index_name,monitoring,used,start_monitoring,end_monitoring
  2    from v$object_usage;

INDEX_NAME   MONITORING USED START_MONITORING    END_MONITORING
------------ ---------- ---- ------------------- -------------------
T_PK         YES        YES  05/26/2003 10:06:32

Rating: 5
Excellent! May 26, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Just the kind of stuff I needed.
Thanks,

Rating: 5
Great! May 26, 2003
Reviewer: Reader from Houston, TX
Tom, does it mean that if I already have created a concatenated index on columns
say a,b,c then i don't need to have an index created on col a, or b or c
separately. In other words, could you please give me a guidance when should I
create a concatenated index? My understanding is that if i use a column
frequently in where clause, i should create an index on that column. Should i
create a concatenated index if i use all of the columns in my where clause with
and, or or conditions? could you clarify? Thanks.

Followup:
if you have an index on a,b,c, you probably do not need ones on a,b or a.

You might need one on B, or B,C, or C, or C,B -- but only you know the answer to
that.

Rating: 4
How to get it in Oracle 8i. August 21, 2003
Reviewer: Saibabu Devabhaktuni from Mountain view, CA
Tom,

Could you please explain how to know which Indexes are being used by application
in Oracle 8.1.7.4.

We are currently doing explain plan on all sql statements in v$sql and get Index
usage info from plan_table, though not accurate, this approach provide very good
estimate. Do you agree?

Thanks

Followup:
i concur, "not 100% accurate, but good estimate"

Rating: 4
Re: How to get it in Oracle 8i. August 21, 2003
Reviewer: Saibabu devabhaktuni from Mountain View, CA
Tom,

Sorry to bug you. You said, we can get which Indexes are being used in Oracle 8i
using stored outlines. Could you please explain it.

Thanks a lot.

Followup:
if you have my book "Expert one on one Oracle" -- i have all of the gory details
in there but in short.

stored query outlines basically saves a really detailed set of hints into a
database table for queries that are run on your system.

you can enable the saving of these stored query outlines.

you would run your applications.  the first time a query is found, we save the
hints.

these hints include "use this index" type hints.

you have a table you can now query to see what queries use which indexes.

Rating: 5
For 9i August 21, 2003
Reviewer: A reader
Can you please explain your Statement :
in 9i, there will be a built in piece of
functionality.
For 9.2

Followup:
in 9i, you can monitor indexes.

alter index t_pk monitoring usage
/

and then query v$object_usage after a while to see if it was used or not.

Rating: 4
Foreign Key-Refernces/Dripping Indexes August 22, 2003
Reviewer: Orca777 from Germany/Frankfurt
Under 9i you must be carefull dropping indexes which lay on columns of a table
needed for Foreign-Key references.
Is this right tom? ;-)

Orca

Followup:
unindexed foreign keys may cause locking/contention issues in all releases of
Oracle, yes.

Rating: 5
Overhead August 22, 2003
Reviewer: A.Titievsky from Louisville,KY
Is there a significant overhead when monitoring multiple indexes?

Followup:
not that I can measure, no

Rating: 5
Index monitoring October 15, 2003
Reviewer: A reader
Tom

I need to determine whether particular indexes on a table are being used for
querying at all.

How can I set up this monitoring i.e. what should be done to set up this
monitoring.

After it is monitored how can we decide whether it is being used frequently or
not ?

Please reply.

Followup:
there is a complete demo of index monitoring and how to enable it above.

as for "frequently" it is a binary thing -- we use it or we do not.  we do not
count "how much"

Rating: 4
For the above post.. October 15, 2003
Reviewer: A reader
our database is 8i and not 9i , as such the above solution might not work.

Can you answer being specific to 8i..

Followup:
nope, you cannot do this reliably in 8i, feature did not exist.

If you have access to my book "expert one on one oracle", you can look at a
possible way to abuse query plan stability to capture some of this information.

Rating: 4
Slight modification October 16, 2003
Reviewer: Jeff Fulton from An Ami in Austria
Hi Tom,

The query above is great, but unfortunately it will report similar indexes that
are on different tables.  I've modified it slightly to include the table name
and only report similar indexes if the table name is the same:

SELECT  *
   FROM
   ( SELECT table_name,
            index_name,
            MAX(DECODE(p, 1,     c,NULL)) ||
            MAX(DECODE(p, 2,', '||c,NULL)) ||
            MAX(DECODE(p, 3,', '||c,NULL)) ||
            MAX(DECODE(p, 4,', '||c,NULL)) ||
            MAX(DECODE(p, 5,', '||c,NULL)) ||
            MAX(DECODE(p, 6,', '||c,NULL)) ||
            MAX(DECODE(p, 7,', '||c,NULL)) ||
            MAX(DECODE(p, 8,', '||c,NULL)) index_cols
       FROM (SELECT table_name, index_name, SUBSTR(column_name,1,30) c,
column_position p
                  FROM USER_IND_COLUMNS )
      GROUP BY table_name, index_name  ) A,
   ( SELECT table_name,
            index_name,
            MAX(DECODE(p, 1,     c,NULL)) ||
            MAX(DECODE(p, 2,', '||c,NULL)) ||
            MAX(DECODE(p, 3,', '||c,NULL)) ||
            MAX(DECODE(p, 4,', '||c,NULL)) ||
            MAX(DECODE(p, 5,', '||c,NULL)) ||
            MAX(DECODE(p, 6,', '||c,NULL)) ||
            MAX(DECODE(p, 7,', '||c,NULL)) ||
            MAX(DECODE(p, 8,', '||c,NULL)) index_cols
       FROM (SELECT table_name, index_name, SUBSTR(column_name,1,30) c,
column_position p
                  FROM USER_IND_COLUMNS )
      GROUP BY table_name, index_name  ) B
         WHERE a.table_name = b.table_name
           AND a.index_name <> b.index_name
           AND a.index_cols LIKE b.index_cols || '%'

October 16, 2003 Andrew -- Thanks for the question regarding "BLOB arrays", version 8.1.6i
You Asked
Hi, Tom!
I have a type
create type MyBlob is table of blob;
I have a table
create table Tb(Blb BLOB);

Code that works:

declare mb myblob:=myblob();
Am integer;
f bfile:=bfilename('SIPA','sakyra.jpg');
begin
dbms_lob.open(f,dbms_lob.lob_readonly);
Am:=dbms_lob.getlength(f);
mb.extend;
dbms_lob.createtemporary(mb(mb.count),TRUE);
dbms_lob.open(mb(mb(count),dbms_lob.lob_readwrite);
dbms_lob.loadfromfile(mb(mb.count),f,Am);
dbms_lob.close(mb(mb.count));
-- once again!!--
mb.extend;
dbms_lob.createtemporary(mb(mb.count),TRUE);
dbms_lob.open(mb(mb(count),dbms_lob.lob_readwrite);
dbms_lob.loadfromfile(mb(mb.count),f,Am);
dbms_lob.close(mb(mb.count));
insert into Tb
select * from Table(cast(mb as myblob));
end;
/

Code that don't works:
declare mb myblob:=myblob();
Am integer;
f bfile:=bfilename('SIPA','sakyra.jpg');
begin
dbms_lob.open(f,dbms_lob.lob_readonly);
Am:=dbms_lob.getlength(f);
 for i in 1..2 loop
  mb.extend;
  dbms_lob.createtemporary(mb(mb.count),TRUE);
  dbms_lob.open(mb(mb(count),dbms_lob.lob_readwrite);
  dbms_lob.loadfromfile(mb(mb.count),f,Am);
  dbms_lob.close(mb(mb.count));
 end loop;
insert into Tb
select * from Table(cast(mb as myblob));
end;
/
It causes:
ORA-22922 nonexistent LOB value
The only difference between two fragments is for ...loop clause.
Where is a mistake?
Thanks in advance.
Andrew
and we said...
this issue is fixed in 8.1.7.  A temporary workaround is to code:

tkyte@TKYTE816> declare
  2     mb myblob:=myblob();
  3     tmp blob;
  4  begin
  5
  6     for i in 1..2
  7     loop
  8       mb.extend;
  9       dbms_lob.createtemporary(tmp,TRUE,dbms_lob.session);
 10           mb(mb.count) := tmp;
 11     end loop;
 12
 13     for i IN 1 .. mb.count
 14     loop
 15       dbms_output.put_line( 'mb('||i||') = ' ||
                                dbms_lob.getlength( mb(i) ) );
 16     end loop;
 17  end;
 18  /
mb(1) = 0
mb(2) = 0

PL/SQL procedure successfully completed.

if you just allocate mb(mb.count) in the createtemporary call, and do not use
the TMP variable, it'll fail, the array loses scope on the blobs outside of that
loop.  By doing the tmp assignment, it works.
Review & Followup
Rating: 5
ORA-22922 October 15, 2003
Reviewer: Leo from Boston, MA
Hi Tom,
   I am facing ORA-22922 (in 9.2.0.2.1 database) when i am trying to do an
Insert.  I don't know what i am doing wrong. To solve the mutating problem on
the CMR table, i did follow your technique. Basically i am trying to create a
history record for all the changes on the TITLE column. Here are the package and
triggers. Please help me.

Thanks
Leo.

create table cmr(
 CMR_ID      NUMBER(10) not null,
 SEC         VARCHAR2(6) not null,
 TITLE                VARCHAR2(250),
 CMR_TEXT             CLOB)
/

CREATE OR REPLACE package cmr_arr_pkg
  as
          type rowid_a is table of rowid index by binary_integer;
          type text_a is table of varchar2(250) index by binary_integer;
          type clob_a is table of clob index by binary_integer;
          oldrows_a      rowid_a;
          cmr_title_a    text_a;
          cmr_clob_a     clob_a;
          empty_a        rowid_a;
          empty_text_a   text_a;
          empty_clob_a   clob_a;
  end;

CREATE OR REPLACE TRIGGER CLAMS.CMR_ARR_INITAILIZE
BEFORE UPDATE
ON CLAMS.CMR
REFERENCING NEW AS NEW OLD AS OLD
begin
    cmr_arr_pkg.oldrows_a := cmr_arr_pkg.empty_a;
    cmr_arr_pkg.cmr_title_a := cmr_arr_pkg.empty_text_a;
    cmr_arr_pkg.cmr_clob_a := cmr_arr_pkg.empty_clob_a;
end;

CREATE OR REPLACE TRIGGER CLAMS.CMR_GET_UPD_ROWS
BEFORE UPDATE
OF TITLE
ON CLAMS.CMR
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
begin
    cmr_arr_pkg.cmr_clob_a(cmr_arr_pkg.cmr_clob_a.count+1) := :new.cmr_text;
    cmr_arr_pkg.oldrows_a(cmr_arr_pkg.oldrows_a.count+1 ) := :old.rowid;
    cmr_arr_pkg.cmr_title_a(cmr_arr_pkg.cmr_title_a.count+1) := :new.title;
    :new.title := :old.title;
    :new.cmr_text := :old.cmr_text;
end;

CREATE OR REPLACE TRIGGER CLAMS.CMR_CRE_HIST_REC
AFTER UPDATE
OF TITLE
ON CLAMS.CMR
begin
  for i in 1 .. cmr_arr_pkg.oldrows_a.count
    loop

       insert into CMR(cmr_id, sec, sub1, sub2, sub3, sub4, title
       , cmr_text
       ,created_date)
              select cmr_seq.nextval
                    ,sec
                    ,sub1
                    ,sub2
                    ,sub3
                    ,sub4
                    ,cmr_arr_pkg.cmr_title_a(i)
                    ,cmr_arr_pkg.cmr_clob_a(i)
                    ,sysdate
                from cmr
               where rowid = cmr_arr_pkg.oldrows_a(i);

    end loop;
end;

Statement:
===========
update cmr
set title = title||'  TEST - REc'
where cmr_id = 226

SQL> /
update cmr
*
ERROR at line 1:
ORA-22922: nonexistent LOB value
ORA-06512: at "CLAMS.CMR_CRE_HIST_REC", line 11
ORA-04088: error during execution of trigger 'CLAMS.CMR_CRE_HIST_REC'

Followup:
the lobs are not fully accessible in the trigger.

Suggestion -- look at work space management and just version enable this table.
there is an application developers guide for workspace managment:

ops$tkyte@ORA920> CREATE USER wsmgmt IDENTIFIED BY wsmgmt;

User created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> GRANT connect, resource, create table to wsmgmt;

Grant succeeded.

ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2          DBMS_WM.GrantSystemPriv
  3      ('ACCESS_ANY_WORKSPACE, MERGE_ANY_WORKSPACE, ' ||
  4       'CREATE_ANY_WORKSPACE, REMOVE_ANY_WORKSPACE, ' ||
  5       'ROLLBACK_ANY_WORKSPACE', 'WSMGMT', 'YES');
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> @connect wsmgmt/wsmgmt
ops$tkyte@ORA920> set termout off
ops$tkyte@ORA920> @&gname
ops$tkyte@ORA920> set termout off
wsmgmt@ORA920> REM GET afiedt.buf NOLIST
wsmgmt@ORA920> set termout on
wsmgmt@ORA920>
wsmgmt@ORA920> create table t
  2  ( cmr_id number,
  3    sec number,
  4    title varchar2(250),
  5    cmr_text clob,
  6    primary key(cmr_id,sec)
  7  )
  8  /

Table created.

wsmgmt@ORA920>
wsmgmt@ORA920> begin
  2          DBMS_WM.EnableVersioning ('T',  'VIEW_WO_OVERWRITE');
  3  end;
  4  /

PL/SQL procedure successfully completed.

wsmgmt@ORA920>
wsmgmt@ORA920> insert into t values ( 1, 1, 'Hello', 'World' );

1 row created.

wsmgmt@ORA920> commit;

Commit complete.

wsmgmt@ORA920>
wsmgmt@ORA920> update t set title = 'HELLO';

1 row updated.

wsmgmt@ORA920> commit;

Commit complete.

wsmgmt@ORA920>
wsmgmt@ORA920> update t set cmr_text = 'WORLD';

1 row updated.

wsmgmt@ORA920> commit;

Commit complete.

wsmgmt@ORA920> set linesize 77
wsmgmt@ORA920> desc t_hist
 Name                                    Null?    Type
 --------------------------------------- -------- ---------------------------
 CMR_ID                                  NOT NULL NUMBER
 SEC                                     NOT NULL NUMBER
 TITLE                                            VARCHAR2(250)
 CMR_TEXT                                         CLOB
 WORKSPACE                                        VARCHAR2(30)
 VERSION                                 NOT NULL NUMBER(38)
 USER_NAME                                        VARCHAR2(4000)
 TYPE_OF_CHANGE                                   VARCHAR2(1)
 CREATETIME                                       DATE
 RETIRETIME                                       DATE

wsmgmt@ORA920> column title format a10
wsmgmt@ORA920> column cmr_text format a10
wsmgmt@ORA920> select cmr_id, sec, title, cmr_text,
  2         type_of_change,
  3         to_char(createtime,'dd-mon hh24:mi:ss') created,
  4         to_char(retiretime,'dd-mon hh24:mi:ss') retired
  5    from t_hist
  6  /

    CMR_ID        SEC TITLE      CMR_TEXT   T CREATED         RETIRED
---------- ---------- ---------- ---------- - --------------- ---------------
         1          1 Hello      World      I 16-oct 10:17:52 16-oct 10:17:52
         1          1 HELLO      World      U 16-oct 10:17:52 16-oct 10:17:53
         1          1 HELLO      WORLD      U 16-oct 10:17:53

Rating: 5
Thanks a lot October 16, 2003
Reviewer: Leo from Boston, MA
Hi Tom,
  I will change my approach to work space management.

Thanks again.
Leo

October 16, 2003 Rory -- Thanks for the question regarding "DML statement on a table", version 8.0.6
You Asked
Good morning sir,

     First of all, I would like to commend you for your work. You have helped a
lot of us Oracle users and has clarified to us a lot of issues regarding Oracle
architecture, etc.

     I would just like to ask you... how can I know when the last DML statement
was done on a table. The table DBA_OBJECTS shows us when the last DDL statement
was done on an object but what I need is when the last DML statement was done on
a table. Please exclude auditing the table in your solution. The scenario is, I
have one schema. I need to know what tables in that schema are still in use via
DML statements. If there is a data dictionary that can tell me when the last
time a DML statement was done on a table then that would be great. If I need to
monitor the tables throughout the day just to see whether DML statements are
being applied to them then I can do that too. But I need your help on how to do
it.

      Thank you sir. Good day and God bless!
and we said...
But by the time you look -- make your decision -- the fact of whether there is
DML or not is "moot" -- the entire situation would have changed.

Seems like a cat chasing its tail, fun to watch but pointless.

Just try locking the table in exclusive mode

ops$tkyte@ORA920.US.ORACLE.COM> lock table scott.emp in exclusive mode nowait;
lock table scott.emp in exclusive mode nowait
                 *
ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified

that means "hey, someones using it right now"

You could look into v$sql to see what statements are refering to your table and
see if there are open_versions and users_executing it.
Review & Followup
Rating: 1
Looking for redundant tables? September 26, 2002
Reviewer: Adrian Billington from UK
Tom

Seems to me he is asking how to identify tables that are no longer having any
DML issued on them... ?

Regards

Adrian

Followup:
Interesting -- looking for "dead" tables maybe. not clear.  maybe they'll
clarify

Rating: 3
Wouldn't monitoring work in this situation September 26, 2002
Reviewer: Joseph O'Brien from Sturbridge, MA
From the 8i reference:
Once enabled, Oracle monitors the table for DML activity. This includes the
approximate number of inserts, updates, and deletes for that table since the
last
time statistics were gathered. Oracle uses this data to identify tables with
stale statistics.

View the data Oracle obtains from monitoring these tables by querying the
USER_TAB_MODIFICATIONS view.

Joe E o

Followup:
DML includes selects in my world. that would not capture selects

Rating: 3
September 26, 2002
Reviewer: BT
Why don't you create a trigger on that table and in the trigger code, do
insertion in a dml log table!

Rating: 3
what's "open_versions" ? September 26, 2002
Reviewer: Robert C from PA
>> look into v$sql...see if there are open_versions and
>> users_executing it.

Tom, what's "open_versions" stand for ?

Thank you

Followup:
tells us if the child cursors are "locked"

Rating: 4
DML on Tables September 30, 2002
Reviewer: Rory from Philippines
Good morning sir,

          Thanks for the solution. The second comment was correct. I was looking
for dead tables. And I would have a whole day to monitor the tables or select on
dynamic tables to pinpoint which tables are "DEAD'.

          I'll try the solutions you gave me. Thanks again

Rating: 2
How about using snapshot logs March 11, 2003
Reviewer: Robert Xuequn Xu
If monitoring DML (insert, update, delete) on certain tables is what you want
(excluding auditing or creating triggers as solutions), simply use snapshot log.
To see if there is DML activity throughout the day, create snapshot logs on the
target tables in the morning, and check the mlog$_<table_name> table for the
<table_name> you want to monitor. It even tells you which type of DML has
issued. The snapshot logs can be dropped easily if you no longer want them, and
re-created easily if you need to monitor DML on the tables again.

Followup:
well, you've just created a trigger haven't you.  sure you can do that -- but,
there are easier infinitely more efficient ways.

Rating: 5
an academic question October 15, 2003
Reviewer: A reader
Hi

I always wondered if a query is considered DML? Oracle University SQL course
says SELECT is not a DML but Oracle documentation says SELECT is DML... Which is
true?

I understand select for update can be DML but a simple query?

Followup:
A Query is technically DML (data MANIPULATION -- not data MODIFICATION)

there is DDL (data definition)
and there is DML (data manipulation)

a query is DML

October 16, 2003 Robert -- Thanks for the question regarding "Processing records in a specific sequence", version 8.1.5
You Asked
Tom,

I have the following table:

Create Table Inbound (
  Msg_Seq     Number(*,0),
  Message     Varchar2(2000),
  Created     Date Default Sysdate,
  Processed   Date Default To_Date('01-jan-1000')
  );

And the following index:

Create Index Inbound_Processed_Idx on Inbound(Processed, Msg_Seq);

When a record is inserted, Msg_Seq is incremented by use of the following
sequence:

Create Sequence Inbound_Seq
Increment by 1 Start With 1
NoMaxValue NoMinValue
NoCycle Cache 20 NoOrder;

There is a process that reads this table, and it must process records in the
exact order in which they are added.  No gaps.  I've been using this cursor, and
it seems to be working well:

Cursor Inbound_Unprocessed is
Select
  Msg_Seq,
  Message
From Inbound
Where Processed = To_Date('01-jan-1000')
Order by Msg_Seq
For Update of Processed
;

This ensures that the records are read in the proper order (explain plan reveals
an index range scan), but does it necessarily ensure that the first record read
is the next available one?  I suspect that the answer is no.

I can, of course, find the first unprocessed record by using this cursor:

Cursor Inbound_Unprocessed is
Select
  Msg_Seq
  Message
From Inbound
Where Processed = To_Date('01-jan-1000')
  and Msg_Seq = (
    Select Min(Msg_Seq)
    From Inbound
    Where Processed = To_Date('01-jan-1000')
    )
For Update of Processed
;

However, this only allows me to process a single record at a time; I have to
re-execute the query for the next record.

Can you suggest any improvements in the process?  I'm starting to read the
documentation on Advanced Queuing.  It's sounds pretty cool, but I'm wondering
if it's overkill for something like this.

Thanks for any insight you can provide!

Robert
and we said...
AQ was *designed* for this, this is what message queueing in the database is all
about.

In anycase.  consider this timeline:

time          process1               process2            process3
------        -----------            ----------          ------------
t1            insert seq=100
t2                                   insert seq=101
t3                                   commit
t4                                                       open query
t5            commit

Now, consider the time between t1 and t5 is very small (0.01 seconds maybe).
The problem is -- process3's cursor will NOT see seq=100, not ever.  The only
data process3 will see with that query is the data that is COMMITTED IN THE
DATABASE at T4.  seq=100 didn't exist then, process3 won't see it until it
finishes all of the data it was working on and starts over again.

Your select min approach gets around that.  AQ -- now in my opinion, that would
be the right approach.
Review & Followup
Rating: 5
October 14, 2002
Reviewer: Robert Massey from Atlanta, GA USA
I only have one process inserting records into the Inbound table, so I don't
have to worry about process2 in your scenario.

However, at some near future point, I had planned to have two processes reading
the Inbound table independently for different purposes.  At that point, I had
planned to add another "Processed2" column, but then I would have to worry about
which process has a particular record lock if I'm updating their respective
Processed column.

As you say, AQ solves all this.  In the AQ introduction, it specifically
mentions single producer / multiple consumer models (among many others).  And I
would imagine AQ would scale better if I ever add more processes.

It looks like I have some studying ahead.  Oracle has so many features, it's
often daunting.  I'm very grateful for both your book and your website.

Followup:
It'll never work with that "processed2" column -- concurrency issues out the
wazzoo.

AQ is the way to go.

As I like to say, "Oracle, it's a career path ;)"

Rating: 5
Never seen AQ Full Fledged AQ Example on this site... October 15, 2002
Reviewer: A reader
Hi Tom,

Thanks for the advice above about using AQ.

I wish you could provide an example as far as this particular question is
concerned.
We see all Great Examples in your site, but i never saw a full blown AQ Example.

I think everybody would be happy to see one.

Thanks in advance.

Followup:
Really -- all you need to do is look at:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76938/toc.htm
 Oracle Application Developer's Guide - Advanced Queuing

In particular:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76938/adq08qap.htm#83406
which is a chapter entitled:

 A Sample Application Using AQ

Rating: 4
PL/SQL notifications in AQ October 16, 2003
Reviewer: Sasa Petkovic from Belgrade, Serbia
Hi Tom,

Few days ago you posted a site with new very interesting thing in 9i. I am very
interested for AQ enhancements-PL/SQL as automatic recipient (stated in doc: in
the past you had to write a separate external message program to dequeu and
process message. Now PL/SQL can be automatically invoked upon receipt of an
interesting message).
As I understood we can define (maybe during creating queue) that some PL/SQL
procedure would be "responsible" for deqeueuing message from the queue. Am I
right?
Now we have a job which works in infinite loop waiting for messages and
processing them.
I went through documentantion but didn't find proper example and deeper
explanation of this new feature in 9iR2.

Thanks

Followup:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:8760267539329
full example -- no infinite loops, it is called by the server.

October 16, 2003 Gunjeet -- Thanks for the question regarding
      "help with internationalization", version version 8.1.6

      You Asked
Hi Thomas,

We have a 3-tier product - Java console, Java application server
and Oracle 8.1.6 (running in MTS mode) as the back end.

The current settings in v$parameter are :

PARAMETER                             VALUE
----------------------------------------------------------------
----------------------------------------------------------------
NLS_LANGUAGE         AMERICAN
NLS_TERRITORY         AMERICA
NLS_CURRENCY                             $
NLS_ISO_CURRENCY     AMERICA
NLS_NUMERIC_CHARACTERS     .,
NLS_CALENDAR             GREGORIAN
NLS_DATE_FORMAT          DD-MON-RR
NLS_DATE_LANGUAGE     AMERICAN
NLS_CHARACTERSET     US7ASCII
NLS_SORT              BINARY
NLS_TIME_FORMAT          HH.MI.SSXFF AM

PARAMETER                             VALUE
----------------------------------------------------------------
----------------------------------------------------------------
NLS_TIMESTAMP_FORMAT     DD-MON-RR HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT     HH.MI.SSXFF AM TZH:TZM
NLS_TIMESTAMP_TZ_FORMAT     DD-MON-RR HH.MI.SSXFF AM TZH:TZM
NLS_DUAL_CURRENCY     $
NLS_NCHAR_CHARACTERSET     US7ASCII
NLS_COMP         BINARY

Based on the locale of the client, we want to be able to enter
data in different languages from the Java console
(for example : input data in Simplified Chinese).

What settings do we need to do on the client side ? Do we
then need to pass them over to the middle tier, so that when a
JDBC connection is established for that client, we can issue an
alter session statement ? The DB tables are designed in such a
way that certain columns have a datatype of NVARCHAR2.

The question is that if we get a chinese version of solaris and
start entering chinese characters in some text fields, what
settings should we do in the product so that we can insert and
query this data in chinese ?

Thanks,

Gunjeet

      and we said...
Assuming that the actual "client" connection to the database
is from the application server, nothing special would need
to be done between the Java console and the Java application
server.  Java natively uses Unicode...actually a Java modified
UTF-8 format.

To support both English and Japanese, you require a database
character set that universally supports both languages.
Hence, for unrestricted multilingual support, you should create
your database using the UTF-8 character set.

When specifying your NLS_LANG, set your locale and territory
to whatever you wish.  But the character set specification
should be UTF8 to avoid any character set conversion.

For more information and other character set considerations,
please see Chapter 3 of the Oracle8i National Language Support
Guide, available online at:

http://technet.oracle.com/doc/oracle8i_816/server.816/a76966/toc.htm
...
Review &
      Followup

      Rating: 2
Can you update
      this response for 9i?  September 01, 2003

Reviewer:  Matt  from Australia
I just discovered that Java stores data internally as UCS-2. This means that any
Java application that pulls data out of a DB where the data is not stored in
this character set must do a characterset conversion. I am wondering what the
overhead of this might be.

I don't suppose you know af anyone has benchmarked this?

In any case, should Java applications just be using UCS-2 as the character set,
or something else?

Thanks in advance

      Rating: 4
The above link
      is not working..  October 16, 2003
Reviewer:
      Thaha Hussain  from Manama, BAH
Dear Tom,

The above link of doc is not working...

- Thaha

          Followup:

as stated on my home page right now - the moved them all.  sufficient data in
the answer to find it again however.

otn.oracle.com -> documentation, open the 8i doc up and goto the stated
reference

October 16, 2003 -- Thanks for the question regarding "Your
      suggestions on OLAP technology", version 9.2.0.1

      You Asked
We are evaluating OLAP technology for implementing some analytics for our
product. We are reviewing technologies from Oracle, Microsoft and other vendors.

Correct my if I am wrong, OLAP technology with Oracle looks like an early stage
feature. I got this assumption when comparing the features, ease of use etc with
other vendors. Even Microsoft also well in advance in that area. I couldn’t see
much customer references for Oracle OLAP.

Please comment on the following issue as you have more expertise with the
different technologies and industry?

What are your suggestions on choosing OLAP technology?
What are your suggestions on Oracle OLAP? Should we consider it? If yes, why?

      and we said...
Umm, "early stage"???

How did you get that impression?  We've been doing it longer then MS has had a
relational database?

http://www.oracle.com/ip/index.html?olap_home.html
http://otn.oracle.com/products/bi/content.html

Let me put it this way - 99.9999% of your OLAP needs are satisfied directly in
Oracle using SQL with analytics, advanced group by and the DW features.
Occassionally you need multi-dimensional cubes and for that we actaully put it
in the database (not in a separate external server).  Meaning -- you don't dump
your data from a RDBMS into an OLAP database (with different security,
management, tools, languages, etc etc etc).  You keep it -- in the database.
The same people that manage your RDBMS today -- manage you OLAP tomorrow --
without having to be trained on an entirely separate, different product.

I don't know how many times I've gone somewhere where the "analysts" dumped the
data from Oracle into an MS OLAP tool to do 'advanced' queries (spending hours
on the dump and load and dump and load and dump and load over and over and
over).  I sit down and do it in a single query -- "oh, i didn't know SQL could
do that...."  A waste of time and computer resources.

Review &
      Followup

      Rating: 5
Oracle
      Express  April 30, 2003
Reviewer:
      A reader
Wasnt Oracle Express the pre-9i OLAP engine?

          Followup:

yes. and Oracle Express is the post 9i OLAP engine -- it is just that for the
last 2 releases of Oracle -- express has been an embedded technology.

That and the analytics functions (8i) have been in there forever.

Add data mining (ala darwin) and you've got all of the analysis tools you could
want.

      Rating:
      5
Thanks  April 30, 2003
Reviewer:
      A reader
Thanks for your comments. I got that impression because I couldn’t see many
customer instillations for Oracle Express/OLAP(9i). I guess that I was thinking
wrong.

>>your OLAP needs are satisfied directly in Oracle using SQL with analytics,
advanced >>group by and the DW features.

Could you please elaborate on the available Oracle features that are helpful for
OLAP?

a)    What do you mean by “SQL with analytics”, is it analytical functions that
oracle provided?
b)    What is ‘advance group by’ feature?
c)    Could you please list the DW features that are useful in OLAP?

Sorry for bugging you.

          Followup:

guess it is a matter of perspective.  I see people doing tiny things with MS
olap stuff.  I see tons more doing big things with our stuff.  Guess it is all
in where you are looking.

I can think of NO data warehousing feature that is not potentially useful in
OLAP -- think about OLAP -- "Online Analytical" -- fast, quick responses -- DW
-- tools to do big things fast.

some quick rollup and cubes:

scott@ORA920> select decode( grouping(deptno), 1, 'summed over dept'),
  2         decode( grouping(ename), 1, 'summed over ename'),
  3         deptno, ename, sum(sal)
  4  from emp
  5  group by rollup(deptno,ename)
  6  /

DECODE(GROUPING( DECODE(GROUPING(E     DEPTNO ENAME        SUM(SAL)
---------------- ----------------- ---------- ---------- ----------
                                           10 KING             5000
                                           10 CLARK            2450
                                           10 MILLER           1300
                 summed over ename         10                  8750
                                           20 FORD             3000
                                           20 ADAMS            1100
                                           20 JONES            2975
                                           20 SCOTT            3000
                                           20 SMITH             800
                 summed over ename         20                 10875
                                           30 WARD             1250
                                           30 ALLEN            1600
                                           30 BLAKE            2850
                                           30 JAMES             950
                                           30 MARTIN           1250
                                           30 TURNER           1500
                 summed over ename         30                  9400
summed over dept summed over ename                            29025

18 rows selected.

scott@ORA920> select decode( grouping(deptno), 1, 'summed over dept'),
  2         decode( grouping(ename), 1, 'summed over ename'),
  3         deptno, ename, sum(sal)
  4  from emp
  5  group by cube(deptno,ename)
  6  /

DECODE(GROUPING( DECODE(GROUPING(E     DEPTNO ENAME        SUM(SAL)
---------------- ----------------- ---------- ---------- ----------
summed over dept summed over ename                            29025
summed over dept                              FORD             3000
summed over dept                              KING             5000
summed over dept                              WARD             1250
summed over dept                              ADAMS            1100
summed over dept                              ALLEN            1600
summed over dept                              BLAKE            2850
summed over dept                              CLARK            2450
summed over dept                              JAMES             950
summed over dept                              JONES            2975
summed over dept                              SCOTT            3000
summed over dept                              SMITH             800
summed over dept                              MARTIN           1250
summed over dept                              MILLER           1300
summed over dept                              TURNER           1500
                 summed over ename         10                  8750
                                           10 KING             5000
                                           10 CLARK            2450
                                           10 MILLER           1300
                 summed over ename         20                 10875
                                           20 FORD             3000
                                           20 ADAMS            1100
                                           20 JONES            2975
                                           20 SCOTT            3000
                                           20 SMITH             800
                 summed over ename         30                  9400
                                           30 WARD             1250
                                           30 ALLEN            1600
                                           30 BLAKE            2850
                                           30 JAMES             950
                                           30 MARTIN           1250
                                           30 TURNER           1500

32 rows selected.

scott@ORA920> select decode( grouping(deptno), 1, 'summed over dept'),
  2         decode( grouping(ename), 1, 'summed over ename'),
  3         deptno, ename, sum(sal)
  4  from emp
  5  group by grouping sets((deptno),(ename))
  6  /

DECODE(GROUPING( DECODE(GROUPING(E     DEPTNO ENAME        SUM(SAL)
---------------- ----------------- ---------- ---------- ----------
                 summed over ename         10                  8750
                 summed over ename         20                 10875
                 summed over ename         30                  9400
summed over dept                              ADAMS            1100
summed over dept                              ALLEN            1600
summed over dept                              BLAKE            2850
summed over dept                              CLARK            2450
summed over dept                              FORD             3000
summed over dept                              JAMES             950
summed over dept                              JONES            2975
summed over dept                              KING             5000
summed over dept                              MARTIN           1250
summed over dept                              MILLER           1300
summed over dept                              SCOTT            3000
summed over dept                              SMITH             800
summed over dept                              TURNER           1500
summed over dept                              WARD             1250

17 rows selected.

Analytics -- search for that word here.  I use them LOTS.  They are the coolest
thing to happen to sql since the keyword select.  see also

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96520/toc.htm
and in particular:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96520/analysis.htm#1020

      Rating: 5
Thanks
      again!  April 30, 2003
Reviewer:
      A reader

      Rating:
      3
Perspective  April 30, 2003
Reviewer:
      Jon  from Sydney
You raise an interesting problem though... perspective.

What ever you may say about MS, they certainly do their marketing well.  Many
people have the impression that to do OLAP well, you need all these extra bits
and pieces that MS supply.  Oracle may have the features, the simplicity and the
power to do all this stuff, but *really* needs to work on its Marketing...

      Rating:
      5
  May 14, 2003
Reviewer:
      A reader
I have Oracle 9.2.0.1 running on Windows. I have installed 9.2.0.3 database
patch. I couldn't see analytic workspace manager tool? How to get this tool?

I will greatly appreciate if you can guide me with some links or steps.

          Followup:

that would be something to ask of support  -sounds like an OEM thing but i've
not used the "analytic workspace manager tool"

      Rating: 4
Definition -
      embedded technology   September 08, 2003
Reviewer:
      Doug  from CT, USA
Tom - can you elaborate why during the last two releases, Express is an
"embedded" technology?  It looks like a bunch of libraries, an app if you will
that just gets run along side oracle and can talk to an Oracle database if it
wants, or some other source. What makes it fit the definition of "embedded".  Is
there another type of component we would NOT consider embedded? Are OFA and OSA
embedded as well?

          Followup:

runs INSIDE oracle

do you start an express server?
do you move data out of Oracle?
is is a standalone product?

it is embedded firmly in the database. it cannot run without the database for it
runs IN the database.

in the past, it was

o a standalone product
o in a separate server
o that tended to move data out of oracle

now, it is in Oracle just like a b*tree index is in oracle.

      Rating: 4
Express 6.3.4
      vs embedded  September 08, 2003
Reviewer:
      Doug  from CT, USA
Actually - the version we installed was 6.3.4, one of the non-embedded versions,
hence my confusion.

I am hearing there is very little app support for the new embedded express..
yet.  You think that's true? or no? There are high hopes for the new EPB?

      Rating: 4
OLAP &
      OFA  October 16, 2003
Reviewer:  Stuart
      Buchan  from United Kingdom
Great Info Tom,

we use OFA 6.3 with an express server database and we are considering the future
upgrade possibilities.

I read the documents on OTN about OLAP within the database as you discussed
here, and then the 11i 6.4 version of OFA - which continues to mention
throughout the seperate express server / database etc.

If we installed OLAP from 9i etc, would we not be able to directly use the OFA
GUI anymore to access the information - this is an important feature for the
user community at the moment who are familiar with the tool. Would we have to
develop a new GUI interface using Java/Web based tools ?

Cheers,

Stuart

          Followup:

sorry -- i am just not familar with the apps side of the house (ofa being the
Oracle Financial Analyzer).  you'd be best off talking to your sales contact who
can bring in a technical apps SC to plot your best course.

October 16, 2003 vijay -- Thanks for the question regarding "How
      to find physical read/Writes made per second", version 8.1.7

      You Asked
Hi,

We are planning to test different raid levels , so i was wondering is there
anyway we can find Reads or Writes per second.

Thanks,
Vijay

      and we said...
v$filestat has this (turn on timed_statistics)

I will warn you when looking at this to consider some extenuating circumstances.

DBWR writes to datafiles in the background.  As long as DBWR can keep up, a slow
IO time can be perfectly acceptable.  A disk performance decrease, on a well
tuned system, can be hidden by DBWR, your end users will never know.

LGWR writes to logs in the FOREGROUND.  Any performance decrease in IO will be
visible to your end users, painfully visible.  You cannot hide this.

TEMPORARY files are read/written in the foreground, you will notice this.

Datafiles are read in the foreground, you will notice this.

So -- datafiles need to have fast reads and can suffer slower writes (to a
point).  Log devices need to be FAST for writing, really really FAST for
writing.  Since you will be running in archive log mode (all production systems
do), they need to be adept at READING as well.  Your archive destination needs
to be FAST.

Consider using more then ONE level of raid, eg:

log (online and archive) consider 0 or 0+1

datafiles consider any level, even 5 (depending on your usage.  If you direct
path load gobs of data, raid 5 could kill you.)

tempfiles, consider raid 0 -- who cares if they get nuked -- just recreate them,
you want really fast reads and writes.

see also

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:359617936136
...

Review &
      Followup

      Rating: 4
More
      Help  August 22, 2001
Reviewer:  Vinnie
      Salerno  from Florida
I have turned on TIMED_STATISTICS but still do not get
any values for WRITETIM or READTIM?  How can I generated
values for these to get AVG reads & Writes /Second?

          Followup:

I've never seen that not happen.  Please contact support and open a TAR for
that, I just tested on my machine and sure enough, right after issuing:

ops$tkyte@ORA8I.WORLD> alter system set timed_statistics=true;

v$filestat started collecting values.

      Rating:
      5
  August 22, 2001
Reviewer:
      A reader
hi tom,

I read your book and it is the best i have ever seen for oracle

      Rating:
      4
Reader  October 01, 2002
Reviewer:
      A reader
I am a little confused
"
DBWR writes to datafiles in the background.  As long as DBWR can keep up, a slow

IO time can be perfectly acceptable.  A disk performance decrease, on a well
tuned system, can be hidden by DBWR, your end users will never know.

LGWR writes to logs in the FOREGROUND.  Any performance decrease in IO will be
visible to your end users, painfully visible.  You cannot hide this.
"

DBWR when it writes at a checkpoint, processes has to wait
for checkpoint to complete. Performance decrease, is it not.

LGWR when it writes at "commit", the server processes
continue to perform and hence performance does not
decrease unless the log buffer is filled or all log
groups are caught up. Is this not true

So, could you clarify why DBWR works in background and
LGWR in forground

Thanks

          Followup:

we only have to wait for DBWR at a checkpoint if we need to advanced into a
logfile that still has blocks that only exist in the cache that need the redo.
then we wait.

If you find you are waiting, add another logfile and hey -- we can now advance
further without waiting.

A checkpoint not complete can be "tuned" around.  We can add dbwr's, we can
increase our logs, we can use the log_* parameters and the fast_start_io_targets
(mttr target in 9i) to make it so we never get this message and never wait.

LGWR on the other hand.  Well, When you COMMIT, COMMIT does not return to you
until LGWR has done the physical write and gotten confirmation back.  You *wait*
for LGWR in the foreground.  YOUR PROCESS, you the client waits for lgwr.  (this
is why you never want to commit in a high speed "for" loop -- you'll spend half
your life WAITING for log file sync's)

      Rating:
      4
Reader  October 02, 2002
Reviewer:
      A reader
Thanks for the clarification.

1. If there is a BLOCK in log buffer and it is not copied to
   redo log, the DBWR will wait before checkpoint the
   block, I agree

2. "logfile that still has blocks that only exist in the cache
    that need the redo"

    Does this mean that if there is a Block in the cache it will
    not be checkpointed until that block has been copied to
    archived log from redo log if in "archived log mode" regardless
    of if the log groups are caught up or not

          Followup:

2) no, it does not mean that.

lets say there is a block in the buffer cache.  It is "dirty" (modified).  it
has NOT been checkpointed to disk (it exists only in cache).

lets say redo log 1 has the redo to redo the changes to this dirty block.

lets say there is a log switch and we are trying to advance into redo log 1 to
reuse it.  We CANNOT reuse UNTIL AFTER that dirty block is checkpointed to disk.

that is what I was saying

      Rating:
      5
Reader  October 03, 2002
Reviewer:
      A reader
Tom,

"
lets say there is a log switch and we are trying to advance into redo log 1 to
reuse it.  We CANNOT reuse UNTIL AFTER that dirty block is checkpointed to disk.
"

1. If this is the case, it seems to me more like LGWR will wait , not the DBWR.
   DBWR will never be blocked (except in the case of log buffer having the
block)
2. In this scenario, will LGWR wait, even if that log 1 has been archived
   completely, because the "modified buffer" that has redo in log 1 has
   not been checkpointed.

If so, it appears that, Oracle tries to do on ONLINE recovery from the current
log
file if by chance there is any corruption in the content of the block.

From "DATA BLOCK CORRUPTIONS: DETECTION ...." by Stephen Haisley
"
If a problem is found (during logical block checking) Oracle tries to
perform recovery on the block........using current online redo log
"

Your comments please

Thanks

          Followup:

1) everyone waits whilst DBWR checkpoints.  the system will appear to freeze

2) yes

we do instance (but not media) recovery using ONLY online redo logs, yes.

      Rating: 5
os
      cache  July 29, 2003
Reviewer:  Reader

someone was saying to me that dbwR process writes to OS cache not directly to
the datafiles. From the OS cache, os process then writes to datafiles. Is it
true? Also, is it true that only LGWR writes directly to redo log files on disk.

Also, can server process write to data files directly? If so, in what
circumstances in oracle can server process do this. Thanks. You are the best.

          Followup:

we use O_SYNC on the writes by dbwr -- meaning dbwr either

a) waits for the write to be put onto disk (sync io)
b) waits for the OS to notify dbwr that the write is in fact on disk (async io)

so, while the data might hit the OS buffer cache, we ask the OS to also put it
onto disk.

Yes, server processes can write directly to datafiles

o direct path load
o create table as select
o insert /*+ APPEND */
o modifications to nologging nocache lobs
o sort direct writes
o index creates

to name a few.

      Rating: 5
How can I find
      how much memory is there for OS cache?  July 29, 2003

Reviewer:  A Reader

          Followup:

you'd ask the guy who runs the OS ;)

solaris and many other unixes/linuxes -- just use whatever is free and shrink it
as people use it for other stuff.

      Rating: 3
how to find
      dbwr has written or not???  October 10, 2003

Reviewer:  Anurag  from INDIA
Hi Tom,

I've read in lots of books and in oracle docs that DBWR writes after timeout ie.
3 secs. Is it standard for it, also how to check this and does it update the
header of datafile or controlfile. Please give details....

          Followup:

it doesn't update the controlfile -- ckpt does that.

you can see what dbwr's been doing in various ways.  one quick one is:

select * from v$sysstat where name like '%DBWR%'

      Rating: 4
how to find
      dbwr....  October 16, 2003
Reviewer:  anurag
       from INDIA
Thanks I found many things, but how to trace that DBWR is writing in every 3
secs?? What exactly is timeout??

Thanks in adv.

regards

          Followup:

just trust us, it is the way it works.

October 16, 2003 Ravi -- Thanks for the question regarding "Can
      two sort operations share the same temp. extent?", version 815

      You Asked
Dear Tom,

1) The temporaray tablespace is set to TEMPORARY. Now, Could two
sort operations use the same extent of a sort segment at one
point of time. For eg, initial and next extent size of sort
segment is 10M. When a sort operation requirng a small space may
use one extent with 10M size and will the rest of the extent
space is free. Will oracle use the rest of the space in that
extent for other sorts assuming all the other extents are full
and no more segments marked free in Sort Extent Pool.

2) Is oracle use any PCTFREE for temporary and rollback extents.
Are there any dictionary views that we can find this info. What
is the default value PCTFREE and PCTUSED for these segments.

Thanks in advance.

Regards
Ravi

      and we said...
Multiple transactions which need a sort on disk, can share the
same sort segment, however, they cannot share the same extent.
The sort segment expands by allocating new extents. The sort
extents are not de-allocated while the instance is running, but
are marked as free and can be re-used as required. Therefore,
the sort segment grows to a certain steady state. The Oracle
server stores the information about the sort segment in the Sort
Extent Pool (SEP), a part of the System Global Area (SGA). Every
statement that needs to sort in the  temporary tablespace checks
this part of the SGA for free extents.  Because the extents do
not have to be allocated and de-allocated  after each operation,
you will benefit an overall database performance  increase.

pctfree makes no sense for temporary or rollback -- it is not
used.  rollback is used in a circular fashion - no freelists
involved.  temp is used on an extent/extent basis, no freelists.

Review &
      Followup

      Rating: 4
more about
      it  October 16, 2003
Reviewer:  Vijay'S
       from India
Hi Tom,
        Good day to you, can you please explain more about SEP  like how Oracle
manages it and if  any link is there for further information related to it that
will be really helpful.

Thanks as always for all your help.
Regards,
Vijay Sehgal

          Followup:

your interface to is is

v$sort_segment
v$sort_usage

you an read about them here (search) or in the docs.

October 16, 2003 Dennis -- Thanks for the question regarding "UNDO
      RETENTION", version 9.2.0

      You Asked
Tom,

Hope all is well.  Sorry I'm on IE (bleah), was having problems with Netscape,
opened up IE, and here I was with the chance to ask a question!  Mozilla doesn't
like our authentication server, so I can't use that at work.

Anyway, to the question.
We are having exports fail with 1555 Snapshot too old errors.  This has been
going on randomly for a few months.  I've been thinking it's our undo retention
period.   When we went to 9i, we had a retention of 15 minutes, but kept blowing
out the UNDO tablespace.  In some moment of infinite wisdom, it was decided to
set the retention to ZERO (keep in mind I'm not considered a "system" dba so my
input isn't as valuable).  Anyway, today they decided to increase the size of
the UNDO and I threw a fit about how it wasn't going to fix the problem, and we
need to up the retention time (which will lead to a need to increase the UNDO
tablespace, but I don't want them to keep throwing space at it in a futile
attempt).  I then explained delayed block cleanout to them (since they didn't
know how you could get 1555 without active transactions on the tables) and sent
them a link (to this very site).

Now they seem stuck on why it has to perform block cleanout with a retention
time of 0.  I guess I don't have a good answer for them if delayed block
cleanout isn't clear enough.  Isn't it that We've changed the data, cleared out
the undo segments thanks to our 0 time, and now we are trying to export (we
export right after a load).  So, it says "hey this block is modified" and tries
to reconstruct the original image, and of course fails.

What am leaving out in my explaination to them??  How does undo differ from
plain old rbs in this respect?

They are saying that "according to documentation, it only overwrites the undo
data if there is not enough free space".  I'm always seeing the UNDO tablespace
at 0% (2.88 MB out of 6GB right now).  Doesn't the statement from the
documentation ONLY apply if we actually had a retention time, and then it would
be just like the old RBS in this respect?  With a time of 0, we are shooting
ourselves in the foot, or so I think.

Thanks Tom.  Sorry for a repeat question (here's the link to what I sent them...

http://asktom.oracle.com/pls/ask/f?p=4950:8:2024200::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:360536564061,%7Bdelayed%7D%20and%20%7Bblock%7D%20and%20%7Bcleanout%7D
As always,

Thanks for the help/assistance/insight/knowledge transfer/advice.

Dennis

      and we said...
an undo tablespace is simply a tablespace where Oracle decides how many RBS's to
create and how big they should grow to.

Under the covers -- it is still JUST ROLLBACK SEGMENTS -- no different.  It is
just that WE decide how many and how big instead of you.

If you have an undo retention period that is very small, there is nothing to
motivate us to extend an rbs -- hence you have the same problem.  You want to up
the undo retention.  Bear in mind that this parameter (retentation) is a
SUGGESTION from you to us.  It'll not cause us to "blow out" undo space - if you
constrain us to x megabytes -- we'll stop.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6894817116500
they should feel very safe in setting undo retention to 500 hours - if they've
also constrained us to a fixed amount of space they are willing to give us.

It is very very easy to test this (that we grow only when we need to).

ops$tkyte@ORA920> create undo tablespace undo_new
  2  datafile size 100m
  3  /
Tablespace created.

ops$tkyte@ORA920> alter system set undo_tablespace = undo_new;
System altered.

ops$tkyte@ORA920> alter system set undo_retention = 0;
System altered.

ops$tkyte@ORA920> create table t ( x char(2000) );
Table created.

ops$tkyte@ORA920> insert into t values ( 'x' );
1 row created.

ops$tkyte@ORA920> commit;
Commit complete.

ops$tkyte@ORA920> @freef 1 undo_new

                                                             %
Tablespace Name        KBytes         Used         Free   Used
---------------- ------------ ------------ ------------ ------
*UNDO_NEW             102,400        1,344      101,056    1.3
                 ------------ ------------ ------------
sum                   102,400        1,344      101,056

at the beginning of the test - we are using about 1m of UNDO in the undo
tablespace.  Now we generate gobs of UNDO:

ops$tkyte@ORA920> begin
  2  for i in 1 .. 100000
  3  loop
  4  update t set x = 'x';
  5  commit;
  6  end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> @freef 1 undo_new

                                                             %
Tablespace Name        KBytes         Used         Free   Used
---------------- ------------ ------------ ------------ ------
*UNDO_NEW             102,400        1,344      101,056    1.3
                 ------------ ------------ ------------
sum                   102,400        1,344      101,056

and at the end -- we are using -- the same 1m.  We obviously wrapped around
and around.  There we NOTHING to motivate us to extend the rbs's in there -- the
undo retention was set low.  So, we bump it up a tad:

ops$tkyte@ORA920> alter system set undo_retention = 10000;
System altered.

ops$tkyte@ORA920> begin
  2  for i in 1 .. 100000
  3  loop
  4  update t set x = 'x';
  5  commit;
  6  end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> @freef 1 undo_new

                                                             %
Tablespace Name        KBytes         Used         Free   Used
---------------- ------------ ------------ ------------ ------
*UNDO_NEW             102,400      102,400            0  100.0
                 ------------ ------------ ------------
sum                   102,400      102,400            0

and look at that -- we were motivated to expand the rbs's in there -- upto
100m (and not a byte more, I didn't put autoextend on and believe me, that was
more then 100meg of undo -- a char(2000) is 2000 bytes for each update)

so, yes, they need to UP the retention as well as dedicate more space, we'll
fill it up and take it from there.

Review &
      Followup

      Rating:
      4
  February 06, 2003
Reviewer:  Nitin
      sharma  from India
its always great to know whats going on behind the curtain ....great insight of
undo management...

      Rating:
      4
clarification  February 06, 2003
Reviewer:
      Dennis  from Missouri
I guess I wasn't clear enough, but you got the answer to me.

My comment about blowing out the UNDO was that we didn't have enough space to
allow us 15 minutes of UNDO, so we were getting unable to extend RBS errors.
That was why the 'system' people dropped the retention to 0, rather than up the
size of the UNDO.  Now, they are wanting to keep the export from failing with
snapshot too old errors by throwing space at the UNDO, but that isn't where the
problem is (fully).  We need to up the retention to a point in time where the
export can perform delayed block cleanout without running into an overwritten
(wrapped) RBS segment, and then we will be fixed.  So it's a combo of adding
space and upping the retention time. (On a side note, I could not enable
resumable due to the below, so I dropped the retention back to zero right before
I left, and our export still failed with ORA-01555 errors - even with the extra
space, so that will help out the defence of the delayed block cleanout being the
culprit) :)

Since nobody is here to monitor the load, I was trying to set the schema to
resumable in case we didn't add enough UNDO for the retention period, but the
developers load most of the tables with an insert /*+ append */ from another
database, and of course resume doesn't work with distributed transactions.  Do
you know of a work-around for that, or is that a topic for a new thread?

Thanks Tom.  You're the greatest!
Dennis

          Followup:

...
My comment about blowing out the UNDO was that we didn't have enough space to
allow us 15 minutes of UNDO, so we were getting unable to extend RBS errors.
....

Sorry -- but the above example shows that won't happen.

UNDO_RETENTION = 10000
undo space = 100m
undo space of 100m was not NEARLY enough to hold 10,000 seconds of UNDO.

undo_retention is "advice" or "a hint", "a request".  It is not a directive.
it'll not blow out the rbs -- else I would have blown out with "failed to
extend" myself above!

set the retention really high.
create the undo tablespace as big as they want it to be and don't autoextend the
datafiles.

undo will grow to that size and not a BYTE larger.

So -- I'm a little confused here....

      Rating:
      4
sorry  February 06, 2003
Reviewer:
      Dennis  from Missouri
I was thinking that yours worked because you had frequent commits...every
transaction was set to inactive...but then you still wouldn't have met the time
limit so I don't know what I was thinking.  I think that you used a commit in a
for loop just shook me up too bad for me to focus and all I could think about
was "he committed".

Sorry for the "my bad".  I will disassociate the whole "must keep time" thing
from my head, and assume that they got an error due to a larger load than
normal, and didn't inform us of that, and then the system dbas took the
retention time down and the reload worked (maybe they only loaded part of it).

This is why you should fix problems when they occur, so you have all the facts,
and not just supposition.

At any rate, thanks for the extra clarification of the UNDO and the retention
setting.  Sorry I was confused too.  I must need more caffeine or sleep, or
both.  But neither of those is really fun hehe.

Thanks Tom.
Dennis

          Followup:

Yes, the failure to extend was because you really "ran out of room".  It would
have happened REGARDLESS of the undo retention setting!

Sleep not fun?  ;)

      Rating: 4
not at
      all!  February 06, 2003
Reviewer:  Dennis
       from Missouri
I see my grammar suffers as well :)

It should have been "you using a" not "you used a".  I know you needed that for
the example, but it was just an oddity and I got all focused in on the commit
part (probably because I'm a little loopy from not sleeping) :)

Of course sleep isn't fun!  All you do is lay there and have cool dreams....but
you could be destroying monsters or cities online, hanging with the
family/friends, playing pain(t) ball, or trying to finish JPL's book so you can
finally open up the bible of Oracle before a new one gets published ;)

Those are the fun things!

Dennis

      Rating: 4
Need some
      help  February 20, 2003
Reviewer:  Dennis
       from Missouri, USA
Tom,

Like the changes, pretty neat!!

There are a few doubts held.  If you could provide clarification, then that
would be wonderful.  Thank you for your time and patience.

1)  Does every transaction have to go to the UNDO, even if it is committed?  If
the SCN is higher than that on the block, why does it need to go to the UNDO?

2) How does DBWR know that the block is committed?  Wouldn't it have to also
suffer delayed block cleanout and risk getting a snapshot too old?  It doesn't
read the redo log, and if the undo has been overwritten, then wouldn't we be
losing transactions??

3)  The block in the buffer cache has to be marked committed, it can't just be
marked change and have to refer to the undo segment, or else we would be losing
transactions.  Is that correct?

4) What if the next process doesn't read the changed block (dirty block) for a
long time?

5)  We are only having problems with the export.  Again, the feeling here is
that it is not understood why the UNDO (RBS)must be visited.  No changes are
happening to the database when the export starts or during the export, and a
change of exporting with consistent=n (we were using y previously) caused the
error to disappear.  Why?  We did not mess with UNDO retention yet(it is still
0).

6)  If we forced a log switch before the export, and then went back to
consistent=y, would we be able to export without errors again?

Thanks Tom.  Sorry it's a long one. (don't a few short ones make up?)
Dennis

          Followup:

1) reads goto undo if and when they need to.  modifications, well they GENERATE
undo so they pretty much always go there (with special exceptions like INSERT
/*+ APPEND */)

2) it doesn't.  DBWR doesn't care about read consistency, it's job is to keep
the buffer clean, flush modified blocks.  DBWR flushes modified blocks that have
uncommitted changes all of the time.

I don't see where you are going with that particular thought...

3) why?  you can have many transactions all modifying the same block at the same
time.  It is really quite complex.  When you commit -- we do not revisit all of
your blocks -- especially if they were flushed to disk already.

If you have my book "Expert One On One Oracle" -- I cover what happens to blocks
during a modification and the dance that the data block, undo block and redo
generated do around eachother to make this all work.  It takes more room then I
can here and needs a picture or two (and my ascii art just ain't up to it)

4) what if? Good question -- what if?  Nothing -- same thing that happens if
they read it straight away.  The database doesn't care about time, it is just
software - 5 milliseconds, 5 days -- it is all the same to it.  the processing
would be the same.

5&6) see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:895410916429
there I get an ora1555 reading from a READ ONLY tablespace.  You must have other
transactions going on in there and your RBS is just sized too small for the
work you are performing.. Increase the permanently allocated size of your RBS
so as to make it such that it does NOT wrap around during the time it takes for
you to do your export.

Switching a logfile -- which doesn't do a thing to UNDO, it is all about redo --
will have zero effect.  In fact, it is so far off of the mark -- hmmm.  I hate
to sound like I'm trying to sell my book -- but I do think I cover this 1555
thing, how transactions work and such in pretty good detail with examples in it.

      Rating: 4
Sorry it's been
      awhile  March 10, 2003
Reviewer:  Dennis
       from Missouri, USA
Tom,

Sorry it's been awhile on this.  Had to get group consensus on the list of
questions for you.  Here goes.

1)
Sorry this was not more clear.  Why would a read transaction have to visit the
undo block if the SCN of the read transaction start time is higher than the SCN
 of the block?

2)
We didn't realize that DBWR flushed uncommitted transactions to disk.  The
thought we were trying to present was that if the block is not marked as
transaction committed, and the only place it is recorded is the REDO and UNDO,
then commits to the block would be lost because the UNDO is overwritten and
there is no record of the block
being changed (other than in the redo log).

3)
So when a block is read that is in the buffer (it's dirty), and the UNDO is
already overwritten, is it assumed that the changes to it are committed and the
block is valid?

4)
Sounds good.

5)
We are assured that nothing is going on at the time of the export.  Other than
sitting here one night and watching top_sessions, any other recommendations on
how to tell that something is going on?

6)
Sorry, yes a log switch is redo, but it also forces a checkpoint, which flushes
the buffer to disk.  This seems to have caused the error to go away
(we changed back to CONSISTENT=Y and do a log switch before the export
(CONSISTENT=N also let us export without errors), and still have left the
UNDO_RETENTION at 0.  We have run for a week and a half with no errors.  Does
this help spark any idea of what could be going on?

Thanks Tom for your help.  We have your book, but feel that the above questions
are left open.

As always,
Thank you for your time and patience.

Dennis

          Followup:

1) here is a short excerpt from my book "Expert one on one Oracle" from chapter
5:

Delayed Block Cleanout

This cause of the ORA-01555 is harder to eliminate entirely but is rare, as the
circumstances under which it occurs do not happen frequently (at least not in
Oracle8I anymore). We have already discussed the block cleanout mechanism, but
to summarize, it is the process whereby the next session to access a block after
it has been modified may have to check to see if the transaction that last
modified the block is still active. Once it determines that it is not active, it
cleans out the block so that the next session to access it does not have to go
through that process again. In order to clean out the block, Oracle determines
the rollback segment used for the previous transaction (from the block's header)
and then determines whether the rollback header indicates whether it has been
committed or not. This confirmation is accomplished in one of two ways. One way
is that Oracle can determine that the transaction committed a long time ago,
even though its transaction slot has been overwritten in the rollback segment
transaction table. The other way is that the commit SCN is still in the
transaction table of the rollback segment, meaning the transaction committed a
short period of time ago and its transaction slot hasn't been overwritten.

In order to receive the ORA-01555 from a delayed block cleanout, all of the
following conditions must be met:

?    A modification is made and commits and the blocks are not cleaned out
automatically (for example, it modified more blocks then fit in 10% of the SGA
block buffer cache).
?    Those blocks are not touched by another session and will not be touched
until our unfortunate query below hits it.
?    A 'long running' query begins. This query will ultimately read some of
those blocks from above.  This query starts at SCN 't1'. That is the read
consistent SCN it must roll data back to in order to achieve read consistency.
The transaction entry for the modification transaction is still in the rollback
segment transaction table when we began.
?    During the query, many commits are made in the system. These transactions
do not touch the blocks in question (if they did we wouldn't have the impending
problem).
?    The transaction tables in the rollback segments roll around and reuse slots
due to the high degree of commits. Most importantly, the transaction entry for
the original modification transaction is cycled over and reused.  In addition,
the system has reused rollback segment extents, so as to prevent a consistent
read on the rollback segment header block itself.
?    Additionally, the lowest SCN recorded in the rollback segment now exceeds
't1'  (it is higher than the read consistent SCN of the query) due to the large
amount of commits.

Now, when our query gets to the block that was modified and committed before it
began, it is in trouble. Normally, it would go to the rollback segment pointed
to by the block and find the status of the transaction that modified it (find
the commit SCN of that transaction). If the commit SCN is less then 't1', our
query can use that block. If the commit SCN is greater then 't1' our query must
rollback that block. The problem is however that our query is unable to
determine in this particular case if the commit SCN of the block is greater then
or less then 't1'. It is unsure as to whether it can use it or not. The
ORA-01555 then results.
......

3) nope -- not at all.  It (read consistency) has nothing to do with whether the
changes are committed or not.  It has to do with "what did the block look like
when my statement began".  If someone modifies a block and commits it whilst we
are reading the table, we'll still see the image of the block from BEFORE the
modification.

5) watch the system, probably deferred block cleanout.  but then again the
problem statement is very vague above "we are having a problem"

6) it fires a log switch checkpoint, which will eventually, sometime, lazily
(maybe hours later) cause the block to be flushed to disk.

Go back to the way you did it before, you'll get the same response from the
system.  It was a fluke most likely caused by the delayed block cleanout.

      Rating: 5
Script
      content?  March 11, 2003
Reviewer:  Fenng

Hi,Tom ,
Could you show me the contents of **freef** script?

Thank you very much!

          Followup:

--------------------------------------------------------
-- free.sql
--
-- This SQL Plus script lists freespace by tablespace
--------------------------------------------------------

column dummy noprint
column  pct_used format 999.9       heading "%|Used"
column  name    format a16      heading "Tablespace Name"
column  Kbytes   format 999,999,999    heading "KBytes"
column  used    format 999,999,999   heading "Used"
column  free    format 999,999,999  heading "Free"
column  largest    format 999,999,999  heading "Largest"
column  max_size format 999,999,999 heading "MaxPoss|Kbytes"
column  pct_max_used format 999.9       heading "%|Max|Used"
break   on report
compute sum of kbytes on report
compute sum of free on report
compute sum of used on report
select name, kbytes, used, free, pct_used
  from (
select (select decode(extent_management,'LOCAL','*','') from dba_tablespaces
where tablespace_name =
b.tablespace_name) || nvl(b.tablespace_name,
             nvl(a.tablespace_name,'UNKOWN')) name,
       kbytes_alloc kbytes,
       kbytes_alloc-nvl(kbytes_free,0) used,
       nvl(kbytes_free,0) free,
       ((kbytes_alloc-nvl(kbytes_free,0))/
                          kbytes_alloc)*100 pct_used,
       nvl(largest,0) largest,
       nvl(kbytes_max,kbytes_alloc) Max_Size,
       decode( kbytes_max, 0, 0, (kbytes_alloc/kbytes_max)*100) pct_max_used
from ( select sum(bytes)/1024 Kbytes_free,
              max(bytes)/1024 largest,
              tablespace_name
       from  sys.dba_free_space
       group by tablespace_name ) a,
     ( select sum(bytes)/1024 Kbytes_alloc,
              sum(maxbytes)/1024 Kbytes_max,
              tablespace_name
       from sys.dba_data_files
       group by tablespace_name
       union all
      select sum(bytes)/1024 Kbytes_alloc,
              sum(maxbytes)/1024 Kbytes_max,
              tablespace_name
       from sys.dba_temp_files
       group by tablespace_name )b
where a.tablespace_name (+) = b.tablespace_name
 )
 where name like upper('%&2%')
order by &1
/

      Rating: 5
Really helped!
      But a couple of clarifications needed for me...  April 15,
      2003
Reviewer:  steve  from Montreal
Hi Tom,

We are using Oracle 9iR2 on Solaris. We are using the UNDO
tablespace feature (i.e. not manual RBS). Our UNDO
tablespace is 4 Gigs. We were experiencing
SNAPSHOT TOO OLD errors and increased the undo retention
from 900 seconds to 5 hrs (thanks to this thread!!). That
solved the errors we were having. Thanks!

I just ran a huge delete statement whci took over 20 minutes.
I believe it was running on _SYSSMU1$. The delete was
immediately followed by a commit.

We then ran a second delete which failed almost immediately
("UNABLE TO EXTEND UNDO TABLESPACE"). The query was running on
_SYSSMU4$ (I think). Here is what the rollback segments look
like just before the second delete failed:

       OPT
NAME       SIZ  EXTENT  RSSIZE aveactive EXTENDS SHRINKS WRAPS # trans HWMSIZE
---------- ---- ------ ------- --------- ------- ------- ----- ------- -------
SYSTEM               7     .42       .00       0       0     0       0     .42
_SYSSMU1$          133 1488.17    852.72     505      79   918       0 1536.11
_SYSSMU2$          127 1340.11    665.74     551      85   970       0 1408.11
_SYSSMU3$           30   58.17    101.35     380      74   742       0  312.11
_SYSSMU4$           74  209.17    353.09     464      80   866       1  472.11
_SYSSMU5$            4    2.11    114.38     426      81   791       0  344.11
_SYSSMU6$            4     .23     43.68     487      83   899       0  256.11
_SYSSMU7$           21    2.23       .14     426      74   810       0  352.11
_SYSSMU8$           46  154.17    129.25     616     107   993       0  296.11
_SYSSMU9$           27  192.17    184.77     421      77   797       0  417.11
_SYSSMU10$         156  519.17    475.76     607      82  1125       0  559.17

11 rows selected.

My questions are the following.

1. When does the RSSIZE go down and by what process?

2. When you say:

     undo_retention is "advice" or "a hint", "a request".  It is
     not a directive.  it'll not blow out the rbs -- else I would
     have blown out with "failed to extend" myself above!

   That applies only to the current RBs - right? What I mean is:

      Our query failed almost immediately. Had we been on _SYSSMU1$,
      it would have had a better chance of succeeding - right? It would
      have overwritten extents that were "younger" than the UNDO_RETENTION,
      allowing the delete to proceed. This overwritting of extents that
      are yonger than the UNDO_RETENTION applies only to the current RBS.
      It's not as though extents from another RBS can be released to the
      tablepsace, allowing other RBSs to grow when needed.

      I guess it failed quickly because we were on a relatively small RBS
      and there was no place left to grow (without "stealing" from the
      other RBSs).

   Am I understanding correctly?

 3. So if my understanding is correct, a high UNDO_RETENTION can increase
    the likelihood of "Unable to Extend Tablespace UNDO..." errors - right?

 Thank you very very much Tom.

 Steve

          Followup:

1) internal, undocumented, and changing.  What this means in your case is that
you have a need for more undo space in your database given the work load you are
throwing at it.

2) here undo_retention had nothing to do with it.  The issue was the RBS you
were assigned to could not grow to accomodate the work you needed to perform.
It mattered not if undo_retention was 1 second or 5 days -- your transaction had
a need to generate gobs of UNDO and was unable to since you limited the rbs to
4gig.

If you had been assigned to the "larger" allocated rbs, it would have run longer
and if there had been sufficient space.

3) No, yes, sort of, indirectly, not really. (false causality happening here)

Unable to extend happens because you run out of space.  Period.

If you set a high undo retention -- you are asking us to use more space.

If you ask us to use more space then you give us, we'll be unable to extend.

(a circular argument...)

      Rating: 5
OLTP x
      DSS  April 15, 2003
Reviewer:  David

Tom, as a guideline I have learned -- as far as old RBS's go:

OLTP:
. more rbs's
. small rbs's
. concurrent ativity
. "round-robin" allocation fashion ( I also heard I could be "next least used"
-- which one is true ? )
. no. of rbs's = aprox. no. of transactions / 4

DSS:
. few or one rbs
. large
. small or no concurrent activity
. size of rollback difined by largest long-running transaction

In a mixed environment with OLTP and DSS, both types of rbs live together. For
example, we can have many small rbs's for oltp and a large one for dss and use
them either:
. assigning them by offlining the dss rbs and onlining the oltp rbs's during the
day and vice-versa for the night (supposing they work alone in those periods of
the day)
. explicitly assigning oltp and dss apps with USE ROLLBACK SEGMENT

That was the "rule of thumb" -- I know you're not fond of ROT's... ;)

How would you distribute a mixed OLTP x DSS workload in the same database with
the new undo management ?

Thanks !

          Followup:

let them all grow as big as they need to be.  (hate use rollback segment)

For DSS why do you need big rbs's?

Maybe for DW -- but even then, most ETL operations should be done in a mode that
generates little to no undo/redo.

      Rating:
      5
Followup/results  April 15, 2003
Reviewer:
      Steve  from Montreal
Thanks!

FYI:  Might be helpful for other readers...

Since we last "spoke", we have been doing massive deletes from 3
tables. But instead of doing it day by day, we have been doing
hour by hour with commits after each hour. We have never
received "Unable to extend Tablespace" errors which
makes sense. Also, each RBS looks "more or less" the same size
(also makes sense).

           OPT     EXT          ave   ave
 NAME       IZE ENTS RSSIZE activ shrink EXTENDS SHRINKS WRAPS #trans HWMSIZE
 ---------- --- ---- ------ ----- ------ ------- ------- ----- ------ -------
 SYSTEM            7    .42   .00    .00       0       0     0      0     .42
 _SYSSMU1$        56 477.30 16.89  26.14     558      98  1105      0 1536.11
 _SYSSMU2$        27 215.42 12.47  25.18     579     106  1170      1 1408.11
 _SYSSMU3$        56 393.36 18.74  14.64     560      94  1142      1  393.36
 _SYSSMU4$        44 281.48 18.17  17.94     535      97  1197      0  472.11
 _SYSSMU5$        53 265.48 17.07  15.61     579     101  1234      0  344.11
 _SYSSMU6$        70 486.23 19.78  15.21     714     107  1358      0  487.98
 _SYSSMU7$        96 654.30 29.37  16.97     645      96  1249      0  654.30
 _SYSSMU8$        53 380.30 24.40  19.16     740     123  1331      1  466.23
 _SYSSMU9$        68 483.36 21.24  18.93     489      84  1113      0  483.36
 _SYSSMU10$       52 349.36 21.25  17.64     655     105  1364      0  559.17

But now our UNDO_RETENTION is "shot". Again, makes sense.

SQL> select count(*) from mo as of
     timestamp sysdate-(2/24)
                      *
ERROR at line 1:
ORA-01555: snapshot too old: rollback segment number 5 with name "_SYSSMU5$"
too small

SQL> SHOW PARAMETER

    undo_retention                       integer     18000

It's making much more sense. Thanks a lot Tom.

How much ($$) to get the "internal, undocumented" info?  ;)

I appreciate all your help.

Steve

      Rating: 4
undo retention
      time for OLTP - - 9.2.0.3  May 23, 2003
Reviewer:
      Baqir Hussain  from Emeryville, CA USA
Tom,
I have two questions:

1. What should be the undo retention time for an OLTP system?

2. If we use default undo retention time (10800) for an OLTP system - what will
be the pro and cons? Are we seeing any performance problem by setting it up to
10800?

          Followup:

I have questions for your questions

how long do you need it for?  You would want undo to be retained for at least as
long as your longest running query.  If you have any "flashback" requirements,
they would have to be considered as well.

You would like your undo to be as small as possible -- but -- as big as it needs
be.  Too small -- ora-1555 snapshot too old.  Too large -- can be a performance
issue.

So, figure out your long running query times and start with that.

      Rating:
      5
  May 25, 2003
Reviewer:
      A reader
Hi Tom,
  We will migrate to oracle 9i in near feature and we are
  planning  to use UNDO tablespace.

  Here is the problem...

  In many batch jobs and stored procedure our develpoer use
  statement
  SET TRANSACTION USE rollback segment = XXXX

  Do we have to modofy all batches and stored procedures?

  I've seen one init.ora parameter undo_suppress_errors.

  Can this parameter setting help anyway in our case if we
  don't modify our programs?

Thanks

          Followup:

that parameter will make it so that the "set transaction" statement will
silently "fail".  It will appear to have worked but won't really do anything.

yes, it can be used to "fix" this issue.

      Rating:
      5
  May 25, 2003
Reviewer:
      A reader
Hi Tom,

I ran quick test on this but it doen't seems to working

SQL>  ALTER SYSTEM SET undo_suppress_errors  = TRUE SCOPE = BOTH;

System altered.

SQL> SHO PARAMETER SUPPRESS

NAME                                 TYPE                  VALUE
------------------------------------ --------------------- --------------------
undo_suppress_errors                 boolean               TRUE
SQL> CREATE OR REPLACE PROCEDURE  TEST_UNDO IS
  2  begin
  3  execute immediate 'SET TRANSACTION USE rollback segment = RB01';
  4  end;
  5  /

Procedure created.

SQL> EXEC TEST_UNDO ;
BEGIN TEST_UNDO ; END;

*
ERROR at line 1:
ORA-02245: invalid ROLLBACK SEGMENT name
ORA-06512: at "GAURANG.TEST_UNDO", line 3
ORA-06512: at line 1

It is not suppressing the error.

Any comments

Thanks

          Followup:

well, you do have to use VALID syntax:

ops$tkyte@ORA920> set transaction use rollback segment rb01;

Transaction set.

ops$tkyte@ORA920> set transaction use rollback segment=rb01;
set transaction use rollback segment=rb01
                                    *
ERROR at line 1:
ORA-02245: invalid ROLLBACK SEGMENT name

lose the "="

      Rating: 5
should
      undo_suppress_errors be set to TRUE always...  May 26, 2003

Reviewer:  Kamal Kishore  from New Jersey, USA

Hi Tom,
Nice discussion on UNDO. clarified a lot for me.
The doubt that I have is on the optimal setting of the parameter
undo_suppress_errors?
The default setting for this seems to be FALSE, thus, generating errors like :

SQL> begin
  2    set transaction use rollback segment rbs ;
  3  end ;
  4  /
begin
*
ERROR at line 1:
ORA-30019: Illegal rollback Segment operation in Automatic Undo mode
ORA-06512: at line 2

SQL>

Should this parameter be left at FALSE, and code modified to remove the SET
TRANSACTION statements (may be at several places in several packages) or can it
be set to TRUE permanently? Are there any issues that we should be aware of when
setting it to TRUE?
Is this parameter provided as a "hot-fix" for old legacy code and recommended
setting should preferably be FALSE?
Thanks,

          Followup:

Here is my take on it.

o You are doing an upgrade.
o You are testing all of your code (you do that right?)
o Some of it will have to change -- that is the time to nuke the "use rollback
segment" code
o you therefore should not need this (unless you haven't tested your code in the
first place)

So, I guess if you don't test your stuff, this is useful as a bandaid on the
morning after you upgrade -- while you are running around fixing dozens of other
issues.

If you do test your stuff, you'll find and fix it before you get to the morning
after the upgrade (and hence won't need it)

I would be looking for this to be false in the long run.

      Rating:
      3
  May 26, 2003
Reviewer:
      A reader

      Rating: 4
undo retention
      increases does help -- 9.2.0.3  May 29, 2003

Reviewer:  Baqir Hussain  from Emeryville, CA USA

Tom,
We are running the following:
UPDATE client_activity_by_agent_1 caba SET num_phone_contacts =
  (SELECT count(DISTINCT(c.client_id)) FROM client c, journal j
   WHERE c.client_id = j.client_id
   AND j.is_phone_contact = 1
   AND j.create_date > SYSDATE - '1'
   AND caba.personnel_id = c.agent_id);
and got java.sql.SQLException: ORA-01555: snapshot too old: rollback segment
number 1 with name "_SYSSMU1$" too small.

The update went through with undo_retention = 400 last week without generating
this error ora-1555.

Last night I increased undo_retention = 3600 and put undotbs1 tablespace into
autoextend mode and this tablespace can extend to 35G.  The tablespace
(undotbs1.dbf data file) did not extend at all and we got this error ora-1555.

TABLESPACE_NAME                AUT      BYTES
------------------------------ --- ----------

/mpmd10/bwalk01/undotbs02.dbf
UNDOTBS1                       YES 1048576000

/mpmd10/bwalk01/undotbs01.dbf
UNDOTBS1                       YES 366477312

We run update job at night when there are almost no load on the database with
very few users.
The undo retention time was increased to 3600 , the oracle must use/generate
more rbs to accomadate it since there are enough space available in the undotbs1
tablespace in autoextend mode.
1. How can we solve this problem?
2. Is it possible that autoextend feature does work for undotbs1 tablespace?
May be some kind og bug??
Please advise.
Thanks

          Followup:

what is the max size of the datafiles however.....

what version...

what I would be tempted to do to avoid that little painful correlated subquery
might be:

create global temporary table gtt ( agent_id int primary key, cnt int )
on commit delete rows;

and at runtime

insert into gtt
select agent_id, count(DISTINCT(c.client_id))
  FROM client c, journal j
 WHERE c.client_id = j.client_id
   AND j.is_phone_contact = 1
   AND j.create_date > SYSDATE - '1'
 group by agent_id;

update ( select num_phone_contacts, cnt
           from client_activity_by_agent_1, gtt
          where client_activity_by_agent_1.agent_id = gtt.agent_id )
  set num_phone_contacts = cnt;

      Rating: 5
ora-01555 in
      Automatic Undo mode  July 15, 2003
Reviewer:  A
      Reader  from USA
Hi Tom,
Can you please explain a little bit more about the ora-01555 in Automatic Undo
mode.  Why and how to correct it?  In our case it happend when running a batch
job. I checked the undo space and found it has a lot space left when (may be
after) the 1555 error.  The old trick does not work in 9i anymore.  What should
we do?
Thank you for your help.

          Followup:

it happens for the same exact reasons it would happen in manual undo mode.  you
have insufficient space configured -- i don't know what "trick" you allude to.

You should ensure

a) your undoretention_period is set sufficient high (longer then your longest
running queries)

b) your undo is not prematurely expired

see

http://otn.oracle.com/oramag/oracle/03-may/o33asktom.html
"undo retention"

for some details

      Rating: 5
The old
      "trick"  July 15, 2003
Reviewer:  A Reader

Here is the "old trick".  When I try to create a large rollback segment using:
CREATE ROLLBACK SEGMENT "RB_LG"
    TABLESPACE "ASI_UNDO_01"
    STORAGE ( INITIAL 1024K NEXT 1024K OPTIMAL 2048K MINEXTENTS 2;

I get the following error message:
ORA-30019: Illegal rollback Segment operation in Automatic Undo mode

When I try to assign a transaction a specific rollback segment by using:
SET TRANSACTION USE ROLLBACK SEGMENT rb_lg;
I get the same error.

Thank again you for your help.

          Followup:

that will NOT fix an ora-1555

that will CREATE an ora-1555

the odds of an ora-1555 are 100% related to the SIZE OF YOUR SMALLEST rollback
segment, never your largest.

using automatic undo, the rollback segments will grow till either

a) you run out of space

so, you don't need to have that "big one", it'll grow..

(i hated that "trick" btw)

      Rating: 4
Does free_space
      imply anything useful?   October 06, 2003

Reviewer:  A reader  from CT, USA
Tom - does the free space stat on the UNDOTBS1 really imply anything?  A user is
saying "We're not doing anything but it is full.. what is going on .. I'm having
issues".  Hasn't specified what issue yet but I'm just curious. It's this just
old retained information that should be ready for a flush if new stuff comes
around?

          Followup:

it is supposed to be "full"

it just means we've allocated extents in the tablespace.

It is like "temp".  at the tablespace level "temp" should appear "full" --
because the space is allocated and in use by temp.  It does not mean there is
not any space in temp -- just that temp is allocated.

      Rating: 4
Another
      approach  October 06, 2003
Reviewer:
      A reader  from CT, USA
That's what I figured..
what do you think of this approach?
select s.username, t.xidusn, t.ubafil,
t.ubablk, t.used_ublk
 from v$session s, v$transaction t
 where s.saddr = t.ses_addr

Accurate?

          Followup:

do this.

update emp set ename = ename;

and now walk away for 2 days.  don't commit.

You'll be using a teeny tiny bit of UNDO as reported by that query.

BUT, you'll have prevented UNDO from rolling back around and reusing the space.
Say the UNDO segment you got assigned was 5 meg in size.  You would show as
using a handful of blocks at best -- but the entire 5 meg of UNDO would be
"unusuable" until you commit since we use it in a circular fashion -- all of the
transactions that happened after you used up the 5 meg, committed -- but we
cannot reuse it as yet!

      Rating: 5
Can deletes
      also cause 1555  October 16, 2003
Reviewer:
      Scott Watson
Tom,

This morning I came across the following in my alert log

ORA-01555 caused by SQL statement below (Query Duration=4943 sec, SCN:
0x0000.4ca5643a):
Thu Oct 16 06:41:54 2003
delete from match_data where category =:"SYS_B_0"
Thu Oct 16 06:42:32 2003

Does this mean my delete suffered from a snapshot too old error.  I wouldn't
have thought this is possible with what I thought I understood about Oracle.  I
thought the delete would retrieve blocks in current mode and therefore not be
subjected to 1555.  Can you set me straight please?

Thanks.

          Followup:

The delete runs the "search" part in consistent read mode.

It performs the delete in current mode.

If you tkprof a statement like that -- you'll see both query and current mode
gets.

updates -- same thing, they search consistently, modify in current mode

You might be interested in the article entitled

"write consistency"

search for that on this site, could be very illuminating.

October 16, 2003 Ken -- Thanks for the question regarding "What is
      a SID, how to change it, how to find out what it is.", version Oracle 8.0

      You Asked
Hi Tom,

A trivia question for you, what is SID and what is it used for?
Would changing it have any impact on the different instances of
the database?  And lastly, where can I find out the SID of my
database?  I don't seem to able to find it in
technet.oracle.com.

Please explain.

Thanks
GoJo.

      and we said...
The SID is a site identifier.  It plus the Oracle_home are
hashed together in Unix to create a unique key name for
attaching an SGA.  If your Oracle_sid or Oracle_home is not set
correctly, you'll get "oracle not available" since we cannot
attach to a shared memory segment that is identified by magic
key.  On NT, we don't use shared memory but the SID is still
important.  We can have more then 1 database on the same oracle
home so we need a way to id them.

Changing it harder then it looks.  I know you are on unix, so
here are the steps for changing it (or the database name) under
Unix - they are different on NT.

How to find the sid -- "select instance from v$thread" will do
that.

PURPOSE
  This entry describes how to find and change the "db_name" for
a database, or   the ORACLE_SID for an instance, without
recreating the database.

SCOPE & APPLICATION
  For DBAs requiring to either find or change the db_name or
ORACLE_SID.

To find the current DB_NAME and ORACLE_SID:
===========================================

Query the views v$database and v$thread.

    V$DATABASE gives DB_NAME
    V$THREAD gives ORACLE_SID

If ORACLE_SID = DB_SID and db_name = DBNAME:

To find the current value of ORACLE_SID:

    SVRMGR> select instance from v$thread;

    INSTANCE
    ----------------
    DB_SID

To find the current value of DB_NAME:

    SVRMGR> select name from v$database;

    NAME
    ---------
    DBNAME

Modifying a database to run under a new ORACLE_SID:
===================================================

1.  Shutdown the instance

2.  Backup all control, redo, and data files.

3.  Go through the .profile, .cshrc, .login, oratab,
tnsnames.ora,  (for SQL*Net version 2), and redefine the
ORACLE_SID environment variable to a new value.

 For example, search through disks and do a grep ORACLE_SID *

4.  Change locations to the "dbs" directory

       % cd $ORACLE_HOME/dbs

    and rename the following files:

    o   init<sid>.ora  (or use pfile to point to the init file.)

    o   control file(s). This is optional if you do not rename
any
        of the controlfiles, and the control_files parameter is
used.
        The "control_files" parameter is set in the
"init<SID>.ora" file or in a file it references with the ifile
parameter.  Make  sure that the control_files parameter does not
point to old file names, if you have renamed them.
    o   "crdb<sid>.sql" & "crdb2<sid>.sql",  This is optional.
These are  only used at database creation.

5.  Change locations to the "rdbms/admin" directory

       % cd $ORACLE_HOME/rdbms/admin

    and rename the file:

    o   startup<sid>.sql.  This is optional.  On some platforms,
 this file may be in the "$ORACLE_HOME/rdbms/install" directory.
 Make sure that the contents of this file do not reference old
init<SID>.ora files that have been renamed.  This file
simplifies the "startup exclusive" process to start your
database.

6.  To rename the database files and redo log files, you would
follow the  instructions in <Note:9560.1>.

7.  Change the ORACLE_SID environment variable to the new value.

8.  Check in the "$ORACLE_HOME/dbs" directory to see if the
password  file has been enabled.  If enabled, the file
"orapw<OLD_SID>" will exist and a new password file for the new
SID must be created  (renaming the old file will not work).  If
"orapw<OLD_SID>" does not exist, skip to step 9.  To create a
new password file, issue the following command as oracle owner:

       orapwd file=orapw<NEWSID> password=?? entries=<number of
users to be granted permission to start the database instance>

9.  Start up the database and verify that it works.  Once you
have done  this, shutdown the database and take a final backup
of all control,  redo, and data files.

10. When the instance is started, the control file is updated
with the  current ORACLE_SID.

Changing the "db_name" for a Database:
======================================

1.  Login to Server Manager

        % svrmgrl
        SVRMGR> connect internal

2.  Type

        SVRMGR> alter system switch logfile;

    to force a checkpoint.

3.  Type

        SVRMGR> alter database backup controlfile to trace
resetlogs;

    This will create a trace file containing the "CREATE
CONTROLFILE"
    command to recreate the controlfile in its current form.

4.  Shutdown the database and exit SVRMGR

        SVRMGR> shutdown

        SVRMGR> exit

    The database must be shutdown with SHUTDOWN NORMAL or
SHUTDOWN IMMEDIATE. It must not be shutdown abnormally using
SHUTDOWN ABORT.

5.  Change locations to the directory where your trace files are
located.  They are usually in the "$ORACLE_HOME/rdbms/log"
directory.  If  "user_dump_dest" is set in the "init<SID>.ora"
file, then go to the  directory listed in the "user_dump_dest"
variable.  The trace file will have the form "ora_NNNN.trc with
NNNN being a number.

6.  Get the "CREATE CONTROLFILE" command from the trace file and
put it in a  new file called something like "ccf.sql".

7.  Edit the "ccf.sql" file

    FROM: CREATE CONTROLFILE REUSE DATABASE "olddbname"
NORESETLOGS ...
      TO: CREATE CONTROLFILE set DATABASE "newdbname"  RESETLOGS
...

    FROM:
    # Recovery is required if any of the datafiles are restored
backups,
    # or if the last shutdown was not normal or immediate.
    RECOVER DATABASE USING BACKUP CONTROLFILE
    TO:
    # Recovery is required if any of the datafiles are restored
backups,
    # or if the last shutdown was not normal or immediate.
    # RECOVER DATABASE USING BACKUP CONTROLFILE

8.  Save and exit the "ccf.sql" file

9.  Rename the old control files for backup purposes and so that
they do not exist when creating the new ones.

10. Edit the "init<SID>.ora" file so that db_name="newdb_name" .

11. Login to Server Manager

        % svrmgrl
        SVRMGR> connect internal

12. Run the "ccf.sql" script

        SVRMGR> @ccf

    This will issue a startup nomount, and then recreate the
controlfile.

    If, at this point, you receive the error that a file needs
media recovery, the database was not shutdown normally as
specified in step 4. You can try recovering the database using
the redo in the current logfile, by issuing:

    SVRMGRL>  recover database using backup controlfile;

    This will prompt for an archived redologfile. It may be
possible to  open the database after applying the current
logfile. BUT this is not guaranteed. If, after applying the
current logfile, the database will not open then it is highly
likely that the operation must be restarted having shutdown the
database normally.

    To apply the necessary redo, you need to check the online
logfiles and apply the one with the same sequence number as
reported in the message. This usually is the logfile with
status=CURRENT.

    To find a list of the online logfiles:

    SVRMGR> select group#, seq#, status from v$log;
    GROUP#     SEQUENCE#   STATUS
    ---------- ---------   ----------------
             1 123         CURRENT     <== this redo needs to be
applied
             2 124         INACTIVE
             3 125         INACTIVE
             4 126         INACTIVE
             5 127         INACTIVE
             6 128         INACTIVE
             7 129         INACTIVE

    7 rows selected.

    SVRMGR> select member
              from v$logfile
             where GROUP# = 1;

    Member
    ------------------------------------
    /u02/oradata/V815/redoV81501.log

    The last command in ccf.sql should be:

    SVRMGR> alter database open resetlogs;

13. You may also need to change the global database name:

    alter database rename global_name to <newdb_name>.<domain>

    See <Note:1018634.102> for further detail.

14. Make sure the database is working.

15. Shutdown and backup the database.
Review &
      Followup

      Rating:
      4
  June 17, 2001
Reviewer:  Ganesh
       from Pune,India
GREAT ANSWER

      Rating: 5
oracle
      dba  September 05, 2001
Reviewer:  mian mazhar
      iqbal

   i found this help a great asset.

      Rating: 3
how many DBAs
      oracle needs to change a light bulb  September 13, 2001

Reviewer:  Mikito Harakiri  from SF, CA
It is amaizing how much mechanical work is required for simple things in oracle.

Relational Model is about normalisation; or to put it simply, no redundant data.
What we see here: an SID in dosen of places?

          Followup:

erh???

The simple thing here is -- don't change the SID.

The database is a relational model -- the software that runs it, well, its not.

ACTUALLY, what we see here is someone trying to UPDATE A PRIMARY KEY.

This is actually a GREAT example of a relational model.  The primary key of the
database is the SID.  You are trying to update the primary key (a BIG NO NO in
relational databases) and have to do the "on update cascade".

So, if you use even a SINGLE foreign key -- you have the same issue.  If you
ever update your primary key value -- you have to do the same thing (find all of
the foreign keys and update them as well).

why you would consider renaming a SID to be a "simple thing" ( i consider
something very obscure -- never having actually ever having to do it myself in
15 years )......

      Rating: 4
How long should
      this take  September 14, 2001
Reviewer:
      frank  from NL
I tried something similar to copy a 70G db. (Didn't want to exp-imp).
I copied all dbf-files & redologs, then copied & altered init.ora
Now when i run my "ccf" file, it just seems to hang. I don't  see nothing
happening, no ctl-files appearing, no CPU (in top) no reaction to ^C
How long should this process of (re)creating ctlfiles take?

          Followup:

it should be virtually instantaneous -- seconds at most.

      Rating: 5
Great
      answer  September 14, 2001
Reviewer:  Joel
       from Columbus, OH USA
Thanks, Tom....this is something that has always puzzled me.

      Rating:
      5
dba  November 15, 2001
Reviewer:  lucas
      mashele  from south africa
highly informative

      Rating:
      3
SID  February 16, 2002
Reviewer:
      Ofoe  from Ghana
Tom ,
Can you an example in changing the SID on WINDOWS 95/98 platforms?

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:634671569269
shows that.

      Rating:
      5
  February 22, 2002
Reviewer:  an
       from DE, usa
great informative!

      Rating: 4
How did you
      connect in the first place?  March 01, 2002

Reviewer:  Liz  from New York
Excellent description of changing a sid.  I did that all the time at a previous
job to create test instances.  I found the why a sid portion to fascinating.  It
was how to find out a sid portion that got me wondering.  If you don't know the
sid, how did you connect to the instance to ask it what it's sid is?

          Followup:

ps -<whatever flags are appropriate on your system> | grep pmon

for example:

$ ps -aux | grep pmon
ora734    3662  0.0  0.518720 6896 ?        S   Jan 21  0:00 ora_pmon_ora734
tkyte     7446  0.0  0.1  944  784 pts/8    S 13:30:09  0:00 grep pmon
ora716   12929  0.0  0.417472 5552 ?        S   Jan 22  0:00 ora_pmon_ora716
oracle9i 13112  0.0 13.6249688205872 ?        S   Jan 18  0:00 ora_pmon_ora9iUTF
ora806   13901  0.0  1.03324014080 ?        S   Feb 26  0:00 ora_pmon_ora806
oracle9i 17941  0.0 24.6413528372528 ?        S   Feb 18  0:00 ora_pmon_ora9i
ora817   25122  0.0 16.2279824245824 ?        S   Feb 01  0:00
ora_pmon_ora817dev
ora815   25424  0.0  2.99008043712 ?        S   Jan 28  0:00 ora_pmon_ora815

shows 7 sids I currently have running on my testbed machine --
ora_pmon_$ORACLE_SID

If you aren't on the server -- the sid just doesn't matter, you need the tns
entry found in the tnsnames.ora file.

On NT, look at the list of Oracle services in control panel.

      Rating: 5
soooo
      cool!  August 20, 2002
Reviewer:  Fenng
       from China ITPUB
It's so cool.
But don't know if it's fit for Oracle9i.
thanks a lot !

          Followup:

c/svrmgrl/sqlplus/

      Rating: 5
Thanks Mr. Tom
        October 06, 2002
Reviewer:  Arun Kumar K T
       from Chitradurga, Karnataka,India
Realy it is fantastic.

      Rating: 4
how to recreate
      the controlfile in Oracle9i  March 20, 2003

Reviewer:  Yakgna  from India
tom,

can you please explain me what all steps needed to recreate control file
with/without existing control file in oracle 9i.
Do we need to backup SPFILE and PFILE while doing so?

as always, thanks for everything you do

Yakgna

          Followup:

You have no existing control files -- no binary backups, no "backup to trace"?

Then the only tool you need is vi or your favorite text editor.  You input a
CREATE CONTROLFILE statement with all of the locations of all of your files.

I don't understand "backing up the spfile or pfile" in this context though?  No,
you don't need to back them up -- they are what tell Oracle where to find the
controlfiles - where to put them when you do the create controlfile.

      Rating: 4
when and how is
      the DBID generated?  June 19, 2003
Reviewer:
      jianhui  from DC
Tom,
When i use clean cold backup to do the database cloning, i found that no matter
i use
1) create control file REUSE database
or
2) create control file SET database
the DBID is alway the same as original source database.

So could you explain the question in the title?

Secondly, what is the initial parameter instance_name used for? I can set it to
any value and still be able to bring up the database even the the instance SID
is totally different. For example, the instance SID is A, i can set
instance_name to B or A, they both work. Is it used for OPS? I need a clue.

          Followup:

dbids are burned into datafiles when the database is created.

you can change it with $ORACLE_HOME/bin/nid (db new id utility) in 9i

check out the Oracle REFERENCE guide for all details on all parameters, but
basically this is a setting you really don't need to touch, especially if you
are "single instance Oracle" (no OPS, no RAC).  In a cluster, if all of the
instances of Oracle running had the same sid, you might want to name each
instance differently so you could tell which one you were connected to (or to
make connecting to a specific instance possible)

      Rating: 5
Cloning an Open
      database  June 30, 2003
Reviewer:  kumar
       from India
Tom,

Can you please validate the following procedure to do the cloning of a open
database ?

1. Take a hot backup of all the datafiles
2. Transfer the datafiles and archive logs to the new server.
3. Get the control file to trace. Edit the parameters for as for normal cloning.
4. In the new init.ora file change the archive destination
and all other relavant settings as for normal cloning.
5. run create controlfile
6. recover database using backup controlfile until cancel
7. open resetlogs

Is there any other way to do this (excluding export/import) ?

Thanks

          Followup:

rman does it with a command.

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96566/rcmdupdb.htm#441628

doing it manually like you propose is the same as an "incomplete media recovery"
and is outlined here:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96572/performingreco.htm#14614

      Rating: 5
Cloning across
      OS  August 13, 2003
Reviewer:  Ma$e

Hi Tom:

Look forward to your sessions at the Oracle World-2003.

On the subject of this discusion:
Can a d/b be cloned across OS ? Say from Solaris to NT/2K ?

Thanks.

          Followup:

not in 9iR2 and before...  :)  read into that "and before" what you will

      Rating: 4
May be in
      10G  August 13, 2003
Reviewer:  Ma$e

Do I read then 10G will support it ?

          Followup:

you read what you want ;) ;)

wink wink

      Rating: 5
I'm feeling
      tidly winky now....  August 13, 2003
Reviewer:
      Ma$e
Hi Tom:

On the serious side of things...

What are the dependancies for cloning a d/b across the same o/s. Issues like:

1) Patch sets applied on the source and target servers
2) Versions of Oracle and OS Binaries
3) any other issues

Thanks

Ma$e

          Followup:

come back in a month or two -- sorry.

      Rating: 5
I meant for
      cloning on 9i Rel 2 or earlier versions.  August 13, 2003

Reviewer:  A reader

          Followup:

ahh - sorry, clones should be clones

same os rev
same oracle rev
same patch levels
"clone"

      Rating:
      4
Thanks...  August 13, 2003
Reviewer:
      A reader

      Rating: 5
what happens
      step by step when alter database open resetlogs  October 15,
      2003
Reviewer:  yohann  from France
Hi Tom,
I try to understand that:
I create a clone database; but at the time to restore datafiles, one or more
datafiles are read-only (r--r--r-- on UNIX); this a bug of the backup software.

So when I "ALTER DATABASE OPEN RESETLOGS", I have:
Ora-1190, 00000, "controlfile or data file %s is from before the last RESETLOGS"

Up to here, I understand why (i "understood" is the exact term...)
Now when i issue a RECOVER DATABASE, all is OK and I can OPEN DATABASE! here I
don't understand anymore !!!

What happens precisely inside Oracle? What checks are made, in which files
(control files, datafiles)?
I tried to dump files at each step, but can't understand.

Thanks for your help.

          Followup:

why did you think the rrr has something to do with this?

does the rrr become writeable at some point?

if you change the files from rrr to writeable first -- does it still happen

why are you associating the two things?

are the files in the backup actually from before the last resetlogs.

what is the entire scenario here

      Rating:
      5
  October 16, 2003
Reviewer:  yohann
       from france
Oh... sorry. I'll try to be more precise and to not forget any crucial
information:

here are the steps:
1. restore a COLD backup (shut down normal).
  The file users01.dbf is r--r--r-- (instead of rw-r--r--)

2. CREATE CONTROLFILE SET DATABASE <SID> RESETLOGS ARCHIVELOG ....
 => OK: Control files are created

3. ALTER DATABASE OPEN RESETLOGS;

see the alert_SID.log contents:
ORA-01110: data file 5: '/oracle/SID/oradata/SID/users01.dbf'
ORA-01114: IO error writing block to file 5 (block # 1)
ORA-27070: skgfdisp: async read/write failed
SVR4 Error: 9: Bad file number
Additional information: 1
Thu Oct 16 12:27:06 2003
ORA-1190 signalled during: alter database open resetlogs

Note : REDO LOGS are now created, and CTLF re-initialized

4. I took a dump of datafile headers and a dump of control file.
when analyzing the dump of datafile headers, i see that the "reset logs count",
"chkpt cnt" and "ctl cnt",  doesn't match between datafiles(since users01.dbf
was read-only on UNIX, Oracle couldn't make any change in the file) and between
the content of controlfile.
moreover the "rba" field has not been reset in the file header.(is it the same
as what is called the Log Sequence Number?)
But the controlfile has been written with the informations of the RESETLOGS (new
SCN, sequence number reset to 1, and so on...)

5. chmod 640 users01.dbf (that's the crucial info i've omitted)
6. RECOVER DATABASE
Since resetlog count, checkpoint count, ctl count,etc... doesn't match, why
Oracle does accept to RECOVER a file that is from a previous incarnation??? what
is the algorithm to check CTL files and datafiles to decide to recover or not?

7. ALTER DATABASE OPEN
 => OK : database open ; all counters are then OK in the headers.

Thank you very much for your help.

          Followup:

6 -- it'll do that in the event there is sufficient data in the available logs
to do so.  since NO information was actually needed, it worked by accident.

there are cases when you can recover through a resetlogs -- it can be tricky
(never never rely on it 100% -- you need to be in a special state).  Rman can do
it as well -- might want to read support notes  <Note:1070453.6>
<Note:237232.1>

you actually didn't need to apply any redo, you got lucky.

But - you now know (via testing) what your restore procedure SHOULD be -- add
the chmod after the restore and you'll never be in this situation.  Good thing
you are practicing.

October 16, 2003 Alex -- Thanks for the question regarding
      "'Reverse' transposition SQL optimisation", version 9.2.0.3

      You Asked
Hello Tom...

We have both your books in our team... thank you, thank you, thank you!

In your expert-one on one you have some helpful instructions on pivotting... but
we have a reverse situation where we want to pivot from columns to rows!

If we could take you through a following script example...

12:31:13 ep@EP> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
PL/SQL Release 9.2.0.3.0 - Production
CORE    9.2.0.3.0       Production
TNS for 32-bit Windows: Version 9.2.0.3.0 - Production
NLSRTL Version 9.2.0.3.0 - Production

set serveroutput on
set arraysize 500

==================================================

Ok.. first thing... create sample data...

==================================================

 Drop table TABLE_OFMANYCOLUMNS;

 CREATE TABLE TABLE_OFMANYCOLUMNS
 (
   PK_ID            NUMBER(9),
   AKEYLOOKUP_ID    NUMBER(9),
   AKEYLOOKUP_DATE  DATE,
   P0030            NUMBER(17,4),
   P0100            NUMBER(17,4),
   P0130            NUMBER(17,4),
   P0200            NUMBER(17,4),
   P0230            NUMBER(17,4),
   P0300            NUMBER(17,4),
   P0330            NUMBER(17,4),
   P0400            NUMBER(17,4),
   P0430            NUMBER(17,4),
   P0500            NUMBER(17,4),
   P0530            NUMBER(17,4),
   P0600            NUMBER(17,4),
   P0630            NUMBER(17,4),
   P0700            NUMBER(17,4),
   P0730            NUMBER(17,4),
   P0800            NUMBER(17,4),
   P0830            NUMBER(17,4),
   P0900            NUMBER(17,4),
   P0930            NUMBER(17,4),
   P1000            NUMBER(17,4),
   P1030            NUMBER(17,4),
   P1100            NUMBER(17,4),
   P1130            NUMBER(17,4),
   P1200            NUMBER(17,4),
   P1230            NUMBER(17,4),
   P1300            NUMBER(17,4),
   P1330            NUMBER(17,4),
   P1400            NUMBER(17,4),
   P1430            NUMBER(17,4),
   P1500            NUMBER(17,4),
   P1530            NUMBER(17,4),
   P1600            NUMBER(17,4),
   P1630            NUMBER(17,4),
   P1700            NUMBER(17,4),
   P1730            NUMBER(17,4),
   P1800            NUMBER(17,4),
   P1830            NUMBER(17,4),
   P1900            NUMBER(17,4),
   P1930            NUMBER(17,4),
   P2000            NUMBER(17,4),
   P2030            NUMBER(17,4),
   P2100            NUMBER(17,4),
   P2130            NUMBER(17,4),
   P2200            NUMBER(17,4),
   P2230            NUMBER(17,4),
   P2300            NUMBER(17,4),
   P2330            NUMBER(17,4),
   P2400            NUMBER(17,4)
 )
 TABLESPACE EP
 PCTUSED    0
 PCTFREE    10
 INITRANS   1
 MAXTRANS   255
 STORAGE    (
             INITIAL          64K
             MINEXTENTS       1
             MAXEXTENTS       2147483645
             PCTINCREASE      0
             BUFFER_POOL      DEFAULT
            )
 LOGGING
 NOCACHE
 NOPARALLEL;

 CREATE UNIQUE INDEX LOOKUP_IDX ON TABLE_OFMANYCOLUMNS
 (AKEYLOOKUP_ID, AKEYLOOKUP_DATE)
 LOGGING
 TABLESPACE INDX
 PCTFREE    10
 INITRANS   2
 MAXTRANS   255
 STORAGE    (
             INITIAL          64K
             MINEXTENTS       1
             MAXEXTENTS       2147483645
             PCTINCREASE      0
             BUFFER_POOL      DEFAULT
            )
 NOPARALLEL;

 CREATE UNIQUE INDEX PK_PK_ID ON TABLE_OFMANYCOLUMNS
 (PK_ID)
 LOGGING
 TABLESPACE INDX
 PCTFREE    10
 INITRANS   2
 MAXTRANS   255
 STORAGE    (
             INITIAL          64K
             MINEXTENTS       1
             MAXEXTENTS       2147483645
             PCTINCREASE      0
             BUFFER_POOL      DEFAULT
            )
 NOPARALLEL;

 ALTER TABLE TABLE_OFMANYCOLUMNS ADD (
   CONSTRAINT PK_PK_ID PRIMARY KEY (PK_ID)
     USING INDEX
     TABLESPACE INDX
     PCTFREE    10
     INITRANS   2
     MAXTRANS   255
     STORAGE    (
                 INITIAL          64K
                 MINEXTENTS       1
                 MAXEXTENTS       2147483645
                 PCTINCREASE      0
                ));

 Declare
 cnt_sequence number := 0;
 cnt_date date ;

 Begin
 for n in 1..200 loop

     cnt_date :=to_date('01/01/2003','dd/mm/yyyy');
     for o in 1..365 loop
         cnt_sequence := cnt_sequence + 1;

         insert into TABLE_OFMANYCOLUMNS
         (PK_ID, AKEYLOOKUP_ID, AKEYLOOKUP_DATE,
         P0030,P0100,P0130,P0200,P0230,P0300,P0330,P0400,P0430,P0500,
         P0530,P0600,P0630,P0700,P0730,P0800,P0830,P0900,P0930,P2400)
         values
         (cnt_sequence,
         n,
         to_date(cnt_date,'dd/mm/yy'),
         (select dbms_random.value(0,200) from dual),
         (select dbms_random.value(0,200) from dual),
         (select dbms_random.value(0,200) from dual),
         (select dbms_random.value(0,200) from dual),
         (select dbms_random.value(0,200) from dual),
         (select dbms_random.value(0,200) from dual),
         (select dbms_random.value(0,200) from dual),
         (select dbms_random.value(0,200) from dual),
         (select dbms_random.value(0,200) from dual),
         (select dbms_random.value(0,200) from dual),
         (select dbms_random.value(0,200) from dual),
         (select dbms_random.value(0,200) from dual),
         (select dbms_random.value(0,200) from dual),
         (select dbms_random.value(0,200) from dual),
         (select dbms_random.value(0,200) from dual),
         (select dbms_random.value(0,200) from dual),
         (select dbms_random.value(0,200) from dual),
         (select dbms_random.value(0,200) from dual),
         (select dbms_random.value(0,200) from dual),
         (select dbms_random.value(0,200) from dual)
         );
         cnt_date := cnt_date +1;
         end loop;
 end loop;
 End;
/

 PL/SQL procedure successfully completed.

  commit;

 Commit complete.

 ==================================================

 Now.. we want to query this table (normally.. there are a lot more (hundreds of
thousands)
 rows with more fields i the table with indexed lookups etc!

 We have the following query whch transposes the data.. but it is VERY expensive
and requires
 a select for every column want values pivotted for)

 ==================================================

 analyze table TABLE_OFMANYCOLUMNS compute statistics;

 Table analyzed.

timing start SeeHowLongThisTakes
timing show
set autotrace traceonly;

 timing for: SeeHowLongThisTakes
Elapsed: 00:00:00.00

I

 (select PK_ID, AKEYLOOKUP_ID, AKEYLOOKUP_DATE,  'P0030' as P_NO, P0030 as
P_VALUE
     from  TABLE_OFMANYCOLUMNS t
 union all
 select PK_ID, AKEYLOOKUP_ID, AKEYLOOKUP_DATE,  'P0100' as P_NO, P0100 as
P_VALUE
     from  TABLE_OFMANYCOLUMNS t
 union all
 select PK_ID, AKEYLOOKUP_ID, AKEYLOOKUP_DATE,  'P0130' as P_NO, P0130 as
P_VALUE
     from  TABLE_OFMANYCOLUMNS t
 union all
 select PK_ID, AKEYLOOKUP_ID, AKEYLOOKUP_DATE,  'P0200' as P_NO, P0200 as
P_VALUE
     from  TABLE_OFMANYCOLUMNS t
 union all
 select PK_ID, AKEYLOOKUP_ID, AKEYLOOKUP_DATE,  'P0230' as P_NO, P0230 as
P_VALUE
     from  TABLE_OFMANYCOLUMNS t
 union all
 select PK_ID, AKEYLOOKUP_ID, AKEYLOOKUP_DATE,  'P0300' as P_NO, P0300 as
P_VALUE
     from  TABLE_OFMANYCOLUMNS t
 union all
 select PK_ID, AKEYLOOKUP_ID, AKEYLOOKUP_DATE,  'P0330' as P_NO, P0330 as
P_VALUE
     from  TABLE_OFMANYCOLUMNS t
 union all
 select PK_ID, AKEYLOOKUP_ID, AKEYLOOKUP_DATE,  'P0400' as P_NO, P0400 as
P_VALUE
     from  TABLE_OFMANYCOLUMNS t
 union all
 select PK_ID, AKEYLOOKUP_ID, AKEYLOOKUP_DATE,  'P0430' as P_NO, P0430 as
P_VALUE
     from  TABLE_OFMANYCOLUMNS t
 union all
 select PK_ID, AKEYLOOKUP_ID, AKEYLOOKUP_DATE,  'P0500' as P_NO, P0530 as
P_VALUE
     from  TABLE_OFMANYCOLUMNS t
 union all
 select PK_ID, AKEYLOOKUP_ID, AKEYLOOKUP_DATE,  'P0600' as P_NO, P0600 as
P_VALUE
     from  TABLE_OFMANYCOLUMNS t
 union all
 select PK_ID, AKEYLOOKUP_ID, AKEYLOOKUP_DATE,  'P0630' as P_NO, P0630 as
P_VALUE
     from  TABLE_OFMANYCOLUMNS t
 union all
 select PK_ID, AKEYLOOKUP_ID, AKEYLOOKUP_DATE,  'P0700' as P_NO, P0700 as
P_VALUE
     from  TABLE_OFMANYCOLUMNS t
 union all
 select PK_ID, AKEYLOOKUP_ID, AKEYLOOKUP_DATE,  'P0730' as P_NO, P0730 as
P_VALUE
     from  TABLE_OFMANYCOLUMNS t
 union all
 select PK_ID, AKEYLOOKUP_ID, AKEYLOOKUP_DATE,  'P0800' as P_NO, P0800 as
P_VALUE
     from  TABLE_OFMANYCOLUMNS t
 union all
 select PK_ID, AKEYLOOKUP_ID, AKEYLOOKUP_DATE,  'P0830' as P_NO, P0830 as
P_VALUE
     from  TABLE_OFMANYCOLUMNS t
 union all
 select PK_ID, AKEYLOOKUP_ID, AKEYLOOKUP_DATE,  'P0900' as P_NO, P0900 as
P_VALUE
     from  TABLE_OFMANYCOLUMNS t
 union all
 select PK_ID, AKEYLOOKUP_ID, AKEYLOOKUP_DATE,  'P0930' as P_NO, P0930 as
P_VALUE
     from  TABLE_OFMANYCOLUMNS t
 union all
 select PK_ID, AKEYLOOKUP_ID, AKEYLOOKUP_DATE,  'P2400' as P_NO, P2400 as
P_VALUE
     from  TABLE_OFMANYCOLUMNS t)
 order by AKEYLOOKUP_ID, AKEYLOOKUP_DATE, P_NO;

1387000 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=23157 Card=1387000 B
          ytes=26353000)

   1    0   SORT (ORDER BY) (Cost=779 Card=1387000 Bytes=26353000)
   2    1     UNION-ALL
   3    2       TABLE ACCESS (FULL) OF 'TABLE_OFMANYCOLUMNS' (Cost=41
          Card=73000 Bytes=1387000)

   4    2       TABLE ACCESS (FULL) OF 'TABLE_OFMANYCOLUMNS' (Cost=41
          Card=73000 Bytes=1387000)

   5    2       TABLE ACCESS (FULL) OF 'TABLE_OFMANYCOLUMNS' (Cost=41
          Card=73000 Bytes=1387000)

   6    2       TABLE ACCESS (FULL) OF 'TABLE_OFMANYCOLUMNS' (Cost=41
          Card=73000 Bytes=1387000)

   7    2       TABLE ACCESS (FULL) OF 'TABLE_OFMANYCOLUMNS' (Cost=41
          Card=73000 Bytes=1387000)

   8    2       TABLE ACCESS (FULL) OF 'TABLE_OFMANYCOLUMNS' (Cost=41
          Card=73000 Bytes=1387000)

   9    2       TABLE ACCESS (FULL) OF 'TABLE_OFMANYCOLUMNS' (Cost=41
          Card=73000 Bytes=1387000)

  10    2       TABLE ACCESS (FULL) OF 'TABLE_OFMANYCOLUMNS' (Cost=41
          Card=73000 Bytes=1387000)

  11    2       TABLE ACCESS (FULL) OF 'TABLE_OFMANYCOLUMNS' (Cost=41
          Card=73000 Bytes=1387000)

  12    2       TABLE ACCESS (FULL) OF 'TABLE_OFMANYCOLUMNS' (Cost=41
          Card=73000 Bytes=1387000)

  13    2       TABLE ACCESS (FULL) OF 'TABLE_OFMANYCOLUMNS' (Cost=41
          Card=73000 Bytes=1387000)

  14    2       TABLE ACCESS (FULL) OF 'TABLE_OFMANYCOLUMNS' (Cost=41
          Card=73000 Bytes=1387000)

  15    2       TABLE ACCESS (FULL) OF 'TABLE_OFMANYCOLUMNS' (Cost=41
          Card=73000 Bytes=1387000)

  16    2       TABLE ACCESS (FULL) OF 'TABLE_OFMANYCOLUMNS' (Cost=41
          Card=73000 Bytes=1387000)

  17    2       TABLE ACCESS (FULL) OF 'TABLE_OFMANYCOLUMNS' (Cost=41
          Card=73000 Bytes=1387000)

  18    2       TABLE ACCESS (FULL) OF 'TABLE_OFMANYCOLUMNS' (Cost=41
          Card=73000 Bytes=1387000)

  19    2       TABLE ACCESS (FULL) OF 'TABLE_OFMANYCOLUMNS' (Cost=41
          Card=73000 Bytes=1387000)

  20    2       TABLE ACCESS (FULL) OF 'TABLE_OFMANYCOLUMNS' (Cost=41
          Card=73000 Bytes=1387000)

  21    2       TABLE ACCESS (FULL) OF 'TABLE_OFMANYCOLUMNS' (Cost=41
          Card=73000 Bytes=1387000)

Statistics
----------------------------------------------------------
        990  recursive calls
         47  db block gets
      31239  consistent gets
       8673  physical reads
          0  redo size
   24139554  bytes sent via SQL*Net to client
      31548  bytes received via SQL*Net from client
       2775  SQL*Net roundtrips to/from client
         22  sorts (memory)
          1  sorts (disk)
    1387000  rows processed

timing show

timing for: SeeHowLongThisTakes
Elapsed: 00:00:20.09

==================================================

 Tom...is there a beter way to construct this query...
 possibly returning the pivotted result with one pass!

 Thanks!

      and we said...
Two ways -- one is a pipelined function, the other just "sql".

first the pipelined function -- we start with an object type that describes our
output.  Think of this as you would a "view" definition -- just a template.
YES, it must be done as a create type -- it cannot be "hidden" in plsql, it must
be a SQL type:

ops$tkyte@ORA920> create or replace type myScalarType as object
  2  ( pk_id           number,
  3    akeylookup_id   number,
  4    akeylookup_date date,
  5    datum          number
  6  )
  7  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace type myTableType as table of myScalarType
  2  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> CREATE TABLE TABLE_OFMANYCOLUMNS
  2   (
  3     PK_ID            NUMBER(9),
  4     AKEYLOOKUP_ID    NUMBER(9),
  5     AKEYLOOKUP_DATE  DATE,
  6     P0030            NUMBER(17,4),
  7     P0100            NUMBER(17,4),
  8     P0130            NUMBER(17,4),
  9     P0200            NUMBER(17,4)
 10  )
 11  /

Table created.

ops$tkyte@ORA920> exec gen_data( 'TABLE_OFMANYCOLUMNS', 100 );

PL/SQL procedure successfully completed.

for demonstration purposes, i'll use 4 columns -- you can have as many as you
like..

ops$tkyte@ORA920> create or replace
  2  function reverse_pivot( p_cursor in sys_refcursor )
  3  return myTableType
  4  pipelined
  5  as
  6      type array is table of table_ofManyColumns%rowtype index by
binary_integer;
  7      l_data array;
  8      l_rec  myScalarType := myScalarType(null,null,null,null);
  9  begin
 10      loop
 11          fetch p_cursor bulk collect into l_data limit 100;
 12          for i in 1 .. l_data.count
 13          loop
 14              l_rec.pk_id           := l_data(i).pk_id;
 15              l_rec.akeyLookup_date := l_data(i).akeyLookup_date;
 16              l_rec.akeylookup_id   := l_data(i).akeyLookup_id;
 17              l_rec.datum           := l_data(i).p0030;
 18              pipe row( l_rec );
 19              l_rec.datum           := l_data(i).p0100;
 20              pipe row( l_rec );
 21              l_rec.datum           := l_data(i).p0130;
 22              pipe row( l_rec );
 23              l_rec.datum           := l_data(i).p0200;
 24              pipe row( l_rec );
 25          end loop;
 26          exit when p_cursor%notfound;
 27      end loop;
 28      close p_cursor;
 29      return;
 30  end;
 31  /

Function created.

there is the procedure to pivot.  it takes a result set as input (a set of
rows from table of many columns).  It bulk fetches 100 rows at a time and for
each row -- it outputs N rows (n = number of columns to unpivot)

ops$tkyte@ORA920>
ops$tkyte@ORA920> select *
  2    from TABLE( reverse_pivot( cursor( select * from table_ofManyColumns
where rownum = 1 ) ) )
  3  /

     PK_ID AKEYLOOKUP_ID AKEYLOOKU      DATUM
---------- ------------- --------- ----------
 644717704     283491708 09-OCT-04 5.0958E+12
 644717704     283491708 09-OCT-04 4.9144E+12
 644717704     283491708 09-OCT-04 4.9737E+12
 644717704     283491708 09-OCT-04 2.3809E+12

and there is how to call it.  You would put whatever query with whatever binds
you want in the CURSOR() call and we'll unpivot it.

You can use SQL as well - we need a table to unpivot with

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table tall_dual ( x primary key ) organization index
  2  as
  3  select rownum from all_objects where rownum <= 100;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select pk_id, akeyLookup_id, akeyLookup_date,
  2         decode( x, 1, p0030, 2, p0100, 3, p0130, 4, p0200 ) datum
  3    from ( select * from table_ofManyColumns where rownum = 1 ) a,
  4         ( select x from tall_dual where x <= 4 ) b
  5  /

     PK_ID AKEYLOOKUP_ID AKEYLOOKU      DATUM
---------- ------------- --------- ----------
 644717704     283491708 09-OCT-04 5.0958E+12
 644717704     283491708 09-OCT-04 4.9144E+12
 644717704     283491708 09-OCT-04 4.9737E+12
 644717704     283491708 09-OCT-04 2.3809E+12

a simple cartesian product will turn each row in A into N rows from B.  Decode
picks off the column we want and wah-lah, you are done

Benchmark/test each approach in your environment to see which is most appealing
to you.
Review
      & Followup

      Rating: 5
Pipe function
      vs Decode!  October 16, 2003
Reviewer:  Alex
       from Sydney, Oz
Hello Tom,

And thank you for the great reply!

We have not followed up earlier is because we have been exploring carefully both
alternatives you suggested.

We were a litle surprised by the results!

At first.. we thought the idea of a pipeline function was very elegant so we
explored that avenue first.  However we soon came to a roadblock - when I first
posted our query I possibly over simplified the issue.

In the original post I outlined that there was a number some lookup tables which
needed to be joined together with a single table with nuumerous columns to be
transposed to rows.  There is actually 2 tables...

Looking at our original View:

--
---------------------------------------------------------------------------------
-

CREATE OR REPLACE FORCE VIEW EP.EP_V_PERIODMAP_CURVE_R_OLD
(EPCM_CALENDAR_ID, EPCM_SEASON_ID, EPCM_DAY_ID, EPCM_DATE, CURVE_ID,
 P_NO, PERIOD_ID, P_VALUE)
AS
select cm.EPCM_CALENDAR_ID, cm.EPCM_SEASON_ID, cm.EPCM_DAY_ID, cm.EPCM_DATE,
cd.CURVE_ID, 'P0030' as P_NO, pm.P0030 as PERIOD_ID, cd.P0030 as P_VALUE
    from   ep_calendar_map cm,
           ep_period_map pm,
           ep_curvedetails cd
    where  cm.EPCM_CALENDAR_ID = pm.EPPM_CALENDAR_ID
    and    cm.EPCM_SEASON_ID   = pm.EPPM_SEASON_ID
    and    cm.EPCM_DAY_ID      = pm.EPPM_DAY_ID
    and    cm.EPCM_DATE        = cd.curvedetails_date
    union all
    select cm.EPCM_CALENDAR_ID, cm.EPCM_SEASON_ID, cm.EPCM_DAY_ID, cm.EPCM_DATE,
cd.CURVE_ID, 'P0100' as P_NO, pm.P0100 as PERIOD_ID, cd.P0100 as P_VALUE
    from   ep_calendar_map cm,
           ep_period_map pm,
           ep_curvedetails cd
    where  cm.EPCM_CALENDAR_ID = pm.EPPM_CALENDAR_ID
    and    cm.EPCM_SEASON_ID   = pm.EPPM_SEASON_ID
    and    cm.EPCM_DAY_ID      = pm.EPPM_DAY_ID
    and    cm.EPCM_DATE        = cd.curvedetails_date
    union all

...

    union all
    select cm.EPCM_CALENDAR_ID, cm.EPCM_SEASON_ID, cm.EPCM_DAY_ID, cm.EPCM_DATE,
cd.CURVE_ID, 'P2400' as P_NO, pm.P2400 as PERIOD_ID, cd.P2400 as P_VALUE
    from   ep_calendar_map cm,
           ep_period_map pm,
           ep_curvedetails cd
    where  cm.EPCM_CALENDAR_ID = pm.EPPM_CALENDAR_ID
    and    cm.EPCM_SEASON_ID   = pm.EPPM_SEASON_ID
    and    cm.EPCM_DAY_ID      = pm.EPPM_DAY_ID
    and    cm.EPCM_DATE        = cd.curvedetails_date;

--
---------------------------------------------------------------------------------
-

For the record... this is has been perform!  (7 seconds is too too long!)

ep@EP> select * from EP_V_PERIODMAP_CURVE_READINGS where curve_id = 120403
and EPCM_DATE between '01-Jul-2003' and '10-Jul-2003';

--
---------------------------------------------------------------------------------
-

But what I did not tell you as you ca derive from the view structure is that the
fields pm.P0030 as PERIOD_ID to pm.P2400 as PERIOD_ID also requires
transposition!

So... we we came up with the following interpretation of your sample code... you
will note that we have created Type ty_cal_map IS RECORD and we manage both the
values (from our UNION query):

pm.P0230 as PERIOD_ID
cd.P0230 as P_VALUE

as

               l_rec.p_no              := 'P0030';
               l_rec.period_id         := ty_output_values(i).p0030;
               l_rec.p_value           := ty_output_values(i).c0030;

as you can see in  function EE_PeriodMapCurveReadings...

--
---------------------------------------------------------------------------------
-

create or replace type myCurvePeriodMapValues as object
(EPCM_calendar_id number,
 EPCM_season_id number,
 EPCM_day_id number,
 EPCM_date date,
 Curve_id number,
 p_no varchar2(6),
 period_id number(9),
 p_value number);
/
/* Source of TYPE BODY MYCURVEPERIODMAPVALUES is not available */
/
create or replace type ty_PeriodMap_Curvedetails as table of
myCurvePeriodMapValues;
/
create or replace function EE_PeriodMapCurveReadings(p_cursor in sys_refcursor)
return ty_PeriodMap_Curvedetails

PIPELINED IS
l_rec myCurvePeriodMapValues :=
myCurvePeriodMapValues(null,null,null,null,null,null,null,null);

Type ty_cal_map IS RECORD
  (Cal_id NUMBER,
   Season_Id NUMBER,
   Day_Id NUMBER,
   C_Date DATE,
   curve_id NUMBER,
   p0030 number, p0100 number, p0130 number, p0200 number, p0230 number, p0300
number,
   p0330 number, p0400 number, p0430 number, p0500 number, p0530 number, p0600
number,
   p0630 number, p0700 number, p0730 number, p0800 number, p0830 number, p0900
number,
   p0930 number, p1000 number, p1030 number, p1100 number, p1130 number, p1200
number,
   p1230 number, p1300 number, p1330 number, p1400 number, p1430 number, p1500
number,
   p1530 number, p1600 number, p1630 number, p1700 number, p1730 number, p1800
number,
   p1830 number, p1900 number, p1930 number, p2000 number, p2030 number, p2100
number,
   p2130 number, p2200 number, p2230 number, p2300 number, p2330 number, p2400
number,
   c0030 number, c0100 number, c0130 number, c0200 number, c0230 number, c0300
number,
   c0330 number, c0400 number, c0430 number, c0500 number, c0530 number, c0600
number,
   c0630 number, c0700 number, c0730 number, c0800 number, c0830 number, c0900
number,
   c0930 number, c1000 number, c1030 number, c1100 number, c1130 number, c1200
number,
   c1230 number, c1300 number, c1330 number, c1400 number, c1430 number, c1500
number,
   c1530 number, c1600 number, c1630 number, c1700 number, c1730 number, c1800
number,
   c1830 number, c1900 number, c1930 number, c2000 number, c2030 number, c2100
number,
   c2130 number, c2200 number, c2230 number, c2300 number, c2330 number, c2400
number);

Type ty_RP_CalPer_Map is table of ty_cal_map index by binary_integer;

ty_output_values Ty_Rp_Calper_Map;

   begin
      loop
          fetch p_cursor bulk collect into ty_output_values limit 100; --
_data, qq_data limit 100;
           for i in 1 .. ty_output_values.count
           loop

               l_rec.epcm_calendar_id  := ty_output_values(i).Cal_id;
               l_rec.epcm_season_id    := ty_output_values(i).Season_id;

               l_rec.epcm_day_id       := ty_output_values(i).Day_id;
               l_rec.epcm_date         := ty_output_values(i).c_date;
               l_rec.curve_id          := ty_output_values(i).curve_id;
               l_rec.p_no              := 'P0030';
               l_rec.period_id         := ty_output_values(i).p0030;
               l_rec.p_value           := ty_output_values(i).c0030;
               pipe row( l_rec );
               l_rec.p_no              := 'P0100';
               l_rec.period_id         := ty_output_values(i).p0100;
               l_rec.p_value           := ty_output_values(i).c0100;
                pipe row( l_rec );
               l_rec.p_no              := 'P0130';
               l_rec.period_id         := ty_output_values(i).p0130;
               l_rec.p_value           := ty_output_values(i).c0130;
                pipe row( l_rec );

...
               l_rec.p_no              := 'P2400';
               l_rec.period_id         := ty_output_values(i).p2400;
               l_rec.p_value           := ty_output_values(i).c2400;

               end loop;
           exit when p_cursor%notfound;
       end loop;
       close p_cursor;
       return;
  -- end;

end EE_PeriodMapCurveReadings;
/

--
---------------------------------------------------------------------------------
-

This produced the vastly improved results:

timing for: new_RP_functions
Elapsed: 00:00:00.00
ep@EP>
ep@EP> set autotrace traceonly;
ep@EP>
ep@EP>
ep@EP> select * from TABLE(EE_PeriodMapCurveReadings(cursor(
  2  select cm.EPCM_CALENDAR_ID, cm.EPCM_SEASON_ID, cm.EPCM_DAY_ID,
CM.EPCM_DATE, cd.curve_id,
  3      pm.P0030, pm.P0100,pm.P0130, pm.P0200,pm.P0230, pm.P0300,pm.P0330,
pm.P0400,pm.P0430, pm.P0
500,
  4      pm.P0530, pm.P0600,pm.P0630, pm.P0700,pm.P0730, pm.P0800,pm.P0830,
pm.P0900,pm.P0930, pm.P1
000,
  5      pm.P1030, pm.P1100,pm.P1130, pm.P1200,pm.P1230, pm.P1300,pm.P1330,
pm.P1400,pm.P1430, pm.P1
500,
  6      pm.P1530, pm.P1600,pm.P1630, pm.P1700,pm.P1730, pm.P1800,pm.P1830,
pm.P1900,pm.P1930, pm.P2
000,
  7      pm.P2030, pm.P2100,pm.P2130, pm.P2200,pm.P2230, pm.P2300,pm.P2330,
pm.P2400,
  8      cd.P0030, cd.P0100,cd.P0130, cd.P0200,cd.P0230, cd.P0300,cd.P0330,
cd.P0400,cd.P0430, cd.P0
500,
  9      cd.P0530, cd.P0600,cd.P0630, cd.P0700,cd.P0730, cd.P0800,cd.P0830,
cd.P0900,cd.P0930, cd.P1
000,
 10      cd.P1030, cd.P1100,cd.P1130, cd.P1200,cd.P1230, cd.P1300,cd.P1330,
cd.P1400,cd.P1430, cd.P1
500,
 11      cd.P1530, cd.P1600,cd.P1630, cd.P1700,cd.P1730, cd.P1800,cd.P1830,
cd.P1900,cd.P1930, cd.P2
000,
 12      cd.P2030, cd.P2100,cd.P2130, cd.P2200,cd.P2230, cd.P2300,cd.P2330,
cd.P2400
 13  from  ep_calendar_map cm,
 14          ep_period_map pm,
 15          ep_curvedetails cd
 16  where cm.EPCM_CALENDAR_ID = pm.EPPM_CALENDAR_ID
 17  and cm.EPCM_SEASON_ID = pm.EPPM_SEASON_ID
 18  and cm.EPCM_DAY_ID = pm.EPPM_DAY_ID
 19  and cm.EPCM_DATE = cd.curvedetails_date
 20  and cd.curve_id = 120403
 21  and cd.curvedetails_date between '01-Jul-2003' and '10-Jul-2003'
 22  )));

Execution Plan
----------------------------------------------------------
   0    SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=8168 Bytes=56
        19584)

   1   0  VIEW (Cost=4 Card=8168 Bytes=5619584)
   2   1    COLLECTION ITERATOR (PICKLER FETCH) OF 'EE_PERIODMAPCURV
        EREADINGS'

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

ep@EP>
ep@EP> timing show
timing for: new_RP_functions
Elapsed: 00:00:01.01
ep@EP> spool off

TKPROF: Release 9.2.0.3.0 - Production on Thu Oct 16 23:50:54 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Trace file: ep_ora_2880.trc
Sort options: fchela  exeela
********************************************************************************
count    = number of times OCI procedure was executed
cpu      = cpu time in seconds executing
elapsed  = elapsed time in seconds executing
disk     = number of physical reads of buffers from disk
query    = number of buffers gotten for consistent read
current  = number of buffers gotten in current mode (usually for update)
rows     = number of rows processed by the fetch or execute call
********************************************************************************

select * from TABLE(EE_PeriodMapCurveReadings(cursor(
select cm.EPCM_CALENDAR_ID, cm.EPCM_SEASON_ID, cm.EPCM_DAY_ID, CM.EPCM_DATE,
cd.curve_id,
       pm.P0030, pm.P0100,pm.P0130, pm.P0200,pm.P0230, pm.P0300,pm.P0330,
pm.P0400,pm.P0430, pm.P0500,
       pm.P0530, pm.P0600,pm.P0630, pm.P0700,pm.P0730, pm.P0800,pm.P0830,
pm.P0900,pm.P0930, pm.P1000,
       pm.P1030, pm.P1100,pm.P1130, pm.P1200,pm.P1230, pm.P1300,pm.P1330,
pm.P1400,pm.P1430, pm.P1500,
       pm.P1530, pm.P1600,pm.P1630, pm.P1700,pm.P1730, pm.P1800,pm.P1830,
pm.P1900,pm.P1930, pm.P2000,
       pm.P2030, pm.P2100,pm.P2130, pm.P2200,pm.P2230, pm.P2300,pm.P2330,
pm.P2400,
       cd.P0030, cd.P0100,cd.P0130, cd.P0200,cd.P0230, cd.P0300,cd.P0330,
cd.P0400,cd.P0430, cd.P0500,
       cd.P0530, cd.P0600,cd.P0630, cd.P0700,cd.P0730, cd.P0800,cd.P0830,
cd.P0900,cd.P0930, cd.P1000,
       cd.P1030, cd.P1100,cd.P1130, cd.P1200,cd.P1230, cd.P1300,cd.P1330,
cd.P1400,cd.P1430, cd.P1500,
       cd.P1530, cd.P1600,cd.P1630, cd.P1700,cd.P1730, cd.P1800,cd.P1830,
cd.P1900,cd.P1930, cd.P2000,
       cd.P2030, cd.P2100,cd.P2130, cd.P2200,cd.P2230, cd.P2300,cd.P2330,
cd.P2400
from  ep_calendar_map cm,
        ep_period_map pm,
        ep_curvedetails cd
where cm.EPCM_CALENDAR_ID = pm.EPPM_CALENDAR_ID
and cm.EPCM_SEASON_ID = pm.EPPM_SEASON_ID
and cm.EPCM_DAY_ID = pm.EPPM_DAY_ID
and cm.EPCM_DATE = cd.curvedetails_date
and cd.curve_id = 120403
and cd.curvedetails_date between '01-Jul-2003' and '10-Jul-2003'
)))

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.37       0.41          0        125          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch       42      1.12       1.24          0      14834          0       19834
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       46      1.50       1.65          0      14959          0       19834

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 57  (EP)

Rows     Row Source Operation
-------  ---------------------------------------------------
   9917  VIEW  (cr=7527 r=0 w=0 time=494932 us)
   9917   COLLECTION ITERATOR PICKLER FETCH (cr=7527 r=0 w=0 time=431058 us)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
   9917   VIEW
   9917    COLLECTION ITERATOR (PICKLER FETCH) OF
               'EE_PERIODMAPCURVEREADINGS'

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                      42        0.00          0.00
  SQL*Net message from client                    42        0.00          0.12
  SQL*Net more data to client                   118        0.00          0.06
********************************************************************************

SELECT "A4"."EPCM_CALENDAR_ID" "EPCM_CALENDAR_ID","A4"."EPCM_SEASON_ID"
  "EPCM_SEASON_ID","A4"."EPCM_DAY_ID" "EPCM_DAY_ID","A4"."EPCM_DATE"
  "EPCM_DATE","A2"."CURVE_ID" "CURVE_ID","A3"."P0030" "P0030","A3"."P0100"
  "P0100","A3"."P0130" "P0130","A3"."P0200" "P0200","A3"."P0230" "P0230",
  "A3"."P0300" "P0300","A3"."P0330" "P0330","A3"."P0400" "P0400","A3"."P0430"
  "P0430","A3"."P0500" "P0500","A3"."P0530" "P0530","A3"."P0600" "P0600",
  "A3"."P0630" "P0630","A3"."P0700" "P0700","A3"."P0730" "P0730","A3"."P0800"
  "P0800","A3"."P0830" "P0830","A3"."P0900" "P0900","A3"."P0930" "P0930",
  "A3"."P1000" "P1000","A3"."P1030" "P1030","A3"."P1100" "P1100","A3"."P1130"
  "P1130","A3"."P1200" "P1200","A3"."P1230" "P1230","A3"."P1300" "P1300",
  "A3"."P1330" "P1330","A3"."P1400" "P1400","A3"."P1430" "P1430","A3"."P1500"
  "P1500","A3"."P1530" "P1530","A3"."P1600" "P1600","A3"."P1630" "P1630",
  "A3"."P1700" "P1700","A3"."P1730" "P1730","A3"."P1800" "P1800","A3"."P1830"
  "P1830","A3"."P1900" "P1900","A3"."P1930" "P1930","A3"."P2000" "P2000",
  "A3"."P2030" "P2030","A3"."P2100" "P2100","A3"."P2130" "P2130","A3"."P2200"
  "P2200","A3"."P2230" "P2230","A3"."P2300" "P2300","A3"."P2330" "P2330",
  "A3"."P2400" "P2400","A2"."P0030" "P0030","A2"."P0100" "P0100","A2"."P0130"
  "P0130","A2"."P0200" "P0200","A2"."P0230" "P0230","A2"."P0300" "P0300",
  "A2"."P0330" "P0330","A2"."P0400" "P0400","A2"."P0430" "P0430","A2"."P0500"
  "P0500","A2"."P0530" "P0530","A2"."P0600" "P0600","A2"."P0630" "P0630",
  "A2"."P0700" "P0700","A2"."P0730" "P0730","A2"."P0800" "P0800","A2"."P0830"
  "P0830","A2"."P0900" "P0900","A2"."P0930" "P0930","A2"."P1000" "P1000",
  "A2"."P1030" "P1030","A2"."P1100" "P1100","A2"."P1130" "P1130","A2"."P1200"
  "P1200","A2"."P1230" "P1230","A2"."P1300" "P1300","A2"."P1330" "P1330",
  "A2"."P1400" "P1400","A2"."P1430" "P1430","A2"."P1500" "P1500","A2"."P1530"
  "P1530","A2"."P1600" "P1600","A2"."P1630" "P1630","A2"."P1700" "P1700",
  "A2"."P1730" "P1730","A2"."P1800" "P1800","A2"."P1830" "P1830","A2"."P1900"
  "P1900","A2"."P1930" "P1930","A2"."P2000" "P2000","A2"."P2030" "P2030",
  "A2"."P2100" "P2100","A2"."P2130" "P2130","A2"."P2200" "P2200","A2"."P2230"
  "P2230","A2"."P2300" "P2300","A2"."P2330" "P2330","A2"."P2400" "P2400"
FROM
 "EP_CALENDAR_MAP" "A4","EP_PERIOD_MAP" "A3","EP_CURVEDETAILS" "A2" WHERE
  "A4"."EPCM_CALENDAR_ID"="A3"."EPPM_CALENDAR_ID" AND "A4"."EPCM_SEASON_ID"=
  "A3"."EPPM_SEASON_ID" AND "A4"."EPCM_DAY_ID"="A3"."EPPM_DAY_ID" AND
  "A4"."EPCM_DATE"="A2"."CURVEDETAILS_DATE" AND "A2"."CURVE_ID"=120403 AND
  "A2"."CURVEDETAILS_DATE">='01-Jul-2003' AND "A2"."CURVEDETAILS_DATE"<=
  '10-Jul-2003'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        6      0.31       0.29          0      14594          0         422
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       10      0.31       0.29          0      14594          0         422

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 57  (EP)   (recursive depth: 2)

Rows     Row Source Operation
-------  ---------------------------------------------------
    211  NESTED LOOPS  (cr=7297 r=0 w=0 time=133946 us)
   7250   MERGE JOIN CARTESIAN (cr=43 r=0 w=0 time=54967 us)
     10    TABLE ACCESS BY INDEX ROWID EP_CURVEDETAILS (cr=12 r=0 w=0 time=1660
us)
    365     INDEX RANGE SCAN EP_CUREVEDETAILS_ID_INDEX (cr=6 r=0 w=0 time=700
us)(object id 37565)
   7250    BUFFER SORT (cr=31 r=0 w=0 time=31077 us)
    725     TABLE ACCESS FULL EP_PERIOD_MAP (cr=31 r=0 w=0 time=1616 us)
    211   INDEX UNIQUE SCAN EP_CALENDAR_MAP_IDX (cr=7254 r=0 w=0 time=41198
us)(object id 38588)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
    211   NESTED LOOPS
   7250    MERGE JOIN (CARTESIAN)
     10     TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF
                'EP_CURVEDETAILS'
    365      INDEX   GOAL: ANALYZED (RANGE SCAN) OF
                 'EP_CUREVEDETAILS_ID_INDEX' (NON-UNIQUE)
   7250     BUFFER (SORT)
    725      TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'EP_PERIOD_MAP'
    211    INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF 'EP_CALENDAR_MAP_IDX'
               (UNIQUE)

********************************************************************************

My first question is... did we approach this implementation of the pipe function
appropriately?

We wonder because... it was NOT as fast as the Decode or Case approach!!!!

Here is the structure and benchmark results:

-- --------------------------------------------------------------

CREATE OR REPLACE FORCE VIEW EP.EP_V_PERIODMAP_CURVE_READINGS
(EPCM_CALENDAR_ID, EPCM_SEASON_ID, EPCM_DAY_ID, EPCM_DATE, CURVE_ID,
 P_NO, PERIOD_ID, P_VALUE)
AS
select cm.EPCM_CALENDAR_ID, cm.EPCM_SEASON_ID, cm.EPCM_DAY_ID, cm.EPCM_DATE,
EP.CURVE_ID,
       decode(x, 1, 'P0030', 2, 'P0100', 3, 'P0130', 4, 'P0200', 5, 'P0230', 6,
'P0300', 7, 'P0330', 8, 'P0400',
                 9, 'P0430', 10, 'P0500', 11, 'P0530', 12, 'P0600', 13, 'P0630',
14, 'P0700', 15, 'P0730', 16, 'P0800',
              17, 'P0830', 18, 'P0900', 19, 'P0930', 20, 'P1000', 21, 'P1030',
22, 'P1100', 23, 'P1130', 24, 'P1200',
              25, 'P1230', 26, 'P1300', 27, 'P1330', 28, 'P1400', 29, 'P1430',
30, 'P1500', 31, 'P1530', 32, 'P1600',
              33, 'P1630', 34, 'P1700', 35, 'P1730', 36, 'P1800', 37, 'P1830',
38, 'P1900', 39, 'P1930', 40, 'P2000',
              41, 'P2030', 42, 'P2100', 43, 'P2130', 44, 'P2200', 45, 'P2230',
46, 'P2300', 47, 'P2330', 48, 'P2400') P_no,

      decode(x, 1, pm.P0030, 2, pm.P0100, 3, pm.P0130, 4, pm.P0200, 5, pm.P0230,
6, pm.P0300, 7, pm.P0330, 8, pm.P0400,
                 9, pm.P0430, 10, pm.P0500, 11, pm.P0530, 12, pm.P0600, 13,
pm.P0630, 14, pm.P0700, 15, pm.P0730, 16, pm.P0800,
              17, pm.P0830, 18, pm.P0900, 19, pm.P0930, 20, pm.P1000, 21,
pm.P1030, 22, pm.P1100, 23, pm.P1130, 24, pm.P1200,
              25, pm.P1230, 26, pm.P1300, 27, pm.P1330, 28, pm.P1400, 29,
pm.P1430, 30, pm.P1500, 31, pm.P1530, 32, pm.P1600,
              33, pm.P1630, 34, pm.P1700, 35, pm.P1730, 36, pm.P1800, 37,
pm.P1830, 38, pm.P1900, 39, pm.P1930, 40, pm.P2000,
              41, pm.P2030, 42, pm.P2100, 43, pm.P2130, 44, pm.P2200, 45,
pm.P2230, 46, pm.P2300, 47, pm.P2330, 48, pm.P2400) Period_ID,

      decode(x, 1, ep.P0030, 2, EP.P0100, 3, EP.P0130, 4, EP.P0200, 5, EP.P0230,
6, EP.P0300, 7, EP.P0330, 8, EP.P0400,
                 9, EP.P0430, 10, EP.P0500, 11, EP.P0530, 12, EP.P0600, 13,
EP.P0630, 14, EP.P0700, 15, EP.P0730, 16, EP.P0800,
              17, EP.P0830, 18, EP.P0900, 19, EP.P0930, 20, EP.P1000, 21,
EP.P1030, 22, EP.P1100, 23, EP.P1130, 24, EP.P1200,
              25, EP.P1230, 26, EP.P1300, 27, EP.P1330, 28, EP.P1400, 29,
EP.P1430, 30, EP.P1500, 31, EP.P1530, 32, EP.P1600,
              33, EP.P1630, 34, EP.P1700, 35, EP.P1730, 36, EP.P1800, 37,
EP.P1830, 38, EP.P1900, 39, EP.P1930, 40, EP.P2000,
              41, EP.P2030, 42, EP.P2100, 43, EP.P2130, 44, EP.P2200, 45,
EP.P2230, 46, EP.P2300, 47, EP.P2330, 48, EP.P2400) P_value

       from ( select x from tall_dual where x <= 48) b,
           ep_calendar_map cm,
           ep_period_map pm,
           ep_curvedetails ep
    where  cm.EPCM_CALENDAR_ID = pm.EPPM_CALENDAR_ID
    and    cm.EPCM_SEASON_ID   = pm.EPPM_SEASON_ID
    and    cm.EPCM_DAY_ID      = pm.EPPM_DAY_ID
    and    cm.EPCM_DATE        = ep.curvedetails_date;

timing for: Benchmark
Elapsed: 00:00:00.00
ep@EP>
ep@EP> select * from EP_V_PERIODMAP_CURVE_READINGS where curve_id = 120403 and
EPCM_DATE between '01-Jul-2003' and '10-Jul-2003';

10128 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=12 Card=315 Bytes=83
          790)

   1    0   MERGE JOIN (CARTESIAN) (Cost=12 Card=315 Bytes=83790)
   2    1     NESTED LOOPS (Cost=11 Card=1 Bytes=253)
   3    2       MERGE JOIN (CARTESIAN) (Cost=11 Card=594 Bytes=140778)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'EP_CURVEDETAILS' (
          Cost=9 Card=1 Bytes=41)

   5    4           INDEX (RANGE SCAN) OF 'EP_CUREVEDETAILS_ID_INDEX'
          (NON-UNIQUE) (Cost=3 Card=286)

   6    3         BUFFER (SORT) (Cost=2 Card=725 Bytes=142100)
   7    6           TABLE ACCESS (FULL) OF 'EP_PERIOD_MAP' (Cost=2 Car
          d=725 Bytes=142100)

   8    2       INDEX (UNIQUE SCAN) OF 'EP_CALENDAR_MAP_IDX' (UNIQUE)
   9    1     BUFFER (SORT) (Cost=12 Card=408 Bytes=5304)
  10    9       INDEX (RANGE SCAN) OF 'SYS_IOT_TOP_38649' (UNIQUE) (Co
          st=1 Card=408 Bytes=5304)

Statistics
----------------------------------------------------------
       4073  recursive calls
          0  db block gets
       8076  consistent gets
          0  physical reads
          0  redo size
     129722  bytes sent via SQL*Net to client
        719  bytes received via SQL*Net from client
         22  SQL*Net roundtrips to/from client
         44  sorts (memory)
          0  sorts (disk)
      10128  rows processed

ep@EP>
ep@EP> set echo off

10128 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=12 Card=315 Bytes=83
          790)

   1    0   MERGE JOIN (CARTESIAN) (Cost=12 Card=315 Bytes=83790)
   2    1     NESTED LOOPS (Cost=11 Card=1 Bytes=253)
   3    2       MERGE JOIN (CARTESIAN) (Cost=11 Card=594 Bytes=140778)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'EP_CURVEDETAILS' (
          Cost=9 Card=1 Bytes=41)

   5    4           INDEX (RANGE SCAN) OF 'EP_CUREVEDETAILS_ID_INDEX'
          (NON-UNIQUE) (Cost=3 Card=286)

   6    3         BUFFER (SORT) (Cost=2 Card=725 Bytes=142100)
   7    6           TABLE ACCESS (FULL) OF 'EP_PERIOD_MAP' (Cost=2 Car
          d=725 Bytes=142100)

   8    2       INDEX (UNIQUE SCAN) OF 'EP_CALENDAR_MAP_IDX' (UNIQUE)
   9    1     BUFFER (SORT) (Cost=12 Card=408 Bytes=5304)
  10    9       INDEX (RANGE SCAN) OF 'SYS_IOT_TOP_38649' (UNIQUE) (Co
          st=1 Card=408 Bytes=5304)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       7333  consistent gets
          0  physical reads
          0  redo size
     129722  bytes sent via SQL*Net to client
        719  bytes received via SQL*Net from client
         22  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      10128  rows processed

timing for: Benchmark
Elapsed: 00:00:01.05

TKPROF: Release 9.2.0.3.0 - Production on Fri Oct 17 00:18:20 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Trace file: ep_ora_1272.trc
Sort options: fchela  exeela
********************************************************************************
count    = number of times OCI procedure was executed
cpu      = cpu time in seconds executing
elapsed  = elapsed time in seconds executing
disk     = number of physical reads of buffers from disk
query    = number of buffers gotten for consistent read
current  = number of buffers gotten in current mode (usually for update)
rows     = number of rows processed by the fetch or execute call
********************************************************************************

select * from EP_V_PERIODMAP_CURVE_READINGS where curve_id = 120403
and EPCM_DATE between '01-Jul-2003' and '10-Jul-2003'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.25       0.30          0        743          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch       44      0.59       0.55          0      14666          0       20256
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       48      0.84       0.85          0      15409          0       20256

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 57  (EP)

Rows     Row Source Operation
-------  ---------------------------------------------------
  10128  MERGE JOIN CARTESIAN (cr=7333 r=0 w=0 time=175006 us)
    211   NESTED LOOPS  (cr=7332 r=0 w=0 time=119713 us)
   7250    MERGE JOIN CARTESIAN (cr=59 r=0 w=0 time=54276 us)
     10     TABLE ACCESS BY INDEX ROWID EP_CURVEDETAILS (cr=28 r=0 w=0 time=1592
us)
    365      INDEX RANGE SCAN EP_CUREVEDETAILS_ID_INDEX (cr=14 r=0 w=0 time=783
us)(object id 37565)
   7250     BUFFER SORT (cr=31 r=0 w=0 time=30287 us)
    725      TABLE ACCESS FULL EP_PERIOD_MAP (cr=31 r=0 w=0 time=1565 us)
    211    INDEX UNIQUE SCAN EP_CALENDAR_MAP_IDX (cr=7273 r=0 w=0 time=34521
us)(object id 38588)
 **** MESSAGE TRUNCATED ****

          Followup:

I don't see anything obviously wrong in your implementation -- but that is why i
gave you two methods -- benchmark them and see which works best in your case.

Most of the times, if you can do it in straight sql, thats best.  there are
cases where plsql can edge it out, but here -- the massive assignments probably
killed us.

      Rating: 5
Pipe function
      vs Decode (cont'd)  October 16, 2003
Reviewer:
      Alex  from Sydney, Oz
Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
    211   NESTED LOOPS
   7250    MERGE JOIN (CARTESIAN)
     10     TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF
                'EP_CURVEDETAILS'
    365      INDEX   GOAL: ANALYZED (RANGE SCAN) OF
                 'EP_CUREVEDETAILS_ID_INDEX' (NON-UNIQUE)
   7250     BUFFER (SORT)
    725      TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'EP_PERIOD_MAP'
    211    INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF 'EP_CALENDAR_MAP_IDX'
               (UNIQUE)

********************************************************************************

My first question is... did we approach this implementation of the pipe function
appropriately?

We wonder because... it was NOT as fast as the Decode or Case approach!!!!

Here is the structure and benchmark results:

-- --------------------------------------------------------------

CREATE OR REPLACE FORCE VIEW EP.EP_V_PERIODMAP_CURVE_READINGS
(EPCM_CALENDAR_ID, EPCM_SEASON_ID, EPCM_DAY_ID, EPCM_DATE, CURVE_ID,
 P_NO, PERIOD_ID, P_VALUE)
AS
select cm.EPCM_CALENDAR_ID, cm.EPCM_SEASON_ID, cm.EPCM_DAY_ID, cm.EPCM_DATE,
EP.CURVE_ID,
       decode(x, 1, 'P0030', 2, 'P0100', 3, 'P0130', 4, 'P0200', 5, 'P0230', 6,
'P0300', 7, 'P0330', 8, 'P0400',
                 9, 'P0430', 10, 'P0500', 11, 'P0530', 12, 'P0600', 13, 'P0630',
14, 'P0700', 15, 'P0730', 16, 'P0800',
              17, 'P0830', 18, 'P0900', 19, 'P0930', 20, 'P1000', 21, 'P1030',
22, 'P1100', 23, 'P1130', 24, 'P1200',
              25, 'P1230', 26, 'P1300', 27, 'P1330', 28, 'P1400', 29, 'P1430',
30, 'P1500', 31, 'P1530', 32, 'P1600',
              33, 'P1630', 34, 'P1700', 35, 'P1730', 36, 'P1800', 37, 'P1830',
38, 'P1900', 39, 'P1930', 40, 'P2000',
              41, 'P2030', 42, 'P2100', 43, 'P2130', 44, 'P2200', 45, 'P2230',
46, 'P2300', 47, 'P2330', 48, 'P2400') P_no,

      decode(x, 1, pm.P0030, 2, pm.P0100, 3, pm.P0130, 4, pm.P0200, 5, pm.P0230,
6, pm.P0300, 7, pm.P0330, 8, pm.P0400,
                 9, pm.P0430, 10, pm.P0500, 11, pm.P0530, 12, pm.P0600, 13,
pm.P0630, 14, pm.P0700, 15, pm.P0730, 16, pm.P0800,
              17, pm.P0830, 18, pm.P0900, 19, pm.P0930, 20, pm.P1000, 21,
pm.P1030, 22, pm.P1100, 23, pm.P1130, 24, pm.P1200,
              25, pm.P1230, 26, pm.P1300, 27, pm.P1330, 28, pm.P1400, 29,
pm.P1430, 30, pm.P1500, 31, pm.P1530, 32, pm.P1600,
              33, pm.P1630, 34, pm.P1700, 35, pm.P1730, 36, pm.P1800, 37,
pm.P1830, 38, pm.P1900, 39, pm.P1930, 40, pm.P2000,
              41, pm.P2030, 42, pm.P2100, 43, pm.P2130, 44, pm.P2200, 45,
pm.P2230, 46, pm.P2300, 47, pm.P2330, 48, pm.P2400) Period_ID,

      decode(x, 1, ep.P0030, 2, EP.P0100, 3, EP.P0130, 4, EP.P0200, 5, EP.P0230,
6, EP.P0300, 7, EP.P0330, 8, EP.P0400,
                 9, EP.P0430, 10, EP.P0500, 11, EP.P0530, 12, EP.P0600, 13,
EP.P0630, 14, EP.P0700, 15, EP.P0730, 16, EP.P0800,
              17, EP.P0830, 18, EP.P0900, 19, EP.P0930, 20, EP.P1000, 21,
EP.P1030, 22, EP.P1100, 23, EP.P1130, 24, EP.P1200,
              25, EP.P1230, 26, EP.P1300, 27, EP.P1330, 28, EP.P1400, 29,
EP.P1430, 30, EP.P1500, 31, EP.P1530, 32, EP.P1600,
              33, EP.P1630, 34, EP.P1700, 35, EP.P1730, 36, EP.P1800, 37,
EP.P1830, 38, EP.P1900, 39, EP.P1930, 40, EP.P2000,
              41, EP.P2030, 42, EP.P2100, 43, EP.P2130, 44, EP.P2200, 45,
EP.P2230, 46, EP.P2300, 47, EP.P2330, 48, EP.P2400) P_value

       from ( select x from tall_dual where x <= 48) b,
           ep_calendar_map cm,
           ep_period_map pm,
           ep_curvedetails ep
    where  cm.EPCM_CALENDAR_ID = pm.EPPM_CALENDAR_ID
    and    cm.EPCM_SEASON_ID   = pm.EPPM_SEASON_ID
    and    cm.EPCM_DAY_ID      = pm.EPPM_DAY_ID
    and    cm.EPCM_DATE        = ep.curvedetails_date;

timing for: Benchmark
Elapsed: 00:00:00.00
ep@EP>
ep@EP> select * from EP_V_PERIODMAP_CURVE_READINGS where curve_id = 120403 and
EPCM_DATE between '01-Jul-2003' and '10-Jul-2003';

10128 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=12 Card=315 Bytes=83
          790)

   1    0   MERGE JOIN (CARTESIAN) (Cost=12 Card=315 Bytes=83790)
   2    1     NESTED LOOPS (Cost=11 Card=1 Bytes=253)
   3    2       MERGE JOIN (CARTESIAN) (Cost=11 Card=594 Bytes=140778)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'EP_CURVEDETAILS' (
          Cost=9 Card=1 Bytes=41)

   5    4           INDEX (RANGE SCAN) OF 'EP_CUREVEDETAILS_ID_INDEX'
          (NON-UNIQUE) (Cost=3 Card=286)

   6    3         BUFFER (SORT) (Cost=2 Card=725 Bytes=142100)
   7    6           TABLE ACCESS (FULL) OF 'EP_PERIOD_MAP' (Cost=2 Car
          d=725 Bytes=142100)

   8    2       INDEX (UNIQUE SCAN) OF 'EP_CALENDAR_MAP_IDX' (UNIQUE)
   9    1     BUFFER (SORT) (Cost=12 Card=408 Bytes=5304)
  10    9       INDEX (RANGE SCAN) OF 'SYS_IOT_TOP_38649' (UNIQUE) (Co
          st=1 Card=408 Bytes=5304)

Statistics
----------------------------------------------------------
       4073  recursive calls
          0  db block gets
       8076  consistent gets
          0  physical reads
          0  redo size
     129722  bytes sent via SQL*Net to client
        719  bytes received via SQL*Net from client
         22  SQL*Net roundtrips to/from client
         44  sorts (memory)
          0  sorts (disk)
      10128  rows processed

ep@EP>
ep@EP> set echo off

10128 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=12 Card=315 Bytes=83
          790)

   1    0   MERGE JOIN (CARTESIAN) (Cost=12 Card=315 Bytes=83790)
   2    1     NESTED LOOPS (Cost=11 Card=1 Bytes=253)
   3    2       MERGE JOIN (CARTESIAN) (Cost=11 Card=594 Bytes=140778)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'EP_CURVEDETAILS' (
          Cost=9 Card=1 Bytes=41)

   5    4           INDEX (RANGE SCAN) OF 'EP_CUREVEDETAILS_ID_INDEX'
          (NON-UNIQUE) (Cost=3 Card=286)

   6    3         BUFFER (SORT) (Cost=2 Card=725 Bytes=142100)
   7    6           TABLE ACCESS (FULL) OF 'EP_PERIOD_MAP' (Cost=2 Car
          d=725 Bytes=142100)

   8    2       INDEX (UNIQUE SCAN) OF 'EP_CALENDAR_MAP_IDX' (UNIQUE)
   9    1     BUFFER (SORT) (Cost=12 Card=408 Bytes=5304)
  10    9       INDEX (RANGE SCAN) OF 'SYS_IOT_TOP_38649' (UNIQUE) (Co
          st=1 Card=408 Bytes=5304)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       7333  consistent gets
          0  physical reads
          0  redo size
     129722  bytes sent via SQL*Net to client
        719  bytes received via SQL*Net from client
         22  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      10128  rows processed

timing for: Benchmark
Elapsed: 00:00:01.05

TKPROF: Release 9.2.0.3.0 - Production on Fri Oct 17 00:18:20 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Trace file: ep_ora_1272.trc
Sort options: fchela  exeela
********************************************************************************
count    = number of times OCI procedure was executed
cpu      = cpu time in seconds executing
elapsed  = elapsed time in seconds executing
disk     = number of physical reads of buffers from disk
query    = number of buffers gotten for consistent read
current  = number of buffers gotten in current mode (usually for update)
rows     = number of rows processed by the fetch or execute call
********************************************************************************

select * from EP_V_PERIODMAP_CURVE_READINGS where curve_id = 120403
and EPCM_DATE between '01-Jul-2003' and '10-Jul-2003'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.25       0.30          0        743          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch       44      0.59       0.55          0      14666          0       20256
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       48      0.84       0.85          0      15409          0       20256

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 57  (EP)

Rows     Row Source Operation
-------  ---------------------------------------------------
  10128  MERGE JOIN CARTESIAN (cr=7333 r=0 w=0 time=175006 us)
    211   NESTED LOOPS  (cr=7332 r=0 w=0 time=119713 us)
   7250    MERGE JOIN CARTESIAN (cr=59 r=0 w=0 time=54276 us)
     10     TABLE ACCESS BY INDEX ROWID EP_CURVEDETAILS (cr=28 r=0 w=0 time=1592
us)
    365      INDEX RANGE SCAN EP_CUREVEDETAILS_ID_INDEX (cr=14 r=0 w=0 time=783
us)(object id 37565)
   7250     BUFFER SORT (cr=31 r=0 w=0 time=30287 us)
    725      TABLE ACCESS FULL EP_PERIOD_MAP (cr=31 r=0 w=0 time=1565 us)
    211    INDEX UNIQUE SCAN EP_CALENDAR_MAP_IDX (cr=7273 r=0 w=0 time=34521
us)(object id 38588)
  10128   BUFFER SORT (cr=1 r=0 w=0 time=17773 us)
     48    INDEX RANGE SCAN SYS_IOT_TOP_38649 (cr=1 r=0 w=0 time=91 us)(object
id 38650)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
  10128   MERGE JOIN (CARTESIAN)
    211    NESTED LOOPS
   7250     MERGE JOIN (CARTESIAN)
     10      TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF
                 'EP_CURVEDETAILS'
    365       INDEX   GOAL: ANALYZED (RANGE SCAN) OF
                  'EP_CUREVEDETAILS_ID_INDEX' (NON-UNIQUE)
   7250      BUFFER (SORT)
    725       TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'EP_PERIOD_MAP'
    211     INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF
                'EP_CALENDAR_MAP_IDX' (UNIQUE)
  10128    BUFFER (SORT)
     48     INDEX (RANGE SCAN) OF 'SYS_IOT_TOP_38649' (UNIQUE)

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                      44        0.00          0.00
  SQL*Net message from client                    44        0.00          0.11
  SQL*Net more data to client                   120        0.00          0.05
********************************************************************************

-- ----------------------------------------------------------------------

So Decode was a bit slower.. but not by much!  And in other tests it was
actually faster - when returning larger date ranges!

Ultimately we have achieved outstanding results but I was hoping you might
provide some more effective analysis of our pipe function...

Thank you so very much!

PS.. any plans to support HTML on your site.. so we can provide some colour
formatting to make posts easier to read?

October 16, 2003 Rui -- Thanks for the question regarding "Send
      data to MS Office 2000 (Excel)", version Developer 6i

      You Asked
Hi Tom

My question is how export data to MS Office 2000 (Excel), using
Forms 6i.
Must be a way to export data to Excel, without impact of the
version of Excel. This means that works in Office 95, 97 or 2000
or other.
I use a excel.pll provided by Oracle (included in examples of
Developer) that don´t work on MS Office 2000 and Oracle does not
provided support to this excel.pll.
Now I installed the Oracle Discoverer and try to send data to my
Excel and ... "voila" ... WORKS !
Why ?

In the way, if you have any help for Word too, I aprecciate.

Best regards
Rui Brito

      and we said...
You would think there would be a way -- If only MS didn't keep
changing their standards with every release.  It sounds like it
would be simple but after years of "dde is the standard", "odbc
is the standard", "ole is the standard", "com is the standard",
"no wait activex is the standard", "no wait dcom is really the
standard", "no wait .Net will be the standard".... I think you
see my point.

We can get the data to excel using OLE automation.
Unfortunately, the exact way to do it will change from release
to release.  In discoverer, a tool built expressing for
reporting, we have the exact code you need to do this (as long
as you have the right version of discoverer.  it didn't work "as
is").  In forms, we have the technology -- but forms is a cross
platform application development tool.  The excel.pll is an
example of using the technology.

What you will need to do (with MS's help) is to figure out what
magic set of OLE calls you need to make to excel in Office 2000.
 See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:319216860351
for an example of using OLE automation.

Also, this support note *MIGHT* be of help.  I don't know what
diffs between 97 and 2000 there might be:

Article-ID:         <Note:2064196.102>
Circulation:        PUBLISHED (EXTERNAL)
Folder:             client.Dev2000.Forms.V4
Topic:              Draft Articles
Title:              MICROSOFT CHANGED OLE CALLING INTERFACES FOR
OFFICE97 --
                    EDIT SPREADSHEET PACKAGE DEFINED IN
EXCEL.PLL
Document-Type:      SOLUTION
Impact:             LOW
Skill-Level:        CASUAL
Server-Version:     VXX
Updated-Date:       04-AUG-1999 21:34:32
References:         <BUG:483090>
Shared-Refs:
     <Problem:1010926.102> OLE AUTOMATION DEMO FOR
EMBEDDED/LINKED EXCEL97 DOCUMENTS DOES NOT WORK
Attachments:        NONE
Content-Type:       TEXT/PLAIN
Products:           45/SF40 (04.05.XX);
Platforms:          912;

Solution Description:
=====================

Change the package body of the SPREADSHEET package defined in
EXCEL.PLL,
replacing some of the OLE calls with the object model.

1. Open \ORANT\FORMS45\PLSQLLIB\EXCEL.PLL in Forms Designer.

2. Replace the SPREADSHEET package body code with the following
code:

-----------------------------------------------------------------
--------

PACKAGE BODY spreadsheet IS
    workbook   OLE2.OBJ_TYPE;        -- CHANGE
    worksheets OLE2.OBJ_TYPE;        -- CHANGE
    obj_hnd    OLE2.OBJ_TYPE;         /* store the object handle
*/

FUNCTION NumToRange(r INTEGER, c INTEGER) RETURN VARCHAR2 IS
    rw     VARCHAR2(5);
    nm     INTEGER;
BEGIN
    nm := ASCII('A') - 1;
    rw := CHR(nm + c) || TO_CHAR(r);
    RETURN UPPER(rw);
END;

FUNCTION get_object_handle RETURN OLE2.OBJ_TYPE IS
    args   OLE2.LIST_TYPE; -- CHANGE
BEGIN
    /* If the server is not active, activate the server
    ** and get the object handle.
    */
--  IF NOT FORMS_OLE.SERVER_ACTIVE('spreadsheet') THEN
    FORMS_OLE.ACTIVATE_SERVER('spreadsheet');
    workbook := FORMS_OLE.GET_INTERFACE_POINTER('spreadsheet');
-- CHANGE
    worksheets := OLE2.GET_OBJ_PROPERTY(workbook, 'Worksheets');
-- CHANGE
    args := OLE2.CREATE_ARGLIST;
-- CHANGE
    OLE2.ADD_ARG(args, 1);
-- CHANGE
    obj_hnd := OLE2.GET_OBJ_PROPERTY(worksheets, 'Item', args);
-- CHANGE
--  END IF;
    RETURN obj_hnd;
END;

PROCEDURE setcell(trow NUMBER, col NUMBER, val NUMBER) IS
    d      OLE2.OBJ_TYPE;
    c      OLE2.OBJ_TYPE;
    n      NUMBER;
    lst    OLE2.LIST_TYPE;
BEGIN
    /* Activate the server and get the object handle
    ** to the spreadsheet.
    */
    d := GET_OBJECT_HANDLE;

    /* Create an argument list and insert the specified
    ** row and column into the argument list.
    */
    lst := OLE2.CREATE_ARGLIST;
    OLE2.ADD_ARG(lst, trow);
    OLE2.ADD_ARG(lst, col);

    /* Call the Cells method to get a handle to the
    ** specified cell.
    */
--  c := OLE2.INVOKE_OBJ(d, 'Cells', lst);
    c := OLE2.GET_OBJ_PROPERTY(d, 'Cells', lst);   -- CHANGE

    /* Set the value of that cell. */
    OLE2.SET_PROPERTY(c, 'Value', val);

    /* Destroy the argument list and the cell object handle. */
    OLE2.DESTROY_ARGLIST(lst);
    OLE2.RELEASE_OBJ(c);
--  OLE2.RELEASE_OBJ(d);   /* this line causes dump */

EXCEPTION
    WHEN OTHERS THEN
        FOR i IN 1..TOOL_ERR.NERRORS LOOP
            MESSAGE(TOOL_ERR.MESSAGE);
            TOOL_ERR.POP;
        END LOOP;
END;

FUNCTION getcell(trow NUMBER, col NUMBER) RETURN NUMBER IS
    c      OLE2.OBJ_TYPE;
    d      OLE2.OBJ_TYPE;
    n      NUMBER;
    lst    OLE2.LIST_TYPE;
BEGIN
    /* Activate the server and get the object handle
    ** to the spreadsheet.
    */
    d := GET_OBJECT_HANDLE;

    /* Create an argument list and insert the specified
    ** row and column into the argument list.
    */
    lst := OLE2.CREATE_ARGLIST;
--  OLE2.ADD_ARG(lst, trow);
--  OLE2.ADD_ARG(lst, col);
    OLE2.ADD_ARG(lst, NumToRange(trow, col));

    /* Call the Cells method to get the value in the
    ** specified cell.
    */
--  c := OLE2.INVOKE_OBJ(d, 'Cells', lst);
--  c := OLE2.INVOKE_OBJ(d, 'Range', lst);
    c := OLE2.GET_OBJ_PROPERTY(d, 'Range', lst); -- CHANGE
    n := OLE2.GET_NUM_PROPERTY(c, 'Value');

    /* Destroy the argument list. */
    OLE2.DESTROY_ARGLIST(lst);
    OLE2.RELEASE_OBJ(c);
--  OLE2.RELEASE_OBJ(d);    /* this line causes dump */

    RETURN n;

EXCEPTION
    WHEN OTHERS THEN
        FOR i IN 1..TOOL_ERR.NERRORS LOOP
            MESSAGE(TOOL_ERR.MESSAGE);
            TOOL_ERR.POP;
        END LOOP;
END;

END;

-----------------------------------------------------------------
--------

3. Recompile the package spec and the package body.

4. Save EXCEL.PLL

5. Delete/Rename the previous version of EXCEL.PLX

6. Open the EXCEL.FMB Sample application in the Forms Designer
   from the following directory:
       \ORANT\FORMS45\DEMOS\OLEEXCEL\EXCEL.FMB

7. Regenerate the form.

Solution Explanation:
=====================

Microsoft has changed some of the OLE calling interfaces for the
Office97
products.  Thus, some of your existing OLE calls need to be
changed to
comply with the new object model.  The changes to the package
body have
been commented with -- CHANGE.

Solution References:
====================

Microsoft Excel Visual Basic Reference -> Microsoft Excel
Objects
  This online documentation which comes with Excel97 provides
  the new object model.

Review &
      Followup

      Rating: 3
My
      opinion  August 23, 2001
Reviewer:
      Vijayaraghavan.T  from INDIA
Tom Guruji,
I have a suggestion. Suppose i wite a query like
  SPOOL xyx.txt
  SELECT ENAME || '|' || SALARY FROM EMP
  SPOOL OFF
if i open the file xyx.txt in Excel it opens a wizard that help to delimit and
populate fields automatically. This is independent of Excel Version.

Do u have any suggestion? One thing is the column must not have | (pipe char)
and it can not include columns  long raw or raw or LOB or Varchar2 with carriage
return.

I like a better approach on this. We have DTS in SQL Server and bcp.
Similar Export and import is there in DB2 also. But why we do not have such
option in Oracle.
Thanks and Rgds
Vijayaraghavan

      Rating: 3
MS Excel
      <> Oracle bridge  July 01, 2002
Reviewer:
      A reader
Try this link:

http://www.oraxcel.com/projects/sqlxl/sqlxl2.html
SQL*XL 2 provides an easy to use bridge between Excel and Oracle databases.
SQL*XL adds a new menu item to Excel so Oracle access appears to be native.
Through comprehensive dialogs you have interaction with the database.

From Oracle...
SQL is taken from a dialog box, spreadsheet or file. The data is put in the
spreadsheet where you want it.

To Oracle...
A full spreadsheet full of data can be inserted with one click. When constraints
are violated you are prompted with the full Oracle error message. Transaction
control lets you rollback or commit the changes.

Update Oracle...
Once data is fetched into Excel, SQL*XL allows you to edit it in the
spreadsheet. When you are done, the changes can be updated in Oracle with one
mouse click. Optionally SQL*XL will ask you to confirm each individual change.

SQL*XL 2

- Connects to Oracle databases only

- Stable and proven release

- During 1999-2002 downloaded 500-800 times per week

      Rating: 3
Write decimal
      to excel  July 11, 2002
Reviewer:  Ken
       from Ontario, Canada
Hi,

I can write to an excel file successfully now, thank to your examples.  However,
I have another small problem is that when I try to write a number with 2 decimal
places to the cell, it ignores the decimal point, so the number n = n * 100
after writing to excel.  What can I do?

By the way, the cell has a format like this: #,##0.0%_);[Red](#,##0.0%)

          Followup:

sorry, don't know.  I neither use excel personally, nor have I ever used com.

      Rating: 5
Export data
      from Dev. 2000 to Excel  August 20, 2002
Reviewer:
      Alejandro Berrones Cruz.  from Hidalgo, Mexico
Thank you!

It is very usefull... I test it with Excel 2000 and it works...

Zorro_Gris

      Rating: 5
How to send a
      form(6i) runtime values to Excel sheet by using DDE
      calls  October 16, 2003
Reviewer:  suresh
       from India
   How to Send a form runtime values to Excel-sheet by
 using DDE calls ?

  I am creating a simple form like dept,emp with relation,
 at runtime how to capture(get) the runtime form values
 Dept(deptno,dname,loc) , Emp(empno,ename,job,....) and these values send to
Excel-sheet in proper format How to do this ?

   pls reply immd..

   Thanks
     suresh.

          Followup:

try otn.oracle.com -> discussion forums under "developer"

I don't run windows on any computers
Haven't touched forms since 1995
Haven't done DDE since about 1992

October 16, 2003 Neil -- Thanks for the question regarding
      "SQL*Loader into multiple tables depending on column being NULL", version
      8.1.7.3.0

      You Asked
Hi Tom,

I'm trying to use SQL*Loader to load data into two different tables depending on
whether the value in a particular field is NULL or not. My *test* data file
(NullTest.dat) looks like this:

"Oak","Tree",20,"metres",1
"Elm","Tree",60,"feet",6
"Ash",,20,"feet",2
"Chestnut","Tree",45,"metres",1

Note that the second field in row 3 is null.

My control file looks like this:

LOAD DATA
INFILE 'NullTest.dat'
REPLACE
INTO TABLE tree_test WHEN tree_type <> BLANKS
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
(TREE_NAME         CHAR,
 TREE_TYPE         CHAR,
 HEIGHT            FLOAT EXTERNAL,
 UOM               CHAR,
 MISC              CHAR TERMINATED BY WHITESPACE)
INTO TABLE tree_test_nulls WHEN tree_type = BLANKS
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
(TREE_NAME         POSITION(1) CHAR,
 TREE_TYPE         CHAR,
 HEIGHT            FLOAT EXTERNAL,
 UOM               CHAR,
 MISC              CHAR TERMINATED BY WHITESPACE)

When I run SQL*Loader I get 3 records loaded into table tree_test (Oak, Elm and
Chestnut) but no records are loaded into table tree_test_null. The log file
indicates that record 3 was discarded as it failed all WHEN clauses. The
relevant section of the log file follows:

Table TREE_TEST, loaded when TREE_TYPE != BLANKS
Insert option in effect for this table: REPLACE

   Column Name        Position   Len  Term Encl Datatype
-------------------- ---------- ----- ---- ---- ---------------------
TREE_NAME                 FIRST     *   ,  O(") CHARACTER
TREE_TYPE                  NEXT     *   ,  O(") CHARACTER
HEIGHT                     NEXT     *   ,  O(") CHARACTER
UOM                        NEXT     *   ,  O(") CHARACTER
MISC                       NEXT     *  WHT O(") CHARACTER

Table TREE_TEST_NULLS, loaded when TREE_TYPE = BLANKS
Insert option in effect for this table: REPLACE

   Column Name        Position   Len  Term Encl Datatype
-------------------- ---------- ----- ---- ---- ---------------------
TREE_NAME                     1     *   ,  O(") CHARACTER
TREE_TYPE                  NEXT     *   ,  O(") CHARACTER
HEIGHT                     NEXT     *   ,  O(") CHARACTER
UOM                        NEXT     *   ,  O(") CHARACTER
MISC                       NEXT     *  WHT O(") CHARACTER

Record 3: Discarded - failed all WHEN clauses.

Table TREE_TEST:
  3 Rows successfully loaded.
  0 Rows not loaded due to data errors.
  1 Row not loaded because all WHEN clauses were failed.
  0 Rows not loaded because all fields were null.

Table TREE_TEST_NULLS:
  0 Rows successfully loaded.
  0 Rows not loaded due to data errors.
  4 Rows not loaded because all WHEN clauses were failed.
  0 Rows not loaded because all fields were null.

I could convert all occurrences of ,, in the input data file to '," ",' to
workaround the problem but I'm unable to edit the *actual* input data file as it
contains sensitive accounts information.

How can I get SQL*Loader to correctly detect a null in the field?

Thanks in anticipation...

Neil W. Garside

PS. Love the "one-on-one" book! I've only had it 2 months and it's nearly worn
out!

      and we said...
I've fought with this issue before (and lost)....

I couldn't figure out a way to do it - BLANKS is not the same as NULL and NULL
cannot be used in a WHEN.

My suggestion has been to run one pass on the input -- creating a discard file
that you then sqlldr into the other table.

In 9i, this is totally solved with external tables which will allow you to use
ANY sql predicate with a multi-table insert to filter and route the data.
Review &
      Followup

      Rating: 5
Multiple INTO
      in SQL Loader  June 04, 2002
Reviewer:
      Bharath  from India
Hi,

Is it necessary to have the clause POSITION(1) from the second INTO TABLE
onwards when the control file contains more than one INTO TABLE ststement.  In
the example given below, If I have POSITION(1) then the data is loaded, if the
WHEN condition happens to be valid. But if I remove the POSITION(1), the data is
not loaded even if the WHEN condition happens to be valid.

I searched about this in your book, but it was not given. Could you explain the
use of POSITION clause in sql loader.

          Followup:

If you did not use the POSITITION(1), sqlldr would pick off where it left off in
the record for the second INTO section.

So, think of the position(1) like a request to "reset record back to the first
character".  Without it -- you would pick up where you left off

      Rating: 5
Multiple INTO
      in SQL Loader  June 04, 2002
Reviewer:
      Bharath  from India
Hi,

Is it necessary to have the clause POSITION(1) from the second INTO TABLE
onwards when the control file contains more than one INTO TABLE ststement.  In
the example given below, If I have POSITION(1) then the data is loaded, if the
WHEN condition happens to be valid. But if I remove the POSITION(1), the data is
not loaded even if the WHEN condition happens to be valid.

LOAD DATA
INFILE 'DEPT_LOADER.txt'
BADFILE 'DEPT_LOADER.bad'
DISCARDFILE 'DEPT_LOADER.dis'
TRUNCATE
INTO TABLE dept_loader
WHEN (deptno='10')
(
deptno INTEGER EXTERNAL TERMINATED BY ",",
dname  CHAR TERMINATED BY "," ENCLOSED BY '"',
loc  CHAR TERMINATED BY "," ENCLOSED BY '"'
)
INTO TABLE dept_loader
WHEN (deptno='20')
(
deptno POSITION(1) INTEGER EXTERNAL TERMINATED BY ",",
dname  CHAR TERMINATED BY "," ENCLOSED BY '"',
loc  CHAR TERMINATED BY "," ENCLOSED BY '"'
)

I searched about this in your book, but it was not given. Could you explain the
use of POSITION clause in sql loader.

Regards,
Bharath

      Rating:
      3
  October 15, 2003
Reviewer:  Sagi
       from UK
Hi Tom,

I have a problem in using WHEN. I am sorry if this is not the correct place to
post.

I tried on ur site and could get no break through. So posting.

I have data like this:

/resource/images/learn/side_nav_dictionary-current.gif
/resource/images/learn/side_nav_links.gif
/resource/images/home/link_request.gif
/resource/jsp/common/internalServerError.jsp
/resource/jsp/common/internalServerError.jsp
/resource/jsp/common/internalServerError.jsp

I want to upload this into a table. But ignore .gif and .jpg records

I tried to use SUBSTR in the column_name where this data goes. But sqlldr gives
error.

My control file looks like this

load data
infile 'c:\data.txt'
BADFILE 'c:\data.bad'
DISCARDFILE 'c:\data.dsc'
APPEND into table ACCESS_INFO
WHEN SUBSTR(URL,-4,4) != '.gif' AND
 SUBSTR(URL,-4,4) != '.jpg'
fields terminated by whitespace OPTIONALLY ENCLOSED BY '"'
(id   SEQUENCE(1,1),
URL   CHAR(3000) )

Your help is very much apprieciated.

Thanx in advance.

          Followup:

what version of the database are you using.

      Rating:
      3
  October 16, 2003
Reviewer:  Sagi

Tom,

Sorry forgot to mention about it.

I have this requirement for both 8.1.7 and 9.2.0

Awaiting for your help.

Thanx in advance.

Regards,

          Followup:

don't think you'll be doing it in sqlldr then -- if you are on unix you can load
from a named pipe that you fill with a grep on that file to filter out the rows
you don't want loaded.

      Rating:
      4
  October 16, 2003
Reviewer:  Sagi

Tom,

I have to do this on WIN2K.

But let me understand what you meant. If I was on Unix, then I would do the
following:

-- Create a named pipe (Say mypipe)
-- use grep to filter. In my case it would be:
   grep -v "*.gif" | mypipe | sqlldr control=myctrl.ctl data=mypipe

Is my understanding right?

Secondly, using CYWIN, can i do from windows machine this?

Thanx in advance.

Regards,

          Followup:

in unix i would:

$ mknod input.dat p
$ egrep -v '(.gif$|.jpg$)' > input.dat &
$ sqlldr .... data=input.dat

on windoze, which doesn't do something as cool as pipes, you may well be plumb
out of luck.  No idea if cywin supports TRUE pipes

October 16, 2003 Wen -- Thanks for the question regarding "Perl
      scripts ", version Oracle 8i Releasae 3 (8.1.7)

      You Asked
Is it possible to call perl scripts from within SQL procedures?

Thanks,
Wen

      and we said...
Yes ...

If you have a perl script set up as a cgi-bin program under a
webserver, UTL_HTTP provides an easy method for invoking them.

If you have java in the database, java can be used to run
external programs.

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:952229840241
If you have C, you can write an external procedure.

http://technet.oracle.com/doc/oracle8i_816/appdev.816/a76939/adg11rtn.htm#1656
...

Review &
      Followup

      Rating: 3
How much of
      perl knowledge is required ?  April 03, 2003

Reviewer:  Max
Tom,

A lot of experienced DBAs recommend learning perl to make life easy in the unix
world for administration; whereas some recommend any korn/bourne shell
scripting.

What is your advise/recommendation for budding DBAs like me who dont know either
of the above ???

          Followup:

PLSQL, RMAN...

then, you can script on a mainframe, unix, or windoze without concern for the
OS.

Me, I can count the OS scripts I use on one hand, with just some of my
fingers...  dbms_job -- better then cron.  sqlplus -- my execution environment.
OS -- just doesn't really matter.

      Rating: 5
FUP - How much
      of perl knowledge is required   April 03, 2003

Reviewer:  Jim  from Australia
Max,
    I was using Perl back in 1996, then I went contracting
I worked at ALOT of sites but it wasn't until 7 years
later that I had to use it again .

During those 7 years I wrote many many shell scripts
so shell scripting was more important to me.

Personally I love Perl, glad to be working with it again
but there was no requirement for me to use for a long time.

My advice would be learn some shell scripting,
by all means learn Perl too but not everywhere
will have Perl but I have never been to a Unix site yet
that didn't have shell scripts..

Regards
Jim

      Rating: 3
Thanks
      !  April 04, 2003
Reviewer:  Max
Tom & Jim : Thanks for your answer. Tom, I already know PLSQL, RMAN etc. I was
asking about the scripting side -- sorry I was not clear.

Jim, thanks for your answer. And with shell scripting, does it matter what shell
? What shell is used more w.r.t. database administration ?

          Followup:

but, if you do your "scripting" in those languages -- you don't even need a
shell script -- that was my point

      Rating: 3
scripting using
      plsql vs shell  April 04, 2003
Reviewer:
      Max
Tom: I too am a strong follower of PLSQL and I agree that most of the scripting
tasks can be done using PLSQL. But what about those places where we try to get a
job ? Those folks are still demanding that the dba *must* have working knowledge
of shell scripting.

And personally I have not worked on unix, so I cant tell where plsql would fall
short and then one has to take support of shell/perl scripting.

      Rating: 4
shell script if
      a must if you are in linux or unix  April 04, 2003

Reviewer:  A reader
it's a must, period

Perl is cool as well, in fact quite a few scripts that comes with Solaris 9 are
already in Perl, also Oracle IAS comes with many Perl scripts.

You dont need to be expert in shell script or Perl, they are pretty simple if
all you want is monitoring, administration, pattern matching etc Shell can start
to get complicated when you want to deal with processes, sub shells...

I have been several shops and many of them bases their ETL Process on KSH
scripts, PL/SQL and SQL scripts (Have seen Perl as well but not as popular), the
advantage of Perl is it is more like programming language than scripting
language, it integrates well with C libraries (you call C functions in Perl, not
sure about Shell scripts), it's more flexible and powerful and seems that quite
a few software providers noticed that.

If I am correct I think OEM used to use TCL (another scripting language) but
there are some Perl now as well!

      Rating: 5
not sure if you
      are development DBA  April 04, 2003
Reviewer:
      A reader
Hi again

I am stating my view from a production DBA view, we code shell scripts and Perl
scripts now and then, quite often. If you are developer or development DBA then
I am not sure how useful can shell scripts or Perl useful to you.

PL/SQL is fine but not to deal with OS, it is for what it is designed for,
inside database. What if the database is down? In practice I have seen more an
instance failure than a server boom boom (dies!)

          Followup:

yes, but if the database is down -- what work are you going to do beyond
"startup"?

      Rating: 2
which shell
      ?  April 04, 2003
Reviewer:  A reader

When you all talk about shell scripting, which shell are you referring to? I
dont know how much they differ from each other, but I know they are different.

      Rating: 5
PL/SQL
      ,RMAN  April 05, 2003
Reviewer:  Tarry
       from Holland
I really agree 100% with tom there......

Rman : You'd want to look into why you would want to use rman as against the
regular os scripts. You'd see that rman outweighs things in tons..save space,so
much of automation is possible, I mean I see people posting on forums as to how
to automate and delete arch log files thru os scripts while rman with a simple
command will do the job for you, it checks during backups for fractures, I mean
you can go on and on..

dbms_job? Well it's one of the many packages in the pl/sql supplied packages
which is over 2000 pages book having practically everything you'd want to do
with pl/sql.

      Rating:
      1
Perl  October 15, 2003
Reviewer:
      Beth  from San Diego, CA USA
It was not useful to me because it did not address my initial question:  Do the
database connections, via a Oracle::DBD, by-pass the tnsnames.ora and why does
it happen that way?

          Followup:

hmmm I do not see any questions from "beth" before this?

if it is using "user" "password" "connect string" -- no, it is not.

perl just uses oci, oci uses net8, net8 will do the same thing whether perl
invokes it or sqlplus is the application using it.

      Rating: 3
There is a need
      for shell or perl scripts for DBA  October 16, 2003

Reviewer:  Jeff  from New York, NY
Tom, in  'Expert One-on-One', you give a clear example of why you would want to
use the shell during export/import. In order to compress/limit file size of
export and to place the export in a safe holding area you would need to use the
shell.

I agree that a DBA must have a command over PL/SQL and RMAN, but a little help
from the shell can give you more options when managing your DB environment.  If
you follow a layered approach to backup and recovery than the shell and or perl
can be a great help.

Shell scripts can also help the DBA to understand what is happening at the OS
level and why.  My background as a UNIX SA, which has entailed years of writing
shell scripts has made me a much better DBA.

          Followup:

as i said tho

... Me, I can count the OS scripts I use on one hand,  ....

one hand.  tiny, few, far between, hardly at all, in the future (soon) ZERO,
zippo, none.

October 16, 2003 William -- Thanks for the question regarding
      "File Exists which is not a BFILE", version 8.1.7

      You Asked
I have a need to know if a file which is defined in the database as a varchar2,
i.e. imagefile    varchar2(255), actually exists on the server or not.  I read
the answer where it was suggested to use the dbms_lob.fileexists function,
however after reading the 8i Application Developer's Guide Large Objects and
trying everything in there, I can not get the varchar2 variable successfully
translated to the BFILE character type.  I would have thought that the following
would have been successful:

image_dir  varchar2(255);
image_fn   varchar2(255);
image_fileb BFILE;
.
.
image_dir := substr(image_file,1,instr(image_file,'/',-1));
image_fn := substr(image_file,1,instr(image_file,'/',-1)+1);
image_fileb := BFILENAME(image_dir, image_fn);
IF (dbms_lob.fileexists(image_fileb)) THEN

However, I receive the error: 'PLS-00382: expression is of wrong type'
I checked and the values of image_dir and image_fn are correct.
Your assistance will be greatly appreciated.

      and we said...
Your error is because fileexists returns a NUMBER, not boolean (don't ask me
why).  But anyway..

BFILES work with directory objects.  If you want to do it by "file path" and
"file name", that'll not work as you have it coded.  Consider:

ops$tkyte@ORA920> declare
  2          l_bfile bfile := bfilename( '/tmp/', 'foo.dat' );
  3  begin
  4          if ( dbms_lob.fileexists( l_bfile ) = 1 )
  5          then
  6                  dbms_output.put_line( 'Yes' );
  7          else
  8                  dbms_output.put_line( 'No' );
  9          end if;
 10  end;
 11  /
declare
*
ERROR at line 1:
ORA-22285: non-existent directory or file for FILEEXISTS operation
ORA-06512: at "SYS.DBMS_LOB", line 485
ORA-06512: at line 4

ops$tkyte@ORA920> create or replace directory MY_DIR as '/tmp'
  2  /

Directory created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          l_bfile bfile := bfilename( 'MY_DIR', 'foo.dat' );
  3  begin
  4          if ( dbms_lob.fileexists( l_bfile ) = 1 )
  5          then
  6                  dbms_output.put_line( 'Yes' );
  7          else
  8                  dbms_output.put_line( 'No' );
  9          end if;
 10  end;
 11  /
Yes

PL/SQL procedure successfully completed.

Now, if you can create a directory object -- that would be easiest -- however we
can use utl_file to achieve this in a fashion:

ops$tkyte@ORA920> create or replace function file_exists( p_fname in varchar2 )
return boolean
  2  as
  3          l_file utl_file.file_type;
  4  begin
  5          l_file :=
  6          utl_file.fopen( substr( p_fname, 1, instr(p_fname,'/',-1) ),
  7                          substr( p_fname, instr( p_fname, '/', -1)+1 ), 'r'
);
  8
  9          utl_file.fclose( l_file );
 10          return TRUE;
 11  exception
 12          when utl_file.invalid_path then return FALSE;
 13          when utl_file.invalid_operation then return FALSE;
 14  end;
 15  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2          if ( file_exists( '/tmp/foo.dat' ) )
  3          then
  4                  dbms_output.put_line( 'Yes' );
  5          else
  6                  dbms_output.put_line( 'No' );
  7          end if;
  8  end;
  9  /
Yes

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> begin
  2          if ( file_exists( '/tmp/foo2.dat' ) )
  3          then
  4                  dbms_output.put_line( 'Yes' );
  5          else
  6                  dbms_output.put_line( 'No' );
  7          end if;
  8  end;
  9  /
No

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating: 1
Won't work for
      image/graphics file  February 04, 2003
Reviewer:
      Will Cook  from Dripping Springs, Texas
I appreciate your quick response, however I had already experimented with the
utl_file functions and they will not work for image/graphics files.  I apologize
for not being more explicit that my problem has to do with image/graphics files,
other than having the variables named image_dir, image_fn, and image_fileb.  My
mental lapse.  Since BFILES deal with large binary objects I was hoping that
using them would solve my problem.  However, I have hundreds of directories each
containing thousands of files, so creating directory objects as outlined is
probably not a feasible solution for me.  Perhaps some future release could
contain a simple file exists regardless of file type function which would accept
data contained in tables which can be extracted and used for the directory and
filename information.

          Followup:

they will check for "existence" which is what you asked for.

foo.dat -- it is binary data.

the fileExists I wrote -- tells me if it exists or not, problem solved.

utl_file cannot be used to read/write binary files (until 9iR2), but it
certainly doesn't care if they are binary or not when opening them

      Rating: 2
My problem was
      different than what I thought  February 05, 2003

Reviewer:  Will Cook  from Dripping Springs, Texas

My actual problem was not that UTL_FILE.FOPEN wouldn't open and therefore check
for the existence of a graphics file, the problem was, at least on my server,
how Oracle wants the directory and filename to be presented.  The following does
not work:
utl_file.fopen( substr( p_fname, 1, instr(p_fname,'/',-1) ),
substr( p_fname, instr( p_fname, '/', -1)+1 ), 'r');
The result is the directory name = /directory/
while the file name = filename
Whereas this does work:
utl_file.fopen(substr(p_fname, 1, instr(p_fname,'/',-1)-1),
substr(p_fname,instr(p_fname,1,instr(p_fname,'/',-1)) 'r');
The result is the directory name = /directory
while the file name = /filename
For some reason, at least running Oracle 8.1.7 on SUN 2.8, it makes a difference
on which argument to UTL_FILE.FOPEN the slash between the directory name and
filename is.

          Followup:

your directory must match the init.ora setting of utl_file_dir typically.

      Rating: 3
Manual needs
      modification  February 05, 2003
Reviewer:  Will
      Cook  from Dripping Springs, Texas
Ah ha!  That makes perfect sense.  Unfortunately then, the examples in the
Oracle8i Supplied PL/SQL Packages Reference, which are:
UTL_FILE_DIR=/appl/gl/log
UTL_FILE_DIR=/appl/gl/out

perhaps should be modified and some better examples and explanations supplied.

      Rating:
      3
  July 24, 2003
Reviewer:  vj
       from in
In my previous application i used to
insert a data from the directory (CREATE OR REPLACE DIRECTORY images AS 'C:\';)
to the BLOB column as well as retrieval...

i got a question ? this application is running on JSP,WEBSPHERE,ORACLE 9I
DB...In one of the JSP page we have a scanning interface, which typically scan
the signature from the physical application..We have to insert that into BLOB
column in the DB..

How can i acheive it ? How do i insert it from JSP to DATABASE and retrieve the
same..any help is highly appreciated..

Rgds

          Followup:

check out the LOB documentation of which there is alot:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-APP
your jsp is probably running on server 1 and the db on server 2 -- you will not
use dbms_lob.loadFromFile or a bfile as that would imply the file is on server 2
-- but it won't be.

So -- you'll be using a java stream to stream the data in.

      Rating: 3
What is the
      error  October 15, 2003
Reviewer:  vikas
      sharma  from delhi india
Hi Tom,

I have written the following the code. I am not able find out where the problem
is.

  1  declare
  2  l_blob  blob := empty_blob;
  3  l_bfile bfile := bfilename( 'MYDIR', 'mog.doc' );
  4      begin
  5  dbms_lob.createtemporary(l_blob,false);
  6   if ( dbms_lob.fileexists( l_bfile ) = 1 )
  7              then
  8                      dbms_output.put_line( 'Yes' );
  9  dbms_lob.fileopen(l_bfile,dbms_lob.file_readonly);
 10  dbms_lob.loadfromfile( l_blob, l_bfile ,
 11                         dbms_lob.getlength(l_bfile) );
 12  dbms_lob.fileclose(l_bfile);
 13  else
 14                      dbms_output.put_line( 'No' );
 15              end if;
 16*    end;
miller@STDDEV> /
Yes
declare
*
ERROR at line 1:
ORA-22288: file or LOB operation FILEOPEN failed
ORA-06512: at "SYS.DBMS_LOB", line 504
ORA-06512: at line 9

I am not able to understand why file open is failing. when file exists is true.

I have check the security also. It is read,modify,delete to everyone.

Thanks

Vikas Sharma

          Followup:

incomplete test case -- i cannot see the definition of MYDIR.  and remember the
file MUST be on the DATABASE SERVER and if you are on windoze, it MUST be on a
local disk -- not a share (for you have to jump through massive hoops to make a
share accessible to a background process like a database)

This is executed on the database server itself (so I know that /tmp/mog.doc
exists)

ops$tkyte@ORA920> create or replace directory MYDIR as '/tmp/'
  2  /

Directory created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> !echo 'Hello World' > /tmp/mog.doc

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2  l_blob     blob := empty_blob;
  3  l_bfile bfile := bfilename( 'MYDIR', 'mog.doc' );
  4      begin
  5  dbms_lob.createtemporary(l_blob,false);
  6   if ( dbms_lob.fileexists( l_bfile ) = 1 )
  7              then
  8                      dbms_output.put_line( 'Yes' );
  9  dbms_lob.fileopen(l_bfile,dbms_lob.file_readonly);
 10  dbms_lob.loadfromfile( l_blob, l_bfile ,
 11                         dbms_lob.getlength(l_bfile) );
 12  dbms_lob.fileclose(l_bfile);
 13  else
 14                      dbms_output.put_line( 'No' );
 15              end if;
 16  end;
 17  /
Yes

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> !chmod a= /tmp/mog.doc

ops$tkyte@ORA920> !ls -l /tmp/mog.doc
----------    1 tkyte    tkyte          12 Oct 16 10:25 /tmp/mog.doc

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2  l_blob     blob := empty_blob;
  3  l_bfile bfile := bfilename( 'MYDIR', 'mog.doc' );
  4      begin
  5  dbms_lob.createtemporary(l_blob,false);
  6   if ( dbms_lob.fileexists( l_bfile ) = 1 )
  7              then
  8                      dbms_output.put_line( 'Yes' );
  9  dbms_lob.fileopen(l_bfile,dbms_lob.file_readonly);
 10  dbms_lob.loadfromfile( l_blob, l_bfile ,
 11                         dbms_lob.getlength(l_bfile) );
 12  dbms_lob.fileclose(l_bfile);
 13  else
 14                      dbms_output.put_line( 'No' );
 15              end if;
 16  end;
 17  /
Yes
declare
*
ERROR at line 1:
ORA-22288: file or LOB operation FILEOPEN failed
Permission denied
ORA-06512: at "SYS.DBMS_LOB", line 504
ORA-06512: at line 9

so, you should be getting more info in there (like I did) but i think your
file exists but is not truly accessible to the server process

      Rating: 3
File open
      Error  October 16, 2003
Reviewer:  Vikas
      Sharma  from Delhi India
hi Tom,

Thanks for your reply. Sorry for not providing the complete test case.
Here is the complete test case.

miller@STDDEV> create or replace directory mydir as '\\mtldb\UTLFILE';
Directory created.

miller@STDDEV>
miller@STDDEV> declare
  2     l_blob  blob := empty_blob;
  3     l_bfile bfile := bfilename( 'MYDIR', 'mog.doc' );
  4         begin
  5     dbms_lob.createtemporary(l_blob,false);
  6      if ( dbms_lob.fileexists( l_bfile ) = 1 )
  7                 then
  8                         dbms_output.put_line( 'Yes' );
  9    dbms_lob.fileopen(l_bfile,dbms_lob.file_readonly);
 10     dbms_lob.loadfromfile( l_blob, l_bfile ,
 11                            dbms_lob.getlength(l_bfile) );
 12     dbms_lob.fileclose(l_bfile);
 13     else
 14                        dbms_output.put_line( 'No' );
 15                 end if;
 16       end;
 17  /
Yes
declare
*
ERROR at line 1:
ORA-22288: file or LOB operation FILEOPEN failed
ORA-06512: at "SYS.DBMS_LOB", line 504
ORA-06512: at line 9

***************************
But when i change the directory as following it works.

miller@STDDEV> create or replace directory mydir as 'h:\UTLFILE';

Directory created.

miller@STDDEV>  declare
  2      l_blob  blob := empty_blob;
  3      l_bfile bfile := bfilename( 'MYDIR', 'mog.doc' );
  4          begin
  5      dbms_lob.createtemporary(l_blob,false);
  6       if ( dbms_lob.fileexists( l_bfile ) = 1 )
  7                  then
  8                          dbms_output.put_line( 'Yes' );
  9     dbms_lob.fileopen(l_bfile,dbms_lob.file_readonly);
 10      dbms_lob.loadfromfile( l_blob, l_bfile ,
 11                             dbms_lob.getlength(l_bfile) );
 12      dbms_lob.fileclose(l_bfile);
 13      else
 14                         dbms_output.put_line( 'No' );
 15                  end if;
 16        end;
 17  /
Yes

PL/SQL procedure successfully completed.

miller@STDDEV>

I can understand that the UTLFILE folder shared. but it is not mapped dirve on
server. It is on the local hard drive H:\ on windows.

Does bfile directory does not work with the UNC path?

And the other issue is if the server is able to see it as the output of file
exists is "YES"  then WHY it is not able to OPEN load it? Is it a bug?

I did not change any security,access permissions and it worked with h:\utlfile.

Thanks,
Regards

Vikas Sharma

          Followup:

no ideas for you as I cannot test using proprietary windoze techniques.  I have
no machines that run that OS.  Sorry.

You'll have to contact support and work it that way -- sorry.

      Rating:
      3
Thanks  October 16, 2003
Reviewer:
      Vikas Sharma  from delhi india
Hi Tom,

Thanks,

Vikas Sharma

October 16, 2003 Cody -- Thanks for the question regarding
      "Returning recordset from package", version 8.1.7

      You Asked
Hi Tom,

I have a question for you, or others that come across this posting.  I have
created a package and then a procedure within that package body for the purpose
of returning a recordset back as a refcursor.  I can then print out my records
as needed.  My problem is that when I try to compile the below code, i receive
the error:

PLS-00103: Encountered the symbol "SELECT" when expecting one of the following:
   ( - + mod not null others <an identifier>
   <a double-quoted delimited-identifier> <a bind variable> avg
   count c

Can I not have multiple selects within the cursor? Is there a way around this?
Thanks for your time!

I can run the sql statemnt in its entirety in SqlPlus or TOAD without a problem,
and receive the desired records. It only croaks when I try to compile it in the
procedure.

-- Code Snipets --------------------------------------------

CREATE OR REPLACE package return_myhomepage_latest_pkg
is
-- declare a type for the cursrot
type t_cursor is ref cursor;
procedure my_homepage_latest( p_cursor in out t_cursor, userid in char );
end;
/

CREATE OR REPLACE Package Body return_myhomepage_latest_pkg
is
procedure my_homepage_latest
( p_cursor in out t_cursor, userid in char
) is
begin
open p_cursor for
select topic.lob_id, decode(lower(docgroup.title),
'none','Other',docgroup.title) as docgrouptitle, docgroup.docgroup_id,
doctype.doctype_id, doctype.title as doctypetitle, docstage.title as topicstage,

topic.topic_id, decode(topic.title,NULL,'-1',topic.title) as topictitle,
topic.series_id, series.title as seriestitle, document.document_id,
document.location,document.title as doctitle
from topic, docstage, preferences, series, document, doctopic, docgroup,
doctype, grouplist2
where topic.topic_id = preferences.topic_id(+)
and topic.series_id = series.series_id(+)
and topic.docstage_id = docstage.docstage_id
and topic.topic_id = doctopic.topic_id
and doctopic.deleted=0
and document.deleted=0
and preferences.preference_type = 1
and document.document_id = doctopic.document_id
and document.doctype_id = doctype.doctype_id
and doctype.docgroup_id = docgroup.docgroup_id
and NOT preferences.topic_id IS NULL
and preferences.user_id = userid
and preferences.isdefault = 0
and document.accessgroup_id = grouplist2.group_id
and grouplist2.user_id = preferences.user_id
and topic.lob_id in (select lob_id from preferences where user_id = userid)
and trunc(document.daterevised) >= (trunc(sysdate) - (select
decode(preferences.viewbyperiod ,'6','7','7','8', preferences.viewbyperiod - 1)
from preferences where user_id = userid and preference_type = 3 and
isdefault=0))
UNION
-- by docstage_id (all topics)
select topic.lob_id, decode(lower(docgroup.title),
'none','Other',docgroup.title) as docgrouptitle, docgroup.docgroup_id,
doctype.doctype_id, doctype.title as doctypetitle, docstage.title as topicstage,

topic.topic_id, decode(topic.title,NULL,'-1',topic.title) as topictitle,
topic.series_id, series.title as seriestitle, document.document_id,
document.location,document.title as doctitle
from topic, docstage, preferences, series, document, doctopic, docgroup,
doctype, grouplist2
where topic.docstage_id = preferences.docstage_id
and topic.docstage_id = docstage.docstage_id
and topic.series_id = series.series_id(+)
and topic.topic_id = doctopic.topic_id
and doctopic.deleted=0
and document.deleted=0
and preferences.preference_type = 1
and document.document_id = doctopic.document_id
and document.doctype_id = doctype.doctype_id
and doctype.docgroup_id = docgroup.docgroup_id
and preferences.topic_id IS NULL
and preferences.Series_id IS NULL
and NOT preferences.Docstage_id IS NULL
and preferences.user_id = userid
and preferences.isdefault = 0
and document.accessgroup_id = grouplist2.group_id
and grouplist2.user_id = preferences.user_id
and topic.lob_id in (select lob_id from preferences where user_id = userid)
and trunc(document.daterevised) >= (trunc(sysdate) - (select
decode(preferences.viewbyperiod ,'6','7','7','8', preferences.viewbyperiod - 1
from preferences where user_id = userid and preference_type = 3 and
isdefault=0))
order by 11, 5 desc, 4, 10, 8
end;
end;
/

-- I can compile this statment properly if I remove the following:
-- Oracle halts on the Select keyword in the statement.

and trunc(document.daterevised) >= (trunc(sysdate) - (select
decode(preferences.viewbyperiod ,'6','7','7','8', preferences.viewbyperiod - 1
from preferences where user_id = userid and preference_type = 3 and
isdefault=0))

      and we said...
Until 9i, the PLSQL parse "lags" behind the SQL we support (group by rollup,
order by in a subquery, CASE statement, among others).

the construct you are using it doens't understand is:

select ...
 where x = y - (select .... )

using a SELECT anywhere a constant could be used is new functionality.

The solutions:

1) (and what I suggest for you) HIDE the construct in a view.  for such a large
query, I would use a view.  PLSQL won't parse the construct giving you problems
and you'll be OK.

2) use native dynamic sql.  eg:

open p_cursor for
'select topic.lob_id, decode(lower(docgroup.title),
''none'',''Other'',docgroup.title) as docgrouptitle, docgroup.docgroup_id,
doctype.doctype_id, doctype.title as doctypetitle, docstage.title as topicstage,

topic.topic_id, decode(topic.title,NULL,''-1'',topic.title) as topictitle,
topic.series_id, series.title as seriestitle, document.document_id,
document.location,document.title as doctitle
from topic, docstage, preferences, series, document, doctopic, docgroup,
doctype, grouplist2
where topic.topic_id = preferences.topic_id(+)
and topic.series_id = series.series_id(+)
and topic.docstage_id = docstage.docstage_id
and topic.topic_id = doctopic.topic_id
and doctopic.deleted=0
and document.deleted=0
and preferences.preference_type = 1
and document.document_id = doctopic.document_id
and document.doctype_id = doctype.doctype_id
and doctype.docgroup_id = docgroup.docgroup_id
and NOT preferences.topic_id IS NULL
and preferences.user_id = :userid
and preferences.isdefault = 0
and document.accessgroup_id = grouplist2.group_id
and grouplist2.user_id = preferences.user_id
and topic.lob_id in (select lob_id from preferences where user_id = :userid)
and trunc(document.daterevised) >= (trunc(sysdate) - (select
decode(preferences.viewbyperiod ,''6'',''7'',''7'',''8'',
preferences.viewbyperiod - 1)
from preferences where user_id = :userid and preference_type = 3 and
isdefault=0))
UNION
-- by docstage_id (all topics)
select topic.lob_id, decode(lower(docgroup.title),
''none'',''Other'',docgroup.title) as docgrouptitle, docgroup.docgroup_id,
doctype.doctype_id, doctype.title as doctypetitle, docstage.title as topicstage,

topic.topic_id, decode(topic.title,NULL,''-1'',topic.title) as topictitle,
topic.series_id, series.title as seriestitle, document.document_id,
document.location,document.title as doctitle
from topic, docstage, preferences, series, document, doctopic, docgroup,
doctype, grouplist2
where topic.docstage_id = preferences.docstage_id
and topic.docstage_id = docstage.docstage_id
and topic.series_id = series.series_id(+)
and topic.topic_id = doctopic.topic_id
and doctopic.deleted=0
and document.deleted=0
and preferences.preference_type = 1
and document.document_id = doctopic.document_id
and document.doctype_id = doctype.doctype_id
and doctype.docgroup_id = docgroup.docgroup_id
and preferences.topic_id IS NULL
and preferences.Series_id IS NULL
and NOT preferences.Docstage_id IS NULL
and preferences.user_id = :userid
and preferences.isdefault = 0
and document.accessgroup_id = grouplist2.group_id
and grouplist2.user_id = preferences.user_id
and topic.lob_id in (select lob_id from preferences where user_id = :userid)
and trunc(document.daterevised) >= (trunc(sysdate) - (select
decode(preferences.viewbyperiod ,''6'',''7'',''7'',''8'',
preferences.viewbyperiod - 1
from preferences where user_id = :userid and preference_type = 3 and
isdefault=0))
order by 11, 5 desc, 4, 10, 8' using userid, userid, userid, userid, userid,
userid;

(strongly encourage you to name PLSQL parameters using P_userid, local
variables like l_userid and package globals like g_userid.  that way, there will
NOT be any conflict (probably) with column names.  consider the procedure:

create procedure p( ENAME in varchar2 )
as
begin
    for x in (select * from emp where ename = ENAME )
    loop

that returns all rows in EMP that have a non-null ENAME, not those rows that
have the value passed into the procedure!!!)

Review &
      Followup

      Rating: 3
Error in the
      cursor statement  March 29, 2002
Reviewer:
      Vishal Shah  from Atlanta, GA USA
Cody,
Looking at you sql statement, there is a close parenthesis missing in the
following code:
and trunc(document.daterevised) >= (trunc(sysdate) - (select
decode(preferences.viewbyperiod ,'6','7','7','8', preferences.viewbyperiod - 1
from preferences where user_id = userid and preference_type = 3 and
isdefault=0))

should be

and trunc(document.daterevised) >= (trunc(sysdate) - (select
decode(preferences.viewbyperiod ,'6','7','7','8', preferences.viewbyperiod - 1)
from preferences where user_id = userid and preference_type = 3 and
isdefault=0))

Notice the close parenthesis before the from.

          Followup:

regardless -- the select used in that way won't compile in plsql until 9i:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2    from dual
  3   where sysdate >= trunc(sysdate) - (select 1 from dual)
  4  /

D
-
X

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2          type rc is ref cursor;
  3          l_cursor rc;
  4  begin
  5          open l_cursor for
  6      select *
  7        from dual
  8       where sysdate >= trunc(sysdate) - (select 1 from dual);
  9  end;
 10  /
     where sysdate >= trunc(sysdate) - (select 1 from dual);
                                        *
ERROR at line 8:
ORA-06550: line 8, column 41:
PLS-00103: Encountered the symbol "SELECT" when expecting one of the following:
( - + mod not null others <an identifier>
<a double-quoted delimited-identifier> <a bind variable> avg
count current exists max min prior sql stddev sum variance
execute forall time timestamp interval date
<a string literal with character set specification>
<a number> <a single-quoted SQL string>
ORA-06550: line 9, column 1:
PLS-00103: Encountered the symbol "END"

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

so the need to dynamically open the ref cursor persists.

      Rating: 3
Invalid
      datatype error when getting recordset from stored
      function  January 21, 2003
Reviewer:  Jonathan
      Rees  from U.K
Tom,
I am getting error: ORA-00902: invalid datatype
when fetching a recordset from ProC as in the following code

EXEC SQL BEGIN DECLARE SECTION;
  SQL_CURSOR  chk_access;
  char        frmnam [FRMNAM_SIZE];
  char        objnam [OBJNAM_SIZE];
  int         danger;
  int         aryidx;
EXEC SQL END DECLARE SECTION;

  /* Execute stored procedure */
  EXEC SQL ALLOCATE :chk_access;
  EXEC SQL CALL pkg_log.sf_dialog_access
                  (:prcnam, :int_true, :usrnam,
                   :sql_pollid, :usrtyp_rdt)
           into :chk_access
          ;
  if (sqlca.sqlcode)
  {
    return FAILURE;
  }

  for ( ;; )
  {
    if ( sqlca.sqlcode == SQLNOTFOUND)
         break;
    EXEC SQL fetch :chk_access
             into  :frmnam,
                   :objnam,
                   :danger,
                   :aryidx;

    /* Process row, store objnam / aryidx */
    strcpy(loc_rsadata[n_rows].frmnam, frmnam);
    strcpy(loc_rsadata[n_rows].objnam, objnam);
    loc_rsadata[n_rows].danger = danger;
    loc_rsadata[n_rows].aryidx = aryidx;

    n_rows++;
  }

  /* Tidy up */
  EXEC SQL CLOSE :chk_access;

The function is declared in pkg_log as follows :

function sf_dialog_access(prcnam in varchar2,
                           debug in number,
                           p_usrnam in sysusr.usrnam%type,
                           p_unitid in varchar2,
                           p_usrtyp in number
                           ) return pkg_cmn.rc;

          Followup:

if you care to share a complete example, one that I can compile and run on my
system from start to finish (similar to what I give here on this site) -- I'll
be glad to look

To many unknowns in your example, whats :usrnam ?  what is sysusr.usrnam%type?
how is pkg_cmn.rc defined?  what does the query return?  where the heck are you
even seeing the error?

Make it really small -- one package, one table, one c routine with the MINIMUM
amount of code possible to reproduce.

      Rating: 3
followup with
      small example - but different error now  January 22, 2003

Reviewer:  Jonathan Rees  from U.K
Here is the package :
create or replace package pkg_test
as
type rc is REF CURSOR;
function recset_sysusr(p_prcnam in varchar2,
                        p_debug  in number) return rc;

end;
/
show error;

create or replace package body pkg_test
as

function recset_sysusr(p_prcnam in varchar2,
                       p_debug  in number) return rc
is
l_recset  rc;
BEGIN

 open l_recset for select usrnam,
                          usrtyp,
                          grpref
                     from sysusr
      ;
  return l_recset;

END;

end;
/
show error;

Here is the Pro*c code :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>

EXEC SQL INCLUDE SQLCA.H;

#define SQLNOTFOUND     1403  /* Mode ORACLE , not found status*/

int debug = 1;
char *prcnam = "TEST";
char errmsg[513] = "NO ERROR DETECTED YET ??";

/* prototypes */
int open_dbase ( char *dbname);  /* Open database given user & passwd*/
char *dberr(void);               /* Return text of last ORACLE error */

main (int argc, char *argv[])
{
EXEC SQL BEGIN DECLARE SECTION;
  SQL_CURSOR  chk_access;
  char        l_usrnam [16];
  int         l_usrtyp;
  int         l_grpref;
EXEC SQL END DECLARE SECTION;

  if ( argc < 1 )
  {
    printf("Usage:  tstprc <user/pass> ");
    return;
  }

  if ( ! open_dbase (argv[1]) )
    return;

  /* Execute stored functio to open cursor for recordset */
  EXEC SQL ALLOCATE :chk_access;
  EXEC SQL CALL pkg_test.recset_sysusr(:prcnam, :debug) into :chk_access ;
  if (sqlca.sqlcode)
  {
    printf("ERROR %s Failed to open ref cursor ", dberr());
    return;
  }

  for ( ;; )
  {
    EXEC SQL fetch :chk_access
             into  :l_usrnam,
                   :l_usrtyp,
                   :l_grpref
             ;
    if ( sqlca.sqlcode)
       break;

    printf ( " user [%s] type [%d] group [%d] ", l_usrnam, l_usrtyp, l_grpref);
  }
  if ( sqlca.sqlcode && sqlca.sqlcode != SQLNOTFOUND)
  {
    printf( " Fetch fail %s", dberr());
  }

  /* Tidy up */
  EXEC SQL CLOSE :chk_access;
  EXEC SQL COMMIT WORK RELEASE;
  return;

}

int open_dbase ( char *dbname)
/* Connect to database give user and password */
{

  /* Attempt to open the database */
  EXEC SQL WHENEVER SQLERROR GOTO fail;
  EXEC SQL CONNECT :dbname;
  return 1;

fail:
  printf( "Open database %s fail %s", dbname, dberr());
  return 0;
}

char *dberr(void)
/* Return text of last ORACLE error */
{
    int len = sqlca.sqlerrm.sqlerrml;

    strncpy(errmsg, sqlca.sqlerrm.sqlerrmc, len);
    errmsg[len] = '\0';

    return errmsg;
}

Table definition for SYSUSR :

$ sql desc sysusr

 ----------------------------------------- -------- ----------------------------
 USRNAM                                             CHAR(15)
 FULNAM                                             CHAR(30)
 PASSWD                                             CHAR(20)
 USRTYP                                             NUMBER(5)
 GRPREF                                             NUMBER(5)
 LSTLOG                                             DATE
 LSTPAS                                             DATE
 LOGSTS                                             NUMBER(5)
 DSPSTS                                             NUMBER(5)
 PASTRY                                             NUMBER(5)
 FRCPAS                                             NUMBER(5)
 NUMLOG                                             NUMBER(5)
 LANCDE                                             NUMBER(5)
 CLTCDE                                             CHAR(5)
 WHSCDE                                             NUMBER(5)

This is what happens when I build the test and run it:

$ bld tstprc
Running: make -f $PROJECT/util/tstprc.mak
Compiling tstprc.o

Pro*C/C++: Release 9.2.0.1.0 - Production on Wed Jan 22 11:33:55 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

System default option values taken from:
/data1/oracle/oracle/precomp/admin/pcscfg.cfg

Linking tstprc
make.tstprc completed
$ tstprc "test/test"
 Fetch fail ORA-01002: fetch out of sequence

This is a different error to the original , so I am afraid
I might have another problem here.

          Followup:

Use

  EXEC SQL EXECUTE
  BEGIN
    :chk_access := pkg_test.recset_sysusr(:prcnam, :debug);
  END;
  END-EXEC;

instead of "call"

      Rating: 3
Followup -
      using EXEC SQL EXECUTE  January 22, 2003
Reviewer:
      Jonathan Rees  from U.K
I did what you suggested, and I was told SQLCHECK=SEMANTICS should be given. So
i added that to the Pro*c precompiler flags. But now the following error is
reported :

Running: make -f $PROJECT/util/tstprc.mak
Generating header /data4/include/tstprc.h
Compiling tstprc.o

Pro*C/C++: Release 9.2.0.1.0 - Production on Wed Jan 22 14:59:41 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

System default option values taken from:
/data1/oracle/oracle/precomp/admin/pcscfg.cfg

Error at line 43, column 20 in file /SSdev/msuria/CDB/CDB_proj/util/tstprc.pc
    :chk_access := pkg_test.recset_sysusr(:prcnam, :debug);
...................1
PLS-S-00201, identifier 'PKG_TEST.RECSET_SYSUSR' must be declared
Error at line 43, column 6 in file /SSdev/msuria/CDB/CDB_proj/util/tstprc.pc
    :chk_access := pkg_test.recset_sysusr(:prcnam, :debug);
.....1
PLS-S-00000, Statement ignored
Semantic error at line 42, column 3, file
/SSdev/msuria/CDB/CDB_proj/util/tstprc.pc:
  BEGIN
..1
PCC-S-02346, PL/SQL found semantic errors
*** Exit 1
Stop.

How & where do I have to declare the procedure? Or is this a
privilege problem !!

Thanks

          Followup:

add

sqlcheck=semantics userid=user/pass

to the proc command line.

      Rating: 5
Followup : Use
      EXEC SQL EXECUTE  January 22, 2003
Reviewer:
      Jonathan Rees  from U.K
Thank you very much Tom. It works a treat.

      Rating: 5
fetch cursor
      Into string  October 16, 2003
Reviewer:  Wor
       from USA
Hi Tom,
I tried the example shown above
I notice that if the ref cursor is returning string values than the fetch
statement is something like this:
>>EXEC SQL fetch :chk_access into  :s_usrnam;
>>EXEC SQL BEGIN DECLARE SECTION;
>>  SQL_CURSOR  chk_access;
>>  char        s_usrnam [80];
>> EXEC SQL END DECLARE SECTION;

Problem:
Since the array size is 80 ( in database this is a varchar2(80) so whenever the
data returned is less than 80 I see the extra spaces. I need trimmed data.

How can I write Cursor to fetch into STRING and NOT ARRAY
so that I do not have to hardcode the array size.
I want to right a generic routine.

If I trim the data after fetching values than the performance is very slow. I
have Trim in my package function.

Thanx in advance.

          Followup:

well, if you are using static sql -- you can just hardcode the array size (else,
how big do you know to allocate for the array??)

anyway, you can use a VARCHAR type (see pro*c guide, tons of examples) or you
can search the pro*c does for asciiz -- a typedef they show you how to setup, to
get blank trimming going with the STRING(n) type

October 17, 2003 Hon Koat -- Thanks for the question regarding
      "RE: Setting a default ORACLE_SID", version 8.1.5

      You Asked
Hi Tom,

This is a follow up of my question at:

http://osi.oracle.com/wa/ask/owa/ask_tom_pkg.display?p_dispid=238214478777
Which home should I change it to? I have 4 Oracle products
installed on my Server but on different homes: Oracle8i, OEM204,
OAS, Developer.

I tried to change it to the <ORA81_HOME> but it does not work.

Some information, the SVRMGR30.EXE is in the <OAS_HOME>\bin
directory.

Thanks.

      and we said...
Well, svrmgr30 is the Oracle8.0 svrmgr.  For Oracle8i, release
8.1.5 or 8.1.6, the server manager program is simply "svrmgrl"
with no version number.  Svrmgr30 will not work with the ORA81
Home (but svrmgrl will).

So, you are using a svrmgr30 that comes OAS.  Since there is no
database in the OAS Oracle Home, it is perfectly normal to get
the ORA-12203: TNS:unable to connect to destination message.
 This is because svrmgr30 is attempting to connect to a local
instance by default and is just telling us it cannot (since
there is not a local instance in the OAS code tree).

The svrmgr30 that is in the OAS oracle home is used by OAS to
connect over sql*net to another instance.  OAS will use it --
you should not have to.  If you do want to use it, you would
ignore the message and at the svrmgr> prompt simply enter

SVRMGR> username/password@tnsconnectString

where tnsconnectString is some connect string you have setup in
the tnsnames.ora file in the OAS Oracle Home.
Review &
      Followup

      Rating: 1
I can?t login
      to SQL plus in oracle  November 25, 2001
Reviewer:
      mohamed  from Egypt
Hi,
I have installed Oracle  Personal Edition   and Developer 2000 on my home PC .
They Ware Installed Successfully .

Now , I want to Make practice for First Part(SQL, PL/SQL) So I try to run SQL
PLUS 8.5 From Oracle For Windows 98 But It display this message : error
Ora-12203: TNS unable to connect to destination.  and ask me about User name ,
Password And Host String
I tried to Enter scott/tiger or system/manager But It still Ask me about User
Name, Password And Host String

I tried to check tnsnames.ora file in oracle\network\admin folder
Here is the sentence in it
Beq-local.world =
  (DESCRIPTION =
    (ADDRESS_LIST =
        (ADDRESS =
          (PROTOCOL = BEQ)
          (PROGRAM = oracle80)
          (ARGV0 = oracle80ORCL)
          (ARGS = '(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))')
        )
    )
    (CONNECT_DATA = (SID = ORCL)
    )
  )
Tcp-loopback.world =
  (DESCRIPTION =
    (ADDRESS_LIST =
        (ADDRESS =
          (PROTOCOL = TCP)
          (Host = 127.0.0.1)
          (Port = 1521)
        )
    )
    (CONNECT_DATA = (SID = ORCL)
    )
  )
TcpExample.world =
  (DESCRIPTION =
    (ADDRESS_LIST =
        (ADDRESS =
          (PROTOCOL = TCP)
          (Host = Production1)
          (Port = 1521)
        )
    )
    (CONNECT_DATA = (SID = ORCL)
    )
  )
SpxExample.world =
  (DESCRIPTION =
    (ADDRESS_LIST =
        (ADDRESS =
          (PROTOCOL = SPX)
          (Service = Server_lsnr)
        )
    )
    (CONNECT_DATA = (SID = ORCL)
    )
  )
NmpExample.world =
  (DESCRIPTION =
    (ADDRESS_LIST =
        (ADDRESS =
          (PROTOCOL = NMP)
          (Server = FinanceServer1)
          (Pipe = ORAPIPE)
        )
    )
    (CONNECT_DATA = (SID = ORCL)
    )
  )
CMExample.world =
  (DESCRIPTION=(address_list=
      (ADDRESS=
          (PROTOCOL=tcp)(PORT=1610)(HOST=CM_SERVER))
      (ADDRESS=
          (PROTOCOL=tcp)(PORT=1521)(HOST=LSNR_SERVER)))
      (CONNECT_DATA=(SID=ORCL))
         (SOURCE_ROUTE=yes)
  )
extproc_connection_data.world =
  (DESCRIPTION =
    (ADDRESS =
        (PROTOCOL = IPC)
        (KEY = EXTPROC%ORACLE_HOME_ID%)
    )
    (CONNECT_DATA = (SID = extproc)
    )
  )
what changes exactly should I do to run SQL plus?

 Are there Sites That Can Support Me In This Part?
my email :mohamed_F33@hotmail.com
please help me immediately

thanks so much.

          Followup:

Well, it'll depend on your default Oracle home and which sqlplus you are using.

Oracle8i PE will have been installed in one oracle home.  If you use the default
oracle home selector tool from the start menu and set it to the database oracle
home and run sqlplus, using "scott/tiger" will work (it'll be a direct
connection)

It is more likely that you are using the sqlplus from D2K.  This sqlplus is in a
different oracle home and uses a different version of support files.  It cannot
directly connect to your local 8i instance, rather it must use the "network" to
connect over net8 (even on your single machine).

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:447620106871
for details.

      Rating:
      5
oracle_home  October 16, 2003
Reviewer:
      atul  from India
Hi Tom,

If a database is running on 8.0.6 and if we upgrade it
to 8.1.7..

Then ,
1)Can we open database again by using 8.0.6 home?
2)Also How to recognise that this database has been upgraded?

Thanks.
Atul

          Followup:

1) you can check out the migration guide -- it has downgrade options -- but, in
general NO, once upgraded, you be upgraded.  downgrading -- very hard and only
in some cases

2) you wouldn't know?  you'd have to sort of startup and see what happens.  but
-- i would certainly hope you would "know" (if not, you need a little tighter
control on your systems maybe?)

      Rating:
      5
ORACLE_HOME  October 16, 2003
Reviewer:
      atul  from India
Hi,

I just want to know that can the database get started
with 8.0.6 home as well as 8.1.7 ..
Once we finish upgrade..

Thanks.
Atul

          Followup:

        "no"

October 17, 2003 sunil -- Thanks for the question regarding "SGA
      question regarding the size", version

      You Asked
I am a new profession to oracle. i am under the
impression that total SGA
size is sum of some
fixed_code_size+shared_pol_size+database_buffers + log_buffers
But when i use the Show SGA command, i get the following
information
Total System Global Area      53748956 bytes
Fixed Size         39428 bytes
Variable Size      22661848 bytes
Database Buffers      30720000 bytes
Redo Buffers        327680 bytes
i am using oracle 7.0 and kept my
db_block_buffers 15000
the database blocksize is 2K
shared_pool_size = 18000000
log_buffer = 327680
we are running the dedicated server options and
not the multithreaded option.
Now my question is the
total sga size doesnot match with the parameter which i
provided.
Does the total size depend on some other parameter also?
If yes what all parameters?

      and we said...
Many other parameters.  You can only approximate the SGA size
and it'll vary from release to release. According to my
calculations, your SGA should be about 54,497,422 and it is very
close.

The variable portion of the SGA holds all of the control
structures for the SGA itself.  The bigger the SGA, the bigger
the variable portion.

          APPROXIMATING SGA SIZE AND SHOWING EXISTING SGA

To approximate the size of the SGA (Shared Global Area), use the
following formula:

( (db_block_buffers * block size) + shared_pool_size +
                                              log_buffers) /.9

Example

      From the init<SID>.ora
      DB_BLOCK_BUFFERS = 200
      LOG_BUFFERS = 8192
      SHARED_POOL_SIZE = 3500000

Default Block Size = 2048 bytes
The Block Size is an Operating System specific default.

db_block_buffers * block size + shared_pool_size + log_buffers
((200*2048)+3500000+8192)/.9

      409600 + 3500000 + 8192 = 3917792 bytes

dividing by 0.9 = 4,353,102 bytes or 4M

The division by .9 is used to take into account the variable
portion of the SGA -- this is only an approximation of the
actual value.

Our calculations come up to 4353102 but the actual value is
4504072(see below).

To check the actual size of the SGA, issue these commands:

      % svrmgrl
      SVRMGR> connect internal
      SVRMGR> show sga

Example of Output:

Total System Global Area       4504072 bytes <-- total size
loaded into memory
              Fixed Size         37704 bytes
           Variable Size       4048576 bytes
        Database Buffers        409600 bytes
            Redo Buffers          8192 bytes  ('log buffers')

Review &
      Followup

      Rating: 4
SGA
      sizing  July 16, 2003
Reviewer:  Don Freeman
       from Harrisburg, PA, USA
This is a good response and should be included somewhere in the Oracle 9i
documentation.  It isn't helpful to have a command SHOW SGA without somewhere
explicitly explaining what gets returned.

      Rating:
      4
  October 17, 2003
Reviewer:  vj
       from in
Dear Tom,

We are running the medium size database and the version is 9ir2.

We are not using any java stored procedures nor any interface with any other
database. It is a straight forward application processing system. What should be
the optimal size of the below 4 items

The approx no of tables would be around 125 and the average record would be
around 25000 per table. We use lot of views for reporting purpose also..

java_pool_size=52428800
large_pool_size=8388608
shared_pool_size=67108864
sort_area_size=524288

My SHOW SGA

Total System Global Area  185670316 bytes
Fixed Size                   453292 bytes
Variable Size             159383552 bytes
Database Buffers           25165824 bytes
Redo Buffers                 667648 bytes

Your help is highly appreciated..

Rgds

          Followup:

you can take the java pool size down to as small as you want (you are not using
it)

unless you are using shared server, you can take that large pool down to 1 to 4
meg as well.

you might give that java pool memory to the buffer cache.  shared pool looks
reasonable (use statspack to see how well you are doing there)

but -- it is a function of the amount of ram on the machine as well. that is an
unknown.

October 17, 2003 Chandra Mohan -- Thanks for the question
      regarding "Unable to Use Oracle 8.1.6 Features", version Oracle 8i
      (Version 8.1.6)

      You Asked
Hi Tom,
       I have installed Oracle 8i (Version 8.1.6) on PC on
Windows 98 Platform Using Typical Installation Procedure.
Installation was succesful. When queried from V$options table, I
see most of the options are not enabled like Partitions, and
some object features etc. Why its like that ? Is win98 version
does not all features or what ? Please tell me.

Chandra

      and we said...
It depends on what you license as to what you can use.  See

http://technet.oracle.com/doc/oracle8i_816/server.816/a76962/ch4.htm#73594
for a complete list of the options available and features
available with each release (standard edition vs enterprise
edition vs personal and so on).

OPTIONS are those things that may be licensed in addition to the
base software with each version.  For example partitioning is an
OPTION that may be licensed with enterprise and personal but NOT
standard edition.

FEATURES are those extra things the release contains by default.
 For example, the feature of "automated standby database" is
available with Enterprise and Personal but NOT standard edition.

Review &
      Followup

      Rating:
      3
  October 17, 2003
Reviewer:
      A reader
can I get link for latest version in which I can see complete list of the
options available and features available with each release?

          Followup:

otn.oracle.com -> documentation -> 9i docs -> new features guide

October 17, 2003 Subhash -- Thanks for the question regarding
      "DBMS_JOB -- how to pass parameters to the job", version 8.0.5

      You Asked
Hi Tom

   I have one procedure execute_job with two in parameters
   (batchnumber and loaddate). Procedure as follows :-

create or replace procedure execute_job(batchnumber in number,
Loaddate in varchar2)
       is
        l_theCursor     integer default dbms_sql.open_cursor;
        l_status        integer;
        p_sql           varchar2(200);
        v_job           number;
        BEGIN
          p_sql  :='alter session set nls_date_format=
''dd-mon-yyyy
          hh24:mi:ss'' ';
          dbms_sql.parse(l_theCursor,p_sql,dbms_sql.native);
          l_status := dbms_sql.execute(l_theCursor);
          dbms_sql.close_cursor(l_theCursor);
          dbms_job.submit(v_job ,  'fileimportedi.LoadFileLater
          (batchnumber);', Loaddate, null);
          commit;
END;

My problem is :-

   when i execute my procedure with following parameters :-

                Execute execute_job(347,'07-mar-2001 19:10:10');

Error comes :-

ERROR at line 1:
ORA-06550: line 1, column 117:
PLS-00201: identifier 'BATCHNUMBER' must be declared
ORA-06550: line 1, column 93:
PL/SQL: Statement ignored
ORA-06512: at "SYS.DBMS_JOB", line 72
ORA-06512: at "SYS.DBMS_JOB", line 140
ORA-06512: at "WMS.EXECUTE_JOB2", line 13
ORA-06512: at line 1

If i changed my dbms_job.submit as follows :-
      dbms_job.submit(v_job ,
'fileimportedi.LoadFileLater(347);',
      Loaddate, null);

then it's run successfully(no error comes) and job successfully
submitted in user_jobs table.

+++

Can we pass batchnumber as  parameter for another packaged
procedure or not ? if yes then how?

Thanks

Subhash

      and we said...
What you should do is to create a parameter table:

create table parameters
( jobid   number PRIMARY KEY,
  batch   number,
  <any other inputs here>
);

and you would then:

....
dbms_job.submit(v_job ,
'fileimportedi.LoadFileLater(JOB);',loadDate );
insert into parameters(v_job,batchnumber);
......

Every job can have access to its job number as a parameter like
that.  That would be used to look up the relevant data.  The
reason for doing this is so you are submitting the SAME sql
string over and over again to the job queue --

       fileimportedi.LoadFileLater(JOB);

and not:

        fileimportedi.LoadFileLater(1);
        fileimportedi.LoadFileLater(2);
        fileimportedi.LoadFileLater(3);

which would tend to trash your shared pool with lots of UNIQUE
sql.

Your loadFileLater routine should delete from the parameter upon
successful completion or you could submit:

  'fileimportedi.LoadFileLater(job);
   declare
       l_job number default job;
   begin
       delete from parameter where jobid = l_job;
   end;'

which would do it automatically for you if loadfilelater
succeeded (the delete would be skipped if the job failed)

short of that, you would have to code:

dbms_job.submit(v_job ,
              'fileimportedi.LoadFileLater(' || batchNumber ||
');',
               Loaddate, null);

but that, as i said, would generate UNIQUE sql for each job
which is BAD.

Review &
      Followup

      Rating:
      4
DBMS_JOB  May 21, 2002
Reviewer:
      Ruben  from Sanjose, CA
Hi,

I am trying to call a pl/sql procedure from a form, i want the form to unbind
after it makes the call to the pl/sql procedure as the procedure is huge and
takes a lot of time to execute, for this i want to call the procedure in a
DBMS_JOB.Please can you illustrate how to go about doing the same.(lets assume
that the procedure needed to be invoked is temp(a,b)).

          Followup:

declare
   l_job number;
begin
   dbms_job.submit( l_job, 'temp( ' || a || ',' || b || ');' );
   commit;
end;

is one way to do it.

If you plan on running temp lots with different inputs do this instead:

create table temp_parms( job_id number primary key, a number, b date );

declare
   l_job number;
begin
   dbms_job.submit( l_job, 'temp( JOB );' );
   insert into temp_parms( l_job, a, b );
   commit;
end;

and have TEMP query temp_parms by job_id -- which we passed in -- to get its
parameters (makes better use of binding and that is important)

      Rating: 4
Conditions for
      substitution of JOB  June 27, 2003
Reviewer:
      Dany  from Australia
Hi Tom,

under what conditions is the JOB keyword replaced by the actual job id?  I have
not been able to find documentation to cover this.

The test below seems to indicate that it's multiple times, selective, and
there's some magic in there!

Note that in the dbms_job the INSERT SELECT statement refers to column JOB in
the WHERE clause but that does not get replaced otherwise we would have more
than two rows in test1; the last statement shows that there are 12 other jobs
for this user.

NPIVS@MDMD.WORLD> create table test1 (job_id number, text varchar2(4000));

Table created.

NPIVS@MDMD.WORLD> get afiedt.sql
  1  declare l_job_id number := NULL;
  2  begin
  3     dbms_job.submit (l_job_id,
  4                             'DECLARE l_job NUMBER := JOB;' ||
  5                             'BEGIN ' ||
  6                                     'INSERT INTO TEST1 (job_id, text) SELECT
JOB, SUBSTR(WHAT, 1, 4000 - 39) || '' '' || JOB FROM USER_JOBS WHERE JOB = l_jo
b;' ||
  7                                     'INSERT INTO TEST1 (job_id, text) values
(JOB, ''The job id is '' || JOB);' ||
  8                                     'COMMIT;' ||
  9                             'END;');
 10* end;
NPIVS@MDMD.WORLD> /

PL/SQL procedure successfully completed.

NPIVS@MDMD.WORLD> commit;

Commit complete.

NPIVS@MDMD.WORLD> -- Some time passes
NPIVS@MDMD.WORLD> -- Check job is complete
NPIVS@MDMD.WORLD> select job, what, this_date, next_date, failures from
user_jobs where what like 'DECLARE l_job %';

no rows selected

NPIVS@MDMD.WORLD> -- Job has completed so it had no failures
NPIVS@MDMD.WORLD> select * from test1;

    JOB_ID
----------
TEXT
------------------------------------------------------------
   1585450
DECLARE l_job NUMBER := JOB;BEGIN INSERT INTO TEST1 (job_id,
 text) SELECT JOB, SUBSTR(WHAT, 1, 4000 - 39) || ' ' || JOB
FROM USER_JOBS WHERE JOB = l_job;INSERT INTO TEST1 (job_id,
text) values (JOB, 'The job id is ' || JOB);COMMIT;END; 1585
450

   1585450
The job id is 1585450

NPIVS@MDMD.WORLD> select count(*) from user_jobs where 45 = 45;

  COUNT(*)
----------
        12

NPIVS@MDMD.WORLD>

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:9088073430469#9099572884547
it is very consistent actually.

I don't see what in your test leads you to believe in magic?   You submit a job
that inserts 2 rows -- and it did.  It ran a query:

insert into ...
select ... from user_jobs where JOB = :x

(job being a COLUMN in a database table in that context, not a PLSQL variable)

Your standalong query with 45=45 is the same as where 1=1 or simply:

select count(*) from user_jobs;

      Rating: 4
Re: Conditions
      for substitution of JOB  June 28, 2003
Reviewer:
      Dany  from Australia
Okay thanks for that.

I had incorrectly thought that it was some sort of string substitution but your
reference indicates it is the standard PL/SQL re-writing of SQL statements to
use bind variables.

Cheers
Dany

      Rating: 5
question on
      dbms_job  October 17, 2003
Reviewer:
      A reader
I understand that dbms_jobs are executed as anonymous pl/sql blocks. There is a
claim being made in our team
that these anonymous pl/sql blocks are being parsed
again and again. Following is an example

DECLARE job BINARY_INTEGER := :job; next_date DATE := :mydate;
    broken BOOLEAN := FALSE; BEGIN pkg1.procedure1();
    :mydate := next_date; IF broken THEN :b := 1; ELSE :b := 0;
    END IF; END;

Is there anything wrong with the way we are using
dbms_job? Any other comments?

Thanx!

          Followup:

sure, they are getting soft parsed each time they are executed.  the job queue
process

a) queries to find jobs
b) finds the jobs
c) builds the above block
d) parse/executes it

but it is a soft parse the 2cnd, 3rd, 4th, nth time.  Should be just fine.  Is
there a percieved issue here?  I mean the jobs are not running 50 times a second
-- more like 50 seconds between runs at best (more likely much longer)

      Rating: 5
I
      agree!  October 17, 2003
Reviewer:
      A reader
Yeah, in my experiments, I have already noticed that
the subsequent parses are soft...I think it is just what
is happenning in the job that is taking time. Then
someone sees in top sql an anonymous block - euqtes it
to multiple parsing and files a bug. But I just wanted
to make sure with you that we are not doing anything
wrong here..

Thanx!

October 17, 2003 Richard -- Thanks for the question regarding "Is
      there a bug in min/max when using decode?", version 8.1.7.3

      You Asked
Hi Tom.

Please take a look at the following and let me know if either I'm
doing something wrong or if Oracle is...

[SQL]> create table foo (f1 number);

Table created.

[SQL]> insert into foo values (-3350);

1 row created.

[SQL]> insert into foo values (-2200);

1 row created.

[SQL]> select min(f1) from foo;

   MIN(F1)
----------
     -3350

[SQL]> select max(f1) from foo;

   MAX(F1)
----------
     -2200

So far, so good.

[SQL]> select min(decode(f1, -1001, null, f1)) from foo;

MIN(DECODE(F1,-1001,NULL,F1))
----------------------------------------
-2200

[SQL]> select max(decode(f1, -1001, null, f1)) from foo;

MAX(DECODE(F1,-1001,NULL,F1))
----------------------------------------
-3350

Suddenly, it appears to be taking the absolute value of field f1.
But, I can further complicate the situation and show that this is
not the case....

[SQL]> insert into foo values (1);

1 row created.

[SQL]> select min(decode(f1, -1001, null, f1)) from foo;

MIN(DECODE(F1,-1001,NULL,F1))
----------------------------------------
-2200

[SQL]> select max(decode(f1, -1001, null, f1)) from foo;

MAX(DECODE(F1,-1001,NULL,F1))
----------------------------------------
1

It appears that min/max are not selecting the correct numbers when
used with decode. Is this a bug? Or a feature?
:-)

--- Rich

      and we said...
Feature.

Suppose you want to know why as well.  Ok.  This example will help us see why:

ops$tkyte@ORA815.US.ORACLE.COM> create table t ( x int );

Table created.

ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM> insert into t select rownum - 10
  2  from all_users where rownum < 20;

14 rows created.

ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM> select x, decode( x, -1000, null, x )
  2  from t;

         X DECODE(X,-1000,NULL,X)
---------- ----------------------------------------
        -9 -9
        -8 -8
        -7 -7
        -6 -6
        -5 -5
        -4 -4
        -3 -3
        -2 -2
        -1 -1
         0 0
         1 1
         2 2
         3 3
         4 4

14 rows selected.

that should start to give us a clue -- select x, right justified (it is a
number), select decode( x, -1000, NULL, ... ) -- left justified, it
apparently is a string.

Further:

ops$tkyte@ORA815.US.ORACLE.COM> select x, decode( x, -1000, null, x )
  2  from t order by 1;

         X DECODE(X,-1000,NULL,X)
---------- ----------------------------------------
        -9 -9
        -8 -8
        -7 -7
        -6 -6
        -5 -5
        -4 -4
        -3 -3
        -2 -2
        -1 -1
         0 0
         1 1
         2 2
         3 3
         4 4

14 rows selected.

sorts as a number....

ops$tkyte@ORA815.US.ORACLE.COM> select x, decode( x, -1000, null, x )
  2  from t order by 2;

         X DECODE(X,-1000,NULL,X)
---------- ----------------------------------------
        -1 -1
        -2 -2
        -3 -3
        -4 -4
        -5 -5
        -6 -6
        -7 -7
        -8 -8
        -9 -9
         0 0
         1 1
         2 2
         3 3
         4 4

14 rows selected.

sorts as a string...

So, what happens is that decode is peeking at the first returnable value -- NULL
in this case -- to decide the return datatype for this decode call (decode is
massively "overloaded").  Well, NULL according to decode defaults to VARCHAR2 --
so, that is what it decides it will return.

So, how to fix?  BE EXPLICIT:

ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM> select x, decode( x, -1000, to_number(null), x )
  2  from t;

         X DECODE(X,-1000,TO_NUMBER(NULL),X)
---------- ---------------------------------
        -9                                -9
        -8                                -8
        -7                                -7
        -6                                -6
        -5                                -5
        -4                                -4
        -3                                -3
        -2                                -2
        -1                                -1
         0                                 0
         1                                 1
         2                                 2
         3                                 3
         4                                 4

14 rows selected.

Now they are numbers again

ops$tkyte@ORA815.US.ORACLE.COM> select x, decode( x, -1000, to_number(null), x )
  2  from t order by 1;

         X DECODE(X,-1000,TO_NUMBER(NULL),X)
---------- ---------------------------------
        -9                                -9
        -8                                -8
        -7                                -7
        -6                                -6
        -5                                -5
        -4                                -4
        -3                                -3
        -2                                -2
        -1                                -1
         0                                 0
         1                                 1
         2                                 2
         3                                 3
         4                                 4

14 rows selected.

ops$tkyte@ORA815.US.ORACLE.COM> select x, decode( x, -1000, to_number(null), x )
  2  from t order by 2;

         X DECODE(X,-1000,TO_NUMBER(NULL),X)
---------- ---------------------------------
        -9                                -9
        -8                                -8
        -7                                -7
        -6                                -6
        -5                                -5
        -4                                -4
        -3                                -3
        -2                                -2
        -1                                -1
         0                                 0
         1                                 1
         2                                 2
         3                                 3
         4                                 4

14 rows selected.

and they sort right as well!

Beware the IMPLICIT CONVERSION!!!!

Review &
      Followup

      Rating: 5
Superb
      explanation.  July 12, 2002
Reviewer:  AB
       from UK

      Rating: 5
Excellent Tom
      !!! As Always !  July 12, 2002
Reviewer:
      Sunil  from Cyprus

      Rating: 5
I agree with
      the first two guys.  July 12, 2002
Reviewer:
      Robert  from USA

      Rating: 5
But how is this
      a "feature" ?  July 12, 2002
Reviewer:
      Robert  from PA, USA
Tom, Glad to run into this article, good to know this, I never thought about
implicit conversion involving NULL.
But I can't help thinking how this is a feature rather than an
inadequacy...something Oracle should be smart enough to "detect" what user wants
--- after all there's only 2 data types involved here.

I mean we give Oracle decode(x,-1000, ....) why isn't it more logicall for
Oracle to say "OK...we are dealing with number and null here, so I will return
either one" --- why "drag" varchar2 into this by defaulting ?

This "null defaulting to varchar2" does not make sense to me.

          Followup:

And we only look at the first -- NULL is a value, whose default type is varchar.
 It is the way it is.

Why do you believe it should be a number?

decode( x, -1000, 'negative one thousand', x )

is NO different to us then

decode( x, -1000, NULL, x )

would you expect us to raise an error "not a number" in the above decode????

it makes sense to me -- NULL = varchar2, unless you explicity say to_number,
to_date.  Period.

Decode is NOT looking at the input to see what type it should return -- it looks
at the DATATYPE of the first returned value (nothing more, nothing less) to
determine what type it'll return.  This demonstrates this:

scott@ORA817DEV.US.ORACLE.COM> select decode( sysdate, sysdate, -1, 'one' ) from
dual;

DECODE(SYSDATE,SYSDATE,-1,'ONE')
--------------------------------
                              -1

scott@ORA817DEV.US.ORACLE.COM> select decode( sysdate, sysdate-1, -1, 'one' )
from dual;
select decode( sysdate, sysdate-1, -1, 'one' ) from dual
                                       *
ERROR at line 1:
ORA-01722: invalid number

DECODE( DATE .....  --> does not return DATE (none of the return types are date,
it would be silly to assume date therefore...)

decode( DATE, DATE, NUMBER --> a-hah, first returned value is a NUMBER, that is
the overloaded decode we will use, the one that returns a number....

So, when the decode returns a NUMBER, it works.  When decode returns a string
that cannot be turned INTO a number, it fails....

It is

a) the way it works
b) the way it always worked
c) the way that seems rather sensible to me personally.

It is also the way it is documented to work:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a85397/expressi.htm#1016155
<quote>
Oracle automatically converts expr and each search value to the datatype of the
first search value before comparing. Oracle automatically converts the return
value to the same datatype as the first result. If the first result has the
datatype CHAR or if the first result is null, then Oracle converts the return
value to the datatype VARCHAR2.
</quote>

      Rating: 5
Best Oracle
      Support  July 12, 2002
Reviewer:  A Reader
       from USA
for both DBAs and developers!

      Rating:
      5
Fabulous  July 12, 2002
Reviewer:  Scott
      Rappoport  from San Diego, CA

      Rating:
      5
Great  July 12, 2002
Reviewer:  Doug
       from CT, USA
Great stuff.. very interesting

      Rating:
      5
  July 12, 2002
Reviewer:  chuck
      jolley  from OKC, OK USA
Good explanation.
It could been "first non-null return", but it isn't.
No problem as long as you know how it works.

          Followup:

but then:

decode( x,-1, NULL, 0, 0, 1, 'one' )

hmmm  that would break....

      Rating: 5
Oracle In Human
      Form := 'TOM'  July 12, 2002
Reviewer:
      A reader
This Guy is  Great

      Rating: 5
Thanks
      Tom  July 12, 2002
Reviewer:  Richard

Great explanation. Thanks very much.

      Rating: 5
problem with
      to_number, case and inline views  December 10, 2002

Reviewer:  A reader
Hi

I searched you site and Metalink but cannot find any relevant information, this
thread seems relevant (decode with case) so I am asking here...

I have this query which runs perfect in 8.1.7 and 9.0.2

select case when to_number(value) > 3600
        then value
        else '1'
        end val
   from sys.v_$parameter
  where name = 'log_checkpoint_timeout'

if I put this query as an inline view such as

select a.val
from
(select case when to_number(value) > 3600
        then value
        else '1'
        end val
   from sys.v_$parameter
  where name = 'log_checkpoint_timeout') a
where a.val != '1'

I get ORA-01722 invalid number. Why?

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:45012348053
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1299201885045
In the first query -- it must have (and you cannot COUNT on this) been doing the
predicate "where name = log_checkpoint_timeout" first AND THEN doing a to_number
on that value.

The second one is not.

The order of evaluation in SQL is totally ambigous -- purposely undefined.  You
cannot rely on anything happening in any order.

Here, the second query you wrote is the same (exactly the SAME) as:

select case when to_number(value) > 3600
        then value
        else '1'
        end val
   from sys.v_$parameter
  where name = 'log_checkpoint_timeout'
    and (case when to_number(value) > 3600
         then value
         else '1'
         end  ) != '1'

that is the query we see -- and evaluate.  We do the to_number(value) before the
name = 'log...' and that fails.

Use
   to_number( decode( name, 'log_checkpoint_timeout', value, null ) )

and you are safe.  Use that or a technique LIKE IT anytime you use to_number in
a query and there is a chance the data you are to_number'ing ISN'T a number
(which should be EVERY time -- if it were always a number, the column should
have been a number in the first place).

Ignore the existence of predicates, everything.  Pretend the to_number would be
applied to each and every row (since, as you see here -- it just might be).

And just because a query works today without anything special does not mean
it'll work tomorrow!!  as the link above shows

      Rating: 5
I thought
      inline views are resolved first???  December 10, 2002

Reviewer:  A reader
Hi

Arent inline views resolved first???

I mean if not what's the point :0

          Followup:

No, no more then "views" are resolved first.

SQL is a non-procedural language.  Don't impose procedural concepts on it in
your thinking -- it doesn't obey them.

You asked a question.

It came up with a plan.

The plan it came up with evaluated the to_number on some data they didn't want
to.  It can (and does) happen all of the time.  Bewary of any query with
to_number or to_date in it that is applied to data that might NOT be a number or
date (when you take the predicate away).

Just analyzing the table could cause a new plan to kick in.
Not analyzing the table could
Upgrading the release of Oracle could
and so on.

Do not rely on the "order of something happening" for things like this - the
order is undefined.

The point of an inline view is to make queries easier to write.   To be able to
write queries that are otherwise not possible.

They were not developed to cause them to be executed "first" or N'th.

      Rating:
      5
  December 11, 2002
Reviewer:
      Mirjana  from Barcelona

      Rating: 5
needs more
      clarification..  July 27, 2003
Reviewer:
      A reader
What do you mean by ...
"
DECODE( DATE .....  --> does not return DATE (none of the return types are date,

it would be silly to assume date therefore...)
"

Are you saying that a function cannot return a date . I dont think so.

SQL>   create or replace function test
  2   return date is
  3   l date;
  4   begin
  5   l:=sysdate;
  6   return l;
  7   end;
  8  /

Function created.

SQL> select test from dual;

TEST
---------
27-JUL-03

Can you please elaborate what you were trying to explain.

Thanks

          Followup:

I was saying that decode looks at the first RETURN type to determine the return
type. It does NOT look at the first parameter.

so

decode( date, date, number )

returns NUMBER, not DATE

decode( date, date, null )

returns VARCHAR2, not date

but

decode( date, <whatever>, date )

returns DATE

never said a function cannot return a date, just that decode -- as used -- was
NOT returning a date.

      Rating: 5
learned
      something new  October 17, 2003
Reviewer:
      Leora
For some reason when I have a decode using a date I am not getting data as such:

select MAX(DECODE(DATE_D, DAY1, hits, 0))
FROM
 (SELECT DATE_D, TO_NUMBER(DECODE(OVERALL_HITS,NULL, 0,   OVERALL_HITS)) AS
OVERALL_HITS,
   FROM TBL
  WHERE DATE_D = DAY1) T

where DAY1 is a bind variable that is assigned
 TRUNC(sysdate - 6, 'DD') - 1

The overall_hits is always 0.

What am I doing wrong???

Thanks for your time!

          Followup:

maybe overall hits is always null.

there were so many things wrong with this example tho (not to mention there is
no complete test case, like I always have -- you know, so we can run it
ourselves!)

the query doesn't compile.

i don't know your datatypes at all (why to_number() overall_hits??? for example,
seems it should already be a number)

the decode looks like "nvl" to me?  why not nvl()?

but, if I make some guesses at the fixes -- you don't always get zero if you
have data that returns something:

ops$tkyte@ORA920> create table tbl( date_d date, overall_hits int );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into tbl values ( trunc(sysdate-6, 'dd' ) -1, 1000 );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          day1 date := trunc(sysdate-6,'dd')-1;
  3  begin
  4          for x in ( select MAX(DECODE(DATE_D, DAY1, overall_hits, 0))  data
  5                   FROM (SELECT DATE_D, TO_NUMBER(DECODE(OVERALL_HITS,NULL,
0,   OVERALL_HITS)) AS
  6                                        OVERALL_HITS
  7                           FROM TBL
  8                              WHERE DATE_D = DAY1) T )
  9          loop
 10                  dbms_output.put_line( x.data );
 11          end loop;
 12  end;
 13  /
1000

PL/SQL procedure successfully completed.

See -- I get 1000

October 19, 2003 Thomas -- Thanks for the question regarding
      "Oracle Text - handling plurals", version 9.2.0

      You Asked
Hi Tom,

I've created a new Oracle Text index with a user defined datastore.  The index
works, but I'm having trouble understanding how Oracle Text handles plural vs.
singular when searching.  For example:

SQL> insert into foo (col1) values ('elephants');

SQL> select count(*) from foo
      where contains(col1, 'elephants', 1) > 0;

COUNT(*)
--------
1

SQL> select count(*) from foo
      where contains(col1, 'elephant', 1) > 0;

COUNT(*)
--------
0

Is using the 'fuzzy' operator the best (or only) way to address this:

SQL> select count(*) from foo
      where contains(col1, 'fuzzy(elephant, 70, 6, weight)', 1) > 0

COUNT(*)
--------
1

or is there something else that can be done when creating the index?  I've tried
using a wordlist preference with the STEM_FUZZY_PREF attribute set, which I
thought would obviate the need to use the 'fuzzy' operator in the contains
clause:

begin
  ctx_ddl.drop_preference('STEM_FUZZY_PREF');
  ctx_ddl.create_preference('STEM_FUZZY_PREF', 'BASIC_WORDLIST');
  ctx_ddl.set_attribute('STEM_FUZZY_PREF','FUZZY_MATCH','ENGLISH');
  ctx_ddl.set_attribute('STEM_FUZZY_PREF','FUZZY_SCORE','60');
  ctx_ddl.set_attribute('STEM_FUZZY_PREF','FUZZY_NUMRESULTS','5000');
  ctx_ddl.set_attribute('STEM_FUZZY_PREF','SUBSTRING_INDEX','TRUE');
  ctx_ddl.set_attribute('STEM_FUZZY_PREF','STEMMER','ENGLISH');
end;

but that doesn't have any effect.  BTW, what does the 'contains' clause in the
search functionality of your asktom application look like?

Thanks much!

      and we said...
I think you just want to use a stem search

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/text.920/a96518/cqoper.htm#14499
select count(*) from foo
      where contains(col1, '$elephant', 1) > 0;

will do that out of the box.

If you do a search on asktom -- I print out the contains clause right on the
"hit" page, just do a search and it'll be there.
Review &
      Followup

      Rating: 4
Using
      stems...  December 02, 2002
Reviewer:  Thomas
      Barry  from Rochester, NY
Tom- thanks for the response.  What would be your philosophy for providing
'stem' capable searches for web users who won't necessarily know about the
different search constructs that Oracle Text offers?  Here's an example of the
functionality I'd like to provide without requiring the user to enter '$word':

Requirement: Allow the user to search our site for 'tablespace'.  The search
results should be basically the same regardless of whether the user enters
'tablespace' or 'tablespaces'

We could certainly massage the search string provided by the user to prefix
search keys with '$', and maybe do nothing if Oracle Text constructs are
supplied.  I'd be curious to know if this is how you might handle such a
requirement...

Thanks for this terrific resource!

          Followup:

That is how I would do it -- in fact that is exactly how i do do it.

You type in a string - i removed unwanted characters, look for " " quoted
strings and preserve them, wrap each term in a {} and then put it back together.

I would anticipate you would have a checkbox

  [] use plurality <or something to that effect>

and upon seeing that checked, you would put {$word}  in there instead of just
{word}

      Rating: 5
Indexing of new
      data  October 19, 2003
Reviewer:  Martin
       from UK
Hi Tom,

I've learned a lot from this thread about oracle text, but i've got a small
problem which is probably easy to remedy, but I can't find anything in the
documentation regarding this (but it's problem just me!).

Basically, new data does not get returned from the query unless I recreate the
index. Is there some mechanism I'm missing for refreshing the index before I
issue the query?

This is 9.2.0.4.0.

SQL> SELECT * FROM t;

A
----------------
XYZ
W X YZ
X Y Z

SQL> create index t_ctx on t(a) indextype is ctxsys.context;

Index created.

SQL> SELECT * FROM T WHERE CONTAINS(a, 'X') > 0;

A
----------------
X Y Z
W X YZ

Ok, now insert a new row which should get returned.

SQL> INSERT INTO t VALUES ( 'V W X Y' );

1 row created.

SQL> SELECT * FROM T WHERE CONTAINS(a, 'X') > 0;

A
----------------
X Y Z
W X YZ

Recreate the index....

SQL> DROP INDEX t_ctx;

Index dropped.

SQL> create index t_ctx on t(a) indextype is ctxsys.context;

Index created.

SQL> SELECT * FROM T WHERE CONTAINS(a, 'X') > 0;

A
--------------------------------------------------------------------------------
V W X Y
X Y Z
W X YZ

As I said before, it's probably something I'm missing, but any advice would be
much appreciated.

Regards

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1086033238621#13120451359967

October 19, 2003 UmaMahesh -- Thanks for the question regarding
      "OS Authentication", version Oracle 8i 8.1.5

      You Asked
Hi Tom

I am in the process of learning DBA concepts.

How to Give OS authentication to an user ? Please give the steps
involved including INIT.ORA parameters.

Thanks in advance

Uma

      and we said...
There are no init.ora parameters other then the optional

os_authent_prefix

I like to set mine to OPS$ (OPerating System).  Then I create a
user:

create user ops$tkyte identified externally;

Now I can:

$ sqlplus /

when logged into unix as the user tkyte.  I'll be ops$tkyte in
the database.
Review & Followup

      Rating: 3
A
      Reader  May 29, 2002
Reviewer:  Rahul Sharma
       from India
Hi Tom

your answer is good and work on UNIX as you told but it don't work on NT.

I created a user

create user ops$rahul identified externally;

after it i login as rahul user and then try

sqlplusw /

it gives me ORA-01017: Invalid username/password invalid: logon denied.

so tell me how can i implement this thing in NT environment.

Thanks.

          Followup:

Read

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/win.817/a73008/ch7.htm#1036322
you need to have a specific setup on windows.

      Rating:
      4
REMOTE_LOGIN_PASSWORDFILE  March 20, 2003

Reviewer:  A reader
Hi

in 81.7 admin guide it says we ave to set REMOTE_LOGIN_PASSWORDFILE to none when
use OS anthentification, how so?

I have created an user called scott identified externally, I set
REMOTE_LOGIN_PASSWORDFILE to exclusive and it still works. Also whats the point
of prefix? Mine is SCOTT in OS and database and it works without touchig of
init.ora parameters. Or the prefix is just to stating the users with the prefix
are OS authentificated? (something to distinguish)

          Followup:

it is more like "if using os authentication exclusively"

if you set to none, you cannot remotely log in, you would be ON the server using
os authentication.

ops$ is just an identifying prefix.

      Rating: 5
ah so ops can
      be bypassed I uess  March 20, 2003
Reviewer:
      A reader
hi thx for the reply

so I guess ops$ is just some dummy identifier! and remote_login_passwordfile can
be set to exclusive

the doco didnt explain very well :0

          Followup:

well, yes and no about the ops$ thing.

say you don't use ops$.  Now an OS user SCOTT can just sqlplus / into your
system.  An OS user SYS or SYSTEM can do the same.  Do you want someone to be
able to create an OS user and just come in???

I use ops$ because that means ONLY the db accounts that being with OPS$ are OS
authenticated.  SYS, SYSTEM, et. al.  are not OS authenticated, you need their
passwords.  And since we all know you don't actually use those accounts -- thats
OK. (good in fact)

      Rating:
      4
prefix  March 20, 2003
Reviewer:
      A reader
Hi

If I know there is an user called OPS$SCOTT in the database I can still get into
the database with os user SCOTT no?

The use of a prefix to me is just to avoid people get into the database by
creating standard database username (such as dbsnmp, sys, system) at OS level,
is that right?

          Followup:

Yes, but tell me - do you have OPS$SYS or OPS$SYSTEM or OPS$CTXSYS or .....

No, you don't...

That is right (and was my point above)

      Rating: 1
Operating
      system user for wondows  October 19, 2003

Reviewer:  Vivek Nema  from Switzerland
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/win.817/a73008/ch7.htm#1036322
link is no longer valid.

Its difficult to find where the exact document is ?

I am looking for configuring the Operating system login mechanism on windows XP
and Windows 2000.

          Followup:

http://download-west.oracle.com/docs/cd/A87861_01/NT817EE/win.817/a73008/ch6.htm#1036322

October 20, 2003 M -- Thanks for the question regarding "Oracle 9i
      generates statistics automatically?", version 9i

      You Asked
We are using Oracle 9i database. We want to use Cost Based Optimiser, so need to
generate statistics for objects. I was wondering if Oracle 9i automatically
generates statistics whenever schema/data get updated. If not, do we need to
generate statics manually whenever schema/data get updated. What is the best way
to do it? (using a procedure, trigger ? using ANALYZE command or something else?
how often to do it? ...)

Thanks for any help

      and we said...
search this site for

dbms_stats monitoring

to read some more info.

I would advise this:

o alter your tables to be "monitoring".  we'll record the effects of changes to
tables in the dictionary.

o use dbms_stats to collect statstics

o use the gather stale option to gather statistics on tables -- this works in
conjunction with the monitoring to gather stats only on objects that NEED it.

o use dbms_job to schedule the stats collection to happen when you want it to
(eg: late at night when the load it low)

o if you use the gather stale, you can do it as frequently as you like since
only "stale" objects will be analyzed.

Review &
      Followup

      Rating: 1
No examples on
      how to use it  May 13, 2002
Reviewer:
      reader  from England
You should have given examples on how to use it

          Followup:

Sorry, my crystal ball is in the shop -- hence my inability to read your mind
and deliver exactly what you wanted.

Read

http://technet.oracle.com/docs/products/oracle9i/doc_library/901_doc/appdev.901/a89852/dbms_sta.htm#999107
It goes through this and has examples as well.

      Rating: 5
Can't believe
      it...  May 13, 2002
Reviewer:  Jan van
      Mourik  from Houston, TX
Mr. Tom, you are slacking... No examples! Are we now supposed to do some of the
research ourselves?!?

Can't believe how demanding some people are! All of us should be grateful for
any and all the time Mr. Kyte spends answering our questions, day in day out,
even in the weekend. And maybe sometimes we have to do some research
ourselves... Isn't that our job anyway???

      Rating: 5
Where is my
      application...?  May 13, 2002
Reviewer:
      Connor  from UK
"Tom - I need my system designed, my research done, my application coded, my
test plan created, and the system implemented.  Your site says 'sorry no
questions' too often - can you please have all my work done for me by the end of
the day"

Man - I cannot believe the audacity of some people...

Back on the original topic, I hear rumours that 9i.2 will do some form of
on-the-fly statistics gathering if required (ie certain parameters set).  Can
you confirm/deny?

Cheers
Connor

          Followup:

(you should see some of the email I get, makes this look tame)

I believe you mean the more intense stats collection that goes on in order to
find problem statements:

<quote>
Setting the Level of Statistics Collection

Oracle9i Release 2 (9.2) provides the initialization parameter STATISTICS_LEVEL
, which controls all major statistics collections or advisories in the database.
This parameter sets the statistics collection level for the database.

Depending on the setting of STATISTICS_LEVEL, certain advisories and statistics
are collected, as follows:

BASIC: No advisories or statistics are collected.

TYPICAL: The following advisories or statistics are collected:

    * Buffer cache advisory
    * MTTR advisory
    * Shared Pool sizing advisory
    * Segment level statistics
    * PGA target advisory
    * Timed statistics

ALL: All of the preceding advisories or statistics are collected, plus the
following:

    * Timed operating system statistics
    * Row source execution statistics

The default level is TYPICAL. STATISTICS_LEVEL is a dynamic parameter and can be
altered at the system or the session level.

When modified by ALTER SYSTEM, all advisories or statistics in the preceding
list are dynamically turned on or off, depending on the new value of
STATISTICS_LEVEL.

When modified by ALTER SESSION, only the following advisories or statistics are
turned on or off in the local session only. Their systemwide state is not
changed.

    * Timed statistics
    * Timed operating system statistics
    * Row source execution statistics
</quote>

Or you might mean:

<quote>

Dynamic Sampling for the Optimizer

The purpose of dynamic sampling is to improve server performance by determining
more accurate selectivity and cardinality estimates. More accurate selectivity
and cardinality estimates allow the optimizer to produce better performing
plans.

You can use dynamic sampling to:

    * Estimate single-table predicate selectivities when collected statistics
cannot be used or are likely to lead to significant errors in estimation.
    * Estimate table cardinality for tables without statistics or for tables
whose statistics are too out of date to trust.

The DYNAMIC_SAMPLING hint lets you control dynamic sampling to improve server
performance by determining more accurate selectivity and cardinality estimates.
You can set the value of DYNAMIC_SAMPLING to a value from 0 to 10. The higher
the level, the more effort the compiler puts into dynamic sampling and the more
broadly it is applied. Sampling defaults to cursor level unless you specify a
table.
See Also:

    * "Dynamic Sampling" for information about when and how to use dynamic
sampling
    * "DYNAMIC_SAMPLING" for details about using this hint

......

Dynamic Sampling

The purpose of dynamic sampling is to improve server performance by determining
more accurate selectivity and cardinality estimates. More accurate selectivity
and cardinality estimates allow the optimizer to produce better performing
plans.

You can use dynamic sampling to:

    * Estimate single-table predicate selectivities when collected statistics
cannot be used or are likely to lead to significant errors in estimation.
    * Estimate table cardinality for tables without statistics or for tables
whose statistics are too out of date to trust.
    * How Dynamic Sampling Works

The primary performance attribute is compile time. Oracle determines at compile
time whether a query would benefit from dynamic sampling. If so, a recursive SQL
statement is issued to scan a small random sample of the table's blocks, and to
apply the relevant single table predicates to estimate predicate selectivities.
The sample cardinality can also be used, in some cases, to estimate table
cardinality.

Depending on the value of the OPTIMIZER_DYNAMIC_SAMPLING initialization
parameter, a certain number of blocks are read by the dynamic sampling query.

</quote>

      Rating: 5
Consultancy for
      nothing...  May 13, 2002
Reviewer:  Andre
      Whittick Nasser  from Brazil
I myself get upset when I receive some messages from so-and-so asking: "Would
you please design a system for ... ", about 40 lines long. And this is no
kidding.

      Rating: 5
IMP ...
      STATISTICS=SAFE  May 13, 2002
Reviewer:  Andre
      Whittick Nasser  from Brazil
Well, every now and then I happen to find these "vague" terms in Oracle
literature.

Oracle says the SAFE option of the ANALYZE parameter imports statistics that are
"not questionable"; otherwise, Oracle recalculates them.

How is that ?

Thanks

          Followup:

When are statistics questionable ?

The precalculated optimizer statistics are flagged as questionable at export
time if:

   -> There are row errors while exporting
   -> The client character set or NCHAR character set does not match the server
      character set or NCHAR character set
   -> A QUERY clause is specified
   -> Only certain partitions or subpartitions are exported

      Rating: 5
dynamic
      sampling  May 12, 2003
Reviewer:  PINGU

hi

what is dynamic sampling for, I have been reading the doc over and over again
about 5 times and I still dont see what is useful for...?

          Followup:

search for optimizer_dynamic_sampling

it basically lets the sql optimizer process interrogate the database table that
is not analyzed but used in a query with other tables that are before parsing
the query.

In that fashion, the database "can have a clue" as to the statistics regarding
the unanalyzed table.

      Rating: 5
sooooo only
      useful  May 12, 2003
Reviewer:  PINGU

when I dont analyze my tables I guess? If that´s the case I am fine! I always
analyze my tables and indexes :-D

          Followup:

global temporary tables...
DUAL...

      Rating: 5
DBMS_STATS vs.
      ANALYZE  May 14, 2003
Reviewer:  Arun Gupta
       from Harrisburg, PA USA
Tom
Though Oracle recommends use of DBMS_STATS to gather statistics, I had a strange
experience. Using Oracle 9.2.0.3, we use DBMS_STATS to gather schema statistics
on a nightly basis. One day, suddenly the CPU utilization on a 4 CPU Sunfire 15k
went up to 25% on all CPUs. I looked at the sessions through OEM and found that
three sessions were marked as active. All these sessions were executing the same
SQL for last several hours. I extracted the SQL and ran it from SQL*Plus with
the same parameters as entered from front end. The query returned results within
2 seconds. I did this 100 times in a loop, no problem. I had personally tuned
the query and saved the plan and execution statistics. Comparison yielded no
difference. The same search fired from the front end just hanged up. The
sessions showed that LIO and PIO were steadily increasing. The sessions were not
waiting on a lock, the queries are select statements. I killed the sessions, CPU
utilization went down to 5%. This kept happening for several days. One day, as I
was investigating, a colleague said "analyze the tables". I checked the last
analyzed date on tables, indexes and columns. They were analyzed the night
before. Still, I issued the analyze table compute statistics against the three
underlying tables. The problem disappeared immediately!! My question here is
that does dbms_stats miss out something which analyze puts back?

Thanks

          Followup:

did you compare the before/afters of the dictionary tables?

were the tables partitioned?

and most importantly -- what parameters did you use for dbms_stats.

      Rating: 4
Analysis not
      required?  May 15, 2003
Reviewer:  Tony
       from INDIA
Do you meant to say that statistics collection using Analyze or dbms_stats is
not at all required
if  optimizer_dynamic_sampling is enabled?

          Followup:

did i say that?

I'm saying for tables that are not analyzed for whatever reason (global
temporary tables pop into mind), this will help correct that.

do you really want to in effect analyze all of your objects for each parse?  or
would it be better to analyze once and use it over and over....

      Rating: 5
confused about
      gather stale and auto ...  October 10, 2003

Reviewer:  Reader
Tom, could you please help me in understanding the difference between gather
stale and gather auto feature in gathering schema level stats? Thanks.

          Followup:

gather auto does what STALE does PLUS it gathers stats on tables that have no
stats.

both require monitoring to be enabled on the tables to work properly.

      Rating: 5
gather stale
      and auto procedures  October 11, 2003
Reviewer:
      Sam Bernard  from Salt Lake City, UT
Do these work only at the schema level? I mean if I wanted to use these
procedures for individual tables in a schema after monitoring flags set to on
and let oracle gather stats, can I? Thanks.

          Followup:

documentation says....

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96612/d_stats2.htm#1003997
you can do it at the database level as well.

it does really "make sense" to do at the "table" level.

      Rating:
      5
Thanks  October 11, 2003
Reviewer:  Sam
      Bernard
Just for curiosity, in which data dictionary table(s), oracle stores the
statistics for tables, indexes? Thanks.

          Followup:

the view you need is user|dba|all_indexes  or user|dba|all_tables

if you are truly curious -- select the view text from all_views to see where the
stuff "really" comes from.

      Rating: 5
Thanks. I did
      and it is amazing! Thanks again for the info.  October 11,
      2003
Reviewer:  Sam Bernard

      Rating: 5
Setting For
      GTT's - 9.2.0.3  October 19, 2003
Reviewer:
      Steve  from New York
Tom,
What value for optimizer_dynamic_sampling would you recommend when using Global
Temporary Tables?
Thanks.

          Followup:

at least 2 -- in order to get all unanalyzed tables (the GTT in this case) to be
sampled (since 0 disables this and 1 doesn't do anything if an index exists)

October 20, 2003 KK -- Thanks for the question regarding "Export
      the report to Excel format in OAS", version 4.0.8

      You Asked
In webdb, I can save the report in the Excel format,
can I call the relevant procedures to generate my report
into the Excel format in OAS?

      and we said...
Well, webdb procedures can be run under OAS using the PLSQL
cartridge so that is one method....

Another is just to use the HTP and OWA_UTIL packages yourself.

For example:

   ...
   owa_util.mime_header( 'application/excel' );
   for x in ( select empno, ename from emp ) loop
      htp.p( x.empno || chr(9) || x.ename );
   end loop;
   ....

That will do an equivalent thing with the plsql cartridge.  The
browser will run excel and feed it the tab separated data.
Review &
      Followup

      Rating: 5
how to Invoke
      more work sheets by using owa_util.mine_header.  October 20,
      2003
Reviewer:  suresh  from India
 i am generating excel-report through forms,
  i am invoking excel by using this command
    OWA_UTIL.MIME_HEADER('application/vnd.ms-excel');

  it Invoke Only One work sheet,How to Invoke more work sheets ? pls reply
immd..

          Followup:

my immediate reply is -- i don't know how to create proprietary spreadsheet
formats such as ms-excel.  you'd have to ask MS how to do that.

October 20, 2003 Jens -- Thanks for the question regarding
      "Advanced Queuing & PL/SQL Notification", version 9.2

      You Asked
hello tom,

I hope you don't get angry now, if I "redirect" you to
http://asktom.oracle.com/pls/ask/f?p=4950:61:166184132340407832::::P61_ID:442119935770
there is MY MOST important question. TODAY I've got your book - but
unfortunately I haven't found hints or even solutions for THAT problem in it
(yet).
could you  P L E A S E  show me the way completing my sample (or giving any
other useful help)? that would be very, very kind of you.

thanks a lot for all your support.

      and we said...
Here is an example of an asyncronous dequeue of a message from a queue using
PLSQL.

(just a reminder -- i've got a life too, sometimes camping without a laptop and
not concocting examples is something I do too.  I did say I was sort of busy and
would try to get you an example when I could)...

Anyway -- here it is:

aq@ORA920> @connect "/ as sysdba"

sys@ORA920> drop user aq cascade;
User dropped.

sys@ORA920> CREATE USER aq IDENTIFIED BY aq;
User created.

sys@ORA920> GRANT CONNECT, RESOURCE, aq_administrator_role TO aq;
Grant succeeded.

sys@ORA920> GRANT EXECUTE ON dbms_aq TO aq;
Grant succeeded.

sys@ORA920> GRANT EXECUTE ON dbms_aqadm TO aq;
Grant succeeded.

sys@ORA920> begin
  2     dbms_aqadm.grant_system_privilege
  3     ('ENQUEUE_ANY','AQ',FALSE);
  4     dbms_aqadm.grant_system_privilege
  5     ('DEQUEUE_ANY','AQ',FALSE);
  6  end;
  7  /
PL/SQL procedure successfully completed.

sys@ORA920> @connect AQ/AQ

aq@ORA920> CREATE type aq.Message_typ as object
  2  ( subject VARCHAR2(30), text VARCHAR2(80));
  3  /
Type created.

that'll be our message payload..

aq@ORA920> begin
  2      DBMS_AQADM.CREATE_QUEUE_TABLE
  3      ( queue_table => 'aq.objmsgs80_qtab',
  4        queue_payload_type =>  'aq.Message_typ',
  5        multiple_consumers => TRUE );
  6
  7      DBMS_AQADM.CREATE_QUEUE
  8      ( queue_name => 'MSG_QUEUE',
  9        queue_table => 'aq.objmsgs80_qtab');
 10
 11      DBMS_AQADM.START_QUEUE
 12      (  queue_name => 'MSG_QUEUE');
 13  end;
 14  /
PL/SQL procedure successfully completed.

aq@ORA920> create procedure enqueue_msg( p_msg in varchar2 )
  2  as
  3   enqueue_options dbms_aq.enqueue_options_t;
  4   message_properties dbms_aq.message_properties_t;
  5   message_handle RAW(16);
  6   message aq.message_typ;
  7  BEGIN
  8     message := message_typ('NORMAL MESSAGE',  p_msg );
  9     dbms_aq.enqueue(queue_name => 'msg_queue',
 10                     enqueue_options => enqueue_options,
 11                     message_properties => message_properties,
 12                     payload => message,
 13                     msgid => message_handle);
 14  end;
 15  /
Procedure created.

a simple procedure to enqueue a message onto that queue

aq@ORA920> create table message_table( msg varchar2(4000) );
Table created.

we'll use that table to let our procedure 'communicate' with us -- our dequeue
procedure that is called automagically

aq@ORA920> create or replace procedure notifyCB( context raw,
  2                                        reginfo sys.aq$_reg_info,
  3                                        descr sys.aq$_descriptor,
  4                                        payload raw,
  5                                        payloadl number)
  6  as
  7   dequeue_options dbms_aq.dequeue_options_t;
  8   message_properties dbms_aq.message_properties_t;
  9   message_handle RAW(16);
 10   message aq.message_typ;
 11  BEGIN
 12     dequeue_options.msgid := descr.msg_id;
 13     dequeue_options.consumer_name := descr.consumer_name;
 14     DBMS_AQ.DEQUEUE(queue_name => descr.queue_name,
 15                     dequeue_options => dequeue_options,
 16                     message_properties => message_properties,
 17                     payload => message,
 18                     msgid => message_handle);
 19     insert into message_table values
 20     ( 'Dequeued and processed "' || message.text || '"' );
 21     COMMIT;
 22  END;
 23  /
Procedure created.

aq@ORA920> begin
  2      dbms_aqadm.add_subscriber
  3      ( queue_name => 'aq.msg_queue',
  4        subscriber => sys.aq$_agent( 'recipient', null, null ) );
  5  end;
  6  /
PL/SQL procedure successfully completed.

aq@ORA920> BEGIN
  2      dbms_aq.register
  3      ( sys.aq$_reg_info_list(
  4          sys.aq$_reg_info('AQ.MSG_QUEUE:RECIPIENT',
  5                            DBMS_AQ.NAMESPACE_AQ,
  6                           'plsql://AQ.notifyCB',
  7                            HEXTORAW('FF')) ) ,
  8        1 );
  9  end;
 10  /
PL/SQL procedure successfully completed.

and that should do it -- ready to go -- and we test:

aq@ORA920> select * from message_table;
no rows selected

nothing up my sleeve...

aq@ORA920> exec enqueue_msg( 'This is a test....' );
PL/SQL procedure successfully completed.

aq@ORA920> commit;
Commit complete.

commit needed, we don't commit in the enqueue procedure..

aq@ORA920> pause

aq@ORA920> select * from message_table;

MSG
-------------------------------------------
Dequeued and processed "This is a test...."

And there it is.

Review &
      Followup

      Rating: 1
Tom, I REALLY
      NEED your help.  March 22, 2003
Reviewer:
      A reader
I've no idea of how to get a PL/SQL procedure notfified, and I'm not sure if
I've understood the concept and process of notification at all. procedure has to
be "registered" - that's clear. but I thought enqueuing a message is the event
on wich the procedure is invoked. so I don't understand the purpose of "post"
then. I don't unterstand the callback function's signature. there are more
question than answers to me. could you please give me a hand and make it clear?

          Followup:

P L E A S E give me time -- i have  your question.  It is in my queue.

      Rating:
      5
  March 24, 2003
Reviewer:
      A reader
first of all I have to apologize for being so impatient ( when I saw some
answers on newer questions). I really didn't want to be unkind.
thanks a lot for that sample. its far better than any other sample I've found so
far (in addition the only complete one). now I can "play around" to get it in
more detail. btw: could you suggest special sources that cover this? thanks.

          Followup:

otn.oracle.com
- discussion forums
 - products
  - database
   - Oracle Advanced Queueing

      Rating:
      3
  March 24, 2003
Reviewer:
      A reader
it's me again, tom. now I'm trying to use POST. so first I changed namespace of
registration to anonymous:

DBMS_AQ.Register(SYS.AQ$_Reg_Info_List(SYS.AQ$_Reg_Info('AQ.MSG_QUEUE:RECIPIENT',
 DBMS_AQ.NAMESPACE_ANONYMOUS, 'lsql://AQ.NotifyCB', HEXTORAW('FF'))), 1);

knowing why 'hextoraw('FF')' - taken from your sample - is appropriate for
'context' parameter (and when it is not) is one of the things I haven't found in
any documentation so far. the same is true for parameter 'payload' of POST as
well. I expected to be able to get "Message_Typ" utilized instead of that raw
...

subscriptions for multiconsumer queues contain schema, queue name and consumer
name, namespace has to be anonymous in order to get notifified via POST. so
after all I posted:

declare
  pl SYS.AQ$_POST_INFO_LIST := SYS.AQ$_POST_INFO_LIST();

begin
  pl.Extend(1);
  pl(pl.COUNT) := SYS.AQ$_POST_INFO('AQ.MSG_QUEUE:RECIPIENT',
DBMS_AQ.NAMESPACE_ANONYMOUS, 'plsql://AQ.notifyCB', HEXTORAW('FF'));

  DBMS_AQ.POST(pl, 1);
end;

but finally nothing happened - so there are some questions left: shouldn't the
registered callback be invoked? should POST have placed a message with content
'FF' hex in the given queue? and what 's the difference to enqueue then? is it
about the namespaces - what is their purpose in general? what went wrong? why
using the default NULL as value for 'payload' within AQ$_POST_INFO object
terminats the session during POST?

          Followup:

what are you trying to use POST, what's the goal there?

I mean -- what is the problem you are trying to solve...

      Rating:
      2
  March 25, 2003
Reviewer:
      A reader
first of all I just try to understand the difference between notification on
enqueuing events and post.
the purpose of post (and anonymous subscriptions as well) is not clear to me.
generally speaking I want to know, when and how to use them (instead ot the
other?).

          Followup:

for this -- i'll suggest you try out the discussion forums on otn.oracle.com.  I
am not expert in AQ at this level.

      Rating:
      1
  March 26, 2003
Reviewer:
      A reader
thanks, tom.

now - with your help - I'm at least quite sure, that I didn't miss any important
source of information (it 's hard to believe). hope that somebody out there can
give me a hand on those topics ...

      Rating:
      2
  March 30, 2003
Reviewer:
      A reader
hello tom,

I'm now trying to propagate messages from one queue to another. therefore I
modified the above sample like that:

connect "/ as sysdba"

drop user aq cascade;
CREATE USER aq IDENTIFIED BY aq;
GRANT CONNECT, RESOURCE, aq_administrator_role TO aq;
GRANT EXECUTE ON dbms_aq TO aq;
GRANT EXECUTE ON dbms_aqadm TO aq;

begin
  dbms_aqadm.grant_system_privilege('ENQUEUE_ANY','AQ',FALSE);
  dbms_aqadm.grant_system_privilege('DEQUEUE_ANY','AQ',FALSE);
end;
/

connect AQ/AQ
CREATE type aq.Message_typ as object(subject VARCHAR2(30), text VARCHAR2(80));
/

begin
  DBMS_AQADM.CREATE_QUEUE_TABLE(queue_table => 'aq.objmsgs80_qtab',
                                queue_payload_type =>  'aq.Message_typ',
                                multiple_consumers => TRUE);
  DBMS_AQADM.CREATE_QUEUE(queue_name => 'MSG_QUEUE',
                          queue_table => 'aq.objmsgs80_qtab');
  DBMS_AQADM.START_QUEUE(queue_name => 'MSG_QUEUE');
end;
/

-- ADDED: Setup additional queue to propagate messages to
begin
  DBMS_AQADM.CREATE_QUEUE_TABLE(queue_table => 'aq.objmsgs80_qtabX',
                                queue_payload_type =>  'aq.Message_typ',
                                multiple_consumers => TRUE);
  DBMS_AQADM.CREATE_QUEUE(queue_name => 'MSG_QUEUEX',
                          queue_table => 'aq.objmsgs80_qtabX');
  DBMS_AQADM.START_QUEUE(queue_name => 'MSG_QUEUEX');
end;
/

create or replace procedure enqueue_msg(p_msg in varchar2)
as
  enqueue_options dbms_aq.enqueue_options_t;
  message_properties dbms_aq.message_properties_t;
  message_handle RAW(16);
  message aq.message_typ;
  recipients DBMS_AQ.aq$_recipient_list_t;

BEGIN
  -- ADDED
  recipients(1) := SYS.aq$_agent('RECIPIENT', null, null);
  message_properties.recipient_list := recipients;

  message := message_typ('NORMAL MESSAGE',  p_msg );
  dbms_aq.enqueue(queue_name => 'msg_queue',
                  enqueue_options => enqueue_options,
                  message_properties => message_properties,
                  payload => message,
                  msgid => message_handle);
end;
/

begin enqueue_msg('This is a test....'); commit; end;
/

-- ADDED: Create loopback database link
create database link AQ.LoopBack connect to AQ identified by AQ using 'ORCL';

-- ADDED: Setup scheduling for messages
begin DBMS_AQADM.Schedule_Propagation(Queue_Name  => 'MSG_QUEUE',
                                      Destination => 'AQ.LOOPBACK',
                                      Start_Time  => sysdate,
                                      Latency     => 0);
end;
/

-- Check scheduling: Everything checked out OK.
select * from user_queue_schedules;

begin enqueue_msg('This should be propagated.'); commit; end;
/

-- Check scheduling: Neither error nor action reported
select * from user_queue_schedules;

begin
  DBMS_AQADM.stop_queue('MSG_QUEUE');
  DBMS_AQADM.drop_queue('MSG_QUEUE');
  DBMS_AQADM.drop_queue_table('aq.objmsgs80_qtab');
  DBMS_AQADM.stop_queue('MSG_QUEUEX');
  DBMS_AQADM.drop_queue('MSG_QUEUEX');
  DBMS_AQADM.drop_queue_table('aq.objmsgs80_qtabX');
end;
/

I ran this script several times and examined both queues via enterprise manager
console, expecting all messages from queue MSG_QUEUE to be propagated to
MSG_QUEUEX.

but there was no content shown for the second queue. user_queue_schedules didn't
report any problems, but sometimes enterprise manager did: although the db-link
works very well "advanced queues/administration/topology" reported "db-link with
errors" (as a red dotted line) - but in addition 0 as error-count. this only
occured, if a db-link was given for parameter DESTINATION. otherwise a message
like "no propagation has been established for queues within this database"
appeared, even if there was one (and this could be seen under "propagation
plans" as well).

now, I've once again no idea, how to go on. could you please help once more?

thank you.

          Followup:

I asked Sean Dillon, a technologist on my team who knows AQ, to take a look at
this and here's what he had to say:
---------

So there are few things we can address.  First of all, queue propagation doesn't
happen immediately.  There is a bit of delay when you enqueue the first message
into a queue that has an enabled queue schedule.  Once the first message goes,
it's very fast because the job queue process is waiting for the next message.
So the script above, even if everything was correct, would not see the message
get propagated because you immediately stop and drop the queues before they have
an opportunity to propagate.

When deciding how you will propagate a message from queue A to queue B, you have
a couple of choices.  You can either specify a subscriber on the queue, which
means all message queued to the message should be send, you can specify a
rule-based subscriber on the queue, which means messages that conform to the
rule will be sent, or you can specify a recipient list in the message itself.
In your example above, you are specifying a recipient list.  This means the only
way Oracle knows who the message is intended for is via the recipient list.  You
can schedule propagation (as you have done), but this doesn't mean all the
messages will be propagated (as you have seen :-)).

What I have done is added a parameter to your enqueue_msg procedure for the
agent address.  When you are instantiating an aq$_agent for the recipient_list,
I pass this parameter in the ADDRESS field of the aq$_agent object.  This lets
you enqueue to a null address or to a remote queue.  Then, when I enqueue the
'This is a test....' message, it will never be propagated because it's agent
address is NULL.  When I enqueue the 'This should be propagated.' message, it
WILL BE propagated because I pass 'aqtest.MSG_QUEUEX@aqtest.loopback' as the
address.

Here's the complete example, with edits:

----------------------
-- SMD: i moved this up so my queues would exists AFTER the example had run.
-- SMD: it will fail the first time you run this script.
connect aqtest/aqtest

begin
  DBMS_AQADM.stop_queue('MSG_QUEUE');
  DBMS_AQADM.drop_queue('MSG_QUEUE');
  DBMS_AQADM.drop_queue_table('aqtest.objmsgs80_qtab');
  DBMS_AQADM.stop_queue('MSG_QUEUEX');
  DBMS_AQADM.drop_queue('MSG_QUEUEX');
  DBMS_AQADM.drop_queue_table('aqtest.objmsgs80_qtabX');
end;
/

connect sys/o9isb9 as sysdba
drop user aqtest cascade;

CREATE USER aqtest IDENTIFIED BY aqtest;
GRANT CONNECT, RESOURCE, aq_administrator_role TO aqtest;
GRANT EXECUTE ON dbms_aq TO aqtest;
GRANT EXECUTE ON dbms_aqadm TO aqtest;

begin
  dbms_aqadm.grant_system_privilege('ENQUEUE_ANY','AQTEST',FALSE);
  dbms_aqadm.grant_system_privilege('DEQUEUE_ANY','AQTEST',FALSE);
end;
/

connect aqtest/aqtest
CREATE type aqtest.Message_typ as object(subject VARCHAR2(30), text
VARCHAR2(80));
/

begin
  DBMS_AQADM.CREATE_QUEUE_TABLE(queue_table => 'aqtest.objmsgs80_qtab',
                                queue_payload_type =>  'aqtest.Message_typ',
                                multiple_consumers => TRUE);
  DBMS_AQADM.CREATE_QUEUE(queue_name => 'MSG_QUEUE',
                          queue_table => 'aqtest.objmsgs80_qtab');
  DBMS_AQADM.START_QUEUE(queue_name => 'MSG_QUEUE');
end;
/

-- ADDED: Setup additional queue to propagate messages to
begin
  DBMS_AQADM.CREATE_QUEUE_TABLE(queue_table => 'aqtest.objmsgs80_qtabX',
                                queue_payload_type =>  'aqtest.Message_typ',
                                multiple_consumers => TRUE);
  DBMS_AQADM.CREATE_QUEUE(queue_name => 'MSG_QUEUEX',
                          queue_table => 'aqtest.objmsgs80_qtabX');
  DBMS_AQADM.START_QUEUE(queue_name => 'MSG_QUEUEX');
end;
/

-- SMD: you'll see there is now a second parameter, as discussed above:
create or replace procedure enqueue_msg(p_msg in varchar2,
                                        p_add in varchar2 default null)
as
  enqueue_options dbms_aq.enqueue_options_t;
  message_properties dbms_aq.message_properties_t;
  message_handle RAW(16);
  message aqtest.message_typ;
  recipients DBMS_AQ.aq$_recipient_list_t;
BEGIN
  -- ADDED
  -- SMD: here's where the parameter is used
  recipients(1) := SYS.aq$_agent('RECIPIENT', p_add, null);
  message_properties.recipient_list := recipients;

  message := message_typ('NORMAL MESSAGE',  p_msg );
  dbms_aq.enqueue(queue_name => 'msg_queue',
                  enqueue_options => enqueue_options,
                  message_properties => message_properties,
                  payload => message,
                  msgid => message_handle);
end;
/

-- SMD: this msgs is meant for THIS queue and will not be propagated.
begin enqueue_msg('This is a test....'); commit; end;
/

-- ADDED: Create loopback database link
create database link aqtest.LoopBack connect to aqtest identified by aqtest
using 'funk92.us.oracle.com';

-- ADDED: Setup scheduling for messages
begin DBMS_AQADM.Schedule_Propagation(Queue_Name  => 'MSG_QUEUE',
                                      Destination => 'aqtest.LoopBack',
                                      Start_Time  => sysdate,
                                      Latency     => 0);
end;
/

-- Check scheduling: Everything checked out OK.
select * from user_queue_schedules;

-- SMD: this msgs is meant for the MSG_QUEUEX queue and WILL BE propagated.
begin enqueue_msg('This should be propagated.',
'aqtest.MSG_QUEUEX@aqtest.LoopBack'); commit; end;
/

-- Check scheduling: Neither error nor action reported
select * from user_queue_schedules;
----------------------------------

I think this is what you're looking for:

aqtest@FUNK92> select t1.cnt, t2.cnt
  2    from (select count(*) cnt from objmsgs80_qtab) t1,
  3         (select count(*) cnt from objmsgs80_qtabx) t2
  4  /

       CNT        CNT
---------- ----------
         1          1

Hope that helps!

_smd_

      Rating:
      5
  April 26, 2003
Reviewer:
      A reader
thank you tom for propagating my enqueued message to sean. I would appreciate it
soooo much if your book (the B E S T! I've ever read) would have covered my
favorite topics such as AQ and XML and fine grained auditing as well - looking
forward to the 9i edition ...

thank you very very very much sean for the solution and the explanations you
gave. I didn't recognize that agent-type as part of the problem at all ...

did you "get" it all from ORACLE's docs? can you recommend BOOKS for AQ and XML
ORACLE features which provide an insight into those issues in the same fashion
like tom's famous expert-one-on-one-book does?

just one question answered the next one pops up: now I'm wondering how one would
get a CERTAIN queue (if there are many) addressed on the remote site. there must
be a way to achieve this, but how?

          Followup:

Hi Sean here again,

You'll notice in the code sample I sent, when we constructed the user agent we
specified the queue name on the remote site:

begin enqueue_msg('This should be propagated.',
'aqtest.MSG_QUEUEX@aqtest.LoopBack'); commit; end;
/

aqtest.MSG_QUEUEX@aqtest.LoopBack basically said 'propagate to the MSG_QUEUEX
queue that belongs to the AQTEST schema in the database identified by link
aqtest.LoopBack.'

Yes, I extracted all this information from the Oracle documentation.  I don't
know of any commercial books covering AQ, but Steve Muench has a great O'Rielly
book on Oracle's XML Developer's Kits.  One thing Steve didn't mention is
anything to do with Oracle XML DB (due to the fact that his book was written in
the 8i time frame).  I would recommend you check the Oracle documentation
(avail. on OTN) on these technologies as a first step, anyway :-).

Advanced Developer's Guide - Advanced Queues
XML API Reference
XML Database Developer's Guide
XML Developer's Kits Guide

Hope that helps!

      Rating:
      4
  April 28, 2003
Reviewer:
      A reader
thank you, sean.

      Rating: 5
Is Advanced
      Queuing implemented using DBMS_PIPE?  July 05, 2003

Reviewer:  Oracle Fan  from Canada
Hi Tom,

I would like to know what technology works underneath Advanced Queuing. Is it
implemented using DBMS_PIPE? Or are these two related to each other in some way?

It will be interesting to know what other Oracle components are implemented
using DBMS_PIPE, such as Workflow, DBMS_JOB, Streams, XML Gateway, etc.

Thanks for your effort.

          Followup:

pipes are just a lightweight IPC mechanism.  they are not really used heavy duty
in any of those things.  It is just a utility.

      Rating: 4
message does
      not get dequeued  September 16, 2003
Reviewer:
      KU  from Nashville, TN
I followed your example code of plsql notification. I am sure I didn't make any
mistake anywhere. The plsql notification doesn't seem to be occurring. My
aq_tm_processes parameter = 1 and job_queue_processes = 1.
(In init.ora, no spfile is present).
Db version is 9.2.0.4 on NT, job_queue_interval parm is obsolete(default 5secs).
I can see the messages in the queue table with the recepient name ('RECEPIENT').
Any pointer as to non-notification, please?

      Rating:
      2
non-notification issue cleared  September 24, 2003

Reviewer:  KU  from Nashvill, tn
I recreated the message_table with 'tablespace' option as I read in a forum.
After this the plsql notification started working.
Thanks anyway,
KU

      Rating: 5
PL/SQL
      notifications does not work on SE ?  October 20, 2003

Reviewer:  Sasa  from Belgrade, Serbia
Hi Tom,

PL/SQL notifications for AQ does not work on SE?

Thanks.

          Followup:

never tried -- don't see it listed separately in the new features guide.

are you getting "an error" -- that would be useful to see.

      Rating: 4
Don't get an
      error  October 20, 2003
Reviewer:  Sasa
       from Belgrade, Serbia
I dont get any error but message is not dequeued and not inserted into
message_table.

Same time I tried on EE and it works fine.
Must say I tested in single consumer enviroment but everything else is just as
in your example.
Thanks

          Followup:

aq_tm_processes is set to what?

any messages in the alert log?

      Rating: 3
Seems some
      mistake in DB  October 21, 2003
Reviewer:
      Sasa  from Belgrade, Serbia
Hi Tom,

aq_tm_process is set to 1.

Found this is in alert log this morning(it seems that our DB will have to check
this):

Corrupt block relative dba: 0x00400864 (file 1, block 2148)
Bad check value found during buffer read
Data in bad block -
 type: 6 format: 2 rdba: 0x00400864
 last change scn: 0x0000.24a7f902 seq: 0x1 flg: 0x06
 consistency value in tail: 0xf9020601
 check value in block header: 0x3c7a, computed block checksum: 0x4
 spare1: 0x0, spare2: 0x0, spare3: 0x0
***
Reread of rdba: 0x00400864 (file 1, block 2148) found valid data
Tue Oct 21 09:46:59 2003
Restarting dead background process EMN0
EMN0 started with pid=13

Thanks

October 20, 2003 OP -- Thanks for the question regarding "Question
      on Trigger's !", version 8.1.6.0

      You Asked
Hi Tom,

How are you Doing !

My Question is based on Triggers Today.

How many Triggers we can write on a table level.

I mean if there is a Trigger T1 (for insert for delete for update)
Already Exists on Table TABLE1 then how many more triggers
can I place for the Table TABLE1.

Thanks and Regards
OP Lalwani

      and we said...
There are no limits, other then obvious practical limits, on the number of
triggers on a table.  We support multiple same type triggers.
Review &
      Followup

      Rating:
      3
ok  October 20, 2003
Reviewer:  Mary
       from Boulder,USA
Dear Sir,
I have some questions regarding triggers.
1)what can be the use of statement level triggers for which
we can't even refer the old or new values of fields?Mention
some situations where statement level triggers can be of
immense use?
2)Can stored procedures call triggers?Mention some examples
for that.
Thanks in advance

          Followup:

1) auditing pops into my head immediately.  more detailed security enforcement
(prevent the update from happening between 9am and 5pm for example).
notification (after trigger -- generate an alert to let someone know the data
changed).  mutating table work arounds (you can read and write the table the
trigger fired on in statement triggers)...

2) no, a trigger "happens" as a side effect of DML.  a trigger can call a
procedure.  it does not make sense to have a procedure "call" a trigger.  a
procedure would have to do some DML to have a trigger get fired as a side
effect.

October 20, 2003 Ganesh -- Thanks for the question regarding
      "difference between count(1) and count(*) ", version 3.0

      You Asked
What is the difference between count(1) and count(*) in a sql query
eg.
select count(1) from emp;
   and
select count(*) from emp;

      and we said...
nothing, they are the same, incur the same amount of work -- do the same thing,
take the same amount of resources.

You can see this via:

ops$tkyte@ORA817.US.ORACLE.COM> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA817.US.ORACLE.COM> select count(*) from all_objects;

  COUNT(*)
----------
     27044

ops$tkyte@ORA817.US.ORACLE.COM> select count(1) from all_objects
  2  /

  COUNT(1)
----------
     27044

and the tkprof will show:

select count(*)
from
 all_objects

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.02          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      5.56       5.56          0     234998          4           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      5.58       5.58          0     234998          4           1

select count(1)
from
 all_objects

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.02          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      5.46       5.47          0     234998          4           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      5.48       5.49          0     234998          4           1

Same number of blocks read/written/processed, same cpu times (basically) same
elapsed times (basically).

they are identical.

Anyone who thinks different (and I know you are out there) will have to post a
test case like the above or some scientific proof otherwise to be taken
seriously....

Review &
      Followup

      Rating: 5
And just in
      case...  August 13, 2001
Reviewer:  Connor
       from UK (wish I wasn't)
And just before anyone jumps on the "count(primary key) is better" bandwagon,
they should take a look at the example on
http://www.oracledba.co.uk/tips/count_speed.htm
which shows (as Tom points out)
that they all work the same nowadays...

      Rating: 4
GOOD
      ANSWER  August 13, 2001
Reviewer:  Ganesh A
       from Pune,India

      Rating: 3
a test result
      show difference between count(*) and count(1)   August 31,
      2001
Reviewer:  Dulimin  from BJ,China
Hi, tom:

Here is my test result, it show count(*) is much fast than count(1).

In other condition ( for example, a query with join), sometime i can find
count(1) is fast than count(*), but i can't find the sample at present. When i
find one, i will send to you.

SVRMGR> connect scott/tiger
Connected.
SVRMGR>
SVRMGR>  drop sequence seq_r1000;
Statement processed.
SVRMGR>  drop table r1000;
Statement processed.
SVRMGR>  create sequence seq_r1000;
Statement processed.
SVRMGR> create table r1000 (id number);
Statement processed.
SVRMGR> insert into r1000 select seq2.nextval from all_objects where rownum<1001
;
1000 rows processed.
SVRMGR> commit;
Statement processed.
SVRMGR> set timing on
Timing                          ON
SVRMGR> select count(*) from r1000, r1000;
COUNT(*)
----------
   1000000
1 row selected.
Parse             0.00 (Elapsed)     0.00 (CPU)
Execute/Fetch     0.43 (Elapsed)     0.00 (CPU)
Total             0.43               0.00
SVRMGR> select count(1) from r1000, r1000;
COUNT(1)
----------
   1000000
1 row selected.
Parse             0.00 (Elapsed)     0.00 (CPU)
Execute/Fetch     0.70 (Elapsed)     0.00 (CPU)
Total             0.70               0.00
SVRMGR> select count(*) from r1000, r1000;
COUNT(*)
----------
   1000000
1 row selected.
Parse             0.00 (Elapsed)     0.00 (CPU)
Execute/Fetch     0.41 (Elapsed)     0.00 (CPU)
Total             0.41               0.00
SVRMGR> select count(1) from r1000, r1000;
COUNT(1)
----------
   1000000
1 row selected.
Parse             0.01 (Elapsed)     0.00 (CPU)
Execute/Fetch     0.69 (Elapsed)     0.00 (CPU)
Total             0.70               0.00
SVRMGR>

          Followup:

I'll have to guess, since you don't say, that you are using 7.x and before when
count(*) and count(1) were different (and count(1) was slower).  In all releases
of the databases for the last 4-5 years, they are the same.

My testing on 8.x with this test case:

drop sequence seq_r1000;
drop table r1000;
create sequence seq_r1000;
create table r1000 (id number);
insert into r1000 select seq_r1000.nextval from all_objects where rownum<1001;

analyze table r1000 compute statistics;
select count(*) from r1000, r1000;
select count(1) from r1000, r1000;

alter session set sql_trace=true;

declare
    n   number;
begin
    for i in 1 .. 10
    loop
        select count(*) into n from r1000, r1000;
        select count(1) into n from r1000, r1000;
    end loop;
end;
/

shows:

SELECT COUNT(*)
FROM
 R1000,R1000

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute     10      0.00       0.00          0          0          0           0
Fetch       10     12.46      12.53          0         40         80          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       21     12.46      12.53          0         40         80          10

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 29     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  SORT AGGREGATE
10000000   MERGE JOIN CARTESIAN
  10010    TABLE ACCESS FULL R1000
10000000    SORT JOIN
  10000     TABLE ACCESS FULL R1000

********************************************************************************

SELECT COUNT(1)
FROM
 R1000,R1000

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute     10      0.00       0.01          0          0          0           0
Fetch       10     12.38      12.38          0         40         80          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       21     12.38      12.40          0         40         80          10

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 29     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  SORT AGGREGATE
10000000   MERGE JOIN CARTESIAN
  10010    TABLE ACCESS FULL R1000
10000000    SORT JOIN
  10000     TABLE ACCESS FULL R1000

they are in effect the same...

      Rating:
      4
  August 31, 2001
Reviewer:
      A reader
TOM WE ALREADY HAVE LOT'S OF DISCUSSION ABOUNT COUNT(*)
ETC.

LET'S JUST NOT WASTE TIME ANYMORE ON THIS TOPIC

      Rating: 3
My database is
      8.1.5  September 03, 2001
Reviewer:  dulimin
       from BJ, China
I forget to say my database version in last post, it's Oracle 8.1.5 EE on Win NT
4.0.

And I have test it on 8.1.7 just now, the result is:

===========================

C:\>svrmgrl

Oracle Server Manager Release 3.1.7.0.0 - Production

Copyright (c) 1997, 1999, Oracle Corporation.  All Rights Reserved.

Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production
With the Partitioning option
JServer Release 8.1.7.0.0 - Production

SVRMGR> connect scott/tiger
Connected.
SVRMGR> insert into r1000 select seq_r1000.nextval from all_objects where
rownum<1001;
1000 rows processed.
SVRMGR> commit;
Statement processed.
SVRMGR> set timing on
Timing                          ON
SVRMGR> select count(*) from r1000, r1000;
COUNT(*)
----------
   4000000
1 row selected.
Parse             0.00 (Elapsed)     0.00 (CPU)
Execute/Fetch     1.33 (Elapsed)     0.00 (CPU)
Total             1.33               0.00
SVRMGR> select count(1) from r1000, r1000;
COUNT(1)
----------
   4000000
1 row selected.
Parse             0.02 (Elapsed)     0.00 (CPU)
Execute/Fetch     2.36 (Elapsed)     0.00 (CPU)
Total             2.38               0.00
SVRMGR> select count(*) from r1000, r1000;
COUNT(*)
----------
   4000000
1 row selected.
Parse             0.01 (Elapsed)     0.00 (CPU)
Execute/Fetch     1.34 (Elapsed)     0.00 (CPU)
Total             1.35               0.00
SVRMGR> select count(1) from r1000, r1000;
COUNT(1)
----------
   4000000
1 row selected.
Parse             0.00 (Elapsed)     0.00 (CPU)
Execute/Fetch     2.33 (Elapsed)     0.00 (CPU)
Total             2.33               0.00
SVRMGR>

============================

If the result is caused by some problem of my environment, what problem is it?

      Rating: 4
count is
      sum(1)  September 04, 2001
Reviewer:  Mikito
      Harakiri  from SF, CA US
Some extra info:

1. There is no need in a separate "count" function as

select sum(1) from emp

does the job (and could do more;).

2. "count" as an abbreviation for sum(1) doesn't really need an argument, for
example

select count(1) from emp

and

select count(2) from emp

return the same data.

In short, "count" having an argument is counterintuitive, at least.

      Rating: 4
SQL
      quirks  September 04, 2001
Reviewer:  Vadim
      Tropashko  from Foster City, CA
Responding to Mikito:

Except that

select count(distinct 1) from emp

demonstrates that count() does depend on the argument in some wierd way. Anyway,
instead of the obscure syntax above, I would suggest to use nested queries like
this:

select count(1) from (
    select distinct 1 from emp
)

      Rating: 4
one more row
      count query:-)  June 10, 2003
Reviewer:  Mikito
      Harakiri
select -1/2+1/2*sqrt(1+8*sum(rownum)) from emp

      Rating: 4
Could you
      please explain the query as well, Mikito?  June 12, 2003

Reviewer:  Reader

      Rating: 3
Mikito is a
      show off.....  July 24, 2003
Reviewer:  Nick
       from NY
Ok, so which performs better ; )

      Rating:
      5
Hmmmm...  August 04, 2003
Reviewer:
      A reader
Quoting a forum answer from an oracle employee on Metalink:
Subject: Re : Performance while using ROWNUM v/s COUNT
Hi,

If the intention is only to count the number of Records matching the criteria
then you could as well use :

count(1) ..

This is the fastest as the numberical literal is the fastest to be summed up.

Max rownum will lead to implicit ordering while count(id) will also be effecient
as the value is a primary key though not as good as count(1). "

          Followup:

well, you could point out to them "hey, we are not summing -- we are COUNTING"

max(rownum) would be a bad idea.

count(id) is dandy if id is a NOT NULL column.

count(1) is just counter intuitive to me and is silently rewritten as count(*)
internally...

      Rating: 5
Re:
      Counter-intuitive/Harakiri  August 04, 2003

Reviewer:  A reader
<quote>
In short, "count" having an argument is counterintuitive, at least.
<\quote>

Not if you need to count distinct values, or non-NULL ocurrances of some column.

      Rating:
      4
Amazing!!!!!!!!  August 10, 2003
Reviewer:
      Saeed Ahmed  from India.
I am amazed to see such a basic things are discused here, This is a lovely
forum!

Thank you very much.

      Rating:
      5
  August 13, 2003
Reviewer:  Su Baba

Hi Tom,

What's the quickest way to find out if a table is empty?  Obviously, select
count(*) is not the quickest way. What about

select 'x' from <table> where rownum = 1;

This will still do a full table scan, wouldn't it.

What's the optimal solution?

thanks

          Followup:

use the CBO and

select /*+ FIRST_ROWS */ primary_key from table where rownum = 1;

ops$tkyte@ORA920LAP> create table emp as select * from scott.emp;

Table created.

ops$tkyte@ORA920LAP> alter table emp add constraint emp_pk primary key(empno);

Table altered.

ops$tkyte@ORA920LAP> analyze table emp compute statistics;

Table analyzed.

ops$tkyte@ORA920LAP> set autotrace traceonly explain
ops$tkyte@ORA920LAP> select /*+ FIRST_ROWS */ empno from emp where rownum = 1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=1 Card=1 Bytes=3)
   1    0   COUNT (STOPKEY)
   2    1     INDEX (FULL SCAN) OF 'EMP_PK' (UNIQUE) (Cost=1 Card=14 Bytes=42)

ops$tkyte@ORA920LAP> set autotrace off;

it'll read the index and stop at the first row.  very fast on a big empty table
(as the index is small and empty).

very fast on a big full table as the index is just read to find the first leaf
node and then "stop"

      Rating:
      5
  August 13, 2003
Reviewer:  Su Baba

Tom,

I tried it with and without /*+ FIRST_ROWS */ hint. In some case, the cost seems
to be lower without the hint. Should I pay attention to the cost at all?

SQL> CREATE TABLE x (col1 NUMBER NOT NULL);

Table created.

SQL> CREATE UNIQUE INDEX x_u1 ON x(col1);

Index created.

SQL>
SQL> BEGIN
  2     FOR i IN 1..14 LOOP
  3        INSERT INTO x VALUES (i);
  4     END LOOP;
  5
  6     commit;
  7  END;
  8  /

PL/SQL procedure successfully completed.

SQL>
SQL> ANALYZE TABLE x COMPUTE STATISTICS;

Table analyzed.

SQL>
SQL> set autotrace traceonly explain
SQL>
SQL> SELECT /*+ FIRST_ROWS */ col1
  2  FROM   x
  3  WHERE  rownum = 1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=1 Card=14
          Bytes=28)

   1    0   COUNT (STOPKEY)
   2    1     INDEX (FULL SCAN) OF 'X_U1' (UNIQUE) (Cost=1 Card=14 Byt
          es=28)

SQL>
SQL> set autotrace off
SQL>
SQL> BEGIN
  2     FOR i IN 15..1000 LOOP
  3        INSERT INTO x VALUES (i);
  4     END LOOP;
  5
  6     commit;
  7  END;
  8  /

PL/SQL procedure successfully completed.

SQL>
SQL> ANALYZE TABLE x COMPUTE STATISTICS;

Table analyzed.

SQL>
SQL> set autotrace traceonly explain
SQL>
SQL> SELECT /*+ FIRST_ROWS */ col1
  2  FROM   x
  3  WHERE  rownum = 1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=3 Card=100
          0 Bytes=3000)

   1    0   COUNT (STOPKEY)
   2    1     INDEX (FULL SCAN) OF 'X_U1' (UNIQUE) (Cost=3 Card=1000 B
          ytes=3000)

SQL>
SQL> SELECT col1
  2  FROM   x
  3  WHERE  rownum = 1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1000 Bytes=30
          00)

   1    0   COUNT (STOPKEY)
   2    1     TABLE ACCESS (FULL) OF 'X' (Cost=2 Card=1000 Bytes=3000)

          Followup:

do this now

delete from x;
commit;

don't analyze

compare the performance.  see what happens when it accidently full scans

use first_rows.

      Rating:
      4
  August 13, 2003
Reviewer:  Su Baba

SQL> select count(*) from x;

  COUNT(*)
----------
      1000

SQL> DELETE FROM x;

1000 rows deleted.

SQL> commit;

Commit complete.

SQL> set autotrace traceonly explain
SQL>
SQL> SELECT /*+ FIRST_ROWS */ col1
  2  FROM   x
  3  WHERE  rownum = 1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=3 Card=100
          0 Bytes=3000)

   1    0   COUNT (STOPKEY)
   2    1     INDEX (FULL SCAN) OF 'X_U1' (UNIQUE) (Cost=3 Card=1000 B
          ytes=3000)

SQL>
SQL> SELECT col1
  2  FROM   x
  3  WHERE  rownum = 1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1000 Bytes=30
          00)

   1    0   COUNT (STOPKEY)
   2    1     TABLE ACCESS (FULL) OF 'X' (Cost=2 Card=1000 Bytes=3000)

          Followup:

i said to BENCHMARK , not explain plan.

created a table t as select * from all_objects and then:

SELECT /*+ FIRST_ROWS */ object_id from t before_delete where rownum = 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          1          2          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          1          2          0           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  COUNT STOPKEY (cr=2 r=1 w=0 time=109 us)
      1   INDEX FULL SCAN T_PK (cr=2 r=1 w=0 time=99 us)(object id 39935)
********************************************************************************
SELECT /*+ FULL(before_delete) */ object_id from t before_delete where rownum =
1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          4          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          4          0           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  COUNT STOPKEY (cr=4 r=0 w=0 time=95 us)
      1   TABLE ACCESS FULL T (cr=4 r=0 w=0 time=87 us)

 so, far, so comparable. now -- delete from t; and commit

SELECT /*+ FIRST_ROWS */ object_id from t after_delete where rownum = 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0        117          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0        117          0           0

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  COUNT STOPKEY (cr=117 r=0 w=0 time=3121 us)
      0   INDEX FULL SCAN T_PK (cr=117 r=0 w=0 time=3117 us)(object id 39935)
********************************************************************************
SELECT /*+ FULL(after_delete) */ object_id from t after_delete where rownum = 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.03       1.05        223        821          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.03       1.05        223        821          0           0

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  COUNT STOPKEY (cr=821 r=223 w=0 time=1054038 us)
      0   TABLE ACCESS FULL OBJ#(39934) (cr=821 r=223 w=0 time=1054035 us)

      Rating:
      5
  August 14, 2003
Reviewer:  Helena
      Marková  from Bratislava, Slovakia

      Rating: 5
I have found
      another opinion  October 20, 2003
Reviewer:
      Sergej  from Germany
Hi Tom,

There is another opinion in:

OCP Introduction to Oracle 9i: SQL Exam Guide, p. 124
"Do not use count(*) to determine the number of rows in a table. Use count(1) or
count(ROWID) instead. These options are faster because they bypass some
unnecessary operations in Oracle's SQL pocessing mechanism."

Thanks,
Best,
Sergej

          Followup:

OPINIONS DON'T COUNT

FACTS -- FACTS COUNT.

first, count(1) internally is rewritten as count(*).  That is a FACT.

second, a simple tkprof shows count(*) and count(rowid) are not significantly
different (in fact, in this run, count(rowid) a little LESS efficient).  the big
difference is:

count(*) means something.  It means literally COUNT THE ROWS

count(1) does not.
count(rowid) does not.

opinions -- they quite simplye DO NOT COUNT.  Facts, backed up with supporting,
reproducible evidence -- very much welcome here.

select count(*) from big_table

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.03          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      5.06       5.36       4327       4338          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      5.08       5.40       4327       4338          0           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=4338 r=4327 w=0 time=5367705 us)
1833792   INDEX FAST FULL SCAN BIG_TABLE_OBJECT_ID_IDX (cr=4338 r=4327 w=0
time=3160178 us)(object id 49917)
********************************************************************************
select count(rowid) from big_table

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      5.33       5.23       4322       4338          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      5.33       5.23       4322       4338          0           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=4338 r=4322 w=0 time=5234072 us)
1833792   INDEX FAST FULL SCAN BIG_TABLE_OBJECT_ID_IDX (cr=4338 r=4322 w=0
time=2898097 us)(object id 49917)

in fact, if you do it over and over and over:

select count(*) from big_table

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        7      0.00       0.00          0          0          0           0
Execute      7      0.00       0.00          0          0          0           0
Fetch       14     35.48      40.91      30254      30366          0           7
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       28     35.48      40.91      30254      30366          0           7
********************************************************************************
select count(rowid) from big_table

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        7      0.00       0.00          0          0          0           0
Execute      7      0.00       0.00          0          0          0           0
Fetch       14     37.61      42.36      30254      30366          0           7
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       28     37.61      42.36      30254      30366          0           7

but you decide.  oh and count(1)

select count(*) from big_table

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        7      0.00       0.00          0          0          0           0
Execute      7      0.00       0.00          0          0          0           0
Fetch       14     35.35      35.87      30254      30366          0           7
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       28     35.35      35.87      30254      30366          0           7

********************************************************************************
select count(1) from big_table

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        7      0.00       0.00          0          0          0           0
Execute      7      0.00       0.00          0          0          0           0
Fetch       14     35.21      35.77      30254      30366          0           7
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       28     35.21      35.77      30254      30366          0           7

      Rating:
      5
Great!  October 21, 2003
Reviewer:
      Jens  from Hamburg
Hi Tom,

you must publish something like "The 10 Oracle Myths". Before I found your side
I read a lot of that COUNT(1) is better than COUNT(*) and that Explicit Cursors
were better than Implicit Cursors (and I told it everbody because I found it in
the literature) - I'm curious how many oracle consultants would say the same!
And what is the worth of an OCP when they learn something like that?

Bye,

Jens

      Rating: 4
to
      Sergej  October 21, 2003
Reviewer:  freek
       from Belgium
Sergej,

Just a warning: test everything you read in that book!
I have the same book and found one error after the other.

To give a nice example, on page 128 they clame that when you use a group by
clause, all nongroup expressions in the column clause of the query must appear
before the grouped expresson in the column clause.
Because of a mistake in their example (forgot a column in the group by clause)
it seems to be true.

greetings

Freek

      Rating: 5
OCP Book
      Errors  October 21, 2003
Reviewer:  Tony
      Andrews  from London, UK
But bear in mind: if the OCP exam is based on the book, you better give the
answer they WANT in the exam, not the RIGHT answer!

October 20, 2003 Logo -- Thanks for the question regarding "No of
      occurences of a string within another string", version 8.1.5

      You Asked
Tom,

Is there a standard SQL function that will give me the number of times a
substring occurs within a string. Or Do I have to write one by myself using
substr, instr functions?

e.g
string_count('abcxyzdefghxyzhi', 'xyz') will return me a value of 2
string_count('abc.efg.hij.klm', '.') will return me a value fo 3

Thanks in advance.

      and we said...
you do not need substr/instr -- length and replace are what you are looking for:

ops$tkyte@ORA9I.WORLD> select (length(:main_str) -
length(replace(:main_str,:srch_str,'')))/length(:srch_str) cnt
  2    from dual
  3  /

       CNT
----------
         2

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> exec :main_str := 'abc.efg.hij.klm'

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> exec :srch_str := '.'

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> select (length(:main_str) -
length(replace(:main_str,:srch_str,'')))/length(:srch_str) cnt
  2   from dual
  3  /

       CNT
----------
         3
Review & Followup

      Rating: 5
Very
      good  September 27, 2001
Reviewer:
      A reader

      Rating: 3
no of
      occurences of a string within another string  September 27,
      2001
Reviewer:  Melwin Nunes  from NJ, USA

CREATE OR REPLACE FUNCTION countm( pi_string          IN  VARCHAR2,
                                   pi_search          IN  VARCHAR2,
                                   pi_case_sensitive  IN  VARCHAR2 DEFAULT 'S')
RETURN NUMBER IS

   vn_count   NUMBER         :=  0;
   vn_exit    NUMBER         :=  0;
   vn_length  NUMBER         :=  LENGTH( pi_string);
   vv_string  VARCHAR2(32000) :=  pi_string;
   vv_search  VARCHAR2(32000) :=  pi_search;

BEGIN

   IF UPPER( pi_case_sensitive) = 'I' THEN

      vv_search  :=  UPPER( vv_search);
      vv_string  :=  UPPER( vv_string);

   END IF;

   FOR length_rec IN 1..vn_length
   LOOP

      vn_exit := vn_exit + 1;

      vn_exit := INSTR( vv_string, vv_search, vn_exit );

      EXIT WHEN vn_exit = 0;

      vn_count := vn_count + 1;

   END LOOP;

   RETURN vn_count;

END countm;

Syntax
COUNTM('char1','char2', 'char3')

Purpose
COUNTM searches char1 beginning with its 1st character for the number of
occurrences of char2 and returns the total count. Specifying 'I' for char3 makes
the search case insensitive, default is case sensitive. If the search is
unsuccessful (if char2 does not appear m times after the nth character of char1)
the return value is 0.
Example 1
SELECT COUNTM( 'CORPORATE FLOOR', 'OR')
  "Countm" FROM DUAL;

 Countm
-------
      3

Example 2
SELECT COUNTM( 'CORPORATE FLOOR', 'or')
"Case Sensitive"
     FROM DUAL;

Case Sensitive
--------------
             0

Example 3
SELECT COUNTM( 'CORPORATE FLOOR', 'or', 'I')
"Case InSensitive"
     FROM DUAL;

Case Sensitive
--------------
             3

          Followup:

Yes, you could do this HOWEVER, just consider the following:

ops$tkyte@ORA8I.WORLD> create table t as select object_name from all_objects;
Table created.

ops$tkyte@ORA8I.WORLD> set timing on

ops$tkyte@ORA8I.WORLD> select max( countm( object_name, 'A' ) ) from t
  2  /

MAX(COUNTM(OBJECT_NAME,'A'))
----------------------------
                           5

Elapsed: 00:00:04.77
ops$tkyte@ORA8I.WORLD> /

MAX(COUNTM(OBJECT_NAME,'A'))
----------------------------
                           5

Elapsed: 00:00:04.17

ops$tkyte@ORA8I.WORLD> select max( (length(object_name) -
length(replace(object_name,'A','')))/length('A') ) from t
  2  /

MAX((LENGTH(OBJECT_NAME)-LENGTH(REPLACE(OBJECT_NAME,'A','')))/LENGTH('A'))
--------------------------------------------------------------------------
                                                                         5

Elapsed: 00:00:00.25
ops$tkyte@ORA8I.WORLD> /

MAX((LENGTH(OBJECT_NAME)-LENGTH(REPLACE(OBJECT_NAME,'A','')))/LENGTH('A'))
--------------------------------------------------------------------------
                                                                         5

Elapsed: 00:00:00.24

I'll stick with SQL for this one thanks -- anytime you CAN do it in SQL, do it.
Using PLSQL, while very very flexible, can increase overhead.

      Rating:
      5
Hmmmmmmmmmmm  October 01, 2001
Reviewer:
      Connor  from Bonny Scotland
There is always some prat who picks holes in things...Me in this case :-)

exec :main_str := 'abc.efg.hij.klm';
exec :srch_str := :main_str;

select (length(:main_str) - length(replace(:main_str,:srch_str,'')))/
          length(:srch_str) cnt
from dual;

          Followup:

grrrrr

missing nvl:

select (length(:main_str) -
nvl(length(replace(:main_str,:srch_str,'')),0))/
          length(:srch_str) cnt
from dual;

      Rating: 4
Search and
      replace  October 20, 2003
Reviewer:
      A reader  from Chicago, IL
Tom,
Assume that I have a variable 'test/one 7/8 test/2'.  I need to replace '/' with
',' only when the preceding or the previous character is not number.  I am
expecting the result should be 'test,one 7/8 test/2'.  Is this possible in SQL?
I tried following but didn't work.
SQL> var val varchar2(20);
SQL> exec :val := 'test/one 7/8 test/2';
SQL> select :val,
     substr(:val,(instr(:val,'/')+1),1) Prec,
     substr(:val,(instr(:val,'/')-1),1) Prev,
     ascii(substr(:val,(instr(:val,'/')+1),1)) PrecAscii,
     ascii(substr(:val,(instr(:val,'/')-1),1)) PrevAsci,
         (case
            when ascii(substr(:val,(instr(:val,'/')+1),1)) between 48 and 57
then 'bingo'
                 when ascii(substr(:val,(instr(:val,'/')-1),1)) between 48 and
57 then 'bingo'
      else
          replace(:val,'/',',')
         end ) derivedValue
 from dual;

:VAL                           P P  PRECASCII   PREVASCI DERIVEDVALUE
------------------------------ - - ---------- ----------
--------------------------------
test/one 7/8 test/2            o t        111        116 test,one 7,8 test,2

I know it can be done in PL/SQL but is this correct approach?

Thanks for your help

          Followup:

it'll be hard in 9i and before (wait'll regexp in 10g -- it'll be a snap).

you'll need to write a plsql function to do it.  I don't see a way to do it
straight forward just using SQL and replace/substr/instr

October 21, 2003 Amitava -- Thanks for the question regarding
      "Creating Temporary Table", version 8.1

      You Asked
As per your instruction I Created the following procedure:
create or replace procedure test604 as
 Begin
 execute immediate 'create global temporary table test604a as
 ( select
trbn7,cust_name,d.icn,req_num,po_num,ci,d.ln_num,ml,d.drwg
line_drwg,
 b.drwg parent_drwg,sub_drwg,d.descr,b.descr
parent_descr,sub_descr,
 orig_src_cd,sent_cd,to_char(d_rqsdt,''yyyymmdd'')
d_rqsdt,d_shp_stat,d_rel_stat,
 ''Plan Ship'' msg,d.qty,b.qty qty_reqd,b.qty plan_qty,''   ''
short_ln,
 to_char(d_plsdt,''yyyymmdd'')
d_plsdt,to_char(d_plsdt,''yyyymmdd'') stadt,
 to_char(d_reldt,''yyyymmdd'')
d_reldt,to_char(d_shpdt,''yyyymmdd'') d_shpdt,carrier,airbill,
 prefix,shortage_text,pseudo_ln from  beam.ordhdr@beam97 h,
beam.orddtl@beam97 d,
 beam.sbomline@beam97 b where h.icn=d.icn and d.icn=b.icn(+) and
 substr(d.ln_num,1,3)=substr(b.ln_num(+),1,3)and h.business =
''STM'' and
 h.order_type = ''UNI''   and h.icn = ''290T513'')';
 End;

Once this procedure is created when I try to execute it,
following error pops up:
ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "BRIOADMIN.TEST604", line 3
ORA-06512: at line 1

Can please let me know where I am commiting the mistake.

      and we said...
My direction would not be to use "create global temporary table"
in a stored procedure but rather to have the table ALREADY
created and just insert into it.  I would not create the table
in the procedure unless there was some truely compelling reason
otherwise.

with the exception of invokers rights routines (also new with 8i
-- see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:961430030094
for some info on them) PLSQL stored procedures execute with
the base privs of the definer (owner) of the routine meaning
that ROLES are not enabled.  See

http://osi.oracle.com/~tkyte/Misc/RolesAndProcedures.html
for more info on that.

You have the create table privelege via a role, you need to have
it granted directly to you.

Review &
      Followup

      Rating: 5
altering global
      temp table  October 25, 2001
Reviewer:
      A reader
Iam trying to add a column to a global temp table
but Iam getting the error

[1]: (Error): ORA-14450: attempt to access a transactional temp table already in
use

I dont see that it is already in use, Iam not using it in any of my sessions, in
my current session too , I deleted the rows and committed.

How should I see if the table is being used by some other session.

How should I alter the global temp table and add a column to it.

thank you

          Followup:

Well, you don't say HOW you created it (on commit delete rows/preserve rows)

If it was preserve rows -- you were doing it to yourself.  Regardless, you can
use v$lock to see who's got it:

ops$tkyte@ORA717DEV.US.ORACLE.COM> create global temporary table t ( x int ) on
commit preserve rows;

ops$tkyte@ORA717DEV.US.ORACLE.COM> insert into t values ( 1 );

ops$tkyte@ORA717DEV.US.ORACLE.COM> select * from v$lock
  2  where id1 = ( select object_id from user_objects where object_name = 'T' );

ADDR     KADDR           SID TY        ID1        ID2      LMODE    REQUEST
CTIME      BLOCK
-------- -------- ---------- -- ---------- ---------- ---------- ----------
---------- ----------
8423EC5C 8423EC6C         12 TO      21884          1          3          0
   21          0
844A2EC4 844A2ED8         12 TM      21884          0          3          0
   21          0

That shows that sid 12 (us) has it...

ops$tkyte@ORA717DEV.US.ORACLE.COM> delete from t;
ops$tkyte@ORA717DEV.US.ORACLE.COM> commit;

ops$tkyte@ORA717DEV.US.ORACLE.COM> select * from v$lock
  2  where id1 = ( select object_id from user_objects where object_name = 'T' );

ADDR     KADDR           SID TY        ID1        ID2      LMODE    REQUEST
CTIME      BLOCK
-------- -------- ---------- -- ---------- ---------- ---------- ----------
---------- ----------
8423EC5C 8423EC6C         12 TO      21884          1          3          0
   43          0

this shows that we STILL got it -- even after the commit -- due to the
"preserve rows" -- if this had been "delete rows", nothing would be in
v$lock...

ops$tkyte@ORA717DEV.US.ORACLE.COM> alter table t add y int;
alter table t add y int
*
ERROR at line 1:
ORA-14450: attempt to access a transactional temp table already in use

the solution at this point would be to disconnect/connect.  In the event other
sessions have it -- you have to KILL them or wait.

      Rating: 5
on commit
      delete or preserve rows  August 05, 2002
Reviewer:
      naga
can i know the difference between on commit delete or preserve rows. as it self
explanative i know what it does. but i am interested in knowing what oracle
interenally does.

which one is recommended..and why is that?

thanks

          Followup:

we either TRUNCATE the temp segment you allocated when you COMMIT (on commit
delete rows) or we TRUNCATE the temp segment you allocated when you logoff (on
commit PRESERVE rows)

It just changes when we truncate.

Neither is better then the other.  You need to ask yourself -- do I need
this data for the TRANSACTION (on commit delete) or for my entire session (on
commit preserve)

      Rating: 3
a
      Reader  January 14, 2003
Reviewer:  Larry
      Liang  from Canada
Hi Tom, I still could not fully understand the on commit delete rows for
transaction duration.  Does that mean all the rows are gone when you insert into
a temp table for some kind of use for a transation immediately after commit the
insert into the temp table?  If the records are gone, it means no data could be
retrieved for your transaction.  I know it is a silly question, and something is
certainly wrong.  Please correct me.

Thanks,

          Followup:

on commit delete rows means when you commit -- rows go "poof", disappear.

so,

  insert into global_temp_table select ....
  commit;

results in empty table.  You would use this when the data in global_temp only
needs to be used during the course of your transaction -- like a staging table
(load up tmp, process tmp into other tables, commit -- tmp goes away)

      Rating: 4
A
      Reader  January 16, 2003
Reviewer:  Larry
      Liang
Thanks for your reply Tom.

From your point, when you insert into a temp table, the records inserted will be
available for use without issuing a commit if I understand the logic correctly.
After you manipulate the records inserted, and then commit just to delete the
records (transaction duration).

If I get your point, this behavior is totally different from permanent tables.
You won't see the records inserted into a permanent table until after you issue
a commit.

Please let me if I am right or wrong.

Thanks again!

          Followup:

you are wrong.

you are right on the first point.  you insert -- you see them -- you commit and
they disappear (assuming on commit delete rows)

you are wrong on the second point.  A transaction HAS ALWAYS BEEN ABLE TO
SEE MODIFICATIONS IT MAKES.

No other session can but that session can most certainly see its changes.

the behaviour for permanent and temporary tables as far as visibility goes is
identical.

      Rating: 5
What
      for?  January 16, 2003
Reviewer:  Anirudh
       from New Delhi, INDIA
Hi Tom,
As you stated:
" this shows that we STILL got it -- even after the commit -- due to the
"preserve rows" -- if this had been "delete rows", nothing would be in
v$lock..."

What is the significance of keeping it locked. Why is that done?
Thanks.

          Followup:

you have a temporary segment -- you are using the object.  It matters not (heck
we don't even KNOW) that it happens to be empty via "delete from t" -- as far as
we are concerned -- there is data in there.

We cannot tell that a table is empty after a delete.  We don't track that sort
of stuff.  Hence the temporary table is still "in use"

      Rating: 4
I got it,
      THANKS..  January 16, 2003
Reviewer:
      Anirudh  from N.Delhi, INDIA

      Rating: 5
Altering global
      temporary table  April 24, 2003
Reviewer:
      Abuaker Khered  from Jeddah- Saudi Arabia
Hi,

Can I do:
create global temporary table gtt_customer
    as select * from customer
       where 0=1
    on commit delete rows

I know it is possible to:
create global temporary table gtt_customer
    as select * from customer
       where 0=1

but can I add " on commit delete rows " to the command, or alter the table
afterward.

Thanks and Regards,

          Followup:

ops$tkyte@ORA920> create global temporary table t
  2  as
  3  select * from all_users where 1=0
  4  on commit delete rows;
on commit delete rows
*
ERROR at line 4:
ORA-00933: SQL command not properly ended

ops$tkyte@ORA920> edit
Wrote file afiedt.buf

  1  create global temporary table t
  2  on commit delete rows
  3  as
  4* select * from all_users where 1=0
ops$tkyte@ORA920> /

Table created.

ops$tkyte@ORA920>

it simply goes BEFORE the subquery

      Rating:
      5
Thanks  April 26, 2003
Reviewer:
      A reader
Thanks,
Abuaker

      Rating: 5
Problem with
      Global Temp Table  May 19, 2003
Reviewer:
      Bharath  from USA
We have a problem, when we join the Global temp table  col with the table at the
remote site.

For e.g(Not the actual sql)
Select X from remote_table,global_temp_table
where remote_table.deal_id = global_temp_table.deal_id

It takes 360 sec to retrieve all the record but if i replace the temp table with
the array like
(remote_tablein(Array_var)) then it retrieves in 2 sec.
The reason we went with Temp Table to avoid 1000 limitation in the IN
variable.Is there any thing that we can do speed up the process by using global
temp table.

          Followup:

try using the driving_site hint to have the query optimized at the other
location or put the gtt ON the remote site.

      Rating: 5
Problem with
      Global Temp Table  May 20, 2003
Reviewer:
      Bharath  from USA
>>put the gtt ON the remote site
We dont have a control on it.
I read somewhere that RuleBased Optimizer will not understand the Index on the
remote site,so it will always go for Full table scan,Is that true?

By Giving driving site hint, will the query will use the  Index on the remote
site?

          Followup:

try it -- see what happens.

      Rating: 4
Problem with
      Global Temp Table  May 20, 2003
Reviewer:
      Bharath  from USA
I tried with driving_site in hint in the sql i saw a slight improvement in the
response time,but large diference in
recursive calls, db block gets,consistent gets, physical
(Response time No Hint:364 Sec)
((Response time with Hint):310 Sec)

Statistics(Without Hint)
----------------------------------------------------------
         16  recursive calls
         28  db block gets
          7  consistent gets
        395  physical reads
          0  redo size
       3022  bytes sent via SQL*Net to client
        379  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          6  sorts (memory)
          2  sorts (disk)
         21  rows processed

Statistics(With the Hint)
----------------------------------------------------------
          4  recursive calls
          4  db block gets
          1  consistent gets
          1  physical reads
          0  redo size
       3002  bytes sent via SQL*Net to client
        379  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         21  rows processed

Will the distributed query optimization will work better in CBO rather in RBO

          Followup:

it would appear, based on the above, that the query is being optimized now on
the remote site.

Tell me this -- if you run the query ENTIRELY on the remote site, what is the
performance there.  You need to find out what the "best possible case in the
world" is before going further there.

      Rating: 5
Problem with
      Global Temp Table  May 21, 2003
Reviewer:
      Bharath  from USA
When we run the entire query in remote site without joining the Global temp
table by passing an array is very fast we are getting the entire result set in 5
sec.

It's Look like under RBO remote table is accessed first and then the local table
bcos of this, predicates(e.g
remote_table.sec_id in(select local_table.sec_id from
local_table))which will reduce the result from the remote table is not used and
remote table is going for Full Table Scan.

          Followup:

no, what I mean is -- put the gtt on the remote site and run the EXACT SAME
THING only 100% locally.

      Rating: 5
Problem with
      Global temp table  May 21, 2003
Reviewer:
      Bharath  from USA
We dont have a write privilege in the remote database that's the main reason we
cant create the GTT on the remote site.

Is this True?
It's Look like under RBO remote table is accessed first and then the local
table, bcos of this, predicates(e.g
remote_table.sec_id in(select local_table.sec_id from
local_table))which will reduce the result from the remote table is not used and
remote table is going for Full Table Scan.

          Followup:

just *test* it -- ask the DBA on the remote site to *help* you test something
quick.

They should be dying to help you -- it is their system you are hitting hard.

      Rating: 5
Problem joining
      multiple tables remotely  May 21, 2003
Reviewer:
      Bharath  from USA
Yes i tried executing GTT in remote, Query returned the output very
fast.Unfortunately we can't take route.If every thing happens remotely query
response in fast.

I  am currently tesing the query  with CBO ,result dosent look promising???

          Followup:

well -- i would really question "why, why cannot i take that route".  does the
remote dba want you pounding on his system?  I don't get it?

      Rating: 5
Yes Tom I
      Understand it  May 21, 2003
Reviewer:
      Bharath  from USA
>>"why, why cannot i take that route"

Yes i know that as a techincal guy  i should have followed this route but due to
some business restriction(we have to convince lot of intermediate managers).we
are not able to do this.But we have overcome this problem by changing GTT to
String array(i.e IN).

But i was thinking that CBO will solve my problem by reading an article from
metalink but it's look like i am getting the same response from CBO oracle
isntance similar to RBO.

      Rating:
      5
reader  May 22, 2003
Reviewer:
      A reader
"
8423EC5C 8423EC6C         12 TO      21884          1
"

What is lock type "TO" and ID2 for "TO" Is it documented

          Followup:

TO is the temporary object enqueue.  id1 is the object number of the temporary
object.  The purpose of the lock is to prevent DDL from being issued against an
object while DML is also occurring.

      Rating: 5
Another
      question  May 23, 2003
Reviewer:  ZS

Tom,
Can you please clarify one point:
"we either TRUNCATE the temp segment you allocated when you COMMIT (on commit
delete rows) or we TRUNCATE the temp segment you allocated when you logoff (on
commit PRESERVE rows)
"
1. does oracle really do TRUNCATE or DELETE?. and if TRUNCATE, how does affect
other users using the same table at the same time?
2. Also, is there any fragmentation/high water mark pointing to the last used
block when we delete and causing performance degradation or this does not happen
in temporary table.
Thanks,
ZS

          Followup:

1) it is functionally equivalent to truncate.

each user gets their own temp segment, they are not overlapping or interleaved
at all.  they are 100% concurrent and separate.

2) no there is not. well, not really.

of course you could:

insert 1,000,000 fat rows into a global temp table.
delete it yourself

then, until the truncate kicks in -- you will full scan all of the blocks.  But
ONLY you -- no other session will even consider touching your blocks.

      Rating:
      5
Sorry  May 23, 2003
Reviewer:  ZS
       from Australia
Sorry I just realized that I should not ask questions here.

      Rating: 5
Thanks
      alot  May 25, 2003
Reviewer:  ZS  from
      australia

      Rating: 3
Can GTT be used
      in recursive calls to procedure???  August 21, 2003

Reviewer:  Suvarna  from Manchester, UK
Hi Tom,

 A call to procedure modifies some values and I need these values in next call
to the same procedure rather than the actual table values. For this I am using a
GTT. I have set on commit preserve rows option, but I find nothing written in
the global temp table. If GTT is replaced with a regular table, it works fine.
Not able to figure out why?

          Followup:

give us a simple, small, concise, self contained test case to demonstrate the
issue.

I don't see what the problem is yet...

      Rating: 5
altering global
      temporary tables  September 04, 2003
Reviewer:
      Yogeeraj  from Mauritius
Dear sir,

I have case where on our production database a table was created with the "on
commit delete rows" clause instead of "on commit PRESERVE ROWS"

The "alter table" command is not working :
===========================================================================
yd@ORA817.MU> CREATE GLOBAL TEMPORARY TABLE T_ORDERS (
  ORDERNO     VARCHAR2 (8),
  JOBNO       VARCHAR2 (6),
  COLOUR      NUMBER (2),
  GMTSEQ      VARCHAR2 (1),
  EXPQTY      NUMBER (6))   ON COMMIT DELETE ROWS;

Table created.

Elapsed: 00:00:00.03
yd@ORA817.MU> alter table t_orders on commit delete rows;
alter table t_orders on commit delete rows
                     *
ERROR at line 1:
ORA-01735: invalid ALTER TABLE option

Elapsed: 00:00:00.01
yd@ORA817.MU>
===========================================================================

The documentations does not mention anything about it....

http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a85397/statem2a.htm#2054899
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a85397/statem3e.htm#2061078

Do we have "drop and recreate the table" as the only solution?

thank you in advance for your reply.

best regards
Yogeeraj

          Followup:

seems so, yes.

      Rating: 5
Temporary table
      for Report???  October 18, 2003
Reviewer:
      Shahadat  from Bangladesh
Dear Tom

I have created a temporary table for report runing but its not working.After
inserting data in a temporary table by using complex query,now i wanted it to
generate a report on the basis of temporary table.
But when i generate report,no data in the report runtime but in pl/sql it shows
well.
What's the problem.
--version
Oracle8i Enterprise Edition Release 8.1.6.0.0 - Production
PL/SQL Release 8.1.6.0.0 - Production
CORE    8.1.6.0.0       Production
TNS for 32-bit Windows: Version 8.1.6.0.0 - Production
NLSRTL Version 3.4.1.0.0 - Production

CREATE GLOBAL TEMPORARY TABLE GTEMPREGHIS (
  BIN            VARCHAR2 (10),
---------------------------------------
  TAXPAYER_DES   VARCHAR2 (65))on commit DELETE rows;

          Followup:

no idea -- given that "plsql can see it", you have to understand that "it was
there"

perhaps the report generating tool you are using (which is not named) is doing a
commit and you have "on commit DELETE rows" -- most likely culprit here.

      Rating: 5
not working
        October 19, 2003
Reviewer:  A reader

Hi Tom
It's not working whatever on commit/on commit delete
.
Report Builder 6.0.8.8.3

create global temporary table
t (x int,y varchar2(2));
whatever on commit/on commit delete
what should i do?does it mean that GTT is only for plsql?I created view by using
GTT,it did not worke as well.

          Followup:

are you populating the temp table IN THE REPORT or in another session?

      Rating: 4
TO: Shahadat
      from Bangladesh  October 20, 2003
Reviewer:
      Jens  from Hamburg
I do the same with Oracle Reports and it works fine. I defined an
after-parameter-form-trigger where i start the procedures to fill the global
temp. tables.

Are you sure, that you use the same session for filling the GTT and running the
report?

      Rating: 5
GTT problem
      reply  October 20, 2003
Reviewer:  Shahadat
       from Bangladesh
Hello Tom/Jens

Thanks for reply.I know GTT works in plsql/reports trigger individually.I also
know that whenever session changed it will not work.
But i wanted GTT by manipulating from Forms6i and then show the manipulation
result in the report6i.But its not working.
Normally we manipulate from forms then insert it into permanet table(otherwise
we use view) and then report.I wanted to replace this permanent table by GTT.Is
it possible?Please give me advice.

          Followup:

you switched sessions.

you fill it in forms (session 1)
you try to read it in reports (session 2)

you need to

a) fill it in reports
b) read it in reports

that is the ONLY way.

October 21, 2003 Lisa -- Thanks for the question regarding "J2EE
      and Oracle's stored procedures", version 9.2.0

      You Asked
Dear Tom,

I am a database developer/development DBA who ascribes to your development
philosophy and approaches.  I am also a Java certified programmer, although I do
not claim Java as a primary skill.   Recently, I have been tasked to serve as
the lead architect for our system.  Sounds great, however, I have a partner who
is a die-hard Java J2EE type.  Here lies the beginning of a power struggle.

Our system is a legacy, enterprise-wide OTLP web application, with some data
warehousing and reporting components and multiple interfaces to external
systems.  Fully deployed, our database will size in the Terabytes.
Unfortunately, every new mini-application added to date has used a different
approach to architecture.  Some applications have SQL embedded right in the Java
business logic code (initially delivered without bind variables by my new
partner), other approaches use SQL externalized in XML, and finally the approach
that our database team lead relies 100% on stored procedures and a single
package interface.

Tasked with defining the new Java/Oracle architecture, I am struggling with how
to defend my position to keep all SQL within the database.  I know that I do not
have to present my arguments to you, but given the large database volume, the
complete lack of SQL skills that I have seen come out of Java development to
date, and the poor legacy database design which necessitates complex SQL, I feel
that our system will absolutely not perform if I leave Java developers free to
write SQL as they wish outside the database.

After preliminary talks with my new partner I know that he is loathe using any
Stored Procedures.  I have done research trying to understand why J2EE
developers are so scared of making use of the database, and so far all that I
can gather (other than just plain arrogance) is that they feel that somehow
using Stored Procedures makes the system hard to maintain and not scalable.
As far as I’m concerned, as long as you maintain and abide by generic
interfaces, the implementation of data access is just that, implementation.  Can
you give specific arguments to help with this debate and advice on how to
convince Java developers?  I know that I am not alone in this battle; the war is
waged in any development shop where J2EE is the word of the day.

Regards,

Lisa

      and we said...
well -- the problem is this...

In order to scale java - you need tons of small machines spread out.  that is
what they see.  lots and lots of machines = the way to scale.  I've seen people
time and time again take my stored procedure based approach (application already
written -- works, runs, scales, in fact excess capacity on the server) and
convert it to java.  They go from a single machine hosting the http daemon and
database to 4-8 front end processors for running java and a BIGGER database
machine that never seems to keep up.  What happened?  they didn't "like" the
stored procedures (they don't scale they say...).  So, they reimplement in java
and find they need N times the hardware to do the same thing slower.  The
database machine is bigger cause they are killing it.  They can scale all righty
- they HAVE to.

They have their hammer, everything is a nail.  the way to scale for them is to
add more hardware.  if you cannot add an infinite amount of hardware you must
not be able to scale right?  (wrong).

You know what will make the database fall over in a spectacular fashion?
Through some really bad sql at it.  Make this sql serialize.  Parse statements
like there is no tomorrow.  Best way to achieve that?  You already answered that
question -- "but given the large database volume, the
complete lack of SQL skills that I have seen come out of Java development to
date, and the poor legacy database design which necessitates complex SQL, I feel

that our system will absolutely not perform if I leave Java developers free to
write SQL as they wish outside the database"

Ask them for specific bullet points as to why the system would be harder to
maintain when you can have that awesome separation of skills.  Procedural coders
(yes, Java is procedural coding -- OO whatever -- it is a procedural language)
stink at SQL unless they get into the "set based mindset".  I can do both
equally well -- I was born and bred a coder long ago.  But once I got it through
my head that "sql is about sets", things got really easy.

the system would in fact be easier to maintain, tune and fix as the SQL would be
coded by people who can do SQL -- which is not a java programmer.  The sql
people cannot in general code java either -- it is an even trade.  they do what
they are good at, we do what we are good at.

Ask them "so, if i identify your poorly performing sql -- how the heck are you
going to fix it as it most likely involves ripping out half of your code since
you must change your algorithms.  rather then you worry your head about that,
I'll take care of it for you"

Also, ask them "so, is J2EE the last thing?" -- and make them answer with a
perfectly straight face please.  The day after it goes production, it is the
existing legacy system we'll be interfacing to in 5 years.  If I dig out my "C
programmers Journal's" from the late 80's/90's -- you would discover that today
we shouldn't even be WRITING software!  why not?  why because the objects that
we created in the 90's would be our components of the 21st century and we would
just assemble any application from these off the shelf components.  Programming
would be dead, we'd be assemblers crafting new application from the old.

anyone out there doing that?

remember "AI"?
expert systems?
CASE?

It is a religous war though, they cannot be won by reason or objectivity.

Review &
      Followup

      Rating: 4
Some other
      reasons why your Java SQL code does not perform  August 31,
      2003
Reviewer:  Eric Ma  from New Jersey, USA

In my opinion, a Java application built on top of an Oracle database fails to
perform not because of less efficient SQL is written (although if you ask a Java
developer what EXPLAIN PLAN and tkprof mean you probably will not get an instant
answer), but because of the following:

1. Use of multiple database calls, instead of combining several SQL's into a
single stored procedure to reduce network trips as well as database efficiency.

2. Ignoring the advantage of SQL in set operations by  getting a complete
resultset, turing it into a Collection of Java objects, and then using
collection iterator to find the desired records.

3. Ignoring the difference between Read-Write and Read-Only situations, and
allowing EJB container to unnecessarily maintain Tx context when the data is
read-only.

4. Poor use of EJB entity beans without understanding the limitations.

Having said the above, it does pay to maintain business logic outside the
database and in Java middle-tier components.  The key is to know where to draw
the line.  I suggest all J2EE developers to read Tom's Expert One-on-One Oracle
and Rod Johnson's Expert One-on-One J2EE Design and Development book in order to
strike a balance between a Java centric and database centric approach.

          Followup:

1) well said

2) :)

3) and not really understanding the difference at all....

4) big time....

application logic -- i agree.

BUSINESS LOGIC -- that, well, no, i agree to disagree.  unless your business is
J2EE and that alone.  business rules = data rules = close to data.  a business
rule could be "you can only be in EMP if there exists a record in DEPT".  would
you argue that should be enforced outside?

application logic = something outside of the database.

      Rating: 5
EJB
      QL  September 01, 2003
Reviewer:
      A reader
Hi Tom

There are features like EJB QL that are being added in J2EE. But in this version
there are lots of limitations and I feel it is not worth using. Do you think it
will pick up? I feel it would be very difficult to tune/debug such features as
you don't know until runtime what SQLs are being fired.

Thanks

          Followup:

i agree with you.  i'll go a step further - it'll not really be POSSIBLE to tune
since the people doing it will know nothing about the tool they are really using
-- the database.  applications live and die based on how they use it.  I see
lots of premature deaths out there.  some things cannot be fixed by adding yet
another tier.

      Rating: 3
J2EE apps
      should use database features intelligently  September 01,
      2003
Reviewer:  Eric Ma  from New Jersey, USA

For the majority (>95%) of web-based applications today, which are HTML
forms/views on top of database tables (I am afraid to say this site is one of
them), I have no doubt that the stored procedure approach probably offers the
best performance/cost ratio.  As a matter a fact, this approach used to be the
best practice promoted by Microsoft, in their DNA (distributed network
application architecture) and early .NET days.  If anyone paid any attention,
last year .NET beat J2EE hands down WRT performance in the now infamous .NET
PetShop vs. J2EE PetStore benchmarking exercise, because M$FT was using stored
procs, and the Java camp was using BMP entity beans for God's sake!

However, with the remaining 5% of all apps, as well as some of the 95% of the
apps that I mentioned above to which absolute performance is not the top
priority, using middle-tier component-based architectures offer great benefits.
Yes, this means more computing power is needed, but hardware and software are so
cheap today (Dell servers and Linux anyone? :-).  Now think about the benefits
you gain:

1. For frequent web page look-and-feel changes, you don't need to touch your
stored procedures, assuming the stored proc is actually generating the HTML.
2. For business rule related changes, hopefully it is a matter of changing XML
configuration files, not your Java classes or stored procedures.  Think about a
workflow type application.
3. Think about the scenario where you have to integrate another data source,
which happens to be M$FT SQL Server.

I can list more of these.  My bottom line is, one has to adopt a pragmatic
approach when building applications, just like in data modelling one has to
strike the right balance between following perfect normal forms and
denormalization for query performance.  Stored procedures are great, if in the
same operation I need to touch multiple tables, especially for retrieving data
(REF CURSOR is my best friend).  However, logic such as "add 1000 to sal if comm
is >= 5000" is best kept outside the database, because tomorrow 1000 can be
changed to 2000.

Just my 2 cents.

          Followup:

dude, you are so preaching to the choir here.

I agree with you 110%

one has to adopt a pragmatic
approach when building applications

smartest thing anyone will say here today I am sure.

well, maybe 99.999%

However, logic such as "add 1000 to sal if comm
is >= 5000" is best kept outside the database, because tomorrow 1000 can be
changed to 2000.

is something I disagree with -- why keeping that fact outside the database
(seems to me that FACT is in fact DATA itself that should be in a table perhaps
for safekeeping, administrative purposes :)

My personal gripe is -- that 95% is huge.  People take the 100% j2ee for
everything approach.  basically 95 times out of 100 or more -- it is not the
right approach and it *kills* people.

      Rating: 5
our
      experience  September 01, 2003
Reviewer:
      Alberto Dell'era  from Milan, Italy
We have had an experience quite similar to Lisa's one when we started to design
the new version of the main site of a customer who ranks as the 1st/2nd (depends
on who you ask ;-) ) company in his huge market sector, with millions of
registered users.

We had a legacy Oracle database with a lot of stored procedures, and we had to
replace a Microsoft-based web site with a new one based on J2EE. Our final
(successfull) architecture has been based on Apache, a J2EE Web Server, a J2EE
Application Server, and of course the lovely&lively Oracle database.

The web server calls the app server's Session EJBs which in turn call stored
packages to do all the data-related work.

This Architecture has been *blessed* by our J2EE Architects, who have pointed
out e.g. the limitations of Entity EJBs in  sectors like concurrency &
consistency (what if both nodes of a clustered app server try to update the same
Entity bean - which wins ? Which version of the data they see ? This is not part
of the J2EE standard ..) and their being "hard to use" in general, and so on.

Actually we use every possible J2EE technology out there (JSP, Servlets,
java-based XSLT and XML, JMX ... name one, we have it!) but for the database
access, we use the approach you suggest - JDBC and stored packages.
Guess what ? Even if the database has only 8 CPUs and 2Gigabytes of Ram, we
haven't had a single serious issue in 6 months of production - neither
consistency, nor performance. The dream of any manager.

And the Java developers are more than happy to leave the "boring" database stuff
to the database developers - who find the same database stuff "cool and
fascinating". There are a lot of cool features in J2EE to play with, and they
don't miss at all the SQL - they see the packages as "big classes with only
static methods", and they're happy with it [Well the smartest ones actually like
sql too - but that's another story ;-)], especially the junior developers who
know everything about Java but almost nothing of J2EE - they are able to use the
database in a matter of hours.

Hope this helps winning your "battle" - at the end you will all win. Guaranteed.

Alberto

      Rating: 5
The debate
      continues  September 02, 2003
Reviewer:  Lisa
      Spory  from Fairfax, VA
Tom,

Thank you for response.  You instructed me to "Ask them for specific bullet
points as to why the system would be harder to maintain when you can have that
awesome separation of skills...” Well, it just so happens that I had an
interesting conversation Friday via IM with a friend that I consider to be a
J2EE guru, and a pretty smart and reasonable guy despite the fact ;)

Here are some particular excerpts:

Java Guy - "The trend is to move toward persistence frameworks (e.g. TopLink)
and actually let the frameworks write the SQL...less errors, fewer portability
issues and better performance...one part of the performance sales pitch is that
framework developers will know how to optimize the SQL for a particular
database..."

Lisa - "So another case of Java developers thinking that they know all and can
generically optimize to any database, any situation?"

Java Guy - "..one reason this trend may have arisen is that Java is a very open
source technology...and Oracle costs a fortune..."

Java guy - "most apps can afford to sacrifice performance for ease of
development and maintainability...many developers don't like stored
procedures...it's definitely a prejudice...my opinion why...they [9 out of 10
DBAs] don't normally control them...they almost always live outside of CM...they
don't play well in the build...and the big one for Java types, they are not
portable...it is more of a human nature thing than a compelling piece of
logic..."

Lisa - "My experience with 9 Java developers...they are so enamored with what
they can do that all 9 will come up with their own way of doing things.  Reuse?
Haven't seen it happen...however the Stored Procedure my colleague just wrote
will be used by both Java and legacy code..."

We go on to argue these points; in the end he conceded that for my particular
situation Stored Procedures might be acceptable (but keep out that business
logic!!).  But again, he only agreed for my situation (legacy code, large
database, poor database design).

Bottom line - I asked for specific reasons to back up his argument and basically
got nothing other than it's a matter of personal preference, that perhaps Java
developers don't like Oracle because it's expensive, and a half-baked CM
argument (ALL our database code lives in CM and we can and do support automated
builds).

My plan of attack is set.  I can and will come up with specific bullet points as
to why we should use Stored Procedures and keep SQL in the right hands.  Unlike
a human nature based argument, I have proof (tkprof and 100's of bad SQL
statements in Production today due to Java developers).

Regards,

Lisa

          Followup:

if this:

"most apps can afford to sacrifice performance for ease of
development and maintainability"

were true, I would be retired as there wouldn't be any need for me.

ask him to give us a "business logic" example that needs to be kept out of the
database.  I'd be interested.  I find most of the time, they need to actually be
IN the database.

business logic = all about data
application logic = not all about data, all about making it look pretty.

      Rating: 5
Business
      Logic  September 02, 2003
Reviewer:  Lisa
      Spory  from Fairfax, VA
Tom,

My Java Guru is on vacation, so I asked another Java colleague for an example of
business logic that must be performed outside the database...

Java Gal - "You cannot schedule an appointment for more than 7 days in advance"

Lisa - "How about -'You cannot schedule an appointment for more than x days in
advance, where x is a dynamic value stored in table t'

Java Gal - "...stored in table t or in an xml config file that can easily be
modified...config file more easily allows different configurations of the BL to
be applied to different users..."

Lisa - "Until your customer wants to be able to dynamically set x...."

Java Gal - "...which can be written back out to the config file.."

Lisa - "Why do all this when you have a database for the pure reason of storing
data?  Seems rather round-a-bout to me...."

Java Gal - "...why do you need a db?...some systems are simple enough that you
can forego the whole expense of a db and just store info as flat files...not all
dbs operate the same, but an xml file is an xml file and can port to any
platform"

So there you go - why do we need a database at all when we have XML!!!!

Sounds like we can all retire ;-)

Regards,

Lisa

          Followup:

if that "business rule" ain't in the database what happens when new UI "foo"
comes along????

are they going to duplicate it???

I fail to see how a config file that has to be backed up, maintained, not lost,
edited separately -- would be easier then a small admin screen that does CRUD on
a config table personally.

there is no winning this argument -- unless you just edit their file and mess it
up.  they'll say "hey, you weren't supposed to do that", I'll say "you have
nothing preventing me from doing so".

The person who says you don't need a db is not anyone you'll be able to
convince, until they actually lose their data.

      Rating: 5
Code Generators
        September 04, 2003
Reviewer:  Lisa
       from Fairfax, VA
Tom,

What is your take on persistence frameworks (i.e. code generators)?  My
counterpart is saying "no way, won't happen" to having every single SQL
statement wrapped in a stored procedure.  He wants to use a persistence
framework (Hybernate, TopLink) to remove SQL writing from the hands of
developers all together.

How do you feel about using O/R mapping against views and not directly against
tables?  Although the SQL is dynamically generated, it will be generated against
a single view.  As database experts we are then free to tune the underlying
views as needed.  This is very similar to an approach my colleague and I took
within a PL/SQL package designed to support "adhoc-like" queries, we dynamically
wrote very simple SQL against pre-defined complex views.  In the case of
multi-table updates we utilized instead-of triggers.

I understand that this is a compromise, using pure Stored Procedures will not
only let us tune single SQL statements but also consolidate database trips, etc.
 However, I highly doubt that I will be able to force 100% use of Stored
Procedures on our program.  He is not against the use of Stored Procedures
entirely, just the exclusive use of Stored Procedures.

As long as the framework supports the use of Stored Procedures, and as long as
generated SQL is executed against views and not tables (therefore no joins),
isn't the goal of keeping the SQL in the database pretty much accomplished?  Do
you see hidden dangers in this approach?

Regards,

Lisa

          Followup:

can't you guess based on the above?  :)

      Rating: 3
My 2 cents to
      Lisa's Most Recent Post  September 05, 2003

Reviewer:  Eric Ma  from New Jersey, USA
Hey Lisa, sounds like you have struck the right balance between pure J2EE and
PL/SQL approaches.  It could be a win-win situation (seriously).  Let us know
how the solution works out.  I have not heard anyone using O/R mapping against
views which uses INSTEAD OF triggers for CRUD.  I am very interested in your
experience.

      Rating: 5
business logic
      outside the database  September 07, 2003
Reviewer:
      Stu Charlton  from Tokyo, Japan
The trend over keeping business logic out of the database is one that I don't
think anyone is going to crack.  This is something that has been hammered into
developers heads over and over for the past 10+ years, and it's not going to go
away.

I can understand where Tom is coming from on this.  Keeping validation
constraints in the database is obvious if you want to eliminate duplication of
effort. But J2EE people are frightened by the database for many cultural
reasons, primarily its proprietary nature, but also because they're first and
foremost "developers" and don't think of things in terms of a "system".  The
object oriented paradigm is a very "closed world" model.

But there are several reasons for why business logic is kept outside of the
database, most not applicable to all of these J2EE web applications.

a) a lot of data is "live".  In financial services, it comes from market data
feeds, order feeds, execution feeds, etc., all with a different format.  Much of
this is transient data, does not need to be transactional, and requires a place
to store some fairly complex rules for mapping one data protocol into another
and also business /regulatory rules for orders & executions.  The database
probably isn't the best place for this.  Financial services are a big supporter
of J2EE for this reason.

b) a lot of data is NOT relational.  Most large companies have significant
investments in ISAM/VSAM and IMS that they still don't want to give up.
Business rules need to run on top of this data given the most "productive"
toolset (which, today is arguably J2EE).  Lots of heavy legacy-invested
industries like telecom and personal banking are therefore happy with J2EE for
this reason.

c) a lot of rules are complex.   People are frightened by the hideous complexity
of the monster IF-statements of the past procedural COBOL programs that housed
such "business logic".   They equate stored procedures in Oracle with these
massive COBOL programs, because usually these procs in legacy systems processed
things one-at-a-time.

So the argument goes - well if a stored procedure is about processing things one
at a time in a huge nested-IF statement, wouldn't it be more maintainable to
factor those if statements out into objects instead?  (The kicker is the proc
never had to be written that badly in the first place, but tell that to 8+ years
of PL/SQL programmers that did otherwise :-)

So the "complex rule" scenario is something that occurs mainly utilities
(billing systems!), manufacturing, aviation scheduling, and some exotic
financial systems (fixed income or derivatives trading, where traders can create
new products on the fly, or quantitative analysis that need to tweak risk
algorithms on the fly with a scripting engine).. All of this stuff probably
should happen in an environment outside of the database.

In a more general sense, many complicated constraint validations that deal with
sets of data have to be written in rather complicated triggers - especially
time-oriented data. One might suggest it's worth the overhead to attempt to
write this sort of validation in an object oriented fashion, and then to pick
the "hotspots" and optimize them into triggers.

As for the O/R mapping trend, it definitely is a growing trend, and I actually
see it as a potentially good thing now that the frameworks are getting quite
mature.  TopLink gives you tremendous flexibility with what kind of SQL is
generated and allows you to tweak this or delegate into stored procedures if you
really want to.  The tradeoff is that now you have a fairly complex framework
that you have to learn to use with your application.  Most apps don't need this:
a few stored procedures will do.  Some apps, on the other hand, can benefit from
this, especially ones that deal with primarily transient data that sometimes
needs to be turned into a database transaction (I'm thinking financial systems
here again: order systems that involve lots of non-transactional updates to a
shared cache, and a few transactional updates).

A good summary of the various approaches to DB logic, from an object-oriented
developer's perspective, was written by Martin Fowler on his website:
http://www.martinfowler.com/articles/dblogic.html
He's one of the few object-oriented experts that is pragmatic enough to
recognize the advantage of using SQL and stored procedure, but he also is very
familiar with the OO culture where so many devs run away screaming from SQL, so
he tries to address both sides in this article.

      Rating: 5
re: business
      logic outside database  September 30, 2003

Reviewer:  Menon:)
From Martin Fowler's article that Stu posted above
( just looked at the conclusion ):
" would suggest that if you go the route of putting a lot of logic in SQL, don't
expect to be portable - use all of your vendors extensions and cheerfully bind
yourself to their technology. **If you want portability keep logic out of
SQL.**"

I agree with the first half - don't think I agree with  the last statement.
Portability is usually associated with  platforms and in that sense if your
logic is in sql or pl/sql it is automatically ported to all platforms that the
underlying database is ported to. If you don't want
to tie yourself to uyour database - then you have a
bigger problem - you lose out on the power of the database and also this can
result in erroneous logic (Tom has
shown this again and again)

I was a fan of Martin Fowler - till I glanced through
one of his latest book on architecture
"Patterns of Enterprise Application Architecture"
It was good from OO point of view but lacked the
sophistication from the database point of view IMO.

Just my .5 cents:)

      Rating: 5
Tom your
      feedback on Stu Charlton's Review  October 06, 2003

Reviewer:  Bala  from Kuala Lumpur
Tom I am a newbie, working mostly on the database and stored procedures for the
past 2 years. We have these discussions about(the new guys..) chosing
architectures, performance time and again....
I found this thread very useful in making some of our decisions...
Need  your take on Stu Charlton's review above (point by point)
That'll help consolidate things. Please address the issues based on the nature
of data(domains and the kind of data being handled.. e.g financials, derivatives
data etc.. like what he has mentioned)

      Rating: 4
Use Facade and
      Factory Patterns In Java  October 20, 2003

Reviewer:  Jon
Hi All,

As always with any debate, there are advantages and disadvantages on both sides.

Coming from the Java camp, the biggest advantage of putting the logic in Java is
that we are not tied to any database vendor.  However, from the database
developers point of view, we are not utilizing the database features and fast
performance (i.e. no network access latency, closer to data, etc).

Like solving most problems, I put another abstraction layer in the application.
I create an wrapper class of the stored procedure.  My application code will
ONLY interact with the wrapper class.

If my company decides to use another database, I just have to plug-in the a new
wrapper class that interact with the new database's stored procedure.

Regards,
Jon

P.S. The JPublisher inside JDeveloper to generate Java wrapper class of the
PLSQL package is a great tool.  It is a bridge between Oracle and Java.

          Followup:

that is EXACTLY what I propose in my books :)

the best (and only) path to database independence is to put all things database
into the databases stored procedure language.

you hide the different concurrency models
you hide the different features
you are ALLOWED to use (exploit) the different features

perfection

      Rating: 5
Your
      feedback  October 21, 2003
Reviewer:  Bala
       from Kuala Lumpur
Tom I posted this above, somehow never got your answer
-
"Tom I am a newbie, working mostly on the database and stored procedures for the

past 2 years. We have these discussions about(the new guys..) chosing
architectures, performance time and again....
I found this thread very useful in making some of our decisions...
Need  your take on Stu Charlton's review above (point by point)
That'll help consolidate things. Please address the issues based on the nature
of data(domains and the kind of data being handled.. e.g financials, derivatives

data etc.. like what he has mentioned)"

          Followup:

i didn't feel like taking stu's thing apart point by point, it is just
information -- everything is valid.  use the data here to form your own
judgements.

i basically agree with what he said -- the problem isn't that the db shouldn't
do it, but that people write bad code sometimes (and then blame the db)

there is nothing to really "say"

      Rating: 5
argument with
      no end  October 21, 2003
Reviewer:  a reader

I have the same issue, being a DBA and loyal Tom follower, of trying to sell the
idea ( "sell" being the keyword ) to developers of why they should implement the
use of packages and procs in our J2EE application.
One of the problems of the issue for me is the perception of the DBA ( although
we are the experts in understanding HOW the database works, and WHAT works BEST
for each RDBMS - aka SQL Server is not Oracle is not Sybase is not DB2 ).  Why
the developer with black-box intuition/mentality is given the power to make
architectural decisions without DBA consent is a mind blower to me.  It is
because they want the "independence".
Now they believe they have the independence without using the RDBMS features.
They have written ( or bought ) email functionality + queuing functionality +
blob processing functionality => the list goes on an on.
Now they want to change the main UI and that will require a rewrite of the
schema - so they are not actually independent but dependent on a technology.
So now in 4 years when J2EE is replaced by someother new technology - another
rewrite will be in order ??
Our application has gone out BETA and now they realize J2EE is as slow as the
old technology. Not one SQL statement has come across my desk.  The reactive
approach is now to buy a 3rd party app to "tune" the J2EE app, and everyone
waves arms and cheers ( i roll my eyes and bury my head in my hands ).
My point is there is a set of tools, and they should be used accordingly. The
teams should TALK and DESIGN for performance right from the START.  Design with
the customer/end-user in mind. Understand performance goals and how the data is
used before coding - then use the right hammer.

      Rating: 5
Wow... great
      explanation from great people  October 23, 2003

Reviewer:  A reader  from from TX, USA

Tom, it's really very useful thread. i am keeping this page in my personal
database.

      Rating: 5
independence
      from the dba  October 25, 2003
Reviewer:  Stu
      Charlton  from Tokyo, Japan (for 1 more day!)
"Why  the developer with black-box intuition/mentality is given the power to
make architectural decisions without DBA consent is a mind blower to me.  It is
because they want the "independence"."

Two reasons:
a) A culture of cyncism within many DBA groups leading to distrust.

Management overburdens DBA groups with operational duties.  They usually do not
reward them for developer support and risk taking (using new features).

This leads to an acrimonious relationship with developer groups, and thus
developers eventually develop the attitude that they should "avoid the DBA".
They carry this attitude with them to other companies, and the attitude spreads,
like a virus.

Of course, developers can develop the same attitude too, which leads management
to not trust the developers and look towards outsourcing...

b) A widespread, but shallow, knowledge of "lock-in" economics.

This is the theory is that it costs you more in the long run due to "switching
costs" if you are tied to a single vendor for any service.  The reason it's
shallow knowledge because it doesn't view this as a trade-off, it views it as a
"rule of thumb".

And if you've hung out on Tom's site for any amount of time, you'll know that
rules of thumb are often wrong, even if they have an element of truth to them
under special circumstances.

Some thing have naturally high switching costs:  switching office buildings, for
example.  Arguably, a database is similar, but "enterprise software" consultants
pride themselves on soundbites for executives that are under pressure to keep
costs down and don't have the attention span to truly think about the issue at
hand.

      Rating: 5
My Two cents (A
      Java Developers point of view).  October 26, 2003

Reviewer:  R&R  from Philadelphia, PA USA

After being in the IT field for over 5 years, I've grown to love trying to keep
various components of logic sperate in terms of technologies. Embedding SQL in
Java code in my opinion is a very poor design and often leads to large amounts
of code changing and testing to perform a simple change. I am all for the use of
Procedures Vs Embedding the SQL within the Java code. For someone to make the
statement that SQL Should be placed within the Java code, is pure ignorance or
fear of giving up power to those that can better manage it within stored
procedures.

      Rating:
      5
Contradiction  October 29, 2003
Reviewer:
      Kimathi  from Los Angeles
Here is an interesting snippet of contradiction.

Chapter 7 - Data Access in J2EE Applications
Expert One-on-One J2EE Design and Development
by Rod Johnson

"Important     Do not use stored procedures to implement business logic. This
should be done in Java business objects. ....."

Your thoughts Tom?

October 21, 2003 Shruti -- Thanks for the question regarding
      "Owa_sylk utility", version 4.0.7

      You Asked
Hi TOM, I have seen your hints about the above utility.

I am trying to query a table dynamically and trying to get the
out put via Oracle application server into my client machines
excel.

My server is HPunix.and I am trying get the output via my
windows 98 machine

Follwoing hint you have given

Create or replace procedure emp_ss
as
begin
owa_sylk.show('select * from emp');
end;
But owa_sylk I am using has diffrent number of arguments for
procedure show. Please point me to right one.

Please let me know if I am on right track.

Thanks,
Shruti

      and we said...
Ok, the owa_sylk I put up for another question was for output to
a file on disk.  Here is the original one for output over a
webserver.  First the example:

ops$tkyte@ORA8I.WORLD> begin
  2      owa_util.mime_header( 'application/excel' );
  3      owa_sylk.show(
  4          p_query => 'select empno id, ename employee,
  5                             sal Salary, comm commission ' ||
  6                     'from scott.emp ' ||
  7                     'where job = :JOB ' ||
  8                     'and sal > :SAL',
  9          p_parm_names =>
 10                 owa_sylk.owaSylkArray( 'JOB', 'SAL'),
 11          p_parm_values =>
 12                   owa_sylk.owaSylkArray( 'MANAGER', '2000'
),
 13         p_sum_column =>
 14                   owa_sylk.owaSylkArray( 'N', 'N', 'Y',
'Y'),
 15          p_show_grid => 'NO' );
 16  end;
 17  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD> exec owa_util.showpage
Content-type: application/excel

ID;ORACLE
P;FCourier New;M200
P;FCourier New;M200;SB
P;FCourier
New;M200;SUB
F;C1;FG0R;SM1;G
F;C2;FG0R;SM0
F;C3;FG0R;SM0
F;C4;FG0R;SM0
F;C5;FG0R;SM0
F;R1;FG0C;SM2
C
;Y1;X2;K"ID"
C;X3;K"EMPLOYEE"
C;X4;K"SALARY"
SION"
C;Y3
C;X2;K7782
C;X3;K"CLARK"
C;X4;K2450
C;X5;K""
C;Y4
C;X2;K7698
C;X3;K"BLAKE"
C;X4;K2850
C;X
5;K""
C;Y5
C;X2;K7566
C;X3;K"JONES"
C;X4;K2975
C;X5;K""
C;Y7
C;X1;K"Totals:"
C;X4;ESUM(R3C:R5C)
C;X5
;ESUM(R3C:R5C)
F;W1 1 7
F;W2 2 4
F;W3 3 8
F;W4 4 6
5 10
E

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> declare
  2      l_cursor integer default dbms_sql.open_cursor;
  3  begin
  4      dbms_sql.parse( l_cursor,
  5          'select empno id, ename employee,
  6                  sal Salary, comm commission ' ||
  7            'from scott.emp ' ||
  8            'where job = ''MANAGER'' ' ||
  9            'and sal > 2000',
 10          dbms_sql.native );
 11
 12      owa_util.mime_header( 'application/excel' );
 13      owa_sylk.show(
 14          p_cursor => l_cursor,
 15          p_sum_column =>
 16              owa_sylk.owaSylkArray( 'N', 'N', 'Y', 'Y' ),
 17          p_show_grid => 'NO' );
 18      dbms_sql.close_cursor( l_cursor );
 19  end;
 20  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD> exec owa_util.showpage
Content-type: application/excel

ID;ORACLE
P;FCourier New;M200
P;FCourier New;M200;SB
P;FCourier
New;M200;SUB
F;C1;FG0R;SM1;G
F;C2;FG0R;SM0
F;C3;FG0R;SM0
F;C4;FG0R;SM0
F;C5;FG0R;SM0
F;R1;FG0C;SM2
C
;Y1;X2;K"ID"
C;X3;K"EMPLOYEE"
C;X4;K"SALARY"
SION"
C;Y3
C;X2;K7782
C;X3;K"CLARK"
C;X4;K2450
C;X5;K""
C;Y4
C;X2;K7698
C;X3;K"BLAKE"
C;X4;K2850
C;X
5;K""
C;Y5
C;X2;K7566
C;X3;K"JONES"
C;X4;K2975
C;X5;K""
C;Y7
C;X1;K"Totals:"
C;X4;ESUM(R3C:R5C)
C;X5
;ESUM(R3C:R5C)
F;W1 1 7
F;W2 2 4
F;W3 3 8
F;W4 4 6
5 10
E

PL/SQL procedure successfully completed.

You have to call owa_util.mime_header first with the appropriate
mime type and then it does the rest.  Here is the code:

Rem
Rem $Id$
Rem
Rem  Copyright (c) 1991, 1996, 1997 by Oracle Corporation
Rem    NAME
Rem      owasylk.sql - Dump to Spreadsheet with formatting
Rem   DESCRIPTION
Rem     This package provides an API to generate a file in the
Rem     SYLK file format.  This allow for formatting in a
Rem     spreadsheet with only a ascii text file.  This version
Rem     of owa_sylk is specific to Oracle8.
Rem   NOTES
Rem
Rem   MODIFIED     (MM/DD/YY)
Rem     clbeck      04/08/98  - Created.
Rem     tkyte       09/10/00  - Made it use UTL_FILE.
Rem
Rem

/*
  This package allows you to send the results of any query to
  a spreadsheet using UTL_FILE

  parameters:
    p_query        - a text string of the query.  The query
                     can be parameterized
                     using the :VARAIBLE syntax.  See example
                     below.

    p_parm_names   - an owaSylkArray of the paramter names
                     used as bind variables in p_query

    p_parm_values  - an owaSylkArray of the values of the
                     bind variable names.  The values
                     muse reside in the same index as the
                     name it corresponds to.

    p_cursor       - an open cursor that has had the query
                     parsed already.

    p_sum_column   - a owaSylkArray of 'Y's and 'N's
                     corresponding to the location
                     of the columns selected in p_query.
                     A value of NYNYY will result
                     in the 2nd, 4th and 5th columns being
                     summed in the resulting
                     spreadsheet.

    p_max_rows     - the maxium number of row to return.

    p_show_null_as - how to display nulls in the spreadsheet

    p_show_grid    - show/hide the grid in the spreadsheet.

    p_show_col_headers - show/hide the row/column headers
                         in the spreadsheet.

    p_font_name    - the name of the font

    p_widths       - a owaSylkArray of column widths.  This
                     will override the default column widths.

    p_headings     - a owaSylkArray of column titles.
                     This will override the default column
                     titles.

    p_strip_html   - this will remove the HTML tags from the
                     results before
                     displaying them in the spreadsheet cells.
                     Useful when the
                     query selects an anchor tag. Only the
                     text between <a href>
                     and </a> tags will be sent to the
                     spreadsheet.

  examples:

    This example will create a spreadsheet of all the MANAGERS
    in the scott.emp table and will sum up the salaries
    and commissions for them.  No grid will be in the
    spreadsheet.

    begin
      owa_sylk.show(
        p_query => 'select empno id, ename employee,
                           sal Salary, comm commission ' ||
                   'from scott.emp ' ||
                   'where job = :JOB ' ||
                   'and sal > :SAL',
        p_parm_names => owa_sylk.owaSylkArray( 'JOB', 'SAL' ),
        p_parm_values =>
                 owa_sylk.owaSylkArray( 'MANAGER', '2000' ),
        p_sum_column =>
                 owa_sylk.owaSylkArray( 'N', 'N', 'Y', 'Y' ),
        p_show_grid => 'NO' );
    end;

    This example will create the same spreadsheet but will
    send in a pre-parsed cursor instead

    declare
      l_cursor number := dbms_sql.open_cursor;
    begin
      dbms_sql.parse(
        l_cursor,
        'select empno id, ename employee,
                sal Salary, comm commission ' ||
          'from scott.emp ' ||
          'where job = ''MANAGER'' ' ||
          'and sal > 2000',
        dbms_sql.native );
      owa_sylk.show(
        p_cursor => l_cursor,
        p_sum_column =>
            owa_sylk.owaSylkArray( 'N', 'N', 'Y', 'Y' ),
        p_show_grid => 'NO' );
      dbms_sql.close_cursor( l_cursor );
    end;

*/

create or replace
package owa_sylk as
--
  type owaSylkArray is table of varchar2(2000);
--
  procedure show(
      p_query         in varchar2,
      p_parm_names    in owaSylkArray default owaSylkArray(),
      p_parm_values   in owaSylkArray default owaSylkArray(),
      p_sum_column    in owaSylkArray default owaSylkArray(),
      p_max_rows      in number     default 10000,
      p_show_null_as  in varchar2   default null,
      p_show_grid     in varchar2   default 'YES',
      p_show_col_headers in varchar2 default 'YES',
      p_font_name     in varchar2   default 'Courier New',
      p_widths        in owaSylkArray default owaSylkArray(),
      p_titles        in owaSylkArray default owaSylkArray(),
      p_strip_html    in varchar2   default 'YES' );
--
  procedure show(
      p_cursor        in integer,
      p_sum_column    in owaSylkArray  default owaSylkArray(),
      p_max_rows      in number     default 10000,
      p_show_null_as  in varchar2   default null,
      p_show_grid     in varchar2   default 'YES',
      p_show_col_headers in varchar2 default 'YES',
      p_font_name     in varchar2   default 'Courier New',
      p_widths        in owaSylkArray default owaSylkArray(),
      p_titles        in owaSylkArray default owaSylkArray(),
      p_strip_html    in varchar2   default 'YES' );
--
end owa_sylk;
/
show error

create or replace
package body owa_sylk as
--
  g_cvalue  varchar2(32767);
  g_desc_t dbms_sql.desc_tab;
  g_lengths owa.vc_arr;
  g_sums owa.vc_arr;
--
--

  procedure p( p_str in varchar2 )
  is
  begin
    htp.p( p_str );
  exception
    when others then null;
  end;

  function build_cursor(
      q in varchar2,
      n in owaSylkArray,
      v in owaSylkArray ) return integer is
    c integer := dbms_sql.open_cursor;
    i number := 1;
  begin
    dbms_sql.parse (c, q, dbms_sql.native);
    loop
      dbms_sql.bind_variable( c, n(i), v(i) );
      i := i + 1;
    end loop;
    return c;
  exception
    when others then
      return c;
  end build_cursor;
--
--
  function str_html ( line in varchar2 ) return varchar2 is
    x       varchar2(32767) := null;
    in_html boolean         := FALSE;
    s       varchar2(1);
  begin
    if line is null then
      return line;
    end if;
    for i in 1 .. length( line ) loop
      s := substr( line, i, 1 );
      if in_html then
        if s = '>' then
          in_html := FALSE;
        end if;
      else
        if s = '<' then
          in_html := TRUE;
        end if;
      end if;
      if not in_html and s != '>' then
        x := x || s;
      end if;
    end loop;
    return x;
  end str_html;
--
  function ite( b boolean,
                t varchar2,
                f varchar2 ) return varchar2 is
  begin
    if b then
      return t;
    else
      return f;
    end if;
  end ite;
--
  procedure print_comment( p_comment varchar2 ) is
  begin
    return;
    p( ';' || chr(10) || '; ' || p_comment || chr(10) || ';' );
  end print_comment;
--
  procedure print_heading( font in varchar2,
                           grid in varchar2,
                           col_heading in varchar2,
                           titles in owaSylkArray )
  is
    l_title varchar2(2000);
  begin
    p( 'ID;ORACLE' );
    print_comment( 'Fonts' );
    p( 'P;F' || font || ';M200' );
    p( 'P;F' || font || ';M200;SB' );
    p( 'P;F' || font || ';M200;SUB' );
    --
    print_comment( 'Global Formatting' );
    p( 'F;C1;FG0R;SM1' ||
           ite( upper(grid)='YES', '', ';G' ) ||
           ite( upper(col_heading)='YES', '', ';H' )  );
    for i in 1 .. g_desc_t.count loop
      p( 'F;C' || to_char(i+1) || ';FG0R;SM0' );
    end loop;
    --
    print_comment( 'Title Row' );
    p( 'F;R1;FG0C;SM2' );
    for i in 1 .. g_desc_t.count loop
      g_lengths(i) := g_desc_t(i).col_name_len;
      g_sums(i) := 0;
      begin
        l_title := titles(i);
      exception
        when others then
          l_title := g_desc_t(i).col_name;
      end;
      if i = 1 then
        p( 'C;Y1;X2;K"' || l_title || '"' );
      else
        p( 'C;X' || to_char(i+1) || ';K"' || l_title || '"' );
      end if;
    end loop;
  end print_heading;
--
  function print_rows(
      c            in integer,
      max_rows     in number,
      sum_columns  in owaSylkArray,
      show_null_as in varchar2,
      strip_html   in varchar2 ) return number is
    row_cnt number          := 0;
    line    varchar2(32767) := null;
    n       number;
  begin
    loop
      exit when ( row_cnt >= max_rows or
                  dbms_sql.fetch_rows( c ) <= 0 );
      row_cnt := row_cnt + 1;
      print_comment( 'Row ' || row_cnt );
      --
      p( 'C;Y' || to_char(row_cnt+2) );

      for i in 1 .. g_desc_t.count loop
        dbms_sql.column_value( c, i, g_cvalue );
        g_cvalue := translate( g_cvalue,
                            chr(10)||chr(9)||';', '   ' );
        g_cvalue := ite( upper( strip_html ) = 'YES',
                             str_html( g_cvalue ),
                             g_cvalue );
        g_lengths(i) := greatest( nvl(length(g_cvalue),
                                  nvl(length(show_null_as),0)),
                                  g_lengths(i) );
        line := 'C;X' || to_char(i+1);
        line := line || ';K';
        begin
          n := to_number( g_cvalue );
          if upper( sum_columns(i)) = 'Y' then
            g_sums(i) := g_sums(i) + nvl(n,0);
          end if;
        exception
          when others then
            n := null;
        end;
        line := line ||
                 ite( n is null,
                      ite( g_cvalue is null,
                               '"'||show_null_as||
                                  '"', '"'||g_cvalue||'"' ),
                             n );
        p( line );
      end loop;
      --
    end loop;
    return row_cnt;
  end print_rows;
--
  procedure print_sums(
      sum_columns  in owaSylkArray,
      row_cnt      in number ) is
  begin
    if sum_columns.count = 0 then
      return;
    end if;
    --
    print_comment( 'Totals Row' );
    p( 'C;Y' || to_char(row_cnt + 4) );
    p( 'C;X1;K"Totals:"' );
    --
    for i in 1 .. g_desc_t.count loop
      begin
        if upper(sum_columns(i)) = 'Y' then
          p( 'C;X' || to_char(i+1) || ';ESUM(R3C:R' ||
                  to_char(row_cnt+2) || 'C)' );
        end if;
      exception
        when others then
          null;
      end;
    end loop;
  end print_sums;
--
  procedure print_widths( widths owaSylkArray ) is
  begin
    print_comment( 'Format Column Widths' );
    p( 'F;W1 1 7' );
    for i in 1 .. g_desc_t.count loop
      begin
        p( 'F;W' || to_char(i+1) || ' ' ||
            to_char(i+1) || ' ' ||
            to_char(to_number(widths(i))) );
      exception
        when others then
          p( 'F;W' || to_char(i+1) || ' ' ||
               to_char(i+1) || ' ' ||
               greatest( g_lengths(i), length( g_sums(i) )));
      end;
    end loop;
    p( 'E' );
  end print_widths;
--
  procedure show(
      p_cursor        in integer,
      p_sum_column    in owaSylkArray default owaSylkArray(),
      p_max_rows      in number     default 10000,
      p_show_null_as  in varchar2   default null,
      p_show_grid     in varchar2   default 'YES',
      p_show_col_headers in varchar2 default 'YES',
      p_font_name     in varchar2   default 'Courier New',
      p_widths        in owaSylkArray default owaSylkArray(),
      p_titles        in owaSylkArray default owaSylkArray(),
      p_strip_html    in varchar2   default 'YES' ) is
  --
    l_row_cnt number;
    l_col_cnt number;
    l_status  number;
  begin
    dbms_sql.describe_columns( p_cursor, l_col_cnt, g_desc_t );
    --
    for i in 1 .. g_desc_t.count loop
      dbms_sql.define_column( p_cursor, i, g_cvalue, 32765);
    end loop;
    --
    print_heading( p_font_name,
                   p_show_grid,
                   p_show_col_headers,
                   p_titles );
    l_status := dbms_sql.execute( p_cursor );
    l_row_cnt := print_rows(
                   p_cursor,
                   p_max_rows,
                   p_sum_column,
                   p_show_null_as,
                   p_strip_html );
    print_sums( p_sum_column, l_row_cnt );
    print_widths( p_widths );
  end show;
--
  procedure show(
      p_query         in varchar2,
      p_parm_names    in owaSylkArray default owaSylkArray(),
      p_parm_values   in owaSylkArray default owaSylkArray(),
      p_sum_column    in owaSylkArray default owaSylkArray(),
      p_max_rows      in number     default 10000,
      p_show_null_as  in varchar2   default null,
      p_show_grid     in varchar2   default 'YES',
      p_show_col_headers in varchar2 default 'YES',
      p_font_name     in varchar2   default 'Courier New',
      p_widths        in owaSylkArray default owaSylkArray(),
      p_titles        in owaSylkArray default owaSylkArray(),
      p_strip_html    in varchar2   default 'YES' ) is
  begin
    show( p_cursor => build_cursor( p_query,
                                    p_parm_names,
                                    p_parm_values ),
          p_sum_column => p_sum_column,
          p_max_rows => p_max_rows,
          p_show_null_as => p_show_null_as,
          p_show_grid => p_show_grid,
          p_show_col_headers => p_show_col_headers,
          p_font_name => p_font_name,
          p_widths => p_widths,
          p_titles => p_titles,
          p_strip_html => p_strip_html );
  end show;
--
end owa_sylk;
/
show error

Review &
      Followup

      Rating: 3
what about
      Image  January 22, 2002
Reviewer:  Reader
       from NJ,uSA
Hi Tom,

  Can you tell me how can I put my company's logo in
thro' this utility ?

Thanks,

      Rating: 3
How to
      call?  February 05, 2002
Reviewer:  Srinivas
       from India
How can I call this from application?

      Rating: 5
Page
      break  September 06, 2002
Reviewer:  Pkgupta
       from FL,USA
Great Package.

In addition to this, is there any way to force a page break. Like after each
Total -- Title row will be again.

Thanks in advance

          Followup:

You got the code, you can make it do *whatever* you want.

      Rating: 3
Small
      Problem  October 21, 2002
Reviewer:
      Priyaramnan  from TN, India
if u could kindly solve my prblem it will be of much use.
i'm getting an error if i run this procedure

declare
*
ERROR at line 1:
ORA-01003: no statement parsed
ORA-06512: at "SYS.DBMS_SYS_SQL", line 1476
ORA-06512: at "SYS.DBMS_SQL", line 614
ORA-06512: at "DEPOSITS.OWA_SYLK", line 233
ORA-06512: at "DEPOSITS.OWA_SYLK", line 269
ORA-06512: at line 5

how to solve this

          Followup:

so, what was the INPUT to this call.

What have you done (given that you actually have 100% of the source code and its
not really complex) to debug this?

      Rating:
      5
owa_util.showpage wrap lines containing
      spaces  December 06, 2002
Reviewer:  Rui
      Brito  from Portugal
Hi Tom
I create a .SLK file using the owa_util.show.
After this I made a spool to a file and call the owa_util.showpage. All strings
with spaces between words are wrapped. I open the showpage source and see a
instr(' ') inside this, even the line < 255 length. I can avoid this and put all
string in same line ?

Example:
K"James Brown and Company" appears like
K"James Brown and
Company"

When I open the file in excel, they give a error

Thanks
Rui Brito

          Followup:

use the UTL_FILE version of owa_sylk instead of the WEB version of owa_sylk.

showpage just dumps using dbms_output and splits (word wraps) where it feels
like .

      Rating: 4
isn't work with
      data greater than 255 chars  March 04, 2003

Reviewer:  Juan Carlos Erazo M.  from Colombia

Hi, the web version sis very useful, but isn't working whit data greater than
255 characters. Let me explain: my select sentence return some column with more
than 255 characters. When this happens Internet Explorer says: "Theres is a
problem displaying record 148".

Could i fix it?

          Followup:

I don't know, can you?

I don't see what IE has to do with anything as it isn't a spreadsheet.

      Rating:
      3
Error  April 03, 2003
Reviewer:  Pavan
      Chelvaraj  from Australia
Hi Tom

Can you please let me know why this error has occured.

  1  begin
  2  owa_util.mime_header( 'application/excel' );
  3  owa_sylk.show(
  4  p_query => 'select empno id, ename employee,
  5  sal Salary, comm commission ' ||
  6  'from scott.emp ' ||
  7  'where job = :JOB ' ||
  8  'and sal > :SAL',
  9  p_parm_names =>
 10  owa_sylk.owaSylkArray( 'JOB', 'SAL'),
 11  p_parm_values =>
 12  owa_sylk.owaSylkArray( 'MANAGER', '2000' ),
 13  p_sum_column =>
 14  owa_sylk.owaSylkArray( 'N', 'N', 'Y', 'Y'),
 15  p_show_grid => 'NO' );
 16* end;
SQL> /
owa_sylk.owaSylkArray( 'JOB', 'SAL'),
*
ERROR at line 10:
ORA-06550: line 10, column 1:
PLS-00201: identifier 'OWA_SYLK.OWASYLKARRAY' must be declared
ORA-06550: line 3, column 1:
PL/SQL: Statement ignored

          Followup:

because we named it owa_sylk, not owaSylk

      Rating: 2
Still the same
      error  April 04, 2003
Reviewer:  Pavan
      Chelvaraj  from Australia
Hi Tom

I am still getting the error message. Can you please help me with that.

  1  begin
  2  owa_util.mime_header( 'application/excel' );
  3  owa_sylk.show(
  4  p_query => 'select empno id, ename employee,
  5  sal Salary, comm commission ' ||
  6  'from scott.emp ' ||
  7  'where job = :JOB ' ||
  8  'and sal > :SAL',
  9  p_parm_names =>
 10  owa_sylk.owa_SylkArray( 'JOB', 'SAL'),
 11  p_parm_values =>
 12  owa_sylk.owa_SylkArray( 'MANAGER', '2000' ),
 13  p_sum_column =>
 14  owa_sylk.owa_SylkArray( 'N', 'N', 'Y', 'Y'),
 15  p_show_grid => 'NO' );
 16* end;
SQL> /
owa_sylk.owa_SylkArray( 'JOB', 'SAL'),
*
ERROR at line 10:
ORA-06550: line 10, column 1:
PLS-00201: identifier 'OWA_SYLK.OWA_SYLKARRAY' must be declared
ORA-06550: line 3, column 1:
PL/SQL: Statement ignored

          Followup:

wait, back up -- owa_sylk.owasylkarray is the datatype (sorry about that)

you did INSTALL THIS package *we* wrote -- that is not shipped with the product
-- right???

Ahh -- i think that is it, you just have grabbed the code from above and
actually installed it perhaps?

      Rating: 4
excellent.
        April 04, 2003
Reviewer:  Lebon Mathew
       from San Francisco, USA
Two subquestions.

1) when I exeuted in sample I got p/sql error.

--------------------------------------------------------
SQL*Plus: Release 9.2.0.1.0 - Production on Fri Apr 4 11:11:06 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

ERROR:
ORA-01017: invalid username/password; logon denied

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - 64bit Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

SQL> declare
  2          l_cursor integer default dbms_sql.open_cursor;
  3      begin
  4          dbms_sql.parse( l_cursor,
  5              'select empno id, ename employee,
  6                      sal Salary, comm commission ' ||
  7                'from scott.emp ' ||
  8                'where job = ''MANAGER'' ' ||
  9                'and sal > 2000',
 10             dbms_sql.native );
 11
 12         owa_util.mime_header( 'application/excel' );
 13         owa_sylk.show(
 14             p_cursor => l_cursor,
 15             p_sum_column =>
 16                 owa_sylk.owaSylkArray( 'N', 'N', 'Y', 'Y' ),
 17             p_show_grid => 'NO' );
 18         dbms_sql.close_cursor( l_cursor );
 19   end;
 20  /
declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SYS.OWA_UTIL", line 323
ORA-06512: at "SYS.HTP", line 860
ORA-06512: at "SYS.OWA_UTIL", line 373
ORA-06512: at line 12

SQL>
Any idea why ?
-----

2)  When I ran the same test on web. I created the above stuff as a procedure
called TEST and called it

http://myserver/pls/scott/test
... it is not opening in excel ..
( however I see the  stuff like
;Y1;X2;K"ID"
C;X3;K"EMPLOYEE"
C;X4;K"SALARY"
SION"
C;Y3
C;X2;K7782
C;X3;K"CLARK"
C;X4;K2450
C;X5;K""
 )

Can you please guide me on how to get the excel open on a browser and see it

Thank you
Lebon Mathew

          Followup:

1) the htp/owa packages were not initialized as they would be via mod_plsql.

you can run something like:

declare
    nm  owa.vc_arr;
    vl  owa.vc_arr;
begin
    nm(1) := 'WEB_AUTHENT_PREFIX';
    vl(1) := 'WEB$';
    owa.init_cgi_env( nm.count, nm, vl );
end;
/

to fix that up.

2) what exactly is in test and what exactly is your browser configured to do
with application/excel mime types.

      Rating: 4
Please Ignore
      my 2nd Question above ... I got the answer.  April 04, 2003

Reviewer:  Lebon Mathew  from SFO, USA
I had to put  like below in the mime type to solve my problem

  OWA_UTIL.mime_header ('application/vnd.ms-excel');

Thank you

      Rating: 2
PLS-00201:
      identifier 'OWA_SYLK.OWASYLKARRAY' must be declared  April
      06, 2003
Reviewer:  Pavan Chelvaraj  from Aus

SQL> declare
  2      nm  owa.vc_arr;
  3      vl  owa.vc_arr;
  4  begin
  5      nm(1) := 'WEB_AUTHENT_PREFIX';
  6      vl(1) := 'WEB$';
  7      owa.init_cgi_env( nm.count, nm, vl );
  8  end;
  9  /

PL/SQL procedure successfully completed.

SQL> ED
Wrote file afiedt.buf

  1  declare
  2  l_cursor integer default dbms_sql.open_cursor;
  3  begin
  4  dbms_sql.parse( l_cursor,
  5  'select empno id, ename employee,
  6  sal Salary, comm commission ' ||
  7  'from scott.emp ' ||
  8  'where job = ''MANAGER'' ' ||
  9  'and sal > 2000',
 10  dbms_sql.native );
 11  owa_util.mime_header( 'application/excel' );
 12  owa_sylk.show(
 13  p_cursor => l_cursor,
 14  p_sum_column =>
 15  owa_sylk.owaSylkArray( 'N', 'N', 'Y', 'Y' ),
 16  p_show_grid => 'NO' );
 17  dbms_sql.close_cursor( l_cursor );
 18* end;
SQL> /
owa_sylk.owaSylkArray( 'N', 'N', 'Y', 'Y' ),
*
ERROR at line 15:
ORA-06550: line 15, column 1:
PLS-00201: identifier 'OWA_SYLK.OWASYLKARRAY' must be declared
ORA-06550: line 12, column 1:
PL/SQL: Statement ignored

I do have these Packages installed on my server.
GENK_VAL_WEA_RI
GENK_VAL_WER_RI
GENK_VAL_WE_RI
SSFK_VAL_WE
SSFK_VAL_WEA
SSFK_VAL_WER

Is there anything else which I need to run or execute before that.

Regards

Pavan Chelvaraj

          Followup:

gee, do you have owa_sylk -- which as I said is something WE WROTE HERE -- it is
not part of the product, it would not be installed unless you installed it.

It is very clear what the error is.

You do not have owa_sylk installed in your schema.

Get it (right above)

Install it.

      Rating:
      3
OWA_SYLK  April 07, 2003
Reviewer:
      Pavan Chelvaraj  from Aus
Can you please let me know where do I find or download owa_sylk utility/package.

I do have owa_util installed on my db.

Cheers

Pavan

          Followup:

tell you what Pavan.

Reread this page from the top to the bottom.  In doing so you will read over the
source code.  I tried to tell you that a couple of times already.

my very last comment:
...
It is very clear what the error is.

You do not have owa_sylk installed in your schema.

Get it (right above)

Install it.
....

      Rating:
      5
Hilarious  April 07, 2003
Reviewer:  A
      reader
Pavan,

Have you ever thought about a change of career?

      Rating:
      4
HK  October 03, 2003
Reviewer:  Sun
       from HK
Hello Tom,

I create owa_sylk and run it, it shows no error, but where can I find my
spreadsheet file? Should I specific the path to store it ? Thanks!

SQL> ed
Wrote file afiedt.buf

  1  declare
  2        l_cursor number := dbms_sql.open_cursor;
  3      begin
  4        dbms_sql.parse(
  5          l_cursor,
  6          'SELECT id, employee,
  7                             Salary, comm from emp',
  8          dbms_sql.native );
  9        owa_sylk.show(
 10          p_cursor => l_cursor,
 11          p_sum_column =>
 12              owa_sylk.owaSylkArray( 'N', 'N', 'Y', 'Y' ),
 13          p_show_grid => 'NO' );
 14        dbms_sql.close_cursor( l_cursor );
 15*     end;
SQL> /

PL/SQL procedure successfully completed.

SQL> show error;
No errors.

          Followup:

this version of owa_sylk uses htp -- its for the web.  it didn't create a file,
it created "http output"

if you owa_util.showpage, it'll dump on your screen.  if you search for owa_sylk
utl_file you'll find one that does files.

      Rating: 5
Very very
      Urgent Pls Help me on this  October 20, 2003

Reviewer:  Suresh  from India
 Hi tom,

    i am generated excel-report through pl/sql procedure,
in that i am using some format-specifications like
htp.p('F;C1;FG0R;SM1;G'),htp.p('P;FArial;M200')....so on..
but i dont' know the exact meanning's of all these type of format
specification....and how to Use...

   but client is asking about look and feel must Perfect...
like borders of excel-must bold(related to some cells),some rows(Column heading
must be bold),Backgroung colour of some rows,foreground colour of some rows and
colums...likethat...
    and you are using these type of format-specification in this page with in
the procedures in so many places....

    what each format specification means ,where to get these format
specifications and how to use....?

    pls reply immd... and send any documents or links available on this
htp.p('....') format specifications...

   It's very urgent..Pls reply these Questions ....

  Regards,
   Suresh.

          Followup:

google for sylk.

sylk is an industry standard file format.
it is documented.
That is just part of that file format.

so, search for SYLK on the web, nothing to do with the database or Oracle at
this point, all about Sylk.

      Rating: 5
very very
      Urgent Tom ....excle formats  October 21, 2003

Reviewer:  suresh  from India
 Hi Tom,

    In google.com i am not find out any format specifications...like
htp.p('F;C2;FG0R;SM0');
htp.p('P;FArial;M200');htp.p('F;SDM6;R1')... so on..

   what each format string means, how it will work...

   pls help me on this topic..it's very urgent...
  pls send any links & documents on this ..

  Regards,
   suresh.

          Followup:

search for sylk

it is an open systems standard

i did not invent it

i found ONLY enough about it to do what I needed

i found it via google searching

I know nothing more about it

searching the web will find it.  hint try something as simple as:

"sylk file format"

and you may well find stuff (i know you will)

October 21, 2003 Andrew -- Thanks for the question regarding
      "Seeing dbms_output buffer from another session?", version 8.1.7

      You Asked
Hi, I have a sqlplus session that is hanging and I want to see what dbms_output
is in its buffer.  If I kill the session, sql*plus will never print the
serveroutput, and I will lose whatever was in the dbms_output buffer.

Is there some way I can see the dbms_output buffer from another session?

I have one workaround: write my own dbms_output which inserts to a table and
does an autonomous-commit.  But this slows down processing so I dont always do
this. Then if a session hangs, I cant tell what dbms_output it wrote.

Any suggestions?

Thanks,
Andrew

      and we said...
See

http://osi.oracle.com/~tkyte/article2/index.html#Errors
and look for the text:

       "One utility I use frequently when writing"...

for my solution to this.  I use UTL_FILE and a small routine we've written
called "debug.f" (works like C's printf).  That way, I can see the output from a
session immediately by "tailing" or editing the file they produce.

Review &
      Followup

      Rating: 2
I have a faint
      idea of what the question is about, can you explain
      more  August 25, 2001
Reviewer:
      A reader
Tom

Can you briefly explain what all you are doing in the
explanation in the given link.

Where are you getting the buffer output from an another session in the given
link.

If possible can you discuss that again.

          Followup:

Sorry, I clarified the answer above -- it was a little vague previously.

      Rating:
      4
H  September 21, 2001
Reviewer:  ray

How can we use the debug.f program a procedure to debug it.

          Followup:

ummm, by adding lots of calls to debug.f to generate a trace file you can read
to see what your program is doing.  It is just a facility to allow you to
generate a log file easily.

      Rating: 5
how can I be
      sure that debug.f is working   September 25, 2001

Reviewer:  ray
i  tested it , but got no output

SQL> set serveroutput on
SQL>  execute debug.f('sdfsdfsdfsdfsdfsdf');

PL/SQL procedure successfully completed.

          Followup:

umm, debug.f (our utility to be found at the above URL) doesn't write to the
screen.  It writes to a file -- and only after calling debug.init and setting up
your utl_file_dir init.ora parameter.

The spec of the debug package, which you should read, has all of the details --
repeated here:

--  PACKAGE TO DUMP DEBUG INFORMATION OF PL/SQL ROUTINE
--  TO A FILE DURING EXECUTION
--
--
--  This package allows the developer to selectively produce debug
--    iformation for pl/sql process.
--
--  Setup:
--    Make sure the utl_file_dir paramter is assigned in the init.ora
--    file.  You need an entry for each dir that you want to be able to
--    write to.
--    eg.
--      utl_file_dir = /tmp
--      utl_file_dir = /home/clbeck/sql/debug
--
--  Usage:
--    There are two procedure to write debug information ( f and fa ).
--    Anywhere in your code that you want to print debug information use:
--
--      debug.f( 'Expected %s bytes, got %s bytes', l_expect, l_got  );
--
--    This will replace the the first %s with the value of l_expect and the
--    second %s with the value of l_got.
--
--    If you have more than 10 %s in your string then you will need to use the
fa
--    procedure like:
--
--      debug.fa( 'List: %s,%s,%s,%s,%s,%s',
--                  argv( 1, 2, l_num, 'Chris', l_cnt, 10 ) );
--
--  Runtime:
--    To enable the debug run:
--
--      debug.init( 'myProc' );
--
--    This will cause only debug for the procedure/package named
--    myProc to be generated.
--    All other debug statements will generate no output.
--    To debug all procedures/packages,
--    set p_modules = 'ALL';
--
--    To stop debug run:
--
--      debug.clear;
--
--  Output:
--    The output looks like:
--
--      981013 130530 (CLBECK.MYPROC, 221)  this is my debug output
--      ^^^^^^ ^^^^^^  ^^^^^^^^^^^^^  ^^^   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
--      date   time    owner.proc  lineno   message
--
--
-- Enhancements and Bugs:
--
--  Send all enhancements requests and bugs to me,
--  Christopher Beck (clbeck@us.oracle.com)
--

read through the code as well -- its pretty straightforward.

      Rating: 5
We are doing
      too much of work with debug.f , whey cant we  September 26,
      2001
Reviewer:  A reader  from USA
just create a table with a varchar2 columns, and keep updating it with all the
debug info, isnt this much more easy.

Tom, why should I use debug.f , when I can do the above.

What are the advantages of debug.f over the method I suggested above

          Followup:

You need to use autonomous transactions with your method (so you can commit, so
another session can see it)

with debug.f -- I can "tail -f filename" and watch the stuff in "real time" --
it scrolls by on my screen without me having to query it.

I put debug.f in my code that others use so I can tell them to generate a trace
file and mail it to me.  Don't have to teach them how to run it, query it, spool
it, etc.  Its just in a file.

You can use a table, sure.  It is just nice to use a file sometimes.  That is
where I prefer to put my traces.

      Rating:
      3
DBA  May 09, 2002
Reviewer:  Robert
      Wood  from Coldwater, MS
Hi Tom,

You have talked a lot about the debug.f module, but you have never showed us the
code for it!

Can you show us the code?

Thanks!

Robert.

          Followup:

follow the link in the answer.  download the script.  it is in there.

      Rating: 4
Code for debug
      package  January 18, 2003
Reviewer:  raju
       from london
Hi Tom,

I just downloaded the code samples from the wrox website as I wanted to get the
debug package as mentioned in your book on page 955. I couldn't find the code
for this package in the Zip file or maybe I don't know the right file name.

However, I can access the code for the package at the above link.

If the code is there in the zip file downloaded from the wrox site then please
let me know the file name. Maybe, this info might help others as well.

Thanks

          Followup:

Use the download for "Beginning Oracle Programing" not Expert one on one.  It
has the "latest release" with the best implementation.

That book goes into big detail on how to use it as well.

      Rating: 5
Changing the
      debug package  February 25, 2003
Reviewer:
      Pratap  from India
Hi Tom

I was thinking of adding a debug_level in the package.
The debug_level can be passed to the f procedure and decided by the programmer.
Do you think this will help.

E.g. -

Level 1 - Debug at entry and exit of procedures - Maybe to get elapsed time for
each procedure

Level 2 - Non-Repetitive code and main information

Level 3 - Repetitive code, like inside a loop or more detailed information

The debugging can be turned on for any level. This might help in reducing the
unwanted information in the trace file.

My concern is the debugging of batch processes which would process huge amount
of data.

Thanks

Pratap

      Rating: 2
Other suggested
      enhancement  June 19, 2003
Reviewer:  Matt
       from Australia
Regarding the previous post.

How about defining a PL/SQL daemon that will manage printing out the debug? In
this way you could change the debug level at run time by sending a message to
the daemon.

Imagine you have a process running that is taking a long while you could
increase the debug level dynamically to increase the amount of debug sent to the
file. You can then work out whay is taking so long.

If you implement any of these enhancements, why don't you post the results here?

Cheers,

      Rating:
      5
  August 15, 2003
Reviewer:
      A reader

      Rating:
      5
debug.f  August 20, 2003
Reviewer:
      A reader
at
http://osi.oracle.com/~tkyte/article2/index.html#Errors
has compile errors. You need to change the following line
in procedure init (in the body)
from
 p_modules in varchar2 default 'all',
to
 p_modules in varchar2 default 'ALL',

for it to compile.
Haven'te tested it - it is possible that we need
the procedure spec's default value from 'ALL' to 'all'
instead.

Please keep up the awesome work!
Thanx!

          Followup:

thanks!

      Rating: 5
DEBUG.f is
      great but...  October 21, 2003
Reviewer:  Robert
      Boyle  from Cork
...what if you are looking to get debug information from a front end process as
it happens from the front end (ie on a web application).  How can you invoke
DEBUG.init from there?  Would you have it built into your JSP code say, and if
you switch on debugging you would invoke DEBUG.init before executing the
procedure call?

Currently I just log to a table depdending on whether debug is on or off, I am
looking to create an advanced version of this with different debug levels but
was having looked at DEBUG.f was wondering if I would be better going with that.

Cheers
Robert.

          Followup:

when I wrote a palm sync program in java -- i used debug.f and just had the java
code call debug.init and debug.f whenever.  that way my plsql, java everything
went into the database.

its what I used there -- especially since the java clients where all over the
place (on the desktops).

In your case, you have to decide if you want to use 2 techniques (debug.f in
plsql, maybe log4j in java) or just one -- up to you.

October 21, 2003 David -- Thanks for the question regarding
      "Limiting the number of returned rows", version 8.1.7

      You Asked
Is there a setting to limit the number of rows that Oracle will return?

I want all queries to return a maximum of only 500 rows, regardless of how many
there might be.

In Sybase there is a "Set Rowcount 500" setting. Does Oracle have a similar
command?

      and we said...
you would put

select *
  from ( select ..... )
 where rownum <= 500;

in order to do that.  there is no "session" setting with which to cause major
pain and confusion.  I cannot tell you how many times I've seen it

o set rowcount 20
o do something
o FORGET to set rowcount off

next update/delete/insert *appears* to work but doesn't (only does 20 rows).
been there, done that.  rowcount on the query is the way to do this in Oracle.

Review &
      Followup

      Rating: 4
Can RowNum be
      used in ANY query  October 15, 2002
Reviewer:
      David Schwartz  from New Jersey, USA
Can I simply append "And Rownum <= 500 " to any & every query string? Are there
special cases/exceptions that I need to look out for?

          Followup:

you want to take a query "QUERY" and wrap it

select * from ( QUERY ) where rownum < 500;

IMPORTANT, wrap it front and back -- don't just ADD "and rownum <= 500"

      Rating:
      5
Question  October 15, 2002
Reviewer:
      H  from USA
why wrap it front and back -- don't just ADD "and rownum <= 500"

please explain - thanks

          Followup:

select * from t

hmmm, bummer AND rownum <=500 isn't right.

select job, count(*) from emp group by job

hmmm, bummer AND rownum <=500 isn't right.

and so on.  That -- and "ORDER BY" / "GROUP BY" would be done AFTER a predicate
so:

select a,b from t where rownum <= 500 order by a;

does this:

o get 500 random rows
o sort it

whereas

select * from ( select a,b, from t order by a) where rownum <= 500;

does this:

o "order" the data
o return the first 500 rows AFTER sorting

different answers

      Rating: 5
How can I
      determine the actual total rows  October 15, 2002

Reviewer:  David Schwartz  from NJ, USA
That's great - thanks.
Now how can I find out what hte actual number of rows were, assuming the query
returns less than 500?
Is there a system var set that I can access?

          Followup:

depends on the environment.  In PLSQL

cursor_name%rowcount

in pro*c  sqlca.sqlerrd[2]

and so on.

      Rating: 3
Accessing
      sql%rowcount  October 17, 2002
Reviewer:  David
      Schwartz  from NJ, USA
I'm using ColdFusion.
this query...
select * from
(
     select * from emp
)
Where RowNum <= 10
works & finds 10 rows.

How can I access the sql%rowcount var?

          Followup:

guess you'll have to ask the makers of cold fusion eh?  it is their "language"
you are using after all.  sql%rowcount is in PLSQL (sqlca.sqlerrd[2] in pro*c,
something else in jdbc, etc etc etc)

      Rating: 1
Generic
      RowCount  October 17, 2002
Reviewer:  David
      Schwartz  from NJ, USA
ok - forget coldfusion (should be easy)....
how can I access it from a standard, generic query?

for example - can I do something like...
Select * From Emp

Select sql%rowcount

Maybe this will help clarify my intentions - In Sybase I would simply run Select
@@rowcount after a query.

          Followup:

As I keep saying -- it depends on the ENVIRONMENT.

Unlike Sybase, we use cursors (in sybase if you used a cursor, a "simple" select
@@rowcount returns WHAT exactly?)

In plsql:

scott@ORA920.US.ORACLE.COM> declare
  2          cursor c is select * from emp;
  3  begin
  4     for r in c
  5     loop
  6        dbms_output.put_line( C%rowcount || ' rows fetched so far....' );
  7     end loop;
  8  end;
  9  /
1 rows fetched so far....
2 rows fetched so far....
3 rows fetched so far....

So, I guess that is what you are looking for.

      Rating: 2
Implicit
      Cursors  October 17, 2002
Reviewer:  David
      Schwartz  from NJ. USA
I thought Oracle always uses a cursor - for every query - by default. So I
figured there was a built in function/var that would store the last cursor
rowcount.

Then I could just query for it. Since it's not C/SQL+/Java etc. a lot of the
interactivity features are lacking.

Am I going down the wrong path?

          Followup:

Yes, you are.

There is no such concept here.  It is 100% dependent on the language how you
would access this.  Each cursor has a state, a context.  We expose this
information in each and every language ( <cursor>%rowcount in plsql,
sqlca.sqlerrd[2] in pro*c and so on).

See, the funny thing is -- in order to parse a query like "select @@rowcount
from dual" - we might have to run 50 queries for you (recursive sql) to parse
it.  So, the "last cursor" wouldn't be yours -- or maybe it would.

      Rating: 3
Explicit cursor
      and %rowcount  October 21, 2003
Reviewer:
      A reader
Good morning Tom,

 I have gone through PL-SQL user guide, but things still not very clear.

 In one of the stored procedures, I am
1. opening a cursor
2. fetching data in a record using loop
3. some processing and insert/update a table
4. end loop
5. check for %rowcount. If %rowcount = 0, set all variables to zero and
insert/update a table
6. close cursor

I assume that %rowcount will have number of rows fetched till end of loop. Is my
assumption correct?

Thanks!!!

          Followup:

cursor%rowcount will have the total rows fetched, yes.

but, i would just use an implicit cursor and set a boolean inside the loop --
easier to code.

or, use select ...bulk collect  and look at array.count to see how many you got

or, using an explicit cursor -- bulk collect anyway.

October 21, 2003 Dianna -- Thanks for the question regarding "Best
      way to check for dbms_job completion?", version 9.2.0

      You Asked
Tom,
I am using your method of storing the parameters for my jobs submitted via
dbms_job in a parameter table and that is working great. Now via my stored
procedure that is submitting the jobs, I need to check to see if the jobs are
completed. Since I am having the job that was submitted delete its parameters
when it finished, I thought that I could check to make sure that the parameters
were deleted and then know that the associated job was finished.

I have two questions:
1) Is this the best way to check if the set of jobs are finished?
2) If so, what would be a good way to query this table repeated to check that
all my jobs are completed. I have created a PL/SQL  table of all of the
submitted job ids and now I want to check my parameter table for the lack of
existence of this set of job ids.

Thanks for your time,
Dianna

      and we said...
why not update the row and set a "completed" flag or put in a message.  That way
you would have an audit trail as well -- you could even time the duration of
times -- they could grab a start time, diff it and update their row with "time
to complete"

I would think that would be better then looking for a delete row since a deleted
row doesn't look any different then a row that was never inserted (what if
something goes really wrong and the job never gets submitted in the first place)

Review &
      Followup

      Rating:
      4
  June 16, 2003
Reviewer:  Dianna
       from Florida
That is a very good idea as it will be useful for me to know if any of the jobs
are taking a long time. If I do have the job update the parameter table row,
what is the best way for me to check in the calling procedure for the completion
of all jobs? I am thinking something like a loop that checks and if any items
are stilling outstanding sleeps for a brief amount of time and then checks
again. Does this sound reasonable? If you have a reference in your book, that
would help as I do have a copy.

Thanks,
Dianna

          Followup:

I typically do this:

begin
  dbms_job.submit...
  dbms_job.submit...
  loop
      dbms_lock.sleep(30);
      look to see if they are done, exit when they are
  end loop;

      Rating: 3
More detailed
      example  June 16, 2003
Reviewer:  Rob
       from PIttsburgh, PA
Tom:

Do you have a more detailed code example of submitting multiple jobs via
dbms_job and then waiting until all are done.
What do you look for to determine that the jobs that you have just submitted are
complete.

begin
  dbms_job.submit...
  dbms_job.submit...
  loop
      dbms_lock.sleep(30);
      look to see if they are done, exit when they are
  end loop;

          Followup:

here is one I used to do a simulation of multiple users in the database running
a procedure.

We submit the job -- placing the inputs in a "job parameter" table.  When that
table is emtpy -- jobs must be done (as the last step, these procedures deleted
their inputs)

create or replace procedure simulation( p_procedure in varchar2, p_jobs in
number, p_iters in number )
authid current_user
as
    l_job number;
    l_cnt number;
begin
    for i in 1 .. p_jobs
    loop
        begin
            execute immediate 'drop table t' || i;
        exception
            when others then null;
        end;
        execute immediate 'create table t' || i || ' ( x int )';
    end loop;

    for i in 1 .. p_jobs
    loop
        dbms_job.submit( l_job, p_procedure || '(JOB);' );
        insert into job_parameters
        ( jobid, iterations, table_idx )
        values ( l_job, p_iters, i );
    end loop;

    statspack.snap;
    commit;
    loop
        dbms_lock.sleep(30);
        select count(*) into l_cnt from job_parameters;
        exit when (l_cnt = 0);
    end loop;
    statspack.snap;
end;
/

      Rating:
      5
  July 25, 2003
Reviewer:
      A reader

      Rating: 4
What is
      l_job  October 20, 2003
Reviewer:
      A reader
Tom,
In your "simulation" procedure how do you set/get values for "l_job"?
Thanks!

          Followup:

dbms_job returns the job id it decided to assign the job.  it is an OUT
parameter.

      Rating: 5
Why
      statspack.snap?  October 21, 2003
Reviewer:
      A reader
Thanks Tom.

Why are you using statspack.snap in the procedure?

          Followup:

i wanted to see what happened during the simulation -- where did they contend
for resources, what did they do, what were the statistics.

so, i take a snap, wait for them to finish, take another snap.  now i can
report.

      Rating: 5
Use a
      pipe  October 21, 2003
Reviewer:  Neil
       from UK
There's an article in October's edition of Oracle Professional
(www.oracleprofessionalnewsletter.com) by Parin Jhaveri and Lev Moltyana
regarding parallel job submission. For those who don't subscribe, here's the
gist of it:
A table holding the requested jobs, their parameters and duration/status is read
by a main package. This executes the jobs via the job queue. The job queue calls
a generic 'child' procedure which executes the task using execute immediate.
Each invocation of the child communicates with the parent using a pipe given it
as a parameter by the parent. So, several child processes using the same pipe to
communicate with the parent provide the means for the parent to determine the
status of all those child processes. If all children are successful, the parent
commits, otherwise it can rollback.
That's the theory, and it seems sensible to me. I'm not convinced of the main
procedure's ability to rollback or commit, but I may have go the wrong end of
the stick. Either way, a quick protoype would establish this one way or the
other.
HTH

          Followup:

the parent procedure would be committing or rolling back ONLY ITS WORK.

the child processes are "on their own", each will commit or not by itself.

October 21, 2003 -- Thanks for the question regarding "Performance
      poor after analyzeing table", version 8.1.7

      You Asked
Hi Tom,

As my understanding, it will use Cost Based Optimzer after analyzing a table.
But sometimes the preformance is very pool after analyzing. So shall we need to
monitor all SQLs and change them to use Hint if have performance problems?

Eg: we have a table ERROR_TBL for 3,528,434 rows
Name                               Null?    Type
---------------------------------- -------- -------------------------
ERROR_NUM                          NOT NULL NUMBER(10)
PROCEDURE_NAME                              VARCHAR2(40)
DATA_OWNER                                  VARCHAR2(20)
OPERATION                                   VARCHAR2(1)
ERROR_DATE                                  DATE
ERROR_VALUE                                 VARCHAR2(250)
ERROR_MESSAGE                               VARCHAR2(2000)

primary key ERROR_TBL_PK(ERROR_NUM)
index ERROR_TBL_IDX(PROCEDURE_NAME)

SQL>
select * from error_tbl where procedure_name = 'tbs_space_prc';
2 rows selected

Explain Plan before analyzing:
--------------------------------------------------------------------
SELECT CHOOSE ()    , COST=0, CARDINALITY=0
  TABLE ACCESS  (BY INDEX ROWID) ERROR_TBL   , COST=0, CARDINALITY=0
    INDEX  (RANGE SCAN) ERROR_TBL_IDX   , COST=0, CARDINALITY=0

Then I did:
SQL> analyze table ERROR_TBL compute statistics;
SQL> select index_name
    , num_rows, sample_size, last_analyzed
    , user_stats, blevel, leaf_blocks, distinct_keys
    , avg_leaf_blocks_per_key, avg_data_blocks_per_key
from user_indexes
where table_name = 'ERROR_TBL';

INDEX_NAME      NUM_ROWS SAMPLE_SIZE     BLEVEL LEAF_BLOCKS DISTINCT_KEYS
AVG_LEAF_BLOCKS_PER_KEY AVG_DATA_BLOCKS_PER_KEY
------------- ---------- ----------- ---------- ----------- -------------
----------------------- -----------------------
ERROR_TBL_IDX    3528434     3528434          3       31876             4
            7969                   15047
ERROR_TBL_PK     3528434     3528434          2        7092       3528434
               1                       1

Explain Plan after analyzing:
--------------------------------------------------------------------
SELECT CHOOSE ()    , COST=9128, CARDINALITY=882109
  TABLE ACCESS ANALYZED (FULL) ERROR_TBL, COST=9128, CARDINALITY=882109

this took my 69 seconds for selecting 2 rows

We have lots of million rows' tables. And we analyzed all tables
recently using compute statistics. Some made the same problems after analyzing.
But I cannot rewrite all SQLs for applications which wrote by EJB.

How can I do?

Thanks in advance
Ling

      and we said...
Here you have massively skewed data with really small numbers of distinct
values.

You have 3,528,434 rows.  There are 4 values of procedure_name.  The optimizer
is looking at that and saying "oh my, thats not good is it".

Look into the analyze command -- in particular:

analyze table t
compute statistics
for TABLE
for all indexes
for all indexed columns SIZE <N>;

you are probably seeing that the default histrogram bucket size is not
appropriate for you.  Play with different values of N and see what happens with
your query.

Review &
      Followup

      Rating:
      4
  March 21, 2002
Reviewer:  Ling
       from Toronto
1. How about bitmap index?
I also used bitmap index
SQL> create  bitmap index error_tbl_idx on ERROR_TBL(PROCEDURE_NAME);
SQL> analyze index error_tbl_idx compute statistics;

But the Explain Plan still same
_________________________________________________________
SELECT CHOOSE ()    , COST=9128, CARDINALITY=882109
  TABLE ACCESS ANALYZED (FULL) ERROR_TBL   , COST=9128, CARDINALITY=882109

2. If I change init.ora to use RULE optimizer, it's better
or not for whole DB performance? Does it not support RBO in future?

          Followup:

since the data in this column is so massively SKEWED and you haven't played with
the histograms as I directed -- the answer is a resounding NO.

You have a select *.  So, we need to go from index to table.  Going from index
to table for a large percentage of the rows is a no-no.  The optimizer is aware
that are 4 unique values but doesn't know that the value of procedure_name =
'tbs_space_prc' is going to return just a FEW rows, its thing 3528434/4 rows --
regardless of the index type!

So, consider this example and use this approach:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t as select mod(rownum,3) X, a.*
from all_objects a;
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from t;
17309 rows created.

...

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from t;
553888 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from t;

  COUNT(*)
----------
   1107776

ops$tkyte@ORA817DEV.US.ORACLE.COM> update t set x = 4 where rownum <= 10;
10 rows updated.

so, I have 1million records.  10 of which have the #4.  the rest have values
of 0, 1, 2.  Now I index that column

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx on t(x) unrecoverable
parallel 8
  2  /

Index created.

and analyze...

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;
Table analyzed.

now lets see what happens:

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where x = 4;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=446 Card=276944
Bytes=24371072)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=446 Card=276944 Bytes=24371072)

full tablescan just like you.  Now, lets play with the histograms:

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics for
columns x size 100;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where x = 4;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=10 Bytes=880)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=4 Card=10 Bytes=880)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=3 Card=10)

and the optimizer has the right info -- does the right thing.  Just to
validate, lets see what happens when we user the #1:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where x = 1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=446 Card=369276
Bytes=32496288)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=446 Card=369276 Bytes=32496288)

back to a full scan - AS IT SHOULD be for that one

      Rating: 3
data
      skew  March 21, 2002
Reviewer:  Mikito
      Harakiri
Tom, with 4 distinct values wouldn't a histogram with just 4 buckets suffice?

Ling, can you run

select procedure_name, count(1)  from error_tbl
group by procedure_name

to see how skewed your column is?

          Followup:

yes it would

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics for
columns x size 4;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where x = 4;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=10 Bytes=880)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=4 Card=10 Bytes=880)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=3 Card=10)

however, it wouldn't if you insert but one more row.

I would hope the reader would say "ah, histograms, interesting concept, let
me go read about them and learn about them so I can use them efficiently,
effectively and to their maximum (i did tell them to PLAY with it) and figure
out what exactly N should be.

You know -- if I put 4 in there, someone would say "hey, you mean I have to know
exactly how many distinct values, yadda yadda yadda..."

(count(*) is more meaningful then count(*) and is exactly the same...)

      Rating: 4
question about
      SIZE=N  March 22, 2002
Reviewer:  Alexander
      Rakhalsky  from Moscow, Russia
    Hi, Tom!
    1. I have a question about "SIZE N" clause of ANALYZE statement. I supposed,
what N determines number of buckets BEFORE histogram compression (what is
removing buckets with same values in ALL_TAB_HISTOGRAMS.ENDPOINT_VALUE). So, if
I issue ANALYZE ... SIZE 10, maximum number in
ALL_TAB_HISTOGRAMS.ENDPOINT_NUMBER (or BUCKET_NUMBER) will be 10. Because that
(and also fact, that Oracle uses height-balanced histograms), I was wondered on
your statement, what SIZE=number of distinct values will be sufficient. After
some experiments (like yours on this page) I suppose, that N in SIZE clause
determines number of buckets AFTER histogram compression. See listing:

SQL> SELECT num_rows
  2    FROM user_tables
  3    WHERE table_name = 'A';

 NUM_ROWS
---------
  1369680

SQL> SELECT owner, COUNT(*)
  2    FROM a
  3    GROUP BY owner;

OWNER                           COUNT(*)
------------------------------ ---------
DBSNMP                               512
PLT                              1375488
SYSTEM                             32768

SQL> ANALYZE TABLE a
  2    ESTIMATE STATISTICS
  3    FOR ALL INDEXED COLUMNS SIZE 2;

Table analyzed.

SQL>  SELECT *
  2     FROM user_histograms
  3     WHERE table_name='A'
  4     AND column_name='OWNER';

TABLE_NAME COLUMN_NAM ENDPOINT_NUMBER ENDPOINT_VALUE
---------- ---------- --------------- --------------
A          OWNER                  705      4,169E+35
A          OWNER                  723      4,328E+35

SQL> SET AUTOTRACE TRACEONLY EXPLAIN
SQL>  SELECT *
  2     FROM a
  3     WHERE owner='DBSNMP';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=12 Card=948 Bytes=21804)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'A' (Cost=12 Card=948 Bytes=21804)
   2    1     INDEX (RANGE SCAN) OF 'Q_IND' (NON-UNIQUE) (Cost=5 Card=948)

SQL> SELECT *
  2    FROM a
  3    WHERE owner='PLT';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=458 Card=1335581
Bytes=30718363)
   1    0   TABLE ACCESS (FULL) OF 'A' (Cost=458 Card=1335581 Bytes=30718363)

    Is it correct? And if so, how Oracle chooses real number of buckets (723 in
example)? Can I adjust this algorithm?
    2. By reading your answer someone can think: "I can collect statistics on my
massively skewed data and will forget about anyperformance problems." However, I
will point to MAIN histogram's limitation  - they useless if you are using bind
variables! See listing:

SQL> var my_var VARCHAR2(10)
SQL> EXEC :my_var:='DBSNMP';

PL/SQL procedure successfully completed.

SQL> select count(*)
  2  from a
  3  where owner =:my_var;

 COUNT(*)
---------
      512

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=458 Card=1 Bytes=11642280)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'A' (Cost=458 Card=684840 Bytes=11642280)

    Because most queries in typical application USES bind variables (and you,
Tom, always agitated for it), I offer following rule: "Always hint your queries
on massively skewed data, because only you (developer) know, what values you
will pass for bind variables at runtime". What you think?

          Followup:

I'll answer this "upside down" -- I've included a support note that should cover
the first part of this question (available on metalink as well).  I will address
the second part about bind variables directly.

With skewed information -- there are two cases:

o I am an OLTP/general purpose system with lots of users/queries.
o I am a datawarehouse with few users and few really really big queries.

99% of the world falls into category 1.  For them -- "bind variables rule".  In
a data warehouse, this is the one case where the hard parse issue is not as
large (data warehouse = queries that run for minutes or hours and you have fewer
queries overall executing in a day.  Hence the contention on the shared pool is
not relevant and the % of the execute time of the query taken by parsing is
trivial).  In an OLTP/general use system, a hard parse can frequently take
LONGER THEN the query itself!  Also, you are executing TONS more queries in this
sort of system so contention on the shared pool is extremely heavy if you hard
parse tons and tons.

So, If I am in an general use system and have skewed data -- I typically know
about it.  Consider the system that has a table with a million rows in it
representing "work".  As new work is added to the system -- it gets a status
code of "1".  A completed piece of work gets a status code of "2", "3",  or "4"
representing the outcome (think of it like a queue of work to be done).
Hopefully, you have very few records with a status code of "1" and almost all
records are "completed" (it is an order entry system for example - 1 = new
order, 2 = processed order, 3 = shipped order, 4 = cancelled order).  Your
application will run queries such as:

  o I need some work to work on -- select * from t where status = 1
    and customer = :customer_name_i_work_on;

  o I need to find the ratio of shipped to cancelled orders
  o etc

That is, you have different queries that will refer to explicit status codes.
There will be other bind variables in there for the variant portion of the query
-- but for the skewed column (remember it has a finite domain of well known
values) you will be using the value in there.  You still have high reuse on
queries (everone in the system uses that first query 10's of thousands of
times).  You do not always bind EVERY value -- only those values in a given
query that VARY over time (and the status code here NEVER varies).

Query 1 above will use the index (if you have one) on status since few records
have a status of 1.  The second query would not -- it would full scan since the
status's you are interested in represent the vast majority of the table.

As for HINTING -- I would never even consider it!!  Think of it -- if you *know*
the skew of the data so much that you know when to hint -- you must know the
values then (and this set of values is small).  You should not use a bind
variable for that particular column, you would not be generating an infinite
number of unique queries (you have a small well defined set of values here
remember) so the hard parse issue is not an issue here -- shared sql is still
effectively used.  This is one reason why I don't LIKE cursor_sharing as a final
solution (only as a bandaid).  It OVER BINDS which can be just as bad as NOT
BINDING.  You use binds on columns where the input values vary from call to call
-- NOT on columns where the value is static from call to call.

Hope that makes sense (i don't like hints, they too are what I would consider a
bandaid)

As for the first part of your two parter ;)  Hope this support note (i don't
write these) clears it up.

Article-ID:         <Note:72539.1>
Circulation:        PUBLISHED (EXTERNAL)
Folder:             server.Performance.SqlTuning
Topic:              Optimizer Statistics and ANALYZE (Inc Histograms)
Title:              Interpreting Histogram Information
Open-Remarks:       See <RemarksOn:NOTE:72539.1>
Document-Type:      BULLETIN
Impact:             MEDIUM
Skill-Level:        NOVICE
Updated-Date:       20-MAR-2002 10:44:38
References:
Shared-Refs:
Attachments:        NONE
Content-Type:       TEXT/PLAIN
Keywords:           CBO;
Products:           5/RDBMS (V7.3.X to V8.1.X);
Platforms:          GENERIC;

PURPOSE
To provide an understanding of how histogram information is stored and
can be interpreted.

SCOPE & APPLICATION
Familiarity of the Cost Based Optimizer is useful.

RELATED DOCUMENTS
@<Note:50750.1>
<Note:1031826.6>

Where there is a high degree of skew in the column distribution, called a
non-uniform distribution of data, histograms should lead to a better
estimation of selectivity. This should produce plans that are more likely
to be optimal.

The histogram approach provides an efficient and compact way to represent
data distributions.

When building histograms the information it stores is interpreted differently
depending on whether the number of buckets requested is less than the number
distinct values or if it is the same.  Specifically, ENDPOINT_NUMBER and
ENDPOINT_VALUE in dba/user/all_histograms would have different meanings.

EXAMPLE
-------

Table TAB1

SQL> desc tab1
 Name                            Null?    Type
 ------------------------------- -------- ----
 A                                        NUMBER(6)
 B                                        NUMBER(6)

Column A contains unique values from 1 to 10000.

Column B contains 10 distinct values. The value '5' occurs 9991 times. Values
'1, 2, 3, 4, 9996, 9997, 9998, 9999, 10000' occur only once.

Test queries:

(1) select * from tab1 where b=5;
(2) select * from tab1 where b=3;

Both the above queries would use a FULL TABLE SCAN as there is no other
access method available.

Then we create an index on column B.

select lpad(INDEX_NAME,10), lpad(TABLE_NAME,10),
       lpad(COLUMN_NAME,10), COLUMN_POSITION, COLUMN_LENGTH
from user_ind_columns
where table_name='TAB1'

SQL> /

LPAD(INDEX LPAD(TABLE LPAD(COLUM COLUMN_POSITION COLUMN_LENGTH
---------- ---------- ---------- --------------- -------------
    TAB1_B       TAB1          B               1            22

Now,

(1) select * from tab1 where b=5;
(2) select * from tab1 where b=3;

Both do an INDEX RANGE SCAN to get the ROWID to do a lookup in the table.

With an INDEX present, it would preferrable to do an INDEX RANGE SCAN for
query (2), but a FULL TABLE SCAN for query (1).

ANALYZING THE TABLE
-------------------

Next, analyze the table using compute statistics:

SQL> analyze table tab1 compute statistics;

From dba_tables:

  NUM_ROWS     BLOCKS EMPTY_BLOCKS  AVG_SPACE  CHAIN_CNT AVG_ROW_LEN
---------- ---------- ------------ ---------- ---------- -----------

     10000         64            0         86          0          10

From dba_tab_columns:

NUM_DISTINCT LOW  HIGH   DENSITY  NUM_NULLS NUM_BUCKETS LAST_ANALYZ SAMPLE_SIZE
------------ ---- ---- --------- ---------- ----------- ----------- -----------
       10000 Full Full     .0001          0           1 30-JUN-1999       10000
          10 Full Full        .1          0           1 30-JUN-1999       10000

For Oracle7, from user_histograms:

SQL> select lpad(TABLE_NAME,10), lpad(COLUMN_NAME, 10),
  2  bucket_number, endpoint_value
  3  from user_histograms 4  where table_name='TAB1';

TABLE_NAME COLUMN_NAME BUCKET_NUMBER ENDPOINT_VALUE
---------- ----------- ------------- --------------
      TAB1           A             0              1
      TAB1           A             1          10000
      TAB1           B             0              1
      TAB1           B             1          10000

For Oracle8, from user_tab_histograms:

SQL> select lpad(TABLE_NAME,10), lpad(COLUMN_NAME, 10),
  2  bucket_number, endpoint_value
  3  from user_tab_histograms 4  where table_name='TAB1';

Analyze has created 1 BUCKET for each column. So all values for the column
are in the same bucket.  The BUCKET_NUMBER represents the BUCKET NUMBER and
ENDPOINT_VALUE represents the last column value in that bucket.

Now query (1) and (2) ; both do a FULL TABLE SCAN.

So, the fact that you have statistics about the table and columns does not
help the optimizer to distinguish between how many of each value we have.
The reason it does a FULL TABLE SCAN is because there is a 1 BUCKET histogram
and any value selected for should be in that bucket.

CREATING HISTOGRAMS -------------------

What you need now is to create histograms so the Optimizer knows how many
values occur for each column.

Query (1): select * from tab1 where b=5;
           should do a FULL TABLE SCAN   and

Query (2): select * from tab1 where b=3;
           should do an INDEX RANGE SCAN

SQL> analyze table tab1 compute statistics for columns b size 10;

select lpad(TABLE_NAME,10), lpad(COLUMN_NAME, 5),
       endpoint_number, endpoint_value
from user_histograms;

TABLE_NAME COLUMN_NAME ENDPOINT_NUMBER ENDPOINT_VALUE
      TAB1           B               1              1
      TAB1           B               2              2
      TAB1           B               3              3
      TAB1           B               4              4
      TAB1           B            9995              5
      TAB1           B            9996           9996
      TAB1           B            9997           9997
      TAB1           B            9998           9998
      TAB1           B            9999           9999
      TAB1           B           10000          10000

So, now there are statistics on the table and on the columns.

You requested 10 buckets (size 10) and there are 10 distinct values.

The ENDPOINT_VALUE shows the column value and the ENDPOINT_NUMBER
shows the cumulative number of rows.

For example, for ENDPOINT_VALUE 2, it has an ENDPOINT_NUMBER 2, the previous
ENDPOINT_NUMBER is 1, hence the number of rows with value 2 is 1.

Another example is ENDPOINT_VALUE 5. Its ENDPOINT_NUMBER is 9995. The previous
bucket ENDPOINT_NUMBER is 4, so 9995 - 4 = 9991 rows containing the value 5.

So, now QUERY (1) does in fact do a Full Table Scan.

SQL> select * from tab1 where b=5
SQL> /

Execution Plan
----------------------------------------------------------
0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=10 Card=9991 Bytes=99910)

1    0   TABLE ACCESS (FULL) OF 'TAB1' (Cost=10 Card=9991 Bytes=99910)

And, QUERY (2) does do an Index Range Scan.

SQL> select * from tab1 where b=3
SQL> /

Execution Plan
----------------------------------------------------------
0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=6 Card=500 Bytes=5000)
1    0   TABLE ACCESS (BY ROWID) OF 'TAB1' (Cost=6 Card=500 Bytes=5000)
2    1     INDEX (RANGE SCAN) OF 'TAB1_B' (NON-UNIQUE)

This is fine if you have a low number of distinct values, but there can
be tables with a huge number of distinct values.  You don't want to
create a bucket for each value. There would be too much overhead.
In this case you would request less buckets than distinct values.

CREATING HISTOGRAMS WITH LESS BUCKETS THAN DISTINCT VALUES
----------------------------------------------------------

SQL> analyze table tab1 compute statistics for columns b size 8;

SQL> select lpad(TABLE_NAME,10), lpad(COLUMN_NAME, 5),
  2> endpoint_number, endpoint_value
  3> from user_histograms;

LPAD(TABLE LPAD( ENDPOINT_NUMBER ENDPOINT_VALUE
---------- ----- --------------- --------------
TAB1     B               0              1
TAB1     B               7              5
TAB1     B               8          10000

Here, Oracle creates the requested number of buckets but puts the same
number of values into each bucket, where there are more endpoint values
that are the same for the frequently occurring value.

The ENDPOINT_NUMBER is the actual bucket number and ENDPOINT_VALUE is
the endpoint value of the bucket determined by the column value.

From above, bucket 0 holds the low value for the column. You cannot see
buckets 1 to 6 so as to save space.

But we have bucket 1 with an endpoint of 5,
        bucket 2 with an endpoint of 5,
         bucket 3 with an endpoint of 5,
        bucket 4 with an endpoint of 5,
        bucket 5 with an endpoint of 5,
        bucket 6 with an endpoint of 5,
        bucket 7 with an endpoint of 5 AND
        bucket 8 with an endpoint of 10000

So bucket 8 contains values between 5 and 10000.
All buckets contain the same number of values (which is why they are called
height-balanced histograms), except the last bucket may have less values
then the other buckets.

If the data is uniform, you would not use histograms. However, if you request
the same number of buckets as distinct values, Oracle creates 1 bucket.  If
you request less buckets, Oracle uses an algorithm to balance values into each
bucket and any values that remain (which have to be less then the number
stored in each height-balanced bucket) go into the last bucket.

STORING CHARACTER VALUES IN HISTOGRAMS
--------------------------------------

Character columns have some exceptional behaviour, in as much as we store
histogram data for the first 5 bytes of any string.  Any predicates that
contain strings greater than 5 characters will not use histogram information
and the selectivity will be 1 / DISTINCT.

Data in histogram endpoints is normalized to double precision floating point
arithmetic.

EXAMPLE
-------

SQL> select * from morgan;

A
----------
a
b
c
d
e
e
e
e

The table contains 5 distinct values. There is one occurance of 'a', 'b', 'c'
and 'd' and 4 of 'e'.

Create a histogram with 5 buckets.

SQL> analyze table morgan compute statistics for columns a size 5;

Looking in user_histograms:

LPAD(TABLE LPAD( ENDPOINT_NUMBER ENDPOINT_VALUE
---------- ----- --------------- --------------
    MORGAN     A               1     5.0365E+35
    MORGAN     A               2     5.0885E+35
    MORGAN     A               3     5.1404E+35
    MORGAN     A               4     5.1923E+35
    MORGAN     A               8     5.2442E+35

So, ENDPOINT_VALUE     5.0365E+35 represents a
            5.0885E+35 represents b
            5.1404E+35 represents c
            5.1923E+35 represents d
            5.2442E+35 represents e

Then if you look at the cumulative values for ENDPOINT_NUMBER,
the corresponding  ENDPOINT_VALUE's are correct.

      Rating: 4
Excellent - But
      why I get this  March 22, 2002
Reviewer:
      pawan  from USA
Tom,
   I was trying yr example and this is what happens
------
ADHOC@SF028I> @count
Enter table name:all_objects
old   1: select count(1) from &TABLE
new   1: select count(1) from all_objects

COUNT(1)
---------
17068

ADHOC@SF028I> set timing on
ADHOC@SF028I> num,3) X,a.* from all_objects a;                <

Table created.

Elapsed: 00:00:08.88
ADHOC@SF028I> insert /*+ append */ into t select * from    t;

17067 rows created.

Elapsed: 00:00:01.31
ADHOC@SF028I> insert /*+ append */ into t select * from    t;
insert /*+ append */ into t select * from t
*
ERROR at line 1:
ORA-12838: cannot read/modify an object after modifying    it    in    parallel

Elapsed: 00:00:00.03
ADHOC@SF028I> insert /*+ append */ into t select * from    t;
insert /*+ append */ into t select * from t
*
ERROR at line 1:
ORA-12838: cannot read/modify an object after modifying    it    in    parallel

Elapsed: 00:00:00.00
ADHOC@SF028I>
---
Can U explain

          Followup:

add a commit after each and every insert /*+ append */ (thats what was in the
... I had to do it a couple of times.

The message is somewhat self explanatory - you cannot read or modify an object
after using the append hint.  commit and you can read/modify it again.

      Rating: 4
It
      works  March 22, 2002
Reviewer:  pawan
       from USA
PLease ignore my last posting - I committed and it works

      Rating:
      4
Excellent  March 22, 2002
Reviewer:
      ling
1) I re-analyzed the table using:
SQL> analyze table error_tbl compute statistics
    for table
    for all indexes
    for all indexed columns size 10;

Yes, it did same thing with you show me.

SQL> select * from user_histograms
    where table_name = 'ERROR_TBL'
    and column_name = 'PROCEDURE_NAME';

TABLE_NAME  COLUMN_NAME      ENDPOINT_NUMBER ENDPOINT_VALUE
ENDPOINT_ACTUAL_VALUE
----------  ---------------- --------------- --------------
----------------------------
ERROR_TBL   PROCEDURE_NAME              1514 5.264172163023
eb_coll_ac_ch_addr_trg
ERROR_TBL   PROCEDURE_NAME              3051 5.264172163023 eb_coll_ac_ch_trg

ERROR_TBL   PROCEDURE_NAME           3528434 5.474313824568
inst_eb_coll_ac_unique_prc
ERROR_TBL   PROCEDURE_NAME                 2 5.212249225386 tbs_space_prc

Execution Plan
---------------------------------------------------------------------------------
---
SELECT CHOOSE ()    , COST=4, CARDINALITY=2
  TABLE ACCESS ANALYZED (BY INDEX ROWID) ERROR_TBL   , COST=4, CARDINALITY=2
    INDEX ANALYZED (RANGE SCAN) ERROR_TBL_IDX   , COST=3, CARDINALITY=2

2) Question, Histograms are not useful for primary key and unique key columns
'cause they are uniformly. So
I)
analyze table x compute statistics for column y size n;
II)
analyze table x compute statistics
for table
for all indexes
for all indexed column size n;

I) is better than II) for saving thousands of rows in user_histograms? Or just
doesn't matter?

Thanks for your help

          Followup:

well, you won't have thousands of rows -- we use buckets (thats what size is all
about).

I is better then II if you know the columns you want to have histograms for
(common).  II is easier to demo with (so you will see me use it lots).

      Rating:
      4
  March 22, 2002
Reviewer:  Ling

Thousands of rows in user_histograms means I analyze all of hundreds of our
tables. And the default value for SIZE is 75.

Anyway, using method II is easy and quick for analyzing hundreds of tables.

Thanks so much for your help!

      Rating: 5
i have a
      simular problem  August 26, 2002
Reviewer:
      raju
i have three hughe tables each of 1,00,000 size

all have proper indexes.

i wrote a query joining these tables (so, totally two conditions) and some
static conditions

when i anlayzed the tables with
script1:
-------
analyze table t1 compute statistics
analyze table t2 compute statistics
analyze table t3 compute statistics

also analyzed the indexes on these tables seperately with
analyze index i1 compute statistics... so on..

after this, when explain plan for the query joining the above three tables all
the tables are scanned fully.

then as you said, i tried analyzing the tables
script2:
-------
analyze table t1
compute statistics
for TABLE
for all indexes;

analyze table t2
compute statistics
for TABLE
for all indexes;

analyze table t3
compute statistics
for TABLE
for all indexes;

then also same... all the tables were scanned fully.

then i did
script3:
--------
analyze table t1
compute statistics
for TABLE
for all indexes
for all indexed columns size 100;

analyze table t2
compute statistics
for TABLE
for all indexes
for all indexed columns size 100;

analyze table t3
compute statistics
for TABLE
for all indexes
for all indexed columns size 100;

then it started using the indexes. then i ran the above script3 with column size
as 1 then also indexes are being used.
then i ran the script2, then also all the indexes were used.

1.i really don't understand here whatz happening?
2.is it because of histograms
3.how is it possible with histograms

i don't see any topic in your book on histograms?(i am not sure is there a
one,.. if please tell me what topic is it?)

          Followup:

I don't understand either cause I don't see a single query, a single tkprof, a
single explain plan.  I don't understand the skew of your data -- nothing.

Nothing in, nothing out.

      Rating: 5
RE: Nothing in,
      nothing out....  August 26, 2002
Reviewer:  Mark
      A. Williams  from Indianapolis, IN USA
Better call the shop and get your crystal ball back ASAP!  By the way, how many
red lights are there between the shop and your place?  Is that good?

Just joking of course...

- Mark

      Rating: 4
Its really good
      and helpful  August 27, 2002
Reviewer:
      Hitesh  from India
Keep it up

      Rating: 4
i have simular
      problem -- sorry for the confusion  August 27, 2002

Reviewer:  raju
sorry tom,

 there was some confusion.

 please ignore my previous question.

i have described main tables that i am using in my query(find query at the
bottom)

SQL> DESC TRANS_SUMY
Name                  Type           Nullable Default Comments
--------------------- -------------- -------- ------- --------
TRANS_SUMY_ID_C       NUMBER(13)
EM_ID_C           VARCHAR2(11)
LEAVE_TYP_ID_C        VARCHAR2(4)
LEAVE_TYP_T           VARCHAR2(255)

RQST_STRT_D           DATE
RQST_STRT_DT_DY_I     VARCHAR2(1)
RQST_END_D            DATE           Y
RQST_END_DT_DY_I      VARCHAR2(1)    Y

SQL> SELECT * FROM USER_IND_COLUMNS WHERE TABLE_NAME = 'TRANS_SUMY';

INDEX_NAME         COLUMN_NAME            column postion     index_type
------------------ ---------------------  -----------------  ------------
XUK1_TRANS_SUMY    EM_ID_C                1                  unique
XUK1_TRANS_SUMY    RQST_STRT_D            2                  unique
XUK1_TRANS_SUMY    RQST_STRT_DT_DY_I      3                  unique
XPK_TRANS_SUMY     TRANS_SUMY_ID_C        1                  unique

SQL> desc TRANS_HDR
Name                    Type         Nullable Default Comments
----------------------- ------------ -------- ------- --------
TRANS_ID_C              NUMBER(13)
TRANS_STAT_ID_C         NUMBER(4)
TRANS_SUMY_ID_C         NUMBER(13)
CURR_APPVR_SEQ_ID_C     NUMBER(2)

INDEX_NAME                     COLUMN_NAME         index
------------------------------ ------------------  ------
XUK1_TRANS_HDR                 TRANS_SUMY_ID_C     unique
XPK_TRANS_HDR                  TRANS_ID_C          unique

SQL> desc TRANS_DETL
Name                   Type         Nullable Default Comments
---------------------- ------------ -------- ------- --------
TRANS_ID_C             NUMBER(13)
TRANS_APPVR_SEQ_ID_C   NUMBER(2)
APPVR_EM_ID_C          VARCHAR2(11)
TRANS_STAT_ID_C        NUMBER(4)

INDEX_NAME                     COLUMN_NAME
------------------------------ ---------------------
XPK_TRANS_DETL                 TRANS_ID_C
XPK_TRANS_DETL                 TRANS_APPVR_SEQ_ID_C

DESC TRANS_STAT_REF
Name              Type          Nullable Default Comments
----------------- ------------- -------- ------- --------
TRANS_STAT_ID_C   NUMBER(4)
TRANS_STAT_SHT_N  VARCHAR2(15)
TRANS_STAT_N      VARCHAR2(30)
TRANS_STAT_T      VARCHAR2(255)

SELECT
      tr.TRANS_id_c,
      tsum.crtd_dz,
      tsum.leave_typ_t,
      tsum.rqst_strt_d,
      tsum.rqst_end_d,
      trde.appvr_em_id_c ,
      trst.TRANS_stat_t,
      curr_appvr_seq_id_c,
      TRANS_appvr_seq_id_c
      FROM
      TRANS_HDR tr,
      TRANS_DETL trde,
      TRANS_SUMY tsum,
      TRANS_stat_ref trst
      WHERE trst.TRANS_stat_id_c      = tr.TRANS_stat_id_c
            AND   tr.TRANS_id_c           = trde.TRANS_id_c
      AND   tr.TRANS_SUMY_id_c    = tsum.TRANS_SUMY_id_c
      AND   trde.TRANS_appvr_seq_id_c = tr.curr_appvr_seq_id_c
      AND (
               EXISTS (
SELECT  1 FROM TRANS_DETL trde WHERE  tr.TRANS_id_c = trde.TRANS_id_c
 AND trde.appvr_em_id_c IN
                (
                  SELECT c.em_id_c FROM prxy_em_ref c, prxy_typ_ref d
                  WHERE c.prxy_em_id_c = '692A'
                  AND   d.prxy_typ_id_c  = c.prxy_typ_id_c
                  AND   d.prxy_typ_sht_n = 'GENERAL'
                  AND   c.actv_i         = 'A'
                   )
                   OR
                   (--trde.appvr_prxy_em_id_c <> '629A' AND

                   trde.appvr_prxy_em_id_c = '629A'
                    AND trde.appvr_prxy_em_id_c IS NOT NULL
                     )
                                       ))

Description                        Object Name                  Cost
Cardinality   Bytes
---------------------------------------------------------------------------------
---------------
SELECT STATEMENT, GOAL = CHOOSE                            522    2913
1016637
 FILTER
  HASH JOIN                                            522    2913
1016637
   TABLE ACCESS FULL               TRANS_STAT_REF            1    82
11644
   HASH JOIN                                            520    3553
735471
    HASH JOIN                                            157    3553
159885
     TABLE ACCESS FULL               TRANS_HDR                       77    3553
        120802
     TABLE ACCESS FULL               TRANS_DETL                    75    77492
       852412
    TABLE ACCESS FULL               TRANS_SUMY                    342    71045
       11509290
  FILTER
   TABLE ACCESS FULL               TRANS_DETL                   75    39
 624
   NESTED LOOPS                                            2    1          29
    TABLE ACCESS BY INDEX ROWID       PRXY_TYP_REF                    1    1
     11
     INDEX UNIQUE SCAN               XUK1_PRXY_TYP_REF        1
    TABLE ACCESS BY INDEX ROWID       PRXY_EM_REF                    1    1
    18
     INDEX UNIQUE SCAN               XUK1_PRXY_EM_REF        1

now , i don't understand why none of the indexes of my primary
tables(trans_sumy, trans_hdr and trans_detl are not being used)

can you guide me where i am missing or can you suggest me any other indexes to
be created on trans_detl or trans_sumy or trans_hdr

this is the row count from all the tables

SQL> select count(*) from trans_sumy;

  COUNT(*)
----------
     71053

SQL> select count(*) from trans_hdr;

  COUNT(*)
----------
     71053

SQL> select count(*) from trans_detl;

  COUNT(*)
----------
     77492

SQL> select count(distinct appvr_em_id_c) from trans_detl;

COUNT(DISTINCTAPPVR_EM_ID_
------------------------------
                          2536

i analyzed the above tables as analyze table <table_name> compute statistics for
table for all indexes(as suggested by you)

      Rating: 4
i have simular
      problem -- explain plan again(better view)  August 27, 2002

Reviewer:  raju
Description                   Object Name       Cost
----------------------------------------------------
SELECT STATEMENT                            522
 FILTER
  HASH JOIN                            522
   TABLE ACCESS FULL           TRANS_STAT_REF    1
   HASH JOIN                            520
    HASH JOIN                            157
     TABLE ACCESS FULL           TRANS_HDR       77
     TABLE ACCESS FULL           TRANS_DETL    75
    TABLE ACCESS FULL           TRANS_SUMY    342
  FILTER
   TABLE ACCESS FULL           TRANS_DETL       75
   NESTED LOOPS                            2
    TABLE ACCESS BY INDEX ROWID       PRXY_TYP_REF    1
     INDEX UNIQUE SCAN               XUK1_PRXY_   1
                                   TYP_REF
    TABLE ACCESS BY INDEX ROWID       PRXY_EM_REF  1
     INDEX UNIQUE SCAN               XUK1_PRXY    1
                                   _EM_REF

          Followup:

question for you -- (well, question and request)

Question: why do you feel that not using indexes is "bad".  Indexes do not mean
any of the following:

o fast response time
o better then full table scan
o must be used

So, does the query perform ok?  Is the response time acceptable?

Now the request:  make this into a question, its not really a review and its
rather "large".  Wait till I'm taking questions and post it there.

      Rating: 5
About Bitmap
      again  August 31, 2002
Reviewer:
      A reader
Dear Tom,
Histograms are fine. But if we have just a couple of distinct values in a
column. Then instead of the B-tree index, why not Bitmap Index, and then collect
statistics(histogram). I hear a lot about bitmap indexes not being useful by
themselves. It is when they are used in conjunction with other bitmap indexes
typically in a warehouse environment do we realise the potential of bitmap
indexes. I feel for the columns where there are a few distinct values, and there
is a skew ( take for example the status codes in a PO-Purchase Order file, the
one still being processed would have a un-processed status, and would be very
few in numbers compared to the ones that are processed), we can collect
histogram statistics to benefit (to pick up unprocessed POs bitmap indexe would
be used, else not). Are there any guidelines on choosing between a bitmap or
b-tree indexes?
Thanks in advance for your response, it has always been greatly valued.
Regards,

          Followup:

Bitmap indexes + OLTP systems = recipe for utter and total failure.

So, take for example that PO purchase order file (typically associated with
OTLP).  Using a bitmap index on it would be very bad.

Now, if you have two states, a b*tree index can be very very useful.  Suppose
the vast majority are PROCESSED.  If you represent PROCESSED as NULL and create
index po_idx on po(processed_flag) -- the NULL entries will never make it into
the b*tree index, the index will ONLY have pointers to the unprocessed rows
(very few of them).

If you don't want to use NULL as the value, you can always use a function based
index:

create index po_index on po( decode( processed, 'N', 'N', NULL ) );

that'll index ONLY the entries with N in them (N = not processed).  And that
index will be quite small and only have one value in it (N) but that is OK, you
are using it to quickly find the unprocessed rows.

      Rating: 3
On the original
      Question...  October 14, 2002
Reviewer:  A
      reader  from BG
Tom,

This is on the original question posted-

   The cardinality of the EXPLAIN PLAN shows 882109 records whereas a query from
the Index table for the Primary Key shows num_rows as 3528434. When he did an
analyze table ERROR_TBL compute statistics, my understanding is that it will NOT
touch the index statistics and thus there will be a discrepancy between index
data and actual table data. This could also have contributed to the performance.
Am i right?

Would,
analyze table t
compute statistics
for TABLE
for all indexes,
analyze the table as well as all its indexes?

Thanks in advance

          Followup:

The num_rows IN THE INDEX is 3.5 million.

The estimated cardnality given a predicate:

select * from error_tbl where procedure_name = 'tbs_space_prc';

is 882k.  You cannot COMPARE num_rows with the cardinality expected from the
output of a particular step as you were trying -- they are apples and pears.

The analyze table does

o the table
o the indexes
o histograms on all columns

consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int , y int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx on t(x);

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select rownum, rownum from
all_users;

46 rows created.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select table_name, num_rows from user_tables;

TABLE_NAME                       NUM_ROWS
------------------------------ ----------
T                                      46

ops$tkyte@ORA817DEV.US.ORACLE.COM> select index_name, num_rows from
user_indexes;

INDEX_NAME                       NUM_ROWS
------------------------------ ----------
T_IDX                                  46

ops$tkyte@ORA817DEV.US.ORACLE.COM> select table_name, count(distinct
column_name) from user_tab_histograms group by table_name;

TABLE_NAME                     COUNT(DISTINCTCOLUMN_NAME)
------------------------------ --------------------------
T                                                       2

It got everything.

But -- what you are seeing in the example (3.5 mill in num_rows, 882k in card)
is EXACTLY what you might expect given that it had histograms (it knows the
predicate would return about 882k rows)

      Rating: 3
Why difference
      in cost when column is indexed!!!  March 10, 2003

Reviewer:  VJ  from CA,USA
Tom,

This is with regards to the first reply you gave in this thread. Please follow
your reply below:
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where x = 4;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=10 Bytes=880)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=4 Card=10 Bytes=880)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=3 Card=10)

and the optimizer has the right info -- does the right thing.  Just to
validate, lets see what happens when we user the #1:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where x = 1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=446 Card=369276
Bytes=32496288)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=446 Card=369276 Bytes=32496288)

back to a full scan - AS IT SHOULD be for that one

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

I have 2 questions:
1. You created an index on the column X and analyzed it. Why is the optimizer
behavior different just by changing the column value?
2. Can you throw some light on the "Size" option you used with the analyze
command?

          Followup:

1) that is the job of the optimizer.  Its goal in life is to take a query and
optimize it.

In this example, where x = 4 should use an index.  where x = 1 should not.

2)
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a85397/statem8a.htm#2058710

      Rating: 3
Why difference
      in cost when column is indexed-contd!!!  March 10, 2003

Reviewer:  VJ  from CA,USA
Tom,

I know you wouldn't believe until you see the actual test results. I ran your
demo with no differences, but i see different behavior than yours. Am i missing
something? why is the cost still high?
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++=
MKT-TEST->create table t as select mod(rownum,3) X, a.* from all_objects a;

Table created.

MKT-TEST->insert /*+ append */ into t select * from t;

4192 rows created.

MKT-TEST->insert /*+ append */ into t select * from t;
insert /*+ append */ into t select * from t
                          *
ERROR at line 1:
ORA-12838: cannot read/modify an object after modifying it in parallel

MKT-TEST->commit;

Commit complete.

MKT-TEST->insert /*+ append */ into t select * from t;

8384 rows created.

MKT-TEST->
MKT-TEST->update t set x = 4 where rownum <= 10;
update t set x = 4 where rownum <= 10
       *
ERROR at line 1:
ORA-12838: cannot read/modify an object after modifying it in parallel

MKT-TEST->commit;

Commit complete.

MKT-TEST->update t set x = 4 where rownum <= 10;

10 rows updated.

MKT-TEST->select x,count(0) from t group by x;

         X   COUNT(0)
---------- ----------
         0       5585
         1       5588
         2       5585
         4         10

MKT-TEST->analyze table t compute statistics;

Table analyzed.

MKT-TEST->set autotrace traceonly explain
MKT-TEST->select * from t where x = 4;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=34 Card=4192 Bytes=3
          43744)

   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=34 Card=4192 Bytes=343744
          )

MKT-TEST->analyze table t compute statistics for columns x size 100;

Table analyzed.

MKT-TEST->select * from t where x = 4;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=34 Card=10 Bytes=820
          )

   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=34 Card=10 Bytes=820)

MKT-TEST->select * from t where x = 1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=34 Card=5588 Bytes=4
          58216)

   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=34 Card=5588 Bytes=458216
          )
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

          Followup:

you have a tiny number of rows.  I had lots.  add more rows.

you don't mention a version either but given 4k objects in all_objects, it is
probably 8.0 or before -- really old stuff.

      Rating:
      5
Excellent  March 10, 2003
Reviewer:
      trevor welch
Top stuff

      Rating: 4
How do I
      compute the value of N for size  March 11, 2003

Reviewer:  Vivek Sharma  from India
Dear TOM, this was very helpful. I wanted to know how to calculate the value of
N for column size <N>. I have a production database and analyzing the tables and
indexes takes around 3-4 hours. If I wrongly calculate the value of N and if it
affects the performance, I can only correct the problem in the night, hence
continuing the bad performance for a day. So to avoid this, what is the better
way to calculate the value of N, so that the first time I analyze the tables and
indexes, it should collect correct histogram values.

Thanks in advance.
Bye
Vivek Sharma

          Followup:

depends on your version -- 9i, you can let us figure it out, 8i, you use your
knowledge of the data and how it will be used to set it appropriately.  You can
use the note above to gain an understanding of what they do for you.

      Rating: 5
This method vs.
      DBMS_STATS.GATHER_DATABASE_STATS  April 02, 2003

Reviewer:  Peter Tran  from Houson, TX USA

Tom,

Wouldn't using DBMS_STATS.GATHER_DATABASE_STATS give you better results?

Would I get a better histogram analysis using DBMS_STATS.GATHER_DATABASE_STATS
package?

In other words, if I had to analyze my tables, would you recommend using the
DBMS_STATS.GATHER_DATABASE_STATS or doing it manually with individual "analyze
table..." commands?

Thanks,
-Peter

          Followup:

I use analyze in demos.

DBMS_STATS (i prefer gather schema stats) should be used in real life.

Maybe I should switch for the demos, but analyze is easier to type ;)

      Rating: 5
Repsonse
      especially for Vivek Sharma   April 02, 2003

Reviewer:  Wolfgang Breitling  from Calgary, Canada

If you are using dbms_stats you do not need to wait until the next night to
correct harmful (for the performance) statistics. Use the stattab option to save
your current statistics. If performance suffers after refreshing the statistics
simply restore the prior statistics. It's just prudent to have a backup when you
make a change - and gathering statistics IS making a change.

      Rating: 4
getting proper
      histograms with gather_table_stats  April 28, 2003

Reviewer:  A reader
Tom, this is for release 9iR2 could you please explain your following statement
with regards to size? How do I let oracle automatically figure out the size
value generate  proper Histograms?

"depends on your version -- 9i, you can let us figure it out"

Here is what I have in one of my tables - 10000 values in each bucket (although
in actual production the data will be very unevenly distributed):

  GROUP_ID   COUNT(*)
---------- ----------
      1167      10000
      1168      10000
      1169      10000
      1170      10000
      1171      10000
      1172      10000
      1173      10000
      1174      10000
      1175      10000
      1176      10000
      1177      10000

When I ran analyze by manually specifying the size value, the histogram looks
right:

analyze table grp compute statistics for columns group_id size 11;

select column_name,ENDPOINT_NUMBER,ENDPOINT_VALUE
from user_histograms;
COLUMN_NAME          ENDPOINT_NUMBER ENDPOINT_VALUE
-------------------- --------------- --------------
GROUP_ID                      869962           1167
GROUP_ID                      879962           1168
GROUP_ID                      889962           1169
GROUP_ID                      899962           1170
GROUP_ID                      909962           1171
GROUP_ID                      919962           1172
GROUP_ID                      929962           1173
GROUP_ID                      939962           1174
GROUP_ID                      949962           1175
GROUP_ID                      959962           1176
GROUP_ID                      969962           1177

However I would like oracle to automatically figure out the size value. So I
did:

SQL> exec dbms_stats.gather_table_stats(user,'GRP',method_opt => 'for all
indexes for all indexed columns', cascade => true);

But the histograms now don't look right:
GROUP_ID                          65           1167
GROUP_ID                          66           1169
GROUP_ID                          67           1170
GROUP_ID                          68           1171
GROUP_ID                          69           1173
GROUP_ID                          70           1174
GROUP_ID                          71           1175
GROUP_ID                          72           1177

Am I missing any option in my gather_table_stats statement?

          Followup:

I cannot reproduce your situation -- but neither of those look right to me, it
should have but two entries given your data (if you want a valid test, you must
test with valid data).

Here is my test, and what I get and it is what I expected:

ops$tkyte@ORA920> REM drop table t;
ops$tkyte@ORA920> REM
ops$tkyte@ORA920> REM create table t ( group_id int );
ops$tkyte@ORA920> REM
ops$tkyte@ORA920> REM insert /*+ append */
ops$tkyte@ORA920> REM into t
ops$tkyte@ORA920> REM select group_id
ops$tkyte@ORA920> REM from
ops$tkyte@ORA920> REM (select 1167+rownum-1 group_id from all_objects where
rownum <=11),
ops$tkyte@ORA920> REM (select rownum r from all_objects where rownum <= 10000 )
ops$tkyte@ORA920> REM /
ops$tkyte@ORA920> REM commit;
ops$tkyte@ORA920> REM
ops$tkyte@ORA920> REM create index group_idx on t(group_id);
ops$tkyte@ORA920>
ops$tkyte@ORA920> select group_id, count(*) from t group by group_id;

  GROUP_ID   COUNT(*)
---------- ----------
      1167      10000
      1168      10000
      1169      10000
      1170      10000
      1171      10000
      1172      10000
      1173      10000
      1174      10000
      1175      10000
      1176      10000
      1177      10000

11 rows selected.

just like you..

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table t compute statistics for columns group_id size
11;

Table analyzed.

ops$tkyte@ORA920> select column_name,ENDPOINT_NUMBER,ENDPOINT_VALUE
  2    from user_histograms
  3   where table_name = 'T';

COLUMN_NAM ENDPOINT_NUMBER ENDPOINT_VALUE
---------- --------------- --------------
GROUP_ID             10000           1167
GROUP_ID             20000           1168
GROUP_ID             30000           1169
GROUP_ID             40000           1170
GROUP_ID             50000           1171
GROUP_ID             60000           1172
GROUP_ID             70000           1173
GROUP_ID             80000           1174
GROUP_ID             90000           1175
GROUP_ID            100000           1176
GROUP_ID            110000           1177

11 rows selected.

now that isn't what you have -- it is what I expect, -- unless you ahve a much
much larger table and didn't tell me about that?

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table t delete statistics;

Table analyzed.

ops$tkyte@ORA920> begin
  2     dbms_stats.gather_table_stats
  3     (user,'T',
  4      method_opt => 'for all indexes for all indexed columns size auto',
  5          cascade => true);
  6  end;
  7  /

PL/SQL procedure successfully completed.

there I used size auto and get:

ops$tkyte@ORA920>
ops$tkyte@ORA920> select column_name,ENDPOINT_NUMBER,ENDPOINT_VALUE
  2    from user_histograms
  3   where table_name = 'T';

COLUMN_NAM ENDPOINT_NUMBER ENDPOINT_VALUE
---------- --------------- --------------
GROUP_ID                 0           1167
GROUP_ID                 1           1177

which, since the data does not actually vary -- is totally what I want to see,
the analyze did what it was supposed to!  you'll really need to vary the data if
you want to see what production will really do.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table t delete statistics;

Table analyzed.

ops$tkyte@ORA920> begin
  2     dbms_stats.gather_table_stats
  3     (user,'T',
  4      method_opt => 'for all indexes for all indexed columns',
  5          cascade => true);
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select column_name,ENDPOINT_NUMBER,ENDPOINT_VALUE
  2    from user_histograms
  3   where table_name = 'T';

COLUMN_NAM ENDPOINT_NUMBER ENDPOINT_VALUE
---------- --------------- --------------
GROUP_ID             10000           1167
GROUP_ID             20000           1168
GROUP_ID             30000           1169
GROUP_ID             40000           1170
GROUP_ID             50000           1171
GROUP_ID             60000           1172
GROUP_ID             70000           1173
GROUP_ID             80000           1174
GROUP_ID             90000           1175
GROUP_ID            100000           1176
GROUP_ID            110000           1177

11 rows selected.

This is what I would expect to see from your test however (assuming group_id
was indexed)

      Rating: 5
size=auto did
      it  April 28, 2003
Reviewer:  A reader

It took longer to analyze but size=auto in the method_opt clause produced the
correct histogram (method_opt => 'for all indexed columns size auto'). This is
really neat - I don't have to worry about specifying different size values for
different tables/columns.

      Rating: 4
Here is the
      realistic data - which histogram is more accurate?  April
      29, 2003
Reviewer:  A reader
Tom, what you pointed out in my previous posting was indeed correct - I had
posted only a part of the histogram and my data wasn't realistic either. But
here are 2 histograms from realistic data.
1) Which histogram is more accurate - the one with analyze or the one with
gather_table_stats? Hope its gather_table_stats - don't wanna specify a size in
analyze.
2) can I speed up dbms_gather_stats?

SQL> select pas_id,count(*) from pas_stats group by pas_id;

    pas_id   COUNT(*)
---------- ----------
        81      36572
       101         39
       121     183437
       181         89
       201        115
       202     135689
       203      89332
       241       3157
       301    1494621
       302     981543
       303       9593

SQL> analyze table pas_stats delete statistics;

SQL> analyze table pas_stats compute statistics for columns pas_id size 11
;

Table analyzed.

Elapsed: 00:03:49.57

select column_name,ENDPOINT_NUMBER,ENDPOINT_VALUE
from user_histograms
where table_name ='pas_stats';

COLUMN_NAME          ENDPOINT_NUMBER ENDPOINT_VALUE
-------------------- --------------- --------------
pas_id                         36572             81
pas_id                         36611            101
pas_id                        220048            121
pas_id                        220137            181
pas_id                        220252            201
pas_id                        355941            202
pas_id                        445273            203
pas_id                        448430            241
pas_id                       1943170            301
pas_id                       2924778            302
pas_id                       2934371            303

analyze table pas_stats delete statistics;

dbms_stats.gather_table_stats(user,'pas_stats',method_opt => 'for all indexes
for all indexed columns size auto',cascade=>true);

  1  select column_name,ENDPOINT_NUMBER,ENDPOINT_VALUE
  2  from user_histograms
  3  where table_name ='pas_stats'
  4* and column_name='pas_id'
SQL> /

COLUMN_NAME          ENDPOINT_NUMBER ENDPOINT_VALUE
-------------------- --------------- --------------
pas_id                             0             81
pas_id                             1            303

      Rating: 3
Query
      Performance  May 29, 2003
Reviewer:  Rahul
       from India
Hi Tom

Good Morning.
I am wrestling with a query using a function for last 3 days but didn't get the
better performance after trying with all ideas,(used hints,indexes etc.).
Below is the function and query-

create or replace function f_getLicenseeLines
(arl_LicenseNo number,
arl_class varchar2 default ''
)
return varchar2 as ls_lines_string  varchar2(4000);
ll_codeid number;
begin
 ls_lines_string := '';
 select codeid into ll_codeid from lwgncodelist where codetype='STS' and code =
'AP';
 if length(trim(arl_class)) is null then

  for rec in
  (
   select distinct  linedesc
   from  lwpragentlines al , lwprlicense lic, lwgnclasslines cl
   where al.licenseid = lic.licenseid
   and  al.lineno = cl.lineno
   and al.class = cl.class
   and lic.licenseno = arl_LicenseNo
   and al.linestatus = ll_codeid
  )
   loop

     ls_lines_string := ls_lines_string || ',' || trim(rec.linedesc) ;

   end loop;

 else

   for rec in
   (
   select  distinct  linedesc
   from  lwpragentlines al, lwprlicense lic, lwgnclasslines cl
   where lic.licenseno = arl_LicenseNo
   and al.licenseid = lic.licenseid
   and  al.lineno = cl.lineno
   and al.class = cl.class
   and al.class = trim(arl_class)
   and al.linestatus = ll_codeid
  )
   loop

    ls_lines_string := ls_lines_string || ',' || trim(rec.linedesc) ;

   end loop;

 end if;

 ls_lines_string := substr(ls_lines_string,2);
 if ls_lines_string = '' then
   ls_lines_string := '-';
 end if;
  return  ls_lines_string;
end ;
/

SELECT C.homestate as homestate,C.agentid as agentid, lastname||'
'||FIRSTNAME||' '||MIDDLENAME||
decode(bcardcount,0,'',null,'','#Y')
AGENTNAME,DECODE(A.SSN,NULL,'-',substr(A.SSN,1,3)||'-'||
substr(A.SSN,4,2)||'-'||SUBSTR(A.SSN,6))   AS SSN, LPAD(licenseno,7,0) LICENSENO
,A.INDIVIDUALID as INDIVIDUALID,
BUSINESSTYPE,to_char(dob,'mm/dd/yyyy') dob , f_getLicenseeLines(B.licenseno)
class  from lwpragent C,
  lwprlicense B, lwgnindividual A  Where     C.agentid = B.agentid AND
A.individualid = C.individualid
 AND B.CLASS = 'PRO'  AND (B.LICENSESTATUS='A' OR  B.LICENSESTATUS ='AP')
/

Table contains the rows-
LWPRAGENT - 162793
LWGNINDIVIDUAL - 150072
LWPRLICENSE - 162865
LWGNCLASSLINES - 18
LWPRAGENTLINES - 414107

Every column used in query and function has proper index on that, but still
query is taking 2-3 MINUTES to fire.
I don't know what I am missing.
Please suggest me what to do with this query to it faster?

Thanks a lot.

          Followup:

so, how long does it take without the function and how many rows does it return
ultimately.

      Rating: 3
Query
      Performance  May 29, 2003
Reviewer:  Rahul
       from India
Hi Tom

Good Morning.
I am wrestling with a query using a function for last 3 days but didn't get the
better performance after trying with all ideas,(used hints,indexes etc.).
Below is the function and query-

create or replace function f_getLicenseeLines
(arl_LicenseNo number,
arl_class varchar2 default ''
)
return varchar2 as ls_lines_string  varchar2(4000);
ll_codeid number;
begin
 ls_lines_string := '';
 select codeid into ll_codeid from lwgncodelist where codetype='STS' and code =
'AP';
 if length(trim(arl_class)) is null then

  for rec in
  (
   select distinct  linedesc
   from  lwpragentlines al , lwprlicense lic, lwgnclasslines cl
   where al.licenseid = lic.licenseid
   and  al.lineno = cl.lineno
   and al.class = cl.class
   and lic.licenseno = arl_LicenseNo
   and al.linestatus = ll_codeid
  )
   loop

     ls_lines_string := ls_lines_string || ',' || trim(rec.linedesc) ;

   end loop;

 else

   for rec in
   (
   select  distinct  linedesc
   from  lwpragentlines al, lwprlicense lic, lwgnclasslines cl
   where lic.licenseno = arl_LicenseNo
   and al.licenseid = lic.licenseid
   and  al.lineno = cl.lineno
   and al.class = cl.class
   and al.class = trim(arl_class)
   and al.linestatus = ll_codeid
  )
   loop

    ls_lines_string := ls_lines_string || ',' || trim(rec.linedesc) ;

   end loop;

 end if;

 ls_lines_string := substr(ls_lines_string,2);
 if ls_lines_string = '' then
   ls_lines_string := '-';
 end if;
  return  ls_lines_string;
end ;
/

SELECT C.homestate as homestate,C.agentid as agentid, lastname||'
'||FIRSTNAME||' '||MIDDLENAME||
decode(bcardcount,0,'',null,'','#Y')
AGENTNAME,DECODE(A.SSN,NULL,'-',substr(A.SSN,1,3)||'-'||
substr(A.SSN,4,2)||'-'||SUBSTR(A.SSN,6))   AS SSN, LPAD(licenseno,7,0) LICENSENO
,A.INDIVIDUALID as INDIVIDUALID,
BUSINESSTYPE,to_char(dob,'mm/dd/yyyy') dob , f_getLicenseeLines(B.licenseno)
class  from lwpragent C,
  lwprlicense B, lwgnindividual A  Where     C.agentid = B.agentid AND
A.individualid = C.individualid
 AND B.CLASS = 'PRO'  AND (B.LICENSESTATUS='A' OR  B.LICENSESTATUS ='AP')
/

Table contains the rows-
LWPRAGENT - 162793
LWGNINDIVIDUAL - 150072
LWPRLICENSE - 162865
LWGNCLASSLINES - 18
LWPRAGENTLINES - 414107

Every column used in query and function has proper index on that, but still
query is taking 2-3 MINUTES to fire.
I don't know what I am missing.
Please suggest me what to do with this query to it faster?

Thanks a lot.

      Rating: 3
Query
      Performance  May 29, 2003
Reviewer:  Rahul
       from India
Hi Tom

Good Morning.
I am wrestling with a query using a function for last 3 days but didn't get the
better performance after trying with all ideas,(used hints,indexes etc.).
Below is the function and query-

create or replace function f_getLicenseeLines
(arl_LicenseNo number,
arl_class varchar2 default ''
)
return varchar2 as ls_lines_string  varchar2(4000);
ll_codeid number;
begin
 ls_lines_string := '';
 select codeid into ll_codeid from lwgncodelist where codetype='STS' and code =
'AP';
 if length(trim(arl_class)) is null then

  for rec in
  (
   select distinct  linedesc
   from  lwpragentlines al , lwprlicense lic, lwgnclasslines cl
   where al.licenseid = lic.licenseid
   and  al.lineno = cl.lineno
   and al.class = cl.class
   and lic.licenseno = arl_LicenseNo
   and al.linestatus = ll_codeid
  )
   loop

     ls_lines_string := ls_lines_string || ',' || trim(rec.linedesc) ;

   end loop;

 else

   for rec in
   (
   select  distinct  linedesc
   from  lwpragentlines al, lwprlicense lic, lwgnclasslines cl
   where lic.licenseno = arl_LicenseNo
   and al.licenseid = lic.licenseid
   and  al.lineno = cl.lineno
   and al.class = cl.class
   and al.class = trim(arl_class)
   and al.linestatus = ll_codeid
  )
   loop

    ls_lines_string := ls_lines_string || ',' || trim(rec.linedesc) ;

   end loop;

 end if;

 ls_lines_string := substr(ls_lines_string,2);
 if ls_lines_string = '' then
   ls_lines_string := '-';
 end if;
  return  ls_lines_string;
end ;
/

SELECT C.homestate as homestate,C.agentid as agentid, lastname||'
'||FIRSTNAME||' '||MIDDLENAME||
decode(bcardcount,0,'',null,'','#Y')
AGENTNAME,DECODE(A.SSN,NULL,'-',substr(A.SSN,1,3)||'-'||
substr(A.SSN,4,2)||'-'||SUBSTR(A.SSN,6))   AS SSN, LPAD(licenseno,7,0) LICENSENO
,A.INDIVIDUALID as INDIVIDUALID,
BUSINESSTYPE,to_char(dob,'mm/dd/yyyy') dob , f_getLicenseeLines(B.licenseno)
class  from lwpragent C,
  lwprlicense B, lwgnindividual A  Where     C.agentid = B.agentid AND
A.individualid = C.individualid
 AND B.CLASS = 'PRO'  AND (B.LICENSESTATUS='A' OR  B.LICENSESTATUS ='AP')
/

Table contains the rows-
LWPRAGENT - 162793
LWGNINDIVIDUAL - 150072
LWPRLICENSE - 162865
LWGNCLASSLINES - 18
LWPRAGENTLINES - 414107

Every column used in query and function has proper index on that, but still
query is taking 2-3 MINUTES to fire.
I don't know what I am missing.
Please suggest me what to do with this query to it faster?

Thanks a lot.

      Rating: 3
Without
      Function  May 30, 2003
Reviewer:  Rahul
       from India
Hi

Without function when I run this query, it takes 47sec but when I use hint
/*+First_rows*/ then it takes 37 sec.

Please suggest.

Thanks

          Followup:

suggest you tell me how many rows it returns utlimately

      Rating: 3
No. of
      Rows  May 30, 2003
Reviewer:  Rahul
       from India
Hi

Sorry forgot to write no. of rows.
Query return around 1000000 rows.

Thanks

          Followup:

1,000,000 function calls is going to take a while, especially since each
function call from SQL is a context switch from sql to plsql back to sql to
plsql back to sql ....

it is doing 12,500 of those (if the query takes 47 seconds without and about 2
minutes or so) a SECOND and each function call itself runs sql.  Pretty
impressive no?

don't know what to tell you.  1,000,000 calls to anything is going to take some
amount of time.

      Rating: 4
Syntax :
      dbms_stats/Analyze  August 06, 2003
Reviewer:
      Neeraj Nagpal  from Los Angeles, CA
I know that you normally suggest using DBMS_STATS as opposed to ANALYZE, what
parameters should be passed to DBMS_STATS in order to get the same effect as :

analyze table t
compute statistics
for TABLE
for all indexes
for all indexed columns SIZE <N>;

Thanks Always for your help,
Neeraj Nagpal

          Followup:

method_opt => 'for all indexed columns size N', cascade => true );

      Rating: 4
Rephrase My
      question..  August 07, 2003
Reviewer:  Neeraj
      Nagpal  from Los Angeles, CA
I should have probably described the problem I am getting with the command I
use, I have a composite partitioned table with local primary key index on it.
When I try to analyze it, this is what I get

Wrote file afiedt.buf

  1   BEGIN
  2      DBMS_STATS.gather_table_stats (
  3         ownname=> 'FNCDEV',
  4         tabname=> 'SALES_LAYER',
  5         partname=> 'ALABAMA',
  6         method_opt => 'for all indexes for all indexed columns',
  7         cascade => false);
  8* End;
13:33:39  SQL> /
 BEGIN
*
ERROR at line 1:
ORA-14506: LOCAL option required for partitioned indexes
ORA-06512: at "SYS.DBMS_DDL", line 179
ORA-06512: at "SYS.DBMS_STATS", line 4469
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at line 2

          Followup:

so, did you try using the options you asked me for in the first place?????

if you do -- you might be surprised, just look up a review.

      Rating: 4
Behaviour of
      Query after analyzing  August 18, 2003
Reviewer:
      Vivek Sharma  from Bombay, India
Dear Tom,

I have a query as under :

select a.customername
from finnonelea.nbfc_customer_m a, finnonelea.lea_agreement_dtl b
where b.applid = :b1 and b.lesseeid = a.customerid;

NBFC_CUSTOMER_M table is a synonym and is an object of another schema
FINNONEGCD. Originally all the schemas were not analyzed and this query was
using following execution plan :

SELECT STATEMENT   [CHOOSE] Cost =
  NESTED LOOPS
    TABLE ACCESS BY INDEX ROWID LEA_AGREEMENT_DTL
      INDEX RANGE SCAN LEA_AGREEMENT_DTL_IDX1
    TABLE ACCESS BY INDEX ROWID NBFC_CUSTOMER_M
      INDEX UNIQUE SCAN SYS_C0076632

But as soon as I analyze the schemas, the same query goes for a FTS on
NBFC_CUSTOMER_M Table which has 2 million records. Why is it so ?
The explain plan after analyzing is

SELECT STATEMENT   [CHOOSE] Cost = 11788
  HASH JOIN
    TABLE ACCESS BY INDEX ROWID LEA_AGREEMENT_DTL
      INDEX RANGE SCAN LEA_AGREEMENT_DTL_IDX1
    TABLE ACCESS FULL NBFC_CUSTOMER_M [ANALYZED]

Can you pls help me out in tuning this sql. My Application vendor says not to
analyze the schemas and use RBO, I suggest analyzing the schemas and let CBO do
the work. But this query works drastically slow. There are many more which has
got affected after analyzing. Please give your expertise in this matter.

Regards

          Followup:

are ALL of the objects analyzed?

what is the cost/card/bytes of each step (use set autotrace traceonly explain
for example)

(but, in order to be supported by your application vendor, you must really
follow THEIR rules)

      Rating: 4
Behaviour of
      Query after analyzing   August 19, 2003
Reviewer:
      Vivek Sharma  from Bombay, India
Dear Tom,

I am sorry for my previous question and thanks a lot for your prompt answer. The
Schema FINNONELEA was not fully analyzed since we were short of time. This is a
big schema with almost 1000 tables out of which only 500 tables analyzed. May be
LEA_AGREEMENT_DTL table was not analyzed. I have checked on my development
environment which is a replica of this live server. Once I analyze both the
tables, it is using proper index whereas if only NBFC_CUSTOMER_M is analyzed,
this table goes for FTS.

But your comment that to gain application vendors support, I should not analyze
the schema puts me in some sort of confusion. Because, I have read in Oracle
Documentation that RBO may not be available in the future version of Oracle
hence we should opt for CBO. What is your comment ?

Regards

          Followup:

My comment is

o you bought a product from a vendor
o this product happens to work on a database
o you must follow their rules for configuring that database if you wish to be
  supported and supportable by them

they are the ones that need to worry about cbo/rbo, being supported on Oracle.
You have to worry about being supported by them, getting the performance you
were promised and so on.  What they do to support you, give you the performance
you were promised is 100% up to them.

      Rating: 4
execution plan
      differs in development db and production db  September 24,
      2003
Reviewer:  A reader
Hi

I have a query which runs blazing fast in production but deadly slow in
development, obviously my database have different parameters however both of
them have very similar amount of data.

In development it uses this plan

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5300 Card=1 Bytes=200)
   1    0   FILTER
   2    1     NESTED LOOPS (Cost=5300 Card=1 Bytes=200)
   3    2       HASH JOIN (Cost=5299 Card=1 Bytes=181)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'CONTRATOS' (Cost=113
Card=1562 Bytes=39050)
   5    4           INDEX (RANGE SCAN) OF 'CONTRATO_SERVICIOS_FK_I' (NON-UNIQUE)
(Cost=8 Card=1562)
   6    3         TABLE ACCESS (FULL) OF 'CONTRATO_DETALLE_LINEAS' (Cost=5146
Card=6241 Bytes=973596)
   7    2       TABLE ACCESS (BY INDEX ROWID) OF 'CLIENTES' (Cost=1 Card=57294
Bytes=1088586)
   8    7         INDEX (UNIQUE SCAN) OF 'CLIENTE_PK' (UNIQUE)
   9    1     SORT (AGGREGATE)
  10    9       TABLE ACCESS (BY INDEX ROWID) OF 'CONTRATO_DETALLE_LINEAS'
(Cost=4 Card=1 Bytes=25)
  11   10         INDEX (RANGE SCAN) OF 'CON_DET_LIN_CO_AP_CO_FK_I' (NON-UNIQUE)
(Cost=3 Card=1)

in Production

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=725 Card=1 Bytes=203)
   1    0   FILTER
   2    1     NESTED LOOPS (Cost=725 Card=1 Bytes=203)
   3    2       NESTED LOOPS (Cost=724 Card=1 Bytes=183)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'CONTRATOS' (Cost=27
Card=1393 Bytes=37611)
   5    4           INDEX (RANGE SCAN) OF 'CONTRATO_SERVICIOS_FK_I' (NON-UNIQUE)
(Cost=4 Card=1393)
   6    3         TABLE ACCESS (BY INDEX ROWID) OF 'CONTRATO_DETALLE_LINEAS'
(Cost=1 Card=329 Bytes=51324)
   7    6           INDEX (RANGE SCAN) OF 'CON_DET_LI_CON_LI_PED_FK_I'
(NON-UNIQUE) (Cost=1 Card=329)
   8    2       TABLE ACCESS (BY INDEX ROWID) OF 'CLIENTES' (Cost=1 Card=57315
Bytes=1146300)
   9    8         INDEX (UNIQUE SCAN) OF 'CLIENTE_PK' (UNIQUE)
  10    1     SORT (AGGREGATE)
  11   10       TABLE ACCESS (BY INDEX ROWID) OF 'CONTRATO_DETALLE_LINEAS'
(Cost=1 Card=1 Bytes=26)
  12   11         INDEX (RANGE SCAN) OF 'CON_DET_LI_PK' (UNIQUE) (Cost=3 Card=1)

hash_area_size in production is 24mb whereas develoment is 128k so I dont
understand why Oracle choses to use hash join.

The plan becomes same if I decrease db_file_multiblock_read_count in development
to 8 (originally 16 as production)

I can understand they have different plan if amount of data is very diferent but
in my case they are similar (development is a copy of production from 3 days
ago)

          Followup:

look at the card numbers (cardinality)

you may very well have the same volume of data however  either

o the skew is totally totally alot (big time) different
o the stats in one of the systems are wrong

can you tell me which might be true?  that is what is affecting the plan here.

      Rating: 5
forget previous
      post  September 24, 2003
Reviewer:
      A reader
Hi

I found the problem, the slow query has default optimizer parameter values....

      Rating: 4
usefulness of
      system statistics  September 24, 2003
Reviewer:
      Reader
Tom, in 9i, what kind of system statistics oracle generates when i use
dbms_stats package to generate statistics? How is it useful? Can I export the
system statistics from prod database to test database and use it to tune
queries? Thanks.

          Followup:

I wrote about this alot in my new book -- looking at system stats.  Here is a
short excerpt:

<quote>
Use SYSTEM Statistics

Oracle9i introduced an alternative to OPTIMIZER_INDEX_COST_ADJ and
OPTIMIZER_INDEX_CACHING that is worth a serious look. The DBMS_STATS package was
enhanced to collect a new type of statistic: system statistics. These statistics
measure actual performance characteristics of your unique system. They measure
your actual CPU and disk-performance characteristics using your actual
workloads. They are not guesses. They are actual, measured observations of what
has occurred in your system during the period of measurement. Depending on the
type of work you actually perform, different costs will be associated with
various operations, affecting the eventual query plans that result.
Every physical configuration will perform a little differently than any other
configuration. That is why the perfect numbers for the OPTIMIZER_INDEX_*
parameters cannot simply be stated. The best numbers rely on your systems
fingerprint-its unique characteristics. Hence, the ability for us to measure,
collect, and then use actual performance characteristics of our systems can
reduce or remove the need to guess at these figures.

System statistics gathering works in one of two ways:
o You tell Oracle to start measuring now, and then after a while, you tell it to
stop measuring now.
o You tell Oracle to measure system statistics for the next <some period of
time>, and Oracle will start and stop by itself.

In order to demonstrate how this works, we need to set up a simulation. For this
example, we will use the explicit start/stop method. We need to simulate a
typical load on our system as well, one that is representative of the load we
would expect day to day. Normally, the DBA would simply gather these system
statistics during normal loads on the system itself; no simulation is necessary.

.....
</quote>

yes, you can export from prod to test

no, i don't see how you can use it to "tune" queries unless the hardward is plug
identical 100% on prod and test.  it would simply cause the plans on test to be
the same as prod and that may or may not be showing you how prod will actually
perform as test has a different "fingerprint"

      Rating:
      5
Thanks  September 25, 2003
Reviewer:
      Reader

      Rating: 5
Can CBO see
      historgrams through a join?  September 30, 2003

Reviewer:  Mike Wilson  from Los Angeles
I finally understand histograms.  I ran into a situation with severe data skew
today this helped me track down the performance issue.  My question is though,
can the CBO use histograms with joins?  Below find an example of an efficient
query that uses histograms to answer a simple question.  If I modify the query
slightly by joining it to a very small table it reverts back to FTS's and
ignoring a useful, analyzed index on a table with valid histogram data that is
*should* use to answer the question.  Thanks for any light you can shed on this
variation of the question above.

/* two tables request and a very small lookup table of values. */

da@prodspi> select count(*) from request;

  COUNT(*)
----------
    212080

/* very small loookup table that is fk'd to request */

da@prodspi> select pk_x_request_status, str_name from x_request_status;

PK_X_REQUEST_STATUS              STR_NAME
-------------------------------- ----------------------------------------
9CF7331A7EE3616EE034080020C902BB NEW
9CF7331A7EE4616EE034080020C902BB RUNNING
9CF7331A7EE5616EE034080020C902BB CANCELLED
9CF7331A7EE6616EE034080020C902BB COMPLETE
9CF7331A7EE7616EE034080020C902BB FAILED
B5408C414C6E27ADE034080020C905F8 READY_TO_RUN
DA5A6AA5552E46E8ADE485F4DB4B9029 NEEDS_TAPE

7 rows selected.

/* now show that the data is skewed */

da@prodspi> edit
Wrote file afiedt.buf

  1  select count(a.pk_request), b.str_name, b.pk_x_request_status
  2  from request a, x_request_status b
  3  where a.pk_x_request_status = b.pk_x_request_status
  4* group by b.str_name, b.pk_x_request_status
da@prodspi> /

COUNT(A.PK_REQUEST) STR_NAME                                 PK_X_REQUEST_STATUS
------------------- ----------------------------------------
--------------------------------
                  1 NEW
9CF7331A7EE3616EE034080020C902BB
              11371 FAILED
9CF7331A7EE7616EE034080020C902BB
                  5 RUNNING
9CF7331A7EE4616EE034080020C902BB
             188634 COMPLETE
9CF7331A7EE6616EE034080020C902BB
              12070 CANCELLED
9CF7331A7EE5616EE034080020C902BB

/* create histograms so I can find the RUNNING jobs by pk_x_request_status */

da@prodspi> analyze table request compute statistics for all indexes
  2  for all indexed columns size 10;

Table analyzed.

da@prodspi> edit
Wrote file afiedt.buf

  1  SELECT
  2      pk_request
  3   FROM "REQUEST"
  4*  WHERE pk_x_request_status = '9CF7331A7EE4616EE034080020C902BB'
da@prodspi> /

PK_REQUEST
--------------------------------
B53E945C635049E0BB9F13A877AE08E8
DC72E30EC66D44E7ACCAEB97A603E791
927CDFD382B441DB8BF61A4E0B794757
95838D2FE8514778B01840C41120628B
0C4A126DA645402E9D8E73979EB46505

Elapsed: 00:00:01.01

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=5 Bytes=320)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'REQUEST' (Cost=4 Card=5 Bytes=320)
   2    1     INDEX (RANGE SCAN) OF 'REQUEST_STATUS_IDX' (NON-UNIQUE) (Cost=3
Card=5)

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

/* very slow before histograms, now, very efficient */

/* now join the large skewed request table to the small lookup table
   which allows me to search by a meaningful tag */
da@prodspi> edit
Wrote file afiedt.buf

  1  SELECT
  2      r.pk_request
  3   FROM da.REQUEST r, da.x_request_status rs
  4   WHERE r.pk_x_request_status = rs.pk_x_request_status
  5*    AND rs.str_name = 'RUNNING'
da@prodspi> /

PK_REQUEST
--------------------------------
B53E945C635049E0BB9F13A877AE08E8
DC72E30EC66D44E7ACCAEB97A603E791
927CDFD382B441DB8BF61A4E0B794757
95838D2FE8514778B01840C41120628B
0C4A126DA645402E9D8E73979EB46505

Elapsed: 00:00:01.06

/* notice I am back to FTS and this query becomes very expensive.  Also note
   the card of step 4.  The CBO seems to revert back to believing I have
   a standard distribution of data on this column */

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=601 Card=42395 Bytes=4493870)
   1    0   NESTED LOOPS (Cost=601 Card=42395 Bytes=4493870)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'X_REQUEST_STATUS' (Cost=1 Card=1
Bytes=42)
   3    2       INDEX (UNIQUE SCAN) OF 'SYS_C0026719' (UNIQUE)
   4    1     TABLE ACCESS (FULL) OF 'REQUEST' (Cost=600 Card=42395
Bytes=2713280)

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

/* just for fun, show that their is a *very* usable index on this column
   that the CBO seems to be ignoring now that that the request table is involved
   in a join. */

da@prodspi> edit
Wrote file afiedt.buf

  1  SELECT /*+ INDEX (r request_status_idx) */
  2      r.pk_request
  3   FROM da."REQUEST" r, da.x_request_status rs
  4   WHERE r.pk_x_request_status = rs.pk_x_request_status
  5*    AND rs.str_name = 'RUNNING'
da@prodspi> /

PK_REQUEST
--------------------------------
B53E945C635049E0BB9F13A877AE08E8
DC72E30EC66D44E7ACCAEB97A603E791
927CDFD382B441DB8BF61A4E0B794757
95838D2FE8514778B01840C41120628B
0C4A126DA645402E9D8E73979EB46505

Elapsed: 00:00:01.08

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1981 Card=42395 Bytes=4493870)
   1    0   NESTED LOOPS (Cost=1981 Card=42395 Bytes=4493870)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'X_REQUEST_STATUS' (Cost=1 Card=1
Bytes=42)
   3    2       INDEX (UNIQUE SCAN) OF 'SYS_C0026719' (UNIQUE)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'REQUEST' (Cost=1980 Card=42395
Bytes=2713280)
   5    4       INDEX (RANGE SCAN) OF 'REQUEST_STATUS_IDX' (NON-UNIQUE)
(Cost=340 Card=42395)

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

Any ideas as to what might cause this?  I have only tested this in this one
case.

          Followup:

The optimizer, in looking at:

  1  SELECT
  2      r.pk_request
  3   FROM da.REQUEST r, da.x_request_status rs
  4   WHERE r.pk_x_request_status = rs.pk_x_request_status
  5*    AND rs.str_name = 'RUNNING'
da@prodspi> /

has NO CLUE as to the correlation between the various columns.  there are no
statistics in 9i and before that would help it -- it is a very difficult problem
actually.  Think about it -- that RS table could have anything in
PK_X_REQUEST_STATUS when STR_NAME = RUNNING.  The optimizer in this case falls
back to "best guess"

I can say in a future release of the database -- things like this will be
solvable without the use of hinting -- but in this case, I would probably go for
a first_rows style approach rather then an index hint.  you want first rows
fastest -- whatever that is -- it leaves more paths open for the optimizer
rather then telling it exactly what to do.

      Rating: 5
More on bitmap
      indexes  October 21, 2003
Reviewer:  Alla
       from Boston, MA USA
I have a table in data warehouse with about 17 million records.

Table has a column called time_key with 63 distinct values

I also have a bitmap index on the time_key column.

Table was analyzed using the following command:

analyze table posted_invoice_lines_facts_t compute statistics for columns
time_key size 100;

When I am selecting from the table and say that time_key = :value, it's using
the index, but when I need to select few time_key, let's say 10, it does not.

Can you explain this please

Thanks

select distinct account_key
from posted_invoice_lines_facts_t
where time_key = 'SEP-03';

tion Plan
-----------------------------------------------------
     SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=1 Bytes=12)
   0   SORT (UNIQUE) (Cost=6 Card=1 Bytes=12)
   1     TABLE ACCESS (BY INDEX ROWID)  OF 'POSTED_INVOICE_LINES_FACTS_T'
(Cost=3 Card=1 Bytes=12)
   2       BITMAP CONVERSION (TO ROWIDS)
   3         BITMAP INDEX (SINGLE VALUE) OF 'POSTED_INVOICE_LINES_TIME'

select distinct account_key
from posted_invoice_lines_facts_t
where time_key in ('JAN-03', 'FEB-03', 'MAR-03',
     'APR-03', 'MAY-03', 'JUN-03', 'JUL-03',
     'AUG-03', 'SEP-03', 'OCT-03');

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=18961 Card=534 Bytes=6408)
   1    0   SORT (UNIQUE) (Cost=18961 Card=534 Bytes=6408)
   2    1     TABLE ACCESS (FULL)  OF 'POSTED_INVOICE_LINES_FACTS_T' (Cost=14135
Card=1042863 Bytes=12514356)

          Followup:

look at the rows it will retrieve from the table:  Card=1,042,863

That is more then you want to 'table access by index rowid' in all likelyhood

tell you what -- HINT the query to use the index and using TKPROF, benchmark
both.  See which is better

(and remember -- please keep chanting

full scans are not evil
indexes are not good incarnate
full scans are not evil
indexes are not good incarnate
full scans are not evil
indexes are not good incarnate
full scans are not evil
indexes are not good incarnate
full scans are not evil
indexes are not good incarnate

over and over....)

October 21, 2003 Edgars -- Thanks for the question regarding
      "Creating custom filter", version 9.0.1.3

      You Asked
Hello Tom,

I need to add filtering option to my application. I have searched all
over the web and didn't found any good methods of creating one. I need
to filter "objects" based on some "parameters" I've decided that I can
create temporary table (filter) where I will write, does this
"parameter" need to be present in object, or not.

create table obj(
       id integer,
       shortdescription varchar(50),
       primary key (id) )
/
create table obj_params(
       obj_id integer,
       param  varchar(50),
       foreign key(obj_id) references obj(id) )
/
create global temporary table filter(
       param varchar(50),
       include integer default(0) )
/
insert into obj values (1,'test 01');
insert into obj values (2,'test 02');
insert into obj values (3,'test 03');

insert into obj_params values ( 1, 'sdl' );
insert into obj_params values ( 1, 'wrm' );
insert into obj_params values ( 1, 'elk' );

insert into obj_params values ( 2, 'sdl' );

insert into obj_params values ( 3, 'elk' );

Than when I insert into filter values ( 'sdl', 0 ); should mean that I
need all objects that doesn't contains 'sdl' parameter, if I insert into
filter values( 'elk', 1 ); should mean that I need all objects that
does have 'elk' parameter. I was trying to write custom function that
returns 1 if objects "matches" filter, but this function is working
quite slowly. Now I'am trying to rewrite this in one query with
subquery that will return all "objects" that "matches" filter, can
you suggest, how can this be achieved? Thank you.

      and we said...
select *
  from obj
 where id in ( select obj_id
                 from obj_params, filter
                where obj_params.param = filter.param
                group by obj_id
               having min(include) = 1
              )

does that.

Review &
      Followup

      Rating: 3
But how to
      exclude all objects having include - 0  August 19, 2002

Reviewer:  Edgar Chupit  from Latvia
Hello Tom,

This query returns all "objects" that have 1 in include field, but how to
exclude from this result set all objects that have param field, that we don't
want? What I really need is something like:

select *
  from obj
 where id in (
              select id
              from obj
              minus
              (
                  select obj_id
                  from obj_params, filter
                  where obj_params.param = filter.param
                  group by obj_id
                  having min(include) = 1
                  minus
                  select obj_id
                  from obj_params, filter
                  where obj_params.param = filter.param
                  group by obj_id
                  having max(include) = 0
              )
      )

But maybe a little bit simpler. Thank you!

          Followup:

you make no sense to me now.

I thought "value", 0 meant I don't want it.  "value", 1 means I do want it.  I
got this idea from this:

<quote>
Than when I insert into filter values ( 'sdl', 0 ); should mean that I
need all objects that doesn't contains 'sdl' parameter, if I insert into
filter values( 'elk', 1 ); should mean that I need all objects that
does have 'elk' parameter.
</quote>

You said -- when I put 'sdl', 0 in the filter -- I DON'T want objects that
contain that.  When I put 'elk', 1 in there -- I want the objects that contain
that.

The query I wrote provides that.

I no longer think that I know what you want.

You'll need to be more clear.

      Rating:
      4
explanation  August 19, 2002
Reviewer:
      Edgars  from Latvia
Hello Tom,

Yes, you understand me correctly, It's my mistake that I didn't explained all
till the end.
<quote> You said -- when I put 'sdl', 0 in the filter -- I DON'T want objects
that contain that.  When I put 'elk', 1 in there -- I want the objects that
contain that. </quote>. I didn't mentioned one condition, when object doesn't
have any params that have 1 I need to include that objects,
what I really want is:

from all objects
remove (filter) objects that don't have PARAMETER with INCLUDE in
FILTER set to 1.
remove (filter) objects that have PARAMETER with INCLUDE in FILTER set
to 0.
Leave all other objects. I hope that I explained everything correctly.

Sorry, for bothering. Thank you!

          Followup:

still confused here.

You say

a) remove objects that do not have a parameter with include set to 1
b) remove objects that have a parameter with include set to 0

"leave" all other objects

Well, if we do a) and b) there are no other objects so I'm at a loss here.  Play
with some subqueries and see if you can get what you want cause I really don't
get it.

      Rating:
      4
  October 21, 2003
Reviewer:
      Randall
Tom,

I am working on a similar query.  I got it working but I would like you to
review and see if there is a better way of doing it.  My database is 8.1.7 but
will be 9iR2 soon.

I have two tables where the first column is a group number and second column is
a unknown number of attributes.  I will always query for a single value of T1.A

create table T1 (A number , B number);
create table T2 (C number, B number);

insert into T1 values (1,100);
insert into T1 values (1,101);
insert into T1 values (1,103);
insert into T1 values (1,105);
insert into T1 values (1,107);

insert into T1 values (2,100);
insert into T1 values (2,102);
insert into T1 values (2,104);

insert into T1 values (3,100);
insert into T1 values (3,101);
insert into T1 values (3,103);
insert into T1 values (3,102);
insert into T1 values (3,104);

insert into T2 values (201,103);
insert into T2 values (201,107);

insert into T2 values (202,107);

insert into T2 values (203,101);
insert into T2 values (203,104);

insert into T2 values (204,102);

Query for A group 1

SQL> SELECT T2.C, T1.B
  2  FROM T2,(SELECT B FROM T1 WHERE A=1) T1
  3  WHERE T2.B = T1.B(+)
  4  ORDER BY 1,2;

         C          B
---------- ----------
       201        103
       201        107
       202        107
       203        101
       203
       204

6 rows selected.

So in this case I would want T2.C values 201 and 202.  203 and 204 should be
excluded because at least one of its B attributes is not in the T1.A set.

My current query would look like this.

SQL> SELECT T2.C
  2  FROM T2,(SELECT B FROM T1 WHERE A=1) T1
  3  WHERE T2.B = T1.B(+)
  4  MINUS
  5  SELECT T2.C
  6  FROM T2,(SELECT B FROM T1 WHERE A=1) T1
  7  WHERE T2.B = T1.B(+)
  8  AND T1.B IS NULL
  9  ORDER BY 1;

         C
----------
       201
       202

This does work but this will be a frequently used query.
The T1 table will have approx 2.5M rows with A groups having between 5 to 500
Bs.
The T2 table will have approx 500K rows with C groups having between 1 to 5 Bs.

The real T2 table has 6 more columns and both tables have PK and FK indexes.  I
tried to keep it simple here.

Thanks.

          Followup:

ops$tkyte@ORA920LAP> SELECT T2.C
  2   FROM T2,(SELECT B FROM T1 WHERE A=1) T1
  3   WHERE T2.B = T1.B(+)
  4   group by t2.c
  5   having count(t1.b) = count(*)
  6  /

         C
----------
       201
       202

      Rating:
      3
  October 23, 2003
Reviewer:
      Randall
Nice query,  I never thought about doing the
"HAVING COUNT( T1.B ) = COUNT( * )"

Unfortunately it didn't work well in this case.  Below is with a much larger
dataset.

SQL> SELECT COUNT(1) FROM T1;

  COUNT(1)
----------
   1915200

SQL> SELECT COUNT(1) FROM T2;

  COUNT(1)
----------
    351170

SQL> SET TIMING ON
SQL>
SQL>
SQL> SET AUTOTRACE ON
SQL>
SQL> SELECT T2.C
  2      FROM T2, ( SELECT B
  3                  FROM T1
  4                 WHERE A = 95694 ) T1
  5     WHERE T2.B = T1.B(+)
  6  GROUP BY T2.C
  7    HAVING COUNT( T1.B ) = COUNT( * );

         C
----------
    229273
    229274
    229275
    229276
    229277
    229279
    229282
    229312
    229339
    529273
    529274

         C
----------
    529275
    529276
    529277
    529279
    529282
    529312

17 rows selected.

Elapsed: 00:00:09.96

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2693 Card=2457 Bytes=39312)
   1    0   FILTER
   2    1     SORT (GROUP BY) (Cost=2693 Card=2457 Bytes=39312)
   3    2       HASH JOIN (OUTER) (Cost=629 Card=351170 Bytes=5618720)
   4    3         TABLE ACCESS (FULL) OF 'T2' (Cost=109 Card=351170
Bytes=2809360)
   5    3         INDEX (RANGE SCAN) OF 'PK_T1' (UNIQUE) (Cost=3 Card= 7
Bytes=56)

Statistics
----------------------------------------------------------
       3586  recursive calls
        723  db block gets
       1817  consistent gets
       2991  physical reads
      83008  redo size
       1130  bytes sent via SQL*Net to client
        536  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          1  sorts (disk)
         17  rows processed

--------------------------------------------

I did however play around with this, (with the help of OEM SQL Analyze) and got
this.  It's not near as clean but it does work.

SQL> SELECT T2.C
  2    FROM T2
  3   WHERE T2.B IN( SELECT B
  4                   FROM T1
  5                  WHERE A = 95694 )
  6     AND NOT EXISTS(
  7            SELECT 1
  8              FROM T2 T4
  9             WHERE T4.C IN( SELECT T2.C
 10                             FROM T2
 11                            WHERE T2.B IN( SELECT B
 12                                            FROM T1
 13                                           WHERE A = 95694 ))
 14               AND T4.B NOT IN( SELECT B
 15                                 FROM T1
 16                                WHERE A = 95694 )
 17               AND T2.C = T4.C );

         C
----------
    229277
    229279
    229282
    229339
    529277
    529279
    529282
    229312
    529312
    229276
    529276

         C
----------
    229273
    229274
    229275
    529273
    529274
    529275

17 rows selected.

Elapsed: 00:00:00.69

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=27 Card=10 Bytes=210)
   1    0   NESTED LOOPS (Cost=27 Card=10 Bytes=210)
   2    1     VIEW OF 'VW_NSO_1' (Cost=6 Card=7 Bytes=91)
   3    2       SORT (UNIQUE) (Cost=6 Card=7 Bytes=56)
   4    3         INDEX (RANGE SCAN) OF 'PK_T1' (UNIQUE) (Cost=3 Card=7
Bytes=56)
   5    1     INDEX (RANGE SCAN) OF 'UK_T2' (UNIQUE) (Cost=3 Card=17559
Bytes=140472)
   6    5       NESTED LOOPS (Cost=7 Card=1 Bytes=24)
   7    6         NESTED LOOPS (Cost=6 Card=1 Bytes=16)
   8    7           INDEX (RANGE SCAN) OF 'PK_T2' (UNIQUE) (Cost=3 Card=1
Bytes=8)
   9    8             INDEX (UNIQUE SCAN) OF 'PK_T1' (UNIQUE) (Cost=2 Card=1
Bytes=8)
  10    7           INDEX (RANGE SCAN) OF 'PK_T2' (UNIQUE) (Cost=3 Card=2
Bytes=16)
  11    6         INDEX (UNIQUE SCAN) OF 'PK_T1' (UNIQUE) (Cost=1 Card=7
Bytes=56)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         95  consistent gets
          0  physical reads
          0  redo size
       1130  bytes sent via SQL*Net to client
        536  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         17  rows processed

October 21, 2003 razvan -- Thanks for the question regarding
      "Checkpoint&logswitch -- what is their relationship.", version 7.3.2

      You Asked
Please Tom

can you help me to understand what happens at the log switch
time
and what happens at checkpoint time and how one event can
determine the other one.
Also i am very confuse where SCN is written: in datafile header,
in rolback segment header etc??

thanks a lot
with respect
razvan

      and we said...
A log switch is when we fill up one online redo log and goto the
next.  A log switch will always initiate a CHECKPOINT (it always
starts a checkpoint going).

A log switch implies a checkpoint will start. A checkpoint
implies nothing about a log switch (checkpoints may be started
based on time, number of changed OS blocks, log switches or
operator intervention.  A checkpoint does not cause a log
switch, a log switch causes a checkpoint).

So, that is the relationship between the two.  The reason a log
switch fires a checkpoint is because the system wants to get the
blocks in the buffer cache that are "protected" by the online
redo log we just filled flushed to disk (so we can reuse that
redo log file).  If we did not flush these blocks out -- we
would need to keep that filled online redo log file until we did
flush them out (it is needed to recover those dirty blocks in
the event of a failure).  So, the log switch fires a checkpoint
in anticipation of needing to reuse that redo log file.

SCNs are recorded in the datafile headers after during a
checkpoint.  That lets us know to which point in time a datafile
has consistent data for.  The SCN is like our internal clock.
The SCN is also present in the redo log files.  These SCN's are
used during recovery to see what log files need to be applied to
what datafiles to make them totally consistent.

Review &
      Followup

      Rating:
      4
  March 22, 2001
Reviewer:
      A reader

      Rating: 5
checkpoint
      frequency  August 24, 2003
Reviewer:  Raja
       from chennai, India
In Reducing Checkpoint Frequency to Optimize Runtime Performance section of the
online doc,

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/instreco.htm#428332
setting the following parameters to ZERO, does checkpoint occur only at the log
switch as fast-start checkpointing is disabled? It it like I take the 9i R2
database back to version 8 with regard to checkpointing as it does occur only at
log switch in older version of oracle? Thanks.

LOG_CHECKPOINT_INTERVAL, LOG_CHECKPOINT_TIMEOUT, FAST_START_MTTR_TARGET and
FAST_START_IO_TARGET

      Rating: 3
After log
      switch why I still need manually checkpoint?  October 21,
      2003
Reviewer:  frank  from canada
Tom,

Here is a testing I did:
SQL> select group#, sequence#, archived, status from v$log;

    GROUP#  SEQUENCE# ARC STATUS
---------- ---------- --- ----------------
         1         53 NO  CURRENT
         2         51 NO  INACTIVE
         3         52 NO  INACTIVE

SQL> alter system switch logfile;

System altered.

SQL> select group#, sequence#, archived, status from v$log;

    GROUP#  SEQUENCE# ARC STATUS
---------- ---------- --- ----------------
         1         53 NO  ACTIVE
         2         54 NO  CURRENT
         3         52 NO  INACTIVE

SQL> alter system checkpoint;

System altered.

SQL> select group#, sequence#, archived, status from v$log;

    GROUP#  SEQUENCE# ARC STATUS
---------- ---------- --- ----------------
         1         53 NO  INACTIVE
         2         54 NO  CURRENT
         3         52 NO  INACTIVE

SQL>

=================================

From this test, I didn't see that the logswitch started the archieve process but
the manual checkpoint did. If a checkpoint triggers the archieving process, and
a logswtch triggers a checkpoint, then a logswitch should result in a log
archive. What did I miss here?

Thanks.

          Followup:

?  how do you think you saw that?

are you in archivelog mode
is automatic archiving enabled

a checkpoint has nothing but nothing to do with archiving redo.  not sure what
you are thinking you are seeing here?

all i see is that after you switched, you made group 1 active (needed for
instance recovery -- it is still protecting blocks in the cache) and group 2
current.  If you would have waited, eventually group 1 would become inactive all
by itself (log switch will initiate a checkpoint, a really lazy, slow in the
background one)

by demanding "system checkpoint RIGHT NOW" you made the redo group inactive --
no longer needed for instance recovery because all of the blocks it was
protecting are on disk now and so it's not needed.

      Rating:
      3
Thanks.  October 24, 2003
Reviewer:
      A reader
Didn't know there was a slow checkpoint and faster checkpoint. Thanks.

October 21, 2003 Meyer -- Thanks for the question regarding
      "Execute procedures concurently in a procedure", version 8.1.6

      You Asked
Tom,

I have a low impact procedure that takes a long time (collects data from remote
DB). Now I have one that runs in a loop (Each remote DB). I want to create a
procedure that calls 5 procedures and runs them all at the same time (not wait
for the previous one to finish). (At the end each called procedure will update a
status table and check to see if it is the last procedure to finish then update
the status table).

Is this possible using pl/sql (if not I can use the OS to execute all 5 at the
same time and get this done...but want to know if you know a better way or have
suggestions). If possible please include syntax.

My goal is this: (quasi-pl/sql below).

---
create or replace procedure calling_procedure

is begin

procedure_1; --this starts and executes next procedure w/out waiting

procedure_2;  --this starts and executes next procedure w/out waitng

procedure_3;  --this starts and executes next procedure w/out waiting

procedure_4; --this starts and executes next procedure w/out waiting

procedure_5; --

end;
---

Also...can I have the source for the spell check function on this site? I would
like to add this to a pl/sql web application I have already created.

Thank You,
Meyer

      and we said...
If you have job_queue_processes set to 5, you can use dbms_job to do this.

You would:

  dbms_job.submit( l_job, 'procedure_1;' );
  dbms_job.submit( l_job, 'procedure_2;' );
  dbms_job.submit( l_job, 'procedure_3;' );
  dbms_job.submit( l_job, 'procedure_4;' );
  dbms_job.submit( l_job, 'procedure_5;' );
  commit;

They will then run in the background.

A search for

spell check

on this site found:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:854500633682

Review &
      Followup

      Rating: 5
Thank
      You  July 20, 2002
Reviewer:  A reader

Very Helpfull!

      Rating: 4
very
      useful  November 25, 2002
Reviewer:  Ariel
      Cary  from Santa Cruz, Bolivia
Tom,

Very interesting way to implement paralellism.

And now, the next step would be to think of a way to get these processes in
sync... that is how to be aware of which processes have finished and which ones
haven't yet. (suppose all five processes have carried out a piece of work of a
big task, and then you need to get all the small result together).
What would be an effective way to get this?

Appreciate your comments.

          Followup:

dbms_job.submit( l_job,
'procedure_1; dbms_alert.signal(...); commit;' );

then, the query coordinator, if you will , waits for the alerts to come in.

That would be one method.

      Rating:
      4
dbms_lock.request  November 26, 2002

Reviewer:  Anthony  from Philippines
We have almost the same requirement as posted by Ariel. We implement
dbms_lock.request and dbms_lock.release. Before we submit the job we issue
dbms_lock.request to lock that process and issue dbms_lock.release if the
process is successful and if the process encountered an error (of course theres
is a handling, I mean, if this process is a pre-requisite of another process it
will not submit the next process ). My question is, is this approach the correct
one or we should use dbms_alert instead? Will it consume CPU while waiting for
the previous process to finish??

TNX..

          Followup:

how do you know to release the lock?

dbms_alert would be useful for the background processes to alert the foreground
parent process "I am done"

I'm not sure how you are using dbms lock to do the same

      Rating: 5
Using
      DBMS_LOCK  November 26, 2002
Reviewer:  Anthony
      V  from Philippines
It goes like this:
  We have a package called batch_process. It has batch coordinator procedure,
lock_process function that locks the process sequence number, then the
job_submitter.

First, we loop to a table that holds all the process together with process
sequence number (inside batch_coordinator), we then call the lock_process to try
and lock that old process_seq_no (lockid->process_seq_no, timeout->0) and return
if we successfully obtain the lock otherwise if the old process is still
executing we wait for it (timeout->maxwait), if the batch_coordinator detects
that it is safe to submit this job, it calls the job_submitter. inside the
job_submitter it has dbms_lock.release(process_Seq_no).
dbms_lock.release(process_Seq_no) is issued if we successfully executed the
procedure or an error was encountered. then we save that current process_seq_no
to a variable to know what was the last process executed.

we already tested this and it is currently running. My question is, is this
approach the correct
way or we should use dbms_alert instead? Will it consume CPU while waiting for
the previous process to finish when using
dbms_lock.req(timeout->dbms_lock.maxwait)??

Thanks for your comments.

          Followup:

you are doing it backwards from the way they are -- your approach is sound for
what you are doing.

They want to run N jobs, wait for them to finish and then continue on.  For them
-- getting ALERTED that the job is done is appropriate.

You -- you want to see if the job is already running.  In which case, lock is
appropriate.

As far as I can tell, this is apples and oranges -- totally different.

      Rating: 5
Using
      dbms_lock  November 26, 2002
Reviewer:  Anthony
      V  from Philippines
So you're saying that we can use this as long as it served our purpose. We dont
have to worry about the resources that it will consume while waiting for the
process to finish...

Tnx again..

          Followup:

when waiting on dbms_lock to return -- you aren't consuming resources like cpu -
you are blocked.

      Rating: 3
And what if
      this processing was local to the DB?  November 26, 2002

Reviewer:  Matt  from Australia
As stated earlier, an interesting way to implement parallelism.

So what if I have a data manipulation process (select data, carry out
calculation on returned data and insert the result) that takes a long time to
run. Would I be better to segment the data that is being processed (in the
initial select) and parallelise this process using the dbms_job approach (and
therefore parallelise the entire process), or use parallel query (to parallelise
the select)?

My guess at the moment is that it is the select that is taking up the most of
the time for this process to run....I think I can hear your answer already.

          Followup:

It totally depends

Does the select take forever but the processing is very very very fast.
Parallel query to speed up the select.

Does the select go ok but the processing is very compute intensive and somewhat
slow.  Parallelize YOUR operations then, divide and conquer with your code.

Hope that was the answer you were anticipating ;)

      Rating: 5
Execute same
      procedure repeatedly  February 12, 2003
Reviewer:
      yuga chodagam  from LA
Tom,
     What if I want to execute same procedure concurrently. Is there a way to do
that. For example. I have a procedure that processes school information. It
takes one school at a time does some processing and then does some aggregates
for each school in a different procedure. This aggregate procedure I want to
call concurrently....Here goes some pseudo code..

procedure process_school as

begin
   for rs in (select schools )loop
    do something with <school 1>;
    ...................
    ...................
    school_aggregate(school1);
   end loop;

exception
        When no_data_found then
         Null;

end;

When I call school_aggregate(school1), I do not want to wait until it finishes
but process next school(ex:school2) in the loop and come and fire
school_aggregate(school2)for school2 (May be the school_aggregate(school1)is
still running) and on and on. Is it possible? If not through PL/SQL is it
possible in Java using threads?

Thanks for all your help Tom
Yuga.

          Followup:

you can accomplish that to a degree using dbms_job as shown above.

      Rating: 4
Running
      procedures in parallel  September 16, 2003

Reviewer:  Mohan  from Bangalore
In a procedure I am submiting 8 jobs to run in parallel in 4 CPU machine. All
the 8 threads run the same procedure with different data. At the end I have to
merge all the results. The end user will get a prompt only after last operation
is complete. The questions I have are

1. I have to monitor the progress of each thread from the main procedure. If I
monitor in a loop then the main procedure also will occupy lots of CPU time. Is
there any function called "sleep" in Oracle like in UNIX. If I write a sleep
routine as external procedure in C will it work. Can the same thing be done in
Windows NT/2K environment.

2. If the procedure is executed by another user, in which user's
session/privileges the job submited by dbms_job will run.

3. How can I see the error messages produced by the procedure where submitted
through dbms_job

Mohan

          Followup:

1) dbms_lock.sleep

2) if the job is a definers rights stored procedure, the procedure will run as
the definer -- as normal.

if the job is an anonymous plsql block of code, the job will run as the
submitter of the job.

3) as they are stored in the alert log, you would have to DIY -- eg, instead of
submitting:

   dbms_job.submit( l_job, 'p;' );

submit:

   dbms_job.submit( l_job, 'begin p; exception when others then insert ( a log
message into a table with the job id); commit; RAISE; end;' );

      Rating: 5
Query
      coordinator.  October 21, 2003
Reviewer:
      A reader
Tom:
I have a question about your suggestion..

"
dbms_job.submit( l_job,
'procedure_1; dbms_alert.signal(...); commit;' );

then, the query coordinator, if you will , waits for the alerts to come in.

That would be one method.
"

Is this the correct way to do this?

DBMS_ALERT.REGISTER('P1');
DBMS_ALERT.REGISTER('P2');
DBMS_ALERT.REGISTER('P3');
DBMS_ALERT.REGISTER('P4');
DBMS_ALERT.REGISTER('P5');
...
DBMS_JOB.SUBMIT(l_job, 'P1; DBMS_ALERT.SIGNAL(''P1'',''msg1''); COMMIT;');
DBMS_JOB.SUBMIT(l_job, 'P2; DBMS_ALERT.SIGNAL(''P2'',''msg2''); COMMIT;');
DBMS_JOB.SUBMIT(l_job, 'P3; DBMS_ALERT.SIGNAL(''P3'',''msg3''); COMMIT;');
DBMS_JOB.SUBMIT(l_job, 'P4; DBMS_ALERT.SIGNAL(''P4'',''msg4''); COMMIT;');
DBMS_JOB.SUBMIT(l_job, 'P5; DBMS_ALERT.SIGNAL(''P5'',''msg5''); COMMIT;');
...
...
DBMS_ALERT.WAITONE('P1',msg1, status1);
DBMS_ALERT.WAITONE('P2',msg2, status2);
DBMS_ALERT.WAITONE('P3',msg3, status3);
DBMS_ALERT.WAITONE('P4',msg4, status4);
DBMS_ALERT.WAITONE('P5',msg5, status5);

if status1=0 and status2=0 and status3=0 and
status4=0 and status5=0
then do whatever that needs to be done to consolidate results from procedures
P1,P2,P3,P4 and P5;

Tom, am I in the right path here?

Thanks.

          Followup:

yes, you cold waitany as well and just collect the messages from whomever
finished first.

      Rating: 5
Creating
      Indexes in Parallel  October 21, 2003
Reviewer:
      Doug Wingate  from New Orleans, LA, USA
I know it's possible that the alternatives I'll list would just have to be
tested and compared for their relative performance, but I wonder if you have any
general comment.  A process creates a table, loads it, creates numerous bitmap
indexes on it, and then partition-exchanges the table into a partitioned table.
The bitmap indexes are presently created one at a time with parallel DDL.  In
principle, one could create multiple indexes "in parallel" using the DBMS_Job
design discussed in this thread.  Is there conceivably any benefit in
introducing the parallel DBMS_Job design either in place of parallel DDL or in
addition to it?  Can any general observation be made at all or does one just
need to try various possibilities and compare them?  Thanks for your comments.

          Followup:

well, there could be some contention introduced by creating many indexes on the
same segment at the same time -- as opposed to just increasing the degree of
parallelism on the existing creates.

but -- yes -- benchmarking is the way to go.

      Rating:
      5
  October 22, 2003
Reviewer:
      A reader

October 21, 2003 Jessica -- Thanks for the question regarding
      "convert database server sysdate to GMT date and daylight saving aware",
      version 9.0.1

      You Asked
We need to code a function to convert database server sysdate to GMT date. So we
use: alter database set time_zone='EST' to set dbtimezone, and then code
something like:
CREATE OR REPLACE FUNCTION FN_GET_GMT_DATE RETURN DATE
IS

   v_dbtimezone varchar2(10);

BEGIN
   select dbtimezone into v_dbtimezone from dual;

   RETURN NEW_TIME(SYSDATE, ltrim(rtrim(v_dbtimezone)), 'GMT');

END FN_GET_GMT_DATE;
/

But this is not daylight saving awarance. What's the best way to convert
database sysdate to GMT date and also daylight saving aware?

      and we said...
Here is one approach (there are probably others)

ops$tkyte@ORA9I.WORLD> select sysdate,
  2
sysdate+(substr(tz_offset(dbtimezone),1,1)||'1')*to_dsinterval('0
'||substr(tz_offset( DBTIMEZONE ),2, 5)||':00')
  3    from dual
  4  /

SYSDATE              SYSDATE+(SUBSTR(TZ_O
-------------------- --------------------
22-apr-2002 09:59:26 22-apr-2002 05:59:26

Review &
      Followup

      Rating: 3
I got
      ORA-01867: the interval is invalid when run the sql  April
      22, 2002
Reviewer:  Jessica  from USA
When I run the sql you provide, I got ORA-01867: the interval is invalid. Could
you tell me what's wrong?

          Followup:

select out the bits and pieces to see what modifications we might have to
accomidate, something like this:

ops$tkyte@ORA9I.WORLD> select tz_offset(dbtimezone) from dual;

TZ_OFFS
-------
-04:00

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> select '0 ' || substr(tz_offset(dbtimezone), 2, 5 ) ||
':00' from dual;

'0'||SUBST
----------
0 04:00:00

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> select to_dsinterval( '0 ' ||
substr(tz_offset(dbtimezone), 2, 5 ) || ':00' ) from dual;

TO_DSINTERVAL('0'||SUBSTR(TZ_OFFSET(DBTIMEZONE),2,5)||':00')
---------------------------------------------------------------------------
+000000000 04:00:00.000000000

ops$tkyte@ORA9I.WORLD> select
(substr(tz_offset(dbtimezone),1,1)||'1')*to_dsinterval( '0 ' ||
substr(tz_offset(dbtimezone), 2, 5 ) || ':00' ) from dual;

(SUBSTR(TZ_OFFSET(DBTIMEZONE),1,1)||'1')*TO_DSINTERVAL('0'||SUBSTR(TZ_OFFSE
---------------------------------------------------------------------------
-000000000 04:00:00.000000000

Thats how i pieced it together in the first place....

      Rating:
      4
  April 22, 2002
Reviewer:
      A reader
Is there a way to accomplish this in Oracle 8i.
tz_offset appears to be 9i function

          Followup:

Not that I am aware of.  All of the timezone functionality is new with 9i.

      Rating: 4
Hope this
      assists -- a solution for 8i  April 22, 2002

Reviewer:  Tom Wurzbach  from Gainesville, FL

We needed to do this with 8i -- what we did was place the server in GMT time and
create a table:

create table timechange_tab(timezone VARCHAR2(10), gmt_from DATE, gmt_thru DATE,
tzoffset NUMBER,
tzabbv VARCHAR2(3));
  timezone = key for your timezones, Eastern, Central, Zulu, whatever...
  gmt_from = the time, at GMT, at which the tz of interest begins in your
timezone
  gmt_thru = the time, at GMT, at which the tz of interest ends in your timezone
  tzoffset = the time to add to GMT to get the localtime in your timezone
  tzabbv = the timezone abbreviation EST, EDT, CDT, etc.

We loaded the table with the times (in GMT) that the times changed for each
timezone, e.g.
...
INSERT INTO timechange_tab VALUES('Eastern', '01-APR-2002 07:00:01',
'27-OCT-2001 06:00:00', -4/24, 'EST');
INSERT INTO timechange_tab VALUES('Eastern', '27-OCT-2001 06:00:01',
'07-APR-2002 07:00:00', -5/25, 'EDT');
...

This allows you to select the current timezone offset, timezone abbreviation and
current local time as such:
SELECT tzoffset, tzabbv, SYSDATE+tzoffset localtime
FROM timechange_tab
WHERE timezone='Eastern' AND (SYSDATE BETWEEN gmt_from AND gmt_thru);

We loaded our table with all US timezones and data through 2025--hopefully, I'll
be at a different job before I need to add more dates to this table.  I'm not at
work, so check my numbers for tz start/stop times.

Hope this helps.

      Rating: 4
What about for
      existing applications eg: Workflow  October 20, 2003

Reviewer:  Matt  from Australia
OK, so I can translate SYSDATE to my timezone and take account of daylight
savings using your approach:

select
sysdate,sysdate+(substr(tz_offset(dbtimezone),1,1||'1')*to_dsinterval('0'||substr
(tz_offset( DBTIMEZONE ),2,5)||':00')
from dual
/

What if I don't have access to the source? For instance, I am using an
application that uses Oracle Workflow, which uses SYSDATE, where we need to use
"SYSDATE accounted for timezone and daylight savings".

Is this possible?

Basically, we have one machine, one instance, many application schemas, two
timezones. We will have two instances of Oracle Workflow. The workflow code will
need to use SYSDATE to set a date that accounts fot TZ and DST.

Any suggestions are gratefully received.

          Followup:

I'm not following you here -- what are you trying to do?  why does workflow need
to use that?

      Rating: 3
More
      information...  October 21, 2003
Reviewer:
      Matt  from Australia
Here is the situation. System that uses work flow in TZ1, the system processes
data from two seperate timezones TZ2 and TZ3. This data arrives with time based
data in the local TZ.

Workflow manages the processing of the data.

My question is:

Can the workflow be run in the timezone that different from the OS timezone?

I need to operate on date data as if it were either TZ2 or TZ3, depending on
where the data came from.

The workflow background engine seems to use SYSDATE, which was why I raised the
question here.

Thanks and Regards,

          Followup:

i would normalize all inputs to GMT -> local time.  then there is no problem.
that is, the guy who inputs the workflow -- puts it in in the local time of the
DATABASE -- not the client.  no worries then.

October 21, 2003 khalid -- Thanks for the question regarding
      "Column order in Index", version 8.1.7

      You Asked
Hi Tom.
If I have a table called Test contains 5 columns a, b, c, d and e.
1. What is the deference between index_1, and index_2 if they are defined as:-
  Index_1.
    Create index_1 on test (a, b);
  Index_2.
    Create index_2 on test (b, a).
  Is the order in columns making difference? What is the deference and how it
effect.

2. Is it reasonable to have index defined on columns a, b and c and another one
  defined on column c and d. What I mean is column c exist in tow index.
  Should I redefine the tow indexes to one index defined in a, b and c and
another one defined on column d only or should I create one index defined on
columns a, b, c and d.

Thank.

      and we said...
Do you have my book "Expert one on one Oracle"?  If so, the chapter on indexes
goes into some detail on this topic.

The order of the columns can make a difference.  index_1 would be most useful if
you ask queries such as:

select * from t where a = :a and b = :b;
select * from t where a = :a;

Index_2 would be most useful if you ask queries such as:

select * from t where a = :a and b = :b;
select * from t where b = :b;

(in 9i, there is a new "index skip scan" -- search for that there to read about
that.  It makes the index (a,b) OR (b,a) useful in both of the above cases
sometimes!)

So, the order of columns in your index depends on HOW YOUR QUERIES are written.
You want to be able to use the index for as many queries as you can (so as to
cut down on the over all number of indexes you have) -- that will drive the
order of the columns.  Nothing else (selectivity of a or b does not count at
all)

2) yes, it is not only reasonable but very very common to have indexes on

(a,b,c)
(c,d)

It would not suffice to add D to the end of A,B,C -- if what you needed was an
index on C,D to answer one set of questions and A,B,C for another.

Review &
      Followup

      Rating:
      3
Thanks  September 28, 2002
Reviewer:
      khalid.  from Yemen
Thank you very much.

      Rating:
      5
  November 18, 2002
Reviewer:
      A reader
Hi Tom,

 Is the ordering of index cloumns makes any differnce in performance?

 Like I have index_1(a,b,c)
     index_2(b,a,c) and index_3 (c,a,b), What is the
difference between these?

          Followup:

If you have my book "Expert one on one Oracle" -- i dive into all of the details
on this and describe how you would set about deciding on how to order the
columns -- it will affect

o the reusability of that index, some people might create more indexes then they
actually need -- if they had reorded some index.

o the compressability of that index

for a query that references all of A, B, C the above three indexes will perform
more or less the same -- regardless of the selectivity of A, B, or C.  Benchmark
in the book ;)

      Rating: 4
One More
      Doubt  November 19, 2002
Reviewer:  Tony

I have created an index_1(a,b). And I have the following queries.

1. select * from x
   where a = 1
   and b = 2;

2. select * from x
   where b = 2
   and a = 1;

3. select * from x
   where a = 1
   and  c = 3
   and b =2;

4. select * from x
   where b = 2
   and c = 3
   and a = 1;

5. select * from x
   where b = 2;

Is the index_1 used by all the above quries?
Is there any difference in index usage by these quries?
If yes/no, How?

Kindly explain in details. Your answer will be very helpful for the whole
developers community. Thanks a lot in advance.

          Followup:

in addition to what Connor said (if you have my book "Expert one on one Oracle"
i actually go into this in lots of detail in the chapter on indexes) - the short
answer to your query is

ALL of the queries could use that index in multiple ways.

1) probably index range scan + table access by rowid (if X has more columns then
A and B)

2) same as #1
3) same as #1
4) same as #1
5) depends on version.  version 9i and up it might index skip scan:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1156161921465

      Rating: 5
For
      Tony...  November 19, 2002
Reviewer:  Connor
      McDonald  from UK
There is a lot more to it than just the index column order and the query
presented to the optimizer.  Things that influence the (cost) optimizer will
include:

- the number of distinct values in each column
- the number of distinct values in the indexes
- the clustering factor of the index

hth
connor

      Rating:
      5
Selectivity  November 19, 2002
Reviewer:
      Alexander  from Lithuania
Hello Tom,

  Can you explain this better:

>for a query that references all of A, B, C the above three indexes will perform
more or less the same -- regardless of the selectivity of A, B, or C.  Benchmark
in the book ;)

  Does it mean than queries:

a) select /*+ index (x index_abc) */ * from x
   where a = 1
   and  c  between 1 and 7
   and b > 2

b) select /*+ index (x index_cba) */ * from x
   where a = 1
   and  c  between 1 and 7
   and b > 2

will have the same performance regardless of the selictivity of A, B and C
fields?

Best regards.

          Followup:

yes and no.  it'll depend -- but in general, for most cases, they would be the
same.

In your case -- if the following was true:

there is
one row for b such that b > 2 (and hence only one index entry)
and c was equal to 2 for that row
and a = 1

and at the same time there is
1,000,000 rows such that a=1
and c was equal to 2 for all of those rows
and one of those rows had b > 2

an index on B,C,A or B,A,C would probably be better then one on A,C,B as the
index on B,C,A would be able to stop "faster"

(but did that REALLY have to do with the selectivity of B over C??? NO cause
this is also true:

there are 5 million other rows with b = 1
each of those 5 million rows have unique values for A
each of those 5 million rows have unique values for C (and all of them could be
between 1 and 7 still

So here -- B is not selective at all but A and C are.

      Rating: 4
maximum key
      length exceeded  November 21, 2002
Reviewer:
      A reader
I am getting the following error while trying to create an index on column
varchar2(1000)
On Oracle 8.1.5  and Windows/2000
----
ERROR at line 1:
ORA-01450: maximum key length (758) exceeded
----
Oracle documentation saying the maximum value is operating system-specific.
Could you please explain me what is meant  and how to rectify it.

This column is using in WHERE condition with LIKE operator.
Is this feasible providing the index on large column like this if not can you
explain it ?

Thanks

          Followup:

It is based on the block size -- you get about 40% of the block size for the max
size of an index key:

ops$tkyte@ORA920.US.ORACLE.COM> select 2048*.4 from dual;

   2048*.4
----------
     819.2

you have a 2k block size -- hence, the largest index key entry will in fact be
758bytes -- you have a column that could blow that out easily.

For something this large -- I doubt your like clauses are all in the form:

where column like 'something something something %'

if the like can start with %, then the index is worthless anyway ... so -- i
recommand interMedia text and a text index on a field this big, regardless of
the block size.

See

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/inter.817/index.htm
for details.

      Rating:
      4
  November 21, 2002
Reviewer:
      Balaji  from India
Hi Tom
Do u then mean to say that if there are 3 columns a b and c then if all the 3
columns are used in the query , then the ordering of columns in the index does
not matter?
Ie selectivity does not come in to picture??

Then when does selectivity come in to picture??

Also , when two columns say a and b are used in one set of queries , and say b
and c are used in other set of queries , then should i create 2 indexes on (a,b)
and (b , c) or will one ondex give better performance for all the queires.
I heard that in 9i , the concept of leading column is eliminated.Please
elaborate on this.

          Followup:

sigh,

it is not the first thing, it is not the most important thing.  sure, it is
something to consider but it is relatively far down there in the grand scheme of
things.

if i have a,b,c and I find I query lots of times like this:

where a = :a and b = :b  and c = :c
where c = :c
where b = :b and c = :c

I would prefer a single index (c,b,a) over any other combination -- it can
easily be used for all of the above queries.

In certain strange, very perculiar and abnormal cases (like the above with
really utterly skewed data), the selectivity could easily matter HOWEVER, they
are

a) pretty rare
b) truly dependent on the values used at runtime, as all skewed queries are

so in general, look at the questions you have, try to minimize the indexes you
need based on that.  Look at the predicates in general, factor out what you
where on and use that as a starting point.

      Rating: 4
Would this be
      the case  May 30, 2003
Reviewer:  Vipin
       from New york
Hi tom,

would you go with creating index over (c,b,a) even if the distinct values in
columns C is just 2 say Y and N. In this case I think C must always be
considered(if needed to be considered) in an Index at last position.

Please explain.

          Followup:

you would be mistaken.

Suppose you have

c char(1)  = 'Y', 'N'
b number   = 1,2,3,4,5
a date     = 1 million distinct values

and you query

where c = 'Y' and b = 5 and a > sysdate

will, I would presonally want that it be C,B,A or B,C,A -- cause we scan on A.
It would be most efficient to goto the place in the index where C=Y, B=5, A >
sysdate and simply scan from there (we are interested in all of those values
until either C or B changes)

If A were first, we would read lots of C=N values or B!= 5 values while scanning
A>sysdate.

The number of distinct values in a column in a concatenated index is not
relevant when considering the position in the index.

HOW YOU USE the index -- that is what is relevant when deciding.

      Rating: 4
why the limit
      is imposed on indexes?  June 10, 2003
Reviewer:
      Tony  from India
Tom,
 Why this restriction "ora-01450 maximum key length (758) exceeded" is imposed
on indexes?. Can't index data be stored in multiple blocks? Really, I don't get
need for this restriction.

          Followup:

no, index data cannot be stored on multiple blocks (starting to defeat the
purpose of an index isn't it?  indexes = small, fast way to get to data by key)

The index key length is a function of your blocksize -- about 40% of the size of
the block.  you have a 2k block, small key length.

      Rating: 4
The word "index
      skip scan" is misleading  June 10, 2003
Reviewer:
      Tony  from India
Tom, The word "index skip scan" is misleading in oracle9i.
Is the index skipped, i mean unused, if this feature is used? Can you explain
please?

          Followup:

No, I don't aggree.

It is a SCAN that SKIPS around in the index structure.  It is not an "index
SKIPPING scan", it is an index skip scan.

the index is not skipped.

the index structure is skipped around in.  see the link above in one of the
other reviews, it has an example.

      Rating: 4
Is it possible
      to change index size limit (40% )?  June 12, 2003

Reviewer:  Tony  from India
"Index key length is a function of your blocksize -- about 40% of the size of
the block."

    Why 40%?. Why can't be 60% or 70%? Is it possible to change the 40% limit?

          Followup:

because we wanted at least 2 keys per block.

Look, if you are getting 1 key per leaf block, you don't deserve an index here,
indexes need MANY rows per leaf block to be efficient.

      Rating: 4
Full
      Scan.  June 12, 2003
Reviewer:  Kashif
       from Houston, TX
Hi Tom,

The definition of a full scan of an index in the Performance Guide is as
follows:

<quote>
A full scan is available if a predicate references one of the columns in the
index. The predicate does not need to be an index driver.
</quote>

What I understand of that is that if a column referenced in the predicate of a
query is part of an index, then irrespective of what its position in the index
is, it can potentially use that index, in a full index scan. Does that sound
about right, and if so, then does the order of columns matter as much?

Secondly, I tried verifying this but for some reason, unless the leading column
of the index is used, the index is not used and instead an FTS is performed.
What exactly does this full scan do?

TIA.

Kashif

          Followup:

yes, it could use the index in a full scan or even better a FAST full scan (read
out of order using multi-block io)

regardless of the position in the index.

it will generally use this when the INDEX itself can be used INSTEAD of the
table:

ops$tkyte@ORA920> create table t as select * from all_objects;

Table created.

ops$tkyte@ORA920> create index t_idx on t(object_name,object_id);

Index created.

ops$tkyte@ORA920> analyze table t
  2  compute statistics
  3  for table for all indexes for all indexed columns;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select object_name from t where object_id = 55;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=18 Card=1 Bytes=27)
   1    0   INDEX (FAST FULL SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=18 Card=1
Bytes=27)

      Rating: 5
Would you go
      for an additional index...  June 12, 2003

Reviewer:  Kamal Kishore  from New Jersey, USA

Hi Tom,
Would you go for an additional index on OBJECT_ID and avoid the FAST FULL SCAN?
Will it be more efficient to have a separate index on OBJECT_ID rather than the
FFS?

When would you choose one over the other?
Thanks,

          Followup:

it depends on my needs.  In the example above, my only goal was to demonstrate
that yes, the index would be, could be used.

If that was a one off query -- I would be quite pleased that the optimizer did
that for me.

If that was a query I executed 500 times a second, I would consider alternative
indexing techniques.

      Rating: 5
Index FFS vs
      Range Scan  June 13, 2003
Reviewer:  Jerry
       from Washington, DC USA
I have found a case where column order on an index does seem to make a
difference in LIO and performance, given the same query.  The same query
generates much more LIO using an FFS versus a range scan.  The only thing that
has changed is the index column order.

Paycheck_status and pay_end_dt are not very selective.

SQL> create index psrpay_check on ps_pay_check
  2    ( paycheck_status,
  3      pay_end_dt,
  4      emplid,
  5      company
  6    ) tablespace psindex storage (initial 10485760 next 10485760 maxextents
unlimited pctincrease 0) nologging pctfree 10;

Index created.

SQL>
SQL> select
  2   a.pay_end_dt
  3  from ps_pay_check a
  4  where a.emplid='xxxyyy'
  5   and a.company='CB1'
  6   and a.pay_end_dt<='30-apr-2003'
  7   and a.paycheck_status='V';

no rows selected

SQL>
SQL> set autotrace on stat exp
SQL> set timing on
SQL> /

no rows selected

Elapsed: 00:00:01.05

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=34 Bytes=714)
   1    0   INDEX (FAST FULL SCAN) OF 'PSRPAY_CHECK' (NON-UNIQUE) (Cost=2
Card=34 Bytes=714)

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

SQL>
SQL> create index psrpay_check on ps_pay_check
  2    (
  3      emplid,
  4      company,
  5      pay_end_dt,
  6      paycheck_status
  7    ) tablespace psindex storage (initial 10485760 next 10485760 maxextents
unlimited pctincrease 0) nologging pctfree 10;

Index created.

SQL>
SQL> select
  2   a.pay_end_dt
  3  from ps_pay_check a
  4  where a.emplid='xxxyyy'
  5   and a.company='CB1'
  6   and a.pay_end_dt<='30-apr-2003'
  7   and a.paycheck_status='V';

no rows selected

SQL>
SQL> set autotrace on stat exp
SQL> set timing on
SQL> /

no rows selected

Elapsed: 00:00:00.07

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=34 Bytes=714)
   1    0   INDEX (RANGE SCAN) OF 'PSRPAY_CHECK' (NON-UNIQUE) (Cost=2 Card=34
Bytes=714)

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

      Rating:
      4
  June 14, 2003
Reviewer:  peter

I have two questions:

1)If I have index_1(col_a, col_b, col_c), will the colunmn order affect the
performance?

e.g. select *
       from table
     where col_a = 1
       and col_b = 2
       and col_c = 3
and
     select *
       from table
     where col_b = 2
       and col_c = 3
       and col_a = 1
and
     select *
       from table
     where col_c = 3
       and col_b = 2
       and col_a = 1

Are there any difference?

2)If I have two tables.  One has millions of rows and other one has thousands of
rows.  Does the order of tables affect the performance?

  table1: big table, table: small table

  select *
    from table1,
         table2
    where table1.col_a = table2.col_a
      and table1.col_b = table2.col_b

  select *
    from table2,
         table1
    where table1.col_a = table2.col_a
      and table1.col_b = table2.col_b

Thanks!

          Followup:

in the exact equalities, no.

in things with ranges (between, <, >) it can, yes.

if you query "where a = 5 and b = 6 and c > 0"

and you have twenty rows with a=5, b=6  (5 of which are c>0)  the index on

(a,b,c)

will process 5 rows in the index leafs (it'll find a=5,b=6, c=0 and scan from
there)

on the other hand, if you have 1,000,000 rows where c>0 (same set of data) and
lots of rows where a=5, you'll process lots more leaf rows looking for the 5
that have b=6.  It'll scan more data.  It has to look at entries like:

0,5,....
1,5,....
2,5,.....
and so on.

instead of just being able to zero in on

5,6,....

in the index.

2) not unless you are using the archaic RBO.

      Rating: 5
Golden rule for
      ordering columns on indexes  September 03, 2003

Reviewer:  juancarlosreyesp@yahoo.com  from Bolivia

Tom I had been investigating about ordering columns in indexes, and this is my
opinion, if you can please get a time (and patiente) to check them.

I think the best rule is,
1. "Create indexes with all the combinations of columns, and test which is
better FOR EVERY SPECIFIC QUERY"
( don't forget to calculate statistics for that indexes before testing )
2. About factor clustering, in the practice the real test is going to be the
query speed and gets.
To know things like this (from documentation)
"
clustering factor.
Indicates the amount of order of the rows in the table based on the
values of the index.
n If the value is near the number of blocks, then the table is very well
ordered. In this case, the index entries in a single leaf block tend to
point to rows in the same data blocks.
n If the value is near the number of rows, then the table is very
randomly ordered. In this case, it is unlikely that index entries in
the same leaf block point to rows in the same data blocks.
The cost of fetching rows using rowids depends on the
index clustering factor.
Oracle does I/O by blocks. Therefore, the optimizer?s decision to use full table
scans
is influenced by the percentage of blocks accessed, not rows. This is called the
index
clustering factor. If blocks contain single rows, then rows accessed and blocks
accessed are the same.
However, most tables have multiple rows in each block. Consequently, the desired
number of rows could be clustered together in a few blocks, or they could be
spread
out over a larger number of blocks.
Consider a case where each block has 100 rows, but on an average only 5 rows for
each block meet the query condition. A query accessing 2% of the rows might need
40% of the blocks.
"
Is interesting but in definitive the clustering is not what determines if an
index is going to be useful or not.
3. This is a question.
If I export and import a table, and the physical order of the rows change
completely, the indexes used to be the best will still be the best, even when in
some situation a full scan will be better than using the index??

Thanks

          Followup:

#1 seems wrong, you should be able to very much reduce the set of necessary
candidate indexes without trying each and every permutation.  you know how you
query, you should be able to eliminate many

#2 you have wrong, the cluster factor is very much what determines if an index
will be useful or not -- the CBO takes it into serious consideration.

#3 not sure what you are asking there -- but the cbo will look at the statistics
and decide whether an index is correct or not.

      Rating: 5
:) Thank you
      Tom  September 04, 2003
Reviewer:
      A reader

      Rating: 5
:) Fixed
      goldedn rules ot determine column ordre in bt
      index  September 04, 2003
Reviewer:
      juancarlosreyesp@yahoo.com  from Bolivia
Hi Tom I fixed the rules, so claiming to your patiente I ask you again, after
some test.

Rules for defining column order B-TREE indexes
1. Normally, you must create indexes on the columns you query or sort.
2. In an equality,  The columns with less unique values must be to the left, the
 columns with more unique values to the right
3. In a like comparison, the columns that has the like comparison should be at
the left first.
3. The clustering factor is what determines if and index is or not used by the
CBO,
   question1:this means it chooses between the index with the column needed and
a full scan?, based on the test I did it seems to be so. Or there is a fixed
criteria.

If I recreate the table and reinsert the record in a specific order
for example create table a as select * from b order by col1,col2;
the cluster in the indexes changed complete from 20148 to 1041, but the same
index was
used
QUESTION:This means that if I query a table in a specific order, specially a
table that
is almost static, is advisable to recreate it in the order I query?? or maybe
only if I do range scans for example between 3 and 10

Thank you

          Followup:

1 -- i would agree, although unique/primary keys and fkeys are another reason

2 -- no, the order of columns in an index is based on how you use them, not on
their cardinality.

3 -- (first #3).  no, see #2.  that generalization is not always true.  in fact,
if you

   where x = 5 and y like 'abc%'

i'd PROBABLY rather the index be on (x,y), not on (y,x)

3 -- (second #3) it chooses between USING the index and NOT USING the index.  It
might full scan, it might do something entirely different.  there are lots of
access plans available.

remember -- a table can be sorted how many ways??? one.  so in general, one
index on a table MAY have a really good cluster factor, the rest won't.

I would use a data structure such as an IOT or a b*tree cluster or a hash
cluster if locale of data (having related data stored together) was my utmost
concern.  I spend lots of time on these topics in both "expert one on one
Oracle" as well as the newer "effective oracle by design"

      Rating: 5
Thanks Tom,
      and...  September 04, 2003
Reviewer:
      juancarlosreyesp@yahoo.com  from Bolivia
Thanks tom, a long multi part question as the one you don't like.

(In your book Expert one on one I didn't found a page talking about column
ordering specifically if you did ;)please tellme the page)
( In your new book are you talking specifically about column ordering in
indexes? )
(about IOT I tried to use them, but there is a problem with developer 6i)

I took this from Oracle internlas November 2002 Michael R. Ault.
      HINT TO OTHER DBAS: IF YOU SUBSCRIBE TO A TRIAL TO ORACLE
      INTERNALS YOU CAN READ PREVIOUS EDITIONS, VERY VERY NICE AND
      FREE :)

Tom, could you please explainme why this happens?
Tom What I see is that the best index is the one that has at the left the column
with more distinct values, I supposed that the best always will be the one with
the column with less distinct values to the left.
What I'm trying to understand is some simple rules to define oracle columns
order, without trying all the permutations (as you said).
Here is the example I´ll do as it is in that very nice article:

(don't be angry, in the example he uses analyze, that's why I use analyze but I
run too EXEC DBMS_UTILITY.ANALYZE_SCHEMA('ADM','COMPUTE') ;)

SQL> DROP TABLE TEST;
Tabla borrada.

SQL> create table test as select * from dba_objects ORDER BY OWNER,OBJECT_TYPE ;

Tabla creada.

SQL> create index test_ord1 on test(object_type,owner,object_name);

Indice creado.

SQL> analyze index test_ord1 compute statistics;

Indice analizado.

SQL> create index test_ord2 on test(object_name,owner,object_type);

Indice creado.

SQL> analyze index test_ord2 compute statistics;

Indice analizado.

SQL> create index test_ord3 on test(owner,object_name,object_type);
Indice creado.
SQL> analyze index test_ord3 compute statistics;
Indice analizado.
SQL> create index test_ord4 on test(owner,object_type,object_name);
Indice creado.
SQL> analyze index test_ord4 compute statistics;
Indice analizado.
SQL> analyze table test compute statistics;
Tabla analizada.

SQL> SELECT INDEX_NAME,CLUSTERING_FACTOR FROM USER_INDEXES WHERE INDEX_NAME LIKE
'TEST%';

INDEX_NAME                     CLUSTERING_FACTOR
------------------------------ -----------------
TEST_ORD1                                  18773
TEST_ORD2                                  26932
TEST_ORD3                                  20250
TEST_ORD4                                  18653

SQL>

SQL>  select
count(distinct(substr(dbms_rowid.rowid_to_restricted(rowid,1),1,8))) from test;

COUNT(DISTINCT(SUBSTR(DBMS_ROW
------------------------------
                           411

SQL>  select count(*) from test;

 COUNT(*)
---------
    30227

SQL>

SQL> select count(*) from test where owner = 'ADM' AND OBJECT_TYPE='TABLE';

 COUNT(*)
---------
      120

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=13)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'TEST_ORD1' (NON-UNIQUE) (Cost=2 C
          ard=27 Bytes=351)

**** he is using index on type,order, this is the column with MORE distinct
values to the left.

SQL> select count(*) from test where owner = 'ADM' AND OBJECT_NAME =
'CUENTAS_ME';

 COUNT(*)
---------
        1

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=28)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'TEST_ORD2' (NON-UNIQUE) (Cost=2 C
          ard=1 Bytes=28)

**** Again he is using index on type,order, this is the column with MORE
distinct values to the left.

SQL> select count(*) from test where object_type = 'TABLE' AND OBJECT_NAME =
'CUENTAS_ME';

 COUNT(*)
---------
        1

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=31)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'TEST_ORD1' (NON-UNIQUE) (Cost=9 C
          ard=1 Bytes=31)

But in this las situation optimicer breakes the previous rule and choose the
index with the column with fewer distinct values to the left.

Question:
So, I don't understand which other options I have other than create all
permutations of table and test.
;) Or you know some rules, please share them. I don't say THE RULES, I say some
rules that works most of the time.
Because I'm where I started I don't know how to choose the best order or
columns.

Thanks Tom

          Followup:

chapter on indexes discusses the order of columns in an index. page 311

as for the rest -- someday, when I'm taking questions - feel free.

what I'm finding is I cannot take any new questions since I get 50-100 of these
sorts of question in a question here. .....

      Rating: 5
Thank you
      :)  September 05, 2003
Reviewer:
      A reader

      Rating: 5
Use most
      selective index  October 18, 2003
Reviewer:
      A reader
Hi Tom,

We are using 9i in OLTP.

I have a query like the following:

select * from t
where column_with_10_distinct_values = :1
and column_with_10000_distinct_values = :2;

Both columns are indexed (non-uniqe b-tree).

But I noticed recently that the query is sometimes choosing the less selective
index on column_with_10_distinct_values.

Q1: why did this happen?
Q2: how to avoid this WITHOUT changing the actually code (i.e. somehow to affect
the CBO to choose the more selective index).

          Followup:

question for you

why then do you have an index on a column with 10 distinct values?

but beyond that -- please give more details, what does the table look like, what
are the distinct 10 values (and their distribution -- eg select
column_with_10_values, count(*) from t group by column_with_10_values)

do you analyze?
how do you analyze (EXACT COMMAND - not "we compute stats", but "we use analyze
table T compute statistics for table for all indexes" or
"dbms_stats.gather....." etc...

how big is the table?

what is your version?

      Rating:
      5
Thanks  October 21, 2003
Reviewer:
      A reader
Maybe it's my fault that I thought index stats are gathered by default if I
gather table stats. Seems I have to specify specifically whether using "analyze
table" or using "dbms_stats".

          Followup:

er?  don't follow?  can you answer my questions?


Subject | Article | Last Updated (East coast time)
FORALL issues... | article only | Oct 22, 2003 06:32 am
Question Regarding System Privilege SYSDBA ! | article only | Oct 22, 2003 12:01 pm
Oracle messaging -- is it good for workflow? | article only | Oct 22, 2003 12:10 pm
How long will Oracle support the Alpha EV4 / EV5 processors? | article only | Oct 22, 2003 05:57 pm
Listing roles and privs recursively | article only | Oct 22, 2003 05:58 pm
Questions About External Files | article only | Oct 22, 2003 06:03 pm
Parameterized View & Truncation of Table | article only | Oct 22, 2003 06:04 pm
Schema updates in 24/7 environment | article only | Oct 22, 2003 06:15 pm
Exportng Tables | article only | Oct 22, 2003 06:19 pm
db cloning -- what is it and why | article only | Oct 22, 2003 07:25 pm
look at the sql to see if it is unreasonable | article only | Oct 23, 2003 08:00 am
Case Insensitive Queries | article only | Oct 23, 2003 12:46 pm
Uncommitted Transactions and online redo log | article only | Oct 23, 2003 01:12 pm
CPU utilization | article only | Oct 23, 2003 01:18 pm
dbms_lock.sleep procedure | article only | Oct 23, 2003 01:21 pm
Teach me more about Application development | article only | Oct 23, 2003 01:29 pm
Tree and "Youngest Common Ancestor" | article only | Oct 23, 2003 01:39 pm
Long Raw to BLOB | article only | Oct 23, 2003 07:21 pm
Partition pruning vs Explicit partition naming. | article only | Oct 23, 2003 07:31 pm
Export Oracle Data Error! | article only | Oct 23, 2003 07:36 pm
About Buffer Cache Hit ratio | article only | Oct 23, 2003 07:39 pm
Materialized View | article only | Oct 24, 2003 09:32 am
open cursors | article only | Oct 24, 2003 09:37 am
catalog and catproc scripts... | article only | Oct 24, 2003 10:43 am
moving tables to a locally managed tablespace | article only | Oct 24, 2003 12:28 pm
Standby database | article only | Oct 24, 2003 12:37 pm
NOLOGGING | article only | Oct 24, 2003 03:40 pm
Query on design | article only | Oct 24, 2003 04:08 pm
'Kill session' priority | article only | Oct 25, 2003 08:05 am
Problems with the explain plan using in top sessions | article only | Oct 25, 2003 08:12 am
parallel server | article only | Oct 25, 2003 11:49 am
Date range per week | article only | Oct 26, 2003 09:21 am
Analyzing tables automatically | article only | Oct 26, 2003 09:34 am
comparing differenece between two tables | article only | Oct 26, 2003 09:37 am
undo retention question | article only | Oct 26, 2003 10:30 am
Unable to drop user | article only | Oct 26, 2003 03:09 pm
chr() function.rst_ | article only | Oct 26, 2003 06:14 pm
MV is not getting selected | article only | Oct 27, 2003 07:39 am
Mutating Table | article only | Oct 27, 2003 07:41 am
Row chaining | article only | Oct 27, 2003 07:55 am
Binding two arrays | article only | Oct 27, 2003 09:04 am
How to refer remote database with out DB link | article only | Oct 27, 2003 09:27 am
Ignore Case function in Oracle | article only | Oct 27, 2003 09:29 am
HTMLDB forums availability | article only | Oct 27, 2003 11:02 am
Two semantically equivalent sql, which one is preferrable | article only | Oct 27, 2003 01:27 pm
locally management tablespaces | article only | Oct 27, 2003 01:30 pm
Rows Affected? | article only | Oct 27, 2003 06:31 pm
ORA-01502, Oracle DEFAULT | article only | Oct 27, 2003 06:42 pm
Oracle Table-Partition | article only | Oct 28, 2003 07:31 am
Perform update | article only | Oct 28, 2003 07:33 am
SQL Query Tuning | article only | Oct 28, 2003 07:37 am
SQL Query | article only | Oct 28, 2003 07:55 am
(Single Trigger with IF condition) OR (Multiple Trigger) | article only | Oct 28, 2003 08:02 am
Caching a table in Memory | article only | Oct 28, 2003 10:21 am
Analytic Functions | article only | Oct 28, 2003 10:21 am
Storage Parameters in TableSpace/Tables | article only | Oct 28, 2003 10:25 am
Generate Create Table Statements | article only | Oct 28, 2003 10:42 am
private database link public synonym | article only | Oct 28, 2003 10:57 am
Recovery Manager ; Backup/Restore Issue | article only | Oct 28, 2003 10:58 am
High CPU in TKPROF for DDL | article only | Oct 28, 2003 11:15 am
Bitmap Indexes | article only | Oct 28, 2003 02:11 pm
Proxy authentication | article only | Oct 28, 2003 09:01 pm
On the use of DBMS_ROWID.ROWID_ROW_NUMBER function | article only | Oct 28, 2003 09:05 pm
Regular Delete vs. Bulk Delete | article only | Oct 28, 2003 09:08 pm
alter database set time_zone doesn't work | article only | Oct 28, 2003 09:09 pm
row(s) 1 - 65 of 65
October 22, 2003 Kashif -- Thanks for the question regarding "FORALL issues...", version 9.2.0.2
You Asked
Hi Tom,

I'm using the FORALL statement to update tables in my applications. When the
update raises an error, it can be saved using the SAVE EXCEPTIONS clause and
then continuing with the update, or the update can be aborted without using that
clause. In both these situations however, I am unable to pin down which specific
record caused the error, i.e. the id of the record. More specifically, is there
a way to store user defined data upon encountering an error during a FORALL
execution? Small example:

declare
type intArray is table of number index by binary_integer;
type charArray is table of varchar2 (2000) index by binary_integer;
type recType is record
(idArray intArray,
 array2, charArray);
l_tabrec recType;
lt_id intArray;
begin
...
... /* populating the l_tabrec record of arrays here. */
...
begin
forall i in 1..l_tabrec.idArray.count
   update table2
      set value = l_tabrec.array2(i)
    where id = l_tabrec.idArray(i);
exception
when others then
 /* here is where I'd like to log the id of the record which fails. */
raise;
end;
end;

I know the SAVE EXCEPTIONS gives us information about the type of error and the
iteration on which it encountered the error, but I'm not sure how I can use that
information to find which record actually caused the error. Hope my question is
clear. Thanks in advance.

Kashif
and we said...
Take a block like this:

ops$tkyte@ORA920> DECLARE
  2      type array is table of t%rowtype index by binary_integer;
  3      data   array;
  4      errors NUMBER;
  5      dml_errors EXCEPTION;
  6      l_cnt number := 0;
  7      PRAGMA exception_init(dml_errors, -24381);
  8
  9      cursor c is select * from t;
 10  BEGIN
 11      open c;
 12      loop
 13          fetch c BULK COLLECT INTO data LIMIT 100;
 14          begin
 15             FORALL i IN 1 .. data.count SAVE EXCEPTIONS
 16                insert into t2 values data(i);
 17          EXCEPTION
 18              WHEN dml_errors THEN
 19                   errors := SQL%BULK_EXCEPTIONS.COUNT;
 20                   l_cnt := l_cnt + errors;
 21                   FOR i IN 1..errors LOOP
 22                       dbms_output.put_line
 23                       ('Error occurred during iteration ' ||
 24                         SQL%BULK_EXCEPTIONS(i).ERROR_INDEX ||
 25                        ' Oracle error is ' ||
 26                         SQL%BULK_EXCEPTIONS(i).ERROR_CODE );
 27                   end loop;
 28          end;
 29          exit when c%notfound;
 30       END LOOP;
 31       close c;
 32      dbms_output.put_line( l_cnt || ' total errors' );
 33  end;
 34  /

There -- the ERROR_INDEX is the index into the arrays you passed to forall.  if
SQL%BULK_EXCEPTIONS(1).ERROR_INDEX = 5, then the 5th record in the bulk
operation has the error.

see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:8784259916366
for a full example.
Review & Followup
Rating: 4
Forall and Remote tables September 22, 2003
Reviewer: A reader
Can you please confirm this . Forall does not work on Remote table updates (I
did not try insert ,delete ).
U need to loop thru like a simple for loop and do the update.
Surprisingly the manual does not list this in the restrictions on Forall.
Are there any alternatives with the bulk collect option for remote dmls?
Thanx Sir.

Followup:
correct, forall does not function over dblinks.

Rating: 5
Do u also agree September 22, 2003
Reviewer: A reader
Do u agree we cannot gain by bulk collecting into a plsql table and then use
simpe for loop to update remote
table.
Instead its better to use for i in ( select ....) loop
                          update table
                          end loop.
 Here I found  a sql single correlated update of a remote table takes longer 3
hrs compared to plsql
 takes 2 minutes.

Thanx for your answers

Followup:
no i do not agree, not 100% anyway.

there are times, yes, that the above may be true.

there are times, yes, that the above may NOT be true.

Rating: 5
May I ask for examples to prove your point September 22, 2003
Reviewer: A reader

Followup:
ops$tkyte@ORA817DEV> /*
DOC>drop table t1;
DOC>drop table t2;
DOC>
DOC>create table t1 as select * from all_objects;
DOC>create table t2 as select * from t1;
DOC>
DOC>alter table t1 add constraint t1_pk primary key(object_id);
DOC>alter table t2 add constraint t2_pk primary key(object_id);
DOC>
DOC>analyze table t1 compute statistics for table for all indexes;
DOC>analyze table t2 compute statistics for table for all indexes;
DOC>*/
ops$tkyte@ORA817DEV> set timing on
ops$tkyte@ORA817DEV> update (select t1.object_name t1_oname, t2.object_name
t2_oname
  2                    from t1, t2@ora817dev.us.oracle.com@loopback t2
  3                   where t1.object_id = t2.object_id )
  4  set t1_oname = t2_oname;

26859 rows updated.

Elapsed: 00:00:22.29
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> commit;

Commit complete.

Elapsed: 00:00:00.04
ops$tkyte@ORA817DEV> update t1
  2     set object_name = (select nvl(max(object_name),t1.object_name)
  3           from t2@ora817dev.us.oracle.com@loopback t2
  4          where t1.object_id = t2.object_id )
  5  /

26859 rows updated.

Elapsed: 00:00:31.34
ops$tkyte@ORA817DEV> commit;

Commit complete.

Elapsed: 00:00:00.02
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> begin
  2          for x in ( select object_id, object_name from
t2@ora817dev.us.oracle.com@loopback )
  3          loop
  4                  update t1 set object_name = x.object_name where object_id =
x.object_id;
  5          end loop;
  6          commit;
  7  end;
  8  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:32.77
ops$tkyte@ORA817DEV>

Rating: 5
Excellant but loopback ? September 22, 2003
Reviewer: A reader
You are always precise and correct. That was convincing enough.
You may ignore this question if you are busy( duh.. you are always ) what was
that loopback thing in the dblink ?
Did you connect to the same schema you are at.
Whats that used for ?

Followup:
it was a loopback dblink -- goes through all of the work of a dblink, without me
having to actually setup 2 databases.

Rating: 4
exception while select September 23, 2003
Reviewer: Vipin from NYC
Hi Tom,

Is there any technique using which we can save exceptions while we are SELECTing
BULK COLLECT into PL/SQL tables.

Suppose we need to bulk update some rows and so i want BULK LOCK it rather than
going through a loop and lock it using a cursor, how will I know that locking of
which rows threw RESOURCE BUSY NOWAIT ERROR.

I want to use:-

SELECT EMPNO, MODIFIED_TIMESTAMP
BULK COLLECT INTO TB_EMPNO, TB_TIMESTAMP
FROM   EMP
FOR  UPDATE NOWAIT;

in place of :-

CURSOR C_EMP
IS
SELECT EMPNO, MODIFIED_TIMESTAMP
FROM   EMP
FOR    UPDATE NOWAIT;

because the latter involves again a looping and processing.

Followup:
that would not "make sense".  what would the use be there?  I mean -- you want
to update only rows that are not locked?  I don't see the benefit?

what are you trying to do, to accomplish -- maybe I can suggest something there.

Rating: 4
September 23, 2003
Reviewer: Vipin
Hi tom,

Sorry for not being clear. The requirement is locking a set of transactions and
updating them. If some of the transactions fails the locking, as somebody else
has already locked it, form a list of these falied transactions and send it to
front end while the other transaction will be processed successfully.

I know that this can be acheived using cursor, but then I will have to loop
through each records and process for each record, or else I have to loop through
each record, lock and then close the loop and finally process all the
transactions in bulk.

What I wanted to know is If I am able to save exceptions while bulk collect, I
can lock using bulk collect, for each of the saved exceptions I can collect the
already locked(hence failed while locking) transactions, continue batch
processing the rest and send frond end the list of failed transactions which is
nothing but the transactions which threw RESOURCE BUSY exception while bulk
collect.

So I wanted to know if we have a SAVE EXCEPTIONS provision or something similar
to that while BULK COLLECTing or else looping through the cursor is the only
option.

Followup:
there is no such concept for a SELECT statment -- it either "works" or it does
not.

you see with

  forall i in 1 .. 10 insert into t values ( i );

is it AS IF there were 10 separate executions of the insert, the statement is
run over and over and over really.

with

  select rownum bulk collect into l_data from all_objects where rownum <= 10;

there is only ONE statement - there is not any concept of saving exceptions row
by row, only by "statement execution".  forall just lets you say "execute this
statement N times"

you will have to loop through the records issuing a select for update nowait
against each.

Rating: 4
September 23, 2003
Reviewer: A reader
Hi tom,

so what is the real difference between

forall i in 1..10 insert into emp..;

and

for i in 1..10 loop
insert into emp....;
end loop;

Is it that 10 context switchs between PL/SQL and SQL is avoided ?

Followup:
yes, that is the crux of it, yes.

one sends the statment with 10 inputs to the sql engine.

the other sends 10 statements with 1 input each to the sql engine

Rating: 5
forall for select. October 13, 2003
Reviewer: joe from PA
Tom,

Since there is no forall for select, what is the best approach for collecting
the entire recordset when you have an index-by-table coming from a web form
submittal containing the primary keys of the rows you want.

I have some ideas, but not sure..
1.  for loop fetching each row individually
  -advantages,  I know how to do this.
  -disadvantages, don't do in many statements what can be done in a single
statement.
2.  an in query, casting your index-by-table type to a sql type and doing
something similar to what you do for a varying in-list.
  - you have documented this well, so assuming an index-by-table can be cast to
a database type table, this should be easy also
3.  casting the index-by table to a real table using a table function, and
joining to its values from the real table.  (not sure if this is wise or even
possible)
4.  Something designed for this, that I am overlooking?

Followup:
select ... BULK COLLECT into .....

or

open c
loop
   fetch c bulk collect into ..... limit 100;
   ....

   exit when c%notfound
end loop;

there are "forall" bulk constructs for selects.

Rating: 3
bulk collect not what I was looking for ( I think) October 13, 2003
Reviewer: joe from pa
Tom,

I wasn't very clear.  I have an index-by table that has all the primary keys I
want.

How do I get the full rows that match up to the values in my index-by table,
without getting them one at a time?

for i in 1 .. pk_table.count loop

     SELECT ... INTO l_rec FROM table WHERE pk = pk_table(i);

end loop;

do I do this or some of my earlier suggestions?
like #2 above..

bulk collect could be part of the answer if I do #2 or #3 above, but I was
asking about the query part to accomodate a list of rows from the index-by-table

Followup:
ahh -- ok, easy enough.  don't use an index by table, use a sql collection and
you can:

select * from table where pk in ( select * from table( collection_variable ) );

ops$tkyte@ORA920LAP> create or replace type array as table of number
  2  /

Type created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> declare
  2      l_data array := array(1,2,3,4,5);
  3  begin
  4      for x in ( select *
  5                   from all_users
  6                  where user_id in ( select *
  7                                       from table(cast(l_data as array)) ) )
  8      loop
  9          dbms_output.put_line( x.username );
 10      end loop;
 11  end;
 12  /
SYSTEM

PL/SQL procedure successfully completed.

Rating: 5
October 14, 2003
Reviewer: Mirjana from Barcelona

Rating: 5
October 14, 2003
Reviewer: Joe from PA
When receiving multiple parameters from a web form, aren't I stuck with:

type vc_arr is table of varchar2(32767) index by binary_integer;

or (as you like to do)

create or replace package types
as
   type array is table of varchar2(4000) index by binary_integer;
end;

but regardless an index-by table?

Is the process of looping through the index-by table and extending nested table
type variable populating it with the values from the index-by table worth it for
its use in SQL like you demonstrated above?  (no need to answer that, I can
determine that on my own, assuming that is the approach that would be taken)

We are talking about a hard limit of 100 keys in this index-by-table

Followup:
you would have to move it over, yes.

Rating: 4
I do "want to update only rows that are not locked?" October 22, 2003
Reviewer: Jim Dickson from London, UK
I have scenario where i do want to update only rows that are not locked.

A selection of rows from master table are output (ascii file) and go through a
Data Quality improvment process (typically re-parsing and geocoding of postal
address info).

No locks are held on these rows, since DQ process is external to Oracle.

Input file read as external table and address line elements (columns) need to be
updated on master table.

If particular row is currently being updated by another user, then logic is to
skip locked row. They will be processed by Data Quality process at a later date.

For the rows that are updated, expect transaction to be atomic.

I can do this with row-by-row cursor, with check for -00054 exception but can
this be done using BULK EXCEPTIONS?

(Also believe this can be done in using undocumented/unsupported SKIP LOCK but
not planning to go there).

9.2.0.3.0 Standard on Solaris 2.8

Followup:

This has to be done row by row -- as the NOWAIT is an option of the SELECT and
there is no "bulk exceptions" processing for selects as there is for
modifications.

Rating: 5
Clearly explained October 23, 2003
Reviewer: Jim Dickson from London, UK
Clearly explained - as ever. Thanks.

October 22, 2003 OP -- Thanks for the question regarding "Question Regarding System Privilege SYSDBA !", version 8.1.6.0.0
You Asked
Hi Tom,

Thanks very much for the previous answers.They were
really of immense help to me and my organisation.

Today I have a Question related with SYSDBA object
privilege.

SCENARIO
========

I have Installed ORACLE8i on my computer in the
organisation and created a local DATABASE where
the name of the database is ORACLE itself.

My computer's NetBios name is TEST.

Now after that I installed Enterprise Manager for
WINDOWS NT 4.0, version 2.0.4 ON THE same computer
TEST.

Now to administrate the DATABASE through Enterprise
Manager I created a Repository through the Login
sysman/OEM_TEMP without creating another Database.
After this I logged in through SQL*PLUS as
sys/change_on_install to create USER OP.Then I wrote

SQL> grant dba,connect,Resource to op;

after that

SQL> grant sysdba to op; and this statement failed .

I thought of Granting this sysdba PRIVILEGE through
EnterPrise Manager.I logged in into Enterprise
console through sysman/OEM_TEMP and then connected
to the Database as sys/change_on_install.Then I tried
to assign SYSDBA to OP.But this again failed and gave
me Insufficient Privilege Message each time.

I again tried the ame thing by connecting to the Database
as sysman/OEM_TEMP.Again the GRANT failed.

SCENARIO 2
==========

I tried to take backup of few datafiles through Enterprise
Manager and again got a Message

"Node does not have proper credentials".

SCENARIO 3
==========

When we login to work with Enterprise Manager,first
we have to make use of sysman/OEM_TEMP and then login
to connect to the database.While logging in to connect
to the database we get 3 options

Normal
sysdba
sysoper

I was able to connect to the database through OP/Miracle
Normal Mode.When I tried with the same login i.e.

OP/MIRACLE sysdba or
OP/MIRACLE sysoper

to connect to the database ,login just failed.

Sorry for writing such a huge scenario,but I am a bit
excited to know these things as I feel these things
are very basic for Enterprise manager to be used properly
and as a dba it's compulsory to have a knowledge these
things.

My Questions are as follows :

Q1) What are the things which a DBA cannot carry out
if the DBA has only GRANT TO DBA role,but does not
have SYSDBA privilege.

Q2) How to GRANT OP SYSDBA privilege ? Is there any
USER by DEFAULT who has SYSDBA privilege.If yes can
I make use of this user to connect to the database
by selecting SYSDBA from the List Box that appears
when the username and password are specified ?

Q3) What is the difference between SYSDBA and SYSOPER ?

Q4) what are credentials and how to create credential
so that I can start with Backup utility from Enterprise
Manager ?

Q5) Is SQL*LOADER a part of Enterprise manager.When we
select database and right click we get 3 options

export
import
load

Is this LOAD option is equivalent of SQL*LOADER ?

I am sorry again for the very lengthy senario and these
Questions,but really looking forward to hear the answers
of these Question from you.

I know you will understand !

Thanks and Regards
OP Lalwani
and we said...
1) you need to be a sysdba to grant sysdba:

tkyte@TKYTE816> connect sys/manager
Connected.
sys@TKYTE816> grant sysdba to scott;
grant sysdba to scott
*
ERROR at line 1:
ORA-01031: insufficient privileges

sys@TKYTE816> connect sys/manager  as sysdba;
Connected.
sys@TKYTE816> grant sysdba to scott;
Grant succeeded.

sysdba allows you do to "internal" things such as startup and
shutdown.  it'll also avoid locked accounts (sys can be locked
but sys as sysdba cannot be).

2) See above.  You have to be in as sysdba to grant it.

3)

  SYSOPER privilege allows operations such as:
    Instance startup, mount & database open ;
    Instance shutdown, dismount & database close ;
        Alter database BACKUP, ARCHIVE LOG, and RECOVER.
      This privilege allows the user to perform basic
        operational tasks
    without the ability to look at user data.

  SYSDBA privilege includes all SYSOPER privileges plus full
         system privileges
    (with the ADMIN option), plus 'CREATE DATABASE' etc..
      This is effectively the same set of privileges available
        when previously connected INTERNAL.

4) I assume you are getting a VNI-2015 (error codes are
important).  If so:

Overview
--------
When scheduling Jobs or Events through the Enterprise Manager
2.x Console, the  Oracle Management Server takes the request
from the Console and submits it to the Intelligent Agent on the
target server.  The Intelligent Agent attempts to establish a
"new" Operating System (OS) logon session for each job that it
receives.  The OS logon credentials are pulled from the
Console's Preferred  Credentials settings.  In the Preferred
Credentials section, each discovered  service is listed along
with its service type.  For OS logons, the " NODE "
service type must contain a valid OS user that has the correct
permissions.

If the logon information provided to the Intelligent Agent is
incorrect or  missing, the Intelligent Agent will return the
error:

    VNI-2015: Authentication Error

Troubleshooting Checklist
--------------------------
The VNI-2015 error can occur for several reasons.

EM Console:
===========
Begin troubleshooting the VNI-2015 error at the EM Console.

1.  Verify the logon credentials for the target Node and target
service have been set correctly in the Preferred Credentials
section of the EM Console.

If the Preferred Credentials are set correctly for the target
node and the target service, continue troublshooting at the
target server:

Windows NT and 2000 servers:
============================
2.  Verify the OS permission 'Logon as a Batch Job' has been
granted to the OS  (NODE) user stored in the EM Console's
Preferred Credentials section.

3.  If the OMS is in a different NT domain, verify the domains
can communicate.

UNIX servers:
=============
4.  If the server is a Unix system, check with the system
administrator and ask if any additional OS security packages are
installed on the server.

5.  Manually test both logons (NODE and SERVICE) at the target
server.

5) load is a layer on top of sqlldr.  exp, imp, sqlldr are
separate tools.  OEM has a gui interface that sits on top of
them.
Review & Followup
Rating: 5
Very useful OEM information July 12, 2001
Reviewer: Kimathi Njeru from El Segundo, CA USA
This answers a lot of questions about Console and OEM.  Precise and to the
point.

Rating: 5
OEM console November 27, 2001
Reviewer: raghunath from Bangalore , India
very good solved a problem

Rating: 4
Grant sysdba priviliges to users February 24, 2002
Reviewer: Lior from Israel
Despite of your detail explenaion I still cannot grant
sysdba to users.
I do not get errors after the grant statement ,
but when I try to export a tablespace with wxport utility
I get error that this can be done only with sysdba privilages .

Followup:
I assume you mean TRANSPORT the tablespace.

Are they in fact CONNECTING as SYSDBA:

$ exp tables=scott.emp

Export: Release 8.1.7.2.0 - Production on Sun Feb 24 09:30:58 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Username: / as sysdba

Connected to: Oracle8i Enterprise Edition Release 8.1.7.2.0 - Production
With the Partitioning option
JServer Release 8.1.7.2.0 - Production
Export done in US7ASCII character set and WE8ISO8859P1 NCHAR character set
server uses WE8ISO8859P1 character set (possible charset conversion)

About to export specified tables via Conventional Path ...
Current user changed to SCOTT
. . exporting table                            EMP         14 rows exported
Export terminated successfully without warnings.

Rating: 4
Grant sysdba priviliges to users February 24, 2002
Reviewer: Lior from Israel
Despite of your detail explenaion I still cannot grant
sysdba to users.
I do not get errors after the grant statement ,
but when I try to export a tablespace with wxport utility
I get error that this can be done only with sysdba privilages .

Rating: 4
Authentication error July 10, 2002
Reviewer: Abdal-Rahman Agha from Saudi Arabia
Hi Tom,

  What do you mean by:
 "3.  If the OMS is in a different NT domain, verify the domains can
communicate."

  I checked all the steps you inform, but still the above error appears. Please
help.

Followup:
Please contact support for configuration and installation assistance on this
one. Not a big OEM user personally (especially on windows)

Rating: 4
Ora-00439 can not connect as sysdba July 10, 2002
Reviewer: Oct from IN
Hi Tom,
Thanks for all previous help.
I am trying to connect as sysdba through sqlplus from Win98 client m/c ,but can
not. error ora-00439.
Data base O9i standard edition.
Connected to:
Oracle9i Release 9.0.1.1.1 - Production
JServer Release 9.0.1.1.1 - Production

SQL> connect scott as sysdba
Enter password: **********
ERROR: ORA-00439: feature not enabled: Enterprise User Security

Whats wrong.
Thanks
Oct

Followup:
New one on me.

Is the database local (if not, you are missing @database).  Since only PE is on
win98, I am tending to think that the database is remote.

and have you truly granted SYSDBA to scott (not really a brilliant idea)

and is remote_login_passwordfile setup properly in the database

Rating: 5
Email me if still.... July 11, 2002
Reviewer: Sikandar Hayat Awan from Pindi Bhattian - Pakistan
Hi OP if still u can't solve the problem then email me at sikandar_h@hotmail.com
instead disturbing TOM again and again. So I will try to solve ur problem.

Rating: 5
ora-00439 July 11, 2002
Reviewer: Mark A. Williams from Indianapolis, IN USA
There is a short discussion on MetaLink about the ora-00439 and 'Enterprise User
Security' with a couple of suggestions.  It may be relevant to your case.  If
you have access to MetaLink the doc id is: 218182.996

- Mark

Rating: 3
System Privileges v/s Object Privileges October 22, 2003
Reviewer: Reader from India
Hello,
Why is System Priv and Object Priv treated differently when it comes to revoking
the priviliges given in a chain. That's A granting to B 'with admin' for System
and Object. Then B grants to C. And When A revokes B's the C's does nt get
revoked in System but gets in Object ?

Followup:
thats the way it works -- I don't know what the "justification" is, it is the
way it has always worked.

October 22, 2003 Raja -- Thanks for the question regarding "Oracle messaging -- is it good for workflow?", version Oracle 8.1.6
You Asked
We are building a web site where lot of documents and messages
flow between multiple users. Transaction is very big and long
running. We have a single database where in multiple users
connect and post business documents and messages to each other.
We need to maintain this data for history and dispute resolution
purposes. Will Oracle advanced queuing technique suit for this
type of purpose. It is similar to Metalink forum with much more
business rules. Is metalink forum implemented using Oracle
advanced queue.
and we said...
Metalink is not done with queues, no.  Queuing can be thought of
as "work flow for data" -- it is used to enable automated, long
(or short) running electronic processes.  You would use queuing
as a way to make sure that after "subroutine 1" ran, "subroutine
2" runs.

With documents + PEOPLE (as opposed to data + PROCESSES), I
would be looking at Workflow (for people).  Queuing is designed
to build large scalable systems to tie processes together.
Workflow on the other hand is designed to automate the flow of
data from person to person OR process to process.

See
http://www.oracle.com/applications/collateral/index.html?ds_wf.html
for more info.
Review & Followup
Rating: 4
May 08, 2003
Reviewer: A reader
as far as I have understood (read in the developers guide) messages can be sent
to MULTIPLE consumers through recipient lists (explicit) or using subscribtions
(implicit).

I not sure about pros & cons of both approaches (if any) in terms of lets say
ressource consumption in general. could you please elaborate this? are there any
differences one should be aware of or advantages one might use?

Followup:
analogy time.

I post to a newsgroup (groups.google.com). anyone interested can see it.  they
subscribe to it and read it.  I (tom) have no idea who reads it.

I send an email to 5 people.  only they can see it.  they didn't even ask for
it.  I (tom) know who recieved it.

You need to figure out which analogy is best suited to solve your business
problem.  resource consumption in this case is sort of way down there on the
list of things to think about here -- what problem are you trying to solve and
which approach is best suited to do it.

Rating: 5
Very nice answer May 08, 2003
Reviewer: A reader

Rating: 5
Above link October 22, 2003
Reviewer: A reader
Hi Tom,
The above link doesn't work, if possible, please tell the relocated link.
Also if you can direct me to a link that compares the workflow packages like
staffware with the Oracle Workflow. Oracle workflow I understand is free, while
packages like staffware costs fortune. Do you have some guidelines on choosing
one over another.
Thanks

Followup:
http://otn.oracle.com/pls/db92/db92.docindex?remark=homepage#index-NEW
and ctl-f for workflow

You need to evaluate software based on your requirements -- thats the only thing
you can ever do when picking software.

October 22, 2003 Kevin -- Thanks for the question regarding "How
      long will Oracle support the Alpha EV4 / EV5 processors?", version 7.3.4

      You Asked
I have a customer looking for us to upgrade their OpenVMS
Alpha's that are EV4 Processors.  We are concerned that we need
newer processors to stay on the Oracle Supported List.  We need
to know the timetable for Oracle's commitment to these older
processors.

Thank You

Kevin O'Reilly

      and we said...
This is a support ALERT on this particular issue:

Article-ID:         <Note:111187.1>
Circulation:        PUBLISHED (EXTERNAL)
Folder:             platform.Unix.DigitalUnix
Topic:              ALERTS/Fixes
Title:              Oracle RDBMS 8.1.7 will NOT run on EV5 or
earlier systems
Document-Type:      ALERT
Impact:             MEDIUM
Skill-Level:        NOVICE
Server-Version:     08.01.07
Updated-Date:       14-FEB-2001 03:48:34
Attachments:        NONE
Content-Type:       TEXT/PLAIN
Products:           0;
Platforms:          87;

Oracle RDBMS 8.1.7 will NOT run on EV5 or earlier systems
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Versions Affected
~~~~~~~~~~~~~~~~~
  8.1.7 +  (Tru64 Unix)

Platforms Affected
~~~~~~~~~~~~~~~~~~
  Platforms using Compaq Alpha chip running Compaq Tru64 Unix.
  We do not forsee this impacting OpenVMS in the forseeable
future.

Description
~~~~~~~~~~~
  Next generation Oracle database software products will be
built with EV56 and
  later CPU optimizations, beginning with Oracle RDBMS 8.1.7
release.
  There will not be support for prior to EV56 generation Alpha
Tru64 UNIX systems.

  The large performance increases resulting from SPIKE and
Oracle's use of Alpha's  Byte/Word features (up to 30% with EV67
generations; mileage may vary depending on  the workload used).
This is a joint Oracle/Compaq decision.

Likelihood of Occurrence
~~~~~~~~~~~~~~~~~~~~~~~~
  Definately will be a problem with 8.1.7 and processors prior
to EV56

Workaround
~~~~~~~~~~
  Upgrade the processor to EV56 or above.

  To identify non-byte/word.
  (Note that this is an EV5.6 CPU and does support byte/word.
  EV5 or EV4 CPUs will not.)
  e.g.

        > % psrinfo -v
        >   Status of processor 0 as of: 04/19/00 15:58:16
        >   Processor has been on-line since 04/09/100 12:14:34
        >   The alpha EV5.6 (21164A) processor operates at 500
MHz,
        >   and has an alpha internal floating point processor.

  Note, (added 17th January 2001). A Compaq EV5.6 Processor
[KN304-FB]
  running at 300Mhz was made available in limited amounts.
Although listed
  as an EV5.6 cpu, this chip actually has no support for
byte/word.
  Consequently this chip will not support Oracle version 8.1.7
(on Tru64 UNIX)
  and 8.2 (VMS) onwards. This Processor has been always been
included in the 'affected'
  list below. Please also note that KN304-FA is the same cpu as
KN304-FB.

Patches
~~~~~~~
  No patches available, 8.1.7 will be written to take advantage
of EV56 and
  above chipset.

Review &
      Followup

      Rating: 5
EV4/EV5 Alpha
      Processor Question...Quick Response !!!  March 13, 2001

Reviewer:  Kevin O'Reilly  from Rochester, NY USA

Tom,
Thanks for the quick response on my question.  I'm glad to hear my Customer
running OpenVMS can continue to run their Application on RDBMS V7.3.4 on their
EV4 Processor.

Kevin O'Reilly
SMP-Corp

      Rating: 3
Processors in
      Standard Edition  October 21, 2003
Reviewer:
      A reader
Hi Tom, I have a technical question,
Oracle Standard Edition uses up to 4 processors
and Oracle Standard Edition One uses up to 1 processors.

This means that the parallel options has some effect if you have 4 processors.

1. How Oracle takes the advantage of the processors.?
2. How can you control the way that proccessors are used?

Thank you :)

          Followup:

parallel works on a single cpu just dandy.  very rare for something that could
be parallelized to take 100% of a single cpu when running serially (lots of IO
going on generally)

but.... if you have SE1 -- you are basically saying "we are small, we are
simple, we have very low end needs". I don't see where parallel even comes into
the picture.

      Rating: 5
What I
      understood about SE4  October 22, 2003
Reviewer:
      A reader
Thanks Tom,
1. What I understood is because access serially, in a SE4 (Standard Edition up
to 4 processors), the use of several processors is automatic?
Nothing to set manually, for example I can't say "to this specific user give 2
processors to his calculation."

2. The advantage of having 4 proccesor in SE4, which proccess benefits, only
calculations, why the difference between your two product SE1 SE4?

3.A last question, Why should I for example use SE1 instead of SE4 if I only
have one processor?

Thanks :)

          Followup:

1) you never assign processors to users in any release.  the OS schedules
processes/threads.  You can control degrees of parallelism (eg you can say "this
query should use parallel degree 2)

but then again the parallel stuff is only part of EE, not SE

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96531/ch5_edit.htm#75672
so it is sort of "moot"

2) cost.  one is for use in really small things (low number of cpu's/therefore
users).  the other for slightly larger

3) see #2

      Rating: 4
And last
      one  October 22, 2003
Reviewer:
      A reader
4. The SE1, is only available from 10G, or it is too available from 9i.

Thanks :)

          Followup:

as pricing/options/availability of 10g is "non existent" right now (product does
not "exist", only announced), it must be for 9i -- but, you would be best off
asking your sales rep

      Rating:
      5
Thanks  October 22, 2003
Reviewer:
      A reader

      Rating:
      5
Thanks  October 22, 2003
Reviewer:
      A reader

October 22, 2003 Andre -- Thanks for the question regarding
      "Listing roles and privs recursively", version 8.1.6

      You Asked
Hi Tom !

It is normally difficult -- in one select -- to list all
privileges and roles assigned to a specific user, since e.g. a
privilege can be assigned to a role, which can be assigned to
another role, which in turn can be assigned to a another role,
which can be granted a user. Can you please help me write a
query which hierarquically lists user, roles and privileges (sys
and tab) ?

Thanks for your assistance.

      and we said...
we can do it but its really slow -- unless we write a better
view.  The following lists all of your roles for you (might list
some N times)...

create view user_role_hierarchy
as
select u2.name granted_role
  from ( select *
           from sys.sysauth$
        connect by prior privilege# = grantee#
          start with grantee# = uid or grantee# = 1) sa,
       sys.user$ u2
 where u2.user#=sa.privilege#
union all select user from dual
union all select 'PUBLIC' from dual
/
grant select on user_role_hierarchy to public;

select * from user_role_hierarchy;

If you create that as SYS for example, it'll show the currently
logged in user their roles (and all of the roles granted to
those roles and so on).

to get all of my privs, i would:

select distinct PRIVILEGE,  owner, table_name
  from dba_tab_privs
 where grantee in ( select * from user_role_hierarchy )
/
select distinct PRIVILEGE
  from dba_sys_privs
 where grantee in ( select * from user_role_hierarchy )
/
Review &
      Followup

      Rating:
      4
Problem  September 19, 2001
Reviewer:
      Reader  from USA
Hi, Tom,

When i run:

select u2.name granted_role
  from ( select *
           from sys.sysauth$
        connect by prior privilege# = grantee#
          start with grantee# = uid or grantee# = 1) sa,
       sys.user$ u2
 where u2.user#=sa.privilege#
union all select user from dual
union all select 'PUBLIC' from dual;

every thing is Ok, but it gave me error as long as i run:

create view user_role_hierarchy
as
select u2.name granted_role
  from ( select *
           from sys.sysauth$
        connect by prior privilege# = grantee#
          start with grantee# = uid or grantee# = 1) sa,
       sys.user$ u2
 where u2.user#=sa.privilege#
union all select user from dual
union all select 'PUBLIC' from dual

the error is:

from sys.sysauth$
         *
ERROR at line 4:
ORA-00942: table or view does not exist

Does something wrong?

Thanks

          Followup:

see

http://osi.oracle.com/~tkyte/Misc/RolesAndProcedures.html
you have the ability to query sys.sysauth via a role and roles are not enabled
during the compilation of stored objects.

You need direct SELECT on the sys tables (or if you create that as SYS as
suggested, you can just grant select on that view)

      Rating: 5
I found this
      tip very useful  March 22, 2002
Reviewer:
      Javier Pflaum  from Cordoba, Argentina
As an Infosec Officer I very often control right in ower DBs and this help me a
lot to do my work.
Thanks.

      Rating: 5
Re: Listing
      roles and privs recursively  October 28, 2002

Reviewer:  CJ  from Albany, NY USA
Is there any easy way to do this for the entire instance? That is, list out all
users, their roles, and their privileges, not just for one user?

          Followup:

You can definitely use the same exact concept -- you would need to change the
queries  to include usernames -- not start with uid (no start with)

      Rating: 5
Account
      Privileges  January 06, 2003
Reviewer:  mo

Tom:

Does the DBA role mean that this account has DBA privilege?  If not how do you
find out if an account has a DBA privilege? Is this error because I am not
logged in as SYS?

SQL> grant select on user_role_hierarchy to public;
grant select on user_role_hierarchy to public
                *
ERROR at line 1:
ORA-01720: grant option does not exist for 'SYS.USER$'

SQL> select * from user_role_hierarchy;

GRANTED_ROLE
------------------------------
CONNECT
RESOURCE
DBA
SELECT_CATALOG_ROLE
HS_ADMIN_ROLE
EXECUTE_CATALOG_ROLE
HS_ADMIN_ROLE
DELETE_CATALOG_ROLE
EXP_FULL_DATABASE
SELECT_CATALOG_ROLE
HS_ADMIN_ROLE
EXECUTE_CATALOG_ROLE
HS_ADMIN_ROLE
IMP_FULL_DATABASE
SELECT_CATALOG_ROLE
HS_ADMIN_ROLE
EXECUTE_CATALOG_ROLE
HS_ADMIN_ROLE
PCSADMIN
PUBLIC

20 rows selected.

          Followup:

you are not allowed to grant on SYS.USER$ and you would be indirectly granting
on it by granting on this view.

So, log in as sys, grant select on user$ to YOUR_SELF with grant option if you
like.

      Rating: 3
account
      privilege  January 06, 2003
Reviewer:  mo

Tom:

well i can not log in as sys as a developer. But is this the correct way of
determining if this account has dba privilege? is that the DBA role that defines
it?

          Followup:

select * from session_roles

      Rating:
      5
Reader  January 07, 2003
Reviewer:
      A reader
"
following lists all of your roles for you (might list some N times)...
"

Is it possible to modify this, to get similar
results for any given user other than logged on user

Thanks

          Followup:

sure it is.  it is just a query that selects out based on USER -- should be easy
to upgrade to an "ALL_" or "DBA_" type query.

      Rating: 5
grantee# = 1
      ????  January 31, 2003
Reviewer:  r
       from london
Why "grantee# = 1" in the query. I queried the dba_users table to find out which
user had the user_id of 1 but I couldn't see any.

Thanks

          Followup:

PUBLIC

      Rating: 4
cascading
      effect  April 28, 2003
Reviewer:  A Reader
       from Salt Lake City, Utah
Why does oracle not cascade the effects of system privs when it is revoked and
the same is not true with revoking object privs as cascading effect is there
with object privs? Is there any logic or rational behind this implementation.

Thanks.

          Followup:

it is the implementation -- it is the design. it is the way it works.

they are two totally different things.  Imagine for a moment the effect of
dropping "the" user that begat all other users.  You would end up with a
database full of people that aren't able to do anything.

object privs are just totally different then system privs.

      Rating:
      4
cascade  May 23, 2003
Reviewer:
      Reader  from Houston, TX
If I granted a system priv to userX with admin option and userY grants that priv
to UserZ. Now if i drop userY, what happens to the priv that was granted to
UserZ by UserY? I was asked this question but did not know the answer. Could you
help? Also, if the same is done with object priv with grant option, what Oracle
would do if I dropped userY? Thanks. You are amazing. I enjoy reading your site
almost everyday.

          Followup:

system privs persist -- things granted via the WITH ADMIN OPTION persist.
object privs are retracted -- things granted via the WITH GRANT OPTION go
away.

Consider -- A and B:

ops$tkyte@ORA920> create table t ( x int );
Table created.

ops$tkyte@ORA920> create user a identified by a;
User created.

ops$tkyte@ORA920> create user b identified by b;
User created.

ops$tkyte@ORA920> grant select on t to a with grant option;
Grant succeeded.

ops$tkyte@ORA920> grant create view to a with admin option;
Grant succeeded.

ops$tkyte@ORA920> grant create session to a;
Grant succeeded.

ops$tkyte@ORA920> grant create session to b;
Grant succeeded.

ops$tkyte@ORA920> @connect a/a

a@ORA920> grant select on ops$tkyte.t to b;
Grant succeeded.

a@ORA920> grant create view to b;
Grant succeeded.

a@ORA920> @connect b/b

b@ORA920> desc ops$tkyte.t
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 X                                            NUMBER(38)

b@ORA920> create view v1 as select * from dual;

View created.

so, B can see ops$tkyte.t and B can create views...  now we'll drop A

b@ORA920> @connect /

ops$tkyte@ORA920> drop user a;
User dropped.

ops$tkyte@ORA920> @connect b/b

b@ORA920> desc ops$tkyte.t
ERROR:
ORA-04043: object ops$tkyte.t does not exist

b@ORA920> create view v2 as select * from dual;

View created.

B can no longer see ops$tkyte.t but B can still create views

b@ORA920>

      Rating: 5
THANKS a
      bunch!  May 24, 2003
Reviewer:  Reader
       from Houston, TX

      Rating: 5
Strong
      suggestion.  May 24, 2003
Reviewer:  Saibabu
      Devabhaktuni  from Palo Alto, CA USA
Since this thread is on Roles/privileges and accessibility, please do read
Metalink doc# 9368.996 before using some of the system privileges like "create
any view/procedure"...etc.

Thanks

      Rating:
      5
  September 03, 2003
Reviewer:
      A reader
Hi Tom,

Can we grant select privileges on certain column of a table ?

for eg. I have table x with column a,b
I want to grant select on only column a to public.

How can I do that?

Thanks

          Followup:

no, you cannot.

you use vertical partitioning to achieve that goal.

or views.

      Rating:
      3
SYSOPER  September 03, 2003
Reviewer:
      Anurag  from INDIA
Tom,

I am using Oracle 8i (8.1.5). When I connect as
conn sys/oracle as sysoper......
it shows connected to an ideal instance.
but the database is already open. Also where are these roles(Sysdba and Sysoper)
listed.
and why are they called as roles. Becoz, normally we don't login as conn
user/pwd as role.

pl. clarify. thanks in adv.

          Followup:

i don't think so.  you are doing something wrong -- you've connected to an idle
instance, not one that is up -- just type startup and see what happens.  if the
database was already going, well, that'll not work.

sysdba and sysoper are "special roles"

actually -- only your dba should be using sysoper and sysdba and they would know
what they are for.  sysdba is "all powerful" and sysoper is allowed to startup
and shutdown.

      Rating:
      3
sysdba....  September 05, 2003
Reviewer:
      anurag  from india
When I wrote ..........startup
it says ... can not start already running oracle.

Can U try this on your machine.

thanks ...

          Followup:

then, why you you think it is "idle"

      Rating: 4
Roles and privs
      assigned to them.  October 01, 2003
Reviewer:
      jj  from Germany
How to get information about a role wrt roles and other privileges assigned to
it? If a role is assigned to a role then just its name is okay, no recursion
needed.

Which views/tables to query to get this info?

          Followup:

dba_tab_privs
dba_sys_privs
dba_role_privs

where grantee = 'ROLE NAME';

      Rating: 4
Execute
      privilege on triggers  October 13, 2003
Reviewer:
      A reader
Tom

We have a table T, it has a post insert trigger post_insert_T_trigger. The
trigger calls a package P, to insert into another table TI.

Logged in as user1, when I inserted rows into the table, the trigger did not
fire( this was evident from the fact that ti was not getting populated).

But when I logged in as user2, and inserted rows into T, the trigger fired and
TI got populated.

1.Now I want to know, what can be the different reasons due to which user1 was
not able to fire the trigger.

2.How can we find out whether user1 has execute privilege on the trigger T.
Which V$view gives that information.

If privileges are being given via a role, how can we find out which role has the
respective privilege, and whether user1 has the role assigned. If the role is
assigned to user1, then to check and see if the execute privilege on trigger T
is not assiged to user1.

          Followup:

what you describe is just not possible.  You've made a mistake somewhere (eg:
most probably cause -- you have many table T's and you used the wrong one)

you need not have execute on a trigger - there is no such concept at all.
triggers are part of the table.  all you need is the ability to insert into the
table and the code will run.

You've made a mistake in your test somewhere.

      Rating: 2
How can I
      verify  October 14, 2003
Reviewer:
      A reader
How can I verify if I have privilege to insert into the table.

          Followup:

well, you could try it - that would tell you right away.

you can query the data dictionary - dba_tab_privs - to see if you or any role
you have has insert or all on that table as well

      Rating: 5
RE: How can I
      verify  October 14, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
RE: How can I verify

Have a look at user_tab_privs.

HTH,

Mark

      Rating: 3
Grants
      created/modified on  October 22, 2003
Reviewer:
      Tommy  from Canada
Is there a way to find out "when" a grant is created or modified? I have looked
into USER_TAB_PRIVS and USER_OBJECTS...

          Followup:

we do not track the time, you would have to have enabled auditing to capture
that.

October 22, 2003 Elie -- Thanks for the question regarding
      "Questions About External Files", version 9.2.0

      You Asked
Hi, Tom.

I am trying to use Oracle 9i's ability to treat ASCII files as Oracle tables and
have run into a few problems.

First off , here is the script I've written that creates a directory called
"external_tables_dir" and then reads an ASCII file called "clntques.txt" whose
fields are pipe delimited:

declare
  ddl  varchar2(2000);
  cnt  integer;
begin
  ddl := 'create or replace directory external_tables_dir as
''/home/athena_d/ora_ext_tbl''';
  execute immediate ddl;

  select count(*)
    into cnt
    from user_tables
   where table_name = 'EXTERNAL_CLNTQUES';

  if ( cnt = 1 ) then
    ddl := 'drop table athena.external_clntques';
    execute immediate ddl;
  end if;

  ddl := '
create table athena.external_clntques
(CLIENT_ID              number(10),
 NAME                   varchar2(60),
 SHORT_NAME             varchar2(15),
 QTEXT                  varchar2(4000),
 QUESTION_CATEGORY      varchar2(60),
 CLI_QUESTION_CODE      varchar2(60),
 IS_ACTIVE_CHK          varchar2(1),
 IS_DELETED_CHK         varchar2(1),
 CREATED_ON             date,
 UPDATED_ON             date,
 CREATED_BY_ID          number(10),
 UPDATED_BY_ID          number(10),
 ALT_KEY                varchar2(60)
)
organization external
(type oracle_loader
 default directory athena_external_tables_dir
 access parameters
 (fields terminated by ''|''
  missing field values are null
  (client_id,
   name,
   short_name,
   qtext,
   question_category,
   cli_question_code,
   is_active_chk,
   is_deleted_chk,
   created_on,
   updated_on char date_format date mask "dd-mon-yyyy hh24:mi:ss",
   created_by_id,
   updated_by_id,
   alt_key)
 )
 location (''clntques.txt'')
)
parallel 2
reject limit unlimited';
execute immediate ddl;
end;
/

And here are the first few rows from the ASCII file:

CLIENT_ID|NAME|SHORT_NAME|QTEXT|QUESTION_CATEGORY|CLI_QUESTION_CODE|IS_ACTIVE_CHK
|IS_DELETED_CHK|CREATED_ON|UPDATED_ON|CREATED_BY_ID
|UPDATED_BY_ID|ALT_KEY
6|||Would you be interested in speaking with a Ryder Vehicle Sales manager to
discuss your current needs?|||Y|N|||||RYDER*00527
6|||Name of person completing the survey if different than
addressed.|||Y|N|||||RYDER*00049
6|||How would you classify your company?|||Y|N|||||RYDER*00050
6|||Supply Chain Services:|||Y|N|||||RYDER*00372
6|||Were you contacted by Ryder Vehicle Sales?|||Y|N|||||RYDER*00523
6|||Are <COMPANY> customers requesting 'Just in Time
Delivery'?|||Y|N|||||RYDER*00007

=============================================================

Question 1:
   Running the above script produces no errors.  However,
   if I then run SELECT * FROM EXTERNAL_CLNTQUES, Oracle creates
   a file EXTERNAL_CLNTQUES_5466.bad containing 6 records that
   were rejected from my ASCII file.  Oracle also creates the
   file EXTERNAL_CLNTQUES_5466.log that contains these error
   messages, one for each of the 6 rejected records:

   KUP-04021: field formatting error for field QTEXT
   KUP-04026: field too long for datatype
   KUP-04101: record 54 rejected in  file
/home/athena_d/ora_ext_tbl/clntques.txt

   This log file also defines the QTEXT field as:

    QTEXT                           CHAR (255)
      Terminated by "|"
      Trim whitespace same as SQL Loader

   And so, Oracle thinks that the QTEXT field in the ASCII file
   is a CHAR(255) column.  However, as specified in my sqlplus
   script, the column name QTEXT is defined as VARCHAR2(4000).
   How can I get my sqlplus script to recognize QTEXT as
   VARCHAR2(4000)?

Question 2:
The first row in ASCII file clntques.txt is a header record, simply giving the
names of the fields making up the file.  After running my script, somehow Oracle
"knows" that this very first row is really not part of the file's data.  I know
this is true because doing a SELECT * FROM EXTERNAL_CLNTQUES starts showing data
from the second row in the file.  How does Oracle know that this file's first
row is, in fact, just a header record?  There is no "SKIP=1" clause that I can
put into my sqlplus script, is there?

Question 3:
Do you know how I can specify "log" and "bad" directories in
my sqlplus script?  I have tried to use the clauses:

   badfile external_tables_dir: ''bad_clntques''
   logfile external_tables_dir: ''log_clntques''

in the script but Oracle then complains with "Invalid statement".

Question 4:
Whenever I run any type of SELECT query against virtual table EXTERNAL_CLNTQUES,
Oracle creates at least 2 log files in the defined directory.  I've noticed that
Oracle sometimes even creates 3 files:  2 log files and a "bad" file if errors
occurred.  Why is Oracle doing this?  In a real life application, how can I tell
if a query running against such a virtual table caused errors?  In order to know
that my query ran OK, it seems that I have to constantly look for any new "bad"
files generated in the defined directory as opposed to simply getting an Oracle
error either displayed on a terminal (as in a SQLPLUS session) or an error that
I can "catch" in an exception handler (as in a plsql package).

I would be interested to know your answers/comments to these questions.

Thanks very much.

Elie

      and we said...
parallel is meaningful ONLY with fixed length (positional) files.  We need to
know that each record is say "100 bytes" in order to parallelize on it.  Thats
just an FYI...

Also, don't you find it tedious to run ddl in plsql?  Just use sqlplus (thats
what I'm going to do)

I see:

create or replace directory external_tables_dir as

but also:

(type oracle_loader
 default directory athena_external_tables_dir

so, perhaps (99.999% likely) you are looking at one file - but loading a totally
different one? I say that because I ran this pretty much unchanged (except no
plsql -- just a script)

ops$tkyte@ORA920.US.ORACLE.COM> @test
ops$tkyte@ORA920.US.ORACLE.COM> drop table external_clntques;

Table dropped.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace directory external_tables_dir
as
  2  '/tmp/'
  3  /

Directory created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create table external_clntques
  2  (CLIENT_ID              number(10),
  3   NAME                   varchar2(60),
  4   SHORT_NAME             varchar2(15),
  5   QTEXT                  varchar2(4000),
  6   QUESTION_CATEGORY varchar2(60),
  7   CLI_QUESTION_CODE varchar2(60),
  8   IS_ACTIVE_CHK          varchar2(1),
  9   IS_DELETED_CHK         varchar2(1),
 10   CREATED_ON             date,
 11   UPDATED_ON             date,
 12   CREATED_BY_ID          number(10),
 13   UPDATED_BY_ID          number(10),
 14   ALT_KEY                varchar2(60)
 15  )
 16  organization external
 17  (type oracle_loader
 18   default directory external_tables_dir
 19   access parameters
 20   (fields terminated by '|'
 21    missing field values are null
 22    (client_id,
 23     name,
 24     short_name,
 25     qtext,
 26     question_category,
 27     cli_question_code,
 28     is_active_chk,
 29     is_deleted_chk,
 30     created_on,
 31     updated_on char date_format date mask "dd-mon-yyyy hh24:mi:ss",
 32     created_by_id,
 33     updated_by_id,
 34     alt_key)
 35   )
 36   location ('clntques.txt')
 37  )
 38  parallel 2
 39  reject limit unlimited;

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> exec print_table( 'select * from
external_clntques' )
CLIENT_ID                     : 6
NAME                          :
SHORT_NAME                    :
QTEXT                         : Would you be interested in speaking with a Ryder
Vehicle Sales manager to discuss your current
needs?
QUESTION_CATEGORY             :
CLI_QUESTION_CODE             :
IS_ACTIVE_CHK                 : Y
IS_DELETED_CHK                : N
CREATED_ON                    :
UPDATED_ON                    :
CREATED_BY_ID                 :
UPDATED_BY_ID                 :
ALT_KEY                       : RYDER*00527
-----------------
CLIENT_ID                     : 6
NAME                          :
SHORT_NAME                    :
QTEXT                         : Name of person completing the survey if
different than addressed.
QUESTION_CATEGORY             :
CLI_QUESTION_CODE             :
IS_ACTIVE_CHK                 : Y
IS_DELETED_CHK                : N
CREATED_ON                    :
UPDATED_ON                    :
CREATED_BY_ID                 :
UPDATED_BY_ID                 :
ALT_KEY                       : RYDER*00049
-----------------
CLIENT_ID                     : 6
NAME                          :
SHORT_NAME                    :
QTEXT                         : How would you classify your company?
QUESTION_CATEGORY             :
CLI_QUESTION_CODE             :
IS_ACTIVE_CHK                 : Y
IS_DELETED_CHK                : N
CREATED_ON                    :
UPDATED_ON                    :
CREATED_BY_ID                 :
UPDATED_BY_ID                 :
ALT_KEY                       : RYDER*00050
-----------------
CLIENT_ID                     : 6
NAME                          :
SHORT_NAME                    :
QTEXT                         : Supply Chain Services:
QUESTION_CATEGORY             :
CLI_QUESTION_CODE             :
IS_ACTIVE_CHK                 : Y
IS_DELETED_CHK                : N
CREATED_ON                    :
UPDATED_ON                    :
CREATED_BY_ID                 :
UPDATED_BY_ID                 :
ALT_KEY                       : RYDER*00372
-----------------
CLIENT_ID                     : 6
NAME                          :
SHORT_NAME                    :
QTEXT                         : Were you contacted by Ryder Vehicle Sales?
QUESTION_CATEGORY             :
CLI_QUESTION_CODE             :
IS_ACTIVE_CHK                 : Y
IS_DELETED_CHK                : N
CREATED_ON                    :
UPDATED_ON                    :
CREATED_BY_ID                 :
UPDATED_BY_ID                 :
ALT_KEY                       : RYDER*00523
-----------------
CLIENT_ID                     : 6
NAME                          :
SHORT_NAME                    :
QTEXT                         : Are <COMPANY> customers requesting 'Just in Time
Delivery'?
QUESTION_CATEGORY             :
CLI_QUESTION_CODE             :
IS_ACTIVE_CHK                 : Y
IS_DELETED_CHK                : N
CREATED_ON                    :
UPDATED_ON                    :
CREATED_BY_ID                 :
UPDATED_BY_ID                 :
ALT_KEY                       : RYDER*00007
-----------------

PL/SQL procedure successfully completed.

and it worked immediately?  As for the QTEXT question -- sure, your database
column definition -- what the input data will be mapped to -- is a
varchar2(4000) but the input type is defaulted -- you didn't specify an input
type for QTEXT.  Hence, it defaults to char(255). do this:

...
   short_name,
   qtext CHAR(4000),
   question_category,
   cli_question_code,
.....

and your log will have:

    SHORT_NAME                      CHAR (255)
      Terminated by "|"
      Trim whitespace same as SQL Loader
    QTEXT                           CHAR (4000)
      Terminated by "|"
      Trim whitespace same as SQL Loader
    QUESTION_CATEGORY               CHAR (255)
      Terminated by "|"

instead....

2) I'm thoroughly confused now.  Q1 was all about "help, I get nothing from my
external table" but this one says "ok, when I select from it -- I only get the
valid data -- it seems to skip the header?????

So - can you or can you not select from this external table?

Anyway - assuming you can, you'll find your header in the BAD file.  Why? Well:

create table athena.external_clntques
(CLIENT_ID              number(10),
....

the string client_id is not convertable to a number(10) so that record is quite
simply rejected.  You can skip this record via:

 default directory external_tables_dir
 access parameters
 (
  records delimited by newline skip 1
  fields terminated by '|'
  missing field values are null

(documented in

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96652/ch12.htm#1009462
)....

q3)

 access parameters
 (
  records delimited by newline
    badfile external_tables_dir:foobar
    skip 1

will create /tmp/foobar.bad for bad records (given my example)...

Since I don't know where you tried to stick this before, I cannot comment on why
it did not work.

q4) Using your original create that worked (no skip, no badfile) I do not get
the same results you do:

ops$ora920@ORA920.US.ORACLE.COM> !rm -f /tmp/*.bad /tmp/*.log

ops$ora920@ORA920.US.ORACLE.COM> !ls -l /tmp/*.bad  /tmp/*.log
ls: /tmp/*.bad: No such file or directory
ls: /tmp/*.log: No such file or directory

ops$ora920@ORA920.US.ORACLE.COM> select count(*) from external_clntques;

  COUNT(*)
----------
         6

ops$ora920@ORA920.US.ORACLE.COM> !ls -l /tmp/*.bad  /tmp/*.log
-rw-r--r--    1 ora920   ora920        155 Dec  8 10:50
/tmp/EXTERNAL_CLNTQUES_23676.bad
-rw-r--r--    1 ora920   ora920       1859 Dec  8 10:50
/tmp/EXTERNAL_CLNTQUES_23676.log

ops$ora920@ORA920.US.ORACLE.COM>

So, I cannot comment on your "two" log files since I don't see two and you don't
show us what they were....

The very existence of a BAD file would indicate errors and you could do the
following.  Here I did use a BADFILE filename (foobar.bad) and created a "bad
table"

ops$ora920@ORA920.US.ORACLE.COM> create table external_clntques_bad
  2  ( text1 varchar2(4000) ,
  3    text2 varchar2(4000) ,
  4    text3 varchar2(4000)
  5  )
  6  organization external
  7  (type oracle_loader
  8   default directory external_tables_dir
  9   access parameters
 10   (
 11    records delimited by newline
 12     fields
 13    missing field values are null
 14  ( text1 position(1:4000),
 15       text2 position(4001:8000),
 16       text3 position(8001:12000)
 17     )
 18   )
 19   location ('foobar.bad')
 20  )
 21  /

Table created.

ops$ora920@ORA920.US.ORACLE.COM>
ops$ora920@ORA920.US.ORACLE.COM>
ops$ora920@ORA920.US.ORACLE.COM> select count(*) from external_clntques_bad;

  COUNT(*)
----------
         1

It lets you see how many bad records there are (and even view if if you want)

ops$ora920@ORA920.US.ORACLE.COM> select * from external_clntques_bad;

TEXT1
---------------------------------------------------------------------------------
--------------------------------------------------
TEXT2
---------------------------------------------------------------------------------
--------------------------------------------------
TEXT3
---------------------------------------------------------------------------------
--------------------------------------------------
CLIENT_ID|NAME|SHORT_NAME|QTEXT|QUESTION_CATEGORY|CLI_QUESTION_CODE|IS_ACTIVE_CHK
|IS_DELETED_CHK|CREATED_ON|UPDATED_ON|CREATED_BY_I
D|UPDATED_BY_ID|ALT_KEY

The entire concept of the "bad" file is to IGNORE records that would cause an
error.  If you got an error (as you seem to imply you want by the comment "it
seems that I have to constantly look for any new "bad"
files generated in the defined directory as opposed to simply getting an Oracle
error either displayed on a terminal"...

You surpressed the errors with reject limit unlimited.  So yes, you would have
to "constantly" look for bad files in your case.  You can have an exception
raised -- that is easy, just set your reject limit down.

Review &
      Followup

      Rating: 5
Questions About
      External Files  December 08, 2002
Reviewer:
      Elie Grunhaus  from Rochester, NY
Thanks very much for your quick reply.

Your comments and examples are very instructive,especially the link you included
to the Oracle documentation on external file access.  I see there several
clauses that will be very useful for me.

      Rating:
      5
  December 11, 2002
Reviewer:
      A reader
Hi Tom,

I have created an external table and I am getting error while accessing it.

 SQL> CREATE directory MY_DIR AS 'E:\ADMIN\';

Directory created.
SQL> grant all  on directory my_dir to public;

Grant succeeded.

  1   create table external_table
  2       (EMPNO NUMBER(4) ,
  3        ENAME VARCHAR2(10),
  4        JOB VARCHAR2(9),
  5        MGR NUMBER(4),
  6        HIREDATE DATE,
  7        SAL NUMBER(7, 2),
  8        COMM NUMBER(7, 2),
  9        DEPTNO NUMBER(2)
 10      )
 11      ORGANIZATION EXTERNAL
 12      ( type oracle_loader
 13        default directory MY_DIR
 14        access parameters
 15        ( fields terminated by ',' )
 16        location ('emp.txt')
 17* )
SQL> /

Table created.

SQL> select * from external_table;
select * from external_table
*
ERROR at line 1:
ORA-29913: error in executing ODCIEXTTABLEOPEN callout
ORA-29400: data cartridge error
KUP-04040: file emp.txt in MY_DIR not found
ORA-06512: at "SYS.ORACLE_LOADER", line 14
ORA-06512: at line 1

-- I have EMP.TXT file on e:\admin dir. on server

Thanks,

          Followup:

Is e: a permanent drive on the server.  (if it is a network mount, you are using
windows, not as easy as unix with mounts)

Is e:\admin accesible to everyone (remember, oracle isn't running as YOU)

Is e:\admin\emp.txt accesible to everyone?

99999 times out of 100000 -- if the answers are no, yes, yes -- the file really
isn't there, it is on the client -- check again.

      Rating:
      5
  December 12, 2002
Reviewer:
      A reader
Hi Tom,

    e: a permanent drive on the server
    ----------------------------------
-- e: is Removable disk drive(External) (We have test PC here in our lab and
they don't have  enough space so I added extrnal disk on one of the NT
workstation and created drive  E:\)

   Is e:\admin accesible to everyone
   ---------------------------------

-- About four people are using this instance from their   machine.We have given
all rights on E:\ to all the people.

   Is e:\admin\emp.txt accesible to everyone?
   ------------------------------------------
-- Since I gave right on E:\ to all It is accesible to everybody. Everybody
mapped drive e:\ on their machine AND they are able to do all  operation(-rwx)
to this drive.

Thanks,

          Followup:

and if you use a permanent drive, in order to test the theory that maybe this
removable media is not available to the services that start on boot up -- what
happens then.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:241814624807
for where I'm going here -- that the removable media is like a network drive and
not universally accessible to the services.

Test by using a real disk.

      Rating: 5
fields are not
      terminated  September 25, 2003
Reviewer:
      Pushparaj  from NJ, USA
Tom,

I have a data file and the fields are not terminated
by anything. It is basically terminated by the field position. What should I
provide in the access parameters
list.

For example,
My datafile is

11SCOTT10
12SAMMY20

empno position (1,2)
ename position (3,7)
deptno position (8,9)

CREATE TABLE emp_ext
( empno number(2),
  ename char(5),
  deptno number(2)
)
organization external
( type oracle_loader
default directory DATA_DIR
access parameters
(
   fields terminated by ????????
)
location ('emp.dat')
)
/

          Followup:

don't use terminated by, use POSTITION(n:m) instead

      Rating: 5
This is great!
      BUT how do I do this?  October 21, 2003
Reviewer:
      chneih  from USA
I have a CSV datafile optionally enclosed by '"' but with comma inside the
enclosed '"' field.

For example

AcctNO   Name    JobTitle Age

123,"Schmoe, Joe","Mechaic",23
456,"David","Manager",45
789,"Blaine, David, E","",20

Thanks,
Chneih

          Followup:

tis ok, that is what the enclosed by is about

      Rating:
      5
Question  October 22, 2003
Reviewer:
      chneih  from USA
How do I load everything to a 4 column table. Right now I'm using

fields delimited by ',' optionally enclosed by '"'

AcctNO   Name    JobTitle Age

123,"Schmoe, Joe","Mechaic",23
456,"David","Manager",45
789,"Blaine, David, E","",20

Thanks again

          Followup:

ops$tkyte@ORA920> desc t
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 A                                            NUMBER(38)
 B                                            VARCHAR2(25)
 C                                            VARCHAR2(15)
 D                                            NUMBER(38)

ops$tkyte@ORA920> !cat t.ctl
LOAD DATA
INFILE *
INTO TABLE T
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
(a,b,c,d)
BEGINDATA
123,"Schmoe, Joe","Mechaic",23
456,"David","Manager",45
789,"Blaine, David, E","",20

ops$tkyte@ORA920> !sqlldr / t.ctl

SQL*Loader: Release 9.2.0.3.0 - Production on Wed Oct 22 18:05:09 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Commit point reached - logical record count 3

  1* select * from t
ops$tkyte@ORA920> /

         A B                         C                        D
---------- ------------------------- --------------- ----------
       123 Schmoe, Joe               Mechaic                 23
       456 David                     Manager                 45
       789 Blaine, David, E                                  20

October 22, 2003 Puneet -- Thanks for the question regarding
      "Parameterized View & Truncation of Table", version 8.1.5.0.0

      You Asked
Dear Tom
Hi!!!
My problems...
1)Why does'nt Oracle permits to create a Parameterized View in
the lines of a Parameterized Cursor. I think there should not be
any problem of creating a parameterized view because in case of
a view only its definition is stored and only when that view is
accessed, data is populated in that view.

Eg:- CREATE VIEW myview (parameter <datatype>)
AS SELECT * FROM <table_name> WHERE <table_name.column_name> =
parameter.

The above view can be accessed like this,
select * from myview (parameter value);

I know there will be some problem with this feature because now
a view definition resides in the shared pool, but in the above
mentioned case it has to reside in the program area of each
user.
But if this feature can be made available, then it would be of
great help.

2)I think there is a bug in Oracle about Truncation of a table.
I tried to truncate a master table on which there are foreign
key links of other tables. Naturally error occurred and the
error message is "ora-02266 unique/primary keys in the table
referenced by enabled foreign keys".
Then I truncated the dependent tables and again tried to
truncate the master table but the same error occurred, the error
message is "ora-02266 unique/primary keys in the table
referenced by enabled foreign keys".
I am hundred percent sure that there is no foreign key violation
in this case. Then I deleted records from the master table and
it executed successfully. I am really baffled by this weird
behaviour of the Truncation command.

3) It has been noticed that even when a DDL command executed on
a table is unsuccessful, data gets commited in that table. Why
does this happen?

      and we said...
1) we do, it works like this:

create view myview
as select * from t where x = userenv('client_info')
/

and then:

SQL> exec dbms_application_info.set_client_info('my_data')
SQL> select * from myview;

You can also use dbms_session.set_context in 8i and up to
greatly increase the namespace/number of parameters you can pass
and reference via the SYS_CONTEXT() function.

2) that is not a bug -- it is the way it is designed to work.
You cannot truncate a table upon which there are ACTIVE foreign
keys referencing the table.  You could have disabled the foreign
keys, trunced the table and then reenabled them.

The truncate checks no constraints, fires no triggers, etc.  It
just wipes out the table.  It has no way of knowing (and doesn't
really care) that the child tables are empty -- there could be
uncomitted rows in them it CANNOT see.  If we let you trunc a
table with child foreign keys enabled, you would end up with
orphans at some point.

next time, just disable the foreign keys, trunc and enabled them
again.

3) DDL is executed like this:

 commit;
 ddl
 commit or rollback;

there is a commit right before and after.  Consider DDL to be a
commit.  If that is not desired, use an automous transaction and
execute the DDL in a subtransaction.  See

http://osi.oracle.com/~tkyte/autonomous/index.html
for details.
Review & Followup

      Rating: 3
Parameterized
      view  October 03, 2002
Reviewer:  A reader
       from BG
Tom,
    Would using such a view benefit performance in any manner? Can you please
explain.

regards

          Followup:

It lets you push a predicate down into the view which can increase performance
in some cases, yes.

That would be one reason for using the technique.

      Rating: 4
SYS_CONTEXT and
      DB_LINK  December 19, 2002
Reviewer:  Robert
       from PA
Tom,
I had a param. view that uses SYS_CONTEXT like this :

SELECT .....
FROM LMSD75  <--- BIG TABLE
WHERE Call_Date BETWEEN To_Date(substr(SYS_CONTEXT
('userenv','client_info'),5,11),'dd-mon-yyyy')
......

worked great until DBA moved the big table to another
database and created db-link and synonym for this big table....that blew my
app....

this WAS the plan:
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=11 Card=3 Bytes=105)
   1    0   SORT (GROUP BY) (Cost=11 Card=3 Bytes=105)
   2    1     FILTER
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'LMSD75' (Cost=6 Card=3
Bytes=105)
   4    3         INDEX (RANGE SCAN) OF 'CL_SCR_CLDT_LMSD75INDX' (NON-UNIQUE)
(Cost=4 Card           =3)

this is the PLAN now:
Execution Plan
----------------------------------------------------------
          0
SELECT STATEMENT Optimizer=CHOOSE

          1                  0
  SORT (GROUP BY)

          2                  1
    FILTER

          3                  2
      REMOTE*
PUB_LMS1_TO_CORP.WORLD

          3
SERIAL_FROM_REMOTE
SELECT "CALL_DATE","SCRIPT","CLIENT","RESPONSE" FROM "LMS"."LMS_D75" "A1" WHERE
"SCRIPT"='YD2'

============

So is it a fact that because the table was re-located to
another db, index and SYSCONTEXT no longer works together ?

Thanks

          Followup:

they work -- what didn't work?

And why wouldn't you expect huge changes in response times by taking a really
big table and simply "moving it".

Another question: when you did this in your test environment, not in production,
what were the results there?

      Rating: 4
SYS_CONTEXT and
      DB_Link  December 20, 2002
Reviewer:  Robert
       from PA
>> they work -- what didn't work?
>>And why wouldn't you expect huge changes in response times by taking a really
big table and simply "moving it".

Well, the server runs the SYS_CONTEXT SQL I showed above , didn't complain...But
it's apparently
full-scanning the 75-mil-row table, I never got the result
but the hour glass...

When I replaced the SYS_CONTEXT call with actual values
the query ran just fine.

>> Another question: when you did this in your test
>> environment, not in production, what were the results there?

DBA moved the table in production only and did not tell the world so I was
totally baffled - Good in dev , hour glass in production - until I trace the sql
and the plan does not show index access as you can see...

So Tom, can you confirm this (CBO) :
     select ... from emp@RemoteDB where empno = sys_context(...)
(where sys_context(...) resolves to '12345' in the current session)
is NOT the same as
     select ... from emp@RemoteDB where empno = '12345'

that only the 2nd will do a index scan ?

Also do you mean that one should expect "huge changes" in response time - either
way - when a big table is moved ?

Thanks

          Followup:

It worked -- it just ran slower.

That DBA should be fired, or at least taken out of control -- put in a
subordinate position with someone in charge of them for a while.  "Oh, I think
I'll just move this table today" -- arg...

the sys_context is local to the session.  it isn't visible on the remote site.
the table comes back and then the predicate is applied.

In this case -- if the table must reside on the other system -- you will want to
use a traditional BIND VARIABLE

  select ... from emp@RemoteDB where empno = :N

that'll work as before.  If you cannot bind, you can always use
cursor_sharing=force / cursor_sharing = exact before/after the statement to
avoid trashing the shared pool.

You should expect huge (possible) changes in response times whenever you make a
fundemental change (like moving a huge table)....  either way.  for example, if
the dba moves this table back -- it'll be like setting "fast=true".

      Rating: 5
An
      alternative.  October 22, 2003
Reviewer:
      Kashif  from Reston, VA
Hi Tom,

I had a similar problem to Robert's problem above where I needed to select data
from a remote database view, but needed an additional predicate evaluated before
returning the results.

As an alternate to the solution you mentioned above, I set the context on the
REMOTE site, and then executed the remote view in the local DB. For example:

REMOTE DB:

test_schema@REMOTEDB> create view myview
  2  as select * from dual
  3  where sys_context ('userenv', 'client_info') = 1
  4  /

View created.

test_schema@REMOTEDB> grant select on myview to public
  2  /

Grant succeeded.

On LOCAL DB:

kash@DEV> exec dbms_application_info.set_client_info@REMOTEDB(1)

PL/SQL procedure successfully completed.

kash@DEV> select DUMMY from test_schema.myview@REMOTEDB
  2  /

DUMMY
-----
X

Eventually I didn't need to do this since the requirements changed, but I was
wondering if the method I outlined above is a viable alternative to your
solution of using bind variables on the local DB, and what the pros and cons
might be. Thanks in advance.

Kashif

          Followup:

it is viable but a bind would require less round trips

October 22, 2003 Jan -- Thanks for the question regarding "Schema
      updates in 24/7 environment", version 8.1.7

      You Asked
Tom,

how do 24/7 shops handle schema/application updates? Reason for asking, a
project manager asked me to think about a way to make schema updates without
stopping user access to the database. This by making a copy of the current
schema, apply changes to the copy, then synq up the schemas and switch the user
over to the new updated schema. This all under the motto: "This application
can't handle downtime. We are 24/7!"
I'm wondering how 24/7 shops handle this. I rather take a 15 or 30 minutes
outage myself :-)

jan

      and we said...
See

http://download-east.oracle.com/otndoc/oracle9i/901_doc/server.901/a90117/tables.htm#12458
for the 9i answer.  There is a brief moment where an exclusive table lock is
mandatory but other then that, you can redefine online.

So -- I have a question for you as well -- if you update the schema (say, add a
column) don't you need to update the CODE as well.  In order to do that, you
will have to stop using the code (not possible to update code currently in
action).  I see some downtime in your future for application updates.
Review &
      Followup

      Rating: 4
Changing
      code...  April 09, 2002
Reviewer:  Jan
       from Houston, TX
Yes, the code will change as well. So in the project manager's view there would
be a *short* outage while the application server is being switched from the one
with the old code to the one with the new code.
My problem however is the database. What for example if mandatory fields are
added, or constraints that change... We would have to write code that pulls over
data and transforms it. Or replay the transactions that have come in... Trying
to get to the point where the two schemas are in sync again as far as the data
is concerned. Then cut off the "old" appserver and switch over to the new one.
And not lose more than the currently uncommitted transactions...
My initial reaction is "nightmare"!

          Followup:

That is what 9i with dbms_redefinition is all about -- it does that sync'ing for
you.

      Rating:
      5
Interesting...  April 09, 2002
Reviewer:
      Jan  from Houston, TX
Very interesting. Another reason to upgrade to 9i :-)

      Rating: 5
Upgrading an 8i
      (24x7) db to 9i with no downtime  October 22, 2003

Reviewer:  Logan Palanisamy  from Santa Clara, CA,
      USA
Tom,

Ours is a 24x7 e-commerce shop. Can't afford any downtime. Currently we are on
8i and would like to upgrade to 9iR2.

Is there anyway to do the upgrade without downtime? I don't think so. Just want
to double check with you.

          Followup:

there will be some amount of downtime, you can minimize it -- but there will be
some.

10g will support rolling upgrades using data guard -- meaning we will be able to
virtually eliminate downtime during an upgrade.

      Rating: 2
URL gives a
      404  October 23, 2003
Reviewer:  Andy Todd
       from London
Tom,

Thanks for the answer, but because the URL you mentioned now gives a 404 I had
to search for quite a while to find the actual feature.

I believe what you were talking about is the DBMS_REDEFINITION package which was
introduced in Oracle 9i and discussed in the  PL/SQL supplied packages and
Administrators Guide (in a section entitled "Managing Tables")

October 22, 2003 Venkateswara Reddy -- Thanks for the question
      regarding "Exportng Tables", version 8.15

      You Asked
Dear Tom

I am egarly waiting for you since 2 weeks. I din't get u. Today I am lucky.

1. I want export certain tables instead of exporting all tables. Out of 200
tables I want 25 tables starting with 'appr%'. As well as my procedures ,
functions and packages.

How can I do that.

Thanks

Reddy

      and we said...
Well, you can export the TABLES easily enough.  Just create a parameter file
using:

  1  select decode( rownum, 1, 'tables=(', ',' ), table_name
  2  from user_tables
  3  where table_name like 'E%'
  4  union all
  5  select ')', null
  6* from dual
scott@ORA8I.WORLD> /

DECODE(R TABLE_NAME
-------- ------------------------------
tables=( E
,        EMP
,        EMP2
,        EMPLOYEE
,        EMP_DEMO
,        EMP_DEPT
,        EMP_SNAPSHOT
)

(spool that to a file) and use partfile= on the EXP command line.

To get all of the procedures -- thats a bit trickier, export wasn't designed to
do that.  You can use this utility:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:743825617527
to accomplish that.
Review & Followup

      Rating:
      5
oops  February 10, 2002
Reviewer:
      Yogeeraj  from Mauritius
Typying mistake!! well i happens to everyone...;)
I guess it should have been "parfile"

Great example.
Cheers

===========================================================
exp help=y

Export: Release 8.1.7.0.0 - Production on Mon Feb 11 08:04:44 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

You can let Export prompt you for parameters by entering the EXP
command followed by your username/password:

     Example: EXP SCOTT/TIGER

Or, you can control how Export runs by entering the EXP command followed
by various arguments. To specify parameters, you use keywords:

     Format:  EXP KEYWORD=value or KEYWORD=(value1,value2,...,valueN)
     Example: EXP SCOTT/TIGER GRANTS=Y TABLES=(EMP,DEPT,MGR)
               or TABLES=(T1:P1,T1:P2), if T1 is partitioned table

USERID must be the first parameter on the command line.

Keyword  Description (Default)        Keyword      Description (Default)
----------------------------------------------------------------------
USERID   username/password            FULL         export entire file (N)
BUFFER   size of data buffer          OWNER        list of owner usernames
FILE     output files (EXPDAT.DMP)    TABLES       list of table names
COMPRESS import into one extent (Y)   RECORDLENGTH length of IO record
GRANTS   export grants (Y)            INCTYPE      incremental export type
INDEXES  export indexes (Y)           RECORD       track incr. export (Y)
ROWS     export data rows (Y)         PARFILE      parameter filename
CONSTRAINTS export constraints (Y)    CONSISTENT   cross-table consistency
LOG      log file of screen output    STATISTICS   analyze objects (ESTIMATE)
DIRECT   direct path (N)              TRIGGERS     export triggers (Y)
FEEDBACK display progress every x rows (0)
FILESIZE maximum size of each dump file
QUERY    select clause used to export a subset of a table
VOLSIZE  number of bytes to write to each tape volume

The following keywords only apply to transportable tablespaces
TRANSPORT_TABLESPACE export transportable tablespace metadata (N)
TABLESPACES list of tablespaces to transport

Export terminated successfully without warnings.
======================================================

      Rating: 5
can i export
      only the table data and import to another user  August 07,
      2002
Reviewer:  john
tom,
can i export only the table data and import to another user

i don't want to export constraints,triggers,grants and indxes to be exported and
then imported to the other user.

thanks
john

          Followup:

Just use the sqlplus copy command for that or CREATE TABLE AS SELECT

(no, you cannot with exp/imp, it'll get the constraints, triggers etc)

      Rating: 5
Export
      options  August 07, 2002
Reviewer:  SB
       from CA
You can use the export options grants=n constraints=n indexes=n triggers=n
rows=y when you are exporting the tables and import them to another user. That
should only import the data.

          Followup:

true, copy and CTAS would be infinitely easier.

      Rating: 5
We coulkd also
      use imp with ignore=y ? after Table import?  August 08, 2002

Reviewer:  Pasko  from Germany
Hi Tom,

I think i read somewhere from your site that :

 I could export the Tables that i want using  parfile as you did above    and
then
To get proceudures and Triggers use :
exp with owner and rows=n

and then   use  imp with ignore=y

Is that correct ?

Regards,

          Followup:

That would pull the rest of the table definitions as well though (so the goal of
just getting tables that begin with E in this example would not be met)

      Rating:
      5
Exports  August 08, 2002
Reviewer:
      Harold  from Nicaragua
How can i export all the tables for 1 to 20 but i need that the table 3, 8, 12,
18 export the data on it, the rest of them no.

thanks for your time

Harold

          Followup:

You'll do two exports then, one with rows=n for tables 1,2,4,5,6,7,.... and one
with rows=y for 3,8,12,18

      Rating: 5
excluding some
      OWNER'S tables  August 09, 2002
Reviewer:
      atul  from india
Sir,

We take full export of our database daily..
Some accounts are test accounts,we actually don't want to include those accounts
into our daily process..

so can it be possible by mofifying your script like..
----------------------------------------------------------
select decode( rownum, 1, 'tables=(', ',' ), OWNER|| '.'||table_name from
dba_tables
       where owner not in ('DEVLOPMENT')
        union all
     select ')', null from dual
-----------------------------------------------------------

so i can exclude development user from daily export..

will that work?
will that create any problems at full import??

Thanks.
atul

          Followup:

export is NOT a backup tool (not a very good one anyway).  hopefully you are not
using it for that!

I would do USER level exports.  I might do this:

set heading off
set feedback off
spool myscript.sh
select 'exp userid=/ owner=' || username || 'file=' || username || '.dmp &'
  from all_users
 where username not in ( 'SYS', 'SYSTEM', ...., 'DEVLOPMENT' );
spool off
host chmod a+x myscript.sh
host myscript.sh

      Rating: 5
Missing
      records  October 08, 2002
Reviewer:  mo
       from Reston, USA
I have a table with 20,399 rows in this table in INSP, but if I tried to export
the table I could export only 20,186 rows. I imported these rows into the same
table in INSD, where I found that all the rows with PODATE > ‘9/27/2002’ are not
being exported/imported.

These are also sometimes were missing from the display in a dropdown list in an
html form. The sql query shows all of the rows up to 10/7/02.

Any ideas? We are thinking about dropping and recreating the table in INSP but
don’t want to lose these rows in the process.

          Followup:

what you describe is not really possible.

if podate is a date, please don't compare it to a string.

Do this (cut and paste)

SQL> select count(*) from T;
SQL> host exp userid=u/p tables=T

and cut and paste the entire response.

      Rating:
      5
  October 08, 2002
Reviewer:  mo

Tom:

I found that instance has been upgraded from 8.1.6 to 8.1.7 after sept 27 and it
seems to me it has to do with a date format.

how would you check for the data stored foramt after the 27.

Thanks

          Followup:

use TO_DATE on strings with an EXPLICIT format (i think we've had this
discussion a couple of times)

use TO_CHAR on dates with an EXPLICIT format.

never rely on the "default"

      Rating:
      5
export  October 08, 2002
Reviewer:
      mo
Tom:

Here are the results per your query. It seems that for some reason sometimes we
have a problem displaying records after 09/27/02 and exportin it too.  An
upgrade happened on that day.

The query for the html form  actually does :

select date_column from table where date_column is not null order by date_column
desc;

PROD> select count(*) from req;

  COUNT(*)
----------
     20460

PROD> host exp=ins/ins tables=req;

PROD> set serveroutput on;

PROD> host exp=picadmin/picadmin tables=req;

          Followup:

Umm, you missed my point on what I wanted.  You must be using that "gui" sqlplus
-- use the command line one.

It would be relevant for me to SEE the exp output -- that is what I really
*need* to see.

Did the DBA do the upgrade "correctly" -- following all of the requisite steps
in the manuals.

And -- this is important -- you do have a TAR with support filed?  right?

      Rating: 5
export
      followup  October 08, 2002
Reviewer:  mo

Tom:

sorry here is the result of the export

PROD> host exp ins/ins tables=req;

Export: Release 8.1.6.0.0 - Production on Tue Oct 8 11:36:49 2002

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.2.0 - 64bit Production
With the Partitioning option
JServer Release 8.1.7.2.0 - 64bit Production
Export done in US7ASCII character set and WE8ISO8859P1 NCHAR character set
server uses WE8ISO8859P1 character set (possible charset conversion)

About to export specified tables via Conventional Path ...
. . exporting table                      REQ
      20460 rows exported
Export terminated successfully without warnings.

          Followup:

that export pulled every record -- i fail to see the problem?  (other then why
are you using the 816 export if you have 817 now?)

      Rating:
      5
export  October 08, 2002
Reviewer:
      mo
Tom:

I found that the 8.1.7 database was using WE (Western European) character set.
The 8.1.6 was using the ASCII7 character set but was switiced to WE later.

Would this affect date queries in any way.

          Followup:

nope.  dates are 7byte binary data regardless of the character set.

      Rating:
      5
export  October 08, 2002
Reviewer:
      mo
Tom:

You are right about export but why do you think a sql query would show all dates
while in an html the same query will show only records up to 9/27/02. do you
think it has to do with the browser.  The weird thing is that sometimes when you
do multiple refreshes the html form will show all records include up today.  Any
hints?

The query for the html form  actually does :

select date_column from table where date_column is not null order by date_column
desc;

          Followup:

You have a bug in your code.  Perhaps you aborted (stopped) the page and it
showed you what you got (20,000+ items in a list -- i sort of question that) .

Or the OS just didn't feel like using the resources to show you 20+K items in a
list due to resources.

You should look at the RAW html (view source) to see what is there.

But -- 20+K items in a list - totally useless don't you think? I would hate a
page like that hitting my browser, would never use that application again (and
would question the sanity of the person writing such a page!)

      Rating:
      5
export  October 08, 2002
Reviewer:
      mo
Tom:

as a followup here are the same query run in unix and windows with two different
results:

  1   SELECT DISTINCT po_date
  2          FROM req
  3         WHERE  po_date IS NOT NULL
  4*        ORDER BY po_date DESC
INSP> /

PO_DATE
---------
27-SEP-02
26-SEP-02
25-SEP-02
24-SEP-02
23-SEP-02
20-SEP-02
19-SEP-02
18-SEP-02
17-SEP-02
16-SEP-02
13-SEP-02

QACOMPDT
---------
12-SEP-02

In unix :

  1   SELECT DISTINCT po_date
  2          FROM req
  3         WHERE  po_date IS NOT NULL
  4*        ORDER BY po_date DESC
PROD> /

what could be happening.
PO_DATE
----------
10/07/2002
10/04/2002
10/03/2002
10/02/2002
10/01/2002
09/30/2002
09/27/2002
09/26/2002
09/25/2002
09/24/2002
09/23/2002
^C
09/20/2002

13 rows selected.

          Followup:

perhaps you are connecting to two entirely different databases -- I mean -- look
at that default date format, they are DIFFERNENT.  Could be different databases.

Look at the output -- one goes from sep-20 to oct-07, the other from 12-sep to
27-sep.

Slow down, take your time, double check things.  query v$database, make sure you
are in the same exact instance.

      Rating:
      5
export  October 08, 2002
Reviewer:
      mo
Tom:

As usual you are incredibly right. The DBA had two instances running, 8.1.6 and
8.1.7.  Apparently the application server was still pointing to the 8.1.6 to
show the data.  However I do not understand how users manage to enter data into
the 8.1.7 through the web unless the DAD was sometimes pinting to 8.1.6 and
sometimes to 8.1.7.  Can this happen?

2.  Strangely, the DBA said he did not update the DAD when he upgraded from
8.1.6 to 8.1.7.  Now we are using 8i IAS but from what I remember of OAS 4.0 you
enter the unix physical directory path into the DAD and you enter the character
set. IF he did change both without updating the DAD is not there something
wrong? Unless the database upgrade takes care of this?

3.  How does SQL*PLUS GUI for windows connect to the unix server. Is it via the
internet or NET8 connection.  IS this different than connecting through SQL*PLUS
in unix on the same server?

Thanks a lot, you are great help.

          Followup:

1) no, it cannot "sometimes" happen.

2) there would be nothing wrong.  A DAD just describes "how to connect".
matters not the version you are connecting to.

btw: DBA didn't technically do an upgrade -- DBA did a copy to a new instance.
An upgrade would have prevented this - there would be no 816 database to confuse
you with.

Sounds like you need some "configuration managment" perhaps -- very shaky place
your working in.

3) It connects over Oracle net.

      Rating:
      5
export  October 08, 2002
Reviewer:
      mo
Tom:

Is it because he has two instances or databases with the same name that the DAD
was getting confused? Once going to one database, and other times going to
second one.

2.  I thought you do tell the DAD what the physcial and virtual path are?

Thanks,

          Followup:

No idea what happened to you -- unless he was sometimes having 816 up and other
times 817.

A DAD just has

o oracle home
o oracle sid OR tns connect string
o optionally user/password

It is very straightforward -- same sort of info you type in at sqlplus.  It is
as PREDICABLE as sqlplus.

Your environment however is so shaky -- I won't even speculate what magic things
might have been happening.  Needs a little bit of "control" don't you think?

      Rating: 4
Regarding
      Tablespace definitions during import.  October 08, 2002

Reviewer:  Nathan  from Singapore
Hi Tom..
 I want to import all users and its objects from Prod db to Test Db and
maintain same tablespace definitions in Test Db for all objects
Is it works?.
I am going to follow the steps below
Create all tablespaces and all users(with proper tablespace defintions as in
prod) ,and do import..
expecting your reply
thanks in advance
Nathan

          Followup:

that'll work

      Rating:
      5
Puzzle  October 09, 2002
Reviewer:
      atul  from india
Sir,

I'm in very interesting situation

1)i have oracle 8 running on one server
2)I have created oracle 7 on other server.
3)Two servers are physically very far from each other
4)i want to export oracle 8 data to oracle 7..
5)I know its possible only if i run catexp7 in oracle 8
and then take export..and again import to 7
6)But how can i do this??
7)Can i transfer catexp7 through mail and tell it to run there..
8)Or can this be possible in other ways...

Plz let me know??

Thanks.
atul

          Followup:

You need to have access to the Oracle8 database from somewhere where you have
the Oracle7 software installed to use exp/imp.

You would run catexp7.sql in the 8 database (it is in the
$ORACLE_HOME/rdbms/admin directory of the 8.x software already)

You NEED to use the version 7.x EXP tool in order to create a dmp file that the
7.x IMP tool can read.  YOU NEED to have the v7 software accessible to the 8.x
instance.

Else, it'll just not work at all.

      Rating:
      5
export  November 04, 2002
Reviewer:
      mo
Tom:

1.  I want to copy 4 tables from a prodcution instance into a test instance.
Would you do an export/import or insert into t (select * from t@prod).

2.  Any advantages/disadvantage for either approach.

3.  Wiht import/export do you have to drop tables one by one in test before you
can do import.

4.  The 4 tables have other tables linked to them using foreign keys.  I tried
doropping the tables and it said it can't because of the foreign keys?

5.  If I want to mirror production (all objects) into test what is the easiest
way to do this?

Thank you,

          Followup:

1) i would probably use the sqlplus copy command actually.  exp/imp would be
number two. CREATE TABLE T AS SELECT * FROM T@PROD would be three

2) see #1

3) ignore=y   makes it so you don't have to (but they should be empty of course)

4) don't drop them then?

5) backup prod, restore to test.

      Rating:
      5
exp  December 11, 2002
Reviewer:
      atul  from India
Sir,

I have two databases...connected to each other through db link..

Can i export one schema of second database from first database through
db-link??? how???

Thanks.
atul

          Followup:

not through a database link.  You just use exp/imp from the command line to do
this.

      Rating: 4
Getting less
      rows without any warnings  March 12, 2003

Reviewer:  Gururaj Kulkarni  from Hongkong

Tom,

I need to export a file from production. Table has 4428718 rows in it and it is
growing slowly. When I export the table, I am getting 4163322 rows. No warnings
and says Export terminated successfully without warnings.

I am using parameter file, which I have attached here.

userid=/
direct=y
compress=n
rows=y
buffer=15000000
indexes=n
triggers=y
file=sas_nice.dmp
log=sas_nice.log
tables=
ops$nice.SAS_SUBSCRIBER_ADD_SERVICE

My exp command is like this

exp userid=ops$itguru/password@nicedb.nwt.com parfile=exp.par

Oracle database version is 8.1.7.4
O.S is open VMS Version 7.2

I have tried this atleast 3-4 times. Each time source has different numbers but
my dump file will have always 4163322 rows. Could you tell me what is the reason
and how to overcome it ?

Thanks
-Gururaj

          Followup:

never heard of such a situation.

Well, the only time was when the person was exporting from a different database
then they were counting against. (so, can you check that out...)

if it persists, time to call support.

      Rating: 4
Export
      Failure  June 16, 2003
Reviewer:  Jihad
      Darwish  from Jordan
Dear Sir,

I'm trying to export tables from database using oracle 7 and the databases was
developed but installed on oracle 8i. It didn't work at all and I had the
following errors

export trminated successfully
oracle error 1436 encountered
connext by loop in user

      Rating: 3
automatic
      script after import  October 22, 2003
Reviewer:
      Marcio  from br
Is it possible run a script (such update) automatic from import? Sample:
You have a table aid_emp (empno, new_ename) into db_export.dmp and you want to
run automatic, without be submitted.
update ( select ename, new_ename
           from emp t1, aid_emp t2
          where t1.empno = t2.empno )
   set ename = new_ename
/
drop table aid_emp
/

          Followup:

nope

October 22, 2003 Stanley -- Thanks for the question regarding "db
      cloning -- what is it and why", version 8.0.4

      You Asked
What is Database cloning?
What are the procedures involved in cloning a database?
What are the advantages & uses?

      and we said...
cloning is as it sounds -- making a copy of.  It is basically
taking a backup of an instance and restoring it elsewhere.
You've "cloned" the database.

Procedures are:  backup, restore it elsewhere (typically on
another machine in exactly the same directory structure but you
can restore it onto the same machine, you'll want to change the
sid and database name, see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:318216852435
for how to do that)

Advantages and uses -- people use it to test with mostly.  You
clone a production instance onto a test machine to try out new
things -- "what if I changed this init.ora parameter", "what if
my code did this instead" and so on...
Review &
      Followup

      Rating: 5
unable to
      clone  July 19, 2002
Reviewer:  Ravi Kumar
       from Chicago, IL USA
Hi Tom,

I am trying to clone one of my database and getting errors. I am on WIN2K and
Oracle 8.1.7. I am giving here the steps that I followed.

. On the source machine, I stopped the services.
. Zipped and copied d:\oracle\admin and d:\oracle\oradata folders to the target
machine.
. Unzipped the files to c:\oracle\admin and c:\oracle\oradata folders. ( Oracle
is installed in C on target machine)
. I tried to start the service and I got this error.
ORA-27101 shared memory realm does not exist
. Later I realized the variation in C: and D: drives. So, I edited the init.ora
file to replaced d: with c: for control files and also dump files.
. After restarting my machine and try to connect, this time I am getting
ORA-01033: ORACLE initialization or shutdown in progress.
. I copied my original init.ora file (install version) to
c:\oracle\admin\<SID>\pfile and still getting the same error.

I have only one version of oracle installed and only one DB on both the
machines.

In your previous answer I noticed you mentioning "exactly the same directory
structure". Is that what I am messing in this situation? If so, what should I
do?

Please help.

Thanks,
Ravi.

          Followup:

did you create a database on the other machine?  have you read about oradim and
how to use it to register an instance on the other machine?  I don't see that
step in there.  Refer to the NT admin guide

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/win.817/a73008/toc.htm
...

      Rating: 3
checked log
      files  July 19, 2002
Reviewer:  Ravi Kumar
       from Chicago, IL USA
Hi Tom,

On the target machine, as part of the installation, I created the database.
Backedup the files. I am able to start the database using those files.

I checked c:\oracle\ora817\database\oradim.log and it showed
ORA-01991: invalid password file 'C:\Oracle\Ora817\DATABASE\PWDeqed.ORA'

I copied the same file from source machine to the target machine. After
restarting I checked the same file and now it is showing
ORA-01157: cannot identify/lock data file 1 - see DBWR trace file
ORA-01110: data file 1: 'D:\ORACLE\ORADATA\<sid>\SYSTEM01.DBF'

It shows that it is still looking at d:\oracle\oradata\ folder where as I don't
have D: drive but only C:
Does it store the hardcoded paths somewhere?

Thanks,
Ravi.

          Followup:

erase the password file, create a new one using orapwd.

      Rating: 1
password
      file  March 20, 2003
Reviewer:  abid dawood
       from pakistan
please tell me how i create passwrod file .
i have very difficulty in it . thanks. when i going to create database it give
the error of password file

          Followup:

orapwd

read about it in your OS admin guide.

      Rating: 2
database
      creation (oracle 9i)  March 20, 2003
Reviewer:
      abid dawood  from pakistan
  I am faceing problem in the creation of New database.
here i write the proper way of New database creation .and i request you to check
my staps  and make correction in it .
First create forder on ":\kar_dba"
(1) copy parameter file in c:\kar_dba
(2) open copy of parameter file and write database name=pak and give names and
path of control files.
(3)startup oracle in nomount through pfile=c:\kar_dba\init.ora
(4) create database pak
datafile
'c:\kar_dba\ka_db1.dbf ' size 20m
logfile
group 1 'c:\kar_dba\ka_lo1.log' size 10m,
group 2 'c:\kar_dba\ka_lo2.log' size 10m;

(error) when i execute my database creation query .It create control files,log
files and in the end give me error that password file is not found. how i add
password file .

          Followup:

step 1

read your OS admin guide

step 2

refer to question right above.

      Rating: 5
feedback
      requested  March 30, 2003
Reviewer:  Saminathan
      Seerangan  from CA,USA
Hi Tom,

I always find asktom to be the most useful source...

I went thru your document for cloning DB at

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:318216852435
I'm getting the following error in (step # 12. Run the "ccf.sql" script )

My old DB name is SAMI
New DB name is CLONEDB

ORA-01503: CREATE CONTROLFILE failed
ORA-01161: database name SAMI in file header does not match given name of
CLONEDB
ORA-01110: Data file 1: '/oracle/oradata/clonedb/system01.dbf'

Could you please help me to resolve this issue.

          Followup:

sounds like you skipped #7 or #10.

      Rating: 5
Sorry,
      overlooked step # 7  March 31, 2003
Reviewer:
      Saminathan Seerangan  from CA,USA
I have changed
a) "olddbname" to "newdbname"
b) "NORESETLOGS" to "RESETLOGS"

but did not change
c) REUSE DATABASE to SET DATABASE

It is working now, Thanks Tom

      Rating: 4
New Server
      Replication  October 22, 2003
Reviewer:
      Denise  from Virginia, USA
Tom

We are buying a new server that will be a mirror image
of the current Production Server.

question:
if we create the exact same directory structure and install
the exact same OS (Windows 2000 Server)...can we simply
restore, from the Production backup tapes, all the Oracle files to the new
server. We would also import a copied
version of the registry from the Production Server to the
new server.

appreciate any feedback and thanks for the links on
how to change a SID....very ambitious and challenging.

          Followup:

i would not suggest that - no.

the registry is a big old black box.

suggest you setup server, install oracle, use oradim to setup the registry
(register the instance) and restore from backup

      Rating:
      5
  October 22, 2003
Reviewer:
      A reader
Tom,

If the same situation applies to Solaris 8.0, can i restore the file systems and
relink oracle without doing any installation??

Thanks

          Followup:

pretty much.

hey -- that would mean maybe unix is easier then windows sometimes?  say it
ain't so

I've always thought the registry was MS's attempt at copy protection.  remember
in the early days when you could just copy a directly and a .ini file and all
would be well?

I HATED getting a new laptop when I ran windows.  took me a week to get going
again (now there is an entire cottage industry built on migration of windows
from one machine to another).  Linux -- I just sort of nfs mount,

tar cvf - . | ( cd /mount/point; tar xf - )

and i be done.  sure, it looks obscure, but if you read man pages........

      Rating: 4
New Server
      Registry Installation  October 23, 2003
Reviewer:
      Denise  from Virginia, USA
Thanks Tom....

for the feedback regarding the Registry import on a new
server installation.

I oftentimes wish we were using Unix or Linux.

I will look into the oradim command.

October 23, 2003 Winston -- Thanks for the question regarding "
      look at the sql to see if it is unreasonable", version 9.2.0.1.0

      You Asked
Hi Tom,

Referring to

http://asktom.oracle.com/pls/ask/f?p=4950:61:::::P61_ID:5051675080968
I would like to know given the following sql result how can we tell if the
logical io is reasonable or not?

 select (196274+23)/499 from dual;
 393.380762 per row.

Many thanks in advance,
Winston

*********************************************************************************
**********************************

CURSOR_ID:7  LENGTH:3722  ADDRESS:9f629278  HASH_VALUE:3169663875
OPTIMIZER_GOAL:CHOOSE  USER_ID:114 (PRODUSER)

INSERT /*+ APPEND  */INTO resource_assign
SELECT :b1 "JOB_ID",DECODE(PROVINCE_STATE,'AB','AB','BC','BC','NAT') REGION,
ACT.LATEST_STATUS_EMPLOYEE1_ID "Work List",DUE_DATE "Order DD",
TRUNC(SCHEDULED_END_DATE) "Activity
DD",DECODE(INSTALLATION_TYPE,'F','Fielded','FR','Fielded','FS',
'Fielded','PF','Fielded','PFS','Fielded','PFR','Fielded','RW','Rackwork','PRW','R
ackwork','SW',
'Software','PSW','Software','Other') "Install Type Category",PURPOSE_TYPE "Order
Purpose Type",
HOLD_CODE "Hold Code",COUNT(SCHEDULED_END_DATE) "# of Activities",
COUNT(HOLD_CODE) HOLD_CODE_COUNT
FROM MY_WORKACTT ACT,MY_ORD O,MY_ORD_ITM OI
WHERE ACT.ORDER_ITEM_ID = OI.ORDER_ITEM_ID  AND OI.ORDER_ID = O.ORDER_ID  AND
((:b2 = 'true'
AND ACT.LATEST_STATUS_EMPLOYEE1_ID IN (
'TNW_LNP_AB_14DAY','TNW_LNP_AB_TODAY','TNW_LNP_AB_FUTURE',
'TNW_LNP_AB_PRIORITY','TNW_LNP_BC_14DAY','TNW_LNP_BC_TODAY','TNW_LNP_BC_FUTURE',
'TNW_LNP_BC_PRIORITY','TNW_LNP_NAT_14DAY','TNW_LNP_NAT_TODAY','TNW_LNP_NAT_FUTURE
',
'TNW_LNP_NAT_PRIORITY'  )) OR (:b3 = 'true'
AND ACT.LATEST_STATUS_EMPLOYEE1_ID IN ( 'TNW_ADSL_AB_14DAY','TNW_ADSL_AB_TODAY',
'TNW_ADSL_AB_FUTURE','TNW_ADSL_AB_PRIORITY','TNW_ADSL_BC_14DAY','TNW_ADSL_BC_TODA
Y',
'TNW_ADSL_BC_FUTURE','TNW_ADSL_BC_PRIORITY','TNW_ADSL_NAT_14DAY','TNW_ADSL_NAT_TO
DAY',
'TNW_ADSL_NAT_FUTURE','TNW_ADSL_NAT_PRIORITY'  )) OR (:b4 = 'true'
AND ACT.LATEST_STATUS_EMPLOYEE1_ID IN (
'TNW_SIMPLEPOTS_AB_14DAY','TNW_SIMPLEPOTS_AB_TODAY',
'TNW_SIMPLEPOTS_AB_FUTURE','TNW_SIMPLEPOTS_AB_PRIORITY','TNW_SIMPLEPOTS_BC_14DAY'
,
'TNW_SIMPLEPOTS_BC_TODAY','TNW_SIMPLEPOTS_BC_FUTURE','TNW_SIMPLEPOTS_BC_PRIORITY'
,
'TNW_SIMPLEPOTS_NAT_14DAY','TNW_SIMPLEPOTS_NAT_TODAY','TNW_SIMPLEPOTS_NAT_FUTURE'
,
'TNW_SIMPLEPOTS_NAT_PRIORITY'  )) OR (:b5 = 'true'
AND ACT.LATEST_STATUS_EMPLOYEE1_ID IN (
'TNW_COMPLEXPOTS_AB_14DAY','TNW_COMPLEXPOTS_AB_TODAY',
'TNW_COMPLEXPOTS_AB_FUTURE','TNW_COMPLEXPOTS_AB_PRIORITY','TNW_COMPLEXPOTS_BC_14D
AY',
'TNW_COMPLEXPOTS_BC_TODAY','TNW_COMPLEXPOTS_BC_FUTURE','TNW_COMPLEXPOTS_BC_PRIORI
TY',
'TNW_COMPLEXPOTS_NAT_14DAY','TNW_COMPLEXPOTS_NAT_TODAY','TNW_COMPLEXPOTS_NAT_FUTU
RE',
'TNW_COMPLEXPOTS_NAT_PRIORITY'  )) OR (:b6 = 'true'
AND ACT.LATEST_STATUS_EMPLOYEE1_ID IN (
'TNW_CENTREX_AB_14DAY','TNW_CENTREX_AB_TODAY',
'TNW_CENTREX_AB_FUTURE','TNW_CENTREX_AB_PRIORITY','TNW_CENTREX_BC_14DAY','TNW_CEN
TREX_BC_TODAY',
'TNW_CENTREX_BC_FUTURE','TNW_CENTREX_BC_PRIORITY','TNW_CENTREX_NAT_14DAY','TNW_CE
NTREX_NAT_TODAY',
'TNW_CENTREX_NAT_FUTURE','TNW_CENTREX_NAT_PRIORITY'  )) OR (:b7 = 'true'
AND ACT.LATEST_STATUS_EMPLOYEE1_ID IN (
'TNW_RULEBREAKERS_AB_TODAY','TNW_RULEBREAKERS_AB_PRIORITY',
'TNW_RULEBREAKERS_BC_TODAY','TNW_RULEBREAKERS_BC_PRIORITY','TNW_RULEBREAKERS_NAT_
TODAY'  ))
OR (:b8 NOT IN ( 'None'  ) AND ACT.LATEST_STATUS_EMPLOYEE1_ID = :b8 ))
AND ((:b10 = 'ORDERDUEDATE'  AND OI.LATEST_DUE_DATE BETWEEN :b11
AND :b12 ) OR (:b10 = 'ACTIVITYDUEDATE'  AND ACT.SCHEDULED_END_DATE BETWEEN :b11
AND :b12 ))
AND (:b16 = 'ALL'  OR (:b16 = 'Fielded'
AND OI.INSTALLATION_TYPE IN ( 'F','FR','FS','PF','PFS','PFR'  )) OR (:b16 =
'Rackwork'
AND OI.INSTALLATION_TYPE IN ( 'RW','PRW'  )) OR (:b16 = 'Software'
AND OI.INSTALLATION_TYPE IN ( 'SW','PSW'  )) OR (:b16 = 'Other'
AND OI.INSTALLATION_TYPE NOT IN (
'F','FR','FS','PF','PFS','PFR','RW','PRW','SW','PSW'  )))
AND ACT.LATEST_STATUS IN (
'21006','21007','21008','21009','21013','21021','21022'  )
AND (:b21 = 'ALL'  OR O.PURPOSE_TYPE = :b21 ) AND (:b23 = 'ALL'  OR OI.HOLD_CODE
= :b23 )
AND (:b25 = 'ALL'  OR ACT.ACTIVITY_NAME = :b25 )
AND (:b27 = 'ALL'  OR OI.PROVINCE_STATE = :b27 )
GROUP BY
DECODE(OI.PROVINCE_STATE,'AB','AB','BC','BC','NAT'),ACT.LATEST_STATUS_EMPLOYEE1_I
D,
O.DUE_DATE,TRUNC(ACT.SCHEDULED_END_DATE),OI.INSTALLATION_TYPE,O.PURPOSE_TYPE,OI.H
OLD_CODE

call        count       cpu   elapsed         disk        query      current
    rows    misses
------- --------- --------- --------- ------------ ------------ ------------
------------ ---------
Parse           1      0.00      0.00            0            0            0
       0         0
Execute         1    149.57    149.69        88561       196274           23
     499         0
------- --------- --------- --------- ------------ ------------ ------------
------------ ---------
total           2    149.57    149.69        88561       196274           23
     499         0

|         Rows Row Source Operation
| ------------ ---------------------------------------------------
|            1 LOAD AS SELECT
|          499 .SORT GROUP BY
|         1873 ..NESTED LOOPS
|         1874 ...NESTED LOOPS
|        31274 ....TABLE ACCESS FULL MY_WORKACTT
|        33146 ....TABLE ACCESS BY INDEX ROWID MY_ORD_ITM
|        62546 .....INDEX UNIQUE SCAN SPORD_ITM_PK
|         1873 ...TABLE ACCESS BY INDEX ROWID MY_ORD
|         3746 ....INDEX UNIQUE SCAN ORD_PKSP

Explain Plan
---------------------------------------------------------------
..10 INSERT STATEMENT SEARCH_COLUMNS=  (COST=9363 CARD=1 BYTES=92 )
...9 .LOAD AS SELECT SEARCH_COLUMNS=
...8 ..SORT (GROUP BY) SEARCH_COLUMNS=  (COST=9363 CARD=1 BYTES=92 )
...7 ...NESTED LOOPS SEARCH_COLUMNS=  (COST=9355 CARD=1 BYTES=92 )
...4 ....NESTED LOOPS SEARCH_COLUMNS=  (COST=9353 CARD=1 BYTES=74 )
...1 .....TABLE ACCESS (FULL) OF 'produser.MY_WORKACTT' SEARCH_COLUMNS=
(COST=9313 CARD=20 BYTES=980 )
...3 .....TABLE ACCESS (BY INDEX ROWID) OF 'produser.MY_ORD_ITM' SEARCH_COLUMNS=
 (COST=2 CARD=986 BYTES=24650 )
...2 ......INDEX (UNIQUE SCAN) OF 'produser.SPORD_ITM_PK' (UNIQUE)
SEARCH_COLUMNS=  (COST=1 CARD=986 )
...6 ....TABLE ACCESS (BY INDEX ROWID) OF 'produser.MY_ORD' SEARCH_COLUMNS=
(COST=2 CARD=653953 BYTES=11771154 )
...5 .....INDEX (UNIQUE SCAN) OF 'produser.ORD_PKSP' (UNIQUE) SEARCH_COLUMNS=
(COST=1 CARD=653953 )

OWNER.TABLE_NAME
...owner.index_name                                     num rows     blocks
sample last analyzed date
----------------------------------------------------- ---------- ----------
---------- -------------------
produser.MY_WORKACTT.................................    6532711      96830
6532711 2003-05-21 23:31:29
...produser.act_idx2.................................    6532711      15827
6532711 2003-05-21 23:31:29
...produser.act_ix_act_nm............................    6532711      30412
6532711 2003-05-21 23:31:29
...produser.ak_key_2_sp_activ........................    6532711      35309
6532711 2003-05-21 23:31:29
...produser.latest_status............................    6532711      15485
6532711 2003-05-21 23:31:29
...produser.latest_status_employee1_id...............    5496002      19631
5496002 2003-05-21 23:31:29
...produser.wrk_act_pk...............................    6532711      14963
6532711 2003-05-21 23:31:29
produser.MY_ORD......................................    2539622      30310
2539622 2003-05-21 23:34:03
...produser.ak_ak_ord_MY_ORD.........................    2539622      11635
2539622 2003-05-21 23:34:03
...produser.ord_pksp.................................    2539622       5752
2539622 2003-05-21 23:34:03
...produser.MY_ORD_ix_appl_ord_num...................    2539622      10265
2539622 2003-05-21 23:34:03
...produser.MY_ORD_ix_cust_fll_nm....................    2539609      10508
2539609 2003-05-21 23:34:03
produser.MY_ORD_ITM..................................    2994409     110492
2994409 2003-05-21 23:34:59
...produser.orditm_pihead_idx........................    2994409      10040
2994409 2003-05-21 23:34:59
...produser.spord_itm_ix_aplctn_corrltn_ky...........    2994409       8065
2994409 2003-05-21 23:34:59
...produser.spord_itm_ix_ord_id......................    2994409       7234
2994409 2003-05-21 23:34:59
...produser.spord_itm_pk.............................    2994409       6808
2994409 2003-05-21 23:34:59
...produser.MY_ORDi_idx_cr...........................         58          1
   58 2003-05-21 23:34:59
...produser.MY_ORD_itm_ix_cor........................      27868         51
27868 2003-05-21 23:34:59

INDEX_NAME (UNIQUENESS) [indexed columns]
---------------------------------------------------------------------------------
----------------------------------
ACT_IDX2 (NONUNIQUE) [order_item_id]
ACT_IX_ACT_NM (NONUNIQUE) [activity_name]
AK_KEY_2_SP_ACTIV (UNIQUE) [process_instance_id activity_name]
LATEST_STATUS (NONUNIQUE) [latest_status]
LATEST_STATUS_EMPLOYEE1_ID (NONUNIQUE) [latest_status_employee1_id]
WRK_ACT_PK (UNIQUE) [MY_WORKACTT_id]
AK_AK_ORD_MY_ORD (UNIQUE) [application_code order_num create_date]
ORD_PKSP (UNIQUE) [order_id]
MY_ORD_IX_APPL_ORD_NUM (UNIQUE) [order_num issue_date]
MY_ORD_IX_CUST_FLL_NM (NONUNIQUE) [customer_name]
ORDITM_PIHEAD_IDX (NONUNIQUE) [process_instance_header]
SPORD_ITM_IX_APLCTN_CORRLTN_KY (UNIQUE) [application_order_item_num order_id]
SPORD_ITM_IX_ORD_ID (NONUNIQUE) [order_id]
SPORD_ITM_PK (UNIQUE) [order_item_id]
MY_ORDI_IDX_CR (NONUNIQUE) [customer_request]
MY_ORD_ITM_IX_COR (NONUNIQUE) [aplctn_corrltn_ky]

Event                                                                 Times
Count      Max.     Total    Blocks
waited on                                                            Waited Zero
Time      Wait    Waited  Accessed
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
db file scattered read (multiblock full scan)....................      9316
8930      0.01      3.86     87000
direct path write................................................         1
   0      0.62      0.62         6
db file sequential read (single block index scan)................      1561
1548      0.01      0.13      1561
control file parallel write......................................         2
   1      0.01      0.01
control file sequential read.....................................        10
   9      0.01      0.01        10
file open........................................................         5
   5      0.00      0.00
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
total............................................................     10895
10493      0.62      4.63     88577

non-idle waits...................................................     10895
10493      0.62      4.63     88577
idle waits.......................................................         0
   0      0.00      0.00

Details for wait event                                                Times
Count      Max.     Total    Blocks
'control file sequential read'                                       Waited Zero
Time      Wait    Waited  Accessed
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
datafile header for file_id: 2...................................         3
   2      0.01      0.01         3
datafile header for file_id: 0...................................         3
   3      0.00      0.00         3
datafile header for file_id: 1...................................         3
   3      0.00      0.00         3
UNKNOWN..........................................................         1
   1      0.00      0.00         1
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
total............................................................        10
   9      0.01      0.01        10

Details for wait event                                                Times
Count      Max.     Total    Blocks
'db file scattered read (multiblock full scan)'                      Waited Zero
Time      Wait    Waited  Accessed
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
produser.MY_WORKACTT.............................................      9316
8930      0.01      3.86     87000
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
total............................................................      9316
8930      0.01      3.86     87000

Details for wait event                                                Times
Count      Max.     Total    Blocks
'db file sequential read (single block index scan)'                  Waited Zero
Time      Wait    Waited  Accessed
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
produser.MY_WORKACTT.............................................      1561
1548      0.01      0.13      1561
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
total............................................................      1561
1548      0.01      0.13      1561

Details for wait event                                                Times
Count      Max.     Total    Blocks
'direct path write'                                                  Waited Zero
Time      Wait    Waited  Accessed
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
produser.resource_assign............................         1         0
0.62      0.62         6
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
total............................................................         1
   0      0.62      0.62         6

      and we said...
see, this one is tricky -- and shows why you CANNOT use a ratio.

select count(*)
 from <really nasty query here>

that is going to return "a row", one row -- no more, no less.  Can I use a ratio
of rows fetched to LIO's performed?  Nah, not at all - you have to step down a
couple of levels at that point.

Looking at the stats for this plan:

|         Rows Row Source Operation
| ------------ ---------------------------------------------------
|            1 LOAD AS SELECT
|          499 .SORT GROUP BY
|         1873 ..NESTED LOOPS
|         1874 ...NESTED LOOPS
|        31274 ....TABLE ACCESS FULL MY_WORKACTT
|        33146 ....TABLE ACCESS BY INDEX ROWID MY_ORD_ITM
|        62546 .....INDEX UNIQUE SCAN SPORD_ITM_PK
|         1873 ...TABLE ACCESS BY INDEX ROWID MY_ORD
|         3746 ....INDEX UNIQUE SCAN ORD_PKSP

Perhaps -- perhaps -- the number 1,872 is really the number you want to put in
there:

ops$tkyte@ORA920> select (196274+23)/1873 from dual;

(196274+23)/1873
----------------
      104.803524

that is the number of rows before the aggregate.

Now, in looking at the predicate, I see lots of little "tricks" in there that
could be causing the optimizer to come up with suboptimal plans easily:

WHERE ACT.ORDER_ITEM_ID = OI.ORDER_ITEM_ID
  AND OI.ORDER_ID = O.ORDER_ID
  AND (( :b2 = 'true' AND .... ))
    OR (:b3 = 'true' AND ACT.LATEST_STATUS_EMPLOYEE1_ID ....))
    OR (:b4 = 'true' ....
....
  AND (:b21 = 'ALL'  OR O.PURPOSE_TYPE = :b21 )
  AND (:b23 = 'ALL'  OR OI.HOLD_CODE .......
  AND (:b25 = 'ALL'  OR ACT.ACTIVITY_NAME = :b25 )
  AND (:b27 = 'ALL'  OR OI.PROVINCE_STATE = :b27 )

so basically, the bind values control dynamically at run time which part of the
predicates should run or not.  If :b2 isn't 'true' that "anded" part doesn't
need to run (although it MIGHT HAVE run and maybe the :b2 = 'true' part was
short circuited)....

My suggestion for this would be to dynamically build this query using the bind
variables to decide WHICH PART OF THE PREDICATE should be there or not.  In that
fashion, the optimizer gets a SPECIFIC PLAN.  One that it can optimize best.
It'll be able to choose the best indexes to use (or not use) based on the
predicate itself -- instead of coming up with the "one plan fits all" model you
have here.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279
for an example of how I might build such a statement dynamically -- you would
then just execute immediate l_insert_stmt;  at the end....

Review &
      Followup

      Rating:
      4
  May 27, 2003
Reviewer:  j.

we use the above "pattern" in conjunction with pipelined  table functions to
generate ref cursors such as:

open refCursor for
  select *
  from   table(pipelined_table_function(<some_parameters>))
  where  p_Option1 = 'TRUE'
    and  <some_conditions_for_option1>
     or  p_Option2 = 'TRUE'
    and  <some_conditions_for_option2>

the table functions just generate series of consecutive number or date values
(1, 2, 3, ... or 01.01.2003, 02.01.2003, ....) and the options specified serve
as (optional) filters for the data generated.

should we avoid this although there is NO ACCESS made to any database table at
all (no chance for the optimizer to get confused)?

          Followup:

probably doesn't matter for that particular case (unless the some_conditions are
expensive to evaluate)

      Rating: 5
Thank you Tom
      very much  May 27, 2003
Reviewer:  Winston

I will rewrite the query and re-run the stored procedure (containing the query)
to see how CPU/buffer gets will change.

For now, is taking 104 buffer gets to retrieve a row reasonable?

          Followup:

from three tables -- probably not.  seems "high"

      Rating: 5
It did change
      the plan after getting rid of tricky conditions  May 27,
      2003
Reviewer:  Winston
call        count       cpu   elapsed         disk        query      current
    rows    misses
------- --------- --------- --------- ------------ ------------ ------------
------------ ---------
Parse           1      0.19      1.45          516          114            0
       0         1
Execute         1     59.94    139.35       250493       238309            5
     499         0
------- --------- --------- --------- ------------ ------------ ------------
------------ ---------
total           2     60.13    140.81       251009       238423            5
     499         1

|         Rows Row Source Operation
| ------------ ---------------------------------------------------
|            1 LOAD AS SELECT
|          499 .SORT GROUP BY
|         1873 ..HASH JOIN
|       226734 ...HASH JOIN

Explain Plan
---------------------------------------------------------------
...8 INSERT STATEMENT SEARCH_COLUMNS=  (COST=30202 CARD=25293 BYTES=7942002
IO_COST=30202 )
...7 .LOAD AS SELECT SEARCH_COLUMNS=
...6 ..SORT (GROUP BY) SEARCH_COLUMNS=  (COST=30202 CARD=25293 BYTES=7942002
IO_COST=30202 )
...5 ...HASH JOIN SEARCH_COLUMNS=  (COST=29374 CARD=25293 BYTES=7942002
IO_COST=29374 TEMP_SPACE=1573000)
...3 ....HASH JOIN SEARCH_COLUMNS=  (COST=19739 CARD=7486 BYTES=1482228
IO_COST=19739 TEMP_SPACE=943000)
...1 .....TABLE ACCESS (FULL) OF 'PRODUSER.MY_ORD_ITM' SEARCH_COLUMNS=
(COST=10715 CARD=7486 BYTES=845918 IO_COST=10715 )
...2 .....TABLE ACCESS (FULL) OF 'PRODUSER.MY_ORD' SEARCH_COLUMNS=  (COST=2965
CARD=2539622 BYTES=215867870 IO_COST=2965 )
...4 ....TABLE ACCESS (FULL) OF 'PRODUSER.MY_WORKACTT' SEARCH_COLUMNS=
(COST=9359 CARD=40802 BYTES=4733032 IO_COST=9359 )

OWNER.TABLE_NAME
...owner.index_name                                     num rows     blocks
sample last analyzed date
----------------------------------------------------- ---------- ----------
---------- -------------------
PRODUSER.MY_WORKACTT.................................
PRODUSER.MY_ORD......................................    2539622      30818
2539622 2003-05-22 23:36:19
PRODUSER.MY_ORD_ITM..................................    2994409     111396
2994409 2003-05-22 23:14:26

Event                                                                 Times
Count      Max.     Total    Blocks
waited on                                                            Waited Zero
Time      Wait    Waited  Accessed
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
db file scattered read (multiblock full scan)....................     14980
   0      0.36     59.73    237767
direct path read.................................................      1817
   0      0.18     16.40     12719
direct path write................................................      1103
   0      0.09      1.01      7720
db file sequential read (single block index scan)................         7
   0      0.05      0.07         7
control file sequential read.....................................         4
   0      0.03      0.03         4
SQL*Net message from client (idle)...............................         1
   0      0.00      0.00
SQL*Net message to client (idle).................................         1
   0      0.00      0.00
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
total............................................................     17913
   0      0.36     77.24    258217

non-idle waits...................................................     17911
   0      0.36     77.24    258217
idle waits.......................................................         2
   0      0.00      0.00

Details for wait event                                                Times
Count      Max.     Total    Blocks
'control file sequential read'                                       Waited Zero
Time      Wait    Waited  Accessed
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
UNKNOWN..........................................................         1
   0      0.03      0.03         1
datafile header for file_id: 0...................................         1
   0      0.00      0.00         1
datafile header for file_id: 1...................................         1
   0      0.00      0.00         1
datafile header for file_id: 2...................................         1
   0      0.00      0.00         1
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
total............................................................         4
   0      0.03      0.03         4

Details for wait event                                                Times
Count      Max.     Total    Blocks
'db file scattered read (multiblock full scan)'                      Waited Zero
Time      Wait    Waited  Accessed
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
PRODUSER.MY_WORKACTT.............................................      6076
   0      0.36     29.13     96323
PRODUSER.MY_ORD_itm..............................................      6970
   0      0.07     21.11    110853
PRODUSER.MY_ORD..................................................      1934
   0      0.07      9.49     30591
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
total............................................................     14980
   0      0.36     59.73    237767

Details for wait event                                                Times
Count      Max.     Total    Blocks
'db file sequential read (single block index scan)'                  Waited Zero
Time      Wait    Waited  Accessed
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
PRODUSER.MY_WORKACTT.............................................         6
   0      0.05      0.05         6
PRODUSER.sp_rpt_assignment_resourcing............................         1
   0      0.02      0.02         1
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
total............................................................         7
   0      0.05      0.07         7

Details for wait event                                                Times
Count      Max.     Total    Blocks
'direct path read'                                                   Waited Zero
Time      Wait    Waited  Accessed
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
temporary tablespace: TEMP.......................................      1817
   0      0.18     16.40     12719
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
total............................................................      1817
   0      0.18     16.40     12719

Details for wait event                                                Times
Count      Max.     Total    Blocks
'direct path write'                                                  Waited Zero
Time      Wait    Waited  Accessed
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
temporary tablespace: TEMP.......................................      1102
   0      0.09      0.98      7714
PRODUSER.sp_rpt_assignment_resourcing............................         1
   0      0.03      0.03         6
----------------------------------------------------------------- ---------
--------- --------- --------- ---------
total............................................................      1103
   0      0.09      1.01      7720

|  parsing recur
| trc_line depth user
| -------- ----- ----
|      368     0   59

produser@9iR2> select (238309+5)/1873 from dual;

(238309+5)/1873
---------------
     127.236519

Oracle optimizer did choose the better plan. The reason why I  said 'better' it
used less CPU and almose same LIOs

two questions.

1. Is there any relationship between number of reasonable LIOs/row and number of
tables joined together?

2. For this specific query  there is huge difference between cpu time (59.94)
and wall clock time(  139.35 ), would you please suggest anything to further
reduce it?

          Followup:

use
alter session set events '10046 trace name context forever, level 12';

and use tkprof.  you'll see what the waits are.  I see the waits there above but
it is too hard for me to read and I've no idea how you captured it (so I don't
know what they represent really).  but it does seem to point to "we waited for
the disk".  Maybe increase the multiblock read count -- don't know what it is
(you can try in the session that runs this, alter session)

don't know if you are using workarea_size_policy=manual|auto (i like auto) but
if manual, you might consider increasing the hash area size in the session for
this "load insert" statement.

1) it depends.  reasonable is in the eye of the beholder sometimes.  In a
transactional system -- the goal is very low.  In a large bulk operation, not
necessarily -- especially if the result set is largish, the inputs are largish,
and no indexing stategy exists that could reduce it.

      Rating: 5
Many
      Thanks  May 28, 2003
Reviewer:  Winston

The original post was done via Trace Analyzer (NOTE:224270.1).
I ran the tkprof on the same raw trace file and here is the result.

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  LOAD AS SELECT
    499   SORT GROUP BY
   1873    HASH JOIN
 226734     HASH JOIN
 226734      TABLE ACCESS FULL SP_ORD_ITM
2539622      TABLE ACCESS FULL SP_ORD
  31273     TABLE ACCESS FULL SP_ACTIVITY

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  control file sequential read                    4        0.02          0.03
  db file scattered read                      14980        0.36         59.73
  direct path write                            1103        0.08          1.01
  direct path read                             1817        0.18         16.39
  db file sequential read                         7        0.04          0.06
  SQL*Net message to client                       1        0.00          0.00
  SQL*Net message from client                     1        0.00          0.00
********************************************************************************

produser@9iR2> show parameter workarea_size_policy

NAME                                 TYPE        VALUE
------------------------------------ ----------- ----------
workarea_size_policy                 string      AUTO

Do you see any chance to bring down ' db file scattered read ' and 'direct path
read'?

Thank you very much for sharing with us!
Winston

          Followup:

direct path read is a read to temp generally -- your pga_aggregate_target may
therefore be too small on your system (but then again, do you expect a hash
table with 1/4 million rows in it to fit into ram?)

what's your multiblock read count?

      Rating:
      5
tweaked  May 28, 2003
Reviewer:
      A reader

      Rating: 5
tweaked
      db_file_multiblock_read_count  May 28, 2003

Reviewer:  Winston
produser@9iR2> show parameter pga_aggregate_target

NAME                                 TYPE        VALUE
------------------------------------ ----------- -----------
pga_aggregate_target                 big integer 25165824

produser@9iR2> show parameter db_file_multi

NAME                                 TYPE        VALUE
------------------------------------ ----------- -----------
db_file_multiblock_read_count        integer     16

I have referred to
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4433887271030
and figured out that the maximum value I should set for
db_file_multiblock_read_count is 128. and I did that and here is the new tkprof
result:

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1     61.54     124.91     245356     238315         29         499
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2     61.54     124.91     245356     238315         29         499

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 59

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  LOAD AS SELECT
    499   SORT GROUP BY
   1873    HASH JOIN
 226734     HASH JOIN
 226734      TABLE ACCESS FULL MY_ORD_ITM
2539622      TABLE ACCESS FULL MY_ORD
  31273     TABLE ACCESS FULL MY_WORKACTT

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  control file sequential read                    4        0.01          0.01
  db file scattered read                       1904        0.26         59.42
  direct path write                            1126        0.05          1.10
  direct path read                             1124        0.04          7.41
  SQL*Net message to client                       1        0.00          0.00
  SQL*Net message from client                     1        0.00          0.00

and in the raw source file I still saw a lot of wait

WAIT #1: nam='control file sequential read' ela= 198 p1=0 p2=1 p3=1
WAIT #1: nam='control file sequential read' ela= 6433 p1=1 p2=1 p3=1
WAIT #1: nam='control file sequential read' ela= 12938 p1=2 p2=1 p3=1
WAIT #1: nam='control file sequential read' ela= 354 p1=0 p2=190 p3=1
WAIT #1: nam='db file scattered read' ela= 32054 p1=9 p2=15951 p3=58
WAIT #1: nam='db file scattered read' ela= 61831 p1=9 p2=16139 p3=126
WAIT #1: nam='db file scattered read' ela= 42163 p1=9 p2=20235 p3=126
WAIT #1: nam='db file scattered read' ela= 27388 p1=9 p2=20363 p3=126
WAIT #1: nam='db file scattered read' ela= 26052 p1=9 p2=20491 p3=126
WAIT #1: nam='db file scattered read' ela= 48961 p1=9 p2=20619 p3=126
......
WAIT #1: nam='db file scattered read' ela= 27994 p1=9 p2=87053 p3=128
WAIT #1: nam='db file scattered read' ela= 26594 p1=9 p2=87181 p3=128
WAIT #1: nam='db file scattered read' ela= 29556 p1=9 p2=87309 p3=128
WAIT #1: nam='db file scattered read' ela= 26842 p1=9 p2=87437 p3=128
WAIT #1: nam='db file scattered read' ela= 61090 p1=9 p2=87565 p3=128
WAIT #1: nam='db file scattered read' ela= 25796 p1=9 p2=87693 p3=124
WAIT #1: nam='db file scattered read' ela= 28740 p1=9 p2=87821 p3=128
WAIT #1: nam='db file scattered read' ela= 26388 p1=9 p2=87949 p3=128

So I guess I cannot reduce the wait time on 'db file scatter read' any further.

          Followup:

so, unless there is some index that could be used to access the tables in
general more efficiently - then no.

but look at the query, is there any index that could help?

      Rating: 4
'db file
      sequential read'   May 30, 2003
Reviewer:
      vinodhps  from India, Chennai.
Hi Tom ,
I too have same problem like Mr.Winston , but i got with 'db file sequential
read' i traced the query with 10046 'level 12 ' which took nearly 5 hrs to
complete. i used itrprof sql analyzer from hotsos. i got output..

CPU TIME AND NON-IDLE WAIT TIME TOTALS FOR STATEMENT
Event  Elapsed   in Statement(%) in Overall(%)
-------------------------------- --------------
CPU    5,229.47             99%            99%
Wait      64.49              1%             1%
-------------------------------- --------------
total  5,293.96            100%           100%

STATISTIC TOTALS FOR STATEMENT
call        count    cpu    elapsed     disk      query     current  miss
rows
---------- ------ -------- -------------------- ----------
-------------------------
Parse          1      0.02      0.04         0          0          0    0
 0
Exec           1      0.01      0.01         0          0          0    0
 0
Fetch         58  5,229.36 16,455.99     2,939 10,496,206     61,319    0
863
Unmap          1      0.08      0.22         0          0          0    0
 0
Sort Unmap     0      0.00      0.00         0          0          0    0
 0
---------- ------ -------- -------------------- ----------
-------------------------
total         61  5,229.47 16,456.26     2,939 10,496,206     61,319    0
863
---------- ------ -------- -------------------- ----------
-------------------------

NON-IDLE WAIT TOTALS FOR STATEMENT
Name                            Elapsed    Count   avg. Elapsed  in Statement(%)
in Overall(%)
--------------------------------------------------
-------------------------------------------
db file sequential read           50.91     1,979          0.03              1%
          1%
latch free                         7.29       250          0.03              0%
          0%
db file scattered read             4.79       106          0.05              0%
          0%
buffer busy waits                  1.25         5          0.25              0%
          0%
file open                          0.24        15          0.02              0%
          0%
SQL*Net message to client          0.01        60          0.00              0%
          0%

--------------------------------------------------
-------------------------------------------
total                             64.49     2,415           N/A              1%
          1%

Explain plan

863 SORT ORDER BY  0
863   UNION-ALL  0
796     FILTER  0
796       NESTED LOOPS  0
796         NESTED LOOPS  0
1,548           NESTED LOOPS  0
1,548             NESTED LOOPS  0
4,042               NESTED LOOPS  0
6,619                 VIEW  0
6,619                   FILTER  0
34,943                     SORT GROUP BY  0
30,685                       NESTED LOOPS OUTER  0
6,619                         FILTER  0
0                           NESTED LOOPS OUTER  0
7,185                             TABLE ACCESS FULL T_MKS_CONS_MST  40,788
0                             TABLE ACCESS BY INDEX ROWID
T_MKS_CONS_CRED_LMT_MST  40,782
6,619                               INDEX RANGE SCAN  43,401
203,104,015                         TABLE ACCESS FULL T_FAS_SALE_AWA  41,196
53,256                 TABLE ACCESS BY INDEX ROWID T_ORD_ORDER_HDR  40,940
60,650                   INDEX RANGE SCAN  56,278
4,042               TABLE ACCESS BY INDEX ROWID T_MKS_CONS_MST  40,788
4,042                 INDEX UNIQUE SCAN  43,403
1,548             TABLE ACCESS BY INDEX ROWID T_PIS_CITY_MST  40,849
1,548               INDEX UNIQUE SCAN  43,608
4,294           TABLE ACCESS BY INDEX ROWID T_ORD_PRD_DTLS  41,071
5,842             INDEX RANGE SCAN  43,601
114,624         VIEW  0
114,624           FILTER  0
30,742             SORT GROUP BY  0
50,633               HASH JOIN OUTER  0
144                 VIEW  0
279                   FILTER  0
264                     HASH JOIN OUTER  0
144                       TABLE ACCESS FULL T_MKS_IND_MST  40,823
180                       TABLE ACCESS FULL T_MKS_IND_CRED_LMT_MST  40,821
30,685                 TABLE ACCESS FULL T_FAS_SALE_AWA  41,196
0       INDEX FAST FULL SCAN  43,464
1     FILTER  0
1       NESTED LOOPS  0
1         NESTED LOOPS  0
13           NESTED LOOPS  0
13             NESTED LOOPS  0
13               NESTED LOOPS  0
1,256                 TABLE ACCESS FULL T_ORD_SCHDL_DTLS  41,074
19                 TABLE ACCESS BY INDEX ROWID T_ORD_PRD_DTLS  41,071
19                   INDEX UNIQUE SCAN  43,601
13               TABLE ACCESS BY INDEX ROWID T_ORD_ORDER_HDR  40,940
13                 INDEX UNIQUE SCAN  43,597
86,047             VIEW  0
86,047               FILTER  0
34,943                 SORT GROUP BY  0
30,685                   NESTED LOOPS OUTER  0
6,619                     FILTER  0
0                       NESTED LOOPS OUTER  0
7,185                         TABLE ACCESS FULL T_MKS_CONS_MST  40,788
0                         TABLE ACCESS BY INDEX ROWID T_MKS_CONS_CRED_LMT_MST
40,782
6,619                           INDEX RANGE SCAN  43,401
203,104,015                     TABLE ACCESS FULL T_FAS_SALE_AWA  41,196
13           TABLE ACCESS BY INDEX ROWID T_MKS_CONS_MST  40,788
13             INDEX UNIQUE SCAN  43,403
1         TABLE ACCESS BY INDEX ROWID T_PIS_CITY_MST  40,849
1           INDEX UNIQUE SCAN  43,608
0       INDEX FAST FULL SCAN  43,464
66     FILTER  0
66       NESTED LOOPS  0
66         NESTED LOOPS  0
137           NESTED LOOPS  0
137             NESTED LOOPS  0
137               NESTED LOOPS  0
6,619                 VIEW  0
6,619                   FILTER  0
34,943                     SORT GROUP BY  0
30,685                       NESTED LOOPS OUTER  0
6,619                         FILTER  0
0                           NESTED LOOPS OUTER  0
7,185                             TABLE ACCESS FULL T_MKS_CONS_MST  40,788
0                             TABLE ACCESS BY INDEX ROWID
T_MKS_CONS_CRED_LMT_MST  40,782
6,619                               INDEX RANGE SCAN  43,401
203,104,015                         TABLE ACCESS FULL T_FAS_SALE_AWA  41,196
775                 TABLE ACCESS BY INDEX ROWID T_ORD_ORDER_HDR  40,940
60,650                   INDEX RANGE SCAN  56,278
137               TABLE ACCESS BY INDEX ROWID T_ORD_EXPORT_DTLS  41,041
137                 INDEX UNIQUE SCAN  43,588
137             TABLE ACCESS BY INDEX ROWID T_PIS_CITY_MST  40,849
137               INDEX UNIQUE SCAN  43,608
300           TABLE ACCESS BY INDEX ROWID T_ORD_PRD_DTLS  41,071
437             INDEX RANGE SCAN  43,601
9,504         VIEW  0
9,504           FILTER  0
30,742             SORT GROUP BY  0
50,633               HASH JOIN OUTER  0
144                 VIEW  0
279                   FILTER  0
264                     HASH JOIN OUTER  0
144                       TABLE ACCESS FULL T_MKS_IND_MST  40,823
180                       TABLE ACCESS FULL T_MKS_IND_CRED_LMT_MST  40,821
30,685                 TABLE ACCESS FULL T_FAS_SALE_AWA  41,196
0       INDEX FAST FULL SCAN  43,464

Here all the tables are analyzed and can i use hash hint instead of nested loop
i think its problem with nested loop .please correct me if iam wrong Tom.this
particular query waits for 'db file sequential read'  for object T_FAS_SALE_AWA
where this particular table has 30,000 row but here because of this nested loop
it goes upto 203,104,015 .How to solve this problem?. do you have any suggestion
on this Tom.

Thanks for all your help.

          Followup:

you don't have a wait problem here - you have a slight "I'm using tons of cpu"
problem here.

your waits for IO do not nearly come close to explaining the difference between
5k cpu and 16k wall clock seconds.

You are somewhat out of CPU.

If you have hints in your query -- get rid of them, let the optimizer do its job
please.

      Rating: 5
CPU
      problem  May 30, 2003
Reviewer:  vinodhps
       from India,Chennai
Hi.. Tom,
Thanks for your immediate response,but what about table with

loop
for i in ( 6619 rows)
  30000 rows table

it goes upto 203,104,015 actual it should use hash join right. if worng please
correct me.
For that query it goes  10,496,206     logical reads
                            61,319     Current
To fetch 863 rows it  to read   10,557,528 block.iam not sure why its like that.

Thanks for your time.

          Followup:

no idea what you are saying here.

concrete example please, just like I provide.

      Rating: 5
How to
      choose  May 30, 2003
Reviewer:  Winston

Hi Tom,

I put some index there, but Oracle9i choose not to use the index until I changed
the default set of two parameters
alter session set optimizer_index_caching=50
alter session set optimizer_index_cost_adj=50.

Here is before index result
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1     62.20     123.20     245409     238315         31         499
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2     62.20     123.20     245409     238315         31         499

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 59

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  LOAD AS SELECT
    499   SORT GROUP BY
   1873    HASH JOIN
 226734     HASH JOIN
 226734      TABLE ACCESS FULL MY_ORD_ITM
2539622      TABLE ACCESS FULL MY_ORD
  31273     TABLE ACCESS FULL MY_WORKACTT

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  control file sequential read                    4        0.02          0.05
  db file scattered read                       1901        0.13         59.09
  direct path write                            1126        0.04          0.97
  direct path read                             1124        0.03          6.13
  SQL*Net message to client                       1        0.00          0.00
  SQL*Net message from client                     1        0.00          0.00
********************************************************************************

and here is after using index result

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.03          0          0          0           0
Execute      1     14.23     166.60     205646     198945         30         499
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2     14.26     166.64     205646     198945         30         499

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 59

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  LOAD AS SELECT
    499   SORT GROUP BY
   1873    HASH JOIN
  31273     INLIST ITERATOR
  31273      TABLE ACCESS BY INDEX ROWID MY_WORKACTT 210160       INDEX RANGE
SCAN LATEST_STATUS (object id 31209)
 226734     HASH JOIN
 226734      TABLE ACCESS FULL MY_ORD_ITM
2539622      TABLE ACCESS FULL MY_ORD

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  control file sequential read                    4        0.00          0.00
  db file sequential read                     57370        0.36         92.83
  direct path write                             978        0.13          0.30
  direct path read                              976        0.28         14.56
  db file scattered read                       8904        0.38         46.95
  SQL*Net message to client                       1        0.00          0.00
  SQL*Net message from client                     1        0.00          0.00
********************************************************************************

I found using index approach reduced CPU time by 80%,  LIOs by 20%, but total
waits (largest wait events switch to 'db file sequential read') increased by 2.5
times.

Could you please share your opinion as to which plan should go with?

          Followup:

I'm partial to the bulk plan (plan 1) personally.  It can use large multi-block
reads with greater efficiency then the other one.

      Rating: 5
NESTED LOOP VS
      HASH JOIN VS SORT MERGE  May 31, 2003
Reviewer:
      vinodhps  from India, Chennai
Hi Tom,
Thanks for your immediate answer, actually my question is if i got 3 table
namely.

vinodh@TNPL.WORLD> SELECT COUNT(*) FROM SMALL_TAB;

  COUNT(*)
----------
        50

vinodh@TNPL.WORLD> SELECT COUNT(*) FROM MEDIUM_TAB;

  COUNT(*)
----------
      1699

vinodh@TNPL.WORLD> SELECT COUNT(*) FROM BIG_TAB;

  COUNT(*)
----------
     23161

In what condition  optimizer will choose Nested loop , Hash Join or Sort Merge.
Some times due to poor sql coding optimizer may choose Nested loop instead of
hash . so i have to wait long time. As your telling not to include hits  on
query i have to rearrange the sql query .i would like to know in what condition
it will choose Nested loop , Hash Join or Sort Merge can you plese explain with
example with these 3 tables .
It will be very much helpful for people like me to learn more .Regarding this I
could not able to find , in your book .

As we know that your are excellent for examples, iam looking forward to your
example for the above.

Thanking you,

          Followup:

ask the optimizer.

it is all about cardinalities -- if it expected to get 1 row from each table to
answer your query -- nested loops.  if it expected to get most of the rows --
bulk operations.

      Rating: 5
Detailed
      information in Performance Tuning guide and reference  May
      31, 2003
Reviewer:  Winston
page 1-13 --Understanding the CBO

The estimator generators three different types of measures:
Selectivity
Cardinality
Cost
These measures are related to each other, and one is derived from another. The
end goal of the estimator is to estimate the overall cost of a given plan. If
statistics are available, then the estimator uses them to compute the measures.
The statistics improve the degree of accuracy of the measures.

page 1-41-- How the CBO chooses the join method

The optimizer estimates the cost of each join method and chooses the method with
the least cost. If a join returns many rows, then the optimizer considers the
following three factors:
. A nested loop join is inefficient when a join returns a large number of
rows(typically, more than 10,000 rows is considered large), and the optimizer
might choose not to use it.
. If you are using the CBO, then a hash join is the most efficient join when a
join returns a large number or rows.

Page 1-50 When the optimizer Uses Sort Merge Joins

The optimzer can choose a sort merge join over a hash join for joining large
amounts of data if any of the following conditions are true:
. The join condition between two tables is not an equi-join
. OPTMIZER_MODE is set to RULE.
. HASH_JOIN_ENABLEED is false.
. Because of sorts already required by other operations, the optimizer finds it
is cheaper to use a sort merge than a hash join.
. The optimizer thinks that the cost of a hash join is higher, based on the
settings of HASH_AREA_SIZE and SORT_AREA_SIZE

      Rating: 4
Thanks
      winston  June 01, 2003
Reviewer:  vinodhps
       from India,chennai
Hi tom,
I thought you will explain with example so that i will have a very clear cut
idea about how the joins are used.

Thank you Mr.winston.. it was useful information for me.

          Followup:

(and it is information that has been available to you all along -- right under
your nose. who would think we would actually document this stuff ;)

      Rating: 5
Thank You
      Tom  June 02, 2003
Reviewer:  vinodhps
       from India,Chennai
Hi Tom ,

Thank you for you help, iam new to your website just for the past 3 months iam
using it.

I just saw this article from the link in your website , some thing like i
expected.But i have a doubt, suppose in this example
below it goes for Nested Loop but  Hash Join will the Optimized one.
As you use to insist not to use Hints in query.
Does the optimizer choose  Hash Join instead of Nested Loop or else i have
change any parameter in init.ora.

Can you Please Explain this Mr.Tom.

case study

Small_tab table has 1,200 rows, 21 blocks
Big_tab table has 600,000 rows, 9,000 data blocks
Query returns 4,000 rows

SELECT s.num, s.date,b.code  FROM  big_tab b, small_tab s
WHERE b.code = s.code
AND b.date = ’01-Jun-03’;

NESTED LOOPS JOIN
  TABLE ACCESS BY INDEX ROW ID "BIG_TAB"
    INDEX RANGE SCAN "BIG_INDX"
  TABLE ACCESS BY INDEX ROW ID "SMALL_TAB"
    INDEX UNIQUE SCAN "SMALL_INDX"

For every row retrieved from orders,
3 logical reads are required to retrieve
the row in SMALL_TAB by the unique index.
4,000 x 3 = 12,000 logical reads to access SMALL_TAB

SELECT s.num, s.date,b.code  FROM  big_tab b, small_tab s
WHERE b.code = s.code
AND b.date = ’01-Jun-03’;

HASH JOIN
  TABLE ACCESS FULL SMALL_TAB
  TABLE ACCESS BY INDEX ROW ID "BIG_TAB"
    INDEX RANGE SCAN "BIG_IDNX"

SMALL_TAB appears first in the execution plan
so it is the table that is bitmapped
One full-table scan (21 Logical Reads) is
required to retrieve all rows from SMALL_TAB
Compare to 12,000 logical reads in the nested loops join case

*Cost-based optimizer usually does a
good job of determining when hash joins are
most appropriate if statistics are available on
 the associated tables and indexes

*Hash joins often use full-table scans on small tables

*Caching frequently scanned small tables may be
appropriate to minimize physical reads

Thanks.

          Followup:

that is a pretty incomplete example.  if b.date is pretty selective

(two unasked for comments here:
o compare dates to dates, numbers to numbers, strings to strings.  do NOT
compare a date to a string as you have.  use TO_DATE on the string with a format
mask to convert

o the year 03 was a long long long time ago.  2003 is more current.  It takes no
less then 4 characters to represent a year these days. )

then where b.date = to_date( '01-jun-2003', 'dd-mon-yyyy' ) will return few rows
and the nested loops would probably be correct.  You give no distribution of
data, indexing schemes, how statistics were collected.

I'm not really even sure what you are asking at the end?  If you are asking
"based on these row counts, what will the CBO do" -- there is insufficient data
to answer.  It'll really depend on the clustering factor of the index on the
b.date column, the number of values, the distribution of values.  I can set up
test cases that go both ways, just by manipulating the data values (no init.ora
parameters)

The CBO will choose what is best based on the data present and how it is
physically organized.

      Rating: 5
Why optimizer
      prefers NLJ here  July 08, 2003
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
Tom
Please see the query plan without hint and with hint. Without hint, the
optimizer chooses NLJ which is proving costly in terms of consistent gets. Why
is the optimizer choosing NLJ over HJ?
Thanks

SQL> ed
Wrote file afiedt.buf

  1  select *
  2  from ccmis.t_sbsd_case_adr sbsd_case_adr, ccmis.t_sbsd_case sbsd_case
  3  where
  4       sbsd_case.idn_case = sbsd_case_adr.idn_case
  5  AND (UPPER(sbsd_case_adr.adr_state) LIKE UPPER(&b3) OR  sbsd_case_adr.adr_s
tate IS NULL)
  6  AND (UPPER(sbsd_case_adr.adr_city) LIKE UPPER(&b2) OR  sbsd_case_adr.adr_ci
ty IS NULL)
  7  AND (sbsd_case_adr.adr_zip_main LIKE &b1 OR  sbsd_case_adr.adr_zip_main IS
NULL)
  8  AND sbsd_case_adr.ind_record_curr = 'Y'
  9  AND sbsd_case_adr.ind_record_delete_logcl = 'N'
 10* AND sbsd_case_adr.dte_end_effv IS NULL
SQL> /
Enter value for b3: '%'
old   5: AND (UPPER(sbsd_case_adr.adr_state) LIKE UPPER(&b3) OR  sbsd_case_adr.a
dr_state IS NULL)
new   5: AND (UPPER(sbsd_case_adr.adr_state) LIKE UPPER('%') OR  sbsd_case_adr.a
dr_state IS NULL)
Enter value for b2: '%'
old   6: AND (UPPER(sbsd_case_adr.adr_city) LIKE UPPER(&b2) OR  sbsd_case_adr.ad
r_city IS NULL)
new   6: AND (UPPER(sbsd_case_adr.adr_city) LIKE UPPER('%') OR  sbsd_case_adr.ad
r_city IS NULL)
Enter value for b1: '%'
old   7: AND (sbsd_case_adr.adr_zip_main LIKE &b1 OR  sbsd_case_adr.adr_zip_main
 IS NULL)
new   7: AND (sbsd_case_adr.adr_zip_main LIKE '%' OR  sbsd_case_adr.adr_zip_main
 IS NULL)

3447 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=12 Card=4 Bytes=584)
   1    0   NESTED LOOPS (Cost=12 Card=4 Bytes=584)
   2    1     TABLE ACCESS (FULL) OF 'T_SBSD_CASE_ADR' (Cost=8 Card=4
          Bytes=348)

   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'T_SBSD_CASE' (Cost=1 C
          ard=1 Bytes=59)

   4    3       INDEX (UNIQUE SCAN) OF 'PK_T_SBSD_CASE' (UNIQUE)

Statistics
----------------------------------------------------------
         21  recursive calls
          0  db block gets
       7404  consistent gets
          0  physical reads
          0  redo size
     364814  bytes sent via SQL*Net to client
       1874  bytes received via SQL*Net from client
        231  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       3447  rows processed

SQL>

SQL> ed
Wrote file afiedt.buf

  1  select /*+ use_hash(sbsd_case_adr, sbsd_case) */ *
  2  from ccmis.t_sbsd_case_adr sbsd_case_adr, ccmis.t_sbsd_case sbsd_case
  3  where
  4       sbsd_case.idn_case = sbsd_case_adr.idn_case
  5  AND (UPPER(sbsd_case_adr.adr_state) LIKE UPPER(&b3) OR  sbsd_case_adr.adr_s
tate IS NULL)
  6  AND (UPPER(sbsd_case_adr.adr_city) LIKE UPPER(&b2) OR  sbsd_case_adr.adr_ci
ty IS NULL)
  7  AND (sbsd_case_adr.adr_zip_main LIKE &b1 OR  sbsd_case_adr.adr_zip_main IS
NULL)
  8  AND sbsd_case_adr.ind_record_curr = 'Y'
  9  AND sbsd_case_adr.ind_record_delete_logcl = 'N'
 10* AND sbsd_case_adr.dte_end_effv IS NULL
SQL> /
Enter value for b3: '%'
old   5: AND (UPPER(sbsd_case_adr.adr_state) LIKE UPPER(&b3) OR  sbsd_case_adr.a
dr_state IS NULL)
new   5: AND (UPPER(sbsd_case_adr.adr_state) LIKE UPPER('%') OR  sbsd_case_adr.a
dr_state IS NULL)
Enter value for b2: '%'
old   6: AND (UPPER(sbsd_case_adr.adr_city) LIKE UPPER(&b2) OR  sbsd_case_adr.ad
r_city IS NULL)
new   6: AND (UPPER(sbsd_case_adr.adr_city) LIKE UPPER('%') OR  sbsd_case_adr.ad
r_city IS NULL)
Enter value for b1: '%'
old   7: AND (sbsd_case_adr.adr_zip_main LIKE &b1 OR  sbsd_case_adr.adr_zip_main
 IS NULL)
new   7: AND (sbsd_case_adr.adr_zip_main LIKE '%' OR  sbsd_case_adr.adr_zip_main
 IS NULL)

3448 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=17 Card=4 Bytes=584)
   1    0   HASH JOIN (Cost=17 Card=4 Bytes=584)
   2    1     TABLE ACCESS (FULL) OF 'T_SBSD_CASE_ADR' (Cost=8 Card=4
          Bytes=348)

   3    1     TABLE ACCESS (FULL) OF 'T_SBSD_CASE' (Cost=8 Card=3442 B
          ytes=203078)

Statistics
----------------------------------------------------------
         21  recursive calls
          0  db block gets
        328  consistent gets
          0  physical reads
          0  redo size
     366004  bytes sent via SQL*Net to client
       1874  bytes received via SQL*Net from client
        231  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       3448  rows processed

          Followup:

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=12 Card=4 Bytes=584)
   1    0   NESTED LOOPS (Cost=12 Card=4 Bytes=584)
   2    1     TABLE ACCESS (FULL) OF 'T_SBSD_CASE_ADR' (Cost=8 Card=4
          Bytes=348)

   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'T_SBSD_CASE' (Cost=1 C
          ard=1 Bytes=59)

   4    3       INDEX (UNIQUE SCAN) OF 'PK_T_SBSD_CASE' (UNIQUE)

optimizer thinks it is going to get 4 rows from T_SBSD_CASE_ADR - apparently,
its getting lots more.

are the stats up to date
are there histograms on relevant columns
are you cursor sharing

      Rating: 5
The histogram
      was missing  July 09, 2003
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
Tom
After posting, I realized that the histogram on ADR_STATE was missing. The data
in this column is highly skewed, most rows have state code of PA with very few
rows having other state codes. Thanks very much.

I have another one which is really baffling. The output of the view VW_SQ_1 when
hash joined with T_PROVR_LEGAL_ENTITY is showing a cardinality of 95. This is
not right. The cardinality is much higher. I don't know what is throwing the
optimize off track. After careful analysis, this low cardinality is coming from

tpls.nbr_seq_hist = MAX(nbr_seq_hist)(subquery)

How does optimize estimate the cardinality in such case?
Thanks.

SQL> set autotrace traceonly;
SQL> ed
Wrote file afiedt.buf

  1  select
  2  SUBSTR(NVL(tple.ind_req_1099, ' '), 1, 1),
  3  SUBSTR(NVL(tpl.idn_vendor, ' '), 1, 1),
  4  UPPER(SUBSTR(RPAD(NVL(tpl.idn_vendor, ' '), 7, ' '), 2, 7)) vendor,
  5  SUBSTR(RPAD(NVL(TO_CHAR(tpls.dte_begin_effv, 'YYYYDDD'), ' '), 7, ' '), 1,
7),
  6  SUBSTR(RPAD(NVL(TO_CHAR(tpls.dte_end_effv, 'YYYYDDD'), ' '), 7, ' '), 1,7),

  7  SUBSTR(RPAD(NVL(TO_CHAR(tpls.dte_change_last, 'YYYYDDD'), ' '), 7, ' '),1,
7),
  8  SUBSTR(RPAD(NVL(TO_CHAR(tpls.dte_change_last, 'HHMISS') || '00',' '), 8, '
'), 1, 8)
  9  from
 10  ccmis.t_provr_legal_entity tple,
 11  ccmis.t_provr_loc tpl,
 12  ccmis.t_provr_loc_status tpls
 13  where
 14  tple.idn_entity_legal_provr = tpl.idn_entity_legal_provr
 15  AND tpl.idn_entity_legal_provr = tpls.idn_entity_legal_provr
 16  AND tpl.idn_loc_provr = tpls.idn_loc_provr
 17  AND tpls.nbr_seq_hist = (SELECT  MAX(nbr_seq_hist)
 18                                     FROM ccmis.t_provr_loc_status tpls2
 19                                     WHERE tpl.idn_entity_legal_provr =
 20                                           tpls2.idn_entity_legal_provr
 21                                       AND tpl.idn_loc_provr =
 22                                           tpls2.idn_loc_provr
 23                                                                      AND tpl
s2.cde_type_status = 'SUB'
 24                                                                      AND tpl
s2.cde_status = 'ESP'
 25*                                      )
 26  /

109358 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=924 Card=95 Bytes=82
          65)

   1    0   NESTED LOOPS (Cost=924 Card=95 Bytes=8265)
   2    1     HASH JOIN (Cost=829 Card=95 Bytes=7600)
   3    2       VIEW OF 'VW_SQ_1' (Cost=291 Card=29345 Bytes=1144455)
   4    3         SORT (GROUP BY) (Cost=291 Card=29345 Bytes=528210)
   5    4           TABLE ACCESS (FULL) OF 'T_PROVR_LOC_STATUS' (Cost=
          173 Card=29345 Bytes=528210)

   6    2       HASH JOIN (Cost=411 Card=128262 Bytes=5258742)
   7    6         TABLE ACCESS (FULL) OF 'T_PROVR_LOC' (Cost=134 Card=
          58476 Bytes=877140)

   8    6         TABLE ACCESS (FULL) OF 'T_PROVR_LOC_STATUS' (Cost=17
          3 Card=137702 Bytes=3580252)

   9    1     TABLE ACCESS (BY INDEX ROWID) OF 'T_PROVR_LEGAL_ENTITY'
          (Cost=1 Card=1 Bytes=7)

  10    9       INDEX (UNIQUE SCAN) OF 'PK_T_PROVR_LEGAL_ENTITY' (UNIQ
          UE)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     236350  consistent gets
       2659  physical reads
          0  redo size
    2918889  bytes sent via SQL*Net to client
      51301  bytes received via SQL*Net from client
       7292  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
     109358  rows processed

SQL> ed
Wrote file afiedt.buf

  1  select /*+ use_hash(tple) */
  2  SUBSTR(NVL(tple.ind_req_1099, ' '), 1, 1),
  3  SUBSTR(NVL(tpl.idn_vendor, ' '), 1, 1),
  4  UPPER(SUBSTR(RPAD(NVL(tpl.idn_vendor, ' '), 7, ' '), 2, 7)) vendor,
  5  SUBSTR(RPAD(NVL(TO_CHAR(tpls.dte_begin_effv, 'YYYYDDD'), ' '), 7, ' '), 1,
7),
  6  SUBSTR(RPAD(NVL(TO_CHAR(tpls.dte_end_effv, 'YYYYDDD'), ' '), 7, ' '), 1,7),

  7  SUBSTR(RPAD(NVL(TO_CHAR(tpls.dte_change_last, 'YYYYDDD'), ' '), 7, ' '),1,
7),
  8  SUBSTR(RPAD(NVL(TO_CHAR(tpls.dte_change_last, 'HHMISS') || '00',' '), 8, '
'), 1, 8)
  9  from
 10  ccmis.t_provr_legal_entity tple,
 11  ccmis.t_provr_loc tpl,
 12  ccmis.t_provr_loc_status tpls
 13  where
 14  tple.idn_entity_legal_provr = tpl.idn_entity_legal_provr
 15  AND tpl.idn_entity_legal_provr = tpls.idn_entity_legal_provr
 16  AND tpl.idn_loc_provr = tpls.idn_loc_provr
 17  AND tpls.nbr_seq_hist = (SELECT  MAX(nbr_seq_hist)
 18                                     FROM ccmis.t_provr_loc_status tpls2
 19                                     WHERE tpl.idn_entity_legal_provr =
 20                                           tpls2.idn_entity_legal_provr
 21                                       AND tpl.idn_loc_provr =
 22                                           tpls2.idn_loc_provr
 23                                                                      AND tpl
s2.cde_type_status = 'SUB'
 24                                                                      AND tpl
s2.cde_status = 'ESP'
 25*                                      )
SQL> /

109358 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=993 Card=95 Bytes=82
          65)

   1    0   HASH JOIN (Cost=993 Card=95 Bytes=8265)
   2    1     HASH JOIN (Cost=829 Card=95 Bytes=7600)
   3    2       VIEW OF 'VW_SQ_1' (Cost=291 Card=29345 Bytes=1144455)
   4    3         SORT (GROUP BY) (Cost=291 Card=29345 Bytes=528210)
   5    4           TABLE ACCESS (FULL) OF 'T_PROVR_LOC_STATUS' (Cost=
          173 Card=29345 Bytes=528210)

   6    2       HASH JOIN (Cost=411 Card=128262 Bytes=5258742)
   7    6         TABLE ACCESS (FULL) OF 'T_PROVR_LOC' (Cost=134 Card=
          58476 Bytes=877140)

   8    6         TABLE ACCESS (FULL) OF 'T_PROVR_LOC_STATUS' (Cost=17
          3 Card=137702 Bytes=3580252)

   9    1     TABLE ACCESS (FULL) OF 'T_PROVR_LEGAL_ENTITY' (Cost=163
          Card=56011 Bytes=392077)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       5963  consistent gets
       3759  physical reads
          0  redo size
    3019723  bytes sent via SQL*Net to client
      51301  bytes received via SQL*Net from client
       7292  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
     109358  rows processed

SQL>

          Followup:

it would be looking at the columns cde_type_status,cde_status,nbr_seq_hist and
trying to figure out if it only got the max(nbr_seq_hist) by the other two --
how many rows would we get back.

In this case, it does look like an optimizer issue since the two tables are on
in the same and if it knows that it'll get 29k from the subquery -- it'll get
29k from the "in"

is nbr_seq_hist unique in t_provr_loc_status?  is it a sequence populated
column, if so, there is a much better way I think to code that query that'll
further reduce the work it does

      Rating:
      5
  July 10, 2003
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
The nbr_seq_hist is a sequence which works as follows:
idn_entity_legal_provr   nbr_seq_hist
======================   ============
123                      1
123                      2
123                      3
345                      1
345                      2

etc.
Basically, for each provider id, there can be more than one row. Each row for a
given provider has a sequence number which increments every time we insert a new
row for that provider. The sequence number always starts from 1 for any provider
id. To this extent, nbr_seq_hist is not unique. The combination of provider id
and nbr_seq_hist is unique. The query is attempting to retrieve only the rows
with highest sequence number.

          Followup:

try this

select *
from
(
select
SUBSTR(NVL(tple.ind_req_1099, ' '), 1, 1),
SUBSTR(NVL(tpl.idn_vendor, ' '), 1, 1),
UPPER(SUBSTR(RPAD(NVL(tpl.idn_vendor, ' '), 7, ' '), 2, 7)) vendor,
SUBSTR(RPAD(NVL(TO_CHAR(tpls.dte_begin_effv, 'YYYYDDD'), ' '), 7, ' '), 1, 7),
SUBSTR(RPAD(NVL(TO_CHAR(tpls.dte_end_effv, 'YYYYDDD'), ' '), 7, ' '), 1,7),
SUBSTR(RPAD(NVL(TO_CHAR(tpls.dte_change_last, 'YYYYDDD'), ' '), 7, ' '),1, 7),
SUBSTR(RPAD(NVL(TO_CHAR(tpls.dte_change_last, 'HHMISS') || '00',' '), 8, ' '),
1, 8),
max(nbr_seq_hist) over ( partition by idn_entity_legal_provr, idn_loc_provr )
max_nbr_seq_hist,
nbr_seq_hist
from
ccmis.t_provr_legal_entity tple,
ccmis.t_provr_loc tpl,
ccmis.t_provr_loc_status tpls
where
tple.idn_entity_legal_provr = tpl.idn_entity_legal_provr
AND tpl.idn_entity_legal_provr = tpls.idn_entity_legal_provr
AND tpl.idn_loc_provr = tpls.idn_loc_provr
and tpl.cde_type_status = 'SUB'
and tpl.cde_status = 'ESP'
)
where nbr_seq_hist = max_nbr_seq_hist
/

      Rating: 5
query
        October 22, 2003
Reviewer:  A reader

Hi Tom
I have an sql query question:

Given the following schema:
DROP TABLE G_1
/

CREATE TABLE G_1(
GROUP_ID     NUMBER,
TARGET_ID    NUMBER)
/

INSERT INTO g_1 VALUES(1,1)
/
INSERT INTO g_1 VALUES(1,2)
/
INSERT INTO g_1 VALUES(1,3)
/
INSERT INTO g_1 VALUES(2,1)
/
INSERT INTO g_1 VALUES(2,2)
/
INSERT INTO g_1 VALUES(2,3)
/
INSERT INTO g_1 VALUES(2,4)
/
INSERT INTO g_1 VALUES(3,4)
/
DROP TABLE G_2
/

CREATE TABLE G_2(
    target_id    NUMBER,
    target_name    VARCHAR2(25)
)
/

INSERT INTO g_2 VALUES(1,'string_pattern1')
/
INSERT INTO g_2 VALUES(2,'string_pattern2')
/
INSERT INTO g_2 VALUES(3,'string_pattern3')
/
INSERT INTO g_2 VALUES(4,'junk')
/

COMMIT;

We have targets that are grouped into groups.
We need to get the list of groups *ALL* of
whose targets match the "string_pattern".

In the above data that would be group # 1.

I came up with the following query which
is very slow - Can you take alook and suggest
any alternatives?

Thanx!!!

with common_query as
(
  select a.group_id
  from stormon_host_groups a, mgmt_targets_view b
  where a.target_id = b.target_id
  and b.target_name not like '%string_pattern%'
)
select distinct c.group_id
from
( select a.group_id
  from stormon_host_groups a, mgmt_targets_view b
  where a.target_id = b.target_id
  and b.target_name like '%string_pattern%'
) c, common_query d
where c.group_id != d.group_id
and c.group_id not in ( select group_id from common_query ) ;

          Followup:

select group_id
  from g_1 where group_id in
         ( select group_id
             from g_1
            where target_id in ( select target_id
                                   from g_2
                                  where target_name like '%string_pattern%' )
         )
 group by group_id
having count(*) =
      ( select count(*)
          from g_2
         where target_name like '%string_pattern%' )
/

is another approach.  find the target_ids you are interested in, find the groups
that have some of them, count the number of rows in that group and compare that
count to the count of targets you are interested in.

      Rating: 5
all right - I
      forgot to change the table names in the previous
      one:)  October 22, 2003
Reviewer:
      A reader
Here is the query I tried with the example tables
that ran slow:)

ith common_query as
(
  select a.group_id
  from g_1 a, g_2 b
  where a.target_id = b.target_id
  and b.target_name not like '%gitmon%'
)
select distinct c.group_id
from
( select a.group_id
  from g_1 a, g_2 b
  where a.target_id = b.target_id
  and b.target_name like '%gitmon%'
) c, common_query d
where c.group_id != d.group_id
and c.group_id not in ( select group_id from common_query ) ;
Many thanx!:)

      Rating: 5
so much for one
      more mistake  October 22, 2003
Reviewer:
      A reader
*smack on the head***

sorry the query should be

with common_query as
(
  select a.group_id
  from stormon_host_groups a, mgmt_targets_view b
  where a.target_id = b.target_id
  and b.target_name not like '%string_pattern%'
)
select distinct c.group_id
from
( select a.group_id
  from stormon_host_groups a, mgmt_targets_view b
  where a.target_id = b.target_id
  and b.target_name like '%string_pattern%'
) c, common_query d
where c.group_id != d.group_id
and c.group_id not in ( select group_id from common_query ) ;

      Rating: 5
thanx
      Tom!  October 23, 2003
Reviewer:
      A reader
I will try it out and see if that gives better results!

October 23, 2003 Elie -- Thanks for the question regarding "Case
      Insensitive Queries", version 8.1.6.0.0

      You Asked
Is there a way to SELECT from a table in a case insensitive
manner?  For example, I want to execute:

   SELECT * FROM SCOTT.EMP WHERE ENAME = 'ToM kYTe';

The only way I know to do this is via:

   SELECT * FROM SCOTT.EMP WHERE UPPER(ENAME) = 'TOM KYTE';

This is fine if the table has a comparitively small number of
records.  But when the table is large, applying the UPPER
function is costly.

I thought of using function based indexes for this, but this
only works in Enterprise, not in Standard.

And perhaps the Oracle InterMedia Cardrodge would help, but this
seems to me like "overkill" for something most other relational
databases do "out of the box", sort to speak.

In Microsoft's Sql Server, for example, one need only set a
certain character set and one is then able to do "mixed case"
queries without any more fuss and without setting any special
indexes.

Is Oracle capable of doing such "mixed case" queries and, if so,
how?

Thank you in advance, Tom, for any help.

Elie Grunhaus
Concentrix Corporation
egrunhaus@concentrix.com

      and we said...
Well, I don't know if I would call SQLserver "most every other
database" but....  There are dozens of things we do out of the
box they do not as well, I guess it just depends on what
features you've grown accustomed to.

With that option in SQLserver it is all or nothing.  Either
every search is case insensitive or not.  That is not
necessarily a good thing either.

The ways to do the case insensitive search in Oracle would be:

o function based indexes as you are aware.

o a shadow column that is indexed and maintained by a trigger.
eg:

  alter table emp add upper_name varchar2(30);
  create trigger emp_trigger
  after insert or update on emp for each row
  begin
     :new.upper_ename := upper(:new.ename);
  end;
  /
  create index upper_ename on ename(upper_ename);

  select * from emp where upper_ename = 'KING';

o intermedia as you are aware.

o using an index organized table you maintain.

  create table upper_ename
  ( ename varchar2(30),
    rid   rowid, primary key (ename,rid) ) organization index;

  create trigger emp_trigger
  after insert or update on emp for each row
  begin
     if (updating and nvl(:old.ename,'x') <>
nvl(:new.ename,'x'))
     then
        delete from upper_ename where
            ename = upper(:old.ename) and rid = :old.rowid;
        insert into upper_ename values
        (upper(:new.ename),:new.rowid );
     elsif ( inserting )
     then
        insert into upper_ename values
        (upper(:new.ename),:new.rowid );
     end if;
   end;

   and then query:

    select * from
       emp where rowid in ( select rid
                              from upper_ename
                             where ename = 'KING' );

    or
    select emp.*
       from emp, upper_emp
      where emp.rowid = upper_emp.rid
        and upper_emp.ename = 'KING';

(thats much like a function based index but your application
must be aware of it).
Review & Followup

      Rating:
      4
Goodness  June 07, 2001
Reviewer:
      Andy  from Kuala Lumpur, Malaysia
What would I do without this ability to search through the archives!

      Rating: 3
does it really
      work ?  December 17, 2001
Reviewer:  Reader
       from NY
Does case statement work in forms and reports 6.x and > ?

or are there any settings required for it ?

          Followup:

They may (like PLSQL in the database in 8i) not recognize it.  The workaround
until they do is to

o hide the construct with dynamic sql
o use a view.

      Rating: 3
From your
      book  December 18, 2001
Reviewer:  Gururaj
      Kulkarni  from Norcross,GA USA
Tom,

I did the following steps to create and make use of
function based indexes. I have followed the way which you
have described in your book. I have the spooled file from
my sql*plus for your reference.

And I would like to know..
1) Why my query plan is showing table access full ?
2) And how these parameters will be helpful ?
   i) QUERY_REWRITE_ENABLE = TRUE
   ii)QUERY_REWRITE_INTEGRITY=TRUSTED

SQL> create index emp_upper_idx on emp(upper(ename));

Index created.

SQL> analyze table emp compute statistics
  2  for table
  3  for all indexed columns
  4  for all indexes;

Table analyzed.

SQL> alter session set query_rewrite_enabled=true;

Session altered.

SQL> alter session set query_rewrite_integrity=trusted;

Session altered.

SQL> set autotrace on explain;
SQL> select ename,empno,sal from emp where upper(ename)='KING';

ENAME          EMPNO       SAL

---------- --------- ---------

KING            7839      5000

Execution Plan
----------------------------------------------------------

   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=40)

   1    0   TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=1 Bytes=40)

SQL> update emp set ename=initcap(ename);

14 rows updated.

Execution Plan
----------------------------------------------------------

   0      UPDATE STATEMENT Optimizer=CHOOSE (Cost=1 Card=14 Bytes=196)

   1    0   UPDATE OF 'EMP'

   2    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=14 Bytes=196)

SQL> select ename,empno,sal from emp where upper(ename)='KING';

ENAME          EMPNO       SAL

---------- --------- ---------

King            7839      5000

Execution Plan
----------------------------------------------------------

   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=40)

   1    0   TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=1 Bytes=40)

SQL> spool off;

Thanks in advance

          Followup:

There are only 14 rows in EMP.  that is not enough to invoke the use of an index
by the CBO.

If we TRICK it into thinking there are 100,000 rows -- it'll use the index:

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

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create index emp_upper_idx on
emp(upper(ename));

Index created.

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

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set query_rewrite_enabled=true;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set
query_rewrite_integrity=trusted;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on explain;
ops$tkyte@ORA817DEV.US.ORACLE.COM> select ename,empno,sal from emp where
upper(ename)='KING';

ENAME           EMPNO        SAL
---------- ---------- ----------
KING             7839       5000

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=33)
   1    0   TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=1 Bytes=33)

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_stats.set_table_stats( user, 'EMP',
numrows => 100000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select ename,empno,sal from emp where
upper(ename)='KING';

ENAME           EMPNO        SAL
---------- ---------- ----------
KING             7839       5000

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=7143 Bytes=235719)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=2 Card=7143
Bytes=235719)
   2    1     INDEX (RANGE SCAN) OF 'EMP_UPPER_IDX' (NON-UNIQUE) (Cost=1
Card=7143)

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off

      Rating: 3
Continuing my
      previous feedback  December 19, 2001
Reviewer:
      Gururaj Kulkarni  from Norcross,GA USA
Tom,

Thanks for the reply. I tried it and it works.
I tried another thing..I set numrows back to 14 and tried
excuting the same query. This time surprisingly my explain plan shows it is
using my functional index.

1)Could you tell me why ?

2)I wanted to know why have you used the parameters
QUERY_REWRITE_ENABLED and QUERY_REWRITE_INTEGRITY before using the query in your
book ?

SQL> exec dbms_stats.set_table_stats(user,'EMP',numrows=>14);

PL/SQL procedure successfully completed.

SQL> set autotrace on explain
SQL> select ename,sal,empno from emp
  2  where upper(ename)='KING';

ENAME            SAL     EMPNO
---------- --------- ---------
King            5000      7839

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=40)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=2 Card=1 Byte
          s=40)

   2    1     INDEX (RANGE SCAN) OF 'EMP_UPPER_IDX' (NON-UNIQUE) (Cost
          =1 Card=1)

          Followup:

1) it is because the first call to dbms_stats, seeing that the number of blocks
wasn't set in user_tables, set it to a high number.  The second call to
dbms_stats -- seeing that blocks wasn't set -- did not reset it (it is like you
deleted the rows -- the number of BLOCKS won't go down).

So the cost of the FULL SCAN was high because the estimated number of blocks was
high (even though there were just 14 rows -- a full scan reads to the high water
mark which we set artificially high).

Use this:
exec dbms_stats.set_table_stats( user, 'EMP', numrows => 14, numblks => 1 );

and it'll go back to the full scan.

2) i used them because they are a prerequisite for having an FBI get used and by
default they are not enabled.  I would have everyone scratching their heads
wondering why.  It was easier to demonstrate with them on -- right there,
explicitly -- then to say "add this to your init.ora, bounce the instance,
etc"...

      Rating: 5
Thank
      you  December 20, 2001
Reviewer:  Gururaj
      Kulkarni  from Norcross,GA USA
Tom,

Thanks for the explaination. It works.

-Gururaj

      Rating:
      4
  January 24, 2002
Reviewer:  Ron
      Chennells  from UK
I'm working on an appliciation that uses the shadow column
approach. I'd like to change it to a function based index
method. As an extra complication the application currently
genrates the queries dynamically using string concatenation
rather than bind variables.I'm considering using the
cursor_sharing=force parameter as a work around.
My question. Are there any interactions between the function
based index and cursor_sharing parameter that I need to consider ? Does the
addition of the wildcard character as shown have any effect ?

Typical case insensitive query.

WHERE cemp_surname_uc_c LIKE 'CH%'

          Followup:

Yes, there can be -- not with FBI's in general, just with the 100% use of bind
variables everywhere.

For example, where x like 'CH%' is a good candidate for an index typically.
"where x like :x" might NOT be according to the optimizer.  That query may need
a hint in order to use the index (might not, depends).

There can be other more insidous side effects with cursor sharing and FBI's
however.  Suppose you create:

create index fbi on t( substr( x, 1, 5 ) );

Now, a query like:

select * from t where substr( x, 1, 5 ) = '12345'

should use an index right -- well, cursor sharing = force goes all out and turns
that into:

where substr( x, :b1, :b2 ) = :b3

whoops -- we don't know that is 1 and 5 in the substr anymore, cannot use the
FBI.

If you have my book -- I go into this in some detail in chapter 10, the
downsides to cursor sharing = force, it can have other side effects.  I consider
it to be a band-aid, a temporary crutch, that can help you till you actually fix
the underlying problem, not a solution.

      Rating: 5
Intermedia and
      Case insensitive  November 13, 2002
Reviewer:  A
      Reader  from USA
Hi Tom,
At the beginning of this thread you mentioned that intermedia can be used in
resolve the case sensitive issue when Oracle Standard Edition is used.  Can you
please give a little bit detail about the steps or point to URL, other than
Oracle documentation, where I can read and learn more.  Our developers don’t
like the shadow-column/trigger solution.
Thank you very much for your help.

          Followup:

why other then the documentation?  Is there an issue with it?  (it does sort of
cover the topic)

You can look on otn.oracle.com

you can search for ctxsys on this site.

      Rating: 5
SQL Server
      Character Sets  November 14, 2002
Reviewer:
      Andy Gilfrin  from Dover, Kent, England
My boss spent 3 weeks developing a procedure in our HR application cant remember
exactly what it did, but it was for a massive deal we were trying to get.

We had to prove we could do something or other to get the deal so I turned up on
site with a CD containing this procedure all ready to impress but it turns out
they had a different case sensitive sort order (what was origionally described
here as a character set) so this procedure wouldnt run because my boss had
decided to go against our standards and use uppercase table names in his
select/update statments.

I spent 1 hour trying to track down all of the uppercase characters and replace
them but it was an impossible task considering we said we could demonstrate this
straight out the box.

In SQL Server 6.5 and 7 the sort order was set at server level, in 2000 this
changed to database level so different DB's on the same server could use a
mixture of sort orders.

The thing to remeber is this if your trying to find 'Jones' in Oracle you use
'Jones' in the where clause and you get back 'Jones'

In SQL server with a case insensitive sort order (which is default as I
remember) you get 'Jones','JONES',JoNes' etc etc.

So you now have the problem in SQL server that you have to use a function to get
the exact match. i.e. benefit in one way restrictive in many others

      Rating: 5
Why UPPER(col)
      and LOWER(col) return different values?  February 19, 2003

Reviewer:  A Reader  from USA
create table t (c1 number,c2 varchar2(20));

insert into t values(1,'Ø 2 cm');
insert into t values(2,'Ø 2.5 mm');
insert into t values(3,'Ø 1,2 x 15,6 cm');
insert into t values(4,'Ø 10 mm');

select * from t where upper( c2 ) >= 'Ø 2';
        C1 C2
---------- --------------------
         1 Ø 2 cm
         2 Ø 2.5 mm

select * from t where lower( c2 ) >= 'Ø 2';

        C1 C2
---------- --------------------
         1 Ø 2 cm
         2 Ø 2.5 mm
         3 Ø 1,2 x 15,6 cm
         4 Ø 10 mm

          Followup:

what is "theta" -- I see a funky character there.

999 times out of 1000 this is due to a character set issue.  what is your CLIENT
character set (nls_lang) and what is your DATABASE character set.  Betcha they
are different -- to the point that whatever "theta" is -- it is not a valid
character in on or the other character set and in the conversion -- all bets are
off.  The upper case "theta" may be very different from the lower case "theta"

      Rating: 5
Upper and Lower
      return different results  February 19, 2003

Reviewer:  A Reader  from USA
I tried different languages and the results are similar - upper and lower
function return different results. I real case, the client side is GERMAN and
the server side is AMERICAN.  The LOWER seems to treat the "2" after the "theta"
as a number and considers all the numerical digits in the string.  The UPPER
seems considering only the first numerical digit.
I tried the following:
select ascii(lower('Ø')) from dual;
which returns 248, and
select ascii(upper('Ø')) from dual;
which returns 216.
I still don't understand why the results for UPPER and LOWER functions are
different.
I thought as long as we can get result from ascii function, it should be a valid
character.

Thank you.

          Followup:

It is not the "2" -- it is the theta --

in your characterset (not LANGUAGE, character set) lower theta is ascii 248,
upper theta is 216 -- that is upper theta <> lower theta -- hence the return
values are NATUALLY different.

pretend theta is 'a'

where 'A' || something >= 'A' || something_else

vs

where 'a' || something >= 'A' || something_else

the second one is always true, because 'a' > 'A'.  The first one is SOMETIMES
true depending on the value of something and something else because 'A' = 'A',
not >

      Rating: 5
UPPER and LOWER
      return same results now.  February 19, 2003

Reviewer:  A Reader  from USA
Thank you very much for the explaination.  Now I get it.  Putting the functions
on both sides:
select * from t where lower(c2)>=lower('Ø 2');
or
select * from t where upper(c2)>=upper('Ø 2');

will always get the correct results.

Thank for your help.

      Rating: 5
Rule Based
      Optimizer and FBI's  April 02, 2003
Reviewer:
      Sachin  from Germany
Database: 8.1.7.4.0 Enterprise Edition
I know that to use Function Based Indices (FBI's)
you have to have Cost Based Optimizer.
We have lot of web based queries that upper all the columns in the where
clause...and we have Rule Based optimizer cuurently (plan to go to CBO..at some
point...but not right now)...
in the mean time....Here is what my solution is..please comment:
Have statistics computed on some of the big tables (invloved in slow
queries)....and

option 1: set the optimizer to "choose" for the whole database

option 2: set the optimizer to choose right before returning the recordset to
the web page for a session.

The table that is loosing indexes because of UPPERs has about 300,000 rows in
it...and the queries do upper for almost all the column (web based search)...
So, shadow column will add a lot of data....and it will be lot of shadows :)

          Followup:

or different approach (for now)

just use a hint in thost queries.  a hint will use CBO, a hint against
unanalyzed tables that produces the plan you want is a whole lot like the RBO
(predicable, always the same boring plan).  Just promise to remove the hints
when you use the CBO.

if you compute stats on SOME tables and leave the optimizer at choose -- any
query that touches them will use the CBO.

      Rating: 5
Hints
      CBO  April 03, 2003
Reviewer:  Sachin
       from Germany
"Just Promise to remove hints...while using CBO"
I am analyzing the tables involved....and adding the hint to use the "upper"
FBI...
Why do we have to remove hints while using CBO....

          Followup:

no - don't analyze the tables, if you are in choose and you have SOME tables
analyzed, but not all -- and you haven't tested your application thoroughly --
you'll be setting yourself up for the worst week of your life....

You remove the hints when using the CBO so the CBO can do its job.

      Rating: 5
hints
      CBO  April 03, 2003
Reviewer:  sachin
       from germany
Thanks...

I wasn't going to set optimizer to choose..since you showed the "HINT"
path...but I was under the impression..that
if the database's optimizer mode is rule...
and we use a hint for index...database will take the Cost Based approach/use
hinted index only if the table is analyzed...guess that is not true...

Thanks again....

          Followup:

I was just reading your other note:

..
option 1: set the optimizer to "choose" for the whole database

option 2: set the optimizer to choose right before returning the recordset to
the web page for a session.

....

option 0 (do nothing) leaves the optimizer mode at choose.
options 1 and 2 do the same..

sooo, naturally I assumed.  If you are using hints, you need no stats.  If you
have stats on everything, you need no hints.

the moment you put a hint in there -- cbo kicks in and the FBI is available.

      Rating: 4
Still UPPER has
      a 'gotcha'..I guess  May 02, 2003
Reviewer:
      Wachaspati Pandey  from Tampa Florida USA
I have Oracle8i Enterprise Edition Release 8.1.7.4.0 which has a DB characterset
as UTF8.

I connect to it on UNIX through SQLPLUS after verifying that echo $NLS_LANG =
'AMERICAN_AMERICA.UTF8'

Once I logon to UNIX, I try this and get that error :

SQL> select upper('ï') from dual;
ERROR:
ORA-01756: quoted string not properly terminated

Also, if I have a table with data like this :
SQL> select * from demo;

COL1
----------
Adelaï¤¥

and I try this..
SQL> select upper(col1) from demo;

UPPER(COL1)
--------------
ADELAï¤¥

I do not get my letter ï in capital. Just 'dela' were made as caps.

Note : I am using the shadow column approach and if the user writes Adelaï¤¥ to
search against, I fail because
my DB UPPER has resulted in "ADELAï¤¥" which is surely not
same as "ADELAÏ¤¥" - which is the caps form, I do (in my ASP Page) before
comparing with shadow column to achieve case insensitive search.

Any help ideas...why is UPPER not working ???

As a side note, if the trial on UNIX is done with client NLS characterset set as
WE8ISO8859P1 (though DB is UTF8), we get followng :

$ export NLS_LANG='.WE8ISO8859P1';
$ sqlplus apps/p2p32apps1@p2pdb32

SQL*Plus: Release 8.0.6.0.0 - Production on Thu May 1 20:39:07 2003

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production

SQL> create table semo (col1 varchar2(234));

Table created.

SQL> insert into semo values ('Adelaï¤¥') ;

1 row created.

SQL> select upper(col1) from semo;

UPPER(COL1)
------------
ADELAÏ¤¥     <================SEEMS to WORK !!!!!!

Really *PUZZLED*

          Followup:

you lied to us.  You said "i'm going to give you UTF8 data" but then you used a
non-utf set of data.

Your client is NOT a utf8 client.  It is us7ascii or maybe we8iso ....

upper is working -- you just need set your NLS_LANG to something appropriate for
your client.

      Rating:
      5
  July 09, 2003
Reviewer:
      A reader

      Rating: 4
Case
      insensitive search with LIKE '%XXX%'  October 23, 2003

Reviewer:  Michael  from Germany
Hello Tom,

This is my test case:

SQL> l

create table t
(a varchar2(30) not null,
 b char(2000) default 'DUMMY1',
 c char(2000) default 'DUMMY2')
SQL> /

I want to search in the column a. The other columns are only there to make the
table "fat".

SQL> insert into t(a)
     select dbms_random.string('X', 30)
     from all_objects
     where rownum <= 20000;

SQL> commit;

This table has 20480 blocks (8k blocks) in 91 extents.
Then i create a function based index for the case insensitive search:

SQL> create index t_upper_a_idx on t(upper(a));

This index has 128 blocks in 16 extents.

SQL> analyze table t compute statistics
     for table for all indexed columns for all indexes;

OK, now the table and index are ready.
Here are some instance parameters:

SQL> show parameter optimizer

NAME                                 TYPE        VALUE
------------------------------------ ----------- -------
optimizer_dynamic_sampling           integer     1
optimizer_features_enable            string      9.2.0
optimizer_index_caching              integer     90
optimizer_index_cost_adj             integer     25
optimizer_max_permutations           integer     2000
optimizer_mode                       string      CHOOSE

SQL> show parameter query

NAME                                 TYPE        VALUE
------------------------------------ ----------- ---------
query_rewrite_enabled                string      TRUE
query_rewrite_integrity              string      enforced

SQL> set autotrace traceonly
SQL> set timing on
SQL> select a, b from t
  2  where upper(a) like upper('ef%3');

Abgelaufen: 00:00:00.03

Ausführungsplan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=16 Bytes=32304)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=5 Card=16 Bytes=32304)
   2    1     INDEX (RANGE SCAN) OF 'T_UPPER_A_IDX' (NON-UNIQUE) (Cost=2
Card=16)

Statistiken
----------------------------------------------------------
         47  recursive calls
          0  db block gets
         12  consistent gets
          6  physical reads
          0  redo size
       2466  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
          1  rows processed

This looks good and is fast.

But:

SQL> select a, b from t
     where upper(a) like upper('%ef%3');

11 Zeilen ausgewählt.

Abgelaufen: 00:00:41.08

Ausführungsplan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1929 Card=1000 Bytes=2019000)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=1929 Card=1000 Bytes=2019000)

Statistiken
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      20053  consistent gets
      18791  physical reads
          0  redo size
       2853  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         11  rows processed

This is very slow. Why does Oracle not fast full scan the index???
When i add a INDEX_FFS hint - it uses the index, but it's even much slower
(seems to go from index to table for *every* row):

SQL> select /*+ INDEX_FFS (T, T_UPPER_A_IDX) */ a, b
     from t
     where upper(a) like upper('%ef%3');

11 Zeilen ausgewählt.

Abgelaufen: 00:05:45.07

Ausführungsplan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=252 Card=1000 Bytes=2019000)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=252 Card=1000
Bytes=2019000)
   2    1     INDEX (RANGE SCAN) OF 'T_UPPER_A_IDX' (NON-UNIQUE) (Cost=7
Card=1000)

Statistiken
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      20119  consistent gets
      19750  physical reads
          0  redo size
       2853  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         11  rows processed

Then i tried the following query:

SQL> select a, b from t
     where rowid in (select /*+ INDEX_FFS (T, T_UPPER_A_INDX) */ rowid
                from t
                where upper(a) like upper('%ef%3'));

11 Zeilen ausgewählt.

Abgelaufen: 00:00:00.03

Ausführungsplan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1013 Card=1000 Bytes=2050000)
   1    0   NESTED LOOPS (Cost=1013 Card=1000 Bytes=2050000)
   2    1     INDEX (FAST FULL SCAN) OF 'T_UPPER_A_IDX' (NON-UNIQUE) (Cost=13
Card=1000 Bytes=24000)
   3    1     TABLE ACCESS (BY USER ROWID) OF 'T' (Cost=1 Card=1 Bytes=2026)

Statistiken
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        136  consistent gets
          0  physical reads
          0  redo size
       2853  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         11  rows processed

This is very fast! (Without the INDEX_FFS hint it's slow!)

But i don't really know why this query works fast, and i try to avoid using
hints in production code/queries.
What to do?

Regards, Michael.

          Followup:

use a bind variable.  period.

one plan will be generated.  the index scan.

October 23, 2003 Logan -- Thanks for the question regarding
      "Uncommitted Transactions and online redo log", version 9.0.1

      You Asked
Tom,

On Page 2-28 of Oracle 9i (9.0.1) Concepts Guide, under the 'Online Redo Log'
section, it says:

Online redo log files record all changes made to the database, including both
"uncommitted" and committed changes.

My question is:

I can understand the need for the committed ones. Why and how are the
uncommitted transaction data stored in the online redo log? What is the reason?

Regards,
Logan Palanisamy

      and we said...
If you have my book "Expert one on one Oracle" -- i cover how transactions are
processed but a real easy way to understand this is:

a) suppose you have a 512k log buffer (ram)
b) you update 1m of data

where would we put the other 512k of data if the log buffer is full?  We would
have to have a log buffer (ram) that was BIGGER then then sum of all uncommitted
redo generated at the same time (with many users, this would be huge).  Disk is
where we keep it therefore.

That is one SIMPLE case -- there are lots of other reasons but that one is
sufficient to understand the need to have committed and uncommitted transactions
logged in the redo log files.
Review &
      Followup

      Rating: 3
Roll forward,
      roll back  November 08, 2002
Reviewer:  Juan
      Guascarancas Pena  from Mexico
Tom,

If we need to apply these redo logs to the database then Oracle would roll
forward (is it statement by statement?): it would update a record that was
rolled back, for instance. Is the roll back phase of recovery done after or
during the roll forward? It rolls back using the redo also, right?

One last question: when information is stored on the rollback segments, is it
also stored on the redo log?

Thanks

PS: Alright, I won't ask more questions today. But wait until monday!!!

          Followup:

When we recover we "roll forward" (from the redo logs) and then rollback any
uncommitted transactions.

undo is logged, yes.  so we recover UNDO as well.

Well, next week is pretty much all about OracleWorld -- won't be taking too many
questions over the web but if your in San Francisco -- I can answer in person ;)

      Rating: 3
Committed
      transactions in Redo Log   February 16, 2003

Reviewer:  Reddy  from Bangalore, India
Hi tom

I have one question regarding to redo log committed transaction.

When I am taking hot backup, my datafile size is say 30GB. while taking hot
backup the datafile will get updated with commited transactins? If not my redo
log fils will have the commited transactions. Once the redo log files are full
those will be archived. After completion backup mode( alter tablespace end
backup) the datafile will be normal. this time the datafile should get update
with commited transactions.

here for synchornizing datafile whether it will read from archived log files? If
yes what about the performance.

Thanks
Reddy

          Followup:

we write to datafiles constantly during a hot backup.  Your datafiles will in
fact contain

o transactions committed a long time ago
o transactions committed during the backup
o TRANSACTIONS NOT YET COMMITTED even
o possibly even blocks that were modified but rolled back during the backup

It is known as a "fuzzy file", it needs redo to make it well again.

Everything goes on as normal during a backup -- we just log a tiny bit extra to
help us recover.

We do not "syncronize the file" after a backup since we've been writing to it
all along.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:271815712711
for info.

      Rating: 5
When does redo
      log file status change  October 23, 2003
Reviewer:
      A reader
What needs to happen to change the status of an online redo log file from active
to inactive? The manuals says that active redo log file contains information
required for crash recovery. Does this mean that an active redo log file
contains information that has not yet been written to the datafiles? Does this
imply that on the next checkpoint the active redo log file will switch to
inactive status?

Your few lines are worth several pages of manuals...

Thanks

          Followup:

it contains redo that is protecting blocks in the buffer cache that have NOT YET
been checkpointed.  they are needed to recover those blocks.

until the blocks they protect are flushed, physically written, that redo log is
"active".

checkpoints happen in the background, almost continously, in a lazy fashion.  it
could be five or N checkpoints in the future before that redo log goes inactive.
 we can (and will) fire a new checkpoint before an in progress one finishes

October 23, 2003 fentu -- Thanks for the question regarding "CPU
      utilization", version 8.1.5

      You Asked
How can I reduce CPU utilization of Oracle database?

      and we said...
shutdown abort

works well ;)

Are you at 100% utilization?  If not, you haven't accomplished your job yet.
You cannot put CPU in the bank and save it for later.  So, if you are running
with idle cycles you should be looking for more ways to use it.

You can do things like:

o Tune queries, use alternate approaches
o reduce the workload
o using MTS (aka shared server) which is just a way to reduce the workload
o identifying WHY you are using so much CPU (this is probably the most important
  and workable way!!!) and then changing the processes that cause this

But then again, if you are at 99.99% utilization -- pat yourself on the back,
you've done well.

Review &
      Followup

      Rating:
      3
Excellent.......  October 24, 2002

Reviewer:  Hitesh  from UK
Thanks a lot.....

      Rating:
      3
  February 04, 2003
Reviewer:  Mike
       from TX
"
Are you at 100% utilization?  If not, you haven't accomplished your job yet.
"

If i had Oracle application running and the CPU utilization was under 20%, what
should i look into to raise the CPU utilization? And what could be for the low
utilization.

          Followup:

Just might mean you bought 5x the machine you needed.

If you are doing a "batch operation" at that point in time, you may have
neglected to take advantage of parallelism.  Eg: instead of using a single
process to load a million records, use 10 that each load 100,000 records.
Instead of running a plsql procedure that loops over and processes a million
records, run 10 copies of it -- each of which process some non-overlapping
subset of the data...

If you are a simple transactional system -- where parallel operations don't make
sense -- you just have 5x more machine then you need.

      Rating:
      3
  February 05, 2003
Reviewer:  Mike
       from TX
Tom, Thanks. I think I understood how the parallelism works.
I was looking for the diagnose methods for causes of "Low CPU Utilization". For
example, use which v$_ or DBA_ views to find out the problems. Does it most
likely waiting for the I/O causing the low CPU utilization?

          Followup:

it would be anything that causes your application to block and wait

IO is a biggy
enqueue waits (waiting on a lock) would be another

look at the big waits -- those are generally the things you are doing when you
are not using the CPU

      Rating: 4
cpu under
      utilization case  February 05, 2003
Reviewer:
      Mike  from TX
Thanks Tom.
I am doing a little test of the cpu utilization case on my laptop. I know the
follow query will generate alot i/o:
select count(*) from t1, t2;
t1 is about 1/5 million and t2 is about 150000.

the cpu utilization is 60%. To find out what's the waits:

mburk@jnas> l
  1  select event, sid, seq#,
  2         wait_time,
  3         seconds_in_wait,
  4  /*     state,
  5         p1text, p1, p1raw,
  6         p2text, p2, p2raw,
  7         p3text, p3, p3raw
  8         p1text || ' = ' || p1 parm1,
  9         p2text || ' = ' || p2 parm2,
 10         p3text || ' = ' || p3 parm3
 11  */
 12         decode( p1text, null, null,
 13                 p1text || ' = ' || p1 ) ||
 14         decode( p2text, null, null,
 15                 ', ' || p2text || ' = ' || p2 ) ||
 16         decode( p3text, null, null,
 17                 ', ' || p3text || ' = ' || p3 )
 18         parameters
 19    from v$session_wait
 20   where event not in ( 'pmon timer', 'rdbms ipc message', 'smon timer',
 21                        'WMON goes to sleep',
 22                        'SQL*Net message from client' )
 23*  order by event, p1, p2
mburk@jnas>

mburk@jnas> @vsessw

Wed Feb 05

                                                           Session Waits

                                           WAIT SECONDS
EVENT                           SID    SEQ TIME IN WAIT PARAMETERS
----------------------------- ----- ------ ---- -------
-------------------------
null event                        9  23689    1
                                 11     83   -1
wakeup time manager               8   2719           20

***** the sid 9 is doing the query.
***** What's the "null event"?

Check the session stats:
mburk@jnas> l
  1  select s.sid, s.value, n.name
  2    from v$sesstat s, v$statname n
  3   where n.statistic# = s.statistic#
  4     and s.value <> 0
  5     and s.sid like '&sid'
  6     and n.name like '&name'
  7     and s.statistic# like '&statistic'
  8*  order by sid, n.name
mburk@jnas>
mburk@jnas> @vsesstat
Enter value for sid: 9
old   5:    and s.sid like '&sid'
new   5:    and s.sid like '9'
Enter value for name: %
old   6:    and n.name like '&name'
new   6:    and n.name like '%'
Enter value for statistic: %
old   7:    and s.statistic# like '&statistic'
new   7:    and s.statistic# like '%'

Wed Feb 05                                                             page    1
                       Session System Statistics - By Sid

 SID            VALUE NAME
---- ---------------- ----------------------------------------------------------
   9                3 CPU used by this session
                    3 CPU used when call started
                6,053 Cached Commit SCN referenced
                    4 Commit SCN cached
                   49 SQL*Net roundtrips to/from client
                6,762 buffer is not pinned count
                   18 buffer is pinned count
                3,990 bytes received via SQL*Net from client
                3,813 bytes sent via SQL*Net to client
                  111 calls to get snapshot scn: kcmgss
                    1 change write time
                    4 cleanout - number of ktugct calls
                    4 cleanouts only - consistent read gets
                   35 cluster key scan block gets
                   35 cluster key scans
                    4 commit txn count during cleanout
                6,938 consistent gets
                  146 consistent gets - examination
                   10 cursor authentications
                    4 db block changes
                    4 db block gets
                    4 dirty buffers inspected
                    8 enqueue releases
                    8 enqueue requests
                   89 execute count
                    4 free buffer inspected
                6,651 free buffer requested
                   64 hot buffers moved to head of LRU
                    4 immediate (CR) block cleanout applications
                   52 index fetch by key
                   53 index scans kdiixs1
                    1 logons cumulative
                    1 logons current
                6,705 no work - consistent read gets
                   39 opened cursors cumulative
                    1 opened cursors current
                    3 parse count (hard)
                   39 parse count (total)
                    5 parse time cpu
                   29 parse time elapsed
            1,500,624 physical reads
            1,493,974 physical reads direct
                  236 physical writes
                  236 physical writes direct
                  236 physical writes non checkpoint
                6,395 prefetched blocks
        1,044,465,908 process last non-idle time
                1,537 recursive calls
                    3 recursive cpu usage

Wed Feb 05                                                             page    2
                       Session System Statistics - By Sid

 SID            VALUE NAME
---- ---------------- ----------------------------------------------------------
   9                4 redo entries
                  240 redo size
                   13 rows fetched via callback
        1,044,465,908 session connect time
                6,942 session logical reads
            1,662,788 session pga memory
            2,711,364 session pga memory max
            1,225,904 session uga memory
            2,040,880 session uga memory max
                   53 shared hash latch upgrades - no wait
                    1 sorts (disk)
                   45 sorts (memory)
              479,387 sorts (rows)
                   41 table fetch by rowid
                6,627 table scan blocks gotten
              485,574 table scan rows gotten
                    2 table scans (long tables)
                    5 table scans (short tables)
                   47 user calls
                   38 workarea executions - optimal
                1,105 workarea memory allocated

70 rows selected.

the results show session 9 using 3/100 seconds of CPU time.
The LIO=6,938 and PIO=1,500,624.

Is this supposed method to find what the casue of low CPU utilization?
Thanks

          Followup:

whats the version

in order to find out waits for "something" -- eg "for that query" you need to
mimick statspack.

start the process
o snapshot (copy) v$sesswait in another sessoin
o wait a bit, remember how long you waited.
o snapshot it again

then join the snaps together and compare the diffs.  You are seeing the stats
for the session from the begining of time for the session.

CPU time and other things will be meaningless averaged over that amount of time.
 but, if you snapshot, wait, snapshot and then find out you have 1.1 cpu seconds
and the wait was say 2 seconds long, you know you were 50% on cpu for
example....

      Rating: 2
CPU
      hungry  February 05, 2003
Reviewer:
      A reader
> But then again, if you are at 99.99% utilization -- pat
> yourself on the back,
> you've done well.

I wouldn't be patting anyone on the back. On the contrary, I'd be scratching my
poor old bald head then.

If I'm at 99,99%, I'm on the edge. There has to be some "comfort zone".

That talk of running at 99.99% is purely theoretical stuff -- very cool not
wasting your cycles.

In my experience though, I like running at 60-80%. We just can't always predict
something is going to jump in and eat up our time slices.

Once -- I remember -- we had a memory leak in a snmp process and it began
consuming cpu and memory. We had time to spot and fix the problem because we
were not short of cpu. Had we been, probably we'd have had a hard time with our
end-users.

I also like using cpu quotas like with profiles and resource manager. They work
fine.

          Followup:

comfort zone = "wasted cycles"

you cannot, repeat, cannot put CPU in the bank.

If you are constantly running at 90% -- what use -- what use -- is that other
10%

Can you save it?
Can you use it else where?

If you go for the "don't know what I need so I'll just double it" thats great
(hardware vendors count on that -- almost as much as they count on no one really
sizing systems -- just buy as much as we have money for).

If an snmp process was running away -- it has the ability to more then consume
100% of a CPU's resources on the machine.  Having "spare" cpu buys you nothing.
Doesn't even make sense.  A runaway is a runaway -- it only takes one to
completely overpower a CPU.  So, if you want to buy 40% more CPUs (say a 10 cpu
machine instead of 6) just in case you get 4 runaway processes -- more power to
you.

Sort of like buying a stock race car for street driving.  Lots of horsepower,
never going to use it.

That last paragraph -- now that makes sense.  The rest of it -- don't agree even
a little.  Waste of money -- that comfort zone does one thing (well, besides
costing money) and that is "makes you feel better".  If that is worth 40% extra
cost -- great.  Do you do the same for RAM?  For disk?  hardware vendors must
*love* you.  Sorry -- just really disagree here.  I see people running around
crazy cause the machine is "running at 90% plus".  So what?  It is *supposed*
to.  I want to load up a machine till it is running at capacity -- then, move
onto next machine.  (this is what grid computing for example is all about)

      Rating: 2
CPU Hungry --
      the mission  February 05, 2003
Reviewer:  Same
      guy as above
Sorry Tom, but I don't like the idea of a squeezed cpu. What if I need more
power all of a sudden, and I can't scale ?

Ok, I can't save my cpu cycles in a bank. But I can keep my cyles from being
used in case I need them. Not too many, not too little -- just enough.

I think you are exaggerating. I may be wrong, but it sounds like common sense to
me.

I think it's a matter of choices and opinions, and above all -- experience. I'd
never risk to be at 99%. My choice, my opinion, my experience. You have yours,
okay.

BTW, this discussion is more suited for a sys admin forum than here.

BTW 2 -- nice discussing with you. I liked it here ;-)

          Followup:

why if I need more power all of a sudden..... then you didn't size, didn't
think about what you needed to do

If you need more power all of a sudden -- you want to look at blade servers or
some similar technology that lets you re-assign power.

Sure, you can buy boxes 50% bigger then you need "just in case", or you can move
stuff around and get away with 1/3 the computing power.

Other "common sense" things I hear all of the time:

o rebuild indexes on a schedule.  everone knows indexes need to be rebuilt
o you must separate tables from indexes by tablespace, it is only common sense
o segments should be in as few extents as possible -- one extent is best by far
o a cold backup is "better" then hot
o you should shutdown your database frequently
o if your query is not using your indexes, the optimizer did the wrong thing
o adding more CPU to your system will make it go faster, especially if it is
100% utilized <<<=== that one is really funny actually

Oh this list of conventional wisdom goes on and on and on and on.

Too each their own.  Me, I will never ever sweat my production boxes running at
or near capacity.  Means I've done my job and my requisition for new hardware
will be taken seriously as people know I use what I order.

Your approach is like ordering two entree's at a restaurant and eating just a
little from each -- wasting the rest "just in case your eyes where smaller then
your stomache" ;)

      Rating: 2
CPU hungry
        February 05, 2003
Reviewer:  another
      reader
If your application uses the data base constantly you can let your server run at
the edge. But, this is not the reality. Processes don't run constantly. They run
in bursts.

Let's say that here is an application that uses 100% of the cpu for 10min every
day. This means that your server won't be able to respond to any other request
in the mean time.
For web applications, 10 min is an inifite time (you get a timeout msg). So,
this is not a good solution.

          Followup:

who runs in bursts?   and it's all about peak usage.

So, you are going to double your costs for 10 minutes?  or maybe fix that so it
uses 50% of the cpu for 20 minutes?   your choice.

      Rating: 3
It could depend
      on the type of workload  February 06, 2003

Reviewer:  Ramakrishna  from India
Dear Tom,
  You have said:
"Are you at 100% utilization?  If not, you haven't accomplished your job yet."
  I was going through the paper on 'Performance Management - Myths and Facts' by
Cary Millsap where he goes on to explain that, if it is a batch-only
environment, then 100% CPU is what one should aim for.  However, if it is an
interactive environment or a mixed environment, running at 100% could mean that
users are actually experiencing excessive delays due to queueing.  Is it right
to say that, for an interactive or mixed system, one should start considering
the purchase of more CPU when the existing system is constantly at, say 70 to
80% CPU utilization AND when the run queue is constantly going over 2?  If we
wait for CPU to be close to 100% in such a system, then perhaps response times
will have started degrading rapidly by then.  This means that it is better to
always have some spare headroom on such systems, doesn't it?

regards
Ramakrishna

          Followup:

ok, 99.9999%

point is -- no need to be concerned because you are actually using your CPU --
it is something you actually want to do.

You cannot put it in the bank.

Use it or lose it.

but in the end -- your choice.  some people will just not be comfortable at or
near capacity I guess.

      Rating: 1
cpu
      cycles  February 06, 2003
Reviewer:
      A reader
From Ahmed Alomari's Oracle8 & UNIX Performance Tuning published by Prentice
Hall:
http://www.amazon.com/exec/obidos/tg/detail/-/0130187062/qid=1044534932/sr=8-1/ref=sr_8_1/103-3789535-0772632?v=glance&s=books&n=507846
(this one is usually very well rated)

"User time percentages should range anywhere from 60% for applications that
issue a large number of system calls to 95% for an application with a minimal
number of system calls." (page 73)

That's what you'll find in virtually every book of this kind.

          Followup:

great -- go for it.  I stand humbly corrected.

No wait -- that is talking about the ratio of user to system calls.

Doesn't seem to be talking about CPU utilization at all.

Back on my soapbox:  USE IT OR LOSE IT.

There Ahmed is talking about User calls vs System calls.  You know, like the
output from top:

CPU states: 97.4% user,  2.3% system,  0.0% nice,  0.1% idle

      Rating: 2
CPU
      hungry  February 06, 2003
Reviewer:  Same
      guy
>who runs in bursts?   and it's all about peak usage.

aha... now, we're speaking the same language. That didn't seem to be the point
you were making ;)

The guys above and me (and literature) are talking of *mean time*, obviously.

You're talking of *peak* usage -- that's hardly measurable and very infrequent
-- not good metrics. You say you keep your *peak* at 99.99% ?

comfort zone = *peak* minus *mean* <> wasted cycles.

I hope this settles the case.

      Rating: 5
CPU
      Usage  February 06, 2003
Reviewer:  Tony
      Andrews  from London, UK
"Sort of like buying a stock race car for street driving.  Lots of horsepower,
never going to use it."

This is really not an area where I have any expertise ("shut up then!"), but I
wonder whether an analogy with supermarket checkouts might be more apt?

If you run a supermarket with 10 manned checkouts, and find that on average only
6 are serving cusomers at any time (i.e. 60% utilisation), then you are wasting
money - you only need 6 checkout staff.

On the other hand, if on average all 10 staff are busy all the time (100%
utilisation) then either you have just the right number, or maybe you haven't
got enough.  It depends how long the queues are (and whether you end up losing
customers as a result).

Now if you can monitor the waiting times of the customers, you can determine
whether or not you need more checkouts.  But if all you can monitor is the
checkout utilisation (you can't see the queues), then at 100% utilisation you
have no idea whether you have just enough checkouts, or whether you need twice
as many.

Does this analogy work?  Are the pessimists saying that all they can monitor is
the utilisation, so they need the spare CPU to be sure?  And is Tom saying that
you should be monitoring the waits/queues - if the throughput is acceptable, you
don't need more CPU, you have just the right amount?

          Followup:

Does this analogy work?

I liked it...

Are the pessimists saying that all they can monitor is
the utilisation, so they need the spare CPU to be sure?  And is Tom saying that
you should be monitoring the waits/queues - if the throughput is acceptable, you

don't need more CPU, you have just the right amount?

That is what I'm saying.  Thanks for saying is "more clearly"

It is like the cache hit ratio thingy.

someone:  "Hey, my cache hit is 99.99%, pretty good eh?"
me:    "oh, thats pretty bad -- that could indicate serious performance issues,
        then again, maybe not -- but in any case, by itself it is a meaningless
        number"

      Rating: 5
Thanks Tony,
      the analogy is great  February 06, 2003
Reviewer:
      A reader

      Rating:
      4
  February 07, 2003
Reviewer:  Mike
       from TX
The quote from "Essential System Administration" by Aeleen Frisch, 2nd Edition,
Published by O'Reily
Page 288
"High level of CPU usage are not a bad thing in themselves (quite the contrary,
in fact, when they mean that system is accomplishing a lot of useful work). The
mere presence of high CPU usage numbers means nothing in and itself. However, if
you are tracking down a system performance problem, and you see such levels of
CPU use consistently over a significant period of time, then you will have
determined that a shortage of CPU cycle is one factor contributing to that
problem (it may not be the total problem, however, we shall see)."

I agree with you on the CPU utilization.  However if there is true on the CPU
bottleneck, as Aeleen Frisch said "levels of CPU use consistently over a
significant period of time". How can we verify in Oracle database perspective it
is CPU resource lacking, ask the management for faster CPU or add more CPUs?

          Followup:

transaction throughput.  hit counts.  amount of work accomplished.

If the system uses 100% of the CPU to do 1tps and it uses 100% of the CPU to do
10tps and it uses 100% of the CPU to do little to no database work -- you have a
problem.

CPU utilization is alot like cache hit ratios (except of course people want low
cpu like golf scores and high cache hit like basketball scors).

"hey, I've got 99.9% cache hit -- pretty good eh".

Much like Aeleen Frisch pointed out for CPU, 99.9% cache hit could be an
indication of serious performance problems, or it might mean nothing or it could
mean things are going great.  you cannot tell from that one number.

Same with CPU -- as others have pointed out here -- you need to look at things
like your run queue, the service times, the backlog. was that 100% really trying
to be 200% or was that 100% really 100%?

it is just a number that has to be taken into consideration along with every
other number and by itself it not very meaningful.

      Rating: 5
what's the TPS?
        February 08, 2003
Reviewer:  A reader

          Followup:

transactions per second.

      Rating: 5
tps = total
      calls / total elapsed in the tkprof?  February 09, 2003

Reviewer:  A reader

          Followup:

No, TPS = transactions per second as reported by statspack for example.  Number
of commits/rollbacks in a period of time divided by period of time.

      Rating:
      5
  February 09, 2003
Reviewer:  Mike
       from TX
So, for a report server we can not use tps to find out the over loaded CPU.
What's should we look for?
Thanks alot

          Followup:

ok, executes / second then.  the load profile portion of a statspack report
would be useful -- right from the top....

      Rating: 5
CPU
      review  February 11, 2003
Reviewer:  Zoran
      Martic  from Dublin, Ireland
If you have CPU utilization at peak time that is >90% in user/kernel space
If you have more or equal active processes then the number of CPU's
YOU NEED TO BE PROUD OF THAT only if:
you tuned your SQL to use as lees as possible CPU (for example to read 3 blocks
instead of 10 blocks from the buffer cache)

The second myth is to have 20% backup CPU for who know what:
It is OK if the application is real-time - that means you have bound latency
(like I have 10ms for every business transaction for charging mobile phones in
real-time).

But usually we are talking about standard OLTP applications.
If you have 20 active processes at peak time and only 10 CPU's you will have to
say 100% larger latency.
That means if you spend 10ms on your SELECT why do you think that the big
problem is to spend 20ms and have 100% CPU utilization.

You need to be very very happy if you tuned all your SQL and your instance and
you are CPU bound.

I DO NOT UNDERSTAND THIS STATEMENT
"I want to leave 20% CPU for something that could happened"
Does this mean that you want to limit the number of processes at peak time to be
less or equal then the number of CPU's. Or you want to have the botleneck on I/O
or something else and to leave 20% idle and you have more processes then CPU's.
THIS IS NOT TOO HARD TO ACHIEVE. THERE ARE TWO METHODS:
1. Use more CPU's then you have processes (great idea if you have enough money,
I think IBM, Sun or Intel will be very helpful to achieve this)
2. Just build stupid application with stupid non-optimized SQL's and you will be
bounded on I/O or enqueue or something else (that means you can achieve 50% idle
with only 8 processes on 8 CPU machine). This will be great because many good
consultants and tuning experts will thank you for this, it will be more work for
smart people. Who wants everything to be optimized.

Tom, I supported you in idea that 100% CPU utilization at peak time and that is
not that much in wait I/O is great (because I/O operations are more time
expensive then CPU operations you in any case need to perform after
reading/writing to disk).
This is OK for I think most (95%) applications and not for real-time maxtime
latency bounded or fixed-time latency bounded (like video streaming)
applications, but this is another story.

Regards,
Zoran

      Rating: 4
cpu vs
      processes  May 27, 2003
Reviewer:  Raja
       from Chennai, India
Tom, is there any connection between number of concurrent users and number of
cpus? The reason i ask is that we are planning for new servers and we want to
decide how many cpus the servers should have? Following are the specifications:

(1) it is a oltp and we anticipate about 300 users to start with and may be 100
to 200 concurrent sessions.
(2) The server will have one instance supporting one database.
(3) We are thinking about RAM of 1G. Initial SGA size will be around 400MB.
SGA_MAX_SIZE will be 500MB.
(4) It will be dedicated server configuration. In future we may go for MTS if
number of users increase but not now as we think shared server is not necessary
now.
(5) Oracle version 9.2.0 on Sun solaris 5.8.
(6) We are not going to run any parallel dml/query on this instance.
(7) the database will be in ARCHIVELOG as we don't run any of our production
databases in NOARCHIVELOG.

Our second server will support a reporting database again with Oracle 9.2.0 on
Sun solaris. We will have read only snapshots on this server that will be
refreshed every evening from our prod oltp database. We are planning to use
parallel query feature on this server. There will be a few reports generated
every evening. How many cpus and RAM we should go for on this reporting server.
We may have concurrent reports run on this server and at present we don't know
how many of them.

I hope I have provided sufficient info to you to make your valuable suggestion.
Thanks so much for your time. I have a week to make a decision on this. If you
are too busy now, I would appreciate if you please try to answer later. Thanks
again.

          Followup:

1) insufficient data.  100 to 200 concurrently ACTIVE sessions (hitting the
enter key simultaneously).  Improbably with only 300 users.  Probably more like
10-20 maximum active sessions.  that is key -- ACTIVE.

2) perfect

3) not relevant, no relationship between number of users and SGA size can be
drawn.

4) perfect

5) great

6) that derives from the "we are oltp"

7) perfect

You should use data guard instead of replication for the reporting instance.
You'll get a reporting site, failover site, a switchover site, a site you can
offload backups too -- all with the least impact on the production site and the
admins.

You'll need to benchmark in order to size your system.  If you ask 1,000 people
for the definition of a "transaction", you'll probably get about 5,000 different
responses.  Are your reports cpu intensives (lots of OLAP/analytics/sorting) or
disk instensive.  Do your oltp users do lots of cpu intensive things or not.

You need to benchmark, that will be the only accurate way to size.  Every day
mhz goes up, disk speed change, rules of thumb that where inaccurate a year ago
don't even apply today.

I can say I run over 1,000 users doing "oltpish" applications on a 7 year old 4
cpu sparc.  It is called "asktom".  but the characteristics of that application
are just totally different from anything you would build.

      Rating: 5
High
      Throughput  May 28, 2003
Reviewer:  Sean Bu
       from Atlanta
Tom,
I have been brought to give recommendations on the OLTP of a web application,
which is a 3-tier configuration, 9iAS R2 with the repository and a 9i R2
customer database. They want me to find the problems as well as to give the
recommendations for the 100X more active users. The system has been in the
production for more than a half-year. The following is the list items I think I
shall zoom in, which are in the order of the importance:

1)    Bind variables
2)    Tune bad queries
3)    Size PGA_SIZE_TARGET
4)    Configure JDBC Connection Pooling
5)    Use Raid 1+0
6)    DB_WRITER_PROCESSES > 1

Do I miss some?

Thanks

Sean

Ps. Someone else are looking into other 9ias components, such as webcahce and
oc4j clusters, so I did not put them in the list though they might be equally
important.

          Followup:

do have have problems?  I mean, you cannot have a plan of attack without knowing
what you are attacking.....

you need to have a set of goal posts, so you'll know when you cross them.

      Rating:
      5
Thanks!  May 28, 2003
Reviewer:
      Raja  from Chennai, India
Will consider dataguard for reporting server. Thanks for your explanation. It
makes a lot of sense to me.

      Rating:
      4
  May 28, 2003
Reviewer:
      A reader
......
do have have problems?  I mean, you cannot have a plan of attack without knowing

what you are attacking.....

you need to have a set of goal posts, so you'll know when you cross them.
......

What I tried to say was the list of "checking" of the health of a typical busy
OLTP. Look at the checking list,Do I miss any important. Of cuase I will look
into the report of statspack.

Thanks.

Sean

          Followup:

there is no such thing as a "typical system" but anyway, assuming they are not
screaming "it is slow", this is somewhat subjective.

get a 15 minute stats pack during "peak" time.

look at the soft parse % and make sure it is above 99%.
look at the waits and see if they are "reasonable"
look at the top sql and see if they look "reasonable"

don't see where raid 10 comes into play necessarily.

      Rating:
      4
  May 28, 2003
Reviewer:
      A reader
When CPU is > 90%, generally, things "seem" sluggish. Even a simple 'ls -l' on a
UNIX shell prompt takes a long time. Opening a telnet session takes long.
Everything takes long.

So, how is this a good thing? I realize that CPU util, by itself, is a
meaningless number, but all of us agree that it is, most likely, the precursor
or symptom of some deeper lurking problem. How to find out what this problem is?

          Followup:

I don't see sluggish response from ls and such at 90% on my systems, but
anyway...

if you are running at 90% continously that is not good.
if you peak at 90% from time to time that is ok.

how to find out what the "problem" is -- you analyze your system. find out what
is taking 90% of the cpu -- is it 1,000 little things or one big thing.  is it
something you need to do or can you skip it all together (be surprised how often
that is true)

      Rating:
      5
  May 29, 2003
Reviewer:  Sean
       from Atlanta
"
.....
    don't see where raid 10 comes into play necessarily.
.....

they are current on raid 5, and i want raid 1+0 for better writing throughput
for 100X current active sessions later.

          Followup:

and N times the disk storage needs.  raid 5 is typically bought on the cheap.
raid 10 is at the high end of the cost scale.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:359617936136
but again -- you are taking the wrong approach.

Sure raid 10 is "faster" then raid 5 -- however -- do they have an identifiable
problem that needs FIXING.  If so, identify it and then come up with solutions.

You are trying to solve things before knowing what needs (if anything!) fixing.

      Rating: 5
minimum
      resource usage  October 08, 2003
Reviewer:
      Pushparaj Arulappan  from NJ, USA
Tom,

In a given environment, what is the minimum resource
consumption by an oracle session.

Assume my environment is HP-UX 11i and Oracle 9.2.0.3.
Given the database configuration, if I connect to a SQLPLUS
session and doing nothing, what will be the minimum resource (memory and etc)
consumed by this SQLPLUS session.

For example,
In my database the SORT_AREA_SIZE parameter is set to 1MB
and the HASH_AREA_SIZE is set to 1 MB.

Does this mean that, the idle SQLPLUS session will
pre-occupy this 2 MB memory ?

Thanks
Pushparaj

          Followup:

no -- sort_area/hash_area sizes are the max dynamic memory that might be
allocate IF you need it and then will be released (eg: you don't sort and hash
at the same time so they would never get 2meg, just 1mg max)

it is a couple hundred k, eg:

ops$tkyte@ORA920LAP> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  /
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('pga')||'%'

NAME                                VALUE
------------------------------ ----------
session pga memory                 220508
session pga memory max             220508

that is 220k right after login

      Rating: 5
Quite an
      Enlightening discussion  October 20, 2003

Reviewer:  H.S.Anand  from New Delhi, India

Hi Tom,
The discussion has --"as always"-- really informative and has cleared quite a
few misunderstandings I had about CPU utilizations!

Kudos to you.

thanks
Anand

      Rating: 4
Querying
      Perfstat Schema  October 22, 2003
Reviewer:
      Matt  from Australia
I am planning to generate some SQL to query the perfstat schema to generate a
summary report for a period, say 24 hours. I snapshot every 10 minutes. I was
thinking I would generate at least the following:

* CPU used by this session for each 10 minute interval in the 24 hour range.
* Top 5 Wait Events for each 10 minute interval in the 24 hour range.

The idea is, to identify trends in resource usage over the period of monitoring.

Before I go any further with this, has anyone done anything like this already?

Tom, do you think that this can be done efficiently in a single query?

Best Regards

          Followup:

I haven't looked at the schema close enough to tell -- but you will LOVE 10g,
100% assured of that (we've given the database a long term memory ;)

      Rating: 5
CPU and Server
      Types  October 22, 2003
Reviewer:  Meyer
       from Baltimore, MD
Tom looking at page 270 of Effective Oracle by Design...and your servers.

Aria-Dev: Sun UltrEnterprise 450 server, 2UltraSparc-2 CPU's 400mhz, 1536MG of
physical memory

Bosshog: Dell Poweredge 2650 server, 2 Xeon CPUs 2.8Ghz, 512k Cache, 6GB of
physical Memory

I know they are not the same CPU's or platforms...but can you explain how
Bosshog has 7 times the CPU speed and four times the ram of Aria-dev but
transactions per second are 2,000 vs. 1,600 (not as far). I know these are not
linear and it involves much more (and not sure of your specifics)...but this
surprised me.

Thanks,
Meyer

          Followup:

you cannot really compare a 400mhz SPARC to a 2.8ghz CISC chip in the first
place (apples and oranges with no real conversion functions).

they were just "2 machines".  I wasn't trying to compare the two machines really
-- but rather the difference between "don't bind" vs "bind" on a multi-cpu
machine with multiple concurrent users.

the tests did insert/commit 50,000 times.  There was lots of other waiting going
on (log file syncs, etc).

also, even if you assume that the one machine had 7 times the CPU -- that does
not mean anything in a purely insert intensive operation.  there the bottleneck
would not really be CPU.

      Rating: 5
CPU
      utilization  October 22, 2003
Reviewer:  praveen
       from India
Excellent discussion...Hats off to Tom and thanks to Fentu whose question fired
an excellent discussion!

      Rating: 5
"Long-Term
      Memory"  October 23, 2003
Reviewer:  Doug
      Wingate  from New Orleans, LA, USA
You remark that Oracle Corp. has given Oracle Database 10g a long-term memory.
Is this a coy reference to developments in connection to the X$Trace fixed view?
 After having learned of this new facility in Milsap & Holt's book, I'm very
interested in whatever information you can provide about plans for X$Trace.  You
can speak openly with us; we'll consider it all off the record and just make
attribution to "a senior administration official."

          Followup:

it is this thing called ADDM, search for that on otn.

October 23, 2003 Sudhanshu -- Thanks for the question regarding
      "dbms_lock.sleep procedure", version 8.1.7

      You Asked
Any comment on this weird behaviour :-

SQL> exec dbms_lock.sleep(3500);

PL/SQL procedure successfully completed.

Elapsed: 00:08:14.18
SQL> exec dbms_lock.sleep(1000);

PL/SQL procedure successfully completed.

Elapsed: 00:03:48.71
SQL> exec dbms_lock.sleep(600);

PL/SQL procedure successfully completed.

Elapsed: 00:09:41.12

Does this mean that dbms_lock.sleep is unreliable ?

Thanks
Suds

      and we said...
I think it means you have a TON more patience then I ever would in a billion
years....  I cannot believe you waited 8 hours....

There is an issue with really large sleep times.  Suggest you sleep no more then
10 minutes or so and for larger "sleeps", loop it, something like this:

create procedure my_sleep( SLEEP_TIME IN NUMBER )
as
begin

for i in 1 .. trunc( SLEEP_TIME/600 )
loop
    dbms_lock.sleep( 600 );
end loop;
dbms_lock.sleep( SLEEP_TIME-trunc(SLEEP_TIME/600)*600 );

end;
/
Review &
      Followup

      Rating:
      3
  October 17, 2002
Reviewer:  Sudhanshu
      Kumar
Thanks Tom for the loop idea.

But the timing 00:08:14.18 means 8 minutes & 14 seconds.And I think sqlplus have
this default format of giving hundreth of a second when we do set timing on. So
that means I only waited for 8 minutes.

          Followup:

DOH -- i read that wrong didn't I.

but yes, dbms_lock.sleep has problems with really large numbers -- the loop will
correct that.

      Rating: 3
DBMS_LOCK
      package  October 23, 2003
Reviewer:  Denise
       from Virginia, USA
Hi Tom

I seem to be pestering you alot these days.

I have a lock on a table that I am trying to figure out
how to release.(see v$lock output)

Oracle usercode                Oracle SID Serial nbr Object name     Lock type
  F_ground  B_groun
------------------------------ ---------- ---------- ---------------
------------- --------- -------
GUI                                     8          3 MAILING         Row-X (SX)
            188

SQL> select * from v$lock where sid = 8;

Thu Oct 23

                                                         Exclusive locks

ADDR     KADDR           SID TY        ID1        ID2      LMODE    REQUEST
CTIME      BLOCK
-------- -------- ---------- -- ---------- ---------- ---------- ----------
---------- ----------
3A330458 3A330524          8 TX     131138     177618          6          0
 6346          0
1D8016AC 1D8016C0          8 TM      65920          0          3          0
 6361          0
1D0EAA18 1D0EAA28          8 JQ          0      19148          6          0
 6524          0

I ran the dbms_lock.sql to create the dbms_lock package.

I then researched the functions used to allocate,request,convert and release
locks.

when I run the dbms_lock.allocate_unique function I keep
getting the following errors.

SQL> EXEC SQL EXECUTE
BEGIN SQL EXECUTE; END;

          *
ERROR at line 1:
ORA-06550: line 1, column 11:
PLS-00103: Encountered the symbol "EXECUTE" when expecting one of the following:
%
The symbol "%" was substituted for "EXECUTE" to continue.

SQL> BEGIN
  2     DBMS_LOCK.ALLOCATE_UNIQUE (lockname IN VARCHAR2,
  3                                lockhandle OUT VARCHAR2);
  4  END;
  5  /
   DBMS_LOCK.ALLOCATE_UNIQUE (lockname IN VARCHAR2,
                                          *
ERROR at line 2:
ORA-06550: line 2, column 43:
PLS-00103: Encountered the symbol "VARCHAR2" when expecting one of the
following:
(
ORA-06550: line 3, column 55:
PLS-00103: Encountered the symbol ";" when expecting one of the following:
return

I have an ora-00054 "Resource Busy with acquire NOWAIT specified" that I am
trying to clear(or release) from the
system)

as always thanks a heap for your time and help.

          Followup:

kill session '8,3', thats the only way to release locks in another session

      Rating:
      4
DUH!!!  October 23, 2003
Reviewer:
      Denise  from Virginia, USA
you know Tom...right after I wrote you I ran this Kill Session script I have
that I use to monitor sessions.

I saw that 'locked table' session still hanging out there
and thought...heck...I'm gonna type:

alter system kill session '8,3' immediate;

wa-la the session was slated marked for kill...thought
"ok it's doing the rollback thing..I'll wait awhile"...
looked back and the session was gone and the table was
no longer locked.

Like I said before I always seem to make things
harder than they are...but it's how I learn.

I promise not to pester you(at least for awhile) :~)

regards,
your biggest oracle fan

October 23, 2003 -- Thanks for the question regarding "Teach me
      more about Application development", version 816

      You Asked
Tom,
 Q1) In a 3 tier architecture with Oracle backend ,iis webser and asp What is
better
              *) To commit thru a transaction call in the front end ASP ( Begin
tran ) code or to commit in the package prcoedure itself.
Does one have the advantage over the other.
I would see no reason to delay the commit by putting it in the front end
code,only would use it if different procedures are being called and want to
commit thru front end at a logical work being finished.
 Q2) A user navigates to a screen ( in the above architectur)which is populated
thru a refcursor
     Wants to update some records here ,does some changes on the screen but does
not commit. Abruptly navigates to a different screen.
What house keeping things should be done by front end in such a case to close
the ref cursor and also in general. other wise I believe it will remain open and
then max open cursor error will eventually come.
 Q3) A screen is populated thru a ref cursor stored procedure. ASP loops thru
this ref cursor and closes it thru ( i dont know the syntax
 rset.close may be) .Since this ref cursor is only a pointer to a defined cursor
are in the database these asp constructs function the same way as a pl/sql
engine would work on the refcursor like loop and exit when c1%not found,close c1
;
  Is this true, because I had this silly doubt that after we send and open
refcursor to asp its still hanging there though records are being fetched thru
ASP code.
 ***********************
 Q4) This is a very interesting one. Creating an Application for testing
applicants with questionaire. There are 100 questions in the database table with
their PK as qid (number).
User will enter say a number between 10 and 100 say he enters 20. this means he
wants to try 20 questions .
I have to bring 20 questions randomly selected from those 100 Questions.
Randomly so that next time If some one selects 20 he should not get the sam
questions.
  MY attempts .load those 100 questions in a plsql table.
 depending upon the user i/p number 20 in this case generate 20 random
  numbers using dbms_random ranging from 1 to 100. feed this to a plsql table
also.
 NOW loop thru these 2 plsql tables to fine the matching index.
 and give them to the front end  thru ref cursor may be.
Is this a good idea. Do u have a better solution? How can I implement this using
object types in Oracle rather than plsql tables because cant use them in select
statements.Can this be done using simple/complex select statment ( for randomly
getting 20 rows , i tried rownum it wont work the result set is always the same
99% ).
 Can U please give an EXAMPLE .U r so helpful .

      and we said...
q1) i think the caller should have the control over the transaction and there is
no reason to "wait" to commit, you can call the procedures like this:

begin
   procedure( arguments );
   commit;
exception
   when others then
        rollback;
        raise;
end;

and run that block.  The reason I like to let the caller decide is that you
might take two or three procedures and string them together as a transaction
some day.  If they all committed or rolled back independently -- by themselves
-- you would not be able to do this.  You would have to re-implement this code
without the commits (else they would NOT be a single transaction).  I feel
strongly that it is up to the CALLER to decide what the scope of the transaction
is, they should always control it.

q2) in a web environment, you are typically stateless.  The ref cursor is
already closed long before they abruptly navigate away from your page since the
ref cursor is closed as soon as the page is generated.  Each page is a
transaction, a total session.

q3) the cursor is there until you close the statement/result set/session.

q4) with just 100 things, this will be more then sufficient (its what I use on a
testing system myself)

scott@ORA817DEV.US.ORACLE.COM> select ename
  2    from ( select ename, dbms_random.random
  3                     from emp
  4                    order by 2 )
  5   where rownum <= 5;

ENAME
----------
BLAKE
TURNER
WARD
JONES
KING

scott@ORA817DEV.US.ORACLE.COM> /

ENAME
----------
JAMES
MILLER
TURNER
WARD
KING

scott@ORA817DEV.US.ORACLE.COM> /

ENAME
----------
JONES
FORD
ADAMS
ALLEN
WARD

Review &
      Followup

      Rating: 4
More
      Clarifications on Q2 and Q3 Please  December 23, 2001

Reviewer:  A reader
Can u please elaborate on answers to Q2) and Q3)
Q2)in a web environment, you are typically stateless.
  What is meant by stateless,are we not connected to a session i.e. database
session. I do not know much about this
 you said :Each page is a
transaction, a total session.
  What is this session ,I am confused since I am a Back-end (not so Backward
)guy I dont know much about these web sessions. Please explain.
q3) the cursor is there until you close the statement/result set/session.
 What is the session here ,is it the database session. If so then till the user
is logged on it will be on.Is it just not sufficient to close the result set

          Followup:

Sorry, If you are building a web application -- you better get an understanding
of these concepts.  In MOST web applications (this one you are using for
example), we are 100% stateless.  Once your page is generated, you consume no
resources in my database, you are out of there, any work you've done is
committed or rolled back.

I get 20,000 plus visits a day to asktom.  If I kept a session open for you, I
would need a HUGE machine.  I in fact have a very small $30,000 USD machine that
does asktom plus many other sites.  I can do this because you are using my
database only for the fraction of a second it takes to generate the page.  We
are 100% stateless.

There are technologies like servlets that can (if you want them to) run in a
stated manner.  They can hold a session open for as long as you like (or not).

It is important that you understand how YOUR middle tier is working, how it uses
connection pooling (or not), how it maintains state (or not), when it begins and
more importantly ENDS database sessions.  I don't see how you could build an
application without understanding how this works.

q3) the session I was referring to was the database session.  A cursor will be
closed when you close it OR when your session ends (since a cursor is specific
to a session)

      Rating: 5
Web Application
      design  December 23, 2001
Reviewer:  Dheeraj
       from India
Dear Tom,

Can you direct me to some  good text on the architectural issues of the Web
application design ( 9iAS ). Something as good as your book.

Regards,

          Followup:

Well, I haven't read any -- i sort of learned it as it was being made up.  Maybe
someone out there who reads this can suggest books they feel are appropriate...

      Rating: 5
What to do for
      the best ?  October 23, 2003
Reviewer:  Neil
       from UK
Tom,
I've been trawling asktom for days looking for hints and tips on how to go about
constructing a web-based application. There's an awful lot of stuff, and I'm
picking up some useful ideas but I'm having a hard time deciding how to
implement. Our current database has a front end that uses servlets to display
the html pages. I am building  another database, and would like to use that
technology.
What is the best method of connecting such pages to the database interactively?
Should I divide them up into functional groups and then write a pl/sql package
to support each? If so, how can I guarantee that locking and concurrency are
preserved? Would this be controlled by the package, or by something external
(bean? strut? asp? ant? warthog? vulture?) that lives on the infamous 'middle
tier'? Can I make the connection between an interactive web page and the
database stateless? If so, how?
Should I just do the whole thing in mod_plsql?
Any articles/sites/advice ?

          Followup:

htmldb.oracle.com

      Rating: 5
I knew you'd
      say that!  October 24, 2003
Reviewer:  Neil
       from UK
Because realistically you can only answer specific questions - so I'm off to
htmldb.oracle.com so that I can find some! *_-

October 23, 2003 Piotr -- Thanks for the question regarding "Tree
      and "Youngest Common Ancestor"", version 9.2.0.1.0

      You Asked
Hello Tom,

I could finally ask you a question...

I have a table like this:

create table tree(name varchar2(30), id number, pid number, primary key(id),
foreign key(pid) references tree(id));

with sample data:

insert into tree values ('A',1,null);
insert into tree values ('B',2,1);
insert into tree values ('C',3,1);
insert into tree values ('D',4,2);
insert into tree values ('E',5,2);
insert into tree values ('F',6,3);
insert into tree values ('G',7,3);
insert into tree values ('H',8,3);
insert into tree values ('I',9,6);
insert into tree values ('J',10,6);
insert into tree values ('K',11,8);
insert into tree values ('L',12,10);

Is it possible to write a single SQL query to answer the following question?

Given the arbitrary set of nodes return their Youngest Common Ancestor YCA. You
can take for granted that the table contains correct trees (no loops) and that
the input node set belongs to exactly one tree.

e.g.

i)   (D,E) -> B
ii)  (I,L) -> F
iii) (L,G,K) -> C
iv)  (K,D) -> A

I wrote a PL/SQL code (using connect by clause, bulk collecting full paths
upwards from node to root and finding common ancestor procedurally in a loop)
for this but I wonder if it is possible to write this in a single SQL query?
Quite a challenge (at least for me). I cannot find a way to do this unless I
could create dynamically number of queries glued with intersects and sprinkled
with connect by, order by, inline views... etc.

For instance for iii) it would look like:

select name from (
    select name, rownum r from (
        select name from (
            select name,level l from tree
            start with name='L'
            connect by prior pid=id
            order by l desc
        )
        intersect
        select name from (
            select name,level l from tree
            start with name='G'
            connect by prior pid=id
            order by l desc
        )
        intersect
        select name from (
            select name,level l from tree
            start with name='K'
            connect by prior pid=id
            order by l desc
        )
    ) order by r desc
) where rownum=1

And of course I would normally use bind variables and dbms_sql package...

But that is not an acceptable solution... too expensive (naive algorithm) and
too easy ;-)

Thank you in advance,

Regards,
Piotr

      and we said...
ops$tkyte@ORA920> select *
  2    from (
  3  select name, min(level), count(*)
  4    from t
  5   start with name in ( 'D', 'E' )
  6   connect by prior pid = id
  7   group by name
  8  having count(*) = 2
  9   order by 2
 10         )
 11   where rownum = 1
 12  /

NAME       MIN(LEVEL)   COUNT(*)
---------- ---------- ----------
B                   2          2

ops$tkyte@ORA920> select *
  2    from (
  3  select name, min(level), count(*)
  4    from t
  5   start with name in ( 'I', 'L' )
  6   connect by prior pid = id
  7   group by name
  8  having count(*) = 2
  9   order by 2
 10         )
 11   where rownum = 1
 12  /

NAME       MIN(LEVEL)   COUNT(*)
---------- ---------- ----------
F                   2          2

ops$tkyte@ORA920> select *
  2    from (
  3  select name, min(level), count(*)
  4    from t
  5   start with name in ( 'L', 'G', 'K' )
  6   connect by prior pid = id
  7   group by name
  8  having count(*) = 3
  9   order by 2
 10         )
 11   where rownum = 1
 12  /

NAME       MIN(LEVEL)   COUNT(*)
---------- ---------- ----------
C                   2          3

ops$tkyte@ORA920> select *
  2    from (
  3  select name, min(level), count(*)
  4    from t
  5   start with name in ( 'K', 'D' )
  6   connect by prior pid = id
  7   group by name
  8  having count(*) = 2
  9   order by 2
 10         )
 11   where rownum = 1
 12  /

NAME       MIN(LEVEL)   COUNT(*)
---------- ---------- ----------
A                   3          2

ops$tkyte@ORA920>

Review &
      Followup

      Rating:
      5
Excellent!  October 20, 2003
Reviewer:
      Piotr Jarmuz  from Poznan, Poland
I knew that if this were at all possible you'd find the solution.

Thank you very much!

      Rating: 5
Another related
      question  October 23, 2003
Reviewer:  Piotr
      Jarmuz  from Poznan, Poland
Hello Tom,

Now I had to slightly modify this query like this:

select * from (
   select name, min(level), count(*) from tree
   start with name in (select name from another_table where SOME_PREDICATE...)
   connect by prior pid=id
   group by name
   having count(*)=(select count(name) from another_table where
SOME_PREDICATE...)
   order by 2
)
where rownum=1;

Is there a way to eliminate duplication of "almost" identical subqueries:

select name from another_table where SOME_PREDICATE...
select count(name) from another_table where SOME_PREDICATE...

They compute resultset and its length at the same time logically so there should
be a away to do it once physically.

Maybe an inline view?

Maybe this is not the best analogy but it has just crossed my mind that it is
similar to the way the "C" compilers can optimize division and modulo in integer
arithmetics under analogical conditions. I am sure you know what I mean.

Thank you in advance.

Piotr

          Followup:

you could "with"

with Q as ( select name, count(*) over () cnt from another_table ... )
select name, min(level), count(*)
  from tree
 start with name in ( select name from q )
 ...
having count(*) = ( select cnt from q where rownum = 1 )

      Rating: 5
It
      works  October 23, 2003
Reviewer:  Piotr
      Jarmuz  from Poznan, Poland
Thank you very much.

What is this "with clause"? I see it for the first time.
Very cool, anyway.

And the execution plan shows something like:
...
TABLE ACCESS (FULL) OF 'SYS_TEMP_0FD9D6607_5823328' (Cost=2 Card=1 Bytes=15)
...

Is this a temporary materialized view?

          Followup:

it is called subquery factoring, new in 9i.

yes, that is like a temporary mv

October 23, 2003 Firas -- Thanks for the question regarding "Long
      Raw to BLOB", version 8.1.7

      You Asked
Hi Tom,

We are using an Oracle 8.1.7 database. Is there a way in PL/SQL or Java Stored
Procedure to convert a Long Raw into a BLOB?

Thanks,
Firas Khasawneh

      and we said...
ops$tkyte@ORA817DEV> create table gtt ( id int, x blob );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create table t ( id int primary key, x long raw );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert into t values( 1, rpad( 'a', 2000, 'a' ) );

1 row created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> declare
  2          l_id number := 1;
  3          l_blob blob;
  4  begin
  5          execute immediate '
  6          insert into gtt
  7          select :x, to_lob(x)
  8            from t
  9           where id = :x' using l_id, l_id;
 10
 11          select x into l_blob from gtt where id = l_id;
 12  end;
 13  /

PL/SQL procedure successfully completed.

if you just want to convert single row for a session.  use

create table new_table as select ...., to_lob(long_raw) from old_table

to permanently convert all rows.
Review &
      Followup

      Rating: 3
Excellent,
      Thanks  October 23, 2003
Reviewer:  Firas
       from NC USA
Hi Tom,

Thanks a lot for your prompt response. Is it possible to update a Long Raw
column with a BLOB value? I read some where that that is possible by converting
the binary bytes into hex but I am not sure if that is possible or how to do it.
Any suggestions is highly appreciated.

          Followup:

nope, no can do there.  You would have to use the API's to update the long raw
(if the long raw/blob is over 32k, if 32k and less then yes, plsql can do it)

October 23, 2003 Ramasamy -- Thanks for the question regarding
      "Partition pruning vs Explicit partition naming.", version 8.1.7

      You Asked
Tom,

1. I feel like, if oracle can prune the partition by itself
it is the best method(including performance) over by specifying
explicitly partition name. Some of my friends are telling me
if i explicitly specify partition name is the best for performance
and reason is if Partition pruning is best then why oracle provides explicit
partition naming option?.

2. oracle10i new features. Is there any document published yet on this.

Thanks in advance.

      and we said...
1) you would name a partition only during administrative type operations.

In an application -- i cannot imagine anyone using a partition name -- ever.
Never.

It is a truly and utterly bad idea.  I see no merits in it at all.  YOu might as
well not be using partitioning at all.

2) not yet.
Review & Followup

      Rating: 5
Partitioning
      Questions  October 23, 2003
Reviewer:
      A reader
Hi Tom,

We are going to partition a table which contains both current data and
historical data. The partition key is NULL for current data and some numbers for
historical data.

partition by range (p_key)
(
partition p_archival values less than (99999999999999) ,
partition p_current  values less than (maxvalue))

My questions:
1. If I specify predicate like "p_key is null" for queries for current data,
will it be partition pruned?
2. Sometime we update a few thousand of rows from current data to history data.
Will row movement expensive for this type of action?

Thanks.

1. If I use

          Followup:

1) yes

ops$tkyte@ORA920> CREATE TABLE t
  2  (
  3    p_key   int,
  4    data       char(20)
  5  )
  6  PARTITION BY RANGE (p_key) (
  7    PARTITION part1 VALUES LESS THAN (9999999999999999999) tablespace tools,
  8    PARTITION part2 VALUES LESS THAN (maxvalue) tablespace users
  9  )
 10  ;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( 1, 'x' );

1 row created.

ops$tkyte@ORA920> insert into t values ( null, 'x' );

1 row created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t partition(part1);

     P_KEY DATA
---------- --------------------
         1 x

ops$tkyte@ORA920> select * from t partition(part2);

     P_KEY DATA
---------- --------------------
           x

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter tablespace tools offline;

Tablespace altered.

ops$tkyte@ORA920> select * from t where p_key is not null;
select * from t where p_key is not null
              *
ERROR at line 1:
ORA-00376: file 8 cannot be read at this time
ORA-01110: data file 8: '/usr/oracle/ora920/OraHome1/oradata/ora920/tools01.dbf'

ops$tkyte@ORA920> select * from t where p_key is null;

     P_KEY DATA
---------- --------------------
           x

ops$tkyte@ORA920> alter tablespace tools online;

Tablespace altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> delete from plan_table;

0 rows deleted.

ops$tkyte@ORA920> explain plan for
  2  select * from t where p_key is null;

Explained.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from TABLE(dbms_xplan.display);

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
--------------------------------------------------

---------------------------------------------------------------------------------
--------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost (%CPU)|
Pstart| Pstop |
---------------------------------------------------------------------------------
--------
|   0 | SELECT STATEMENT     |             |     4 |   140 |     3  (34)|
|       |
|*  1 |  TABLE ACCESS FULL   | T           |     4 |   140 |     3  (34)|     2
|     2 |
---------------------------------------------------------------------------------
--------

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

   1 - filter("T"."P_KEY" IS NULL)

12 rows selected.

2) sure, it'll be like a delete + insert instead of just update.  it'll
definitely affect it.

October 23, 2003 Paul -- Thanks for the question regarding "Export
      Oracle Data Error!", version Oracle 8i

      You Asked
Dear Tom.
We use Oracle8i on HP server and we use the Export utility to
export oracle
user data everyday, but we got error and fail to export the data
when the
data exceed 2GB, how can we do? we use this as kinds of backup
method and
import it sometimes to our testing server!
Thanks in advance!

      and we said...
EXP is not a backup method! Hopefully you are doing REAL
backups, not just exports.

Anyway, one method to export more then 2 gig is:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:122612348063
Another is to have export use more then 1 dmp file.  In Oracle8i
release 8.1.5 and up, you can use

FILESIZE maximum size of each dump file
FILE     output files (EXPDAT.DMP)

to specify more then one output file and the max size of the
output files as well.

Review &
      Followup

      Rating: 4
Question : exp
      with filesize How to make it non interactive  October 23,
      2003
Reviewer:  A reader
When we specify filesize it always prompts you for the file name.
1) How to avoid this so that it will automatically create files of the size
mentioned in the fileszise param when you run this from command prompt without
asking u for the filename

          Followup:

list the name of the output files in the exp command

files=(file1.dmp,file2.dmp,file3.dmp,.......fileN.dmp)

October 23, 2003 Om -- Thanks for the question regarding "About
      Buffer Cache Hit ratio", version 8.1.7

      You Asked
Hi Tom,

Many thanks for your previous response.

One of our PRODUCTION database is having buffer cache hit raio as
15% from the last few days.

The database size is about 12 GB and the db_block_buffer is 20Mb
at present.

Now there can be so many reasons for this low buffer cache hit ratio
as according to the manual this may be because of

1) may be because of FULL table scans.

2) Bad application design etc

My Question is
================

Question 1) We can find out about the FULL table scans by writing this query

select name,value from v$sysstat
where name like '%table_scans%';

WHICH GIVES FOLLOWING RESULT ON OUR production DATABASE

table scans (short tables)                                         89365825
table scans (long tables)                                          20006613
table scans (rowid ranges)                                                0
table scans (cache partitions)                                            0
table scans (direct read)                                                 0

I really do not know how to interpret this.

How to find out which tables are performing full table scans ?

Ques 2) If I know the table names which are performing full table
scans ...How to conclude that these can be CACHED or not ?
What size is generally recommended for a table to be CACHED ?

Let me type the size of the TOP 5 tables in the database.

The size is in MB.

AE_APPL_ER          1189.0625
AE_APRORS           956.53125
AE_ERROR_B          556.25
AE_ERRO_AGES        417.875
A_MESSAGES          405.78125

The shared pool for the database is 50MB.

Ques 3) Also when I see

SQL> select name,value
  2  from v$sysstat
  3  where name='free buffer inspected';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
free buffer inspected                                               5585949

SQL> select event,total_waits
  2  from v$system_event
  3  where event in
  4  ('free buffer waits','buffer busy waits');

EVENT                                                            TOTAL_WAITS
---------------------------------------------------------------- -----------
free buffer waits                                                        981
buffer busy waits                                                     139668

It gives the above information.

How to interpret this ?

Please help ?

Best regards,
Om

      and we said...
it might not be a problem at all.

HAVE YOU IDENTIFIED IT AS A PROBLEM.

It could be an indication that you have reached perfection, just as much as an
indication that you are having a problem.

Have you identified that

a) you have a problem
b) you have found the cause of the problem.

suggestion:

goto
http://www.hotsos.com/
and read the paper "Are you still using cache hit ratios?" -- you can
get that free by signing up.

Hey, I have a question for you since you gave me those two random numbers
without any other information:

I just took a car trip and counted the red lights I had to stop at.  I had to
stop at 50 red lights.  Is that good or bad?

(no matter what you answer, I will successfully argue the counter is actually
true).

20mill full table scans looks bad -- but, is that over 2 minutes, 2 days, 2
weeks, 2 months, 2 years, etc.

You want to use statspack to get a holistic view of your database in a window of
time (say 15-30 minutes MAX between snapshots)

You need to back up first tho and

a) determine that you in fact have a problem.  That is not clear hear at all.
Could this be a case of CTD?  (compulsive tuning disorder)?

b) if you have a problem, state what it is and try to understand WHY you have a
problem (find the cause -- then the cure)

Similar to a patient walking into doctors office with a headache (full scans).
Doctor prescribes painkiller (fixed sympton).  Patient dies -- it was a brain
tumor, the headache was simply a sympton.

Review &
      Followup

      Rating: 4
Index on dual
      table   May 09, 2003
Reviewer:  Sasa
      Petkovic  from Belgrade, Yugoslavia
Hi Tom,
In the article on hotsos ("when to use index") written by
Cary Millsap/Hotsos Enterprises, Ltd. found next:
" In Oracle8i, we have found that creating and using an index on system.dual
significantly reduces the number of LIOs required to select its single row. We
have observed about a 10:1 associated improvement in response time for queries
of dual. If your application makes, for example, millions of LIO calls per day
against one-row reference tables, then our research suggests that you can
conserve millions of LIOs per day and about 90% less CPU for those queries by
indexing them."

Please would you comment this as it was big suprise for me.

Thanks

          Followup:

well, it would be a bit shaky as it would involve two things

a) indexing dual
b) analyzing dual

in order for the index to be used -- it would be an index fast full scan - in
order to do that -- you need dual analyzed.

None of that would be supported.

What would be supported however is:

create table mydual ( x int primary key ) organization index;
insert into mydual values ( 1 );
commit;

that'll do it -- use mydual instead of dual.

Note, a future release (future as of may 9th 2003) resolves this issue.

what Cary was trying to point out there was that the rule "do not index small
tables" is a fallacy, mythology.

      Rating: 4
How Does This
      Save LIOs?  May 12, 2003
Reviewer:  Doug
      Wingate  from New Orleans, Louisiana, USA
It seems quite reasonable that a one-block index of a three-block table would
save logical reads:  To select a particular row, one makes two single-block
reads instead of scanning the three-block table.  But how does an index on a
single-row table reduce logical reads?

Thanks.

          Followup:

A full scan does some amount of work to figure out what to scan, the index does
less setup work (and then finds out "oh, i'm done" right away).

Your MILEAGE WILL VARY when running this test -- in various releases, the full
scan will do more (or less, or different kinds) of IO, but in this example, for
this small dual like table, it is a 7:1 difference:

ops$tkyte@ORA920> create table t ( x int primary key );

Table created.

ops$tkyte@ORA920> insert into t values ( 1 );

1 row created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920> select * from t;

         X
----------
         1

ops$tkyte@ORA920> select * from t where x = 1;

         X
----------
         1

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace on
ops$tkyte@ORA920> select * from t;

         X
----------
         1

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

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

ops$tkyte@ORA920> select * from t where x = 1;

         X
----------
         1

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   INDEX (UNIQUE SCAN) OF 'SYS_C0010309' (UNIQUE)

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

ops$tkyte@ORA920> set autotrace off

      Rating: 5
Investigating
      the 7:1 Ratio  October 23, 2003
Reviewer:  Doug
      Wingate  from New Orleans, LA, USA
Okay, I understand now that for very small objects, the cost of "setup" may
overwhelm the cost of reading the objects themselves, so it may for instance
cost less to read a one-block index than to read a one-block table.  Would the
causes of the 7:1 ratio of consistent gets in your example be exposed if I
examined the recursive SQL and wait events in an extended SQL trace?

Thanks.

          Followup:

i had no recursive sql in this case.

wait events maybe -- if you did this after flushing everything (no physical io)
but that would be self defeating as it would imply the dictionary cache was
probably killed as well so there would be recursive calls and all as well

October 24, 2003 alan -- Thanks for the question regarding
      "Materialized View", version 8.1.6

      You Asked
Hi Tom;

Can you explain further the option on the MATERIALIZED VIEW which is the ENABLE
QUERY REWRITE? What it does, is any privileges I need to have in order to
implement that said option.

Thank you and more power

      and we said...
The privs you need are:

o grant create session
o grant create table
o grant create materialized view
o grant query rewrite

Here is a brief extract from my book, from the chapter on Materialized views,
that gives you an overview of what they are about -- especially the QUERY
REWRITE capability:

...
A Brief History

Summary table management, another name for materialized views, has actually been
around for some time. Tools such as Oracle Discoverer (an ad-hoc query and
reporting tool) implemented this feature quite a while ago. Using Discoverer, an
administrator would set up various summary tables in the database. The tool
would then parse queries before sending them to Oracle ? if it determined that
some summary table existed that could answer the question more readily, it would
rewrite the query to access the summary tables instead of the tables you
originally specified and submit it to Oracle for processing. This was great, as
long you used that tool to process your queries. If you ran a query in SQL*Plus
or from your Java JDBC client then the query rewrite would not (could not) take
place. Furthermore, the synchronization between the details (original source
data) and the summaries could not be performed or validated for you
automatically since the tool ran outside the database.

Furthermore, since version 7.0, the Oracle database itself has actually
implemented a feature with many of the characteristics of summary tables -
namely the Snapshot. This feature was initially designed in order to support
replication, but I would use it myself to ?pre-answer? large questions. So, I
would have snapshots that did not use a database link to replicate data from
database to database but rather just summarized or pre-joined frequently
accessed data. This was good but not great ? the application had to know to use
the summary tables in the first and this made the application more complex to
code and maintain. If I added a new summary then I would have to find the code
that could make use of it and rewrite that code.

In Oracle 8i, release 1, version 8.1.5 (Enterprise and Personal Editions) Oracle
took the ?best of both worlds? and produced the materialized view. Oracle took

o The query rewriting capabilities from tools like Discoverer
o The automated refresh and scheduling mechanisms from snapshots in the
database.  This makes materialized views ?self maintaining?
o The optimizer?s ability to find the best plan out of many alternatives

and created the materialized view. The advantage of this is that now every
application can take advantage of the automated query rewrite facility,
regardless of whether access to the database is via SQL*Plus, Oracle Forms,
JDBC, ODBC, Pro*C, OCI or some third party tool. Every Oracle8I Enterprise
database can have summary table management. And since everything takes place
inside of the database, the details can be easily synchronized with the
summaries ? or at least the database knows when they aren?t synchronized and
might bypass ?stale? summaries (you control its behavior in this case). By
putting the functionality right in front of the data, anything that can access
Oracle can take advantage of that functionality.
................

Review &
      Followup

      Rating: 5
VERY CLEAR
      ANSWER  November 20, 2001
Reviewer:  Gonela
       from Chicago,USA
Tom,

Detailed and clear explanation about MV. Thank you

      Rating: 3
Query Rewrite
      used?   April 23, 2002
Reviewer:  Charanjiv
       from Delhi, India
Hi
How do we know if the query rewrite functionality is being used.
I created a MV on a copy of the emp table

CREATE SNAPSHOT LOG ON t1 WITH rowid(
sal, deptno )
    INCLUDING NEW VALUES;

create materialized view mv_1
    build immediate
    refresh force on commit
    ENABLE QUERY REWRITE
    as
    select deptno, count(*) cnt1, count(e.sal) cnt2, sum(e.sal)
as sum_sal
      from t1 e
     group by deptno
/

enable the query rewrite for the session
ALTER SESSION SET QUERY_REWRITE_ENABLED = TRUE ;

set autotrace on

SQL> select sum(sal) from t1 where deptno = 10 ;

  SUM(SAL)
----------
  71680100

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'T1'

SQL> select sum_sal from mv_1 where deptno = 10 ;

   SUM_SAL
----------
  71680100

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=6)
   1    0   TABLE ACCESS (FULL) OF 'MV_1' (Cost=1 Card=1 Bytes=6)

Am i right in thinking that with Query rewrite on the query on the detail table
t1 should have been rewriten to query the MV? Why was this not done.

Using 8.1.7 on Windows 2000

          Followup:

You need to analyze the tables and use the CBO.

You are not.

      Rating: 5
Very Nice
      detail  July 30, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan

      Rating: 5
MV LOG
      options  September 23, 2003
Reviewer:
      A reader
Hi Tom,

I noticed that the MV log could created simply as:

CREATE SNAPSHOT LOG ON t1 WITH rowid;

or

CREATE SNAPSHOT LOG ON t1 WITH rowid( sal, deptno )
    INCLUDING NEW VALUES;

Could you tell me the difference and which one should I use?

Thanks.

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/statements_64a.htm#2064651

      Rating: 3
Create a
      materialized view over union all view  October 17, 2003

Reviewer:  hongtao wang
when I create a view over a union all view, I found the query can't be query
rewrite.

How can I achive the goal?

hope can get your help!

          Followup:

er?

hows about a simple, small, concise (YET COMPLETE) example -- concentrate on
simple, small -- but remember COMPLETE so I too can run it.

show us the issue you are having exactly.  (please no multi-page create tables
or anything -- as small as you can make it)

      Rating: 5
Grouping data
      via MV  October 23, 2003
Reviewer:  Alvin
       from Philippines
The data on a particularly "FAT" table would definitely benefit on an IOT table
unfortunately some of the columns overflows and my benchmark shows that it takes
more Logical I/O on some query.

I was thinking if i could make the data near together by the use of MV's and
using order by col1,col2 ?

          Followup:

clusters do that -- hash and b*tree clusters.

October 24, 2003 Connie -- Thanks for the question regarding "open
      cursors", version 8.1.6

      You Asked
Tom,
Could you please explain why I see cursors in the V$OPEN_CURSOR
view even though I believe they are closed. Are they really
open?I wrote a small example to illustrate my question.

SQL> start cursortest
SQL> select sql_text
  2  from v$open_cursor where upper(sql_text) like
('%CURSORTEST%');

no rows selected
SQL> create or replace procedure testit
  2  as
  3  junk varchar2(1);
  4  CURSOR dual_cursor
  5  IS
  6  SELECT dummy cursortest
  7    FROM dual;
  8  begin
  9  open dual_cursor;
 10  fetch dual_cursor into junk;
 11  close dual_cursor;
 12  end;
 13  /
Procedure created.

SQL> --
SQL> begin testit;end;
  2  /
PL/SQL procedure successfully completed.
SQL> --

SQL> select sql_text
  2  from v$open_cursor where upper(sql_text) like
('%CURSORTEST%');

SQL_TEXT

------------------------------------------------------------

SELECT DUMMY CURSORTEST FROM DUAL

Thanks for any any help you can provide.
Connie Halpin

      and we said...
See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:553222846752
for the solution.  V$OPEN_CURSOR represents a set of cached
cursors the server has for you.  The query

          select a.value, b.name
            from v$mystat a, v$statname b
           where a.statistic# = b.statistic#
             and a.statistic#= 3

gives you the actual number of truely open cursors

Review &
      Followup

      Rating: 4
The query
      always return 1 open cursor  June 25, 2002

Reviewer:  A reader  from USA
Hi Tom,
Two questions:
1. I run you query listed here
select a.value, b.name
            from v$mystat a, v$statname b
           where a.statistic# = b.statistic#
             and a.statistic#= 3;
The result is the same in any databases I have (9 in total) at any time.
    VALUE NAME
--------- ------------------------
        1 opened cursors current

I think this is not the case.

2. When I run
select count(*) from v$open_cursor;
I get 1996 as the result.  However, my parameter file setting is:
Open cursors = 1000.
How can that be?  You have explained above that v$open_cursor is not the
currently open cursors. Should the number returned from v$open_cursor smaller
than the setting in parameter file, 1000 in my case, even though the number is
not the actually openning cursors?  The database has no problem.
Thank you very much.

          Followup:

1) and ... why do you believe that?  care to explain why you think it is wrong
(i don't think it is wrong)

SQL*Plus: Release 8.1.7.0.0 - Production on Tue Jun 25 15:35:20 2002
(c) Copyright 2000 Oracle Corporation.  All rights reserved.
Connected to:
Oracle8i Enterprise Edition Release 8.1.7.3.0 - Production
With the Partitioning option
JServer Release 8.1.7.3.0 - Production

ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.value, b.name
  2              from v$mystat a, v$statname b
  3             where a.statistic# = b.statistic#
  4               and a.statistic#= 3;

     VALUE NAME
---------- ------------------------------
         1 opened cursors current

2) select sid, count(*) from v$open_cursor group by sid

open_cursor is by session.  You are looking across sessions.

      Rating: 5
Clearing up
      confusion regarding Open Cursors  December 10, 2002

Reviewer:  Mary Ruiz  from Atlanta GA USA
I looked all over for this information.  I am trying to set up alerting before
the ORA-1000 error is posted in the alert log. I am now changing my code to
count the maximum number of open cursors for a session, and check it against a
threshold value, instead of counting all open cursors for a session

      Rating: 4
Just to
      clarify  March 12, 2003
Reviewer:  Robert
       from Ireland
Tom

After testing would I be right in saying then that the query:

SELECT a.VALUE, b.NAME
FROM v$mystat a, v$statname b
WHERE a.statistic# = b.statistic#
AND a.statistic#= 3

Returns only those open cursors for your current session?  If this is so, how do
you get all the open cursors for the database?  Would you use v$open_cursors
then?
Thanks
R.

          Followup:

you would use v$sesstat.

I did a bad thing here too -- i should have coded using the NAME not the number:

ops$tkyte@ORA9I> select a.value, b.name
  2    from v$mystat a, v$statname b
  3   where a.statistic# = b.statistic#
  4     and b.name = 'opened cursors current'
  5  /

     VALUE NAME
---------- ------------------------------
         1 opened cursors current

ops$tkyte@ORA9I> select sum(a.value), b.name
  2    from v$sesstat a, v$statname b
  3   where a.statistic# = b.statistic#
  4     and b.name = 'opened cursors current'
  5   group by b.name
  6  /

SUM(A.VALUE) NAME
------------ ------------------------------
          94 opened cursors current

      Rating: 5
Oops i did it
      again...  March 12, 2003
Reviewer:  Robert
       from Ireland
Sorry about that Tom,
a little more investigation leads me to believe that if you have a 1000 set as
max number of open cursors you are allowed a 1000 per session, for all sessions.
 Is this correct.

Also I replaced v$mystat with v$sesstat to get all the session open cursors.  Is
this the correct view? If so is it misleading that it gives me the same number
of open cursors as v$open_cursors does?

Cheers
R.

          Followup:

You are allowed 1000 PER SESSION.  so there could be 10,000 of them if you have
10 sessions.

if you are in a single user database -- v$sesstat and v$mystat will return
basically the same thing for opened cursors current unless smon or some other
background process is awake and doing something.

      Rating: 4
v$open_cusor
      has a higher value than your last query?  April 08, 2003

Reviewer:  Doug  from CT, USA
Hi Tom -
I tried this -

SQL> select sum(a.value), b.name
      from v$sesstat a, v$statname b
     where a.statistic# = b.statistic#
       and b.name = 'opened cursors current'
     group by b.name
  2    3    4    5    6  /

SUM(A.VALUE) NAME
------------ ------------------------------------------------------------
         515 opened cursors current

SQL> select count(1) from v$open_Cursor;

  COUNT(1)
----------
       377

SQL>

How can v$open_cursor contain less than your query?

          Followup:

v$open_cursor = your session.

v$sesstat (summed over) = sum of all sessions.

      Rating: 4
? v$open_cursor
      has a sid column  April 08, 2003
Reviewer:
      Doug  from CT, USA
How can v$open_cursor be only my own cursors? It has a sid column.

          Followup:

never mind, restart....(geez, i even said myself:

...
2) select sid, count(*) from v$open_cursor group by sid

open_cursor is by session.  You are looking across sessions.
......

above) thats what I get for firing off an answer too fast.  thanks for checking
back.

but anyway, on a busy system -- one would expect there to be differences --
cursors come and cursors go.  All you need is one session to exit between calls
and bamm -- there go hundreds of cursors potentially.

Now, since V$ views are *not* read consistent -- they are 100% point in time
pictures -- at the point in time you look at them -- you'll see this.

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96536/ch3.htm#1086021
Even if you try a single query like so:

ops$tkyte@ORA9I> select sum(a.value), b.name
  2  from v$sesstat a, v$statname b
  3  where a.statistic# = b.statistic# and b.name = 'opened cursors current'
  4  group by b.name
  5  union all
  6  select count(*), 'v$open_cursor' from v$open_cursor
  7  /

SUM(A.VALUE) NAME
------------ ------------------------------
         100 opened cursors current
         129 v$open_cursor

ops$tkyte@ORA9I> /

SUM(A.VALUE) NAME
------------ ------------------------------
          85 opened cursors current
         129 v$open_cursor

ops$tkyte@ORA9I> /

SUM(A.VALUE) NAME
------------ ------------------------------
         110 opened cursors current
         175 v$open_cursor

ops$tkyte@ORA9I> /

SUM(A.VALUE) NAME
------------ ------------------------------
          91 opened cursors current
          97 v$open_cursor

ops$tkyte@ORA9I> /

SUM(A.VALUE) NAME
------------ ------------------------------
         144 opened cursors current
         137 v$open_cursor

ops$tkyte@ORA9I> /

SUM(A.VALUE) NAME
------------ ------------------------------
         146 opened cursors current
          95 v$open_cursor

v$sesstat is queried at a different point in time then v$open_cursor.  Both
answers are *right* at their respective points in time.  If you do this on a
"single user" system -- you'll see much more predicable responses.

      Rating: 5
Great
      Tom,Explained in an easily perceptible manner, >
      5*  April 25, 2003
Reviewer:  Saminathan
      Seerangan  from CA,USA

      Rating:
      3
Monitoring  October 22, 2003
Reviewer:
      Harri  from Finland
Is there a way to monitor open_cursors usage? I want to get noted if
open_cursors parameter is too small.

My monitoring "sofware" uses this query:

select value-(select max(value) from v$sesstat where STATISTIC#='3') from
v$parameter where name='open_cursors';

...but sometimes, some databases it gives negative value, so my logic must be
wrong or there is a bug or something.

          Followup:

well, statistic# is a number (why are you comparing to a string????)

are  you sure that 3 is open cursors in your database (i should not have used 3
myself)

      Rating:
      3
...  October 23, 2003
Reviewer:
      Harri  from Finland
SQL> select STATISTIC#, name from v$sysstat where NAME='opened cursors current';

STATISTIC# NAME
---------- ----------------------------------------------------------------
         3 opened cursors current

SQL>

SQL> select value-(select max(value) from v$sesstat where STATISTIC#='3') from
v$parameter where name='open_cursors';

VALUE-(SELECTMAX(VALUE)FROMV$SESSTATWHERESTATISTIC#='3')
--------------------------------------------------------
                                                    2677

SQL> show parameter open_cursors

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
open_cursors                         integer 3000

SQL> select value-(select max(value) from v$sesstat where STATISTIC#=3) from
v$parameter where name='open_cursors';

VALUE-(SELECTMAX(VALUE)FROMV$SESSTATWHERESTATISTIC#=3)
------------------------------------------------------
                                                  2677

...but sometimes it gives negative values!?

          Followup:

so, select the max value as well -- see what it "is".

      Rating: 4
Open
      Cursors  October 23, 2003
Reviewer:  Dan
      Clamage  from Pittsburgh, PA
Great! Now will this show me ref cursors as well as "regular" cursors? I don't
think it does (after running a quick test). So I can find out whether my web app
has a ref cursor leak (ref cursors being opened but never closed). We use
connection pooling so a session may stay active for quite some time.

          Followup:

ref cursors ARE regular cursors.

yes, it'll show you.

ops$tkyte@ORA920LAP> select name, value
  2   from v$mystat a, v$statname b
  3   where a.statistic# = b.statistic#
  4    and b.name = 'opened cursors current'
  5  /

NAME                                VALUE
------------------------------ ----------
opened cursors current                  1

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> variable x refcursor
ops$tkyte@ORA920LAP> variable y refcursor
ops$tkyte@ORA920LAP> variable z refcursor
ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> exec open :x for select * from dual;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> /

NAME                                VALUE
------------------------------ ----------
opened cursors current                  2

ops$tkyte@ORA920LAP> exec open :y for select * from dual;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> /

NAME                                VALUE
------------------------------ ----------
opened cursors current                  3

ops$tkyte@ORA920LAP> exec open :z for select * from dual;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> /

NAME                                VALUE
------------------------------ ----------
opened cursors current                  4

      Rating: 5
ref cursors
      strike again  October 23, 2003
Reviewer:  Dan
      Clamage  from Pittsburgh, PA
I think I see what's going on here.
<pre>
declare
  type typ_ref is ref cursor;
  v_ref typ_ref;
  v_sum number;
  v_name varchar2(100);
begin
  select name, value
  into v_name, v_sum
  from v$mystat a, v$statname b
  where a.statistic# = b.statistic#
    and b.name = 'opened cursors current';

  dbms_output.put_line(v_name || ': ' || v_sum);

  open v_ref for select 1 from dual;
  open v_ref for select 'test' from dual;
  open v_ref for select user from dual;
  open v_ref for select sysdate from dual;
  open v_ref for select * from dual;

  select name, value
  into v_name, v_sum
  from v$mystat a, v$statname b
  where a.statistic# = b.statistic#
    and b.name = 'opened cursors current';

  dbms_output.put_line(v_name || ': ' || v_sum);
end;
/

opened cursors current: 7
opened cursors current: 9
</pre>

My cursor space is being reused. I was expecting Oracle to store all the
different queries in memory.

Thanks!

      Rating:
      3
...  October 24, 2003
Reviewer:
      Harri  from Finland
SQL> select STATISTIC#, name from v$sysstat where NAME='opened cursors current';

STATISTIC# NAME
---------- ----------------------------------------------------------------
         3 opened cursors current

SQL> select max(VALUE) from v$sesstat where STATISTIC#=3;
MAX(VALUE)
----------
381

SQL> show parameter open_cursors
NAME TYPE VALUE
------------------------------------ ------- ------------------------------
open_cursors integer 150

SQL> select value-(select max(value) from v$sesstat where STATISTIC#='3')
2 from v$parameter where name='open_cursors';
VALUE-(SELECTMAX(VALUE)FROMV$SESSTATWHERESTATISTIC#='3')
--------------------------------------------------------
-231

          Followup:

any chance someone did an alter system?

ops$tkyte@ORA920> select STATISTIC#, name from v$sysstat where NAME='opened
cursors current';

STATISTIC# NAME
---------- ------------------------------
         3 opened cursors current

ops$tkyte@ORA920>
ops$tkyte@ORA920> select max(VALUE) from v$sesstat where STATISTIC#=3;

MAX(VALUE)
----------
        10

ops$tkyte@ORA920>
ops$tkyte@ORA920> show parameter open_cursors

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
open_cursors                         integer     500
ops$tkyte@ORA920>
ops$tkyte@ORA920> select value-(select max(value) from v$sesstat where
STATISTIC#='3')
  2    from v$parameter where name='open_cursors';

VALUE-(SELECTMAX(VALUE)FROMV$SESSTATWHERESTATISTIC#='3')
--------------------------------------------------------
                                                     490

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter system set open_cursors = 5;

System altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> show parameter open_cursors

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
open_cursors                         integer     5
ops$tkyte@ORA920>
ops$tkyte@ORA920> select value-(select max(value) from v$sesstat where
STATISTIC#='3')
  2    from v$parameter where name='open_cursors';

VALUE-(SELECTMAX(VALUE)FROMV$SESSTATWHERESTATISTIC#='3')
--------------------------------------------------------
                                                      -5

ops$tkyte@ORA920>

October 24, 2003 Ashok -- Thanks for the question regarding
      "catalog and catproc scripts... ", version 8.1.6

      You Asked
Hi Tom ,

      Could u please tell me what would be the Impact on a live
database if catalog.sql and catproc.sql are run?

Thanks in advance.

Thanks & Regds
   Ashok.E.

      and we said...
You should never do that in a database where people are logged
in running applications.

They recreate all of the plsql packages -- so anyone running
plsql in the form of stored procedures, triggers and so on would
be massively impacted (it would probably just "hang" since
standard cannot be rebuilt until no one is using it).

They recreate all of the data dictionary views -- which are
heavily used by many applications.

There should never be a reason to run either of these scripts on
a live instance.  They are run only during install and upgrade
time.
Review
      & Followup

      Rating: 4
catalog for
      LMT  June 02, 2003
Reviewer:  kelly
       from dallas, tx
In creating a Local Managed Tablespace database, do I need to run catalog and
catpro? Although the information is stored in data file bitmap, doesn't it still
need dictionary to be viewed by users?

          Followup:

LMTS just manage space in the data file headers. catalog and catproc and all are
still very much needed.  they create the dictionary views and such.

      Rating: 5
Impact of rerun
      catalog on production database  October 24, 2003

Reviewer:  Olaf  from Germany
Tom,

I want to reinstall some Oracle options from my production database. I always
made it using Oracle Univesal installer.
But some option (Spatial and Data Mining) I can still see in V$OPTIONS and in
sql headear during the start.
I found that the rerun of catalog.sql can help.
It is one of a prod. database of our customers.
What impact can this make on the database and applications

Thanks in advance

Olaf

          Followup:

none.  just let them be.  it won't affect your day to day operations.

      Rating:
      5
  October 24, 2003
Reviewer:
      A reader
Tom.

I try to deinstall the options regarding Oracle licenses
(We are still in 30-days test period).
If I still see the Data Mining option in the sql header,
can it causes a problem (it always deinstalled with OUI)

Olaf

          Followup:

it is fine.

October 24, 2003 Duncan -- Thanks for the question regarding
      "moving tables to a locally managed tablespace", version 8.1.7.4

      You Asked
Tom

I have a question regarding the MOVE clause of alter table.
We wanted to move from a dictionary managed tablespace to a locally managed one.
 To do this I:

1) created the local tablespace using uniform extents of 40K
2) I then used alter table move command to move the table into the new
tablespace

So in the DMT it looks like:

11:25:57 OPT1@OP4>exec print_Table('select * from dba_segments where
segment_name = ''ATTENDANCE''')
;
OWNER                         : OPT1
SEGMENT_NAME                  : ATTENDANCE
PARTITION_NAME                :
SEGMENT_TYPE                  : TABLE
TABLESPACE_NAME               : DATA
HEADER_FILE                   : 14
HEADER_BLOCK                  : 12482
BYTES                         : 10035200
BLOCKS                        : 1225
EXTENTS                       : 1
INITIAL_EXTENT                : 10002432
NEXT_EXTENT                   : 10002432
MIN_EXTENTS                   : 1
MAX_EXTENTS                   : 2147483645
PCT_INCREASE                  : 0
FREELISTS                     : 1
FREELIST_GROUPS               : 1
RELATIVE_FNO                  : 14
BUFFER_POOL                   : KEEP
-----------------

PL/SQL procedure successfully completed.

Then I did:

11:26:59 OPT1@OP4>alter table attendance
11:27:16   2  move tablespace DATA_S;

Table altered.

Elapsed: 00:00:00.05
11:27:26 OPT1@OP4>exec print_Table('select * from dba_segments where
segment_name = ''ATTENDANCE''')
;
OWNER                         : OPT1
SEGMENT_NAME                  : ATTENDANCE
PARTITION_NAME                :
SEGMENT_TYPE                  : TABLE
TABLESPACE_NAME               : DATA_S
HEADER_FILE                   : 25
HEADER_BLOCK                  : 1234
BYTES                         : 10035200
BLOCKS                        : 1225
EXTENTS                       : 245
INITIAL_EXTENT                : 10002432
NEXT_EXTENT                   : 40960
MIN_EXTENTS                   : 1
MAX_EXTENTS                   : 2147483645
PCT_INCREASE                  : 0
FREELISTS                     : 1
FREELIST_GROUPS               : 1
RELATIVE_FNO                  : 25
BUFFER_POOL                   : KEEP
-----------------

PL/SQL procedure successfully completed.

I was hoping that this command would actually reset the HWM of the table so that
the table itself would shrink to its exact size with no
extra storage.

However looking at dba_Segments after the move it seems that it has not shrunk,
and the initial extent is ~10M, NEXT = 40K
How can the initial extent not be equal to the NEXT extent if we have uniform
extents??

Why did the alter table move command not shrink the table?

Now if instead I do:

11:36:22 OPT1@OP4>create table T1
11:38:10   2  as select *
11:38:13   3  from attendance;

Table created.

Elapsed: 00:00:00.01
11:38:21 OPT1@OP4>drop table attendance;

Table dropped.

Elapsed: 00:00:00.03
11:38:30 OPT1@OP4>create table ATTENDANCE
11:38:39   2  as select * from
11:38:42   3  T1;

Table created.

I now move the table again since the default tablespace of the current user
is not the correct one:

Elapsed: 00:00:00.01
11:38:47 OPT1@OP4>alter table attendance move
11:38:54   2  tablespace DATA_S;

Table altered.

Elapsed: 00:00:00.00
11:39:05 OPT1@OP4>exec print_Table('select * from dba_segments where
segment_name = ''ATTENDANCE''')
;
OWNER                         : OPT1
SEGMENT_NAME                  : ATTENDANCE
PARTITION_NAME                :
SEGMENT_TYPE                  : TABLE
TABLESPACE_NAME               : DATA_S
HEADER_FILE                   : 25
HEADER_BLOCK                  : 1234
BYTES                         : 40960
BLOCKS                        : 5
EXTENTS                       : 1
INITIAL_EXTENT                : 40960
NEXT_EXTENT                   : 40960
MIN_EXTENTS                   : 1
MAX_EXTENTS                   : 2147483645
PCT_INCREASE                  : 0
FREELISTS                     : 1
FREELIST_GROUPS               : 1
RELATIVE_FNO                  : 25
BUFFER_POOL                   : DEFAULT
-----------------

PL/SQL procedure successfully completed.

So now I get what I want ie 1 extent and INITIAL=NEXT.
Why did I not see that with the first alter table move.... command?
Is it because the HWM of the table was taken into account?

If this is the case how can I deallocate the unused space with one move command?

Thanks for your help.

      and we said...
with LMTs initial is just "the initial allocation" -- it is not an initial
extent size -- they are all 40k -- you just have a gazillion of them to give you
10m of storage.

you should:

alter table t move new_tablespace storage( initial 1k next 1k pctincrease 0
minextents 1 );

that'll reset the STORAGE parameters -- else they are copied from the existing
segment.  It has nothing to do with the HWM, it has everything to do with the
legacy storage parameters.
Review & Followup

      Rating: 4
Oracle doesn't
      seem to like this  October 24, 2003
Reviewer:
      Duncan  from UK
Elapsed: 00:00:00.01
16:18:40 OPT1@OP4>alter table ANALYSIS
16:19:10   2  move DATA_S
16:19:10   3  storage( initial 40k
16:19:10   4   next 40k
16:19:10   5   pctincrease 0
16:19:10   6   minextents 1
16:19:10   7  );
move DATA_S
     *
ERROR at line 2:
ORA-14133: ALTER TABLE MOVE cannot be combined with other operations

Also when you say we have loads of extents were you saying that we have too
many?  The reason I ask is that the data may not be archived for about 7 years
so if by then we were to have extents numbers eg > 3000, could this be a
problem?

          Followup:

you missed the TABLESPACE keyword, sorry -- my psuedo codeish example probably
is misleading that way.

ops$tkyte@ORA920> alter table t move users;
alter table t move users
                   *
ERROR at line 1:
ORA-14133: ALTER TABLE MOVE cannot be combined with other operations

ops$tkyte@ORA920> alter table t move TABLESPACE users;

Table altered.

ops$tkyte@ORA920> alter table t move tablespace users
  2  storage ( initial 1k next 1k pctincrease 0 minextents 1 );

Table altered.

      Rating:
      5
thanks  October 24, 2003
Reviewer:
      A reader
please can you comment on my last question regarding the number of extents
reaching 3000+.  Would this cause performance issues?

          Followup:

nope.  i've "accidently" let a table get into 32,000+ extents and never bothered
to fix it (was during a benchmark to boot -- just put the object in the wrong
tablespace)

October 24, 2003 sing -- Thanks for the question regarding
      "Standby database ", version 8.0.6

      You Asked
Tom,

Thanks for your answers to all my earlier questions.
Now, I am trying to create a standby database and I
am running into problems.

Steps I followed:

0. Created primary database.
1. Configured the listener.ora and tnsnames.ora for standby db.
2. Shutdown primary db and took cold backup of primary db.
3. Started primary db.
4. Created controlfile for standby using alter database create
standby controlfile as 'f';
5. Transferred files (data, log, control) to standby site.
6. Created initstdby.ora with relevant parameters.
 My iniorcl1.ora and initstdby.ora are here:
Then after setting ORACLE_SID=stdby,
svrmgrl> startup nomount
oracle instance started
svrmgrl>alter database mount standby database;
I get the following error:
ORA-01102: cannot mount database in EXCLUSIVE mode

How do I proceed?

# Primary database iniorcl1.ora file ---created by S
#################################################################
##############

db_name = orcl1

db_block_size = 8192

control_files = (/u02/oradata/orcl1/control01.ctl,
                 /u03/oradata/orcl1/control02.ctl,
                 /u04/oradata/orcl1/control03.ctl)

dml_locks = 200

background_dump_dest = /u01/app/oracle/admin/orcl1/bdump
core_dump_dest = /u01/app/oracle/admin/orcl1/cdump
user_dump_dest = /u01/app/oracle/admin/orcl1/udump

db_files = 80

db_file_multiblock_read_count = 32

db_block_buffers = 550

shared_pool_size = 20000000

log_checkpoint_interval = 99999

processes = 100

log_buffer = 32768

sequence_cache_entries = 10

sequence_cache_hash_buckets = 10

max_dump_file_size = 10240

log_archive_start = true
log_archive_dest = /u01/app/oracle/admin/orcl1/arch
log_archive_format = "T%TS%S.ARC"

rollback_segments = (r01, r02, r03, r04)

global_names = FALSE

#######################################

#
#Standby database inistdby.ora file ---created by S
#################################################################
##############

db_name = orcl1

db_block_size = 8192

control_files = (/u02/oradata/stdby/controlstdby1.ctl,
                 /u03/oradata/stdby/controlstdby2.ctl,
                 /u04/oradata/stdby/controlstdby3.ctl)

dml_locks = 200

background_dump_dest = /u01/app/oracle/admin/stdby/bdump
core_dump_dest = /u01/app/oracle/admin/stdby/cdump
user_dump_dest = /u01/app/oracle/admin/stdby/udump

db_files = 80

db_file_multiblock_read_count = 32

db_block_buffers = 550

shared_pool_size = 20000000

log_checkpoint_interval = 99999

processes = 100

log_buffer = 32768

sequence_cache_entries = 10

sequence_cache_hash_buckets = 10

max_dump_file_size = 10240

log_archive_start = true
log_archive_dest = /u01/app/oracle/admin/stdby/arch
log_archive_format = "T%TS%S.ARC"
db_file_name_convert = ('/u02/oradata/orcl1'
'/u02/oradata/stdby')
log_file_name_convert = ('/u02/oradata/orcl1'
'/u02/oradata/stdby')

rollback_segments = (r01, r02, r03, r04)

global_names = FALSE

#######################################################
Thanks Sing

and then

      and we said...
You are running into this since you are trying to have a standby
on the same machine with the real database.  The SGA's are
bumping into eachother:

1. Shutdown the standby database

2. Starting with Oracle Release 7.3.3:
   add _standby_lock_name_space=<new_name> in the init.ora of
   standby database

   Starting with Oracle Release 8.0.X:
   add lock_name_space=<new_name> in the init.ora of standby
   database

   where <new_name> is a name other than the primary's database
   name, up to 8 characters long

   You can use a naming convention like:
   If Primary db_name = DB1
   set lock_name_space = DB1s

3. startup nomount standby database

4. alter database mount standby database

The above solution works for TESTING standby databases on the
same machine as the Primary Database.

Do not, however, use the same machine to keep an actual standby
database.  This defeats the purpose of a standby if your entire
machine were to crash.

Review &
      Followup

      Rating: 5
Same Problem on
      Windows!!!  June 20, 2003
Reviewer:
      H.S.Anand  from New Delhi, India
Hi tom,
I am trying the same thing (i.e. Standby Database on same machine) on Windows 2k
with 9i Rel.2
And I was missing the LOCK_NAME_SPACE=<new_name> parameter.

Can you please explain how this will work?do you mean that that when i mount the
standby database
i must use the name given in this parameter??
I.e.

ALTER DATABASE <NAME IN LOCK NAME SPACE> MOUNT STANDBY DATABASE;

I'd appreciate if you can clarify this. SO that i can test my standby database
in the same machine.
Thanks
--H.S.Anand

          Followup:

read

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96653/consider.htm#45989

and then chapter 3, it has the step by steps for creating the physical standby
with caveats for "when you are on the same system"

      Rating: 4
Standby
      Database On Same Host (Windows 2000 Server)  June 24, 2003

Reviewer:  H.S.Anand  from New Delhi, India

Hi tom,
I finally managed to implement the standby database on the same host (Windows
2000 Server) and

LOCK_NAME_SPACE=<new_name> was the only parameter that i was missing out from
the init.ora file of the standby database.

Thank you.

--H.S.Anand

      Rating: 3
Setting up
      Standby Databases in 8.1.7.4   October 11, 2003

Reviewer:  Sriram  from Florida
Tom,

I want to setup standby Database for Oracle Applications 11i at site B. We have
a T1 connection between site A and site B. My primary database generates about
6-7 Archived log files of 300MB each every day. But, transferring 300MB over a
T1 connection is taking a long time. I tried resizing the log files on the
primary instance but that is causing log switching issues.

Can you suggest any other way of doing this? Does 9i have any features that can
help us in this scenario?

Thanks,
Sriram

          Followup:

define "log switching issues", what "issues" did you encounter?

If you think about it -- you have 2.1 gig to move.  regardless of what you do,
you have 2.1 gig to move.  It is a pure "math" problem.  In order to move 2.1
gig in the time you want -- you need a network of bandwidth "X".

Now, in 9i, you can have LGWR trickle that data over constantly -- so as you
COMMIT, LGWR will squirt the redo over the network asyncronously for you -- so,
you can have large logs that are constantly being transmitted.  Beware -- if you
flood the pipe, you'll obviously generate a backlog -- slowing down commit
response time.

      Rating: 5
Delay log
      apply  October 24, 2003
Reviewer:  Martin
      Guillen  from Buenos Aires, Argentina
Hi Tom: I'm testing Data Guard and it works great.
Now I'm guessing how can I setup DG so it delays a few hours the apply of redo
to the standby database. Has I read I can do it via log_archive_n setting right?
Other question: I need enough room in primary so it can have all that "time" of
redo, right? I mean: 12 hours delay -> 12 hours of redo in primary.
Or the logs are transmitted but not applied until the time configured?
finishing: in case of switchover, how can I modify clients so they can connect
to the standby (now primary)? I was think in changing the DNS entry that they
have in tnsnames.ora (it will work) but I think that there are more "elegant"
solution.
Thank You,
Martin.

          Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96653/log_transport.htm#1037696
yes, you read that....

and that says:

The DELAY interval is relative to when the archived redo log is complete at
the destination. It does not delay the transport of the redo log to the standby
database.

so, it does not delay the transmission, just the application.  the redo is at
the standby.

You can use Oracle Names/LDAP to centralize the naming of databases -- so you
can fix it in one location to point to another place.

You can use a tns entry with multiple hosts listed in the order they should be
attempted.

October 24, 2003 Adrian -- Thanks for the question regarding
      "NOLOGGING", version 816

      You Asked
Tom

I've been re-visiting my knowledge on NOLOGGING as a colleague asked me a
question about it today and I hate to say I couldn't remember fully why the term
is a tiny bit of a misnomer (there's no denying that there is a general
confusion "out there" about NOLOGGING and its consequences for redo generation,
recoverability etc.).

Anyway, with NOLOGGING "switched" on for already created tables and indexes, it
seems that all DML is logged unless you use direct path in some way. If I have a
NOLOGGING table and INSERT without /*+ APPEND */ I get logging, and if I have a
LOGGING table and INSERT with /*+ APPEND */ I get logging. I only get no logging
when I have a NOLOGGING table with /*+ APPEND */.

But I'm not sure about ALTER TABLE MOVE and ALTER INDEX REBUILD. If I leave the
tables and indexes in NOLOGGING mode and then perform either of these rebuilds
without specifying the NOLOGGING clause in the ALTER statement, will the action
be logged? If the answer is yes, then would it make sense just to leave all
tables and indexes in NOLOGGING mode anyway?

Regards

Adrian

      and we said...
It is even more deep then that.  For example:

Table Mode    Insert Mode     ArchiveLog mode      result
-----------   -------------   -----------------    ----------
LOGGING       APPEND          ARCHIVE LOG          redo generated
NOLOGGING     APPEND          ARCHIVE LOG          no redo
LOGGING       no append       ""                   redo generated
NOLOGGING     no append       ""                   redo generated
LOGGING       APPEND          noarchive log mode   no redo
NOLOGGING     APPEND          noarchive log mode   no redo
LOGGING       no append       noarchive log mode   redo generated
NOLOGGING     no append       noarchive log mode   redo generated

(geez -- I hope i got that table right!  AND the table changes for 9iR2 which
can be run in "FORCE LOGGING" mode meaning the 4th column ALWAYS say "redo
generated")

Basically -- if you change Insert Mode to "UNRECOVERABLE OPERATION" -- the table
is the same (eg: are you performing an operation which MIGHT be able to be done
without redo generation)....

It will only NOT generate redo in the 3 cases above:

o Object is NOLOGGING and database is ARCHIVE LOG
o Object is either NOLOGGING or LOGGING and database is NO ARCHIVE LOG

NO NO NO -- it does not make sense to leave objects in NOLOGGING mode in a
production instance!!!! it should be used CAREFULLY, and only in close
coordination with the guys responsible for doing backups -- every non-logged
operation performed makes media recovery for that segment IMPOSSIBLE until you
back it up.

see also

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:485221567528

Review &
      Followup

      Rating: 5
Clear and
      Concise! Thanks.  September 05, 2002
Reviewer:
      Robert  from Memphis, TN USA

      Rating: 5
But what
      if...?  September 05, 2002
Reviewer:  Adrian
      Billington  from UK
Tom

Good table. Agree so far, but back to my question about leaving NOLOGGING set to
"on".

1) as we are a production instance, we are in archivelog mode of course;

2) this means that only developers writing direct path SQL*loader scripts and
using /*+ append */ hints in their inserts run the risk of having unrecoverable
work sent to the database;

3) so my plunge into seemingly dangerous territory is only borne out of the
situation whereby, despite having NOLOGGING tables/indexes, everything is logged
anyway unless you specifically ask it not to be - and asking it not to be would
be controlled and managed anyway.

(Let's face it, I'm only trying to get around a situation where if I want a
nologged action I have to alter the table to nologging, direct load insert,
alter table back to logging again).

Which is why I also asked about any table moves or index rebuilds...

Thanks

Adrian

          Followup:

table moves and rebuilds will be done like APPEND would be.

In a production system -- you don't want objects to be left in "nologging" mode.
 You should turn it on -- do whatever -- turn it off and back it up.

      Rating:
      5
  August 06, 2003
Reviewer:  Su Baba
       from Ca, USA
Hi Tom,

In an archive log mode database instance, under what condition would redo NOT be
generated for a CTAS operation?  Is it enough to do just the following?

CREATE TABLE x AS
SELECT * FROM y
NOLOGGING;

or do I need to set the table to nologging as in the case of INSERT /*+ APPEND
*/?

thanks

          Followup:

that doesn't create a nologging table.  that is no different then:

CREATE TABLE x AS
SELECT * FROM y
HELLO_WORLD_HOW_ARE_YOU;

ops$tkyte@ORA9I> @mystat "redo size"
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('redo size')||'%'

NAME                                VALUE
------------------------------ ----------
redo size                             560

ops$tkyte@ORA9I> create table t NOLOGGING as select * from all_objects;

Table created.

ops$tkyte@ORA9I> @mystat "redo size"
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('redo size')||'%'

NAME                                VALUE
------------------------------ ----------
redo size                           73712

ops$tkyte@ORA9I> create table tt as select * from all_objects;

Table created.

ops$tkyte@ORA9I> @mystat "redo size"
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('redo size')||'%'

NAME                                VALUE
------------------------------ ----------
redo size                         5071308

      Rating:
      4
NOLOGGING  August 30, 2003
Reviewer:
      Deepak Gupta  from Delhi,India
Dear Mr. Tom,

    Hi,

CREATE TABLE x AS
SELECT * FROM y
HELLO_WORLD_HOW_ARE_YOU;

and

CREATE TABLE x
nologging
AS
SELECT * FROM y
HELLO_WORLD_HOW_ARE_YOU;

Q1 - what is difference between above two ?  or what advantage using a nologging
option ( as in last  followup you said in both cases redo log will generate ) ?

database in archive mode

Q2- If I put index tablespace (that conatain b-tree indexes) in nologging mode
what advantage or disavantage from this ?
( because at a time recovery index only need to rebuild )

          Followup:

q1) i did not say thing -- in fact i demonstrated exactly the difference between

create table x as

and

create table x NOLOGGING as

one generated lots of redo.
one did not.

your first CTAS will generate much redo
your second CTAS will not

q2) only an "advantage" if you create/rebuild indexes in there frequently.  make
sure to BACK IT UP afterwards else you will have unrecoverable changes in that
tablespace after a recovery.

      Rating: 5
unrecoverable
      columns on v$datafile not being updated  October 24, 2003

Reviewer:  Robert Hayden  from Kansas City, MO

I have found all of the threads on the "nologging" option very useful.  During
testing, I have found that if I rebuild an index with the nologging option, then
the columns unrecoverable_change# and unrecoverable_time on v$datafile are not
updated to reflect the nologging operation.  This would seem to have a
disasterous affect on our ability to monitor such activity for a standby
database.  Hopefully, I am just missing something....

Here is an example, file# 7 is the i_tst tablespace.  Note that the nologging
operation did not change the v$datafile columns, especially on file#7.  I was
able to reproduce on Oracle 8.1.7.4 and Oracle 9.2.0.3 on AIX.

SQL> create table rwh (col1 number) tablespace d_tst;

Table created.

SQL> insert into rwh values (1);

1 row created.

SQL> commit;

Commit complete.

SQL> create index xie1rwh on rwh (col1) tablespace i_tst;

Index created.

SQL> select index_name, logging from user_indexes;

INDEX_NAME                     LOG
------------------------------ ---
XIE1RWH                        YES

SQL> select file#, unrecoverable_change#, unrecoverable_time from v$datafile;

     FILE# UNRECOVERABLE_CHANGE# UNRECOVER
---------- --------------------- ---------
         1                132534 06-JUN-03
         2                     0
         3                     0
         4                     0
         5                     0
         6                     0
         7                     0
         8                132534 06-JUN-03

8 rows selected.

SQL> alter index xie1rwh rebuild tablespace i_tst nologging;

Index altered.

SQL> select index_name, logging from user_indexes;

INDEX_NAME                     LOG
------------------------------ ---
XIE1RWH                        NO

SQL> select file#, unrecoverable_change#, unrecoverable_time from v$datafile;

     FILE# UNRECOVERABLE_CHANGE# UNRECOVER
---------- --------------------- ---------
         1                132534 06-JUN-03
         2                     0
         3                     0
         4                     0
         5                     0
         6                     0
         7                     0
         8                132534 06-JUN-03

8 rows selected.

Thanks
Robert

          Followup:

not to worry -- fact is, if the object is really really small like that, we
cheat.

we generated the redo, silently.

ops$tkyte@ORA9I> create tablespace test
  2  datafile '/tmp/test.dbf' size 25m
  3  /

Tablespace created.

ops$tkyte@ORA9I>
ops$tkyte@ORA9I> drop table t;
drop table t
           *
ERROR at line 1:
ORA-00942: table or view does not exist

ops$tkyte@ORA9I> create table t tablespace test
  2  as
  3  select * from all_objects ;

Table created.

ops$tkyte@ORA9I>
ops$tkyte@ORA9I> create index t_idx on
t(object_name,object_type,owner,object_id) tablespace test;

Index created.

ops$tkyte@ORA9I> pause

ops$tkyte@ORA9I>
ops$tkyte@ORA9I> select file#, unrecoverable_change#, unrecoverable_time
  2    from v$datafile
  3   where file# in ( select file_id from dba_data_files where tablespace_name
= 'TEST' )
  4  /

               FILE# UNRECOVERABLE_CHANGE# UNRECOVERABLE_TIME
-------------------- --------------------- --------------------
                  65                     0

ops$tkyte@ORA9I>
ops$tkyte@ORA9I>
ops$tkyte@ORA9I> select index_name, logging from user_indexes where table_name =
'T';

INDEX_NAME                     LOG
------------------------------ ---
T_IDX                          YES

ops$tkyte@ORA9I> alter index t_idx rebuild tablespace test NOLOGGING;

Index altered.

ops$tkyte@ORA9I> select file#, unrecoverable_change#, unrecoverable_time
  2    from v$datafile
  3   where file# in ( select file_id from dba_data_files where tablespace_name
= 'TEST' )
  4  /

               FILE# UNRECOVERABLE_CHANGE# UNRECOVERABLE_TIME
-------------------- --------------------- --------------------
                  65         7725934409672 24-oct-2003 15:48:25

ops$tkyte@ORA9I>
ops$tkyte@ORA9I>
ops$tkyte@ORA9I> select index_name, logging from user_indexes where table_name =
'T';

INDEX_NAME                     LOG
------------------------------ ---
T_IDX                          NO

ops$tkyte@ORA9I> alter index t_idx rebuild tablespace test;

Index altered.

ops$tkyte@ORA9I> select file#, unrecoverable_change#, unrecoverable_time
  2  select index_name, logging from user_indexes where table_name = 'T';
select index_name, logging from user_indexes where table_name = 'T'
*
ERROR at line 2:
ORA-00923: FROM keyword not found where expected

ops$tkyte@ORA9I> select file#, unrecoverable_change#, unrecoverable_time
  2    from v$datafile
  3   where file# in ( select file_id from dba_data_files where tablespace_name
= 'TEST' )
  4  /

               FILE# UNRECOVERABLE_CHANGE# UNRECOVERABLE_TIME
-------------------- --------------------- --------------------
                  65         7725934409716 24-oct-2003 15:48:25

ops$tkyte@ORA9I>
ops$tkyte@ORA9I> drop tablespace test including contents and datafiles
  2  /

Tablespace dropped.

That shows we do it "right" but look:

ops$tkyte@ORA9I> create tablespace test
  2  datafile '/tmp/test.dbf' size 25m
  3  /

Tablespace created.

ops$tkyte@ORA9I>
ops$tkyte@ORA9I> drop table t;
drop table t
           *
ERROR at line 1:
ORA-00942: table or view does not exist

ops$tkyte@ORA9I> create table t tablespace test
  2  as
  3  select * from all_objects where rownum=1;

Table created.

ops$tkyte@ORA9I>
ops$tkyte@ORA9I> create index t_idx on
t(object_name,object_type,owner,object_id) tablespace test;

Index created.

ops$tkyte@ORA9I> pause

ops$tkyte@ORA9I>
ops$tkyte@ORA9I> select file#, unrecoverable_change#, unrecoverable_time
  2    from v$datafile
  3   where file# in ( select file_id from dba_data_files where tablespace_name
= 'TEST' )
  4  /

               FILE# UNRECOVERABLE_CHANGE# UNRECOVERABLE_TIME
-------------------- --------------------- --------------------
                  65                     0

ops$tkyte@ORA9I>
ops$tkyte@ORA9I>
ops$tkyte@ORA9I> select index_name, logging from user_indexes where table_name =
'T';

INDEX_NAME                     LOG
------------------------------ ---
T_IDX                          YES

ops$tkyte@ORA9I> alter index t_idx rebuild tablespace test NOLOGGING;

Index altered.

ops$tkyte@ORA9I> select file#, unrecoverable_change#, unrecoverable_time
  2    from v$datafile
  3   where file# in ( select file_id from dba_data_files where tablespace_name
= 'TEST' )
  4  /

               FILE# UNRECOVERABLE_CHANGE# UNRECOVERABLE_TIME
-------------------- --------------------- --------------------
                  65                     0

ops$tkyte@ORA9I>
ops$tkyte@ORA9I>
ops$tkyte@ORA9I> select index_name, logging from user_indexes where table_name =
'T';

INDEX_NAME                     LOG
------------------------------ ---
T_IDX                          NO

ops$tkyte@ORA9I> alter index t_idx rebuild tablespace test;

Index altered.

ops$tkyte@ORA9I> select file#, unrecoverable_change#, unrecoverable_time
  2  select index_name, logging from user_indexes where table_name = 'T';
select index_name, logging from user_indexes where table_name = 'T'
*
ERROR at line 2:
ORA-00923: FROM keyword not found where expected

ops$tkyte@ORA9I> select file#, unrecoverable_change#, unrecoverable_time
  2    from v$datafile
  3   where file# in ( select file_id from dba_data_files where tablespace_name
= 'TEST' )
  4  /

               FILE# UNRECOVERABLE_CHANGE# UNRECOVERABLE_TIME
-------------------- --------------------- --------------------
                  65                     0

ops$tkyte@ORA9I>
ops$tkyte@ORA9I> drop tablespace test including contents and datafiles
  2  /

Tablespace dropped.

do it for a tiny object and it was actually logged....

so, you just have too small of a test case.

October 24, 2003 Alvaro -- Thanks for the question regarding
      "Query on design", version 8.1.7

      You Asked
Hi tom,

we have a desing problem ...

we have a table of products like this

PRODUCT
------------------------------
PROD_ID     VARCHAR2(15)
PROD_DESC   VARCHAR2(30)
PROD_TYPE   VARCHAR2(15)
PROD_COST   NUMBER(14,5)
PROD_LEVEL  NUMBER(3)
PROD_STATUS VARCHAR2(2)
PROD_CLASIF VARCHER2(2)

And there is another table with the attributes of each product like this

PRODUCT_ATTRIBUTES
------------------------------
PROD_ID         VARCHAR2(15)
PROD_ATTR       VARCHAR2(15)
PROD_ATTR_VALUE VARCHAR2(30)

And exist another table with all the valid values for the attributes

ATTRIBUTES_VALUES
------------------------------
ATTR_ID        VARCHAR2(15)
ATTR_VALUE     VARCHAR2(30)

SO ... We have products definitions like this

PRODUCT

PROD_ID   PROD_DES    PROD_COST  PROD_LEVEL  PROD_STATUS  PROD_CLASIF
----------------------------------------------------------------------
1000      SINGLE BED  100000     1           ACTIVE       A
1001      DOUBLE BED  200000     1           ACTIVE       A
1002      BABY CHAIR   50000     1           ACTIVE       B
...

PRODUCT_ATTRIBUTES

PROD_ID    PROD_ATTR      PROD_ATTR_VALUE
---------------------------------------------------------------------
1000       CATEGORY       BED ROOM
1000       COLOR          RED
1000       LENGTH         2.00
1000       WIDTH          1.00
1000       MATERIAL       WOOD
1000       STYLE          CLASIC
1000       GROUP          FIRST CLASS
...
1001       CATEGORY       BED ROOM
1001       COLOR          RED
1001       LENGTH         2.00
1001       WIDTH          1.80
1001       MATERIAL       IRON
1001       STYLE          CLASIC
1001       GROUP          FIRST CLASS
...
1002       CATEGORY       DINING ROOM
1002       COLOR          BLUE
1002       LENGTH         0.50
1002       WIDTH          0.50
1002       MATERIAL       WOOD
1002       STYLE          YOUNG
1002       GROUP          FIRST CLASS

then, we want to solve users questions using a query :

*  All the products with  Category "BED ROOM" and Color "RED"  ???
*  All the products with  Category "BED ROOM" and Color "RED" and Material
"IRON"  ???
*  All the product  with  Color "RED" and style "CLASIC"  ???

Every condition of the questions is a row in the struture, so we don't Know how
to build a
"single" query to solve such questions. We use that structure because the
attributes are dynamic,
we don't know  how many features a product will have, product A could have
color,size,material
(3 attributes) ... and product b could have color,style,group, and length (4 or
more ...) and so on.

Is this a wrong design ??? or Just we don´t know how to build a query ???

please forgive me for my english ...

thanks for your help ...

Acueto

      and we said...
Here is a excerpt from my forthcoming book where I talk about this (and show you
how ugly, hard and inefficient queries against your very flexible model will be)

(2)Do not use Generic Data Models

Frequently I see applications built on a generic data model for "maximum
flexibility" or applications built in ways that prohibit performance. Many times
- these are one in the same thing! For example, it is well known you can
represent any object in a database using just four tables:

Create table objects ( oid int primary key, name varchar2(255) );

Create table attributes
( attrId int primary key, attrName varchar2(255),
datatype varchar2(25) );

Create table object_Attributes
( oid int, attrId int, value varchar2(4000),
primary key(oid,attrId) );

Create table Links ( oid1 int, oid2 int,
primary key (oid1, oid2) );

That's it - no more CREATE TABLE for me! I can fill the attributes table up with
rows like this:

insert into attributes values ( 1, 'DATE_OF_BIRTH', 'DATE' );
insert into attributes values ( 2, 'FIRST_NAME',    'STRING' );
insert into attributes values ( 3, 'LAST_NAME',    'STRING' );
commit;

And now I'm ready to create a PERSON record:

insert into objects values ( 1, 'PERSON' );
insert into object_Attributes values( 1, 1, '15-mar-1965' );
insert into object_Attributes values( 1, 2, 'Thomas' );
insert into object_Attributes values( 1, 3, 'Kyte' );
commit;

insert into objects values ( 2, 'PERSON' );
insert into object_Attributes values( 2, 1, '21-oct-1968' );
insert into object_Attributes values( 2, 2, 'John' );
insert into object_Attributes values( 2, 3, 'Smith' );
commit;

And since I'm good at SQL, I can even query this record up to get the FIRST_NAME
and LAST_NAME of all PERSON records:

ops$tkyte@ORA920> select
     max( decode(attrName, 'FIRST_NAME', value, null )) first_name,
  2  max( decode( attrName, 'LAST_NAME',  value, null ) ) last_name
  3    from objects, object_attributes, attributes
  4   where attributes.attrName in ( 'FIRST_NAME', 'LAST_NAME' )
  5     and object_attributes.attrId = attributes.attrId
  6     and object_attributes.oid = objects.oid
  7     and objects.name = 'PERSON'
  8   group by objects.oid
  9  /

FIRST_NAME           LAST_NAME
-------------------- --------------------
Thomas               Kyte
John                 Smith

Looks great, right? I mean, the developers don't have to create tables anymore,
we can add columns at the drop of a hat (just requires an insert into the
ATTRIBUTES table). The developers can do whatever they want and the DBA can't
stop them. This is ultimate "flexibility". I've seen people try to build entire
systems on this model.

But, how does it perform? Miserably, terribly, horribly. A simple "select
first_name, last_name from person" query is transformed into a 3-table join with
aggregates and all. Further, if the attributes are "NULLABLE" - that is, there
might not be a row in OBJECT_ATTRIBUTES for some attributes, you may have to
outer join instead of just joining which in some cases can remove more optimal
query plans from consideration.

Writing queries might look pretty straightforward, but it's impossible to do in
a performant fashion. For example, if we wanted to get everyone that was born in
MARCH or has a LAST_NAME = 'SMITH', we could simply take the query from above
and just wrap an inline view around that:

ops$tkyte@ORA920> select *
  2    from (
  3  select
     max(decode(attrName, 'FIRST_NAME', value, null)) first_name,
  4  max(decode(attrName, 'LAST_NAME',  value, null)) last_name,
  5  max(decode(attrName, 'DATE_OF_BIRTH',  value, null))
                                                      date_of_birth
  6    from objects, object_attributes, attributes
  7   where attributes.attrName in ( 'FIRST_NAME',
                                     'LAST_NAME', 'DATE_OF_BIRTH' )
  8     and object_attributes.attrId = attributes.attrId
  9     and object_attributes.oid = objects.oid
 10     and objects.name = 'PERSON'
 11   group by objects.oid
 12         )
 13   where last_name = 'Smith'
 14      or date_of_birth like '%-mar-%'
 15  /

FIRST_NAME           LAST_NAME            DATE_OF_BIRTH
-------------------- -------------------- --------------------
Thomas               Kyte                 15-mar-1965
John                 Smith                21-oct-1968

So, it looks "easy" to query, but think about the performance! If you had a
couple thousand OBJECT records, and a couple tens of thousands of
OBJECT_ATTRIBUTES - Oracle would have to process the entire inner group by query
first and then apply the WHERE clause.

This is not a made up data model, one that I crafted just to make a point. This
is an actual data model that I've seen people try to use. Their goal is ultimate
flexibility. They don't know what OBJECTS they need, they don't know what
ATTRIBUTES they will have. Well - that is what the database was written for in
the first place: Oracle implemented this thing called SQL to define OBJECTS and
ATTRIBUTES and lets you use SQL to query them. You are trying to put a generic
layer on top of a generic layer - and it fails each and every time except for
the most trivial of applications.

Review &
      Followup

      Rating: 2
The crux of the
      question wasn't answered!  June 26, 2003
Reviewer:
      A reader
Tom, the example you used is okay but I am sure such designs are used often in
the real world and doesn't portray real business problems posed by Alvaro
question such as

"...We use that structure because the attributes are dynamic, we don't know  how
many features a product will have, product A could have color,size,material (3
attributes) ... and product b could have color,style,group, and length (4 or
more ...) and so on. "

How would you tackle a design issue such as the one described above?

          Followup:

I answered the crux of the question:

quote:
Is this a wrong design ??? or Just we don´t know how to build a query ???

answer:
wrong design

I thought it was very straightforward.  Not having all of the requirements -- I
cannot comment further.  But I would betcha there is a finite set of attributes
such a thing could have -- at the very least, there is a very LARGE set of
common attributes.

They have gone the route of "total flexibility" -- which I many times find is
"we didn't gather the requirements really".  That could be part of the issue.

I would go with

create table t (
<set columns -- things we know everything has>,
<columns most things have, color, size, cost, shape, etc>,
<couple of flex fields for oddities>
);

a single table, many attributes.  Ability to index.  Ability to query
performantly.

I would also bet that most attributes are in fact "well known"

      Rating:
      2
Correction!  June 26, 2003
Reviewer:
      A reader
I should have said Tom's example (4 table data structure) is "Not" used in the
real world very often...

          Followup:

you would be surprised how many systems do this, "flexibility" is their mantra.
excessively poor performance is their life.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6692296628899
for the classic example of this sort of thinking taken to an illogical extreme.

      Rating: 4
Alternatives
      for that bad design  June 26, 2003
Reviewer:
      Alvaro  from Barranquilla, Colombia
Ok, Tom we understood your approach ...

If we use that design in our application the performace will be poor ...

So , We are thinking to store all attributes in the same table no matter what
king of attribute ..

product table would be

prod_id
prod_atr1
prod_atr2
prod_atr3
...
prod_atr20

With this model we have all the information in one table, but the problem would
be the user must know that atr1 is color and atr is size and atr3 is last_name
... and so on.
The flexibility is not 100% but the performance would be better ...

what do you think about it ...

Thanks
Acueto

          Followup:

I think you'll find MOST attributes are in fact well known.

FEW attributes are not known.

So, I think you have a table with lots of fixed attributes -- color, size,
last_name and so on.

You have a couple of columns at the end that vary by "product"

and the end users would have to "know"

But think about how excellent they would have to "know" SQL in order to query
the other model at all -- look at the "or" example I have -- ugly.

      Rating: 5
Proper data
      analysis required  June 26, 2003
Reviewer:
      A reader  from UK
Very good points Tom! Proper analysis of the business procedures and data
storage requirements should yield the right balance between flexibility and
performance.

      Rating: 5
Column
      names  June 26, 2003
Reviewer:
      A reader  from UK
Alvaro

From the information you have provided it seems that most products have the same
basic information about them, eg. colour, category, material. What prevents you
from using these as the column names? Without a full analysis of the business,
though, it would be impossible to determine the correct table/column set to use
for you data storage.

      Rating:
      4
attributes  June 26, 2003
Reviewer:
      Alvaro  from Colombia
Thanks UK ...

As you said we would use color, material, category and  ...
for column names, but actually, the attributes could be more and in some cases
unknow for new products.
I could say you our product attributes are this and no more:
(color, material, heigh, width, category , ... ) only 15 attributes but it's not
the real world.
If a new product is added to our company it could have some attribute that does
not exits, for this reason we are thinking in table fields like

prod_id
prod_atr1
prod_atr2
...
prod_atr20

      Rating: 5
Tom's Date of
      Birth is 15th March 1965  June 26, 2003
Reviewer:
      A reader
Is that true ?

      Rating:
      5
Analyse  June 26, 2003
Reviewer:
      Paul  from UK
Unknown will be just fine for new products, simply leave it NULL.

Ok, now I'm getting to know more about your business, but more information is
still required. For example what is the scope of the products covered by the
business? Could you use the scope then to come up with good column names that
will cover 99% of all products you would need to store? Might it be necessary to
sub-divide products into specific groups, perhaps requiring more than one table?
Maybe two groups of products have totally different attributes. Also, we are
probably only looking at one tiny part of your whole system, perhaps other parts
will affect how this part is designed.

Designing, as with any planning type activity will be more accurate and complete
when the information provided is accurate and complete.

      Rating: 5
Think of it
      from the users point of view.  June 26, 2003

Reviewer:  Chuck Jolley  from OKC, OK USA
I try to think of these things from the users point of view.
Numeric attributes like size, cost, number per package etc that virtualy all of
your products will have
or properties that could use list of values from lookup tables like color,
material, group, style, etc get columns.
If the LOV would be too long to use for data entry or a drop-down on an inquery
screen, why give it it's own column?
Just put that stuff in a wide descriptive column. You can still index and search
on it.

select *
  from products
 where desc like '%64 SQUARES%'
   and desc like '%TELESCOPING LEGS%'
   and color = 'RED'
   and width = 17
   and units = 'INCHES'
   and etc;

Gets you all the red 17" chess and checkers tables with telescoping legs.

Without creating a table with 1000 columns or 1000 lines of sql.

If something REALY new comes in that won't fit you can always add another
column.
chuck

          Followup:

(tagged xml might even be a consideration.... oh, did I really say that out
loud)

we can index that (XML) and do section searches amongst other things.

      Rating: 5
Flexible
      database design  June 26, 2003
Reviewer:
      Yong  from CA, USA
The example Tom put out here
was used/have been useing in a real world.

I involved two projects for two big companies at Bay Area. The java developers
int my team try to convince me how flexible the data model will be. I gave the
exact answer as Tom did: your flexible will kill the database. Unfortuantely, I
didn't win these wars and development started. I knew what was going to happen.

In the test environment, we have 20 rows on each tables, java developers could
show how easy and flexible for a java develper to add any new attribute and
feature. Then they were vary happy.

When we started the production, and table rows grew to 10,000 records in the
tables, we encountered performance problem. After a user provide user name and
password, our J2EE app loaded search the person's profile, it took 40 seconds to
show up the next page!

Happy now?

Finally, I have to re-design the data model 6 month later.

I read many Tom's idea and couldn't agree more.

      Rating: 4
Supporting this
      model  June 26, 2003
Reviewer:  will
       from TX
I'm currently cursed with maintaining this type of model.  It supports a web app
that generates a search on the various attributes.  The queries that are
generated are something (evil) to behold.  No way it will scale.

The question is how to redesign the structures to support the user's ability to
add products and attributes and efficiently maintain and query the resulting
large and varied set of searchable attributes.

Above you recommend a product (with flex field) approach.

We could probably get around the "user created model" requirement by giving them
some facility to assign the flexfields for a given use.  However, the number of
flexfields would be very large.

To support the large and diverse set of attributes,  would you recommend a
separate table for each product type?  How fine should the distinction be?  For
example, should you have a single "computer" table or a "PC" and "Server" table?
 Do you think a company like Amazon would store the data in a table per product
type model?

I realize that these are decisions that would have to be made for our specific
requirements, but any discussion on the alternatives would be greatly
appreciated.  Will you discuss alternatives in your book?  I know I'll be first
in line when it comes out.

Thanks for the great site (wow, cool spellcheck).

          Followup:

You might consider XML, you can store attributes in a tagged "document",  Oracle
text can index that.  You can do section searching on it.

benchmarking such an implementation would be crucial obviously.

      Rating: 5
Also, the
      generic design prevents enforcement.  June 26, 2003

Reviewer:  Chuck Jolley  from OKC, OK USA
I forgot to add, that doing the design genericaly like that prevents the use of
two of the most classic types of constraints: foreign keys and lookup tables.
What is to prevent someone from entering 'INCHES' for the value of the attribute
'COLOR'?
Let alone the "attribute" 'RANDOM_STRING'?
A 10,000 line check contraint?
10,000 lines of application code?
In our shop we call this the "Big Chief Tablet" school of design.
The database is just used like a gigantic piece of notebook paper.
In my view all the attribute columns except "desc" should be enforced  by check
constraints or forgeign keys on lookup tables.
This alone will save the programmers a huge amount of work.
chuck

          Followup:

hear hear.

(when I talk of xml above -- it would be for ONLY those things that are
definitely NOT KNOWABLE until months after the system is fielded -- the
exceptions, things to add to version 2)

      Rating: 5
Re: Tom's Date
      of Birth is 15th March 1965  June 26, 2003

Reviewer:  A reader
I've got to say that if it's true, he looks like heck for being only 38 (just
kidding, Tom!).

      Rating: 5
Similar
      problem  June 26, 2003
Reviewer:  av

I am facing same problem in our system. We do have these 4 tables to store the
template data. Mostly the data is from the forms/applications templates.

For example the template is an application when you need to apply for a credit
card or auto loan or insurance etc., The system doesn’t know the template and
its attributes well in advance. Developers can create new template and the
engine automatically recognizes it as a new template and registers its fields
and values in the database. The 4-

table structure is:

1. Template_type Table
Template_Type_id number,
Template Type Name varchar2

2. Template Table
Template_id number,
Template Name varchar2,
Template_Type_id number

3. Template_Field Table
Field_id number,
Field_name varchar2,
Template_type_id number

4. Field_value Table
Field_Value_id number,
Number_val number,
String_val varchar2,
Date_Val Date
Template_id number,
Field_id number

With this flexible table structure developers don’t care about the number of
templates and the number/type of fields in it. It is easy for them to allow
users to fill templates and store them in database. It is also easy to show all
templates and its fields.

But the real problem is with the reporting (both ad-hoc and standard). The
field’s sizes in each template vary between 100 and 500. Right now we have 40
templates, 8000 fields and 400,000 field_values.

We designed the datawherehouse table as a flat table with 1000 columns (number,
varchar2 and date) and transforming the data into columns in ETL process, and we
are reusing columns. Because we need to give sort by, filter, group by,
aggregate functions etc features on each and every fields in ad-hoc reporting.
So we thought that de-normalizing and storing the data in columns give
flexibility at report level.

Now the queries for a simple select become

Select decode(Template_Type, 1, number1_column, 2, Number15_column, 3,
number34_col, null) column1, decode(Template_type, 1,  varchar22_col, 5,
varchar35_col, null) column2
From dw_table

The reason for this that we want to give a report of all the field values for
that user templates (if the field exists in multiple templates then we want get
all of them like cross-template) is with the column reuse let say field
“Total_cost” can exists in any column. So using the metadata we are preparing
the above decode to find out all values.

These queries are taking a very long time. I couldn’t fully understand the XML
approach that you talked about. Is it a viable solution for my problem?

Can you please let me know the best approach to solve the above said reporting
problem? If you need to implement this framework how do you handle this design?

          Followup:

It'll help on searches for a document instance (XML will) cause you can

select ... from t where contains( 'something within attribute' )

and get the instances back.

As for being a data warehouse solution, not so good.  there you need facts,
dimensions.

Like I keep saying -- this flexibility = great, good for the developers.  this
flexibility = stinks, terrible for any ad-hoc queryies, aggreations, "ors", you
know  -- what people want to do with data.

I think  your data warehouse needs 40 fact tables (40 is NOT a big number, you
don't have that many templates).

I think your data warehouse needs columns that convey meaning and are
appropriately indexed with proper datatypes.

I think your "etl" process would convert their "glob of nothing" into something
that people can use in the warehouse.

Their structure is excellent for working on a document instance ("hey, get me
the data for document=555, that user is here to input the information")

Their structure is horrible for actually using the data any other way.

I would have changes to a template notify someone so the warehouse can be
modified to accomidate it properly.

      Rating:
      5
Thanks  June 26, 2003
Reviewer:
      A reader
Thanks a bunch for your feedback. I totally accept your point. Still I have a
question.

1.    The reason for merging them to provide cross-template queries, like get
the cost field from Template 1 and Template 5 or aggregate quantity from
Template 10 and Template 20. I have to merge the columns in the different table
to get this kind of reports.

Another different question, which is not related to above argument is:

Some templates have around 500 columns, so they will become 500 columns in a
table (I have this problem with current table too).  Is it going to scale well?
Please let me know if you can think any better approach?
Thanks in advance.

          Followup:

facts and dimensions are useful to break out "500" columns into more meaningful
sets of tables.

It sounds like you need 1 fact table with common attributes, then 40 other
"subfact like tables" for the non-common attributes, with dimensions off of
each.  Practically speaking in a DW environment, I would probably have 41 facts
-- one for the common with all rows from all templates -- and the ability if
need be to drill down to the non-common details.

Then I would have 40 more fact tables -- that replicate the common but also have
the non-common attributes.

In that fashion, I don't have to join common to non-common to query "template 1"
, I just query template 1.

      Rating: 4
The original
      question  June 27, 2003
Reviewer:  A support
      manager  from UK
I agree with Tom whole heartily - just don't go down the route of 'common
object' tables. It kills performance, and confuses support staff (or at least
gives them a long learning curve on the meaning of data).

I inherited a DB put together by a bunch of dogmatic OO programmers: All data
"items" are in a single narrow table with a "class descriptor" to tell you if
looking at (say) a stock item or an outlet. Values associated with item are in
an attribute table. Certain items have properties that are items in their own
right, these are in a link table. Certain items have parents - eg a "steak
dinner" has a parent of "meat dish" which has a parent of "food". Each of these
relationships is held in a single two column table of item, parent. Most simple
queries run as 3 or more joins to the item table.

Performance is a dog. And to make the batch (output) interfaces fly the
developers had to create temporary tables that contained a conventional
(multi-column) form of the data

Performance is designed in - it follows analysis and design, it is not a
generic, commodity item.

      Rating: 5
Don't you have
      to take DB theory to get a programming degree any
      more?  June 27, 2003
Reviewer:  Chuck Jolley
       from OKC, OK USA
What's the difference between that and using flat files?
I mean, they could have done the same thing with three flat files and a couple
of app maintained "index" files.
They would have saved the price of Oracle and support.
It might have even run faster.

      Rating: 4
Generic
      Database Models, don't try this at home  June 27, 2003

Reviewer:  Jeff Klee, M.O.S. (Man-of-Science)  from
      Research Triangle Park, North Carolina
Tom,

I really enjoy your column. This thread peaked my interest because I'm an
application DBA and I support Oracle's "Oracle Clinical" application for a large
pharmaceutical company. Oracle Clinical is a large "system" which is generally
used to capture clinical trials data using a flexible and generic data
structure. I would describe the datamodel in a nutshell by saying that it is
built around two entities, a question (i.e. "what is your birthday", "what is
the barcode on the blood sample") and a response (i.e. "16-JUN-1988" and
"AA400055H" respectively). In effect, the RESPONSES table contains almost all of
the dynamic data and the remaining schema objects (~10,000 although I didn't
count them), are the metadata behind the RESPONSES table and the metadata that
is used to construct the presentation layer on the fly. As you suggest, building
a system like this is not like picking fruit and you need an army of folks to
build it (read “a large pile of money”) and another highly trained group to "fly
it". My role in this is to trouble-shoot problems where the typical problem
statement is something like  “why is the database slow" (usually when someone
builds a slow custom-query) and “can you make it go" (usually a non-database
issue - network routers, defective firewalls etc), so, I’ve become familiar with
the underlying table structures (~400 tables). After working with this product
for a year, I have concluded that the generic database model probably makes
sense in this case because you can build a single product, sell it to several
end users and there is a large enough market to justify the expense (read
“payback the cost of building it”). The main benefit of this architecture is
that it allows a company to build their own presentation layer and capture any
kind of data they want, and do this quickly. Although it's marketed as a
validated clinical-trials application, you could choose to capture other types
of data. My intent is not to challenge your premise, "Do not use Generic Data
Models" but to emphasize it. I would go on to qualify that statement by saying
that one should have a sound reason to do it, like a business plan that
justifies the expense of building such a complex system by weighing the benefits
of doing so. In summary, Oracle Clinical provides my company with a high-speed
mechanism for deploying custom applications (on the web). Although Oracle
provides a mechanism for addressing performance problems and building generic
"extract views" that make it easy to query the database, designing a system this
complicated is probably a lot of "hooey" if you're only deploying one instance
of it (and then I would call that a career development system). Also note that,
since the RESPONSES table contains actual entities and their attributes, we
extract some of them to a relation model where we can build high-performance
(and more readable and easier to understand) queries. So my conclusion is that
this generic model makes sense in this case and it does what it’s intended to
do, capture data. Don’t try this at home though.

      Rating: 5
Bad design rant
      continued  June 27, 2003
Reviewer:  A Support
      Manager  from UK
If I had my way I would take their degrees from them, Chuck!

As I said above, performance is not an accident, it is good design.

In the OO system above, this generic approach to design has made the system far
too complicated, and too divorced from the meaning of the business data, I would
love to know that a widget costs a dollar and not that Item 10000324 has an
attribute of type 345 with a value of 1.
By the way, this madness also pervades into the application code itself - too
many layers of abstraction and objects that are way to big. No wonder the
consultancy that put this in for our customer went bust.

      Rating: 4
Synthetic
      primary key and Real Primary Key  August 23, 2003

Reviewer:  A reader
Tom,

can you tell me what is Synthetic primary key and Real Primary Key.

Thanks for your time.

          Followup:

synthetic primary keys are made of rubber.

real primary keys are made of numbers, dates, strings :)

(that was a joke... never mind)

We normally call them a surrogate key.

it is a key you use in place of the real or natural key of a table.  Consider a
table of documents:

create table documents
( path   varchar2(255),
  name   varchar2(255),
  .... other data ....
)

arguably, the primary key -- the natural key of this table is PATH,NAME.
However:

a) we move documents, path might change
b) we rename documents, name might change
c) path and name are "big"
d) path and name are 2 columns, more typing in queries

So, path and name, while they are the natural keys are not necessarily the best
fields to use as the primary key.  Especially for A) and B) -- cascading updates
to foreign keys is *nasty*.  Just because you renamed a document doesn't mean it
is a different document.

So, in real life, we would probably:

create table documents
( id     number PRIMARY KEY,
  path   varchar2(255),
  name   varchar2(255),
  .... other data ....,
  constraint name_unq unique( path, name )
)

we would introduce ID, a surrogate (synthetic) key.  It is imutable (never
changing) solving the A) and B) problem.  It is small solving the C) data
storage problem and it is a single simple column solving the D) issue.

      Rating:
      5
Excellent  August 23, 2003
Reviewer:
      A reader
***** for the joke
***** for the answer

whenever i read your responses i will be very very serious. Today you made me
laugh with the joke .. Great..Thanks :)

      Rating:
      3
informatica  September 30, 2003
Reviewer:
      kit  from england
Re generic designs. I understand there is always a tradeoff
but reading your comments, your do not seem to like them

Have you seen the underlying informatica design. how do they allow users to
create their own source and target columns and apply business logic

          Followup:

the only think i know about informatica is it really likes to do things as
deadly slow as it can -- a row by slow row process.

beyond that, i haven't looked at their tables/implementation -- I just try to
get people to use the database to its fullest.  sometimes a simple
insert/update/create table as select will do in seconds/minutes what otherwise
would take hours/days.

      Rating:
      2
sql  October 03, 2003
Reviewer:
      redmeat  from sg
hi,

it may work if you can build the sql string below.

SELECT * FROM PRODUCT WHERE PROD_ID IN
(
SELECT PROD_ID FROM PRODUCT_ATTRIBUTES A
WHERE A.PROD_ATTR IN (SELECT B.PROD_ATTR
        FROM PRODUCT_ATTRIBUTES B
        WHERE B.PROD_ID = A.PROD_ID
        AND B.PROD_ATTR='GROUP'
        AND B.PROD_ATTR_VALUE = 'FIRST CLASS')
INTERSECT
SELECT PROD_ID FROM PRODUCT_ATTRIBUTES A
WHERE A.PROD_ATTR IN (SELECT B.PROD_ATTR
        FROM PRODUCT_ATTRIBUTES B
        WHERE B.PROD_ID = A.PROD_ID
        AND B.PROD_ATTR='CATEGORY'
        AND B.PROD_ATTR_VALUE = 'BED ROOM')
)
/

PROD_ID         PROD_DESC                       PROD_COST PROD_LEVEL PR PR
--------------- ------------------------------ ---------- ---------- -- --
1000            SINGLE BED                         100000          1 A  A
1001            DOUBLE BED                         200000          1 A  A

      Rating: 5
question on
      design  October 24, 2003
Reviewer:
      A reader
Hi Tom
We have a situation where we have huge files
stored in the database (Eventually this will run
in 9i/10G)

We need to parse it. Parsing in pl/sql has appeal as
you have something in the DB and you do the parsing,
store the parsed results in tables for the UI to display.

However, pl/sql is not meant to be used (I think )
as a parsing mechanism. perl *perhaps* is better bcos
it is designed upfront for this. (10G regular expressions
in PL/sql mitigate this problem a bit may be for 10G)
But doing it in perl introduces the "extproc" complexity and with it perhaps
other complications.
If you want to do it in the middle tier then you have to ship this file contents
over - not a good idea.

What are your thoughts?
1. pl.sql
2. perl with extproc
3. middle tier

My choices are as above in the order of preference.
Please let me know what your thoughts are.

Thanx for an amazing site!!

          Followup:

what is this document and what does your parsing involve (i've never personally
seen the appeal of perl, ugh)

      Rating: 5
Thanx
      Tom!  October 24, 2003
Reviewer:
      A reader
The document is a text document (if that is what you meant)
It contains information that needs to be parsed and
put in tables for the UI. The information is in a format
that would need reasonable amount of parsing to be
carried out.

Thanx!

          Followup:

do you have a "for example" (and why wouldn't the client that stuffs it in do
the parsing?)

      Rating:
      5
thanx!  October 24, 2003
Reviewer:
      A reader
Unfortunately, I can not give you an example right now
for confidentiality reasons.

Anyways, I dont think the parsing involved is going to be
*very* heavy duty and can be done in pl/sql with the array
of string manipulations. My concern is that if you are using too much pl/sql
processing (due to the size of the file) it may bog down the database. The
process itself
can be done in background using jobs. - it is not the
"ui" ineractiveness that bothers me but the resource
consumption at the database.

for example (fake)
a=b,c=d,

put it in a table where col_a  = b and col_c = d

Thanx!!

October 25, 2003 Avi -- Thanks for the question regarding "'Kill
      session' priority", version 8.1.6

      You Asked
How can I change the priority of the 'kill session'
so the killed session entry in v$session will be deleted faster ?

      and we said...
The session is *killed* but it is being cleaned up.  When you
kill a session -- outstanding work must still be cleaned up.  It
may take quite a while for this to occur as it is a relatively
low priority background event.

Also, if the user is still physically connected and idle (eg:
they are sitting at a sqlplus prompt) -- we will wait for them
to contact us so we can give them the error message "you are
killed".  In that case, they may stay in the killed state for
quite a while, until they hit the keyboard.

If we are in the second case, they can stayed KILLED forever --
their locks are released.  To see this, simply log in as scott
and issue "select * from emp for update".

In another session do this:

ops$tkyte@ORA8I.WORLD> select * from scott.emp for update
nowait;
select * from scott.emp for update nowait
                    *
ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified

shows scott has them all locked...  Now, identify scotts
session and kill it:

ops$tkyte@ORA8I.WORLD> alter system kill session '286,9195';
ops$tkyte@ORA8I.WORLD> select username, status from v$session
where username = 'SCOTT';

USERNAME        STATUS
--------------- ----------
SCOTT           KILLED

The rows are now unlocked...

ops$tkyte@ORA8I.WORLD> select * from scott.emp for update
nowait;

     EMPNO ENAME      JOB              MGR HIREDATE         SAL
     COMM     DEPTNO
---------- ---------- --------- ---------- --------- ----------
---------- ----------
      7369 SMITH      CLERK           7902 17-DEC-80        800
                  20
....

So, if after killing a session the resources remained locked
-- that is due to the rollback that is taking place in the
background.  If it took them a long time to get where they were
in the transaction, it may take a long to to "undo" what they
did and hence it'll take a while.  Work is work and what was
done must be undone.

Also, in Oracle8i PMON defers transaction recovery of "killed"
sessions to SMON which may use fast start parallel rollback if
necessary.  This frees up locks held by the killed process
immediately.
Review & Followup

      Rating: 5
Session killed
      from killed from the client side, but alive from the server
      side  October 29, 2001
Reviewer:  Nag

Tom

I killed quite a few sessions while an update was going on( the same update), as
it was taking too much of time.
Apart from that a few other session were also killed by me.

Now, I find that one of my process which usually takes 20 secons is taking 130
seconds.

I called up the dba , and was informed that I have round about 15 sessions
showing up on the server side.

Was this the reason for my process to slow down.

Suppose say I have 50 sessions killed from the client side, but alive from the
server side, what kind of impact it will have on my work(DML, selects, updates,
deletes, and inserts) i.e. the work Iam doing in my current session.

          Followup:

depends, if the killed sessions were still in the process of rolling back, it
could slow things down.  Check out USED_UBLK in v$transaction to see if there
are outstanding transactions and how fast they are rolling back.

I cannot say "yes that was the reason".  A DBA should have been able to tell you
(using OS tools and peeking at whats going on in the database).

      Rating: 2
'Kill session'
      priority  November 28, 2001
Reviewer:  Femi
       from London
Tom,

what actually does USED_UBLK in v$transaction tell me. Would it tell me if the
user that was killed has any transaction being rolled back?

          Followup:

If USED_UBLK > 0, the session is in the process of being rolled back.

If USED_UBLK is 0, the session is effectively KILLED, resources have been
released, we are just waiting for their client to send a request so we can tell
them "you have been killed".  Locks are gone at this point, its rolled back.

If you see used_ublk > 0 (say 1000) and 15 seconds later its at 800, you can
guess that it will take about another minute (15 seconds/ 200 blocks, 800 more
to go) to finish rolling back.

      Rating: 3
Killing the OS
      server process.  November 28, 2001
Reviewer:
      Mark J. Bobak  from Ann Arbor, MI
Hi Tom,

Have had luck in most cases w/ the following steps:
1.)  kill session
2.)  kill -9 <OS_PID_OF_SERVER_PROCESS>
3.)  ORADEBUG WAKEUP <ORACLE_PID_OF_PMON>

That seems to always immediately make the session go away.
Any negatives in doing that?  Seems to me that it just gets
deferred.

Thanks,

-Mark

PS.  Above the window I'm typing in now, in BIG RED letters
it says "Please do not use this to ask a question."  But,
clearly, people do, particularly when the question is
directly related to the original question.  So, I'm
wondering....is the policy changed?  Evolving?  Is it ok
to ask directly related followups?  Is the warning intended
to dissuade people from asking unrelated questions?  Or
are we all just breaking the rules?  (Including you, since
it also says "I will not be able to answer your questions
here.")  ;-)

          Followup:

I personally like alter system kill session all by itself.

1) it is soooo infrequent that I have to do this (i mean -- really infrequent).
There is a BUG in your system if you find you must do this frequently.

2) it lets the client know "nicely" that the session was killed.  Otherwise they
get a nastly "3113" or "packet writer failure" when they come back.

3) normally, the speed with which the killed session gets rolled back is more
then sufficient (and monitorable).

but yes, kill -9 is something that is done (orakill on NT)

(i changed the "please don't ask" to "keep it in line with the question".  used
to be I didn't have a way to followup a comment ;)

      Rating: 4
Excellent,
      thanks!  November 28, 2001
Reviewer:  Mark J.
      Bobak  from Ann Arbor, MI
Thanks Tom.  In the above, my #1, I meant to write
'alter system kill session'.  And I agree, that's almost
always enough.

However, in cases (fairly rare on my system) where that's
not enough, my #2 and #3 above have (almost?) never failed
to zap the session.

Anyhow, thanks, and thanks for clearing up the policy.

-Mark

      Rating: 5
Column
      details  August 06, 2002
Reviewer:  Gururaj
      Kulkarni  from Bangalore, Karnataka INDIA
Tom,

I referred to user_col_comments to get the information about used_ublk of
v_$transaction(SYS). But there is nothing.

Where would I get the information about columns and the contents?

Thanks

          Followup:

The reference manual contains all of this stuff for the data dictionary views:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76961/toc.htm

      Rating: 5
Abnormally
      terminated application and rbs  October 24, 2003

Reviewer:  Vera  from D.C.
Hi, Tom,
Your Expert One-To-One is the book I read over and over again as I used to read
The Lord of the Rings. Right now I am searching through it and prepare paper
mostly based on it - in order to understand the below issue and also to lay out
reasons for my team why we cannot and should not disable some of Oracle
functionalities.
We have huge application that takes a long long time to run. It was literally
translated from COBOL, it processes records row by row, inserts them in 3
"target" tables and log error table and commits for every row. I hope I am close
to convincing my superiors of the idea of rewriting it, but right now my problem
is this:
One of our users started the application on the server through the front-end GUI
(Visual Basic). Then his computer got frozen and he manually rebooted it. At
that time I happened to look at trace files and alert log and saw them rapidly
growing with messages (end-of-file, resource busy --the application was trying
to truncate tables in a loop (Tom, I did not write this code!)--  and "logical"
error messages related to the fact that non-truncated tables had more than one
records etc. I called the user and found out about reboot. I looked at the
"target" tables and saw that they still were loaded - slowly, at the rate of 1
record per 7 minutes. Our error log table was slowly growing too - with the same
messages as in the files. It was late and our DBAs who could reboot the instance
were gone for the day. I killed the user's session from my TOAD. Session got
marked "KILLED" not INACTIVE and the files were still growing and tables were
still (very very slowly) loaded. In a couple of hours, after I already left and
while this was (I am sure) still going on, another user kicked out the same job
so the next day I found our error log table populated with millions of records
(remember, application commits every record in a loop - Tom, I did not write
this code!). Well, somebody stipulated that all this mess happened because
Oracle was rolling back and my superior (very technical and smart, but he is not
Oracle person) asked me if we could get rid of rollback segments, redo and other
Oracle functionalities ("we have batch jobs and we do not need all these
wonderful things"). When I said no, I was asked to prove it and that is what I
am trying to do now. I am digging and will dig through your book and other
sources, and I already did, but I - and many many other developers I am sure -
would be extremely interested in how you see that: why Oracle continued to load
tables and why we should not somehow disable rollback (I am not talking about
NOLOGGING AND INSERT/*+APPEND*/, I am talking about completelly disabling rb),
redo etc.
At this very moment I do not have an access to our system, but I am sure we use
Dedicated server mode.

          Followup:

ummm, just give him the concepts guide and say "read it"

it would become obvious that the only reason you use oracle is to have redo,
rollback, and other related functionality.

just point him/her to this page -- bookmark this followup.

The answer to their question is "NO", you cannot turn off redo, you cannot turn
off rollback, and "YES" it sounds as if the code has been done as wrong and
inefficiently as possible.

the entire reason they bought a database would be obviated by turning off these
things.

October 25, 2003 -- Thanks for the question regarding "Problems
      with the explain plan using in top sessions", version 8.1.7.3.0

      You Asked
Hi Tom,

The other day, one of the developers asked me help because one of his queries
was taken a lot of time to return
and when he checked  the explain plan for that session in the Top SEssion tool,
the explain plan looked fine.

When I checked the query using tkprof I found out that the query was being
resolved using the rule optimizer, this was because the developer had
changed the optimizer to rule in his session and that was causing all the
problems to the query, the explain plan chose by the rule optimizer wasn't the
best approach for that query.
But I also found out something really weird for me, the Top session explain plan
was totally different from the explain plan that I got
using auto trace and tkprof. I mean the explain plan that I got for that query
using auto trace and tkprof were the same and they showed me
that the query was resolved using the rule optimizer, but the top session showed
an explain plan as if the session was using the choose optimizer.

Is it mean that "Top Session" is aware only of the setting in the init.ora but
not on "any" settings that we were altering through the session?

Any advice on these will be really appreciated.

Thanks

      and we said...
explain plan AND autotrace can both "lie" -- especially explain plan!!!!

The only source of "truth" is

o tkprof run WITHOUT explain= !!!
o v$sql_plan (in Oracle9i and up)

You see -- explain plan is executed in the current session (whatever the "top
session" tools session was).  It will use the current environment for doing the
explain plan.  (if you have my book "Expert one on one Oracle" -- i have an
example of this in there you can test with to see this)

TKPROF is the source of truth -- OR you must run the explain plan in the session
with the sessions settings (sort_area_size, db_file_multiblock_read_count --
everything)

Review &
      Followup

      Rating:
      3
  November 02, 2002
Reviewer:
      A reader
thanks for answering me Tom.
I understood what you said about tkprof and autotrace because I always use these
tools for tuning, but I'm still confuse in the case of Top SEssion and I want to
understand why Top session was showing something different from tkprof.

You said <quote> You see -- explain plan is executed in the current session
(whatever the "top session" tools session was).  It will use the current
environment for doing the explain plan. <end quote>

Let me see if I understood you:
The user changed the optimizer mode to rule in his session (before executing the
query), and as Top Session is not aware of individual settings that users can do
in their sessions, that's why when the user checked the explain plan for that
same session using top session, the explain plan was displayed using the choose
optimizer, because this is the mode in which the database was set up?

Thanks

          Followup:

tkprof shows the query plan that was really actually used when the query ran.
The trace file contains the honest -- true query plan that was executed.

"top sessions" (whatever that is -- something from OEM or some other tool I
guess) just use explain plan to determine what plan might have been used.  The
explain plan generated is dependent very much on the environment in place at the
time of generation.  If the session that really ran the SQL had sort_area_size
of 10m but the top session thing had a sort area size of 512k -- they might show
VERY different plans.

Yes, your interpretation is valid.

      Rating: 5
tkprof is
      best  November 03, 2002
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
I recently started using a third party tool and OEM Tuning tool just for
experimenting. The explain plans generated by these tools sometimes differ
widely from those generated by tkprof. Only tkprof can be trusted to give the
actual execution plan used by the CBO. For several weeks I was confused like a
"deer in the headlights" before realizing this.

      Rating: 4
What privs do I
      need to use TKPROF?  October 25, 2003
Reviewer:
      Ajums  from Ohio
Hi Tom,
One of the problems in our shop is that developers aren't supposed to be doing
any tuning. Any query which is slow is fixed up by the DBA's.

I have access to my schema in a limited fashion. I am not able to do a number of
things including create indexes.

Do I need special rights for TKPROF?

Regards,
Ajums

          Followup:

you are kidding?  seriously?  and what brain surgeon came up with this concept?
Must be a brain surgeon because they certainly are not into software development
or building systems.

Not creating indexes - that I agree with (thats a DBA's job).  Telling the DBA
"hey, we really need this index for this thing over here" and letting them look
holistically at the system and determining whether an attribute needs to be
added to an existing index or a new one created or none at all -- thats their
job.

but tuning queries -- that is your job.

In my newest book "Effective Oracle By Design" -- I show how to set up for
"tkprof for the masses".  In general, you need access to the trace files on the
server.  There are a couple of ways to do that -- but the DBAS need to
facilitate this for you.

      Rating: 4
You are
      right.  October 31, 2003
Reviewer:  Ajums
       from Ohio
Hi Tom,
I absolutely agree with you 100 percent. Unfortunately the cartoons in my shop
don't.

I guess I am totally stuck now.

Regards,
Ajums

October 25, 2003 parker -- Thanks for the question regarding
      "parallel server", version 8.1.7

      You Asked
Hi Tom

I have been doing some reading on parallel server. I have the following
questions.

Is parallel server a normal practice in real time. What kind of requirements
will make one choose paralle server option.

Finally

Can you explain in depth about how a database can be set up in a parallel server
mode.

Parker

      and we said...
"in real time" -- not sure what that means.

OPS is designed for highly available systems. You can protect your disks with
raid.  you can protect your network via redundancies.  If the computer with your
database fails however -- its all over.  With OPS -- many computers in a cluster
have the database open -- there is no single point of failure.

OPS is designed for highly scalable systems.  Need more resources, drop in a new
box.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:936229578864
or search for

ops oracle paralle server

on my site for more info.  Setup is in the docs -- in depth is way too many
pages for this forum

Review &
      Followup

      Rating: 5
latency issues
      with OPS  March 05, 2003
Reviewer:  P
       from AL
are there any latency issues with OPS...what happening is  when our application
is connected to standalone DB it workes well but when thru OPS sometimes we see
delayed deletes or inserts...meaning...if user delets an item its still shows up
in the list but if you refresh it goes away...same thing with insert ...when
user inserts it doesnt show up in the list but it does after refresh....i was
just wondering could this be OPS issue or something else!!!!!
appreciate all of your comments

          Followup:

gotta be something else, it doesn't work like that.

      Rating: 5
help-
      installing ops on red hat 7.2  March 24, 2003

Reviewer:  P  from AL
tom
we are trying to install ops on redhat 7.2.
i have all the documentation from metalink and oracle documentation also....do i
need to to know anything special about installing ops on redhat 7.2
we are testing linux for our migration from...NT and some other UNIX
environments...

thank you for all your help
P

      Rating: 4
here is the
      scenario we are facing  April 29, 2003
Reviewer:
      P  from AL
hi tom,
do you think this is a OPS issue We are currently using 2 node OPS for all
production databases (there are 3 databases total).  Each node has it's own IP
address and shares raw disks.
Sometimes a select doesn't return the values from an earlier insert, update or
delete.  This happens when the insert/update/delete runs and commits on one node
and then the query runs immediateley after that on the other node.  This can
happen on subsequent page views (in testing where page-views are hit within
milliseconds after each other), or on a single page view where multiple database
connections are obtained and released

The refresh problem was corrected by shutting down one
of the instances of the database and directing the connection pool to the other
instance.  When the second node was brought back up and the connection pool
distributed across both instances the problem returned.
That's why we think it is a synchronization problem between nodes

          Followup:

I'm wondering how a human can measure milliseconds?

but no versions
no os's
not much of anything to go on....

      Rating: 2
ops
      issue  April 30, 2003
Reviewer:  P
       from AL
sun OS
oracle version Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production

oh and generally humans use time machine to measure miliseconds ;)

          Followup:

so, I'm wondering how you measured this.

      Rating: 1
what is
      it....  May 01, 2003
Reviewer:  P  from
      AL
hey tom,
this is the site i go to when i have any question regarding oracle.
well if you dont want to answer my question.....thats fine with me....
enjoy!
P

          Followup:

really -- how did you measure this -- across two machines, down to the
millisecond?

I need to know that the test is

a) valid
b) correct
c) believable

if you are not willing to share with me that, I'm not going to try and track
down a red herring.

      Rating:
      1
timemachine  May 01, 2003
Reviewer:
      P  from AL
oh and we use timemachine thats what i tried to tell you but seems like you dont
know whats timemachine!!!! ;)

anyway....

      Rating:
      5
  May 01, 2003
Reviewer:  Jim
       from Australia
Its amazing how the rating falls when Tom doesnt agree :-)

      Rating: 5
P needs to call
      Support  May 01, 2003
Reviewer:  Jim
       from Portland, OR USA
Hey, P , you will attract more flys with honey than with vinegar.  If you think
the software is broken then pay for support and call them.  Getting aserbic
(with a free service) isn't going to get you very far and makes you look highly
unprofessional.

          Followup:

i'm willing to look -- i just need to know how they are measuring this - across
nodes -- down to the millisecond.

convince me there is a problem, I'll look.  too many problems are with the TEST,
not the database...

      Rating:
      5
  May 01, 2003
Reviewer:  Sam

Mr. "P from AL",
You way over reacted.
It seems you are new to this site. Tom is a real Guru in oracle, who is devoting
lot of energy and time to solve practical issues faced by oracle users.
Time and again, lot of experts have actually fallen flat, when they claim some
thing (e.g count(1) faster count(*), index scan faster than full table scan etc)
and when they share their test case, Tom proves them wrong.
Though highly unlikely, let's for a moment say Tom doesn't know how to measure
milliseconds, then don't you think, you should share it with all of us.  I mean
as a developer, learning new tip/trick is always fun. We all will be obliged to
learn something new.

      Rating: 4
It realy
      doesn't work like that  May 01, 2003
Reviewer:
      A visitor  from South-west Ontario
If everything else fails read the documentation. There can be no synchronization
issue of the kind described above on the Oracle parallel server, simply because
it's one database. The change is either in the database or it is not. If there
were some problems with locking and/or transaction control mechanism, one would
have had more serious things to wory about than "refreshing"

      Rating: 3
take it
      easy  May 02, 2003
Reviewer:  P  from
      AL
Hey guys
Take it easy, I know tom is one of the very respected people in industry and I
also have utmost respect for him. I was just joking nothing serious...
About my rating well I thought response was not helpful to me so I said not
helpful what’s wrong in that.
Be cool…
Tom I will provide you with all our testing results but right now I am arguing
with my managers for using PLSQL packages and procedures. Let me get that done
because it’s the most imp things for us. They say if we use PL/SQL packages we
cannot reuse them in other EJB applications. Well I am going to provide some
examples (from asktom J)…lets see…I hope they see my point

          Followup:

i'm just very very curious how you could test this down to the millisecond ON
TWO MACHINES -- that is

machine 1 commits change to block X at 0.01
machine 2 opens a query that references block X at 0.02

how did you time that, what method was used.

      Rating: 5
openMosix and
      OPS  May 06, 2003
Reviewer:  P  from AL

hi tom
i am looking into migrating some of our stuff to linux and OPS and for
clustering we are thinking about openMosix
I found this link

http://howto.ipng.be/openMosix-HOWTO/a1266.htm
that might cause problem....any suggestions!!!
have a nice day!
P

          Followup:

Oracle9i RAC is supported and working dandy on Linux.  You would have to live
within the box of what RAC supports -- not just any clusterware will do.

      Rating: 5
ops and
      openMosix  May 06, 2003
Reviewer:  P
       from AL
tom
we are using redhat 7.2 and oracle 8.1.7 will that be a problem if we use
openMosix as said in the above thread(shared memory)
P

          Followup:

contact support -- they are the keepers of the "ok platforms and required patch
level" sort of information.

      Rating: 5
ops and
      linux  May 06, 2003
Reviewer:  P  from
      AL
Hi tom,
I called support but the only thing they told me is its (openMosix) not
supported and asked me to use oracle cluster manager.
But when I searched for OCM ... documentation says it’s only supported for 9i. I
specifically asked if it could be used for 8.1.7 and red hat linux 7.2 and he
said yes. I couldn’t find anything about using it for OPS(8.1.7) and not for
RAC(9i).
This is just a proof of concept project and so I don’t have any csi # for
support
May be you could get some more info...
We want to install 2 node OPS on Redhat linux 7.2 (open source downloaded from
redhat.com)…is it possible?
Thanks
P

          Followup:

Sorry -- 817 is fairly "old" and linux is fairly "new" and a moving target.

9iRAC is infinitely superior and very different from 8i OPS -- I would not
consider a proof of concept with 8i OPS as 8i OPS is so different in its
characteristics from 9i RAC that you would have an apples and toaster oven
comparision.

      Rating: 4
ops and
      linux  May 06, 2003
Reviewer:  P  from
      AL
so basically you are saying there is no way i could do this...right???!!!
i guess this is only with linux coz we have sun os and running our production
oracle ops there...

          Followup:

what I'm saying is

o you have to listen to what support says works (I personally am not sure with
817 on linux with ops)

o to do a proof of concept i would consider nothing other then 9iRAC - period.

      Rating:
      4
  May 07, 2003
Reviewer:
      A reader
Hey P!, what about your test :-)

      Rating: 4
OPS and
      RAC  October 14, 2003
Reviewer:  Mohan

I don't have enough experience on OPS. I want to know whether Real Application
Clusters(RAC) in 9i is same as OPS.

Mohan

          Followup:

It is better then OPS.

It is the next generation OPS.

It is an advancement over OPS to the degree that they renamed the product to
RAC.

See

http://download-west.oracle.com/docs/cd/B10501_01/rac.920/a96597/toc.htm
check out the concepts guide for RAC

      Rating: 4
concepts
      ...  October 19, 2003
Reviewer:  Shankar
       from Houston, TX
I was readin the concept guide for RAC.

http://download-west.oracle.com/docs/cd/B10501_01/rac.920/a96597/pslkgdtl.htm
<quote>By default, a resource is allocated for each data block that resides in
the cache of an instance<quote>.

What does a resource mean in this context?

          Followup:

http://download-west.oracle.com/docs/cd/B10501_01/rac.920/a96597/pslkgdtl.htm#22304
here, resource is in the "classic sense".  A "thing" is a "resource" -- it is
the level at which things are managed.

Like an open file is "a resource"
Like a printer is "a resource"

      Rating: 1
Isn't
      "max_commit_propagation_delay" relevant here ?  October 20,
      2003
Reviewer:  Hemant K Chitale  from Singapore

Surely, the OPS instance parameter
"max_commit_propagation_delay" is relevant.
It defaults to a platform-dependent value.
What it means is that transactions committed on
one node are not visible on the other node till
the delay period or the instances synchronise their
SCNs, whichever is earlier.
Setting max_commit_propagation_delay=0 is an option
but, supposedly, has a performance impact.

          Followup:

all i wanted to know what "how did they measure this down to the millisecond" --
i questioned their testing procedure.  I like to find out what the issue is
before prescribing a "solution".

      Rating: 5
Resource
      ..  October 20, 2003
Reviewer:  Reader

Tom, my understanding is that here the resource is a block in RAC? If so, to
maintain the resource, does oracle need memory in the sga? If so, how much
memory per block? Is it like a list of blocks that are currently available in
the RAC instances? Thanks.

          Followup:

the resources are distributed across the nodes in the cluster (only one node has
the resource for a block).  they are allocated from the shared pool using a
dynamically sizable array structure.  you can monitor their usage in
gv$resource_limit.

it'll depend on various factors as to "how much per block" but it looks small,
just a couple of bytes (bit flags)

      Rating:
      4
  October 25, 2003
Reviewer:  Irfan
       from Kuwait
Hi Tom,

We are currently using OPS 734, writing to one database in raid through three
unix machines each having seperate instances. Our application consultant has
suggested to eliminate OPS and the application will be designed to write from
one machine (since most of the data is written from application from one machine
only) to gain some performance. Is it really a good idea to remove and work
directly with oracle database instead of OPS?

          Followup:

are you experiencing any performance issues as it is?

you do understand that 734 is like really really really old.

      Rating:
      4
  October 25, 2003
Reviewer:  Irfan
       from Kuwait
Hi Tom,

We are really in the process of changing our application, servers and upgrading
database (to the latest release of Oracle). Till that time, it is decided that
eliminating OPS will give performance since DLM is installed in all the
machines. Any suggestions? Thanks.

          Followup:

ummm, so going from 3 down to one machine will increase performance?

Rather then invoking mysticism, hypothesis, and conjecture -- have the
consultant earn their fee and prove it scientifically.  Give me some real
numbers to deal with from your system.

Since you are upgrading to 9iR2 -- that'll be RAC and the architecture is quite
different.  You'll be pleased.  And unless the consultant has done a couple of
RAC implementations personally -- ignore them, they are not knowledgable, your
money would be better spent on someone who does.

October 26, 2003 Jeni -- Thanks for the question regarding "Date
      range per week", version 8.1.7

      You Asked
Tom,

Is there a way by using SQL Plus to get a date range that would show the number
of week as well?

For example:
   January 2003
 S  M Tu  W Th  F  S
          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

I would like to show 1/1/03 - 1/4/03 as "week 1" of Jan. 03
1/5/03 - 1/11/03 as "week 2" of Jan. 03
1/12/03 - 1/18/03 as "week 3" Jan. 03 etc..

Is there a way I can do this?

Thanks for your help!

      and we said...
I think IW with a slight "shift" will do it:

try: to_char( the_date+1, 'iw' )

eg:

ops$tkyte@ORA920> select dt, to_char( dt+1, 'iw' )
  2    from ( select to_date('20-dec-2002')+rownum dt
  3             from all_objects
  4                    where rownum < 45 );

DT        TO
--------- --
21-DEC-02 51
22-DEC-02 52
23-DEC-02 52
24-DEC-02 52
25-DEC-02 52
26-DEC-02 52
27-DEC-02 52
28-DEC-02 52
29-DEC-02 01
30-DEC-02 01
31-DEC-02 01
01-JAN-03 01
02-JAN-03 01
03-JAN-03 01
04-JAN-03 01
05-JAN-03 02
06-JAN-03 02
07-JAN-03 02
08-JAN-03 02
09-JAN-03 02
10-JAN-03 02
11-JAN-03 02
12-JAN-03 03
13-JAN-03 03
14-JAN-03 03
15-JAN-03 03
16-JAN-03 03
17-JAN-03 03
18-JAN-03 03
19-JAN-03 04
20-JAN-03 04
21-JAN-03 04
...
Review
      & Followup

      Rating:
      5
Excellent...............  January 24, 2003

Reviewer:  Sikandar Hayat Awan  from Pindi Bhattian -
      Pakistan
I was searching for the same option and you gave the solution. Thanks

Secondly I have purchased your book and now I am reading it, and found it to be
very informative.

      Rating: 3
Can you explain
      your query...  January 24, 2003
Reviewer:
      Jeni  from Charlotte, NC
Tom,

I have a few more question from your query.

1. What is "IW"?
2. What do you mean by slight "shift"?

          Followup:

IW = iso week = documented date format

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/sql_elements4a.htm#48635
by shift -- I mean, add a day.  the ISO week ends on Sunday, you wanted to end
on Saturday.  So, we shift the date by a day

      Rating:
      5
Wonderful  March 28, 2003
Reviewer:
      Pichaimani Bala  from Santa Clara, CA USA
Ask Tom is my ready reckoner

      Rating: 5
How to convert
      week # back to a date?  August 20, 2003
Reviewer:
      A reader
How to get the the date of first day of the week for a given week #?  For
example, the beginning day for week# 34 in 2003 is AUG-18-2003, how do I find
the beginning date for the same week # 34 in 2002?  'iw' can't be used in the
to_date function.

Thanks!

          Followup:

well, we can use the fact that at least from 1800 to 2199, jan 4th is in the
first week... this:

ops$tkyte@ORA920> select *
  2    from (
  3  select dt1, to_char( dt1, 'yyyy' ),
  4         to_char( dt4, 'iw' ),
  5         decode( to_char( dt4, 'iw' ), '01', null, '******' ) xxx
  6    from (
  7  select add_months( to_date( '01-jan-1800', 'dd-mon-yyyy'), (rownum-1)*12 )
dt1,
  8         add_months( to_date( '02-jan-1800', 'dd-mon-yyyy'), (rownum-1)*12 )
dt2,
  9         add_months( to_date( '03-jan-1800', 'dd-mon-yyyy'), (rownum-1)*12 )
dt3,
 10         add_months( to_date( '04-jan-1800', 'dd-mon-yyyy'), (rownum-1)*12 )
dt4,
 11         add_months( to_date( '05-jan-1800', 'dd-mon-yyyy'), (rownum-1)*12 )
dt5,
 12         add_months( to_date( '06-jan-1800', 'dd-mon-yyyy'), (rownum-1)*12 )
dt6,
 13         add_months( to_date( '07-jan-1800', 'dd-mon-yyyy'), (rownum-1)*12 )
dt7
 14    from all_objects
 15   where rownum <= 400
 16         )
 17         )
 18   where xxx is not null
 19  /

no rows selected

shows that.

Then, we can use the fact that next_day(dt-7,'mon') returns the monday of that
week that DT falls in.

sooo, it would seem that if we

take jan-4th of the year of interest.

we add to that the (week-2)* 7 days  (-1 becuase we start at week one, -1 again
because we back off 7 days) .

and then ask for the next monday -- we get it:

ops$tkyte@ORA920> variable year varchar2(4)
ops$tkyte@ORA920> variable week number
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec :year := '2003'; :week := 34

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select next_day( to_date( '04-jan-' || :year, 'dd-mon-yyyy' )
+ (:week-2)*7, 'mon' )
  2    from dual
  3  /

NEXT_DAY(TO_DATE('04
--------------------
18-aug-2003 00:00:00

this query generates all of the "first days of the week" by IW week for a year:

ops$tkyte@ORA920> select year, week,
  2         next_day( to_date( '04-jan-' || year, 'dd-mon-yyyy' ) + (week-2)*7,
'mon' )
  3    from (select '2003' year, rownum week from all_objects where rownum <= 53
)
  4  /

YEAR       WEEK NEXT_DAY(TO_DATE('04
---- ---------- --------------------
2003          1 30-dec-2002 00:00:00
2003          2 06-jan-2003 00:00:00
2003          3 13-jan-2003 00:00:00
2003          4 20-jan-2003 00:00:00
2003          5 27-jan-2003 00:00:00
2003          6 03-feb-2003 00:00:00
2003          7 10-feb-2003 00:00:00
2003          8 17-feb-2003 00:00:00
2003          9 24-feb-2003 00:00:00
2003         10 03-mar-2003 00:00:00
2003         11 10-mar-2003 00:00:00
2003         12 17-mar-2003 00:00:00
2003         13 24-mar-2003 00:00:00
2003         14 31-mar-2003 00:00:00
2003         15 07-apr-2003 00:00:00
2003         16 14-apr-2003 00:00:00
2003         17 21-apr-2003 00:00:00
2003         18 28-apr-2003 00:00:00
2003         19 05-may-2003 00:00:00
2003         20 12-may-2003 00:00:00
2003         21 19-may-2003 00:00:00
2003         22 26-may-2003 00:00:00
2003         23 02-jun-2003 00:00:00
2003         24 09-jun-2003 00:00:00
2003         25 16-jun-2003 00:00:00
2003         26 23-jun-2003 00:00:00
2003         27 30-jun-2003 00:00:00
2003         28 07-jul-2003 00:00:00
2003         29 14-jul-2003 00:00:00
2003         30 21-jul-2003 00:00:00
2003         31 28-jul-2003 00:00:00
2003         32 04-aug-2003 00:00:00
2003         33 11-aug-2003 00:00:00
2003         34 18-aug-2003 00:00:00
2003         35 25-aug-2003 00:00:00
2003         36 01-sep-2003 00:00:00
2003         37 08-sep-2003 00:00:00
2003         38 15-sep-2003 00:00:00
2003         39 22-sep-2003 00:00:00
2003         40 29-sep-2003 00:00:00
2003         41 06-oct-2003 00:00:00
2003         42 13-oct-2003 00:00:00
2003         43 20-oct-2003 00:00:00
2003         44 27-oct-2003 00:00:00
2003         45 03-nov-2003 00:00:00
2003         46 10-nov-2003 00:00:00
2003         47 17-nov-2003 00:00:00
2003         48 24-nov-2003 00:00:00
2003         49 01-dec-2003 00:00:00
2003         50 08-dec-2003 00:00:00
2003         51 15-dec-2003 00:00:00
2003         52 22-dec-2003 00:00:00
2003         53 29-dec-2003 00:00:00

53 rows selected.

please -- check out the boundary value conditions!!! (eg: test this, date things
can be tricky)

      Rating: 5
It
      works!  August 22, 2003
Reviewer:
      A reader
Thanks Tom!

You say "...check out the boundary value conditions!!! (eg: test this, date
things can be tricky)"

Are saying check for year 1800 and 2199 or did you mean something else?

          Followup:

first day/last day or months -- leap years anything on boundaries.

I'm pretty sure the logic is "sound", you need to verify it (and understand it)

      Rating: 3
How can I get
      the month via iso week?  September 05, 2003

Reviewer:  David Pujol  from Barcelona, Catalonia

I'm building a time table. I'm computing the weeks with standard iso ('IW') and
weeks with 'WW'. For example:

/SYSTEM@BBR.ORA9R2> select to_char(to_date('30-09-2003', 'dd-mm-yyyy'), 'WW')
from dual;

TO
--
39

/SYSTEM@BBR.ORA9R2> select to_char(to_date('30-09-2003', 'dd-mm-yyyy'), 'IW')
from dual;

TO
--
40

When I analyze the data group by week I can choose with Iso or normal option,
but when I analyze the same data group by month (for example), it can be
possible that month be 9 or 10 (depends of iso week). How can I compute month,
quarter, .. from standard week iso?. I know that there is iso year but I don't
find a iso for month, quarter, .. for example.

example of my table:
numweek,
numweekiso,
nummonth,
nummonthiso,
namemonth,
namemonthiso,
numtri,
numtriiso,
numquarter,
numquarteriso ......

          Followup:

you cannot -- you already said "it might be month 9 or 10".  weeks span months,
they span qtrs.

there isn't an "iso" month.  there are just months 1..12

same with qtr -- qtrs 1..4

perhaps you are just looking for "mm" and "q" formats?

      Rating:
      5
EXCELLENT!!!!!!!!!  September 05, 2003

Reviewer:  Reader

      Rating: 3
How can I
      implement this "iso month" from iso week?  September 07,
      2003
Reviewer:  David Pujol  from Catalonia,
      Barcelona
I'm computing months and quarters. Let me show you my problem with one example:

I'm grounping by "iso" week and there is one date (31-12-2003, for example) that
iso week is 01, so if iso week is 1, then I need that month be 1, quarter be 1
and year (iso year) be 2004.
I have too, another version for this date (normal version) that group by
standart week (week 53, month 12, quarter 3 and year 2003)

<ORA817@SYSTEM> select to_char(to_date('31-12-2003', 'dd-mm-yyyy'), 'IW') from
dual;

TO
--
01

<ORA817@SYSTEM>  select to_char(to_date('31-12-2003', 'dd-mm-yyyy'), 'ww') from
dual;

TO
--
53

I don't know if you understand me (sorry for my english) but I need that month,
quarter and year be consistent with week (iso and standart) result, so, if week
is 01, I need that month became 1 and ...,

I have a customer that wants analyze his data with iso week, and he wants that
when he groups by month, the result be coherent with the iso Week. In my
example, the 31-12-2003 will not be computed in 2003 year if we've grouped by
iso week and then we've grouped by month.

How can I implement this "iso month" from iso week??

A lot of thanks!!!!

          Followup:

guess you can use decode and say "hey, if iso week = 1, then qtr = 1, month = 1
else qtr = to_char and month = to_char"

you know, just treat iso week = 1 differently using case or decode.

      Rating: 3
weeks within
      the month  September 08, 2003
Reviewer:
      Muhammad Ahmad Imran  from Faisalabad, Pakistan
Tom, your results are on the basis of year. How can I get the results on the
basis of month as 08-sep-2003 is the week 2.

          Followup:

not sure what you are asking for here.

      Rating: 2
Explaination
      the prev. comment  September 09, 2003
Reviewer:
      Muhammad Ahmad Imran  from pakistan
Tom Let's say I have the monthS as:

  January 2003             February 2003
 S  M Tu  W Th  F  S     S  M  T  W  T  F  S
          1  2  3  4                       1
 5  6  7  8  9 10 11     2  3  4  5  6  7  8
12 13 14 15 16 17 18     9 10 11 12 13 14 15
19 20 21 22 23 24 25    16 17 18 19 20 21 22
26 27 28 29 30 31       23 24 25 26 27 28

I am trying for a function which accept a date argument and returns a number
which show the week of the month.

Let I say 12/1/2003 seeing above calendar function should return 1 and so on...
e.g.

Date    Week of Month
3/1/03        1
4/1/03        1
5/1/03        2
21/1/03       4
1/2/03        1
28/2/03       5   and so on....

I have a database of a college and in which I have a table which have the
enquires about certain courses through out the month now i want to generate a
report which show in which days of any month(start,middle,last) there are the
most enquires.

I think now you understand my problem.

          Followup:

just a little math....

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create table t (x date );

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> insert into t select to_date('01-jan-2003')+rownum-1
  2   from all_objects
  3  where rownum <= 60;

60 rows created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select x,
  2         next_day(trunc(x,'mm')-8, 'sun'),
  3         trunc( (x-next_day(trunc(x,'mm')-8, 'sun'))/7 )+1 "week"
  4   from t;

X         NEXT_DAY(       week
--------- --------- ----------
01-JAN-03 29-DEC-02          1
02-JAN-03 29-DEC-02          1
03-JAN-03 29-DEC-02          1
04-JAN-03 29-DEC-02          1
05-JAN-03 29-DEC-02          2
06-JAN-03 29-DEC-02          2
07-JAN-03 29-DEC-02          2
08-JAN-03 29-DEC-02          2
09-JAN-03 29-DEC-02          2
10-JAN-03 29-DEC-02          2
11-JAN-03 29-DEC-02          2
12-JAN-03 29-DEC-02          3
13-JAN-03 29-DEC-02          3
14-JAN-03 29-DEC-02          3
15-JAN-03 29-DEC-02          3
16-JAN-03 29-DEC-02          3
17-JAN-03 29-DEC-02          3
18-JAN-03 29-DEC-02          3
19-JAN-03 29-DEC-02          4
20-JAN-03 29-DEC-02          4
21-JAN-03 29-DEC-02          4
22-JAN-03 29-DEC-02          4
23-JAN-03 29-DEC-02          4
24-JAN-03 29-DEC-02          4
25-JAN-03 29-DEC-02          4
26-JAN-03 29-DEC-02          5
27-JAN-03 29-DEC-02          5
28-JAN-03 29-DEC-02          5
29-JAN-03 29-DEC-02          5
30-JAN-03 29-DEC-02          5
31-JAN-03 29-DEC-02          5
01-FEB-03 26-JAN-03          1
02-FEB-03 26-JAN-03          2
03-FEB-03 26-JAN-03          2
04-FEB-03 26-JAN-03          2
05-FEB-03 26-JAN-03          2
06-FEB-03 26-JAN-03          2
07-FEB-03 26-JAN-03          2
08-FEB-03 26-JAN-03          2
09-FEB-03 26-JAN-03          3
10-FEB-03 26-JAN-03          3
11-FEB-03 26-JAN-03          3
12-FEB-03 26-JAN-03          3
13-FEB-03 26-JAN-03          3
14-FEB-03 26-JAN-03          3
15-FEB-03 26-JAN-03          3
16-FEB-03 26-JAN-03          4
17-FEB-03 26-JAN-03          4
18-FEB-03 26-JAN-03          4
19-FEB-03 26-JAN-03          4
20-FEB-03 26-JAN-03          4
21-FEB-03 26-JAN-03          4
22-FEB-03 26-JAN-03          4
23-FEB-03 26-JAN-03          5
24-FEB-03 26-JAN-03          5
25-FEB-03 26-JAN-03          5
26-FEB-03 26-JAN-03          5
27-FEB-03 26-JAN-03          5
28-FEB-03 26-JAN-03          5
01-MAR-03 23-FEB-03          1

60 rows selected.

      Rating: 4
week of
      month  October 25, 2003
Reviewer:  Howard
       from Canada
Hi Tom,
when I tried the code below (got your formula)

select x,
           next_day(trunc(x,'mm')-8, 'sun'),
           trunc( (x-next_day(trunc(x,'mm')-8, 'sun'))/7 )+1 "week"
     from
(select to_date('01-jan-2003')+rownum-1 x
     from all_objects
    where rownum <= 260
)

I found that the month of Jun, 2003 does not have week 1.

X         NEXT_DAY(       week
--------- --------- ----------
........
........
24-MAY-03 27-APR-03          4
25-MAY-03 27-APR-03          5
26-MAY-03 27-APR-03          5
27-MAY-03 27-APR-03          5
28-MAY-03 27-APR-03          5
29-MAY-03 27-APR-03          5
30-MAY-03 27-APR-03          5
31-MAY-03 27-APR-03          5
01-JUN-03 25-MAY-03          2  <<<<-------- should be 1
02-JUN-03 25-MAY-03          2
03-JUN-03 25-MAY-03          2

seems there is a bug in the formula.

          Followup:

yup, you'll need to add a decode in there to check for months that actually
begin on sundays (boundary value condition i warned about looking for :)

select x,
       next_day(trunc(x,'mm')-8, 'sun'),
       decode( trunc(x), trunc(x,'mm'), 1, trunc( (x-next_day(trunc(x,'mm')-8,
'sun'))/7 )+1 ) "week",
       decode( decode( trunc(x), trunc(x,'mm'), 1, trunc(
(x-next_day(trunc(x,'mm')-8, 'sun'))/7 )+1 ),
               trunc( (x-next_day(trunc(x,'mm')-8, 'sun'))/7 )+1, null, '<<<<<'
)
  from (select to_date('01-jan-2003')+rownum-1 x
          from all_objects
         where rownum <= 400
)
/

should do it -- it flags the two rows (jun1 2003 and feb1 2004) in that result
set that would be affected.  we'll just return 1 on the first day of the month,
do the math otherwise.

      Rating: 5
week of
      month  October 26, 2003
Reviewer:  Howard
       from Canada
Tom, you are simply the best!!!!!!!

thanks.

      Rating: 5
week of
      month  October 27, 2003
Reviewer:
      A reader
Hi Tom,
Sorry keeps bugging you at this topic.....

select x,
       next_day(trunc(x,'mm')-8, 'sun'),
       decode( trunc(x), trunc(x,'mm'), 1, trunc( (x-next_day(trunc(x,'mm')-8,
'sun'))/7 )+1 ) "week",
       decode( decode( trunc(x), trunc(x,'mm'), 1, trunc(
(x-next_day(trunc(x,'mm')-8, 'sun'))/7 )+1 ),
               trunc( (x-next_day(trunc(x,'mm')-8, 'sun'))/7 )+1, null, '<<<<<'
)
  from (select to_date('01-jan-2003')+rownum-1 x
          from all_objects
         where rownum <= 400
)
/

X         NEXT_DAY(       week DECOD
--------- --------- ---------- -----
30-MAY-03 27-APR-03          5
31-MAY-03 27-APR-03          5
01-JUN-03 25-MAY-03          1 <<<<<
02-JUN-03 25-MAY-03          2 <<<<------- should be 1
03-JUN-03 25-MAY-03          2 <<<<------- should be 1
04-JUN-03 25-MAY-03          2 <<<<------- should be 1
05-JUN-03 25-MAY-03          2 <<<<------- should be 1
06-JUN-03 25-MAY-03          2 <<<<------- should be 1
07-JUN-03 25-MAY-03          2 <<<<------- should be 1
08-JUN-03 25-MAY-03          3 <<<<------- should be 2
09-JUN-03 25-MAY-03          3 <<<<------- should be 2
10-JUN-03 25-MAY-03          3 <<<<------- should be 2
11-JUN-03 25-MAY-03          3 <<<<------- should be 2
12-JUN-03 25-MAY-03          3 <<<<------- should be 2
13-JUN-03 25-MAY-03          3 <<<<------- should be 2
14-JUN-03 25-MAY-03          3 <<<<------- should be 2
15-JUN-03 25-MAY-03          4 <<<<------- should be 3
16-JUN-03 25-MAY-03          4 <<<<------- should be 3
17-JUN-03 25-MAY-03          4 <<<<------- should be 3
18-JUN-03 25-MAY-03          4 <<<<------- should be 3
19-JUN-03 25-MAY-03          4 <<<<------- should be 3
20-JUN-03 25-MAY-03          4 <<<<------- should be 3
21-JUN-03 25-MAY-03          4 <<<<------- should be 3
22-JUN-03 25-MAY-03          5 <<<<------- should be 4
23-JUN-03 25-MAY-03          5 <<<<------- should be 4
24-JUN-03 25-MAY-03          5 <<<<------- should be 4
25-JUN-03 25-MAY-03          5 <<<<------- should be 4
26-JUN-03 25-MAY-03          5 <<<<------- should be 4
27-JUN-03 25-MAY-03          5 <<<<------- should be 4
28-JUN-03 25-MAY-03          5 <<<<------- should be 4
29-JUN-03 25-MAY-03          6 <<<<------- should be 5
30-JUN-03 25-MAY-03          6 <<<<------- should be 5
01-JUL-03 29-JUN-03          1
02-JUL-03 29-JUN-03          1
03-JUL-03 29-JUN-03          1
04-JUL-03 29-JUN-03          1
05-JUL-03 29-JUN-03          1
06-JUL-03 29-JUN-03          2

I tried the query below and seems give the expected results........

select x dt,
(to_char( x+1, 'iw') -
to_char(to_date('01/'||to_char(x,'mon/')||to_char(x,'yyyy'),'dd/mon/yyyy') +
1,'iw') + 1)
 Week_of_month
from
(select to_date('01-jan-2003','dd/mon/yyyy')+rownum-1 x
from all_objects
where rownum <= 400
)
/

Thanks.

October 26, 2003 Brett -- Thanks for the question regarding
      "Analyzing tables automatically", version 9.2.0.4

      You Asked
Hi Tom,

Is there a way to analyze all tables in a schema on a periodic basis?  For
instance, I would like to analyze tables every night at specific time.  That
would save me from manually performing this task periodically.

Thanks,

Brett

      and we said...
dbms_job is the tool you want.  say you wanted to do this every night at 3am

declare
   l_job number;
begin
   dbms_job.submit( l_job,
                    'dbms_stats.gather_schema_stats( ''SCOTT'', ..... );',
                    trunc(sysdate)+1+3/24,
                    'trunc(sysdate)+1+3/24' );
end;
/

will schedule a job to start running tonight at 3am and evernight there after.

make sure your job_queue_processes init.ora setting is at least 1 or more.
Review &
      Followup

      Rating: 4
When to use OEM
      vs. DBMS_JOB  October 25, 2003
Reviewer:
      Dan  from Yukon Territory, Canada
Brett, I don't know what your overall environment is like, but you might
consider OEM (Oracle Enterprise Manager).  OEM really shines when you have more
than a "few" databases to look after.  OEM provides you with it's own job
system, gives you a single console to go and check for failed jobs, make sure
all your databases are up, backups completed etc.  But like anything else it
does come at a bit of a price in terms of extra complexity etc.

I resisted OEM for a few years (Blagh! GIU evil!) but finally caved in around
database number 15 or so and saw the light.

But - this brings up an interesting issue... when to use DBMS_JOB and when to
use OEM's Job scheduling system.  I've finally come down to this:

* If it's something the DBA cares about (e.g. regular backups) - it's an OEM
job.

* If it's something the application cares about (e.g. Load all the data found in
a particular directory everynight) - it should be done with DBMS_JOB.  that way
it kind of "lives with" the system.

But I'm not really sure if those are the best guidelines.  There are a few
things that are clearly better (I think...) to do with OEM, initiating an OS
batch job on the server springs to mind. And things like update stats jobs fall
somewhere in the middle of my suggested guidelines!

So does anyone have any comments on this?  If I'm running OEM anyway what kinds
of jobs should be scheduled/managed with OEM, and what jobs should be
scheduled/managed with DBMS_JOB?

Any input would be appreciated.

          Followup:

wait until you see EM with 10g -- it is really cool.

October 26, 2003 partha -- Thanks for the question regarding
      "comparing differenece between two tables", version 8.1.6

      You Asked
i want's to see the diffrence between two tables
columnnames,datatype,datalength.

I have following sql statement, which doesn't give me appropriate result.

select table_name,column_name,data_type,data_length
from user_tab_columns a where
(a.column_name, a.data_type, a.data_length) not in
(select column_name, data_type, data_length from user_tab_columns b
where a.table_name = 'TEST');

could you please help me.

Thanks in advance.

Partha

      and we said...
scott@ORA817.US.ORACLE.COM> create table t1 as select * from emp where 1=0;

Table created.

scott@ORA817.US.ORACLE.COM> create table t2 as select * from t1;

Table created.

scott@ORA817.US.ORACLE.COM>
scott@ORA817.US.ORACLE.COM> alter table t2 drop column ename;

Table altered.

scott@ORA817.US.ORACLE.COM> alter table t2 modify job varchar2(10);

Table altered.

scott@ORA817.US.ORACLE.COM>
scott@ORA817.US.ORACLE.COM> column data_type format a10
scott@ORA817.US.ORACLE.COM> (
  2  select 'IN T1, NOT T2', column_name,data_type,data_length
  3    from user_tab_columns
  4   where table_name = 'T1'
  5  MINUS
  6  select 'IN T1, NOT T2', column_name,data_type,data_length
  7    from user_tab_columns
  8   where table_name = 'T2'
  9  )
 10  UNION ALL
 11  (
 12  select 'IN T2, NOT T1', column_name,data_type,data_length
 13    from user_tab_columns
 14   where table_name = 'T2'
 15  MINUS
 16  select 'IN T2, NOT T1', column_name,data_type,data_length
 17    from user_tab_columns
 18   where table_name = 'T1'
 19  )
 20  /

'INT1,NOTT2'  COLUMN_NAME                    DATA_TYPE  DATA_LENGTH
------------- ------------------------------ ---------- -----------
IN T1, NOT T2 ENAME                          VARCHAR2            10
IN T1, NOT T2 JOB                            VARCHAR2             9
IN T2, NOT T1 JOB                            VARCHAR2            10

scott@ORA817.US.ORACLE.COM>
Review &
      Followup

      Rating: 5
Comparing Two
      Tables columnnames,datatype,datalength  July 18, 2001

Reviewer:  Partha  from USA
Really it is very useful for everybody.

Thanks.

      Rating: 5
Comparing data
      in two tables  November 09, 2001
Reviewer:
      Sam
Tom, your example is great and is really usefull especially when working in dev
environment with multiple versions of a table.

But it would really be useful , if you show us how to compare the data in
two-three  tables. Suppose lets say the structure of two tables is similar, then
how can we decide if the data in the two tables is different or similar or to
what extent it is different.

Is it possible to compare data in two tables when the structure of the tables
are different.

The answer to these questions , in my opinion would make this thread more
valuable.

          Followup:

If you want to compare the DATA in the tables -- use the same exact concept --
just for the data.

You of course can only compare apples to apples and oranges to oranges.  If the
table structure is radically different -- you'll have to determine WHAT and HOW
to compare the columns.

Here is a quick example:

ops$tkyte@ORA717DEV.US.ORACLE.COM> create table emp as
  2  select ename, empno, e.deptno, d.dname
  3    from scott.emp e, scott.dept d
  4   where e.deptno = d.deptno
  5  /

Table created.

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> REM compare our EMP to their DEPT
ops$tkyte@ORA717DEV.US.ORACLE.COM> REM we obviously cannot compare LOC, we don't
have it!
ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> select *
  2    from (
  3  select deptno, dname, 'In EMP, not in DEPT'
  4    from emp
  5   minus
  6  select deptno, dname, 'In EMP, not in DEPT'
  7    from scott.dept
  8  )
  9  UNION ALL
 10  select *
 11    from (
 12  select deptno, dname, 'In DEPT, not in EMP'
 13    from scott.dept
 14   minus
 15  select deptno, dname, 'In DEPT, not in EMP'
 16    from emp
 17  )
 18  /

    DEPTNO DNAME          'INEMP,NOTINDEPT'
---------- -------------- -------------------
        40 OPERATIONS     In DEPT, not in EMP

Now -- bear in mind the following:

o EMP has deptno 10 many times, DEPT only has it once -- this sort of difference
is NOT reported

o we "lost" deptno 40 when we joined - since its not in EMP, it does show up.

      Rating: 2
Comparison of
      Denormalised Tables  November 05, 2002
Reviewer:
      Michelle  from York, England
We have a denormalised database.  The requirement is to freeze data at certain
points and then compare the data sets to see whats changed.

This sounds easy enough at parent level as a minus one way states the additions
and the minus the other states the deletions.

The problem is however on changes in the child records, because of the joins a
change in a child record can result in x rows being created and reverse when the
child is being deleted.  I have got to the part of identifying these increased
or decreased child records but need to find a clever way of identifying why the
child record has been deleted or added.  Each column has a flag changed column
to determine if the column is ammended, added or deleted.

so eg. the below shows that in the new table C2 has been added..... using a
minus I can pick out the C2 records but the only change which should be marked
is the flag of C2 should read 'New' , as all the other details eg 1 A B C W..W
are unchanged...but how can I single out the change was C2?? bearing in mind the
change could be any of the columns of A B C W..W and C..C  The parent is 1.

Any ideas??

OLD

1    A    B    C    W1    C1
1    A    B    C    W2    C1
1    A    B    C    W3    C1

NEW
1    A    B    C    W1    C1
1    A    B    C    W2    C1
1    A    B    C    W3    C1
1    A    B    C    W1    C2
1    A    B    C    W2    C2
1    A    B    C    W3    C2

Hope I have explained it semi-okay!??
Your help would be appreciated..!

      Rating: 4
What about
      performance  March 20, 2003
Reviewer:  David
       from AL
Morning, Tom.

Is this the approach you would take if the two tables had a couple of million
rows and you needed to compare ALL columns (and there are 70-80 columns)?

I'm trying to update a few million rows, and because UPDATE is relatively slow,
I'm going to:

create table as select <do update here> * (see note below)
then
delete from original table
then
insert 'updated' rows into original table.

I know that this is a viable approach when UPDATE simply takes too long, but I
want to have reasonable assurances that I have all the rows in my staging table
that I'll be deleting from the original table.  I thought I'd try the MINUS
operator, but on that many rows and that many columns, it seems logical that
MINUS might take a good deal of time.  If this turns out to be the case, what
might your approach be to meet this requirement?

As always, thanks.  Your service is invaluable to me.

* Note:  If I may sqeeze in a related question, I've researched the problem of
the 'do update here' approach (using SQL to do transformations because UPDATE
takes so long).  There's a good deal of discussion on this topic in your
archives.  You advocate INSERT or CREATE TABLE AS SELECT rather than array
processing or bulk collects, etc. and as usual, my experience confirms your
method to be best.  But I'd like to have the ability to commit batches into this
staging table and as yet I haven't figured out a way to do that from a simple
CREATE TABLE AS SELECT statement.  Storage becomes a problem in my environment,
and I'd like to have 'restartability' if the CREATE TABLE doesn't complete.
Your thoughts?

          Followup:

No, for millions of rows this would take a while.

why wouldn't you just:

create table as select <do the update, to EVERY row, whether it needed it or
not>
drop old table
rename new table to old table

anything that "commits" is going to consume even MORE storage??? confused by
that request.  CTAS in parallel with nologging will just read and write the data
ASAP
???

      Rating: 4
Why I don't
      think I can drop and rename  March 20, 2003

Reviewer:  David  from AL
It's a production table in a financial application (OFSA).  No way I could drop
the table.  It would take me weeks just to understand the effect of dropping a
table in the application.  It's taken me some time to convince everyone that
create..delete from...insert is the way to go.

So if you can't drop the table, and knowing that MINUS will take time, what
alternatives might we have?

          Followup:

everything will take time.  minus is very very brutally effective.

I wondering why you think you can update it if it is a table that is part of a
packaged application.  Have you verified that is SUPPORTED?

create
truncate
insert /*+ append */

would be better then delete.

      Rating: 4
Why commit
      batches  March 20, 2003
Reviewer:  David
       from AL
The create table as select sometimes doesn't complete because of tablespace
limitations (exceeds maxextents - forgive me, I'm not the DBA.)  When it fails,
the whole operation rolls back and I might have run for an hour and got nothing
accomplished.  I have to get the DBA to create a bigger tablespace and I alter
the user to use that tablespace, run the create table and alter the user back to
the default tablespace.

If I could commit batches, it seems to me, I might get some batches processed
before I run out of space.  Or since the batches would be smaller, maybe I
wouldn't run out of space.  At any rate, I could restart and only have to
'update' the rows that didn't get commited in the previous run.

          Followup:

umm, i would just size it right personally.

orders of magnitude faster.

you should be able to size this by running the CTAS with "and rownum < 10000"
added to it, analyze the table to see its size -- and multiply out to the full
number of expected rows.

      Rating: 4
Based on tables
      data difference how can we populate an insert, update,delete such
      statements  March 20, 2003
Reviewer:  kumar
       from San Diego
Tom,

I would like to extend this question, and if I have table A and tables B of
similar structures ( no matter on same and different databases). Is there any
easy way to populate those data difference scripts like inserts, updates,
deletes if any. Well this should generate only taking as one the table table A
as baseline  and generating against table B.

Thanks
Kumar

          Followup:

You have just described in some detail the concepts behind a SNAPSHOT (aka
materialized view)

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76959/mview.htm#29854

      Rating: 5
Audit
      Trail  March 20, 2003
Reviewer:  A Reader
       from UK
Hi,

Based on an earlier discussion, we've implemented a 'history' functionality
against an application that we can't touch (i.e. no triggers etc.) as follows:

each day, take a copy of the tables we want to track -
compare this to the copy you took yesterday using the minus approach described
above.

We then have some dynamic sql that goes through the results and figures out the
individual changes so we get:

change_date_time change_by change_column change_old_value change_new_value

entries for each update.  We don't want deletes or inserts (though we could get
them I suspect quite easily)

This runs REALLY quickly (like sub-second for the whole thing) across tables we
have with 26000+ rows and is quite neat - the users like it anyway.

The whole thing is pretty much configured from tables so we don't have lots of
code to write to get a new table included in the history runs.

If anyone wants any more info - let me know via here.

Regards,

Paul

      Rating:
      4
Why?  April 01, 2003
Reviewer:
      A reader
Hi Tom, your response is very good... but i have a doubt because i don't know
what's the difference between not in and minus...
supose a have 2 tables
t1 (code varchar2(5));
t2 (code varchar2(5));

i made this 2 select :

SQL> select code
  2    from t1
  3   where code not in (select distinct code
  4                        from t2);

no rows selected

SQL> select code
  2    from t1
  3  minus
  4  select distinct code
  5  from t2;

CODE
-----
00001
00002
00003
00005
00006
00007
00009
00010
.
.
.
.

can you explain me why?
Thanks

          Followup:

caveat #1

beware the NULL:

ops$tkyte@ORA920> select * from dual where 1 not in ( 2, null );
no rows selected

code in t2 must have some nulls (the distinct is redundant, not necessary
btw)...  it is not KNOWN if code is not in a set that contains nulls.

caveat #2

beware the sort distinct set operations might incurr...

ops$tkyte@ORA920> select * from ( select * from dual union all select * from
dual );

D
-
X
X

ops$tkyte@ORA920> select * from ( select * from dual union all select * from
dual )
  2  MINUS
  3  select 'Y' from dual;

D
-
X

ops$tkyte@ORA920>

a minus b is:

distinct ( a minus b )

      Rating: 5
get the extra
      records between two tables  April 22, 2003

Reviewer:  Zo A.  from Madagascar
Hi Tom,

Tell me, what is wrong with my syntax here, I would like to get all fields if an
extra records exist.

SQLWKS> select count(*) from
     2> (select art_code, art_name from agcv_articles
     3> minus
     4> select art_code, art_name from agc_articles)
     5>
COUNT(*)
----------
      2255
1 ligne sélectionnée
SQLWKS> select count(*) from
     2> (select art_code from agcv_articles
     3> minus
     4> select art_code from agc_articles)
     5>
COUNT(*)
----------
       156
1 ligne sélectionnée

My output should look like
111 kjqshds
101 jkljkl
103 jshk
...
102 kjkl

156 records found

          Followup:

there is nothing wrong with your syntax?

you'll have to tell me more about what you mean by "I would like to get all
fields if an
extra records exist."

      Rating: 5
More
      Details  April 22, 2003
Reviewer:  Zo A.
       from Madagascar
What I want is to get the extra records from the view AGCV_ARTICLES which do not
exist in the table AGC_ARTICLES. So here I should have 156 extra records. But my
problem is if I add more than one field in (select art_code, ART_NAME ...) I get
2255 records instead of 156 records !!! Why ?

SQLWKS> select count(*) from
     2> (select art_code, art_name from agcv_articles
     3> minus
     4> select art_code, art_name from agc_articles)
     5>
COUNT(*)
----------
      2255
1 ligne sélectionnée
SQLWKS> select count(*) from
     2> (select art_code from agcv_articles
     3> minus
     4> select art_code from agc_articles)
     5>
COUNT(*)
----------
       156
1 ligne sélectionnée

My output should look like
111 kjqshds
101 jkljkl
103 jshk
...
102 kjkl

Thanks for your quick reply.

Zo

          Followup:

Because A MINUS B is really:

sort( distinct(A) ) minus sort(distinct(B))

minus (intersect, union, ... ) inject a distinct step in there.

Also, if the data is:

ART_CODE      ART_NAME
---------     ---------
1             x
1             y

ART_CODE      ART_NAME
---------     ----------
1             a
1             b

then the two column would return 2 as the count (since (1,x), (1,y) MINUS (1,a),
(1,b) is (1,x), (1,y)

and the single column count would return 0 (since (1), (1) minus (1), (1) is the
null set)

you probably want

select * from t1 where (art_code) NOT IN ( select art_code from t2)

maybe?

      Rating:
      4
Thanks  April 23, 2003
Reviewer:  Zo
      A.  from Madagascar
Great Tom,

I've changed my sql code into :
select * from agcv_articles where (art_code) NOT IN ( select art_code from
agc_articles) as you suggest and it works fine. I thought with MINUS
instruction, I can get the same result !!!

Thanks

Zo

      Rating: 4
comparing same
      table and insert into the 3rd table with flag  June 20, 2003

Reviewer:  A reader
tom,

can you please help me with the following..

my requirment goes like this,

i get nightly load on some temp tables and after the load, i populate the table
"tb_today". tb_today have a composite primary key.

by next day i will get the next set of load, mostly with changes and very few
new inserts.
before my table tb_today gets populated  i need to rename the table to
"tb_yesterday" and create a new table with the same name tb_today and insert the
records from the load.

finally, i will have two tables, tb_today and tb_yesterday. - i wanted to know
how to compare the two tables and populate the third table "tb_use_me_everyday"
that will have only the values of new inserts and the newly updated records. the
new record should be with a flag say 'I' and old record with 'U'

Thank you very much

          Followup:

insert into tb_use_me_everyday
select 'U' flag, a.*
  from ( select * from tb_today MINUS select * from tb_yesterday );

update tb_use_me_everyday
set flag = 'I' where primary-key-cols not in ( select primary-key-cols from
tb_yesterday )

(make sure to be using the CBO and that the primary key cols are NOT NULL for
that second query)

that grabs everything in tb_today that isn't in tb_yesterday (updates and
inserts) and then marks the records that really didn't exist yesterday as
inserted.

that is one way.

      Rating: 5
Thanks again to
      Tom !!  June 22, 2003
Reviewer:
      A reader

      Rating: 5
better approach
      for handling this ..?  June 22, 2003
Reviewer:
      A reader
from the previous question,
by next day ...i need to rename the table to "tb_yesterday" and create a new
table with the same name tb_today and insert the records from the load.

tom, what would be your way of handling this situation, rename the table and
create a new table or any other way that would be best for the above

Thanks

          Followup:

it is "fine" to rename the table

      Rating: 5
Word of
      warning.....  June 23, 2003
Reviewer:  A
      Reader  from UK
Hi,

We are using a solution like this here.... you may hit issues if this thing runs
automatically each day and say over a weekend or some days, there was an issue
with the load and it isn't spotted, you can 'lose' history that way.
Effectively all issues have to be fixed same day or you get your 'yesterday'
table overwritten and lose the ability to produce deltas.

Regards,

Paul

      Rating: 4
thanks to paul
      for his warning  June 23, 2003
Reviewer:
      A reader
Tom,

what paul says makes sense, though i did remeber this but did not give much
importance, other than having a backup is there any other way we can handle it
in ease?

thanks Tom!

      Rating: 3
comparing
      differenece between two tables - 9i dbms_metadata  June 24,
      2003
Reviewer:  Jim Dickson  from London, UK

If i want to compare ddl of tables in 9i, is there a way to use dbms_metadata?

I can get the 2 ddls but set operator MINUS does not work
(because i believe dbms_metadata's output is XML).

OR is the best way in 9i still to select from user_tab_columns?

          Followup:

they are clobs, you can use dbms_lob.compare in sql maybe?  (but the
owner/schema will get in the way)

      Rating: 5
Is there a way
      to "diff" two clobs?  August 07, 2003
Reviewer:
      A reader
Hi Tom
The dbms_lob.compare() just tells whether two lobs
are different or not? Is there any functionality to
do a "diff" and tell what the differences are?
We need to find out the diffs between
different clobs stored in the database.

Thanx!

          Followup:

there is no "diff" in the lob support packages.

you could....

a) find a java routine out there that does it and load it in as a stored
procedure

b) write the lobs out using utl_file and use an OS command run by java to "diff"
the files

c) write your own "diff" (non-trivial task)

      Rating: 5
Comparing the
      contenst of two tables  October 25, 2003
Reviewer:
      Sean  from NJ, USA
Tom,

The script you provided to compare the contents of two tables is very useful for
us.  Very often I need to compare a lot of reference tables between Dev database
and Prod database.

We always use script to patch production database.  If someone forgot to write
an insert statement in patch script which he did in development work in Dev
database, our production database will not get this record during the patching
process.

Is it possible to write a stored procedure which takes table name, a temp table
name and database link as parameters, compares the contents of two tables (same
table, but in different databases) and populates the differences of the table
contents to the temp table?

Thanks so much for your help.

Regards,
Sean

          Followup:

anything is possible.  sure, you can do that.  you would just be using dynamic
sql is all.

October 26, 2003 Doug -- Thanks for the question regarding "undo
      retention question", version 9203

      You Asked
Tom - with auto rollback management things seem to be on a time limit.  If a
user works on a transaction and walks away from their desk, with the new rention
period knock out the rollback for their transaction with a different approach
than manual rollback management? In other words, under a heavy load where
everything starts to wrap around, will a user lose their rollback after the
setting for retention?

      and we said...
no, the retention is simply a request "please don't reuse this rollback before N
minutes"

we will never never reuse ACTIVE undo information in any circumstance.

AUM is manual undo with the ability to request us not to reuse it before N
minutes have past.  That is all -- it is not "significantly" different in
application from manual undo.  it is manual undo where you

o do not pick how many rbs's (we do)
o can tell us how long to try and keep INACTIVE undo for

Review &
      Followup

      Rating: 4
Ok - very
      clear  October 26, 2003
Reviewer:  Doug
       from CT, USA
Clears that up - thanks.

October 26, 2003 Marcell -- Thanks for the question regarding
      "Unable to drop user", version 8.1.7

      You Asked
Dear Tom,

I just simply can't drop one user in sqlplus.

SQL> drop user a;
drop user a
*
ERROR at line 1:
ORA-00604: error occurred at recursive SQL ldwvel 1
ORA-00942: table or view does not exist
ORA-06512: at line 7

I also checked there is no table or other objects created by a user.

Can you solve the problem for me? Thanks,

      and we said...
hmm, ldwvel?

hate cut and pastes that are not really cut and pastes.  makes me suspicious :)

Anyway, it appears you have a "DROP" trigger and on line 7, it is doing
something "bad", something that is not working.

ops$tkyte@ORA920> create or replace trigger drop_trigger
  2  before drop on database
  3  declare
  4          n       number;
  5  begin
  6          execute immediate 'select count(*) from not_a_table' into n;
  7  end;
  8  /

Trigger created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create user a identified by a;

User created.

ops$tkyte@ORA920> drop user a;
drop user a
*
ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 1
ORA-00942: table or view does not exist
ORA-06512: at line 4

ops$tkyte@ORA920> drop trigger drop_trigger;

Trigger dropped.

ops$tkyte@ORA920> drop user a;

User dropped.

So, look in DBA_TRIGGERS for a triggering_event = 'DROP' trigger and then fix
it.

October 26, 2003 Saibabu -- Thanks for the question regarding
      "chr() function.rst_", version 8.1.7.4

      You Asked
Hi Tom,

Can you help me understand char() function. Let us say:

select chr(1234567) from dual;

What number in that series Oracle picks to convert it to a character.

Thanks,
 Sai.

      and we said...
ops$tkyte@ORA920> select chr(1234567),
  2         dump( chr(1234567) ),
  3         chr( mod(1234567,256) ),
  4         dump( chr( mod(1234567,256) ) )
  5    from dual;

C DUMP(CHR(1234567 C DUMP(CHR(MOD(123
- ---------------- - ----------------
? Typ=1 Len=1: 135 ? Typ=1 Len=1: 135

CHR works by returning the ASCII value represented by the input.  ASCII codes
run 1..256 (or 1..128 depending).  So it mod's the input by 256 (as shown here)
to get a valid code.  That is, it only looks at the low byte to see what
character should be returned.

The exact behavior, including this modulo fact, is documented:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96540/functions18a.htm

Review &
      Followup

      Rating: 4
How to findout
      these ?  October 26, 2003
Reviewer:
      A reader
Hello Sir from the link it says this
The following example is run on an ASCII-based machine with the database
character set defined as WE8ISO8859P1:

SELECT CHR(67)||CHR(65)||CHR(84) "Dog" FROM DUAL;

Dog
---
CAT

To produce the same results on an EBCDIC-based machine with the WE8EBCDIC1047
character set, the preceding example would have to be modified as follows:

SELECT CHR(195)||CHR(193)||CHR(227) "Dog"
   FROM DUAL;

Dog
---
CAT

Question :1) How do we find out our machine is ASCII based or EBCDIC based.What
is the machine here ? is it where the DB is installed ?
          2) How to find the current DATABASE CHARACTER set for our DB.
          3) How do we generate the ASCII and EDCDIC table in sql/plsql. so that
chr(number) = Ascii and chr(number)=
EBCDIC
Thanx much

          Followup:

1) its going to be based on the character set of the database.

2) select * from nls_database_parameters;

3) huh?  don't know what you mean

      Rating: 5
Thanx a
      Lot  October 26, 2003
Reviewer:
      A reader
 3) How do we generate the ASCII and EBCDIC table in sql/plsql. so that
chr(number) = Ascii and chr(number)=
EBCDIC.

I meant how to generate the ascii tables or the EBCDIC table . Like we know that
chr(10) is carriage return ,chr(9) is tab. what is teh chr
 value for empty space,single space ..s o how to get the complete set.How do we
distinguish between tab and empty space , single white spaace as all the length
are just 1.

          Followup:

oh, you google for it.  they are "standards", nothing invented here.  there are
ascii tables out there that define what "9" means and ebcdic tables for the
same.

"ebcdic table"
"ascii table"

plug that into google and see what happens.

      Rating: 5
Thanx for your
      replies  October 26, 2003
Reviewer:
      A reader

October 27, 2003 Ramachandran -- Thanks for the question regarding
      "MV is not getting selected", version 8.1.7

      You Asked
Hi tom

Information As you required.

Hi Tom

Think, i have not furnished details properly. Sorry for that.

This is My FACT Table

Create table Sbib_Ownr.Sbib_dochdr
(Carnumcod    Varchar2(4)   Not null,
 Docnum        Number(10)    Not null,
 Saldat     Date          Not null,
 Actsaldat      Date          Not null,
 Issctycod    Varchar2(5)   Not Null,
 Cntcod        Varchar2(2)   Not null,
 Rtgtxt        Varchar2(200) Not null,
 Netamt        number(15,3)  Not null,
 Maindocnum    Number(10)    Not null,
 Refnum        number(10)    Not null,
 upddat        date          Not null,
 Srcind         Varchar2(1)   Not null,
 errind        varchar2(1)   not null,
 PRCMTH         DATE          NOT NULL,
 Issloccod    Number(7)             ,
 Jnytyp        Varchar2(2)           ,
 Farbas     Varchar2(20)          ,
 Orgcty        Varchar2(5)           ,
 Dstcty        Varchar2(5)           ,
 Retamt         number(15,3)          ,
 oalpayout    number(15,3)          ,
 Errmsg         Varchar2(200)         ,
 constraint pk_sbib_dochdr primary key(Carnumcod,docnum)
            using index tablespace Sbibind,
 constraint fk_sbib_dochdr_saldat foreign key(saldat)
                                  references sbib_time(saledate),
 constraint fk_sbib_dochdr_country foreign key(cntcod)
                                  references com_countries(cco_cnty_iso_cd)
)
Storage
(
 Initial 64K
 Next    64K
 Pctincrease 0
)
tablespace Sbibdat nologging
/
Create Bitmap index Bmi_Sbib_Dochdr_Saldat on Sbib_Ownr.Sbib_dochdr(saldat)
Tablespace Sbibind
/
Create Bitmap index Bmi_Sbib_Dochdr_cntcod on Sbib_Ownr.Sbib_dochdr(cntcod)
Tablespace Sbibind
/

Grant all on Sbib_Ownr.Sbib_dochdr to sbib_usr;

-- Coupon information  FACT
Prompt Creating table coupon detail live.....

Create table Sbib_Ownr.Sbib_cpndtl
(Carnumcod    Varchar2(4)   not null,
 Docnum        number(10)    not null,
 Cpnnum        number(2)     not null,
 OprCardsgcod    varchar2(3)   not null,
 frmarpcod    varchar2(5)   not null,
 frmctycod    varchar2(5)   not null,
 toarpcod    varchar2(5)   not null,
 toctycod    varchar2(5)   not null,
 Orgfrmctycod   Varchar2(5)   not null,
 Orgtoctycod    varchar2(5)   not null,
 Revsecind      varchar2(1)   Not null,
 netamt        number(15,3)  Not null,
 Refnum         number(10)    Not null,
 upddat         date          Not null,
 Cpnsta         Varchar2(1)   Not null,
 errind         varchar2(1)   not null,
 inboutind    varchar2(1)           ,
 Oprcarnumcod   varchar2(4)           ,
 Spacod        VARCHAR2(4)           ,
 rbdcod        varchar2(2)           ,
 Cotcod        varchar2(2)           ,
 grsamt        number(15,3)          ,
 dscamt        number(15,3)          ,
 comamt        number(15,3)          ,
 Retamt         number(15,3)          ,
 oalpayout    number(15,3)          ,
 Errmsg         Varchar2(200)         ,
 constraint pk_sbib_cpndtl primary key(Carnumcod,docnum,cpnnum)
            Using index tablespace Sbibind,
 constraint fk_sbib_cpndtl foreign key(carnumcod,docnum)
                           references sbib_dochdr(carnumcod,docnum),
 constraint fk_sbib_cpndtl_frmcty foreign key(orgfrmctycod)
                           references com_cities(cct_city_cd),
 constraint fk_sbib_cpndtl_tocty foreign key(orgtoctycod)
                           references com_cities(cct_city_cd)
)
Storage
(
 Initial 64K
 Next    64K
 Pctincrease 0
)
Tablespace Sbibdat nologging
/
Create Bitmap index Bmi_sbib_cpndtl_Carrcd on
Sbib_Ownr.sbib_cpndtl(oprcardsgcod)
tablespace Sbibind
/
Create bitmap index Bmi_sbib_cpndtl_frmcty on
Sbib_Ownr.sbib_cpndtl(orgfrmctycod)
Tablespace Sbibind
/
Create bitmap index Bmi_sbib_cpndtl_tocty on Sbib_Ownr.sbib_cpndtl(orgtoctycod)
Tablespace Sbibind
/

This is my Time Table

Create Table Sbib_Ownr.Sbib_Time
(
  Saledate    Date         Not null,
  Salemonth   Varchar2(8)  Not null,
  Saleqtr     Varchar2(13) Not null,
  Saleyear    Number(4)    Not null,
  Fysaleqtr   Varchar2(20) Not null,
  Fysaleyear  Varchar2(15) Not null,
  constraint Pk_sbib_time primary key(saledate)
  Using Index Tablespace Sbibind
)
Storage
(
 Initial 32K
 Next    32K
 Pctincrease 0
)
Tablespace Sbibdat nologging
/

This contains one row for one month. The Date will be always represented as 1st
day of month ( '01-jan-2003','01-feb-2003')

---------------------------------
My MV Create Statement is

Create materialized view Sbib_Ownr.Sbib_Orgdst_Mv
Tablespace Sbibdat
Pctfree 0
storage (initial 64k next 64k pctincrease 0)
Parallel
build immediate
refresh on demand
enable query rewrite
as
select saledate,  oprcardsgcod, oprcarnumcod,cntcod,
       orgcty,  dstcty,rtgtxt,inboutind,
       sum ( decode (cotcod,'F',b.netamt,0)) F_Netamt,
       sum ( decode (cotcod,'J',b.netamt,0)) J_Netamt,
       sum ( decode (cotcod,'Y',b.netamt,0)) Y_Netamt,
       sum ( decode (cotcod,'F',b.retamt,0)) F_retamt,
       sum ( decode (cotcod,'J',b.retamt,0)) J_retamt,
       sum ( decode (cotcod,'Y',b.retamt,0)) Y_retamt,
       sum ( decode (cotcod,'F',b.oalpayout,0)) F_oalpayout,
       sum ( decode (cotcod,'J',b.oalpayout,0)) J_oalpayout,
       sum ( decode (cotcod,'Y',b.oalpayout,0)) Y_oalpayout,
       count (decode (cotcod,'F',1)) F_Cpncnt,
       count (decode (cotcod,'J',1)) J_Cpncnt,
       count (decode (cotcod,'Y',1)) Y_Cpncnt
  from Sbib_Ownr.sbib_dochdr a, Sbib_Ownr.sbib_cpndtl b, Sbib_Ownr.sbib_time c
 where c.saledate=a.saldat
   and a.carnumcod=b.carnumcod
   and a.docnum=b.docnum
   and b.oprcardsgcod<>'S'
 group by
saledate,oprcardsgcod,oprcarnumcod,cntcod,orgcty,dstcty,rtgtxt,inboutind
/
Create unique index Sbib_orgdst_Mv_uniq on Sbib_Ownr.sbib_orgdst_mv
(saledate,oprcardsgcod,oprcarnumcod,cntcod,orgcty,dstcty,rtgtxt,inboutind)
Tablespace Sbibind
/

--------------------------------------------

I have created a View to access the above materialize view.

Create or replace View Sbib_Ownr.Sbib_Orgdst_View as
select saledate,  oprcardsgcod, oprcarnumcod,cntcod,
       orgcty,  dstcty,rtgtxt,inboutind,
       sum ( decode (cotcod,'F',b.netamt,0)) F_Netamt,
       sum ( decode (cotcod,'J',b.netamt,0)) J_Netamt,
       sum ( decode (cotcod,'Y',b.netamt,0)) Y_Netamt,
       sum ( decode (cotcod,'F',b.retamt,0)) F_retamt,
       sum ( decode (cotcod,'J',b.retamt,0)) J_retamt,
       sum ( decode (cotcod,'Y',b.retamt,0)) Y_retamt,
       sum ( decode (cotcod,'F',b.oalpayout,0)) F_oalpayout,
       sum ( decode (cotcod,'J',b.oalpayout,0)) J_oalpayout,
       sum ( decode (cotcod,'Y',b.oalpayout,0)) Y_oalpayout,
       count (decode (cotcod,'F',1)) F_Cpncnt,
       count (decode (cotcod,'J',1)) J_Cpncnt,
       count (decode (cotcod,'Y',1)) Y_Cpncnt
  from Sbib_Ownr.sbib_dochdr a, Sbib_Ownr.sbib_cpndtl b, Sbib_Ownr.sbib_time c
 where c.saledate=a.saldat
   and a.carnumcod=b.carnumcod
   and a.docnum=b.docnum
   and b.oprcardsgcod<>'S'
 group by
saledate,oprcardsgcod,oprcarnumcod,cntcod,orgcty,dstcty,rtgtxt,inboutind

This view is just for the sake of avoiding typing big query every time and looks
exactly the same way of MV

-------------------------------------------------------------------

The following is the query i issued ..

  1  select * from sbib_orgdst_view
  2  Where saledate=to_Date('01-jan-2003','dd-mon-yyyy')
  3*   and oprcardsgcod='BA' and cntcod='AE'
SQL> /

393 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1542 Card=54 Bytes=1
          5498)

   1    0   VIEW OF 'SBIB_ORGDST_VIEW' (Cost=1542 Card=54 Bytes=15498)
   2    1     SORT (GROUP BY) (Cost=1542 Card=54 Bytes=4752)
   3    2       HASH JOIN (Cost=1537 Card=54 Bytes=4752)
   4    3         NESTED LOOPS (Cost=424 Card=734 Bytes=45508)
   5    4           INDEX (UNIQUE SCAN) OF 'PK_SBIB_TIME' (UNIQUE)
   6    4           TABLE ACCESS (BY INDEX ROWID) OF 'SBIB_DOCHDR' (Co
          st=424 Card=11009 Bytes=605495)

   7    6             BITMAP CONVERSION (TO ROWIDS)
   8    7               BITMAP AND
   9    8                 BITMAP INDEX (SINGLE VALUE) OF 'BMI_SBIB_DOC
          HDR_SALDAT'

  10    8                 BITMAP INDEX (SINGLE VALUE) OF 'BMI_SBIB_DOC
          HDR_CNTCOD'

  11    3         TABLE ACCESS (FULL) OF 'SBIB_CPNDTL' (Cost=1111 Card
          =10786 Bytes=280436)

Statistics
----------------------------------------------------------
        158  recursive calls
         10  db block gets
      12178  consistent gets
       7308  physical reads
          0  redo size
      56456  bytes sent via SQL*Net to client
       3190  bytes received via SQL*Net from client
        397  SQL*Net roundtrips to/from client
          8  sorts (memory)
          1  sorts (disk)
        393  rows processed

SQL>

------------------------------------------------------------

This is the count from MV itself for the above query.

SQL> select count(*) from sbib_orgdst_mv
  2  Where saledate=to_Date('01-jan-2003','dd-mon-yyyy')
  3    and oprcardsgcod='BA' and cntcod='AE';

  COUNT(*)
----------
       393

--------------------------------------------------------------

SQL> select
mview_name,rewrite_enabled,rewrite_capability,last_refresh_date,last_refresh_type

  2  from user_mviews;

MVIEW_NAME                     R REWRITE_C LAST_REFR LAST_REF
------------------------------ - --------- --------- --------
SBIB_ORGDST_MV                 Y TEXTMATCH 22-MAR-03 COMPLETE

----

I have all the reqd. privilages

SQL> select * from session_privs where privilage like 'QUERY%'
  2  /

PRIVILEGE
----------------------------------------
QUERY REWRITE

1 rows selected.

SQL> select name,substr(value,1,15) from v$parameter where
  2  name in ('query_rewrite_enabled','query_rewrite_integrity');

NAME                       SUBSTR(VALUE,1,
------------------------- ---------------
query_rewrite_enabled      TRUE
query_rewrite_integrity   enforced

I have analyzed the all the above tables and mVs with for all columns
for all indexes for table option.

Still it is not taking MV and query against FACT only.

      and we said...
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76994/mv.htm#36837
Non-SQL Text Rewrite Restrictions

   1. SELECT and GROUP BY lists, if present, must be the same in the query and
the materialized view and must contain straight columns, that is, no expressions
are allowed in the columns.

   2. Aggregate operators must occur only as the outermost part of the
expression; that is, aggregates such as AVG(AVG(x)) or AVG(x)+AVG(x) are not
allowed.

   3. The WHERE clause must contain only inner or outer equijoins, and they can
be connected only by ANDs. No ORs and no selections on single tables are
allowed in the WHERE clause.

   4. HAVING or CONNECT BY clauses are not allowed.

Got you -- what was happening was that only EXACT text rewrites were permitted
due to the

   and b.oprcardsgcod<>'S'

predicate you had.  So, a query using the EXACT TEXT (case and all) would work.
A query that did not use the EXACT text (eg: your view) would not work.
Additionally, the addition of further predicates to the EXACT text would render
it unusable as well.

Your solution, leave out the

   and b.oprcardsgcod<>'S'

Consider:

ops$tkyte@ORA817DEV> Create materialized view Sbib_Orgdst_Mv
  2  build immediate
  3  refresh on demand
  4  enable query rewrite
  5  as
  6  select saledate,  oprcardsgcod, oprcarnumcod,cntcod,
  7         orgcty,  dstcty,rtgtxt,inboutind,
  8         sum ( decode (cotcod,'F',b.netamt,0)) F_Netamt,
  9         sum ( decode (cotcod,'J',b.netamt,0)) J_Netamt,
 10         sum ( decode (cotcod,'Y',b.netamt,0)) Y_Netamt,
 11         sum ( decode (cotcod,'F',b.retamt,0)) F_retamt,
 12         sum ( decode (cotcod,'J',b.retamt,0)) J_retamt,
 13         sum ( decode (cotcod,'Y',b.retamt,0)) Y_retamt,
 14         sum ( decode (cotcod,'F',b.oalpayout,0)) F_oalpayout,
 15         sum ( decode (cotcod,'J',b.oalpayout,0)) J_oalpayout,
 16         sum ( decode (cotcod,'Y',b.oalpayout,0)) Y_oalpayout,
 17         count (decode (cotcod,'F',1)) F_Cpncnt,
 18         count (decode (cotcod,'J',1)) J_Cpncnt,
 19         count (decode (cotcod,'Y',1)) Y_Cpncnt
 20    from sbib_dochdr a, sbib_cpndtl b, sbib_time c
 21   where c.saledate=a.saldat
 22     and a.carnumcod=b.carnumcod
 23     and a.docnum=b.docnum
 24   group by
saledate,oprcardsgcod,oprcarnumcod,cntcod,orgcty,dstcty,rtgtxt,inboutind
 25  /

Materialized view created.

ops$tkyte@ORA817DEV> exec dbms_stats.set_table_stats( user, 'SBIB_ORGDST_MV',
numrows => 10000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> Create unique index Sbib_orgdst_Mv_uniq on sbib_orgdst_mv
  2  (saledate,oprcardsgcod,oprcarnumcod,cntcod,orgcty,dstcty,rtgtxt,inboutind)
  3  /

Index created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> Create or replace View Sbib_Orgdst_View as
  2  select saledate,  oprcardsgcod, oprcarnumcod,cntcod,
  3         orgcty,  dstcty,rtgtxt,inboutind,
  4         sum ( decode (cotcod,'F',b.netamt,0)) F_Netamt,
  5         sum ( decode (cotcod,'J',b.netamt,0)) J_Netamt,
  6         sum ( decode (cotcod,'Y',b.netamt,0)) Y_Netamt,
  7         sum ( decode (cotcod,'F',b.retamt,0)) F_retamt,
  8         sum ( decode (cotcod,'J',b.retamt,0)) J_retamt,
  9         sum ( decode (cotcod,'Y',b.retamt,0)) Y_retamt,
 10         sum ( decode (cotcod,'F',b.oalpayout,0)) F_oalpayout,
 11         sum ( decode (cotcod,'J',b.oalpayout,0)) J_oalpayout,
 12         sum ( decode (cotcod,'Y',b.oalpayout,0)) Y_oalpayout,
 13         count (decode (cotcod,'F',1)) F_Cpncnt,
 14         count (decode (cotcod,'J',1)) J_Cpncnt,
 15         count (decode (cotcod,'Y',1)) Y_Cpncnt
 16    from sbib_dochdr a, sbib_cpndtl b, sbib_time c
 17   where c.saledate=a.saldat
 18     and a.carnumcod=b.carnumcod
 19     and a.docnum=b.docnum
 20     and b.oprcardsgcod<>'S'
 21   group by
saledate,oprcardsgcod,oprcarnumcod,cntcod,orgcty,dstcty,rtgtxt,inboutind
 22  /

View created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> alter session set query_rewrite_enabled = true;

Session altered.

ops$tkyte@ORA817DEV> alter session set query_rewrite_integrity = enforced;

Session altered.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> set autotrace traceonly explain
ops$tkyte@ORA817DEV> select *
  2    from Sbib_Orgdst_View
  3   where saledate=to_Date('01-jan-2003','dd-mon-yyyy')
  4     and oprcardsgcod='BA'
  5     and cntcod='AE';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=100)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'SBIB_ORGDST_MV' (Cost=3 Card=1
Bytes=100)
   2    1     INDEX (RANGE SCAN) OF 'SBIB_ORGDST_MV_UNIQ' (UNIQUE) (Cost=2
Card=1)

ops$tkyte@ORA817DEV> set autotrace off

Review &
      Followup

      Rating: 5
Tom the
      Great  March 24, 2003
Reviewer:
      Lakshminarasimhan  from Dubai
Hi Tom

This is amazing.  I really never expect the behavioiur of Non - equi join.
Many thanks for this.

      Rating: 4
can we select
      null columns when we create MV's   October 10, 2003

Reviewer:  aish  from tx
CREATE MATERIALIZED VIEW erp_MV
REFRESH complete WITH ROWID
START WITH SYSDATE NEXT trunc(SYSDATE) + 1
AS
SELECT col1 ,col2 , col3 , null col4
-- this col4 is not in table1 and i want to add in Materialized View
from table1
where (.........)

it says "Zero length columns are not allowed here "

i can understand that iam not giving any datatype for col4 and iam overriding
the syntax.
But when i create the materialized view i needto add some empty column there .
Can u help me to solve this problem

          Followup:

ops$tkyte@ORA920LAP> create materialized view emp_mv
  2  as
  3  select empno, ename, cast( null as varchar2(20) ) x
  4    from emp;

Materialized view created.

      Rating: 5
thanks
      tom  October 10, 2003
Reviewer:  aish
       from tx
as usual thanks

      Rating: 3
redundant
      joins  October 21, 2003
Reviewer:  Asim
      Naveed  from Pakistan
Hi,

I create a MV like this
CREATE MV MV1 .....
ENABLE QUERY REWRITE.....

SELECT year, quarter, month, week, day,
GROUPING_ID(year, quarter, month, week, day) gid,
sum(sales) sales
FROM sales, time
WHERE sales.timeid= time.timeid
GROUP BY ROLLUP(year, quarter, month, week, day)

The mv is created , and ive seen in Enterprise manager
that its enabled for query rewrite, all the tables and
mvs are analyzed.

When I issuse the following, MV is not used, Note that
there are redundant "sales.time_id=time.time_id" join.

SELECT year, quarter, SUM(SALES)
FROm sales, time
where
sales.timeid=time.timeid
            and time.year=2003
and sales.timeid=time.timeid
            and time.quarter='QTR1'
GROUP BY YEAR, QUARTER

The reason is that the query is being generated by
a third party tool, and that tool has a bug that it
generates redundant join conditions. But when I contacted
that party, they ACCEPT that its a bug, but they also
said that oracle should even then use a MV.

In my opinion also, oracle should use MV.

Please correct us.

          Followup:

confirmed the problem:

ops$tkyte@ORA920LAP> create table t ( pk int primary key, year int, quarter int,
month int, week int, day int );

Table created.

ops$tkyte@ORA920LAP> create table s ( sales int, fk references t );

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create materialized view mv
  2  enable query rewrite
  3  as
  4  select year, quarter, month, week, day, grouping_id(year, quarter, month,
week, day ) gid,
  5         sum(sales) sales
  6    from t, s
  7   where t.pk = s.fk
  8   group by rollup( year, quarter, month, week, day )
  9  /

Materialized view created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> exec dbms_stats.set_table_stats( user, 'T', numrows =>
10000000, numblks => 1000000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec dbms_stats.set_table_stats( user, 'S', numrows =>
100000000, numblks => 10000000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec dbms_stats.set_table_stats( user, 'MV', numrows =>
100, numblks => 10 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> alter session set query_rewrite_enabled=true;

Session altered.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> set autotrace traceonly explain
ops$tkyte@ORA920LAP> select year, quarter, month, week, day, grouping_id(year,
quarter, month, week, day ) gid,
  2         sum(sales) sales
  3    from t, s
  4   where t.pk = s.fk
  5     and t.pk = s.fk
  6     and t.quarter = 1
  7   group by rollup( year, quarter, month, week, day )
  8  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1212028 Card=1 Bytes=104)
   1    0   SORT (GROUP BY ROLLUP) (Cost=1212028 Card=1 Bytes=104)
   2    1     HASH JOIN (Cost=1212026 Card=1 Bytes=104)
   3    2       TABLE ACCESS (FULL) OF 'T' (Cost=96176 Card=100000
Bytes=7800000)
   4    2       TABLE ACCESS (FULL) OF 'S' (Cost=961744 Card=100000000
Bytes=2600000000)

ops$tkyte@ORA920LAP> select year, quarter, month, week, day, grouping_id(year,
quarter, month, week, day ) gid,
  2         sum(sales) sales
  3    from t, s
  4   where t.pk = s.fk
  5     and t.quarter = 1
  6   group by rollup( year, quarter, month, week, day )
  7  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=1 Bytes=91)
   1    0   SORT (GROUP BY ROLLUP) (Cost=5 Card=1 Bytes=91)
   2    1     TABLE ACCESS (FULL) OF 'MV' (Cost=3 Card=1 Bytes=91)

ops$tkyte@ORA920LAP> set autotrace off

And that is a nice, compact -- yet complete test case you can use to file a
problem report with support.  It looks like a "bug" to me as well.  it should be
able to use the MV, the extra join condition is throwing it off.

      Rating: 5
redundant joins
        October 22, 2003
Reviewer:  Guy  from
      Manchester, England
Asim Naveed,

A workaround.... If this is a consistent bug in the program then if you
re-create the mv with the second redundant join in it then the mv *is* used.

*** your MV with including the redundant join

create materialized view mv2
enable query rewrite
    as
    select year, sum(1) sales
      from t, s
      where t.pk = s.fk and t.pk = s.fk
     group by year
/

*** the generated sql from the application, with the redundant join...

select year, sum(1) sales
from t, s
where t.pk = s.fk and t.pk = s.fk
group by year;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=82 Bytes=2132
   1    0   TABLE ACCESS (FULL) OF 'MV2' (Cost=1 Card=82 Bytes=2132)

*** and it worked.

(I'm on 8.1.7.4 so had to dumb down the query a bit, but I could still repeat
you orginal problem)

(Tom, great site, great books, keep up the good work etc.)

          Followup:

doh, as I smack my forehead and say "hey, I should have thunk of that".  thanks!

      Rating: 5
What if few
      columns dropped  October 26, 2003
Reviewer:
      Lakshminarasimhan r  from Dubai
Hi tom

If we have a master table in DB1 Schema A Snapshot to DB2 Schema B.  after some
time if we drop some column in master table (DB1 Schema A), is it mandatory to
drop the snapshot in DB2 schema B and  recreate it to refresh the structure or
any other way?

          Followup:

if the table in db2 includes that column, you'll need to drop and recreate the
snapshot -- yes.

October 27, 2003 Doug -- Thanks for the question regarding
      "Mutating Table", version 8.1.6

      You Asked
Tom,
I've got a mutating table problem... I have a table with two
status fields: 'loaded_status' and 'processed_status'. When the
'loaded_status' column is updated to 'C' (complete) I want to
fire off a trigger that passes the ID value for the row to a
stored procedure. That stored procedure will do some processing
and then update the 'processed_status' column in the original
table. I put an update row-level trigger in the table to do an
insert of the ID value into a temp table, then had an insert
trigger on the temp table to execute the stored procedure that
processes , then updates the 'processed_status' column in the
original row...but the dreaded ORA-04091 mutating table error
occurred.

Any suggestions on how to do this? (I have read your paper
'Avoiding Mutating Tables' but the cases presented didn't seem
to fit my example...)
Thanks,
  Doug J.

      and we said...
Well, the procedure cannot update the row if it is called from
the row level trigger -- but the trigger that CALLS the
procedure can.

Can your logic be as simple as:

create or replace trigger T_trigger
before update on T for each row
begin
   some_procedure( :new.id );
   :new.processed_status := 'P';
end;
/

or perhaps:

begin
   :new.processed_status := some_procedure( :new.id,
                                   :new.processed_status );
end;
/

In that way, some_procedure can decide what the value of
processed status should be and return it (or return the current
value).

In the event you really don't want to do that for some reason --
take another look at the Avoiding Mutating Tables.

http://osi.oracle.com/~tkyte/Mutate/index.html
It does exactly what you want -- with a minor modification to
avoid recursion.  (really recommend easy solution above tho)
something like:

ops$tkyte@DEV8I.WORLD> create table t
  2  ( id int primary key,
  3    load_status char(1),
  4    processed_status char(1)
  5  )
  6  /

Table created.

ops$tkyte@DEV8I.WORLD>
ops$tkyte@DEV8I.WORLD> create or replace package state_pkg
  2  as
  3      type myArray is table of t.id%type
  4                index by binary_integer;
  5
  6      todo  myArray;
  7      empty myArray;
  8
  9      avoid_recursion boolean default false;
 10  end;
 11  /

Package created.

ops$tkyte@DEV8I.WORLD>
ops$tkyte@DEV8I.WORLD> create or replace trigger t_bu
  2  before update on t
  3  begin
  4      if ( not state_pkg.avoid_recursion )
  5      then
  6          state_pkg.todo := state_pkg.empty;
  7      end if;
  8  end;
  9  /

Trigger created.

ops$tkyte@DEV8I.WORLD> create or replace trigger t_bu_fer
  2  before update on t for each row
  3  begin
  4      if ( not state_pkg.avoid_recursion
  5           and
  6           :new.load_status = 'C' )
  7      then
  8          state_pkg.todo(state_pkg.todo.count+1) := :new.id;
  9      end if;
 10  end;
 11  /

Trigger created.

ops$tkyte@DEV8I.WORLD> create or replace
  2  procedure some_process( p_x in int )
  3  as
  4  begin
  5      update t set processed_status = 'P' where id = p_x;
  6  end;
  7  /

Procedure created.

ops$tkyte@DEV8I.WORLD> create or replace trigger t_au
  2  after update on t
  3  begin
  4      if ( not state_pkg.avoid_recursion )
  5      then
  6      begin
  7          state_pkg.avoid_recursion := TRUE;
  8          for i in 1 .. state_pkg.todo.count loop
  9              some_process( state_pkg.todo(i) );
 10          end loop;
 11          state_pkg.avoid_recursion := FALSE;
 12      exception
 13          when others then
 14              state_pkg.avoid_recursion := FALSE;
 15              RAISE;
 16      end;
 17      end if;
 18  end;
 19  /

Trigger created.

ops$tkyte@DEV8I.WORLD> insert into t
  2  values ( 1, null, null );

1 row created.

ops$tkyte@DEV8I.WORLD> select * from t;

        ID L P
---------- - -
         1

ops$tkyte@DEV8I.WORLD> update t
  2     set load_status = 'C'
  3   where id = 1;

1 row updated.

ops$tkyte@DEV8I.WORLD> select * from t;

        ID L P
---------- - -
         1 C P

Review &
      Followup

      Rating:
      5
  May 21, 2002
Reviewer:  steve
      Mckee  from Montreal, quebec
Hi Tom,

I was trying to implement the workaround mentioned in
your article on mutating tables. I succeeded when using
simply a scaler, but I'm stuck when I need to keep
a record. Here's the error I get:

insert into ppcalldata values ('999',999,1);
insert into ppcalldata values ('999',999,1)
            *
ERROR at line 1:
ORA-06530: Reference to uninitialized composite
ORA-06512: at "MTR.SHADOW_MT_AIFER", line 3
ORA-04088: error during execution of trigger 'MTR.SHADOW_MT_AIFER'

Below is the SQL to set everything up. I tried, as much as possible to follow
your example. And I tried to search for
"Reference to uninitialized composite" but had no luck.

drop table ppcalldata
/
create table ppcalldata (
   ref_num NUMBER(15),
   pnum             VARCHAR2(30),
   traffic_case int
   )
/
create or replace type callIdentifier as object
( ref_num NUMBER(15),
 pnum             VARCHAR2(30)
  )
/
-- attribute_datatype
--
-- This is any Oracle datatype except LONG, LONG RAW, NCHAR, NCLOB, NVARCHAR2,
-- ROWID, UROWID, the PL/SQL-specific types BINARY_INTEGER (and its subtypes),
-- BOOLEAN, PLS_INTEGER, RECORD, REF CURSOR, %TYPE, and %ROWTYPE, and types
-- defined inside a PL/SQL package.

--create or replace type arrayOfCalls as table of callIdentifier -- index by
binary_integer

REM this package is used to maintain our state.  We will save the rowids of
newly
REM inserted / updated rows in this package.  We declare 2 arrays -- one will
REM hold our new rows rowids (newRows).  The other is used to reset this array,
REM it is an 'empty' array

create or replace package state_pkg
as
       type arrayOfCalls is table of callIdentifier index by binary_integer;

       newMTRows arrayOfCalls;
       newCFRows arrayOfCalls;
       empty     arrayOfCalls;
  end;
/

REM We must set the state of the above package to some known, consistent state
REM before we being processing the row triggers.  This trigger is mandatory,
REM we *cannot* rely on the AFTER trigger to reset the package state.  This
REM is because during a multi-row insert or update, the ROW trigger may fire
REM but the AFTER tirgger does not have to fire -- if the second row in an
update
REM fails due to some constraint error -- the row trigger will have fired 2
times
REM but the AFTER trigger (which we relied on to reset the package) will never
fire.
REM That would leave 2 erroneous rowids in the newRows array for the next
insert/update
REM to see.   Therefore, before the insert / update takes place, we 'reset'

create or replace trigger shadow_MT_bi
  before insert or update on ppcalldata
  begin
          state_pkg.newMTRows := state_pkg.empty;
          state_pkg.newCFRows := state_pkg.empty;
  end;
/

REM This trigger simply captures the rowid of the affected row and
REM saves it in the newRows array.

create or replace trigger shadow_MT_aifer
  after insert or update on ppcalldata for each row
  begin
    if ( :new.traffic_case = 1 ) then  -- CF
          state_pkg.newCFRows( state_pkg.newCFRows.count+1 ).ref_num :=
:new.ref_num;
          state_pkg.newCFRows( state_pkg.newCFRows.count ).pnum      :=
:new.pnum;
    elsif ( :new.traffic_case = 2 )  then  -- MT
          state_pkg.newMTRows( state_pkg.newMTRows.count+1 ).ref_num :=
:new.ref_num;
          state_pkg.newMTRows( state_pkg.newMTRows.count ).pnum      :=
:new.pnum;
    end if;
  end;
/

REM this trigger processes the new rows.  We simply loop over the newRows
REM array processing each newly inserted/modified row in turn.

create or replace trigger shadow_MT_ai
  after insert or update on ppcalldata
  begin
          for i in 1 .. state_pkg.newCFRows.count loop
            delete ppcalldata where
              ref_num       = state_pkg.newCFRows(i).ref_num and
              pnum          = state_pkg.newCFRows(i).pnum             and
              traffic_case  = 2;
          end loop;

          for i in 1 .. state_pkg.newMTRows.count loop
            delete ppcalldata a
            where  a.ref_num      =  state_pkg.newMTRows(i).ref_num and
                   a.pnum         = state_pkg.newMTRows(i).pnum     and
                   a.traffic_case = 2 and exists
              ( select 0
                from ppcalldata b
                where b.ref_num        = a.ref_num and
                      b.pnum           = a.pnum    and
                      b.traffic_case   = 1
             );
         end loop;
  end;
/

If you could help, that would be great.

Thanks Tom.

Steve

          Followup:

The composite object type isn't initialied.  Code:

create or replace trigger shadow_MT_aifer
  after insert or update on ppcalldata for each row
  begin
    if ( :new.traffic_case = 1 ) then  -- CF
          state_pkg.newCFRows( state_pkg.newCFRows.count+1 ) := callIdentifier(
:new.ref_num, :new.pnum );
    elsif ( :new.traffic_case = 2 )  then  -- MT
          state_pkg.newMTRows( state_pkg.newMTRows.count+1 ) := callIdentifier(
:new.ref_num, :new.pnum );
    end if;
  end;
/

instead of the way you are (or don't mix object relational sql types with plsql
tables of records -- eg: use a record type in PLSQL and you don't have to use
this syntax)

      Rating: 5
with a minor
      modification to avoid recursion....  May 22, 2002

Reviewer:  Pasko  from Hamburg, Germany
Hi Tom

Thanks for your response to the above Infamous Mutating Error...

Could you please give us more clarification on why you added this Recursion
check on your  example above , but there's no such a check on the link you have
given above...

I guess the  reason is that so that i avoid updation of  the rows that are being
modified from within my procedure which is called in the  AFTER INSERT STATEMENT
Trigger...

Right?

          Followup:

Because in the general link above, I made the safe assumption that you would not
be updating the same table that the trigger is firing upon.  We need it here in
order to avoid the infinite loop that could otherwise happen.

      Rating: 5
Trigger to
      update same row after inserting  September 18, 2002

Reviewer:  Jeff  from AZ USA
Hi Tom,

This is a very useful workaround on updating. Is there a workaround for firing a
trigger to update the same row after the row is inserted into a table? Say, in
your table T, after an application does "insert into t values (2, 'Y', 'N');",
the result should be
SQL> select * from t where id = 2;
ID  L   P
--  --  --
2   Y   N
But, we want it to be
ID  L   P
--  --  --
2   Y   Y
It sounds strange, and really the application is wrong. We do not have the
source code to modify the application, so we need to write a tigger on the table
to accomplish that. Can it be done by trigger?

Thank you very much.

          Followup:

You just need to code:

begin
   ....
   :new.p := 'Y';
   ......

in a BEFORE INSERT FOR EACH ROW trigger.  You can directly modify the :new
record changing the values.

      Rating: 5
Thank you,
      Sir  September 18, 2002
Reviewer:  Jeff Yuan
       from AZ USA
Tom,

The "BEFORE insert" trigger makes my day meaningful. Thank you so much.

Regards,

Jeff

      Rating: 5
why array every
      time  November 08, 2002
Reviewer:
      A reader
Hi tom,

 If I have a table

city

city_code   varchar2(5)(PK)
city_name   varchar2(30) NOT NULL (unique)
city_state_cd  varchar2(5) (FK state table) NULL
city_county_cd varchar2(5) (FK country Table) NOT NULL

NOTE : if state is null then after that insert
1.)  there must not be any combination(of city and country)
    allowed for insert or update
2.)  if there is atleast one state already availabe in
      the table for the combination of city and country
       then null state is not allwoed for that combination
      of city and state

I have function as......

CREATE OR REPLACE FUNCTION city_check(p_city varchar2,p_state varchar2,p_country
varchar2)
RETURN number IS

stv_city varchar2(200);
stv_stp  varchar2(200);
stv_cny  varchar2(200);

BEGIN

    SELECT     city_name,city_stp_code,city_cny_code
    INTO       stv_city, stv_stp,stv_cny
    FROM       city
    WHERE      city_name     = p_city
    AND     city_cny_code    = p_country;

    IF(  stv_stp is not null) then

        IF (stv_stp = p_state) THEN
                    RAISE DUP_VAL_ON_INDEX;
            END IF;
            return(1);
         ELSE
        RAISE DUP_VAL_ON_INDEX;
         END IF;

EXCEPTION
   WHEN DUP_VAL_ON_INDEX THEN
         RAISE;
   WHEN no_data_found THEN
      DBMS_OUTPUT.PUT_LINE('NO-DATA-FOUND : ' ||SQLERRM);
      return(1);
   WHEN too_many_rows THEN
    DBMS_OUTPUT.PUT_LINE('TOO-MANY-ROWS : ' ||SQLERRM);
    IF(p_state is null) then
        RAISE;
    END IF;
        BEGIN

        SELECT     city_name,city_stp_code,city_cny_code
        INTO       stv_city, stv_stp,stv_cny
        FROM       city
        WHERE      city_name     = p_city
        AND     city_stp_code   = p_state
        AND     city_cny_code    = p_country;

        RAISE DUP_VAL_ON_INDEX;
    EXCEPTION
         WHEN no_data_found THEN
         return(1);
         WHEN others THEN
         RAISE;
    END;
   WHEN others THEN
       DBMS_OUTPUT.PUT_LINE('others : ' || SQLERRM);
       RAISE;
END;
/
show err

I have row level trigger that

CREATE OR REPLACE
TRIGGER  city_insertupdate_row
 BEFORE INSERT or UPDATE
 ON city
 FOR EACH ROW
DECLARE
     v_temp NUMBER;
 BEGIN
      v_temp :=
city_check(:new.city_name,:new.city_stp_code,:new.city_cny_code);

      IF(v_temp = -1) THEN
            RAISE_APPLICATION_ERROR(-20002,'Record Already Exists !!');
      END IF;

END city_insertupdate_row;
/

OUTPUT
---------

SQL>  exec p_city.update_city('AA','Middletown',NULL,'US',:msg_cd,:msg_str);
check_city:others : ORA-04091: table DATADEPOT.CITY is mutating,
trigger/function may not see it

CI CITY_NAME                      CI CI
-- ------------------------------ -- --
LV Las Vegas                      NV US
LG 123456789012345678901234567890    LG
AB A12345678912345678901234567890    US
M1 METUCHAN                       NJ US
MD Middletown                        US
XX x y z                          NY US
AA AA                                GB

can you help me with that ?

thanks,

          Followup:

I don't understand why you are using a trigger at all?  What is your logic -
what is wrong with just using declaritive integrity?

      Rating: 3
Strange
      Problem  December 10, 2002
Reviewer:
      abhijit  from Mumbai
Hi Tom,
I have got three tables

Create table TB_IW_WORKITEM_INFO
(
  ID             VARCHAR2(44),
  SOURCE_QUEUE   VARCHAR2(20),
  DEST_QUEUE     VARCHAR2(20),
  TIME_SPENT     VARCHAR2(26),
  NATURE         NUMBER(3),
  STATUS         VARCHAR2(60),
  QUEUE_DATE     DATE,
  PROCESSOR      VARCHAR2(12),
  CFDT_QUEUENAME VARCHAR2(50) default ('')
)

create table TB_IW_WORKITEM
(
  WORKITEM_ID        VARCHAR2(44) not null,
  WORKITEM_NAME      VARCHAR2(44) not null,
  BRANCH_CODE        NUMBER(5) not null,
  RLABEL2            VARCHAR2(32),
  RLABEL3            VARCHAR2(32),
  PRODUCT            VARCHAR2(32),
  RLABEL4            VARCHAR2(32),
  RLABEL5            VARCHAR2(32),
  IW_REFERENCE       NUMBER(10),
  REFERENCE_NUMBER   NUMBER(10),
  MAKER_DATE         DATE not null,
  MAKER_NAME         VARCHAR2(12) not null,
  CLARIFYECI         VARCHAR2(12),
  EXCEPTIONS_I       NUMBER(10),
  EXCEPTIONS_E       NUMBER(10),
  EXCEPTIONS_C       NUMBER(10),
  RLABEL1            VARCHAR2(32),
  COUNTRY            VARCHAR2(32) default (' ') not null,
  CITY               VARCHAR2(32) default (' ') not null,
  RLABEL8            VARCHAR2(32),
  RLABEL7            VARCHAR2(32),
  FOLDER_NUMBER      VARCHAR2(30),
  RLABEL6            VARCHAR2(32),
  RLABEL9            VARCHAR2(32),
  PARENT_WORKITEM_ID VARCHAR2(44),
  PARENT_WORKITEM_NM VARCHAR2(44),
  TXN_IMG_AUTH_STAT  VARCHAR2(1) default ('A') not null,
  ACTION_TAKEN_DATE  DATE
)
create table TB_IW_WANG_CITIDOCS
(
  WORKITEM_ID               VARCHAR2(44) not null,
  CITIDOCS_CABINET_INDEX    NUMBER(10) not null,
  CITIDOCS_REFERENCE_NUMBER NUMBER(10) not null,
  WORKITEM_TYPE             NUMBER(3) default (0) not null,
  ATTACHMENT_ID             NUMBER(10) default (0) not null,
  IMG_AUTH_STAT             VARCHAR2(1) default ('A') not null
)

I have got a (14) triggers on the TB_IW_WORKITEM_INFO table. 3 of them are
disabled. I have used three separate packages to maintain the state. I used
those packages after seeing your example on table mutation.

Whenever I insert a row, I get an error stating that
ORA-04063:  has errors
ORA-04063: package body "ORBIDATA.WKITEM_COUNT_PKG" has errors
ORA-06508: PL/SQL: could not find program unit being called
ORA-06512: at "ORBIDATA.TRG_BINS_WKITEM_COUNT", line 2
ORA-04088: error during execution of trigger 'ORBIDATA.TRG_BINS_WKITEM_COUNT'

Here is the code of my package. All the three packages have the same code, but
its this one that is giving the error.

create or replace package WKITEM_COUNT_PKG
As

  -- Created : 12/10/2002 4:37:51 PM
  -- Purpose : To Maintain the state during updateworkitemscount
  -- Used by the trigger trg_updateworkitemscount on TB_IW_WORKITEM_INFO table
    type ridArray is table of rowid index by binary_integer;
    newRows ridArray;
    empty   ridArray;
    avoid_recursion boolean default false;
end;

Here is the text of the trigger which is causing the problem.
create or replace trigger trg_bins_wkitem_count
    before insert or update on TB_IW_WORKITEM_INFO
begin
            if ( not wkitem_count_pkg.avoid_recursion ) then
                        wkitem_count_pkg.newRows := wkitem_count_pkg.empty;
            end if;

end;

Could you please help me out? Do you need more information?

Cheers!
Abhijit

          Followup:

what is hard to understand about:

ORA-04063: package body "ORBIDATA.WKITEM_COUNT_PKG" has errors

????

go into sqlplus and issue:

SQL> show errors package body wkitem_count_pkg

you have a package body for that guy and it is invalid!  if you did not mean to
have a package body, drop the package and recreate it.

A house built of cards is a fragile thing indeed.  14 triggers, 3 packages to
maintain state -- trying to avoid mutating tables.  Ugg, sounds really
complex....

      Rating: 3
Thanks
      Tom  December 10, 2002
Reviewer:  Abhijit
       from Mumbai
Hi Tom,
Thanks for your prompt reply. I dropped the package and re-created it. However
heeding your warning, I decided to do away with the third-package (also avoided
three triggers in this process). Now I have got two packages and 11 triggers.

Will test and let you know about the results.
My project manager is of the opinion that existing code / objects should not be
disturbted under any circumstances. Esp once they have been reviewed and frozen.
So all the developers of this team simply create new objects. Hence so many
triggers. I have now attempted to reverse this trend, by modifying one of the
existing triggers. Lets see what happens.

Cheers!
Abhijit

      Rating: 3
ORA-06530:
      Reference to uninitialized composite.  December 11, 2002

Reviewer:  Kashif  from Houston, TX
Hi Tom,

I ran into this problem with the following code:

declare
Type varchar2_table_type IS TABLE OF VARCHAR2 (50) INDEX BY BINARY_INTEGER;
lt_denial_str varchar2_table_type;
type number_table_type is table of number index by binary_integer;
v_table1 dbms_sql.varchar2_table;
n_table1 dbms_sql.number_table;
...
...
SELECT to_number (substr (ad_ac.lnum, 6)) lnum,
decode(ad_ac.excsvobl,'Y','1','0') ||
decode(ad_ac.incexcin,'Y','1','0') ||
decode(ad_ac.empleng,'Y','2','0') ||
decode(ad_ac.empltemp,'Y','2','0') ||
decode(ad_ac.bankrupt,'Y','3','0') ||
decode(ad_ac.crdt_oth,'Y','3','0') ||
decode(ad_ac.delcrdob,'Y','3','0') ||
decode(ad_ac.garnish,'Y','3','0') ||
decode(ad_ac.limcrdt,'Y','3','0') ||
decode(ad_ac.otsdsour,'Y','3','0') ||
decode(ad_ac.paymtuna,'Y','3','0') ||
decode(ad_ac.poorcred,'Y','3','0') ||
decode(ad_ac.collsuff,'Y','4','0') ||
decode(ad_ac.cashlack,'Y','5','0') ||
decode(ad_ac.insuffnd,'Y','5','0') ||
decode(ad_ac.insufinc,'Y','5','0') ||
decode(ad_ac.vrfycrdt,'Y','6','0') ||
decode(ad_ac.vrfyempl,'Y','6','0') ||
decode(ad_ac.vrfyincm,'Y','6','0') ||
decode(ad_ac.vrfyres,'Y','6','0') ||
decode(ad_ac.appincmp,'Y','7','0') ||
decode(ad_ac.crdtref,'Y','7','0') ||
decode(ad_ac.insuffcr,'Y','7','0') ||
decode(ad_ac.insuffle,'Y','7','0') ||
decode(ad_ac.nofile,'Y','7','0') ||
decode(ad_ac.denfhlmc,'Y','9','0') ||
decode(ad_ac.denothr1,'Y','9','0') ||
decode(ad_ac.deny_hud,'Y','9','0') ||
decode(ad_ac.deny_va,'Y','9','0') ||
decode(ad_ac.denyfnma,'Y','9','0') ||
decode(ad_ac.nocredit,'Y','9','0') ||
decode(ad_ac.otherbx1,'Y','9','0') ||
decode(ad_ac.resleng,'Y','9','0') ||
decode(ad_ac.tempres,'Y','9','0')
BULK COLLECT
INTO n_table1, v_table1
FROM ADV_ACTN ad_ac, viewpipe_temp v
WHERE ad_ac.lnum = v.lnum;
--
for i in 1 .. n_table1.count
loop
      lt_denial_str (n_table1(i)) := v_table1 (i);
      n_table1.delete(i);
      v_table1.delete(i);
end loop;
--
end;

This problem seems to be intermittent however, I've run into it only once so
far, in all my executions of this same piece of code. Per your explanation, it
might be as a result of mixing plsql types and sql types, but that isn't the
case here. Also, the query does generate output, around 20K records, so the two
tables which are bulk collected into are populated. Any feedback would be
useful. Thanks.

Kashif

      Rating: 3
For those
      interested...  December 19, 2002
Reviewer:
      Kashif  from Houston, TX
1606456 is the bug number which documents the above problem. The fix suggested
is to upgrade to 8.1.7.

Kashif

      Rating: 5
What about
      this  January 10, 2003
Reviewer:  Sujata
       from Germany
Consider a table like this:

order_id number
<some composite unique key that identifies the uniqueness of a row besides
order_id) say comp_uq
Order_type number
Order_status varchar2(50)

Order_id is an auto_generated primary key
Order_type column can have only two possible values (1 or2)
Order_status column can have only two possible values ('Processing' or
'Completed')

Now, an order_type of 1 should have a corresponding 2 (comparing comp_uq)

Order_status column for a order_type of 2 can not be modified through the app.

Problem: When ORDER_STATUS column for a order_type of 1 is updated to 2....
find the order_id through comp_uq keys and order_type 2...and update the
order_status to 2 if it's 1.

Question:
Can this be done through a trigger...because it will be updating the same column
that the trigger is on.
If yes How?

Thanks

          Followup:

you would just use the :old and :new psuedo records in a before for each row
trigger to accomplish that sort of logic.  No need to update the table, just
change the :new values.

      Rating: 5
Mutating table
      error  January 13, 2003
Reviewer:  Sujata
       from Germany
The problem is that the trigger would execute when an order of order_type 1 is
being updated to "PROCESSING"...At that point I have to find the counterpart
order_type of 2 and update the order_status to "PROCESSING"...
This requires querying the ORDER table...and getting into a mutating table
error...

          Followup:

sounds like a design issue to me -- either that second record should be joined
with this first record (eg: the data in the second record is DERIVED data --
sort of like you don't store "age", you store "date of birth").  Or that second
record shouldn't exist at all, its attributes should be part of this record.

see

http://asktom.oracle.com/~tkyte/Mutate/index.html
for the ugly way to do this -- I find 99.9999% of the time -- the issue is
circumvented by a practical design however.  I would join and derive probably.

      Rating: 4
whats wrong
      ?  February 03, 2003
Reviewer:  Ram
       from India
Oracle 8.1.5, PB 7.0.

i have the work of debugging the following delicate ( bad ?) piece of code and
determine what caused what.

here it is:

A ( id -> pk, other columns...)
B ( id -> pk references A(id) on delete cascade , other columns...)
C ( id -> pk references B(id) on delete cascade , other columns...)

in addition to 'cascade delete' there is:

on delete trigger of A:
delete from b where id = ...

on delete trigger of B:
delete from C where id = ...

try a deletion on A or B and the triggers happily mutate !

so the PB code does the following:
disable the triggers.
delete from c where id = ...
delete from b where id = ...
delete from a where id = ...
enable the triggers.
the deletion goes from the child to the parent step by step instead of just:
delete from a where id = ...

when this was run the log showed mutating trigger errors for a huge no. of
records (in production).

in development environment:
fortunately there seems to be enough error handling, writing each and every
error into a log file.

so, if the disable trigger step doesnt go through, it is logged and the process
is terminated because it will surely fire the mutating table error.

i confirmed this by running it as a user who doesnt have alter table priv ( to
disable the trigger ).
and sure enough the process terminated and the log showed 'insufficient privs'
error .
i am unable to recreate the production issue.

any ideas on what could have happened , so that the trigger is not disabled but
yet escaped error handling (logging) ?
how do you suggest we clean up this kind of code ?
Thanks !

          Followup:

I'm really confused.

why would the triggers have deletes in them -- especially if the RI is setup
with ON DELETE CASCADE which would do the delete for you???????

why bother having triggers if you disable them????

why bother with 3 tables even if the primary keys are the fkeys -- looks like
there should be one table A and no B or C.

Writing errors into log files is called "blindly ignoring the error".  I call it
a bug.  Your application should have failed -- frozen in its tracks.  Instead it
sounds like you silently ignore the errors and let the end user think "all is
ok".

I've no idea what you mean by "yet escaped the error handling" here.  But anyway
-- I've no idea why

o you have triggers
o you have more then one table...

either.

      Rating: 4
confusions
      abound !  February 03, 2003
Reviewer:  Ram
       from India
i am confused too about the triggers part. the tables are ok ( i guess ) because
they contain other information not present in the parent.
that the design as well as the code is a mess, is clear.
temporarily, i am just trying to determine how this mutating table error got
logged.
(more out of curiosity as i am not able to understand how it happened!)
so i'll try to frame the "yet escaped the error handling part" better here:

the code goes like this:

disable the triggers.
if error then
  log the error and halt the process
else
  proceed with the deletions...
end if

so it could only be one of the following two:
a) error while disabling, log that error and halt the process.
b) disabling is successful, so the deletions go through. or rather, there should
not be any mutating table error.

The log shows the mutating table error.
so its not option a) because the process did not stop.
its not option b) because i got the mutating table error which means -> trigger
disabling unsuccessful (again a guess here). but if the triggers were not
disabled then it should have logged that and halted the process.....
my confusion goes in a cycle :-)

question :- under what circumstances the ' disable trigger ' statement failed
but did not throw any error and proceeded to the next step ?
what happened here ?

          Followup:

In a one to one relation ship like that, the parent should just have those
columns.

I cannot comment on the code here and "what happened or didn't happen".  It is
all mucked up in the PB code.

But anyway -- think about it, it should be clear what happens in a multi-user
environment

session 1 disables triggers - this is a GLOBAL OPERATION, triggers are disabled
for every session

session 1 starts deleting

session 2 disables triggers -

session 1 completes and ENABLED the triggers -- this is a GLOBAL OPERATION,
triggers are enabled for EVERY SESSION

session 2 starts getting mutating table errors

If they did something so basically wrong with triggers -- I fear for the
integrity of your data all together in a multi user environment!

Nothing failed here -- code worked exactly as coded (and that is the problem!)

      Rating: 5
One-to-one
      tables  February 03, 2003
Reviewer:  Tony
      Andrews  from London, UK
Tom,

You said: "why bother with 3 tables even if the primary keys are the fkeys --
looks like there should be one table A and no B or C."

This is often done by database designers when B is a subtype of A.  For example,
Manager might be a subtype of Employee, and Managers have a lot of extra
attributes and relationships.  Also, only 5% of Employees are Managers.  The
designer may have decided to hold the Manager data in a separate table for
performance reasons.

Now, since you know more about performance than most designers and have said the
above I wonder whether you think that such a design decision is NEVER valid - or
would you agree it might make sense sometimes?

          Followup:

"for performance reasons" -- what, they wanted it to run slower?

It would make sense if the object has hundreds of attributes, you want to stay
well under 250 columns/table if at all possible -- we start chaining rows
whether they would fit on a block or not at 255.

Put the optional attributes "at the end of the table" and when they are NULL --
they consume 0 bytes of storage.  When they are NOT NULL, you don't need to
join.

It makes sense to LOGICALLY model these as supertypes/subtypes -- but
physically, I would prefer them in a single table.

      Rating: 5
"for
      performance reasons" -- what, they wanted it to run
      slower?  February 03, 2003
Reviewer:  Tony
      Andrews  from London, UK
Yes, they actually did that somewhere I worked a while ago.  System testing was
showing performance to be poor, so the project's Oracle "expert" decided that
some drastic changes were required.  This involved splitting what had been one
table with maybe 150 columns into about 6 smaller tables like:
MAIN_TABLE
- SUBTYPE 1
  - SUBSUBTYPE 1A
  - SUBSUBTYPE 1B
- SUBTYPE 2
- SUBTYPE 3

This was the same guy who I've mentioned before, who wanted to drop all the
check constraints to improve performance!

Needless to say the changes were approved and made on the basis that they
"might" help, without any testing being done to verify the claim.

      Rating: 4
Summary
      Table  March 26, 2003
Reviewer:  Abubaker
      Khered  from Saudi Arabia
Hi Tom,

I have a summary table where i summarize the activity of an item into one record
monthly.  In this table there is an opening balance for every item brought
forward form the previous period (ia_ob_qty, ia_ob_cost), along with current
month transaction (ia_mnth_qty, ia_mnth_cost). I'm trying to do a database
trigger that whenever there is an insert or update on an item, trigger should
fire and update the opening balance of the same item but for the next period.
But I'm getting mutating error.  My example is as below:

The Table is
desc INV_ITEM_ACTVTY

 Name                            Null?    Type
 ------------------------------- -------- ----
 IA_COMPANY                      NOT NULL NUMBER(2)
 IA_YEAR                         NOT NULL NUMBER(4)
 IA_PERIOD                       NOT NULL NUMBER(2)
 IA_ITEM                         NOT NULL NUMBER(10)
 IA_WRHS                         NOT NULL VARCHAR2(5)
 IA_BATCH_NO                     NOT NULL VARCHAR2(25)
 IA_OB_QTY                       NOT NULL NUMBER(12,2)
 IA_OB_COST                      NOT NULL NUMBER(12,2)
 IA_MNTH_QTY                     NOT NULL NUMBER(12,2)
 IA_MNTH_COST                    NOT NULL NUMBER(12,2)

Trigger

CREATE OR REPLACE TRIGGER "AIU_INV_ITEM_ACTVTY"
    AFTER INSERT OR UPDATE
    ON "INV_ITEM_ACTVTY"
    REFERENCING OLD AS OLD NEW AS NEW
    FOR EACH ROW

DECLARE
  t_cur_prd  NUMBER(6);
  t_nxt_year NUMBER(4);
  t_nxt_prd  NUMBER(2);
  t_qty      NUMBER(12,2):=0;
  t_cost     NUMBER(12,2):=0;

BEGIN
  select to_char(sysdate,'yyyymm') into t_cur_prd from dual;

  IF :new.ia_period < 12 THEN
     t_nxt_year := :new.ia_year;
     t_nxt_prd  := :new.ia_period + 1 ;
  ELSE
     t_nxt_year := :new.ia_year + 1;
     t_nxt_prd  := 1 ;
  END IF;

  t_qty  := :new.ia_ob_qty  + :new.ia_mnth_qty;
  t_cost := :new.ia_ob_cost + :new.ia_mnth_cost;

  IF (:new.ia_year*100+:new.ia_period) <= t_cur_prd THEN
   BEGIN
     update inv_item_actvty
        set ia_ob_qty   = t_qty,
            ia_ob_cost  = t_cost
      where ia_company  = :new.ia_company
        and ia_year     = t_nxt_year
        and ia_period   = t_nxt_prd
        and ia_item     = :new.ia_item
        and ia_wrhs     = :new.ia_wrhs
        and ia_batch_no = :new.ia_batch_no ;

     if sql%notfound then
        insert into inv_item_actvty
              (IA_COMPANY, IA_YEAR, IA_PERIOD, IA_ITEM, IA_WRHS,
               IA_BATCH_NO, IA_OB_QTY, IA_OB_COST, IA_MNTH_QTY, IA_MNTH_COST)
        values(:new.ia_company, t_nxt_year, t_nxt_prd, :new.ia_item,
               :new.ia_wrhs,:new.ia_batch_no, t_qty, t_cost, 0, 0)
     end if;
   END;
  END IF;
END;
/

When i issue an update command the following error arise

update INV_ITEM_ACTVTY set ia_mnth_qty=ia_mnth_qty, ia_mnth_cost=ia_mnth_cost
where ia_year=2001 and ia_period=11;

update INV_ITEM_ACTVTY set ia_mnth_qty=ia_mnth_qty, ia_mnth_cost=ia_mnth_cost
       *
ERROR at line 1:
ORA-04091: table INV_ITEM_ACTVTY is mutating, trigger/function may not see it
ORA-06512: at "AIU_INV_ITEM_ACTVTY", line 24
ORA-04088: error during execution of trigger 'AIU_INV_ITEM_ACTVTY'

Am I using the right approach or do you have another suggestion.

Your advice is appreciated.

Regards

          Followup:

You just described an on commit refresh materialized view.  But the summary data
would reside in ANOTHER TABLE.

You cannot update the table upon which this row trigger is firing.  You have a
slight "recursion" issue as well.  You have an update of table T that fires this
trigger -- which in turn tries to update T which would fire the trigger etc...

I would investigate materialized views (lots less code)

else read the link in the above answer and write lots of tricky code.

      Rating: 4
After insert
      trigger doesnot give latest date  April 09, 2003

Reviewer:  Anil  from Malaysia
Dear Tom,
I am working in a datawarehouse application. I am having a TRANSACTIONS table.
TRANSACTIONS table is loaded thru sql*loader

After every daily run i need to fire a query for which output should go to a
text file.

For example structure of TRANSACTIONS TABLE is
Busdate  date,
Acctno   varchar2(40)

I created a trigger as follows

create or replace trigger amla
after insert on transactions
declare
pragma autonomous_transaction;
l_rows number;
busdate date;
begin
select max(busdate) into busdate from transactions;
l_rows := dump_csv('select * from transactions where busdate=(select
max(busdate) from transactions)','c:\oracle\admin\dwdev\udump','abc'||busdate);
dbms_output.put_line(to_char(l_rows)||' rows extracted to ascii file');
end;
/

My problem is that i am getting one day late data. If the data is loaded for
10-apr , i get the text file for 9-apr, similarly if the data is loaded for
11-apr i get the text file for 10-apr.

When i put after insert trigger, it should fire after the insert is over in the
table. If the data load is over for 10-apr, trigger should fire , get the max
date as 10-apr and give the text file for 10-apr.

Can u please what is wrong in the trigger and suggest a workaround.

Regards,
Anil

          Followup:

thats funny.

I write about this in my book -- you used an automous transaction for some
reason.   The autonomous transaction has very very very few uses (and this is
not one of them)

It is "as if" you did "host( 'sqlplus scott/tiger @dump_table.sql' )" from your
trigger.  your trigger is running in totally separate transaction from the
parent transaction that fired it.  Hence, just like that sqlplus session would

a) not be blocked by your uncommitted modifications
b) not be able to SEE your uncommitted modifications

your trigger cannot see them either.  Consider:

ops$tkyte@ORA920> create table t as select * from all_users where 1=0;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t select * from all_users where rownum = 1;

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> variable atrans refcursor
ops$tkyte@ORA920> variable sameTrans refcursor
ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          PRAGMA autonomous_transaction;
  3  begin
  4          open :atrans for select * from t;
  5  end;
  6  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2          open :sameTrans for select * from t;
  3  end;
  4  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> print atrans

no rows selected

ops$tkyte@ORA920> print sameTrans

USERNAME                          USER_ID CREATED
------------------------------ ---------- ---------
SYS                                     0 07-MAR-03

You did it to yourself!

Also, considering that sqlldr BATCH inserts and commits -- your trigger is
getting fired for each BATCH.  do you really want that -- do you want to run
this over and over and over and over and over?  (or just once).

So, ask yourself "what was my intention with this autonomous transaction thing,
why did I put that in there??"

second question "do i really want to dump the table over and over and over -- or
just once, after the load"

Here is a suggestion -- it seems you have a file you loaded, you seem to be
dumping that data right back out.  Why wouldn't you just hang onto the file you
loaded???

Suggestion 2 -- lose the trigger, modify the script that calls sqlldr right now
to have a call to sqlplus to dump the file instead of a trigger.  It gets done
ONCE that way.

      Rating: 5
Autotomous
      triggers...  April 09, 2003
Reviewer:  Tony
      Andrews  from London, UK
If I had a penny for every time I've seen this lately on forums...

Q: I'm getting mutating error in trigger - help

A: Use PRAGMA AUTONOMOUS_TRANSACTION

...I'd have a few quid!

          Followup:

pragma autononous_transaction in a trigger to avoid mutating table issues almost
certainly implies "you've a bug in your code"

It is a useful tool in the hands of those that understand transactions and what
they mean and how they work...

funny thing on the above trigger is that they couldn't have been hitting a
mutating trigger error even, its a STATEMENT trigger!

      Rating: 5
After insert
      trigger doesnot give latest date   April 09, 2003

Reviewer:  Anil  from Malaysia
Please refer to the above problem regarding TRANSACTIONS table and TRIGGER amla.

You are absolutely right, i forget the consideration that sqlldr will insert in
batches and trigger will fire after every commit.

In fact the load is about 1 million rows for this table.

In our datawarehouse we load several tables. I had to run a query on
TRANSACTIONS table after every daily run. TRANSACTIONS table has a busdate which
signifies loaddate. I can do it only with a trigger so that after all the 1
million rows are inserted the query runs and gives output to a text file.

I can't use dbms_job as the time of load is not fixed. If i make the query time
based, then i have a problem. So i thought of putting a trigger so that after
the load is successful the query runs automatically and put the output in a text
file irespective of the time when the load was done.

Can u suggest some workaround. I am using oracle 8.1.7

Regards,
Anil

          Followup:

well, first -- I think you missed the nugget that "Hey, you already have the
data unloaded, no need to unload -- you are loading it right?  just save that
file".

Short of that...  something is running sqlldr.  Modify this something to have a
call to sqlplus AFTER the load.  There is no such thing as an "after sqlldr
trigger" -- sqlldr is just a client application, no different then SQLPLUS as
far as we are concerned.  Modify the LOAD program to dump the data you just
loaded to disk once again (seems pointless tho -- "hey, take this file, load it,
unload it")

      Rating: 3
connecting
      pooling in web environment.  October 03, 2003

Reviewer:  Tommy  from Canada
Just wonder if the state package will cause any issue in web enviornment when
connection pooling is enabled.

          Followup:

no, we reset it properly before/after statement execution.

      Rating: 3
connecting
      pooling in web environment  October 03, 2003

Reviewer:  Tommy  from Canada
Are you refering to the use of avoid_recursion flag? Can you elaborate more?

          Followup:

No, I mean my entire state is set properly so that each trigging DML operation
works in a "pristine" state -- even within a session -- we reset my packages
entire state.  So, since it works in a session from call to call (remenants of
DML-1 are not visible to DML-2 in a single session) it naturally works across
"connection pool sessions"

      Rating: 4
Q on Mutationg
      Trigger  October 09, 2003
Reviewer:  Anil
      Pant  from Hyderabad, India
Hello,
This question may be a repitative one. If so pls give me the link so that I can
go thru.
Here Im not trying to compare SQL Server and Oracle. Since I've worked on both
Im bit curious. I always wondered why in Oracle trigger we've mutating concept
and same thing we dont have something like that in SQL Server.
But I liked the Oracle way. Infact in one of project I was on SQL Server I was
trying to debug someone's code and it took me almost a day to find out why value
was changing then I learnt that in SQL Server you can change the column value in
trigger written on that table.
Is there any other RDBMS which has this concept or its only in Oracle ? Also why
Oracle has given this ?I think it wud be false if I say it as a restriction but
we should call it an an feature.

          Followup:

sqlserver doesn't have row triggers -- hence they solved the mutating table
constraint by simply not have a feature that would necessitate them thinking
about it.

In Oracle you may certaining change a value in a BEFORE FOR EACH ROW trigger as
well.

  :new.column := 'hello world';

it is even easier then in sqlserver -- we don't have to update the table, we
just set the value naturally.

It is a feature, it is there for your protection.  The order of operations in
SQL is not defined at all and can change as the query plan changes for a given
dml statement.  Hence, from run to run against the same data you would see a
different result in the database if we removed "mutating tables"

      Rating: 5
Is that really
      a procedure returning a value, or is it a function?  October
      25, 2003
Reviewer:  Tom  from Seattle WA USA

At the very beginning you said:
"Can your logic be as simple as:
begin
   :new.processed_status := some_procedure( :new.id,
                                   :new.processed_status );
end;
/"
But a procedure's RETURN cannot, I understand, return a value, so is
some_procedure() technically a function, or could it somehow set
:new.processed_status using an OUT parameter, or do I misunderstand
:new.processed_status is being set to the return value from a procedure here?

          Followup:

picky picky :)

I call procedures and functions by those names interchangeably.  yes, there are
procedures which do not return a value.  yes there are functions which can.

x := some_function(y,z);

would be technically more accurate.  I could call them subroutines as well but
that would date me to a degree.  Maybe I'll call them "methods", then it would
be "cool" ;)

the only difference between a procedure and a function in PLSQL is that a
function is just like a procedure with the last parameter being an OUT.  there
isn't much difference between:

  procedure p( x in number, y in date, z in varchar2, RET_VAL OUT NUMBER );

and

  function f( x in number, y in date, z in varchar2 ) returns number;

      Rating: 4
Concurrent
      updates using the package variable solution?   October 25,
      2003
Reviewer:  Tom Starr  from Seattle WA USA

Isn't concern warranted if multiple users store their update rowids in the same
package variable(s) simultaneously (ridArray, which is flushed before each use
in this case:
http://osi.oracle.com/~tkyte/Mutate/index.html
Or am I missing something fundamental here about scope?
Thanks in advance.
And thanks again for the best web site in the known universe!!

          Followup:

data segments in plsql are just like data segments in real programs.

everyone shared code (text)
no one shares data

every session has its own set of variables. it is "session safe"

      Rating: 5
Hall of
      fame  October 27, 2003
Reviewer:  Tom Starr
       from Seattle WA USA
What you said at the very beginning worked in spades for me.
I cannot describe how good it is to see such simple syntax.
Question: I changed your trigger from a before update,
to before insert or update.
To:
create or replace trigger T_trigger
before INSERT OR UPDATE on T for each row
begin
 :new.processed_field := a_procedure( :new.read_field);
end;
/
It sure seems to work well, and I have a static Java method which calls
java.net.URLEncoder.encode behind the function.
Should I be concerned?
Thanks again, and again and again.....

          Followup:

i don't see any reason to be "concerned", no

October 27, 2003 Aparajita -- Thanks for the question regarding
      "Row chaining", version 8.1.7

      You Asked
Tom,

Let's say I have a table T with the following columns(field1 to field20). Now,
as part of a requirement I have to add some 20 odd columns(lets' say each
varchar2(15)). What steps I would require to take to find out that the table
doesn't undergo row chaining or migration due to the addition of these new
columns. I am in a dataware house environment with 16k block size. Only inserts
takes place with occasional upate into the table. In case of a backout is it
advisable to drop these added columns or just mark them unused? or re-building
the table using CTAS(create table as). This is a very big table with 100 million
records.

Structure of Table T
======================
Field1    number(16),
Field2    number(10),
Field3    number(6),
Field4    numer(9),
field5    number(9),
field6    number(10),
field7    varchar2(10),
field8    varchar2(7),
field9    number(10),
field10    date,
field11    varchar2(7),
field12    varchar2(30),
field13    varchar2(1),
field14    number(10),
field15    number(10),
field16    number(10),
field17    number(10),
field18    number(10),
field19    number(7),
field20    number(5)

Thanks very much in advance.

      and we said...
if only inserts take place and very few update -- and the newly added columns
are defaulted to NULL and allow NULLs -- migration is not a problem.

Migration would only happening during an update or the addition of a column with
a non-null DEFAULT value (which implies an update)

Addition 20 more columns of 15 bytes each would only add about 400 bytes to each
row and the row is not that wide to begin with so row chaining won't happen
either (that only happens with rows that exceed your block size).

To back out -- I would set unused and if I really felt like it later -- alter
table move to clean them out.

100 million records would not take very long to rebuild actually -- my desktop
pc can do it pretty fast -- a real server would make it take no time at all.
Review &
      Followup

      Rating:
      5
  October 27, 2003
Reviewer:
      A reader
Tom,

Suppose the columns that are being added are updated for all the existing
records (in millions), ...what should i need to do.

one more doubt - the updated row combined with the existing row should exceed
the block size for row migration or the size of new columns that are being
updated and being larger than the block size only amount for the row migration ?

Thanks for your time and consideration.

          Followup:

I would probably "create table new_table as select ......" to get the new
columns added and defaulted.

then drop old_table
then rename new_table to old_table
then index it, grant it, constrain it, etc

if you are using 9i, you can use online redefinition in order to do this while
people are modifying/accessing it even.

row migration happens when you update a row and the row no longer fits on the
block it was on given all of the OTHER rows there.  We move that row to a new
block.  The row itself still fits on a block, just not that one it was
originally on.

      Rating: 5
Thanks
      Tom.  October 27, 2003
Reviewer:
      A reader

October 27, 2003 Jon -- Thanks for the question regarding "Binding
      two arrays", version 8.1.7

      You Asked
Along the lines of using OR techniques to bind a list submitted from the client
into an in clause, what is the best way of achieving this for a two-dimensional
array?

Given two lists (arrays) like '123456,245678' & '3,8', I would like to end up
with the query:

select *
from account
where (account_number,cris_product_id) in
   ((123456,3),(245678,8));

I've come up with this:

select /*+ first_rows */ *
from account
where (account_number,cris_product_id) in
   (select acc_no, cris_prod_id
    from
       (select rownum rn, column_value acc_no
        from table(cast(Str2Tbl('123456,245678') as TIDTab))) x,
       (select rownum rn, column_value cris_prod_id
        from table(cast(Str2Tbl('3,8') as TIDTab))) y
    where x.rn = y.rn);

Some questions:
1. Can I be assured that the use of rownnum will get me the output in the order
corresponding to input, so that the array indices match up?
2. This approach would become a bit cumbersome when we need to extend it to 3 or
more lists - is there a better way to do this in 8.1.7?
3. How would we do this in 9.2?

Thanks!

      and we said...
ops$tkyte@ORA817DEV> create or replace type myScalarType as object
  2  ( x int, y int )
  3  /

Type created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace type myTableType as table of myScalarType
 2  /

Type created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace
  2  function str2tbl( p_str1 in varchar2,
  3                    p_str2 in varchar2 )
  4  return myTableType
  5  as
  6      l_str1   long default p_str1 || ',';
  7      l_str2   long default p_str2 || ',';
  8      l_n1     number;
  9      l_n2     number;
 10      l_data   myTableType := myTabletype();
 11  begin
 12      loop
 13          l_n1 := instr( l_str1, ',' );
 14          l_n2 := instr( l_str2, ',' );
 15          exit when (nvl(l_n1,0) = 0) or (nvl(l_n2,0) = 0);
 16          l_data.extend;
 17          l_data( l_data.count ) :=
 18            myScalarType( ltrim(rtrim(substr(l_str1,1,l_n1-1))),
 19                          ltrim(rtrim(substr(l_str2,1,l_n2-1))) );
 20          l_str1 := substr( l_str1, l_n1+1 );
 21          l_str2 := substr( l_str2, l_n2+1 );
 22      end loop;
 23      return l_data;
 24  end;
 25  /

Function created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select *
  2    from TABLE( cast( str2tbl( '1,2,3','4,5,6' ) as myTableType ) )
  3  /

         X          Y
---------- ----------
         1          4
         2          5
         3          6

ops$tkyte@ORA817DEV>

you could of course use a type that had N columns in it, and the str2tbl
function took N optional inputs to allow you to have between say 1 and 5 output
columns if need be

In 9i, i would change this to be a pipelined function but that is about it.

October 27, 2003 -- Thanks for the question regarding "How to
      refer remote database with out DB link ", version 8.1.7

      You Asked
Hi Tom,

I have the following questions for you.

1. Is there any way i can refer the remote database stored procedures, views and
tables etc like LOCAL database with out database link in the code.

Cause i need to reuse some of the code like back end stored procedures, tables,
views etc in other database with out duplicating it in Local database.

2. The same way, i need to make a foreign key constraint relationship
from the local database table to a remote database table.

Please kindly let me know if this would be possible and if yes, how should i do
it.

Thanks a lot.

      and we said...
1) synonyms and views provide for location transparency.  You would create a
synonym for remote procedures, i live views for remote tables/views.

2) that will NEVER happen and does not make sense.  If you have such a tight
dependency -- you are basically saying you need ONE database.

There is no chance for distributed referential integrity.  Think about it -- the
inavailability of one database makes the OTHER database unavailable as well --
you cannot do that.
Review & Followup

      Rating: 5
Thank you so
      much  October 27, 2003
Reviewer:
      A reader
Tom,

Your answers are always great.

      Rating:
      5
  October 27, 2003
Reviewer:
      A reader

October 27, 2003 Saminathan -- Thanks for the question regarding
      "Ignore Case function in Oracle", version 8.1.7.3

      You Asked
Dear Tom,

I hope oracle does not have IgnoreCase function to look for both uppercase and
lowercase strings(stored in varchar2 column).

The requirement is that end-user(web-based) wants to store the data in their
preferred way (few of them like UPPER and few of them like lower or InitCap). We
cannot do any case conversion because next time when the end-user wants to view,
it should be displayed exactly how they typed.

The problem comes when we do search on those columns.

We decided to store user's original entry in one column and UPPER(userentry) in
another column. yes, redundancy.

Could you please provide us the better solution? Or do you have any customized
script to ignore case?

Thanks in advance
Sami

      and we said...
create index t_idx on t(upper(ename));

and then

select * from t where upper(ename) = upper(:bind_variable);

See

http://asktom.oracle.com/~tkyte/article1/index.html
for further details
Review & Followup

      Rating:
      5
  October 27, 2003
Reviewer:
      A reader

      Rating: 5
Also I found
      more detailed info at   October 27, 2003
Reviewer:
      A reader
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1560806455855

October 27, 2003
      -- Thanks for the question regarding
      "HTMLDB forums availability", version 9.2.0.4

      You Asked
Hi Tom,
I know this is not the right forum to post this question, but since HTMLDB is
not supported yet by Oracle, I cant ask this to any support person. Wonder if
you could help me out on this.

Since the last 2 to 3 days I dont see a link to the HTMLDB forums at
marvel.oracle.com.
Has it moved to someplace else, or down for some other reason. It was an
invaluable resource for people like me who are seriously considering porting
applications as soon as it goes Production.

Thanks Tom as always, this site is awesome!!!!!

Satish

      and we said...
it is on OTN in the discussion forums

https://forums.oracle.com/forums/forum.jsp?forum=137

Review &
      Followup

      Rating: 1
Is there a
      problem with forums on Technet?  October 27, 2003

Reviewer:  A reader
I have been having a lot of problems getting into the technet forums lately. I
also see on your home page that you mention that OTN has moved all the
documents.

Can you shed any light on this, or can you suggest who this should be reported
to?

Thanks

          Followup:

there is a contact us link on their site?  I'm not part of OTN really.

      Rating: 1
What is the
      problem with Oracle Forums?  December 09, 2003

Reviewer:  A reader
Can you shed any light into why the forums is not working so much?

October 27, 2003 Wolfgang -- Thanks for the question regarding
      "Two semantically equivalent sql, which one is preferrable", version 8.1.7

      You Asked
Using your test harness I was executing two different sql which produce the same
result. I was repeating each sql 10 times to reduce the effect of random
fluctuations:

truncate table run_stats;

exec runstats_pkg.rs_start;

select a.setid, 'TTS',1
from ps_setid_tbl a
   , ps_set_cntrl_tbl b
where a.setid = b.setcntrlvalue (+)
  and b.setcntrlvalue is null
/
/
/
/
/
/
/
/
/
/

exec runstats_pkg.rs_middle;

select a.setid, 'TTS',1
from ps_setid_tbl a
where not exists (select null from ps_set_cntrl_tbl b where a.setid =
b.setcntrlvalue)
/
/
/
/
/
/
/
/
/
/

exec runstats_pkg.rs_stop;

Name                                Run1      Run2      Diff
LATCH.shared pool                     84        83        -1
STAT...calls to get snapshot s        13        12        -1
STAT...db block gets                  80        81         1
STAT...recursive cpu usage             4         5         1
LATCH.redo writing                     0         2         2
STAT...calls to kcmgcs                 4         6         2
STAT...redo entries                    9        11         2
STAT...CPU used by this sessio         9        12         3
STAT...CPU used when call star         9        12         3
STAT...enqueue releases                5         2        -3
LATCH.enqueue hash chains              6         2        -4
LATCH.library cache                  460       456        -4
LATCH.messages                         0         4         4
STAT...enqueue requests                6         2        -4
STAT...free buffer requested           5        11         6
LATCH.enqueues                        13         5        -8
STAT...consistent changes             37        47        10
STAT...db block changes               47        59        12
LATCH.checkpoint queue latch           1        16        15
STAT...bytes received via SQL*     3,138     3,106       -32
STAT...redo size                   2,124     2,224       100
STAT...buffer is not pinned co       104     4,544     4,440
STAT...buffer is pinned count      4,440         0    -4,440
STAT...no work - consistent re       202     4,642     4,440
STAT...consistent gets             4,758     9,199     4,441
STAT...session logical reads       4,838     9,280     4,442
LATCH.cache buffers chains         5,210    14,055     8,845

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
5,868    14,717     8,849  39.87%

I would intuitively prefer version 2. It conveys more clearly, at least in my
view, the answer to the problem: Produce a list of setids that are missing from
set_cntrl_tbl. However, version 2 does more consistent gets and therefore more
logical reads and especially more cache buffer chains latch requests. Maybe it's
time to let go of my dislike of outer joins.

      and we said...
Mmm, me -- I like NOT IN :)

I used this example in my latest book.  In addition to sounding really cool
"hash anti joins" are great for this type of processing

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7565644027229
I'd give NOT IN a chance -- but never be afraid of an outer join -- using the
outer join as you did is a way to trick the RBO into doing almost the equivalent
of a hash anti join, something only the CBO truly knows how to do right.
Review &
      Followup

      Rating: 3
Some more
      tests  October 27, 2003
Reviewer:  Wolfgang
      Breitling  from Calgary, Canada
OK, I ran the comparison to a "not in". It's essentially equal to the not
exists:

truncate table run_stats;

exec runstats_pkg.rs_start;
set termout off

select a.setid, 'TTS',1
from ps_setid_tbl a
   , ps_set_cntrl_tbl b
where a.setid = b.setcntrlvalue (+)
  and b.setcntrlvalue is null
/
/
/
/
/
/
/
/
/
/

exec runstats_pkg.rs_middle;

select a.setid, 'TTS',1
from ps_setid_tbl a
where a.setid not in (select b.setcntrlvalue from ps_set_cntrl_tbl b)
/
/
/
/
/
/
/
/
/
/

set termout on
exec runstats_pkg.rs_stop;

PL/SQL procedure successfully completed.

Run1 ran in 7 hsecs
Run2 ran in 10 hsecs
run 1 ran in 70% of the time

Name                                Run1      Run2      Diff
STAT...calls to get snapshot s        13        12        -1
STAT...db block gets                  80        79        -1
STAT...parse time cpu                  1         0        -1
STAT...parse count (hard)              2         1        -1
STAT...parse time elapsed              1         0        -1
STAT...calls to kcmgcs                 4         6         2
STAT...redo entries                    9        11         2
STAT...sorts (memory)                  6         4        -2
STAT...enqueue releases                5         2        -3
LATCH.enqueue hash chains              6         2        -4
STAT...sorts (rows)                3,141     3,137        -4
LATCH.library cache                  600       596        -4
STAT...enqueue requests                6         2        -4
STAT...free buffer requested           5        10         5
STAT...CPU used by this sessio         8        14         6
STAT...CPU used when call star         8        14         6
LATCH.enqueues                        13         5        -8
STAT...consistent changes             37        47        10
STAT...db block changes               47        58        11
LATCH.shared pool                    144       125       -19
STAT...redo size                   2,124     2,160        36
LATCH.row cache objects               63        15       -48
STAT...bytes received via SQL*     3,138     2,926      -212
STAT...buffer is not pinned co       104     4,544     4,440
STAT...session logical reads       4,838     9,278     4,440
STAT...no work - consistent re       202     4,642     4,440
STAT...buffer is pinned count      4,440         0    -4,440
STAT...consistent gets             4,758     9,199     4,441
LATCH.cache buffers chains         5,205    14,055     8,850

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
6,110    14,877     8,767  41.07%

PL/SQL procedure successfully completed.

However, taking your comment about the coolness of hash anti join, if I set
always_anti_join = hash (or use a hash_aj hint) the not in is significantly
leaner on resources - albeit not faster (the tables are rather small and the
times miniscule so any random fluctuations are magnified):

truncate table run_stats;

alter session set always_anti_join=hash;

exec runstats_pkg.rs_start;
set termout off

select a.setid, 'TTS',1
from ps_setid_tbl a
   , ps_set_cntrl_tbl b
where a.setid = b.setcntrlvalue (+)
  and b.setcntrlvalue is null
/
/
/
/
/
/
/
/
/
/

exec runstats_pkg.rs_middle;

select a.setid, 'TTS',1
from ps_setid_tbl a
where a.setid not in (select b.setcntrlvalue from ps_set_cntrl_tbl b)
/
/
/
/
/
/
/
/
/
/

set termout on
exec runstats_pkg.rs_stop;

Session altered.

PL/SQL procedure successfully completed.

Run1 ran in 7 hsecs
Run2 ran in 11 hsecs
run 1 ran in 63.64% of the time

Name                                Run1      Run2      Diff
LATCH.checkpoint queue latch           1         0        -1
LATCH.shared pool                    104       103        -1
STAT...calls to get snapshot s        13        12        -1
STAT...recursive cpu usage             4         3        -1
STAT...calls to kcmgcs                 4         6         2
STAT...redo entries                    9        11         2
STAT...enqueue releases                5         2        -3
LATCH.enqueue hash chains              6         2        -4
STAT...CPU used by this sessio         5         9         4
STAT...CPU used when call star         5         9         4
STAT...enqueue requests                6         2        -4
LATCH.library cache                  525       531         6
STAT...free buffer requested           5        11         6
LATCH.enqueues                        13         5        -8
STAT...consistent changes             37        47        10
STAT...index fast full scans (        10        20        10
STAT...db block changes               47        59        12
STAT...db block gets                  80       121        41
STAT...no work - consistent re       202       132       -70
STAT...buffer is not pinned co       104         4      -100
STAT...redo size                   2,124     2,224       100
STAT...bytes received via SQL*     3,138     2,926      -212
STAT...buffer is pinned count      4,440         0    -4,440
STAT...session logical reads       4,838       260    -4,578
STAT...consistent gets             4,758       139    -4,619
LATCH.cache buffers chains         5,210       565    -4,645

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
5,953     1,300    -4,653 457.92%

PL/SQL procedure successfully completed.

I don't want to overload this thread with too much output, but I found that only
"not in" reacts to the hash_anti_join setting or hint, not the original outer
join query nor the "not exists".

          Followup:

in 9i -- it goes quite naturally without any prompting.

      Rating: 3
query unnesting
      analysis is difficult  October 27, 2003
Reviewer:
      Mikito Harakiri
Is plan different in both cases? I suggest putting "nomerge" hint in order to
eliminate possibility that  optimiser is transforming one query to another.

October 27, 2003 sandeep -- Thanks for the question regarding
      "locally management tablespaces", version 8.1.6

      You Asked
Goodevening tom !

create table fact_actuals
(
    Time_id            VARCHAR2(16)    ,
    Channel_id        VARCHAR2(16)    ,
    Item_id            VARCHAR2(16)    ,
    Route_id        VARCHAR2(16)    ,
    Salesman_id        VARCHAR2(16)    ,
    Customer_id        VARCHAR2(20)    ,
    Wholesaler_id        VARCHAR2(20)    ,
    Disc_prog_id        VARCHAR2(16)    ,
    Gross_sales_raw        NUMBER(15,3)    ,
    Gross_sales_8oz        NUMBER(15,3)    ,
    Free_cases_raw        NUMBER(15,3)    ,
    Free_cases_8oz        NUMBER(15,3)    ,
    Gross_sales_amount    NUMBER(15,3)    ,
    Free_cases_amount    NUMBER(15,3)    ,
    Discount_amount        NUMBER(15,3)    ,

on the above table containing 1 million rows suppose i write group by query,
group by on all id columns. All id columns contains a fewer different values
(for ex. channel could be either country, region or area) resulting into 25000
rows
1. I have created a locally managed TEMPORARY tablespace of size 500m WITH
uniform extent size 2M is it sufficient ?
2. Also the create temporary tablespace command did not allow me to specify
extent management local autoallocate. Autoallcation is not allowed for temporary
tablespace ?
3. How the extent management happens in temporary tablespaces (locally managed
and dictionary managed ) when the extents are deallocated ?
4. Can we create rollback tablespace/segments using locally managed tablespace
feature ? What are advantages of Locally managed tablespace over Dictionary
managed ?

      and we said...
1) you should set the extent size on your temporary tablespaces to be in line
with your SORT_AREA_SIZE setting.  We "page" to temp in that size.

500m should be sufficient for that query in isolation -- if you have 1,000 users
running it that would be different.

2) correct, temp tablespaces should have fixed sized extents.  Autoallocate
would not make sense since extents, once allocated, are not freed -- the system
manages them internally.  You would have alot of different sized extents being
managed and that would just not be a good thing.  They should be the same size
and should be the sort_area_size.

3) The extents are not deallocated.
see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:374218170986
4) yes you can.

I always hated pctincrease and having initial set different from next.  I always
liked have pctincrease = 0, initial=next.  A locally managed tablespace with
uniform extents makes that the "de-facto" rule, you cannot subvert it as you can
with a dictionary managed tables.  It totally avoids fragmentation -- it is
impossible to have fragmentation in these tablespaces.

Locally managed tablespaces are much faster at allocating and de-allocating
extents - many order of magnitudes faster.

locally managed tablespaces decrease contention on the data dictionary.

There is nothing bad about them, only good things.
Review &
      Followup

      Rating: 4
about locally
      managed tablespaces  March 27, 2001
Reviewer:
      sandeep ketkar  from Pune (INDIA)
Thankx tom, this is very useful for us.

      Rating: 5
question
      4  March 27, 2001
Reviewer:  Helena Markova
       from Bratislava, Slovakia

      Rating: 4
always wanted
      to understand the difference :)  September 22, 2001

Reviewer:  Olga  from Canada
Very useful, thanks

      Rating:
      5
  October 16, 2001
Reviewer:  Omer
      Zenciroglu
perfect!

      Rating: 4
How do I find
      the datafiles attched to a temporary tablespace  April 02,
      2002
Reviewer:  She  from CT, US
How do I find the datafiles attched to a temporary tablespace (locally managed)

          Followup:

see dba_TEMP_files if you created a temporary tablespace with TEMPfiles.  See
dba_DATA_files otherwise.

      Rating: 5
Excellent
      explanations  April 02, 2002
Reviewer:
      A reader

      Rating:
      4
pctincrease  April 02, 2002
Reviewer:
      Rajiv  from nyc
Tom:
I have hardly seen anyone advocating for a non-zero value for PCTINCREASE
parameter. Is there any specific situations which could benefit from a non zero
pctincrease. Could you please explain the rationale of creation of this
parameter in Oracle originally?

Thanks.

          Followup:

system tablespace which are dictionary managed in the days when 121 extents was
the max if you picked a 2k blocksize.

thats about it.  You don't manage system, you don't allocate stuff in system and
you never drop stuff in system.  Hence, having different sized extents is OK
(its a problem when you drop/truncate in system and you don't do that).

That should help explain the rationale as well - for a very long time, there was
a hard limit on the  number of extents, so a pctincrease made a lot of sense to
ensure you wouldn't hit max extents (requiring a rebuild)

      Rating: 4
when LMT
      useful  April 03, 2002
Reviewer:  lakshmi
      narasimhan R  from dubai, UAE
hi tom

In what circumstances a Locally managed table space
will an overhead.  If you have a table which is having
millions of rows. but inserts and updates are very low(data downloads always
through interfaces) compared to Query, then having LMT is advisable.

          Followup:

I know of NO circumstance when an LMT is not superior to a DMT.

In 9i, they are the default type of tablespace.
In 9iR2 -- system will be LMT as well, completing the picture.

      Rating: 4
Bugs with
      rollbacks?  April 03, 2002
Reviewer:  Simon
      Holt  from Dublin, Eire
Locally managed tablespaces are without doubt one of the best inventions since
sliced bread, and have saved me and my clients a lot of time and effort
recently.

However, I have read on Metalink of a couple of bugs relating to placing
rollback segments in locally managed tablespaces in 8i; sample text follows:

----------------Begin included text (Metalink Forum entry)--------
Also, be aware that there are some outstanding bugs with rollback segments in
locally
managed tablespaces. Bug 1176609 was filed because the creation of rollback
segments
in a locally managed tablespace fails with an ORA-1552 if another non-system RBS
doesn't
exist/online in a dictionary managed tablespace. Actually if you do not have a
non-system
rollback segment in a dictionary managed tablespace, you cannot create any new
objects.
This bug is unresolved because there is a workaround available.

Bug 1021631 causes the
server session to crash when attempting to drop a rollback segment in a locally
managed
tablespace. This bug is fixed in release 9i.

Melissa Holman
Oracle Support

-----------End included text---------------------

Now, the first bug is a bit of a trivial problem because there is a workaround
for it (an annoying one, but a workaround, nonetheless).

The second one (1021631) is more concering, because I do not want to be in a
position where I cannot drop a rollback segment for some reason. Therefore, I've
recommended that we build our databases using dictionary management for rollback
tablespaces. I always make sure that the storage parameters are sensible eg
(INITIAL 1M NEXT 1M MINEXTENTS 20), so the segments are well behaved!

Do you have any views on this?

          Followup:

How often do you drop a rollback segment??

but even so, to reproduce

...
Offline all the rollback segments in the database. (only SYSTEM is up)
drop rollback segment p;
.....

you would not be offlining all rollback segments to drop one (maybe in a TEST
machine you might in order to rebuild all of them for some reason but in PROD --
never)

I've been using LMTs since the day after they came out -- I've never hit any
issues with regards to them -- particularly with regards to my rbs's.

Ultimately -- it is your choice of course.

      Rating: 5
Temp Space
      Free  April 03, 2002
Reviewer:  Randy
      Richardson  from St. Louis, MO
Tom,

Can you please explain when extents in the temp space are freed.  I was under
the impression that it was transaction based.  Once a transaction is committed.
That extent is not de-allocated but free to be used by another session.

We have a system that has a 6 GIG temp tablespace.
3-teir with 6 application servers (JDE)
500 connections
20-40 active sessions at a time.
Oracle 8.1.7

With the temp tablespace as type temporary.  The space
gradually fills up till we get the error Unable to allocate extent in temp.

With type permanent, its almost always empty.  The most I have seen in a one
time is ~ 800 meg.

Which leads me to think that the temp segments stay locked for that session, not
the transaction.

Could you clarify, Thanks for all the help you provide!!

          Followup:

Use v$sort_usage and v$sort_segment on a true temporary tablespace to see what's
what.  I've never seen what you describe above personally.  Using those dynamic
views, you'll be able to see whats what.

sort space is released when you are done with it, not at the session level.

      Rating: 5
locally
      management tablespaces  April 16, 2002
Reviewer:
      Scott  from Rochester, New York
Thank you for the detailed description, very useful.

      Rating: 5
Clarification
      on pctincrease  July 18, 2003
Reviewer:  Bharath
      Kumar  from India
Hi Tom,

You have been advocating a 0 pctincrease on dictionary managed tablespaces. I
have a friend who once said "keep intial=next and pctincrease of 1. It is as
good as zero and also makes sure that freespace coalesce happens automatically".
Is this true ? Please clarify.

Thanks

Bharath

          Followup:

it is a really utterly bad idea.

if initial = next and pctincrease = 0, you NEVER NEVER need to coalesce - all
extents are the same size.

if you set it to 1, they are all different and you have swiss cheese.

      Rating: 5
another
      argument for LMT temporary tablespaces  July 18, 2003

Reviewer:  Jeni  from Omaha, NE USA
Excellent advice, Tom -- I've had the pctincrease/extent size debate with
others, and I believe you are 100% correct.
I too have learned to love LMTs; took us over 2 hours one night to start an
instance (after having to abort it to get it down) -- the vendor-installed db
had tiny extent size in the dictionary-managed temp ts -- grew to over 49K
extents!  Since the sort_segment is dropped on clean shutdown or on startup
after crash or shutdown abort, the dictionary had to mark those 49K extents as
free, and it just couldn't deal with it.  We even tried to offline drop the ts
-- no luck; TAR analyst said we'd just have to wait.  We switched to LMT, and
don't have to worry about repeating that experience.

      Rating: 3
My arguments
      for LM temporary tablespace  July 23, 2003

Reviewer:  Herman Mamontov  from Miami, FL USA

1. Only in LM temporary tablespace you can use temfiles. So, you can add/drop
files without the tablespace being recreated. This feature is very useful.
2. RMAN doesn't backup temp files. Im my case it's about ~100Gb and it saves me
time and tapes.

      Rating: 5
maxextents
      unlimited  July 26, 2003
Reviewer:  Reader

Tom, If I set maxextents unlimited on all of my tables in LMT and manage space
at tablespace level, will there be any performance implication with regard to
extent space management at the bitmap level? because the bitmap does not know
max number of extents.
Even though for extent allocation and deallocation, data dictionary tables are
not updated for LMT, data dictionary has to keep track of how many extents a
table has at any point of time so some base tables need to be updated. still
some overhead in using LMT? Thanks.

          Followup:

in an LMT you have no choice, maxextents is unlimited, period (well the
exception is rollback segments in an lmt, they are stopped at 32k extents)

so, moot sort of question, it is already happening that way and there is no way
NOT to have it happen that way.

LMT's are the way to go.  period.

      Rating: 4
Localy manage
      System Tablespace  July 28, 2003
Reviewer:
      Js  from INDIA
hi Sir ..
what is reason behind this ....

-----------------------------------------------------
When the SYSTEM tablespace is locally managed, you must define a default
temporary
tablespace when creating a database.
A locally managed SYSTEM tablespace cannot be used for default temporary
storage.
------------------------------------------
In a database with a locally managed SYSTEM tablespace, dictionary tablespaces
cannot be created.
It is possible to plug in a dictionary managed tablespace using the
transportable feature, but
it cannot be made writable.

      Rating:
      4
maxextents  July 28, 2003
Reviewer:
      Reader
<quote>in an LMT you have no choice, maxextents is unlimited, period <quote>

so, one cannot specify maxextents 300 for example for tables in LMT? I did not
know this. By default maxextents is unlimited for segments created in LMT?
Thanks.

          Followup:

well, to be picky about it -- you can specify it:

ops$tkyte@ORA920> create table t ( x int ) storage ( maxextents 300 );

Table created.

but it is totally ignored

ops$tkyte@ORA920> select max_extents from user_tables where table_name = 'T';

MAX_EXTENTS
-----------
 2147483645

      Rating:
      5
THANKS  July 28, 2003
Reviewer:
      Reader

      Rating:
      5
segments  October 01, 2003
Reviewer:
      A reader
Tom,

My segments in autoallocate LMTs are set to:

MAX_EXTENTS
-----------
 2147483645

I try to set them to UNLIMITED by I get:
ORA-25150: ALTERING of extent parameters not permitted

Database limits in the Reference Guide say segments can be UNLIMITED.

You mean they are unlimited, although max_extents is set as above ? Why is this
number there ?

          Followup:

that is for all intents and purposes "unlimited".  that number means "no limit"

      Rating:
      5
tablespace  October 01, 2003
Reviewer:
      A reader
Now, I tried to set the LMT tablespace's datafile autoextensibility to unlimited
and I got (pay attention to MXBYTES):

SQL> exec print_table ('select * from dba_data_files where
tablespace_name=''USERS''')
------------------------------
FILE_NAME                     : /ora/92/oradata/oraprd/users01.dbf
FILE_ID                       : 9
TABLESPACE_NAME               : USERS
BYTES                         : 2092957696
BLOCKS                        : 255488
STATUS                        : AVAILABLE
RELATIVE_FNO                  : 9
AUTOEXTENSIBLE                : YES
MAXBYTES                      : 34359721984
MAXBLOCKS                     : 4194302
INCREMENT_BY                  : 1280
USER_BYTES                    : 2092892160
USER_BLOCKS                   : 255480
------------------------------

Sould I also understand it as "unlimited" ?

          Followup:

that is 32gig, probable maximum for your OS.

      Rating: 5
more
      info  October 27, 2003
Reviewer:  Vrajesh
      Shah  from USA
I understood that the datafile size should be
 64K + size of the extent *N like
If you create a locally managed tablespace with uniform size
5m -- make sure every datafile is sized to be:

  64k + 5m * N

If we used Auto segment management with uniform extent what is the additional
overhead assococited with the auto segment management? It Should be 64K for
extenet management + 64K for segmant management?

          Followup:

for those files -- just "size it", the extents will not all be the same size so
this extent issue doesn't come into play really.

If you want 5m of space, 5m+64k would be appropriate (or just ignore the 64k
"overhead" as that is the size of all initial extents and just sort of gets lost
in the "noise" there)

October 27, 2003 E.J. -- Thanks for the question regarding "Rows
      Affected?", version 7.3.4.2

      You Asked
Is there any way to get the number of rows affected by a query
within a procedure or function?  Basically I'd like to know how
many rows were updated, inserted, selected or deleted by the
previously executed statement in a PL/SQL block.

I believe in Sybase/SQL Server the global variable @@rowcount
does this.

Thanks,
E.J. Wilburn

      and we said...
In PLSQL, you can use the builtin

sql%rowcount

to get the number of rows affected.  For example:

tkyte@ORA734.US.ORACLE.COM> begin
  2  update emp set ename = lower(ename);
  3  dbms_output.put_line( sql%rowcount || ' rows updated' );
  4  end;
  5  /
14 rows updated

PL/SQL procedure successfully completed.

Now, that won't work for a SELECT, the SQL%rowcount works only
for inserts/updates/deletes.  To do a select, we need a cursor.
Here are examples:

tkyte@ORA734.US.ORACLE.COM> begin
  2      for x in ( select * from emp )
  3      loop
  4          dbms_output.put_line( sql%rowcount || ' rows
selected' );
  5      end loop;
  6  end;
  7  /
rows selected
rows selected
....
rows selected

PL/SQL procedure successfully completed.
that just shows an implicit cursor will not work.  We need to
use an explicit cursor to get the rowcount:

tkyte@ORA734.US.ORACLE.COM> declare
  2      cursor c1 is select empno from emp;
  3  begin
  4      for x in c1
  5      loop
  6          dbms_output.put_line( c1%rowcount || ' rows
selected' );
  7      end loop;
  8  end;
  9  /
1 rows selected
2 rows selected
3 rows selected
...
14 rows selected

PL/SQL procedure successfully completed.

In the above example, c1%rowcount is only valid INSIDE the
loop.  If you need the "grand total" outside of the loop -- you
must be explicit -- so you control when the cursor is closed:

tkyte@ORA734.US.ORACLE.COM> declare
  2      cursor c1 is select empno from emp;
  3      l_empno number;
  4  begin
  5      open c1;
  6      loop
  7          fetch c1 into l_empno;
  8          exit when c1%notfound;
  9      end loop;
 10      dbms_output.put_line( c1%rowcount || ' total rows
selected' );
 11      close c1;
 12  end;
 13  /
14 total rows selected

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating: 5
attention at
      ROWCOUNT with SELECT  August 20, 2001
Reviewer:
      Franz Holzinger  from Germany
In the "PL/SQL User’s Guide and Reference" Chapter 5-36 there is a wrong
example:

LOOP
FETCH c1 INTO my_ename, my_deptno;
IF c1%ROWCOUNT > 10 THEN
...
END IF;
...
END LOOP;

I tried this without success. Now I know why.

          Followup:

What is wrong with that?  My example above:

tkyte@ORA734.US.ORACLE.COM> declare
  2      cursor c1 is select empno from emp;
  3  begin
  4      for x in c1
  5      loop
  6          dbms_output.put_line( c1%rowcount || ' rows selected' );
  7      end loop;
  8  end;
  9  /
1 rows selected
2 rows selected
3 rows selected
...
14 rows selected

PL/SQL procedure successfully completed.

shows that C1%rowcount works -- SQL%rowcount does not.

      Rating: 4
how to do the
      sql%rowcount with open x for 'select * from emp'  September
      27, 2002
Reviewer:  A reader

  Hi I want to check the # of rows "selected" with and implicit cursor and if
the rows < 100 return the REFCURSOR
other wise raise user defined exception and return message

 right now we are running two stmts.
1) select count(*) from emp;

  to count

 if count < 100 then
2.) open rc for select * from emp;

  can you suggest one query solution

thanks,

          Followup:

Why not just return the first 100 rows?

open rc for select * from (select * from emp) where rownum < 100;

Else, to make your thing more "efficient", you would:

select count(*) from emp where rownum <= 100;

(but it looks like you are trying to do this to "reduce the workload on your
machine".  If that is the case -- ALL of the counting things will INCREASE it.
I suppose one way to approach this might be:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function fail_at_threshold(
p_number in number, p_threshold in number )
  2  return number
  3  as
  4  begin
  5      if ( p_number >= p_threshold )
  6      then
  7          raise_application_error( -20002, 'Query exceeds ' || p_threshold ||
' rows' );
  8      end if;
  9      return p_number;
 10  end;
 11  /

Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.*, fail_at_threshold( count(*) over
(), 5 )
  2    from ( select *
  3             from ( select empno, ename
  4                      from scott.emp
  5                     where ename like '%KIN%' )
  6            where rownum <= 5 ) A
  7  /

     EMPNO ENAME      FAIL_AT_THRESHOLD(COUNT(*)OVER(),5)
---------- ---------- -----------------------------------
      7839 KING                                         1

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.*, fail_at_threshold( count(*) over
(), 5 )
  2    from ( select *
  3             from ( select empno, ename
  4                      from scott.emp  )
  5            where rownum <= 5 ) A
  6  /
select a.*, fail_at_threshold( count(*) over (), 5 )
            *
ERROR at line 1:
ORA-20002: Query exceeds 5 rows
ORA-06512: at "OPS$TKYTE.FAIL_AT_THRESHOLD", line 7
ORA-06512: at line 1

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

the text in bold is your query you want to run - just wrap the rest around it
(and in 8i, you'll need to use dynamic sql for the analytics in PLSQL).

That'll process upto N rows and if the result is in fact N rows -- it'll "fail"
the query.

You'll do less query work then you do now and achieve the same net result.

      Rating: 5
Context
      Switching  September 27, 2002
Reviewer:  Ik
       from BG
Tom,
   Would selecting just COUNT(*) OVER () and then within the PL/SQL block (after
FETCH) checking if it equals 5 be a better option than using a database
function? That way also, you can RAISE error and exit after the first record is
fetched.
Function would involve context switching and also would be an overhead if the
actual rows are less than 100.
Am i correct?

          Followup:

No, you would have to run the query and then re-run the query.

For 100 or less rows, the overhead of calling plsql from sql will be marginal at
best.

Tell me -- would you rather run the entire (possibly very complex) query 2 times
-- or call this tiny function 100 or less times?

Thinking harder, I see we could OPTIMIZE this via:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.*,
  2             decode(rownum,1,fail_at_threshold( count(*) over (), 5 ),0)
  3    from ( select *
  4             from ( select empno, ename
  5                      from scott.emp
  6                     where ename like '%K%' )
  7            where rownum <= 5 ) A
  8  /

     EMPNO ENAME      DECODE(ROWNUM,1,FAIL_AT_THRESHOLD(COUNT(*)OVER(),5),0)
---------- ---------- ------------------------------------------------------
      7698 BLAKE                                                           3
      7782 CLARK                                                           0
      7839 KING                                                            0
ops$tkyte@ORA817DEV.US.ORACLE.COM>

now it only gets called once per query -- even better.

Gradual refinement, thats what its all about...

      Rating: 5
Pardon me if
      iam getting it wrong...  September 27, 2002

Reviewer:  Ik  from BG
Tom,
  This is what i was looking at. Dunno if i have the syntax right. (Don't have
Oracle on this machine)

open rc for
    'select empno, ename, COUNT(*) OVER () cnt
    from scott.emp
    where ename = ''XYZ'';
LOOP
FETCH rc INTO var1, var2, var3;
IF var3 = 5 THEN
   RAISE <exception>;
END IF;

END LOOP;

Why would this run and then re-run? It will fail after fetching first record
right?

          Followup:

Ok, so now say the count(*) over () comes back with 4.

What then -- you just fetched the first row, the result set is useless to the
end user.

You now have to close rc, and OPEN IT ALL OVER AGAIN -- doing all of the work --
again.

Use the decode.  One query, one function call, least amount of code, I cannot
think of a more efficient way to do it.

      Rating: 3
Sorry for
      dragging this conversation on...but  September 27, 2002

Reviewer:  Ik  from BG
I missed the ROWNUM part.
open rc for
'select empno, ename, COUNT(*) OVER () cnt    from scott.emp
    where ename = ''XYZ''
    AND ROWNUM <= 5';
LOOP
FETCH rc INTO var1, var2, var3;
IF var3 = 5 THEN
   RAISE <exception>;
ELSE
   <Do regular processing>
END IF;
END LOOP;

He wants only data less than 5. If it has 5 or more then ...boom else loop
through and process the data.

          Followup:

the requirement:

a) check to make sure query returns less then 100 rows
b) if it does, return the 100 rows to the client via a ref cursor

Your <do regular procesing> is re-running the query from the get go for the
client.

Use decode( rownum, 1, function, 0 ).

      Rating:
      4
dbms_output  September 28, 2002
Reviewer:
      Anuradha  from Ind
Dear Tom,
  I get the following error while running the PL/SQL code .What does this error
message implies.
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SYS.DBMS_OUTPUT", line 57
ORA-06512: at line 47

Is there some other package which displays output that doesn't throw the buffer
overflow error.My requirement is I want to update composite value in a table
whenever rate is amended and I do batch processing. I want to check if all data
are considered or not.When using dbms_output buffer overflow occurs and the
procedure aborts.

Thanks for your great work.

          Followup:

dbms_output has a 255 bytes/line limit.  You are exceeding it in a large way:

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( rpad('*',1000,'*')
);
BEGIN dbms_output.put_line( rpad('*',1000,'*') ); END;

*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SYS.DBMS_OUTPUT", line 57
ORA-06512: at line 1

It should (and in later releases does) throw:

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( rpad('*',256,'*')
);
BEGIN dbms_output.put_line( rpad('*',256,'*') ); END;

*
ERROR at line 1:
ORA-20000: ORU-10028: line length overflow, limit of 255 bytes per line
ORA-06512: at "SYS.DBMS_OUTPUT", line 84
ORA-06512: at "SYS.DBMS_OUTPUT", line 58
ORA-06512: at line 1

      Rating:
      5
Remedy?  September 29, 2002
Reviewer:
      Anuradha  from India
Dear Tom,
   In continuation of my previous post, can you please tell me how can I avoid
that error and also tell me if there is  some other package which displays
output that
doesn't throw the buffer overflow error. Will rtrim avoid this error message ?
When using dbms_output buffer overflow occurs and the procedure aborts.

Thankyou very much
Anuradha

          Followup:

To avoid the error

a) don't dbms_output.put_line lines over 255 bytes wide!

create or replace procedure p ( p_str in varchar2 )
is
   l_str   long := p_str;
begin
   loop
      exit when l_str is null;
      dbms_output.put_line( substr( l_str, 1, 250 ) );
      l_str := substr( l_str, 251 );
   end loop;
end;
/

a small routine like that can be useful.

You should read about UTL_FILE as well.  Or search this site for
"MY_DBMS_OUTPUT"

      Rating: 5
alternative to
      dbms_output  September 29, 2002
Reviewer:
      Anuradha  from India
Dear Tom,
   Sorry to progressing the above post . Is there any alternative package to
dbms_output.put_line.This package displays at the completion of the procedure
but I want to display as and when the varible is asked to display.
Thanks a lot
Anuradha

          Followup:

You will need pipelined functions in Oracle9iR1 and up in order to do that.

Or, you can use UTL_FILE and do a tail -f on the file in another window.

Or, you can use dbms_application_info to set values in v$session or
v$session_longops to monitor from another session.

Prior to that -- when you call a procedure, it goes away to the server to run it
and doesn't come back until its done.  There is no chance to "see" what it is
printing out until it is DONE.

      Rating:
      5
  September 30, 2002
Reviewer:
      A reader
Tom, Can you please show with an example of how to achieve this with Oracle 9i
Pipelined functions? Thanks.

          Followup:

ops$tkyte@ORA9I.WORLD> create or replace type myMsgType as table of varchar2(50)
  2  /

Type created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> create or replace function show_me_NOW( p_n in number )
return myMsgType
  2  PIPELINED
  3  as
  4  begin
  5      for i in 1 .. p_n
  6      loop
  7          pipe row( 'The Time is ' || systimestamp );
  8          dbms_lock.sleep(1);
  9      end loop;
 10      return;
 11  end;
 12  /

Function created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> set arraysize 1
ops$tkyte@ORA9I.WORLD> select * from TABLE( show_me_NOW(5) );

COLUMN_VALUE
--------------------------------------------------
The Time is 01-OCT-02 09.38.54.782566000 AM -04:00
The Time is 01-OCT-02 09.38.55.830566000 AM -04:00
The Time is 01-OCT-02 09.38.56.860433000 AM -04:00
The Time is 01-OCT-02 09.38.57.890444000 AM -04:00
The Time is 01-OCT-02 09.38.58.920509000 AM -04:00

ops$tkyte@ORA9I.WORLD>

You cannot tell -- but I saw the first output seconds before the last -- setting
the arraysize is RELEVANT, else the client will wait for all 5 rows before
showing the first.

      Rating:
      5
  January 19, 2003
Reviewer:
      A reader
Hi Tom,

 I saw your reply on PIPLINED function on google forum.

 I tried it .It is working like charm.

 I want to see explain plan and statistics for this.

 For arraysize 100 it is working fine but for arraysize 1 it is giving some
errors.

 SQL> set autotrace traceonly
SQL>  set arraysize 100
SQL>  select * from TABLE(foo);

22 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   COLLECTION ITERATOR (PICKLER FETCH) OF 'FOO'

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

SQL> set arraysize 1
SQL>  select * from TABLE(foo);
SP2-0612: Error generating AUTOTRACE report

22 rows selected.

SP2-0612: Error generating AUTOTRACE report

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   COLLECTION ITERATOR (PICKLER FETCH) OF 'FOO'

SP2-0612: Error generating AUTOTRACE STATISTICS report
SQL>

 Why it is giving error on statistics?

 Please advise.

 Thanks,

          Followup:

set arraysize to 2 -- I've noticed that -- with an arraysize of 1, plus and
autotrace has an issue.  You want the arraysize bigger then one nominally
anyway.

      Rating:
      5
rowcount  January 19, 2003
Reviewer:
      mo
Tom:

Excellent explanation of getting records count in all cases of pl/sql
programming.  However

Are you calling the following an explicit cursor because in other explanation:

<We need to use an explicit
cursor to get the rowcount>

tkyte@ORA734.US.ORACLE.COM> declare
  2      cursor c1 is select empno from emp;
  3  begin
  4      for x in c1
  5      loop
  6          dbms_output.put_line( c1%rowcount || ' rows selected' );
  7      end loop;
  8  end;
  9  /

http://asktom.oracle.com/pls/ask/f?p=4950:8:1915821::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1191388287886,%7Bdifference%7D%20and%20%7Bbetween%7D%20and%20%7Bexplicit%7D%20and%20%7Bcursor%7D%20and%20%7Bimplicit%7D%20and%20%7Bcursor%7D
you say <If you (you personally) do
not do the cursor managment (open, close, fetch, %isopen, %notfound, etc) it is
implicit>.

Can you clarify this confusion on whether the above is a typo or it is truly an
explicit cursor.

Thank you

          Followup:

it is explictly implicit.

It is technically an EXPLICIT cursor (by the book) that we are using implicitly
in a cursor for loop.

technically, explicit
as used, more implicit than explicit.

      Rating:
      5
  January 23, 2003
Reviewer:
      A reader
Hi Tom,

Is it possible to raise and exception if an implicit cursor does not return any
rows?

Eg:

begin
for i in (select f1 from t)
loop
---
--
end loop;
exception
When no_rows_returned_by_this_implicit_cursor then
---
---
end;

I tried using sql%rowcount, when_no_data_found etc. But nothing works. Can you
please help. Thanks.

          Followup:

declare
   l_found boolean := false;
begin
   for x in ( select ... )
   loop
       l_found := true;
       ...
   end loop;
   if ( NOT l_found ) then raise NO_DATA_FOUND; end if;

      Rating: 4
Reset
      SQL%ROWCOUNT to zero  July 03, 2003
Reviewer:
      Robert  from PA
Hi, Tom
 Quick, how do I reset SQL%ROWCOUNT to zero before
 the next SQL ?
 (if not possible , pls suggest the cheapest way to do this)

Thanks !

          Followup:

UMM -- why?  what could the possible logic be.

      Rating: 4
COUNT(*) OVER
      ()   July 04, 2003
Reviewer:  A reader

In the above question,  COUNT(*) OVER ()  has been used a lot. How is the over()
helping here. Can you please explaining.

          Followup:

without it, the query fails:

scott@ORA920> select dname, count(*) from dept;
select dname, count(*) from dept
       *
ERROR at line 1:
ORA-00937: not a single-group group function

with it, you get the count of the result set "joined" to each row

scott@ORA920> select dname, count(*) OVER () from dept;

DNAME          COUNT(*)OVER()
-------------- --------------
ACCOUNTING                  4
RESEARCH                    4
SALES                       4
OPERATIONS                  4

scott@ORA920>

it is called an analytic function, the coolest thing to happen to sql since
"SELECT"

      Rating: 5
Count or
      rows?  July 04, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Hi Tom,
At our client site, we have a packaged procedure that returns user query results
in batches (25 each). When users click "next" from the web client, we return the
next 25 rows.

Now, the users are asking for getting the COUNT of rows that will eventually be
returned by the query. They want to know from the start, as to how many total
rows are there (that is, if they keep clicking next, how many next clicks will
it take for the entire resultset to be displayed).

We told them that it will be too resource intensive to do a count and then also
do the actual query to return the results (will take more time and resources to
return the answer), but they insist on seeing something like "25 of 1234 rows"
when they query from the web client.

1). One way would be to perform a Count(*) on the same query WHERE clause and
then do the same query WHERE clause to actually get rows returned to the user
(we can do this COUNT(*) only the first time, not every time user clicks next).

2). Other is what you showed in the examples above - to use analytic functions.
Since our development and test servers are Oracle9i and production is at least
8.1.7, we could also use the approach shown by you to return the count along
with the query results. If we do this way, we would be returning the COUNT every
time when user clicks next from the web client.

My question is, which one would be better? 1) or 2).

Will it be too much overhead calling the analytic function to get the count for
each "next" click as opposed to only once (only first time)?

Which apporoach will be cheaper on our server resources?
(or if there is a hidden approach #3, that i missed).
Thanks,

          Followup:

well, tell them to buy approximately 50 times the amount of hardware and other
resources then they have now, they are telling you to do hundreds times more
work.  Make sure they benchmark this before doing it -- seriously.

1) well, hope the data doesn't change but yes, this is what you would want to do
if they actually make the bad decision to do this.

2) #1

      Rating: 5
Yes,
      But...  July 04, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Hi Tom,
That is exactly what we tried to put across them about NOT getting the count of
rows.

But, Unfortunately, Long ago a fat client application was provided to them
(which is now a web based solution) which had this facility of showing the COUNT
of total number of rows that they can expect.

Now the question they have is, well we had that in the fat client application
that we have been using and have gotton used to it. Whay suddenly its not
possible? (just because its a web application?).

The situation is, when the fat client application was developed the volume of
data that was being handled was negligible and not too many users. No one cared
about the performance impact and decision was made to show them the total count
(I wasn't around at this site at that time).

Now that the volume of data has grown tremendously and number of users have
outgrown the handful of users that the client had before, it might (or rather
will be) a huge performance impact if we still provided that feature.

We already had several sessions explaining about the benefits of NOT getting the
total count, but it keeps coming back and back and ...

          Followup:

when will they file the bug report against google?  google lies on its counts --
and not only that, it stops giving results out after a while (try to goto page
200)...

Your logic is sound, client server limited the user population cause you had to
install stuff.  Think about how many people would use amazon.com if you had to
install version 5.4.23 of the amazon client supported on windows version XXX --
no one (anyone remember when UPS or FedEx used to install a hard wired computer
at large businesses so they could run an application...)

Put it on the web and boom -- you've got an unlimited population of users
suddenly.  And they'll all want you to add 5 gazillion new features.

How about this as a compromise?  Instead of saying "there was 1235 hits", add a
link that says "click here to waste computer resources and see how many hits
your query might return"  (you can use a nicer message -- but the concept is
sound, it is the way forms, our client server tool, did it -- you went "menu ->
count hits")

      Rating: 4
Tracking
      progress of procedure.  October 27, 2003
Reviewer:
      Saibabu Devabhaktuni  from Mountain View, CA
Tom,

I have a package which does the batch processing every day on our database. I
have included dbms_output.put_line in all of procedures in that package to
report progress of processing in a logfile. It is reporting dbms_output buffer
only after executing procedure in full. For example:

create or replace procedure test_p as
begin
dbms_output.put_line('Starting....');
execute immediate('create table test(a number)');
dbms_output.put_line('Created test table');
execute immediate('create table test2(a number)');
dbms_output.put_line('Created test2 table');
end;
/

How can I get dbms_output to write output after creating each table???

Thanks,
 Sai.

          Followup:

you cannot -- that is not the way it works.... (if you want the gory details on
exactly how dbms_output works -- i have it all in my book "Expert one on one
Oracle"

but -- search for

dbms_application_info.set_session_longops

on this site to see how to achieve what you want.

October 27, 2003 -- Thanks for the question regarding "ORA-01502,
      Oracle DEFAULT", version 8.0.6

      You Asked
1. I am trying to load a huge data into the database using SQL*LOADER with
DIRECTPATH=TRUE.
(Before loading, the database is in normal condition.
After loading, the constraints are not enabled back.
)

Error after loading: ORA-01502: index utest_pk_customer or partition of such
index is in unstable state.

How can resolve this problem?

2. I have a table customer
     (....
      create_date not null default sysdate
     )

   when i try to insert data without specifying the value for create_date. the
error shows cannot insert null into column create_date.  Is the above definition
correct?

3.  I have 2 database db1(db1.net) and db2(db2.net). I would like export out
some tables like city,state and country from db1 into dmp files. Then import
them into db2.
I have exported the data into code.dmp using db1/db1@db1.net.

My parfile for import is
userid=udb2/udb2@db2.net
file=code.dmp
fromuser=udb1/udb1@db1.net
touser=udb2/udb2@db2.net
ignore=y
commit=y
log=import.log
tables=
CITY, STATE, COUNTRY

But I am getting these errors:

IMP-00017: following statement failed with ORACLE error 1435:
 "ALTER SCHEMA = "UDB2/UDB2@DB2.NET""
IMP-00003: ORACLE error 1435 encountered
ORA-01435: user does not exist
. importing UDB2/UDB2@DB2.NET's objects into UDB2/UDB2@DB2.NET
IMP-00017: following statement failed with ORACLE error 1435:
 "ALTER SCHEMA = "UDB2/UDB2@DB2.NET""
IMP-00003: ORACLE error 1435 encountered
ORA-01435: user does not exist
. importing UDB2/UDB2@DB2.NET's objects into UDB2/UDB2@DB2.NET
IMP-00003: ORACLE error 1435 encountered
ORA-01435: user does not exist
IMP-00034: Warning: FromUser "UDB1/UDB1@DB1.NET" not found in export file
IMP-00033: Warning: Table "CITY" not found in export file
IMP-00033: Warning: Table "STATE" not found in export file
IMP-00033: Warning: Table "COUNTRy" not found in export file

Thanks.

      and we said...
1)
01502, 00000, "index '%s.%s' or partition of such index is in unusable state"
// MERGE: 1489 RENUMBERED TO 1502
// *Cause: An attempt has been made to access an index or index partition
//         that has been marked unusable by a direct load or by a DDL
//         operation
// *Action: DROP the specified index, or REBUILD the specified index, or
//         REBUILD the unusable index partition

Since you don't really show the command you use (eg: directpath=true isn't
valid) and I don't see the log file, I can just list some of the possible causes
(cut and pasted right from the manual:

n SQL*Loader runs out of space for the index, and cannot update the index.
n The data is not in the order specified by the SORTED INDEXES clause.
n There is an instance failure, or the Oracle shadow process fails while
building
the index.
n There are duplicate keys in a unique index.
n Data save points are being used, and the load fails or is terminated by a
keyboard interrupt after a data save point occurred.

2) the correct syntax and method of insert is:

ops$tkyte@ORA717DEV.US.ORACLE.COM> create table t ( x int, y date default
sysdate not null );

Table created.

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> insert into t (x) values ( 1);

1 row created.

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> select * from t;

         X Y
---------- ---------
         1 19-OCT-01

if the date column appears in the column list when you insert and you are
inserting a NULL, it will not use the default.  Since you don't give a complete
example, I have to assume you are including the date column in the insert and by
" when i try to insert data without specifying the value for create_date" you
really mean you are inserting a null value.

2) you just use the USERNAME not the connect string.  fromuser=udb1 touser=udb2
Review &
      Followup

      Rating: 5
ORA-001435
      during import  October 27, 2003
Reviewer:
      Olaf  from germany
I must make a fromuser/touser import in one of the customers database. The user
has a very long name: a_bc_defgh_one_place_in_france. I can see the username in
dba_users and I can connect as this user to the database,
but only if I use the quatations " ".
I tried to start the import with a parfile or from a command line with all
possible quatations, but I always have a error Ora_001435 (user not exits).

...export client uses WE8ISO8859P1 character set (possible charset
conversion)export server uses WE8ISO8859P1 NCHAR character set (possible
ncharset conversio
n)
"ALTER SESSION SET CURRENT_SCHEMA= "a_bc_djdkjfdj_kdkdk_""
IMP-00003: ORACLE error 1435 encountered
ORA-01435: user does not exist
IMP-00000: Import terminated unsuccessfully

Can you help? Are the username case sensitive?

          Followup:

they can be case sensitive, but it is highly unusual and not a recommended
practice.

I'll have to refer you to support for this one.

      Rating: 5
Import using
      case sensitive user name in Oracle  October 27, 2003

Reviewer:  Olaf  from Germany
Tom,

I found what my problem was about. I had to import data in customer database.
The username in Oracle have upper and lower case (was a customer's chose, cannot
be changed). If I try to import data using fromuser touser parameters the import
tool overwrite the name to upper-case and I receive the error "no such user". I
tryed to use the quotation-marks or parameter file (with quotations too), but
without succeess.

Can you help?

Olaf

          Followup:

if you

$ imp fromuser=SCOTT     (no touser!)

Import: Release 9.2.0.3.0 - Production on Mon Oct 27 18:42:15 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Username: "a"      <<<<<==== quoted username
Password:

Connected to: Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production

Export file created by EXPORT:V09.02.00 via conventional path

Warning: the objects were exported by SCOTT, not by you

import done in US7ASCII character set and AL16UTF16 NCHAR character set
import server uses WE8ISO8859P1 character set (possible charset conversion)
. importing SCOTT's objects into a   <<<<=== in lower case

but, you should tell your customer "you are heading towards a heap of miserable
problems if you insist on a mixed case username"

October 28, 2003 seshu -- Thanks for the question regarding
      "Oracle Table-Partition", version 8.0.5.1

      You Asked
Hi,

I've a table :  " TABLE2000 "
          with Partitions :
                TAB_1999_H1( Tablespace TAB_DATA_99)
                TAB_1999_H2( Tablespace TAB_DATA_99)
                TAB_2000_H1( Tablespace TAB_DATA_00)
                TAB_2000_H1( Tablespace TAB_DATA_00)
Now,I've modified the table storage parameters  for 1999 year
partitions and
created a NEW table namely, " TABLE2000_NEW"
          with Partitions :
                NEWTAB_1999_H1( Tablespace NEWTAB_DATA_99)
                NEWTAB_1999_H2( Tablespace NEWTAB_DATA_99)

Also Inserted the 1999 year data into new partitions.

Now, I want to drop the earlier( 1999 year ) partitions and
want to append the newly created partitions to OLD table with
2000 year data.

My  LATEST table should have structure(partitions) like this :

                NEWTAB_1999_H1( Tablespace NEWTAB_DATA_99)
                NEWTAB_1999_H2( Tablespace NEWTAB_DATA_99)
                TAB_2000_H1( Tablespace TAB_DATA_00)
                TAB_2000_H1( Tablespace TAB_DATA_00)

Could you please suggest me the procedural way with
example,which
can save my valuable time(I need to finish this before 8am
tomorrow ie 10th Sep)

Thanks in advance, Regards

      and we said...
What we will do is create a table that looks like your
partitions and play the old "swap" routine.  We can exchange
partitions with tables -- turning a parition into a table or a
table into a partition or both.... Here is how it might look:

ops$tkyte@DEV816> CREATE TABLE table2000
  2  ( x int,
  3    y int,
  4    z DATE
  5  )
  6  PARTITION BY RANGE (z)
  7  (
  8  PARTITION tab_1999_h1 VALUES LESS
  9  THAN(to_date('30-jun-1999','dd-mon-yyyy')),
 10  PARTITION tab_1999_h2 VALUES LESS
 11  THAN(to_date('31-dec-1999','dd-mon-yyyy')),
 12  PARTITION tab_2000_h1 VALUES LESS
 13  THAN(to_date('30-jun-2000','dd-mon-yyyy')),
 14  PARTITION tab_2000_h2 VALUES LESS
 15  THAN(to_date('31-dec-2000','dd-mon-yyyy'))
 16  )
 17  /

Table created.

insert into table2000  values ( 1, 1, '15-jun-1999' );
insert into table2000  values ( 2, 2, '15-dec-1999' );
insert into table2000  values ( 3, 3, '15-jun-2000' );
insert into table2000  values ( 4, 4, '15-dec-2000' );

ops$tkyte@DEV816> CREATE TABLE new_table2000
  2  ( x int,
  3    y int,
  4    z DATE
  5  )
  6  PARTITION BY RANGE (z)
  7  (
  8  PARTITION newtab_1999_h1 VALUES LESS
  9  THAN(to_date('30-jun-1999','dd-mon-yyyy')),
 10  PARTITION newtab_1999_h2 VALUES LESS
 11  THAN(to_date('31-dec-1999','dd-mon-yyyy'))
 12  )
 13  /

Table created.

insert into NEW_table2000  values ( -1, -1, '15-jun-1999' );
insert into NEW_table2000  values ( -2, -2, '15-dec-1999' );

So, how to make the NEW partitions replace the old?  we will
make A new partition become a table and swap that table for an
OLD partition.  It'll look like this:

ops$tkyte@DEV816> create table temp ( x int, y int, z date );
Table created.

ops$tkyte@DEV816> alter table new_table2000
  2  exchange partition newtab_1999_h1
  3  with table temp;

Table altered.

ops$tkyte@DEV816>
ops$tkyte@DEV816> alter table table2000
  2  exchange partition tab_1999_h1
  3  with table temp;

Table altered.

ops$tkyte@DEV816>
ops$tkyte@DEV816> rename temp to table2000_tab_1999_h1;

Table renamed.

ops$tkyte@DEV816>
ops$tkyte@DEV816> select * from table2000;

         X          Y Z
---------- ---------- ---------
        -1         -1 15-JUN-99
         2          2 15-DEC-99
         3          3 15-JUN-00
         4          4 15-DEC-00

ops$tkyte@DEV816> select * from table2000_tab_1999_h1;

         X          Y Z
---------- ---------- ---------
         1          1 15-JUN-99

ops$tkyte@DEV816> select * from new_table2000;

         X          Y Z
---------- ---------- ---------
        -2         -2 15-DEC-99

So, that shows that table2000 has the new paritition swapped
it.  We've saved its old data in a table and new_table2000 no
longer has that data (it has an empty partition now).  We simply
repeat the process:

ops$tkyte@DEV816>
ops$tkyte@DEV816> create table temp ( x int, y int, z date );

Table created.

ops$tkyte@DEV816>
ops$tkyte@DEV816> alter table new_table2000
  2  exchange partition newtab_1999_h2
  3  with table temp;

Table altered.

ops$tkyte@DEV816>
ops$tkyte@DEV816> alter table table2000
  2  exchange partition tab_1999_h2
  3  with table temp;

Table altered.

ops$tkyte@DEV816>
ops$tkyte@DEV816>
ops$tkyte@DEV816> rename temp to table2000_tab_1999_h2;

Table renamed.

ops$tkyte@DEV816>
ops$tkyte@DEV816>
ops$tkyte@DEV816> select * from table2000;

         X          Y Z
---------- ---------- ---------
        -1         -1 15-JUN-99
        -2         -2 15-DEC-99
         3          3 15-JUN-00
         4          4 15-DEC-00

ops$tkyte@DEV816> select * from table2000_tab_1999_h2;

         X          Y Z
---------- ---------- ---------
         2          2 15-DEC-99

ops$tkyte@DEV816> select * from new_table2000;

no rows selected

ops$tkyte@DEV816>

and the partitions are swapped.
Review &
      Followup

      Rating: 5
When should a
      table be partitioned? Version 8.1.7.0.0  April 29, 2002

Reviewer:  Drew  from Trevose, PA USA
Hi Tom,
We have some tables that have a few million records and keep growing.  We may
need to partition these tables. My question is: based on your knowledge, when do
we need to think of partitioning a table, e.g., about howmany records?  How can
we tell if a table needs partitioning or performance problems are caused by not
partitioning the table?  Thank you very much!

          Followup:

It is not necessarily a number of records or even a size in gigabytes -- it is
whether the things you do would be more easily done with it.

do you need to age data off?  classic partitioning example...

could you benefit from partition elimination?  eg: all or most of your queries
always use some date range, or some criteria.  Rather then adding that to each
index, you could partition by it.

are your tables getting larger then you feel comfortable managing?  eg: it might
take longer to restore a 100gig tablespace then 1-10 gig tablespace (and the
other 90gig of data is online whilst doing this)

and so on.  It is when the benefits of partitioning become tangible to you --
that is when partitioning should be used.

      Rating:
      5
  February 19, 2003
Reviewer:
      A reader
Hi Tom,

Am reading Datawarehousing guide as per your suggestion and its helping me a
lot. Thanks tom. I have some doubts.

http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96520/transfor.htm#13138
As per docs

<quote>

Parallel Load Scenario
This section presents a case study illustrating how to create, load, index, and
analyze a large data warehouse fact table with partitions in a typical star
schema. This example uses SQL*Loader to explicitly stripe data over 30 disks.

The example 120 GB table is named facts.
The system is a 10-CPU shared memory computer with more than 100 disk drives.
Thirty disks (4 GB each) are used for base table data, 10 disks for indexes, and
30 disks for temporary space. Additional disks are needed for rollback segments,
control files, log files, possible staging area for loader flat files, and so
on.
The facts table is partitioned by month into 12 partitions. To facilitate backup
and recovery, each partition is stored in its own tablespace.
Each partition is spread evenly over 10 disks, so a scan accessing few
partitions or a single partition can proceed with full parallelism. Thus there
can be intra-partition parallelism when queries restrict data access by
partition pruning.
Each disk has been further subdivided using an operating system utility into 4
operating system files with names like /dev/D1.1, /dev/D1.2, ... , /dev/D30.4.
Four tablespaces are allocated on each group of 10 disks. To better balance I/O
and parallelize table space creation (because Oracle writes each block in a
datafile when it is added to a tablespace), it is best if each of the four
tablespaces on each group of 10 disks has its first datafile on a different
disk. Thus the first tablespace has /dev/D1.1 as its first datafile, the second
tablespace has /dev/D4.2 as its first datafile, and so on, as illustrated in
Figure 13-5.

Step 1: Create the Tablespaces and Add Datafiles in Parallel
The following is the command to create a tablespace named Tsfacts1. Other
tablespaces are created with analogous commands. On a 10-CPU machine, it should
be possible to run all 12 CREATE TABLESPACE statements together. Alternatively,
it might be better to run them in two batches of 6 (two from each of the three
groups of disks).

CREATE TABLESPACE TSfacts1
DATAFILE /dev/D1.1'  SIZE 1024MB REUSE,
DATAFILE /dev/D2.1'  SIZE 1024MB REUSE,
DATAFILE /dev/D3.1'  SIZE 1024MB REUSE,
DATAFILE /dev/D4.1'  SIZE 1024MB REUSE,
DATAFILE /dev/D5.1'  SIZE 1024MB REUSE,
DATAFILE /dev/D6.1'  SIZE 1024MB REUSE,
DATAFILE /dev/D7.1'  SIZE 1024MB REUSE,
DATAFILE /dev/D8.1'  SIZE 1024MB REUSE,
DATAFILE /dev/D9.1'  SIZE 1024MB REUSE,
DATAFILE /dev/D10.1  SIZE 1024MB REUSE,
DEFAULT STORAGE (INITIAL 100MB NEXT 100MB PCTINCREASE 0);
...

CREATE TABLESPACE TSfacts2
DATAFILE /dev/D4.2'  SIZE 1024MB REUSE,
DATAFILE /dev/D5.2'  SIZE 1024MB REUSE,
DATAFILE /dev/D6.2'  SIZE 1024MB REUSE,
DATAFILE /dev/D7.2'  SIZE 1024MB REUSE,
DATAFILE /dev/D8.2'  SIZE 1024MB REUSE,
DATAFILE /dev/D9.2'  SIZE 1024MB REUSE,
DATAFILE /dev/D10.2  SIZE 1024MB REUSE,
DATAFILE /dev/D1.2'  SIZE 1024MB REUSE,
DATAFILE /dev/D2.2'  SIZE 1024MB REUSE,
DATAFILE /dev/D3.2'  SIZE 1024MB REUSE,
DEFAULT STORAGE (INITIAL 100MB NEXT 100MB PCTINCREASE 0);
...
CREATE TABLESPACE TSfacts4
DATAFILE /dev/D10.4' SIZE 1024MB REUSE,
DATAFILE /dev/D1.4'  SIZE 1024MB REUSE,
DATAFILE /dev/D2.4'  SIZE 1024MB REUSE,
DATAFILE /dev/D3.4   SIZE 1024MB REUSE,
DATAFILE /dev/D4.4'  SIZE 1024MB REUSE,
DATAFILE /dev/D5.4'  SIZE 1024MB REUSE,
DATAFILE /dev/D6.4'  SIZE 1024MB REUSE,
DATAFILE /dev/D7.4'  SIZE 1024MB REUSE,
DATAFILE /dev/D8.4'  SIZE 1024MB REUSE,
DATAFILE /dev/D9.4'  SIZE 1024MB REUSE,
DEFAULT STORAGE (INITIAL 100MB NEXT 100MB PCTINCREASE 0);
...
CREATE TABLESPACE TSfacts12
DATAFILE /dev/D30.4'  SIZE 1024MB REUSE,
DATAFILE /dev/D21.4'  SIZE 1024MB REUSE,
DATAFILE /dev/D22.4'  SIZE 1024MB REUSE,
DATAFILE /dev/D23.4   SIZE 1024MB REUSE,
DATAFILE /dev/D24.4'  SIZE 1024MB REUSE,
DATAFILE /dev/D25.4'  SIZE 1024MB REUSE,
DATAFILE /dev/D26.4'  SIZE 1024MB REUSE,
DATAFILE /dev/D27.4'  SIZE 1024MB REUSE,
DATAFILE /dev/D28.4'  SIZE 1024MB REUSE,
DATAFILE /dev/D29.4'  SIZE 1024MB REUSE,
DEFAULT STORAGE (INITIAL 100MB NEXT 100MB PCTINCREASE 0);

</quote>

My question is should'nt the second tablespace's first datafile be on /dev/D2.2
Am not getting the math here how come /dev/D4.2

Thanks for all your help.

          Followup:

kidding right?

if it were me, there would be one big device and someone else would worry about
striping it for me.

The example is a little botched as well -- a 100mb extent size in a datafile of
1024mb ?  hmm, 24mb of wastage.

But anyway, it is just an example - I'm not really going to dig into their
theoretical disk layout if you don't mind ;)

      Rating:
      4
  February 20, 2003
Reviewer:
      A reader
Tom,

Thanks for the repy.Can you please explain what do you mean by one big device.

          Followup:

just give me a big mount point -- /tom/put/your/data/here -- with inifinite
storage.

don't give me

/tom/put/it/here
/tom/or/put/it/here
/tom/or/maybe/over/here
..........
/tom/now/your/really/confused

;)

big mount point with inifinite storage and the hardware guys in the background
moving disks or whatever they use these days to get all of the little lights on
the disk array flashing equally.

I'll put all of my data there.  I'll still think about redo and arch and control
files (don't ever want to lose them and need them to be really super optimized
in many cases -- no contention desired)....

      Rating:
      5
  February 20, 2003
Reviewer:
      A reader
Thanks Tom. I like your style of answering questions ;)

      Rating: 4
? re: Table
      Partition  April 21, 2003
Reviewer:  VJ
       from Freedom Land
Hi Tom,

I want to create a table which has two fields id number, flag char(1) with
partition on flag. The values in the flag will be 'R' and 'T'. so the data would
look like

Id    Flag
-----------
1      R
2      R
3      R
4      R
5      R
6      R
7      R
8      R
9      R
10     R
11      T
12      T
13      T
14      T
15      T
.............

So i want two partitions one for flag='R' and one for flag='T'. Can you give me
an example how to achieve this.

Thanks a million in advance
VJ

          Followup:

ops$tkyte@DEV816> CREATE TABLE table2000
  2  ( x int,
  3    y int,
  4    z DATE
  5  )
  6  PARTITION BY RANGE (z)
  7  (
  8  PARTITION p1 VALUES LESS
  9  THAN('S'),
 14  PARTITION p2 VALUES LESS
 15  THAN('U')
 16  )
 17  /

      Rating: 1
Mixed datatype
      in partition key  April 22, 2003
Reviewer:
      A reader
This table creation in 8.1.7.4 gives an ORA-01858: a non-numeric character was
found where a numeric was expected.

          Followup:

so use a little initiative, take the obvious cut and paste, fix the datatype of
Z to be appropriate for you.

My point here was -- the question was already answered above, i had an example
of a very very simple partition clause -- you need to read about partitioning.

I can see what is going to happen here -- you will apply partitioning, not
understand much about it, system performance will go down the tubes, you will
blame partitioning and another myth starts (partitioning is 'complex',
partitioning is 'slow').

This is a trivial syntax question ...

ops$tkyte@ORA920LAP> CREATE TABLE table2000
  2  ( x int,
  3    y int,
  4    z varchar2(5)
  5  )
  6  PARTITION BY RANGE (z)
  7  (
  8  PARTITION p1 VALUES LESS
  9  THAN('S'),
 10  PARTITION p2 VALUES LESS
 11  THAN('U')
 12  )
 13  /

Table created.

suggest you read a ton more before going down this path.

      Rating:
      5
  June 16, 2003
Reviewer:  sara
       from Canada
Hi

I am a biginer to Oracle. As I am trying to partitioning a table it says :

ORA-00439: feature not enabled: Partitioning

how to enable the partitioning feature.

sorry for the basic question.

          Followup:

you need to

a) be on the enterprise edition
b) have purchased it

if you have a & b, then using OUI (the Oracle Universal Installer) is the way to
enable it.

if either of a or b is not true, you don't have it.

      Rating: 4
indexes while
      partitioning  June 30, 2003
Reviewer:  Vipin
      from New york
Hi Tom,

Could you please tell me when should one consider about the Global indexes and
when about the local indexes while partitioning there tables.

          Followup:

well, think about how you use them.  but search for

global local performance

to read some articles about them.

      Rating: 5
Partitioning
      Strategy  September 21, 2003
Reviewer:
      A reader
Hi Tom,

Say if I have two very large tables transactions and transaction items, which I
want to partition.

txns(txnid, txndate, txntype)
txnitems (txnid, amount, accountid)

Most queries are for the sum of amount within certain date range.

I can range partion transactions table by transaction date, but how should I
partition the transaction items table? Is it possible that I partition it using
the txndate in the txns table? Or do you think it is helpful to partition the
txns table by range partition (txndate) hash subpartition (txnid) and hash
partition (txnid) on the txns items table?

          Followup:

"Most queries are for the sum of amount within certain date range."

sounds like a screaming call for a teeny tiny bit of denormalization doesn't it?

think about the amount of work you could save every day if you made it so that
most queries consume 50% or less resources!!!  You might carry that date down
into the details.

I do not blindly recommend partitioning schemes.  I wrote about this in my last
book "Effective Oracle By Design".  Partitioning tables, without knowledge of
how they are used (for real, not just a single sentence), is a recipe for
certain disaster!

given as it stands, range partitioning txns by date SEEMS to make sense and it
would follow that hash partitioning TXNITEMS by TXNID MIGHT make sense -- but
one cannot say for sure.

      Rating: 5
How to get the
      archived off data back to a partitioned table  September 24,
      2003
Reviewer:  AD  from UK
Tom,

I have a partitioned table with partitioned key on the year_month.

e.g. 200301 - for jan 2003's data
     200302 - for feb 2003's data etc.

Partitioning is implemented in such a way that anything older than 2 yrs of data
is archieved off to a table.

e.g. A_arch_200106 - archived data for jun 2001
     A_arch_200107 - archived data for jul 2001 etc.

Where A is the table name.

Now, I have a requirement to get the archived off data back to the partitioned
table A, so that A_arch_200106 will represent data for partitioned 200106,
A_arch_200107 will represent data for partitioned 200107 etc. in the same way
they were in the table before the archiving took place.

What should be my approach to handle this?

Thanks & Regards

          Followup:

to archive the data you did an alter table exchange partition right -- so you
swapped a totally empty table with the full partition -- ending up with a full
table and an empty partition.

Now, you could have either:

a) left the empty partition -- in which case -- you just alter exchange back and
you are done

b) dropped the empty partition -- in which case you must now SPLIT the last
partition in two, resulting in an empty and full partition and then exchange the
new empty slice for the full table you've restored

c) merged the empty partition in to a "mega" empty partition which can be split
very fast since it is empty -- repeat step b) above.

      Rating: 5
Partition
      Granule  October 23, 2003
Reviewer:
      A reader
Hi Tom,

Our database stores history transactions of about 100 years. The typical queries
are reports searching data within range of 1 year, 5 years, 10 years, and 20
years, start from any calendar year.

I am considering to partition the table by transaction date.

What factors will you consider on how big the partition granule should be?

I am considering to partition the data by 1 year or 5 years. Which one do you
recommend?

          Followup:

maybe by the month.

else many queries might have to hit 24 months of data -- instead of just 12.

      Rating: 5
Partitioned
      table and Replication  October 27, 2003
Reviewer:
      Alvin  from Philippines
I have both your books. And i just implemented partitioning on one big table. My
problem is since partitioning my table i didn't define any columns as unique or
as primary key.

My problem now is i want to replicate that table on another machine for read
only purposes and oracle 8i docs states that

"Considerations for Replicated Tables
The following sections discuss considerations for tables you plan to use in a
replication environment.

Primary Keys
If possible, each replicated table should have a primary key. Where a primary
key is not possible, each replicated table must have a set of columns that can
be used as a unique identifier for each row of the table. If any of the tables
that you plan to use in your replication environment do not have a primary key
or a set of unique columns, alter these tables accordingly. In addition, if you
plan to create any primary key snapshots based on a master table, that master
table must have a primary key.
"

Do i need PK defined ? or does oracle documentation stated this just to ease on
conflict resolution ?

          Followup:

why do you have a table with no primary or unique constraints???????

      Rating: 5
Partitioned
      table, Primary Keys and Replication  October 27, 2003

Reviewer:  Alvin  from Philippines
Sorry i got confused. I re-read on pp643 (Local indexes and Uniqueness)of Expert
one-on-one that PK and UK CAN be enforced but only per partition (never
across)and the partitioning key must be included.

Suppose i have a table

Create table t
( bill_num  number  <- PK
, bill_date date   )
partition by range ( bill_date)
....

and my query uses a lot of bill_num in its predicate i can create a non-prefixed
composite pk constraint like below

alter table t add constraint t_pk primary key ( bill_num, bill_date);

By doing so i can ensure uniqueness on a partition level and i can 'age-out'
some data without the need of rebuilding any indexes ? Right ?

          Followup:

If you want a unique constraint (primary key, whatever), you will either:

o use that column(s) in the partitioning scheme so that a local index can be
used

o not use the column(s) in the partitioning schema and then use a global index
on that column(s)

those are your choices.  If you use a global index, you can still perform
partition level operations in 9i using the maintain global indexes clause.  It
will not cause a rebuild, it will maintain the index as you perform the
partition level operations.

October 28, 2003 -- Thanks for the question regarding "Perform
      update", version 8.1.7

      You Asked
Tom,

I have following table (administrative) which lists all sick, other,vacation
days (depends on the 'status' column)

userid   admindate  status
16       1/5/2003   SICK
16       2/2/2003   VACATION

and also I have table (tmp_calendar_coverage) which needs to be updated based on
the table above:

datecompleted   userid  issick   isvacation  isother
1/1/2003        16      0        0           0
1/5/2003        16      1        0           0

where 1 set in 'issick' column due to the fact from administrative table that
userid took a sick day on that day.

Please, advise on how to perform such update (update statement).
thank you

      and we said...
i would never maintain the second table.  It is easy to get the information you
need from the first table alone.

select admindate, userid,
       max( decode( status, 'SICK', 1, 0 ) ) issick,
       max( decode( status, 'VACATION', 1, 0 ) ) vacation,
       .....
  from t
 where userid = :x
 group by admindate, userid
/

you do not need the other table at all.  but if you do maintain it -- a simple
trigger on the first table is what you need:

create or replace trigger T after insert on T
for each row
begin
   if ( :new.status = 'SICK' )
   then
      update T2 set issick = 1
       where userid = :new.userid
         and datecompleted = :new.admindate;

      if (sql%rowcount = 0)
      then
           insert into t2 (datecompleted,userid,issick,isvacation,....)
           values (:new.admindate,:new.userid,1,0,.....);
      end if;
   end if;
   -- repeat above block for other cases
end;
/

and it would be best if the trigger was just one line of code:

begin
   proc( :new.userid, :new.admindate, :new.status );
end;

and proc did all of the SQL/work.
Review &
      Followup

      Rating:
      5
  October 27, 2003
Reviewer:
      A reader
Excellent!!

      Rating:
      3
  October 27, 2003
Reviewer:
      A reader
Tom,

The table - tmp_calendar_coverage does not contain only columns I listed
below...That's why I do need this table.
Also, first I populate tmp_calendar_coverage table and assign 0 to
issick,isvacation and isother columns, since my administrative table is not
created yet. Then, I populate details of my administrative table. Once, my
administrative table got populated with all sick, vacation,other day sales rep
took, I need to update my tmp_calendar_coverage table with those days.

So, how would you advise in that case?

          Followup:

I think i gave both answers did i not?  I said "looks like you need A table" but
"if you insist on two here is the approach"

I still think "this is a single table problem".  I don't think the issick,
isvacation, etc data belongs in that second table -- fine, have other columns,
but they are not necessary.

Either that or the first table is NOT necessary.

One or the other is not needed.

October 28, 2003 Howie -- Thanks for the question regarding "SQL
      Query Tuning", version 8.0.5

      You Asked
Hi Tom !

I hope you will help me to tune following query ?

My problem is why it is going for full table scan for PS_JOB
table . There are proper indexes on this table ? Or there is
some thing wrong in query design? Please help me as its
production problem and is to be fixed immediately ?

Query :

SELECT DISTINCT
A.COMPANY,
A.EMPLID,
A.SSN,
A.NAME,
C.EMPL_STATUS,
TO_CHAR(D.LAST_DATE_WORKED,'YYYY-MM-DD'),
TO_CHAR(D.TERMINATION_DT,'YYYY-MM-DD'),
TO_CHAR(A.PAY_END_DT,'YYYY-MM-DD'),
TO_CHAR(A.CHECK_DT,'YYYY-MM-DD'),
A.ADDRESS1,
A.ADDRESS2,
A.CITY,
A.STATE,
A.ZIP,
B.ERNCD_SPCL,
B.SPCL_EARNS
  FROM
PS_PAY_CHECK A,
PS_PAY_SPCL_EARNS B,
PS_JOB C,
PS_EMPLMT_SRCH_GBL C1,
PS_EMPLOYMENT D
  WHERE B.COMPANY = A.COMPANY
    AND B.PAYGROUP = A.PAYGROUP
    AND B.PAY_END_DT = A.PAY_END_DT
    AND B.OFF_CYCLE = A.OFF_CYCLE
    AND B.PAGE# = A.PAGE#
    AND B.LINE# = A.LINE#
    AND B.SEPCHK = A.SEPCHK
    AND C.EMPLID = C1.EMPLID
    AND C.EMPL_RCD# = C1.EMPL_RCD#
    AND C1.OPRCLASS = 'ALLPANLS'
    AND D.EMPLID = C1.EMPLID
    AND D.EMPL_RCD# = C1.EMPL_RCD#
    AND ( B.ERNCD_SPCL = '401'
     AND A.PAY_END_DT = TO_DATE(:1,'YYYY-MM-DD')
     AND C.EFFDT =
        (SELECT MAX(EFFDT) FROM PS_JOB
        WHERE C.EMPLID = EMPLID
          AND C.EMPL_RCD# = EMPL_RCD#
          AND EFFDT <= SYSDATE)
    AND C.EFFSEQ =
        (SELECT MAX(EFFSEQ) FROM PS_JOB
        WHERE C.EMPLID = EMPLID
          AND C.EMPL_RCD# = EMPL_RCD#
          AND C.EFFDT = EFFDT)
     AND A.EMPLID = C.EMPLID
     AND C.EMPLID = D.EMPLID
     AND C.EMPL_RCD# = D.EMPL_RCD# )
/

Explain Plan :

1.907 SELECT STATEMENT    BAL cost = 907
  2.1 SORT UNIQUE
    3.1 FILTER
      4.1 NESTED LOOPS
        5.1 NESTED LOOPS
          6.1 NESTED LOOPS
            7.1 NESTED LOOPS
              8.1 TABLE ACCESS FULL PS_JOB
              8.2 TABLE ACCESS BY INDEX ROWID PS_EMPLOYMENT
                9.1 INDEX UNIQUE SCAN PS_EMPLOYMENT UNIQUE
            7.2 INDEX UNIQUE SCAN PS_FAST_SCRTY_1 UNIQUE
          6.2 TABLE ACCESS BY INDEX ROWID PS_PAY_CHECK
            7.1 INDEX RANGE SCAN PS1PAY_CHECK NON-UNIQUE
        5.2 TABLE ACCESS BY INDEX ROWID PS_PAY_SPCL_EARNS
          6.1 INDEX UNIQUE SCAN PS_PAY_SPCL_EARNS UNIQUE
      4.2 SORT AGGREGATE
        5.1 INDEX RANGE SCAN PSAJOB NON-UNIQUE
      4.3 SORT AGGREGATE
        5.1 INDEX RANGE SCAN PSAJOB NON-UNIQUE

Indexes Present on PS_JOB

INDEX_NAME    UNIQUENESS    COLUMN_NAME    COLUMN_POSITION
PS#JOB    NONUNIQUE    EMPLID    1
    NONUNIQUE    EMPL_RCD#    2
    NONUNIQUE    DEPTID    3
    NONUNIQUE    JOBCODE    4
PS0JOB    NONUNIQUE    DEPTID    1
    NONUNIQUE    EMPLID    2
    NONUNIQUE    EMPL_RCD#    3
    NONUNIQUE    EFFDT    4
    NONUNIQUE    EFFSEQ    5
    NONUNIQUE    JOBCODE    6
PS1JOB    NONUNIQUE    JOBCODE    1
    NONUNIQUE    EMPLID    2
    NONUNIQUE    EMPL_RCD#    3
    NONUNIQUE    EFFDT    4
    NONUNIQUE    EFFSEQ    5
    NONUNIQUE    DEPTID    6
PS2JOB    NONUNIQUE    POSITION_NBR    1
    NONUNIQUE    EMPLID    2
    NONUNIQUE    EMPL_RCD#    3
    NONUNIQUE    EFFDT    4
    NONUNIQUE    EFFSEQ    5
    NONUNIQUE    DEPTID    6
    NONUNIQUE    JOBCODE    7
PS9JOB_L    NONUNIQUE    COMPANY    1
    NONUNIQUE    PAYGROUP    2
    NONUNIQUE    EFFDT    3
    NONUNIQUE    EMPLID    4
PSAJOB    NONUNIQUE    EMPLID    1
    NONUNIQUE    EMPL_RCD#    2
    NONUNIQUE    EFFDT    3
    NONUNIQUE    EFFSEQ    4
    NONUNIQUE    DEPTID    5
PS_JOB    UNIQUE    EMPLID    1
    UNIQUE    EMPL_RCD#    2
    UNIQUE    EFFDT    3
    UNIQUE    EFFSEQ    4

Please give some tips or any help which can improve the
performance of this query.

Thanks in advacne

Howie

      and we said...
Lets look at the predicate:

  WHERE B.COMPANY = A.COMPANY
    AND B.PAYGROUP = A.PAYGROUP
    AND B.PAY_END_DT = A.PAY_END_DT
    AND B.OFF_CYCLE = A.OFF_CYCLE
    AND B.PAGE# = A.PAGE#
    AND B.LINE# = A.LINE#
    AND B.SEPCHK = A.SEPCHK
    AND C.EMPLID = C1.EMPLID
    AND C.EMPL_RCD# = C1.EMPL_RCD#
    AND C1.OPRCLASS = 'ALLPANLS'
    AND D.EMPLID = C1.EMPLID
    AND D.EMPL_RCD# = C1.EMPL_RCD#
    AND ( B.ERNCD_SPCL = '401'
     AND A.PAY_END_DT = TO_DATE(:1,'YYYY-MM-DD')
     AND C.EFFDT =
        (SELECT MAX(EFFDT) FROM PS_JOB
        WHERE C.EMPLID = EMPLID
          AND C.EMPL_RCD# = EMPL_RCD#
          AND EFFDT <= SYSDATE)
    AND C.EFFSEQ =
        (SELECT MAX(EFFSEQ) FROM PS_JOB
        WHERE C.EMPLID = EMPLID
          AND C.EMPL_RCD# = EMPL_RCD#
          AND C.EFFDT = EFFDT)
     AND A.EMPLID = C.EMPLID
     AND C.EMPLID = D.EMPLID
     AND C.EMPL_RCD# = D.EMPL_RCD# )
/

First lets see how the tables join up:

  WHERE B.COMPANY = A.COMPANY
    AND B.PAYGROUP = A.PAYGROUP
    AND B.PAY_END_DT = A.PAY_END_DT
    AND B.OFF_CYCLE = A.OFF_CYCLE
    AND B.PAGE# = A.PAGE#
    AND B.LINE# = A.LINE#
    AND B.SEPCHK = A.SEPCHK

    AND C.EMPLID = C1.EMPLID
    AND C.EMPL_RCD# = C1.EMPL_RCD#

    AND D.EMPLID = C1.EMPLID
    AND D.EMPL_RCD# = C1.EMPL_RCD#

     AND A.EMPLID = C.EMPLID

     AND C.EMPLID = D.EMPLID
     AND C.EMPL_RCD# = D.EMPL_RCD# )
/

So

B joins to A (company, paygroup, pay_end_dt, off_cycle, page#,
line#, spechk)

A joins to C (emplid)

C joins to D (emplid, empl_rcd# )

C joins to C1 (emplid, empp_rcd# )

C1 joins to D (emplid, empl_rcd# )

(the join condition for C to D appears redundant to me -- you
have
    AND C.EMPLID = C1.EMPLID
    AND C.EMPL_RCD# = C1.EMPL_RCD#

    AND D.EMPLID = C1.EMPLID
    AND D.EMPL_RCD# = C1.EMPL_RCD#

    AND C.EMPLID = D.EMPLID
    AND C.EMPL_RCD# = D.EMPL_RCD# )

c -> c1 -> d
|
D

but if you have ( c.x = c1.x AND d.x = c1.x and C.x = D.x ) one
of d.x = c1.x or c.x = d.x is redundant... So we can forget
about C1 joins to D

So, a "graph" of this might look like:

   C-A-B
   /\
  D  C1

Looking for columns that could be used to "reduce" any of the
tables we have only:

    AND C1.OPRCLASS = 'ALLPANLS'
    AND B.ERNCD_SPCL = '401'
    AND A.PAY_END_DT = TO_DATE(:1,'YYYY-MM-DD')

all of the other predicates are JOIN conditions OR correlated
subqueries.  They are the only predicates that could be used to
"start" the query with.  We need to start the query somewhere --
we have to pick one of the tables to "start" with.  We have NO
predicates on C (other then join conditions to other tables) so
if C (PS_JOB) is choosen as the "driving" table (and it was)--
we must full scan it and join it to A, D and C1.

That is what:

            7.1 NESTED LOOPS
              8.1 TABLE ACCESS FULL PS_JOB
              8.2 TABLE ACCESS BY INDEX ROWID PS_EMPLOYMENT
                9.1 INDEX UNIQUE SCAN PS_EMPLOYMENT UNIQUE

is starting to do.  It is full scanning PS_JOB and joining it to
D (PS_EMPLOYMENT).  Then it goes after the other tables to be
joined to.

Now, unless C1(oprclass) or B(erncd_spcl) or a(pay_end_dt) are
indexed, there is no opportunity to avoid a full scan on SOME
table.

Without knowing everything about the tables -- the cardinality
of the above columns, whether they are indexed and so on -- it
is impossible to say how this could be written better.  Unless
one of the three predicates above restricts to a very small set
one of C1, B, or A via an index, a full scan has to happen --
there simply are no predicates to reduce the "starting" table
otherwise.
Review & Followup

      Rating: 3
CBO or
      RBO  October 01, 2001
Reviewer:  Chenna

Tom

When we have so many tables in the from clause , and
1.we want to confirm, if statistics are analyzed for all of them how do we do
that.
2.How do we confirm which optimizer is being used.
3.What will be the impact if some of the tables are analyzed and some others are
not analyzed.
4.Is it possible to give all the  join conditions first and then give the
filtering conditions, will this effect theperformance of the query.
5.How can I be sure if the statistics are accurate or not.
6.Under cbo or rbo , if I see that an index is not being used what are the first
things I should look for causing this.
7.Can we analyze tables in other schema's other then ours.
8.Before I start tuning a query , I guess ( if Iam using CBO)  if the tables are
analyzed  and that the statistics are current for all the tables in the from
clause , how do I be sure of this.

          Followup:

1) look at the last_analyzed column in user_tables

2) tkprof will tell you (
http://asktom.oracle.com/~tkyte/tkprof.html
 but if any table is analyzed (any table the query
references) and the optimizer goal is NOT set to RULE -- you are using CBO

3) less information is available to the optimizer, it may make bad decisisions
given insufficient data.  It'll "guess" the missing data

4) using CBO, the order of the predicates is not relevant. put them in any order

5) look at last_analyzed, if they are current -- the probably are.  Also look
into "alter table T monitoring" and then use dbms_stat to gather stats on
"stale" tables.  That'll help you keep the stats up to date once they are up to
date.

6) first -- think about "is using the index really the most efficient thing".
Many times the answer is surprisingly "no, no its not".  Many things can affect
this -- i listed quite a few in my book with lots of examples.  There is no
"first thing" to look at -- but a common case is you issue:

  select x from t order by x;

or something similar and wonder why the index on x isn't used.  If X is nullable
-- it cannot be used as entirely NULL entries are not made in b*tree indexes.

7) yes

ops$tkyte@ORA9I.WORLD> analyze table scott.emp compute statistics;

Table analyzed.

ops$tkyte@ORA9I.WORLD> show user
USER is "OPS$TKYTE"

from the sql reference manual:

<quote>
Prerequisites
The schema object to be analyzed must be local, and it must be in your own
schema or you must have the ANALYZE ANY system privilege.

If you want to list chained rows of a table or cluster into a list table, the
list table must be in your own schema, or you must have INSERT privilege on the
list table, or you must have INSERT ANY TABLE system privilege.

If you want to validate a partitioned table, you must have INSERT privilege on
the table into which you list analyzed rowids, or you must have INSERT ANY TABLE
system privilege.
</quote>

8) see 5 and 1.

      Rating: 3
upto date
      stats  October 01, 2001
Reviewer:  Hi

use dbms_stat to gather stats on
"stale" tables.  That'll help you keep the stats up to date once they are up to
date.

1.what do you mean by stale tables.

2.This is one of the trickiest sentences I have heard in some time

"That'll help you keep the stats up to date once they are up to date."

please explain it

          Followup:

1) stale tables are tables that have been modified via inserts/updates/deletes
and are in need of new statistics.  Using the alter table T monitor, we'll tell
you when a table is stale, you don't need to figure it out yourself.

2) what i was trying to say there was that after you analyze all of the tables
(get their stats up to date), using alter table T monitor and dbms_stats will
help you automagically keep their stats UP TO date.

      Rating: 4
Breaking it
      down  October 02, 2001
Reviewer:  Trevor
       from Oz
Tom,

Your reply was very interrsting...
Often I get asked to "fix" large and or complex SQL some
have an explain plan that fills 5 A4 pages.

I'll read your reply again however I am having a little
trouble understanding how you determined the
join of the tables.
I can see B joins to  A
          A joins to C
          C joins to C1
but not   C1 joins to D, doesn't D joing to C1 ?
D.EMPLID = C1.EMPLID ...

I'm about to searching your book, but any extra info
or pointers you cn give about breaking an SQL statement
down to tune it would be greatly appreciated

          Followup:

It does, and I said it did:

.....
C1 joins to D (emplid, empl_rcd# )

(the join condition for C to D appears redundant to me -- you have
    AND C.EMPLID = C1.EMPLID
    AND C.EMPL_RCD# = C1.EMPL_RCD#

    AND D.EMPLID = C1.EMPLID
    AND D.EMPL_RCD# = C1.EMPL_RCD#

    AND C.EMPLID = D.EMPLID
    AND C.EMPL_RCD# = D.EMPL_RCD# )

c -> c1 -> d
|
D

but if you have ( c.x = c1.x AND d.x = c1.x and C.x = D.x ) one of d.x = c1.x or

c.x = d.x is redundant... So we can forget about C1 joins to D
........

I just removed it from consideration, it wasn't relevant in the query.

The ONLY way I know to break down the SQL statement is to do exactly what I just
did above.  Piece by piece, draw a graph of what joins to what and how...

      Rating: 5
Tom, very
      important question and very urgent-need to know this
      info  October 03, 2001
Reviewer:  Chenna

In the explain plan how can we know, if the outer query is doing a full table
scan  for each row returned by the inner query i.e. the sub query.

Lets assume that the query involves only 2 tables.

How will the explain plan appear when the table in the outer query does a full
table scan for every row returned by the inner query, and

How will the explain plan appear when the table in the outer query does one full
table scan for all the rows returned by the inner query.

I know that you are on tour, but if you get any chance kindly answer this doubt,
it is very crucial for me.

Thank you

          Followup:

You can tell by the indentation.  In the following, BIG is "create table big as
select * from all_objects".  SMALL is the same but with "where rownum < 10"

The first query full scans SMALL for every row in BIG:

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly

ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2    from big
  3   where EXISTS ( select null from small where small.object_id =
big.object_id );

8 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=7 Card=845 Bytes=81965)
   1    0   FILTER
   2    1     TABLE ACCESS (FULL) OF 'BIG' (Cost=7 Card=845 Bytes=81965)
   3    1     TABLE ACCESS (FULL) OF 'SMALL' (Cost=1 Card=1 Bytes=4)

See how they are at the same level and the operation is FILTER.  Here will full
scan BIG and filter it by full scanning SMALL.  The IO reported supports this as
well:

Statistics
----------------------------------------------------------
          0  recursive calls
     202743  db block gets
      84707  consistent gets
          0  physical reads
          0  redo size
       2010  bytes sent via SQL*Net to client
        430  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          8  rows processed

Now we do the query in a different fashion:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2    from big
  3   where object_id in ( select object_id from small )
  4  /

8 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=12 Card=9 Bytes=990)
   1    0   NESTED LOOPS (Cost=12 Card=9 Bytes=990)
   2    1     VIEW OF 'VW_NSO_1' (Cost=3 Card=9 Bytes=117)
   3    2       SORT (UNIQUE) (Cost=3 Card=9 Bytes=36)
   4    3         TABLE ACCESS (FULL) OF 'SMALL' (Cost=1 Card=9 Bytes=36)
   5    1     TABLE ACCESS (BY INDEX ROWID) OF 'BIG' (Cost=1 Card=16894
Bytes=1638718)
   6    5       INDEX (UNIQUE SCAN) OF 'BIG_IDX' (UNIQUE)

It is doing a nested loops join (not a filter).  the nested loops is done
against a sort/unique of a full scan of SMALL with an index access into big to
find the corresponding rows.

The reduced IO numbers support this as well

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

      Rating:
      5
Reader  October 03, 2001
Reviewer:
      Reader  from USA
Tom,

Could you tell us if outer-table of a NL, should
always be a FULL Table Scan. Are there other
execution path be possible for a outer table of NL
for better performance.

Thanks

          Followup:

well,

select *
  from emp, dept
 where dept.deptno = 10
   and emp.deptno = dept.deptno
/

that would probably INDEX range scan an index on DEPTNO and then table access by
rowid DEPT and then index range scan an index on deptno in EMP and then table
access by rowid EMP.

No full scan.

A query such as:

select *
  from emp, dept
 where emp.deptno = dept.deptno
/

would NECESSARILY full scan at LEAST one of the two tables -- it is the most
efficient way -- you are getting all rows, a full scan = fastest way to get all
rows.

      Rating: 5
but the qustion
      I asked was -- please be patient with me  October 03, 2001

Reviewer:  Chenna
"
In the explain plan how can we know, if the outer query is doing a full table
scan  for each row returned by the inner query i.e. the sub query.
"

"
Lets assume that the query involves only 2 tables.

How will the explain plan appear when the table in the outer query does a full
table scan for every row returned by the inner query, and

How will the explain plan appear when the table in the outer query does one full

table scan for all the rows returned by the inner query.

I know that you are on tour, but if you get any chance kindly answer this doubt,

it is very crucial for me.

Thank you
"

Tom, kindly focus of the wory 'EVERY' I used in my question.

"
Followup from Tom:  You can tell by the indentation.  In the following, BIG is
"create table big as
select * from all_objects".  SMALL is the same but with "where rownum < 10"

The first query full scans SMALL for every row in BIG:

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly

ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2    from big
  3   where EXISTS ( select null from small where small.object_id =
big.object_id );

8 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=7 Card=845 Bytes=81965)
   1    0   FILTER
   2    1     TABLE ACCESS (FULL) OF 'BIG' (Cost=7 Card=845 Bytes=81965)
   3    1     TABLE ACCESS (FULL) OF 'SMALL' (Cost=1 Card=1 Bytes=4)

See how they are at the same level and the operation is FILTER.  Here will full
scan BIG and filter it by full scanning SMALL.  The IO reported supports this as

well:

"

You explanation for the above query is that first the big table is full scanned
, and then the rows are filtered by full scanning the small table. Fine and
perfect.

But , the essence of my question was, which I repeat in the context of the above
example ..

There is only one full table scan of the big and the small table for all the 8
rows returned by the query . Iam I right? Apparently that is right.

Now my question is

if I have query like

select columns
from  a,b
whre a.b=b.b
and a.c not in (select a.c from where substr(a.c,1,5)='asdf')

Lets assume the sub query returns 10 rows.

1.will table a be scanned once for each row returned by the sub query.i.e. will
table a be scanned 10 times.

2.It it is so , then how will the explain plan appear.

3.Can you give an example where this kind of a full table scan for each row
returned by the sub query can occur, i.e. 10 rows returned 10 full table scans
done.

I think I did a good job in putting across my confusion or question to you
clearly.

          Followup:

In regards to:

"There is only one full table scan of the big and the small table for all the 8
rows returned by the query . Iam I right? Apparently that is right."

that is NOT correct.  The plan:

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=7 Card=845 Bytes=81965)
   1    0   FILTER
   2    1     TABLE ACCESS (FULL) OF 'BIG' (Cost=7 Card=845 Bytes=81965)
   3    1     TABLE ACCESS (FULL) OF 'SMALL' (Cost=1 Card=1 Bytes=4)

is telling us that for EACH ROW in big (16k of them) we did a FULL SCAN of small
( we scanned small 16k+ times).  I said that in my answer "The first query full
scans SMALL for every row in BIG:"

As for your query:

select columns
from  a,b
whre a.b=b.b
and a.c not in (select a.c from where substr(a.c,1,5)='asdf')

The predicate "where substr(a.c,1,5) = 'asdf'" PRECLUDES nulls from being in the
subquery HENCE there is a chance it'll use an ANTI-JOIN which as I demonstrated
above does not full scan for each row.

The answer is "it depends".  It might -- it might not.  You'll have to get the
query plan and SEE what it says.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:953229842074
for lots of examples that show full scans, anti joins, etc etc etc...

      Rating: 5
Will the number
      of rows retrieved from the driving table matter  October 03,
      2001
Reviewer:  Chenna
Look at the below execution plan

T-ONE MILLION ROWS
T1-1000 ROWS

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (GROUP BY)
   2    1     MERGE JOIN (OUTER)
   3    2       SORT (JOIN)
   4    3         TABLE ACCESS (FULL) OF 'T'
   5    2       SORT (JOIN)
   6    5         TABLE ACCESS (FULL) OF 'T1'

Driving table is t, as though t1 is the the table retrieving smaller number of
rows, it has an outer join on it.

Will the driving table matter where the execution plan is like the above, or
will it matter only when the nested loops join is used.

-by the way Kindly answer the above comment too

thank you

          Followup:

geez, a plan without a query.  Ok, hhhmmm, what could the query be?  using
cbo/rbo?  what does t look like, t1???

I see an outer join so maybe the query was:

select * from t, t1 where t.id = t1.id(+);

here there can only BE one driving table -- t.  t1 cannot drive it since t1 is
outer joined to.

Yes, if you outer join t to t1, it'll take longer with your example then outer
joining t1 to t.  You process 1,000,000 rows with the first, 1,000 with the
second.

      Rating: 5
difference of
      time between dev and prod  October 03, 2001

Reviewer:  Chenna
Tom

The above query took 22 minutes to run in dev, where as in prod it took 5
minutes to run.

What can be the factors which contribute to the peformance improvement.

          Followup:

could be different plans.
could be different IO.
could be that one system has a sort area size of 100meg, the other 64k.
could be <almost anything here>

Use tkprof

http://asktom.oracle.com/~tkyte/tkprof.html
to analyze the performance on both systems.

      Rating: 5
How did the
      indentation tell you about the multiple full scans of the small
      table  October 03, 2001
Reviewer:  Jay

This , by far is the best discussion on sql tuning I have ever read. Thank you
very much to Tom.

/&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&/
.  The plan:

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=7 Card=845 Bytes=81965)
   1    0   FILTER
   2    1     TABLE ACCESS (FULL) OF 'BIG' (Cost=7 Card=845 Bytes=81965)
   3    1     TABLE ACCESS (FULL) OF 'SMALL' (Cost=1 Card=1 Bytes=4)

is telling us that for EACH ROW in big (16k of them) we did a FULL SCAN of small

( we scanned small 16k+ times).  I said that in my answer "The first query full
scans SMALL for every row in BIG:"

Statistics
----------------------------------------------------------
          0  recursive calls
     202743  db block gets
      84707  consistent gets
          0  physical reads
          0  redo size
       2010  bytes sent via SQL*Net to client
        430  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          8  rows processed

/&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&/

202743 +84707=287450

Let us assume that the small table 10 blocks , so by full scanning the samll
table 16000 times i.e. for each row in the big table, we have read 160000
blocks.

1.So 16,000 blocks  out of 287450 have been explained, what about the rest of
the block reads, a chunk of them are for reading the big table. I guess I am
right.

2.Will filtering also leading to reading of some blocks. Filtering is done in
the temporary segments, so the figure 287450 also includes the block reads in
the temp segment for filtering. Is my understanding right.

3.what do  the below signify
2010  bytes sent via SQL*Net to client
        430  bytes received via SQL*Net from client

4.How can I accurately divide the 287450 among the different operations which
went into fulfilling the above query. Infact how can we do it for any other
query.

5.Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=7 Card=845 Bytes=81965)
   1    0   FILTER
   2    1     TABLE ACCESS (FULL) OF 'BIG' (Cost=7 Card=845 Bytes=81965)
   3    1     TABLE ACCESS (FULL) OF 'SMALL' (Cost=1 Card=1 Bytes=4)

How is the indentation above and level telling you that for every row in big
there has been a full scan of small. What does indentation signify( I know that
it signifies the series of steps in excution , with the inner most happening
first, but how does it signify or is telling to you that for every row in big
there has been a full scan of small).

How are you arriving at that conclusion.

          Followup:

Everytime we full scan, we get the table info in current mode (so we know where
to scan).  In this case:

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly;
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from small;
9 rows selected.

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

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

thats the 200k plus db block gets -- reading the table information over and over
for small.

2) there were NO sorts and specifically no sorts to disk.  TEMP was not
involved. Oracle gets the first row from BIG, full scans SMALL and outputs that
row (or not).  Then it gets the second row and so on.  There is no temp space
usage in this query.

3) we sent 2k of data from sqlplus to the server, we recieved 1/2k of data in
response.

4) you cannot in general.  It is an aggregate number, comprised of the values
from potentially dozens of steps.  In this case, its easy -- I know its the
reads on the extent info for small.  In most cases, you don't know.  You can use
tkprof to see the number of rows flowing OUT of each step which can help -- but
then again, you might see 1 row flowing out of a full scan that contributed
10000 consistent gets so its not 100% clear.

that plan is saying "we full scan BIG and FILTER the results by FULL scanning
small".  FILTER implies "loop" -- that says

 for x in ( select * from big )
 loop
    for y in ( select * from small )
    loop

to me -- I don't know how to explain it better in text without a whiteboard I
guess.

      Rating: 3
Runtime
      evaluations  July 25, 2002
Reviewer:  Skip
      Eckles  from Conway, AR USA
Tom,
I am using CBO, and have a very large database (2.5Tb).  When I run my queries
they can take hours when running with the right plan, and days with the wrong
plan. Tkprof only shows what happened during the run, how can I see what is
happening? So I know if it will take hours or days?

          Followup:

set autotrace traceonly explain

will show you the plan that will be used in sqlplus

      Rating:
      4
  July 26, 2002
Reviewer:  Sagi
       from India.
Hi Tom !

Let me know whether my understanding is correct.

DBMS_STATS package gather the statistics automatically and we dont have to
execute ALTER TABLE X COMPUTE STATISTICS, when we are using ALTER TABLE X
MONITOR.

Regards,

          Followup:

No, that is not correct.

dbms_stats can be scheduled to run automagically using dbms_job (just like
analyze could be if you wanted)

dbms_stats has the ability to gather stats on the minimal set of tables -- just
tables that need it.  analyze table compute/estimate does not

      Rating: 3
Can you please,
      help me write this query better  August 30, 2003

Reviewer:  MIKE
SELECT batch_apis.ID, dosage_lkup.state,
components_lkup.component_name, manufacturer_lkup.NAME,
batch,
TO_CHAR (batch_apis.date_of_manuf, 'DD-MON-YYYY') manuf_date,
manuf_batch_no, batch_size, batch_size_units,
batch_apis.coa coa, batch_apis.description description,
TO_CHAR (batch_apis.received_date, 'DD-MON-YYYY') received_date,
LOCATION LOCATION,
batch_apis.comments comments
FROM batch_apis,
manufacturer_lkup,
products,
locations_lkup,
dosage_lkup,
components_lkup
WHERE RTRIM (LTRIM (batch_apis.ID)) = RTRIM (LTRIM ('BCHAPI828033017143'))
AND manufacturer_lkup.ID(+) = batch_apis.manufacturer_fk_id
AND products.ID(+) = batch_apis.product_fk_id
AND locations_lkup.ID(+) = received_location_fk_id
AND dosage_lkup.ID(+) = products.dosage_fk_id
AND components_lkup.ID(+) = products.component_fk_id;

          Followup:

i would question "why do I need to have rtrim(ltrim()) around my database
column?  do I have such seriously poor data entry validations?

i think

where batch_apis.id = 'BCHAPI828033017143'

would be correct and if not, you need to FIX that column and clean the data.
else, this is massive full scan city (assuming that an index on ID would make
sense of course)

and then i would question "are the outer joins really necessary or did someone
put them in there "just in case""

if they are "just in case", figure out if they are really needed.  they can kill
you.

      Rating: 5
Tuning SQL for
      annual totals  October 27, 2003
Reviewer:
      A reader
Hi Tom,

I am tuning an application at a client's site these days. We have a very large
TXNS table here (10m+ rows) and the application is written in ASP.NET so
everything is Dynamic SQL.

I have tuned the OLTP part of the system and now it's time to tune the reporting
part..

The queries for most reports are like:

SELECT
accountid,
sum(amount) total_all,
sum(case when txndate between to_date('20030401') and to_date('20040331') then
amount else 0 end) total_2003,
sum(case when txndate between to_date('20040401') and to_date('20050331') then
amount else 0 end) total_2004,
sum(case when txndate between to_date('20050401') and to_date('20060331') then
amount else 0 end) total_2005,
sum(case when txndate between to_date('20060401') and to_date('20070331') then
amount else 0 end) total_2006,
sum(case when txndate between to_date('20070401') and to_date('20080331') then
amount else 0 end) total_2007
FROM txns
WHERE
txndate between to_date('20030401') and to_date('20080331')
and accountid in (select accountid from accounts where accounttype = 'A');

The returned years could be anything between 1 - 20.

Do you think this query is the correct way to retrieve the result? If not, do
you have any suggestion for improvement?

Thanks.

          Followup:

well, the to_dates should have an explicit date format mask if you ask me -- i'd
never never rely on a default mask.

also, thats not the "real" query.  it is sort of missing a group by.

other then that, it looks dandy.  if the goal is to speed it up -- an index on
(accountid, txndate, amount) might be useful as it could be used as a "skinnier"
version of the existing transaction table (eg: not really as an index but as a
skinnier table -- I'm think fast full scan here of the index)

October 28, 2003 Bhavani -- Thanks for the question regarding "SQL
      Query", version 8.1.7

      You Asked
Hi Tom,

I have a table like this

create table temp1
(f_name varchar2(100),
 f_date date,
 f_amount integer
);

Records in this table will be like this:

Ajay             15-JUL-02     500
Bhavani          15-JUL-02     700
Chakri           17-JUL-02     200
Ajay             17-JUL-02     100

Given two dates(Say between 15-JUL-02 and 18-JUL-02), I want to get the Output
like this between those two dates.

               15-JUL-02    16-JUL-02        17-JUL-02     18-JUL-02
Ajay              500          0                 100           0
Bhavani           700          0                   0           0
Chakri              0          0                 200           0

I think, I have a way to do this.  Creating a View Dynamically using Execute
Immediate and getting the values from that view.

But I am wondering is there any better way to get data like this from a single
query.  Please Advise.

Sincerely
Bhavani

      and we said...
Since the number of columns is dynamic -- every time you run this "query" they
could be different -- you'll use a stored procedure which returns a dynamically
opened ref cursor (not a view -- a view would be useless here)

Here is an example of what I mean by that:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package demo_pkg
  2  as
  3      type rc is ref cursor;
  4
  5      procedure get_query( p_cursor in out rc, p_start date, p_end date );
  6  end;
  7  /

Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package body demo_pkg
  2  as
  3
  4  procedure get_query( p_cursor in out rc, p_start date, p_end date )
  5  is
  6      l_query long := 'select name ';
  7  begin
  8
  9      for i in 1 .. trunc(p_end)-trunc(p_start)+1
 10      loop
 11          l_query := l_query || ', sum( decode( trunc(d), ' ||
 12                          'to_date( ''' || to_char(p_start+i-1,'yyyymmdd') ||
 13                          ''', ''yyyymmdd'' ), amt, 0 )) "' ||
 14                          to_char(p_start+i-1) || '"';
 15      end loop;
 16      l_query := l_query || ' from t group by name';
 17      open p_cursor for l_query;
 18  end;
 19
 20  end;
 21  /

Package body created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autoprint on
ops$tkyte@ORA817DEV.US.ORACLE.COM> variable x refcursor
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec demo_pkg.get_query( :x, '15-jul-2002',
'18-jul-2002' );

PL/SQL procedure successfully completed.

NAME        15-JUL-02  16-JUL-02  17-JUL-02  18-JUL-02
---------- ---------- ---------- ---------- ----------
Ajay              500          0        100          0
Bhavani           700          0          0          0
Chakri              0          0        200          0

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec demo_pkg.get_query( :x, '14-jul-2002',
'18-jul-2002' );

PL/SQL procedure successfully completed.

NAME        14-JUL-02  15-JUL-02  16-JUL-02  17-JUL-02  18-JUL-02
---------- ---------- ---------- ---------- ---------- ----------
Ajay                0        500          0        100          0
Bhavani             0        700          0          0          0
Chakri              0          0          0        200          0

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

Review &
      Followup

      Rating: 5
SQL
      query  July 21, 2002
Reviewer:  munz
       from Reston, VA
TOm:

Based on this, can you always define a variable, define a string for it and the
opne cursor for it.

will this also work from in list search like:

l_list=('va','ca','pa');
l_query:='select * from table where col in '||l_list;
open cursor for l_query;

          Followup:

yes it will but it'll violate my "you better use bind variables" rule wouldn't
it.

Make sure to put an

 execute immediate 'alter session set cursor_sharing=force';
 open ...
 execute immediate 'alter session set cursor_sharing=exact';

around the dynamic open to lessen the impact.  I would prefer NOT to use this
approach at all.

      Rating:
      5
Great  July 21, 2002
Reviewer:
      Bhavani  from Dominican Republic
This Solves Lot of My problems.  Once again, you are the Best.

      Rating: 5
Ope
      ncursor  July 22, 2002
Reviewer:  munz
       from Reston, USA
TOm:

A followup to what you wrote:

execute immediate 'alter session set cursor_sharing=force';
execute immediate 'alter session set cursor_sharing='exact';

what doest this do?

Thanks

          Followup:

search this site for cursor_sharing and read all about it!

or read chapter 10 of my book "Expert one on one"

      Rating:
      5
Great!  July 22, 2002
Reviewer:
      A reader
Simple, concise suggestion and explanation of a useful development principle!

Thanks Tom!

      Rating: 3
that's cross
      tab query  July 22, 2002
Reviewer:  Bhavesh
      Tailor  from Charlotte, NC, USA
Hi Tom:

The above result can be easily obtained in MS Access using crosstab query. Why
Oracle does NOT provide crosstab query?

          Followup:

MS Access the GUI reporting tool can do a cross tab (and heck, you can use that
tool against Oracle if you like).

Oracle reports the GUI reporting tool -- does crosstabs.
Oracle Discover -- the GUI ad-hoc query tool -- does crosstabs.

We have it.  If you want to do it in PURE SQL in Access (eg: not using the GUI
reporting tool) what then?  Show me the code you would write in VB using Access
directly that will do a cross tab?  That is what we should be comparing here.

      Rating:
      4
  July 22, 2002
Reviewer:  Jim

Although not recommended, liked the example using
cursor_sharing parameter.

      Rating: 4
Very
      Nice  August 29, 2003
Reviewer:
      R.Chakravarthi  from Chennai,India
Dear Sir,
can I have a query that selects rows from all tables in my
schema.Is a single select possible?Please inform me if you
have a query like that.
Thanks.

          Followup:

you could write one that union alls all of the tables.

you would have to make every table look structurally the same (same number of
columns -- eg, select NULL to make up columns when a table doesn't have enough,
same types of columns -- eg, use to_char on number/dates to make everything a
string)

of what possible use this could ever be -- well, that I cannot fathom.

      Rating:
      3
Nice  September 02, 2003
Reviewer:
      R,Chakravarthi  from Chennai,India
Dear Sir,
Well and wish the same from you.I have some questions related to queries.
1)Suppose if I have a string like "Oracle",How to write a query that prints each
character in each line?
2)Have a look at the following queries.
   (i)select ename,deptno,sal from emp e1 where sal = (select max(sal) from emp
e2 where e1.deptno = e2.deptno) order by deptno;
   (ii)select ename,deptno,sal from emp where sal in(select
 max(sal) from emp group by deptno;
  My Question is Is there any other way that this query can
 be issued?
3) How to fix the "Single row subquery returns more than one
 row" message?
Expecting your reply.
Thanks

          Followup:

1)

ops$tkyte@ORA920> variable x varchar2(20)
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec :x := 'Oracle'

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select substr( :x, rownum, 1 )
  2    from all_objects
  3   where rownum <= length(:x);

S
-
O
r
a
c
l
e

6 rows selected.

2) given that you gave me 2 totally different queries that return 2 totally
different answers -- I'd have to say "i must be able to give an infinite set of
responses since you are just looking for random queries" :)

ops$tkyte@ORA920> drop table emp;

Table dropped.

ops$tkyte@ORA920> create table emp as select * from scott.emp;

Table created.

ops$tkyte@ORA920> drop table dept;

Table dropped.

ops$tkyte@ORA920> create table dept as select * from scott.dept;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> update emp set sal = 2000;

14 rows updated.

ops$tkyte@ORA920> update emp set sal = 3000 where rownum = 1;

1 row updated.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select ename,deptno,sal
  2    from emp e1
  3   where sal = (select max(sal)
  4                  from emp e2
  5                 where e1.deptno = e2.deptno)
  6   order by deptno;

ENAME          DEPTNO        SAL
---------- ---------- ----------
CLARK              10       2000
KING               10       2000
MILLER             10       2000
SMITH              20       3000
ALLEN              30       2000
TURNER             30       2000
JAMES              30       2000
WARD               30       2000
MARTIN             30       2000
BLAKE              30       2000

10 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select ename,deptno,sal
  2    from emp
  3   where sal in (select max(sal)
  4                   from emp
  5                  group by deptno );

ENAME          DEPTNO        SAL
---------- ---------- ----------
ALLEN              30       2000
WARD               30       2000
JONES              20       2000
BLAKE              30       2000
SCOTT              20       2000
TURNER             30       2000
JAMES              30       2000
MILLER             10       2000
FORD               20       2000
ADAMS              20       2000
KING               10       2000
CLARK              10       2000
MARTIN             30       2000
SMITH              20       3000

14 rows selected.

see, totally different answers to your query with the same data  This is why
I have a chapter in my new book on "don't tune a query, tune a question"

I have to assume that you want the set of people, by department, that make the
TOP salary in that dept...  queries such as:

select ename, deptno, sal
  from ( select ename, deptno, sal,
                rank() over ( partition by deptno order by sal desc nulls last )
r
           from emp )
 where r = 1;

select ename, deptno, sal
  from ( select ename, deptno, sal,
                max(sal) over ( partition by deptno ) max_sal
           from emp )
 where sal = max_sal
/

select ename, deptno, sal
  from ( select ename, deptno, sal,
                first_value(sal) over ( partition by deptno order by sal desc
nulls last ) first_sal
           from emp )
 where sal = first_sal
/

will do that.

3) there is nothing to fix here.

You have a logic/algorithm problem.

You are basically saying something like:

select * from t where x = ( subquery )

and subquery ISN'T a single value but a set.  what would you have us do?  you
need to phrase your problem in english and then we might be able to tell you how
to solve it.

the error "single row subquery" isn't an "oracle" error -- it is a bug in your
logic/code/data....

      Rating:
      3
Nice  September 03, 2003
Reviewer:
      R.Chacravarthi  from Chennai,India
Dear Sir,
 Thanks for your reply.My second question was
   2)I have put a query which returns highest salary getting employees
departmentwise.Both the queries return the same resultset in my system.I use
oracle v8.0 personal edition.Queries and their resultsets follow after this.
** What I asked you was "Is there any other way that this query can be
expressed?".But you are agonising over my question.I don't know why
Any how Thanks for your time spending.If you have any other way of expressing
the same query,Please do write a follow up.

SQL> select ename,sal,deptno from emp e1 where sal = (select max(sal) from
  2  emp e2 where e1.deptno = e2.deptno) order by deptno;

ENAME             SAL     DEPTNO
---------- ---------- ----------
KING             5000         10
SCOTT            3000         20
FORD             3000         20
BLAKE            2850         30

SQL> select ename,sal,deptno from emp where sal in(select max(sal) from emp
  2  group by deptno) order by deptno;

ENAME             SAL     DEPTNO
---------- ---------- ----------
KING             5000         10
SCOTT            3000         20
FORD             3000         20
BLAKE            2850         30

SQL> spool off

          Followup:

your queries -- in general -- do NOT RETURN THE SAME data -- i proved that.  so
what if you data is setup such that they do not.  they are *not* the same query.
 they ask totally totally different questions.  they are not even a little
comparable.

that is why i "agonized" over this -- you are making a very very very false
assumption.  One cannot tell you a 3rd way to write a query given that the first
two queries are not even remotely similar.

remember -- it only takes one counter case to prove something wrong.  your
queries are not similar.

no matter how many times you run them -- they are not similar. they are not the
same.

I did try to answer your question which I asssume was "find the highest paid
people by deptno".  I gave you many examples.  your first query does that.  your
second query DOES NOT..

your second query returns the set of people who make as much as the highest paid
person IN ANY DEPTNO.  not just theirs -- ANY deptno.

(i'm trying to teach you how to read the sql so you do not make errors like
this, that is the "agonization" here)....

      Rating:
      3
Nice  September 08, 2003
Reviewer:
      R.Chacravarthi  from Chennai,India
Dear sir,
well and wish the same from you.
1)I tried to put a query which groups employees under their managers.But it
always returned errors.I tried a pl/sql block which returned the result set I
was looking for.Can this anonymous block be transformed into a sql query?Could
you please help? Please don't forget to specify the different formats of the
same query.
Thanks in advance.
SQL> begin
  2   for x in (select distinct mgr from emp order by mgr) loop
  3     dbms_output.put_line('Manager:'||x.mgr);
  4    for y in (select ename from emp where mgr = x.mgr) loop
  5      dbms_output.put_line(y.ename);
  6    end loop;
  7    dbms_output.put_line(' ');
  8   end loop;
  9  end;
 10  /
Manager:7566
SCOTT
FORD
Manager:7698
ALLEN
MARTIN
TURNER
JAMES
Manager:7782
MILLER
Manager:7788
ADAMS
Manager:7839
JONES
BLAKE
CLARK
JOE
Manager:7902
SMITH
Manager:

PL/SQL procedure successfully completed.

SQL> spool off
2)Is there a query format like
   select 'select * from a' from b;
 If yes,please provide some examples.
 Thanks for your previous reply.

          Followup:

  1  select mgr, cursor( select ename from emp emp2 where emp2.mgr = emp.mgr )
  2* from ( select distinct mgr from emp ) emp
ops$tkyte@ORA920LAP> /

       MGR CURSOR(SELECTENAMEFR
---------- --------------------
      7566 CURSOR STATEMENT : 2

CURSOR STATEMENT : 2

ENAME
----------
SCOTT
FORD

      7698 CURSOR STATEMENT : 2

CURSOR STATEMENT : 2

ENAME
----------
ALLEN
WARD
MARTIN
TURNER
JAMES

is one way.

ops$tkyte@ORA920LAP> break on mgr skip 1
ops$tkyte@ORA920LAP> select mgr, ename from emp order by mgr, ename;

       MGR ENAME
---------- ----------
      7566 FORD
           SCOTT

      7698 ALLEN
           JAMES
           MARTIN
           TURNER
           WARD

      7782 MILLER

      7788 ADAMS

      7839 BLAKE
           CLARK
           JONES

      7902 SMITH

           KING

14 rows selected.

is perhaps another.

      Rating: 4
Sql
      Query  September 09, 2003
Reviewer:  archary
       from India
Hi Tom,
Need your help.
For example I have tables and data as bellow.
I have same empname in all deptno with diff in sal.
If there is no employee in a deptno. still i have to display empname with sal =
0.
Please help. Thanks in advance.

SQL> desc tt
 Name                                      Null?    Type
 ----------------------------------------- -------- -------- ENAME
                          VARCHAR2(10)
 SAL                                           NUMBER
 DNO                                           NUMBER

SQL>
SQL>
SQL> select * from tt;

ENAME             SAL        DNO
---------- ---------- ----------
A                 100         10
A                 200         10
A                 300         20

Elapsed: 00:00:00.66
SQL> desc t
 Name                                      Null?    Type
 ----------------------------------------- -------- -------- DNO
                             NUMBER

SQL> select * from t;

       DNO
----------
        10
        20
        30

Like to display output as bellow.

ENAME             SAL        DNO
---------- ---------- ----------
A                 100         10
A                 200         10
A                 300         20
A                 0           30

          Followup:

where the heck did the ename come from?????? if there is no emp in dno=30, how
do they have a name????

in general, this is an outer join

select ename, nvl(sal,0), dno
  from tt, t
 where tt.dno(+) = t.dno;

      Rating: 4
Sql
      Query  September 10, 2003
Reviewer:  archary
       from india
Hi,
Thank you very much.
My intension is
In a year 2003 I have fixed customers for 12 months.
So I have year and month info in one table.
My senario is I haven't made any sales to my fixed customers in some months in a
year.
This info I am not logging in my transaction table.
Means I don't have any info about customers for few months.
So in this case I need to generate a report for 12 months. If no sales in a moth
still i have to show customer name with zero amount.

SQL> desc t_cust
 Name                                      Null?    Type
 ----------------------------------------- -------- -------
 CNO                                                NUMBER
 AMT                                                NUMBER
 MTH                                                NUMBER
 YR                                                 NUMBER

SQL> select * from t_cust;

       CNO        AMT        MTH         YR
---------- ---------- ---------- ----------
      1001        100          1       2003
      1001        200          2       2003
      1001          4          4       2003
      1001        500          2       2003
      1002       2000          1       2003
      1002       3000          2       2003
      1002       2000          3       2003
      1002       3000          3       2003
      1002       6000          6       2003
SQL> desc myr
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------
 MTH                                                NUMBER
 YR                                                 NUMBER

SQL> select * from myr;

       MTH         YR
---------- ----------
         1       2003
         2       2003
         3       2003
         4       2003
         5       2003
         6       2003
         7       2003
         8       2003
         9       2003
        10       2003
        11       2003
        12       2003
Please provide solution. Thanks in advance.
I am able to get for one customer.
select mth, yr, cno, sum(amt)
from
(
select a.mth mth, a.yr yr,
decode(a.cnt,0,(select unique cno from t_cust where cno = 1001),cno) cno,
nvl(a.amt,0) amt, a.c
from
(
select cno cno, myr.mth mth, myr.yr yr, amt amt, count(cno) over ( partition by
myr.mth ) cnt
from t_cust, myr
where t_cust.mth(+) = myr.mth
and t_cust.yr(+) = myr.yr
and t_cust.cno(+) = 1001
) a
)
group by mth, yr, cno

          Followup:

you need to join your list of distinct customer ids to the year/month table --
and then outer join to that.

outer join to:

select year, month, cust_id
  from year_month_table, (select distinct cust_id from customer_table)

      Rating:
      3
query  October 17, 2003
Reviewer:
      A reader
Hi Tom
I have a data in two table as given below

Table..A
     colA1  colA2
       1     null
       2     null
       3     null
       4     11
       5     null
       6     null

Table..B
       colB1 colA2
       1     11
       2     12
       3     13

I want to update column colA2 of table..A with colA2 of tableB
in a single statement.The final result of Table..A is  like this

Table..A
     colA1  colA2
       1     null
       2     12
       3     null
       4     11
       5     13
       6     null

Here Value 11 already exist in TableA so need not to update
so the rest  12,13 from TableB should update in TableA
any order.

Can you please give me the solution .

          Followup:

search for

update join

on this site

      Rating: 4
Sql Query to
      cut string  October 23, 2003
Reviewer:  ARC
       from S'Pore
Hi Tom,
Need your help to write query to cut a string into parts.
I have data as bellow.

Table : TB_ODW_GO

REF_NBR LOG_DESC
----------------------------------------------------------
0234567 -111: ORDER:10901 NOT FOUND-222 ZIPCODE: NOTFOUND
1274033 -111: ORDER:12343 NOT FOUND

I want to display output as bellow.

REF_NBR ERR_CD ERR_MSG
-----------------------------------------------------------
0234567 -111   ORDER:10901 NOT FOUND
0234567 -222   ZIPCODE: NOTFOUND
1274033 -111   ORDER:12343 NOT FOUND

Thanking you in advance.

ARC.

          Followup:

you want a single row as two?  is that correct?  is it "two" or "n"

      Rating: 4
Sql Query
        October 28, 2003
Reviewer:  ARC  from
      S'Pore
Hi Tom,
I need it for 'n' lines.

Thanks
ARC

          Followup:

you'll be doing it procedurally -- in 9i, you can write a pipelined function to
do it.

something like this.  I made a simplifying assumping that the string was
formated as

-<ERROR_CODE>: <MESSAGE>-<ERROR_CODE>: <MESSAGE>

that is, - precedes error code, error code terminated by :.  If that is not your
case, you need to write the code to parse your string:

ops$tkyte@ORA920PC> create table t ( ref_nbr number, log_desc varchar2(80) );

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert into t values ( 0234567, '-111: ORDER:10901 NOT
FOUND-222: ZIPCODE: NOTFOUND' );

1 row created.

ops$tkyte@ORA920PC> insert into t values ( 1274033, '-111: ORDER:12343 NOT
FOUND' );

1 row created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create or replace type myScalarType as object
  2  ( ref_number number, err_cd number, err_msg varchar2(40) )
  3  /

Type created.

ops$tkyte@ORA920PC> create or replace type myTableType as table of myScalarType
  2  /

Type created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create or replace function parse( p_cur in sys_refcursor )
  2  return myTableType
  3  PIPELINED
  4  as
  5      type array is table of t%rowtype index by binary_integer;
  6      l_data array;
  7      l_rec  myScalarType := myScalarType(null,null,null);
  8      l_tmp  long;
  9      n      number;
 10  begin
 11      loop
 12          fetch p_cur bulk collect into l_data limit 100;
 13          for i in 1 .. l_data.count
 14          loop
 15              l_rec.ref_number := l_data(i).ref_nbr;
 16              l_data(i).log_desc := l_data(i).log_desc || '-';
 17
 18              loop
 19                  n := instr( l_data(i).log_desc, '-', 2 );
 20                  exit when (nvl(n,0)=0);
 21
 22                  l_tmp := substr( l_data(i).log_desc, 1, n-1);
 23                  l_data(i).log_desc := substr( l_data(i).log_desc, n );
 24
 25                  n := instr( l_tmp, ':' );
 26                  l_rec.err_cd  := substr(l_tmp, 1, n-1 );
 27                  l_rec.err_msg := trim( substr( l_tmp, n+1 ) );
 28                  pipe row(l_rec);
 29              end loop;
 30          end loop;
 31          exit when p_cur%notfound;
 32      end loop;
 33      close p_cur;
 34      return;
 35  end;
 36  /

Function created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select * from TABLE( parse( cursor(select * from t) ) );

REF_NUMBER     ERR_CD ERR_MSG
---------- ---------- ----------------------------------------
    234567       -111 ORDER:10901 NOT FOUND
    234567       -222 ZIPCODE: NOTFOUND
   1274033       -111 ORDER:12343 NOT FOUND

      Rating: 4
Sql Query cut
      string   October 30, 2003
Reviewer:  ARC
       from S'pore
Tom,
Thanks for solution. Really it is very good and also I came to know about
"pipelined" with this solution which I am not aware of before.
The same thing I achived using PL/SQL records and table in version 8.1.7
Thanks again.

ARC

October 28, 2003 Saminathan -- Thanks for the question regarding
      "(Single Trigger with IF condition) OR (Multiple Trigger)", version
8.1.7.3

      You Asked
Dear Tom,

Requirement:-
-------------
1) UpdateDate column should up updated for each update(row level)
2) CreateDate column should be updated for each insert(row level)

Question:-
----------
Which one of the following method would be better?

1) One trigger with IF condition(insert or update)
2) TWO separate trigger for INSERT and UPDATE

--------------------------------------------------
1) create or replace trigger trig_01
 ...
before INSERT OR UPDATE
begin

if INSERTING then
:new.createdate := sysdate;
:new.update := sysdate;
end if;

if UPDATING then
:new.update := sysdate;
end if;

end;
-------------------------------------------------
2)
A)
create or replace trigger trig_01
 ...
before INSERT

begin
:new.createdate := sysdate;
:new.update := sysdate;
end;

B)
create or replace trigger trig_02
 ...
before UPDATE

begin
:new.update := sysdate;
end;

----------------------------------------------------------

Thanks
Sami

      and we said...
how about one trigger and defaults?

create table t ( ....., createdate date default sysdate, updatedate date default
sysdate )

create trigger t before update on t for each row
begin
   :new.updatedate := sysdate;
end;
/

that'll have the least impact on inserts (no impact) and require but one trigger
on the update.
Review & Followup

      Rating: 4
but we need
      change in timezone and replication should be
      handled  October 27, 2003
Reviewer:  Saminathan
      Seerangan  from NJ,USA
Dear Tom,

Thank you so much for your prompt response.

But we have to handle replication and timezone change(to GMT)

For example
A)
IF (DBMS_REPUTIL.FROM_REMOTE = FALSE)
....
...

also timezone should be stored in GMT format.
B)
:NEW.CREATEDATE := NEW_TIME(SYSDATE,GETZONE,'GMT');

So we cannot simply go with DEFAULT values.

In this scenario what would you prefer? I mean (1)Single Trigger with IF
condition OR (2)Multiple Trigger

Thanks again
-Sami

          Followup:

single trigger works for me.  since both must always set updatedate anyway.

      Rating: 4
Ignore
      Replication (A) issue from my previous review  October 27,
      2003
Reviewer:  A reader

      Rating: 3
Is it the
      same?  October 28, 2003
Reviewer:  Frank
       from NL
Maybe I am overlooking something, but your solution is not exactly the same as
the original poster's:
You provide a default (= overrulable) value for creationdate.

If I insert ...(creationdate,...) values (sysdate - 1, ...), your solution will
provide different results then the original

So you end up needing an insert trigger I guess.

          Followup:

you would not insert those values obviously.

October 28, 2003 Vijay -- Thanks for the question regarding
      "Caching a table in Memory", version 8i

      You Asked
Hi,
Is it enough if we use
ALTER TABLE <table_name> CACHE; to push the table to cache ?.
Do I need to do anything else to have the table in the Memory ?

Thx, VJ

      and we said...
That command simply makes a notation in the data dictionary that
blocks from this table should be handled differently when they
get into the cache.  Usually, when we full scan an object, we
put the blocks onto the least recently end of the list.  These
blocks are candidates for "aging" from the buffer cache.  By
altering the table to 'cache', we put the blocks onto the most
recently used end -- making them less prone to being aged out of
the buffer cache.

From the sql reference manual we see:

...
for data that is accessed frequently, specifies that the blocks
retrieved for this table are placed at the most recently used
end of the LRU list in the buffer cache when a full table
scan is performed. This attribute is useful for small lookup
tables.
...

So, no -- issueing the alter table ... cache command does not
put the table into memory, you must full scan the table for that
to happen.

Consider looking into setting up multiple buffer pools and
associating this table with its own buffer pool if you are dead
serious about caching it.  Bufrer pools are

<quote tuning manual>
Schema objects are referenced with varying usage patterns;
therefore, their cache behavior may be quite different. Multiple
buffer pools enable you to address these differences. You can
use a KEEP buffer pool to maintain objects in the buffer cache
and a RECYCLE buffer pool to prevent objects from consuming
unnecessary space in the cache. When an object is allocated to a
cache, all blocks from that object are placed in that cache.
Oracle maintains a DEFAULT buffer pool for objects that have
not been assigned to one of the buffer pools.
</quote>

Bear in mind tho... there is really no true way to a have a
purely "in memory" table -- even cached tables are subject to
aging from the buffer cache.
Review &
      Followup

      Rating: 5
Finding which
      tables to cache  October 27, 2003
Reviewer:
      A reader
Hi Tom,

What is your approach to find which tables to cache in SGA?

In a properly tuned system, this caching should be unnecessary and won't give
much benefit, is it correct?

Thanks,

          Followup:

you normally should not ever need to use this, it only comes into play when you
full scan a table that exceeds the short table threshold -- causing us to cache
it differently.

it is rare to do that.
you would know you are doing it.

i've used it once in my experience.

      Rating: 5
How about alter
      table ... storage (buffer_pool keep)?  October 27, 2003

Reviewer:  A reader
Thanks Tom.

Do you mean that I should use "alter table ... storage (buffer_pool keep)"
instead of "alter table ... cache", for small frequently-accessed look-up
tables?

          Followup:

not really -- they were always "fully cachable" as is.

I ascribe to the 99/1 rule (variation on the 80/20 rule).  the buffer cache is
fairly efficient.  If it was meant to be cached, and the memory exists, it will
be cached.  small, frequently accessed lookup tables would be in the cache by
"popular demand" 99% of the time already.

      Rating:
      4
  October 27, 2003
Reviewer:  Dave
       from Colorado
One of the situations for getting involved in setting the buffer pool attribute
might be in a partitioned fact table -- you might choose to set the most recent
few partitions to "keep", and the older ones to "recycle", thus helping to
prevent a more rare scan of older data from spoiling the day of the majority of
users who access recent data.

      Rating: 3
what about
      large tables  October 28, 2003
Reviewer:
      Duncan  from UK
From what was said above am I to take it that putting large tables in the buffer
keep pool is going to be pointless because if they take up most of the pool then
they'll get aged out as soon as a new object requires the space in memory to be
cached?

          Followup:

no?

that is not what it means.

blocks are aged out of buffer pools based on how often they are used.  So, if
you put large tables in a keep pool AND you use their blocks lots (more then
other blocks), they'll stay in there.

      Rating: 5
What about
      Performance  October 28, 2003
Reviewer:
      A reader  from Santo Domingo, Dominican Republic

Is there any sample about the performance impact of caching tables.

          Followup:

blocks are always cached.

the alter table cache command is somewhat of a misnomer.  it simply changes the
way a full scan of a LARGE table would be treated in the buffer cache.  It
doesn't "cache a table".

Performance is relative.

alter table cache can kill performance.
alter table cache can enhance positively performance.
alter table cache can have zero effect on performance.

it is a tool, that can sometimes - in rare cases (eg: example posted by another
reader -- Dave -- above) be used.

October 28, 2003 Rich -- Thanks for the question regarding
      "Analytic Functions", version 8.1.7

      You Asked
Tom,

Thanks for providing this forum for answering questions. I'm trying to get the
hang of analytic functions and I'm having a problem with the following code. I
have to comment out "where dr <= 3)" to get it to work. "dr" shows up as the
column heading but I add my conditional statement I get invalid column name.
Help!

Ps. I have your book and I hope you sign on the dotted line for the next one.

select *
FROM (select candidate_id,
             deptid,
             actual_start_date,
             annual_sal_amt,
             dense_rank()
             over (partition by deptid
                   order by annual_sal_amt desc) dr
             from obapp1.offer
             where actual_start_date >= '01-JAN-2002'
             and annual_sal_amt > 0)
--           where dr <= 3)
order by deptid,
         annual_sal_amt desc

      and we said...
scott@ORA817DEV.US.ORACLE.COM> select *
  2  FROM (select empno,
  3               deptno,
  4               hiredate,
  5               sal,
  6               dense_rank()
  7               over (partition by deptno
  8                     order by sal desc) dr
  9               from emp
 10               where hiredate >= to_date( '01-JAN-1980', 'dd-mon-yyyy' )
 11               and sal > 0)
 12           where dr <= 3
 13  order by deptno, sal desc
 14  /

     EMPNO     DEPTNO HIREDATE         SAL         DR
---------- ---------- --------- ---------- ----------
      7839         10 17-NOV-81       5000          1
      7782         10 09-JUN-81       2450          2
      7934         10 23-JAN-82       1300          3
      7788         20 09-DEC-82       3000          1
      7902         20 03-DEC-81       3000          1
      7566         20 02-APR-81       2975          2
      7876         20 12-JAN-83       1100          3
      7698         30 01-MAY-81       2850          1
      7499         30 20-FEB-81       1600          2
      7844         30 08-SEP-81       1500          3

10 rows selected.

You have to apply the constraint against the analytic function (what you call
DR) outside of the inline view.  The anlytic functions have to be evaluated AND
THEN you can "where" on them.  Simply put the where dr <= 3 where I have it.
Review &
      Followup

      Rating: 4
Analytic
      Functions  March 25, 2002
Reviewer:  Rich
      Sekunda  from Montvale, NJ
Thanks for pointing that out. These new (for me and my application) functions
are full of possibilities but will take a little getting used to.

Again, thanks for your help!

      Rating: 4
groupping
      analytic functions  September 11, 2002
Reviewer:
      A reader
hi tom

I have this query

select deptno,job, sum(sal), sum(sal) over ()
from emp
group by deptno, job, sal

it works but I dont understand why shouldnt it be

select deptno,job, sum(sal), sum(sal) over ()
from emp
group by deptno, job, sum(sal) over ()

???

          Followup:

because analytics are done last.  You probably mean to do:

  1  select a.*, sum(sal) over ()
  2  from (
  3  select deptno,job, sum(sal) sal
  4  from emp
  5  group by deptno, job
  6* ) a
scott@ORA920.US.ORACLE.COM> /

DEPTNO JOB              SAL SUM(SAL)OVER()
------ --------- ---------- --------------
    10 CLERK           1300          29025
    10 MANAGER         2450          29025
    10 PRESIDENT       5000          29025
    20 ANALYST         6000          29025
    20 CLERK           1900          29025
    20 MANAGER         2975          29025
    30 CLERK            950          29025
    30 MANAGER         2850          29025
    30 SALESMAN        5600          29025

9 rows selected.

I think

      Rating: 4
what the
      difference between  September 11, 2002
Reviewer:
      A reader
select a.*, sum(sal) over ()
from (
select deptno, sum(sal) sal
from emp
where deptno in (10, 20)
group by deptno, sal) a

and

select deptno, sum(sal), sum(sal) over ()
from emp
where deptno in (10, 20)
group by deptno, sal

?

          Followup:

besides that they give different results in general?

ops$tkyte@ORA920.US.ORACLE.COM> select * from emp
  2  /

DEPTNO        SAL
------ ----------
    10        100
    10        100
    10        100
    10        100
    10        100
    20        200
    20        200
    20        200
    20        200

9 rows selected.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select a.*, sum(sal) over ()
  2  from (
  3  select deptno, sum(sal) sal
  4  from emp
  5  where deptno in (10, 20)
  6  group by deptno, sal) a
  7  /

DEPTNO        SAL SUM(SAL)OVER()
------ ---------- --------------
    10        500           1300
    20        800           1300

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select deptno, sum(sal), sum(sal) over ()
  2  from emp
  3  where deptno in (10, 20)
  4  group by deptno, sal
  5  /

DEPTNO   SUM(SAL) SUM(SAL)OVER()
------ ---------- --------------
    10        500            300
    20        800            300

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

The query:

select deptno, sum(sal), sum(sal) over ()
from emp
where deptno in (10, 20)
group by deptno, sal

gets the result set:

select deptno, sum(sal), SAL
  from emp
 where deptno in ( 10, 20 )
 group by DEPTNO, SAL

and then applies the analytic function (they are dead last) to the SAL column --
which since you grouped by it roles all of the same values into one row.  Doing
that -- you get the sum of the DISTINCT sal's

I got the sum of the summed sals.

      Rating: 5
conditional
      anlytic functions  October 30, 2002
Reviewer:
      raju
can the analytic function be evaluated based on a condtion for that function
only

we have the surveys, i need to display the survey status(live or not)

SQL>
SQL>    SELECT
  2     sur.ppl_survey_question_start_date  surve_start_date,
  3     sur.ppl_survey_question_desc survey_desc,
  4     decode(SUBSTR(dr,9),ROWID,'Live',NULL) survey_status
  5     FROM
  6     (
  7     SELECT
  8     ppl_survey_question_start_date,
  9     ppl_survey_question_desc ,
 10     MAX(to_char(ppl_survey_question_start_date,'YYYYMMDD') || ROWID)
over(PARTITION BY ppl_division_id) dr
 11     FROM
 12     ppl_survey_question_ref
 13     ) sur
 14  /

SURVE_START_DATE SURVEY_DESC
                 SURVEY_STATUS
----------------
--------------------------------------------------------------------------------
-------------
10/25/2002       What do you think?

10/26/2002       What do you think?

10/27/2002       What do you think?

11/1/2002        How many questions are in this survey?
                 Live

i need to display the survey status as live only when the survey start date <=
sysdate. surveys whose start dates are greater than sysdate
should n't be live

in the above query survey with start date 10/27/2002 should be live

i wrote the following query:
SQL>    SELECT
  2     sur.ppl_survey_question_start_date  surve_start_date,
  3     sur.ppl_survey_question_desc survey_desc,
  4     decode(SUBSTR(dr,9),ROWID,'Live',NULL) survey_status
  5     FROM
  6     (
  7     SELECT
  8     ppl_survey_question_start_date,
  9     ppl_survey_question_desc ,
 10
MAX(to_char(decode(sign(ppl_survey_question_start_date-trunc(SYSDATE)),1,to_date(
'01010001','ddmmyyyy'),ppl_survey_question_start_date),'YYYYMMDD') || ROWID)
over(PARTITION BY ppl_division_id) dr
 11     FROM
 12     ppl_survey_question_ref
 13     ) sur
 14  /

SURVE_START_DATE SURVEY_DESC
                 SURVEY_STATUS
----------------
--------------------------------------------------------------------------------
-------------
10/25/2002       What do you think?

10/26/2002       What do you think?

10/27/2002       What do you think?
                 Live
11/1/2002        How many questions are in this survey?

i want to know, is there a better way to do this?

and also is dense_rank be better than the max function

          Followup:

I'm not sure why you are using analytics here at all???

select ppl_survey_question_start_date, ppl_survey_question_desc,
       case when ppl_survey_question_start_date <= sysdate then 'LIVE'
            else 'NOT LIVE'
       end status
 from ppl_survey_question_ref
/

      Rating: 3
conditional
      analytic functions  October 31, 2002
Reviewer:
      raju
tom,

  i don't want all the surveys to be live, but i want the surveys to be live
whose start_date =
max start date and less than today and for a division

we can only have one live survey at a time for a division

for example

if today is : 10/31/2002

survey id     start date   status    Division
----------    ----------   -------   --------
1             10/27/2002   Live      abc
2             11/2/20002             abc
3             10/26/2002   Live      cde
4             12/1/2002              abc
5             10/24/2002             cde
6             11/21/2002             cde
7             10/26/2002             abc

thanks

          Followup:

then your selecting of

max( start_date || rowid ) over ( partition by division) max_start_rowid

was good and you just need to:

select ...
       decode( start_date || rowid, max_start_rowid, 'LIVE', null ) status
  from ( .....

is appropriate

      Rating: 4
unable to
      allocate memory error with analytic function  November 18,
      2002
Reviewer:  raju
the following ref cursor gives with anlytic function an error:

ORA-04031: unable to allocate 4200 bytes of shared memory ("shared pool","SELECT
ppl_content_typ_c ...","library cache","kkslpkp - literal info.")
ORA-06512: at "PCSFB.PPL_HOME_PKG", line 78 ORA-06512: at line 1

  OPEN o_content_details FOR
   SELECT
   ppl_content_typ_c content_type,
   ppl_content_hdng content_heading,
   ppl_content_t content_desc,
   ppl_content_url content_url,
   ppl_video_url video_url,
   ppl_audio_url audio_url
   FROM
   (
   SELECT
   a.ppl_content_typ_c,
   a.ppl_content_hdng,
   a.ppl_content_t,
   a.ppl_content_url,
   a.ppl_video_url,
   a.ppl_audio_url,
   dense_rank() over (PARTITION BY a.ppl_content_typ_c ORDER BY
decode(ppl_divsn_id_c ,'ALL',1,0) ASC,a.ppl_content_eff_d DESC NULLS LAST) dr
   FROM ppl_tcontent a WHERE a.ppl_content_eff_d <= v_curr_date AND
a.ppl_divsn_id_c IN ('ALL',v_divsn_id)) WHERE dr = 1;

i don't understand why is it giving that error as this is static
can you guide where i can use bind variables to avoid this error?

purpose of the query is : we have contents for each division, we need to pick
the content that has max effective date and which is less than sysdate

          Followup:

you've trashed the shared pool -- it is full.  flush it or restart (so its
"clean") and this query won't do that.  It's in the parse phase -- trying to
allocate library cache and you've plain run out.

Most likely -- you aren't using bind variables elsewhere in your system or your
shared pool really is too small (thats very rare, the bind variable thing -- for
whatever reason, is not unfortunately)

      Rating: 5
What are the
      side-effects of Flushing the shared Pool ?  November 19,
      2002
Reviewer:  pasko  from Hamburg,Germany

Hi Tom,
Thanks for a Great Website + Great Answers!

Beside the fact that Flushing the shared Pool would remove all cached pl/sql and
cursors,  what are other side-effects of flushing the shared Pool..

Thanks and best regards.

          Followup:

that is the side effect.

if you find you NEED to flush the shared pool on a recurring basis - you are
killing performance, limiting scalability and need to find the app that isn't
using bind variables and fix it.

      Rating: 5
unable to
      allocate memory error with analytic function   November 19,
      2002
Reviewer:  raju
Tom,

thanks for the response.Even we flush shared pool, after sometime we keep
getting this error:

earlier(for around 1 month) it was working well, all of sudden we are getting
this error,

this is the rest of the stored proc code,

can you please tell me am i not using bind variables any where?

   BEGIN
      --
      SELECT
      ppl_survey_question_id,
      ppl_survey_question_desc
      INTO
      v_survey_id_c,
      v_survey_t
      FROM
      (
      SELECT
      ppl_survey_question_id,
      ppl_survey_question_desc,
      ppl_division_id,
      ppl_survey_question_start_date
      FROM
      ppl_survey_question_ref
      WHERE   ppl_division_id IN ('ALL',v_divsn_id)
      AND   ppl_survey_question_start_date <= TRUNC(SYSDATE)
      ORDER BY decode(ppl_division_id,'ALL',1,0) ASC,
ppl_survey_question_start_date DESC NULLS LAST
      )
      WHERE
      /*NOT EXISTS (SELECT 1 FROM ppl_tem_survey_choice emsr
                  WHERE emsr.ppl_survey_id_c = srv.ppl_survey_id_c
                  AND   emsr.sid_em_id_c     = i_emp_id)
      AND*/
      ROWNUM = 1;
      BEGIN
         SELECT
         'Y'
         INTO
         v_survey_answered_ind
         FROM
         ppl_employee_survey_answer res
         WHERE res.sid_employee_id = i_emp_id
         AND   res.ppl_survey_question_id = v_survey_id_c
         AND   rownum = 1;
      EXCEPTION
         WHEN no_data_found THEN
            v_survey_answered_ind := 'N';
      END;
      --
      OPEN o_survey FOR
      SELECT
      v_survey_id_c survey_id,
      v_survey_t    survey_desc,
      v_survey_answered_ind question_answered
      FROM dual;
      --
      OPEN o_survey_choice FOR
      SELECT
      chc.ppl_survey_answer_id choice_seq,
      chc.ppl_survey_answer_desc choice_desc
      FROM
      ppl_survey_answer_ref chc
      WHERE chc.ppl_survey_question_id = v_survey_id_c;
      --
   EXCEPTION
      WHEN no_data_found THEN
         OPEN o_survey FOR
         SELECT 1 FROM dual WHERE 1 = 0;
         OPEN o_survey_choice FOR
         SELECT 1 FROM dual WHERE 1 = 0;
   END;

thanks

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1163635055580
to see if you are using binds.

STATIC sql in plsql is always 100% bind friendly.

      Rating: 3
I don't get the
      same results as in your book, p 546  February 11, 2003

Reviewer:  Jon  from Hartford, CT USA
At first I thought I was typing it in wrong, but I downloaded the .sql file and
did a cut and paste.  Why am I not getting my running totals as shown in the
book?

SQL> break on deptno skip 1
SQL> select ename, deptno, sal,
  2         sum(sal) over
  3                (order by deptno, ename) running_total,
  4         sum(sal) over
  5                (partition by deptno
  6                 order by ename) department_total,
  7         row_number() over
  8                (partition by deptno
  9                 order by ename  ) seq
 10  from emp
 11  order by deptno, ename
 12  /

ENAME                   DEPTNO       SAL RUNNING_TOTAL DEPARTMENT_TOTAL
SEQ
-------------------- --------- --------- ------------- ----------------
---------
Smith                        1        70        106000           106000
1
Smith                                 90        106000           106000
2
Smith                                110        106000           106000
3
Smith                                130        106000           106000
4
Smith                                150        106000           106000
5
Smith                                170        106000           106000
6
Smith                                190        106000           106000
7
Smith                                210        106000           106000
8
Smith                                230        106000           106000
9
Smith                                250        106000           106000
10

          Followup:

and so what does select * from EMP look like to you?

looks like you have a whole lotta SMITHs in there...

      Rating: 3
Answer to your
      question  February 12, 2003
Reviewer:  Jon
       from Hartford, CT USA
Yes, I just generated some test data using something like

begin
for i in 1..10 loop
for j in 1..100 loop
  insert into emp values ('Smith',i, j*70);
end loop;
end loop;
end;
/

I never committed the data.  My purpose was to get timing statistics in our
environment similar to what you reflected in your book.  But when I got the
questionable results, I wondered why.

Using the above,
SQL> select * from emp where rownum < 6;

ENAME                   DEPTNO       SAL
-------------------- --------- ---------
Smith                        1        70
Smith                                140
Smith                                210
Smith                                280
Smith                                350

and the first few rows from the query are:
SQL> select ename, deptno, sal,
  2         sum(sal) over
  3                (order by deptno, ename) running_total,
  4         sum(sal) over
  5                (partition by deptno
  6                 order by ename) department_total,
  7         row_number() over
  8                (partition by deptno
  9                 order by ename  ) seq
 10  from emp
 11  order by deptno, ename
 12  /

ENAME                   DEPTNO       SAL RUNNING_TOTAL DEPARTMENT_TOTAL
SEQ
-------------------- --------- --------- ------------- ----------------
---------
Smith                        1        70        353500           353500
1
Smith                                140        353500           353500
2
Smith                                210        353500           353500
3
Smith                                280        353500           353500
4
Smith                                350        353500           353500
5

          Followup:

they all have the same ENAMES -- when you order by them -- they are all the
SAME.  Mine worked simply because the ENAMES are unique in the scott/tiger EMP
table.

add

, rowid

to the query:

select ename, deptno, sal,
  2         sum(sal) over
  3                (order by deptno, ename, ROWID) running_total,
  4         sum(sal) over
  5                (partition by deptno
  6                 order by ename, ROWID ) department_total,
  7         row_number() over
  8                (partition by deptno
  9                 order by ename, ROWID  ) seq
 10  from emp
 11  order by deptno, ename
 12  /

or more simply, just run demobld.sql to get the real tables loaded up if you
want to follow the examples blow by blow

      Rating:
      5
Thanks  February 12, 2003
Reviewer:
      Jon  from Hartford, CT USA
I knew there had to be a reason.  That's a really important thing to know, and
it hadn't occurred to me.  In the real world, we might end up with duplicate
last names (although I don't live in "Smith-ville") and then you really won't
get a running total unless you use the technique you just explained.

As always, you had the answer - you're the best, Tom!

      Rating: 5
Analytic
      Function in PL/SQL ref_cursor  May 08, 2003

Reviewer:  Dale Ware  from St. Louis, Missouri

Tom,

I run the following anonymous PL/SQL block in my 8i instances.

set term off serverout off feed off timing off head off pause off arraysize 100
col owner form a16
col index_owner form a16
col blevel form 99,999
col leaf_blocks head 'Leaf|Blocks'
col blocks head 'Table|Blocks'
ttitle skip2 "--  Top 10 Indexes which are potential Candidates for Rebuilding"
skip 2
variable x refcursor;
set term on serverout on head on pagesize 44 linesize 132 autoprint on
declare
   pi_version        varchar2(20);
   pi_compatibility  varchar2(20);
begin
   dbms_utility.db_version(pi_version,pi_compatibility);
   IF pi_version > '8.1.0.0.0' THEN
      OPEN :x FOR 'select owner, index_name, leaf_blocks , blevel,
tablespace_name, blocks
                     from (select idx.owner, index_name, leaf_blocks,
                                  blevel, idx.tablespace_name, blocks,
                                  rank () over (order by leaf_blocks desc nulls
last) rank
                             from dba_indexes idx,
                                  dba_tables tbl
                            where idx.table_owner = tbl.owner
                              and idx.table_name = tbl.table_name
                              and idx.tablespace_name is not null
                              and idx.leaf_blocks > (tbl.blocks * .75))
                    where rank <= 10';
   END IF;
end;
/
set term off serverout off head off

However, when I run this in 8.0 instances it throws the following error:

      OPEN :x FOR 'select owner, index_name, leaf_blocks , blevel,
tablespace_name, blocks
                  *
ERROR at line 7:
ORA-06550: line 7, column 19:
PLS-00103: Encountered the symbol "select owner, index_name, leaf_blocks ,
blevel, tablespace_name," when expecting one of the
following:
select

Is it possible to run this in 8.0 and below? If so how?

Thanks,
Dale

      Rating: 3
does this
      require analytic function or can be done without them  May
      21, 2003
Reviewer:  Asim Naveed  from Pakistan

I am trying to write SQL query for a question, but i
failed to make it. I thought may be analytic functions
can do some thing.
here are the create, and insert statements.

Create table T1
(
QUARTER        VARCHAR2(100),
DIVISION    VARCHAR2(100),
SALES         NUMBER(10)
);

INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr1-1998', 'Div1', 3);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr1-1998', 'Div1', 3);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr1-1998', 'Div2', 13);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr1-1998', 'Div2', 13);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr1-1998', 'Div3', 43);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr1-1998', 'Div3', 43);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr1-1998', 'Div4', 23);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr2-1998', 'Div1', 43);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr2-1998', 'Div2', 33);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr2-1998', 'Div3', 33);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr2-1998', 'Div4', 4);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr3-1998', 'Div1', 4);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr3-1998', 'Div2', 23);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr3-1998', 'Div3', 2);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr3-1998', 'Div4', 1);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr4-1998', 'Div1', 1);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr4-1998', 'Div2', 23);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr4-1998', 'Div3', 2);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr4-1998', 'Div4', 21);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr1-1999', 'Div1', 123);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr1-1999', 'Div2', 3);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr1-1999', 'Div3', 4);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr1-1999', 'Div4', 6);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr2-1999', 'Div1', 7);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr2-1999', 'Div2', 8);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr2-1999', 'Div3', 6);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr2-1999', 'Div4', 5);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr3-1999', 'Div1', 5);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr3-1999', 'Div2', 4);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr3-1999', 'Div3', 6);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr3-1999', 'Div4', 7);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr4-1999', 'Div1', 5);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr4-1999', 'Div2', 55);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr4-1999', 'Div3', 36);
INSERT INTO T1 (QUARTER, DIVISION, SALES) VALUES ('Qtr4-1999', 'Div4', 43);

as you can see there are four divisions in each quarter.
the question is that show all those quarters in which
the sales of div1 is greater than sales of div2. The out
put should be like this

QUARTER        DIVISION      SUM(SALES)
---------------------------------------------
Qtr1-1999      Div1          123
Qtr1-1999      Div2          3
Qtr2-1998      Div1          43
Qtr2-1998      Div2          33
Qtr3-1999      Div1          5
Qtr3-1999      Div2          4

to acheive the above result i wrote the following query

Select quarter, division, sum(sales) from T1 where  division in ('Div1', 'Div2')
GROUP BY quarter, division having quarter
 IN
(
SELECT quarter
FROM T1
GROUP BY QUARTER
HAVING SUM(DECODE(division, 'Div1', sales, 0))
> SUM(decode(division, 'Div2', sales,  0))
)

this query is working fine, but for some reason
I want a query without DECODE/CASE
which returns same result. Could analytical functions help.?

Thanks in advance

          Followup:

well, the right way to do this is in one pass.  You get more pleasing output as
well.  we could unpivot the data using DUAL if necessary.  But DECODE is the
best answer.  Just to make you happy, there is a non-decode (but slower)
version.

ops$tkyte@ORA920> select quarter,
  2         sum(decode(division,'Div1',sales,null)) Div1,
  3         sum(decode(division,'Div2',sales,null)) div2
  4    from t1
  5   group by quarter
  6  having sum(decode(division,'Div1',sales,null)) >
sum(decode(division,'Div2',sales,null))
  7  /

QUARTER          DIV1       DIV2
---------- ---------- ----------
Qtr1-1999         123          3
Qtr2-1998          43         33
Qtr3-1999           5          4

ops$tkyte@ORA920>
ops$tkyte@ORA920> select quarter,
  2         sum(instr(division,'Div1')*sales) Div1,
  3         sum(instr(division,'Div2')*sales) Div2
  4    from t1
  5  group by quarter
  6  having sum(instr(division,'Div1')*sales) >
sum(instr(division,'Div2')*sales)
  7  /

QUARTER          DIV1       DIV2
---------- ---------- ----------
Qtr1-1999         123          3
Qtr2-1998          43         33
Qtr3-1999           5          4

ops$tkyte@ORA920>

      Rating: 2
reason of not
      using decode  May 22, 2003
Reviewer:  Asim
      Naveed  from Pakistan
Thanks for your reply.
I am using Oracle Version 9i Release 2

Although your answer is producing the information
without using decode, but my problem is still not solved,
because the reason I avoid decode/case and now also instr
is that I can not break the expression SUM(sales)
into SUM(DECODE(division, 'Div1', sales, null) because
i am writing a dynamic SQL, and the expression "sum(sales)"
is coming from a table at run time, and this expression
could be anything for e.g
SUM(sales), SUM(price*volume), COUNT(DISTINCT salesid),
COUNT(Distinct customerid).  etc. etc.

see

SELECT
quarter,
division,
"here goes the full expression which is coming from table"

Also my output requirement is to get divisions on rows
instead of columns, (how can we do this with dual)

Today I have written the following query which is doing
exactly what I want , but, if possible i need a better
query.

SELECT quarter, division, SUM(sales)
from t1 c
where division IN ('Div1', 'Div2')
and     quarter IN (
select quarter from t1 a where division = 'Div1'
GROUP BY quarter
HAVING SUM(sales) >
       (select  NVL(SUM(sales), 0)
        from t1 b
        where b.quarter = a.quarter AND division = 'Div2')
                               )
GROUP BY quarter, division

See in the above query, I just replace "SUM(sales)"
with what ever expression I get from
the table (e.g COUNT(salesid), SUM(price*volume)) and its done.

main point is that such a solution is required in which
the aggregate expression can be plugged
as it comes from the table with out breaking that
expression.

          Followup:

you can do this with decode, I see no issues with that at all.  why do you
believe otherwise?

anyway, guess you are back to your solution.

remember this:

generic is great
generic is slow
specific solutions will massively outperform generic solutions
it is up to you to made the tradeoff, decide which is more important.

      Rating: 2
One more thing
      to mention about instr  May 22, 2003
Reviewer:
      Asim Naveed  from Pakistan
your solution with instr can produce wrong results.
if the input division are like this.
first division = 'Div1'
second division = 'Div1Div2'

          Followup:

i never really expected anyone to use it, it would be "wrong"

      Rating:
      3
Question  June 10, 2003
Reviewer:
      PRS  from PRS
Hi Tom,
   I have a table named comments. It has following columns.
   opp_id number(10);
   comm_id number(10);
   commoent varchar2(4000);

   Data is shown as below.

   opp_id       comm_id             comment
   1            101                 test1
   1            202                 test2
   1            300                 test3
   1            550                 test4
   2            220                 test1
   3            301                 test1
   3            501                 test2

  Here opp_id and comm_id both are genereated by the oracle sequence.

   I need to transfer this data into the following table.

   RSF_COMMENT
   opp_id number(10);
   comm_id number(10);
   commoent varchar2(4000);

   But here comm_id is a sequential number. Data is shown below.

   opp_id       comm_id   comment
   1            1         test1
   1            2         test2
   1            3         test3
   1            4         test4
   2            1         test1
   3            1         test1
   3            2         test2

   I cannot use straight insert into SELECT as I need to
derive sequential comment id. So that can be achieved by PL/SQL procedure.

   Is there anyway we can derive the sequential id using the
SELECT statement by having anylatical function?

Thanks for your help,
PRS

          Followup:

ops$tkyte@ORA920> insert into t2
  2  select opp_id, row_number() over (partition by opp_id order by comm_id),
com
  3    from t1
  4  /

7 rows created.

ops$tkyte@ORA920> select * from t2;

    OPP_ID    COMM_ID COM
---------- ---------- -----
         1          1 test1
         1          2 test2
         1          3 test3
         1          4 test4
         2          1 test1
         3          1 test1
         3          2 test2

7 rows selected.

      Rating: 4
Analytics can
      solve this one i suppose -- for Asim Naveed   June 11, 2003

Reviewer:  A reader  from MN, USA
  1  select quarter, division, sum(sales) over (partition by quarter, division)
sum_sales
  2  from t1
  3  where quarter in ('Qtr1-1999', 'Qtr2-1998', 'Qtr3-1999')
  4* and division in ('Div1', 'Div2')
RKPA01> /

QUARTER    DIVISION    SUM_SALES
---------- ---------- ----------
Qtr1-1999  Div1              123
Qtr1-1999  Div2                3
Qtr2-1998  Div1               43
Qtr2-1998  Div2               33
Qtr3-1999  Div1                5
Qtr3-1999  Div2                4

6 rows selected.

      Rating: 1
for MN from
      USA  June 13, 2003
Reviewer:  Asim Naveed
       from Pakistan
MN,

Please read the question carefully , i asked for those
records in which sum(sales) of  div1 is greater than
sum(sales) in div2. the out put have to show both records
(i.e of div1 and div2 )

i.e qtr1-1998 div1  200000
    qtr1-1998 div2  1000

see above div1 sales is greater than div2 in quarter 1 of
1998.

      Rating: 4
Is this a
      candidate for Analytic function  July 16, 2003

Reviewer:  Arun  from Mumbai,
Tom,
  I have a table with this kind of data

REQID             C_NODE             P_NODE
3838             1             0
3838             2             9
3838             2             10
3838             3             11
3838             7             3
3838             7             8
3838             8             11
3838             9             3
3838             9             8
3838             10             7
3838             11             1
Now i need a result set such that,  for a criterion of 10
i will get
7
3
8
11
1

ie
 3838             10             7
3838             7             3
3838             7             8
3838             3             11
3838             8             11
3838             11             1

 How could i go about this?. I thought i could use analytic functions, but found
we can have running totals etc only through that.  Is there a possibility of
doing this without procedures

Thanks for all your help,
Regards,
Arun

          Followup:

proof that what is obvious to one person is just a meaningless jumble to
another.

"Now i need a result set such that,  for a criterion of 10
i will get "

a criterion of 10 what? against what?

I cannot fathom how you got from input data to that result set

      Rating:
      1
Sorry  July 16, 2003
Reviewer:
      A reader
Tom,
Sorry for wasting your time.  It never occured... I was wondering with this
problem for the past 4 hours :-(...

For the table

REQID             C_NODE             P_NODE
3838             1             0
3838             2             9
3838             2             10
3838             3             11
3838             7             3
3838             7             8
3838             8             11
3838             9             3
3838             9             8
3838             10             7
3838             11             1

i need to print
7
3
8
11
1
when the where clause is  this

select ...... <tablename > where  cnode  = 10

which comes by a look up like this
             C_NODE             P_NODE
               10       ---->>>>>      7
---->>>     7       ---->>>>>      3
                 7        ---->>>>>     8
                 3         ---->>>>>    11
Regards,
Arun

          Followup:

Much clearer -- that is a hierarchical query -- connect by is the answer,
analytics not so useful.  consider:

ops$tkyte@ORA920LAP> column graph format a10
ops$tkyte@ORA920LAP> select rpad( '*', 2*level, '*' ) graph, level, t.*
  2    from t
  3   start with c_node = 10
  4   connect by prior p_node = c_node;

GRAPH           LEVEL      REQID     C_NODE     P_NODE
---------- ---------- ---------- ---------- ----------
**                  1       3838         10          7
****                2       3838          7          3
******              3       3838          3         11
********            4       3838         11          1
**********          5       3838          1          0
****                2       3838          7          8
******              3       3838          8         11
********            4       3838         11          1
**********          5       3838          1          0

9 rows selected.

that is your entire tree -- looking at this:

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select rpad( '*', 2*level, '*' ) graph, level, t.*
  2    from t
  3   start with c_node = 10
  4   connect by prior p_node = c_node and level <= 4;

GRAPH           LEVEL      REQID     C_NODE     P_NODE
---------- ---------- ---------- ---------- ----------
**                  1       3838         10          7
****                2       3838          7          3
******              3       3838          3         11
********            4       3838         11          1
****                2       3838          7          8
******              3       3838          8         11
********            4       3838         11          1

7 rows selected.

it looks like you want everything at level 4 and below in the tree... so

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select distinct p_node
  2    from t
  3   start with c_node = 10
  4   connect by prior p_node = c_node and level <= 4;

    P_NODE
----------
         1
         3
         7
         8
        11

there you go

      Rating:
      4
Thanks  July 18, 2003
Reviewer:
      Arun
Thank you very much Tom.
Regards,
Arun

      Rating: 4
can I use
      analytic here?  July 25, 2003
Reviewer:
      steve  from nyc,USA
Hi Tom,

I have 2 tables:
table1: dept#, itemId, ...
table2: item_id, item_type,...

table2 contains all the items. And I just want to insert
into table1 the items which exist in table2 not in the dept of table1 with type
'A' and 'B'.
e.g.
dept#     itemid
----------------
1          100
1          101
2          102
2          103
3          100
4          103
5          101

T2:
item_id  type
-------  ----
100       A
101       A
102       B
103       C
104       C
105       A

and result I expect:
dept#     itemid
----------------
1          100
1          101
1          102
1          105
2          102
2          103
2          100
2          101
2          105
....       ...
5          100
5          101
5          102
5          105

I can get the result by the following sql,
Can I use analytic function to reach the same goal?
beacuse I am thinking if there is new dept added in, I have to add new code.

insert into t1 (dept_no,item_id)
select 1,item_id
FROM  t2
WHERE item_type in ('B', 'A')
 and  item_id not in ( select item_id from t1
                        where t1.dept_no = 1
                      )
union all
insert into t1 (dept_no,item_id)
select 2,item_id
FROM  t2
WHERE item_type in ('B', 'A')
 and  item_id not in ( select item_id from t1
                        where t1.dept_no = 2
                      )
union all
...
unioa all
insert into t1 (dept_no,item_id)
select 5,item_id
FROM  t2
WHERE item_type in ('B', 'A')
 and  item_id not in ( select item_id from t1
                        where t1.dept_no = 5
                      )

Thanks!

Steve

          Followup:

insert into t1 ( dept_no, item_id )
select *
  from (select distinct deptno from t1 ),
       (select item_id from t2 )
 where (dept_no, item_id ) NOT IN ( select dept_no, item_id from t1 );

cartesian product all of the deptnos with all of the item ids and insert the
ones that do not exist.

      Rating: 5
A minor
      correction  July 25, 2003
Reviewer:
      A reader
A minor correction: Item_type needs to be added.

insert into t1 ( dept_no, item_id )
select *
  from (select distinct deptno from t1 ),
       (select item_id from t2 where item_type in ('A', 'B'))
 where (dept_no, item_id ) NOT IN ( select dept_no, item_id from t1 );

      Rating: 4
rank() and
      plsql  August 25, 2003
Reviewer:  rd
       from sweden
Hi,

Please tell me why the following code fails.

-----------------
declare
type cur_type is ref cursor;
c1  cur_type;
vSql varchar2(500):=
            'select acc.rowid accrow
                   ,dense_rank() over (partition by acc.social_security_number
                                                  order by limit_increase_amount
desc
                                                     ,account_utilisation desc
                                                     ,account_number desc
                                                       ) dr
            from   cm_accounts acc
                   ,cm_customer cus
            where  acc.social_security_number=cus.social_security_number
            and acc.social_security_number=''3507249658'')';

type rid_tab is table of rowid index by binary_integer;
type rank_tab is table of number(2) index by binary_integer;
rids rid_tab;
drs     rank_tab;
begin
   open c1 for vSql;
   loop
          fetch c1 bulk collect into rids,drs limit 1000;
       exit when c1%notfound;
       forall i in 1 .. rids.count
                 update cm_accounts
              set     acc_rank=drs(i)
              where     rowid=rids(i);
   end loop;
   commit;
end ;
-----------------

Thanks

          Followup:

well, when I generated some tables to go with this (be nice if tables came with
test case), i got this first:

declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at line 4

so for me at least, 500 wasn't big enough there... upping vSQL to 5000, i got:

declare
*
ERROR at line 1:
ORA-00933: SQL command not properly ended
ORA-06512: at line 21

meaning your sql was bad...

running just your SQL, i see it was:

ops$tkyte@ORA920>             select acc.rowid accrow
  2                     ,dense_rank() over (partition by
acc.social_security_number
  3                                                    order by
limit_increase_amount desc
  4                                                       ,account_utilisation
desc
  5                                                       ,account_number desc
  6                                                         ) dr
  7              from   cm_accounts acc
  8                     ,cm_customer cus
  9              where  acc.social_security_number=cus.social_security_number
 10              and acc.social_security_number='3507249658')
 11  /
            and acc.social_security_number='3507249658')
                                                       *
ERROR at line 10:
ORA-00933: SQL command not properly ended

that parens that didn't need be there. taking that out, I ran it and got:

ops$tkyte@ORA920> declare
  2  type cur_type is ref cursor;
  3  c1  cur_type;
  4  vSql varchar2(5000):=
  5              'select acc.rowid accrow
  6                     ,dense_rank() over (partition by
acc.social_security_number
  7                                                    order by
limit_increase_amount desc
  8                                                       ,account_utilisation
desc
  9                                                       ,account_number desc
 10                                                         ) dr
 11              from   cm_accounts acc
 12                     ,cm_customer cus
 13              where  acc.social_security_number=cus.social_security_number
 14              and acc.social_security_number=''3507249658''';
 15
 16  type rid_tab is table of rowid index by binary_integer;
 17  type rank_tab is table of number(2) index by binary_integer;
 18  rids rid_tab;
 19  drs        rank_tab;
 20  begin
 21     open c1 for vSql;
 22     loop
 23            fetch c1 bulk collect into rids,drs limit 1000;
 24         exit when c1%notfound;
 25         forall i in 1 .. rids.count
 26                   update cm_accounts
 27                set     acc_rank=drs(i)
 28                where     rowid=rids(i);
 29     end loop;
 30     commit;
 31  end ;
 32  /

PL/SQL procedure successfully completed.

Now, if you are in 8i (you never said), you would find that this will raise:

declare
*
ERROR at line 1:
ORA-01001: invalid cursor
ORA-06512: at line 23

and that is because BULK FETCH on a dynamically opened refcursor was simply "not
supported" then.  The solution would be:

ops$tkyte@ORA817DEV> create or replace view v
  2  as
  3  select acc.rowid accrow
  4         ,dense_rank() over (partition by acc.social_security_number
  5                             order by limit_increase_amount desc
  6                                     ,account_utilisation desc
  7                                     ,account_number desc) dr
  8     from  cm_accounts acc
  9          ,cm_customer cus
 10    where  acc.social_security_number=cus.social_security_number
 11           and acc.social_security_number=userenv('client_info');

View created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> declare
  2          type rid_tab is table of rowid index by binary_integer;
  3          type rank_tab is table of number(2) index by binary_integer;
  4          rids rid_tab;
  5          drs  rank_tab;
  6
  7          cursor c1 is select * from v;
  8  begin
  9          dbms_application_info.set_client_info( '3507249658' );
 10          open c1;
 11          loop
 12                  fetch c1 bulk collect into rids,drs limit 1000;
 13          forall i in 1 .. rids.count
 14            update cm_accounts set acc_rank=drs(i) where rowid=rids(i);
 15           exit when c1%notfound;
 16     end loop;
 17     commit;
 18  end ;
 19  /

PL/SQL procedure successfully completed.

NOTE the important MOVING Of the exit when c1%notfound!!!!! it would be a bug
to have it before the update -- else your last fetch of say 999 rows, which
raises the c1%notfound, would never get processed!!!!!

      Rating:
      4
  August 25, 2003
Reviewer:
      A reader
Hi,
Here I have something about analytic functions, I hope it help somebody.

http://www.geocities.com/juancarlosreyesp/OracleAskTom.pdf
:)

      Rating:
      5
  August 26, 2003
Reviewer:  Helena
      Marková  from Bratislava, Slovakia

      Rating:
      5
Analytics?  September 18, 2003
Reviewer:
      Steve  from UK
I have a table with the following data:
location  cpyear period prod_code, cases, cups
99951     2002     12    EU17       1      72
99951     2003      5    EU17       3     216
99951     2002     13    EU17       1      72

and I need output as:

99951     2003      5    EU17       5     360

As you can see cases and cups are summed and I'm looking for the latest year and
period.

Thanks

Steve

          Followup:

ops$tkyte@ORA920> select location, max( cpyear * 100 + period ), prod_code,
sum(cases), sum(cups)
  2    from t
  3   group by location, prod_code;

  LOCATION MAX(CPYEAR*100+PERIOD) PROD_CODE  SUM(CASES)  SUM(CUPS)
---------- ---------------------- ---------- ---------- ----------
     99951                 200305 EU17                5        360

or...

ops$tkyte@ORA920>
ops$tkyte@ORA920> select location,
  2         trunc(max( cpyear * 100 + period )/100) cpyear,
  3         mod(max( cpyear * 100 + period ),100) period,
  4         prod_code, sum(cases), sum(cups)
  5    from t
  6   group by location, prod_code;

  LOCATION     CPYEAR     PERIOD PROD_CODE  SUM(CASES)  SUM(CUPS)
---------- ---------- ---------- ---------- ---------- ----------
     99951       2003          5 EU17                5        360

      Rating:
      5
  September 18, 2003
Reviewer:
      Steve  from UK
Excellent thanks!  I was convinced that I needed to use analytics and so
couldn't see the wood for the trees!

Steve

      Rating: 4
Going
      further  October 08, 2003
Reviewer:  bobspa

Tom,

   is is possible to convert this code fragment using analytics => for
performance improvement(?) [ i'm not yet too familiar with this 8i feature --
been sleeping for years :( ]. i'm using 8.1.6 EE

 select bank_id, txn_date,acct_type,dep_cd,dep_term,
interest_rate,currency_code,sum(amt_in_dollars),sum(amt_in_org_curr),count(distin
ct dep_name), tax_disc
 from deposit_txns
group by bank_id, txn_date,acct_type,dep_cd,dep_term,
interest_rate,currency_code,tax_disc

the table deposit_txns has  millions of rows and is taking a lot of time. best
time is 8 minutes elapsed. (sort area is already 8KB)

thanks and best regards

          Followup:

there is no need for analytics there at all, they do not apply at all in this
case.  this query only does normal aggregation.

sort area is already '8kb'??  is that a typo?

      Rating: 4
Calculating
      count Using analytic functions  October 27, 2003

Reviewer:  Sridhar Reddy  from USA
Hi Tom,
    I have a small issue for which i could not find a solution on this site,so
iam asking you.

I have table with the following values
date       varchar2   number    number
1/12/2002   first       1         2
1/11/2002   first       3         4
1/10/2002   first       5         6
1/13/2002   second      7         8
1/14/2002   second      9         10

I would like to select something like this.
date       varchar2   number    number   count
1/12/2002   first       1         2        3
1/11/2002   first       3         4        3
1/10/2002   first       5         6        3
1/13/2002   second      7         8        2
1/14/2002   second      9         10       2

do a count of the values on the varchar2 column and assign it.is this possible
in one single statement?please help

Thanks,
Sridhar

          Followup:

ops$tkyte@ORA920> select d, v, n1, n2, count(*) over (partition by v)
  2    from t;

D          V                  N1         N2 COUNT(*)OVER(PARTITIONBYV)
---------- ---------- ---------- ---------- --------------------------
01/12/2002 first               1          2                          3
01/11/2002 first               3          4                          3
01/10/2002 first               5          6                          3
01/13/2002 second              7          8                          2
01/14/2002 second              9         10                          2

      Rating: 5
Thanks a lot
      Tom  October 28, 2003
Reviewer:  Sridhar
      Reddy  from USA
Tom ,
   It helped me. thanks a lot for your quick response.
Thanks,
Sridhar.

      Rating: 5
Taking it a
      step ahead  October 28, 2003
Reviewer:  Sridhar
      Reddy  from USA
Tom,
   Your response helped me a lot.but i was curious how we can do the same thing
taking into account another column.
   date        v2         n1        n2
  1/12/2002   first       10         2
  1/11/2002   first       10         4
  1/10/2002   first       10         6
  1/13/2002   first       20         8
  1/14/2002   first       20         10
  1/12/2002   two         10         2
  1/11/2002   two         10         4
  1/10/2002   two         10         6
  1/13/2002   two         20         8
  1/14/2002   two         20         10

I would like to select something like this.
   date        v2         n1        n2   count
  1/12/2002   first       10         2      3
  1/11/2002   first       10         4      3
  1/10/2002   first       10         6      3
  1/13/2002   first       20         8      2
  1/14/2002   first       20         10     2
  1/12/2002   two         10         2      3
  1/11/2002   two         10         4      3
  1/10/2002   two         10         6      3
  1/13/2002   two         20         8      2
  1/14/2002   two         20         10     2

What should we do if we need to select the count based on N1
even though "first" is repeated 5 times i want to get 3 and 2 as the count based
on the column N1.Is this possible in one single statement?Please help.
Thanks,
Sridhar

          Followup:

count(*) over (partition by v2, n1)

      Rating: 5
Another
      Example  October 28, 2003
Reviewer:  BK
       from NJ, USA
Thought of contributing my 2 cents. Don't know without analytic function how
this could be achieved.

select description,sales_account,count(*) over(partition
       by sales_account) Count_SA,
       cost_of_sales_account,count(*) over(partition by
       cost_of_sales_account) Count_COSA
from mtl_system_items

Hope this helps.

October 28, 2003 Reji -- Thanks for the question regarding
      "Storage Parameters in TableSpace/Tables", version Oracle 8i ( 8.1.5 &
      8.1.6)

      You Asked
Tom:
I have a qn for you related to storage parameters with
TableSpaces and Tables.
If I specifiy storage parameters like INITIAL, NEXT, PCTFREE,
PCTUSED then do I have to specify these parameters in the table
level or not. Would you please let me know when and where we
should specify storage parameters in the tablespace and table
level.

-Thx
-Reji

      and we said...
There are 2 types of tablespaces we have to consider

o locally managed
o dictionary managed

For Locally managed tablespaces (the only kind I use in
Oracle8i) The storage parameters NEXT, PCTINCREASE, MINEXTENTS,
MAXEXTENTS, and DEFAULT STORAGE are not valid.  They are
totally ignored.   The storage parameter INITIAL is only used
if the tablespace is not UNIFORMALLY sized (I myself use
UNIFORM, not AUTOALLOCATE in which case the initial can be
specified). See

http://technet.oracle.com/doc/oracle8i_816/server.816/a76965/c03space.htm#4346
for an in depth look at locally managed tablespace.  So, for
objects in a locally managed tablespace, you only have to
consider things such as PCTFREE and PCTUSED.  These are
typically defaulted from the definition of the tablespace or may
be overridden on an object by object basis.  I prefer to let
them default (not specify at the create table level) and
override only for objects that are the exception -- most of my
create tables do not specify a storage clause.  It is the
exception that would.

When considering storage in a dictionary managed tablespace then
you have the following things to consider:

o if you do not specify the storage parameters on the CREATE
TABLE statment, the table will inherit its storage
characteristics from the tablespace itself.

o A create table may supply zero, one or more of these
parameters to override the tablespace level definition.

So, if you use locally managed tablespaces (recommended) you
do not have to think about this too much.  If you use
AUTOALLOCATE, you must consider the initial extent size for each
object.  If you use dictionary managed tablespaces, you
should consider all of them but again, I try to let them default
from the tablespace definition (and I always leave pctincrease=0
to avoid fragmentation on a dictionary managed tablespace).

Review &
      Followup

      Rating:
      3
  May 09, 2001
Reviewer:
      A reader

      Rating: 3
Comments from
      the Group  March 07, 2002
Reviewer:
      A reader
Hi Tom.

Already posted this to the comp.databases.oracle.server group but since i did
not get any response from you assumed that u have overlooked that message and i
am posting it here.

Thread Name : Algorithm for calculating extent size in LMT
Thread At :
http://groups.google.com/groups?hl=en&threadm=a66lb9%24920%241%40lust.ihug.co.nz&prev=/groups%3Fq%3Dcomp.databases.oracle.server%26hl%3Den%26btnG%3DGoogle%2BSearch
In This thread Jonathan told how Oracle caluclates the Number of Extents handed
over to the Object if the Next, Initial and Minextents have been specified or in
short the storage parameters have been specified.

According to u'r comment below you are saying that the storage parameters are
ignored but i found that that is not the case.

<Quote>
For Locally managed tablespaces (the only kind I use in
Oracle8i) The storage parameters NEXT, PCTINCREASE, MINEXTENTS,
MAXEXTENTS, and DEFAULT STORAGE are not valid.  They are
totally ignored.   The storage parameter INITIAL is only used
if the tablespace is not UNIFORMALLY sized (I myself use
UNIFORM, not AUTOALLOCATE in which case the initial can be
specified).
</QUOTE>

Can u please comment on this.

Thanks.

Best Regards,
Ganesh R

          Followup:

Yeah, thanks -- I fixed this comment in one or two other posts.  What I mean was
the SIZE you request is ignored when sizing the extent.

It is true that if you have an LMT with 1m uniform extents and ask for a table
with an initial of 50m -- we will allocate 50, 1m extents (not ONE 50m extent,
50 ONE meg extents).  So to say they are "totally" ignored is perhaps too
strong.  They are totally ignored when it comes to choosing the SIZE of an
extent.  They are used however to initially size the segment (using multiple
extents)

      Rating: 4
Practical size
      of LMT extent?  April 04, 2002
Reviewer:
      chuanlarp sat.  from DC USA
Tom,

    Your follow-up is very clear on tables that are bigger than a single extent.
 What about tables that are usually smaller than a single extent.   For example,
if I specify LMT with 2 MB uniform extent, but 10% of the tables are pretty
small like lookup tables that occupy only 2-300 KB.
- Will these tables get 1 or 2 extents (4MB) when they are first allocated?

- What is the trade off between LMT with 64MB or 2MB size?

Thank you

          Followup:

If you have small tables like that, they don't belong in a LMT with a 2mb
extent.

In any case, they will get the number of extents you ask for.  If you leave off
a storage clause, they'll get 1 2mb extent.  If you say "initial 4mb", they'll
get 2 2mb extents.  It's up to you how many 2mb extents they will get.

You want to place objects by their size into small medium large and extra large
tablespaces.  No, I don't have the size of what small is, it depends on you and
your app.  small might be 64k, small might be 1m -- don't know.

There are no trade offs between a 2m and 64m LMT -- you would put objects that
fit nicely into the 2m one and others that fit nicely into the 64m on.  A 100meg
table would be good in the 2m LMT.  A 1gig table -- the 64m one.

      Rating: 5
LMT
      Recommended?  July 18, 2002
Reviewer:  Sikandar
      Hayat Awan  from Pindi Bhattian - Pakistan
Oracle recommends LMT but all the system/default tablespaces are dictionary
managed (system, indx, users etc.)?

          Followup:

In 9iR1 -- all but system are LMTS
In 9iR2 -- all, including system, are LMTS

      Rating:
      5
Deprecated?  April 07, 2003
Reviewer:
      Jake Spencer  from Charlotte, NC
Tom,

Why are PCTFREE and PCTUSED listed in the "Deprecated Features" section of the
9iR2 Concepts manual?  In what way are these deprecated?

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96524/b_deprec.htm#634923

          Followup:

ASSM (automatic segment space management) deprecated them.  If you use manual
(freelists) segment space management, they still apply

      Rating: 4
Why is PCTFREE
      deprecated?  April 08, 2003
Reviewer:  Oleg
      Letaev  from St.Petersburg, Russia
Tom,

AFAIK, automatic space management deprecates only PCTUSED.
But you can (and sometimes must) use PCTFREE.

          Followup:

pctused is 100%, they just moved the entire discussion to the deprecated chapter
is all.  pctfree is still used upon creation but if you think about it -- if
pctused is deprecated -- and pctused told us when to put a block back onto the
freelist and pctfree told us when to take it off.... and there are no
freelists....  after object creation -- of what use is pctfree?

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96521/tables.htm#246

      Rating: 4
Why use Inital
      in Dictionary Managed Tablespace  October 27, 2003

Reviewer:  Naveen  from Inida
Hi Tom,

Why should one use initial even if it is Dictionary Managed Tablespace. I
understand the use of MINEXTENTS, MAXEXTENTS, INITTRANS,MAXTRANS, PCTFREE and
PCTUSED. In what cases should we use Inital and Next. As far as i understand,
Oracle allocates one extent of size specified by Initial parameter.

Say, i have two tables Table-X and Table-Y. Table-X contains all the picklist
values that are used in our application. Table-Y gets the rows once the user/(s)
accessess the site.

I feel it is logical to specify the inital parameter for Table-X as i know how
many rows to be inserted and how much space is consumed by each row(Plaese
correct me if i am wrong).

And for Table-Y, even if i specify Initial parameter and if it is transaction
intensive table, the initial extent i have specified fills up pretty quickly and
Oracle has to go through the laborious process of allocating the extent. So how
does specifying INITIAl parameter improve the performance. Am I thinking right?

Thanks
Nav.

          Followup:

don't use DMT's period!!!!

you don't need to use intial.
you can use initial.
it is an option.
it is a "feature".

initial is a way of preallocating space, that is all, nothing more, nothing
less.  Don't read too much into it and use LMT's only!

      Rating: 4
I am using
      LMTs..but..  October 28, 2003
Reviewer:
      Naveen  from India
Hi Tom,

Thanks for the reply. I agree with you that the parameter is obsolete and LMT is
"the" thing. But i need to get this into my head (or rather out of it). Why does
one need to preallocate space for a table using INITIAL. Every book i read
explains WHAT it is and not WHY it is needed and HOW should one need to allocate
the size. Could you please explain.(I am reading your book Expert One-on-One and
in that you have mentioned INITIAl as obsolete parameter. I am working on two
different versions 8.1.5 and 9iR2, so i feel i should understand the effect of
initial)

Thanks
Nav.

          Followup:

one does NOT NEED TO.

one has the ABILITY TO.

815 supports LMTS so you don't really need to look at it :)

initial just sets up "how much space will I allocate when I create the object".
If you KNOW a table will consume 100 meg of storage and you are simply creating
it in order to load it, you might well say:

initial 100m

just to get it allocated.  To make sure you can allocate the space.  To have it
there waiting for you.

But, you don't have to.

      Rating: 4
Initial extent
      for a transaction table  October 28, 2003

Reviewer:  Ashiq Shamsudeen A  from Trichy, Tamil
      Nadu, India
Hi Tom,

 If I able to foresee the size of table then according to it ,I can specify the
initial extent size during the table creation.How about a transaction table
where you can't foresee the size of the table ,because daily there will be
thousand's of insert's will happen and ever increasing table(data wise).
  How to specify(or calculate) the  initial extent size for these tables,if my
tablespace is LMT?.

          Followup:

you are over analyzing this.  really.

for a transaction table, use a LMT with either

o system managed extent sizes.  we'll do the work.

o a uniform extent size that lets the table grow about 1 extent per UNIT OF
TIME.  where you pick UNIT OF TIME as you see fit (eg: 1 extent per month would
be nice).

      Rating: 5
Thanks
      again  October 29, 2003
Reviewer:  Naveen
       from India
Hi Tom,

Thanks again. I think i got the answer.

Thanks
Nav

October 28, 2003 -- Thanks for the question regarding "Generate
      Create Table Statements", version 8.1.6

      You Asked
I have Following SQL Statement to generate Create table Statement,
How can i modify to generate create table statements for a schema.

Please modify the below statement to generate all create table statements for a
schema.

------------------------------
spool generate_table.lst
set serveroutput on size 1000000
declare
starting boolean :=true;
r_owner varchar2(30) := '&1';
r_table_name varchar2(30) := '&2';
begin
dbms_output.put_line('create table '||r_owner||'.'||r_table_name||'(');
for r in (select column_name, data_type, data_length, data_precision,
data_scale, data_default, nullable
            from all_tab_columns
           where table_name = upper(r_table_name)
             and owner=upper(r_owner)
           order by column_id)
loop
  if starting then
    starting:=false;
  else
    dbms_output.put_line(',');
  end if;

  if r.data_type='NUMBER' then
    if r.data_scale is null then
      dbms_output.put(r.column_name||' NUMBER('||r.data_precision||')');
    else
      dbms_output.put(r.column_name||'
NUMBER('||r.data_precision||','||r.data_scale||')');
    end if;
  else if r.data_type = 'DATE' then
    dbms_output.put_line(r.column_name||' DATE');
  else if instr(r.data_type, 'CHAR') >0 then
    dbms_output.put(r.column_name||' '||r.data_type||'('||r.data_length||')');
  else
    dbms_output.put(r.column_name||' '||r.data_type);
  end if;
  end if;
  end if;
  if r.data_default is not null then
    dbms_output.put(' DEFAULT '||r.data_default);
  end if;
  if r.nullable = 'N' then
    dbms_output.put(' NOT NULL ');
  end if;
end loop;
dbms_output.put_line(' ); ');
end;
/

spool off

      and we said...
Well, frankly, I would simply use:

exp userid=user/password rows=n grants=n indexes=n constraints=n owner=user
imp userid=user/password full=y indexfile=user.sql

then, user.sql will have everything you want.

short of that, you could turn your anonymous block into a function and then use
it in a query as follows:

scott@ORA817.US.ORACLE.COM> create or replace function create_table( r_owner in
varchar2, r_table_name in varchar2 )
  2  return varchar2
  3  as
  4     starting boolean :=true;
  5  begin
  6  dbms_output.put_line('create table '||r_owner||'.'||r_table_name||'(');
  7  for r in (select column_name, data_type, data_length, data_precision,
  8  data_scale, data_default, nullable
  9              from all_tab_columns
 10             where table_name = upper(r_table_name)
 11               and owner=upper(r_owner)
 12             order by column_id)
 13  loop
 14    if starting then
 15      starting:=false;
 16    else
 17      dbms_output.put_line(',');
 18    end if;
 19
 20    if r.data_type='NUMBER' then
 21      if r.data_scale is null then
 22        dbms_output.put(r.column_name||' NUMBER('||r.data_precision||')');
 23      else
 24        dbms_output.put(r.column_name||'
 25  NUMBER('||r.data_precision||','||r.data_scale||')');
 26      end if;
 27    else if r.data_type = 'DATE' then
 28      dbms_output.put_line(r.column_name||' DATE');
 29    else if instr(r.data_type, 'CHAR') >0 then
 30      dbms_output.put(r.column_name||'
'||r.data_type||'('||r.data_length||')');
 31    else
 32      dbms_output.put(r.column_name||' '||r.data_type);
 33    end if;
 34    end if;
 35    end if;
 36    if r.data_default is not null then
 37      dbms_output.put(' DEFAULT '||r.data_default);
 38    end if;
 39    if r.nullable = 'N' then
 40      dbms_output.put(' NOT NULL ');
 41    end if;
 42  end loop;
 43  dbms_output.put_line(' ); ');
 44  return null;
 45  end;
 46  /

Function created.

scott@ORA817.US.ORACLE.COM>
scott@ORA817.US.ORACLE.COM>
scott@ORA817.US.ORACLE.COM> select create_table( user, table_name )
  2  from user_tables
  3  where rownum < 5
  4  /

CREATE_TABLE(USER,TABLE_NAME)
---------------------------------------------------------------------------------
--------------------------------------------------

scott@ORA817.US.ORACLE.COM> spool generate_table.lst
scott@ORA817.US.ORACLE.COM> exec null;
create table SCOTT.A(
Y
NUMBER(,0) );
create table SCOTT.B(
X
NUMBER(,0) NOT NULL  );
create table SCOTT.BONUS(
ENAME VARCHAR2(10),
JOB VARCHAR2(9),
SAL NUMBER(),
COMM NUMBER() );
create table SCOTT.CONTACT(
ACCOUNTNUMBER
NUMBER(10,0) NOT NULL ,
COMPANYNAME VARCHAR2(80),
CONTACTNAME VARCHAR2(80),
ADDRESS1 VARCHAR2(80),
ADDRESS2 VARCHAR2(80),
CITY VARCHAR2(40),
STATE VARCHAR2(20),
ZIP VARCHAR2(20),
COUNTRY VARCHAR2(20),
TELEPHONE VARCHAR2(20),
FAXNUMBER VARCHAR2(20),
EMAIL VARCHAR2(80) );

PL/SQL procedure successfully completed.

scott@ORA817.US.ORACLE.COM> spool off
Review &
      Followup

      Rating: 5
Generate Create
      Table Statemnets for a Schema  August 05, 2001

Reviewer:  Gattu  from USA
You are really great tom.

      Rating: 5
exec :null
      ;  April 16, 2002
Reviewer:  A reader

when I created the function it returned nothing

then I saw what you did

scott@ORA817.US.ORACLE.COM> select create_table( user, table_name )
  2  from user_tables
  3  where rownum < 5
  4  /

CREATE_TABLE(USER,TABLE_NAME)
---------------------------------------------------------------------------------

--------------------------------------------------

scott@ORA817.US.ORACLE.COM> spool generate_table.lst
scott@ORA817.US.ORACLE.COM> exec null;

when I tried the same it worked.

Can you explain why in first place , the query ....

scott@ORA817.US.ORACLE.COM> select create_table( user, table_name )
  2  from user_tables
  3  where rownum < 5
  4  /

.....did not return any output, and then

when you did exec :null is returned the output , why so

can you please explain the technical details

          Followup:

If you get my book -- i give you more information about dbms_output then anyone
really needs to know (plus tons of other useful stuff).

basically -- dbms_output puts the data into an array.  sqlplus "pulls" the data
out and prints it AFTER an insert/update/delete/PLSQL block.  It is not
expecting a SELECT to generate any dbms_output (selects don't fire triggers,
plsql isn't generally run from a select) hence plus doesn't "pull it".

By doing "exec null", you are running a plsql block -- plus says "ah, maybe
there it something to print" and it finds your last block of stuff to print out.

      Rating: 5
Tom, is it
      possible  April 17, 2002
Reviewer:
      A reader
to generate the create table statements for a whole schema, and not just a
single table.

Do you have a script to do that, or can you tell us how to generate create table
scripts for all the tables in a schema.

Thank you

          Followup:

Already answered:

Well, frankly, I would simply use:

exp userid=user/password rows=n grants=n indexes=n constraints=n owner=user
imp userid=user/password full=y indexfile=user.sql

then, user.sql will have everything you want.

.....

      Rating: 3
A
      thought  April 17, 2002
Reviewer:
      A reader
Export and Import are server side tools. We wouldnt normally have them installed
on the client side. And most of the developers have no access to the database
server.

In that context, how can we export from the client side to a client side
directory. The objective is to export all the objects belonging to a particular
users own schema.

Mind you we are not talking about any server side activity, and no DBA's
involved in this.

Is this possible at all.

          Followup:

Sorry -- but I've never seen a development environment where the developers
don't have access to EXP and IMP, I've just never seen it.

In 9i there is dbms_metadata that can be used.  In 8i and before (and actually
in 9i) exp is the way to go.  I would let my developers have at it with these
tools, make them available somehow.

      Rating: 5
what can be
      exported.  April 18, 2002
Reviewer:  Kulguru

Assuming that the developers had access to import and export. What kind of
objects , which objects belonging to which schema can they export.  Can they
export objects belonging to other schemas, but have been grants access.

What is the developers try to export schemas from production database, would it
not slow down production database performance.

and lastly, if a developer runs export from the client side, where will the dump
file be created, the client side or the server side.

Thank you

          Followup:

export is just a program.  It runs queries (SQL).  It retrieves data.  It can do
nothing that the developer could not do using SQLPlus.  It is just a tool, it is
not evil.

They can only export things to which they have been granted access.  (just like
they can only query things to which they have been granted access).

What if the developers log into production and issue "select count(*) from t, t,
t, t, t" where t is a 100,000 row table?  would not that slow down production --
probably.  The point here is?  They can log into production today and do select
* from table, export is just another way to do that.  If you are concerned about
developers consuming too much resources on production we have resource profiles
to limit that.

The dmp file is created where EXP is run.  EXP is just a client program much
like SQLPlus is.

      Rating: 4
Answer of above
      question  April 18, 2002
Reviewer:  Riaz
      Shahid  from Islamabad, Pakistan
By default, the dmp file will be saved on client side. However u can give file
path also to store file where u desire.

          Followup:

And that will STILL be on the client side.  The DMP file is ALWAYS saved on the
client side.

      Rating: 5
How about other
      objects?  April 25, 2002
Reviewer:  Michael
      Leung  from HK
Tom,

If I want to export triggers, functions and other objects also, can I use
exp/imp?

Michael

          Followup:

yes.  exp gets all of those.

      Rating: 5
External Tables
      (organization external)  December 23, 2002

Reviewer:  Another Reader  from Germany
I am still missing the external tables definition in the output of the imp
utility.

I am working with 9.2

          Followup:

[tkyte@tkyte-pc-isdn 9iTrain]$ exp userid=/ tables=external_table

Export: Release 9.2.0.1.0 - Production on Mon Dec 23 11:21:25 2002

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to: Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
Export done in WE8ISO8859P1 character set and AL16UTF16 NCHAR character set

About to export specified tables via Conventional Path ...
. . exporting table                 EXTERNAL_TABLE
Export terminated successfully without warnings.
[tkyte@tkyte-pc-isdn 9iTrain]$ imp userid=/ full=y show=y

Import: Release 9.2.0.1.0 - Production on Mon Dec 23 11:21:38 2002

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to: Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

Export file created by EXPORT:V09.02.00 via conventional path
import done in WE8ISO8859P1 character set and AL16UTF16 NCHAR character set
. importing OPS$TKYTE's objects into OPS$TKYTE
 "CREATE          TABLE "EXTERNAL_TABLE" ("EMPNO" NUMBER(4, 0), "ENAME" VARCH"
 "AR2(10), "JOB" VARCHAR2(9), "MGR" NUMBER(4, 0), "HIREDATE" DATE, "SAL" NUMB"
 "ER(7, 2), "COMM" NUMBER(7, 2), "DEPTNO" NUMBER(2, 0)) ORGANIZATION EXTERNAL"
 " (TYPE "ORACLE_LOADER" DEFAULT DIRECTORY "DATA_DIR" ACCESS PARAMETERS ( "
 "fields terminated by ','  ) LOCATION ( 'emp.dat' ) ) "
Import terminated successfully without warnings.

can you show me a counter case?

      Rating: 5
External Tables
      (organization external) with indexfile=test.sql  December
      27, 2002
Reviewer:  Another Reader  from Germany

Your example is OK.

But, it's easier to use the schema generated with the indexfile option instead
of show=y.

exp ROWS=N FULL=Y FILE=exptest
imp ROWS=N FULL=Y FILE=exptest INDEXFILE=indexschema.sql

Now I am missing the external tables in indexschema.sql

          Followup:

Well, that is a horse of a different color -- I was merely demonstrating that if
you IMPORT -- the external table is there.  I wasn't aware at all that you
wanted it with the indexfile statement -- that wasn't clear.

You can use dbms_metadata in 9ir2 (doesn't work properly for external tables in
9ir1) instead if you like as well.

      Rating:
      4
  October 28, 2003
Reviewer:  Fraser
      Clegg  from Birmingham, UK
Tom,

I'm having problems with the indexfile option (8.1.6.0 on Sun 5.8) - Many of my
create/alter table statements are wrapping making the script almost unusable on
a regular basis:
REM  ALTER TABLE "GENEVA"."SMDEPENDENTLINKS" ADD PRIMARY KEY
REM  ("DEPENDEEID","DEPENDENTID","ASSOCIATION_ID_","DEPENDEETYPE","DE
REM  PENDENTTYPE") USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255

Is this normal behaviour - is there anyway round it.
Thanks,
Fraser

          Followup:

hmm, i reproduced in 815 using your table/column names.

in 817, it doesn't reproduce.  so, it looks like they fixed that in the terminal
release of 8i.  It actually appears to be an EXPORT issue as using 817's import
on the 815 dmp file exhibits the problem but using 817's export/import it works.

I don't know of a workaround.

October 28, 2003 Angelo -- Thanks for the question regarding
      "private database link public synonym", version 9.0.1

      You Asked
Tom,

What I am attempting to do is create a private database link then create a
public synonym for that link.
I need the link to run procedeure A on the remote database.

i.e

Execute immediate procedure a @remote.database.

These reasoning behind the private link is a secure issue dictated by the higher
offices.

Is this possible?

Is there a better way to do it?

      and we said...
private is private.

public isn't private.

conflict of interest here.

If you could create a public synonym to expose a private dblink, it would be the
same as a public database link -- hence it would raise the same "security"
issues (whatever that might be -- i cannot think of any if everyone needs access
to the other system, they need access don't they).

You would create a public database link.  I'll be glad to address the real
security concerns they might be having but I would need to know what they are
exactly.

Another option for you could be:

a) create the private link "foo"
b) create a procedure a
            as
            begin
                 a@foo;
            end;
c) grant execute on a
d) running A as other users will work.  it'll run a@foo and foo will be the
   private database link.

Review &
      Followup

      Rating:
      3
  April 05, 2002
Reviewer:
      A reader

      Rating:
      3
  April 11, 2002
Reviewer:  Mike
       from NY,NY
"
If you could create a public synonym to expose a private dblink, it would be the

    same as a public database link -- hence it would raise the same "security"
    issues (whatever that might be -- i cannot think of any if everyone needs
access
    to the other system, they need access don't they).
"

Tom, could you provid your benchmart test on the point of "security"??

          Followup:

You need to tell me what your concerns with regards to a public database link
are (assuming that the DATA does need to be exposed that is).

Look at the requirement in the question, then detail the concerns a public
database link would bring out in such a circumstance.

I will address those concerns.  Me, I haven't any.

      Rating:
      3
  April 11, 2002
Reviewer:  Mike
       from NY, NY
Sorry for the 1st attemped posting, here is one I wanted to posted. Please
remove the above one.

The following was  copied from another forum. Do you agree with it. Please
address in     yours/benchmark test, if so.
    Thanks so much.

    "
The point is this: You can create a PUBLIC database link
    that includes the authentication
    information for a SINGLE USER on the remote database. For example:

    In database A you create a public db link that connects to database B. The
db
    link is
    configured to have all users sign in to database B as SYS/ORACLE, regardless
of
    the users
    actual privileges in database A.
    "

          Followup:

well, I quite simply question the santity of anyone using SYS in any capacity,
let alone in a dblink

Hey -- did you know, you can run your cgi-bin's on your webserver as root, you
have that ability.  NOW, the question is -- would you?  (no, no you wouldn't)

change sys to scott
change oracle to tiger

now, does that database link look so bad?

Inside Oracle we use public database links lots. There are two ways to setup the
link:

a) as a proxy account (as above)..  In this case, we setup the link with a
username/password however the username is generally something like "PUBLIC_READ"
and the password is "PASSWORD".  This account has granted to it on the remote
site "CREATE SESSION" and "SELECT <on specific views>".  This is a generic
account, anyone is allowed to read those views of the data.

b) not as a proxy account, meaning each user will log in as themselves.  Here
security is done user by user as normal.

Anyone that uses SYS -- well, they are <word deleted>.

      Rating: 5
How should we
      refer remote packages locally using private dblink  October
      27, 2003
Reviewer:  A reader
Hi Tom,

I have two accounts in my local database. One is the owner of the database
schema and another is the application db account which has only access/update
provilege on tables. No DDL privilege.

I have a package in the remote DB which refers TYPES etc.

In this case, how do i reference the remote package
locally using private db link.

Also, my remote package contains many stored procedures which has arguments.

How do i access this remote package locally with out PUBLIC database link. Up on
successful local packge creation,
i need to provide execute privilege on this local package to Application db
account (in local database).

Could you suggest a way of doing it.

          Followup:

you refer to it in the same fashion as a public database link.  The syntax of a
dblink is the same regardless of the type.

I would suggest a synonym:

create synonym local_pkg for remote_pkg@private_db_link;

and then just use local_pkg as if it were a local package.

      Rating: 4
I am using SQL
      types in the remote package  October 28, 2003

Reviewer:  A reader
Hi Tom,

Thanks.

My remote package is using SQL types. In this case, Will i still able to access
this remote package locally in local DB.

My local database has two accounts. One is the owner of the schema and another
is the controlled db access account with necessary privilege.

I tried as below as local db schema owner.

create database link remotelink connect to <remoteuser> identified by
<remotepasswd> using 'connectstring' ;

create synonym <package_name> for store_asr2iom_order_lps@remotelink ;

This remote package refers SQL types as below :

create or replace type asrtype as table of varchar2(18) ;

All the above synonym setup, i am doing it as a local db schema owner.

I need to give all these privilege to my local application db user (controlled
db account) to execute the local package (actually remote package).

How do i grant these privilege to them.

Could you give an example if that would be possible.

note: i am using private database link.

          Followup:

the types are not dblink friendly, no.  if you need to pass inputs/outputs --
it'll not work.  you would need to use plsql table types, that will work over a
dblink.

October 28, 2003 sreekumar -- Thanks for the question regarding
      "Recovery Manager ; Backup/Restore Issue", version 8.1.6

      You Asked
I tested the following without a catalog database and with a
catalog database.

1> The database is in Archivelog Mode.
2> Used the following Recovery Manager scripts to take a full
backup.

run {
        allocate channel c1 type disk;
         backup
        (database format'd:\oracle\backup\df_%t_s%s_s%p');
        }

Database was backed up without reporting any error messages.

3> Manually deleted a few user tables from a user tablespace.

4> Restored the above backup using the following RMAN scripts.

    Database was shutdown and mounted.

    run {
            allocate channel c1 type disk;
            restore database;
            recover database;
            }

    Recovery manager didn't report any error message.

    Database is opened using ALTER DATABASE OPEN;  command.

    However, I found that the deleted tables are not got
restored.

5> I did the same steps for a user tablespace as well.  That
also, didn't make a difference.

6> I tried to backup the control file (explicitly) along with
the database and restored both (control file and database) to
the original location.  That also didn't make a difference.

CAN PLEASE TELL ME IS ANY THING IS WRONG IN THE RMAN SCRIPTS ..?
AND WHY THE DELETED TABLES NOT GOT RESTORED..?

      and we said...
Because you fully recovered the database.  Since it is in
archive log mode and you did a recover -- it recovered it ALL
THE WAY to right now (after your dropped the tables).  You did
not do a point in time recovery -- you seem to want to recover
the database to the point in time immediately before you dropped
some tables.  RMAN did exactly what you asked it to -- recover
the database to right now.

See

http://technet.oracle.com/doc/oracle8i_816/server.816/a76990/rmantspi.htm#443831
for info on point in time recovery.

Review &
      Followup

      Rating: 4
onec using RMAN
      backup  September 03, 2003
Reviewer:  Vera
       from Russia
Tom,
I create database (archivelog mode), then make full backup with RMAN (and
controlfile).
I remove database totally and restore and recover it through RMAN .
I open database with resetlogs .
When I remove database again and try to restore and recover it, RMAN outputs
error:
RMAN> restore database ;

Starting restore at 03-SEP-03

using channel ORA_DISK_1
RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-03002: failure of restore command at 09/03/2003 10:22:54
RMAN-06026: some targets not found - aborting restore
RMAN-06023: no backup or copy of datafile 11 found to restore
RMAN-06023: no backup or copy of datafile 10 found to restore
...
Does it mean that I can use the backup only onec and if so ,
how does RMAN define a unavailable state for restore the database ? Or do I
wrong something ?
Thanks.

      Rating: 4
Can we use RMAN
      to backup RAW files? Thanks.  September 10, 2003

Reviewer:  Reader

          Followup:

you can use rman to backup anything you can put an oracle database file on.

yes. that includes raw

      Rating: 5
memory for
      backup and restore  September 27, 2003
Reviewer:
      reader
Tom,

(1) when Rman backsup datafiles, how much memory it would use to backup by
reading blocks from datafiles to a disk drive?

(2) Is this memory allocated in the PGA of the server process that does the
backup?

(3) If I set parallalism to 4, RMAN will use four channels that means four
server processes, in this case the memory allocation the same for each channel?

(4) Could you also help me to understand how much memory I should allocate in
large pool If I used slaves for tape backup?

Thanks.

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96566/rcmtunin.htm#448792

      Rating: 4
block
      corruption  September 27, 2003
Reviewer:  Sam
      Bernard  from Sandy, UT
How Rman finds block corruption? What does it check to find if the block is
corrupted or not when it backs up datafiles? Thanks.

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96566/rcmconc1.htm#465438

      Rating: 2
it does not
      help!  September 27, 2003
Reviewer:  Sam
       from Sandy, UT
From the above link,

<quote> If the server session encounters a datafile block during a backup that
has already been identified as corrupt by the database, then the server session
copies the corrupt block into the backup and Oracle logs the corruption in the
control file as either a logical or media corruption. RMAN copies the block in
case the user wants to try to salvage the contents of the block.

If RMAN encounters a datafile block with a corrupt header that has not already
been identified as corrupt by the database, then it writes the block to the
backup with a reformatted header indicating that the block has media corruption.
<quote>

The doc is so generic and it is not helpful how rman checks and what it checks
to see if the block is corrupt or not? May be it is a proprietory info for
oracle to share. Anyway, if you know how it does detect block corruption, please
share. Thanks.

          Followup:

it looks at the block - is says "does this block make sense, does it compute,
would it be able to be processed normally"

if not, block is not good.
if yes, block is good

exactly what it does, well, since you and I don't really even care what a block
is -- its beyond what we really need to know.  that is tests the logical
integrity of the block is way more then enough.

I've no idea if it is proprietary or not, it is just way beyond (the tests it
does) what we need to know.  it tests the block to see "can this block be made
sense of". thats all.

also quoted from that link:
...
RMAN tests data and index blocks that pass physical corruption checks for
logical corruption, for example, corruption of a row piece or index entry. If
RMAN finds logical corruption, it then logs the block in the alert.log and
server session trace file. By default, error checking for logical corruption is
disabled.
.....

      Rating:
      5
  September 29, 2003
Reviewer:
      A reader

      Rating:
      5
Thanks.  September 29, 2003
Reviewer:
      Sam  from Sandy, UT

      Rating:
      5
blockrecover  October 17, 2003
Reviewer:
      Shankar  from Houston, TX
Tom, I wanted to practice blockrecover feature in RMAN. Is there a way I can
cause block corruption in a datafile to result in ora 01578 error? I am using
9iR2 on Windows 2000. Do you have any script that would corrupt a datafile that
would result in block corruption error? Thanks.

          Followup:

you'd have to shutdown normally, use some binary editor to mess around with the
blocks on a TEST database.  startup and full scan the affected table to trigger
the error.  I don't have a script to corrupt a datafile, no.

I've written one off C programs to do so, but not a generic utility, no.

      Rating: 4
Which one is a
      binary editor on NT?  October 20, 2003
Reviewer:
      Reader

          Followup:

no idea -- maybe someone out that that uses windows has a favorite one they use.

      Rating: 4
windows binary
      editor  October 20, 2003
Reviewer:
      A reader  from Florida
http://www.textpad.com/

      Rating: 5
RE:
      TextPad  October 20, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
A Reader,

The TextPad website says that:

"TextPad ® is a powerful, general purpose editor for plain text files."

Does it actually have a binary editor too?

Thanks,

Mark

      Rating: 4
datafile
      corruption  October 28, 2003
Reviewer:
      Duncan  from UK
Tom

I am testing out a restore after loss of a datafile.
I took a hot backup using RMAN.  I then tested a full  restore of the database
which worked fine.

I then tested a point in time restore of the db which also worked ok. After
doing this I did not take another backup so my db was opened with a resetlogs.

Next I simulated media failure by deleting a datafile. I am now testing the
restore from the original backup set (taken before the pit recovery), RMAN is
reporting errors when I attempt this restore:

RMAN> run {
     allocate channel ch1 type disk;
     allocate channel ch2 type disk;
     allocate channel ch3 type disk;
     allocate channel ch4 type disk;
     restore database;
     recover  database ;
}
RMAN-03022: compiling command: allocate
RMAN-03023: executing command: allocate
RMAN-08030: allocated channel: ch1
RMAN-08500: channel ch1: sid=14 devtype=DISK

RMAN-03022: compiling command: allocate
RMAN-03023: executing command: allocate
RMAN-08030: allocated channel: ch2
RMAN-08500: channel ch2: sid=15 devtype=DISK

RMAN-03022: compiling command: allocate
RMAN-03023: executing command: allocate
RMAN-08030: allocated channel: ch3
RMAN-08500: channel ch3: sid=16 devtype=DISK

RMAN-03022: compiling command: allocate
RMAN-03023: executing command: allocate
RMAN-08030: allocated channel: ch4
RMAN-08500: channel ch4: sid=17 devtype=DISK

RMAN-03022: compiling command: restore
RMAN-03025: performing implicit partial resync of recovery catalog
RMAN-03023: executing command: partial resync
RMAN-08003: starting partial resync of recovery catalog
RMAN-08005: partial resync complete

RMAN-03022: compiling command: IRESTORE
RMAN-03026: error recovery releasing channel resources
RMAN-08031: released channel: ch1
RMAN-08031: released channel: ch2
RMAN-08031: released channel: ch3
RMAN-08031: released channel: ch4
RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-03002: failure during compilation of command
RMAN-03013: command type: restore
RMAN-03002: failure during compilation of command
RMAN-03013: command type: IRESTORE
RMAN-06026: some targets not found - aborting restore
RMAN-06023: no backup or copy of datafile 27 found to restore
RMAN-06023: no backup or copy of datafile 26 found to restore
RMAN-06023: no backup or copy of datafile 25 found to restore
RMAN-06023: no backup or copy of datafile 24 found to restore
RMAN-06023: no backup or copy of datafile 23 found to restore
RMAN-06023: no backup or copy of datafile 15 found to restore
RMAN-06023: no backup or copy of datafile 9 found to restore
RMAN-06023: no backup or copy of datafile 7 found to restore
RMAN-06023: no backup or copy of datafile 5 found to restore
RMAN-06023: no backup or copy of datafile 4 found to restore
RMAN-06023: no backup or copy of datafile 3 found to restore
RMAN-06023: no backup or copy of datafile 2 found to restore
RMAN-06023: no backup or copy of datafile 1 found to restore

RMAN> list backup;

RMAN-03022: compiling command: list

List of Backup Sets
Key     Recid      Stamp      LV Set Stamp  Set Count  Completion Time
------- ---------- ---------- -- ---------- ---------- ----------------------
47672   1          508589487  0  508589321  2          28-OCT-03

    List of Backup Pieces
    Key     Pc# Cp# Status      Completion Time        Piece Name
    ------- --- --- ----------- ---------------------- ------------------------
    47676   1   1   AVAILABLE   28-OCT-03              /op4/backup/02f50t89_1_1

    List of Datafiles Included
    File Name                                  LV Type Ckp SCN    Ckp Time
    ---- ------------------------------------- -- ---- ---------- -------------
    7    /op4/datafiles/drsys01.dbf            0  Full 164683756  28-OCT-03
    9    /op4/datafiles/DATA_S_2.dbf           0  Full 164683756  28-OCT-03
    24   /op4/datafiles/temp03.dbf             0  Full 164683756  28-OCT-03
    26   /op4/datafiles/DATA_M_1.dbf           0  Full 164683756  28-OCT-03

List of Backup Sets
Key     Recid      Stamp      LV Set Stamp  Set Count  Completion Time
------- ---------- ---------- -- ---------- ---------- ----------------------
47673   2          508589567  0  508589321  1          28-OCT-03

    List of Backup Pieces
    Key     Pc# Cp# Status      Completion Time        Piece Name
    ------- --- --- ----------- ---------------------- ------------------------
    47677   1   1   AVAILABLE   28-OCT-03              /op4/backup/01f50t89_1_1

    List of Datafiles Included
    File Name                                  LV Type Ckp SCN    Ckp Time
    ---- ------------------------------------- -- ---- ---------- -------------
    1    /oracle/app/oracle/oradata/op4/system01.dbf 0  Full 164683755  28-OCT-0
3
    23   /op4/datafiles/temp02.dbf             0  Full 164683755  28-OCT-03
    25   /op4/datafiles/DATA_S_1.dbf           0  Full 164683755  28-OCT-03

List of Backup Sets
Key     Recid      Stamp      LV Set Stamp  Set Count  Completion Time
------- ---------- ---------- -- ---------- ---------- ----------------------
47674   3          508589575  0  508589321  3          28-OCT-03

    List of Backup Pieces
    Key     Pc# Cp# Status      Completion Time        Piece Name
    ------- --- --- ----------- ---------------------- ------------------------
    47678   1   1   AVAILABLE   28-OCT-03              /op4/backup/03f50t89_1_1

    List of Datafiles Included
    File Name                                  LV Type Ckp SCN    Ckp Time
    ---- ------------------------------------- -- ---- ---------- -------------
    2    /op4/datafiles/tools01.dbf            0  Full 164683757  28-OCT-03
    5    /op4/datafiles/users01.dbf            0  Full 164683757  28-OCT-03
    15   /op4/datafiles/rbs02.dbf              0  Full 164683757  28-OCT-03
    27   /op4/datafiles/DATA_L_1.dbf           0  Full 164683757  28-OCT-03

List of Backup Sets
Key     Recid      Stamp      LV Set Stamp  Set Count  Completion Time
------- ---------- ---------- -- ---------- ---------- ----------------------
47675   4          508589642  0  508589321  4          28-OCT-03

    List of Backup Pieces
    Key     Pc# Cp# Status      Completion Time        Piece Name
    ------- --- --- ----------- ---------------------- ------------------------
    47679   1   1   AVAILABLE   28-OCT-03              /op4/backup/04f50t89_1_1

    List of Datafiles Included
    File Name                                  LV Type Ckp SCN    Ckp Time
    ---- ------------------------------------- -- ---- ---------- -------------
    3    /op4/datafiles/rbs01.dbf              0  Full 164683758  28-OCT-03
    4    /op4/datafiles/temp01.dbf             0  Full 164683758  28-OCT-03

What is causing these errors? Is it because I didn't take a backup after doing
the pit recovery? Am I not allowed to use an older backup set?

          Followup:

you need to take a full backup immediately after a resetlogs.  resetlogs BREAKS
the redo chain.

      Rating: 5
Read about
      database incarnation  October 28, 2003
Reviewer:
      Kishan
Hi Duncan:

A resetlogs on a database completes a logical lifetime of the database and it
creates a new incarnation of the same.
So, unless you have a backup of the current incarnation, you may not be able to
recover it.
If you are using 9i, you can use RMAN to list all the incarnations and restore
the database to a previous incarnation, provided you have all the necessary
backup( control file, data file, archive logs) available for that incarnation.

Hope this helps.

      Rating:
      5
Thanks  October 29, 2003
Reviewer:
      Duncan  from UK
Thanks very much, very useful.

October 28, 2003 Harris -- Thanks for the question regarding "High
      CPU in TKPROF for DDL", version 9.0.1

      You Asked
Hi,

I've a simple DDL statement inserting 47 records to a table- the CPU and ELAPSED
Times stats from TKPROF are unusually high for the amount of rows being
inserted.
There are approx 10 triggers on this table- although i can see the trigger code
execute in a reasonable amount of time.
The whole tkprof output file has unreasonable CPU and Elapsed Times.
What approach should I take from here in investigating this?

********************************************************************************

UPDATE  OE_ORDER_LINES
    SET     ACCOUNTING_RULE_ID             = :b310
    ,       ACCOUNTING_RULE_DURATION       = :b309
    ,       CALCULATE_PRICE_FLAG            = :b308
    ,       ACTUAL_ARRIVAL_DATE            = :b307
    ,       ACTUAL_SHIPMENT_DATE           = :b306
    ,       AGREEMENT_ID                   = :b305
    ,       ARRIVAL_SET_ID                 = :b304
    ,       ATO_LINE_ID                    = :b303
    ,       ATTRIBUTE1                     = :b302
    ,       ATTRIBUTE10                    = :b301
    ,       ATTRIBUTE11                    = :b300
    ,       ATTRIBUTE12                    = :b299
    ,       ATTRIBUTE13                    = :b298
    ,       ATTRIBUTE14                    = :b297
    ,       ATTRIBUTE15                    = :b296
    ,       ATTRIBUTE16                    = :b295   --For bug 2184255
    ,       ATTRIBUTE17                    = :b294
    ,       ATTRIBUTE18                    = :b293
    ,       ATTRIBUTE19                    = :b292
    ,       ATTRIBUTE2                     = :b291
    ,       ATTRIBUTE20                    = :b290
    ,       ATTRIBUTE3                     = :b289
    ,       ATTRIBUTE4                     = :b288
    ,       ATTRIBUTE5                     = :b287
    ,       ATTRIBUTE6                     = :b286
    ,       ATTRIBUTE7                     = :b285
    ,       ATTRIBUTE8                     = :b284
    ,       ATTRIBUTE9                     = :b283
    ,       AUTO_SELECTED_QUANTITY         = :b282
    ,       AUTHORIZED_TO_SHIP_FLAG        = :b281
    ,       BOOKED_FLAG                    = :b280
    ,       CANCELLED_FLAG                 = :b279
    ,       CANCELLED_QUANTITY             = :b278
    ,       COMMITMENT_ID                  = :b277
    ,       COMPONENT_CODE                 = :b276
    ,       COMPONENT_SEQUENCE_ID          = :b275
    ,       CONFIG_HEADER_ID               = :b274
    ,       CONFIG_REV_NBR                 = :b273
    ,       CONFIG_DISPLAY_SEQUENCE        = :b272
    ,       CONFIGURATION_ID               = :b271
    ,       CONTEXT                        = :b270
    ,       CREATED_BY                     = :b269
    ,       CREATION_DATE                  = :b268
    ,       CREDIT_INVOICE_LINE_ID         = :b267
    ,       CUSTOMER_LINE_NUMBER       = :b266
    ,       CUSTOMER_SHIPMENT_NUMBER       = :b265
    ,       CUSTOMER_ITEM_NET_PRICE        = :b264
    ,       CUSTOMER_PAYMENT_TERM_ID       = :b263
    ,       CUSTOMER_DOCK_CODE             = :b262
    ,       CUSTOMER_JOB                   = :b261
    ,       CUSTOMER_PRODUCTION_LINE       = :b260
    ,       CUST_PRODUCTION_SEQ_NUM        = :b259
    ,       CUSTOMER_TRX_LINE_ID           = :b258
    ,       CUST_MODEL_SERIAL_NUMBER       = :b257
    ,       CUST_PO_NUMBER                 = :b256
    ,       DELIVERY_LEAD_TIME             = :b255
    ,       DELIVER_TO_CONTACT_ID          = :b254
    ,       DELIVER_TO_ORG_ID              = :b253
    ,       DEMAND_BUCKET_TYPE_CODE        = :b252
    ,       DEMAND_CLASS_CODE              = :b251
    ,       DEP_PLAN_REQUIRED_FLAG         = :b250
    --,       DROP_SHIP_FLAG           = p_line_rec.drop_ship_flag
    ,       EARLIEST_ACCEPTABLE_DATE       = :b249
    ,       END_ITEM_UNIT_NUMBER           = :b248
    ,       EXPLOSION_DATE                 = :b247
    ,       FIRST_ACK_CODE                 = :b174
    ,       FIRST_ACK_DATE                 = :b173
    ,       FOB_POINT_CODE                 = :b246
    ,       FREIGHT_CARRIER_CODE           = :b245
    ,       FREIGHT_TERMS_CODE             = :b244
    ,       FULFILLED_QUANTITY             = :b243
    ,       FULFILLED_FLAG                 = :b242
    ,       FULFILLMENT_METHOD_CODE        = :b241
    ,       FULFILLMENT_DATE               = :b240
    ,       GLOBAL_ATTRIBUTE1              = :b239
    ,       GLOBAL_ATTRIBUTE10             = :b238
    ,       GLOBAL_ATTRIBUTE11             = :b237
    ,       GLOBAL_ATTRIBUTE12             = :b236
    ,       GLOBAL_ATTRIBUTE13             = :b235
    ,       GLOBAL_ATTRIBUTE14             = :b234
    ,       GLOBAL_ATTRIBUTE15             = :b233
    ,       GLOBAL_ATTRIBUTE16             = :b232
    ,       GLOBAL_ATTRIBUTE17             = :b231
    ,       GLOBAL_ATTRIBUTE18             = :b230
    ,       GLOBAL_ATTRIBUTE19             = :b229
    ,       GLOBAL_ATTRIBUTE2              = :b228
    ,       GLOBAL_ATTRIBUTE20             = :b227
    ,       GLOBAL_ATTRIBUTE3              = :b226
    ,       GLOBAL_ATTRIBUTE4              = :b225
    ,       GLOBAL_ATTRIBUTE5              = :b224
    ,       GLOBAL_ATTRIBUTE6              = :b223
    ,       GLOBAL_ATTRIBUTE7              = :b222
    ,       GLOBAL_ATTRIBUTE8              = :b221
    ,       GLOBAL_ATTRIBUTE9              = :b220
    ,       GLOBAL_ATTRIBUTE_CATEGORY      = :b219
    ,       HEADER_ID                      = :b1
    ,       INDUSTRY_ATTRIBUTE1            = :b218
    ,       INDUSTRY_ATTRIBUTE10           = :b217
    ,       INDUSTRY_ATTRIBUTE11           = :b216
    ,       INDUSTRY_ATTRIBUTE12           = :b215
    ,       INDUSTRY_ATTRIBUTE13           = :b214
    ,       INDUSTRY_ATTRIBUTE14           = :b213
    ,       INDUSTRY_ATTRIBUTE15           = :b212
    ,       INDUSTRY_ATTRIBUTE16           = :b211
    ,       INDUSTRY_ATTRIBUTE17           = :b210
    ,       INDUSTRY_ATTRIBUTE18           = :b209
    ,       INDUSTRY_ATTRIBUTE19           = :b208
    ,       INDUSTRY_ATTRIBUTE20           = :b207
    ,       INDUSTRY_ATTRIBUTE21           = :b206
    ,       INDUSTRY_ATTRIBUTE22           = :b205
    ,       INDUSTRY_ATTRIBUTE23           = :b204
    ,       INDUSTRY_ATTRIBUTE24           = :b203
    ,       INDUSTRY_ATTRIBUTE25           = :b202
    ,       INDUSTRY_ATTRIBUTE26           = :b201
    ,       INDUSTRY_ATTRIBUTE27           = :b200
    ,       INDUSTRY_ATTRIBUTE28           = :b199
    ,       INDUSTRY_ATTRIBUTE29           = :b198
    ,       INDUSTRY_ATTRIBUTE30           = :b197
    ,       INDUSTRY_ATTRIBUTE2            = :b196
    ,       INDUSTRY_ATTRIBUTE3            = :b195
    ,       INDUSTRY_ATTRIBUTE4            = :b194
    ,       INDUSTRY_ATTRIBUTE5            = :b193
    ,       INDUSTRY_ATTRIBUTE6            = :b192
    ,       INDUSTRY_ATTRIBUTE7            = :b191
    ,       INDUSTRY_ATTRIBUTE8            = :b190
    ,       INDUSTRY_ATTRIBUTE9            = :b189
    ,       INDUSTRY_CONTEXT               = :b188
    ,       INTMED_SHIP_TO_CONTACT_ID = :b187
    ,       INTMED_SHIP_TO_ORG_ID     = :b186
    ,       INVENTORY_ITEM_ID              = :b185
    ,       INVOICE_INTERFACE_STATUS_CODE          = :b184
    ,       INVOICE_TO_CONTACT_ID          = :b183
    ,       INVOICE_TO_ORG_ID              = :b182
    ,       INVOICED_QUANTITY              = :b181
    ,       INVOICING_RULE_ID              = :b180
    ,       ORDERED_ITEM_ID                        = :b179
    ,       ITEM_IDENTIFIER_TYPE           = :b178
    ,       ORDERED_ITEM                     = :b177
    ,       ITEM_REVISION                  = :b176
    ,       ITEM_TYPE_CODE                 = :b175
    ,       LAST_ACK_CODE                  = :b174
    ,       LAST_ACK_DATE                  = :b173
    ,       LAST_UPDATED_BY                = :b172
    ,       LATEST_ACCEPTABLE_DATE         = :b171
    ,       LAST_UPDATE_DATE               = :b170
    ,       LAST_UPDATE_LOGIN              = :b169
    ,       LINE_CATEGORY_CODE             = :b168
    ,       LINE_NUMBER                    = :b167
    ,       LINE_TYPE_ID                   = :b166
    ,       LINK_TO_LINE_ID                = :b165
    ,       MODEL_GROUP_NUMBER             = :b164
 --   ,       MFG_COMPONENT_SEQUENCE_ID      =
p_line_rec.mfg_component_sequence_id
    ,       MFG_LEAD_TIME                  = :b163
    ,       OPEN_FLAG                      = :b162
    ,       OPTION_FLAG                    = :b161
    ,       OPTION_NUMBER                  = :b160
    ,       ORDERED_QUANTITY               = :b159
    ,       ORDERED_QUANTITY2              = :b158
    ,       ORDER_QUANTITY_UOM             = :b157
    ,       ORDERED_QUANTITY_UOM2          = :b156
--We should not allow to update org_id(operting unit)
--  ,       ORG_ID                         = p_line_rec.org_id
    ,       ORDER_SOURCE_ID               = :b155
    ,       ORIG_SYS_DOCUMENT_REF          = :b154
    ,       ORIG_SYS_LINE_REF              = :b153
    ,       ORIG_SYS_SHIPMENT_REF          = :b152
    ,       CHANGE_SEQUENCE                = :b151
    ,       OVER_SHIP_REASON_CODE          = :b150
    ,       OVER_SHIP_RESOLVED_FLAG        = :b149
    ,       PAYMENT_TERM_ID                = :b148
    ,       PLANNING_PRIORITY              = :b147
    ,       PREFERRED_GRADE                = :b146
    ,       PRICE_LIST_ID                  = :b145
    ,       PRICE_REQUEST_CODE             = :b144  -- PROMOTIONS SEP/01
    ,       PRICING_ATTRIBUTE1             = :b143
    ,       PRICING_ATTRIBUTE10            = :b142
    ,       PRICING_ATTRIBUTE2             = :b141
    ,       PRICING_ATTRIBUTE3             = :b140
    ,       PRICING_ATTRIBUTE4             = :b139
    ,       PRICING_ATTRIBUTE5             = :b138
    ,       PRICING_ATTRIBUTE6             = :b137
    ,       PRICING_ATTRIBUTE7             = :b136
    ,       PRICING_ATTRIBUTE8             = :b135
    ,       PRICING_ATTRIBUTE9             = :b134
    ,       PRICING_CONTEXT                = :b133
    ,       PRICING_DATE                   = :b132
    ,       PRICING_QUANTITY               = :b131
    ,       PRICING_QUANTITY_UOM           = :b130
    ,       PROGRAM_APPLICATION_ID         = :b129
    ,       PROGRAM_ID                     = :b128
    ,       PROGRAM_UPDATE_DATE            = :b127
    ,       PROJECT_ID                     = :b126
    ,       PROMISE_DATE                   = :b125
    ,       RE_SOURCE_FLAG                 = :b124
    ,       REFERENCE_CUSTOMER_TRX_LINE_ID = :b123
    ,       REFERENCE_HEADER_ID            = :b122
    ,       REFERENCE_LINE_ID              = :b121
    ,       REFERENCE_TYPE                 = :b120
    ,       REQUEST_DATE                   = :b119
    ,       REQUEST_ID                     = :b118
    ,       RETURN_ATTRIBUTE1              = :b117
    ,       RETURN_ATTRIBUTE10             = :b116
    ,       RETURN_ATTRIBUTE11             = :b115
    ,       RETURN_ATTRIBUTE12             = :b114
    ,       RETURN_ATTRIBUTE13             = :b113
    ,       RETURN_ATTRIBUTE14             = :b112
    ,       RETURN_ATTRIBUTE15             = :b111
    ,       RETURN_ATTRIBUTE2              = :b110
    ,       RETURN_ATTRIBUTE3              = :b109
    ,       RETURN_ATTRIBUTE4              = :b108
    ,       RETURN_ATTRIBUTE5              = :b107
    ,       RETURN_ATTRIBUTE6              = :b106
    ,       RETURN_ATTRIBUTE7              = :b105
    ,       RETURN_ATTRIBUTE8              = :b104
    ,       RETURN_ATTRIBUTE9              = :b103
    ,       RETURN_CONTEXT                 = :b102
    ,       RETURN_REASON_CODE             = :b101
    ,       RLA_SCHEDULE_TYPE_CODE         = :b100
    ,       SALESREP_ID                    = :b99
    ,       SCHEDULE_ARRIVAL_DATE          = :b98
    ,       SCHEDULE_SHIP_DATE             = :b97
    ,       SCHEDULE_STATUS_CODE           = :b96
    ,       SHIPMENT_NUMBER                = :b95
    ,       SHIPMENT_PRIORITY_CODE         = :b94
    ,       SHIPPED_QUANTITY               = :b93
    ,       SHIPPED_QUANTITY2              = :b92  -- OPM B1661023 04/02/01
    ,       SHIPPING_METHOD_CODE           = :b91
    ,       SHIPPING_QUANTITY              = :b90
    ,       SHIPPING_QUANTITY2             = :b89 -- OPM B1661023 04/02/01
    ,       SHIPPING_QUANTITY_UOM          = :b88
    ,       SHIP_FROM_ORG_ID               = :b87
    ,       SUBINVENTORY                   = :b86
    ,       SHIP_TOLERANCE_ABOVE           = :b85
    ,       SHIP_TOLERANCE_BELOW           = :b84
    ,       SHIPPABLE_FLAG                 = :b83
    ,       SHIPPING_INTERFACED_FLAG       = :b82
    ,       SHIP_TO_CONTACT_ID             = :b81
    ,       SHIP_TO_ORG_ID                 = :b80
    ,       SHIP_MODEL_COMPLETE_FLAG       = :b79
    ,       SHIP_SET_ID                    = :b78
    ,       SOLD_TO_ORG_ID                 = :b77
    ,       SOLD_FROM_ORG_ID               = :b76
    ,       SORT_ORDER                     = :b75
    ,       SOURCE_DOCUMENT_ID             = :b74
    ,       SOURCE_DOCUMENT_LINE_ID        = :b73
    ,       SOURCE_DOCUMENT_TYPE_ID        = :b72
    ,       SOURCE_TYPE_CODE               = :b71
    ,       SPLIT_FROM_LINE_ID             = :b70
    ,       LINE_SET_ID                    = :b69
    ,       SPLIT_BY                       = :b68
    ,       MODEL_REMNANT_FLAG             = :b67
    ,       TASK_ID                        = :b66
    ,       TAX_CODE                       = :b65
    ,       TAX_DATE                       = :b64
    ,       TAX_EXEMPT_FLAG                = :b63
    ,       TAX_EXEMPT_NUMBER              = :b62
    ,       TAX_EXEMPT_REASON_CODE         = :b61
    ,       TAX_POINT_CODE                 = :b60
    ,       TAX_RATE                       = :b59
    ,       TAX_VALUE                      = :b58
    ,       TOP_MODEL_LINE_ID              = :b57
    ,       UNIT_LIST_PRICE                = :b56
    ,       UNIT_LIST_PRICE_PER_PQTY       = :b55
    ,       UNIT_SELLING_PRICE             = :b54
    ,       UNIT_SELLING_PRICE_PER_PQTY    = :b53
    ,       VISIBLE_DEMAND_FLAG            = :b52
    ,       VEH_CUS_ITEM_CUM_KEY_ID        = :b51
    ,       SHIPPING_INSTRUCTIONS          = :b50
    ,       PACKING_INSTRUCTIONS           = :b49
    ,       SERVICE_TXN_REASON_CODE        = :b48
    ,       SERVICE_TXN_COMMENTS           = :b47
    ,       SERVICE_DURATION               = :b46
    ,       SERVICE_PERIOD                 = :b45
    ,       SERVICE_START_DATE             = :b44
    ,       SERVICE_END_DATE               = :b43
    ,       SERVICE_COTERMINATE_FLAG       = :b42
    ,       UNIT_LIST_PERCENT           = :b41
    ,       UNIT_SELLING_PERCENT        = :b40
    ,       UNIT_PERCENT_BASE_PRICE     = :b39
    ,       SERVICE_NUMBER              = :b38
    ,       SERVICE_REFERENCE_TYPE_CODE = :b37
    ,       SERVICE_REFERENCE_LINE_ID   = :b36
    ,       SERVICE_REFERENCE_SYSTEM_ID = :b35
    ,       TP_CONTEXT                  = :b34
    ,       TP_ATTRIBUTE1               = :b33
    ,       TP_ATTRIBUTE2               = :b32
    ,       TP_ATTRIBUTE3               = :b31
    ,       TP_ATTRIBUTE4               = :b30
    ,       TP_ATTRIBUTE5               = :b29
    ,       TP_ATTRIBUTE6               = :b28
    ,       TP_ATTRIBUTE7               = :b27
    ,       TP_ATTRIBUTE8               = :b26
    ,       TP_ATTRIBUTE9               = :b25
    ,       TP_ATTRIBUTE10              = :b24
    ,       TP_ATTRIBUTE11              = :b23
    ,       TP_ATTRIBUTE12              = :b22
    ,       TP_ATTRIBUTE13              = :b21
    ,       TP_ATTRIBUTE14              = :b20
    ,       TP_ATTRIBUTE15              = :b19
    ,       FLOW_STATUS_CODE             = :b18
    ,       MARKETING_SOURCE_CODE_ID    = :b17
    ,       ORIGINAL_INVENTORY_ITEM_ID  = :b16
    ,       ORIGINAL_ITEM_IDENTIFIER_TYPE = :b15
    ,       ORIGINAL_ORDERED_ITEM_ID    = :b14
    ,       ORIGINAL_ORDERED_ITEM       = :b13
    ,       ITEM_RELATIONSHIP_TYPE      = :b12
    ,       ITEM_SUBSTITUTION_TYPE_CODE = :b11
    ,       LATE_DEMAND_PENALTY_FACTOR  = :b10
    ,       OVERRIDE_ATP_DATE_CODE      = :b9
    ,       USER_ITEM_DESCRIPTION       = :b8
    ,       BLANKET_NUMBER              = :b7
    ,       BLANKET_LINE_NUMBER         = :b6
    ,       BLANKET_VERSION_NUMBER      = :b5
    --MRG B
    ,       UNIT_COST                   = :b4
    --MRG E
    ,       LOCK_CONTROL                = :b3
    WHERE   LINE_ID                = :b2
      AND   HEADER_ID                = :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1   6800.00    6856.81          0          0          0           0
Execute     47  13400.00   14117.99          0        188        159          47
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       48  20200.00   20974.80          0        188        159          47

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 173  (APPS)   (recursive depth: 1)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  UPDATE STATEMENT   GOAL: CHOOSE
      0   UPDATE OF 'OE_ORDER_LINES_ALL'
      0    TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF
               'OE_ORDER_LINES_ALL'
      0     INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF 'OE_ORDER_LINES_U1'
                (UNIQUE)

********************************************************************************

      and we said...
looks like you used the 8i tkprof tool on a 9i trace file.

The times in a 9i trace file are in millionths of seconds.  The times in 8i were
thousands of seconds.

So, you are off by a couple of orders of magnitude here.

Use 9i's tkprof to get the right output.

9i trace + 9i tkprof:

select count(*) from all_objects

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.04          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      6.88       6.72          0     151060          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      6.91       6.77          0     151060          0           1

9i trace + 8i tkprof:

select count(*) from all_objects

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1    300.00     465.40          0          0          0           0
Execute      1      0.00       5.22          0          0          0           0
Fetch        2  68800.00   67269.50          0     151060          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4  69100.00   67740.12          0     151060          0           1

so, you can slide the decimal place over 4 places as well, your tkprof should
therefore be:

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1       .68        .68          0          0          0           0
Execute     47      1.34       1.41          0        188        159          47
Fetch        0      0.00       0.00          0          0          0           0

Review &
      Followup

      Rating:
      5
  October 28, 2003
Reviewer:
      A reader
Great Stuff!!

October 28, 2003 ahmad -- Thanks for the question regarding
      "Bitmap Indexes", version 8.0.5

      You Asked
Hi tom,
tanx for ur help about OPS ,Now i have problem with bitmap
indexes.
what is the structure of a bitmap index?how is it made and how
is it stored
e,g;
ENAME
------
KING
MILLER
JHON
AHMAD
1.HOW R BITMAP FOR THESE VALUES ABOVE MADE ?
2. HOW ARE THEY STORED?
3. IF THERE is another duplicate value(two times KING) ,so is a
bitmap created for both KING or the one made previously is used
4. are the bitmaps for the same value stored in one block and
can they span more than one block.
5.what is the BIT-and-or function.
tanx in advance

      and we said...
I strongly recommend a cover to cover reading of the Oracle
Server Concepts manual for everyone (I do it myself with every
x.y release where y changes -- its a great refresher and you
pick up on new things that were added as well).  See

http://technet.oracle.com/doc/oracle8i_816/server.816/a76965/toc.htm
Virtually all of your questions are addressed in this manual,
specifically on the section dedicated to bitmapped indexes:

http://technet.oracle.com/doc/oracle8i_816/server.816/a76965/c08schem.htm#5381
In a bitmap index, the data is stored in the index like this:

KING   00000010000..00101001000100
MILLER 00001000000...0010010010000
JHON   00000000010.....00100100001
AHMAD  00100000000....000000001010

The "key value" (ename in this example) is in the index only a
few times (sometimes once and sometimes more then once) and
accompanied by a bitmap.  This bitmap is a pointer to the rows
that contain this value.  Therefore, 1 single index entry may
point to MANY rows simultaneously.

See the chapter on bitmapped indexes.  It goes over their
structure, how they are stored, bit or'ing and and'ing and so on.
Review &
      Followup

      Rating: 4
bitmap
      index  May 06, 2002
Reviewer:  Brigitte
       from Germany
 Hallo Tom,

 I have 2 tables with almost the same number of rows (41693, 42014). The bitmap
indexes have the same distinct values (3). The columns mandant are defined with
NOT NULL.

 select i.TABLE_NAME,i.INDEX_NAME,column_name,substr(INDEX_TYPE,1,6) type,
 BLEVEL,LEAF_BLOCKS,DISTINCT_KEYS,AVG_LEAF_BLOCKS_PER_KEY,
 AVG_DATA_BLOCKS_PER_KEY,CLUSTERING_FACTOR,NUM_ROWS
 from dba_indexes i, dba_ind_columns c
 where i.index_name=c.index_name;
                                                   AVG AVG
                                       Le  Da
                    In     B  num         af  ta
                   dex Le Leaf        Blo Blo
 ta                 ty ve  Blo     dist  cks cks Clust   num
 ble Index     col  pe  l  cks     Keys  Key Key  Fakt  rows
 --- --------- --- --- -- ---- ------ --- --- ----- -----
 ARC BM_ARCHIV MAN BIT    1    2      3   1   1     4     4
     PK_ARCHIV AID NOR    1  109    41697    1   1 41697 41697
 DOK BM_DOKU   MAN BIT    1    3      3   1   9    27    27
     PK_DOKU   OID NOR    1   90  42014   1   1 42014 42014
     UK_DOKU   NID NOR    1  244    42014    1   1 41954 42014

 But there is a big difference in the number of extents of the bitmap indexes
bm_archiv and bm_doku(64 k to 2,2 mb).
 I can't understand the big number of extents of bm_doku, because there is only
1 bit per table row.

select SEGMENT_NAME, SEGMENT_TYPE,TABLESPACE_NAME,EXTENT_ID,FILE_ID,
BLOCK_ID,BYTES "Bytes",BLOCKS "Blocks"
from dba_extents
where SEGMENT_TYPE=upper('INDEX);

                           ext file  Block               Blo
SEGMENT_NAME   seg_type     id   id     id       Bytes   cks
-------------- ---------- ---- ---- ------ ----------- -----
BM_ARCHIV      INDEX         0   11   1657      65,536     8
****************************               ----------- -----
Summe                                           65,536     8

PK_ARCHIV      INDEX         0   11      9      65,536     8
               INDEX         1   11    801      65,536     8
. . .
               INDEX        13   11   1025      65,536     8
               INDEX        14   11   1065      65,536     8
****************************               ----------- -----
Summe                                          983,040   120

BM_DOKU        INDEX         0   11   1033      65,536     8
               INDEX         1   11   1057      65,536     8
. . .
               INDEX        33   11   1537      65,536     8
               INDEX        34   11   1545      65,536     8
****************************               ----------- -----
Summe                                        2,293,760   280

PK_DOKU        INDEX         0   11    881      65,536     8
               INDEX         1   11    889      65,536     8
. . .
               INDEX        10   11    961      65,536     8
               INDEX        11   11    969      65,536     8
****************************               ----------- -----
Summe                                          786,432    96

UK_DOKU        INDEX          0   11    497     65,536     8
               INDEX          1   11    505     65,536     8
. . .
               INDEX         30   11    737     65,536     8
               INDEX         31   11   1337     65,536     8
****************************                ---------- -----
Summe                                        2,097,152   256

Thanks

          Followup:

I'm going to recommend a book

http://www.amazon.com/exec/obidos/ASIN/0201715848
Practical Oracle 8i: Building Efficient Databases
by Jonathan Lewis

It is an excellent book and will also help to clear up why the bitmaps are
different sizes (it is based on how the table data is physically organized --
that can have a huge impact on the size of the bitmap.  As well, how the data
was input into the system can have a large impact as well -- was the index built
before loading, maintained during a load/insertion, or built afterwards)

In any case -- you will not be sorry you have this book.  Guaranteed!

Here is an example showing that the physical organization can impact the size of
the resulting bitmap index:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t1 as select owner from
big_table order by owner;

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from t1;

  COUNT(*)
----------
   1099008

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t2 as select owner from t1 order
by dbms_random.value;

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create bitmap index t1_bm on t1(owner);
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create bitmap index t2_bm on t2(owner);
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec show_space( 'T1_BM', user, 'INDEX' );
Free Blocks.............................0
Total Blocks............................64
Total Bytes.............................524288
Unused Blocks...........................22
Unused Bytes............................180224
Last Used Ext FileId....................7
Last Used Ext BlockId...................44233
Last Used Block.........................42

PL/SQL procedure successfully completed.

total of 64-22 = 42 blocks used

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec show_space( 'T2_BM', user, 'INDEX' );
Free Blocks.............................0
Total Blocks............................128
Total Bytes.............................1048576
Unused Blocks...........................30
Unused Bytes............................245760
Last Used Ext FileId....................7
Last Used Ext BlockId...................44361
Last Used Block.........................34

PL/SQL procedure successfully completed.

total of 128-30 = 98 blocks used.  And the only difference -- one table is
organized by owner, the other is randomly sorted.  The compactness of the
bitmaps is directly related to the organization of the data

      Rating: 4
Short and
      Simple !!  May 06, 2002
Reviewer:  pawan
       from USA
Excellent explanation Tom.

      Rating:
      5
  May 06, 2002
Reviewer:  Dave

Is this clustering factor much of an influence on the Cost of index access,
according to the CBO, Tom?

          Followup:

Yes, it does.  If you have my book -- see page 279 where I create a
"disorganized" and "colocated" table and show the effects on the performance,
the plan, and the optimizer.

      Rating:
      4
  May 07, 2002
Reviewer:  Brigitte
       from Germany
Thanks for your quick response

      Rating: 5
bitmap
        January 27, 2003
Reviewer:  mo
Tom:

In your book, chapter 7:

1. Are you recommending not to use bitmap index at all for OLTP system even if
you have a table with gender column and
yo uare doing queries on it?

2. If i have a table with 100,000 records and I ceate a b-tree index on gender I
will have 100,000 entries in the index while a bitmap will only create two
entries each pointing to thousands of table rows? Is this correct?

3.  let me clarify this. are you saying that with a bitmap index on gender in
OLTP, someone updates one row for gender. Then he locks thousands of rows or all
rows in the table with a value for that gender? This would be very bad if true?

4. How would you make a decision. If you only query gender but do not update
then it is ok to create a bitmap index but if you allow edit of gender then use
a b-tree index? is this valid?

Thank you,

          Followup:

1) if you do lots of single row updates/deletes/inserts to a table, you will not
have a bitmap index on it, if you do -- you will have a headache.

If I have an OLTP system -- why would I be querying by GENDER?

No, I would not use a bitmap index on any transactional tables....

2) that is an over simplification but a handy way to think about it.  The number
of entries in the bitmap will conceptually be two but physically the bitmaps
will be stored in chunks and there would undoubtably be more then two entries.

3) yes, that is the nature of the bitmap.  Consider the sex change operation --
now you've locked lots of "M" records and "F" records all for a single row.
That is only part of the problem -- a bitmap index quickly deteriorates with
lots of singleton updates to it and the size gets really large really fast.

4) do you insert into this table?  Delete from it?  inserts and deletes do the
damage just as nicely as an update.

If you modify the table frequently -- you will not be using a bitmapped index
and I would be hard pressed to see why you would need to in a TRANSACTIONAL
system.

      Rating: 5
bitmap and the
      optimizer  May 29, 2003
Reviewer:  Reader
       from Houston, TX
(1) Is it true that the cost based optimizer will recognize the bitmap index
only if the table (col of the table, say the gender col on which the bitmap
index is created) is analyzed? In other words, if i created a bitmap index on a
table col and if i did not analyze the table, then the bitmap index will never
be used by the optimizer. Is it correct tom? Thanks.

(2) As a followup to an earlier query in this thread, if i have created a bitmap
index on the job column of emp table and if i update ONE row for job col with
value from CLERK to ANALYST, does oracle lock all of the rows that have values
of either CLERK or ANALYST? Thanks.

          Followup:

1) it is true that only the CBO will recognized bitmapped indexes.

How to invoke the cbo?  that could happen in many ways

o hints
o optimizer_mode/goal
o use of features that require it like partitions, iots, domain indexes
o access of at least ONE analyzed table in a query

so no, just not analyzing will not make a bitmap "never used".  other things can
cause it to become visible.  all that is needed is that the CBO is used

2) no, it does not.  see

http://www.dbazine.com/jlewis3.html
jonathan goes into this in some detail there.

      Rating: 4
Bitmap index in
      your new book  July 19, 2003
Reviewer:
      steve  from NYC, USA
Hi Tom,

On Page 16 in your new book, Effective Oracle by Design,
you mentioned that
"Using a bitmap index, the query could go from running in hours to executing in
less than a minute". Could you please give me an example of that?

Thanks!

Steve

          Followup:

think about it -- haven't you seen indexes take a query from "really long" to
"really fast"...

take a big table (say 50,000,000 rows).
say it has columns "state", "product", "color" in it (and lots of other stuff)

you have 50 states * 5 products * 2 colors.  500 distinct values.  massively
skewed data (you sell more in CA then in RI)

now, tell me how many red gidgets you sold in virginia.  fast -- bitmap = fast,
b*tree = no useful due to low cardinality and skew, full scan = read lots.

      Rating: 5
bitmap index on
      iot  August 21, 2003
Reviewer:  A Reader

Tom, could you help me understanding usefulness of the mapping table for IOT if
a bitmap index is created on IOT? do i have to periodically rebuild the mapping
tables depending upon the updates/deletes on IOT? If so, could you please guide
me how it is done? Thanks.

          Followup:

the mapping table just hides the universal rowid from the bitaamp -- it is a
simple translation layer.

I would not rebuild it, no.

      Rating: 5
Bitmap index on
      a global temporary table  October 28, 2003

Reviewer:  Piotr Jarmuz  from Poznan, Poland

Will a bitmap index on some global temporary table also negatively impact
performance in OLTP? Or maybe each session will see its own "instance"?

Thank you in advance.

          Followup:

each will see its own and only its own (each session gets their own segment).

I cannot fathom the use tho.

      Rating: 5
It's not really
      pure OLTP  October 28, 2003
Reviewer:  Piotr
      Jarmuz  from Poznan, Poland
It's the polymorphism I need
when I search for objects of class A
they might really come as B or C if B and C are derived classes of A. So I
implement such a search in two stages:

1. Bulk collecting classids and object ids into global temporary table.

2. For each distinct class selecting objects of only this class with a
corresponding view that "understands" this class.

This complexity is hidden in stored procedures and it works like dandy... but
for a large results set.

So for example a search might come with:

2300 objects of class A
3500 objects of class B
14320 objects of class C

Only three distinct values for classid so I am wondering if creating bitmap
index would help here...

Thank you very much.

          Followup:

nope, won't be of much use for selecting OUT all instances.

bitmaps are good for various aggregation types of queries mostly ('how many') or
ANDing ORing lots of predicates together.

to simply retrieve all objects of class C -- full scan.

      Rating: 5
You are the
      best  October 28, 2003
Reviewer:  Piotr
      Jarmuz  from Poznan, Poland
I have just finished my tests...

You were right...

It's too much ado about special case...
Anyway... I have some heuritsics that make it little better,
If I know that a class is a leaf class I do straight select...
If I know with bull collect that there are less than BULK_COLLECT_LIMIT say ~
1000 I do it in nested PL/SQL table and if more I do it in global temporary
table...

But you were right...

Thank you

Regards,

Piotr

October 28, 2003 -- Thanks for the question regarding "Proxy
      authentication ", version 9i release 2

      You Asked
I tried to use proxy authentication. I have two users: APP_SERVER and APP_USER,
and I used GRANT APP_USER CONNECT THROUGH APP_SERVER.

After connecting to Oracle using proxy authentication I can find in V$SESSIONS
two new sessions - one for APP_SERVER user and one for APP_USER.

Why two new sessions? Does it mean that using proxy authentication is consuming
more resources on server (more sessions, more memory...)?

Thanks,

R. Kazimir

      and we said...
that is the way it works.  A single oracle CONNECTION (physical circut) can
support many many sessions.

you can have a 1 to 1 relationship whereby each session has a process (most of
us "assume" this)

you can have a 1 to many relationship whereby each process supports many
sessions (shared server, aka multi-threaded server)

you can have a 1 to many relationship whereby each process supports many
sessions in "dedicated server mode" -- your client can start many sessions
simultaneously.  Autotrace in sqlplus does this for example so it can get v$
stats for your "session" without affecting the v$ stats for your "session".
Forms does this for debugging.  Ntier proxy authentication does this as well.

so yes, you have two sessions, but only one process/thread.  The first session
must exist (we MUST authenticate that user else all is for "naught").  It is not
consuming any significant amount of resources on the server, it is very
lightweight -- you never do anything in that other session.
Review &
      Followup

      Rating:
      5
  October 26, 2003
Reviewer:  hrishy
       from PA
Hi Tom

I have a application where the middle tier user connects to the database
(muser).
muser also provides a connection pool to the database.The muser and password is
stored
in a plain text file.A COM component reads this file and makes a connection to
the database.

I have a requirement wherein i should allow folks to connect to my application
through the middletier only.If they make a connection through ODBC sqlplus TOAD
etc they
should be given only read acesses to the data.I have read through proxy
authentication.
But the examples are for java and OCI is there a example or a pointer for
COM+,or ASP ?

Do you have any other ideas through application roles or something to do this ?
I am using Oracle9iR2.

          Followup:

well, if everyone already uses a single super user "muser", all you need to do
is setup another user "ruser" -- for read only user and broadcast that password?

muser is only available to the middle tier already right?  (you could store the
password a little better, but only the middle tier can use muser in theory)

So, just create a read only account and let people use that -- grant it the
proper least set of privs and you are good to go

      Rating:
      5
  October 28, 2003
Reviewer:  hrishy
       from PA
Hi Tom

Yes thats exactly what we are intending to do.create another user ruser with
read only privlegies.However we need to prevent muser from logging in from sql
plus toad etc.Since the users can easily see the password from the com.ini file
stored in plain text.Just wundering is there any other way of cutting of users
who use muser to log in to sqlplus and manage to get into the database

regards
Hrishy

          Followup:

ummm, if you leave the keys in a fake rock outside your house AND the fake rock
is obviously FAKE -- will people not rob you?

the problem is your architecture - that of storing plain text passwords in a
file on the client.

perhaps MS approaches to "security" are not "good"?

rethink your entire approach.  else, just let anyone do whatever they want.  for
you have left that as a definite choice for anyone as it is.

October 28, 2003 Miles -- Thanks for the question regarding "On
      the use of DBMS_ROWID.ROWID_ROW_NUMBER function", version 8.1.7

      You Asked
Hi Tom,
Great book!, buying your book is one of the best investment I ever made.
I want your opinion of this, we have a large batch process that takes hours to
execute and which fetches (full table scan) on a joined of two huge table.  We
only have a small window to run this large batch process and so we are thinking
of breaking this batch process into several batch process and executing each in
parallel by using DBMS_ROWID.ROWID_ROW_NUMBER during data retrieval as follows:

SELECT a.*, b.*
  FROM large_table1 a,
       large_table2 b
 Where a.key = b.key
   And Mod(Dbms_Rowid.Rowid_Row_Number(a.rowid, Max_Process) = threadNo_in;
Where:
Max_Process is the number of batch process to be executed in parallel
(e.g. 4  -- start for 4 batch job)
threadNo_in is the thread / batch process for this batch job, say 0 - indicating
that this session's batch job is 0.

Using the Mod function on the Dbms_Rowid.rowid_row_number we intend to group the
records of the large_table1 into say thread 0, 1, 2, 3, and start a separate
session for each thread, effectively executing each process in parallel.

My question is, is this a safe implementation of the DBMS_ROWID.ROWID_ROW_NUMBER
function?  Can you see any potential problem with this?  Maybe you can suggest
something else.

      and we said...
I would not do that, you want to break the data up by EXTENTS or FILES.  Having
them each do "every other row" would have the entire table "hot" and you
wouldn't be able to use the rowids to actually access the data.

you would generate N rowid pairs that "covered the table"

and then query:

SELECT a.*, b.*
  FROM ( select * from large_table1 where rowid between :x and :y ) a,
       large_table2 b
 Where a.key = b.key

so you would use rowid ranges.  Here is an example of how to generate by file --
you can use extents, whatever suits you best:

ops$tkyte@ORA920> create tablespace test
  2  datafile size 1088k
  3  autoextend off
  4  /

Tablespace created.

ops$tkyte@ORA920> alter tablespace test add datafile size 1088k autoextend off;

Tablespace altered.

ops$tkyte@ORA920> alter tablespace test add datafile size 1088k autoextend off;

Tablespace altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t ( x int, y char(255) ) tablespace test;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2          for i in 1 .. 100000000
  3          loop
  4                  insert into t values ( i, i );
  5                  commit;
  6          end loop;
  7  end;
  8  /
begin
*
ERROR at line 1:
ORA-01653: unable to extend table OPS$TKYTE.T by 128 in tablespace TEST
ORA-06512: at line 4

ops$tkyte@ORA920> select dbms_rowid.rowid_relative_fno(rowid), count(*)
  2    from t
  3   group by dbms_rowid.rowid_relative_fno(rowid)
  4  /

DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID)   COUNT(*)
------------------------------------ ----------
                                  15       3456
                                  16       3429
                                  17       3456

Ok, so you want to process the rows in there file by file -- in separate
threads/processes.  You would feed them rowid ranges:

ops$tkyte@ORA920> select 'select count(*) from t where rowid between
chartorowid(''' ||
  2         dbms_rowid.rowid_create( 1, user_objects.data_object_id,
  3                                  dba_data_files.RELATIVE_FNO, 1, 0 )
  4         || ''') and chartorowid(''' ||
  5         dbms_rowid.rowid_create( 1, user_objects.data_object_id,
  6                   dba_data_files.RELATIVE_FNO, dba_data_files.blocks+1, 1 )
  7                                  || ''');'
  8  from user_objects, dba_data_files
  9  where user_objects.object_name = 'T'
 10  and dba_data_files.tablespace_name = 'TEST'
 11  /

'SELECTCOUNT(*)FROMTWHEREROWIDBETWEENCHARTOROWID('''||DBMS_ROWID.ROWID_CREATE(1,U

SER_OBJECTS.DATA_OBJECT_ID,DBA_DAT
---------------------------------------------------------------------------------

----------------------------------
select count(*) from t where rowid between chartorowid('AAAIWLAAPAAAAABAAB') and

chartorowid('AAAIWLAAPAAAACJAAB');
select count(*) from t where rowid between chartorowid('AAAIWLAAQAAAAABAAB') and

chartorowid('AAAIWLAAQAAAACJAAB');
select count(*) from t where rowid between chartorowid('AAAIWLAARAAAAABAAB') and

chartorowid('AAAIWLAARAAAACJAAB');

then they would only scan their relevant files, never hitting anyone elses.  of
course you can take this down to the extent level if you desire, just set up any

covering -- yet non-overlapping -- rowid range.
Review &
      Followup

      Rating: 4
On the use of
      DBMS_ROWID.ROWID_ROW_NUMBER function  February 03, 2003

Reviewer:  Miles  from CA, USA
Thanks for tip, Tom.
Still in connection to this topic, but what if there's only one relative file
number for the table?  Does this mean I need to break the data by EXTENTs Level?
 Can you please give me an example of breaking data by extent level?

          Followup:

Ok, it was fun to write - so here goes:

this is for a table in a SINGLE file, breaks it into 4 "more or less" equi
ranges:

select relative_fno, min_block start_block, max_block+blocks-1 end_block,
       sum_blocks,
       dbms_rowid.rowid_create( 1, data_object_id, relative_fno, min_block, 0 )
       min_rid,
       dbms_rowid.rowid_create( 1, data_object_id, relative_fno,
                                   max_block+blocks-1, 10000 ) max_rid
  from (
select relative_fno,
       block_id,
       min(block_id) over (partition by grp) min_block,
       max(block_id) over (partition by grp) max_block,
       blocks,
       sum(blocks) over (partition by grp) sum_blocks
  from (
select relative_fno,
       block_id,
       blocks,
       trunc( (sum(blocks) over (order by block_id)-0.01) /
              (sum(blocks) over ()/4) ) grp
  from dba_extents
 where segment_name = 'BIG_TABLE'
   and owner = user order by block_id
       )
       ), (select data_object_id
             from user_objects
            where object_name = 'BIG_TABLE' )
 where block_id = max_block
/

So, how does it work?  we start from the middle:

big_table@ORA920> select relative_fno,
  2         block_id,
  3             blocks,
  4             sum(blocks) over (order by block_id) cum_blocks,
  5             sum(blocks) over () tot_blocks,
  6             trunc( (sum(blocks) over (order by block_id)-0.01) /
  7                    (sum(blocks) over ()/4) ) grp
  8    from dba_extents
  9   where segment_name = 'BIG_TABLE'
 10     and owner = user order by block_id
 11  /

RELATIVE_FNO   BLOCK_ID     BLOCKS CUM_BLOCKS TOT_BLOCKS        GRP
------------ ---------- ---------- ---------- ---------- ----------
          11          9          8          8      13312          0
          11         17          8         16      13312          0
          11         25          8         24      13312          0
          11         33          8         32      13312          0
....
          11      10249       1024      11264      13312          3
          11      11273       1024      12288      13312          3
          11      12297       1024      13312      13312          3

84 rows selected.

We kept a running total of the blocks (cum_blocks) and had the tot number of
blocks in the table (tot_blocks).  Now, If I take the tot_blocks and divide by 4
-- i get the number of blocks I would like optimally in each group.  We take
that number and divide it into the running total number of blocks and assign a
group id GRP of 0,1,2,3 to each group.  Next we:

big_table@ORA920> select relative_fno,
  2         block_id,
  3         min(block_id) over (partition by grp) min_block,
  4         max(block_id) over (partition by grp) max_block,
  5             blocks,
  6             sum(blocks) over (partition by grp) sum_blocks
  7    from (
  8  select relative_fno,
  9         block_id,
 10             blocks,
 11             trunc( (sum(blocks) over (order by block_id)-0.01) /
 12                    (sum(blocks) over ()/4) ) grp
 13    from dba_extents
 14   where segment_name = 'BIG_TABLE'
 15     and owner = user order by block_id
 16         )
 17  /

RELATIVE_FNO   BLOCK_ID  MIN_BLOCK  MAX_BLOCK     BLOCKS SUM_BLOCKS
------------ ---------- ---------- ---------- ---------- ----------
          11          9          9       3209          8       3328
          11         17          9       3209          8       3328
          11         25          9       3209          8       3328
          11         33          9       3209          8       3328
....
          11       8073       6665       8201        128       2560
          11       8201       6665       8201       1024       2560
          11       9225       9225      12297       1024       4096
          11      10249       9225      12297       1024       4096
          11      11273       9225      12297       1024       4096
          11      12297       9225      12297       1024       4096

84 rows selected.

Basically find the minimum block and maximum block per group.  Here I compute
sum_blocks as well -- just to see how well the data was distributed in the
groups.  It'll probably never be perfect (lmts with uniform extents and an
evenly divisible number of extents would do that)...

Lastly we:

big_table@ORA920> select relative_fno, min_block start_block, max_block+blocks-1
end_block, sum_blocks,
  2         dbms_rowid.rowid_create( 1, data_object_id, relative_fno, min_block,
0 ) min_rid,
  3         dbms_rowid.rowid_create( 1, data_object_id, relative_fno,
max_block+blocks-1, 10000 ) max_rid
  4    from (
  5  select relative_fno,
  6         block_id,
  7         min(block_id) over (partition by grp) min_block,
  8         max(block_id) over (partition by grp) max_block,
  9             blocks,
 10             sum(blocks) over (partition by grp) sum_blocks
 11    from (
 12  select relative_fno,
 13         block_id,
 14             blocks,
 15             trunc( (sum(blocks) over (order by block_id)-0.01) /
 16                    (sum(blocks) over ()/4) ) grp
 17    from dba_extents
 18   where segment_name = 'BIG_TABLE'
 19     and owner = user order by block_id
 20         )
 21             ), (select data_object_id
 22                   from user_objects
 23                          where object_name = 'BIG_TABLE' )
 24   where block_id = max_block
 25  /

RELATIVE_FNO START_BLOCK  END_BLOCK SUM_BLOCKS MIN_RID            MAX_RID
------------ ----------- ---------- ---------- ------------------
------------------
          11           9       3336       3328 AAAHerAALAAAAAJAAA
AAAHerAALAAAA0ICcQ
          11        3337       6664       3328 AAAHerAALAAAA0JAAA
AAAHerAALAAABoICcQ
          11        6665       9224       2560 AAAHerAALAAABoJAAA
AAAHerAALAAACQICcQ
          11        9225      13320       4096 AAAHerAALAAACQJAAA
AAAHerAALAAADQICcQ

that is -- we keep only the row where the block_id = MAX_BLOCK id, that keeps
one row PER GROUP.  From this - we now have the STARTING block, the ENDING BLOCK
and are ready to generate the rowids for each range.

There you go ;)

      Rating: 5
Is this how PQ
      does it  February 03, 2003
Reviewer:  Rob
       from Pittsburgh, PA
Tom:

Is your approach the same approach taken by Parallel Query when it parallelizes
a scan of a non-partitioned table.

          Followup:

similar but they take a slightly more sophisticated approach.  But it is
fundementally based on rowid ranges.

      Rating:
      5
Reader  February 03, 2003
Reviewer:
      A reader
RELATIVE_FNO   BLOCK_ID  MIN_BLOCK  MAX_BLOCK     BLOCKS SUM_BLOCKS
------------ ---------- ---------- ---------- ---------- ----------
          11          9          9       3209          8       3328

For the same file 11, min_block is 9 and max_block is 3209
The total blocks = 3209 - 9 + 1 = 3201. Could you explain
how do you get total blocks = 3328

Thanks

          Followup:

You did not have the entire report.  The last extent has a starting block of
3209, but an ENDING block of 3209+128-1 or 3336.  Now, from that we take away 8
blocks (since min_block is 9) and we get 3328...

Here is the bigger picture with GRP included:

RELATIVE_FNO   BLOCK_ID  MIN_BLOCK  MAX_BLOCK     BLOCKS SUM_BLOCKS        GRP
------------ ---------- ---------- ---------- ---------- ---------- ----------
          11          9          9       3209          8       3328          0
          11         17          9       3209          8       3328          0
          11         25          9       3209          8       3328          0
          11         33          9       3209          8       3328          0
          11         41          9       3209          8       3328          0
....

          11       3081          9       3209        128       3328          0
          11       3209          9       3209        128       3328          0
          11       3337       3337       6537        128       3328          1
          11       3465       3337       6537        128       3328          1

      Rating:
      5
  February 08, 2003
Reviewer:  Miles
       from Hayward, CA
Excellent!  This is just what we wanted to do.  Thanks! Tom.

      Rating: 4
Great Start
      but...  September 03, 2003
Reviewer:  Skip
       from Conway, AR USA
I am really interested in this article because I need to do essentially the same
thing (split a table for multiple processes).  However, I have a couple of
questions.  (ENV 9.2.0.3EE db)

1) Why do counts of rowid ranges force you into single thread?
2) How can I split a table into 4 equal parts?  When I use your technique above
I got 25 parts, my system can only handle 4 processes running at once.

Thanks

          Followup:

1) what do you mean "force you into a single thread???"

2) see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:10498431232211
it handles rowids across files and can give you "4"

      Rating: 5
Forced single
      thread  September 04, 2003
Reviewer:  Skip
       from Conway, AR USA
What I mean by forced single thread is that no matter what parallel degree the
table is or if I use a parallel hint the query is always run with one process
showing in v$session.

          Followup:

big_table@ORA920> @split BIG_TABLE 4

       GRP MIN_RID            MAX_RID
---------- ------------------ ------------------
         0 AAAJirAALAAAAAJAAA AAAJirAALAAADAICcQ
         1 AAAJirAALAAADAJAAA AAAJirAALAAAGQICcQ
         2 AAAJirAALAAAGQJAAA AAAJirAALAAAJQICcQ
         3 AAAJirAALAAAJQJAAA AAAJirAALAAAMgICcQ

Elapsed: 00:00:01.88
big_table@ORA920> select /*+ full(big_table) parallel(big_table,8) */
  2  count(*)
  3  from big_table
  4  where rowid between 'AAAJirAALAAAAAJAAA' and 'AAAJirAALAAADAICcQ';

  COUNT(*)
----------
    856998

Elapsed: 00:00:18.11
big_table@ORA920> set autotrace traceonly explain
big_table@ORA920> /
Elapsed: 00:00:00.01

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=608 Card=1 Bytes=7)
   1    0   SORT (AGGREGATE)
   2    1     SORT* (AGGREGATE)
                              :Q23000
   3    2       TABLE ACCESS* (FULL) OF 'BIG_TABLE' (Cost=608 Card=8750
Bytes=61250)                           :Q23000

   2 PARALLEL_TO_SERIAL            SELECT /*+ PIV_SSF */ SYS_OP_MSR(COUNT(*)) F
                                   ROM (SELECT /*+ NO_EXPAND ROWID(A2) */ A2.RO
                                   WID C0 FROM "BIG_TABLE" PX_GRANULE(0, BLOCK_
                                   RANGE, DYNAMIC)  A2 WHERE A2.ROWID>='AAAJirA
                                   ALAAAAAJAAA' AND A2.ROWID<='AAAJirAALAAADAIC
                                   cQ') A1

   3 PARALLEL_COMBINED_WITH_PARENT

that ran in parallel. not all access paths are parallelizable.

      Rating:
      5
  October 27, 2003
Reviewer:  Robert
       from Memphis, USA

      Rating: 5
Most efficent
      method   October 27, 2003
Reviewer:  Robert
       from Memphis, USA
Tom,

I ran the following query on a rowid range for a table which contains approx.
1/10 of the table.
The 'date_inserted' field in the second query is not indexed.

1. Can you explain the behavior why Oracle chose to scan the Primary Key index
on the 'count(*)' in the first query instead of doing table access by rowid
range like the 'count(date_inserted)' did?
(as you can see, the 'count(*)' query read approximatly 5 times as many blocks
as the 'count(date_inserted)' did)

2. How can we use this rowid range to do parallel full table scans by rowid
range so we can take advantage of the multi block read count of full table
scans, while running multiple queries on the same table?

16:19:26 SQL> set autotrace on
16:19:33 SQL> select count(*) from accounts
16:19:37   2   where rowid between chartorowid('AAAC5rACwAABn0LAAA')
16:19:37   3                   and chartorowid('AAAC5rACwAABq8KEJA')
16:19:38   4
16:19:38 SQL> /

 COUNT(*)
---------
   361519

 real: 78342

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5056 Card=1 Bytes=7)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FAST FULL SCAN) OF 'PK_ACCOUNTS' (UNIQUE) (Cost=5
          056 Card=34449 Bytes=241143)

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
      69124  consistent gets
      66643  physical reads
          0  redo size
        172  bytes sent via SQL*Net to client
        293  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

16:20:57 SQL>
16:39:54 SQL>
16:39:56 SQL> edit
Wrote file afiedt.buf

  1  select count(date_inserted) from accounts
  2   where rowid between chartorowid('AAAC5rACwAABn0LAAA')
  3*                  and chartorowid('AAAC5rACwAABq8KEJA')
16:40:05 SQL> /

COUNT(DATE_INSERTED)
--------------------
              361519

 real: 21050

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=25217 Card=1 Bytes=1
          4)

   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (BY ROWID RANGE) OF 'ACCOUNTS' (Cost=25217
          Card=34449 Bytes=482286)

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

16:40:28 SQL>

Thanks,

Robert.

          Followup:

how up to date are the stats on that index?

are they accurate?

      Rating: 5
More
      info  October 27, 2003
Reviewer:  Robert
       from Memphis, USA
Tom,

The table was analyzed one day ago, with 'analyze table...estimate
statistics'... it would not have changed significantly since then.

Thanks,

Robert.

          Followup:

ok, are the INDEX stats accurate -- was the INDEX analyzed a day ago as well?

see how big that index is -- the index is 7 times the size of 1/10 of the
table???

the index is about 70k blocks
1/10th of the table is 10k blocks

meaning the index is 70% the size of the table in real life?

that is leading me to think the optimizer is thinking the index is smaller then
it really is.

      Rating: 5
'analyze
      table...'  October 28, 2003
Reviewer:
      Robert  from Memphis, USA
Tom,

Doesn't 'analyze table...estimate statistics' automatically analyze the index
stats as well?

Thanks,

Robert.

          Followup:

it'll get the table, the indexes and histograms:

ops$tkyte@ORA920PC> create table t as select object_id, object_name from
all_objects;

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create index t_idx on t(object_id);

Index created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> analyze table t
  2  estimate statistics
  3  /

Table analyzed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select table_name, num_rows from user_tables where
table_name = 'T';

TABLE_NAME                       NUM_ROWS
------------------------------ ----------
T                                   32778

ops$tkyte@ORA920PC> select index_name, num_rows from user_indexes where
table_name = 'T';

INDEX_NAME                       NUM_ROWS
------------------------------ ----------
T_IDX                               30207

ops$tkyte@ORA920PC> select count(*) from user_tab_histograms where table_name =
'T';

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

ops$tkyte@ORA920PC>

      Rating: 5
...but back to
      the original question, please...  October 28, 2003

Reviewer:  Robert  from Memphis, USA
Tom,

Hence the fact that the 'analyze table...estimate statistics' also calculates
index stats, answers your concern that the index stats were out of date.

Assuming I am giving you accurate information (which I believe I am). And
according to dba_tables and dba_indexes which shows this table and indexes to
have been analyzed 2 days ago...

1. Why are the queries above generating those explain plans?
...and (perhaps more importantly)...
2. How can we use rowid ranges to run multiple programs simultaniously (i.e.
parallel), each with a different rowid range...all accessing the table as a full
table scan, thereby getting the speed of the multi block read count in parallel.

Thanks,

Robert.

          Followup:

1) the optimizer thinks the index is much smaller then it is.  Can you peek at
the stats and see if they are even anywhere CLOSE to reality.

2) this would be an appropriate time to consider using HINTS as you have a
highly specialized piece of code here.  You can hint this query.

      Rating: 5
More
      info......  October 28, 2003
Reviewer:
      Robert  from Memphis, USA
Tom,

The stats appear to be accurate...

select count(*) from accounts;

 COUNT(*)
---------
 13811676

select num_rows
  from dba_tables
 where table_name = 'ACCOUNTS';

 NUM_ROWS
---------
 13779291

select distinct_keys
  from dba_indexes
 where table_name = 'ACCOUNTS'
   and index_name = 'PK_ACCOUNTS';

DISTINCT_KEYS
-------------
     13230309

Can you please give an example of using hints in order to achieve a full table
scan on several jobs running in parallel, each running a different rowid range.
Each job would process a different rowid range on a big table and each job would
do a full table scan in order to use multi block read count.
I have your new book "Effective Oracle by Design" and in Chapter 8 (I believe)
you mention doing this.. but I can't find the example(s).

Thanks,

Robert

          Followup:

what are the index stats on this index? are they ACCURATE.

does it make sense to you that 70k blocks are in the index but only 100k blocks
are in the table???

 LEAF_BLOCKS
 NUMBER
 DISTINCT_KEYS
 NUMBER
 AVG_LEAF_BLOCKS_PER_KEY
 NUMBER
 AVG_DATA_BLOCKS_PER_KEY
 NUMBER
 CLUSTERING_FACTOR
 NUMBER
 STATUS
 VARCHAR2(8)
 NUM_ROWS
 NUMBER
 SAMPLE_SIZE
 NUMBER
 LAST_ANALYZED
 DATE

October 28, 2003 Bo -- Thanks for the question regarding "Regular
      Delete vs. Bulk Delete", version 8.1.6

      You Asked
Hi. Tom.
Thank you for taking this question.
In a recent Big Volumn data processing, I use the bulk delete.  However, I found
the speed is very slow.  To make a compare, I use regular delete to treat
similar amount of data on same table, the speed is about same.

For this particular case, we have around 300,000 IDs to be deleted.

The REGULAR DELETE looks like:
DELETE FROM t
WHERE  id IN (select id from a)

The BULK DELETE is:
DECLEAR
TYPE num_nt   IS TABLE OF VARCHAR2(20);
TYPE descr_nt IS TABLE OF VARCHAR2(20);

   nt_x_nums      num_nt := num_nt();
   nt_a_descrs    descr_nt := descr_nt();

   v_cnt_sqlrow    NUMBER := 0;
SELECT id BULK COLLECT
     INTO  nt_x_nums
     FROM  a;

   v_cnt_sqlrow :=SQL%ROWCOUNT;
   -- DBMS_OUTPUT.PUT_LINE(v_cnt_sqlrow);

   FORALL i IN nt_x_nums.FIRST..nt_x_nums.LAST
       DELETE FROM t
       WHERE  id= nt_x_nums(i)

       RETURNING id BULK COLLECT INTO nt_a_descrs

Could you please provide a rule to determine when should use bulk delete?

Thanks,

      and we said...
You would always want to use the "regular" delete.

Less code, runs in less (generally) time.

the rule to decide when to use a bulk delete: "use it when you cannot do the
delete in a single SQL statement".

Here, you probably had a table with more some number of indexes and deleting
just does alot of work.  Maintaining the index structures for 300,000 deletes
can be "cumbersome".  Additionally there is lots of UNDO and REDO generated.

If 300,000 was a large percentage of the table, it is sometimes better to:

create table temp nologging as select * from t where id not in ( select id from
a );

(keep the rows you want)

index temp (unrecoverable, in parrallel )
grant on temp (as you had for t)
drop table t;
rename temp to t;

Review &
      Followup

      Rating: 4
bulk delete
      several tables  November 14, 2002
Reviewer:
      A reader
Hi

I have to delete 6 tables where a column is compared to a column of a 15
millions rows table

I was wondering if I could do this in bulk deletes such as

declare
type x is table of number;
x_array x;
begin
select cod_id bulk collect
into x_array
from x_table;

....................................

then now I dont know how to proceed because basically the delete is something
like

delete t1 where cod_id = (select cod_id from x_table);
delete t2 where cod_id = (select cod_id from x_table);
delete t3 where cod_id = (select cod_id from x_table);
delete t4 where cod_id = (select cod_id from x_table);
delete t5 where cod_id = (select cod_id from x_table);
delete t6 where cod_id = (select cod_id from x_table);

but obviously to do this I have to query 6 times the 15 millions rows table, I
was wondering if I can query once then use that result to all 6 tables in
PL/SQL?

thank you

          Followup:

delete t1 where cod_id in ( select cod_id from x_table );
...
delete t6 where cod_id in ( select cod_id from x_table );

Matters not if x_table has 15 BILLION rows -- it is the size of t1, ... t6 that
matters.  Consider:

big_table@ORA920.LOCALHOST> create table t ( x int );
big_table@ORA920.LOCALHOST> insert into t select rownum from all_users;
big_table@ORA920.LOCALHOST>
big_table@ORA920.LOCALHOST> analyze table t compute statistics;
big_table@ORA920.LOCALHOST>
big_table@ORA920.LOCALHOST> select count(*) from big_table;

  COUNT(*)
----------
   1000000
big_table@ORA920.LOCALHOST>
big_table@ORA920.LOCALHOST> set autotrace on
big_table@ORA920.LOCALHOST>
big_table@ORA920.LOCALHOST> delete from t where x in ( select id from big_table
);

Execution Plan
----------------------------------------------------------
   0      DELETE STATEMENT Optimizer=CHOOSE (Cost=38 Card=36 Bytes=216)
   1    0   DELETE OF 'T'
   2    1     NESTED LOOPS (Cost=38 Card=36 Bytes=216)
   3    2       TABLE ACCESS (FULL) OF 'T' (Cost=2 Card=36 Bytes=72)
   4    2       INDEX (UNIQUE SCAN) OF 'BIG_TABLE_PK' (UNIQUE) (Cost=1 Card=1
Bytes=4)

Statistics
----------------------------------------------------------
         64  recursive calls
         38  db block gets
         92  consistent gets
         13  physical reads
       8516  redo size
        788  bytes sent via SQL*Net to client
        822  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          5  sorts (memory)
          0  sorts (disk)
         36  rows processed

big_table@ORA920.LOCALHOST>
big_table@ORA920.LOCALHOST> set autotrace off

that was really fast -- big_table is really big but it didn't really *matter*

      Rating: 3
t1 to t6 are
      even bigger :p  November 15, 2002
Reviewer:
      A reader
Hi

Table t1 to t6 have around 50 million rows each!

I dont understand why the size depends on t1 to t6, I think in the example you
provided it´s using index scan because we are comparing values from t1´s column
to big_table´s PK no?
I thought sub-queries always have to be resolved first that´s why i thought it
would query 6 times the whole table if I run 6 deletes

I have this PL/SQL anonymous block

DECLARE
  TYPE X_EMPNO        IS TABLE OF X.EMPNO%TYPE;
  X_EMPNO_ARRAY       X_EMPNO;
BEGIN
  SELECT empno BULK COLLECT
    INTO X_EMPNO_ARRAY
    FROM emp;
  FORALL i IN X_EMPNO_ARRAY.FIRST..X_EMPNO_ARRAY.LAST
    DELETE FROM x
     WHERE empno = X_EMPNO_ARRAY(i);
    DELETE FROM y
     WHERE empno = X_EMPNO_ARRAY(i);.... 6 delets for 6 tables
END;
/

will these deletes work like

delete from x wher empno in (select empno from emp)?

I queried v$sql and I see delete from x where empno = :B1 for this PL/SQL Block

          Followup:

No, don't do it that way -- just stop.  (try my way ok)

If big table has an index (pk or otherwise), it is the size of t1..t6.

DON'T TRY TO OUT GUESS the database here, it actually knows what to do.

I'd just love to see you array fetch 50million records -- you wanna talk *dog
slow*.  That is exactly what you would be doing.

do what I told you to -- use explain plan, check it out.

      Rating: 4
forgot to
      say  November 15, 2002
Reviewer:
      A reader
I alsof forgot to say that the BIG_TABLE is not indexed, it´s a temporary table

      Rating: 5
I thought about
      that array fetch was horrible too!  November 15, 2002

Reviewer:  A reader
I thought about array fetching 50 millions would be horrible too! That's why my
doubts of how to do it. The problem is with RBS space, if I do a straight delete
like this I doubt it very much I have enough RBS, I have a 2GB RBS just for this
kind of jobs but I dont think even 2GB is enough however I left one delete going
today after I left office we will see if it works on Monday (assumming there are
enough RBS space)

big_table has a index on cod_id, the delete I ran was

delete from t1 a
where exists (select null
                from big_table b
               where a.cod_id = b.cod_id)

the explain plan was FTS of t1 and index scan on big_table

I had a previous delete which looks like

delete from t1
where cod_id in (select cod_id from big_table)

the explain table looked horrible, sort-merge going on for both tables of 50
millions rows and 15 millions rows so I opted for EXISTS however I am not quite
sure if my choice was correct.

My last question is about sub-queries, I always thought sub-queries are resolved
first but from your example it doesnt seem so... however if there were a
predicate in your sub-query I guess it will be resolved first right?

          Followup:

what does RBS have to do with 50 million rows in a table you read?? anyway..

it depends (as i keep saying) on the SIZE of t1.  If t1 is large - sort merge =
WAY TO GO.  If t1 is small - exists is ok.

Subqueries are not resolved first, second, third or last.  there are numerous
ways things can go -- many many many ways.

      Rating: 4
delete
      failed  November 18, 2002
Reviewer:
      A reader
Hi

I checked in the office today and the delete failed due to RBS space (as I
expected!). It took 2.5 hours reading 15 milliong physical reads, 70 millions
block gets, 45 millions consistent gets and 65 millions block changes

I am using now

DECLARE
  TYPE X_EMPNO        IS TABLE OF X.EMPNO%TYPE;
  X_EMPNO_ARRAY       X_EMPNO;
  L_ROWNUM            NUMBER := 50000;
BEGIN
  WHILE TRUE
  LOOP
  SELECT empno BULK COLLECT
    INTO X_EMPNO_ARRAY
    FROM emp;
   WHERE ROWNUM <= L_ROWNUM;
  IF SQL%NOTFOUND
  THEN
    EXIT;
  END IF;
  SET TRANSACTION USE ROLLBACK SEGMENT RBIG;
  FORALL i IN X_EMPNO_ARRAY.FIRST..X_EMPNO_ARRAY.LAST
    DELETE FROM x
     WHERE empno = X_EMPNO_ARRAY(i);
  FORALL i IN X_EMPNO_ARRAY.FIRST..X_EMPNO_ARRAY.LAST
    DELETE FROM y
     WHERE empno = X_EMPNO_ARRAY(i);
    COMMIT;
  END LOOP;
END;
/

I will let you know if this goes better!

I would like to do plain delete as well but due to disk space problem I cannot
have more RBS...

Cheers

          Followup:

that won't do anything except go into an INFINITE LOOP.  Have fun with that.

Your question was "how to delete from t1..t6 given a really big X_TABLE".  It
was not "how to efficiently delete a gazillion rows.  You never mentioned how
big t1..t6 are, how many rows you really expect to delete.  sounds like a lot
(that would be why the rbs was used so heavily).

Suggest you read:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6407993912330
and change UPDATE to DELETE -- instead of deleting the records you don't want,
SELECT OUT the records you do -- drop the old table -- rename new table.

No rollback needed at all for that.

      Rating: 4
ops forgot to
      say  November 18, 2002
Reviewer:
      A reader
I forgot to say that you might think my pl/sql is very wrong because of rownum I
am using, I forgot to add a delete at the end which is

DELETE FROM emp
     WHERE empno = X_EMPNO_ARRAY(i);

          Followup:

see above - you don't want to delete, I think you want to CREATE TABLE AS SELECT
ROWS_TO_KEEP and drop.

It would be about <very big number here> times faster. You can create the tables
with nologging and no rollback.  You can recreate the indexes with parallel
nologging.  It'll be super fast as compared to deleting most of the rows from
indexed tables.

      Rating: 5
How to Tune
      Slow Deleting?  December 06, 2002
Reviewer:  A
      Reader  from USA
Hi Tom,
I have I table that has over 2m rows.  When I deleted a row using:
delete from t where id=i;
The id is pk column.  This operation took 40 seconds to come back.  Since I have
a loop to do so to delete many many records (maybe up to 1m), it will never come
back.  Beacuse many tables (8) reference this table and this is a production
database, I cannot do a truncate table, or creating a temp table to do the
delettion.  What can I tune to make the deletion faster, or where is the
possible problem?
Thanks you for you help in advance.

          Followup:

Make sure the CHILD TABLES have indexes on their foreign keys or else you are
forcing a FULL SCAN on the child tables for each and every single row you
delete.  That is what it sounds like to me - you have unindexed foreign keys.

Here is a script that will help you detect unindexed foreign keys in a schema:

select decode( b.table_name, NULL, '****', 'ok' ) Status,
       a.table_name, a.columns, b.columns
from
( select a.table_name, a.constraint_name,
         max(decode(position, 1,     cname,NULL)) ||
         max(decode(position, 2,', '||cname,NULL)) ||
         max(decode(position, 3,', '||cname,NULL)) ||
         max(decode(position, 4,', '||cname,NULL)) ||
         max(decode(position, 5,', '||cname,NULL)) ||
         max(decode(position, 6,', '||cname,NULL)) ||
         max(decode(position, 7,', '||cname,NULL)) ||
         max(decode(position, 8,', '||cname,NULL)) ||
         max(decode(position, 9,', '||cname,NULL)) ||
         max(decode(position,10,', '||cname,NULL)) ||
         max(decode(position,11,', '||cname,NULL)) ||
         max(decode(position,12,', '||cname,NULL)) ||
         max(decode(position,13,', '||cname,NULL)) ||
         max(decode(position,14,', '||cname,NULL)) ||
         max(decode(position,15,', '||cname,NULL)) ||
         max(decode(position,16,', '||cname,NULL)) columns
    from (select substr(column_name,1,30) cname,
                 substr(table_name,1,30) table_name,
                 substr(constraint_name,1,30) constraint_name,
                 position
            from user_cons_columns) a,
         user_constraints b
   where a.constraint_name = b.constraint_name
     and b.constraint_type = 'R'
   group by a.table_name, a.constraint_name ) a,
( select table_name, index_name,
         max(decode(position, 1,     cname,NULL)) ||
         max(decode(position, 2,', '||cname,NULL)) ||
         max(decode(position, 3,', '||cname,NULL)) ||
         max(decode(position, 4,', '||cname,NULL)) ||
         max(decode(position, 5,', '||cname,NULL)) ||
         max(decode(position, 6,', '||cname,NULL)) ||
         max(decode(position, 7,', '||cname,NULL)) ||
         max(decode(position, 8,', '||cname,NULL)) ||
         max(decode(position, 9,', '||cname,NULL)) ||
         max(decode(position,10,', '||cname,NULL)) ||
         max(decode(position,11,', '||cname,NULL)) ||
         max(decode(position,12,', '||cname,NULL)) ||
         max(decode(position,13,', '||cname,NULL)) ||
         max(decode(position,14,', '||cname,NULL)) ||
         max(decode(position,15,', '||cname,NULL)) ||
         max(decode(position,16,', '||cname,NULL)) columns
    from ( select substr( column_name, 1, 30 ) cname,
                  substr( table_name, 1, 30 ) table_name,
                  substr( index_name, 1, 30 ) index_name,
                  column_position position
             from user_ind_columns  ) user_ind_columns
   group by table_name, index_name ) b
where a.table_name = b.table_name (+)
  and b.columns (+) like a.columns || '%'
/

      Rating: 5
Yes, that's
      where the problem is!  December 18, 2002
Reviewer:
      A Reader  from USA
Thank you!

      Rating: 5
cascade
      delete  October 28, 2003
Reviewer:
      A reader
hi tom
if i cascade delete a central parent table with
multiple child tables (of the order of 20) - the
total rows in all tables reach a million, the delete
is naturally slow. We can not afford to drop and
recreate the table. Is it a good idea to alter
table drop the constraints, drop indexes - delete the data and then add back the
constraints. I am afraid that the
data that goes in would not get validated and don't
think it is a good idea. Your advice please? (the delete
can be issued by the user anytime in the UI.) It
does run in the background as a dbms_job - so that
is not the problem - it is the resource intensiveness
of that which is troubling us.

Thanx for an awesome site and two awesome books - have
both of them, read them more or less and love them!

          Followup:

are you saying "it will delete 1,000,000 rows" or the sum of all of the rows is
1,000,000

It will not "naturally" be slow.

it will be a direct function of:

o the number of child rows to cascade the delete to.
o the presence or lack thereof of indexes on the child tables foreign keys.

if you update the parent primary key OR you delete from the parent table --
indexing the childs fkeys is pretty much "a mandatory thing".  have you done
that.

      Rating: 5
thanx
      Tom!  October 28, 2003
Reviewer:
      A reader
Turns out indexes were not there in the test
(I am aware of the fk index thinggy and its impact
on child table)
a side question - does the lack of indexes also result
in more redo? Well, on thinking more about it - with indexes - even more redo
(for index maintenance)
will be generated, right? One of the concerns
(perhaps unnecessary) was that the redo generated was
too high during cascade deletes )

          Followup:

more indexes = more redo = (in this case) better performance over all

given you were doing this without indexes -- the "concern" isn't relevant

      Rating: 5
thanx
      Tom!  October 28, 2003
Reviewer:
      A reader

October 28, 2003 Jessica -- Thanks for the question regarding
      "alter database set time_zone doesn't work", version 9.0.1

      You Asked
Hi Tom,
We have a Oralce 9i database run on Sun Solaris Server, the Unix server timezone
is set to 'EST'(Eastern Standard Time). When we created the database, the db
timezone wasn't explicitly specified. So when I issued the following statement:
"Select dbtimezone from dual"
I got '-05:00' from sqlplus.It shows absolute time zone offsets from GMT. This
is expectable since the db timezone defaults to Unix Server timezone. But what
if I want the above select statement returns time zone regions code(like 'EST')
instead?
By looking Doc ID: Note:149120.1 in Metalink, I issued the following statement:

ALTER DATABASE <my db name> SET TIME_ZONE = 'US/Eastern'
or
ALTER DATABASE <my db name> SET TIME_ZONE = 'EST'

But when I issued "SELECT DBTIMEZONE FROM dual;", It still show the offsets.
I tried another thing: I set nls_time_tz_format='EST' in the init.ora file and
restart the database. After I issued "SELECT DBTIMEZONE FROM dual;" This time,
it shows 'EST'. But still, "alter database..." couldn't change anything, it
stick to 'EST' from then on...

One word, it seems the alter database statement doesn't work. Following is a
quotes from the document from Metalink:

"If the database time zone is set to ’US/Pacific’, then it will return
’US/Pacific’. However, if the database time zone is set to the absolute time
zone offset, such as ’-07:00’, it returns ’-07:00’ since it may not map to a
unique time zone region considering that daylight savings may be in effect."

My question is: besides in the create databse statement,how to set database
timezone?

Thanks for your help?

      and we said...
that note is missing "the alter database only takes effect after a
shutdown/restart".  thats why the init.ora appears to work - the alter database
kicked in at that point.

ops$tkyte@ORA9I.WORLD> select dbtimezone from dual;

DBTIME
------
-04:00

ops$tkyte@ORA9I.WORLD> alter database set time_zone = 'EST';

Database altered.

ops$tkyte@ORA9I.WORLD> select dbtimezone from dual;

DBTIME
------
-04:00

ops$tkyte@ORA9I.WORLD> connect / as sysdba;
Connected.
ops$tkyte@ORA9I.WORLD> shutdown
Database closed.
Database dismounted.
ORACLE instance shut down.
ops$tkyte@ORA9I.WORLD> startup
ORACLE instance started.

Total System Global Area  358909124 bytes
Fixed Size                   279748 bytes
Variable Size             335544320 bytes
Database Buffers           16777216 bytes
Redo Buffers                6307840 bytes
Database mounted.
Database opened.
ops$tkyte@ORA9I.WORLD> select dbtimezone from dual;

DBTI
----
EST

Review &
      Followup

      Rating: 5
Alter database
      not working?  July 29, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bahttian - Pakistan
1>
"thats why the init.ora appears to work "
I don't think that alter database will change in init.ora. Is this a control
file?

2>
I am trying to change the time zone of my db with alter database but it is
giving error?

SQL> select dbtimezone from dual;

DBTIME
------
-07:00

SQL> alter database set time_zone='-05:00';
alter database set time_zone='-05:00'
*
ERROR at line 1:
ORA-02231: missing or invalid option to ALTER DATABASE

while alter session is working fine.
SQL> alter session set time_zone = '-05:00';

Session altered.

SQL>

          Followup:

1) the alter database didn't change the init.ora.

it was a case of false causality.  they said "but we changed the init.ora and
restarted and things worked differently.  must be the init.ora change".  NO, it
was the alter database change that took effect due to the restart.

2)
The database time zone is relevant only for TIMESTAMP WITH LOCAL TIME ZONE
columns. Oracle normalizes all TIMESTAMP WITH LOCAL TIME ZONE data to
the time zone of the database. You can set the database time zone when the
database is created.

It is not possible to change the database timezone when TIMESTAMP WITH LOCAL
TIME ZONE columns are used.  Since TIMESTAMP WITH LOCAL TIME ZONE columns are
stored normalized to the database timezone, that means that after changing the
database timezone the interpretation of the data would be wrong. Therefore, if
there is any TIMESTAMP WITH LOCAL TIME ZONE column in the database and
you try to change the time zone you will receive that error

      Rating: 4
How do I change
      just the time?  October 28, 2003
Reviewer:
      D'Arcy P  from Edmonton Alberta
The time Oracle is using is 9 minutes fast and we want to change it. I cannot
find any reference to changing it on your site or OTN.
In case you need to know, dbms is on a Sun box, client machines are Win2000 pcs.

Sorry for using this thread to ask a new ?, but it's kinda related (!)
Thanks Tom

          Followup:

umm, the time on the SERVER itself is therefore 9 minutes fast since we get the
time from the OS.

use the OS utilities to fix your time.


Subject | Article | Last Updated (East coast time)
HAVING clause of GROUP BY. | article only | Oct 29, 2003 06:33 am
Replicate or not !!! | article only | Oct 29, 2003 06:55 am
compiling pro*c from unix on NT | article only | Oct 29, 2003 06:56 am
Snapshot -- Upload and download | article only | Oct 29, 2003 06:57 am
index,Joins | article only | Oct 29, 2003 07:40 am
Partition tablespacesing y | article only | Oct 29, 2003 07:43 am
dense_rank vs. rank | article only | Oct 29, 2003 11:15 am
Column statistics | article only | Oct 29, 2003 11:45 am
First an last day for any week in any month | article only | Oct 29, 2003 02:22 pm
Edit Long Raw Column | article only | Oct 29, 2003 02:25 pm
Batch processing and OLTP - Design and developement guide | article only | Oct 29, 2003 03:24 pm
How to estimate temp tablespace space needed for a 380 Gigs table | article only | Oct 29, 2003 05:18 pm
Is it possible to encrypt PL/SQL procedure? | article only | Oct 29, 2003 05:21 pm
recordlength vs buffer in exp/imp | article only | Oct 30, 2003 07:02 am
event triggers | article only | Oct 30, 2003 07:06 am
dba_free_space | article only | Oct 30, 2003 07:17 am
star query | article only | Oct 30, 2003 11:12 am
When is it safe to compress archive log files? | article only | Oct 30, 2003 11:28 am
xml generation methods | article only | Oct 30, 2003 12:48 pm
Passing parameters to XSL-stylesheet - how? | article only | Oct 30, 2003 01:00 pm
Oracle9i: Formatting XML Tags | article only | Oct 30, 2003 04:52 pm
Usefulness Of Materialized Views | article only | Oct 30, 2003 09:41 pm
How to capture user id from a table while auditing another | article only | Oct 31, 2003 07:40 am
Backup/Recovery | article only | Oct 31, 2003 07:52 am
Password Security During Logins | article only | Oct 31, 2003 07:57 am
How to send more than 32K message | article only | Oct 31, 2003 08:02 am
sql*plus on unix terminals | article only | Oct 31, 2003 08:05 am
How can I DO PATTERN MATCHING from within PL-SQL using REGULAR Expression | article only | Nov 01, 2003 11:42 am
Writing form output to Excel | article only | Nov 01, 2003 11:52 am
Password Management | article only | Nov 01, 2003 11:53 am
DB JOB time shift | article only | Nov 01, 2003 12:11 pm
utl_file_dir instance parameter and UTL_FILE - usage information | article only | Nov 01, 2003 12:15 pm
Finding NULLs by index | article only | Nov 01, 2003 12:30 pm
startup and shutdown info | article only | Nov 01, 2003 01:09 pm
how to access data to and from foxpro and oracle simultaneously | article only | Nov 01, 2003 01:12 pm
Moving windows around date | article only | Nov 01, 2003 06:27 pm
Advanced Queueing Solution | article only | Nov 02, 2003 12:31 pm
Difference between Oracle8 and Oracle8i | article only | Nov 02, 2003 03:31 pm
ANSI/ISO standards? | article only | Nov 02, 2003 04:19 pm
Creating test environment from prodution box. | article only | Nov 03, 2003 07:27 am
Case and Decode and others | article only | Nov 03, 2003 02:08 pm
I need to use the LTRIM for Time Function | article only | Nov 03, 2003 02:11 pm
Oracle support of ANSI joins. | article only | Nov 03, 2003 02:18 pm
Library cache latch contention | article only | Nov 03, 2003 02:42 pm
Slow processing on database using merge | article only | Nov 03, 2003 05:52 pm
Using A Struct within PL/SQL | article only | Nov 03, 2003 05:53 pm
Full Database export and import : v8.1.6 | article only | Nov 03, 2003 05:56 pm
design the table | article only | Nov 03, 2003 05:57 pm
Library Cache | article only | Nov 03, 2003 06:09 pm
Control the Oracle Job | article only | Nov 03, 2003 06:11 pm
Performance and Oracle Sequences | article only | Nov 04, 2003 08:11 am
TSPITR | article only | Nov 04, 2003 08:24 am
dba responsibilities | article only | Nov 04, 2003 08:29 am
PL/SQL Soundex function | article only | Nov 04, 2003 08:40 am
Temporary tables and indexes | article only | Nov 04, 2003 10:16 am
How Oracle Warehouse Builder compares to Informatica? | article only | Nov 04, 2003 04:06 pm
Views of Views | article only | Nov 04, 2003 04:07 pm
caching prepared callable statements in java | article only | Nov 04, 2003 05:23 pm
row(s) 1 - 58 of 58
October 29, 2003 Kashif -- Thanks for the question regarding "HAVING clause of GROUP BY.", version 8.1.7.3
You Asked
Hi Tom,

Recently I came across a query with the following text:

SELECT bbl1.*, COUNT(*) DupeCount
 FROM branch_book_list bbl1
 WHERE bbl1.branch_name = 'Branch A'
 GROUP BY bbl1.branch_name, bbl1.book_ISBN, bbl1.book_name
 HAVING NOT EXISTS (
       SELECT bbl2.*, COUNT(*)
       FROM branch_book_list bbl2
       WHERE branch_name = 'Branch B'
       GROUP BY bbl2.branch_name, bbl2.book_ISBN, bbl2.book_name
       HAVING bbl1.book_ISBN = bbl2.book_ISBN
          AND bbl1.book_name = bbl2.book_name
          AND COUNT(*) = COUNT(ALL bbl1.book_ISBN))

Specifically, I'm interested in the 'HAVING NOT EXISTS' and 'HAVING
bl1.book_ISBN = bbl2.book_ISBN' parts of the query. How do they work, and how is
it different from putting them in the main WHERE clause of the query? I know the
HAVING clause is applied after the groupings are done, but what possible reason
could one have to put the join statements in the HAVING clause? About the only
time I've used HAVING along with the GROUP BY is when I check the COUNT (*) or
other group by expression value, e.g. HAVING COUNT (*) > 2. Hope my question is
clear. Thanks.

Kashif
and we said...
Having is a where clause that is applied AFTER aggregation.

It normally "wheres" on aggregates, eg:

-- find all jobs with more then three people in it
select job, count(*)
  from emp
 group by job
having count(*) > 3;

there are no "join clauses" in the having -- I see a correlated subquery, but no
joins.

that is an inefficient query to say the least, a bit pedantic.

select book_isbn, book_name,
       sum(decode( branch_name, 'Branch A', 1, 0 )) branch_a_cnt,
       sum(decode( branch_name, 'Branch B', 1, 0 )) branch_b_cnt
  from branch_book_list
 where branch_name in ( 'Branch A', 'Branch B' )
 group by book_isbn, book_name
 having sum(decode( branch_name, 'Branch A', 1, 0 )) !=
                  sum(decode( branch_name, 'Branch B', 1, 0 ))
    and sum(decode( branch_name, 'Branch A', 1, 0 )) != 0
/

does the same thing....
Review & Followup
Rating: 4
Well... June 09, 2003
Reviewer: Kashif from Houston, TX
Hi Tom,

Thanks for the response. Some follow-ups:

1) The correlated part of the subquery, i.e.

'bbl1.book_ISBN = bbl2.book_ISBN
AND bbl1.book_name = bbl2.book_name'

appears after the HAVING keyword, which to me implies that the correlation is
done after the aggregation. Is that not true? If anything, it seems to me that
it would not be very performant, since they aggregate and then apply the
filtering correlated join. Why would they not simply do:

SELECT bbl1.*, COUNT(*) DupeCount
 FROM branch_book_list bbl1
 WHERE bbl1.branch_name = 'Branch A'
 GROUP BY bbl1.branch_name, bbl1.book_ISBN, bbl1.book_name
 HAVING NOT EXISTS (
      SELECT bbl2.*, COUNT(*)
      FROM branch_book_list bbl2
      WHERE branch_name = 'Branch B'
      and bbl1.book_ISBN = bbl2.book_ISBN --Here...
      AND bbl1.book_name = bbl2.book_name  --And here...
      GROUP BY bbl2.branch_name, bbl2.book_ISBN, bbl2.book_name
      HAVING COUNT(*) = COUNT(ALL bbl1.book_ISBN))

..where the correlated joins of the subquery appear BEFORE the HAVING clause,
and make it more intuitive? Are they not the same thing?

2) Your version of the query, though easier to read, in fact does not produce
the same results. Some setup data:

create table branch_book_list
(BRANCH_NAME CHAR(10), BOOK_ISBN CHAR(13), BOOK_NAME CHAR(40))
/
insert into branch_book_list values (
'Branch A', '1-56592-401-0', 'Transact-SQL Programming')
/
insert into branch_book_list values (
'Branch A', '1-56592-578-5', 'Oracle SQL*Plus: The Definitive Guide')
/
insert into branch_book_list values (
'Branch A', '1-56592-756-7', 'Transact-SQL Cookbook')
/
insert into branch_book_list values (
'Branch B', '1-56592-401-0', 'Transact-SQL Programming')
/
insert into branch_book_list values (
'Branch B', '1-56592-756-7', 'Transact-SQL Cookbook')
/
insert into branch_book_list values (
'Branch B', '1-56592-948-9', 'Oracle SQL*Loader: The Definitive Guide')
/

Your query returns no rows, whereas the other query returns one row. Thanks in
advance for any feedback.

Kashif

Followup:
I cannot answer "why they did it that way" as I cannot imagine doing it anywhere
near that way in the first place.

yes, it is not very performant, in fact, it is one of the slower ways to
accomplish this task

use varchar2 and it'll work.  or change the decode to use:

ops$tkyte@ORA920LAP> select book_isbn, book_name,
  2         sum(decode( branch_name, rpad('Branch A',10), 1, 0 )) branch_a_cnt,
  3         sum(decode( branch_name, rpad('Branch B',10), 1, 0 )) branch_b_cnt
  4    from branch_book_list
  5   where branch_name in ( 'Branch A', 'Branch B' )
  6   group by book_isbn, book_name
  7   having sum(decode( branch_name, rpad( 'Branch A', 10 ), 1, 0 )) !=
  8                    sum(decode( branch_name, rpad( 'Branch B', 10 ), 1, 0 ))
  9      and sum(decode( branch_name, rpad( 'Branch A', 10 ), 1, 0 )) != 0
 10  /

BOOK_ISBN     BOOK_NAME                                BRANCH_A_CNT BRANCH_B_CNT
------------- ---------------------------------------- ------------ ------------
1-56592-578-5 Oracle SQL*Plus: The Definitive Guide               1            0

rpad

char is a hateful datatype -- suggest you avoid it like the plague -- just
pretend it does not exist!

Rating: 3
Having clause is redundant June 09, 2003
Reviewer: Mikito Harakiri
select deptno, sum(sal) from emp
group by deptno
having count(1) > 3

is equivalent to:

select * from (
 select deptno, sum(sal) sumsal, count(1) cnt
 from emp group by deptno
) where cnt > 3

It doesn't matter where subquery filter is written: in inner, or outer query. A
smart optimizer should be able to push the predicate down into the inner query.

Followup:
Mikito, welcome back, long time no hear.

lets see, Websters defines redundant as:

Main Entry: re·dun·dant
Pronunciation: -d&nt
Function: adjective
Etymology: Latin redundant-, redundans, present participle of redundare to
overflow -- more at REDOUND
Date: 1594
1 a : exceeding what is necessary or normal : SUPERFLUOUS b : characterized by
or containing an excess; specifically : using more words than necessary c :
characterized by similarity or repetition <a group of particularly redundant
brick buildings> d chiefly British : no longer needed for a job and hence laid
off
2 : PROFUSE, LAVISH
3 : serving as a duplicate for preventing failure of an entire system (as a
spacecraft) upon failure of a single component

Now, while the two queries above are "equivalent", the only one that contains
redundant syntax would be -- well -- yours :)

You have a redundant SELECT
Yours uses more words than necessary

You are correct about the optimizer -- but I would use

having count(*) > 3

(what is UP with count(1) -- why why do people do that.  It is so wrong.  You
want a count of records -- that is by design what count(*) is.  count(1) is not
any different then count('foobar') or count(55).  It takes away meaning.)

Rating: 4
Thanks. June 09, 2003
Reviewer: Kashif from Houston, TX
Thanks Tom, it makes more sense now. Though something just cropped up in my
mind: when an AND condition is written after the HAVING clause, does it imply
that it will be applied after the GROUP BY clause as part of the HAVING clause?
Or is the AND condition evaluated before any aggregation takes place?

Also, I think what Mikito was trying to demonstrate was the following, with
respect to the above query, and perhaps trying to eliminate the extra lines of
code:

select book_isbn, book_name, branch_a_cnt, branch_b_cnt
from
(
 select book_isbn, book_name,
         sum(decode( branch_name, rpad('Branch A',10), 1, 0 )) branch_a_cnt,
         sum(decode( branch_name, rpad('Branch B',10), 1, 0 )) branch_b_cnt
    from branch_book_list
   where branch_name in ( 'Branch A', 'Branch B' )
   group by book_isbn, book_name
)
where branch_a_cnt != branch_b_cnt
and branch_a_cnt != 0

Kashif

Followup:
The entire having clause is processed AFTER the aggregation.

What extra lines of code? we have the same amount of "code".  The optimizer is
smart enough to know that:

select book_isbn, book_name,
       sum(decode( branch_name, 'Branch A', 1, 0 )) branch_a_cnt,
       sum(decode( branch_name, 'Branch B', 1, 0 )) branch_b_cnt
  from branch_book_list
 where branch_name in ( 'Branch A', 'Branch B' )
 group by book_isbn, book_name
 having sum(decode( branch_name, 'Branch A', 1, 0 )) !=
                  sum(decode( branch_name, 'Branch B', 1, 0 ))
    and sum(decode( branch_name, 'Branch A', 1, 0 )) != 0
/

is using the same sum's

Rating: 3
Count(*) vs Count(1) June 10, 2003
Reviewer: John Kho from Singapore
You said:
<
(what is UP with count(1) -- why why do people do that.  It is so wrong.  You
want a count of records -- that is by design what count(*) is.  count(1) is not
any different then count('foobar') or count(55).  It takes away meaning.)
>

Well, I read a book called PL/SQL Reference published by O Reilly (can't
remember the author) and a tip for faster queries was to use count(1) rather
than count(*). But that was for Oracle 7 and 8i. Not too sure if it is still
applicable for 9i.

Followup:
it was *never true*

did they have a benchmark?

7.3.4:

SELECT COUNT(*)   FROM SYS.SOURCE$

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute     10      0.00       0.00          0          0          0          10
Fetch       10      0.55       2.50       2441       3210         30          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       21      0.55       2.50       2441       3210         30          20

********************************************************************************
SELECT COUNT(1)   FROM SYS.SOURCE$

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute     10      0.00       0.00          0          0          0          10
Fetch       10      1.00       2.60       2438       3210         30          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       21      1.00       2.60       2438       3210         30          20

and 7.1.6

SELECT COUNT(*)   FROM SYS.SOURCE$

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute     10      0.01       0.01          0          0          0          10
Fetch       10      0.26       1.44       1302       1460         30          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       21      0.27       1.45       1302       1460         30          20

Misses in library cache during parse: 1
Optimizer hint: CHOOSE
Parsing user id: 10     (recursive depth: 1)
********************************************************************************

SELECT COUNT(1)   FROM SYS.SOURCE$

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute     10      0.00       0.00          0          0          0          10
Fetch       10      0.43       1.57       1302       1460         30          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       21      0.43       1.57       1302       1460         30          20

in 8i, they made count(1) and count(*) internally "the same".  Probably in
reaction to all of the systems "tuning" with count(1)'s  ;)

Rating: 4
Count(*) Vs Count(1) June 10, 2003
Reviewer: kumar from India
Tom,

Even I remember reading the same piece of advice ( Use count(1) instead of
Count(*) ) in an Oracle Press Book for 8i.
1. Was this advice valid at any point of time ?
2. Is there really any difference in those choices ?
3. Whatever I may choose to use ( Count(*), Count(1) or Count(column) ), will
the optimizer  use a fast full scan given an unique index exists ?

Thanks

Followup:
count(1) is now internally optimized to be count(*) -- probably in reaction
to that "advice".

Rating: 5
count(*) vs count(1) - A classic Oracle Myth June 10, 2003
Reviewer: Paul from UK
Just click to
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1156159920245

      Rating: 3
having and
      sum() not working surprisingly  June 12, 2003

Reviewer:  Asim Naveed  from Pakistan
Hi,
Please consider the follwoing. Why the last query is not
working. Oracle version 9i release 2

SQL> DESC TIME;
 Name                             Null?    Type
-------------------------------- -------- -----------------
TIMEID                                    NOT NULL NUMBER(6)
YEAR                                              NUMBER(10)
QUARTER                                        VARCHAR2(10)
MONTH                                          VARCHAR2(10)
WEEK                                           VARCHAR2(20)
DAY                                            VARCHAR2(20)

SQL> DESC SALES;
Name                                      Null?    Type
----------------------------------------- -------- --------
SALESID                                  NOT NULL NUMBER(11)
PRODUCTID                                NOT NULL NUMBER(10)
MARKETID                                 NOT NULL NUMBER(10)
TIMEID                                   NOT NULL NUMBER(6)
CATEGORYID                               NOT NULL NUMBER(4)
CLASSID                                  NOT NULL NUMBER(4)
DISTRIBUTORID                            NOT NULL NUMBER(10)
SALESMANID                               NOT NULL NUMBER(10)
PRICE                                    NOT NULL NUMBER(10)
VOLUME                                   NOT NULL NUMBER(10)
FORECAST                                          NUMBER(10)
TARGET                                            NUMBER(10)

  1   select time.quarter
  2   from (select distinct time.quarter from time) time
  3   where time.quarter
  4   in
  5   (select time.quarter
  6   from sales, time
  7   where sales.timeid = time.timeid
  8   group by time.year, time.quarter
  9*  having sum(1*2) > 3)
SQL> /

QUARTER
----------
Qtr1-2000
Qtr2-2000
Qtr3-2000
Qtr4-2000
Qtr1-1998
Qtr2-1998
Qtr3-1998
Qtr4-1998

SQL> ED
Wrote file afiedt.buf

  1   select time.quarter
  2   from (select distinct time.quarter from time) time
  3   where time.quarter
  4   in
  5   (select time.quarter
  6   from sales, time
  7   where sales.timeid = time.timeid
  8   group by time.year, time.quarter
  9*  having sum(sales.price*sales.volume) > 3)
SQL> /
 select time.quarter
*
ERROR at line 1:
ORA-03113: end-of-file on communication channel

Why the last query is giving error

          Followup:

metalink.oracle.com is where you want to go, file a tar please.

      Rating: 4
having vs
      where  June 16, 2003
Reviewer:  Scott Wesley
       from Perth, WA
G'day

There seems to be performance gains at times, using having instead of the where
clause, for reasons I'm still trying to explain, but here is my example.

This is the statement I started with.
   1  SELECT /*+ CHOOSE DRIVING_SITE(s) */
   2             SUM(sales_excl_tax)
   3            ,c.sales_rep_nbr
   4      FROM   ths_sales s
   5            ,trd_sales_rep_customers c
   6      WHERE  c.acc_nbr    = s.acc_nbr
   7      AND    s.state_code = c.state_code
   8      AND    c.sales_rep_nbr=5
   9      AND    s.fin_yr        = 2003
  10      AND    s.state_code    = 'WA'
  11* GROUP BY c.sales_rep_nbr
This consistantly took about 22 seconds to run on my development box.

This query, however, consistantly took less than 10 seconds to run.
  1  SELECT /*+ CHOOSE DRIVING_SITE(s) */
  2             SUM(sales_excl_tax)
  3            ,c.sales_rep_nbr
  4      FROM   ths_sales s
  5            ,trd_sales_rep_customers c
  6      WHERE  c.acc_nbr    = s.acc_nbr
  7      AND    s.state_code = c.state_code
  8      AND    s.fin_yr        = 2003
  9      AND    s.state_code    = 'WA'
 10  GROUP BY c.sales_rep_nbr
 11* HAVING c.sales_rep_nbr=5
Even when I removed the having clause.
So by adding the sales_rep_nbr condition into the where changed the explain plan
somewhat, and slowed the query.

Where:
 Id        Parent Id Object            Operation        Options         Pos
 --------- --------- ----------------- ---------------- -------------------
  0                  Cost: 463 Card:1  SELECT STATEMENT REMOTE          463
    1              0                   SORT             GROUP BY NOSORT   1
      2            1                   NESTED LOOPS                       1
        3          2                   REMOTE                             1
        4          2 THS_SALES         TABLE ACCESS     BY INDEX ROWID    2
         5         4 THSSALES_PK       INDEX            RANGE SCAN        1
Having:
 Id         Parent Id Object              Operation          Options    Pos
 -------------------- ------------------- ------------------ --------------
  0                   Cost: 10901 Card:1  SELECT STATEMENT   REMOTE    ####
    1               0                     FILTER                          1
      2             1                     SORT               GROUP BY     1
        3           2                     HASH JOIN                       1
          4         3                     REMOTE                          1
          5         3 THS_SALES           TABLE ACCESS       FULL         2

Interesting....

          Followup:

on of them pulled the table and joined (big bulky operation) and then filtered.

the other used that nested loops technique row by row by row by row.

do you have statistics on all objects and are they accurate.

      Rating: 5
HAVING clause
      of GROUP BY  September 22, 2003
Reviewer:
      cbarr  from Boston, MA USA

      Rating: 5
select cols ,
      Aggr. from ( select with Group By Having Clause) Group By
      cols  September 26, 2003
Reviewer:  pasko
       from HH
Hi Tom,
Thanks for the followup above.

I have a problem with a Group By Having Clause :

take a look at the following two Queries from a VIEW with a GROUP BY and HAVING
CLAUSE

When i do a  ' select without a group by ' , then the answer is fine.
But when i add a Group by to this in_line_view with a 'Group By and a Having
Clause',
then strange things happen:

In one of our Databases, the connection is suddenly broken and
i get the ORA-03113:  error ;

and in the other like in this example, it gives no rows back
although i know for sure that i should get some rows back.

Example :

SQL> SELECT timestart from  vw_having_count_star  where  rownum < 5 ;

TIMESTAR
--------
24.09.03
24.09.03
02.09.03
02.09.03

SQL> SELECT COUNT(*)  FROM vw_having_count_star ;

  COUNT(*)
----------
         0

SQL>SELECT * FROM v$version
BANNER
----
Oracle9i Enterprise Edition Release 9.2.0.4.0 - 64bit Production
PL/SQL Release 9.2.0.4.0 - Production
CORE    9.2.0.3.0    Production
TNS for Solaris: Version 9.2.0.4.0 - Production
NLSRTL Version 9.2.0.4.0 - Production
5 rows selected

My view was created as :

CREATE OR REPLACE FORCE VIEW  VW_HAVING_COUNT_STAR
AS
SELECT
  t.number_vals_received
, t.phoneNum
, t.TXid
, t.customerID
, t.entityID
, t.timestart
, COUNT (*) nchargedVALs
---
FROM SUMMARY_TRANS t, SUMMARY_TRANS_CHILD  s
WHERE ((    t.customerID IN
(
'21212', '6464'
  )
AND t.timestart BETWEEN TO_DATE ('20030901 00:00:00','YYYYMMDDHH24:MI:SS')
   AND TO_DATE ('20030930 23:59:59', 'YYYYMMDDHH24:MI:SS' )
    )
)
AND t.status = 0
AND t.number_vals_received > 0
AND t.phoneNum = s.phoneNum
AND t.TXid = s.TXid
AND s.direction = 1
GROUP BY t.TXid,
t.phoneNum,
t.customerID,
t.entityID,
t.timestart,
t.number_vals_received
HAVING number_vals_received > COUNT (*)

My observation is :

if HAVING is omitted,then this works fine;

It seems we can't do selects of the form :

select aggregate_columns , aggregates( example. count(*) , sum )
from
(
inline_view_with_HAVING_clause
)
GROUP BY group_by_clause

Is that true Tom , or is that a Bug in our Software.

Thanks in advance.

          Followup:

3113 = contact support, oracle is having a problem.

      Rating: 4
How to write
      this select  October 28, 2003
Reviewer:
      A reader
My data is as follows, and I need to find out the keys having status both 'Y'
AND 'N'. How can achieve it..

SQL> CREATE TABLE TTEST ( KEY NUMBER, FLAG VARCHAR2(10));

Table created.

SQL>
SQL>
SQL> INSERT INTO TTEST VALUES( 1 , 'Y');

1 row created.

SQL> INSERT INTO TTEST VALUES( 2,'Y');

1 row created.

SQL> INSERT INTO TTEST VALUES( 2,'Y');

1 row created.

SQL> INSERT INTO TTEST VALUES( 3,'Y');

1 row created.

SQL> INSERT INTO TTEST VALUES( 4, 'Y');

1 row created.

SQL> INSERT INTO TTEST VALUES( 5, 'Y');

1 row created.

SQL> INSERT INTO TTEST VALUES( 5,'N');

1 row created.

SQL> COMMIT;

Commit complete.

SQL> SELECT * FROM TTEST;

      KEY FLAG
--------- ----------
        1 Y
        2 Y
        2 Y
        3 Y
        4 Y
        5 Y
        5 N

7 rows selected.

          Followup:

select key
  from ttest
 where flag in ( 'Y', 'N' )
 group by key
having count(distinct flag) = 2;

October 29, 2003 Justin -- Thanks for the question regarding
      "Replicate or not !!!", version 8.1.7

      You Asked
Hi Tom,

I have to two databases and what i need to do is synchronize 5 tables of one
database with other database.

Tom do you suggest i should go with replication or i should create a link with
trigger on these tables so that these tables perform DML on each other.

Thanks.

      and we said...
easy question, simple answer

use replication -- don't re-invent the wheel.

Review &
      Followup

      Rating:
      3
  March 25, 2003
Reviewer:
      A reader
Hi Tom,

We have Oracle 8.1.5.0 running on Sol 8 and we are in the process of upgrading
to 8.1.7.4 to another server Sol 8.
Our Sr. DBA has suggested replicating as we cannot shutdown our d/b to upgrade,
am  not sure if this is the right approach, can you pls. comment on this.

Thanks.

          Followup:

are you at or near capacity on the 815 server?

have you tested on your test system what replication will do to you? and your
performance?

You can minimize your downtime but for a system upgrade like this, I believe you
will have some (even with replication -- if you spend the next 6 months testing
it -- you will have downtime).

This is bigger then a breadbox.  I've been down this route with some customers
already.  It takes hours of discussion.  My advice -- you need to thoroughly --
off to the side -- TEST THIS 100%.

Consider the downtime you will incur if you don't.

      Rating:
      4
  March 26, 2003
Reviewer:
      A reader
Tom,

Acoording to you,which will be the best approach of upgrading, so that we will
have minimum downtime.

Thanks.

          Followup:

practice, practice, practice.

I was working with a customer who was able to tell me that it would take them 57
minutes from start to finish -- why?  they had done it over 1/2 dozen times --
practiced it like a fire drill.  Had it down pat.  Shaved every second off
possible.

Did this take time?

Yes -- they spent a good 4 weeks setting up for this.

Did the upgrade go off without a hitch?

Yes -- they knew exactly what to do, when to do it, how to do it and had fully
tested the application code before hand.  They hit (and fixed) problems in test
before they became a problem in production (there is no such thing as a P1 tar
in test you know -- they happen when you do something in production that messes
it all up)

      Rating:
      5
  March 26, 2003
Reviewer:
      A reader
Thanks Tom!!

      Rating: 3
Question on
      data replication  March 26, 2003
Reviewer:
      PRS  from USA
Hi Tom,

   I have a schema(SNAP) with all objects defined as a read only snapshots
   with inceremental refresh. We do refresh all these snapshot early
   morning/late night in this schema. Source instance has snapshot logs
   defined for each table.

   My question is I have a different schema(ADM) where I need to push this
   data in a different table structure. One snapshot can map to more
   than one tables in this schema. I could see two soltions for this.

   1. Define an after inser/update/delete trigger on each snapshot(in SNAP
schema)
      and log those id's in some table. Then write a procedure to push
      that data into the more than one table in ADM schema. This seems to be
      a good option. But ORACLE says we do not gurantee that trigger on a
snapshot
      will always execute 100%. ORACLE says do not define any trigger on the
internal
      objects such as snapshot. This is the response I got it from TAR.

   2. Write a procedure to compare the difference in data and push tat data into
      more than one table in ADM schema. But this is the moset expensive option
as
      everyday there will be atleast 100,000 rows to pickup for each table. Each
table
      volume is 3.5 to 4 million rows. So I think this is not a right option.

   Can you suggest any other valid option?

Thanks,
PRS

          Followup:

1) that is not a good idea.  The snapshot refresh mechanism is a wierd beast and
changes from release to release.  read only snapshots do not support triggers,
this would not be a suggested course of action

2) Why would ADM not just create snapshots using horizontal and vertical slices
of the source data itself??

      Rating: 2
Need some
      info  March 26, 2003
Reviewer:  PRS
       from USA
I cannot define snapshot in ADM schema as one row of one table in SNAP schema
can map to one row in many tables in ADM schema. So You have to have some kind
of procedural logic to do that.
 I just want to capture the daily changed(insert/update/delete) records to log
in to some table use that table as a driving table to load the data into ADM
schema.
  We are using ORACLE9i release2. I heard that with the use of oracle stream you
define as a table in SNAP schema and you apply the changes through ORACLE STREAM
from source to destination database. And as long as I define that as a table in
SNAP schema I can have the trigger also on it.

 Please shed some light on ORACLE STREAM option here.

Thanks
PRS

          Followup:

yes, streams can do that -- with your help, you'll get logical change records
and can procedurally process them.

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-STR

      Rating:
      1
question  March 26, 2003
Reviewer:
      A reader
please see the question above

          Followup:

why?  oh I guess i'm too slow today.... anyway -- i don't sit here all day,
catch as catch can.

(funny, but in my interface -- your review is the top, looking up I see my wall
;)

      Rating:
      3
sorry  March 26, 2003
Reviewer:
      A reader
I do not mean that way.

      Rating:
      3
Question  May 08, 2003
Reviewer:
      PRS  from USA
Hi Tom,
   I have a database in 9i(9.2) which has a schema of read only snapshots
comming from ORACLE8i(8.1.7). We refresh the read only snapshot in 9i instance
incrementally twice a day.

   I want to replicate this snapshot data into some other tables in 9i database
by writing a procedure or triggers on a snapshot. But oracle does not support
trigger on a read only snapshot. So I am exploring the idea of using the oracle
stream.
   So I am creating a seperate small database(A) with ORACLE9i(9.2) and define
the read only snapshot in to that database A. Then setup an ORACLE stream from
Database A to the Database B where you define snapshot as a table.
So I can have trigger on table in database B and I can move the data in a
different table structures. So question is

   can I define an ORACLE STREAM on read only snapshot in database A point to
tables with the same structure in Database B?

   Your reply is appreciated.
Thanks,
Prashant Shah

          Followup:

why would you want to replicate that data into the same database?

      Rating:
      3
  May 09, 2003
Reviewer:  PRS
       from USA
Data are in different structure. We are using peoplesoft CRM application. It has
it's own customer data model. So we are trying to fit our current CDM model into
peoplesoft CDM model.

          Followup:

yes, you can use streams to do the ETL'ing for you.   you would set up a custom
apply for the logical change record and do whatever you wanted to it.

      Rating: 3
Example of
      stream   May 14, 2003
Reviewer:  PRS
       from USA
Hi Tom,
   I did not see any example of stream on this site.
Do you have any example showing the concept of oracle stream? If you have it,
can you please post it to this site?

Thanks,
PRS

      Rating: 4
What is the
      advantage of using stream over replication?  June 25, 2003

Reviewer:  Yong  from CA, USA
Tom,

Both stream and replication can do the job above.

If one use Orace stream to capture any change to the database, it is almost the
same as a standby database.

What is advantage of using stream over the replication in this case (except that
stream can capture/apply to non-oracle database)?

          Followup:

no, standby is a little like streams, streams is not like standby.  standby can
use some of the streams technology -- but standby is lots more then streams.

(replication can apply to non-oracle as well in some cases)....

streams is the way forward, less intrusive, more flexible (rules,
transformations, filters)

      Rating:
      5
  June 26, 2003
Reviewer:  Yong
       from CA, USA
Got it!
Thanks,

      Rating:
      5
Sreams  August 11, 2003
Reviewer:
      A reader
Tom,
Can streams be used to replicate data to a set of tables if the destination data
is in a xml file(say no DB here) or if the destination data is in a xml file in
a table.column which is a blob.

Any other suggesstions other than streams is really appreciated.

Thank you

          Followup:

you would write your own custom apply -- to maintain the XML.

so streams would

o capture the changes
o stage the changes
o invoke your custom routine to apply those changes to your XML document
instance.

      Rating:
      5
Streams  August 11, 2003
Reviewer:
      A reader
Thank you

      Rating: 3
October
      Sky  October 16, 2003
Reviewer:  Andy
       from Los Angeles, CA
I have 3 DBs with almost identicial schemas.  We plan using replication(2 ways)
technology to make it virtually a single database.  I am impressed that the
flexibility(rule, transformation) of Streams.  Will you suggest that we should
go for Streams rather than 2-way replication in my situation?  Will Oracle use
Streams to replace replication in the long run?

          Followup:

i would suggest a single database in virtually every situation.

streams is the future direction for replication implementations.

i would seriously ask myself however "do i really want replication AT ALL,
wouldn't a single database serve me infinitely better"

      Rating: 3
An
      example  October 17, 2003
Reviewer:  Matt
       from Australia
"i would suggest a single database in virtually every situation.
"

Can you please suggest a situation where you might not consider using multiple
schemas in a single database instance?

          Followup:

i'm at a loss for words.  I guess I'd have to say "i don't have any such
situations"

3rd party apps are about it.  they sometimes need their own database as they may
have specific version/setup requirements.  Here the database is less a database
then part of their black box.

But for my stuff ("my" meaning my companies stuff), nope, i'd be at a loss for
words to describe a scenario whereby I would run more then one instance on a
machine.

      Rating: 4
replication
        October 28, 2003
Reviewer:  venkat
       from India
Tom,

We need to replicate around 40% of the database objects from the production site
into another site for UAT (uni directional) . The users want "closer to
synchronous replication". Which is the best way to achieve this? Oracle version
is 8.1.7.4 on Sun solaris.

Regards,
Venkat

          Followup:

read only snapshots.

see the replication guide.

      Rating:
      4
replication  October 29, 2003
Reviewer:
      Venkat  from India
Tom,

Thank you very much. If all the objects of a schema need to be replicated, what
is the mecahnism you like to recommend?

Regards,
Venkat

          Followup:

use OEM to set up read only snapshots via a gui that'll let you do many objects
at once.

October 29, 2003 Liz -- Thanks for the question regarding
      "compiling pro*c from unix on NT", version

      You Asked
hi tom!  i have an Oracle 8 Pro*C application
originally developed under UNIX that i need to port to NT.  what

do i have to do to compile and link the thing on an NT box,
assuming i'm using MSVC++ 6.0?

btw i've searched the Oracle documentation and hit several dead
ends.  please help!  the success of our app depends on your
answer...

liz

      and we said...

I've done this quite a bit.

Here is an example makefile showing my method (from Oracle8i,
release 8.1 -- will be slightly different for 8.0).  I use an
older version of MSVC++ and I use the command line (not that
"user friendly" gui which apparently just wants to get in my way
;)

-------------------------------------------------------------
CPU=i386
WRB_DIR         = .\

!include <\msdev\include\win32.mak>

MSLIB_DIR   = c:\msdev\lib
ORACLE_HOME = d:\oracle\ora81

TGTEXE      = test.exe

OBJS = test.obj

NTUSER32LIBS    = $(MSLIB_DIR)\user32.lib   \
                  $(MSLIB_DIR)\msvcrt.lib  \
                  $(MSLIB_DIR)\oldnames.lib  \
                  $(MSLIB_DIR)\kernel32.lib   \
                  $(MSLIB_DIR)\advapi32.lib

SQLLIB  = $(ORACLE_HOME)\precomp\lib\msvc\orasql8.lib

INCLS   = -Ic:\msdev\include \
          -I$(ORACLE_HOME)\precomp\public

CFLAGS  = $(INCLS) -DWIN32 -DWIN_NT

$(TGTEXE): $(OBJS)
    $(link) $(LINKOPT) \
    -out:$(TGTEXE) $(OBJS) \
        $(COMPLIB) \
        $(MSLIB_DIR)\libc.lib \
        $(NTUSER32LIBS) \
        $(SQLLIB)

test.c: test.pc
    proc iname=test.pc include=c:\msdev\include parse=full

---------------------------------------------------------

So, I've created a rule to turn my test.pc into a test. c file
with the line:

test.c: test.pc
    proc iname=test.pc include=c:\msdev\include parse=full

Note that parse=full, the default on UNIX, must be specified if
you need it as the precompiler on NT defaults to parse=none
(don't ask why -- i don't know and i agree that it is silly that
it does).

Then, the default rule for turning .c into .obj files kick in
and it compiles the code for me.  I added the
$ORACLE_HOME\precomp\public to the CFLAGS for the compilation so
it can find sqlca.h and such.

the name of the sqlplus is most likely different if you are
using 8.0 -- it should be in more or less the same place -- see
what is there and alter my sqllib above.

As long as you wrote portable C, it should go just fine.

Review &
      Followup

      Rating: 2
Pro
      C  April 13, 2001
Reviewer:  Darshan
       from India
I am stuck up with basics of PRO C. I am trying to compile the sample .pc files
in ../demo/proc folder.
It is giving me lots of errors.
I just require the preliminary knowledge before compilation of a Pro C file. And
that atleast one of the sample file should be compiled.
All the help on PRO C over the net speaks of sntactical stuff, but no help is
seen on the basic compilation thoroughly.

Also I have found that some people have suggested to compile the programs as :
proc INAME=cppdemo1 MODE=ORACLE

Some are using the syntax:
make -f $ORACLE_HOME/precomp/demo/proc/demo_proc.mk build EXE=cppdemo1
OBJS=cppdemo.o

Which is the right one...????

      Rating: 5
Pro*c ".c" file
      to ".pc" file ?  October 29, 2003
Reviewer:
      Fenng  from CNOUG
HI ,Tom ,

Is there any tool can translate  ".c" file to ".pc" file?

Now I have a ".c" file ,and I want to analyze the ".pc" source code,but the file
has been deleted.

any tool or method ?

Thank you!

          Followup:

not that I am aware of -- but the original code is all there in comments, you
should be able to just read the .c file itself.

      Rating: 4
Thank
      you!  October 29, 2003
Reviewer:  Fenng

Any way ,thank you!

I only want to extract the sql from the cgi file ,than I use the unix command
"strings" to get it from the cgi file.

So it's enough for me.

BTW: your new book is great !

October 29, 2003 Jennifer -- Thanks for the question regarding
      "Snapshot -- Upload and download", version 8.1.6

      You Asked
Hi Tom,

I have two scenarios at our replications,
1). We need to centralize data from different area like
Austrlia.region.com, usa.region.com, asia.region.com,
usa.region.com is a master site, every day night, we need to
upload data from all different site into master site.
2). each site, salesperson need to extract related data into
laptop and upload infor they made during day. For example, In
asia.region.com, salesperson need to extract their own data. At
evening, they connect to master database to upload infor, after
that, they need to download data again.

How can I do it? For the laptops, where do I keep those changes
or new infor? Should I create snapshot log at laptop? Does
Oracle has build-in package to do that for uploading,
DBMS_SNAPSHOT.REFRESH ?

Thanks

      and we said...
It sounds like each region will be a master site.

The salespeople will be updatable snapshot sites of their
region.

The centralized site, if it is READ ONLY copies, would be
snapshot sites of the regions (a set of snapshots per region) or
if it is updatable would be an N-Way master in conjunction with
each region.

You really need to give the replication guides at:

http://technet.oracle.com/doc/oracle8i_816/server.816/index.htm
a good read through.  You would create masters at each of the
regional sites.  they MIGHT have a master at the centralized
site (if it is updatable).

At the regional sites, each table will have a snapshot log on it
to support the updatable snapshots the salepeople will make.
The salespeople will have updatable snapshots, propably using
deployment templates (see

http://technet.oracle.com/doc/oracle8i_816/server.816/a76959/dt_conc_.htm#17099
).

Review &
      Followup

      Rating:
      4
Replication  August 20, 2002
Reviewer:
      A reader
Hi Tom,
I hope you can help me and tell me if I chose the right direction.
Right now I have everything in only one database, OLTP applications and DSS
queries.
What I would like to do is to move the DSS queries and the batch process to
another database in order to improve performance on the OLTP applications during
all the day.
In order to do that I was thinking to use snapshot replication.
Let say that I will create database-A and database-B, database-A will support
all the oltp applications and database-B will support the dss queries and the
batch process. What I  have in mind is to create snapshot logs  on database-A
over the tables that we need to have an updatetable snapshot on database-B.
My question are the following:
1-Can I have only a couple of updatetable snapshots on database-B and all the
rest objects on B will belong only to database-B without causing performance
problems to the users in database-B.
2-I need to propagate the updates between the two databases (a->b and b->a) as
soon as these updates are done. Is the snapshot replication the best option for
me or should I choose the multimaster replication? I think the snapshot
replication is the best alternative but perhaps I missed something and I am
wrong. Can you help me with this.

Thanks in advance

          Followup:

What is the chance that you can roll an upgrade (since you will be drastically
changing the system)?

If so, a logical standby database would be JUST the thing for you.

Zero impact on the OLTP system (snapshot logs will impact it, refreshes will
impact it)

Trickle feed if you like (or don't trickle feed) during the day on the DSS
system.

Creates a nice failover box for you.

Can have additional structures for DSS (eg: indexes, materialized views, and
such).

Updateable snapshots require ADVANCED multi-master support on the OLTP box, not
just snapshot logs.

You do not want to put your batch processes on the other machine (think about it
-- the OLTP machine would have to do the BATCH work *and* the DSS machine would
have to do it -- the goal of making the OLTP machine would not be met).  That --
and doing update anywhere replication will almost certainly involve a MAJOR
rewrite of large portions of your application to handle update conflicts.

If you want to do this cheaply and easily, a logical standby is what you want.
Replication would be *way* hard.

Short of a logical standby, you could use a physical standby (available from 7.3
on up).  The physical standby can be opened read only for reporting however

o it cannot have the additional indexes and such you would probably want
o it cannot be "trickle" fed into during the day while you are reporting on it

      Rating: 4
physical
      standby  October 29, 2003
Reviewer:  venkat
       from India
Tom,

As we know that the physical stand by will always be in recovery mode and the
same is used for Disaster Recovery.

When the primary database is working, can we open the stand by database at a
given point of time in read only mode and issue queries and use the same for the
generation of reports? In this case, is the database opened using resetlogs
option?

When we do this way, can we continue to use this as standby like before? If so,
how the archived files from the primary site can be applied on this standby and
how to open this database again when it is needed for querying and report
generation?

Regards,
Venkat

          Followup:

with a physical standby it is either

o in managed recovery mode applying redo
o opened read only for querying
o not started

when you open for query -- we do NOT perform a reset logs, that would break it.

while open for query, the archives are just saved up on the standby -- waiting
to be applied when you put the machine back into managed recovery mode.

October 29, 2003 Venkatasubramanian -- Thanks for the question
      regarding "index,Joins", version 8.1.7.4

      You Asked
Dear Tom,

During batch operations which involve lot of insert and update operations, is it
better to drop the indexes and recreate using parallel and nologging option to
speed up the performance. What is your viewpoint?

What should be the ordering columns in an index? Does it make an impact on the
performance?

Does the order of joins need to evaluated and changed towards performance
improvement in 9i?or  can we leave this to CBO?

Regards,
Venkat

      and we said...
insufficient data to answer.  there are lots of factors involved here.

o what is "alot".  many people think a table with 1,000,000 rows is big for
example -- i don't, thats medium.  "alot" is vague.

o are you accomplishing your goals in your maintanence window already?

o do others need to access this data?

among others.....

If you have my book "expert one on one Oracle" i discuss the order of columns in
an index in depth.  It totally depends on HOW you use the index, the data.
Suppose you copy ALL_OBJECTS and need to ask these questions of this copy:

o show me the information for the EMP TABLE owned by SCOTT
o show me the information for all PROCEDURES owned by TKYTE
o show me a list of all objects owned by MDSYS

well, the only logical index is therefore:

( owner, object_type, object_name )

it can be used to answer ALL THREE queries.  You look at your questions and
organize the index to answer them.

Note that specifically the most selective column is LAST in this index.  you do
not put selective columns "first" -- selectivity has nothing to do with where to
put columns -- but how you query the data does!

with the CBO, the order of predicates is not relevant.
Review &
      Followup

      Rating: 5
One real world
      example   October 29, 2003
Reviewer:  Chuck
      Jolley  from OKC, OK USA
Maybe a real world example would help?
We have a tax database with accounts going back about 8 years.
That’s about 3.3 million rows in the accounts table.
On a medium size, 4 year old, Compaq server with 2G of ram.

The accounts table has 20 indexes on about 40 columns and
insert and update triggers that maintain flags in the table
and update summary tables for real time summary reporting.

Once a year we get the information we need to create the new
year’s tax accounts. About 350,000 inserts.

At one time, we dropped the indexes and disabled the triggers,
did our insert and then ran procedures to set the flags and
do the accounting summarizations and recreate the indexes.
This took about 2 hours and was done off hours.

Now we just do the insert and let the database do it’s thing.
It takes about half as much time and runs during regular
hours.

In this particular case, our statistics (8.1.7) do not get off
enough to confuse the CBO and we just let the regular
Saturday night statistics rebuild job we run make things
perfect again the following weekend.

So, in our case, the extra work involved in doing it
the “high performance” way actually made the over all
process slower!

Your mileage may vary ;)
Chuck

October 29, 2003 CJ -- Thanks for the question regarding
      "Partition tablespacesing y", version 8.1.7,9.2

      You Asked
Just wondering if you have any standards about partition tablespaces. I have a
couple of large tables that are partitioned by quarter. Does it really matter if
each partition is in its own tablespace? What about 4 quarters per tablespace.
Should it just be about manageability?

Thanks in advance,
CJ

      and we said...
it is 100% about manageability.

they can all be in the same tablespace -- but remember a tablespace is one of
the basic units of recovery -- so, if you have many partitions in there and need
to recover the tablespace, you'll take lots of partitions offline (block level
recovery in 9i with RMAN reduces this effect considerably)

do it in a manner that is easy to manage.  since we predominantly balance IO at
the OS/disk level with raid and striping, its not about IO

October 29, 2003 Bo -- Thanks for the question regarding
      "dense_rank vs. rank", version 8.1.7

      You Asked
Hi, Tom.
Could you please tell me more about how to determine when to use analyatic
function dense_rank, and when to use rank?

Thanks.

Bo

      and we said...
You need to tell ME when you want to use either/or.  This is a short extract
from my book where I cover analytic functions in great detail.

dense_rank

This function computes the relative rank of each row returned from a query with
respect to the other rows, based on the values of the expressions in the ORDER
BY clause. The data within a group is sorted by the ORDER BY clause and then a
numeric ranking is assigned to each row in turn starting with 1 and continuing
on up. The rank is incremented every time the values of the ORDER BY expressions
change. Rows with equal values receive the same rank (NULLS are considered equal
in this comparison). A dense rank returns a ranking number without any gaps.
This is as compared to RANK below.

...

rank

This function computes the relative rank of each row returned from a query with
respect to the other rows, based on the values of the expressions in the ORDER
BY clause. The data within a group is sorted by the ORDER BY clause and then a
numeric ranking is assigned to each row in turn starting with 1 and continuing
on up. Rows with the same exact values of the order by expressions receive the
same rank, however if two rows do receive the same rank ? the rank numbers will
subsequently "skip". If two rows are #1, there will be no #2 ? rank will assign
the value of 3 to the next row in the group. This is in comparison to DENSE_RANK
which does not skip values.

.....

We will use the SCOTT.EMP table for these examples. The first question we will
answer is "give me the set of sales people making the top 3 salaries in each
department"?

scott@TKYTE816> break on deptno skip 1

scott@TKYTE816> select *
  2    from ( select deptno, ename, sal,
  3                  dense_rank() over ( partition by deptno
  4                                      order by sal desc ) dr
  5            from emp )
  6   where dr <= 3
  7   order by deptno, sal desc
  8  /

    DEPTNO ENAME             SAL         DR
---------- ---------- ---------- ----------
        10 KING             5000          1
           CLARK            2450          2
           MILLER           1300          3

        20 SCOTT            3000          1
           FORD             3000          1
           JONES            2975          2
           ADAMS            1100          3

        30 BLAKE            2850          1
           ALLEN            1600          2
           TURNER           1500          3

10 rows selected.

What we did here was to use the DENSE_RANK() function to get the top three
salaries. We assigned the dense rank to the SAL column sorted descending.  If
you recall from above, a dense rank does not skip numbers and will assign the
same number to those rows with the same value. Hence, after the result set is
built in the inline view ? we can simply select all of the rows with a dense
rank of three or less ? that gives us everyone who makes the top three salaries
by department number.  Just to show what would happen if we tried to use RANK
and encountered these duplicate values:

scott@TKYTE816> select deptno, ename, sal,
  2         dense_rank() over ( partition by deptno
  3                                 order by sal desc ) dr,
  4         rank() over ( partition by deptno
  5                           order by sal desc ) r
  6    from emp
  7   order by deptno, sal desc
  8  /

    DEPTNO ENAME             SAL         DR          R
---------- ---------- ---------- ---------- ----------
        10 KING             5000          1          1
           CLARK            2450          2          2
           MILLER           1300          3          3

        20 SCOTT            3000          1          1
           FORD             3000          1          1
           JONES            2975          2          3 <<----
           ADAMS            1100          3          4 <<----
           SMITH             800          4          5

        30 BLAKE            2850          1          1
           ALLEN            1600          2          2
           TURNER           1500          3          3
           WARD             1250          4          4
           MARTIN           1250          4          4
           JAMES             950          5          6

14 rows selected.

If we had used RANK, it would have left ADAMS out of the result set ? but ADAMS
is one of the people in department 20 making the top 3 salaries so he belongs in
the result.  In this case, using RANK over DENSE_RANK would not have answered
our specific query.

Review &
      Followup

      Rating:
      5
Ran()  December 15, 2002
Reviewer:
      Streve
In your last example,
select deptno, ename, sal,
         dense_rank() over ( partition by deptno
                                order by sal desc ) dr,
        rank() over ( partition by deptno
                          order by sal desc ) r
   from emp
  order by deptno, sal desc

How do I get exactly first N records for all dept
(e.g first 4 for dept 30)?
Thanks you!

          Followup:

select *
  from ( select a.*,
                row_number() over ( partition by DEPTNO
                                    order by SAL DESC ) rn
           from emp )
 where rn <= 4
 order by deptno, sal desc

      Rating: 1
Check this
        August 25, 2003
Reviewer:  A reader

Hi Tom,
I am using the following query to rank the cpu time usage, dont care about the
sql which was took too much of cpu etc
it gives me the following , is my query wrong or is there something wrong with
analsyis function.
I am using enterpise edition release 9.0.1.1.1

SQL>  SELECT CPU_TIME CPUTIME,
  2   RANK() OVER (ORDER BY SUM(CPU_TIME) DESC) AS CPU
  3   FROM V$SQL
  4   GROUP BY CPU_TIME
  5  /

   CPUTIME        CPU
---------- ----------
  20031250          1
  14968750          2
  13906250          3
  13359375          4
   6125000          5
    468750          6
    500000          7
    531250          8
   3703125          9
    546875         10
    703125         11

   CPUTIME        CPU
---------- ----------
    453125         12
    515625         13
   2546875         14
    578125         15
    437500         16
    406250         17
     46875         18
    484375         19
   1906250         20
     31250         21
    593750         21

   CPUTIME        CPU
---------- ----------
     78125         23
    562500         24
     62500         25
   1578125         26
     15625         27
    625000         28
   1250000         28
    140625         30
    375000         30
    125000         32
    968750         33

   CPUTIME        CPU
---------- ----------
    296875         34
     93750         35
    421875         35
    156250         37
    390625         37
    750000         39
    734375         40
    234375         41
    640625         42
    609375         43
    109375         44

   CPUTIME        CPU
---------- ----------
    218750         45
    359375         46
    171875         47
    328125         48
    312500         49
    281250         50
    265625         51
    250000         52
    203125         53
    187500         54
         0         55

55 rows selected.

SQL> SPOOL OFF

          Followup:

it is your use of the analytic function that is improper here.  Looking at your
query:

SQL>  SELECT CPU_TIME CPUTIME,
  2   RANK() OVER (ORDER BY SUM(CPU_TIME) DESC) AS CPU
  3   FROM V$SQL
  4   GROUP BY CPU_TIME
  5  /

in english, one might say this:

show me the CPU time -- removing all duplicates (select cpu_time, group by
cpu_time)

but, before you remove the duplicates -- sum up the cpu time by cpu time --
order the data by that and rank it.

So, if you had 1,000,000 sessions that had 1 cpu second each -- you would have
found the report to be:

   CPUTIME        CPU
---------- ----------
         1          1
  20031250          2
  14968750          3
...

if you throw "sum(cpu_time)" into the select list, that is the number you are
ranking!  then it'll make more sense why "small" cpu times are at the top of the
list, before larger ones.  that just means you had a couple of sessions with the
SAME cpu time.

Now, since I'm not sure what question you are trying to answer, I cannot tell
you how to write the query.  It could be as simple as "lose the sum and group
by"

      Rating:
      5
dense_rank()  October 29, 2003
Reviewer:
      Venkateswara Reddy  from Bangalore, India
Hi tom

I am enjoying with this  and solving most of the problems.

I want to get TOP3 dept  Salaries where SMITH works.

I am trying so many ways. I din't get. Can you tell how.

thanks in advance

Reddy

          Followup:

select *
  from ( select distinct sal
           from emp
          where deptno = ( select deptno from emp where ename = 'SMITH' )
          order by 1 desc )
 where rownum <= 3;

      Rating: 4
ANSI
      standards  October 29, 2003
Reviewer:  Ashiq
      Shamsudeen A  from Trichy, Tamil Nadu, India
Hi Tom,

 Are these Analytical functions are ANSI SQL standards or Oracle implementing by
its own?

          Followup:

i *believe* there are draft ansi specs for some of these, yes.

not really following that much anymore.  sql92 became so large, with so many
levels and options that "ansi sql 92" compliance was utterly meaningless.  sql99
-- 10 times more so.

      Rating:
      5
  December 09, 2003
Reviewer:  Mark
       from KC
Thanks Tom for all your help. Analyatic functions do Rock!

I got a request from a buisness user wanting to change the ranking to say
"(Tie)" for items that are tied. i.e.

10   SCOTT       3000          1(Tie)
10   FORD        3000          1(Tie)
10   JONES       2975          3
10   ADAMS       1100          4

20   ALLEN       1600          1
20   TURNER      1500          2
20   WARD        1250          3(Tie)
20   MARTIN      1250          3(Tie)
20   JAMES        950          5

Know of any clever ways of doing this?  I'm at a loss.

Thanks

October 29, 2003 -- Thanks for the question regarding "Column
      statistics", version 8.1.7

      You Asked
Hi Tom,
  First, your URL is very well.
  Tom, I have problem about column statistics..
  Can you show me, how you get Low-High Date values from statistics?
  Now I used UTL_RAW package to cast RAW to varchar2 and number,
  but I don't know -- How to cast type "date" column?

Thank you.

      and we said...
You'll have to look at it a byte at a time.  See

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76975/oci03typ.htm#421890
it documents the 7 byte format.  Then, using that, you can:

ops$tkyte@ORA817DEV> create table t ( x date );
Table created.

ops$tkyte@ORA817DEV> insert into t values ( sysdate );
1 row created.

ops$tkyte@ORA817DEV> insert into t values ( to_date( '30-nov-1992 3:17 pm',
'dd-mon-yyyy hh:mi am' ) );
1 row created.

ops$tkyte@ORA817DEV> select to_char(x,'dd-mon-yyyy hh24:mi:ss'), dump(x) d from
t;

TO_CHAR(X,'DD-MON-YY D
-------------------- --------------------------------------------------
02-apr-2003 08:43:20 Typ=12 Len=7: 120,103,4,2,9,44,21
30-nov-1992 15:17:00 Typ=12 Len=7: 119,192,11,30,16,18,1

ops$tkyte@ORA817DEV> analyze table t compute statistics;
Table analyzed.

ops$tkyte@ORA817DEV> set numformat 99

ops$tkyte@ORA817DEV> select high_value,
  2         to_number(substr(high_value,1,2),'xx')-100 century,
  3         to_number(substr(high_value,3,2),'xx')-100 year,
  4         to_number(substr(high_value,5,2),'xx') month,
  5         to_number(substr(high_value,7,2),'xx') day,
  6         to_number(substr(high_value,9,2),'xx')-1 hour,
  7         to_number(substr(high_value,11,2),'xx')-1 min,
  8         to_number(substr(high_value,13,2),'xx')-1 second
  9    from user_tab_columns
 10   where table_name = 'T';

HIGH_VALUE     CENTURY YEAR MONTH DAY HOUR MIN SECOND
-------------- ------- ---- ----- --- ---- --- ------
78670402092C15      20    3     4   2    8  43     20

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select
  2         low_value,
  3         to_number(substr(low_value,1,2),'xx')-100 century,
  4         to_number(substr(low_value,3,2),'xx')-100 year,
  5         to_number(substr(low_value,5,2),'xx') month,
  6         to_number(substr(low_value,7,2),'xx') day,
  7         to_number(substr(low_value,9,2),'xx')-1 hour,
  8         to_number(substr(low_value,11,2),'xx')-1 min,
  9         to_number(substr(low_value,13,2),'xx')-1 second
 10    from user_tab_columns
 11   where table_name = 'T';

LOW_VALUE      CENTURY YEAR MONTH DAY HOUR MIN SECOND
-------------- ------- ---- ----- --- ---- --- ------
77C00B1E101201      19   92    11  30   15  17      0

Review &
      Followup

      Rating: 5
URL is very
      well  April 02, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Glad to know that your URL is very well, Tom.  Not sure where to take a sick URL
these days... :)

- Mark

      Rating: 4
Column
      statistics  April 02, 2003
Reviewer:
      A reader  from Thailand
For along time I want to know, how to get this values from column statistics,
you make it's easy for me -- Thank you.

      Rating:
      3
user_tab_columns  October 29, 2003

Reviewer:  Matt  from UK
I am trying to convert the low_value and high_value raw columns in
user_tab_columns.

The actual low and high values from source table are:

5145 and 675891

I can do a dump of low and high:

Typ=2 Len=3: 194,52,46
Typ=2 Len=4: 195,68,59,92

user_tab_columns has a low_value and high_value of C2342E and C3443B5C.

I can do a to_number(substr( low_value,1,2 ), 'xx' ) etc... to give me the
194,152,46 ...

My question (finally) is - how do I get from the low_value values of 194,152,46,
back to the original number of 5145.

I am on 8.1.7.4 and don't have utl_raw.raw_to_number.

Any help appreciated

          Followup:

ops$tkyte@ORA920LAP> select dump(5145,16) from dual;

DUMP(5145,16)
---------------------
Typ=2 Len=3: c2,34,2e

1 row selected.

ops$tkyte@ORA920LAP>

you can dump in base 16 to see what the number in hex would be but I don't
really have anything to "go the other way" -- from the hex to the number.

sorry -- i don't have that plsql written.  the oci guide does describe the
number format but it would be tricky to convert.

October 29, 2003 Juan Carlos -- Thanks for the question regarding
      "First an last day for any week in any month", version 8.1.7.0.0

      You Asked
Hi, how determine the first and last day for any specific week and month?

Thank's

      and we said...
  1* select trunc(sysdate,'w'), trunc(sysdate,'w')+6, last_day(sysdate)
      from dual
ops$tkyte@ORA8I.WORLD> /

TRUNC(SYS TRUNC(SYS LAST_DAY(
--------- --------- ---------
15-APR-01 21-APR-01 30-APR-01

ops$tkyte@ORA8I.WORLD> !cal
   April 2001
 S  M Tu  W Th  F  S
 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

Review &
      Followup

      Rating: 5
Excelent
      function...  April 17, 2001
Reviewer:
      A reader  from Cali - Colombia
It's very useful. I want more information about date formats in Oracle.

      Rating:
      5
  April 17, 2001
Reviewer:  Ch Srinivasa
      Rao  from Singapore
This SQL is more usefull because this is a undocumented source.

      Rating:
      5
  April 18, 2001
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating: 4
First an last
      day for any week in any month  October 09, 2002

Reviewer:  Dirk Brouwer  from Burgum, The Netherlands

Very usefull, but from now on I would use:

select trunc(sysdate,'iw'), trunc(sysdate,'iw')+6, last_day(sysdate) from dual,
because this year (2002) the first day of the week is tuesday, when you're using
'w'.

      Rating:
      4
  February 03, 2003
Reviewer:  Dirk
      Brouwer  from Burgum, The Netherlands
Be carefull with my last remark:
> "select trunc(sysdate,'iw'), trunc(sysdate,'iw')+6,
> last_day(sysdate) from dual,
> because this year (2002) the first day of the week
> is tuesday, when you're using 'w'."
There's a bug in Oracle7. This year (2003) on a saterday it is selecting the
next week instead of this week.
It's working allright in Oracle8.

      Rating: 4
Calendar Query
      in Oracle  May 27, 2003
Reviewer:  D Porter
       from Mobile, AL USA
Here is a query that was modified from the queries put together by Tom.  It does
its own calculation of the week (based on Sunday being the first day of the
week).  The month and year dates are entered and it generates the result:

select max(decode( to_char(dt,'d'), 1, to_char(dt,'dd'), null )) Su,
       max(decode( to_char(dt,'d'), 2, to_char(dt,'dd'), null )) Mo,
       max(decode( to_char(dt,'d'), 3, to_char(dt,'dd'), null )) Tu,
       max(decode( to_char(dt,'d'), 4, to_char(dt,'dd'), null )) We,
       max(decode( to_char(dt,'d'), 5, to_char(dt,'dd'), null )) Th,
       max(decode( to_char(dt,'d'), 6, to_char(dt,'dd'), null )) Fr,
       max(decode( to_char(dt,'d'), 7, to_char(dt,'dd'), null )) Sa
  from ( select trunc(to_date('2003-05-15'),'year')+rownum-1 dt,
                trunc((rownum + to_char(trunc(to_date('2003-05-15'), 'year'),
'd') - 1  ) / 7 + .9999) weekofyear
           from all_objects
          where rownum <=
to_number((to_char(add_months(trunc(to_date('2003-05-01'), 'mm'), 1), 'ddd') -
1))
       ) calendar
 where to_char(dt, 'mm') = to_char(to_date('2003-05-15'), 'mm')
 group by weekofyear
 order by weekofyear

      Rating:
      4
  October 28, 2003
Reviewer:
      A reader
Hi Tom,

could you please tell us the difference between 'w' and 'iw' formats.

Thanks

          Followup:

documented in the sql reference guide, under DATE FORMATS.

just use different "standards".

      Rating:
      4
  October 28, 2003
Reviewer:
      A reader
Hi Tom,

I just went through the documentation and I just have one doubt here, if i need
to get the date of "MONDAY" (that is the first day of the week) of any week to
which a particular date belongs should i use 'w' or 'iw'.
Would :-
Select trunc('any_date', 'iw') from dual
will always return me the first day (MONDAY) of the week.

          Followup:

IW Same day of the week as the first day of the ISO year
 W Same day of the week as the first day of the month

seems to work

ops$tkyte@ORA920LAP> l
  1  select dt,
  2         trunc(dt+30,'iw'),
  3        next_day( (dt+30)-7, 'MON'),
  4        trunc(dt+30,'w')
  5    from (select add_months( to_date('01-jan-2003','dd-mon-yyyy'),
12*(rownum-1) ) dt
  6            from all_objects
  7*             where rownum <= 20 )
ops$tkyte@ORA920LAP> /

DT              TRUNC(DT+30,'IW NEXT_DAY((DT+30 TRUNC(DT+30,'W'
--------------- --------------- --------------- ---------------
Wed 01-jan-2003 Mon 27-jan-2003 Mon 27-jan-2003 Wed 29-jan-2003
Thu 01-jan-2004 Mon 26-jan-2004 Mon 26-jan-2004 Thu 29-jan-2004
Sat 01-jan-2005 Mon 31-jan-2005 Mon 31-jan-2005 Sat 29-jan-2005
Sun 01-jan-2006 Mon 30-jan-2006 Mon 30-jan-2006 Sun 29-jan-2006
Mon 01-jan-2007 Mon 29-jan-2007 Mon 29-jan-2007 Mon 29-jan-2007
Tue 01-jan-2008 Mon 28-jan-2008 Mon 28-jan-2008 Tue 29-jan-2008
Thu 01-jan-2009 Mon 26-jan-2009 Mon 26-jan-2009 Thu 29-jan-2009
Fri 01-jan-2010 Mon 25-jan-2010 Mon 25-jan-2010 Fri 29-jan-2010
Sat 01-jan-2011 Mon 31-jan-2011 Mon 31-jan-2011 Sat 29-jan-2011
Sun 01-jan-2012 Mon 30-jan-2012 Mon 30-jan-2012 Sun 29-jan-2012
Tue 01-jan-2013 Mon 28-jan-2013 Mon 28-jan-2013 Tue 29-jan-2013
Wed 01-jan-2014 Mon 27-jan-2014 Mon 27-jan-2014 Wed 29-jan-2014
Thu 01-jan-2015 Mon 26-jan-2015 Mon 26-jan-2015 Thu 29-jan-2015
Fri 01-jan-2016 Mon 25-jan-2016 Mon 25-jan-2016 Fri 29-jan-2016
Sun 01-jan-2017 Mon 30-jan-2017 Mon 30-jan-2017 Sun 29-jan-2017
Mon 01-jan-2018 Mon 29-jan-2018 Mon 29-jan-2018 Mon 29-jan-2018
Tue 01-jan-2019 Mon 28-jan-2019 Mon 28-jan-2019 Tue 29-jan-2019
Wed 01-jan-2020 Mon 27-jan-2020 Mon 27-jan-2020 Wed 29-jan-2020
Fri 01-jan-2021 Mon 25-jan-2021 Mon 25-jan-2021 Fri 29-jan-2021
Sat 01-jan-2022 Mon 31-jan-2022 Mon 31-jan-2022 Sat 29-jan-2022

20 rows selected.

but i don't know what the ISO year "means" frankly

      Rating:
      5
  October 29, 2003
Reviewer:
      A reader
Hi Tom,

Wouldn't trunc('my_date', 'd')+1 be appropriate here?

Please comment.

          Followup:

only if the first day of your week is sunday -- believe that it'll depend on
your NLS settings.

October 29, 2003 Jerry -- Thanks for the question regarding "Edit
      Long Raw Column", version 8.1.7

      You Asked
We have a PeopleSoft HR, and have a table with a long raw column containing HTML
text.  I am a member of the Church of Lob, but PSFT are atheists, so I can't
change the table.

We have some incorrect data in much of the HTML text that I need to basically
pull out and edit.

I looked at your site on Friday, and found a couple of examples of using
utl_raw.cast_to_varchar2 (going on memory here).

I think what I got from your site (which I refer to rather than the
documentation), is that I'd have to do a PL/SQL loop, cast the long raw to a
varchar2 variable, write a text parsing routine, cast back to raw, and load into
the database?

Could you confirm that?  Any quick examples of pulling long raw out and putting
it back in that you have laying around would be helpful.

Thanks much.

The HTML text in the column wouldn't exceed 32k in size.  They are fairly simple
web pages.

      and we said...
Yes, it would look like this.  where I concatenate 'hello ' on -- you would be
fixing it up.  the last block just shows the update worked.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int, y long raw );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2          l_raw long raw;
  3  begin
  4          l_raw := utl_raw.cast_to_raw( rpad('*',32000,'*') );
  5          insert into t values ( 1, l_raw );
  6  end;
  7  /

PL/SQL procedure successfully completed.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2          l_text long;
  3          l_raw  long raw;
  4  begin
  5          for x in ( select rowid r, t.* from t )
  6          loop
  7                  l_text := utl_raw.cast_to_varchar2(x.y);
  8                  dbms_output.put_line( 'it is ' || length(l_text) || ' bytes
longs');
  9                  l_text := 'hello ' || l_text;
 10                  l_raw := utl_raw.cast_to_raw( l_text );
 11                  update t set y = l_raw where rowid = x.r;
 12          end loop;
 13  end;
 14  /
it is 32000 bytes longs

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2          for x in ( select t.* from t )
  3          loop
  4                  dbms_output.put_line( 'it is ' ||
length(utl_raw.cast_to_varchar2(x.y)) || ' bytes longs');
  5          end loop;
  6  end;
  7  /
it is 32006 bytes longs

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating:
      5
  October 29, 2003
Reviewer:
      A reader
Hi Tom,
I've an old appliaction which uses LONG RAW datatype to display image from
VISUAL BASIC program..We've to insert new image in this column...

How can I do that??
(We are migrating to 9.2 within 3-4 month meanwhile i've to
use long raw..)

Thanks..

          Followup:

you would write VB code to read the image and write it a chunk at a time to the
long raw.

October 29, 2003 to -- Thanks for the question regarding "Batch
      processing and OLTP - Design and developement guide", version 9.2.0

      You Asked
I'd like to ask you if there are rules to observe when one starts to design and
develop two different types of applications - batch and OLTP.
Will you please point out the mains characteristics of each type and how to
avoid basic traps when designing and developping them ?

Thank you.

      and we said...
The rules are the same

o design to perform
o benchmark yourself constantly
o have SPECIFIED, clear performance criteria to meet (not just "go fast")
o benchmark yourself constantly
o develop on a full database
o see #2
o test to scale (if you are going to have 1,000 users -- you better test more
then
  1 user )
o see #2

There really aren't that many differences in my experience.  OLTP has many batch
characteristics, batch has OLTP.

The same design principles you should be using on ANY system apply here.
Review &
      Followup

      Rating: 5
PL/SQL as ETL
      tools  January 02, 2003
Reviewer:  Ajeet
       from Cincinnati,oh,usa
Tom - Most of the companies now prefer to use ETL tools such as Informatica,
Data Stage etc.I found that PL/SQL has alot more features and performance
features than these tool.what is your opinion.why do you think that these ETL
tools are becoming so popular.Do we have an ETL tool like informatica in
oracle.(I have never used oracle warehouse builder -- may be there is an ETL
tool).

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6749198829295

      Rating: 5
What is
      OLTP?  October 29, 2003
Reviewer:
      A reader
Tom,
I keep reading about characteristics of OLTP and DW databases and how they are
different. My question here is that we have system with 25 million to 35 million
rows in each table. A smallest query with 2 table join has the potential for
producing more than 120k rows in the output. Can such a system be characterized
as OLTP by any definition however remote?
Thanks.

          Followup:

120k rows does not fit in with my definition of OLTP, nope.

October 29, 2003 Saurabh -- Thanks for the question regarding "How
      to estimate temp tablespace space needed for a 380 Gigs table", version
      9.2.0.4.0

      You Asked
Hello Tom,

 I have a 4.2 Billion row table with 8 columns
 I need to group by 5 columns.
 This table is 380 Gigs.

  how much space in temp tablespace will I need.
  Will it be close to 380 X 2 = 760 Gigs.
  I need to use parallel query ( degree 8 or 16 probably )

 Thanks for your advice.
Saurabh

      and we said...
in 9iR2 as you have, this is pretty straightforward.

big_table@ORA920> delete from plan_table;

4 rows deleted.

big_table@ORA920>
big_table@ORA920> explain plan for
  2  select object_name, owner, object_id, created, last_ddl_time, count(*)
  3    from big_table
  4   group by object_name, owner, object_id, created, last_ddl_time;

Explained.

big_table@ORA920>
big_table@ORA920> select * from table( dbms_xplan.display );

PLAN_TABLE_OUTPUT
-------------------

------------------------------------------------------------------------------
|Id| Operation            |  Name       | Rows  | Bytes |TempSpc| Cost (%CPU)|
------------------------------------------------------------------------------
|0 | SELECT STATEMENT     |             |  1833K|    85M|       | 21993   (6)|
|1 |  SORT GROUP BY       |             |  1833K|    85M|   225M| 21993   (6)|
|2 |   TABLE ACCESS FULL  | BIG_TABLE   |  1833K|    85M|       |  2417  (12)|
------------------------------------------------------------------------------

8 rows selected.

the plan as shown by dbms_xplan includes this.

Bear in mind, it is not a function so much of the number of rows in the source
table -- more the number of distinct values of the group by columns!

Review &
      Followup

      Rating: 4
Why no values
      ?  September 20, 2003
Reviewer:
      A reader
It gives me no values for tempspc for scott schema i just analyzed.I have listed
2 queries.
The first query is user objects table it does not even give me those rows ,bytes
,cost .tempspc columns is not there

The second query gives vlues for rows bytes cost but no tempspc
SQL> conn scott/tiger@orcl92
Connected.
SQL>  explain plan for
  2       select object_name,  object_id, created, last_ddl_time, count(*)
  3     from user_objects
  4   group by object_name,  object_id, created, last_ddl_time ;

Explained.

SQL> set line 2000
SQL> select * from table( dbms_xplan.display );

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
-------------------------------

---------------------------------------------------------------------------------

| Id  | Operation                       |  Name         | Rows  | Bytes | Cost
|
---------------------------------------------------------------------------------

|   0 | SELECT STATEMENT                |               |       |       |
|
|   1 |  SORT GROUP BY                  |               |       |       |
|
|   2 |   VIEW                          | USER_OBJECTS  |       |       |
|
|   3 |    UNION-ALL                    |               |       |       |
|
|*  4 |     FILTER                      |               |       |       |
|
|   5 |      TABLE ACCESS BY INDEX ROWID| OBJ$          |       |       |
|
|*  6 |       INDEX RANGE SCAN          | I_OBJ2        |       |       |
|

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
-------------------------------
|*  7 |      TABLE ACCESS BY INDEX ROWID| IND$          |       |       |
|
|*  8 |       INDEX UNIQUE SCAN         | I_IND1        |       |       |
|
|   9 |     TABLE ACCESS BY INDEX ROWID | LINK$         |       |       |
|
|* 10 |      INDEX RANGE SCAN           | I_LINK1       |       |       |
|
---------------------------------------------------------------------------------

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

   4 - filter("SYS_ALIAS_1"."TYPE#"<>1 AND "SYS_ALIAS_1"."TYPE#"<>10 OR "SYS_A
              LIAS_1"."TYPE#"=1 AND  (SELECT 1 FROM "SYS"."IND$" "I" WHERE "I"

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
-------------------------------
              ."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)
   6 - access("SYS_ALIAS_1"."OWNER#"=:B1 AND "SYS_ALIAS_1"."LINKNAME" IS NULL)
       filter("SYS_ALIAS_1"."LINKNAME" IS NULL AND "SYS_ALIAS_1"."NAME"<>'_def
              ault_auditing_options_' AND "SYS_ALIAS_1"."NAME"<>'_NEXT_OBJECT')
   7 - 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)
   8 - access("I"."OBJ#"=:B1)
  10 - access("L"."OWNER#"=:B1)

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
-------------------------------
Note: rule based optimization

34 rows selected.

SQL>   explain plan for
  2       select deptno, count(*)
  3     from emp
  4   group by deptno ;

Explained.

SQL> select * from table( dbms_xplan.display );

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
---

--------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
--------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             |     3 |     9 |     4 |
|   1 |  SORT GROUP BY       |             |     3 |     9 |     4 |
|   2 |   TABLE ACCESS FULL  | EMP         |    14 |    42 |     2 |
--------------------------------------------------------------------

Note: cpu costing is off

10 rows selected.

Thanx Sir

          Followup:

well, with 14 rows -- how much temp space were you expecting to have to use???

dbms_xplan is very cool.  it shows you only relevant information.  that just
means you won't NEED any temp.  Consider --

scott@ORA920> analyze table emp compute statistics;
Table analyzed.

scott@ORA920> delete from plan_table;
3 rows deleted.

scott@ORA920> explain plan for
  2  select deptno, count(*) from emp group by deptno;
Explained.

scott@ORA920> select * from table( dbms_xplan.display );

PLAN_TABLE_OUTPUT

-------------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost (%CPU)|
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             |     3 |     6 |     4  (50)|
|   1 |  SORT GROUP BY       |             |     3 |     6 |     4  (50)|
|   2 |   TABLE ACCESS FULL  | EMP         |    14 |    28 |     3  (34)|
-------------------------------------------------------------------------

8 rows selected.

for 14 rows with 3 distinct values of deptno -- we won't need any temp.  lets
move the goalposts however -- lots of rows, lots of values:

scott@ORA920> exec dbms_stats.set_table_stats( user, 'EMP', numrows => 2000000,
numblks => 1000000 );

PL/SQL procedure successfully completed.

scott@ORA920> exec dbms_stats.set_column_stats( user, 'EMP', 'DEPTNO', distcnt
=> 2000000 );

PL/SQL procedure successfully completed.

scott@ORA920> delete from plan_table;

3 rows deleted.

scott@ORA920>
scott@ORA920> explain plan for
  2  select deptno, count(*) from emp group by deptno;

Explained.

scott@ORA920>
scott@ORA920> select * from table( dbms_xplan.display );

PLAN_TABLE_OUTPUT
-------------------------------------

-------------------------------------------------------------------------------
| Id  | Operation            |  Name     | Rows  | Bytes |TempSpc| Cost (%CPU)|
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |           |  2000K|  3906K|       |   104K  (4)|
|   1 |  SORT GROUP BY       |           |  2000K|  3906K|    30M|   104K  (4)|
|   2 |   TABLE ACCESS FULL  | EMP       |  2000K|  3906K|       | 99312   (4)|
-------------------------------------------------------------------------------

8 rows selected.

when we need to count 2,000,000 deptno's, the optimizer sings a different song
and says "you know, we'll be swapping some of this out"

      Rating: 5
it
      works.  September 21, 2003
Reviewer:
      Saurabh  from Bayarea, ca
Tom, we were able to group by our 4.2 billion row
table. wew !!

Your advice on how to calculate temp space needed was extremely helpfull.
It certainly works..

allthough a minor point to notice is
that the plan did said it needed 798 G,
but we used about 705 Gigs only.
( I was monitoring our temp space usuage very
  carefully while the process was running. )
also it did end up using about 2 times the table size ?

The final table created is 520K.

Thanks for the extreme fast response and
for teaching me something new.

==

after analyzing the 4.2 billion row table with 0.001 percent.

SQL> select count(1) from plan_table;

  COUNT(1)
----------
         0

SQL>  explain plan for
  2    select level1, level2,
  3       level3,
  4         level4,
  5         level5,
  6             domain,
  7         count(distinct machineid) as all_dom, count(distinct us_mid) as
us_dom,
  8             count(distinct intl_mid) as intl_dom
  9             from q_temp
 10  group by level1, level2, level3, level4, level5, domain
 11  /

Explained.

SQL> set linesize 1000
SQL>
SQL> /

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
--------------------------------------

---------------------------------------------------------------------------------
------------------------
| Id  | Operation            |  Name       | Rows  | Bytes |TempSpc| Cost  |  TQ
   |IN-OUT| PQ Distrib |
---------------------------------------------------------------------------------
------------------------
|   0 | SELECT STATEMENT     |             |  4264M|   297G|       |  2537K|
   |      |            |
|   1 |  SORT GROUP BY       |             |  4264M|   297G|   798G|  2537K|
45,01  | P->S | QC (RAND)  |
|   2 |   TABLE ACCESS FULL  | Q_TEMP      |  4264M|   297G|       | 47144 |
45,00  | P->P | HASH       |
---------------------------------------------------------------------------------
------------------------

Note: cpu costing is off

10 rows selected.

SQL>

          Followup:

it is an estimate remember!  it is as close as it can estimate.... you needed
about 90% of what it estimated, thats pretty good.

just like the row count estimates -- I'll bet there was a little less then
4,264,000 rows....

      Rating: 5
typo
      fixed.  September 21, 2003
Reviewer:
      A reader
When I said
"the final table created was 57K"
I meant it had 57K rows.

      Rating: 5
Thanx for the
      no values ans  September 21, 2003
Reviewer:
      A reader
You are Great

      Rating: 3
How to get the
      current Temp Tblspace Size ?  September 21, 2003

Reviewer:  A reader

          Followup:

query the data dictionary?

dba_temp_files is the most likely view, if you've set things up correctly.

      Rating: 3
Temp Space
      Requirement not comming up  October 27, 2003

Reviewer:  Devanshi Parikh  from NJ
I have a query which involves 3 tables with the first table having 214 million
rows, the second and the third are extreamly small with 1000 rows. When the
query is executed, temp space is being used, as seen from v$sort_usage. I tried
using the dbms_xplan.display function, which does give me the explain plan, but
not the TEMP space column. My sort_area_size is 120M. Also, the
sort_area_retained_size is 16M. Are there any special parameters that need to be
set in order to use this correctly ?

Below is the query

explain plan for
SELECT /*+ FULL(pr)  PARALLEL(pr, 4)  */
       pr.Business_Unit_GL, xaxn.EY_Xaxn_Group,
       to_date('31-OCT-2003', 'DD-MON-YYYY HH24:MI:SS'),
       cal.End_Dt,
    to_date('01-JAN-1998', 'DD-MON-YYYY HH24:MI:SS'),
       'PRJ', 'D', xaxn.Descr,0,
       sum(decode(xaxn.EY_Amt_Qty_flg,
                  'A', pr.Resource_Amount,
                  'Q', pr.Resource_Quantity, 0)) Sum,
       count(pr.rowid) Count,
       'N', ''
FROM   ps_proj_resource pr, ps_ey_xaxn_grps xaxn,    ps_cal_detp_tbl cal
WHERE  pr.Analysis_Type = xaxn.Analysis_Type
and    pr.Resource_Type = xaxn.Resource_Type
and    pr.Resource_Category LIKE xaxn.Resource_Category
and    pr.Resource_Sub_Cat  LIKE xaxn.Resource_Sub_Cat
and    pr.Resource_Sub_Cat <> 'RES'
and    pr.System_Source LIKE xaxn.System_Source
and    pr.Accounting_Dt BETWEEN
    cal.Begin_Dt and cal.End_Dt
and    pr.System_Source NOT IN ('IW1','ADJ','BAL','SUM')
and    cal.SetID = 'GLOBE'
and    cal.Calendar_ID = 'PE'
group by pr.Business_Unit_GL, xaxn.EY_Xaxn_Group, cal.End_Dt,
         xaxn.Descr;

ps_proj_resource pr - Table with 214 million rows
ps_ey_xaxn_grps - Table with 60 rows
ps_cal_detp_tbl - Table with 1000 rows

select * from table(dbms_xplan.display);

SQL> /

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
------------------------

| Id  | Operation             |  Name             | Rows  | Bytes | Cost  |  TQ
   |IN-OUT| PQ Distrib |

--------------------------------------------------------------------------------
------------------------

|   0 | SELECT STATEMENT      |                   |  3021 |   286K|   521K|

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
   |      |            |

|   1 |  SORT GROUP BY        |                   |  3021 |   286K|   521K| 14,0
3  | P->S | QC (RAND)  |

|   2 |   MERGE JOIN          |                   |  3021 |   286K|   521K| 14,0
2  | P->P | HASH       |

|   3 |    SORT JOIN          |                   |  4679 |   347K|   521K| 14,0
2  | PCWP |            |

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
|*  4 |     HASH JOIN         |                   |  4679 |   347K|   521K| 14,0
2  | PCWP |            |

|   5 |      TABLE ACCESS FULL| PS_EY_XAXN_GRPS   |    65 |  2340 |     2 | 14,0
0  | S->P | BROADCAST  |

|*  6 |      TABLE ACCESS FULL| PS_PROJ_RESOURCE  |   170M|  6502M|   521K| 14,0
2  | PCWP |            |

|*  7 |    FILTER             |                   |       |       |       | 14,0
2  | PCWP |            |

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

|*  8 |     SORT JOIN         |                   |       |       |       | 14,0
2  | PCWP |            |

|*  9 |      INDEX RANGE SCAN | PSCCAL_DETP_TBL   |   258 |  5418 |     3 | 14,0
1  | S->P | BROADCAST  |

--------------------------------------------------------------------------------
------------------------

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

   4 - access("PR"."ANALYSIS_TYPE"="XAXN"."ANALYSIS_TYPE" AND
              "PR"."RESOURCE_TYPE"="XAXN"."RESOURCE_TYPE")
       filter("PR"."RESOURCE_CATEGORY" LIKE "XAXN"."RESOURCE_CATEGORY" AND "PR".
"RESOURCE_SUB_CAT" LIKE

              "XAXN"."RESOURCE_SUB_CAT" AND "PR"."SYSTEM_SOURCE" LIKE "XAXN"."SY
STEM_SOURCE")

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
   6 - filter("PR"."RESOURCE_SUB_CAT"<>'RES' AND "PR"."SYSTEM_SOURCE"<>'IW1' AND

              "PR"."SYSTEM_SOURCE"<>'ADJ' AND "PR"."SYSTEM_SOURCE"<>'BAL' AND "P
R"."SYSTEM_SOURCE"<>'SUM')

   7 - filter("PR"."ACCOUNTING_DT"<="CAL"."END_DT")
   8 - access("PR"."ACCOUNTING_DT">="CAL"."BEGIN_DT")
       filter("PR"."ACCOUNTING_DT">="CAL"."BEGIN_DT")
   9 - access("CAL"."SETID"='GLOBE' AND "CAL"."CALENDAR_ID"='PE')

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Note: cpu costing is off

31 rows selected.

Thanks,

          Followup:

see what it says without the parallel operations.

      Rating: 3
Tried using
      SERIAL  October 27, 2003
Reviewer:  Devanshi
      Parikh  from NJ
I think that is what you meant. The plan for the same statement was displayed
using the select below. Also, I tried taking out the PARALLEL hint and going
thro' the same steps again, but again, the same output. No mention of TEMP
space.

SQL> select * from table(dbms_xplan.display('PLAN_TABLE',null,'SERIAL'));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

---------------------------------------------------------------------------
| Id  | Operation             |  Name             | Rows  | Bytes | Cost  |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                   |  3021 |   286K|   521K|
|   1 |  SORT GROUP BY        |                   |  3021 |   286K|   521K|
|   2 |   MERGE JOIN          |                   |  3021 |   286K|   521K|
|   3 |    SORT JOIN          |                   |  4679 |   347K|   521K|
|*  4 |     HASH JOIN         |                   |  4679 |   347K|   521K|
|   5 |      TABLE ACCESS FULL| PS_EY_XAXN_GRPS   |    65 |  2340 |     2 |
|*  6 |      TABLE ACCESS FULL| PS_PROJ_RESOURCE  |   170M|  6502M|   521K|

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
|*  7 |    FILTER             |                   |       |       |       |
|*  8 |     SORT JOIN         |                   |       |       |       |
|*  9 |      INDEX RANGE SCAN | PSCCAL_DETP_TBL   |   258 |  5418 |     3 |
---------------------------------------------------------------------------

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

   4 - access("PR"."ANALYSIS_TYPE"="XAXN"."ANALYSIS_TYPE" AND
              "PR"."RESOURCE_TYPE"="XAXN"."RESOURCE_TYPE")
       filter("PR"."RESOURCE_CATEGORY" LIKE "XAXN"."RESOURCE_CATEGORY" AND

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
              "PR"."RESOURCE_SUB_CAT" LIKE "XAXN"."RESOURCE_SUB_CAT" AND
              "PR"."SYSTEM_SOURCE" LIKE "XAXN"."SYSTEM_SOURCE")
   6 - filter("PR"."RESOURCE_SUB_CAT"<>'RES' AND
              "PR"."SYSTEM_SOURCE"<>'IW1' AND "PR"."SYSTEM_SOURCE"<>'ADJ' AND
              "PR"."SYSTEM_SOURCE"<>'BAL' AND "PR"."SYSTEM_SOURCE"<>'SUM')
   7 - filter("PR"."ACCOUNTING_DT"<="CAL"."END_DT")
   8 - access("PR"."ACCOUNTING_DT">="CAL"."BEGIN_DT")
       filter("PR"."ACCOUNTING_DT">="CAL"."BEGIN_DT")
   9 - access("CAL"."SETID"='GLOBE' AND "CAL"."CALENDAR_ID"='PE')

Note: cpu costing is off

33 rows selected.

Thanks so much

          Followup:

no, i meant -- get a serial plan generated.  not a parallel one.

      Rating: 3
Output without
      PARALLEL Hint :  October 28, 2003
Reviewer:
      Devanshi Parikh  from NJ
Here is my SELECT :

explain plan for
SELECT /*+ FULL(pr) */
       pr.Business_Unit_GL, xaxn.EY_Xaxn_Group,
       to_date('31-OCT-2003', 'DD-MON-YYYY HH24:MI:SS'),
       cal.End_Dt,
    to_date('01-JAN-1998', 'DD-MON-YYYY HH24:MI:SS'),
       'PRJ', 'D', xaxn.Descr,0,
       sum(decode(xaxn.EY_Amt_Qty_flg,
                  'A', pr.Resource_Amount,
                  'Q', pr.Resource_Quantity, 0)) Sum,
       count(pr.rowid) Count,
       'N', ''
FROM   ps_proj_resource pr, ps_ey_xaxn_grps xaxn, ps_cal_detp_tbl cal
WHERE  pr.Analysis_Type = xaxn.Analysis_Type
and    pr.Resource_Type = xaxn.Resource_Type
and    pr.Resource_Category LIKE xaxn.Resource_Category
and    pr.Resource_Sub_Cat  LIKE xaxn.Resource_Sub_Cat
and    pr.Resource_Sub_Cat <> 'RES'
and    pr.System_Source LIKE xaxn.System_Source
and    pr.Accounting_Dt BETWEEN
    cal.Begin_Dt and cal.End_Dt
and    pr.System_Source NOT IN ('IW1','ADJ','BAL','SUM')
and    cal.SetID = 'GLOBE'
and    cal.Calendar_ID = 'PE'
group by pr.Business_Unit_GL, xaxn.EY_Xaxn_Group, cal.End_Dt,
         xaxn.Descr;

select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

---------------------------------------------------------------------------
| Id  | Operation             |  Name             | Rows  | Bytes | Cost  |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                   |  3021 |   286K|  2086K|
|   1 |  SORT GROUP BY        |                   |  3021 |   286K|  2086K|
|   2 |   MERGE JOIN          |                   |  3021 |   286K|  2086K|
|   3 |    SORT JOIN          |                   |  4679 |   347K|  2085K|
|*  4 |     HASH JOIN         |                   |  4679 |   347K|  2085K|
|   5 |      TABLE ACCESS FULL| PS_EY_XAXN_GRPS   |    65 |  2340 |     2 |
|*  6 |      TABLE ACCESS FULL| PS_PROJ_RESOURCE  |   170M|  6502M|  2085K|

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
|*  7 |    FILTER             |                   |       |       |       |
|*  8 |     SORT JOIN         |                   |       |       |       |
|*  9 |      INDEX RANGE SCAN | PSCCAL_DETP_TBL   |   258 |  5418 |     3 |
---------------------------------------------------------------------------

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

   4 - access("PR"."ANALYSIS_TYPE"="XAXN"."ANALYSIS_TYPE" AND
              "PR"."RESOURCE_TYPE"="XAXN"."RESOURCE_TYPE")
       filter("PR"."RESOURCE_CATEGORY" LIKE "XAXN"."RESOURCE_CATEGORY" AND

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
              "PR"."RESOURCE_SUB_CAT" LIKE "XAXN"."RESOURCE_SUB_CAT" AND
              "PR"."SYSTEM_SOURCE" LIKE "XAXN"."SYSTEM_SOURCE")
   6 - filter("PR"."RESOURCE_SUB_CAT"<>'RES' AND
              "PR"."SYSTEM_SOURCE"<>'IW1' AND "PR"."SYSTEM_SOURCE"<>'ADJ' AND
              "PR"."SYSTEM_SOURCE"<>'BAL' AND "PR"."SYSTEM_SOURCE"<>'SUM')
   7 - filter("PR"."ACCOUNTING_DT"<="CAL"."END_DT")
   8 - access("PR"."ACCOUNTING_DT">="CAL"."BEGIN_DT")
       filter("PR"."ACCOUNTING_DT">="CAL"."BEGIN_DT")
   9 - access("CAL"."SETID"='GLOBE' AND "CAL"."CALENDAR_ID"='PE')

Note: cpu costing is off

33 rows selected.

Thanks,

          Followup:

whats your exact version?

      Rating: 3
The version is
      9.2.0.3  October 29, 2003
Reviewer:  Devanshi
      Parikh  from NJ
select banner from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.3.0 - 64bit Production
PL/SQL Release 9.2.0.3.0 - Production
CORE    9.2.0.3.0       Production
TNS for HPUX: Version 9.2.0.3.0 - Production
NLSRTL Version 9.2.0.3.0 - Production

Thanks,

          Followup:

well, looking at the plan:

---------------------------------------------------------------------------
| Id  | Operation             |  Name             | Rows  | Bytes | Cost  |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                   |  3021 |   286K|  2086K|
|   1 |  SORT GROUP BY        |                   |  3021 |   286K|  2086K|
|   2 |   MERGE JOIN          |                   |  3021 |   286K|  2086K|
|   3 |    SORT JOIN          |                   |  4679 |   347K|  2085K|
|*  4 |     HASH JOIN         |                   |  4679 |   347K|  2085K|
|   5 |      TABLE ACCESS FULL| PS_EY_XAXN_GRPS   |    65 |  2340 |     2 |
|*  6 |      TABLE ACCESS FULL| PS_PROJ_RESOURCE  |   170M|  6502M|  2085K|

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
|*  7 |    FILTER             |                   |       |       |       |
|*  8 |     SORT JOIN         |                   |       |       |       |
|*  9 |      INDEX RANGE SCAN | PSCCAL_DETP_TBL   |   258 |  5418 |     3 |
---------------------------------------------------------------------------

it won't NEED any sort.  the hash join will hash ps_ey_xaxn_grps into memory..
It'll full scan ps_proj_resource and hash lookup into ps_ey_xaxn_grps -- and
output only 4,679 rows to be sorted.  This is thought to be 347k of data.  that
first into your 120m nicely.

then, you have a sort of 258 rows

then merged.

According to the stats, there isn't any temp needed.

So, now, you need to validate for us the estimate cardinalities.  The
information in the explain plan is valid if and only if the estimated cards
there are accurrate.

      Rating:
      4
  October 29, 2003
Reviewer:  Devanshi
      Parikh  from NJ
Please let me know what additional information should I provide to better
understand the dbms_xplan functionality. If I understood correctly, did you mean
to say that the actual execution of the query could be "different" than what it
shows ?

Thanks,

          Followup:

no, what i meant was:

o do you expect 65 rows from PS_EY_XAXN_GRPS
o do you expect 170m rows from PS_PROJ_RESOURCE after applying the filter in
step #6
o do you expect the resulting HASH join between the two to result in 4,769 rows
after applying filter #4

are those numbers "reasonable" or "way off"

      Rating:
      5
  October 29, 2003
Reviewer:
      A reader
I should be able to calculate those numbers for the filters tomorrow, since we
are going to run a backup soon on the database.
Just a curious question, (I have not tried this yet, but I will when I get a
window )
Say, if I were to change the sort_area_size for just my session to something
like 64K, and then run the explain plan, can I say for sure that I "will" be
sorting now ? In that case , the TEMP SPACE column should show up, right ?

Thanks,

          Followup:

i do,

ops$tkyte@ORA920LAP> alter session set workarea_size_policy = manual;

Session altered.

ops$tkyte@ORA920LAP> alter session set sort_area_size = 100000000;

Session altered.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> delete from plan_table;

10 rows deleted.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> explain plan for
  2  SELECT /*+ ORDERED USE_HASH( pr xaxn ) FULL(pr) */
  3         pr.Business_Unit_GL, xaxn.EY_Xaxn_Group,
  4         to_date('31-OCT-2003', 'DD-MON-YYYY HH24:MI:SS'),
  5         cal.End_Dt,
  6      to_date('01-JAN-1998', 'DD-MON-YYYY HH24:MI:SS'),
  7         'PRJ', 'D', xaxn.Descr,0,
  8         sum(decode(xaxn.EY_Amt_Qty_flg,
  9                    'A', pr.Resource_Amount,
 10                    'Q', pr.Resource_Quantity, 0)) Sum,
 11         count(pr.rowid) Count,
 12         'N', ''
 13  FROM
 14  ps_ey_xaxn_grps xaxn,
 15  ps_proj_resource pr ,
 16  ps_cal_detp_tbl cal
 17  WHERE  pr.Analysis_Type = xaxn.Analysis_Type
 18  and    pr.Resource_Type = xaxn.Resource_Type
 19  and    pr.Resource_Category LIKE xaxn.Resource_Category
 20  and    pr.Resource_Sub_Cat  LIKE xaxn.Resource_Sub_Cat
 21  -- and       pr.Resource_Sub_Cat <> 'RES'
 22  and    pr.System_Source LIKE xaxn.System_Source
 23  and    pr.Accounting_Dt BETWEEN
 24      cal.Begin_Dt and cal.End_Dt
 25  -- and       pr.System_Source NOT IN ('IW1','ADJ','BAL','SUM')
 26  --and       cal.SetID = 'GLOBE'
 27  --and       cal.Calendar_ID = 'PE'
 28  group by pr.Business_Unit_GL, xaxn.EY_Xaxn_Group, cal.End_Dt,
 29           xaxn.Descr;

Explained.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
--------------------------------------------------

---------------------------------------------------------------------------
| Id  | Operation             |  Name             | Rows  | Bytes | Cost  |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                   |   211 | 33338 |   158 |
|   1 |  SORT GROUP BY        |                   |   211 | 33338 |   158 |
|   2 |   MERGE JOIN          |                   |   211 | 33338 |   122 |
|   3 |    SORT JOIN          |                   |   327 | 45780 |    77 |
|*  4 |     HASH JOIN         |                   |   327 | 45780 |    37 |
|   5 |      TABLE ACCESS FULL| PS_EY_XAXN_GRPS   |    65 |  3640 |    11 |
|   6 |      TABLE ACCESS FULL| PS_PROJ_RESOURCE  |   170M|    13G|    11 |
|*  7 |    FILTER             |                   |       |       |       |
|*  8 |     SORT JOIN         |                   |       |       |       |
|   9 |      TABLE ACCESS FULL| PS_CAL_DETP_TBL   |   258 |  4644 |    11 |
---------------------------------------------------------------------------

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

   4 - access("PR"."ANALYSIS_TYPE"="XAXN"."ANALYSIS_TYPE" AND
              "PR"."RESOURCE_TYPE"="XAXN"."RESOURCE_TYPE")
       filter("PR"."RESOURCE_CATEGORY" LIKE "XAXN"."RESOURCE_CATEGORY" AND
              "PR"."RESOURCE_SUB_CAT" LIKE "XAXN"."RESOURCE_SUB_CAT" AND
              "PR"."SYSTEM_SOURCE" LIKE "XAXN"."SYSTEM_SOURCE")
   7 - filter("PR"."ACCOUNTING_DT"<="CAL"."END_DT")
   8 - access("PR"."ACCOUNTING_DT">="CAL"."BEGIN_DT")
       filter("PR"."ACCOUNTING_DT">="CAL"."BEGIN_DT")

Note: cpu costing is off

29 rows selected.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> alter session set sort_area_size = 32767;

Session altered.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> delete from plan_table;

10 rows deleted.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> explain plan for
  2  SELECT /*+ ORDERED USE_HASH( pr xaxn ) FULL(pr) */
  3         pr.Business_Unit_GL, xaxn.EY_Xaxn_Group,
  4         to_date('31-OCT-2003', 'DD-MON-YYYY HH24:MI:SS'),
  5         cal.End_Dt,
  6      to_date('01-JAN-1998', 'DD-MON-YYYY HH24:MI:SS'),
  7         'PRJ', 'D', xaxn.Descr,0,
  8         sum(decode(xaxn.EY_Amt_Qty_flg,
  9                    'A', pr.Resource_Amount,
 10                    'Q', pr.Resource_Quantity, 0)) Sum,
 11         count(pr.rowid) Count,
 12         'N', ''
 13  FROM
 14  ps_ey_xaxn_grps xaxn,
 15  ps_proj_resource pr ,
 16  ps_cal_detp_tbl cal
 17  WHERE  pr.Analysis_Type = xaxn.Analysis_Type
 18  and    pr.Resource_Type = xaxn.Resource_Type
 19  and    pr.Resource_Category LIKE xaxn.Resource_Category
 20  and    pr.Resource_Sub_Cat  LIKE xaxn.Resource_Sub_Cat
 21  -- and       pr.Resource_Sub_Cat <> 'RES'
 22  and    pr.System_Source LIKE xaxn.System_Source
 23  and    pr.Accounting_Dt BETWEEN
 24      cal.Begin_Dt and cal.End_Dt
 25  -- and       pr.System_Source NOT IN ('IW1','ADJ','BAL','SUM')
 26  --and       cal.SetID = 'GLOBE'
 27  --and       cal.Calendar_ID = 'PE'
 28  group by pr.Business_Unit_GL, xaxn.EY_Xaxn_Group, cal.End_Dt,
 29           xaxn.Descr;

Explained.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
--------------------------------------------------

---------------------------------------------------------------------------------
--
| Id  | Operation             |  Name             | Rows  | Bytes |TempSpc| Cost
 |
---------------------------------------------------------------------------------
--
|   0 | SELECT STATEMENT      |                   |   211 | 33338 |       |
445K|
|   1 |  SORT GROUP BY        |                   |   211 | 33338 |       |
445K|
|   2 |   MERGE JOIN          |                   |   211 | 33338 |       |
445K|
|   3 |    SORT JOIN          |                   |   327 | 45780 |   120K|
445K|
|*  4 |     HASH JOIN         |                   |   327 | 45780 |       |
445K|
|   5 |      TABLE ACCESS FULL| PS_EY_XAXN_GRPS   |    65 |  3640 |       |
11 |
|   6 |      TABLE ACCESS FULL| PS_PROJ_RESOURCE  |   170M|    13G|       |
11 |
|*  7 |    FILTER             |                   |       |       |       |
 |
|*  8 |     SORT JOIN         |                   |       |       |       |
 |
|   9 |      TABLE ACCESS FULL| PS_CAL_DETP_TBL   |   258 |  4644 |       |
11 |
---------------------------------------------------------------------------------
--

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

   4 - access("PR"."ANALYSIS_TYPE"="XAXN"."ANALYSIS_TYPE" AND
              "PR"."RESOURCE_TYPE"="XAXN"."RESOURCE_TYPE")
       filter("PR"."RESOURCE_CATEGORY" LIKE "XAXN"."RESOURCE_CATEGORY" AND
              "PR"."RESOURCE_SUB_CAT" LIKE "XAXN"."RESOURCE_SUB_CAT" AND
"PR"."SYSTEM_SOURCE"
              LIKE "XAXN"."SYSTEM_SOURCE")
   7 - filter("PR"."ACCOUNTING_DT"<="CAL"."END_DT")
   8 - access("PR"."ACCOUNTING_DT">="CAL"."BEGIN_DT")
       filter("PR"."ACCOUNTING_DT">="CAL"."BEGIN_DT")

Note: cpu costing is off

29 rows selected.

ops$tkyte@ORA920LAP>

but note, you probably want to be looking at pga_aggregate_target and not
sort_area_size in 9iR2

      Rating: 5
A related
      question  October 29, 2003
Reviewer:  Devanshi
      Parikh  from NJ
I should be able to calculate those numbers for the filters tomorrow, since we
are going to run a backup soon on the database.
Just a curious question, (I have not tried this yet, but I will when I get a
window )
Say, if I were to change the sort_area_size for just my session to something
like 64K, and then run the explain plan, can I say for sure that I "will" be
sorting now ? In that case , the TEMP SPACE column should show up, right ?

Thanks,

      Rating: 5
Excellent
      !!!!  October 30, 2003
Reviewer:  Devanshi
      Parikh  from NJ
Tom,
   You were absolutely right ! The cardinalities were quite close to the actual
numbers that I got by executing those filters separately. The query did not use
any (physical) temp space. Also, to prove that it will use temp, if the
sort_area_size is decreased. Here is what I did.

alter session set sort_area_size=32768;
REM Ran the explain plan of the query above.
REM Here is what I got.
select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
---

| Id  | Operation             |  Name             | Rows  | Bytes |TempSpc| Cost
  |

--------------------------------------------------------------------------------
---

|   0 | SELECT STATEMENT      |                   |  3115 |   346K|       |  235

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
7K|

|   1 |  SORT GROUP BY        |                   |  3115 |   346K|   808K|  235
7K|

|   2 |   MERGE JOIN          |                   |  3115 |   346K|       |  235
6K|

|   3 |    SORT JOIN          |                   |  4825 |   419K|  1112K|  235
6K|

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
|*  4 |     HASH JOIN         |                   |  4825 |   419K|       |  235
6K|

|   5 |      TABLE ACCESS FULL| PS_EY_XAXN_GRPS   |    67 |  2881 |       |
2 |

|*  6 |      TABLE ACCESS FULL| PS_PROJ_RESOURCE  |   170M|  7480M|       |  208
6K|

|*  7 |    FILTER             |                   |       |       |       |
  |

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

|*  8 |     SORT JOIN         |                   |       |       |       |
  |

|*  9 |      INDEX RANGE SCAN | PSCCAL_DETP_TBL   |   258 |  6450 |       |
3 |

--------------------------------------------------------------------------------
---

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

   4 - access("PR"."ANALYSIS_TYPE"="XAXN"."ANALYSIS_TYPE" AND
              "PR"."RESOURCE_TYPE"="XAXN"."RESOURCE_TYPE")
       filter("PR"."RESOURCE_CATEGORY" LIKE "XAXN"."RESOURCE_CATEGORY" AND
              "PR"."RESOURCE_SUB_CAT" LIKE "XAXN"."RESOURCE_SUB_CAT" AND "PR"."S
YSTEM_SOURCE"

              LIKE "XAXN"."SYSTEM_SOURCE")
   6 - filter("PR"."RESOURCE_SUB_CAT"<>'RES' AND "PR"."SYSTEM_SOURCE"<>'IW1' AND

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

              "PR"."SYSTEM_SOURCE"<>'ADJ' AND "PR"."SYSTEM_SOURCE"<>'BAL' AND
              "PR"."SYSTEM_SOURCE"<>'SUM')
   7 - filter("PR"."ACCOUNTING_DT"<="CAL"."END_DT")
   8 - access("PR"."ACCOUNTING_DT">="CAL"."BEGIN_DT")
       filter("PR"."ACCOUNTING_DT">="CAL"."BEGIN_DT")
   9 - access("CAL"."SETID"='GLOBE' AND "CAL"."CALENDAR_ID"='PE')

Note: cpu costing is off

33 rows selected.

Now, I see the TEMP SPACE column show up. This is an extreamly useful feature,
since it allows us DBA's to (almost) accurately predict the TEMP space usage for
huge queries like this.

Thanks,

October 29, 2003 AMIT -- Thanks for the question regarding "Is it
      possible to encrypt PL/SQL procedure?", version 8

      You Asked
We want to encrypt PL/SQL procedures for an application software
based on Oracle 8 or Oracle 9. This is to restrict the user of
the application and also the DBA of the RDBMS or anybody who
uses the application or the database to go through the PL/SQL
procedures, functions or packages which are relevant to the said
application.

      and we said...
see

http://technet.oracle.com/doc/oracle8i_816/appdev.816/a77069/c_wrap.htm#1859
...

Review &
      Followup

      Rating: 2
Solution
      confusing  January 07, 2002
Reviewer:  Bob
      Maggio  from USA/New York
I am not sure I understand the solution. It looks like I wrap the source code
file? as in wrap iname=/dir/func1.sql oname=/dir/func1_wrap.sql
But I am still able to view both files. Do I then run those files in oracle and
does it create the functions a different way? It didn't seem to.

          Followup:

you run func1_wrap.sql, you deliver func1_wrap.sql.

You keep func1.sql in case you ever need to modify the source code.

Take this for example:

ops$tkyte@ORA817DEV.US.ORACLE.COM> @test
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure p
  2  as
  3  begin
  4          dbms_output.put_line( 'Hello world' );
  5  end;
  6  /
Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select text from user_source where name= 'P';

TEXT
---------------------------------------------------------------------------------
--------------------------------------------------
procedure p
as
begin
        dbms_output.put_line( 'Hello world' );
end;

now you see it...

ops$tkyte@ORA817DEV.US.ORACLE.COM> host wrap iname=test.sql oname=test_wrap.sql

PL/SQL Wrapper: Release 8.1.7.2.0 - Production on Mon Jan 07 12:44:21 2002
Copyright (c) Oracle Corporation 1993, 2000.  All Rights Reserved.
Processing test.sql to test_wrap.sql

ops$tkyte@ORA817DEV.US.ORACLE.COM> @test_wrap.sql
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure p wrapped
  2  0
  3  abcd
  4  abcd
  5  abcd
  6  abcd
  7  abcd
  8  abcd
  9  abcd
 10  abcd
 11  abcd
 12  abcd
 13  abcd
 14  abcd
 15  abcd
 16  abcd
 17  abcd
 18  3
 19  7
 20  8106000
 21  1
 22  4
 23  0
 24  4
 25  2 :e:
 26  1P:
 27  1DBMS_OUTPUT:
 28  1PUT_LINE:
 29  1Hello world:
 30  0
 31
...
 86  /

Procedure created.

and now you don't

ops$tkyte@ORA817DEV.US.ORACLE.COM> select text from user_source where name =
'P';

TEXT
---------------------------------------------------------------------------------
--------------------------------------------------
procedure p wrapped
0
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
3
7
8106000
1
4
0
4
2 :e:
1P:
1DBMS_OUTPUT:
1PUT_LINE:
1Hello world:
0
...

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

      Rating: 5
Error on
      wrap  January 08, 2002
Reviewer:  Bob Maggio
       from USA/New York
I must have missed the error msg the first time, since it created the function,
abeit with errors.
PL/SQL Wrapper: Release 8.1.7.0.0 - Production on Tue Jan 08 09:20:37 2002

Copyright (c) Oracle Corporation 1993, 2000.  All Rights Reserved.

Processing encrypt.sql to encrypt.plb
PSU(103,1,1,56):Encountered the symbol "" when expecting one of the following:

   . @ % ; is authid deterministic parallel_enable as character

PL/SQL Wrapper error: Compilation error(s) for:
CREATE OR REPLACE function encrypt
Outputting source and continuing.

It seems I created the source on my NT machine, FTP'd the source to the UNIX
box, and ran it there. The wrap program seemd to choke on the windows return
chars. I removed them via VI and all is well. Thanks.

      Rating: 5
Nice
      example  January 08, 2002
Reviewer:  Daryl
       from Texas
Bob, when ftp-ing, set your transfer type to text using the "asc" command (if
using command line ftp) or checking the correct box if using wsftp or Coffee Cup
ftp. The default transfer mode is usually binary, which preserves end-of-line
characters exactly. The text mode will transfer the source using the correct
end-of-line translation. From Windows to UNIX, this means the carriage returns
(^M) at the end of each line will be removed.

      Rating: 4
This is
      great  January 09, 2002
Reviewer:  Mohammed
      Ahmed
Can we encrypt "sqlplus" scripts also?

          Followup:

create a procedure, wrap it, call that from sqlplus.

No, sqlplus scripts are "clear text", only packages/procedures/functions can be
wrapped.

      Rating: 4
Wrapping
      Procedure  March 13, 2003
Reviewer:  Anil
       from Malaysia
I created a procedure , i was able to wrap the code. I logged in thru OEM as
sysdba , i am not able to see the code. Upto this level it is fine.

Now i executed the procedure, the procedure takes around 15 minutes to complete.
My problem is that if i login thru OEM , go to Database-->Instance-->sessions.
Now if i go to the particular user who is executing the procedure , and click on
tab SQL, the full code is visible.

Is there any wayout to hide the code when the procedure is executing.

Thanks and regards,

          Followup:

the full PLSQL or just the SQL query?

there is no way to hide SQL -- no way.

      Rating: 3
???
      PSU(103,1,211,46) ???  July 12, 2003
Reviewer:
      j.
hi tom, I tried to wrap a package for the very first time (source extracted from
db), but got an error.

it seems to me as if some of those SQL features integrated in PL/SQL since 9i
cause this to happen (in my case the "over" of an analytical function in a SQL
statement within the code).

do one have to avoid these new features of 9i in order to get source code
wrapped, or did I just do anything wrong?

          Followup:

please file a tar with support for something like that, it would be a "bug"

      Rating: 5
"there is no
      way to hide SQL -- no way"  July 12, 2003

Reviewer:  John  from MA
I've always wondered about the effects of wrapping.
Apparently the reuse of Sql is safe, but what about performance? (I know I
should try it an see but I can't even generate a stored procedure where I'm at,
no v$'s nothing)
Does wrapping create "p-code" so it actually runs faster?
or just scrambles it in some way causing delay in unscrabbling?
Would 9i's feature of compiling source make this feature obsolete?
10i?

          Followup:

wrapping code will not affect performance one way or the other -- it simply
obscures the procedural code from prying eyes.

      Rating: 5
I still see the
      code  July 12, 2003
Reviewer:  Reader

Tom,

SQL> select text from user_source where name = 'P';

TEXT
---------------------------------------------------------------------
procedure p wrapped
0
abcd
abcd
abcd
abcd
abcd
9200000
1
4
0
4
2 :e:
1P:
1DBMS_OUTPUT: <--------------------
1PUT_LINE:    <--------------------
1Hello world: <--------------------
0

0
0
14
2
0 9a b4 55 6a :2 a0 6b 6e
....
....

          Followup:

so, do a non-trival function.  do something real world.  See if you can tell me
what this does not by running it -- but by reading the wrapped code.  it is all
of 3 lines of procedural code:

create or replace function f wrapped
0
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
3
8
9200000
1
4
0
9
2 :e:
1FUNCTION:
1F:
1P_NUMBER:
1NUMBER:
1RETURN:
1=:
11:
1*:
1-:
0

0
0
31
2
0 a0 8d 8f a0 b0 3d b4
:2 a0 2c 6a a0 7e 51 b4 2e
5a a0 51 65 b7 :2 a0 7e :2 a0
7e 51 b4 2e a5 b b4 2e
65 b7 :2 19 3c b7 a4 b1 11
68 4f 1d 17 b5
31
2
0 3 7 23 1f 1e 2b 1b
30 34 38 3c 40 44 47 4a
4b 50 53 57 5a 5e 60 64
68 6b 6f 73 76 79 7a 7f
80 82 83 88 8c 8e 92 96
99 9b 9f a1 ad b1 b3 b4
bd
31
2
0 1 a e 1a :2 e c 23
2a :2 1 7 10 12 :2 10 5 1b
22 1b 16 7 e 17 19 1b
23 24 :2 1b :2 19 :2 e 7 :4 2 :9 1

31
4
0 :b 1 :a 4 :f 5
:3 4 :2 3 :7 1
bf
4
:3 0 1 :3 0 2
:a 0 2c 1 :7 0
5 :2 0 3 4
:3 0 3 :7 0 5
4 :3 0 5 :3 0
4 :3 0 7 9
0 2c 2 a
:2 0 3 :3 0 6
:2 0 7 :2 0 9
d f :3 0 10
:2 0 5 :3 0 7
:2 0 13 :2 0 15
c 25 5 :3 0
3 :3 0 8 :2 0
2 :3 0 3 :3 0
9 :2 0 7 :2 0
e 1b 1d :3 0
11 19 1f 13
18 21 :3 0 22
:2 0 24 16 26
11 15 0 27
0 24 0 27
18 0 28 1b
2b :3 0 2b 0
2b 2a 28 29
:6 0 2c :2 0 2
a 2b 2f :3 0
2e 2c 30 :8 0

1d
4
:3 0 1 3 1
6 1 e 2
c e 1 14
2 1a 1c 1
1e 2 17 20
1 23 2 25
26 1 27
1
4
0
2f
0
1
14
1
2
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0
2 0 1
3 1 0
0

/

      Rating: 4
Decrypt
      Procedure  July 13, 2003
Reviewer:  vinodhps
       from India,chennai
Hi Tom,
Is it possible to decrypt the encrypted procedure.
In our software we got a recuirment like.

Tender process
--------------
Qutoation file will be sealed and submitted to purchase department, after some
day tender will be opened in front of the vendors Department head, Accounts
Head.. as the tender value may be of several millions. Tender mostly will be for
L1(low amount ).So the data here is crucial even DBA or developer or no one
should open that or should not see that. only at that particualr day it has to
be opend and then it should not be hiden.

Is there any provision in Oracle as i tried to know about that but still now i
could not able to get any solution for that , If you have experianced any of
this kind of situation , that will be Appriciated.

Thank you tom,

          Followup:

nope, once wrapped -- always wrapped.

but I don't know what that has to do with a "quotation file" -- whatever that
is.

      Rating: 4
Decrypt
      Data  July 14, 2003
Reviewer:  vinodhps
       from India, chennai
Hi Tom,
Thanks for you quick responce , my requirement regarding Quotation is that...
what the manual process is, physically they get quotation form which will be
sealed. Few day tender will be as a open day.. until that time they use to get
sealed quotation form from  the vendor , then finally they will open in front of
every one.Then the lowest quoated person will be given the contract.

Is it possible to automat this process using oracle Tom.
Thanking you

          Followup:

then the quotation needs to be encrypted -- not the plsql code.
dbms_obfuscation_toolkit can help you maybe

      Rating:
      4
thanks  July 16, 2003
Reviewer:
      vinodhps  from India, Chennai
Hi tom,
i have not yet read dbms_obfuscation_toolkit in your book,
i will try that.
thanks

      Rating: 4
Find unwrapped
      pkg...  September 10, 2003
Reviewer:  Robert
       from CT
I am exploring the guts of Marvel (HTML DB)...

Can I use SQL to show whick packages are NOT wrapped ?

Thanks

          Followup:

i would not bother, you are looking at the demo, not the real thing.  it'll be
different and wrapped in real life.

you just have to peek at the first 1 or 2 lines in dba_source to see if
something is wrapped.

      Rating: 5
Unwrapped
      Source  September 10, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Robert,

Here is an ad hoc query that you may be able to use to find unwrapped source.

select   owner,
         name,
         type
from     all_source
where    type in ('PACKAGE', 'PACKAGE BODY', 'PROCEDURE', 'FUNCTION')
and      line = 1
and      instr(text,'wrapped') < 1
order by owner, name, type;

I have not tested the query or examined what impact it may have on the system.
Probably needs tweaked as well.

HTH,

- Mark

      Rating: 5
HTML DB is
      wrapped for a reason  September 23, 2003
Reviewer:
      Tyler D. Muth  from Reston, VA
HTML DB (formerly Project Marvel) is wrapped to protect Oracle's intellectual
property.  All of the package bodies are wrapped.

If you would like to talk about how it works in a conceptual manner I would be
happy to answer any questions you may have as it helps in developing HTML DB
applications.

I liken this to asking how to get the source for Multi-Version Read Consistency.
 Of course Oracle is not going to distribute the source, but the idea is covered
in the concepts guide.  I would go as far as saying that unless you work for a
company trying to build a competing technology, the source would be a
meaningless waste of time.  Tom has NEVER viewed the source code for any aspect
of the database, as it doesn't really matter how it is written, but more how it
functions.

Anyway, as I stated before, I would be happy to answer any conceptual questions
on this topic.  I am a developer on the HTML DB team and familiar with every
aspect of it's architecture.

      Rating: 4
Why I can still
      see the source code in all_source?   October 14, 2003

Reviewer:  A reader
The wrapped script is generated all right, but ALL_SOURCE is still showing the
unwrapped code!

      Rating: 4
Sorry, please
      ignore above question. I forgot to recompile using the wrapped
      code.  October 14, 2003
Reviewer:
      A reader

      Rating: 5
Comparing
      wrapped functions  October 29, 2003
Reviewer:
      A reader
Hi Tom,
If I generete a wrapped function now, and
in 8 month after, I generate again that function.

1. Should both be the same?
2. Even if I migrate from a previous release for example 9.01 to 9.2, and both
were generate by different releases?

Thank you.

          Followup:

1) if you are using the same release -- sure.

2) no, you could expect the generated wrapped code to be different, no reason
why it could not/would not be.

      Rating: 5
:)
      Thanks  October 29, 2003
Reviewer:
      A reader

      Rating: 4
How to See
      USER-2 's Package Body being in User-1  October 29, 2003

Reviewer:  BIJAY K Pusty  from MI USA
Hi Tom
I am asking here another question.
There are 2 Users[ USER1, USER2] in one INstance WHSP.

USER1 has a package U1_PKG1
USER2 wanted to just see the Package Body i.e. U1_PKG1 not connecting to USER1
rather than being in USER2.

Is it possible ??

Thanks
--Bijay

          Followup:

only if user2 has

o execute any procedure
o access to dba_source

otherwise, all_source says "don't show the body, just the spec", thats the way
it was designed.

October 30, 2003 -- Thanks for the question regarding
      "recordlength vs buffer in exp/imp", version 8.1.6

      You Asked
I don't quite understand recordlength in export, what it use for
and also what 's bufsiz

      and we said...
recordlength is the IO unit, what is read/written to/from the dmp file.

buffer size is a RAM buffer.  when imp reads from the dmp file, it fills a
buffer and then array inserts that many rows.  If the buffersize is 500k and
each record in some table is a max of 5k in width, IMP will array insert 100
records at a time.  Likewise, EXP would array fetch 100 records at a time.
Review &
      Followup

      Rating: 5
why set it high
      or low?  June 22, 2002
Reviewer:  Doug
       from Danbury, CT USA
Tom - I usually take the default setting.  What are the advantages of setting it
high or low? (buffer).. If one has plenty of memory, why not just set it real
high? Wouldn't it speed things up?

          Followup:

It'll speed things up -- upto a point.  The bigger the buffer, the more we read
from the dmp file before inserting into the database.  If you stuff too much
stuff into the database in one call, you may find yourself waiting for the log
buffer to flush or buffer space to be freed up.  If you trickle it in in a
smaller batch, lgwr will empty the buffer cache in the background (you won't
wait) and dbwr might checkpoint in the background (you won't have to wait).
Like virtually everything, there are diminishing returns after a while (and your
mileage will vary, there is no "perfect number" for all)

      Rating:
      3
Reader  June 24, 2002
Reviewer:
      A reader
"
you may find yourself waiting for the log
buffer to flush or buffer space to be freed up.
"

Does Oracle stop processing during a log buffer
flush. I thought Oracle keep prcocessing during
log buffer flush

          Followup:

If you need to put stuff in the log buffer, and the log buffer is full, you will
wait until there is sufficient space in the log buffer.  Oracle doesn't stop --
but you might have to wait whilst we make room.

      Rating: 3
How about for
      export  October 21, 2002
Reviewer:  Sanjaya
      Balasuriya  from Sri Lanka
Tom,

How the buffer size affect the export process.
For fetching data, no logs are created. (I think I'm correct)
So buffer won't be full and no waiting. Isn't it ?

So bigger buffer should give better performence.

Thanks.

-San

          Followup:

...
Likewise, EXP would array fetch 100 records at a time.

....

that is how it would affect EXP.  I don't understand the reference to "logs"
(although redo log is generally generated, block cleanouts cause that).

I don't understand "so buffer won't be full...."

a larger buffer will give better performance to a point, everything has
dimishing marginal returns.

      Rating: 4
import from NT
      to UNIX  October 27, 2003
Reviewer:  Victor
       from NJ
Tom,

I started to look at this site only recently and I learned a lot from you and
your readers. Thank you.

My question is I need to move Oracle database from NT to Solaris. Documentation
says "The RECORDLENGTH parameter is necessary when you must transfer the export
file to another operating system that uses a different default value."

How to set this parameter correctly in EXP(NT) and IMP(UNIX)?

Thanks,

Victor

          Followup:

don't worry about it, not relevant there.

don't set it.

(but don't forget to BINARY ftp the dmp file!!!!)

      Rating: 4
import from NT
      to UNIX   October 28, 2003
Reviewer:  Victor
       from NJ
Thank you, Tom

      Rating: 4
imp from
      several .dmp files  October 29, 2003
Reviewer:
      Victor  from NJ
Tom,

I created full export in NT on 5 dmp files,
then I zipped them,
FTPed in binary mode to UNIX running Solaris,
unziped them back in dmp files
and issued IMP using fromusers/tousers options.

When import switched to a 2-nd dmp file I got SEGMENT fault
error. Could you, please, explain how to deal with this?

Thanks, Victor

          Followup:

contact support for something like this.

October 30, 2003 -- Thanks for the question regarding "event
      triggers", version 8.1.7.4

      You Asked
Hi Tom

I am trying to write some triggers for some security purposes so I have been
reading chapter 13 in Application Development Fundamentals from Oracle 8.1.7
documentation (working with system events)

I have some questions regarding System Defined Event Attributes. There is this
table 13-1 in the chapter about system events but it doesnt say what can each
event attribute have. For example ora_sysevent, what does this do? From some
examples I can see it can be LOGON, ASSOCIATE STATISTICS etc. What else can it
be? Does it include all client events listed in table 13-2 and 13-3 in the
chapter?

Then I tried to create a trigger to track who truncated the table. I created
following table

create table event_log
(log_date varchar2(36),
 event varchar2(36),
 username varchar2(36),
 owner varchar2(36),
 object_name varchar2(36),
 object_type varchar2(36),
 dbname varchar2(36)
);

create or replace trigger truncate_trg
after truncate
as
  insert into event_log
  values(sysdate,
         ora_sysevent,
         ora_login_user,
         ora_dict_obj_owner,
         ora_dict_obj_name,
         ora_dict_obj_type,
         ora_database_name)
end;
/

it complains about

ERROR at line 3:
ORA-00969: missing ON keyword

I guess it means I have to code

after truncate ON table_x

But is there a way to track any table, or tables owned by a user? Or I have to
create a trigger for each table? I havent been able to find examples in the
documentation. Only a couple

Cheers

      and we said...
ora_sysevent returns the name of the event that fired the trigger (eg: TRUNCATE
in your case).

That table *does* say what each and every attribute function returns.  I mean,
look at:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76939/adg14evt.htm#1002767
it tells you in the 3rd column "returns the event name that fired the trigger"

You are missing the "ON" with that truncate, ON SCHEMA for example:

ops$tkyte@ORA920.US.ORACLE.COM> create or replace trigger xxx
  2  before truncate on schema
  3  begin
  4    dbms_output.put_line( 'Hey, you truncated!' );
  5  end;
  6  /

Trigger created.

ops$tkyte@ORA920.US.ORACLE.COM> truncate table emp;

Table truncated.

ops$tkyte@ORA920.US.ORACLE.COM> exec null;
Hey, you truncated!

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating: 5
scope of event
      triggers  October 15, 2002
Reviewer:
      A reader
Hi

If I want to create a event trigger for several schemas, so instead of

after create on username.schema

how can I do this? I tried user1.schema, user2.schema but it gives errors

          Followup:

after create on database
begin
   if ( ora_dict_obj_owner in ( 'USER1', 'USER2', 'USER3' ) )
   then
       .....
   end if;
end;

      Rating: 5
automatic grant
      privileges  October 15, 2002
Reviewer:
      A reader
Hi

I have several schemas, I am trying to create triggers and procedures so
whenever I create a new table in any of the schemas then grants of select,
insert, delete priv would get granted to a user automatically.

For example my super user is ORADBA, I was thinking to create a trigger in
ORADBA´s schema which calls a procedure that insert the new object names into
temporary table, then from another procedure that reads this temp table and
grant necessary privileges to necessary users

I cannot achieve this because trigger is triggered whenever a create statement
is run and I think it runs under user ORADBA and since ORADBA is not owner of
any of new tables he cannot grant privs to other users. If I use authid in
procedures called by the trigger then it´s useless too since current_user would
still be ORADBA

Is there any smarter way or I have to create several triggers under each schema?

I also read 210693.1 How to Automate Grant Operations When New Objects Are
Created in a SCHEMA/DATABASE on Metalink, there it uses a job to do the grant
job, it does not say why. Is there any specific reasons to do so?
(I am using 8.1.7.4)

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:646423863863
for a less complex way -- it uses a JOB so you DON'T HAVE to write any code at
all except for the very simple trigger.

I don't like that note, it is far too complex of an implementation.  See my link
-- short, to the point -- easy to understand.

      Rating: 4
regarding
      execute immediate  October 15, 2002
Reviewer:
      A reader
Hi

I checked the link you provided, there is this code

'execute immediate "grant select on ' ||
                                     ora_dict_obj_name ||
                                    ' to scott";'

which equals to

execute immediate 'grant select on  to scott'

Dont we have to add BEGIN END here?

Also I have a problem, I normally create tables for several schemas, does that
mean I have to create a trigger under each schema?

          Followup:

the entire thing is:

create or replace trigger do_grant
after CREATE on schema
declare
    l_str varchar2(255);
    l_job number;
begin
    if ( ora_dict_obj_type = 'TABLE' )
    then
        l_str := 'execute immediate "grant select on ' ||
                                     ora_dict_obj_name ||
                                    ' to scott";';
        dbms_job.submit( l_job, replace(l_str,'"','''') );
    end if;
end;
/

and no, you need not BEGIN/END the code.  DBMS_JOB wraps an anonymous block
around it for you.

You would WANT to create the trigger in EACH schema to avoid any issues with
giving the GRANT.  If you created ONE trigger, it would run as THAT person with
THEIR privs.  They may well not be able to GRANT on that object.

      Rating:
      5
Reader  May 16, 2003
Reviewer:
      A reader
Is there an event ORA_SPACE_ERROR_INFO that takes
6 arguments. I could not find in the search of
"Oracle9i Application Developer's Guide - Fundamentals"

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96590/adg14evt.htm#1005307

      Rating: 5
under what user
      is event trigger fired?  October 27, 2003

Reviewer:  A reader
Hi

I have a question regarding privileges using event triggers.

I created a table under scott, scott has only connect, resumable and resource
roles.
create table suspend_op (suspend_date date,
                         sid number,
                         error_msg varchar2(256)
                         username varchar2(30))
                         ;

I then created a trigger under system
create or replace trigger trg_after_suspend
after suspend on database
declare
   l_sid number;
   l_error_msg varchar2(256);
begin
   select session_id, error_msg
     into l_sid, l_error_msg
     from dba_resumable
    where name = 'TEST';
   insert into scott.suspend_op values (sysdate, l_sid, l_error_msg,user);
end;
/

I then logon as scott and ran this
alter session enable resumable name 'TEST';

create table x as select * from dba_source; -- this provokes unable to extend
temp segment by 128 in tablespace USERS

I then queried my suspend_op table

SUSPEND_DATE        SID ERROR_MSG
        USERNAME
------------ ----------
---------------------------------------------------------------- --------

28-OCT-03            17 ORA-01536: space quota exceeded for tablespace 'USERS'
        LSC

My question is under what user is this trigger fired? Under SCOTT but since it´s
a trigger scott becomes user SYSTEM so he ca query dba_resumable?

          Followup:

triggers run with definers rights.

that trigger runs "as system"

Now, that said -- STOP DOING THAT.  sys and system are our accounts.  do not use
them, leave them be.  don't put code into them unless you are explicitly
directed to (eg: you must place password verification routines in the SYS
schema).  Do not use them for testing, for trying, for your code.

      Rating: 4
Why it use the
      job to do the grant  October 29, 2003
Reviewer:
      Den Chang  from NY, NY
I still don't understand why the trigger requires to use dbms_job to grant the
priv. to users.  Is there a way we get it done immediately without submitting a
job?

          Followup:

DDL commits.

you cannot commit in the trigger.

even if the trigger used an autonomous transaction to perform its work it
wouldn't "work".  The atran would not SEE the object since the transaction that
is creating the object has not yet committed.

You need to use a job.

      Rating: 5
Where to get
      this from ?  October 29, 2003
Reviewer:
      A reader
Hello Sir,
   Can we query any DB view or table to a list of these
ora_  dict , obj , owner etc etc.
Thanx

          Followup:

you read the documentation -- thats where functions are listed.

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96590/adg14evt.htm#1000872

October 30, 2003 Ak -- Thanks for the question regarding
      "dba_free_space", version 8.1.5

      You Asked
Hi tom,
  I see dba_free_space_collesped,dba_free_space_collesped_temp1,temp2
In data dictionary ...
what is this temp1,2 is all about ??

Thanks

Regards
   Ak

      and we said...
Ignore them -- there are no public synonyms for them -- they are simply views we
use to create other views.  If you look at the definition of:

create or replace view DBA_FREE_SPACE_COALESCED (
TABLESPACE_NAME
,TOTAL_EXTENTS
,EXTENTS_COALESCED
,PERCENT_EXTENTS_COALESCED
,TOTAL_BYTES
,BYTES_COALESCED
,TOTAL_BLOCKS
,BLOCKS_COALESCED
,PERCENT_BLOCKS_COALESCED
) as
select name,total_extents, extents_coalesced,
       extents_coalesced/total_extents*100,total_blocks*c.blocksize,
       blocks_coalesced*c.blocksize, total_blocks, blocks_coalesced,
       blocks_coalesced/total_blocks*100
from DBA_FREE_SPACE_COALESCED_TMP1 a, DBA_FREE_SPACE_COALESCED_TMP2 b,
      sys.ts$ c
where a.ts#=b.ts# and a.ts#=c.ts#
union all
select name, total_extents, total_extents, 100, total_blocks*c.blocksize,
       total_blocks*c.blocksize, total_blocks, total_blocks, 100
from DBA_FREE_SPACE_COALESCED_TMP3 b, sys.ts$ c
where b.ts# = c.ts#

you can see it just queries the 3 TMP views.
Review &
      Followup

      Rating:
      5
  October 30, 2003
Reviewer:  Rory
      Concepcion  from Philippines
Hi Tom,

Hope you can help me out. I'm trying to make a shell script
that will connect to all our databases and check the percent of free space per
tablespace or percent of used space per tablespace. Either will do. As long as I
can see that whether only 10% is the remaining free space for a certain
tablespace. I did the following.

1. created a user "MONITOR"
2. granted select on dba_free_space and dba_data_files to monitor.
3. logged in as monitor and created the following views:

## FREE SPACE PER TABLESPACE VIEW

create or replace view free_view2
as select tablespace_name "TBLSPC", SUM(BYTES) "FREE"
from dba_free_space
group by tablespace_name;

## SIZE OF TABLESPACE

create or replace view tablespace_size
as select tablespace_name "TBLSPC", sum(bytes) "SIZE"
from dba_data_files
group by tablespace_name;

4. Now when the shell script connects to the database using the schema "MONITOR"
I execute the following sql.

## USED SPACE IN PERCENTAGE

SELECT F.TBLSPC, TO_CHAR((FREE/SIZE)*100, '999999.99')
FROM FREE_VIEW2 F, TABLESPACE_SIZE S
WHERE F.TBLSPC = C.TBLSPC;

I want this script to give me the space used up per tablespace in percentage.
But the script has the following error.

SELECT F.TBLSPC, TO_CHAR((FREE/SIZE)*100, '999999.99')
                               *
ERROR at line 1:
ORA-00936: missing expression

Please help me out on this. Thanks

          Followup:

why why why would you not use the tools that come with the database that already
do this and much much much more???

size is a keyword

SQL> SELECT F.TBLSPC, TO_CHAR((FREE/"SIZE")*100, '999999.99')
  2  FROM FREE_VIEW2 F, TABLESPACE_SIZE S
  3  WHERE F.TBLSPC = S.TBLSPC;

(change c.tblspc to s.tblspc as well)

      Rating:
      5
good  October 30, 2003
Reviewer:
      A reader

October 30, 2003 wallacel -- Thanks for the question regarding
      "star query", version 8.1.6

      You Asked
Tom:

I have some question regarding star query.

one book i read says that
select /*+ star */   is equivalent to
select /*+ordered use_NL(table) index(table concat_idx) */

and then it gives me an example.
select /* ordered use_nl(orderfacts) index(orderfacts of_idx) */
empno,ename,deptno,itemno
from orders,emp,dept,orderfacts
where orderfacts.empno=emp.empno
and orderfacts.deptno=dept.deptno
and orderfacts.ordno=orders.ordno
and orderfacts.ordno=317548813;

is same as
select /* star */
empno,ename,deptno,itemno
from orders,emp,dept,orderfacts
where orderfacts.empno=emp.empno
and orderfacts.deptno=dept.deptno
and orderfacts.ordno=orders.ordno
and orderfacts.ordno=317548813;

In this example ,a concatenated index exists on the empno,deptno
and ordno columns of the orderfacts table.
THE ORDERS TABLE IS JOINED WITH THE EMP TABLE,THE RESULT
SET IS MERGED WITH THE DEPT TABLE AND THEN THE RESULT SET IS
JOINED WITH THE CONCATENATED INDEX FOR ORDERFACTS USING
NESTED LOOPS.

QUESTION:
1.since orders,emp,dept share no common column, the result set
would be
(#rows of orders)*(#rows of emp)*(#rows of dept)
am i right?

2.if it is true , then the result set would be pretty huge,
why not use ordno index of
orderfacts?(orderfacts.ordno=317548813) maybe will get rid of
most of
rows.

Regards

      and we said...
That setup is not a very good example of a /*+ star */ query.
Consider this definition:

....
One type of data warehouse design centers around what is known
as a star schema, which is characterized by one or more very
large fact tables that contain the primary information in the
data warehouse and a number of much smaller dimension tables
(or lookup tables), each of which contains information about the
entries for a particular attribute in the fact table.
....

from the designing and tuning for performance.

But we will do some tricky things with these as well with Star
Transformations

http://technet.oracle.com/doc/oracle8i_816/server.816/a76994/schemas.htm#11975
these work best in the any of the following circumstances:

The number of dimensions is large

The fact table is sparse

There are queries where not all dimension tables have
    constraining predicates

In your case, the number of dimensions is large.
Review &
      Followup

      Rating: 4
Star Schema
        April 23, 2002
Reviewer:  Ron  from
      USA
Tom,
Can you explain about the Star Schema and how it works?
In a book I read a fact table can not have its own primary key and the PK of the
fact table would be the composite of all the dimensions.is it true? please
explain.

Thanks a lot.

          Followup:

see

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76994/logical.htm#97642

...
Creating a New Fact Table

You must define a fact table for each star schema. A fact table typically has
two types of columns: those that contain facts, and those that are foreign keys
to dimension tables. From a modeling standpoint, the primary key of the fact
table is usually a composite key that is made up of all of its foreign keys; in
the physical data warehouse, the data warehouse administrator may or may not
choose to create this primary key explicitly.
.......

It is not a requirement but rather the somewhat logical outcome.  It can have
some other primary key.

      Rating:
      4
  January 14, 2003
Reviewer:
      A reader
From your reply above, it is not clear how Oracle identifies a table as a fact
table or a dimension table. I am aware that the star transformation access path
will extrace information from fact tables using bitmap indexes and then join it
to dimension tables. My question is how does Oracle recognize a table as fact or
dimension. My understanding is that it is based on the FK relationships. It
appeared to me that you were saying something contradictory to that above.
Please clarify. Thanks as always.

          Followup:

where t.x = t1.a
  and t.y = t2.b
  and t.z = t3.c

look at the predicate -- also, there will be primary keys on t1, t2, t3 and t
will have a foreign key to each of t1, t2, t3.

the fact table, its the guy in the middle with fkeys to the surrounding tables.

      Rating: 4
Is Bit Map
      Index is mandatory for Star Schema  January 14, 2003

Reviewer:  Lakshmi Narasimhan  from Dubai
Hi Tom

I am doing a  small data mart with star schema.
i am using oracle 8.1.7.

I have established proper foreign key relation ship with all  the dimension
tables.  Is it enough or is it mandatory to create bit map index on each key in
fact table which is referring dimensions in DW design.

          Followup:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76994/schemas.htm#11292
you should create the bitmaps

      Rating:
      4
  January 15, 2003
Reviewer:
      A reader
So are you saying it is mandatory to have FK relationships set in a star schema?
I thought you said in the above post that it is optional? If the constraints are
not defined, then will the star queries work?

Also, I read about a bug in metalink (bug no. 2716265, ver. 9.2.0) which makes
star queries, fine grained security and materialized views not work together
(all 3 features can not be used together it seems - it causes ORA-600 error).
Curious to know if there are any exceptions to this?  Anybody is using them
together? We are working on implementing FGS on our datawarehouse and this came
as a damper). Do you have any comments on this bug?

          Followup:

where did i say that?

that bug doesn't say MV's cannot be used -- in fact, it really isn't saying that
FGAC cannot be used either.  What it says is "hey, if we end up with this query,
we get an ora-600".   if you were to have typed that query in without FGAC (but
using the FGAC predicate that would have been added), you may well see the same
effect.

This is the exception -- not the other way around.

      Rating:
      3
  January 15, 2003
Reviewer:
      A reader
"the fact table, its the guy in the middle with fkeys to the surrounding
tables."

So if we don't define the fkeys then how will Oracle identify the guy in the
middle?

          Followup:

read the predicate -- it is obvious.

from orders,emp,dept,orderfacts
where orderfacts.empno=emp.empno
and orderfacts.deptno=dept.deptno
and orderfacts.ordno=orders.ordno
and orderfacts.ordno=317548813;

orderfacts -> emp
orderfacts -> dept
orderfacts -> orders

forget that it has "facts" in the name.  You have a guy in the middle --
orderfacts -- with all of these tables surrounding it in the predicate.

But -- you will be using fkeys if the database is to fully recognize them -- you
will use fkeys, you will use bitmaps -- you will in short read what the link
above points to!

      Rating: 5
star
      optimization vs. star transformation  May 26, 2003

Reviewer:  bob from NJ
Tom,

It says in the 9i R2 docs that a concat index on the foreign keys is sufficient
for the star query. However, the 9i R2 DW guide says you need the bitmap
indexes.

You indicated both as well.

is there a difference in the literature between the star optimization and the
star transformation.  ie the optimization can happen with the concat btree
index, but the transformation needs individual bitmaps?

from the 9i docs:
A star join uses a join of foreign keys in a fact table to the corresponding
primary keys in dimension tables. The fact table normally has a concatenated
index on the foreign key columns to facilitate this type of join, or it has a
separate bitmap index on each foreign key column.

Can you clarify this for me and can you also give an indication of the likely
improvement that can come with a star query transformation using bitmap indexes
as opposed to the traditional access methods?

          Followup:

check out

http://www.dbazine.com/jlewis6.html
Jonathan has a nice way of explaining things.

      Rating: 4
other than star
      schema do we have anything else to fine tune the BIG
      table  October 30, 2003
Reviewer:  a rader
       from CA
Thanks for explaining the STAR schema.

i have another question, say for example we have one fact table with huge data
and all the reports and day to day inserts are based on this fact table.
due to its size all the reports and other operations are getting affected.
now we are thinking of creating some lookup table for reports and as a
concequence all the reports should be modified to refer to new lookup tables.

The question is "is there any other way other than fine tunning the fact table
to scheive this goal".
i just want to make sure iam not missing anything which is available
Thanks in advance
a reader

          Followup:

read about materialized views.  you can create rollups/aggregates of the big
table and let the reports use them transparently -- just like INDEXES (eg: you
can tune the reports instantly using MV's, just like adding an index can
sometimes be used to tune)

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96520/mv.htm#721

October 30, 2003 Alan -- Thanks for the question regarding "When
      is it safe to compress archive log files?", version 8.1.7

      You Asked
Hi Tom,

Periodically I need to compress my archived log files to save space.  My script
compresses all the files except the last one (the one with the highest sequence
number).  My assumption is that ARCH might still be working on that last one but
it must be finished with all the previous ones.

Now I've heard that this might not be true.

So, when is it safe to compress archive log files?

Thanks,

Alan

      and we said...
You need to check the data dictionary to see if we are done with it (they are
correct -- whoever said your way might not be "safe".  we can have multiple ARCH
processes going, each writing to different files)

You need to query v$archived_log to see if we actually finished that file (see
the archived column).

What I do is generate a script that queries the v$ tables after reading the arch
directory -- to see what files it is safe to compress:

#!/bin/ksh

export DUPLEX_LOC=/arch2/oradata/ora8i/arch

echo set heading off > /tmp/$$.sql
echo set feedback off >> /tmp/$$.sql
echo set linesize 200 >> /tmp/$$.sql
echo set trimspool on >> /tmp/$$.sql
echo column cmd format a70 >> /tmp/$$.sql
echo spool /tmp/$$.sh >> /tmp/$$.sql
echo select "'/opt/gnu/bin/gzip -v ' || name ||' ; cp -p '||name||'.gz
$DUPLEX_LOC ' " >> /tmp/$$.sql
echo 'from sys.v_$archived_log' >> /tmp/$$.sql
echo 'where name in(' >> /tmp/$$.sql
ls /arch1/oradata/ora8i/arch/*.log | sed "s/^/'/;s/"'$'"/',/" >> /tmp/$$.sql
echo 'NULL ) and sequence# not in ' >> /tmp/$$.sql
echo '( select sequence# from v$log where archived = '"'NO'"')' >> /tmp/$$.sql
echo '/' >> /tmp/$$.sql
echo spool off >> /tmp/$$.sql
echo exit >> /tmp/$$.sql
-----------------------------------------------------

change the ls command of course.  All that does is generates a sql script -- run
it and see what it does (understand it before you run it please).

If you are not on unix (sorry for you) you'll have to modify that to be some
sort of "bat" file I suppose.  You can see the approach though -- I'm
dynamically generating a query that looks like:

set heading off
set feedback off
set linesize 200
set trimspool on
column cmd format a70
spool /tmp/21513.sh
select '/opt/gnu/bin/gzip -v ' || name ||' ; cp -p '||name||'.gz  '
from sys.v_$archived_log
where name in(
'/arch1/oradata/ora8i/arch/redo_1_18504.log',
..... other files here .....
'/arch1/oradata/ora8i/arch/redo_1_18707.log.gz',
NULL ) and sequence# not in
( select sequence# from v$log where archived = 'NO')
/
spool off
exit

That query, when run, will generate a script with the compress commands in it.
Review &
      Followup

      Rating:
      5
Thanks  July 14, 2002
Reviewer:
      A reader
It's amazing what can run for years and years and still be wrong.

Thanks for the help!!!

      Rating:
      3
NULL  August 08, 2003
Reviewer:
      Lidia  from Russia
Why do you include in script files with NULL name , documentation tells us, that
is in case of clearing unarchived log files (very rare)?
And how will  the line with name =NULL   in your final script /tmp/21513.sh (for
gzip)  look?

          Followup:

I added NULL in the event there was nothing in the directory.  It is not looking
for a log with a name of NULL, it would be looking for an log whose name "is
null" which will never happen.  the lines in question are:

echo 'where name in(' >> /tmp/$$.sql
ls /arch1/oradata/ora8i/arch/*.log | sed "s/^/'/;s/"'$'"/',/" >> /tmp/$$.sql
echo 'NULL ) and sequence# not in ' >> /tmp/$$.sql

if /arch1/oradata/ora8i/arch/*.log returned nothing and I didn't have NULL ) in
there, the predicate would be:

where name in ( )

which would fail.  instead the predicate is:

where name in ( NULL )

which successfully will find NOTHING.

      Rating:
      5
  October 30, 2003
Reviewer:
      A reader
Tom,

Lets say i performed an incomplete recovery, will v$archived_log return
duplicate records?? if yes then Tom can you please comment on this.

          Followup:

no, it will not.

      Rating: 5
Great script...
      and great model for others.  October 30, 2003

Reviewer:  Robert  from Memphis, USA

October 30, 2003 Vipin -- Thanks for the question regarding "xml
      generation methods", version 8.1.7

      You Asked
Hi Tom,

This is very basic doubt regarding XML generation from RDBMS data.

I just wanted to know when to use DBMS_XMLGEN instead of SYS_XMLGEN or
SYS_XMLGEN instead of SQLX functions like XMLELEMENT, XMLFOREST etc. In almost
any scenario I deal with, I am getting a feeling that all these can be used. I
am sure that there would be specific context in which each methode would
outperform the other. Please explain this with performance as the very most
important factor.
It would be really helpful if you show some examples where in SQLX function can
only be used in place of SYS_XMLGEN or DBMS_XMLGEN.

      and we said...
I asked Sean Dillon, our local XML technologist, to take a look at this and
here's what he had to say:

Hi Vipin,

SYS_XMLGEN is a native SQL function that returns as an XML document the results
of a passed-in SQL query.  SYS_XMLGEN is a part of the XML Developer's Kits
(XDKs), which are back-portable to Oracle 8.1.6.
XMLGEN was a package for inserting XML into tables and generating XML from
queries.  XMLGEN is deprecated.  Use DBMS_XMLSave and DBMS_XMLQuery now, which
are packages shipped with the XDKs.
DBMS_XMLGEN is a supplied package with Oracle9i.  It converts the results of a
SQL query to a cononical XML format.  It takes an arbitrary SQL query as input,
and converts it to XML format and returns as a CLOB.  It's much the same as the
DBMS_XMLQuery package in the XDK, but it's linked to the C libraries and
compiled into the database kernel, offering faster speeds (XMLQuery uses Java
under the covers).
SQLX is a set of operators that can be included in a relational query meant to
return an XML document (or XML fragment).  SQLX, in my opinion, should be used
above all else for generating XML as it is also linked into the kernel, is used
in a SQL statement (which is typically faster than invoking a PL/SQL stack), and
is FAR more flexible than DBMS_XMLQuery or DBMS_XMLGEN in terms of the XML
structure you may want to create.

According to the info you submitted, you're on Oracle8.1.7.  This means you
cannot use SQLX or DBMS_XMLGEN.  Your only option would be to download the
latest stable version of the XDKs from:
http://otn.oracle.com/tech/xml/xdkhome.html
As an example of SQLX, check out:
http://asktom.oracle.com/~sdillon/rss.html
This is an example of using SQLX to produce an RSS report of Ask Tom news feeds.
 I include the SQLX statement used to create one of the reports, and this gives
you an idea of how to make a fairly simple XML document.  This report, however,
would be pretty difficult to make from DBMS_XMLQuery or DBMS_XMLGEN (I don't
think it's impossible, just difficult ;-)).  I hope that helps.

_smd_
Sean Dillon - Principal Technologist, Oracle Corporation
Author "Beginning Oracle Programming"
http://www.amazon.com/exec/obidos/ASIN/186100690X
Review
      & Followup

      Rating: 4
Few more
      doubts  August 11, 2003
Reviewer:  Vipin
       from New york
Thanks Sean,

That was wonderful explanation.

Could you clear the following doubts also:-

1. Is SYS_XMLGEN SQL function written using SQLX function or are they C based
routines like DBMS_XMLGEN.

2. Isn't DBMS_XMLQuery written using JAVA, which pulls them a step back from
DBMS_XMLGen which is written in C(performance wise.) I think the most apt place
for choosing DBMS_XMLQuery is when you need bind variables in your query(the
resultset of which has to converted to XML.)

3. From your AskTom RSS examples it makes very clear that SQLX can just acheive
anything you want and from your explanation it is understood that we need to go
for it whenever possible since these can used in SQL queries. But my doubts here
is if you have a situation where in you need to use a nested table structure?
i.e. a requirement where in you have a set of items for tag something like DEPT
tag holding EMPLOYEE LIST tag which include some employees , their names, their
hiredates as separate tags.

In this scenario shouldn't we use objects, nested tables and SYS_XMLGEN function
to generate the XML or else still SQLX functions can acheive it?

I am scared to use nested tables or varrays (becuase there might be some
performance impact.) , Hide this statement from Tom ;)

      Rating:
      2
  August 13, 2003
Reviewer:  Vipin
       from Newyork
Hi Tom/Sean

any help regarding this?????

          Followup:

That was wonderful explanation.

Could you clear the following doubts also:-

1. Is SYS_XMLGEN SQL function written using SQLX function or are they C based
routines like DBMS_XMLGEN.

. SYS_XMLGEN is C based and linked into the kernel.

2. Isn't DBMS_XMLQuery written using JAVA, which pulls them a step back from
DBMS_XMLGen which is written in C(performance wise.) I think the most apt place
for choosing DBMS_XMLQuery is when you need bind variables in your query(the
resultset of which has to converted to XML.)

. Well, a couple of things here.  You can use DBMS_XMLGEN with bind variables.
. This is done by creating your context from a PL/SQL REF CURSOR.  The REF
. CURSOR can be opened w/ bind variables...
.
. open c for 'select * from emp where ename = :x' using p_name;
.
. OR you can use cursor_sharing=force which causes Oracle to try to make
. everything bind variables in the SQL you submit.  This comes with it's own
. sets of pros and cons, so don't just enable it arbitrarily, but it's an option
. and will give you faster execution w/ DBMS_XMLGEN w/o having to incur the
. penalty of using the JVM stack.

3. From your AskTom RSS examples it makes very clear that SQLX can just acheive
anything you want and from your explanation it is understood that we need to go
for it whenever possible since these can used in SQL queries. But my doubts here

is if you have a situation where in you need to use a nested table structure?
i.e. a requirement where in you have a set of items for tag something like DEPT
tag holding EMPLOYEE LIST tag which include some employees , their names, their
hiredates as separate tags.

. Using nested tables and varrays in your SQLX queries would definitely
. be pretty complex, whereas "select * from table {with a nested table or
. varray}" translates quite nicely w/ DBMS_XMLGEN and/or DBMS_XMLQUERY.  That
. doesn't mean it's impossible though.  I would strive to get your queries and
. desired output via SQLX if at all possible before moving on to the
. "potentially easier" solution.  IMO, easier doesn't necessarily mean better
. or faster (and usually means quite the opposite).

In this scenario shouldn't we use objects, nested tables and SYS_XMLGEN function

to generate the XML or else still SQLX functions can acheive it?

I am scared to use nested tables or varrays (becuase there might be some
performance impact.) , Hide this statement from Tom ;)

. Heheh, Tom reads pretty much EVERYTHING that comes across this site.
. You don't think I'd actually HIDE it from him do you? ;-).

      Rating: 5
Very
      useful.  August 14, 2003
Reviewer:  Kashif
       from Reston, VA

      Rating: 4
Ampersand in
      XML  September 03, 2003
Reviewer:  Sasa
       from Belgrade, Serbia
Hi Tom,

1.I have a problem during generate XML with special chars.
See following query:

select xmltype.getclobval(
    xmlelement(
        "TEST",
        xmlforest(
            id, testamp
        )
    )
 ) as xmlpayload
FROM (select 1 as id, '1ST&2ND' as testamp from dual) ;

It returns:
XMLPAYLOAD
----------------------------------------------------
<TEST><ID>1</ID><TESTAMP>1ST&amp;2ND</TESTAMP></TEST>

You could see that "amp;" is added to the end of "&" sign.

Could you reccomend any workaround for that as using escape sequence didn' help
(or I did something wrong).

Maybe the next question is for Sean Dillon.
2. If we have a field which name contains "#" when we create XML we got next:

select xmltype.getclobval(
        xmlelement(
            "TEST",
            xmlforest(
                TEST#SIGN
            )
        )
    ) as xmlpayload
FROM (select 1 as TEST#SIGN from dual)

It returns:
XMLPAYLOAD
----------------------------------------------------
<TEST><TEST_x0023_SIGN>1</TEST_x0023_SIGN></TEST>

You see that instead of "TEST#SIGN ", got "TEST_x0023_SIGN".

Any workaround for that or just to change naming convetion.

Thanks,

          Followup:

&amp; is the only correct way for it to be in XML.  & is a entity flag.

seems you might need to change the naming convention to conform to XML itself. #
has special meaning in XML as well.

      Rating: 3
How about
      CDATA?  September 03, 2003
Reviewer:  Dušan
       from London, UK
OK, & is a special charachter, but surely CDATA is meant to go around that (and
allow you to store binary and other XML syntax-incorrect stuff).

Now, this is a correct piece of XML:

<TEST>
 <ID>1</ID>
 <TESTAMP>![CDATA[1ST&2ND]]</TESTAMP>
</TEST>

Open it up in any XML editor, and voila, "&" is a literal part of CDATA.

But:

1. How do I generate XML with CDATA sections? Tried this:

SELECT
    xmlelement(
        "TEST",
        xmlforest(
            id, testamp
        )
    ).getstringval()
 AS xmlpayload
FROM (SELECT 1 AS id, '<![CDATA[1ST&2ND]]>' AS testamp FROM dual) ;

but got this output:

<TEST><ID>1</ID><TESTAMP>&lt;![CDATA[1ST&amp;2ND]]&gt;</TESTAMP></TEST>

Obviously, I have to "tell" xmltype that this thing is CDATA.

After reading through the documentation, I got to the point where I found about
makeCDataSection and createCDataSection (DBMS_XMLDOM), but as usual, there is no
sign of living examples. Am I on the right trail?

2. If you can demonstrate how to do 1, please demonstrate how to do the
opposite, to get "columns" from CDATA XML elements, with no <![CDATA[...]]>
around my string (I am hopeful that there is another way except trimming out the
string using string functions, which is obvious).

Many thanks

      Rating: 3
Is Sean Dillon
      around?  October 02, 2003
Reviewer:  Dušan
       from London, UK
Tom,
I still haven't found a way to deal with CDATA :(
I believe Sean Dillon was away at the time I submitted the last posting in this
thread. Could you ask Sean to provide some advice with CDATA and the APIs I
mentioned?
Many thanks

      Rating:
      3
ORA-03115  October 15, 2003
Reviewer:
      Yogesh  from Pune India
Hi,

I'm running on 8.1.7, but when I execute following query

select xmlgen.getxml('select Empno, Ename, d.deptno from emp e, dept d where
e.deptno=d.deptno') from dual

I'm getting following error
ORA-03115 unsupported network datatype or representation

its explaination in oracle docs says "Upgrade the older version of Oracle and
try again."....

If I can describe the package, why it is not allowing me to use it ?

          Followup:

the sqlplus client is what version?

      Rating: 2
I need also
      solution for CDATA  October 16, 2003
Reviewer:
      msc
Hi,

I have same question the Dušan has.

How to create CDATA sections with xml* functions?

I have been digging around quite a lot but no luck... I hope there is a
solution.

          Followup:

I asked Sean Dillon, our XML technologist, to take a look at this and here's
what he had to say:
------------------

Hiya MSC,

In order to create CDATA sections in your XML, you're going to have to use the
DBMS_XMLDOM package or corresponding Java classes.  There is a XDB_XMLDOM type
called DOMCharacterData,  DBMS_XMLDOM constant called CDATA_SECTION_NODE, and
procs/funcs in the DBMS_XMLDOM package:

. makeCDataSection()
. makeCharacterData()

These generally take a DBMS_XMLDOM.DOMNode and cast it to a CDATA section.  Hope
that helps!

      Rating: 3
Extract and
      Extractvalue  October 24, 2003
Reviewer:
      Sasa  from Belgrade, Serbia
Hi,

I have no question this time, maybe only "answer" to my question posted in this
thread and to all interested in this matter:

I asked how to avoid "&" to show in XML as "&amp;'".
Tom answered me that is legal as "&" is special char so it must be treated
separately.
I lived with that as used some string function after extracting from XML.

Example:
SELECT
    xmlelement(
        "TEST",
        xmlforest(
            id, testamp
        )
    ).getstringval()
 AS xmlpayload
FROM (SELECT 1 AS id, '1ST&2ND' AS testamp FROM dual) ;

got:
<TEST><ID>1</ID><TESTAMP>1ST&amp;2ND</TESTAMP></TEST>

I tried to extract xml with:

SELECT
extract(value(d),'/TEST/TESTAMP/text()').getstringval() as TESTAMP
FROM
TABLE(XMLSequence(xmltype('<TEST><ID>1</ID><TESTAMP>1ST&amp;2ND</TESTAMP></TEST>'
).extract('/TEST'))) d;

It worked but got this output:

1ST&amp;2ND

Of course this was not same I started with:
1ST&2ND

But digging through the Oracle doc I found that exists also extractvalue
function, states in doc:
" extractValue() permits you to extract the desired value more easily than when
using the equivalent extract function. It is an ease-of-use and shortcut
function. So instead of using:

extract(x,'path/text()').get(string|num)val()
you can replace extract().getStringVal() or extract().getnumberval() with
extractValue() as follows:

extractValue(x, 'path/text()')

With extractValue() you can leave off the text(), but ONLY if the node pointed
to by the 'path' part has only one child and that child is a text node.
Otherwise, an error is thrown.

extractValue() syntax is the same as extract()."

Applying extractvalue function to previous:

SELECT
    extractvalue(value(d),'/TEST/TESTAMP/text()') as TESTAMP
    FROM
TABLE(XMLSequence(xmltype('<TEST><ID>1</ID><TESTAMP>1ST&amp;2ND</TESTAMP></TEST>'
).extract('/TEST'))) d;

got correct:

1ST&2ND

Also, it returns correctly all other "problematic" character: > , < , ", ' .

I think this is probably intended to Sean Dillon who should explain this
different behaviour.

Thanks

      Rating: 5
Thanks,
      Sean!  October 30, 2003
Reviewer:  msc

October 30, 2003 Michael Garfield -- Thanks for the question
      regarding "Passing parameters to XSL-stylesheet - how?", version 9.2.0

      You Asked
Hi Tom

I'm trying to pass a parameter to a XSL-stylesheet for an XML transformation.
Below is a simple example! I cannot get it to work! Am I doing something wrong?

In the example below (output from SQL*Plus session) I expected to get

x=suppliedX

Any help appreciated!
Regards
Michael

Example:

SQL*Plus: Release 9.2.0.3.0 - Production on Ons Okt 29 10:53:23 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Forbindelse er oprettet til :
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
With the OLAP and Oracle Data Mining options
JServer Release 9.2.0.4.0 - Production

SQL> select XMLType('<test><tag>text</tag></test>').transform(
  2    XMLType('
  3      <xsl:stylesheet version="1.0" xmlns:xsl="
">http://www.w3.org/1999/XSL/Transform">
  4      <xsl:output method="xml"/>
  5      <xsl:param name="x" select="''defaultX''"/>
  6      <xsl:template match="tag">
  7        <xsl:text>x=</xsl:text>
  8        <xsl:value-of select="$x"/>
  9      </xsl:template>
 10      </xsl:stylesheet>'),
 11    'x=suppliedX').getClobVal()
 12   from dual;

XMLTYPE('<TEST><TAG>TEXT</TAG></TEST>').TRANSFORM(XMLTYPE('<XSL:STYLESHEETVERSIO

--------------------------------------------------------------------------------

x=defaultX

1 række er valgt.

      and we said...
I asked Sean Dillon, our local XML technologist, to take a look at this and
here's what he had to say:

change...

 11    'x=suppliedX').getClobVal()

...to...

 11    'x="''suppliedX''"').getClobVal()

...and bingo:

sdillon@SLAP> @xformparm

XMLTYPE('<TEST><TAG>TEXT</TAG></TEST>').TRANSFORM(XMLTYPE('<XSL:STYLESHEETVERSIO
--------------------------------------------------------------------------------
x=suppliedX

Hope that helps!

_smd_
Review
      & Followup

      Rating: 5
Too obvious,
      but not in the fine manual...  October 30, 2003

Reviewer:  Michael Garfield Sørensen, CeDeT  from
      Denmark
Well, it solved the problem. And the solution seems obvious. Too bad that I
couldn't figure it out by reading the fine manual(s). Thank you very much!

October 30, 2003 Kristi -- Thanks for the question regarding
      "Oracle9i: Formatting XML Tags ", version Oracle9i

      You Asked
I started with the example online at DBMS_XMLGEN Example 5:

http://download-west.oracle.com/otndoc/oracle9i/901_doc/appdev.901/a88894/adx05xml.htm#1015194
and modified it for my database.  I am able to format all of my tags except
for the top level tag.  By naming the column that points to a particular Object
Type I can name all my tags something like this:

<?xml version="1.0"?>
<Msg>
 <FileGet>
  <SYS_FIL_CASE16 PK="1001" UT="1" UserID="Bob">
   <Col CaseNo="A245" ShortTitle="Test Case"/>
   <Ent PK="501" UT="1" PKParent="1001" ParentEntCode="CAS" PKFile="1001"
UserID="1">
    <Col Ref_PartyType_ID="1" FirstName="Bob" MiddleName="T" LastName="Jones"/>
    <Ent PK="2" UT="1" PKParent="501" ParentEntCode="1" PKFile="1001"
UserID="1">
     <Col Ref_AddressType_ID="1" Address1="124 Main Street" Address2="Apt. B"
City="Denver"/>
    </Ent>
   </Ent>
  </SYS_FIL_CASE16>
 </FileGet>
</Msg>

Since the top level tag, SYS_FIL_CASE16, is actually a TYPE that is a TABLE
consisting of all other types making up the hierarchical set of tables/data.  I
seem to be stuck with the tag SYS_FIL_CASE16 and can't seem to get around it
because its not a column, its a type.  Is there any way to get around this so I
can name it Ent??

Since all of my xml will need to be formatted this way, I will have multiple
types named the same which of course is not possible to do.  Is there a way to
not use the create type x as table of y and just create the hierarchical types
as columns of the other tables??  I tried this with the example 5 and the CAST
fails.  I am thinking since I was able to name all the other tags, I have to be
able to name this last one.???

-----------------------------------------------------------
Additional Info:  I would like to get this out of sql and not transform it.
------------------------------------------------------------

-- Table ddl pulled from oem
CREATE TABLE "KRISTI"."ENT_ADDRESS" ("PK" VARCHAR2(38 byte) NOT
    NULL, "UT" VARCHAR2(38 byte) NOT NULL, "PKPARENT"
    VARCHAR2(38 byte) NOT NULL, "PARENTENTCODE" VARCHAR2(3 byte)
    NOT NULL, "PKFILE" VARCHAR2(38 byte), "USERID" VARCHAR2(38
    byte) NOT NULL, "REF_ADDRESSTYPE_ID" NUMBER(38) NOT NULL, "ADDRESS1"
    VARCHAR2(60 byte) NOT NULL, "ADDRESS2" VARCHAR2(60 byte), "ADDRESS3"
    VARCHAR2(60 byte), "ADDRESS4" VARCHAR2(60 byte), "CITY"
    VARCHAR2(30 byte) NOT NULL, "REF_STATEPROVINCE_ID"
    NUMBER(38) NOT NULL, "ZIPPOSTALCODE" VARCHAR2(10 byte),
    CONSTRAINT "PK_ENT_ADDRESS" PRIMARY KEY("PK")
    USING INDEX
    TABLESPACE "INDX"
    STORAGE ( INITIAL 64K NEXT 0K MINEXTENTS 1 MAXEXTENTS
    2147483645 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1)
    PCTFREE 10 INITRANS 2 MAXTRANS 255)
    TABLESPACE "USERS" PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS
    255
    STORAGE ( INITIAL 64K NEXT 0K MINEXTENTS 1 MAXEXTENTS
    2147483645 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1)
    LOGGING;

CREATE TABLE "KRISTI"."ENT_PARTY" ("PK" VARCHAR2(38 byte) NOT
    NULL, "UT" VARCHAR2(38 byte) NOT NULL, "PKPARENT"
    VARCHAR2(38 byte) NOT NULL, "PARENTENTCODE" VARCHAR2(3 byte)
    NOT NULL, "PKFILE" VARCHAR2(38 byte), "USERID" VARCHAR2(38
    byte) NOT NULL, "REF_PARTYTYPE_ID" NUMBER(38) NOT NULL, "FIRSTNAME"
    VARCHAR2(30 byte) NOT NULL, "MIDDLENAME" VARCHAR2(30 byte), "LASTNAME"
    VARCHAR2(30 byte) NOT NULL,
    CONSTRAINT "PK_ENT_PARTY" PRIMARY KEY("PK")
    USING INDEX
    TABLESPACE "INDX"
    STORAGE ( INITIAL 64K NEXT 0K MINEXTENTS 1 MAXEXTENTS
    2147483645 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1)
    PCTFREE 10 INITRANS 2 MAXTRANS 255)
    TABLESPACE "USERS" PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS
    255
    STORAGE ( INITIAL 64K NEXT 0K MINEXTENTS 1 MAXEXTENTS
    2147483645 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1)
    LOGGING ;

CREATE TABLE "KRISTI"."FIL_CASE" ("PK" VARCHAR2(38 byte) NOT NULL,
    "UT" VARCHAR2(38 byte) NOT NULL, "USERID" VARCHAR2(38 byte)
    NOT NULL, "CASENO" VARCHAR2(10 byte) NOT NULL, "SHORTTITLE"
    VARCHAR2(30 byte) NOT NULL, "REF_CASETYPE_ID" NUMBER(38) NOT
    NULL, "REF_DISPOSITIONTYPE_ID" NUMBER(38), "REF_DISPOSITIONSTAGE_ID"
    NUMBER(38), "FILINGDATE" DATE NOT NULL, "DISPDATE" DATE, "REF_OFFICIAL_ID"
    NUMBER(38), "CAS_OFF_FIRSTNAME" VARCHAR2(10 byte), "CAS_OFF_LASTNAME"
    VARCHAR2(20 byte),
    CONSTRAINT "PK_FIL_CASE" PRIMARY KEY("PK")
    USING INDEX
    TABLESPACE "INDX"
    STORAGE ( INITIAL 64K NEXT 0K MINEXTENTS 1 MAXEXTENTS
    2147483645 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1)
    PCTFREE 10 INITRANS 2 MAXTRANS 255)
    TABLESPACE "USERS" PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS
    255
    STORAGE ( INITIAL 64K NEXT 0K MINEXTENTS 1 MAXEXTENTS
    2147483645 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1)
    LOGGING ;

CREATE TABLE temp_clob_tab(result CLOB);

create table sxmsg(pk varchar(38));

-- inserts
insert into sxmsg(pk) values(1001);
INSERT INTO "KRISTI"."FIL_CASE" ("PK" ,"UT" ,"USERID" ,"CASENO" ,"SHORTTITLE"
,"REF_CASETYPE_ID" ,"REF_DISPOSITIONTYPE_ID" ,"REF_DISPOSITIONSTAGE_ID"
,"FILINGDATE" ,"DISPDATE" ,"REF_OFFICIAL_ID" ,"CAS_OFF_FIRSTNAME"
,"CAS_OFF_LASTNAME" )
VALUES ('1001' ,'1' ,'Bob' ,'A245' ,'Test Case' ,1 ,1 ,1 ,sysdate ,sysdate ,1
,'George' ,'Smith'  );
INSERT INTO "KRISTI"."ENT_PARTY" ("PK" ,"UT" ,"PKPARENT" ,"PARENTENTCODE"
,"PKFILE" ,"USERID" ,"REF_PARTYTYPE_ID" ,"FIRSTNAME" ,"MIDDLENAME" ,"LASTNAME" )

VALUES ('501' ,'1' ,'1001' ,'CAS' ,'1001' ,'1' ,1 ,'Bob' ,'T' ,'Jones'  )
INSERT INTO "KRISTI"."ENT_ADDRESS" ("PK" ,"UT" ,"PKPARENT" ,"PARENTENTCODE"
,"PKFILE" ,"USERID" ,"REF_ADDRESSTYPE_ID" ,"ADDRESS1" ,"ADDRESS2" ,"ADDRESS3"
,"ADDRESS4" ,"CITY" ,"REF_STATEPROVINCE_ID" ,"ZIPPOSTALCODE" )
VALUES ('2' ,'1' ,'501' ,'1' ,'1001' ,'1' ,1 ,'124 Main Street' ,'Apt. B' ,''
,'' ,'Denver' ,3 ,'80201'  )

-- types
CREATE or REPLACE TYPE ent_address16 AS OBJECT(
    "@Ref_AddressType_ID" NUMBER(38),
    "@Address1" VARCHAR2(30),
    "@Address2" VARCHAR2(30),
    "@Address3" VARCHAR2(30),
    "@Address4" VARCHAR2(30),
    "@City" varchar2(30),
    "@Ref_StateProvince_ID" number(38),
    "@ZipPostalCode" VARCHAR2(10)
 );
/

CREATE OR REPLACE TYPE sys_ent_address16 AS OBJECT(
    "@PK" VARCHAR2(38),
    "@UT" VARCHAR2(38),
    "@PKParent" VARCHAR2(38),
    "@ParentEntCode" VARCHAR2(3),
    "@PKFile" VARCHAR2(38),
    "@UserID" VARCHAR2(38),
    "Col" ent_address16
 );
/

CREATE OR REPLACE TYPE ent_party16 AS OBJECT(
    "@Ref_PartyType_ID" varchar2(38),
    "@FirstName" VARCHAR2(30),
    "@MiddleName" varchar2(30),
    "@LastName" varchar2(38)
 );
/

CREATE OR REPLACE TYPE sys_ent_party16 AS OBJECT(
    "@PK" VARCHAR2(38),
    "@UT" VARCHAR2(38),
    "@PKParent" VARCHAR2(38),
    "@ParentEntCode" VARCHAR2(3),
    "@PKFile" VARCHAR2(38),
    "@UserID" VARCHAR2(38),
    "Col" ent_party16,
    "Ent" sys_ent_address16
 );
/

CREATE OR REPLACE TYPE fil_case16 AS OBJECT(
   "@CaseNo" varchar2(10),
   "@ShortTitle" VARCHAR2(30),
   "@Ref_CaseType_ID" Number(38),
   "@Ref_DispositionType_ID" Number(38),
   "@FilingDate" Date,
   "@DispDate" Date
 );
/

CREATE OR REPLACE TYPE sys_fil_case16 AS OBJECT(
    "@PK" VARCHAR2(38),
    "@UT" VARCHAR2(38),
    "@UserID" varchar2(10),
    "Col" fil_case16,
    "Ent" sys_ent_party16
 );
/

CREATE OR REPLACE TYPE CasePartyAddress16_ntabtyp AS TABLE OF sys_fil_case16
/

CREATE OR REPLACE TYPE sxmsg16 AS OBJECT(
   pk VARCHAR2(38),
   CasePartyAddr CasePartyAddress16_ntabtyp
 );
/

-- view
CREATE OR REPLACE VIEW CasePartyAddress OF sxmsg16
WITH OBJECT IDENTIFIER (PK)
AS SELECT s.pk,
     CAST( MULTISET(
       SELECT sys_fil_case16(sc.pk, sc.ut, sc.userid,
         fil_case16(c.caseno, c.shorttitle, c.ref_casetype_id,
c.ref_dispositiontype_id, c.filingdate, c.dispdate),
         sys_ent_party16(sp.PK, sp.UT, sp.PKParent, sp.parentEntCode, sp.pkfile,
sp.userid,
         ent_party16(p.Ref_PartyType_ID, p.FirstName, p.MiddleName, p.LastName),
         sys_ent_address16(sa.PK, sa.UT, sa.PKParent, sa.parentEntCode,
sa.pkfile, sa.userid,
         ent_address16(a.ref_addresstype_id, a.address1, a.address2, a.address3,
a.address4, a.city, a.ref_stateprovince_id, a.zippostalcode))))
       FROM fil_case sc, fil_case c, ent_party sp, ent_party p, ent_address sa,
ent_address a
       WHERE sc.pk = s.pk and sc.pk = c.pk and sp.pkparent = c.pk and sp.pk =
p.pk and sa.pkparent = p.pk and sa.pk = a.pk )
       AS CasePartyAddress16_ntabtyp)
   FROM sxmsg s;

-- procedure
CREATE OR REPLACE  PROCEDURE "KRISTI"."SP_DBMS_XMLGEN"
AS
   qryCtx DBMS_XMLGEN.ctxHandle;
   result CLOB;
BEGIN
 qryCtx := dbms_xmlgen.newContext('
            SELECT CasePartyAddr "FileGet"
            FROM CasePartyAddress d'
            );
 DBMS_XMLGEN.setRowTag(qryCtx, NULL);
 DBMS_XMLGEN.setRowSetTag(qryCtx, 'Msg');
 DELETE temp_clob_tab;
 result := DBMS_XMLGEN.getXML(qryCtx);
 INSERT INTO temp_clob_tab VALUES(result);
 End;
/

-- sqlplus
set serveroutput on;
begin
 sp_dbms_xmlgen();
end;
/
set pagesize 2000;
set linesize 120;
set long 4000;
select * from temp_clob_tab;

      and we said...
I sent this to our resident XML guru, Sean Dillon and he says....

It's relatively easy to change the names of elements in generated XML from
DBMS_XMLGEN. You can use column aliases to accomplish this. In the following
example, I build a couple of simple types and then use two different queries in
the DBMS_XMLGEN.NEWCONTEXT call to modify the element names of my generated xml
documents:

SQL> create type phonelist_vartyp as varray(10) of varchar2(20)
  2  /

Type created.

SQL> create type address_typ as object(
  2    street varchar2(200),
  3    city varchar2(200),
  4    state char(2),
  5    zip varchar2(20)
  6  )
  7  /

Type created.

SQL> create type customer_typ as object(
  2    customer_no number,
  3    customer_name varchar2(200),
  4    address address_typ,
  5    phone_list phonelist_vartyp
  6  )
  7  /

Type created.

SQL> create table customer_tab(
  2    customer_no number,
  3    customer_name varchar2(200),
  4    street varchar2(200),
  5    city varchar2(200),
  6    state char(2),
  7    zip varchar2(20),
  8    phone1 varchar2(20),
  9    phone2 varchar2(20),
 10    phone3 varchar2(20),
 11    phone4 varchar2(20),
 12    constraint customer_pk primary key (customer_no)
 13  )
 14  /

Table created.

SQL> create or replace view customer_view
  2    of customer_typ
  3    with object identifier( customer_no )
  4    as select ct.customer_no, ct.customer_name,
  5              address_typ( ct.street, ct.city, ct.state, ct.zip ),
  6              phonelist_vartyp( ct.phone1, ct.phone2, ct.phone3, ct.phone4 )
  7         from customer_tab ct
  8  /

View created.

SQL> insert into customer_tab values (1, 'Jean Nance', '2 Avocet Drive',
  2                                   'Redwood Shores', 'CA', '95054',
  3                                   '415-555-1212', '650-123-3456',
'609-555-1212', '201-555-1212') ;

1 row created.

SQL> insert into customer_tab values (2, 'John Nike', '323 College Drive',
  2                                   'Edison', 'NJ', '08820',
  3                                   '609-555-1212', '201-555-1212',
'415-555-1212', '650-123-3456') ;

1 row created.

SQL> set autoprint on
SQL> variable c clob;
SQL> declare
  2    qryctx dbms_xmlgen.ctxhandle;
  3  begin
  4    -- notice SELECT *: just take the defaults
  5    qryctx := dbms_xmlgen.newcontext('select * from customer_view');
  6    dbms_xmlgen.setrowsettag(qryctx,'PeopleList');
  7    dbms_xmlgen.setrowtag(qryctx,'Employees');
  8    :c := dbms_xmlgen.getxml(qryctx);
  9  end;
 10  /

PL/SQL procedure successfully completed.

C
-------------------------------------------------------------------------------
<?xml version="1.0"?>
<PeopleList>
    <Employees>
        <CUSTOMER_NO>1</CUSTOMER_NO>
        <CUSTOMER_NAME>Jean Nance</CUSTOMER_NAME>
        <ADDRESS>
            <STREET>2 Avocet Drive</STREET>
            <CITY>Redwood Shores</CITY>
            <STATE>CA</STATE>
            <ZIP>95054</ZIP>
        </ADDRESS>
        <PHONE_LIST>
            <VARCHAR2>415-555-1212</VARCHAR2>
            <VARCHAR2>650-123-3456</VARCHAR2>
            <VARCHAR2>609-555-1212</VARCHAR2>
            <VARCHAR2>201-555-1212</VARCHAR2>
        </PHONE_LIST>
    </Employees>
    <Employees>
        <CUSTOMER_NO>2</CUSTOMER_NO>
        <CUSTOMER_NAME>John Nike</CUSTOMER_NAME>
        <ADDRESS>
            <STREET>323 College Drive</STREET>
            <CITY>Edison</CITY>
            <STATE>NJ</STATE>
            <ZIP>08820</ZIP>
        </ADDRESS>
        <PHONE_LIST>
            <VARCHAR2>609-555-1212</VARCHAR2>
            <VARCHAR2>201-555-1212</VARCHAR2>
            <VARCHAR2>415-555-1212</VARCHAR2>
            <VARCHAR2>650-123-3456</VARCHAR2>
        </PHONE_LIST>
    </Employees>
</PeopleList>

SQL> declare
  2    qryctx dbms_xmlgen.ctxhandle;
  3  begin
  4  -- in this one, we use aliases to make attributes and rename elements:
  5    qryctx := dbms_xmlgen.newcontext('
  6               select customer_no "@cno", customer_name "@cname",
  7                      address "address_info", phone_list "phone_numbers"
  8                 from customer_view');
  9    dbms_xmlgen.setrowsettag(qryctx,'PeopleList');
 10    dbms_xmlgen.setrowtag(qryctx,'Employees');
 11    :c := dbms_xmlgen.getxml(qryctx);
 12  end;
 13  /

PL/SQL procedure successfully completed.

C
-------------------------------------------------------------------------------
<?xml version="1.0"?>
<PeopleList>
    <Employees cno="1" cname="Jean Nance">
        <address_info>
            <STREET>2 Avocet Drive</STREET>
            <CITY>Redwood Shores</CITY>
            <STATE>CA</STATE>
            <ZIP>95054</ZIP>
        </address_info>
        <phone_numbers>
            <VARCHAR2>415-555-1212</VARCHAR2>
            <VARCHAR2>650-123-3456</VARCHAR2>
            <VARCHAR2>609-555-1212</VARCHAR2>
            <VARCHAR2>201-555-1212</VARCHAR2>
        </phone_numbers>
    </Employees>
    <Employees cno="2" cname="John Nike">
        <address_info>
            <STREET>323 College Drive</STREET>
            <CITY>Edison</CITY>
            <STATE>NJ</STATE>
            <ZIP>08820</ZIP>
        </address_info>
        <phone_numbers>
            <VARCHAR2>609-555-1212</VARCHAR2>
            <VARCHAR2>201-555-1212</VARCHAR2>
            <VARCHAR2>415-555-1212</VARCHAR2>
            <VARCHAR2>650-123-3456</VARCHAR2>
        </phone_numbers>
    </Employees>
</PeopleList>

That's it... you have pretty good control over your generated XML element names,
as well as the flexibility to move elements to attributes by aliasing columns
with at-signs (from the block above):

  6  select customer_no "@cno", customer_name "@cname",

...resulted in:

    <Employees cno="1" cname="Jean Nance">
...and...
    <Employees cno="2" cname="John Nike">

If you are constructing a type in your query, you can alias that as well. The
following block would work fine:

SQL> declare
  2    qryctx dbms_xmlgen.ctxhandle;
  3  begin
  4    qryctx := dbms_xmlgen.newcontext('
  5               select ct.customer_no, ct.customer_name,
  6                      address_typ(ct.street, ct.city, ct.state, ct.zip)
"addresses",
  7                      phonelist_vartyp(ct.phone1, ct.phone2, ct.phone3,
ct.phone4) "phone_numbers"
  8                 from customer_tab ct');
  9    dbms_xmlgen.setrowsettag(qryctx,'PeopleList');
 10    dbms_xmlgen.setrowtag(qryctx,'Employees');
 11    :c := dbms_xmlgen.getxml(qryctx);
 12  end;
 13  /

You get the idea... hope that helps!

***********************************************************
FOLLOW-UP TO REVIEW 1:
Yeah, using cast(multiset in your view makes the standard column aliasing
functionality a bit more difficult. You could easily use XSLT to transform the
resulting document to whatever it is you want the new entity names to be, for
example:

SQL> create or replace function transform_xml_with_xsl(
  2    p_xml  in clob,
  3    p_xsl  in clob )
  4    return clob
  5  is
  6    l_par  xmlparser.parser;
  7    l_xml  xmldom.domdocument;
  8    l_pro  xslprocessor.processor;
  9    l_xsl  xslprocessor.stylesheet;
 10    l_clob clob;
 11  begin
 12    l_par := xmlparser.newparser;
 13    xmlparser.parseclob(l_par, p_xsl);
 14    l_xsl := xslprocessor.newstylesheet(xmlparser.getdocument(l_par),null);
 15    xmlparser.parseclob(l_par, p_xml);
 16    l_xml := xmlparser.getdocument(l_par);
 17    dbms_lob.createtemporary(l_clob, true, dbms_lob.session);
 18    l_pro := xslprocessor.newprocessor;
 19    xslprocessor.processxsl(l_pro,l_xsl,l_xml,l_clob);
 20    xmlparser.freeparser(l_par);
 21    xslprocessor.freeprocessor(l_pro);
 22    return l_clob;
 23  exception
 24    when others then
 25      xmlparser.freeparser(l_par);
 26      xslprocessor.freeprocessor(l_pro);
 27      raise;
 28  end transform_xml_with_xsl;
 29  /

Function created.

SQL> create or replace function sp_dbms_xmlgen
  2    return clob
  3  as
  4    l_qryctx dbms_xmlgen.ctxhandle;
  5    l_xsl    clob;
  6  begin
  7    l_xsl :=
  8  '<?xml version="1.0"?>
  9  <xsl:stylesheet xmlns:xsl="
http://www.w3.org/1999/XSL/Transform
version="1.0">
 10    <xsl:template match="node()|@*">
 11      <xsl:copy>
 12        <xsl:apply-templates select="@*|node()"/>
 13      </xsl:copy>
 14    </xsl:template>
 15
 16    <xsl:template match="SYS_FIL_CASE16">
 17      <NEWENT><xsl:apply-templates select="@*|node()"/></NEWENT>
 18    </xsl:template>
 19  </xsl:stylesheet>';
 20
 21    l_qryctx := dbms_xmlgen.newcontext('
 22               select casepartyaddr "FileGet"
 23               from casepartyaddress d');
 24    dbms_xmlgen.setrowtag(l_qryctx, null);
 25    dbms_xmlgen.setrowsettag(l_qryctx, 'msg');
 26
 27    return transform_xml_with_xsl(
 28             p_xml => dbms_xmlgen.getxml(l_qryctx),
 29             p_xsl => l_xsl );
 30
 31  end;
 32  /

Function created.

SQL> show errors
No errors.
SQL>
SQL> var c clob;
SQL> begin
  2   :c := sp_dbms_xmlgen;
  3  end;
  4  /

PL/SQL procedure successfully completed.

C
-------------------------------------------------------------------------------
<?xml version = '1.0' encoding = 'UTF-8'?>
<msg>
  <FileGet>
    <NEWENT PK="1001" UT="1" UserID="Bob">
      <Col CaseNo="A245" ShortTitle="Test Case" Ref_CaseType_ID="1"
           Ref_DispositionType_ID="1" FilingDate="31-OCT-01"
           DispDate="31-OCT-01"/>
      <Ent PK="501" UT="1" PKParent="1001" ParentEntCode="CAS"
           PKFile="1001" UserID="1">
        <Col Ref_PartyType_ID="1" FirstName="Bob" MiddleName="T"
             LastName="Jones"/>
        <Ent PK="2" UT="1" PKParent="501" ParentEntCode="1"
             PKFile="1001" UserID="1">
          <Col Ref_AddressType_ID="1" Address1="124 Main Street"
               Address2="Apt. B" City="Denver" Ref_StateProvince_ID="3"
               ZipPostalCode="80201"/>
        </Ent>
      </Ent>
    </NEWENT>
  </FileGet>
</msg>

r/s,
Sean Dillon
Review & Followup

      Rating: 1
More complex
      use of Object Types  October 30, 2001
Reviewer:
      Kristi Richardson  from Denver, CO
The problem was with the top level object type..  Notice that all the columns
have an alias but the top level type obviously does not..  How do you either
avoid using the type so you can use an alias, OR format that tag in the example?

      Rating: 5
My unsolicited
      feedback  October 30, 2001
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
By changing your procedure to this:

CREATE OR REPLACE PROCEDURE kristi.sp_dbms_xmlgen AS
  qryctx dbms_xmlgen.ctxhandle;
  result clob;
BEGIN
  qryctx := dbms_xmlgen.newcontext('
  select casepartyaddr "FileGet" from casepartyaddress');
  dbms_xmlgen.setrowtag(qryctx, NULL);
  dbms_xmlgen.setrowsettag(qryctx, 'Msg');
  dbms_xmlgen.useitemtagsforcoll(qryctx);
  delete from temp_clob_tab;
  commit;
  result := dbms_xmlgen.getxml(qryctx);
  insert into temp_clob_tab values (result);
  commit;
  dbms_xmlgen.closecontext(qryctx);
END;

You should get this as output:

SQL> begin
  2  sp_dbms_xmlgen();
  3  end;
  4  /

PL/SQL procedure successfully completed.

SQL> select * from temp_clob_tab;

RESULT
------------------------------------------------------------------
<?xml version="1.0"?>
<Msg>
 <FileGet>
  <FileGet_ITEM PK="1001" UT="1" UserID="Bob">
   <Col CaseNo="A245" ShortTitle="Test Case" Ref_CaseType_ID="1"
Ref_DispositionType_ID="1" FilingDate="30-OCT-01" DispDate="30-OCT-01"/>
   <Ent PK="501" UT="1" PKParent="1001" ParentEntCode="CAS" PKFile="1001"
UserID="1">
    <Col Ref_PartyType_ID="1" FirstName="Bob" MiddleName="T" LastName="Jones"/>
    <Ent PK="2" UT="1" PKParent="501" ParentEntCode="1" PKFile="1001"
UserID="1">
     <Col Ref_AddressType_ID="1" Address1="124 Main Street" Address2="Apt. B"
City="Denver" Ref_StateProvince_ID="3" ZipPostalCode="80201"/>
    </Ent>
   </Ent>
  </FileGet_ITEM>
 </FileGet>
</Msg>

Not 100% what you were wanting, I think, but it may help...

- Mark

      Rating: 5
Thanks a bunch
      that was exactly what I needed!  October 31, 2001

Reviewer:  Kristi Richardson  from Denver, CO

      Rating: 4
the function
      TRANSFORM_XML_WITH_XSL does not compile..  October 31, 2001

Reviewer:  Kristi Richardson  from Denver, CO

The function you wrote required me to grant some permissions for xmlparser,
xmldom, and xslprocessor.  However, when I compile the function, I am still
getting an error on line 8 l_pro  xslprocessor.processor....

Errors for FUNCTION TRANSFORM_XML_WITH_XSL:

LINE/COL ERROR
-------- ----------------------------------------------
8/10     PLS-00201: identifier 'XMLPROCESSOR.PROCESSOR' must be declared
8/10     PL/SQL: Item ignored
18/6     PLS-00320: the declaration of the type of this expression is incomplete
or malformed

I looked in the package sys.xmlprocessor and there is no processor in there..
Am I missing something?

          Followup:

You need to download the latest copies of the Oracle XDKs. You can get them from
Oracle Technet at
http://technet.oracle.com/
They are backwards compatible, so the v9.0.1 of the XDK
will work with Oracle v8.1.6. You will also need to load the Java XDK into the
database as well, the PL/SQL XDKs are dependent on them.

      Rating: 5
DTD
      Problem  November 21, 2002
Reviewer:
      Bharath  from USA
How to avoid DOCTYPE beign generated in DTD using getxmlmetadata.Reson is when i
validate generated DTD against xmlspy it's giving an error like DOCTYPE
expected.Here is my code

import java.math.*;
import java.net.URL;
import java.sql.*;
import oracle.jdbc.*;
import oracle.jdbc.driver.*;
import oracle.xml.parser.v2.*;
import oracle.xml.sql.query.*;
import java.io.*;
public class GenXMl
{
  public GenXMl()
  {

  }

  public static void main(String args[]) throws SQLException,IOException
  {
    String tabName = "USER_TBL";
    String user = "comps/comps";
    URL stylesheetURL = null;
    DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
    System.out.println("select A.deal_id as \"@deal_id \" ,");
    Connection conn =
DriverManager.getConnection("jdbc:oracle:thin:@ds101.dev.global.chase.com:1521:gi
b_dev7","comps","comps");
    OracleXMLQuery qry = new OracleXMLQuery(conn,"select A.deal_id as \"@id\"
,"+
             "A.name,tr_industry_desc," +
            "cmb_deal JPM_lead_deal, "+
            "deal_source.DEAL_SIZE_AMT_USDE ," +
   "cursor( SELECT TR_PURPOSE.DESCRIPTION Purpose_Desc"+
             " FROM DEAL_PROD_TYP, "+
              "TR_PURPOSE "+
             "WHERE ( DEAL_PROD_TYP.PRODTYP_ID = TR_PURPOSE.PRODTYP_ID ) and "+
             "(deal_prod_typ.deal_id = A.deal_id ) and "+
             "( ( DEAL_PROD_TYP.DATA_SOURCE_ID = 13 ) )) Purpose_details,"+
    "cursor(  SELECT TR_AGENT.AGENT_NAME "+
             " FROM TR_DEAL_AGENT,   "+
             "TR_AGENT,  "+
             "DEAL,   "+
             "TR_DEAL_SOURCE "+
             "WHERE ( TR_AGENT.AGENT_ID = TR_DEAL_AGENT.AGENT_ID ) and "+
             "( TR_DEAL_SOURCE.DEAL_ID = DEAL.DEAL_ID ) and  "+
             "( TR_DEAL_SOURCE.TR_ID = TR_DEAL_AGENT.TR_ID ) and "+
             "( tr_deal_source.deal_id = A.deal_id)  and " +
             "( ( TR_DEAL_AGENT.AGENT_TYPE_ID = 2 ))) Admin_agents ,"+
   "cursor(  SELECT  FAC_TYPS.DESCRIPTION  Facs_Desc,  "+
            "FACS.FAC_SIZE_AMT,"+
            "GBIS.CURRENCY.DESCRIPTION Currency_desc,  " +
            "undrawn_txt undrawn_bps ,"+
            "first_drawn_txt first_drawn_bps ,"+
            "fully_drawn_txt AIS,"+
            "Months_between(Trunc(Sysdate) , ADD_MONTHS(IN_MARKET_FROM_DT + " +
            "DECODE(TENOR_D,NULL,0,TENOR_D), "+
            "DECODE(TENOR_M,NULL,0,TENOR_M) + DECODE(TENOR_Y,NULL,0,TENOR_Y *
12)))   Maturity, " +
            "ADD_MONTHS(IN_MARKET_FROM_DT + " +
            "DECODE(TENOR_D,NULL,0,TENOR_D), "+
            "DECODE(TENOR_M,NULL,0,TENOR_M) + DECODE(TENOR_Y,NULL,0,TENOR_Y *
12))   Actual " +
            " FROM DEAL, "+
            " TR_DEAL_SOURCE, "  +
            " FACS,   " +
            " FAC_TYPS,  "+
            " GBIS.CURRENCY " +
            " WHERE ( DEAL.DEAL_ID = TR_DEAL_SOURCE.DEAL_ID ) and  "+
            " ( TR_DEAL_SOURCE.DEAL_ID = FACS.DEAL_ID ) and  "+
            " ( FAC_TYPS.FAC_TYP_ID = FACS.FAC_TYP_ID ) and  "+
            " (A.deal_id = deal.deal_id) and "+
            " ( FACS.CURRENCY_ID = GBIS.CURRENCY.ID )) as Tranche_Details ,"+
  "cursor( SELECT RTNGS.DEBT_INST_ABBR, "+
           " RTNG_VALS.VALUE  "+
           "FROM RTNGS, "+
          " RTNG_VALS  "+
          " WHERE ( RTNG_VALS.ID = RTNGS.RTNG_VALS_ID ) and "+
          "( ( RTNGS.DEAL_ID = A.deal_id ) ))  AS Rating_Details , "+
  "cursor( SELECT  "+
           "TR_DEAL_SOURCE.GROSS_UW_TXT, "+
            "TR_DEAL_SOURCE.ARRANGE_FEE_TXT, "+
            "TR_DEAL_SOURCE.ADMIN_FEE_TXT, "+
            "TR_DEAL_SOURCE.ARRANGE_FEE_USDE "+
            "FROM DEAL_SOURCE,   "+
            "TR_DEAL_SOURCE  "+
           "WHERE ( DEAL_SOURCE.DEAL_ID = TR_DEAL_SOURCE.DEAL_ID ) and  "+
            "( DEAL_SOURCE.DATA_SOURCE_ID = TR_DEAL_SOURCE.DATA_SOURCE_ID  ) and
"+
            "( ( TR_DEAL_SOURCE.DEAL_ID = A.DEAL_ID ) )) AS Fee_Details " +
           "FROM deal A,tr_deal_source,deal_source,tr_industry" +
           " WHERE  tr_deal_source.deal_id = A.deal_id" +
            " and tr_deal_source.deal_id = deal_source.deal_id " +
            " and tr_industry.tr_industry_id = tr_deal_source.tr_industry_id " +
            " and deal_source.data_source_id = 13 and" +
            " rownum < 2");
        qry.setRowsetTag("DEAL_INFORMATION");//tags encapsulating the whole doc
        qry.setRowTag("DEAL");              // sets the row separator tag
        qry.setRowIdAttrName("");
        qry.setRowIdAttrValue("");
        qry.useUpperCaseTagNames();                // use upper case tag names
        qry.keepObjectOpen(true);
        writeTo(qry.getXMLString(),"deals.xml");
        writeTo(qry.getXMLMetaData(qry.DTD,false),"deals.dtd");
        qry.getXMLMetaData();
        conn.close();
        qry.close();
//       Get the schema
//       qry.setMaxRows(2);           // set the maximum number of rows to be
returned
//      qry.setSkipRows(3);                  // numbers of rows to skipped
//      qry.setStylesheetHeader("my.xsl");            // the stylesheet spec.

//      qry.setStylesheetHeader("C:\\XML\\deals.xsl");
//      XMLDocument xmldoc = (XMLDocument)qry.getXMLDOM();
//      qry.setRowIdAttrName("cnt");    // sets the id attribute of the row
element
//      qry.useNullAttributeIndicator(true);      // use attr. to indicate
nullness
//      qry.setErrorTag("ERR");            // tag for errors writen to XML doc
//      qry.keepObjectOpen(true);
//      qry.set

//      String xmlMetaData = qry.getXMLMetaData(qry.DTD,true);
//      print out the result to the screen
//        System.out.println(" OUTPUT IS XML DATA :\n"+xmlString);
//     System.out.println(" OUTPUT IS XML META DATA :\n" + xmlMetaData);
//     Close the JDBC connection
  }
  private static  void writeTo(String str_xml,String as_filename) throws
IOException
  {

      FileWriter  out = new FileWriter("c:\\xml\\"+as_filename);
//      System.out.println(" OUTPUT IS XML DATA :\n"+str_xml);
      out.write(str_xml);
      out.close();
  }
  }

          Followup:

Yeah, it seems that my DTDs that are being generated do not validate in XMLSpy.
That's not a problem with the DTDs, it seems to be a problem in the way XMLSpy
is trying to validate the DTD.  It seems as if it's trying to validate an XML
document, not a DTD.  Since DTD's are not valid XML, the DTD seems to be
invalid.  Here's my sample based on a quick sample GenXMl class of my own:

<!DOCTYPE DEPARTMENTS [
<!ELEMENT DEPARTMENTS (DEPARTMENT)*>
<!ELEMENT DEPARTMENT (DEPARTMENT_NAME, MANAGER_ID?, EMPLOYEES)>
<!ATTLIST DEPARTMENT ID CDATA #REQUIRED>
<!ELEMENT DEPARTMENT_NAME (#PCDATA)>
<!ELEMENT MANAGER_ID (#PCDATA)>
<!ELEMENT EMPLOYEES (EMPLOYEES_ROW)*>
<!ELEMENT EMPLOYEES_ROW (EMPLOYEE_NAME?)>
<!ELEMENT EMPLOYEE_NAME (#PCDATA)>
]>

If you compare this with the recommendation or other W3C compliant DTDs, you'll
see that it's just fine.  I'm not sure why XMLSpy isn't working properly?

      Rating: 4
Where to
      start  November 22, 2002
Reviewer:
      A reader
Hi Sean ,
         I am novice to XML and have oracle 9i Release2 .
Where do you want me to start learning XML ? like books etc

Thanks in Advance

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-XML
the xml app developer guide

      Rating: 5
DTD
      Problem  November 23, 2002
Reviewer:  Ashok
       from USA
Thanks for answering the DTD issue.
I tried validating in Turbo XML from TIBCO i got the same error msg Internal
subset not supported.I tried getting the XML schema using the function
getXmlMetaData(SCHEMA)i got the same error using the above tools.
But the interseting thing is instead of using GetXmlMetaData i tried some thing
like this

Doc arr[] = qry.getXmlSchema()
String ls_xsd
//Find the total elements in the array
for(i =1 ,i++ ,i <= Total elements in the array)
{
 ls_xsd = ls_xsd + arr[i]
}

writeto(ls_xsd,"c:\xml\deals.xsd");

Output Generated from this  one is succesfully vaidated against both the
tools.Do you think this is the right way to generate the Metadata from the
generated XML.

Thx
Ashok

          Followup:

Yes, I'd say the "getXmlSchema" method would be the most appropriate way to get
the XML Schema :-).

      Rating: 2
Tree
      Structure  January 24, 2003
Reviewer:  Ken
       from Vancouver, Canada
This example was very illuminating, but we have a variation on this problem
where our data is in the form of a tree structure.
Could you provide an example of this using the Emp file tree structure
(mgr/emp#)?
ie:
select e.name from emp e
start with e.mgr  is null
connect by e.mgr = prior e.id

Smith & Doe work for King
Fudpucker works for Doe (not really in your table, but we like him)

Expected XML:

<Employee> <name> King</name>
   <Employee><name> Smith</name> </Employee>
   <Employee><name> Doe</name>
       <Employee><name> Fudpucker</name></Employee>
   </Employee>
</Employee>

Can this be done in a view?

          Followup:

Ken,
There is no out-of-the-box support for generating recursive nodes w/ a
connect-by query.  You could write a function to do what you're asking for, but
it would involve either hand-generating the XML in a connect by for loop or
using the XML parsers to dynamically construct the resulting XML document.

      Rating: 4
attribute-value
      in rowset tag  March 18, 2003
Reviewer:
      Umesh  from Franklin Park, NJ, USA
Is there any way to insert attribute-value pair in ROWSET tag? The value is a
sequence number.I am using oracle 9.0.1.

          Followup:

In 9.0.1, the only way to accomplish what you're trying to do would be through
the Java or PL/SQL DOM, or after generating the document using the XML SQL
Utility and post-processing the XML document using XSLT (See ht
tp://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1827993798549
and search for XSLT).

In Oracle9i Release 2, you can use new SQL extensions called "SQLX" (or SQLXML)
to generate your XML documents.  SQLX gives your VERY granular control over how
your documents are created, and would most likely give you better performance
than generating the document through the XML SQL Utility, then running it
through the XSLT processor or loading the document up into an in-memory DOM to
add attributes to the <ROWSET/> node(s).  For more information on SQLX, see ht
tp://otn.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96620
/xdb12gen.htm#1027862 or ht
tp://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6912626759561.

Hope that helps!

_smd_

      Rating: 4
attribute value
      in rowset tag  March 19, 2003
Reviewer:
      Umesh  from NJ, USA
Thanks for valuable suggestion. U saved lot of my time.

      Rating: 4
is it possible
      to generate this kind of xml from oracle?  April 27, 2003

Reviewer:  Pravesh from India
Tom/Sean Dillon

is it possible to generate this kind of xml from oracle?. (please see the below
xml)  ..If yes, which specific way of generating the xml i should follow. -
Plsql Package or SQLX or XSU or ...?

from your above example i see that you use the object relation approach of
creating types and varray - is this the best way to follow for my xml ?

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE ematrix SYSTEM "ematrixml.dtd">
<ematrix>

 <creationProperties>
 <release>9.5.2.1-Global</release>
 <datetime>2002-08-23T22:44:25Z</datetime>
 <event>export</event>
 <dtdInfo>&ematrixProductDtd;</dtdInfo>
 </creationProperties>

<businessObject id="00000.00000.00000.00000">
<objectType>SII Project</objectType>
    <objectName><![CDATA[Breakage Risk]]></objectName>
    <objectRevision>1181033367349434</objectRevision>
    <vaultRef>eService Production</vaultRef>
    <policyRef>SII Project</policyRef>
    <owner>
      <userRef>103013592</userRef>
    </owner>
    <description><![CDATA[Project Owner = J. Castro ; Team Driving=
]]></description>
    <creationInfo>
       <datetime>2002-10-01T08:17:37Z</datetime>
    </creationInfo>
    <modificationInfo>
       <datetime>2002-10-01T08:17:37Z</datetime>
    </modificationInfo>
    <attributeList count="4">
       <attribute>
          <name>Originator</name>
          <string>103013592</string>
       </attribute>
       <attribute>
          <name>Total Benefit CY1</name>
          <string>-289</string>
       </attribute>
       <attribute>
          <name>Is Funding Source Different</name>
          <string>TRUE</string>
       </attribute>
       <attribute>
          <name>Business Priority Code</name>
          <string></string>
       </attribute>
    </attributeList>
    <stateList count="4">
       <state>
          <name>Create</name>
          <stateStatus>enabled</stateStatus>
       </state>
       <state>
          <name>Assign</name>
          <current/>
       <stateStatus>enabled</stateStatus>
       <actualInfo>
          <datetime>2002-10-01T08:17:37Z</datetime>
       </actualInfo>
       </state>
       <state>
          <name>Evaluate</name>
          <stateStatus>enabled</stateStatus>
       </state>
       <state>
          <name>Approve</name>
          <stateStatus>enabled</stateStatus>
      </state>
    </stateList>
    <fromRelationshipList count="3">
       <relationship>
          <relationshipDefRef>Member</relationshipDefRef>
             <relatedObject>
                <businessObjectRef>
                   <objectType>Person</objectType>
                   <objectName>103013592</objectName>
                   <objectRevision>-</objectRevision>
                   <vaultRef>eService Production</vaultRef>
                </businessObjectRef>
             </relatedObject>
             <attributeList count="2">
                <attribute>
                   <name>Project Access</name>
                   <string>Project Owner</string>
                </attribute>
                <attribute>
                   <name>Project Role</name>
                   <string></string>
                </attribute>
             </attributeList>
       </relationship>
       <relationship>
       <relationshipDefRef>SII Project Cost Item</relationshipDefRef>
          <relatedObject>
             <businessObjectRef>
                <objectType>SII Cost Item</objectType>
                <objectName><![CDATA[Breakage Risk]]></objectName>
                <objectRevision>1201033367349434</objectRevision>
                <vaultRef>eService Production</vaultRef>
             </businessObjectRef>
          </relatedObject>
       </relationship>
       <relationship>
       <relationshipDefRef>SII Project Benefit Item</relationshipDefRef>
          <relatedObject>
             <businessObjectRef>
                <objectType>SII Benefit Item</objectType>
                <objectName><![CDATA[Breakage Risk]]></objectName>
                <objectRevision>1191033367349434</objectRevision>
                <vaultRef>eService Production</vaultRef>
             </businessObjectRef>
          </relatedObject>
       </relationship>
    </fromRelationshipList>
    </businessObject>
</ematrix>

      Rating: 5
function
      TRANSFORM_XML_WITH_XSL  September 08, 2003

Reviewer:  A reader
Tom,
Can the function TRANSFORM_XML_WITH_XSL be modified to return more than 1 XML
file?

I have an XML file and XSL file.  I would like to transform the single XML file
into multiple XML files.  Is that possible with the xslprocessor.processxsl or
can this be done at all in Oracle using PL/SQL?

Thank you

          Followup:

sorry, don't know -- don't really do very much with xml personally.

you can try devtrends.oracle.com -- Cameron O'Rourke is more of an "outside the
database" guy and works with that stuff.  He may have some ideas.

      Rating:
      5
Richard  October 26, 2003
Reviewer:
      A reader  from Washington
Hi Tom/Sean,

This is the first question I ever have submitted,

Oracle DBMS_XMLQUERY package generate canonical xml file like the following:
<?xml version="1.0" ?>
<ROWSET>
  <ROW>
    <FILE_ADD_COUNT>2</FILE_ADD_COUNT>
    <FILE_MODIFY_COUNT></FILE_MODIFY_COUNT>

    <TRAN_TYPE>A</TRAN_TYPE>
    <DOC_TYPE>CR</DOC_TYPE>
    <TRAN_TYPE>B</TRAN_TYPE>
    <DOC_TYPE>CR</DOC_TYPE>
  </ROW>
</ROWSET>

In order to generate a non-canonical xml per customer need for example
<?xml version="1.0" ?>
<FILE_HEADER>
    <FILE_ADD_COUNT>2</FILE_ADD_COUNT>
    <FILE_MODIFY_COUNT></FILE_MODIFY_COUNT>
    <DETAIL>
      <TRAN_TYPE>A</TRAN_TYPE>
      <DOC_TYPE>CR</DOC_TYPE>
    </DETAIL>
    <DETAIL>
      <TRAN_TYPE>B</TRAN_TYPE>
      <DOC_TYPE>CR</DOC_TYPE>
    </DETAIL>
</FILE_HEADER>

I use the the following XSLT clob, which does not make result xml clob return
file_header info, what is wrong with this XSLT
and also how to make dbms_xmlquery package return
element even if element associated table column value is null

<xsl:stylesheet version="1.0" xmlns:xsl="
">http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>
  <xsl:template match="/">
   <FILE_HEADER>
      <FILE_ADD_COUNT><xsl:value-of  select="FILE_ADD_COUNT"/></FILE_ADD_COUNT>
      <FILE_MODIFY_COUNT><xsl:value-of
select="FILE_MODIFY_COUNT"/></FILE_MODIFY_COUNT>
      <xsl:for-each select="ROWSET/ROW">
      <DETAIL>
         <TRAN_TYPE><xsl:value-of select="TRAN_TYPE"/></TRAN_TYPE>
         <DOC_TYPE><xsl:value-of select="DOC_TYPE"/></DOC_TYPE>
      </DETAIL>
      </xsl:for-each> <!-- ROWSET/ROW -->
   </FILE_HEADER>
  </xsl:template>
</xsl:stylesheet>';

Thanks for help and advice.

          Followup:

I asked Sean Dillon, our local XML technologist, to take a look at this and
here's what he had to say:
--

Hiya "a reader",

Yeah, your XSLT VALUE-OF elements are just a little off...  Since you are
matching "/" (<xsl:template match="/">) in the template, you have to fully
qualify any element value you want to select from the match point.  So instead
of saying...

<FILE_MODIFY_COUNT><xsl:value-of
select="FILE_MODIFY_COUNT"/></FILE_MODIFY_COUNT>

...you'd say...

<FILE_MODIFY_COUNT><xsl:value-of
select="FILE_HEADER/FILE_MODIFY_COUNT"/></FILE_MODIFY_COUNT>

So for your example, your stylesheet would look like so:

<xsl:stylesheet version="1.0" xmlns:xsl="
">http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>
  <xsl:template match="/">
   <FILE_HEADER>
      <FILE_ADD_COUNT>
        <xsl:value-of  select="FILE_HEADER/FILE_ADD_COUNT"/>
      </FILE_ADD_COUNT>
      <FILE_MODIFY_COUNT>
        <xsl:value-of select="FILE_HEADER/FILE_MODIFY_COUNT"/>
      </FILE_MODIFY_COUNT>
      <xsl:for-each select="FILE_HEADER/DETAIL">
        <TRAN_TYPE><xsl:value-of select="TRAN_TYPE"/></TRAN_TYPE>
        <DOC_TYPE><xsl:value-of select="DOC_TYPE"/></DOC_TYPE>
      </xsl:for-each> <!-- DETAIL -->
   </FILE_HEADER>
  </xsl:template>
</xsl:stylesheet>

You can see in the <xsl:for-each select="FILE_HEADER/DETAIL"> stylesheet
element, we only had to select "TRAN_TYPE" and "DOC_TYPE" from the source XML
doc.  This is because inside that <xsl:for-each/> we are selecting only the
children of FILE_HEADER/DETAIL.

Hope that helps!

_smd_

      Rating: 5
the last
      question correction  October 27, 2003
Reviewer:
      RICHARD  from WASHINGTON
Sorry, the source xml should look like following

Sorry, the question should be more simplier,

the source xml should be a number of records like

The source xml should be the following instead

<?xml version="1.0" ?>
<ROWSET>
<ROW>
<FILE_ADD_COUNT>3</FILE_ADD_COUNT>
<FILE_MODIFY_COUNT></FILE_MODIFY_COUNT>

<TRAN_TYPE>A</TRAN_TYPE>
<DOC_TYPE>CR</DOC_TYPE>
</ROW>
<ROW>
<FILE_ADD_COUNT>3</FILE_ADD_COUNT>
<FILE_MODIFY_COUNT></FILE_MODIFY_COUNT>

<TRAN_TYPE>B</TRAN_TYPE>
<DOC_TYPE>CR</DOC_TYPE>
</ROW>
<ROW>
<FILE_ADD_COUNT>3</FILE_ADD_COUNT>
<FILE_MODIFY_COUNT></FILE_MODIFY_COUNT>

<TRAN_TYPE>C</TRAN_TYPE>
<DOC_TYPE>CR</DOC_TYPE>
</ROW>
</ROWSET>

          Followup:

Hi Sean here again...  so yeah, same concepts apply:

<xsl:stylesheet version="1.0" xmlns:xsl="
">http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>
  <xsl:template match="/">
   <FILE_HEADER>
     <xsl:for-each select="ROWSET/ROW">
       <ROW>
         <FILE_ADD_COUNT><xsl:value-of
select="FILE_ADD_COUNT"/></FILE_ADD_COUNT>
         <FILE_MODIFY_COUNT><xsl:value-of
select="FILE_MODIFY_COUNT"/></FILE_MODIFY_COUNT>
         <TRAN_TYPE><xsl:value-of select="TRAN_TYPE"/></TRAN_TYPE>
         <DOC_TYPE><xsl:value-of select="DOC_TYPE"/></DOC_TYPE>
       </ROW>
     </xsl:for-each> <!-- ROW -->
   </FILE_HEADER>
  </xsl:template>
</xsl:stylesheet>

Good luck!

_smd_

October 30, 2003 Elie -- Thanks for the question regarding
      "Usefulness Of Materialized Views", version 9.2.0

      You Asked
Hi, Tom.

I have a general question about materialized views (MVs).

I have been playing with MVs for several weeks now and am confused about
something.

So long as a user submits a SQL query that is precisely the same as an MV's
underlying SQL query, the explain plan shows that the submitted query is, in
fact, using the MV.

However, if a user happens to, say, use a SELECT list in a submitted query that
is slightly different than the SELECT list in the MV's underlying SQL query,
then the explain plan shows that the MV is not being used.

So then, how can one practically use MV's since, in a DSS tool such as
Discoverer, who knows what columns a user might choose for a SELECT list or even
what columns might be included in the WHERE clause of a submitted query?

Am I supposed to create MV's for every possible query that a user might create
via a DSS tool or even through something like SQLPLUS?

Is there some way to create a single MV in which, say, all of the columns in all
of the tables making up the FROM list of the MV's underlying SQL query are
included so that, if a user chooses any such columns in a submitted query, then
Oracle will make use of the MV?

Thank you.

      and we said...
If you have my book -- i have various examples.  The select lists do not have to
match BUT the mv must be capable of answering the question.

If I create a MV that is select deptno, count(*) from emp group by deptno and
then ask for counts by hiredate - I won't be able to use the MV of course.

The MV is an extremely powerful tool -- it works just like an index would work.
So, I would ask the same question "of what use are indexes" -- well, very
useful.  Same with MV's.

In an pure ad-hoc environment, people might use OEM, with tracing and the OLAP
recommendation tools that tell you "hey, if you create this MV given your
workload, it'll be really useful".

As for that last paragraph -- think about it?  Why would that massive MV that
simply copies all of the data be useful or faster?  You are missing the point of
MV's.

Say you had a table full of census data.  280million + rows -- one row for each
of us.  You KNOW that a frequently asked question is "how many people live in my
state".  "how many people live in my zip code" and so on.  You create a couple
of MV's (like you would create a couple of indexes) to answer these frequently
asked questions rapidly.  Will you get *every* question covered -- probably not.
 Can you massively increase the resources available on your machine by reduing
the workload by these popular questions?  Most certainly.

Look at the MV as a tool -- like an index.
Review &
      Followup

      Rating:
      4
Query_rewrite  October 07, 2002
Reviewer:
      Suresh  from NY
Tom,
   May be Elie has to enable Query_rewrite here..What is your opinion.

          Followup:

she said that it works for some of the queries -- hence, it must already be
enabled.

without an example it is not possible to say but given the question, I would say
it is almost certainly a case of "the mv could not be used since it didn't have
the proper level of detail".

      Rating: 4
Usefulness Of
      Materialized Views  October 07, 2002
Reviewer:
      Elie Grunhaus  from Rochester, NY USA
Thanks, Tom.

Good response.

I guess my "missing the point" of materialized views, as you mentioned, is all
part of playing and learning.

I think it's time I got your book and learn some more.

Elie

      Rating: 4
MView not being
      used....  June 24, 2003
Reviewer:
      A reader
Hi tom,

I am confused why the Mview is not in this situation...Can you help me figure
out?

Thanks
--------------------------------------------------------
  1  create materialized view cpfact_mv
  2  build immediate
  3  refresh complete
  4  enable query rewrite
  5  as
  6  select
  7    CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.CDW_CAMPAIGN_ID,
  8    CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.CAMPAIGN_ID,
  9    count(CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.RESPONSES) RESPONSES,
 10    count(distinct CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.RESPONDER_COMPANY_ID)
RESPONDER_COMPANIES,
 11    count(distinct CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.RESPONDER_SITE_ID)
RESPONDER_SITES,
 12    count(distinct CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.LEAD_COMPANY_ID)
LEAD_COMPANIES,
 13    count(distinct CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.LEAD_SITE_ID)
LEAD_SITES,
 14    count(distinct CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.UNIQUE_LEAD) UNQ_LEADS,
 15    count(distinct CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.UNIQUE_RESPONDER)
UNQ_RESPONDER
 16  FROM
 17    CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW
 18  GROUP BY
 19    CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.CDW_CAMPAIGN_ID,
 20*  CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.CAMPAIGN_ID
CDW2TEST:SQL> /
Materialized view created.

CDW2TEST:SQL> analyze table cpfact_mv compute statistics;

Table analyzed.

CDW2TEST:SQL> set autotrace on explain
CDW2TEST:SQL>      select
  2        CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.CDW_CAMPAIGN_ID,
  3        CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.CAMPAIGN_ID,
  4        count(CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.RESPONSES) RESPONSES,
  5       count(distinct CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.RESPONDER_COMPANY_ID)
RESPONDER_COMPANIES,
  6       count(distinct CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.RESPONDER_SITE_ID)
RESPONDER_SITES,
  7       count(distinct CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.LEAD_COMPANY_ID)
LEAD_COMPANIES,
  8       count(distinct CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.LEAD_SITE_ID)
LEAD_SITES,
  9       count(distinct CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.UNIQUE_LEAD)
UNQ_LEADS,
 10       count(distinct CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.UNIQUE_RESPONDER)
UNQ_RESPONDER
 11     FROM
 12       CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW
 13     GROUP BY
 14       CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.CDW_CAMPAIGN_ID,
 15*    CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.CAMPAIGN_ID
 16  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=9 Bytes=189)
   1    0   TABLE ACCESS (FULL) OF 'CPFACT_MV' (Cost=1 Card=9 Bytes=18
          9)

CDW2TEST:SQL>    select
  2        CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.CDW_CAMPAIGN_ID,
  3        count(CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.RESPONSES) RESPONSES,
  4       count(distinct CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.RESPONDER_COMPANY_ID)
RESPONDER_COMPANIES,
  5       count(distinct CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.RESPONDER_SITE_ID)
RESPONDER_SITES,
  6       count(distinct CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.LEAD_COMPANY_ID)
LEAD_COMPANIES,
  7       count(distinct CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.LEAD_SITE_ID)
LEAD_SITES,
  8       count(distinct CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.UNIQUE_LEAD)
UNQ_LEADS,
  9       count(distinct CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.UNIQUE_RESPONDER)
UNQ_RESPONDER
 10     FROM
 11       CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW
 12     GROUP BY
 13*      CDW_MKT.DWD_MKT_CAMPAIGN_FACT_NEW.CDW_CAMPAIGN_ID
CDW2TEST:SQL> /
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=83 Card=3 Bytes=324)
   1    0   SORT (GROUP BY) (Cost=83 Card=3 Bytes=324)
   2    1     TABLE ACCESS (BY GLOBAL INDEX ROWID) OF 'DWD_MKT_CAMPAIG
          N_FACT_NEW' (Cost=83 Card=387037 Bytes=41799996)

   3    2       INDEX (FULL SCAN) OF 'CDWCPID' (NON-UNIQUE) (Cost=26 C
          ard=387037)

All i did was, took out a field CAMPAIGN_ID which is at the same level as
CDW_CAMPAIGN_ID (i.e, each CAMPAIGN_ID has surrogate CDW_CAMPAIGN_ID). The
results expected are same with or without the field  CAMPAIGN_ID. So why is
Mview not getting used. I am on oracle 8.1.7

Any idea?

          Followup:

have you met the requirements for MV rewrite?  like query_rewrite_enabled = ???
(session or system level setting)

      Rating: 3
Yes I
      did....  June 25, 2003
Reviewer:
      A reader
Hi Tom,

I did "ALTER SESSION SET QUERY_REWRITE_ENABLED=TRUE". That's why it was using
the MV in the first case if you see the explain plan for the first time.

Thanks

          Followup:

I see, it is the count(distinct ...)

think about it, we CANNOT rewrite using that MV, you would get the wrong answer.

think about what count(distinct ...) by A,B does.

then, think about what count(distinct...) by A does.

then think "can I take a count(distinct) by a,b and give you the count of
distinct values by A alone????"

the answer is no.

If I tell you for:

a=1, b=2  count(distinct c) = 1
a=1, b=3  count(distinct c) = 1

what can you say about the count(distinct c) by a?  You can say:

a) count(distinct c) could be 1 by a.  Since A is constant, C could have been
the number 5 in both rows above

b) count(distinct c) could be 2 by a.  C could have been 5 with b=2 and 7 when
b=3

so, the MV cannot be used.

      Rating: 3
But then
      again..  June 25, 2003
Reviewer:
      A reader
Hi tom,

Like i said in my initial posting, that count(distinct c) by a,b will fetch the
same result as count(distinct c) by a or count(distinct c) by b, as "a has one
to one relationship with b" i.e., "a represents the surrogate value for b".

Therefore, i think the mview should be used. Your thoughts?

          Followup:

You know it would be "all right"

I know (now) it would be "all right"

but how, pray tell, can the software know that is "all right"?  Because IN
GENERAL, it is not "all right"

      Rating: 5
Error in
      MV  July 02, 2003
Reviewer:  Jab
Tom, I created a regular view with the same definition as the mv below. It works
fine. But when I copied the same definition to the mv, I get that error. Can you
explain why ?

SQL> ed
Wrote file afiedt.buf

  1  CREATE MATERIALIZED VIEW "SYSTEM"."MV_UM_EQPTO"
  2  BUILD IMMEDIATE
  3  REFRESH FORCE
  4  ON COMMIT
  5  AS
  6  select
  7     obt_po.*,
  8     obt_con_por.id_con_porta as id_con_porta_extremo,
  9     obt_con_por.subinterface,
 10
SUBSTR(api_rede.obter_label_porta(obt_con_por.id_con_porta,obt_con_por.nivel),1,2
0) as porta_co
 11     proto.protocolo,
 12     vel.velocidade,
 13     con.id_conexao,
 14      con.id_operadora,
 15     con.id_protocolo,
 16     con.id_velocidade,
 17      con.id_finalidade,
 18      con.id_rede_conectada,
 19      con.eqpto_ext_b,
 20      con.id_status,
 21      con.desig,
 22      con.obs,
 23     ip.ip_porta_a as ip,
 24     ip.mascara_ip_a as mask_ip,
 25     fr.dlci,
 26     fr.cir,
 27     fr.pir,
 28     fr.id_fr_lmi,
 29     atm.pcr,
 30     atm.scr,
 31     atm.vcd,
 32     atm.vci,
 33     atm.vpi,
 34     e1.ts_ini,
 35     e1.ts_fim,
 36      rede_con.descricao as rede_conectada,
 37      rel.id_conexao_tipo
 38  from
 39  api_rede.obter_conexoes_por_porta obt_con_por,
 40  api_equip.obter_porta_de_eqpto obt_po,
 41  db_rede.conexao con,
 42  db_rede.protocolo proto,
 43  db_rede.velocidade vel,
 44  db_rede.fr fr,
 45  db_rede.atm atm,
 46  db_rede.e1 e1,
 47  db_rede.ip ip,
 48  db_rede.rede_conectada rede_con,
 49  db_rede.rel_conexao_tipo_finalidade rel
 50  where
 51    obt_con_por.id_porta = obt_po.id_porta and
 52    obt_con_por.id_con_porta = con.id_con_porta_a and
 53    con.id_protocolo = proto.id_protocolo and
 54    con.id_velocidade = vel.id_velocidade(+) and
 55    con.id_conexao = fr.id_conexao(+) and
 56    con.id_conexao = atm.id_conexao(+) and
 57    con.id_conexao = e1.id_conexao(+) and
 58    con.id_conexao = ip.id_conexao(+) and
 59     con.id_rede_conectada = rede_con.id_rede_conectada(+) and
 60*    con.id_finalidade = rel.id_finalidade
SYSTEM@ORAHMG> /
api_rede.obter_conexoes_por_porta obt_con_por,
         *
ERROR at line 39:
ORA-01031: insufficient privileges

          Followup:

never ever never create things as SYSTEM.  that is not your schema.  don't use
it.

make sure you've met the basic prerequisites for MV creation:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/statements_63a.htm#2063839

      Rating:
      5
  July 02, 2003
Reviewer:
      A reader

      Rating:
      4
MV  October 30, 2003
Reviewer:  ARC
       from S'Pore
Hi Tom,
I have a MV called mv_test.
Shall I write a query like this ?
'select * from mv_test;'
Please explain.

Thanks
ARC

          Followup:

you can

you don't have to.

read the docs (data warehouse guide).  they describe this feature in some detail

October 31, 2003 Thet -- Thanks for the question regarding "How to
      capture user id from a table while auditing another", version 9.2.0.3

      You Asked
Hi Tom:

I recently picked up your EOD and EOOO books and am truly enjoying them. They
are quite complimentary to each other. Great job. Thanks much indeed.

I am being asked to keep track of who does what when (fine grained auditing) on
a database (someone else designed).  It is a customer facing java based medical
information management application to be hosted on the Internet.

The problem is that the application accesses the database using a single
database user id.  Thus simply turning on the auditing will not capture who
performs SELECT, INSERT, UPDATE, DELETE on the tables invovled.

The database is highly encapsulated. It is more like a data dump than a
database. SQL is being generated by a databus object. This would require code
change to the application.

What would be the best strategy to capture the authenticated user id and how to
best integrate it with FGA?

Here are a few ideas I have entertained but the question still remains on how to
integrate a chosen approach with built in FGA.

1) Capture the session id, user id, and session status when the application
authenticates the user. Then include the session id in the every SQL statement
executed against the database.

2) Instead of the session id, include the user id in every SQL statement sent.

3) Write a generic stored proc to handle ALL the SQL statements (performance
issues, efficiency issues, and the amount of code needed to be truly generic)

4) Create Oracle users for each registered user and have the application connect
using this uder id. This raises a question on efficiency in connection pooling.
In current design, there is only one user connecting to the database. The
application takes advantage of connection pooling at the Java and JDBC level.

Are there other options? What would be the best approach? Give up FGA and write
triggers?

Thanks much,

tw-
---

      and we said...
since there is no authenticated userid in the database, there isn't too much WE
can actually do.

the java application that is so badly abusing the database will have to pass
this information in -- they, they are the ONLY ones that might even have a clue
as to "who is knocking on the door".

1) the session id -- that would most likely NOT WORK.  they are in all
probability using a connection pool.  meaning -- the session will migrate from
user to user to user.

and I don't see how including (or even how you would include) the session id in
"every query"?

2) how would they "include" the userid?

3) major application change

4) you could just have the java application use dbms_session to set a
client_identifier.  This will be audited in the audit trail normally.  The java
application would do this after each connection pool "grab" or connection to the
database.

triggers won't help.  there is nothing in the database right now that gives you
ANY idea who the real user is.  The application is going to have to tell you
this, there is no other way.

It is a shame.  Security added as an afterthought.  Imagine if your system were
a car.  You buy it to discover "what -- no locks!  no key!  just get in and
drive!?!?!"  I can see it now, you'd be wrapping a big chain and padlock around
the car everytime you parked.  Not pretty, not easy, not elegant.

Good thing security is designed INTO cars, instead of bolted on AFTER the fact!

Think about that ;)

Review &
      Followup

      Rating: 3
Perhaps I did
      not make it clear  October 25, 2003
Reviewer:
      Thet Win  from CA, USA
Thanks, Tom, for your reply.

>since there is no authenticated userid in the database,
>there isn't too much WE can actually do.
I think I did not clarify about the authentication the java application uses.
There are a number of user tables in the database (person, group, device) that
the application consults with for registration and authentication. But, the
userids are just entries in a table not actual Oracle userids.

>4) you could just have the java application use
>dbms_session to set a client_identifier.
This sounds like the only possible approach. I will look into this. Currently,
the application only generates SQL queries. Only tables, indexes, and some
sequences are in the database.

Follow up questions:
1) What are your thoughts on data encapsulation and could you point me to any
URL or books on this?
2) What would be a good source on what should the database handle vs. what the
java application does for Internet based 3 tier Java/XML applications?

Thanks,

tw-
---

          Followup:

exactly -- since there is no authenticated user in the database, since all
security is convienently hidden in a program outside the database, there isn't
too much WE can actually do.

The java program MUST do it -- the java program MUST provide us the identity in
some fashion.

1) data encapsulation is good.  But not if the only way to get to the data is
via this encapsulation.  Encapsulation is an APPLICATION concept.  many
applications should be using the data -- perhaps in many different ways.
encapsulation should not be used to preclude other access, only as a programming
construct to make things "easier"

2) depends on whose camp you are in.  "its all about java" or "its all about the
data".

I'm in the "its all about the data" camp personally.  applications come,
applications go.  programming paradigms are a dime a thousand, they come and go
even faster then applications.  If you listened to people in the early 90's --
we wouldn't even be writing software today, just assembling components as object
oriented techniques would have made software development obsolete.  Every object
we need would have already been coded --  just waiting to be used.

      Rating:
      4
Thanks  October 25, 2003
Reviewer:  Thet
      Win  from CA, USA
Hi Tom:

Thank you so much for your reply.

I am also, of course, in the database camp. However, I am trying find a good
balance. It looks like it is too late to do anything useful (for this release)
and I will start plan for gen 2. of this product where I hope to take advantage
of new 9.2 features.

Thanks again.

tw-
---

      Rating: 4
Using
      dbms_session in Fine Grained Auditing  October 30, 2003

Reviewer:  Thet Win  from CA
>4) you could just have the java application use
> dbms_session to set a
> client_identifier.  This will be audited in the audit
> trail normally.  The java application would do this after
> each connection pool "grab" or connection to the
> database.

How do you set the client_identifier from the Java App while executing a SQL
command like 'SELECT user_group from ACCOUNT_GROUP where user_name = 1:' ?

Thanks.

          Followup:

you set it BEFORE executing such a sql statement.

you call dbms_session.set_identifier

      Rating: 4
Using
      dbms_session in Fine Grained Auditing   October 31, 2003

Reviewer:  Jeroen  from Singapore
Hi Tom,

We are developing a J2EE application that is connecting to the database with a
shared user and thereby concealing the identify of the true end user.

As an alternative to using dbms_session to set the client identifier we were
considering passing the user name within a hint inside the SQL statement as this
would then be passed through to a trace file (e.g. SELECT /*+ username */ ... )

1. Any thoughts on this approach ? I know it will result in a hard parse for
each SQL statement submitted but it does ensure that if we were to trace the SQL
to a file it would identify the user executing that statement (except for
triggers).

2. Also is there any feature in 10g that would provide for visibility of a
user's identity between an application server and the database ?

          Followup:

1) I would not even dream in a gazillion years of such an approach.

It would mean that you don't even use a single stored procedure either -- which
means you've already done something big time wrong.

please don't even CONSIDER this approach

2) ummm, its there in 9i.  It is called ntier proxy authentication.  (search for
proxy authentiation on the documentation).  short of using the client identifer
that is another way to accomplish this

      Rating: 3
Using
      dbms_session in Fine Grained Auditing   October 31, 2003

Reviewer:  Thet Win  from CA, USA
Hi Tom and Jeroen:

The following article by Cameron O'Rourke on OTN talks about both proxy
authentication and using client_identifier. It provides code samples as well.

http://otn.oracle.com/oramag/oracle/03-may/o33trends.html
Thanks,

tw-
---

October 31, 2003 Betty -- Thanks for the question regarding
      "Backup/Recovery", version 8.1.6

      You Asked
I am recommeding a Backup/Recovery Strategy to be used by
multiple clients.  The question was asked why not use O/S
backups instead of RMAN?  I explained that this is not
recommended by Oracle, but could not tell them exactly why.
Could you please help me so that O/S will not be the only backup
we use.
Thanks,
Betty

      and we said...
There is nothing wrong with doing just OS backups.  It is what I
do with my 2 databases I manage.  OS backups are just as valid
as RMAN backups.  RMAN is a great tool but it is not the only
way to do it.  Many people still prefer using a scripting tool
of there choice such as perl or ksh to do this.

RMAN is good if you have lots of databases.  The catalog it uses
remembers lots of details for you.  You don't have as much to
think about.

RMAN is good if you do not have good "paper work" skills in
place.  Using OS backups, it is more or less upto you to
remember where they are, what they are called and so on.  You
have to do all of the book keeping RMAN would do.

RMAN provides incremental backups, something you cannot get
without RMAN.

RMAN provides tablespace point in time recovery.  You can do
this without RMAN but you have to do it by yourself and it can
be rather convoluted.

RMAN is more integrated with OEM.  If you do OS backups, you'll
have to do everything yourself.  With RMAN you may have less
scripting to develop, test and maintain.

Review &
      Followup

      Rating: 4
Rman
      Backup  June 20, 2003
Reviewer:  Himnish
      Narang  from Chandigarh, India
Hi Tom,

I am getting ready to implement the RMAN backup. I have one doubt in my
strategy. I 'll be using a catalog to implement the rman backup. What will
happen if I lost my recovery catalog? I have not been able to find any
documentaion on this point. Please help me in thos regard.

Thanks in advance.

          Followup:

well, one would hope you would backup your recovery catalog and not "lose it"
but if you did, the control files for the database are sufficient.  You'll have
lost your bookkeeper -- but not your backups.

Suggestion: don't lose your recovery catalog, you wouldn't lose your other
databases would you?

also, goto metalink.oracle.com and read:
Article-ID:         <Note:174623.1>

      Rating: 5
data and
      time  July 01, 2003
Reviewer:  Reader

Tom, how do i get time column that shows me date and time including seconds in
the following output. Thanks. I am using 9.2.0 on windows platform.

RMAN> list backupset; <--------- DOES NOT LIST THE UNFINSIHED BACKUPSET FILES

List of Backup Sets
===================

BS Key  Type LV Size       Device Type Elapsed Time Completion Time
------- ---- -- ---------- ----------- ------------ ---------------
1       Full    625M       DISK        00:03:41     01-JUL-03
        BP Key: 1   Status: AVAILABLE   Tag: TAG20030701T180632
        Piece Name: C:\ORACLE\ADMIN\RMANBACKUP\DF_PRACTICE_1_1.BUS
  SPFILE Included: Modification time: 29-JUN-03
  List of Datafiles in backup set 1
  File LV Type Ckp SCN    Ckp Time  Name
  ---- -- ---- ---------- --------- ----
  1       Full 3944775    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\SYSTEM01.DBF
  2       Full 3944775    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\UNDOTBS01.DBF
  3       Full 3944775    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\CWMLITE01.DBF
  4       Full 3944775    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\DRSYS01.DBF
  5       Full 3944775    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\EXAMPLE01.DBF
  6       Full 3944775    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\INDX01.DBF
  7       Full 3944775    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\ODM01.DBF
  8       Full 3944775    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\TOOLS01.DBF
  9       Full 3944775    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\USERS01.DBF
  10      Full 3944775    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\XDB01.DBF
  11      Full 201816     05-JUN-03 C:\ORACLE\ORADATA\PRACTICE\READONLY01.DBF
  12      Full 3944775    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\FREELIST_DEMO01.D
BF
  13      Full 3944775    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\DUMMY01.DBF

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96566/rcmconfg.htm#450840

[tkyte@tkyte-pc Desktop]$ export NLS_DATE_FORMAT="dd-mon-yyyy hh24:mi:ss"

[tkyte@tkyte-pc Desktop]$ rman target=/

Recovery Manager: Release 9.2.0.3.0 - Production
Copyright (c) 1995, 2002, Oracle Corporation.  All rights reserved.
connected to target database: ORA920 (DBID=2494788706)

RMAN> list backupset
2> ;

using target database controlfile instead of recovery catalog

List of Backup Sets
===================

BS Key  Type LV Size       Device Type Elapsed Time Completion Time
------- ---- -- ---------- ----------- ------------ --------------------
1       Full    79M        DISK        00:02:00     02-jul-2003 07:23:54
        BP Key: 1   Status: AVAILABLE   Tag: TAG20030702T072153
        Piece Name: /d02/oradata/bkup/df_ORA920_8_1.bus

      Rating: 5
How to recover
      control files without recovery catalog  October 31, 2003

Reviewer:  Olaf  from Germany
Tom,

I just red your new book and tried to verify my backup strategy (make a test
recovery)
I always use the RMAN without catalog database. What happens if I'll loose my
contolfiles? I always include contol file in my backup.

32> # Control file backup
33>
34> ALLOCATE CHANNEL ch00
35>     TYPE 'SBT_TAPE';
36> SEND 'NB_ORA_CLIENT=mydatabase02';
37> BACKUP
38>     FORMAT 'bk_u%u_s%s_p%p_t%t'
39>     CURRENT CONTROLFILE;
40> RELEASE CHANNEL ch00;
41> }

After each backup I have a three backup files on my tape.
datafiles, archivelogs and controlfiles: so I know the identifer for my backup
of control file.

RMAN> connect target /
connected to target database: BRRCAT01 (not mounted)
RMAN> set dbid=2857051879
executing command: SET DBID
RMAN> run {ALLOCATE CHANNEL c1 DEVICE TYPE sbt;
2> RESTORE CONTROLFILE FROM '/bk_u06f56i1f_s6_p1_t508774447';
3> }
using target database controlfile instead of recovery catalog
allocated channel: c1
channel c1: sid=13 devtype=SBT_TAPE
channel c1: VERITAS NetBackup for Oracle - Release 4.5GA (00000000)
Starting restore at 31-OCT-03
channel c1: restoring controlfile
channel c1: restore complete
replicating controlfile
input filename=D:\ORACLE\ORADATA\BRRCAT01\CONTROL01.CTL
output filename=D:\ORACLE\ORADATA\BRRCAT01\CONTROL02.CTL
output filename=D:\ORACLE\ORADATA\BRRCAT01\CONTROL03.CTL
Finished restore at 31-OCT-03
released channel: c1

It is all works fine. Now I can mount my database.
But have I to reset log now? I always thought I can recover until last
transaction if I use archive log mode and I have no changes of the database
structure (no new datafiles since last backup of control file).

My questions:
how can I recover without to do reset log in this case,
when should I made the backup of control files:
after each TS or datafile creations,
where can I find the DB ID if db not mounted (RMAN not reports it by connection
as it is mentioned in Oracle docu)

Thanks in advance

Olaf

          Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96566/rcmrecov.htm#1009235

      Rating: 5
DB
      ID  October 31, 2003
Reviewer:
      A reader
Tom,

I still not found how can I find my DBID if the db not mounted.

Olaf

      Rating: 4
db
      id  October 31, 2003
Reviewer:  Reader

You can look at the rman autobackup controlfile name and it has dbid appended to
it.

October 31, 2003 Azman -- Thanks for the question regarding
      "Password Security During Logins", version 8.0.6

      You Asked
I understand that setting
ORA_ENCRYPT_LOGIN = True in client machine will ensure that
password will be sent in an encrypted form from client to
server. Does ths works if we are using odbc to communicate
between client and server?

      and we said...
You do not need to set that anymore - it was valid in 7.1.
Today, passwords are encrypted by default.

Yes, it'll encrypt the password in ODBC since ODBC uses Net8 and
Net8 is the layer that does the encryption.  Note however that
if you set ODBC tracing on -- it will tend to record the
password in cleartext as the password is not encrypted until
they give it to us.
Review & Followup

      Rating:
      4
  October 06, 2003
Reviewer:  Tarek

As far as i know the password is not encrypted when a user try to connect to the
database using net8 layer, right?
I know that to encrypt network connection the Advanced Security "option" of
enterprise edition should be used.
Am i wrong?
Is it enough to put ORA_ENCRYPT_LOGIN = True in the client machine?
So what do you mean with
"Today, passwords are encrypted by default"
When are they encrypted by default?

Thanks
Tarek

          Followup:

yes it is.

passwords are encrypted BY DEFAULT using net8 -- meaning, you need to do
nothing, install no additional software, just use it out of the box and the
passwords are transmitted encrypted.

      Rating:
      5
encryption  October 06, 2003
Reviewer:
      Spock
Tarek, you can encrypt your client/server connection using:

SQLNET.ENCRYPTION_CLIENT=...
SQLNET.ENCRYPTION_SERVER=...

http://download-west.oracle.com/docs/cd/B10501_01/network.920/a96581/sqlnet.htm#436668
May the force be with you

      Rating:
      5
  October 06, 2003
Reviewer:
      A reader
Tom, will the above really encrypt the connection between client and sever ? Is
it part of 9ir2 EE ?

BTW:

Gee, Dr. Spock... It's not Tarek, it's Sarek.

And "The force be with you" is from Star Wars, not Star Trek. The right greeting
is "Live long and prosper!"

          Followup:

Dr. Spock wrote books on rearing human children.  My mom used to read his stuff.

Mr. Spock -- he might have known Sarek :)

you need to do NOTHING, nada, nunca, to get the passwords encrypted.  It just
happens, has happened for many many many years.

You need to do NOTHING

      Rating:
      5
  October 06, 2003
Reviewer:
      A reader
Ok, Capitain Tom ;)))))))

The parameters described by MISTER Spock are valid for the entire connection,
right ?

Are they shipped with 9ir2 EE, after all -- or are they an "option" (bought
separately), as they used to be part of the Advanced Networking Option  ?

          Followup:

those parameters are for the Advanced Security Option (ASO)

they do it for the entire connection -- all data, not just the password.

not shipped with EE, ASO is a for fee option.

      Rating: 4
Was there ever
      a cleartext retry option?  October 07, 2003

Reviewer:  Justin  from Boise, ID
Since Oracle 8 clients can communicate with an Oracle 7 database, is or was
there a fallback path in the Net8 code where it would try with an unencrypted
password, or did all versions of Oracle 7 accept the default Oracle 8 encrypted
passwords?

          Followup:

well, only 7.3 was supported and it supported encrypted passwords (its been a
long long time..)

This was implemented in Oracle7 Release 7.1. If you use an old tool (i.e. a tool
created with a prior version of the RDBMS libraries) the login routines will
fall back to the old "send the password in clear text" mode. To prevent users
from accidentally revealing their passwords in this way, the dba can set the
ora_encrypt_login and dblink_encrypt_login variables to true. If this is done
and the user attempts to login with an old tool, they will receive an error

      Rating: 3
Looks okay
      but.....  October 29, 2003
Reviewer:  Jahudi
       from The Netherlands
Tom,

Does this also count for a role password?

Thanks,

Jahudi

          Followup:

nope, that is just a dml statement -- you need ASO (advanced security) for
encrypting the entire conversation

      Rating:
      5
  October 29, 2003
Reviewer:  Mike
       from TX
"
those parameters are for the Advanced Security Option (ASO)

they do it for the entire connection -- all data, not just the password.

not shipped with EE, ASO is a for fee option.
"

Please confirm: the loging passwd is always encrypted without explicitly setup
the net8's ASO?

          Followup:

correct, the password you use to login with is encrypted regardless.

      Rating: 3
It is possible
      to encrypt data stream even without ASO  October 31, 2003

Reviewer:  Piotr Jarmuz  from Poznan, Poland

Just in case someone would like to know...

If your client machine is UNIX, Linux or Windoze with unix emulation layer e.g.
Cygwin then it is trivial to set up SSL encryption even without ASO.

1. Make sure that your database server is running ssh daemon (in the world of
Unix this is standard).

2. Start ssh client on the client machine in local forwarding mode. For your
convenience you may want to set up public key authentication before so that you
don't need the passwords.

$> ssh -NfL 1521:localhost:1521 your.db.server

3. Edit tnsnames.ora file on the client to contain the entry like this:

SECURED_SERVER =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SID = your_original_server_sid)
    )
  )

4. Use sqlplus to test the connection:

$> sqlplus username/password@secured_server

5. Enjoy secure data flow :)

If your client is a "naked" Windows machine (without reasonable set of unix-like
tools) it might even work but I have never tested...

          Followup:

very nice!  thanks!

      Rating: 4
It does work
      even for windoze  October 31, 2003
Reviewer:
      Mario  from Rome, Europe
Hi Tom and Piotr,

it does work even for windoze: you can
use SecureCRT for 'port forwarding' on
windoze client.

many thanks Piotr.

Mario from Rome, Europe

P.S. ... and yes from time to time I use windows ;-(

October 31, 2003 Venkat -- Thanks for the question regarding "How
      to send more than 32K message", version 8.1.7

      You Asked
Hi Tom,

    I have written a mail sending program. Message contents is
generated automatically based on some condition. I am holding
message contents in a field of data type varchar2. Atlast this is
passed to package for sending mail. When message content exceeds
32K, it gives error. how to handle this situation. Do we have any
other data type field which supports size of 1 or 2 GB. It would
really nice if you give me an example to do this.

Thanks in Advance,
Venkat

      and we said...
You would have to use a CLOB (temporary clob) and pass the CLOB to your routine
instead of a varchar2.  You can only have 32k of text in a varchar2 variable.
You can put upto 4gig of text into a CLOB.
Review &
      Followup

      Rating: 2
How to send
      more than 32K message  July 02, 2002
Reviewer:
      A reader
Tom,

  It would be really helpful for me if you can give me an example. because I am
not much aware of lob.
I think i am asking too much from you.

Thanks,
Venkat

          Followup:

Surprisingly -- there are actually documents describing this!

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/index.htm
application developers guides for large objects....

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_clob    clob;
  3  begin
  4      dbms_lob.createTemporary( l_clob, TRUE );
  5
  6      for i in 1 .. 100
  7      loop
  8          dbms_lob.writeAppend( l_clob, 32000, rpad('*',32000,'*') );
  9      end loop;
 10
 11      dbms_output.put_line( 'I have put ' || dbms_lob.getLength(l_clob) || '
bytes of data into a clob!' );
 12  end;
 13  /
I have put 3200000 bytes of data into a clob!

PL/SQL procedure successfully completed.

      Rating:
      5
  July 02, 2002
Reviewer:  Lee
       from USA
I have a proc that uses Tom's html email proc with some LOB modification, I'll
posted here if Tom do not mind.

          Followup:

Sure.  Go for it. (try to keep it 72 characters wide)

      Rating: 5
How to send
      more than 32K message  July 02, 2002
Reviewer:
      A reader
Hi Tom,

    Thank you very very...............very much. this really
helped me.

Venkat

      Rating:
      5
  July 02, 2002
Reviewer:  Lee

This is the function, I have a nother one that wil parse an sql statment and
return the results as an HTMS then call this function with the results. Again,
if you like I can post that one too.

I hope it's helpfull.

P.S.
Tom, if you see anything that can be improved I'll appritiate your comments
(arter all, it is your proc)

 FUNCTION HTML_EMAIL(P_TO            IN VARCHAR2,
                                             P_FROM          IN VARCHAR2,
                                             P_SUBJECT       IN VARCHAR2,
                                             P_TEXT          IN VARCHAR2 DEFAULT
NULL,
                                             P_HTML          IN VARCHAR2 DEFAULT
NULL,
                                             P_SMTP_HOSTNAME IN VARCHAR2 DEFAULT
'MAIL.DEALTIME.COM',
                                             P_SMTP_PORTNUM  IN NUMBER DEFAULT
25,
                                             P_LOB_HTML IN CLOB DEFAULT NULL)
                          RETURN VARCHAR2
        IS
            L_BOUNDARY      VARCHAR2(255) DEFAULT 'a1b2c3d4e3f2g1';
            L_CONNECTION    UTL_SMTP.CONNECTION;
            L_BODY_HTML     CLOB := EMPTY_CLOB;  --THIS LOB WILL BE THE EMAIL
MESSAGE
            L_OFFSET        NUMBER;
            L_AMMOUNT       NUMBER;
            L_TEMP          VARCHAR2(32767) DEFAULT NULL;
            V_TO VARCHAR2(1000) := LOWER(P_TO);
            V_FLAG BOOLEAN := FALSE;

            V_PROC_STEP VARCHAR2(4000);

        BEGIN

             V_PROC_STEP := 'STEP 1';

             IF V_TO NOT LIKE '%lsagi@dealtime.com%' THEN
                  V_TO := V_TO||';lsagi@dealtime.com';
             END IF;

             V_PROC_STEP := 'STEP 1.5';

            L_CONNECTION := UTL_SMTP.OPEN_CONNECTION( P_SMTP_HOSTNAME,
P_SMTP_PORTNUM );
            UTL_SMTP.HELO( L_CONNECTION, P_SMTP_HOSTNAME );
            UTL_SMTP.MAIL( L_CONNECTION, P_FROM );
--            UTL_SMTP.RCPT( L_CONNECTION, P_TO );

            L_TEMP := L_TEMP || 'MIME-Version: 1.0' ||  CHR(13) || CHR(10);

            V_PROC_STEP := 'STEP 2';

            IF INSTR(V_TO, ';', 1, 1) = 0 THEN
                   L_TEMP := L_TEMP || 'To: ' || V_TO || CHR(13) || CHR(10);
                   UTL_SMTP.RCPT( L_CONNECTION, V_TO );
            ELSE
                 WHILE V_FLAG = FALSE LOOP
                       SQL_STM := SUBSTR(V_TO, 1, INSTR(V_TO, ';', 1, 1) - 1);
                       V_FLAG := INSTR(V_TO, ';', 1, 1) = 0;
                       V_TO := NVL(SUBSTR(V_TO, LENGTH(SQL_STM) + 2), V_TO);

                       L_TEMP := L_TEMP || 'To: ' || NVL(SQL_STM, V_TO) ||
CHR(13) || CHR(10);
                       UTL_SMTP.RCPT( L_CONNECTION, NVL(SQL_STM, V_TO));

                 END LOOP;
            END IF;

            L_TEMP := L_TEMP || 'From: ' || P_FROM || CHR(13) || CHR(10);
            L_TEMP := L_TEMP || 'Subject: ' || P_SUBJECT || CHR(13) || CHR(10);
            L_TEMP := L_TEMP || 'Reply-To: ' || P_FROM ||  CHR(13) || CHR(10);
            L_TEMP := L_TEMP || 'Content-Type: multipart/alternative; boundary='
||
                                 CHR(34) || l_BOUNDARY ||  CHR(34) || CHR(13) ||

                                 CHR(10);

            ----------------------------------------------------

            V_PROC_STEP := 'STEP 3';

            -- Write the headers
            DBMS_LOB.CREATETEMPORARY( L_BODY_HTML, FALSE, 10 );
            DBMS_LOB.WRITE(L_BODY_HTML,LENGTH(L_TEMP),1,L_TEMP);

            ----------------------------------------------------

            V_PROC_STEP := 'STEP 4';

            -- WRITE THE TEXT BOUNDARY
            L_OFFSET := DBMS_LOB.GETLENGTH(L_BODY_HTML) + 1;
            L_TEMP   := '--' || L_BOUNDARY || CHR(13)||CHR(10);
            L_TEMP   := L_TEMP || 'content-type: text/plain; charset=us-ascii'
||
                          CHR(13) || CHR(10) || CHR(13) || CHR(10);
            DBMS_LOB.WRITE(L_BODY_HTML,LENGTH(L_TEMP),L_OFFSET,L_TEMP);

            ----------------------------------------------------

            V_PROC_STEP := 'STEP 5';

            IF P_TEXT IS NOT NULL THEN
                -- WRITE THE PLAIN TEXT PORTION OF THE EMAIL
                L_OFFSET := DBMS_LOB.GETLENGTH(L_BODY_HTML) + 1;
                DBMS_LOB.WRITE(L_BODY_HTML,LENGTH(P_TEXT),L_OFFSET,P_TEXT);
            END IF;

            ----------------------------------------------------

            V_PROC_STEP := 'STEP 6';

            -- WRITE THE HTML BOUNDARY
            L_TEMP   := CHR(13)||CHR(10)||CHR(13)||CHR(10)||'--' || L_BOUNDARY
||
                            CHR(13) || CHR(10);
            L_TEMP   := L_TEMP || 'content-type: text/html;' ||
                           CHR(13) || CHR(10) || CHR(13) || CHR(10);
            L_OFFSET := DBMS_LOB.GETLENGTH(L_BODY_HTML) + 1;
            DBMS_LOB.WRITE(L_BODY_HTML,LENGTH(L_TEMP),L_OFFSET,L_TEMP);

            ----------------------------------------------------

            V_PROC_STEP := 'STEP 7';

            BEGIN
                    IF P_HTML IS NOT NULL THEN
                        -- WRITE THE HTML PORTION OF THE MESSAGE
                        L_OFFSET := DBMS_LOB.GETLENGTH(L_BODY_HTML) + 1;

                        V_PROC_STEP := 'STEP 7.1';

DBMS_LOB.WRITE(L_BODY_HTML,LENGTH(P_HTML),L_OFFSET,P_HTML);

                        V_PROC_STEP := 'STEP 7.2';

                    ELSIF P_LOB_HTML IS NOT NULL THEN
                          -- WRITE THE LOB HTML PORTION OF THE MESSAGE
                          L_OFFSET := DBMS_LOB.GETLENGTH(L_BODY_HTML) + 1;

                          V_PROC_STEP := 'STEP 7.3';

                          DBMS_LOB.COPY(L_BODY_HTML, P_LOB_HTML,
DBMS_LOB.GETLENGTH(P_LOB_HTML), L_OFFSET, 1);
                    END IF;
            EXCEPTION
                          WHEN OTHERS THEN
                                DBMS_LOB.WRITE(L_BODY_HTML,LENGTH('STEP 7 ~
'||SQLERRM),L_OFFSET, 'STEP 7 ~ '||SQLERRM);
             END;

            ----------------------------------------------------

            V_PROC_STEP := 'STEP 8';

            -- WRITE THE FINAL HTML BOUNDARY
            L_TEMP   := CHR(13) || CHR(10) || '--' ||  L_BOUNDARY || '--' ||
CHR(13);
            L_OFFSET := DBMS_LOB.GETLENGTH(L_BODY_HTML) + 1;
            DBMS_LOB.WRITE(L_BODY_HTML,LENGTH(L_TEMP),L_OFFSET,L_TEMP);

            ----------------------------------------------------

            V_PROC_STEP := 'STEP 9';

            -- SEND THE EMAIL IN 1900 BYTE CHUNKS TO UTL_SMTP
            L_OFFSET  := 1;
            L_AMMOUNT := 1900;
            UTL_SMTP.OPEN_DATA(L_CONNECTION);
            WHILE L_OFFSET < DBMS_LOB.GETLENGTH(L_BODY_HTML) LOOP
                UTL_SMTP.WRITE_DATA(L_CONNECTION,

DBMS_LOB.SUBSTR(L_BODY_HTML,L_AMMOUNT,L_OFFSET));
                L_OFFSET  := L_OFFSET + L_AMMOUNT ;
                L_AMMOUNT := LEAST(1900,DBMS_LOB.GETLENGTH(L_BODY_HTML) -
L_AMMOUNT);
            END LOOP;
            UTL_SMTP.CLOSE_DATA(L_CONNECTION);
            UTL_SMTP.QUIT( L_CONNECTION );
            DBMS_LOB.FREETEMPORARY(L_BODY_HTML);

            RETURN 'SUCCESS';

        EXCEPTION
        WHEN OTHERS THEN

--              SQL_STM := GET_PARAMETER_VAL('HTML_EMAIL');
--              IF SQL_STM = 'SUCCESS' THEN
--                      SQL_STM := 'SELECT ';
--                      FOR I IN 1 .. T_VAR.COUNT LOOP
--                          SQL_STM := SQL_STM||''''||T_VAR(I)||''' =
'||T_VAR(I)||'||'' ~ ''||';
--                      END LOOP;
--              END IF;
--
--              SQL_STM := RTRIM(SQL_STM, '||'' ~ ''||')||' FROM DUAL';

             --SQL_STM := 'SELECT P_TO FROM DUAL';

             --DBMS_OUTPUT.PUT_LINE(SQL_STM);

             --EXECUTE IMMEDIATE SQL_STM INTO V_PARAMETERS;

             VAR := Lee_Utl.STATS_AUD ('LEE_UTL.HTML_EMAIL',
                                              P_TO||' ~ '||P_FROM||' ~
'||V_PROC_STEP,
                                              SYSDATE,
                                              SYSDATE,
                                              V_EXEC_USER,
                                              'N1 ~ '||SQLERRM);

             VAR := Email (V_EXEC_USER,
                                   'lsagi@dealtime.com',
                                   NULL,
                                   NULL,
                                   'ERROR ~ LEE_UTL.HTML_EMAIL',
                                   SQLERRM);

             RETURN SQLERRM;

        END;

      Rating: 5
Lee's
      code:  July 02, 2002
Reviewer:  Samosa
      Kachori  from like a mouse in the cage I am trapped in my cubicle

Hi Lee,
can you post that one also? This is very useful! Thank you so much!

      Rating: 5
How to pass
      clob from frontend? and convert to varchar2 in
      backend  September 19, 2002
Reviewer:
      A reader
I am using COMMA_TO_TABLE to convert a string passed from java/jsp frontend into
a plqsltable and loop that plsqltable to update a database table value.
Since only 32k sring can be passed inside the proc,
i have a to use IN parameter as CLOB (which was currently varchar2).
My databse table col1 datatype is varchar2(20). I should be able to pass any
number of col1 values seperated by comma.
My question is :
1.How do i pass this 'comma seperated string' as a CLOB from the frontend into
the procedure?
2.How do i convert that long string (clob)  back into a varchar2 inside my proc
and update/insert my table.

Ex: I need to do something like
create procedure x(v in clob)
as
...
p dbms_utility.uncl_array;
n number
DBMS_UTILITY.COMMA_TO_TABLE('"'||REPLACE(v, ',', '","')||'"', n, p);

Then Loop the table populated, and update some table:
for i in 1..n
update t set col1=p(i) where something...
or insert p(i) into col1

So i just need to use CLOB to 'pass' a long string from frontend whil the
underlying datatype of teh col1 is varchar2.

How do i do this both frontend (how to convert string into clob and pass as proc
parameter from java/jsp) and backend (how to insert it as varchar2 afer
converting clob string)

The string is a user selection of certin values which the user checks
(checkboxes for example), each string checked is appended with a comma and sent
into a proc. I have to eliminate the 32k limit.

Thanks.

          Followup:

comma to table is NOT designed to do that at all!  Beware.  If you have my book
-- I describe what it does, and it is not for this.

Now, you want to pass a string of more then 32k -- so you are thinking "clob",
however the clob will require you to make a couple of round trips back and forth
in order to construct it (hence the single call over, single call back is
somewhat defeated).

My solution to this would DEFINITELY be the following:

o make sure the first line of my application in java is "conn.setAutoCommit
(false);"

o one time, i would (in sqlplus)

create global temporary table listOfValues ( data <whatever datatype> )
on commit delete rows;

o in the java program, set the .setDefaultExecuteBatch on a prepared statement
to say 100 or so

o insert the values into the listOfValues table (they will be batched and sent
100 or so at a time)

o run the procedure and have it use the global temp table.

It'll save tons of angst in

a) constructing this big string
b) destructing this big string
c) using clobs for something that can be done easier
d) avoiding the issues you'll hit with comma_to_table

(and you must have the biggest screens in the entire planet with very patient
end users if they can select things from a list that'll exceed 32k -- especially
since comma to table only handles things 30 chars or less -- they would have to
pick over 1,000 things from this list! wow)

      Rating: 3
Output a big
      file without linefeeds!  October 31, 2003

Reviewer:  Guy  from Manchester, England
Tom,
I have an output file that I have to produce for the UK Inland Revenue (i.e.
mandatory file format), where the records are not separated by linefeeds (they
use pipe characters) and CR/LF's are NOT allowed in the file.

I get execptions using UTL_PUT after 32K and I tried to use UTL_FILE.FFLUSH but
it also needs a CRLF.

Any way I can create this file in PLSQL?

Ta - Guy

          Followup:

you would have to use a java stored procedure or an external procedure or an
external process (program).  plsql is going to put a linefeed in there.

October 31, 2003 Monish -- Thanks for the question regarding
      "sql*plus on unix terminals", version 8.1.7

      You Asked
Hi Tom,

I normally use Sql*Plus Client from a Windows PC and I am able to set the
linesize using: set linesize 1000 etc.

In Unix Terminals however it does not seem to work. I think it is due to the
unix terminal seetings.

Please inform, How can I set the LineSize (to say 1000) in Unix Terminals.

Thanks,
Monish

      and we said...
that is the way telnet works.

You were refering to the "sqlplus gui" (which is deprecated in the next release
of Oracle).

Telnet gives you a fixed width screen only -- there is no virtual "back and
forth, left and right" in general.

The linesize is in fact working -- the issue you have is that telnet doesn't
work the way you want it to.

Review &
      Followup

      Rating: 4
sqlplus gui
      will be deprecated in next release  March 30, 2003

Reviewer:  Monish  from CA, USA
Hi Tom,

If sqlplus gui is being deprecated in next release, what will be the new user
interface to interact with sql.

Thanks.

          Followup:

there is and will be character mode sqlplus which IMHO was/is the best
interface. I never used that "gui" thing in windows when forced to run windows
-- always the dos command line version.

there is "isql" plus as well, the browser thing (which I never use)...

      Rating: 5
sql+ - no
      more?  March 30, 2003
Reviewer:
      A reader
Tom -
 1. what is IMHO?
 2. Do you mean that on windows, the sql+ gui will not ship with the 10i (if so
-- why?? -- what's suddenly wrong??) but that the cmd version will remain?

          Followup:

1) imho = in my humble opinion
2) sqlplusw.exe, the single abberation, deviation from cross platform "sameness"
may or may not ship -- it is "deprecated".  isqlplus -- the browser version --
is supported in addition to sqlplus -- the normal command line version.

      Rating: 5
Emacs + sqlplus
      = all I would ever need  March 31, 2003
Reviewer:
      A reader
Running sqlplus in Emacs shell mode or sql-oracle mode is not only efficient,
powerful and flexible, but also a joy.

      Rating:
      5
  March 31, 2003
Reviewer:
      A reader
yuck! emacs is horrible! who cares if its gnu.
vi (or vim) is the only way, man.

      Rating:
      3
  March 31, 2003
Reviewer:  robert
       from PA
>> there is "isql" plus as well, the browser thing (which I >> never use)...

oh, I'd like to give it a go..never knew about it.
Tom, you know how I can try it out ?
either Win client or telnet.
Thanks

          Followup:

it is browser based -- with 9i.

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/index.htm
and search for isqlplus

      Rating: 3
More memory
      with char mode sqlplus  June 19, 2003
Reviewer:
      Tim
Hi Tom,

You have mentioned couple of times that char mode sqlplus is much better than
gui sqlplus and it is your favourite interface. Plus, now that it is going to be
deprecated, I wanted to get used to char mode sqlplus.

But I saw that char mode takes a lot of memory. Also, the copy-paste convenience
is lost in char mode and unlike gui sqlplus I cannot set a default directory to
work with char mode sqlplus.

Is there a way around for the above problems ? I have W2K OS.

TIA !!!

          Followup:

what do you mean "it takes alot of memory".

I copied and pasted all of the time in DOS windows.  I liked it even BETTER
cause I could select a SQUARE region.

Alt-space/e/p  (alt-space then edit then paste, once you do alt-space/e/p a time
or time, it almost becomes a keystroke you can do it so fast).

then there is an option hidden in some user friendy menu to make it so you can
"copy on select" -- it'll let you select text and when you hit "enter" the
selected text is placed in the copy buffer.

      Rating:
      3
  June 20, 2003
Reviewer:  Tim

I mean "it takes alot of memory". If I see in windows task  manager, the mem
usage shown by sqlplus.exe is much higher than sqlplusw.exe ! Do you know why
???

And by the copy-paste convenience (not feature) being lost I meant that in gui
sqlplus, one can simply use ctrl-c/ctrl-v to cut-paste OR just mark the
statement and while holding the left mouse click, right click to paste.

TIA !!!

          Followup:

what is much higher?

and is it "too much"

and is task manager really at all accurate (i mean, just iconify all of your
windows -- wow, you have a ton more memory -- or do you?)  I don't trust task
manager.

and like I said, you'll find the "select text, hit enter" instead of ctl-c and
"alt-space/e/p" to be as handy as ctl-c/ctl-v.  Once you do it 10 times, it'll
be like you've always been doing it.

      Rating:
      3
  June 20, 2003
Reviewer:  Tim

Yes, I agree with you. Once I do that copy-paste couple of times, I will get
used to that feature even :)

And I am sorry with the memory part -- if you have an window open, then the
memory shown is more than the memory shown for other window which is minimized.
So when I was looking the memory usage of sqlplus.exe, the sqlplusw.exe windows
were minimized. But if I minimize all the windows and see the memory usage, they
all show the same -- rather sqlplus.exe consumes less memory :)

Sorry once again for all the trouble Tom !!

      Rating:
      4
  June 20, 2003
Reviewer:
      A reader
Everybody is entitled to their own views. You might find it easier to use the
command-line version. But I find the gui version somuch easier. And I am sure
there are many out there who feel the same way.

I am no great Microsoft lover. But I just don't understand the rationale of
de-supporting a client tool designed to exploit the features of the most popular
desktop platform!!

Is Oracle trying to prove something to MS??

          Followup:

we are making it so that we are the same everywhere.

sqlplusw.exe does not fit that particular niche, it was "extra special for that
os"

there is

o sql worksheet in oem
o isqlplus

and a new thing i cannot yet talk about....

that are the same on all platforms and supportable.

      Rating: 5
DOS based
      SQL*Plus  June 20, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
If you click on the "Command Prompt" system menu (click the icon that appears on
the left on the title bar) and click on properties, it will show you "Command
Prompt" properties. From the many tabs available, click on the "Options". under
"Edit Options", select "QuickEdit Mode" and "Insert Mode", you can also set
buffer size.

Once you do this, then for doing cut-n-paste, all you need to do is select the
text you want to copy and click right mouse button. The text is copied to the
clipboard (this is now same as selecting the text and pressing Ctrl-C in GUI
version, but I find this much much better and faster).

When you want to paste, just put your mouse cursor over the "Command Prompt"
window and press right mouse button. The text will be pasted at the current
cursor position.

This is so much easier than the GUI version. (no need to do Ctrl-C and Ctrl-V
anymore).

This has proved so easy for me, I do not even care about the GUI SQL*Plus (as if
it does not exists for me at all).

      Rating: 3
a trick in
      sqlplusw.exe  June 21, 2003
Reviewer:  W.Hwa
      Chan  from China
in sqlplusw.exe, if you wanna copy something, you don't
need anything more than a mouse with 2 buttons. It's simple,
hold left button to select what you want, then click on
the right button (keep holding the left button while you
clicking on the right button!),
the text you'vd just selected
will be pasted right after the next 'SQL>' prompt.
you can also select SQUARE in this way easily. :)

          Followup:

and if you upgrade your OS to linux, you can do that trick in every application
easier.

left click and drag to hilight.

middle click to paste....

      Rating: 5
a new thing you
      cannot talk about.. wonder what it is.. fast=true ..:)  June
      21, 2003
Reviewer:  A reader  from MN, USA

<quote>
and a new thing i cannot talk about
</quote>

well cant u just type it on here then ;)
jus kidding..
thanks for all the work you do for the oracle user community.. we are really
indebted you...
Thanks

          Followup:

You'll find the new thing to be truly

"Marvel"ous

      Rating: 2
No Horizontal
      Scrolling in Character SQL*PLUS  June 26, 2003

Reviewer:  A reader
In Character based sql plus: there is no horizontal scrolling available.

What if I had to set lines to 2000?
That will be limited by Terminal.

      Rating: 3
Cursor keys on
      command line: windows vs Linux  October 31, 2003

Reviewer:  Mark Rynbeek  from Cambridge, UK

hi Tom,

in a SQL*Plus client on Windows2000 (the command shell version) I can use the
cursor keys to get previous commands back. Pageup and pagedown works as well.
Joy !

But on Linux it never seems to work - if you press cursor up or cursor down the
keyboard-input is not interpreted correctly, you just see these weird input
values :

markr1@D002>^[OA
SP2-0042: unknown command "A" - rest of line ignored.

markr1@D002>^[OB

Can I get around this? Is it a SQL*PLus setting or a Linux setting ?

          Followup:

in unix, its a function of the shell, and you are not in the shell anymore.

November 01, 2003 Pascal -- Thanks for the question regarding "How
      can I DO PATTERN MATCHING from within PL-SQL using REGULAR Expression ",
      version 8.1.6

      You Asked
Hi Tom,

How r u doing..
I must be quick on this because today is the first time i am able to put my
Qnand hope that u will see it..

My problem is :

1.

I need to search for matches in a table which has a bunch of Mobile phone
numbers, from different countries...

For example , i need to return some rows from this Table, with Phone numbers
matching the regular expression : +346[017][07]*

(I hope u r familiar with  java.util.regex  Package : jdk 1.4)

that means return rows from the Table with anything matching the above regular
expression:

This means match:

pattern = +346[017]5[07]*

matching every occurrence of numbers enclosed within [ ] and expanding them, i
would like to get a match to any of the following:

+346 0 5 0*
+346 0 5 7*
+346 1 5 0*
+346 1 5 7*
+346 7 5 0*
+346 7 5 7*

Ofcourse thats just a simple pattern, it could get too complicated to expand it
manually.

Also,i would like to ask if u have any other alternative for a Query to search
for an Operator from a Table of Operators, assuming that i have a child Table
match_list which has all  Mobile number matches for that Operator in REGULAR
EXPRESSION form such as +4917[24]*

Is it also a good idea to expand the pattern and write triggers to expand it
into another Table Automatically?

Ok, Tom i hope i will hear from  u soon.

Regards,

pascal

      and we said...
Well, I got together with Mark Piermarini and this is what we came up with.

We went to:

http://www.savarese.org/oro/software/OROMatcher1.1.html
and got the perl regexp matcher they have.  we then wrote this small java layer:

create or replace and resolve java source named "RegExp" as
import com.oroinc.text.regex.*;
import oracle.sql.*;

public final class RegExp
{
  static PatternMatcher matcher;
  static PatternCompiler compiler;

  static {
    compiler = new Perl5Compiler();
    matcher  = new Perl5Matcher();
  }
  static Pattern pattern;
  static String  last_test_pattern = null;
  static PatternMatcherInput input = new PatternMatcherInput("");

  public static final void compile( String test_pattern ) throws Exception
  {
     pattern = compiler.compile( test_pattern );
  }

  public static final int match(String test_input)  throws Exception
  {
      input.setInput( test_input );
      return matcher.contains(input, pattern) ? 1 : 0;
  }
}
/
show errors
create or replace function reg_exp_match(
  p_test_input in varchar2) return number
as language java name 'RegExp.match(java.lang.String) return int';
/
show errors
create or replace procedure reg_exp_compile(
  p_test_input in varchar2)
as language java name 'RegExp.compile(java.lang.String) ';
/
show errors

We went out of our way to make the "match" routine as efficient as possible
(hence the compile return)....  Now we can:

exec reg_exp_compile( 'ALL.*OBJ' );

select object_name
  from all_objects
 where reg_exp_match(object_name) = 1
/

and you have regular expressions.  You can use any regular expressions from
anyone and can squeeze even more performance out of it by NCOMPing the java byte
code on your platform.

I don't see how using a trigger can help you here, you don't know the patterns
until you go to search, not when you insert.
Review &
      Followup

      Rating:
      5
Excellent  December 12, 2001
Reviewer:
      A reader  from CA, US
I'm sure I'll need it one day.
Funny that Oracle hasn't made it a built in yet.

      Rating: 5
Pattern
      Matching within PL-SQL  December 13, 2001

Reviewer:  Pascal  from Hamburg,Germany
Hi Tom ...

Thank you very much for taking all the Trouble to answer my Query....

I  went  to
http://www.savarese.org/oro/software/OROMatcher1.1.html
and downloaded OROMatcher1.1

I am now trying to load ORO Classes into my Oracle Schema but look what i get
instead....

First i  jar-ed all the .class Files from  Folder \com\oroinc\text\regex   into
myload.jar File

and then i used loadjava : as Follows :

C:\java\ORO\OROa\com\oroinc\text\regex>loadjava -user
pascal/pascal@my_host:1521:sid -v -f -r -t -s -g public myload.jar

initialization complete
loading  : Perl5StreamInput
creating : Perl5StreamInput
loading  : OpCode
creating : OpCode
loading  : Substitution
creating : Substitution
loading  : PatternMatcherInput
creating : PatternMatcherInput
loading  : Perl5Debug
creating : Perl5Debug
loading  : Perl5Compiler
creating : Perl5Compiler
loading  : Perl5MatchResult
creating : Perl5MatchResult
loading  : Util
creating : Util
loading  : Perl5Matcher
creating : Perl5Matcher
loading  : Perl5Pattern
creating : Perl5Pattern
loading  : Perl5Substitution
creating : Perl5Substitution
loading  : PatternMatcher
creating : PatternMatcher
loading  : MatchResult
creating : MatchResult
loading  : Pattern
creating : Pattern
loading  : Perl5Repetition
creating : Perl5Repetition
loading  : PatternCompiler
creating : PatternCompiler
loading  : StringSubstitution
creating : StringSubstitution
loading  : MalformedPatternException
creating : MalformedPatternException
loading  : CharStringPointer
creating : CharStringPointer
loading  : META-INF/MANIFEST.MF
creating : META-INF/MANIFEST.MF
Error while granting to Perl5StreamInput
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to OpCode
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to Substitution
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to PatternMatcherInput
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to Perl5Debug
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to Perl5Compiler
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to Perl5MatchResult
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to Util
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to Perl5Matcher
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to Perl5Pattern
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to Perl5Substitution
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to PatternMatcher
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to MatchResult
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to Pattern
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to Perl5Repetition
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to PatternCompiler
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to StringSubstitution
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to MalformedPatternException
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to CharStringPointer
    ORA-04042: procedure, function, package, or package body does not exist

resolver :
resolving: Perl5StreamInput
Error while resolving class Perl5StreamInput
    ORA-04043: object Perl5StreamInput does not exist

resolving: OpCode
Error while resolving class OpCode
    ORA-04043: object OpCode does not exist

resolving: Substitution
Error while resolving class Substitution
    ORA-04043: object Substitution does not exist

resolving: PatternMatcherInput
Error while resolving class PatternMatcherInput
    ORA-04043: object PatternMatcherInput does not exist

resolving: Perl5Debug
Error while resolving class Perl5Debug
    ORA-04043: object Perl5Debug does not exist

resolving: Perl5Compiler
Error while resolving class Perl5Compiler
    ORA-04043: object Perl5Compiler does not exist

resolving: Perl5MatchResult
Error while resolving class Perl5MatchResult
    ORA-04043: object Perl5MatchResult does not exist

resolving: Util
Error while resolving class Util
    ORA-04043: object Util does not exist

resolving: Perl5Matcher
Error while resolving class Perl5Matcher
    ORA-04043: object Perl5Matcher does not exist

resolving: Perl5Pattern
Error while resolving class Perl5Pattern
    ORA-04043: object Perl5Pattern does not exist

resolving: Perl5Substitution
Error while resolving class Perl5Substitution
    ORA-04043: object Perl5Substitution does not exist

resolving: PatternMatcher
Error while resolving class PatternMatcher
    ORA-04043: object PatternMatcher does not exist

resolving: MatchResult
Error while resolving class MatchResult
    ORA-04043: object MatchResult does not exist

resolving: Pattern
Error while resolving class Pattern
    ORA-04043: object Pattern does not exist

resolving: Perl5Repetition
Error while resolving class Perl5Repetition
    ORA-04043: object Perl5Repetition does not exist

resolving: PatternCompiler
Error while resolving class PatternCompiler
    ORA-04043: object PatternCompiler does not exist

resolving: StringSubstitution
Error while resolving class StringSubstitution
    ORA-04043: object StringSubstitution does not exist

resolving: MalformedPatternException
Error while resolving class MalformedPatternException
    ORA-04043: object MalformedPatternException does not exist

resolving: CharStringPointer
Error while resolving class CharStringPointer
    ORA-04043: object CharStringPointer does not exist

synonym  : Perl5StreamInput
synonym  : OpCode
synonym  : Substitution
synonym  : PatternMatcherInput
synonym  : Perl5Debug
synonym  : Perl5Compiler
synonym  : Perl5MatchResult
synonym  : Util
synonym  : Perl5Matcher
synonym  : Perl5Pattern
synonym  : Perl5Substitution
synonym  : PatternMatcher
synonym  : MatchResult
synonym  : Pattern
synonym  : Perl5Repetition
synonym  : PatternCompiler
synonym  : StringSubstitution
synonym  : MalformedPatternException
synonym  : CharStringPointer
loadjava: 38 errors

There i got stuck,

i have jdk 1.4 Beta on my Windwos 2000 Client ....
Oracle Back-end is on Solaris Machine.

My  CLASSPAPH ENV  variable is correctly set and i also added  the /com  path in
the list.

Please, i would like more help on how t load these  classes into Oracle  so that
i  can test your pattern Matcher program.

Best Regards,

Pascal

          Followup:

1. Download
http://www.savarese.org/oro/downloads/download/OROMatcher-1.1.zip
2. Unzip this file. Make sure that you have your un-zipping program create the
directories.

   You should have a tree like this:

   C:\temp\OROMatcher-1.1.0a>tree
   Folder PATH listing
   Volume serial number is 71FAE346 1C7F:F8DB
   C:.
   +---com
   ¦   +---oroinc
   ¦       +---text
   ¦           +---regex
   +---doc
   ¦   +---api
   ¦   ¦   +---images
   ¦   +---images
   +---examples
   +---images

3. Now we want to ignore the 'doc', 'examples', and 'images' directories. Put
another way, we only want to include everything in the 'com' directory. I used
the free utility called unzip.exe but use whatever you desire. Make sure that
you have your zipping program includes the sub-directories. This command creates
a file named ORO.ZIP.

C:\temp\OROMatcher-1.1.0a>zip -r oro.zip com
updating: com/ (256 bytes security) (stored 0%)
updating: com/oroinc/ (256 bytes security) (stored 0%)
updating: com/oroinc/text/ (256 bytes security) (stored 0%)
updating: com/oroinc/text/regex/ (256 bytes security) (stored 0%)
updating: com/oroinc/text/regex/CharStringPointer.class (188 bytes security)
(deflated 50%)
updating: com/oroinc/text/regex/MalformedPatternException.class (188 bytes
security) (deflated 30%)
updating: com/oroinc/text/regex/MatchResult.class (188 bytes security) (deflated
32%)
updating: com/oroinc/text/regex/OpCode.class (188 bytes security) (deflated 49%)
updating: com/oroinc/text/regex/Pattern.class (188 bytes security) (deflated
22%)
updating: com/oroinc/text/regex/PatternCompiler.class (188 bytes security)
(deflated 51%)
updating: com/oroinc/text/regex/PatternMatcher.class (188 bytes security)
(deflated 51%)
updating: com/oroinc/text/regex/PatternMatcherInput.class (188 bytes security)
(deflated 58%)
updating: com/oroinc/text/regex/Perl5Compiler.class (188 bytes security)
(deflated 51%)
updating: com/oroinc/text/regex/Perl5Debug.class (188 bytes security) (deflated
40%)
updating: com/oroinc/text/regex/Perl5Matcher.class (188 bytes security)
(deflated 54%)
updating: com/oroinc/text/regex/Perl5MatchResult.class (188 bytes security)
(deflated 49%)
updating: com/oroinc/text/regex/Perl5Pattern.class (188 bytes security)
(deflated 40%)
updating: com/oroinc/text/regex/Perl5Repetition.class (188 bytes security)
(deflated 36%)
updating: com/oroinc/text/regex/Perl5StreamInput.class (188 bytes security)
(deflated 43%)
updating: com/oroinc/text/regex/Perl5Substitution.class (188 bytes security)
(deflated 49%)
updating: com/oroinc/text/regex/StringSubstitution.class (188 bytes security)
(deflated 50%)
updating: com/oroinc/text/regex/Substitution.class (188 bytes security)
(deflated 38%)
updating: com/oroinc/text/regex/Util.class (188 bytes security) (deflated 64%)

4. Let's double-check the contents of that file.

C:\temp\OROMatcher-1.1.0a>unzip -v oro.zip
Archive:  oro.zip
 Length   Method    Size  Ratio   Date   Time   CRC-32    Name
--------  ------  ------- -----   ----   ----   ------    ----
       0  Stored        0   0%  12-13-01 09:11  00000000  com/
       0  Stored        0   0%  12-13-01 09:11  00000000  com/oroinc/
       0  Stored        0   0%  12-13-01 09:11  00000000  com/oroinc/text/
       0  Stored        0   0%  12-13-01 09:11  00000000  com/oroinc/text/regex/
    1458  Defl:N      735  50%  10-21-99 19:25  bbe7f548
com/oroinc/text/regex/CharStringPointer.class
     331  Defl:N      232  30%  10-21-99 19:26  bf322bfe
com/oroinc/text/regex/MalformedPatternException.class
     379  Defl:N      258  32%  10-21-99 19:25  33232905
com/oroinc/text/regex/MatchResult.class
    2400  Defl:N     1220  49%  10-21-99 19:25  5ebe5019
com/oroinc/text/regex/OpCode.class
     241  Defl:N      188  22%  10-21-99 19:25  173e9b29
com/oroinc/text/regex/Pattern.class
     507  Defl:N      248  51%  10-21-99 19:25  4782e5ad
com/oroinc/text/regex/PatternCompiler.class
     574  Defl:N      280  51%  10-21-99 19:25  fa3a5349
com/oroinc/text/regex/PatternMatcher.class
    2354  Defl:N      989  58%  10-21-99 19:25  48e548c6
com/oroinc/text/regex/PatternMatcherInput.class
   13501  Defl:N     6569  51%  10-21-99 19:26  ee171492
com/oroinc/text/regex/Perl5Compiler.class
    2599  Defl:N     1563  40%  10-21-99 19:26  7d5322dc
com/oroinc/text/regex/Perl5Debug.class
   11287  Defl:N     5148  54%  10-21-99 19:26  f850bc2a
com/oroinc/text/regex/Perl5Matcher.class
    1198  Defl:N      612  49%  10-21-99 19:26  2e63a3dd
com/oroinc/text/regex/Perl5MatchResult.class
     891  Defl:N      537  40%  10-21-99 19:26  a87088c4
com/oroinc/text/regex/Perl5Pattern.class
     468  Defl:N      298  36%  10-21-99 19:26  9becfff7
com/oroinc/text/regex/Perl5Repetition.class
    2218  Defl:N     1259  43%  10-21-99 19:25  51a33c0c
com/oroinc/text/regex/Perl5StreamInput.class
    2624  Defl:N     1329  49%  10-21-99 19:27  d52ea45f
com/oroinc/text/regex/Perl5Substitution.class
    1009  Defl:N      503  50%  10-21-99 19:27  47fc9bae
com/oroinc/text/regex/StringSubstitution.class
     362  Defl:N      224  38%  10-21-99 19:26  743dd420
com/oroinc/text/regex/Substitution.class
    2844  Defl:N     1030  64%  10-21-99 19:27  900ecc52
com/oroinc/text/regex/Util.class
--------          -------  ---                            -------
   47245            23222  51%                            23 files

5. This ORO.ZIP file is what you need to load into the database.

C:\temp\OROMatcher-1.1.0a>loadjava -u scott/tiger -f -r -v oro.zip
arguments: '-u' 'scott/tiger' '-f' '-r' '-v' 'oro.zip'
creating : class com/oroinc/text/regex/CharStringPointer
loading  : class com/oroinc/text/regex/CharStringPointer
creating : class com/oroinc/text/regex/MalformedPatternException
loading  : class com/oroinc/text/regex/MalformedPatternException
creating : class com/oroinc/text/regex/MatchResult
loading  : class com/oroinc/text/regex/MatchResult
creating : class com/oroinc/text/regex/OpCode
loading  : class com/oroinc/text/regex/OpCode
creating : class com/oroinc/text/regex/Pattern
loading  : class com/oroinc/text/regex/Pattern
creating : class com/oroinc/text/regex/PatternCompiler
loading  : class com/oroinc/text/regex/PatternCompiler
creating : class com/oroinc/text/regex/PatternMatcher
loading  : class com/oroinc/text/regex/PatternMatcher
creating : class com/oroinc/text/regex/PatternMatcherInput
loading  : class com/oroinc/text/regex/PatternMatcherInput
creating : class com/oroinc/text/regex/Perl5Compiler
loading  : class com/oroinc/text/regex/Perl5Compiler
creating : class com/oroinc/text/regex/Perl5Debug
loading  : class com/oroinc/text/regex/Perl5Debug
creating : class com/oroinc/text/regex/Perl5Matcher
loading  : class com/oroinc/text/regex/Perl5Matcher
creating : class com/oroinc/text/regex/Perl5MatchResult
loading  : class com/oroinc/text/regex/Perl5MatchResult
creating : class com/oroinc/text/regex/Perl5Pattern
loading  : class com/oroinc/text/regex/Perl5Pattern
creating : class com/oroinc/text/regex/Perl5Repetition
loading  : class com/oroinc/text/regex/Perl5Repetition
creating : class com/oroinc/text/regex/Perl5StreamInput
loading  : class com/oroinc/text/regex/Perl5StreamInput
creating : class com/oroinc/text/regex/Perl5Substitution
loading  : class com/oroinc/text/regex/Perl5Substitution
creating : class com/oroinc/text/regex/StringSubstitution
loading  : class com/oroinc/text/regex/StringSubstitution
creating : class com/oroinc/text/regex/Substitution
loading  : class com/oroinc/text/regex/Substitution
creating : class com/oroinc/text/regex/Util
loading  : class com/oroinc/text/regex/Util
resolving: class com/oroinc/text/regex/CharStringPointer
resolving: class com/oroinc/text/regex/MalformedPatternException
resolving: class com/oroinc/text/regex/MatchResult
resolving: class com/oroinc/text/regex/OpCode
resolving: class com/oroinc/text/regex/Pattern
resolving: class com/oroinc/text/regex/PatternCompiler
resolving: class com/oroinc/text/regex/PatternMatcher
skipping : class com/oroinc/text/regex/PatternMatcherInput
resolving: class com/oroinc/text/regex/Perl5Compiler
resolving: class com/oroinc/text/regex/Perl5Debug
resolving: class com/oroinc/text/regex/Perl5Matcher
skipping : class com/oroinc/text/regex/Perl5MatchResult
skipping : class com/oroinc/text/regex/Perl5Pattern
skipping : class com/oroinc/text/regex/Perl5Repetition
skipping : class com/oroinc/text/regex/Perl5StreamInput
resolving: class com/oroinc/text/regex/Perl5Substitution
skipping : class com/oroinc/text/regex/StringSubstitution
skipping : class com/oroinc/text/regex/Substitution
resolving: class com/oroinc/text/regex/Util

      Rating: 5
How can I DO
      PATTERN MATCHING from within PL-SQL using REGULAR Expression , version
      8.1.6  December 13, 2001
Reviewer:  Pascal
       from Hamburg,Germany
Hi Tom,
I managed to use Loadava to load the Class Files  below into Oracle DB , but
some are INVALID  as indicated below:

I still managed to load your sample Java Stored procedure though , because i
note  that it does not use any of those INVALID classes .

I also modified your Code a little bit by writing :

import java.sql.*
instead of  import oracle.sql.*

The Classes are:

Match                      INVALID
Perl5Debug                 INVALID
Perl5Compiler
Perl5MatchResult
Util                       INVALID
Perl5Matcher
Perl5Pattern
Perl5Substitution          INVALID
PatternMatcher
MatchResult
Pattern
Perl5Repetition
PatternCompiler
StringSubstitution        INVALID
MalformedPatternException
CharStringPointer
META-INF/MANIFEST.MF
RegExp
Substitution              INVALID
OpCode
PatternMatcherInput
Perl5StreamInput

Some Tests for your program:

SQL>  select * from reg_test;

ID
------------------------------
+49179232332
+4917223232
+49160232323
+4917423232
+25574123232
+25574432432
+25574234343
+4915634343
+4916334343
+3467832323
+346023232
346122342
+346243232
+346545454
+346823232
+34686
+346222423
+34623345353
+346322332
+3464323232

exec reg_exp_compile( '\346[34]+' );

SQL> select *
  2    from reg_test
  3   where reg_exp_match(id) = 1;

ID
------------------------------
+346322332
+3464323232

I am stll Testing it if it works for more Complex RegExps.

Once again, thank you very very much for  this.

Best regards,

Pascal

      Rating: 5
I got it Right
      This time around!!..Everything now works fine.....  December
      14, 2001
Reviewer:  Pascal  from Hamburg,Germany

Hi Tom,

Thanks very much for your followup to my Query...i managed to install zipped
/com Folder and i loaded all the class Files successfully....

But i have one more question,
Where does the Pattern that i compile gets stored , before i match it with some
string, because in this case the
reg_exp_compile  and
reg_exp_match  and not in a package  and so how does Oracle Remember the
Compiled string  before it gets executed .

Best Regards,

Pascal

          Followup:

It's in the static attribute "pattern" in the java class.  I broke compile out
from match for performance reasons -- rather then compiling the string for each
call or comparing the last compiled string to the current match string on each
call -- we just call compile once and match as many times as we like.  Just
makes it faster...

public final class RegExp
{
  static PatternMatcher matcher;
  static PatternCompiler compiler;

  static {
    compiler = new Perl5Compiler();
    matcher  = new Perl5Matcher();
  }
  static Pattern pattern;
  static String  last_test_pattern = null;
  static PatternMatcherInput input = new PatternMatcherInput("");

  public static final void compile( String test_pattern ) throws Exception
  {
     pattern = compiler.compile( test_pattern );
  }

  public static final int match(String test_input)  throws Exception
  {
      input.setInput( test_input );
      return matcher.contains(input, pattern) ? 1 : 0;
  }
}

      Rating: 5
Combining
      reg_exp_compile and reg_exp_match in One CALL  December 27,
      2001
Reviewer:  pascal  from hamburg,Germany

Hi Tom and Mark,

Happy New Year in advance to u Guyz!

I was thinking of a way to combine
reg_exp_compile and
reg_exp_match  so that they could be called in a Single  SQL statement , instead
of one procedural call and one SQL.

How could this be achieved.

Thanks,

pascal

************************************************
exec reg_exp_compile( 'ALL.*OBJ' );
select object_name
  from all_objects
 where reg_exp_match(object_name) = 1
***********************************************

          Followup:

You can -- and in fact the first version we wrote did that -- however, the
overhead of doing this was considered too large.

You are going to call reg_exp_match many times, perhaps 10's of thousands or
even more.  Every bit of extra work that it must do is overhead that should be
avoided.

You can add a method:

public final class RegExp
{
  static PatternMatcher matcher;
  static PatternCompiler compiler;

  static {
    compiler = new Perl5Compiler();
    matcher  = new Perl5Matcher();
  }
  static Pattern pattern;
  static String  last_test_pattern = null;
  static PatternMatcherInput input = new PatternMatcherInput("");

  public static final void compile( String test_pattern ) throws Exception
  {
     pattern = compiler.compile( test_pattern );
  }

  public static final int match(String test_input)  throws Exception
  {
      input.setInput( test_input );
      return matcher.contains(input, pattern) ? 1 : 0;
  }

  public static final int match2( String test_input, String test_pattern )
  throws Exception
  {
      -- compare test_pattern to last_test_pattern --
      -- if different
            compile( test_pattern );
            last_test_pattern = test_pattern
         end if
         return match( test_input )
  }

}
/

The overhead of doing that string compare on every call adds up over time.  We
didn't like that so didn't do it that way (but apparently we left the
last_test_pattern in there!)

      Rating:
      5
Follow-up  January 09, 2002
Reviewer:
      Pascal  from Hamburg,Germany
Hi Tom amd Mark,

How are you guys doing..

Thanks for your previous follow-up on my question...

I am planning to use this regExp package as follows:

I have a  DB Table which has a  list of Operators and their corresponding
Patterns , which matches all mobile numbers for a particular operator.

I have a c++ program which feeds the database with some short messages , and i
need to compare each message so that i could extract an Operator by checking the
originating phone numbers from the short messages ...and so,  that  means i have
to compile and match fo each and every short message that i receive  and there
could be millios of them....

Do you think i could use this package  for the above mentioned purpose without
incurring performance problems?

Also, i don't have much experience with java, and so could you help me write
that   match_2  Compare logic  if you have time please ?

Thanks in advance,

Best regards,

Pasko

          Followup:

Millions -- I would not bank on this being "fast".  You can try Native
Compilation which will speed up the java (possibly by alot).

I would definitely suggest NOT using the match_2 method if you plan on doing
this lots -- it really does add lots of overhead.

 public static final int match2( String test_input, String test_pattern )
  throws Exception
  {
      if (last_test_pattern == null || !test_pattern.isEqual(last_test_pattern))
      {
         compile(test_pattern);
         last_test_pattern = test_pattern;
      }
      return match( test_input )
  }

should do it.

      Rating: 5
I ordered ur
      Book !  January 10, 2002
Reviewer:  Pascal
       from HH, Germany
Hi Tom/Mark,

Thanks again for your follow-up on my Qn.

I added match2 to regExp class you sent me earlier but  compiled with errors  on
 jdk 1.4 beta .

regExp.java:33: cannot resolve symbol
symbol  : method isEqual  (java.lang.String)
location: class java.lang.String
        if (last_test_pattern == null || !test_pattern.isEqual(last_test_pattern
))
                                                      ^
1 error

But i changed :  your 'isEqual'  method  TO  'equals' and it compiled
successfully :

if (last_test_pattern == null || !test_pattern.isEqual(last_test_pattern))

TO:

  if (last_test_pattern == null || !test_pattern.equals(last_test_pattern))

I am still testing this to see if i get any performance improvements.

Thanks very much for your support  .

Today i ordered your BOOK and i am looking forward to getting it soon , because
it is not available in most of the English   Bookshops here in Germany.

Best regards,

Pasko

      Rating: 5
How about using
      this .....  February 08, 2002
Reviewer:
      Pascal  from HH,Germany
Hi Tom/Mark,
I went to  this Link   and found that they also have another Tool called
PerlTools-1.2.zip

at :

http://www.savarese.org/oro/downloads/index.html#PerlTools
So i downloaded it and wrote my simple Wrapper JSP.

It looks like :

create or replace and resolve java source named "PerlExp" as

import com.oroinc.text.regex.*;
import com.oroinc.text.perl.*;

import oracle.sql.*;

public final class   PerlExp
{

  static Perl5Util   perl;

  static {
     perl    =  new Perl5Util();
  }

   public static final  int match( String p_pattern , String  p_input )
    {

    p_pattern  = "/" + p_pattern   + "/"  ;

      return  perl.match(p_pattern , p_input) ? 1 : 0 ;

    }

}
/

create or replace function perl_match(
  p_pattern IN  varchar2 ,   p_input  IN  varchar2 )
  return number
as language java name 'PerlExp.match(java.lang.String ,  java.lang.String  )
return int';
/

Soi could  just  code in a Siingle sQL   as : from your Example:

select  *  from all_objects where
perl_match( 'ALL.*OBJ' , object_name ) = 1

What do you think is the Performance of this one compared to the Earlier
procedure.

Best Regards,

Pascal

          Followup:

I don't know -- benchmark it for us and let us know ;)

I will guess:

the original implementation will be faster.

o it does not create a temporary string "/" + pattern + "/"
o it does not compile the pattern for each row.

      Rating: 5
Function-based
      Index for Java functions  December 24, 2002

Reviewer:  Tony  from India
Do we have to create function-based index to speed up the performance in pattern
serach using this Java function?.

How to make a deterministic Java function for the above case?

          Followup:

pattern matching isn't something an index is going to help.  You have to inspect
each and every record -- no chance to short circut it.

what exactly would you INDEX? there isn't anything...

      Rating: 4
Error during
      compilation  February 17, 2003
Reviewer:  Sasa
      Petkovic  from Belgrade, Yugoslavia
Hi Tom,

I successfully loaded all the classes from or.zip but got an error during
compilation java source "RegExp".
There is an output:
CREATE OR REPLACE AND resolve java source named "RegExp" AS
                                                *
ERROR at line 1:
ORA-29536: badly formed source: Encountered "PUBLIC" at line 4, column 1.
Was expecting one of:
<EOF>
"#sql" ...
"abstract" ...
"final" ...
"public" ...
"class" ...
"import" ...
"interface" ...
";" ...
"\\u001a" ...

Could you help please.

Sasa

          Followup:

what version of sqlplus are you using?

and do it with SET ECHO ON

and show us the ENTIRE cut and paste -- you must either

o have a typo
o be using a client that doesn't know what to do with create or replace java
code.

      Rating:
      2
  February 17, 2003
Reviewer:  Sasa
      Petkovic  from Belgrade,Yugoslavia
Hi Tom,
I did as you recomended and got as follows:

SQL*Plus: Release 8.1.7.0.0 - Production on Mon Feb 17 17:25:27 2003
(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.3.0 - Production
With the Partitioning option
JServer Release 8.1.7.3.0 - Production

SQL>
SQL> CREATE OR REPLACE AND resolve java source named "RegExp" AS
  2  import com.oroinc.text.regex.*;
  3  import oracle.SQL.*;
  4
  5  PUBLIC final class RegExp
  6  {
  7    static PatternMatcher matcher;
  8    static PatternCompiler compiler;
  9
 10    static {
 11      compiler = NEW Perl5Compiler();
 12      matcher  = NEW Perl5Matcher();
 13    }
 14    static Pattern pattern;
 15    static String  last_test_pattern = NULL;
 16    static PatternMatcherInput input = NEW PatternMatcherInput("");
 17
 18    PUBLIC static final void COMPILE( String test_pattern ) throws EXCEPTION
 19    {
 20       pattern = compiler.COMPILE( test_pattern );
 21    }
 22
 23    PUBLIC static final INT match(String test_input)  throws EXCEPTION
 24    {
 25        input.setInput( test_input );
 26        RETURN matcher.contains(input, pattern) ? 1 : 0;
 27    }
 28  }
 29  /
CREATE OR REPLACE AND resolve java source named "RegExp" AS
                                                *
ERROR at line 1:
ORA-29536: badly formed source: Encountered "PUBLIC" at line 4, column 1.

Was expecting one of:

<EOF>
"#sql" ...
"abstract" ...
"final" ...
"public" ...
"class" ...
"import" ...
"interface" ...
";" ...
"\\u001a" ...

SQL>

Cut and paste whole input and output.

Regards,

Sasa

PS
Run from TOAD (calling sql plus from TOAD)

          Followup:

so, why did you change my example and capitalize the word public?  Java is a
case sensitive language.

Use the code I provided as I provided it and it'll work.

Change the case -- and you are on your own.

I'll betcha that neat "toad" thingy pretty printed that for you did it?  well,
java isn't plsql, "toad" isn't going to do good things to it.  RETURN EXCEPTION
-- all of those "shouting statements" will fail (why do people like keywords
upper cased?  I've always found that to be 100% ugly)

      Rating: 3
Built-in
      owa_pattern  February 17, 2003
Reviewer:
      Alexander  from Russia
Maybe built-in package owa_pattern can be used? Comment from package:
   The package OWA_PATTERN is a "regular expression" pattern matching
   package.  There are 3 fundamental subprograms in OWA_PATTERN.

   The are: AMATCH, MATCH, and CHANGE.

      Rating: 5
TOAD
      capitalization effect   February 18, 2003

Reviewer:  Sasa  from Belgrade, Yugoslavia

Hi Tom,

You are completely right. I didn't notice that I had capitalization effect
option on in TOAD.
Now it works.Thanks again.

Regards,

Sasa

      Rating:
      4
  March 08, 2003
Reviewer:  j.

hi tom,

I 've tried your sample - everything worked very well. but I've now two
additional questions:

1st: what (in general) is the right way to remove all the database objects
created for this? actually the tool I use (SQL navigator) doesn't show
dependencies between stored programs "REG_EXP*" and their associated java
classes.

2nd: I get the following output from USER_OBJECTS:

/10074bae_Substitution
/184abd21_Perl5Repetition
/22eba5d6_Perl5Matcher
/2ca03ed7_MalformedPatternExce
/74c0e441_Perl5Substitution
/781a1e56_Perl5StreamInput
/83c2e19d_Perl5Compiler
/8a706168_StringSubstitution
/a572ed52_PatternMatcher
/af39100e_MatchResult
/b3a68f05_Perl5Pattern
/dbdcd9f3_PatternMatcherInput
/dc521360_CharStringPointer
/e8ec9c82_Perl5MatchResult
/ea37ca8e_PatternCompiler                                   /ede86355_Perl5Debug
                                       REG_EXP_COMPILE
                  REG_EXP_MATCH
RegExp                                                      RegExp
                                       com/oroinc/text/regex/OpCode
                  com/oroinc/text/regex/Pattern
com/oroinc/text/regex/Util

could you please explain, what has happened here, if there 's anything wrong?
where did all classes except of Util, OpCode and Pattern get their (unexpected)
names from, and is there any way to "rearrange" that?

thanks for your help.

and can be rearranged, that ALL classes include their pathes)?
anything wrong with the way I installed your example? could those entries with
"/" be avoided/hidden

          Followup:

1) use user dependencies -- but don't you know what you installed???  You drop
that which you install and it goes away.

2) they are the short names (we need 30 characters) of the external interfaces
available to you.  You can use dbms_java.longname to get their "longer" name
back.

How about this -- create a user regex.  Put all this stuff in there -- and
nothing more, nothing less.  Then, if you want to remove it -- drop user regex
cascade; Grant on that which you want exposed and the "ugly" object names don't
exist as far as anyone else is concerned.

      Rating:
      3
  March 08, 2003
Reviewer:  j.

30 characters only - I should have known.

thanks, DBMS_JAVA.LONGNAME helps very much to identify java classes through
their fully qualified names.

but one problem is left (to me): I've already searched user_dependencies and
didn't find links between PL/SQL code (REG_EXP_MATCH, REG_EXP_COMPILE) and java
source (RegExp) there. so do one have to do a "code search" in order to check
those dependencies?

          Followup:

create or replace procedure reg_exp_compile(
  p_test_input in varchar2)
as language java name 'RegExp.compile(java.lang.String) ';
/

See how it is done as a string there?

  1  create or replace procedure reg_exp_compile(
  2    p_test_input in varchar2)
  3* as language java name 'I_dontReally.ExistAnywhere(java.lang.String) ';
ops$tkyte@ORA920> /

Procedure created.

ops$tkyte@ORA920> exec reg_exp_compile('xxx' );
BEGIN reg_exp_compile('xxx' ); END;

*
ERROR at line 1:
ORA-29540: class I_dontReally does not exist
ORA-06512: at "OPS$TKYTE.REG_EXP_COMPILE", line 0
ORA-06512: at line 1

it is sort of like dynamic SQL in that sense.

      Rating:
      5
NullPointerException  April 25, 2003

Reviewer:  A reader
I tried the following:

begin
  reg_exp_compile('^[AB].*i$');
end;

select * from emp
where reg_exp_match(last_nm)=1

I got ORA-29532 NullPointerException

I changed this to

select * from emp
where  last_nm is not null
reg_exp_match(last_nm)=1

and everything worked fine. It gave me all last names starting with A or B and
ending with i.

So, apparenntly, the regexp package cannot handle a null input.

How can I fix this so that a null column doesnt match any regexp? So I dont have
to keep adding this 'and column is not null' condition to any query using this
regexp?

Thanks

          Followup:

just either add a check in the java or wrap yet another layer of plsql.

most efficient would be:

where decode( last_nm, null, 0, reg_exp_match(last_nm) ) = 1

as that would not even have the overhead of invoking the function (and your
approach is "unsafe", we can and will flip flop the order of evaluation of the
predicate AT WILL -- use the decode -- not the predicate - to ensure order of
operations)

      Rating: 3
Flip-flop the
      order?  April 25, 2003
Reviewer:
      A reader
Oracle flip-flops the order of evaluation of predicates at will?

Doesnt it follow the basic short-circuit boolean evaluation?
So, if I have

where 1=0
and <bunch of other stuff>

This query should come out right away, since it is never going to match
anything, right?

          Followup:

it is purposely "not said".

We can evaluate:

where A and B

as where A and then B or where B and then A.

You have NO real true 100% control over it.

      Rating:
      4
Substituting?  April 25, 2003
Reviewer:
      A reader
I wrote a fancy regexp using this method and it is working like a charm. Saved
me hours of work.

Logical extension: Can I use the Substitution methods in the ORO classes to
perform my substitutions on the matches that I found? Using the \(...\)
groupings in my compiled pattern and referencing them as \1, \2, etc in my
UPDATE

i.e. something like

update table
set column=Substitute(my_pattern,'\1..\2..')
where reg_exp_match(...)=1

This would be awesome. Anyway to do this?

Thanks

          Followup:

You would have to look at the ORO classes and see if they support the "replace"
sort of functionality.

      Rating: 4
Jakarta
      ORO  April 26, 2003
Reviewer:  A reader

Seems the ORO software has moved to
http://jakarta.apache.org/
I downloaded ORO 2.0.7 and added the
following method to your Java source

public static final String substitute(String p_input,String p_expression) throws
Exception
  {
    Perl5Util util = new Perl5Util();
    return util.substitute(p_expression,p_input);
  }

Now I can do

UPDATE table
set column=substitute(column,'s/pattern/replacement/')
where ...

Also, changed your compile procedure to a function so that it can be selected
from dual instead of having to run PL/SQL and then the SQL.

  public static final String compile( String p_pattern ) throws Exception
  {
      try {
          pattern = compiler.compile(p_pattern);
          return "Pattern compiled successfully: " + p_pattern;
      } catch (MalformedPatternException e) {
          return "Error compiling pattern: " + e.getMessage();
      }
  }

Thanks.

          Followup:

why

select regexp.compile( ? ) from dual;

is considered "easier" then

begin regexp.compile( ? ); end;

(especially when one takes more keystrokes to type......)

      Rating: 4
select from
      dual vs. pl/sql  April 27, 2003
Reviewer:
      A reader
Well, most front-end query apps here have no PL/SQL blocks in their code. They
are 100% query apps, so this just makes it easier to provide a 'select from
dual' interface to this feature rather than a 'begin ... end;' interface.

I guess its just a matter of personal preference...

      Rating: 5
fyi, how to
      make pattern_match work with backrefs  May 14, 2003

Reviewer:  Dave Lowenstein  from San Diego, CA

I searched high and low for how to make owa_pattern.match work with saving what
it found in an array of back references (backrefs). I figure that
asktom.oracle.com is a good place to put what I found. I pulled this out of thin
air, as it wasn't in any docs i found.

Just like in Perl regular expressions, your backrefs need to be between
parentheses to stand up and be counted. I don't know why i didn't think of that
before. Here's a working example of what i spent a large portion of yesterday
trying to make work. It finds any characters between two pipes and saves what it
finds into an array:

declare
a owa_text.vc_arr;
b boolean;
c varchar(3000):='|abc|';
begin
b:=owa_pattern.match(line=>c, pat=>'|(\w*)|', backrefs=>a);
if a.exists(1)
then
for i in a.first..a.last
loop
dbms_output.put_line(a(i));
end loop;
end if;
end;
/

SQL>/
abc

PL/SQL procedure successfully completed.

      Rating: 3
Largest
      matching string  July 11, 2003
Reviewer:  Arun
      Kalavath  from UK
Hi Tom
I am trying to do something similar. I have a list of phone numbers and I want
to return the largest matching number.

drop table t
  2  /

Table dropped.

SQL>
SQL> create table t(pnum varchar2(15) )
  2  /

Table created.

SQL> insert into t values ('0123456789')
  2  /

1 row created.

SQL> insert into t values ('012345678')
  2  /

1 row created.

SQL> insert into t values ('01234567')
  2  /

1 row created.

SQL> insert into t values ('0123456')
  2  /

1 row created.

SQL> insert into t values ('0123')
  2  /

1 row created.

SQL>
SQL> create or replace procedure get_best_match (Arg_pnum in t.pnum%type )
  2  as
  3  result t.pnum%type;
  4  BEGIN
  5  SELECT MAX( pnum ) into result
  6  FROM t
  7  WHERE (pnum = substr( Arg_pnum, 1, 1 )
  8        or pnum = substr( Arg_pnum, 1, 2 )
  9        or pnum = substr( Arg_pnum, 1, 3 )
 10        or pnum = substr( Arg_pnum, 1, 4 )
 11        or pnum = substr( Arg_pnum, 1, 5 )
 12        or pnum = substr( Arg_pnum, 1, 6 )
 13        or pnum = substr( Arg_pnum, 1, 7 )
 14        or pnum = substr( Arg_pnum, 1, 8 )
 15        or pnum = substr( Arg_pnum, 1, 9 )
 16        or pnum = substr( Arg_pnum, 1, 10 )
 17        or pnum = substr( Arg_pnum, 1, 11 )
 18        or pnum = substr( Arg_pnum, 1, 12)
 19        or pnum = substr( Arg_pnum, 1, 13 )
 20        or pnum = substr( Arg_pnum, 1, 14 )
 21        or pnum = substr( Arg_pnum, 1, 15 ));
 22     dbms_output.put_line (result);
 23  end get_best_match;
 24
 25  /

Procedure created.

SQL> select * from t;

PNUM
---------------
0123456789
012345678
01234567
0123456
0123

SQL> set serveroutput on

SQL>  exec get_best_match('0123');
0123

PL/SQL procedure successfully completed.

SQL>  exec get_best_match('01231');
0123

PL/SQL procedure successfully completed.

SQL>  exec get_best_match('0123456');
0123456

PL/SQL procedure successfully completed.

SQL> exec get_best_match('01234561');
0123456

PL/SQL procedure successfully completed.

SQL> exec get_best_match('01234562');
0123456

PL/SQL procedure successfully completed.

SQL>  exec get_best_match('01234567');
01234567

PL/SQL procedure successfully completed.

I.e. I need to return the highest matching number. As you can see currently we
use substring. Is there a better way to achive this?

Thank you
Arun

          Followup:

ops$tkyte@ORA817DEV> variable n varchar2(20)
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> exec :n := '0123'

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select substr( max(to_char(length(pnum),'fm00009') || pnum
), 6 )
  2    from t
  3   where :n like pnum||'%';

SUBSTR(MAX(TO_CH
----------------
0123

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> exec :n := '01231'

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> /

SUBSTR(MAX(TO_CH
----------------
0123

ops$tkyte@ORA817DEV> exec :n := '0123456'

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> /

SUBSTR(MAX(TO_CH
----------------
0123456

ops$tkyte@ORA817DEV> exec :n := '01234561';

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> /

SUBSTR(MAX(TO_CH
----------------
0123456

ops$tkyte@ORA817DEV> exec :n := '01234562';

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> /

SUBSTR(MAX(TO_CH
----------------
0123456

ops$tkyte@ORA817DEV> exec :n := '01234567';

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> /

SUBSTR(MAX(TO_CH
----------------
01234567

      Rating: 3
Sorry, forgot
      version number  July 11, 2003
Reviewer:  Arun
      Kalavath  from UK
We use Oracle 8.1.7. Enterprise edition.

Thanks
Arun

      Rating: 4
Always does a
      full tablescan  July 15, 2003
Reviewer:  Arun
      Kalavath  from UK
Hi Tom

Thank you for this reply. Could you tell me why you used "substr(
max(to_char(length(pnum),'fm00009') || pnum
), 6 )". As I understand it, this statments adds the length of the maximum value
returned as a 'fm00009' to the max value and then we substring this from the 6th
position. I got the same result in the same format when I did
select max(pnum) from t where :n like pnum||'%';
So what is the difference?

Question 2 :- I tried adding an index on pnum. But the query alway seems to do a
full table scan. How can I get the query to use an index on pnum column? Or is
this because I only got few records in the table?

SQL> create index xx on t (pnum);

Index created.

SQL> set autotrace traceonly
SQL>  select max(pnum) from t where :n like pnum||'%';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'T'

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

SQL> drop index xx;

Index dropped.

SQL> create unique index xx on t (pnum);

Index created.

SQL> select max(pnum) from t where :n like pnum||'%';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'T'

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

Thanks
Arun

          Followup:

to to_char() trick was used to get the PNUM that had the maximum length, the max
worked on the first characters -- getting the one with the maximum length.

I'm fairly sure "max" will work as well -- havent thought it all of the way out.

and of course it does a full scan -- it has to look at EACH and EVERY row.  that
-- and you are using the RBO and the RBO isn't very smart.

Consider if you used the CBO AND pnum was NOT NULL (or you added "and pnum is
not null" to the query itself -- since nulls are not indexed in b*trees)

ops$tkyte@ORA920LAP> create table t
  2  as
  3  select rownum pnum, a.* from all_objects a;

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create index t_idx on t(pnum);

Index created.

ops$tkyte@ORA920LAP> alter table t modify pnum NOT NULL;

Table altered.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> variable x varchar2(5)
ops$tkyte@ORA920LAP> exec :x := '1234'

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> set autotrace on explain
ops$tkyte@ORA920LAP> select max(pnum) from t where :x like pnum ||'%';

 MAX(PNUM)
----------
      1234

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'T'

ops$tkyte@ORA920LAP> analyze table t compute statistics for table for all
indexes for all indexed columns;

Table analyzed.

ops$tkyte@ORA920LAP> select max(pnum) from t where :x like pnum ||'%';

 MAX(PNUM)
----------
      1234

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=4)
   1    0   SORT (AGGREGATE)
   2    1     FIRST ROW (Cost=2 Card=1532 Bytes=6128)
   3    2       INDEX (FULL SCAN (MIN/MAX)) OF 'T_IDX' (NON-UNIQUE) (Cost=2
Card=1)

the cbo will use the index as a skinny table and full scan it very
efficiently

      Rating: 4
Thank you
      Tom  July 16, 2003
Reviewer:  Arun Kalavath
       from UK

      Rating:
      5
  September 12, 2003
Reviewer:
      A reader
Can we achive(pattern matching) this in oracle 10g feature "Regular Expressions"
?

Thanks

          Followup:

regular expressions are pattern matching, yes.

      Rating:
      5
OWA_PATTERN  September 12, 2003
Reviewer:
      Tatiane
Sirs, Oracle already provides a package for handling regular expressions:
OWA_PATTERN. I am using 9iR2. I imagine 10G will make it built-in in the
database.

Tom, why the heck is it not listed anywhere in the online docs (like dbms_system
is not either) ?

SQL> desc owa_pattern
FUNCTION AMATCH RETURNS NUMBER(38)
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 LINE                           VARCHAR2                IN
 FROM_LOC                       NUMBER(38)              IN
 PAT                            VARCHAR2                IN
 FLAGS                          VARCHAR2                IN     DEFAULT
FUNCTION AMATCH RETURNS NUMBER(38)
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 LINE                           VARCHAR2                IN
 FROM_LOC                       NUMBER(38)              IN
 PAT                            TABLE OF VARCHAR2(4)    IN/OUT
 FLAGS                          VARCHAR2                IN     DEFAULT
FUNCTION AMATCH RETURNS NUMBER(38)
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 LINE                           VARCHAR2                IN
 FROM_LOC                       NUMBER(38)              IN
 PAT                            VARCHAR2                IN
 BACKREFS                       TABLE OF VARCHAR2(32767) OUT
 FLAGS                          VARCHAR2                IN     DEFAULT
FUNCTION AMATCH RETURNS NUMBER(38)
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 LINE                           VARCHAR2                IN
 FROM_LOC                       NUMBER(38)              IN
 PAT                            TABLE OF VARCHAR2(4)    IN/OUT
 BACKREFS                       TABLE OF VARCHAR2(32767) OUT
 FLAGS                          VARCHAR2                IN     DEFAULT
FUNCTION CHANGE RETURNS NUMBER(38)
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 LINE                           VARCHAR2                IN/OUT
 FROM_STR                       VARCHAR2                IN
 TO_STR                         VARCHAR2                IN
 FLAGS                          VARCHAR2                IN     DEFAULT
PROCEDURE CHANGE
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 LINE                           VARCHAR2                IN/OUT
 FROM_STR                       VARCHAR2                IN
 TO_STR                         VARCHAR2                IN
 FLAGS                          VARCHAR2                IN     DEFAULT
FUNCTION CHANGE RETURNS NUMBER(38)
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 MLINE                          RECORD                  IN/OUT
   ROWS                         TABLE OF VARCHAR2(32767) IN/OUT
   NUM_ROWS                     NUMBER(38)              IN/OUT
   PARTIAL_ROW                  BOOLEAN                 IN/OUT
 FROM_STR                       VARCHAR2                IN
 TO_STR                         VARCHAR2                IN
 FLAGS                          VARCHAR2                IN     DEFAULT
PROCEDURE CHANGE
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 MLINE                          RECORD                  IN/OUT
   ROWS                         TABLE OF VARCHAR2(32767) IN/OUT
   NUM_ROWS                     NUMBER(38)              IN/OUT
   PARTIAL_ROW                  BOOLEAN                 IN/OUT
 FROM_STR                       VARCHAR2                IN
 TO_STR                         VARCHAR2                IN
 FLAGS                          VARCHAR2                IN     DEFAULT
PROCEDURE GETPAT
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 ARG                            VARCHAR2                IN
 PAT                            TABLE OF VARCHAR2(4)    IN/OUT
FUNCTION MATCH RETURNS BOOLEAN
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 LINE                           VARCHAR2                IN
 PAT                            VARCHAR2                IN
 FLAGS                          VARCHAR2                IN     DEFAULT
FUNCTION MATCH RETURNS BOOLEAN
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 LINE                           VARCHAR2                IN
 PAT                            TABLE OF VARCHAR2(4)    IN/OUT
 FLAGS                          VARCHAR2                IN     DEFAULT
FUNCTION MATCH RETURNS BOOLEAN
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 LINE                           VARCHAR2                IN
 PAT                            VARCHAR2                IN
 BACKREFS                       TABLE OF VARCHAR2(32767) OUT
 FLAGS                          VARCHAR2                IN     DEFAULT
FUNCTION MATCH RETURNS BOOLEAN
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 LINE                           VARCHAR2                IN
 PAT                            TABLE OF VARCHAR2(4)    IN/OUT
 BACKREFS                       TABLE OF VARCHAR2(32767) OUT
 FLAGS                          VARCHAR2                IN     DEFAULT
FUNCTION MATCH RETURNS BOOLEAN
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 MLINE                          RECORD                  IN
   ROWS                         TABLE OF VARCHAR2(32767) IN
   NUM_ROWS                     NUMBER(38)              IN
   PARTIAL_ROW                  BOOLEAN                 IN
 PAT                            VARCHAR2                IN
 RLIST                          RECORD                  OUT
   ROWS                         TABLE OF NUMBER(38)     OUT
   NUM_ROWS                     NUMBER(38)              OUT
 FLAGS                          VARCHAR2                IN     DEFAULT
FUNCTION MATCH RETURNS BOOLEAN
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 MLINE                          RECORD                  IN
   ROWS                         TABLE OF VARCHAR2(32767) IN
   NUM_ROWS                     NUMBER(38)              IN
   PARTIAL_ROW                  BOOLEAN                 IN
 PAT                            TABLE OF VARCHAR2(4)    IN/OUT
 RLIST                          RECORD                  OUT
   ROWS                         TABLE OF NUMBER(38)     OUT
   NUM_ROWS                     NUMBER(38)              OUT
 FLAGS                          VARCHAR2                IN     DEFAULT

          Followup:

owa_pattern, a very limited -- not too functional implementation -- is part of
the web toolkit and is documented in the same place htp, htf, owa, owa_util, et.
al. are.

in the ias documentation.  it is part of the webserver.

      Rating: 5
owa_pattern ,
      owa_text Installation  October 15, 2003
Reviewer:
      pasko  from HH,Germany
Hi Tom,

i'm back again to my original query:

It seems there's a new Version again for OROMatcher stuff:

your initial version for reg_exp_match and reg_exp_compile still works though,
with the addition of the new import statements as :

import org.apache.oro.text.regex.*;
import org.apache.oro.text.perl.*;

Download @:

http://jakarta.apache.org/oro/index.html
They have even provided an executable which can directly be loaded in the
Database with the loadjava Utility:

jakarta-oro-2.0.7.jar

------------------------------------------------------

A Question though about the owa_pattern and owa_match Packages :

-----------------------------------------------------

I note that, if i run the following scripts under the
$ORACLE_HOME/rdbms/admin  Folder ,

@pubpat.sql
@pubtext.sql
@privpat.sql
@privtext.sql

i could also get most of the Regular Expressions functionality that i wanted.

The Limitations for this owa_pattern as listed in the Package are :

--------------------------

 Possible future enhancements:
   -----------------------------
   * \B - match on non-"word" boundaries (between \w and \w, or \W and \W)

   * "or" character matches:
       change(text,'(Unix|unix)','UNIX') would change both occurances

   * Using control character references:

        A backslashed c followed by a single character, such as \cD, matches
        the corresponding control character.

   -- No support for:
   --   \b == Backspace (in a character class)
   --   \r == Carriage return
   --   \f == Form feed
   -- Modified support for:
   --   \s == A whitespace charcter -> [ \t\n\r\f]
   --   \S == A non-whitespace character

----------------------------------------------------

Are we allowed to install the packages as stand-alone Packages in other
Applications which might require their usage ?

Thanks in advance.

          Followup:

in 8i and up -- they should just be "already there" -- they are part of the
database, installed by default.

in 10g - the regex stuff is going to be fantastic.

      Rating: 5
How to pass
      Array of Pattern back and forth from Oracle to
      Java...  October 31, 2003
Reviewer:  pasko
       from HH,Germany
Hi Tom,

 I know regular  Expressions are coming in 10G,
but in the meantime, i was just trying to figure out how to pass an Array of
type: Pattern from your Example above:

i mean an array of Compiled Regular Expressions.

The Reason i am doing this is that i need to save the Compiled Patterns so that
i could re-visit some of them again when doing the Matching without recompiling
them again.

I know the solution in this case is optimzed for compile- once , match-many
times using the same compiled Pattern.

Now i want to Optimize it further by caching some frequently Used Patterns and
skip the Compilation process.

Something like this pseudo code :

    // I  get this from Oracle table
    String[]  tokens = { array_of_regular_Expressions } ;

    //   I need to keep track of some known number of
    //   Compiled patterns
    Pattern[]    patterns ;  // Array of patterns

    patterns  =   new  Pattern[ tokens.length ];

    for( int token=0; token < tokens.length; token++ ){

    patterns[token] = compiler.compile( tokens[token] );
    }

   I know using some Examples on this site, i could pass
   the String Array: tokens from Oracle to Java.

   But how to pass an Array of Class Type :  Pattern .

   Thanks in advance.

          Followup:

you would not pass an array of patterns?  You would just make that a static data
structure in the class itself.  there is no SQL type that corresponds to
"pattern", it is 100% java.

Just set up a hash table of "reg expression" to "compiled regex" and in the
compile function -- just do a lookup in the hash table to see if you've compiled
it and if so -- just set the pattern to that, else compile it, put it in the
hash table and set the pattern to that.

November 01, 2003 Vasaku -- Thanks for the question regarding
      "Writing form output to Excel ", version Forms 6i

      You Asked
Tom

I have a form , and the requirements is after the user queries the forms, he
should have the ability to click a button which will write the data from the
block to a  1.flat file , 2.Exel work sheet.

Can you provide me withe the information as to how we can achieve this.

Thanks

Vasaku

      and we said...
1) and 2) are basically the same -- since a .CSV (comma separated values) file
is both a flat file and something Excel will gladly open.

You would use TEXT_IO in forms to open and write the text file.  You would use
forms built-ins to navigate the block you wanted to save (first_record,
next_record built-ins).

If you wanted to get "fancy", you could use the SYLK file format for the
worksheet.  Lets you use fonts and such in the spreadsheet.  Search this site
for OWA_SYLK UTL_FILE to see an example from the database (you would replace
utl_file with TEXT_IO).
Review & Followup

      Rating: 4
Would like more
      info. about OWA_SYLK UTL_FILE:  August 06, 2002

Reviewer:  Mr. 420  from Like a mouse in the cage, I
      am trapped inside this cubicle
Hi Tom! This OWA_SYLK sounds pretty cool, because it allows you to use different
fonts, does it allow us do something else also? I went to Oracle 9i
documentation at

http://tahiti.oracle.com/pls/db92/db92.homepage?remark=tahiti
but to my surprise my search turned up nothing regarding this
package/utility. If you can point me in the right direction, I would really
appreciate it! Thanks!

          Followup:

Search this site
for OWA_SYLK UTL_FILE to see an example from the database

SYLK is an industry standard file format (search GOOGLE for info on SYLK)

OWA_SYLK is a package Chris Beck and I wrote together -- its "freeware"
available here.

      Rating:
      5
  August 18, 2002
Reviewer:  Vasaku

After writing to the Excel file, how can I automatically open it. That is , I
dont want to go and click the csv file to open it . As soon as I finish writing
to csv, the file should open itself.

I tried using..

HOST FILEPATH/FILENAME.. but doing this the dos window comes up and closes but
does not open the file.

Advise please.

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:878828052727

      Rating: 5
VERY VERY
      useful  August 18, 2002
Reviewer:  Vasaku

But Tom, when the csv file we are trying to write to is already in use we get an
error when we try to write to it(ORA-302000).

Now how can we caputure the state of the csv file we are writing even before we
try to write to that file , so that we can avoid writing to it and prompt the
user to close that file ,and then try to write to it..

Thanks

          Followup:

Thats windows locking it.

I guess you'll have to open it, try to write to it and catch the exception (that
is how you'll discover that it is already optening).

Else, you'll want to write a windows dll with windows specific OS calls that you
can call from ORA_FFI.  These windows specific calls (of which I know not -- not
a windows programmer) would tell you if the file is locked or not.

Path of least resistance -- write a small routine that opens and trys to write
to the file and then closes it.  Have that return a boolean true or false.  Call
that utility routine.

      Rating: 5
LPAD
      RPAD  August 18, 2002
Reviewer:  Vasaku

Tom

I'm building the query in forms dyanmically to pass it to the dump_csv routine
like

if some condition then
p_query := P_query ||'  another where condition'

I have a lot like the above.

To start with I have default sql like

Select
 col1,
 col2
 ....
 col10
from tabl
     tab2
     tab3
where  condition 1
       conditon  2
       condition 3
       rest of them to be developed dyanmically

In my when button pressed trigger which calls dump_csv I', repeatedly running
into ora-06502. But I have declared it as p_query (32000) which shoul be more
than sufficient, and I'm doing no conversions at all.

Now I thin I should get rid of the empty spaces for each column above. Is it
possible that I pass the entire sql into a function and get the scrubbed output
back without any empty spaces.

Can you suggest the solution

          Followup:

Your where clause is probably getting the 6502 -- not the query string itself.

You are building a where clause that compares a number to a date or a string to
a number or something and the conversion error is happening during the execution
of the QUERY itself.

You can prove this by putting an exception block around the execute statement in
the procedure.  You can:

  begin
      execute it
  exception
      when others then raise_application_error( -20001, 'Error processing your
query ' || sqlerrm );
  end;

if you start getting ora-20001's you know it is the query itself that is
generating the error (you are putting invalid where clauses there)

      Rating: 5
question about
      dump csv again  August 19, 2002
Reviewer:
      Vasaku
Tom

As  youknow I'm using dump_csv from oracle forms using TEXT_IO.

now for one of the columns in the query I'm passing, I need not the column
values, but the look up description for that column value.

And that column is always going to be in position 5 in the select clause.

For example lets say that the 5th column is deptno , but I want to write dname
to the flat file.

I see two ways of doing it .

1.Write a database function returning the description and make it part of the
select.

2.Capture the column in your dump_csv and get the description.

Can you please show how to achieve the solution using 2, as I'm discouraged to
use functions in the above scenario.

Thanks

          Followup:

I see many many more then 2 ways.

3) use a join

select emp.empno, emp.ename, dept.dname, emp.hiredate
  from emp, dept
where emp.depto = dept.deptno

4) use a select of a select

select empno, ename, (select dname from dept where dept.deptno=emp.deptno),
emp.hiredate
from emp;

Use #3.

      Rating: 5
#4 is more
      appealing   August 19, 2002
Reviewer:
      Vasaku
to me than #3, as I dont want to add an additional table to my query.

Theoritically which do you think is more economical i.e. #3,#4 or using a
database function, or doing the look up in the column count loop in the dump_csv
routine.

Actually, even in number 4 the way I seeit , if the lookup table has a lot of
rows and not properly indexes that will slow down the query in a big way.

Why did you suggest #3 and not #4.

Thanks

          Followup:

Doh -- read it again.  How many tables are in query #3, now, how many queries
are in query #4

The answer is the SAME.

You scare me (people like you do).  the database was built to join, joins
are not evil, improperly designed tables (eg: a lookup without an index perhaps)
are.  And in fact, query #3 would be infinitely preferable in all cases give
this example -- and we would certainly hope the cost based optimizer would AVOID
any and all indexes and do the right thing which would be a hash join for a
large set!!!!  That query doesn't WANT to use any indexes

      Rating: 5
o.k.
        August 19, 2002
Reviewer:  VASAKU

Tom

Sorry for scaring you, but I have never used select within a select the way  you
have used , that was the reason I was concerned.

So in effect when optimizer resolves #3 and #4 it takes the same path according
to you.

Now if #3 is better as you have said, I was interested to know as to why #3 is
more preferable to #4, and in what cases #4 would be the ideal solution.

Once agan sorry for scaring you.

          Followup:

No, you scare me because you are "afraid of joins", and that means you don't
believe the database should be asked to do heavy lifting and that is exactly
what the database is about.

I can show you 25 table joins that outperform a single table query.

I can show you the opposite.

The number of tables referenced in a query has no bearing whatever on its
performance.

#4 is cool if you want to avoid an outer join and there is a change that a row
in emp won't have a mate in dept.  It has other uses as well.

      Rating:
      5
link  August 19, 2002
Reviewer:
      Vasaku
---------
#4 is cool if you want to avoid an outer join and there is a change that a row
in emp won't have a mate in dept.  It has other uses as well.
-------------
Tom can you give me a link or discuss the "other uses as well"

How can I avoid a outer join using #4

          Followup:

The "how can I avoid an outer join" should be obvious.  You use that technique
(#4) to pick up the dname from dept instead of outer joining EMP to DEPT --
thats that.

Its other users might be to get the first rows of a complex query back faster --
to run little correlated subqueries for each row instead of a massive
sort/aggregate.

I've used it when I don't know what table I want to join to -- consider:

select object_type, object_name,
       decode( status, 'INVALID', '*', '' ) status,
       decode( object_type,
                'TABLE', (select tablespace_name from user_tables where
                              table_name = object_name),
                'TABLE PARTITION', (select tablespace_name from
        user_tab_partitions where partition_name = subobject_name),
                'INDEX', (select tablespace_name from user_indexes where
          index_name = object_name),
                'INDEX PARTITION', (select tablespace_name from
         user_ind_partitions where partition_name = subobject_name),
                'LOB', (select tablespace_name from user_segments where
         segment_name = object_name),
                null ) tablespace_name
from user_objects a
order by object_type, object_name
/

(oh yes, I know I could have used user_segments -- I just found the performance
of that to not be quite as good as this)

Use your imagination.

      Rating: 4
Another way to
      get data to EXCEL from FORMS  August 20, 2002

Reviewer:  Tom Reid  from Edinburgh, Scotland

An often overlooked way to get data from forms to EXCEL is by using the PLL
library function write_block. You can define the separation character between
the fields it writes, thus can easily produce CSV output files for reading into
EXCEL. If you put the code for this behind an ICON your whole forms app can use
it.

Please see my website
http://the-big-o.port5.com/
for the articles on using PLL libraries in FORMS

      Rating: 5
INLINE VIEW IN
      FROM CLAUSE AND DUMP_CSV  August 20, 2002

Reviewer:  Vasaku
Is the dump_csv smart enough to handle inline views in the from clause of a
select which we pass into dump_csv.

For example if the from clause is like

emp,dept,(select a,b from c)

can the dbms_sql.parse capable enough to parse the above from clause

          Followup:

yes.

      Rating: 5
WinApi for
      selecting directory and path  August 23, 2002

Reviewer:  Vasaku
How can I call the utility which will open a  dialog box, just like save as or
open in windows , and will allow the user to select the directory and file to
write his Excel output .

Does forms has any builtins to call the win api dialog box to select the
directory and file?

          Followup:

Not that I am aware of.  Forms has a utility to let you pick an existing file
(get_filename or something like that) but I'm not sure if it lets you pick a
non-existent one.

try
http://otn.oracle.com/
under discussion forums -> developer.   I myself haven't written a form
since 1995...

      Rating: 5
Know before
      hand  August 23, 2002
Reviewer:  Vasaku

Tom

Is there any way I can gather the amount of data which a query is going to
return,i.e. I want to write to excel file only when the amount of data is more
that a particular size or particular number of rows.

Is this possible , or how can we make this possible?

          Followup:

Not even we know the answer to that.

Sure, we can "estimate" it -- if you were to explain the query and you were
using the cost based optimizer -- we attempt to guesstimate the cardinality of
the result set:

scott@ORA920.LOCALHOST> analyze table emp compute statistics;
Table analyzed.

scott@ORA920.LOCALHOST> analyze table dept compute statistics;
Table analyzed.

scott@ORA920.LOCALHOST> set autotrace traceonly explain
scott@ORA920.LOCALHOST> select * from emp;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=14 Bytes=448)
   1    0   TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=14 Bytes=448)

scott@ORA920.LOCALHOST> select count(*) from emp;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=14)

scott@ORA920.LOCALHOST> select * from emp, dept;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=10 Card=56 Bytes=2800)
   1    0   MERGE JOIN (CARTESIAN) (Cost=10 Card=56 Bytes=2800)
   2    1     TABLE ACCESS (FULL) OF 'DEPT' (Cost=2 Card=4 Bytes=72)
   3    1     BUFFER (SORT) (Cost=8 Card=14 Bytes=448)
   4    3       TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=14 Bytes=448)

scott@ORA920.LOCALHOST> set autotrace off

So, you could explain the query and use the estimated total CARD but it could be
way off.

The bottom line, until you fetch the last row -- neither one of us knows the
total number of rows you'll be getting!

      Rating: 5
How to send
      reports to excel tab pages in web.  October 31, 2003

Reviewer:  suresh  from India
I have a form and the requirements is after the user queries the forms, he
should have the ability to click a button which will write the data from the
block to a Exel work sheets(Multiple Tabpages - Each sheet has one report).
(Take dept report is send to one tab sheet and emp is send another tab sheet )
and i am generating this report in Web
by using Oracle9i and Forms 6i.

I am invoking the Excel using

 OWA_UTIL.MIME_HEADER('application/vnd.ms-excel');

Can you provide me with the information as to how we can achieve this.( pls tell
me all possible solutions).

   pls reply immd...

 Regards,
  suresh.

          Followup:

sorry -- don't do windows myself.  the only thing i know is an open systems
standard called SYLK that is not MS proprietary.  It is a generic file format.
Do not know if it does "work sheets" or not.

      Rating: 5
very very
      urgent - excel reports.  October 31, 2003

Reviewer:  Suresh  from India
Tom ,

I have a form , and the requirements is after the user queries the forms, he
should have the ability to click a button which will write the data from the
block to a
Exel work sheets(Multiple tabs).

(This is not related to Multiple-tab sheets, Consider Each report as Individual
Report).

But client asking to consider each tab sheet as one ExcelReport. (Consider Dept
as one excel report and emp as another excel report).(no tab sheets)

i am Written one procedure to invoke excel by  using
  OWA_UTIL.MIME_HEADER('application/vnd.ms-excel');
  and sended data to this invoking excel(dept report).

  but problem is next to invoke excel again and generate another excel emp
report.

   i have one button in form to generate two excel reports ( dept , emp as
separate excel reports ) ?

   i am done this application in Web, using Oracle 9i & Forms 6i(when ever i
click this button in web , it generate two separate excel reports(dept,emp) ).

Can you provide me with the information as to how we can achieve this.
Pls reply immd by all possible Options. very Urgent.

Thanks
 Suresh.

          Followup:

as i've said before -- i don't do windows.

i don't use excel

i quite simply "don't know"

November 01, 2003 John -- Thanks for the question regarding
      "Password Management", version 8.1.5

      You Asked
Hi,
Can I, as DBA, force users to change their password on first
use...In other words, I want to assign a temporary password when
I create the user and force the user to change it on their first
login...I tried looking at the profile password settings, but
nothing seemd to do what I wanted...Is this posssible to do?

Thanks,
John Greco
Oracle DBA/Application Developer
Minnesota Department of Transportation

      and we said...
Yes you can in Oracle8i.  It would look like this:

ops$tkyte@8i> drop user a cascade;

User dropped.

ops$tkyte@8i> create user a identified by a password expire;

User created.

ops$tkyte@8i> grant connect to a;

Grant succeeded.

ops$tkyte@8i> connect a/a
ERROR:
ORA-28001: the password has expired

Changing password for a
Old password:
New password:
Retype new password:
Password changed
Connected.

ops$tkyte@8i> show user
USER is "A"
ops$tkyte@8i>

Be aware -- the client that connects to the database  is
responsible for doing this "change" password magic.  There is no
magic ability for the database to reach out and interact with
the end user here.  For example, if I was writing a command line
pro*c program and wanted to be able to deal with this expired
password, I might code something like:

main( argc, argv )
int argc;
char *  argv[];
{
char    oracleid[50];
char    newpw[255];

    strcpy( oracleid, USERID );

    EXEC SQL WHENEVER SQLERROR CONTINUE;
    EXEC SQL CONNECT :oracleid;

    printf( "sqlca.sqlcode = %d\n", sqlca.sqlcode );
    if ( sqlca.sqlcode == -28001 )
    {
        printf( "Password expired, enter new one\n" );
        printf( "Password: " );
        gets(newpw);
        EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();
        EXEC SQL CONNECT :oracleid ALTER AUTHORIZATION :newpw;
    }
    else if ( sqlca.sqlcode < 0 )
    {
        sqlerror_hard();
    }

    printf("\nConnected to ORACLE as user: %s\n\n", oracleid);
......

When I set USERID to a/a and do the same test as above, my
program generates:

$ ./t
sqlca.sqlcode = -28001
Password expired, enter new one
Password: newpw

Connected to ORACLE as user: a/a

But you must consider that it was MY program that did the
password prompting and everything.

Some tools (forms for example) can deal with this -- others
might not be able to (depends on their support for Oracle
features).

Review &
      Followup

      Rating: 4
What about from
      web?  May 14, 2002
Reviewer:  Robert Boyle
       from Ireland
Tom,

Good answer and helpful.  Do you have any idea how to do this from a web app
developed in Coldfusion?

We tried the "alter user blah identified by blah" but we can't execute the
cfquery as the password has expired.  We can catch the -28001 code but as we
can't execute the cfquery we don't know what to do with it :o(

I notice the Pro C uses ALTER AUTHORIZATION - is there something similar we
could use on expired passwords?

Thanks in advance.
Robert

      Rating: 2
Is there an
      answer to Robert's question?  September 26, 2002

Reviewer:  Jay Bostock  from Scotland
I was just wondering if there is an answer to Robert's question above - I am
writing a web-app using JDBC to connect to an oracle database and need to handle
this error (ora-28001). How could I do this?

Thanks
Jay

          Followup:

Well, actually -- you don't want an answer to his question as that would be very
specific to that very specific product cold fusion (about which I know squat).

Here is a support note (also avaialable on metalink by searching for

ora-28001 jdbc

surprisingly!) that addresses YOUR question:

Article-ID:         <Note:124970.1>
Circulation:        PUBLISHED (EXTERNAL)
Folder:             languages.Java.JDBC
Topic:              Sample Code Articles
Title:              Example: How to Change an Expired Password in JDBC?
Document-Type:      SCRIPT
Impact:             LOW
Skill-Level:        NOVICE
Server-Version:     08.01.07
Updated-Date:       04-SEP-2002 07:12:09
References:
Attachments:        NONE
Content-Type:       TEXT/PLAIN
Products:           972/OCI;
Platforms:          GENERIC;

Overview
--------

Starting with Oracle8i, the database provides a number of password management
capabilities including the ability to expire passwords with or without some
grace period during which the user has an option to change the password.

If the password has expired with no grace period, the attempt to connect to
the database returns an ORA-28001 error. In this case, the user must change
the password before continuing as the current connection is not valid.

If there is a grace period, the database returns an ORA-28002 warning and the
user has the option to change the password if desired, but can continue to use
the existing connection if not (ORA-28002 is a warning only).

The capability to programmatically change the password has been available in
OCI8 since 8.1.5, but was only recently exposed via JDBC, starting in 8.1.7.
Currently, this capability is only exposed in the 8.1.7 JDBC OCI driver. The
JDBC Thin driver does not currently support any password management features.

Program Notes
-------------

This sample has been tested using the following configuration:

  Sun JDK 1.2 for Solaris
  8.1.7 JDBC OCI driver for Solaris
  8.1.7 Enterprise Edition Server for Solaris

The 8.1.7 JDBC OCI driver is mandatory, but the use of JDK 1.2 should not be
required. Also, this should work against any 8.1.X version of the database.

To run this sample, do the following:

a.) Set up your environment for Oracle (i.e. ORACLE_HOME, etc.)

b.) Set up your CLASSPATH to include the Oracle JDBC drivers

  setenv CLASSPATH .:$ORACLE_HOME/jdbc/lib/classes12.zip  (C shell)

    OR

  export CLASSPATH=.:$ORACLE_HOME/jdbc/lib/classes12.zip  (Korn/Bourne shell)

  NOTE: If you are using JDK 1.1, substitute the 8.1.7 classes111.zip file.

c.) Compile the java source code to create a Class file

  javac Employee.java

d.) Run the program

  java Employee -- defaults to local connection as scott/tiger

    OR

  java Employee url user pass -- to specify different connect parameters

    url contains the complete JDBC connect url including jdbc:oracle:oci8:@

Caution
-------

The sample program in this article is provided for educational purposes only
and is NOT supported by Oracle Support Services.  It has been tested
internally, however, and works as documented.  We do not guarantee that it
will work for you, so be sure to test it in your environment before relying
on it.

Program
-------

 - - - - - - - - - - - - - - - - Code begins here - - - - - - - - - - - - - - -
-

/*
 * This sample is a modified version of the Employee.java
 * sample that ships with 8.1.X. It demonstrates how to use
 * the new Password Management/Password Aging features supported
 * in JDBC OCI starting with version 8.1.7.
 *
 * NOTE: Password management isn't available in the Thin driver!
 *
 * If invoked with no args, it uses the scott/tiger user and
 * the default connection. If you wish to use a TNS service
 * name, different user and/or password, you can invoke as:
 *
 *     java Employee url user password
 */

import java.io.*;
import java.sql.*;
import java.util.*;

// The Sample Java application

class Employee
{
  public static void main (String args []) throws SQLException
  {
    // Load the Oracle JDBC driver

    DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());

    // Default connection (but could be modified to specify service name)

    // NOTE: Must use oci8 here ... not supported by the thin driver

    String url = "jdbc:oracle:oci8:@";
    String usr = "scott";
    String pwd = "tiger";

    // Use singleton array so we can pass the connection to
    // another function as an OUT parameter

    Connection conn[] = new Connection[1];

    // Use the command line args if provided

    if ( args.length >= 1 ) url = args[0];
    if ( args.length >= 2 ) usr = args[1];
    if ( args.length == 3 ) pwd = args[2];

    // Create a properties object and initialize it

    Properties props = new Properties();
    props.put("user",usr);
    props.put("password",pwd);

    // Attempt the initial connection to the database

    try {

       System.out.println("Attempting initial connection to " + url +
                          " as " + usr + "/" + pwd);

       conn[0] = DriverManager.getConnection (url, props);

       // If the password has expired, but there is a grace
       // period, you will receive a warning, but no error.

       SQLWarning sqlw = conn[0].getWarnings();

       if ( sqlw != null && sqlw.getErrorCode() == 28002 )
       {
          // For interacting with user to get new password

          String answer = null;
          DataInputStream dis = new DataInputStream(System.in);

          System.out.println("Your password will expire soon!");
          System.out.println("Would you like to change it now (y or n)?");

          try {
            answer = dis.readLine();
          }
          catch ( IOException ioe ) {
            ioe.printStackTrace();
          }

          // If user does not want to change the password, just
          // continue. The original connection is ok to use.

          if ( answer.equals("y") || answer.equals("Y") )
          {
             // Otherwise, close the original connection
             // and open a new one, changing the password.

             // Technically, it is not necessary to close
             // the other connection, but this will waste
             // resources by keeping the session alive if
             // you have no intention of using it.

             conn[0].close();

             retryConnection(conn, url, props);
          }
       }
    }
    catch ( SQLException sqle ) {

       // If the password has expired and there is no grace
       // period, then you must change the password or exit.

       if ( sqle.getErrorCode() == 28001 ) {

          // For interacting with user to get new password

          String answer = null;
          DataInputStream dis = new DataInputStream(System.in);

          System.out.println("Your password has expired!");
          System.out.println("Would you like to change it now (y or n)?");

          try {
             answer = dis.readLine();
          }
          catch ( IOException ioe ) {
            ioe.printStackTrace();
          }

          // If user wants to change password, do it.
          // Otherwise exit since connection is unusable.

          if ( answer.equals("y") || answer.equals("Y") )
          {
             retryConnection(conn, url, props);
          }
          else
          {
             System.out.println("Exiting ... ");
             System.exit(0);
          }
       }
    }

    // Create a Statement
    Statement stmt = conn[0].createStatement ();

    // Select the ENAME column from the EMP table
    ResultSet rset = stmt.executeQuery ("select ENAME from EMP");

    // Iterate through the result and print the employee names
    while (rset.next ())
      System.out.println (rset.getString (1));

    // Close the RseultSet
    rset.close();

    // Close the Statement
    stmt.close();

    // Close the connection
    conn[0].close();
  }

  private static void retryConnection(Connection conn[],
                                      String url,
                                      Properties props) throws SQLException {

    Connection newconn = null;
    String newpass = null, newpass2 = null;
    DataInputStream dis = new DataInputStream(System.in);

    try {
       System.out.println("New Password: ");
       newpass = dis.readLine();

       System.out.println("Confirm Password: ");
       newpass2 = dis.readLine();
    }
    catch ( IOException ioe ) {
    }

    if ( ! newpass.equals(newpass2) ) {
       System.out.println("Passwords don't match!");
       System.out.println("Exiting ... ");
       System.exit(1);
    }

    props.put("OCINewPassword",newpass);

    System.out.println("Attempting to change password ... ");

    newconn = DriverManager.getConnection(url, props);

    System.out.println("Connected");

    conn[0] = newconn;
  }
}

 - - - - - - - - - - - - - - - -  Code ends here  - - - - - - - - - - - - - - -
-

Sample Output
-------------

If the password is current and no ORA-28001 or ORA-28002 error occurs.

Attempting initial connection to jdbc:oracle:oci8:@ as scott/tiger
smith
allen
ward
jones
martin
blake
clark
scott
king
turner
adams
james
ford
miller

If the password is expired and either a ORA-28001 or ORA-28002 error occurs.

Attempting initial connection to jdbc:oracle:oci8:@ as scott/tiger
Your password has expired!
Would you like to change it now (y or n)?
y
New Password:
tiger2
Confirm Password:
tiger2
Attempting to change password ...
Connected
smith
allen
ward
jones
martin
blake
clark
scott
king
turner
adams
james
ford
miller

The one notable difference is that, for ORA-28002, you should see the message:

  Your password will expire soon!

Additional Search Words
-----------------------

expiration

      Rating:
      1
  October 31, 2003
Reviewer:
      A reader

          Followup:

hmm, very useful feedback.  thanks?

November 01, 2003 Sergey -- Thanks for the question regarding "DB
      JOB time shift", version 9.2.0

      You Asked
Hello Tom!

During daily observation I have noticed, that DBA_JOBS.NEXT_DATE value
shifts a little every day. I've made test case, take a look please:

create or replace package TEST1 as
procedure wait1;
end;
/

create or replace package body TEST1 as
procedure wait1 is
  begin
    sys.dbms_lock.sleep(10);
  end;
end;
/

select text from sys.dba_errors where owner=user;

create or replace package TEST2 as
procedure wait2;
end;
/

create or replace package body TEST2 as
procedure wait2 is
  begin
    sys.dbms_lock.sleep(100);
  end;
end;
/

select text from sys.dba_errors where owner=user;

DECLARE
  jobno number;
BEGIN
  DBMS_JOB.SUBMIT(jobno, 'TEST1.WAIT1;', sysdate, 'sysdate + 1');
  DBMS_JOB.RUN(jobno);
END;
/
commit;

DECLARE
  jobno number;
BEGIN
  DBMS_JOB.SUBMIT(jobno, 'TEST2.WAIT2;', sysdate, 'sysdate + 1');
  DBMS_JOB.RUN(jobno);
END;
/
commit;

show parameter job
column "Sysdate" format a15
column last_date format a15
column next_date format a15
column "What" format a15

select job, to_char(sysdate, 'dd-mon hh24:mi:ss') "Sysdate",
to_char(last_date, 'dd-mon hh24:mi:ss') last_date,
to_char(next_date, 'dd-mon hh24:mi:ss') next_date,
substr(what, 1, 14) "What", total_time
from sys.dba_jobs;

And the output is:

Package created.

Package body created.

no rows selected

Package created.

Package body created.

no rows selected

PL/SQL procedure successfully completed.

Commit complete.

PL/SQL procedure successfully completed.

Commit complete.

       JOB Sysdate         LAST_DATE       NEXT_DATE       What
TOTAL_TIME
---------- --------------- --------------- --------------- ---------------
----------
         2 06-oct 14:16:56 06-oct 14:08:31 07-oct 14:08:31 TEST1.WAIT1;
  11
         3 06-oct 14:16:56 06-oct 14:08:42 07-oct 14:08:42 TEST2.WAIT2;
 102

Next day:

       JOB Sysdate         LAST_DATE       NEXT_DATE       What
TOTAL_TIME
---------- --------------- --------------- --------------- ---------------
----------
         2 07-oct 14:20:43 07-oct 14:08:36 08-oct 14:08:36 TEST1.WAIT1;
  21
         3 07-oct 14:20:43 07-oct 14:08:46 08-oct 14:08:46 TEST2.WAIT2;
 205

Next day:

       JOB Sysdate         LAST_DATE       NEXT_DATE       What
TOTAL_TIME
---------- --------------- --------------- --------------- ---------------
----------
         2 08-oct 17:01:39 08-oct 14:08:41 09-oct 14:08:41 TEST1.WAIT1;
  31
         3 08-oct 17:01:39 08-oct 14:08:51 09-oct 14:08:51 TEST2.WAIT2;
 308

You see, seconds are shifting, and it's not very depends of time for own job.
How can I use this mechanism for business critical operations?
For one of my servers time shift reaches more than 10 minutes now.
Is something wrong?
Is it related with [BUG:2649244] "JOBS may not run at scheduled intervals"?

Thanks In Advance.

      and we said...
it is the expected, defined behavior.

the job runs "on or about the scheduled time".  The queue is inspected every now
and again and the job is run.  So, I job initially scheduled to run at midnight
-- might run at 12:01 am.  Then, the next date is computed -- "sysdate+1".  Now
the job is scheduled to run tommorow at 12:01 am -- NOT midnight.

So, you need to use date functions that evaluate to a fixed time in the future.

For example, you want a job to run at 1am every night:

  trunc(sysdate)+1+1/24

that will always evaluate to 1am TOMORROW -- regardless of when you evaluate it.
Review &
      Followup

      Rating:
      4
  October 28, 2003
Reviewer:  Dave
       from Colorado
This is unrelated to the shifting behaviour ...

Because DBMS_JOB does not guarantee an exact start time, one available
methodology for getting closer to an exact time would seem to be to schedule the
job to start a little early then start the procedure with a call to
dbms_lock.sleep()

For example, to run a job at three am exactly, you might schedule the procedure
for 2:55 am, then call ...

dbms_lock.sleep((trunc(sysdate)+(3/24)-sysdate)*24*60*60);

          Followup:

that would just cause cascading problems as the job queue process would be STUCK
sleeping.

Nothing but nothing is going to be dead on exact

      Rating:
      5
Great!  October 28, 2003
Reviewer:
      Christo Kutrovsky  from Ottawa, ON Canada
Great idea Dave  !!! that will come handy one day!.

          Followup:

please don't

a) no assurance the job starts with enough time to "sleep" in the first place

b) you'll consume a job queue process for the duration of the sleep!

      Rating: 4
DB JOB time
      shift  October 29, 2003
Reviewer:  Sergey
       from Germany
Many thanks for the explanation!
The major idea is that NEXT DATE is computing AFTER end of job as I understand.

          Followup:

start of job, not end.

if you have my book "Expert one on one Oracle" -- i've a pretty comprehensive
section on DBMS_JOB and scheduling with -- how to set up static timeframes, how
to do custom schedulings, how to use it correctly.

      Rating: 4
Ref:
      Sleeping  October 29, 2003
Reviewer:  Dave
       from Colorado
"a) no assurance the job starts with enough time to "sleep" in the first place"

Start it earlier, surely?

"b) you'll consume a job queue process for the duration of the sleep! "

Is there an alternative method you can suggest for getting closer to a
guaranteed start time, Tom?

          Followup:

like a dog chasing its tail.

So, now you have a job queue process sitting there sleeping (and not running
anything -- meaning other jobs that are trying to start earlier to sleep
themselves cannot -- meaning everything starts getting backed up)

Normally, if the database is up and running, if there are sufficient job queue
processes configured, the jobs will in fact start within SECONDS of their
scheduled start.

So, it is not really "an issue".

if you didn't have sufficient job queues configured -- sleeping on them would
certainly ensure jobs would be delayed even more!  so, if you didn't have enough
configured, that would only make it worse.

So, assuming you have them configured appropriately -- you would then just make
sure the db was up.  if the db wasn't up -- sort of moot.

So, in real life, jobs start within seconds of their scheduled start and its not
an issue.  but to prevent the inevitable "slide" in time - you need to use
"anchored" next functions -- functions that evaluate to a fixed time in the
future, not just sysdate+1.  A 20 second slide over 10 days starts to add up

      Rating:
      4
  October 30, 2003
Reviewer:  Dave
       from Colorado
Ah, did you mean that the session might not start at the scheduled time because
of insufficient job_queue_processes?

I interpreted your about a 12:00 job maybe starting at 12:01 to mean that the
scheduler might start it later than expected even if there were sufficient
queues, because it's not necessarily accurate in it's start time, or because
(more likely guess) the job_queue_interval (in 8i) does not match the
granularity of the time you want the job to start -- eg. it might wake up every
ten minutes, but you want the job to start at 12:05.

          Followup:

it is a function of

o insufficient job queues
o jobs hogging job queues (variation of above)
o the fact that in 8i, 9i, for ever -- job queues are polled.  they will NEVER
be exact, never.  they will slip by seconds at the very very least.  even if you
use dbms_lock.sleep -- you yourself would be subject to a slip of a second,
which over time would add up!

      Rating: 3
Combine your
      ideas for a (maybe) useful technique  October 31, 2003

Reviewer:  Michael  from Austria
I think, if you simple connect both of your ideas to a combined technique you
get the best method to let a job start at an exact date (although I have to
admit that I cannot think of a case where this could be mandatory)
So if you want to have an action starting nearly exactly at 13:00 each day
(assuming a default JOB_QUEUE_INTERVAL of 60):

use trunc(sysdate+1)+13/24-2/1440 for next_date of your job
use dbms_lock.sleep((trunc(sysdate)+13/24-sysdate)*86400) at the beginning of
your job

      Rating:
      4
  October 31, 2003
Reviewer:  Dave
       from Colorado
Well, as i originally said .. "This is unrelated to the shifting behaviour ...".
I think you missed that comment.

The DBMS_LOCK.SLEEP() methodology was not meant to counteract the dbms_job start
time shifting -- i take it as gospel that you would specify the start time with
a function that prevents slippage, and I also assumed that there were sufficient
job queue processes.

It was just in case dbms_job didn't start jobs with the precision required by
the application _for_other_reasons_. If it was not acceptable that a job might
start 30 seconds late, then starting it five minutes early and letting it sleep
until the correct start time might give a more accurate start time for the main
functionality of the procedure.

You know what might be a useful enhancement? Prioritisation within the job
queue, or multiple queues with usage restricted through different permission
sets.

          Followup:

operative words here are "might give "  but that conflicts with "not acceptable"
:)

10g will pretty address that last part

      Rating: 3
DB JOB time
      shift  November 03, 2003
Reviewer:  Jürgen
       from Austria
Hi, I had the same problem running a job that takes data once a hour. The job
should run 10 minutes after a full hour and we know the problem of time
shifting.
We solved this problem by using a function instead of sysdate which gets the
next date for our interval.
So no matter what sysdate is we round to the next hour +10 minutes of course
this is also possible for days, months and minutes ...

It was the first time I visited this site because I'm know reading Effective
Oracle by Design and the page is great and the book also!

November 01, 2003 Luigi -- Thanks for the question regarding
      "utl_file_dir instance parameter and UTL_FILE - usage information",
      version 9.0.1

      You Asked
Hi, Tom, nice to see you online again!
I'm managing a project which has several tasks. One of them is to produce XML
files from SQL queries (in PL/SQL stored procedures) and store them on the
server (by the way, it's a Linux Suse 7.3 - installing Oracle Server 9i wasn't
so simple...).
I tried using DBMS_XMLQUERY package, and wrote the following (in SCOTT schema,
for testing purpose only), which creates a XML clob from a query:

Function       GET_XML_FROM_QUERY(sql_statement varchar2)
  RETURN  clob IS
   l_ctx    dbms_xmlquery.ctxType;
   l_xml    clob;
BEGIN
    dbms_lob.createtemporary(l_xml,true,dbms_lob.session);
    l_ctx := dbms_xmlquery.newContext(sql_statement);
    dbms_xmlquery.setRaiseNoRowsException(l_ctx, false);
    l_xml := dbms_xmlquery.getXML(l_ctx);
    dbms_xmlquery.closeContext(l_ctx);
    RETURN l_xml;
EXCEPTION
   WHEN others THEN
       dbms_xmlquery.closeContext(l_ctx);
       raise;
END;

This works fine. Then I tried to store the clob returned into a physical file in
the server. I wrote the following:

procedure xmltofile(p_xml in clob, my_path varchar2, my_file varchar2,
my_open_mode varchar2) is
        l_xml  varchar2(30000);
        l_line varchar2(2000);
        l_amt  pls_integer := 30000;
        l_idx  pls_integer := 1;

   my_file_handle                 utl_file.file_type;
BEGIN
    my_file_handle := utl_file.fopen_nchar(my_path, my_file, my_open_mode);
        for i in 1 .. ceil(dbms_lob.getlength(p_xml)/l_amt) loop
         l_xml := dbms_lob.substr(p_xml, l_amt, l_idx);
         loop
           exit when l_xml is null;
           l_line := substr(l_xml, 1, instr(l_xml, chr(10))-1);
           utl_file.put_line_nchar(my_file_handle, l_line);
           l_xml := substr(l_xml, instr(l_xml, chr(10))+1);
         end loop;
       end loop;
    utl_file.fclose_all;
exception
    when others then utl_file.fclose_all;
    raise;
     end xmltofile;

Some of the code above was taken from an answer you gave in the past to someone
else.
Then, I set up the utl_file_dir instance parameter with the value '/u01/utldir',
which is a folder I created in my Linux server (CHMOD 777 to avoid any problem).
I tried to execute the second procedure as follows:

BEGIN
  xmltofile(get_xml_from_query('select * from dept'), '/u01/utldir', 'test.xml',
'w');

EXCEPTION
WHEN OTHERS THEN
  dbms_output.put_line('Error '||TO_CHAR(SQLCODE)||': '||SQLERRM);
RAISE;
END;

It worked. The file test.xml was generated correctly.
So, what's the problem? Simple to tell, maybe harder to solve.
What about if I would generate a file not just in the /u01/utldir folder, but
also in the /u01/utldir/userdir folder?
In other words, I'd like to generate the output files placing them in different
dirs for different users. Is there any way to do that?
I just tried statements like:

  xmltofile(get_xml_from_query('select * from dept'), '/u01/utldir/userdir',
'test.xml', 'w');

or

  xmltofile(get_xml_from_query('select * from dept'), '/u01/utldir',
'./userdir/test.xml', 'w');

but none worked.

Thank you very much.

Luigi

      and we said...
utl_file_dir works directory by directory and never never includes
subdirectories.

You can use a java stored procedure to do this easily -- I asked
Mark.Piermarini@oracle.com to help me out quickly.  You can do this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2          dbms_java.grant_permission
  3          ( USER,
  4           'java.io.FilePermission',
  5           '/tmp/-',
  6           'write' );
  7  end;
  8  /
PL/SQL procedure successfully completed.

(the "-" allows for recusive permissions)

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace
  2  and compile
  3  java source named "ClobUtil"
  4  as
  5
  6  import java.io.*;
  7  import java.lang.*;
  8  import java.sql.*;
  9  import java.util.*;
 10  import oracle.jdbc.driver.*;
 11  import oracle.sql.*;
 12
 13  public class ClobUtil {
 14    public static void save(CLOB clob, String filename)  throws Exception
 15    {
 16        Connection conn = DriverManager.getConnection("jdbc:oracle:kprb:");
 17        conn.setAutoCommit(false);
 18
 19          InputStream is = clob.getAsciiStream();
 20          FileOutputStream os = new FileOutputStream(filename);
 21
 22          int size = clob.getChunkSize();
 23          byte buffer[] = new byte[size];
 24          int length;
 25
 26          while ((length = is.read(buffer, 0, size)) != -1) {
 27            os.write(buffer, 0, length);
 28          }
 29
 30          is.close();
 31          os.close();
 32          conn.close();
 33    }
 34  }
 35  /

Java created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> show errors
No errors.
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure save_clob(
  2    p_clob      in CLOB,
  3    p_filename  in VARCHAR2)
  4  as language java name 'ClobUtil.save(
  5    oracle.sql.CLOB,
  6    java.lang.String) ';
  7  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2          l_clob clob;
  3  begin
  4          dbms_lob.createtemporary( l_clob, true );
  5          dbms_lob.open( l_clob, dbms_lob.lob_readwrite );
  6          for i in 1 .. 8333
  7          loop
  8                  dbms_lob.writeAppend( l_clob, length('hello world '),
'hello world ' );
  9          end loop;
 10
 11          save_clob( l_clob, '/tmp/foo.dat' );
 12          save_clob( l_clob, '/tmp/tkyte/foo.dat' );
 13  end;
 14  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> !ls -l /tmp/foo.dat /tmp/tkyte/foo.dat
-rw-r--r--   1 ora817      99996 Jun  6 13:09 /tmp/foo.dat
-rw-r--r--   1 ora817      99996 Jun  6 13:09 /tmp/tkyte/foo.dat

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

Review &
      Followup

      Rating: 5
Java = Standard
      Packages Killer ?  June 06, 2002
Reviewer:
      Mauricio de Barros Borges  from Brazil
  Tom,

  I have already had problems with this limitation of UTL_FILE (unique output
directory). We solved it using shell scripts runned as Unix crontab jobs that
distributed the files from this directory to a whole directory tree.

   But, my point here is: you solved the problem easily with Java. It's more
powerfull and extensible: do you (and Oracle) think that Java Stored Procedures
will replace the PL/SQL standard packages and they will be maintained for
"backward compatibility purposes only" in the future ?

   Maurício

          Followup:

You need to read my book.

There are advantages to PLSQL -- do this in java:

begin
   for x in ( select * from emp )    loop
      ..... process record
   end loop;
end;

tell me how much code you have to write?

PLSQL lives
PLSQL is being enhanced (9i R2 has new plsql features)
New server functionality is exposed as PLSQL (dbms_ utl_ packages)

There are times when a little java is good, times when plsql is good.  Me -- I
use a little java to spice up the plsql -- to get it to do things PLSQL as a
language is not adept at.

Java is a non-trivial language, it is huge.  PLSQL is small, simple, yet
surprisingly complete (hey, if they could write advanced replication in Oracle
7.1.6 entirely 100% in plsql !!)

There is room for more then one language in the world.  I use SQL until i cannot
do it in SQL.  Then I use PLSQL.  I use that until I cannot do it with that
language (or it is too awkward) -- then I use java.  When I cannot do it in Java
-- I do it in C (hey, if C is a replacement for Java when Java cannot do it or
cannot do it efficiently -- maybe we should all write in C -- or assembler?)

PLSQL is here to stay, don't worry about that.  No, I do not forsee the plsql
statement packages being rewritten in Java (most of them are written mostly in C
today actually and are just wrappers)

and I should have mentioned...

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96612/u_file.htm#998101
In Oracle9iR2 -- utl_file would be "flexible" enough with directory objects to
do this.  You can add directories on the fly.

<quote>
In the past, accessible directories for the UTL_FILE functions were specified in
the initialization file using the UTL_FILE_DIR parameter. However, UTL_FILE_DIR
access is not recommended. It is recommended that you use the CREATE DIRECTORY
feature, which replaces UTL_FILE_DIR. Directory objects offer more flexibility
and granular control to the UTL_FILE application administrator, can be
maintained dynamically (that is, without shutting down the database), and are
consistent with other Oracle tools. CREATE DIRECTORY privilege is granted only
to SYS and SYSTEM by default.
</quote>

      Rating: 5
Nice
      answer  June 06, 2002
Reviewer:  Mauricio de
      Barros Borges  from Brazil
  First of all, calm down, man, it seems that my question just offended your
belief on PL/SQL: that was not my intention....
  I am a PL/SQL programmer myself and I do like the language very much (already
had to defend PL/SQL solutions over Java solutions): your problem resolution
path (first SQL, then PL/SQL, Java, C...) is what I use as well, always. It's
just that sometimes I saw people getting stuck trying to solve something using
PL/SQL when solving the problem using Java (or C) would be much easier,
cleaner...
  And I asked because I've heard sometime ago that Oracle would deprecate PL/SQL
in favor of Java and I wanted to hear the opinion of who is "inside the corp"...
Thank you, you answer was very useful !

   By the way, I read your entire book already ! Very good one, indeed !

   Thank you !

          Followup:

;)

no worries...

      Rating: 5
But why is the
      Oracle connection is required  January 15, 2003

Reviewer:  A reader  from India
Hi Tom

ClobUtil is not doing any database activity, so why is an Oracle connection
opened?

Thanks

          Followup:

because I cut and pasted the code from a routine that lived outside the
database, where jdbc is brain dead (or optimized for databases that despise
things like concurrency controls, data integrity).

there:

 17        conn.setAutoCommit(false);
 18

is mandatory.  when i plopped it in the database -- instead of removing that, I
grabbed the connection (it is a freebie -- it doesn't cost anything really, it
is already there)

      Rating: 4
Hard coding
      UTL_FILE_DIR  January 21, 2003
Reviewer:
      Sushama Jaiswal  from New York, NY USA
In all the examples that I have seen so far, the UTL_FILE_DIR (example ('d:\tmp'
or '\tmp') is always harcoded in the procedure, or passed as a parameter to the
procedure.

My question is how could I not hard code the directory name in a variable and/or
pass it as parameter. Instead I want the procedure to determine the directory
from v$parameter

But if I include this line in PL/SQL procedure

vDirectory varchar2(50);
select value into vDirectory
from V$PARAMETER
where lower(name) = 'utl_file_dir';

or use NDS for the same, the compiler or run time(with NDS) says
'Table or View does not exist'

I need this so that the procedure is independent of the database setup(whether
it is QA or prod)

          Followup:

Well, you could use Oracle9iR2 which lets you

create or replace directory my_dir as '/tmp';

and then use 'MY_DIR' in UTL_FILE instead of a real path name...

You could put it into your OWN table and fetch it out...

Bear in mind that in general utl_file_dir is a LIST of directories, not a single
entity:

utl_file_dir = /tmp  /tmp/foo   /tmp/bar

so I wouldn't use it (utl_file_dir).  Use your own table.

Read:

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html
to discover they why behind your pls-201 error

      Rating: 4
How do I solve
      this error  January 22, 2003
Reviewer:
      Brijesh
sys@ORA92> ed
Wrote file afiedt.buf

  1  begin
  2  dbms_java.grant_permission ('SCOTT', 'java.io.FilePermission', 'E:\-',
'write');
  3* end;
sys@ORA92> /
begin
*
ERROR at line 1:
ORA-04031: unable to allocate 4032 bytes of shared memory ("shared
pool","JAVA$POLICY$SHARED$0000000dSYS","joxlod: in ehe","ioc_allocate_pal")
ORA-06512: at "SYS.DBMS_JAVA", line 0
ORA-06512: at line 2

sys@ORA92> show parameter shared_pool

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
shared_pool_reserved_size            big integer 1677721
shared_pool_size                     big integer 33554432
sys@ORA92> show parameter java_pool

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
java_pool_size                       big integer 4194304
sys@ORA92> alter system flush shared_pool;

System altered.

sys@ORA92>  begin
  2   dbms_java.grant_permission ('SCOTT', 'java.io.FilePermission', 'E:\-',
'write');
  3   end;
  4  /
 begin
*
ERROR at line 1:
ORA-04031: unable to allocate 4032 bytes of shared memory ("shared
pool","JAVA$POLICY$SHARED$0000000dSYS","joxlod: in ehe","ioc_allocate_pal")
ORA-06512: at "SYS.DBMS_JAVA", line 0
ORA-06512: at line 2

In the error messages doc it is given as

-------------
ORA-04031 unable to allocate string bytes of shared memory
("string","string","string","string")
Cause: More shared memory is needed than was allocated in the shared pool.
Action: If the shared pool is out of memory, either use the DBMS_SHARED_
POOL package to pin large packages, reduce your use of shared memory, or
increase the amount of available shared memory by increasing the value of the
initialization parameters SHARED_POOL_RESERVED_SIZE and SHARED_
POOL_SIZE. If the large pool is out of memory, increase the initialization
parameter LARGE_POOL_SIZE.

------------------

This statement is confusing.

>If the shared pool is out of memory, either use the DBMS_SHARED_
POOL package to pin large packages, reduce your use of shared memory

Does this mean that the more package I ping in my shared_pool the more will be
available memory ?

Thanks Brijesh.

          Followup:

Your shared pool is pretty small -- and your java pool size is far too small to
run anything java.

java shared pool should be 20meg.

      Rating: 4
SPFile and
      utl_file_dir parameter  June 09, 2003
Reviewer:
      fan  from pa
>>Bear in mind that in general utl_file_dir is a LIST of >>directories, not a
single
>>entity:
>>utl_file_dir = /tmp  /tmp/foo   /tmp/bar

Tom, I just installed 9.2 on Linux at home
Now in post-install config stage.
I use dbstart/dbshut. (which docs says read the spfile)
i did:
ALTER SYSTEM SET UTL_FILE_DIR='/tmp'
                 SCOPE=SPFILE;

I want to specify additonal dirs:

Should I do..
ALTER SYSTEM SET UTL_FILE_DIR='/tmp /home/oradev /home/devman /home/oracle'
                 SCOPE=SPFILE;

This correct ? Is this effective immediately ?
Thanks

          Followup:

In 9iR2, use DIRECTORY objects instead of utl_file_dir.  much easier, no reboots
needed.

create directory TMP_DIR as '/tmp';

declare
   l_file utl_file.file_type;
begin
   l_file := utl_file.fopen( 'TMP_DIR', 'foo.dat', 'w' );
   utl_file.put_raw( l_file, utl_raw.cast_to_raw( 'blah blah blah' ) );
   utl_file.fclose( l_file );
end;
/

(uppercase in the 'TMP_DIR' is purposeful and important)....

you can even grant READ or READ WRITE on a directory object -- much better.

      Rating: 4
DIRECTORY
      objects...  June 09, 2003
Reviewer:  fan
       from PA
>>In 9iR2, use DIRECTORY objects instead of utl_file_dir.  >>much easier, no
reboots needed.

That's cool...but our code just lost backward compatibility (to 8i)?

so the ALTER SYSTEM...set UTL_FILE_DIR='/tmp /home/devman...' is good ?

          Followup:

ops$tkyte@ORA920> alter system set utl_file_dir='/tmp','/home/tkyte'
scope=spfile;

System altered.

and then bounce the database (note 'foo','bar' -- not 'foo,bar')

ops$tkyte@ORA920> show parameter utl

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
utl_file_dir                         string      /tmp, /home/tkyte
ops$tkyte@ORA920> set echo on
ops$tkyte@ORA920> @test
ops$tkyte@ORA920> !rm -f /tmp/foo.dat /home/tkyte/foo.dat

ops$tkyte@ORA920>
ops$tkyte@ORA920> !ls -l /tmp/foo.dat /home/tkyte/foo.dat
ls: /tmp/foo.dat: No such file or directory
ls: /home/tkyte/foo.dat: No such file or directory

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2     l_file utl_file.file_type;
  3  begin
  4     l_file := utl_file.fopen( '/tmp', 'foo.dat', 'w' );
  5     utl_file.put_line( l_file, 'hello world' );
  6     utl_file.fclose( l_file );
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> declare
  2     l_file utl_file.file_type;
  3  begin
  4     l_file := utl_file.fopen( '/home/tkyte', 'foo.dat', 'w' );
  5     utl_file.put_line( l_file, 'hello world' );
  6     utl_file.fclose( l_file );
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> !ls -l /tmp/foo.dat /home/tkyte/foo.dat
-rw-rw-r--    1 ora920   ora920         12 Jun  9 13:54 /home/tkyte/foo.dat
-rw-rw-r--    1 ora920   ora920         12 Jun  9 13:54 /tmp/foo.dat

      Rating: 5
And the other
      way?  June 16, 2003
Reviewer:  Steve
       from UK
I have to save files to numerous directories.  Is there a similar technique
available rather than have to declare 55 directories?
Cheers

Steve

          Followup:

you'll need 55 directory objects to use UTL_FILE on 55 directories.

or

you can write a java stored procedure and grant it write access on a "root"
directory and all subdirectories.

      Rating: 5
My
      mistake!  June 16, 2003
Reviewer:  steve
       from UK
I meant to say read in files from 55 directories, but presumably your answer
still applies!

Cheers

Steve

          Followup:

yup

      Rating: 4
GRANT READ ON
      DIRECTORY...  June 16, 2003
Reviewer:  fan
       from PA
>> you can even grant READ or READ WRITE on a directory object -- much better.

Tom, I only did :
grant read on directory 'tmp_dir' to HR ;

But HR can write to that 'tmp_dir'...is  WRITE a default ?

Thanks

          Followup:

No, but if HR created it, HR can write it.  consider:

ops$tkyte@ORA920> create directory tmp_dir as '/tmp/'
  2  /

Directory created.

ops$tkyte@ORA920> grant read on directory tmp_dir to scott;

Grant succeeded.

ops$tkyte@ORA920> select grantee, privilege from
  2  dba_tab_privs where owner = 'SYS' and table_name = 'TMP_DIR';

GRANTEE                        PRIVILEGE
------------------------------ ----------------------------------------
OPS$TKYTE                      READ
OPS$TKYTE                      WRITE
SCOTT                          READ

so, check dba_tab_privs and if you believe something is in error, give us a FULL
test case to work with.

      Rating: 4
9iR2 -- Don't
      seem like there is READ-ONLY Priviledge  June 19, 2003

Reviewer:  Robert  from PA
Hi Tom,
9iR2 --- Don't seem like there is READ-ONLY Priviledge re UTL_FILE
Below is what I did to show that READ priviledge allows
one to WRITE...seems by default.
It also allows deletion.

OTN's example (OBE) also shows similar case...just GRANT READ...
Then that user can write.
-------------------------
select owner, directory_name, trim(directory_path) from all_directories

SYS     MEDIA_DIR      /opt/oracle/product/9.2.0/demo/schema/product_media/
SYS     LOG_FILE_DIR   /opt/oracle/product/9.2.0/demo/schema/log/
SYS     DATA_FILE_DIR  /opt/oracle/product/9.2.0/demo/schema/sales_history/
SYS     TMP_DIR        /tmp
SYS     TMP_TEST       /tmp/test
SYS     MYDIR          /home/rchin
SYS     USER_DIR       /tmp/user

dbarchin@ORA1> create user goober identified by goober ;

User created.

dbarchin@ORA1> grant connect, resource to goober;

Grant succeeded.

dbarchin@ORA1> connect sys as sysdba
Enter password:
Connected.

dbarchin@ORA1> grant read on directory tmp_test to goober;

Grant succeeded.

dbarchin@ORA1> @connect goober/goober

****** WELCOME TO ORACLE 9i Release 2 ******

goober@ORA1> !ls -aFl /tmp/test
drwxrwxrwx    2 rchin    rchin        4096 Jun 15 15:46 ./
drwxrwxrwt   36 root     root         4096 Jun 18 23:40 ../

goober@ORA1> ed
Wrote file afiedt.buf

  1  DECLARE
  2     v_fh    UTL_FILE.fILE_TYPE;
  3     v_buffer  VARCHAR2(4000) := 'Hello Utl_File';
  4  BEGIN
  5     v_fh := UTL_FILE.FOPEN('TMP_TEST', 'user_goober.txt', 'w');
  6     UTL_FILE.PUT_LINE(v_fh, v_buffer);
  7     UTL_FILE.FCLOSE(v_fh);
  8  EXCEPTION
  9     WHEN UTL_FILE.ACCESS_DENIED THEN
 10       DBMS_OUTPUT.PUT_LINE('No Access!!!');
 11     WHEN others THEN
 12       DBMS_OUTPUT.PUT_LINE('SQLERRM: ' || SQLERRM);
 13* END;
goober@ORA1> /

PL/SQL procedure successfully completed.

goober@ORA1> !ls -aFl /tmp/test
total 20
drwxrwxrwx    2 rchin    rchin        4096 Jun 19 00:34 ./
drwxrwxrwt   36 root     root         4096 Jun 18 23:40 ../
-rw-rw-r--    1 oracle   oinstall       15 Jun 19 00:34 user_goober.txt

goober@ORA1> @connect dbarchin
Enter password:

****** WELCOME TO ORACLE 9i Release 2 ******

dbarchin@ORA1> select grantee, privilege from
dba_tab_privs where owner = 'SYS' and table_name = 'TMP_TEST';

GRANTEE                        PRIVILEGE
------------------------------ ----------------------------------------
HR                             READ
RCHIN                          READ
RCHIN                          WRITE
GOOBER                         READ

4 rows selected.

          Followup:

bummer, I see an in progress bug on that.

      Rating: 5
Shared
      drives  August 07, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
I am trying to create directories with of shared drives on my client. I am using
Oracle 9i R2 on wind000. I have mapped drives e.g.

G:\ or \\server\data\
H:\ or \\server2\data\

but when I access these directores no it results nothing. While the same is
working on local drives, C:\ or D:\

I am interested to use network shared drives.

          Followup:

too bad you aren't on unix where this works "as one would expect"

remember - Oracle has to run as a "service" on windows.  When you log in --
those shared drives exist -- but when you friend down the hall logs in -- what
is g: or h:?  windows is not a true multi-user OS in the conventional sense.
What you see is what YOU see, Oracle cannot see it.

search for

utl_file network drive nt

on this site for a paper on how to set up windows to permit Oracle to see a nt
share.

      Rating:
      5
Great  August 10, 2003
Reviewer:
      Sikandar Hayat  from Pindi Bhattian - Pakistan
Thanks TOM I was not giving login/pass in the service. After reading

http://asktom.oracle.com/pls/ask/f?p=4950:8:2197672568838825509
Now I am able to get my results.

      Rating: 3
Correction to
      the xmltofile code  September 29, 2003
Reviewer:
      MSG  from Universal City, CA USA
Tom:

The existing code was writing blank line to the OS file once the CLOB is more
than 30 K, my application DBA and I have corrected the code and would like to
submit it for your review.

Thanks
MSG

Modified Code:

  procedure xmltofile(
  p_xml        in  clob,
  p_path       in  varchar2,
  p_file       in  varchar2,
  p_open_mode  in  varchar2)
  is
  v_amt          constant  pls_integer := 32767;
  v_idx          pls_integer := 1;
  v_file_handle  utl_file.file_type;
  begin
  v_file_handle := utl_file.fopen(p_path, p_file, p_open_mode, v_amt);

  for i in 1..ceil(dbms_lob.getlength(p_xml) / v_amt) loop

    utl_file.put_raw(
      v_file_handle,
      utl_raw.cast_to_raw(dbms_lob.substr(p_xml, v_amt, v_idx)));

    v_idx := v_idx + v_amt;

  end loop;

  utl_file.fclose(v_file_handle);
  exception
  when others then
    utl_file.fclose(v_file_handle);
    raise;
  end xmltofile;

      Rating: 4
WHY DOESN'T
      THIS WORK  October 31, 2003
Reviewer:
      sonali  from waltham , ma
In oracle 9i I suppose one can use directory to write to csv file... All I want
to do is to write to csv file from a table.. here is my code

CONNECT SYSTEM/xxx@xyz;
CREATE DIRECTORY utl_file_logs AS 'e:\dumps\ngitcs';
grant read, write on directory utl_file_logs to ngitcs;

CONNECT NGITCS/NGITCS@xyz;

CREATE OR REPLACE PROCEDURE LOAD_TO_CSV(TNAME VARCHAR2)
IS
v_File UTL_FILE.FILE_TYPE;

BEGIN

v_File := UTL_FILE.FOPEN('utl_file_logs', 'test.txt', 'A', 2000);
UTL_FILE.PUT_LINE(v_File, TNAME);
UTL_FILE.FCLOSE(v_File);
END LOAD_TO_CSV;
/

EXECUTE NGITCS.LOAD_TO_CSV('MWEBORG');

I tried conventional 8i way.. using the procedure example you gave, bothe gave
me the same error, what am I doing wrong ?

I have init.ora set up to that path and also the dba_user in the OS group.
create or replace procedure dump_table_to_csv( p_tname in varchar2,
                                                   p_dir   in varchar2,
                                                   p_filename in varchar2 )
    authid current_user  /* <<<== if you want... runs as "invoker" and runs with
ROLES */
    is
        l_output        utl_file.file_type;
        l_theCursor     integer default dbms_sql.open_cursor;
        l_columnValue   varchar2(4000);
        l_status        integer;
       l_query         varchar2(1000)
                       default 'select * from ' || p_tname;
       l_colCnt        number := 0;
       l_separator     varchar2(1);
       l_descTbl       dbms_sql.desc_tab;
   begin
       l_output := utl_file.fopen( p_dir, p_filename, 'w' , 32000);
       execute immediate
       'alter session set nls_date_format=''dd-mon-yyyy hh24:mi:ss'' ';

       dbms_sql.parse(  l_theCursor,  l_query, dbms_sql.native );
       dbms_sql.describe_columns( l_theCursor, l_colCnt, l_descTbl );

       for i in 1 .. l_colCnt loop
           utl_file.put( l_output,
               l_separator || '"' || l_descTbl(i).col_name || '"' );
           dbms_sql.define_column( l_theCursor, i, l_columnValue, 4000 );
           l_separator := ',';
       end loop;
       utl_file.new_line( l_output );

       l_status := dbms_sql.execute(l_theCursor);

       while ( dbms_sql.fetch_rows(l_theCursor) > 0 ) loop
           l_separator := '';
           for i in 1 .. l_colCnt loop
               dbms_sql.column_value( l_theCursor, i, l_columnValue );
               utl_file.put( l_output, l_separator || l_columnValue );
               l_separator := ',';
           end loop;
           utl_file.new_line( l_output );
       end loop;
       dbms_sql.close_cursor(l_theCursor);
       utl_file.fclose( l_output );

       execute immediate 'alter session set nls_date_format=''dd-MON-yy'' ';
   exception
       when others then
           execute immediate 'alter session set nls_date_format=''dd-MON-yy'' ';
         raise;
 end;
 /

execute dump_table_to_csv( 'NGITCS.mweborg', 'c:\oracle\ora92', 'Sonalitest.dat'
);

          Followup:

why didn't you give us the error code and error message?????

i'll guess.  this code:

v_File := UTL_FILE.FOPEN('utl_file_logs', 'test.txt', 'A', 2000);

is failing.  it is because the directory name is case sensitive and you created
a directory named UTL_FILE_LOGS, not utl_file_logs (identifiers are folded to
uppercase unless you "quote" them)

but, if I guessed wrong, hows about you give us the basic info such as "whats
the error"

      Rating: 4
i forgot put
      the error  October 31, 2003
Reviewer:
      sonali  from waltham, ma
With 9i code
SQLWKS>  execUTE NGITCS.LOAD_TO_CSV('MWEBORG');
ORA-29280: invalid directory path
ORA-06512: at "SYS.UTL_FILE", line 18
ORA-06512: at "SYS.UTL_FILE", line 424
ORA-06512: at "NGITCS.LOAD_TO_CSV", line 7
ORA-06512: at line 2

with 8i code
SQLWKS> execute dump_table_to_csv( 'NGITCS.MWEBORG', 'C:\ORACLE\admin',
'Sonalitest.txt' );
ORA-29280: invalid directory path
ORA-06512: at "NGITCS.DUMP_TABLE_TO_CSV", line 49
ORA-06512: at line 2

          Followup:

in 9i - case issue above

in 8i - since I cannot see your utl_file_dir init.ora parameter, I cannot
comment but I would guess you set it "wrong" or not at all.

      Rating: 5
YOU ARE THE
      BEST  November 03, 2003
Reviewer:  sonali
       from waltham , ma
It worked 9i code, it was the case issue !! I would have never figured that one
out.
In 8i I have the correct init.ora file and have restarted the server.. but as
this worked in 9i code. I will not bother you anymore.
Thanks again

November 01, 2003 Martin -- Thanks for the question regarding
      "Finding NULLs by index", version 8.1.6.1

      You Asked
We had the problem that we were not allowed to specify NOT NULL
on a column but we had to search the NULLs efficiently very
often. They constitute a small sample of the complete data set.
The test case I prepared uses a function-based index on the
function nvl. Perhaps you can use it.

Martin

-- findnulls.sql demonstrates the use of function-based indexes
-- for finding nulls via an index.

-- Martin Haltmayer, 2000/07/15

-- The "nologging" is for speed only.

-- The plan.sql is a script just to display and to empty the
plan_table.

-- Tested on 8.1.6.1 NT 4.0 SP 6 a

-- Please remember to grant query rewrite to the executing user,
to set
-- query_rewrite_enabled = true and query_rewrite_integrity =
trusted
-- in init.ora!

spool findnulls.sql.lst

set timing on

-- To make sure we can drop the table lock it.
lock table test in exclusive mode;
drop table test cascade constraints;

whenever sqlerror exit failure rollback

create table test pctfree 0 nologging as
select decode (mod (rownum, 100), 0, to_number (null), rownum)
as n, a.object_name as c
from all_objects a, all_objects b
where rownum <= 1000000
/

describe test

create index test_i on test (n) pctfree 0 nologging;

analyze table test compute statistics;

explain plan for
select --+ all_rows
count (*) from test where n is null;

@plan.sql

select --+ all_rows
count (*) from test where n is null;

create index test_f on test (nvl (n, - 9999)) pctfree 0
nologging;

analyze table test compute statistics;

select num_rows from user_indexes where index_name = 'TEST_I';
select num_rows from user_indexes where index_name = 'TEST_F';

explain plan for
select --+ all_rows
count (*) from test where nvl (n, - 9999) = - 9999;

@plan.sql

select --+ all_rows
count (*) from test where nvl (n, - 9999) = - 9999;

whenever sqlerror continue

spool off

      and we said...
Thanks Martin, one of the first times I've gotten advice instead
of a question ;)

For more info on function based indexes, see

http://osi.oracle.com/~tkyte/article1/index.html
In the above, you would pick a value (where Martin uses -9999)
that cannot exist in your table naturally (else you will get a
"false positive").

Bitmapped indexes would provide a similar functionality as they
do index NULLS.  For example (apache_log was analyzed
previously):

scott@ORA816.US.ORACLE.COM> select count(*) from apache_log
where host is null;

  COUNT(*)
----------
        10

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Byt
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'APACHE_LOG' (Cost=2 Card=

scott@ORA816.US.ORACLE.COM> create bitmap index apache_idx
on apache_log(host);

Index created.

scott@ORA816.US.ORACLE.COM> select count(*) from apache_log
where host is null;

  COUNT(*)
----------
        10

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes
   1    0   SORT (AGGREGATE)
   2    1     BITMAP CONVERSION (COUNT)
   3    2       BITMAP INDEX (SINGLE VALUE) OF 'APACHE_IDX'

Just be aware of concurrency issues with regards to bitmap
indexes and concurrent updates of the indexed key value.
Review &
      Followup

      Rating: 5
Can you update
      this response for 9.0.1.4.0?  February 16, 2003

Reviewer:  Matt  from Australia
For the belwo can you please make comments specific to 9.0.1.4.0?

I currently have a table with 9 columns, something like:

my_table
(
id NUMBER,
num NUMBER,
day DATE,
count NUMBER,
f_seq NUMBER,
t_seq NUMBER,
accept1_dt DATE,
accept2_dt DATE,
accept3_dt DATE,
)

The table is indexed on (id, num, day, count) as data is typically retrived from
this table using these column values.

A new requirement is to retrieve data based on f_seq and t_seq. Something like:

select id, num, day, count
from my_table
where 1=1
and f_seq > :start_seq
and t_seq is null

Typical data in the table for f_seq and t_seq (where "..." indicates that id,
num, day are the same in each row) are:

... count f_seq t_seq
... ===== ===== =====
        1 999   1001
        2 1001  8888
        3 8888  9982
        4 9982

My question is twofold:

1) Can I set up a functiona based index similar to above on the t_seq column so
that I can easily pull out NULLs?
2) What is the preferred indexing approach:
  - Add the f_seq column and NVL(t_seq,-9999) function to the existing index
(and possibly adversely affect existing queries)
  - Create a new index (id, num, day, count, f_seq, NVL(t_seq,-9999)) to cater
for these new querying requirements. (and possibly adversely affect existing
inserts / updates)
  - Something else that I haven't thought of

Thanks and Regards,

          Followup:

you don't need to.  An index on f_seq, t_seq would accomplish that.

It is that ENTIRELY null entries are not made into a b*tree.  If you have a
concatenated index (on more then one column) and AT LEAST one of the columns is
not null -- that'll find it's way into the index.

Consider:

ops$tkyte@ORA920> create table t ( f_seq int, t_seq int, x char(1) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index t_idx on t(f_seq,t_seq);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace on explain
ops$tkyte@ORA920> select f_seq, t_seq
  2    from t
  3   where f_seq > 0 and t_seq is null;

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE)

ops$tkyte@ORA920>
ops$tkyte@ORA920> select f_seq, t_seq, x
  2    from t
  3   where f_seq > 0 and t_seq is null;

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T'
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE)

ops$tkyte@ORA920> set autotrace off

see how the first query doesn't access the table -- the index is sufficient to
answer the question -- hence it is getting f_seq and t_seq from the index -- the
null entry is indexed in this case (since f_seq is not null)

      Rating:
      4
Idea  February 18, 2003
Reviewer:
      Michael  from Germany
Hi!

To get NULL values indexed i used the following:

create table t as
select object_name, trunc(dbms_random.value(1, 1000)) testcol
from all_objects;

insert into t
select object_name, trunc(dbms_random.value(1, 1000)) testcol
from all_objects;

update t
set testcol = null
where object_name like 'ALL_OB%';
commit;

Now 12 rows (on my db) have an NULL in testcol.

analyze table t compute statistics
for table for all columns for all indexes;

select * from t where testcol is null;

... 12 rows ...

0      SELECT STATEMENT Optimizer=CHOOSE (Cost=32 Card=12 Bytes=312)

1    0   TABLE ACCESS (FULL) OF 'T' (Cost=32 Card=12 Bytes=312)

          5  recursive calls
          0  db block gets
        315  consistent gets
          4  physical reads
          0  redo size
        712  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
         12  rows processed

Now with the index:

create index testidx on t(testcol, 1);
analyze table t compute statistics
for table for all columns for all indexes;

select * from t where testcol is null;

... Again 12 rows ...

0      SELECT STATEMENT Optimizer=CHOOSE (Cost=13 Card=12 Bytes=312)

1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=13 Card=12 Bytes=312)

2    1     INDEX (RANGE SCAN) OF 'TESTIDX' (NON-UNIQUE) (Cost=2 Card=12)

          0  recursive calls
          0  db block gets
          9  consistent gets
          0  physical reads
          0  redo size
        712  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         12  rows processed

Seems to work ...
Tom, are there any disadvantages with this solution?

          Followup:

any time you have a concatenated index (index with more then one key in it) AND
one of the columns in the index is NOT NULL (the constant "1" is not null in
your example) -- then an index entry is made.

The thing is -- an index on an entirely NULL KEY is not made into a b*tree.  You
just have an example where an entry is always made in the b*tree..

it works -- it is just "bigger".  Also -- it is a FUNCTION BASED INDEX so it'll
not work in 8.0 and before:

ops$tkyte@ORA806> create index t_idx on t(x,1);
create index t_idx on t(x,1)
                          *
ERROR at line 1:
ORA-00904: invalid column name

and it requires the CBO (that index is invisible to the RBO) as is the bitmap
though ;)

      Rating: 4
your comment --
      it is just "bigger"  April 22, 2003
Reviewer:
      A reader  from NJ
Hi Tom, This is for 9.2.0...I liked the approach of including a constant when
creating an index on columns that allow null values. But I also agree with your
comment that the index will be bigger. Can you suggest what would be appropriate
for the case below? Let's say I have
create table dev (devid number not null, devvalue varchar2(100), mtime date);
The table has 4.5 million rows - 2.5M are null and 2M are not null. I often
would have to query on mtime as "where mtime is not null". Creating a simple
index on mtime is doing a full table scan. As I undersatnd from this and your
other postings its because null values don't make it to the index. If I create a
composite index on (mtime,1) it will work but as you said it would be large
because null entries will make it in the index. Can you please suggest a better
way (  a function-based index ??) that would help me avoid a full table scan
when I run this query: select devid, devvalue from dev where mtime is not null;
Thanks.

          Followup:

why do you want to avoid an index when 50% or more of the table would be
returned?

a full scan IS APPROPRIATE and faster then an index here.

      Rating: 4
Continuation of
      my previous post  April 22, 2003
Reviewer:
      A reader  from NJ
Tom, this is in continuation of my previous post...
I tried:

create index DUP_DEV_MTIME on DEV( decode(mtime, null, null, mtime));
analyze table dev compute statistics for columns mtime;

But its still doing a full table scan.What did I do wrong?

          Followup:

full scans are not evil
full scans are not evil
full scans are not evil
full scans are not evil
full scans are not evil
full scans are not evil
full scans are not evil
full scans are not evil
full scans are not evil
full scans are not evil
full scans are not evil

just keep saying that over and over and over

full scans are not evil
full scans are not evil
full scans are not evil
full scans are not evil
full scans are not evil
full scans are not evil

      Rating: 5
Pages 303-307
      of your book say it all !!  April 24, 2003

Reviewer:  A reader
Tom, I wish I had read 'Why isn't my indexed being used' section of your book
before asking stupid (make that semi-stupid :) questions. Pages 303-307 really
say a lot, in very simple terms. Something really neat and USEFUL I learned was
your trick about: "alter table .. disable table lock;" Thanks a lot!!

      Rating: 5
is (col,1) a
      fbi  October 31, 2003
Reviewer:  bob
       from PA
"For the optimizer to use function based indexes, the following session or
system variables must be set:
QUERY_REWRITE_ENABLED=TRUE
QUERY_REWRITE_INTEGRITY=TRUSTED"

you mentioned that you cannot do this in 8.1.5 because it is an FBI, which
wasn't available.

tom if index on t(col,1) was an FBI, why does a select count(*) from table where
column is null still continue to use the index if QUERY_REWRITE_ENABLED=FALSE?
(or is false not the correct way to turn it off)

i am using 9.2

          Followup:

it will do a fast full scan just to count rows.  it doesn't even look at the
data, it just knows that any index that is assured to return a NON NULL VALUE
(as t(col,1) would -- it always returns 1) has an entry for each row.

I doesn't need query rewrite to make that leap, it is a "safe" operation.

November 01, 2003 -- Thanks for the question regarding "startup and
      shutdown info", version 9.2.0

      You Asked
How do I find the time at which last time the instance
shutdown and startup? Is there any view that captures the timestamp of when it
occurred? Is there a trigger one can write to capture the info? I am aware that
alert log file contains this info. Thanks.

      and we said...
v$instance has the startup time.

you can have a database event trigger on shutdown to record the shutdown times
if you like.  We do not record that information in the database itself.

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96590/adg14evt.htm#1001932

November 01, 2003 karthik -- Thanks for the question regarding "how
      to access data to and from foxpro and oracle simultaneously", version 9201

      You Asked
how to access data to and fro  from foxpro,oracle,access,... simultaneously,
both in HTML-DB as-well in d2k(forms/reports).

please let me know which will be the feature development-tool.
HTML-DB, Jdeveloper, IDS, ... and why?

please also let me know th essential pkg's that a DBA/developer should Know like
dbms_stat, dbms_pipe, dbms_aq,...

thanks in advance,
Karthik.R

      and we said...
You can use generic connectivity to achieve that:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4406709207206
that'll let you use odbc to foxpro.

As for the essential packages -- no such list really.  You need to be AWARE of
them all, and use in depth the ones that make sense.  DBMS_AQ for example -- do
you have the need for message queuing and processing?  if yes -> dbsm_aq is
really important.  if no -> dbms_aq is something that its nice to know exists
but you don't need to know it in depth.

skim the entire supplied packages guide, realize they are there, and when a need
arises say "ah hah, i remember reading something about that" and go back and get
the details then.
November 01,
      2003 Frank -- Thanks for the question
      regarding "Moving windows around date", version 8.1.7

      You Asked
8.1.7, Solaris 5.8

I have the following construct:

table:

id         number
txn_date   date
rel_id     number
signal     varchar2(1)
amount     number
...

index on id
index on rel_id
index on txn_date
index on signal

75+mln records

all records have <signal is null>, except for new records.
once a week i have to check all new records:

List 7 days period with in it txn_date of new record (signal = 'Y') where total
amount > x

so, if total over (txn_date - 6 days upto txn_date) > x
or total over (txn_date - 5 days upto txn_date + 1)  > x
or ...
or total over(txn_date upto txn_date + 6 days) > x

then
list id
(+ start/end-date of 7 days period if possible)

I started out with analytic functions but only came half-way ;-(

tia,
Frank

      and we said...
something like this should do it -- we compute the various numbers you want and
can then where on them.  I did this for 5 days, you can expand to 7...

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( txn_date date, x int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2      for i in 1 .. 25
  3      loop
  4          insert into t values ( sysdate+i, i );
  5      end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> column x format 99
ops$tkyte@ORA817DEV.US.ORACLE.COM> column sum_5_0 format 999999
ops$tkyte@ORA817DEV.US.ORACLE.COM> column sum_4_1 format 999999
ops$tkyte@ORA817DEV.US.ORACLE.COM> column sum_3_2 format 999999
ops$tkyte@ORA817DEV.US.ORACLE.COM> column sum_2_3 format 999999
ops$tkyte@ORA817DEV.US.ORACLE.COM> column sum_1_4 format 999999
ops$tkyte@ORA817DEV.US.ORACLE.COM> column sum_0_5 format 999999
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select txn_date,
  2
ops$tkyte@ORA817DEV.US.ORACLE.COM> select txn_date,
  2         sum(x) over( order by txn_date desc range 5 preceding ) sum_5_0,
  3         sum(x) over( order by txn_date desc range between 4 preceding and 1
following ) sum_4_1,
  4         sum(x) over( order by txn_date desc range between 3 preceding and 2
following ) sum_3_2,
  5         sum(x) over( order by txn_date desc range between 2 preceding and 3
following ) sum_2_3,
  6         sum(x) over( order by txn_date desc range between 1 preceding and 4
following ) sum_1_4,
  7         sum(x) over( order by txn_date desc range between 0 preceding and 5
following ) sum_0_5,
  8         sum(x) over( order by txn_date desc range between 5 preceding and 5
following ) sum_5_5,
  9         x
 10    from t
 11  /

TXN_DATE  SUM_5_0 SUM_4_1 SUM_3_2 SUM_2_3 SUM_1_4 SUM_0_5    SUM_5_5   X
--------- ------- ------- ------- ------- ------- ------- ---------- ---
14-JAN-02      25      49      72      94     115     135        135  25
13-JAN-02      49      72      94     115     135     129        154  24
12-JAN-02      72      94     115     135     129     123        172  23
11-JAN-02      94     115     135     129     123     117        189  22
10-JAN-02     115     135     129     123     117     111        205  21
09-JAN-02     135     129     123     117     111     105        220  20
08-JAN-02     129     123     117     111     105      99        209  19
07-JAN-02     123     117     111     105      99      93        198  18
06-JAN-02     117     111     105      99      93      87        187  17
05-JAN-02     111     105      99      93      87      81        176  16
04-JAN-02     105      99      93      87      81      75        165  15
03-JAN-02      99      93      87      81      75      69        154  14
02-JAN-02      93      87      81      75      69      63        143  13
01-JAN-02      87      81      75      69      63      57        132  12
31-DEC-01      81      75      69      63      57      51        121  11
30-DEC-01      75      69      63      57      51      45        110  10
29-DEC-01      69      63      57      51      45      39         99   9
28-DEC-01      63      57      51      45      39      33         88   8
27-DEC-01      57      51      45      39      33      27         77   7
26-DEC-01      51      45      39      33      27      21         66   6
25-DEC-01      45      39      33      27      21      15         55   5
24-DEC-01      39      33      27      21      15      10         45   4
23-DEC-01      33      27      21      15      10       6         36   3
22-DEC-01      27      21      15      10       6       3         28   2
21-DEC-01      21      15      10       6       3       1         21   1

25 rows selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2    from (
  3  select txn_date,
  4         sum(x) over( order by txn_date desc range 5 preceding ) sum_5_0,
  5         sum(x) over( order by txn_date desc range between 4 preceding and 1
following ) sum_4_1,
  6         sum(x) over( order by txn_date desc range between 3 preceding and 2
following ) sum_3_2,
  7         sum(x) over( order by txn_date desc range between 2 preceding and 3
following ) sum_2_3,
  8         sum(x) over( order by txn_date desc range between 1 preceding and 4
following ) sum_1_4,
  9         sum(x) over( order by txn_date desc range between 0 preceding and 5
following ) sum_0_5,
 10         sum(x) over( order by txn_date desc range between 5 preceding and 5
following ) sum_5_5,
 11         x
 12    from t
 13   )
 14   where sum_5_0 > 50
 15      or sum_4_1 > 50
 16      or sum_3_2 > 50
 17      or sum_2_3 > 50
 18      or sum_1_4 > 50
 19      or sum_0_5 > 50
 20  /

TXN_DATE  SUM_5_0 SUM_4_1 SUM_3_2 SUM_2_3 SUM_1_4 SUM_0_5    SUM_5_5   X
--------- ------- ------- ------- ------- ------- ------- ---------- ---
14-JAN-02      25      49      72      94     115     135        135  25
13-JAN-02      49      72      94     115     135     129        154  24
12-JAN-02      72      94     115     135     129     123        172  23
11-JAN-02      94     115     135     129     123     117        189  22
10-JAN-02     115     135     129     123     117     111        205  21
09-JAN-02     135     129     123     117     111     105        220  20
08-JAN-02     129     123     117     111     105      99        209  19
07-JAN-02     123     117     111     105      99      93        198  18
06-JAN-02     117     111     105      99      93      87        187  17
05-JAN-02     111     105      99      93      87      81        176  16
04-JAN-02     105      99      93      87      81      75        165  15
03-JAN-02      99      93      87      81      75      69        154  14
02-JAN-02      93      87      81      75      69      63        143  13
01-JAN-02      87      81      75      69      63      57        132  12
31-DEC-01      81      75      69      63      57      51        121  11
30-DEC-01      75      69      63      57      51      45        110  10
29-DEC-01      69      63      57      51      45      39         99   9
28-DEC-01      63      57      51      45      39      33         88   8
27-DEC-01      57      51      45      39      33      27         77   7
26-DEC-01      51      45      39      33      27      21         66   6

20 rows selected.

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

(love those analytic functions....)
Review &
      Followup

      Rating:
      5
  December 20, 2001
Reviewer:
      A reader
Excellent

      Rating: 3
Halfway indeed
      ;-)  December 21, 2001
Reviewer:  Frank
      Tollenaar  from NL
This is exactly how far i came !
The point i miss is the fact that i don't want to check the whole table every
time, but only the 7 days periods around the _new_ records. Problem is, that i
can't add this to the where clause, since the analytic functions also use this
where clause...

          Followup:

Halfway question indeed.  your question really wasn't that clear.  I had to
guess alot.  Now it is more understandable.  I would use a function based index
on

create index t_idx on t(signal,decode(signal,'Y',txn_date,null)) compress 1;

Now, we have an index that only contains values when signal is 'Y' (use whatever
value you use there of course).  Since only totally NON-NULL index entries are
made in b*tree -- we haven't added any leaf rows to this -- just added the
txn_date.  Now, we can easily generate the set of all rows such that they are
within 5 days (N days for you) of the LAST new record.

Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( txn_date date, x int, signal
char(1), data char(2000) default 'x' );

Table created.

data is just there to make the rows big, so the optimizer thinks "big table"

ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2      for i in 1 .. 25
  3      loop
  4          insert into t (txn_date,x,signal) values ( sysdate+i, i, 'Y' );
  5      end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t
(txn_date,x,signal) select sysdate-50, rownum, null from all_objects;

17119 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

added lots of "old data"

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx on
t(signal,decode(signal,'Y',txn_date,null)) compress 1;
Index created.

that leading edge Y is a constant, just get rid of it, store just the txn_date
(compress 1)

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx2 on t(txn_date);
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;
Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze index t_idx validate structure;
Index analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select LF_ROWS from index_stats;

   LF_ROWS
----------
        25

that shows only 25 rows in the index, not 18,000

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set query_rewrite_enabled=true;
Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on
ops$tkyte@ORA817DEV.US.ORACLE.COM> select min(decode(signal,'Y',txn_date,null))
-5 from t where signal = 'Y'
  2  /

MIN(DECOD
---------
17-DEC-01

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=8)
   1    0   SORT (AGGREGATE)
   2    1     FIRST ROW (Cost=1 Card=25 Bytes=200)
   3    2       INDEX (RANGE SCAN (MIN/MAX)) OF 'T_IDX' (NON-UNIQUE) (Cost=1
Card=25)

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

shows we can get the date we are interested in very fast...

ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2    from (
  3  select txn_date,
  4         sum(x) over( order by txn_date desc range 5 preceding ) sum_5_0,
  5         sum(x) over( order by txn_date desc range between 4 preceding and 1
following ) sum_4_1,
  6         sum(x) over( order by txn_date desc range between 3 preceding and 2
following ) sum_3_2,
  7         sum(x) over( order by txn_date desc range between 2 preceding and 3
following ) sum_2_3,
  8         sum(x) over( order by txn_date desc range between 1 preceding and 4
following ) sum_1_4,
  9         sum(x) over( order by txn_date desc range between 0 preceding and 5
following ) sum_0_5,
 10         sum(x) over( order by txn_date desc range between 5 preceding and 5
following ) sum_5_5,
 11         x
 12    from t
 13   where txn_date >= ( select min(decode(signal,'Y',txn_date,null)) -5 from t
where signal = 'Y' )
 14   )
 15   where sum_5_0 > 50
 16      or sum_4_1 > 50
 17      or sum_3_2 > 50
 18      or sum_2_3 > 50
 19      or sum_1_4 > 50
 20      or sum_0_5 > 50
 21  /

that where clause gets all of the rows we are interested in now...

TXN_DATE     SUM_5_0    SUM_4_1    SUM_3_2    SUM_2_3    SUM_1_4    SUM_0_5
SUM_5_5          X
--------- ---------- ---------- ---------- ---------- ---------- ----------
---------- ----------
15-JAN-02         25         49         72         94        115        135
  135         25
14-JAN-02         49         72         94        115        135        129
  154         24
13-JAN-02         72         94        115        135        129        123
  172         23
12-JAN-02         94        115        135        129        123        117
  189         22
11-JAN-02        115        135        129        123        117        111
  205         21
10-JAN-02        135        129        123        117        111        105
  220         20
09-JAN-02        129        123        117        111        105         99
  209         19
08-JAN-02        123        117        111        105         99         93
  198         18
07-JAN-02        117        111        105         99         93         87
  187         17
06-JAN-02        111        105         99         93         87         81
  176         16
05-JAN-02        105         99         93         87         81         75
  165         15
04-JAN-02         99         93         87         81         75         69
  154         14
03-JAN-02         93         87         81         75         69         63
  143         13
02-JAN-02         87         81         75         69         63         57
  132         12
01-JAN-02         81         75         69         63         57         51
  121         11
31-DEC-01         75         69         63         57         51         45
  110         10
30-DEC-01         69         63         57         51         45         39
   99          9
29-DEC-01         63         57         51         45         39         33
   88          8
28-DEC-01         57         51         45         39         33         27
   77          7
27-DEC-01         51         45         39         33         27         21
   66          6

20 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=54 Card=858 Bytes=96954)
   1    0   VIEW (Cost=54 Card=858 Bytes=96954)
   2    1     WINDOW (SORT)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=54 Card=858
Bytes=9438)
   4    3         INDEX (RANGE SCAN) OF 'T_IDX2' (NON-UNIQUE) (Cost=2 Card=858)
   5    4           SORT (AGGREGATE)
   6    5             FIRST ROW (Cost=1 Card=25 Bytes=200)
   7    6               INDEX (RANGE SCAN (MIN/MAX)) OF 'T_IDX' (NON-UNIQUE)
(Cost=1 Card=25)

      Rating: 4
how about days
      of a month which not predictable  October 13, 2002

Reviewer:  A reader
Hi

when we use range preceding we specify the number of days as your example, what
if I want to calculate some values from last month and compare with todays which
can be unpredicatable since number of days of months can differ.

For example today, Sunday 13th of October I billed $10000, I want to calculate
the % increase or decrease from average bill rate of ALL Sundays from September.
There were 5 Sundays (billed $12000, $21000, $9800, $19000 and $13200) last
September and I billed an average of $15000 so today's bill is -33.33% compared
with September's average (Sundays). Last month we had 30 days but we could have
had 28 or 31 days as well if we were in March or September, we also could have
had 4 or 5 Sundays. How can we use analytic functions to calculate these
averages?

Thank you

          Followup:

You would need to use dynamic SQL and build the query on the fly as the NUMBER
OF COLUMNS changes (hence it is a brand new, unique, never before seen query).

      Rating: 3
Additional
      question regarding range  February 13, 2003

Reviewer:  Shetty Yermal  from Pittsburgh, PA USA

Tom,

How can I specify my range of values instead of relative range.

For example can I use the following statement as
"order by txn_date desc range between 5 preceding and 5
following"

"order by txn_date desc range between b.start_date preceding and b.start_date"

where b is a table containing start_date and end_date and can be equijoined with
main table.

          Followup:

when you tried it -- what happened ;)

ops$tkyte@ORA920> create table t
  2  as
  3  select sysdate+rownum dt
  4    from all_users
  5   where rownum <= 10
  6  /

Table created.

ops$tkyte@ORA920> select dt,
  2         first_value(dt) over ( order by dt
  3                                range between 5 preceding
  4                                          and 5 following ) fixed,
  5         first_value(dt) over ( order by dt
  6                                range between minrange preceding
  7                                          and maxrange following ) variable
  8    from t, ( select 5 minrange, 5 maxrange
  9                from dual )
 10   order by dt
 11  /

DT        FIXED     VARIABLE
--------- --------- ---------
14-FEB-03 14-FEB-03 14-FEB-03
15-FEB-03 14-FEB-03 14-FEB-03
16-FEB-03 14-FEB-03 14-FEB-03
17-FEB-03 14-FEB-03 14-FEB-03
18-FEB-03 14-FEB-03 14-FEB-03
19-FEB-03 14-FEB-03 14-FEB-03
20-FEB-03 15-FEB-03 15-FEB-03
21-FEB-03 16-FEB-03 16-FEB-03
22-FEB-03 17-FEB-03 17-FEB-03
23-FEB-03 18-FEB-03 18-FEB-03

10 rows selected.

      Rating: 3
Additional
      question regarding range  February 13, 2003

Reviewer:  Shetty Yermal  from Pittsburgh, PA USA

Tom,

How can I specify my range of values instead of relative range.

For example can I use the following statement as
"order by txn_date desc range between 5 preceding and 5
following"

"order by txn_date desc range between b.start_date preceding and b.start_date"

where b is a table containing start_date and end_date and can be equijoined with
main table.

      Rating: 5
Multiple
      Windowing  April 13, 2003
Reviewer:
      Lakshminarasimhan R  from Dubai
Hi tom

i have a query with respect to the above thread.

I am having a table like this

SALEDATE
OPRCARDSGCOD
OPRCARNUMCOD
CNTCOD
FRMCTYCOD
TOCTYCOD
COTCOD
RBDCOD
NETAMT
CPNCNT

Saledate through Rbdcod columns are Dimensions and
Netamt and Cpncnt are Measures.

My user want to see Moving Annaul Total(MAT) for last
11 months for any of the above combination.

Like They may need to MAT for the combination of
all the dimension or
only for OPrcardsgcod, Cntcod, Frmctycod, toctycod
or only for OPrcardsgcod and cntcod only like this.

I initially thought

creating a MAT like

sum(netamt) over (partition by
Oprcardsgcod,oprcarnumcod,Cntcod,frmctycod,toctycod,cotcod,rbdcod order by
Saledate Range Interval '11' month preceeding) will anyway create MAT for all
the dimension combination and when it will be stored in table permemenantaly we
can just select netamt only for differnt group by..  But i found that approach
is wrong.

HOw to obtaint the above requirement.

One way could be creating different columns with differne partition by
statement.

But it must be too heavy for the users to use so many columns in the report.

can you suggest me some better idea.

          Followup:

no idea what a "MAT" is -- is that like a moving average?

tell you what -- when I'm taking questions -- be more explicit and put this
there, in a new question.

      Rating: 4
how do i do
      this ( oracle 8.1.7 standard edition on Windoze)  May 14,
      2003
Reviewer:  umesh  from bangalore india

I have a table which has the open_date and close_date of orders as follows

create table oc_date
   ( open_date date,
     close_date date);

insert into oc_date values ('01-jan-2000','5-jan-2000');
insert into oc_date values ('02-jan-2000','15-jan-2000');
insert into oc_date values ('03-jan-2000','16-jan-2000');
insert into oc_date values ('05-jan-2000','16-jan-2000');
insert into oc_date values ('12-jan-2000','26-jan-2000');
insert into oc_date values ('13-jan-2000','26-jan-2000');
insert into oc_date values ('15-jan-2000','26-jan-2000');
commit;

i need an output as follows

open_date close_date  cnt (of orders still open )
01-JAN-00 05-JAN-00   1   -> 1 order is open
02-JAN-00 15-JAN-00   2   -> 2 orders open till 02-jan-00
03-JAN-00 16-JAN-00   3   -> 3 ordes open till  03-jan-00
05-JAN-00 16-JAN-00   3  -> 1st order is closed on 5-jun-00
12-JAN-00 26-JAN-00   4
13-JAN-00 26-JAN-00   5
15-JAN-00 26-JAN-00   4 -> 2nd order closed on 15-jun-00
27-JAN-00 28-JAN-00   1 -> all orders closed except
                            present one

I need the number of orders which are still open ( till the open_date) for e.g
consider an open_date 05-jan-00
number of ordes open are 3 becoz the order opened on 1-jan-00 is closed on the
present date of interest which is 05-jan-00
but next record open_date =15-jan-00 has 4 records becoz 4 orders are open
Thanks in advance

          Followup:

I think your example has a "bug",

15-JAN-00 26-JAN-00   4 -> 2nd order closed on 15-jun-00

should have a count of 5, not 4 (it would have 6 if the second order did not
close, 5 since it did).

So, this will do it:

ops$tkyte@ORA920LAP> select open_date, close_date,
  2         (select count(*)
  3                from oc_date t2
  4                   where t2.open_date <= t1.open_date
  5                     and t2.close_date > t1.open_date ) x
  6    from oc_date t1;

OPEN_DATE CLOSE_DAT          X
--------- --------- ----------
01-JAN-00 05-JAN-00          1
02-JAN-00 15-JAN-00          2
03-JAN-00 16-JAN-00          3
05-JAN-00 16-JAN-00          3
12-JAN-00 26-JAN-00          4
13-JAN-00 26-JAN-00          5
15-JAN-00 26-JAN-00          5
27-JAN-00 28-JAN-00          1

8 rows selected.

      Rating: 5
thanks for the
      order dates in the question above  May 15, 2003

Reviewer:  umesh  from bangalore, india
it was really made simple by you Tom, Thanks
I thought this needed analytics and broke my head using analytics

      Rating: 5
current date
      with preceding (are analytics the answer?)  October 31, 2003

Reviewer:  bob  from PA
Tom,

Just like the original thread, what if the counts were all relative to SYSDATE
instead of the most recent row..
---

If a user wanted generate count/sums/avg/etc.. over various windows, with the
window of time being, the last day, month, year, etc.. analytics only fit when
that window is relative to a value in a current row.  right?

so that if I wanted counts over the last month,year, or second, I could only get
them relative to the txn_date for a given row.  If I was interested in many
different intervals relative to SYSDATE, should I just do a bunch of subqueries
each with a SYSDATE-X, or should I union a sysdate row into the tables the
analytics are being used against, so that my counts can be relative to that row?

I wasn't sure if this was something that analytics shouldn't be asked to do, and
if standard approaches using multiple inline views each of which looking at the
same data over different time range were better for.

          Followup:

i'm not sure what you mean?  if they were not relative to the txn_date for a
given row -- what use would sysdate be?  can you provide a small example with
data?

      Rating:
      5
clarification  November 01, 2003
Reviewer:
      bob  from pa
Tom,

I hope this makes more sense.  SQL is probably more universally understable than
my logic, so I will try and use it to more to express my thoughts.I can easily
copy what you did above over various time windows, relative to a particular or
all rows.
If I wanted counts of transactions over the 5,6, and 7 days preceeding every
transaction I could do something like:

select
 count(*) over (order by txn_date range numdstoinverval(5,'days') preceding)
last_5,
 count(*) over (order by txn_date range numdstoinverval(6,'days') preceding)
last_6,
count(*) over (order by txn_date range numdstoinverval(7,'days') preceding)
last_7
from transactions;

but if instead I wanted counts over 5,6,7 days preceding the current day (and
there was not necessarily any transactions today), I don't think I could use
these analytics anymore. is that right?

without analytics I use your standard pivot technique on my dummy summary (see
below) column to pivot a unioned vertical result set into a horizontal that
looks something like:

the pivoted version looks like:

       last 5    last 6     last 7
cat A    10        15         20
cat B     3        24         46

the vertical looks like
 SELECT category,
       count(*) ,
       'last 5' summary -- pivot on this value
FROM table
WHERE date_col > sysdate -5
GROUP BY category
union
SELECT category,
       count(*) ,
       'last 6'
FROM table
WHERE date_col > sysdate -6
GROUP BY category
union
SELECT category,
       count(*) ,
       'last 7'
FROM table
WHERE date_col > sysdate -7
GROUP BY category
)

I thought I might be able to use the first approach using analytics instead if I
did something like this:

select * from (
select
  txn_date,
  count(*) over (order by txn_date range 5....),
  count(*) over (order by txn_date range 6....),
  count(*) over (order by txn_date range 7....)
from (
select tnx_date from table
union
select sysdate from dual
)
)
where txn_date=sysdate

The where clause might not be right idea, but the intention is to only show the
counts of the window relative to the sysdate row only.  (I assume sysdate will
be the same date/time within the same query no matter how many times it is used,
and that may be incorrect)

Is it likely that analytics with a unioned sysdate row is a better approach than
the pivot of the queries unioned together.
I hope this makes more sense.  I look forward to your insight.  I have a
pivoting version working, so I am sure I could experiment with the analytics
version and post my comparison results, but I thought you might have some
insight without actually solving the problem.

Using the union, I can easily see that depending on the time ranges being looked
at, I will likely be querying up and counting the same data many times, where I
think the analytics might do this more efficiently internally.

          Followup:

oh, for that, CASE is infinitely useful

select count( case when DT between sysdate and sysdate-5 then 1 else null end ),
       count( case when DT between sysdate and sysdate-15 then 1 else null end )
       ...

and can even be combined with analytics when necessary.

      Rating: 5
thanks - you're
      great!  November 02, 2003
Reviewer:  bob
       from PA

November 02, 2003 Ed -- Thanks for the question regarding "Advanced
      Queueing Solution", version 9.2

      You Asked
Hi Tom.

I have a question about an Advanced Queueing solution.

Some background:

Our application software helps track ticketing for Orchestras and  Opera
Companies. We track single tickets and packages of tickets.

I created a trigger to populate a package summary table of single ticket
availability. It was created using the "mutating table" workaround described in
many texts.

Our ticket table is hit pretty hard, and this trigger solution is too slow.

------------

So my idea was to have the trigger on the TICKET table populate a queue and then
a process dequeue the queue and populate the summary table. Thus, updating the
TICKET table would be faster (just depositing a limited amount of info to a
queue) and the summary table would be populated asynchronously.

------------

I AM NEW TO AQ, and I don't understand the dequeue process. In my above
scenario, I want a single process to dequeue the queue regulary. So I want this
process scheduled, say every 10 seconds, BUT, while one process is dequeueing, I
don't want another dequeue process to start while the first dequeue process is
running.

Is this idea feasible? How do you build and schedule a dequeue process as I just
described?

Thanks,

Ed Sileo
Atlanta, Georgia.

      and we said...
what about a materialized view that is refreshed periodically.

that is what I would use, not AQ for this.

Look in the SQL reference for CREATE MATERIALIZED VIEW and the data warehousing
guide has a good write up as well.
Review &
      Followup

      Rating: 5
q on chapter 1
      page 26 of your book  June 24, 2003
Reviewer:
      A reader
You suggest AQ as a possible solution for a scenario
where the user needed to run a 45 sec job in an MTS
configuration. The AQ would give an illusion of more
responsive system. Could this have been done by a job
submitted in the database (dbms_job)?
Wonder why you did not suggest that and wonder what I am
missing here:)

Please keep up the great work - love your site!

          Followup:

dbms_job = 10 jobs at a time, on the same machine (in 8i, 1,000 at a time in 9i)

AQ = as many as you like, more scalable solution.  Many more features,
capabilities.

      Rating: 4
Is it possible
      to send message to IM from AQ  June 25, 2003

Reviewer:  Yong  from CA, USA
Tom,

Is it possible to send a message to an instant message( Yahoo IM or AOL IM) from
AQ?

Thanks,

          Followup:

I believer there is a java API (client) for AIM.  If so, yes, you can download
it, write a java stored procedure and call it from a PLSQL routine that is the
"endpoint" of an aq queue.

      Rating:
      1
AQ  June 25, 2003
Reviewer:  aks
       from NY
It is a buggy product. refrain from using it/

          Followup:

spoken like a man who never reads documentation.  Brillant, utterly and totally
brillant.

But yet, you use windows?  hmm.

      Rating: 4
Send a message
      to IM  June 26, 2003
Reviewer:  Yong
       from CA, USA
Tom,

I know Jabber can do this. One can write a java stored procedure and call Jabber
class from PL/SQL. But using jabber involves a lot more. Hopefully Oracle will
have a generic funciton/package to complish this.

Thanks,

      Rating: 5
dbms_job and
      AQ  November 02, 2003
Reviewer:
      A reader
Tom,
Can you please give us some pointers on when to use dbms_job and when to use AQ?

Thank you

          Followup:

well, dbms_job can run what?  plsql -- if the thing that gets the message must
be written in java or c outside the database jobs would not apply.

do you want the processor of the message to be on another machine?  dbms_job
won't apply.

do you want message prioritization, routing, multiple consumers?  dbms_job won't
apply.

It is one of those things that you know you need it when you need it.  when
dbms_job cannot effectively do it or meet your requirements.

one off background call to a stored procedure? dbms_job..
recurring need to run something on a timed basis?  dbms_job...

processing an inbound order entry queue with 1000's of orders arriving?  AQ (for
example)

      Rating: 5
dbms_job and
      AQ  November 02, 2003
Reviewer:
      A reader
Thank you

November 02, 2003 preiyanka -- Thanks for the question regarding
      "Difference between Oracle8 and Oracle8i", version

      You Asked
COULD YOU TELL ME THE EXACT DIFEERENCE BETWEEN ORACLE 8
AND ORACLE8I RELEASE.WHICH i SHOULD PREFER to use and for what
reasons.

      and we said...
see

http://technet.oracle.com/doc/oracle8i_816/server.816/a76962/toc.htm
you need an account on technet but they are free and you can get
one right away.

Oracle8i is simply the next release of the database after
Oracle8 (and before that was Oracle7).  I myself would always
prefer to be using the latest software so the answer to "what
should I prefer" would be Oracle8i, release 2 (version 8.1.6).
That is the latest shipping production version of the database.
If I was starting a new project -- it is what I would use.

Review &
      Followup

      Rating: 1
not at all
      useful  November 02, 2003
Reviewer:  karthik
      anagurti  from INDIA
actually i want to know why the oracle has used the name oracle8i and what r the
new features in oracle 8i

          Followup:

gee, I guess i should have read your mind and answered your question instead of
what the person asked me for huh.  That would be neat wouldn't it.

http://download-west.oracle.com/docs/cd/A87860_01/doc/index.htm
it is a tad late at this point in time (this is an old question) to be looking
at 8i, 9iR2 is the one to be looking at

http://otn.oracle.com/pls/db92/db92.docindex?remark=homepage#index-NEW

      Rating: 3
It's called
      Time-Travel  November 02, 2003
Reviewer:
      Robert  from CT
>>gee, I guess i should have read your mind and answered >>your question instead
of  what the person asked me for huh.

Maybe he IS the original "asker"..but got into some kind of time-warp thing &
changed his name in the meantime....

      Rating: 5
to the TARDIS
      K-9  November 03, 2003
Reviewer:  Kevin
      Meade  from Connecticut USA

November 02, 2003 Tony -- Thanks for the question regarding
      "ANSI/ISO standards?", version 9i

      You Asked
Hi Tom,
Are there any ANSI/ISO standards for SELECTs also? Like, how Oracle deals with
count(*), * etc. in SELECT clause? If yes, is there any plan for Oracle to
support them in near future?
Thanks

      and we said...
there is this thing called ANSI SQL99 (and sql92 and sql89 and sql89 with
referential integrity)

the are the sql standard

Oracle was the first vendor EVER to comply with the ANSI SQL standard way back
in the early 90's...

ANSI SQL99 describes the entire language, selects included.
November 03, 2003 Mangesh -- Thanks for the question regarding
      "Creating test environment from prodution box.", version Oracle 8.05

      You Asked
Hi,
    I am working as Analyst programmer for past 5 years .
 I was always interested in database level jobs rather than
 front-end .
 I plan to take up a DBA job in near future.
 My question is -
    Which is the fastest way to create a test database
 from a production database which is quite big in size (400 GB).
 The easiest way , I know is, export/import. But thsi may not be
 the fastest way.

 I just wish to copy all database objects/structures like tables,indexes etc.,
and not the data.

 Your guidance is highly appreciated.

Regards,
Mangesh

      and we said...
If you just want to create a copy of a schema without the data - then exp with
ROWS=N owner=SOME_USER and imp full=Y will be the fastest way we have -- short
of running the original DDL scripts that is (the imp will take as long to run as
the scripts would have.  The only additional time is the time to export the
schema definitions)
Review & Followup

      Rating: 2
full=y and
      rows=n are conflicting options  January 22, 2002

Reviewer:  Bhavesh Mehta  from USA
full=y and rows=n are conflicting options.
So you have to use rows=n and explcictly specify all
other options like tables, indexes etc.

The error you get is:
EXP-00026: conflicting modes specified
EXP-00000: Export terminated unsuccessfully

Would be nice to make these two option not conflicting.

          Followup:

Nope, you did something else there.  I think you had a "tables=" or "owner="
hiding in that command line as well.  cut and paste an example for us like this:

(i did this in 734, 806, 817, 901 -- all to the same successful effect)

$ exp userid=/ full=y rows=n

Export: Release 8.1.7.2.0 - Production on Tue Jan 22 18:06:10 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.2.0 - Production
With the Partitioning option
JServer Release 8.1.7.2.0 - Production
Export done in US7ASCII character set and WE8ISO8859P1 NCHAR character set
server uses WE8ISO8859P1 character set (possible charset conversion)
Note: table data (rows) will not be exported

About to export the entire database ...
. exporting tablespace definitions
. exporting profiles
. exporting user definitions
. exporting roles

      Rating: 3
Entire box
      copy  January 23, 2002
Reviewer:  nirmal
       from qatar
Well,

Our production database size is 100gb. Now i want to make test environment for
the production server in another server. How can i do this?... hope that
export/import is not the best solution in this case...

Rgds,
Nirmal

          Followup:

If the server is the same environment (hardware/os) just backup production and
restore to test.

great way to test if your dba's know how to do a restore to another machine,
which is what they would have to do if the current machine gets burnt up in a
fire or something.

      Rating: 3
Creating Test
      Databases from Production Databases  January 23, 2002

Reviewer:  Alexandre  from Rio de Janeiro
I think using RMAN is the fastest way to do that...

      Rating: 4
Clone A
      Solution to This Problem  January 23, 2002

Reviewer:  A reader
Hi Tom,

I Think Clone of the 100 GB Database can be considered as a Good and viable
solution to this problem.

Right ???

Regards,
Ganesh R

          Followup:

sure, but a clone is a copy and that's what we are talking about how to do here.

      Rating: 4
Actual
      Cloning.  January 24, 2002
Reviewer:
      A reader
Actually i was trying to tell a way to do it ... Cant u directly copy the DB
Files and creat a clone and not do it via Export and Imports.. Wont that be
faster and more effecient and less amount of work from the DBA.

Regards,
Ganesh R

      Rating:
      3
Reader  January 24, 2002
Reviewer:
      Reader
Cloning REQUIRES same operating system on both
source and target. The originall question does
not mention the OS , hence exp/imp is a
viable generic option in my opinion

      Rating: 5
how to get
      roles etc  September 15, 2002
Reviewer:  T
       from USA
Tom,
Our database is really small....3gigs or so(8.1.7.3.0 in Windows 2000 advanced
server).

We are moving it to UNIX.
There are about 2000 users in the database...
There are 10 schemas (users with some objects)
Database is running Dictionary Managed Tbalespaces (DMT)  and Rule based
optimizer (RBO.
We want to move it to LMT and CBO.
I have created the export with compress=n and direct=y and just the owners that
have schemas...and I will import those..
Now...the other users, all the grants and all the roles...
Can you help me with a script to do those...those have to be precreated before
the import right?
Am I on the right track....with this approach..?

Thanks....

          Followup:

Here is one approach (since unix is involved this'll be easier, better "script"
tools for you there ;)

on windows, do:

c:\> exp userid=/ grants=y indexes=n rows=n constraints=n full=y triggers=n

that should go pretty fast and create a smallish file.  BINARY ftp that to
Unixland and:

$ strings expdat.dmp > test.sql

Now, you have a DDL script file we can edit and clean up.  Using vi, edit the
file and you'll see the first couple of lines are some junk you'll delete.

Then there will be the create tablespace statements -- you can remove them as
well since you are doing them different.

After that, you'll find PROFILES (create profile statements), keep them or
remove them as you desire....

Next, you'll find things like:

ALTER USER "SYS" IDENTIFIED BY VALUES 'D0204756F458CBB9' TEMPORARY TABLESPACE
"TEMPORARY"
ALTER USER "SYSTEM" IDENTIFIED BY VALUES 'A3661D5636A21570' TEMPORARY TABLESPACE
"TEMPORARY"
CREATE USER "OUTLN" IDENTIFIED BY VALUES '4A3BA55E08595C81' TEMPORARY TABLESPACE
"TEMPORARY"
CREATE USER "OPS$ORA817" IDENTIFIED EXTERNALLY DEFAULT TABLESPACE "USERS"
TEMPORARY TABLESPACE "TEMPORARY"
CREATE USER "OPS$CLBECK" IDENTIFIED EXTERNALLY TEMPORARY TABLESPACE "TEMPORARY"
CREATE USER "TMC" IDENTIFIED BY VALUES 'E7ECA5922344E8B2' DEFAULT TABLESPACE
"USERS" TEMPORARY TABLESPACE "TEMPORARY"
CREATE USER "WEB$CLBECK" IDENTIFIED BY VALUES 'A145F35A5B688D72' TEMPORARY
TABLESPACE "TEMPORARY"
.......

These will create your users in the new database (and optionally reset your SYS
and SYSTEM passwords!).  For now, leave them as is (we'll add the "/" in a
moment).  You'll now page down 20,000 lines (over your users...)

After that, you'll find things like:

....
CREATE USER "OPS$TKYTE" IDENTIFIED EXTERNALLY DEFAULT TABLESPACE "USERS"
TEMPORARY TABLESPACE "TEMPORARY"
CREATE ROLE "SELECT_CATALOG_ROLE"
REVOKE "SELECT_CATALOG_ROLE" FROM SYSTEM
CREATE ROLE "EXECUTE_CATALOG_ROLE"
REVOKE "EXECUTE_CATALOG_ROLE" FROM SYSTEM

Here come your roles, edit as you wish, keeping the ones you want and discarding
the rest (eg: your target database probably already has the select catalog role,
no harm in keeping it -- it'll just give you an error when you run it on the
target system that you'll have to ignore)

Little further down, you'll find:

....
CREATE ROLE "TEST_ROLE"
REVOKE "TEST_ROLE" FROM SYSTEM
GRANT UNLIMITED TABLESPACE TO "SYSTEM" WITH ADMIN OPTION
GRANT UNLIMITED TABLESPACE TO "OUTLN"
GRANT EXECUTE ANY PROCEDURE TO "OUTLN"
GRANT CREATE SESSION TO "RECOVERY_CATALOG_OWNER"
GRANT ALTER SESSION TO "RECOVERY_CATALOG_OWNER"
GRANT CREATE TABLE TO "RECOVERY_CATALOG_OWNER"

All of your system grants...

and after the grants:

...
ALTER USER "SYSTEM" DEFAULT ROLE ALL
ALTER USER "OUTLN" DEFAULT ROLE ALL
ALTER USER "OPS$ORA817" DEFAULT ROLE ALL
ALTER USER "OPS$CLBECK" DEFAULT ROLE ALL
ALTER USER "APPS" DEFAULT ROLE ALL
.......

keep those as well.

Later, after the CREATE SEQUENCE section (which you can delete, these should all
be in the user level exports as well, don't need them) you'll find the CREATE
DIRECTORY and CREATE CONTEXT, you'll want to keep them as they will not come
over in the user level exports (they are "global" system objects, not owned by
anyone)

Now, there will be a bunch of other creates -- skip over these till you come to
CREATE PUBLIC SYNONYM.  We'll need to grab many of these.  Skip down in this
section until you stop having synonyms for SYS and SYSTEM objects (they'll
already be there).  If you had java installed, this will be thousands of lines
to skip.  Once you skip them, - keep the ones you want, remove the rest.

Now we are into the create tables and stuff by user, we can tell that from the
output of export:

About to export the entire database ...
. exporting tablespace definitions
. exporting profiles
. exporting user definitions
. exporting roles
. exporting resource costs
. exporting rollback segment definitions
. exporting database links
. exporting sequence numbers
. exporting directory aliases
. exporting context namespaces
. exporting foreign function library names
. exporting object type definitions
. exporting system procedural objects and actions
. exporting pre-schema procedural objects and actions
. exporting cluster definitions
. about to export SYSTEM's tables via Conventional Path ...

We can skip over that and goto the bottom -- there export will have objects
like:

. exporting synonyms
. exporting views
. exporting stored procedures
. exporting operators
. exporting indextypes
. exporting posttables actions
. exporting snapshots
. exporting snapshot logs
. exporting job queues
. exporting refresh groups and children
. exporting dimensions
. exporting post-schema procedural objects and actions
. exporting user history table
. exporting default and system auditing options
. exporting statistics
Export terminated successfully with warnings.

again, edit and keep what you need.

Now you have a script that you need to add "/" to

:1,$s'^V^M'/'

(ctl-v ctl-m) should do that.

      Rating: 5
"script to do
      those" wasn't very clear  September 15, 2002

Reviewer:  t  from usa
A script to create all the existing users, all the roles and all the grants
(grants to schemas).....

      Rating: 5
Now you have a
      script that you need to add "/" to  September 15, 2002

Reviewer:  t  from usa
Tom,
Thanks a bunch...You showed that this can be done without using third part tools
(tools that Reverse engineer roles etc.)
I am learning UNIX commands and core DBA work at the same time....
the :1controlV controlM command that you gave
when I have VI editor open(the file is open in the VI editor)
then I just enter this command and it will put / after all the
statements.....cool unix is cool....

Question:
Can I import users in tablespaces I like...or
just create the tablespaces that will be default in the old databasefor
users...(so that create user statements that is coming in won't fail)
and then change them once they get to UNIX side...to any tablespace I want to...

Thanks...

          Followup:

You can edit any of the statements in there you like to change their default
tablespaces, yes.

      Rating: 5
Substitute
      Pattern Match Failed  September 16, 2002
Reviewer:
      t  from usa
"again, edit and keep what you need.

Now you have a script that you need to add "/" to

:1,$s'^V^M'/'

(ctl-v ctl-m) should do that."

I edited the file in notepad...and then open it through vi..
while it was open....I typed:
again, edit and keep what you need.

Now you have a script that you need to add "/" to

:1,$s'^V^M'/'

I got "Substitute Pattern Match Failed"

          Followup:

notepad -- you let windoze software CORRUPT your file.   sorry for you.  you let
those dos guys ruin the end of line marker.

Try again, don't TOUCH the file with windows stuff.

      Rating: 5
Substitute
      Pattern Match Failed..addendum  September 16, 2002

Reviewer:  t  from USA
I meant, I typed:

:1,$s'^V^M'/'

not

again, edit and keep what you need.

Now you have a script that you need to add "/" to

:1,$s'^V^M'/'

          Followup:

so, I must be missing something cause:

:1,$s'^V^M'/'

and

:1,$s'^V^M'/'

are looking pretty much identical to me.

      Rating:
      4
useful  September 16, 2002
Reviewer:
      Ralph V.  from Philippines
Would you still recommend this approach for bigger databases? Let's say I have a
production DB on NT with several partitioned tables, several tablespaces and
users. Now I want to transfer it to Unix. Editing a long script is somewhat
daunting. I have tried this once when I followed your advice regarding
transfering a multi-tablespace db to a  standard (users-indx-sys-temp-rbs)
database. I must have spent two hours editing that indexfile. :)

I haven't done full database imports yet, just schemas.

Thanks

          Followup:

Well, actually -- if you want my opinion....

You have a system.
You want to move it.

You are saying "i don't have my scripts, I don't have any way to recreate my
schema"

I would say "you need some configuration management in your life".  I cannot
imagine not being able to run scripts in order to recreate my environment.  Oh
well....

Anyway, if you have your own install scripts -- you are done.

If you do not, we'll give you monster script which you can edit.  You can use
other tools (eg: designer pops into mind -- and there are others) to reverse
engineer the data dictionary from and create your schemas as well (and then
you'll even have some configuration management capabilities!)

      Rating:
      4
useful  September 16, 2002
Reviewer:
      Ralph V.  from Philippines
Would you still recommend this approach for bigger databases? Let's say I have a
production DB on NT with several partitioned tables, several tablespaces and
users. Now I want to transfer it to Unix. Editing a long script is somewhat
daunting. I have tried this once when I followed your advice regarding
transfering a multi-tablespace db to a  standard (users-indx-sys-temp-rbs)
database. I must have spent two hours editing that indexfile. :)

I haven't done full database imports yet, just schemas.

Thanks

      Rating: 5
shortcomings of
      'strings'  September 18, 2002
Reviewer:
      Connor  from UK
Most ports of 'strings' have a limit around the 1024 or 2048 character mark at
which point they will silently truncate the rest of the ddl.  Bummer especially
if you've got partitioned tables - long long ddl there.

To avoid this, you're then up against using show=y, but nope, can't use that
because the line breaks all over the place.  So now we're into indexfile hacks,
but suddenly adding a ';' or '/' at the end of each ddl ain't so easy because
its no longer a single line.

All I can say is what a relief it is to have dbms_metadata. :-)

Cheers
Connor

      Rating: 4
Backup/Restore
      to clone database  December 18, 2002
Reviewer:
      Edgar  from Riga, Latvia
Hello Tom,

about your statement "If the server is the same environment (hardware/os) just
backup production and
restore to test." Can I backup (using rman) from production on linux and restore
it on devel. on windows? Or this is not possible and the only way to recreate
production environment is export/import from windows? Thank you!

          Followup:

that is not the same OS, so -- in a word -- no.

      Rating:
      4
  December 18, 2002
Reviewer:
      A reader
Tom,

Can a rman backup done on Solaris Intel restored on Linux Red hat 8.0 and vice
versa..

Thanks

          Followup:

different operating systems -- totally not supported even if it accidently
works.

short answer = no.

      Rating:
      5
  June 25, 2003
Reviewer:  MG
       from Germany
Hi Tom,

I have install the oracle.

Now I want to create the DB from perl. Once I execute "create database...", I
want to detect, whether this command was executed succesfully.

What is the standard way to do this? Currently I check the log files. Is there
any other professional way?

thanks

          Followup:

why you would want to do this from perl is beyond me however, "check the return
code" comes to mind?

      Rating: 5
System
      User  July 01, 2003
Reviewer:  John French
       from Atlanta, GA
Tom,

     How do you make the Full Import from a Full Export not recreate all the
System objects like advanced Queuing etc.

Thanks!!

          Followup:

well, you wouldn't since system is exported as normal.

you usually full import into a minimally configured database.  I never full
import (tad slow on todays sized databases) myself and use user level
exp/imports in parallel personally.

      Rating: 5
imp/
      exp  July 25, 2003
Reviewer:  Junior
       from va, usa
Tom:

1.
So I cannot just simply exp xx.dmp from windows 2000 and imp xx.dmp in UNIX-
AIX?
2.
The import command for UNIX and WINDOWS is same?

Thank for the answer.

          Followup:

sure you can.. dmp files are cross system portable.

      Rating: 5
full
      export/Import  July 28, 2003
Reviewer:  atul
       from India
Hi,

I have to build test database from production.
Test database should contain same users like production and
same table definition without data under same user as it's in production..

For this should i use

1)in production:
exp / full=y rows=n file=proddump.dmp

2)Crate all the users in test

3)Dropping existing table from test.

4)In test
imp / files=proddump.dmp ignore=y

(Should i have to give ignore=Y here)

Are all steps given are OK?

Thanks,
Atul

          Followup:

how about this easier process

a) restore your backup of production

done.

      Rating: 5
Is step 2)
      above : Create all the users in test necessary with Full Export
      ?  July 28, 2003
Reviewer:  pasko

Hi Tom,

Do we have to pre-create Users on Test DB, when we do Full Export/Import ?

          Followup:

no, the import will do that.

      Rating: 5
Restore a
      backup of production if they have different block
      size?  July 29, 2003
Reviewer:  Peter Tran
       from Houston, TX USA
Tom,

Can you restore to another instance if it was configured with a different block
size?

If my 1-terabyte production database was was orginally created with an 8K
blocksize and I took a back up of it, and now I want to restore this database to
a test instance that was configured with a 16K block size.  Is this possible?

Or do I have to go the route of exp/imp.  Your book recommends not to do exp/imp
for anything over 15G.

Thanks,
-Peter

          Followup:

umm, no -- the block size is an attribute of the tablespace/files themselves.

in 9i, you can create a tablespace of 16k blocks -- and then "alter move" "alter
rebuild" the tables/indexes into them.

      Rating:
      5
EXP/IMP  October 23, 2003
Reviewer:
      atul  from India
Hi,

I have 2 bases on same node,i want to exp all the objects
from base 1 to base 2 without data.

But base 2 contains some tables etc..without data,
Should i need to drop them all first??

What is the way in exp/imp

Thanks.
Atul

          Followup:

what is a "base".

i suppose you meant "database".

I cannot answer your question, in fact NO ONE can answer your question -- except
for you.

do you want to drop the tables?  go ahead
do you need to?  no, you can import with ignore=y, it'll ignore errors from
"object already exists" type of failure.

      Rating: 5
How to create
      test schemas for 40 users  November 03, 2003

Reviewer:  Olaf
Tom,

I have to create 40 test schemas in Oracle. My customer always have 40 prod
schemas and now want to have for each prod schema one test (1 to 1 matching the
prod, also 40 prods and 40 tests)
How would you make this. Export/import of 40 schemas seems not to be the simlest
and fastest way to do this.

Thanks in advance

Olaf

          Followup:

why doesn't it?

      Rating:
      5
  November 03, 2003
Reviewer:  Olaf

wiht exp/imp I need 40 times (for each user) create dump file, create user test
and imp it. I thought I can use something like "create as ...", as my data
always in the database why should I export it?
I need to double the schemas in the same database under dif.
name: for user smith create smithtest, and do this for all users

November 03, 2003 play -- Thanks for the question regarding "Case
      and Decode and others", version 8.1.7

      You Asked
Tom

1.I usually run into a situation where I run it queries against huge tables, and
they run for an hour. some times when I want to discontinue the query , the only
way is to kill the sqlplus window, i.e. kill the session.
Is there anything I can do from an another session to kill the query only  in my
other session. Can this be done.

Further, When I kill the query inbetween, how can I determine whether the table
or the rows in the table which I was updating are still locked or not.

Are the rows locked untill, rollback is completed.

2.which oracle manual describes all the data dictionary tables and explains each
column?

3.can we kill a session, when a rollback is going on, if no , why.

4.In what way case is better than decode performance wise, and logic wise.

thank you

      and we said...
1) you can kill sessions.  You cannot kill the query without killing the
session.

You should be able to "ctl-c" from sqlplus (works for me) to kill a long running
query.  If your "query" is really an update -- it can take a long time to
rollback (even longer then it took to do the update) so be patient with the
ctl-c.  Its working.

The rows are locked until your rollback completes.

2) The Oracle Reference manual:

http://technet.oracle.com/doc/oracle8i_816/server.816/a76961/toc.htm
3) sure you can kill a session when its rolling back however since killing a
session INVOLVES rolling back -- don't expect it to go away any faster.

4) You know, performance wise I haven't benchmarked it properly.  CASE seems to
be marginally slower in 8i doing things that DECODE can do (eg:

( case when object_type = 'INDEX' then 1
              when object_type = 'TABLE' then 1
              when object_type = 'PROCEDURE' then 1
              else 0
         end )

is better as:

decode( object_type, 'INDEX', 1, 'TABLE', 1, 'PROCEDURE', 1, 0 )

but in 9i with the searched cased expression:

( case object_type
              when 'INDEX' then 1
              when 'TABLE' then 1
              when 'PROCEDURE' then 1
              else 0
         end )

they are about the same when doing the same things.

Logic wise, it is night and day.  Decode is somewhat obscure -- CASE is very
very clear.  Things that are easy to do in decode are easy to do in CASE, things
that are hard or near impossible to do with decode are easy to do in CASE.
CASE, logic wise, wins hands down.

Review &
      Followup

      Rating:
      5
9i...  August 23, 2001
Reviewer:
      Connor  from UK
Interestingly, somewhere in the 9i doco is that "decode" is deprecated and new
applications should use CASE exclusively.

      Rating:
      5
question  September 18, 2001
Reviewer:
      Akhilesh  from India
If someone asks you a question ' what is the difference between decode and case,
how will you answer it briefly'

          Followup:

decode is the function Oracle originally provided with releases of the database
prior to 8.1.6.  Starting with 816, CASE is the standard way to achieve the same
results - with more meaning (case is easier to read and understand) and is the
recommended function to use.

Everything decode can do, CASE can.
CASE can do many things decode either cannot or you would be hard pressed to
make it do so.

      Rating: 5
USING case to
      select columns for insert  September 24, 2001

Reviewer:  Alex
Tom , Iam doing an insert into  select from

In that I a situation where in

(CASE when p.btd='A' THEN  sum(p.pk_value)
               when (p.btd='B'
               and p.int_cd='SP_OPT') THEN sum(p.gPD_VALUE)
               else 0
               end) GPD
IF P.BTD='A', THEN i HAVE TO SELECT     sum(p.pk_value), and  if p.btd='B' and
p.int_cd='SP_OPT' , i have to select
sum(p.gPD_VALUE)

lets say the whole statement is like this
select p.btd,
(CASE when p.btd='A' THEN  sum(p.pk_value)
               when (p.btd='B'
               and p.int_cd='SP_OPT') THEN sum(p.gPD_VALUE)
               else 0
               end) GPD

from params_table p

Kindly provide me with the solution , I have also tried

sum(CASE when p.btd='A' THEN  p.pk_value
               when (p.btd='B'
               and p.int_cd='SP_OPT') THEN p.gPD_VALUE
               else 0
               end) GPD

          Followup:

Well, it definitely can be done:

  1  select deptno, case when job = 'CLERK' then sum(sal)
  2                      when job = 'ADMIN' then sum(comm)
  3                      else 0
  4                 end sum_something,
  5         job
  6   from emp
  7* group by deptno, job
scott@ORA817DEV.US.ORACLE.COM> /

    DEPTNO SUM_SOMETHING JOB
---------- ------------- ---------
        10          1300 CLERK
        10             0 MANAGER
        10             0 PRESIDENT
        20             0 ANALYST
        20          1900 CLERK
        20             0 MANAGER
        30           950 CLERK
        30             0 MANAGER
        30             0 SALESMAN

9 rows selected.

Since you don't really mention at all what issue you might possibly be running
into.... I cannot rely suggest a solution (my crystal ball is in the shop this
week ;)

      Rating:
      4
  July 29, 2002
Reviewer:
      A reader
Tom, Is there any way to use an ELSE clause in a decode expression like in CASE?

          Followup:

yes,

  select decode( x, 5, 'five', 6, 'six', 'Else I just don''t know!' )
    from t

the last entry is the "else return this"

      Rating: 4
select case not
      working  August 05, 2002
Reviewer:  Ashwani
      Singh  from India
Dear Tom,
      we wanted to user case. But in our case the query does'nt seem to work.
Following quer gives an error while executing.

  1  SELECT distinct I.ISSUE_ID,I.FDDI_NO,I.IP_ADDRESS,C.NAME DEPTT_NAME,
  2  I.ISSUE_DATE,E.EMP_NAME,
  3  T.CATEGORY,T.SOFTWARE_ID,T.INVENTORY_ID,
  4  DECODE(T.CATEGORY,'T',S.SCID,'P',P.PERIPHERAL_NAME,'A',
  5  A.ACCESSORY_NAME,'S',W.SOFTWARE_NAME) NAME,
  6   CASE WHEN T.CATEGORY='A' and t.inventory_id is null THEN 'A'
  7         WHEN T.CATEGORY='A'  and t.inventory_id is not null THEN 'AS' END ,
  8  V.OEM_NO
  9  FROM ISSUE_MAIN I,ISSUE_TRANS T,SYS_COFIG_MAST_MAIN S,
 10  ACCESSORY_NAME_MAST A,PERIPHERAL_MAST P,RECEIVE_DETAIL_OEM v,
 11  SOFTWARE_MAST W,COST_CENTERS_JOIN C,PROF_ILE E,
 12  SYS_INVENTORY u
 13  WHERE
 14  I.ISSUE_ID=T.ISSUE_ID AND I.DEPTT_ID=C.ID AND
 15  T.SOFTWARE_ID=A.ACCESSORY_ID(+) AND
 16  T.SOFTWARE_ID=P.PERIPHERAL_ID(+) AND
 17  T.SOFTWARE_ID=W.SOFTWARE_ID(+) and
 18  I.EMP_CODE=E.EMP_CODE and
 19  v.RECEIVE_ID(+)=u.RECEIVE_ID
 20  and v.NAME_ID(+)=u.NAME_ID
 21  and v.BRAND_ID(+)=u.BRAND_ID
 22  and v.OEM_ID(+)=u.OEM_ID
 23  and u.INVENTORY_ID(+)=t.INVENTORY_ID
 24  and t.SOFTWARE_ID=u.name_id(+)
 25* ORDER BY i.issue_id;
SQL> /
 CASE WHEN T.CATEGORY='A' THEN 'A'
           *
ERROR at line 6:
ORA-00923: FROM keyword not found where expected.

Could you kindly guide us as to how can we use and clause and get the desired
output.
We are using 8.1.5.0.0

Is case statement possible only with Oracle 9i.

regards,
Ashwani

          Followup:

You do not have 816 and up -- that is what is needed for case.

you can simply:

   decode( t.category || '/' || t.inventory_id, 'A/', 'A' 'AS' )

in your instance.

      Rating: 5
Select
      Case  August 06, 2002
Reviewer:  Ashwani
      Singh  from India
Dear Tom,
 We r facing a new Situation now the t.category_id in the following query may
have value 'P' as well since case is not working in 815 there fore we are still
in the soup. Kindly guide us.

SELECT distinct I.ISSUE_ID,I.FDDI_NO,I.IP_ADDRESS,C.NAME DEPTT_NAME,
  2  I.ISSUE_DATE,E.EMP_NAME,
  3  T.CATEGORY,T.SOFTWARE_ID,T.INVENTORY_ID,
  4  DECODE(T.CATEGORY,'T',S.SCID,'P',P.PERIPHERAL_NAME,'A',
  5  A.ACCESSORY_NAME,'S',W.SOFTWARE_NAME) NAME,
  6   CASE WHEN T.CATEGORY='A' and t.inventory_id is null THEN 'A'
  7         WHEN T.CATEGORY='A'  and t.inventory_id is not null THEN 'AS'
            when t.category='P' then 'P' end,
  8  V.OEM_NO
  9  FROM ISSUE_MAIN I,ISSUE_TRANS T,SYS_COFIG_MAST_MAIN S,
 10  ACCESSORY_NAME_MAST A,PERIPHERAL_MAST P,RECEIVE_DETAIL_OEM v,
 11  SOFTWARE_MAST W,COST_CENTERS_JOIN C,PROF_ILE E,
 12  SYS_INVENTORY u
 13  WHERE
 14  I.ISSUE_ID=T.ISSUE_ID AND I.DEPTT_ID=C.ID AND
 15  T.SOFTWARE_ID=A.ACCESSORY_ID(+) AND
 16  T.SOFTWARE_ID=P.PERIPHERAL_ID(+) AND
 17  T.SOFTWARE_ID=W.SOFTWARE_ID(+) and
 18  I.EMP_CODE=E.EMP_CODE and
 19  v.RECEIVE_ID(+)=u.RECEIVE_ID
 20  and v.NAME_ID(+)=u.NAME_ID
 21  and v.BRAND_ID(+)=u.BRAND_ID
 22  and v.OEM_ID(+)=u.OEM_ID
 23  and u.INVENTORY_ID(+)=t.INVENTORY_ID
 24  and t.SOFTWARE_ID=u.name_id(+)
 25* ORDER BY i.issue_id;
SQL> /
 CASE WHEN T.CATEGORY='A' THEN 'A'

Regards and thanx in advance,

Ashwani Singh

          Followup:

decode can do most of the stuff case can do -- especially with equals.  Just
think about what you are asking for and apply DECODE techniques to it.

You are saying:

  decode( category,
          'P', 'P',       -- when p, return p
          decode( category,   -- else, when no p, do this
                  'A', decode(inventory_id, NULL, 'A', 'AS' )
        )

      Rating: 5
decode or case
      for groups of columns  September 09, 2002

Reviewer:  A reader
hi tom

in 8.1.7 can we do decode((column1, column2), ....) ? I tried with case and
decode but no luck

Basically what I am trying to do is

if a = x and b = y and c = z then value

          Followup:

decode( A, x, decode( B, Y, decode( C, Z, VALUE, null ), NULL ), NULL )

says

   if a = z
   then
        if b = y
        then
            if c = z
            then
                value
            else
                null
            end if
         else
            null
    ......

and case would be

select case when (a=x and b=y and c=z) then value else null end,
        .....

      Rating: 4
problems with
      CASE when used inside a procedure  September 11, 2002

Reviewer:  Adrian  from Exeter England
Will CASE work inside a procedure, if so what am I doing wrong in my test case?

Is it a version specific thing?

My test case is below on 8.1.6 database running on win NT

12:12:47 rdb2 on NEIQT> ed
Wrote file afiedt.buf

  1  select accounting_class_id, (case when accounting_class_id = 1 then 'ONE'
  2  when accounting_class_id = 99 then 'NINE NINE'
  3  else 'WOOWOO'
  4  end) test
  5* from accountingclass
12:12:56 rdb2 on NEIQT> /

ACCOUNTING_CLASS_ID TEST
------------------- ---------
                  2 WOOWOO
                  5 WOOWOO
                  4 WOOWOO
                  1 ONE
                  3 WOOWOO
                 99 NINE NINE

6 rows selected.

Elapsed: 00:00:00.50
12:12:57 rdb2 on NEIQT> begin
12:13:15   2  for rec_acc in (
12:13:27   3  select accounting_class_id, (case when accounting_class_id = 1
then 'ONE'
12:13:27   4  when accounting_class_id = 99 then 'NINE NINE'
12:13:27   5  else 'WOOWOO'
12:13:27   6  end) txt
12:13:27   7  from accountingclass) loop
12:13:27   8  dbms_output.put_line(rec_acc.txt);
12:13:27   9  end loop;
12:13:27  10  end;
12:13:27  11  /
select accounting_class_id, (case when accounting_class_id = 1 then 'ONE'
                             *
ERROR at line 3:
ORA-06550: line 3, column 30:
PLS-00103: Encountered the symbol "CASE" when expecting one of the following:
( - + mod not null others <an identifier>
<a double-quoted delimited-identifier> <a bind variable> avg
count current exists max min prior sql stddev sum variance
execute forall time timestamp interval date
<a string literal with character set specification>
<a number> <a single-quoted SQL string>

Elapsed: 00:00:00.11
12:13:30 rdb2 on NEIQT>

          Followup:

read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3027089372477
...

      Rating: 2
How to use case
      in where clause  January 30, 2003
Reviewer:
      A reader
How can case be used in where clause
I mean
select (case when objecy_type = 'XYZ' then 'ABC else 'DEF) test
where test not in select (test from tableb)

This returns an error invalid operation how can this be done using case

          Followup:

either:

select (case when objecy_type = 'XYZ' then 'ABC' else 'DEF') test
where (case when objecy_type = 'XYZ' then 'ABC' else 'DEF')
      not in (select test from tableb)
from t

or

select test
  from ( select (case when objecy_type = 'XYZ' then 'ABC' else 'DEF') test
           from t
       )
 where test not in (select test from tableb)

      Rating: 5
Case vs
      decode  February 03, 2003
Reviewer:  Barry
       from Omaha, Nebraska
Tom:  I tell my clients that CASE is basically a Decode on steroids wearing
spandex.  Steroids because its more powerful than Decode was and Spandex because
CASE is a lot more flexible.  For small things, I still use Decode because it
was a BIG help when it came along.  For situations with lots of possible
"else's" I use CASE.  I also tell clients CASE is like it's own little query in
the select statement.  Thanks for your insightful explanation.

      Rating: 3
is this a cool
      case case?  May 20, 2003
Reviewer:  j.

select count(decode(Grp, 'A', 1)) Count1,
       count(case when Grp = 'A' then 1 end) Count2
from   (
         select 'A' Grp from dual
         union all
         select 'A' Grp from dual
         union all
         select null Grp from dual
       )

or would you call this an undocumented way to count non null entries only?

          Followup:

why not just

select count(*) from (whatever) where grp = 'A';

      Rating:
      3
  May 21, 2003
Reviewer:
      A reader
this "pattern" is more useful if one wants to generate counts for SEVERAL groups
with just ONE select:

select count(case when Grp = 'A' then 1 end) Count1,
       count(case when Val = 1   then 1 end) Count2
from   (
         select 'A' Grp, 1 Val from dual
         union all
         select 'A' Grp, to_number(null) Val from dual
         union all
         select null Grp, 2 Val from dual
       )

but would you RELY on that behaviour? I've never found such kind of "expression"
documented for count ...

          Followup:

count( expression ) returns a count of non-null values.

So, as long as expression returns anything NOT NULL when you want to count
something and returns NULL otherwise, it is valid.

      Rating: 4
One place where
      decode does better then case  May 21, 2003

Reviewer:  Raj  from Los Angeles, CA USA
Hi Tom,

Thanks for the information and making it clear what we should use because DECODE
came first I am use to using it all time but I think it's a time to change to
CASE.

But, one place I see decode doing better is

SQL> select case when null = null
  2              then 1
  3     else 2
  4      end ,
  5         decode(null,null, 1, 2) from dual
  6  /

CASEWHENNULL=NULLTHEN1ELSE2END DECODE(NULL,NULL,1,2)
------------------------------ ---------------------
                             2                     1

SQL>

You can compare nulls in decode for true but not in case.
Which means if two variables are compared in decode for = then it returns true
if both are null. Which is not possible directly in case.

      Rating:
      4
Query  May 30, 2003
Reviewer:
      praveen  from India
Hi Tom,
    How to get the sum if difference between created and last_ddl_time is
greater than 100 grouping by object_type
It should display as

x                 Y
----------------  -------------
PROC                         36
TAB                        9090
                            190

I tried the query below but it gives the error.

  1  select case when object_type in ('PROCEDURE','FUNCTION') then
  2              'PROC'
  3              when object_type in ('TABLE','VIEW') then
  4              'TAB' else null
  5              end x,
  6         sum(case when created-last_ddl_time > 100 then
  7             sum(object_id)
  8              end) y
  9  from t
 10* group by object_type
SQL> /
select case when object_type='PROCEDURE' then
                 *
ERROR at line 1:
ORA-00937: not a single-group group function

Why am I getting this error when I am using group by at the end.

Can you please explain

          Followup:

you have to group by "case when object_type in ......"

but i don't understand the question you are actually trying to answer -- sum(
sum(object_id) )????

      Rating: 4
Reply : One
      place where decode does better then case   May 30, 2003

Reviewer:  Mita  from NJ
You can't do NULL = NULL, do NULL IS NULL And works.

> select case when null is null
  2  then 1
  3  else 2
  4   end ,
  5      decode(null,null, 1, 2) from dual;

CASEWHENNULLISNULLTHEN1ELSE2END DECODE(NULL,NULL,1,2)
------------------------------- ---------------------
                              1                     1

      Rating: 3
CASE and DECODE
      have subtle differences in usage?  August 06, 2003

Reviewer:  Adrian Billington  from UK
Tom

I've identified (i.e. not sure if documented) a small difference in using CASE
and DECODE in a MERGE statement in PL/SQL. The background is :-

1) I want to make the INSERT part of the MERGE keep a counter going ( i.e. to
decompose SQL%ROWCOUNT for auditing ). To do this I need to "piggy-back" an
inserted column and CASE or DECODE calling a counter function is a nice way to
do this.

2) The dummy CASE or DECODE must return the true value to be inserted into the
target column, so whatever is evaluated during the counter function execution,
the return value from CASE or DECODE will be the same.

I have some output, demoing the principle below. What is strange is that when
using CASE, Oracle seems to know that whatever CASE does, I'm going to get the
same value back, so it doesn't bother executing the counter function (some
special optimization maybe ??? ). But when using DECODE, the counter function
gets fired. The only way to make CASE fire the counter function is to make the
WHEN-THEN and the ELSE returns different.

The code below should make this clearer. Any ideas?

920>
920> create package decase as
  2     function set_int return pls_integer;
  3     function get_int return pls_integer;
  4     procedure reset_int;
  5  end;
  6  /

Package created.

920>
920> create package body decase as
  2
  3     n pls_integer not null := 0;
  4
  5  function set_int return pls_integer is
  6  begin
  7     n := n + 1;
  8     return 0;
  9  end set_int;
 10
 11  function get_int return pls_integer is
 12  begin
 13     return n;
 14  end get_int;
 15
 16  procedure reset_int is
 17  begin
 18     n := 0;
 19  end reset_int;
 20
 21  end;
 22  /

Package body created.

922>
920> create table t ( x int, y char(1) );

Table created.

920> insert into t select object_id,'X' from user_objects where rownum <= 5;

5 rows created.

920> commit;

Commit complete.

920>
920> --
920> -- Using CASE to execute the counter function...
920> --
920> merge into t
  2     using ( select object_id as x, 'Y' as y from user_objects where rownum
<= 10 ) s
  3     on ( t.x = s.x )
  4  when matched then
  5     update
  6     set y = s.y
  7  when not matched then
  8     insert ( t.x, t.y )
  9     values ( case decase.set_int when 0 then s.x else s.x end, s.y );

10 rows merged.

920>
920> exec dbms_output.put_line('Integer is at ' || to_char(decase.get_int));
Integer is at 0

PL/SQL procedure successfully completed.

920> exec decase.reset_int;

PL/SQL procedure successfully completed.

920> rollback;

Rollback complete.

920>
920> --
920> -- Using DECODE to execute the counter function...
920> --
920> merge into t
  2     using ( select object_id as x, 'Y' as y from user_objects where rownum
<= 10 ) s
  3     on ( t.x = s.x )
  4  when matched then
  5     update
  6     set y = s.y
  7  when not matched then
  8     insert ( t.x, t.y )
  9     values ( decode( decase.set_int, 0, s.x, s.x ), s.y );

10 rows merged.

920>
920> exec dbms_output.put_line('Integer is at ' || to_char(decase.get_int));
Integer is at 5

PL/SQL procedure successfully completed.

920> exec decase.reset_int;

PL/SQL procedure successfully completed.

920> rollback;

Rollback complete.

920>
920> --
920> -- Making CASE execute the counter function...
920> --
920> merge into t
  2     using ( select object_id as x, 'Y' as y from user_objects where rownum
<= 10 ) s
  3     on ( t.x = s.x )
  4  when matched then
  5     update
  6     set y = s.y
  7  when not matched then
  8     insert ( t.x, t.y )
  9     values ( case decase.set_int when 0 then s.x else null end, s.y );

10 rows merged.

920>
920> exec dbms_output.put_line('Integer is at ' || to_char(decase.get_int));
Integer is at 5

PL/SQL procedure successfully completed.

Thanks
Adrian

          Followup:

the laws of SQL will be against you.  You are attempting to force procedural
nuances on a DECIDELY and purposeful "non procedural" language.

In the next release, there is no reason why the behaviour of the two functions
would be reversed -- or that entier does it -- or that both do.

but decode does short circut.  Look closer at your decode:

decode( decase.set_int, 0, s.x, s.x )

that is the case as:

  case when decase.set_int = 0
       then s.x
       ELSE S.X
   end

      Rating: 4
Killing The
      Session  August 09, 2003
Reviewer:  Bipin
      Ganar  from INDIA
Hi Tom,
I have one procedure ,inserting one table on conditions.But as this is taking so
much time i was not able to trace whether it was working or not.so I kill the
session but as soon as i kill the session the status becomes KILLED
Now my problem , i was not able to work as it says resource is busy. It
continues for the full day.I could work on the table next day.Can u tell me how
i can the session and freed the resources at earliest cost.
Second thing what is criteria for relesing the resources and time for realesing
the resource?

      Rating: 3
Exactly my
      point ...  August 11, 2003
Reviewer:  Adrian
      Billington  from UK
Tom

>> Look closer at your decode:
>>    decode( decase.set_int, 0, s.x, s.x )
>>    that is the case as:
>>       case when decase.set_int = 0
>>           then s.x
>>           ELSE S.X
>>       end

You correctly point out that the DECODE and the CASE I've quoted above are the
same semantically. This is my point. Despite this saying that no matter what the
DECASE.SET_INT function returns you're going to get the S.X column anyway, the
DECODE still executes the function but CASE seems to be a little smarter and
just uses the S.X without bothering itself to evaluate the function call. Is
this a deliberate optimization that made it into the CASE expression - I can
only assume that it is ?

Regards
Adrian
Adrian

          Followup:

ok, i see now, yes they optimized away the constant - since CASE is a language
construct, not a function -- they recognized that

case <CONSTANT>
when <value1> then S.X
else               S.X
end

is the same as S.X

      Rating: 4
Case Statement
      Not working in PL/SQL  August 26, 2003
Reviewer:
      Gaurav Thakur  from India
I have been using your site for lot of references and it had helped me a lot. I
was trying if i could get the result of case statement in the variable or i
could use it in the PL/SQL Block i was not able to do that. Please Help.

I am using oracle database 8.1.7
 DECLARE
   v_val  NUMBER;
 BEGIN
 select case when job = 'CLERK' then sum(sal)
                 when job = 'ADMIN' then sum(comm)
                        else 0
                   end sum_something INTO v_val;
     from scott.emp
    WHERE empno = 7131
    group by deptno, job;
   dbms_output.put_line(v_val);
 END;

Gives The following Error:
ERROR at line 4:
ORA-06550: line 4, column 8:
PLS-00103: Encountered the symbol "CASE" when expecting one of the following:
( * - + all mod null <an identifier>
<a double-quoted delimited-identifier> <a bind variable>
table avg count current distinct max min prior sql stddev sum
unique variance execute the forall time timestamp interval
date <a string literal with character set specification>
<a number> <a single-quoted SQL string>

Thanks and regards
Gaurav Thakur

      Rating: 3
no difference
      between case and decode  September 14, 2003

Reviewer:  dm
  1* select case when null = null then 1 else 2 end from dual
SQL> /

CASEWHENNULL=NULLTHEN1ELSE2END
------------------------------
                             2

SQL> ed
Wrote file afiedt.buf

  1  select case when null = null then 1
  2*             when null is null then 0 else 2 end from dual
SQL> /

CASEWHENNULL=NULLTHEN1WHENNULLISNULLTHEN0ELSE2END
-------------------------------------------------
                                                0

SQL>

to check if something is null it's null is null and
 not null = null.

          Followup:

er?  your point is?

      Rating: 5
Can case do
      this?  September 16, 2003
Reviewer:  Jennifer
      Chen
I wrote a stored procedure that updates a table. If a null is passed in for the
field, then the field is not updated (I set it back to its original value), if a
' ' is passed in, then the field is set to null, if a value is passed in, then
the field is set to the new value. I don't know how to use case for this. Also,
I can use dynamic sql to accomplish the same thing. Would you recommend dynamic
sql or decode (case) from performance perspective.

Your response is greatly appreciated and very helpful.

CREATE OR REPLACE PROCEDURE alias.sp_mod_people_index
(p_MPINumber    IN alias.people_index.mpi_number%TYPE,
 p_EYE        IN alias.people_index.eye%TYPE,
 p_FPC        IN alias.people_index.fpc%TYPE,
 p_HAI        IN alias.people_index.hai%TYPE,
 p_HGT        IN alias.people_index.hgt%TYPE,
 p_SKN        IN alias.people_index.skn%TYPE,
 p_WGT        IN alias.people_index.wgt%TYPE)
/*
Description    : This procedure modifies a record in the PeopleIndex table. It
uses the MPINumber
                : to identify the record and set null to the fields that are '
'.
Input        : MPINumber, SEX, RAC, HGT, WGT, EYE, HAI, SKN, POB, FPC
Output        : None
Author        : Jennifer Chen
Creation Date    : 06/20/2003
*/
AS

BEGIN
  -- check for unallowable nulls.
  IF p_MPINumber IS NULL THEN
    RAISE_APPLICATION_ERROR(-20000,'MPINumber is Required');
  END IF;

  UPDATE alias.people_index
     SET eye = DECODE(p_EYE, ' ', null, null, eye, p_EYE),
         fpc = DECODE(p_FPC, ' ', null, null, fpc, p_FPC),
         hai = DECODE(p_HAI, ' ', null, null, hai, p_HAI),
         hgt = DECODE(p_HGT, ' ', null, null, hgt, p_HGT),
         skn = DECODE(p_SKN, ' ', null, null, skn, p_SKN),
         wgt = DECODE(p_WGT, ' ', null, null, wgt, p_WGT)
   WHERE mpi_number = p_MPINumber;

  COMMIT;

  EXCEPTION
    WHEN NO_DATA_FOUND THEN
      NULL;
    WHEN OTHERS THEN
      IF SQLCODE != 0 THEN
        RAISE_APPLICATION_ERROR(-20001, SQLERRM);
      END IF;

END sp_mod_people_index;
/
SHOW errors

          Followup:

why use case?

1) always use static sql whenever possible

2) use that which works and works well (decode falls into that, you don't have
to run out and convert to decode)

3) TRANSACTIONAL control belongs in the client -- that commit in there scares me
to death!!!

4) no_data_found will never be raised UNLESS You put a trigger on this table and
that trigger does a select into.  In that case -- do you really want to "just
ignore it??????"  (rhetorical question -- answer is of course "no way").  that
NO_DATA_FOUND must be removed, it is a bug

5) why bother with that when others??? all you are doing is hiding the actuall
ORA-xxxxx error message from the client!!!  you make it harder to diagnose
issues.

code should be no more then:

BEGIN
  -- check for unallowable nulls.
  IF p_MPINumber IS NULL THEN
    RAISE_APPLICATION_ERROR(-20000,'MPINumber is Required');
  END IF;

  UPDATE alias.people_index
     SET eye = DECODE(p_EYE, ' ', null, null, eye, p_EYE),
         fpc = DECODE(p_FPC, ' ', null, null, fpc, p_FPC),
         hai = DECODE(p_HAI, ' ', null, null, hai, p_HAI),
         hgt = DECODE(p_HGT, ' ', null, null, hgt, p_HGT),
         skn = DECODE(p_SKN, ' ', null, null, skn, p_SKN),
         wgt = DECODE(p_WGT, ' ', null, null, wgt, p_WGT)
   WHERE mpi_number = p_MPINumber;
end;

period....

      Rating: 5
THANK
      YOU  September 17, 2003
Reviewer:
      A reader
Good Morning, Tom!

Really appreciate your quick response. That helped a lot. The exceptional code
was generated by the Oracle migration tool, which converts PL/SQL code from SQL
Server to Oracle. Of course, the tool is not smart enough to convert the long
dynamic SQL to a couple of lines by using DECODE J…

I have the habit to use commit in the code. In fact, all my developers have the
same habit. I got that from books like ‘Oracle8i Advanced PLSQL Programming’,
‘Oracle8 PLSQL Programming’, etc. It’s really fresh and good to learn something
new.

Thanks again for your time and help.

Jennifer.

      Rating:
      3
OK  September 22, 2003
Reviewer:
      Peter  from Boulder,USA
Dear Sir,
Oracle doc. says coalesce and nullif functions can be used
instead of "case" logic.will that work?Could you please provide an example.
Thanks in advance.

          Followup:

what would be better to say is

"case can perform the same functionality as nullif or coalesce.  However, it is
easier for you to use nullif and coalesce.  Additionally, it is more meaningful
to someone reading your query later as to what your intention was"

nullif and coalesce are not replacements for CASE.

CASE can be a replacement for them.  CASE is more "flexible", generic, not as
specific in goal as they are.

      Rating: 4
Using Case in
      Where Clause  October 02, 2003
Reviewer:
      Sridhar  from USA
Tom,
  I have written something like this in the where clause of my select
statement.after the code review i was asked to simplify this can you please tell
me an alternate way of doing this.I have tried it but could not think of
anything.

AND (eoio.SOT LIKE (case when eoio.OG='C' THEN 'FL%' ELSE '%' END) OR eoio.SOT
LIKE (case when eoio.OG='C' THEN 'PG%' ELSE '%' END))

Thanks
M S Reddy

          Followup:

what a "silly" request.  really.  not from you, from the people who told you to
"simplify it"

is

  where nvl(eoio.OG,'x') <> 'C'
     or ( eoio.og = 'C' and ( eoio.sot like 'FL%' or eoio.sot like 'PG%' ) )

considered "simplier"

      Rating: 4
Group by and
      CASE together  October 02, 2003
Reviewer:
      Sridhar  from USA
Tom,
  Thanks for the Quick reply and i was happy to see your reply in my favour.I
have one more question regarding case and group by together.
consider this example
SELECT  abc.oon
       ,xyz.ion
       ,eic.cid
       ,SUM(NVL(eod.da,0))
       ,(SELECT sum(NVL(pa,0))
           FROM ejp
           WHERE ejp.ion=xyz.ion) tp
       ,CASE WHEN abc.cbo IS NULL THEN
         xyz.son
    ELSE
        (select a.son from a
         where a.bon = abc.cbo)
    END as pson
       ,CASE WHEN abc.cobo IS NULL THEN
         xyz.ion
    ELSE
         (SELECT a.ion FROM a
          WHERE a.cid=xyz.cid
          and a.bon=abc.cbo)
        END as pion
if i include these in the Group by
abc.oon
xyz.ion
eic.cid
abc.cbo
xyz.cid

will that give me correct results or else should i include the whole case
statement in the group by clause.

Thanks for the Help!
M S Reddy.

      Rating: 5
Performace in
      my case  October 17, 2003
Reviewer:  David
      Schwartz  from nj, usa
Can you comment on this query, regarding performace? The tables have about
12,000 rows. Will the case statements with select kill the perf?

Select
    b.Emp_id,
    b.first_name,
(Case
    When ( Select rn.Emp_ID From Roster_Names rn Where rn.Emp_ID = b.Emp_ID ) =
b.Emp_ID
    Then ( Select rn.Last_Name From Roster_Names rn Where rn.Emp_ID = b.Emp_ID )
    Else ( Case
            When ( Select rr.Emp_ID From Roster rr Where rr.Emp_ID = b.Emp_ID )
= b.Emp_ID
            Then ( Select rr.Last_Name From Roster rr Where rr.Emp_ID = b.Emp_ID
)
            Else ( Select bb.Last_Name From Broker bb Where bb.Emp_ID = b.Emp_ID
)
          End
        )
End ) As Last_Name
From broker b

here's the tables...

Create Table Broker (
    Emp_ID Number (6,0) Not Null,
    First_Name varchar(30) Not Null,
      Last_Name varchar(30) Not Null,
      Constraint Broker_PK Primary Key ( Emp_ID ) ) ;

Create Table Roster (
    Emp_ID Number (6,0) Not Null,
    First_Name varchar(30) Not Null,
      Last_Name varchar(30) Not Null,
      Constraint Roster_PK Primary Key ( Emp_ID ) ) ;

Create Table Roster_Names (
    Emp_ID Number (6,0) Not Null,
    First_Name varchar(30) Not Null,
      Last_Name varchar(30) Not Null,
      Constraint Roster_Names_PK Primary Key ( Emp_ID ) ) ;

          Followup:

why not

nvl( (Select rn.Last_Name From Roster_Names rn Where rn.Emp_ID = b.Emp_ID),
     nvl( (Select rr.Last_Name From Roster rr Where rr.Emp_ID = b.Emp_ID),
          (Select bb.Last_Name From Broker bb Where bb.Emp_ID = b.Emp_ID) )

instead?  don't hit the tables twice.

Looking pretty darn like a "data model that is broken" if you want my opinion
tho.  seems to me that first_name, last_name are in the wrong tables here.

      Rating: 5
Broken Data
      Model  October 17, 2003
Reviewer:  David
      Schwartz  from nj, usa
>>Looking pretty darn like a "data model that is broken"

It is!
There are 2 tables that store the names. One deptartment maintains their own
version of half of the employees. Emp_ID is the common column. I want to get the
names from Roster for the half that are there.

Is there a better way?

          Followup:

redesign the schema to correctly hold your data?

      Rating: 5
is this
      possible using case?  November 03, 2003
Reviewer:
      A reader
Hi

I have this result set,

DURATION is in seconds
CURRENT_DAY stores number of seconds elapsed of current date, for example from
start_date to end_date there are 82 seconds and all of them occured in
start_date, in fourth line 89970 seconds elapsed and 82 of them offured in start
date and 89888 occurd in the day after

COD_ACC        START_DATE      DURATION      END_DATE         CURRENT_DAY
NEXT_DAY
-------------- --------------- ------------- ---------------- ------------
-------------
N09381005H0001  20030731235838            82   20030801000000           82
      0
N09381005H0001  20030704161416           218   20030704161754          218
      0
N09381005H0001  20030730235838            82   20030731000000           82
      0
N09381005H0001  20030831235838         89970   20030902005808           82
  89888

I need to calculate the aggreate so the output would be

COD_ACC        END_MONTH    AGG_Month
-------------- ---------- -----------
N09381005H0001     200307         382
N09381005H0001     200308          82
N09381005H0001     200309       89888

can I used case for this? seems impossible :-0

          Followup:

ops$tkyte@ORA920PC> select cod_acc,
  2         decode( gsd, 0, sd, ed ) end_month,
  3         sum(decode( gsd, 0, scd, snd ) ) agg_month
  4    from (select cod_acc,
  5                 sd,
  6                             sum(current_day) scd,
  7                             ed,
  8                             sum(next_day) snd,
  9                 grouping(sd) gsd
 10            from (select cod_acc,
 11                                 trunc(start_date/100000000) sd,
 12                                             current_day,
 13                                             trunc(end_date/100000000) ed,
 14                                             next_day
 15                    from t)
 16           group by cube(cod_acc,sd,ed)
 17          having (grouping(cod_acc)=0 and grouping(sd)=0 and grouping(ed) =
1) or
 18                 (grouping(cod_acc)=0 and grouping(sd)=1 and grouping(ed) =
0)
 19                  )
 20    group by cod_acc, decode(gsd,0,sd,ed)
 21  /

COD_ACC          END_MONTH  AGG_MONTH
--------------- ---------- ----------
N09381005H0001      200307        382
N09381005H0001      200308         82
N09381005H0001      200309      89888

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select cod_acc,
  2         dt,
  3         sum(agg_month) agg_month
  4    from (
  5  select cod_acc,
  6         decode(x,1,trunc(start_date/100000000), trunc(end_date/100000000))
dt,
  7         decode(x,1,current_day,next_day) agg_month
  8    from t, ( select 1 x from dual union all select 2 x from dual )
  9         )
 10   group by cod_acc, dt
 11  /

COD_ACC                 DT  AGG_MONTH
--------------- ---------- ----------
N09381005H0001      200307        382
N09381005H0001      200308         82
N09381005H0001      200309      89888

      Rating:
      5
  January 07, 2004
Reviewer:
      A reader

November 03, 2003 Brett -- Thanks for the question regarding "I
      need to use the LTRIM for Time Function", version 9I

      You Asked
The leading zero on the 09:30 am time is a

I have to use LTRIM to zero suppress.

Here is what I have written so far:

SELECT C_SEC_ID, SEC_NUM, C_SEC_DAY,  LTRIM(TO_CHAR(C_SEC_TIME, 'HH:MI AM'))
  FROM COURSE_SECTION;

I do not want the leading zero in the C_SEC_TIME.  I have tried several
variations of the function and all fail.

Results
 C_SEC_ID    SEC_NUM C_SEC_DAY  LTRIM(TO
--------- ---------- ---------- --------
        1          1 MWF        10:00 AM
        2          2 TR         09:30 AM
        3          3 MWF        08:00 AM
        4          1 TR         11:00 AM

      and we said...
ops$tkyte@ORA920> select to_char( sysdate-.5, 'fmhh:mi am' ) from dual;

TO_CHAR(
--------
4:30 am

ops$tkyte@ORA920>

the fm -- format modifier -- does just that.   your ltrim is not doing anything
-- just to_char the date...
Review &
      Followup

      Rating:
      1
LTRIM  September 27, 2003
Reviewer:
      Brett Rhoades  from Dyer, Indidan
The question was not understood.  I was asking what I needed to do to use the
LTRIM with a time function to remove the leading zero.

          Followup:

trick question then -- you DON'T NEED ltrim!

you use fm

and if you wanted to use ltrim -- well, it would be pretty easy.  You want to
ltrim 0 -- there you go

ltrim( string, '0' )

but don't ltrim, that would be wrong, use fm as shown.

      Rating: 2
LTRIM of time
      function  September 27, 2003
Reviewer:  Brett
      Rhoades  from Dyer, IN
I did try the adding the fm to the time and here is what occurred.
TO_CHAR(C_SEC_TIME, 'fmHH:MI AM') to see what would occur, here is the results:

TO_CHAR(
--------
10:0 AM
9:30 AM
8:0 AM
11:0 AM
2:0 PM
9:0 AM
10:0 AM
8:0 AM
2:0 PM
3:0 PM
8:0 AM

          Followup:

ops$tkyte@ORA920LAP> select to_char( x, 'fmhhfm:mi am' ) from t;

TO_CHAR(
--------
9:00 am

fm on
fm off
fm on
fm off

(fm is documented, its behavior can be read about...)

      Rating: 5
Is this on the
      manual?  September 27, 2003
Reviewer:  Ringo
       from Hong Kong
What a greate tools !

Is it on the oracle manual?

I been writing small routine outside oracle to remove the '0' and a magic word
just did it !

          Followup:

yes, fm would be "documented" in the sql reference manual as all formats all..

      Rating: 5
LTRIM of Time
      Function  September 27, 2003
Reviewer:  Brett
      Rhoades  from Dyer, IN
The FM is great and will be used for other future problems!  But not the
solution I need.  I need to know how to use the LTRIM with a time function so
the result will be from 08:00 AM to 8:00 AM.   Again, I have tried it in several
ways to no avail...  This is what I need.

Thanks.

          Followup:

select to_char( x, 'fmhhfm:mi am' ) from t;

what the heck is wrong with that????????

but, if for some unfathomable reason, you are "outlawed" from doing it right,
you can:

ops$tkyte@ORA920LAP> select to_char( x, 'fmhhfm:mi am' ) from t;

TO_CHAR(
--------
9:00 am

ops$tkyte@ORA920LAP> select ltrim( to_char( x, 'hh:mi am' ),'0') from t;

LTRIM(TO
--------
9:00 am

ltrim is pretty easy to use, you just have to tell it what to ltrim, that's all.

it'll be slower
it'll take long to type
it is up to you

      Rating: 4
LTRIM for Time
      Function  September 27, 2003
Reviewer:  Brett
      Rhoades  from Dyer, IN
Thank you very much.  I tried the code and this is the result:

SQL> SELECT C_SEC_ID, SEC_NUM, C_SEC_DAY, LTRIM( TO_CHAR(C_SEC_TIME, 'HHFM:MI
AM' ), '0')
  2    FROM COURSE_SECTION;
  C_SEC_ID    SEC_NUM C_SEC_DAY  LTRIM(TO
---------- ---------- ---------- --------
         1          1 MWF        10:0 AM
         2          2 TR         9:30 AM
         3          3 MWF        8:0 AM
         4          1 TR         11:0 AM
         5          2 TR         2:0 PM
         6          1 MWF        9:0 AM
         7          2 MWF        10:0 AM
         8          1 TR         8:0 AM
         9          1 MWF        2:0 PM
        10          2 MWF        3:0 PM
        11          1 MTWRF      8:0 AM

I don't mean to be a pain but, if LTRIM strips the zero on the left, why does it
also strip the zero on the right?  How can I fix that using the LTRIM?
Again I appreciate your patience.

      Rating: 5
LTRIM of Time
      Function  September 27, 2003
Reviewer:  Brett
      Rhoades  from Dyer, IN
Thanks.  It works!!

SQL> SELECT C_SEC_ID, SEC_NUM, C_SEC_DAY, LTRIM( TO_CHAR(C_SEC_TIME, 'HH:MI AM'
), 0 )
  2    FROM COURSE_SECTION;

  C_SEC_ID    SEC_NUM C_SEC_DAY  LTRIM(TO
---------- ---------- ---------- --------
         1          1 MWF        10:00 AM
         2          2 TR         9:30 AM
         3          3 MWF        8:00 AM
         4          1 TR         11:00 AM
         5          2 TR         2:00 PM
         6          1 MWF        9:00 AM
         7          2 MWF        10:00 AM
         8          1 TR         8:00 AM
         9          1 MWF        2:00 PM
        10          2 MWF        3:00 PM
        11          1 MTWRF      8:00 AM

      Rating: 2
Homework
      Assignment?  September 28, 2003
Reviewer:
      Jeremy Smith  from Salt Lake City, UT
Given the example data the person asking the question presented, and their
"requirement" that it be using LTRIM, I'm highly suspicious that this was a
class homework assignment.  I don't know how you feel about those...

          Followup:

I find the language differences from country to country add additional meaning.

many people don't have questions, they have "doubts".  doubts being a negative
term in my mind, but not theirs

"reply immd" is used casually, not as a demand even if it sounds like that.

"requirement" is used when "I think i have a need" is more appropriate.

so, I don't assume "homework" all of the time.

I can say, if this was homework and the assignment was "use ltrim to remove a
leading zero", school has become trivial since I was there.  First, ltrim is
very easy to use (fully documented, i was surprised at the question actually,
using ltrim is somewhat "easy" here).  Second, ltrim is the wrong approach to
the question, fm/fm would be the right way to do it.

      Rating: 5
Thanks for
      making me know about this today ! - you have lots of patience too
        September 28, 2003
Reviewer:
      A reader

      Rating: 5
number - number
      returns -0  November 03, 2003
Reviewer:
      A reader
Hi

I am puzzled why I am getting negative zeros in this query

select
dur_sesion_ip,
FEC_INICIO_IP,
dur_sesion_ip -
(trunc(to_date(FEC_INICIO_IP,'YYYYMMDDHH24MISS') + 1) -
to_date(FEC_INICIO_IP,'YYYYMMDDHH24MISS')) * (24*3600) seg_curr_dia
  from bvg_idrs_lsc
 where cod_cuenta != 'N05416776X0001'

DUR_SESION_IP   FEC_INICIO_IP SEG_CURR_DIA
------------- --------------- ------------
         3049  20030704152112       -28079
         1229  20030704151842       -30049
         3340  20030704152434       -27586
           97  20030704162453       -27210
         1495  20030704144655       -31690
          126  20030704153044       -30430
         1980  20030704150000       -30420
          218  20030704161416       -27726
         1229  20030731235942         1211
           82  20030731235838           -0

I dont understand how this is possible.... (I should see 0 instead of -0) May
you give some hints?

          Followup:

  1  select
  2  dur_sesion_ip,
  3  FEC_INICIO_IP,
  4  dur_sesion_ip -
  5  (trunc(to_date(FEC_INICIO_IP,'YYYYMMDDHH24MISS') + 1) -
  6  to_date(FEC_INICIO_IP,'YYYYMMDDHH24MISS')) * (24*3600) seg_curr_dia
  7    from
  8    (
  9  select 82 dur_sesion_ip,  '20030731235838' fec_inicio_ip
 10   from dual
 11*  )
ops$tkyte@ORA920> /

DUR_SESION_IP FEC_INICIO_IP  SEG_CURR_DIA
------------- -------------- ------------
           82 20030731235838   -2.240E-36

its a very small number.  I think you used numformat to control the display:

ops$tkyte@ORA920> set numformat 9999999999999
ops$tkyte@ORA920> /

 DUR_SESION_IP FEC_INICIO_IP    SEG_CURR_DIA
-------------- -------------- --------------
            82 20030731235838             -0

so, it is "normal"

      Rating: 5
I dont
      understand why this data behave this way  November 03, 2003

Reviewer:  A reader
Hi

I dont understand why I get very small number (you are right, I formatted the
column to get -0)

select
dur_sesion_ip,
(trunc(to_date(FEC_INICIO_IP,'YYYYMMDDHH24MISS') + 1) -
to_date(FEC_INICIO_IP,'YYYYMMDDHH24MISS')) * (24*3600) x,
dur_sesion_ip -
(trunc(to_date(FEC_INICIO_IP,'YYYYMMDDHH24MISS') + 1) -
to_date(FEC_INICIO_IP,'YYYYMMDDHH24MISS')) * (24*3600) z
from bvg_idrs_lsc
where cod_cuenta = 'N09381005H0001'

DUR_SESION_IP          X          Z
------------- ---------- ----------
          218      27944     -27726
           82         82 -2.240E-36
        89970         82      89888
           82         82 -2.240E-36

82 - 82 is 0 :-0

          Followup:

it isn't 82.

ops$tkyte@ORA920PC> select dur_sesion_ip,
  2  (trunc(to_date(FEC_INICIO_IP,'YYYYMMDDHH24MISS') + 1) -
  3  to_date(FEC_INICIO_IP,'YYYYMMDDHH24MISS')) * (24*3600) x,
  4  dur_sesion_ip -
  5  (trunc(to_date(FEC_INICIO_IP,'YYYYMMDDHH24MISS') + 1) -
  6  to_date(FEC_INICIO_IP,'YYYYMMDDHH24MISS')) * (24*3600) z
  7  from (select 82 dur_sesion_ip,     '20030731235838' fec_inicio_ip from dual
)
  8  /

DUR_SESION_IP          X          Z
------------- ---------- ----------
           82         82 -2.240E-36

ops$tkyte@ORA920PC> set numformat 999.9999999999999999999999999999999999999999
ops$tkyte@ORA920PC> /

                                DUR_SESION_IP
         X
---------------------------------------------
---------------------------------------------
                                            Z
---------------------------------------------
  82.0000000000000000000000000000000000000000
82.0000000000000000000000000000000000022400
   -.0000000000000000000000000000000000022400

November 03, 2003 Tony -- Thanks for the question regarding "Oracle
      support of ANSI joins.", version 9.2

      You Asked
Hi Tom,
While looking into the documents of 9i, I found that Oracle now supports ANSI
joins. Since Oracle optimizer uses where clause to draw the execution plan (cost
etc.), how it does with new natural joins? Is it just another layer on the top
of existing Oracle's way of joins? I was wondering that if this not the case,
then did Oracle change its Kernel in 9i to accommodate this. I am curious to
know more details about this. Any help in this regard will be highly
appreciated.
Regards,

      and we said...
natural joins -- a bug waiting to happen -- are done exactly the same way as
they were when you specified the join conditions yourself.  There is no
difference there, just the syntax is different.  We look up in both tables for
all of the common column names and build the where clause for you

scott@ORA920> select ename, dname from emp natural join dept;

ENAME      DNAME
---------- --------------
SMITH      RESEARCH
ALLEN      SALES
WARD       SALES
JONES      RESEARCH
MARTIN     SALES
BLAKE      SALES
CLARK      ACCOUNTING
SCOTT      RESEARCH
KING       ACCOUNTING
TURNER     SALES
ADAMS      RESEARCH
JAMES      SALES
FORD       RESEARCH
MILLER     ACCOUNTING

14 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=14 Bytes=252)
   1    0   HASH JOIN (Cost=6 Card=14 Bytes=252)
   2    1     TABLE ACCESS (FULL) OF 'DEPT' (Cost=3 Card=4 Bytes=44)
   3    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=3 Card=14 Bytes=98)

scott@ORA920> select ename, dname from emp, dept
  2  where emp.deptno = dept.deptno;

ENAME      DNAME
---------- --------------
SMITH      RESEARCH
ALLEN      SALES
WARD       SALES
JONES      RESEARCH
MARTIN     SALES
BLAKE      SALES
CLARK      ACCOUNTING
SCOTT      RESEARCH
KING       ACCOUNTING
TURNER     SALES
ADAMS      RESEARCH
JAMES      SALES
FORD       RESEARCH
MILLER     ACCOUNTING

14 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=14 Bytes=252)
   1    0   HASH JOIN (Cost=6 Card=14 Bytes=252)
   2    1     TABLE ACCESS (FULL) OF 'DEPT' (Cost=3 Card=4 Bytes=44)
   3    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=3 Card=14 Bytes=98)

i strongly, vehemently strongly, discourage the use of this "feature" called
natural joins

to join tables by their colum names -- how patently "just a bad idea".  We've
bugs logged saying "hey, this natural join returns the wrong answer" -- but wait
-- they were comparing it to what they THOUGHT should have been the query, not
with what the query really way.  Consider a model like this:

create table emp ( empno, deptno, ename );
create table dept ( deptno, dname, empno /* person responsible for this dept */
);

now, generate a list of enames and the dname they work in using a natural join,
whoops.

Never never never use this "feature"

Review &
      Followup

      Rating: 3
"Oracle support
      of ANSI joins.", version 9.2  November 03, 2003

Reviewer:  pete moss  from uk, staffordshire
      university
Tom - totally agree with you about natural joins, a really stupid idea!

A very important fact to note for anyone who is contemplating using ANSI join
syntax in a new application is to note the fact that forms 9i does NOT support
the new syntax!  We made the assumption that the new release of forms would
support the new features of the database only to discover, to the setback of our
project, that it will not compile ANSI style joins - crazy I know, and we had
trouble getting oracle support to believe us on this - but trust me on this, we
now have a proper bug number and everything ;)

pete :)

      Rating: 3
But
      surely  November 03, 2003
Reviewer:  Fred
      Funk  from UK
"to the setback of our  project"

You would have noticed this the first time you attempted to compile the form !

Forms is always a few steps behind in the versions of PL/SQL - it would be
almost impossible for it to keep up.

      Rating: 3
well,
      yes!  November 03, 2003
Reviewer:  pete moss
       from uk
As you point out, we did spot this straight away - but we had been planning all
along to use ANSI style joins (going so far as getting training for developers
who did not know the ansi syntax!), which meant that we had to start developing
our system knowing that we will, at some point - once it is supported, go back
and retro-code loads of queries into that style, if we want to have all sql
through the system to be consistent.  which is a pain, to say the least!

hey ho.

          Followup:

well, i'm not going back and changing my decodes to case, nor would I ever
rewrite the old style joins to ansi.  but anyway.

in forms however, how often do you really join?  and why isn't this code in
STORED PROCEDURES (if it is doing data stuff)?  forms is pretty much "default
block" stuff or built on stored procedures....

and the difference between:

from emp inner join dept on (emp.deptno = dept.deptno)

vs

from emp, dept where emp.deptno = dept.deptno

hopefully didn't take a fully day or training?

      Rating: 3
Forms SQL and
      PL/SQL  November 03, 2003
Reviewer:  Gary
       from Sydney, Australia
Forms is quite happy with ANSI joins as part of the definition of a block based
on a where clause or a record group.
The issue is with Forms' PL/SQL.
I'm using Forms 6i, which states "This release of Form Builder uses PL/SQL 8 on
the client and the server."

But Oracle version numbering makes it difficult to work out what equates to
what.

Stick to either procedures in the database, or use dynamic SQL in the forms.
That way you can always gain the advantage of migration to later database
versions without forcing a migration of your forms versions.

PS. I also agree that NATURAL joins are a mistake. Just have those often
standard 'CREATED_BY', 'CREATED_ON' columns on your tables and watch the lack of
results.
Maybe we could petition Oracle for a parameter for turning it off.

November 03, 2003 Ravi -- Thanks for the question regarding
      "Library cache latch contention", version 805

      You Asked
Tom,

In my report.txt, week after week, I am seeing the contention
for lib. cache latch statistics.

In lib cache latch statistics, the SQL AREA GETHITRATIO is only
0.172 and PINHITRATIO is only 0.685

In the latch statistics section,
for library cache

Gets 15934992
misses 114277
hit_ratio .993
sleeps 68578
sleeps/misses 0.6

In order to improve this, I increased the shared pool size, set
the shared pool reserved size and pinned the often reloaded
objects. Are these steps ok. What might be the other steps that
I should take to avoid the latch contention on lib. cache.

Thanks
Ravi

      and we said...
Lbrary cache latch contention is typically caused by NOT using
bind variables.  It is due to excessive parsing of statements.

One way to see if this might be the case in your situation is to
run a script like:

create table t1 as select sql_text from v$sqlarea;

alter table t1 add sql_text_wo_constants varchar2(1000);

create or replace function
remove_constants( p_query in varchar2 ) return varchar2
as
    l_query long;
    l_char  varchar2(1);
    l_in_quotes boolean default FALSE;
begin
    for i in 1 .. length( p_query )
    loop
        l_char := substr(p_query,i,1);
        if ( l_char = '''' and l_in_quotes )
        then
            l_in_quotes := FALSE;
        elsif ( l_char = '''' and NOT l_in_quotes )
        then
            l_in_quotes := TRUE;
            l_query := l_query || '''#';
        end if;
        if ( NOT l_in_quotes ) then
            l_query := l_query || l_char;
        end if;
    end loop;
    l_query := translate( l_query, '0123456789', '@@@@@@@@@@' );
    for i in 0 .. 8 loop
        l_query := replace( l_query, lpad('@',10-i,'@'), '@' );
        l_query := replace( l_query, lpad(' ',10-i,' '), ' ' );
    end loop;
    return upper(l_query);
end;
/
update t1 set sql_text_wo_constants =
remove_constants(sql_text);

select sql_text_wo_constants, count(*)
  from t1
 group by sql_text_wo_constants
having count(*) > 100
 order by 2
/

The output of that last query will show you statements that are
identical in the shared pool after all numbers and character
string constants have been removed.  These statements -- and
more importantly their counts -- are the potential bottlenecks.
In addition to causing the contention, they will be HUGE cpu
consumers.

If you discover your applications do not use bind variables --
you must have this corrected.  You'll never have a good hit
ratio if everyone submits "unique" sql.  Your shared pool will
never be used right and you'll be using excessive CPU (90% of
the time it takes to process "insert into t values ( 1 )" is
parsing.  If you use "insert into t values ( :x )", and bind the
value of 1 -- then the next person that runs that insert will
benefit from your work and run that much faster.
Review &
      Followup

      Rating:
      3
  May 02, 2001
Reviewer:  Pedro
      Oliveira  from Lisbon, Portugal

      Rating:
      4
  May 26, 2001
Reviewer:  Sean
       from Atlanta

      Rating: 5
review of
      bstat/estat question  September 07, 2001
Reviewer:
      Wayne  from DFW, Texas
Tom,

  The query you gave in your answer sheds good light on tuning using the
bstat/estat utilities.
Wayne

      Rating: 5
Performance
      stuff  December 11, 2001
Reviewer:  Tania
      Wroe  from South Africa
Excelent

      Rating: 4
anxious to try
      this out  April 30, 2002
Reviewer:
      A reader
I have been trying to understand what is behind my extremely high library cache
hit ratio (99.5%), yet relatively poor gethitratio of 60%.  From what I read no
physical db tuning would improve the gethitratio since there was plenty of free
memory for the shared pool.

      Rating: 4
Error while
      processing   May 24, 2002
Reviewer:  Raman
      Pujani  from India
Hi Tom,

Most of the time when I run remove_constant and the query to generate report, I
get :

update t1 set sql_text_wo_constants = remove_constants(sql_text)
*
ERROR at line 1:
ORA-03113: end-of-file on communication channel

What could be the reason ?

Regards,
Raman.

          Followup:

Please contact support -- it will be a bug, you may require a patch.  I've never
seen that behaviour myself.

There will be a trace file on the server they will need to diagnose this issue.

meanwhile, you can do something like:

declare
  l_tmp long;
begin
  for x in ( select rowid rid, sql_text from t1 )
  loop
     l_tmp := remove_constants( x.sql_text );
     update t1 set sql_text_wo_constants = l_tmp where rowid = x.rid;
   end loop;
end;
/

instead of the single update.

      Rating: 4
Can you please
      explain ?  June 21, 2002
Reviewer:
      A reader
Hi tom ,

 the solution/work around, you have given above, does it
use the bind variables ?

  beacause I don't know yet, after all your explanation in all questions and
your book
the difference between the cursor variable and bind variable

 I think can we use the bind variable in the following way
will the performance better ?

/********************************************/
declare
  l_tmp long;
begin
  open  x for
   'select rowid rid, sql_text from t1' --returns 10000 rows
  loop
     l_tmp := remove_constants( x.sql_text );
      execute immediate
    'update t1 set sql_text_wo_constants=:a where rowid  :y'
     using remove_constants( x.sql_text ),x.rid
   end loop;
end;
/
/*********************************************/

 could you please explain. Tankans..

          Followup:

NO -- that would be a terribly slow, bad way to do it.

It violated my rule #1

a) if you can do it in single SQL statment -- DO IT.

All of the code above makes 100% use of bind variables where appropriate.  When
considering "have I done this bind variable thing right" you only need look at
the sql statement:

update t1 set sql_text_wo_constants = remove_constants(sql_text);

and then ask "will I ever submit a statement that looks ALMOST exactly like that
except that some constant has changed?"

The answer in this case is "definitely NOT, there aren't even any constants in
there".

When you see queries like:

update t set x = 5 where y = 2;
update t set x = 6 where y = 1;
update t set x = 8 where y = 43;

Those statements demand bind variables.  That should be a single statment:

update t set x = :x where y = :y;

that is executed with different inputs.  As my update:

update t1 set sql_text_wo_constants = remove_constants(sql_text);

has no constants -- there are thusly NO binds to be done.

      Rating: 5
Tom, that is
      excellent!  June 21, 2002
Reviewer:  Sanjay
       from Canada
Could you clarify further as how to rad the output from query to make out
whether bind variables are being used or not?
SQL> /

Function created.

SQL>  update t1 set sql_text_wo_constants = remove_constants(sql_text);

276 rows updated.

SQL> select sql_text_wo_constants, count(*)  from t1 group by
sql_text_wo_constants
  2  having count(*) > 100 order by 2;

no rows selected

SQL> /

no rows selected

SQL> 2
  2* having count(*) > 100 order by 2
SQL> c/100/5
  2* having count(*) > 5 order by 2
SQL> /

SQL_TEXT_WO_CONSTANTS
--------------------------------------------------------------------------------
  COUNT(*)
----------
SELECT OBJECT_NAME FROM ALL_OBJECTS WHERE OBJECT_ID = @
        92

What does @ mean here?

          Followup:

That means you have 92 copies of the statement in the shared pool and the ONLY
difference between them is the number in the object_id =

My remove constants turns any character string literal into '#' and any number
into @.  It then counts how many statements are in the shared pool that are the
same EXCEPT for the constants.

If you

select sql_text from v$sql where upper(sql_text) like 'SELECT OBJECT_NAME FROM
ALL_OBJECTS WHERE OBJECT_ID%'

you'll see what i mean. You have someone NOT using bind variables in all
likelyhood.

      Rating:
      5
ORA-03113  June 21, 2002
Reviewer:
      Harrison Picot  from Haymarket, VA USA
    ORA-03113: end-of-file on communication channel

I am sorry that I can't remember the context, but I have
seen that message in relation to some kind of a space
problem.  It wasn't an Oracle bug it was my bug. I can't
remember more about it than that, but I remember thinking
that it was "obvious" after I found it.  Sorry this is so
vague, but had I called Oracle support, I'm not sure that
message would trigger an answer to the problem I had. At
the time I thought my database had been corrupted by
lightening, and restored from backups, but when I got to
same point again, I got the same error and that was when
the light went on.  Anyway, this can just be a warning that
ORA 03113 is not a clear message, so checking your logs
(as Tom suggests) will worth a lot more than that error.

      Rating: 5
Can you explain
      this ?  June 24, 2002
Reviewer:
      A reader
Hi tom,

 Thanks, for your answer.
I would like to still ask you one question on this bind variable thing.

 Now as you've explained me
1.) if I can do it in one stmt. I should do it.

Ok, my qustion is in your followup answer

/***************************************************/
Followup:
Please contact support -- it will be a bug, you may require a patch.  I've never

seen that behaviour myself.

There will be a trace file on the server they will need to diagnose this issue.

meanwhile, you can do something like:

declare
  l_tmp long;
begin
  for x in ( select rowid rid, sql_text from t1 )
  loop
     l_tmp := remove_constants( x.sql_text );
     update t1 set sql_text_wo_constants = l_tmp where rowid = x.rid;
   end loop;
end;
/

instead of the single update.

/********************************************************/

 don't  we have the same situation where
we are saying

 update t set x =  l_tmp where y = x.rid

isn't it same as

 update t set x = 4  where y = 5

 so won't the sql stmt will distinct at run time
it be executed the # of rows time ?
are we using bind varible ? if yes how ?
if not, then in this case, will it be better to use bind
using the execute immedieate ?

 Plese explain..
thanks in advence

          Followup:

No, it is not.  I am using host variables.  The statement:

update t1 set sql_text_wo_constants = l_tmp where rowid = x.rid;

is really:

pdate t1 set sql_text_wo_constants = :BV1 where rowid = :BV2;

anywhere there is a PLSQL variable -- it is really a host variable, a bind
variable.  That is part of the beauty of PLSQL -- it makes binding almost MOOT.
You have to absolutely go out of your way to NOT bind in plsql.

      Rating: 5
Thanks
      Tom  June 24, 2002
Reviewer:  A reader

Hi Tom,

 Thanks, for clearing this up for me.
 Now I feel comfortable with this bind variable and cursor variables, as you've
acknoledged that pl/sql cursor variable/ plsql variable is it self a bind/host
variable.

  I will further investigate and explore this becacuse I know I have seen some
illustration where in the pl/sql block
where we can use the pl/sql varable, dynamic stmt is used.

 I think from my point of view almost all statement can be written using pl/sql
variable ( DDL stmt excluded). but I will investigate further ...

thanks..

      Rating: 4
Regarding
      ORA-03113  June 24, 2002
Reviewer:  Colin
      Davies  from Pasadena, CA, USA
I just ran into this over the weekend while building a new instance on a new
server.  It was apparently the result of not having enough semaphores.

When I reduced the PROCESSES parameter in the init.ora file to 50, the error
went away; when I raised it, it came back.  We adjusted the kernel parameters in
the /etc/system file, rebooted the server, and everything was fine again.

          Followup:

Well, let me say something about the 3113 error.

It means "something went wrong, we died".

It can be caused by literally thousands and thousands of things.  It is a very
generic error message.

A crash (segmentation fault) in the server process could cause it.
Not enough semaphores can cause it (but only on startup, not later).
Network errors can cause it.
and so on.

There is no single answer to "what causes a 3113".

If you look up support note  <Note:19790.1>  on metalink, you'll see:

Explanation:
        There are numerous causes of this error. It is a 'catch all' type
        error which means 'I cannot communicate with the oracle shadow process'.
        This usually occurs when the Oracle server process has died for
        some reason.

Diagnosis:
        Oracle support need more information to determine what caused the
        error. The most useful items are:

        1) Does this occur when trying to (a) make a connection to the
           database or (b) on an established connection ?

        2) There should be a trace file from the failed session in
           USER_DUMP_DEST

        3) Does a particular statement reproduce this problem or is it
           random ? Use SQL_TRACE on the session to find the problem
           statement.

      Rating: 5
More on ORA
      error  June 25, 2002
Reviewer:  Jose Fco.
      Cleto  from Dominican Republic
I also saw this error once, one of my developers was trying to insert a record
and have a before insert/update tirgger with a :OLD.Field1 var the is updating
part.

create or replace trigger biur_t before insert or update on t for each row
begin
  ...
  if inserting then
    ...
     :OLD.Field1 := sysdate;
  else
    ...
end;

Guess what??? it looks just normal and compile as well but every time they try
to insert a record the same messages POP.

We search the web, we look some books and open the traces files and every log
and trying to find every string with the offending table or user and That's all
folks, we fix it and it's gone away.

      Rating: 5
More on ORA
      error  June 25, 2002
Reviewer:  Jose Fco.
      Cleto  from Dominican Republic
I also saw this error once, one of my developers was trying to insert a record
and have a before insert/update tirgger with a :OLD.Field1 var the is updating
part.

create or replace trigger biur_t before insert or update on t for each row
begin
  ...
  if inserting then
    ...
     :OLD.Field1 := sysdate;
  else
    ...
end;

Guess what??? it looks just normal and compile as well but every time they try
to insert a record the same messages POP.

We search the web, we look some books and open the traces files and every log
and trying to find every string with the offending table or user and That's all
folks, we fix it and it's gone away.

      Rating: 4
Determine who
      submitted the bad SQL?  August 21, 2002
Reviewer:
      Eric Raskin  from Pleasantville, NY USA
I've run your script on my database and found a few statements that are
candidates for bind variables.  We have a bunch of Oracle Forms applications
running, so I'm not sure which one is executing each statement.  How do I
determine who submitted the offending SQL?

          Followup:

Well, the shared pool by its very definition is "application agnostic" - it is
after all the "shared pool"

It could be that EVERY application issues the offending sql.
It could be that 15 applications do
Or just one.

If you have the FMT files (text files representing the forms) you could "grep"
them looking for references to the tables in question.

If you see the sql running (using showsql.sql available on this site via search
for example), you'll be able to see who is doing it.

      Rating: 4
OEM Is not
      using Bind variables.  August 22, 2002
Reviewer:
      A reader
Hi Tom,

Your remove_constants logic is very useful for me.

I was testing your remove_constants function in one of our devlopment
environments and found one Intresting point.

/* ORACLEOEM */ SELECT MIN_VALUE, MAX_VALUE, INCREMENT_BY, LAST_NUMBER, CYCLE_FL
AG, ORDER_FLAG, CACHE_SIZE FROM SYS.DBA_SEQUENCES WHERE SEQUENCE_OWNER = ('#') A
ND SEQUENCE_NAME = ('#')

SQL_TEXT_WO_CONSTANTS
--------------------------------------------------------------------------------
  COUNT(*)
----------
       113

/* ORACLEOEM */ SELECT TEXT FROM DBA_SOURCE WHERE OWNER = '#' AND NAME = '#' AND
 TYPE = '#' ORDER BY LINE
       567

/* ORACLEOEM */ SELECT INITCAP(STATUS) FROM DBA_OBJECTS WHERE OWNER = '#' AND OB
JECT_NAME = '#' AND OBJECT_TYPE = '#'
       590

So OEM is not using your rule of thumb(it is not using bind variables).

My doubt is why can't OEM use bind variables?

          Followup:

can't is the wrong word.

didn't is the correct word.

It would be an OEM bug.  In fact, #1504636 is filed about this issue.  Not
everyone has read my book yet ;)  Working on that....

      Rating: 5
Outstanding
      tuning tool!  August 22, 2002
Reviewer:
      Jimko  from Rochester, NY
Using this great technique, I found and fixed three minor transgressions in my
code that were wasting cache space. However the vast majority of SQL that was
duplicated in the cache was generated by Oracle Sales Analyzer or Oracle
Discoverer. Don't you have any in-house readers, Tom? ;-)

I can understand why OSA might not be able to use bind variables because of its
dynamic nature, but Discoverer builds fairly straight forward queries against
static tables. Are there any techniques we can use to shield the database from
abuse by query generation tools?

By coincidence, I had read you book up through page 448 last night. I
experienced some deja vu when I resumed my reading this evening on page 449
where you cover this very function.

Your book is excellent. I don't know why more technical documentation isn't
written in the 1st person. It's the natural way to communicate.

          Followup:

Actually, in a data warehouse (such as you would be using these tools) -- not
using Binds is ok.

You need binds when you are parsing X statements / second.

When are you parsing 1 statement every Y seconds, it is not nearly as important.

OLTP = mandatory binds
Mixed Used = mostly 99% binds
DW = binds if you want to (i cannot believe i said that!)

The plans for the dataware house will be better if you use constants/literals in
general.  You do not have the scalability wall since you are doing queries that
take a long time (and hence you are not parsing x queries per second, the
latching doesn't hit you).  Also, the usage of the shared pool is "gentler" so
the space problem is not bad at all -- we have time to manage the shared pool,
we aren't in a rush as much.

      Rating: 5
RE: DW = binds
      if you want to...  August 23, 2002
Reviewer:
      Mark A. Williams  from Indianapolis, IN USA
DW = binds if you want to (i cannot believe i said that!)

Are you sure you don't have a fever?  Body snatched?  Did it feel weird to say
it?

Just kidding, of course...

- Mark

      Rating: 5
Bind
      Variables  June 02, 2003
Reviewer:  vinodhps
       from India,Chennai
Hi Tom ,
Function was pretty useful.. i corrected those queires which was found ........

oiisdba@OIIS8.WORLD> update t1 set sql_text_wo_constants =
remove_constants(sql_text);

12867 rows updated.

oiisdba@OIIS8.WORLD> select sql_text_wo_constants, count(*)
  2    from t1
  3   group by sql_text_wo_constants
  4  having count(*) > 100
  5   order by 2
  6  /

SQL_TEXT_WO_CONSTANTS
---------------------------------------------------------------------------------
-----------------
  COUNT(*)
----------
SELECT * FROM T_FAS_SALE_AWA WHERE DOC_NO=@
       115

SELECT
ROWID,REAM_ID,PALLET_CODE,BATCH_NO,PART_KEY,QLTY_DEST,USERID,DATESTAMP,PRINT_CNT,
RG_VARIETY
D,SMR_FLAG,REAM_STATUS,DEFECT_CD,REAM_WT FROM T_PMS_REAM_DTL WHERE PART_KEY = @
AND REAM_ID > '#'
D REAM_ID < '#' AND (PALLET_CODE=:@) AND (BATCH_NO=:@) ORDER BY REAM_ID
       489

Thanks...

      Rating: 4
Very
      useful  June 05, 2003
Reviewer:
      A reader

      Rating: 5
how can pl/sql
      code lead to lack of usage of bind variables   June 10, 2003

Reviewer:  A reader  from ca
"anywhere there is a PLSQL variable -- it is really a host variable, a bind
variable.  That is part of the beauty of PLSQL -- it makes binding almost MOOT.

You have to absolutely go out of your way to NOT bind in plsql.
"

well, if you do a simple select as below in pl/sql
wont it violate the always-use-bind-variable rule
(due to the hard coded constant 'FGL'?
....
select a
into b
from t
where t.x = 'FGL';
....

Thanx!

          Followup:

that is properly bound.

Tell me -- if you run that procedure over and over and over -- how many
different SQL statements will you have?

One.

If on the other hand, you code:

   open ref_cursor for 'select * from t where t.x = ' || some_variable;

the answer to that question will be:

infinitely many.

That is why you need to use binds in dynammic sql:

  open ref_cursor for 'select * from t where t.x = :x' USING some_variable;

but in static sql in plsql -- you need not.  By definition, in static sql, the
values will NEVER change and you need bind only when the values CHANGE

      Rating: 5
thanx for a
      clear explanation!  June 10, 2003
Reviewer:
      A reader  from ca
now I understand - in pl/sql if static sql then anyways
the query does not change -
if not then you have to use variables which are
bound - so is there anyway that you could be violating
the "always-bind" rule in pl/sql?

Thanx!

      Rating: 5
oops! got
      it!  June 10, 2003
Reviewer:  A reader
       from ca
i guess using the dynamic sql you showed is the
way you would violate the "always-gind" rule..'thanx!:)

      Rating: 5
static sql in
      pl/sql  June 10, 2003
Reviewer:
      A reader  from ca
Tom
You said,
" but in static sql in plsql -- you need not.  By definition, in static sql, the

values will NEVER change and you need bind only when the values CHANGE ".

Does this argue for encapsulating all select statements in functions (That could
be reused later?)  this would ensure that there are no two select stmts that are
the same with different values within pl/sql packages?
e.g.
instead of having
select x
from w
where v = &#8216;F&#8217;

at one place
and
select x from w where v = &#8216;G&#8217;

at another you make a function of that select statement and use it.

Do you see any pros/cons of this approach?

Thanx for your wonderful site!:)

          Followup:

that would be taking it to an extreme perhaps.  not that I'm 100% down on it --
i just see it heading towards "no sql in your plsql package, you have to add it
to this package over here" sort of runs.  A  good idea taken too far can become
an inhibitor.

Given that it is humans typing in the code, the probability that we'll flood the
shared pool with hundreds of statements that *could have been shared* is
limited.

So, no, I would not encapsulate all sql in functions.

If I could just get everyone to put all sql in plsql.... that would be a huge
step forward

      Rating: 5
thanx - that
      makes sense!  June 10, 2003
Reviewer:
      A reader

      Rating: 5
bind variables
      in install statements  June 11, 2003
Reviewer:
      A reader  from CA
lot of times some data has to be there in the
installed tables - usually metadata...
in our case, those dont use bind values.
For exmaple,

insert into table t1 values ( 1, 'metadata 1');
insert into table t1 values ( 2, 'metadata 1');
insert into table t1 values ( 3, 'metadata 1');

There could be many such statements.

Since they are one time they do fill up the library
cache once in the beginning.

Do you think they are harmful (Since their effect
is only in the beginning)

          Followup:

if it would be as easy to avoid as not avoid, you should avoid it.

what would be wrong with a file like:

1,'metadata 1'
2,'metadata 2'
....

and sqlldr or an external table (in 9i)...

      Rating:
      4
thanx!  June 12, 2003
Reviewer:
      A reader  from ca
I guess i just wanted to gauge the priority of this work -
from what you say, it seems like it should be done but
is not such a big problem

Regards

      Rating:
      5
  June 24, 2003
Reviewer:
      A reader
Tom,

I used your function remove_constants and have found out that are whole buch of
queries that dont use bind variables.

The situation is I dont have acces to the codes so i cannot fix it (3rd party
software) and to make matters worse we are running on 8.0.5. To upgarde the
systems to 9i will take atleast 3-4 months.Till then can you suggest something
that i can do.

Thanks.

          Followup:

ask they 3rd party vendor why they write such bad code and when are they going
to fix it?

if you cannot fix the code, which has a bug, there is not much you can do in
8.0.

in 8i and up you MIGHT be able to use cursor_sharing, MAYBE.

      Rating:
      5
  July 12, 2003
Reviewer:  Fan Zhang
       from Palo Alto, CA USA
Tom,
 As the function remove_constants converts all the numbers to @, it may also
change the names of columns and tables.  e.g., both 'select c1 from t1' and
'select c2 from t2' will be converted as 'SELECT C@ FROM T@'. Is it better to
conver only the numbers after the '=' sign?  Thanks,

          Followup:

no, it is better to not over complicate things.

This is a simple solution to a nasty problem.  I assume a sensible human being
will be reading the output and upon seeing

select c@ from t@

in the report they would know full well "ok, ignore that one"

I would not want to write a SQL parser -- it is not just = what about:

select substr( x, 1, 40 ) from t;
select substr( x, 1, 41 ) from t;
select substr( x, 33, 20 ) from t;

select 55, a.* from a;
select 56, a.* from a;

select * from t where x > 55;
select * from t where x > 56;

select * from t where x between 1 and 100;
select * form t where x between 32 and 52;

and so on.....

      Rating:
      5
  July 13, 2003
Reviewer:  Fan Zhang
       from Palo Alto, CA USA
Tom,

Thanks very much for your reply.  I agree the situation is much more complicated
than I first thought, and it’s hard to list all the possibilities where numbers
are used as values. On the other hand, the numbers is used in a name (tables,
columns, schemas…) in limited number of ways.  By taking this advantage, the
following modified function seems working in most cases, though I feel there can
be many exceptions (e.g., to_date).

The reason for me to pursue the issue is that I know in some databases, for
security raeson, numbers are used intensively in column/table names.  If we get
1000 'select * from t@ where c@ = @', it may come from 1 case of 'select * from
t1 where c1=@' and 999 of 'select * from t2 where c2=@'.

*****************
create or replace function
remove_constants( p_query in varchar2 )  return varchar2
as
    l_query         long;
    l_char          varchar2(1);
    l_in_quotes     boolean default FALSE;
    l_before_number     boolean default FALSE;
    l_ascii         number;
begin
    for i in 1 .. length( p_query )
    loop
        l_char  := substr(p_query,i,1);
        l_ascii :=ascii(l_char);

        if ( l_in_quotes  )
        then
            if (l_char ='''')
            then
               l_query     := l_query || '''';
               l_in_quotes := FALSE;
            else
               l_query     := l_query || '#';
            end if;
        elsif ( NOT l_in_quotes and l_char = '''' )
        then
            l_in_quotes    := TRUE;
            l_query        := l_query || '''';
        elsif ( (NOT l_in_quotes )
                and l_before_number
                and (l_ascii between 48 and 57))
        then
            l_query := l_query || '@';
        else
            l_query := l_query || l_char;
        end if;

        if ( l_ascii = 36                    -- 36     => $
              or l_ascii = 95                -- 95     => _
              or l_ascii between 65 and 90   -- 65-90  => A-Z
              or l_ascii between 97 and 122  -- 97-122 => a-z
           )
        then
            l_before_number := FALSE;
        elsif (not l_before_number
               and l_ascii between 48 and 57 -- 48-57  => 0-9
              )
        then
            l_before_number := FALSE;
        elsif ( NOT l_in_quotes  )
        then
            l_before_number := TRUE;
        end if;

    end loop;

    for i in 0 .. 8 loop
        l_query := replace( l_query, lpad('@',10-i,'@'), '@' );
        l_query := replace( l_query, lpad(' ',10-i,' '), ' ' );
        l_query := replace( l_query, lpad('#',10-i,'#'), '#' );
    end loop;
    dbms_output.put_line(l_query);
    return upper(l_query);

end;
/
************

          Followup:

this sentence:

for security raeson, numbers are used intensively in column/table names.

should read:

in a misguided attempt to achieve security via obscurity, numbers are used
intensively in column/table names achieving no actual security benefit but
removing much value from the database itself.

      Rating: 5
Which
      Process?  July 15, 2003
Reviewer:  Sunil
      Gururaj  from Cyprus
Hi Tom,

Could you let me know which "ADDRESS" column of v$session relates to the address
in v$sqlarea view on which your above query is operating on.

Put it simply, I need to find out the program or the process which is NOT using
Binds and need to advice the development team accordingly.

Thanks,

          Followup:

and you cannot tell by the queries?

sql_address column is probably what you are looking for -- to goto v$sql

      Rating: 5
Nice Tool to
      catch the culprit SQLs  August 30, 2003
Reviewer:
      SUDIPT SINHA  from INDIA
Hi Tom
It is very nice tool to catch the culprit SQLs. Here Iam using Oracle
JDeveloper. I have query like this

SELECT XYZ.TADIBANK_ID, XYZ.TADIBRANCH_ID, XYZ.TADVCHALLAN_ID,
XYZ.TADDCHALLAN_DATE, XYZ.CTDILOGIN_ID, XYZ.CTDDMODIFIED_DATE,
XYZ.CTDIOFFICE_ID, XYZ.TADVREGN_ID, XYZ.TADVCST_REGN_ID,
XYZ.TADICIRCLE_OFFICE_ID, XYZ.TADIASSMT_YEAR, XYZ.TADVACT_ID, XYZ.TADIPERIOD,
XYZ.TADRCHALLAN_AMT, XYZ.TADVSCROLL_ID, XYZ.TADVERROR_ID,
XYZ.TADVRECONCILIATION_FLAG, XYZ.TADIRECONCILIATION_COUNT,
XYZ.TADVREC_CONFIRM_FLAG_PCR_RCPT, FUNC1('#',XYZ.TADVACT_ID,'#') AS ACT,
XYZ.TADDFROM_DATE, XYZ.TADDTO_DATE, FUNC1('#','# .........................
FROM XYZ
WHERE...............

in where clause I have used the bind variable. But in the FUNC1 I have used the
literals and it should be same for all the users, still it is coming in that
query(which u have given) for more than 100 times. Whether I have to use bind
for those literal also?

Regards
Sudipt

          Followup:

are the literal values for the queries the SAME?

what release of the database are you using?

are the queries executed from a stored procedure or directly?

      Rating: 5
Nice Tool to
      catch the culprit SQLs.. contd..  September 01, 2003

Reviewer:  SUDIPT SINHA  from India
1. The literal values for the queries are the SAME (for all users)

2. Release of the database -- Oracle 9.2.0.3

3. The queries are executed directly using JDBC( in built in Oracle JDeveloper).
FUNC1() is a stored function in Oracle DB.

          Followup:

peek into v$sql_shared_cursor for that query and see which columns are flagged
with "Y"s -- eg, this is a short excerpt from my book "Efficient Oracle by
design" showing what I mean:

...
We simply set up the four users A, B, C, and D. We grant CREATE SESSION and
CREATE TABLE privileges to users A, B, and D, and grant just CREATE SESSION to
C. Then we run the following script:

create public synonym emp for d.emp;
alter system flush shared_pool;

connect a/a
create table emp ( x int );
select * from emp;

connect b/b
create table emp ( x int );
select * from emp;

connect d/d
create table emp ( x int );
grant select on emp to c;
select * from emp;

connect c/c
select * from emp;

Now, let's take a quick peek at V$SQL, which shows all of the currently cached
SQL statements.

ops$tkyte@ORA920> select address, executions, sql_text
  2    from v$sql
  3   where upper(sql_text) like 'SELECT * FROM EMP%';

ADDRESS  EXECUTIONS SQL_TEXT
-------- ---------- ------------------------------
58DBD9CC          1 select * from emp
58DBD9CC          1 select * from emp
58DBD9CC          2 select * from emp

We can see that there are three versions of the query in the shared pool and
that one of the versions was used two times-that is the query submitted by users
C and D.

We can also query V$SQL_SHARED_CURSOR to determine exactly why Oracle developed
three query plans, to discover the root cause for the mismatch here. This view
has many columns that can be used to determine why a plan in the shared pool was
not shared. For example, the ROW_LEVEL_SEC_MISMATCH column would be flagged for
two queries that appear to be the same but differed at runtime based on the
security predicate returned by Oracle's Virtual Private Database (VPD) feature.
The OUTLINE_MISMATCH column would be flagged if one query used a query outline
and the other did not. Other columns in this view provide similar mismatch
information. In this example, we can see this information regarding our queries:

ops$tkyte@ORA920> select kglhdpar, address,
  2         auth_check_mismatch, translation_mismatch
  3    from v$sql_shared_cursor
  4   where kglhdpar in
  5   ( select address
  6       from v$sql
  7      where upper(sql_text) like 'SELECT * FROM EMP%' )
  8  /

KGLHDPAR ADDRESS  AUTH_CHECK_MISMATCH TRANSLATION_MISMATCH
-------- -------- ------------------- --------------------
58DBD9CC 59D4C9C8 N                   N
58DBD9CC 59E54EB0 Y                   Y
58DBD9CC 5898D42C Y                   Y

In this case, Oracle flagged two columns: AUTH_CHECK_MISMATCH, meaning the
authorization/translation check failed for the existing cursor (query plan), and
TRANSLATION_MISMATCH, meaning the base objects of the child cursor (query plan)
do not match. This shows us exactly why there are three queries plans (child
cursors): The base objects referenced were different.

      Rating: 5
Nice Tool to
      catch the culprit SQLs.. contd..   September 02, 2003

Reviewer:  SUDIPT SINHA  from India
I am sorry that the user I am talking about are the browing user not the
Database user.
In my database I am using only one schema(one DB user). The application is
accessed through Oracle 9iAS. Still I am getting so many count using your query.

Regards
Sudipt

          Followup:

use the technique above to find out WHY

      Rating: 5
Library Latch
      Cache Contention  October 30, 2003
Reviewer:
      JHT  from NJ, USA
Tom,

Wow, I think this is the best website for interesting and complicated oracle
issues!!!

If I am reading my statspack report correctly, I have a library latch cache
contention.  So I ran the "remove_constants" procedure Tom recommended at the
top of this thread and no SQL with > 100 counts turned up.  I have set
cursor_sharing=FORCE in the 8.1.6 db.  What are other possible reasons for
library cache latch contention?  Below are snippets from the statspack report...
 Thanks.

                                                                Snap Length
Start Id    End Id       Start Time             End Time         (Minutes)
--------  --------  --------------------  --------------------  -----------
     190       191  26-Oct-03 01:07:56    26-Oct-03 01:16:00           8.07

Cache Sizes
~~~~~~~~~~~
           db_block_buffers:      131072
              db_block_size:        8192
                 log_buffer:      524288
           shared_pool_size:   100000000

Load Profile
~~~~~~~~~~~~
                                       Per Second      Per Transaction
                                  ---------------      ---------------
                  Redo size:           151,257.45             2,187.62
              Logical reads:             6,142.08                88.83
              Block changes:               946.70                13.69
             Physical reads:               778.34                11.26
            Physical writes:                77.79                 1.13
                 User calls:             1,609.01                23.27
                     Parses:             1,047.90                15.16
                Hard parses:                 0.01                 0.00
                      Sorts:                 1.05                 0.02
               Transactions:                69.14

              Rows per Sort:      117.75
  Pct Blocks changed / Read:       15.41
         Recursive Call Pct:       60.36
 Rollback / transaction Pct:        0.30

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        Buffer Nowait Ratio:       99.94
        Buffer  Hit   Ratio:       87.33
        Library Hit   Ratio:      100.04
        Redo   NoWait Ratio:      100.00
       In-memory Sort Ratio:       97.84
           Soft Parse Ratio:      100.00
            Latch Hit Ratio:       89.24

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
latch free                                      2,182,681      251,374   42.78
slave wait                                         30,702      137,440   23.39
db file sequential read                           127,626       74,321   12.65
log file sync                                      43,839       41,690    7.09
io done                                            30,386       40,844    6.95
         -------------------------------------------------------------

                                                Pct    Avg                Pct
                                    Get         Get Sleeps      Nowait Nowait
Latch Name                        Requests     Miss  /Miss    Requests   Miss
------------------------------- ------------ ------ ------ ----------- ------
Active checkpoint queue latch            924    0.0                  0
Checkpoint queue latch               121,876    0.0    0.1           0
Token Manager                            164    0.0                  0
archive control                            2    0.0                  0
archive process latch                      2    0.0                  0
cache buffer handles                     138    0.0                  0
cache buffers chains               5,518,468    0.2    0.1     666,964    0.0
cache buffers lru chain              475,650    0.2    0.0     382,391    0.2
channel handle pool latch                170    0.0                170    0.0
channel operations parent latch          340    0.0                170    0.0
constraint object allocation             243    0.0                  0
direct msg latch                      93,078    0.0    0.0           0
dml lock allocation                  247,685    0.1    0.0           0
done queue latch                      29,564    0.0             29,567    0.0
enqueue hash chains                  330,628    0.2    0.1           0
enqueues                             394,860    0.2    0.1           0
event group latch                        170    0.0                  0
global transaction                     7,431    0.0                  0
global tx free list                      531    0.0                  0
global tx hash mapping                 2,090    0.0                  0
job_queue_processes parameter l          154    0.0                  0
ksfv messages                        122,630    0.0                  0
ktm global data                            2    0.0                  0
latch wait list                    1,409,267    2.1    0.1   1,653,622    4.0
library cache                     11,937,161   22.3    0.8           0
library cache load lock                   56    0.0                  0
list of block allocation              71,525    0.0    0.0           0

          Followup:

confusion

you ran for 8 minutes (8*60 = 480 cpu seconds)

but you had 2,513 seconds of waits..

are you like a 16/32 cpu machine here?

you do not have a hard parse issue - you have 100% soft parses (PERFECTION,
congrats!)

what you might have is an "over parse" issue, soft parses are less expensive
than hard, but only "no parse" is free.

details. need more details -- describe the system

      Rating: 4
Ratio query x
      parse  October 31, 2003
Reviewer:  Marcio
       from br
Tom, I'm wondering about you said:
"You need binds when you are parsing X statements / second.
When are you parsing 1 statement every Y seconds, it is not nearly as
important."

How can I get such ratio? -- x statements / second.

thanks,
Marcio

          Followup:

you would sort of know -- it is your system, are you OLTP with "transactions per
second" or are you a warehouse with N concurrently executing massive queries.

But, you can use statspack to see executions/second and transactions/second to
get a gist.

      Rating: 4
Library Latch
      Cache Contention  November 03, 2003
Reviewer:
      JHT  from NJ USA
Thanks Tom for your earlier response to this issue.

Our DB Server is 8 CPUs.  It is an OLTP system where the majority of the
processing is this:  There is A LOT (26,000 per second) of inserts into a view.
An "instead of" trigger fires (and discards about 23,000 inserts because of some
application logic).  The rest (3,000 inserts) is processed and
updates/deletes/inserts into a table (IPMAP) that keeps track of what cable
modem owns what ip address.  This triggers also inserts into a "history" table
to store this same information.

If this is a soft parse issue, how do I go about improving this?

Below is the costly SQL statements (in terms of buffer gets) in this 8 minute
window.  The first statement is the insert into the VIEW.

   Buffer Gets     Executes   per Exec    Total   Hash Value
-------------- ------------ ------------ ------ ------------
SQL statement
------------------------------------------------------------------------------
     1,848,346      214,866          8.6   62.2   2948872974
insert into LEASEVIEW (IPADDR,REGIONID,DEVICEID,RAREMOTEID,DEVICETYPE,LEASEDAT

       859,372      214,946          4.0   28.9    837720088
SELECT COUNT(*)   FROM IPMAP  WHERE IPADDR = :b1  AND REGIONID = :b2  AND DEVI

       216,003       27,029          8.0    7.3   1919955916
SELECT COUNT(*)   FROM IPMAP  WHERE ((IPADDR = :b1  AND REGIONID = :b2 ) OR (D

       205,496       27,019          7.6    6.9    146615416
UPDATE IPMAP SET DEVICEID=:b1,RAREMOTEID=:b2,DEVICETYPE=:b3,LEASEDATE=:b4,EXPD

       198,669       27,040          7.3    6.7   1492083554
SELECT AD.ACCTTYPE,DA.DATAACCOUNTID ACCOUNTID   FROM ACCTDEVICE AD,DATAACCOUNT

          Followup:

I would not have done it that way -- for such intensive processing -- I would
have built a stored procedure that would reject 23 out of 26k records --
inserting the other 3k.

what is the calling application and is it parsing the insert before each and
every insert?

also -- the trigger, does it contain the code "in it" or is the trigger a single
line -- a call to a stored procedure.  If not, each execute is going to cause a
soft parse of all of the trigger SQL.  Consider:

ops$tkyte@ORA920PC> create table t ( x int );

Table created.

ops$tkyte@ORA920PC> create or replace view v as select * from t;

View created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create or replace procedure p
  2  as
  3  begin
  4          for x in ( select * from dual IN_PROCEDURE )
  5          loop
  6                  null;
  7          end loop;
  8  end;
  9  /

Procedure created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create or replace trigger v_trigger
  2  instead of insert on v
  3  begin
  4          for x in ( select * from dual IN_TRIGGER )
  5          loop
  6                  null;
  7          end loop;
  8          p;
  9  end;
 10  /

Trigger created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert into v values ( 1 );

1 row created.

ops$tkyte@ORA920PC> insert into v values ( 1 );

1 row created.

ops$tkyte@ORA920PC> insert into v values ( 1 );

1 row created.

ops$tkyte@ORA920PC> insert into v values ( 1 );

1 row created.

ops$tkyte@ORA920PC> insert into v values ( 1 );

1 row created.

ops$tkyte@ORA920PC>

tkprof shows us:

select * from dual IN_TRIGGER

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        5      0.00       0.00          0          0          0           0
Execute      5      0.00       0.00          0          0          0           0
Fetch       10      0.00       0.00          0         15          0           5
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       20      0.00       0.00          0         15          0           5
********************************************************************************
select * from dual IN_PROCEDURE

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      5      0.00       0.00          0          0          0           0
Fetch       10      0.00       0.00          0         15          0           5
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       16      0.00       0.00          0         15          0           5

See how the trigger sql was parsed 5 times for 5 executes, but plsql stored
procedures nicely "cache" the sql.

So, my advice:

a) don't use the view
b) use a stored procedure
c) make DARN sure the caller parses the begin p(....); end; block ONCE and
executes it over and over and over again.

your parsing will go way way way down.

      Rating: 4
Library Latch
      Cache Contention  November 03, 2003
Reviewer:
      JHT  from NJ USA
Oops...I meant 26,000 inserts per minute (not per second)

November 03, 2003 Brett -- Thanks for the question regarding "Slow
      processing on database using merge", version 9.2.0.4

      You Asked
Tom,

After further investigating, I have found that the problem is with using the
merge statement on tables with millions of rows.  I have several tables that use
the following logic:

PROCEDURE UPSERT
 (P_ID IN SX_SHIP_METHOD_CARR_MAPS.ID%TYPE
 ,P_STATUS IN SX_SHIP_METHOD_CARR_MAPS.STATUS%TYPE
 ,P_SXSHPMTH_FK IN SX_SHIP_METHOD_CARR_MAPS.SXSHPMTH_FK%TYPE
 ,P_SXMULSIT_FK IN SX_SHIP_METHOD_CARR_MAPS.SXMULSIT_FK%TYPE
 ,P_SXUFS_FK IN SX_SHIP_METHOD_CARR_MAPS.SXUFS_FK%TYPE
 ,P_DEFAULT_FLAG IN SX_SHIP_METHOD_CARR_MAPS.DEFAULT_FLAG%TYPE
 )
 IS

V_DATA SX_SHIP_METHOD_CARR_MAPS_TT := SX_SHIP_METHOD_CARR_MAPS_TT();
V_STATUS VARCHAR2(2);
BEGIN
   select decode(to_char(abs(p_status)), '0', '0', '1')
   into v_status
   from dual;

   if g_sync_mode = 'S' then
      BEGIN
         insert into sx_ship_method_carr_maps
                 ( id
                  ,status
                  ,sxshpmth_fk
                  ,sxmulsit_fk
                  ,sxufs_fk
                  ,default_flag
                 )
          values ( p_id
                  ,v_status
                  ,p_sxshpmth_fk
                  ,p_sxmulsit_fk
                  ,p_sxufs_fk
                  ,p_default_flag
                 );
      EXCEPTION
         WHEN DUP_VAL_ON_INDEX THEN
            update sx_ship_method_carr_maps
               set  status       = v_status
                   ,sxshpmth_fk  = p_sxshpmth_fk
                   ,sxmulsit_fk  = p_sxmulsit_fk
                   ,sxufs_fk     = p_sxufs_fk
                   ,default_flag = p_default_flag
               where id = p_id;
      END;
   else
      v_data.extend;
      v_data(1) := sx_ship_method_carr_maps_rt( p_id
                                               ,v_status
                                               ,p_sxshpmth_fk
                                               ,p_sxmulsit_fk
                                               ,p_sxufs_fk
                                               ,p_default_flag
                                              );
      merge into sx_ship_method_carr_maps t1
         using (select * from table(cast(v_data as
sx_ship_method_carr_maps_tt))) t2
         on (t1.id = t2.id)
         when matched then
            update set  t1.status       = t2.status
                       ,t1.sxshpmth_fk  = t2.sxshpmth_fk
                       ,t1.sxmulsit_fk  = t2.sxmulsit_fk
                       ,t1.sxufs_fk     = t2.sxufs_fk
                       ,t1.default_flag = t2.default_flag
         when not matched then
            insert ( id
                    ,status
                    ,sxshpmth_fk
                    ,sxmulsit_fk
                    ,sxufs_fk
                    ,default_flag
                   )
            values ( t2.id
                    ,t2.status
                    ,t2.sxshpmth_fk
                    ,t2.sxmulsit_fk
                    ,t2.sxufs_fk
                    ,t2.default_flag
                   );
   end if;
END;
--;

END SX_SHIP_METHOD_CARR_MAPS_PKG;
/

When the mode is 'S', the performance is no problem because I truncate the table
and load each record right without trying to find it.  When the mode is 'M', I
use the merge statement to update it if it is there or insert it if it is not.
The only index I have on the table is the primary key (id).  On tables of more
than 3 million rows, it appears to take a while to find the record (I verified
this using SQLPLUS).  However, if I use the insert/update logic for everything,
I don't have the slowdown.  Is there something specific to the merge that would
cause this?

Thanks,

Brett

Tom,

I am experiencing very slow execution times for inserts on my database using the
merge command.  Running statspack reveals this.  However, if I change the merge
command to use and insert/update logic, performance improves and my buffer cache
goes from 0% to over 90% instantly.  I am trying to figure out why this may be
happening.  My guess is that the database is on a RAID 5 and there a lots of
read contentions.  I am running on a 2-processor system with 2Gig of RAM.

STATSPACK report for

DB Name DB Id      Instance Inst Num Release   Cluster Host
------- ---------- -------- -------- --------- ------- ---------
PROD    4205553307 prod            1 9.2.0.4.0 NO      ZEUS

      Snap Id     Snap Time       Sessions  Cur/Sess  Comment
      ------- ------------------  --------  -------- ------------
Begin:      1 30-Oct-03 14:25.22        14       4.1
  End:      2 30-Oct-03 14:35:44        16       5.7
Elapsed:            10.37 (mins)

Cache Sizes (end)
~~~~~~~~~~~~~~~~~
               Buffer Cache:    48M      Std Block Size:       8K
           Shared Pool Size:    68M          Log Buffer:      512K

Top 5 Timed Events
~~~~~~~~~~~~~~~~~~                                         % Total
Event                                Waits    Time (s)    Ela Time
------------------------------ ----------- ----------- -----------
db file scattered read             104,821         454       78.61
CPU time                                           120       20.74
db file sequential read                631           3         .53
control file parallel write            202           0         .07
control file sequential read           186           0         .03

Any thoughts on what I can do to make this system for efficient?

Thanks,

Brett

      and we said...
why are you doing this row by row by stinking slow row??????

why would you merge a row at a time?

merge is designed to merge two SETS, not single rows.

your row by row by slow slow slow row processing is the killer here.  You have
tons of procedural code that is just slowing you way way way down.  You are
dyanmically allocating storage, casting, using object types -- all of which is
wholly unneccesary.

The invoker of this routine shouldn't be -- the invoker should just be running
MERGE on sets, period.

Your insert/update routine is many many times slower then it needs to be as well
- forget about MERGE for a moment, you are using many orders of magnitude more
resources then you ever need to!

To answer the very last question "what can you do to make this system more
efficient" i have only one word for you:

SETS

it is all about sets, SQL is about sets, the best way to make SQL perform is to
think SETS, not procedurally, SETS.  MERGE is all about SETS.  MERGE is not
about row by row (cobol is maybe)...

I found this so slow -- I could not even bear to run a 1,000,000 row test!  Here
is a couple of thousand rows.  We can make it faster -- and I'll iteratively
show you how -- but really a single sole MERGE statement is all you want.  I'll
do 10000 rows to show you really slow (your way) and just plain slow (a simple
modification).  I use a script "big_table.sql" from my new book Effective Oracle
By Design to create my test table:

---------------- big_table.sql ----------------------------------

create table big_table
as
select rownum id, a.*
  from all_objects a
 where 1=0
/
alter table big_table nologging;

declare
    l_cnt number;
    l_rows number := &1;
begin
    insert /*+ append */
    into big_table
    select rownum, a.*
      from all_objects a
     where rownum <= &1;

    l_cnt := sql%rowcount;

    commit;

    while (l_cnt < l_rows)
    loop
        insert /*+ APPEND */ into big_table
        select rownum+l_cnt,
               OWNER, OBJECT_NAME, SUBOBJECT_NAME,
               OBJECT_ID, DATA_OBJECT_ID,
               OBJECT_TYPE, CREATED, LAST_DDL_TIME,
               TIMESTAMP, STATUS, TEMPORARY,
               GENERATED, SECONDARY
          from big_table
         where rownum <= l_rows-l_cnt;
        l_cnt := l_cnt + sql%rowcount;
        commit;
    end loop;
end;
/

alter table big_table add constraint
big_table_pk primary key(id)
/

begin
   dbms_stats.gather_table_stats
   ( ownname    => user,
     tabname    => 'BIG_TABLE',
     method_opt => 'for all indexed columns',
     cascade    => TRUE );
end;
/
select count(*) from big_table;
------------------------------------------------------------------

I use that to create a 10000 row table and then ran this script "slow.sql"

---------------------- slow.sql ---------------------------------------

create or replace type myScalarType
as object
(ID               NUMBER,
 OWNER            VARCHAR2(30),
 OBJECT_NAME      VARCHAR2(30),
 OBJECT_TYPE      VARCHAR2(18),
 CREATED          DATE,
 LAST_DDL_TIME    DATE,
 TIMESTAMP        VARCHAR2(19),
 STATUS           VARCHAR2(7)
)
/
create or replace type myArrayType
as table of myScalarType
/

drop table big_table;
@big_table &1
alter table big_table modify object_id null;
exec dbms_stats.set_table_stats( user, 'BIG_TABLE', numrows => 1000000, numblks
=> 15000 );

drop table updates;

create table updates
as
select id+(select trunc(count(*)/2) from big_table) id,
       owner, object_name, object_type, created,
       last_ddl_time, timestamp, status
  from big_table
/
alter table updates add constraint updates_pk primary key(id);

create or replace
procedure do_single_row
( p_id            in big_table.id%type,
  p_owner         in big_table.owner%type,
  p_object_name   in big_table.object_name%type,
  p_object_type   in big_table.object_type%type,
  p_created       in big_table.created%type,
  p_last_ddl_time in big_table.last_ddl_time%type,
  p_timestamp     in big_table.timestamp%type,
  p_status        in big_table.status%type )
as
    l_data myArrayType := myArrayType();
begin
    l_data.extend;
    l_data(1) :=
       myScalarType( p_id, p_owner, p_object_name,
                     p_object_type, p_created,
                     p_last_ddl_time, p_timestamp,
                     p_status );

    merge into big_table t1
    using ( select * from table( cast(l_data as myArrayType) ) ) t2
    on ( t1.id = t2.id )
    when matched then
         update set t1.owner = t2.owner,
                    t1.object_name = t2.object_name,
                    t1.object_type = t2.object_type,
                    t1.created = t2.created,
                    t1.last_ddl_time = t2.last_ddl_time,
                    t1.timestamp = t2.timestamp,
                    t1.status = t2.status
    when not matched then
         insert ( id, owner, object_name, object_type,
                  created, last_ddl_time, timestamp,
                  status)
         values ( t2.id, t2.owner, t2.object_name,
                  t2.object_type, t2.created,
                  t2.last_ddl_time, t2.timestamp, t2.status );
end;
/

create or replace procedure slow_way
as
begin
    for x in ( select * from updates )
    loop
        do_single_row( x.id, x.owner, x.object_name,
                       x.object_type, x.created,
                       x.last_ddl_time, x.timestamp, x.status );
    end loop;
end;
/

@trace
exec slow_way
---------------------------------------------------------------------

I set the stats on big_table so as to get a plan for the merge that would be
used for millions of rows in big_table (else, the merge used a hash anti join on
a small table, that would skew our findings for this scaled down test).  I
wanted an index lookup as that is what would really happen in real life.  Given
we are doing a row at a time, this is a valid use of setting stats to "get a
plan" -- so we can extrapolate what would happen if we did this process 1000000
times instead of just the 1,000 i did here:

the above script will update 1/2 the rows in big_table and insert 1/2 as many as
exist by design.

Now, TKPROF shows us from this run:

MERGE into big_table t1
    using ( select * from table( cast(:b1 as myArrayType) ) ) t2
    on ( t1.id = t2.id )
    when matched then
         update set t1.owner = t2.owner,
                    t1.object_name = t2.object_name,
                    t1.object_type = t2.object_type,
                    t1.created = t2.created,
                    t1.last_ddl_time = t2.last_ddl_time,
                    t1.timestamp = t2.timestamp,
                    t1.status = t2.status
    when not matched then
         insert ( id, owner, object_name, object_type,
                  created, last_ddl_time, timestamp,
                  status)
         values ( t2.id, t2.owner, t2.object_name,
                  t2.object_type, t2.created,
                  t2.last_ddl_time, t2.timestamp, t2.status )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute  10000     12.65      15.36          0      25600      23216       10000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    10001     12.66      15.36          0      25600      23216       10000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 542     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
  20000  MERGE  (cr=25646 r=0 w=0 time=6067730 us)
  10000   VIEW  (cr=25019 r=0 w=0 time=2636444 us)
  10000    NESTED LOOPS OUTER (cr=25019 r=0 w=0 time=2329853 us)
  10000     COLLECTION ITERATOR PICKLER FETCH (cr=0 r=0 w=0 time=291352 us)
   5000     TABLE ACCESS BY INDEX ROWID BIG_TABLE (cr=25019 r=0 w=0 time=1275712
us)
   5000      INDEX RANGE SCAN BIG_TABLE_PK (cr=20019 r=0 w=0 time=953202
us)(object id 56866)

Now, if we just extrapolate up to 1,000,000 rows:

ops$tkyte@ORA920> select 1000000/10000*12.66 from dual;

1000000/10000*12.66
-------------------
               1266

  1* select 1266/60 from dual
ops$tkyte@ORA920> /

   1266/60
----------
      21.1

it would take 21 CPU minutes to do this (trust me, elapsed wall clock time even
longer)!!!  So, lets modify the routine a little bit.  You are using object
types, nested tables, cast -- very cpu intensive -- that and the optimizer is
guessing that there will be 8k rows in that little subquery as well (no stats
for that "table()").  Lets create a small 1 row table:

drop table iot;
create table iot ( x int primary key ) organization index;
insert into iot values ( 1 );
analyze table iot compute statistics;

and rewrite out do_single_row routine "better"

create or replace
procedure do_single_row_better
( p_id            in big_table.id%type,
  p_owner         in big_table.owner%type,
  p_object_name   in big_table.object_name%type,
  p_object_type   in big_table.object_type%type,
  p_created       in big_table.created%type,
  p_last_ddl_time in big_table.last_ddl_time%type,
  p_timestamp     in big_table.timestamp%type,
  p_status        in big_table.status%type )
as
begin
    merge into big_table t1
    using ( select p_id id,
                   p_owner owner,
                   p_object_name object_name,
                   p_object_type object_type,
                   p_created created,
                   p_last_ddl_time last_ddl_time,
                   p_timestamp timestamp,
                   p_status status
              from iot
             where x = 1 ) t2
    on ( t1.id = t2.id )
    when matched then
         update set t1.owner = t2.owner,
                    t1.object_name = t2.object_name,
                    t1.object_type = t2.object_type,
                    t1.created = t2.created,
                    t1.last_ddl_time = t2.last_ddl_time,
                    t1.timestamp = t2.timestamp,
                    t1.status = t2.status
    when not matched then
         insert ( id, owner, object_name, object_type,
                  created, last_ddl_time, timestamp, status)
         values ( t2.id, t2.owner, t2.object_name, t2.object_type,
                  t2.created, t2.last_ddl_time,
                  t2.timestamp, t2.status );
end;
/

No temporary variables, less procedural code, uses all real tables (analyzed).
The tkprof is now:

MERGE into big_table t1
    using ( select :b8 id,
                   :b7 owner,
                   :b6 object_name,
                   :b5 object_type,
                   :b4 created,
                   :b3 last_ddl_time,
                   :b2 timestamp,
                   :b1 status
              from iot
             where x = 1 ) t2
    on ( t1.id = t2.id )
    when matched then
         update set t1.owner = t2.owner,
                    t1.object_name = t2.object_name,
                    t1.object_type = t2.object_type,
                    t1.created = t2.created,
                    t1.last_ddl_time = t2.last_ddl_time,
                    t1.timestamp = t2.timestamp,
                    t1.status = t2.status
    when not matched then
         insert ( id, owner, object_name, object_type,
                  created, last_ddl_time, timestamp, status)
         values ( t2.id, t2.owner, t2.object_name, t2.object_type,
                  t2.created, t2.last_ddl_time,
                  t2.timestamp, t2.status )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute  10000     11.94      12.60          0      35587      23194       10000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    10001     11.95      12.61          0      35587      23194       10000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 542     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
  20000  MERGE  (cr=35627 r=0 w=0 time=3548112 us)
  10000   VIEW  (cr=35009 r=0 w=0 time=1010034 us)
  10000    NESTED LOOPS OUTER (cr=35009 r=0 w=0 time=933976 us)
  10000     INDEX UNIQUE SCAN SYS_IOT_TOP_56881 (cr=10000 r=0 w=0 time=401922
us)(object id 56882)
   5000     TABLE ACCESS BY INDEX ROWID BIG_TABLE (cr=25009 r=0 w=0 time=353633
us)
   5000      INDEX UNIQUE SCAN BIG_TABLE_PK (cr=20009 r=0 w=0 time=150502
us)(object id 56878)

So, it is a little better, but not good enough.  Lets do it "right".  forget the
code, lets just go for it, single SQL statement:

merge into big_table t1
using ( select * from updates ) t2
on ( t1.id = t2.id )
when matched then
     update set t1.owner = t2.owner,
                t1.object_name = t2.object_name,
                t1.object_type = t2.object_type,
                t1.created = t2.created,
                t1.last_ddl_time = t2.last_ddl_time,
                t1.timestamp = t2.timestamp,
                t1.status = t2.status
when not matched then
     insert ( id, owner, object_name, object_type,
              created, last_ddl_time, timestamp, status)
     values ( t2.id, t2.owner, t2.object_name, t2.object_type,
              t2.created, t2.last_ddl_time,
              t2.timestamp, t2.status )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      1.03       1.03          0      15783       8319       10000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      1.05       1.05          0      15783       8319       10000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 542

Rows     Row Source Operation
-------  ---------------------------------------------------
      2  MERGE  (cr=15832 r=0 w=0 time=1042526 us)
  10000   VIEW  (cr=15141 r=0 w=0 time=474658 us)
  10000    NESTED LOOPS OUTER (cr=15141 r=0 w=0 time=440052 us)
  10000     TABLE ACCESS FULL OBJ#(56889) (cr=127 r=0 w=0 time=49104 us)
   5000     TABLE ACCESS BY INDEX ROWID OBJ#(56887) (cr=15014 r=0 w=0
time=276923 us)
   5000      INDEX RANGE SCAN OBJ#(56888) (cr=10014 r=0 w=0 time=166679
us)(object id 56888)

now, that -- that is significant.  what about 1,000,000 rows?

merge into big_table t1
using ( select * from updates ) t2
on ( t1.id = t2.id )
when matched then
     update set t1.owner = t2.owner,
                t1.object_name = t2.object_name,
                t1.object_type = t2.object_type,
                t1.created = t2.created,
                t1.last_ddl_time = t2.last_ddl_time,
                t1.timestamp = t2.timestamp,
                t1.status = t2.status
when not matched then
     insert ( id, owner, object_name, object_type,
              created, last_ddl_time, timestamp, status)
     values ( t2.id, t2.owner, t2.object_name, t2.object_type,
              t2.created, t2.last_ddl_time,
              t2.timestamp, t2.status )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.31          0          0          0           0
Execute      1     67.60     277.51      14310      55205    2159380     1000000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2     67.61     277.82      14310      55205    2159380     1000000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 542

Rows     Row Source Operation
-------  ---------------------------------------------------
      2  MERGE  (cr=55544 r=14310 w=0 time=277757501 us)
1000000   VIEW  (cr=26882 r=14304 w=0 time=54631955 us)
1000000    HASH JOIN OUTER (cr=26882 r=14304 w=0 time=51744510 us)
1000000     TABLE ACCESS FULL OBJ#(56903) (cr=12561 r=0 w=0 time=1592967 us)
1000000     TABLE ACCESS FULL OBJ#(56901) (cr=14321 r=14304 w=0 time=31082443
us)

there we go -- a couple of minutes, not a couple of hours!  1 cpu minute (lots
less then 21) Hugely different.

So, the best way to make this have "fast=true"?  Use set based operations, a
single merge.  lose the slow by slow (oops -- meant row by row) processing.

Review &
      Followup

      Rating: 5
Excellent,
      truly detailed   November 02, 2003
Reviewer:
      A reader

      Rating: 4
Another MERGE
      Problem  November 03, 2003
Reviewer:  Jasbir
      Kular  from Toronto, Ontario
The following PL/SQL block using a MERGE and Table Function gives me an error in
9.2.0.3.0 but not in 9.2.0.1.0.  The error is ORA-01008: not all variables bound
ORA-06512: at "BNDWQA.TEST_DATA_FUN", line
11 ORA-06512: at line 4

The equivalent PL/SQL block using an Insert/Update statement works fine in both
Oracle versions.

DECLARE
   P_ID NUMBER := 1;
BEGIN
   MERGE
   INTO TEST_DATA_2
   USING
      (SELECT ID, DATA
        FROM TABLE(TEST_DATA_FUN(CURSOR(
             SELECT * FROM TEST_DATA WHERE (ID = P_ID)
             )))
      ) SEL
   ON ( ID = SEL.ID )
   WHEN MATCHED THEN
   UPDATE
   SET DATA = SEL.DATA
   WHEN NOT MATCHED THEN
   INSERT (ID, DATA)
    VALUES (SEL.ID, SEL.DATA);
END;

Here are the test table and functions I used:

CREATE TABLE TEST_DATA
   (ID NUMBER,
    DATA VARCHAR2(30));

INSERT INTO TEST_DATA VALUES (1, 'TEST1');
INSERT INTO TEST_DATA VALUES (2, 'TEST2');
INSERT INTO TEST_DATA VALUES (3, 'TEST3');

CREATE TABLE TEST_DATA_2
   (ID NUMBER,
    DATA VARCHAR2(30));

CREATE TYPE TEST_DATA_TYPE AS OBJECT
   (ID NUMBER,
    DATA VARCHAR2(30));

CREATE TYPE TEST_DATA_TAB AS TABLE OF TEST_DATA_TYPE;

CREATE OR REPLACE FUNCTION TEST_DATA_FUN (P SYS_REFCURSOR)
RETURN TEST_DATA_TAB
PIPELINED
PARALLEL_ENABLE(PARTITION P BY ANY)
IS
   L_ID NUMBER;
   L_DATA VARCHAR2(30);
   L_TEST_DATA_TYPE TEST_DATA_TYPE;
BEGIN
   LOOP
      FETCH P INTO L_ID, L_DATA;
      EXIT WHEN P%NOTFOUND;
      L_TEST_DATA_TYPE := TEST_DATA_TYPE(L_ID, L_DATA);
      PIPE ROW (L_TEST_DATA_TYPE);
   END LOOP;
   RETURN;
END;

Thanks.

          Followup:

confirmed, did you file a tar with support?  you have the perfect reproducible
testcase for getting a bug opened.

      Rating: 4
Another MERGE
      Problem   November 04, 2003
Reviewer:  Jasbir
      Kular  from Toronto, Ontario.
Yes, I logged a TAR.  I am trying to install Oracle 9.2.0.4.0 and run the test
on that version.

      Rating: 4
Another MERGE
      Problem   November 04, 2003
Reviewer:  Jasbir
      Kular  from Toronto, Ontario
I tried my test on 9.2.0.4.0 and the bug still exists in that version.

November 03, 2003 Wayne -- Thanks for the question regarding "Using
      A Struct within PL/SQL", version 8.1.5

      You Asked
Tom,

I have a java object that I need to pass in to a stored
procedure
as an IN parameter.  This Object contains two vectors.  The Main
object
will be saved in table A while the first vector contains several
objects
that will be saved in table B and the second vector contains
several
objects to be saved in table C.  I would like to pass the entire
object in and I believe I may be able to do this using the
Oracle
Struct type.  1. Is this correct in assuming this and 2. once it
is
passed to the stored procedure how do I manipulate the data once
I have it there.  Any examples would be appreciated.

thanks,

wayne

      and we said...
You will have to use Oracle8 object types.  From these, we can
use Jpublisher to create Java Classes to map to these types.  It
doesn't work the other way around however (starting with java
objects -> Oracle8 types).

Please check out

http://technet.oracle.com/doc/java.815/a64685/oraext5.htm#1039456
for our abilities in this arena.

You will be able to create an Oracle8 object type that has 2
vectors in it and pass this in total to a plsql routine but
you'll have to START with the Oracle8 object type and work
backwards if you want to do that.
Review &
      Followup

      Rating: 4
Same
      requirement and the URL is broken  November 03, 2003

Reviewer:  Victor Jaen  from Panama
Hello!
I have almost same requirement to pass a Java object with vectors inside it to
PL/SQL, but the URL is posted here is broken.
Where can I get the information of how to do this?
Thanks! I need it ASAP!

          Followup:

jpublisher -- it is a documented feature of the database.  a quick peek into the
docs would find it faster then waiting for me!

http://otn.oracle.com/pls/db92/db92.docindex?remark=homepage#index-JPU

November 03, 2003 Gunjeet -- Thanks for the question regarding
      "Full Database export and import : v8.1.6", version v 8.1.6

      You Asked
Hi Tom,

I have a question about full export/import of database (I have
only used export/import of specific schemas before).

I have a script to do a full export of the database. It does
the following :

$SQLPLUS /nolog <<EOF >> $LOGFILE 2>&1
connect internal/xyz  as sysdba
startup  restrict
EOF

$ORACLE_HOME/bin/exp sys/change_on_install direct=y
file=$LOCATIONDIR/$DATFILE full=y >> $LOGFILE 2>&1

This seems to work fine.

I have another script to do a full import of this data as
a means of recovery. The import script does the following :

Stops all server processes including all oracle processes (ora_
+
listener process).

Then it does :

$ORACLE_HOME/bin/svrmgrl << EOF >> $LOGFILE 2>&1
connect internal/xyz as sysdba
startup nomount
disconnect
exit
EOF

$DBIMP sys/change_on_install
file=$LOCATIONDIR/$DATFILE full=y >> $LOGFILE 2>&1

This gives the following error :

IMP-00058: ORACLE error 1033 encountered
ORA-01033: ORACLE initialization or shutdown in
progressUsername:

I have also tried the above import command with ignore=y. It
gives
the same error.

However, if before invoking the import command, I issue a create
database command followed by @catalog.sql, then a bunch of
create tablespace
commands, @catproc.sql, @caths.sql, @otrcsvr.sqland then call
import, everything works fine.

Someone else wrote these scripts and I'm trying to figure out
why
we have to explicitly create the database and tablespaces in the
import script when we have a dump of full export available.

Please let me know how a full export of database can be
imported.

Thanks in advance,

Gunjeet

      and we said...
Well, you get the

IMP-00058: ORACLE error 1033 encountered
ORA-01033: ORACLE initialization or shutdown in progress

because you have only done:

startup nomount

IMP needs a database to be up and running in order to import
into.  If you do the create database statement -- and then are
able to run catproc and so on -- that implies the database is UP
(if you can run catproc -- the database is UP and opened and
ready to go).

You have to explicity create the database -- that will create a
database with one tablespace -- system.  the import will create
the rest.  import does not CREATE a database -- you must.  But
once you have, it'll fill it up.  exp and imp Never exp or imp
SYS owned objects -- they are always assumed to exist (else we
would never be able to export from v7 and import into v8 for
example...)

Review &
      Followup

      Rating: 3
full import
      questions  November 03, 2003
Reviewer:
      jianhui  from DC
Tom,
In case i need to import entire database from A to B so that I can get B updated
to the time when export of A was done. And B was a clone of A in the past. No
restore and recovery can be considered here since the platform is different and
we have the dump file already.

In order to do this, which way will be better?
1) Recreate B from scratch with only SYS, SYSTEM, and do FULL import
2) Drop all non SYS/SYSTEM users then do FULL import
3) In case 1), should I create all tablespaces? I wonder that import is not able
to do this since the physical layout of the tablespaces might not be the same
between A and B

          Followup:

1 and optionally 3 if you like.

if the datafile names will be different, you'll have to precreate the
tablespaces yes.

November 03, 2003 wallacel -- Thanks for the question regarding
      "design the table", version 8.1.6

      You Asked
Tom:

when doing the database design , i have the following question.

let's say we have emp table which have empid,empname,deptno,
as well as empPicture, empGreeting, empFamilyPict.
since we don't use empPicture , empGreeting( which is avi),
empFamilypicture a lot, do you think it make sense to create
another to store that?

so instead of have one table
emp(empid number,
empname varchar2(100),
deptno number,
empPicture blob,
empGreeting blob,
empFamilyPict blob).

we have two, one is
emp(empid number,
empname varchar2(100),
deptno number)

another is
empBlob(empid number,
empPicture blob,
empGreeting blob,
empFamilyPict blob).

or latter on i change it to 4 table , like

1.emp(empid number,empname varchar2(100),deptno number)
2.empPict(empid number,empPicture blob);
3.empGreeting(empid number,empGreeting blob)
4.empFpict(empid number,empFamilyPict blob).

does that make sense? which one is better?
one table? two table? 4(multiple table)?
Regards,

      and we said...
1 table.

With blobs, which are typically stored out of line, you already
physically have the data elsewhere.  Logically they belong
together -- keep them together.

Review &
      Followup

      Rating: 4
maximum no of
      columns per table  July 04, 2002
Reviewer:
      nvssk  from India
If an entity is having say 100 fields, Is it suggested to create one single
table? or split into multiple tables based on the most frequently accesses
columns? Do we need to consider data block size?

If we keep them in one single table what is the impact on performance?

If an entity is having LONG and/or CLOB columns also, what all should be
considered for creating tables ?

Do we need to decide on storage parameters for each transaction table ?

          Followup:

if an entity has 100 attributes, it has 100 attributes -- it'll be a single
table.  hopefully the average row (or max row actually) fits on a block -- but
that is true whether you have 10, 100 or more columns

A full scan will take longer -- but you should be asking "what is the
performance impact if we split them out".  Well, you'll have two (or N) primary
key indexes to maintain, not just 1.  You'll have n*(m+1) additional IO's for
every row retrieve (where n = number of tables you choose, m = height of your
index).  It could really impact performance badly.

do no use longs, period.

when you use clobs, they are moved out of the table into their own segment when
they exceed 4000 bytes, the work is done for you, no need to factor them out
special.

I size objects by small, medium, large and extra large, using a locally managed
tablespace with uniform extents.  Alternatively, you can use a LMT with system
manged extents and not even worry about it yourself.

      Rating:
      5
Thanks  July 04, 2002
Reviewer:
      nvssk  from India
Thank you very mcuh.

      Rating: 5
Primary
      key  July 04, 2002
Reviewer:  nvssk
       from India
Hi Tom,
Thanks for answering the above questions.

 If a table D depends on table C ( D cant exist without C)
and table C depends on table B,
and table B depends on table A.

What is the best way to design the keys so that it results in better performance
? Can we composite primary keys for the above scenario ? what are the other
alternatives ?

Do we need to take any diversion, if table A have composite primary key  in the
above example?

          Followup:

You can certainly use composite keys if you choose.

The smaller the key, the faster the compare -- however, you have to outweigh the
costs of using a surrogate key and maintaining two unique indexes if you decide
to 'shorten' the key by using a single ID field populated via a sequence for
example. (there are tradeoffs).

I tend to go with natural keys if the natural key is 1 to 3 columns (and NEVER
changes), surrogate keys (id field populated via sequence) otherwise.

      Rating: 5
How about using
      clusters?  July 04, 2002
Reviewer:  Ian
      Matyssik  from Nihon
Hello,
     I understand that relative information better be in the same table. But
wouldn't it be better if we will separate our LOBs into different table, I say
that because LOBs are usially big and some times it takes time to store them
them or update them. During that time I understand that the ROW will be locked
and other information will be impossible to update. I hope you understand what I
am trying to say. So I was thinking if it would be any better to use clusters as
a PK on all related tables (separated for logical or organizational reasons) .
Could you please advice on that.

          Followup:

As I said before:

...
when you use clobs, they are moved out of the table into their own segment when
they exceed 4000 bytes, the work is done for you, no need to factor them out
special
.......

It doesn't matter that lobs are big, they are not stored in the table segment.

If someone is updating the LOB, yes the row will be locked -- but that is the
way it SHOULD BE.

I believe this is one table, with one row per thing.  LOBS do not take an
inordinate amount of time to modify -- the row is protected from other updates,
but reads of the data are not blocked.  I do not view this as an issue.

I would not introduce the complexity you describe, no.

      Rating: 3
Should LOBs be
      stored in different Tablespace  July 05, 2002

Reviewer:  Jerry  from NY USA
LOB Should be stored in different Tablespace , I think that is what Oracle
recommends . If that will be the case then there would be need for two tables .

          Followup:

NO, you are missing the point of how lobs are stored.

they are stored in 3 pieces

o in the table, there is a lob locator (pointer)
o in the same tablespace as the table, there is a lob index (more pointers to
all of the chunks)

o in another segment which may be in another tablespace (or not, upto you), are
the lob chunks.

When you create a table with a LOB, there are 3 segments (objects) created --
the table itself, the lob index (which will be in the same tablespace) and the
lob segment (which you control)

We already "broke" all of this stuff out physically.  There is NEVER a need for
two tables here.

Look at the create table statement for a lob as shown by exp/imp:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int, y clob );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> !exp userid=/ tables=t
Export: Release 8.1.7.3.0 - Production on Fri Jul 5 11:12:50 2002
(c) Copyright 2000 Oracle Corporation.  All rights reserved.
Connected to: Oracle8i Enterprise Edition Release 8.1.7.3.0 - Production
With the Partitioning option
JServer Release 8.1.7.3.0 - Production
Export done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set
About to export specified tables via Conventional Path ...
. . exporting table                              T          0 rows exported
Export terminated successfully without warnings.

ops$tkyte@ORA817DEV.US.ORACLE.COM> !imp userid=/ full=y indexfile=t.sql
Import: Release 8.1.7.3.0 - Production on Fri Jul 5 11:13:02 2002
(c) Copyright 2000 Oracle Corporation.  All rights reserved.
Connected to: Oracle8i Enterprise Edition Release 8.1.7.3.0 - Production
With the Partitioning option
JServer Release 8.1.7.3.0 - Production
Export file created by EXPORT:V08.01.07 via conventional path
import done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set
. . skipping table "T"

Import terminated successfully without warnings.

ops$tkyte@ORA817DEV.US.ORACLE.COM> !cat t.sql

REM  CREATE TABLE "OPS$TKYTE"."T" ("X" NUMBER(*,0), "Y" CLOB) PCTFREE 10
REM  PCTUSED 40 INITRANS 1 MAXTRANS 255 NOLOGGING STORAGE(INITIAL 524288
REM  FREELISTS 1 FREELIST GROUPS 1) TABLESPACE "USERS" LOB ("Y") STORE AS
REM  (TABLESPACE "USERS" ENABLE STORAGE IN ROW CHUNK 8192 PCTVERSION 10
REM  NOCACHE NOLOGGING STORAGE(INITIAL 524288 FREELISTS 1 FREELIST GROUPS
REM  1)) ;
REM  ... 0 rows

You already have all of the control you need over the location of the lob, one
table -- period.

      Rating: 3
You are the
      Real Oracle GURU.  July 05, 2002
Reviewer:
      A reader
Wonderful and Thanks for the detailed explanation , Once again you are the best
in the business.

      Rating: 5
Easier
      understanding of table design  July 06, 2002

Reviewer:  nvssk  from India
Hi Tom,
How can we achieve, easier developer understanding of the table design, without
one explaining the table design document / ER Model.
The problem reported is difficulty in understanding the keys and relationship.

When an application is going to be developed in Java/JSP in bottom-up approach(
table design first), Other than ER modeling, what specific features OO can be
used in  oracle ? what are the performance impacts ?

          Followup:

#1) telepathy I suppose?

#2) I would get database people and teach them Java/JSP.  Or I would get
database people -- let them do the database logic in finely tuned stored
procedures and have the java/jsp guys just call the plsql code (NO SQL in
java)...  (of course, the java jsp programmers will forget to use bind variables
and refuse to use prepared/callable statements more then once but hey -- at
least the sql would run good)

If you are setting out to write a high performance database application, write a
database application.  Don't write a "cool java/jsp" that happens to use a
database -- the database is going to be where you succeed or fail, don't work
AROUND it, with with it.

      Rating: 5
Network data
      transfer  July 08, 2002
Reviewer:  nvssk
       from India
Hi Tom,
If the table contains say 100 columns,and you access the whole object, Wont it
be lot of data on network,especially when its a web based application.

What is the performance impact due to this heavy data transfer ?Or Does it
matter at all?

Can it be solved by creating separate views( of the required columns) for the
usage based on the requirement and pickup the data only from the views.

Is there any impact on the joins?
Thanks for your help.

          Followup:

don't access the whole object then -- don't be lazy, don't code "select *"
(which in my humble opinion is a BUG in your code if you do!  The fastest way to
a broken program is to code:

select *    <<<<==== use of "*" should be limited to sqlplus testing

insert into t values (   <<<==== didn't list the columns
insert into t select ...   <<<<===== ditto

a simple drop and re-create with the columns in a different order or the
addition or deletion of a column in the table BREAKS your application)

SELECT only that data which you actually NEED.  Don't use "*" and this entire
conversation is rendered MOOT.

      Rating:
      4
  July 08, 2002
Reviewer:
      A reader
Excuse the stupid question. Will Oracle not read the entire data block where the
row is stored? What is the difference in the way Oracle read the data for a
select * and for select col1, col2..etc?

          Followup:

It isn't a stupid question -- it is actually more complex then you probably
anticipate and takes a couple of points to fully answer

o first, there is the "BUG" inherit in the "select *" code. A simple
addition or deletion of a column will generally "break" an application. One of
the glaring exceptions to this is PLSQL with implicit cursors.

   for x in (select * from t)

will not break but is unnecessarily inefficient.

o second, there are different paths the optimizer can take to answer a query
and it is very dependent on the columns you select.  For example:

big_table@ORA817DEV.US.ORACLE.COM> select object_name from big_table where
rownum < 100000;

Execution Plan
----------------------------------------------------------
   0  SELECT STATEMENT Optimizer=CHOOSE (Cost=212 Card=1459904 Bytes=36497600)
   1    0  COUNT (STOPKEY)
   2    1    INDEX (FAST FULL SCAN) OF 'OBJECT_NAME_IDX' (NON-UNIQUE) (Cost=212
C
ard=1459904 Bytes=36497600)

Statistics
----------------------------------------------------------
          0  recursive calls
         45  db block gets
        583  consistent gets
          0  physical reads
          0  redo size
    3228183  bytes sent via SQL*Net to client
       2534  bytes received via SQL*Net from client
         21  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      99999  rows processed

big_table@ORA817DEV.US.ORACLE.COM> select * from big_table where rownum <
100000;

Execution Plan
----------------------------------------------------------
   0 SELECT STATEMENT Optimizer=CHOOSE (Cost=577 Card=1459904 Bytes=150370112)
   1    0  COUNT (STOPKEY)
   2    1    TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=577 Card=1459904
Bytes=1503
70112)

Statistics
----------------------------------------------------------
          0  recursive calls
         18  db block gets
       1397  consistent gets
          0  physical reads
          0  redo size
   11972872  bytes sent via SQL*Net to client
       2534  bytes received via SQL*Net from client
         21  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      99999  rows processed

big_table@ORA817DEV.US.ORACLE.COM>

Look at the huge differences in consistent gets (blocks processed).  This is due
entirely to the one query being answered via an index and the other full
scanning.  If you include columns in your select list that FORCE us to goto the
base table -- when you don't need to -- you are forcing a less optimal query
plan on us

o third, look at the difference in data transfered!.  Almost 4x as much --
that could be huge.

o and the last thing I'll mention will be chained rows...  The impact from
this is subtle.  If you have chained rows -- and you do a select *, we'll be
forced to construct the entire row (which will take additional random IO's to
find the "end" block for your row - we have to piece it together).  Now, if you
select only the columns you need AND these columns are on the head rowpiece,
we'll never assemble the entire row -- we do just what we need to do to get your
data.

So, yes, Oracle reads an entire block -- but by selecting only the columns you
need you can:

o avoid reading that block in the first place (skipping the table access by
rowid step in an index range scan for example)

o avoid reading multiple blocks to construct the entire row

o open up better query plans, more query plans

o have less buggy code -- code that is much more resilient to change

o transmit lots less data over the network

and so on....

      Rating:
      4
  July 09, 2002
Reviewer:  Anil
      Sekhar  from New York
Great answer. Thanks Tom.

So if Oracle reads the entire datablock (when the data is not available from an
index) where the row is stored, why are you against limiting a table to the
frequently accessed attributes of an entity? If an entity has 100 attributes and
only 20 are accessed frequently, is it not a waste to read 100 columns when you
actually need only 10-15 of them? Sorry if I am beating a dead horse.

Also can you please tell more about the select * bug?

Thanks.

          Followup:

We read a block -- if you have 20 columns or 100 columns we read the block.

If you only need 20 of them, and we need to read the block, we'll still read the
block.  We'll only process 20 of them.

The select * bug is that if you have a program that does a select *, the * is
resolved at runtime.  If the number, order or types of the columns CHANGE over
time -- your program breaks.  If you selected ONLY those columns of interest --
only those changes that really truly affect you -- in fact affect you.  We can
ADD columns and you do not break.  If we drop a column and you don't select that
column -- you don't break -- if you did a SELECT * however, you would have in
your FETCH command a place for this missing column and you would break.

      Rating: 5
Parent child
      relationships  July 11, 2002
Reviewer:
      nvssk  from India
Lets say, There are tables P, C1, C2, C3, C4 and P is the parent of C1 ..C4.

If the application need to show C1,C2,C3,C4 togather for each row in P, what is
the best way to model it?
i.e,
P1
  C11
  C12
  C21
  C22
  C31
  C32,etc.

1. If you manage as Parent child, one need to write UNION between queries i.e,
select p.p1,c1.c11
from P,C1
where P.pc1=C.pc1
UNION
select p.p1,c2.c21
from P,C1
where P.pc1=C.pc1

Is this an efficient way?
OR
2.Can we have a table C in between P and C1,C2,C3,C4
where P maps to C (1:n) and C maps C1(1-1),C maps to C2(1-1) etc.
This way we can get all the required data in one query, no need of unions. But,
we will not be able to put referential constrains as C stored all primary keys
of C1,C2,C3,C4 in one column.

What is the right approach in both of the above ?

          Followup:

If P has a 1-1 relationship with 3 of the C* tables and either a 1-1 or 1-m (one
to many) with 1 of the C* tables, you can write it as:

select *
  from p, c1, c2, c3, c4
 where p.key = c1.fkey
   and p.key = c2.fkey
   and p.key = c3.fkey
   and p.key = c4.fkey

if any of the relationships are OPTIONAL, add the outer join (+) to the query.

If more then 1 of the child tables is 1-m with P, then you cannot do it with a
join (since if C1 has two rows that match with P and C2 has 3 rows -- you'll end
up with 6 rows -- a cartesian production).  You'll want to use a UNION ALL --
better then UNION

      Rating: 4
Data
      Model  September 13, 2002
Reviewer:  Mark
       from CA
Tom,
I have the following requirements and could you please tell me whether I can
achive this using the Logical model.
It is a technical Membership info Design.

1.One business Type can have multiple comanies under it.
2.One company Must Belong to only on Business Type.
3.One company can have multiple employees
4.we are interested in only the employees with Tech Membership and they have to
enroll every year.(we need the Historical Info also).
5.Each company Appoints multiple officers.
6.Each Business Type will have a Director who is selected from the Officers
table.
7.Director for each business type should be from that business type only.
I have designed the tables for the above requirements but I am having the
problems in enforcing the Relation between Business Type and Company and the
requirement 7 at the same time.
Table1:
Business Type
Columns
Bus_Type (Primary Key)

Table2:
Company
Columns
Company Code (Primary Key)
Bus_Type (Foreign Key (table1))

Table3:
Member
Columns
Member Id        PK
Enrollment Year  PK
Company Code     PK (FK to Table 2)

Table4
Officer Table
Officer Id   PK (FK to table3)
Year         PK (FK to Table3)
Company Code PK  (FK to Table3)
Officer Role (Non Key Column)

Directors
Bus_Type PK (FK to Table1)
Year     PK (FK to Table4)
Director Id  (FK to Table4 column Officer Id)
Directors Company (FK to Table4 Column Company Code)

But In the above model I can not enforce the Requirement 7
which is Director of the bus type Must belong to that bussiness Type.

Could you please tell me how to fit all the above requirements in One logical
model.

      Rating: 5
One or two
      tables?  January 10, 2003
Reviewer:  John
       from New York
Get back to how to design tables:
    I have a system where we need to generate alerts.  The alert will be stored
in an alert table, and end user will see them based on queries.  For each alert,
we need some basic information, such as alert code, and create date.  We also
need to store the detailed information for the event that causes the alert, and
that detailed information varies depending on the alert.  In another words,
different kinds of detailed information need to be captured for different alert.
 I am now considering three options, but  not sure what is the best way to
handle this.
Alerts are generated based on ALERT_DEFINITION table:

|alert_definition|-------<|alert_record|
(1 to M relationship)

The 3 options I am considering are:

1. Create one ALERT_RECORD table, and create columns that will be needed to
capture different types of alert detail information.  For columns that don't
apply to a particular type of alert, the value will be null.
    The problem is that the information is not stored in a logical way.  For
each row (alert) in the table, there will be lots of columns that are not
applicable to it, and therefore have a value of NULL.  There may well be
confusions down the road as to which column apply to which type of alert.

2. Create one ALERT_RECORD table for each alert type.  Information will be
stored in a logical way, but I will end up with 20 plus tables just for alert
detail information.  That will also require extra Java programming work to
present those information.

3. Create one ALERT_RECORD table, but only use it to store the basic alert
information (alert code, and create date).  Create another table called
ALERT_RECORD_DETAIL to store the detailed information.  The alert details will
be stored as Display_heading and display_value.  For alert detail like this:

display1          display2          display3
value1            value2            value3

will be stored in ALERT_RECORD_DETAIL as 3 records:

display_heading      display_value
--------------       --------------
display1             value1
display2             value2
display3             value3

and I probably need another column to store the display order of the heading.

This design will require lots of parsing in the Java code to present the
information.

|alert_definition|-------<|alert_record|--------<|alert_rec_detail|

Is there any other better way to handle this situation?  Thanks.

          Followup:

No version info.  Ok, assuming you are using the latest software:

option four
create table alert_record( alert_code int, alert_date date, alert_info XMLTYPE )

or, in earlier releases

option four
create table alert_record( alert_code int, alert_date date, alert_info clob )

if you don't need to have the data structured.  You can use Oracle text
(intermedia) to index the xmltype or clob for searching if that is needed.

      Rating:
      5
  January 29, 2003
Reviewer:
      Mirjana  from Barcelona

      Rating:
      4
Thanks.  February 04, 2003
Reviewer:
      John  from New York
We are trying to design our database to accomendate Oracle 8.1.7 and up.  So I
guess the XMLTYPE option is out.

The alert detail information I need to store and display later to users are:
product_id, packing_list, acknowledge_date, etc.  Those information have to be
stored in a structured way.  I don't know how can I display them if I use
XMLTYPE or clob?

          Followup:

original question was:

...
  I have a system where we need to generate alerts.  The alert will be stored
in an alert table, and end user will see them based on queries.  For each alert,

we need some basic information, such as alert code, and create date.  We also
need to store the detailed information for the event that causes the alert,
and
that detailed information varies depending on the alert.  In another words,
different kinds of detailed information need to be captured for different alert.

 I am now considering three options, but  not sure what is the best way to
handle this.

1. Create one ALERT_RECORD table, and create columns that will be needed to
capture different types of alert detail information. ...

2. Create one ALERT_RECORD table for each alert type....

3. Create one ALERT_RECORD table, (then a table with variable name/value
pairs).....

#1 is the easiest

#2 can be thought of as a variation on #1 -- think "views"

#3 would be avoided by me if at all possible.  People are going to want to query
"where product_id = 55 and acknowledge date between A and B or packing_list =
C".  Try doing that with variable name/values

So, I guess I vote for #1 with the #2 variation on a theme (views) to remove the
ambiguity of who needs what data...

      Rating: 4
store as
      segname  May 30, 2003
Reviewer:  Mark
      Rynbeek  from Cambridge, UK
Hi Tom,

thanks for the CLOB info I can find here. I have a question regarding the "store
as segname" option for CLOBS.

We have to design a table with biological data, proteins. These proteins have a
sequence of amino-acids, represented by a long letter string. On average,
they're not really long (only .05% is longer than 4000 characters) but still you
need the CLOB.

There seems to be some consensus here that CLOB data should be in its own
tablespace, physically but not logically. So you get something like :

CREATE TABLE protein
(         x                     INTEGER
        , Y                     VARCHAR2(50)
        , z                     CLOB
)
TABLESPACE t1
lob (z)
   STORE AS SEGNAME (TABLESPACE t2
                     PCTVERSION 0
                     CACHE READS
                     NOLOGGING
                    )
;

Does that make sense to you? What would be the benefit - can you tune t2
differently than t1 because you only store CLOBS in there?

I would think, regarding the percentage that most data would fit in the row
itself anyway that we should not bother about all this (but I'm lazy by nature)

Cheers,
Mark.

          Followup:

you'd have to ask the people wanting the separate tablespace "why".

the goal is to achieve even IO over all devices.  If segrating this way helps
that, that is good.

That nologging is a bit dangerous no?  do you not care about these proteins?
(there is a word I never thought I would use myself in a sentence here ;)

      Rating: 4
Question
      answered elsewhere  May 30, 2003
Reviewer:  Mark
      Rynbeek  from still in Cambridge
hi Tom,
regarding my previous question I think you already answered something likewise
on

http://asktom.oracle.com/pls/ask/f?p=4950:8:234994012547885035::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:386618744661,
Sorry about that, so never mind my question.
Your website is just too big !

      Rating: 5
Survey
      Design  July 21, 2003
Reviewer:  mo

Tom:

Can you give me your opinion on the following:  I have a survey form that I want
to put on the web for users.  The paper form has a lot questions and checkboxes.
 A user can check all checkboxes if he wants. For example, one question can have

Languages Spoken?
  English
  French
  Spanish
  Chinese
  Japanese
  Other _________

Here is my question.  If you are designing a table to store all this info is it
better to have one table with columns like:
create table survey as
(survey_id,
language1,
language2,
language3,
language4,
language5
language_other );

or

You would do 3 tables as (one main table and one lookup table and one child data
table that can have multiple records for each language:

create table survey as (
survey_id,
language )

create table language as (
lang_Code,
language)

create table survey_language as (
survey_id,
lang_code,
lang_other)

2.  The second question is that I have a lot of these questions so if I design
it in one table it will be huge.  If I break them into lookup tables and child
tables like optoin two, would it be better to put all code like languages,
colors, etc.  into one tables of codes or you would break it up to several
lookup tables one for colors and one for languages etc.

3. Can you tell me the advantage/disadvantages of each approach from oracle
stand point of view?

          Followup:

depends -- do you know for 100% sure how many lanaguages there will be.

surveys are an example where the "funky data model" as I call it -- where you
have this very generic model -- might make sense.

It allows very flexible storage -- it stinks for querying - you might have to
denormalize for quering later -- so you would have columns spanish, english,
french (not lang1, lang2, lang3).  so you can query "select count(*) from t
where survey_id = 55 and english = 1 and spanish = 1" to find all surveys filled
out by people that speak spanish and english.

      Rating: 5
table
      design  July 22, 2003
Reviewer:  mo

Tom:

Do I understand that you are in favor of option 1 for one big table for the
whole survey where i define one column for each checkbox value.  The table may
end up with more than 100 columns.

I was planning to create two other tables, one is a lookup table for all
languages, and other is where i store the survey_id and language code.  The
query would then be a two table join or three table join.  Is there a way oracle
recommends doing survey kind of design?

2.  ALso, if you have several lookup tables would you recommend combining all of
it into one (code,description) or create one table for each category.

          Followup:

1) do it in the funky data model and then DENORMALIZE if possible for reporting.

2) a table per lookup type.

      Rating: 5
another table
      design question  August 11, 2003
Reviewer:
      A reader
We have a situation where there are different types of data
(type being a categorization of a trow in a table) - let us
say these are "type1", type2.(in our case there are 10
or so such types)

Now each of these have same set of information stored
in a table (same columns - col1, col2, ....col20)

Is it better to store them all in one table (with a type
column in addition to the above columns ) or is it
better to separate them into 10 different tables (with
col1, col2, col3 etc..)

Most of the times these tables need not be queried together
in a join.

What are the pros/cons of creating multiple tables of
the form
type1_table (col1, col2, ...),
type2_table (col1, col2, ...) etc..
versus
one table
single_table( type, col1, col2...)

Thanx!

          Followup:

if they all store the same set of information -- then your "type" column is
nothing more then yet another attribute -- no more or less relevant then col1,
col2, col3

so, unless there is more then meets the eye here -- one table.

      Rating:
      5
thanx!  August 11, 2003
Reviewer:
      A reader

      Rating: 5
multi user
      problem  October 14, 2003
Reviewer:
      A reader
Hi Tom

For report query i am using temporary table.I am taking required information
from different table then inserting into a temporary table then report
generate.If anyone want to use this report then error message.so my question is

1)what is useful process to generate report in my mentioned criteria.
2)If my system is to generate serial number.what procedure should i take?
say i have to generate serial number for different person.in my form the
operator fillup the whole screen then he click in the generate button.

Everything is for multi user.

with regards,

          Followup:

use a real global temporary table

no concurrency issues whatsoever

read about them in the sql reference guide "create global temporary ...."

      Rating: 5
Column
      allocation and performance issues.  October 21, 2003

Reviewer:  Alvin  from Philippines
I'm in a system where table columns are defined as

Create table T
( c1 varchar2(2056)
, c2 varchar2(2056)
);

when the data that's actually stored are never more than 100 characters.

1. Is there any performace issue on such allocations ? Or columns being varchar2
will be internally optimized by oracle.
2. How do i measure the impact of such allocations as compared defining the
table as such

Create table T2
( c1 varchar2(200)
, c2 varchar2(200)
);

          Followup:

1) i wrote about this in my new book "Effective Oracle By Design".  Some of the
things I said about such nonsense was:

...
In addition to using the proper base datatype such as number, date, or string,
you should also use the most specific type you can. For example, use
VARCHAR2(30) for a field that is up to 30 characters in length; do not use
VARCHAR2(4000).

Ask Tom

"I work with a modelers group. My modeler would like to define every VARCHAR2
field with the maximum length, which means that a table with 20 VARCHAR2 fields
will all be defined with a maximum of 2000 or 4000 bytes. I tried to talk to him
about the reasons we identify data with correct lengths and names in order to
understand what we have in our database. He told me that it doesn't matter,
since Oracle just stores the length, etc., and there is no overhead. I don't
believe this is true, but have been jumping between so many databases that I
cannot find a document on the internals of Oracle storage. Can you help me out
here with this question?"

My gut response was, "This is your data modeler, my goodness!" They are the ones
who are supposed to be telling you that it is vital to use the appropriate
length when defining fields! That is their job. Let's just forget about things
like storage for a minute, why don't we ask him:

. What is going to happen when users pull this up in a query tool that formats
each field based on the width of the column in the database? They'll see one
column and need to scroll way over to see the second, the third, and so on.

. Say the code prepares a query that selects ten columns that are VARCHAR2. The
developers, for performance, would like to array fetch (very important). They
would like to array fetch say 100 rows (very typical). So, you have 4,000 ´ 10 ´
100 = almost 4MB of RAM the developers must allocate! Now, consider if that were
ten VARCHAR2(80) fields (it's probably much smaller than that). That's about
78KB. Ask the data modeler how much RAM he is willing to kick in for this
system.

. Now, the developers start to build a data-entry screen to put data into the
database. Wow, that code field can be 4,000 characters long and that first name
can be 4,000 characters long. How the heck is anyone going to know what sort of
data can really go in there?

Tell your data modeler group members that they need to consider the length as a
constraint. Just as they use primary and foreign keys, they should use the
proper and correct length on fields. You can always expand a field via a command
like alter table t modify c varchar2(bigger_number). There is no reason to use
the maximum length everywhere. It will hurt the applications you develop,
because they will mistakenly allocate many megabytes of RAM. Just think of the
array fetch example with an application server. Now, it's not just 4MB; it's 4MB
´ number of connections. You are talking some real memory here for a single
query, and you'll be doing a lot of them at the same time.

End Ask Tom

A CHAR(2000) will consume 2,000 bytes of storage whether you put in the letter
a, the string `hello world', or 2,000 characters. A CHAR is always blank-padded.
Additionally, are you thinking about using an occasional index in your system?
If so, beware of storage-related problems. Consider the following:

tkyte@ORA817.US.ORACLE.COM> create table t ( a varchar2(4000), b
varchar2(4000));
Table created.

tkyte@ORA817.US.ORACLE.COM> create index t_idx on t(a);
create index t_idx on t(a)
                      *
ERROR at line 1:
ORA-01450: maximum key length (3218) exceeded
I've noticed that 9i seems to have magically improved upon this CM
NOTE: In Oracle9i, the maximum key length is larger - but the restriction still
exists.  For example, an index on T(a,b) would raise: ORA-01450: maximum key
length (6398) exceeded in Oracle9i.

My system has an 8KB block size. I would need to use at least a 16KB block size
to index a single column, but even then, if I tried to create a concatenated
index on T(A,B), it would fail there!

The same holds true for your numbers and the new Oracle9i TIMESTAMP datatypes:
When appropriate, use scales and precisions on those fields in order to better
define your data integrity and to give applications that much more information
about the data itself.

In short, never be tempted to use anything other than a DATE or TIMESTAMP
datatype to hold a date value, and never be tempted to use a VARCHAR2 to hold a
number. Use the appropriate and correct type for each to ensure maximum
performance, and more important, to protect your data integrity.

2) it won't affect raw performance, the costs of doing such a thing are found
elsewhere.

      Rating: 5
Order of column
      in a table  October 22, 2003
Reviewer:  Ashiq
      Shamsuden A  from Trichy, Tamil Nadu, India
Hi Tom,

  When you create a table , how you design it ? For example if I've table and
it's going to have  100 columns and the table will have all kinds of datatypes.
It'll be having composite primary key(of three columns).

 Total of 100 columns ,the datatype what i'm going to have is

  44 --> varchar2's
  20 --> date's
  35 --> number's
  1  --> clob

  How to order the columns for the creation , so I should get benefit by design
properly.

          Followup:

put the most frequently accessed columns "first"

put the columns most likely to be null "last"

but most importantly -- don't over analyze it.  it won't make a tremendous
amount of difference at the end of the day.

I just always put the primary keys first (convention) and the follow with the
columns as they "make sense".  when I think of a person for example:

person( unique_id primary key,
        first_name,
        last_name,
        middle_initial,
        address,
        .....

the data just follows in the "natural order" i see it in my head.

      Rating:
      5
Middle?  October 22, 2003
Reviewer:
      A reader
Tom,
I know one third party application that has all the key columns exactly at the
middle of the column list, in a table. The application is supposed to work with
any database. How would you rate this?
Thanks

          Followup:

confusing but not a big deal

      Rating:
      5
Thanks.  October 23, 2003
Reviewer:
      Alvin
I have both your books and i just read the first chapter of the new book.

Followup on my earlier question.

How would oracle store varchar2(2000) and varchar2(30) in the block buffer cache
? won't the former entail additional overhead of any kind ?

My developers are a tad greedy on allocations and i want to show them the
effects of such (if any).

          Followup:

they will not be stored any differently, no.

the effects are all in the client, in the loss of data integrity, in the loss of
meaning, in the sloppiness of the implementation.

      Rating: 5
cool info on
      column ordering thanks  October 23, 2003
Reviewer:
      Kevin Meade  from Norwich CT, USA
Thanks Tom, may I offer my 2 cents;  I would note regarding column ordering
these clarifications: 1) if the reader has read this thread, you have explained
why primary key comes first (some modelers also suggest foreign keys come next
(eh..)), 2) also you have explained why "most used" columns come second, 3) I
would mention that "likely to be null" columns at the end of a row is as I
recall because null columns at the end of a row take no space on the row but
null columns between columns with values take 1 byte (ooh boy jumping at the
space savings).

As always though you hit the nail on the head with "don't over analyze, do what
makes sense".  I can't say I every paid much attention to most of this as there
are always more important fish to fry.  Perfection is never the goal (ooh, did I
say never?).

My I also suggest, there is always alphabetical order (hmmm...).

          Followup:

i remember once working as a coder -- doing some Ada programming.

the "senior reviewers" wanted me to code all subroutines in alphabetical order,
that last comment reminded me of that.  I hated that!  (i made them really made
by using "renames" in ada to give the appearance of compliance without really
reordering any code -- i just added AA_ AB_ AC_ to the front of each routine and
then renamed AA_proc1 to proc1 :)

      Rating: 4
clob
      clarification  October 23, 2003
Reviewer:
      Rob  from Pittsburgh, PA
Tom:

When you say:

when you use clobs, they are moved out of the table into their own segment when
they exceed 4000 bytes

Does this mean that all clobs from each row are moved, or just the ones where
the the clob in a particular row exceeds 4000 bytes.

Thanks,

Rob

          Followup:

just the ones that are bigger then 4000

      Rating: 5
Storing Real
      Numbers and Pseudo-Numbers  October 23, 2003

Reviewer:  Dave  from Colorado
Quote: "... never be tempted to use a VARCHAR2 to hold a
number"

When storing such entities as Zip codes, Social Security Numbers, credit card
numbers, telephone numbers etc., I have always used Char(), or VarChar2(),
because my view was that these are not "real" numbers.

They are codes for which all of the characters happen to be numeric, and any
operation that the system is going to perform will be character-based. For
example, formatting a telephone number as (999) 999-9999, or viewing the last
four of an SSN. We're certainly never going to perform any algebraic op's on
them, like adding one zip code to another, and we can easily apply check
constraints to ensure that ...

Translate(SSN,'0123456789','0000000000')='000000000'

... for example.

What's your opinion, Tom? Does this seem like a valid distinction, "real" and
"pseudo" numbers?

          Followup:

they are not numbers as you said.  zip codes in particular are "postal codes" in
general and are alpha numeric in reality.

so, since they are not numbers, store in a string -- you'll never treat them as
numbers so it's ok.

      Rating: 3
Length of
      primary key  October 31, 2003
Reviewer:  Kunjan
      Mehta  from Bangalore, India
Hi Tom,

We are using surrogate keys as the primary keys. We are using oracle sequences
to generate surrogate keys.

I believe, we can generate upto Number(27) through the sequences. is it correct?
If not, is there any limit on the largest number being generated by sequence?
What is it?

Is there any desired maximum length for the primary key? (e.g. it should not be
more than 19 digits or something like that)?

We are desinging a customized ERP. Is it advisable to use a single sequence for
all the surrogate keys across the entire application? (approx 1000 tables in
total) or should each table have one dedicated sequence or each logical group
(say tables for Accounts, tables for materials etc.) should use a dedicated
sequences each?

(we're using 9iR1 as database.)

          Followup:

ops$tkyte@ORA920LAP> select rpad('9',27,'9')/60/60/24/366/1000 from dual;

RPAD('9',27,'9')/60/60/24/366/1000
----------------------------------
                        3.1623E+16

27 9's is a big big number.  that shows it would take 3.16e16 YEARS to exhaust
the sequence generating 1000 numbers a second

the number of digits is not going to have much bearing on anything.  it'll be
just fine to use a single number column as big as it gets.

You should not have a single sequence.  see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2985886242221

      Rating: 3
not exhausting
      sequence, but what about latches  October 31, 2003

Reviewer:  freek  from Belgium
Tom,

Using 1 sequence for an entire application wouldn't exhaust the sequence, but
wouldn't it be still better to use 1 sequence per table / column to reduce
waits?

          Followup:

you know what, i didn't read the entire thing.  thanks -- i'll finish my answer
now :)

      Rating: 4
What about
      Table APIs  November 03, 2003
Reviewer:  A P
      Clarke  from London, England
Towards the top of this thread you said:

>> SELECT only that data which you actually NEED.

This is my preferred approach (got burned by SELECT * a long while back).
Howvever, there are a number of respected practioners out there who recommend
using Table APIs i.e. PL/SQL encapsulation packages that act as the sole method
of interacting with a table.  The advantages are security and (allegedly)
performance - no multiple versions of the same SELECT statement knocking around,
just one "tuned" one.

However, it seems to me that the SLCT() functions in Table APIs - such as the
ones generated by Oracle Designer - must always select every columns, unless we
code a SLCT()  function for every single permutation of column, which I think is
impractical.

I have survived a long time without using Table APIs but I got doubts when I
read Steven Feuerstein's PL/SQL Best Practices book.  What do you think?  Can
table APIs be reconciled with "only SELECT what you need"?

Cheers, APC

P.S.  I'm not trying to set up some PL/SQL version of "Clash of the Titans" here
:P

          Followup:

well, table API's to me are somewhat weak in the first place -- or just have
special use cases (simple CRUD screens/data entry).

I mean, NONE of my applications -- none of them -- use simplistic "select ...
from t where ....".  They all access multiple tables -- all of the time.

How many times are your needs actually met by "select * from emp"?

This page you are looking at is built from some 7 different tables (if I don't
include the html db stuff that happens as a side effect).

A simple table API in my world is virtually useless.

I use API's -- but, they are all special purpose API's (asktom is built on such
an API).  The queries are specificially designed to answer a specific question.
The API's to modify all touch multiple tables.

I've personally never had a use for single table API packages -- maybe others
can comment and change my mind (but i doubt it -- beyond simple single table
CRUD screens they only seem to make things harder)

      Rating: 4
Phew! I thought
      it was just me  November 03, 2003
Reviewer:  A P
      Clarke  from London, England
>>  How many times are your needs actually met by "select * from emp"?

Um, almost never.  That was a rhetorical question, right?

The only time I have dabbled with something like Table APIs was writing some
database procedures that used REF CURSORs to assemble resultsets for processing
by an EJB web site.  But, like you said about this site here, the queries were
"specificially designed to answer a specific question".  Apart from anything
else, such an approach strikes me as a sensible division of labour - I knew
Oracle, they knew Java.

Anyway, thanks for putting my mind at rest with regards to generic Table APIs.

Cheers, APC

      Rating: 5
Table
      APIs  November 03, 2003
Reviewer:  Tony
      Andrews  from London, UK
Makes sense to me, but unfortunately not to those I report to at the moment.  I
just wrote a table API for our project (my first for this one).  My procedures
were based on business logic.  For example, when you create a new (let's say)
salary record for an employee, you have to "close" the previous record by
setting the end date to the day prior to the new record's start date.  So my
routine did that.  It was rejected by my team leader as being "not modular
enough".  She said I should write one procedure to insert the new record, and
another to update the old record.  The client application will then call them in
sequence...

          Followup:

team leader needs to read the next chapter in their programming book....

;)

it doesn't seem to make SENSE to do what she is describing.

      Rating: 3
This must be
      some new definition of the word "modular"  November 03, 2003

Reviewer:  A P Clarke  from London, England

Maybe it's like playground slang, where children take a normal word and
pronounce with such a sneer as to make it mean "stupid" or some variant thereof.
 As in, "You still like Girls Aloud?  You must be really _modular_!"

Seriously, the major defensible reason for building Table APIs is to encapsulate
business logic.  To design a Table API that is dependent on the client to
enforce the rules is, er, counter-intuitive.

Good luck, APC

      Rating: 5
Exactly the
      opposite is better  November 16, 2003
Reviewer:
      Kevin  from Connecticut USA
Hi Tom, I'm enjoying the thread, can't blame the manager for wanting to be
modular, but can't say I like the approach at all either.  If I may interject my
thoughts too:

Tony, your manager should consider that hiding the behaviour is often the best
approach.  Requiring an app to do "redundant" work is a prescription for
failure.  In my shop I require all DBAs and Developers to hide things like this.
 We prefer to have the database automatically recongnize the need to update
these kinds of related changes.  Perhapes you should consider constructing the
two modular components as she requests but then instead of requiring the client
to call both, point out that the client's job is to tell the database it wants a
new record, and that it is really the database's job to automatically close the
prior one.  After all, this is a data behaviour built into your database design
and as long as you decided you have to have this behaviour, you might as well
let the database enforce it.  Should you decide to change this design, you would
need to change client apps too and that is always bad.  If the database is in
control, then there is no way for the client to forget it, do it different, or
do it wrong, and you won't need to recode everything multiple times for each app
that might decide in the future it wants to create new records because the data
rule is in only one place, the data base.  This means, always enforced, always
right, always the same, no developer required (I didn't mean that like it
sounds).

In particular, we enjoy liberal use of views and instead of triggers in our goal
of database managed rules.  Our client apps are much lighter this way and there
are vastly fewer data goofups to boot.  Good luck.

November 03, 2003 Brian -- Thanks for the question regarding
      "Library Cache", version 8.1.6

      You Asked
select sum(pins), sum(reloads) from V$LIBRARYCACHE
SUM(PINS) SUM(RELOADS)
287980714 640661

This means that only a small amount of sql is not being held in the library
cache.(which is a good)

As we are not fully using bind variables (ie our queries are not being cached)
why is this so high?

Bryan

      and we said...
Well, while 640k sounds big (ask bill gates, I think he said once "640k should
be enought for anybody") -- in light of the number of pins, its really rather
small.

As a pct of pins, it is very very small.

You must either:

o have some procedures you run infrequently.  they get called in, aged out and
called in again.

o do things that force a flush (invalidation of code, grants, etc)

In light of the fact that you do not make effective use of bind variables, this
is quite simply "noise", your problem is bind variables, not that 0.22% of all
PINS resulted in a reload.
Review & Followup

      Rating: 5
Pending
      question...  November 28, 2001
Reviewer:  Andre
      Whittick Nasser  from Rio de Janeiro, Brazil
I got this pending question... Now that you mentioned it...

To the best of my knowledge, the first submission of an SQL statement is
considered a "load". From then on, because of invalidations -- say, because of
modifications of a dependent table -- that statement is a candidate to be "aged
out" in the LRU list.

What I never found reference about is:

If it's a reload, it's assumed there's an "old" -- "stale", as you like --
version that is "re-validated" in the shared pool when it's issued again. I
assume here that the process of reparsing is somehow optmized since the "stale"
code is already in the pool.

Otherwise, there would be an entire "load", which by definition is a whole
submission, parse, etc.

It seems there is an intermediate "aged-out state" where the code is there and a
new submission will not have to "load" it fully, hence the term "reload".

Further, if the code were completely aged out, the server would never know about
the old version and would never know a new issue of the statement was coming in
and could never compute this as a reload but rather as a new load.

Would you please comment ? Any links ?

          Followup:

It's not necessarly optimized -- its just that there are lots of bits and pieces
of stuff all over the place, we age different pieces of memory at different
times for different reasons.

Consider (a single user system here, I'm the only one doing stuff)

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

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int );

ops$tkyte@ORA817DEV.US.ORACLE.COM> select namespace, pins, reloads from
v$librarycache where namespace = 'SQL AREA';

NAMESPACE             PINS    RELOADS
--------------- ---------- ----------
SQL AREA            208411        494

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1 );

ops$tkyte@ORA817DEV.US.ORACLE.COM> select namespace, pins, reloads from
v$librarycache where namespace = 'SQL AREA';

NAMESPACE             PINS    RELOADS
--------------- ---------- ----------
SQL AREA            208627        495

Now, i had run this test a couple of times -- the reloads are up by 1 because
the plan for "insert into t..." was reloaded, we invalidated it -- but it was
still "there" (no reason to give up something if we don't have to).

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

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int );

ops$tkyte@ORA817DEV.US.ORACLE.COM> select namespace, pins, reloads from
v$librarycache where namespace = 'SQL AREA';

NAMESPACE             PINS    RELOADS
--------------- ---------- ----------
SQL AREA            208775        495

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1 );

ops$tkyte@ORA817DEV.US.ORACLE.COM> select namespace, pins, reloads from
v$librarycache where namespace = 'SQL AREA';

NAMESPACE             PINS    RELOADS
--------------- ---------- ----------
SQL AREA            208787        496

see -- another "reload" of the plan for that insert -- its a fresh plan -- its
a totally different table t

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1 );

ops$tkyte@ORA817DEV.US.ORACLE.COM> select namespace, pins, reloads from
v$librarycache where namespace = 'SQL AREA';

NAMESPACE             PINS    RELOADS
--------------- ---------- ----------
SQL AREA            208793        496

no, no reload, its OK...

so, its a reload -- not a load in this case -- even though it looks a TON like a
load, not a reload (its an entirely DIFFERENT table!  You could even drop table
T and create a synonym T for some other table and see similar effects....

      Rating: 4
Not clear
      yet...  November 28, 2001
Reviewer:  Andre
      Whittick Nasser  from Rio de Janeiro, Brazil
Tom,

I was already aware of the idea behind your example. What is not clear to me yet
is:

"How does Oracle know it's a reload, not a load?"

The reload count goes on increasing every time you invalidate the SQL statement
and re-issue the statement.

OK.

But, is it entirely issued again -- as if it was never there before, in which
case this would be a load -- or the code is in there still and your DDL only
invalidates it and the statement continues in there like some garbage ?

In short, a load means a complete age-out, as if the code was never there and a
reload means you are somehow reusing it ?

          Followup:

Because the thing is invalidated, its still up there, just not valid.

as for
"How does Oracle know it's a reload, not a load?"
to you and me -- it looks like a load, smells like a load but its not.  Those
insert into t statements are totally 100% different -- except for their "name",
their "signature", hence the one just overwrote the other -- it was "reloaded".

      Rating: 5
Just to wrap it
      up...  November 28, 2001
Reviewer:  Andre
      Whittick Nasser  from Rio de Janeiro, Brazil
So... I think I got it.

Plase confirm:

A load is a complete reissuing of the statement, when there is not the "stale",
invalidated code in the shared pool respective to it. So, a reolad ocurrs when:
a) it's the first time the statement is submitted;
b) it was invalidated and aged out of the shared pool and the same statement is
re-issued.

A reload is  a kind of "re-validation" of the code that is "stale", invalidated,
 STILL in the shared pool. This re-validation is caused by the reissuing of the
respective statement. Oracle keeps track of this code to "know" it will be
elegible for reuse, that is, a reload.

Thanks once more !

      Rating: 5
... my
      typo...  November 29, 2001
Reviewer:  Andre
      Whittick Nasser  from Rio de Janeiro, Brazil
In my review just above, instead of "...So, a REOLAD ocurrs when:...", please
read "...So, a LOAD ocurrs when:..."

      Rating: 4
How to
      invalidate?  May 09, 2002
Reviewer:  Doug
       from Rye, NY USA
What are some other ways statements could be invalidated?
I'm certainly not dropping and re-creating tables in my production database so
what are some other causes of the invalidation that leads to a reload?

          Followup:

changing of permissions...
changing of indexes on tables that are involved in views...
analyzing an object...
running out of room in the shared pool....

      Rating:
      3
  November 03, 2003
Reviewer:
      Dillip  from CA, USA
In a database, I am getting a high SQL invalidations

NAMESPACE             GETS    GETHITS  HIT RATIO       PINS    PINHITS  HIT
RATIO    RELOADS INVALIDATIONS
--------------- ---------- ---------- ---------- ---------- ----------
---------- ---------- -------------
SQL AREA            271018     262824      96.98     849407     671960
79.11     161025        168674

the shared_pool parameters are

shared_pool_reserved_size 33554432
shared_pool_size 671088640

I tried changing the
cursor_sharing force
but still I see so many invalidations, what could be causing this ?

          Followup:

flushes, aging, alters, statistics gathering, grants, many things.

all of those other numbers are pretty small -- doesn't look like much activity?

has it been identified as a performance issue?

November 03, 2003 Omar -- Thanks for the question regarding
      "Control the Oracle Job", version 8.1.7

      You Asked
Dear Tom,

I create an oracle job to run every day at 12:30 pm (Egypt time) as

sql> declare
 job_no     number;
 begin
 sys.dbms_job.submit(job_no,'customer.APPEND_OPUS_PARTENER(5);'
,'sysdate+7/24,'sysdate + 7/24');
 commit;
 sys.dbms_output.put_line('job = '||to_char(job_no));
 end;

The job is running ok, I have one problem which is When the job is failed it
tries again to run correctly many times and this make a problem for me,
The job is sending data from Insurance technical system to the GL of Oracle
Financials,
For example if I have 300 new policies (Records) and it is required to send
theses data to oracle financials by this job the job is running and when there
is an error in the record 70 the job try to send the records again but evey time
it fails at the record 70  This behavioure make the first 69 records to be
transfered to the GL 10 times , I want a way to make the job stop when it fails
and not to try to Resend the data again, Could I have a line in any Oracle Alert
file (I am a Developer not an Administrator),

Regards,

Omar.

      and we said...
Hmm, sounds like you are committing in there?  why?

think about it -- unless you develop some logic YOURSELF to remember what
transactions you have processed -- how can we??  We are just running code, your
code committed work.  Re-running your code is causing the work to be done again.
 Seems to me that YOU need to flag the records as "already processed"????

also, unless you want this job to slip over time, your next should be:

  trunc(sysdate)+1+12/24+30/24/60

(did you really mean 12:30pm or 12:30am??  yours would be 7am, but anyway....

this is something you need to fix, your choices are either

o flag records as processed so you know not to process them again

o make this one big transaction so that when it fails it fails ENTIRELY

o catch the failure via an exception block, rollback, log the error and commit
that, continue to the next record.  have some process in place to check this
failed "record" table you are populating and fix them.

Review &
      Followup

      Rating:
      3
Explanation  November 03, 2003
Reviewer:
      Omar  from Egypt
Yes I do commit there it is the only way to do the job !,
I can not create a column to indicate whether the record is processed or not
because simply I extract data from a view that is collects data from many tables
in the technical insurance system,
Yes the job is slip over time How could I prevent it from slipping?
Would you explain
" trunc(sysdate)+1+12/24+30/24/60 " in details,

Thanks Tom.

          Followup:

why is it the "only way to do the job".

seems you have a transaction "post these things from db1 to db2".  by committing
part way through -- you have introduced the very issue you are worried about.

you'll need to come up with some LOGIC that allows you to RESTART in the event
of a failure.  There is nothing the database can do since you have subverted the
entire transactional integrity process here by committing after each thing.  YOU
must remember "oh, already did that -- don't do it again".  perhaps you need an
addition audit trail table to help you out there.

my date function takes TODAY, resets it to midnight (trunc) - adds 1 day to it
(midnight tomorrow) and then adds 12 hours and then add 30 minutes.

      Rating: 4
Good
      suggestion  November 04, 2003
Reviewer:
      Omar  from Egypt
Ok, It seems to be a good solution for the problem, I will extract data from a
table instead of a view and I will add an additinal column indicates whether the
record is sent or not, If there is any failure I will Restart the proccess from
the last record I sent,
Ok Thanks Tom.

November 04, 2003 Sandra -- Thanks for the question regarding
      "Performance and Oracle Sequences", version 8.1.7

      You Asked
1. Are there any performance concerns with using one Oracle sequence to generate
primary key values across a couple thousand tables? We started to go down the
path of creating a separate Oracle sequence for each table. Considering the fact
that we can cache sequences on the server, I'm not convinced there are
performance issues with concurrent, heavy usage of a single sequence.

2. What is the difference between caching a sequence (CREATE SEQUENCE
<owner>.<sequence_name> CACHE n) and pinning a sequence (execute
sys.dbms_shared_pool.keep(<owner>.<sequence_name>,'Q')?

      and we said...
1) there could be some contention introduced by using a single object for
everything but its more of a feeling I have then anything I can point to on
paper.

There is only one sequence cache latch/instance - so regardless of whether you
have 1 or 1,000 that won't change.  I guess the "hot" spot would be sys.seq$ --
but even thats probably not true since that table is very very very small in any
case.

Oh bother -- i knew it would come to this.  Ok, I ran a benchmark.  The setup:

o I created ten sequences, seq1, ... seq10
o I had a script:

declare
    l_val number;
begin
    for i in 1 .. 100000
    loop
        select seq&1..nextval into l_val from dual;
    end loop;
end;
/
exit

that would just select from them.

o I ran this csh script:

#!/bin/csh -f

sqlplus perfstat/perfstat <<EOF
exec statspack.snap(10);
exit
EOF

sqlplus / @selectseq 1 &
sqlplus / @selectseq 1 &
sqlplus / @selectseq 1 &
sqlplus / @selectseq 1 &
sqlplus / @selectseq 1 &
sqlplus / @selectseq 1 &
sqlplus / @selectseq 1 &
sqlplus / @selectseq 1 &
sqlplus / @selectseq 1 &
sqlplus / @selectseq 1 &

wait

sqlplus perfstat/perfstat <<EOF

exec statspack.snap(10);
exit

EOF

sqlplus / @selectseq 1 &
sqlplus / @selectseq 2 &
sqlplus / @selectseq 3 &
sqlplus / @selectseq 4 &
sqlplus / @selectseq 5 &
sqlplus / @selectseq 6 &
sqlplus / @selectseq 7 &
sqlplus / @selectseq 8 &
sqlplus / @selectseq 9 &
sqlplus / @selectseq 10 &

wait

sqlplus perfstat/perfstat <<EOF
exec statspack.snap(10);
exit
EOF

So, 10 guys selecting at first the same sequence and next 10 guys selecting 10
different ones.... statspack says....

           Single Sequence                      Ten Sequences
           ---------------------------          -----------------------
run time:  3.47                                  2.42 minutes

single sequence Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
enqueue                                           116,126      163,747   88.42
log file parallel write                            14,608       20,176   10.89
latch free                                            909          989     .53
control file parallel write                            67          146     .08
log file sync                                          12          108     .06
          -------------------------------------------------------------

ten sequences Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
buffer busy waits                                   2,589       16,183   40.70
log file parallel write                             1,801       14,092   35.44
latch free                                            973        7,826   19.68
db file parallel write                                 99        1,172    2.95
log file sync                                          12          248     .62
          -------------------------------------------------------------

So, I would say there will be a difference based on this, a potentially big
difference (good to know that my "gut" can be right sometimes ;)

2) the difference there is that the CREATE SEQUENCE CACHE causes us to cache
values for the sequence in memory.  If you cache 20 -- we update seq$ every 20
requests.  If you cache 200, we update the physical table seq$ every 200
requests.

The pinning affects how the sequence would be treated in the shared pool.  If
not pinned (and I'm not a big fan of pinning, if you need to pin -- it indicates
a fundemental problem that should be fixed), the sequence is subject to aging
out of the shared pool.  So, if you don't touch it, it'll be aged out -- and you
can "lose" some values.  For exmaple, if you cache 200 -- select 100 values (so
100 are left in the cache) and we decide to age the sequence out -- you'll have
"lost" those 100 values.

Hope that helps...
Review & Followup

      Rating:
      3
  March 07, 2002
Reviewer:  Harold
      Flores  from Managua, Nicaragua
If i pin some sequences on the SGA and the cache is 0 i still losing numbers of
the sequence???

What can i do for not to lose values that i already have cache on the SGA??

Regards

          Followup:

You know -- I never understand why people worry about "losing" the numbers...
A simple rollback will "lose" a number.  A shutdown will.  Losing 19 numbers
(with a default cache of 20) is no big deal (you are not going to exhaust that
sequence, not in many dozens and dozens of years).

If you have cache = nocache, you will of course not "lose" any, you don't have
any cached to lose.  If you pin a cached sequence, you'll lose some on shutdown
but not otherwise.

      Rating:
      3
  March 08, 2002
Reviewer:  Harold
      Flores  from Managua, Nicaragua
We use sequence for account, if we lose a single number that is a big deal.

          Followup:

Then you cannot be using a sequence for as I said -- a simple rollback will in
fact lose one!!!  An instance failure can lose one!!!

SEQUENCES are not gap free under ANY circumstance -- EVER.  They are 100%
assured to have a gap at some point.  100%....

So, if in fact its a "big deal" due to some artificial external constraint, you
are already out of luck.

      Rating: 4
Thanks a
      bunch!  March 08, 2002
Reviewer:  Sandra
      Dawson  from Orlando, FL
Very useful info. Ran the benchmark on our machine resulting in the same "Top 5
Wait Events". Looks like there's some locking issues when all ten jobs utilize
the same sequence. The time difference was not quite as big in our environment,
but it's certainly enough to convince me. Thanks again!

      Rating:
      3
  April 19, 2002
Reviewer:  Ivan
       from Columbus, OH USA
The results are valid for a particular heavy usage test scenario, but how can
you measure the "overhead" of maintaining the questioner's couple thousand
sequences?

Sequences in practice are arbitrary numbers.  Not withstanding your results, it
is simpler to have a single arbitrary number generator than hundreds of
arbitrary generators.  Less is blessed.  Most sequence objects also usually
require a synonym and privileges.  All of this stuff must be kept in the
catalog, loaded into the SGA, backed up, recovered, created, whatever.

Your test grabbed thousands of numbers per second.  If there are a couple
thousand tables in this database, it is likely that only a few sequences are
really consistently "heavy usage".  Many sequences are rarely or lightly used in
a day's work.  The issue is different, when you compare accessing 1000 sequences
for one value versus accessing one sequence for 1000 values.

I would suggest, for new sequence needs, that using a common sequence be looked
at.  An application database of a couple thousand tables likely could work well
with a few score sequences.  The only exceptions could be for sequences with:
   1) consistently "heavy usage"
   2) for user purposes: numbers that are seen (e.g., Order Number), or desire a
somewhat comfortable consistent assignment

One small disadvantage of a common general sequence is that bigger number values
tend to use more storage space in a table.  Also, if a sequence "goes berserk"
by some error, its effect and resolution would be more wide ranging.

          Followup:

Well, I disagree with most everything you say EXCEPT that "in general, less is
best".

The question is:

Are there any performance concerns with using one Oracle sequence to generate
primary key values across a couple thousand tables?

You say:

<quote>
If there are a couple
thousand tables in this database, it is likely that only a few sequences are
really consistently "heavy usage".
</quote>

Well the problem with that is -- there is ONLY one sequence, not a few, one.
 So, there is ONE sequence that is consistently "heavy usage".

The query is "are there any performance concerns".  The answer is (apparently
and provably) YES, YES there are and here they are.  Do with it what you might.

With thousands of tables -- it sounds like a big database.  Big databases have
lots of users.  The ENTIRE PURPOSE, CONCEPT of a sequence was to improve
concurrency.  here we see that if lots of people use it at the same exact time,
there are contention issues.  You just possibly multiplied your contention
issues by THOUSANDS.

You might think it is "simplier" -- I don't agree in general.  Contention is a
hard thing to fix *after the fact*.  It is easier to fix it up front (consider
having to retrofit the code in 6 months to use different sequences.  Not so
*simple* anymore)

We are not comparing:

<quote>
The issue is different, when you compare accessing 1000 sequences
for one value versus accessing one sequence for 1000 values.
</quote>

as you say.  We are comparing:

accessing 1,000 sequences vs
accessing one sequence to 10's/100's or millions of values.

EVERY row inserted during the day into ANY TABLE would need that sequence.  Your
statement only stands up if this system inserts a single row into each table per
day.  Highly *unlikely* in my opinion.

A common sequence can be looked at  -- but a SINGLE sequence for thousands of
tables -- I don't think so.  If you have a single sequence, you by definition
have a consistently "heavy usage" one.

I would not consider synonyms and grants as issues because

a) you shouldn't use synonyms so much ANYWAY for ANYTHING
b) this logic can and should be hidden in stored procedures and if not, we have
this thing called a "role".

So, I beg to differ.  If you read the question (use ONE sequence for thousands)
and the conclusion (apparently there is measurable contention if you use ONE
sequence by lots of concurrent sessions)..... I don't see how you can argue with
it.

and the advice-- there can be a big difference, take this into consideration.

As with all information everywhere in the world -- do with it what you might.

      Rating: 3
heavy usage of
      sequence  October 16, 2002
Reviewer:  prem
       from milano, italy
what about impact of sequence update on ora-001555

          Followup:

ora-1555's are always caused fundemtally by having ROLLBACKS that are too small.

the recursive sql can cause the symptom (lots of commits).

You can increase performance by increasing the cache size( reduce the recursive
sql)

      Rating: 5
What was cache
      size?  July 09, 2003
Reviewer:  Alan
       from NJ
Hi Tom,

Excellent benchmark!

Just out of curiosity, do you remember what your cache size was set to when you
ran this test?  Just curious if
a cache size of say 100,000 would have helped the single sequence test out a
little better.

Thanks.

          Followup:

it was the default, yes, a larger cache size would have helped.

      Rating: 5
One more
      question about this test  July 09, 2003
Reviewer:
      Alan  from NJ
Tom,

In the second test, each unix process is essentially hitting their own sequence,
whereas in the first test 10 processes are competing for the same sequence.

Would the test results have been much different if each of the multiple sequence
processes were hitting the sequences being used by the other processes?  This
would be more of a real world example for cases where user processes are
inserting to master/detail tables.

Unfortunately I can't set up test case until I get home tonight, so I was
wondering what your "gut feel" was. ;)

Thanks.

          Followup:

no, the only thing I was trying to show here was "yes, if you use a single
sequence -- you will increase contention, there is a downside to using a single
sequence for thousands of tables"

That is all the test was doing.

      Rating: 4
Cache size
      makes a HUGE difference......  July 09, 2003

Reviewer:  Mark J. Bobak  from Ann Arbor, MI

In fact, cache size is so important, it seems to be the
overwhelming factor in this test.

First, I slightly modified Tom's test.  I added one more
sequence, seq11, and used that for the 'single sequence test'.

Well, I was going to post all my data, but I don't think there
is room here.

So, what I found was that at the default cache size, my test
was nearly identical results to Tom's.  Also, I noticed
that the single sequence test produced notably less redo.
Then, I did two more tests, first, I increased the cache
size for seq11, the single sequence test, from 20 to 200.
(Logic being, 10x the load on this sequence, let's increase
the cache size by 10x.)  In this case, the test timing was
exactly the same elapsed time.  The single sequence test was
still dominated by enqueue waits on the SQ enqueue, but
total waits were reduced, and total elapsed time was the
same as the multiple sequences test.  Also, redo generation
was way lower on the single sequence test.

Finally, I altered the seq11 index again to cache 200,000.
In this case, the single sequence test actually beat the
performance of the multiple indexes.  Redo generation was
way down again, on the single sequence test.

And, lest you claim I gave the single sequence test an
advantage by increasing the cache value, and leaving the
other sequences at default cache size, I ram one more test.
This time, I left the single sequence at 200,000, and I
increased the cache of all 10 sequences in the multi-
sequence test to 200,000 as well.  Elapsed time, the single
sequence edged out the multiples, 1.78 minutes vs. 1.82
minutes.  Redo generation, single sequence was the clear
winner, at slightly more than 1/2 the redo that the multiple
sequence test generated.

So, it seems to me:

 - less is better
 - cache size is critical, don't be afraid to bump it up
on busy sequences.

Just my two bits.....

          Followup:

you would be amazed what setting a sequence cache via alter sequence to 100,000
or more can do during a large load -- amazed.

      Rating:
      5
Nice  July 09, 2003
Reviewer:  Alan
       from NJ
Thanks for posting your findings Mark.

Since you have your test case handy, can you do a quick test?

Change the code to:
declare
    l_val number;
begin
    for i in 1 .. 10000  -- reduce by a factor of 10
    loop
        select seq1.nextval into l_val from dual;
        select seq2.nextval into l_val from dual;
        select seq3.nextval into l_val from dual;
        select seq4.nextval into l_val from dual;
        select seq5.nextval into l_val from dual;
        select seq6.nextval into l_val from dual;
        select seq7.nextval into l_val from dual;
        select seq8.nextval into l_val from dual;
        select seq9.nextval into l_val from dual;
        select seq10.nextval into l_val from dual;
    end loop;
end;
/

And then launch with 10 processes.  This should simulate the second test case
competing for the same sequence as the first test case did.

Thanks.

      Rating: 3
Hmm....Not sure
      what to make of this......  July 09, 2003

Reviewer:  Mark J. Bobak  from Ann Arbor, MI

I made the changes that Alan requested......

And things came out about even.  Redo generation, elapsed
time, and in both cases, the wait time was dominated by
latch free wait event.  From monitoring V$SESSION_WAIT
during the test, I'd say it was cache buffers chains
latch contention.  Actually, I realized those were level
10 snaps.  The StatsPack data confirms it was cache buffers
chains latch contention.

          Followup:

could have been the banging on dual -- you might consider modifying the
benchmark to be:

insert into t values ( s.nextval )

(making sure that T is either in an ASSM tablespace in 9i or you have multiple
freelists configured for it)

      Rating:
      4
Thanks  July 09, 2003
Reviewer:
      Alan  from NJ
Mark,

Thanks a million for testing.

Were enqueues similar as well between both test cases?

Regards.

      Rating: 4
No enqueue
      waits....  July 09, 2003
Reviewer:  Mark J.
      Bobak  from Ann Arbor, MI
Over 99% of waits were latch free, and a huge majority of
those were on cache buffers chains.  The enqueue waits go
away when you increase the cache size.

      Rating: 5
Results from
      further testing.  July 10, 2003
Reviewer:
      Alan  from NJ
Tom,

I changed the benchmark to insert into tables as you described and increasing
the cache size for the single sequence test didn't seem to make much difference.

My test.
Create tablespace test_seq with ASSM Uniform 1M.
Create tables 1 - 10 as
(x number(38) primary key)

anonymous pl/sql loop changed to:
test_single.sql
begin  -- single sequence test
  for i in 1..10000
  loop
    insert into t2 values (seq1.nextval);
    insert into t3 values (seq1.nextval);
    insert into t4 values (seq1.nextval);
    insert into t5 values (seq1.nextval);
    insert into t6 values (seq1.nextval);
    insert into t7 values (seq1.nextval);
    insert into t8 values (seq1.nextval);
    insert into t9 values (seq1.nextval);
    insert into t10 values (seq1.nextval);
    insert into t1 values (seq1.nextval);
  end loop;
end;
/
exit

AND
test_multi.sql
begin  -- multiple sequence test
  for i in 1..10000
  loop
    insert into t2 values (seq2.nextval);
    insert into t3 values (seq3.nextval);
    insert into t4 values (seq4.nextval);
    insert into t5 values (seq5.nextval);
    insert into t6 values (seq6.nextval);
    insert into t7 values (seq7.nextval);
    insert into t8 values (seq8.nextval);
    insert into t9 values (seq9.nextval);
    insert into t10 values (seq10.nextval);
    insert into t1 values (seq11.nextval);
  end loop;
end;
/
exit

For this test, seq1 had a cache of 10,000 and seq2-11 had a cache of 1000.

The single sequence test took 3.5 minutes to run (this is on NT after all) and
had the following:
Top 5 Timed Events
~~~~~~~~~~~~~~~~~~                                                     % Total
Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
enqueue                                            10,929         329    29.45
buffer busy waits                                  11,997         301    26.87
db file sequential read                             3,223         115    10.26
latch free                                          4,422         104     9.27
CPU time                                                          101     9.03

The multiple sequences test took 2.07 minutes to run and showed:
Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
db file sequential read                               214           1    44.29
CPU time                                                            1    19.64
control file parallel write                            40           0     7.97
db file parallel write                                 10           0     7.71
control file sequential read                           44           0     7.05

I then went back and modified seq1 to have a cache of 100,000 and ran the single
sequence test again, but it didn't improve the speed or results by very much.
Enqueues and Buffer Busy Waits still dominated the top 5 events although with
slightly lower waits than the first test.

So as usual, your gut feel is correct.

Regards.

      Rating:
      5
  August 01, 2003
Reviewer:
      Balasubramanian.R  from Tamilnadu, Indian
Followup:
A shutdown will.  Losing 19 numbers
(with a default cache of 20) is no big deal (you are not going to exhaust that
sequence, not in many dozens and dozens of years).

What we observed for cache sequence as
1) if we do normal shutdown, we are not losing 19 numbers.
2) if we do shutdown abort only, we are losing 19 numbers.

Why we lose numbers in sequence with cache when shutdown abort.

          Followup:

because you aborted the database, it just "went away", it did not take the time
to goto the data dictionary and update the SEQ$ table.

      Rating: 4
any thoughts on
      this statspack report  September 10, 2003

Reviewer:  Joseph
STATSPACK report for

DB Name         DB Id    Instance     Inst Num Release     Cluster Host
------------ ----------- ------------ -------- ----------- ------- ------------
PRDPROD       1079374339 prdprod             1 9.2.0.1.0   NO      prd-proddb

            Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:      82 08-Sep-03 15:51:26       20       5.1
  End Snap:      83 08-Sep-03 16:18:35       22       6.3
   Elapsed:               27.15 (mins)

Cache Sizes (end)
~~~~~~~~~~~~~~~~
               Buffer Cache:       112M      Std Block Size:         8K
           Shared Pool Size:       160M          Log Buffer:     1,024K

Load Profile
~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:                991.39              3,436.12
              Logical reads:                893.03              3,095.22
              Block changes:                  5.26                 18.24
             Physical reads:                  0.98                  3.40
            Physical writes:                  0.17                  0.59
                 User calls:                  3.02                 10.47
                     Parses:                  6.54                 22.65
                Hard parses:                  0.14                  0.49
                      Sorts:                 34.03                117.94
                     Logons:                  0.09                  0.32
                   Executes:                 17.00                 58.90
               Transactions:                  0.29

  % Blocks changed per Read:    0.59    Recursive Call %:    96.76
 Rollback per transaction %:    1.06       Rows per Sort:    29.43

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:  100.00
            Buffer  Hit   %:   99.89    In-memory Sort %:  100.00
            Library Hit   %:   98.38        Soft Parse %:   97.83
         Execute to Parse %:   61.54         Latch Hit %:   99.99
Parse CPU to Parse Elapsd %:   92.93     % Non-Parse CPU:   97.75

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   16.71   20.38
    % SQL with executions>1:   76.38   79.43
  % Memory for SQL w/exec>1:   73.19   75.37

Top 5 Timed Events
~~~~~~~~~~~~~~~~~                                                     % Total
Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
CPU time                                                          152    83.39
control file parallel write                           524          13     6.92
log file sync                                         319           9     5.16
db file parallel write                                 42           4     2.07
log file parallel write                               654           3     1.77
          -------------------------------------------------------------
 Wait Events for DB: PRDPROD  Instance: prdprod  Snaps: 82 -83
-> s  - second
-> cs - centisecond -     100th of a second
-> ms - millisecond -    1000th of a second
-> us - microsecond - 1000000th of a second
-> ordered by wait time desc, waits desc (idle events last)

                                                                   Avg
                                                     Total Wait   wait    Waits
Event                               Waits   Timeouts   Time (s)   (ms)     /txn
---------------------------- ------------ ---------- ---------- ------ --------
control file parallel write           524          0         13     24      1.1
log file sync                         319          1          9     29      0.7
db file parallel write                 42         21          4     90      0.1
log file parallel write               654        638          3      5      1.4
db file sequential read               624          0          1      1      1.3
db file scattered read                 74          0          0      6      0.2
latch free                              4          2          0     13      0.0
SQL*Net more data to client           161          0          0      0      0.3
control file sequential read          294          0          0      0      0.6
direct path read (lob)                  4          0          0      0      0.0
LGWR wait for redo copy                 1          0          0      0      0.0
direct path write                       1          0          0      0      0.0
SQL*Net message from client         4,005          0      3,727    931      8.5
wakeup time manager                    51         51      1,530  30004      0.1
SQL*Net message to client           4,005          0          0      0      8.5
SQL*Net more data from clien           24          0          0      0      0.1
          -------------------------------------------------------------
 Background Wait Events for DB: PRDPROD  Instance: prdprod  Snaps: 82 -83
-> ordered by wait time desc, waits desc (idle events last)

                                                                   Avg
                                                     Total Wait   wait    Waits
Event                               Waits   Timeouts   Time (s)   (ms)     /txn
---------------------------- ------------ ---------- ---------- ------ --------
control file parallel write           524          0         13     24      1.1
db file parallel write                 42         21          4     90      0.1
log file parallel write               654        638          3      5      1.4
control file sequential read          130          0          0      0      0.3
db file sequential read                 3          0          0      0      0.0
LGWR wait for redo copy                 1          0          0      0      0.0
rdbms ipc message                   3,694      3,233      9,655   2614      7.9
smon timer                              5          4      1,334 ######      0.0
          -------------------------------------------------------------
 SQL ordered by Gets for DB: PRDPROD  Instance: prdprod  Snaps: 82 -83
-> End Buffer Gets Threshold:   10000
-> Note that resources reported for PL/SQL includes the resources used
-> by
   all SQL statements called within the PL/SQL code.  As individual SQL
   statements are also reported, it is possible and valid for the summed
   total % to exceed 100

                                                     CPU      Elapsd
  Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
        739,536        2,982          248.0   50.8    29.84     29.39 1498920852
select max(nvl(option$,0)) from sysauth$ where privilege#=:1 con nect by
grantee#=prior privilege# and privilege#>0 start with (g rantee#=:2 or
grantee#=1) and privilege#>0 group by privilege#

        316,545            3      105,515.0   21.8    28.06     28.34 3734722662
 SELECT UPPER(LTRIM(RTRIM(ID))) SOLUTION_FK_ID,        TO_NUMBER
(SUBSTR(ID,INSTR(ID,'_',1,2)+1,LENGTH(ID))) SOLUTION_NUMBER,
    UPPER(NAME) NAME,        SOLUTIONS_PKG.GET_CATEGORY_FOR_VIEW
_LOG(ID) CATEGORY,        to_char(DATE_CREATED,'mm/dd/yyyy') DAT
E_CREATED,        UPPER(CREATED_BY) CREATED_BY,        to_char(E

        164,631        2,652           62.1   11.3    14.95     15.09 3666276338
SELECT *            FROM soln_xref_categories           WHERE so
lution_fk_id = :b1 /*ADVICE(1915): Cursor reference to a externa l variable (use
a parameter) [209] */
                    AND record_status = 'CURRENT'

        154,550        2,810           55.0   10.6    11.52     11.48 1242355473
SELECT review_status         FROM enotebook_reviews        WHERE
 e_notebook_entity_id = :b1 AND record_status = 'CURRENT'

         96,967           13        7,459.0    6.7     3.11      3.03 2342908987
Module: Foglight Oracle Agent
select ((owner||'.')||table_name)  from role_tab_privs r ,user_r ole_privs u
where (r.role='SELECT_CATALOG_ROLE' and u.granted_ro
le=r.role)

         76,063           13        5,851.0    5.2     1.83      1.80 4010130737
Module: Foglight Oracle Agent
select ((owner||'.')||table_name)  from sys.user_tab_privs where  (grantee in
('PUBLIC',:b0) and privilege='SELECT') union select  granted_role  from
sys.user_role_privs where username=:b0

         58,817          148          397.4    4.0     6.06      6.83  890248754
BEGIN SOLUTIONS_PKG.add_sample_component(:1,:2,:3,:4,:5,:6); END ;

         49,435        2,991           16.5    3.4     2.97      2.87 3013728279
select privilege#,level from sysauth$ connect by grantee#=prior privilege# and
privilege#>0 start with (grantee#=:1 or grantee#=
1) and privilege#>0

         26,567          148          179.5    1.8    54.72     55.15  155406885
BEGIN JOB_JAVA_PKG.INSERT_NEW_SOLUTION(:1,:2,:3,:4); END;

         25,966          148          175.4    1.8    54.53     54.83 1121734198
INSERT INTO solutions                   (NAME, sol_type, created
_by,                    candidate_key                   )
 SQL ordered by Gets for DB: PRDPROD  Instance: prdprod  Snaps: 82 -83
-> End Buffer Gets Threshold:   10000
-> Note that resources reported for PL/SQL includes the resources used
-> by
   all SQL statements called within the PL/SQL code.  As individual SQL
   statements are also reported, it is possible and valid for the summed
   total % to exceed 100

                                                     CPU      Elapsd
  Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
     VALUES (:b1, :b4, :b3,                     :b2 || :b1
             )

         24,052          148          162.5    1.7     2.73      2.80 3720500110
SELECT /*+ ALL_ROWS*/                  *            FROM soln_co
mponents           WHERE solution_fk_id = :b2             AND co
mponent_fk_id = :b1             AND record_status = 'CURRENT'

         23,987          148          162.1    1.6     2.16      2.41  597870277
SELECT MAX (ID)         FROM soln_components        WHERE soluti
on_fk_id = :b2          AND component_fk_id = :b1          AND r
ecord_status = 'CURRENT'

         18,963          150          126.4    1.3    49.01     49.29  458776950
SELECT NVL (MAX (days_counter), 0) + 1         FROM solutions
     WHERE TO_DATE (date_created) = TO_DATE (SYSDATE)          A
ND UPPER (sol_type) LIKE '%' || UPPER (:b2         ) || '%'
     AND UPPER (NAME) LIKE '%' || UPPER (:b1     ) || '%'

         17,656            8        2,207.0    1.2     1.29      1.29 2727211991
      SELECT ID,          TEST,          CREATED_BY,          MO
DIFIED_BY,          DATE_CREATED,          STATUS,          NVL(
JOB_JAVA_PKG.get_chemist(ID),'NOT ASSIGNED')NAME,          JOB_J
AVA_PKG.get_review_status(ID) REVIEW_STATUS,     ANALITICAL_METH
OD_# ,     JOB_JAVA_PKG.get_review_status(ID) REVIEW_STATUS, REA

         14,083            3        4,694.3    1.0     1.35      1.35  356648237
select instrument_main_pkg.get_instrument_name(ins.instrument_ma
in_fk_id)  INST_NAME,                 columns_java_pkg.get_colum
n_name(ins.columns_tab_fk_id)                    COLUMN_NAME,
              columns_java_pkg.get_column_serial_no(ins.columns_
tab_fk_id)               COLUMN_SERIAL_NO,                 frx_r

          7,254          117           62.0    0.5     0.39      0.43 3789430917
SELECT trim(fname) || ' ' || trim(lname)         from employees
        where upper(trim(usid)) = upper(trim(:b1))

          6,370          148           43.0    0.4     0.41      0.38 1451474974
UPDATE sample_prep          SET record_status = 'HISTORY'
 WHERE SAMPLE = :b2 AND jobs_fk_id = :b1

          5,704        2,856            2.0    0.4     0.29      0.30 4276509072
SELECT category_desc         FROM category_lkup        WHERE ID
= :b1 AND record_status = 'CURRENT'

          4,509          148           30.5    0.3     4.91      4.84 1345456906
SELECT NVL (MAX (days_counter), 0) + 1               FROM soluti
ons              WHERE -- TO_DATE(DATE_CREATED)=TO_DATE(SYSDATE)
                    -- AND                    ID LIKE '%' || :b2
      || '%'                AND UPPER (sol_type) LIKE '%' || UPP
 SQL ordered by Gets for DB: PRDPROD  Instance: prdprod  Snaps: 82 -83
-> End Buffer Gets Threshold:   10000
-> Note that resources reported for PL/SQL includes the resources used
-> by
   all SQL statements called within the PL/SQL code.  As individual SQL
   statements are also reported, it is possible and valid for the summed
   total % to exceed 100

                                                     CPU      Elapsd
  Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
ER (:b1         ) || '%'

          4,383        1,461            3.0    0.3     0.09      0.13 2216996262
SELECT test            FROM jobs           WHERE ID = :b1 AND re
cord_status = 'CURRENT'

          -------------------------------------------------------------
 SQL ordered by Reads for DB: PRDPROD  Instance: prdprod  Snaps: 82 -83
-> End Disk Reads Threshold:    1000

                                                     CPU      Elapsd
 Physical Reads  Executions  Reads per Exec %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
            220          148            1.5   13.8     6.06      6.83  890248754
BEGIN SOLUTIONS_PKG.add_sample_component(:1,:2,:3,:4,:5,:6); END ;

            202          159            1.3   12.6     0.07      0.36 3111103299
select /*+ index(idl_ub1$ i_idl_ub11) +*/ piece#,length,piece fr om idl_ub1$
where obj#=:1 and part=:2 and version=:3 order by pi ece#

            161          148            1.1   10.1     2.73      2.80 3720500110
SELECT /*+ ALL_ROWS*/                  *            FROM soln_co
mponents           WHERE solution_fk_id = :b2             AND co
mponent_fk_id = :b1             AND record_status = 'CURRENT'

            143

          Followup:

looks like an almost idle, low use system without any major problems.

you have a bad query executed three times and the biggest load on your system
seems to be your tuning tool :)

      Rating: 4
Sequences in
      the create table  November 03, 2003
Reviewer:
      Subra  from Bay Area, CA
Your excellent responses help Oracle community.
I wonder why Oracle is not allowing me use sequence.nextval in the the DEFAULT
part of column specification in table ddl.
ALTER TABLE T_TEST_ID
MODIFY (TEST_ID NUMBER(10) DEFAULT SEQ_TEST_ID.nextval)
                                   *
ERROR at line 2:
ORA-00984: column not allowed here

I need to be able to automatically populate the test_id column (with
sequence.nextval) whenever user inserts a row, and I do not want to allow the
user to use SEQ_TEST_ID.nextval. How could I do it with column specification in
table ddl ?

          Followup:

you cannot, this is accomplished via a trigger.

begin select s.nextval into :new.id from dual; end;

November 04, 2003 -- Thanks for the question regarding "TSPITR",
      version 8.1.6

      You Asked
Tom:

i am practice manually TSPITR(without using rman)

i tried several times, but it always stuck at some point,

after i issue
recover database using backup controlfile until cancel.
it works fine until i issue cancel.

it gives me message as following:Specify log: {<RET>=suggested | filename | AUTO
| CANCEL}
cancel
ORA-01547: warning: RECOVER succeeded but OPEN RESETLOGS would get error below
ORA-01152: file 1 was not restored from a sufficiently old backup
ORA-01110: data file 1: '/t02/clone/wpbk/system01.dbf'

ORA-01112: media recovery not started

but that's impossible, because the backup controlfile is
definitely created at a later time than the backup that is being used.

Regards,

      and we said...
It is not complaining about the controlfile, it is complaining that the
system01.dbf has data checkpointed in it that POST DATES the point at which you
cancelled the recovery -- there is data in system01.dbf that was created AFTER
the point in time you cancelled the recovery.  system01.dbf is NOT OLD enough to
be recovered to the point in time you cancelled the query -- it contains data
made AFTER that point.

In order to do a manual TSPITR  you must:

o get a copy of system backed up BEFORE the point in time you want to recover
to.

o get a copy of the tablespace backed up BEFORE ....

o get a copy of the rollback segments backed up BEFORE ....

o restore these (to another machine, its safest).  PITR this "database".

o mount the database and offline drop the non-relevant (non-restored files).

o open the database

o make the tablespace readonly

o transport it using EXP.

o shutdown

o on the other system import the transported tablespace.

followup to comment

It does not matter if you have a whole COLD backup or whatever.

It only matters that you are in ARCHIVELOG mode, the mode of backup is quite
simply NOT relevant.

If you give me a full cold backup and the archive logs, the above steps are
correct, appropriate and function very well.

Review &
      Followup

      Rating:
      1
  April 25, 2001
Reviewer:
      A reader
that's no possible , because i use a cold whole backup.

      Rating:
      5
dependencies  September 12, 2002
Reviewer:
      A reader
Hi Tom

I am reading through Oracle ILT Backup & Recovery about TSPITR. It talks about
dependencies that we have to resolve them before we start recovery, does this
mean that if I have table EMP & DEPT if I dropped table DEPT accidently in my
recovery procedure I have to include table EMP too?

I guess so because if I have a new department after my last backup and new
employees are assigned to that department then if I recover my last backup I
would have a old DEPT table whch is not consistent with new EMP table.

Am I correct?

          Followup:

depends on whether you actually had foreign keys from emp to dept.

dependencies would be coded relationships such as foreign keys, indexes and
such.

If you are doing a TSPITR of EMP, and EMP has a fkey to DEPT, and DEPT is at a
different point in time from EMP, there obviously could be issues you have to
reconcile.

      Rating: 5
contradiction
      between ILT text and Documentation  September 12, 2002

Reviewer:  A reader
in 8.1.7 backup & recovery documnetation it states:

General Restrictions
TSPITR has several restrictions. You cannot do the following:

Recover a SYSTEM tablespace.

Recover dropped tablespaces.

Recover a tablespace that has been dropped and re-created with the same name.

but in 8i ILT text it suggests:

TSPITR by way of transportable tablespaces provides basically the same
functionality
as O/S TSPITR, but is more flexible since:
• You can recover dropped tablespaces, which you cannot do by using O/S TSPITR.
.......... etc

one says we can recover dropped tablespace and the other not, which one is
correct?

          Followup:

One says you cannot recover a tablespace that was dropped if you recreated
another one with the same name.

Another way to say that is "you CAN recover a tablespace that was dropped, as
long as you didn't create a new one by that same name"

They do not conflict

      Rating: 5
TSPITR using a
      clone database  September 16, 2003
Reviewer:
      A reader
Tom,

With RMAN my clone is up with system, RBS and the required user tablespace( this
holds one of the partition)

2. I create a table say 'TEMP' in tablespace SYSTEM ( i cannot create it any
where- else because it is a clone instance)

3. I exchange table partition with  TEMP.

before exchange:

SVRMGR> select partition_name, tablespace_name from user_tab_partitions;
PARTITION_NAME                 TABLESPACE_NAME
------------------------------ ------------------------------
P1                             TEST_USERS01
P2                             TEST_USERS02
2 rows selected.

I am trying to recover partiton p1 in other instance.
TEST_USERS01 is READ ONLY.

4. At this point :

SVRMGR> select partition_name, tablespace_name from dba_tab_partitions;
PARTITION_NAME                 TABLESPACE_NAME
------------------------------ ------------------------------
P1                             SYSTEM
P2                             TEST_USERS02
2 rows selected.

Now when i try to exp with transportable option for tablespace=TEST_USERS01 i
get

SVRMGR> select reason from sys.pluggable_set_check where ts1_name ='TEST_USERS01
';
REASON
--------------------------------------------------------------------------------
Default tablespace and partition not selfcontained
Default tablespace and partition not selfcontained
2 rows selected.

what am i doing wrong here.

-Thanks,

          Followup:

indexes?

      Rating: 3
No
      indexes  September 16, 2003
Reviewer:
      A reader
Tom,

This is from the previous review. My table is a simple table
with no indexes....

Thanks,

          Followup:

oh wait, simple

you cannot transport an incomplete set of partitions.

from the admin guide:

o A partitioned table is partially contained in the set of tablespaces.

The tablespace set you want to copy must contain either all partitions of a
partitioned table, or none of the partitions of a partitioned table. If you
want to transport a subset of a partition table, you must exchange the
partitions into tables.

      Rating: 3
Partition
      exchange  September 17, 2003
Reviewer:
      A reader
Tom,

I did a partition exchange into a table t_temp. But the table exists in SYSTEM
tablespace because it is a clone instance and i cannot create it any where else
becuase a clone instance has only system rollback segment online.Now what
tablespace should i transport SYSTEM or TEST_USERS01?

SVRMGR> select partition_name, tablespace_name from dba_tab_partitions;
PARTITION_NAME                 TABLESPACE_NAME
------------------------------ ------------------------------
P1                             SYSTEM
P2                             TEST_USERS02
2 rows selected.

Thanks,

          Followup:

but the tablespace you are trying to transport -- test_users02 -- contains a
PARTITION

I see you are trying to get:

..
I am trying to recover partiton p1 in other instance.
TEST_USERS01 is READ ONLY.
.....

so, you exchanged P1 with TEMP.  TEMP is now a TABLE in TEST_USERS02.  You can
either:

a) use a conventional export and export TEMP, you have your data

b) DROP everything else (except temp) in TEST_USERS02 and then transport that
tablespace

c) create yet another tablespace in this temporary instance and alter table temp
move that_tablespace.  then transport THAT tablespace.

      Rating: 3
Clone
      Instance  September 17, 2003
Reviewer:
      A reader
Tom,

based on your followup to my question: my responses are with
***)

Followup:
but the tablespace you are trying to transport -- test_users02 -- contains a
PARTITION

I see you are trying to get:

..
I am trying to recover partiton p1 in other instance.
TEST_USERS01 is READ ONLY.
.....

so, you exchanged P1 with TEMP.  TEMP is now a TABLE in TEST_USERS02.  You can
either:

***) TEMP is now a table in SYSTEM tablespace. I cannot create TEMP anywhere
else becuase it is a CLONE instance. If i try to create TEMP anywhere else i get
:

ORA-1552: Cannot use system rollback segment for non-system tablespace <tbsp
name>

I i create a rollback segment and bring it online:

I get error message:

"CLONE database can have only system rollback segment online"

a) use a conventional export and export TEMP, you have your data

***) Does that mean if i accidently drop a partition in production i cannot use
TSPITR?

b) DROP everything else (except temp) in TEST_USERS02 and then transport that
tablespace

***) I cannot use TEST_USERS02 as i am on CLONE instance and this tablespace
does not exist for it as ONLINE

c) create yet another tablespace in this temporary instance and alter table temp

move that_tablespace.  then transport THAT tablespace

***) Though i can create a tablespace on CLONE instance, i cannot alter table
temp to another tablespace because i get
the same error: ORA-01552.

Tom,

in 8i do we have the following exp parameter:

point_in_time_recover ? I believe this is obsolete and transportable tablespace
is the option. Please help me regarding this.

Thank you.

          Followup:

no, the temp table is in the original tablespace -- you swapped a partition with
a table -- the partition BECAME a table.

P1 was temp in system, it is now a partition in system,
TEMP was partition P1 in test_users02, it is not a TABLE in test_users02

the alter exchange didn't MOVE ANYTHING

add some rollback segments to this clone instance, do what I described.

      Rating:
      5
Excellent  September 17, 2003
Reviewer:
      A reader
Tom,

I understood the concept.

Thank you very much.

      Rating: 2
using rman for
      tspitr  November 03, 2003
Reviewer:  Duncan
       from UK
Tom

I am practising doing a tspitr using an auxiliary database (AUX).  The db is
8.1.7.4 and I have a backup of the db BEFORE the point in time i am recovering
to.

The command i used is
run {
     allocate auxiliary channel chdev1 type disk format
'/op4/backup/b_%u_%p_%c';
     recover tablespace DATA until scn 164724699;
}

The output i get is:

RMAN-03022: compiling command: allocate
RMAN-03023: executing command: allocate
RMAN-08030: allocated channel: chdev1
RMAN-08500: channel chdev1: sid=9 devtype=DISK

RMAN-03022: compiling command: recover

RMAN-03027: printing stored script: Memory Script
{
# set the until clause
set until  scn 164724699;
# restore the controlfile
restore clone controlfile to clone_cf;
# replicate the controlfile
replicate clone controlfile from clone_cf;
# mount the controlfile
sql clone 'alter database mount clone database';
# archive current online log for tspitr to a resent until time
sql 'alter system archive log current';
# resync catalog after controlfile restore
resync catalog;
}
RMAN-03021: executing script: Memory Script

RMAN-03022: compiling command: set

RMAN-03022: compiling command: restore

RMAN-03022: compiling command: IRESTORE
RMAN-03023: executing command: IRESTORE
RMAN-08016: channel chdev1: starting datafile backupset restore
RMAN-08502: set_count=21 set_stamp=508868571 creation_time=31-OCT-03
RMAN-08021: channel chdev1: restoring controlfile
RMAN-08505: output filename=/oracle/app/oracle/oradata/AUX/control01.ctl
RMAN-08023: channel chdev1: restored backup piece 1
RMAN-08511: piece handle=/op4/backup/0lf59dur_1_1 tag=null params=NULL
RMAN-08024: channel chdev1: restore complete

RMAN-03022: compiling command: replicate
RMAN-03023: executing command: replicate
RMAN-08058: replicating controlfile
RMAN-08506: input filename=/oracle/app/oracle/oradata/AUX/control01.ctl
RMAN-08505: output filename=/oracle/app/oracle/oradata/AUX/control02.ctl
RMAN-08505: output filename=/oracle/app/oracle/oradata/AUX/control03.ctl

RMAN-03022: compiling command: sql
RMAN-06162: sql statement: alter database mount clone database
RMAN-03023: executing command: sql
RMAN-03026: error recovery releasing channel resources
RMAN-08031: released channel: chdev1
RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-03002: failure during compilation of command
RMAN-03013: command type: recover
RMAN-03015: error occurred in stored script Memory Script
RMAN-03006: non-retryable error occurred during execution of command: sql
RMAN-07004: unhandled exception during command execution on channel clone_defaul
t
RMAN-20000: abnormal termination of job step
RMAN-11003: failure during parse/execution of SQL statement: alter database moun
t clone database
RMAN-11001: Oracle Error: ORA-01103: database name 'OP4' in controlfile is not '
AUX'

RMAN>

What is going on here?  OP4 is the target db sid. Why does it think that the AUX
dbname is OP4 in the contolfile??

Please help there is nothing about this in the manuals or on google.

Thanks

          Followup:

The database name in the controlfile is not consistent with the db_name
parameter in the init.ora file.

the database name is OP4 -- forget about the SID, thats just an environment
variable used to locate configuration files and generate a key to the shared
memory segment.

      Rating:
      1
thanks  November 04, 2003
Reviewer:
      Duncan  from uk
ok so the db_name is not the same as in the controlfile.
So now the question is obviously why not?

The db_name from the init.ora file is AUX (as is should be, surely, since it is
the init file for the AUX instance.), and the controlfile is produced by RMAN.

So which is wrong. Got to be the controlfile hasn't it? Why is the controlfile
got the wrong db_name, what went wrong?!!

          Followup:

I don't know, you typed in the control file yourself??

why should the db_name in the init.ora be AUX when the database name is not AUX?

in fact, if you follow the step by steps in the RMAN guide, you would discover
it is clearly documented that the db_name is the SAME !!!!

http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76990/rmantspi.htm#443514
you might want to follow their step by steps the first time to practise.

      Rating: 5
rationale for
      same db_name with auxiliary  November 21, 2003

Reviewer:  rob  from pa
When you do the manual Table PITR using RMAN (because you don't want the whole
tablespace recovered to that point int time), you connect RMAN to the catalog
and its target is just the auxiliary instance, there is no specific AUXILIARY
instance used.  Since the Target is the auxiliary instance the RMAN catalog
would have no way of knowing about what tablespace you are attempting to recover
unless your auxiliary had the same DB_NAME as your original source.  (I assume
that's how it knows which from which db I want to recover a particular
tablespace from, please correct me if that is wrong?) However, when you do
automated TSPITR from within RMAN you connect to the target, catalog, and
auxiliary, so it knows the source of where you intend to recover stuff from and
it is a little less obvious that the db_name should be the same, although
completely understandable. The concepts, differentiation between, and purpose
for global name, SID, service name, db_name, and tns_alias can take a little
getting used to.

November 04, 2003 mohammad -- Thanks for the question regarding
      "dba responsibilities", version 8.1.6.0

      You Asked
What are the responsibilities does a dba have to shoulder in an organisation?
Does he/she require to know development side also in the context of the present
day job market for DBAs?

      and we said...
If they do not understand the development side, they will not be a very
effective DBA.  The main goal of the DBA is to make things run smoothly as
regards the database(s).  Availability, good performance, these are things the
DBA is concerned with.

If the DBA doesn't know or understand the tools used to develop against the
database, or the goal of the developers -- they would not be able to do their
job.
Review
      & Followup

      Rating:
      4
  June 20, 2002
Reviewer:
      A reader
How many DBA teams should there be in a company? I mean whether the DBA teams
should be split to perform specific activities such as support or development
... Does it make sense?

          Followup:

depends on the size of the company and how many databases you have and what you
do.

small company, off the shelf software, little to no development.  no, not
really.

large company, off the shelf software, little to no development.  no, not
really.

lots of development with "dev", "test", "prod" systems -- maybe two or three
groups within the group to help support that.  should still be the same group
however as the "dev" dba's have to follow the same company guidelines in general
as the "prod" dba's (but they can bounce the instance more frequently)

It is more a matter of scale (size) and tasks then a yes/no.

      Rating: 3
Also
      ..  June 20, 2002
Reviewer:  Mark  from
      UK
Tom,

Wouldn't you say that the responsibilities of the DBA are different in
development and production?

In production, they are responsible for maintaining a secure and stable
environment, whilst in development their main role is to provide a service to
developers?

Mark

          Followup:

But -- they have to be working towards the same end goal -- the same set of
standards, the same basic configuration.

In development -- they can bounce more often then production but they still must

o run in archivelog (you really want to lose that dev instance?  I don't.
Besides- how can you tell how you will run unless you run in the same mode)

o use the same sort of sizing and tablespace rules as production -- else when
you go production you have a mess

o enforce the same security policy as production will have, else when you go
.....

and so on.  I do not see their overall jobs being significantly different.  That
is why I said they might be two groups within the SAME org but they are working
for the same group really.  THey have to have the same end goal in mind.

      Rating: 5
DBA or
      Developer...  June 20, 2002
Reviewer:  Pasko
       from Hamburg,Germany
Hi Tom

Thanks for comments from above...

I also  think that Dba's should know a lot about Development..or else there
would have very little for them to do...unless you are a part-time DBA ..or may
be A System Admin  + DBA..

Regards

      Rating: 4
groups within
      the group  June 20, 2002
Reviewer:
      A reader
Tom,
What is your opinion in case dev and prod groups are not within the same group?
If these groups are split and every group do what they want, won't that create
mess?

          Followup:

YES, the stuff you develop will not meet the guidelines and rules of the
production team -- hence you will not be able to deploy your developed piece of
software.  Your company has rules, it has standards -- unless everyone is
working to the same SET of rules and standards -- it won't work.

      Rating: 4
production and
      development  June 20, 2002
Reviewer:  Mark
       from UK
Tom,

So when my DBA denies a developer access to the V$ views, as he wouldn't give
such access in production, I don't have a leg to stand on.  I'm not being
flippant - this has happened to me!

Mark

          Followup:

No, this is different.

Does the application being developed rely on this access?  If so, maybe they are
right.

Does the developer need access to this in order to tune and analyze their
application -- then the DBA is wrong to deny access.

By security I meant things like "my application schema will have DBA granted to
it and SELECT ANY TABLE as well just for good measure".

There is a difference between preventing someone from doing their job and common
sense....

      Rating:
      4
  June 20, 2002
Reviewer:
      A reader
Tom,
And what if "prod" dba group forbids the access to the "dev" dba group to the
production systems, in the "mess" case when these groups are separated?
Isn't that ridiculous?

          Followup:

no, not necessarily.  why would it be?

Let's say we work together as developers in the same group.

We'll use the same methodology
We'll use the same tools
We'll use the same standards (coding standards)
etc...

Touch my code tho and I'd be really upset.  We are working on similar things,
but different.

My point is -- they cannot each work in a vacum.  Maybe I'm the developer
building generic utilities to be used by the other groups.  If I use my own
naming convention, my own style of doing stuff, use a different source code
control system and so on -- it won't work out very well.

Same with the database.  prod does not have to let dev into the system (unless
of course an upgrade is failing, then it would make sense from prod's
perspective to "ask for help") to be successful at all.

      Rating:
      4
  June 20, 2002
Reviewer:
      A reader
Why would prod and dev dba's be in different groups? Shouldn't they be a part of
one team?

          Followup:

well, i say NO.  At most I say:

...
lots of development with "dev", "test", "prod" systems -- maybe two or three
groups within the group to help support that.
......

"within the group" -- teams within the team -- depends on the size and scale of
the organization.

      Rating:
      5
Eenvironments  June 20, 2002
Reviewer:
      Andre  from Brazil
I have worked in very controlled environments, with very well separated
(1)development, (2)UAT and (3)production systems.

Especially in a financial institution, there are lots of PROCEDURES telling you
about methodologies, workflows, etc.

For a system to be in production, it would have to be in development first, then
it would have to be approved in UAT (User Acceptance Testing) so it could
finally end up in production.

All the interfaces between these stages were well documented and should follow
strict rules.

In this typical scenario, there could be a "multipurpose" DBA along this
workflow -- eg in small environments -- or else a couple of them in each stage.

Besides, all the database work should be accordingly documented and the programs
should be catalogued: no one messing with code in production, no way. So one
line of code could be changed in production, someone would do it in dev first,
then go to UAT and finally to prod.

When code is altered, there is a department called "change management" to take
care of cataloguing the new code. The same goes to database objects which have
to be under the control of a "data administrator" (not database administrator,
the DBA).

Hope this helps !

      Rating: 4
production and
      development   June 21, 2002
Reviewer:  Mark
       from England
Tom,

I totally agree that the DBA procedures should be the same within development
and production.  My point was really about their attitude.

Oh, and congratulations to Andre of Brazil for their World Cup victory over
England (and apologies to all Americans who do not understand the beautiful
game).

Mark

      Rating: 4
"within the
      group"  June 26, 2002
Reviewer:
      A reader
Hi Tom,
Do you mean by this that all these guys should be in groups within one and only
one dba group?
What if for example manager of prod group wants his group to do some development
and dev group is not consulted?

          Followup:

let's not get carried away.

I am suggesting that there is one org -- the DBA org.

Within there -- there are subgroups (like a typical hierarchy).  They all follow
the same rules, have the same goals, but support different people.  That's all

      Rating: 5
Check the
      following Link  June 27, 2002
Reviewer:
      Sikandar Hayat Awan  from Pindi Bhattian - Pakistan

http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/nav/getting_started.htm

      Rating: 2
As a DBA, I'd
      like to make a few points ...  July 08, 2002

Reviewer:  Harrison Picot  from Haymarket, VA USA

It is hard to think of anything less likely to produce a serious thought, than a
bunch of developers figuring out if DBAs have enough to do; the "grass is
greener on the other side of the fence" sure gets a lot of play.  As a DBA, let
me say that Tom's book, "Expert one-on-one Oracle", is for developers not DBAs,
but most developers don't know 1/10 of what is in it. Tom has also pointed out
that everybody should read the concepts manuals, and almost nobody has.  So
before assigning work to DBAs, how about learning what is in Tom's books and the
concept manuals?  If you did that, you wouldn't need the DBA to explain it to
you. As Tom has pointed out, Oracle is not a black box and to create scalable
applications the developers need understand how Oracle works. The suggestion
that they learn it from DBAs is without merit. Most of what developers do, they
do without showing it to DBAs or discussing it with them, and they are not
required to take any suggestions from DBAs. Agree that we can fire you and I
will agree that we need to raise our standards.

Now I know that Tom encourages the idea that DBAs should know this and that, and
I agree with some of his ideas, but it is the developers who write the code,
SQL, PL/SQL and the idea that DBAs should (or ever will) know more than a
developer is silly.  I have worked with, at a major corporation, contractors
expert in Visual Basic, and remember many ugly things they did, including asking
"What is this SQL stuff?", so a DBA that knows that answer is a help, but is
hardly what is needed.  And remember, Tom is not perfect, if he were he would
not have said "Fire the DBA" as many times as he has, without saying "Fire the
developer" at least once.

Developers can mess up and it is a bug.  When DBAs mess up there is often a loss
of data (yes, I lost a byte one time) and you can get fired (no) for what would
be head scratcher for a developer.  You are also blamed if the database is slow,
or if it runs out of space, and often denied the hardware to adequately test
backups, or create boxes to fail over to, and I have had situations where
software went from a developer to production with no testing at all. Also, DBAs
and developers frequently roll up to different managers, and developers often
ask for things that don't make sense and are not approved by any manager, and if
they are wrong, they are more likely to keep their job than the DBA that did
what they asked.  There is a lot more pressure on DBAs as a single mistake can
be awful.

So my idea is, read Tom's book (I am doing that also, but you need it more) and
read the concept's manuals (both volumes), and learn big parts of both, and then
you will not need to blame the DBA for not telling you about bind variables, and
will be glad to have a DBA that does backups, can restore in case of failure and
keeps your brothers from trashing a production box, which, despite allegations
to the contrary, is quite a lot to do.  And for my part, I promise to quit
saying things under my breath after you announce that your instance "has been
trashed by forces unknown" and you want it back to six days ago. Which I can do.

And remember, we never see anyone working as hard as we do, because we (really)
only see all of what we do, not what anyone else does, so don't be quick to put
stuff on someone else's plate (of course we could agree that if I forget to do
something because I was talking to you, you take the blame ... huummm  ... I
like that idea.  Any mistake I make can lead to a firing).

If you want to rely on something, let it be that most people want to be part of
something that works, and instead of figuring out in advance how people should
work together, treat people as individuals that will work with you if you give
them the resources to do so.

Cheers,

Harrison

(Read the books, and I really will stop mumbling under my breath;-)

          Followup:

Hey -- a simple search for

fire developers

turns up 4 hits.  One of them:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3379873654938
says very very clearly:

<quote>
Here are the steps to fix this problem:

a) fire the existing developers.  They obviously don't use databases very often.

b) hire developers who have experience writing large scale, scalable
applications, have read the documenation and are willing to fix bugs in their
code when discovered.

Sorry to be so blunt but .....
</quote>

So, I am on record for saying just that!  That, and I search for "fire dba" and
do not see an inordinate number of times (you have to read the 16 or so hits --
I'm using talking about a trigger getting fired, not the DBA ;)

(also in my book -- all of the "bad" examples are developers!  not DBA's.  Don't
worry -- I beat up on both equally)

Now, I don't recall seeing in here where I said or implied:
...
and the idea that DBAs should (or ever will) know more than a
developer is silly.
......

I will stand by what I said here -- if the DBA (who should be working WITH, not
AGAINST, not FOR the developers) doesn't understand how the developers are using
the database -- they will not be able to do their DBA job.  Period.

      Rating: 4
dba
      responsibilities  July 09, 2002
Reviewer:
      Iain  from Scotland, UK
As Tom has stated, the them (Developers) and us (DBA) mentallity is not really
going to ensure a fast, reliable, available system. Rather than have development
teams and dba teams in each dev/production area, would it not make more sense to
have a unified team of developers and DBA's. If a developer really understood
the finer points of managing a DB with respect to e.g. buffers, disk I/O,
tuning, recovery, security....the list goes on, then I would be out of a job.
The converse is true, if I did a large volume of development...why employ
developers? It makes sense to utilise each others specialised knowledge. As far
as firing is concerned, if you can't do the job you were employed to do.....
If a DBA runs out of space, loses data etc when this was avoidable, you have to
ask the question...would I not be better serving in McDonalds?

      Rating:
      4
  July 10, 2002
Reviewer:  DBA from
      Aus.
Just be proactive, if you sit and wait for developers to ask for help, you'll
end up being the mushroom and looking after potentially badly developed apps.
I suppose that makes my job more interesting, fixing messes... :)

I agree that the DBA(s) should be as close to each other and developers. Its
about relationship management, make the developers your friends!

Very tough saying fire people, isn't training a better option?? Or hiring a
contractor to help short term?
I personally would ask up front of an employer what the grounds for dismissal
were. Otherwise you could cop the blame on a bad management decision eg
deadline, lack of infrastructure, lack of guts etc :)

Have Fun

          Followup:

As for the firing thing -- I didn't say "fire the dba's" here -- I did point to
a note where I said "fire the developers" but if you read that one -- you'll see
that it had to be said in that case.

In that case

o they knew what the problem was
o they knew they were the cause of the problem
o they refused to fix it after being told of it

They were educated to the problem, they refused to fix a bug, they gotta go ;)

      Rating: 5
Some more
      questions  April 16, 2003
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
Tom
As far as DBA working with developers goes, I have one question:
a) Who does the job of query tuning? DBA or developer?

I am in an environment where most developers have worked with SQL and PL/SQL for
the first time. They have never read an Oracle manual, never heard about
analytic functions, dynamic SQL, bind variables, bulk binds and never run an
explain plan. Their main task is to somehow make the application work. As a
result of this, when I review the code, I find majority of code which can be
replaced by more efficient constructs, but that would mean a complete code
rewrite and testing. Sometimes, when I see something totally untolerable, I
advise them about better ways of doing the same thing, but such advise is
generally ignored.

Is fixing such bad code a DBA responsibility? The project management seems to
think that DBA should fix all the poorly written code even if he has to rewrite
it. Is that a fair expectation?
Thanks

          Followup:

a) everyone -- from day one.

It is not anyones job.
It is everyones job.

and it starts way before a line of code is dreamt of.
It starts with the design (logical and physical)
It continues through development as the developer gets it going as good as they
can in single user mode.
It continues through scalability/concurrency testing where the dba's monitor the
instance as a hole, work WITH (not against, not for, not whatever -- WITH) the
developers to fix the problems discovered
It continues to the end user acceptance where they actually use the tool in 50
different ways no one anticipated finding yet more issues to resolve.

So then when it goes production -- you don't tune anything, it works.

Fixing the code might not be the DBA responsibility -- but in this environment
you describe I might suggest that

a) stored procedures are the only interface method -- no sql in the applications
whatsoever

b) a mixed team of DBA/Developers are broken off to design and implement the
database part -- the "real" part of the system

c) some of the developers remain behind to work on the lesser, not as important
parts -- like the UI.

That way -- the DBA's get to understand the developers and the developers in b)
become database savvy.

Otherwise, you are lost...

I do not believe in the DBA/developer wall -- tear down that wall.  It is a
database.  There are applications working on the database.  If either part fails
to meet expectation -- neither part is useful.  It is like a two phase commit
here -- you either BOTH succeed or you both have failed.

      Rating:
      5
Reader  April 30, 2003
Reviewer:
      A reader
Can you put data administrator in the mix and
compare and contrast their roles and responsibilities
with DBAs

          Followup:

whats a "data administrator".

      Rating: 5
my
      role  April 30, 2003
Reviewer:
      A reader
Hi

My role as production DBA is basically, getting mails with SQL scripts to run in
pre-production and production. Dont have time for more! Doing more than 50 per
day due to high number of databases (over 500)

Production DBA is a not very recommended job really

      Rating: 5
Your
      role?  April 30, 2003
Reviewer:
      A reader
...sounds more like a database operater (DBO). Stand up and be a DBA: write some
scripts that process your mail and extract those SQL statements, and
automatically submit to as many databases as you wish. Almost all can be
automated, on unix platforms of course.

      Rating: 5
you wish you
      can do that in production  May 01, 2003
Reviewer:
      A reader
Of course I have thought of making everything automatic, it is simply
impossible, when the SQL scripts arrives before run them we must check them, you
dont know what kind of script they send you! Plus, we have like 10 software
providers so we first try to educate them but obviously if you work with 10
consultancies who then sub-contract people from more consultancies you can
probably spend all your life educating them! Basically there is a standard
procedure but in practice there are always things which does not fit in these
standards

If you work in a big environment you would see running scripts automatically is
totally out of question, if we do that we probably spend half of time restoring
backups :)

Things to check are such as

owner of objects (sometimes they specify the development user!)
storage parameters
tablespace specifications
check if there is commit in DML scripts, not allowed
check synonym creations
check if they grant privileges to roles and not users

when a guy first arrived here asked us why we dont automize the process, we told
him, sure try it. After two months he gave up :)

      Rating:
      5
Reader  May 06, 2003
Reviewer:
      A reader
The definition of Data Administrator in DW is from a book by Sid Adelman et al.

"
The role responsible for the enterprise's data resource and for the
administration
control and coordination of all data-related analysis activities. The DA has the
responsibility for planning and defining the conceptual framework for the
overall data environment. The functions of the DA typically include requirements
definition, logical data modeling, data definitions, logical to physical
mapping, maintenance of inventory of the current system, data analysis, and
the meta data responsibility.
"

This site may be little off for these type of questions, however, I like to
know if possible, how a DA and DBA compare and contrast in their roles and
responsibilities

          Followup:

Apparently

A DA talks about data.
A DBA works with data.

;)

the DA sounds like a traditional data modeler/data architect role.

      Rating: 5
Will you please
      guide me   June 26, 2003
Reviewer:  Pragya Rohit
      Gulati  from Ambala,INDIA
Hi Tom

        It seems to be unbelievable as first time I came across this site. You
are great in way you answer the queries. I am a programmer new in Oracle and
working on ORACLE 7 server release 7.1.4.1.0 and Pl/SQL Release 2.1.4.0.0. on
SCO UNIX Open Server. I am working in an Automobile dealership as programmer. I
have designed Forms and Reports(.rpts) for the company.
Now I have been given the charge of whole organization. You can say that I have
been the system manager there. The system runs for Inventory Control, Accounting
and Vehicle Job Processing System etc. There is 1 server and 8 dumb terminals.
Now problem with me is that I don&#8217;t know what type of responsibilities are
there, which should be handled by DBA. Actually I don&#8217;t know the abc of
DBA. Will you please guide me ( if possible for you,I know you have a lot of
queries to answer) where can I get the material regarding Data Base
Administration so that atleast I can handle the day to day activities.

Hope you will understand the problem with me and  help.

Regards,

          Followup:

A dba has one thing they cannot do wrong -- ever.

That is recovery.  Can you recover your database from a total catastrophic
failure (which if your hardware is as old as your software is coming next week
maybe)?  Do you know backup and recovery like the back of your hand?  if not,
there you go, that is what you are doing.

      Rating:
      3
Thanks  June 27, 2003
Reviewer:  Pragya
      Rohit Gulati  from Ambala,INDIA
Hi Tom
       I daily backup the entire data base to a .dmp file to another machine. In
last 2 years i did not face any failure but once i had deleted an important file
that i recorvered from .dmp file. About hardware we have Compaq Proliant PIII
server.

          Followup:

you did not recover an important file from a dmp file.

You recreated the data as it looked at the point in time you took the dmp --
meaning you lost each and every change made to it.

export is not a physical backup.
export "backups", if you can call them that, assure that you WILL lose data as
all of the changes between the export and the "restore" will be lost forever.

only a backup is a backup.

      Rating: 2
Disagree on
      some points  June 27, 2003
Reviewer:  Marc
      Blum  from Aachen, Germany
Hi, i followed the thread and want to throw in some personal opinions. Regarding
the administration of the DEV database we follow these rules:

- the whole database is regularly rebuild from scratch by a script machine

- Testdata is provided by scripts (reproduceability)

- all scripts above are taken from the version control system

- DEV databases are *never ever* backed up (if it gets screwed up, I rebuild a
clean one). If buggy code logically corrupts my data, these database looses any
value, so why back it up?

- each developer is advised to work as if the DEV database is completely rebuild
tomorrow morning, so don't rely on anything in that database

- the holy grail is the version control system, nothing else

- the DEV-DBA is the maintainer of all database scripts. All DDL has to be
discussed with him/her. Developers aren't allowed to change the data model. On
the other hand the DBA commits himself to propagate all DDL ASAP to the DEV
databases.

Marc

          Followup:

My DEV databases are backed up.

It has my code in there. (holy grail aside, i checked out the code this morning
and have been working on it for 5 hours.  now it fails.  choices = lose 5 hours
of work or restore from backup)

It has my data in there. (I generally don't have scripts to build testdata
except for version 0.0 of the system.  after that, it is prod data copied down
but with modifications.  again, easier to restore then rebuild IMO)

It has to be running in archivelog mode anyway, how else can you tell how it
will work in real life.  (maybe YOUR dev databases aren't backed up, maybe YOU
have that procedure in place)

You can do what you want with DEV, but I find it tons faster to restore in the
event of failure.  I find it easier to restore then rebuild.  Also, it tests
that your procedures actually work, which is a nice side effect.

      Rating:
      5
DBA_RESPONSABILITIES  June 27, 2003

Reviewer:  A reader
SQL> select count(*) from dba_responsabilities;

  COUNT(*)
----------
8374319983

SQL> select count(*) from user_responsabilities;

  COUNT(*)
----------
         0

      Rating: 3
Please
      guide  November 04, 2003
Reviewer:  reader
       from India
Hi Tom,
      I really don't have words to appreciate the kind of knowledge you hold of
Oracle, it’s a little personal but I would like to know what will you call
yourself a developer having sound knowledge of database things or a DBA having
sound knowledge of development related activities, please don’t take it
offending.  I have been working as a developer in Oracle with just PL/SQL from
long, left other languages and tools back behind and as time went by learning
more about databases I have a desire to move towards database administration but
not just backup and recovery, more of the way you deal with the things
development plus anything of databases we ask you and you know it, I too want to
make a mark in Oracle world, I really want to be an expert what would you
suggest, how should somebody go on this, I do understand practically working on
things is very important but in real life one doesn’t get chance to explore
everything but I want your comments on this please.

Thanking you in anticipation and waiting for your reply.

          Followup:

i believe in a triumvirate actually.

there are dba's -- they manage the database, do the installs, backups, ===>>
RECOVERY <<<===, patching, general management of the system and softare.

there are coders.  they do the gui, they do some formatting of data, they have
the middle tier on out.  more power to them.

then there are the database developers.  they own the data.  they define the
transactions, build the logic to get the data, modify the data.  they understand
what a database is, how transactions work, what an IOT means, when a cluster
might be appropriate, what facilities the database provides so that it doesn't
need to be recoded over and over and over again.  they know analytics.  they can
write multi-line sql statements and aren't afraid of doing so.

i'm in that last class.

November 04, 2003 Jenna -- Thanks for the question regarding
      "PL/SQL Soundex function", version 8.1.6

      You Asked
Tom

I am currently investigating the benefits of using the built-in
PL/SQL function SOUNDEX to retrieve surnames that are
phonetically similar.  I have tested it against a number of
similar surnames with varying success.  For example, according
to the soundex function, LEE and LEIGH are not phoentically
similar.

In order to evaluate the soundex function, I need to fully
understand how the it works.  However, I can't find any
documentation in the Oracle manuals other than its syntax.

Have you any experience of using it?   Do you know how it works?

Regards

Jenna

      and we said...
It is the standard soundex algorithm as described by Knuth in
Volume 3 of The Art of Computer Programming.  See

http://www.myatt.demon.co.uk/sxalg.htm
for a web page that has it documented online.
Review &
      Followup

      Rating: 3
Soundex
      Function  November 04, 2003
Reviewer:
      Denise  from Virginia, USA
Hi Tom

is the Soundex Algorithm available with the Standard Edition?....or just the
Enterprise Edition.

thanks

          Followup:

select soundex(...) from

works in SE, yes.

November 04, 2003 Surya -- Thanks for the question regarding
      "Temporary tables and indexes", version 8.1.5

      You Asked
Hi Tom,

Our application is using temporary table for processing
intermediate results. I created temporary table for session.
I created index on the temporary table.

My questions are:

1) Why this temporary table is created on SYSTEM table spaces
with my ID as owner?

2) Is the table not using temporary table space for storing
data?

3) The index on temporary table is created in the default
tablespace of my Id. Why it is not created in temporary
tablespace.

4) I read in Oracle Manuals, that DML operation on temporary
table will not create any redolog but the undo information (RBS)
generates redolog which is required for Instance recovery.
I used logminer to analyze the archived log file.
I found the information of insert into temporary table and the
username doing the insert operation as my Id.
Why DML operations are generating redolog?

I would appreciate if you can throw some light on the temporary
table usage.

Thanks in advance.

Best Regards,
Surya

      and we said...
1) the temporary tablespace will allocate extents dynamically at
runtime from the USERS temporary tablespace -- there is nothing
in system (unless your temporary tablespace is in fact SYSTEM)

Why do you think it is in system?

2) it is. for example:

tkyte@TKYTE816> create global temporary table foo
  2  ( x int )
  3  /

Table created.

tkyte@TKYTE816>
tkyte@TKYTE816> select table_name, tablespace_name
  2  from user_tables where table_name = 'FOO';

TABLE_NAME                     TABLESPACE_NAME
------------------------------ ------------------------------
FOO

tkyte@TKYTE816>
tkyte@TKYTE816> select temporary_tablespace
  2  from dba_users
  3  where username = user;

TEMPORARY_TABLESPACE
------------------------------
TEMP

tkyte@TKYTE816>
tkyte@TKYTE816> insert into foo values ( 1 );

1 row created.

tkyte@TKYTE816> commit;

Commit complete.

tkyte@TKYTE816>
tkyte@TKYTE816> alter tablespace temp offline;

Tablespace altered.

tkyte@TKYTE816>
tkyte@TKYTE816> insert into foo values ( 1 );
insert into foo values ( 1 )
*
ERROR at line 1:
ORA-01542: tablespace 'TEMP' is offline, cannot allocate space
in it

that shows that space is coming from my TEMP -- not system.

3) it is -- why do you think its coming from system?

tkyte@TKYTE816> create index foo_idx on foo(x);
Index created.

tkyte@TKYTE816> select index_name, tablespace_name from
user_indexes where
  2  index_name = 'FOO_IDX';

INDEX_NAME                     TABLESPACE_NAME
------------------------------ ------------------------------
FOO_IDX

temporary stuff have no tablespace -- their extents from from
the users temporary tablespace dynamically at runtime.

4) they generate significantly LESS redo then real tables -- not
"no redo".  For example:

scott@DEV816> create table t1 ( x int );
Table created.

scott@DEV816> create global temporary table t2 ( x int );
Table created.

scott@DEV816> set autotrace on

scott@DEV816> insert into t1 select rownum from all_objects;
23080 rows created.

Statistics
----------------------------------------------------------
...
     348116  redo size
...

scott@DEV816> insert into t2 select rownum from all_objects;
23080 rows created.

Statistics
----------------------------------------------------------
...
      72508  redo size
...

The temporary table in this case geneate 1/5 the redo of the
real table.

Review &
      Followup

      Rating: 5
5 sessions of
      the same schema - global temporary tables  October 15, 2001

Reviewer:  A reader
Tom

Iam logged in as scott/tiger from 5 different sessions.

Now I have a global temporary table called gl_temp_tbl in the scott schema.

Each one of those sessions is doing inserts and deletes, updates and truncates
on this gl_temp_tbl.

Now my question is , will each of those session have there own extents for the
said table, or will this lead to  a situation where each situation is deleting
and truncating the data being inserted from the other session.

2.How can I determine , as to how many versions of the global temporary table
are running  at given point of time.
In my case above , there are 5. But how should I know that.
From which view in the data dictionary , I can get this information.

          Followup:

each session in fact has their own table, in their own space, exclusive of every
of session.

They will not contend with eachother at all.

2) you can see temp information in v$sortusage

ops$tkyte@ORA9I.WORLD> @printtbl8 'select * from v$sort_usage'
PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> create global temporary table foo ( x int, y char(2000)
);
Table created.

ops$tkyte@ORA9I.WORLD> insert into foo select user_id, username from all_users;
61 rows created.

ops$tkyte@ORA9I.WORLD> @printtbl8 'select * from v$sort_usage'
USERNAME                      : OPS$TKYTE
USER                          : OPS$TKYTE
SESSION_ADDR                  : 8834F6C0
SESSION_NUM                   : 1544
SQLADDR                       : 87513890
SQLHASH                       : 2907312366
TABLESPACE                    : TEMP
CONTENTS                      : TEMPORARY
SEGTYPE                       : DATA
SEGFILE#                      : 201
SEGBLK#                       : 585
EXTENTS                       : 1
BLOCKS                        : 64
SEGRFNO#                      : 1
-----------------

PL/SQL procedure successfully completed.

      Rating:
      4
  November 09, 2001
Reviewer:
      Sameer  from NJ
What is the difference between

'create tablespace temp ... temporary'
and
create temporary tablespace temp ...'

The former uses datafiles, the latter uses tempfile. In either case, permanent
objects cannot be stored.

I read all the relevant docs, but in terms of practical usage, whats the diff?
How to choose between the two ?

          Followup:

The true temporary tablespace (create temporary tablespace) can use locally
managed tablespaces (very nice, fast, ensures uniform extents), the other
cannot.

The true temporary tablespace uses files that CANNOT be backed up (well you can
but you are totally wasting your time).  The dictionary managed ones don't have
to be backed up but your recovery procedures are complicated by the fact they
are datafiles (you have to go through extra steps to do a full recovery with
missing datafiles -- no so with tempfiles).

The true temporary tablespae uses "sparse" files.  Create a 2 gig temp
tablespace with tempfiles, then create a 2gig temp with datafiles - time them.
Big difference.

      Rating: 3
Do you mean
      'create tablespace temp......temporary' is outdated
      ?  November 09, 2001
Reviewer:  Sameer
       from NJ
You mentioned all the advantages of true temporary tablespace ('create temporary
tablespace temp'). So do you mean to say that's the only sytax that makes sense
now and the other temp ts ('create tablespace temp ... temporary') is outdated
syntax and shouldn't really be used anymore ?

          Followup:

Yes, i use true temporary tablespaces exclusively now myself.  I would not
recommend using tablespaces that could be permanent via an alter -- only true
temporary tablespaces with tempfiles.

      Rating: 3
Could you tell
      me why is this ?  November 12, 2001
Reviewer:
      Gururaj Kulkarni  from Norcross, GA USA
SQL> create global temporary table tool (x int);

Table created.

SQL> select table_name,tablespace_name from user_tables
where table_name='TOOL';

TABLE_NAME                     TABLESPACE_NAME
------------------------------ -----------------------------
TOOL

SQL> select temporary_tablespace from dba_users where user='SCOTT';

TEMPORARY_TABLESPACE
------------------------------
SYSTEM
TEMP
SYSTEM
SYSTEM
SYSTEM
SYSTEM
TEMP
SYSTEM
SYSTEM
SYSTEM
SYSTEM
TEMP
SYSTEM
SYSTEM

14 rows selected.

Why do I have both as System and Temp temporary table space ? and Why 14 rows
have been selected ?

          Followup:

Well, the reason for the NULL tablespace name is because a temporary tablespace
simply doesn't HAVE a tablespace!  When you use the temp table, it will allocate
space in the tablespace mandated by the TEMPORARY_TABLESPACE for the current
schema.  So, if you put data into this temp table, it will use either TEMP or
SYSTEM in your system depending on who you are (best to ALTER those users and
have their TEMP tablespace set to something other then SYSTEM!!!)

As for the query -- the reason it returns 14 rows -- you must have 14 users in
your database.  You obviously ran this query when logged in as SCOTT.  In this
case, the query was the same as:

select temporary_tablespace from dba_users where 1 = 1;

You see -- you used the USER pseudo column -- it returns the name of the
CURRENTLY logged in user.  If you logged in as any other user, that would return
0 rows.

The query returns both system and temp as temporary tablespaces because some of
your users have TEMP and others as SYSTEM as their temporary tablespace.

      Rating:
      5
  November 13, 2001
Reviewer:  Helena
      Markova  from Bratislava, Slovakia
Thank you for explaining the usage of the temporary tables.

      Rating: 5
temp tablespace
      datafiles  August 03, 2003
Reviewer:  cosmin
      ioan  from FL
hi Tom,
on 9.0.2.3, I created multiple datafiles for the temp tablespace so that I can
spread the I/O but to my surprise, when I create large temporary tables, Ora
starts filling the first datafile, then next, etc, basically, not using the
round-robin fashion as all other tablespaces do.  any explanation for that?

thank you v. much,
Cosmin

          Followup:

test case?  here is mine:

ps$tkyte@ORA920> create temporary tablespace temp2
  2  tempfile size 100m, size 100m, size 100m, size 100m, size 100m
  3  /
Tablespace created.

created a 500m temp tablespace...

ops$tkyte@ORA920> alter user ops$tkyte temporary tablespace temp2;
User altered.

set myself to use it...

ops$tkyte@ORA920> create global temporary table t
  2  on commit delete rows
  3  as
  4  select *
  5    from big_table.big_table
  6   where 1=0;
Table created.

when I put stuff into T now, it'll go into TEMP2...

ops$tkyte@ORA920> select file_id, extents_cached, extents_used
  2    from v$temp_extent_pool
  3   where tablespace_name = 'TEMP2';

no rows selected

nothing there yet...

ops$tkyte@ORA920> insert into t
  2  select *
  3    from big_table.big_table
  4   where rownum <= 100000;

100000 rows created.

ops$tkyte@ORA920> select file_id, extents_cached, extents_used
  2    from v$temp_extent_pool
  3   where tablespace_name = 'TEMP2';

   FILE_ID EXTENTS_CACHED EXTENTS_USED
---------- -------------- ------------
         2              2            2
         3              2            2
         4              2            2
         5              2            2
         6              2            2

Now, spread out over the 5 files -- just to show that it isn't the case that
file id 2 could only HOLD 2 extents -- that it went round and round -- we'll add
some more data:

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t
  2  select *
  3    from big_table.big_table
  4   where rownum <= 100000;

100000 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select file_id, extents_cached, extents_used
  2    from v$temp_extent_pool
  3   where tablespace_name = 'TEMP2';

   FILE_ID EXTENTS_CACHED EXTENTS_USED
---------- -------------- ------------
         2              4            4
         3              4            4
         4              4            4
         5              4            4
         6              4            4

two more in each -- so, file 2 didn't "fill up" and then move on... it went
round and round

ops$tkyte@ORA920> commit;
Commit complete.

ops$tkyte@ORA920> drop table t;
Table dropped.

ops$tkyte@ORA920> alter user ops$tkyte temporary tablespace temp;
User altered.

ops$tkyte@ORA920> drop tablespace temp2;
Tablespace dropped.

      Rating: 5
great example;
      I'm still missing something  August 03, 2003

Reviewer:  cosmin  from FL
hey Tom, here's my setup:
(initially I had the TEMP tablespace with only one datafile, then I added three
more, one for each remainder of my disks but I'm still seeing used space in only
one datafile; I even restarted the server several times in the interim):
[sorry for the wide/messy formatting  -- is there any easy way to display column
widths not on a case by case basis?)

thx much,
Cosmin

SQL> select USERNAME, DEFAULT_TABLESPACE, TEMPORARY_TABLESPACE from dba_users
  2  where username='MS';

USERNAME                       DEFAULT_TABLESPACE
------------------------------ ------------------------------
TEMPORARY_TABLESPACE
------------------------------
MS                             MS
TEMP

SQL> select file_id, extents_cached, extents_used
  2     from v$temp_extent_pool
  3     where tablespace_name = 'TEMP';

   FILE_ID EXTENTS_CACHED EXTENTS_USED
---------- -------------- ------------
         4           5949         3884

SQL>    select * from dba_tablespaces
  2     where tablespace_name='TEMP';

TABLESPACE_NAME                BLOCK_SIZE INITIAL_EXTENT NEXT_EXTENT MIN_EXTENTS
------------------------------ ---------- -------------- ----------- -----------
MAX_EXTENTS PCT_INCREASE MIN_EXTLEN STATUS    CONTENTS  LOGGING   FOR EXTENT_MAN
----------- ------------ ---------- --------- --------- --------- --- ----------
ALLOCATIO PLU SEGMEN DEF_TAB_
--------- --- ------ --------
TEMP                                 8192        1048576     1048576           1
                       0    1048576 ONLINE    TEMPORARY NOLOGGING NO  LOCAL
UNIFORM   NO  MANUAL DISABLED

SQL> SELECT * FROM DBA_TEMP_FILES;

FILE_NAME
--------------------------------------------------------------------------------
   FILE_ID TABLESPACE_NAME                     BYTES     BLOCKS STATUS
---------- ------------------------------ ---------- ---------- ---------
RELATIVE_FNO AUT   MAXBYTES  MAXBLOCKS INCREMENT_BY USER_BYTES USER_BLOCKS
------------ --- ---------- ---------- ------------ ---------- -----------
D:\ORACLE\ORADATA\MS\TEMP01.DBF
         1 TEMP                           8388608000    1024000 AVAILABLE
           1 NO           0          0            0 8387559424     1023872

E:\ORACLE\ORADATA\MS\TEMP01.DBF
         2 TEMP                           6291456000     768000 AVAILABLE
           3 YES 6291456000     768000        12800 6290407424      767872

FILE_NAME
--------------------------------------------------------------------------------
   FILE_ID TABLESPACE_NAME                     BYTES     BLOCKS STATUS
---------- ------------------------------ ---------- ---------- ---------
RELATIVE_FNO AUT   MAXBYTES  MAXBLOCKS INCREMENT_BY USER_BYTES USER_BLOCKS
------------ --- ---------- ---------- ------------ ---------- -----------

F:\ORACLE\ORADATA\MS\TEMP01.DBF
         3 TEMP                           1.0486E+10    1280000 AVAILABLE
           2 YES 1.0486E+10    1280000        12800 1.0485E+10     1279872

C:\ORACLE\ORADATA\MS\TEMP01.DBF
         4 TEMP                           1.0486E+10    1280000 AVAILABLE

FILE_NAME
--------------------------------------------------------------------------------
   FILE_ID TABLESPACE_NAME                     BYTES     BLOCKS STATUS
---------- ------------------------------ ---------- ---------- ---------
RELATIVE_FNO AUT   MAXBYTES  MAXBLOCKS INCREMENT_BY USER_BYTES USER_BLOCKS
------------ --- ---------- ---------- ------------ ---------- -----------
           4 YES 3.4360E+10    4194302        12800 1.0485E+10     1279872

          Followup:

temp was already allocated and didn't need to get anymore space.

This would be like asking "hey, why isn't my table spread across the 4 files.  I
started with one file, filled the table up and then add three more".

For the same reason -- temp is in a single file, we've already allocated it.

sounds like you want to drop it and recreate it.

      Rating: 5
What about the
      indexes ?  October 01, 2003
Reviewer:  Tony
       from UK
We have a large temporary table used as an intermediate step in a batch process.
The process drops indexes, issues and insert /*+ append */ and then re-creates.
The table is partitioned on Organization to allow the batch process to issue a
truncate at the beginning (rather than deleting).

I am looking at amending this to be a global temporary table with indexes (so we
no longer need the expensive truncate) - marking the indexes unusable initially,
inserting  and then rebuilding. I was intrigued about your comment that each
user effectively had their own copy of a table in their own schema. Is this true
for indexes too - how would marking them unusable affect the situation ??

Is this a viable method ?

          Followup:

it would not be necessary to mark the indexes unusable as you would be using a
direct path insert (/*+ append */) into the gtt

that will cause the index to be built off to the side and merged into the index
after the insert finished.  since the index does not "exist" (it would be empty)
there is nothing to merge.

I would propose that you need not set unusable the indexes on the gtt since
there is no structure to merge into at the end.

in any case:

ops$tkyte@ORA920> alter index gtt_idx unusable;
alter index gtt_idx unusable
*
ERROR at line 1:
ORA-14451: unsupported feature with temporary table

      Rating:
      5
Thanks  October 01, 2003
Reviewer:
      Tony  from UK
Is there any advantage of keeping the /*+ Append */ given that there will be
little redo generated due to it being a gtt ?

          Followup:

yes, it makes a difference - try it in sqlplus with set autotrace on statistics
and see.

      Rating: 3
Temp Tablesapce
      size in 9.2.0  October 01, 2003
Reviewer:
      A reader
Hello Tom,

  I got a really weird probelm using Oracle 9.2.0 on windows 2000.

  The Temp tablespace couldn't extend more than 4G and it failed & I had to add
another datafile to Temp tablespace. I remember we had this problem with 8.1.7.
Do we still have this problem with 9.2 ? Please let me know.

Thanks in advance

          Followup:

no error messages for me to look at?

      Rating:
      3
  October 02, 2003
Reviewer:
      A reader
Hi Tom,

  I got this error : ORA-01114 : IO error writting block to file

HTH,
Thanks

          Followup:

bummer?

not sure what else I can say?

if the rest of the error stack is not obvious, you might consider contacting
support.

      Rating: 5
Any
      Backup/Restore Issues when using /*+ APPEND */ on a GTT
      ?  October 02, 2003
Reviewer:  pasko
       from HH,Germany
Hi Tom,

Thanks for a greate Follow-up above..

I am planning to be using a GTT with insert /*+ APPEND */
on it on a production Database.

This will be used for filling some summary Statistics Tables.

Will there be any issues with Backup/Restore procedures, with or without rman.

And also, why can't we specify nologging on a GTT to even take down further the
amount of REDO generated ?

thanks in advance.

          Followup:

nope, no issues -- gtt's live in TEMP and you don't even backup true temporary
tablespaces.

      Rating:
      5
Errors  November 03, 2003
Reviewer:
      atul  from India
Hi Tom

while running one batch job we are getting following errors

ORA-01114: IO error writing block to file 76 (block # 1471)

ORA-27063: skgfospo: number of bytes read/written is incorrect
IBM AIX RISC System/6000 Error: 28: No space left on device

But when i check in v$datafile we have only 33 datafiles then why error shows
file# as 76.
When i check db_file parameter it's 75,so temp_file in temporary tablespace is
76th one??

I check that i found no space left in that,
How to go about it?
any solutions?

Can i drop temp tablespace and recreate it with auto extent on?

Why temp file is feeling too much?

Thanks.
atul

          Followup:

temp -- yes.

you can drop and recreate, yes.

      Rating: 4
Dynamic
      temporary tables  November 04, 2003
Reviewer:
      A reader
Hi Tom,

   What do you think if I create the temporary table "dynamically" each time to
store the temporary results of the queries? The temporary table does not exist
before the query, only create when the query is submitted. And the temporary
table does not drop after the query. These temporary tables only be dropped by a
job which is schedule to run at night (e.g. 1:00a.m.). And where Oracle stores
the temporary table definition (in SYSTEM tablespace)?

Thanks,
David

          Followup:

DDL in an application is a very very very bad idea.

create global temporary table is ddl. it is in system like everything else.

i would encourage you to reconsider your overall design.

November 04, 2003 Alex -- Thanks for the question regarding "How
      Oracle Warehouse Builder compares to Informatica? ", version 9i

      You Asked
Dear Tom,

I don't know if you ever worked with Oracle's OWB tool, but here is my question.

I've worked in the past with Informatica, Datastage, and MS DTS (Microsoft Data
Transformation Services). I consider Informatica the most complex and harder to
learn, followed by Datastage, and having DTS to be the easiest to learn but also
offering much less functionality than the other 2. I have an opportunity to work
on a project that utilizes Oracle Warehouse Builder and I would like to know how
it compares to the above 3 ETL tools, as far as easiness to learn (can you learn
it on the job or it is advised to go to the 4-day training), complexity, and
functionality.

I'll appreciate your feedback.

Thank you,
Alex Kourtis

      and we said...
I asked Derrick Cameron, our resident BI/DW/Tools expert and he says:

...
I can't make a direct comparison between OWB and these products without first
hand experience using them.  However, they all have a similar common goal, and
as such would have some common characteristics.  So learning OWB would be easier
than not having used any tool at all.

In many ways OWB should be relatively easier due to its support for a single
target - that is the Oracle database (in fact flat files too, but not other
dbs).  This reduces ( and optimizes) the possible ways it would generate code,
which would simplify diagnostic and deployment activities.  For example, OWB
uses pl/sql to process data (platform independent) while others use C, which
introduces OS compatibility issues.  If you are famliar with sql/plsql (most
developers are) you can expose the code OWB generates and see what it is doing.
This is rarely required, but it's a good thing.

The tool is all GUI and you can easily define sources, targets, and mappings to
move data in a few minutes.  Do you need a course?  No.  Of course it would
accelerate your understanding of how best to use the tool.  You'll have to
decide that for yourself.
.......
Review
      & Followup

      Rating:
      3
  December 05, 2002
Reviewer:
      A reader
For the benefit of a wider audience, can we have a more basic discussion here?
Sorry if my questions are too naive.

What is the purpose of these ETL tools in the first place? For loading data into
datawarehouse databases(whether oracle or any other d/b), each of the databases
have provided tools that are most appropriate for that d/b (that is what they
claim anyway. Tom has repeatedly said he prefers sqlldr to C + OCI calls for
dataloads). Similarly extracting data from the database to flat files also is
supported by all the databases. Whatmore, many databases even offer connectivity
to other databases using interfaces like ODBC or transparent gateways.
Mainframes and other Cobol type systems too can download data to Text files. And
for transforming data, what better tool than SQL? Or if you want you can do it
on a Unix platform using sed/awk etc most efficiently. And Oracle database has a
vast array of features like MVs, dimensions, Transportable Tablespaces, sqlldr
with direct and parallel etc which are great tools for extraction transformation
and loading. I don't really know if an ETL tool can do anything more
efficiently.

I have not used any of the ETL tools myself. But I have worked on a couple of
datawarehouse projects where they just 'play cat and mouse' with the data using
ETL tools. On many occasions it just adds up the cost and cause delays. I have
seen data extracted into text files from and Oracle ODS database using an ETL
tool and loaded into another Oracle warehouse database using sqlldr. I have also
seen cases where ETL tools directly interfacing with the database using OCI. But
I have not seen a situation where a ETL tool was really indespensable.

I heard that Oracle's ETL tool (warehouse builder) is now made part of Oracle 9i
database as the 'ETL engine'. I have no clue what that means!. Can someone throw
some light on this whole ETL thing. Even after working on 3 datawarehouse
projects for nearly 3 years, I don't really understand the rationale of their
existence! I must be really stupid!

          Followup:

Here is my 2 cents -- yes, any tool can get in the way, any tool can be useful
-- it is 100% a two way street there. Taken to illogical extremes, anything can
be bad like that.

On of the important things in a data warehouse is documentation, "where did this
bit of data come from", "what was done to this data over time", "why do you give
me X as the answer when this other thing gives me Y as the answer". In order to
answer those questions AND keep all of the books as to what comes from where and
when - I think you'll find OWB pretty good at that. It is to the point where you
can be sitting looking at a report and because our stuff knows about OWB -- you
can click on a data element in the report and trace its lineage back through the
system to see where it came from. So, I can see this data is actually merged
data from two systems where system1 was added to system2 and system1 was my
mainfram VSAM files and system2 was some relational data from my new web
interface on the internet. This is more then just a "neat" function -- this will
save the analyst hours when asked to justify their numbers. This will save days
verifying the system is getting stuff from where it should be. In short -- one
of the predominant "features" of OWB is the repository -- the bookkeeping it
does. It automates many of the mundane tasks you would otherwise.

By "ETL" engine we mean much of what you actually said. We have the gateways to
everything (part of iAS). That gives us great "E" capability. Inside the
database with things like merge, multi-table inserts, external tables, pipelined
functions, transportable tablespaces and the like -- we have the "TL" parts, the
transform and load. Whereas most ETL tools take a pure "dump", "transform",
"load" approach -- hitting the file system 2, 3, 4 times along the way -- we
just extract, tranform and load in as few steps as we can - you even have the
ability to do the ETL in one continous step -- no intermediate files, no
intermediate steps.

And I asked Derrick Cameron, our resident BI/DW/Tools guru to respond as well
and he says:

Firstly, I think it is important to recognize there are a great many
successful data warehouse implementation projects that did not use an
ETL tool.  Not surprising considering ETL tools are still early in the
evolution of this technology.  I totally agree about the use of
sqlloader (actually, external tables now), sql, plsql as the optimal way
to load and process data in an Oracle database, as well as your other
comments about database features.  I too have worked on data warehousing
projects without the use of an ETL tool so your comments have merit.

So what does an ETL tool, and in particular OWB, bring to a data
warehousing project?  Many good things.

1.  Automated code generation.  OK, we're coders.  We can do that.
There's more to it than that.  In a team environment you varying levels
of expertise, techniques, and approaches, etc.  OWB generates all this
code (sql, plsql, tcl scripts, etc.) in a standardized way that so
implementation and maintenance is consistent, and much easier to
maintain, than trying to figure out how someone else coded the
transformations.  Some projects may have only semi technical staff and
coding may be difficult for them.  A non-issue with OWB.  It is also
quite common for development staff to continue to do things the way they
know how rather than adopting the latest best practices, such as
leveraging the 9i ETL functions such as external tables, merge
processing, pipelined transformations, etc.  To address one of the
questions about OWB and the 9i ETL engine - the point is OWB generates
code that takes advantage of database ETL features such as merge, multi
table insert, external tables, etc.  The OWB development group work VERY
closely with the server technologies group to ensure optimal techniques
are used.  Its a very collaborative development environment.  They are
right up to date with the latest server features, leveraging all the
things you raise in your comments, but implemented consistently in a
standardized way.

2.  Utilities.  In our custom development project we built a whole
infrastructure around process management and control.  I'm talking about
auditing the load jobs, monitoring, reporting, tracking, etc. jobs and
processes.  This is part of what a tool brings to a project.  It's one
thing to code a transformation and another to log these and know when
the jobs were run, how long they took, what the results were, how the
exceptions were handled, etc.  In a large production warehouse these
become important.

3.  Documentation.  Ever used Excel spreadsheets to document flows from
source to target?  What a pain.  I have.  Dreary work.  Never
maintained.  OWB provides extensive reporting of all the metadata in the
repository, including lineage tracking.  Better yet, end users can drill
from a workbook in Discoverer (end user ad hoc query tool) back to the
source - target mapping to determine where the data came from.  This is
not developed for each workbook but come as a result of building the
mappings in the tools and exploiting the tool integration.

4.  This brings me to inter-tool integration.  ETL is not the end goal.
It's getting information to end users.  Without an ETL tool you start
from scratch in reporting.  OWB allow you to build your reporting End
User metadata layer (EUL) as you develop ETL processes.  In fact,
updates to the EUL are now (OK, maybe in a month) real time.  The saves
a lot of effort building the business metadata reporting layer.  This
also extends to OLAP.  OWB generates multidimensional OLAP Cubes in
Oracle 9i, and provides the support for cube loading and OLAP metadata.
Again, you're building from scratch there without the ETL tool. Same
goes for Designer.  We're really just scratching the surface here.  The
bottom line - Datawarehousing is a lot more than just ETL, and tools
(those that are integrated that is), save a lot of development and
maintenance time and effort down stream.

Maybe two or three years ago a lot of this was not there.  Its come a
long way and should now be a serious consideration for anyone building a
data warehouse.

      Rating: 1
How Oracle
      Warehouse Builder compares to Informatica  December 06, 2002

Reviewer:  Joe Kaeding  from Portland, OR USA

Great information, but it doesn't answer the question posed.  As a resident
expert in ETL (Derrick Cameron), I would hope that he should have first hand
experience with the major competing tools or at least be versed enough to
provide a substantive comparison.

      Rating: 4
Very good
      explanation of why an ETL tool  January 02, 2003

Reviewer:  Ramakrishna  from India
Dear Tom/Derrick,
  Thanks, I too was looking at the ETL tool as just a hindrance to loading data
into the warehouse the fastest way.  So, the use of an ETL tool like DataStage
or Informatica will be justified PROVIDED we use their metadata management and
reporting features, right?  Unfortunately (at least in DataStage), it is
possible to get away with doing just the plain ETL without really capturing all
the metadata about data lineage, transformation steps, etc (in fact, there is a
separate plugin called MetaBroker which needs to be purchased to do some of this
metadata tracking).
It is good that OWB seems to be doing all of this automatically so the metadata
is readily available.  A great advantage (which the other tools lack) is linking
to this metadata from the end-user reports.  And, of course, OWB will be
optimized for Oracle better than the other tools so it makes sense to use it
when the db is Oracle.

regards
Ramakrishna

      Rating: 5
Fantastic
      discussion...  April 30, 2003
Reviewer:
      Kashif  from Houston, TX
Thanks to Tom and Derrick for the fantastic discussion on the merits of an ETL
tool, as compared to homegrown ETL scripts. I myself have never really used an
ETL tool (we work off only pl/sql and shell scripts, and it works fine for us),
but I can see the value in using one. Especially as the market for BI matures,
it will become even more important to have documentation and information about
the integrity and source of your data, based on which you make multiple analyses
and decisions, and an ETL tool can provide just that. Thanks again.

Kashif

      Rating: 5
I was exactly
      looking for this kind of information  May 13, 2003

Reviewer:  Sami  from Bangalore,India
Thanks Tom & Derrick Cameron.

      Rating:
      4
OWB  July 15, 2003
Reviewer:  judy
       from USA
I was convinced to use the OWB and I recommended my company to purchase the OWB.
Now I have the OWB and I have hard time to use it. It has many components and
the documentation is like hundreds of pages. Can you point to me a good demo or
tutoring step by step how to use it?

I have created my Star schema using ERwin and I tried to import the meta data to
the repository and failed.

Any recommendation for a good training sites will be appreciated.

          Followup:

see

http://otn.oracle.com/products/warehouse/content.html

      Rating: 3
ETL To
      migrate..  October 29, 2003
Reviewer:  vj
       from in
I am new to Oracle Warehouse builder..

i wanted to know if Oracle Warehouse Builder can be used as an ETL tool in order
to migrate data from DB2 to Oracle. i want some assistance in gathering more
information on how to use Oracle Warehouse Builder and achieve this..

Any help highly appreciated

          Followup:

Yes it can, you have gateways to pretty much all relational and many
non-relational data stores.  OWB works with them all.  If you have OWB as part
of iAS, you have the gateways as well

      Rating: 3
Q on
      OWB  October 30, 2003
Reviewer:  Anil Pant

Hi,
is OWB client 9.2 is supported in Win 98 ?

          Followup:

good question for support -- i've never run win98, no experience with it.

      Rating: 3
Q on OWB
        October 30, 2003
Reviewer:  A reader

Hi,
Can anyone tell me does OWB 9.2 client runs on Win98 ?

          Followup:

support says......

O/S Information:
Certification of this product on MS Windows 98 is not planned.

      Rating: 3
Licensing of
      OWB  November 04, 2003
Reviewer:  Jasbir
      Kular  from Toronto, Ontario
Is OWB included with the Enterprise Oracle Database license?

          Followup:

in 10g, it'll be.

in 9i timeframe, it is part of iAS licensing

      Rating: 3
Licensing of
      OWB   November 04, 2003
Reviewer:  Jasbir
      Kular  from Toronto, Ontario
Do you mean iDS instead of iAS?

          Followup:

hmm, doh, yes -- ids, sorry about that.

in 10g, DB
in 9i and before ids

November 04, 2003 John -- Thanks for the question regarding "Views
      of Views", version 8.1.7

      You Asked
I remember hearing some time ago that creating views based upon other existing
views should be avoided as it can often confuse the optimiser and result in full
table scans.

I expect that this is just another urban myth however I would be interested in
your opinion on this issue.  I think that using views within a view definition
can aid readability however is there any performance impact as opposed to
incuding the entire text within a single view definition?

      and we said...
It'll never "confuse" the optimizer.

It often PRECLUDES the optimizer from doing something.  If the views contain
things like rownum, aggregates (group by), distincts, sorts, analytics, level,
and other operations -- then it can become not possible to "push things down"
into the view.

What happens (i'll use rownum cause it is easy to see the effect) is people
assume a view like:

create view v as select rownum r, ename from emp;

with a query like:

select * from v where ename = 'SMITH';

should be exactly the same as:

select rownum r, ename from emp where ename = 'SMITH';

Well, quite simply -- it is not:

ops$tkyte@ORA920> create or replace view v as select rownum r, ename from emp;
View created.

ops$tkyte@ORA920> select * from v where ename = 'TURNER';

         R ENAME
---------- ----------
        10 TURNER

ops$tkyte@ORA920> select rownum r, ename from emp where ename = 'TURNER';

         R ENAME
---------- ----------
         1 TURNER

See, the view returned A DIFFERENT ANSWER here.  But -- what people compare is
the performance of

select * from v where ...
and
select * from emp where .....

apples and toaster ovens.  Consider that a where against an outer join isn't the
same as a outer join against a where and so on and you'll see where the issues
come in.

It is not that the optimizer is 'confused'.  It is that the queries are
semantically different (as above).

So, you must be careful you are getting what you wanted when you join views --
just as you must be careful you are getting what you wanted when you skip the
views.  Tell me -- is  1 TURNER the right answer or is 10 TURNER?

So, don't be afraid of views of views (we just after all replace text in there
and optimize).  Just make sure views of views is the answer you want -- in the
most optimal fashion you can get it.

read

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96533/opt_ops.htm#1009807

Review &
      Followup

      Rating:
      4
  April 08, 2003
Reviewer:  jens

very great rownum example there ...

here is one, where rownum causes a query to be slow:

-- this takes HOURS ...
select B.Col1
from   ( -- ... although this returns only a FEW rows
         -- within a tenth part of a SECOND
         select A.Col1, RowNum
         from   <complex_view> A
         where  <some conditions>
       ) B

I took this statement from a more complex real case and couldn't figure out,
what 's going on here.

and just to be honest: query doesn't only get faster, if rownum is removed -
replacing "complex view" with a less complex one is another "solution".

but what is that rownum thing?

          Followup:

rownum is a psuedo column -- when it is in the inline view like that -- the
outer parts of the query cannot be "pushed" into it -- rownum must be assigned
AND THEN the outer part of the query can be done.

Don't view this as a performance nasty -- view it as a tuning tool.  I use
rownum in subqueries all of the time to make them go faster as well ;)

      Rating:
      4
  April 09, 2003
Reviewer:
      A reader
yes, but I didn't get it, why it takes longer to "wrap" the results from the
inner most query (taken VERY FAST) with an outer query simple as the above one.
lets say: the inner query selects 3 rows, and they get their rownums assigned.
what is left to be done after that for the outer query except of presenting the
set, which was generated by the inner query?

tom, I don't understand, what you mean by "Don't view this as a performance
nasty -- view it as a tuning tool."

          Followup:

show us the plans - then we can comment.

as for the "as a tuning tool" -- I'm just saying for every cloud there is a
silver lining.  I use rownum as a tuning technique, to make queries go faster
(as well as slower)

      Rating:
      2
  April 09, 2003
Reviewer:
      A reader
It looks horrible if I copy the output taken from SQLPLUS into this area here. I
prefer other tools, but now I have to spool the explain plans. how can I get the
explain plans formatted in a way that fits using SQLPLUS?

          Followup:

I do it all of the time?  Most of my pages have it.

Just cut out a couple of blanks here and there.

      Rating:
      3
  April 23, 2003
Reviewer:
      A reader
OK. let 's take this instead of the first sample:

create table test(a number, b varchar2(20), primary key(a))
/
insert into test(a, b) values(1, 'xxx')
/
insert into test(a, b) values(2, 'yyy')
/
insert into test(a, b) values(3, 'zzz')
/
commit
/
analyze table test compute statistics
/

set autotrace on explain statistics

this is the (fast) non rownum version:

select A.* from test

         A B
---------- --------------------
         1 xxx
         2 yyy
         3 zzz

----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=3 Bytes=15)
   1    0   TABLE ACCESS (FULL) OF 'TEST' (Cost=1 Card=3 Bytes=15)

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

and here is the version

select A.*, RowNum from test A

         A B                        ROWNUM
---------- -------------------- ----------
         1 xxx                           1
         2 yyy                           2
         3 zzz                           3

---------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=3 Bytes=15)
   1    0   COUNT
   2    1     TABLE ACCESS (FULL) OF 'TEST' (Cost=1 Card=3 Bytes=15)

----------------------------------------------------------
          0  recursive calls
          4  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        315  bytes sent via SQL*Net to client
        311  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          3  rows processed

after adding rownum an additional COUNT step appears.

such an additional COUNT step (in conjunction with an additional VIEW step above
it) is the only difference between the explain plans (slow and fast version) of
the "real world" queries (where COUNT is much more expensive!).
in general both explain plans for both related cases (slow and fast) look pretty
much the same.

where does the COUNT come from?

          Followup:

the count step in this example doesn't do anything.

how about another example that actually makes it do something "bad"

ROWNUM is the count guy.

do this

select a.*, rownum from test a where rownum < 100;

look at that plan -- see the count STOPKEY

The COUNT in this example does nothing other then assign ROWNUM a value.

      Rating:
      3
  April 24, 2003
Reviewer:
      A reader
so it must be the additional VIEW step, which makes the difference:

select V2.*
from   (
         select V1.*, RowNum
         from   Test V1
       ) V2
where  V2.a < 3

leads to:

         A B                        ROWNUM
---------- -------------------- ----------
         1 xxx                           1
         2 yyy                           2

----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=3 Bytes=114)
   1    0   VIEW (Cost=1 Card=3 Bytes=114)
   2    1     COUNT
   3    2       TABLE ACCESS (FULL) OF 'TEST' (Cost=1 Card=3 Bytes=15)

----------------------------------------------------------
          0  recursive calls
          4  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        284  bytes sent via SQL*Net to client
        311  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed
phases COUNT and VIEW only appear in conjunction with adding the rownum column.

in the "real cases" (querying a complex view instead of a table) these two
additional steps are the only differences between the execution plans for the
slow and fast version - they cause a hugh difference in costs calculated there
...

but why?

          Followup:

rownum, aggregates, distincts, set operations (like intersect, minus) outer
joins - many things can cause it to be "not possible" to push (merge) the outer
query with the inner query.

For example:

select v2.*
  from ( select v1.*, rownum from test v1 ) v2
  where v2.a < 3

is very very very very totally different then

select v1.*, rownum from test v1 where v1.a < 3

So, Oracle must resolve (the view step) the inline view AND THEN apply the
predicate to it.

The first query says "hey, please take the query select * from test, assign
rownum to it.  Then, after you've done that -- find the A's < 3 and output those
rows"

The second query says "Hey, please find the A's < 3, assign rownum and output --
you don't have to assign rownum to each and every row ok..."

ops$tkyte@ORA920> create table t ( x int, a int, data char(100) );

Table created.

ops$tkyte@ORA920> create index t_idx on t(a);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t select object_id, rownum, 'x' from all_objects;

30007 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> column data noprint
ops$tkyte@ORA920> set autotrace on
ops$tkyte@ORA920>
ops$tkyte@ORA920> select v2.*
  2    from (select v1.*, rownum from t v1 ) v2
  3   where v2.a < 3;

         X          A     ROWNUM
---------- ---------- ----------
     17602          1        127
      7516          2        128

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   VIEW
   2    1     COUNT
   3    2       TABLE ACCESS (FULL) OF 'T'

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

ops$tkyte@ORA920>
ops$tkyte@ORA920> select v1.*, rownum
  2    from t v1
  3   where v1.a < 3;

         X          A     ROWNUM
---------- ---------- ----------
     17602          1          1
      7516          2          2

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   COUNT
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'T'
   3    2       INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE)

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

ops$tkyte@ORA920> set autotrace off

See the difference -- the results are different but the plans are very much
different.  The first has to asign rownum to each ROW AND THEN apply the
predicate, the other can apply the predicate and then assign rownum to the rows.

You get what you ask for -- if you demand we "assign rownum then apply
predicate", we oblige (just like if you ask us to aggregate, etc etc etc)

      Rating:
      3
  April 26, 2003
Reviewer:
      A reader
my fault. sure - the predicate has to be part of the inline view, if one wants
to have consecutive row numbers assigned to the result set (in my test case the
SAME row numbers were generated - for SOME REASON?).

and in fact in the real world case both predicate and rownum pseudo column ARE
part of the inline view (so my sample was not appropriate - sorry about that).
the reason why the predicate could NOT be pushed INTO this view must be
somewhere inside its body. for instance some sub-views contain OUTER joins (you
mentioned above as possible cause). there are function calls in WHERE-clauses as
well (does that matter?).

for rownum it is clear enough. could you please give some more details
why/how/WHEN aggregates, distincts, set operations, outer joins within views
prevent predicates from being pushed into them (or do they in general)?

thanks in advance.

          Followup:

see chapter 2 of the Oracle9iR2 Performance and Tuning guide -- the section on
"how the optimizer transforms sql"

might be different chapter numbers in early docs -- all docs available on
otn.oracle.com under documentation.

      Rating: 4
But Oracle9i
      doc says that..  July 15, 2003
Reviewer:
      Tony  from India
You argue that views are not evil, but Oracle9i Database Performance Tuning
Release 2,Chapter 1 says

"Views can speed up and simplify application design. A simple view definition
can mask data model complexity from the programmers whose priorities are to
retrieve, display, collect, and store data.

However, while views provide clean programming interfaces, they can cause
sub-optimal, resource-intensive queries. The worst type of view use is when a
view references other views, and when they are joined in queries. In many cases,
developers can satisfy the query directly from the table without using a view.
Usually, because of their inherent properties, views make it difficult for the
optimizer to generate the optimal execution plan. "

This doc clearly says that views are not good. Your comment please?

          Followup:

where did it say "views are not good"

It did what I do all of the time -- NOTHING is black, NOTHING is white.  NOTHING
is entirely good.  NOTHING is entirely evil.

I would say the "usually" part in their statement is false.

      Rating: 3
Same strange
      issues with inline views  November 04, 2003

Reviewer:  Alla  from Boston, MA USA
Hi Tom;

I am having the same issue as Jens above.

I have a query:

select set1.supplier_number, set1.supplier_name,
   set1.amount, set2.amount
from (select *
      from (select supplier_number, supplier_name,
              amount
            from <very complex view>
            where account_key = :acct_key
              and time_key = :time_key_1
            order by amount desc)
      where rownum < 51) set1,
     (select supplier_number, supplier_name,
           sum(amount) as amount
      from <same complex view>
      where account_key = :acct_key
        and time_key in (:time_key_2, :time_key_3)) set2
where set1.supplier_number = set2.supplier_number;

Each inline view executes very fast: first one in 0.1 sec, second one in 0.3 sec

I was always under the impression that Oracle would execute both inline views
and then just merge them. Result sets are very small. First one is obviously 50
records, second one is about 150 records.

But when I try to run the whole query, it takes forever

Any ideas why?

Thanks in advance

Alla

P.S. I don't have an access to the server where Oracle is installed, so I can't
use tkprof :-(

          Followup:

need to see the plans (autotrace or explain plan) for

a) both inline views
b) the whole thing

 lets see the results of

set timing on
set autotrace traceonly statistics
<inline view 1>
<inline view 2>
<whole thing>

      Rating:
      2
  November 04, 2003
Reviewer:  Alla
       from Boston, MA USA
Is it possible to send this information directly to you, without it being
published here? (I am a consultant and it does not feel right to expose all the
tables etc)

Thanks a lot in advance

          Followup:

ask somebody if it is OK

you see, the reason this site exists is to collaborate.  it is not very scalable
for me to answer individual questions -- for that, you can try support and a
tar.  here, everything is on the out and out...

i think this would be instructive for everyone to work thru, i don't have the
bandwidth really to work person by person.

      Rating:
      3
  November 05, 2003
Reviewer:  Alla
       from Boston, MA USA
I dig further some more and found the following:

1. Explain plan for the whole query is not the same as explain plans for the 2
inline views, i.e. I would've assumed that the explain plan for the whole query
would be something like this:
HASH JOIN
 PLAN FOR THE INLINE VIEW 1
 PLAN FOR THE INLINE VIEW 2

But it looked like

HASH JOIN
  PLAN FOR THE INLINE VIEW THAT DOES NOT HAVE ROWNUM
   (same as the explain plan for the inline view)
  PLAN FOR THE INLINE VIEW WITH ROWNUM
   (completely different from the explain plan)

So, it all boiled down to exactly the same problem as Jens was having.

As soon as I take the rownum out of there, it works great.

So, what I ended up doing was re-writing a query like this

select *
from (select ...
      from (select ...
            from ...
            where ...) set1,
           (select ...
            from ...
            where ...) set2
      where set1.column1 = set2.column1
      ORDER BY ...)
where rownum < 51.

I.e. instead of limiting first inline view to 50 rows and then join it to the
second inline view, I let them both be as many rows as the DB returns, then join
them, order by and only then limit the number of rows

Any clues????

Thanks

P.S. Sorry, I still can't send those explain plans, but I thought that at least
the solution would be helpful for people even if we don't know the reason why it
was not working as (at least) I expected it should

November 04, 2003 R -- Thanks for the question regarding "caching
      prepared callable statements in java", version 9.0.1

      You Asked
Tom
Thanx for a great web site!:)

Elsewhere you have said

"If you are programming in java -- i'm saying DO NOT

   o prepare
   o bind
   o execute
   o close

all of the time -- instead

  o prepare in your CONSTRUCTOR

  o bind + execute in your methods over and over"

I am using JDBC - and i use pl/sql procedures that get
called as follows:
----
 try
 {
   callableStmt = (OracleCallableStatement) connection.prepareCall (
         "begin ? := f(?,?); end;");
      callableStmt.registerOutParameter(1, OracleTypes.INTEGER);
      callableStmt.setRAW(2, bla);
      callableStmt.setString(3, bla2);

      callableStmt.execute();
      result = (int) callableStmt.getInt(1);
    }
    finally
    {
      callableStmt.close();
    }
----

I was wondering whether your comment applies to the above code
which means should I create a cache of above callable statements
based on perhaps a session id and the statement string?
Is that possible, given that the connection object itself may change
because it comes from another layer of code of connection
pooling in my case - what i am saying is
in the "connection.prepareCall", the connection itself may be
different for different executions of the above
code because of connection pooling - in the *same* session.

How would you implement a "cache" in such a case?
Also, how costly is it to not have the above cache - i
just want to get a sense.

I guess the "cache" would close the statements once the
session expires?

      and we said...
connection pooling mucks this up.  The callable statement can only be reused in
the context of that connection.

So, you should grab a connection
prepare your callable statement
  use it over and over and over if you can
close it
give back the connection

How costly not to have?  see

http://asktom.oracle.com/~tkyte/runstats.html
and look at the linked to example at the bottom.  The more you parse, the less
you scale.
Review & Followup

      Rating:
      5
  May 30, 2003
Reviewer:
      A reader

      Rating: 5
It pays to
      cache PreparedStatements  May 30, 2003
Reviewer:
      Peter Tran  from Houston, TX USA
We have the same issue.  We wrote a test program to show the performance impact
of having to do hard parse vs soft parse vs parse once in JAVA.

Here's the result.

# records | 64  | 128 | 256  | 512  | 1024 | 2048 | 4096  | 8192  | 16384
--------------------------------------------------------------------------
Cached PS | 0.3 | .21 | 0.38 | 0.58 | 1.09 | 2.08 |  4.20 |  8.51 | 16.69
PS        | 0.4 | .65 | 1.16 | 2.03 | 3.54 | 6.62 | 13.39 | 26.65 | 58.16
Statement | 0.4 | .67 | 1.29 | 2.44 | 4.51 | 9.04 | 18.19 | 35.70 | 74.47
--------------------------------------------------------------------------

(I would print out more, but it didn't fit very well.  If you graph the above
numbers, you'll see that the Statement (hard parse) starts to show an almost
exponential runtime growth compared to using the cached PreparedStatement (parse
once) as we processed more records.  Using the PreparedStatement and closing it
each time (soft parse) isn't as bad as using the Statement, but it still pretty
expensive.)

The test itself was pretty simple.

* Create a test table inserting 100,000 rows of data.
* Retrieve N number of records for each iteration
  and call the 3 test methods below.
* Create three test methods
  * Statement using literal (hard parse)
  * PreparedStatement using bind variable (soft parse)
  * Cached PreparedStatement (parse once)

The first two test methods:
 * Gets a connection
 * Creates a Statement or PreparedStatement
 * Executes a query against test table
 * close PS
 * close connection (puts back into pool)

The third test method:
 * Gets a cached connection
 * Gets the cached PreparedStatement associated with
   the above connection
 * Exectes a query agaist test table
 * Put PS back into cache
 * Put connection back into cache (not into pool)

The PreparedStatement using bind variable is currently how we code it in our
application, because we too use connection pooling.  With connection pooling,
JAVA developers are taught to get the connection whenever they need it (cheap),
create the PreparedStatement, use it, and then close all resources.

In our applications, we have CORBA servers that do a lot of batch processing,
but they get their unit of work in spurts.  Within those unit of work, the
server batch up and reuse the PS as much as possible, but then it closes all
open resources before returning to the client.  So, if a client makes 1,000,000
request to the server sending 1000 records to process each time, it still doing
1,000,000 soft parses.

What we don't see is doing a lot of soft parsing can really add up quick and
impact performance.

As Tom says, with connection pooling you have to "roll" your own caching scheme.

We did implement our own caching scheme, but here's some issues you should be
aware of if you're going to do the same thing.

1)  A cached PreparedStatment (PS) is tied to a connection, so you have to also
cache the connection.  That means you need a way to get the same connection all
the time from the connection pool.  If your application is using Global
Temporary Tables (GTT), then you cannot share the connections.

2)  Caching the connection and PreparedStatement reduce the number of available
cursors.  The limit is set by the open_cursors configuration.  If you exceed
this value, then you will get "ORA-01000 maximum open cursors exceeded."  Thus,
you may need to limit what gets cached to SQLs that are executed a lot and are
really expensive to parse.  (You'll need to profile your code and study
statspack to find these SQLs.)

3)  Your caching scheme needs to handle cleaning up all open resources (PS,
connection).  A couple of options are: have the client invoke a cleanup task at
the end of the processing, or have a daemon thread run in the background and
auto-close the PS and connections (e.g. GC thread in the JVM).

4)  If you're caching scheme requires you to write wrapper classes that
implements the java.sql.* interfaces, then you'll have issues with trying to
support both JDK 1.3 and JDK 1.4 at the same time, because the interfaces
changed in JDK 1.4 to support JDBC 3.0.

5)  If you application is multi-threaded, then you need to handle the situation
when two threads can possibly ask for the same connection and the same
PreparedStatement.

          Followup:

to make that test more interesting (i have a very very similar benchmark I use
and will be publishing) make it multiuser!

not only are statements slower in single user, they will KILL YOU -- seriously
dead -- in multi-user environment.

thanks for the independent outside confirmation.

      Rating: 5
multi-thread
      the test  May 30, 2003
Reviewer:  Peter Tran
       from Houston, TX USA
I need to validate that the wrapper classes can handle multiple threads.

Using multiple threads isn't true a multi-user environment (from the CORBA
server perspective), but it's pretty close to simulating one.  To the database,
the multiple threads will look like multiple users.

I'll publish updated results when I get them.

      Rating: 5
caching
      prepared callable statements in java  May 30, 2003

Reviewer:  R Menon  from CA, USA
Thanx for the reponses, Tom and Peter Tran - appreciate it!
Peter, I would be curious to see the updated results of
the multithreaded version of tests.

Tom I had a quick related question. Till now
I associated cursor with the ResultSets. Now I see
that the cursor is just a state - it could be associated
with a resutlset or a statement itself (the statement's
state would consists of anything that can be
set using the "statement.set" methods. Do you agree
or do you want to comment on this?

On my subsequent investigation I found that with JDBC 3.0
you can now cache statements using JDBC features.
If you use implicit caching then regardless of
whether you do connection pooling or not, your
statements get cached by the database (apparently
there is a cache per connection)
Not sure about the implications on the max cursors
reached problem.

Here is a segment from Oracle's JDBC book.

"Statement caching improves performance by caching executable statements that
are
used repeatedly, such as in a loop or in a method that is called repeatedly.
JDBC 3.0
defines a statement-caching interface.
Statement caching can:
-  Prevent the overhead of repeated cursor creation
- Prevent repeated statement parsing and creation"

More interesting details are in the jdbc doc at

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/java.920/a96654/toc.htm
In particular see chapter 14

          Followup:

a cursor is just a pointer to some sql statement -- be it an
insert/update/delete/merge/plsql block whatever.

we have many tools and techniques to allow developers to be lazy (session cached
cursors for example, cursor sharing, etc) however, none of them can ever
approach the performance you would get by doing it right in the first place.

You want fast, do it right.

      Rating: 5
It pays to
      cache PreparedStatements   June 04, 2003
Reviewer:
      J. Smucr  from Czech Republic
Excellent thread.
Peter Tran  from Houston, TX USA, wrote:

<quote>
1) A cached PreparedStatment (PS) ... If your application is using Global
Temporary Tables (GTT), then you cannot share the connections.
</quote>

Could you please tell us more about this?
Many thanks.

          Followup:

if they are "ON COMMIT DELETE ROWS" i don't see the relevance of that statement
either.

      Rating: 5
Sharing a
      connection and GTT  June 04, 2003
Reviewer:
      Peter Tran  from Houston, TX USA
Tom,

Correct me if I'm wrong, but if two threads share the same connection and get
the same cached PreparedStatement, then won't you run into problems because the
two threads will start seeing each other's data?

I thought GTT requires each thread to have its own session.

-Peter

          Followup:

when using connection pooling, it is imperative that you commit or rollback
before giving the connection back (end the transaction) -- else the next guy is
in the middle of your mess.

So, you always commit/rollback.

If you use "ON COMMIT DELETE ROWS", the gtt's empty themselves transactionally
using the equivalent of "truncate".

You'll not see anyones data.

      Rating: 5
Caching
      connection  June 04, 2003
Reviewer:  Peter
      Tran  from Houston, TX USA
Tom,

My comments were in regards to my caching scheme.

A cached PreparedStatement is associated with a connection, which requires the
connection to also be cached.  In my caching implementation, I cache both the
connection and all cached PreparedStatement associated with that connection.
Thus, when I get a connection from the pool, I don't give it back to the pool
until the client completely exits.

The cached connection is stored in a java.util.Map.  To get the connection, you
have to provide a key name.  The value of the map is a java.util.Stack of cached
connections.  I did it this way to support the ability for multiple threads
requesting the same key name to get its own connection.

-Peter

      Rating: 4
caching
      prepared callable statements in java  June 05, 2003

Reviewer:  A reader
This question is for Peter and Tom.
Why would you still need your own caching scheme
since JDBC (at least JDBC 2.0 and above) seems to
provide its own per connection statment caching scheme
(As per my note earlier) - Is n't that the best way to solve
this problem?

Repeating the link below for your convenience.

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/java.920/a96654/toc.htm
In particular see chapter 14

          Followup:

as long as tkprof says "it was parsed once", i would be 100% for it.

      Rating: 5
Why we don't
      use ORACLE built-in cache  June 06, 2003
Reviewer:
      Peter Tran  from Houston, TX USA
R Menon,

Here's the reason why we implemented our own caching scheme.

1)  We can use any JDBC driver with our own caching scheme.  We're not dependent
on ORACLE.  Not all JDBC implementation have a built-in caching scheme.

2)  We didn't want to cast our classes to an ORACLE implementation.  We wanted
our JDBC access to be database ignorant.  (Great for portability, sucks for
performance).

3)  A cached PreparedStatement is associated with a connection.  With connection
pooling, you're not guaranteed to get the same connection.  Therefore, even if
you went with ORACLE's built-in cache, you'll need some way to hold  the same
connection.

[quote from your link]
Because each physical connection has its own cache, multiple caches can exist if
you enable statement caching for multiple physical connections. When you enable
statement caching on a pooled connection, all the logical connections will use
the same cache. If you try to enable statement caching on a logical connection
of a pooled connection, this will throw an exception.
[/quote]

From the above quote, [Tom - correct me if I'm wrong], this will break using GTT
if two threads gets the same logical connection and access the same cached
Statement.  You can't control this if you use all the ORACLE built-in caching
features.

4)  There are other things we can do with the wrapper classes that is outside
the cache.  E.g. When an exception is thrown from using a PreparedStatement,
typically you don't get any information about the actual SQL string.  We can
trap for this and log out more information, etc.

5)  I haven't tested the ORACLE 9.2.0.3 driver implementation Statement caching,
but the 9.0.1 implementation had some serious bugs.

6)  Not sure how the ORACLE implementation is going handle the issue when all
available cursors have been exhausted because there's so many cached Statement.

-Peter

          Followup:

1) that is not a benefit to a database guy (any database guy).  guess what
happens if you take the data away?  you've got a bunch of bits that do nothing
called code.  guess what happens if you take the code away?  you have all of
your data -- you can start over if you like.

2) pay attention to that last part.

3) no, you use gtt's with ON COMMIT DELETE ROWS.  you cannot -- repeat -- cannot
have transactions shared by more then one person so a connection grab will grab
a connection -- do a transaction and either commit or rollback.  you would not
use SESSION based gtts

      Rating: 5
Comment on
      Tom's Followup:  June 06, 2003
Reviewer:  Peter
      Tran  from Houston, TX USA
1)  If you take the data away, then it's a mute point whether I use Oracle's
implementation or my own caching scheme.  I wouldn't need to access the data
because there is none.  The wrappers are written so they implement the
java.sql.* APIs, so technically, my application code using the wrapper classes
don't have to change.  I would only have to disable my own caching scheme and
use the native driver version.

We have to evaluate the entire system's architecture when we make a decision to
use a tool or go with a certain design and weigh portability, maintainability,
performance, etc. - there are trade-offs.  In this case, the trade off was to do
a little more work on our side to maintain the portability benefit.

R Menon - It was only in JDBC 3.0 specification that mentions anything about
Statement caching, but the implementation is still left up to the driver
provider.  You'll find that not all ORACLE JDBC providers have implemented the
JDBC 3.0 specification, so if you decide to use a non-ORACLE JDBC implementation
driver, you may have to provide your own caching implementation.

2)  Trust me, I know.  Generic implementations are great (sometimes), but more
often than not, performance takes a hit.

3)  I understand what you're saying.  In other words, if the application
programmer decides to create multiple threads and share the same logical
connection between these threads, then the programmer is doing something stupid
and therefore deserves all the headache that comes along with doing something
stupid.

Thanks,
-Peter

      Rating: 5
Comment on
      Peter's comments titled" Why we don't use ORACLE built-in cache
      "  June 08, 2003
Reviewer:  R Menon
       from CA, USA
in my case 1 and 2 are not a concern - Also, I Am a little
skeptical of sacrificing performance for portability
since if you are switching a database then may be you would have to worry about
even more important things - but
mostly you dont change your database often. What I am
saying is that many of us end up sacrificing performance
for nothing (I am not saying that is the case with you, Peter)

Regarding the third point, i did not quite understand
it - may be i did not understand the quote itself:)
Are you saying that if an application is using GTT
then the connection would have to be saved between
calls? I thought the whole point is to not worry
about saving connections since each connection would
have its own cache (which has to be the case in any
implementation since you can not share statements across
connections anyways.)

I am also curious to know about the point 6 you raise about
how Oracle deals with the fact that it may have lots
of cursors opened due to the caching scheme. I suspect
it deals with it in a typical cache management where
the cache size itself is lower than the max cursors
limit and then lru is used to recycle cache entries?

      Rating: 5
Reply to R
      Menon  June 10, 2003
Reviewer:  Peter Tran
       from Houston, TX USA
You don't have to switch database to have problems.  Just switching driver
implementation will bring its own set of headaches.  E.g. if you use the ORACLE
implementation for caching, then either your variable handles will be Oracle
classes, or you will need to cast to an Oracle implementation inside your class.
 Now imagine changing from Oracle implementation to say Merant driver (another
JDBC implementation for ORACLE).  How many different places in your code will
you have to refactor?

With my wrapper, I only have to do it in a couple of classes.

As to the third point, the problem I brought up shouldn't be an issue if the GTT
was created with ON COMMIT DELETE ROWS as Tom points out.  A connection (even if
it's cached) shouldn't be shared across different threads, so each thread should
commit before releasing the connection back to the cache.

Oracle's implementation allows the client to limit the number of
PreparedStatements that is cached.  The client is allowed to control the maximum
number of cached PS, but it doesn't do anything if the client sets it to a high
value and exhausts all available cursors.

Unless you want to do a lot of bookkeeping, there's very little you can do if
the client goes berserk and starts to cache everything.

      Rating: 5
reply to peter
      Tran  June 11, 2003
Reviewer:  R. Menon
       from ca
Thanx Peter
You are right - the driver change could itself
be a problem. I have worked only with Oracle's driver!
Anyways, in my case I dont expect even the driver to
change.

Btw, does a driver change mean that
Oracle specific extensions provided by Oracle's
JDBC driver (that could improve performance considerably)
will go away? I hope not!

The problem of the cache size that you mention may  not
be really an issue - it is upto the client to set
the cache size properly and managing that in itself
should not be a big deal.

      Rating: 5
Reply to R
      Menon  June 11, 2003
Reviewer:  Peter Tran
       from Houston, TX USA
[quote]
Anyways, in my case I don't expect even the driver to change.
[/quote]

I didn't either.  You'll never know what's going to happen, but I have to admit,
Oracle's JDBC implementation have gotten better.  The earlier implementation
(e.g. 9.0.1) were notoriously buggy.

[quote]
Btw, does a driver change mean that Oracle specific extensions provided by
Oracle's JDBC driver (that could improve performance considerably) will go away?
I hope not!
[/quote]

Yep.  There's no guarantee that the other driver implementation will use the
same extensions.  Why would do they have to?

[quote]
The problem of the cache size that you mention may not be really an issue - it
is upto the client to set the cache size properly and managing that in itself
should not be a big deal.
[/quote]

Imagine a really large client/server application where the servers are CORBA
services written by different developers.  Each developer is going to try to get
the best performance out of his/her component, so will probably cache as many
PreparedStatements as possible.  Now you have a N CORBA servers x M CORBA
services x O PreparedStatements, and you can exhaust the database resources
really quick.

      Rating: 5
reply to Peter
      Tran   June 19, 2003
Reviewer:  R Menon
       from CA
Hi Peter
"Yep.  There's no guarantee that the other driver implementation will use the
same extensions.  Why would do they have to?
"
They dont - It is just that since in my case, I use
many of the performance enhancing extensions that
are Oracle specific, I was thinking that would
be a disadvantage. But again, the assumption
(in my case) is that the driver wont change.

"Imagine a really large client/server application where the servers are CORBA
services written by different developers.  Each developer is going to try to get

the best performance out of his/her component, so will probably cache as many
PreparedStatements as possible.  Now you have a N CORBA servers x M CORBA
services x O PreparedStatements, and you can exhaust the database resources
really quick."

I get that - I guess there needs to be a central control
over such cases. It would be more of a guideline
unfortunately - something that says that each developer
can not use more than "x" number of entries in theprepared statements cache. Not
very elegant though.

Thanx!

      Rating: 5
Native dynamic
      query under PL/SQL and avoiding soft parse  November 04,
      2003
Reviewer:  Piotr Jarmuz  from Poznan, Poland

Hello Tom,

First, sorry for not being 100% thread compatible.

In the system that I am building I need to use dynamic queries with predicates
qlued at runtime with object views. The queries are built from object views and
so called selectors. The selectors can be nicely combined with OR and AND and
are automagically inherited because if they are defined and attached to some
base class then any derived class sees them right away. Selectors are associated
with indices if indexing make sense for them. There could be potentially
hundreds of thousends of them (queries) so it is not possible (practical) to
code them all statically but a specific application would use only small
fraction of that potential < 1000 definitely. This glueing is part of business
logic and polymorphism, however and is placed in PL/SQL packaged procedures.
This on the other hand causes the query to be softparsed each time it is
executed. I can't use dbms_sql package because I need to return refcursors to
clients. As far as I know there is currently no way to do it in PL/SQL. So I
plan to use a little trick. The stored procedure would not return the refcursor
but rather the query string with embedded bind variables and info how many
parameters it needs to the client. Client library knows the parameters for
binds. And then at client site buried in library code I would use custom
statement cache in JDBC or built-in in OCI and Perl DBI to avoid the soft parse
altogether. I see no performance issues at all in current implementation however
I'd like to squeeze every possible cycle out of the CPU. Another reason is Perl
(our primary rapid development langauge of choice) and DBD driver problem with
refcursor performance. It does not prefetch on such cursors so it is order of
magnitude slower than straight select and this trick would also fix that. I'd
still control any possible SQL in one centralized place so no problem with
hunting down the culprit clients for bad SQL.

Could you please comment on that? Is it worth the candle?

It would be nice if native dynamic execute could also use a statement handle
cache per DB session.

Thank you in advance.

Regards,
Piotr

          Followup:

just use session_cached_cursors=100 and let us do a softer soft parse.


Subject | Article | Last Updated (East coast time)
How is the where clause applied | article only | Nov 05, 2003 07:44 am
Partition level export and global indexes | article only | Nov 05, 2003 07:53 am
xmldb | article only | Nov 05, 2003 08:09 am
Locking and Cocurrency in 9i. | article only | Nov 05, 2003 08:28 am
tune these max statements with subquery | article only | Nov 05, 2003 09:29 am
data files | article only | Nov 05, 2003 09:37 am
INLIST ITERATOR | article only | Nov 05, 2003 09:43 am
Spawn Jobs from a Procedure that run in parallel | article only | Nov 05, 2003 05:39 pm
Oracle Database recursive table relationships | article only | Nov 05, 2003 06:07 pm
Partitioning question | article only | Nov 05, 2003 06:16 pm
Mass Insertion . | article only | Nov 06, 2003 07:07 am
Replication | article only | Nov 06, 2003 07:08 am
full table scan & unique-nonunique indexes | article only | Nov 06, 2003 07:10 am
row migration | article only | Nov 06, 2003 07:16 am
Jave Stored Procedure vs PL/SQL Stored Procedure | article only | Nov 06, 2003 07:29 am
Table Compression -- in regards to Jan/Feb 2003 article | article only | Nov 06, 2003 07:33 am
sqlplus command line usage | article only | Nov 06, 2003 07:36 am
Regarding Library Cache | article only | Nov 06, 2003 07:44 am
global index on hash partitioned table | article only | Nov 06, 2003 07:49 am
local index on OLTP partitioned table | article only | Nov 06, 2003 07:53 am
Profile | article only | Nov 06, 2003 05:22 pm
v$sqltext issues | article only | Nov 06, 2003 05:24 pm
interMedia Text questions | article only | Nov 06, 2003 05:33 pm
Rows locks from select for update clause | article only | Nov 07, 2003 08:11 am
using aggregate functions | article only | Nov 07, 2003 08:57 am
Limit the object created for a user | article only | Nov 07, 2003 09:08 am
oracle 9i | article only | Nov 07, 2003 09:57 am
TIMESTAMP columns in DBA_OBJECTS | article only | Nov 07, 2003 10:29 am
create a file for excel | article only | Nov 07, 2003 10:34 am
Optimizer Plan Stability | article only | Nov 07, 2003 10:46 am
Problem in Archiving data | article only | Nov 07, 2003 02:46 pm
long raws -- viewing and getting the length | article only | Nov 07, 2003 02:47 pm
Single Schema vs Multiple Schema - pros & cons | article only | Nov 07, 2003 05:22 pm
Cursors in package spec | article only | Nov 07, 2003 05:26 pm
About Oracle Production numbers | article only | Nov 08, 2003 10:13 am
SPFILE parameter | article only | Nov 08, 2003 08:20 pm
Restricted session | article only | Nov 08, 2003 09:46 pm
How to parse the input string and construct dynamic sql efficiently | article only | Nov 08, 2003 09:50 pm
Data Archival and Retrieval | article only | Nov 09, 2003 07:02 am
import of a 8.1.7 export file | article only | Nov 09, 2003 02:05 pm
The use of extended characters in a varchar2 field (accent) | article only | Nov 10, 2003 07:21 am
How to merge columns data into single CLOB column | article only | Nov 10, 2003 07:36 am
Cluster on One Table | article only | Nov 10, 2003 07:40 am
partition pruning | article only | Nov 10, 2003 07:43 am
TWO_TASK environment variable and LD_LIBRARY_PATH | article only | Nov 10, 2003 07:45 am
Q: nologging option | article only | Nov 10, 2003 07:48 am
Book update | article only | Nov 10, 2003 07:49 am
Sorting groups | article only | Nov 10, 2003 07:59 am
deterministic package function | article only | Nov 10, 2003 08:11 am
Why isn't this using an index | article only | Nov 10, 2003 11:58 am
why multiple versions? What would be the cause of BIND_MISMATCH? | article only | Nov 10, 2003 12:07 pm
Deleting duplicate records without using rowid and rownum | article only | Nov 10, 2003 03:05 pm
Oracle9i, init.ora isn't working | article only | Nov 11, 2003 07:13 am
Several Doubts | article only | Nov 11, 2003 07:28 am
Oracle9i ANSI -INNER/OUTER - joins with multiple tables | article only | Nov 11, 2003 07:32 am
Fine Grained Access ERROR on INSERT when generating unique keys | article only | Nov 11, 2003 07:34 am
Who is locking my row(s)? | article only | Nov 11, 2003 07:56 am
date fomat containing timezone data | article only | Nov 11, 2003 08:07 am
Generating Web Based Reports | article only | Nov 11, 2003 11:49 am
DBMS_STATS and MONITORING | article only | Nov 11, 2003 01:41 pm
LOB | article only | Nov 11, 2003 01:43 pm
JDBC : SQL vs PL/SQL, Which performs better | article only | Nov 11, 2003 01:44 pm
Running procedures dynamically. | article only | Nov 11, 2003 01:46 pm
unindexed foreign keys | article only | Nov 11, 2003 02:16 pm
row(s) 1 - 64 of 64
November 05, 2003 Nag -- Thanks for the question regarding "How is the where clause applied", version 8.1.7
You Asked
Tom

I have a scenario
select columns,date_column
(
Select columns ,date_column
from Table1,Table2

union

Select columns ,date_column
from Table1,Table2

union

Select columns ,date_column
from Table1,Table2

union

Select columns ,date_column
from Table1,Table2) testtab

where date_column between :date1 and :Date two

1.Is the where clause applied after the whole of the rest of the query is
resolved. That is are all the rows retrieved and then the validation is done to
see if the date column occurs between the two dates.

2.If I push the date validation into each of the individual queries will it be
better.
I'm asking this because when when I remove the where condition it executes in 80
seconds but when I add the condition to check dates for a range of 3 months , it
takes over 10 minutes.

3.If the format of date_column and :date1 and Date2 are different will the query
fail( because I'm using abetween and not an '=').
(If date1 and date2 are varchar2 , is it o.k. if I convert date_column into
varchar2 )

Thanks
and we said...
use sql_trace+tkprof or autotrace and all will be revealed.

The answer is "it could be pushed down into the inline views" or "it might not
be being pushed down".

In this case, I'm going to guess -- it is being pushed down and instead of doing
full scans, we are using an index and doing an index range scan to get three
months of data is being really slow for you (unoptimal single block IO instead
of really fast full scanning).

Now as for the last part -- if I've said it once, I've said it a billion times:

compare DATES to DATES
STRINGS to STRINGS
NUMBERS to NUMBERS

and never never never compare

Strings to numbers
numbers to dates
dates to strings
etc......

USE TO_DATE(:date1,'format') and TO_DATE(:datetwo, 'format' )

Just do it.  Always compare a date to a date.  Period.  Always use an explicit
format.  Period.  If you don't, you have a bug just waiting to happen.
Review & Followup
Rating: 5
where clause September 01, 2002
Reviewer: Nag
I'm using the where clause after building the entire inline view. So in my
opinion the where clause will be applied after the inline view is answered and
all the rows are collected. I will have to wait untill next week to do the
sqltrace or autotrace.

But in the mean time can you expand on the following with an illustration.

***********
it is being pushed down and instead of doing
full scans, we are using an index and doing an index range scan to get three
months of data is being really slow for you (unoptimal single block IO instead
of really fast full scanning).
***********

Please give an illustrated example for the above.

Followup:
OPINIONS DO NOT COUNT.

ops$tkyte@ORA920.US.ORACLE.COM> create table t1 ( dt date );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM> create table t2 ( dt date );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> exec gen_data( 'T1', 100 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> exec gen_data( 'T2', 100 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create index t1_idx on t1(dt);

Index created.

ops$tkyte@ORA920.US.ORACLE.COM> create index t2_idx on t2(dt);

Index created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set autotrace on
ops$tkyte@ORA920.US.ORACLE.COM> select *
  2    from ( select *
  3             from t1
  4               union
  5                   select *
  6                     from t2
  7             )
  8   where dt between to_date( '01-jan-2001', 'dd-mon-yyyy' )
  9                and to_date( '31-dec-2001', 'dd-mon-yyyy' );

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   VIEW
   2    1     SORT (UNIQUE)
   3    2       UNION-ALL
   4    3         INDEX (RANGE SCAN) OF 'T1_IDX' (NON-UNIQUE)
   5    3         INDEX (RANGE SCAN) OF 'T2_IDX' (NON-UNIQUE)

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

That shows the where clause is being pushed down.  We do two index range scans
AND THEN union the data.  If the where clause was not pushed, we couldn't use
the indexes!

I know you have my book (well, you should given the number of questions you post
;)  See the chapter on indexes -- pages 278-284 for an example of what I mean.

Rating: 5
error September 03, 2002
Reviewer: Nag
SQL> create table t1 ( dt date );

Table created.

SQL>
SQL>  exec gen_data( 'T1', 100 );
BEGIN gen_data( 'T1', 100 ); END;

*
ERROR at line 1:
ORA-00904: invalid column name
ORA-06512: at "SCOTT.GEN_DATA", line 34
ORA-06512: at line 1

Is the above because I did not create synonym for T1 and Gen_data?

Followup:
No, it is because gen_data is a little routine I wrote to generate data.
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1552006405645

Rating: 5
query September 03, 2002
Reviewer: Nag

TOM

This is the actual query. If I qeury for a single day range for ln_date , then
the qeury is returning data in 23 seconds. But when I increase the range to 3
months it is taking more than 10 7 minutes.

Can you suggest a better way to write this query

SELECT  LNS.LN_CAT_SEC,
          LNS.LN_KEY,
          LNS.LN_DATE
          LBWS.LN_BORROWER,
          LBWS.TO_DATE(LN_BRW_BG_MONTH||'01','YYYYMMDD'),
          LBWS.LAST_DAY(TO_DATE(LN_LAST_MTH||'01','YYYYMMDD')),
          LNS.LN_COMP_NAME
          LBWS.LN_BORW_KEY,
          LNS.LOAN_TERM,
          LT.LN_TYPE,
          LBWS.LOAN_CATEGORY,
          LBWS.LOAN_PRIORITY,
          LNS.LOAN_STATUS

        FROM LOANS LN,
          LOAN_BORROWERS LBWS,
          LOAN_OPTIONS LNS ,
          LOAN_TYPES LT
    WHERE LNS.LN_KEY = LN.LN_KEY
      AND LNS.LN_BORW_KEY =LBWS.LN_BORW_KEY(+)
      AND (LNS.LN_BORW_KEY IS NULL OR LNS.LN_BORW_KEY IN (SELECT LN_KEY FROM
LOAN_BORROWERS WHERE LOAN_STATUS in ('D','A','P')))
      AND LT.LOAN_TYPE = 'LONG'
       AND LNS.LN_BORW_KEY NOT IN (SELECT LBS.LN_KEY
                                   FROM LOAN_BORROWERS LBS
                                   WHERE LNS.LN_BORW_KEY=LBS.LN_KEY)
      and to_char(lns.ln_date,'yyyy/mm/dd') between '2001/01/01' and
'2001/3/01'

Thanks

Followup:
Is LN_KEY NOT NULL in LOAN_BORROWERS?

If not, try using where NOT EXISTS instead of NOT IN.

also, don't to_char(dt) and compare to strings.  to_date the strings!!!

Rating: 5
Yes, it is a not null column September 03, 2002
Reviewer: Nag
I.
But Tom  what you have advised is right in contradiction to what you have
advetised  on
http://asktom.oracle.com/pls/ask/f?p=4950:8:1244890::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:442029737684,%7Bnot%7D%20and%20%7Bexists%7D
Here you have said...

"NOT IN can be just as efficient as NOT EXISTS -- many orders of magnitude
BETTER even -- if an "anti-join" can be used (if the subquery is known to not
return
nulls)
"

In my case above the only time subquery can return null is when

(SELECT LBS.LN_KEY
FROM LOAN_BORROWERS LBS
WHERE LNS.LN_BORW_KEY=LBS.LN_KEY)

does not return a row.

What is the basis of your advise that NOT EXISTS might be better, cause as per
your opinion in the given link, not in should be a better choice here.

Please discuss.

II.
The changed query:

SELECT  LNS.LN_CAT_SEC,
          LNS.LN_KEY,
          LNS.LN_DATE
          LBWS.LN_BORROWER,
          LBWS.TO_DATE(LN_BRW_BG_MONTH||'01','YYYYMMDD'),
          LBWS.LAST_DAY(TO_DATE(LN_LAST_MTH||'01','YYYYMMDD')),
          LNS.LN_COMP_NAME
          LBWS.LN_BORW_KEY,
          LNS.LOAN_TERM,
          LT.LN_TYPE,
          LBWS.LOAN_CATEGORY,
          LBWS.LOAN_PRIORITY,
          LNS.LOAN_STATUS

        FROM LOANS LN,
          LOAN_BORROWERS LBWS,
          LOAN_OPTIONS LNS ,
          LOAN_TYPES LT
    WHERE LNS.LN_KEY = LN.LN_KEY
      AND LNS.LN_BORW_KEY =LBWS.LN_BORW_KEY(+)
      AND (LNS.LN_BORW_KEY IS NULL OR LNS.LN_BORW_KEY IN (SELECT LN_KEY FROM
LOAN_BORROWERS WHERE LOAN_STATUS in ('D','A','P')))
      AND LT.LOAN_TYPE = 'LONG'
AND not exists (SELECT LBS.LN_KEY
                                   FROM LOAN_BORROWERS LBS
                                   WHERE LNS.LN_BORW_KEY=LBS.LN_KEY)
      and lns.ln_date between to_date('2001/01/01','yyyy/mm/dd') and
('2003/01/01','yyyy/mm/dd')

Pls note that I have removed the to_char on the lns.ln_date and have converted
the strings to dates. But the issue here is my date column is of the format
mm-dd-yyyy, and my date  range is in the format yyyy/mm/dd. How should I convert
lns.ln_date into yyyy/mm/dd format without converting into char?

Thanks

Followup:
it is not in conflict at all.  You need to read more carefully.  Read what I
said in the answer:

is LN_KEY NOT NULL in LOAN_BORROWERS?

If not, try using where NOT EXISTS instead of NOT IN.

that says simply:

If the table LOAN_BORROWERS *allows* for nulls in LN_KEY, then try using not
exists over not in.

That is 100% in agreement with:

(if the subquery is known to not return nulls)

The only way the subquery is known to not return nulls in this case if it LN_KEY
is defined in the CREATE TABLE as NOT NULL.

I discuss not in vs not exists in the link you so kindly put here.

II) you totally don't understand dates at all?

I tried to explain here:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5146386543335
Think of a date like a number -- does a number have commas?  dollar signs?  a
minus?  no, not really -- it is just bit and byte data stored in a table and all
of those things are FORMATTING information.

So is mm/dd/yyyy -- etc.  The FORMAT you use to convert a DATE to a STRING, or
in this case a STRING to a DATE -- have NO MEANING past the conversion.  The
storage of the data in the database is 100% unaffected by the format.

select to_date( '01012002', 'ddmmyyyy' ),
       to_date( '01012002', 'mmddyyyy' ),
       to_date( '01/01/2002', 'mm/dd/yyyy' ),
       to_date( '01-jan-2002', 'dd-mon-yyyy' ),
      ........

no matter how you "format it" - they are all jan 1st, 2002.  They are all dates
-- they all have the same exact 7 bytes.

There is no "converting" needed.  Your "date column has NO FORMAT".

Rating: 5
Oops my mistake September 03, 2002
Reviewer: Nag
you said..

"
Is LN_KEY NOT NULL in LOAN_BORROWERS?

If not, try using where NOT EXISTS instead of NOT IN.

also, don't to_char(dt) and compare to strings.  to_date the strings!!!
"

I read it as "If so , try using where not exists instead of NOT IN" . I with
draw the first part of the comment above.

But the seond part of the comment relating to "also, don't to_char(dt) and
compare to strings.  to_date the strings!!!   " still holds good.

Rating: 5
yyyy/mm/dd September 04, 2002
Reviewer: Nag
Tom, your explanation on dates is crystal clear.

1.But in your website you have said at several places that when we compare dates
then format 'YYYY/MM/DD' will be better. Why is that so. Actually when does the
above format  make sense.

2.You consistently ask us to convert strings into dates and then compare with
dates, and not convert date columns into strings and compare with strings. Why
is that , what is the advantage of doin thus, and what is the downside of not
doing so

Followup:
1) If and only if you insist, utterly insist, on comparing dates as STRINGS and
not DATES (which is the 100% wrong way to do it)

When you format a date into a STRING and use the format YYYY MM DD (year then
month then day) -- it is "sortable" -- you can use =, <, between , order by and
so on safely.  If you try mm/dd/yyyy -- it isn't sortable anymore.

2) think about it.

Should I:

select * from t where to_char( date_column, 'some_format' ) = 'some string'

(hence pretty much precluding using any indexes on date_column) or should i:

select * from t where date_column = to_date( 'some string', 'some_format' );

which lets me use an index.  Also consider how many times is TO_CHAR performed
in this query:

select * from t where to_char( date_column, 'some_format' ) = 'some string'

vs how many times is to_date done in the correct one?  (answer: to_char is done
once per row.  to_date is done once per query.  which would you rather do??)

Rating: 5
Excellent explaination: September 04, 2002
Reviewer: Mr. 420 from Like a mouse in a cage I am trapped inside this cubicle
:-) Tom, You R DA MAN! If there was a Nobel prize for Oracle, You would win
it...no contest! Great Job! Please continue to do what you are doing.

Rating: 3
TKPROOF October 22, 2003
Reviewer: vivek from UK
Tom,

I have a stored procedure running slow at a my client office. procedure is
generating some 50 reocrds in an hour time. I turned sqltrace on, i found that
its doing a full table scan for most of the tables where as my SQL is just
searching on Primary key for those tables. One of my SQL is

SELECT bl_id,task_grp_id,task_item_id INTO p_blid,p_taskgrp,p_taskitem FROM EQ
WHERE eq_id = P_EQID;

I have got 'EQ_PK' Index and eq_id is only primary key for EQ table. On client
database this table has got a million records and its happening for most of the
tables and slowing down the things considerably.

I tried to replicate the scenario back in my office. I found following result
from TKPROF

SELECT BL_ID,TASK_GRP_ID,TASK_ITEM_ID
FROM
 EQ  WHERE EQ_ID = :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.03          0          0          0           0
Execute     39      0.00       0.00          0          0          0           0
Fetch       39      0.00       0.00          0         78          0          39
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       79      0.03       0.03          0         78          0          39

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 42  (AFM)   (recursive depth: 1)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
    195   TABLE ACCESS (BY INDEX ROWID) OF 'EQ'
    390    INDEX (UNIQUE SCAN) OF 'EQ_PK' (UNIQUE)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

I am not sure why this is different behaviour. Only Difference in setup is that
on client side they are having different tablespace for indexes where as i am
having them on same tablespace.

Could you please help me out as you have done so many times in past.

thanks
Vivek

Followup:
so, maybe you share with us the tkprof from the BAD system eh?

funny how a unique scan would output 390 rows -- but the table access only 195
and you really only got 39 -- hmmm.

Rating: 2
Tunning October 24, 2003
Reviewer: vivek from UK
Tom,
I found the cause of problem, on my client office they have anaylse the schema
and computed the statistics for schema. and that has changed the execution plan.
I deleted the statistics and rerun the code it picks up the index. So it looks
to be a SQL optimisation problem ( i think so).
Since Oracle is doing CBO after statistics is caputred, i think i need to put
hints in my SQL to run them properly. Is that correct ? or could i do any think
else so that Oracle picks up indexes (just thinking, if i can avoid putting
hints in all my code).

Thnaks
Vivek

Followup:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6601251003901

      Rating: 4
Further
      Query  November 04, 2003
Reviewer:  Vivek
       from UK
Tom

After reading the paper within this thread i had advised my client to set
following parameters
OPTIMIZER_INDEX_CACHING = 50 and OPTIMIZER_INDEX_COST_ADJ = 50. and the
performance seems to be OK in there test environment. They are testing the
application via load runner.
Now what i want to know is ,if i am going to put hints in my SQL will that
override these parameters? As my client is expecting to have upto 5 million rows
in serval tables within a 4 week time in production environment. Looking at this
volume would you suggest to put hints in SQL or tweak the parameter as volume
grows.
Another query about CBO is when i had gathered the statistics and the two
parameters are not in init.ora how come CBO finds that FULL TABLE SCAN is least
expensive way to execute almost all the queries whereas appropriate index were
available for the query.

Thanks
Vivek

          Followup:

why would that indicate "i must hint"????? what is the thought process here???

rebrowse that paper - it tells you how the things are costed by default and what
effect these parameters have on costing.

November 05, 2003 Ravi -- Thanks for the question regarding
      "Partition level export and global indexes", version 815

      You Asked
Dear Tom,

I have a table T1 with partition P1 and P2. I also have a  Local
index LI1 and Global Index GI1 and Non-partitoned Index NPI1
defined on the table T1.

Now I wish to export only the partition P1 of Table T1 with the
following parameters

Tables=T1:p1
indexes=y
grants=y
constraints=y

What will happen to the global indexe and non-partitioned
indexe. Will they be exported or not.

Will the Local Indexes defined on it be exported?.

Then, I am exporting the partiton P2 of Table T2 with the same
export options. And I would like to import P1 and P2 to a
different database. Now, will the dump have the knowlege of the
global/non-partitioned indexes and recreate them or should I
have to recreate them upon import.

Please assume that data is not  changed from the first export to
the second export finishing.

Please clarfiy the above and HAVE A GREAT NEW YEAR.

Thanks
Ravi

      and we said...
The easiest way to see is to try as usual.  So, I setup a table
T with partitions and tested it out.  Here is the result:

tkyte@TKYTE816> CREATE TABLE t
  2  ( x int,
  3    y int check (y<0),
  4    z DATE
  5  )
  6  PARTITION BY RANGE (z)
  7  (
  8  PARTITION p1 VALUES LESS
  9  THAN(to_date('30-jun-1999','dd-mon-yyyy')),
 10  PARTITION p2 VALUES LESS
 11  THAN(to_date('31-dec-1999','dd-mon-yyyy'))
 12  )
 13  /
Table created.

tkyte@TKYTE816> create index t_local on t(x,y) local;
Index created.

tkyte@TKYTE816> create index t_global  on t(z) global;
Index created.

tkyte@TKYTE816> insert into t  values ( -1, -1, '15-jun-1999' );
tkyte@TKYTE816> insert into t  values ( -2, -2, '15-dec-1999' );

tkyte@TKYTE816> grant insert on t to scott;
Grant succeeded.

tkyte@TKYTE816> grant update on t to system;
Grant succeeded.

tkyte@TKYTE816> host exp userid=tkyte/tkyte tables=t:p1
indexes=y grants=y constraints=y

....

tkyte@TKYTE816> host imp userid=tkyte/tkyte full=y show=y

Import: Release 8.1.6.0.0 - Production on Tue Jan 2 08:22:35
2001

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.6.0.0 -
Production
With the Partitioning option
JServer Release 8.1.6.0.0 - Production

Export file created by EXPORT:V08.01.06 via conventional path
import done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR
character set
. importing TKYTE's objects into TKYTE
 "CREATE TABLE "T" ("X" NUMBER(*,0), "Y" NUMBER(*,0), "Z" DATE)
PCTFREE 10 P"
 "CTUSED 40 INITRANS 1 MAXTRANS 255 LOGGING TABLESPACE "USERS"
PARTITION BY R"
 "ANGE ("Z" )  (PARTITION "P1" VALUES LESS THAN (TO_DATE('
1999-06-30 00:00:0"
 "0', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN'))
PCTFREE 10 PCTUSE"
 "D 40 INITRANS 1 MAXTRANS 255 STORAGE(INITIAL 131072 NEXT
131072 MINEXTENTS "
 "1 MAXEXTENTS 4096 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL D"
 "EFAULT) TABLESPACE "USERS" LOGGING, PARTITION "P2" VALUES LESS
THAN (TO_DAT"
 "E(' 1999-12-31 00:00:00', 'SYYYY-MM-DD HH24:MI:SS',
'NLS_CALENDAR=GREGORIAN"
 "'))  PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
STORAGE(INITIAL 131072 N"
 "EXT 131072 MINEXTENTS 1 MAXEXTENTS 4096 PCTINCREASE 0
FREELISTS 1 FREELIST "
 "GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "USERS" LOGGING )"
. . skipping partition "T":"P1"

 "GRANT INSERT ON "T" TO "SCOTT""
 "GRANT UPDATE ON "T" TO "SYSTEM""

 "CREATE INDEX "T_LOCAL" ON "T" ("X" , "Y" )  PCTFREE 10
INITRANS 2 MAXTRANS "
 "255 LOGGING LOCAL(PARTITION "P1"  PCTFREE 10 INITRANS 2
MAXTRANS 255 STORAG"
 "E(INITIAL 131072 NEXT 131072 MINEXTENTS 1 MAXEXTENTS 4096
PCTINCREASE 0 FRE"
 "ELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE
"USERS" LOGGING,"
 " PARTITION "P2"  PCTFREE 10 INITRANS 2 MAXTRANS 255
STORAGE(INITIAL 131072 "
 "NEXT 131072 MINEXTENTS 1 MAXEXTENTS 4096 PCTINCREASE 0
FREELISTS 1 FREELIST"
 " GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "USERS" LOGGING ) "
 "CREATE INDEX "T_GLOBAL" ON "T" ("Z" )  PCTFREE 10 INITRANS
2 MAXTRANS 255 S"
 "TORAGE(INITIAL 131072 NEXT 131072 MINEXTENTS 1 MAXEXTENTS 4096
PCTINCREASE "
 "0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS" LOG"
 "GING"
IMP-00057: Warning: Dump file may not contain data of all
partitions of this table
Import terminated successfully with warnings.

so, as you can see -- the definition of T fully comes out --
including both index definitions.
Review &
      Followup

      Rating: 5
difference
      between partitioned global index and non-partitioned global
      index  November 04, 2003
Reviewer:
      A reader
Hi

what is the difference between partitioned global index and non-partitioned
global index

:-?

          Followup:

a non partitioned global index is just an index in a single partition.

a partitioned global index might be in one or more partitions.

      Rating: 5
non-partitioned
      global index  November 04, 2003
Reviewer:
      A reader
Hi

When I do

create index x_idx on x(y)

on a partitioned table I am creating a global non-partitioned index or a plain
index? (What do you call this type of index?)

Because if I do

create index x_idx on x(y)
global

I am creating a global partitioned index right?

How different are these two and how do they affect optimizer access path :-?

          Followup:

you are creating a global non-partitioned index.

that is a "plain old index"

there is no difference between the two above, global is the default.

November 05, 2003 mathew -- Thanks for the question regarding
      "xmldb", version 9i

      You Asked
hi,
  How is XML support in 9iR2 better than what was given in 9iR1? Also can you
provide an example of how one can load a physical file from the OS into the
DB,query aganist it and create a physical file from a query?
  I happen to read that 9iR2 compress data 3-10 times more than the previous
versions. How will this improve performance? Will this help in both OLTP and DSS
kind of system? Is there a seperate option to select while instalation?

thanks.

regards,
Mathew

      and we said...
See

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96620/toc.htm
for XML new features and an overview of whats available.

Search for

load lob

on this site for examples of loading lobs (also, check out the above referenced
manual for XML examples)

Table compression (example:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4635180000986
is new with 9iR2 EE and is a data warehousing feature.  It is not suited for
OLTP environments.  It is used for large repositories of relatively static
information.

Review &
      Followup

      Rating: 3
XML DB vs
      XDB  August 26, 2002
Reviewer:  andrew
       from ca, usa
I recently saw a demo of XDB - I guess there are two names still going around?
One thing I picked up from the demo was that an FTP interface is supported, so
you can upload and download documents with that too.

      Rating: 5
How to rollup
      sum in XML  August 26, 2002
Reviewer:  san

Hello Tom,

I need to rollup the sum in the xml output. Can this be achieved using
sys_xmlgen as a single query? If not is there any other way to achieve that? Can
you please explain with an example. I am using Oracle 8.1.7

Senario
--------

Sno  employee_id   branch_id  salary
1       10           10         100
2       11           10         100
3       12           11          50
4       13           11          50
5       14           12         200

Reqires XML format
------------------

Employee_id    branch_id    salary
10               10           100
11               10           100
                             -----
                               200
12                11           50
13                11           50
                             -----
                               100
14                12           200

Thank you

          Followup:

don't know but tell you what.

Play with it for about a week and if you cannot get it -- ask it as a question
next week.  My XML guy's wife just had a baby and is sort of busy this week ;)

      Rating: 5
RE: My XML
      guy's wife...  August 26, 2002
Reviewer:  Mark
      A. Williams  from Indianapolis, IN USA
Would that Sean Dillon?

I don't know why I care about these things, but for some reason it seems nicer
when you know little things about the people on the other end of the wire...

- Mark

          Followup:

Yup, thats Sean Dillon (guy who led the beginning Oracle book) -- had a baby
girl last friday (aug 23rd)

      Rating:
      3
error  September 06, 2002
Reviewer:
      reader  from India
Hi Tom,
   I wanted to insert an XML data in XMLTYPE table.
The content of my xml file is
    <Employee>
        <Name>Shailesh</Name>
        <Age>23</Age>
    </Employee>
Shown below are the steps that I followed as per oracle documentation. However I
get error.
Please advise.

SQL> conn system@vks2_ora9i2
Connected.
SQL> create directory XMLDIR as 'D:\oracle\ora92\utl_file_dir\';

Directory created.

SQL> grant read on directory xmldir to public with grant option;

Grant succeeded.

SQL> conn scott@vks2_ora9i2
Connected.
SQL> create table xmltable of xmltype;

Table created.

SQL> create or replace function get_Document(filename varchar2) return clob is
  2     xbfile bfile;
  3     xclob clob;
  4  begin
  5    xbfile  := bfilename('XMLDIR',filename);
  6    dbms_lob.open(xbfile);
  7
  8    dbms_lob.createtemporary(xclob,TRUE,dbms_lob.session);
  9    dbms_lob.loadfromfile(xclob,xbfile, dbms_lob.getlength(xbfile));
 10    dbms_lob.close(xbfile);
 11    return xclob;
 12  end;
 13  /

Function created.

SQL> INSERT INTO XMLTABLE
  2  VALUES(XMLTYPE(get_Document('Shailesh.xml')));
VALUES(XMLTYPE(get_Document('Shailesh.xml')))
       *
ERROR at line 2:
ORA-31011: XML parsing failed
ORA-19202: Error occurred in XML processing
LPX-00210: expected '<' instead of '<'
Error at line 1
ORA-06512: at "SYS.XMLTYPE", line 0
ORA-06512: at line 1

SQL> spool off

P.S :-  If I insert the XML in following way it does not give me any error.
    INSERT INTO XMLTABLE
    VALUES(XMLTYPE('<Employee><Name>Shailesh</Name><Age>23</Age></Employee>'));
    But this is not what I want. I want to read an XML file and then insert it
in XMLTABLE.

          Followup:

ops$tkyte@ORA920.US.ORACLE.COM> host cat tkyte.xml
    <Employee>
        <Name>Shailesh</Name>
        <Age>23</Age>
    </Employee>

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace directory xmldir as
'/export/home/tkyte/'
  2  /

Directory created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> drop table xmltable;

Table dropped.

ops$tkyte@ORA920.US.ORACLE.COM> create table xmltable of xmltype;

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace function get_Document(filename
varchar2) return clob is
  2     xbfile bfile;
  3     xclob clob;
  4  begin
  5    xbfile  := bfilename('XMLDIR',filename);
  6    dbms_lob.open(xbfile);
  7
  8    dbms_lob.createtemporary(xclob,TRUE,dbms_lob.session);
  9    dbms_lob.loadfromfile(xclob,xbfile, dbms_lob.getlength(xbfile));
 10    dbms_lob.close(xbfile);
 11    return xclob;
 12  end;
 13  /

Function created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> insert into xmltable values ( xmltype(
get_document( 'tkyte.xml' ) ) );

1 row created.

ops$tkyte@ORA920.US.ORACLE.COM> select * from xmltable;

SYS_NC_ROWINFO$
---------------------------------------------------------------------------------
--------------------------------------------------
<Employee>
  <Name>Shailesh</Name>
  <Age>23</Age>
</Employee>

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

It worked for me -- Are your sure the file has what you think in it?  Also, this
could be character set related -- if you have a UTF8 instance on windows -- it
might be (and in that case, contact support and reference in progress bug
#2535898)

      Rating: 4
XDB schema
      export  October 31, 2002
Reviewer:  Kumar
       from San Diego
Tom,

Can you able to do the exprot of this XDB schema? I am trying in serveral ways
and I could not get it done all the time the error comes is exp-00010 xdb is not
a valid user.

Can you tell me what kind of export do we need on this to get the things to pass
to another database.

      Rating: 5
Where are
      theses XML related packages?  July 03, 2003

Reviewer:  A Reader
Hi Tom,
I recently installed Oracle 9iR2 (Standard Sdition).  When I read Oracle
docmentation and other examples, there are many packages related to XML, such
as, DBMS_XDB, DBMS_XMLSchema, DBMS_XDB_VERSION, DBMS_XMLDOM,DBMS_XMLGEN,
DBMS_XMLQUERY etc.  Howevery, in my database, I can only see DBMS_XMLGEN.
Oracle documentation says XML DB is installed while installation.  Should I do
manual installation or my first installation went wrong?  Other features seem to
be fine.
Thank you for your help.

          Followup:

XML db is installed if you use dbca and one of the sample databases or ask for
it to be installed.

fire up dbca and install it.

      Rating:
      5
  November 04, 2003
Reviewer:
      RICHARD  from WASHINGTON
Hi Tom,

Regarding the bug: #2535898 as you mentioned above, if I install XDK 9.2.0.6,
will it resolve the bug automatically?

for example, if I use that get_document() to convert a non-utf-8 xml file into a
clob and print it, it shows a lot of reverse question mark, if I install XDK
9.2.0.6, Does this problem will go away automatically?

Please advise.  Thanks for help.

Richard

          Followup:

you would contact support (as referenced above) to see the "fixed in" version
information.

metalink.oracle.com

a ? does not mean "bug" btw - 9999 times out of 10000 it means "you didn't set
your NLS_ stuff correctly"

      Rating:
      5
  November 04, 2003
Reviewer:
      RICHARD  from WASHINGTON
Hi Tom,

Thank you very much your advice, please be more specific about your comment:
"not set up NLS stuff correctly", the database has existed for seven years, how
do I correct it?

Thanks for help.

Richard

          Followup:

i mean -- you are using an 8bit NLS language setting for example and when you
try to retrieve and display some UTF8 data -- it appears as ? since there isn't
a mapping to an 8bit character set.

Meaning, it could have been working fine for 100years with 8bit data but once
you have other sessions plopping multi-byte data in there -- you get ? marks.

verify your client is capable of displaying the data.

      Rating:
      5
  November 04, 2003
Reviewer:
      RICHARD  from WASHINGTON
Hi Tom,

Following my last response.  Does "not set up NLS stuff correctly" mean database
parameter set up or when using XSLT and dbms_xmlsave package?  Please be a bit
more specific.

Thanks.

Richard

      Rating: 4
XDB
      Installation  December 11, 2003
Reviewer:
      Raghu
Hi Tom,

Thanks for all the great work you are doing for all of us. This question  is
related to XML Guru Sean.

I am trying to re-install the XMLDB in the Oracle database.

I followed the steps given in the documentation.
i executed
catqm.sql <pwd> <ts_name> <ts_temp>

But this script fails in between and i see that it has disconnected from the
database and there are lots of Not connected to Oracle error.

I opened the catqm.sql and tried to execute each script inside it.
and i found that the problem occurs in the script catxdbz.sql.

The first pl/sql code within the file executed but the second one to register
the schema did not work and says table does not exist .
error in XDB.DBMS_XMLSCHEMA_INT and XDB_DBMS_XMLSCHEMA.

so can you please help me out with this installation.

A reply in this will be greatly appreciated.

November 05, 2003 Winston -- Thanks for the question regarding
      "Locking and Cocurrency in 9i.", version 9iR2

      You Asked
Hi Tom,

I am rereading your 1st book page 114 about locking and cocurrency. I wonder if
we can still use the "select XIDUSN, XIDSLOT,XIDSQN from v$transaction' to
uniquely identify a transaction with the introducton of automatic undo
management mode in Oracle9i. And if we could still user the following query to
get (rbs, slot, seq) and compare it with the result (xidusn, xidslot, xidsqn)
from v$transaction.
select username, v$lock.sid,
trunc(id1/power(2,16)) rbs,
bitand(id1,to_number('ffff','xxxx'))+0 slot,
id2 seq, lmode, request, block
from v$lock, v$session
where v$lock.type='TX'
and v$lock.sid=v$session.sid
and v$session.username=USER

Thank you in advance,
Winston

      and we said...
To to show that you learn new things every day -- between the time I wrote the
book and now -- I've found an easier way:

ops$tkyte@ORA920> select dbms_transaction.local_transaction_id from dual;

LOCAL_TRANSACTION_ID
-----------------------
13.36.60919

ops$tkyte@ORA920> select XIDUSN, XIDSLOT,XIDSQN from v$transaction;

    XIDUSN    XIDSLOT     XIDSQN
---------- ---------- ----------
        13         36      60919

but yes, it would work with UNDO tablespaces since they are just rollback
segments in disguise.
Review & Followup

      Rating: 4
Does ORACLE
      recycle transaction ID?  March 31, 2003
Reviewer:
      Jusung Yang  from San Jose, CA, USA
Hi Tom,

There was a thread at comp.databases.oracle.server about the uniqueness of
transaction ID. Used to be that transaction ID can run out and the associated
rollback segment needs to be dropped and recreated. I wonder how the the ORACLE
9i auto
undo management handles this. Will ORACLE recycle transaction IDs? If not, what
happens when the transaction IDs run out?

          Followup:

the rbs's in the undo tablespace are created, dropped, resized all of the time.
It is interesting to monitor your alert log -- ramp up concurrent users and
watch the database automatically create and online new rbs to handle the load --
and if you are patient enough, watch it offline and drop them later when they
are not needed anymore.

      Rating:
      4
dbms_transaction.local_transaction_id   August 28,
      2003
Reviewer:  vc
Dear Tom,

Please consider this:

SQL*Plus: Release 9.2.0.3.0 - Production on Thu Aug 28 17:35:07 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production

Case 1:
=======
SQL> SET TRANSACTION READ ONLY;

Transaction set.

SQL> select select XIDUSN, XIDSLOT,XIDSQN from v$transaction;

SQL> select XIDUSN, XIDSLOT,XIDSQN from v$transaction;

no rows selected

SQL> commit;

Commit complete.

SQL> select XIDUSN, XIDSLOT,XIDSQN from v$transaction;

no rows selected

Case 2:
=======
SQL> SET TRANSACTION READ ONLY;

Transaction set.

SQL> select XIDUSN, XIDSLOT,XIDSQN from v$transaction;

no rows selected

SQL> SELECT dbms_transaction.local_transaction_id FROM dual;

LOCAL_TRANSACTION_ID
------------------------------------------------------------------------------

7.19.23877

SQL> select XIDUSN, XIDSLOT,XIDSQN from v$transaction;

    XIDUSN    XIDSLOT     XIDSQN
---------- ---------- ----------
         7         19      23877

SQL>

=================

Is it a bug in dbms_transaction.local_transaction_id or expected behaviour and,
if the latter,  why in Case 1 there is no transaction record in v$transaction ?

Thank you.

          Followup:

it would be a bug if you were able to actually update something.

ops$tkyte@ORA920> SELECT dbms_transaction.local_transaction_id FROM dual;

LOCAL_TRANSACTION_ID
---------------------------------------------------------------------------------
--------------------------------------------------
4.9.19278

ops$tkyte@ORA920> update emp set empno = empno;
update emp set empno = empno
       *
ERROR at line 1:
ORA-01456: may not perform insert/delete/update operation inside a READ ONLY
transaction

but you cannot. so, no bug... you are "read only"

In case #1, why would you "expect" a record in v$transaction?  you don't really
"need" one

      Rating:
      2
dbms_transaction.local_transaction_id   August 29,
      2003
Reviewer:  vc
OK, probably I was not clear enough.

You said:
"In case #1, why would you "expect" a record in v$transaction?  you don't really
"need" one "

I never expected any record in v$transaction in Case 1.  Thank you for
confirming that.

Now as to Case 2,  the real question is:

Why does running 'SELECT dbms_transaction.local_transaction_id FROM dual;'
creates an entry in v$transaction ?  That's what my Case 2 demonstrates. Clearly
there is no update/delete/insert.

dbms_transaction.local_transaction_id creating this entry is quite misleading.
What's the point of having a transaction id for the read-only transaction
(isolation level read-only) ?

Thank you.

          Followup:

well -- you sort of asked for one didn't you?

the act of asking -- makes one appear.  why is it misleading -- you are *in* a
transaction. we didn't need to identify that transaction -- well, not until you
said "please -- identify this transaction for me" any way...

using the set transaction "created a transaction" -- you were in one explicity!
when you asked for the identifier -- it became "exposed".

      Rating:
      2
  August 29, 2003
Reviewer:  vc

OK.

The transaction information appearing as the result of calling the function is
misleading for several reasons:

1.  You said 'why would you "expect" a record in vtransaction?  you don't really
"need" one'. That's a fair statement for the read-only transaction -- after all
it (the transaction) does not generate undo and, therefore, the transaction id
(XIDUSN, XIDSLOT, XIDSQN) does not have any meaning in the context of the
read-only transaction.

2. Taking the above into account,  what's the real meaning of the transaction id
created as a result of dbms_transaction.local_transaction_id execution ? Just a
number without any meanining ? If so,  what's the use of such a number ?

3. Does Oracle create a 'real' transaction entry in v$transaction where XIDUSN,
XIDSLOT, XIDSQN have the usual semantics just in order to return a transaction
id ?  If so,  it's quite counter-intuitive for someone accustomed to seeing
entries in v$transaction only for update/insert/deletes.

Thanks.

          Followup:

sorry -- sometimes the answer is just "it is the way it is"

i fail to see why this particular behaviour is confusing, or bothersome, or
having any effect at all really on  you.

you started a transaction
you asked for the transaction id
we gave it to you

i don't know exactly why it isn't visible in the v$ tables until you ask for it
but -- so?

#2 -- you can use it to uniquely identify your transaction for whatever purpose
you see fit.

#3 -- there are other times you'll see it in there.  touch a dblink for example
and see what happens.  lock a table.  there are other times.

one would be wondering (i sure am)

"if you don't know what to do with this value,
 and it bothers you that when you ask for the value something else happens
 and yet you have no purpose for this value,
 why are you asking for the value,
 thus causing the undesired side effect???"

      Rating: 2
More
      inconsistency with
      dbms_transaction.local_transaction_id  August 29, 2003

Reviewer:  vc
You said 'the act of asking -- makes one appear.  why is it misleading -- you
are *in* a transaction.'

Consider this:

SQL>  alter session set isolation_level=serializable;

Session altered.

SQL> select * from t;

         X
----------
         1

SQL> SELECT dbms_transaction.local_transaction_id FROM dual;

LOCAL_TRANSACTION_ID
--------------------------------------------------------------------------------
16.81.7410

/* So far so good -- I am in the transaction */
SQL> commit;

Commit complete.

SQL> alter session set isolation_level=read committed;

Session altered.

SQL> select * from t;

         X
----------
         1

SQL> SELECT dbms_transaction.local_transaction_id FROM dual;

LOCAL_TRANSACTION_ID
--------------------------------------------------------------------------------

/* How come ? I am not in a transaction ? Or a transaction running in the READ
COMMITTED mode is not quite a transaction */

SQL> commit;

Commit complete.

SQL>

Wouldn't you agree that the behaviour is quite inconsistent ?  Under the
SERIALIZABLE/READ-ONLY level I can get a transaction id plus an entry in
v$transaction as a bonus whilst in READ COMMITTED I get nothing ? As an aside,
I do understand how read consistency is achieved in both mode and one can
probably rationalize why dbms_transaction.local_transaction_id behaves
differently but it would not make the behaviour consistent surely.

Thanks.

          Followup:

guess if you think this is something that is broken and needs to be fixed,
you'll need to goto support and file a tar to get a bug going.

but -- if you think about the very definition of serializable -- maybe you'll
figure it out....

serializable means "i'm ALWAYS in a transaction -- ALWAYS.  Transactions begin
with the FIRST STATEMENT regardless of type..."

read committed -- well -- it doesn't mean that.

      Rating:
      5
Reader  November 05, 2003
Reviewer:
      A reader
"
the rbs's in the undo tablespace are created, dropped, resized all of the time.

It is interesting to monitor your alert log -- ramp up concurrent users and
watch the database automatically create and online new rbs to handle the load --

and if you are patient enough, watch it offline and drop them later when they
are not needed anymore.
"

1. Is there a view to monitor rbs dropped and rbs added
2. Leaving aside "flashback queries" what is the best way
   to monitor and resize undo tablespace, to optimal size

          Followup:

1) dba_rollback_segs show you what is there, the history is only in the alert

2) determine how long you anticipate having your longest running query run for.
set the undo_retention to some period of time larger then that.  I like to let
the undo tablespace autoextend naturally to be large enough by itself rather
then size it manually -- but you can use v$undostat (OEM gives you a nice
graphic) to right size it based on your observed workload (eg: you have to
actually be running at load in order to accurately determine "how much undo
space would I need to have 5 hours of retained undo")

November 05, 2003 josh -- Thanks for the question regarding "tune
      these max statements with subquery", version 8.1.7

      You Asked
How can I convince my developers there is a better way to do this?

SELECT MAX (weight) weight
            FROM client.pet_weight
           WHERE pet_id = :pet_id
             AND setup_date =
                          (SELECT MAX (setup_date)
                             FROM client.pet_weight
                            WHERE pet_id = :pet_id)

      and we said...
the answer is always -- BENCHMARK IT....

ops$tkyte@ORA920> create table t ( weight int, pet_id int, setup_date date );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t ( pet_id, weight, setup_date )
  2  select mod(rownum,1000), dbms_random.random,
sysdate-1000+dbms_random.value(0,1000)
  3    from all_objects
  4  /

31611 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index t_idx on t( pet_id, setup_date, weight );

Index created.

ops$tkyte@ORA920> analyze table t compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;

Table analyzed.

SELECT max(weight) from t where pet_id = :b1
           and setup_date = (select max(setup_date)
                               from t
                              where pet_id = :b1 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute   1000      0.42       0.45          0       2000          0           0
Fetch     1000      0.06       0.04          0       2005          0        1000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2001      0.48       0.50          0       4005          0        1000

Rows     Row Source Operation
-------  ---------------------------------------------------
   1000  SORT AGGREGATE (cr=2005 r=0 w=0 time=33826 us)
   1000   INDEX RANGE SCAN T_IDX (cr=2005 r=0 w=0 time=24134 us)(object id
49798)

that is the current way -- 1/2 cpu second, 4,000 logical IOs..

********************************************************************************
SELECT weight from (select weight from t where pet_id = :b1
                 order by pet_id DESC, setup_date DESC, weight DESC )
         where rownum = 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute   1000      0.21       0.20          0          0          0           0
Fetch     1000      0.06       0.07          0       2000          0        1000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2001      0.28       0.28          0       2000          0        1000

Rows     Row Source Operation
-------  ---------------------------------------------------
   1000  COUNT STOPKEY (cr=2000 r=0 w=0 time=66046 us)
   1000   VIEW  (cr=2000 r=0 w=0 time=52862 us)
   1000    INDEX RANGE SCAN DESCENDING T_IDX (cr=2000 r=0 w=0 time=46646
us)(object id 49798)

that gets the same results -- in about 1/2 the cpu time with 1/2 the LIO's..

and if they cannot use order by in a subquery, just declare a cursor that looks
like the following and open/fetch 1 row/close it:

********************************************************************************
SELECT weight from t where pet_id = :b1
                 order by pet_id DESC, setup_date DESC, weight DESC

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute   1000      0.25       0.19          0          0          0           0
Fetch     1000      0.05       0.05          0       2000          0        1000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2001      0.31       0.25          0       2000          0        1000

Rows     Row Source Operation
-------  ---------------------------------------------------
   1000  INDEX RANGE SCAN DESCENDING T_IDX (cr=2000 r=0 w=0 time=46575
us)(object id 49798)

for similar results

Review &
      Followup

      Rating:
      4
tuning  May 19, 2003
Reviewer:
      lakshmi  from Bangalore india
Excellent

      Rating: 5
One minor
      question  May 19, 2003
Reviewer:  Alla
       from Boston, MA USA
Tom;

You re-wrote the statement the following way:

SELECT weight from (select weight from t where pet_id = :b1
                 order by pet_id DESC, setup_date DESC, weight DESC )
         where rownum = 1

Why did you say "order by pet_id", when your pet_id is a single value?

Thanks

Alla

          Followup:

just helps ensure the index is used.  doesn't hurt.

      Rating: 5
Too
      good  May 19, 2003
Reviewer:  Arun Gupta
       from Harrisburg, PA USA
Tom
You need to share with us the secret of how you think of these clever ways of
rewriting a query...

          Followup:

think in sets, know what is available to you, understand SQL.  it all comes from
that.

      Rating: 5
Too
      Good  May 20, 2003
Reviewer:  umesh
       from bangalore india
that was great Tom, You keep me in "MY JOB"  Thanks Tom
keep up the good work

      Rating: 4
Too
      Good  May 20, 2003
Reviewer:  Aijaz
       from UAE
Excellent workaround.

      Rating:
      5
  May 20, 2003
Reviewer:  Vipin

Excellent Tom

      Rating: 4
inline
      view  May 20, 2003
Reviewer:  A reader

just wondering, why the pedicate is not pushed (or view merged) to the inline
view?

SELECT weight from (select weight from t where pet_id = :b1
                 order by pet_id DESC, setup_date DESC, weight DESC )
         where rownum = 1

pushed to

SELECT weight from (select weight from t where pet_id = :b1
                    and rownum = 1
                 order by pet_id DESC, setup_date DESC, weight DESC )

          Followup:

because rownum is special, like aggregates, order by, set operations, distinct,
etc.

you can actually use rownum to stop merging from happening

      Rating: 2
CONVERT
      SQL  May 20, 2003
Reviewer:  Clab

CONVERT SQL

SELECT /*+ INDEX_DESC(T T_IDX) */
       WEIGHT
  FROM T
 WHERE PET_ID = :B1
   AND ROWNUM = 1

          Followup:

that is a terrible approach.

don't do that anyone!

if you have that in your code, get rid of that developer....

hints are hints, they are not directives, not commands.

if you want the right answer, all of the time, please use my approach.

If you use this approach, with a hint, you are basically saying "just give me
any row for that pet_id -- thanks".

Don't do this
Don't do this
Don't do this
Don't do this
(just keep repeating that over and over and over)

      Rating: 5
simply
      superb!!!!!!  May 20, 2003
Reviewer:
      praveen  from india
Excellent solution.

      Rating:
      5
  May 21, 2003
Reviewer:  Mirjana
       from Barcelona

      Rating:
      3
curious!  May 21, 2003
Reviewer:  A
      Reader  from India
i am just wondering, if you push pedicate to the inner query and if the result
is same then why there is a need of the upper one.

SELECT weight from (select weight from t where pet_id = :b1
                    where rownum = 1
                    order by pet_id DESC, setup_date DESC, weight DESC )

it looks similar to

select weight from t
where pet_id = :b1
and rownum = 1
order by pet_id DESC, setup_date DESC, weight DESC

Tom, what is the diff ? Could please explain us more.

Thank you.

          Followup:

they are not the same.

We want to

a) sort
b) then get the first row

Your query with it "pushed" would

a) get A row
b) sort it

quite different.

      Rating: 3
How do i
      optimize this qeury?. It is taking more than 10 min....  May
      21, 2003
Reviewer:  siva  from INDIA
Hi TOM,
  Could you please tell me how to optimize this query?.
I created indexes for all joining columns. Each table has has 5 lakhs, 1 lakhs,
10 thousand, 1 thousand and 500 rows.

It is taking 10 -15 min to run the report from this query. I'm waiting for your
reply.

Thanks & Regards,
Siva

SELECT
JOB.EMPLID,
JOB.DEPTID,
JOB.SETID_DEPT ,
job.LOCATION,
job.SETID_LOCATION,
loc.descr ,
ADDRESS.NAME_PREFIX,
ADDRESS.FIRST_NAME,
ADDRESS.MIDDLE_NAME,
ADDRESS.LAST_NAME,
ADDRESS.ADDRESS1,
ADDRESS.ADDRESS2,
ADDRESS.ADDRESS3,
ADDRESS.ADDRESS4,
ADDRESS.CITY,
ADDRESS.STATE,
ADDRESS.POSTAL,
ADDRESS.COUNTRY,
SCHEME.GRANT_DT
 FROM       PS_JOB JOB,
            PS_S_SS_PERDATA_VW ADDRESS,
            ps_location_tbl Loc,
            PS_S_SS_SCHEME SCHEME ,
            PS_S_SS_CTRY_SETUP COUNTRY
WHERE 1=1
AND JOB.REG_TEMP = 'R'
 AND JOB.EMPL_CLASS IN ('F','U')
 AND JOB.EMPL_STATUS IN ( 'A','L','S','P')
 AND TO_CHAR(GRANT_DT,'MMYYYY') = '052003'
 AND JOB.EFFDT =
    ( SELECT MAX(B.EFFDT)
    FROM PS_JOB B
         WHERE JOB.EMPLID =B.EMPLID
    AND JOB.EMPL_RCD = B.EMPL_RCD
    AND B.EFFDT  <= sysdate )
 AND JOB.EFFSEQ =
            (SELECT MAX(C.EFFSEQ)
             FROM PS_JOB C
             WHERE JOB.EMPLID =C.EMPLID
             AND JOB.EMPL_RCD =C.EMPL_RCD
             AND JOB.EFFDT = C.EFFDT )
 AND JOB.EMPL_RCD=0
 AND Loc.effdt =
    ( Select max(LocEffdt.effdt) from ps_location_tbl LocEffdt
    where loc.setid = LocEffdt.setid
    and loc.location = LocEffdt.location
    and LocEffdt.effdt <= sysdate )
AND LOC.EFF_STATUS='A'
AND COUNTRY.EFFDT =
 ( SELECT MAX(CNTRY.EFFDT)
    FROM PS_CTRY_SETUP CNTRY
    WHERE COUNTRY.COUNTRY = CNTRY.COUNTRY
    AND CNTRY.EFFDT <= SYSDATE)
AND COUNTRY.EFF_STATUS = 'A'
AND JOB.EMPLID=ADDRESS.EMPLID
AND job.SETID_LOCATION = loc.setid
AND job.LOCATION =  loc.location
AND loc.country = country.country
AND SCHEME.SCHEME_TYPE = COUNTRY.SCHEME_TYPE

/

          Followup:

hopefully, it is not using your indexes, they could be the major cause of
performance issues.

tell you what, when I'm taking questions -- feel free to post the query, a
defintion of a "lakh", a tkprof, and the output of a set autotrace traceonly
explain.

Make sure you are using the CBO and look at ANALYTIC functions instead of those
nastly correlated subqueries.  for example, these two queries are the same:

select object_name, object_type, created
  from t
 where created = ( select max(created)
                      from t t2
                     where t2.object_type = t.object_type )
/
select object_name, object_type, created
  from (
select object_name, object_type, created, max(created) over (partition by
object_type) max_created
  from t
       )
 where created = max_created
/

(create table t as select * from all_objects) but the second is more efficient.

      Rating: 4
question
      regadring complex view merging and push predicates  May 21,
      2003
Reviewer:  A reader
Hello

Oracle says views with

Set operators (UNION, UNION ALL, INTERSECT, MINUS)
A CONNECT BY clause
A ROWNUM pseudocolumn
Aggregate functions (AVG, COUNT, MAX, MIN, SUM) in the select list

are not mergeble

and views with

A GROUP BY clause
A DISTINCT operator in the select list

can be merged with complx view merging, but if a view definision such as

CREATE VIEW avg_salary_view AS
  SELECT department_id, AVG(salary) AS avg_sal_dept,
    FROM employees
    GROUP BY department_id;

and a query

SELECT departments.location_id, avg_sal_dept
  FROM departments, avg_salary_view
  WHERE departments.department_id = avg_salary_view.department_id
    AND departments.location_id = 2400;

Oracle merges it to

SELECT departments.loc, AVG(salary)
  FROM departments, employees
  WHERE departments.department_id = employees.department_id
    AND departments.location_id = 2400
  GROUP BY departments.rowid, departments.location_id;

Why he can merge here??? We have an aggreate funcion AVG() here!

And why in the following example which is very similar to above

CREATE VIEW emp_group_by_deptno
  AS SELECT department_id,
        AVG(salary) avg_sal,
        MIN(salary) min_sal,
        MAX(salary) max_sal
       FROM employees
      GROUP BY department_id;

SELECT *
  FROM emp_group_by_deptno
 WHERE department_id = 50;

transformed into

SELECT department_id,
   AVG(salary) avg_sal,
   MIN(salary) min_sal,
   MAX(salary) max_sal,
  FROM employees
 WHERE department_id = 50
 GROUP BY department_id;

Why here the view is nor merged instead it pushes the predicate???

Both example have aggreate functions and group by, but one can use complex view
merging but the other not... All these examples are from the documentation.

Confused :-((

          Followup:

and this relates to the question at hand how?

      Rating:
      5
Lakh  May 21, 2003
Reviewer:  Kalita
       from UK
One lakh = 100,000 in India :-)

      Rating:
      5
  May 21, 2003
Reviewer:  Randy

I have been trying to use what you have showed here but I have not had the
success I'd hoped for.  I'f you have time, Please take a look at what I have.

The SQL is geneated from our custom application for adhoc reports. I have all
the data the users can query in four tables.  The sub-select is what is slowing
it down.

If running the whole SQL, runtime is ~ 28 sec. and
62K  physical reads.  If I do first do the sub-select and put those results into
the main select, total runtime is less than 5 sec.

My database is OEE 8.1.7.3.4.  V_RPT_ALL is a view of three tables RPT_CR(65k
rows) and RPT_ASSOC(300k rows) have a many to many relationship with
Cross_association in the middle of the two.

Here is the explain plan for the whole select and as two parts:

SELECT CRTABLE.CR_ID, CRTABLE.STATUS_ID, CRTABLE.DATEREQUESTED,
CRTABLE.LOCATION, CRTABLE.AREA,
CRLARGE.DESCRIPTIONTEXT
FROM CMR.V_RPT_ALL CRTABLE,
     CMR.V_CMRGETCMRTEXTDETAILS CRLARGE
WHERE CRTABLE.CR_ID IN
    (SELECT CR_ID
     FROM CMR.V_RPT_ALL A
     WHERE DATEREQUESTED BETWEEN
          TO_DATE('3/18/2003','mm/dd/yyyy')
          AND TO_DATE('4/2/2003 23:59','mm/dd/yyyy hh24:mi')
     AND LOCATION_ID IN (657)
     AND SYS_TYPE_ID IN (208244)
     AND SYS_NAME_ID IN (4885)
     AND ASSOC_TYPE_ID = 8
     GROUP BY CR_ID
     HAVING COUNT (*) >= 1)
AND CRTABLE.CR_ID = CRLARGE.CR_ID
ORDER BY CRTABLE.CR_ID;

143 rows selected.

Elapsed: 00:00:28.05

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=12306 Card=7 Bytes=14567)
   1    0   SORT (ORDER BY) (Cost=12306 Card=7 Bytes=14567)
   2    1     NESTED LOOPS (Cost=12304 Card=7 Bytes=14567)
   3    2       NESTED LOOPS (Cost=12297 Card=7 Bytes=14343)
   4    3         NESTED LOOPS (Cost=12294 Card=1 Bytes=2041)
   5    4           NESTED LOOPS (Cost=15 Card=1 Bytes=26)
   6    5             VIEW OF 'VW_NSO_1' (Cost=14 Card=1 Bytes=13)
   7    6               FILTER
   8    7                 SORT (GROUP BY) (Cost=14 Card=1 Bytes=33)
   9    8                   HASH JOIN (Cost=11 Card=1 Bytes=33)
  10    9                     NESTED LOOPS (Cost=6 Card=21 Bytes=462)
  11   10                       TABLE ACCESS (BY INDEX ROWID) OF 'RPT_ASSOC'
(Cost=3 Card=1 Bytes=14)
  12   11                         INDEX (RANGE SCAN) OF 'AK_RPT_ASSOC_SYS_NAME'
(NON-UNIQUE) (Cost=1 Card=1)
  13   10                       INDEX (RANGE SCAN) OF 'AK_CROSS_ASSOCIATION'
(UNIQUE) (Cost=3 Card=403417 Bytes=3227336)
  14    9                     INDEX (RANGE SCAN) OF 'AK_RPT_CR' (NON-UNIQUE)
(Cost=4 Card=867 Bytes=9537)
  15    5             TABLE ACCESS (BY INDEX ROWID) OF 'RPT_CR' (Cost=1
Card=61103 Bytes=794339)
  16   15               INDEX (UNIQUE SCAN) OF 'PK_RPT_CR' (UNIQUE)
  17    4           VIEW OF 'V_CMRGETCMRTEXTDETAILS'
  18   17             SORT (GROUP BY) (Cost=12279 Card=60957 Bytes=7375797)
  19   18               MERGE JOIN (OUTER) (Cost=12279 Card=241598
Bytes=29233358)
  20   19                 SORT (JOIN) (Cost=126 Card=61490 Bytes=245960)
  21   20                   INDEX (FAST FULL SCAN) OF 'PK_CHANGEREQUEST'
(UNIQUE) (Cost=8 Card=61490 Bytes=245960)
  22   19                 SORT (JOIN) (Cost=12153 Card=241598 Bytes=28266966)
  23   22                   TABLE ACCESS (BY INDEX ROWID) OF 'LARGE_TEXT'
(Cost=5691 Card=241598 Bytes=28266966)
  24   23                     INDEX (FULL SCAN) OF 'PK_LARGE_TEXT' (UNIQUE)
(Cost=606 Card=241598)
  25    3         INDEX (RANGE SCAN) OF 'PK_CROSS_ASSOCIATON' (UNIQUE) (Cost=3
Card=403417 Bytes=3227336)
  26    2       TABLE ACCESS (BY INDEX ROWID) OF 'RPT_ASSOC' (Cost=1 Card=191931
Bytes=6141792)
  27   26         INDEX (UNIQUE SCAN) OF 'PK_RPT_ASSOC' (UNIQUE)

Statistics
----------------------------------------------------------
         29  recursive calls
         12  db block gets
       6283  consistent gets
      62428  physical reads
          0  redo size
      15119  bytes sent via SQL*Net to client
       1424  bytes received via SQL*Net from client
         11  SQL*Net roundtrips to/from client
         12  sorts (memory)
          3  sorts (disk)
        143  rows processed

/* This is the Sub-select.  The "HAVING COUNT" part is nessesary because of
Buisness Rules.  The Count >= Number will be equal to the number of IDs in the
LOCATION_ID IN statment. */

SELECT CR_ID
FROM CMR.V_RPT_ALL A
WHERE DATEREQUESTED
   BETWEEN TO_DATE('3/18/2003','mm/dd/yyyy')
   AND TO_DATE ('4/2/2003  23:59','mm/dd/yyyy hh24:mi')
AND LOCATION_ID IN (657)
AND SYS_TYPE_ID IN (208244)
AND SYS_NAME_ID IN (4885)
AND ASSOC_TYPE_ID = 8
GROUP BY CR_ID
HAVING COUNT (*) >= 1;

35 rows selected.

Elapsed: 00:00:00.81

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=13 Card=1 Bytes=33)
   1    0   FILTER
   2    1     SORT (GROUP BY) (Cost=13 Card=1 Bytes=33)
   3    2       HASH JOIN (Cost=11 Card=1 Bytes=33)
   4    3         NESTED LOOPS (Cost=6 Card=21 Bytes=462)
   5    4           TABLE ACCESS (BY INDEX ROWID) OF 'RPT_ASSOC' (Cost=3 Card=1
Bytes=14)
   6    5             INDEX (RANGE SCAN) OF 'AK_RPT_ASSOC_SYS_NAME' (NON-UNIQUE)
(Cost=1 Card=1)
   7    4           INDEX (RANGE SCAN) OF 'AK_CROSS_ASSOCIATION' (UNIQUE)
(Cost=3 Card=403417 Bytes=3227336)
   8    3         INDEX (RANGE SCAN) OF 'AK_RPT_CR' (NON-UNIQUE) (Cost=4
Card=867 Bytes=9537)

Statistics
----------------------------------------------------------
          7  recursive calls
          0  db block gets
         17  consistent gets
          0  physical reads
          0  redo size
       1457  bytes sent via SQL*Net to client
        647  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          4  sorts (memory)
          0  sorts (disk)
         35  rows processed

-- Main Select with results from sub-select

SELECT CRTABLE.CR_ID, CRTABLE.STATUS_ID,
     CRTABLE.DATEREQUESTED, CRTABLE.LOCATION,
     CRTABLE.AREA, CRLARGE.DESCRIPTIONTEXT
FROM CMR.V_RPT_ALL CRTABLE,
     CMR.V_CMRGETCMRTEXTDETAILS CRLARGE
WHERE CRTABLE.CR_ID IN
      (79645,79655,79657,79667,79692,79718,
       79721,79726,79847,79888,80116,80166,
       80172,80187,80216,80221,80241,80414,
       80417,80418,80525,80551,80648,80854,
       80923,80925,80931,80997,81002,81017,
       81020,81169,81207,81208,81239)
AND CRTABLE.CR_ID = CRLARGE.CR_ID
ORDER BY CRTABLE.CR_ID;

143 rows selected.

Elapsed: 00:00:04.22

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=153 Card=1 Bytes=2068)
   1    0   NESTED LOOPS (Cost=153 Card=1 Bytes=2068)
   2    1     NESTED LOOPS (Cost=152 Card=1 Bytes=2036)
   3    2       MERGE JOIN (Cost=149 Card=1 Bytes=2028)
   4    3         VIEW OF 'V_CMRGETCMRTEXTDETAILS' (Cost=142 Card=35
Bytes=70525)
   5    4           SORT (GROUP BY) (Cost=142 Card=35 Bytes=4235)
   6    5             NESTED LOOPS (OUTER) (Cost=142 Card=139 Bytes=16819)
   7    6               INLIST ITERATOR
   8    7                 INDEX (RANGE SCAN)OF 'PK_CHANGEREQUEST' (UNIQUE)
(Cost=2 Card=35 Bytes=140)
   9    6               TABLE ACCESS (BY INDEX ROWID) OF 'LARGE_TEXT' (Cost=4
Card=241598 Bytes=28266966)
  10    9                 INDEX (RANGE SCAN) OF 'PK_LARGE_TEXT' (UNIQUE) (Cost=3
Card=241598)
  11    3         SORT (JOIN) (Cost=7 Card=35 Bytes=455)
  12   11           INLIST ITERATOR
  13   12             TABLE ACCESS (BY INDEX ROWID) OF 'RPT_CR' (Cost=5 Card=35
Bytes=455)
  14   13               INDEX (RANGE SCAN) OF 'PK_RPT_CR' (UNIQUE) (Cost=2
Card=35)
  15    2       INLIST ITERATOR
  16   15         INDEX (RANGE SCAN) OF 'PK_CROSS_ASSOCIATON' (UNIQUE) (Cost=3
Card=231 Bytes=1848)
  17    1     TABLE ACCESS (BY INDEX ROWID) OF 'RPT_ASSOC' (Cost=1 Card=191931
Bytes=6141792)
  18   17       INDEX (UNIQUE SCAN) OF 'PK_RPT_ASSOC' (UNIQUE)

Statistics
----------------------------------------------------------
         22  recursive calls
          0  db block gets
       4311  consistent gets
          0  physical reads
          0  redo size
      15119  bytes sent via SQL*Net to client
       1424  bytes received via SQL*Net from client
         11  SQL*Net roundtrips to/from client
          8  sorts (memory)
          0  sorts (disk)
        143  rows processed

Thanks

          Followup:

what does something like:

select  *
 from (
SELECT CRTABLE.CR_ID, CRTABLE.STATUS_ID, CRTABLE.DATEREQUESTED,
CRTABLE.LOCATION, CRTABLE.AREA,
CRLARGE.DESCRIPTIONTEXT,
max(case when daterequested between TO_DATE('3/18/2003','mm/dd/yyyy') AND
TO_DATE('4/2/2003 23:59','mm/dd/yyyy hh24:mi')
           and location_id in (657)
           and SYS_TYPE_ID IN (208244)
           AND SYS_NAME_ID IN (4885)
           AND ASSOC_TYPE_ID = 8
      then 1
      else null
 end) over(partition by crtable.cr_id) flag
FROM CMR.V_RPT_ALL CRTABLE,
     CMR.V_CMRGETCMRTEXTDETAILS CRLARGE
where CRTABLE.CR_ID = CRLARGE.CR_ID
)
where flag = 1
ORDER BY CR_ID;

do

      Rating:
      5
  May 21, 2003
Reviewer:  Randy

Not so good.  I also tried moving the sub-select up into the FROM part but that
didn't help ether.

143 rows selected.

Elapsed: 00:01:74.80

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=56610 Card=398550 By
          tes=846520200)
   1    0   VIEW (Cost=56610 Card=398550 Bytes=846520200)
   2    1     WINDOW (BUFFER)
   3    2       MERGE JOIN (Cost=56610 Card=398550 Bytes=828186900)
   4    3         MERGE JOIN (Cost=9309 Card=402035 Bytes=25328205)
   5    4           SORT (JOIN) (Cost=8949 Card=403417 Bytes=20170850)
   6    5             MERGE JOIN (Cost=3902 Card=403417 Bytes=20170850)
   7    6               SORT (JOIN) (Cost=2366 Card=191931 Bytes=8061102)
   8    7                 TABLE ACCESS (FULL) OF 'RPT_ASSOC' (Cost=260
Card=191931 Bytes=8061102)
   9    6               SORT (JOIN) (Cost=1536 Card=403417 Bytes=3227336)
  10    9                 TABLE ACCESS (FULL) OF 'CROSS_ASSOCIATION' (Cost=59
Card=403417 Bytes=3227336)
  11    4           SORT (JOIN) (Cost=361 Card=61103 Bytes=794339)
  12   11             TABLE ACCESS (FULL) OF 'RPT_CR' (Cost=166 Card=61103
Bytes=794339)
  13    3         SORT (JOIN) (Cost=47301 Card=60957 Bytes=122828355)
  14   13           VIEW OF 'V_CMRGETCMRTEXTDETAILS' (Cost=12279 Card=60957
Bytes=122828355)
  15   14             SORT (GROUP BY) (Cost=12279 Card=60957 Bytes=7375797)
  16   15               MERGE JOIN (OUTER) (Cost=12279 Card=241598
Bytes=29233358)
  17   16                 SORT (JOIN) (Cost=126 Card=61490 Bytes=245960)
  18   17                   INDEX (FAST FULL SCAN) OF 'PK_CHANGEREQUEST'
(UNIQUE) (Cost=8 Card=61490 Bytes=245960)
  19   16                 SORT (JOIN) (Cost=12153 Card=241598 Bytes=28266966)
  20   19                   TABLE ACCESS (BY INDEX ROWID) OF 'LARGE_TEXT'
(Cost=5691 Card=241598 Bytes=28266966)
  21   20                     INDEX (FULL SCAN) OF 'PK_LARGE_TEXT' (UNIQUE)
(Cost=606 Card=241598)

Statistics
----------------------------------------------------------
          0  recursive calls
        197  db block gets
      13753  consistent gets
      55425  physical reads
          0  redo size
      15174  bytes sent via SQL*Net to client
       1424  bytes received via SQL*Net from client
         11  SQL*Net roundtrips to/from client
          0  sorts (memory)
          9  sorts (disk)
        143  rows processed

      Rating:
      4
  May 21, 2003
Reviewer:
      A reader

      Rating:
      4
  May 21, 2003
Reviewer:
      A reader
Hey Tom,

As many visitors of this web site are indian, may be you should get used to some
Indian phrases :-)

      Rating:
      5
  May 21, 2003
Reviewer:  Randy

Tom,  Thanks for your help.  I think I found the problem.

The view V_CMRGETCMRTEXTDETAILS is used to flatten the text data into four
columns.

CREATE OR REPLACE VIEW CMR.V_CMRGETCMRTEXTDETAILS (
   CR_ID,
   DESCRIPTIONTEXT,
   CHANGEPLANTEXT,
   TESTPLANTEXT,
   BACKOUTPLANTEXT )
AS
(SELECT CR.CR_ID,
             MAX (DECODE (D.TEXT_TYPE_ID, 1, D.TEXT, NULL)) AS DESCRIPTIONTEXT,
             MAX (DECODE (D.TEXT_TYPE_ID, 2, D.TEXT, NULL)) AS CHANGEPLANTEXT,
             MAX (DECODE (D.TEXT_TYPE_ID, 3, D.TEXT, NULL)) AS TESTPLANTEXT,
             MAX (DECODE (D.TEXT_TYPE_ID, 4, D.TEXT, NULL)) AS BACKOUTPLANTEXT
        FROM CMR.LARGE_TEXT D, CMR.CHANGEREQUEST CR
       WHERE CR.CR_ID = D.CR_ID(+)
    GROUP BY CR.CR_ID)
/

SQL> DESC LARGE_TEXT
 Name                   Null?    Type
 --------------------- -------- ----------------
 CR_ID                  NOT NULL NUMBER(15)
 TEXT_TYPE_ID           NOT NULL NUMBER(15)
 VERSION                NOT NULL NUMBER(15)
 TEXT                            VARCHAR2(4000)

I created a table by (select * from V_CMRGETCMRTEXTDETAILS)
and it returned very quick.

I guess I will take this route unless you can suggest a better way.

Thanks for your time.

      Rating: 3
Query with
      explain plan  May 22, 2003
Reviewer:  siva
       from INDIA
Hello TOM,
   I ran the query with autotrace on.   Can you give the suggestion how can i
rewrite the second query. I'm fighting with this query for the past one week.
   Is it possible to go for materialized view for this query?. if it is, which
method (COMPLETE REFRESH etc) of MV i can use.
   I used analytic functions. But the output are different. Why?. How do i use
analytic function on PS_JOB Table. B'cos there are two MAX function with
  different logic.

SQL> get s_ss_r01_1_qry
  1  SELECT count(*)  FROM        PS_JOB JOB,
  2        PS_S_SS_PERDATA_VW ADDRESS,
  3        ps_location_tbl Loc,
  4        PS_S_SS_CTRY_SETUP  COUNTRY ,
  5        PS_S_SS_SCHEME_TBL  SCHEME
  6        WHERE 1=1
  7        AND JOB.REG_TEMP = 'R'
  8        AND JOB.EMPL_CLASS IN ('F','U')
  9        AND JOB.EMPL_STATUS IN ( 'A','L','S','P')
 10        AND JOB.EMPL_RCD=0
 11        AND TO_CHAR(S_SS_GRANT_DT,'MMYYYY') =  '052003'
 12        AND JOB.EMPLID=ADDRESS.EMPLID
 13        AND job.SETID_LOCATION = loc.setid
 14        AND job.LOCATION =  loc.location
 15        AND loc.country = country.country
 16        AND SCHEME.S_SS_SCHEME_TYPE = COUNTRY.S_SS_SCHEME_TYPE
 17        AND JOB.EFFDT = ( SELECT
 18                     MAX(B.EFFDT) FROM PS_JOB B
 19             WHERE JOB.EMPLID =B.EMPLID
 20             AND JOB.EMPL_RCD = B.EMPL_RCD AND B.EFFDT  <= sysdate)
 21             AND JOB.EFFSEQ = (SELECT MAX(C.EFFSEQ)
 22                     FROM PS_JOB C WHERE JOB.EMPLID =C.EMPLID
 23             AND JOB.EMPL_RCD =C.EMPL_RCD
 24                     AND JOB.EFFDT = C.EFFDT )
 25             AND Loc.effdt = ( Select max(LocEffdt.effdt)
 26             from ps_location_tbl LocEffdt where loc.setid = LocEffdt.setid
and
 27             loc.location = LocEffdt.location and LocEffdt.effdt <= sysdate )
AND
 28             LOC.EFF_STATUS='A' AND COUNTRY.EFFDT = ( SELECT MAX(CNTRY.EFFDT)
FROM
 29             PS_S_SS_CTRY_SETUP CNTRY WHERE COUNTRY.COUNTRY = CNTRY.COUNTRY
AND
 30           CNTRY.EFFDT <= JOB.EFFDT ) AND COUNTRY.EFF_STATUS = 'A'
 31* ORDER BY      SCHEME.S_SS_SCHEME_TYPE,COUNTRY.COUNTRY,JOB.EMPLID
SQL> /

  COUNT(*)
----------
     27288

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=1 Bytes=105)
   1    0   SORT (AGGREGATE)
   2    1     FILTER
   3    2       NESTED LOOPS (Cost=5 Card=1 Bytes=105)
   4    3         NESTED LOOPS (Cost=4 Card=1 Bytes=97)
   5    4           NESTED LOOPS (Cost=3 Card=1 Bytes=57)
   6    5             NESTED LOOPS (Cost=2 Card=1 Bytes=31)
   7    6               TABLE ACCESS (FULL) OF 'PS_S_SS_CTRY_SETUP' (C
          ost=1 Card=1 Bytes=18)

   8    6               TABLE ACCESS (BY INDEX ROWID) OF 'PS_S_SS_SCHE
          ME_TBL' (Cost=1 Card=1 Bytes=13)

   9    8                 INDEX (RANGE SCAN) OF 'PS1S_SS_SCHEME_TBL' (
          NON-UNIQUE) (Cost=1 Card=1)

  10    5             TABLE ACCESS (BY INDEX ROWID) OF 'PS_LOCATION_TB
          L' (Cost=1 Card=61 Bytes=1586)

  11   10               INDEX (RANGE SCAN) OF 'PSALOCATION_TBL' (NON-U
          NIQUE)

  12    4           TABLE ACCESS (BY INDEX ROWID) OF 'PS_JOB' (Cost=1
          Card=1 Bytes=40)

  13   12             INDEX (RANGE SCAN) OF 'PSDJOB' (NON-UNIQUE)
  14    3         INDEX (RANGE SCAN) OF 'PS_PERSONAL_DATA' (UNIQUE)
  15    2       SORT (AGGREGATE)
  16   15         FIRST ROW (Cost=3 Card=2 Bytes=38)
  17   16           INDEX (RANGE SCAN (MIN/MAX)) OF 'PSAJOB' (NON-UNIQ
          UE) (Cost=3 Card=2)

  18    2       SORT (AGGREGATE)
  19   18         FIRST ROW (Cost=3 Card=1 Bytes=22)
  20   19           INDEX (RANGE SCAN (MIN/MAX)) OF 'PSAJOB' (NON-UNIQ
          UE) (Cost=3 Card=1)

  21    2       SORT (AGGREGATE)
  22   21         FIRST ROW (Cost=2 Card=1 Bytes=20)
  23   22           INDEX (RANGE SCAN (MIN/MAX)) OF 'PS_LOCATION_TBL'
          (UNIQUE) (Cost=2 Card=1)

  24    2       SORT (AGGREGATE)
  25   24         FIRST ROW (Cost=2 Card=1 Bytes=12)
  26   25           INDEX (RANGE SCAN (MIN/MAX)) OF 'PS_S_SS_CTRY_SETU
          P' (UNIQUE) (Cost=2 Card=1)

Statistics
----------------------------------------------------------
          7  recursive calls
          4  db block gets
    3144530  consistent gets
       4336  physical reads
          0  redo size
        203  bytes sent via SQL*Net to client
        315  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
         29  sorts (memory)
          0  sorts (disk)
          1  rows processed

  1  SELECT count(1)
  2  FROM  PS_JOB JOB,  PS_S_SS_PERDATA_VW ADDRESS,
  3        (select setid, location, descr, COUNTRY, effdt from ( select setid,
location, descr ,COUNTRY, eff_status
  4         effdt, max(effdt) over (partition by  setid, location) max_effdt
  5             from ps_location_tbl    )
  6             where effdt = max_effdt
  7            and effdt<=sysdate
  8            and eff_status = 'A' ) Loc,
  9        (select Country ,s_ss_scheme_type,effdt from
 10           ( select Country ,s_ss_scheme_type, eff_status,
 11           effdt, max(effdt) over (partition by Country ) max_effdt
 12      from PS_S_SS_CTRY_SETUP  )
 13      where effdt = max_effdt
 14      and effdt<=sysdate
 15      and eff_status = 'A' )  COUNTRY ,
 16        PS_S_SS_SCHEME_TBL  SCHEME
 17        WHERE 1=1
 18   AND JOB.REG_TEMP = 'R'
 19   AND JOB.EMPL_CLASS IN ('F','U')
 20   AND JOB.EMPL_STATUS IN ( 'A','L','S','P')
 21   AND JOB.EMPL_RCD=0
 22        AND TO_CHAR(S_SS_GRANT_DT,'MMYYYY') =  '052003'
 23        AND JOB.EMPLID=ADDRESS.EMPLID
 24        AND job.SETID_LOCATION = loc.setid
 25        AND job.LOCATION =  loc.location
 26        AND loc.country = country.country
 27        AND JOB.EFFDT = ( SELECT
 28                     MAX(B.EFFDT) FROM PS_JOB B
 29             WHERE JOB.EMPLID =B.EMPLID
 30             AND JOB.EMPL_RCD = B.EMPL_RCD AND B.EFFDT  <= sysdate)
 31       AND JOB.EFFSEQ = (SELECT MAX(C.EFFSEQ)
 32                     FROM PS_JOB C WHERE JOB.EMPLID =C.EMPLID
 33                     AND JOB.EMPL_RCD =C.EMPL_RCD
 34                     AND JOB.EFFDT = C.EFFDT )
 35*       AND SCHEME.S_SS_SCHEME_TYPE = COUNTRY.S_SS_SCHEME_TYPE
SQL> save job1 repl
Wrote file job1
SQL> /

  COUNT(1)
----------
     26563

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=44 Card=1 Bytes=122)
   1    0   SORT (AGGREGATE)
   2    1     FILTER
   3    2       NESTED LOOPS (Cost=44 Card=1 Bytes=122)
   4    3         NESTED LOOPS (Cost=43 Card=1 Bytes=114)
   5    4           NESTED LOOPS (Cost=39 Card=35 Bytes=2590)
   6    5             NESTED LOOPS (Cost=6 Card=1 Bytes=40)
   7    6               TABLE ACCESS (FULL) OF 'PS_S_SS_SCHEME_TBL' (C
          ost=1 Card=1 Bytes=13)

   8    6               VIEW
   9    8                 WINDOW (SORT)
  10    9                   TABLE ACCESS (FULL) OF 'PS_S_SS_CTRY_SETUP
          ' (Cost=1 Card=82 Bytes=1476)

  11    5             VIEW (Cost=34 Card=3526 Bytes=119884)
  12   11               WINDOW (SORT)
  13   12                 TABLE ACCESS (FULL) OF 'PS_LOCATION_TBL' (Co
          st=14 Card=3526 Bytes=91676)

  14    4           TABLE ACCESS (BY INDEX ROWID) OF 'PS_JOB' (Cost=1
          Card=1 Bytes=40)

  15   14             INDEX (RANGE SCAN) OF 'PSDJOB' (NON-UNIQUE)
  16    3         INDEX (RANGE SCAN) OF 'PS_PERSONAL_DATA' (UNIQUE)
  17    2       SORT (AGGREGATE)
  18   17         FIRST ROW (Cost=3 Card=2 Bytes=38)
  19   18           INDEX (RANGE SCAN (MIN/MAX)) OF 'PSAJOB' (NON-UNIQ
          UE) (Cost=3 Card=2)

  20    2       SORT (AGGREGATE)
  21   20         FIRST ROW (Cost=3 Card=1 Bytes=22)
  22   21           INDEX (RANGE SCAN (MIN/MAX)) OF 'PSAJOB' (NON-UNIQ
          UE) (Cost=3 Card=1)

Statistics
----------------------------------------------------------
         13  recursive calls
         12  db block gets
    1375223  consistent gets
       2612  physical reads
          0  redo size
        203  bytes sent via SQL*Net to client
        315  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
         33  sorts (memory)
          0  sorts (disk)
          1  rows processed

          Followup:

when I'm taking questions -- feel free to put that there with all of the
supporting information one would need to be able to give you a suggestion with.
you know, tkprofs, descriptions of the tables, cardinalities, etc.

      Rating: 3
Query with
      explain plan  May 22, 2003
Reviewer:  siva
       from INDIA
Hello TOM,
   I ran the query with autotrace on.   Can you give the suggestion how can i
rewrite the second query. I'm fighting with this query for the past one week.
   Is it possible to go for materialized view for this query?. if it is, which
method (COMPLETE REFRESH etc) of MV i can use.
   I used analytic functions. But the output are different. Why?. How do i use
analytic function on PS_JOB Table. B'cos there are two MAX function with
  different logic.

SQL> get s_ss_r01_1_qry
  1  SELECT count(*)  FROM        PS_JOB JOB,
  2        PS_S_SS_PERDATA_VW ADDRESS,
  3        ps_location_tbl Loc,
  4        PS_S_SS_CTRY_SETUP  COUNTRY ,
  5        PS_S_SS_SCHEME_TBL  SCHEME
  6        WHERE 1=1
  7        AND JOB.REG_TEMP = 'R'
  8        AND JOB.EMPL_CLASS IN ('F','U')
  9        AND JOB.EMPL_STATUS IN ( 'A','L','S','P')
 10        AND JOB.EMPL_RCD=0
 11        AND TO_CHAR(S_SS_GRANT_DT,'MMYYYY') =  '052003'
 12        AND JOB.EMPLID=ADDRESS.EMPLID
 13        AND job.SETID_LOCATION = loc.setid
 14        AND job.LOCATION =  loc.location
 15        AND loc.country = country.country
 16        AND SCHEME.S_SS_SCHEME_TYPE = COUNTRY.S_SS_SCHEME_TYPE
 17        AND JOB.EFFDT = ( SELECT
 18                     MAX(B.EFFDT) FROM PS_JOB B
 19             WHERE JOB.EMPLID =B.EMPLID
 20             AND JOB.EMPL_RCD = B.EMPL_RCD AND B.EFFDT  <= sysdate)
 21             AND JOB.EFFSEQ = (SELECT MAX(C.EFFSEQ)
 22                     FROM PS_JOB C WHERE JOB.EMPLID =C.EMPLID
 23             AND JOB.EMPL_RCD =C.EMPL_RCD
 24                     AND JOB.EFFDT = C.EFFDT )
 25             AND Loc.effdt = ( Select max(LocEffdt.effdt)
 26             from ps_location_tbl LocEffdt where loc.setid = LocEffdt.setid
and
 27             loc.location = LocEffdt.location and LocEffdt.effdt <= sysdate )
AND
 28             LOC.EFF_STATUS='A' AND COUNTRY.EFFDT = ( SELECT MAX(CNTRY.EFFDT)
FROM
 29             PS_S_SS_CTRY_SETUP CNTRY WHERE COUNTRY.COUNTRY = CNTRY.COUNTRY
AND
 30           CNTRY.EFFDT <= JOB.EFFDT ) AND COUNTRY.EFF_STATUS = 'A'
 31* ORDER BY      SCHEME.S_SS_SCHEME_TYPE,COUNTRY.COUNTRY,JOB.EMPLID
SQL> /

  COUNT(*)
----------
     27288

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=1 Bytes=105)
   1    0   SORT (AGGREGATE)
   2    1     FILTER
   3    2       NESTED LOOPS (Cost=5 Card=1 Bytes=105)
   4    3         NESTED LOOPS (Cost=4 Card=1 Bytes=97)
   5    4           NESTED LOOPS (Cost=3 Card=1 Bytes=57)
   6    5             NESTED LOOPS (Cost=2 Card=1 Bytes=31)
   7    6               TABLE ACCESS (FULL) OF 'PS_S_SS_CTRY_SETUP' (C
          ost=1 Card=1 Bytes=18)

   8    6               TABLE ACCESS (BY INDEX ROWID) OF 'PS_S_SS_SCHE
          ME_TBL' (Cost=1 Card=1 Bytes=13)

   9    8                 INDEX (RANGE SCAN) OF 'PS1S_SS_SCHEME_TBL' (
          NON-UNIQUE) (Cost=1 Card=1)

  10    5             TABLE ACCESS (BY INDEX ROWID) OF 'PS_LOCATION_TB
          L' (Cost=1 Card=61 Bytes=1586)

  11   10               INDEX (RANGE SCAN) OF 'PSALOCATION_TBL' (NON-U
          NIQUE)

  12    4           TABLE ACCESS (BY INDEX ROWID) OF 'PS_JOB' (Cost=1
          Card=1 Bytes=40)

  13   12             INDEX (RANGE SCAN) OF 'PSDJOB' (NON-UNIQUE)
  14    3         INDEX (RANGE SCAN) OF 'PS_PERSONAL_DATA' (UNIQUE)
  15    2       SORT (AGGREGATE)
  16   15         FIRST ROW (Cost=3 Card=2 Bytes=38)
  17   16           INDEX (RANGE SCAN (MIN/MAX)) OF 'PSAJOB' (NON-UNIQ
          UE) (Cost=3 Card=2)

  18    2       SORT (AGGREGATE)
  19   18         FIRST ROW (Cost=3 Card=1 Bytes=22)
  20   19           INDEX (RANGE SCAN (MIN/MAX)) OF 'PSAJOB' (NON-UNIQ
          UE) (Cost=3 Card=1)

  21    2       SORT (AGGREGATE)
  22   21         FIRST ROW (Cost=2 Card=1 Bytes=20)
  23   22           INDEX (RANGE SCAN (MIN/MAX)) OF 'PS_LOCATION_TBL'
          (UNIQUE) (Cost=2 Card=1)

  24    2       SORT (AGGREGATE)
  25   24         FIRST ROW (Cost=2 Card=1 Bytes=12)
  26   25           INDEX (RANGE SCAN (MIN/MAX)) OF 'PS_S_SS_CTRY_SETU
          P' (UNIQUE) (Cost=2 Card=1)

Statistics
----------------------------------------------------------
          7  recursive calls
          4  db block gets
    3144530  consistent gets
       4336  physical reads
          0  redo size
        203  bytes sent via SQL*Net to client
        315  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
         29  sorts (memory)
          0  sorts (disk)
          1  rows processed

  1  SELECT count(1)
  2  FROM  PS_JOB JOB,  PS_S_SS_PERDATA_VW ADDRESS,
  3        (select setid, location, descr, COUNTRY, effdt from ( select setid,
location, descr ,COUNTRY, eff_status
  4         effdt, max(effdt) over (partition by  setid, location) max_effdt
  5             from ps_location_tbl    )
  6             where effdt = max_effdt
  7            and effdt<=sysdate
  8            and eff_status = 'A' ) Loc,
  9        (select Country ,s_ss_scheme_type,effdt from
 10           ( select Country ,s_ss_scheme_type, eff_status,
 11           effdt, max(effdt) over (partition by Country ) max_effdt
 12      from PS_S_SS_CTRY_SETUP  )
 13      where effdt = max_effdt
 14      and effdt<=sysdate
 15      and eff_status = 'A' )  COUNTRY ,
 16        PS_S_SS_SCHEME_TBL  SCHEME
 17        WHERE 1=1
 18   AND JOB.REG_TEMP = 'R'
 19   AND JOB.EMPL_CLASS IN ('F','U')
 20   AND JOB.EMPL_STATUS IN ( 'A','L','S','P')
 21   AND JOB.EMPL_RCD=0
 22        AND TO_CHAR(S_SS_GRANT_DT,'MMYYYY') =  '052003'
 23        AND JOB.EMPLID=ADDRESS.EMPLID
 24        AND job.SETID_LOCATION = loc.setid
 25        AND job.LOCATION =  loc.location
 26        AND loc.country = country.country
 27        AND JOB.EFFDT = ( SELECT
 28                     MAX(B.EFFDT) FROM PS_JOB B
 29             WHERE JOB.EMPLID =B.EMPLID
 30             AND JOB.EMPL_RCD = B.EMPL_RCD AND B.EFFDT  <= sysdate)
 31       AND JOB.EFFSEQ = (SELECT MAX(C.EFFSEQ)
 32                     FROM PS_JOB C WHERE JOB.EMPLID =C.EMPLID
 33                     AND JOB.EMPL_RCD =C.EMPL_RCD
 34                     AND JOB.EFFDT = C.EFFDT )
 35*       AND SCHEME.S_SS_SCHEME_TYPE = COUNTRY.S_SS_SCHEME_TYPE
SQL> save job1 repl
Wrote file job1
SQL> /

  COUNT(1)
----------
     26563

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=44 Card=1 Bytes=122)
   1    0   SORT (AGGREGATE)
   2    1     FILTER
   3    2       NESTED LOOPS (Cost=44 Card=1 Bytes=122)
   4    3         NESTED LOOPS (Cost=43 Card=1 Bytes=114)
   5    4           NESTED LOOPS (Cost=39 Card=35 Bytes=2590)
   6    5             NESTED LOOPS (Cost=6 Card=1 Bytes=40)
   7    6               TABLE ACCESS (FULL) OF 'PS_S_SS_SCHEME_TBL' (C
          ost=1 Card=1 Bytes=13)

   8    6               VIEW
   9    8                 WINDOW (SORT)
  10    9                   TABLE ACCESS (FULL) OF 'PS_S_SS_CTRY_SETUP
          ' (Cost=1 Card=82 Bytes=1476)

  11    5             VIEW (Cost=34 Card=3526 Bytes=119884)
  12   11               WINDOW (SORT)
  13   12                 TABLE ACCESS (FULL) OF 'PS_LOCATION_TBL' (Co
          st=14 Card=3526 Bytes=91676)

  14    4           TABLE ACCESS (BY INDEX ROWID) OF 'PS_JOB' (Cost=1
          Card=1 Bytes=40)

  15   14             INDEX (RANGE SCAN) OF 'PSDJOB' (NON-UNIQUE)
  16    3         INDEX (RANGE SCAN) OF 'PS_PERSONAL_DATA' (UNIQUE)
  17    2       SORT (AGGREGATE)
  18   17         FIRST ROW (Cost=3 Card=2 Bytes=38)
  19   18           INDEX (RANGE SCAN (MIN/MAX)) OF 'PSAJOB' (NON-UNIQ
          UE) (Cost=3 Card=2)

  20    2       SORT (AGGREGATE)
  21   20         FIRST ROW (Cost=3 Card=1 Bytes=22)
  22   21           INDEX (RANGE SCAN (MIN/MAX)) OF 'PSAJOB' (NON-UNIQ
          UE) (Cost=3 Card=1)

Statistics
----------------------------------------------------------
         13  recursive calls
         12  db block gets
    1375223  consistent gets
       2612  physical reads
          0  redo size
        203  bytes sent via SQL*Net to client
        315  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
         33  sorts (memory)
          0  sorts (disk)
          1  rows processed

      Rating: 5
Excellent
      work  June 04, 2003
Reviewer:  A reader
       from India
Excellent work....

      Rating: 5
Tuning
      Subquery  June 11, 2003
Reviewer:  Praveen
       from India
Hi Tom,
     Can you pl. help me to tune this query,
 csa_acc_perf->129 million records, and cln_pre_account->  500,000 records.
SELECT a.account_id,
    a.bill_sequence,
       a.tot_amount,
    a.bill_amount,
    a.tot_vat,
    a.bill_vat
FROM (SELECT account_id,
         bill_sequence,
         tot_amount,
         bill_amount,
         tot_vat,
         bill_vat
             max(bill_sequence)
             over (partition by account_id) max_bill_sequence
        FROM csa_acc_perf )  a
      where a.bill_sequence = max_bill_sequence
        and exists (select 'X'
        from cln_pre_account cln
        where a.account_id = cln.account_id)

          Followup:

it'll go really fast if you don't run it (thats the easiest way to speed
something up).

seriously -- the inline view and the way you have it coded could be harming you
here.  It precludes the predicate being pushed down -- so that query must
materialize the inline view for all account_ids, even ones NOT in the
cln_pre_account table.

Perhaps you mean to query:

select * from
(
SELECT account_id,
         bill_sequence,
         tot_amount,
         bill_amount,
         tot_vat,
         bill_vat
             max(bill_sequence)
             over (partition by account_id) max_bill_sequence
        FROM csa_acc_perf
 where account_id IN ( select account_id from cln_pre_account )
)
where bill_sequence = max_bill_sequence

or if account_id is known to be unique in cln_pre_account

select * from (
SELECT account_id,
         bill_sequence,
         tot_amount,
         bill_amount,
         tot_vat,
         bill_vat
             max(bill_sequence)
             over (partition by account_id) max_bill_sequence
        FROM csa_acc_perf a, cln_pre_account b
      where a.caccount_id = b.account_id )
where bill_sequence = max_bill_sequence
/

remember analytics get done AFTER a where clause, so if you push them down into
an inline view they must get done BEFORE the outer portion of the query can work
on them -- hence, you are analytic'ing lots of data perhaps that you need not.

      Rating:
      5
  June 12, 2003
Reviewer:
      A reader

      Rating: 5
Very
      smart.  August 15, 2003
Reviewer:  Kashif
       from Reston, VA

      Rating: 5
rownum vs sub
      select  August 19, 2003
Reviewer:  Alex
       from Canberra
Was just wondering wether I should be recomending rownum instead of sub-select,
and here is the evidence for rownum!

      Rating: 4
/*+ index_desc
        October 15, 2003
Reviewer:  Mark
       from UK
In the above example with the /*+ index_desc */ hint you say this is a bad
approach.

Would you agree that it is useful in the following scenario to get the list of
id's in latest order first ?

select /*+ index_desc ( d table_uk ) */ *
from drive_table
order by id desc

table_uk - unique key for id.

The plan for with and without the hint:

SELECT STATEMENT Optimizer MODE=CHOOSE        3 M         66000

  SORT ORDER BY        3 M    348 M    66000

    TABLE ACCESS FULL    MRP.DRIVE_TABLE    3 M    348 M    7967

SELECT STATEMENT Optimizer MODE=CHOOSE        3 M         755514

  TABLE ACCESS BY INDEX ROWID    MRP.DRIVE_TABLE    3 M    348 M    755514

    INDEX FULL SCAN DESCENDING    MRP.TABLE_UK    3 M         8312

In my query with large table the performance is massively improved - why doesn't
the optimizer identify this plan by default (8.1.7.4).

          Followup:

is it improved?

I cannot tell from your plan how many rows are coming out of this (don't know
what you used to dump the plan but i don't know which is cost/card and bytes)

I'll guess 3million

to read 3 million rows via an index -- really utterly bad

it gets the first rows first -- but the last row could take days.

Now, if you want the first rows first, you can (and should) say /*+ first_rows
*/

that is a 'safe' hint.  it gives the optimizer something to work with - it is
not dictating HOW to get first rows first, just that you would like the first
rows first.

Else, it is thinking -- hmmm, lots of rows - index = kiss of death.

      Rating:
      4
Hmm  October 16, 2003
Reviewer:
      Mark  from UK
We want the last 10 orders in a 3million order table:

apps@apr11i.devl> set autotrace traceonly
apps@apr11i.devl> SELECT *
FROM (
SELECT /*+ index_desc ( a mrp_gross_requirements_u1) */ *
FROM mrp_gross_requirements a
ORDER BY demand_id DESC )
WHERE ROWNUM <=10
  2    3    4    5    6    7
apps@apr11i.devl> /

10 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=755514 Card=3763361
          Bytes=1802649919)

   1    0   COUNT (STOPKEY)
   2    1     VIEW (Cost=755514 Card=3763361 Bytes=1802649919)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'MRP_GROSS_REQUIREMEN
          TS' (Cost=755514 Card=3763361 Bytes=365046017)

   4    3         INDEX (FULL SCAN DESCENDING) OF 'MRP_GROSS_REQUIREME
          NTS_U1' (UNIQUE) (Cost=8312 Card=3763361)

Statistics
----------------------------------------------------------
         11  recursive calls
          0  db block gets
         31  consistent gets
          0  physical reads
          0  redo size
       5021  bytes sent via SQL*Net to client
        355  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
         10  rows processed

  1  SELECT *
  2  FROM (
  3  SELECT *
  4  FROM mrp_gross_requirements a
  5  ORDER BY demand_id DESC )
  6* WHERE ROWNUM <=10
apps@apr11i.devl> /

10 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=66000 Card=3763361 B
          ytes=1802649919)

   1    0   COUNT (STOPKEY)
   2    1     VIEW (Cost=66000 Card=3763361 Bytes=1802649919)
   3    2       SORT (ORDER BY STOPKEY) (Cost=66000 Card=3763361 Bytes
          =365046017)

   4    3         TABLE ACCESS (FULL) OF 'MRP_GROSS_REQUIREMENTS' (Cos
          t=7967 Card=3763361 Bytes=365046017)

Statistics
----------------------------------------------------------
         17  recursive calls
          4  db block gets
      62519  consistent gets
      62507  physical reads
          0  redo size
       5021  bytes sent via SQL*Net to client
        355  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          4  sorts (memory)
          0  sorts (disk)
         10  rows processed

And First Rows:

 1  SELECT *
  2  FROM (
  3  SELECT /*+ First_Rows */ *
  4  FROM mrp_gross_requirements a
  5  ORDER BY demand_id DESC )
  6* WHERE ROWNUM <=10
apps@apr11i.devl> /

10 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=66000 Card
          =3763361 Bytes=1802649919)

   1    0   COUNT (STOPKEY)
   2    1     VIEW (Cost=66000 Card=3763361 Bytes=1802649919)
   3    2       SORT (ORDER BY STOPKEY) (Cost=66000 Card=3763361 Bytes
          =365046017)

   4    3         TABLE ACCESS (FULL) OF 'MRP_GROSS_REQUIREMENTS' (Cos
          t=7967 Card=3763361 Bytes=365046017)

Statistics
----------------------------------------------------------
          3  recursive calls
          4  db block gets
      62516  consistent gets
      62506  physical reads
          0  redo size
       5021  bytes sent via SQL*Net to client
        355  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          4  sorts (memory)
          0  sorts (disk)
         10  rows processed

It looks like the gains are made because there is no 'SORT' operation needed
when using the index and it speeds up our process significantly.

Why isn't /*+ First_Rows */ hint causing the optimizer to say 'hey I have an
index here I can use to get the answer really quickly ?'.

          Followup:

does demand_id have a NOT NULL constraint on it?

I cannot reproduce your findings in 8174:

big_table@ORA817DEV> big_table@ORA817DEV> big_table@ORA817DEV> set autotrace
traceonly explain
big_table@ORA817DEV>
big_table@ORA817DEV> SELECT *
  2  FROM (
  3  SELECT /*+ FIRST_ROWS */ *
  4  FROM big_table a
  5  ORDER BY id DESC )
  6  WHERE ROWNUM <=10
  7  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=26416 Card=1576192
Bytes=222243072)
   1    0   COUNT (STOPKEY)
   2    1     VIEW (Cost=26416 Card=1576192 Bytes=222243072)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=26416
Card=1576192 Bytes=143433472)
   4    3         INDEX (FULL SCAN DESCENDING) OF 'BIG_TABLE_IDX' (NON-UNIQUE)
(Cost=3594 Card=1576192)

big_table@ORA817DEV> SELECT *
  2  FROM (
  3  SELECT /*+ index_desc( A BIG_TABLE_IDX ) */ *
  4  FROM big_table a
  5  ORDER BY id DESC )
  6  WHERE ROWNUM <=10
  7  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=26416 Card=1576192
Bytes=222243072)
   1    0   COUNT (STOPKEY)
   2    1     VIEW (Cost=26416 Card=1576192 Bytes=222243072)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=26416
Card=1576192 Bytes=143433472)
   4    3         INDEX (FULL SCAN DESCENDING) OF 'BIG_TABLE_IDX' (NON-UNIQUE)
(Cost=3594 Card=1576192)

big_table@ORA817DEV>
big_table@ORA817DEV> set autotrace off
big_table@ORA817DEV>

      Rating: 4
Yep definitiely
      not nullable  October 17, 2003
Reviewer:
      Mark  from UK
Demand ID is not null (apologies for SQL layout - its autogenerated from TOAD)

CREATE TABLE MRP_GROSS_REQUIREMENTS
(
  DEMAND_ID                    NUMBER,
  LAST_UPDATE_DATE             DATE,
  LAST_UPDATED_BY              NUMBER,
  CREATION_DATE                DATE,
  CREATED_BY                   NUMBER,
  LAST_UPDATE_LOGIN            NUMBER,
  INVENTORY_ITEM_ID            NUMBER,
  ORGANIZATION_ID              NUMBER,
  COMPILE_DESIGNATOR           VARCHAR2(10),
  USING_ASSEMBLY_ITEM_ID       NUMBER,
  USING_ASSEMBLY_DEMAND_DATE   DATE,
  USING_REQUIREMENTS_QUANTITY  NUMBER,
  ASSEMBLY_DEMAND_COMP_DATE    DATE,
  DEMAND_TYPE                  NUMBER,
  ORIGINATION_TYPE             NUMBER,
  DISPOSITION_ID               NUMBER,
  DAILY_DEMAND_RATE            NUMBER,
  REQUEST_ID                   NUMBER,
  RESERVE_QUANTITY             NUMBER,
  PROGRAM_APPLICATION_ID       NUMBER,
  PROGRAM_ID                   NUMBER,
  PROGRAM_UPDATE_DATE          DATE,
  SOURCE_ORGANIZATION_ID       NUMBER,
  RESERVATION_ID               NUMBER,
  UPDATED                      NUMBER,
  STATUS                       NUMBER,
  APPLIED                      NUMBER,
  DEMAND_CLASS                 VARCHAR2(30),
  FIRM_QUANTITY                NUMBER,
  FIRM_DATE                    DATE,
  OLD_DEMAND_QUANTITY          NUMBER,
  OLD_DEMAND_DATE              DATE,
  DEMAND_SCHEDULE_NAME         VARCHAR2(10),
  PROJECT_ID                   NUMBER(15),
  TASK_ID                      NUMBER(15),
  PLANNING_GROUP               VARCHAR2(30),
  END_ITEM_UNIT_NUMBER         VARCHAR2(30),
  LENDING_PROJECT_ID           NUMBER,
  LENDING_TASK_ID              NUMBER,
  CHECK (DEMAND_ID IS NOT NULL),
  CHECK (LAST_UPDATE_DATE IS NOT NULL),
  CHECK (LAST_UPDATED_BY IS NOT NULL),
  CHECK (CREATION_DATE IS NOT NULL),
  CHECK (CREATED_BY IS NOT NULL),
  CHECK (INVENTORY_ITEM_ID IS NOT NULL),
  CHECK (ORGANIZATION_ID IS NOT NULL),
  CHECK (COMPILE_DESIGNATOR IS NOT NULL),
  CHECK (USING_ASSEMBLY_ITEM_ID IS NOT NULL),
  CHECK (USING_ASSEMBLY_DEMAND_DATE IS NOT NULL),
  CHECK (USING_REQUIREMENTS_QUANTITY IS NOT NULL),
  CHECK (DEMAND_TYPE IS NOT NULL),
  CHECK (ORIGINATION_TYPE IS NOT NULL)
)
TABLESPACE MRPTAB
PCTUSED    80
PCTFREE    2
INITRANS   10
MAXTRANS   255
STORAGE    (
            INITIAL          426M
            NEXT             20M
            MINEXTENTS       1
            MAXEXTENTS       2147483645
            PCTINCREASE      0
            FREELISTS        4
            FREELIST GROUPS  1
            BUFFER_POOL      DEFAULT
           )
LOGGING
NOCACHE
NOPARALLEL;

CREATE INDEX MRP_GROSS_REQUIREMENTS_N1 ON MRP_GROSS_REQUIREMENTS
(ORGANIZATION_ID, COMPILE_DESIGNATOR, INVENTORY_ITEM_ID, USING_ASSEMBLY_ITEM_ID,
USING_ASSEMBLY_DEMAND_DATE)
LOGGING
TABLESPACE MRPIDX
PCTFREE    1
INITRANS   11
MAXTRANS   255
STORAGE    (
            INITIAL          154640K
            NEXT             880K
            MINEXTENTS       1
            MAXEXTENTS       2147483645
            PCTINCREASE      0
            FREELISTS        4
            FREELIST GROUPS  1
            BUFFER_POOL      DEFAULT
           )
NOPARALLEL;

CREATE INDEX MRP_GROSS_REQUIREMENTS_N2 ON MRP_GROSS_REQUIREMENTS
(APPLIED, COMPILE_DESIGNATOR, ORGANIZATION_ID)
LOGGING
TABLESPACE MRPIDX
PCTFREE    1
INITRANS   11
MAXTRANS   255
STORAGE    (
            INITIAL          86080K
            NEXT             6160K
            MINEXTENTS       1
            MAXEXTENTS       2147483645
            PCTINCREASE      0
            FREELISTS        4
            FREELIST GROUPS  1
            BUFFER_POOL      DEFAULT
           )
NOPARALLEL;

CREATE UNIQUE INDEX MRP_GROSS_REQUIREMENTS_U1 ON MRP_GROSS_REQUIREMENTS
(DEMAND_ID)
LOGGING
TABLESPACE MRPIDX
PCTFREE    1
INITRANS   11
MAXTRANS   255
STORAGE    (
            INITIAL          8920K
            NEXT             8920K
            MINEXTENTS       1
            MAXEXTENTS       2147483645
            PCTINCREASE      0
            FREELISTS        4
            FREELIST GROUPS  1
            BUFFER_POOL      DEFAULT
           )
NOPARALLEL;

Any chance its an init.ora setting ??

SELECT name,value
FROM v$parameter
ORDER BY name
/

O7_DICTIONARY_ACCESSIBILITY    TRUE
_complex_view_merging    TRUE
_fast_full_scan_enabled    FALSE
_like_with_bind_as_equality    TRUE
_new_initial_join_orders    TRUE
_optimizer_mode_force    TRUE
_optimizer_undo_changes    FALSE
_or_expand_nvl_predicate    TRUE
_ordered_nested_loop    TRUE
_push_join_predicate    TRUE
_push_join_union_view    TRUE
_shared_pool_reserved_min_alloc    4100
_sort_elimination_cost_ratio    5
_sqlexec_progression_cost    0
_table_scan_cost_plus_one    TRUE
_trace_files_public    TRUE
_use_column_stats_for_function    TRUE
active_instance_count
always_anti_join    NESTED_LOOPS
always_semi_join    STANDARD
aq_tm_processes    1
audit_file_dest    ?/rdbms/audit
audit_trail    NONE
background_core_dump    full
background_dump_dest    /orav8174APR11I/rdbms/log/APR11I/background
backup_tape_io_slaves    FALSE
bitmap_merge_area_size    1048576
blank_trimming    FALSE
buffer_pool_keep
buffer_pool_recycle
commit_point_strength    1
compatible    8.1.7
control_file_record_keep_time    7
control_files    /orav8174APR11I/dbs/dbsAPR11I/cntl01APR11I.dbf,
/orav8174APR11I/dbs/dbsAPR11I/cntl02APR11I.dbf,
/orav8174APR11I/dbs/dbsAPR11I/cntl03APR11I.dbf
core_dump_dest    /orav8174APR11I/rdbms/log/APR11I/core
cpu_count    6
create_bitmap_area_size    8388608
cursor_sharing    EXACT
cursor_space_for_time    FALSE
db_block_buffers    41400
db_block_checking    FALSE
db_block_checksum    TRUE
db_block_lru_latches    3
db_block_max_dirty_target    0
db_block_size    8192
db_domain
db_file_direct_io_count    64
db_file_multiblock_read_count    8
db_file_name_convert
db_files    500
db_name    APR11I
db_writer_processes    1
dblink_encrypt_login    FALSE
dbwr_io_slaves    0
disk_asynch_io    TRUE
distributed_transactions    165
dml_locks    10000
enqueue_resources    32000
event    10262 trace name context forever, level 4000
fast_start_io_target    41400
fast_start_parallel_rollback    LOW
fixed_date
gc_defer_time    10
gc_files_to_locks
gc_releasable_locks    0
gc_rollback_locks    0-1024=32!8REACH
global_names    FALSE
hash_area_size    30000000
hash_join_enabled    TRUE
hash_multiblock_io_count    0
hi_shared_memory_address    0
hpux_sched_noage
hs_autoregister    TRUE
ifile    ?/dbs/ifilecbo.ora
instance_groups
instance_name    APR11I
instance_number    0
java_max_sessionspace_size    0
java_pool_size    100000000
java_soft_sessionspace_limit    8000000
job_queue_interval    90
job_queue_processes    5
large_pool_size    0
license_max_sessions    0
license_max_users    0
license_sessions_warning    0
lm_locks    12000
lm_ress    6000
local_listener
lock_name_space
lock_sga    FALSE
log_archive_dest    /archlogs/archAPR11I/arch
log_archive_dest_1
log_archive_dest_2
log_archive_dest_3
log_archive_dest_4
log_archive_dest_5
log_archive_dest_state_1    enable
log_archive_dest_state_2    enable
log_archive_dest_state_3    enable
log_archive_dest_state_4    enable
log_archive_dest_state_5    enable
log_archive_duplex_dest
log_archive_format    %t_%s.dbf
log_archive_max_processes    1
log_archive_min_succeed_dest    1
log_archive_start    FALSE
log_archive_trace    0
log_buffer    1048576
log_checkpoint_interval    100000
log_checkpoint_timeout    1200
log_checkpoints_to_alert    TRUE
log_file_name_convert
max_commit_propagation_delay    700
max_dump_file_size    UNLIMITED
max_enabled_roles    100
max_rollback_segments    100
mts_circuits    0
mts_dispatchers
mts_listener_address
mts_max_dispatchers    5
mts_max_servers    20
mts_multiple_listeners    FALSE
mts_servers    0
mts_service    APR11I
mts_sessions    0
nls_calendar
nls_comp
nls_currency
nls_date_format    DD-MON-RR
nls_date_language
nls_dual_currency
nls_iso_currency
nls_language    american
nls_numeric_characters    .,
nls_sort    binary
nls_territory    america
nls_time_format
nls_time_tz_format
nls_timestamp_format
nls_timestamp_tz_format
object_cache_max_size_percent    10
object_cache_optimal_size    102400
open_cursors    1000
open_links    4
open_links_per_instance    4
ops_interconnects
optimizer_features_enable    8.1.7
optimizer_index_caching    90
optimizer_index_cost_adj    40
optimizer_max_permutations    2000
optimizer_mode    CHOOSE
optimizer_percent_parallel    0
oracle_trace_collection_name
oracle_trace_collection_path    /orav8174APR11I/rdbms/log/APR11I/core
oracle_trace_collection_size    5242880
oracle_trace_enable    FALSE
oracle_trace_facility_name    oracled
oracle_trace_facility_path    ?/otrace/admin/fdf
os_authent_prefix    ops$
os_roles    FALSE
parallel_adaptive_multi_user    FALSE
parallel_automatic_tuning    FALSE
parallel_broadcast_enabled    FALSE
parallel_execution_message_size    2152
parallel_instance_group
parallel_max_servers    16
parallel_min_percent    0
parallel_min_servers    0
parallel_server    FALSE
parallel_server_instances    1
parallel_threads_per_cpu    2
partition_view_enabled    FALSE
plsql_v2_compatibility    FALSE
pre_page_sga    FALSE
processes    500
query_rewrite_enabled    TRUE
query_rewrite_integrity    enforced
rdbms_server_dn
read_only_open_delayed    FALSE
recovery_parallelism    5
remote_dependencies_mode    TIMESTAMP
remote_login_passwordfile    NONE
remote_os_authent    FALSE
remote_os_roles    FALSE
replication_dependency_tracking    TRUE
resource_limit    FALSE
resource_manager_plan
rollback_segments    rb01, rb02, rb03, rb04, rb05, rb06, rb07, rb08, rb09, rb10,
rb99
row_locking    always
serial_reuse    DISABLE
serializable    FALSE
service_names    APR11I
session_cached_cursors    200
session_max_open_files    10
sessions    600
shadow_core_dump    full
shared_memory_address    0
shared_pool_reserved_size    52428800
shared_pool_size    524288000
sort_area_retained_size    0
sort_area_size    11796480
sort_multiblock_read_count    2
sql92_security    FALSE
sql_trace    FALSE
sql_version    NATIVE
standby_archive_dest    ?/dbs/arch
star_transformation_enabled    FALSE
tape_asynch_io    TRUE
text_enable    FALSE
thread    0
timed_os_statistics    0
timed_statistics    TRUE
tracefile_identifier
transaction_auditing    TRUE
transactions    660
transactions_per_rollback_segment    20
use_indirect_data_buffers    FALSE
use_sigio    TRUE
user_dump_dest    /orav8174APR11I/rdbms/log/APR11I/user
utl_file_dir    /usr/tmp

          Followup:

how about this -- use the 10053 trace event, compress the resulting trace file,
email to me (and then wait till next week - taking a 3 day weekend :)

i'll take a look at that.

alter session set events '10053 trace name context forever, level 1';
set autotrace traceonly explain
query with first_rows
query with index_desc
exit

      Rating: 5
query tuning
      question  October 17, 2003
Reviewer:
      A reader
Tom,

select count(distinct(a)), b
from t1
group by b;

A query like above takes a long time in our system -
I don't have access to the tkprof yet (sorry) Anyways,
I was thinking that will the above query benefit if
we create an index on a, b  - to use index as a
skinny table?

Unfortunately, as I was typing, I just realized b is nullable so this option is
perhaps not feasible!
Version is 9.0.1.4.

Any other comments from you may be helpful (I know
I have not given you enough info)

Thank you for your unbelievable service!

          Followup:

as long as one of A or B is defined as "NOT NULL" or you put in a predicate that
asserts that null A's and B's are not part of the result set -- sure.

If B is nullable and A is NOT NULL -- it is OK -- the entire table will be
covered by the index.  It would only be if both A and B are not null that the
index could not be used.

      Rating:
      5
thanx!  October 17, 2003
Reviewer:
      A reader

      Rating: 5
why index_desc
      is bad approach?  October 24, 2003
Reviewer:
      A reader
Hi

I dont understand why using this hint is a bad approach. I get what  asked for.
For example

select * from emp;

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

I want empno 7999

select /*+ index_desc (emp emp_pk) */ * from emp where rownum = 1

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
----------
      7999 ALLEN      SALESMAN        7698 20-FEB-81       1600        300
  30

I get what I asked.... If this is a bad approach then when do we use this
hint???

          Followup:

why use sql at that point.  we should just write in assembler maybe.

you don't get what you asked for there. what you get is very unstable,
unreliable code that if I ever saw in any application I was responsible for
would get you a sound smack on the head.

I don't know what you were "asking for" there but let me tell you what this
query:

select /*+ index_desc (emp emp_pk) */ * from emp where rownum = 1

does -- it returns a RANDOM record from emp.  nothing more, nothing less.  It
does not have to use that index.  That index might not exist.

it is very very very unreliable.  don't use hints in an attempt to get an
"answer".  the only thing you'll get is a "bug"

this is as bad as relying on "group by" to sort -- the database just doesn't
work that way.

      Rating: 5
I dont get your
      point, it does not return random values  October 24, 2003

Reviewer:  A reader
Hi

I have this table

select empno, ename from emp;

     EMPNO ENAME
---------- ----------
      7999 ALLEN
      7521 WARD
      7654 MARTIN
      7698 BLAKE
      7782 CLARK
      7839 KING
      8100 SMITH
      7566 JONES
      7788 SCOTT
      7876 ADAMS
      7900 JAMES
      7902 FORD
      7934 MILLER

If I want the max empno I can use

select /*+ index_desc (emp emp_pk) */ empno, ename from emp where rownum = 1;

     EMPNO ENAME
---------- ----------
      8100 SMITH

without sorting huh

          Followup:

no you cannot

no you cannot

no you cannot

you cannot do this reliably, you cannot count on this being so.

if you do, you are mistaken, you write fragile code that WILL BREAK.

suppose one year from now -- you go on vacation.  Someone comes along and says
"you know what, emp_pk doesn't fit the corporate standard for index names.  It
must be ABC$123_EMP_PRIM_KEY_INDEX.  Let me fix that"

now what?

or -- you know what, EMPNO needs to be unique but it must permit nulls for
temporary employees (system change).  Lets make EMPNO nullable.  Now what?
(index path for your query is obviated, cannot be used)

or -- you upgrade.  And we decide that since hints are hints and not directives
that we'll ignore you.  The FASTEST way to answer that query is in fact a FULL
SCAN and just stop after the first row.  Fair is Fair -- we are TOTALLY correct
and justified in doing that.

The ONLY way -- repeat -- THE ONLY way to get data from a relational database is
to use the ORDER BY clause.  PERIOD.  anything else -- well, thats a bug waiting
to happen.

The funny thing is -- the database will do the right thing for you "out of the
box", without you telling it anything special.

big_table@ORA920> set autotrace traceonly explain
big_table@ORA920> select * from ( select * from big_table order by id desc )
where rownum = 1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=58184 Card=1 Bytes=141)
   1    0   COUNT (STOPKEY)
   2    1     VIEW (Cost=58184 Card=3500000 Bytes=493500000)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=58184
Card=3500000 Bytes=350000000)
   4    3         INDEX (FULL SCAN DESCENDING) OF 'BIG_TABLE_PK' (UNIQUE)
(Cost=7658 Card=3500000)

That is the only correct way to achieve your goal.

your approach -- frightens me, scares me to death.  if you do this lots, you
have just created a maintenance nightmare of EPIC proportions for the
developers/dba's that come after you.

      Rating: 5
How about this
      one ?  November 05, 2003
Reviewer:
      A reader
Hi Tom,

I have a query but using min function (and I think similiar to max function).
The query is to get the min seq for each id. What do you of this query compare
with your suggestion?

select a.id, a.seq from seq_table a
where not exist (select 1 from seq_table b
                 where b.id = a.id and a.seq < b.seq)

Thanks,
David

          Followup:

looks pretty inefficient, but benchmark it.

think about the join it must do to satisfy that!

i'd rather make a single pass on the table, not multiple ones.

      Rating: 5
Something
      similar to this  December 09, 2003
Reviewer:
      A reader
Related to this, how do I go about doing the following:

Suppose I have a date in my query results like so

select ...,my_date,.... from ....

I want to get all the rows where my_date equals the second highest date in the
resultset. Basically, get max()-1 and then get all rows where
my_date=max(my_date)-1

I tried something like this

select ...,dense_rank() over (order by my_date)
from ...

I get something like

1
1
1
2
2
3
3
3
4

Now, I want to get just the rows with 3.

How can I do this? max(dense_rank() ...)-1 is not allowed!

Thanks

November 05, 2003 Ashiq -- Thanks for the question regarding "data
      files ", version 8.1.7

      You Asked
Hi Tom,

 We're product based company  and we've mid range ERP systems. The system has
financial accouting, sales, production ,hrms, and inventory modules.
   I've created separate tablespace for each modules and given "autoextend on"
for the corresponding data files. My senior DBA telling me not to give
"autoextend on" , instead he is telling me add new data files with 512mb(with
autoextend off) each time ,once datafiles got filled up.

    But i told my senior dba ,some point time user have to wait for us to create
additional data files and in production we can't make user to wait for us,but
still he's not agreeing with me.

  1.What is your suggestion? how you used design the physical files? Like
datafile with "autoextend on" or "autoextend off" .
  2.Is any drawbacks having only one datafiles for each modules?
  3.Is any advantage of having many datafile for each modules.

thanx

      and we said...
autoextend is something you either

a) love and use
b) hate and fear

there seems to be no middle ground.  Me, I use it -- i'm not always there to
watch things.  My opinion is -- if there is free space on my file system -- and
this space is to be used for database data -- what does it matter if

a) i allocate the space and micromanage everything
b) the system does it for me

well, it does matter if I'm not there to do it, as that will incur downtime.

I use autoextend.  you set the next = extent size for the LMT, set maxsize to
your maximum desired size (if any).  I go with a tablespace/module.  A file (or
two or three -- depends, files have limits as to max sizes).  Use striping at
the OS level to spread IO out (yes, I actually put indexes in the same
tablespace as my tables!).

but then, I don't want to be chained to my desk micromanaging bits and bytes on
the disk.

I'm sure we'll hear back from some people who hate autoextend :)

note, autoextend only really works well on "cooked" file systems -- not raw.
above discussion does not apply to raw...
Review &
      Followup

      Rating: 3
No hate, but
      not use  November 04, 2003
Reviewer:  A
      reader
I just can't see the benefit of using it.
I like the feeling of knowing that when my TS run out of space I still have some
space in the filesystem to add.
If the same happens with autoextend enabled then, well, better you have some
additional "physical disk" to add on the filesystem or you'll be in troubles.
I think autoextend off gives you more time to react.
And if you set the maxsize parameter, could you tell me what is the difference
with the fixed datafile size model?
Also, if you set the proper monitor scripts, you'd hardly run out of space (this
is valid for both models I think)
Thanks.

          Followup:

like I said, matter of choice.

I think the FASTEST way to respond is "autoextend" -- your monitor scripts would
tell you "hey, you know what, while you were out galavanting around, I added
some more storage -- ok".

it is what works for you (but I see "hate" out there lots)

agreed you must have sufficient disk space -- but why have the db return
possibly "failed to extend" when there is gobs of disk space but you just didn't
allocate it??  me, I'd rather autoextend, grab space as i need it, add more disk
when I need to.  I let my files run at 100% full

ops$tkyte@ORA9I> edit
Wrote file afiedt.buf

  1  select (select decode(extent_management,'LOCAL','*','') from
dba_tablespaces where tablespace_name =
  2  b.tablespace_name) || nvl(b.tablespace_name,
  3                      nvl(a.tablespace_name,'UNKOWN')) name,
  4        kbytes_alloc kbytes,
  5        kbytes_alloc-nvl(kbytes_free,0) used,
  6        nvl(kbytes_free,0) free,
  7        ((kbytes_alloc-nvl(kbytes_free,0))/
  8                                               kbytes_alloc)*100 pct_used
  9  from ( select sum(bytes)/1024 Kbytes_free,
 10                       max(bytes)/1024 largest,
 11                       tablespace_name
 12        from  sys.dba_free_space
 13        group by tablespace_name ) a,
 14       ( select sum(bytes)/1024 Kbytes_alloc,
 15                       sum(maxbytes)/1024 Kbytes_max,
 16                       tablespace_name
 17        from sys.dba_data_files
 18        group by tablespace_name
 19        union all
 20        select sum(bytes)/1024 Kbytes_alloc,
 21                       sum(maxbytes)/1024 Kbytes_max,
 22                       tablespace_name
 23        from sys.dba_temp_files
 24        group by tablespace_name )b
 25  where a.tablespace_name (+) = b.tablespace_name
 26* order by &1
ops$tkyte@ORA9I> /
old  26: order by &1
new  26: order by 5

                                                             %
Tablespace Name        KBytes         Used         Free   Used
---------------- ------------ ------------ ------------ ------
*RECDATA               26,624        2,560       24,064    9.6
*ATCDEMO_DATA           1,024          512          512   50.0
*BR_DATA                1,024          512          512   50.0
*BR_IDX                 1,024          512          512   50.0
*FLOW_1                 1,024          512          512   50.0
*IATP_ARCH_DATA         1,024          512          512   50.0
*INDX                   1,024          512          512   50.0
*KT_INDEX               1,024          512          512   50.0
*OMPSYS_DATA            1,024          512          512   50.0
*PITCHES_DATA           1,024          512          512   50.0
*TQM_IDX                1,024          512          512   50.0
*SECAPP                 1,024          512          512   50.0
*SARA                   1,024          512          512   50.0
*PRP_IDX                1,024          512          512   50.0
*PITCHES_IDX            1,024          512          512   50.0
*OMPSYS_IDX             1,024          512          512   50.0
*OEM_REPOSITORY         1,024          512          512   50.0
*KT_DATA                1,024          512          512   50.0
*GPS_IDX                1,024          512          512   50.0
*FLOWS_DATA         1,716,224      896,000      820,224   52.2
*DES2K_IDX              2,048        1,536          512   75.0
*ISOC                   2,048        1,536          512   75.0
*UNDOTBS1             565,248      500,288       64,960   88.5
*FLOW_3                 5,120        4,608          512   90.0
*REVIEW_IDX             7,168        6,656          512   92.9
*XDB                  379,392      360,960       18,432   95.1
*SURVEY_IDX            14,336       13,824          512   96.4
*TQM_DATA              25,600       25,088          512   98.0
*WEBDB_IDX             52,224       51,712          512   99.0
*USERS              4,147,200    4,109,824       37,376   99.1
*TOOLS                 97,280       96,768          512   99.5
*SYSTEM               763,904      763,072          832   99.9
*PKT_DATA             585,728      585,216          512   99.9
*WEBDB_DATA         1,466,368    1,465,856          512  100.0
*BOPPS              5,766,656    5,766,656            0  100.0
*DES2K_DATA           139,776      139,776            0  100.0
*GPS_DATA               2,048        2,048            0  100.0
*IATP_INDX             48,376       48,376            0  100.0
*OGS_DATA             102,912      102,912            0  100.0
*OLR_IDX                5,120        5,120            0  100.0
*PEOPLE               443,392      443,392            0  100.0
*TEMPORARY            199,680      199,680            0  100.0
*SURVEY_DATA           39,424       39,424            0  100.0
*RFP_DATA             120,320      120,320            0  100.0
*REVIEW_DATA        2,387,456    2,387,456            0  100.0
*PRP_DATA              41,984       41,984            0  100.0
*PPR_IDX                5,632        5,632            0  100.0
*PPR_DATA              75,776       75,776            0  100.0
*PMD_DATA              93,184       93,184            0  100.0
*PLATFORM_DATA         15,360       15,360            0  100.0
*PKT_IDX              323,584      323,584            0  100.0
*PEOPLE_RAW         3,790,336    3,790,336            0  100.0
*ORA_SP_DATA           12,800       12,800            0  100.0
*OLR_DATA              16,384       16,384            0  100.0
*LMS_DATA              57,856       57,856            0  100.0
*IATP_DATA             36,784       36,784            0  100.0
*FLOW_2                79,360       79,360            0  100.0
*DRSYS                 53,248       53,248            0  100.0
                 ------------ ------------ ------------
sum                23,732,392   22,752,168      980,224

58 rows selected.

and autoextend as needed (my max is 32gig, i have no fear right now of exceeding
that)

i monitor space at the os level to judge when I need more disk.

matter of preference i guess

      Rating: 3
Well, yes,
      but...  November 04, 2003
Reviewer:  A
      reader
See that query output? It is exactly what scare me to death. (I'm accepting here
that I fear of aoutoextend, so you're right on that ;) ).
However, how can you tell how much free space do you have for each tablespace? I
found really usefull taht kind of info.
You would have to either cross this output with your "df -k" command (and you'd
need to know exactly where each datafile is located ) or just have a lot of free
disk space out there.
Finally, I agree that, like you said, is just a matter of preference.
Thank you.

          Followup:

df -k

that is in fact what I use.  I have *sufficient* free space out there

Lets say each tablespace was 20% "free" -- except for one -- it was 100% full.
The sum of the 20%'s was say 10gig.  You have NO space on the OS (it is full).
The full guy must wait until you

a) buy more disk
b) reorg some of the other tablespaces (taking them to 100% perhaps)

to give it more room.

Me, I keep the file systems free space as high as possible, giving it (via
autoextend) to the file that needs it.  I don't preallocate -- for that leads
(in my case) to premature "i'm squeezed for space".

      Rating: 5
to a
      reader  November 05, 2003
Reviewer:  Marcio
       from br
<quote>
"However, how can you tell how much free space do you have for each tablespace?
I
found really usefull taht kind of info."
</quote>

If you're wondering to know about max use Tom's script like this:

  1  select name, kbytes, used, max_size, pct_max_used
  2    from (
  3  select nvl(b.tablespace_name,
  4               nvl(a.tablespace_name,'UNKOWN')) name,
  5         kbytes_alloc kbytes,
  6         kbytes_alloc-nvl(kbytes_free,0) used,
  7         nvl(kbytes_free,0) free,
  8         ((kbytes_alloc-nvl(kbytes_free,0))/
  9                            kbytes_alloc)*100 pct_used,
 10         nvl(largest,0) largest,
 11         nvl(kbytes_max,kbytes_alloc) Max_Size,
 12         decode( kbytes_max, 0, 0, (kbytes_alloc/kbytes_max)*100)
pct_max_used
 13  from ( select sum(bytes)/1024 Kbytes_free,
 14                max(bytes)/1024 largest,
 15                tablespace_name
 16         from  sys.dba_free_space
 17         group by tablespace_name ) a,
 18       ( select sum(bytes)/1024 Kbytes_alloc,
 19                     sum(maxbytes)/1024 Kbytes_max,
 20                tablespace_name
 21         from sys.dba_data_files
 22         group by tablespace_name ) b
 23  where a.tablespace_name (+) = b.tablespace_name
 24  order by 1
 25*       )
ops$t_mp00@MRP817> /

                                                             %
                                                MaxPoss    Max
Tablespace Name        KBytes         Used       Kbytes   Used
---------------- ------------ ------------ ------------ ------
CQDB_TBS               51,200       29,568      512,000   10.0
CQUEST_TBS             51,200       17,664      512,000   10.0
DRSYS                  90,112        4,232   33,554,416     .3
INDX                   59,392            8   33,554,416     .2
RBS                   532,480      118,792   33,554,416    1.6
SRE                   512,000      116,416    1,024,000   50.0
SYSTEM                270,336      228,944   33,554,416     .8
TOOLS                  12,288          168   33,554,416     .0
USERS                 110,592            8   33,554,416     .3
                 ------------ ------------
sum                 1,689,600      515,800

      Rating: 4
why "hate and
      fear" ?  November 05, 2003
Reviewer:  Ashiq
      Shamsudeen A  from Trichy, Tamil Nadu, India
Hi Tom,

Why we've to "hate and fear" if the data files are autoextend on.  Nowadays h/w
are very cheap ,and moreover the space what it's going occupy will be the
data(valuable data's).Other than data ,nothing is going to be there.
  If the db files are in "autoextend off" then at one point of time if the db
files get filled up then we've to add additinal db file ,there is no option left
out,isn't it?
   Then in what circumstances you'll be using db file with "autoextend off" ?

          Followup:

never said you had to -- said "you either love it or hate it"

if i was using RAW, that'd do it, i would use it there.

otherwise I'm using it.

      Rating: 5
Adding data
      file  November 05, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
> "if the db files get filled up then we've to add additinal db file"

If you are not using autoextend, you can simply resize the file manually to a
bigger size if needed.  You do not necessarily need to add an additional file
(though that also works).

- Mark

November 05, 2003 -- Thanks for the question regarding "INLIST
      ITERATOR", version 8.1.7

      You Asked
Hi Tom,

    Some questions about the SQL tuning.

1) I found that when using "IN" in the where clause , INLIST ITERATOR is shown
on the explain plan in a cost-based database (and using the index correctly, the
response is fast). However, no such INLIST ITERATOR in rule-based (and using the
full table scan, the response is slow). Is INLIST ITERATOR only occur on
cost-based? Is it possible to force the optimizer to use INLIST ITERATOR in a
rule-based database (without any hints added to the SQL statement or using alter
session set optimizer_mode = choose)? Or is it possible to rewrite the "IN" to
other forms such that the index can be used in rule-based database? I have tried
to rewrite "IN" to "OR" but the index still cannot be used. The only way the
index can be used is using UNION ALL the values of "IN".

2) If the database is rule-based (optimizer_mode=rule), and the table has
statistics, will Oracle use cost-based to answer the query? I rememeber that
Oracle will use rule-based if the optimizer_mode is set to rule (from Oracle
documentation), no matter whether the table has statistics. But I found that in
some situation Oracle will use cost-based.

Thanks,
David

      and we said...
1) the CBO is the only optimizer to be using, especially since in the very next
release of Oracle -- the RBO is gone, non-existent.

There are literrally hundreds of things the CBO can do (in-list iterator being
one) that the RBO has no idea about.  The Performance guide lists all that the
RBO knows of (very simple stuff).

You will not see a plan using in-list iterator without using the CBO.

but -- the RBO is index crazy -- it will use an index with an inlist.

  1  select /*+ RULE */ * from emp
  2* where empno in ( 1,2,3,4 )
scott@ORA920> /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   CONCATENATION
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
   3    2       INDEX (UNIQUE SCAN) OF 'EMP_PK' (UNIQUE)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
   5    4       INDEX (UNIQUE SCAN) OF 'EMP_PK' (UNIQUE)
   6    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
   7    6       INDEX (UNIQUE SCAN) OF 'EMP_PK' (UNIQUE)
   8    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
   9    8       INDEX (UNIQUE SCAN) OF 'EMP_PK' (UNIQUE)

  1  select /*+ RULE */ * from emp
  2* where empno in ( select user_id from all_users )
scott@ORA920> /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   NESTED LOOPS
   2    1     VIEW OF 'VW_NSO_1'
   3    2       SORT (UNIQUE)
   4    3         NESTED LOOPS
   5    4           NESTED LOOPS
   6    5             TABLE ACCESS (FULL) OF 'USER$'
   7    5             TABLE ACCESS (CLUSTER) OF 'TS$'
   8    7               INDEX (UNIQUE SCAN) OF 'I_TS#' (NON-UNIQUE)
   9    4           TABLE ACCESS (CLUSTER) OF 'TS$'
  10    9             INDEX (UNIQUE SCAN) OF 'I_TS#' (NON-UNIQUE)
  11    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
  12   11       INDEX (UNIQUE SCAN) OF 'EMP_PK' (UNIQUE)

2) The "some situation" is when the underlying object is not even visible to the
RBO -- such as

o IOT's index organized tables
o partitioned tables
o tables with non-default degrees of parallelism set
o tables with domain (text, spatial, etc) indexes

for example.  Those tables, regardless of the presence of statistics, can ONLY
be processed by the CBO.

Review &
      Followup

      Rating: 4
Full table scan
      of large table may be good?  April 11, 2003

Reviewer:  A reader
Hi Tom,

   My existing database is rule-based and this database cannot change to
cost-based in the near future. I found that when using inlist operator, the
performance is so bad. Only include the CHOOSE hint can solve the problem. I
want to ask, should I use CHOOSE hint if the where clause has the inlist
operator (assume the database is rule-based)?

   Another question:

Table : Records
buyer : 2500000
stock : 400000
unit  : 2500000
stock_code : 100
other_stock : 500
order : 400000

select count(*)
from buyer BY,stock ST,UNIT UT,stock_code SC,other_stock OS,order OD
where BY_ID = UT_ID AND
      ST_ID = UT_ID AND
      OD_ID = BY_ID AND
      OD_ID = ST_ID AND
      SC_code_1 = BY_code_1 AND
      SC_code_2 = BY_code_2 AND
      UT_CODE = OS_CODE(+) AND
      OD_id in (1,2,3);

Execution Plan (4 seconds)
----------------------------------------------------------

   0      SELECT STATEMENT Optimizer=RULE

   1    0   SORT (AGGREGATE)

   2    1     COUNT (STOPKEY)

   3    2       NESTED LOOPS

   4    3         NESTED LOOPS

   5    4           NESTED LOOPS (OUTER)

   6    5             NESTED LOOPS

   7    6               NESTED LOOPS

   8    7                 TABLE ACCESS (FULL) OF 'order'

   9    7                 TABLE ACCESS (BY INDEX ROWID) OF 'stock'

  10    9                   INDEX (RANGE SCAN) OF 'ST_INDEX_7' (NON-UNIQUE)

  11    6               TABLE ACCESS (BY INDEX ROWID) OF 'UNIT'

  12   11                 INDEX (RANGE SCAN) OF 'UT_INDEX_2' (NON-UNIQUE)

  13    5             INDEX (UNIQUE SCAN) OF 'OS_PRIMARY_KEY' (UNIQUE)

  14    4           TABLE ACCESS (BY INDEX ROWID) OF 'buyer'

  15   14             INDEX (UNIQUE SCAN) OF 'BY_PRIMARY_KEY' (UNIQUE)

  16    3         INDEX (UNIQUE SCAN) OF 'SC_PRIMARY_KEY' (UNIQUE)

But now I change the order of the FROM table list as follow. The reason I move
ORDER to the left is in rule-based, the right-most table in the FROM list is
resolve first. (I want OTHER_STOCK is resolve first since it contains only about
500 records)

select count(*)
from buyer BY,stock ST,order OD, UNIT UT,stock_code SC,other_stock OS
where BY_ID = UT_ID AND
      ST_ID = UT_ID AND
      OD_ID = BY_ID AND
      OD_ID = ST_ID AND
      SC_code_1 = BY_code_1 AND
      SC_code_2 = BY_code_2 AND
      UT_CODE = OS_CODE(+) AND
      OD_id in (1,2,3);

Execution Plan (over 10 mins)
----------------------------------------------------------

   0      SELECT STATEMENT Optimizer=RULE

   1    0   SORT (AGGREGATE)

   2    1     COUNT (STOPKEY)

   3    2       NESTED LOOPS

   4    3         NESTED LOOPS

   5    4           NESTED LOOPS (OUTER)

   6    5             NESTED LOOPS

   7    6               NESTED LOOPS

   8    7                 TABLE ACCESS (FULL) OF 'stock_code'

   9    7                 TABLE ACCESS (BY INDEX ROWID) OF 'buyer'

  10    9                   INDEX (RANGE SCAN) OF 'BY_INDEX_4' (NON-UNIQUE)

  11    6               TABLE ACCESS (BY INDEX ROWID) OF 'UNIT'

  12   11                 INDEX (UNIQUE SCAN) OF 'UT_PRIMARY_KEY' (UNIQUE)
  13    5             INDEX (UNIQUE SCAN) OF 'OS_PRIMARY_KEY' (UNIQUE)

  14    4           INDEX (UNIQUE SCAN) OF 'OD_PRIMARY_KEY' (UNIQUE)

  15    3         TABLE ACCESS (BY INDEX ROWID) OF 'stock'

  16   15           INDEX (UNIQUE SCAN) OF 'ST_PRIMARY_KEY' (UNIQUE)

The result is so bad. I don't know why in case 1, the full table scan of the
table ORDER (340000 records) is faster than the full scan of the table
STOCK_CODE (100 records only). Is the inlist operator (OD_ID in table ORDER)
makes the full table scan of ORDER faster?

Thanks
David

          Followup:

You are apparently using the CBO with a mixture of analyzed and unanalyzed
tables which is like playing with a loaded gun.  Someone will be very sorry in
the end.  In your database, it makes sense to use RBO only.

The RBO is very sensitive to the order of tables in the from clause (right to
left) and the order of predicates (bottom up).  If you want to use the RBO, you
need to write your queries to that optimizer specifically.

      Rating: 3
Execution
      Plan...  April 14, 2003
Reviewer:
      A reader
Hi Tom,

   I have a question regarding the SQL tuning in Oracle. I found that I cannot
predict the execution plan that Oracle is going to generate for the SQL
statement. My tuning "habit" is, for the poor SQL, look the execution plan, then
look whether the index has been used, what is the value of the cost value (for
cost-based) etc. But I cannot explain why Oracle generate the execution plan A
for SQL A, execution plan B for SQL B...... is this a normal practice for SQL
tuning? For example, in rule-based, the last table in the FROM is the driving
table. But I found that this is not always the true...

    Another question. What is the meaning of the cost value (and its unit) in
the execution plan? (I sometimes found that the cost is over 1000 for some SQLs)
Thanks,
David

          Followup:

It is the job of the optimizer to develop a query plan.

It is our job to read it.

To understand how it works, you need to read the performance guide, the way the
optimizer does what it does is documented in there.

Ignore the cost for now.  It is just a number.  It doesn't have direct relevance
to time, space, anything you or I care about.

      Rating: 3
choose is not a
      "hint"?  April 15, 2003
Reviewer:
      A reader
Hi Tom,

   You mentioned that choose is not a hint. Do you mean there is no /*+ choose
*/? But I found that it is valid to use /*+ choose */.

David

          Followup:

so there is, never noticed it before.

      Rating: 5
how does
      in-list work  May 07, 2003
Reviewer:  PINGU

Hi

Have a question about in-list operator, the following example if from Oracle 9.2
docu (Database Performance Tuning Guide and Reference)

SELECT header_id, line_id, revenue_amount
  FROM so_lines_all
 WHERE header_id IN (1011,1012,1013);

SELECT header_id, line_id, revenue_amount
  FROM so_lines_all
 WHERE header_id = 1011
    OR header_id = 1012
    OR header_id = 1013;
Plan
-------------------------------------------------
SELECT STATEMENT
  INLIST ITERATOR
    TABLE ACCESS BY INDEX ROWID SO_LINES_ALL
      INDEX RANGE SCAN SO_LINES_N1

If we use RBO here we would see

TABLE ACCESS BY INDEX ROWID SO_LINES_ALL
      INDEX RANGE SCAN SO_LINES_N1

3 times and a final concatenation, so it´s like running the query

SELECT header_id, line_id, revenue_amount
  FROM so_lines_all
 WHERE header_id = :b1

3 times, am I right?

Using IN-List Iterator how does Oracle perform? I mean it does not run the query
3 times as RBO but then how does it do it?

From the docu it says:

"extra step occurs when the IN-list iterator feeds the equality clause with
unique values from the IN-list"

If I have three values in the list, Oracle binds three times but does it run
three times as RBO...? This part the docu doesnt explain fully in my opinion...

Finally, in the docu it also says

"The IN-list operator is especially useful if there is an expensive first step
that you do not want to repeat for every IN-list element"

It then shows an example (example 2.5), in that example I dont understand which
part is the expensive first part

cheers

          Followup:

yes on the RBO discussion....

It (the cbo) is conceptually the same but it has greater abilities to merge and
optimize more complex queries -- optimizing the union all (concat stuff)
wouldn't be trivial.  considering it a single query and doing stuff that way --
is.

That last part is what I'm talking about -- instead of the query being
physically processed AS IF it were 3 queries (in your case) with whatever work
each of the three queries needs to do -- the CBO would do it once.

      Rating: 5
more
      examples...  May 07, 2003
Reviewer:
      A reader
Hi again

Thanks for your prompt reply. I have a further question after read a bit further
the documentation.

In example 2-11, 2-12, 2-13

It says 2-12 is the worst, I can understand that but which is better between
2-11 and 2-13? I suspect 2-11 is the best but in the documentation it seems that
it´s saying 2-13 is better, I dont get it... :-(

EXAMPLE 2-11
--------------------------------------------------------
SELECT h.customer_id, l.line_id, l.revenue_amount
  FROM so_lines_all l, so_headers_all h
 WHERE l.s7 = 20
   AND h.original_system_reference = l.attribute5
   AND h.original_system_source_code IN (1013,1014);

Plan
--------------------------------------------------
SELECT STATEMENT
  NESTED LOOPS
    TABLE ACCESS FULL SO_LINES_ALL
    TABLE ACCESS BY INDEX ROWID SO_HEADERS_ALL
      INDEX RANGE SCAN SO_HEADERS_N5

EXAMPLE 2-13
------------------------------------------------------
SELECT /*+INDEX(h so_headers_n9 */ h.customer_id, l.line_id, l.revenue_amount
  FROM so_lines_all l, so_headers_all h
 WHERE l.s7 = 20
   AND h.original_system_reference = l.attribute5
   AND h.original_system_source_code IN (1013,1014);

Plan
--------------------------------------------------
SELECT STATEMENT
  NESTED LOOPS
    TABLE ACCESS FULL SO_LINES_ALL
    INLIST ITERATOR
      TABLE ACCESS BY INDEX ROWID SO_HEADERS_ALL
        INDEX RANGE SCAN SO_HEADERS_N9

          Followup:

hows about a link into otn.oracle.com to the documentation you are referencing.

      Rating: 5
sorry here it
      is  May 07, 2003
Reviewer:  PINGU

sorry

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/opt_ops.htm#1004651

      Rating: 5
IN list as a
      subquery  May 07, 2003
Reviewer:  Arun Gupta
       from Harrisburg, PA USA
Tom
I know that I am not comparing similar elements, but please see the SQL
statements and their plans. In first statement, I see the INLIST ITERATOR but in
the second SQL I am simulating the list through a query and getting a cartesian.
The table in queries is a global temporary table for session.
Thanks

SQL> ed
Wrote file afiedt.buf

  1  DELETE FROM t_refl_search_work_sess
  2      WHERE ROWID IN ('AAQHaJAABAAAHaKAAB', 'AAQHaJAABAAAHaKAAC',
  3*             'AAQHaJAABAAAHaKAAF')
SQL> /

0 rows deleted.

Execution Plan
----------------------------------------------------------
   0      DELETE STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=7)
   1    0   DELETE OF 'T_REFL_SEARCH_WORK_SESS'
   2    1     INLIST ITERATOR
   3    2       TABLE ACCESS (BY USER ROWID) OF 'T_REFL_SEARCH_WORK_SE
          SS' (Cost=1 Card=1 Bytes=7)

SQL>
DELETE
FROM t_refl_search_work_sess
WHERE ROWID IN (
      SELECT e.ROWID
      FROM   t_refl_search_work_sess e
      WHERE  e.ROWID >
      (SELECT MIN (x.ROWID)
       FROM   t_refl_search_work_sess x
       WHERE
             x.idn_entity_legal_provr = e.idn_entity_legal_provr
         AND    x.idn_loc_provr = e.idn_loc_provr
         ND    x.txt_value_field_2 = e.txt_value_field_2) )

/

Execution Plan
----------------------------------------------------------
   0      DELETE STATEMENT Optimizer=CHOOSE (Cost=11 Card=1 Bytes=377)
   1    0   DELETE OF 'T_REFL_SEARCH_WORK_SESS'
   2    1     NESTED LOOPS (Cost=11 Card=1 Bytes=377)
   3    2       MERGE JOIN (CARTESIAN) (Cost=10 Card=1 Bytes=192)
   4    3         TABLE ACCESS (FULL) OF 'T_REFL_SEARCH_WORK_SESS' (Co
          st=2 Card=1 Bytes=7)

   5    3         BUFFER (SORT) (Cost=8 Card=1 Bytes=185)
   6    5           VIEW OF 'VW_SQ_1' (Cost=8 Card=1 Bytes=185)
   7    6             SORT (GROUP BY) (Cost=8 Card=1 Bytes=185)
   8    7               TABLE ACCESS (FULL) OF 'T_REFL_SEARCH_WORK_SES
          S' (Cost=2 Card=1 Bytes=185)

   9    2       TABLE ACCESS (BY USER ROWID) OF 'T_REFL_SEARCH_WORK_SE
          SS' (Cost=1)

      Rating: 5
in-list
      iterator in oracle 7  June 03, 2003
Reviewer:
      A reader
Hi

is in-list iterator possible in oracle 7?

          Followup:

I believe that access path was added in 8.0, so no.  I cannot get it to use one
but in 8.0 and up -- it uses it naturally.

      Rating: 5
why
      concatenation in this query  November 04, 2003

Reviewer:  A reader
Hi

We recently rebuilt our database, using hash partitioning and facing heavy
performance problems

SELECT *
  FROM INVENTARIOS_SERVICIO_HOTEL
 WHERE ISH_CODEMP = :b1
   AND ISH_CODEMPPROREC = NVL(:b2,'00000')
   AND ISH_CODREC = NVL(:b3,'000')
   AND ISH_TIPCON = :b4
   AND ISH_REFSER = :b5
   AND ISH_TIPSER = :b6
   AND ISH_FECINV >= TRUNC(to_date(:b7, 'MM/DD/YYYY hh24:mi:ss'))
   AND ISH_FECINV <= TRUNC(to_date(:b8, 'MM/DD/YYYY hh24:mi:ss'))
   AND INSTR(:b9,DIA_SEMANA(TRUNC(ISH_FECINV))) != 0
   AND ISH_CONPRE = :b10
   AND ISH_THAGEN = :b11
   AND ISH_CHAGEN = :b12
   AND ISH_RESCON = 0
   AND ISH_RESPEN = 0

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=2 Bytes=184)
   1    0   CONCATENATION
   2    1     FILTER
   3    2       TABLE ACCESS (BY GLOBAL INDEX ROWID) OF
'INVENTARIOS_SERVICIO_HOTEL' (Cost=3 Card=1 Bytes=92)
   4    3         INDEX (RANGE SCAN) OF 'ISH_PK' (UNIQUE) (Cost=6 Card=1)
   5    1     FILTER
   6    5       TABLE ACCESS (BY GLOBAL INDEX ROWID) OF
'INVENTARIOS_SERVICIO_HOTEL' (Cost=3 Card=1 Bytes=92)
   7    6         INDEX (RANGE SCAN) OF 'ISH_PK' (UNIQUE) (Cost=6 Card=1)

Extremely slow!

I dont understand why ther is concatenation, if I change

AND ISH_CODEMPPROREC = NVL(:b2,'00000') to
AND ISH_CODEMPPROREC = NVL('','00000') or
AND ISH_CODEMPPROREC = NVL('00000','00000')

Plan becomes

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=7 Card=1 Bytes=92)
   1    0   TABLE ACCESS (BY GLOBAL INDEX ROWID) OF 'INVENTARIOS_SERVICIO_HOTEL'
(Cost=7 Card=1 Bytes=9
   2    1     INDEX (RANGE SCAN) OF 'ISH_PK' (UNIQUE) (Cost=6 Card=1)

Extremely fast!

I dont understand, I even used 10053 event but cant see why

Please help

          Followup:

well, given i have NO CLUE what your table, indexes, statistics, have no tkprof
with the bad and the good, yadda yadda yadda look like, i guess we are in the
same boat?

      Rating: 3
I have these
      statistics  November 04, 2003
Reviewer:
      A reader
Hi

Version 8.1.7.4 on Sun Solaris.

The table has 3500000 rows, hash partitioned in 8 partitions, the primary key is
globally partitioned (was first locally partitioned but was even slower).

ISH_CODEMPPROREC is part of primary key (not the leading column).

I have following statistics, the funny thing is both query reads same amount of
blocks but the one with bind variable is much much slower. With bind variable
takes 9 minutes, using literals for ISH_CODEMPPROREC takes 30 seconds, dont
understand why. The query runs fast when I get rid of NVL() function or use
literals for ISH_CODEMPPROREC....

var b1 varchar2(5)
var b2 varchar2(5)
var b3 varchar2(3)
var b4 varchar2(1)
var b5 number
var b6 varchar2(10)
var b7 varchar2(30)
var b8 varchar2(30)
var b9 number
var b10 varchar2(10)
var b11 varchar2(4)
var b12 varchar2(14)

exec :b1 := 'VVT'
exec :b2 := '00000'
exec :b3 := '000'
exec :b4 := 'V'
exec :b5 := 1565
exec :b6 := 'HOTEL'
exec :b7 := '1/3/2003 0:0:0'
exec :b8 := '1/31/2003 0:0:0'
exec :b9 := 1234567
exec :b10 := 'GENERAL'
exec :b11 := 'A1/3'
exec :b12 := 'ES'

SELECT *
  FROM INVENTARIOS_SERVICIO_HOTEL
 WHERE ISH_CODEMP = :b1
   AND ISH_CODEMPPROREC = NVL(:b2,'00000')
   AND ISH_CODREC = NVL(:b3,'000')
   AND ISH_TIPCON = :b4
   AND ISH_REFSER = :b5
   AND ISH_TIPSER = :b6
   AND ISH_FECINV >= TRUNC(to_date(:b7, 'MM/DD/YYYY hh24:mi:ss'))
   AND ISH_FECINV <= TRUNC(to_date(:b8, 'MM/DD/YYYY hh24:mi:ss'))
   AND INSTR(:b9,DIA_SEMANA(TRUNC(ISH_FECINV))) != 0
   AND ISH_CONPRE = :b10
   AND ISH_THAGEN = :b11
   AND ISH_CHAGEN = :b12
   AND ISH_RESCON = 0
   AND ISH_RESPEN = 0

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=2 Bytes=184)
   1    0   CONCATENATION
   2    1     FILTER
   3    2       TABLE ACCESS (BY GLOBAL INDEX ROWID) OF
'INVENTARIOS_SERVICIO_HOTEL' (Cost=3 Card=1 Bytes=92)
   4    3         INDEX (RANGE SCAN) OF 'ISH_PK' (UNIQUE) (Cost=6 Card=1)
   5    1     FILTER
   6    5       TABLE ACCESS (BY GLOBAL INDEX ROWID) OF
'INVENTARIOS_SERVICIO_HOTEL' (Cost=3 Card=1 Bytes=92)
   7    6         INDEX (RANGE SCAN) OF 'ISH_PK' (UNIQUE) (Cost=6 Card=1)

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

SELECT *
  FROM INVENTARIOS_SERVICIO_HOTEL
 WHERE ISH_CODEMP = :b1
   AND ISH_CODEMPPROREC = NVL('00000','00000')
   AND ISH_CODREC = NVL(:b3,'000')
   AND ISH_TIPCON = :b4
   AND ISH_REFSER = :b5
   AND ISH_TIPSER = :b6
   AND ISH_FECINV >= TRUNC(to_date(:b7, 'MM/DD/YYYY hh24:mi:ss'))
   AND ISH_FECINV <= TRUNC(to_date(:b8, 'MM/DD/YYYY hh24:mi:ss'))
   AND INSTR(:b9,DIA_SEMANA(TRUNC(ISH_FECINV))) != 0
   AND ISH_CONPRE = :b10
   AND ISH_THAGEN = :b11
   AND ISH_CHAGEN = :b12
   AND ISH_RESCON = 0
   AND ISH_RESPEN = 0

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=7 Card=1 Bytes=92)
   1    0   TABLE ACCESS (BY GLOBAL INDEX ROWID) OF 'INVENTARIOS_SERVICIO_HOTEL'
(Cost=7 Card=1 Bytes=9
   2    1     INDEX (RANGE SCAN) OF 'ISH_PK' (UNIQUE) (Cost=6 Card=1)

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

          Followup:

sorry -- something else is afoot here, not that i don't believe you -- but --
well, i'm not believing something.

TKPROF please

      Rating: 5
I only have the
      bad query tkprof  November 05, 2003
Reviewer:
      A reader
Hi

I only have the awful query tkprof... hope it can give you more information. The
other one I dont have, but it was lightning fast. As you can see it´s in fact a
DELETE but they work the same way, without bind variable or the NVL() very fast,
with one of these two deadly slow !

DELETE
  FROM INVENTARIOS_SERVICIO_HOTEL
 WHERE ISH_CODEMP = :b1
   AND ISH_CODEMPPROREC = NVL(:b2,'00000')
   AND ISH_CODREC = NVL(:b3,'000')
   AND ISH_TIPCON = :b4
   AND ISH_REFSER = :b5
   AND ISH_TIPSER = :b6
   AND ISH_FECINV >= TRUNC(:b7)
   AND ISH_FECINV <= TRUNC(:b8)
   AND INSTR(:b9,DIA_SEMANA(TRUNC(ISH_FECINV))) != 0
   AND ISH_CONPRE = :b10
   AND ISH_THAGEN = :b11
   AND ISH_CHAGEN = :b12
   AND ISH_RESCON = 0
   AND ISH_RESPEN = 0

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      5    452.73     478.34          0      17838          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        6    452.73     478.34          0      17838          0           0

          Followup:

you are the same guy as above right?

can you give me what i asked for?  a tkprof that compares the two. (query plans
AND ALL)

all of a sudden, a SELECT is now a delete?

lets keep it consistent please.  one problem at a time.

November 05, 2003 Kartik -- Thanks for the question regarding
      "Spawn Jobs from a Procedure that run in parallel", version 8.1.7

      You Asked
I have a table that contains 2 million transaction records. A procedure reads
these transaction records and creates an output record in the RESULT table.
Usually for each record in the TRANSACTION table there is a record in the RESULT
table. Even though all the SQL queries and the code has been optimized, to the
best of our ability, the procedure take a very long time to run.

I thought of splitting the transaction table into four tables (T1,T2,T3,T4),
each containing 500,000 records. There would be a main procedure (PROC_MAIN)that
would spawn four instances of the procedure (P1, P2, P3 and P4) using DBMS_JOB
package. This would create four RESULT tables (R1,R2,R3,R4).

1. In the main procedure (PROC_MAIN) how do I check if all the four jobs
(P1,P2,P3,P4) have completed?
2. The Main procedure (PROC_MAIN) has to wait until all the four instances are
complete because I need to consolidate the RESULT tables (R1,R2,R3,R4) into a
single table and generate some reports. All this has to be automated without any
manual intervention.  Any suggestions?

      and we said...
I use a technique like the following:

create or replace procedure simulation( p_procedure in varchar2, p_jobs in
number, p_iters in number )
authid current_user
as
    l_job number;
    l_cnt number;
begin
    for i in 1 .. p_jobs
    loop
        begin
            execute immediate 'drop table t' || i;
        exception
            when others then null;
        end;
        execute immediate 'create table t' || i || ' ( x int )';
    end loop;

    for i in 1 .. p_jobs
    loop
        dbms_job.submit( l_job, p_procedure || '(JOB);' );
        insert into job_parameters
        ( jobid, iterations, table_idx )
        values ( l_job, p_iters, i );
    end loop;

    statspack.snap;
    commit;
    loop
        dbms_lock.sleep(30);
        select count(*) into l_cnt from job_parameters;
        exit when (l_cnt = 0);
    end loop;
    statspack.snap;
end;
/

I have a parameter table which I populate with inputs to the "back ground"
processes.

After I commit, the jobs start running -- I just "poll" the job parameter table
till they are done...

Now, you don't need to split the table into 4 -- you can just use ROWID ranges.
If you use this script I call "split"

-------------------------------------------------

set verify off
define TNAME=&1
define CHUNKS=&2

select grp,
       dbms_rowid.rowid_create( 1, data_object_id, lo_fno, lo_block, 0 )
min_rid,
       dbms_rowid.rowid_create( 1, data_object_id, hi_fno, hi_block, 10000 )
max_rid
  from (
select distinct grp,
       first_value(relative_fno)
        over (partition by grp order by relative_fno, block_id
              rows between unbounded preceding and unbounded following) lo_fno,
       first_value(block_id    )
       over (partition by grp order by relative_fno, block_id
            rows between unbounded preceding and unbounded following) lo_block,
       last_value(relative_fno)
        over (partition by grp order by relative_fno, block_id
        rows between unbounded preceding and unbounded following) hi_fno,
       last_value(block_id+blocks-1)
        over (partition by grp order by relative_fno, block_id
         rows between unbounded preceding and unbounded following) hi_block,
       sum(blocks) over (partition by grp) sum_blocks
  from (
select relative_fno,
       block_id,
       blocks,
       trunc( (sum(blocks) over (order by relative_fno, block_id)-0.01) /
              (sum(blocks) over ()/&CHUNKS) ) grp
  from dba_extents
 where segment_name = upper('&TNAME')
   and owner = user order by block_id
       )
       ),
       (select data_object_id from user_objects where object_name =
upper('&TNAME') )
/

-------------------------------- eof --------------------

and run it

SQL> @split T 4

it'll produce something like:

big_table@ORA920LAP> @split big_table 4

       GRP MIN_RID            MAX_RID
---------- ------------------ ------------------
         0 AAAHchAAJAAAAAJAAA AAAHchAAJAAAA4ICcQ
         1 AAAHchAAJAAAA4JAAA AAAHchAAJAAABwICcQ
         2 AAAHchAAJAAABwJAAA AAAHchAAJAAACgICcQ
         3 AAAHchAAJAAACgJAAA AAAHchAAJAAADgICcQ

those are 4 non-overlapping rowid ranges that complete "cover" the table -- so,
you can query:

  for x in ( select /*+ FIRST_ROWS */ * from t where rowid between X and Y )
  loop

and just pass in a rowid pair for X and Y.  that way, you can use the above
query to generate 4 rowid ranges, insert them into the job parameter table and
run 4 copies of your procedure.  they'll each process about 1/4th of the table.
Review &
      Followup

      Rating:
      4
  June 18, 2003
Reviewer:  Kartik
      Dhekne  from New Jersey
Thank you very much for your solution.
One quick question. I am assuming that the procedures that are spawned would
delete the record in JOB_PARAMETERS table upon completion. Otherwise there will
be no exit from the last loop.

          Followup:

correctg

      Rating:
      5
Reader  June 22, 2003
Reviewer:
      A reader
"
dbms_job.submit( l_job, p_procedure || '(JOB);' );
"

Could you explain how the PL/SQL parser works when you
concatenate the iterative procedure "p_procedure" with
the actual job "'(JOB);'" and pass it as the second
parameter to dbms_job.submit

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:9088073430469#9099572884547

      Rating:
      5
Hi  September 03, 2003
Reviewer:
      A reader
I am sorry I have posted this review on wrong question earlier. I am posting it
again in the correct place

In the order by clause in the analytic expressions , am I correct in saying that

for first_value(relative_fno) block_id is not necessary and for
first_value(block_id) relative_fno need not be there.

i.e. the query would look like

set verify off
define TNAME=&1
define CHUNKS=&2

select a.grp,
       dbms_rowid.rowid_create( 1, b.data_object_id, a.lo_fno, a.lo_block, 0 )

  min_rid,
       dbms_rowid.rowid_create( 1, b.data_object_id, a.hi_fno, a.hi_block, 10000

) max_rid
  from (
          select distinct grp,
                 first_value(relative_fno) over
                                              (partition by grp order by
relative_fno
                                                 rows between unbounded
preceding and unbounded following) lo_fno,
                 first_value(block_id) over
                                              (partition by grp order by
block_id
                                                 rows between unbounded
preceding and unbounded following) lo_block,
                 last_value(relative_fno) over
                                              (partition by grp order by
relative_fno
                                                 rows between unbounded
preceding and unbounded following) hi_fno,
                 last_value(block_id+blocks-1) over
                                              (partition by grp order by
block_id
                                                 rows between unbounded
preceding and unbounded following) hi_block,
                 sum(blocks) over (partition by grp) sum_blocks
          from (
                  select relative_fno,
                         block_id,
                         blocks,
                         trunc((sum(blocks) over (order by relative_fno,
block_id)-0.01) / (sum(blocks) over ()/&CHUNKS)) grp
                  from dba_extents
                  where segment_name = upper('&TNAME')
                    and owner = user
                  order by block_id
               )
       )a,
       (
          select data_object_id
           from user_objects
            where object_name = upper('&TNAME')
       )b
/

thanks
amit

          Followup:

i don't get your point -- care to point out what differences you made and then
you can prove it works or not via testing.

I might have extra columns in some of the inline views -- when I built this
query -- i did it from the inside out.  so I could see what it was doing at each
level.  I sometimes add a column or two in there in order to "debug" and maybe i
left one in there.  is that what you mean?

      Rating:
      5
  October 27, 2003
Reviewer:  Robert
       from Memphis, USA

      Rating: 3
Can I Split a
      Global Temporary Table?  November 04, 2003

Reviewer:  Upendra  from NJ
Hi Tom,

   This is a great site - very informative. I also read your book - Expert
One-On-One. I've already recommended it to my team-mates. You are doing a great
job, keep it up!

I have a couple of questions:

1) How do I make the Split work on a Global Temporary Table? I tried the
following: -

10:41:53 SQL> create global temporary table t
10:42:03   2  on commit preserve rows
10:42:08   3  as
10:42:11   4  select * from testocc1;

Table created.

Elapsed: 00:00:02.62
10:42:19 SQL> commit;

Commit complete.

Elapsed: 00:00:00.00
10:42:23 SQL> select count(*) from testocc1;

  COUNT(*)
----------
    675158

Elapsed: 00:00:00.24
10:42:28 SQL> @split_table t 10

no rows selected

Elapsed: 00:00:00.42

2) We are currently building a data warehouse. We get fixed width files (master
and transaction ) from several source systems. The data warehouse source schema
contains external tables, one per file and views which reside on top of the
external tables which contains many row level transformations - for example CASE
statements, NVL handling, decimal conversion etc. We used Direct Path Insert to
populate fact tables and we use single 'Insert into .. Select from ..' statement
approach. What are your thoughts on this approach? Some of the questions that
come to my mind are :-
1. What happens if query execution plan changes in the production making the SQL
statement itself go slower? What can I do to minimize this risk?
2. We have seen that when you load a fact table using the
external_table->view->joined with dimension table approach is slower at times -
this is in-consistent. However if I dump the data from the view into a Global
Temporary Table, it goes faster - well many times. Would it make sense to split
the Global Temporary Table as you've shown in this thread and then submit them
as load jobs. What are your thoughts?

Thanks,

Upendra

          Followup:

1) you cannot -- it would not make sense.

you cannot because -- it has no real "extents"

it doesn't make sense because the only use of split would be to have many
sessions work on different portions but the only session that can see your GTT
is -- YOUR OWN!!

2) i love that approach

2.1) what if it changes to go faster?  you can freeze the plan, but in general,
in absence of a bug, let it do what it does.

2.2) have you looked at using optimizer dynamic sampling. what are the different
plans generated?

      Rating: 4
Load from
      External Table->View->into Normal Table and
      Split?  November 05, 2003
Reviewer:
      A reader
Thank you for quick response. Next time, I'll take a closer look at the
difference in plans when I switch to a temporary table from a View. Not sure if
they looked the same or not.

Some more questions:-
-------------------------------------

1. Some of our transaction files are really large, close to 80 or more million
rows in 1 day alone. We plan to run daily loads. In this scenario, would it make
sense to load the data from the view into real table, then split that table
into, say 10 pieces and then submit them as jobs. So they can all run  in
parallel. Even if some of these jobs were to fail, we will be able to restart
the batch job for only the ones that failed rather that having to run one big
job. What are your thoughts on this?

2. What is the difference between splitting and loading versus 'alter session
enable parallel DML'. Are we introducing another layer of parallelism when we
split?

3. We have a surrogate key (also defined a Primary Key) on our fact
(partitioned) table. If I disable the Primary Key before loading, it would drop
the UNIQUE INDEX. How to avoid INDEX maintenance during this load? I also want
to avoid re-creating this INDEX because there may be history data sitting in
this table.

Thanks,

Upendra

          Followup:

1) could be -- depends on whether the time is spent processing the record (eg:
that is the bottleneck and needs to be parallelized) or whether loading the
stage table with 80m rows would be.

sounds "reasonable" that loading a stage and then processing it using "split" in
parallel would be more efficient (although if you can request N input files,
that would work as well -- just have N external tables)

2) my "split" by rowids -- lets you parallelize your PROCEDURAL CODE.  parallel
dml takes an insert/update/delete and executes it in parallel -- pdml will not
help a procedural process get parallelized

3) if you disable the primary key during the load -- you need to recreate it.

if you don't maintain it during the load -- you need to recreate it.

so, unless you maintain it during the load, you are recreating it.  that is it
-- no other choices really.

November 05, 2003 allen -- Thanks for the question regarding
      "Oracle Database recursive table relationships", version 8i

      You Asked
Sorry if this may seem a silly question but I havn't been able
to get a clear definition of a recursive relationship and how to
apply it in a Query.

      and we said...
A recursive relationship is one where a table has a foreign key
to itself.  for example, the scott/tiger table EMP has an EMPNO
as the primary key and the MGR as a foreign key to itself.  That
relationship is recursive -- a person is both an EMPLOYEE and a
MANAGER potentially.

To query that we use the connect by statement, for example:

  1  select lpad( '*', level, '*' ) || ename ename
  2  from emp
  3  start with mgr is NULL
  4* connect by prior empno = mgr
scott@DEV816> column ename format a25
scott@DEV816> /

ENAME
-------------------------
*KING
**JONES
***SCOTT
****ADAMS
***FORD
****SMITH
**BLAKE
***ALLEN
***WARD
***MARTIN
***TURNER
***JAMES
**CLARK
***MILLER

14 rows selected.

That starts with MGR is null which is KING.  He has no manager
and is at the top of our hierarchy.  We then connect this
starting record to every other record such that the other
records MGR column equals this records EMPNO column.  We do that
for every record we find until we hit the end of the recursion.

The level psuedo column tells us how deep into the tree we are.
I'm using it here to indent the data so you can see the
hierarchy.
Review & Followup

      Rating:
      4
  May 16, 2001
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating: 5
Recursive
      relationship  January 04, 2002
Reviewer:
      sanjai  from MI, USA
Tom,

I was wondering what is the best way to resolve many to many recursive
relationship.

For example if a employee reports to one or more manager and a manager is being
reported by one or more employee.

What is the best way to design the schema for this.

Thanks a lot.

      Rating: 3
More on
      recursive relationship  March 25, 2002
Reviewer:
      Sue  from Cambridge, MA USA
Someone here stumbled across a foreign key in his schema where the parent table
and column was the same as the child table and column.  What does this mean?  I
am assuming this was a mistake, but also wondering why Oracle allows this?

ALTER TABLE SCHEMA.TABLE_1
    ADD CONSTRAINT FK1_TABLE_1
    FOREIGN KEY (COLUMN_A)
    REFERENCES SCHEMA.TABLE_1 (COLUMN_A)
/

Thanks,
Sue

          Followup:

It looks like a "fast=false" option to me.  It'll always allow a row to be
inserted, it'll never reject a row, it'll only make things go slower.

I suppose we allow it cause the language doesn't explicity forbid it.  Not sure,
but it isn't doing anything.

      Rating: 5
what is your
      view  November 21, 2002
Reviewer:
      A reader
Hi tom,

  In the recursive relationship in the emp table,

SQL> desc emp
 Name                   Null?    Type
 -------------------- -------- ---------------------------
 EMPNO                   NOT NULL NUMBER(4)
 ENAME                            VARCHAR2(10)
 JOB                              VARCHAR2(9)
 MGR                              NUMBER(4)
 HIREDATE                         DATE
 SAL                              NUMBER(7,2)
 COMM                             NUMBER(7,2)
 DEPTNO                           NUMBER(2)

SQL> select empno,mgr,ename from emp;

     EMPNO        MGR ENAME
---------- ---------- ----------
      7369       7902 SMITH
      7499       7698 ALLEN
      7521       7698 WARD
      7566       7839 JONES
      7654       7698 MARTIN
      7698       7839 BLAKE
      7782       7839 CLARK
      7788       7566 SCOTT
      7839            KING       <<--------
      7844       7698 TURNER
      7876       7788 ADAMS
      7900       7698 JAMES
      7902       7566 FORD
      7934       7782 MILLER
        10            kobi

15 rows selected.

SQL> set pages 40
SQL> update emp set mgr = 7839
  2   where empno = 7839;

1 row updated.

SQL> commit;

Commit complete.

SQL>  select empno,mgr,ename from emp;

     EMPNO        MGR ENAME
---------- ---------- ----------
      7369       7902 SMITH
      7499       7698 ALLEN
      7521       7698 WARD
      7566       7839 JONES
      7654       7698 MARTIN
      7698       7839 BLAKE
      7782       7839 CLARK
      7788       7566 SCOTT
      7839       7839 KING
      7844       7698 TURNER
      7876       7788 ADAMS
      7900       7698 JAMES
      7902       7566 FORD
      7934       7782 MILLER
        10            kobi

15 rows selected.

SQL> update emp set mgr = null
  2   where empno = 7839;

1 row updated.

SQL> commit;

Commit complete.

SQL> update emp set mgr = 10
  2   where empno = 7839;

1 row updated.

SQL> rollback
  2  ;

Rollback complete.

SQL>
line 3 truncated.
  1  INSERT INTO EMP (EMPNO, ENAME, JOB,MGR, HIREDATE,
     SAL,COMM, DEPTNO)
  2* VALUES (20,'JKIDD','Player',20,sysdate,1000,1000,40)
  3  /

1 row created.

SQL>  select empno,mgr,ename from emp;

     EMPNO        MGR ENAME
---------- ---------- ----------
      7369       7902 SMITH
      7499       7698 ALLEN
      7521       7698 WARD
      7566       7839 JONES
      7654       7698 MARTIN
      7698       7839 BLAKE
      7782       7839 CLARK
      7788       7566 SCOTT
      7839            KING
      7844       7698 TURNER
      7876       7788 ADAMS
      7900       7698 JAMES
      7902       7566 FORD
      7934       7782 MILLER
        10            vivek
        20         20 JKIDD

16 rows selected.

  here

  1.) how come we allow 20 empno with 20 mgr
        as empno (20) does not exits in the table
   2.) also after insert we can update it to the same
       parent

-- also what will  be the best way to catch all the
   integrity violation execptions  ( not using OTHERS)
     so that for all integrity violation error we can send
  a genaric error message to the application ?

Thanks,

          Followup:

1) you don't have a foreign key apparently.  you don't have RI enabled on this
table, there is no foreign key from mgr to empno

2) of course, there is no rule saying that you cannot.

scott@ORA920.US.ORACLE.COM> insert into emp (empno,mgr) values ( 1, 2 );

1 row created.

scott@ORA920.US.ORACLE.COM> rollback;

Rollback complete.

no integrity constraint...

scott@ORA920.US.ORACLE.COM> alter table emp add constraint emp_fk_emp foreign
key(mgr) references emp(empno);

Table altered.

scott@ORA920.US.ORACLE.COM> insert into emp (empno,mgr) values ( 1, 2 );
insert into emp (empno,mgr) values ( 1, 2 )
*
ERROR at line 1:
ORA-02291: integrity constraint (SCOTT.EMP_FK_EMP) violated - parent key not
found

with integrity constraint...

scott@ORA920.US.ORACLE.COM> rollback;

Rollback complete.

scott@ORA920.US.ORACLE.COM>
scott@ORA920.US.ORACLE.COM>
scott@ORA920.US.ORACLE.COM> declare
  2     integrity_constraint exception;
  3     pragma exception_init( integrity_constraint, -02291 );
  4  begin
  5      insert into emp (empno,mgr) values ( 1, 2 );
  6  exception
  7      when INTEGRITY_CONSTRAINT
  8      then
  9          raise_application_error( -20001, 'Generic error message number 512'
);
 10  end;
 11  /
declare
*
ERROR at line 1:
ORA-20001: Generic error message number 512
ORA-06512: at line 9

and that is how to map and oracle error code to a user defined exception that
you can catch by name

      Rating:
      5
Thanks  November 21, 2002
Reviewer:
      A reader
Thanks tom .

      Rating: 5
Unindexed FK
      for this case  November 04, 2003
Reviewer:
      MIke
Is there issue for without indexing for the FK on Mgr?

          Followup:

only if you

a) update DEPT's primary key (bad bad hugely bad idea)
b) delete from DEPT

if you do either of those, yes, you want to index deptno in EMP

for purposes of this example, no, there would be no difference.

      Rating:
      5
  November 05, 2003
Reviewer:
      A reader
"
only if you

a) update DEPT's primary key (bad bad hugely bad idea)
b) delete from DEPT

if you do either of those, yes, you want to index deptno in EMP

for purposes of this example, no, there would be no difference.
"
what about insert?

          Followup:

insert, fine.

November 05, 2003 Brian -- Thanks for the question regarding
      "Partitioning question", version 8.1.7

      You Asked
Let's say we have a table of documents with key of doc_id.  This table contains
millions of documents(rows).  There is also a

table that assigns one or more industry codes to each doc:

create table doc_ind
( doc_id    integer,
  ind_code      varchar2(10)
)

This table will therefore also contain millions of rows.

The industry code is a part of the default set of industry codes used by our
business.

Now let's say that we sign deals with clients that want to use their own
industry classifications.  I don't want to create new tables for each client
(there could eventually be hundreds of clients and therefore hundreds of new
tables) and have some scheme where logic is built into the application to derive
the correct table_name to query based on the client.

My first thought is to create a table like:

create table doc_client_ind
( client_id     varchar2(10),
  doc_id    integer,
  ind_code      varchar2(10)
)

The client id could be any value - usually resembling the client name.

Because each new client would add millions of rows to this table, I want to use
partitioning.  After reading up on partitioning in your book, my situation does
not seem to fit perfectly into standard scenarios.  At first, it seemed I would
need to use hash partitioning.  However, the number of distinct client_id values
in this table will be very small to start with and grow over time.  It is not
clear how I would easily manage hash partitioning well over time.

In order to use range partitioning, it seems to me that client_id's would need
to be assigned with values that have a natural ascending sort value (i.e.,
c0001, c0002, etc.).  Then I could have one partition per client and create them
on an ongoing as

needed basis.

I have three questions:

1. Are there other options (with or without partitioning) I don't see to manage
this?
2. Is there a way to manage this effectively using the original essentially
random client id values?
3. What do you suggest given the limited info above?
4. Given your suggestion in question 3, what are the disadvantages, caveats I
need to be aware of?

Thanks - Brian

      and we said...
Well, in 9i, there is LIST partitioning as well, that maybe something to
consider.  Here each client_id could be targeted to a specific partition.

If you just want to achieve a nice distribution of data across many
disks/partitions, you could hash right now on DOC_ID.  There will be millions of
unique values for that from the get go -- that will hash very nicely.  Since
you'll always join on doc_id and client_id, partition elimination will take
place just as effectively.

So, either look at 9i with list partitioning if you feel you would gain some
advantage to partition by client_id (one would be that if you drop a client,
gettng rid of their data is trivial -- drop the partition).

Or, hash partition on the doc_id to achieve a good overall spread of data.

Review &
      Followup

      Rating:
      4
Thanks  October 12, 2001
Reviewer:
      Brian  from Rockville, MD
Actually the table will mainly be used to query for docs that have a certain
industry tagged to them:

select doc_id
from doc_client_ind
where client_id = 'ABC'
and ind_code = 'IND01'.

All queries against this table will only be meaningful if
where client_id =
is part of the statement.

Given this use, I don't think hash partitioning on doc_id gives me what I am
looking for.  That is why I want to partition on client_id to handle that level
of elimination and then index on ind_code will be used within that partition.

Thanks for the suggestion of LIST partitioning with 9i.  Until we implement 9i,
is my only option to assign client ids as suggested in my original question
(i.e., c0001, cooo2, etc.)?

          Followup:

How many IND_CODE values are there (what would select count(distinct ind_code)
from t return?)

      Rating:
      4
Thanks  October 12, 2001
Reviewer:
      Brian  from Rockville, MD
Anywhere from 50 - 100

          Followup:

Then yes, in the meantime, you would be best served using a range partition with
specific ranges based on customer id.

One idea to play with might also be an IOT (index organized table) whose primary
key is CLIENT_ID, IND_CODE, DOC_ID using index key compression.  In this
fashion, the CLIENT_ID, IND_CODE would almost be factored out of the table all
together and we would just have doc_ids in the IOT.

      Rating: 5
Instructional
      as always  October 12, 2001
Reviewer:  Brian
       from Rockville, MD
Thanks Tom

      Rating: 4
More
      light  June 12, 2003
Reviewer:
      A reader
Hi tom,

I did not understand the link between getting the count of distinct ind_code and
the partition based on specific ranges of customer id as explained by you.

"Then yes, in the meantime, you would be best served using a range partition
with
specific ranges based on customer id. "

Also, can you also shed some light on key compression and when you say  "
One idea to play with might also be an IOT (index organized table) whose primary

key is CLIENT_ID, IND_CODE, DOC_ID using index key compression."  and especially
"In this
fashion, the CLIENT_ID, IND_CODE would almost be factored out of the table all
together and we would just have doc_ids in the IOT."

          Followup:

I wanted to know if client id was a small domain, something manageable.  If not,
they would not be able to range partition by it as it would be an ongoing job
just to split and partition the table.

50 -- they could do it.
500,000 -- they would have a hard time at it (but hash partitioning would start
to come into play).

values in between with ranges/hashes -- not practical at some point.

Index key compression is a feature whereby repeating fields on the leading edge
of the index are removed from the leaf block row entries.  Compresses the data.

If you create an index on (owner,object_type,object_type) on a copy of
all_objects - with compress 2, we would store the owner, object type ONCE per
leaf block, rather then once per ROW per leaf block -- leading to significant
savings in space.

      Rating: 5
Thanks
      Tom!!!  June 13, 2003
Reviewer:
      A reader

      Rating: 4
IOT with Hash
      Partitioning  June 20, 2003
Reviewer:
      A reader
Hi tom,

is Index Organized table possible with Hash partitioning? If not, why not?

Thanks
Venkat.

          Followup:

ops$tkyte@ORA920> create table t
  2  (
  3    object_id        number primary key,
  4    object_type      varchar2(18)
  5  )
  6  organization index
  7  partition by hash(object_id) partitions 8
  8  /

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t select object_id, object_type from all_objects
  2  /

31595 rows created.

      Rating: 4
How about in
      8i....  June 23, 2003
Reviewer:
      A reader
Hi tom,

I am sorry i missed out the version. Is it possible to do it in 8i.

Thanks
Venkat.

          Followup:

ops$tkyte@ORA817DEV> @test
partition by hash(object_id) partitions 8
                                        *
ERROR at line 7:
ORA-25198: only range partitioning is supported for index-organized table

was not a feature in that release

      Rating: 5
null
      values...  June 27, 2003
Reviewer:  Alvin
       from Philippines
Greetings,

couple of questions for you,

1.What if the column that i'm partitioning contains null entries ? is this
allowed ? how will oracle know where to place the data ?

2. in Page 620 of the expert one on one book an error 01502 was raised because
the local_nonprefixed index was unusable why ? are "queries" on Partitioned
Tables (PT) index dependent ?

3. say i have a table
create table detail
( cust_id      number
, cust_balance number
, some other columns.....
)

and cust_id has 65 distinct values over 7 million records. Will partitioning
hash ( cust_id ) be of any use assuming that the Front-End apps issues out a
dynamic query that will not use any indexed columns on any of its predicate ?

one advantage that i see in using PT's is data will be forced group by
partition.

          Followup:

1) yes allowed, it'll put them where it "wants".

ops$tkyte@ORA920> create table t
  2  (
  3    object_id        number ,
  4    object_type      varchar2(18)
  5  )
  6  partition by hash(object_id) partitions 8
  7  /

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t select object_id, object_type from all_objects;

31589 rows created.

ops$tkyte@ORA920> insert into t select NULL, 'xxx' from all_users;

47 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      l_cnt number;
  3  begin
  4      for x in ( select partition_name
  5                   from user_tab_partitions
  6                  where table_name = 'T' )
  7      loop
  8          execute immediate
  9          'select count(*)
 10             from t partition(' || x.partition_name || ')'
 11             into l_cnt;
 12          dbms_output.put_line
 13          ( x.partition_name || ' ' || l_cnt || ' rows...' );
 14
 15          execute immediate
 16          'select count(*)
 17             from t partition(' || x.partition_name || ')
 18            where object_id is null'
 19            into l_cnt;
 20          dbms_output.put_line
 21          ( x.partition_name || ' ' || l_cnt || ' NULL rows...' );
 22      end loop;
 23  end;
 24  /
SYS_P347 3965 rows...
SYS_P347 47 NULL rows...
SYS_P348 3948 rows...
SYS_P348 0 NULL rows...
SYS_P349 4042 rows...
SYS_P349 0 NULL rows...
SYS_P350 3930 rows...
SYS_P350 0 NULL rows...
SYS_P351 3868 rows...
SYS_P351 0 NULL rows...
SYS_P352 3895 rows...
SYS_P352 0 NULL rows...
SYS_P353 4072 rows...
SYS_P353 0 NULL rows...
SYS_P354 3916 rows...
SYS_P354 0 NULL rows...

PL/SQL procedure successfully completed.

for example -- they all went into the "first" partition in that case.

2) page 640 maybe?  it was raised because

   a) the index was unusable
   b) the query plan said "i need to read that index"

queries against partitioned tables are no more or less "index dependent" then
queries against unpartitioned tables.  same thing would happen against a
"normal" table if its indexes were unusable and query plan said "i want to use
that index"

3) 65 is too small in all likelyhood, unless you are really lucky.  It would be
far too easy to have this happen:

ops$tkyte@ORA920> select count(distinct object_id) from t;

COUNT(DISTINCTOBJECT_ID)
------------------------
                      65

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      l_cnt number;
  3  begin
  4      for x in ( select partition_name
  5                   from user_tab_partitions
  6                  where table_name = 'T' )
  7      loop
  8          execute immediate
  9          'select count(*)
 10             from t partition(' || x.partition_name || ')'
 11             into l_cnt;
 12          dbms_output.put_line
 13          ( x.partition_name || ' ' || l_cnt || ' rows...' );
 14      end loop;
 15  end;
 16  /
SYS_P395 65 rows...
SYS_P396 0 rows...
SYS_P397 0 rows...
SYS_P398 0 rows...
SYS_P399 0 rows...
SYS_P400 0 rows...
SYS_P401 0 rows...
SYS_P402 0 rows...

PL/SQL procedure successfully completed.

All 7million rows could be in the same hash partition.  You would use range or
list instead.

      Rating:
      5
cont.  June 27, 2003
Reviewer:
      Alvin  from Philippines
therefore minimizing physical i/o block reads.

      Rating: 5
Why a different
      index?  June 29, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Hi Tom,
I was trying your examples on pages 639-640 of Expert one-on-one Oracle and for
the query using both coluns A and B, it seems to be using the NONPREFIXED index
(the example in book shows it using the PREFIXED index)?

Is there something in my test runs that makes it use the other index (or Oracle
init.ora settings)?
Thanks,

ops$kkishore @ ORA817.WORLD > create table partitioned_table
  2  (
  3    a int,
  4    b int
  5  )
  6  partition by range(a)
  7  (
  8    partition part_1 values less than (2),
  9    partition part_2 values less than (3)
 10  )
 11  /

Table created.

ops$kkishore @ ORA817.WORLD >
ops$kkishore @ ORA817.WORLD > create index idx_local_prefixed on
partitioned_table (a, b) local ;

Index created.

ops$kkishore @ ORA817.WORLD > create index idx_local_nonprefixed on
partitioned_table (b) local ;

Index created.

ops$kkishore @ ORA817.WORLD >
ops$kkishore @ ORA817.WORLD > insert into partitioned_table values (1, 1) ;

1 row created.

ops$kkishore @ ORA817.WORLD >
ops$kkishore @ ORA817.WORLD > alter index idx_local_prefixed modify partition
part_2 unusable
  2  /

Index altered.

ops$kkishore @ ORA817.WORLD > alter index idx_local_nonprefixed modify partition
part_2 unusable
  2  /

Index altered.

ops$kkishore @ ORA817.WORLD >
ops$kkishore @ ORA817.WORLD > set autotrace on explain
ops$kkishore @ ORA817.WORLD > select * from partitioned_table where a = 1 and b
= 1 ;

         A          B
---------- ----------
         1          1

1 row selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=26)
   1    0   TABLE ACCESS (BY LOCAL INDEX ROWID) OF 'PARTITIONED_TABLE' (Cost=1
Card=1 Byte
          s=26)

   2    1     INDEX (RANGE SCAN) OF 'IDX_LOCAL_NONPREFIXED' (NON-UNIQUE) (Cost=1
Card=1)

ops$kkishore @ ORA817.WORLD > select * from partitioned_table where b = 1 ;
ERROR:
ORA-01502: index 'OPS$KKISHORE.IDX_LOCAL_NONPREFIXED' or partition of such index
is in unusable
state

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=2 Bytes=52)
   1    0   PARTITION RANGE (ALL)
   2    1     TABLE ACCESS (BY LOCAL INDEX ROWID) OF 'PARTITIONED_TABLE' (Cost=1
Card=2 By
          tes=52)

   3    2       INDEX (RANGE SCAN) OF 'IDX_LOCAL_NONPREFIXED' (NON-UNIQUE)
(Cost=3 Card=2)

ops$kkishore @ ORA817.WORLD >

          Followup:

it is able to -- it used A = 1 to do partition elimination (which can do both
LOCAL INDEX and TABLE partition elimination).  So, it only read part_1 of the
idx_local_nonprefixed.

In 9i, if you analyze the table you might find this to be the behavior:

ops$tkyte@ORA920> set autotrace on explain
ops$tkyte@ORA920> select * from partitioned_table where a = 1 and b = 1 ;

         A          B
---------- ----------
         1          1

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=4)
   1    0   INDEX (RANGE SCAN) OF 'IDX_LOCAL_PREFIXED' (NON-UNIQUE) (Cost=2
Card=1 Bytes=4)

ops$tkyte@ORA920> select * from partitioned_table where b = 1 ;
ERROR:
ORA-01502: index 'OPS$TKYTE.IDX_LOCAL_PREFIXED' or partition of such index is in
unusable state

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=4)
   1    0   PARTITION RANGE (ALL)
   2    1     INDEX (SKIP SCAN) OF 'IDX_LOCAL_PREFIXED' (NON-UNIQUE) (Cost=2
Card=1 Bytes=4)

ops$tkyte@ORA920> set autotrace off

In 817, if you analyze it, you'll get:

ops$tkyte@ORA817DEV> set autotrace on explain
ops$tkyte@ORA817DEV> select * from partitioned_table where a = 1 and b = 1 ;

         A          B
---------- ----------
         1          1

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=4)
   1    0   INDEX (RANGE SCAN) OF 'IDX_LOCAL_PREFIXED' (NON-UNIQUE) (Cost=1
Card=1 Bytes=4)

ops$tkyte@ORA817DEV> select * from partitioned_table where b = 1 ;
ERROR:
ORA-01502: index 'OPS$TKYTE.IDX_LOCAL_PREFIXED' or partition of such index is in
unusable state

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=4)
   1    0   PARTITION RANGE (ALL)
   2    1     INDEX (FULL SCAN) OF 'IDX_LOCAL_PREFIXED' (NON-UNIQUE) (Cost=1
Card=1 Bytes=4)

ops$tkyte@ORA817DEV> set autotrace off

bottom line is -- plans can and will change.  even though you get a subtly
different plan, the mechanics were identical.  We achieved partition elimination
and one query worked where as the other (cause it could not achieve this
elimination) did not.

      Rating: 5
analyze was the
      key...  June 29, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Hi Tom,
This looks like an example in support of your statement:
<quote>
bottom line is -- plans can and will change.
</quote>

And it did change.

The plan came out to be different even though the steps performed were same.

In this particular case, as pointed out by you, the analyze was the key. Once I
analyzed the table and did it again, it picked up the PREFIXED index.

It looks like that you are saying that even the use of NONPREFIXED index by the
optimizer is NOT a sub-optimal execution plan and is acceptable, correct?
Thanks,

          Followup:

in light of the fact there were no stats, yes, it was perfectly acceptable.

And note that in 9i, the plans change yet again to something completely
different (new feature index skip scan)

      Rating: 5
Null values
      again...  July 01, 2003
Reviewer:  Alvin
       from Philippines
In pp638 of your book you said that non-prefixed local indexes may or may not
allow partition elimination.

If the column partition containing null values such a case for not using
partition elimination ? because as you've said in my ealier question ( see 2
post earlier of this one ) that it places null values where it wants.

1. Will Null values scattered in diff partitions ?

2. Or NOT because when it places null values at a particular partition it ALWAYS
puts ALL null valued columns in the SAME partition ?

          Followup:

range:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96524/c12parti.htm#461507
goes into the "max value" partition

list:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96524/c12parti.htm#466634
goes into the "default" partition

hash:

not defined.

I would suggest you want to partition on a NOT NULL column in any case

      Rating:
      3
Partitioning  July 10, 2003
Reviewer:
      vj  from in
Tom,

I mean overall how efficient is to use the RANGE Partitioning for NON-NUMERIC
Columns ? I guess Oracle has to convert to ASCII and do the same right...

Also got a question of how can i take a call of when i go for Hash..Between
Range and Hash most of the times i get beaten..Kindly help me..i might have
around 1 mil records..

          Followup:

it is a binary compare, no more or less efficient then numbers really (numbers
in Oracle are like packed decimal "strings" with 2 digits per byte).

The decisions on hash, range or list are based on HOW you use the data, WHAT you
want to accomplish via partitioning.

Suppose you wanted to accomplish:

o aging of data, allowing for easy purge

then range partitioning is the only thing that made sense.

Suppose you wanted to accomplish

o administrative ease of use
o increased concurrency on an OLTP table (keyed reads)

then hashing by the key with some global indexes that are range partitioned by
their respective keys would be appropriate.

You have to state what it is you want to accomplish via partitioning, then the
answer will be "obvious" or at least "more obvious"

      Rating: 4
to change the
      range values ...  September 17, 2003
Reviewer:
      reader
Tom, We have partitioned tables based on range value with primary key that is a
number. Currently the range is like, 1-100000 in one partition and each
partition is expected to have 100000 records . We wanted to change the range
like 1-200000 in the first partition and each of the other partitions will have
200000 records. How do i do this? Thanks.

          Followup:

you would merge them.

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/statements_32a.htm#2114071

      Rating: 3
Partitioning/
      storage parameters  November 03, 2003
Reviewer:
      wasey  from US
I have tables in one schema 'A' for each year  as  follows using single table
space
Y00(id number, account_no number, trans_date date)  -- There are indexes on id,
account_no, trans_date
Y01(id number, account_no number, trans_date date)    -- There are indexes on
id, account_no, trans_date
Y02(id number, account_no number, trans_date date)    -- There are indexes on
id, account_no, trans_date
Y03(id number, account_no number, trans_date date)   -- There are indexes on id,
account_no, trans_date
There are approximately 1.5 million records in each table

I would like to create a single table in schema 'B' with partition on Date range
and would like to migrate the data from schema 'A' to schema 'B', so that each
partition will have its own table space and I would like to create a single
table space for all the  indexes representing all the partitions.
What should be my approach to define the storage parameters or sizing  of table
space
PCTFREE ..
 STORAGE (
   INITIAL ....
   MINEXTENTS ....
   MAXEXTENTS ....
)

As I am getting the following error  creating a index on one of the tables
CREATE INDEX ID_1 ON TABLE1
*
ERROR at line 1:
ORA-01659: unable to allocate MINEXTENTS beyond 18 in tablespace TBS_1

          Followup:

use locally managed tablespaces

do not use storage parameters

1.5 million records is pretty small if that is table -- 2 numbers and a date.
Just use 1 or 2 megabyte uniform extents.

no initial
no next
no pctincrease

ever again!

      Rating: 3
Partitioning/
      storage parameters   November 04, 2003
Reviewer:
      wsaey  from US
Can you please elaborate
why we do not need the storage parameters? and create only local table spaces.

Regards,

          Followup:

locally MANAGED tablespaces.

there are two types:

a) uniform extents.  all extents are exactly the same size.
b) system allocated. Oracle decides how big extents should be based on growth
patterns.

In both cases, the only storage parameters I like to see are:

( initial 1k next 1k pctincrease 0 minextents 1 )

that makes sure the initial segment create will get one extent and grow from
there an extent at a time.

      Rating: 4
creation of
      partition table  November 05, 2003
Reviewer:
      Ashiq Shamsudeen A  from Trichy , Tamil Nadu, India

Hi Tom,

 I've a partition table a_part ,if i try to create table b_part (using the
syntax create table b_part as select * from a_part) .Its creating ordinary table
instead of partition one,why?

          Followup:

because you told it to.

you said "hey, create a table b_part please"

you didn't say "hey, create a partitioned table b_part using these rules"

you just created a table, nothing more, nothing less.  if you want a partitioned
table, create a partitioned table and populate it with "as select * from
a_part".

      Rating:
      4
partitioning/storage parameters  November 05, 2003

Reviewer:  wasey  from US
Thanks Tom
based on my previous question, lets say I have more tha three columns and  in
each of my Y00, Y01 ... tables

I would like to create the indexes as following

CREATE INDEX ID1 ON Y00
(ACCOUNT_NO)
  INITRANS   2
  MAXTRANS   255
LOCAL (
  PARTITION Y00
    LOGGING
    NOCOMPRESS
    TABLESPACE TBS1
    PCTFREE    10
    INITRANS   2
    MAXTRANS   255
    STORAGE    (
                INITIAL          32M
                MINEXTENTS       1
                MAXEXTENTS       2147483645
                FREELISTS        1
                FREELIST GROUPS  1
                BUFFER_POOL      DEFAULT
               ),
  PARTITION Y01
    LOGGING
    NOCOMPRESS
    TABLESPACE TBS1
    PCTFREE    10
    INITRANS   2
    MAXTRANS   255
    STORAGE    (
                INITIAL          32M
                MINEXTENTS       1
                MAXEXTENTS       2147483645
                FREELISTS        1
                FREELIST GROUPS  1
                BUFFER_POOL      DEFAULT
               ),
  PARTITION Y02
    LOGGING
    NOCOMPRESS
    TABLESPACE TBS1
    PCTFREE    10
    INITRANS   2
    MAXTRANS   255
    STORAGE    (
                INITIAL          32M
                MINEXTENTS       1
                MAXEXTENTS       2147483645
                FREELISTS        1
                FREELIST GROUPS  1
                BUFFER_POOL      DEFAULT
               ),

 .......

Based on your explanation
should I use
               INITIAL         32M
                NEXT            32M
                MINEXTENTS       1
                MAXEXTENTS       2147483645 (remove this ?)
                FREELISTS        1   (remove ?)
                FREELIST GROUPS  1   (remove ?)
                BUFFER_POOL      DEFAULT  (remove ?)

What is the best way to modify and I would like make sure that the index
creation does not fail due to tablespace size

Thanks,

          Followup:

i would use initial 1k next 1k minextents 1 and a LMT (lmts have unlimited
extents already)

(as i sort of said above :)

or on CREATE (as opposed to a alter index rebuild or alter table move), just
LEAVE THE STORAGE CLAUSE OFF, you don't need it at all

      Rating:
      3
Partitions/Storage Parameters  November 05, 2003

Reviewer:  wasey  from US
Tom,
 Can you please clarify your comments

I have the following Revenue Information table
CREATE TABLE REV_INF (
  ACT_ID      NUMBER (20)    ,
  ACT_NO       NUMBER (10) ,
  CG_QTY      NUMBER (6),
  CG_AMT      NUMBER (12,2),
  BILL_DT    DATE,
  --
  --
  --
  CG_ACC_CD_1      VARCHAR2 (2),
  CG_ACC_CD_2       VARCHAR2 (2),

   TABLESPACE TBS1
   PCTFREE 10
   PCTUSED 40
   INITRANS 1
   MAXTRANS 255
PARTITION BY RANGE (BILL_DT)
  (
PARTITION RD_PREV VALUES LESS THAN (TO_DATE(' 1980-01-01', 'YYYY-MM-DD'))
TABLESPACE TB80
PCTFREE 10
   PCTUSED 40
   INITRANS 1
   MAXTRANS 255
 STORAGE (
   INITIAL 94371840
   MINEXTENTS 1
   MAXEXTENTS 2147483645
 ),
PARTITION Y90 VALUES LESS THAN (TO_DATE(' 1990-01-01', 'YYYY-MM-DD')) TABLESPACE
TB90
PCTFREE 10
   PCTUSED 40
   INITRANS 1
   MAXTRANS 255
 STORAGE (
   INITIAL 94371840
   MINEXTENTS 1
   MAXEXTENTS 2147483645
 ),
PARTITION Y00 VALUES LESS THAN (TO_DATE('2000-01-01', 'YYYY-MM-DD'))TABLESPACE
TB00
PCTFREE 10
   PCTUSED 40
   INITRANS 1
   MAXTRANS 255
 STORAGE (
   INITIAL 94371840
   MINEXTENTS 1
   MAXEXTENTS 2147483645
 ),
PARTITION Y10 VALUES LESS THAN (TO_DATE('2010-01-01', 'YYYY-MM-DD')) TABLESPACE
TB10
PCTFREE 10
   PCTUSED 40
   INITRANS 1
   MAXTRANS 255
 STORAGE (
   INITIAL 94371840
   MINEXTENTS 1
   MAXEXTENTS 2147483645
 ),
);

CREATE INDEX ACTNO_IX ON
  REV_INF(ACT_NO)
LOCAL
  (
PARTITION RD_PREV TABLESPACE TBS_IX
PCTFREE 10
PCTUSED 40

 STORAGE (
   INITIAL 94371840
   MINEXTENTS 1
   MAXEXTENTS 2147483645
 ),
PARTITION Y90 TABLESPACE TBS_IX
PCTFREE 10
PCTUSED 40
 STORAGE (
   INITIAL 94371840
   MINEXTENTS 1
   MAXEXTENTS 2147483645
 ),
PARTITION Y00 TABLESPACE TBS_IX
PCTFREE 10
PCTUSED 40
 STORAGE (
   INITIAL 94371840
   MINEXTENTS 1
   MAXEXTENTS 2147483645
 ),
PARTITION Y10 TABLESPACE TBS_IX
PCTFREE 10
PCTUSED 40
 STORAGE (
   INITIAL 94371840
   MINEXTENTS 1
   MAXEXTENTS 2147483645
 )
);

/***************************** /
with your comment --

no initial
no next
no pctincrease
ever again!

/*****************************/
Should the table be reconstructed as follows with locally managed TABLESPACES by
removing the all the storage parameters.

CREATE TABLE REV_INF (
  ACT_ID      NUMBER (20)    ,
  ACT_NO       NUMBER (10) ,
  BILL_DT    DATE,
  --
  --
  --
  CG_ACC_CD_1      VARCHAR2 (2),
  CG_ACC_CD_2       VARCHAR2 (2),

   TABLESPACE TBS1
   PCTFREE 10
   PCTUSED 40
   INITRANS 1
   MAXTRANS 255
PARTITION BY RANGE (BILL_DT)
  (
PARTITION RD_PREV VALUES LESS THAN (TO_DATE(' 1980-01-01', 'YYYY-MM-DD'))
TABLESPACE TB80,

PARTITION Y90 VALUES LESS THAN (TO_DATE(' 1990-01-01', 'YYYY-MM-DD')) TABLESPACE
TB90
,
 ,
PARTITION Y00 VALUES LESS THAN (TO_DATE(' 2000-01-01', 'YYYY-MM-DD'TABLESPACE
TB00
,
 ,
PARTITION Y10 VALUES LESS THAN (TO_DATE(' 2010-01-01', 'YYYY-MM-DD')) TABLESPACE
TB10

);

CREATE INDEX ACTNO_IX ON
  REV_INF(ACT_NO)
LOCAL
  (
PARTITION RD_PREV TABLESPACE TBS_IX,

PARTITION Y90 TABLESPACE TBS_IX,

PARTITION Y00 TABLESPACE TBS_IX,

PARTITION Y10 TABLESPACE TBS_IX
);

Thanks,
Wasey

November 06, 2003 Ganesh -- Thanks for the question regarding "Mass
      Insertion .", version 8.1.7

      You Asked
Hai Tom,

thanx for ur quick reply.  Tom, Actually we do a monthly processing on this
table T1.  For a particular account in a month there may be transaction only for
few days.   When we move the information to the new table T2 we have to provide
info. for the missing dates based on the previous date information.

So for example if an account has entries in T1 for 3 days say 1/11/2002,
10/11/2002, 20/11/2002 then we populate the entires for 2/11/2002 to 9/11/2002
with 1/11/2002 info.
similary for 11/11/2002 to 19/11/2002 we populate it with 10/11/2002 info.
and for 21/11/2002 to 30/11/2002 we populate with 20/11/2002 rec.

Hope this info. is sufficient.

Hi Tom,

We have the following business requirement.

1. Table T1 will hold the transaction history. T1 contains ac_number,
transaction_date and other columns.  For a Ac_number,there may be two records
with different trasanction_date's.
eg
   Ac_Number      Transaction Date Amt
   10020101       2002/09/10       1000
   10020101       2002/09/20       5000

2. We have another table t2, where we need to populate the record's as follows
   Ac_Number      Transaction Date Amt
   10020101       2002/09/10       1000
   10020101       2002/09/11       1000
   10020101       2002/09/12       1000
   10020101       2002/09/13       1000
   10020101       2002/09/14       1000
   10020101       2002/09/15       1000
   10020101       2002/09/16       1000
   10020101       2002/09/17       1000
   10020101       2002/09/18       1000
   10020101       2002/09/19       1000
   10020101       2002/09/20       5000

    We need to take the record of 2002/09/10 and populate the records from
2002/09/11 to 2002/09/19. ( The records from 2002/09/11 to 2002/09/19 are called
break up for each day.) The table t1 has approx 50,000 rows. We use a curosor on
t1 take the first transaction and the last transaction for that account and
insert the records as shown above in t2. To do this it takes 6hrs. Can u help us
to do this faster.

Thanks and Regards
Ganesh.A

      and we said...
the following can do that in a single statement using a direct path load:

ops$tkyte@ORA920.US.ORACLE.COM> select * from t;

 AC_NUMBER TDATE             AMT
---------- ---------- ----------
  10020101 2002/09/10       1000
  10020101 2002/09/20       5000
  10020101 2002/09/25       6000
  20020101 2002/09/05        500
  20020101 2002/09/15        750
  20020101 2002/09/22       1000

6 rows selected.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> insert /*+ append */ into t2
  2  select ac_number, tdate+x-1, amt
  3    from (
  4  select ac_number, tdate, nvl((lead(tdate) over ( partition by ac_number
order by tdate))-1,tdate) next_tdate, amt
  5    from t
  6         ),
  7             idx
  8   where x <= next_tdate-tdate+1
  9  /

34 rows created.

ops$tkyte@ORA920.US.ORACLE.COM> commit;

Commit complete.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select * from t2
  2  order by ac_number, tdate
  3  /

 AC_NUMBER TDATE             AMT
---------- ---------- ----------
  10020101 2002/09/10       1000
  10020101 2002/09/11       1000
  10020101 2002/09/12       1000
  10020101 2002/09/13       1000
  10020101 2002/09/14       1000
  10020101 2002/09/15       1000
  10020101 2002/09/16       1000
  10020101 2002/09/17       1000
  10020101 2002/09/18       1000
  10020101 2002/09/19       1000
  10020101 2002/09/20       5000
  10020101 2002/09/21       5000
  10020101 2002/09/22       5000
  10020101 2002/09/23       5000
  10020101 2002/09/24       5000
  10020101 2002/09/25       6000
  20020101 2002/09/05        500
  20020101 2002/09/06        500
  20020101 2002/09/07        500
  20020101 2002/09/08        500
  20020101 2002/09/09        500
  20020101 2002/09/10        500
  20020101 2002/09/11        500
  20020101 2002/09/12        500
  20020101 2002/09/13        500
  20020101 2002/09/14        500
  20020101 2002/09/15        750
  20020101 2002/09/16        750
  20020101 2002/09/17        750
  20020101 2002/09/18        750
  20020101 2002/09/19        750
  20020101 2002/09/20        750
  20020101 2002/09/21        750
  20020101 2002/09/22       1000

34 rows selected.

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

and to test it out -- speedwise -- i loaded it up:

DOC>begin
DOC>        for i in 1 .. 50000/3
DOC>        loop
DOC>                insert into t values ( i, '2002/09/10', 1000 );
DOC>                insert into t values ( i, '2002/09/20', 5000 );
DOC>                insert into t values ( i, '2002/09/25', 6000 );
DOC>        end loop;
DOC>end loop;
DOC>/

ops$tkyte@ORA920.US.ORACLE.COM> insert /*+ append */ into t2
  2  select ac_number, tdate+x-1, amt
  3    from (
  4  select ac_number, tdate, nvl((lead(tdate) over ( partition by ac_number
order by tdate))-1,tdate) next_tdate, amt
  5    from t
  6         ),
  7             idx
  8   where x <= next_tdate-tdate+1
  9  /

266677 rows created.

Elapsed: 00:00:18.91
ops$tkyte@ORA920.US.ORACLE.COM> commit;

Commit complete.

and it took less then 20 seconds on my desktop pc.

Review &
      Followup

      Rating:
      5
Excellent  November 19, 2002
Reviewer:
      A reader
People say that SQL is a simple language, yet there are volumes written on it
and there only few out there who masters it.
You are one of them.

Keep up the good work Tom.
Cheers !

      Rating: 5
To
      clarify  November 19, 2002
Reviewer:  Sai
       from Palo Alto, CA USA
Make sure to create pivot table idx(x number) with 31 rows from 1 to 31.

          Followup:

DOH - must have been asleep

here is the FULL example i ran:

create table t ( ac_number int, tdate date, amt number );
create table t2 ( ac_number int, tdate date, amt number );

create table idx ( x primary key ) organization index
as
select rownum from all_objects where rownum <= 32;

alter session set nls_date_format = 'yyyy/mm/dd';

insert into t values ( 1, '2002/09/10', 1000 );
insert into t values ( 1, '2002/09/20', 5000 );
insert into t values ( 1, '2002/09/25', 6000 );

insert into t values ( 2, '2002/09/05', 500 );
insert into t values ( 2, '2002/09/15', 750 );
insert into t values ( 2, '2002/09/22', 1000 );

begin
    for i in 1 .. 50000/3
    loop
        insert into t values ( i, '2002/09/10', 1000 );
        insert into t values ( i, '2002/09/20', 5000 );
        insert into t values ( i, '2002/09/25', 6000 );
    end loop;
end loop;
/

select count(*) from t;
pause

select * from t;
*/

insert /*+ append */ into t2
select ac_number, tdate+x-1, amt
  from (
select ac_number, tdate, nvl((lead(tdate) over ( partition by ac_number order by
tdate))-1,tdate) next_tdate, amt
  from t
       ),
       idx
 where x <= next_tdate-tdate+1
/
commit;

      Rating:
      5
  November 20, 2002
Reviewer:
      A reader
If that is what you ran, you would have got a syntax error. You have an end
loop; in place of end;

It was informative to learn DOH stads for 'Deliriously Overcome with Hilarity'.
(Found out from acronymfinder.com)

          Followup:

Interestingly -- it is *not* a syntax error!

ops$tkyte@ORA920.US.ORACLE.COM> begin
  2          for i in 1 .. 50000/3
  3          loop
  4                  insert into t values ( i, '2002/09/10', 1000 );
  5                  insert into t values ( i, '2002/09/20', 5000 );
  6                  insert into t values ( i, '2002/09/25', 6000 );
  7          end loop;
  8  end loop;
  9  /

PL/SQL procedure successfully completed.

for the same reason that:

ops$tkyte@ORA920.US.ORACLE.COM> begin
  2          for i in 1 .. 50000/3
  3          loop
  4                  insert into t values ( i, '2002/09/10', 1000 );
  5                  insert into t values ( i, '2002/09/20', 5000 );
  6                  insert into t values ( i, '2002/09/25', 6000 );
  7          end loop;
  8  end DOH;
  9  /

PL/SQL procedure successfully completed.

works -- it is "sloppy code" (shouldn't have done it) but it works none the
less.  We can label end blocks like that and LOOP was just taken as a "name".

Find the bug in this code:

begin
   for i in 1 .. 1000
   loop
       insert into t values ( i );
   end loop
   commit;
   rollback;
end;
/

it compiles, it runs but table t never gets any rows... why not?  missing
semi-colon, commit becomes a NAME for the end loop statement instead of a
commit....

Actually -- I was using "DOH" as the sound that Homer Simpson makes on the TV
show when he realizes he's done a stupid thing ;)  Never knew it had another
meaning!

      Rating:
      4
  November 20, 2002
Reviewer:  A
      Ganesh  from Singapore
hai Tom,

Thanks a lot.  It helped us in improving performance to a greater extent.

      Rating: 3
genarate date
      values  November 05, 2003
Reviewer:  reader

hi,
Can we generate date values using a query from dual (i dont want to create table
to hold date values)
let me explain the process in detail.
iam designing a report for invoice. in that i have a field which allows users to
select date from calendar.
to design the calender i need the basic query which results all valid date
values for x or y or z year.

for example
SQL> select sysdate from dual;

results the current date .

i neeed a query which reults all the date values for a particular year .

i hope that i made my question clear
thanks

          Followup:

  1  select add_months( to_date('01-jan-2003','dd-mon-yyyy'), rownum-1 ) dt
  2    from
  3  ( select 1
  4    from
  5  (select 0 w, 1 x, 2 y, 3 z
  6     from dual
  7    where rownum = 1
  8  )
  9   group by cube( w,x,y,z )
 10  )
 11*  where rownum <= 12
ops$tkyte@ORA920LAP> /

DT
---------
01-JAN-03
01-FEB-03
01-MAR-03
01-APR-03
01-MAY-03
01-JUN-03
01-JUL-03
01-AUG-03
01-SEP-03
01-OCT-03
01-NOV-03
01-DEC-03

12 rows selected.

      Rating: 5
A little
      simpler version to generate N rows  November 06, 2003

Reviewer:  Logan Palanisamy  from Santa Clara CA
      95051
Tom may have added the extra inline view with w, x, y, z to explain the concept.
Anyways, here is a simplified one.

SQL> select add_months( to_date('01-jan-2003','dd-mon-yyyy'), rownum-1 ) dt
  2  from
  3  ( select 1 from dual group by cube( 1,1,1,1 )
  4  )
  5  where rownum <= 12;

DT
---------
01-JAN-03
01-FEB-03
01-MAR-03
01-APR-03
01-MAY-03
01-JUN-03
01-JUL-03
01-AUG-03
01-SEP-03
01-OCT-03
01-NOV-03
01-DEC-03

12 rows selected.

SQL>

The "select 1 from dual group by cube( 1,1,1,1 )" simply generates 16 rows.

cube(1,1) will generate four rows
cube (1,1,1) will generate 8 rows and so on.

November 06, 2003 Andrew -- Thanks for the question regarding
      "Replication ", version 8.1.5

      You Asked
Tom

I recently read the first chapter from your Oracle Expert One to One book and it
so amazed me and supported things I have been saying for years I purchased it
immediately. I have started to read it and its brilliant.

Anyway on to the question.

We are using 2 way replication and found that there are many problems. One of
the main ones being that it locks the tables during replication which has meant
that our main table is unavaliable for a long time during the day.

I must point out that we are using 8 Databases located on ships floating around
in the see, all linked via satellite on a very slow connection.

People higher up the chain are keen to rewrite replication ourselves but I have
been totally opposed to this and it was fantastic to read in you book that you
agree. I managed to convince them that Oracle replication was the way to go be
honest it just wouldnt work ( I would suggest its our network speed rather than
anything else).

Could you give me some basic idea of exactly what replication does in the
background so that I can prove that rewritting replication is a seriously bad
idea.

Thanks

Andy Gilfrin.

      and we said...
ask them, "if Oracle has to do some locking to provide concurrency controls at
the bit and byte level -- why wouldn't we.  Are they just stupid or are we that
smart?"

I'm not a big fan of bits and bytes data replication (replication at the table
level, underneath an application).

I am a huge fan of using AQ and custom messages to broadcast EVENTS.  Say this
was an HR system, I would not bits and bytes replicate the HR tables.  I would
broadcast a "new hire" event -- those that are interested would subscribe to
this event and do something when they dequeued a "new hire" event and its
message payload.

That way, you replicate just what you need, as you need it to whomever you want
to have it.  In fact, we've taken this to the next level in 9iR2:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96571/toc.htm

So, I would not call it replication -- I would call it "we'll use a messaging
infrastructure based on queueing software we have in the database".

You do not want to rewrite replication -- you want to use a wholly different
technique built on top of AQ and just send messages about.

Review &
      Followup

      Rating:
      5
Replication  October 23, 2002
Reviewer:
      Andy Gilfrin  from Dover, UK
Thats the approach I'm looking at implementing.

Basically the system was replicating 300+ record insets per database per day.
Why ? so that we could be sure if somebody moved from one ship to another then
their records would be avalible.

What I'm suggesting is only send the records if they actually go to another
ship. That way you only get what you need. It actualy turns out that during
development we were told that ship transfers were regular occurances, turns out
its 4 or 5 people per month, so we are replicating over 2000 records per day
just for 4 or 5 people a month.

"Are they just stupid or are we that smart?"

I think its you who is Smart hopefully some of its is rubbing off on me :) .

Thanks for the reply and keep up the fantastic work.

          Followup:

Thats the ticket.  A more directed flow of just whats needed, where it is
needed, as it is needed -- especially in these afloat applications.

(sorry about the sarcastic comment ;)  it just came out wrong)

      Rating: 5
Export vs
      Replication  April 24, 2003
Reviewer:  David

I must set up this scenario:

Consider two databases: one for production and another for UAT. We have
development too, but it does not matter here. We were asked to create in uat a
clone of the production environment. The loading of data from prod to uat has to
happen nightly. End users must have the ability to do dml and select on uat.

Our question is which would be the best method:

#1. A script run from crontab at night with:
    . exp from prod consistent=y
    . delete/truncate the respective tables in uat
    . imp to uat ignore=y

#2. Replication (I would have to study because I have never implemented this)

Which method would you suggest ? Any other ideas ? Maybe unload/load ?

          Followup:

Option #3

Hot backup prod
Restore to UAT

faster then any of the above, no impact on production AND it tests your backups
and ability to use them properly!

      Rating: 5
exp vs
      replication  April 24, 2003
Reviewer:  David

Your answer was right, but my question was wrong...

I will only replicate a couple of schemas, not the entire database. The other
uat schemas must remain untouched.

What now ?

          Followup:

do you have the ability to TRANSPORT the necessary tablespaces

- you'll make them read only
- copy their files and export their metadata
- make them read write

- drop the old ones from uat
- attach the new ones to uat

      Rating: 5
exp vs
      replication  April 24, 2003
Reviewer:  David

But all shemas are mapped to the same USERS tablespace. This is not neat -- I
know. This is not my database -- not my fault.

I would add that these schemas are small -- few tables, few rows -- nothing
much.

What now ?

          Followup:

exp -> ftp -> drop tables -> imp.

      Rating: 4
Why not
      snapshot Replication?  April 24, 2003
Reviewer:
      Anand  from Atlanta, Georgia, USA
Incase you have only a few tables and few rows, then why not replicate them
using simple Snapshot Replication ?

1. Just create snapshot logs on the source
2. Have a database link between Prod and UAT.
3. Create snapshots with incremental automatic refreshes

Tom, what say you?

          Followup:

I say sounds like it adds overhead and complexity to something that could be
solved very simply with exp/imp.

      Rating: 4
Can you pls
      elaborate ?  April 24, 2003
Reviewer:  Anand
       from Atlanta, USA
Tom,

Why do you say it adds overhead ?

Advantages of Snapshot replication :

1. No need to drop the tables everytime. This means no downtime for users. These
tables can continue to be used by users even while replication is happening.

2. Only the differential data is moved to UAT. We don't have to drop/delete the
tables each time and load them all over again.

3. All the contraints/grants/whatever are still preserved. Just the data is
moved.

Just for moving 10 records over to UAT, why do we have to
export/drop/delete/import? Kinda sounds waste of time.

What happens if something happens while export/ftp/import and they are done
un-attended during night time?

Snapshots are very error-free, zero-maintenance procedures. I personally have
been using this technique without any major headache/calls from users during
night time.

Thx Tom.

          Followup:

Snapshot logs impact the performance of the production system.
Snapshot logs impact the maintainence of the production system.
Database links and the refresh process are another thing to manage/maintain.
The two systems are dependent on eachother more tightly.

In order for UAT to be usable -- it must be read/write.  If you make it read
write -- UAT would be updating production.  That would be *bad*.

So, not only is replication not the best way -- it would be actually a really
bad idea.  You cannot use read only snapshots (by definition) and you DON'T WANT
UAT to update prod.

Think about what replication is good at -- keeping the two systems in sync.
That is not what is required here -- rather what is required is that the test
system is refreshed, populated, made a copy of prod every night after the users
go home.

      Rating: 3
Replication of
      a user - how  May 05, 2003
Reviewer:  Riyaz H,
       from Chennai, South India
Hi Tom,

Congrats!!! You are doing wonderful job for the users of oracle. This is the
site, which is having a solution for almost all queries & problems.

We have a following situation in the client place:

1. Production data base is having the seperate oracle user (live).
2. User name / password not known to the client MIS dept.
3. We have not given the user name / pwd to client MIS dept, for the following
reasons:
    a. They (at client place) are beginners in oracle.
    b. Data security is our concern

4. So we have decided to give the client, another user in the same database
server (replicate of production-live user) for them to query the database.
(apart from Front end tool)

5. For that, we granted "select" permission for all the tables in live user TO
dummry user (in same database server) and created synonyms (for each table/view)
in the dummy user. And client provided with username and password for that
replicate (dummy) user.

Now the question is, Is this any other "easier method" to give the replication
of the user in same database?

Hope I am clear with my question. Thanks in advance.

          Followup:

are your clients permitted to start and stop the database?  do they own the
machine the database is installed on?  do they have "root" on that machine.

If so, you are only kidding yourself.  Unless you host this database and deny
them physical access to the server, they are already in your LIVE schema doing
whatever.

Your approach for creating a "read only user" is correct.  Unfortunately for you
-- it certainly won't give you the attributes you desire however.  Your approach
works when you physically own and control the database -- to prevent normal end
users from messing with the data.  It will certainly not deter someone who can
connect "internal" or "as sysdba" or as a DBA -- and I assume they do run
scripts that do backups, startup and shutdown and so on -- meaning they
certainly have that privilege.

      Rating: 3
Thanks for the
      reply.  May 06, 2003
Reviewer:  Riyaz H.
       from Chennai, South India
Thanks for the reply.

As you said, their DBA does the start and shut down of database.

But we tried to do this, basically NOT TO ALLOW the other users (who themselves
saying they are programmers) not to play with live data. Instead, by giving the
dummy user name and password, they themselves can apply select and retrive the
data.

          Followup:

great, then goal achieved.

You could "skip" the synonym if you like by using alter session set
current_schema instead but the synonym works just fine.

      Rating: 4
Thanks
      again  May 06, 2003
Reviewer:  Riyaz H.
       from Chennai, India
Thanks a lot.

      Rating: 5
Sync between
      OLTP and data warehouse  June 05, 2003
Reviewer:
      Minnie  from USA
Hi Tom,
Thanks for your great help you have done before for me.
After review all of above, I know more about replication.
There are such requirements in my organiztion now:

1. Oracle 8.1.7 or 9.2.0.3;
2. Production database is only for an third-party application which is using RBO
because they are not ready for CBO yet;
3. Users request to do realtime ad-hoc query and reporting based on several big
tables;

My thoughts are
Opt#1. create a flat, refresh fast materialied view of base tables either in
prod database or other database. then create aggresive, query-rewrite
materialized view based on the previous MV (it's impossiable in PROD because of
RBO, i guess).
Opt#2. only sync some PROD tables that reporting needs with another database(say
REPORT) using CBO. do the denormalization and materialized view only in REPORT

My questions are
Q1: How to sync PROD and REPORT? As you say, there will be overhead if using
materialized view replic. what's the best way to sync PROD and REPORT with
mininized impact to PROD.
Q2: Are there possibilities to let the sync-ed database using CBO while PROD
database still use RBO in order to use qurey rewrite feature.

BTW, we are going to implement data warehouse. we should figure out the way of
realtime sync before it.

Thanks again and again

      Rating: 4
Need your
      advice  June 11, 2003
Reviewer:  Reader

there is a problem that comes up after upgrading a database to 8.1.7.
Package SYS.DBMS_REPCAT_RGT has become invalid.

=====

alter PACKAGE SYS.DBMS_REPCAT_RGT compile BODY;

Warning: Package Body altered with compilation errors.

SQL> show err

Errors for PACKAGE BODY SYS.DBMS_REPCAT_RGT:

LINE/COL ERROR

--------
-----------------------------------------------------------------

1146/9   PL/SQL: Statement ignored

1147/36  PLS-00705: exception 'MISSING_OFFLINE_DIRPATH' used in
expression

         requiring return type

===============
Checked in the metalink and is advised to run catrep.sql.
but the problem is the database is up and running.... and can't take it down
immediately.
Can you pls. advise that what are the impacts of this if we keep the object
invalid in the database....
or can it be dropped, if yes, how?? and what will be the impact of dropping it?
Is there any useful link that you can provide to find out the significance of
replication related package. Thanks in advance.

          Followup:

do you use replication?  if not, ignore it. if so, replication is currently "not
working" so you need to fix it.

metalink.oracle.com is in fact the relevant link.  open a tar.

      Rating: 5
replicate
      packages/procedures/functions?  July 10, 2003

Reviewer:  Kamal Kishore  from New Jersey, USA

Hi Tom,
Can we replicate packages/procedures/functions or only the tables and their
data?
Thanks,

          Followup:

yes you can

      Rating: 5
Package
      replication to different schema on target?  November 05,
      2003
Reviewer:  Kamal Kishore  from New Jersey, USA

Hi Tom,
Can you replicate a pacakge from the source server to the target server on a
different schema?

That is, for example if I have the source package as SCOTT.Employee_Maint, then
can it be replicated to the target as KKISHORE.Employee_Maint?
Thanks,
-Kamal.

          Followup:

replication is going to go schema -> same schema

November 06, 2003 Rakesh -- Thanks for the question regarding "full
      table scan & unique-nonunique indexes", version 817

      You Asked
Hi Tom

1) Pardon me if this question sounds repetitive to you.
   I was going through the forums the thread on index scan & full table scan
where
you mention that not to consider full table scan as evils

Till now i was under the impression that index scan will always result in faster
access
than full table scans
Could you site some scenarios where full table scans are better over index scans

2) Whats the difference between a unique & non unique index & which are more
advantageous to use in which situations

Regards
Rakesh

      and we said...
1)
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6749454952894#6760861174154
2) well, there are times you cannot use a unique index -- when the data is not
unique.  so that is pretty clear cut.

I never create unique indexes -- i create constraints that may create a unique
index.  I create "non unique" indexes to tune with -- but if I was to create a
unique index -- that really means I meant to create a UNIQUE CONSTRAINT!  That
gives the opimizer more information, more sematics.

so, never create a unique index, use "non unique" indexes to tune, use
constraints to get others created as a side effect.

Review &
      Followup

      Rating:
      5
  November 04, 2003
Reviewer:
      A reader

      Rating: 4
Also valid for
      partitioned indexes?  November 04, 2003
Reviewer:
      Eric  from Argentina
Hi Tom,
I'm wonder if your comment for the second question is also valid for local
nonprefixed indexes.
I thought the only way to have a unique index on those are the global unique
indexes.
Also, Oracle automatically creates a global unique index if you specify a unique
constraint on a non partition key column. Is there a way to avoid that behavior?
i.e. having a local (nonprefixed) index enforced with a unique constraint?
Thanks.

          Followup:

yes, for partitioned.

unique indexes should be created by UNIQUE or PRIMARY KEY constraints -- for if
you have a unique index without one or the other, you've done something wrong.

you can use the "using index" clause of the constraint clause to get the global
partitioning scheme you desire (else you get a single partition)

if the unique columns are NOT part of the partition key, you'll be using a
GLOBAL index to enforce uniqueness.

      Rating:
      3
Question.  November 05, 2003
Reviewer:
      Christo Kutrovsky  from Ottawa, ON Canada.
Can you confirm, that the optimiser will NOT consider the fact that a given
index is unique, if there's no unique constraint associated with it ?

If yes, can you confirm with a test case on a 9iR2 daatabase.

          Followup:

nope, never said that.  it is not that it will not consider it (it'll cost
certain things differently) it is that query rewrite relies on constraints being
in place -- not indexes.  so it can affect the optimizer, but the optimizer
doesn't "not" consider it.

      Rating:
      4
Cool.  November 06, 2003
Reviewer:
      Christo Kutrovsky  from Ottawa, ON Canada
Got it.

I guess I did not express my self correctly, by "not" consider it I ment will
treat it differently (i.e. could result in a different plan) if only a unique
index was present, and not a unique constraint.

I guess it the most affected are query rewrites.

November 06, 2003 -- Thanks for the question regarding "row
      migration", version 8.1.7

      You Asked
Hi Tom,

I asked this questions to my coleagues/friends and not convinced with their
answers.

What is the advantage of having forward address of a row that is migrated, in
one block and row data in another block. I know that it is due to the index
entry. why cant the server be built such that it updates the index entry once a
row is migrated because it is one time update and there will  be no double block
read while retrieving the data.

If a table dont have an index at all and row migration takes place for a row,
how will you support having forward address in one block and data in another
block.

i am asking this question out of curiousity and nothing else. If i am wrong
please excuse.

Regards,
Vijaya Chander V.S

      and we said...
You could but -- it would seriously damage the index structure.

In an index, you have the rowid as part of the key.  An update to a row might
have to update 6 indexes (cause the row moved) and update the indexes -- MOVING
the rows in the index, perhaps splitting that up - but definitely cause tons of
extra work.

An occasional extra IO on an indexed read was deemed preferrable to having to
reach out and touch tons of index data.

Additionally -- with the exception of:

o index organized tables when you update a primary key
o partitioned tables with "enable row movement" specifically turned on and an
update to the partition key

a rowid is immutable -- it is assigned to a row upon insert and will never
change.  Many tools count on this fact (replication used to but still can,
indexes do, Oracle Forms does by default for row locking, and so on)

Review &
      Followup

      Rating: 4
thank
      you  October 14, 2002
Reviewer:  Vijaya Chander
      V.S  from Bangalore, India
Hi Tom,

Thank you for your explanation. Why cant we think to delete that row and insert
into the table again if a row has to be migrated due to the unavailability of
space. Actually we are doing this operation on behalf of the server to get rid
of the migrated rows. why cant this feature be implemented in the server itself.

          Followup:

It could be, there is not reason TECHNICALLY it could be however

o the rowid would change (bad)
o ALL of the indexes would have to be updated (very bad)
o it is not frequent (so why worry)

I can count on one hand the number of times I've felt compelled to lose sleep
over migrated rows.  People worry way too much about it.  Until MOST of the
table is migrated, it is not an issue (and points to the fact that you've set
pctfree way too high for that table perhaps)

      Rating: 4
Does this apply
      to chained rows also ?  October 15, 2002
Reviewer:
      Arun Gupta  from Harrisburg, PA USA
I read in the Oracle manual that to Oracle chained rows and migrated rows are
same. Does your explanation apply to chained as well as migrated rows. I ask
since I am being made to lose sleep over chained/migrated rows.
Thanks
Arun

          Followup:

A migrated row is a special case of a chained row.

A migrated row can be "fixed".

A 'truly' chained row is a row that spans more then one block cause it doesn't
fit on a block.  They cannot be "fixed"

check out:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4423420997870

      Rating: 5
A third
      possibility ?  October 16, 2002
Reviewer:
      Connor McDonald  from UK
With regard to

"o index organized tables when you update a primary key
 o partitioned tables with enable row movement ..."

could you also get a rowid change if you updated a row in a compressed block
(9.2) ?

Cheers
Connor

          Followup:

it might migrate, but won't change the rowid:

ops$tkyte@ORA920.LOCALHOST> create table t compress
  2  as
  3  select * from all_objects order by owner, object_type
  4  /

Table created.

ops$tkyte@ORA920.LOCALHOST> create table t2 as select object_id, rowid rid from
t;

Table created.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA920.LOCALHOST> select CHAIN_CNT from user_tables where table_name =
'T';

 CHAIN_CNT
----------
         0

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> update t set owner = rownum;

29313 rows updated.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA920.LOCALHOST> select CHAIN_CNT from user_tables where table_name =
'T';

 CHAIN_CNT
----------
     29311

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> select * from t2 minus select object_id, rowid from
t;

no rows selected

ops$tkyte@ORA920.LOCALHOST> select object_id, rowid from t minus select * from
t2;

no rows selected

see they all migrate, the rowids are the same for each row...

      Rating:
      4
  July 07, 2003
Reviewer:  Vipin
       from New york
Hi Tom,

Reading this article, now the difference between Row chaining and Row migration
is very clear.

But I am left with a doubt that when Row chaining results and when Row migration
results.

From your above example Updation of row where x=3 resulted in migration of row
where x=2 row, but at the same time updation of row where x=3 later resulted in
row chaining. Now please tell me what leads to what? Hope I am clear.

One more doubt I am having is , in one another article you said that Row
migration is fixable but Row chaining is not.

http://asktom.oracle.com/pls/ask/f?p=4950:8:397388528205097280::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:5968286426175
But with export/import or alter table move row chaining is also fixable right?

Please explain.

          Followup:

A row migrates when an update causes it to not fit on the block with the other
rows that are there.  So, the row moves to a block where it can fit

A row CHAINS over multiple blocks when it is too big to fit on a block.  If you
have a table:

create table t ( x varchar2(4000) );

and you use a 2k block size
and you put 4000 characters in there

you will have a chained row -- that is not "fixable" by any means short of
putting it into a database with a larger block size.  export and import or alter
table move will not *fix* this condition.

      Rating:
      4
  July 07, 2003
Reviewer:  Vipin

Hi Tom,

I am sorry I asked this question as the followup of another question. Actually I
wanted to ask this question as a followup of this question:-

http://asktom.oracle.com/pls/ask/f?p=4950:8:397181020737905888::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:4423420997870

      Rating: 5
row
      migration  October 15, 2003
Reviewer:
      Reader
If i have a 4kb block and after update of a row the row size is larger than 4kb,
how does oracle handle this? Will it handle as migration or chaining? Thanks.

          Followup:

chained -- the row doesn't fit on a block.  it'll be chained over more then 1
block.

      Rating: 5
Row movement
      enabled  October 17, 2003
Reviewer:  Neil
       from UK
What happens to the space left when a row is moved as a result of "enable row
movement" in a partitioned table?

          Followup:

it is just as if you had "deleted" and "inserted" the row.  it is available for
reuse by subsequently inserted rows.

      Rating: 5
Row movement
      enabled on a partition or partitions  October 27, 2003

Reviewer:  Neil  from UK
I want to use row movement to archive data. Each target table has two
partitions, a live and an archive. When a record is marked for archival in the
driving table, a process will run to take the foreign key and go and mark all
the other related records in the other target tables.
If I have the archived partitions in their own tablespaces, I can mark them as
read only. However, row movement implies that I'll need to change this to get
the newly archived data into the archive partition. I would then have to change
the tablespace back to read only. To do this, I need to know if the row movement
has finished. What's the best way to achieve that?

          Followup:

you are running the "archive process" are you not?

you would know when it is done by the fact the archive process completed??

not sure what you mean by "when the row movement is done", it is done when the
transaction commits?

      Rating: 5
Row
      movement  October 27, 2003
Reviewer:  Neil

So the row movement isn't done in the background but as part of the transaction
updating the row. That's fine.

      Rating: 5
dbms_stats and
      chain count  November 05, 2003
Reviewer:
      Shankar  from Houston, TX
Tom, If I used dbms_stats package to collect statistics for a table, it does not
update chain_cnt column in dba_tables view. If I used analyze command, only then
it updates the chain_cnt column. It was strange to me because Oracle is pushing
dbms_stats package to collect statistics. Why shouldn't dbms_stats package
generate this very important statistics. Thanks.

          Followup:

chain_cnt isn't used by the optimizer so dbms_stats doesn't bother collecting
it.  to the optimizer, chain_cnt doesn't exist.

dbms_stats collects only that which the optimizer uses

November 06, 2003 Peter -- Thanks for the question regarding "Jave
      Stored Procedure vs PL/SQL Stored Procedure", version 8.1.6

      You Asked
Hi Tom:

can you explain between Java Stored Procedure vs PL/SQL Stored
Procedure?

1. JSP is multi-threaded vs PL/SQL SP is single-threaded?
2. JSP run byte code in Oracle JVM vs byte code in Oracle PL/SQL
engine? which one is more efficient?

Thanks for your help!
Peter Cheng

      and we said...
The difference is that you code one in java and the other in
PLSQL.  thats it quite simply.  They are both stored procedures
and exhibit the same behavior in that respect.

1) jsp = java server page, not a java stored procedure.  A java
stored procedure could be multi-threaded (that is supported)
however it is not recommended at all.  It is recommended you do
not use threads in a java stored procedure.

2) There is the Oracle jvm in the database which is architected
in much the same was as the PLSQL vm is (PLSQL runs in a VM just
like java).

As to which is more efficient -- if you are doing "data heavy"
stuff, plsql will be more efficient both in processing resources
as well as time to code (the language is written to do SQL
stuff).  Java is not.

I use java stored procedures when I find I cannot use PLSQL.
For example, sending an email with an attachment - plsql = hard,
java = easy.  Loop over the rows in a table and do something,
plsql = easy, java = hard -- i use plsql there.

Review &
      Followup

      Rating:
      3
  February 07, 2002
Reviewer:
      A reader

      Rating: 5
rumors of
      desupporting java in database  July 17, 2003

Reviewer:  A reader
Hi Tom
I have heard rumors that Oracle may be planning
to desupport java in database in future?

Have you heard anything of this sort?

Thanx for this awesome site!

Menon:)

          Followup:

no, not true.  we moved the j2ee containers out of the database into the middle
tier but java in the db is alive and kicking

      Rating:
      5
thanx!  July 17, 2003
Reviewer:
      A reader
btw, why was the decision made to move the j2ee containers
out of db? performance reasons? Any urls that discuss
this issue would be great!

Thank you!

Menon:)

          Followup:

no urls, just practical reasons -- performance being one.

      Rating:
      3
  July 21, 2003
Reviewer:
      A reader
jsp = java server page

and there I was thinking it meant

Jackson's method of Structured Programming.

      Rating: 4
Multi
      threading  November 05, 2003
Reviewer:  Phil
       from UK
from above "A java stored procedure
could be multi-threaded (that is supported) however it is not recommended at
all.  It is recommended you do not use threads in a java stored procedure."

why is it recommended you do not use threads in a java stored procedure?

thanks

          Followup:

because only one thread at a time really runs at a time.  java stored procedures
should be

a) simple
b) to the point
c) very very small
d) doing only that which plsql cannot do

so they really don't need threads anyway.

      Rating:
      5
JSP  November 05, 2003
Reviewer:  Tony
      Andrews  from London, UK
<QUOTE>
java stored procedures should be

a) simple
b) to the point
c) very very small
d) doing only that which plsql cannot do
</QUOTE>

Good advice.  However, I worry that those that like Java especially will tend to
merely use JSPs instead of PL/SQL procedures as a matter of course.

Where I currently work we have a very mature application that does a lot of its
core database work in Pro*C, merely because stored procedures weren't around
when it was first written.  I heard recently there were plans for a rewrite - in
Java!  The reason given was that there are more Java programmers in the "market"
than PL/SQL programmers.  What kind of stupid reason is that?

          Followup:

ask them how many DATABASE programmers there are - and what language they
program in.

      Rating: 4
Dime a
      Dozen  November 05, 2003
Reviewer:  Dan
      Clamage  from Pittsburgh, PA
They're thinking, more = cheaper. Like VB programmers, dime a dozen. They may
also be thinking, who will be maintaining the system in 10 years?

I read one performance test between Java and PL/SQL. For database-intensive
work, PL/SQL beat the snot out of Java. For complex calculations, Java was
faster. I guess because Java has an optimized math runtime library. If you
native-compile PL/SQL, you'll get a huge performance boost.

          Followup:

wait'll 10g ;)  then there goes that math argument too

      Rating: 4
To Tony:
        November 06, 2003
Reviewer:  Jens
       from Germany
Tony Andrews  from London, UK  wrote:

<QUOTE>
The reason given was that there are more Java programmers in the "market"
than PL/SQL programmers.  What kind of stupid reason is that?
</QUOTE>

What a bad decision! But Oracle has pushed it sometimes ago when 99% of every
Oracle magazine was full of Java: Jave here and Java there and nobody wants to
hear about new features in the databse or pl/sql. If you pick up some of these
"more Java programmers from the market" you take somebody in your team who has
perhaps a good java knowledge but a bad understanding of the database und query
processing and so on. I met a lot of good java programmmers who made a good job
as database-performance-killer!

Somebody who knows PL/SQL has a better knowledge in Oracle and helps you to
bring back the licence costs of your oracle installation into your company
needs. But for these only-java-guys you don't need oracle.

Bye,

Jens

Bye,

Jens

November 06, 2003 Paul -- Thanks for the question regarding "Table
      Compression -- in regards to Jan/Feb 2003 article", version 9iR2

      You Asked
In the Jan/Feb 2003 issue of Oracle magazine, you mention using the new
COMPRESS feature of 9i R2.

I think this was predicated on space being the only issue.  Some
applications may still want speed to be considered.  In my tests of this
feature, SELECT queries ran 2-3 times slower.  I presume this was the
overhead with un-mapping the compressed data.

Shouldn't cons of a new technique be mentioned as well as pros?

      and we said...
Sure -- cons and pros should be mentioned however -- I do not see your
observations.

For example, when running our TPC-H's with 9iR2 -- with compression on the
partsupp table with 2.4 billion rows, we found individual queries running within
10-15% PLUS and MINUS of eachother (net net -- no change).  But -- the amount of
disk used -- well, that was less.

Here is a tiny test I've done with these tables:

big_table@ORA920> select count(*) from big_table;

  COUNT(*)
----------
   1000000

big_table@ORA920> exec show_space( 'BIG_TABLE' );
Dictionary space managed...
Free Blocks.............................1
Free Blocks.............................1
Total Blocks............................13312
Total Bytes.............................109051904
Total MBytes............................104
Unused Blocks...........................301
Unused Bytes............................2465792
Last Used Ext FileId....................11
Last Used Ext BlockId...................12297
Last Used Block.........................723

PL/SQL procedure successfully completed.

big_table@ORA920> exec show_space( 'BIG_TABLE_COMPRESSED' );
Dictionary space managed...
Free Blocks.............................0
Free Blocks.............................0
Total Blocks............................1792
Total Bytes.............................14680064
Total MBytes............................14
Unused Blocks...........................116
Unused Bytes............................950272
Last Used Ext FileId....................11
Last Used Ext BlockId...................17289
Last Used Block.........................12

PL/SQL procedure successfully completed.

Not the hugest table in the world but demonstrable..  So, let the "benchmark"
begin:

select count(*)
from
 big_table

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      2.83       3.14       2090       2102          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      2.84       3.16       2090       2102          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 64

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=2102 r=2090 w=0 time=3140578 us)
1000000   INDEX FAST FULL SCAN BIG_TABLE_PK (cr=2102 r=2090 w=0 time=1753332
us)(object id 30637)

********************************************************************************

select count(*)
from
 big_table_compressed

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      2.77       2.75       1675       1680          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      2.78       2.76       1675       1680          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 64

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=1680 r=1675 w=0 time=2753003 us)
1000000   TABLE ACCESS FULL BIG_TABLE_COMPRESSED (cr=1680 r=1675 w=0
time=1453750 us)

Interesting to note that the CBO blew off the index in this second case.  The
big compressed table was faster here -- even though we did not use the primary
key index (which is actually bigger then the table in this case)

select *
from
 big_table

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch    66668     13.16      13.14      13010      78803          0     1000000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    66670     13.16      13.14      13010      78803          0     1000000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 64

Rows     Row Source Operation
-------  ---------------------------------------------------
1000000  TABLE ACCESS FULL BIG_TABLE (cr=78803 r=13010 w=0 time=6270376 us)

********************************************************************************

select *
from
 big_table_compressed

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.05          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch    66668     10.83      11.12       1675      68232          0     1000000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    66670     10.84      11.18       1675      68232          0     1000000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 64

Rows     Row Source Operation
-------  ---------------------------------------------------
1000000  TABLE ACCESS FULL BIG_TABLE_COMPRESSED (cr=68232 r=1675 w=0
time=5652688 us)

full scan performance is better -- less cpu time...

Then I ran:

declare
    l_rec big_table%rowtype;
begin
    for x in ( select id from big_table )
    loop
        select * into l_rec from big_Table where id = x.id;
        select * into l_rec from big_Table_compressed where id = x.id;
    end loop;
end;

that is, 1,000,000 index lookups into the tables:

SELECT *
from
 big_Table where id = :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute 1000000    105.98     117.37          0          0          0
0
Fetch   1000000     87.06      98.81      12994    4000000          0
1000000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   2000001    193.05     216.18      12994    4000000          0
1000000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 64     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
1000000  TABLE ACCESS BY INDEX ROWID BIG_TABLE (cr=4000000 r=12994 w=0
time=77622071 us)
1000000   INDEX UNIQUE SCAN BIG_TABLE_PK (cr=3000000 r=0 w=0 time=41014618
us)(object id 30637)

********************************************************************************

SELECT *
from
 big_Table_compressed where id = :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute 1000000     91.84     102.51          0          0          0
0
Fetch   1000000     93.76     108.75       4129    4000000          0
1000000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   2000001    185.60     211.26       4129    4000000          0
1000000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 64     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
1000000  TABLE ACCESS BY INDEX ROWID BIG_TABLE_COMPRESSED (cr=4000000 r=4129 w=0
time=86706480 us)
1000000   INDEX UNIQUE SCAN BTC_PK (cr=3000000 r=2091 w=0 time=47275461
us)(object id 33636)

once again, comparable -- no huge difference one way or the other (although
the compressed table was much nicer on my buffer cache I suppose, got more in
there)

Now trying some sorts/ aggregates:

select *
from
 big_table order by owner

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch    66668     19.53      25.85      26094      13021         21     1000000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    66670     19.53      25.85      26094      13021         21     1000000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 64

Rows     Row Source Operation
-------  ---------------------------------------------------
1000000  SORT ORDER BY (cr=13021 r=26094 w=14523 time=18193085 us)
1000000   TABLE ACCESS FULL BIG_TABLE (cr=13021 r=11571 w=0 time=3664604 us)

********************************************************************************

select *
from
 big_table_compressed order by owner

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch    66668     14.75      18.57      14525       1680          2     1000000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    66670     14.76      18.58      14525       1680          2     1000000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 64

Rows     Row Source Operation
-------  ---------------------------------------------------
1000000  SORT ORDER BY (cr=1680 r=14525 w=14524 time=13191888 us)
1000000   TABLE ACCESS FULL BIG_TABLE_COMPRESSED (cr=1680 r=1 w=0 time=2533512
us)

again, comparable (and the compressed table is faster)

select owner, count(*)
from
 big_table group by owner

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        3      4.31       5.81      11560      13021          0          29
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      4.32       5.82      11560      13021          0          29

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 64

Rows     Row Source Operation
-------  ---------------------------------------------------
     29  SORT GROUP BY (cr=13021 r=11560 w=0 time=5809966 us)
1000000   TABLE ACCESS FULL BIG_TABLE (cr=13021 r=11560 w=0 time=2447794 us)

********************************************************************************

select owner, count(*)
from
 big_table_compressed group by owner

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        3      3.38       3.74         39       1680          0          29
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      3.38       3.75         39       1680          0          29

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 64

Rows     Row Source Operation
-------  ---------------------------------------------------
     29  SORT GROUP BY (cr=1680 r=39 w=0 time=3749106 us)
1000000   TABLE ACCESS FULL BIG_TABLE_COMPRESSED (cr=1680 r=39 w=0 time=1919768
us)

same thing...

select *
from
 big_table where object_name like 'A%'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     2150      1.62       2.36      11546      15143          0       32232
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2152      1.63       2.37      11546      15143          0       32232

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 64

Rows     Row Source Operation
-------  ---------------------------------------------------
  32232  TABLE ACCESS FULL BIG_TABLE (cr=15143 r=11546 w=0 time=2141337 us)

********************************************************************************

select *
from
 big_table_compressed where object_name like 'A%'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     2150      0.89       1.01         76       3824          0       32232
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2152      0.89       1.01         76       3824          0       32232

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 64

Rows     Row Source Operation
-------  ---------------------------------------------------
  32232  TABLE ACCESS FULL OBJ#(33635) (cr=3824 r=76 w=0 time=834673 us)

and again

So, the reason I didn't say "but beware, it is much much slower is because I
simply do not see that, our TPC-H's do not see that.

If you have a reproducible test case, something I can see myself, I'd be glad to
take a look at it and see if I cannot explain it.

However, I would say -- it should go without saying -- that everything,
EVERYTHING, needs to be benchmarked on your systems.  This goes without saying
(one would hope).  You would test this feature, you would benchmark it, you
would evaluate it.

I do try to present a balanced view of things here.  I am not in the business of
promoting features for features sake -- but rather making the experience using
Oracle stuff "better".  It would no benefit me to have people not be informed...
 I do try to go out of my way to caveat things (if you have my book Expert one
on one Oracle for example -- you would find most chapters with a "caveats"
section)

Review &
      Followup

      Rating: 4
historical
      partitioning is the solution for many problems  January 17,
      2003
Reviewer:  volkmar.buehringer  from germany

this feature fits nice in the usage of historical partitioning

- from time to time all old date is packed in a historical partition with
compress turned on

it is important to mention that with the right sorting order
you can improve the compression ratio, and the more
old data with the same base key you have the more you can compress
( also sorting can improve the clustering ratio of
your indexes )

so tables would consist of a recent partition
with large pctfree for updates
and a compressed historical partition ( with no updates )

indexes are local with pctfree 0 rebuild on old partition
(there are no changes )
and broken up indexes on new partition

this setup not only saves a lot of disks, speeds up
queries of old data ( think of clustering for indexes )
better cache utilisation ...
and also speeds up insertion of new data, because
indexes of recent partition are smaller and so better
cached

from time to time you have to rebuild this setup to
put data from recent partition into historical partition

compared to most useless reorganization this
really helps

      Rating: 5
Resource
      Tradeoff.  January 17, 2003
Reviewer:  Jer
      Smith  from Salt Lake City, UT
Whether it's compression on the filesystem level for your applications,
compression in network protocols, or compression on your database tables, you're
making a trade-off.

The way I always looked at it is that compression offloads additional work from
the disk onto the CPU (and maybe RAM).  In the example, you're reading a whole
lot fewer blocks off disk when your reading the compressed table--to make up for
it, at some point, those need to be decompressed, which takes CPU work.
If you've got a slow disk, but plenty of CPU to spare, you should benefit by
compression.  If you've got a lightning fast 15K RPM disk array and a 486, you'd
be a fool to compress.  (well, you are already, but...)

What I find interesting is that the evidence from the examples here don't
illustrate my theory.  Each of the compressed queries show _less_ CPU time for
the compressed tables.  I'd guess that either the CPU time due to decompression
isn't included in the stats, or the CPU used by reading the disk is greater than
from doing the compression (which would depend on your system, I would guess).
Any thoughts?

Also another question related to this: you mentioned the compressed table being
nicer on your buffer cache--so it's stored in there still compressed?  Does that
mean we have to go through decompressing the data every time?

          Followup:

the cpu time to do the compression is there -- it is all there.

We have a "low bandwidth" compression algorithm.  A simple symbol table lookup.
It is not binary compression ala 'zip' or 'gzip' (which is hugely costly).  It
is a symbol table lookup which is very fast.

You are seeing the side effect of not having to latch, relatch, get, reget the
blocks over and over again.

The block is stored "compressed" on disk and in the cache.

One of the reasons I did things like:

select * from big_table_compressed

and

select * from big_table_compressed where id = :x

was to drive home that this stuff doesn't carry a 200 or 300% overhead -- no
way.  We decompressed each block (more then once) for the select * from
big_table_compressed -- we decompressed each block many times for the keyed
read....

      Rating: 5
In depth
      analysis - Excellent!  January 17, 2003
Reviewer:
      Paul D. Phillips  from Orlando, FL
I hope I didn't come across as being critical; in my testing, (which, of course,
I have since purged) the run times were very different.  I believed that it was
going to be encountered by others, also.

I will need to rerun my tests, obviously.  I'll try to share what I find with
you.   For me, though, it will be difficult to re-setup the tests, I used a much
larger amount of data.  Regardless, I appreciate your "rapid reaction".  I was
very disappointed in the results.  Now it seems the COMPRESS answer may be the
one I was originally hoping for - smaller and as efficient.

      Rating: 5
Why is it
      optional  January 18, 2003
Reviewer:  Frank
       from NL
Not having read the original article, I do not see any cons in your tests here.
Why did Oracle not make it the default setting ?

          Followup:

it only works on BULK operations

o insert /*+ append */
o create table as select
o direct path loads
o moves

it is for read mostly/read only data.

it will impact the speed of the load to a degree.

it would change the default -- something which we are always hesistant to do --
and with this one, since it is specically and only a data warehouse feature, I
don't see it becoming the default.

The article is online at:

http://otn.oracle.com/oramag/oracle/03-jan/o13asktom.html

      Rating:
      5
  June 17, 2003
Reviewer:
      A reader
Hi Tom,

 Is compress table is good for Datawarehose only? Can we use it in OLTP
applications?

 Our DBA is telling us "update" is very costly for compress table. Reason is it
has to read data,uncomress it,update data and again comress it and save to disk.

 Could you pls. through some light(update) on this ?

 Thanks

          Followup:

DBA is right but for all of the wrong reasons.

Table compression works only for BULK direct path type operations:

alter table move
create table as select
insert /*+ APPEND */
direct path sqlldr

if you use insert/update/delete -- the modified data IS NOT COMPRESSED.  If you
start with a large compressed table and update each row -- each row will be
decompressed and stored decompressed (that is the "costly" part)

Now, if you have say an AUDIT TRAIL in this OLTP database that is partitioned by
say MONTH and you must keep say 3 years online you can:

a) alter the table to be compress -- NO IMPACT on any inserted data, no overhead
here.

b) at the end of the month, create next months partition -- just like you do now

c) alter table move partition -- move last months partition, this'll COMPRESS it
as well (big bulky moves)

so, you can use this in OLTP systems -- you just apply it to READ MOSTLY or READ
ONLY data.  Using partitioning and other techniques makes this very feasible.

      Rating:
      4
  June 17, 2003
Reviewer:  Dave
       from Colorado
Let me mention one of the "cons" of compressed tables-- you can neither add nor
delete columns from a compressed table.

      Rating:
      5
  June 18, 2003
Reviewer:
      A reader
Why is it so Tom?

          Followup:

if you are talking about the followup right above -- it is currently a
limitation of a compressed table.  "it just is", the block format is very
different.

It is trivial to "drop" a column (at least set unused).  You would simply use a
view (rename T to T_TBL, create view T as select a,b,d,e from t_tbl -- column C
is now "gone")

      Rating: 5
Is there way to
      to estimated space saving from compression without actually compressing
      the table.  November 05, 2003
Reviewer:
      Ajeet  from OH.USA
Hi Tom,
I have read allmost all I could find on compression including your book.Very
nice feature.I have a big warehouse having Large tables and I am compressing
them one by way.I want to collect data on space saved.Is there a way I can
actually tell the difference in space usage between a compress and noncompressed
table without actually compressing the tables.The reason I asked this -- that my
test environment has alot less data then production --so actually I have to copy
production to test and then compress and then measures the savings of space..or
query performance.
I do think this question is not logical but I thought you may tell me a way..as
you always do.

Thanks
Ajeet

          Followup:

you have to compress the table to measure the effect.

      Rating:
      4
drawbacks  November 06, 2003
Reviewer:
      A reader
here

http://www.dbasupport.com/forums/showthread.php?s=20fdc443a085f163b54fb6b415d80964&threadid=38090

      Rating: 5
Re: A
      Reader  November 07, 2003
Reviewer:  Dushan
       from Czech rep.
Regarding:

http://www.dbasupport.com/forums/showthread.php?s=20fdc443a085f163b54fb6b415d80964&threadid=38090

I have tested bitmap index on compressed table (9.2.0.4, win2000), no problem
occured.
And, anyway, tablespace of size 32K? Hmmmm!

November 06, 2003 shalini -- Thanks for the question regarding
      "sqlplus command line usage", version oracle 8i rel 2

      You Asked
Hi Tom,

I am calling sqlplus from a batch file ..
I want to call sqlplus and execute a packaged procedure in 1
line.. Is it possible ?
currently i am calling a .sql file as :

sqlplus scott/tiger @upd1.sql

wht i want to do is something like this :

sqlplus scott/tiger @(or exec)packgname.procname

please help

thanks in advance

Regards
Shalini

      and we said...
You can probably use ECHO for this on NT:

C:\>echo exec dbms_output.put_line( 'Hello' ); | sqlplus
scott/tiger@ora8i.us.oracle.com

SQL*Plus: Release 8.1.6.0.0 - Production on Mon Aug 21 16:57:43
2000

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Enterprise Edition Release 8.1.5.0.0 - Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.0.0 - Production

scott@8i> Hello

PL/SQL procedure successfully completed.

scott@8i> Disconnected from Oracle8i Enterprise Edition Release
8.1.5.0.0 - Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.0.0 - Production

C:\>

On Unix, it is much easier:

#!/bin/csh -f

sqlplus scott/tiger <<EOF
exec dbms_output.put_line( 'Hello' );
EOF

(but the echo trick works there as well)

Review &
      Followup

      Rating:
      5
password  March 25, 2003
Reviewer:
      A reader
tom,  I want to put a password on the package
so when I publish the package for some procedures I want to put the password as
parameter

 how can we do it ?

I have to grant execute to public for this package but one of the procedure is
voletile

I want something like this

package db_util
proceudre diable_all_triggers
-------------------------------
schema_name   varchar2
password      varchar

when they execute from sqlplus
user should not see the password
is it possible ?

 oracle version 8.1.7.0
 os:linux 7.3 ( redhat)

          Followup:

huh? password to "what".

I would not make it password driven, if you need to protect this one procedure,
maintain a table of users that is allowed to use it instead.

you cannot hide "binds"

      Rating:
      5
Thanks!!  March 26, 2003
Reviewer:
      A reader
I just wanted to restrict some users to execute this proc.

without any other work. because it is just a samall utility
I can't create user table in prod. system for this purpose..

 I know in database I am suppose to process the data and not expect too many
interface/security things.

Thanks

          Followup:

then I would suggest you remove the procedure from this package and put it into
its own package and grant execute on that to the proper people.

      Rating:
      5
Thanks..  March 26, 2003
Reviewer:
      A reader
I will do that !!

thanks !!

      Rating: 3
SQL*Plus
      Deprecated in 10i  March 26, 2003
Reviewer:  Tim
      Hall  from UK
Hi.

What are your thoughts about SQL*Plus being deprecated in 10i in favour of
iSQL*Plus?

What's going to be the preferred way to run batch scripts that require an
element of UNIX/Windows scripting in conjunction with SQL*Plus?

Cheers

Tim...

          Followup:

where did you read that?

      Rating:
      5
SQL*Plus  March 26, 2003
Reviewer:  Mark
      A. Williams  from Indianapolis, IN USA
This is only for sqlplusw.exe NOT sqlplus.exe (the command-line version)...
Metalink has a desupport note I think, but I can't recall for certain at the
moment.  There was a recent discussion on c.d.o.s as well.

- Mark

          Followup:

excellent -- i always hated sqlplusw.exe ;)

I've not heard anything about plus going away.

      Rating:
      5
SQL*Plus  March 26, 2003
Reviewer:  Mark
      A. Williams  from Indianapolis, IN USA
Found it...

Here's the doc id: 171870.1

<snip>
The SQL*Plus for Windows graphical user interface (GUI) will be desupported in
future releases of SQL*Plus. The browser-based iSQL*Plus interface will replace
the SQL*Plus for Windows GUI. The SQL*Plus for Windows command line (DOS)
interface will continue to be supported.
</snip>

So, SQL*Plus itself will not go away, just the Windows GUI version.

- Mark

      Rating: 5
RE: i always
      hated sqlplusw.exe...  March 26, 2003
Reviewer:
      Mark A. Williams  from Indianapolis, IN USA
RE: i always hated sqlplusw.exe

yeah, but that was before you upgraded your o/s! :)

- Mark

      Rating: 5
Farewell
      SQL*Plus GUI  March 26, 2003
Reviewer:
      A reader
We will miss you SQL*Plus GUI,
Thanks for the "cut and pastes" which
the DOS (can I use that word on this forum ;) )
cannot...

From those afflicted with Windoze

          Followup:

DOS did the cut and paste in a superior fashion I thought even.

I could easily copy a SQUARE of data from sqlplus in dos.  I could cut the
empno, ename column out -- leaving the other junk there.

      Rating:
      2
  May 01, 2003
Reviewer:
      A reader
hello tom,

how can one get a history of executed SQL commands within SQLPLUS (windows) to
reexecute them once more?

          Followup:

sqlplus keeps only the last one. It is what it is -- a very simple, functional
and invaluable command line tool.

It is all about running scripts.

      Rating:
      2
  May 01, 2003
Reviewer:
      A reader
hey - for some reason I can use cursor keys to get previous executed statements
(in a "doskey fashion") ...

          Followup:

works on dos (try f7 ;)

      Rating: 4
sql
      history  May 01, 2003
Reviewer:  Ashokkumar
       from LA,CA
We can use sqlplus worksheet for previous command history.

      Rating: 5
Run sqlplus in
      Emacs  May 01, 2003
Reviewer:  A reader

... and you will have everything that you desire for a full featured interactive
command line tool. SQL*Plus is a bit rudimentary by itself, I have to say. Lack
of statement history beyond the last one is just one of its shortcomings (even
mysql's doing better in this regard), and the difficulty in editing current
statement is another - all solved by running it in Emacs.

And yes, sure we can all use sqlplus in a DOS window (just to get command line
history with an up arrow key) from MS Windoze, and yeah, I am sure that's what
Larry really wants!

      Rating:
      5
  May 01, 2003
Reviewer:
      A reader
Well there you go... you can cut and paste
in a DOS window...

EMACS can provide a handy interface to
SQL*Plus as mentioned above.

          Followup:

define _editor=vi

that is my solution.  whenever I find myself accidently in emacs -- I always
have to open another window so I can ps and find the pid of emacs and do a kill
-9 on it.

      Rating: 5
A
      addition  May 01, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
It seems that with the next Oracle release, only the support for SQL*Plus GUI on
windows will be removed. The application will still be shipped. Only that
support is being removed and it is being put in the list of deprecated features
(meaning do not count on it being there forever...)

      Rating: 5
how do you get
      sqlplus command line history  May 27, 2003

Reviewer:  A reader
Tom, vi is for editing current statement only, can you get previous command
lines with it? At the minimum, sqlplus should have a bash-like capability.

          Followup:

it does not.

      Rating: 5
buffer edit in
      sqlplus for Linux?  September 10, 2003
Reviewer:
      Sikandar Hayat  from Pindi Bhattian - Pakistan
Hi TOM,
I have installed Oracle 9i R2 on RH 7.2 box and after connecting with sqlplus
unable to get the buffer in the editor. In windows I was doing with "ed". With
"ed" it gets the last sql into the editor but in sqlplus vi is not doing the
same. Which parameter I should use to do it?

          Followup:

SQL> define _editor=vi

i have that in my login.sql

      Rating: 5
I already did
      this  September 11, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
I already did this but it seem to be not working with me. I did  this like,

SQL>define _editor=vi
SQL>vi

but it is doing nothing but if I use the following,

SQL>!vi it opens the vi but without buffer sql statement.

I want to know that after setting this param I have to use just vi at command
line?

Sorry for so basic question as I am new on this platform.

          Followup:

just type "edit"

SQL> define _editor=vi
SQL> edit

      Rating: 5
Very
      nice  September 12, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
Very nice TOM it worked for me. Thanks

      Rating: 5
execute the
      script without supplying parameters on prompt  October 04,
      2003
Reviewer:  A reader
Tom

I want to execute a script like the following ...

@getprocedurecode pr_test

i.e. it should accept the parameter on the same line as the script name, I dont
want to get prompted and then give the parameter. How to achieve this,

Right now when I execute a script it prompts me for the input parameter, and
when I give it it executes.

Thanks in advance.

          Followup:

just use &1 in the script -- if you passed in something, it'll not prompt you

      Rating: 5
&1 Vs
      &tablename  October 04, 2003
Reviewer:
      A reader
&1 does not prompt, but &tablename prompts even when parameter is passed. Is &1
predefined for the purpose of not prompting i.e. is this inherently defined in
sqlplus ?

          Followup:

&1 &2 &3 .....

are the positional parameters

you could:

define &tablename=&1

if you like.

      Rating: 4
How To change
      parameter value For next run...  October 05, 2003

Reviewer:  Vikas Sharma  from Delhi India
Hi Tom,

I have sql file say abc.sql. Which has a insert statment like

insert INTO mytable values (&name,&title);

now i want to run this sql file sveral times on one sql session prompt but i
want that i should be prompt for name and title every time i run.

For example on sql prompt if i do @abc.sql first time i will be prompted for
name and title but if i run second time i will not.

Thanks & Regards

Vikas Sharma

          Followup:

hmmm, you say

"...but i want that I should be prompt(ed) for the name and title every time.."

and

"i will be prompted for name and title but if I run second time I will not"

not sure WHICH behaviour you are looking for -- but we can do either or...
consider:

ops$tkyte@ORA920> @test
ops$tkyte@ORA920>
ops$tkyte@ORA920> select '&name' from dual;
Enter value for name: foo
old   1: select '&name' from dual
new   1: select 'foo' from dual

'FO
---
foo

Note that I used &name in the query -- single ampersand.  rerunning and i
get:

ops$tkyte@ORA920> @test
ops$tkyte@ORA920> set echo on
ops$tkyte@ORA920>
ops$tkyte@ORA920> select '&name' from dual;
Enter value for name: bar
old   1: select '&name' from dual
new   1: select 'bar' from dual

'BA
---
bar

Note that I got prompted again for name -- it did not retain the value.  if I
use &&name however:

ops$tkyte@ORA920> edit test

ops$tkyte@ORA920> @test
ops$tkyte@ORA920> set echo on
ops$tkyte@ORA920>
ops$tkyte@ORA920> select '&&name' from dual;
Enter value for name: foo
old   1: select '&&name' from dual
new   1: select 'foo' from dual

'FO
---
foo

I got prompted the first time....

ops$tkyte@ORA920> @test
ops$tkyte@ORA920> set echo on
ops$tkyte@ORA920>
ops$tkyte@ORA920> select '&&name' from dual;
old   1: select '&&name' from dual
new   1: select 'foo' from dual

'FO
---
foo

ops$tkyte@ORA920>

but not on subsequent executions

so, use &name to get asked each time, &&name to get asked once.

      Rating: 5
How to clear
      the value set for &  October 05, 2003

Reviewer:  A reader
How to clear this value after we are done with executing our select.It is stored
for the entrie session of sqlplus

          Followup:

SQL> undefine foo

      Rating: 5
use UNDEFINE
      command...  October 05, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
SQL> prompt &table
Enter value for table: tab#1
tab#1
SQL> prompt &table
Enter value for table: tab#2
tab#2
SQL>
SQL>
SQL> prompt &table
Enter value for table: tab#1
tab#1
SQL>
SQL> prompt &table
Enter value for table: tab#2
tab#2
SQL>
SQL> prompt &&table
Enter value for table: tab#3
tab#3
SQL>
SQL> prompt &&table
tab#3
SQL>
SQL> undefine table
SQL>
SQL> prompt &&table
Enter value for table: tab#4
tab#4
SQL>
SQL>

      Rating: 5
& and
      'Accept'  October 06, 2003
Reviewer:
      A reader
What is the difference between & and Accept in sqlplus?
Is accept also used for entering a parameter.

          Followup:

accept is used to set an &define_name in a user friendly fashion.  it lets you
prompt "nicely" for a value

&define_name will just ask the user for a value if not is set and then
substitute the value into the statement.

      Rating: 1
"sqlplus
      command line usage" page doesn't tell how to insert an
      ampersand  November 05, 2003
Reviewer:  Randy
      Strauss  from Mountain View, CA
I want, in a batch file to
   insert into country_info ('Antigua & Barbuda');
(this is what the customer needs.)
But & always prompts for input.

I tried && and &&& and \& and /&
and looked in AskTom and 4 books and NOTHING!

Is there a way to input special chars in general, too?
But this isn't for questions.  I'd try the usual route,
but that has been saying "the backlog is too large"
for weeks...  Is there a good forum where I can ask?
-Randy
Thanks, Randy

          Followup:

set define off

i searched for

how to insert ampersand

and found it straight away....

well, about the backlog issue -- it is just a matter of timing, there is only
one of me (really, i answer them all -- even these)....  i've taken lots of new
questions - but anyway -- other great forums:

groups.google.com -- comp.databases.oracle.* groups.  internet usenet news
groups.

otn.oracle.com -- discussion forums

      Rating: 2
A search of the
      net revealed the answer  November 05, 2003

Reviewer:  Randy  from Mtn View, CA
set escape "\";
and then \& works.  Thanks, -r

November 06, 2003 Venkateswara Reddy -- Thanks for the question
      regarding "Regarding Library Cache", version 901

      You Asked
Hi tom

After a long time, I am able to reach you. thanks for your help.

In my database I am getting the following statistics. As per my knowledge this
configuration is fine.

1.
Data Block Buffer Hit Ratio : .989845262
Shared SQL Pool
  Dictionary Hit Ratio      : .992542932
  Shared SQL Buffers (Library Cache)
    Cache Hit Ratio         : .999886764
    Avg. Users/Stmt   : .263392857
    Avg. Executes/Stmt      : 137.360863

2.
Global Name: M12.US.ORACLE.COM
Date Stamp: 2003-11-05 061734
Obj mem: 64078131 bytes
Shared sql: 48670045 bytes
Cursors: 88750 bytes
MTS session: 21070088 bytes
Free memory: 75090840 bytes (71.61M)
Shared pool utilization (total): 112836926 bytes (107.61M)
Shared pool allocation (actual): 100663296 bytes (96M)
Percentage Utilized (does not include recommended 30% overhead): 112%

PL/SQL procedure successfully completed.

3.
> SGA CACHE STATISTICS
> ********************
> SQL Cache Hit rate = 99.99
> Dict Cache Hit rate = 99.25
> Buffer Cache Hit rate = 98.98
> Redo Log space requests = 0
> Total SGA = 298.22
>
> INIT.ORA SETTING
> ****************
> Shared Pool Size = 100663296 Bytes
> DB Block Buffer = 0 Blocks
> Log Buffer = 1048576 Bytes
>

PL/SQL procedure successfully completed.

the above results are fine or not

2. For tuning the database I am using quest centrla tool. Always I am getting
LIbrary cahce miss rate is 67.5%. But I have set my shared pool size is 96MB.
this will suffice my most requirements.

Can you tell me is this ok or any thing I need to set or whether I have to
increse my shared_pool size.

thanks in advance

Reddy

      and we said...
1) hit ratios are meaningless.  totally -- 100% meaningless.

so tell me, which query is better in this discussion:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6749454952894#6760861174154
hmmm, i've a 98% cache hit just like you -- but, is my system "tuned"???  nope,
not a chance.

cache hit ratios are meaningless.

2) you'd have to ask that tool -- if you show me meaningful information, like
that present in a statspack report (nice textual, just the facts, no blinking
red lights because a cache hit ratio dropped below a certain threshold) maybe i
can comment :)

is your cache miss rate due to "you just started up" or due to "i don't use bind
variables" or due to "this is a dev box and we recompile"

Only one of the 3 "example reasons" needs to be fixed (bind variables)
immediately.  the first will take care of itself and the third is unavoidable.

you see -- it depends.

Review &
      Followup

      Rating: 4
Library Cache
      hit ratios  November 06, 2003
Reviewer:
      Venkateswara Reddy  from Bangalore, India`
Hi Tom

thanks for your answer, but in my development system we cannot use primary and
foreign keys ( for few tables) these constraints programmatically we are
handling. for our application we cannot go for fully normalised tables. This may
be reason. Is is it good method or where I should look without changing the
table definition I have to tune my db.

thanks

Reddy

          Followup:

huh? what does that have to do with anything -- i'm missing some context or
something here.

(doing RI in the application -- a custom developed application -- is to me the
worst idea I've ever heard.  well, it's right up there with not using bind
variables I guess)

      Rating: 5
I think doing
      RI in application is worse  November 06, 2003

Reviewer:  Wolfgang Breitling  from Calgary, Canada

I don't think one should compare doing RI in the application and not using bind
variables in terms of "worseness".
For me they are two completely different kinds of sins. Not using bind variables
"only" affects performance and especially scalability, while rolling your own RI
in the application opens the door wide to integrity issues.
In the upgrade project I am working on now we ran into several cases where the
delivered upgrade scripts failed because the assumed referential integrity was
violated - for several reasons, but mostly because of data "fixes" made directly
to the database.

November 06, 2003 Orna -- Thanks for the question regarding "global
      index on hash partitioned table", version 8.1.7

      You Asked
Hi,

in '8i DBA handbook 'it says :

'You cannot create global indexes for hash partitions'

Is that true ?
I created a table with hash partitions and I had no problem creating a global
index for her, and also to use it..

Orna

      and we said...
Correct, i am not aware of any such restriction:

tkyte@TKYTE816> CREATE TABLE hashed
  2  ( empno   int,
  3    ename   varchar2(20)
  4  )
  5  PARTITION BY HASH (empno)
  6  ( partition part_1 ,
  7    partition part_2 )
  8  /

Table created.

tkyte@TKYTE816>
tkyte@TKYTE816> insert into hashed select empno, ename from scott.emp
  2  /

14 rows created.

Execution Plan
----------------------------------------------------------
   0      INSERT STATEMENT Optimizer=CHOOSE (Cost=1 Card=82 Bytes=1640)
   1    0   TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=82 Bytes=1640)

tkyte@TKYTE816>
tkyte@TKYTE816> create index hash_idx on hashed(ename) GLOBAL
  2  /

Index created.

tkyte@TKYTE816>
tkyte@TKYTE816> set autotrace on explain
tkyte@TKYTE816> select * from hashed where ename = 'KING'
  2  /

     EMPNO ENAME
---------- --------------------
      7839 KING

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=2 Bytes=50)
   1    0   TABLE ACCESS (BY GLOBAL INDEX ROWID) OF 'HASHED' (Cost=1 Card=2
   2    1     INDEX (RANGE SCAN) OF 'HASH_IDX' (NON-UNIQUE) (Cost=1 Card=2)

Can only suggest you check out the website for the book and see if anyones filed
an errata sheet against this issue and if not, file one yourself.
Review &
      Followup

      Rating: 5
hash partition
      indexes and range partition indexes  November 05, 2003

Reviewer:  A reader
Hi

I am quite confused when to use local or global indexes. As far as I understand
from the documentation we have following indexes

range partitions we have

create index idx1 on tab1(col1)
local (partition 1 .... ) ===> local index

create index idx1 on tab1(col1)
global partition by range (col1)
(partiton 1 ....)  ===> global partitioned index

create index idx1 on tab1(col1)
global ===> Plain simple index (even with word global) because it does not
appear in user_part_indexes

I dont understand very well when we should use each of them when using range
partitioning. In DWH local for sure and in OLTP which of these 3?

In hash partitioning we have following index partition types

create index idx1 on tab1(col1)
local (partition 1 .... ) ===> local index, this local index can have different
number of partitions not as range when we use local index we have same number of
partitions as table partitions... How so? Doesnt this local index looks more
like range partitioning global partitioned index?

create index idx1 on tab1(col1) global ===> this is a simple index as create
index idx1 on tab1(col1), there are no difference beween these 2

When use hash partitioning what type index should we use?

I have read your book about global and local indexes (seems like you only talked
about range partitions situations), it says in OLTP we should want to use global
index (does not say partitioned or not partitioned?) because we can reduce index
probes. I dont understand why they are reduced? Can you explain?

For example if I partition emp on hiredate and index empno column

hiredate 200301 - 200302 in partition 1
hiredate 200302 - 200303 in partition 2
hiredate 200303 - 200304 in partition 3
hiredate 200304 - 200305 in partition 4

if my query is

select * from emp
where hiredate between 200301 and 200302
and empno between 1 and 1000;

I understand this query would use partition elimination

if I run this query

select * from emp
where empno between 1 and 1000;

This would use the index on empno, now I dont see why type of query would
increase index probes? Can you give an example based on this table?

          Followup:

(only range partitioned global indexes were supported!)

do you have my new book "Effective Oracle By Design"

I covered this exact topic in some detail in the partitioning chapter....

As for the last question about the "increased index probes"

you ahve 4 partitions.  assuming a locally partitioned index, you have 4 index
partitions as well.

Now, that second query only uses EMPNO. there are 4 index partitions for the
index on EMPNO.  empno =1 can be in ANY one (or all, or some, or none) of the 4
partitions.  We must inspect them ALL.

Now, if you have 4 table partitions partitioned by hiredate AND an index on
empno that was range partitioned such that:

empnos < 5000 -> part 1
empnos < 10000 -> part 2
.......

and issued that second query -- we could easily discover "ah hah, only index
partition part1 is relevant, lets do one index range scan of one partition"

      Rating: 5
scan
      speed  November 05, 2003
Reviewer:
      A reader
scanning a single partition of 100mb is faster than scanning 4 partitions of
25mb each?

          Followup:

is it?

the answer is

yes sometimes
no sometimes
maybe othertimes

      Rating: 5
confused with
      this sentence  November 06, 2003
Reviewer:
      A reader
Hi in previous post you said

You have 4 partitions.  assuming a locally partitioned index, you have 4 index
partitions as well.  ---> which index partitions are we talking about here? The
one on empno column? Is that globally partitioned or locally?

Now, that second query only uses EMPNO. there are 4 index partitions for the
index on EMPNO. empno = 1 can be in ANY one (or all, or some, or none) of the 4
partitions.  We must inspect them ALL.

Are we assuming here the index on empno, lets call it empno_idx, is locally
non-prefixed partitioned or locally prefixed partitioned? I guess it is
non-prefixed since hiredate does not form part of this index.

If empno_idx is locally non-prefixed partitioned -- we could easily discover "ah
hah, only index partition part1 is relevant when we need to query when empno =
1.

          Followup:

i said "assuming a locally partitioned index" and we were talking about the only
indexed column empno.  so it would be the 4 index partitions on empno, yes.  it
is locally partitioned (part of the assumption statement)

i'm assuming the index was

a) locally partitioned
b) on empno

since we did not partition by empno, but by hiredate, and hiredate is not in the
index -- it is locally partitioned, non-prefixed (if it were prefixed with the
partition key, this discussion is moot, the index would be (hiredate,empno) and
would in all likelyhood NOT be used for "where empno ...." alone

how pray tell could we "easily discover" that last "fact"?  hiredate isn't part
of the predicate!

if you queried "where hiredate = :x and empno = 1" then sure, it would.  but the
query is "empno = 1" and empno = 1 could have been hired on ANY date, hence it
could be in any of the prefixed index partitions

      Rating: 5
oops make a
      pardon about this setence  November 06, 2003

Reviewer:  A reader
Hi just read locally in your post about this setence

You have 4 partitions.  assuming a locally partitioned index, you have 4 index
partitions as well.  ---> which index partitions are we talking about here? The
one on empno column? Is that globally partitioned or locally?

Please ignore "The one on empno column? Is that globally partitioned or
locally?"

November 06, 2003 -- Thanks for the question regarding "local index
      on OLTP partitioned table", version 8.1.7.4

      You Asked
Hi

I have a quite big table in an OLTP database, the requirement is to keep current
week data only in the database and keep 2 years data outside. I have thought
about partition the table by a column called year_week_no then export the
previous week partition data then truncate or drop the previous week partition
achieveing the requirement

I think the table should be globally indexed but we have several queries which
bases on week number as well something like

WHERE year_week_no = :B1
and contract_id = :C1

Can I locally prefix index that column which is the partition key? Or it would
affect the queries performance? In above query for example I would global index
contract_id and local index week_no.

I am wondering because many people tell me partitioned table and OLTP databases
dont go together very well

thank you

      and we said...
" partitioned table and OLTP databases
dont go together very well"

myth... total myth.  The true statement is

"poorly designed physical schemas and OLTP databases don't go together very
well"

Many people have heard "partitioning is like setting fast=true, just turn it on
and watch!".  Then, they just "turn it on" and watch their system run slower.
Why?  Because they didn't turn it in in a fashion that suited their system.  Eg:
they would partition a table by DATE (as you are) and then locally partition all
indexes (by date).  Problem -- queries didn't always (or even usually) use DATE
hence no partition elimination.  Now, (supposing they did 10 partitions for
example) their simple "select * from EMP where empno = :x" has to do 10 index
range scans in order to find that record instead of 1 index range scan.  Boy,
did you just increase the workload on your system or what?.  Now, if they had
used a global index on EMP partitioned by EMPNO (either all in one partition or
by range on empno to spread it out) they may well increase performance

o index smaller, maybe height smaller, less IO
o we have N indexes -- higher concurrency on inserts -- less segment level
  contention

So, the question is -- can global indexes fit into your plans?  For OLTP it
almost has to.  In 9i you can do partition level operations AND maintain global
indexes -- in 8i and before, you cannot (that drop partition would imply down
time with global indexes -- in order to rebuild the index)

Now, in your case, if the vast preponderance of queries all have:

  where year_week_no = :b1 and ....

in them, and you partition the tables AND indexes by that -- you'll find those
queries run at the same speed, if not a bit faster.  partition elimination at
the index level kicks in, one index partition (one SMALLER index partition).
Just like now.

It will be the queries that do NOT use year_week_no - they will have to index
range scan N partitions.  But in your case it sounds like there will only ever
be ONE partition in there at any point in time -- so this whole discussion is
probably "moot" -- it would be the same as not having partitions performance
wise.  You would use local indexes in order to avoid having the indexes go
invalid when you added next weeks partition and when you dropped last weeks
data.

Review &
      Followup

      Rating: 4
doesnt this
      sound contradictory  November 18, 2002
Reviewer:
      A reader
Hi

You said

"Now, if they had used a global index on EMP partitioned by EMPNO (either all in
one partition or by range on empno to spread it out) they may well increase
performance"

"It will be the queries that do NOT use year_week_no - they will have to index
range scan N partitions"

In the second quote it seems that you mean queries not using year_week_no would
not gain performance even the indexes involved are global, doesnt sound
consistent with first quote.... :-?

          Followup:

when you take two sentences, from two different paragraphs and put them next to
each -- sure they can sound contradictory.

sentence one comes from a paragraph describing that if you partition a table by
DATE but query by EMPNO -- you should globally partition the index by EMPNO
perhaps -- else you'll pay the penalty of N index range scans for each index
access.

sentence two comes from a much later paragraph that starts off with:

...
Now, in your case, if the vast preponderance of queries all have:

  where year_week_no = :b1 and ....

in them, and you partition the tables AND indexes by that
.......

point is: local and global indexes are both good, both useful, both have their
place.  Understand them or just don't use partitioning cause you'll totally
botch everything up.  The sentences don't contradict eachother -- they just
apply in wholly different cases.

      Rating: 4
partition table
      just to speed up data purge  November 20, 2002

Reviewer:  A reader
Hi

Is it feasible to partition a table by date to speed up data purge?

We have a 50 millions rows table in an OLTP database (CRM)from which we have to
delete 20 millions of old customer cases, this table has around 66 columns, 16
indexes which 2 of them are compositely indexed (3 columns each), as you can
imagine deleting this table is extremely slow, 80000 rows takes 20 minutes so
roughly calculating it would take around 3 days to delete 20 millions of rows.

Since the delete statement is

delete from XXX where end_date between X and Y

note: end_date is indexed and used during this delete

we have been thinking partition by that field and then drop the corresponding
partitions.

My question is, in an OLTP database (highly intensive database) if we partition
such table and create all old indexes as global indexes (to avoid scanning of
several local index structures) with zero local index would this affect normal
transactions performance?

Very few queries use end_date in WHERE clause

          Followup:

well, the global indexes in 8i and before would go invalide during the "purge"
and would have to be rebuilt.

Can you afford the downtime?

In 9i, you can maintain the global indexes during the purge.  Will not be "as
fast" but no index rebuild needed (and should be faster then a delete but I
haven't tested how much faster)

      Rating: 5
downtime is
      affordable  November 21, 2002
Reviewer:
      A reader
Hi

I reckon in this case downtime is affordable although I think we dont need to
bring database down, we would rebuild the indexes online with parallel degree
and nologging, I doubt rebuild index would take three days :D

So I guess this may be a approach :-?

          Followup:

correct -- downtime here means "your OLTP application isn't working whilst this
is happening"

      Rating: 3
Which index is
      best  November 25, 2002
Reviewer:  Balaji CN
       from Chennai India
Hi Tom
I have a basic doubt.
I have a global index on a partitioned table Say a date field Hire_dt.
I drop the Global index and create a local index on the same column Hire_dt.
Which one would give a better performance Global index or Local index.Assume I
have a min of 1 Million records in the table.

Regards

          Followup:

You know what -- if one always gave better performance then the other in all
circumstances, why in the world would we have bothered inventing the other
type???

This is an unanswerable question given the amount of information we have here.

What you need to do is understand the characteristics, the functionality of, the
structure of the global index vs the local index.

Suppose you do queries like:

select count(*) from t where hire_dt between A and B

suppose further that you had partitioned such that A is in one partition and B
is in another.  Suppose this query is executed thousands of times per day.

Now, I might want a single global index so that I do a SINGLE index range scan
in order to count those records.  Otherwise when I did that query -- I would
have to perform AT LEAST two index range scans and perhaps more then 2 depending
on the range in order to answer the question.

Suppose instead you do queries like:

select * from t where hire_dt = A

Now, a local index would be a nice thing.  I'll have N indexes -- I'll be
spreading my workload out (we'll have less people banging on each local index
partition then we would if there were just one global index partition).

Suppose it is crucial to be able to age data out by hire_dt (eg: anyone hired
more then 7 years ago -- get rid of them).  Now, when I do the alter table drop
partition -- all global indexes (in 8i and before) will go invalid -- my system
is basically offline till I rebuild.  If I used local indexes -- not so.

And so on.  Partitioning is a tool, a very nice tool.  You have to look at what
your primary goals are, how your system uses the data -- and then (and only
then) can you begin to pick the partitioning scheme that will work nicely for
you!

      Rating: 5
local vs
      global  November 06, 2003
Reviewer:
      A reader
Hi Mr Kyte

What is better for optimizer,

CREATE INDEX SALES_TIME_BIX ON SALES
(TIME_ID)
LOCAL (
  PARTITION SALES_1995
    NOLOGGING
    TABLESPACE SH_DATA_01,
  PARTITION SALES_1996
    NOLOGGING
    TABLESPACE SH_DATA_01
)

OR

CREATE INDEX SALES_TIME_BIX ON SALES (TIME_ID)
GLOBAL PARTITION BY RANGE(TIME_ID)
(
         PARTITION SALES_IDX_1995 VALUES LESS THAN
                (TO_DATE('01-JAN-1996','DD-MON-YYYY','NLS_DATE_LANGUAGE =
American')),
         PARTITION SALES_IDX_1996 VALUES LESS THAN
                (TO_DATE('01-JAN-1997','DD-MON-YYYY','NLS_DATE_LANGUAGE =
American')),
         PARTITION SALES_IDX_MAXVALUE VALUES LESS THAN
                (MAXVALUE)
)

Or they are both the same just that local is more manageable?

          Followup:

the answers are

the first one
the second one
neither
both
it depends.

they are apples and toaster ovens.  you need to understand the "physics" here.
how the data will be stored.

suggestion - on a white board -- draw pictures.  Understand what would be in the
table partitions.  then above that, draw out what would be in the index
partitions for the local one, then below it, draw out what would be in the
global index partitions.

Then, run some predicates by it and see if it would be able to partition
eliminate on the top or the bottom or neither or both.

neither is more manageable in every case.
neither is better in every case.

      Rating: 5
I did draw on a
      paper  November 06, 2003
Reviewer:
      A reader
suggestion - on a white board -- draw pictures.  Understand what would be in the

table partitions.  then above that, draw out what would be in the index
partitions for the local one, then below it, draw out what would be in the
global index partitions.

Hi Mr Kyte

I drawed this on paper, local indexes I understand perfectly. The thing is with
global paratitioned range indexes. If I globally partition the index into same
number (and same criteria) of partitions as table partitions I dont see what is
the difference between local and global!

November 06, 2003 Puneet -- Thanks for the question regarding
      "Profile", version 8.1.5.0.0

      You Asked
Dear Tom
Hello and a very Good Afternoon.
My problem......

1. I've created a Profile for my Materials System by the name
MMSPROFILE in which I've given the IDLE TIME as 20 Mins.
2. When I create any new users I connect user to this Profile.
3. I checked it and found that even after attaching the profile
the user doesn't get disconnected from the system even if he's
not accessing the Database for more than 20mins.
4. What can be the probable cause and remedy.
5. I'm also interested in knowing that suppose this Profile
starts working and the user after 20mins. get Disconnected then
how to trap this event and tell the user that he's been
Disconnected from the system at the Front End. My Front End is
Developer/2000 Forms 5.x

Thanks
With Warm Regards
Puneet Mathur

      and we said...
what is your init.ora parameter resource_limit set to?  If FALSE
(the default) profiles are not enforced.  Only when set to TRUE
will it work.

the way the idle time logout works is:

o use logs in.  session status will be active or inactive in
V$SESSION

o after X minutes in the inactive status, their session will be
KILLED.  Their status in v$session will be turned into KILLED.
All locks and resources they had will be released but their
CONNECTION will remain.

o the very next time the client contacts the database, they will
receive the error message "your session has been killed".  At
that point in time, their entry in V$SESSION will go away. (but
not before that).

So, that is how the client will be notified, as soon as they
attempt to use the database, the database will tell them "sorry,
you've been logged out".  Then and only then their physical
connection to the database will "go away".  Prior to that, they
are effectively logged out (no longer holding locks and such)
but still connected.
Review & Followup

      Rating:
      4
Review  October 24, 2002
Reviewer:
      Irfan  from Kuwait
In oracle 9i, I set idle_time=30 in profile. after 30 minutes I checked the
v$session, I found SNIPED under the status column.
It remained on SNIPED for hours. SNIPED status only vanishes when user try to
use same session.

1. What if user does not access that session again. Will it remain marked as
SNIPED forever?
2. Does that SNIPED session has released the resourses it was holding?
3. Will SNIPED session be count against the session parameter in init.ora
4. How much performance will go down when we make RESOURSE_LIMIT=TRUE

          Followup:

1) yes
2) yes
3) yes
4) ask yourself how much will your systems requirements be affected if you
DON'T.  The overhead is marginal.

      Rating: 3
how profile
      limits resource usage connected via ACCESS  February 24,
      2003
Reviewer:  June  from MD, USA
Hi Tom,

we set up adhoc_profile for our business user. If it is connected to Oracle
database via SQL*PLUS or TOAD, there is no problem to do some query. However for
some of the user, they only know ACCESS and connect to DB from there using 'get
external table' then 'link tables'. When the user is able to link all tables he
is granted to, he couldn't open any one of them, instead he received the error
msg of 'ORA-02395:...' due to the limit we set up.

my question is: we know he is able to at least query those small tables via
SQL*plus, when connected via ACCESS, how is the resource limit hit even he is
linking to the tables? how should we set up meaningful limits in this case? with
unlimited resource setting, sure there will be no problem.

and is there any guideline how to set up logical_reads_per_call?

the setting for adhoc_profile is:
composite_limit  300750
cpu_per_call     3000
logical_reads_per_call 30500
idle_time        60

Thanks in advance.

          Followup:

access is quite the inefficient tool.  It generally feels "database not smart,
have to pull all data from all tables back to client and do work there"

I'm not an access "tuner", never used the tool -- but see if there isn't a way
to tell access "database is pretty smart, just let it process the query"

      Rating: 2
sniped status
      in V$session  May 13, 2003
Reviewer:
      eashwar  from Uganda - East Africa
after setting the idle_time in the profile , i have experience the same 'SNIPED'
status for various sessions.

so i reverted back, better the sessions be 'INACTIVE' than 'SNIPED' either ways
i will have to re-start the instance to re-set session limit count.

thanks

          Followup:

why is it "better".

do this:

lock table emp in exclusive mode;
go away.

Now, leave that inactive, make sure to use a table that lots of people want to
use.  Then tell me which is "better"

      Rating:
      4
shutdown  May 13, 2003
Reviewer:  Chris
      Major  from Hungary
How does shutdown normal/immediate handle these killed/sniped sessions?

          Followup:

shutdown immediate will shutdown.  shutdown normal will wait for the killed
session to acknowledge it was killed and then shutdown (hence, don't shutdown
normal)

      Rating: 4
ACCESS and
      profile  May 13, 2003
Reviewer:  Dana Day
       from Phoenix, AZ USA
I believe the answer to the ACCESS and profile conflict lies in the way ACCESS
processes.
It usually uses a "fetch a row at a time", rather than batch fetch. So LIO's go
way up, exceeding the limit of the profile. TOAD/SQL*PLUS use higher fetch
counts and don't run into the limit.

      Rating: 4
Processes vs.
      Sessions  November 06, 2003
Reviewer:  Doug
      Wingate  from New Orleans, LA USA
As I understand you, rows in V$Session marked either as SNIPED or KILLED remain
in V$Session until there's further communication from the client.  I also
understand that locks and some other unspecified resources are released.  What
happens to the server process itself, assuming all server processes are
dedicated?  If the server process persists, what about its PGA resources? Are
any of those released or are they held by the server process until it goes away?
 Do any of the answers to these questions differ according to whether the
session is SNIPED or KILLED?

Thanks so much for your amazing efforts on this site.

          Followup:

server process stays.  the pga resources in 9i would be dynamically released
(using pga_aggregate_target) or just paged out by the OS (use it or have it
paged out)

November 06, 2003 richard -- Thanks for the question regarding
      "v$sqltext issues", version 8.1.5

      You Asked
Tom,

I'd like to see the complete sql_text of statements in the shared pool.
v$sql and v$sqlarea only show the first 1000 bytes, but v_$sqltext has the
entire statement in 64 byte chunks. I can get what I want with either of

   select piece, sql_text
   from sys.v_$sqltext
   where address = upper('&address')
   order by piece;

   or

   select piece, sql_text
   from sys.v_$sqltext
   where hash_value = &hash_value
   order by piece;

It thus appears that each of hash_value and address (which have different
values) uniquely identify a statement in the shared pool. Can you explain the
meaning of each of these and why Oracle need both?

Also for some reason the hash_value version of the above gets into an
infinite loop on my windows 2000 professional, 8.1.5 system. Do you happen to
know anything about this? The address version works just fine.

Thanks

Richard

      and we said...
The address is a pointer into the SGA (where the statement physically exists).
The HASH value is how you find the statement in the shared pool when you submit
it.  That is, you sumbit "select * from emp" to Oracle, we HASH it -- find the
statement (and hence the address of the statement) and then can goto that
statement.

You can use either to pull up the sql_text from v$sqltext in general however....
 address will be "safer" solely due to the fact that a HASH value for two
different statements might be the same (that is a side effect of a HASH function
-- "select * from emp" and "select count(*) from dual" might hash to the same
value.  their ADDRESSES will be different however).

8.1.5 never was and never will be supported or supportable on Win2k -- there are
definite known issues on that new OS that was developed long after the release
of 815 that prevent it from functioning normally.  The only certified, supported
releases of Oracle on windows/2000 are Oracle8i release 2 and up (and in fact,
only 817 is supported).  You will find many "quirks" with 8.1.5 on win/2k.

In any case, ADDRESS is more appropriate as it will be unique in v$sql.

Review &
      Followup

      Rating: 5
cleared up a
      mystery that puzzled me for a long time  September 23, 2001

Reviewer:  richard gostanian  from salem, nh

      Rating: 4
A
      Reader  September 24, 2001
Reviewer:  A
      Reader  from USA
Some authors in their books, suggest using
the (address, hash_value) pair to query v$sqlarea ...

My interpretation is: When one queries the v$session and
grabs the sql_address and sql_hash_value. By the time he
queries the v$sqlarea, the sql statement might have been
swapped out and the address might have been assigned to another SQL statement.
(address, hash_value) pair will
validate the SQL statement by two parameters where as
address validates by one

What is your opinion on this

Thanks.

          Followup:

Theoretically possible but highly unlikely.  Things are aged out on a LRU and
since the address was very recently "used", it'll be one of the last candidates
for aging.

Let's put it this way -- address & hash together would definitely work and be
safer yet they either address or hash alone.

      Rating: 3
Analyzing
      v$sqltext  March 24, 2003
Reviewer:  Robert
       from Memphis, USA
Tom,

Hope this is on subject here... I searched the sight but did not see this
problem addressed, and it seemed logical to post it as a 'follow-up' here...

I want to use v$sqltext to analyze sql usage.
I need v$sqltext vs. v$sqlarea, in case the queries are > 1000 characters.

Here is my requirement
* Find all sql using a particular table.

Here is my issues with v$sqltext
* The text goes into v$sqltext.sql_text in 64 byte chunks. It is possible that
words (e.g. my table name) could be split in two between the chunks.

My question
* Do you have a sql trick for stringing the v$sqltext.sql_text fields together
so you can search for a word even if the word spans 2 sql_text rows?

Thanks,

Robert.

          Followup:

You cannot satisfy

"* Find all sql using a particular table."

from any V$ table.  Stuff comes in, stuff goes out.

You don't have a version -- but fine grained auditing in 9i does that.

You can use

v$sqltext_with_newlines, write a function that glues them all together, or

select *
  from (
select address, hash_value,
       lag(sql_text) over (partition by address, hash_value order by piece) ||
       sql_text ||
       lead(sql_text) over (partition by address, hash_value order by piece)
          sql_text
  from v$sqltext_with_newlines
       )
 where upper(sql_text) like '%EMP%'
/

which puts together at least three chunks -- each bigger then an identifier --
so it should catch them all.

w

      Rating: 5
That's
      it!  March 24, 2003
Reviewer:  Robert
       from Memphis, USA
Tom,

That's what I was looking for...... analitic function: LAG
Thanks!

(running 8.1.7.4)

Also, I understand limitation on v$sql, but I figure I can capture most
important queries if I take snapshot of this table every hour. ....Can't I?

Thanks again,

Robert.

          Followup:

sure, just be careful of over querying v$ tables....  can be a performance hit
if you do it too often.

      Rating:
      3
  May 04, 2003
Reviewer:
      A reader
I used to use v$sqlarea or v$open_cursor (instead of that mentioned above) to
catch texts of executed statements (together with information from v$session).
is that the wrong way or just another valid approach? in other words: what is
the "most safe" SQL statement to get session infos (one row per session) in
conjunction with text of the current executed statement?

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:767025833873
is the script I use.

      Rating:
      4
  May 04, 2003
Reviewer:
      A reader
thanks tom for that snippet.

(I'm surprised that you don't do it with JUST SQL - like you usually prefer to
do)

      Rating:
      3
  November 06, 2003
Reviewer:  A
      reader
Tom,

If the address is preferred to get the SQL from v$sql_text then why statspack
report the hashvalue ? Can you pls get me the SQL so that I can get the SQL in a
formatted way from the v$SQLTEXT or v$SQL_TEXT_NEW_LINE ?

  Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
     20,397,961          868       23,500.0     6.9   3742981312

          Followup:

the hash value + sql text is enough for you to drill down with into v$sql text
with newlines to get the entire query.

      Rating:
      3
  November 06, 2003
Reviewer:  A
      reader
Tom,

If the address is preferred to get the SQL from v$sql_text then why statspack
report the hashvalue ? Can you pls get me the SQL so that I can get the SQL in a
formatted way from the v$SQLTEXT or v$SQL_TEXT_NEW_LINE ?

  Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
     20,397,961          868       23,500.0     6.9   3742981312

November 06, 2003 sanjay -- Thanks for the question regarding
      "interMedia Text questions", version 8.1.6

      You Asked
Tom,
Can we use the Context Option against any table containing
text ? We would like to store text, received as part of
a user request (it will be more than 4000 chars, is a CLOB
a better way of storage?) and then allow keyword searching,
with keyword higlighting.
In order to use Context, is it enough to build Context
Indexes on these columns and then write a front-end
which issues Context Queries.
For. e.g We pose questions to you and later we can
search using keywords. The search returns a result set
with ranking and keyword higlighting.

      and we said...
The answer is simply "yes".

Here are some more details.  I use context myself on a very
similar appliation "Ask Tom".  People contribute questions -- I
follow up and answer them and if I feel they have broad impact,
I "publish" them.  In order to publish them, I format the
question and answer into 1 field with some HTML markup and such
and put it into a single CLOB column.  The table I put this into
looks like this:

create table submitted_indexed_questions
( dispid number primary key,
  text   clob
);

Of course, your table may have many other attributes, I leave
many attributes in another table.  That other table has all of
the questions and answers.  The above table only has questions
available for everyone to look at.

I have an index on this other table:

create index search_idx
on submitted_indexed_questions(text)
indextype is ctxsys.context
/

That allows me to do text based searches against this table.  I
have a search routine (you can interact with it at

http://osi.oracle.com/wa/ask/owa/ask_tom_pkg.search
).

Basically, I can now run queries like:

ask_tom@OSI1.WORLD> l
  1  select count(*)
  2   from submitted_indexed_questions
  3* where contains( text, 'index', 1 ) > 0
ask_tom@OSI1.WORLD> /

  COUNT(*)
----------
       130

ask_tom@OSI1.WORLD> select count(*) from
submitted_indexed_questions;

  COUNT(*)
----------
       573

that shows me 130 out of 573 questions have the word index in
them... I can get more complex:

ask_tom@OSI1.WORLD> select count(*)
  2  from submitted_indexed_questions
  3  where contains( text, 'index and desc', 1 ) > 0;

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

ask_tom@OSI1.WORLD>

Now I see only 4 of them have both index and desc in them...

Now, if I want to rank them and get the "top 5" I can do
something like:

  1  select *
  2  from ( select dispid, score(1),
                               dbms_lob.substr(text,25,1) text
  3           from submitted_indexed_questions
  4          where contains( text, 'index', 1 ) > 0
  5          order by 2 DESC )
  6* where rownum < 6
ask_tom@OSI1.WORLD> /

         DISPID        SCORE(1) TEXT
--------------- --------------- -------------------------
   239614547000             100 Igor -- Thanks for the qu
    14812348049             100 Ratnaprabha
                                 -- Thanks f

     9212348049             100 Robert
                                 -- Thanks for th

   153212348067              72 Paul
                                 -- Thanks for the

   157412348067              67 Lalita
                                 -- Thanks for t

Now, once I've searched, I can display a document marked up (do
a search at the url above and you'll see your hits with the
search terms marked up for you -- intermedia text does this as
well).  It would look like this:

        CTX_DOC.MARKUP( index_name =>  'search_idx',
                        textkey =>      p_dispid,
                        text_query =>   p_search_str,
                        restab =>      'markup',
                        query_id =>     userenv('sessionid'),
                        plaintext => true,
                        starttag =>    '<b><font color="red">',
                        endtag =>      '</b></font>' );

Here we simply call the MARKUP procedure and specify the name of
our index (so it knows what table we are accessing, what the
text column in that table is and what the primary key of that
table is).  We pass in the primary key to identify the instance
we wish to mark up.  We give it our search string, what we used
to find this document.  We tell it what table to place the
marked up document in ("markup" in this case).  This table looks
like:

ask_tom@OSI1.WORLD> desc markup
 Name                          Null?    Type
 ----------------------------- -------- --------------------
 QUERY_ID                      NOT NULL NUMBER
 DOCUMENT                               CLOB

We pass it a unique key that we can use to identify our markedup
document (I find userenv('sessionid') great for this purpose).
We tell it the tags to use to markup our text, I use bolded red
as my markup.

Review &
      Followup

      Rating:
      5
Excellent  May 12, 2001
Reviewer:
      Kinnari  from Mumbai, India
Excellent example for the beginners !!!!!!

      Rating: 5
Very good
      explanation !  July 21, 2001
Reviewer:
      A reader

      Rating:
      5
  September 27, 2001
Reviewer:
      A reader
Hi Tom,
I am searching a table which a millian records in it ..I have to display all the
rows which has that particular text in any of the columns..
say i have
c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 columns in test table
and i want to list the rows which has zzzzzz in any column.
What is the best solution....
pl help

          Followup:

Consider indexing a function with interMedia text that concatenates all of the
fields together

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:440419921146
or if the data is relatively static (not updated frequently) and the values in
c1, c2, c3, ... repeat alot (the cardinality of each column is low) a bitmap
index on EACH OF c1, c2, c3 (eg: N indexes -- not an index with N columns) might
work well.

      Rating: 3
link doesn't
      work  September 13, 2002
Reviewer:  rajiv

http://osi.oracle.com/wa/ask/owa/ask_tom_pkg.search
are the words intermedia search option and context are same or different?

is there anything new in 9i with context option.

where do i find more info on that.

does your book talk about that

          Followup:

sorry -- just click on the search link.

context = intermedia = text

Yes there is new stuff all of the time.  On otn.oracle.com, you'll find links to
the documentation.  There is a new features guide and each document also lists
"whats new in this document"

      Rating: 5
Rebuild after
      new records inserted?  January 30, 2003
Reviewer:
      A Reader  from USA
Hi Tom,
Is it true that the rows inserted into a table after the context index has been
built will not appear in the search results and after we insert new records we
need to rebuild the index to include the new records?  My search results do not
include the new records.
Thank you very much.

          Followup:

It depends.  See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:238814521769
for a discussion of the ctxsrv process.  A procedure I use on my internal site
-- instead of running the ctxsrv process is:

create or replace procedure sync_all_ctx_indexes
as
    l_err  long;
begin
    for x in (  select u.name idx_owner,
                       idx_name idx_name
                  from sys.user$ u, dr$index
                 where idx_owner# = u.user#
                   and u.name <> 'CTXSYS' )
    loop
        begin
            ctx_ddl.sync_index( x.idx_owner || '.' || x.idx_name );
        exception
            when others then
                l_err := l_err || sqlerrm;
        end;
    end loop;

    if ( l_err is not null )
    then
        raise_application_error( -20001, 'Errors ' || l_err );
    end if;
end;
/

then we have dbms_job run that every couple of minutes.

      Rating: 5
markup over
      multiple rows  February 21, 2003
Reviewer:
      Scott  from Perth, WA
G'day

Is the ctx_doc.markup applicable/useful/efficient when selecting multiple rows.
After looking at the definition, and having a small play, it seems that it is
only useful AFTER you've found your record to get the primary key.

For example, when you Google a few words, the result list marks-up the words
searched for.
As ctx_doc.markup is a procedure, this doesn't seem possible, unless you build a
function that called for each row returned would go away, markup the text, find
and remove the record from restab and return the marked-up text.

Or is ctx_doc.markup intended for marking larger documents as requested?

And what's the latest URL for

http://osi.oracle.com/wa/ask/owa/ask_tom_pkg.search
as this seems to be out-of-date (doesn't work)...
You said 'just click the search link', but I didn't know what you meant.

Thanks.

          Followup:

you use it to markup A document for viewing.

What you would do to do a google like thing would be to format the hitlist,
insert it into a table and mark it up - probably not the most efficient thing in
the world...

just goto asktom.oracle.com -- the search is on the home page now.

      Rating: 3
Link is
      dead  March 24, 2003
Reviewer:  Phil
       from London
Hello Tom,
I'm really interested in this and would be keen to see
the way you've implemented the search package, however the
link (
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:440419921146
 is dead.
Please can you re-post it?
Thanks!

          Followup:

works for me.

      Rating: 5
Index not
      supported for RAW PKs ? - 9iR2  March 25, 2003

Reviewer:  pasko
Hi Tom,

thank for a Great answer above ..

i wanted to test CTX index on some XML data which is stored in the database ,
but i keep hitting the error shown below.

My table has a  parimary key column of RAW(16) Type   and i am always using
sys_guid() to auto-fill this column.

ORA-29855: error occurred in the execution of ODCIINDEXCREATE routine
ORA-20000: Oracle Text error:
DRG-10528: primary keys of type RAW are not allowed
ORA-06512: at "CTXSYS.DRUE", line 157
ORA-06512: at "CTXSYS.TEXTINDEXMETHODS", line 186

Are  RAW types not supported in this case as  the error says ?

Thanks .

          Followup:

It is not supported until the next release of Oracle after 9iR2 when this
implementation restriction is removed.

      Rating: 5
too
      good  May 07, 2003
Reviewer:  john

thanks for the simple examples given.

Tom, can you please give examples how do we do the same in Oracle 9i as well.
Like how do we create any index and use the contains function on the text field.

thanks

          Followup:

cut and paste -- there is no difference.

      Rating: 5
Tables
      underlying Oracle Text  May 08, 2003
Reviewer:
      Gurunandan  from Goa, India
Thanks for the clear explanation. How do I go about getting at the tables
underlying Oracle Text? For example, I want to build a thesaurus tree with SYN,
NT and BT I use the "contains(SYN/NT/BT)" construct. but I would much rather
generate the tree using selects on some ctxsys tables. Is there a way to do
this?

          Followup:

not recommended at all.  use the documented views -- sure, access the base
tables at your own risk and be prepared to rewrite your code that does this with
every release.

perhaps what you need is in ctx_user_thes* views

      Rating: 5
Create the
      fnd_lobs_ctx intermedia index  October 25, 2003

Reviewer:  Sean
I use the script as shown following to create the index and got the error, could
you point out what the probelms?
Many thanks.
-----------------------------------------------
whenever sqlerror exit failure rollback;
connect &1/&2

declare
  stmt    varchar2(1000);
  mycount number;
  cursor  oldIndex is select owner from all_indexes
                      where  index_name = 'FND_LOBS_CTX';
begin
  for dropit in oldIndex loop
    execute immediate 'drop index '||dropit.owner||'.fnd_lobs_ctx force';
  end loop;
  delete from fnd_lobs where file_id = -1;
  select count(*) into mycount from fnd_lobs;
  if (mycount = 0) then
    insert into fnd_lobs (file_id,file_content_type,oracle_charset,file_format)
    values (-1,'text/html','UTF8','TEXT');
  end if;
  commit;
  stmt := ' create index fnd_lobs_ctx on fnd_lobs(file_data)         '||
          ' indextype is ctxsys.context                              '||
          ' parameters(''lexer            fnd_lobs_global_lexer      '||
          '              language column  language                   '||
          '              filter           fnd_lobs_charset_filter    '||
          '              format column    file_format                '||
          '              charset column   oracle_charset             '||
          '              storage          fnd_lobs_basic_storage     '||
          '              wordlist         fnd_lobs_basic_wordlist'') ';
  execute immediate stmt;
exception
  when others then
    execute immediate 'drop index fnd_lobs_ctx force';
    raise;
end;
/
commit;
exit;
------------------------------------------
declare
*
ERROR at line 1:
ORA-29855: error occurred in the execution of ODCIINDEXCREATE routine
ORA-20000: interMedia Text error:
ORA-06508: PL/SQL: could not find program unit being called
ORA-06512: at line 30

          Followup:

try it just as a script -- get a handle on what statement is causing it.  given
that I don't have an entire test case (eg: whats fnd_lobs_global_lexer, etc etc
etc)....

Also, just try creating a simple text index -- is interMedia installed correctly
on your system. are any of the packages invalid and not compileable?

(and oh, if this is an 11i apps upgrade or something -- please, contact support,
that would be the best way to handle that.)

      Rating: 5
Why I need to
      rebuild index  October 28, 2003
Reviewer:
      Yong  from CA, USA
Tom,

create table notes(note_id number primary key,title varchar2(255),text clob)

create index search_body_idx
on notes(text)
indextype is ctxsys.context;

create index search_title_idx
on notes(title)
indextype is ctxsys.context;

insert into notes values(note_seq.nextval,'this is oracle title','asdf asd some
sdf');

commit;

select * from notes
where contains( text, 'some', 1) > 0
or contains(title,'oracle') > 0

return no rows.

After I rebuild the indexes, it works. I try this twice.
Is there setup need to make index happend automatically?

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:238814521769

      Rating:
      5
  October 29, 2003
Reviewer:  Yong
       from CA, USA
Tom,

Thanks, ctxsvr works.
I have another question

whey the following query doesn't work

  1  select * from notes
  2  where  contains(title,'database',1) > 0
  3* or contains(text,'dadtabase',1)>0
SQL> /
or contains(text,'dadtabase',1)>0
   *
ERROR at line 3:
ORA-29907: found duplicate labels in primary invocations

the following query works

select * from notes
where  contains(title,'database',1) > 0
union all
select * from notes
where contains(text,'dadtabase',1)>0

I wonder whether there is a better way than the union all

          Followup:

change the 1 to a 2 in the second one.  you "labeled" the contains clause with
the same label

      Rating:
      5
  October 29, 2003
Reviewer:  Yong
       from CA, USA
Tom,

I got an error when I query and display html.

for c in (select * from notes where parent_id=0 order by created_date desc) loop
      i:=i+1;
      htp.p('<b>'||i||'. </b><b>'||c.title||'</b><p>'||c.text);
      for d in (select * from notes
                start with parent_id=c.note_id
                connect by prior note_id=parent_id) loop
          htp.p('<hr>'||d.text);
      end loop;
end loop;

ORA-06502: PL/SQL: numeric or value error

The problem may be caused by text which is clob type.
Should I retrieve the clob piece by piece and display?
or any better way to handle this?

thanks,

          Followup:

well, you don't give the line numbers or anything - so it is hard to say.

if the clob exceeds 32k, you'll need to print it a piece at a time since htp.p
takes a varchar2 and in plsql varchar2's are limited to 32k

      Rating:
      5
  October 30, 2003
Reviewer:  Yong
       from CA, USA
Tom,

I fixed using dbms_lob.read as follows

procedure get_content(id number,string varchar2) is
   Lob_loc       CLOB;
   Buffer        VARCHAR2(32767);
   Amount        BINARY_INTEGER := 32767;
   Position      INTEGER := 1;
   i             INTEGER;
   last         number;
BEGIN
   SELECT text INTO Lob_loc FROM notes where  note_id=id;

   DBMS_LOB.OPEN (Lob_loc, DBMS_LOB.LOB_READONLY);
   last:=round(dbms_lob.GETLENGTH(Lob_loc)/32767)+1;
   FOR i IN 1..last LOOP
      DBMS_LOB.READ (Lob_loc, Amount, Position, Buffer);
      Position := Position + Amount;
      dbms_output.put_line(substr(buffer,1,255));
   END LOOP;
   DBMS_LOB.CLOSE(Lob_loc);
EXCEPTION WHEN OTHERS THEN
   dbms_output.put_line(sqlerrm);
   DBMS_LOB.CLOSE(Lob_loc);

Let me know if you see some potential problem.

Thanks,

          Followup:

other then wondering how we got from htp to dbms_output, i see no "issues"
(unless the lob is evenly divisible by 32767 of course ;)

      Rating: 5
Do I have to
      replace '_' with a space in the search?  October 31, 2003

Reviewer:  Yong  from CA, USA
Tom,

Thank you for pointing to the division

round(dbms_lob.GETLENGTH(Lob_loc)/32767)+1;

I changed to ceil(dbms_lob.GETLENGTH(Lob_loc)/32767)

I found another problem with search

insert into notes(note_id,text) values(12345,'this_word_with_underline');

SQL> select note_id from notes where contains(text,'this_word_with_underline',1)
> 0

no rows selected

SQL> select note_id from notes  where contains(text,'this word with
underline',1) > 0;

   NOTE_ID
----------
     12345

I have to replace all '_' with a space to search the word.

What I did wrong?

          Followup:

well, by default, Oracle text is going to turn the _ into ' ' in the orginal
input.  If you look at the actual tokens generated from that:

insert into notes(note_id,text) values(12345,'this_word_with_underline');

  1* select token_text from  DR$T_IDX$I
ops$tkyte@ORA920> /

TOKEN_TEXT
----------------------------------------------------------------
UNDERLINE
WORD

this, with -- stopwords, not indexed.  You can either cleanup the input string,
or do what I do here:

ops$tkyte@ORA920> select * from notes where contains( text,
'{this_word_with_underline}' ) > 0;

   NOTE_ID TEXT
----------
--------------------------------------------------------------------------------
     12345 this_word_with_underline

"quote" the search string with braces.

      Rating: 5
Does search
      sequnce matter?  November 06, 2003
Reviewer:
      Yong  from CA, USA
Tom,

I assume that sequnce of searching words should not be matter. But how to
explain this?

SQL> insert into notes(note_id,text) values(12345,'pl/sql email ascii excel');

1 row created.

SQL> commit;

Commit complete.

SQL> select note_id from notes where contains(text,'ascii excel') > 0;

   NOTE_ID
----------
     12345

SQL> select note_id from notes where contains(text,'excel ascii') > 0;

no rows selected

          Followup:

the STRING "ascii excel" exists

the STRING "excel ascii" does not

if you want to find all documents that contain excel and ascii -- use {}...

ops$tkyte@ORA920LAP> select note_id from notes where contains(text,'ascii
excel') > 0;

   NOTE_ID
----------
      1234

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select note_id from notes where contains(text,'excel
ascii') > 0;

no rows selected

ops$tkyte@ORA920LAP> select note_id from notes where contains(text,'{excel}&
{ascii}') > 0;

   NOTE_ID
----------
      1234

ops$tkyte@ORA920LAP>

      Rating:
      5
  November 06, 2003
Reviewer:  Yong
       from CA, USA
Tom,

My assumption was wrong. I need to tokenize the search string and replace all
unwanted character and have '&'. It works.

Thanks a lot!

November 07, 2003 Sitarama -- Thanks for the question regarding
      "Rows locks from select for update clause", version 9.0.1.3

      You Asked
Hi Tom,
From my knowledge of oracle, i understand that SELECT FOR UPDATE clause acquires
row locks. In that case, if in a given table, when my query updates one set of
data, same query should be able to update different set of data by using select
for update clause. Let me put it in an example. I ran this plsql code from one
session. As you see this updates data in PRCS_STS table where the process ids
are PF31.

declare
  cursor c1 is
   select PRS_WOO_PRCS_ID,prs_sts
          from prcs_sts
         where PRS_WOO_PRCS_ID = 'PF31'
    for update of prs_sts;
begin
   for c1_rec in c1 loop
   update prcs_sts
           set prs_sts = 'Y'
         where current of c1;
   end loop;
end;
/

Now from a different session, i run another plsql statement which updates rows
with process ids PF32.

declare
  cursor c1 is
   select PRS_WOO_PRCS_ID,prs_sts
          from prcs_sts
         where PRS_WOO_PRCS_ID = 'PF32'
    for update of prs_sts;
begin
   for c1_rec in c1 loop
   update prcs_sts
           set prs_sts = 'Y'
         where current of c1;
   end loop;
end;
/

However, to my surprise, the second query should be able to update the rows
marked PF32 which was not happening. Until I rollback the updates which i
performed in the first session, the plsql in the second session was not
successful.

Please clarify.

You have a great day Tom.

Thanks
Sitarama Chakravarthy

      and we said...
I can only imagine that you made a mistake somewhere in your testing.  It does
not work the way you describe.  Using an autonomous transaction, I can
demonstrate in a single session that this works.

first, I'll fill up a table, then run the pf31 block.  I'll re-run the pf31
block again as an AUTONOMOUS (sub or nested transaction -- runs AS IF it were in
another session) transaction to show that it would be blocked.  Then run the
pf32 block successfully.

ops$tkyte@ORA9I.WORLD> create table prcs_sts( PRS_WOO_PRCS_ID varchar2(5),
prs_sts char(1) );

Table created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> insert into prcs_sts values( 'PF31', 'n' );

1 row created.

ops$tkyte@ORA9I.WORLD> insert into prcs_sts values( 'PF31', 'n' );

1 row created.

ops$tkyte@ORA9I.WORLD> insert into prcs_sts values( 'PF31', 'n' );

1 row created.

ops$tkyte@ORA9I.WORLD> insert into prcs_sts values( 'PF32', 'n' );

1 row created.

ops$tkyte@ORA9I.WORLD> insert into prcs_sts values( 'PF32', 'n' );

1 row created.

ops$tkyte@ORA9I.WORLD> insert into prcs_sts values( 'PF32', 'n' );

1 row created.

ops$tkyte@ORA9I.WORLD> COMMIT;

Commit complete.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> declare
  2    cursor c1 is
  3     select PRS_WOO_PRCS_ID,prs_sts
  4            from prcs_sts
  5           where PRS_WOO_PRCS_ID = 'PF31'
  6      for update of prs_sts;
  7  begin
  8     for c1_rec in c1 loop
  9     update prcs_sts
 10             set prs_sts = 'Y'
 11           where current of c1;
 12     end loop;
 13  end;
 14  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> declare
  2    PRAGMA AUTONOMOUS_TRANSACTION;
  3    cursor c1 is
  4     select PRS_WOO_PRCS_ID,prs_sts
  5            from prcs_sts
  6           where PRS_WOO_PRCS_ID = 'PF31'
  7      for update of prs_sts;
  8  begin
  9     for c1_rec in c1 loop
 10     update prcs_sts
 11             set prs_sts = 'Y'
 12           where current of c1;
 13     end loop;
 14  end;
 15  /
declare
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
ORA-06512: at line 4
ORA-06512: at line 9

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> declare
  2    PRAGMA AUTONOMOUS_TRANSACTION;
  3    cursor c1 is
  4     select PRS_WOO_PRCS_ID,prs_sts
  5            from prcs_sts
  6           where PRS_WOO_PRCS_ID = 'PF32'
  7      for update of prs_sts;
  8  begin
  9     for c1_rec in c1 loop
 10     update prcs_sts
 11             set prs_sts = 'Y'
 12           where current of c1;
 13     end loop;
 14     COMMIT;
 15  end;
 16  /

PL/SQL procedure successfully completed.

it works...

Review &
      Followup

      Rating: 3
Row Locks using
      SELECT for UPDATE clause  July 07, 2002
Reviewer:
      Sitarama Chakravarthy  from Mumbai, India
Hi Tom,
Sorry to bother you again. In an atempt to understand this concept more clearly,
I ran the example which you presented. It worked. I realised that there is
something that i am grossly doing wrong. The only difference I can see in your
plsql and mine is the kind of data that was selected for update. I modified my
WHERE clause to include all the columns of my primary key in the table
(PRS_BSNS_SGMNT, PRS_BSNS_SUB_SGMNT, PRS_DATE, PRS_WOO_PRCS_ID). The modified
query is as follows.

declare
cursor c1 is
  select  PRS_BSNS_SGMNT, PRS_BSNS_SUB_SGMNT,
          PRS_DATE, PRS_WOO_PRCS_ID,prs_sts
   from   prcs_sts
  where   PRS_BSNS_SGMNT = 'FRX'
    AND  PRS_BSNS_SUB_SGMNT = 'USDINR'
    AND  PRS_DATE = '28-MAR-02'
    AND  PRS_WOO_PRCS_ID = 'PF31'
    for update of prs_sts;
begin
  for c1_rec in c1 loop
      update prcs_sts
         set prs_sts = 'Y'
       where current of c1;
  end loop;
end;
/

PL/SQL procedure successfully completed.

declare
  PRAGMA AUTONOMOUS_TRANSACTION;
  cursor c1 is
  select PRS_BSNS_SGMNT,PRS_BSNS_SUB_SGMNT,PRS_DATE,
         PRS_WOO_PRCS_ID,prs_sts
    from prcs_sts
   where PRS_BSNS_SGMNT = 'FRX'
     AND  PRS_BSNS_SUB_SGMNT = 'USDINR'
     AND  PRS_DATE = '28-MAR-02'
     AND  PRS_WOO_PRCS_ID = 'PF32'
     for update     of prs_sts;
begin
   for c1_rec in c1 loop
   update prcs_sts
      set prs_sts = 'Y'
    where current of c1;
   end loop;
   COMMIT;
end;
/

PL/SQL procedure successfully completed.

As shown above, it worked and it worked even when i ran this query without the
AUTONOMOUS_TRANSACTION and from a different session. Then, I tried the same
thing with one of primary key disabled in the WHERE clause of the plsql.

declare
cursor c1 is
  select  PRS_BSNS_SGMNT, PRS_BSNS_SUB_SGMNT,
          PRS_DATE, PRS_WOO_PRCS_ID,prs_sts
   from   prcs_sts
  where   PRS_BSNS_SGMNT = 'FRX'
    AND  PRS_BSNS_SUB_SGMNT = 'USDINR'
--    AND  PRS_DATE = '28-MAR-02'
    AND  PRS_WOO_PRCS_ID = 'PF31'
    for update of prs_sts;
begin
  for c1_rec in c1 loop
      update prcs_sts
         set prs_sts = 'Y'
       where current of c1;
  end loop;
end;
/

PL/SQL procedure successfully completed.

declare
  PRAGMA AUTONOMOUS_TRANSACTION;
  cursor c1 is
  select PRS_BSNS_SGMNT,PRS_BSNS_SUB_SGMNT,PRS_DATE,
         PRS_WOO_PRCS_ID,prs_sts
    from prcs_sts
   where PRS_BSNS_SGMNT = 'FRX'
     AND  PRS_BSNS_SUB_SGMNT = 'USDINR'
--     AND  PRS_DATE = '28-MAR-02'
     AND  PRS_WOO_PRCS_ID = 'PF32'
     for update     of prs_sts;
begin
   for c1_rec in c1 loop
   update prcs_sts
      set prs_sts = 'Y'
    where current of c1;
   end loop;
   COMMIT;
end;
/

declare
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
ORA-06512: at line 17

It did not work, either in the same session with AUTONOMOUS_TRANSACTION option
or in a different session.

From this it looks like the data that is selected for update has an impact on
how SELECT FOR UPDATE works. My question is, is there any relation between the
usage of SELECT FOR UPDATE clause and the primary key of a table? Please
clarify. Thanks again for your prompt reply.

          Followup:

supply a complete test case including

o create table statement
o any and all indexes
o inserts

My example did not even have a primary key...  In any case, I cannot reproduce -
you'll have to show us how to step by step by step:

ops$tkyte@ORA9I.WORLD> drop table prcs_sts;

Table dropped.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> create table  prcs_sts
  2  ( PRS_BSNS_SGMNT varchar2(3),
  3    PRS_BSNS_SUB_SGMNT varchar2(10),
  4    PRS_DATE date,
  5    PRS_WOO_PRCS_ID varchar2(5),
  6    prs_sts varchar2(1),
  7    constraint pk primary key(PRS_BSNS_SGMNT,PRS_BSNS_SUB_SGMNT,PRS_DATE,
PRS_WOO_PRCS_ID)
  8  )
  9  /

Table created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> insert into prcs_sts values(  'FRX', 'USDINR',
'28-MAR-02', 'PF31', 'N' );

1 row created.

ops$tkyte@ORA9I.WORLD> insert into prcs_sts values(  'FRX', 'USDINR',
'28-MAR-02', 'PF32', 'N' );

1 row created.

ops$tkyte@ORA9I.WORLD> commit;

Commit complete.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> declare
  2  cursor c1 is
  3    select  PRS_BSNS_SGMNT, PRS_BSNS_SUB_SGMNT,
  4            PRS_DATE, PRS_WOO_PRCS_ID,prs_sts
  5     from   prcs_sts
  6    where   PRS_BSNS_SGMNT = 'FRX'
  7      AND  PRS_BSNS_SUB_SGMNT = 'USDINR'
  8      --AND  PRS_DATE = '28-MAR-02'
  9      AND  PRS_WOO_PRCS_ID = 'PF31'
 10      for update of prs_sts;
 11  begin
 12    for c1_rec in c1 loop
 13        update prcs_sts
 14           set prs_sts = 'Y'
 15         where current of c1;
 16    end loop;
 17  end;
 18  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> declare
  2    PRAGMA AUTONOMOUS_TRANSACTION;
  3    cursor c1 is
  4    select PRS_BSNS_SGMNT,PRS_BSNS_SUB_SGMNT,PRS_DATE,
  5           PRS_WOO_PRCS_ID,prs_sts
  6      from prcs_sts
  7     where PRS_BSNS_SGMNT = 'FRX'
  8       AND  PRS_BSNS_SUB_SGMNT = 'USDINR'
  9       -- AND  PRS_DATE = '28-MAR-02'
 10       AND  PRS_WOO_PRCS_ID = 'PF32'
 11       for update     of prs_sts;
 12  begin
 13     for c1_rec in c1 loop
 14     update prcs_sts
 15        set prs_sts = 'Y'
 16      where current of c1;
 17     end loop;
 18     COMMIT;
 19  end;
 20  /

PL/SQL procedure successfully completed.

      Rating: 5
for update
      clause  August 20, 2003
Reviewer:
      A reader
if we have two tables in a join in a select statement
and if we don't specify which table to lock in the for update clause, how does
Oracle figure out which tables
to lock. What are the consequences of not specifying the
table you want to lock?

Thanx!

          Followup:

it'll lock em all. this simulation shows what happens when you do for update, vs
for update of <columns>

ops$tkyte@ORA920> create table t1 ( x int );

Table created.

ops$tkyte@ORA920> create table t2 ( y int );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace view v
  2  as
  3  select *
  4    from t1, t2
  5   where t1.x = t2.y
  6  /

View created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t1 values ( 1 );

1 row created.

ops$tkyte@ORA920> insert into t2 values ( 1 );

1 row created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from v for update;

         X          Y
---------- ----------
         1          1

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          pragma autonomous_transaction;
  3  begin
  4          update t1 set x = 1;
  5  end;
  6  /
declare
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
ORA-06512: at line 4

ops$tkyte@ORA920> declare
  2          pragma autonomous_transaction;
  3  begin
  4          update t2 set y = 1;
  5  end;
  6  /
declare
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
ORA-06512: at line 4

another transaction cannot update t1 or t2 -- both locked

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920> select * from v for update of x;

         X          Y
---------- ----------
         1          1

ops$tkyte@ORA920> declare
  2          pragma autonomous_transaction;
  3  begin
  4          update t1 set x = 1;
  5  end;
  6  /
declare
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
ORA-06512: at line 4

ops$tkyte@ORA920> declare
  2          pragma autonomous_transaction;
  3  begin
  4          update t2 set y = 1;
  5          commit;
  6  end;
  7  /

PL/SQL procedure successfully completed.

here we can and did update t2, but not t1 since t1.x was the "for update"
column

hopefully -- that shows you the consequences?

      Rating: 5
thanx
      Tom!!!  August 21, 2003
Reviewer:
      A reader
That definnitely shows the consequences. One more question.

If you don't specify the table or the column name in a
"select for update" clause, in which order does Oracle
acquire the locks (first t1, then t2 or first t2, then t1)?

If the order is not fixed then this perhaps has consequences in terms of
deadlock (one session getting it
in order (t1, t2) and the other getting it in the order
(t2, t1).

Thanx for an awesome site!!

          Followup:

consider it "atomic" for all intents and purposes.

as far as you can tell, it is.

the order for a given query would be given the same plans and all -- but it'll
be a function of how the data is accessed.

      Rating: 5
ok - here is
      the guideline that started this q  August 21, 2003

Reviewer:  A reader
Tom
Could you kindly comment on the validity of the
statement below:
From what you told me my conclusions are:
1. You should specify the exact table that you want
to lock in your "for update" clause because otherwise
you would end up locking all the tables - the order
of the locking itself is not important (or is
atomic - so does not come into play - it is the
fact that more locks are acquired than necessary
that is of concern.
2. I don't think the alternative of breaking up into
multiple cursors is a good idea - I would instead specify
the tables to be locked in a single query.
Thanx a million Tom!!

"SQL statements that lock rows should be analyzed carefully to insure that
deadlock and lock ordering issues are avoided. ....
Consider the following cursor that attempts to lock qualifying rows:
CURSOR lock_departure(x_dep_id NUMBER) IS
  SELECT DEP.STATUS_CODE,
         DEL.STATUS_CODE,
         LD.LINE_DETAIL_ID,
         PLD.PICKING_LINE_DETAIL_ID
  FROM   WSH_DEPARTURES DEP,
         WSH_DELIVERIES DEL,
         SO_LINE_DETAILS LD,
         SO_PICKING_LINE_DETAILS PLD
  WHERE  DEP.DEPARTURE_ID = x_dep_id
  AND    DEL.ACTUAL_DEPARTURE_ID(+) = DEP.DEPARTURE_ID
  AND    LD.DEPARTURE_ID(+) = DEP.DEPARTURE_ID
  AND    PLD.DEPARTURE_ID(+) = DEP.DEPARTURE_ID
FOR UPDATE NOWAIT;
The problem with this query is that the locking order is largely dependent on
the execution plan and the row source order. For example, it is possible that
the rows in SO_LINE_DETAILS can be locked before the rows in
SO_PICKING_LINE_DETAILS. It is also possible that the rows of
SO_PICKING_LINE_DETAILS are locked before the rows in SO_LINE_DETAILS. The
locking order is based on the join order (i.e. execution plan). If one user ran
this query under the RBO, and another user ran this query under the CBO, locking
order issues could arise due to the likelihood of a plan difference. Another
problem with this cursor is that it performs non-qualified locking via the FOR
UPDATE. The FOR UPDATE clause can take additional optional arguments specifying
the tables to be locked. For example, FOR UPDATE OF DEP means that only the rows
in WSH_DEPARTURES should be locked. The solution to this query is to specify the
tables to be locked in the FOR UPDATE clause via the FOR option, or break the
query into separate cursors such that each cursor locks a single table only. For
example, the above cursor can be rewritten as follows:
CURSOR lock_departure(x_dep_id NUMBER) IS
  select departure_id
  from   WSH_DEPARTURES
  where  DEPARTURE_ID = x_dep_id
FORUPDATE NOWAIT;
CURSOR lock_deliveries(x_dep_id NUMBER) IS
  select delivery_id
  from   WSH_DELIVERIES
  where  ACTUAL_DEPARTURE_ID = x_dep_id
FOR UPDATE NOWAIT;

CURSOR lock_line_details(x_dep_id NUMBER) IS
  select line_detail_id
  from   SO_LINE_DETAILS
  where  DEPARTURE_ID = x_dep_id
FOR UPDATE NOWAIT;
CURSOR lock_picking_details(x_dep_id NUMBER) IS
  select picking_line_detail_id
  from   SO_PICKING_LINE_DETAILS
  where  DEPARTURE_ID = x_dep_id
FOR UPDATE NOWAIT;
Begin
  OPEN lock_departure(entity_id);
  CLOSE lock_departure;
  OPEN lock_deliveries(entity_id);
  CLOSE lock_deliveries;
  OPEN lock_line_details(entity_id);
  CLOSE lock_line_details;
  OPEN lock_picking_details(entity_id);
  CLOSE lock_picking_details;
End;
In summary, do not code a SQL statement that performs an unqualified lock via
the FOR UPDATE clause. You should either break up the SQL statement into
multiple single table cursors or specify the FOR <table> option of the FOR
UPDATE clause.

          Followup:

I think the deadlock concerns are way overrated here.  the probability of them
happening is extremely small.  breaking the cursor up into lots of pieces would
be a really bad idea in my opinion and experience.  A single query is the right
answer.

      Rating: 5
thank you
      Tom!!  August 22, 2003
Reviewer:
      A reader
Love your site and am continuously JUST AMAZED by your
generosity, your technical knowhow and your tireless
efforts day in and day out for Oracle community!!

      Rating:
      5
Reader  August 23, 2003
Reviewer:
      A reader
I tried your sample code:
I got the error
ORA-06519: active autonomous transaction detected and rolled back
The v$transaction showed no rows. How to track the
autonomous transaction with v$ views

Thanks

SQL> declare
  2      PRAGMA AUTONOMOUS_TRANSACTION;
  3      cursor c1 is
  4       select PRS_WOO_PRCS_ID,prs_sts
  5              from prcs_sts
  6             where PRS_WOO_PRCS_ID = 'PF31'
  7        for update of prs_sts;
  8    begin
  9       for c1_rec in c1 loop
 10       update prcs_sts
 11               set prs_sts = 'Y'
 12             where current of c1;
 13       end loop;
 14    end;
 15  /
declare
*
ERROR at line 1:
ORA-06519: active autonomous transaction detected and rolled back
ORA-06512: at line 10

SQL> select * from v$transaction;

no rows selected

SQL> declare
  2      PRAGMA AUTONOMOUS_TRANSACTION;
  3      cursor c1 is
  4       select PRS_WOO_PRCS_ID,prs_sts
  5              from prcs_sts
  6             where PRS_WOO_PRCS_ID = 'PF31'
  7        for update of prs_sts;
  8    begin
  9       for c1_rec in c1 loop
 10       update prcs_sts
 11               set prs_sts = 'Y'
 12             where current of c1;
 13       end loop;
 14    end;
 15  /
declare
*
ERROR at line 1:
ORA-06519: active autonomous transaction detected and rolled back
ORA-06512: at line 10

          Followup:

umm, which example exactly are you running?

if you run the block of plsql code that DOES NOT GET BLOCKED -- yes, it needs
either a commit or a rollback at the end of it.

      Rating:
      5
reader  August 24, 2003
Reviewer:
      A reader
I was running the block of pl/sql code that DOES BLOCK twice and got
the error. I like to know if there is anyway to track the
atonomous TXs from v$ views

Thanks

          Followup:

then you unblocked the code -- the ONLY way to get that error would be for the
code to not have blocked at all.  the ONLY way.

an autonomous transaction is purely a "you are doing it in your session" sort of
thing.  externally - from any one elses perspective, it is not any different
then any other transaction.  they appear no differently.

      Rating: 3
A little
      confused  August 25, 2003
Reviewer:  reader
       from India
Hi Tom Good day to you.

Well I am a little confused, kindly help.

As you said "an autonomous transaction is purely a "you are doing it in your
session" sort of thing.  externally - from any one elses perspective, it is not
any different
then any other transaction.  they appear no differently."

The ORACLE doc A76965-01 for 8i says "Once invoked, an autonomous transaction is
totally independent of the main
transaction that called it. It does not see any of the uncommitted changes made
by the main transaction and does not share any locks or resources with the main
transaction. Changes made by an autonomous transaction become visible to other
transactions upon commit of the autonomous transactions."

The transaction through autonomous transaction is completed opening another
session or does it uses the same session which initiated the transaction if you
can give and example for this it will be a great help.

Thanks for all your help.

Best Regards

          Followup:

i see no conflict there.

someone asked "how do I identify an atrans"

i said -- you cannot.  they "look" no different.  It would be like trying to
pick the cloned sheep daisy out of a herd of sheep.  Unless you yourself placed
daisy in the herd, you would have no clue which one was she.

sure, it is independent -- but so what?  externally -- it is just yet another
transaction.

      Rating:
      3
  August 25, 2003
Reviewer:  reader
       from India
Hi Tom,
      With all the respect to you, don't get me wrong I only want to know if
autonomous transaction opens another session or uses the same session. I feel it
uses same session still we have people with different opinions just wanted to
know the right thing.

Thanks as always for your helps...

          Followup:

uses the same session -- it is just an independent sub transaction.

It is just like "recursive sql", you've been doing autonomous transactions for
years -- you just never knew it.  If you select a sequence and we have to update
seq$ (and commit that update), we did it in an autonomous transaction if you
will

      Rating: 4
Dead
      lock  October 22, 2003
Reviewer:  Dillip K.
      Praharaj  from CA
I do not think

If I run this from 2 sessions it waits, but from the same session with PRAGMA
AUTONOMOUS_TRANSACTION, it triggers a rollback,

Can someone confirm why so ?

declare
    cursor c1 is
    select row_id,id,sts
        from test
        where ID = 'PF31'
        order by row_id desc
        for update of sts;
    begin
     for c1_rec in c1 loop
       update test
       set sts = 'Y'
       where current of c1;
      end loop;
    end;

          Followup:

umm, you deadlocked yourself -- that is why it failed in a single session.  What
did you want it to do?  block forever????

      Rating:
      4
deadlock  October 22, 2003
Reviewer:
      Dillip K. Praharaj  from CA
But I am doing a PRAGMA AUTONOMOUS_TRANSACTION for the second query, is it not
same as running the query from another session ?

          Followup:

no

it is running a second transaction from YOUR SESSION

wouldn't you be mad if we just sat there for the rest of time???? that seems to
be what you want -- that would be "bad".

      Rating:
      5
deadlock  October 22, 2003
Reviewer:
      Dillip K. Praharaj  from CA
Trust me I would not like to wait. Thanks for the clarification.

      Rating: 5
Lock 2nd users
      for view only  November 06, 2003
Reviewer:
      Junior  from VA, USA
Tom:

We are creating an web application. Now, question is: When more than one users
work on same record, only the first one who selects the record can update it.
The rest of users are view only for this record. Does Oracle has such function?
E.g. if we use 'select for update', then the application will be hang on there
until first guy release or rollback the record and we don't like it.
Do you have any other solutions? Thank you for your helps.

          Followup:

the application would use "select for update NOWAIT"

if you get it -- you got it.  if you don't get it, you don't got it.

November 07, 2003 Suresh -- Thanks for the question regarding
      "using aggregate functions", version 8.1.7

      You Asked
What is the best way to get the 'other columns' also  of  rows retruned when
using aggregate functions.

For eg.

      select max(inv_amt), cust_code
        from invoices
        group by cust_code;

Now, say if you want to get the invoice_number, and the Invoice_date also for
the respective record, what is the best way to do it.

Thank you,
Suresh.

      and we said...
There are a couple of ways to do this.

Lets say you are working with the EMP table and want to get the DEPTNO and
MAX(sal) by deptno and would like to get the ENAME and HIREDATE that goes with
that....

FIRST -- we have to assume that CUST_CODE,INV_AMT is unique or we will get >1
record back for a given cust_code (I mean, if cust_code = 100 has two entries
with inv_amt = 1000000 -- then there are 2 invoice_numbers and 2 invoice_dates
with the MAX!!  so be prepared for that!!)

Ok, we can:

scott@ORA817DEV.US.ORACLE.COM> select deptno, sal, ename, hiredate
  2    from emp
  3   where sal = ( select max(sal) from emp e2 where e2.deptno = emp.deptno )
  4   order by deptno
  5  /

    DEPTNO        SAL ENAME      HIREDATE
---------- ---------- ---------- ---------
        10       5000 KING       17-NOV-81
        20       3000 SCOTT      09-DEC-82
        20       3000 FORD       03-DEC-81
        30       2850 BLAKE      01-MAY-81

scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM> select deptno,
  2             to_number(substr( max_sal, 1, 10 )) sal,
  3             substr( max_sal, 20 ) ename,
  4             to_date( substr( max_sal, 11, 9 ) ) hiredate
  5    from (
  6  select deptno, max( to_char( sal,'fm0000000009') || hiredate || ename )
max_sal
  7    from emp
  8   group by deptno
  9             )
 10  /

    DEPTNO        SAL ENAME       HIREDATE
---------- ---------- ----------- ---------
        10       5000 KING        17-NOV-81
        20       3000 SCOTT       09-DEC-82
        30       2850 BLAKE       01-MAY-81

scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM> select distinct deptno,
  2             first_value(sal) over ( partition by deptno order by SAL desc
nulls last ) sal,
  3             first_value(ename) over ( partition by deptno order by SAL desc
nulls last ) ename,
  4             first_value(hiredate) over ( partition by deptno order by SAL
desc nulls last )
  5             hiredate
  6    from emp
  7   order by deptno
  8  /

    DEPTNO        SAL ENAME      HIREDATE
---------- ---------- ---------- ---------
        10       5000 KING       17-NOV-81
        20       3000 FORD       03-DEC-81
        30       2850 BLAKE      01-MAY-81

scott@ORA817DEV.US.ORACLE.COM>

the totally interesting thing here is that all three queries are correct
(given your problem statement) but they all return different answers given the
same data!!!

(this is one of my favorite problems in SQL, dealing with top-n's, max's and
such -- I use questions like this in interviews all of the time to see if people
can catch this subtle problem, most do not...)

So, which on is "best", thats in the eye of the beholder.  I would probably use
the analytic functions (first_value) as they are by far the cleanest approach
and offer immense functionality once you learn them.

the one that uses max( to_char(sal,'fm.......) is sort of a trick.  You encode
the value you want to max in a fixed length string, glue the rest of the data
onto the end of it and then substr it back out. Very effective, can be very
performant (but the analytic functions are typically faster still).

The last one is the traditional set based, sql approach to the problem. Boring
but tried and true.  It works, can perform well in the right circumstances
(proper indexes, right amount of data and such)....

There are other approaches as well -- this just gives you a flavor of what you
can do.
Review
      & Followup

      Rating: 5
using aggregate
      functions  December 08, 2001
Reviewer:  Suresh
      Nair  from CT, USA
Definitely the response was very hepful.  It was the answer to my last two days
of search for the same. And also it told me that there are something like
'Analytic functions'. For the time being I will be using the 2nd method (The
trick). Before using the 'Analytic functions' method let me learn what it is
first.

Tom, Thank you very much.

      Rating: 4
analytical
      functions  December 08, 2001
Reviewer:
      A reader
The second row of the third query:

20       3000 FORD       03-DEC-8

doesn't look right to me. One can deduce that the hiredate for FORD was
'03-DEC-8' which is not a valid fact in the database.

          Followup:

The second row of the 3rd query is (and always was)

 DEPTNO        SAL ENAME      HIREDATE
---------- ---------- ---------- ---------
        10       5000 KING       17-NOV-81
        20       3000 FORD       03-DEC-81
        30       2850 BLAKE      01-MAY-81

scott@ORA817DEV.US.ORACLE.COM>

your browser must have done something funny to it -- there was an 81 there, not
just an 8.

      Rating: 4
immense
      functionality ?  December 08, 2001
Reviewer:
      Mikito Harakiri
Tom, can you be a litle bit more specific why and if analytical functions are so
powerful? Relational theory tells us that joins are a the best thing since
sliced bread and that referencing "previous" row is bad. How would you compare
"sliding window" analytical query with this:

select empno,
       (select sum(sal) from
         (select rownum rn, sal from
           (select sal from emp order by -sal)
         )
       where center-1<=rn and rn<=center+1 )
from (select rownum center, empno from
       (select empno from emp order by -sal)
      )

Don't you think that adding new features into the language distract users from
being able to leverage existing ones?

          Followup:

Analytic functions make possible what was either

o hard to code (relational theory is awesome but sometimes people have jobs to
do, reports to crank out)

o impratical to perform for performance reasons

In "theory" many things are "bad", in practice -- they are necessary.  (i'm not
a big fan of "theory", I'm a big fan of getting things done, done well, done
fast)

Try to compute a 5 day moving average sometime using relational theory.  Doable
but not practical.

You yourself violate relational theory above, you are using our psuedo column
rownum which assigns order to rows in a tuple.  Thats a big bad no-no.  You are
relying on the ordering of rows in a set -- another Oracle non-pure extension
(order by in a subquery -- thats a huge no-no)....  You've broken many rules.

Ok, after looking at your query, I deduce that it simply shows the sum of the
salary of

a) the row "in front of" or "preceding" the current row
b) the current row
c) the row "after" or "following" the current row

So, I loaded up 1,000 employees (i tried with 30,000 but I got really bored
waiting for the answer...) and ran the queries:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table emp ( empno int NOT NULL, sal
int NOT NULL );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */
  2  into emp
  3  select rownum, rownum from all_objects
  4  where rownum <= 1000;

1000 rows created.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from emp;

  COUNT(*)
----------
      1000

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table emp add constraint emp_pk primary
key(empno);

Table altered.

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

Index created.

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

Index created.

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

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set sql_trace=true;

I overindexed just to give every possible index to your query it might want.
Upon running TKPROF to review the result of your query vs the SAME query using
analytic functions we find:

select empno,
       (select sum(sal) from
         (select rownum rn, sal from
           (select sal from emp order by -sal)
         )
       where center-1<=rn and rn<=center+1 ) x
from (select rownum center, empno, sal from
       (select empno, sal from emp order by -sal)
      )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch       68     14.22      14.19          0       4004       4004        1000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       70     14.23      14.20          0       4004       4004        1000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 63

Rows     Row Source Operation
-------  ---------------------------------------------------
   1000  VIEW
   1000   COUNT
   1000    VIEW
   1000     SORT ORDER BY
   1000      INDEX FAST FULL SCAN (object id 23779)

select empno,
       sum(sal) over (order by sal desc
                      rows between 1 preceding and 1 following ) x
 from emp

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch       68      0.03       0.03          0          4          0        1000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       70      0.03       0.03          0          4          0        1000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 63

Rows     Row Source Operation
-------  ---------------------------------------------------
   1000  WINDOW SORT
   1000   INDEX FULL SCAN (object id 23780)

So, which query would YOU want to use in the real world??

I find the second query using analytic functions not only faster but more
intuitive.  No fancy "order and assign a row number, find the row numbers around
my rownumber and add them up".  Just a simple "sum the salary of the preceding
and following row"  period.

Much much faster, easier to read -- easier to comprehend and code.  Thats why I
think analytic functions RULE.

Larry Ellison is doing this "Americas Cup" boat thing.  When he bought the boat
it ran on "SQLServer" (the boat is fully instrumented to capture hundreds of
thousands of pieces of data from windspeed and direction to water related
things, the fullness of the sails, everything).  The app they had was so complex
and used so many products there was one guy in New Zealand that knew how to set
it up and run it.

Our group moved it into Oracle, we use nothing but SQL and a simple HTML
interface and they get every bit of functionality they had plus tons more.  No
need for a complex, external OLAP server -- good old SQL with analytic functions
does it all (and more).  I myself find less moving parts = greater chance of
success.  The more functionality we have in the database, the better off we are.

As for:

"Don't you think that adding new features into the language distract users from
being able to leverage existing ones?"

I'd say -- NO WAY (very loudly).  Look at languages like Java with J2EE and J2SE
and the base java language and jdk 1.1, 1.2, 1.3, etc etc etc.  Should that
language "stand still" or should it keep moving (i say move).  Same with the
database - its when you stand still that you become obsolete.  Analytic
functions allow us to do things that were not feasible before, required dumping
the data OUT into some other "server", learning some other environment,
programming some other environment.

Here at least all the developers have to learn is the database.  They don't have
to learn the database, its apis, some OLAP tool, its apis and nuances, some
OTHER tool that does stuff differently and so on.

Just my 2cents worth.  I appreciate the functionality the analytic functions
give us (so much so I did an entire chapter on them in my book, I felt they were
that important -- more benchmarks in there comparing performance)...

      Rating:
      4
informative  December 10, 2001
Reviewer:
      reader
where can I find more (docs, articles and different uses ) on analytical
functions? ( is it specific for Oracle Database products ?)

          Followup:

Well, my book has a good chapter on it.

The data warehousing guide does as well:

http://technet.oracle.com/doc/oracle8i_816/server.816/a76994/analysis.htm#11237
afaik, they are Oracle'isms.

      Rating: 4
Are there
      limits on analytic functions?  December 10, 2001

Reviewer:  jaromir nemec  from Austria, Europe

The answer is very useful and complete.
A small extension - the analytic functions are also a pure SQL alternative to
cursor loop processing.
I have a simple question to that topic.

A table stores transaction entries with transaction date (unique timestamp) and
some other attributes, not interesting for this case.
Transactions belong to sessions following this rules:

·    the first transaction in the session is marked
·    all subsequent transaction (ordered by transaction date) till to the start
of next session belongs to the current session

Question: can I assign to all transactions of a session the transaction date of
the first transaction of this session?
Something like FIRST_VALUE, but the problem is, you have no session
identification to partition on it, you know only the start and end of the
session.

With a cursor loop it is a simple if statement, is it possible to solve that
with analytic function?

Thanks

Jaromir Nemec
www.db-nemec.com

          Followup:

You wouldn't use analytic functions for that -- no.  You cannot update an
"ordered" result set and analytic functions involve ordering.

scott@ORA817DEV.US.ORACLE.COM> update ( select ename,
  2        first_value(sal) over ( partition by deptno order by sal ) new_sal,
  3                  sal
  4             from emp )
  5  set sal = new_sal
  6  /
update ( select ename,
       *
ERROR at line 1:
ORA-01732: data manipulation operation not legal on this view

for this tho, i would use a correlated subquery:

update t
   set trans_date = ( select max(trans_date)
                        from t t2
                       where flag = 1
                         and t2.trans_date < t.trans_date )
 where flag is null
/

      Rating: 5
once more
      analytic functions   December 11, 2001
Reviewer:
      Jaromir Nemec  from Austria Europe
The update is not the problem in the transaction / session assignment.

The question is if it is possible to perform this task "analytical" i.e. without
a selfjoin or without a cursor loop. (A correlated subquery is a kind of join).

The necessary feature would be

instead of partitioning by a column e.g.

   .... OVER (PARTITION BY session_identifikation ORDER BY trans_date)
   -- I don't know the session identification

partitioning by start and stop boundaries, something like that ....

   .... OVER (PARTITION BY all records between start flag and the stop flag
        ORDER BY trans_date)

Isn't it a nice feature for the overnext realase?

Thanks

Jaromir Nemec
www.db-nemec.com

          Followup:

You cannot update ordered relations -- that is the limitation here, the analytic
functions naturally impose ORDER, we do not update "ordered" things.

      Rating: 4
Sorry for not
      saying it clear!  December 15, 2001
Reviewer:
      Jaromir Nemec  from Austria Europe
I have a table ...

SQL> SELECT to_char(trans_date,'hh24:mi:ss') trans_date, start_flag
FROM trans
ORDER BY trans_date;

TRANS_DA S
-------- -
12:00:00 X
12:01:00
12:01:30
12:02:00
12:05:00 X
12:06:00
12:07:30
12:09:00 X
12:09:30

9 Zeilen ausgewählt.

The Question is: can I do the query bottom using analytic functions only
(omitting joins or cursor loops)?

SQL> SELECT to_char(a.trans_date,'hh24:mi:ss') trans_date, a.start_flag,
  2  to_char(b.trans_date,'hh24:mi:ss') AS session_start
  3  FROM trans a, trans b
  4  WHERE b.trans_date =
  5   (SELECT MAX(trans_date)
  6    FROM trans c
  7    WHERE c.start_flag = 'X' AND c.trans_date <= a.trans_date)
  8  ORDER BY a.trans_date
  9  ;

TRANS_DA S SESSION_
-------- - --------
12:00:00 X 12:00:00
12:01:00   12:00:00
12:01:30   12:00:00
12:02:00   12:00:00
12:05:00 X 12:05:00
12:06:00   12:05:00
12:07:30   12:05:00
12:09:00 X 12:09:00
12:09:30   12:09:00

9 Zeilen ausgewählt.

SQL>

Thanks

Jaromir

          Followup:

No, because there is no predicable way to define the window.  We cannot say
"look backwards until you find the first non-null start flag".  Our ranges are
based on numeric ranges (all rows before me with a date within 5 days of my date
for example) or exact row counts (5 rows in front of me).

      Rating: 5
Can you expand
      on the subtle problem you are talking about  December 17,
      2001
Reviewer:  A reader
"(this is one of my favorite problems in SQL, dealing with top-n's, max's and
such -- I use questions like this in interviews all of the time to see if people

can catch this subtle problem, most do not...) "

Tom, when you say subtle problem , do you mean  how to find otu top-n's

          Followup:

I mean that the question is ambigous -- look at my answer above -- three
queries, all are technically accurate, they return the top-n, but they all
return DIFFERENT answers....

      Rating: 5
Is there some
      difference in SQL Engine of 8.1.5 and 8.1.7 ?  December 19,
      2001
Reviewer:  Bhawna Rajesh  from Taipei, Taiwan.

Hi Tom,

Sorry for posting this query here, but needed your help urgently. I have got an
export dump of Designer Repository which I have taken from 8.1.5 (Win NT)
database using exp80 utility. Right now, I am trying to import it back to
another server 8.1.7 (AIX), the import gets hanged. When tried to tack down the
problem point, got a package 'cioue_element_type', which used IN predicate in
one of the procedures. The package gets indefinitely hanged, while compiling.
Tried writing a similar query with IN predicate, and got ORA-00920:invalid
relational operator.
Here is the query I wrote:
  1  select * from pam_app
  2* where (cod_pam, DES_VAL) in ('QTY_MAX_TLX_ADD', 32)
SQL> /
where (cod_pam, DES_VAL) in ('QTY_MAX_TLX_ADD', 32)
                            *
ERROR at line 2:
ORA-00920: invalid relational operator

And, here is the statement of the package giving problem:
  -- Remove Reserved Names entry and comment
  delete from sdd_reserved_names
   where (rn_res_name, rn_type) in ((upper(view_name), 'VIEW')
                                   ,(upper(pack_name), 'PACKAGE')
                                   ,(upper(pack_name), 'PACKAGE BODY')
                                   ,(upper(app_view_name), 'VIEW BODY'));

This is within procedure DROP_VIEWS_PACKS.

I replaced the IN predicate with suitable OR conditions, and it got compiled.
Probably the Repository is not in a stable state, I mean most of the utilities
of Designer 2000 are not working. I need to do a clean import of Designer schema
once more. Can you tell me if there is some setting in parameter file, which
will allow IN predicates ??
Both Oracle instances have Compatible parameter as 8.1.0
Designer version is 2.1.2.

Thanks in advance.

      Rating: 3
Question on
      sort  September 08, 2002
Reviewer:  Kapil
       from INDIA
Hi Tom,
It has been noticed that the time taken to sort a result set is directly
proportional to the number of columns selected keeping the same columns on which
the sort is performed. It was understandable if the time increased if the number
of columns, on which the sort is performed, increased.
Could you please explain reason for the above behaviour?

          Followup:

the more columns, the wider the data.  Think about it -- you have a set of data
like:

A (4bytes)  B (10 bytes) C (10 bytes)

each swap (when you sort, you MOVE lots of stuff to get them in order) moves 24
bytes.

Now, you sort

A (4bytes)  B (10 bytes) C (10 bytes)  D(100 bytes)

you are moving 124bytes.  Wider data, longer time to swap places.

And I doubt the "directly proportional" truly applies as at some point, you'll
hit the sort_area_size threshold and start writing to disk which will make it
less then a linear function of time!

      Rating: 3
Analytic
      functions are very useful but.....  September 08, 2002

Reviewer:  A Reader  from Bangalore
Tom,
  Eversince i read your book i have been advocating the use of analytic
functions. However whenever my queries use analytic functions query time shoots
up.
How do i debug it? How do i identify which oracle parameter is causing poor
performance?
Is there anyway that we can estimate the memory required for PARTITIONING and
aggregating by analytic functions.

regards

          Followup:

Tell me, compare the performance of analytic functions to some process that
produces the same exact result but doesn't use them.

Then tell me they are slow.  In the book I demonstrated a pure "SQL" approach vs
analytic functions.  Not only are the analytics of course easier to code, they
ran slighty (understatement) faster.

Do queries that include analytics run slower then a query without them?  Do
bears do their stuff in the woods?  Of course.

There is no "Oracle parameter" (well there is that undocumented _fast = true,
but I'm not allowed to talk about that ;) that is causing poor performance.  You
would really need to give a test case with all of the parameters (I use this
table, with this much data, with this distribution over the partion by field, I
have these indexes, I use this query and I get this performance - the TKPROF
report).

The memory required?  We use sort_area_size, you let us use as much as you want
and we use all that we can -- then we swap to disk.

      Rating: 5
Really stuck up
      here (Analytic Functions)  September 08, 2002

Reviewer:  Ik  from Bangalore
Tom,

First of all, Thanks a zillion for this wonderful site and all the information
we get through this.

Sorry, I should have given more clarity. On matrix reports (6i) when the number
of records returned by the query is large (more than 4000 or so), the reports
time (time taken at the client end) goes on to 10 minutes. This is due to the
presence of summary columns with product orders. These reports need to be under
a minute or else they throw the whole batch process off track.
    Thus i went on to use analytic functions instead of the summary columns but
now the query itself is going for nearly 75 secs. At the reports end, time has
been significantly reduced but i want to cut down on the Query time.
 I believe that query performance could be improved if there could be more
memory available for grouping and sorting.
Can you please guide me on how analyic functions are processed internally and
which oracle parameters do matter.

Thanks in advance.

          Followup:

Lets see -- you take a process that took 10 minutes procedurally -- get it to 75
seconds in SQL and still aren't happy.....

So -- give it more memory and try.  But then again, i HATE guessing so you
should really see in sqlplus if you are doing sort to disk or not.  If you
aren't, allowing us to use more memory won't do squat.

Anyway -- suggest you enable sql_trace, get a trace file, run tkprof and analyze
what you got.  If you have my book, read chapter 10 - that is what you need, not
to search for the holy grail of init.ora parameters.

      Rating: 5
Thank
      You  September 09, 2002
Reviewer:  Ik
       from Bangalore
Tom,
  Thanks a lot. My problem is that since iam on client site i do not access to
Tkprof and other tuning tools. The only method is to keep changing the query and
clock the runtimes.

One last Question.

I have a requirement which can be handled by using analytic function.
I have two choices of implementation whereby i handle them differently at the
report end.
1. Get a COUNT over smaller partitions (sets of data). This is by having more
selective columns in the PARTITION BY clause.
2. Do an COUNT OVER a larger recordset by having a single column in the PARTITON
BY clause.

Which do you think would consume more Memory and Processing?

regards
ik

          Followup:

You should tell your client that they could save TONS of money by giving you
access to the requisite tools necessary to do your job.

You also should have access to AUTOTRACE in sqlplus:

http://asktom.oracle.com/~tkyte/article1/autotrace.html
requires no server access.

only answer to the question is "benchmark it and see"

      Rating: 3
Question on
      sort  September 14, 2002
Reviewer:  Kapil
       from INDIA
Hi Tom,
Well, that is an acceptable explaination.
With regards to the sort on disk, I have confirmed it through the trace file as
well as session statistics that the sort is taking place completely in memory.
Thanks anyway.

      Rating: 4
anlytical
      functions -- oracle 9i  September 26, 2002

Reviewer:  john  from USA
tom,
where can find the info on anlytical functions for oracle 9i

thanks

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96520/analysis.htm#10981

      Rating: 4
Why isn't 4
      rows more "correct" here ?  September 26, 2002

Reviewer:  Robert Chin  from PA
Tom, can the analytical function , like the one you demonstrated, return the
same result as the first, "old-fashioned" method ? :

    DEPTNO        SAL ENAME      HIREDATE
---------- ---------- ---------- ---------
        10       5000 KING       17-NOV-81
        20       3000 SCOTT      09-DEC-82
        20       3000 FORD       03-DEC-81
        30       2850 BLAKE      01-MAY-81

Also, can you explain why this result is not *more* correct than the ones
returning 3 rows, excluding either SCOTT or FORD, or this is just a subjective
thing depening on requirement ?
Thanks

          Followup:

It is all subjective.

Yes, analytics can return the 4 row answer.

scott@ORA920.US.ORACLE.COM> l
  1  select *
  2    from (
  3    select deptno,
  4           sal,
  5          ename,
  6          hiredate,
  7          rank() over (partition by deptno order by sal desc ) rnk
  8      from emp
  9    )
 10* where rnk = 1
scott@ORA920.US.ORACLE.COM> /

    DEPTNO        SAL ENAME      HIREDATE         RNK
---------- ---------- ---------- --------- ----------
        10       5000 KING       17-NOV-81          1
        20       3000 SCOTT      19-APR-87          1
        20       3000 FORD       03-DEC-81          1
        30       2850 BLAKE      01-MAY-81          1

scott@ORA920.US.ORACLE.COM>

      Rating: 4
Can table size
      affect analytical functions  October 15, 2002

Reviewer:  A reader
Hi Tom
I have a query which is using first_value and when I run the query on the
original table, which has a size of 1.2 Gigabytes, the query is aborted with the
error 3113, but If I create a table with the 28 records involved in the logic
and I run the query using that new little table and I get the results.
Is it possible that the size of the table can affect the execution of the query?
Thanks in advance

          Followup:

3113 = contact support, open a tar, probably got a bug.

      Rating:
      3
  February 25, 2003
Reviewer:
      A reader
Hi Tom,
In our DW we have a table which stores the currency rates, it stores around
million records, and uses a effective date to show the effectivness of the
record,
Table structure
currencyid    char(3)
exchrate      number(21,4)
Effectivedate date

What i need is to write a query which fetches the effective record as per a
particular date, i have made a function but the function makes the query become
very slow as the query returns about 100,000 rows.
Is there a way to speeden things here, I can make a Materlialised view or view,
but not any procedure to create a table.
To sum up what i need is effected record as per that day, since the day maybe
anything date we need to retrieve from the DW table the effective rate.
Can we make a View which gives the records from date Say 01-01-1999 till today 1
row per day and ist corresponding exchange rate.

          Followup:

how slow is slow?  how fast is fast?

anyway -- something like:

select *
  from (
select a.*,
       row_number() over (partition by currencyid order by effectivedate desc)
rn
  from t a
 where effectivedate <= to_date( :your_date, 'mm-dd-yyyy' )
       )
 where rn = 1;

that'll get the record for the max effective date by currency id.

      Rating: 2
What is the
      best (most efficient) way to do that?  July 13, 2003

Reviewer:  j.
hi tom,

I'm looking for a way to get an ordered result set divided into "buckets" each
containing a certain (predefined) number of elements and get consecutive numbers
assigned to those groups generated.

for example: each group should contain 2 entries

GeneratedGroupNumber   SomeOrderedValue
        1                    -10
        1                     24.3
        2                     55
        2                    103
        3                    234.8

I haven't found any solution to achieve this so far (unfortunately NTILE doesn't
fit). can you please give me a hint?

          Followup:

scott@ORA920.US.ORACLE.COM> select trunc((rownum-0.01)/2), sal
  2    from ( select sal from emp order by sal )
  3  /

TRUNC((ROWNUM-0.01)/2)        SAL
---------------------- ----------
                     0        800
                     0        950
                     1       1100
                     1       1250
                     2       1250
                     2       1300
                     3       1500
                     3       1600
                     4       2450
                     4       2850
                     5       2975
                     5       3000
                     6       3000
                     6       5000

14 rows selected.

just use rownum and divide.

      Rating:
      3
  July 13, 2003
Reviewer:  j.

Incredible: How could I miss this simple solution? No need for analytical
functions at all. Thank you very much!

      Rating: 3
Query for
      you  July 18, 2003
Reviewer:  Abhijit
       from Mumbai
Hi Tom,
I have a query which is really puzzling me.
I have got three tables.
1) UNIT_INFO_TABLE
    UNIT_NUMBER (PK) (VARCHAR2),
    STATUS,
    MFG_ID (FOREIGNKEY)

    '100'
    'AVAILABLE'
    null
    '101'
    'AVAILABLE'
    null
    '102'
    'AVAILABLE'
    201
2) MFG_INFO TABLE
    MFG_ID (PK_KEY), MFG_NAME (VARCHAR2)
    200            VOLVO
    201            FORD
3) UNIT_PRICES TABLE
    ASSET_NUMBER, UNIT_NUMBER (FOREIGN KEY), DATE_IN_SERVICE, VALUE
    1001          '100'              02/05/1997       $200
    1001-1          '101'             03/04/1997       $350
    1002          '102'             04/05/1998       $450

I wish to display the asset summary for each asset and the date_in_service
column is the cause of my trouble.
I have written the following query so far.

SELECT UI.UNIT_NUMBER, UI.STATUS, MI.MFG_NAME, SUM(UP.VALUE)
    FROM UNIT_INFO UI,
         MFG_INFO MI,
         UNIT_PRICES UP
    WHERE UP.UNIT_NUMBER = UI.UNIT_NUMBER (+)
    AND UI.MFG_ID = MI.MFG_ID(+)
    AND UNIT_NUMBER LIKE '10%'
    GROUP BY UI.UNIT_NUMBER, UI.STATUS, MI.MFG_NAME

However I have to add the date_in_service column in the select list and it has
to
display the date_in_service of the main asset only. For example the values
without
an "-" in the column value is a main asset. How should I go about it?

Thanks & Regards,
Abhijit

          Followup:

doesn't make sense.  what gets displayed for rows where asset number contains a
'-' then.

      Rating: 3
assigning
      consecutive numbers to groups  July 24, 2003

Reviewer:  j.
hi tom,

I'm want to get CONSECUTIVE group numbers assigned (instead of existing
GENERATED number) to elements of an ordered result set.

sample: each group contains at most two elements.

Value  Generated GroupNo  Wanted GroupNo
  A           23                1
  B           24                2
  B           24                2
  C           25                3
  C           25                3
  D           26                4

... but I have not found the "pattern" to solve this problem so far. can you
please help?

          Followup:

ops$tkyte@ORA920LAP> select distinct x, y, dense_rank() over ( order by y )
  2    from t;

X          Y DENSE_RANK()OVER(ORDERBYY)
- ---------- --------------------------
A         22                          1
B         23                          2
C         25                          3
D         26                          4

4 rows selected.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create global temporary table temp
  2  ( x varchar2(1) primary key, z int )
  3  /

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> insert into temp
  2  select distinct x, dense_rank() over (order by y)
  3    from t;

4 rows created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> update ( select t.z old_z, temp.z new_z
  2             from t, temp
  3                    where t.x = temp.x )
  4    set old_z = new_z;

6 rows updated.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select * from t;

X          Y          Z
- ---------- ----------
A         22          1
B         23          2
B         23          2
C         25          3
C         25          3
D         26          4

6 rows selected.

      Rating: 2
My query has
      not yet been answered  July 28, 2003
Reviewer:
      Abhijit  from Mumbai
Hi Tom,
Why have you missed my query?

Regards,
Abhijit

          Followup:

i don't look at every review/followup - especially when I'm on vacation which I
was....

      Rating: 4
rewrite
      queries  September 30, 2003
Reviewer:
      PINGU_SAN
Hi

I have a query which uses conventional correlated query (which is damn slow!), I
tried to rewrite it into 2 other queries and both of these new queries return
different number of rows. The first query takes so long (over 9 hours when I
cancelled it!) that I never got the number of rows!

I am not sure but it seems that I am failing in the category of "queries do what
we want but returns different number of rows"

May you look and see if the logics are different in any sense...? Please thank
you!

ORIGINAL QUERY
-----------------
select count(*)
  from contrato_detalle_lineas cont_det,
       clientes cli,
       contratos cont
 where cont_det.estado_atis is null
   and cont_det.COD_EMPRESA = cli.COD_EMPRESA
   and cont_det.ID_CLIENTE = cli.ID_CLIENTE
   and cont_det.cod_empresa = cont.COD_EMPRESA
   and cont_det.ID_CLIENTE = cont.ID_CLIENTE
   and cont_det.COD_CONTRATO = cont.COD_CONTRATO
   and CONT.COD_SERVICIO = 'IMAIL'
   and cont_det.cod_empresa = 'TSAI'
   and cont_det.num_version_detalle = (select max(num_version_detalle)
                                         from contrato_detalle_lineas e
                                        where e.cod_empresa  =
cont_det.cod_empresa
                                          and e.id_cliente   =
cont_det.id_cliente
                                          and e.cod_contrato =
cont_det.cod_contrato
                                          and e.cod_contrato_detalle =
cont_det.cod_contrato_detalle
                                          and e.dom64_operacion_realizada !=
'SIN'
                                         and e.fch_registro is not null)
   and cont_det.dom64_operacion_realizada != 'BAJ';

SECOND REWRITTEN QUERY
------------------------
select count(*)
  from (select cli.id_fiscal,
               cont_det.num_version_detalle,
               cont_det.cod_empresa,
               cont_det.id_cliente,
               cont_det.cod_contrato,
               cont_det.cod_contrato_detalle,
               cont_det.dom64_operacion_realizada,
               cont_det.fch_registro
          from contrato_detalle_lineas cont_det,
               clientes cli,
               contratos cont
         where cont_det.estado_atis is null
           and cont_det.COD_EMPRESA = cli.COD_EMPRESA
           and cont_det.ID_CLIENTE = cli.ID_CLIENTE
           and cont_det.cod_empresa = cont.COD_EMPRESA
           and cont_det.ID_CLIENTE = cont.ID_CLIENTE
           and cont_det.COD_CONTRATO = cont.COD_CONTRATO
           and cont.cod_servicio = 'IMAIL'
           and cont_det.cod_empresa = 'TSAI'
           and cont_det.dom64_operacion_realizada != 'BAJ') x,
     (select y.cod_empresa, y.id_cliente, y.cod_contrato,
y.cod_contrato_detalle,
             max(y.num_version_detalle)
             over (partition by y.cod_empresa, y.id_cliente,
                                y.cod_contrato, y.cod_contrato_detalle)
maxversion
        from contrato_detalle_lineas Y
       where y.fch_registro is not null
         and y.dom64_operacion_realizada != 'SIN') z
 where x.num_version_detalle = z.maxversion
   and x.cod_empresa  = z.cod_empresa
   and x.id_cliente   = z.id_cliente
   and x.cod_contrato = z.cod_contrato
   and x.cod_contrato_detalle = z.cod_contrato_detalle;

THIRD REWRITTEN QUERY
-----------------------
select count(*)
  from (select cli.id_fiscal,
               num_version_detalle,
               cont_det.cod_empresa,
               cont_det.id_cliente,
               cont_det.cod_contrato,
               cont_det.cod_contrato_detalle,
               cont_det.dom64_operacion_realizada,
               cont_det.fch_registro,
               max(case when fch_registro is not null and
dom64_operacion_realizada != 'SIN'
                        then num_version_detalle
                        else null
                        end)
               over (partition by x.cod_empresa, x.id_cliente,
                                  x.cod_contrato, x.cod_contrato_detalle)
maxversion
          from contrato_detalle_lineas cont_det,
               clientes cli,
               contratos cont
         where cont_det.estado_atis is null
           and cont_det.COD_EMPRESA = cli.COD_EMPRESA
           and cont_det.ID_CLIENTE = cli.ID_CLIENTE
           and cont_det.cod_empresa = cont.COD_EMPRESA
           and cont_det.ID_CLIENTE = cont.ID_CLIENTE
           and cont_det.COD_CONTRATO = cont.COD_CONTRATO
           and cont.cod_servicio = 'IMAIL'
           and cont_det.cod_empresa = 'TSAI'
           and cont_det.dom64_operacion_realizada != 'BAJ') x ) z
  where num_version_detalle = maxversion;

FOURTH REWRITTEN QUERY
------------------------
select count(*)
 from (select
              x.*,
              max(case when fch_registro is not null and
dom64_operacion_realizada != 'SIN'
                       then x.num_version_detalle
                       else null
                   end)
              over (partition by x.cod_empresa, x.id_cliente,
                                 x.cod_contrato, x.cod_contrato_detalle)
maxversion
         from (select cli.id_fiscal,
                      num_version_detalle,
                      cont_det.cod_empresa,
                      cont_det.id_cliente,
                      cont_det.cod_contrato,
                      cont_det.cod_contrato_detalle,
                      cont_det.dom64_operacion_realizada,
                      cont_det.fch_registro
                 from contrato_detalle_lineas cont_det,
                      clientes cli,
                      contratos cont
                where cont_det.estado_atis is null
                  and cont_det.COD_EMPRESA = cli.COD_EMPRESA
                  and cont_det.ID_CLIENTE = cli.ID_CLIENTE
                  and cont_det.cod_empresa = cont.COD_EMPRESA
                  and cont_det.ID_CLIENTE = cont.ID_CLIENTE
                  and cont_det.COD_CONTRATO = cont.COD_CONTRATO
                  and cont.cod_servicio = 'IMAIL'
                  and cont_det.cod_empresa = 'TSAI'
                  and cont_det.dom64_operacion_realizada != 'BAJ') x ) z
  where num_version_detalle = maxversion;

The third and fourth I suspect they are the same.

Also in your first reply I think only the query with analytic functions achieve
what we are looking for no? The first query only returned one row with 3000
salary which cannot be correct in real world...!

      Rating: 5
about last part
      of question  September 30, 2003
Reviewer:
      A reader
sorry I should have said analytic function does not give correct solution!

      Rating: 5
How would you
      substr  October 16, 2003
Reviewer:
      A reader
Tom in the below query how would you substr if the query retrieves multiple
varchar2 columns whose data length varies from row to row.

select deptno,
  2             to_number(substr( max_sal, 1, 10 )) sal,
  3             substr( max_sal, 20 ) ename,
  4             to_date( substr( max_sal, 11, 9 ) ) hiredate
  5    from (
  6  select deptno, max( to_char( sal,'fm0000000009') || hiredate || ename )
max_sal
  7    from emp
  8   group by deptno
  9             )
 10  /

          Followup:

you would encode them using a fixed width format

max( to_char(sal, 'fm000000009' ) ||
     nvl( to_char(hiredate,'yyyymmddhh24miss'), rpad( ' ', 14 ) ) ||
     nvl( rpad( ename, 10 ), rpad( ' ', 10 ) ) DATA

(the last column doesn't have to be padded out obviously)...

now:

to_number( substr( data, 1, 9 ) ) SAL,
to_date( substr( data, 10, 14 ), 'yyyymmddhh24miss' ) ) hiredate,
substr( data, 24 ) ename

gets your data back (use rtrim when appropriate as well)

      Rating: 4
Maximum Records
      on a Day  November 07, 2003
Reviewer:  Rohit
      Gulati, Ambala India  from INDIA
Sir,
     We have a table which contains data about workshop jobs opened. We want
that day which has highest no of jobs opened through a single sql command.
Is it possible two have two dates having the highest no of job opened using a
single sql command.

with kind regards,

          Followup:

select *
  from ( select trunc(date_column) theDay, count(*) theCnt
           from table
          order by 2 desc )
 where rownum <= 2

November 07, 2003 -- Thanks for the question regarding "Limit the
      object created for a user", version 8.1.7

      You Asked
Hi Tom,

  I wonder whether Oracle can limit the object created (say 10) for a particular
user. Is it possible to implement such requirement?

Thanks,
David

      and we said...
you can use a BEFORE CREATE trigger to count the number of existing objects in
the schema and fail it if it exceeds your maximum.

ops$tkyte@ORA817DEV> create or replace trigger no_more_than_10
  2  before create on database
  3  declare
  4          l_cnt   number;
  5  begin
  6          if ( user in ( 'A', 'B', 'C' ) )
  7          then
  8              select count(*) into l_cnt
  9                from dba_objects
 10               where owner = USER
 11                 and object_type = 'TABLE';
 12
 13              if ( l_cnt >= 10 )
 14              then
 15                      raise_application_error( -20001, 'You can only do 10
things!' );
 16              end if;
 17          end if;
 18  end;
 19  /

Trigger created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> drop user a cascade;

User dropped.

ops$tkyte@ORA817DEV> create user a identified by a;

User created.

ops$tkyte@ORA817DEV> grant create session, create table to a;

Grant succeeded.

ops$tkyte@ORA817DEV> alter user a quota unlimited on users default tablespace
users;

User altered.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> @connect a/a
ops$tkyte@ORA817DEV> set termout off
a@ORA817DEV> REM GET afiedt.buf NOLIST
a@ORA817DEV> set termout on
a@ORA817DEV>
a@ORA817DEV> begin
  2          for i in 1 .. 10
  3          loop
  4                  execute immediate 'create table t' || i || ' (x int)';
  5          end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

a@ORA817DEV> create table t11 ( x int );
create table t11 ( x int )
*
ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 1
ORA-20001: You can only do 10 things!
ORA-06512: at line 13

Review &
      Followup

      Rating:
      2
  November 05, 2003
Reviewer:  RK
       from Berwyn, PA
A before create trigger on the entire database!?! In an environment where there
is a lot of DDL flying around(as it would be in this case where they actually
want to limit people to only 'x' creations), wouldn't that be bad performance
wise?

Couldn't this be done via a stored proc and forcing 'restricted' users to come
through that proc? This would let everybody else be free from the overhead of
this trigger execution?

          Followup:

in an environment where you have lots of DDL flying around, you've got much much
much larger problems.

I do not see this being an issue -- DDL is so hugely expensive as it is, this is
like adding a single straw to a hay bale.

I do not consider this an issue..  Everyone else not restricted by this won't be
materially affected.

This is the easiest, most correct way to do it.

      Rating: 5
Is this trigger
      be the first event fired?  November 06, 2003

Reviewer:  A reader
Hi Tom,

  Thanks for you suggestion. Don't know this trigger before. Is this trigger
(before create on database or before create on schema) the first event fired
after the user issue the "create ..." command in Oracle? i.e. such as updating
any data dictionary (no redo or undo generation)?

Thanks,
David

          Followup:

it'll be one of the first, there will be minimal redo/undo generated.

      Rating: 4
It works but
      ...  November 06, 2003
Reviewer:  Orca
       from Germany
a rdbms should be able to control the max number of user objecs via :

ALTER USER ORCA MAX_NUM_OBJECTS = 100;

Orca

          Followup:

well, basically you are saying "an rdbms should do absolutely every single thing
we could ever dream of"

if you ask me -- no, the rdbms SHOULD NOT have such a thing.

we have a mechanism by which you can efficiently extend the server to do
whatever wild/crazy things you want.

it cannot do "everything" under the sun.

      Rating: 5
ALTER USER ORCA
      MAX_NUM_OBJECTS = 100;  November 06, 2003

Reviewer:  Kal
Then you would need max_num_ based based on different object types :-) I can't
really think of a case when limiting the number of objects created by a user
would be required.

      Rating: 5
MAX_NUM_OBJECTS
      ...  November 07, 2003
Reviewer:  Orca
       from germany, born in a 2000 year old city build by the roman
      imperator augustus :-))
ok!
Do not went to make an elephant of a fly,
with MAX_NUM_OBJECTS simply the nummer in user_objects is restricted, and the
default is unlimited.

The danger of the oracle possibilties shown in in the database-trigger example
is that there is to much custom code instead of a solid solution. This i
expeirences is similar to unix where you have a lot of mighty tools, and tend
very often to things custom instead thinking of a solid solution.

But sure not everything is need to implement.
Orca

PS.: but anyway i love the oracle server and a lot more the pl/sql language

          Followup:

hey -- i live in unix/linux.  I'm a whole lot less "custom" then each and every
unique windows "pc" i've ever seen.   If you ask me -- windows lends itself to
the 'problem' you are ascribing to unix.

But anyway -- I totally consider this trigger a "solid solution".

o it is small
o it is concise
o it clearly does what it intends to do
o if you cannot manage a single small trigger in your solution, you are probably
missing a piece of "change managment" software.

This is *not any different* then a subroutine in a large module.  No different,
no less "solid".

Another example.  Customer requests:

"Hey, I'd like Oracle to make it so I could grant "alter these 20 users" to some
user -- not alter user which gives them all users, but these 20"

"Hey, well, I'd like Oracle to make it so this table over here that has username
to users they are allowed to alter would be used.  So, I'd like the grant to
actually look at this table to see if george is able to alter mary's password"

"Hey, well thats all well and fine, I'd like Oracle to make it so this user can
alter any account that beging with APP_"

"Hey -- requests ad-infinitum...... all variations on a theme"

No way possible implement every thing -- but the database has this powerful
construct:

o stored procedures
o that run with definers rights

so, I can create a procedure:

create procedure reset_password( p_username in varchar2, p_password in varchar2)
as
begin
   if ( USER='GEORGE' and p_username in ( 'MARY', 'BILL', 'SUE' ) )
   then
      execute immediate 'alter user ' || p_username || ' identified by ' ||
                   p_password;
   else
      send_email_to_admin( 'george trying to change ' || p_username );
      raise_application_error( -20001, 'George, you cannot do that' );
   end if;
end;
/

grant execute on reset_password to george;

Now, since that procedure executes with the base privs of the definer -- and
assuming the definer had alter user, we just were able to implement the first
customers requirement in a couple of seconds!  It would be just as easy to do #2
and #3 as well.  In fact -- any of the infinite number of "variations on a
theme" are possible this way.  And are as secure as you make them (see the nice
"you are busted" email i generated in there).

Anyway, extensibility is a good thing

It does not make things less solid

In fact, looking at windows with the "we'll try to do it all and make it appear
trivial so a monkey can be certified to run your systems" approach seems to be
having certain issues in the area of "maintainability", "securability" and other
ilities as well.

November 07, 2003 ASHRAF -- Thanks for the question regarding
      "oracle 9i", version 9i

      You Asked
Dear tom
first thanks for your site which is my anual browse every day

i have the following :

1- why oracle chose appach as web server and don't let us working with for
example iis ?

2- i have about 3000 word document in the followin format

observation           content       agent
------------------------------------------
data                  data           data

it's like table as above i need to installd in the document's in the database so
i can search for agent number and then  i can get observation and content ?

best reg

ashraf gamal

      and we said...
1) we are all about open systems, portability and choice.

IIS is all about windows, windows, windows.

Actually, you can use IIS if you so choose.  See

http://technet.oracle.com/docs/products/ias/doc_library/1022doc_otn/index.htm
for details.

2) cut and paste the table into EXCEL.  Save it as CSV.  SQLLDR can easily load
CSV files for you.

followup to comment two

fact: MS Windows 2000 SP1
fact: Oracle9i Application Server Enterprise Edition 1.0.2
symptom: Installing only HTTP server
symptom: ORAIISP8.DLL for MS IIS Plug-in Component is Missing From the HTTP
Installation
cause: The installation guide incorrectly states the Oracle Plug-in for MS IIS
Plug-in component is installed during the HTTP only install.

fix:

Run the installer and select the STANDARD or Enterprise installation
to install the Oracle MS IIS Plug-in component.

Review &
      Followup

      Rating: 3
thank's
      tom  June 30, 2001
Reviewer:  ashraf gamal
       from kuwait

      Rating: 3
where can i
      find the file call oraiisp8.dll  June 30, 2001

Reviewer:  ashraf gamal  from kuwait
is the file located at on oracle database cd or in oracl9i application server

      Rating: 2
Oracle 9i
      Installation  November 07, 2003
Reviewer:
      Denise  from Virginia, USA
Hi Tom

was searching on your site for some 9i installation tips
and could only find this thread....sorry if this is in the
wrong topic.

I am ecstatic to report that I've been given the go ahead
to use an old testserver as I please. I feel like a kid with
a new 'toy'.

My plans are to install oracle 9i(v. 9.0.2) on a NT4 OS..
and I must emphasize it is the ENTERPRISE EDITION!

The server currently has 8.1.7 SE.

questions:

should I perform the 9i install on a 'clean' machine...
remove all 8.1.7 files...registry etc...or can I simply
install 9i over 8.1.7 and perform a upward migration?

preferably I would like to start with a new fresh db.

can I export/import 8.1.7 tables to 9i?

any tips on the best way to do this is greatly appreciated.

I can't wait to start playing with table partitioning and
Analytic Functions....also developing gui forms.

as always....THANKS!

          Followup:

902?  hopefully you mean 920

you should read the migration guide (available on otn.oracle.com if you don't
have one).  it lays out all of the options.  you can do a direct upgrade if you
like. you can export/import.

the migration guide covers the in's and out's of each.

      Rating: 3
Oracle 9i
      version  November 07, 2003
Reviewer:  Denise
       from Virginia, USA
well Tom....essentially it is version 9.0.1.1.1
I took a second look...it is the version that was used
in my Oracle courses at school(2 years ago).

Hey...I'm happy that I am being allowed to install
any version of Oracle with a '9' on it.

thanks for the documentation tips..I also remembered
browsing the CD and saw installation documentation there
as well.

I'm off to the oracle 'wild blue yonder'

thanks   ;~)

November 07, 2003 Andre -- Thanks for the question regarding
      "TIMESTAMP columns in DBA_OBJECTS", version 8.1.6

      You Asked
What´s the meaning of the TIMESTAMP column in the DBA_OBJECTS
view ? It´s confusing. The reference manual says it the time of
the "specification" for the object. What does that mean ?

Besides, which of the columns in DBA_OBJECTS reflects the time
of a REPLACE action (like in CREATE OR REPLACE PROCEDURE) ?

Thanks

      and we said...
The timestamp is used in remote dependency checking.  It is the
time of the last update that changed the specification of the
object.  If the object is a PACKAGE -- the "specification" is
clear.  If the object is a VIEW, the specification is perhaps
not as clear.  The specification of a view is the number, names,
and types of columns it returns.  Same with other objects --
their specification is their "external interface".  Anytime
their external interface has changed -- their timestamp will
change.

We use this timestamp to manage dependencies between remote
objects (eg: in a distributed system where we have local
procedures that call remote procedures -- we have to invalidate
local procedures that depend on remote procedures whose
timestamps have changed).

CREATED = date of creation of the object

LAST_DDL_TIME = last ddl on object, would include CREATE OR
REPLACE (example below)

TIMESTAMP = last time the external "view" or "specification" of
the object changed -- will be between created and last_ddl_time.

here is an example with a view:

tkyte@TKYTE816> create view v
  2  as
  3  select * from dual;

View created.

tkyte@TKYTE816>
tkyte@TKYTE816> select timestamp, to_char(created,'hh24:mi:ss'),
  2         to_char(last_ddl_time,'hh24:mi:ss')
  3    from dba_objects
  4   where object_name = 'V'
  5     and owner = USER
  6  /

TIMESTAMP           TO_CHAR( TO_CHAR(
------------------- -------- --------
2000-12-13:13:11:26 13:11:26 13:11:26

it starts with the timestamp = created = last_ddl_time.  Now,
lets alter the view (doesn't change its EXTERNAL interface):

tkyte@TKYTE816>
tkyte@TKYTE816> exec dbms_lock.sleep(2)

PL/SQL procedure successfully completed.

tkyte@TKYTE816>
tkyte@TKYTE816> alter view v compile
  2  /

View altered.

tkyte@TKYTE816> select timestamp, to_char(created,'hh24:mi:ss'),
  2         to_char(last_ddl_time,'hh24:mi:ss')
  3    from dba_objects
  4   where object_name = 'V'
  5     and owner = USER
  6  /

TIMESTAMP           TO_CHAR( TO_CHAR(
------------------- -------- --------
2000-12-13:13:11:26 13:11:26 13:11:28

Now, the timestamp did not change -- it is still the same as
the created.  The last_ddl_time did however change as we just
did some DDL on the view.  We can in fact do some DDL on the
view you might think would change the timestamp:

tkyte@TKYTE816>
tkyte@TKYTE816> exec dbms_lock.sleep(2)

PL/SQL procedure successfully completed.

tkyte@TKYTE816> create or replace view v
  2  as
  3  select * from dual;

View created.

tkyte@TKYTE816>
tkyte@TKYTE816> select timestamp, to_char(created,'hh24:mi:ss'),
  2         to_char(last_ddl_time,'hh24:mi:ss')
  3    from dba_objects
  4   where object_name = 'V'
  5     and owner = USER
  6  /

TIMESTAMP           TO_CHAR( TO_CHAR(
------------------- -------- --------
2000-12-13:13:11:26 13:11:26 13:11:30

But it does not -- the "signature" or external interface of
the view did not change.  If we do a create or replace that
changes the interface of the view:

tkyte@TKYTE816>
tkyte@TKYTE816> exec dbms_lock.sleep(2)

PL/SQL procedure successfully completed.

tkyte@TKYTE816> create or replace view v
  2  as
  3  select dummy x from dual;

View created.

tkyte@TKYTE816>
tkyte@TKYTE816> select timestamp, to_char(created,'hh24:mi:ss'),
  2         to_char(last_ddl_time,'hh24:mi:ss')
  3    from dba_objects
  4   where object_name = 'V'
  5     and owner = USER
  6  /

TIMESTAMP           TO_CHAR( TO_CHAR(
------------------- -------- --------
2000-12-13:13:11:32 13:11:26 13:11:32

tkyte@TKYTE816>

And so now the timestamp is different since the
"specification" of this view is different.
Review &
      Followup

      Rating: 5
timestamp and
      synonym  February 25, 2003
Reviewer:  Saradha
      Bavanandam  from NJ, USA
Tom,

CREATE OR REPLACE PROCEDURE command does alter the
LAST_DDL_TIME as well as the TIMESTAMP column eventhough
the specification is not changed.

SQL> create or replace procedure test_timestamp as
  2  col1 number;
  3  begin
  4  col1 := 10;
  5  end;
  6  /

Procedure created.

SQL> SELECT created,last_ddl_time,timestamp from user_objects where object_name
= 'TEST_TIMESTAMP'
/
CREATED             LAST_DDL_TIME       TIMESTAMP
------------------- ------------------- -------------------
2003-02-25.08.52.32 2003-02-25.08.52.32 2003-02-25:08:52:32

I recompile it again..

SQL> create or replace procedure test_timestamp as
  2  col1 number;
  3  begin
  4  col1 := 10;
  5  end;
  6  /

Procedure created.

SQL> SELECT created,last_ddl_time,timestamp from user_objects where object_name
= 'TEST_TIMESTAMP'
/
CREATED             LAST_DDL_TIME       TIMESTAMP
------------------- ------------------- -------------------
2003-02-25.08.52.32 2003-02-25.08.54.11 2003-02-25:08:54:11

Our problem is:

A local database procedure which is accessing a remote procedure through a
database link is becoming INVALID after the remote procedure had been
re-compiled and after that we never able to execute the local procedure at
all...

The scenario is.

Assume initially the remote procedure is compiled and also the local procedure
compiled and everything is working fine.

And one week-end we re-compile the remote procedure..because
there is some logic change in the procedure.

On Sunday Morning:

REMOTE: SQL> create or replace procedure test_timestamp as
  2  col1 number;
  3  begin
  4  col1 := 10;
  5  end;
  6  /

Procedure created.

On Monday we noticed that the local procedure "CALL_TIMESTAMP" which is calling
the remote "TEST_TIMSTAMP" has become INVALID because of the remote procedure is
re-compiled. (which is O.K)
But we could not get the local procedure (CALL_TIMESTAMP)work after that..

In the local database we try to access the remote
procedure...through a public synonym created in the local database for the
remote procedure (TEST_TIMESTAMP).

LOCAL:SQL> DESC TEST_TIMESTAMP

02019, 00000, "connection description for remote database not found"

But if I try to qualify like this..gives no problem.

LOCAL:SQL> DESC SCOTT.TEST_TIMESTAMP@REMOTE.ORACLEDB.COM
TEST_TIMESTAMP

The database link and the synonym in the local database are
created like this..

LOCAL:SQL>CREATE PUBLIC DATABASE LINK 'REMOTE.ORACLEDB.COM'
CONNECT TO SCOTT IDENTIFIED BY TIGER USING 'REMOTE.ORACLEDB.COM'
/

LOCAL:SQL>CREATE PUBLIC SYNONYM TEST_TIMESTAMP FOR
SCOTT.TEST_TIMESTAMP@REMOTE.ORACLEDB.COM
/
Synonym Created.

We dropped and re-created the database link as well as the
synonym and still the local procedure "CALL_TIMESTAMP" is
not compiling successfully. It does not give any compilation
error but the STATUS is INVALID.

Just to make sure, we commented the reference to TEST_TIMESTAMP and re-compiled
the local procedure and compiled it successfully and the status is VALID. And we
put back the TEST_TIMESTAMP reference and re-compiled it and the STATUS has
become INVALID with no compilation error.

Finally what we did was,

We created totally a new procedure with a different name
(say TEST_TIMESTAMP_NEW) but with the same source code as
the TEST_TIMESTAMP in the remote database and try to access
the new procedure (TEST_TIMESTAMP_NEW) from the local procedure (CALL_TIMESTAMP)
and it is working fine.

We just could not figure out what could be the reason.
Is it something to do with the time delay, the time the REMOTE procedure is
re-compiled and the time the local procedure is first executed. In this case the
REMOTE procedure got re-compiled on SUNDAY morning and the LOCAL procedure has
been first accessed only on Monday.

Please guide us on this...

The remote database is on a HP server and the local database is on a NT server.
The database version on both
server is 8.1.7.4

Thanks
Saradha

          Followup:

set remote_dependencies_mode to signature

(and use PACKAGES -- never procedures, break that dependency change.  no change
in spec, great just compile the BODY.  it would solve this issues as well)

      Rating:
      4
  March 04, 2003
Reviewer:  Saradha
      Bavanandam  from NJ, USA
Tom,

I still do not understant why it is not allowing me to
recompile my local procedure successfully after the remote
procedure is recompiled. I understand the local procedure
is getting invalid after the remote procedure is recompiled. But why I am not
able to recompile my local procedure that references the remote procedure. The
compilation gives no error but the status remain INVALID.

Does the time delay matters ? I try to execute or recompile my local procedure
after more than 12 hours after the remote procedure is compiled.

Thanks
Saradha

          Followup:

it shouldn't, you can contact support for that.

      Rating: 5
compilation and
      weblogic server  July 30, 2003
Reviewer:
      Pushparaj Arulappan  from NJ,USA
Tom,

We have couple of packages and procedures compiled in
our database that are being called from the Java.

These procedures and packages are common that these are
being called by multiple applications.

Whenever we re-compile one of these package, the WEB
application team screams on us. They say, they have to
recylce their weblogic server everytime we recompile
any of these procedures or packages. It seems that the
the connection pool from the wep application keeps the
timestamp of these programs and when it sees it is modified
and it complains.

Could you please give us advice on these. It's only problem
for the WEB application because other application uses VB
and so no problem even we re-compile any number of times.

Is there any settings that we can do, or the wep application
team can do in their weblogic server to take care of this.

          Followup:

seems like a weblogic bug does it not?  I mean -- why the heck would they do
that.

Sorry, don't know what to say -- I cannot modify the code in weblogic.

      Rating: 5
Does Timestamp
      change on Auto recompile??  July 31, 2003

Reviewer:  H.S.Anand  from New Delhi, India

Hi tom,
Does the timestamp of the object change, when it is lying in the database in
"invalid" state and is automatically recompiled when a call is placed?
If it does, in such a case, how would one find out when an object was actually
modified or state was changed due to an auto-recompile.
Actually in one of the data centers, the librarian asked me this question,
because he wanted to know exactly when somebody fired a modified code in the
production server??
And I did not have a very convincing answer, b'coz i believe auto recompile also
changes the timestamp.
How correct am i? Please advice.
Regards,
Anand

          Followup:

simple simulation:

ops$tkyte@ORA920> create or replace procedure p
  2  as
  3  begin
  4          null;
  5  end;
  6  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace procedure p2
  2  as
  3  begin
  4          p;
  5  end;
  6  /

Procedure created.

P2 is dependent on P

ops$tkyte@ORA920> alter session set nls_date_format = 'dd-mon-yyyy hh24:mi:ss';
Session altered.

ops$tkyte@ORA920> select object_name, last_ddl_time from user_objects where
object_name in ('P','P2' );

OBJECT_NAME                    LAST_DDL_TIME
------------------------------ --------------------
P2                             31-jul-2003 07:14:55
P                              31-jul-2003 07:14:55

both objects are currently valid and compiled "as of" that time

ops$tkyte@ORA920> exec dbms_lock.sleep(2);
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> alter procedure p compile;
Procedure altered.

ops$tkyte@ORA920> select object_name, last_ddl_time from user_objects where
object_name in ('P','P2' );

OBJECT_NAME                    LAST_DDL_TIME
------------------------------ --------------------
P2                             31-jul-2003 07:14:55
P                              31-jul-2003 07:14:58

Now, the compilation of P, which invalidated P2 affects ONLY P's
last_ddl_time.  P2 hasn't been compiled so its last_ddl_time is unaffected
until...

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_lock.sleep(2);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec p2;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select object_name, last_ddl_time from user_objects where
object_name in ('P','P2' );

OBJECT_NAME                    LAST_DDL_TIME
------------------------------ --------------------
P2                             31-jul-2003 07:15:00
P                              31-jul-2003 07:14:58

we run it and it compiled itself

So, the last ddl time for an invalid object is the time of its last compile (not
when it went invalid).

      Rating:
      4
LAST_DDL_TIME  November 06, 2003
Reviewer:
      Ravi  from Chicago, IL
Tom,

Is there a way to find out what was the last DDL on a table?

Thanks

          Followup:

LAST_DDL_TIME in user_objects.

  1* select object_name, last_ddl_time from user_objects where object_name = 'T'
ops$tkyte@ORA920LAP> /

OBJECT_NAME                    LAST_DDL_TIME
------------------------------ --------------------
T                              06-nov-2003 10:43:38

ops$tkyte@ORA920LAP> alter table t add yy int;

Table altered.

ops$tkyte@ORA920LAP> select object_name, last_ddl_time from user_objects where
object_name = 'T'
  2  /

OBJECT_NAME                    LAST_DDL_TIME
------------------------------ --------------------
T                              06-nov-2003 16:44:16

ops$tkyte@ORA920LAP>

      Rating: 5
Thank you but
      .. ..  November 07, 2003
Reviewer:
      A reader
Tom,

I was looking for the actual DDL statement itself but not the timestamp.  Sorry
for the confusion.

Thanks

          Followup:

you'd have to either

a) enable AUDITING (eg: no code, run some DDL, audit what is done in your
database)

b) create event triggers for before alter/create/etc.  In 9i you have the
function ora_sql_txt to capture the actual ddl, in 8i you can get some, but not
all of the information using event attribute functions.  See the APPLICATION
DEVELOPERS guide (fundementals) for a list of what event triggers you have and
the functions available.

November 07, 2003 KK -- Thanks for the question regarding "create a
      file for excel", version

      You Asked
Can I create a file in Excel format
by using procedure(utl_file)
or SQL*Plus functions?

      and we said...
Yes, excel is quite happy with CSV files (comma separated
values).  Here is a simple stored procedure that shows this at
work:

create or replace function  dump_csv( p_query     in varchar2,
                                      p_separator in varchar2
default ',',
                                      p_dir       in varchar2 ,
                                      p_filename  in varchar2 )
return number
is
    l_output        utl_file.file_type;
    l_theCursor     integer default dbms_sql.open_cursor;
    l_columnValue   varchar2(2000);
    l_status        integer;
    l_colCnt        number default 0;
    l_separator     varchar2(10) default '';
    l_cnt           number default 0;
begin
    l_output := utl_file.fopen( p_dir, p_filename, 'w' );

    dbms_sql.parse(  l_theCursor,  p_query, dbms_sql.native );

    for i in 1 .. 255 loop
        begin
            dbms_sql.define_column( l_theCursor, i,
l_columnValue, 2000 );
            l_colCnt := i;
        exception
            when others then
                if ( sqlcode = -1007 ) then exit;
                else
                    raise;
                end if;
        end;
    end loop;

    dbms_sql.define_column( l_theCursor, 1, l_columnValue, 2000
);

    l_status := dbms_sql.execute(l_theCursor);

    loop
        exit when ( dbms_sql.fetch_rows(l_theCursor) <= 0 );
        l_separator := '';
        for i in 1 .. l_colCnt loop
            dbms_sql.column_value( l_theCursor, i, l_columnValue
);
            utl_file.put( l_output, l_separator || l_columnValue
);
            l_separator := p_separator;
        end loop;
        utl_file.new_line( l_output );
        l_cnt := l_cnt+1;
    end loop;
    dbms_sql.close_cursor(l_theCursor);

    utl_file.fclose( l_output );
    return l_cnt;
end dump_csv;
/

That takes in any query and creates a CSV file from it on the
server.  You can use this in sqlplus as well by removing the
calls to UTL_FILE and replacing them with dbms_output.put calls
instead.  Then, spool the results of the sqlplus session on the
client.

Also, see
http://osi.oracle.com/~tkyte/flat/index.html
for another sqlplus way to do this

Review &
      Followup

      Rating: 4
appending to
      the end of excel file  August 16, 2002
Reviewer:
      A reader
this is great! i have a view file in sql script that i save as text file. in
excel i did visual basic to run this script and this works. my problem is i have
to run the complete month over aqain to get all data. i can't seem to append to
the bottom of file.
any sugestions?
THANKS,
Lorenzo

          Followup:

l_output := utl_file.fopen( p_dir, p_filename, 'a' );

instead of

l_output := utl_file.fopen( p_dir, p_filename, 'w' );

      Rating:
      3
Performance  October 04, 2002
Reviewer:
      A reader
Hi, Tom,

Did you ever test the code with large number of reocrds
in a table?

I have a table with columns > 80 and number of records
> 500,000. When I exported table data to the file with this procedure, seems
take forever. I think it is because the "fetch" operation-- one at a time, slow
everything down. Do we have a workaroud to that please? Or do we have another
Package/method to run them as batch process to save time?

Thanks

          Followup:

search this site for array_flat (pro*c)

or goto asktom.oracle.com/~tkyte and get the unloader

or rewrite the code to use dbms_sql array processing (probably more work then
you want to do)

      Rating: 3
please check
      this  October 05, 2002
Reviewer:  aamir
       from K.S.A.
i'm just a little programmer... i just want to know to how can i lock a table so
that no can fetch the records except me?...

          Followup:

You would have to buy a database with extremely poor concurrency controls such
as Access, SQLServer or DB2.

In Oracle -- we don't block readers of information.

What is your goal, what is your requirement.  I might be able to help you
achieve your goal -- but not using the solution you are trying to use.

(oh and hey -- what the heck does this have to do with "create a file for
excel"???? especially since during the time you put this here -- I was actually
accepting questions??????  use the right place for asking questions -- the ask
question link on the home page.

      Rating: 4
Creating an
      Excel file in client machine  October 06, 2002

Reviewer:  Srikant  from India
That was great.  But Tom, is there any way in which I can save it in the
client's machine itself.  Actually my requirement will be like this.  When we
run a report, as they can convert it into a pdf or rtf file, the user should be
able to save the report in Excel format.  Any way in achieving this?

Thanks a lot for your priceless service.

Sri

          Followup:

If you are using oracle reports, output type can be set to CSV for excel.

If you are using forms, rewrite the routine to use text_io instead of utl_file

If you have sqlplus available, see

http://asktom.oracle.com/~tkyte/flat/index.html

      Rating: 3
Creating an
      Excel file from Oracle Reports  October 13, 2002

Reviewer:  Srikant  from India
Thanks for you reponse Tom.  But I couldn't achieve it.
I used the following settings in System Parameters for exporting my report in
Oracle Reports (Report Builder 6.0.8.11.3).
DesFormat: Data Type - Character, Width - 1024, Initial Value - csv
DesName: Data Type - Character, Width - 1024, Initial Value - c:\mycsv.csv
DesType: Data Type - Character, Width - 80, Initial Value - File
I am getting the following error: - REP-0826: Invalid Printer driver 'csv'
specified by parameter DESFORMAT
I have Office 2000 installed in my system and Excel is working fine.  Kindly
advice.

          Followup:

sigh, did you pull up the documentation to see what the values for these
parameters might be?

I did, look up DESFORMAT in the documentation for reports.  You'll see you can
get pdf, html, html with style sheets, rtf, DELIMITED <<<====, and XML.

      Rating: 3
create a file
      for excel using utl_file  March 05, 2003
Reviewer:
      S.Srinivasan  from Singapore
Hi Tom!

I am getting the following error when I use dump_csv funtion with test_dump_csv
procedure.

Could you please help me.

Thanks.
S.Srinivasan

sql>exec test_dump_csv;
BEGIN test_dump_csv; END;

*
ERROR at line 1:
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at "SYS.UTL_FILE", line 120
ORA-06512: at "SYS.UTL_FILE", line 204
ORA-06512: at "SIDAS.DUMP_CSV", line 15
ORA-06512: at "SIDAS.TEST_DUMP_CSV", line 5
ORA-06512: at line 1

          Followup:

set your utl_file_dir init.ora parameter do you can actually write files.

      Rating: 3
create a file
      for excel using utl_file_dir parameter in init.ora  March
      05, 2003
Reviewer:  S.Srinivasan  from Singapore

Hi Tom!

Thanks for your quick response. I have already set up the utl_file_dir in the
init.ora file. I would like to have a CSV output for a query.
Need your help.
Thanks.
S.Srinivasan

          Followup:

 14  begin
 15      l_output := utl_file.fopen( p_dir, p_filename, 'w' );
 16

see -- line 15 is the fopen.

ERROR at line 1:
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at "SYS.UTL_FILE", line 120
ORA-06512: at "SYS.UTL_FILE", line 204
ORA-06512: at "SIDAS.DUMP_CSV", line 15
ORA-06512: at "SIDAS.TEST_DUMP_CSV", line 5
ORA-06512: at line 1

your error -- coming from line 15

you either

o did not restart after putting the utl_file_dir in
o or the utl_file_dir is NOT set to include the directory you are using

the open of the file is failing -- make it so that you can actually open a file
in that directory.

Just code a small block

declare
    l_output        utl_file.file_type;
begin
    l_output := utl_file.fopen( 'directory', 'filename', 'w' );
end;

and put in the directory and file name you are trying to use.

When that fails -- fix it so it doesn't.

Then you'll be able to run dump_csv.

      Rating: 4
create a file
      for excel using utl_file_dir   March 06, 2003

Reviewer:  S.Srinivasan  from Singapore
Hi Tom!

Thank you very much for the response & Code. Wonderful!I got it.

The code I tried with invalid path. Now I specify the utl_file_dir path within
quotes(') as a file path, it works.

Once again Thank you very much.

      Rating: 5
if there is a '
      in p_query ....  March 06, 2003
Reviewer:
      Joe
hi,Tom, the dump_csv works great with the sql like :
select * from employee.
but it fail with the query with ' , like
select * from employee where name='joe',

I tried to use this query instead, but fail either.
...where name=\'joe\'

any suggesttion? thx.

          Followup:

to get a quote in a string:

'select * from employee where name = ''joe'' '

you use two quotes.  BUT, if possible -- do this:

dbms_application_info.set_client_info( 'joe' );
...
dump_csv( ...., 'select * from employee where name = userenv(''client_info'')',
..

for that will work like a BIND VARIABLE.

      Rating: 3
Struck with
      Reporting...  March 17, 2003
Reviewer:  Vj
       from india
Tom,

We have an application running on java, oracle 8.1.7...we generally have been
using crystal reports as a reporting engine...beleive me, we faced enough
problems..

also considering that the client want to print the reports in the line printer,
which we couldnt do using crystal.. the next best thing came to my mind was
PL/SQL..(i couldnt think of anything)

In the sense, pump the RECORDS required for report formatted properly using
PL/SQL procedures and dump it into the CLOB col in the table with session id
etc..

the front-end guys would take the report and start printing it to the line
printer...

1. I want to know, is this approach is right ?
2. Do you see an performance constraint on this ?
there might be some reports which can run upto 2000 pages..
3. Is there is any other alternate for this ?

Need your suggestion..

          Followup:

that is one approach.  another would be to have plsql use utl_file to create a
file on the server which you then send to the printer.

      Rating: 3
Struck with
      Reporting...  March 17, 2003
Reviewer:  Vj
       from india
Tom,

We have an application running on java, oracle 8.1.7...we generally have been
using crystal reports as a reporting engine...beleive me, we faced enough
problems..

also considering that the client want to print the reports in the line printer,
which we couldnt do using crystal.. the next best thing came to my mind was
PL/SQL..(i couldnt think of anything)

In the sense, pump the RECORDS required for report formatted properly using
PL/SQL procedures and dump it into the CLOB col in the table with session id
etc..

the front-end guys would take the report and start printing it to the line
printer...

1. I want to know, is this approach is right ?
2. Do you see an performance constraint on this ?
there might be some reports which can run upto 2000 pages..
3. Is there is any other alternate for this ?

Need your suggestion..

      Rating: 4
Excel output
      from a report  March 23, 2003
Reviewer:  S.L

hi tom,

  first sorry for asking a question in this way. when ever i try to ask a
question always i am getting that now you are not accepting any questions. so, i
am asking you this way.

  I read all the information given by you for the delimited output. But what is
my problem is i am able to convert my report output into delimited output and
able to view the file in excel. but the record of my output is displaying along
with the headers. I tried all the measures which i can but unable to find out
why the output is coming like this. can you help me and tell me the solution.

  thank you very much

          Followup:

try otn.oracle.com -> discussion forums -> developer

I've never written a report in my life.

      Rating: 4
Outputting Data
      from Reports To Excel  March 24, 2003
Reviewer:
      Nivas
hi tom,

    I have some tabular reports. When i used Desformat as Delimited and run the
report the first record of the report is displaying in the header line after the
headers like

 Empno   EName   Salary   Deptno      7796       Scott   10000  101.

  How can i avoid this situation.

  Need your help.....

  Thanks
   nivas.

      Rating: 5
got error with
      to_char function  March 27, 2003
Reviewer:
      A reader  from de, usa
Hi Tom:

when I ran:

declare
    l_rows    number;
begin
    l_rows := dump_csv('select to_char(hiredate, 'yyyymmdd hh24:mi:ss') from
scott.emp',
                ',',
                            '/tmp',
                            'test.dat' );

end;
/

got: SP2-0552: Bind variable "MI" not declared.

why is it?

thank you very much

          Followup:

'' to get a ' in a string of course....

l_rows := dump_csv('select to_char(hiredate, ''yyyymmdd hh24:mi:ss'') from
scott.emp'

      Rating: 5
create a file
      for excel  March 28, 2003
Reviewer:  Tarek
       from Italy
Hi Tom,
it's the first time i'm writing in your form, hope this is
the correct way. It's a few days i started reading your form,
it's very helpful (although a little bit difficult.. i'm a newbie).
I tried this procedure and function and it works great.
One thing. When executing the procedure test_dump_csv is there a way to run the
query dinamically? I mean is it possible to put instead of the query a variable
and then when running the procedure supply the query i want to run?
Thanks a lot.

I know almost nothing about PL/SQL. Where can i find a simple book that teaches
it? (very simple, not for gurus!!)

          Followup:

the query by definition is "dynamic"....

you can:

exec dump_csv( '&Query', ',', '/tmp', 'foo.txt' );

and that'll ask you for a query at runtime in sqlplus.

I suggest:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96624/toc.htm
to learn PLSQL -- it is just a language like anyother, with control structures
and so on.  It is not a terribly hard language.  Move onto the books from
bookstores only after reading that one.  You need to have the foundation before
being told how to do stuff in the language.

      Rating: 4
create a file
      or excel  March 28, 2003
Reviewer:  Tarek

I tried your tip but get this error:
SQL> exec dump_csv('select * from all_users',',','/tmp','test.csv');
BEGIN dump_csv('select * from all_users',',','/tmp','test.csv'); END;

      *
ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00221: 'DUMP_CSV' is not a procedure or is undefined
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

SQL> get test_dump_csv.sql
  1  create or replace procedure test_dump_csv
  2  as
  3      l_rows  number;
  4  begin
  5      l_rows := dump_csv( 'select *
  6                             from all_users
  7                             where rownum < 25',
  8                          ',', '/tmp', 'test.csv' );
  9* end;
SQL> exec test_dump_csv;

PL/SQL procedure successfully completed.
When i run the procedure it works.
The only difference i have seen from procedure to directly running the function
is that variable l_rows. What it is for?
Thanks for the pl/sql link.

          Followup:

dump_csv is a function use

exec dbms_output.put_line( dump_csv( ..... ) )

      Rating: 2
Column
      Header  November 07, 2003
Reviewer:  John
       from USA
Hi, TOm,

This is a very nice scripts, but what if I want to add
header to each data column(name of the columns, like select stmt result in
SALPLUS), how to do that?
I check the white paper for DBMS_SQL, it seems to me that a bit awkward.

Help please,

Thanks

          Followup:

whats awkward about dbms_sql?

but anyway, there is a describe columns API call you can use:

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96612/d_sql2.htm#1004494
after we parse, you can describe and be told

a) how many columns
b) what their names are

then you can print them out.

November 07, 2003 pushparaj -- Thanks for the question regarding
      "Optimizer Plan Stability", version 8.1.7

      You Asked
Tom,

I am testing the Oracle 8i new feature CREATING STORED OUTLINE
for the SQL statments to be used in my application.
I am able to do this for Queries from the SQL plus.
But I would like to create and store the outlines for sqls
that are executing from the stored procedures. For example, If I have a stored
procedure like this:

CREATE OR REPLACE PROCEDURE emp_proc (nDeptNo IN NUMBER, nSumSal OUT NUMBER)
IS
   CURSOR c1 IS
   SELECT empno,sal FROM emp
   WHERE  deptno = nDeptNo;
BEGIN
   FOR rec IN c1
   LOOP
       IF rec.sal > 50000 THEN
          UPDATE emp
          SET    sal = sal-1000
          WHERE  empno = rec.empno;
       ELSE
          UPDATE emp
          SET    sal = sal+1000
          WHERE  empno = rec.empno;
       END IF;
   END LOOP;

   SELECT SUM(sal) INTO nSumSal FROM emp
   WHERE  deptno = nDeptNo;
END;

Could please show me some examples for how to create a stored outline for the
CURSOR SELECT and the SELECT .. INTO statements exucting from the stored
procedures.

Thanks
Pushparaj

      and we said...
Well, hopefully you would never ever really write code like that right?  I mean
-- it should be:

  update emp
     set sal = sal + decode( sign(sal-50000), 1, -1000, 1000 )
   where deptno = nDeptNo;

  select sum(sal) into nSumSal
    from emp;

you would never do procedurally what you can and should do in a single statement
;)

Here is a very short extract from my book -- I have a big chapter on stored
outlines.  In general, I wrote a stored procedure -- and then executed it:

tkyte@TKYTE816> alter session set create_stored_outlines = hr_application;
Session altered.

tkyte@TKYTE816> exec show_emps
7876,ADAMS
...
7521,WARD

PL/SQL procedure successfully completed.

tkyte@TKYTE816> alter session set create_stored_outlines = FALSE;
Session altered.

tkyte@TKYTE816> set long 50000
tkyte@TKYTE816> select name, sql_text
  2    from user_outlines
  3   where category = 'HR_APPLICATION'
  4  /

......

the stored outlines will be generated.
Review &
      Followup

      Rating: 4
Enforcing use
      of stored outlines for an application  April 03, 2003

Reviewer:  Moh'd Saeed
Hi Tom:

Can I implement stored outlines for an application that generate simillar sql
statements, differing in the literal values only (i.e dynamic sql statements) by
using cursor_share=force ?

Would that have any other side effect ?

Thanks for sharing your knowledge.
M.Saeed

          Followup:

If you have my book "Expert one on one Oracle" -- i cover this topic:

...
Oracle release 8.1.6 introduced a feature I refer to as 'auto binding'. In
Chapter 10 on Tuning Strategies and Tools, I stressed the importance of using
bind variables, and demonstrated a new feature in the database whereby the
database kernel itself will rewrite queries that use constants to use bind
variables instead. This feature, cursor sharing, has an anomaly with regards to
stored outlines. Depending on how the outline was generated, we will either
store the plan for a query with a bind variable, or not. An example will help to
clarify. We will run the same exact query in a session where CURSOR_SHARING is
enabled. In one case, we will generate the outline using DDL via the CREATE
OUTLINE command and in the other case, we will use the ALTER SESSION command to
have outlines generated for us. We can then compare the SQL_TEXT stored for
each:

tkyte@TKYTE816> alter session set cursor_sharing = force;

Session altered.

tkyte@TKYTE816> create or replace outline my_outline
  2  for category my_category
  3  on select * from dual where dummy = 'X';

Outline created.

tkyte@TKYTE816> alter session set create_stored_outlines = true;

Session altered.

tkyte@TKYTE816> select * from dual where dummy = 'X';

D
-
X

tkyte@TKYTE816> alter session set create_stored_outlines = false;

Session altered.

tkyte@TKYTE816> select name, category, sql_text from user_outlines;

NAME                          CATEGORY        SQL_TEXT
----------------------------- --------------- ------------------------------
SYS_OUTLINE_0104122003150057  DEFAULT         select * from dual where dummy
                                                = :SYS_B_0

MY_OUTLINE                    MY_CATEGORY     select * from dual where dummy
                                                = 'X'

As you can see, the stored queries are very different from each other. The one
we generated via the CREATE OUTLINE command is exactly as we had entered it. The
CURSOR_SHARING code was not executed in this case, since we did not actually run
the query. The query text was stored verbatim. On the other hand, the query text
for the implicitly generated outline shows the effect of the query rewrite for
us. We can plainly see that our constant X was turned into a bind variable for
us. This SQL was stored for us.

Depending on your needs, both methods may be applicable. It is just important to
understand that there is a subtle different between the explicitly generated
plan, and the implicitly generated one with CURSOR_SHARING enabled.
...................

so, yes you can -- and the side effects are as above -- beyond the normal side
effects of cursor sharing.

      Rating: 5
Useful
      info  April 08, 2003
Reviewer:  Amit Sanghvi
       from UK
excellent answer by Tom, once again.

      Rating: 5
One more
      Question on stored outlines  April 23, 2003

Reviewer:  A reader
Thanks a lot for your earlier response. That clarifies a lot.

If I want to store the outlines for all SQL generated by an applicaion, is a
logon trigger the only way to set the session variable
create_stored_outline=my_app ?

M'Saeed

          Followup:

no, anything that can do an alter session can do it.

a logon trigger is a logical choice

      Rating: 3
Any other
      options...  April 24, 2003
Reviewer:
      A reader
Thanks Tom.

I was hoping you had some other options/suggestions/tricks for me to consider
and if possible their pros and cons.

Thanks

M.Saeed

          Followup:

sometimes -- the answer is "nope, not really"...

the logon trigger is awesome for this.
the application itself can do this.

that -- that would be about it.

      Rating:
      5
  May 23, 2003
Reviewer:
      A reader
Hi Tom,

 We are using SIEBEL CRM application and we are planning to use stored
oullines.Siebel sql use bind variable. siebel statement look like this..

SELECT
      T5.CONFLICT_ID,
      T6.POSTN_ID,
      T1.PER_TITLE_SUFFIX,
      T1.PR_POSTN_ID,
      T1.LAST_UPD,
      T15.NAME,
      T1.X_FIND_LAW_FLG,
      T1.PR_REP_SYS_FLG,
      T2.STATE,
      T1.X_YELLOW_PAGE_HEADERS,
      T1.MID_NAME,
      T5.ROW_ID,
      T1.X_PR_CONTACT_TYPE_ID,
      T6.ROW_ID,
      T1.X_CUST_SEGMENT,
      T5.CREATED,
      T1.DISA_CLEANSE_FLG,
      T1.EMAIL_ADDR,
      T1.CREATED_BY,
      T5.MODIFICATION_NUM,
      T1.X_GOVT_FLG,
      T1.X_RATING,
      T3.ZIPCODE,
      T1.X_CONTACT_FLG,
      T1.EMP_ID,
      T3.CITY,
      T3.COUNTY,
      T1.PR_REP_DNRM_FLG,
      T17.PR_BL_ADDR_ID,
      T1.X_YELLOW_PAGE_AMT,
      T1.LAST_NAME,
      T1.CREATOR_LOGIN,
      T1.X_SOURCE,
      T5.ATTRIB_34,
      T17.X_MAIN_PT_FLG,
      T1.CONFLICT_ID,
      T6.CON_FST_NAME,
      T1.X_POSITION_TYPE,
      T1.X_MIDDLE_NAME,
      T1.X_BIOG_DATA,
      T2.ROW_ID,
      T1.X_ADMIT_YR,
      T1.X_ADDR_OVERRIDE_FLG,
      T1.PR_REP_MANL_FLG,
      T16.LOGIN,
      T3.STATE,
      T1.X_NES_CREATE_DATE,
      T1.PR_PER_ADDR_ID,
      T1.CREATED,
      T17.X_ACCOUNT_NAME,
      T16.NICK_NAME,
      T1.X_ISLN_NUM,
      T17.PR_SRV_AGREE_ID,
      T17.PR_SHIP_ADDR_ID,
      T1.ROW_ID,
      T1.X_ROSTER_NAME,
      T17.X_ORG_ID,
      T12.LOGIN,
      T1.X_CONTACT_ID,
      T1.X_BAD_ADDR_FLG,
      T1.DEDUP_TOKEN,
      T1.FST_NAME,
      T17.X_SUB_STATUS,
      T5.LAST_UPD_BY,
      T7.NICK_NAME,
      T1.X_YELLOW_PG_AD_TYPE,
      T1.X_CORP_FLG,
      T1.X_INACTIVE_REASON_CD,
      T13.NICK_NAME,
      T17.NAME,
      T9.NAME,
      T3.ROW_ID,
      T6.CON_MID_NAME,
      T1.PRIV_FLG,
      T5.ATTRIB_07,
      T1.X_OTHER_ADS,
      T17.LOC,
      T10.ROW_ID,
      T1.X_YELLOW_PG_FLG,
      T3.ADDR,
      T1.X_FIND_LAW_AD_TYPE,
      T6.ROW_STATUS,
      T17.PR_POSTN_ID,
      T1.BIRTH_DT,
      T3.X_STREET_NAME,
      T2.ZIPCODE,
      T1.X_RATING_CHG_DT,
      T17.PR_BL_PER_ID,
      T3.COUNTRY,
      T1.X_PRACTICE_AREA,
      T1.COMMENTS,
      T9.ROW_ID,
      T1.X_RATING_INVG_YR,
      T17.PR_ADDR_ID,
      T15.ROW_ID,
      T1.ASST_PH_NUM,
      T1.MODIFICATION_NUM,
      T8.PR_EMP_ID,
      T1.X_ATTORNEY_FLG,
      T4.ROW_ID,
      T5.PAR_ROW_ID,
      T1.PR_OPTY_ID,
      T2.COUNTY,
      T3.X_STREET_NUM,
      T17.ROW_ID,
      T1.X_LISTING_ID,
      T14.ATTRIB_04,
      T1.X_LAW_SCHOOL,
      T17.INTEGRATION_ID,
      T1.X_PROMOTION_CODE,
      T2.CITY,
      T6.CON_LAST_NAME,
      T1.X_BAD_ADDR_USER,
      T1.X_NON_LEGAL_FLG,
      T17.PAR_OU_ID,
      T1.X_EXPIRATION_DT,
      T1.X_INDIV_ID,
      T17.PR_SHIP_PER_ID,
      T1.X_COMP_INFO_LAST_UPD,
      T5.ATTRIB_34,
      T1.OWNER_PER_ID,
      T5.LAST_UPD,
      T1.OU_MAIL_STOP,
      T1.WORK_PH_NUM,
      T1.LAST_UPD_BY,
      T1.PREF_COMM_MEDIA_CD,
      T2.ADDR,
      T1.X_BAD_ADDR_DT,
      T5.ATTRIB_40,
      T1.X_YEAR_BORN,
      T1.CON_ASST_NAME,
      T1.X_INACTIVE_DT,
      T1.X_PR_ALT_PHONE_TYPE_ID,
      T1.PR_DEPT_OU_ID,
      T5.CREATED_BY,
      T4.PR_EMP_ID,
      T1.SEX_MF,
      T1.X_PRACTICE_GROUP,
      T1.X_SOLO_FLG,
      T10.ATTRIB_04,
      T2.COUNTRY,
      T1.FAX_PH_NUM,
      T8.ROW_ID,
      T1.PER_TITLE,
      T1.X_ACCNT2_NAME,
      T1.X_DNS_FLG,
      T4.NAME,
      T1.X_PR_DNS_ID,
      T1.JOB_TITLE,
      T17.X_ACCNT_LEVEL,
      T1.ASGN_USR_EXCLD_FLG
   FROM
       SIEBEL.S_CONTACT T1,
       SIEBEL.S_ADDR_ORG T2,
       SIEBEL.S_ADDR_PER T3,
       SIEBEL.S_POSTN T4,
       SIEBEL.S_CONTACT_X T5,
       SIEBEL.S_POSTN_CON T6,
       SIEBEL.S_EMPLOYEE T7,
       SIEBEL.S_POSTN T8,
       SIEBEL.S_CONTACT_XM T9,
       SIEBEL.S_CONTACT_XM T10,
       SIEBEL.S_ADDR_PER T11,
       SIEBEL.S_EMPLOYEE T12,
       SIEBEL.S_EMPLOYEE T13,
       SIEBEL.S_ORG_EXT_X T14,
       SIEBEL.S_CONTACT_XM T15,
       SIEBEL.S_EMPLOYEE T16,
       SIEBEL.S_ORG_EXT T17
   WHERE
      T1.X_BAD_ADDR_USER = T13.LOGIN (+) AND
      T1.EMP_ID = T12.ROW_ID (+) AND
      T1.PR_DEPT_OU_ID = T17.ROW_ID (+) AND
      T1.PR_DEPT_OU_ID = T14.PAR_ROW_ID (+) AND
      T1.PR_PER_ADDR_ID = T11.ROW_ID AND
      T1.ROW_ID = T5.PAR_ROW_ID (+) AND
      T1.X_PR_ALT_PHONE_TYPE_ID = T10.ROW_ID (+) AND
      T17.PR_ADDR_ID = T2.ROW_ID (+) AND
      T1.X_PR_DNS_ID = T9.ROW_ID (+) AND
      T1.PR_PER_ADDR_ID = T3.ROW_ID (+) AND
      T17.PR_POSTN_ID = T8.ROW_ID (+) AND
      T8.PR_EMP_ID = T7.ROW_ID (+) AND
      T1.PR_POSTN_ID = T4.ROW_ID AND
      T1.PR_POSTN_ID = T6.POSTN_ID AND T1.ROW_ID = T6.CON_ID AND
      T4.PR_EMP_ID = T16.ROW_ID (+) AND
      T1.X_PR_CONTACT_TYPE_ID = T15.ROW_ID (+) AND
      ((T1.BU_ID = :1) AND
      (T1.PRIV_FLG = :2)) AND
      (T1.LAST_NAME LIKE :3 AND T11.CITY LIKE :4 AND T11.STATE LIKE :5)
   ORDER BY
      T1.BU_ID, T1.LAST_NAME, T1.FST_NAME, T1.MID_NAME

As you can see it is using bind variable..

I have created stored outline for above statement it is working from sql*plus
BUT it is NOT working from siebel application.

From siebel we are passing different city,state and last name
to the query

Can we use

alter session set cursor_sharing = force
  before creating outlines??

Please help.

Thnak

          Followup:

if query is using binds, cursor_sharing=force would have no effect on the query
-- it already USES binds.

the application would have to issue alter session set use_stored_outlines = ....

You would need to get in touch with siebel and ask them how best to tune their
application.

(please don't put 15billion line queries that don't really add much to the
statement -- it just makes the page really long)

      Rating:
      5
  May 23, 2003
Reviewer:
      A reader
Hi Tom,

 Sorry for long siebel query
Can we issue alter session set use_stored_outlines = ....

statement from ON LOGON Trigger ?

Then login to siebel.Keep needed outline drop others.

2) I am doing all this in test envt.
  If above thing is possible then how can i export outline from test into
production envt.

Thnaks a lot for much needed help

          Followup:

do you have my book "expert one on one Oracle"?  i cover these topics in some
detail there...

but yes, you can use the on logon trigger.

you can use export/import on the out line tables to move them.

      Rating: 5
procedural
      justification  May 23, 2003
Reviewer:  Mark
       from Buffalo
Tom,

Regarding the first example, I can see through tkprof/execution plan that:

============
 update emp
     set sal = sal + decode( sign(sal-50000), 1, -1000, 1000 )
   where deptno = nDeptNo;

  select sum(sal) into nSumSal
    from emp;
============

is better than:

============
CREATE OR REPLACE PROCEDURE emp_proc (nDeptNo IN NUMBER, nSumSal OUT NUMBER)
IS
   CURSOR c1 IS
   SELECT empno,sal FROM emp
   WHERE  deptno = nDeptNo;
BEGIN
   FOR rec IN c1
   LOOP
       IF rec.sal > 50000 THEN
          UPDATE emp
          SET    sal = sal-1000
          WHERE  empno = rec.empno;
       ELSE
          UPDATE emp
          SET    sal = sal+1000
          WHERE  empno = rec.empno;
       END IF;
   END LOOP;

   SELECT SUM(sal) INTO nSumSal FROM emp
   WHERE  deptno = nDeptNo;
END;
=============

However, we have forms applications that lock (for update) when "editing" a row.
So, we need to lock/inform users that rows are locked.

What would be the best way to do this? Would this justify doing it
"procedurally"?:

=============
CREATE OR REPLACE PROCEDURE emp_proc (nDeptNo IN NUMBER, nSumSal OUT NUMBER)
IS
   CURSOR c1 IS
   SELECT empno,sal FROM emp
   WHERE  deptno = nDeptNo
   FOR UPDATE of sal NOWAIT;
BEGIN
   FOR rec IN c1
   LOOP
       IF rec.sal > 50000 THEN
          UPDATE emp
          SET    sal = sal-1000
          WHERE CURRENT OF c1;
       ELSE
          UPDATE emp
          SET    sal = sal+1000
          WHERE CURRENT OF c1;
       END IF;
   END LOOP;

   SELECT SUM(sal) INTO nSumSal FROM emp
   WHERE  deptno = nDeptNo;
EXCEPTION
   WHEN OTHERS
   THEN
      .....check if locked - inform user....
END;
===============

Thanks.

          Followup:

bulk collect it.

create or replace procedure p( p_deptno in number, p_sumSal out number )
as
    type ridArray is table of rowid index by binary_integer;
    type salArray is table of number index by binary_integer;

    l_rids ridArray;
    l_sals salArray;
begin
    select rowid, decode( sign(sal-50000), 1, -1000, 1000 )
      bulk collect into l_rids, l_sals
      from emp
     where deptno = p_deptno
       FOR UPDATE NOWAIT;

    forall i in 1 .. l_rids.count
        update emp set sal = l_sals(i) where rowid = l_rids(i);

    p_sumSal := 0;
    for i in 1 .. l_sals.count
    loop
        p_sumSal := p_sumSal+l_sals(i);
    end loop;
end;
/

      Rating:
      5
  May 23, 2003
Reviewer:
      A reader
Hi Tom,

 OUTLINES IS ROCKING. It is working like a charm.

 I tried creating them using ON LOGON Trigger. My boss is very very  happy...

 Many thanks to you.

      Rating: 5
Thanks for the
      bulk collect answer.  May 23, 2003
Reviewer:
      Mark  from Buffalo

      Rating:
      5
  May 24, 2003
Reviewer:
      A reader
Hi Tom,

I've read this from Jonathan Lewis's artical

"Third -- while stored outlines are extremely useful for solving critical
performance problems, there is a cost involved. If stored outlines are
activated, then Oracle checks whether a relevant stored outline exists every
time a new statement is parsed. If there are large numbers of statements without
a stored outline, then this overhead has to be balanced against the benefit you
get on the few statements that do have stored outlines. However, this is only
likely to be an issue on a system that has other, more serious, performance
problems."

http://www.dbazine.com/jlewis4.html
We are using siebel crm application.Currently we've just One stored outline in
out production database and in near feature we will add couple more..

Will the benefits obtain from outline for couple of sql statements offset by all
other sql statement issued by Application(oracle check all statement for
outline) ?

Thanks

          Followup:

as long as siebel is keeping the soft parse ratio at or near 100%, it'll be OK.

It would affect a system where you have hard parses all over the place.  if
statspack says they are doing OK on the parsing (soft parse % is above 99%),
it'll probably not be measurable (the impact on you).

(the hard parsing would be the "other more serious performance problems" he
alludes to ;)

      Rating:
      5
  June 14, 2003
Reviewer:
      A reader
Hi Tom,

 I am exporting stored outlines from oracle 8.1.7.0 to
oracle 9.2.0.2 database.

 Exported Outline is not working.
 (I've seen theree table in oracle 9 in outln schema but two tables in oracle
8i. Is this is the reason?)

 Please advice
 Thnaks

      Rating: 5
it is not using
      the plan!  August 11, 2003
Reviewer:  reader

Please see below. Why it is not using the stored outline plan? Thanks.

create table employees_test
as
select last_name, employee_id from hr.employees
group by last_name, employee_id
/

Table created.

set autotrace traceonly explain
SQL> select employee_id, last_name from employees_test where employee_id > 0
/

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

create or replace outline MyOutline
for category mycategory
ON
select employee_id, last_name from employees_test where employee_id > 0
/

Outline created.

SQL> analyze table employees_test compute statistics
/  2

Table analyzed.

SQL> set autotrace traceonly explain
SQL> select employee_id, last_name from employees_test where employee_id > 0
/

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=107 Bytes=107
          0)

   1    0   TABLE ACCESS (FULL) OF 'EMPLOYEES_TEST' (Cost=2 Card=107 B
          ytes=1070)

SQL> alter table employees_test
add constraint emp_pk
primary key(employee_id)
/

Table altered.

SQL> analyze table employees_test compute statistics
/  2

Table analyzed.

SQL> select employee_id, last_name from employees_test where employee_id > 0
  2  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=107 Bytes=107
          0)

   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMPLOYEES_TEST' (Cost=2
          Card=107 Bytes=1070)

   2    1     INDEX (RANGE SCAN) OF 'EMP_PK' (UNIQUE) (Cost=1 Card=107
          )

SQL> alter session set use_stored_outlines = mycategory
/  2

Session altered.

SQL> set autotrace traceonly explain
SQL> select employee_id, last_name from employees_test where employee_id > 0
/  2

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=107 Bytes=107
          0)

   1    0   TABLE ACCESS (FULL) OF 'EMPLOYEES_TEST' (Cost=2 Card=107 B
          ytes=1070)

          Followup:

huh?

you started with full table scan

you created outline that uses full table scan

you added index such that query used index range scan

you enabled your outline

it when back to full table scan

looks like it worked to me?

      Rating: 5
Yes, it does
      but .....  August 12, 2003
Reviewer:  reader

My stored outline has the following plan.

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

However, when I enabled the outline, it has cost info that was not there. Is
this how it works? or am I not interpreting it properly. Thanks.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=107 Bytes=107
          0)

   1    0   TABLE ACCESS (FULL) OF 'EMPLOYEES_TEST' (Cost=2 Card=107 B
          ytes=1070)

          Followup:

you used the CBO the second time around, the CBO always assigned costs.

it is YOUR plan.

the output is just with cost and card -- since the CBO was used.

      Rating: 5
THANK
      YOU  August 12, 2003
Reviewer:  reader

      Rating: 5
optimizer
      cutoff .....  August 12, 2003
Reviewer:
      reader
Tom, Is there any cutoff percentage with regard to the value of the column based
on which optimizer would decide to use index or full table scan. For example, if
I have a table like, create table t (name varchar2(10)), and most of the values
for name is same and say only 10% of the name col has different values, if I
select from the table with where clause = name that falls in 90% of the values,
would optimizer chose to do FTS? Thanks.

          Followup:

depends - if the 10% are all of the same value, it might full scan, it might
not.

if the 10% are mostly unique, it would more then likely use the index.

It is not just about the % of data returned, things like the clustering factor,
the cost of an index/table access and so on come into play.

      Rating:
      5
UNUSED  October 09, 2003
Reviewer:
      pr  from USA
Tom,

The USED column in the USER_OUTLINES table, shows UNUSED
after I create and try to use it.

SQL > alter session set create_stored_outlines = GET_EMP;
Session altered.

SQL > select * from emp where deptno=10;
..
10 rows selected.

SQL > alter session set create_stored_outlines = FALSE;
Session altered.

SQL> select category,used,sql_text from user_outlines
  2  where category='GET_EMP';

CATEGORY                       USED
------------------------------ ---------
SQL_TEXT
--------------------------------
GET_EMP                        UNUSED
select * from emp where deptno=10

SQL > ALTER SESSION USE_PRIVATE_OUTLINES=GET_EMP;
Session altered.

SQL > select * from emp where deptno=10;
..
10 rows selected.

SQL> select category,used,sql_text from user_outlines
  2  where category='GET_EMP';

CATEGORY                       USED
------------------------------ ---------
SQL_TEXT
--------------------------------
GET_EMP                        UNUSED
select * from emp where deptno=10

Can you please help me where I am doing the mistake.

thanks
pr..

          Followup:

well, you have doctored the inputs there (i really hate it when that happens --
how many questions I get with "cut and pastes" that have TYPOS in the ora error
messages!  dead give away)

ops$tkyte@ORA920> ALTER SESSION USE_PRIVATE_OUTLINES=GET_EMP;
ALTER SESSION USE_PRIVATE_OUTLINES=GET_EMP
              *
ERROR at line 1:
ORA-00922: missing or invalid option

Hmmmmm, but anyway, you have a stored outline,

alter session set use_stored_outlines=true;

(and a flush of the shared pool may well be needed)

      Rating: 3
privilege
      issue  October 10, 2003
Reviewer:
      A reader
Tom,

It seems that it requires SELECT_CATALOG_ROLE privlege
for that schema. It had worked without flush of the
shared pool after the DBA granting SELECT_CATALOG_ROLE
privilege.

thanks
pr..

      Rating: 5
How to move
      outlines from Oracle9i to Oracle8i  November 06, 2003

Reviewer:  Miroslav  from Santa Clara, CA
Hello Tom,

is there a way to move outlines from 9i into 8i database?

I'm trying to capture the statements at the 9i database into outline, move it to
the 8i (which has better statistics), rebuild, and move back to 9i.

The import at 8i complained because OL$ and OL$HINT have extra column(s) in 9i
database.

          Followup:

huh?  why why why in the world would you do anything like this at all?

get the right stats into the right database.

garbage in
garbage out

      Rating: 4
How many
      outlines are snsfull  November 07, 2003
Reviewer:
      Orca  from
      Universum/Milkstreet/Solarsystem/Earth/Germany/Baden.../...
Hi Tom,
sorry the question must come;
The Table where the sql for outline is stored stores them in a long-field. When
i capture thousends of outlines will this impact the performance in compare to
find the right SQL in v$sql?

Orca

          Followup:

they use the hash, not the text.

November 07, 2003 Sankar -- Thanks for the question regarding
      "Problem in Archiving data", version 8.1.6

      You Asked
Hi Tom,

In our application, we have about 150 tables. And it was orginally developed for
v 7.0.3 and thus does not have even any partioning in the database.

There are about 10 huge tables which needs to be archived for every 2 Years...
The Business logic we are using as below...

1. Create a temporary table ( by dyanamic sqls)
2. Select the rows to be archived from the main table and insert the selected
rows to temp tables created in the previous step.(this selection of rows is not
a simple one and this query has some joins with other tables also)
3. Use the export utility to export the temp table completely.
4. Truncate the temp table
5. Delete the selected rows from the main table...

These process will be repeated for all 10 tables to be exported...

Now the problems we are facing are as follows..
1. The performance is too slow.
2. Combatability in the exported files. There is no upward compatability in the
export, So if i do a export in 7.3.4 today and anoher day if i try to import to
8.1.7, then it may give some problems;;

Is there a better way of approaching the archival procedure...

Tom, please help me out!

Thanks,
Sankar

      and we said...
A better process would be:

CREATE TABLE ARCHIVE_DATA nologging
as
select <data you want to delete>
/

CREATE TABLE KEEP nologging
as
select <data you want to keep>
/
CREATE INDEX KEEP_IDX1 on KEEP(....) nologging parallel ...
....
CREATE INDEX KEEP_IDXn on KEEP(....) nologging parallel ...

drop table old_data;
rename keep to old_data;

backup database.

So the steps are to quickly split the table into two using unrecoverable options
(no rollback, no redo genearted -- faster, you can use parallel option as well).
 Then index the data you want to keep online using the un recoverable options
and in parallel if you want.  then drop the old data and rename the table you
want to keep.

This should be much faster then using inserts and deletes and it'll re-org your
data nicely.

Exports are upward compatible.  The are not backwards compatible.

An export from version X is importable into version Y as long as x <= y.
An export from version Y is NOT importable into version X if y > x.

You might want to dump to a flat file is you are concerned about that.  A simple
tool such as:

http://osi.oracle.com/~tkyte/flat/index.html
might do it or a tool such as:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:459020243348
can do it faster.

Review &
      Followup

      Rating: 4
better
      option?  October 15, 2001
Reviewer:  Dennis
       from NY
After loading all data (keep and unkeep) into tables, if I just truncate the
original table and load back the data (keep) from its table by using direct load
insert, should I get a better performance?

          Followup:

Well, if you just drop the original table and rename the keep -- you'll get the
best as you reduced the IO.  Truncating the original would not in my opinion buy
you very much.

Any indexes on original would generate rollback and redo (even with direct load
insert) so you would want to drop them and recreate them anyway.

Constraints would have to be validated on either of keep or original -- so you
save nothing there.

Using original would add additional, unnecessary processing to the process.

      Rating: 4
better
      option?  October 15, 2001
Reviewer:  Dennis
       from NY
Sorry for being not clear in my previous comment. I meant if i just truncate the
original table, i don't need to worry about the lost of indexes, grant ... etc
instead of better performace. I guess the performance is not better than the
solution you suggested.

          Followup:

Yes, you need not "worry" about the indexes and grants however -- you would
spend more time building the indexes (and generate redo/rollback).

Also, if you used a VIEW

create view The_Table as select * from original;

And granted on the VIEW -- you need not worry about grants either.

      Rating: 3
Archiving
      data  November 07, 2003
Reviewer:  Raza
       from Canada
I have a table with 1.2 millions rows in it, and I need to retain data for
provided number of days. Rest of the data should be archived into another table
<table_name_HIST>. Archived data will be accumulated, and in above provided
solution it creates the table every time. What would be the best way to this.

          Followup:

1.2 million rows is pretty small -- I assume you'll be "archiving" every
day/week or so, so the amount of data to move shouldn't be a large percentage of
the overall.

given those assumptions

a) insert into hist select * from current where date > :x
b) delete from current where date > :x

and periodically -- IF NEEDED -- alter index coalesce.

November 07, 2003 Devinder -- Thanks for the question regarding
      "long raws -- viewing and getting the length", version

      You Asked
Is there a way to see what is in a long raw
column? If there is some value in a long raw
column Can I determine what is the size of
the long raw column.

      and we said...
Using C -- yes.

Using PLSQL -- no (unless the long raw is always 32k or less).

If the long raw is 32k or less, you can do some things with it.
Lets say you had a table IMAGE with a long raw in it. You can do
something like this:

scott@8.0> create or replace function lr_length( p_rowid in
rowid ) return number
  2  as
  3          l_raw   long raw;
  4  begin
  5          select image into l_raw
  6            from image
  7           where rowid = p_rowid;
  8
  8          if ( l_raw is null ) then
  9                  return null;
 10          else
 11                  return utl_raw.length(l_raw);
 12          end if;
 13  exception
 14          when others then return NULL;
 15  end;
 16  /

Function created.

scott@8.0>
scott@8.0> create or replace function lr2hex( p_rowid in rowid )
return varchar2
  2  as
  3          l_raw   long raw;
  4          l_str   varchar2(2000);
  5          l_len   number;
  6  begin
  7          select image into l_raw
  8            from image
  9           where rowid = p_rowid;
 10
 10          if ( l_raw is not null ) then
 11                  l_len := least( utl_raw.length( l_raw ),
1000 );
 12                  l_raw := utl_raw.substr(l_raw,1,l_len);
 13          end if;
 14          l_str := l_raw;
 15
 15          return l_str;
 16  exception
 17          when others then return NULL;
 18  end;
 19  /

Function created.

scott@8.0>
scott@8.0> column hex format a40
scott@8.0> select rowid, lr_length( rowid ) len, lr2hex( rowid )
hex from image
  2  /

ROWID                     LEN HEX
------------------ ----------
----------------------------------------
AAABy+AAcAAAAMyAAA         17 2F746D702F55303030303434312E747874
AAABy+AAcAAAAMyAAB         17 2F746D702F55303030303434322E646F63
AAABy+AAcAAAAMyAAC         13 2F746D702F5530303030343634
AAABy+AAcAAAAMyAAE         13 2F746D702F5530303030343633
AAABy+AAcAAAANjAAB
AAABy+AAcAAAANkAAA         34
626D696C6C65724073756E636F61737473636965
                              6E74696669632E636F6D0D0A0D0A

the utl_raw package may not be installed in your database.  If
not, cd $ORACLE_HOME/rdbms/admin and do an ls *raw*.  You'll
find a .sql and .plb file.  Install the .sql file and then the
.plb file WHEN CONNECTED AS SYS OR INTERNAL using svrmgrl.
Then, you'll have it.

In Oracle8.0 and up, blobs replace LONG RAWS and the dbms_lob
package gives you 100% access to the length and contents of
binary data.

Review &
      Followup

      Rating:
      3
  March 28, 2001
Reviewer:
      A reader
the location of the UTL_RAW down load has moved.
The question is where??

      Rating:
      5
  March 28, 2001
Reviewer:
      A reader
My bad. Look for the files on the local install of Oracle.
Everything drops in place.

      Rating: 5
will above
      solution be applied to Long also  November 25, 2002

Reviewer:  kumar  from US
how can I apply the above solution to a case of finding the
length of Long column instead of Long Raw. I have to write some temp queries to
be run from sql.
Thanks & regards

          Followup:

scott@8.0> create or replace function long_length( p_rowid in rowid ) return
number
  2  as
  3          l_txt   long;
  4  begin
  5          select image into l_txt
  6            from image
  7           where rowid = p_rowid;
  8          return length(l_txt);
 13  exception
 14          when others then return NULL;
 15  end;
 16  /

      Rating: 4
Application
      Developer  May 13, 2003
Reviewer:  Dawar
      Naqvi  from CA USA
Tom,

I have an existing table below.
Here is the desc of table:

desc loft_glo_terms

NAME                            Null?     Type
------------------------------- --------- -----
TERMS                                     VARCHAR2(1000)
DEFINITION                                VARCHAR2(3000)

From sql navigator I cannot see complete data in some of the fileds for the
column called definition.Because of the lenght of the data. Do I nedd to change
the datatype?f yes what will be the data type and how it will work.
Any thought will appreciate as usual.

cheers,
Dawar, Burbank USA

          Followup:

time to call the makers of sql navigator i guess?

i've never used it myself.

      Rating:
      4
Devloper  October 14, 2003
Reviewer:
      Bipin Ganar  from INDIA
Hi Tom,
I'm not able to find how can i read image.
I have table Emp_Sign(Empno varchar2(10),Sign Long Raw)

I'm Cropping the Tiff file and with read_image_file i stored in database it
shows like

SQL >Select * from Emp_Sign;

Empno Sign
----- -------
E005  4

Now , when i read the image from the developer(Forms6) it shows on the screen as
i'm using same tool for storing and retrieving . But if i use VB or some other
application it is difficult get sign on the screen.
and second thing while storing sing by using read_image_file how can i reduce
the size of the sign in long raw column

      Rating: 2
length of raw
      over 32k  November 06, 2003
Reviewer:
      Anandhi  from PA, USA
Could you please provide a sample c program to get the length of a long raw
column?

          Followup:

you'd have to either

a) use oci to piecewise fetch and add up the lengths of the pieces
b) use to_lob to put into a gtt and then dbms_lob.getlength would work

I'll do (b), it is easier and more "general purpose".  I'll just ask that you
select out the ROWID of the row that points to the LONG RAW and give me the
table name, column name and rowid of the row you want the long raw length of.
We'll use a global temporary table to help us out:

ops$tkyte@ORA9I> create global temporary table gtt
  2  ( x blob )
  3  on commit delete rows
  4  /

Table created.

ops$tkyte@ORA9I>
ops$tkyte@ORA9I> create or replace
  2  function length_of_longraw( p_tname in varchar2,
  3                              p_cname in varchar2,
  4                              p_rowid in rowid ) return number
  5  AUTHID CURRENT_USER
  6  as
  7      pragma autonomous_transaction;
  8      l_length number;
  9  begin
 10      execute immediate
 11      'insert into gtt(x)
 12       select to_lob(' || p_cname || ')
 13         from ' || p_tname || '
 14        where rowid = :x' using p_rowid;
 15
 16      select dbms_lob.getlength(x) into l_length
 17        from gtt;
 18
 19      commit;
 20
 21      return l_length;
 22  end;
 23  /

Function created.

ops$tkyte@ORA9I> exec dbms_output.put_line( length_of_longraw( 'rfp.image',
'image', chartorowid('AAAKPrAAzAAABfLAAD') ))
5541376

PL/SQL procedure successfully completed.

      Rating:
      5
  November 07, 2003
Reviewer:
      A reader
thanks a lot!

Anandhi

      Rating:
      5
  November 07, 2003
Reviewer:
      A reader
And this procedure (using to_lob to convert long raw to blob) will work only if
the length is < 64K?

Thanks
Anandhi

          Followup:

the example used a 5.5 meg long raw.

so no, it does not have that limit...

November 07, 2003 Prince -- Thanks for the question regarding
      "Single Schema vs Multiple Schema - pros & cons", version 8.1.7

      You Asked
We have a single schema with few 100 objects some of them having millions of
rows. The schema have data for various clients. Each client is identified by
thier clientid and is a column in almost all the tables.

Ex: (will have data of all the clients in the same table)
----
Table1 (clientid number(10) , col1 varchar2(30)) ;
Table2 (clientid number(10) , col1 varchar2(30), col2 number(30)) ;

We are thinking of splitting the tables into separate schema having each clients
data in its own schema.

Ex: (will have each clients data in their own schema)

sch1.Table1 (col1 varchar2(30)) ;
sch1.Table2 (col1 varchar2(30), col2 number(30)) ;

sch2.Table1 (col1 varchar2(30)) ;
sch2.Table2 (clientid number(10) , col1 varchar2(30), col2 number(30)) ;

sch3.Table1 (col1 varchar2(30)) ;
sch3.Table2 (col1 varchar2(30), col2 number(30)) ;

Now, assume I have 1000 clients and want to separate them into their own schema.

I will probably have 800 - 1000 small objects (including indexes etc) per
schema.

I need to give the information to the management. So please provide as much
details (on the pros and cons) you can

Question:
~~~~~~~~~

What are all the cons to the approach?

Will there be any performance problem having many objects and many schema in the
same instance? (I have the DB with 2G memroy dual processor)

What would be the impact on the resource (SGA, disk etc etc)
(I will club some of many schema into the same tablespace)

Note: I am not considering Oracle EE at this time.

Thank you very much..

      and we said...
cons:
o harder to manage
o harder to upgrade
o harder to patch
o harder to maintain
o causes your shared pool size to increase 1,000 times (shared sql goes down the
tubes)
o takes more space
o queries against the dictionary will be impacted
o latching on the shared pool goes WAY up (latching = locks = serialization
device = slows you down)

pros:
o none that I can think of.

Review &
      Followup

      Rating: 4
multiple
      schemas or Virtual private database(s)?  December 17, 2001

Reviewer:  Bernd Boecker  from Bonn , GERMANY

Maybe its worth to read ORACLE 8i documentation of

"Fine Grained Access Control / Virtual Private Database"

for a possible secure solution for row-level access with a predictor like
'clientid'...

      Rating: 4
I wasn't
      thinking of shared pool  December 17, 2001

Reviewer:  Prince
Thanks for pointing the correct points.

      Rating: 5
Schema
      name  October 11, 2002
Reviewer:  atul
       from India
Sir,

I have one task to complete i have one schema called test
Now my maneger ask me to change the name of that schema

But this has some referances to other schmas..

So according to you whats the easiest way to go for that??

Thanks.
atul

          Followup:

There is no way to rename a schema.  Period.

You could:

exp user=test owner=test
imp user=new_schema fromuser=test touser=new_schema

but you'll have to fix up hard coded references to TEST.

      Rating:
      4
  May 21, 2003
Reviewer:
      A reader
tom, how would you decide in case you had to manage several small applications
if their database part contains few data but most of the logic is implemented on
database side? would you (always) recommend to have EVERY application to have
their OWN separate schema (although that would have very few objects ... and
data)?
we are thinking of using prefixes for object names and manage privileges through
roles ...

what would you consider pros & cons in such a scenario?

          Followup:

i like a schema per application.  applications come and applications go.  if you
put them all together in one big thing, they all become interdependent and
unmanagable.  separate schemas force you to document the dependencies via grants
at least.

      Rating: 2
Comment to
      Ben's comment  May 21, 2003
Reviewer:  Marc
      Blum  from Aachen, Germany
FGAC is only available in Enterprise Edition, this is even true in 9. And the OP
doesn't consider EE.

          Followup:

sorry but you are coming in way out of left field on this one? who is ben?

I've no idea what point you are trying to make.

I've no idea what "OP" is.  But whatever OP is -- they must like writing tons of
their own code -- yeah, thats lots cheaper and much more efficient then buying
stuff.

      Rating: 4
1 schema or 1
      instance per application  May 21, 2003
Reviewer:
      Kuo-Chun  from Buchanan, NY
Tom:

In here we have some applications using relatively small databases ( < 150M) and
not too many transaction each day.
We are currently moving them to another platform. We are debating whether we
should merge them under 1 instance with 1 schema per application or keep the
current configuration (1 instance/application)? What are the pros and cons of
each scenario? Thank you for your help.

          Followup:

there are NO pros of an instance per application.

only headaches.

there is only one logical answer -- the fewer database instances you have, the
better.  The less memory you need, the more control you have, the less you have
to backup, recover, manage.

You want as FEW instances as you can get away with.  To me, one per server is
perfect.

      Rating: 3
???
      EXECUTE/GRANT ANY ???  May 22, 2003
Reviewer:
      A reader
one of our development teams wants to have one certain schema of their
application to provide "services for administration purposes": create/drop
users, grant users to roles.

in order to achieve this the ask for EXECUTE ANY and GRANT ANY for that "special
user".

I want/have to "convince" them now, that explicit object privileges with grant
option (instead of the requested system privileges) are sufficient under any
circumstances. I don't want to be responsible for the code of that "super-user"
(the usage of the system privileges granted). I want to keep control at the
database level as much as possible!

could you pls confirm that (or give some how-to-advice if I'm wrong)?

thanx in advance.

          Followup:

they only need a user that has

o the ability to create users (pretty dangerous, but maybe acceptable to you).
I would give them a stored procedure for this.

o I wouldn't want them dropping users, maybe the ability to lock an account, but
since dropping is irrevocable, maybe you want to do this differently

o roles granted to this user with the ADMIN option -- then that user can grant
the role as well.

o privs on objects granted to this user with the GRANT option -- that user can
grant the privs as well.

You are 100% right to go for LEAST PRIVILEGES here.  avoid the "any" privs, use
fine grained privs.

      Rating:
      5
  May 23, 2003
Reviewer:
      A reader
thanks, tom, for that complete summary: there 's no doubt left that we can get
it working the way you described.

      Rating: 3
coupling
      application schemas  May 24, 2003
Reviewer:
      A reader
tom, in an instance with several schemas belonging to different applications -
would you decide to couple these application schemas in a manner where you have
only ONE cross application dependency between objects of those schemas?

lets assume two applications/schemas:

application/schema A: object A.1
application/schema B: object B.1, object B.2

if objects B.1 and B.2 both use object A.1: would you decide to create an object
B.A1 as B's "local wrapper" for object A.1 wich is then used by B.1 and B.2?
that way there would be only one dependency left between schemas A and B (no
matter how often B uses A.1). on the other hand special "wrapper objects" have
to be created (and maintained) all-around.

I'm really not sure whether it is worth it or not. what would you suggest (in
terms of pros & cons)?

          Followup:

depends -- do you anticipate A.1 changing -- if so, B.A1 may be useful in
"hiding" that change from you -- stress MAY there.

The dependencies are all still there, it is just the ability to fix things.  Say
A.1 is a table and they DROP a column "X" and add a column "Y".  Your view B.A1
could "hide" that -- supplying a NULL value for X and hiding that Y even exists.
 Some changes can be made without impacting the code.

      Rating: 5
Dropping sample
      schemas  June 09, 2003
Reviewer:  Jerry G.
      Katchatchurian  from Bellvue Shore
Tom, I happened to install the samples schemas in the database. However, I want
to get rid of them now. The manuals talk about how to create them, but do not
mention how to drop them. What is the best way to do it ? Just dropping each one
wih the cascade option ? I'm not sure. I'm afraid I can mess up something.

Thanks

          Followup:

drop user cascade;

[tkyte@tkyte-pc schema]$ ls */*_drop.sql
human_resources/hr_drop.sql  order_entry/oe_drop.sql
sales_history/sh_drop.sql
order_entry/oc_drop.sql      product_media/pm_drop.sql  shipping/qs_drop.sql
[tkyte@tkyte-pc schema]$

there are "drop" scripts that drop each object in the schema, but drop user
cascade gets them out of there.

      Rating: 5
Sorry, I forgot
      about the tablespaces  June 09, 2003
Reviewer:
      Jerry  from Bellvue Shore
Oh yes, how about the sample schemas tablespaces ? How can I drop them ?

          Followup:

after you drop the users, drop the tablespace.

      Rating: 5
one schmea per
      year - is that right?  June 25, 2003
Reviewer:
      Safrin  from India
Thanks tom- "asktom" make us to be in line with current markend trend

My question:

Ver - 8.1.5 work group server
OS - Novel netware 5.0
company -     medium size
data    -    2 GB each year
Users    -    Max 150

We are creating schema for every year, and making common tables/master tables
available to all years using synonym/grants.

Will this approach is right for every year of operation? or can
you suggest any other method.

Note: Partition is not possible in our installation.

          Followup:

it seems fine, if it is working for you (given the info i have, no idea what
your goals are...)

      Rating: 4
new schema for
      each year  June 26, 2003
Reviewer:  Safrin
       from India
Thanks for answer.

In this method of creating schema for every year, we find it "difficult" to
maintain the same for the following reason.
1. New schema has to be created every year
2. We have to give grant for each new schema (year)
3. Synonym has to be created in the new schema for common and master related
objects (alter session will not work, since queries refering both master and
transaction tables)

Since partition can not be used in our installation, I would like to know, is
any other good method available?
or else, can you plz give me any tips for using this method more safely &
effectively.

Thanks in advance.

          Followup:

since all you have to do is log into a new schema, run a script that

o creates your tables
o grants on your tables
o drops and creates synonyms

I do not see this as "hard"?  I mean it boils down to:

SQL> @happy_new_year

doesn't it?

      Rating: 5
what about
      migrating to different schema  November 06, 2003

Reviewer:  LB  from New Jersey
Hi Tom,

We (2 developers) have been working on a reporting application for about 8
months now. We are soon going to be migrating all our data, and code to a new
schema.  The 2 schemas are quite different since the old schema was not
normalized and the new one is.  That means there will be several more in-between
tables, primary keys, constraints,etc.

We (the 2 developers) have two options I can think of:

1) Change all our procedure, function, and report code to match the new schema.
That way everything will be in sync.

2) Create some views for the tables that have the same field and table names as
our code currently refers.  That would make the conversion job easier.  Plus the
old schema is a little easier to understand with more legible names and less
table joins.

I am leaning towards option one since that is probably the long-term better
solution although it will mean much more work.

Do you have any recommendations?

(p.s. I searched the site for this issue but didn't find it so I hope I am not
repeating the question.)

          Followup:

wow, 8 months on a reporting application?

I personally would be doing #1 and only doing #2 when performance was not
acceptable.

why?  i'd be really bored after 8 months, it would cost less, it would be
easier, it is what views are for...

      Rating: 5
Thanks for
      reply  November 07, 2003
Reviewer:  LB
       from New Jersey
8 months is a lot?
Complex Actuate reports take a long time to develop.  In our last project we
were working on a reporting application for about 2 1/2 years.  There were over
100 reports that were run nightly for over 100 different datasets.

In any case it sounds like you recommend the option of views?  (I wasn't sure
because you said option 1 but referred to the views in option 2).

Thanks for your time and attention!

          Followup:

whoops -- got the numbers 110% backwards.

VIEWS (#2) and only do #1 when #2 did not perform adequately.  sorry about that.

      Rating:
      5
Thanks!!!  November 07, 2003
Reviewer:
      LB  from New Jersey

      Rating: 5
Followup
      question.  November 07, 2003
Reviewer:
      Kashif  from Reston, VA
Hi Tom,

Can you expand on the following cons to having multiple schemas:

"
o causes your shared pool size to increase 1,000 times (shared sql goes down the
tubes)
o latching on the shared pool goes WAY up (latching = locks = serialization
device = slows you down)
"

I'm not sure how these are cons to having multiple schemas and not single
schemas. Thanks.

Kashif

          Followup:

well, if you have N multiple schemas -- all of the same except for the schema
name, you will have N copies of each parsed sql statment in the shared pool.

instead of one copy of "select * from emp", there will be N of them.

instead of having ONE hard parse on "select * from emp" there will be N of them.

One each for each schema.

So, you need a shared pool N times as large

And one that does N times as much work.

November 07, 2003 Tony -- Thanks for the question regarding
      "Cursors in package spec", version 8.1.7

      You Asked
Tom,

Can you tell me where the best place to put cursors is, the package spec or
body?
The reason I'm asking is that I work with a developer who insists on putting ALL
cursors in the package spec, he tells me that this is the right way to do it.
Surely you we should only put cursors in the package spec if they are used more
than once in the package. All the cursors in this package are only used once, so
I believe these should be put in the declaration section of the
procedure/function they are called from. I'm I right?
What are advantages and disadvantages of putting ALL cursors in the package
spec?
Does it affect performance?

Thanks you

      and we said...
Me, my personal preference is, has been and will always be:

is
  ...
begin
  ....
  for x in ( select .... )
  loop

That is for smallish result sets of under say 50-100 rows.  I like this because:

o it is marginally more efficient CPU wise then using an explicit cursor
o I can glance at the code and easily see what data I'm dealing with -- the
  query is staring me in the face.
o it makes me use views when queries get "gnarly big".  I still see what data
  I'm querying up in the view, but the complexity of the view is hidden.

Now, if I HAVE to use an explicitly defined cursor, I would opt for a LOCAL one.
 Why?  Well,

o it is marginally more efficient CPU wise then a global cursor (one defined
  in the spec).
o I can still glance at the code and easily see what I'm dealing with
o It makes me use views since the query is in my procedure and I don't want it
  to obscure the rest of the code

(see a pattern here?)...

Also, a cursor with a local scope (in a procedures STACK) automagically cleans
up after itself.  No "if cursor%isopen" junk littering my code (none of my code
ever has used that 'feature').

I don't have to worry that "oh, you know procedure P1 uses cursor_x, and I use
cursor_x, and since they are the same cursor_x will we stomp on eachother?" --
you know, that problem you ALWAYS have with global variables.

So, in order of preference:

a) no cursors (select into, for x in ( select..... )
b) locally declared cursors, when forced into it for whatever reason
   - bulk binds that need to use the LIMIT clause
   - ref cursors

globally declared cursors, in a packge spec, I would avoid as you lose

o encapsulation
o performance
o readability of the package body
o they are "globals" and globals are in general "bad"

Review &
      Followup

      Rating:
      5
  June 13, 2003
Reviewer:  Mirjana
       from Barcelona

      Rating: 5
Great
      response  June 13, 2003
Reviewer:  Tony
       from Reading, UK
Very good point about using...

for x in ( select .... )
  loop

and using views (there are none used on this project, just huge cursors).
Also project standards insist on creating cursors for selecting MAX values and
COUNTs(single row queries) rather than select into.
Is this method good or bad?

          Followup:

that method is dog slow, requires more code, incurrs more programming bugs.

select into is

o correct
o fast
o efficient
o more correct
o the only way real programmers do it. (maybe we can shame people into
changing?)

      Rating: 5
How to use view
      in for c in (select * from emp) loop  June 13, 2003

Reviewer:  Yong Wu  from CA, USA
Tom,

I have used

for c in (select ...) loop
end loop;

in all my development.

I don't know or never use 'view' in the loop.
Can you give an example of using view? in

for c in (select ...) loop
end loop

Thanks,

          Followup:

sure

for x in (select * from all_users) loop

it is no different then a table.

      Rating: 4
ref
      cursor  June 13, 2003
Reviewer:
      A reader
Hi Tom,

If it's a ref cursor, is it true we only define ONE package spec in ONE schema,
like

create or replace package pkg_refcur
is
    type refcur is ref cursor;
end;
/

and use it(pkg_refcur.refcur) whenever we need a ref cursor?

thanks,

          Followup:

exactly, yes. (and in 9i, just use SYS_REFCURSOR)

      Rating: 5
Reply to main
      question  July 02, 2003
Reviewer:  Tony
      Johnston  from Reading, UK
Tom,

I showed the developer your response and this was his reply

<quote>
My view on this is simple.

All of my cursor definitions are *fully parameterized* and hence can be
considered to be individual (reusable) units of code. As a result, it is my view
that they all should exist at the same level as functions and procedures.

Having the benefit of hindsight I would not now place the global cursor
definitions in the package specification but in the package body. The only
reason for this is one of scope.
</quote>

Can I have your comments on this please?

Thanks

          Followup:

to each their own.

Me, I find

is
begin
   open c(x,y,z);
   loop
      fetch c into ....

or

is
begin
    for x in c(x,y,z)
    loop

infinitely less readable then

is
begin
   for x in ( select * from emp where .... )
   loop

and I use views for big queries.  All of my queries are *fully parameterized* as
well.  I just prefer implicit cursors to explicit ones and use them where ever
practical.

At the end of the day -- it is more a matter of style then anything else.

      Rating:
      5
Thanks  July 03, 2003
Reviewer:
      Tony  from Reading, UK
I have used a mixture of the three (not really used views much, I've found that
a lot of team leaders I've worked for seem to be scared of database objects, eg.
TYPEs, VIEWs and even TRIGGERS) but I do agree with you that yours is much more
readable and I will endeavour to use the method you use.

Thanks for a great site.

p.s. The three sample chapters from your new book are very good, I will place my
order as soon as it is released.

      Rating:
      5
  August 02, 2003
Reviewer:
      A reader
"
I don't have to worry that "oh, you know procedure P1 uses cursor_x, and I use
cursor_x, and since they are the same cursor_x will we stomp on eachother?" --
you know, that problem you ALWAYS have with global variables.
"

Tom please explain and illustrate, suppose we have a cursor declared in a
package spec, and it is called simultaneously
from two different places, what is the downside of it, what will happen. It is
just a select, why should it make a difference even if it is called from 100
places simultaneously.

This is different from global variables. If session A sets the global variable
to a value 1 , then session 2 calling it might have a problem.

Please discuss.

          Followup:

try to open that cursor two times, without closing it.  see what happens.

global variables are not shared over sessions.  you've got that wrong.

      Rating: 5
session 1 and
      session 2  August 02, 2003
Reviewer:
      A reader
In session one I did the following..

SQL> ed
Wrote file afiedt.buf

  1  create or replace package test1 as
  2  cursor c1 is
  3  select deptno
  4  from dept;
  5* end;
SQL> /

Package created.

SQL> ed
Wrote file afiedt.buf

  1  declare
  2  lv_var number;
  3  begin
  4  open test1.c1;
  5  fetch test1.c1 into lv_var;
  6  dbms_output.put_line(lv_var);
  7* end;
SQL> /

PL/SQL procedure successfully completed.

Then I opened session 2 and did the following

SQL> declare
  2  lv_var number;
  3  begin
  4  open test1.c1;
  5  fetch test1.c1 into lv_var;
  6  dbms_output.put_line(lv_var);
  7  end;
  8  /

PL/SQL procedure successfully completed.

SQL> set serveroutput on
SQL> declare
  2  lv_var number;
  3  begin
  4  open test1.c1;
  5  fetch test1.c1 into lv_var;
  6  dbms_output.put_line(lv_var);
  7  end;
  8  /
declare
*
ERROR at line 1:
ORA-06511: PL/SQL: cursor already open
ORA-06512: at "SCOTT.TEST1", line 3
ORA-06512: at line 4

I infer two things from the above

1.A single cursor cannot be opened twice from the same session , if it is not
closed the first time.

2.But from across different sessions it can be opened simultaneously, but only
once. If we try to open it the second time from any session , it will fail.

Tom, how can we ensure that the cursor is closed , what are the different ways..

I did the following..

SQL> set serveroutput on
SQL> declare
  2  lv_var number;
  3  begin
  4  close test1.c1;
  5  open test1.c1;
  6  fetch test1.c1 into lv_var;
  7  dbms_output.put_line(lv_var);
  8
  9  end;
 10  /
10

PL/SQL procedure successfully completed.

Lastly, my dept has data, but why is the dbms_output.put_line not showing any
output.. above

SQL> select * from dept;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING     NEW YORK
        20 RESEARCH       DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS     BOSTON

          Followup:

1) correct

2) correct -- each session has it's own data segment if you will, just like if
you start up the same program two times in your OS.  they have their own data
and cursors are just "variables" like a number or date really.

If you follow my standard -- don't use globals -- you don't have to worry about
it.

If you use globals AND the cursor is left open -- it is what we call "a bug".
You cannot (should not) use "cursor%isopen" and close it -- as that might
totally mess up some other process.  A cursor should be closed when you are done
with it and if it is not -- you have a bug to fix.

lastly, your output is there -- look at the 10 right above plsql procedure
successfully completed.

      Rating: 5
each session
      has it's own data segment   August 02, 2003

Reviewer:  A reader
"each session has it's own data segment"

Tom please elaborate on the above. For each session what does this data segment
hold.

          Followup:

whatever you want -- it is your plsql global variables that are in there.

      Rating: 5
why not fail
      the cursor  August 02, 2003
Reviewer:
      A reader
I find it interesting that , you admit that leaving a cursor open is a very bad
idea, but on the other hand the software allows to do so.

1.Why cant oracle come up with an enhancement and make it impossible to leave a
cursor open, i.e. make it a must to close it.

2.Secondly, what is a memory leak due to leaving cursors open, is the scenario
we are discussing any way related to it.

          Followup:

That isn't the point.

the software must allow you to do so.

it can be very very useful.

the bad practice is leaving it open when you didn't mean to, after you were done
using it.

my point was -- if your code ever tries to open a cursor that is already opened
-- that is most likely a bug in your code.  Just like two subroutines in a VB
program both opening the same file at the same time (you sort of get "garbage"
in that file as a result)

1) we do already.  if you use local variables.  done.  if you use globals, you
get the POWER of leaving them open, coupled with the responsibility of managing
them.

2) leaking memory is much like leaking a cursor.  you opened a resource, you
never close it and never reference it again, you've leaked that resource.  do it
often enough and you run out of that resource.

      Rating: 5
1+1 !=
      2  August 02, 2003
Reviewer:  A reader

Tom

Somehow your statements are not matching up.

You say it is a bad practice, but at the same time you say

-
if you use globals, you
get the POWER of leaving them open, coupled with the responsibility of managing
them.
-

Please tell where can this be useful, where can this be used powerfully, in what
scenario, to handle what, accomplish which kind of functionality?

And what does managing them amount to? Well, I leave it open, in this context
what does 'managing them' amount to ?

          Followup:

global variables, in general, are a bad practice.  They are easily abused by
programmers that don't know any better or get a little "lazy" (oh, bother, don't
add yet another formal documented parameter -- lets just put a global up here.
we'll remember to set it properly everywhere we need to).

Sometimes -- they are the only way to do it (think about if you wanted to know
how many times a procedure in a package was invoked in your session -- perhaps a
global variable would be useful and efficient there.  In this case, I would hide
it in a package body -- NOT the spec, so the procedure in the body could
increment it and then provide a "get" function to return its value upon demand.

Sometimes -- like in a trigger that wants to communicate information back to the
caller -- it is the only way.  The trigger can set variable values in a package
spec that other procedures/function can then read.

Global variables are a programming tool -- easily and frequently abused by those
that do not yet know any better (haven't been burned hugely by them yet -- it'll
happen, 100% sure on that) or those that have gotten lazy in their programming.

Not always abused, easily abused.

I do not have a real world case where by leaving a global cursor open in a
package spec or body would be considered "good".  I'm sure someone has one -- I
just never do it.

      Rating: 5
A rationale for
      exposing cursors in package spec  November 07, 2003

Reviewer:  Stew Stryker  from Hanover, NH USA

I publish a cursor in the package specification when I need to share that cursor
with another procedure.  In my case, I sometimes need to run the same cursor in
the automated test package.  It's not often, but I've done it twice in the past
4 months.

In a specific example where I'm creating a summary table, I use the public
cursor compare the original query result with the results in the summary table.

If anyone care, I recently started using the Test-Driven Development methodology
(as espoused by Kent Beck and other XPers) and the EXCELLENT FREE utPLSQL
automated testing suite from Steven Feuerstein.  It has totally changed how I
program, all for the better.  My quality's much higher and the work doesn't take
any longer than before.

To bring this shameless plug back on topic, my test package runs the same query
as my main program and I use it to compare the results.  So it has to be public.

          Followup:

and now you have a global, ugh.

don't see why your test package -- which should be calling UNITS (subroutines)
needs access to something that should be private.  SQL is not the interface here
-- subroutines that take inputs and produce outputs are.

I still haven't seen the light on this one.  I cannot understand why you need
"the sql"

November 08, 2003 muhammad ahmad -- Thanks for the question
      regarding "About Oracle Production numbers", version 9.1.2

      You Asked
I just want to ask how Oracle design the production no.
I mean after oracle7 it is oracle8 then 8i then 8.1.5. then 8.1.6. then 8.1.7
then 9i and so on. How they suggest these serials.

Secondly what is currently available version no and which is in the pipeline.

thanks, it is just an interst for me to know oracle well

      and we said...
thats funny -- a question about how the numbering works, but the database
version you give in the question is "one that does not exist :)"  9.1.2 isn't a
version.  I guess it can be confusing.

the numbering scheme changes over time.  we've had:

7.0.<patch level>
7.1.<patch level>
7.2.<patch level>
7.3.<patch level>
8.0.<patch level>

then 8i, was all 8.1

8.1.5.<patch level>
8.1.6.<patch level>
8.1.7.<patch level>

and in 9i, we changed it once again

9iR1
9.0.<patch.level>
9iR2
9.2.<patch.level>

and in the 10 time frame it will be:

10.1.<patch level> 10g release 1
10.2.<patch level> 10g release 2 and so on

Currently, 9.2.0.3 or 9.2.0.4 is the production version (depends on platform)
and 10.1 is in the pipeline.
Review &
      Followup

      Rating:
      4
Thanks  September 07, 2003
Reviewer:
      Muhammad Ahmad Imran  from pakistan
Thanks Tom, it is definitely funny for you but some serious for me.

Actually one of my student in database class ask me about that, so i ask you for
the topic.

Anyways thanks for the answering the foolish questions that arises in the minds

Ahmad Imran

          Followup:

never said it was foolish - said to read the link where I went into lots of
detail on the topic!  thats all.

      Rating: 3
And what is
      that link????  September 08, 2003
Reviewer:
      A reader
Hi Tom,

 Bothering you, but not able to search the link you suggested in follow up.

          Followup:

sorry -- thought it was a different question.  i got confused.  no link for this
one.

      Rating: 5
version -
      8.1.7.4.10?  September 12, 2003
Reviewer:
      Sri  from India
Dear Tom,

I hope think this question will be appropriate here.

I installed 8i Release 3 (8.1.7.0.0) in my Windows 2000 Server.  After this I
applied the patchset 8.1.7.4.1.  Now when I gave "Select Version From
V$Instance" in sql*plus, it gave me the result "8.1.7.4.1".  After this I
applied the 8.1.7.4.10 patch.  The process went through smooth and didn't give
me any problems.  However, when I restarted the database and queried V$Instance,
it still says "8.1.7.4.1".  Is this the expected behaviour?  If so, how else can
I check (just for curiosity) which is the patch that is installed in my
database.

Thank you.

          Followup:

not familar with the ".10" patch.  i'll suggest you file a tar with support on
this one.

      Rating: 5
RE: 8.1.7.4.10
      Patch  September 12, 2003
Reviewer:  Mark A.
      Williams  from Mark A. Williams
Yes, that is the expected behavior - it is *documented* right there in the patch
notes.  You did *read* the patch notes before applying the patch, right?

If you want to find out what is installed, the easiest way is simply to click on
the "Installed Products" button in the Oracle Universal Installer.

- Mark

      Rating: 4
Is newer
      version available  November 08, 2003
Reviewer:
      Muhammad Ahmad Imran  from Faisalabad, Pakistan
Hello Tom,

Last time when oracle release 9i R1 I subscribe for the cd's and they send me
the a set of 4 cd's. I just want to know is it in practice or in pipeline that
post the cd's of release 10g 1 or 2.
if yes how can I subscribe it.

Thanks

          Followup:

10g release 1 is due out at the end of calendar year 2003.  when it is
production, it'll be available for download on otn.oracle.com and you can order
cd's to be shipped at that point in time as well.

      Rating: 3
I think, I got
      you on this one!  November 08, 2003
Reviewer:
      A reader
Hi Tom,
I was in Oracle training course, and they mentioned ( documented in notes as
well) that: -
Starting with Oracle9i database release 2, there is also application server
release number. For 9.2.0.1.0:-
9---> Major database release number
2---> Database maintenance release number
0---> Application server release number
1---> Component specific release number
0---> Platform specific release number

November 08, 2003 Hitesh -- Thanks for the question regarding
      "SPFILE parameter", version 9.0.1

      You Asked
If the instance is running with SPFILE parameter and you have changed the
dynamic parameter to SPFILE only. At this time it does not validate the new
parameter value. Now if you shutdown the database and start the database, it
fails as SPFILE has invalid value for the changed dynamic parameter. You can't
edit the SPFILE and can't create the init.ora file. This requires DBA to create
pfile everytime you start the database so that you have a good working copy of
init.ora file. This glitch will force DBA to use old style init.ora file.

With SPFILE, database can be started remotely. When I tried with 8i client, it
still asks for pfile location. I created init.ora file on client with one
parameter spfile=?/dbs/spfileORA9.ora
and it worked. Is it the correct way to start the database remotely?

Thanks,
Hitesh Patel

      and we said...
you can create a pfile which simply has:

spfile=/path/to/good/spfile.ora

and startup pfile=<that pfile>.

I do not see this "glitch" preventing anything from working.  The solution is
simple (heck you found it yourself in the very next part of the question).  Even
if we validated the file -- it would not assure us it would be there during the
next startup (or that it would even be an SPFILE)

You cannot use the 8i client as it is obviously SPFILE "unaware".  The client
used to read and process the pfile, the 8i client is expecting to ALWAYS read
and parse the pfile.  Use the 9i client.  Your workaround is sufficient.

Review &
      Followup

      Rating: 3
Editing
      spfile  December 12, 2001
Reviewer:  Jan van
      Mourik  from Houston, TX
Tom,

Didn't quite get it yet...
Is there any way to edit the spfile when the database is down? Or to create an
init.ora from the spfile when the database is down?

Thanks, jan

          Followup:

tkyte@TKYTE9I.US.ORACLE.COM> connect sys/manager as sysdba;
Connected.

tkyte@TKYTE9I.US.ORACLE.COM> shutdown
Database closed.
Database dismounted.
ORACLE instance shut down.

tkyte@TKYTE9I.US.ORACLE.COM> create pfile from spfile;
File created.

tkyte@TKYTE9I.US.ORACLE.COM> create spfile from pfile;
File created.

      Rating: 5
Easy
      enough...  December 12, 2001
Reviewer:  Jan van
      Mourik  from Houston, TX
Thanks a lot Tom, that looks easy enough :-)

jan

      Rating: 2
Error while
      creating spfile  January 13, 2002
Reviewer:
      Kazi Nazmul Hassan  from Bangladesh
I followd the standerd notations and got this error. Couldn't work with spfile.

SQL> connect sys/a as sysdba
Connected.
SQL> shutdown
Database closed.
Database dismounted.
ORACLE instance shut down.
SQL> create spfile from pfile;
create spfile from pfile
*
ERROR at line 1:
ORA-01078: failure in processing system parameters
LRM-00110: syntax error at ''

SQL> show parameter spfile;
ORA-01034: ORACLE not available

SQL> spool off

          Followup:

check your init.ora parameters, sounds like something is formatted improperly.
I cannot reproduce.

      Rating:
      4
  December 03, 2002
Reviewer:
      A reader
Tom,

When you shutdown the database, how does oracle maintain the user session.i.e.of
sysdba.

          Followup:

you still have your dedicated server:

...
Oracle9i Enterprise Edition Release 9.2.0.2.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.2.0 - Production

sys@ORA920> !ps -auxww | grep ora920
ora920   27412  0.0  1.4 248872 7216 ?       S    Dec02   0:03 ora_pmon_ora920
ora920   27414  0.0  5.6 250292 29220 ?      S    Dec02   0:00 ora_dbw0_ora920
ora920   27416  0.0  1.3 253428 7144 ?       S    Dec02   0:05 ora_lgwr_ora920
ora920   27418  0.0  1.6 249232 8484 ?       S    Dec02   0:00 ora_ckpt_ora920
ora920   27420  0.0  5.8 248324 30120 ?      S    Dec02   0:05 ora_smon_ora920
ora920   27422  0.0  1.9 248280 9848 ?       S    Dec02   0:00 ora_reco_ora920
ora920   27424  0.0  2.0 248276 10512 ?      S    Dec02   0:00 ora_cjq0_ora920
ora920   27426  0.1  6.8 248376 34968 ?      S    Dec02   2:19 ora_qmn0_ora920
ora920   27428  0.0  1.1 248856 5864 ?       S    Dec02   0:00 ora_s000_ora920
ora920   27430  0.0  1.1 248736 5692 ?       S    Dec02   0:00 ora_d000_ora920
ora920   29728  0.5  2.3 248448 11832 ?      S    14:46   0:00 oracleora920
(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))
tkyte    29729  0.0  0.1  3820  976 pts/4    S    14:46   0:00 /bin/bash -c ps
-auxww | grep ora920
tkyte    29731  0.0  0.1  3252  552 pts/4    S    14:46   0:00 grep ora920

sys@ORA920> shutdown
Database closed.
Database dismounted.
ORACLE instance shut down.
sys@ORA920> !ps -auxww | grep ora920
ora920   29728  1.4  2.4 61144 12716 ?       S    14:46   0:00 oracleora920
(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))
tkyte    29732  0.0  0.1  3820  976 pts/4    S    14:46   0:00 /bin/bash -c ps
-auxww | grep ora920
tkyte    29734  0.0  0.1  3252  552 pts/4    S    14:46   0:00 grep ora920

sys@ORA920> exit
Disconnected from Oracle9i Enterprise Edition Release 9.2.0.2.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.2.0 - Production
[tkyte@tkyte-pc tkyte]$ ps -auxww | grep ora920
tkyte    29736  0.0  0.1  3252  552 pts/4    S    14:47   0:00 grep ora920
[tkyte@tkyte-pc tkyte]$

when you connect as sysdba, you fire up a dedicated server -- that is where it
is.

      Rating:
      5
  December 03, 2002
Reviewer:
      A reader
Thanks tom for your wonderful answer..

      Rating: 3
Editing
      Spfile  December 04, 2002
Reviewer:  Kevni
      Bishop  from UK
Tom

Think this site is the best....

If you are using an spfile and have the one line entry
spfile=?/dbs/spfileORA9.ora in your init.ora file. In the case of a typo, could
you not overwrite it in the init.ora file below the spfile entry? Thereby
correcting the error, then once the database is up re-enter the invalid entry
into the spfile and remove the extra line from the init.ora.

          Followup:

sorry -- not following the train of thought here.  If using an spfile, there is
no init.ora file (pfile) necessarily.  You would create one in order to startup.

      Rating: 3
Editing
      spfile  December 04, 2002
Reviewer:  Kevin
      Bishop  from UK
OK Tom

I was offering the following way of working around having to re-create the
init.ora file starting the database, then re-creating the spfile.
As in this example, we overwrite the db_block_buffers parameter as oppossed to
overwriting a corrupt entry.

For arguments sake lets say we are working in /u01/9i/dbs and the
"spfileSID.ora" has db_block_buffers=200 entry in it.

mv spfileSID.ora spfileSID.foo
create initSID.ora with the following :-
spfile=/u01/9i/dbs/spfileSID.foo
db_block_buffers=10000

Startup the database, whalla 10000 block buffers.
alter sysetm set db_block_buffers=10000 scope=spfile;

Remove initSID.ora file and mv spfileSID.foo spfileSID.ora

          Followup:

Excellent -- got it.  Nice.  Consider:

sys@ORA920.US.ORACLE.COM> alter system set processes=5000 scope=spfile;
System altered.

sys@ORA920.US.ORACLE.COM> shutdown
Database closed.
Database dismounted.
ORACLE instance shut down.

...
Connected to an idle instance.

idle> startup
ORA-00064: object is too large to allocate on this O/S (1,4720000)
idle> Disconnected

I'm hosed! cannot start.  processes=5000 too big.  So, using Kevins approach and
the fact that the LAST parameter value will be used, I just:

[ora920@tkyte-pc-isdn dbs]$ cat temp.ora
spfile= /usr/oracle/ora920/OraHome1/dbs/spfileora920.ora
processes = 150

Connected to an idle instance.

idle> startup pfile=temp.ora
ORACLE instance started.

Total System Global Area  143725064 bytes
Fixed Size                   451080 bytes
Variable Size             109051904 bytes
Database Buffers           33554432 bytes
Redo Buffers                 667648 bytes
Database mounted.
Database opened.
idle> show parameter processes

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
...
processes                            integer     150
idle> alter system set processes=150 scope=spfile;
System altered.

and there you go.  Very nice, Thanks!

      Rating:
      4
  December 05, 2002
Reviewer:  Sagi
       from India
Hi Tom,

First of all thanx to you and Kevin for a wonderful solution.

Coming to your example, wherein you set the parameter value for processes to a
wrong value and then when you tried to start the DB got the error.

But assuming that I have changed a few parameter or say i have changed a
parameter long ago and now i restart the DB, how will i know the error is
related to which parameter.

I know that you can look the ALERT log file. But then as I said above it could
have been one of the parameters set long ago or a few of them at the same
day....

Does'nt the error (while starting the DB) tell you which parameter value is set
wrong???

I dont have Oracle 9i DB. Therefore can'nt try out for myself.

Would appreciate your answer and thanx for the good work.

Regards,
Sagi

          Followup:

well, you get the:

[tkyte@tkyte-pc-isdn sqlstuff]$ oerr ora 64
00064, 00000, "object is too large to allocate on this O/S (%s,%s)"
// *Cause:  An initialization parameter was set to a value that required
//          allocating more contiguous space than can be allocated on this
//          operating system.
// *Action: Reduce the value of the initialization parameter.

and then if you didn't know/remember, I guess I would:

[tkyte@tkyte-pc-isdn dbs]$ strings spfileora920.ora
*.aq_tm_processes=1
*.background_dump_dest='/usr/oracle/ora920/OraHome1/admin/ora920/bdump'
*.compatible='9.2.0.0.0'
*.control_files='/usr/oracle/ora920/OraHome1/oradata/ora920/control01.ctl','/usr/
oracle/ora920/OraHome1/oradata/ora920/control02.ctl','/usr/oracle/ora920/OraHome1
/oradata/ora920/control03.ctl'
*.core_dump_dest='/usr/oracle/ora920/OraHome1/admin/ora920/cdump'
*.db_block_size=8192
*.db_cache_size=31457280
*.db_create_file_dest='/usr/oracle/ora920/OraHome1/oradata/ora920'
*.db_domain=''
*.db_file_multiblock_read_count=16
*.db_name='ora920'
*.dispatchers='(PROTOCOL=TCP) (SERVICE=ora920XDB)'
*.fast_start_mttr_target=300
*.hash_join_enabled=TRUE
*.instance_name='ora920'
*.java_pool_size=20971520
*.job_queue_processes=10
*.large_pool_size=16777216
*.log_archive_dest='/usr/oracle/ora920/OraHome1/oradata/ora920_arch'
*.log_archive_start=TRUE
*.open_cursors=300
*.pga_aggregate_target=25165824
*.plsql_native_library_dir='/usr/oracle/ora920/OraHome1/native'
*.plsql_native_make_file_name='/usr/oracle/ora920/OraHome1/plsql/spnc_makefile.mk
'
*.plsql_native_make_utility='make'
*.processes=150
*.query_rewrite_enabled='FALSE'
*.remote_login_passwordfile='EXCLUSIVE'
*.shared_pool_size=52428800
*.sort_area_size=524288
*.star_transformation_enabled='FALSE'
*.timed_statistics=TRUE
*.undo_management='AUTO'
*.undo_retention=10800
*.undo_tablespace='UNDOTBS1'
*.user_dump_dest='/usr/oracle/ora920/OraHome1/admin/ora920/udump'
*.utl_file_dir='*'
[tkyte@tkyte-pc-isdn dbs]$

and look for the nasty one (or at least now you have enough to create a pfile
from scratch as well)

      Rating:
      5
Interesting  May 09, 2003
Reviewer:
      Doug  from CT, USA
I found this very interesting as I had difficulty with a bad parameter in the
spfile a few months ago.

Out of curiosity, even the pfile format in 9i has this new *. before each line.
What's the reason for it?

          Followup:

it is not necessary, it is optional.  ignore it or don't use it.  up to you.

      Rating: 4
Many dedicated
      server process  May 10, 2003
Reviewer:
      A reader
Tom,

I see so many dedicated server process running in my server, for my instance
oracleTSTETMCT there 6 dedicated server processes running.

is there anything wrong happening over here, please let me know

HYD280R:oracle:TSTEENWS: ps -ef|grep oracle
  oracle 20167     1  0   May 08 ?        0:16 oracleTSTETMCT
(DESCRIPTION=(LOCAL=no)(ADDRESS=(PROTOCOL=BEQ)))
  oracle 20173     1  0   May 08 ?        0:17 oracleTSTETMCT
(DESCRIPTION=(LOCAL=no)(ADDRESS=(PROTOCOL=BEQ)))
  oracle 20165     1  0   May 08 ?        0:16 oracleTSTETMCT
(DESCRIPTION=(LOCAL=no)(ADDRESS=(PROTOCOL=BEQ)))
  oracle 20986 20975  0 00:12:28 pts/6    0:00 -sh
  oracle   380     1  0   Apr 21 ?        0:01
/u01/app/oracle/product/8.1.7/bin/tnslsnr LISTENER -inherit
  oracle   406     1  0   Apr 21 ?        0:01 ora_pmon_TSTEENWS
  oracle   408     1  0   Apr 21 ?        0:02 ora_dbw0_TSTEENWS
  oracle   410     1  0   Apr 21 ?        0:02 ora_lgwr_TSTEENWS
  oracle   412     1  0   Apr 21 ?        1:44 ora_ckpt_TSTEENWS
  oracle   414     1  0   Apr 21 ?        0:00 ora_smon_TSTEENWS
  oracle   416     1  0   Apr 21 ?        0:00 ora_reco_TSTEENWS
  oracle   432     1  0   Apr 21 ?        0:01 ora_pmon_DEVETMCT
  oracle   434     1  0   Apr 21 ?        0:06 ora_dbw0_DEVETMCT
  oracle   436     1  0   Apr 21 ?        0:07 ora_lgwr_DEVETMCT
  oracle   438     1  0   Apr 21 ?        1:39 ora_ckpt_DEVETMCT
  oracle   440     1  0   Apr 21 ?        0:02 ora_smon_DEVETMCT
  oracle   442     1  0   Apr 21 ?        0:00 ora_reco_DEVETMCT
  oracle   450     1  0   Apr 21 ?        0:01 ora_pmon_TSTESCLC
  oracle   452     1  0   Apr 21 ?        0:01 ora_dbw0_TSTESCLC
  oracle   454     1  0   Apr 21 ?        0:01 ora_lgwr_TSTESCLC
  oracle   456     1  0   Apr 21 ?        1:29 ora_ckpt_TSTESCLC
  oracle   458     1  0   Apr 21 ?        0:01 ora_smon_TSTESCLC
  oracle   460     1  0   Apr 21 ?        0:00 ora_reco_TSTESCLC
  oracle 20892     1  0 09:39:31 ?        0:01 oracleDEVETMCT
(DESCRIPTION=(LOCAL=no)(ADDRESS=(PROTOCOL=BEQ)))
  oracle 20169     1  0   May 08 ?        0:17 oracleTSTETMCT
(DESCRIPTION=(LOCAL=no)(ADDRESS=(PROTOCOL=BEQ)))
  oracle 20171     1  0   May 08 ?        0:16 oracleTSTETMCT
(DESCRIPTION=(LOCAL=no)(ADDRESS=(PROTOCOL=BEQ)))
  oracle 24516     1  0   Apr 27 ?        0:20 ora_dbw0_TSTETMCT
  oracle 24518     1  0   Apr 27 ?        0:19 ora_lgwr_TSTETMCT
  oracle 24514     1  0   Apr 27 ?        0:01 ora_pmon_TSTETMCT
  oracle 20894     1  0 09:43:07 ?        0:00 oracleDEVETMCT
(DESCRIPTION=(LOCAL=no)(ADDRESS=(PROTOCOL=BEQ)))
  oracle 24520     1  0   Apr 27 ?        1:03 ora_ckpt_TSTETMCT
  oracle 24522     1  0   Apr 27 ?        0:04 ora_smon_TSTETMCT
  oracle 24524     1  0   Apr 27 ?        0:00 ora_reco_TSTETMCT
  oracle 20175     1  0   May 08 ?        0:11 oracleTSTETMCT
(DESCRIPTION=(LOCAL=no)(ADDRESS=(PROTOCOL=BEQ)))
  oracle 20975 20973  0 00:12:04 pts/6    0:00 -sh

Thanks for your time!

          Followup:

so?  you will see these for people who have connected to your database.

6 is tiny.

      Rating: 4
Removing spfile
      parameter?  July 31, 2003
Reviewer:  Doug
       from CT, USA
Tom - is there anyway to remove a parameter from the spfile without copying to
an init version, editing and re-createing?

          Followup:

you could set it to its default value, but no -- not really otherwise.

      Rating: 5
spfile
      scope  August 12, 2003
Reviewer:  Reader

If I issued,

alter system set undo_tablespace = undotbs2;

without specifying SCOPE, even after I bounced the instance the change persisted
as seen below. Does it mean that without the scope clause, the change is
affected for the current instance as well as it updated the spfile. If so, why
do we need SCOPE=BOTH clause? Or it depends on which parameter we change using
alter system? Thanks.

SQL> show parameter undo

NAME                                 TYPE        VALUE
------------------------------------ ----------- --------------
undo_management                      string      AUTO
undo_retention                       integer     10800
undo_suppress_errors                 boolean     FALSE
undo_tablespace                      string      UNDOTBS2

          Followup:

from the docs:

...
BOTH

BOTH indicates that the change is made in memory and in the server parameter
file. The new setting takes effect immediately and persists after the database
is shut down and started up again.

If a server parameter file was used to start up the database, then BOTH is the
default. If a parameter file was used to start up the database, then MEMORY is
the default, as well as the only scope you can specify.

      Rating:
      5
reset  November 08, 2003
Reviewer:
      Shankar  from Houston, TX
Tom, please see below. Why can't I rest the parameter open_cursors to the
default value? Thanks.

SQL*Plus: Release 9.2.0.1.0 - Production on Sat Nov 8 17:39:58 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

SQL> show parameter open_cursors

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
open_cursors                         integer     300
SQL> alter system set open_cursors = 500 scope=spfile;

System altered.

SQL> show parameter open_cursors

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
open_cursors                         integer     300
SQL> alter system set open_cursors = 500;

System altered.

SQL> show parameter open_cursors

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
open_cursors                         integer     500

SQL> show parameter spfile;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
spfile                               string      %ORACLE_HOME%\DATABASE\SPFILE%
                                                 ORACLE_SID%.ORA
SQL> show parameter open_cursors;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
open_cursors                         integer     500
SQL> alter system reset open_cursors scope=both sid='practice';
alter system reset open_cursors scope=both sid='practice'
*
ERROR at line 1:
ORA-32010: cannot find entry to delete in SPFILE

          Followup:

in single instance oracle it would be:

  1* alter system reset open_cursors scope=spfile sid='*'
ops$tkyte@ORA920> /

System altered.

      Rating:
      5
Thanks  November 09, 2003
Reviewer:
      Shankar

November 08, 2003 Andre -- Thanks for the question regarding
      "Restricted session", version 8.1.5

      You Asked
Hi!

Where do I find if session is in restricted mode ? After I executed the  "alter
system enable restricted session;" command .

      and we said...
tkyte@TKYTE816> select logins from v$instance;

LOGINS
----------
RESTRICTED

tkyte@TKYTE816> alter system disable restricted session;

System altered.

tkyte@TKYTE816> select logins from v$instance;

LOGINS
----------
ALLOWED

Review &
      Followup

      Rating: 5
ALTER SYSTEM
      QUIESCE RESTRICTED   November 08, 2003
Reviewer:
      Reader
Tom, Where ALTER SYSTEM QUIESCE RESTRICTED; command will be useful? Also, what
is the difference from using alter system enable restricted session? I read the
doc and still unable to understand the difference between the two. Thanks.

          Followup:

quiesce restricted lets you safely update code and ddl in the database without
shutting down and kicking everyone out.

the restricted session only lets people with those privs log in.  you would
"kick out" people without that and just let dba's in.

November 08, 2003 potential -- Thanks for the question regarding
      "How to parse the input string and construct dynamic sql efficiently",
      version 8.1.7

      You Asked
Tom,

Good to have a chance to ask you questions again.

I am working on a web/database application, userid will be passed from jsp to
oracle stored proc. The format of the userid will be like '50-60, 100, 200,
300-500, 700'.

In the stored proc, I need to construct a sql statement to do a update based on
these userid(s):

update t set status = 'y'
where (userid between 50 and 60)
      or userid = 100
      or userid = 200
      or (userid between 300 and 500)
      or userid = 700

How can I parse the input string and construct the above sql effciently? Also,
do I still need to consider to bind variable in this case? If so, how to do it?

Thanks.

      and we said...
the java guys couldn't make it any harder for you?

something like this:

ops$tkyte@ORA920> create table t ( userid int, status char(1) );

Table created.

ops$tkyte@ORA920> insert into t select rownum, 'n' from all_objects where rownum
<= 100;

100 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace procedure do_it( p_string in varchar2 )
  2  as
  3      type array is table of number index by binary_integer;
  4
  5      l_lbnd   array;
  6      l_hbnd   array;
  7      l_string long := p_string || ',';
  8      l_piece  varchar2(255);
  9      l_n      number;
 10  begin
 11      loop
 12          l_n := instr( l_string, ',' );
 13          exit when (nvl(l_n,0) = 0);
 14
 15          l_piece := substr( l_string, 1, l_n-1 );
 16          l_string := substr( l_string, l_n+1 );
 17
 18          l_n := instr( l_piece, '-' );
 19          if ( l_n != 0 )
 20          then
 21              l_lbnd(l_lbnd.count+1) := to_number(substr( l_piece, 1, l_n-1
));
 22              l_hbnd(l_hbnd.count+1) := to_number(substr( l_piece, l_n+1 ));
 23          else
 24              l_lbnd(l_lbnd.count+1) := l_piece;
 25              l_hbnd(l_hbnd.count+1) := l_piece;
 26          end if;
 27      end loop;
 28      forall i in 1 .. l_lbnd.count
 29          update t set status = 'y' where userid between l_lbnd(i) and
l_hbnd(i);
 30  end;
 31  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec do_it( '1, 5, 10, 20-25, 30, 42-45' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from t where status = 'y';

    USERID S
---------- -
         1 y
         5 y
        10 y
        20 y
        21 y
        22 y
        23 y
        24 y
        25 y
        30 y
        42 y
        43 y
        44 y
        45 y

14 rows selected.

You ALWAYS need to consider bind variables -- ALWAYS

Review &
      Followup

      Rating: 5
update range of
      values  September 29, 2003
Reviewer:
      Santhanam  from Bangalore India
Tom
      Excellent example
Thanks

      Rating: 5
A similar
      question  November 08, 2003
Reviewer:
      potential
Thanks a lot, Tom. Your answer is the most useful one. I have a similar question
now.

The input from java middle layer is a ',-' delimited string like '50-60, 100,
200, 300-500, 700'.

Instead of doing update like the above, I need to do a select like the
following:

select * from t
where (userid between 50 and 60)
      or userid = 100
      or userid = 200
      or (userid between 300 and 500)
      or userid = 700

How can I do this efficiently?

Thanks, Tom.

          Followup:

well, you cannot.  since you basically have a totally unknown number of inputs
-- you'll be generating unique queries many times.

Tell me -- is the number you are comparing to an integer?  so between 50 and 60
is 50, 51, 52, .... 60??  then we could do something.

      Rating:
      4
  November 08, 2003
Reviewer:
      potential
In this case, yes, the numbers will be comparing to integers. I would like to
hear from you about something that we can do. Thanks again.

          Followup:

you can one time:

create global temporary table gtt ( x int )

and then you can parse the string around commas so you'd get

50-60
100
200
300-500
700

then you can "if" on instr( piece, '-' ) for each piece and:

  if 0, then insert value into gtt
  if not zero
     parse lo and hi values
     insert into gtt
     select lo+rownum-1 from all_objects where rownum <= hi-lo+1;
  end if;

and then do a select * from t where c in ( select x from gtt );

November 09, 2003 Jagadish -- Thanks for the question regarding
      "Data Archival and Retrieval", version 8.1.7

      You Asked
Dear Tom,

I have a Data Archival and Retrieval requirement. The requirement is:
after 2 years from the transaction-closed date, the data has to be
archived. It's not the entire data. Only some 15 columns from a
combination of 8 to 10 tables. The tables as of now contain around 5
million records and are growing. Since the archiving needs to be done
only for certain conditions, we assume that around 100,000 records could be
archived annually.

Once archived the original records in the tables will be deleted. The
requirement is also to view the archived data with a reporting tool. The
archived data will be held for another 2 years and then even that will be
deleted.

I am proposing to create history tables. After 2 years the required data
will automatically move into the history tables and the original records
deleted. For reporting I am proposing the Business Objects.

The main aim from the client is to ensure that they do not encounter any
potential future System Performance issues.

My question to you is, Is my approach correct? Will this approach have
any impacts on the System Performance? Is there any better way of Data
Archival and Retrieval? How best the transportable tablespaces can be used in my
case?

Can you please give me your valuable inputs?

Thanks very much in advance.

Best Regards

      and we said...
Sounds like a description for partitioning to me.

You would partition by transaction-closed-date.

You would have a partition per year or half-year or quarter or month.

Each partition would be in it's own tablespace.

To get rid of old data, drop a partition.  Very very very very fast.

In 9i, old partitions (inactive, no more or little DML) can be compressed to 1/3
or maybe even 1/12 of their original size.

You definitely want to use partitioning for this -- the advantages are:

o archiving becomes trivial.  Heck, you can even just transport the formatted
datafiles onto tape and put them back onto the database at will allowing you to
keep as many years as you like.  Some of it would be offline but reattachable in
a matter or minutes/hours without having to rebuild the data

o the aging process is just "done" for you.  No code, no redo, no rollback, no
massive "move data from here to there", no massive deletes, no rebuilds (reorgs)
due to these big operations

Review &
      Followup

      Rating: 4
Re: Data
      Archival and Retrieval  August 27, 2002
Reviewer:
      Jagadish Nanjappa  from Bangalore, India
Dear Tom,

Thank you very much for your response. It has made my mind to think in a new way
now.
But can you please make it a bit clear for me about how I can apply partitioning
for this?

I shall give you an example on the requirement:

Say,
Table T1 consists of Columns: A,B,C,D and E
Table T2 consists of Columns: F,G,H,I,J and K
Table T3 consists of Columns: L,M,N,O,P and Q

Now I will only need to archive the data of columns :B,D,G,J,N,P and Q after 2
years from the transaction-closed date, the data of the remaining columns will
be deleted.

Do you mean to say that I move the data of columns B,D,G,J,N,P and Q to a
history table and then partition it?

As mentioned earlier we are using Oracle 8i.

Can you please make it clear for me using the above example?

Thanks a lot for sparing your valuable to me for me.

Best Regards

          Followup:

I would just keep them.  Compress the partitions (using 9iR2) and forget they
are there.

I would NOT try to remove the data, it would incurr some serious processing that
just doesn't seem necessary.  By the time you get 2 years out -- I would hope
you would be on 9iR2 (or better) so you can just compress the data!

      Rating: 4
Operations
      across tablespaces/partitions?  October 31, 2002

Reviewer:  Jonas  from Berkeley, CA
This sounds like exactly what I need, but I want to verify this will work in the
manner I am expecting.

For most tables, partitioning by quarter will do the trick. The only issue I
have is dealing with tables that may generate a full table scan (for instance:
an account table). It makes sense that these tables should not be partitioned by
quarter (they may be partitioned by some other value(s)), but Im curious if
there are performance implications of:

1.) queries that join across partitions/tablespaces, or
2.) queries that generate result sets from rows that span multiple
partitions/tablespaces?

Im also concerned with how to deal with data that may have RI constraints across
partitions. For example:

create table my_parent (
  parent_id int,
  name varchar2(64),
  update_dt date);

  alter table my_parent
         add( constraint parent_pk
                         primary key (parent_id) );

create table my_child (
  child_id int,
  parent_id int,
  name varchar2(64),
  update_dt date);

    alter table my_child
           add( constraint child_pk
                           primary key (child_id));

alter table my_child
       add ( constraint parent_child_fk
                  foreign key (parent_id)
                             references my_parent );

-- first chain
insert
into
my_parent
(parent_id, name, update_dt)
values
(1, 'parent a', to_date('31-Dec-2000', 'dd-mon-yyyy'));

insert
into
my_child
(child_id, parent_id, name, update_dt)
values
(10, 1, 'child a', to_date('31-Dec-2000', 'dd-mon-yyyy'));

insert
into
my_child
(child_id, parent_id, name, update_dt)
values
(20, 1, 'child b', to_date('01-Jan-2001', 'dd-mon-yyyy'));

-- second chain
insert
into
 my_parent
(parent_id, name, update_dt)
values
(2, 'parent b', to_date('31-Dec-2001', 'dd-mon-yyyy'));

insert
into
my_child
(child_id, parent_id, name, update_dt)
values
(30, 2, 'child c', to_date('31-Dec-2001', 'dd-mon-yyyy'));

insert
into
my_child
(child_id, parent_id, name, update_dt)
values
(40, 2, 'child c', to_date('01-Jan-2002', 'dd-mon-yyyy'));

commit;

Assuming these tables are partitioned per year, we end up with a chaining
situation because of these cusp values.
Whats the best approach to managing these values? Write a proc that updates the
timestamp so that the data falls on one side of the partition or the other?
write a trigger to cascade the timestamp across all related tables for any DML
on a given table?

Thanks in advance.

          Followup:

...

1.) queries that join across partitions/tablespaces, or

depends -- are the tables partitioned by the join keys?  can we use partition
wise joins. It may be the same speed, it may be faster, it may be slower.  You
would need to understand partitioning (thats sort of straight forward) and then
say "if I was to join partitioned table A to partitioned table B -- how would I
do it?"  -- and that will really depend on the other predicates involved.

In general -- in a system designed to take advantage of it -- partitioning does
not make anything slower -- it may make many things faster, especially admin
stuff.

Used blindly -- it can be a mess.  If you have Jonathan Lewis's book "Practical
Oracle8i" -- he has a pretty good write up on this - you won't be sorry you got
that book either.

2.) queries that generate result sets from rows that span multiple
partitions/tablespaces?

no worries there - as long as you use it appropriately....  anything used
"wrong" will be bad.

I don't know what you mean by the "chaining situation".  I think you might mean
"criss crossed extents" ( sigh -- i know what I mean but I'm thinking no one
else will) but anyway -- I'm assuming that when you give me:

create table my_parent (
  parent_id int,
  name varchar2(64),
  update_dt date);

  alter table my_parent
         add( constraint parent_pk
                         primary key (parent_id) );

you really mean my_parent is partioned by update_dt -- but then the index on
parent_id would be a GLOBAL index (cause the only way to enforce uniqueness
there would be to have a global index on parent id OR to have parent_id be part
of the partition key -- which can be done actually).  So the RI would really
rely on a GLOBAL index - not a partitioned object -- which might defeat your
goal somewhat (until 9i maybe when "maintain global indexes" is possible on
partition operations -- but it does make them take longer)

      Rating: 5
Partitioning on
      status code column  December 30, 2002
Reviewer:
      Sam  from San Mateo, CA
Tom,

We have a similar requirement where we want to archive some rows the purchase
order lines table.  However, instead of archiving on transaction date we need to
archive the rows in the order_lines table which have 'CLOSED' in the
transaction_status column.

Should we do a hash partition on the transaction_status column or should we use
a separate table to store these rows to be archived?  The transaction_status
column value is 'OPEN' when a row is first created.  Will the row be moved to a
different partition automatically when the column is updated and how expensive
is this operation compared to us inserting these rows to a different table and
deleting them from the existing table?

          Followup:

You do not want to hash on that -- you only want to hash on almost unique data
(or at least data with lots of values).  You have an equal chance that
everything would be in the same partition with 2 values as you do that they
would be in different partitions...

If you partition by this status, then an update of the status key will be
processed much in the same manner as a DELETE + INSERT physically.  Expensive?
More then then not moving yes, but does that outweigh the benefits?

      Rating: 5
Re:
      Partitioning on status code column   December 30, 2002

Reviewer:  Sam  from San Mateo, CA
Tom,

I meant to say list partition instead of hash partition.  Does that make your
answer any different?

Thank you.

          Followup:

only to delete the first paragraph ;)

it'll be a delete+insert physically.  What you are doing is robbing peter (the
end user) of a little tiny bit of time in order to pay paul (the mass archive)

the archive becomes trivial

the end users have a small amount of overhead added, like sales tax, annoying
but not noticable unless you make a really large purchase (update tons of rows
at the same time)

whether it is "too much" -- that is something you'll need to benchmark.

      Rating: 5
Does this
      archiving/partitioning theory hold water?  October 29, 2003

Reviewer:  Neil  from Cold and wet UK
A trigger will detect the closing of an account and mark it as archivable.
Each night right AFTER the hot backup has run, a procedure will look at all the
accounts records thus marked, decide whether they fall outside a period of grace
and if so, using the primary key, will go and mark all the related records in a
set of major tables with a "sequence number of the day".
It will then update the account records marked as archived with this same
sequence number.
The date and the sequence number will be stored in a separate control table.
The major tables (including the account table) are partitioned on the archivable
column.
If the archivable column contains anything greater than 100000 (i.e. a sequence
number), they belong in the archive partition of the table. Otherwise they'll
belong in the live partition.
Each partition will live in a different tablespace.
So, enabling row movement will have Oracle move all the relevant rows from the
live partitions into the archive partitions after the given period of grace.
Since one of the main reasons for doing this is to cut down the amount of time
taken to do the hot backups (by precluding the archive partitions), the archive
partitions need to be backed up less frequently, but still need protection.
After the rows have all been moved, an export file can be generated from the
archive partitions using

select *
  from table (arch_partition)
 where archive_status = todays_sequence_number

If anything happens during the archiving process, the live partition tablespaces
can be recovered to point in time (from the recently created hot backup). Any
records already in the archive partition will be identifiable and can therefore
be removed.
If anything happens to an archive tablespace, it can be recovered from its last
backed up version, and the various export files can be applied to it (at the
DBA's
leisure) until it is back up to date.
In the event of a disaster, both methods would need to be applied.
The archive partitions will be read only until the partitioning program runs
when they'll be switched to writable, and switched back again when the process
finishes.
I would like to back up the archive partitions as infrequently as possible as
they'll hold around 70% of the data (We are obliged to keep this archived data
on line for 6 years).
Indexing need only be local to the live partitions except for those using the
primary keys and defining unique constraints.
Does this seem like a reasonable scenario?
Could I set the archive partitions to use transportable tablespaces to avoid SCN
problems in the event of recovery?
Version is 9.2.0.3.0
Thanks in advance for your time.

          Followup:

i was following it until:

Could I set the archive partitions to use transportable tablespaces to avoid SCN

problems in the event of recovery?

what SCN problems?

      Rating: 5
Transportable
      tablespaces  October 29, 2003
Reviewer:
      Neil  from UK (still cold, but no longer wet)
Thanks for your prompt reply!

"Could I set the archive partitions to use transportable tablespaces to avoid
SCN problems in the event of recovery?"

Well, when I mooted the theory to the DBAs, they were making noises about SCN
problems. I guess what I was really asking in addition to "does this theory hold
water" is "can you envisage any SCN problems?" and "would there be any advantage
to using TTs?"
Sorry if I wasn't clear.
Rgds,

          Followup:

transportable tablespaces are used for cross database moves as well -- the SCN
cannot come into play, if you make the tablespace read/write, we'll update the
file headers with the appropriate information -- but a transported tablespace
can have whacky information in the first place by design.

i know of no "issues"

      Rating: 5
TTS needs
      staging area for partitions  November 06, 2003

Reviewer:  dharma  from CA USA
Everything looks fine except that the partitions have to be exchanged to take it
into a export TTS. Also if they have local indexes for that partition they have
to be rebuilt int the staging table before I take a backup.If I bring them back
in I have to load into the staging table(automatically done bu IMP) and then
exchange partitions. and the local indexes are taken along.

Is there any easy way to handle the indexes? Are the steps above right which is
how I am doing now.

Thanks
-dharma

          Followup:

yes, they have to be exchanged -- but that was the whole point, you wanted to
ARCHIVE the data (get it out of there).  so that isn't a "bad thing"

the indexes to NOT have to be rebuilt.  If you take the idea of putting your
table  partition into the same tablespace as the local index partitions, when
you exchange the empty indexed table with the full indexed partition -- you'd
end up with a full indexed table in a self contained tablespace.  You would then
transport this tablespace with indexes and all.  To restore, plug it back in and
you'll have a fully loaded table plus indexes to exchange back with.

If you have unreasonable fears of putting index and tables in the same
tablespace for whatever reason, you'll just need to transport TWO tablespace --
the table and its index tablespace.

      Rating: 3
Primary key
      invalidation  November 08, 2003
Reviewer:
      Anurag  from West Point, GA
On our 8.1.7.4 instance on Solaris 8, the table's primary key goes invalid when
a partition is dropped. The table has to be taken offline to rebuild, which
requires downtime.

Is there any way to avoid invalidation of the primary key during a partition
swap?

          Followup:

upgrade to 9i - which allows for maintaining global indexes during partition
operations

or

use a partitioning scheme that employs locally partitioned indexes

November 09, 2003 julien -- Thanks for the question regarding
      "import of a 8.1.7 export file", version 8.1.6

      You Asked
I have an export file from a 8.1.7 database. I try to import it into a 8.1.6
database and I have an error : IMP-00069: Could not convert to environment
national character set's handle
Any suggestion ?

Regards,

Julien

      and we said...
DMP files are not FORWARD compatible.  8.1.6 IMP cannot read 8.1.7 (or up) DMP
files.

You should use the 8.1.6 EXP against the 8.1.7 database to create a DMP file
that the 8.1.6 IMP can read.  The file formats change over time.

8.1.7 IMP can read and process and 8.1.6 DMP file -- they are backwards (but not
FORWARDS) compatible.
Review & Followup

      Rating: 4
imp-00069
        October 28, 2001
Reviewer:  Dov Hit.
       from Israel. But I work with a lot of Foreign comoanies like :
      Amdocs,MindCti etc.
I like your url.
It's very strict and sharp.

      Rating:
      2
?  February 28, 2002
Reviewer:  David
      Quinlan  from Isle of Man
I have the same problem

Where do I go to download the 8.1.6 exp utility?
I have searched Oracle.com and others for it...

          Followup:

you have an 816 database.  It has the 816 exp tool.  You cannot download just
"816 exp", it's part of the toolset.

Using the 816 database -- you will export from 817 over the network.

      Rating: 5
Very Very
      helpful   November 05, 2002
Reviewer:  Mayur
      Patel  from Australia
HI your response was very helpfula nd your archives are very easy to search.

      Rating: 2
Data Import
      from 9i Personal Database Export to 8.1.5  October 15, 2003

Reviewer:  Nazmul Hoque  from Bangladesh
I want import dmp in the server database 8.1.5 of the DMP File Of 9i Personal
Database (O/S 98) can i do this, if Yes than how......

Please help me

Nazmul Hoque
Dhaka, Bangladesh

          Followup:

the answer above sort of says "no can do"

You would have to use the 815 export tool against 9i

that would create a dmp file the 815 imp tool can use -- it would not export any
9i specific constructs that 8i cannot deal with.

      Rating: 2
PERSONAL 9i TO
      8.1.5  November 02, 2003
Reviewer:  Nazmul
      Hoque  from Bangladesh
Thanks a lot for your reply.
Just please help how do I connect the Client 8.1.5 pc of OSW/98 to the 9i
Personal Database Pc Of OSw/98 under the same network (Windows 2000 Server).
I alrady install 8.1.5 in another pc and configar the tnsnames.ora through Net8
Configaration Assistance, but it is fail to connect the 9i Personal Database on
ther other PC like server. Please help me how do i connect the 9i personal
database PC.
Thanks in advance for your kind Co-Operation.
Nazmul, Dhaka

          Followup:

"failed to connect"

"my car won't start"

equally vague issues.

any errors?  did you configure and start the tns listener on the 9i database?
can you tnsping the 9i instance?  have you ever set up the networking component
before?  have you been through the docs?  do you have access to someone who has
setup the networking component before?

      Rating: 3
Personal 9i
      Database to 8.1.5 Client  November 02, 2003

Reviewer:  Nazmul Hoque  from Bangladesh
Thanks for your query...

I can connect 9i personal Database by user name and passward with out listner on
same pc. and also can connect when from developer (forms and report 6i) by the
user name/password@tnsnames that is as under on the same pc :

# D:\ORAWIN95\NET80\ADMIN\TNSNAMES.ORA Configuration
File:d:\orawin95\net80\admin\tnsnames.ora
# Generated by Oracle Net8 Assistant

HRMDATA =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = BEQ)(PROGRAM = oracle80)(ARGV0 =
oracle80HRMDATA)(ARGS = '(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))'))
    (CONNECT_DATA = (SID = HRMDATA))
  )

EXAMPLE3.WORLD =
  (DESCRIPTION =
    (ADDRESS = (COMMUNITY = nmp.world)(PROTOCOL = NMP)(Server =
FinanceServer1)(Pipe = ORAPIPE))
    (CONNECT_DATA = (SID = ORCL))
  )

EXAMPLE1.WORLD =
  (DESCRIPTION =
    (ADDRESS = (COMMUNITY = tcp.world)(PROTOCOL = TCP)(Host = Production1)(Port
= 1521))
    (CONNECT_DATA = (SID = SID1))
  )

BEQ-LOCAL.WORLD =
  (DESCRIPTION =
    (ADDRESS = (COMMUNITY = beq.world)(PROTOCOL = BEQ)(PROGRAM = oracle73)(ARGV0
= oracle73ORCL)(ARGS = '(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))'))
    (CONNECT_DATA = (SID = ORCL))
  )

EXAMPLE2.WORLD =
  (DESCRIPTION =
    (ADDRESS = (COMMUNITY = spx.world)(PROTOCOL = SPX)(Service = Server_lsnr))
    (CONNECT_DATA = (SID = ORCL))
  )

TCP-LOOPBACK.WORLD =
  (DESCRIPTION =
    (ADDRESS = (COMMUNITY = tcp.world)(PROTOCOL = TCP)(Host = 127.0.0.1)(Port =
1521))
    (CONNECT_DATA = (SID = ORCL))
  )

But i want to connect 9i personal database from the other pc where i install
client 8.1.5 to import data from 9i Personal database by useing export tools of
8.1.5. I configur the tnsnames.ora on the pc where i install 8.1.5 client, but
when i try to connect it says "no listner",
Please help me how i export data from 9i personal database useing  8.1.5 client
from the other pc.
For yor information i have install oracle 8.1.5 server on the NT and Windows
2000 server. and also install oracle 8.1.7 on NT server. and install 8.1.5
client in ine  pc where i have install oracle 8.1.5 sever and also install 8.1.7
clint on two pc where i install 8.1.7 server. i am runing DBA job from the pc
where where i install 8.1.7 client I/O useing server.

Hope you understand my problem and know about me also. Please give me a gide
line how do i export data by useing 8.1.5 export tools from 9i Personal
Database.

Thanks
Nazmul Hoque
Dhaka, Bangladesh

          Followup:

ummm, so which tns connect string are you using

we can rule out the BEQ ones -- they work only locally.

likewise we can rule out the 127.0.0.1 ones since that is the local loopback tcp
connection and would prevent machine to machine talking...

so, that basically leaves "exampleN"???

      Rating: 3
Personal 9i
      Database to 8.1.5 Client  November 02, 2003

Reviewer:  Nazmul Hoque  from Bangladesh
Thanks for your query...

I can connect 9i personal Database by user name and passward with out listner on
same pc. and also can connect when from developer (forms and report 6i) by the
user name/password@tnsnames that is as under on the same pc :

# D:\ORAWIN95\NET80\ADMIN\TNSNAMES.ORA Configuration
File:d:\orawin95\net80\admin\tnsnames.ora
# Generated by Oracle Net8 Assistant

HRMDATA =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = BEQ)(PROGRAM = oracle80)(ARGV0 =
oracle80HRMDATA)(ARGS = '(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))'))
    (CONNECT_DATA = (SID = HRMDATA))
  )

EXAMPLE3.WORLD =
  (DESCRIPTION =
    (ADDRESS = (COMMUNITY = nmp.world)(PROTOCOL = NMP)(Server =
FinanceServer1)(Pipe = ORAPIPE))
    (CONNECT_DATA = (SID = ORCL))
  )

EXAMPLE1.WORLD =
  (DESCRIPTION =
    (ADDRESS = (COMMUNITY = tcp.world)(PROTOCOL = TCP)(Host = Production1)(Port
= 1521))
    (CONNECT_DATA = (SID = SID1))
  )

BEQ-LOCAL.WORLD =
  (DESCRIPTION =
    (ADDRESS = (COMMUNITY = beq.world)(PROTOCOL = BEQ)(PROGRAM = oracle73)(ARGV0
= oracle73ORCL)(ARGS = '(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))'))
    (CONNECT_DATA = (SID = ORCL))
  )

EXAMPLE2.WORLD =
  (DESCRIPTION =
    (ADDRESS = (COMMUNITY = spx.world)(PROTOCOL = SPX)(Service = Server_lsnr))
    (CONNECT_DATA = (SID = ORCL))
  )

TCP-LOOPBACK.WORLD =
  (DESCRIPTION =
    (ADDRESS = (COMMUNITY = tcp.world)(PROTOCOL = TCP)(Host = 127.0.0.1)(Port =
1521))
    (CONNECT_DATA = (SID = ORCL))
  )

But i want to connect 9i personal database from the other pc where i install
client 8.1.5 to import data from 9i Personal database by useing export tools of
8.1.5. I configur the tnsnames.ora on the pc where i install 8.1.5 client, but
when i try to connect it says "no listner",
Please help me how i export data from 9i personal database useing  8.1.5 client
from the other pc.
For yor information i have install oracle 8.1.5 server on the NT and Windows
2000 server. and also install oracle 8.1.7 on NT server. and install 8.1.5
client in ine  pc where i have install oracle 8.1.5 sever and also install 8.1.7
clint on two pc where i install 8.1.7 server. i am runing DBA job from the pc
where where i install 8.1.7 client I/O useing server.

Hope you understand my problem and know about me also. Please give me a gide
line how do i export data by useing 8.1.5 export tools from 9i Personal
Database.

Thanks
Nazmul Hoque
Dhaka, Bangladesh

      Rating: 3
PERSONAL 9I TO
      8.15   November 03, 2003
Reviewer:  NAZMUL
      HOQUE  from Bangladesh
Thanks a lot for your reply..

Now i am useing the below TNSNAMES to connect 9i database from other pc of
ORACLE 8.1.5 client :

# C:\ORAWIN95\NET80\ADMIN\TNSNAMES.ORA Configuration
File:C:\orawin95\net80\admin\tnsnames.ora
# Generated by Oracle Net8 Assistant

HRMDATA =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.16)(PORT = 1521))
    (CONNECT_DATA = (SID = hrmdata))
  )

H/EVER WHEN I TRY TO CONNECT BY ABOVE GETTING
ERROR :-
ORA-12541 : NO LISTENER
DO YOU WANT ME USE THE BELOW :

HRMDATA =
  (DESCRIPTION =
    (ADDRESS = (COMMUNITY = tcp.world)(PROTOCOL = TCP)(Host = 192.168.1.16)(Port
= 1521))
    (CONNECT_DATA = (SID = HRMDATA))
  )

PLEASE ADVISE ME IF ABOVE IS OKAY FOR ME OR I HAVE ANYTINH MORE. WAITING FOR
YOUR REPLY.

THANKS
NAZMUL HOQUE
DHAKA, BANGLADESH

          Followup:

do you have a listener running on 192.168.1.15??????

your original configuration seemed to suggest strongly that "no, i did not, it
was running on the loopback link 127.0.0.1"

      Rating: 4
Conecting
      Personal Database for other pc  November 04, 2003

Reviewer:  Nazmul Hoque  from Bangladesh
Thanks for Your reply and Query.

Listener is running On that PC Where I Installed Oracle 9i personal Database and
IP of that Pc is 192.168.1.16 and Listener.ora as follows :

# LISTENER.ORA Network Configuration File:
g:\oracle\ora90\network\admin\listener.ora
# Generated by Oracle configuration tools.

LISTENER =
  (DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS_LIST =
        (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC0))
      )
      (ADDRESS_LIST =
        (ADDRESS = (PROTOCOL = TCP)(HOST = nuton)(PORT = 1521))
      )
    )
  )

SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (SID_NAME = PLSExtProc)
      (ORACLE_HOME = g:\oracle\ora90)
      (PROGRAM = extproc)
    )
    (SID_DESC =
      (GLOBAL_DBNAME = hrmdata)
      (ORACLE_HOME = g:\oracle\ora90)
      (SID_NAME = hrmdata)
    )
  )

And The PC where i installed oracle client 8.1.5, currently there are no
Listner. I try to configare the listner on this pc by wizard, But it was hang at
one stage. Is it required the Listner in this pc also ?  If yes than provide me
what shall write in the Listener. What else is required to connect The Personal
Oracle Datebase 9i.

Regards
Nazmul Hoque
Dhaka, Bangladesh

          Followup:

did you just change that?  because if you did not JUST change that -- your
original tnsnames.ora file would not have worked.

tell you what.  shutdown the listener.  modify the listener.ora to have the IP
address (not hostname, no clue what that resolved to) and restart it.

you need no listener on 8i, just on the 9i database in order to go from 8i to 9i

      Rating: 4
Oracle 8.1.5
      client connect to Oracle 9i Personal Database  November 05,
      2003
Reviewer:  Nazmul Hoque  from Bangladesh

Thanks a Lot for Your valuable reply.

Finally I have understand that To connect Oracle 9i Personal Database from other
PC where Installed Oracle 8.1.5 Client I need the follows :

1. Need to modify the line of LISTENER.ORA of 9i Personal Database Pc From :
(ADDRESS = (PROTOCOL = TCP)(HOST = nuton)(PORT = 1521))
To :
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.16)(PORT = 1521))

and than restart the pc.

2. I have to use TRNSNAMES.ORA on the pc where installed the  Oracle 8.1.5
Client like TCP-LOOPBACK.WORD as follows :

HRMDATA =
  (DESCRIPTION =
    (ADDRESS = (COMMUNITY = tcp.world)(PROTOCOL = TCP)(Host = 192.168.1.16)(Port
= 1521))
    (CONNECT_DATA = (SID = HRMDATA))
  )

Hope i am very close to solution of my problem, Please know me, if any mistake
of my understanding. I try the above and let you know the status.

Best Regards
Nazmul Hoque

          Followup:

1) well, restarting the pc would be the microsoft approach.  you could

a) shutdown listener
b) modify config
c) startup listener.

as stated

2) looks right to me at a glance

      Rating: 5
Connection To
      Oralce 9i Personal Database From Oracle 8.1.5 Client
      PC  November 06, 2003
Reviewer:  Nazmul
      Hoque  from Bangladesh
Thanks for your confirmation.

1. I have confusion about start/shutdown listener, Normally we always get in the
personal version Start and Stop Database option in menu. We stat the data base
before start the job. It seams you are talking about this. Instead of shutdown
the pc

2. And the "modify config" means modify the configuration of the listener.ora
not the config.sys of windows system file.

Hope I am going in the right direction. Please let me the know if any wrong
there.

Beast Regards
Nazmul Hqoue

          Followup:

c:\> lsnrctl stop
c:\> lsnrctl start

do you have a dba that can help you out ?  it would only take a second of their
time.

I'm not telling you to touch ANYTHING at the OS level -- nothing to do with
windoze, only oracle config files.

      Rating: 5
Connecting 9i
      Personal Database From the Other Pc  November 09, 2003

Reviewer:  Nazmul Hoque  from Bangladesh
Hi Dear,

After drive the long way I am still getting the same message "no listener"

Currently I am useing on the client 8.1.5 client pc TNSNAMES.ORA as bewlow :

# C:\ORAWIN95\NET80\ADMIN\TNSNAMES.ORA Configuration
File:C:\orawin95\net80\admin\tnsnames.ora
# Generated by Oracle Net8 Assistant

HRMDATA =
  (DESCRIPTION =
    (ADDRESS = (COMMUNITY = tcp.world)(PROTOCOL = TCP)(Host = 192.168.1.16)(Port
= 1521))
    (CONNECT_DATA = (SID = HRMDATA))
  )

And useing the LISTENER.ORA On the Pc 9i Database PC as below :

# LISTENER.ORA Network Configuration File:
g:\oracle\ora90\network\admin\listener.ora
# Generated by Oracle configuration tools.

LISTENER =
  (DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS_LIST =
        (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC0))
      )
      (ADDRESS_LIST =
        (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.16)(PORT = 1521))
      )
    )
  )

SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (SID_NAME = PLSExtProc)
      (ORACLE_HOME = g:\oracle\ora90)
      (PROGRAM = extproc)
    )
    (SID_DESC =
      (GLOBAL_DBNAME = hrmdata)
      (ORACLE_HOME = g:\oracle\ora90)
      (SID_NAME = hrmdata)
    )
  )

When i try to connect the 9i personal database from 8.1.5 clinet pc by
HRM/HRM@HRMDATA it is shownung error :

12541 : NO LISTENER

What shall i do now .....

Please advise

Regards
Nazmul Hoque

          Followup:

well, I see your IP addresses are the "i'm behind a firewall using NAT" type of
addresses.

can the PC's even see eachother?  are you sure they are seeing the "same
eachother"

and -- the listener is "running" is it not.

      Rating: 5
Connecting
      Personal Database From the Other PC (oracle 8.1.5
      client)  November 10, 2003
Reviewer:  Nazmul
      Hoque  from Bangladesh
Hi Dear,
Thanks a lot for suggestion, No I ma bale to connect the Personal 9i Database
from client Pc (Oracle 8.1.5). I Already import data form 9i by using the oracle
8.1.5 export tools. and import the same data on the server where I have install
8.1.5 server. Now user are getting data from server.
My problem was that I did not start the listener by lsnrctl start. As thought it
is started automatic like server. Any way after start the listener my was
solved. Thanks once again for your kind Co-Operation
Best Regards
Nazmul Hoque

November 10, 2003 Perez -- Thanks for the question regarding "The
      use of extended characters in a varchar2 field (accent)", version 7.3.3

      You Asked
When I introduce in a form´s varchar2 field a spanish word with
an accent vocal and commit it, the result is that the accent is
missing. e.g. José - Jose; camión - camion.
The same result is achieved with SQL PLUS, ORACLE NAVIGATOR,
DEVELOPER 2000.
Why is this happening? How can I solve that problem?
Thanks a lot!

..............................................................

OS Client: Windows NT, 95/98
OS Server :Windows NT 4.0
SQL> /

PARAMETER                      VALUE

------------------------------ ------------------------------

NLS_LANGUAGE                   AMERICAN

NLS_TERRITORY                  AMERICA

NLS_CURRENCY                   $

NLS_ISO_CURRENCY               AMERICA

NLS_NUMERIC_CHARACTERS         .,

NLS_DATE_FORMAT                DD-MON-YY

NLS_DATE_LANGUAGE              AMERICAN

NLS_CHARACTERSET               US7ASCII

NLS_SORT                       BINARY

NLS_CALENDAR                   GREGORIAN

NLS_RDBMS_VERSION              7.3.3.0.0

11 filas seleccionadas.

SQL> spool off

      and we said...
Strange -- a database on NT would typically have the
characterset WE8ISO8859P1 but the problem is now clear.

The clients have in their registry a setting of NLS_LANG.  Its
set to WE8ISO8859P1.  This is what the client tools always put
in there -- its the correct setting for Windows Clients.  Its an
8 bit character set.

Your database on the other hand is US7ASCII -- a 7bit
characterset.  The HIGH bit is getting stripped off on the way
in -- in support of the databases characterset.

Your solutions:

o use an NLS_LANG of us7ascii on the client.  This will affect
the behaviour of certain tools like forms and reports as well.
No translation will take place and 8bit data can in fact be
stored

o recreate your database with a characterset that supports 8bit
data fully.
Review & Followup

      Rating:
      5
  January 04, 2002
Reviewer:  Toby

Exactly what I was looking for.

      Rating:
      4
Excellent  September 18, 2003
Reviewer:
      A reader
So now I can setup the application to properly accept words with accents.  What
about composing a query to retrieve 'similar' words, with and without the
accent.
If one user enters José and the other Jose, is there a way to query for both
case?

          Followup:

Oracle text should be able to do that with stemming or fuzzy searching

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-TEX
you could use convert() to conver to us7ascii, that may work as well (but would
obviate an index until you build a function based one)

      Rating:
      3
  November 09, 2003
Reviewer:
      A reader
I have two 9.0.1.4 databases with identical nls_* parameters. Both receive input
from mod_plsql web forms, via Oracle 9iAS 9.0.3. One database accepts accented
characters, the other does not. Any idea what might need to be adjusted?

NAME                                 TYPE        VALUE
------------------------------------ -----------
nls_calendar                         string
nls_comp                             string
nls_currency                         string
nls_date_format                      string
nls_date_language                    string
nls_dual_currency                    string
nls_iso_currency                     string
nls_language                         string      AMERICAN
nls_length_semantics                 string      BYTE
nls_nchar_conv_excp                  string      FALSE
nls_numeric_characters               string
nls_sort                             string
nls_territory                        string      AMERICA
nls_time_format                      string
nls_time_tz_format                   string
nls_timestamp_format                 string
nls_timestamp_tz_format              string

          Followup:

the app servers have different NLS_LANG settings.

one has a character set the same as the database (or at least at 8bits) and the
other does not.

November 10, 2003 -- Thanks for the question regarding "How to
      merge columns data into single CLOB column", version 9.2.0

      You Asked
There are 10 transactional tables having few columns as VARCHAR2 and few as
CLOB. There are only 35000 records in total. For each record I have to generate
a table named SEARCH_TABLE by concatenating the VARCHAR2 and CLOB fields data
into one 'TEXT' CLOB column. The structure of SEARCH_TABLE is...

CREATE TABLE SEARCH_TABLE
       (ID      VARCHAR2(8)   PRIMARY KEY,
        TEXT    CLOB);

The values are concatenated as a XML data. So if I have say 4 columns (C1, C2,
C3, C4) in Table T1 then the TEXT field of SEARCH_TABLE is polulated as...

<SEARCH_DATA>
<C1>...data...</C1><C2>...data...</C2><C3>...data...</C3><C4>...data...</C4>
</SEARCH_DATA>

To do this I have written the PL/SQL block that opens a cursor on the each table
and does the concatenation of the fields including the XML tags (Column names).

declare
    v_T1_ID                            T1.T1_ID%TYPE;
    v_TITLE                            T1.TITLE%TYPE;            --
VARCHAR2(100)
    v_DESCRIPTION                      T1.DESCRIPTION%TYPE;      -- CLOB
    searchdata                         CLOB;

    CURSOR curs is
        SELECT
            T1_ID,
            TITLE,
            DESCRIPTION
        FROM
            T1
        ORDER BY
            T1_ID;
begin
    open curs;
    loop
         fetch curs into
            v_T1_ID,
            v_TITLE,
            v_DESCRIPTION;

         exit when curs%NOTFOUND;

dbms_output.put_line(v_T1_ID);

        searchdata :=
         '<SEARCH_DATA>'
         || '<TITLE>'         || NVL(v_TITLE, '')         || '</TITLE>'
         || '<DESCRIPTION>'   || NVL(v_DESCRIPTION, '')   || '</DESCRIPTION>'
         ||
         '</SEARCH_DATA>';

         insert into SEARCH_TABLE VALUES(v_T1_ID, searchdata);
    end loop;
    close curs;
    commit;
end;
/

Problem is that it takes lot of time to do the population. After some
investigation I found that more the fields I have to concatenate the more time
it takes (and also many XML tags I concatenate, two for each column). For 1500
records and 6 columns it took me 11 minutes. Not good.

Is String Concatenation in Oracle is costly (something similar to Java String Vs
StringBuffer). What is the right way of doing this.

Thanks

Oct 6, 2003
-----------
Hi Tom,

I have a running database that have many transactional tables. Now I need to
implement a Search facility in my Java (JSP)

based application that is similar to google. The fields to search for are only
the VARCHAR2 and CLOB fields of these tables.

There are two types of search I need to provide.

[1]. First type is that each record in tables (only VARCHAR2 and CLOB fields)
will represent a document and the search will

be done in that document for the search words provided by the user. The result
of-course should be rank based.

[2]. Second type of search is the advanced search where user will be given the
options to select which tables to search in

and in which fields in each table to search in.

I investigated/thought of many ideas and started reading "Oracle Text" (aka
InterMedia). Then I tried the idea I asked the

question about. I tried merging all the fields I need of each row of each table
into XML format into the SEARCH_TABLE CLOB

field so that it will represent a document for that row. I planned to use the
"CONTEXT" TEXT index for indexing the

SEARCH_TABLE.TEXT field.

There are some drawbacks of this idea.

1. Mojority of the database will be duplicated. In fact more than double due to
those many XML Tags. Anyway I can live with

that.
2. I planed to sync the SEARCH_TABLE whenever the main tables get
updated/inserted using the trigger but found that :new

doesn't work on CLOB fields. I need to find the solution for this or find
alternative solution.
3. Updating the index is also an issue and I planned to use the dbms_job to
update the index at regular interval.

I found the existing way of putting everything into one cell (XML document Way)
for searching the words. Advance search

implementation will be difficult. Isn't it? Also I need the gist something what
google shows, summary kind of text where the

majority of words found. Further the words should be highligted in that summary
(gist).

Currently I am tring another way. Now each record of each table (only relevant
VARCHA2 and CLOB fields) is stored as set of

records in SEARCH_TABLE. The new form of SEARCH_TABLE looks like...

create table search_table
( table_name     varchar2(30)    not null
, record_id      number          not null
, column_name    varchar2(30)    not null
, column_value   clob
);

Storing the data into this table from the main tables is staright forward and
for me it took less than a minute (I have

around 30000 records in total). But now I don't know how to search this table
treating a set of rows's column_values as one

document. I don't know how to search.

Perhaps I have asked too many question in this single question but your detailed
answer will help me and others like me who

wants to provide search facility using "Oracle Text". I am sure it will be a
great document for such developers. Please help.

Thanks.

      and we said...
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:440419921146
you want to index a function -- that is the way it works on asktom actually.

I have the quesiton in one column, the answer in another.  there is a separate
table that contains all of the followups, a third table contains my followup to
a followup.

I index a function on the main table with the q&a in it.  when that row is
modified -- my function gets called with the primary key.  I assemble the page
into a clob and let Oracle text index it. (i have triggers on the two detail
tables such that when the details are modified, the followups, they in turn
update a dummy column in the "parent" so the entire "page" is reindexed)

very nifty.  that is what you want -- you don't need to persist the "xml" just
index it.
Review & Followup

      Rating: 5
It works
      well  November 10, 2003
Reviewer:  Jon
       from Sydney
We did the same thing, and found it worked great.  However, at a later stage we
moved to storing the assembled document (with XML tags) in a separate table, and
building the index using a master-detail datastore.  This reduced the index
rebuilding time, because we could populate the XML table using a few batch SQL
statements, rather than row-by-row.  The subsequent master-detail index build
was much faster than calling a PL/SQL function for each row. (esp in 8.1.7)

This was done to minimize the potential downtime if the index was broken or
needed to be rebuilt.  The XML table can be built behind the scenes, and then we
do the index rebuild in a matter of minutes.

          Followup:

guess it depends on how fast your plsql is :)

but you can rebuild "online" with text as well -- removing downtime.

ask_tom@ASKUS> exec ctx_ddl.optimize_index( 'SEARCH_IDX', 'FULL' )

is something I do from time to time on asktom, it is fully online and has the
nice attribute that it doesn't even invoke the plsql function, it works on the
data directly in the index.

November 10, 2003 Sudhanshu -- Thanks for the question regarding
      "Cluster on One Table", version 8.1.7

      You Asked
I have a table having around 10Million rows. Data can be grouped against a key,
each key generally having around 10-200 rows. Data against this key gets
inserted over a large period of time. Distinct keys in this table at present are
.7 million.

All of our queries search on the basis of this key and as the data is scattered
across files it takes time for all of the queries to execute. Ours is an OLTP
application thus insertion in this table are online.

Should i create a single table cluster on this table with my key as cluster
column?

Thanks.

      and we said...
This might be a condidate for a cluster, yes

You will of course want to benchmark -- you have to understand the cluster will
take marginally longer to insert into since the data now has a "place" to go --
and maintaining this structure will take longer then maintaining a HEAP table
(normal table you have now).

If you have my book -- I go into the in's and out's of clusters.  If you can
take the insert hit, it could be very good for your retrieval.  remember -- you
only insert a row once, you might query it up 1,000 times -- therefore, this
shifting of overhead from one function to another (from query to insert) could
be well worth it in the long term.
Review &
      Followup

      Rating: 3
Is IOT
      appropriate here?  July 08, 2002
Reviewer:
      andrew  from ca, usa
Tom
Please could you comment on the appropriateness of an IOT for this problem. If
appropriate - under what circumstances would the cluster solution be better?

          Followup:

probably not.

A cluster just ensures that data that shares a common key is stored together.

An IOT goes a step further and imposes "order" on the data -- 1, 2, 3, 4, 5, ...
type of order.

It is more expensive to insert into an IOT then a cluster -- as we might have to
physically MOVE data to make room for new data (index splits and such).

In a cluster, we just make sure that like data is stored together.  It doesn't
matter that physically we store 1, 5, 2, 3, 4 -- just that all of the 1's are
together, all of the 5's and so on.

Since the goal here was to cluster like values together (to reduce the physical
IO's and make better, more efficient use of the buffer cache)  a cluster would
probably be more appropriate.

      Rating: 4
confused by
      'single table' hash cluster in Chapter 6  July 08, 2002

Reviewer:  Doug  from Danbury, CT USA
Tom - in this question here, you are suggesting a single table might be worth
clustering to co-locate the data.  In your book (pg. 238) you make reference to
a special case of the single table hash cluster. You say this is designed for
those occassions when you want to access a table by primary key and don't wish
to cluster other tables with it.  "If you need fast access to an employee record
by EMPNO - a single table hash cluster might be called for".

In the example in the book it looks like just a regular old table with a
standard primary key.  In this example in the thread it looks like there are
many records with the same value.

So..
If I have a table that is not joined to other tables and I need fast access to
it's primary key, do I want a single table hash cluster?  If so, isn't it
somewhat more common to just make use of the index on the primary key? If I can
predict the size of this table is it worth single table hash clustering? In this
question thread, it seems we can store many values together but in the book
example, I think you imply that empno is guaranteed to be completely unique
between 1 and 50,000.

The example here makes sense but the book example is confusing me.  Tables are
accessed by primary key quite frequently - I would assume I would see more
single table hash clusters if they are worth doing in single table mode.

          Followup:

I think that either

o I'm confused by your question
o your confusing topics

In this question -- the user wants to physically colocate related data by some
value.  "... each key generally having around 10-200 rows."  They would like all
data with the same key value physically stored near all of the data with the
same key.

The answer to that is CLUSTER (well, more explicitly -- B*Tree CLUSTER).  In
their case, it'll be a single table b*tree cluster but that is not any different
then a multi-table one in our eyes.

Now, you are talking about a table with a primary key (unique) AND that you want
to access purely by primary key.  That is when a HASH cluster comes into play.
Normally, you would create a table with a primary key index and then when
accessing the table -- we would:

o index unqiue scan the index (say that is 2-3 IO's to find the key)
o table access by rowid (that is 1 IO to get the data)

Using a HASH cluster however we can take that above work and instead:

o hash the primary key (CPU operation) into a physical location
o do the single IO needed to read that block

So, we can cut out the index access all together.  "the data is the index"

Even if that table is JOINED to other tables -- this still makes sense, as long
as the access path is generally via the primary key (and don't forget -- we can
create other indexes on this object as well!)

So, in this thread, I was talking about b*tree clusters (see the book on page
224).  The example I used there was EMP+DEPT and a cluster key on DEPTNO, not
EMPNO. The goal was to get the DEPT info physically colocated with the EMP info
for that DEPTNO.

Later, I go onto HASH clusters.

Why are hash clusters not the norm than?

a) you don't know the number of records ultimately
b) there is a performance penalty on insertions (see above -- i discuss that in
the answer)
c) 99% of people aren't aware of them, most people never bother to learn about
them

I've used them to great success with some very large tables.

      Rating: 5
collision
      ....  November 01, 2003
Reviewer:  Reader

Tom, in your new book on page 385, you mention collision in hash table. Could
you please elaborate what it is and how exceeding value for hashkeys parameter
affect the hash table? Thanks for your time. Your book is great.

          Followup:

a collision occurs when 2 "hash key values" -- the values that are hashed --
result in the same "location", the same place, the same hash value.

You are taking a possibly infinite set of values (the keys you hash) and are
hashing them into a finite set of values.  You will have unavoidable
"collisions" that result in a little extra lookup time (we hash to where we
expect to find the data and then have to look for the data -- it might not be
"right there" since something else may well have hashed to that value already as
well).

If you set hashkeys too low and many values hash to the same location, we'll
have to link together lots of blocks in order to find your data.  Say you set
hashkeys to 5 (for illustration purposes).  Oracle will conceptually set up a 5
block table:

+----+----+----+----+----+
|    |    |    |    |    |
|    |    |    |    |    |
+----+----+----+----+----+

Now, you create a table in this hash cluster based on a key (say "id").  It
turns out that 50 rows will fit onto a single block with your data.  You start
inserting information and get 'lucky', the data spreads out evenly so that you
have 250 rows spread across 5 blocks.

Now, any retrieval such as "select * from T where id = 55" will result in 1
logical IO since Oracle will hash 55 to a database block address and read that
block, finding all of the data for id=55.

Now, you insert row 251 -- with id=60.  It cannot fit onto the block it hashed
to since that block is totally full.  Assume further that 55 and 60 "hash" to
the same block address.  Oracle will link another block to it:

hash(55) = block 3 = hash(60)
            |
            V
+----+----+----+----+----+
|    |    |50  |    |    |
|    |    | 55 |    |    |
+----+----+----+----+----+
            |
            V
          +----+
          | 60 |
          |    |
          +----+

So, now

"select * from t where id = 60"

will take 2 lio's (as well where id = 55, where id = 50) since we had so many
"collisions" that we filled up that block and had to link other blocks to it.

      Rating: 5
Great
      explanation - collision  November 02, 2003

Reviewer:  Reader
I understand that where id=60 will take two LIOs. Why for id=50 and id=55 also
take 2 LIOs? Thanks again.

          Followup:

because the data for 50 and 55 could also be on that linked block.  if you were
to insert yet another row with id=50, it would has to that block, that block is
full -- hence it goes onto the linked blocks.  once linked -- any of the data
that could be on the head block might be on the linked blocks as well -- we have
to check for it.

      Rating: 5
Comparing
      Lookup Methods  November 09, 2003
Reviewer:
      A reader
In your new book, you compared the different methods of doing lookups.

For heap table, you used an example of TABLE ACCESS BY INDEX ROWID, which is not
so efficient as SINGLE-TABLE HASH CLUSTER or IOT.

I am wondering if we could achieve the same or better performance if we use
INDEX FAST FULL SCAN on the heap table?

Thanks.

          Followup:

an index fast full scan ??? to do a keyed read????

nope, not a chance (think about it -- do you really want to full scan the entire
index each time??????)

      Rating: 5
Single-Table
      B-Tree Cluster  November 09, 2003
Reviewer:
      A reader
Thanks Tom.

According to Oracle DBA Guide:

"The SINGLE TABLE option is valid only for hash clusters. HASHKEYS must also be
specified."

Is it right that I should not use a single-table b-tree index cluster.

          Followup:

no, it is not right.

you can use a b*tree cluster very effectively with a single table in it.
Consider a single table in a b*tree cluster that has a key "deptno" on it. The
single table is EMP.  Now -- all emps in the same deptno will be physically
"colocated" together.  If that is advantageous to your application, it'll be a
good thing.

      Rating: 5
IOT versus
      clustered table  November 10, 2003
Reviewer:
      Dushan  from Czech republic
As it was said, insert into IOT table is more expensive than insert into
clustered table. Is this the only benefit?

As for me the biggest cons of clustered table in common are:
1) Not possible to partition
2) Not possible to direct path load (same for IOT, I think)

So, when we store huge amount of data into clustered table, deleting from this
table turns into nightmare. From this point IOT wins (truncate/drop partition).

          Followup:

it was said?  where?

A cluster allows you to store rows from many tables together on a single block.
All of the data from "DEPT" and "EMP" can be physically stored on a single
block.

IOT's do not have that.

They all are just TOOLS -- to be used at the appropriate time.  Look at the
Oracle data dictionary -- when you create a table (with columns, indexes,
constraints, etc) -- we store that in a cluster so that most of the information
about a TABLE will be on the same block.

You need to be aware of how each works, what is can and cannot do for you and so
on.

      Rating: 5
Clustered table
      experience  November 10, 2003
Reviewer:
      Dushan  from Czech rep.
Understand, Oracle data dictionary is an excellent example - changes to data
dictionary are rare (usally), quick response is required, low IO.

Unfortunalely, I had seen rather strange table design on two billing systems
regarding using of cluster.
In the first case, weekly CDR tables (big tables) were clustered on customer
code column, and were joined in partitioned view. So, no problem with getting
rid of data for one week, nevertheless, inserts of new CDR's suffered. Everybody
believed that billing process is much faster using these clustered tables, until
we tested (by accident) the whole process on regular heap tables with regular
B-tree indexes. No impact on performance at all!

Second case - different client, different billing system but the same CDR story.
Even worse - really huge clustered table and problem with deleting. Just
nightmare!

Of course, I agree with you that this all about TOOLS to be used in proper way.

(Just reading your Effective Oracle by Design, excellent book!)

November 10, 2003 -- Thanks for the question regarding "partition
      pruning", version 8.1.7

      You Asked
Tom,

I have a table called submission_file which is non partitioned.
Currently i did create subm_id as primary key.

structure of sf(submission_file)

subm_id,
dt_cr,
latest_status_dt (date field).

Then I have a submission_detail file which is partitioned on dt_cr
but i do not have any primary key or local indexes on this table.

structure of submission_detail:

subm_id,
dt_cr, ... other columns.

now my query is:

explain plan for select /*+ ORDERED USE_nl(b) */ *
from submission_file1 a, submission_detail1 b
where a.latest_status_dt between to_date('12/01/2002','mm/dd/yyyy') and
to_date('12/05/2002','mm/dd/yyyy')
and a.dt_cr = b.dt_cr
and a.subm_id = b.subm_id

does a partition elimination.

However

explain plan for select /*+ ORDERED USE_hash(b) */ *
from submission_file1 a, submission_detail1 b
where a.latest_status_dt between to_date('12/01/2002','mm/dd/yyyy') and
to_date('12/05/2002','mm/dd/yyyy')
and a.dt_cr = b.dt_cr
and a.subm_id = b.subm_id

does not do partition elimination.  I have even adjusted  _subquery parameters.

But the hash join does not eliminate partitions.

can you help me regarding this.

Thanks

Plan Table
--------------------------------------------------------------------------------
| Operation                  |  Name    |  Rows | Bytes|  Cost  |  TQ  |IN-OUT|
PQ Distrib | Pstart| Pstop |

--------------------------------------------------------------------------------
----------------------------

| SELECT STATEMENT           |          |   999K|  263M|1434806 |      |      |
           |       |       |

|  HASH JOIN                 |          |   999K|  263M|1434806 |      |      |
           |       |       |

Plan Table
--------------------------------------------------------------------------------

|   TABLE ACCESS FULL        |SUBMISSIO |     9K|    1M|     29 |      |      |
           |       |       |

|   PARTITION RANGE ALL      |          |       |      |        |      |      |
           |     1 |     3 |

|    TABLE ACCESS FULL       |SUBMISSIO |     2M|  443M|  11985 |      |      |
           |     1 |     3 |

--------------------------------------------------------------------------------

Plan Table
--------------------------------------------------------------------------------
----------------------------

      and we said...
why are you hinting?  why not just analyze and let the optimizer do its job????

the hash join just cannot do what you want it to do.  It has NO idea before it
begins what partition MIGHT be involved -- you are making it hash the two tables
and join them -- therefore, it has to do the entire thing

My advice -- avoid hints like the plague.  Don't go there, just let the
optimizer do its job.  Read:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6601251003901
for some important advice but just let it do its job!

Review &
      Followup

      Rating: 2
hash
      join???  December 25, 2002
Reviewer:
      A reader
"the hash join just cannot do what you want it to do.  It has NO idea before it
begins what partition MIGHT be involved -- you are making it hash the two tables

and join them -- therefore, it has to do the entire thing"

Tom,

I did not understand the above statement? what does it mean?

          Followup:

umm, which part isn't clear?

They forced a hash join.
As far as the database is concerned, ALL partitions from the partitioned table
are involved

explain plan for select /*+ ORDERED USE_hash(b) */ *
from submission_file1 a, submission_detail1 b
where a.latest_status_dt between to_date('12/01/2002','mm/dd/yyyy') and
to_date('12/05/2002','mm/dd/yyyy')
and a.dt_cr = b.dt_cr
and a.subm_id = b.subm_id

(and guess what - they are!  all partitions are involved in this HASH join)

See -- the table B is partitioned by dt_cr.  Now, you tell me -- before this
query begins -- what partitions do you need to hash in order to do the hash
join?  Well all of them -- why?  there is no predicate on b.dt_cr, sure there is
a JOIN condition -- but there isn't any predicate that could be used for
partition elimination.

They did not care to share the nested loops plan with us - show I cannot comment
on that but there it may well be doing a full scan of A followed by a full scan
of a single partition in B for each and every row (in which case, the hash join
would be infinitely preferred).

      Rating: 3
BUT
      ....  December 25, 2002
Reviewer:
      A reader
according to oracle document:  179518.1

In 8.1.6 the code was enhanced to allow elimination to occur with a hash join.
It was recognized that the non-partitioned table contains the information to
eliminate partitions. If this information is compared with the dictionary
information regarding partition ranges, then a list of partitions to be
accessed can be generated. This activity only occurs if certain cost and
selectivity criteria are met. The elimination IS NOT done in the join step
itself, rather it is performed as recursive SQL independently of the join
itself and therefore elimination is not join order dependant. In this case  the
following plan can be seen:

The example which is given does a partition elimination for a hash join. I tried
that example. It works. However for my case it does not. Might be because all my
partitions belong to same tablespace ????????

          Followup:

tablespace has nothing to do with it. nothing.

So, look to the note -- "if certain cost and selectivity criteria are met".

Look at your query:

explain plan for select /*+ ORDERED USE_hash(b) */ *
from submission_file1 a, submission_detail1 b
where a.latest_status_dt between to_date('12/01/2002','mm/dd/yyyy') and
to_date('12/05/2002','mm/dd/yyyy')
and a.dt_cr = b.dt_cr
and a.subm_id = b.subm_id

b is partitioned on dt_cr.

You are selecting some (possibly all) set of dt_cr's from A based on that
between.  So tell me, does the optimizer have the ability to see that given your
predicate on A, this makes sense?  to do partition elimination?  apparently --
not.

it is doing what it is doing.

If you would care to share howto setup your test (eg: populated off of data
dictionary data or some procedural process), we can look further.

      Rating: 3
cannot get
      partition elimination  December 25, 2002
Reviewer:
      A reader
Tom,

I tried that every possible option. But cannot get it to work in my case (using
hash join to eliminate partition).

I will post the use_NL plan on this page after sometime.

Thanks for all the help.

      Rating: 4
Please look at
      this ....  December 26, 2002
Reviewer:
      A reader
Tom,

Please look at the below results:

First the NL query path.

Q1) why did it eliminate the partitions in case of nested loop?

Plan Table
--------------------------------------------------------------------------------
| Operation                  |  Name    |  Rows | Bytes|  Cost  |  TQ  |IN-OUT|
PQ Distrib | Pstart| Pstop |

--------------------------------------------------------------------------------
----------------------------

| SELECT STATEMENT           |          |   999K|  263M|     36M|      |      |
           |       |       |

|  NESTED LOOPS              |          |   999K|  263M|     36M|      |      |
           |       |       |

Plan Table
--------------------------------------------------------------------------------

|   TABLE ACCESS FULL        |SUBMISSIO |     9K|    1M|     29 |      |      |
           |       |       |

|   PARTITION RANGE ITERATOR |          |       |      |        |      |      |
           |   KEY |   KEY |

|    TABLE ACCESS FULL       |SUBMISSIO |     2M|  443M|   3995 |      |      |
           |   KEY |   KEY |

--------------------------------------------------------------------------------

Plan Table
--------------------------------------------------------------------------------
----------------------------

8 rows selected.

Q2)   select column_name, num_distinct from user_tab_columns
SQL> /
DEPTNO                                    4
DNAME                                     4
LOC                                       4

SQL> alter session set "_subquery_pruning_cost_factor"=1;

Session altered.

SQL> alter session set "_subquery_pruning_reduction"=100;

Session altered.

SQL>
explain plan for
select /*+ ordered use_hash(p) */ col1
from dept d, range_part p
where d.deptno = p.col1
and d.loc='DALLAS';SQL>   2    3    4    5

Explained.

SQL> @?/rdbms/admin/utlxplp.sql

Plan Table
--------------------------------------------------------------------------------
| Operation                  |  Name    |  Rows | Bytes|  Cost  |  TQ  |IN-OUT|
PQ Distrib | Pstart| Pstop |

--------------------------------------------------------------------------------
----------------------------

| SELECT STATEMENT           |          |     1 |   22 |      3 |      |      |
           |       |       |

|  HASH JOIN                 |          |     1 |   22 |      3 |      |      |
           |       |       |

Plan Table
--------------------------------------------------------------------------------

|   TABLE ACCESS FULL        |DEPT      |     1 |    9 |      1 |      |      |
           |       |       |

|   PARTITION RANGE ITERATOR |          |       |      |        |      |      |
           |   KEY |   KEY |

|    TABLE ACCESS FULL       |RANGE_PAR |    84 |    1K|      1 |      |      |
           |   KEY |   KEY |

--------------------------------------------------------------------------------

Plan Table
--------------------------------------------------------------------------------
----------------------------

*** 2002-12-25 21:56:10.634
*** SESSION ID:(10.437) 2002-12-25 21:56:10.630
Partition Iterator Information:
  partition level = PARTITION
  call time = RUN
  order = ASCENDING
  Partition iterator for level 1:
   iterator = SQL {count= 1, text = SELECT distinct TBL$OR$IDX$PART$NUM("RANGE_P
ART", 0,  "COL1") FROM (SELECT "D"."DEPTNO" COL1 FROM "DEPT" "D" WHERE "D"."LOC"
='DALLAS') ORDER BY 1}
   index = 0
  current partition: part# = 2, subp# = 65535, abs# = 2

SQL> insert into dept values(50,'RESEARCH','OPERATIONS');

1 row created.

SQL> commit;

Commit complete.

SQL> analyze table dept compute statistics;

Table analyzed.

SQL> alter session set "_subquery_pruning_cost_factor"=1;

Session altered.

SQL>  alter session set "_subquery_pruning_reduction"=100;

Session altered.

SQL> select /*+ ordered use_hash(p) */ col1
from dept d, range_part p
where d.deptno = p.col1
and d.loc='DALLAS';  2    3    4

no rows selected

SQL> explain plan for
select /*+ ordered use_hash(p) */ col1
from dept d, range_part p
where d.deptno = p.col1
and d.loc='DALLAS';  2    3    4    5

Explained.

SQL> @?/rdbms/admin/utlxplp.sql

Plan Table
--------------------------------------------------------------------------------
| Operation                  |  Name    |  Rows | Bytes|  Cost  |  TQ  |IN-OUT|
PQ Distrib | Pstart| Pstop |

--------------------------------------------------------------------------------
----------------------------

| SELECT STATEMENT           |          |     1 |   23 |      3 |      |      |
           |       |       |

|  HASH JOIN                 |          |     1 |   23 |      3 |      |      |
           |       |       |

Plan Table
--------------------------------------------------------------------------------

|   TABLE ACCESS FULL        |DEPT      |     1 |   10 |      1 |      |      |
           |       |       |

|   PARTITION RANGE ITERATOR |          |       |      |        |      |      |
           |   KEY |   KEY |

|    TABLE ACCESS FULL       |RANGE_PAR |    84 |    1K|      1 |      |      |
           |   KEY |   KEY |

--------------------------------------------------------------------------------

Plan Table
--------------------------------------------------------------------------------
----------------------------

8 rows selected.

why did the optimizer do a partition elimination in this case when using hash
join?

q3)  I have

 select column_name, num_distinct from user_tab_columns
  2  where table_name='SUBMISSION_FILE'
  3  and column_name in ('DT_CR','LATEST_STATUS_CHANGE_DT');

DT_CR                                  9120
LATEST_STATUS_CHANGE_DT                9120

We get around 10000 submission files and around 8 million transaction details
associated with those submission files.
partition key is dt_cr.  But the biggest problem is
DT_CR on submission files and transaction details can be same or have a
difference of 1 day between them. The files get there status changed in between
and for reporting purposes we have to look at latest_status_change_dt and not
dt_cr. So how can we write queries so that it does partition elimination when
doing a join? NL will not help ...

Thanks

          Followup:

q1) you have to think about how NL's are *done*

for x in ( select * from outer_table )
loop
    do some query against inner table
end loop

vs how a hash join is done

hash t1
hash t2
put them together

totally -- very different logic....  It is trivial to use partition elimination
against the inner table using NL join - it is like running a correlated
subquery.  hash operations are "en-mass", bulk operations.  We would have to:

select distinct dt_cr from outer table
rewrite query against inner table to say "where dt_cr = :x or dt_cr = :y...."

sometimes we do, sometimes we say "not worth it"

q2) because your artificially induced it to with the undocumented parameters.
There isn't enough data in any of those tables to warrant what work we are doing
unless you artificially make it do it that way.  You have no data, no stats
maybe even -- worth doing this extra work for.

q3) Do you have STATS on these objects??? Here is my test case:

ops$tkyte@ORA817DEV> drop table small_table;

Table dropped.

ops$tkyte@ORA817DEV> drop table big_table;

Table dropped.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create table small_table ( latest_status_dt date, dt_cr
date, subm_id number );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create table big_table ( dt_cr date, subm_id number, data
char(255) )
  2  partition by range( dt_cr )
  3  ( partition p01 values less than (to_date('01012002','ddmmyyyy') ),
  4    partition p02 values less than (to_date('01022002','ddmmyyyy') ),
  5    partition p03 values less than (to_date('01032002','ddmmyyyy') ),
  6    partition p04 values less than (to_date('01042002','ddmmyyyy') ),
  7    partition p05 values less than (to_date('01052002','ddmmyyyy') ),
  8    partition p06 values less than (to_date('01062002','ddmmyyyy') ),
  9    partition p07 values less than (to_date('01072002','ddmmyyyy') ),
 10    partition p08 values less than (to_date('01082002','ddmmyyyy') ),
 11    partition p09 values less than (to_date('01092002','ddmmyyyy') ),
 12    partition p10 values less than (to_date('01102002','ddmmyyyy') ),
 13    partition p11 values less than (to_date('01112002','ddmmyyyy') ),
 14    partition p12 values less than (to_date('01122002','ddmmyyyy') )
 15  )
 16  /

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> exec dbms_stats.set_table_stats( user, 'SMALL_TABLE',
numrows => 10000, numblks => 100 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> exec dbms_stats.set_table_stats( user, 'BIG_TABLE', numrows
=> 10000000, numblks => 100000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> delete from plan_table;

5 rows deleted.

ops$tkyte@ORA817DEV> set echo off

Explained.

Plan Table
--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |    26K|    3M|   3073 |       |       |
|  HASH JOIN                |          |    26K|    3M|   3073 |       |       |
|   TABLE ACCESS FULL       |SMALL_TAB |    26 |  806 |      3 |       |       |
|   PARTITION RANGE ITERATOR|          |       |      |        |   KEY |   KEY |
|    TABLE ACCESS FULL      |BIG_TABLE |    10M|  953M|   2879 |   KEY |   KEY |
--------------------------------------------------------------------------------

8 rows selected.

I get the *same* results in 817 and 9iR2.

I'm guessing you don't have any or they are totally wrong.

      Rating: 4
what does this
      mean?  December 26, 2002
Reviewer:
      A reader
Tom,

"(eg: populated off of data
dictionary data or some procedural process)" what does this mean?

          Followup:

statistics basically.

      Rating: 4
sorry, mismatch
      in cut and paste  December 26, 2002
Reviewer:
      A reader
SQL> select * from dept;
        10 ACCOUNTING     NEW YORK
        20 RESEARCH       DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS     BOSTON
        50 RESEARCH       OPERATIONS

SQL> update dept set loc='DALLAS' where deptno=50;

1 row updated.

SQL> commit;

Commit complete.

SQL> select column_name, num_distinct from user_tab_columns
  2  where table_name='DEPT';
DEPTNO                                    5
DNAME                                     4
LOC                                       5

SQL> analyze table dept compute statistics;

Table analyzed.

SQL> /

Table analyzed.

SQL> select column_name, num_distinct from user_tab_columns
  2   where table_name='DEPT';
DEPTNO                                    5
DNAME                                     4
LOC                                       4

SQL> select /*+ ordered use_hash(p) */ col1
from dept d, range_part p
where d.deptno = p.col1
and d.loc='DALLAS';  2    3    4

no rows selected

SQL> explain plan for
select /*+ ordered use_hash(p) */ col1
from dept d, range_part p
where d.deptno = p.col1
and d.loc='DALLAS';  2    3    4    5

Explained.

SQL> @?/rdbms/admin/utlxplp.sql
| Operation                  |  Name    |  Rows | Bytes|  Cost  |  TQ  |IN-OUT|
PQ Distrib | Pstart| Pstop |

--------------------------------------------------------------------------------
----------------------------

| SELECT STATEMENT           |          |     2 |   44 |      3 |      |      |
           |       |       |

|  HASH JOIN                 |          |     2 |   44 |      3 |      |      |
           |       |       |

|   TABLE ACCESS FULL        |DEPT      |     2 |   18 |      1 |      |      |
           |       |       |

|   PARTITION RANGE ALL      |          |       |      |        |      |      |
           |     1 |     4 |

|    TABLE ACCESS FULL       |RANGE_PAR |    84 |    1K|      1 |      |      |
           |     1 |     4 |

--------------------------------------------------------------------------------
----------------------------

8 rows selected.

SQL> alter session set "_subquery_pruning_cost_factor"=1;

Session altered.

SQL> >  alter session set "_subquery_pruning_reduction"=100;
SP2-0734: unknown command beginning ">  alter s..." - rest of line ignored.
SQL> alter session set "_subquery_pruning_reduction"=100;

Session altered.

SQL> explain plan for
select /*+ ordered use_hash(p) */ col1
from dept d, range_part p
where d.deptno = p.col1
and d.loc='DALLAS';    2    3    4    5

Explained.

SQL> alter system flush shared_pool;

System altered.

SQL> explain plan for
select /*+ ordered use_hash(p) */ col1
from dept d, range_part p
where d.deptno = p.col1
and d.loc='DALLAS';   2    3    4    5

Explained.

SQL> @?/rdbms/admin/utlxplp.sql
| Operation                  |  Name    |  Rows | Bytes|  Cost  |  TQ  |IN-OUT|
PQ Distrib | Pstart| Pstop |

--------------------------------------------------------------------------------
----------------------------

| SELECT STATEMENT           |          |     2 |   44 |      3 |      |      |
           |       |       |

|  HASH JOIN                 |          |     2 |   44 |      3 |      |      |
           |       |       |

|   TABLE ACCESS FULL        |DEPT      |     2 |   18 |      1 |      |      |
           |       |       |

|   PARTITION RANGE ITERATOR |          |       |      |        |      |      |
           |   KEY |   KEY |

|    TABLE ACCESS FULL       |RANGE_PAR |    84 |    1K|      1 |      |      |
           |   KEY |   KEY |

--------------------------------------------------------------------------------
----------------------------

8 rows selected.

Thanks

          Followup:

see above -- me thinks you have no stats or the stats are wrong or the stats are
telling us "don't do that"

      Rating:
      5
Excellent!!!  December 26, 2002
Reviewer:
      A reader
Tom,

Thanks for your very very valuable input. I did analyze  all the
partitions....but think somehow my stats got screwed up.

It now does use hash join.

Thanks again....

      Rating: 5
Another
      case....  December 26, 2002
Reviewer:
      A reader
Tom,

For a given dt_cr of submission file, The details can be either

 a) on the date created as of submission file (dt_cr)
 b) one date prior the submission file dt_cr (dt_cr - 1)
 c) one day after the submission file (dt_cr + 1)

so i query:

explain plan for select /*+ ordered use_hash(b)  */ *
from submission_file1 a, submission_detail1 b
where a.latest_status_change_dt between to_date('12/01/2002','mm/dd/yyyy') and
to_date('12/05/2002','mm/dd/yyyy')
and a.subm_id = b.subm_id
and b.dt_cr between trunc(a.dt_cr) - 1 and  trunc(a.dt_cr) + 1;

SQL> @?/rdbms/admin/utlxplp.sql
| Operation                  |  Name    |  Rows | Bytes|  Cost  |  TQ  |IN-OUT|
PQ Distrib | Pstart| Pstop |

--------------------------------------------------------------------------------
----------------------------

| SELECT STATEMENT           |          |     6K|    1M| 123872 |      |      |
           |       |       |

|  HASH JOIN                 |          |     6K|    1M| 123872 |      |      |
           |       |       |

|   TABLE ACCESS FULL        |SUBMISSIO |    26 |    2K|     16 |      |      |
           |       |       |

|   PARTITION RANGE ALL      |          |       |      |        |      |      |
           |     1 |    12 |

|    TABLE ACCESS FULL       |SUBMISSIO |    10M|    1G|  15178 |      |      |
           |     1 |    12 |

--------------------------------------------------------------------------------
----------------------------

does the partition elimination not work for this?

          Followup:

Here -- it would be even less likely.

you are performing functions on columns (stats are almost useless there)

We have no idea what domain of b.dt_cr's are likely or possible here.

Now, since submission_file1 is a "small table" -- only 10k rows -- you can do
this easily enough:

ops$tkyte@ORA920> delete from plan_table;
5 rows deleted.

ops$tkyte@ORA920> explain plan
  2  for
  3      select *   /* Look ma -- no hints!!!*/
  4        from small_table a, big_table b,
  5        ( select min(trunc(dt_cr)-1) min_dt_cr,
  6                max(trunc(dt_cr)+1) max_dt_cr
  7                   from small_table
  8          where latest_status_dt between to_date('12/01/2002','mm/dd/yyyy')
  9                                     and to_date('12/05/2002','mm/dd/yyyy')
 10            ) parms
 11       where a.latest_status_dt between to_date('12/01/2002','mm/dd/yyyy')
                                       and to_date('12/05/2002','mm/dd/yyyy')
 12         and b.dt_cr between trunc(a.dt_cr)-1 and trunc(a.dt_cr)+1
 13         and b.dt_cr between parms.min_dt_cr and parms.max_dt_cr
 14         and a.subm_id = b.subm_id
 15  /

Explained.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set echo off

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
--------------------------------------------------

---------------------------------------------------------------------------------
----------
| Id  | Operation                  |  N| Rows  | Bytes | Cost  | Pstart| Pstop |
---------------------------------------------------------------------------------
----------
|   0 | SELECT STATEMENT           |   |     1 |   149 |  9642 |       |       |
|*  1 |  HASH JOIN                 |   |     1 |   149 |  9642 |       |       |
|*  2 |   TABLE ACCESS FULL        | SM|    25 |   775 |    11 |       |       |
|   3 |   NESTED LOOPS             |   | 25000 |  2880K|  9630 |       |       |
|   4 |    VIEW                    |   |     1 |    18 |    11 |       |       |
|*  5 |     FILTER                 |   |       |       |       |       |       |
|   6 |      SORT AGGREGATE        |   |     1 |    18 |       |       |       |
|*  7 |       TABLE ACCESS FULL    | SM}    25 |   450 |    11 |       |       |
|   8 |    PARTITION RANGE ITERATOR|   |       |       |       |   KEY |   KEY |
|*  9 |     TABLE ACCESS FULL      | BI|     3 |   300 |  9619 |   KEY |   KEY |
---------------------------------------------------------------------------------
----------

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

   1 - access("A"."SUBM_ID"="B"."SUBM_ID")
       filter("B"."DT_CR">=TRUNC("A"."DT_CR")-1 AND
"B"."DT_CR"<=TRUNC("A"."DT_CR")+1)
   2 - filter("A"."LATEST_STATUS_DT">=TO_DATE('2002-12-01 00:00:00', 'yyyy-mm-dd
hh24:mi
              :ss') AND "A"."LATEST_STATUS_DT"<=TO_DATE('2002-12-05 00:00:00',
'yyyy-mm-
              dd hh24:mi:ss'))
   5 - filter(TO_DATE('2002-12-01 00:00:00', 'yyyy-mm-dd
hh24:mi:ss')<=TO_DATE('2002-12-
              05 00:00:00', 'yyyy-mm-dd hh24:mi:ss'))
   7 - filter("SMALL_TABLE"."LATEST_STATUS_DT">=TO_DATE('2002-12-01 00:00:00',
'yyyy-mm-
              dd hh24:mi:ss') AND
"SMALL_TABLE"."LATEST_STATUS_DT"<=TO_DATE('2002-12-05
              00:00:00', 'yyyy-mm-dd hh24:mi:ss'))
   9 - filter("B"."DT_CR">="PARMS"."MIN_DT_CR" AND
"B"."DT_CR"<="PARMS"."MAX_DT_CR")

Note: cpu costing is off

32 rows selected.

      Rating: 5
Question on
      example tables  February 26, 2003
Reviewer:
      Tommy Williamson  from Ridgeland, MS USA
Tom,
I created the small_table and big_table like your example and set the
statistics. However I was suprised at the explain plan when I ran this SQL:

SQL> explain plan for SELECT  *
  2  FROM big_table b
  3  WHERE b.dt_cr BETWEEN TO_DATE('11/01/2002','mm/dd/yyyy') AND
TO_DATE('11/20/2002','mm/dd/yyyy');

Explained.

SQL> @?/RDBMS/ADMIN/UTLXPLS

Plan Table
--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |    25K|    2M|   1266 |       |       |
|  TABLE ACCESS FULL        |BIG_TABLE |    25K|    2M|   1266 |    12 |    12 |
--------------------------------------------------------------------------------

It appears to only be reading the last partition like I expected (Pstart = 12
Pstop = 12), but why doesn't PARTITION RANGE show up in the plan?

Thanks

          Followup:

it is implied -- pstart/pstop is showing it.  it is doing the right thing.

      Rating: 4
Partition
      elemination - what's wrong???  November 05, 2003

Reviewer:  Michael  from Germany
Hi Tom!

I did the following:

SQL> create table parttest (
  2    part_key number(38) not null,
  3    data varchar2(10)
  4  )
  5  partition by range (part_key) (
  6    partition p1 values less than(1000) tablespace tbs_1,
  7    partition p2 values less than(2000) tablespace tbs_2
  8  );

Tabelle wurde angelegt.

SQL> analyze table parttest compute statistics
  2  for table for all columns for all indexes;

Tabelle wurde analysiert.

SQL> alter tablespace tbs_1 offline normal;

Tablespace wurde geändert.

SQL> var a number
SQL> var b number
SQL> var c number
SQL> execute :a := 1000; :b := 500; :c := 1500;

PL/SQL-Prozedur wurde erfolgreich abgeschlossen.

SQL> select count(*) from parttest
  2  where part_key >= :a
  3  and part_key between :b and :c;

  COUNT(*)
----------
         0

This statement works, but:

SQL> select count(*)
  2  from (select * from parttest
  3        where part_key >= :a)
  4  where part_key between :b and :c;
from (select * from parttest
                    *
FEHLER in Zeile 2:
ORA-00376: Datei 16 kann zur Zeit nicht gelesen werden
ORA-01110: Datendatei 16: 'E:\APPS\ORACLE\ORADATA\ORA92DB\TBS_1.ORA'

Seldom, what's the difference?

SQL> explain plan for
  2  select count(*) from parttest
  3  where part_key >= :a
  4  and part_key between :b and :c;

EXPLAIN PLAN ausgeführt.

SQL> @?/RDBMS/ADMIN/UTLXPLS

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
---------
| Id  | Operation                  |  Name       | Rows  | Bytes | Cost  |
Pstart| Pstop |
---------------------------------------------------------------------------------
---------
|   0 | SELECT STATEMENT           |             |     1 |    13 |     2 |
|       |
|   1 |  SORT AGGREGATE            |             |     1 |    13 |       |
|       |
|*  2 |   FILTER                   |             |       |       |       |
|       |
|   3 |    PARTITION RANGE ITERATOR|             |       |       |       |   KEY
|   KEY |
|*  4 |     TABLE ACCESS FULL      | PARTTEST    |     1 |    13 |     2 |   KEY
|   KEY |

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter(TO_NUMBER(:Z)<=TO_NUMBER(:Z) AND TO_NUMBER(:Z)<=TO_NUMBER(:Z))
   4 - filter("PARTTEST"."PART_KEY">=TO_NUMBER(:Z) AND
              "PARTTEST"."PART_KEY">=TO_NUMBER(:Z) AND
"PARTTEST"."PART_KEY"<=TO_NUMBER(:Z))

Note: cpu costing is off

19 Zeilen ausgewählt.

SQL> explain plan for
  2  select count(*)
  3  from (select * from parttest
  4        where part_key >= :a)
  5  where part_key between :b and :c;

EXPLAIN PLAN ausgeführt.

SQL> @?/RDBMS/ADMIN/UTLXPLS

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
---------
| Id  | Operation                  |  Name       | Rows  | Bytes | Cost  |
Pstart| Pstop |
---------------------------------------------------------------------------------
---------
|   0 | SELECT STATEMENT           |             |     1 |    13 |     2 |
|       |
|   1 |  SORT AGGREGATE            |             |     1 |    13 |       |
|       |
|*  2 |   FILTER                   |             |       |       |       |
|       |
|   3 |    PARTITION RANGE ITERATOR|             |       |       |       |   KEY
|   KEY |
|*  4 |     TABLE ACCESS FULL      | PARTTEST    |     1 |    13 |     2 |   KEY
|   KEY |

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter(TO_NUMBER(:Z)>=TO_NUMBER(:Z) AND TO_NUMBER(:Z)<=TO_NUMBER(:Z))
   4 - filter("PARTTEST"."PART_KEY">=TO_NUMBER(:Z) AND
              "PARTTEST"."PART_KEY"<=TO_NUMBER(:Z) AND
"PARTTEST"."PART_KEY">=TO_NUMBER(:Z))

Note: cpu costing is off

19 Zeilen ausgewählt.

So, what is the difference between these queries??

I would like to use a view which is identical with the inline view of the second
(failing) query, so that i can take offline partitions and modify the view so
that the endusers (which are using this view) don't notice the change (and
moreover don't get any ORA-00376 error messages).

Regards.

          Followup:

the first query is filtering based on the "where part_key >= :a" first AND THEN
on the between.

The second query is filtering on the between AND THEN the "where part_key >= :a"

the "where part_key >= :a" precluded the first partition.
the "between" does not.

it is the difference between:

4 - filter("PARTTEST"."PART_KEY">=TO_NUMBER(:Z) AND
           "PARTTEST"."PART_KEY">=TO_NUMBER(:Z) AND
           "PARTTEST"."PART_KEY"<=TO_NUMBER(:Z))

and

4 - filter("PARTTEST"."PART_KEY">=TO_NUMBER(:Z) AND
           "PARTTEST"."PART_KEY"<=TO_NUMBER(:Z) AND
           "PARTTEST"."PART_KEY">=TO_NUMBER(:Z))

Now, as much as I "don't like it" -- since it will have side effects on the
optimizer in other places, we can request that the "view" not be merged into

It can be very tricky hiding hints in views, it can lead to suboptimal plans
further on down the road.  It accomplishes your goal, but that might not always
be "good".

Here is a small simulation based on your case.  Since you cannot "bind" into a
view, i'll use an application context -- that is what your end uses will have to
do...

ops$tkyte@ORA920PC> alter tablespace users offline normal;

Tablespace altered.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> var a number
ops$tkyte@ORA920PC> var b number
ops$tkyte@ORA920PC> var c number
ops$tkyte@ORA920PC> execute :a := 1000; :b := 500; :c := 1500;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> exec dbms_application_info.set_client_info( 1000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create or replace view v
  2  as
  3  select /*+ NO_MERGE */ *
  4    from parttest
  5   where part_key >= to_number( sys_context( 'userenv', 'client_info' ) )
  6  /

View created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select count(*) from parttest
  2  where part_key >= :a
  3  and part_key between :b and :c;

  COUNT(*)
----------
         0

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select *
  2   from (select /*+ NO_MERGE */ part_key from parttest
  3          where part_key >= :a)
  4   where part_key between :b and :c
  5  /

no rows selected

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select count(*)
  2  from (select /*+ NO_MERGE */ part_key from parttest
  3        where part_key >= :a)
  4  where part_key between :b and :c;

  COUNT(*)
----------
         0

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select count(*)
  2    from v
  3   where part_key between :b and :c;

  COUNT(*)
----------
         0

      Rating: 4
Another
      idea  November 07, 2003
Reviewer:  Michael
       from Germany
Hi Tom!

The trick with the hint works great.
But there seems to be a way to do it without the hint, but i don't know if it is
better than the hint ...

First i added antoher partition to the table parttest:

  1  alter table parttest
  2  add partition p3 values less than (3000)
  3*     tablespace tbs_2
SQL> /

Tabelle wurde geändert.

(There a still no rows in the table.)

SQL> analyze table parttest compute statistics
  2  for table for all columns for all indexes;

Tabelle wurde analysiert.

Now i turn tablespace TBS_1 offline again.

SQL> alter tablespace tbs_1 offline normal;

Tablespace wurde geändert.

Now i know that partition p1 is not available. Then i modify the view v from:

create or replace view v as
select part_key, data
from parttest partition (p1)
union all
select part_key, data
from parttest partition (p2)
union all
select part_key, data
from parttest partition (p3);

To:

create or replace view v as
/* select part_key, data
   from parttest partition (p1)
union all */
select part_key, data
from parttest partition (p2)
union all
select part_key, data
from parttest partition (p3);

I explicitly tell Oracle which partitions it should (not) use.

SQL> var b number
SQL> var c number
SQL> execute :b := 500; :c := 1500;

PL/SQL-Prozedur wurde erfolgreich abgeschlossen.

SQL> set autotrace on
SQL> select *
  2  from v
  3  where part_key between :b and :c;

Es wurden keine Zeilen ausgewählt

Ausführungsplan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=20)
   1    0   FILTER
   2    1     VIEW OF 'V' (Cost=2 Card=1 Bytes=20)
   3    2       UNION-ALL (PARTITION)
   4    3         FILTER
   5    4           TABLE ACCESS (FULL) OF 'PARTTEST' (Cost=2 Card=1 Bytes=20)
   6    3         FILTER
   7    6           TABLE ACCESS (FULL) OF 'PARTTEST' (Cost=2 Card=1 Bytes=20)

Statistiken
----------------------------------------------------------
         96  recursive calls
          0  db block gets
         22  consistent gets
          0  physical reads
          0  redo size
        283  bytes sent via SQL*Net to client
        368  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
          0  rows processed

This works, but why is the third partition also scanned, since the data simply
cannot be in there? I think only partition p2 should be scanned.

Are there other dangers involved in this "name-the-
partitons-exclicitly-in-the-view"-approach except that it is not easy to
administer and "ugly".

Regards.

          Followup:

this seems to be an apples/oranges comparision?  where is the predicate in the
view?

      Rating: 3
Predicate
      necessary?  November 08, 2003
Reviewer:
      Michael  from Germany
The second view (with the partitions) doesn't have the predicate because i
exclicitly omit the unavailable partition with all the rows with partkey < 1000.
It is simply not necessary. (Right?)

What i want to achieve through the view is, that the unavailable partition is
not used.

          Followup:

Oh, i see, you want to do DDL -- i overlooked that.

well, what I'm seeing here is that you are silently removing data that should be
queried from being queried!

user asked for between 500 and 1500 -- you take away 500..999 and dont tell
them!  that seems like "a bad idea"??

i thought this was just a bad predicate at first (where partkey > 1000 and
partkey between 500 and 1500) but now I see you want to "hide" data from them.

wy not just exchange the partition with an empty table -- work on the "full
table" and exchange it back in?

      Rating:
      4
  November 10, 2003
Reviewer:
      Michael  from Germany
Yes, if the user wouldn't know about the missing data, it would be "a really bad
idea"!
But the user will know that the data with partkey < 1000 isn't queried.
In reality partkey is a date and each partition contains the data from one year.
The users know: We are in 2003, the data from the actual year and the data from
the last two years is online, so the data from year 2000 and before is offline.
For example in january 2004 i want to offline the data from 2001. To achive this
i plan to do the following:

1. Inform the end users about the "offlining" of the 2001 data.
2. create or replace v, so that the data from 2001 isn't queried. Here i don't
know, which method is better: predicate or union all on the available partitions
(See my previous reply)???
3. alter tablespace DATA_2001 offline normal;
4. Archiving the data files from the DATA_2001 tablespace.

If an end users needs the data from 2001 (or before) - rare - i will restore the
required data files, turn the tablespace DATA_2001 online again and then change
the view so that the data is visible and can be queried by the user.
When the end user finish the work with the old data, i archive it again.

Is this a "bad idea"?

          Followup:

you say

"But the user will know that the data with partkey < 1000 isn't queried"

how??? their predicate was between 500 and 1500 !!! i would surely expect that
partkey < 1000 would in fact be queried.

if you can inform the users of everything, fine -- just smells "fishy" to me.
end users are notorious for ignoring emails, alert/warning screens, etc.  (ask
me, I'm an enduser as we all are.  I just hit delete on sendmails (broadcasts),
those little flash screens on applications?  ignore them, etc -- its our nature)

I would have the queries fail -- so the user knows "oh, i was asking for stuff
that ain't around, better change my question so that the report doesn't provide
misleading information as it gets printed and sent around"

but you know your environment better then me.

      Rating: 4
Other part of
      the story ...  November 10, 2003
Reviewer:
      Michael  from Germany
Well, you are right - an error message is definitly better than a "wrong" (=
here: not the expected data was queried) query result.

But - i know it's "funny" -, it were the end users, that said: "We don't want to
see an error message about unavailable data, we just want results!"

I think the best i can do is to change the end users attitude about this issue,
so that they understand why an error message can be better than a (wrong!)
result.
(Even when this probably means more work for me, because then they will
immediately realize that they need the old data ;-)

November 10, 2003 Jayaraman -- Thanks for the question regarding
      "TWO_TASK environment variable and LD_LIBRARY_PATH", version 8.1.6.0.0

      You Asked
Hi Tom
Thanks for answering my earlier questions.

1.Why do we require the TWO_TASK variable in unix? If we are going to connect to
the local database using the connect string as defined in the tnsnames.ora, then
this variable need not be set. Am i right in assuming it? If not, for what
purpose do we set it/don't set it?

2.When is the LD_LIBRARY_PATH used and what does it contain? Does LD stand for
link directory? If so, then is it used only when relinking oracle executables?
Kindly explain in detail.

3.If LMT is really a better option than dictionary managed tablespace then, why
doesn't oracle make it the default tablespace type when creating tablespaces?

You have still not told me if your book is available in Seoul, Korea!! Please
ask Wrox immediately as then, i would not trouble you much.

Thanks & Regards,
Ashok

      and we said...
1) you do not, you never REQUIRE it.  It is an ALTERNATIVE.

Instead of typing:

sqlplus scott/tiger@remote_database

you can:

setenv TWO_TASK remote database
sqlplus scott/tiger

It just defaults the connect string for you.

2) LD_LIBRARY_PATH is used by ld, the system link editor -- the thing that loads
and runs programs.  It tells ld where to look for libraries.  It is used at
RUNTIME as well as link time.  It needs to be set in order to run code that is
dynamically linked (as Oracle is)

3) it is in 9i.

I'll ask again. i did not hear back.  you should be able to take the ISBN from
amazon into any book store and ask them if they are able to order it.
Review &
      Followup

      Rating:
      4
Thanks  December 22, 2001
Reviewer:
      A reader

      Rating:
      5
TWO_TASK  November 05, 2003
Reviewer:
      atul  from India
Hi ,

I have two databases on diff nodes,

its tnsnames.ora entries are as follows:

qieceu21_106.world =
  (DESCRIPTION =
    (ADDRESS_LIST =
        (ADDRESS =
          (COMMUNITY = switch.world)
          (PROTOCOL = TCP)
          (Host = ihqr02a1)
          (Port = 1522)
        )
    )
    (CONNECT_DATA = (SID = qieceu21_106)
    )
  )

and other is

fieceu11_589.world =
  (DESCRIPTION =
    (ADDRESS_LIST =
        (ADDRESS =
          (COMMUNITY = ethernet.world)
          (PROTOCOL = TCP)
          (Host = iade41a2-tr0)
          (Port = 5129)
        )
    )
    (CONNECT_DATA = (SID = fieceu11_589)
    )
  )

Also sqlnet.ora file on both node is same in contents..

From one database i am able to connect as
"user/uesr@fieceu11_589"

but in other database if i try to connect
"user/user@qieceu21_106"
Its gives error

ORA-12154 TNS:could not resolve service name
While giving "user/user@qieceu21_106.world" i could connect

while every parameter is same in both databases..

Like

ORACLE_SID=fieceu11_589;
export ORACLE_SID;
TWO_TASK=`cat /poclstd/parms/TWO_TASK/${ORACLE_SID}`;
export TWO_TASK;
ORACLE_HOME=`cat /poclstd/parms/ORACLE_HOME/${ORACLE_SID}`;
export ORACLE_HOME;

Kindly tell me what is going wrong??
or what should i have to do to get same results in both databases

Thank,
Atul

          Followup:

what is your tns_admin set to (environment variable)

i don't think you are using the tnsnames.ora file you THINK you are if the above
is the actual tnsnames.ora file.

      Rating:
      5
Query  November 06, 2003
Reviewer:
      atul  from India
Hi,

I checked in both environments there is no entry for
tns_admin..

I am giving you sqlnet.ora for both:

TRACE_LEVEL_CLIENT = OFF
sqlnet.expire_time = 2147483647
names.default_domain = world
name.default_zone = world

and

TRACE_LEVEL_CLIENT = OFF
sqlnet.expire_time = 2147483647
names.default_domain = world
name.default_zone = world

They are same,

What could be the other reasons??

          Followup:

if you are on solaris|linux, use truss|strace to verify the files being read,
like this:

$ csh
$ strace sqlplus scott/tiger@aria |& grep tnsnames
access("/home/tkyte/.tnsnames.ora", F_OK) = -1 ENOENT (No such file or
directory)
access("/etc/tnsnames.ora", F_OK)       = -1 ENOENT (No such file or directory)
access("/usr/oracle/ora920/network/admin/tnsnames.ora", F_OK) = 0
stat64("/usr/oracle/ora920/network/admin/tnsnames.ora", {st_mode=S_IFREG|0644,
st_size=1286, ...}) = 0
open("/usr/oracle/ora920/network/admin/tnsnames.ora", O_RDONLY) = 7

that shows the tnsnames.ora file i'm really using is my
$ORACLE_HOME/network/admin/tnsnames.ora file

make sure you are using the right one

      Rating: 5
Which
      tnsnames?  November 07, 2003
Reviewer:  Tracy
      Tupman  from UK
I used truss as you indicated to find out which tnsnames file was being used,
and from the output it looks like it has read from two tnsnames.ora. Now I'm
guessing that it uses the first one, but does it, and if so why does it bother
to read from any others?

test:/export/home/oracle$ truss sqlplus a/a@bos6 > testfil 2>&1
test:/export/home/oracle$ grep tnsnames testfil
access("/export/home/oracle/.tnsnames.ora", 0)  = 0
access("/var/opt/oracle/tnsnames.ora", 0)       Err#2 ENOENT
access("/var/opt/oracle/tnsnames.ora", 0)       Err#2 ENOENT
access("/u01/app/oracle/product/8.1.7/network/admin/tnsnames.ora", 0) = 0
stat("/export/home/oracle/.tnsnames.ora", 0xFFBE9528) = 0
open("/export/home/oracle/.tnsnames.ora", O_RDONLY) = 9
stat("/u01/app/oracle/product/8.1.7/network/admin/tnsnames.ora", 0xFFBE9528) = 0
open("/u01/app/oracle/product/8.1.7/network/admin/tnsnames.ora", O_RDONLY) = 9
access("/export/home/oracle/.tnsnames.ora", 0)  = 0
access("/var/opt/oracle/tnsnames.ora", 0)       Err#2 ENOENT
access("/var/opt/oracle/tnsnames.ora", 0)       Err#2 ENOENT
access("/u01/app/oracle/product/8.1.7/network/admin/tnsnames.ora", 0) = 0
stat("/export/home/oracle/.tnsnames.ora", 0xFFBE9218) = 0
open("/export/home/oracle/.tnsnames.ora", O_RDONLY) = 10
stat("/u01/app/oracle/product/8.1.7/network/admin/tnsnames.ora", 0xFFBE9218) = 0
open("/u01/app/oracle/product/8.1.7/network/admin/tnsnames.ora", O_RDONLY) = 10
test:/export/home/oracle$

          Followup:

what is in your .tnsnames.ora file

you can use the .tnsnames.ora file to override/add to the "real" tnsnames.ora
file.

i'll betcha your .tnsnames.ora file will be the culprit here.

      Rating:
      5
.tnsnames.ora  November 07, 2003
Reviewer:
      Tracy  from uk
yes, you are right. I moved the .tnsnames.ora to /var/opt/oracle/tnsnames.ora
and then ran the test again. This time it stopped when it found the file in
/var/opt/oracle and didn't bother looking elsewhere. Thanks.

      Rating:
      5
AIX  November 07, 2003
Reviewer:
      atul  from India
Hi,

What's a way for AIX unix

Thanks,
Atul

          Followup:

askibm.ibm.com :)

i don't know the name of the "trace" tool if any on AIX.

      Rating:
      5
strace  November 10, 2003
Reviewer:
      atul  from India
Hi Tom,

I'm giving you the output of strace with .world and
without .world

with .world

15B sqlplus                         0.766113           open /etc/tnsnames.ora fd
=8 RDONLY
15B sqlplus                         0.783159           open /etc/tnsnames.ora fd
=8 RDONLY
15B tnslsnr                         0.847759           open /etc/hosts fd=12 RDO
NLY

Without .world

15B tnslsnr                         0.774610           open /etc/hosts fd=14 RDO
NLY

In without .world case its not looking for tnsnames.ora but still i'm able to
connect..

Plz help me out

Thanks.
Atul

          Followup:

i've no clue what I'm looking here.  doesn't look like anything useful -- i
mean, whats the tnslsnr entry?  we don't care about that at all.

sorry, no idea what i'm looking at.

November 10, 2003 ted -- Thanks for the question regarding "Q:
      nologging option", version 8i

      You Asked
tom,
from oracle documentation nologging can be used with following
operation:

    alter table...move partition
    alter table...split partition
    alter index...split partition
    alter index...rebuild
    alter index...rebuild partition
    create table...as select
    create index
    direct load with SQL*Loader
    direct load INSERT

question:
1. what is direct load insert ? is this the same
   as:
   insert into tab_a nologging as select * from tab_b;
   ( tab_a and tab_b have same layout).

2. what happen behind the nologging operation.
   is it true neither rbs(undo) and redo log are used with
nologging on.

thnx in advance as always.
ted chyn

      and we said...
1) a direct load insert is done via:

insert /*+ APPEND */ ....

see
http://technet.oracle.com/doc/server.815/a67781/c21dlins.htm#365
for lots more info.

2) with nologging -- sort of a misnomer as some logging takes
place -- the data dictionary changes -- we do things in newly
allocated extents.  In the event it fails -- rolling back
involves undoing a dictionary update (unlink that newly linked
extent and its gone).  Commiting involves making that dictionary
update permanent.  Since we perform the operation in "new" space
-- we don't have any before images we need to restore and since
these things are writing to the datafiles -- we have no data
blocks we would need to "redo".

Don't forget -- backup after doing these operations or be
prepared to redo the work if you need to perform media recovery.
 Since its not logged -- archive logs won't recover it.
Review &
      Followup

      Rating: 4
any relation
      between nologging and direstload /*+append*/  May 30, 2002

Reviewer:  P  from LA
Tom,
here is the responce I got from one of the DBA's.
------------
Yes, I had noticed that you were blowing up the rollback segment.
Unfortunately, you are laboring under a number of misconceptions as to how
Oracle actually works.  No logging is unnecessary because this database is not
in archivelog mode.  Technically you get a small performance benefit by not
running the changes through the online redo logs.  However, you are not havening
performance problems.  The append hint does NOT perform direct path loads.
Direct path is only available thought SQL* Loader.  Append prevents the database
from checking the free space list when inserting new rows.  This would provide a
performance benefit if you were loading into a table that you had previously
deleted rows from.  If you are starting with an empty table it buys you nothing.
---------------------
My question is
1)Is it true that if database is not in archivelog mode then one cannot use
NOLOGGING and  /*+append*/ hint to minimise redo and undo log generation?
2) is the last statement of above reply is correct?

"If you are starting with an empty table it buys you nothing."
 that is /*+append*/ will not work if you start with an empty table

          Followup:

sigh, time for a new dba.

They are the ones laboring under the misconception that they know how Oracle
actually works.

Setting the nologging option in this case (noarchivelog) is not necessary they
are correct.  The insert /*+ append */ won't generate log for the table
modifications.  In archivelog mode, the table would (should) be in nologging
mode.

So, in a noarchive log mode database, you don't need to set nologging on the
table to bypass redo generation with insert /*+ append */.  This is easy to see
and demonstrate:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t as select * from all_objects
where 1=0;

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on statistics
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from
all_objects;

23014 rows created.

Statistics
----------------------------------------------------------
...
       5932  redo size
...
      23014  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> rollback;
Rollback complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select * from all_objects;
23014 rows created.

Statistics
----------------------------------------------------------
...
    2575040  redo size
...
      23014  rows processed

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

Rollback complete.

I don't know about you, but if I don't have to wait for 2.5meg of redo to be
a) generated and b) written (yes, you have to wait for it to write) -- I don't
want to.

Consider the savings on a really big load.

"small performance benefit" -- i think not.  Ask them "ok, so what about when
the logs switch and we start checkpointing?  and when the logs wrap and we have
to wait for the checkpoints to complete?  Can't I avoid that by bypassing redo
as well as getting the TREMENDOUS savings from not writing it in the first
place???"

As for the comment:
"The append hint does NOT perform direct path loads.  Direct path is only
available thought SQL* Loader."

Please give them the link above -- (to the server concepts manual, which they
OBVIOUSLY need to read)

<quote>
Introduction to Direct-Load INSERT

Direct-load INSERT enhances performance during insert operations by formatting
and writing data directly into Oracle datafiles, without using the buffer cache.
This functionality is similar to that of the Direct Loader utility (SQL*Loader).

</quote>

Hmmmm.......

The comment about an empty table vs a previously loaded table is a bunch of
HOOEY as well.  total and utter hooey.  They no not what they speak of.

1) FALSE.  Test case above was performed in a noarchive log mode database.
Serious savings - SERIOUS (and looky at that - the table was *empty*)

2) FALSE.

just read the contents of the link, have them read it.  It is very clearly
documented -- just waiting to be read!

      Rating:
      4
  May 30, 2002
Reviewer:  Ram Gopal
      Das  from New York
Tom, Please clarify this once again for me. An empty table has the HWM at 0
blocks. So whether we use /*+APPEND*/ or not the insert will start using the
blocks above HWM. So what you are saying is that, in this case (ie of an empty
table) the fuction of the append hint would be only to invoke the Direct Mode
insert (ie bypassibg the DB buffer)? Right? If yes then I think Oracle
Documentation has not clearly explained it - what I mean is that the doc gives
an impression that /*+APPEND*/ means using blocks above HWM. So I think many
people tend to believe that it is unnecessary to use append hint on an empty
table.

This is a very subtle distiction. Thanks for pointing out.

Rgds,

Ram

          Followup:

The advantage is:

a) no redo need be generated
b) write directly to blocks, bypass buffer cache
c) no freelist inspection for each and every block allocated (and you'll be
doing lots of those)

It is very useful to use APPEND on any table when you are bulk loading into it
LOTS OF ROWS.  It's faster, it's cheaper, it's better -- just make sure that you
clear it with the smart DBA so they can schedule a backup (when done
unrecoverable -- nologging -- it needs to be backed up shortly thereafter)

      Rating: 5
noarchivelog
      and /*+append*/  May 30, 2002
Reviewer:  P
       from LA
tom,
you said
"Setting the nologging option in this case (noarchivelog) is not necessary they
are correct.  The insert /*+ append */ won't generate log for the table
modifications.  "

could you please explain what is the technical reason it doesnot generate
redo/undo log when one uses /*+append*/ in noarchivelog mode. link would be just
fine

          Followup:

The technical reason is that is writes directly to the database files -- no
logging needed, we bypass the buffer cache -- we don't need the log for instance
recovery.  Since you cannot do MEDIA recovery in noarchive log mode, we don't
need the log for that either.

Hence, in noarchivelog mode, we don't need the log.

In archivelog mode, we assume you want to be able to do media recovery.  Hence,
by default, we log the changes so that you can.  If you specify NOLOGGING, we
won't.

That is why.

      Rating:
      5
  May 30, 2002
Reviewer:
      A reader

      Rating: 5
What did the
      DBA say?  May 31, 2002
Reviewer:  AB
       from UK
P from LA
---------

I hope you showed this to your DBA - I for one would be very interested to know
how he responded to the public trashing of his so-called knowledge... :)

AB

      Rating:
      5
  May 31, 2002
Reviewer:
      A reader
"It is very useful to use APPEND on any table when you are bulk loading into it
LOTS OF ROWS.  It's faster, it's cheaper, it's better "

--Is there a way I can use APPEND in exp/imp? I know we can use DIRECT mode
during exports. Is there anything equivalent for imports? If not why? Thanks.

          Followup:

No, there is not.  The reason -- import does single row array inserts and INSERT
/*+ APPEND */ works only on inserts with SELECTS

insert ... values -- no append.
insert ... select -- append is possible.

      Rating: 4
snap shot too
      old error   May 31, 2002
Reviewer:  P
       from LA
tom,
here is the PL/SQL job I am using. I know I can do that using /*+APPEND*/ but I
just want to know the reason for this error
I am commiting after 100 rows event I tried commit after 10 rows, still it gives
snapshot too old error

---------
SQL> ed
Wrote file afiedt.buf

  1  DECLARE
  2  n_rowcount NUMBER(10) ;
  3  dt_load_date date;
  4  CURSOR c1 IS
  5      SELECT
  6  A.LAST_UPDATE col1,
  7  B.mel_seq col2,
  8  A.CM_ID col3,
  9  C.mel_seq col4,
 10  A.LK_ID col5
 11  FROM
 12  ECDB.ECDB_SETROUTE_LINKS A,
 13  NCDBMEL.MASTER_EQUIPMENT_LIST B,
 14  NCDBMEL.MASTER_EQUIPMENT_LIST C
 15  where
 16  a.cm_id = b.equipment_id and
 17  a.lk_id = c.equipment_id
 18  ;
 19   BEGIN
 20      dt_load_date := SYSDATE;
 21      n_rowcount :=0;
 22      FOR c1_rec IN c1 LOOP
 23          INSERT INTO NCDBMEL.EROUTE_LINKS
 24    (
 25    EROUTE_LINKS_SEQ,
 26    CREATE_USER,
 27    CREATE_DATE,
 28    MODIFIED_USER,
 29    MODIFIED_DATE,
 30    MEL_SEQ,
 31    EQUIPMENT_ID,
 32    LINK_MEL_SEQ,
 33    LINK_EQUIPMENT_ID
 34    )
 35          VALUES
 36              (NCDBMEL.MAIN_SEQ.NEXTVAL,
 37               'LOAD DATA',
 38               dt_load_date ,
 39               null,
 40               c1_rec.col1,
 41               c1_rec.col2,
 42               c1_rec.col3,
 43               c1_rec.col4,
 44     c1_rec.col5
 45              )   ;
 46            n_rowcount := n_rowcount + 1;
 47            IF n_rowcount =100 THEN
 48              COMMIT WORK;
 49              n_rowcount :=0;
 50            END IF;
 51      END LOOP;
 52      COMMIT WORK;
 53* END;
SQL> /
DECLARE
*
ERROR at line 1:
ORA-01555: snapshot too old: rollback segment number 4 with name "RB3" too
small
ORA-06512: at line 22

SQL>

          Followup:

it is DUE TO THE COMMIT.

get rid of the commit and see what happens.

THIS SHOULD be a single statement -- period, plain and simple, no if and or buts
about it.

      Rating: 4
snapshot too
      old  May 31, 2002
Reviewer:  P  from LA

Tom,
I also want to mention that no one other than me is using those tables. Infact
we have very very few people logged on that DB.

thank you for resourceful site and fantastic book

P

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:895410916429
or see my book about this.  You are doing it to yourself!!! by committing.  If
you have the book -- then you really know how I feel about doing it procedurally
vs doing it in a single statement -- this screams "single statement".

      Rating: 4
snapshot too
      old  May 31, 2002
Reviewer:  P  from LA

tom,
so you are saying there is no way we can do this without increasing our
RS?(which is 30 meg).

P

          Followup:

30meg is pretty darn small.

But, I betcha you might find that a single insert into as select would do it --
have you even tried it?

      Rating: 4
snapshot too
      old  May 31, 2002
Reviewer:  P  from LA

tom,
yes i did with /*+APPEND*/ hint and without also - doesnt work.
it blows up no matter what I do.
single statement or batchjob doesnt matter
and I know 30 meg is small but we dont need more its not a prd system, for that
matter its not even a dev system and with very very few users.
oh btw we are using sequent as our OS(i know i know its old old but hey we have
it).

P

          Followup:

Well, you can either spend lots of time beating yourself about the head trying
to make this work with a ton of code or -- you can just increase the size of
your RBS to be more appropriate.

Just add a big rbs from temp or somewhere, use the dreaded "set transaction use
rolback" statement -- do the transaction and drop the newly added rbs.

      Rating: 2
snapshot too
      old  May 31, 2002
Reviewer:  P  from LA

well how big is big?

I have so many tables to migrate.
some have millions of rows and some with few hundred

          Followup:

when you say "migrate" what do you mean?  Is NCDBMEL.EROUTE_LINKS an empty table
that you are filling?  how many indexes on it -- you'll have to give me more
info.

If you are doing a one time migration -- drop the indexes, alter the table
nologging, do the index /*+ append*/ as select, rebuild the indexes in parallel
with nologging.  No rollback at all.  No redo either.

      Rating: 2
failed to
      extend rollback segment  May 31, 2002
Reviewer:
      P  from LA
here it is tom
I am running following sql (as you said single SQL) with /*+APPEND*/ linek and
our DB is in noarchivelog.
if I understand it correctly then it should not generate any rollback segment -
no indexes on destination table (only table thats it).

-------------
SQL> INSERT /*+APPEND */ INTO NCDBMEL.MEL_REFERENCE
  2  (
  3  MEL_REFERENCE_SEQ,
  4  MEL_SEQ,
  5  CREATE_USER,
  6  CREATE_DATE,
  7  MODIFIED_USER,
  8  MODIFIED_DATE,
  9  EQUIPMENT_ID,
 10  REF_TYPE_CODE,
 11  REFERENCE,
 12  REF_SHEET,
 13  REF_MODULE,
 14  REF_COORD,
 15  DOCUMENT_TYPE,
 16  ENGDWGS_SEQ
 17  )
 18  (
 19  SELECT
 20  NCDBMEL.MAIN_SEQ.NEXTVAL,
 21  B.MEL_SEQ,
 22  'LOAD DATA',
 23  SYSDATE,
 24  NULL,
 25  A.LAST_UPDATE,
 26  A.SETROUTE_KEY,
 27  NULL,
 28  A.SETROUTE_REFERENCE,
 29  A.REF_SHEET,
 30  'EC',
 31  NULL,
 32  'DRAWING',
 33  NULL
 34  FROM
 35  ECDB.ECDB_SETROUTE_REFERENCE A,
 36  NCDBMEL.MASTER_EQUIPMENT_LIST B
 37  WHERE
 38  A.SETROUTE_KEY = B.EQUIPMENT_ID AND
 39  B.CATEGORY_CODE IN ('EROUTE CABLE','EROUTE RACEWAY','EROUTE FROMTO')
 40  );
NCDBMEL.MAIN_SEQ.NEXTVAL,
        *
ERROR at line 20:
ORA-00604: error occurred at recursive SQL level 1
ORA-01562: failed to extend rollback segment number 4
ORA-01628: max # extents (20) reached for rollback segment RB3

          Followup:

You must have missed an index somewhere....

consider:

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t as select * from all_objects
where 1=0;

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from
all_objects;

23027 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select used_ublk from v$transaction;

 USED_UBLK
----------
         1

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx on t(object_name);

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from
all_objects;

23028 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select used_ublk from v$transaction;

 USED_UBLK
----------
       116

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

see the difference.  I'm thinking - you have an index....

      Rating: 2
RBS
      full  May 31, 2002
Reviewer:  P  from
      LA
also tom we have more than 2 million rows to migrate for this table

          Followup:

Well, here we go -- noarchivelog mode test database:

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t as select * from big_table
where 1=0;

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from
big_table;

1101008 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select used_ublk from v$transaction;

 USED_UBLK
----------
         1

Hmmm (big_table is really CTAS from all_objects with lots of insert intos -- it
is "fat")

Took 1 block of RBS stuff.

Even if 2 million rows DOUBLED THAT - it would fit in 30meg I think (2 blocks)

You are doing something wrong.

maybe:

a) there is an index
b) there is an index

I think -- maybe -- there is an index.......

      Rating:
      2
nologging  June 03, 2002
Reviewer:
      P  from LA
tom
I checked there are ZERO indexex on EROUTE_TERMINATION table and I get this -
I am using /*+append*/

THERE ARE NO INDEXES ON ANY DESTINATION TABLES
--------------------
SQL> ed
Wrote file afiedt.buf

  1  INSERT /*+APPEND */ INTO NCDBMEL.EROUTE_TERMINATION
  2  (
  3  EROUTE_TERMINATION_SEQ,
  4  CREATE_USER,
  5  CREATE_DATE,
  6  MODIFIED_USER,
  7  MODIFIED_DATE,
  8  MEL_SEQ,
  9  EQUIPMENT_ID,
 10  TERMINATION_MEL_SEQ,
 11  TERMINATION_EQUIPMENT_ID,
 12  WIRE,
 13  BLOCK,
 14  POINT,
 15  VENDOR,
 16  CORJ,
 17  NKC_SEQ
 18  )
 19  (
 20  SELECT
 21  ncdbmel.main_seq.nextval,
 22  'LOAD DATA',
 23  SYSDATE,
 24  NULL,
 25  NULL,
 26  B.mel_seq,
 27  A.CJ_ID,
 28  C.mel_seq,
 29  A.E_ID,
 30  A.WIRE,
 31  A.BLOCK,
 32  A.POINT,
 33  A.VENDOR,
 34  A.CORJ,
 35  A.NKC_SEQ
 36  FROM
 37  ECDB.ECDB_SETROUTE_TERM A,
 38  NCDBMEL.MASTER_EQUIPMENT_LIST B,
 39  NCDBMEL.MASTER_EQUIPMENT_LIST C
 40  WHERE
 41  A.CJ_ID = B.EQUIPMENT_ID
 42  AND A.E_ID = C.EQUIPMENT_ID
 43* )
SQL> /
ncdbmel.main_seq.nextval,
        *
ERROR at line 21:
ORA-00604: error occurred at recursive SQL level 1
ORA-01562: failed to extend rollback segment number 2
ORA-01628: max # extents (20) reached for rollback segment RB1

------------------------

          Followup:

Look for anything, anything at all, with regards to the table.  Triggers for
example.  I quite simply cannot reproduce.  Make sure to look when logged in as
a DBA and do the looking in the DBA_ views (as I can create an index on your
table, I can create a trigger on your table -- need not be in the NCDBMEL
schema.

There is something else out there -- there is a reason, you'll just need to dig
a little deeper.  I've done this on archivelog/noarchivelog and the results are
the same.

Look for a trigger this time (also look for the index but make sure to look in
the DBA_ views)

      Rating:
      5
  June 03, 2002
Reviewer:
      A reader
Tom,
 One of the comment in follow up is

"if I understand it correctly then it should not generate any rollback segment -

no indexes on destination table (only table thats it)."

I am confused here.

 Direct load shold not generate REDO but it Should geneate
ROLLBACK information.

 Am i right?

          Followup:

Wrong, it writes above the HWM for the table.

In noarchivelog mode databases -- append means NO UNDO, NO REDO (for the TABLE)

In archivelog mode databases -- append means NO UNDO and
   - if table is logging -- redo will be generated
   - if table is nologging
           - if version is 9iR1 and before -- NO REDO
           - if version if 9iR2 and after
                - if database permits unloggged operations NO REDO
                - else redo will be generated
(for the TABLE)

read the link I convienently provided in the answer -- details abound there in
the documentation!

      Rating: 2
failed to
      extend RBS  June 03, 2002
Reviewer:  P
       from LA
ok here it is
DB is noarchivelog
only one user
I droped and ecreated this table so no indexes, no triggers only table
----------------------
SQL> DROP TABLE EROUTE_TERMINATION;

Table dropped.

SQL> CREATE TABLE NCDBMEL.EROUTE_TERMINATION
  2   ( EROUTE_TERMINATION_SEQ NUMBER(20) NOT NULL,
  3   CREATE_USER VARCHAR2(10),
  4   CREATE_DATE DATE,
  5   MODIFIED_USER VARCHAR2(10),
  6   MODIFIED_DATE DATE,
  7   MEL_SEQ NUMBER(20),
  8   EQUIPMENT_ID VARCHAR2(30),
  9   TERMINATION_MEL_SEQ NUMBER(20),
 10   TERMINATION_EQUIPMENT_ID VARCHAR2(30),
 11   WIRE VARCHAR2(8),
 12   BLOCK VARCHAR2(4),
 13   POINT VARCHAR2(4),
 14          VENDOR VARCHAR2(1),
 15   CORJ VARCHAR2(1),
 16          NKC_SEQ NUMBER(3,0)
 17  ) ;

Table created.

SQL> ed
Wrote file afiedt.buf

  1  INSERT /*+APPEND */ INTO NCDBMEL.EROUTE_TERMINATION
  2  (
  3  EROUTE_TERMINATION_SEQ,
  4  CREATE_USER,
  5  CREATE_DATE,
  6  MODIFIED_USER,
  7  MODIFIED_DATE,
  8  MEL_SEQ,
  9  EQUIPMENT_ID,
 10  TERMINATION_MEL_SEQ,
 11  TERMINATION_EQUIPMENT_ID,
 12  WIRE,
 13  BLOCK,
 14  POINT,
 15  VENDOR,
 16  CORJ,
 17  NKC_SEQ
 18  )
 19  (
 20  SELECT
 21  ncdbmel.main_seq.nextval,
 22  'LOAD DATA',
 23  SYSDATE,
 24  NULL,
 25  NULL,
 26  B.mel_seq,
 27  A.CJ_ID,
 28  C.mel_seq,
 29  A.E_ID,
 30  A.WIRE,
 31  A.BLOCK,
 32  A.POINT,
 33  A.VENDOR,
 34  A.CORJ,
 35  A.NKC_SEQ
 36  FROM
 37  ECDB.ECDB_SETROUTE_TERM A,
 38  NCDBMEL.MASTER_EQUIPMENT_LIST B,
 39  NCDBMEL.MASTER_EQUIPMENT_LIST C
 40  WHERE
 41  A.CJ_ID = B.EQUIPMENT_ID
 42  AND A.E_ID = C.EQUIPMENT_ID
 43* )
SQL> /
ncdbmel.main_seq.nextval,
        *
ERROR at line 21:
ORA-00604: error occurred at recursive SQL level 1
ORA-01562: failed to extend rollback segment number 4
ORA-01628: max # extents (20) reached for rollback segment RB3

          Followup:

email me at tkyte@oracle.com

      Rating: 2
Talk to your
      DBA  June 04, 2002
Reviewer:  A reader
       from USA
I think P from LA should talk to his/her DBA.  Even though the DBA may not be
clear with some concepts, I am sure the DBA can still help.  Please let Tom
answer some questions that more people are interested in.  Thanks a lot!

      Rating:
      3
Seq.nextval  June 04, 2002
Reviewer:
      A reader
Tom,

I believe the problem is with the sequence generation. if we remove seq.nextval
and use a simple "rownum" then I did not get the error. what is the reason?

Thanks,

          Followup:

I saw the sequence myself.  I still could not reproduce the issue:

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop sequence t_seq;
Sequence dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create sequence t_seq;
Sequence created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t as select * from all_objects
where 1=0;
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t
  2  select OWNER, OBJECT_NAME, SUBOBJECT_NAME, t_seq.nextval,
  3         DATA_OBJECT_ID, OBJECT_TYPE, CREATED, LAST_DDL_TIME,
  4         TIMESTAMP, STATUS, TEMPORARY, GENERATED, SECONDARY
  5    from (select * from all_objects
  6          union all
  7          select * from all_objects
  8          union all
  9          select * from all_objects )
 10  /
69135 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select used_ublk from v$transaction
  2  /

 USED_UBLK
----------
         1

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

      Rating: 4
nologging and
      backup  June 04, 2002
Reviewer:  Pushparaj
      Arulappan  from Edison,New Jersey, USA
Tom,

Our Database is currently running in ARCHIVELOG mode.
Every Saturday we refresh 3 static tables with millions of
rows. Around 10 million rows for each table. We basically copy data from the
other database into our production database. For example, we use the following
command to do this.
COPY TO scott/tiger@D100 INSERT emp USING SELECT * FROM emp@D200 WHERE deptno
between 1 and 10;

These tables are currently created with logging mode.
SELECT logging FROM dba_tables WHERE table_name='EMP';
LOGGING
-------
YES

After refereshing these three tables the amount of archive log generation is
around 30 GB. All these three tables have a unique index.

The loss of data in these three tables is not a concern because we can bring the
data from the other database any time. And these three tables purely a read only
table.

To save the archiving space and even speed up this, I am planning to use the
Direct-load INSERT.

INSERT /*+ APPEND */ INTO emp
SELECT * FROM emp@D200 WHERE deptno between 1 and 10;

My main concern is: Question1:
1) After I load these tables with direct-load insert, should I have to take the
backup of the entire database (cold backup) or just taking the export of these
three tables alone is enough ?

Assume 05/01/2002 is my last full database backup and I have refereshed these
three tables using Direct-Load insert method on 05/08/2002 , 05/15/2002 and
05/22/02 and have only taken the export of these three tables and on 05/27/02
the database crashes due to some media failures.
Can I restore the database from 05/01/2002 backup and recover the database using
the archive logs untill 05/27/02 for the rest of the tables in the database
without any problem.

Question2:
Since redo for the indexes can not be avoided, I want to drop the indexes and
recreate them after the load. Is there any other good way ? ( I can do a QUERY
on the other database to see if the rows are unique before I insert into my
tables )

Question3:
Can I leave the tables in LOGGING mode and create the INDEX in NOLOGGING mode.
Is it advisable ?

Question4:
Would you reccommend to alter these three tables permenantly into NOLOGGING mode
? If I alter the tables into a NOLOGGING mode, do I need to take a FULL DATABASE
BACKUP after that?

Question5:
What is advantage of creating these three tables with NOLOGGING over LOGGING
mode.

Please give me any other advise that you think is good for my situation.

Thanks
Pushparaj

          Followup:

I would consider doing this:

a) the tables in question are in NOLOGGING mode all of the time

b) use the direct path insert to populate them -- do not use regular inserts
(like COPY).  Do not have any indexes

(that way, when loaded, they will not generate undo or redo)

c) create NON-UNIQUE indexes on the table (the columns you normally use in a
primary key).  When you add the indexes -- do it in PARALLEL with NOLOGGING

d) add the primary key with "exceptions into" -- you will capture duplicate rows
for cleaning up.  The primary key will "kidnap" the non-unique index and use it.

e) after doing these operations -- simply backup (hot backup) the affected
tablespaces (the tablespaces with the table / index data ).  Add that to your
backup set.

That is it.

      Rating: 4
How big are the
      rollback segment extents?  June 04, 2002
Reviewer:
      Mike  from Portland, OR
The error says it's failing at 20 extents.  How big is your extent size for the
rollback segment?

      Rating: 4
mr
      p  June 04, 2002
Reviewer:  A reader

mr p how can you have 20 as maxextents of RBS

ORA-01628: max # extents (20) reached for rollback segment RB3

LOL

      Rating: 5
The direct load
      INSERT /*+ APPEND */ does not work on one db  August 26,
      2002
Reviewer:  Sean Bu  from Atlanta
Tom,

The direct load of "Insert /*+ append */ .." does not work on one 8.1.7.3
database (which generated more redo than no-direct load) , but works on another
8.1.7.3 database on the same HP server. The both are on the same oracle home. I
could not figure the reasons of the differentail.

I have traced on the both sessions on the two databases. If you need them , I
can send your the raw files. By looking into the trace outputs of the both,
except the difference order of the statements shown in the trace files (the test
procedure were same for the two db), I have not enabled to justify the reasons
direct load not working on the RESUMIX database.

The test table t was in LMT on the both databases.

[oracle@ne:RESUMIX_8.1.7_64]$ sqlplus toolman

SQL*Plus: Release 8.1.7.0.0 - Production on Mon Aug 26 09:47:56 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Enter password:

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.3.0 - 64bit Production
With the Partitioning option
JServer Release 8.1.7.3.0 - 64bit Production

SQL> drop table t;

Table dropped.

SQL> create table t as select * from dba_objects where 0=1;

Table created.

SQL> set autotrace on stat

SQL>  insert into t select * from dba_objects;

4534 rows created.

Statistics
----------------------------------------------------------
          7  recursive calls
        121  db block gets
       2846  consistent gets
          0  physical reads
     430600  redo size
        646  bytes sent via SQL*Net to client
        469  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
       4534  rows processed

SQL> rollback;

Rollback complete.

SQL> insert /*+ append */ into t select * from dba_objects;

4534 rows created.

Statistics
----------------------------------------------------------
          2  recursive calls
         17  db block gets
       2786  consistent gets
          0  physical reads
     459848  redo size
        634  bytes sent via SQL*Net to client
        482  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
       4534  rows processed

SQL>exit

On the DM database, IT WORKS.

[oracle@ne:dm_8.1.7_64]$ sqlplus toolman

SQL*Plus: Release 8.1.7.0.0 - Production on Mon Aug 26 09:45:10 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Enter password:

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.3.0 - 64bit Production
With the Partitioning option
JServer Release 8.1.7.3.0 - 64bit Production

SQL> drop table t;

Table dropped.

SQL> create table t as select * from dba_objects where 0=1;

Table created.

SQL> set autotrace on stat
SQL> insert into t select * from dba_objects;

4663 rows created.

Statistics
----------------------------------------------------------
          9  recursive calls
        657  db block gets
       3953  consistent gets
          0  physical reads
     508488  redo size
        647  bytes sent via SQL*Net to client
        468  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
       4663  rows processed

SQL> rollback;

Rollback complete.

SQL> insert /*+ append */ into t select * from dba_objects;

4663 rows created.

Statistics
----------------------------------------------------------
         50  recursive calls
         24  db block gets
       3845  consistent gets
          0  physical reads
       2424  redo size
        633  bytes sent via SQL*Net to client
        482  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
       4663  rows processed

SQL> exit

          Followup:

You've got archivelogmode on and you didn't set the table to NOLOGGING.

read the very first comment on this page above for details....

      Rating:
      5
Thanks!  August 26, 2002
Reviewer:  Sean
      Bu

      Rating: 4
Just curious
      novice  August 26, 2002
Reviewer:  HS
       from USA
how do u display 'STATISTICS' report?

          Followup:

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on statistics

http://asktom.oracle.com/~tkyte/article1/autotrace.html

      Rating: 4
Relationship
      b/w logging/nologging and archive/noarchive mode-if
      any  August 26, 2002
Reviewer:  HS
       from USA
What is the 'official' oracle defined relationship between logging/nologging and
archive/noarchive mode of a database. Please explain.

Thanks in advance for your answer.

          Followup:

In noarchivelog mode, you need not ALTER the underlying object to have the
NOLOGGING attribute to perform a non-logged activity (since in noarchivelog
mode, we only need to suport instance recovery, not media recovery -- and we can
support that without generating the redo entries)

In archivelog mode, you must go out of your way to say "yes, I really mean to do
this".  It prevents "joe user" from doing a non-logged operation on some table
-- destroying its recoverability from backups.  "Joe user" cannot do an insert
/*+ append */ without generating redo unless "Joe user" can also alter the table
(something typically doable only by the DBA who really needs to be aware when
non-logged things happen so they can backup)

      Rating: 5
db mode, table
      mode and insert mode - differences  August 27, 2002

Reviewer:  A reader
Tom,

I used to have confusion about the nologging option. Once I did an experiment,
everything bEcame clear. As given below, there are 8 different possibilites with
db mode, table mode and insert mode.

Here is the summary

Case    DB Mode         Table Mode      Insert Mode     Redo Size  Undo Size
======  ===========     ==========      ============    =========  =========
1       Archivelog      Logging         No append       17373988   73
2       Archivelog      Nologging       No append       17374140   74
3       Archivelog      Logging         Append          17462504    1
4       Archivelog      Nologging       Append            138496    1
5       NoArchivelog    Logging         No append       17383188   73
6       NoArchivelog    Nologging       No append       17379244   74
7       NoArchivelog    Logging         Append            138824    1
8       NoArchivelog    Nologging       Append            139324    1

Here are the details:
=====================
SQL> select log_mode from v$database;

LOG_MODE
------------
ARCHIVELOG

SQL>
SQL> drop table t;

Table dropped.

SQL> create table t as select * from all_objects where 1 = 0;

Table created.

SQL> select logging from user_tables where table_name = 'T';

LOG
---
YES

SQL> set autotrace on statistics
SQL> insert into t select * from all_objects ;

159568 rows created.

Statistics
----------------------------------------------------------
       8341  recursive calls
      12760  db block gets
    1679474  consistent gets
       4076  physical reads
   17373988  redo size
        860  bytes sent via SQL*Net to client
        566  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
     159568  rows processed

SQL> set autotrace off
SQL> select used_ublk from v$transaction;

 USED_UBLK
----------
        73

SQL>
SQL> drop table t;

Table dropped.

SQL> create table t nologging as select * from all_objects where 1 = 0;

Table created.

SQL> select logging from user_tables where table_name = 'T';

LOG
---
NO

SQL> set autotrace on statistics
SQL> insert into t select * from all_objects ;

159568 rows created.

Statistics
----------------------------------------------------------
       9005  recursive calls
      12769  db block gets
    1680085  consistent gets
       3585  physical reads
   17374140  redo size
        862  bytes sent via SQL*Net to client
        566  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
     159568  rows processed

SQL> set autotrace off
SQL> select used_ublk from v$transaction;

 USED_UBLK
----------
        74

SQL>
SQL> drop table t;

Table dropped.

SQL> create table t as select * from all_objects where 1 = 0;

Table created.

SQL> select logging from user_tables where table_name = 'T';

LOG
---
YES

SQL> set autotrace on statistics
SQL> insert /*+ append */ into t select * from all_objects ;

159568 rows created.

Statistics
----------------------------------------------------------
       9309  recursive calls
       1101  db block gets
    1678076  consistent gets
       3599  physical reads
   17462504  redo size
        847  bytes sent via SQL*Net to client
        580  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
     159568  rows processed

SQL> set autotrace off
SQL> select used_ublk from v$transaction;

 USED_UBLK
----------
         1

SQL>
SQL> drop table t;

Table dropped.

SQL> create table t nologging as select * from all_objects where 1 = 0;

Table created.

SQL> select logging from user_tables where table_name = 'T';

LOG
---
NO

SQL> set autotrace on statistics
SQL> insert /*+ append */ into t select * from all_objects ;

159568 rows created.

Statistics
----------------------------------------------------------
       9387  recursive calls
       1103  db block gets
    1678141  consistent gets
       3557  physical reads
     138496  redo size
        847  bytes sent via SQL*Net to client
        580  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
     159568  rows processed

SQL> set autotrace off
SQL> select used_ublk from v$transaction;

 USED_UBLK
----------
         1

SQL> select log_mode from v$database;

LOG_MODE
------------
NOARCHIVELOG

SQL>
SQL> drop table t;

Table dropped.

SQL> create table t as select * from all_objects where 1 = 0;

Table created.

SQL> select logging from user_tables where table_name = 'T';

LOG
---
YES

SQL> set autotrace on statistics
SQL> insert into t select * from all_objects ;

159568 rows created.

Statistics
----------------------------------------------------------
      10499  recursive calls
      12771  db block gets
    1681361  consistent gets
       6052  physical reads
   17383188  redo size
        853  bytes sent via SQL*Net to client
        566  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          4  sorts (memory)
          0  sorts (disk)
     159568  rows processed

SQL> set autotrace off
SQL> select used_ublk from v$transaction;

 USED_UBLK
----------
        73

SQL>
SQL> drop table t;

Table dropped.

SQL> create table t nologging as select * from all_objects where 1 = 0;

Table created.

SQL> select logging from user_tables where table_name = 'T';

LOG
---
NO

SQL> set autotrace on statistics
SQL> insert into t select * from all_objects ;

159568 rows created.

Statistics
----------------------------------------------------------
       8862  recursive calls
      12769  db block gets
    1679977  consistent gets
       3824  physical reads
   17379244  redo size
        857  bytes sent via SQL*Net to client
        566  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
     159568  rows processed

SQL> set autotrace off
SQL> select used_ublk from v$transaction;

 USED_UBLK
----------
        74

SQL>
SQL> drop table t;

Table dropped.

SQL> create table t as select * from all_objects where 1 = 0;

Table created.

SQL> select logging from user_tables where table_name = 'T';

LOG
---
YES

SQL> set autotrace on statistics
SQL> insert /*+ append */ into t select * from all_objects ;

159568 rows created.

Statistics
----------------------------------------------------------
       9852  recursive calls
       1108  db block gets
    1678166  consistent gets
       3653  physical reads
     138824  redo size
        843  bytes sent via SQL*Net to client
        580  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
         15  sorts (memory)
          0  sorts (disk)
     159568  rows processed

SQL> set autotrace off
SQL> select used_ublk from v$transaction;

 USED_UBLK
----------
         1

SQL>
SQL> drop table t;

Table dropped.

SQL> create table t nologging as select * from all_objects where 1 = 0;

Table created.

SQL> select logging from user_tables where table_name = 'T';

LOG
---
NO

SQL> set autotrace on statistics
SQL> insert /*+ append */ into t select * from all_objects ;

159568 rows created.

Statistics
----------------------------------------------------------
       9731  recursive calls
       1104  db block gets
    1678440  consistent gets
       3405  physical reads
     139324  redo size
        844  bytes sent via SQL*Net to client
        580  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
     159568  rows processed

SQL> set autotrace off
SQL> select used_ublk from v$transaction;

 USED_UBLK
----------
         1

      Rating: 4
Confusion
      clearing up but...  August 27, 2002
Reviewer:
      HS  from USA
Tom here what you said :
"In noarchivelog mode, you need not ALTER the underlying object to have the
NOLOGGING attribute to perform a non-logged activity (since in noarchivelog
mode, we only need to suport instance recovery, not media recovery -- and we can
support that without generating the redo entries)

In archivelog mode, you must go out of your way to say "yes, I really mean to do
this".  It prevents "joe user" from doing a non-logged operation on some table
-- destroying its recoverability from backups.  "Joe user" cannot do an insert
/*+ append */ without generating redo unless "Joe user" can also alter the table
(something typically doable only by the DBA who really needs to be aware when
non-logged things happen so they can backup)"

From this I infer the following :
(A)In 'noarchive log' mode, logging/nologging attribute of an object has no
effect. As Oracle will perform only instance recovery and this can be achieved
without logged entries. Hence my question is ;
if i have logging option enabled for objects in 'noarchive log' mode database
this means I am asking Oracle to do more work for nothing. hence I should have
nologging option enabled for a database in noarchive log mode?

(B)And for a database in archive log mode all oobjects should have logging
option enabled-default. I do it at the tablespace level and let objects inherit
this attribute.

And finally what operations are logged anyway regardless of logging/nologging
option? i am thinking all DML, is this correct.

Appreciate your answer. thank you.

          Followup:

(A) no, 100% wrong.

It means that in NOARCHIVELOG mode, whenever we can, we skip redo generation.
If you say "insert /*+ append */ into t select ...", in noarchivelog mode, we
KNOW we can safely skip redo generation and hence we do.

(B) correct.

Everything is logged EXCEPT for statements that listed above in the original
question

      Rating: 5
Thanks and
      follow up  August 27, 2002
Reviewer:  HS
       from USA
Tom,
with reference to my earlier question bullet (A) and your answer, i have to ask
that why would i want to have logging option enabled if oracle can recover the
instance with nologging option.
I am thinking in terms of my test/QA env. where we drop/create indexes left and
right in a datawarehouse situation. Prod DB is in archive log mode.

Thanks again.

          Followup:

It doesn't matter, you don't need to have it on, or off -- in noarchive log mode
it is not very meaningful.

So, don't rush out there and turn it "off", it won't do anything for you.  It is
already as good as off.  the potential damage could be that the nologging
attribute finds its way into production by accident.  we don't want that to
happen!

      Rating: 5
Perfect - thank
      you and unrelated comment :)  August 27, 2002

Reviewer:  HS  from USA
Hey Tom,
I understand that this is a pro. site and I would like it to be that way
also.However did you guys think about adding some fun things like graphical
expressions, theme's like beach, ski, nature etc?

I think it will spice up a things little bit in a good different way....:)and
would be nice to express ourselves in a different way....

          Followup:

If I had my way, this site would be black text on a white background with links
and no pictures whatsoever....

Themes -- no, I doubt I would be going down that path.  My favorite font is
Courier, images -- find them just getting the way.

      Rating: 5
nologging and
      standby database  August 27, 2002
Reviewer:
      Logan Palanisamy  from Santa Clara, CA, USA
Tom,

If we perform nologging operations such as "insert /*+append +/ select .." and
"alter index ... rebuild nologging" on a production database in archive log mode
with standby database, there is no archive log generated for them. Then how does
the standby database syncup with the production db? Will the standby database
have this data? If yes, how?

          Followup:

No, it won't -- that is exactly why using NOLOGGING should be left upto the
experienced DBA who is aware of such issues and will make sure that if they do
happen (cause they want it to) they fix the standby

Note in 9iR2, you can make it so that even if

a) nologging is on the table
b) you do a non-loggable thing (like append)
c) the database is in archive log mode

we ignore you and generate redo log anyway (for just such total protection).

      Rating: 5
nologging and
      standby  August 27, 2002
Reviewer:  Logan
      Palanisamy  from Santa Clara, CA, USA
Does it then mean, there is no such thing as non-loggable operation in 9iR2?
Redo is generated for everything including 'nologging' operations and tables.

          Followup:

No, it means there is the ability for the DBA to make it so that everything is
logged.

If the DBA doesn't do that, caveat emptor -- you are back to where you began.

      Rating:
      4
Reader  August 27, 2002
Reviewer:
      A reader
Tom

There is no "direct read buffer" parameter in Oracle 8i.
How to tune the direct load process. Are there parameters
relevent to "direct load/insert" process which will be
helpful for us to comprehend

Thanks

          Followup:

not familar with "direct_read_buffer" -- what tool/version did you use this
with?

      Rating:
      4
Reader  August 28, 2002
Reviewer:
      A reader
Tom,

I was actually referring to the init<SID>.ora
parameter. I have read it in the article
"The Mysteries of DBWR Tuning" by Steve Adams

<quote>
From version 7.1.5 onwards, this (DBWR load) can be
avoided by using the parallel query option to force blocks to be read into
"direct read buffers", rather than into
the database buffer cache
<quote>

Thanks

          Followup:

It is *not* a parameter -- it is an "activity" if you will.

Sort reads - use direct read buffers (eg: no need to use the buffer cache and
read consistency -- we KNOW you are the only one that owns these blocks and the
only one who will use them -- putting them into the LRU would be pointless and
expensive)

Parallel query -- we can do a global checkpoint before running the query to
flush the dirty blocks to disk.  Now, PQ can bypass the buffer cache and just
read from disk -- not putting stuff in the cache, not wasting time looking in
the cache for data.  It just "happens" tho, there is no "parameter"

      Rating:
      5
Reader  September 01, 2002
Reviewer:
      A reader
Tom,

I have a table t with 104,190 rows created from DBA_OBJECTS
The HWM is 1287 blocks. Arraysize is set at default
I performed two queries
    1. do an insert update owner = owner
    2. select /*+ parallel(t 4) */  * from t;
For Each of this statement, before and after the statement
    1. I found the blocks that are dirty
    2. I gathered statistics from v$sysstat DBWR%, and
       "table scan%" and found the delta

1. When I did parallel query, DBWR checkpoint buffers witten is only 46
   where as v$bh.dirty = 103. Physical reads however shows 1292 close to
   HWM which is correct
2. When I did parallel query, there were 2628 CR and 428 block gets
   What are these used for

Thanks

SQL*Plus: Release 8.1.7.0.0 - Production on Sun Sep 1 07:53:24 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production
With the Partitioning option
JServer Release 8.1.7.0.0 - Production

SQL>  select count(*), dirty from v$bh group by dirty;

  COUNT(*) D
---------- -
      1247 N
        33 Y

SQL> insert into stats_t select'first',statistic#, name,class,value from stats;

18 rows created.

Update the table T
==================
SQL> update t set owner = owner;

104192 rows updated.

SQL> commit;

Commit complete.

SQL> insert into stats_t select'next',statistic#, name,class,value from stats;

18 rows created.

SQL> select count(*), dirty from v$bh group by dirty;

  COUNT(*) D
---------- -
      1177 N
       103 Y

SQL> select f.name, f.value, n.value, n.value-f.value from ....

SQL> /

NAME                                      VALUE      VALUE N.VALUE-F.VALUE
---------------------------------------- ---------- ---------- -------------
DBWR buffers scanned                           0        250             250
DBWR checkpoint buffers written                5       2459            2454
DBWR checkpoints                               1         25              24
DBWR cross instance writes                     0          0               0
DBWR free buffers found                        0        250             250
DBWR lru scans                                 0          1               1
DBWR make free requests                        0          1               1
DBWR revisited being-written buffer            0          0               0
DBWR summed scan depth                         0        250             250
DBWR transaction table writes                  0         32              32
DBWR undo block writes                         0       1152            1152
table scan blocks gotten                     649       1945            1296
table scan rows gotten                     54684     158883          104199
table scans (cache partitions)                 0          0               0
table scans (direct read)                      0          0               0
table scans (long tables)                     42         43               1
table scans (rowid ranges)                     0          0               0
table scans (short tables)                    33         34               1

18 rows selected.

SQL> truncate table stats_t;

Table truncated.

Select from Table t in Parallel Mode
====================================
SQL>select /*+ parallel(t 4) */  * from t;

104192 rows selected.

Elapsed: 00:00:19.29

Execution Plan
----------------------------------------------------------

   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=196 Card=105123 Byte

          s=19973370)

   1    0   TABLE ACCESS* (FULL) OF 'T' (Cost=196 Card=105123 Bytes=19 :Q17000

          973370)

   1 PARALLEL_TO_SERIAL            SELECT /*+ Q17000 NO_EXPAND ROWID(A1) */ A1.

                                   "SEQ#",A1."OWNER",A1."OBJECT_NAME",A

Statistics
----------------------------------------------------------

        567  recursive calls

        428  db block gets

       2628  consistent gets

       1292  physical reads

        684  redo size

   11575319  bytes sent via SQL*Net to client

     771431  bytes received via SQL*Net from client

       6948  SQL*Net roundtrips to/from client

          7  sorts (memory)

          0  sorts (disk)

     104192  rows processed

SQL> select count(*), dirty  from v$bh group by dirty;

  COUNT(*) D
---------- -
      1202 N
        78 Y

SQL> select f.name, f.value, n.value, n.value-f.value from ....

NAME                                         VALUE      VALUE N.VALUE-F.VALUE
---------------------------------------- ---------- ---------- -------------
DBWR buffers scanned                          250        250               0
DBWR checkpoint buffers written              2459       2505              46
DBWR checkpoints                               25         27               2
DBWR cross instance writes                      0          0               0
DBWR free buffers found                       250        250               0
DBWR lru scans                                  1          1               0
DBWR make free requests                         1          1               0
DBWR revisited being-written buffer             0          0               0
DBWR summed scan depth                        250        250               0
DBWR transaction table writes                  32         32               0
DBWR undo block writes                       1152       1152               0
table scan blocks gotten                     1958       3265            1307
table scan rows gotten                     159034     263254          104220
table scans (cache partitions)                  0          0               0
table scans (direct read)                       0         52              52
table scans (long tables)                      43         95              52
table scans (rowid ranges)                      0         52              52
table scans (short tables)                     39         58              19

SQL> select table_name , blocks from dba_tables where table_name = 'T';

TABLE_NAME                         BLOCKS
------------------------------ ----------
T                                    1287

          Followup:

and this relates to logging vs nologging how??

      Rating:
      2
Reader  September 02, 2002
Reviewer:
      A reader
I agree , it is not directly related.
I thought nologging relates to => direct insert
            direct insert relates to => direct read
              direct read relates to => parallel query

BTW, I figured it out by joining v$bh and dba_tables on
file_id and block_id where segment_name = 'T' and found
that all the buffers used by 'T' were flushed to disk

Thanks

          Followup:

Thats like the 7 degrees of Kevin Bacon there.

      Rating: 5
rollback and
      index maintenance  September 17, 2002
Reviewer:
      A reader
Hi Tom

How come direct insert generates minimum ROLLBACK?

Also in a nologging direct insert why index still generates so much redo?

          Followup:

a direct path insert writes above the high water mark of the table.  In order to
"rollback" such an insert -- all we have to do is push the high water mark back
to where it was.  We don't need to go back to each block and undo the insert --
we just undo the advancing of the high water mark on the table and wah-lah -- it
is undone.

An index is a complex data structure, unlike a HEAP table.  Hence, when we add
something to an index -- that data goes into the MIDDLE of the structure (eg:
you add 'B' to an index containing 'A' and 'C' -- 'B' goes in between A and C).
So, in the index, we are maintaining a data structure, not just adding at the
"end".  So, in order to rollback we do need to go back and undo the inserts (we
need the rollback data).  We cannot simply "unadvance a high water mark" and in
effect erase that which we added like we can with a heap table.

      Rating: 5
Direct path
      insert/Insert - Append : Aren't they same  September 17,
      2002
Reviewer:  Logan Palanisamy  from Santa Clara,
      CA, USA
Tom,

Are the "direct path insert" and "Insert /* +Append */" the same or different?

I thought the "insert /* +Append */" inserts into a brand new extent, rather
than above the HWM. You are saying "direct path insert" inserts above HWM?
Inserting above HWM doesn't necessarily mean inserting into a brand new extent.

Would appreciate if you could highlight the differences?

          Followup:

inserts above the HWM and it is known as a direct path insert.

Correct, it does not imply a brand new extent.

Read all about it:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/c21dlins.htm#365
....

      Rating: 4
Direct mode
      insert and index maintenance  September 17, 2002

Reviewer:  Rob  from Pittsburgh, PA
Tom:

This is from the Oracle doc referenced by your link.

Index Maintenance
For direct-load INSERT on nonpartitioned tables or partitioned tables that have
local or global indexes, index maintenance is done at the end of the INSERT
operation. This index maintenance is performed by the parallel execution servers
for parallel direct-load INSERT or by the single process for serial direct-load
INSERT on partitioned or nonpartitioned tables.

If your direct-load INSERT modifies most of the data in a table, you can avoid
the performance impact of index maintenance by dropping the index before the
INSERT and then rebuilding it afterwards.

My question is:

What is the "performance impact" that is avoided by dropping the index before
the INSERT and then rebuilding it afterward. Since index maintenance is done at
the end of the INSERT operation by the parallel execution servers for parallel
direct-load INSERT, it seems like the index is maintained in parallel. Is the
performance advantage achieved by recreating or rebuilding the index with
nologging? Is there any other advantage?

          Followup:

there can be significant advantages AND disadvantages.

some (not all, i'm sure someone will add something else -- they always do)
advantages:

o you can rebuild the index with nologging on (no rollback, no redo for the
index maintenance)

o you can rebuild the index in parallel

o you get a nicely compacted index at the end

some disadvantages:

o if you added 100meg to a 5 gig table, rebuilding the index might take
significant amounts of time

o the index is unavailable -- meaning the table is "offline" sort of during this
entire load/reindex operation (this index just won't be there)

      Rating:
      4
Clarification  September 17, 2002

Reviewer:  Rob  from Pittsburgh, PA
This index maintenance is performed by the parallel execution servers for
parallel direct-load INSERT

One of the advantages you mention concerns rebuilding the index in parallel.
What are the parallel execution servers (note the plural) mentioned above doing
if not rebuilding the index in parallel?

          Followup:

They are MAINTAINING the index in parallel (during a parallel direct path
insert)

If you REBUILD and index in parallel, that is much different from MAINTAINING
and index (adding new entries to existing index) in parallel.

REBUILD = full scan table in parallel (since index doesn't have all of the
data), sort it and build new index.

MAINTAIN = build your little index pieces for the data you inserted and then add
it to the existing index in bulk at the end.

      Rating: 3
One more
      clarification   September 17, 2002
Reviewer:
      Rob  from Pittsburgh, PA
Tom:

Sorry to be a nag, I guess I am being dense about this but I am not
understanding the maintain process.

1) If the index is maintained after all of the rows are inserted into the table,
what info is telling the process what rows in the index to maintain. Does it
know based upon the rows that have been inserted into the table but not
committed?
3) If multiple processes are updating the index why is it slower than multiple
processes building a new index.

MAINTAIN = build your little index pieces for the data you inserted and then add
it to the existing index in bulk at the end.

          Followup:

1) yes, the rows it processed are the ones it is maintaining -- it writes above
the HWM to the table and creates some index data off to the side.  it'll merge
that in after the fact, when the insert is done.

3) didn't say that it would be did I?  If you add 100m to a 5gig table, it would
probably be faster to maintain.

if you add 5 gig to a 100meg table, it would probably be faster to rebuild.

      Rating: 3
Redo
      Size  February 25, 2003
Reviewer:  John
       from Sydney, Australia
Hi Tom,

I'm inserting about 30M records into a truncated NOLOGGING table using INSERT
/*+ APPEND */ after dropping all unique indexes, marking all other indexes as
unusable (and altering session) and disabling all constraints. The statistics
are telling me I'm generating about 180Mb of redo. Without the APPEND it's about
850Mb. What I'm keen to know is if 180Mb is really as small as the redo gets.
What size redo would you expect for this insert?
Is the redo because of the generation of a sequence number, managing the
extents, marking the inserted rows, or ... something else?

Your comments would be appreciated.

          Followup:

You may well have missed an index.  It could be recursive SQL due to a sequence
(alter your sequences and set the cache = 100,000 or more -- it'll go MUCH
faster as well).

redo for recursive sql (auditing, sequences, anything you call as a side effect)
redo for space mgmt (extents, quotas, etc)

You can use log miner to see more clearly what the redo is.

      Rating: 5
Problem in
      Creating Database  March 04, 2003
Reviewer:
      MWG  from Germany
Hi Tom,

I have a script to create database, as

startup nomount pfile="e:\oracle\ora92\database\initTESTORA.ora";
CREATE DATABASE TESTORA  controlfile reuse
LOGFILE GROUP 1 ('e:\oracle\oradata\TESTORA\REDO\redo01.log',
'z:\oracle\MIRLOG\TESTORA\redo01.log') SIZE 10M,
 GROUP 2 ('e:\oracle\oradata\TESTORA\REDO\redo02.log',
'z:\oracle\MIRLOG\TESTORA\redo02.log') SIZE 10M,
 GROUP 3 ('e:\oracle\oradata\TESTORA\REDO\redo03.log',
'z:\oracle\MIRLOG\TESTORA\redo03.log') SIZE 10M
MAXINSTANCES 1
MAXLOGHISTORY 1
MAXLOGFILES 50
MAXLOGMEMBERS 5
MAXDATAFILES 100
DATAFILE 'e:\oracle\oradata\TESTORA\SYS\system01.dbf' SIZE 300M REUSE AUTOEXTEND
ON NEXT  10240K MAXSIZE UNLIMITED
UNDO TABLESPACE "undotbs01" DATAFILE 'e:\oracle\oradata\TESTORA\UNDO\undo01.dbf'
SIZE 500M REUSE AUTOEXTEND ON NEXT  5120K MAXSIZE UNLIMITED
DEFAULT TEMPORARY TABLESPACE TEMP TEMPFILE
'e:\oracle\oradata\TESTORA\TEMP\temp01.dbf' SIZE 55M REUSE AUTOEXTEND ON NEXT
640K MAXSIZE UNLIMITED
CHARACTER SET WE8ISO8859P15
NATIONAL CHARACTER SET AL16UTF16;

spool off

spool e:\oracle\ora92\assistants\dbca\logs\CreateDBFiles.log
CREATE TABLESPACE "USERS" LOGGING DATAFILE
'e:\oracle\oradata\TESTORA\USR\users01.dbf' SIZE 10M AUTOEXTEND ON NEXT 1M
MAXSIZE UNLIMITED EXTENT MANAGEMENT LOCAL;
CREATE TABLESPACE "RBS" LOGGING DATAFILE
'e:\oracle\oradata\TESTORA\RBS\rbs01.dbf' SIZE 20M AUTOEXTEND ON NEXT 1M MAXSIZE
UNLIMITED EXTENT MANAGEMENT LOCAL;
CREATE TABLESPACE "INDX" LOGGING DATAFILE
'e:\oracle\oradata\TESTORA\INDX\indx01.dbf' SIZE 50M AUTOEXTEND ON NEXT 1M
MAXSIZE UNLIMITED EXTENT MANAGEMENT LOCAL;
spool off

spool e:\oracle\ora92\assistants\dbca\logs\CreateDBCatalog.log
@e:\oracle\ora92\rdbms\admin\catalog.sql;
@e:\oracle\ora92\rdbms\admin\catexp7.sql;
@e:\oracle\ora92\rdbms\admin\catblock.sql;
@e:\oracle\ora92\rdbms\admin\catproc.sql;
@e:\oracle\ora92\rdbms\admin\catoctk.sql;
@e:\oracle\ora92\rdbms\admin\owminst.plb;
exit;

My question is, After I create database with this script, and try to connect to
the database, then it says, 'sys user doesn't have create session privilege'.

Whats wrong with my script?? could you please help me.

thank you in advance.

MWG from Germany

          Followup:

You should not use SYS (or system for that matter) anyway.  Probably best you
cannot log in with SYS, that is our account, you don't need to use it (in fact
9i makes it excessively hard by default to use it at all!)

connect ... AS SYSDBA

should do it.

      Rating:
      5
  March 06, 2003
Reviewer:  MWG
       from Germany
Hi Tom,

Thanks for the earlier reply.

But Tom, as I want to run the pupbld.sql, so I do need to connect as system. but
with my script, I cannot connect to system ( because, system doesn't have create
session privilege), so I connect to system as sysdba and then ran the
pupbld.sql, but after that, when I try to connect to another user, then oracle
says, "pupbld.sql should run when connecting to system, therefore cannot load
data to SQLPLUS_PRODUCT_PROFILE".

So could you please tell what I have done wrong???

Thanks,

MWG from Germany

          Followup:

you didn't follow the directions to run it as system.

run it as system as it says.  fix your system account, run it, unfix the system
account.

      Rating: 5
Doubt
        May 30, 2003
Reviewer:  Cefers.br
       from Brasil
I read the Oracle Documentation link you mentioned above and I found that:

[...]
Advantage over Parallel Direct Load (SQL*Loader)

Direct-path INSERT operations ensure atomicity of the transaction.
[...]

Can you explain what is ATOMICITY of the transaction?

Thanks in advance...

          Followup:

it either happens entirely or not at all.  It is a single statement.  it is
atomic.

      Rating: 5
how can i
      prevent DML from SQL*PLUS  July 14, 2003
Reviewer:
      MUBARAK MUNSOUR ELNOUR  from yeman
Dear
Tom

what is the value taht i insert into TABLE sqlplus_product_profile to prevent
any DML OPERATION FROM SQL*PLUS

THANKS

MUBARAK

          Followup:

http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a90842/ch10.htm#1005767
fortunately, we document all of this stuff!

      Rating: 4
Regarding /*+
      APPEND */ problem by P from LA   July 14, 2003

Reviewer:  A reader  from MN, USA
Hi Tom,
  I was just browsing this thread and when I came to the problem face by "P from
LA" I could see that he was giving the hint in his query as "/*+APPEND*/" no
SPACE between the + and APPEND. I suppose that his hint was never used and hence
it was doing a conventional path insert using the RBS and generating REDO. hence
his inserts were failing. Am I right in this regard? Or was his problem solved
by you offline?

Thanks

          Followup:

that space after the + was only needed in earlier releases and then only inside
PLSQL -- did not apply

the problem was the sequence and the massive recursive sql taking place.
altering the sequence to have a much larger cache size not only worked around it
but made it lots faster

      Rating: 5
Thank you.. I
      tried it on 9iR2 and it worked without the spaces as well
      :)  July 15, 2003
Reviewer:  A reader

      Rating: 4
NOLOGGING at
      Tablespace level  July 16, 2003
Reviewer:
      A reader
Hi Tom,
Can I get any improvement in performance by placing tablespace in NOLOGGING mode
for DW application running in NOARCHIVE mode?

          Followup:

not really -- the operations such as

create table as select
insert /*+ APPEND */
direct path loads

will already skip logging in a noarchivelog mode database as they do direct
writes to the datafiles

other operations such as insert/update/delete/merge will always generate redo
regardless of the mode

      Rating: 5
Why this small
      difference?  July 17, 2003
Reviewer:
      A reader
Tom,
Thanks for your quick response. But I am noticing small difference as below:
SQL> select log_mode from v$database;

LOG_MODE
------------
NOARCHIVELOG

SQL> select substr(segment_name,1,30), tablespace_name from user_segments where
tablespace_name = 'TEST_TS';

no rows selected

SQL> select LOGGING from user_tablespaces where  tablespace_name = 'TEST_TS';

LOGGING
---------
NOLOGGING

SQL> create table test tablespace test_ts as select * from dba_objects where
1=0;

Table created.
SQL> set autotrace on statistics
SQL> insert into test select * from dba_objects;

8733 rows created.

Statistics
----------------------------------------------------------
          9  recursive calls
        350  db block gets
      10304  consistent gets
        219  physical reads
     945336  redo size
        652  bytes sent via SQL*Net to client
        470  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
       8733  rows processed
SQL> rollback;

SQL> alter tablespace TEST_TS logging;

Tablespace altered.

SQL> select LOGGING from user_tablespaces where  tablespace_name = 'TEST_TS';

LOGGING
---------
LOGGING
SQL> insert into test select * from dba_objects;

8733 rows created.

Statistics
----------------------------------------------------------
         35  recursive calls
        362  db block gets
      10302  consistent gets
          0  physical reads
     947424  redo size
        653  bytes sent via SQL*Net to client
        470  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
       8733  rows processed

          Followup:

that is totally insignificant -- the extra redo could happen for any number of
reasons -- eg: recursive sql executed in order to grab a new extent.

nologging would take the redo down to a really small number, it is not coming
into play here AT ALL

      Rating: 4
SQL*Loader
      Direct Load  October 04, 2003
Reviewer:  K
       from CA
SQL*Loader Direct LOAD bypasses the archive logs.  If that is the case how do
one replicate the data in contigency database ie 8i standby.  Secondly is there
a command to find out in primary and in standby database to see if nologging
operation was performed on data load and on on any DML performed.  If so can you
please explain with an example of how it works.  You have mentioned in 9i even
with nologging on table level Oracle ignores and generates redo log anyways for
total protection. Is there a command to be set on primary or contigency or is it
by default setting in Oracle 9i.

Thanks

          Followup:

no it doesn't.

only if the object was marked as NOLOGGING in the database AND the database
allows for nonlogged operations.

a direct path load does not bypass the redo logs.  not unless you go way way out
of your way to do so -- and in a standby environment -- you would not "do so"

      Rating: 5
IS APPEND
      WORKING ?  November 10, 2003
Reviewer:  Abhi
      malhotra  from INDIA
hello sir,

You have given comment in the middle of this discussion.

"
Note in 9iR2, you can make it so that even if

a) nologging is on the table
b) you do a non-loggable thing (like append)
c) the database is in archive log mode

we ignore you and generate redo log anyway (for just such total protection).
"

----------------------------------------------------------
SQL> select log_mode from v$database;

LOG_MODE
------------
ARCHIVELOG

SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
PL/SQL Release 9.2.0.1.0 - Production
CORE    9.2.0.1.0       Production
TNS for 32-bit Windows: Version 9.2.0.1.0 - Production
NLSRTL Version 9.2.0.1.0 - Production

SQL> conn j/j
Connected.
SQL> create table a as select * from dba_objects where rownum<1;

Table created.

SQL> set autot on stat
SQL> insert into a select * from dba_objects;

21947 rows created.

Statistics
----------------------------------------------------------
        429  recursive calls
       7397  db block gets
      20771  consistent gets
        737  physical reads
    3041696  redo size                      <<--- First Time Redo

SQL> truncate  table a;

Table truncated.

SQL>
SQL> insert /*+APPEND*/ into a select * from dba_objects;

21947 rows created.

Statistics
----------------------------------------------------------
        357  recursive calls
        224  db block gets
      19504  consistent gets
          3  physical reads
    2779828  redo size                     <<--- Second Time Redo

SQL> truncate table a;

Table truncated.

SQL> alter table a nologging;

Table altered.

SQL> insert /*+APPEND*/ into a select * from dba_objects;

21947 rows created.

Statistics
----------------------------------------------------------
        528  recursive calls
        225  db block gets
      19527  consistent gets
          0  physical reads
      24308  redo size          <<--- Third time very less Redo

Q1.

Is there  --APPEND--   working with 9iR2,
                           Database in Archivelog mode,
                           Table is in Nologging ?

Q2.
    Why There diff. with First Time and Second Time Redo.

Thanx
Abhi

          Followup:

q1) your database wasn't altered to force logging

ops$tkyte@ORA920PC> select force_logging from v$database;

FOR
---
NO

q2)
insert append and insert work differently, i would expect them to do different
things.  one writes above the high water mark, maintains indexes in bulk. The
other does not.  you cannot compare them.

November 10, 2003 Leo -- Thanks for the question regarding "Book
      update", version 9

      You Asked
Are you planning a new version of your book, covering 9i topics? And if so, when
will it be available?

      and we said...
In august there will be another book:

http://www.amazon.com/exec/obidos/tg/detail/-/0072230657/
It is not "Expert One on One Oracle version 2"..  It is different.

I do expect to to "version 2" of Expert One on One perhaps as early as the first
half of 2004
Review & Followup

      Rating: 4
Differences to
      version 1 ?  June 26, 2003
Reviewer:  Lars
       from Germany
Hello Tom,

do you plan to redesign version 2 completely or will it be a kind of update due
to new features in Oracle DBs in the meantime ?

          Followup:

new publisher,

new database versions,

many changes.

It'll be the same style, same format, but a fairly large rewrite as many things
have changed/been added since 2001

      Rating: 5
Why Oracle
      Press  June 26, 2003
Reviewer:
      A reader
I hope you will keep the same format as expert one on one.
I know the contents will be excellant since its your book.

Oracle Press books in general are just high price Trash.
It just looks good.

      Rating:
      5
Awesome!  June 26, 2003
Reviewer:  Leo
      Foretich  from Baton Rouge, LA
Tom,

I will forward this to our other DBAs, and I am sure our agency will order a
stack of them.

Now, any way to get them autographed?  ...just kidding.

Thanks again,
Leo

      Rating: 5
expert one on
      one v1.0  November 10, 2003
Reviewer:
      A reader
Hi

I dont rememeber where but did you mention by any chance an electronic version
of your first book? I like the book very much, I try to carry them to where I go
but it is impossble! It is HUGE... Any time soon for the electronic version? Do
us, the owner of book have to pay any charge?

Also is v2.0 of this book going to be released any time soon?

TIA

          Followup:

I do not own the book.

the publisher that did went out of business.

it was bought by apress.  I have no control over it at all.

we were going to release all/some of it on CD with my newer book -- but since
then went out of business and I switched publishers, that is not possible.

      Rating: 5
Suggestion for
      a new book...  November 10, 2003
Reviewer:
      A reader
Tom:
I own both of your books (the second one is on its way).  But I am not the kind
of person who can sit and read 1000+ page from start to finish. I do read
sections of them at a time.  I tend to use your web site more often and I do
learn a lot from it.  How about doing a book with a FAQ format based on
questions most often asked at this web site?
Thanks!

      Rating: 5
the faq is
      already there  November 10, 2003
Reviewer:
      A reader
click on the tab "FAQ by Category" on asktom home page.

November 10, 2003 Maik -- Thanks for the question regarding
      "Sorting groups", version 8i

      You Asked
Hi Tom,

i will sort the following resultset (a grouped select):

select col1, col2, count(col3) from tab group by col1, col2;

col1    col2    count(col3)
A    1    7
A    2    14
B    1    3
B    2    18

This resultset should now be sorted on col3 desc, but it shoud be group-wise:

col1    col2    count(col3)
B    1    3
B    2    18
A    1    7
A    2    14

A and B are different groups. I need an order, where the group with the highest
count(col3) for col2=2 is first, but within the groups the order shoud be on
col1, col2!

It is possible with and without analytic functions (and how ...)??

Thanks a lot,
Maik

      and we said...
with and without:

ops$tkyte@ORA920LAP> select col1, col2, cnt_col3
  2    from (
  3  select col1, col2, count(col3) cnt_col3
  4    from t
  5   group by col1, col2
  6         )
  7   order by max(cnt_col3) over (partition by col1) DESC, cnt_col3 DESC
  8  /

C C   CNT_COL3
- - ----------
B 2         18
B 1          3
A 2         14
A 1          7

ops$tkyte@ORA920LAP> select col1, col2, count(col3) cnt_col3
  2    from t
  3   group by col1, col2
  4   order by (select max(count(col3)) from t t2 where t2.col1 = t.col1 group
by col1, col2) DESC,
  5            cnt_col3 DESC
  6  /

C C   CNT_COL3
- - ----------
B 2         18
B 1          3
A 2         14
A 1          7

Review &
      Followup

      Rating: 4
A little
      confusion  November 08, 2003
Reviewer:  Asim
      Naveed  from Pakistan
Is using a subquery allowed in ORDER BY CLAUSE????

I have seen this first time, and I dont think that
its clear from the syntax diagram of ORDER BY clause
in SQL referrence manual. or is it a scalar subquery??

here is a copy /paste of ORDER BY clause

ORDER [SIBLINGS] BY
{ expr | position | c_alias } [ ASC | DESC ] [ NULLS FIRST | NULLS LAST ]
[, { expr | position | c_alias } [ ASC | DESC ] [ NULLS FIRST | NULLS LAST ]]...

If i think its a correlated subquery, then will it
execute for each row from the rowset before group by
or execute for each row from the rowset formed
after group by.

Please can you describe your solution which is without
analytic functions even if it is a scalar subquery or not.

Thanks

          Followup:

yes, since 8i.

but the first query uses analytics.

the second does not.

it is a "scalar subquery"

and since order by's are done "last", it'll conceptually happen AFTER the group
by.  but of course, it is all "not defined" in the language that is SQL, so it
could happen whenever the optimizer feels like making it happen.

      Rating: 4
statements need
      some correction...  November 10, 2003
Reviewer:
      Maik Rabe  from Stuttgart, Germany
Hi Tom,

i am very impressed about your knowledge again! I did not know, that a subselect
in the ORDER BY clause is possible.
But there is still some mistake in your response. You forget in both statements,
that i will sort on count(col3), when col2 = 2. So i have added this condition
to the non-analytic statement in the "order by subselect" and it works right
now:

create table mr1 (a number, b number, c number);

insert into mr1 values(1,1,7);
insert into mr1 values(1,1,5);
insert into mr1 values(1,2,6);
insert into mr1 values(1,2,4);
insert into mr1 values(2,1,4);
insert into mr1 values(2,1,1);
insert into mr1 values(2,2,4);
insert into mr1 values(2,2,3);
insert into mr1 values(2,2,4);
insert into mr1 values(2,2,3);

select a,b,c from (
select a, b, sum(c) c from mr1
group by a,b) t1
order by (select sum(t2.c) from mr1 t2 where t2.A=t1.a and t2.b=2 group by
t2.a,t2.b) desc,a,b

         A          B          C
---------- ---------- ----------
         2          1          5
         2          2         14
         1          1         16
         1          2         10

Now, the ordering is done on groups (Col A) whereas the goup with the highest
sum(C) for B=2 is on top, and within each group the order is ascending on Col2.

But for the analytic function i think, that this is not possible to add the
condition col2=2 to the analytic order clause. I am right?

          Followup:

ops$tkyte@ORA920PC> select col1, col2, cnt_col3
  2    from (
  3  select col1, col2, count(col3) cnt_col3
  4    from t
  5   group by col1, col2
  6         )
  7   order by max( decode(col2,2,cnt_col3,null) ) over (partition by col1)
DESC, col1, col2
  8  /

      COL1       COL2   CNT_COL3
---------- ---------- ----------
         2          1          5
         2          2         14
         1          1         16
         1          2         10

November 10, 2003 Jan -- Thanks for the question regarding
      "deterministic package function", version 8.1.7

      You Asked
How can I can set a package function to be deterministic? It is easy for
standalone function, but I couldn`t find the right syntax for package functions.

      and we said...
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package demo_pkg
  2  as
  3          function f( x in number ) return number DETERMINISTIC;
  4
  5          function f2( x in number ) return number;
  6  end;
  7  /

Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package body demo_pkg
  2  as
  3          function f( x in number ) return number
  4          is
  5          begin
  6                  return 1;
  7          end;
  8
  9          function f2( x in number ) return number
 10          is
 11          begin
 12                  return 1;
 13          end;
 14  end;
 15  /

Package body created.

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx1 on t(demo_pkg.f(x));

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx1 on t(demo_pkg.f2(x));
create index t_idx1 on t(demo_pkg.f2(x))
                         *
ERROR at line 1:
ORA-30553: The function is not deterministic
Review &
      Followup

      Rating:
      5
  April 28, 2002
Reviewer:
      A reader

      Rating: 5
how it
      work  April 29, 2002
Reviewer:
      A reader
if i have table t with
10
20
30
40
how index affect can explain more

          Followup:

I assume you are asking about function based indexes and how they work.

See

http://osi.oracle.com/~tkyte/article1/index.html

      Rating:
      3
  April 29, 2002
Reviewer:  Sagi
       from India.
Hi Tom!

I saw this DETERMINISTIC for the first time. Can you tell us more about this.
Please

Thank you in Advance.

Regards,

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1547006324238

      Rating:
      3
  May 28, 2003
Reviewer:
      A reader
tom, our application uses several lookups. the items (distributed across several
lookup tables) are always of type number and are always accessed through their
names. we want functions with "meaningful" names (instead of scalar sub-queries)
to make use of those entries from within SQL.

we could have created one (packaged) function for each and every lookup table
entry such as:

select <function_for_lookup_entry_1> from dual;
   .
   .
   .
select <function_for_lookup_entry_n> from dual;

but in order to be more flexible we are thinking of putting ALL these entries
into ONE "packaged" associative array instead. item names serve as indexes for
the entries which then contain the numbers to be used.
the array is initialized when the package is called for the first time in a
session. its data (name and value for each item) is read from the lookup tables.
thus our lookup function interface is always "up-to-date", and there is no
hard-coded "link" left anymore between the entries in the lookup tables
(item/value) and the code (function-name/value according to the lookup table
entries).

we would just have to issue:

select <...>.GetCodeForItem('<item_name>') from dual

I know you sometimes use to say "don't be TOO generic - be explicit!". but would
this apply for what we are trying to achieve? what do you think about our
approach?

and in addition: would you think we would take a risk if we declare that
GetCodeForItem to be DETERMINISTIC to improve performance?

I'm very, very curious about your comments on that ...

          Followup:

I don't like it at all.  Calling PLSQL from SQL should not be done "as a matter
of fact"

VIEWS = way to hide constructs like scalar subqueries

you'll find calling plsql from sql has a high cost that becomes very noticable
if you do it hundreds of thousands of times.

      Rating:
      4
  May 31, 2003
Reviewer:
      A reader
in our projects it became very "common" to use the return values of those
packaged functions as filters in WHERE clauses WITHIN views. thus the package
state provides some kind of "adjustable environment" for SQL statement inside
the views.

isn't this achieved best by using contexts at all? was there any reason in
former releases (back to 7.3.x) to prefer this "package approach" rather then
using contexts?

          Followup:

contexts would be infinitely more efficient performance wise.

7.3, you had but one "context" variable -- dbms_application_info.set_client_info
and then 'select * from ... where x = userenv('client_info')

      Rating:
      4
  June 02, 2003
Reviewer:  j.

tom, I've three additional questions:

since all values are stored as varchar2 in contexts but in most cases the values
we want/have to store are for (small) enumerations and of type number we would
have to convert from varchar2 to number all the time - or otherwise change these
datatypes within our data model from number to varchar2 to avoid the mismatch in
type between the table columns and the return value of SYS_CONTEXT.
1) would you now think of changing an already existing model in order to store
'1', '2', '3' instead of 1, 2, 3 to make the datatypes fit without any
conversion?

2) is the number of entries for contexts limited?

3) how can one "search" for certain entries within a given context (without
trial and error via SYS_CONTEXT)?

          Followup:

you would simply use to_number(sys_context()) in the query (or to_date when
appropriate)

do NOT change the model.  you have numbers, nothing wrong with using a to_number
on sys_context to do the right thing.

2) only by memory (no, not really)

3) select * from session_context

      Rating:
      5
  June 02, 2003
Reviewer:  j.

tom, is SYS_CONTEXT executed once per SQL statement (you wrote somewhere else
ORACLE treats it as if bind variables were used) or for each row?

          Followup:

it is a bind, once per query in effect.

      Rating:
      4
  June 04, 2003
Reviewer:  j.

now there 's only one problem left. we want to raise an exception if an attempt
is made from within SQL on an entry that does NOT exist in a given context.

in order to achieve this we created the following function:

create or replace function RaiseError
return varchar2
is
begin
  Raise_Application_Error(-20001, 'Something went wrong.');
end;
/

and used it that way:

select nvl(SYS_CONTEXT('<our_context>', '<some_entry>'), RaiseError)
from   dual

but got the exception we expected to get only in case we would have tried to
access an NON-EXISTENT entry.

we thought expr2 for nvl would have been evaluated only if expr1 was null. why
is our function invoked at all if expr1 is NOT null?

sure, we could do:

select case when SYS_CONTEXT('<our_context>', '<some_entry>') is not null
            then SYS_CONTEXT('<our_context>', '<some_entry>')
            else RaiseError
       end
from   dual

or - even worse - bring PL/SQL back into play (just to "wrap" and "hide" all
this case stuff).

how would you solve this kind of problem?

btw: how would ORACLE in general treat IDENTICAL expressions within the SQL
statement - evaluate them once per statement (no matter how often they occur) or
once per occurrence of the expression within that statement? would SYS_CONTEXT
in our case be called once or twice?

          Followup:

You can use DECODE or CASE to short circuit the evaluation.  Apparently,
NVL(a,b) evaluates both a and b each time regardless.

ops$tkyte@ORA920> create or replace function foo return varchar2
  2  as
  3  begin
  4      raise program_error;
  5  end;
  6  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> column d format a20
ops$tkyte@ORA920>
ops$tkyte@ORA920> select nvl( sys_context('userenv','session_user'), foo ) d
from dual;
select nvl( sys_context('userenv','session_user'), foo ) d from dual
                                                   *
ERROR at line 1:
ORA-06501: PL/SQL: program error
ORA-06512: at "OPS$TKYTE.FOO", line 4

ops$tkyte@ORA920> select decode( sys_context('userenv','session_user'), null,
foo, sys_context('userenv','session_user') ) d
  2    from dual;

D
--------------------
OPS$TKYTE

ops$tkyte@ORA920> select case
  2         when sys_context('userenv','session_user') is null then foo
  3         else sys_context('userenv','session_user')
  4         end d
  5    from dual;

D
--------------------
OPS$TKYTE

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace function foo return varchar2
  2  as
  3  begin
  4      dbms_application_info.set_client_info( userenv('client_info')+1 );
  5      return userenv('client_info');
  6  end;
  7  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_application_info.set_client_info( 0 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select nvl( comm, foo ) from emp;

NVL(COMM,FOO)
-------------
            1
          300
          500
            4
         1400
            6
            7
            8
            9
            0
           11
           12
           13
           14

14 rows selected

see how the function is called 14 times...but

ops$tkyte@ORA920> exec dbms_application_info.set_client_info( 0 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select decode( comm, null, foo, comm ) d from emp;

D
--------------------
1
300
500
2
1400
3
4
5
6
0
7
8
9
10

14 rows selected.

decode does it 10

      Rating:
      4
  June 12, 2003
Reviewer:  j.

our current approach is to use packaged deterministic functions in where clauses
within our views in order to get these views "configured" based on the users
"environment" contained in package variables.

would you think that it could - in general - be worth it to change the EXISTING
application from the common "procedural" solution to a context centric one?

to be honest: comparing these two approaches I was unable to measure that huge
performance gain I expected from contexts treated as bind variables and
processed without those switches between SQL and PL/SQL.
but, on the other hand, wouldn't DETERMINISTIC functions minimize this overhead
too (and sometimes be more handy than contexts)?

          Followup:

depends on the query plans here.  if you were doing indexed keyed reads then:

select * from emp where ename = pkg.f(x);

will call pkg.f(x) just to get the key value to be used against the index
(deterministic, whatever).

on the other hand, kill the index access and pkg.f(x) will be called once per
row (deterministic, whatever)..

deterministic doesn't come into play here -- only with function based indexes
and MVs.  If you did not measure any difference, then you were already calling
the functions just once (i many times used tricks like:

   where column = ( select f(x) from dual where rownum > 0 )

to get this function called ONCE per query instead of once per row per query)

Consider:

ops$tkyte@ORA920> create table emp as select * from scott.emp;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace function f( x in number ) return varchar2
  2  deterministic
  3  as
  4  begin
  5          dbms_application_info.set_client_info( userenv('client_info')+1 );
  6          return 'KING';
  7  end;
  8  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_application_info.set_client_info( 0 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select empno, ename from emp where ename = f(5);

     EMPNO ENAME
---------- ----------
      7839 KING

ops$tkyte@ORA920> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
14

once per row

ops$tkyte@ORA920> exec dbms_application_info.set_client_info( 0 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select empno, ename from emp where ename = ( select f(5) from
dual where rownum > 0 );

     EMPNO ENAME
---------- ----------
      7839 KING

ops$tkyte@ORA920> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
1

once per query

ops$tkyte@ORA920> exec dbms_application_info.set_client_info( 0 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index ename_idx on emp(ename);

Index created.

ops$tkyte@ORA920> select empno, ename from emp where ename = f(5);

     EMPNO ENAME
---------- ----------
      7839 KING

ops$tkyte@ORA920> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
2

index access for whatever reason calls it twice, but only twice -- to get the
key to scan the index with

So, you must already have been using it in a fashion whereby it wasn't called
much.

      Rating:
      5
  June 12, 2003
Reviewer:  j.

very smart answer. for me another lesson learnt: the index makes the difference:
its existence causes the function to be invoked just to get the return values in
order to use them to scan the index entries, right? this seems to hold true no
matter if the function is deterministic or not, right?

but how can calls be skipped at all in case the function is NOT deterministic?
the results returned by the function may differ on each and every call and have
therefore to be calculated per row?

I don't understand the reasons why:

1st: the NON-DETERMINISTIC function is NOT called per row
2nd: an index is needed to prevent ORACLE from calling the DETERMINISTIC
function MULTIPLE times.
3rd: your (very neat) rownum trick isn't applied in general to queries where
"patterns" like the above appear?
4th: a function like that:

create or replace function f return number
deterministic
is
begin
  dbms_application_info.set_client_info(userenv('client_info') +1);
  return 10;
end f;
/

is NOT called only ONCE in a statement like that:

select f from all_objects;

please tell me: what did I miss?

          Followup:

correct -- deterministic does not come into play here at all.

deterministics comes into play in 2 cases

o function based indexes.  you can index deterministic functions.   you cannot
index other kinds

o MV's -- there are query rewrites that can happen with deterministic functions,
they will not happen otherwise.

1) the non-deterministic function would be called exactly the same number of
times as the deterministic

2) it is the manner in which the result is processed.  for index range scans you

    a) get a value
    b) scan for it

in full tablescans you

   a) read every row
   b) process the predicate (which calls a function)

3) yes it is.  by the definition of rownum it is...

4) correct, it is not

you didn't miss anything?

      Rating:
      4
  June 13, 2003
Reviewer:  j.

as for 3: tom, I just wanted to know WHY ORACLE needs yor that rownum trick in
order to get the query executed that way (if the function involved is defined to
be deterministic)?

as for 4: yes, I know/see it is, but the question is (once again) WHY?

today I've got the following reply on a TAR (I opened a few weeks ago) which is
related to that deterministic issue. they speak of a "non public" bug:

<quote>
A deterministic function is one that with a given input value always returns the
same output value.
For example:
Create or replace function square(a number) return number deterministic is
begin
return a * a;
end;
For a given value of 'a' the result will be always the same: a^2.
This means that if we use this function in a query like this:
select sum(square(deptno)) from emp;
square function could be called only 3 times, because there are only 3 different
values of deptno. If you define the function without deterministic keyword, the
function must be called 14 times, for each row of the emp table.
This is the expected behavior, but THERE IS A BUG (a NON PUBLIC ONE, you cannot
see it on Metalink) about this optimization is not considered, hence the
function is called 14 times regardless how was defined.
This bug is still on development and we don't have information about when the
fix will be ready.
</quote>

so from my point of view it 's now clear that I can expect a deterministic
function to be invoked "on demand" instead of per row in a future release.

do YOU have/can YOU get any additional information on the bug mentioned above?

          Followup:

because deterministic works in exactly two places right now -- function based
indexes and mv's.  We used neither here.

Not all bugs are bugs.  This is not a bug.  Think about it in this case.  Use
that square example above.  Now, assume that dept has 1,000,000 rows.  Assume
further that deptno is just a number (38 digits of precision).  Assume further
that deptno is in fact unique in this table.  now what?  you want that query to
run *doggedly slow* when you query:

select sum(square(deptno)) from emp;

because each time you call square, Oracle must first check some magical cache of
"function + inputs -> answer" to see if the question was already asked or not
and then, upon not finding it -- compute the answer and put it into this cache?
The very act of looking it up, not finding it and putting it in there probably
takes longer in this case then running the function in the first place.

And now I have this million element cache consuming gobs of ram -- doing
basically nothing.

Where there is room for an enhancement (and this would be an enhancement, not a
bug) would be:

select * from emp where ename = f('CONSTANT');

and f is deterministic.  Here, it would be very nice if Oracle coded:

select * from emp where ename = :x

and transparently bound in the value for :x for us -- just like it does with
SYS_CONTEXT() -- but that leads us to - if you have such a function, and you
want to ensure it is called but once, SYS_CONTEXT is the tool you were looking
for.

      Rating:
      5
  June 13, 2003
Reviewer:  j.

yes, I totally agree (although I expect things to work as described in ORACLE's
documentation: they did not only mention fbis and mvs in this context but
"normal" queries too)

but nevertheless my case is more like your sample - or even more simple: a
function without any argument at all (i.e. just returning a package variable
used as a some kind of "context" as described in my question from june 12,
2003).

back to the roots of this question - I just wanted to ask you if you would think
that switching from deterministic functions to contexts would bring a
performance gain which makes the work that has to be done (for an existing
application) worth it - given that deterministic functions work the way one can
expect ...

          Followup:

If the function is being called many times in the execution of a query
then sys_context would be very very appropriate and would in all likelyhood
increase performance by removing the context switch from sql to plsql and back
that is occuring many times.

If the function is being called once already, you will probably not notice any
appreciable changes in query response time.

      Rating: 3
What do you
      think of that?  July 12, 2003
Reviewer:  j

IMHO there are two contradictory goals: avoid context switches between PL/SQL
and SQL by using contexts AND get PL/SQL and SQL code robust by using symbols
instead of literals.

goals: assure that context entries in code do exist at runtime and getting their
names/contents constrained in addition (e.g. for uniqueness).

solution:
1) put entries into database table(s) in order to get entries "registered",
their names/contents "restricted" and context(s) "loaded" at logon time.

2) generate interface package(s) providing the symbols for those entries needed
in PL/SQL and SQL code.

3) use package entries instead of literals for accessing context(s) in order to
get verified whether a referenced context entry exists or not.

using deterministic packaged functions, generated from table contents and
returning names of context entries one can make use of contexts to minimize
overhead AND rely on the existence of context entries referenced by code
(through package entries) at compile time.

what do YOU consider pros & cons of this infrastructure?

          Followup:

#3 brings you all of the way back to the original problem.

      Rating:
      2
?  July 12, 2003
Reviewer:  j.

but what is wrong with SYS_CONTEXT('MyCtx',
MyPackage.MyFunctionReturningMyEntryName) instead of SYS_CONTEXT('MyCtx',
'MyEntryName')?

          Followup:

ops$tkyte@ORA920> create or replace package my_pkg
  2  as
  3          function f  return varchar2 deterministic;
  4  end;
  5  /

Package created.

ops$tkyte@ORA920> show err
No errors.
ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package body my_pkg
  2  as
  3          function f return varchar2
  4          deterministic
  5          is
  6          begin
  7                  dbms_application_info.set_client_info(
userenv('client_info')+1 );
  8                  return 'x';
  9          end;
 10  end;
 11  /

Package body created.

ops$tkyte@ORA920> create or replace context myCtx using my_pkg;

Context created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_application_info.set_client_info( 0 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from emp where ename = sys_context( 'myCtx', my_pkg.f
);

no rows selected

ops$tkyte@ORA920> select sys_context( 'userenv', 'client_info') sc from dual;

SC
----------
14

ops$tkyte@ORA920>

we don't want the function called 14 times.  back to square one

      Rating: 4
I agree - we
      don't ...  July 13, 2003
Reviewer:  j.

I didn't even know/expect that contexts are treated as if bind variables were
only in case there is no PL/SQL involved.

this leads (back) to the question: what other opportunities does one have to
achieve what I wanted to do - avoid any attempt to access non-existent context
entries in any piece of code (handling this at runtime is second best only)?

          Followup:

I've gone as far as I can go with this one.  Short of good coding practices
there aren't any.

      Rating: 4
the "slight"
      difference  August 01, 2003
Reviewer:  j.

why are user defined contexts NOT treated like the predefined context USERENV?

select SYS_CONTEXT('USERENV', 'NON_EXISTENT_ENTRY') from dual

works the way I wanted but

select SYS_CONTEXT('MYCTX', 'NON_EXISTENT_ENTRY') from dual

does NOT ...

          Followup:

q) how we we konw that 'NON_EXISTENT_ENTRY' isn't valid?

a) we don't

      Rating:
      2
  August 01, 2003
Reviewer:  j.

raising an exception in case

select * from session_context
where  namespace = 'MYCTX'
  and  attribute = 'NON_EXISTING_ENTRY'

returns no rows would fit the needs ...

btw: the statement

select SYS_CONTEXT('USERENV', 'XXX') from dual

raises ORA-20003 under 8.1.7.1.0 but NO exception with 9.2.0.1.0.

          Followup:

Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production

ops$tkyte@ORA920> select SYS_CONTEXT('USERENV', 'XXX') from dual;
select SYS_CONTEXT('USERENV', 'XXX') from dual
                                          *
ERROR at line 1:
ORA-02003: invalid USERENV parameter

      Rating: 5
very
      helpful!  August 01, 2003
Reviewer:
      A reader
Tom
Could you kindly explain the rownum trick - what it
does?
select empno, ename from emp where ename = ( select f(5) from dual where rownum
> 0 );

usually, I have seen queries with rownum < somthing...

Thank you!

          Followup:

it materialized the subquery -- in order to stabilize the result set (subquery)
with rownum -- we actually have to "build it" since rownum is dynamically
assigned.  So, the subquery is executed into temp and the result of that is
used.  since the result set just calls the function once -- there we go.

      Rating: 3
*** but it 's
      different in (my) 9.2.0.>>>1<<<.0
      ***  August 01, 2003
Reviewer:  j.

I've checked this in two stand-alone instances ...

      Rating: 5
sorry could not
      understand  August 01, 2003
Reviewer:
      A reader
"it materialized the subquery -- in order to stabilize the result set (subquery)

with rownum -- we actually have to "build it" since rownum is dynamically
assigned.  So, the subquery is executed into temp and the result of that is
used.  since the result set just calls the function once -- there we go."

Hi Tom
Sorry could not understand - I know what materialized
views are but what do you mean by "materialized the query".
 I know how rownum works but what is the significance of
"dynamic assignment" of rownum?

Sorry - if these are too basic questions!!

Thanx!!

          Followup:

if you had:

select *
  from t
 where x in ( select max(x) from t2 group by y )

you understand that we would have to "materialize" the subquery -- make it
"exist" -- to have the set of max(x)'s by y.  We would put that into temp and
then use it to process further.

rownum in a subquery does the same thing -- since rownum is so "fluid", in order
to get a stable result set to look at (one that would NOT return ambigous,
non-reproducible answers to us depending on how the query was processed) we must
"materialize" (produce, actually get) the subquery and then use its results.

      Rating: 5
Thanx
      Tom!  August 03, 2003
Reviewer:
      A reader
So can I say that materializing a query simply means
that you evaluate the query (or subquery) and have the results stored in temp to
be used by the enclosing
query (in our case)?

Thank you - You are just amazing!!!

      Rating: 5
Is sys_context
      deterministic?  November 10, 2003
Reviewer:
      Piotr Jarmuz  from Poznan, Poland
I must use the sys_context-instead-of-bind-variable trick and timings shows it
is 3-4 times slower than pure binds. So I assume it is not deterministic and
called many times. But I can't prove it.

Can you clarify?

Thanks in advance.

          Followup:

time to look elsewhere for "why":

ops$tkyte@ORA920PC> @test
ops$tkyte@ORA920PC> /*
DOC>drop table t;
DOC>create table t as select * from all_objects;
DOC>*/
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create or replace procedure p1
  2  as
  3          l_str varchar2(25) := '%';
  4  begin
  5          for x in ( select * from t where object_name like l_str )
  6          loop
  7                  null;
  8          end loop;
  9  end;
 10  /

Procedure created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create or replace context myCtx using p2
  2  /

Context created.

ops$tkyte@ORA920PC> create or replace procedure p2
  2  as
  3  begin
  4          dbms_session.set_context( 'myCtx', 'myBind', '%' );
  5          for x in ( select * from t where object_name like sys_context(
'myCtx', 'myBind' ) )
  6          loop
  7                  null;
  8          end loop;
  9  end;
 10  /

Procedure created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> exec runstats_pkg.rs_start

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> exec p1

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> exec runstats_pkg.rs_middle

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> exec p2

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> exec runstats_pkg.rs_stop(1000000)
Run1 ran in 126 hsecs
Run2 ran in 124 hsecs
run 1 ran in 101.61% of the time

Name                                  Run1        Run2        Diff

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
64,468      64,092        -376    100.59%

PL/SQL procedure successfully completed.

show YOUR test case (a 100% fully complete one please -- tables, inserts, etc so
we can all run it)

      Rating: 5
Got
      it  November 10, 2003
Reviewer:  Piotr
      Jarmuz  from Poznan, Poland

      Rating: 5
My "Got it" was
      before your answer  November 10, 2003
Reviewer:
      Piotr Jarmuz  from Poznan, Poland
It was something else completely unrelated.

The query I have told about has to process ~300000 calls divided by 6 call
places = ~50000 rows (I checked it with dbms_application_info and another
packaged function) but it returns only few.

Query looks something like this:

select * from phonenumber_x where address_id in (
       select a.id from address_x a, store_mission_x sm where
       a.rev_state=10 and sys_context('COOS_CONTEXT','date')>=a.rev_start and
sys_context('COOS_CONTEXT','date')<a.rev_end and
       sm.rev_state=10 and sys_context('COOS_CONTEXT','date')>=sm.rev_start and
sys_context('COOS_CONTEXT','date')<sm.rev_end and
       sm.address_id=a.id and sm.ext_id=:b_ext_id
) and id in (select phonenumber_id from phonetype pt, phonenumber_type_x pnt
                  where rev_state=10 and
sys_context('COOS_CONTEXT','date')>=rev_start and
sys_context('COOS_CONTEXT','date')<rev_end and pt.id=pnt.phonenumber_id and
name=:b_name)

where *_x are views on other tables all in all 8 tables, 6 indices and explain
plan with 30 steps. Dates are for revisioned objects and revisioned
associations. They (dates) are implicit so I wanted to save the need to bind
them explicitly in "using clause". Explicit binds correspond to the stored
procedure parameters.

While this query was going on another test application was running too and
disturbed my timings. That's it. So I guess there is no need to show you 100%
test case, is it?

You are right. As always.

But I'd realized my mistake before your answer appeared. Thank you anyway.

Regards,

Piotr

November 10, 2003 Paul -- Thanks for the question regarding "Why
      isn't this using an index", version

      You Asked
Is there a way to query a table and have the results be
derived solely from an index?

I have a fairly large table with many large columns (longs, long

raw).  I need distinct data from a single column which has been
indexed.  I was under the impression that the optimizer will
resolve the query by accessing just the index if the index has
enough info.  I enabled fast_full_index_scan and tried hints.  A

full table scan is used every time!!

      and we said...
Is the column nullable?  If so, all NULL index entries are not
indexed (hence a "select distinct X from T" must full scan if X
is indexed and X allows NULLS).

For example, say I have a table T:

scott@dev8i> create table t ( x int, y long raw );
Table created.

scott@dev8i> create index x_idx on t(x);
Index created.

And then I:

scott@dev8i> set autotrace on explain
scott@dev8i> select distinct x from t;

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (UNIQUE)
   2    1     TABLE ACCESS (FULL) OF 'T'

Now, that used a full scan -- it has too since X could be NULL
and hence would not be in the index.  Now, if I add a predicate:

scott@dev8i> select distinct x from t where x > 0;

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (UNIQUE)
   2    1     INDEX (RANGE SCAN) OF 'X_IDX' (NON-UNIQUE)

I see it uses the index (however, its not a FFS of the index
which is what you asked for) so I try:

scott@dev8i> select /*+ INDEX_FFS(t x_idx) */ distinct x from t;

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=82
Bytes=1066
          )

   1    0   SORT (UNIQUE) (Cost=3 Card=82 Bytes=1066)
   2    1     TABLE ACCESS (FULL) OF 'T' (Cost=1 Card=82
Bytes=1066)

but that doesn't work cause of the NULLness of X, so I:

scott@dev8i> alter table t modify x not null;

Table altered.

scott@dev8i> select /*+ INDEX_FFS(t x_idx) */ distinct x from t;

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=82
Bytes=1066
          )

   1    0   SORT (UNIQUE) (Cost=6 Card=82 Bytes=1066)
   2    1     INDEX (FAST FULL SCAN) OF 'X_IDX' (NON-UNIQUE)
(Cost=4 C
          ard=82 Bytes=1066)

and see that it does. (the predicate + hint would have worked
for the FFS as well -- in place of the alter)

Review &
      Followup

      Rating: 5
Descending
      Index and Fast Full Index Scan  April 19, 2002

Reviewer:  Peter L  from CA, USA
Hi Tom,

If I change the index to a descending index, fast full index scan won't be used
anymore. What's the reason for this?

thanks

drop index x_idx;
create index x_idx on t(x DESC);

select /*+ INDEX_FFS(t x_idx) */ distinct x from t;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=82 Bytes=1066
          )

   1    0   SORT (UNIQUE) (Cost=5 Card=82 Bytes=1066)
   2    1     TABLE ACCESS (FULL) OF 'T' (Cost=2 Card=82 Bytes=1066)

          Followup:

Looks like the ability to use that in a FFS is added in 9i.  A desc index is
really implemented as a function based index, as the optimizer gets smarter with
them -- it'll use them in more places.

      Rating: 5
adding "id >
      0" is good or not?  November 10, 2003
Reviewer:
      A reader
Hi Tom,

  Do you think adding "id > 0" predicate is good if adding such predicate can
speed up the query? I found that if not using such predicate, the query need 3
mins to execute (explain plan shows using Full table scan). However, it takes
only 20 seconds if using the "id > 0" (explain plan shows range index). (assume
id must be greater than 0, database is rule-based)

Thanks,
David

          Followup:

best way to speed up is to use CBO :)

one would need to "see" the queries and the plans and understand the table (eg:
is this a table that got really full once, is almost empty now and hence has
tons of empty blocks -- but very few rows) in order to diagnose further.

November 10, 2003 gs -- Thanks for the question regarding "why
      multiple versions? What would be the cause of BIND_MISMATCH?", version
      9.2.0.3

      You Asked
Tom,

I got the following statements from v$sql* .

What does it imply when the parsion_shema/user_id is 0 (SYS)?.

In the v$sql_shared_cursor I see the only difference is in BIND_MISMATCH. Is it
the cause for 16 occurences of the same sql, in the v$sql?

If the sql statments are exactly same, will they always have the same ADDRESS?
Is the child_adress will be different if one of the v$sql_shared_cursor columns
have different value?

What would be the cause of BIND_MISMATCH?

Thanks..

SQL> select parsing_user_id, parsing_schema_id, sql_text, address, child_address
from v$sql where address = '8EE280E8' ;

PARSING_USER_ID PARSING_SCHEMA_ID    SQL_TEXT                        ADDRESS
CHILD_AD
--------------- -----------------
-----------------------------------------------------    -------- ------------
              0                 0    update recordmst set BATCHKEY = :1,
RECORDTYPE = :2,    8EE280E8 8E9425DC
                    ENTITY = :3 ... where RECORD# = :36

          0                 0    update recordmst set BATCHKEY = :1, RECORDTYPE
= :2,    8EE280E8 9273E6F0
                    ENTITY = :3 ... where RECORD# = :36

          0                 0    update recordmst set BATCHKEY = :1, RECORDTYPE
= :2,    8EE280E8 90377C0C
                    ENTITY = :3 ... where RECORD# = :36

    ....total 15 times

             37                34    update recordmst set BATCHKEY = :1,
RECORDTYPE = :2,
                    ENTITY = :3 ... where RECORD# = :36            8EE280E8
8C215350

16 rows selected.

SQL> select parsing_user_id, parsing_schema_id, sql_text, address, version_count
from v$sqlarea where address = '8EE280E8' ;

PARSING_USER_ID PARSING_SCHEMA_ID SQL_TEXT                             ADDRESS
VERSION_COUNT
--------------- -----------------
-------------------------------------------------------------- --------
-------------
              0                 0 update prrecordmst set PRBATCHKEY = :1,
RECORDTYPE = :2,     8EE280E8         16
                  ENTITY = :3 ... where RECORD# = :36

SQL> select * from v$sql_shared_cursor where KGLHDPAR = '8EE280E8';

ADDRESS  KGLHDPAR U S O O S L S E B P I S T A B D L T R I I R L I O S M U T N F
-------- -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
8C215350 8EE280E8 N N N N N N N N N N N N N N Y N N N N N N N N N N N N N N N N

--What would be the cause of BIND_MISMATCH?

SQL> select address, child_address, CHILD_NUMBER , HASH_VALUE, PLAN_HASH_VALUE,
PARSING_USER_ID, PARSING_SCHEMA_ID from v$sql where address = '8EE280E8' ;

ADDRESS  CHILD_AD CHILD_NUMBER HASH_VALUE PLAN_HASH_VALUE PARSING_USER_ID
PARSING_SCHEMA_ID
-------- -------- ------------ ---------- --------------- ---------------
-----------------
8EE280E8 8BCF8008            0 1907582321               0               0
         0
8EE280E8 8C69CD4C            1 1907582321               0               0
         0
8EE280E8 8FFE65BC            2 1907582321               0               0
         0
8EE280E8 8E532E2C            3 1907582321               0               0
         0
8EE280E8 8E3F5A54            4 1907582321               0               0
         0
8EE280E8 92C90050            5 1907582321               0               0
         0
8EE280E8 91A600E0            6 1907582321               0               0
         0
8EE280E8 8FBD4D84            7 1907582321               0               0
         0
8EE280E8 8FD49758            8 1907582321               0               0
         0
8EE280E8 8F597C14            9 1907582321               0               0
         0
8EE280E8 8B94E608           10 1907582321               0               0
         0
8EE280E8 8F6A24BC           11 1907582321               0               0
         0
8EE280E8 8E9425DC           12 1907582321               0               0
         0
8EE280E8 9273E6F0           13 1907582321               0               0
         0
8EE280E8 90377C0C           14 1907582321               0               0
         0
8EE280E8 8C215350           15 1907582321      3689500139              37
        34

      and we said...
It is all about bind values in this case.  There are many factors concerning the
ability to share a cursor -- and the length of the bind variables can affect
this.

Consider this simple example:

ops$tkyte@ORA920> create table t ( x varchar2(2000) );
Table created.

ops$tkyte@ORA920> alter system flush shared_pool;
System altered.

ops$tkyte@ORA920> declare
  2          a   varchar2(1)    := 'x';
  3          b   varchar2(100)  := rpad('x',100,'x');
  4          c   varchar2(500)  := rpad('x',500,'x');
  5          d   varchar2(1000) := rpad('x',1000,'x');
  6  begin
  7          insert into t values(a);
  8          insert into t values(b);
  9          insert into t values(c);
 10          insert into t values(d);
 11  end;
 12  /

PL/SQL procedure successfully completed.

Now, if you ran sql_trace and tkprof -- you would find there is one query plan
generated -- in fact I did and the result was:

INSERT into t values (:b1)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        4      0.00       0.00          0          3          0           0
Execute      4      0.00       0.00          0          4         29           4
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        8      0.00       0.00          0          7         29           4

Misses in library cache during parse: 1
Misses in library cache during execute: 2
Optimizer goal: CHOOSE
Parsing user id: 119     (recursive depth: 1)

They all resolved to the same "query text" but as you can see by the 4 parse
calls -- these were 4 separate SQL statements submitted by PLSQL.  Now, looking
in v$sql:

ops$tkyte@ORA920> column sql_text format a30
ops$tkyte@ORA920> column address new_val ADDR
ops$tkyte@ORA920> select parsing_user_id puid, parsing_schema_id psid,
  2         sql_text, address, child_address
  3    from v$sql
  4   where sql_text like 'INSERT%into%t%values%(:b1)'
  5  /

      PUID       PSID SQL_TEXT                       ADDRESS  CHILD_AD
---------- ---------- ------------------------------ -------- --------
       119        119 INSERT into t values(:b1)      5743C918 57C567B4
       119        119 INSERT into t values(:b1)      5743C918 57CC4554
       119        119 INSERT into t values(:b1)      5743C918 573D4D3C

We see there are three versions of this insert -- they differ only because of
the magnitude of the bind values assigned:

ops$tkyte@ORA920> select *
  2    from v$sql_shared_cursor
  3   where KGLHDPAR = '&ADDR'
  4  /
old   3:  where KGLHDPAR = '&ADDR'
new   3:  where KGLHDPAR = '5743C918'

ADDRESS  KGLHDPAR U S O O S L S E B P I S T A B D L T R I I R L I O S M U T N F
-------- -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
57C567B4 5743C918 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N
57CC4554 5743C918 N N N N N N N N N N N N N N Y N N N N N N N N N N N N N N N N
573D4D3C 5743C918 N N N N N N N N N N N N N N Y N N N N N N N N N N N N N N N N

this is caused by wide flucuations in the sizes.  I think there is a break
point at 32 -- 80 -- ....

ops$tkyte@ORA920> declare
  2          a   varchar2(1)    := 'x';
  3          b   varchar2(100)  := rpad('x',10,'x');
  4          c   varchar2(500)  := rpad('x',20,'x');
  5          d   varchar2(1000) := rpad('x',30,'x');
  6  begin
  7          insert into t xxx values(a);
  8          insert into t xxx values(b);
  9          insert into t xxx values(c);
 10          insert into t xxx values(d);
 11  end;
 12  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select parsing_user_id puid, parsing_schema_id psid,
  2         sql_text, address, child_address
  3    from v$sql
  4   where sql_text like 'INSERT%into%t%values%(:b1)'
  5  /

      PUID       PSID SQL_TEXT                       ADDRESS  CHILD_AD
---------- ---------- ------------------------------ -------- --------
       119        119 INSERT into t xxx values(:b1)  57C23DF0 57B9C2FC
       119        119 INSERT into t values(:b1)      57CE0AEC 57301790
       119        119 INSERT into t values(:b1)      57CE0AEC 57C136B0
       119        119 INSERT into t values(:b1)      57CE0AEC 57C48E44

they all parsed to the same child cursor, but:

ops$tkyte@ORA920> declare
  2          a   varchar2(1)    := 'x';
  3          b   varchar2(100)  := rpad('x',10,'x');
  4          c   varchar2(500)  := rpad('x',32,'x');
  5          d   varchar2(1000) := rpad('x',33,'x');
  6  begin
  7          insert into t yyy values(a);
  8          insert into t yyy values(b);
  9          insert into t yyy values(c);
 10          insert into t yyy values(d);
 11  end;
 12  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select parsing_user_id puid, parsing_schema_id psid,
  2         sql_text, address, child_address
  3    from v$sql
  4   where sql_text like 'INSERT%into%t%values%(:b1)'
  5  /

      PUID       PSID SQL_TEXT                       ADDRESS  CHILD_AD
---------- ---------- ------------------------------ -------- --------
       119        119 INSERT into t xxx values(:b1)  57E794B4 57EEBA78
       119        119 INSERT into t values(:b1)      57A6E0E4 57C1FD78
       119        119 INSERT into t values(:b1)      57A6E0E4 575DA0C0
       119        119 INSERT into t values(:b1)      57A6E0E4 57C18D9C
       119        119 INSERT into t yyy values(:b1)  578824D4 57DF5E2C
       119        119 INSERT into t yyy values(:b1)  578824D4 57DDD68C

6 rows selected.

resulted in 2 yyy versions (32/33)...

The parsing_user/schema_id is recursive sql (sql executed as sys) generally.
Review &
      Followup

      Rating: 4
Your examples
      and explanations are excellent.  April 30, 2003

Reviewer:  gs
Tom,

You have four inserts in the fist PL/SQL block and has only three SQL statements
in the shared pool. Is it becaz, two of them could be sharing the same SQL,
though the bind length is different?

Is the version_count in v$sqlarea same as the count(KGLHDPAR) from
v$sql_shared_cursor? [Assume the entries are still there in the view]

Which V$ tables are the right one to find the multiple versions of the same SQL?

Also I noticed, when the statement get invalidated, the parsing user/schemaid
becomes "0". And, when someone else re-issues the same statement, the parsing
user/schemaid get assigned the id of the executing user/schema.

In my original posting, none of those statements were recursive SQLs. They were
issued by OCI calls from front end. Could there be any other reason other than
invalidations,  causing the Parsing user/schema id to be 0 [assuming they are
not recursive]

Thanks....

2:47:51 DBUSR> create or replace synonym test_user.t1 for test_owner.v_t1 ;

Synonym created.

12:48:22 DBUSR> select  INVALIDATIONS, executions, parsing_user_id puid,
parsing_schema_id psid,   parse_calls, sql_text, address, child_address from
v$sql where sql_text like 'select * from t1%' ;

INVALIDATIONS EXECUTIONS       PUID       PSID PARSE_CALLS SQL_TEXT
                        ADDRESS  CHILD_AD
------------- ---------- ---------- ---------- -----------
--------------------------------------------- -------- --------
            1          0          0          0           0 select * from t1
                        74B94B90 72254620

12:48:25 DBUSR> select * from v$sql_shared_cursor where KGLHDPAR = '74B94B90' ;

ADDRESS  KGLHDPAR U S O O S L S E B P I S T A B D L T R I I R L I O S M U T N F
-------- -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
72254620 74B94B90 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N

-- as TEST_USER from another session "select * from t1" from another session"

12:49:14 DBUSR>  select  INVALIDATIONS, executions, parsing_user_id puid,
parsing_schema_id psid,   parse_calls, sql_text, address, child_address from
v$sql where sql_text like 'select * from t1%' ;

INVALIDATIONS EXECUTIONS       PUID       PSID PARSE_CALLS SQL_TEXT
                        ADDRESS  CHILD_AD
------------- ---------- ---------- ---------- -----------
--------------------------------------------- -------- --------
            2          1         89         89           1 select * from t1
                        74B94B90 72254620

-- as TEST_OWNER from another session, "select * from t1"

12:49:20 DBUSR> select  INVALIDATIONS, executions, parsing_user_id puid,
parsing_schema_id psid,   parse_calls, sql_text, address, child_address from
v$sql where sql_text like 'select * from t1%' ;

INVALIDATIONS EXECUTIONS       PUID       PSID PARSE_CALLS SQL_TEXT
                        ADDRESS  CHILD_AD
------------- ---------- ---------- ---------- -----------
--------------------------------------------- -------- --------
            2          1         89         89           1 select * from t1
                        74B94B90 72254620
            0          1         88         88           1 select * from t1
                        74B94B90 727D2060

12:51:19 DBUSR> select * from v$sql_shared_cursor where KGLHDPAR = '74B94B90' ;

ADDRESS  KGLHDPAR U S O O S L S E B P I S T A B D L T R I I R L I O S M U T N F
-------- -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
72254620 74B94B90 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N
727D2060 74B94B90 N N N N N N N N N N N N N Y N N N Y N N N N N N N N N N N N N

          Followup:

yes -- this shows it was the 500/1000 pair that shared:

ops$tkyte@ORA920> create table t ( x varchar2(2000) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter system flush shared_pool;

System altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          a   varchar2(1)    := 'x';
  3          b   varchar2(100)  := rpad('x',100,'x');
  4          c   varchar2(500)  := rpad('x',500,'x');
  5          d   varchar2(1000) := rpad('x',1000,'x');
  6  begin
  7          --insert into t values(a);
  8          --insert into t values(b);
  9          insert into t values(c);
 10          insert into t values(d);
 11  end;
 12  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> column sql_text format a30
ops$tkyte@ORA920> column address new_val ADDR
ops$tkyte@ORA920> select parsing_user_id puid, parsing_schema_id psid,
  2         sql_text, address, child_address
  3    from v$sql
  4   where sql_text like 'INSERT%into%t%values%(:b1)'
  5  /

      PUID       PSID SQL_TEXT                       ADDRESS  CHILD_AD
---------- ---------- ------------------------------ -------- --------
       358        358 INSERT into t values(:b1)      5CBC5DA0 5CC6A9E8

v$sqlarea is an aggregate of v$sql and yes -- it should match in parity with
v$sql_shared_cursor...

The other time I've seen 0 is failed parses....

ops$tkyte@ORA920> alter system flush shared_pool;

System altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2          for x in ( select * from not_there ) loop null; end loop;
  3  end;
  4  /
        for x in ( select * from not_there ) loop null; end loop;
                               *
ERROR at line 2:
ORA-06550: line 2, column 25:
PL/SQL: ORA-00942: table or view does not exist
ORA-06550: line 2, column 11:
PL/SQL: SQL Statement ignored

ops$tkyte@ORA920>
ops$tkyte@ORA920> select sql_text, parsing_user_id, parsing_schema_id
  2    from v$sql
  3   where sql_text like '%not_there%';

SQL_TEXT                       PARSING_USER_ID PARSING_SCHEMA_ID
------------------------------ --------------- -----------------
select sql_text, parsing_user_             358               358
id, parsing_schema_id   from v
$sql  where sql_text like '%no
t_there%'

begin  for x in ( select * fro               0                 0
m not_there ) loop null; end l
oop; end;

      Rating: 5
Different
      execution plans for children of same SQL  November 07, 2003

Reviewer:  Ashish S  from New York, NY
Is it possible that the childern of same SQL get
different execution plans?

We have a situation where there 2 children in V$SQL, both
have object_status VALID. The V$SQL_PLAN table shows 2
different execution plans. One uses index and other
one does FULL table scan.

The V$SQL_SHARED_CURSOR shows that the difference between
these child cursors is BIND_MISMATCH.

          Followup:

yes. absolutely.  that is one of the reasons for child cursors.  100%

      Rating:
      4
Good  November 10, 2003
Reviewer:
      Ashish S  from New York, NY
1. Does BIND_MISMATCH between the two different plans mean
that the optimizer is going to use different plans
depending on value of bind variable?

2. On 9i RAC, we see different in execution plans on
different nodes for the same SQL. Why this happens?

Thanks for your help!

          Followup:

1) COULD use different, has the ABILITY to use different.  not "will"

2) you have different init.ora parameters set that influence the optimizer (eg:
db_file_multiblock_read_count) or you have different SESSION settings for some
of those parameters.

November 10, 2003 Prakash -- Thanks for the question regarding
      "Deleting duplicate records without using rowid and rownum", version 8.1.7

      You Asked
Hi Tom,

If there is any duplications of records in a table, we know how to eliminate the
duplicate rows using rowid.
But is there any possibility to delete the duplicate records in a table without
using rowid and rownum.
my friend who is  working in Oracle for arnd 4 years says that it is not
possible. But i think there should be some way to do this.

Pls give me some suggestion TOM. i would be thankful to you, if you can
illustrate with examples.

Thanks in Advance

Prakash

      and we said...
in a single statement -- no, relational algebra doesn't permit it.  Entirely
duplicate records (where all columns are the same) make this not possible.

using rownum -- no, you cannot use rownum either.

Using rowid, yes, you can use rowid.

Using procedural code, yes, you can use procedural code.

Using more then one statement, yes, I can do this in 3 statements:

insert dups into temp table
delete dups from orig table
put de-duped data back into orig table from temp table

Review &
      Followup

      Rating:
      3
ummmm  June 19, 2002
Reviewer:  Mal
       from Redwood City, CA
Hi Tom.

Love this web site, always learning from it.

I just want to clarify the logic to do this.  Please correct me if I'm wrong.

1) Put duplicates in a temp table.

Create Table silly_dups as
Select *
From   evil_table
Group  By col1, col2, col3....
Having count(*) > 1;

2) Remove duplicates from table.
Delete From evil_table
where  (col1, col2, col3...) in
(Select col1, col2, col3
 From   silly_dups);

3) Put duplicate rows back.

Insert into evil_table (col1, col2, col3...)
Select col1, col2, col3...
From   silly_dups;

Is that what you meant?

          Followup:

3) select DISTINCT ....

but other then that, yes.  It is more complex if the columns you consider "DUPS"
are less then the number of columns in the table (eg: you have 5 columns -- a
row is a dup if the first 3 are the same) -- but takes the same number of
statements.

      Rating: 3
Why wouldn't
      you use ROWID  June 19, 2002
Reviewer:
      Trevor
Prakash,
        Just wondering why you don't want to use ROWID?
Every row has a unique one, it's the fastest way to access
a row, you can remove the dups in a single SQL statement
as opposed to 3 or writing lines of PL/SQL

Regards
Trevor

      Rating: 3
I agree with
      Trevor  June 19, 2002
Reviewer:  Ib
       from China
Even i am just wondering why the poster doesnt want to use RowId instead of
complicating the work!!!

Ib.

          Followup:

Many times, I get asked questions of a purely academic nature.  This is one of
them -- they just "want to know" for whatever reason.

      Rating: 3
Not only
      academic question, resources plays role as well  June 21,
      2002
Reviewer:  Jaromir Nemec  from Europe

In my opinion, if the table is small you may play with chasing of dubs as much
as you will (e.g. with rowid); but with big tables (that is relative to your box
and disc throughput)  you simple MUST avoid the equi-join on the dubs columns
and you must get them out with one simple sort of the table. From this point of
view it is not an academic question bus one of the resources.

Jaromir D.B. Nemec

www.db-nemec.com

          Followup:

depends on the number of dups doesn't it (rhetorical question, answer = yes).

If the columns (or some subset thereof) upon which you decide are the columns to
decide dups (i assume you meant dups, not dubs) are indexed, AND there are some
small number of dups to consider, using the index will be just as good as using
the rowid (it be cached after all -- you are proccessing those rows).

I was always of the opinion that its always OK to be academic (to wonder, could
I).  Never said to do this this way....

      Rating: 4
Sorry for being
      confusing, .....  June 23, 2002
Reviewer:
      Jaromir Nemec  from Europe
Sorry for being confusing, of course I meant dups not dubs, sorry! Dubs could be
German, but I am not sure!

Of course the task of removing and reinserting  of known dups depends on the
number of dups (this is the task 2) and 3) in the above response). The index on
dup columns is very usefull here.

But the "hard work" is to find the dups (task 1) above - "put the duplicates in
a temp table"). And this work depends not on the result (i.e. number of dups)
but on the size of the searched table. It is obviously less complicated to find
10 dups in a table with 1 million records than to find the same 10 dups in a
table with 100 million records.

My statement was: if the table is big enough, than there is a difference if the
dup search (task 1) above) is done by
a)    Equi-join on the searched table (= more resource consuming)
b)    Sort of the searched table (= less resource consuming).

Interesting question is, if it is possible to use index (index columns are the
dup columns) for this task of searching dups in a table. I have no idea, can you
provide any hint?

Thanks

Jaromir Nemec

          Followup:

Agreed, using analytic functions we can do this efficiently.  Most other methods
require some sort of correlated subquery which is like the equi join you talk
of.

Like this:

delete from t
  where rowid in ( select rid
                     from ( select rowid rid,
                                   row_number() over
                                          ( partition by x order by rowid ) rn
                              from t
                           )
                    where rn > 1 )
/

that'll remove "dups" based on the column X (just partition by the "key to
de-dup by")

If you have an index on that column (and at least one of the columns in the
index is NOT NULLABLE), then it'll use that index in the plan...

      Rating: 5
Another reason
      for de-duping without rowid  June 24, 2002

Reviewer:  Johan Snyman  from JHB SA
Another reason for wanting to remove duplicates without using rowid's may be to
have a more "pure SQL" approach that would work in non-Oracle databases as well,
where rowid does not exist or an equivalent unique row identifier is not exposed
to the user...

One would presumably only need to do this to clean up the data before migrating
to Oracle, tho ;-)

      Rating: 4
What is we have
      more than 1 duplicate?  November 07, 2003

Reviewer:  Pichai Bala  from Santa Clara, CA

Hi Tom,
   I think the problem with the analytic function is it can't be used to remove
the duplicates if we have more than one duplicate in the table. Any thoughts.
   Otherwise using Analytic functions is simple, straight and great.
   Thanks for your wonderful service to the oracle community.

          Followup:

sure it can.

you would partition by your "key", you would order by something, you would use
row_number

select *
  from ( select a.*,
                row_number() over ( partition by THE_KEY order by ANYTHING ) rn
           from t )
 where rn = 1;

      Rating: 4
What if we have
      more than 1 duplicate?   November 10, 2003

Reviewer:  Pichai Bala  from Santa Clara, CA

Tom,
  I would like to remove(delete) the duplicates using Analytic function if there
is more than 1 duplicate for the KEY. I can't create a new table without the
duplicates every time.

Thanks

          Followup:

delete from t
 where rowid not in ( select rowid
                        from ( select a.*,
                row_number() over ( partition by THE_KEY order by ANYTHING ) rn
                        from t )
                       where rn = 1);

would be one approach.

or

delete from t
 where rowid in ( select rowid
                        from ( select a.*,
                row_number() over ( partition by THE_KEY order by ANYTHING ) rn
                        from t )
                       where rn <> 1);

would perhaps be a better one.. depends on volumes of data.

November 11, 2003 thomas -- Thanks for the question regarding
      "Oracle9i, init.ora isn't working", version 9.0.1

      You Asked
Tom,

I've just installed Oracle9i.  I changed my init.ora settings but they don't
seem to be changing.  For example, I set the db_cache_size and shared_pool to a
new value and restarted but my SGA size didn't change at all.

What's going on?

      and we said...
The answer is a new feature, the Stored Parameter File (SPFILE).

In Oracle8i and before, when you started the database, the init.ora file for
that database had to be on your client machine.  If you attempted to remotely
start a database instance, you needed to have a local copy of the init.ora as
the Oracle backend process running on your client would process the init.ora.

In Oracle9i, the parameter file is stored in a binary format on the server
itself -- you no longer need to have a local copy (although you may if you like)
to start the database remotely.  It also means that changes made via the ALTER
SYSTEM command may now persist across server restarts.  No more updating the
init.ora.

The SPFILE is stored on the server in the location specified by the
initialization parameter "spfile":

sys@TKYTE901.US.ORACLE.COM> show parameter spfile

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
spfile                               string      %ORACLE_HOME%\DATABASE\SPFILE%
                                                 ORACLE_SID%.ORA
sys@TKYTE901.US.ORACLE.COM>

You can (but shouldn't unless you have some compelling reason to) change this
location from the default.

This spfile is created via the new command "CREATE SPFILE" which has the syntax:

CREATE SPFILE = 'filename' FROM PFILE ='pfilename';

You can create a textual version of this binary parameter file using the CREATE
PFILE command:

CREATE PFILE = 'pfilename' FROM SPFILE = 'filename';

This will always create the text parameter file on the SERVR (not on the client
that executed this command).  This might be used to export all parameters, make
many changes to them, and then create a new SPFILE using this pfile.

In the event you want to use a PFILE (parameter file), you would use the PFILE=
option on the startup command:

SQL> startup pfile=filename

When you make changes to system parameters now, you have an extended syntax on
the ALTER SYSTEM command to set parameters:

ALTER SYSTEM set parameter = value SCOPE = MEMORY | SPFILE | BOTH;

The Scope clause allows to to set a system parameter:

o in memory -- affects the database right now, will not be in place after a
restart

o in the spfile -- does not change the current setting of the parameter but will
modify the spfile so that upon a restart this parameter will take effect

o both -- changes the current instances setting as well as updates the spfile.

Review &
      Followup

      Rating:
      5
  June 26, 2001
Reviewer:  Joel
       from Columbus, OH USA

      Rating: 5
init.ora in 9i
      database  June 27, 2001
Reviewer:  Umesh S.
      Kasturi  from Hubli, India
a good concept..but I will be happy to get more details..

      Rating:
      5
  October 31, 2001
Reviewer:  Nikhil
      Saxena  from India
I was not knowing this before

      Rating: 4
Oracle9i,
      init.ora isn't working  March 12, 2003
Reviewer:
      Hamilton Budayao  from Philippines
thanks for the information. this site is really helpful.

i had the same problem, i can't change my audit_trail  parameter to true in 9i
but can easily change it in 8.1.7. version using init.ora file.

however, i've tried changing the parameter using the alter system command,
ALTER SYSTEM SET AUDIT_TRAIL = DB SCOPE = BOTH
what did i miss if i have encountered the error "specified initialization
parameter cannot be modified"?  can you please answer my question or just give a
detailed procedure on how to change a parameter value in 9i enviromentment

          Followup:

use scope = spfile

and then restart.  that error says "sorry, that is one you cannot change online,
hence scope=both won't work"

      Rating:
      3
  May 08, 2003
Reviewer:
      A reader
Sorry, I am still having problems:

SQL> create spfile='D:\oracle9i\admin\SHAN\pfile\SPFILESHAN.ORA' from
  2  pfile='D:\oracle9i\admin\SHAN\scripts\init.ora';

File created.

SQL> sho parameter spfile

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
spfile                               string

SQL> shutdown
Database closed.
Database dismounted.
ORACLE instance shut down.
SQL> startup
ORACLE instance started.

Total System Global Area  135338868 bytes
Fixed Size                   453492 bytes
Variable Size             109051904 bytes
Database Buffers           25165824 bytes
Redo Buffers                 667648 bytes
Database mounted.
Database opened.
SQL> sho parameter spfile

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
spfile                               string

Why is the spfile still blank after a restart? Do we have to mention the full
spfile name everytime we startup like this:

SQL> startup pfile='D:\oracle9i\admin\SHAN\pfile\SPFILESHAN.ORA'
SP2-0714: invalid combination of STARTUP options

SQL> startup pfile='D:\oracle9i\admin\SHAN\pfile\SPFILESHAN.ORA'
LRM-00123: invalid character 0 found in the input file
ORA-01078: failure in processing system parameters

Please clarify with an example. Thank you verymuch.
Regards,

Shan.

          Followup:

unless you set it -- it'll look for it in the default location.

just go:

create spfile from pfile=.....

and it'll put it in the dbs directory named correctly for the default.

you obviously cannot use an spfile as a pfile -- spfiles are binary, pfiles are
ascii text.

      Rating:
      5
  May 09, 2003
Reviewer:
      A reader
Got it! Thank you.

One more question, if we create the spfile at a non-default location, how do we
specify that during the startup?

          Followup:

you can use a pfile with spfile= for example -- but i see no possible logical
reasonable reason to not use the default location.

      Rating: 5
Why binary
      format...  May 09, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Hi Tom,
I always wondered the reasoning behind making the SPFILE a binary file and not a
regular text file (like the init.ora file was).
Was there a reason (security ...), to make the format binary?
Also, If you change a parameter in your SPFILE using ALTER SYSTEM command and
later restart the database, only to find that something went wrong and database
will not start. In this situation, how to revert back the changed setting (can
not edit the file, since it is in binary format).
Thanks a lot for the explanation on this concept.

          Followup:

it is a system maintained file, not be to messed about with by anyone via any
mechanism other then SQL commands.

Making it a text file would be too tempting for us -- we would be tempted just
to edit the thing.  Hence, binary -- like a control file, datafile redo log
file, dmp file....

you can easily recover from a mistake like that.

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2200190221847#6742226475226

      Rating: 5
Very
      useful.  May 31, 2003
Reviewer:  Sreeviswa
      Peesapati  from East Granby, CT USA
I am very much impressed by the way Tom answers the doubts. Thank you Tom, it is
Very helpful.

-Viswa

      Rating:
      4
  July 30, 2003
Reviewer:
      A reader

      Rating: 4
insufficient
      priviledge  November 11, 2003
Reviewer:
      jack
I've tried to execute "create
pfile='D:\oracle9i\admin\SHAN\pfile\SPFILESHAN.ORA' from
pfile='D:\oracle9i\admin\SHAN\scripts\init.ora'" in sql plus, but it returns
"insufficient privileges".
my login is system/manager. What privilege do I need?  system is supposed to be
a dba?

          Followup:

you need to be as SYSDBA

just being a 'dba' isn't good enough.

November 11, 2003 Guru -- Thanks for the question regarding
      "Several Doubts", version 7.3

      You Asked
Dear Tom,

   I am having several doubts.  They are

1) Can you give a default value for a parameter in a stored
procedure or a function.

2) When using RUN_PRODUCT how do i run a report without
displaying the default parameter form.

3) How to get the no. of months, days between two dates.

4) What is a database's SCN(System Change Number).  how is it
used while executing query.  Pl. explain me in detail if
possible with an example.

5) How can i know the current ISOLATION LEVEL.  Is there any
command for that.

Thanking You,
Guru Prasad M.R.

      and we said...
1) yes,

   create or replace procedure foo( x in date DEFAULT SYSDATE )
   ....

2) you could use a parameter paramform=no or run the report in
batch.

3) see

http://osi.oracle.com/~tkyte/Misc/DateDiff.html
(to get months, simply use months_between( a, b ))

4) SCN is our internal clock.  The clock ticks up by 1 every
time a transaction completes.  When we recover, we use the SCN
to see how much data might need to be applied to a block to
recovery it.  During a query, we look at the SCN that is in
place when your query began.  During the processing of your
query, we make sure you see NO data that was modified after that
SCN.  If we goto a block and find the data on that block was
modified after the SCN you started with -- we reconstruct the
block from the rollback segments until it looks like it did when
your SCN was the current SCN.

5) There is no mechanism to retrieve that particular bit of data
that I am aware of.  I can think of a way to do it efficiently
in Oracle8i -- but not in 7.3.
Review &
      Followup

      Rating: 3
Q on
      SCN  November 11, 2003
Reviewer:  Anil Pant
       from India
SCN is sequentially generated number and for every transaction SCN is generated.
What's the size of field ? What if the number reaches max value and can this
number be reset? If the number is reset what's the impact ?

          Followup:

SCN is an increasing number (drop the sequential part, misleading)

they go quite large:

ops$tkyte@ORA920> set numformat 999,999,999,999,999,999
ops$tkyte@ORA920> select dbms_flashback.get_system_change_number from dual;

GET_SYSTEM_CHANGE_NUMBER
------------------------
       7,711,379,223,126

you'd recreate to reset.  It would take a couple of billion years generating
thousands of scn's per second to exhaust them

November 11, 2003 Prasad -- Thanks for the question regarding
      "Oracle9i ANSI -INNER/OUTER - joins with multiple tables", version
Oracle9i

      You Asked
Hi Tom,
I am looking for the resources/examples on using Oracle9i ANSI joins on multiple
tables. Most of the examples i found are using just two tables to explain the
join. I'd appreciate if you could give the examples of writing complex
multitable joins for Oracle9i.

I want to join tables A,B,C,D,E in such a way that tables C,D & E will have
OUTER join with table A on a key column. I have used this type of join in
Informix and now trying to convert it into Oracle9i.

Thanks
Prasad

      and we said...
The same syntax you used in Informix, given that it was "ansi" style is
supported in Oracle9i.

There are not "advanced resources" on this cause you are making it harder then
it is.  It really is as straight forward as it looks.

Just use parens and keep nesting the joins:

ops$tkyte@ORA9I.WORLD> create table a ( x int );

Table created.

ops$tkyte@ORA9I.WORLD> create table b ( x int );

Table created.

ops$tkyte@ORA9I.WORLD> create table c ( x int );

Table created.

ops$tkyte@ORA9I.WORLD> create table d ( x int );

Table created.

ops$tkyte@ORA9I.WORLD> create table e ( x int );

Table created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> insert into a values ( 1 );

1 row created.

ops$tkyte@ORA9I.WORLD> insert into b values ( 1 );

1 row created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> select *
  2    from (((( a inner join b on a.x = b.x ) left outer join c on a.x = c.x )
  3            left outer join d on a.x = d.x ) left outer join e on a.x = e.x )
  4  /

         X          X          X          X          X
---------- ---------- ---------- ---------- ----------
         1          1

does exactly that.  follow the parens and it becomes very easy.  You said to
join A to b and outer join that to c, outer join that to d, outer join that to e
-- thats exactly what I typed in....

Review &
      Followup

      Rating: 5
What's your
      preference  December 26, 2001
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Tom:

You've been doing SQL for a few years...  I've never really found the "ansi"
syntax to be all that appealing.  Maybe I'm not being flexible - I'm also having
a hard time moving from C to Java (even though I don't code that much anymore, I
suppose I should stay current :) especially with the pace that Oracle Corp. is
integrating/implementing Java features!)  What's your preference for the
"Oracle" syntax and the "ansi" syntax?

- Mark

          Followup:

Here is what I think.

I'm frequently given a query that goes on for 2 or 3 pages and asked "how can I
make it go faster".

My first step is to break the predicate into "joins" and "predicates" so I can
draw a picture on the board of what is being joined to what and how they are
joined.  Then I list out the predicates.  Now I can start attacking the query.

This syntax will help me get started as it naturally breaks them out.  What is a
join is very different from what is a predicate, it is more clear, it is easier
to read, it is more self documenting.

Does that mean I'll use it?  I'm not sure yet.  Probably over time I will.

      Rating: 3
Oracle9i ANSI
      -INNER/OUTER - joins with multiple tables  March 07, 2002

Reviewer:  Ted  from Philadelphia, PA
Is the syntax shown in your explanation for 9i only.  I am on 8.1.7 and the
select statement does not parse correctly.

Your Query:
select *  from (((( a inner join b on a.x = b.x ) left outer join c on a.x = c.x
)
 left outer join d on a.x = d.x ) left outer join e on a.x = e.x )

My results:
SQLWKS> select *  from (((( a inner join b on a.x = b.x ) left outer join c on
a.x = c.x )
     2>  left outer join d on a.x = d.x ) left outer join e on a.x = e.x )
     3>
select *  from (((( a inner join b on a.x = b.x ) left outer join c on a.x = c.x
)
                    *
ORA-00928: missing SELECT keyword

Thanks
Ted

          Followup:

...
The same syntax you used in Informix, given that it was "ansi" style is
supported in Oracle9i.
....

yes, that syntax is new with 9i

      Rating: 4
join by
      composite foreign key   March 07, 2002
Reviewer:
      Mikito Harakiri
ANSI join might be superior when you join 2 tables with more than one equality
predicate. For example, what does

select * from emp, dept
where emp.deptno = dept.deptno
and emp.deptno = dept.deptno(+)

mean?

          Followup:

It means exactly the same thing as:

select *
  from emp LEFT OUTER JOIN dept on (emp.deptno=dept.deptno)
 where emp.deptno = dept.deptno
/

does.  Now what?  Is the ansi join superior in some way here?  I'm not seeing
it.  It does not preclude me from making stupid queries like that.  I do see
people doing this sort of "silly" thing all of the time, like this:

select * from t1, t2
where t1.x = t2.x(+) and t2.y > 5;

I ask them "why the outer join", they say "well we need it in case there isn't a
record in t2 for that x and we need to see all records". I say "well that
predicate t2.y > 5 gets rid of the records where y is null and y is null
whenever you make up a missing record so the outer join is not doing anything
but making the query run slower...."

      Rating: 4
full outer
      join  March 08, 2002
Reviewer:  mikito
      harakiri
The example

select a.c1, a.c2, b.c1, b.c2 from (
  select 1 c1, NULL c2 from dual
    union
  select 1 c1, 1 c2 from dual
    union
  select 1 c1, 2 c2 from dual
) a,(
  select 1 c1, NULL c2 from dual
    union
  select 1 c1, 1 c2 from dual
    union
  select 2 c1, 1 c2 from dual
) b
where a.c1(+)= b.c1 and a.c2= b.c2(+)

demonstrates that the outer join is really between the tables, not between the
join predicates. So does ANSI syntax. Plus you can express full outer join.

          Followup:

Agreed -- I like the new style for its "documentation" purposes.

Many times people give me 5 page queries -- with 10 tables.  Step 1 - pull the
joins out from the predicate so I can see what goes with what.

Ansi style joins will do step 1 for me.

      Rating: 4
Outer join with
      3 tables  March 10, 2002
Reviewer:  sivababu
       from GERMANY
Hello TOM,
     I traversed all the outer join here. But i coudn't know how to write the
query for following table.

TABLE T1:
 column A
    1
    2
    3
    4
    5
    6
TABLE T2
 COLUMN A   COLUMN B  COLUMN C
    1         10        10
    2         10        20
    3         10        30
TABLE T3
   COLUMN A           COLUMN C
      3                 10
      4                 20
      5                 30
I need the result like as follows:

    A               B           C
    1               10          10
    2               10          20
    3               10          30
    3               null        10
    4               null        20
    5               null        30
    6               null        null

I can achive with the following statement.
     select  t2.a, t2.b , t2.c from t2
union
select t3.a,null,t3.c from t3
union
select t1.a,null,null from t1
Where not exists (
select 1 from t2 where t1.a = t2.a union select  1 from t3 where t1.a = t3.a)

Is there any other way to do it?. B'cos our table has more than 1 lakh rows in
t1, t2 and t3. It will take more time to fetch the record thru this way.

We are using oracle 8i and oracle 9i.

          Followup:

sorry -- the question you are asking requires literally a TON of work -- a ton.

Looks like a problem in the data model more then anything else.

You can try:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select t1.a, x.b, x.c
  2  from t1, (select a, b, c from t2 UNION ALL select a, to_number(null), c
from t3 ) x
  3  where t1.a = x.a(+)
  4  /

         A          B          C
---------- ---------- ----------
         1         10         10
         2         10         10
         3         10         10
         3                    10
         4                    20
         5                    30
         6

7 rows selected.

which should be more efficient then the above.

      Rating:
      4
thanks  March 11, 2002
Reviewer:
      sivababu  from GERMANY
Hello TOM,
    Thanks very much. I need this way. B'cos my production tables are like this.

regards,
sivababu

      Rating: 5
full outer
      join, AND and WHERE predicate  August 27, 2002

Reviewer:  A reader
Hi Tom

I have a further query regarding full outer joins in 9i. I tried these two
syntax

select empno, b.deptno
from emp a full outer join dept b on (a.deptno = b.deptno)
and empno=7934

select empno, b.deptno
from emp a full outer join dept b on (a.deptno = b.deptno)
where empno=7934

I get two different results! What´s the difference between using an AND and a
WHERE after outer join condition?

          Followup:

Consider:

scott@ORA920.US.ORACLE.COM> select empno, b.deptno
  2  from emp a full outer join dept b on (a.deptno = b.deptno)
  3  and empno=7934
  4  /

     EMPNO     DEPTNO
---------- ----------
      7369
      7499
      7521
      7566
      7654
      7698
      7782
      7788
      7839
      7844
      7876
      7900
      7902
      7934         10
                   40
                   30
                   20

17 rows selected.

scott@ORA920.US.ORACLE.COM> select empno, b.deptno
  2  from emp a full outer join dept b on (a.deptno = b.deptno and empno=7934)
  3  /

     EMPNO     DEPTNO
---------- ----------
      7369
      7499
      7521
      7566
      7654
      7698
      7782
      7788
      7839
      7844
      7876
      7900
      7902
      7934         10
                   40
                   30
                   20

17 rows selected.

scott@ORA920.US.ORACLE.COM>
scott@ORA920.US.ORACLE.COM>
scott@ORA920.US.ORACLE.COM> select empno, b.deptno
  2  from emp a full outer join dept b on (a.deptno = b.deptno)
  3  where empno=7934
  4  /

     EMPNO     DEPTNO
---------- ----------
      7934         10

scott@ORA920.US.ORACLE.COM>

In your first query -- you outer joined on (a.deptno = b.deptno AND empno =
7934).  To that -- you applied NO where clause.

In the second query, you outer joined on ( a.deptno = b.deptno) AND THEN to that
result you applied the predicate empno = 7934 (meaning you didn't need a full
outer join at all -- just a RIGHT outer join would do)

      Rating: 5
I msut be dumb
      but I dont get it  August 27, 2002
Reviewer:
      A reader
Hi Tom

I am probably dumb but how can you join a.deptno=deptno and empno=7934

may be if you could rewrite

select empno, b.deptno
from emp a full outer join dept b on (a.deptno = b.deptno and empno=7934)

to the old style SQL (with right outer join) so I might be able to understand it
:-(

          Followup:

there was no FULL OUTER join in the past.

It is sort of like outer joining to a constant:

scott@ORA920.LOCALHOST> select dept.deptno, dname, ename, job
  2    from emp, dept
  3   where dept.deptno = emp.deptno(+)
  4      and 'SALESMAN' = emp.job(+)
  5   order by 1
  6  /

DEPTNO DNAME          ENAME      JOB
------ -------------- ---------- ---------
    10 ACCOUNTING
    20 RESEARCH
    30 SALES          ALLEN      SALESMAN
    30 SALES          WARD       SALESMAN
    30 SALES          MARTIN     SALESMAN
    30 SALES          TURNER     SALESMAN
    40 OPERATIONS

7 rows selected.

scott@ORA920.LOCALHOST> spool off

That says "give me every DEPT record and if there is a mate in EMP with a job of
salesman - let me see that as well"

      Rating: 4
Query to get
      resolved  August 28, 2002
Reviewer:  Vikas
      Khanna  from india
SELECT
RT.XID,NVL(RL.TEXT,RLFB.TEXT),NVL(RL.REVERSE_NAME,RLFB.REVERSE_NAME),RT.SYMMETRIC
,          RT.TRANSITIVE,RT.INHERIT,RT.AUTOMATIC,RT.XTENSION_XID
FROM MAM_RELATION_TYPES RT,MAM_RELATION_LANGUAGES RL,
MAM_RELATION_LANGUAGES RLFB
WHERE RT.XID > 4 AND RL.RELATION_TYPE_XID(+) = RT.XID
AND RL.LANGUAGE_ID(+) = 4 AND RLFB.RELATION_TYPE_XID = RT.XID AND
RLFB.LANGUAGE_ID = 1

What is the meaning of RL.LANGUAGE_ID(+) = 4 in this Query as the outer join is
always wrt the table.columnname. Please explain this concept?

Can uou please rewrite this query for us using Ansi (to be converted and run
well in Oracle 9i)

          Followup:

Think about it.  If instead of

RL.LANGUAGE_ID(+) = 4

it had

RL.LANGUAGE_ID(+) = rt.column_that_has_the_value_of_4_in_it

would it make more sense?  If so, just consider it that way -- what exactly is
the difference between a constant and column in this case?  Nothing really.

from
 MAM_RELATION_TYPES RT left outer join MAM_RELATION_LANGUAGES RL
 on ( rl.relation_type = rt.xid and rl.language_id = 4 )
 inner join MAM_RELATION_LANGUAGES RLFB on ( RLFB.RELATION_TYPE_XID = RT.XID )
where RLFB.LANGUAGE_ID = 1

I believe would do it

      Rating: 4
Help my
      confusion!  October 08, 2002
Reviewer:  Steve
      Jelfs  from UK
Outer joins have always managed to confuse me.  Could you help with this?
SQL> DROP TABLE ACTIVITY_TEST;

Table dropped.

SQL> CREATE TABLE ACTIVITY_TEST (
  2    ACTIVITY_ID  NUMBER,
  3    NAME         VARCHAR2 (30),
  4    MORDER       NUMBER (3),
  5    BGCOLOR      VARCHAR2 (7),
  6    TEXT_COLOUR  VARCHAR2 (7) ) ;

Table created.

SQL> INSERT INTO ACTIVITY_TEST ( ACTIVITY_ID, NAME, MORDER, BGCOLOR,
  2  TEXT_COLOUR ) VALUES (
  3  1, 'Key Kids events', 1, '#9999CC', '#000000');

1 row created.

SQL> DROP TABLE EVENT_TEST;

Table dropped.

SQL> CREATE TABLE EVENT_TEST (
  2    ID             NUMBER        NOT NULL,
  3    START_DATE     DATE,
  4    END_DATE       DATE,
  5    GROUP_ID       NUMBER,
  6    FRANCHISE_ID   NUMBER,
  7    ACTIVITY_ID    NUMBER,
  8    USER_ID        NUMBER,
  9    PLACE          VARCHAR2 (50),
 10    NOTES          VARCHAR2 (2000),
 11    LAST_UPDATED   DATE,
 12    COUNTRY_ID     VARCHAR2 (4),
 13    ACTIVITY_NAME  VARCHAR2 (100),
 14    TIME           VARCHAR2 (30),
 15    TASKFORCE_ID   NUMBER ) ;

Table created.

SQL> INSERT INTO EVENT_TEST ( ID, START_DATE, END_DATE, GROUP_ID, FRANCHISE_ID,
ACTIVITY_ID, USER_ID,
  2  PLACE, NOTES, LAST_UPDATED, COUNTRY_ID, ACTIVITY_NAME, TIME, TASKFORCE_ID )
VALUES (
  3  1011,  TO_Date( '10/01/2002 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),
TO_Date( '10/01/2002 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM')
  4  , 3, 0, 1, 360, NULL, NULL,  TO_Date( '01/23/2002 06:16:34 PM', 'MM/DD/YYYY
HH:MI:SS AM')
  5  , '1', 'Halloween', NULL, NULL);

1 row created.

SQL> INSERT INTO EVENT_TEST ( ID, START_DATE, END_DATE, GROUP_ID, FRANCHISE_ID,
ACTIVITY_ID, USER_ID,
  2  PLACE, NOTES, LAST_UPDATED, COUNTRY_ID, ACTIVITY_NAME, TIME, TASKFORCE_ID )
VALUES (
  3  6222,  TO_Date( '10/12/2002 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),
TO_Date( '10/12/2002 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM')
  4  , 3, 1, 1, 101, NULL, NULL,  TO_Date( '06/19/2002 02:01:45 PM', 'MM/DD/YYYY
HH:MI:SS AM')
  5  , '1', 'Great Activity', NULL, NULL);

1 row created.

SQL> DROP TABLE COUNTRY;

Table dropped.

SQL> CREATE TABLE COUNTRY (
  2    COUNTRY_ID  VARCHAR2 (4),
  3    COUNTRY     VARCHAR2 (30),
  4    IMAGE_NAME  VARCHAR2 (30),
  5    ALT_NAME    VARCHAR2 (20),
  6    SHORT_CODE  VARCHAR2 (3) );

Table created.

SQL> INSERT INTO COUNTRY ( COUNTRY_ID, COUNTRY, IMAGE_NAME, ALT_NAME,
  2  SHORT_CODE ) VALUES (
  3  '1', 'United Kingdom', 'uk_flag_curve.gif', 'United Kingdom', 'UK');

1 row created.

SQL> INSERT INTO COUNTRY ( COUNTRY_ID, COUNTRY, IMAGE_NAME, ALT_NAME,
  2  SHORT_CODE ) VALUES (
  3  '2', 'France', 'france_flag_curve.gif', 'France', 'FR');

1 row created.

SQL> INSERT INTO COUNTRY ( COUNTRY_ID, COUNTRY, IMAGE_NAME, ALT_NAME,
  2  SHORT_CODE ) VALUES (
  3  '3', 'Germany', 'gsa_flag_curve.gif', 'GSA', 'DE');

1 row created.

SQL> INSERT INTO COUNTRY ( COUNTRY_ID, COUNTRY, IMAGE_NAME, ALT_NAME,
  2  SHORT_CODE ) VALUES (
  3  '4', 'Spain', 'iberia_flag_curve.gif', 'Iberia', 'ES');

1 row created.

SQL> INSERT INTO COUNTRY ( COUNTRY_ID, COUNTRY, IMAGE_NAME, ALT_NAME,
  2  SHORT_CODE ) VALUES (
  3  '5', 'Belgium', 'benelux_flag_curve.gif', 'Benelux', 'BE');

1 row created.

SQL> INSERT INTO COUNTRY ( COUNTRY_ID, COUNTRY, IMAGE_NAME, ALT_NAME,
  2  SHORT_CODE ) VALUES (
  3  '6', 'Switzerland', 'gsa_flag_curve.gif', 'GSA', 'CH');

1 row created.

SQL> INSERT INTO COUNTRY ( COUNTRY_ID, COUNTRY, IMAGE_NAME, ALT_NAME,
  2  SHORT_CODE ) VALUES (
  3  '7', 'Sweden', 'nordic_flag_curve.gif', 'Nordic', 'SE');

1 row created.

SQL> INSERT INTO COUNTRY ( COUNTRY_ID, COUNTRY, IMAGE_NAME, ALT_NAME,
  2  SHORT_CODE ) VALUES (
  3  '8', 'Norway', 'nordic_flag_curve.gif', 'Nordic', 'NO');

1 row created.

SQL> INSERT INTO COUNTRY ( COUNTRY_ID, COUNTRY, IMAGE_NAME, ALT_NAME,
  2  SHORT_CODE ) VALUES (
  3  '9', 'Netherlands', 'benelux_flag_curve.gif', 'Benelux', 'NL');

1 row created.

SQL> INSERT INTO COUNTRY ( COUNTRY_ID, COUNTRY, IMAGE_NAME, ALT_NAME,
  2  SHORT_CODE ) VALUES (
  3  '10', 'Luxembourg', 'benelux_flag_curve.gif', 'Benelux', 'LU');

1 row created.

SQL> INSERT INTO COUNTRY ( COUNTRY_ID, COUNTRY, IMAGE_NAME, ALT_NAME,
  2  SHORT_CODE ) VALUES (
  3  '11', 'Italy', 'italy_flag_curve.gif', 'Italy', 'IT');

1 row created.

SQL> INSERT INTO COUNTRY ( COUNTRY_ID, COUNTRY, IMAGE_NAME, ALT_NAME,
  2  SHORT_CODE ) VALUES (
  3  '12', 'Finland', 'nordic_flag_curve.gif', 'Nordic', 'FI');

1 row created.

SQL> INSERT INTO COUNTRY ( COUNTRY_ID, COUNTRY, IMAGE_NAME, ALT_NAME,
  2  SHORT_CODE ) VALUES (
  3  '13', 'Denmark', 'nordic_flag_curve.gif', 'Nordic', 'DK');

1 row created.

SQL> INSERT INTO COUNTRY ( COUNTRY_ID, COUNTRY, IMAGE_NAME, ALT_NAME,
  2  SHORT_CODE ) VALUES (
  3  '0', 'Europe', 'europe_flag_curve.gif', 'Europe', NULL);

1 row created.

SQL> INSERT INTO COUNTRY ( COUNTRY_ID, COUNTRY, IMAGE_NAME, ALT_NAME,
  2  SHORT_CODE ) VALUES (
  3  '100', 'None', NULL, NULL, NULL);

1 row created.

SQL> INSERT INTO COUNTRY ( COUNTRY_ID, COUNTRY, IMAGE_NAME, ALT_NAME,
  2  SHORT_CODE ) VALUES (
  3  '14', 'Poland', 'poland_flag_curve.gif', NULL, 'PL');

1 row created.

SQL> INSERT INTO COUNTRY ( COUNTRY_ID, COUNTRY, IMAGE_NAME, ALT_NAME,
  2  SHORT_CODE ) VALUES (
  3  '15', 'Austria', 'gsa_flag_curve.gif', 'GSA', 'AT');

1 row created.

SQL> INSERT INTO COUNTRY ( COUNTRY_ID, COUNTRY, IMAGE_NAME, ALT_NAME,
  2  SHORT_CODE ) VALUES (
  3  '16', 'Emerging Markets', 'emerging_flag_curve.gif', 'Emerging Markets',
NULL);

1 row created.

SQL> INSERT INTO COUNTRY ( COUNTRY_ID, COUNTRY, IMAGE_NAME, ALT_NAME,
  2  SHORT_CODE ) VALUES (
  3  '17', 'Portugal', 'iberia_flag_curve.gif', 'Iberia', 'PO');

1 row created.

SQL> DROP TABLE FRANCHISE_TEST;

Table dropped.

SQL> CREATE TABLE FRANCHISE_TEST (
  2    ID        NUMBER,
  3    NAME      VARCHAR2 (50),
  4    BGCOLOR   VARCHAR2 (10),
  5    XLOB      VARCHAR2 (1)  NOT NULL,
  6    CALENDAR  VARCHAR2 (1)  NOT NULL ) ;

Table created.

SQL> INSERT INTO FRANCHISE_TEST ( ID, NAME, BGCOLOR, XLOB, CALENDAR ) VALUES (
  2  1, 'Franchise 1', '#FFFF33', 'N', 'Y');

1 row created.

SQL> INSERT INTO FRANCHISE_TEST ( ID, NAME, BGCOLOR, XLOB, CALENDAR ) VALUES (
  2  0, 'None', '#FFFFFF', 'N', 'Y');

1 row created.

SQL> commit;

Commit complete.

SQL> insert into activity_test Values(2,'School Events',2,'#FF9999','#000000');

1 row created.

SQL> commit;

Commit complete.

SQL> col name format a20
SQL> col activity_name format a20

SQL> select a.name, e.activity_name
  2  from activity_test a, event_test e
  3* WHERE a.activity_id = e.activity_id

NAME                 ACTIVITY_NAME
-------------------- --------------------
Key Kids events      Halloween
Key Kids events      Great Activity

SQL> ed
Wrote file afiedt.buf

  1  select a.name, e.activity_name
  2  from activity_test a, event_test e
  3* WHERE a.activity_id = e.activity_id(+)
SQL> /

NAME                 ACTIVITY_NAME
-------------------- --------------------
Key Kids events      Halloween
Key Kids events      Great Activity
School Events

SQL> ed
Wrote file afiedt.buf

  1  select a.name, e.activity_name, f.bgcolor
  2  from activity_test a, event_test e, country c, franchise_test f
  3  WHERE a.activity_id = e.activity_id(+)
  4  AND   c.country_id = e.country_id
  5* AND   e.franchise_id = f.id
SQL> /

NAME                 ACTIVITY_NAME        BGCOLOR
-------------------- -------------------- ----------
Key Kids events      Halloween            #FFFFFF
Key Kids events      Great Activity       #FFFF33

What I really need to see is the entry for School events with nulls for the
activity_name and BGCOLOR, but adding the extra tables seem to "lose" them!!

          Followup:

You need to keep on outer joining is all.

When we outer join FROM a to e, all columns of E will be NULL when we "make up a
row".

Hence, when you join E to C and E to F, the predicate is "and c.country_id =
NULL and NULL = f.id".  that'll never "work"

try

  3  WHERE a.activity_id = e.activity_id(+)
  4  AND   c.country_id(+) = e.country_id
  5* AND   e.franchise_id = f.id(+)

      Rating: 4
OK - managed it
      with some thought  October 09, 2002
Reviewer:
      Steve  from uk
I tried -
select a.name, e.activity_name, f.bgcolor
from activity_test a, event_test e, country c, franchise_test f
WHERE a.activity_id = e.activity_id(+)
AND   c.country_id(+) = e.country_id
AND   e.franchise_id = f.id(+)

And that brings back what I need.  Amazing what sleeping on a problem can do.
Still don't fully understand what's happening though!

      Rating:
      5
D'oh  October 09, 2002
Reviewer:
      Steve  from UK
Thanks Tom.  Didn't see your reply until after my last post - but the
explanation helps a great deal.

Cheers

      Rating:
      3
  June 18, 2003
Reviewer:
      A reader

      Rating:
      5
Excellent  July 03, 2003
Reviewer:
      Satya  from LA, CA, USA
I remember using multiple left joins / multiple tables with outer joins long
time back. I was not able to recollect the syntax I tried looking into SQL
Reference and Code Tip archives for a long time. Finally, tried here and got my
solution in the first search itself.

Good information with nice search facility.

      Rating: 5
Strange
      thing  September 26, 2003
Reviewer:
      A reader
Hi Tom,

I used your example and tried to rewrite it, but the following two returns very
different result. Could you help explain? Thanks.

SQL> ed
Wrote file afiedt.buf

  1   select *
  2*    from (((( a inner join b on a.x = b.x ) left outer join (c inner join d
on c.x = d.x) on a.x = c.x )))
SQL> /

         X          X          X          X
---------- ---------- ---------- ----------
         1          1

SQL> ed
Wrote file afiedt.buf

  1   select *
  2*    from (((( a inner join b on a.x = b.x ) left outer join c  on a.x = c.x
inner join d on c.x = d.x)))
SQL> /

no rows selected

SQL>

          Followup:

you change the order of operation.  it is like removing parens from a predicate
and ANDs and ORs

(a inner join b) left outer join (c inner join d)

is like

AB left outer join CD

whereas

(a inner join b) left out join c inner join d

is like

(AB left outer join c) inner join D

ABC inner join D

totally totally different.

      Rating: 4
ANSI join
      problem  November 11, 2003
Reviewer:  Tony
       from India
What is wrong with the following query?:

1)SQL> SELECT S.DNAME, S.LOC, S1.EMPNO, S1.ENAME, S1.JOB, S1.SAL, S1.DEPTNO,
S1.GRADE, S2.GRADE, S2.LO
SAL, S2.HISAL
  2  FROM (SCOTT.DEPT S FULL OUTER JOIN
  3         (SCOTT.EMP S1 FULL OUTER JOIN SCOTT.SALGRADE S2 ON
S2.GRADE=S1.GRADE)
  4       ON S1.DEPTNO=S.DEPTNO) ;
FROM (SCOTT.DEPT S FULL OUTER JOIN
            *
ERROR at line 2:
ORA-00600: internal error code, arguments: [kokbtns1], [], [], [], [], [], [],
[]

Why do I get internal error?

2) SELECT
d.deptno,d.dname,d.loc,e.empno,e.ename,e.deptno,e.sal,e.grade,s.grade,s.losal,s.h
isal
FROM dept d FULL OUTER JOIN emp e ON(d.deptno=e.deptno) FULL OUTER JOIN salgrade
s

ON(e.grade=s.grade);

Are query 1 and 2 the same?

          Followup:

1) contact support.

2) no.  in #1, all of the rows in EMP would appear.  in #2, all of the rows in
DEPT would appear.  The results would be different.

November 11, 2003 Michael -- Thanks for the question regarding
      "Fine Grained Access ERROR on INSERT when generating unique keys", version
      9.0.1

      You Asked
I'm using VPD/Fine Grained Access Control (FGAC) to implement security on my 9i
backend. I created a security policy function that returns the predicate 'owner
= USER'; - each of the tables has an additional column titled OWNER which
contains the name of the logged-in user. Every time a user inserts a record, a
BEFORE INSERT trigger fires (for every row) and inserts the USER name into the
OWNER column. This is fairly straightforward and ensures that users can see only
their rows. Using the DBMS_RLS.add_policy procedure, I attached the security
policy to several tables and made it effective upon SELECT, UPDATE, INSERT, and
DELETE statements.

However, the frontend Java application (custom-made) upon INSERT generates
unique IDs (sequences are not used...I know that this would be better) by
selecting max(ID)+1 from the primary key columns of the tables. The problem is
that the predicate is appended to the SELECT max(ID)+1 query to limit the
max(ID) to only those rows where 'owner = USER'. Therefore, the max(ID)
generated is not the largest ID for the entire table, but only the largest among
the USER rows.

So unless that USER happens to have the the largest ID in the whole table (and
it has worked then), a primary-key violation error will be returned and the
INSERT operation will be aborted.

How can I allow every USER to select from AND get the absolute largest ID from
the PK column without allowing that user to select records that don't belong to
him? If I had developed the application, I would have made use of sequences on
the back-end to generate unique primary key IDs. Unfortunately, I don't have
this option and must work with the Java application as is. Is there a way to
"trick" Oracle into selecting the MAX(id) from the entire table while enforcing
the security-policy function and the predicate restriction at the same time?

NOTE: the front-end Java application understands only the base table names, NOT
Views created by me on the server. If the answer to this problem involves views,
how can I make use of them on the backend when the front-end code does not
recognize them?

I have already posted this question on OTN forums and on Metalink, but still no
solution.

Your help is greatly appreciated!

Michael

      and we said...
FIRE you java programmers and hire some DATABASE PROGRAMMERS.

You have an issue with concurrency as well here -- two people can and will
generate the same ID -- even without VPD.  Just takes two people working at the
same time.

You cannot have your cake and eat it to.  You have asked us to "not show these
rows to anyone" and are coming back later and saying "well, maybe you can show
them if they ask this particular question".

If the java program only uses that max(id) for this insert and then discards it
-- you could simply supply your OWN unique id in the trigger.  That is, figure
out the max(id) right now, create a sequence that starts higher then that in the
database, add to the trigger that maintains the OWNER column this:

   select your_seq.nextval into :new.id from dual;

You would solve the issue -- this will ONLY work if the java guys do not use the
results of that max(id) for any other purpose other then to insert.

Your real solution -- throw out this piece of "lovely code" that was written by
people knowing not much about databases and start over.
Review &
      Followup

      Rating: 5
Fine Grained
      Access when generating unique keys  March 06, 2002

Reviewer:  Michael Honig  from New York, NY USA

Thanks Tom,

This response certainly shed light on the issue. Unfortunately, I don't have the
authority to fire the Java programmers, but only to work with their mistakes.
There is one method in the whole Java program that generates the unique IDs. It
is called by other methods throughout the 100K plus lines of code. If the method
could make use of an Oracle sequence, then perhaps the problem could be solved.
After all, the other parts of the program don't care how the primary key is
generated, as long as it is generated.

      Rating: 5
Application
      name?  November 11, 2003
Reviewer:  Andy
       from Black Forest, Germany
I'm in a very similar situation, where I'm extending app X to operate with a
database as the back end instead of using binary files (!). Our solution was
what you suggested, namely to completely re-write the code in question so that
the primary key production is not dependent on reading rows I shouldn't have
access to. Another idea had occurred to me though: would it have been possible
to programme the FGAC so that it is application-specific i.e. to leave my
security logic in app X, use FGAC to apply the same logic for ANY OTHER
application that wants to access the data (i.e. everything except app X). I know
that that is probably a supremely bad idea in that creates more problems
(security logic in two places, split between DB and client etc.) but - in
principle - is it possible to use FGAC to apply/exclude security policies only
for named apps?

          Followup:

yes, yes it is..

November 11, 2003 -- Thanks for the question regarding "Who is
      locking my row(s)?", version 7.3.4

      You Asked
In my PL/SQL code, when I select for update, if anybody else is working & has
been locked what I want to update, I want my program to wait for n seconds for
the row(s) to be released, if released then continue updating, if still not
released after n seconds, detects the user who is locking the resource and
informs me that person. I am writing pseudo code like this:

    select xxx for update yyy
       wait n seconds
           message (the user USER is locking the record RECORD)

Thanks.

      and we said...
You cannot tell WHO is locking your record

You can only tell WHO MIGHT BE (the entire set of people that might be).

Oracle does not maintain an expensive list of all row locks in the system
anywhere.  That would inhibit greatly

o scalability
o performance
o concurrency

(as it does in SQLServer and DB2 and others).  Locks are an attribute of the
data, not stored in a serialized data structure as they do.  (if you are
interested in the details, you can look at my book -- i go into this i a large
amount of depth)

Now, your general logic can be implemented:

declare
    resource_busy   exception;
    pragma exception_init( resource_busy, -54 );
    success boolean := False;
begin

  for i in 1 .. 3
  loop
     exit when (success);
     begin
           select xxx from yyy where .... for update NOWAIT;
           success := true;
     exception
           when resource_busy then
               dbms_lock.sleep(1);
     end;
   end loop;

   if ( not success ) then
         raise_application_error( -20001, 'row is locked by another session' );
   end if;

Review
      & Followup

      Rating: 3
Learn a lot,
      but one question...  July 05, 2002
Reviewer:
      jimmy lee  from P.R.China
in above scripts, variable success always true. Because "select ... from ...
where... for update nowait" never raise a error "resource busy",just return "0
rows selected";

BTW, "select xxx from yyy where .... for update NOWAIT;"
perhaps should add "execute immediate" as dynamic sql.

It's my pleasure.

          Followup:

Your wrong.  select * from update nowait will raise an ORA-54, resource busy.

That is what NOWAIT does.  Try it and see.

Why use execute immediate?  It is static sql.  sigh....

In one session I execute:

ops$tkyte@ORA817DEV.US.ORACLE.COM> lock table emp in exclusive mode;

Table(s) Locked.

In another, I run:

ops$tkyte@ORA817DEV.US.ORACLE.COM> set echo on
ops$tkyte@ORA817DEV.US.ORACLE.COM> set timing on
ops$tkyte@ORA817DEV.US.ORACLE.COM> @test
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      resource_busy   exception;
  3      pragma exception_init( resource_busy, -54 );
  4      success boolean := False;
  5      l_rec   emp%rowtype;
  6  begin
  7
  8    for i in 1 .. 3
  9    loop
 10       exit when (success);
 11       begin
 12             select * into l_rec from emp where rownum=1 for update NOWAIT;
 13             success := true;
 14       exception
 15             when resource_busy then
 16                 dbms_lock.sleep(1);
 17       end;
 18     end loop;
 19
 20     if ( not success ) then
 21           raise_application_error( -20001, 'row is locked by another
session' );
 22     end if;
 23  end;
 24  /
declare
*
ERROR at line 1:
ORA-20001: row is locked by another session
ORA-06512: at line 21

works as advertised.

      Rating: 4
thanks a
      lot  July 05, 2002
Reviewer:  jimmy lee

thank you for your good answer, I learn a lot from it and your great book
"Expert One-On-One". I'm a newcomer for oracle, but I love it.
1.for exclusive lock of full table such as  "lock table XXX in exclusive mode" ,
above scripts work well. another situation, for rows lock  as "select XXX from
YYY for update nowait", it just return "0 rows selected", no ora-00054 returned.
I don't know why.
2. yes,static sql can work well, you say it. but original scritps shoud be
"select XXX into ..." instead of "select XXX From YYY ...". now I guess perhaps
"XXX" omited "into".
thank you again.

          Followup:

1) no they don't, you are mistaken.  Again, in one session issue:

ops$tkyte@ORA817DEV.US.ORACLE.COM> update emp set ename=ename where empno=7369;

1 row updated.

Now, goto another session and:

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      resource_busy   exception;
  3      pragma exception_init( resource_busy, -54 );
  4      success boolean := False;
  5      l_rec   emp%rowtype;
  6  begin
  7
  8    for i in 1 .. 3
  9    loop
 10       exit when (success);
 11       begin
 12             select * into l_rec from emp where empno=7369 for update NOWAIT;
 13             success := true;
 14       exception
 15             when resource_busy then
 16                 dbms_lock.sleep(1);
 17       end;
 18     end loop;
 19
 20     if ( not success ) then
 21           raise_application_error( -20001, 'row is locked by another
session' );
 22     end if;
 23  end;
 24  /
declare
*
ERROR at line 1:
ORA-20001: row is locked by another session
ORA-06512: at line 21

again works as advertised.

It is IMPOSSIBLE for this block to say "0 rows selected".  I am using a select
into without a check for NO_DATA_FOUND.  It would through an error at the very
least for the failed select into (but it doesn't, it throws a resource busy --
trust me)

If you can prove otherwise, lay out the steps bit by bit and show us -- just
like I do.

2) the original script was psuedo code

      Rating: 5
Thank you very
      much and sorry for my mistake.  July 06, 2002

Reviewer:  A reader
Yes, You're right. I made a mistake. thank you for your patient response.

      Rating: 5
A way to reduce
      'resource busy' aborts.  April 23, 2003
Reviewer:
      barbara spiecker  from Phoenix, AZ USA
This logic will enable me to reduce the number of 'resource busy' aborts when I
am re-loading a data mart while others are trying to use it.   THanks!

      Rating:
      3
  June 27, 2003
Reviewer:  Doug

Tom,

What if I have to replace all the code where I want to lock the rows using your
above solution ? One way is I can go adding the code around the cursor/select
stmt. But what if I have to make this change at 20-30 places ? Is there a
generic function that I can write and simply call that ?

Thanks !

          Followup:

it needs to be done around the queries -- 20/30 is not very many.

      Rating: 4
Thanks
      !!!  June 27, 2003
Reviewer:  Doug

Thanks again Tom for your help. 20/30 is not much, but could increase to 200/300
.. then what ? ;) So I thought better ask you and do it right the first time !!!

      Rating: 5
find the locked
      row  November 11, 2003
Reviewer:
      A reader
Hi

Using Enterprise Manager, Lock Manager we can actually find the rowid of locked
rows. However I did a small test now and to be honest the rowid shown in OEM
hasnt got much meaning except it shows the file_id correct... Do you know how
OEM accomplish this task showing us the locked row rowid?

If OEM can, we can too!?

          Followup:

if you have three sessions (the blocker off somewhere in the world, the blockee,
and someone curious) we can do it.   look in v$session... consider if you do
this:

ops$tkyte@ORA920> select ename from emp where empno = 7369 for update;

ENAME
----------
SMITH

ops$tkyte@ORA920> !plus

SQL*Plus: Release 9.2.0.3.0 - Production on Tue Nov 11 07:54:49 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production

ops$tkyte@ORA920> select * from emp where empno = 7369 for update;

that is blocked -- now in yet another session

ops$tkyte@ORA920> select row_wait_obj#, row_wait_file#, row_wait_block#,
row_wait_row# from v$session where username = user;

ROW_WAIT_OBJ# ROW_WAIT_FILE# ROW_WAIT_BLOCK# ROW_WAIT_ROW#
------------- -------------- --------------- -------------
           -1              0               0             0
        57054             13            1042             0
           -1              0               0             0

ops$tkyte@ORA920> l
  1  select empno from emp where rowid =
  2* dbms_rowid.rowid_create( 1, 57054, 13, 1042, 0 )
ops$tkyte@ORA920> /

     EMPNO
----------
      7369

ops$tkyte@ORA920>

November 11, 2003 -- Thanks for the question regarding "date fomat
      containing timezone data", version 8.1.6

      You Asked
I would like to know if it is possible to configure the Oracle data format to
also capture the timezone that date and time orginated.

      and we said...
In Oracle9i release 1 (9.0) and up -- yes.  There is a datatype TIMESTAMP WITH
TIMEZONE that does that.

Before that -- no, the Oracle DATE datatype cannot store that, nor is it
timezone aware.
Review & Followup

      Rating: 3
date fomat
      containing timezone data  August 14, 2002

Reviewer:  Jasbir Kular  from Toronto, Ontairo

Thanks for the info.  Since my company is using 8i and they are sloooowly
migrating to 9i, I might just make my own data type that contains a date and a
timezone field.  Then I can use this user defined data type within my tables.

          Followup:

Well, you might consider using this technical need as a push to migrate to 9i
(you are after all doing some NEW DEVELOPMENT HERE, this isn't maintenance on an
existing system, this is new development -- either a brand new app or version 2
of an existing one)

Just having the TZ stored with a date is virtually useless unless you write the
thousands of lines of code needed to

o support date arithmetic, subtract 5pm EST from 7pm PST.  That should be 5
hours, not 2

o support TZ conversion (and with umpteen timezones -- each with different rules
-- good luck)

      Rating: 4
No point having
      TZ datatype column  August 15, 2002
Reviewer:
      Sajid Anwar  from London
Hi Tom,
As you mentioned two points about TZ. I dont see any reason why one would go for
TimeZone datatype unles and until he/she finds the way to do arithmetic on it.
It really surprised me that ORACLE hasnt given anything for it. Could you please
tell us atleast two good reasons WHY SHOULD WE USE TimeZone datatype?

Regards,
Sajid Anwar

          Followup:

Umm, Oracle9i  -- there is "TIMESTAMP WITH TIMEZONE" -- it does all of that.
So, for the last two releases of Oracle -- you have had this capability.

      Rating: 4
problem
      inserting timestamp timezone  October 24, 2002

Reviewer:  A reader
Hi

I am following some examples from ILT guide and I get this error

SQL> CREATE TABLE times (
  2   ts TIMESTAMP
  3   , tsz TIMESTAMP WITH TIME ZONE
  4   , tlz TIMESTAMP WITH LOCAL TIME ZONE
  5   , oldtime DATE );

SQL> INSERT INTO times VALUES
  2  ( '21-MAY-01 10:00:00.25 AM'
  3  , '21-MAY-01 10:00:00.25 AM CET'
  4  , '21-MAY-01 10:00:00.25 AM'
  5  , '21-MAY-01' );
( '21-MAY-01 10:00:00.25 AM'
  *
ERROR at line 2:
ORA-01830: date format picture ends before converting entire input string

SQL> SELECT DBTIMEZONE, SESSIONTIMEZONE from dual;

DBTIME SESSIONTIMEZONE
------ ---------------------------------------
+02:00 Europe/London

SQL> select * from nls_session_parameters;

PARAMETER                      VALUE
------------------------------ -------------------------------
NLS_LANGUAGE                   ENGLISH
NLS_TERRITORY                  UNITED KINGDOM
NLS_CURRENCY                   £
NLS_ISO_CURRENCY               UNITED KINGDOM
NLS_NUMERIC_CHARACTERS         .,
NLS_CALENDAR                   GREGORIAN
NLS_DATE_FORMAT                YYYY-MON-DD HH24:MI:SS
NLS_DATE_LANGUAGE              ENGLISH
NLS_SORT                       BINARY
NLS_TIME_FORMAT                HH24.MI.SSXFF
NLS_TIMESTAMP_FORMAT           DD-MON-RR HH24.MI.SSXFF
NLS_TIME_TZ_FORMAT             HH24.MI.SSXFF TZR
NLS_TIMESTAMP_TZ_FORMAT        DD-MON-RR HH24.MI.SSXFF TZR
NLS_DUAL_CURRENCY              €
NLS_COMP                       BINARY
NLS_LENGTH_SEMANTICS           BYTE
NLS_NCHAR_CONV_EXCP            FALSE

Anything wrong with my session?!?!?

          Followup:

it is your nls_timestamp_format.  You have it with HH24 -- but you use a string
with HH and AM:

ops$tkyte@ORA920.US.ORACLE.COM> alter session set NLS_TIMESTAMP_FORMAT =
'DD-MON-RR HH24.MI.SSXFF';

Session altered.

ops$tkyte@ORA920.US.ORACLE.COM> select localtimestamp from dual;

LOCALTIMESTAMP
---------------------------------------------------------------------------
24-OCT-02 16.22.39.517361

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> INSERT INTO times VALUES
  2  ( '21-MAY-01 10:00:00.25 AM'
  3  , '21-MAY-01 10:00:00.25 AM CET'
  4  , '21-MAY-01 10:00:00.25 AM'
  5  , '21-MAY-01' );
( '21-MAY-01 10:00:00.25 AM'
  *
ERROR at line 2:
ORA-01830: date format picture ends before converting entire input string

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> alter session set NLS_TIMESTAMP_FORMAT =
'DD-MON-RR HH.MI.SSXFF AM';

Session altered.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> INSERT INTO times VALUES
  2  ( '21-MAY-01 10:00:00.25 AM'
  3  , '21-MAY-01 10:00:00.25 AM CET'
  4  , '21-MAY-01 10:00:00.25 AM'
  5  , '21-MAY-01' );

1 row created.

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

      Rating: 2
wierd
      result  October 25, 2002
Reviewer:
      A reader
format iw returns wierd result.
My query is as follows:

SQL>select to_char(to_date('&dt','dd-mon-yyyy'),'iw') from dual;
Enter value for dt: 31-dec-2002
old   1: select to_char(to_date('&dt','dd-mon-yyyy'),'iw') from dual
new   1: select to_char(to_date('31-dec-2002','dd-mon-yyyy'),'iw') from dual

TO
--
01

SQL> /
Enter value for dt: 30-dec-2002
old   1: select to_char(to_date('&dt','dd-mon-yyyy'),'iw') from dual
new   1: select to_char(to_date('30-dec-2002','dd-mon-yyyy'),'iw') from dual

TO
--
01 (but weeks belongs to year 2002 here it returns week of year 2003)

SQL> /
Enter value for dt: 29-dec-2002
old   1: select to_char(to_date('&dt','dd-mon-yyyy'),'iw') from dual
new   1: select to_char(to_date('29-dec-2002','dd-mon-yyyy'),'iw') from dual

TO
--
52

So any weekly report taken on 30/31 of dec will show a wrong result?Please
discuss.

          Followup:

depends on the number of weeks in the year -- the ISO guys say some days from
last year belong to a week in this year.

it is not wrong.  it is in fact correct.

      Rating: 3
to_char works
      to_date doesn't  January 20, 2003
Reviewer:
      Ram  from OH, USA
Tom,
When I tried in sql/plsus, I'm getting the following error:

SQL> select to_char(sysdate,'CCYYMMDDHHMMSSmmmmmm') from dual; -----> this works

TO_CHAR(SYSDATE,'CCY
--------------------
21030120030129010101

SQL> select to_date('21030120030129010101','CCYYMMDDHHMMSSmmmmmm') from dual;
select to_date('21030120030129010101','CCYYMMDDHHMMSSmmmmmm') from dual
                                      *
ERROR at line 1:
ORA-01820: format code cannot appear in date input format

My vendor exchanges dates in this format.
How to solve this problem.
thanks
Ram

          Followup:

well, just to be clear -- both to_char and to_date are working -- it is just
that the format used for OUTPUT cannot be used for INPUT.

you'll have to subtract 100 years I suppose.  Maybe if you told me how you need
to process this - I would be able to answer better.

ops$tkyte@ORA920> select add_months(
to_date(substr('21030120030129010101',1,14),'YYYYMMDDHH24MISS'),
  2  -1200 ) from dual
  3  /

ADD_MONTH
---------
20-JAN-03

      Rating: 2
date
      format  January 21, 2003
Reviewer:  Ram
       from OH USA
Hi,
The purpose of this is, vendor sends a respone with date and time portion as
mentioned in the above format, which we would like to store in our db (date
column).
If the date and time I have received is more than what I have, I have to update
some info. in that table.
let me know if it's possible.
thanks

          Followup:

I gave you the function to do that?  now what?

      Rating: 5
timezone
      !!  March 04, 2003
Reviewer:  A reader

Hi tom,

  I know from above discussion 9i is the way to go for timezone info. but i
don't have 9i in company.

 I have 8i (8.1.7). and our application has needs to store that info and  sysc
sydny,tokyo, london time,perform calculations and output the data analysis..

 so could you please suggest your view to store data for this type of app.

 I was thinking to have everything in GMT in time dim. and
create another flake dim for conversion

table 1 : time
--------------
timekey number
...
other time info

table 2 : timezone
-------------------
zone      varchar2
name      varchar2
converzion_fn  varchar2

sample values
zone    name   converzion_fn
EST     EST     GMT-5 or (timekey -5)

what do you say ?

any better solution for 8.1.7 ?

      Rating: 3
any solution to
      above question ?  March 04, 2003
Reviewer:
      A reader

          Followup:

My gosh -- give me a brief moment of time to put it together sometimes!

Ok, we went back to our 817 code for a web calendar we built that needed
timezone support.  We did code up a package with extensive timezone support:

http://asktom.oracle.com/~tkyte/timedate.zip
It was authored by Joel Kallman and covers pretty much the globe as far as we
know.  The main routine after you install is:

create or replace package timedate_pkg
as

--
-- For any installation, the following package global variable
-- should be set to the locale of the server where this package
-- is installed (e.g., '1' implies that the database is running in
-- timezone/locale America/New York)
--
g_server_locale constant number := 1;

This converts a date into GMT -- you get the p_locale numbers from the ID
column of the TIMEDATE_LOCALES$ table

function to_gmt_date(
    p_local_date in date,
    p_locale in number )
return date;

Converts gmt to some other timezone

function to_local_date(
    p_gmt_date in date,
    p_locale in number )
return date;

sort of like SYSDATE but returns SYSDATE adjusted to be in the timezone you
ask for

function cur_local_date(
    p_locale in number )
return date;

obvious

function cur_gmt_date
return date;
--

end timedate_pkg;
/

      Rating: 5
Thanks
      !!  March 07, 2003
Reviewer:  A reader

thanks tom it's great !!

 I am late to check back ain't i ?
This is great  thanks !!

      Rating: 5
Looking for
      examples  April 23, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian _ Pakistan
Hi TOM,
Sorry for disturbance. I am trying to understand new time zone feature in 9i but
unable to understand it as lack of some good examples.

I am reading about,
  TIMESTAMP
  TIMESTAMP WITH TIME ZONE
  TIMESTAMP WITH LOCAL TIME ZONE
What is the difference in these and any examples to easily understand these.

I am currently reading the following,

http://www.csis.gvsu.edu/GeneralInfo/Oracle/server.920/a96529/ch3.htm#50740
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96521/whatsnew.htm#977988
Oracle 9i New Features by Robert G. Freeman

          Followup:

timestamp -- that doesn't have a timezone.  It is much like DATE is in Oracle8i
except it supports fractional seconds.

When you insert the time, the timezone is not saved with it -- it is just a
"timestamp".  It'll be a value like "Wed April 4, 2003 10:05:02.123456 am"

A timestamp with timezone stores the timezone as well -- meaning if you were in
California and I was in Virginia (i am, but I've no clue what TZ pakistan is in
;) and we both inserted that same time, yours would be:

Wed April 4, 2003 10:05:02.123456 am PACIFIC

and mine would be:

Wed April 4, 2003 10:05:02.123456 am EASTERN

and if we subtracted them from eachother -- the difference would be 3 hours
since that is the difference between our time zones.  If we just used timestamp
(without timezone) it would have be "0 hours" difference.

With LOCAL time zone just means that

o Data is normalized to the database time zone when it is stored in the
database.

o When the data is retrieved, users see the data in the session time zone.

consider (my database is US/Eastern by the way...)

ops$tkyte@ORA920.US.ORACLE.COM> create table t ( x timestamp, y timestamp with
time zone, z timestamp with local time zone )
  2  /

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> alter session set time_zone = 'US/PACIFIC'
  2  /

Session altered.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> insert into t values ( systimestamp,
systimestamp, systimestamp );

1 row created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select * from t;

X
---------------------------------------------------------------------------
Y
---------------------------------------------------------------------------
Z
---------------------------------------------------------------------------
23-APR-03 07.47.16.451473 PM
23-APR-03 07.47.16.451473 PM -04:00
23-APR-03 04.47.16.451473 PM

X is without timezone
Y is with timezone and that is 7.47 with a GMT minus 4 offset (4 hours from GMT)

Z is with timezone and the 7.47 that was inserted was "normalized", or input as
eastern time (since systimestamp is the SERVERS time) and upon retrieval
converted to MY timezone -- pacific -- for me.

ops$tkyte@ORA920.US.ORACLE.COM> delete from t;

1 row deleted.

ops$tkyte@ORA920.US.ORACLE.COM> insert into t values ( localtimestamp,
localtimestamp, localtimestamp );

1 row created.

ops$tkyte@ORA920.US.ORACLE.COM> select * from t;

X
---------------------------------------------------------------------------
Y
---------------------------------------------------------------------------
Z
---------------------------------------------------------------------------
23-APR-03 04.50.06.474172 PM
23-APR-03 04.50.06.474172 PM US/PACIFIC
23-APR-03 04.50.06.474172 PM

these all come out the same since I used my own timezone but...

ops$tkyte@ORA920.US.ORACLE.COM> alter session set time_zone = 'US/CENTRAL';

Session altered.

ops$tkyte@ORA920.US.ORACLE.COM> select * from t;

X
---------------------------------------------------------------------------
Y
---------------------------------------------------------------------------
Z
---------------------------------------------------------------------------
23-APR-03 04.50.06.474172 PM
23-APR-03 04.50.06.474172 PM US/PACIFIC
23-APR-03 06.50.06.474172 PM

you can see the difference now with the timezone vs local timezone...

      Rating: 5
One
      more  April 24, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
Great examples to understand timestamp feature. Now one more question is if in
"Timestamp with Local time zone" there is no zone

LOCALTIMESTAMP
------------------------------
24-APR-03 05.06.36.000001 PM

So how it is recognizing on client and giving the correct time or it is placing
time zone info hidden.

Say on the server I have set time zone with '-05:00' while on client it is
'+05:00'.

          Followup:

clients have a timezone (see my alter session) and the database has a timezone.

The database takes the date in its timezone and converts it to whatever timezone
the client asked for (has set)

that is the reason for the difference when I set the client timezone to
US/Central for example -- the database returned the timestamp in a time relative
to MY local timezone -- adjusted from the databases timezone.

      Rating:
      5
Great...  April 24, 2003
Reviewer:
      Sikandar Hayat  from Pindi Bhattian - Pakistan
Thanks TOM I got your point and timestamp concept.

      Rating:
      5
  June 02, 2003
Reviewer:
      A reader
Hi Tom,
What is the differene between systimestamp and localtimestamp function ?

Thanks

          Followup:

documentation is amazing for stuff like this:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/functions64a.htm#999875
LOCALTIMESTAMP returns the current date and time in the session time zone in
a value of datatype TIMESTAMP. The difference between this function and
CURRENT_TIMESTAMP is that LOCALTIMESTAMP returns a TIMESTAMP value while
CURRENT_TIMESTAMP returns a TIMESTAMP WITH TIME ZONE value.

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/functions130a.htm#999454
SYSTIMESTAMP returns the system date, including fractional seconds and time
zone of the system on which the database resides. The return type is TIMESTAMP
WITH TIME ZONE.

      Rating:
      4
v$timezone_names  June 23, 2003
Reviewer:
      phil
in the view v$timezone_names , what does the TZABBREV  actually represent ?

how can America/New_York and Australia/Sydney be EST ?

if i wanted to create a record with a "timestamp with  time zone" for sydney ...
how can i use the abbreviation. When I insert a record with an EST timezone the
db tells me that I am -4 hours .... which is not sydney !

thanks

          Followup:

Australia uses it too.  is the the abbreviation and there are many "duplicates".
 It takes a locale and a tz abbreviation to know what is really means.

suggestion -- use long names to avoid any sense of confusion.

      Rating:
      5
ora-01830  July 23, 2003
Reviewer:
      Tatiane
Tom, I am getting an intermittent ora-01830 when I run the code below. It may
run several times without an error, but sometimes there it is, for the SAME
data.

There are occasions when I run it, the error comes up -- I reload the page
(using the refresh button on the browser) and then the page appears without the
error. Very strange, isn't it ?

This code has run in development, uat and this error happens only in production.
They are all 9.2.0.1. I searched Metalink for some clue, but I was not able to
find anything suggestive.

This code generated the error:

BEGIN pkg_cad_eqpto.insere_eqpto( '2', 'prm - safs2', '192.252.250.090', '', '',
'23/07/2003', '1', 'PGR/SAFS', '46', '1342', '921', '1342', '', '', '1', '3',
'165', :id_eqpto, :cod_erro, :msg_erro); END;

CREATE OR REPLACE  PACKAGE BODY "API_EQUIP"."PKG_CAD_EQPTO" is

-- ----- insere_eqpto -----------

procedure insere_eqpto
(
 p_id_status   number,
 p_nome        varchar2,
 p_ip          varchar2,
 p_ip_nat      varchar2,
 p_end_no      varchar2,
 p_d_ativ      varchar2,
 p_id_tipo     number,
 p_sig_centro  varchar2,
 p_id_modelo   number,
 p_id_propriet number,
 p_id_cliente  number,
 p_id_emp_manut  number,
 p_versao_sw   varchar2,
 p_obs         varchar2,
 p_caract      varchar2,
 p_finalidade  varchar2,
 p_login       varchar2,
 p_id_eqpto out number,
 p_cod  out number,
 p_msg  out varchar2
)
is

  v_caract     pkg_global.table_of_number:=pkg_global.table_of_number();
  v_finalidade pkg_global.table_of_number:=pkg_global.table_of_number();

  v_d_ativ        date := to_date(p_d_ativ,'DD/MM/YYYY');
  v_id_eqpto      number;
  v_id_op         number;
  v_id_eqpto_hist number;
  v_cnt           number;

  nome_existe  exception;

begin
.
.
.

Apparently, the error ocurrs in:
  v_d_ativ        date := to_date(p_d_ativ,'DD/MM/YYYY');

Thanks for your help.

          Followup:

no versions or anything -- but -- contact support for this one.

      Rating:
      3
ora-01830  July 24, 2003
Reviewer:
      Tatiane
I said above:

They are all 9.2.0.1.

You said above:

no versions or anything -- but -- contact support for this one.

:(

      Rating: 4
one more
      question  July 24, 2003
Reviewer:  bachina
       from bangalore
i am getting the date as yyyyww i.s 200330 (year and week of the year) i want to
change it to ddmmyyyy where ddmm are the first date of that week and month is
the month in which that week is?
can you help me in this regard

          Followup:

PLEASE, test this -- here are 3 different ways - they'll all behave differently
on boundary values, TEST this to make sure the one you pick is the one that
works:

ops$tkyte@ORA920LAP> column d new_val D
ops$tkyte@ORA920LAP> select to_char( sysdate, 'yyyyww' ) d from dual;

D
------
200330

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select trunc(to_date( substr('&d',1,4), 'yyyy' ), 'y') from
dual;
old   1: select trunc(to_date( substr('&d',1,4), 'yyyy' ), 'y') from dual
new   1: select trunc(to_date( substr('200330',1,4), 'yyyy' ), 'y') from dual

TRUNC(TO_
---------
01-JAN-03

ops$tkyte@ORA920LAP> select trunc(trunc( to_date( substr('&d',1,4), 'yyyy'
),'y') + (substr('&d',5)-1)*7,'w') from dual;
old   1: select trunc(trunc( to_date( substr('&d',1,4), 'yyyy' ),'y') +
(substr('&d',5)-1)*7,'w') from dual
new   1: select trunc(trunc( to_date( substr('200330',1,4), 'yyyy' ),'y') +
(substr('200330',5)-1)*7,'w') from dual

TRUNC(TRU
---------
22-JUL-03

ops$tkyte@ORA920LAP> select trunc(trunc( to_date( substr('&d',1,4), 'yyyy'
),'y') + (substr('&d',5)-1)*7,'iw') from dual;
old   1: select trunc(trunc( to_date( substr('&d',1,4), 'yyyy' ),'y') +
(substr('&d',5)-1)*7,'iw') from dual
new   1: select trunc(trunc( to_date( substr('200330',1,4), 'yyyy' ),'y') +
(substr('200330',5)-1)*7,'iw') from dual

TRUNC(TRU
---------
21-JUL-03

ops$tkyte@ORA920LAP> select next_day(trunc( to_date( substr('&d',1,4), 'yyyy'
),'y') + (substr('&d',5)-2)*7,'sun') from dual;
old   1: select next_day(trunc( to_date( substr('&d',1,4), 'yyyy' ),'y') +
(substr('&d',5)-2)*7,'sun') from dual
new   1: select next_day(trunc( to_date( substr('200330',1,4), 'yyyy' ),'y') +
(substr('200330',5)-2)*7,'sun') from dual

NEXT_DAY(
---------
20-JUL-03

      Rating: 4
restricting the
      week form 1-52  July 25, 2003
Reviewer:
      srinivasa rao bachina  from bangalore
Hi Tom,
 Thanks for quick reply,

my req will suit with

select trunc(trunc( to_date( substr('&d',1,4), 'yyyy'
),'y') + (substr('&d',5)-1)*7,'iw') from dual;

and one more constraint in that is the week should be in bet 1-52.
if we give 209972 it should give the error,but is returning 10/5/2100
can you please help me in this case also?

          Followup:

add a CASE or DECODE statement in there to do that

case when substr( '&d',5) between 1 and 52 then trunc(trunc(.....)
     else to_date( '99999999', 'mmddyyyy' )
end

for example

      Rating:
      5
  August 05, 2003
Reviewer:
      A reader

      Rating:
      5
current_timestamp  November 10, 2003

Reviewer:  pasko  from Hamburg,Germany
Hi Tom,

thanks for a Great thread above..
I have a similar problem , whereby a coleague of mine had coded something like
this:

------
select to_char( current_timestamp , 'YYYY-MM-DD HH24:MI:SS.FF')  into
l_local_variable
from dual ;
------
First of all, why do you discourage selecting from Dual, like my colleague did
above, because
I have read in your site that ,
the above should be coded as follows in the Declaration Section:
------
--i want Timestamp as a String because this will be padded --with some other
Characters later:

l_local_variable  varchar2(32) :=
to_char( current_timestamp , 'YYYY-MM-DD HH24:MI:SS.FF') ;
-----

So , whats the trouble with the selecting from Dual ?

--
But my Real problem is that there are different clients which are connecting to
Oracle 9iR2 Database from different machines, all located also in Germany...
and we have observed that , this function stores wrong Timestamps..sometimes 1
Hour ahead.

So i read the Docs and told him  that current_timestamp is also influenced by
session or Database Time zones and that he should have used systimestamp
instead.

I tried to test by connecting through all the Clients locally using sqlplus ,
but i couldn't reproduce the wrong timestamps.

So , we were wondering where the this error could be coming from.

If the client Time-Zones are not set, how does this function behave ?
Does it take the dbtimezone instead?

Actually , after we get the timestamp value , we have to further convert it to
GMT , taking into account the daylight savings...

I have searched for some Time-Zone Names from the View :
v$timezone_names , but could not find the one suitable for Germany.

In JAVA, i had once used Time-Zone 'Europe/Berlin', but this is not listed in
the View.

Do you have any idea as to what Time-Zone should be used in Germany ?

I want to later use the Function : FROM_TZ to convert my Timestamps to GMT.

I hope you will consider my Question.

Thanks in advance.

With Best Regards.

          Followup:

when a direct assignment would work:

  x := y;

why would you confuse the issue by selecting it?

  select y into x from dual;

when:

  x := y;

is what you want -- that is what you should code.

My server is in EST, my client is faking to be in PST:

[tkyte@tkyte-pc tkyte]$ echo $TZ
PST
[tkyte@tkyte-pc tkyte]$ sqlplus scott/tiger@ora920

SQL*Plus: Release 9.2.0.4.0 - Production on Mon Nov 10 20:57:18 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.4.0 - Production

scott@ORA920PC> select current_timestamp, systimestamp from dual;

CURRENT_TIMESTAMP
---------------------------------------------------------------------------
SYSTIMESTAMP
---------------------------------------------------------------------------
10-NOV-03 08.57.22.615300 PM +00:00
10-NOV-03 03.57.22.615241 PM -05:00

scott@ORA920PC> select to_char( current_timestamp , 'YYYY-MM-DD HH24:MI:SS.FF'),
  2   to_char( systimestamp, 'YYYY-MM-DD HH24:MI:SS.FF') from dual;

TO_CHAR(CURRENT_TIMESTAMP,'YY TO_CHAR(SYSTIMESTAMP,'YYYY-MM
----------------------------- -----------------------------
2003-11-10 20:57:42.347983    2003-11-10 15:57:42.347958

See the differences -- BUT -- if i use a direct connect (fork/exec the dedicated
server), it picks up my TZ:

[tkyte@tkyte-pc tkyte]$ echo $TZ
PST
[tkyte@tkyte-pc tkyte]$ plus

SQL*Plus: Release 9.2.0.4.0 - Production on Mon Nov 10 20:58:30 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.4.0 - Production

ops$tkyte@ORA920PC> select dbtimezone from dual;

DBTIME
------
-05:00

ops$tkyte@ORA920PC> select current_timestamp, systimestamp from dual;

CURRENT_TIMESTAMP
---------------------------------------------------------------------------
SYSTIMESTAMP
---------------------------------------------------------------------------
10-NOV-03 08.58.49.140554 PM +00:00
10-NOV-03 08.58.49.140502 PM +00:00

so, make sure you use a dedicated server with the "right" TZ when you are
testing

not sure what TZ you want to use in germany -- do the 3 character codes look
familar?  do you use daylight savings (if not, the Etc/GMT-n ones look good)

      Rating: 5
what if the $TZ
      is not set  November 11, 2003
Reviewer:
      pasko  from HH,Germany
Hi Tom,

Thank you very much for the above Response..

Actually, i also wanted to know how would the current_timestamp behave when my
$TZ is not set .

Would it then consider the dbtimezone ?

I note  that my dbtimezone is false :  it should be +01:00

SQL> select sessiontimezone , dbtimezone from dual ;

SESSIONTIMEZONE
---------------------------------------------------------------------------
DBTIME
------
+01:00
+02:00

In Germany ,  we use Time-Zone  MEZ.

Some machines have :
CET for Central European Time or
MET for Mid-European Time

Example :

--Our $TZ is not set
SQL> host echo $TZ

SQL> host date
Tue Nov 11 13:22:53 MET 2003

--in another machine,a few minutes later ,
SQL> host date
Tue Nov 11 13:26:18 CET 2003

SQL>

Another question , is that, would the Daylight Savings Time be taken into
account , if i set the DBTIMEZONE explicitly with the alter database command ?

I have seen the View v$timezone_names has 'MET' and 'CET' in it , but i am not
sure if this will take into account the Daylight Savings Time and also not sure
which one to use if i want to set the dbtimezone for Germany.

Thanks in advance.

Regards.

          Followup:

we get it from the OS -- whatever the OS reports back to us as the time, thats
the time.

for dst to work, you use a timezone setting that obeys dst, then it just
happens, you need not set anything.

sorry - i'm not a "timezone expert" at all -- don't follow all of the rules and
such.  if MET or CET automagically flips times on your computers OS clock, they
would be what you want.  You might want to talk to someone who understands the
nuances of timezones in your area of the world.

November 11, 2003 Gunjeet -- Thanks for the question regarding
      "Generating Web Based Reports", version v8.1.6.0.0

      You Asked
Hi Tom,

We are bundling Oracle Enterprise Edition 8.1.6 with our
product.
We need to now provide some web based reports. Are there
any toolkits that are part of Oracle8i Enterprise Edition
that we can use for this purpose ?

What are the other options ?

Thanks,

Gunjeet

      and we said...
We would use either WebDB or Oracle Reports server for this.

WebDB (Portal) will provide HTML only basic reports.

http://www.oracle.com/ip/develop/ids/index.html?portal.html
Oracle Reports will provide HTML or PDF sophisticated reports.

http://www.oracle.com/ip/develop/ids/index.html?reports.html
...

Review &
      Followup

      Rating:
      5
report  February 27, 2003
Reviewer:
      mo
Tom:

I have one pl/sql procedure that generates an html form for a shipping request.
The form can be printed by clicking on a "PRINT" button.

 If a user logs in and finds that there are 5 requests waiting for him.  Now I
want to print all 5 from the list page without going to each form by clicking a
button.

is there a way to control the browser print function using pl/sql or you would
do this through javascript. Problem with javascript it will print whatever page
is displayed and in this case i need to run 5 forms using pl/sql in the
background.

Any hints?

Thank you,

          Followup:

not with plsql -- plsql runs on the server -- you would have to have plsql write
some javascript of some sort.

Don't know, not much of a javascript programmer

      Rating:
      3
reports  February 27, 2003
Reviewer:
      mo
Tom:

Actually you gave me a good hint.  The print button will be a submit button to a
procedure that reads the various request numbers. BAsed on that it wil create
one big page that has the different forms separated by page breaks. At the end I
will have a javasctipt tags that do a window.print() which will print the 5
forms at once. then I close the page and re-submit the original one where user
was.

      Rating:
      5
reports  March 22, 2003
Reviewer:
      mo
Tom:

I have a few html reports created by pl/sql procedures.  I know you can not
control format a lot by html but I am trying to create a header/footer using
pl/sql.

How would you do it.  Would you count the number of records on each page and put
a page break and a new header every time you hit that count? Would not that work
differently on varios printers?

          Followup:

what is a header and footer in the context of html??

what is a page break in the contet of html??

      Rating:
      5
repots  March 22, 2003
Reviewer:  mo

Tom:

Page breaks can be created in HTML using style tags or style sheets.

What I mean by header and footer is a piece of data that will always print at
the top and bottom of each page.  My question is can you somehow control it
using pl/sql.  can pl/sql control page formats or the only way to do it using a
counter based on number of records?

          Followup:

think about it...

think about how you generate a page with plsql....

you can answer this yourself -- of course you have to code the logic.  PLSQL is
a general purpose programming language, period.

      Rating: 3
generating HTML
      reports  April 15, 2003
Reviewer:  krishna
       from USA
web based reports(html) can be generated from SqlPlus using mark on option.

      Rating: 3
generating HTML
      reports  April 15, 2003
Reviewer:  krishna
       from USA
web based reports(html) can be generated from SqlPlus using mark up option.

      Rating: 5
html
      reports  September 15, 2003
Reviewer:  mo

Tom:

I created headers in an HTML report using pl/sql.  I count 30 records on a page
and then close the table tag, insert a hard page and open a new table tag.

My problem is that one column of the record (item_description) can vary from day
to day.  This results in unordered report.  The page break will not take effect
until it hits every 30th record.

Is there a way I can have pl/sql count number of lines on a page insted of
records before i print a page break?

Thank you,

          Followup:

er?  don't really know what you mean

if by lines you mean "lines the browser displays" -- not really -- that would
purely be a function of the width of the screen.  I run at 3200x1200, but
someone at 1024x768 would see something totally different.

      Rating:
      3
report  September 16, 2003
Reviewer:
      mo
Tom:

Well most users use 800 x 600 resolution. But my objective is to get a header on
each page on a html report.

i print stock number, description, and quantity.  if description is one line
everything comes out fine when i insert a page break/new header every 30 record
count.  Problem is that some items have 2 -6 line descriptions.  This screws up
the printout.  SO I may get a page and a half and then place a page break and
start on the 3rd page.

I  was asking if there is a way i can use pl/sql to count number of lines/words
in description or the end of line in the printout and use a counter and then
base the page break based on that counter (every 66 lines) rather than record
number which will not work in this case.

          Followup:

no, think about it -- if the user changes the size of the window - the browser
wraps things differently.

if the use used "ctl+" or "ctl-" to change the font size -- the browser wraps
things differently.

there is no way, no way -- for you to predict how many lines things will take
unless you start using a <pre> tag (like I do).  But then your users will have
to scroll right and left when they make the windows too small.

for DISPLAY this sounds like you want a frame -- top frame has headers, bottom
frame has data.  user scrolls bottom frame and headers stay there.  Give users a
"print" button to get a report formatted nicely for printing.

      Rating:
      5
report  September 16, 2003
Reviewer:
      mo
Tom:

Thanks for the great hint. But what do you mean by
< Give users a
"print" button to get a report formatted nicely for printing.>

Are you saying use HTML frames for display reports only, and use <PRE> for
printing reports with headers or would the frame also be printed on each page?

Do I need two reports (one display and one for printing)?

          Followup:

yes, that is what i'm saying.

the one that gives you the best "gui" is generally not the same one that gives
you the best "printing" view.

Many web pages have a "view me" and "print me" version.

I'm not saying use the <PRE> tag.  I'm just saying -- consider having two views,
two pages.  one that looks good on screen.  one that looks good on paper.

      Rating: 3
Use
      CSS  September 16, 2003
Reviewer:  Yong Wu

CSS may help you on this

          Followup:

Tyler, friend of Tom here:

1. Check out the following link for page breaks in CSS:

http://www.javascriptkit.com/dhtmltutors/pagebreak.shtml
2. Try the following link to tell you how to use different style rules depending
on whether someone is viewing the page on screen or printint it (@media screen,
@media print):

http://www.w3schools.com/css/css_mediatypes.asp
3. You can't actually print from JavaScript as this would be a security flaw,
you can only popup the print dialogue.

4. This forum is not a good place to find HTML related information.  Your
problem of how to print from Oracle is solved with the PL/SQL Web Toolkit, ie
htp.p .
I would suggest the following links for good HTML resources:

http://www.w3schools.com/
http://alistapart.com/
(check out the archives)

http://zeldman.com/
(he is to html what Tom is to Oracle)

      Rating: 3
oracle
      reports..  September 22, 2003
Reviewer:  vj
       from in
We use oracle 9i r2 database

I have been using Oracle Reports for over 3-4 years and the current product
which we are generating brought up an discussion on what the reporting tool
should be..

the front end is : JSP
middle tier      : websphere
backend          : 9ir2

Since i know the kind of restrictions in crystal reports , business objects i
strongly recommended using ORACLE REPORTS. But i dont know the constraints of
using the oracle reports for the above mentioned arch.

To name a few..

1. Like crytal reports, the report server should run in application server and
nothing should be installed in the client machine except the viewer download

2. Invoking the reporting engine (oracle reports) from the JSP with different
parameter

Any help is highly appreciated..

Rgds

          Followup:

reports runs fully as a middle tier application.

      Rating: 4
i need to know
        September 29, 2003
Reviewer:  huss
       from Egypt
hi tom,
 i need in to know , what is the optimal way to run the report on web
i use web.show_document but i face many problems like send all parameter clear
test attached to the url
plus some users able to copy the url to past it any were to access the rep
without pass throw normal way and etc.

          Followup:

i don't do forms, haven't touched it since 1995.  try otn.oracle.com ->
discussion forums.

      Rating: 2
Report serrver
      and Commercial Applications  September 29, 2003

Reviewer:  Govind Agnihotri  from CA
My two cents -- Reports -- Dollars ???

 The complexity and and the undocumented documentation of the 9ias Report server
will be  too much to handle, unless you have a research group in your think tank
to disect this application.
 The infrastructure, the IASDB  and all the associated processess  take lot of
resources and preseverence to get a hint of( with the help of  IAS architects
mid you).

I would not suggest  this version of reports, probably  lower version are more
sinmpler.
Gov

      Rating: 3
independent
      !!!  November 03, 2003
Reviewer:  vj
       from in
Tom,

First of all thanks for the reponse on "oracle reports..".

as i mentioned, we are trying to use the oracle reports over the other reporting
tool. My client is asking me, do i have to buy an Oracle 9ias just for Oracle
Reports ? (he uses Websphere app server otherwise)

Can i buy just Reports Server only !!!

      Rating: 3
information
      required  November 03, 2003
Reviewer:
      reader
can we design drilldown button in oracle 9i reports.

          Followup:

don't know, don't really use reports myself.  sorry.  you can try the discussion
forums on otn.oracle.com, there is one for developer

      Rating: 5
web
      reports  November 07, 2003
Reviewer:  mo

Tom:

I am trying to test web pdf reporting via report builder.  I downloaded
Developer 6i zip file to c:\reports6i (9i would not run on win 98) unzipped it
in c:\reports6i and when I tried to install using the default home directory it
gave me the message:

"Location specified "c:\OraHome1" is already used as an oracle 817 production" .
 It can not be used as an Oracle Home for 'win95'.

I selected c:\OraHome2 instead and it installed fine.  When I ran report builder
to create a report I could not connect to the database I have. It gave this
message:

REP-0501: Unable to connect to specified database.
ORA-12154. TNS: could not resolve service name.

I checked the tnsnames.ora file in both homes and it has the service for the
database.  In face I always connect using sql*plus without any problem.

Do you know what is the problem here and how I can fix it?

Thank you

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:447620106871
you need to use net.  you must not have the tnsnames.ora in the orahome2 setup
correctly -- that is what that error message is saying exactly.

      Rating:
      5
reports  November 10, 2003
Reviewer:
      mo
Tom:

I am trying to connect from Reports 6i to an oracle database 8.1.7 running on
IBM/AIX.  It is not on windows.

I did a search on tnsnames.ora on the whole C drive and I got this:

C:\OraHome1\network\ADMIN
C:\OraHome1\network\ADMIN\SAMPLE
C:\OraHome2\NET80\ADMIN
C:\OraHome2\NET80\ADMIN\SAMPLE
C:\OraHome2\NET80\TNSAPI
C:\reports6i\win32\SQLNET\8_0_6_0_0-1181195\install\NET80\CLIENT
C:\reports6i\win32\SQLNET\8_0_6_0_0-1181195\install\NET23\CLIENT

I copied the main tnsnames.ora to all of them except the \SAMPLE one.  I connect
fine with the file using SQL*PLUS for windows and SQL NAvigator for windows.
The TNS entry is correct. I also copied the sql*net file.

ISSD.XXX.COM =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = XXX.XXX.XXX.XX)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = ISSD)
    )
  )

I am still getting:
REP-0501: Unable to connect to specified database.
ORA-12154. TNS: could not resolve service name.

Is it failing because the Reports 6i is not compaible with the Net 80 client
software or for other reasons?  Do I have to install another oracle client?

Thank you,

          Followup:

what connect string are you using in the report -- is it issd.xxx.com or just
issd?

also, if you use

(description=(address_list=(address=.....)))

in the connect string (INSTEAD of issd.xxx.com in the report) -- does that work?

      Rating:
      5
reports  November 11, 2003
Reviewer:
      mo
Tom:

I am using "issd" as my connect string for "Database". I did not quite
understand what you mean by your second question.

Strangely when I booted up the PC this morning and tried to connect it worked!!

1. I created a test report based on emp table and saved it as pdf file on the
C:\.  However I could not save it to oracle database which I believe I must do
in order to run it using a link from a web page? or does the report get saved on
the unix directory and the report server call it from there using the database
defined in the server DAD?

2.  Do I have to recompile the RDF file created by Reports 6i for Windows using
Reports 6i compiler on unix?

Thank you,

          Followup:

use issd.xxx.com, your sqlnet.ora must not have had a default domain in the
configuration for reports.

1) i don't do reports, never ran the product.  i do know the report does not
have to be saved into the database -- just deployed to your application server.

2) not sure, try otn.oracle.com -> discussion forums.  there are people there
that use reports every day...

      Rating: 3
jsp based
      reports  November 17, 2003
Reviewer:  reader
       from US
i am trying to use the below example

http://otn.oracle.com/products/reports/htdocs/getstart/examples/simplejsppf/doc/orbr_jsp.pdf
but when i try to execute this from my ie it is not getting executed.
it is just getting haulted in the middle.since i'm trying to run the reports in
the developer machine
i have no where to look for except my OC4J.
how do i get my reports running in IE
thanks
reader

November 11, 2003 Anton -- Thanks for the question regarding
      "DBMS_STATS and MONITORING", version 8i

      You Asked
Hi Tom,

Thanks for taking time out to answer this question.

I am in the process of switching from using ANALYZE to DBMS_STATS and MONITORING
on a schema that has over 6000 tables (PeopleSoft app).  Running the old analyze
on this schema (compute on tables and indexes and indexed columns) takes over 12
hours to complete.  So, I am looking at using DBMS_STATS to update stale
statistics only (which should cut the analyze time down significantly as we
estimate only 10% of the tables should be stale).  My questions are:

1.  Do I need to initially do a full DBMS_STATS gather statistics on the schema
before setting up a daily gather stale stats ?
2.  I understand that the xxx_TAB_MODIFICATIONS table is updated in the
background every few hours or so with the tables' DML counts.  Can I force the
xxx_TAB_MODIFICATIONS table to be up to date before running the DBMS_STATS
gather stale statistics ?
3.  What statistics do you recommend gathering as a minimum (I assume compute on
table, index and index columns) ?

Thanks in advance.

      and we said...
1) no, you just need to have relatively current stats in place and alter the
tables to be Monitoring.

2) you would just alter the tables to be monitoring right after analyzing them
-- then by definition, the dictionary table is up to date.

3) typically table, index and indexed columns -- but then again, Peoplesoft
should be giving you some directions on this.

Also, you can gather stats in parallel on large tables using dbms_stats -- can
be a time saver.
Review & Followup

      Rating: 5
Clarification
      on question 2  December 02, 2001
Reviewer:
      A reader
Hi Tom,

Thanks for your reply.

Just to clarify my question 2.

If I am running a job to execute DBMS_STATS gather stale statistics every day
(at say 2am), can I force Oracle to update the XXX_TAB_MODIFICATIONS table just
before running the DBMS_STATS (so DBMS_STATS will see all stale tables when it
starts)?  My concern is that say if someone does a large data load into table Y
at midnight, but Oracle does not update XXX_TAB_MODIFICATIONS until say 4am then
the 2am DBMS_STATS run will not re-analyze table Y until the next DBMS_STATS run
(24 hours later) and we may be left with sub-optimal CBO decisions (based on
stale stats that should have been updated).

          Followup:

The only way I know of to get the new information forced to disk is to shutdown
the database.

      Rating: 4
Great but
      superficial...  December 18, 2001
Reviewer:
      Alexandre Matthes  from Rio de Janeiro - Brasil
Hi Tom,

I´ve submited a job automating statistics on our database and it is working
well, but I´d like to know how Oracle works (internally) a bit more deep:

- How Oracle knows that an object has stale statistics ???
- What percentage of updates, deletes and inserts an object must have to be
considered by Oracle that it has stale statistics ???
- Does Oracle considers only the number of inserts, updates and deletes to judge
a monitorized object's statistics stale???

Thanks in advance...

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1260600445561

      Rating: 3
about question
      3...  December 19, 2001
Reviewer:  Harri
       from Finland
Index statistics(at least something) is also generated with command 'analyze
table compute statistics'. What is the reason to do separate analyze for indexes
or indexed columns?

          Followup:

If you do analyze table compute statistics it'll do the table and the index (but
not the columns -- no histograms).

You might not want to do the indexes, so we let you say "for table".  That'll
just get the table.

You might want to do the table and the columns -- so we let you say "for table
for all columns" and so on.

You might want to do the table, the indexes and the columns so we let you say
"for table for all indexes for all indexed columns".

You might want to do the table and in 5 other sessions - the 5 indexes, so you
can "analyze table t compute statitistics for table" and in 5 other sessions
"analyze INDEX t_idx compute statistics" at the same time.

Its just for flexibility.

      Rating: 5
Monitoring
      indexes.  November 11, 2003
Reviewer:
      Kashif  from Reston, VA
Hi Tom,

Can MONITORING be turned on for an index to gather stale statistics, or are
index statistics gathered automatically for a table that has stale statistics,
and has MONITORING turned on? I couldn't find anything in the docs clarifying
this; in fact I saw that Monitoring can be turned on for indexes in 9i, but that
seems to only monitor whether an index is being used or not, and does not have
any bearing on statistics gathering.

Kashif

P.S. Congratulations on being selected Oracle Magazine's Oracle Book Author of
the Year! Now you can add 'celebrated prize-winning author' to your list of
accomplishments on the resume... ;)

          Followup:

monitoring is about the table for statistics.  it is keyed off of modifications
to the base table.

if your gather command says "cascade=>true", it'll get the indexes.

November 11, 2003 Nag -- Thanks for the question regarding "LOB",
      version 8.1.7

      You Asked
Tom
I.
1.
create tablelobpracticeTABLE
(DOC_ID                    NUMBER NOT NULL
,CRC32                          VARCHAR2(8) NOT NULL
,IMAGE                          BLOB NOT NULL
) TABLESPACE     practicetablespace      PCTFREE         10  PCTUSED         40
STORAGE  ( INITIAL         100k    NEXT            100K   PCTINCREASE     0 )
LOB (IMAGE)
STORE AS somedoc_blobseg
(tablespace lobpractice
 storage (initial 100K NEXT 100K PCTINCREASE 0)
 disable storage in row
 chunk 32768
pctversion 10
 index lob_IDX
 nocache nologging)

In the above example for creating lobs, what does CHUNK  option m ean. The same
with pctversion and disable storage in row.

2.What value and locator mean with reference to a lob?

3.Look at this question :

The dba creates a table containing two lob columns : a blob and a clob. What is
the maximum number of tablespaces that contain data for this table.

The answer give is 5. Can you please explain how it can be 5.

The answer as given is : The first tablespace stores the inline table data,
including locator data for the lobs. The second and third are used to store the
lob value data. The fourth and firth are used to store index data  for the
LOB's. Indexes are used to manage the contensts of the LOB. SINCE TABLE
PARTITIONING  is not allowed on tables containing lob columns , there can be no
more than 5 tablespaces used .

Tom, can you Explain this question using an example table with two lob's.

II.
Is it true that we can o use a subquery anywhere a
constant could be used.

Can you illustrate this enhancement.

Thank you

Thank you

      and we said...
1) that is a 100% rtm question

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a85397/statem3e.htm#2062077
2) value is the value, locator is the pointer to the value.

3) the max number of tablespaces USED to be 5 in oracle8.0.

In 8i, the correct answer is "unknown" (3 usually without partitioning).
In 8.0, the correct answer is 5.

There is the table (tablespace 1)
There is a blob (tablespace 2)
There is a clob (tablespace 3)
There is a blob INDEX which you USED to be able to have in a different
tablespace from the table but cannot anymore.
There is a clob INDEX which you USED to be able to have in a different
tablespace from the table butt cannot anymore.

ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE TABLE t(
  2  COLLECTION_YEAR     int,
  3  COMPOUND_NUMBER     clob,
  4    constraint HIST_PART_RANGE_LOCPREINDX_PK PRIMARY KEY(COLLECTION_YEAR )
  5    deferrable initially immediate
  6    using index local
  7    storage ( MINEXTENTS 1 MAXEXTENTS UNLIMITED PCTINCREASE 0 )
  8  )
  9  STORAGE ( MINEXTENTS 1 MAXEXTENTS UNLIMITED PCTINCREASE 0 )
 10  PARTITION BY RANGE (COLLECTION_YEAR) (
 11    PARTITION PART_95 VALUES LESS THAN (1996) ,
 12    PARTITION PART_96 VALUES LESS THAN (1997) ,
 13    PARTITION PART_97 VALUES LESS THAN (1998) ,
 14    PARTITION PART_98 VALUES LESS THAN (1999) ,
 15    PARTITION PART_99 VALUES LESS THAN (2000) ,
 16    PARTITION PART_00 VALUES LESS THAN (2001) ,
 17    PARTITION PART_01 VALUES LESS THAN (2002) )
 18  ;

Table created.

shows in 8i you can partition these tables.

II) sure,

ops$tkyte@ORA817DEV.US.ORACLE.COM> select ( select count(*) from dual )
  2    from ( select * from dual )
  3   where (select count(*) from dual) between
  4             (select count(*) from dual) and (select count(*) from dual)
  5    and (select count(*) from dual ) = 1
  6  /

(SELECTCOUNT(*)FROMDUAL)
------------------------
                       1

Review &
      Followup

      Rating: 5
Cant two
      lobs...  January 27, 2002
Reviewer:  Nag

Go into a single table space

thanks

          Followup:

yes, but whats the question there?  the question was -- what is the MAX.

The min is easy -- its one tablespace.  The table, lob indexes and lob segments
can all be in the same.

      Rating: 3
what happens to
      the LOB table space while partitioning the table  July 11,
      2002
Reviewer:  vinci  from USA
Hi Tom,
If i partition the table containing the LOB column as shown above in the
example, then how many LOB table spaces can be there?
Meaning,
Sya I partition the table into 5 partitions based on some range.
Can I partition the LOB column into 5 tablespaes too for that range? Is it
possible...? Syntax would be helpful..
Thanks..

          Followup:

This is a trick I demonstrate in my book (along with lots of others).  When
faced with figuring out such a thing, and not wanting to interpret the N pages
of train tracks for the CREATE TABLE statement -- I turn to exp/imp.

Start with something simple:

ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE TABLE t(
  2  COLLECTION_YEAR     int,
  3  COMPOUND_NUMBER     clob,
  4    constraint HIST_PART_RANGE_LOCPREINDX_PK PRIMARY KEY(COLLECTION_YEAR )
  5    deferrable initially immediate
  6    using index local
  7    storage ( MINEXTENTS 1 MAXEXTENTS UNLIMITED PCTINCREASE 0 )
  8  )
  9  STORAGE ( MINEXTENTS 1 MAXEXTENTS UNLIMITED PCTINCREASE 0 )
 10  PARTITION BY RANGE (COLLECTION_YEAR) (
 11    PARTITION PART_95 VALUES LESS THAN (1996) tablespace users,
 12    PARTITION PART_96 VALUES LESS THAN (1997) tablespace xxx )
 13  ;

Table created.

then, export it (use rows=n if the table has data)

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> host exp userid=/ tables=t

Export: Release 8.1.7.3.0 - Production on Fri Jul 12 07:45:24 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.3.0 - Production
With the Partitioning option
JServer Release 8.1.7.3.0 - Production
Export done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set

About to export specified tables via Conventional Path ...
. . exporting table                              T
. . exporting partition                        PART_95          0 rows exported
. . exporting partition                        PART_96          0 rows exported
Export terminated successfully without warnings.

then, import with indexfile=.  This will not really import, just creates the
DDL for the object

ops$tkyte@ORA817DEV.US.ORACLE.COM> host imp userid=/ tables=t indexfile=t.sql

Import: Release 8.1.7.3.0 - Production on Fri Jul 12 07:45:26 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.3.0 - Production
With the Partitioning option
JServer Release 8.1.7.3.0 - Production

Export file created by EXPORT:V08.01.07 via conventional path
import done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set
. . skipping partition "T":"PART_95"

. . skipping partition "T":"PART_96"

Import terminated successfully without warnings.

this ddl is very very specific ddl -- lets take a look

ops$tkyte@ORA817DEV.US.ORACLE.COM> host cat t.sql

REM  CREATE TABLE "OPS$TKYTE"."T" ("COLLECTION_YEAR" NUMBER(*,0),
REM  "COMPOUND_NUMBER" CLOB) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
REM  LOGGING STORAGE( MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 0)
REM  TABLESPACE "USERS" LOB (COMPOUND_NUMBER) STORE AS (ENABLE STORAGE IN
REM  ROW CHUNK 8192 PCTVERSION 10 NOCACHE STORAGE( BUFFER_POOL DEFAULT))
REM  PARTITION BY RANGE ("COLLECTION_YEAR" ) (PARTITION "PART_95" VALUES
REM  LESS THAN (1996) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
REM  STORAGE(INITIAL 524288 FREELISTS 1 FREELIST GROUPS 1) TABLESPACE
REM  "USERS" NOLOGGING LOB (COMPOUND_NUMBER) STORE AS (TABLESPACE "USERS"
REM  ENABLE STORAGE IN ROW CHUNK 8192 PCTVERSION 10 NOCACHE NOLOGGING
REM  STORAGE(INITIAL 524288 FREELISTS 1 FREELIST GROUPS 1)), PARTITION
REM  "PART_96" VALUES LESS THAN (1997) PCTFREE 10 PCTUSED 40 INITRANS 1
REM  MAXTRANS 255 STORAGE(INITIAL 40960 NEXT 40960 MINEXTENTS 1 MAXEXTENTS
REM  2147483645 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL
REM  DEFAULT) TABLESPACE "XXX" LOGGING LOB (COMPOUND_NUMBER) STORE AS
REM  (TABLESPACE "XXX" ENABLE STORAGE IN ROW CHUNK 8192 PCTVERSION 10
REM  NOCACHE STORAGE(INITIAL 40960 NEXT 40960 MINEXTENTS 1 MAXEXTENTS 505
REM  PCTINCREASE 50 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)) ) ;
REM  ... 0 rows
REM  ... 0 rows
CONNECT OPS$TKYTE;
CREATE INDEX "OPS$TKYTE"."HIST_PART_RANGE_LOCPREINDX_PK" ON "T"
("COLLECTION_YEAR" ) PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(
MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 0) LOGGING LOCAL(PARTITION
"PART_95" PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 524288
FREELISTS 1 FREELIST GROUPS 1) TABLESPACE "USERS" NOLOGGING, PARTITION
"PART_96" PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 40960 NEXT
40960 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 0 FREELISTS 1
FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "XXX" LOGGING ) ;
REM  ALTER TABLE "OPS$TKYTE"."T" ADD CONSTRAINT
REM  "HIST_PART_RANGE_LOCPREINDX_PK" PRIMARY KEY ("COLLECTION_YEAR")
REM  DEFERRABLE ENABLE ;
REM  ALTER TABLE "OPS$TKYTE"."T" ENABLE CONSTRAINT
REM  "HIST_PART_RANGE_LOCPREINDX_PK" ;

and there you go -- you see the syntax for explicitly pointing the lob to
where you want (in fact, you have the specific DDL now, you can see most of the
options and just change them.

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

      Rating: 4
Can i partition
      just the LOB column data and not the whole table?  July 23,
      2002
Reviewer:  vinci  from USA
Hi tom,
Thanks for letting me know an easier way to get the syntax.
Is this possible?:-
I have a table t (
                 col1 varchar2(10),
                 col2 LOB,
                 col3 number(10)
                )
I dont want to partition the whole table.
I just want to hash partition the LOB column into different tablespaces.
Something like-
table t
in tablespace t1
lob(col2) store as
     hash partition 1
     hash partition 2 etc
I dont think this is possible.

The problem I am facing is this:
The table (excluding the LOB column ) is pretty small but has many rows(600000
rows per day).
Only the LOB column is large and occupies more space.
If I need to distribute the LOB column into different table spaces, then I need
to  partition teh table on some key (say col1) and then distribute col2 into the
corresponding partitions.
Now in future if i need to add another column to thsi table and make that column
unique,i cant do it since the partition key of teh table will not have that new
unique column.
What do i do in this case? Merge the partitions and lob tartitions and the
repartition it? Is it a safe way?

          Followup:

Why do you believe you need to distribute the lob across more then one
tablespace?

There are GLOBALLY partitioned INDEXES - in which case the UNIQUE constraint can
be enforced by a GLOBALLY PARTITIONED index -- the index to enforce uniqueness
need not be equi partitioned with the table.

Lobs are equi (locally) partitioned.

      Rating: 4
Create
      tablespace  November 11, 2003
Reviewer:
      Marcio  from br
When you receive a export with clob and was point a tablespace that you wont
create in your database, how can I import that?
I did like it, but I'm looking forward for easy way may be.

ops$t_mp00@MRP817> @asklob
ops$t_mp00@MRP817>
ops$t_mp00@MRP817> drop table t;

Table dropped.

ops$t_mp00@MRP817> drop user a cascade;

User dropped.

ops$t_mp00@MRP817>
ops$t_mp00@MRP817> create table t ( x int constraint t_pk primary key using
index tablespace users,
  2                   lob clob ) tablespace sre
  3   lob ( lob ) store as ( tablespace lob_tbs );

Table created.

ops$t_mp00@MRP817>
ops$t_mp00@MRP817> select segment_name, segment_type, tablespace_name
  2    from user_segments;

SEGMENT_NAME                   SEGMENT_TYPE       TABLESPACE_NAME
------------------------------ ------------------ ------------------------------
T                              TABLE              SRE
SYS_IL0000028763C00002$$       LOBINDEX           LOB_TBS
T_PK                           INDEX              USERS
SYS_LOB0000028763C00002$$      LOBSEGMENT         LOB_TBS

4 rows selected.

ops$t_mp00@MRP817>
ops$t_mp00@MRP817> insert into t
  2  select rownum, 'Value made up to clob '|| rownum
  3    from all_objects
  4   where rownum <= 10;

10 rows created.

ops$t_mp00@MRP817>
ops$t_mp00@MRP817> select * from t;

         X LOB
---------- -------------------------------------------------
         1 Value made up to clob 1
         2 Value made up to clob 2
         3 Value made up to clob 3
         4 Value made up to clob 4
         5 Value made up to clob 5
         6 Value made up to clob 6
         7 Value made up to clob 7
         8 Value made up to clob 8
         9 Value made up to clob 9
        10 Value made up to clob 10

10 rows selected.

ops$t_mp00@MRP817>
ops$t_mp00@MRP817> create user a identified by a
  2  default tablespace users quota unlimited on users
  3  temporary tablespace temp;

User created.

ops$t_mp00@MRP817>
ops$t_mp00@MRP817> grant connect, resource to a;

Grant succeeded.

ops$t_mp00@MRP817> revoke unlimited tablespace from a;

Revoke succeeded.

ops$t_mp00@MRP817>
ops$t_mp00@MRP817> @su a/a
ops$t_mp00@MRP817> set termout off
a@MRP817> set feedback on
a@MRP817> get afiedt.buf nolist
a@MRP817>
a@MRP817> host exp / tables=t file=t.dmp

Export: Release 8.1.7.0.0 - Production on Tue Nov 11 12:10:59 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production
With the Partitioning option
JServer Release 8.1.7.0.0 - Production
Export done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set

About to export specified tables via Conventional Path ...
. . exporting table                              T         10 rows exported
Export terminated successfully without warnings.

a@MRP817> host imp / fromuser=ops$t_mp00 touser=a file=t.dmp

Import: Release 8.1.7.0.0 - Production on Tue Nov 11 12:11:00 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production
With the Partitioning option
JServer Release 8.1.7.0.0 - Production

Export file created by EXPORT:V08.01.07 via conventional path
import done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set
. importing OPS$T_MP00's objects into A
IMP-00017: following statement failed with ORACLE error 1950:
 "CREATE TABLE "T" ("X" NUMBER(*,0), "LOB" CLOB)  PCTFREE 10 PCTUSED 40 INITR"
 "ANS 1 MAXTRANS 255 LOGGING STORAGE(INITIAL 65536) TABLESPACE "SRE" LOB ("LO"
 "B") STORE AS  (TABLESPACE "LOB_TBS" ENABLE STORAGE IN ROW CHUNK 8192 PCTVER"
 "SION 10 NOCACHE  STORAGE(INITIAL 65536))"
IMP-00003: ORACLE error 1950 encountered
ORA-01950: no privileges on tablespace 'SRE'
Import terminated successfully with warnings.

a@MRP817>
a@MRP817> select * from t;
select * from t
              *
ERROR at line 1:
ORA-00942: table or view does not exist

well, here I did copy/paste from error message and re-created the table.

a@MRP817>
a@MRP817> CREATE TABLE "T" ("X" NUMBER(*,0), "LOB" CLOB);

Table created.

a@MRP817> host imp / fromuser=ops$t_mp00 touser=a file=t.dmp ignore=y

Import: Release 8.1.7.0.0 - Production on Tue Nov 11 12:11:01 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production
With the Partitioning option
JServer Release 8.1.7.0.0 - Production

Export file created by EXPORT:V08.01.07 via conventional path
import done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set
. importing OPS$T_MP00's objects into A
. . importing table                            "T"         10 rows imported
About to enable constraints...
Import terminated successfully without warnings.

a@MRP817>
a@MRP817> select * from t;

         X LOB
---------- -------------------------------------------------
         1 Value made up to clob 1
         2 Value made up to clob 2
         3 Value made up to clob 3
         4 Value made up to clob 4
         5 Value made up to clob 5
         6 Value made up to clob 6
         7 Value made up to clob 7
         8 Value made up to clob 8
         9 Value made up to clob 9
        10 Value made up to clob 10

10 rows selected.

a@MRP817> select segment_name, segment_type, tablespace_name
  2    from user_segments;

SEGMENT_NAME                   SEGMENT_TYPE       TABLESPACE_NAME
------------------------------ ------------------ ------------------------------
T                              TABLE              USERS
SYS_IL0000028768C00002$$       LOBINDEX           USERS
T_PK                           INDEX              USERS
SYS_LOB0000028768C00002$$      LOBSEGMENT         USERS

4 rows selected.

Any another way to do that?
thks,
Marcio

          Followup:

that is the only way to do it with multi-segment objects (partitioned tables,
lobs, IOT's with overflows....)

imp cannot deal with how to map an object that spans tablespaces, if you do not
have the same tablespaces, you must precreate it.

November 11, 2003 -- Thanks for the question regarding "JDBC : SQL
      vs PL/SQL, Which performs better", version 8.1.5

      You Asked
Hi Tom:
I have been asked to prove whether SQL performance is better than PL/SQL
performance while being called from java. Following is my setup:
Table trans - 5 million recs, indexes on trans_id,area_code and zip_code
 TRANS_ID                                  NOT NULL NUMBER(12)
 CUSTOMER_ID                                        NUMBER(6)
 AREA_CODE                                          NUMBER(3)
 PHONE_NUMBER                                       NUMBER(7)
 ZIP_CODE                                           NUMBER(5)
 ZIP_ADDL_CODE                                      NUMBER(5)
 TOTAL_MINS                                         NUMBER(6)
 DISCOUNT                                           NUMBER(12,5)
 TOTAL_AMOUNT                                       NUMBER(12,5)
 SESSION_ID                                         NUMBER(16)

Table areamaster - 300 recs - index on area_code
 Name                                      Null?    Type
 AREA_CODE                                 NOT NULL NUMBER(3)
 RATE                                               NUMBER(12,2)

Table zipmaster - 1000 recs - index on zip_code
 ZIP_CODE                                  NOT NULL NUMBER(5)
 ZIP_ADDL_CODE                                      NUMBER(5)
 CITY_NAME                                          VARCHAR2(30)
----------------------------------------------------------------------
java code for standard SQL (meat of it) is
      try
      {
        Class.forName(dbDriver).newInstance();
        Connection conn = DriverManager.getConnection(dbUrl,
                                            dbUser, dbPassword);

        String  sql = "SELECT a.trans_id, a.customer_id, a.phone_number, b.rate,
" +
                       " c.city_name from areamaster b, zipmaster c, trans a" +
                       " where c.zip_code = a.zip_code and " +
                       " b.area_code = a.area_code and c.zip_code = ?" +
                       " order by 1,2 ";

        PreparedStatement stmt = null;
        startTime =  System.currentTimeMillis();
        System.out.println("Start Time: " + startTime);

        for(int i=0;i<101;i++) {
          stmt = conn.prepareStatement(sql);
          stmt.setInt(1,i);
          ResultSet rs = stmt.executeQuery();
          rs.close();
          stmt.close();
        }
        endTime = System.currentTimeMillis();
        System.out.println("End Time: " + endTime);
        timeElapsed = endTime - startTime;
        System.out.println("Time Elapsed: " + timeElapsed);
        System.out.println("******************************");
        conn.close();
----------------------------------------------------------------------
java code for PL/SQL (meat of it) is
      try
      {
        Class.forName(dbDriver).newInstance();
        Connection conn = DriverManager.getConnection(dbUrl,
                                            dbUser, dbPassword);

        startTime =  System.currentTimeMillis();
        System.out.println("Start Time: " + startTime);
        for(int i=0;i<101;i++) {
          stmt = conn.prepareCall("{call s2_pack.s2_proc(?,?)}");
          stmt.setInt(1,i);
          stmt.registerOutParameter(2, OracleTypes.CURSOR);
          stmt.execute();
          rset = ((OracleCallableStatement) stmt).getCursor(2);
          rset.close();
          stmt.close();
        }

        endTime = System.currentTimeMillis();
        System.out.println("End Time: " + endTime);
        timeElapsed = endTime - startTime;
        System.out.println("Time Elapsed: " + timeElapsed);
        System.out.println("******************************");
        System.out.println("End of test");
        conn.close();
        return;
      }
----------------------------------------------------------------------
pl/sql package.proc being called above is (returning ref cursor)

create or replace package s2_pack is
type    trans_rec is record
        (
          trans_id      trans.trans_id%type,
          customer_id   trans.customer_id%type,
          phone_number  trans.phone_number%type,
          rate          areamaster.rate%type,
          city_name     zipmaster.city_name%type
        );
type    transrec_refcurtype is ref cursor return trans_rec;
procedure s2_proc(x_in_zip     in number,
                  x_out_cursor in out transrec_refcurtype);
end;
/
create or replace package body s2_pack is
procedure s2_proc (x_in_zip     in number,
                   x_out_cursor in out transrec_refcurtype)
 as
begin
open    x_out_cursor for
select  a.trans_id,
        a.customer_id,
        a.phone_number,
        b.rate,
        c.city_name
from    areamaster b,
        zipmaster c,
        trans a
where   c.zip_code = a.zip_code         and
        b.area_code = a.area_code       and
        c.zip_code = x_in_zip
order by
        1,2;
end s2_proc;
end s2_pack;
/
----------------------------------------------------------------------

QUESTION IS:

I am getting better performance with the java code calling the SQL
than the one calling the stored proc. Why is that ? The "select" statement in
both is the same. I expect the PL/SQL SP one to perform better. Also, I noticed
that a lot of time is spent in returning the ref cursor in the java code. Is my
test invalid or incorrect ? If so, what is the best way to prove or test that
SP's perform better than standard SQL while being called from java ? Please note
that the above specific case returns 500,000 rows in each case i.e. 5000 rows
per iteration and I ran the above as a single client session on a Unix box. More
specifically the SQL one ended in 108437 milliseconds and the PL/SQL
one ended in 110535 milliseconds. I also analyzed all the tables and indexes and
also did a dbms_shared_pool.keep on the procedure to pin it. Quite frankly, I am
baffled - I must be doing something wrong.
Please advise !

Thanks in advance !

      and we said...
#1.  PL/SQL that just opens a ref cursor and returns it and does nothing could
not possibly be faster then just SQL.  I would expect them to be about the same.
 PL/SQL cannot magically make SQL go faster, you have not reduced the amount of
data on the network, there are no "time based" efficiencies to be gained this
way.

What you DO GAIN with this approach is:

- the owner of the procedure needs SELECT on the table, not the user connecting
to the database via jdbc.  They just need EXECUTE on the process.  This is much
more secure in my opinion.  By putting all of the sql logic into the database,
you avoid people connecting via SQL plus and mucking about with the data -- all
they can do is run your stored procedures.

- it is infinitely easier to fix SQL bugs this way.  Suppose you discover the
query is wrong.  Zap a stored procedure and instantly everyone is running the
new code -- no downloads, no find all installations of the java and update the
classes, no muss, no fuss.  This is huge.

- it is infinitely easier to tune the SQL this way.  I can run the stored
procedure in SQL plus using sql_trace and tkprof (

http://asktom.oracle.com/~tkyte/tkprof.html
) and tune it outside of the application.  I don't have to recompile the java
classes, run them, tune the sql, recompile the java and so on.  Just recreate a
stored procedure and run.

- I can add new functionality at the drop of a hat.  All of a sudden -- you need
to track WHO is running that SQL for security purposes.  I can tweak a stored
procedure in seconds, put it out there and wah-lah -- my changes are in effect
for EVERY user, even if they are already running the application.

and so on.  The advantages of the stored procedure are in the management and
flexibility of it all.  There is absolutely no change that PLSQL executing your
SQL can cause a select to return data faster (or slower)

You should use the stored procedure, it is just a better programming
technique, is more secure and protects you from changes in the long run

#2. 108.437 seconds VERSUS 110.535 seconds shows the two are basicaly very
comparable actually.  You quite simply cannot draw any conclusions from this
test -- it was a draw.  Less then 2 additional WALL CLOCK seconds per 100
executions.  2/100th of a second (0.02).  I would actually put that down to
"noise".  If you run the program a couple of dozen times - sometimes PLSQL would
win, sometimes SQL would win (at least it does on my machine) by a second or
two.   It is not a meaningful difference in execution time.

#3.  Your program is designed poorly from a performance standpoint and doesn't
really test what you need to test (you never FETCH the data!).  Your test case
adds tons of overhead to the application by preparing the statement INSIDE of
the loop.  The entire concept of a callable statement is to remove the need to
soft parse and setup the statement each and every time.  That is like compiling
a subroutine everytime you want to call it -- you would never consider doing
that, so do not do it in sql.

I ran the test below, with these results:

$ java test
JDBC driver version is 8.1.6.2.0
2001-04-14 09:12:34.122:Going to PLSQL_RightWay
2001-04-14 09:14:50.014:Going to SQL_RightWay
2001-04-14 09:17:08.36:done

On this particular invocation, plsql took 135.892 seconds whereas sql took
138.346 seconds (on the prior run, SQL won by about 1.5 seconds, this time it
lost by 2.5 seconds -- next time it might win again)

Here is the SQL for the test:

create or replace package my_pkg
as
    type rc is ref cursor;
    procedure open_cursor
    ( p_count in number, p_cursor in out rc );
end;
/

create or replace package body my_pkg
as
    procedure open_cursor( p_count in number, p_cursor in out rc )
    is
    begin
        open p_cursor
         for select object_name
        from all_objects
       where rownum <= p_count;
    end;
end;
/

And now the java:

import java.sql.*;
import oracle.jdbc.driver.*;
import oracle.sql.*;

class test
{

public static void showTimeStamp( String msg )
{
    System.out.println( (new
       Timestamp(System.currentTimeMillis())).toString() +
                  ":" + msg );
}

static CallableStatement cstmt_persistent;

public static void PLSQL_RightWay(Connection conn ) throws Exception
{
    if ( cstmt_persistent == null )
    {
        cstmt_persistent =
            conn.prepareCall("{ call my_pkg.open_cursor( ?,? ) }" );
        cstmt_persistent.registerOutParameter( 2, OracleTypes.CURSOR );
    }

    cstmt_persistent.setInt( 1, 5000 );
    cstmt_persistent.execute();
    ResultSet rset = (ResultSet)cstmt_persistent.getObject(2);
    while( rset.next() )
    {
        //System.out.println( rset.getString(1) );
    }
    rset.close();
}

static PreparedStatement pstmt_persistent;

public static void SQL_RightWay(Connection conn ) throws Exception
{
    if ( pstmt_persistent == null )
    {
        pstmt_persistent =
            conn.prepareStatement
            ("select object_name from all_objects where rownum <= ?");
    }

    pstmt_persistent.setInt( 1, 5000 );

    ResultSet rset = pstmt_persistent.executeQuery();
    while( rset.next() )
    {
        //System.out.println( rset.getString(1) );
    }
    rset.close();
}

public static void main(String args[])throws Exception
{
    DriverManager.registerDriver
     (new oracle.jdbc.driver.OracleDriver());

    Connection conn=DriverManager.getConnection
      ("jdbc:oracle:thin:@aria-dev:1521:ora816dev"
          ,"scott","tiger");

    DatabaseMetaData meta=conn.getMetaData();
    System.out.println
    ("JDBC driver version is "+meta.getDriverVersion());

    conn.setAutoCommit(false);

    // stmt.execute( "alter session set sql_trace=true" );

    showTimeStamp( "Going to PLSQL_RightWay" );
    for( int i = 0; i < 100; i++ ) PLSQL_RightWay(conn);

    showTimeStamp( "Going to SQL_RightWay" );
    for( int i = 0; i < 100; i++ ) SQL_RightWay(conn);

    showTimeStamp( "done" );
}
}

Review &
      Followup

      Rating: 2
What about
      pre-fetching?  September 10, 2001
Reviewer:
      Niranjan Ramakrishnan  from Sunnyvale, CA USA
Isn't it true that we can do ResultSet pre-fetching when executing SQL directly
from JDBC but *can not* when getting a REF CURSOR from PL/SQL? Won't this mean
additional round-trips for the latter which will make it even less performant?

          Followup:

That is absolutely NOT true.  Consider:

create or replace package demo_pkg
as
    type refcur is ref cursor;

    procedure get_cur( x in out refcur );
end;
/

create or replace package body demo_pkg
as

procedure get_cur( x in out refcur )
is
begin
    open x for select ename from emp;
end;

end;
/

And java:

import java.sql.*;
import java.io.*;
import oracle.jdbc.driver.*;

class curvar
{
  public static void main (String args [])
  throws SQLException, ClassNotFoundException
  {
      String query  = "begin demo_pkg.get_cur( :1 ); end;";

      DriverManager.registerDriver (new oracle.jdbc.driver.OracleDriver());

      Connection conn=
        DriverManager.getConnection
         ("jdbc:oracle:thin:@aria-dev:1521:ora817dev",
          "scott", "tiger");

      Statement sqlTrace = conn.createStatement ();
      sqlTrace.execute( "alter session set sql_trace=true" );
      sqlTrace.close();

      CallableStatement cstmt = conn.prepareCall(query);
      cstmt.registerOutParameter(1,OracleTypes.CURSOR);
      cstmt.execute();

      ResultSet rset = (ResultSet)cstmt.getObject(1);

      while( rset.next() )
        System.out.println( rset.getString(1) );

      rset.close();
      cstmt.close();
  }
}

The tkprof report shows us:

SELECT ENAME
FROM
 EMP

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          2          4          14
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.00          0          2          4          14

That is, it took 2 fetches to get 14 rows showing the default prefetch of 10 was
in action with this ref cursor.

      Rating: 4
In my case, the
      ad hoc SQL performs far better than the PL/SQL  May 29, 2003

Reviewer:  Jon Atkinson  from New York, NY

I'm doing something very similar, but the results aren't so close. The ad hoc
SQL executes in about 300ms, while calling the function and iterating thru the
results, even from sql*plus, takes over 2 seconds. The results are the same
(300ms and > 2sec) when I execute the ad hoc SQL or the function from Java over
the JDBC thin driver.

We added some debugging output to the Java code that showed that an inordinate
amount of time is spent in oracle.jdbc.driver.OracleCallableStatement.getCursor(
int ). Does that method just create the ref cursor, send it over the wire, and
cast it to a Java ResultSet? Or does it also execute the SQL up to the first
returned row?

The SQL is the same in both cases, except that the function uses parameterized
variables in the SQL, instead of changing constants. Thus this approach is far
more scalable because it's using bind variables. But overall it's no more
scalable, because of the performance hit of the ref cursor.

We're using a function that returns a ref cursor, instead of a procedure that
takes a ref cursor variable as an OUT parameter. I don't think this will make
any difference, but I'll try it that way to be sure.

I can understand that the PL/SQL can't execute SQL faster than the SQL itself.
But it seems like there is substantial overhead with ref cursors, and it's not
caused by the JDBC thin driver or the network.

Are we missing something here? Are there other approaches (like returning a
PL/SQL collection) that would verify that the ref cursor is the cause of the
performance hit? If not, are there any workarounds to this?

Thanks for your time.

          Followup:

reproducible example please?  You've got mine (did you run it?  do you see the
same thing)?

      Rating: 5
sql versus
      pl/sql - what about readability?  June 05, 2003

Reviewer:  R Menon  from CA
Thanx for a great web site &#8211; I am a big fan of yours!

You keep suggesting that if you can do it in sql, do it instead of using
procedural (pl/sql) code &#8211; and I agree.

However, I am wondering whether the readability of code decreases once the
select stmt becomes too complex and we start hitting a point of diminishing
returns. For example, do you think the following query in the procedure
(performance wise it is fine) should be rewritten in procedural code (which
would do it step by step and presumably would be more readable)?

Is the burden on the developer to understand the query (it is reasonably simple
if you break it in steps but it does take considerably more effort than
procedural code )?
What are your thoughts?

---the example query --
procedure get_multi_hosts_comp_summary ( p_job_guid raw,
    p_execution_guid raw, p_cursor out refCursor)
  is
  begin
    open p_cursor for
      select delta_comp_guid, lhs_host_name, rhs_host_name,
        decode ( delta_comp_guid, null, 'BEING_COMPUTED',
          decode ( instr ( hw_result||os_result || oracle_result ||
                  os_registered_sw_result, 'ERROR'), 0,
            decode
            ( instr(hw_result||os_result || oracle_result ||
                    os_registered_sw_result, 'COW_RESULT_DIFFERENT'),
              0,'COW_RESULT_SAME', 'COW_RESULT_DIFFERENT'
            ),
              'ERROR_IN_COMPARISON'
          )
        ) result,
        decode( delta_comp_guid, null, 'BEING_COMPUTED',
          decode( hw_result, 'ERROR', 'HW_COMPARISON_ERROR',
            hw_result ) ) hw_result,
        decode( delta_comp_guid, null, 'BEING_COMPUTED',
          decode( os_result, 'ERROR', 'OS_COMPARISON_ERROR',
            os_result ) ) os_result,
        decode( delta_comp_guid, null, 'BEING_COMPUTED',
          decode( oracle_result, 'ERROR', 'OS_COMPARISON_ERROR',
            oracle_result ) ) oracle_result,
        decode( delta_comp_guid, null, 'BEING_COMPUTED',
          decode( os_registered_sw_result, 'ERROR', 'OS_COMPARISON_ERROR',
            os_registered_sw_result ) ) os_registered_sw_result
      from
      (
        select delta_comp_guid, lhs_host_name, rhs_host_name,
          max(hw_result) hw_result,
          max(os_result) os_result,
          max(oracle_result) oracle_result,
          max(os_registered_sw_result) os_registered_sw_result
        from
        (
          with common_columns as
          (
            select a.job_guid job_guid, map.delta_comp_guid delta_comp_guid,
              a.lhs_host_name lhs_host_name, a.rhs_host_name rhs_host_name
            from
            (
              select  distinct j.job_id job_guid, jp.scalar_value lhs_host_name,

                t.target_name rhs_host_name
              from mgmt_job j,  mgmt_job_exec_summary exs,
                   mgmt_job_target jt, mgmt_targets t,
                   mgmt_job_parameter jp
              where j.job_id = p_job_guid
                and j.job_id = exs.job_id
                and exs.execution_id = p_execution_guid
                and jt.job_id = j.job_id
                and jt.target_guid = t.target_guid
                and jp.job_id = j.job_id
                and jp.execution_id = exs.execution_id
                and jp.parameter_name = 'firstCompareTarget'
            ) a,
            mgmt_comp_id_to_job_id_map map
            where a.job_guid = map.job_guid (+)
              and a.rhs_host_name = map.rhs_host_name(+)
          )
          select c.delta_comp_guid, c.lhs_host_name, c.rhs_host_name,
            decode(max(error_count), 0,
              decode(max(s.different_count+s.left_count+s.right_count), 0,
                'COW_RESULT_SAME', 'COW_RESULT_DIFFERENT'),
                'ERROR'
            )  hw_result,
            null os_result,
            null oracle_result,
            null os_registered_sw_result
          from common_columns c, mgmt_delta_comp_summaries s
          where c.delta_comp_guid = s.delta_comp_guid(+)
            and instr(HOST_CONFIG_HW_VIEW_LIST, s.collection_type) != 0
          group by c.delta_comp_guid, c.lhs_host_name, c.rhs_host_name
          union
          select c.delta_comp_guid, c.lhs_host_name, c.rhs_host_name,
            null hw_result,
            decode(max(error_count), 0,
              decode(max(s.different_count+s.left_count+s.right_count), 0,
                'COW_RESULT_SAME', 'COW_RESULT_DIFFERENT'),
                'ERROR'
            )  os_result,
            null oracle_result,
            null os_registered_sw_result
          from common_columns c, mgmt_delta_comp_summaries s
          where c.delta_comp_guid = s.delta_comp_guid(+)
            and instr('HOST_CONFIG_OS_VIEW_LIST', s.collection_type) != 0
          group by c.delta_comp_guid, c.lhs_host_name, c.rhs_host_name
          union
          select c.delta_comp_guid, c.lhs_host_name, c.rhs_host_name,
            null hw_result,
            null os_result,
            decode(max(error_count), 0,
              decode(max(s.different_count+s.left_count+s.right_count), 0,
                'COW_RESULT_SAME', 'COW_RESULT_DIFFERENT'),
                'ERROR'
            )  oracle_result,
            null os_registered_sw_result
          from common_columns c, mgmt_delta_comp_summaries s
          where c.delta_comp_guid = s.delta_comp_guid(+)
            and instr(ORACLE_TOPLEVEL_VIEW_NAME, s.collection_type) != 0
          group by c.delta_comp_guid, c.lhs_host_name, c.rhs_host_name
          union
          select c.delta_comp_guid, c.lhs_host_name, c.rhs_host_name,
            null hw_result,
            null os_result,
            null oracle_result,
            decode(max(error_count), 0,
              decode(max(s.different_count+s.left_count+s.right_count), 0,
                'COW_RESULT_SAME', 'COW_RESULT_DIFFERENT'),
                'ERROR')  os_registered_sw_result
          from common_columns c, mgmt_delta_comp_summaries s
          where c.delta_comp_guid = s.delta_comp_guid(+)
            and instr('ECM$ORACLE_TOPLEVEL', s.collection_type) != 0
          group by c.delta_comp_guid, c.lhs_host_name, c.rhs_host_name
        ) diff_qry
        group by delta_comp_guid, lhs_host_name, rhs_host_name
      ) diff_qry1;

  end;

          Followup:

i would never rewrite working, fast sql as procedural code.

Tell me -- how would a procedural implementation of that be "more readable" or
understandable. (maybe comments on what it is doing would help -- but that would
be as true for a procedural implementation wouldn't it)

      Rating: 4
JDBC : SQL vs
      PL/SQL, Which performs better  June 05, 2003

Reviewer:  R Menon  from CA
well, since procedural code is done in steps
it is more readable than a huge sql select -
(kinda like a huge function versus one that is split
up into many) - at least that is my opinion.

On the same topic, from the book &#8220;Oracle Performance Planning&#8221;, we
get:
"
When implementing data access (queries) or data
changes (DML), use SQL. This rule is specific to the business logic modules of
code where procedural code is mixed with data access (non-procedural SQL)
code. There is great temptation to put procedural logic into the SQL access.
This
tends to result in poor SQL that is resource-intensive. SQL statements with
DECODE case statements are very often candidates for optimization, as are
statements with a large amount of OR predicates or set operators, such as UNION
and MINUS."

Again this seems to suggest that we should not have huge
selects..

          Followup:

I disagree 100% -- but do what makes you feel "good" and performs "better then
good enough".

I often take tons of procedural code and make it a single "resource intensive"
sql statement.

How can fetching data out - to process in PLSQL (which is an interpreted
language basically) that is running on the same server -- be better in general
then doing it in SQL on that same server without a context switch -- in C?

That query you have would be broken into tons of just as hard to understand
queries (at the aggregate level, looking at the procedure) -- even harder I
would say.  Do this -- write the procedural implementation of that and ask
yourself "if I documented both equally well, which is 'better'"

      Rating: 4
agree with
      you  June 05, 2003
Reviewer:  R Menon
       from CA
hi tom
I will stick to the select statement way of doing
things (as per your suggestion) - you are probably
right- the procedural code also may be complex.
thanx!:)

      Rating:
      5
  June 05, 2003
Reviewer:  Dave
       from Colorado
It seems a truism to say o, but whether it's procedural code or SQL you make it
easier to maintain by adding descriptive comments, right?

It seems too often that a perfectly rational developer can pepper PL/SQL with
expplanatory comments but has some kind of a mental block on commenting within
the sql itself.

Consider the complex SQL example given. What would be wrong with providing
in-line commentary on the real meaning of some of the complex decodes, or the
individual data sets that are union'ed together? I'd bet that if it was broken
down into procedural code it would get commented. Or am I wrong?

          Followup:

I actually find that my SQL is more and more procedural when I look at it.
Especially with inline views.

I tend to build queries from the inside out -- they are like subroutines to me
actually (oh, that might put a date on me -- let me call them "methods")

Here is a cool little "program" that creates a golfing schedule for 8 player
over 7 weeks, randomly assigning them to different groups:

ops$tkyte@ORA920> with players as
  2  ( select 'P'||rownum username
  3      from all_objects
  4     where rownum <= 8),
  5  weeks as
  6  ( select rownum week
  7      from all_objects
  8     where rownum <= 7 )
  9  select week,
 10         max(decode(rn,1,username,null)) u1,
 11         max(decode(rn,2,username,null)) u2,
 12         max(decode(rn,3,username,null)) u3,
 13         max(decode(rn,4,username,null)) u4,
 14         max(decode(rn,5,username,null)) u5,
 15         max(decode(rn,6,username,null)) u6,
 16         max(decode(rn,7,username,null)) u7,
 17         max(decode(rn,8,username,null)) u8
 18   from ( select username,
 19                 week,
 20                 row_number() over (partition by week order by rnd) rn
 21            from ( select username, week, dbms_random.random rnd
 22                     from players, weeks
 23                 )
 24       )
 25   group by week
 26  /

      WEEK U1 U2 U3 U4 U5 U6 U7 U8
---------- -- -- -- -- -- -- -- --
         1 P5 P4 P7 P1 P3 P2 P6 P8
         2 P3 P6 P8 P7 P1 P5 P4 P2
         3 P8 P1 P7 P6 P5 P4 P3 P2
         4 P3 P5 P8 P4 P7 P2 P1 P6
         5 P8 P7 P1 P4 P3 P6 P5 P2
         6 P3 P2 P6 P1 P4 P8 P7 P5
         7 P1 P2 P4 P8 P7 P5 P6 P3

7 rows selected.

U1..U4 is one group, U5..U8 is another....  when asked to do this for a friend
(come up with random assignments over 7 weeks for 8 players), this was actually
the first approach that popped into my head...

comments might be:

o create a set of 7 weeks (weeks)

o create a set of 8 players (players)

o cartesian product weeks with players, so we end up with 56 rows -- each player
  in each week.  Assign Random number to each row in this set

o Now, for each week, sort the players by that random number -- and assign the
  number 1, 2, 3, 4, .. 8 to them after sorting (that is the row_number() )

o then use a standard pivot technique to pivot the results by week.

If you use that in a create table Assignments as <select>, you can use:

ops$tkyte@ORA920> with players as
  2  ( select 'P'||rownum username
  3      from all_objects
  4     where rownum <= 8)
  5  select u1,
  6        max( decode( rn, 1, decode(u2,u1,to_number(null),plays), null )) p1,
  7        max( decode( rn, 2, decode(u2,u1,to_number(null),plays), null )) p2,
  8        max( decode( rn, 3, decode(u2,u1,to_number(null),plays), null )) p3,
  9        max( decode( rn, 4, decode(u2,u1,to_number(null),plays), null )) p4,
 10        max( decode( rn, 5, decode(u2,u1,to_number(null),plays), null )) p5,
 11        max( decode( rn, 6, decode(u2,u1,to_number(null),plays), null )) p6,
 12        max( decode( rn, 7, decode(u2,u1,to_number(null),plays), null )) p7,
 13        max( decode( rn, 8, decode(u2,u1,to_number(null),plays), null )) p8
 14   from ( select u1, u2, plays,
 15                 row_number() over ( partition by u1 order by u2) rn
 16            from ( select a.username u1, b.username u2,
 17                         (select count(*)
 18                            from assignments
 19                           where (select a.username from dual) in
(u1,u2,u3,u4)
 20                             AND (select b.username from dual) in
(u1,u2,u3,u4)
 21                         ) +
 22                         (select count(*)
 23                            from assignments
 24                           where (select a.username from dual) in
(u5,u6,u7,u8)
 25                             AND (select b.username from dual) in
(u5,u6,u7,u8)
 26                         ) plays
 27                    from players a,
 28                         players b
 29                 )
 30        )
 31  group by u1
 32  /

U1  P1  P2  P3  P4  P5  P6  P7  P8
-- --- --- --- --- --- --- --- ---
P1       5   3   1   3   4   2   3
P2   5       1   3   3   4   2   3
P3   3   1       3   3   4   4   3
P4   1   3   3       3   2   6   3
P5   3   3   3   3       2   4   3
P6   4   4   4   2   2       1   4
P7   2   2   4   6   4   1       2
P8   3   3   3   3   3   4   2

8 rows selected.

to see how "fair" the assignments were -- that shows P1 plays with P2 5 times,
P3, 3 times and so on.

I'll let you figure that one out -- it would obviously need comments in real
code -- but I think you'll find it almost procedural in nature if you start from
the inside and work your way out.

      Rating: 5
Agree with
      DaveJDBC : SQL vs PL/SQL, Which performs better   June 05,
      2003
Reviewer:  A reader
yes inline comments would make it much more readable.
would do that for my code - thanx!:)

      Rating: 4
Does whole
      PL/SQL get parsed only during the first invocation from
      java?  July 10, 2003
Reviewer:  Tony Li
       from CA USA
Since you are using bind variables when you invoke PL/SQL from Java side. I
wonder if the whole PL/SQL gets parsed during the first invocation from java.

If this is true, does that mean that there is no need to use dynamic PL/SQL for
parsing individual SQL statement in the PL/SQL package? Because the whole
package has been parsed during the first invocation.

Thanks,

Tony

          Followup:

plsql is stored compiled, so a plsql package/procedure/function is already
parsed and compiled.

what gets parsed at runtime is your "begin p; end;" block -- not P, just that
anonymous block.

Now, if you plsql routine does sql, that sql will be parsed as normal.  an
initial soft (probably) parse and then plsql caches that cursor so subsequent
executions of it don't parse at all.

if you do dynamic sql, then the sql is parsed EVERY time you invoke it (unless
you use dbms_sql and parse once, execute many).

using static sql is best, use dynamic sql only when static sql just won't work
at all.

      Rating:
      5
  July 11, 2003
Reviewer:
      A reader

      Rating: 5
My experiment
      just proved what you have told me  July 11, 2003

Reviewer:  Tony Li  from CA, USA
Thanks you so very much for your quick reply and wonderful advice! My experiment
just proved your statement.

Many Thanks!!!

      Rating: 5
JDBC: comparing
      sql in Java versus sql in pl/sql  September 28, 2003

Reviewer:  A reader
Hi Tom
I know you are in favor of moving sql statements
from Java to plsql for various reasons that you already
have mentioned. I was trying to set up a benchmark
to see if there were any performance related
differences between the two cases.

I was not expecting any performance differences.
Anyways, what puzzled me that if I have a code as:
create the callable statement:

for (int i=0; i < 10; i ++)
{
  bind the callable statement
  execute the callable statement (returns a ref cursor)
  ..
}
then the callable statement was getting soft parsed
10 times. I would have expected it to get parsed
only once.  If I put the same select in Java
it got soft parsed only once (since I prepared
the statement only once across the iteration.)

Is there a way to ensure that the same can be
true for plsql case as well? Does this have something
to do with the fact that we are passing an
anonymous block to execute a procedure (there is
no other way ) I set the session_cached_cursors to
100 and still see the same behavior

Menon:)

          Followup:

ref cursors are soft parsed each time -- they have to be.

session_cached_cursor offsets this.

but really -- why would you be in a hard loop getting N result sets back into
java when you could just call a procedure to process N result sets

      Rating: 5
ah! There I go
      again!  September 28, 2003
Reviewer:
      A reader
This is the second time - I forgot about the ref
cursors!!!
Anyways, the only reason I was looping was
to execute the same query again and again so that the
measurement times are big enough to average out.

I do agree - we will never need to do the looping
at the client side in real life. I did realize it
but as I said I was trying to get more reliable benchmark
results.

I guess I have to select a really big table
to do the benchmark then?

Thanx!
Menon:)

          Followup:

depends on what exactly it is you are trying to measure.

If you are trying to measure the client performance -- it would be best to set
up a test driver that called the client over and over and over with different
implementations (one with straight sql, other with plsql calls)

      Rating: 5
JDBC: SQL
      PL/SQL  September 29, 2003
Reviewer:  Zilani
       from Bangladesh
Hi tom
i am greatful for your site. I need a solution for inserting a collection from
java to a table using PL/SQL procedure. I want to write a procedure to do this
and call it from java. The parameter of the procedure may be refcursor/array or
anything like that. I want to get the collection and want to insert by loop in
the procedue. It will be helpfull if you show me an example.
best regards.

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:712625135727

      Rating:
      5
thanx!  September 29, 2003
Reviewer:
      A reader
"depends on what exactly it is you are trying to measure."

Well, I was trying to measure the differences in
two cases:
1. I use a select statement in Java.
2. I use an anonymous block that calls a stored
   procedure from Java doing the same thing that the
   select statement does.

I didn't really expect any performance differences
in the two but wanted to benchmark it anyways.
I "solved" the issue by changing the ref cursor
issue in the select to return a count instead :)
But somehow my results are fairly random!
In some runs they are close to each other in
terms of run time and in terms of latch consumption.
In other runs the plsql case was around 50% slower!!
The database is newly installed (9.2.0.1.0)
Could you kindly take a look at the benchmarks and
see if there is something wrong? I use your
DbmsOutput.java and your runstats_pkg apart from
the following files.

Thanx a ton!
Menon:)

---schema.sql--
rop table t1;
create table t1 as
select sys_guid() key1, owner, object_id, object_type
from all_objects;

commit;
insert /*+APPEND */ into t1
select sys_guid() key1, 'SYS', object_id, object_type from all_objects;

commit;
insert /*+APPEND */ into t1
select sys_guid() key1, 'SYS', object_id, object_type from all_objects;

commit;
insert /*+APPEND */ into t1
select sys_guid() key1, 'SYS', object_id, object_type from all_objects;

alter table t1 add constraint t1_pk primary key ( key1 );
create index t1_idx1 on t1 ( owner );
analyze table t1 compute statistics;
------
------Benchmark.java - uses DbmsOutput.java and
------- your runstats package to compare
------- two approaches - effectively runs
-------your runstats from java and prints results

import java.sql.*;
import oracle.jdbc.OracleDriver;
import oracle.jdbc.OracleConnection;
import oracle.jdbc.OraclePreparedStatement;
import oracle.jdbc.OracleCallableStatement;

public class Benchmark
{
  public static void setSessionCachedCursors ( OracleConnection connection,
    int sessionCachedCursors )
    throws SQLException
  {
    String stmtStr = "alter session set session_cached_cursors=" +
      sessionCachedCursors ;
    Statement stmt = null;
    try
    {
      stmt = connection.createStatement();
      stmt.execute( stmtStr );
    }
    finally
    {
      stmt.close();
    }
  }

  public static void startTrace ( OracleConnection connection )
    throws SQLException
  {
    String setTimedStatisticsStmt = "alter session set timed_statistics=true";
    String setTraceStmt =
      "alter session set events '10046 trace name context forever, level 12'";
    Statement stmt = null;
    try
    {
      stmt = connection.createStatement();
      stmt.execute( setTimedStatisticsStmt );
      stmt.execute( setTraceStmt );
    }
    finally
    {
      stmt.close();
    }
  }

  public static void markStart( OracleConnection connection )
    throws SQLException
  {
    _benchmarkStatementArray[BENCHMARK_START_INDEX].execute();
  }

  public static void markMiddle( OracleConnection connection )
    throws SQLException
  {
    _benchmarkStatementArray[BENCHMARK_MIDDLE_INDEX].execute();
  }

  public static void markEnd( OracleConnection connection )
    throws SQLException
  {
    _benchmarkStatementArray[BENCHMARK_STOP_INDEX].execute();
  }

  public static void closeBenchmarkStatements (
    OracleConnection connection )  throws SQLException
  {
    for( int i=0; i < _benchmarkStatementArray.length; i++)
    {
      _benchmarkStatementArray[i].close();
    }
  }

  public static void prepareBenchmarkStatements (
    OracleConnection connection ) throws SQLException
  {
    _benchmarkStatementArray[BENCHMARK_START_INDEX]=
      (OracleCallableStatement) connection.prepareCall( BENCHMARK_START );

    _benchmarkStatementArray[BENCHMARK_MIDDLE_INDEX]=
      (OracleCallableStatement) connection.prepareCall( BENCHMARK_MIDDLE );

    _benchmarkStatementArray[BENCHMARK_STOP_INDEX]=
      (OracleCallableStatement) connection.prepareCall( BENCHMARK_STOP );
    _benchmarkStatementArray[BENCHMARK_STOP_INDEX].setInt(1,
      BENCHMARK_DIFFERENCE_THRESHOLD);
    _dbmsOutput = new DbmsOutput ( connection );
    _dbmsOutput.enable ( DBMS_OUTPUT_BUFFER_SIZE );
  }

  public static void printBenchmarkResults()
    throws SQLException
  {
    _dbmsOutput.show();
    _dbmsOutput.close();
  }

  private static String BENCHMARK_START = "begin runstats_pkg.rs_start; end;";
  private static String BENCHMARK_MIDDLE = "begin runstats_pkg.rs_middle; end;";
  private static String BENCHMARK_STOP = "begin runstats_pkg.rs_stop(?); end;";

  private static OracleCallableStatement[] _benchmarkStatementArray = new
OracleCallableStatement[3];

  private static DbmsOutput _dbmsOutput;
  private static final int DBMS_OUTPUT_BUFFER_SIZE = 1000000;

  private static final int BENCHMARK_START_INDEX = 0;
  private static final int BENCHMARK_MIDDLE_INDEX = 1;
  private static final int BENCHMARK_STOP_INDEX = 2;
  private static final int BENCHMARK_DIFFERENCE_THRESHOLD = 200;
}
----------TestSqlInJavaOrPlsql.java -----
import java.sql.*;
import oracle.jdbc.OracleDriver;
import oracle.jdbc.OracleConnection;
import oracle.jdbc.OraclePreparedStatement;
import oracle.jdbc.OracleCallableStatement;

public class TestSqlInJavaOrPlsql
{

  public static void main(String[] args) throws SQLException
  {
    long startTime=0, middleTime=0, endTime=0;

    DriverManager.registerDriver( new OracleDriver());
    _checkUsage( args );

    OracleConnection connection = _getOracleConnection();
    Benchmark.setSessionCachedCursors ( connection, 100 );
    Benchmark.prepareBenchmarkStatements ( connection );

    _prepareJavaBasedSqlStmt( connection );
    _preparePlsqlBasedSqlStmt( connection );

    // first execute through Java based statement
    try
    {
      Benchmark.startTrace( connection );
      //startTime = System.currentTimeMillis();
      Benchmark.markStart ( connection );
      _executePlsqlBasedCode ( connection );

      //middleTime = System.currentTimeMillis();
      Benchmark.markMiddle ( connection );
      _executeJavaBasedCode ( connection );
      Benchmark.markEnd ( connection );
      //endTime = System.currentTimeMillis();

      long timeTakenForJava = (middleTime - startTime);
      long timeTakenForPlsql = (endTime - middleTime);
      Benchmark.printBenchmarkResults ();
    }
    finally
    {
      _closeAll( connection );
    }
  }

  private static void _closeAll ( OracleConnection connection )
  {
    try
    {
      _javaBasedStmt.close();
      _plsqlBasedStmt.close();
      Benchmark.closeBenchmarkStatements ( connection );
      connection.close();
    }
    catch ( SQLException e)
    {
      //ignore;
    }
  }

  private static void _checkUsage (String[] args)
  {
    int argc = args.length;
    if( argc > 1)
    {
      System.out.println( "usage: java TestSqlInJavaOrPlsql num_of_iterations");
      System.out.flush();
      System.exit(1);
    }

    if (argc == 1)
    {
      _numOfIterations = Integer.parseInt( args[0] );
    }
  }

  private static OracleConnection _getOracleConnection ()
    throws SQLException
  {
    String connectionURL = "jdbc:oracle:thin:@usunrat24:1521:ora92i";
    OracleConnection connection = (OracleConnection)
      DriverManager.getConnection ( connectionURL, "benchmark", "benchmark");

    connection.setAutoCommit ( false) ;
    return connection;
  }

  private static void _prepareJavaBasedSqlStmt( OracleConnection connection )
    throws SQLException
  {
    String javaBasedSQLStmtString =
      "select /* from java */ count(*) from t1 " +
      " where owner = ?";
    _javaBasedStmt = (OraclePreparedStatement)
      connection.prepareStatement ( javaBasedSQLStmtString );
  }

  private static void _preparePlsqlBasedSqlStmt( OracleConnection connection )
    throws SQLException
  {
    String plsqlBasedSQLStmtString = "begin ? := test_pkg.get_count_data(?);
end;";
    _plsqlBasedStmt = (OracleCallableStatement)
      connection.prepareCall ( plsqlBasedSQLStmtString );
  }

  private static void _executeJavaBasedCode( OracleConnection connection)
    throws SQLException
  {
    ResultSet rset = null;
    int count = 0;
    try
    {
      for( int i=0; i < BENCHMARK_ITERATION_COUNT; i++)
      {
        _javaBasedStmt.setString(1, "SYS");

        rset = _javaBasedStmt.executeQuery();
        int j=0;
        if ( rset.next() )
        {
          count = rset.getInt(1);
        }
        if( i == 0)
          System.out.println ( "Java : result" + count );
      }
    }
    finally
    {
      if ( rset != null )
      {
        try
        {
          rset.close();
        }
        catch (Exception e) {} // ignore;
      }
    }

  }

  private static void _executePlsqlBasedCode( OracleConnection connection)
    throws SQLException
  {
    int count = 0;
    try
    {
      for( int i=0; i < BENCHMARK_ITERATION_COUNT; i++)
      {
        _plsqlBasedStmt.setString( 2, "SYS" );
        _plsqlBasedStmt.registerOutParameter( 1,
oracle.jdbc.OracleTypes.NUMBER);
        _plsqlBasedStmt.execute();
        count = ( int) _plsqlBasedStmt.getInt(1);
        if( i == 0)
          System.out.println ( "Plsql : result" + count );
      }
    }
    finally
    {
      ;
/*
      if ( rset != null )
      {
        try
        {
          rset.close();
        }
        catch (Exception e) {} // ignore;
      }
*/
    }

  }

  private static OracleCallableStatement _plsqlBasedStmt;
  private static OraclePreparedStatement _javaBasedStmt;
  private static int _numOfIterations = 1;
  private static final int BENCHMARK_ITERATION_COUNT = 200;
}
-----

          Followup:

way too much code for me to read, especially written in a foreign language like
that.

You run it lots.
You average the results.

      Rating: 5
sorry - forgot
      one benchmark file  September 29, 2003
Reviewer:
      Menon
---test_pkg.sql
create or replace package test_pkg as
  function get_data ( p_owner_name in varchar2 )
    return sys_refcursor;
  function get_count_data ( p_owner_name in varchar2 )
    return number;
end;
/
show errors
/
create or replace package body test_pkg as
  function get_data ( p_owner_name in varchar2 )
    return sys_refcursor
  is
    l_cursor sys_refcursor;
  begin
    open l_cursor for
      select /* from plsql */ owner, object_id, object_type
      from t1
      where owner = p_owner_name;

    return l_cursor;
  end;

  function get_count_data ( p_owner_name in varchar2 )
    return number
  is
    l_count number;
  begin
    select /* from plsql */ count(*)
    into l_count
    from t1
    where owner = p_owner_name;

    return l_count;
  end;
end;
/
show errors
/
----
Menon:)

      Rating: 4
Broken
      Link  September 29, 2003
Reviewer:  Shree
      420  from like a mouse in the cage i am trapped inside this
      cubicle
Hi Tom,
This link you posted is broken :-(

http://asktom.oracle.com/~tkyte/tkprof.html
Is it possible for you to fix it?
That will really help. Thank you.

          Followup:

fixed

      Rating: 5
RE: Broken
      Link  September 29, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
The problem is the page that is linked to redirects to a non-existant page.
But, really, how hard would it be to look up the documentation with having the
link?

- Mark

      Rating: 5
thanx
      Tom!  September 29, 2003
Reviewer:  Menon

"way too much code for me to read, especially written in a foreign language like

that.  "

No problems - I understand - if I find anything amiss
I will post it here. If anyone wants to take a crack
at reviewing the code - you are more than welcome:)

Menon:)

      Rating: 5
RE: Crack at
      the code  September 29, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Well, I don't think I will take a crack at the code, but I can say that I just
read Cary Millsap's new book (Optimizing Oracle Performance) over the weekend
and I highly, highly recommend it for doing performance investigation.  It gets
very technical and detailed and is at points rigorous (which is what makes it
good), especially the chapter on Queueing Theory, but it is important stuff
nonetheless IMO.  (Not that my opinion should carry much weight mind you)...

- Mark

          Followup:

it is a really cool book, i'm enjoying it.  He put into words so eloquently
things I've been trying to say -- I keep finding myself saying "yeah, you go"
while I'm reading it.

      Rating: 5
thanx
      Mark!  September 29, 2003
Reviewer:  Menon

"Well, I don't think I will take a crack at the code, but I can say that I just
read Cary Millsap's new book (Optimizing Oracle Performance) over the weekend
and I highly, highly recommend it for doing performance investigation.  It ..."

You read it over the weekend?!:) That is amazing - it
must be really good. Well, mine is on its way already!
I hope to utilize it in my own product soon.

Thanx anyways:)
Menon:)

      Rating: 5
great
      book!  October 03, 2003
Reviewer:  Menon

I have not read it fully yet - but it is really an awesome
book! He has taken the usage of sql trace to the next
level and has given scientific proof for all his statements. Once you read it,
it all seems common sense
and that is the sign of a truely great writer!

Tom - do you think his method will become the defacto
performance diagnotic method instead of the ususal
method being followed currently?

Thanx!

          Followup:

it already is, isn't it?

given that the "current method" does not really work....

      Rating: 5
that is true
      !  October 03, 2003
Reviewer:  Menon

but I think his idea of narrowing constraining
the data diagnotic collection process accurately and
also his rigor of focusing on the business objective
is not being followed currently in majority of tuning
projects. This also comes back to your idea of instrumenting your code. Lots of
similarities between
your approach and his approach.

Menon:)

      Rating:
      5
Thank  October 07, 2003
Reviewer:
      Zilani  from Dhaka Bangladesh
Hi Tom
Thanks for your reply.Its great! Your online help is most useful.
Best regards

      Rating:
      4
  November 11, 2003
Reviewer:  Steve
       from New York
Tom, you wrote above,
"i would never rewrite working, fast sql as procedural code."

I definitely agree, however it seems that Steven Feuerstein tends to disagree on
this topic.

See the "2. Break Your SQL Addiction" section at:

http://otn.oracle.com/oramag/oracle/03-nov/o63tech_plsql.html
I'm curious to hear your thoughts on this.
Thanks.

          Followup:

we have to agree to disagree.  it won't be the first time.

If you flip a couple more pages to my article in asktom, same magazine, you'll
see my counter point to that!

different strokes for different folks.  I believe he is a procedural programmer.

I consider myself a database developer.

      Rating: 5
"Break your SQL
      addiction"  November 11, 2003
Reviewer:  Tony
      Andrews  from London, UK
In this article, SF is advocating a "modularised" approach to using SQL.  Maybe
Tom would agree with the purpose but not with the methods?

For instance, the example he gives is using a function to hide the expression
last_name||','||first_name, to protect the application from requirements changes
such as using a space instead of a comma.

An alternative would be to create a view including:

..., last_name||','||first_name as full_name, ...

That achieves the same objective without invoking PL/SQL.

          Followup:

his approach is somewhat orthogonal to what I propose in my article ('where does
a cursor belong')

You can definitely "over modularize".  A view would be much more appropriate
then layers and layers of code.

I do agree with many things there -- like keeping the code to the size that it
fits on the screen!!!

November 11, 2003 Anshu -- Thanks for the question regarding
      "Running procedures dynamically.", version Oracle 8.0.4

      You Asked
Dear Tom,
         I want to create object dynamically . The attributes of
 this object are known only at the runtime ( these attributes
 are the arguments, both IN  and OUT , of the function(s) to be
executed ).Somehow
I'm not able to form this  dynamic Object . Can u tell me how
to make it dynamic .
Moreover can I pass this dynamically created object as an
argument to a procedure ? If so , how ?

OR is there any other way to pass the arguments of the function
as a single entity/block to a procedure , so that I can
disintegrate
them within a procedure and after the execution of the function
(to be executed by this procedure ),
pass THEM back in integrated form as out parameter to this
procedure ?

What I wanna do is something like this :-
*****************************************************
procedure calling_proc(package_name in varchar2 , function_name
IN varchar2, argument_list IN OUT ???? )
is

begin
     return_value := called_function(arg1 , arg2 , .......);
argument_list := It now contains the OUT and IN OUT values.

end;

*********************************************
In the above skeleton code , I can make the function dynamic .
The main problem is of argument_list (which is the combination
of all the parameters of the called_function.

I hope that U can solve this problem .
Thanx
Anshu

      and we said...
As long as you do not mind using VARCHARs for all arguments --
yes we can do this.  In Oracle8.0, we can code a procedure like:

ops$tkyte@8.0> create or replace procedure
  2  run_proc( pkg_name  in varchar2,
  3            proc_name in varchar2,
  4            args      in out dbms_sql.varchar2s )
  5  is
  6      cursor_name     pls_integer default
dbms_sql.open_cursor;
  7      ignore          pls_integer;
  8      l_sql            long;
  9      l_sep            char(1) default '(';
 10      l_end           varchar2(25) default '; end;';
 11  BEGIN
 12      l_sql := 'begin ' || pkg_name || '.' || proc_name;
 13
 13      for i in 1 .. args.count
 14      loop
 15          l_sql := l_sql || l_sep || ':' || i;
 16          l_sep := ',';
 17          l_end := '); end;';
 18      end loop;
 19      l_sql := l_sql || l_end;
 20
 20      dbms_output.put_line( l_sql );
 21      dbms_sql.parse(cursor_name, l_sql, dbms_sql.native);
 22
 22      for i in 1 .. args.count
 23      loop
 24          dbms_sql.bind_variable( cursor_name, ':'||i,
 25                                  args(i), 2000 );
 26      end loop;
 27      ignore := dbms_sql.execute(cursor_name);
 28      for i in 1 .. args.count
 29      loop
 30          dbms_sql.variable_value( cursor_name,
 31                                   ':'||i, args(i) );
 32      end loop;
 33      dbms_sql.close_cursor(cursor_name);
 34  END;
 35  /

Procedure created.

To show how to use it, we'll create a package with 2 different
procedures and then run them

ops$tkyte@8.0> create or replace package my_pkg
  2  as
  3      procedure proc1( p1 in varchar2, p2 in out number );
  4      procedure proc2( arg1 out date,
  5                       p2 in number,
  6                       p3 in number default 2 );
  7  end;
  8  /

Package created.

ops$tkyte@8.0>
ops$tkyte@8.0> create or replace package body my_pkg
  2  as
  3
  3  procedure proc1( p1 in varchar2, p2 in out number )
  4  as
  5  begin
  6      p2 := length( p1 );
  7  end;
  8
  8  procedure proc2( arg1 out date,
  9                   p2 in number,
 10                   p3 in number default 2 )
 11  as
 12  begin
 13      arg1 := sysdate + p2 - p3;
 14  end;
 15
 15  end;
 16  /

Package body created.

This shows how to interface with the runproc package.  We use
an array to similuate "varargs".  This array is IN OUT and will
contain our before and after images of the variables to the
routine

ops$tkyte@8.0> declare
  2      myargs    dbms_sql.varchar2s;
  3      empty    dbms_sql.varchar2s;
  4  begin
  5      myargs(1) := 'Hello World';
  6      myargs(2) := null;
  7
  7      run_proc( 'my_pkg', 'proc1', myargs );
  8      for i in 1 .. myargs.count loop
  9          dbms_output.put_line( 'args(' || i || ') = ' ||
 10                                  myargs(i) );
 11      end loop;
 12      dbms_output.put_line( '-------------------------' );
 13
 13      myargs := empty;
 14      myargs(1) := null;
 15      myargs(2) := 7;
 16      myargs(3) := 2;
 17      run_proc( 'my_pkg', 'proc2', myargs );
 18      for i in 1 .. myargs.count loop
 19          dbms_output.put_line( 'args(' || i || ') = ' ||
 20                                  myargs(i) );
 21      end loop;
 22  end;
 23  /
begin my_pkg.proc1(:1,:2); end;
args(1) = Hello World
args(2) = 11
-------------------------
begin my_pkg.proc2(:1,:2,:3); end;
args(1) = 11-JUN-00
args(2) = 7
args(3) = 2

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating: 5
Extremely
      helpful  September 18, 2001
Reviewer:
      A reader
I have a similar requirement and your response very nicely illustrates an
effective solution.  I was able to take and modify the sample code and have my
solution up and running in about 20 minutes.

THANKS!!!!!

      Rating: 5
Can I give The
      Value Back in A Ref Cursors?  September 25, 2001

Reviewer:  Carlo  from Switzerland
GREAT CODE!

Can you tell me if it is possible to pass the values back in a Ref Cursor?

          Followup:

Sure, we'll just have to use a SQL type (this works in 8.0 and up -- the "open
results" would be a little different in 8.0 using the keyword "THE" instead of
table but otherwise would be the same)

This is the example done in 8i then:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace type argType as table of
varchar2(4000)
  2  /

Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package types
  2  as
  3      type rc is ref cursor;
  4  end;
  5  /

Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure
  2  run_proc( pkg_name  in varchar2,
  3            proc_name in varchar2,
  4            args      in argType,
  5            results   in out types.rc
  6            )
  7  is
  8      cursor_name     pls_integer default dbms_sql.open_cursor;
  9      ignore          pls_integer;
 10      l_sql            long;
 11      l_sep            char(1) default '(';
 12      l_end           varchar2(25) default '; end;';
 13      l_args          argType := argType();
 14  BEGIN
 15      l_args.extend( args.count );
 16      l_sql := 'begin ' || pkg_name || '.' || proc_name;
 17
 18      for i in 1 .. args.count
 19      loop
 20          l_sql := l_sql || l_sep || ':' || i;
 21          l_sep := ',';
 22          l_end := '); end;';
 23      end loop;
 24      l_sql := l_sql || l_end;
 25
 26      dbms_output.put_line( l_sql );
 27      dbms_sql.parse(cursor_name, l_sql, dbms_sql.native);
 28
 29      for i in 1 .. args.count
 30      loop
 31          dbms_sql.bind_variable( cursor_name, ':'||i,
 32                                  args(i), 2000 );
 33      end loop;
 34      ignore := dbms_sql.execute(cursor_name);
 35      for i in 1 .. args.count
 36      loop
 37          dbms_sql.variable_value( cursor_name,
 38                                   ':'||i, l_args(i) );
 39      end loop;
 40      dbms_sql.close_cursor(cursor_name);
 41
 42      open results for select * from TABLE( cast (l_args as argType) );
 43  END;
 44  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package my_pkg
  2  as
  3      procedure proc1( p1 in varchar2, p2 in out number );
  4      procedure proc2( arg1 out date,
  5                       p2 in number,
  6                       p3 in number default 2 );
  7  end;
  8  /

Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package body my_pkg
  2  as
  3
  4  procedure proc1( p1 in varchar2, p2 in out number )
  5  as
  6  begin
  7      p2 := length( p1 );
  8  end;
  9
 10  procedure proc2( arg1 out date,
 11                   p2 in number,
 12                   p3 in number default 2 )
 13  as
 14  begin
 15      arg1 := sysdate + p2 - p3;
 16  end;
 17
 18  end;
 19  /

Package body created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> variable r1 refcursor
ops$tkyte@ORA817DEV.US.ORACLE.COM> variable r2 refcursor
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      myargs    argType;
  3  begin
  4      myArgs := argType( 'Hello world', NULL );
  5      run_proc( 'my_pkg', 'proc1', myargs, :r1 );
  6
  7      myArgs := argType( null, 7, 2 );
  8      run_proc( 'my_pkg', 'proc2', myargs, :r2 );
  9  end;
 10  /
begin my_pkg.proc1(:1,:2); end;
begin my_pkg.proc2(:1,:2,:3); end;

PL/SQL procedure successfully completed.

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

COLUMN_VALUE
---------------------------------------------------------------------------------
--------------------------------------------------
Hello world
11

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

COLUMN_VALUE
---------------------------------------------------------------------------------
--------------------------------------------------
30-SEP-01
7
2

      Rating: 4
what exactly I
      am looking for - but giving error with varchar2 out
      param  January 17, 2002
Reviewer:  Ravi
      Kumar  from Chicago, USA
Tom,

I have a procedure with an out parameter of varchar2. When I execute this
procedure, it runs fine. But when I am calling this same procedure from
dynamic_sql, it is giving me the following error.

ORA-06502: PL/SQL: numeric or value error: character string buffer too small

If I change the type of out parameter to number, it works fine.

Here are my original procedure and the calling procedure.

Please suggest.

Thanks,
Ravi.

============

CREATE OR REPLACE procedure c (one in number, two out varchar2) as
begin
   dbms_output.enable(1000000);
   dbms_output.put_line('one : ' || one);
   insert into ITEM_TABLE values (one, 'kumar');
   commit;
   two := 'success';
   dbms_output.put_line('Done : ' || two);
end;

and the calling procedure is :

CREATE OR REPLACE procedure run_c is
  v_in_param       NUMBER;
  v_out_param      varchar2(9);
  v_user_name      varchar2(30) := 'TEST';
  v_procedure      varchar2(30) := 'C';
  v_string         long;
  v_ignore         pls_integer;
  v_cursor_name    pls_integer default dbms_sql.open_cursor;
BEGIN
  v_in_param := 2;
  v_string := 'begin ' || v_user_name || '.' || v_procedure || '(:param_in,
:param_out); end;';
  DBMS_OUTPUT.Put_Line('STR : ' || v_string);
  dbms_sql.parse(v_cursor_name, v_string, dbms_sql.native);
  dbms_sql.bind_variable(v_cursor_name, 'param_in', v_in_param);
  dbms_sql.bind_variable(v_cursor_name, 'param_out', v_out_param);
  v_ignore := dbms_sql.execute(v_cursor_name);
  dbms_sql.variable_value(v_cursor_name, ':param_out', v_out_param);
  dbms_output.put_line('Q is : ' || v_out_param);
  dbms_sql.close_cursor(v_cursor_name);
END;

          Followup:

ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE OR REPLACE procedure run_c is
  2    v_in_param       NUMBER;
  3    v_out_param      varchar2(9);
  4    v_user_name      varchar2(30) := user;
  5    v_procedure      varchar2(30) := 'C';
  6    v_string         long;
  7    v_ignore         pls_integer;
  8    v_cursor_name    pls_integer default dbms_sql.open_cursor;
  9  BEGIN
 10    v_in_param := 2;
 11    v_string := 'begin ' || v_user_name || '.' || v_procedure || '(:param_in,
 12  :param_out); end;';
 13    DBMS_OUTPUT.Put_Line('STR : ' || v_string);
 14    dbms_sql.parse(v_cursor_name, v_string, dbms_sql.native);
 15    dbms_sql.bind_variable(v_cursor_name, 'param_in', v_in_param);
 16    dbms_sql.bind_variable(v_cursor_name, 'param_out', v_out_param, 30);
 17    v_ignore := dbms_sql.execute(v_cursor_name);
 18    dbms_sql.variable_value(v_cursor_name, ':param_out', v_out_param);
 19    dbms_output.put_line('Q is : ' || v_out_param);
 20    dbms_sql.close_cursor(v_cursor_name);
 21  END;
 22  /

Procedure created.

Use the version of bind_variable that lets you tell it the max length, else, it
defaulted to the length of the original string -- 1 character.

That works.

      Rating: 5
with parameters
      already defined in the string   October 26, 2002

Reviewer:  A reader
Tom,

If i have a variable which holds the procedure name along with just the in
parameters

 xyz_SP('EMP',1234,'Scott');

I want to execute this dynamically, is this possible with out breaking the above
string for passing variables and then
using dbms_sql.bind( )

Thanks

          Followup:

Not really -- not even with cursor sharing.  you'll need to parse and bind.

      Rating: 4
Great
      Code  June 18, 2003
Reviewer:  Sankalp
       from Malaysia(KL)
Its a great code.
Just to extend this. Can the PLS errors ,occuring during creation of any
procedure dynamically, be captured programatically?

          Followup:

just like any error, yes.  sqlcode, sqlerrm

      Rating: 4
How to create
      from all_source?   November 11, 2003
Reviewer:
      Steve  from UK
Say I am connected to database B but want to create a Package that exists on
database A.  I can view the source via all_source@db_link but it is larger than
32k characters.

Is there a way to create this package on database B?  I don't particularly want
to use Advanced Replication (partly because I can't connect to database B from
Database A).

          Followup:

you can use dbms_sql -- there is an "array interface" to parse.

so, you would select the code out line by line, plop it into an array and parse
that.

it can be huge

      Rating: 5
Thanks
      but....  November 12, 2003
Reviewer:  Steve
       from UK
I can see how to declare the array and I'm comfortable with using arrays to bind
variables.  I'm not sure how to parse an array and haven't been aboe to find a
good example on here or in the documentation.  Could you just provide a small
example?  Thanks

      Rating: 5
It's
      ok....  November 12, 2003
Reviewer:  Steve
       from UK
found it varchar2s!

Thanks

November 11, 2003 steven -- Thanks for the question regarding
      "unindexed foreign keys", version

      You Asked
Do you have a script that lists all the
foreign keys with no associated indexes?

      and we said...
goto
http://osi.oracle.com/~tkyte/unindex/index.html
Review &
      Followup

      Rating:
      5
unindex.sql  November 08, 2002
Reviewer:
      mo
Tom:

I tried several times to download this file and it just says "Getting file
information" and keeps going for ever.

Any other places where i can find this?

Thanks,

          Followup:

loads up just dandy for me.  try right click / save link as.

No complaints from anyone else.  Try an open systems browser like mozilla or
netscape if you are using MS IE.  See if it is some mistake in their
implementation.

      Rating:
      5
unindex  November 08, 2002
Reviewer:
      mo
Tom:

Thanks, it worked.

what is the 2nd column in the list represent:

SQL> @unindex

STAT TABLE_NAME                     COLUMNS              COLUMNS
---- ------------------------------ -------------------- --------------------
**** APPLICATION_INSTANCES          AI_APP_CODE
ok   EMP                            DEPTNO               DEPTNO

2.  After generating a report, do you recommend creating indexes on the ******
by

SQL>CREATE INDEX APPLICATION_INSTANCES_AI_APP_CODE_index_01
  ON APPLICATION_INSTANCES.AI_APP_CODE;

3.  Is there a script that automates these or you have todo it one by one.

Thank you,

          Followup:

1) huh?  the second column is the table name...

2) I do not recommend creating indexes unless you have a need.  You have my book
"expert one on one oracle" -- see page 108 for a discussion

3) you have all of the info you need to turn my unindex.sql into a
"create_the_indexes.sql" script.

      Rating: 5
The Second
      Column  November 08, 2002
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Tom:

I believe Mo is wondering about (amongst other things) the second column in the
"Columns" pair...  You must have encrypted the script, so that no one would ever
be able to read it and figure out what it does. :)

- Mark

          Followup:

Ahh -- yes, well, that second column repeated there is the list of indexed
columns that would be used.  For example:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table p ( x int primary key );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table c ( A references p, B int,
constraint c_pk primary key (a,b) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> column columns format a20 word_wrapped
ops$tkyte@ORA817DEV.US.ORACLE.COM> column table_name format a30 word_wrapped
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select decode( b.table_name, NULL, '****',
'ok' ) Status,
  2             a.table_name, a.columns, b.columns
  3  from
  4  ( select substr(a.table_name,1,30) table_name,
  5                   substr(a.constraint_name,1,30) constraint_name,
  6               max(decode(position, 1,     substr(column_name,1,30),NULL)) ||
  7               max(decode(position, 2,', '||substr(column_name,1,30),NULL))
||
  8               max(decode(position, 3,', '||substr(column_name,1,30),NULL))
||
  9               max(decode(position, 4,', '||substr(column_name,1,30),NULL))
||
 10               max(decode(position, 5,', '||substr(column_name,1,30),NULL))
||
 11               max(decode(position, 6,', '||substr(column_name,1,30),NULL))
||
 12               max(decode(position, 7,', '||substr(column_name,1,30),NULL))
||
 13               max(decode(position, 8,', '||substr(column_name,1,30),NULL))
||
 14               max(decode(position, 9,', '||substr(column_name,1,30),NULL))
||
 15               max(decode(position,10,', '||substr(column_name,1,30),NULL))
||
 16               max(decode(position,11,', '||substr(column_name,1,30),NULL))
||
 17               max(decode(position,12,', '||substr(column_name,1,30),NULL))
||
 18               max(decode(position,13,', '||substr(column_name,1,30),NULL))
||
 19               max(decode(position,14,', '||substr(column_name,1,30),NULL))
||
 20               max(decode(position,15,', '||substr(column_name,1,30),NULL))
||
 21               max(decode(position,16,', '||substr(column_name,1,30),NULL))
columns
 22      from user_cons_columns a, user_constraints b
 23     where a.constraint_name = b.constraint_name
 24       and b.constraint_type = 'R'
 25     group by substr(a.table_name,1,30), substr(a.constraint_name,1,30) ) a,
 26  ( select substr(table_name,1,30) table_name, substr(index_name,1,30)
index_name,
 27               max(decode(column_position, 1,
substr(column_name,1,30),NULL)) ||
 28               max(decode(column_position, 2,',
'||substr(column_name,1,30),NULL)) ||
 29               max(decode(column_position, 3,',
'||substr(column_name,1,30),NULL)) ||
 30               max(decode(column_position, 4,',
'||substr(column_name,1,30),NULL)) ||
 31               max(decode(column_position, 5,',
'||substr(column_name,1,30),NULL)) ||
 32               max(decode(column_position, 6,',
'||substr(column_name,1,30),NULL)) ||
 33               max(decode(column_position, 7,',
'||substr(column_name,1,30),NULL)) ||
 34               max(decode(column_position, 8,',
'||substr(column_name,1,30),NULL)) ||
 35               max(decode(column_position, 9,',
'||substr(column_name,1,30),NULL)) ||
 36               max(decode(column_position,10,',
'||substr(column_name,1,30),NULL)) ||
 37               max(decode(column_position,11,',
'||substr(column_name,1,30),NULL)) ||
 38               max(decode(column_position,12,',
'||substr(column_name,1,30),NULL)) ||
 39               max(decode(column_position,13,',
'||substr(column_name,1,30),NULL)) ||
 40               max(decode(column_position,14,',
'||substr(column_name,1,30),NULL)) ||
 41               max(decode(column_position,15,',
'||substr(column_name,1,30),NULL)) ||
 42               max(decode(column_position,16,',
'||substr(column_name,1,30),NULL)) columns
 43      from user_ind_columns
 44     group by substr(table_name,1,30), substr(index_name,1,30) ) b
 45  where a.table_name = b.table_name (+)
 46    and b.columns (+) like a.columns || '%'
 47  /

STAT TABLE_NAME                     COLUMNS              COLUMNS
---- ------------------------------ -------------------- --------------------
ok   C                              A                    A, B

see -- the fkey is on A, but the index we'll use is the one on A, B.  I just
wanted to print out both.

BTW: if you have my book -- the script I have in there is "better" -- it is
"100%" -- it finds all cases.  The above query can have some "false positives"!!

Loved the comment on "you must have encrypted it".  It does surprise me when
people just won't "dig a little bit" to try and figure it out.

      Rating:
      5
index  November 08, 2002
Reviewer:
      mo
Tom:

Thanks.  I will read p108 of your book this weekend.

But it seems you are saying "do not always create index on foreign keys" which
contradicts with the article "unindexed Foreign Keys" which seems to favor it
for all of them.

Thanks,

          Followup:

read the book -- i clearly lay out when you NEED the index.  Other times you do
not.

      Rating:
      5
unindex  January 15, 2003
Reviewer:
      mo
Tom:

If you have a SQL statement that joins several tables A, B and C and you have
indexes on the columns in the WHERE clause, are you still doing a FULL TABLE
SCAN if you have one foreign key that is unindexed? So indexes will not take
effect in this search at all?

Thank you,

          Followup:

huh?  you totally missed the point.

the optimizer will use any index that exists if it makes sense.

indexes + fkeys are for concurrency as well as performance reasons.

      Rating:
      5
index  January 16, 2003
Reviewer:
      mo
Tom:

I will read your chapter 7 to clear up some issues on indexes this weekend But
let me confirm with you.

I have DEPT table with 10 records and EMP table with 1 million records. I have
50 employees with hiredate '16-JAN-2003'. I want to query the people that have
been hired today.

Case 1:  I have an index on emp(hiredate) and on foreign key emp(deptno). I do:

select a.*,b.* from dept a, emp b where a.deptno=b.deptno
and b.hiredate = to_date('16-jan-2003','dd-mon-yyyy');

I should only scan 50 records in the emp table?Correct

2. Case 2: I have an index on emp(hiredate) and no foreign key index. I do the
same query, I should be scanning the 1 million records in emp table because
foreign key is not indexed?

3. Case 3. I do not have a hiredate index nor FK index. The result will be as
case 2. scan 1 million records.

Is this correct?

THank you

          Followup:

case 1: the fkey is not relevant to the discussion.  the fkey or lack thereof is
not relevant at all.

Assuming the optimizer chooses to use the index on hiredate, yes, an index range
scan resulting in 50 table access by rowids to the EMP which would probably just
full scan dept after that to join to it since dept is tiny.

case 2: not relevant.  who cares of emp.deptno is indexed or not in this case.
it makes no difference.

the optimizer would do the SAME THING as case one.  I cannot even imagine why it
would be "different" or why you would think it would be.  Look at the predicate
-- think "If I was asked to do this query plan, what would I do" -- the index or
lack thereof on emp.deptno doesn't come into play at all.

case 3: full scan of emp -- but only because of the lack of an index on
hiredate, emp.deptno -- doesn't come into play at all.

      Rating:
      5
index  January 16, 2003
Reviewer:
      mo
Tom:

As a followup to what you said:

1. It sounds like the optimzier has its own mind to decide whether to use the
hiredate index or not? Does this mean it can do a full scan even though a
hiredate index is there?

2.  You say in your book Chapter 2 P111. "When you query from the parent to the
child. you will find that not having the index in place will slow down the
queries:
select * from dept, emp where emp.deptno = dept.deptno and dept.deptno = :x; "

This contradicts what you say here that having an index on deptno should not
matter.

I thought the whole purpose of indexing foreign keys is to avoid deadlocks and
to eliminate slow queries (parent/child).

Thank you,

          Followup:

1) of course it can.

2) no it doesn't, it does not at all.  I would like you to think about it for a
bit... Look at the queries.  Tell me -- what is the DRIVING table in each case.
try to *envision* what is going on.

the whole purpose of indexing fkeys is

a) to avoid taking a full table lock on the child table when you DELETE from
parent or UPDATE a parent primary key.

b) to avoid really bad performance with ON DELETE CASCADE (a full scan of child
for every deleted parent row)

that is all.

      Rating:
      5
unindex  January 31, 2003
Reviewer:
      mo
Tom:

1.  from your book P302, you say that when you perform a join query between
parent (dept) and child (emp), you will find that having unindexed foreign key
will slow down your query.  Here you say it should not matter. I am really
confused?

2.  Is there a way to check the number of records scanned when the query runs? I
was trying to check if TKPROF does that but I did not know how you set it up in
SQL*PLUS?

Thank you,

          Followup:

1) depends on the way you query.

from PARENT to CHILD, you would like an index on the child's foreign key.

from CHILD to PARENT - matters not if the childs fkey is indexed.

consider:

select * from emp, dept where emp.empno = 1234 and emp.deptno = dept.deptno;

tell me -- does it matter if emp(deptno) is indexed?  Nah, the query plan should
be:

a) goto emp_pk and get the rowid for empno = 1234
b) goto emp using that rowid and get the row
c) using the deptno you just got in b) use dept_pk to find the rowid for the row
   in dept
d) goto dept using that rowid

2) if you mean the rows flowing through each step of the query plan -- only
tkprof does that.  If you mean total rows returned -- autotrace traceonly will
do that.

      Rating: 3
But isn't
      index-on-foreign-key less important with 9i way of locking the child
      table?  January 31, 2003
Reviewer:  Nilendu
      Misra  from Mountain View, CA USA
From what I understand, (in versions prior to 9i) while dropping a single row
from the parent table - Oracle used to hold an 'exclusive' lock on the entire
child table, so there could be NO DML possible on the child table till the
deletion on the parent table is over.

This would not be the case however, if the foreign key(s) in the child table is
indexed. In that case Oracle will pick up a shared lock on the *index* on the
foreign key and *not* on the entire table in exclusive mode. This allowed DML on
child table.

In 9i, I read that Oracle does "much less" locking on child table when parent is
being DML'ed upon. The lock on the child table in 9i is taken "momentarily and
released immediately". So there would be less need to create index on the
foreign keys in Oracle 9i for that locking issue.

          Followup:

there is still a lock -- a blocking lock.  try this:

ops$tkyte@ORA920> create table p ( x int primary key );

Table created.

ops$tkyte@ORA920> create table c ( x references p );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into p values ( 1 );

1 row created.

ops$tkyte@ORA920> insert into p values ( 2 );

1 row created.

ops$tkyte@ORA920> insert into c values ( 1 );

1 row created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into c values ( 1 );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          pragma autonomous_transaction;
  3  begin
  4          delete from p where x = 2;
  5  end;
  6  /
declare
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
ORA-06512: at line 4

so, my advice would still be

if you

a) delete from parent
b) update parents primary key

then you should INDEX the fkeys of the children.  period

      Rating: 5
unindex foreign
      key  February 01, 2003
Reviewer:  mo

Tom:

I did not realize that when you have a join statement you can go from child to
parent and parent to child.

Let me clarify this. you said this is going from child to parent:

select * from emp, dept where emp.empno = 1234 and emp.deptno = dept.deptno

would this be going from parent to child and when an indexed foreign key makes a
difference?

select * from emp, dept where dept.deptno = 10 and emp.deptno = dept.deptno.

2.  What I mean by finding out the number of records scanned is when I do a
query

select * from emp, dept where emp.empno = 1234 and emp.deptno = dept.deptno

I want to know how many rows oracle read before giving me my result. did it read
one row, 100 rows, before telling me there is one employee.

Can you point me on how do I set tkprof utility and how to read the output. I
could not find it in your book?

Thanks,

          Followup:

forget foreign keys for a minute..

just use common sense here -- you have two tables T1 and T2.  can you join
t1->t2 and t2->t1?  sure, why not?

Forget fkeys, just think "relational database"

fkeys are for data integrity -- period.

joining is joining is joining is joining.  period.

Look at the predicate and ask yourself "hey, if I was a computer, what indexes
would I like to see in order to make this go fast".  fkeys have nothing  to
do with it.

when you look at your query:

select * from emp, dept where dept.deptno = 10 and emp.deptno = dept.deptno.

just think of it like this:

select * from a, b where b.c = 10 and b.c = a.c;

now, what indexes would you like to see.  forget the fkeys, just think "what
indexes might be good to have".  Maybe one on a(c) for the join, and maybe one
on b(c) for the "b.c=10" part.

2) you cannot see the rows in autotrace, you can see the consistent gets (which
is the important thing).

you did not read the most important chapter in the book then.  Read chapter 10.
It is all about tkprof, tracing, statspack....

      Rating:
      5
indexed  February 01, 2003
Reviewer:
      mo
Tom:

OK I am thinking like you said. What is the differenct between joining EMP -->
DEPT  or DEPT -->EMP . the end result is the same (columns order may differ). IT
is one big table with all rows of both tables.  (not sure if an index makes the
join faster but from your comment it seems it does). So I need an index on the
common column between EMP and DEPT which is deptno.

This says that you always need to index the column you join the two tables with
to make the join faster?

2. Now after we create one big fat table as a result of the join comes to play
"where emp.empno=1234" or "where dept.deptno=10".  In case one you need an index
on emp.empno and in case 2 you need an index on dept.deptno which is already
there because it is primary key. Am I wrong?

Thanks,

          Followup:

No, thats not what I'm saying.....  In fact, if they are both really big and you
are going for most of the rows in both -- an index could be fatal.  see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6749454952894
for example.  Use an index there to join and boy does it take a long time..

I never speak in absolutes ;)  wait, I just did -- theres a conundrum for you...

Given my knowledge of EMP and DEPT and that there are just a few emps/deptno --
it would make sense to have deptno indexed in that particular query.

2) correct. not wrong.

      Rating:
      5
index  February 02, 2003
Reviewer:
      mo
Tom:

Thanks, I think I do understand what is happening.
A join between two tables does not mean that oracle is reading/creating all
records in both tables like I thought. If this is the case then what is the use
of the index as you said.

Oracle (depending on the query) read the index on the child table and then goes
to the index on the parent table to get results if columns were on the index. If
not then it uses the rowid of each index to get the record from table.

2.  Does not the CBO know when it is fatal to use an index and when not. I
thought it automatically knows what is the best thing to do. If you return more
than 20% of rows in table then it does a full scan if not it uses the index if
it exists?Correct

3.  I read your example on Page 301 on why oracle does not store nulls in b-tree
index but did not quite understand how you are using this select statement to
null values when 90% of rows have same value. Do you have other examples here?

Does this mean if I have a table with 10,000 records and 10 records with x=null
and I do
select x from table where x is null;
I have to do a full tbale scan.  IT does not make sense?

          Followup:

2) yes it does -- see that link, it did the right thing.

20% is not exactly the number, see my chapter on indexes in "Expert one on one
Oracle" -- look at the colocated/disorganized example. Same table, same data,
same indexes, same query -- differently plans (one index, one full scan)

3) on that page -- what I was getting at was this:

...
Previously, I said that the fact that totally NULL index entries are not stored
in a B*Tree can be used to your advantage - here is how. Say you have a table
with a column that takes on exactly two values. The values are very skewed - say
90% or more of the rows take on one value and 10% or less take on the other
value. We can index this column efficiently to gain quick access to the rows
that have the 10% or less value. This comes in handy when you would like to use
an index to get to the 10% or less rows but want to FULL scan to get to the 90%
or more rows and you want to conserve space. The solution is to use a NULL for
the 90% value and whatever value you want for the 10% value. Say the table was a
'queue' table of sorts. People inserted rows for another process to work on. The
vast majority of the rows in this table are in the processed state, very few in
the unprocessed state. I might set up the table like this:
.........

my point was --

a) you have a table with a column with 2 (or few) values
b) the vast majority are of the same value
c) you would like to use an index to get rapid access to the minority

therefore, store NULL in the column for the "vast majority" of records.  When
you index this column ONLY the values that are NON NULL will be in the index.
The index will be small, compact and when used -- would only access a small
fraction of the table.

We are purposefully SKIPPING the indexing of the 90% of the rows here.

On that very same page I have an example that shows "select x from table where x
is null" may in fact use an index -- confusion on my part here as to how you
could ask that particular question given I have the counter example sitting
right in front of you already??

If there is a concatenated index on t(x,y) -- and y is NOT NULL, then "where x
is null" can use that index.

If there is a single column index on t(x) OR a concatenated index on t(x,y) and
y is NULLABLE (as is X), then "where x is null" cannot use either of those
indexes (since the totally null entries are not to be found in the index!)

      Rating:
      5
index  February 07, 2003
Reviewer:
      mo
Tom:

1.  I looked at your example of colocated/disorganized example and understood
how you look at the clustering of the table relative to the index.  But then you
say it only applies if your query is a range scan which you do very few times.
Does this mean the cluster factor example does not count when you have exact
match query or other queries?

2.When you say colocated does it mean onw row is on one block while the other
one row can be stored on several blocks. So when oracle read an idnex value it
may have to go to several blocks to read it.  Or is it always one row = one
block if it fits in the block size?

3.you say:
<therefore, store NULL in the column for the "vast majority" of records.  When
you index this column ONLY the values that are NON NULL will be in the index.
The index will be small, compact and when used -- would only access a small
fraction of the table.>

Can you show me how you do it in emp  table. let us say you have a gender column
and there are 100,000 employees of which 90,000 are MALE.  Now how can I store
nulls instead of 'MALE' just to get to not index the values.  Is that what you
mean?

4. Also You said a b tree index will have 1 to 1 entry between the index and the
table. If nulls are not indexed then you may have a table with 10,000 records
and only 2000 index entries?Am i correct?

Thank you,

          Followup:

1) select * from t where c = 'foo'

that could be an index range scan
or a unique index (and we know it will hit A row)

it is a matter of the the number of ROWS you will retrieve via the index.

2) colocated means "together".  If the index has entries:

1,2,3,4,4,4,5,5,5,6,7,8,9,9,9

in order -- and the data in the TABLE is physically organized such that all of
the 1's are together, then the 2's then the 3's and and so -- the table and
index are nicely clustered.  I called this "colocated" -- together.

the cluster factor is simply a measure of how sorted the table is with respect
to the index in question.  If I read 15 rows from one index block (those rows
are colocated together -- I'm reading them via a range scan) -- how many
different database blocks will I have to read to pick up those 15 rows.  Are the
rows that are colocated in the INDEX -- also colocated in the TABLE?  If so,
cluster factor is good.  If not, cluster factor is bad.

3) just insert nulls instead of M?

4) from the index to the table there is a 1:1 relationship.  From the table to
the index there is a 1:(0 or 1) relationship.

      Rating: 5
which column to
      index   June 06, 2003
Reviewer:
      A reader  from CA
HI Tom
If I have a table as follows:

create table t
(
  jguid raw(16) not null,
  cguid raw(16) not null,
  constraint j_fk1 foreign key( jguid )
    references j_table(  jguid ) on delete cascade,
  constraint c_fk foreign key( cguid )
    references c_table( cguid )
      on delete cascade
) monitoring;

This has two foreign keys and since rows in both involved
parent tables can be deleted, I need to create the
foreign keys. My question is - should I create
indexes on (jguid only and cguid only) or just
one index on jguid and cguid.

Assume that this table is always accessed using both jguid and cguid.

          Followup:

you need jguid on the leading edge of one index and cguid on the leading edge of
another.

2 indexes.

      Rating: 5
Foreign Key and
      Indexes  October 31, 2003
Reviewer:
      A reader
Hi Tom,

I am new to my current project. The data model is designed in such a way that
most of the static text columns in lookup tables such as "name", "short name",
and "description" are stored in a "TEXTS" table and the columns in the tables
only contain the corresponding TEXTID. According to the data architect, is to
provide multi-language support in the application.

For example:

ORGTYPES(
   ORGTYPEID NUMBER,
   ORGTYPENAME NUMBER -- the textid of the orgtype name in
)

TEXTS
(
  TEXTID           NUMBER(14)                   NOT NULL,
  TEXTENGLISH      VARCHAR2(128 BYTE)           NOT NULL
)

TRANSLATIONS(
  TRANSLATIONID    NUMBER(14)                   NOT NULL,
  TRANSLATIONTEXT  VARCHAR2(128 BYTE)           NOT NULL,
  LANGUAGEID       NUMBER(4)                    NOT NULL,
  TEXTID           NUMBER(14)                   NOT NULL
)

My questions:
1. Is this a good design?
2. Is there any other way to provide multi-language support in Oracle?
3. Right now most of the foreign keys from the lookup tables to TEXTS:TEXTID are
not indexed. The texts are static and are rarely changes. Should I index them?
This will affect almost 60 lookup tables in the database.

Thanks for your time.

          Followup:

it is a standard technique for boilerplate text, yes.

      Rating: 5
Enforce 1 to 1+
      relationship  November 11, 2003
Reviewer:
      A reader
Hi Tom,

Is there any way that I can enforce a (1 to 1+) relationship? That is, for each
parent, there must be at least one child record.

For example, I want each order has at least one order line items.

Any idea? Thanks.

          Followup:

well, it is something that could only be validated at COMMIT time (else you
would have a really hard time creating the record at all -- if the parent exists
without child, parent cannot exist sort of thing...)

so, yes, we can do it with a little bit of recursive integrity -- that lets us
defer the checking until commit time:

ops$tkyte@ORA920PC> create sequence pk;

Sequence created.

ops$tkyte@ORA920PC> create sequence li_sequence;

Sequence created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create table po
  2  ( po_id  int primary key,
  3    data      char(10),
  4    li_seq int not null deferrable initially deferred
  5  )
  6  /

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create table li
  2  ( li_id  references po,
  3    li_seq int,
  4    data      char(10),
  5    constraint li_pk primary key(li_id,li_seq)
  6  )
  7  /

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> alter table po add constraint li_fk foreign
key(po_id,li_seq) references li(li_id,li_seq);

Table altered.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> variable x number
ops$tkyte@ORA920PC> insert into po values ( pk.nextval, 'data', null ) returning
po_id into :x;

1 row created.

ops$tkyte@ORA920PC> insert into li values ( pk.currval, li_sequence.nextval,
'data' );

1 row created.

ops$tkyte@ORA920PC> update po set li_seq = li_sequence.currval where po_id = :x;

1 row updated.

ops$tkyte@ORA920PC> commit;

Commit complete.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert into po values ( pk.nextval, 'data', null ) returning
po_id into :x;

1 row created.

ops$tkyte@ORA920PC> commit;
commit
*
ERROR at line 1:
ORA-02091: transaction rolled back
ORA-02290: check constraint (OPS$TKYTE.SYS_C007240) violated

ops$tkyte@ORA920PC> insert into po values ( pk.nextval, 'data', 55 ) returning
po_id into :x;
insert into po values ( pk.nextval, 'data', 55 ) returning po_id into :x
*
ERROR at line 1:
ORA-02291: integrity constraint (OPS$TKYTE.LI_FK) violated - parent key not
found

ops$tkyte@ORA920PC> commit;

Commit complete.

      Rating: 4
Antoher idea
      for 1 : 1..n  November 12, 2003
Reviewer:
      Michael  from Germany
Hello!

Here's antoher idea to implement an 1 : 1..n relationship:

SQL> create table t_dept as
  2  select * from scott.dept where 1=0;

Tabelle wurde angelegt.

SQL> create table t_emp as
  2  select * from scott.emp where 1=0;

Tabelle wurde angelegt.

SQL> alter table t_dept
  2  add (primary key(deptno));

Tabelle wurde geändert.

SQL> alter table t_dept
  2  add (emp_count number(38) default 0 not null
  3       constraint dept_must_have_employees
  4       check (emp_count > 0)
  5       deferrable initially deferred
  6      );

Tabelle wurde geändert.

SQL> alter table t_emp
  2  add (foreign key (deptno) references t_dept(deptno)
  3      );

Tabelle wurde geändert.

SQL> create or replace trigger emp_trig
  2  after insert or delete or update of deptno on t_emp
  3  for each row
  4  begin
  5    if inserting then
  6      update t_dept
  7      set emp_count = emp_count + 1
  8      where deptno = :new.deptno;
  9    elsif updating then
 10      update t_dept
 11      set emp_count = emp_count - 1
 12      where deptno = :old.deptno;
 13      update t_dept
 14      set emp_count = emp_count + 1
 15      where deptno = :old.deptno;
 16    elsif deleting then
 17      update t_dept
 18      set emp_count = emp_count - 1
 19      where deptno = :old.deptno;
 20    end if;
 21  end;
 22  /

Trigger wurde erstellt.

SQL> insert into t_dept(deptno, dname, loc)
  2  values (10, 'DEVELOPMENT', 'NY');

1 Zeile wurde erstellt.

SQL> commit;
commit
*
FEHLER in Zeile 1:
ORA-02091: Transaktion wurde zurückgesetzt
ORA-02290: Verstoß gegen CHECK-Regel (MIKE.DEPT_MUST_HAVE_EMPLOYEES)

SQL> insert into t_dept(deptno, dname, loc)
  2  values (10, 'DEVELOPMENT', 'NY');

1 Zeile wurde erstellt.

SQL> insert into t_emp(ename, deptno)
  2  values ('BLAKE', 10);

1 Zeile wurde erstellt.

SQL> commit;

Transaktion mit COMMIT abgeschlossen.

Now with 2 sessions:

Session A:

SQL> insert into t_emp(ename, deptno)
  2  values ('CLARK', 10);

1 Zeile wurde erstellt.

Switch to session B:

SQL> delete t_emp
  2  where ename = 'BLAKE';

Session B blocks now, till Session A commits or rolls back.

Switch to session A:

SQL> commit;

Transaktion mit COMMIT abgeschlossen.

Session B unblocks and commits:

1 Zeile wurde gelöscht.

SQL> commit;

Transaktion mit COMMIT abgeschlossen.


Subject | Article | Last Updated (East coast time)
Why Cursor? | article only | Nov 12, 2003 10:25 am
Optimizer statistics | article only | Nov 12, 2003 10:30 am
Parallel Query | article only | Nov 12, 2003 11:26 am
Auditing of PL/SQL Functions and Procedure Calls | article only | Nov 12, 2003 11:28 am
Wait Event stats | article only | Nov 12, 2003 04:35 pm
Dev Schema Versioning | article only | Nov 12, 2003 04:46 pm
Fine Grained Auditing | article only | Nov 12, 2003 04:50 pm
sql trace and tkprof | article only | Nov 13, 2003 06:28 am
IN LIST as TABLE | article only | Nov 13, 2003 06:48 am
optimizer_index_cost_adj | article only | Nov 13, 2003 06:57 am
FGAC - Parent and Child Tables | article only | Nov 13, 2003 06:58 am
three questions | article only | Nov 13, 2003 07:00 am
Migration | article only | Nov 13, 2003 08:47 am
Update statement | article only | Nov 13, 2003 09:17 pm
How can I track the execution of PL/SQL and SQL? | article only | Nov 13, 2003 09:19 pm
chained rows | article only | Nov 13, 2003 09:23 pm
killing a session | article only | Nov 14, 2003 09:17 am
Can UTL_FILE package used to create ASCII files | article only | Nov 14, 2003 10:21 am
case statement, single extent and raw device | article only | Nov 15, 2003 08:47 am
DDL Auditing - automatically including sql alt script file names | article only | Nov 15, 2003 08:52 am
Undo Tablespaces. | article only | Nov 15, 2003 08:56 am
Increasing Export and Import Speed? | article only | Nov 15, 2003 09:05 am
pragma exception init, and raise application error | article only | Nov 16, 2003 10:23 am
tablespace fragmentation or something else | article only | Nov 16, 2003 10:25 am
different states of integrity constraints | article only | Nov 16, 2003 10:27 am
copy command | article only | Nov 16, 2003 11:59 am
Mutating Tabel | article only | Nov 16, 2003 03:09 pm
Performance issue for accessing table of a remote database | article only | Nov 17, 2003 06:43 am
Language Issue | article only | Nov 17, 2003 06:44 am
Pipelined functions | article only | Nov 17, 2003 06:48 am
Procedure and Function sharing the same name and parameter. | article only | Nov 17, 2003 07:37 am
row(s) 1 - 31 of 31
November 12, 2003 Neeha -- Thanks for the question regarding "Why Cursor?", version 8.1.7
You Asked
Hello Sir,

I have searched many of your archives and other sites but I am still confused
that why we use cursor, what are the circumstances that force us to do things
using cursor. is it some performance reason or some other.

Kindly tell me a very simple example which shows that it is necessory to use
cursor and has not been done otherwise.

thanks
and we said...
you use a cursor if and only if and only when you cannot do it in a single sql
statement.

a cursor gives you the ability to procedurally, row by row by slow row, process
the data.

you use them only when nothing else is available to you.

but seriously, they are like a "read" in a file based system.  they just give
you the ABILITY to read a row at a time (or rows at a time) from a result set
and process it.
Review & Followup
Rating: 3
Can You code an example? September 27, 2003
Reviewer: Neeha from India
Thanks for your quick response but
....
you use a cursor if and only if and only when you cannot do it in a single sql
statement
....

This is what i want what will be the situation when we can not do it in a single
sql, please code some simple exampe

thansk for your quickest response

Followup:
you need to procedurally process each record before doing something with it.
there would be an example.  you need to

a) read
b) inspect
c) process

each record for example.

think about "a report" -- you use cursors there alot in order to retrieve and
print the data on a screen.

Rating: 4
How about you want to present the data to a user? September 28, 2003
Reviewer: Jim from Portland, OR USA
You often use a cursor when you want to present the data to a user in some sort
of GIU.

Rating: 1
September 28, 2003
Reviewer: A reader

Rating: 4
Why (Explicit) Cursor September 28, 2003
Reviewer: Bharath from India
When a SQL statement in PL/SQL returns more than one row then TOO MANY ROWS
exception is thrown.. Because for this reason you need to declare a explicit
cursor.. And also for another most important reason given by TK..

Followup:
well, there is BULK COLLECT as well

select .... BULK COLLECT into .... from t;

Rating: 5
special cases when pl/sql is faster than single SQL September 28, 2003
Reviewer: A reader
Hi Thomas

I have a case where pl/sql is more suitable but I have a problem, the case is a
bit different now and I dont know how to do it in single cursor, may you have a
look?

My case

select ename, mgr
  from emp a
 where sal = (select max(sal) from emp x where x.deptno = a.deptno)

changed into

declare
l_counter number := 0;
l_last_deptno number;
begin
   for i in (select deptno, sal from emp order by deptno, sal desc)
   loop
      if l_counter = 0 or i.deptno != l_last_deptno then
         dbms_output.pue_line(i.deptno||', '||i.ename);
      end if;
      l_last_deptno := i.deptno;
      l_counter := l_counter + 1;
   end loop;
end;
/

in first SQL I have to access emp several times (correlated) whereas in second
only once

The problem is now I have this

select ename, mgr
  from emp a
 where sal = (select max(sal) from emp x where x.deptno = a.deptno and x.job !=
'PRESIDENT')

I dont think I can change this into PL/SQL and access only once EMP anymore....
Do you think it is still possible?

Cheers

Followup:
there are so many ways to rewrite that.

and very strange but for me -- the plsql code is always slower then any of the
SQL based approaches!

ops$tkyte@ORA920> drop table emp;

Table dropped.

Elapsed: 00:00:00.13
ops$tkyte@ORA920> create table emp
  2  as
  3  select object_name ename, mod(rownum,10)+1 deptno, object_id empno,
object_id+1 mgr,
  4         object_id/100 sal
  5    from all_objects
  6  /

Table created.

Elapsed: 00:00:01.99
ops$tkyte@ORA920>
ops$tkyte@ORA920> create index emp_idx on emp(deptno)
  2  /

Index created.

Elapsed: 00:00:00.27
ops$tkyte@ORA920> analyze table emp compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns
  5  /

Table analyzed.

Elapsed: 00:00:00.40
ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select ename, mgr
  2    from emp a
  3   where sal = ( select max(sal) from emp x where x.deptno = a.deptno )
  4  /

ENAME                                 MGR
------------------------------ ----------
MY_TABLE                            55593
DO_IT                               56050
EMP                                 56065
RUNSTATS_PKG                        56032
RUNSTATS_PKG                        56033
RUN_STATS                           56026
STATS                               56031
T                                   56053
BONUS                               55402
DEPT_PK                             55404

10 rows selected.

Elapsed: 00:00:00.10
ops$tkyte@ORA920>
ops$tkyte@ORA920> select a.ename, a.mgr
  2    from emp a, ( select max(sal) max_sal, deptno
  3                    from emp
  4                               group by deptno ) b
  5   where a.deptno = b.deptno
  6     and a.sal = b.max_sal
  7  /

ENAME                                 MGR
------------------------------ ----------
MY_TABLE                            55593
DO_IT                               56050
EMP                                 56065
RUNSTATS_PKG                        56032
RUNSTATS_PKG                        56033
RUN_STATS                           56026
STATS                               56031
T                                   56053
BONUS                               55402
DEPT_PK                             55404

10 rows selected.

Elapsed: 00:00:00.13
ops$tkyte@ORA920>
ops$tkyte@ORA920> select ename, mgr
  2    from ( select ename, mgr, sal, max(sal) over (partition by deptno)
max_sal
  3             from emp )
  4   where sal = max_sal
  5  /

ENAME                                 MGR
------------------------------ ----------
EMP                                 56065
RUNSTATS_PKG                        56032
RUNSTATS_PKG                        56033
RUN_STATS                           56026
STATS                               56031
T                                   56053
BONUS                               55402
MY_TABLE                            55593
DEPT_PK                             55404
DO_IT                               56050

10 rows selected.

Elapsed: 00:00:00.18
ops$tkyte@ORA920>
ops$tkyte@ORA920> select substr( data, 10, 30 ) ename,
  2         to_number( substr( data, 40 )) mgr
  3    from (
  4  select deptno, max( to_char( sal, 'fm000000.00' ) ||
  5                      rpad( ename, 30 ) ||
  6                                          mgr ) data
  7    from emp
  8   group by deptno
  9         )
 10  /

ENAME                                 MGR
------------------------------ ----------
EMP                                 56065
RUNSTATS_PKG                        56032
RUNSTATS_PKG                        56033
RUN_STATS                           56026
STATS                               56031
T                                   56053
BONUS                               55402
MY_TABLE                            55593
DEPT_PK                             55404
DO_IT                               56050

10 rows selected.

Elapsed: 00:00:00.35
ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2  l_counter number := 0;
  3  l_last_deptno number;
  4  begin
  5     for i in (select * from emp order by deptno, sal desc)
  6     loop
  7        if l_counter = 0 or i.deptno != l_last_deptno then
  8           dbms_output.put_line(i.deptno||', '||i.ename);
  9        end if;
 10        l_last_deptno := i.deptno;
 11        l_counter := l_counter + 1;
 12     end loop;
 13  end;
 14  /
1, EMP
2, RUNSTATS_PKG
3, RUNSTATS_PKG
4, RUN_STATS
5, STATS
6, T
7, BONUS
8, MY_TABLE
9, DEPT_PK
10, DO_IT

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.94
ops$tkyte@ORA920>

Rating: 5
sory posted incorrect code September 28, 2003
Reviewer: A reader
Hi

The pl/sql code was an incorrect one, the correct one should be as follows

declare
   l_counter number := 0;
   l_max_sal number;
   l_last_deptno number;
begin
   for i in (select ename, deptno, comm, sal from emp order by deptno, sal desc)
   loop
      if l_counter = 0 or i.deptno != l_last_deptno or (i.deptno = l_last_deptno
and i.sal = l_max_sal) then
         l_max_sal := i.sal;
         dbms_output.put_line(i.ename||' '||i.deptno||' '||nvl(i.comm, 0)||'
'||i.sal);
      end if;
      l_last_deptno := i.deptno;
      l_counter := l_counter + 1;
   end loop;
end;
/

cheers

Rating: 4
I think for very large table PL/SQL would be faster? September 28, 2003
Reviewer: A reader
Hi

If the table involved has million of rows wouldnt pl/sql approach be faster? It
would only has to scan the table once instead of scan million of times using
correlated sub query

Another question, I see you used analytic function in one of approaches, if the
original query is

select ename, mgr
  from emp a
 where sal = ( select max(sal) from emp x where x.deptno = a.deptno where job !=
'PRESIDENT' )
/

How can you convert that using analytic function?

Cheers

Followup:
tell you what, you benchmark it for us and let us know (my response is, as
usual, if you can do it in a single sql query ..... etc etc etc.   The worst
performing code I generally see is when people try to second guess the SQL
engine, try to "save it from doing work")......

Give it a go, SEE what happens.  I gave you the framework, you just need to try
it out (no, plsql won't be faster)....

select max( case when job != 'PRESIDENT' then sal else null end ) over
(partition by deptno) max_sal

will be the analytic you were looking for -- to find the max salary for a
non-president job by deptno.

Rating: 5
not able to find the link September 28, 2003
Reviewer: A reader
There was a thread where you showed 3 ways to do the below
ops$tkyte@ORA920> select substr( data, 10, 30 ) ename,
  2         to_number( substr( data, 40 )) mgr
  3    from (
  4  select deptno, max( to_char( sal, 'fm000000.00' ) ||
  5                      rpad( ename, 30 ) ||
  6                                          mgr ) data
  7    from emp
  8   group by deptno
  9         )
 10  /

i.e. display details along with group by, can you please give that link. I have
searched but could not find it. I remember " 3 ways" in   the thread.

Followup:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2165134263446

      Rating: 3
Can we not do
      similar things using Record or Table datatypes?  September
      29, 2003
Reviewer:  Neeha  from India
Hi Again Sir,
   You say that we do things one by one and see the results for each action.

By using PL/SQL tables datatypes we can do and select the whole table even, then
how can you say that Cursor is necessory?
(Sorrrrry, but I just want to learn a better)
thanks

          Followup:

benchmark it.

see which approach is

a) faster
b) more efficient (look at ram utilization, cpu usage, etc)
c) more scalable
d) easier to code
e) easier to maintain
f) readable

and decide for yourself.  Me, i'm lazy, i like to write as little code as
possible. cursors are natural, cursors are easy.

      Rating: 2
Cursor
      Use  November 12, 2003
Reviewer:  Kalyan
       from Mumbai, INDIA
We mainly use cursor in two places
1. Simplicity, readability and maintainability of codes. Theoritically most
pl/sql can be converted into a single sql with innovative way of thinking but at
the cost of time,readability and maintainability. Where as Cursor is transparent
and gives u full control over the data. For example, in employee table, if you
have so and so combination of location, department, salary grade, years of
experience, no of years with the co and so many other criteria, you want to
raise salary by some percentage. for different combination, different rule and
so on.
2. To avoid no data found / to many rows
We use a FOR LOOP with a default value set or exception as required.

          Followup:

1) i beg to differ, to disagree.

Just yesterday -- I took tons of procedural code, using "easy to read" (quote,
unquote -- tongue in cheek) procedural code (it was running "slow") and after
spending lots of time "figuring out what the HECK the goal was", rewrote the
loops as simple INSERT INTO SELECTS -- well, the average loop went from over 36
seconds, down to under 2.  Hmmmm.

Cursors, processing slow by slow (opps, meant row by row) are an approach -- but
usually the wrong one.

Using CASE in SQL -- I'd betcha my sql would be more readable then your scads of
procedural code -- AND it would outperform it by many times.

2) umm, thats called an exception

   begin
      select ... into ...  from ...
   exception
      when no_data_found then <setup defaults>
   end

i don't see HOW you can use this to avoid "too many rows" -- that would be AN
ERROR would it not (and if you just want the "first" row -- use "and rownum =
1", you won't get too many rows)

but -- if you cannot make the leap to set based processing...  if it is deemed
"too hard"...

November 12, 2003 -- Thanks for the question regarding "Optimizer
      statistics", version 8.1.7

      You Asked
Hi, Tom.

You have recommend me to use dbms_stats.gather_schema_stats(user, 99,
method_opt=>null, cascade=>true) to gather optimizer statistics.
I have the next questions:
1. Is there any benefit to use estimate 99% vs. default compute statistics?
2. What means null for method_opt. Default hier is 'for all columns size 1'.
Means it, that this default will create histigrams for all columns, and you will
not do this?
3. Is gather_database_stats gather the statistic for sys and system?

And one more easy question:

What means ad hoc query?

Olaf

      and we said...
I do not recall giving that advice.  I tell people to use dbms_stats lots, but I
do not recall giving out a specific command such as that.  My general advice
would be:

o use dbms_stats
o use it in parallel if you need
o use the alter table monitoring so you can gather stats on stale tables only
o review the use of your tables to see if you need to compute histograms or not
on indexed columns...

I may have given an EXAMPLE that used that, but I don't believe I've said "here
is the optimal command to use"

1) 99% is compute, anything over 49% is equivalent to compute.  It is just that
99 is the max you can pass in there, if you give 100 -- it'll fail.

2) In the example, I did not want to compute histograms (i'm sure that:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1411208576534
is the example you are refering to).  In fact in reading that again I point out
that the person was not computing histograms and that they might (ahh -- it was
you that I pointed this out to!)

3) yes, gather_database_stats would do that to various degrees in various
releases.  I would discourage the use of that particular api call and use
gather_schema_stats instead.

Review &
      Followup

      Rating:
      4
  December 07, 2001
Reviewer:
      A reader
Thank you Tom for your quick responce. But it's still not clear for me the
parameter method_opt of dbms_stats. All I can find in

http://technet.oracle.com/doc/oracle8i_816/server.816/a76936/dbms_st2.htm#1010205
for this parameter is:
"Method options of the following format...:

FOR ALL [INDEXED | HIDDEN] COLUMNS [SIZE integer]..."

and that FOR ALL COLUMNS SIZE 1 is default.

It's not enough. Can you recommend me another, more informative document?
Is this default value gathers histograms for all columns too and what is hidden
columns?

          Followup:

Read

http://technet.oracle.com/doc/oracle8i_816/server.816/a76989/ch4d8.htm#30559
it will explain it all.

      Rating:
      5
Reader  December 07, 2001
Reviewer:
      Reader  from USA
Tom,

This is a general, hope you do not mind me asking here. It
might help others who may also have similar issues.

I was trying to search for your reply in one of the posting. I typed "the parse
can be done by server1" in the search box, but the search engine was parsing the
tokens individually searching for "the", "parse","can","be" and so on.
I eventually ended up guessing the archive-week, found the composite Q/A for the
week and did a <cntrl F> and typed the string. Found the topic in the posting
titled "October 31, 2001 Mike -- Thanks for the question regarding "SQL_TRACE --
how to enable in other sessions.", version 8.1.5"

Is there a easier way to search the archive for a long
string (as a concatenated string) in your site ,if so it will be very helpful

Thanks

          Followup:

We are going to add an "advanced search" that'll let you put in a phrase (and
search by date, subject, and so on).  We are planning some UI changes in the
near future, nothing too radical, just better placement of some things based on
feedback.

      Rating:
      5
gather_database_stats in 9.2  November 11, 2003

Reviewer:  Olaf
Tom,

are you still recommend to prefer gather_schema_stats vs gather_database_stats
in Oracle 9.2?
I have 40 schemas, it's simplier for me to use database one.

Thanks in advance

Olaf

          Followup:

i believe in schema independence -- each schema might be a big different from
every other one.  I schedule as a job for the schema, just my style.

it would be up to you.

      Rating: 5
database
      statistics  November 12, 2003
Reviewer:
      A reader
Tom,

I thought it was not the question of style but Oracle8 had problems if
statistics had been gathered for schemas sys and system
(what gather_database_stats always did).
Are there such problems in Oracle 9i.
Should we still avoid gather statistics for sys and system?

          Followup:

we were talking 92 in that context.

in 92, its ok to gather stats system wide IF YOU WANT.  If you have an existing
production system, and you've never done that in the past, please don't do it
now without TESTING IT first elsewhere.

November 12, 2003 Suresh -- Thanks for the question regarding
      "Parallel Query", version 8.1.7

      You Asked
Tom,
   Suppose I have enabled parallel query on one of the tables out of 50 in my
application..Then if I query using a join between those tables (only one of them
is parallel query enabled ) then would Oracle still use the parallel query when
running that query. If not, is there something like a hint to make Oracle use
the parallel query against that table.

eg...EMP table with 1 million rows and parallel query enabled.
    DEPT with 1000 rows and parallel query NOT enabled.

Then the following query will it use the parallel query on EMP table.
  Select * from EMP, DEPT WHERE emp.deptno = dept.deptno;

Thanks,
Suresh

      and we said...
Any operation that could be parallelized against that table in any query would
be a condidate for parallelization (against that table)

You can always add the parallel hint to the query.

As for your question -- it might, it might not.  Depends on what table is
driving the query.  If we full scan DEPT and index access EMP, no -- most likely
a serial operation.  If we full scan EMP and index access DEPT -- probably.  If
we full scan both, probably.

Easy enough to see what'll happen (dbms_stats is great for this "what if" stuff)

ops$tkyte@ORA817.US.ORACLE.COM> create table emp as select * from scott.emp
where 1=0;

Table created.

ops$tkyte@ORA817.US.ORACLE.COM> create table dept as select * from scott.dept
where 1=0;

Table created.

ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> alter table dept add constraint dept_pk primary
key(deptno);

Table altered.

ops$tkyte@ORA817.US.ORACLE.COM> alter table emp add constraint emp_fk foreign
key(deptno) references dept;

Table altered.

ops$tkyte@ORA817.US.ORACLE.COM> create index emp_fk_idx on emp(deptno);

Index created.

ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> exec dbms_stats.set_table_stats( USER, 'EMP',
numrows => 1000000, numblks => 100000, avgrlen=> 700 )

PL/SQL procedure successfully completed.

ops$tkyte@ORA817.US.ORACLE.COM> exec dbms_stats.set_table_stats( USER, 'DEPT',
numrows => 1000, numblks => 13, avgrlen=> 100 )

PL/SQL procedure successfully completed.

ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> alter table emp parallel 5;

Table altered.

ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA817.US.ORACLE.COM> Select * from EMP, DEPT WHERE emp.deptno =
dept.deptno;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2001 Card=10000000
Bytes=1170000000)
   1    0   NESTED LOOPS (Cost=2001 Card=10000000 Bytes=1170000000)
   2    1     TABLE ACCESS (FULL) OF 'DEPT' (Cost=1 Card=1000 Bytes=30000)
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=2 Card=1000000
Bytes=87000000)
   4    3       INDEX (RANGE SCAN) OF 'EMP_FK_IDX' (NON-UNIQUE) (Cost=1
Card=1000000)

ops$tkyte@ORA817.US.ORACLE.COM> drop index emp_fk_idx;

Index dropped.

ops$tkyte@ORA817.US.ORACLE.COM> Select * from EMP, DEPT WHERE emp.deptno =
dept.deptno;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=31624 Card=10000000
Bytes=1170000000)
   1    0   MERGE JOIN* (Cost=31624 Card=10000000 Bytes=1170000000)
                              :Q132002
   2    1     SORT* (JOIN) (Cost=8 Card=1000 Bytes=30000)
                              :Q132002
   3    2       TABLE ACCESS* (FULL) OF 'DEPT' (Cost=1 Card=1000 Bytes=30000)
                              :Q132000
   4    1     SORT* (JOIN) (Cost=31616 Card=1000000 Bytes=87000000)
                              :Q132002
   5    4       TABLE ACCESS* (FULL) OF 'EMP' (Cost=2879 Card=1000000
Bytes=87000000)                          :Q132001

   1 PARALLEL_TO_SERIAL            SELECT /*+ ORDERED NO_EXPAND USE_MERGE(A2) *
                                   / A1.C0,A2.C0,A1.C1,A1.C2,A2.C1,A2.C2,A2.C3,
                                   A2.C4,A2.C5,A2.C6,A2.C7 FROM :Q132000 A1,:Q1
                                   32001 A2 WHERE A2.C0=A1.C0

   2 PARALLEL_COMBINED_WITH_PARENT
   3 PARALLEL_FROM_SERIAL
   4 PARALLEL_COMBINED_WITH_PARENT
   5 PARALLEL_TO_PARALLEL          SELECT /*+ Q132001 NO_EXPAND ROWID(A1) */ A1
                                   ."DEPTNO" C0,A1."EMPNO" C1,A1."ENAME" C2,A1.
                                   "JOB" C3,A1."MGR" C4,A1."HIREDATE" C5,A1."SA
                                   L" C6,A1."COMM" C7 FROM "EMP" PX_GRANULE(0,
                                   BLOCK_RANGE, DYNAMIC)  A1

ops$tkyte@ORA817.US.ORACLE.COM> set autotrace off

So, just as I guessed -- when we use the index to go to emp -- no parallel query
(have to have a partitioned index to get parallel query to kick in there but in
this case it would be silly to do that unless we partitioned the index by deptno
but then it would not parallel range scan in this case -- it would partition
eliminate instead -- but i digress...)

When we take the index away, and it full scans both, we get a parallel plan.

followup to comment

Not exactly.

Parallel query would be used for ANY operation against the table that could be
parallelized.  Given the example query, the only thing that could be
parallelized would be a full scan.

Your second observation is accurate (and demonstrated in full above as well)

Review &
      Followup

      Rating: 4
More
      doubts  July 21, 2001
Reviewer:  Suresh
       from NewYork
Tom,
   I understand from your answer that Parallel query would work only if it did
full table scan. Now, If I were to issue a join select statement against a table
with 10 million rows(index present, parallel query enabled) and another small
table (1000 rows, index present, 'driving' table in the query), then Oracle
would not perform parallel query against the first table as it is going to do a
index scan in the first table..Is this right or am I missing some thing here?

      Rating: 2
Parallel
      query  April 22, 2002
Reviewer:  Kalpesh
       from USA
Hi Tom,
I am executing following query on two databases.
Two databases are same means they have same tables,same data, same indexes.Same
init.ora parameters.
Those are copy of eachother.

SELECT NVL(b.trade_price,0), NVL(b.previous_day_change,0), NVL(b.daily_high,0),
NVL(b.daily_low,0), NVL(b.w_52_high,0), NVL(b.w_52_low,0), NVL(b.volume,0)
INTO :trade_price, :previous_day_change, :daily_high,
               :daily_low, :w_52_high, :w_52_low, :volume
      from tkmaster.tbsymbols a,tkdaily.tbtick b
      where a.symbol_id=b.symbol_id
      and concat(a.symbol_name_reuters,'.N') = 'GE.N'
      and a.symbol_type_id = 1

First one is producing followin explain plan:

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=104 Card=73 Bytes=81
          76)

   1    0   HASH JOIN* (Cost=104 Card=73 Bytes=8176)                   :Q429300
                                                                       2

   2    1     TABLE ACCESS* (FULL) OF 'TBSYMBOLS' (Cost=56 Card=73 Byt :Q429300
          es=2920)                                                     0

   3    1     TABLE ACCESS* (FULL) OF 'TBTICK' (Cost=46 Card=22545 Byt :Q429300
          es=1623240)                                                  1

   1 PARALLEL_TO_SERIAL            SELECT /*+ ORDERED NO_EXPAND USE_HASH(A2) SW
                                   AP_JOIN_INPUTS(A2) */ A1.C1,A1.C2,A1.C3,A1.C
                                   4,A1.C5,A1.C6,A1.C7 FROM :Q4293001 A1,:Q4293
                                   000 A2 WHERE A2.C0=A1.C0

   2 PARALLEL_FROM_SERIAL
   3 PARALLEL_TO_PARALLEL          SELECT /*+ NO_EXPAND ROWID(A1) */ A1."SYMBOL
                                   _ID" C0,A1."VOLUME" C1,A1."TRADE_PRICE" C2,A
                                   1."PREVIOUS_DAY_CHANGE" C3,A1."DAILY_HIGH" C
                                   4,A1."DAILY_LOW" C5,A1."W_52_HIGH" C6,A1."W_
                                   52_LOW" C7 FROM "TKDAILY"."TBTICK" A1 WHERE
                                   ROWID BETWEEN :B1 AND :B2

Second one is producing following explain plan:

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=139 Card=73 Bytes=72
          27)

   1    0   NESTED LOOPS (Cost=139 Card=73 Bytes=7227)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'TBSYMBOLS' (Cost=66 Ca
          rd=73 Bytes=2920)

   3    2       INDEX (RANGE SCAN) OF 'IDX_SYMBOL_TYPE_ID' (NON-UNIQUE
          ) (Cost=15 Card=73)

   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'TBTICK' (Cost=1 Card=5
          7740 Bytes=3406660)

   5    4       INDEX (UNIQUE SCAN) OF 'PK_TICK_SYMID' (UNIQUE)

It is taking more time on First one then the second one.
We have same indexes on both the datapools for both the tables:
Table_name   Index_name
TBTICK       PK_TICK_SYMID (SYMBOL_ID)
TBSYMBOLS    IDX_EXCHANGE_ID (EXCHANGE_ID)
TBSYMBOLS    IDX_SYMBOL_TYPE_ID(SYMBOL_TYPE_ID)
TBSYMBOLS    IDX_SYMNM(SYMBOL_NAME)
TBSYMBOLS    IDX_SYMNM_REUTER(SYMBOL_NAME_REUTERS)
TBSYMBOLS    PK_SYM_SYMID(SYMBOL_ID)

Tbtick is having one index, tbsymbols is having 5 different indexes on different
columns.

What could be the reason behind this ?
Why it is executing parallely on first one and why it is using index on second
one ?
Do I need to rebuild the indexes on first one ?
How can I eliminate this ?
Please let me know your suggestions.

Thanks

          Followup:

I doubt the init.ora's are the same OR the statistics in one differ
significantly from the other.

verify the init.ora's (diff a show parameter from each database) and check the
stats.

      Rating: 2
Parallel
      Query  April 23, 2002
Reviewer:  Kalpesh
       from USA
Hi Tom,
As per your suggestion, I have taken the difference of "show parameters"
(init.ora) for both the Databases and found no difference in any of the
parameters.

Tom, you have said statistics might be different ?
Can you explain that and How can I find and fix that ?
Thanks

          Followup:

select table_name, num_rows from user_tables;  compare the results.

do the same for indexes, look in user_tab_histograms as well.

      Rating: 2
Parallel Query
        April 25, 2002
Reviewer:  Kalpesh
       from USA
Hi Tom,
I have taken the statistics from user_tables, user_indexes and
user_tab_histograms. I am seeing differences between both the Databases.
Now what is the next step to fix the problem.
Please let me know.

Thank You

          Followup:

make the statistics the same.

analyze the tables in the "bad" database.

      Rating: 5
Another
      problem.  April 24, 2003
Reviewer:  Ganesh
      Raja  from Singapore
Hi,

I am experiencing Problems with Parallel Query. The query is running in parallel
if No Condition is given, but if there is any condition then the Query is not
running in parallel.

There is no Change in the Plan between these two queries except that the Query
is not running in parallel.

Since the Inner Query will be Submiited for EUL in discoverer i am Simulating
what Discoverer will write as a Query.

Thanks a Ton !!!!

Regards,
Ganesh R

===========================================================
                       Query
============================================================
Select *
From
(
      Select
          Revenue.Business_Unit,
          Revenue.Business_Division,
          Revenue.Product_Line,
          Revenue.Revenue_Mu,
          Revenue.Product_Sub_Family,
            Revenue.Part_Number,
            Revenue.Part_Description,
            Revenue.Order_Type,
            Al.Meaning            Return_Reason,
            Revenue.Order_Number,
            Revenue.Order_Line,
            Revenue.GL_Date        Revenue_Date,
            Cogs.GL_Date        Cogs_Date,
             Decode(Revenue.Currency,
            'USD',1,GDR.Conversion_Rate
              )    Conversion_Rate,
            Revenue.USP
            *
            Decode(Revenue.Currency,
            'USD',1,GDR.Conversion_Rate
              )                USP,
            Decode(OOLA.Line_Category_Code,
                  'RETURN',
                  -Ordered_Quantity,
                  Ordered_Quantity)        Quantity,
            Revenue.Revenue
            *
            Decode(Revenue.Currency,
            'USD',1,GDR.Conversion_Rate
              )                Revenue,
            COGS.Cogs                COGS
      From
          OE_Order_Headers_All    OOHA,
          OE_Order_Lines_All    OOLA,
        AR_Lookups            AL,
          GL_Daily_Rates        GDR,
            (
                  Select    Business_Unit,
                          Business_Division,
                          Product_Line,
                          Revenue_MU,
                          Product_Sub_Family,
                          Part_Number,
                          Part_Description,
                          Order_Type,
                          Order_Number,
                          Order_Line,
                          GL_Date,
                          Currency,
                          Sum(USP)                USP,
                          Sum(Revenue)            Revenue
                  From
                  (
                        Select    XAMT.D_BU                    Business_Unit,
                                XAMT.E_Dv                    Business_Division,
                                XAMT.F_PL                    Product_Line,
                                MC.Segment4                    Revenue_Mu,
                                MC.Segment5
Product_Sub_Family,
                                MSIB.Segment1                Part_Number,
                                MSIB.Description
Part_Description,
                                RCTA.Trx_Number                Invoice_Number,
                                RCTLGDA.GL_Date                GL_Date,
                                RCTA.Interface_Header_Attribute1
Order_Number,
                                RCTLA.Interface_Line_Attribute6    Order_Line,
                                RCTA.Interface_Header_Attribute2     Order_Type,
                                RCTA.Invoice_Currency_Code        Currency,
                                RCTLA.Unit_Selling_Price        USP,
                                RCTLGDA.Amount                Revenue
                        From        Mtl_System_Items_B        MSIB,
                        Mtl_Item_Categories        MIC,
                                MTl_Categories            MC,
                                Ra_Customer_Trx_All        RCTA,
                                Ra_Customer_Trx_Lines_All    RCTLA,
                        RA_Cust_Trx_Line_GL_Dist_All    RCTLGDA,
                            GL_Code_Combinations        GCC,
                            XXATORCL.XXAT_AR_MU_TAB            XAMT
                        Where        1=1
                        And        MSIB.Inventory_Item_Id            =
MIC.Inventory_Item_Id
                        And        MSIB.Organization_Id             =
MIC.Organization_Id
                And        Mic.Category_Id                = Mc.Category_Id
                  And        Mic.Category_Set_Id             = 73 /* Product
Hierarchy */
                        And        RCTA.Customer_Trx_Id            =
RCTLA.Customer_Trx_Id
                        And        RCTLA.Inventory_Item_Id            =
MSIB.Inventory_Item_Id
                        And        RCTLA.Warehouse_Id            =
MSIB.Organization_Id
                And        RCTLA.Customer_Trx_Line_Id         =
RCTLGDA.Customer_Trx_Line_Id
                  And        RCTLGDA.Code_Combination_Id        =
GCC.Code_Combination_Id
                  And        XAMT.MU                    = MC.Segment4
                  And        GCC.Segment2                = '400001'
                  And        RCTLGDA.Amount                 != 0
                  And        RCTLGDA.Account_Set_Flag        = 'N'
                        And        Interface_Line_Context            = 'ORDER
ENTRY'
                  )    Revenue
                  Group By
                    Business_Unit,
                    Business_Division,
                          Product_Line,
                          Product_Sub_Family,
                          Revenue_Mu,
                          Part_Number,
                          Part_Description,
                          Order_Type,
                          Order_Number,
                          Order_Line,
                          GL_Date,
                          Currency
            ) Revenue,
          (
            Select    Order_Line,
                    GL_Date,
                    Sum(Nvl(Trans_Cogs,Base_Cogs))
                        * Decode(Cogs.Currency_Code,
                                  'USD',1,GDR.Conversion_Rate)  Cogs
            From
            (
                    Select    /*+ Parallel(MTA,5) Index(MMT
MTL_MATERIAL_TRANSACTIONS_U1) */
                        MMT.Source_Line_Id        Order_Line,
                            MTA.Transaction_Date        GL_Date,
                            Nvl(MTA.Currency_Code,
                                GSOB.Currency_Code)    Currency_Code,
                            MTA.Transaction_Value        Trans_Cogs,
                        MTA.Base_Transaction_Value    Base_Cogs
                    From        MTL_Material_Transactions    MMT,
                            MTL_Transaction_Accounts    MTA,
                            GL_Code_Combinations        GCC,
                            HR_Organization_Information    HOI,
                            GL_Sets_Of_Books            GSOB
                    Where        MMT.Transaction_Id        = MTA.Transaction_Id
                    And        MTA.Reference_Account        =
GCC.Code_Combination_Id
                    And        MTA.Organization_Id        = HOI.Organization_Id
                    And        HOI.Org_Information1        =
GSOB.Set_Of_Books_Id
                    And        HOI.ORG_Information_Context    = 'Accounting
Information'
                    And        GCC.Segment2            = '700001'
                    And        MTA.Accounting_Line_Type    = '2'
              )            COGS,
             GL_Daily_Rates    GDR
             Where        COGS.Currency_Code        = GDR.From_Currency (+)
              And        Trunc(COGS.GL_Date)        =
Trunc(GDR.Conversion_Date(+))
              And        GDR.To_Currency(+)        = 'USD'
              And        GDR.Conversion_Type(+)        = 'Corporate'
              Group By    Order_Line,
                      Gl_Date,
                      Cogs.Currency_Code,
                      GDR.Conversion_Rate
        )    COGS
      Where         OOHA.Header_Id            = OOLA.Header_Id
      And        OOLA.Line_Id            = Revenue.Order_Line
      And        AL.Lookup_Code(+)            = OOLA.RETURN_REASON_CODE
      And        AL.Lookup_Type(+)         = 'CREDIT_MEMO_REASON'
      And        Revenue.Order_Line        = COGS.Order_Line(+)
      And        To_Char(Revenue.GL_Date,
                      'YYYYMM')        = To_Char(COGS.GL_Date(+),'YYYYMM')
      And        To_Char(OOHA.Order_Number)    = Revenue.Order_Number
    And        Revenue.Currency            = GDR.From_Currency (+)
      And        Trunc(Revenue.GL_Date)        = Trunc(GDR.Conversion_Date(+))
      And        GDR.To_Currency(+)        = 'USD'
      And        GDR.Conversion_Type(+)        = 'Corporate'
)
--Where      Product_Line Like 'F47515%'
--And      Revenue_Date Between '01-MAR-2003' and Last_Day('01-MAR-2003')

/*
Uncomment the Abve Two Lines and the query is Going for Serial Access. With
Comment or Without Comment the The Material_Transaction_Accounts Table (MTA)
which i have hinted to run in parallel is Going for a Full Table Scan
*/

============================================================

          Followup:

I don't really know what to do with a N page query like that.  The sql parser in
my head overloaded.

I doubt the plans are the same -- look closer -- you'll find an index path in
there somewhere.

In any case, the query itself is not useful.  one would need the plans AND this
would not be the right place to do that -- when I'm taking questions, this would
be a candidate for a new question.

      Rating:
      4
  July 07, 2003
Reviewer:  Vipin
       from New york
Hi Tom,

could you please clear this basic doubt for me. Will I be benefited from the
Oracle Parallel query option unless and until my table is stripped across
multiple data files which are located on multiple disks, so that mutiple
controllers can do the IO for me in parallel.

          Followup:

that will increase the benfit, yes.

is it mandatory -- no, not necessarily, will it be benefical, absolutely.

      Rating:
      2
  July 07, 2003
Reviewer:  Vipin

Could you please tell in which scenario the parallel query option will help when
the table is not stripped that is the table is residing in the same tablespace.

          Followup:

are you a multi-user system?
do you have some degree of scalability even when more then one person hits your
database at the same time?

so why not for parallel?

select sum( some_number ) from big_table;

say that is done in parallel 2 on a single disk machine.  Might it be true that
while process 1 is reading disk , process 2 is summing some numbers and vice
versa.

Unless you run everything in single user mode, you are in fact doing "parallel"
stuff right now.

Consider an index build

i read
i sort
i write

might it be that two processes doing it get it done faster, even though it is
the same disk.  You do not need to have >1 disk to increase throughput -- unless
your process is 100% disk based.

Full scans read ahead -- so every IO reads many blocks.  It takes time to
process those blocks, during which you are not reading (and so someone else
might be able to!)

      Rating:
      4
  July 07, 2003
Reviewer:
      A reader
Tom,

 is it just benchmarking that would drive me to use Parallel query option or is
there any situation in which by seeing the problem i get a spark in head "Oh
here I can use PARALLEL query"...

          Followup:

administrative things pop into my head first and foremost....  (create index,
other "big" things)

then there is DIY parallelism (do it yourself) - make your CODE parallelizable -
that big ETL (extract/transform/load) you do, make sure your T is parallelizable
but writing code that can run concurrently on bits of the data

then there is parallel query, sort of what I would use last...  it is of most
use when you have few users -- and how often does that happen anymore ;)

      Rating:
      5
  July 07, 2003
Reviewer:
      A reader

      Rating: 4
Inspite of
      Noparallel Hint Parallel Query is executed.  August 12, 2003

Reviewer:  A reader
Tom,
    I've a index on a table with degree 10. Our parallel query was dying with
error ORA-12805. We decided not use the parallel query but we cannot set the
degree to 1. Hence we used hint NOPARALLEL. Inspite of of hint my query is
executed in parallel. Below are 3 trials of the query with changes in the hint.

   Can you suggest any reason why my parallel query is used inspite of hint or
without hint ?

12:49:58 OPS$QDW@QDWP:tpcds1> set autotrace traceonly explain
12:50:06 OPS$QDW@QDWP:tpcds1> SELECT  /*+ NOPARALLEL INDEX(pstn_order_c_2
pstn_order_c_a_pk) */ data_date
12:50:17   2  FROM   pstn_order_c_2
12:50:21   3  WHERE  order_number = '123456'
12:50:25   4  AND    sub_zone     = 'ZA';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=85 Card=1 Bytes=15)
   1    0   INDEX* (FAST FULL SCAN) OF 'PSTN_ORDER_C_N_PK' (UNIQUE) (C :Q108810
          ost=85 Card=1 Bytes=15)                                      7000

   1 PARALLEL_TO_SERIAL            SELECT /*+ Q1088107000 */ A1."DATA_DATE" FRO
                                   M :I."PSTN_ORDER_C_2"."PSTN_ORDER_C_N_PK"  P
                                   X_GRANULE(0, BLOCK_RANGE, DYNAMIC) A1 WHERE
                                   A1."ORDER_NUMBER"='123456' AND A1."SUB_ZONE"
                                   ='ZA'

2:51:29 OPS$QDW@QDWP:tpcds1> SELECT  /*+ NOPARALLEL */ data_date
12:51:32   2  FROM   pstn_order_c_2
12:51:36   3  WHERE  order_number = '123456'
12:51:41   4  AND    sub_zone     = 'ZA';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=85 Card=1 Bytes=15)
   1    0   INDEX* (FAST FULL SCAN) OF 'PSTN_ORDER_C_N_PK' (UNIQUE) (C :Q108811
          ost=85 Card=1 Bytes=15)                                      0000

   1 PARALLEL_TO_SERIAL            SELECT /*+ Q1088110000 */ A1."DATA_DATE" FRO
                                   M :I."PSTN_ORDER_C_2"."PSTN_ORDER_C_N_PK"  P
                                   X_GRANULE(0, BLOCK_RANGE, DYNAMIC) A1 WHERE
                                   A1."ORDER_NUMBER"='123456' AND A1."SUB_ZONE"
                                   ='ZA'

12:52:29 OPS$QDW@QDWP:tpcds1> 12:52:29 OPS$QDW@QDWP:tpcds1> SELECT  data_date
12:52:35   2  FROM   pstn_order_c_2
12:52:39   3  WHERE  order_number = '123456'
12:52:43   4  AND    sub_zone     = 'ZA';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=85 Card=1 Bytes=15)
   1    0   INDEX* (FAST FULL SCAN) OF 'PSTN_ORDER_C_N_PK' (UNIQUE) (C :Q108811
          ost=85 Card=1 Bytes=15)                                      3000

   1 PARALLEL_TO_SERIAL            SELECT /*+ Q1088113000 */ A1."DATA_DATE" FRO
                                   M :I."PSTN_ORDER_C_2"."PSTN_ORDER_C_N_PK"  P
                                   X_GRANULE(0, BLOCK_RANGE, DYNAMIC) A1 WHERE
                                   A1."ORDER_NUMBER"='123456' AND A1."SUB_ZONE"
                                   ='ZA'

          Followup:

and you have filed a tar with support right?

NOPARALLEL(pstn_order_c_2)

      Rating: 5
Parallel
      Query  November 12, 2003
Reviewer:  wor
       from US
I have read several bits and peices about Parallel Query and bind variables.
We have Oracle on Win2000, 2 CPU's and around 80GB hard drive.
1) Parallel Query is good if you have huge tables that requires full table scan.
Is this correct ?
2) When you use Bind variables in the SQL the oracle does Full Table Scan
beacase the oracle does not know what value the bind variable may have. Is this
correct ?

If both the answer are correct than
What is the harm in writing all the SQl statements with parallel hint when using
bind variables ( even for tables with 100,000 rows ) ?
Is there only a win-win situation in this or is there any drawback ?

          Followup:

1) that is one of the uses, yes.  you need to meet other criteria (eg: you want
to allow a single user to use tons of resources for example) but yes.

2) no, that is patently "not correct"

November 12, 2003 Ketan -- Thanks for the question regarding
      "Auditing of PL/SQL Functions and Procedure Calls", version 8.1.7

      You Asked
Hi Tom,

Thanks a lot for sharing your in-depth knowledge with us.

I have a requirement to audit PL/SQL procedures and function calls with all
parameter values. I want to write some generic framework which could be used by
all the packages and functions.

Example is:

Procedure CreateEmp ( Emp ID Number,
            EmpName Varchar2,
            Department Varchar2) is
BEGIN
  --
  -- First call should be the audit call
  -- with all parameter values
  --
  audit('CreateEmp',EmpId, EmpName,Department);
  --
END CreateEmp;
--
Procedure ChangeEmp ( Emp ID Number,
            EmpName Varchar2,
            Address    varchar2) is
BEGIN
  --
  -- First call should be the audit call
  --
  audit('ChangeEmp',EmpName, Address);
  --
END ChangeEmp;

I want to write a generic audit procedure which could take any number of
arguments, and create audit records with values.

The result of above audit would be:

Audit table will have a master record "CreateEMP".
Whereas AuditValues table will have all child records with name, value pair.
Like Emp Name=Scott, Dept=Accounts, etc.

This type of auditing provides many advantages to the application:
1. User can see what PL/SQL they are executing
2. Developers can use it for debugging
3. During fresh install, when schema is changed, import/export or
backup/recovery is not an option. User can replay the entire provisioning
generated through PL/SQL audit. QA would love it.

What is the right way to design this framework so that I can utilize Oracle's
power more and with less code. Any new features in Oracle 8i, 9i? Please guide
me with right approach of solving it.

Thanks a lot in advance.

Regards,

Ketan.

      and we said...
It could be as easy as this:

ops$tkyte@ORA920> create or replace type params as table of varchar2(4000);
  2  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table audit_tbl
  2  ( call_stack  varchar2(4000),
  3    called     timestamp default systimestamp,
  4    parameters params
  5  )
  6  nested table parameters store as audit_tbl_parameters;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace trigger audit_tbl_trigger
  2  before insert on audit_tbl for each row
  3  begin
  4          :new.call_stack := dbms_utility.format_call_stack;
  5  end;
  6  /

Trigger created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace procedure p( p_input1 in int, p_input2 in
date, p_input3 in varchar2 )
  2  as
  3  begin
  4          insert into audit_tbl(parameters) values
(params(p_input1,p_input2,p_input3));
  5          dbms_output.put_line( 'Hello World' );
  6  end;
  7  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec p( 1, sysdate, 'blah blah blah' );
Hello World

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from audit_tbl;

CALL_STACK
---------------------------------------------------------------------------------
--------------------------------------------------
CALLED
---------------------------------------------------------------------------
PARAMETERS
---------------------------------------------------------------------------------
--------------------------------------------------
----- PL/SQL Call Stack -----
  object      line  object
  handle    number  name
0x57d6cb10         2  OPS$TKYTE.AUDIT_TBL_TRIGGER
0x57d36220         4  procedure OPS$TKYTE.P
0x57d2748c         1  anonymous block
29-JUL-03 07.42.30.684036 AM
PARAMS('1', '29-JUL-03', 'blah blah blah')

Review &
      Followup

      Rating: 5
Auditing of
      PL/SQL procedures and functions  July 29, 2003

Reviewer:  Ketan. Bengali  from Sunnyvale, CA, USA

Hi Tom,

Thank you very much. It is very much useful. Ours is a complex Element
Management/Network Management System developed using Oracle Forms,Java
Swing/Servlet, CORBA and SNMP. To configure any element in the database, all of
these clients invoke PL/SQL APIs. To simulate the exact scenario, I have
packaged your procedures.

create or replace package audit_pack is
    --
    -- Putting it in seperate package will
    -- allow us to add/change any future audit
    -- requirements without changing other PL/SQL APIs.
    --
    procedure insert_audit( p_params params);
end audit_pack;
/
create or replace package body audit_pack as
    --
    procedure insert_audit(p_params params) is
    begin
        insert into audit_tbl(parameters) values
            (p_params);
        --
    end insert_audit;
end audit_pack;
/
create or replace package emp_pack as
    --
    procedure CreateEmp(
        p_input1 in int,
        p_input2 in date,
        p_input3 in varchar2 );
    --
end emp_pack;
/
create or replace package body emp_pack as
    --
    procedure CreateEmp(
        p_input1 in int,
        p_input2 in date,
        p_input3 in varchar2 ) is
    begin
        --
        audit_pack.insert_audit(params(p_input1,p_input2,p_input3));
        --
        dbms_output.put_line( 'Hello World' );
        --
    end CreateEmp;
    --
end emp_pack;
/
EMSDB53.IPVERSE  SQL > exec emp_pack.CreateEmp(29,sysdate,'Scott');
Hello World

PL/SQL procedure successfully completed.

EMSDB53.IPVERSE  SQL > select * from audit_tbl;

CALL_STACK
--------------------------------------------------------------------------------
CALLED
---------
PARAMETERS
--------------------------------------------------------------------------------
----- PL/SQL Call Stack -----
  object      line  object
  handle    number  name
81fcf92c         2  IPVERSE.AUDIT_TBL_TRIGGER
82e58ea0         6  package body IPVERSE.AUDIT_PACK
80e6eb54         9  package body IPVERSE.EMP_PACK
80fe2374         1  anonymous block
29-JUL-03
PARAMS('29-JUL-03', 'Scott', '29')

If you look at call stack, it doesnot print procedure name "EMP_PACK.CreateEmp".
It only prints package name "EMP_PACK". Is this the default behaviour? While
passing params, I could pass procedure name as well; is it a right approach? How
to get parameter names (p_input1, p_input2, p_input3)? I can use
dbms_sql.describe_columns; is there a better way other than using
describe_columns?

Once again thanks a lot for helping us understand the power of Oracle.

Regards,

Ketan.

          Followup:

the object of interest is "EMP_PACK" (you could have 50 CreateEmp procedures in
there).

The line number -- 9 -- identifies where in the package you are.  We only track
top level objects -- things you could "grant on".  You can grant on EMP_PACK,
you cannot grant on EMP_PACK.CREATEEMP -- createemp doesn't "exist" really.

I would not bother with the procedure name unless you really want it, the line
number tells me what I want.

same with the parameter names, no need to record that -- you don't want to make
the audit routine dog slow.

      Rating: 4
Auditing of
      PL/SQL Procedures and Functions  July 29, 2003

Reviewer:  Ketan  from Sunnyvale, CA, USA
I have a requirement to store procedure name and parameter names in the audit.

Use Case I:

We are developing an application which would be deployed to many telecom
customers. Customers donot know the internal table structures. Whenever user
makes a change, GUI invokes the API. Now API might be creating records in 10
tables but user is not aware of it. When user wants to see the audit, we can't
show them records from 10 tables. We have to show them the original API call
which they understands very well.

Use Case II:

Since it is a real time network management system, certain test cases can be
executed only when the network is in certain state. It takes a lot of time for
QA to bring network to that state. It would be easier for QA team to generate
PL/SQL calls from these audit data and reprovision any new system any time.

If we donot capture API calls then QA would have to:
a. Test GUI anyway as it is part of the product
b. Maintain PL/SQL test automation files to configure different systems to test
different scenario.

Now if we add more parameters to the procedure then QA has to change all test
automation scripts manually. It also adds chances of human errors.

By providing procedure name and parameter names in the audit, they can only test
the GUI and generate test automation scripts from the audit tables. This is less
error prone and will give more productivity.

Use Case III:

GUI is written in Java Swing which internally calls servlet and servlet calls
PL/SQL API. Different developers are responsible for maintaining different part
of the code. Now when a problem arise, entire team is needed to debug it as
different team members have specialization in different technologies. By
providing this audit, it is easy to narrow down problem between Swing, Servlet
or PL/SQL level and only one person can solve it either in QA or at customer
site.

We are planning to provide this feature in such a way that it could be turned on
or off any time by specifying application parameter.

Thanks,

Ketan

          Followup:

Ok, then the programmers will have to pass a name, and two arrays -- an array of
names and an array of values.

      Rating: 5
Thanks a
      lot  July 29, 2003
Reviewer:  Ketan
       from Sunnyvale, CA, USA
Thanks a lot Tom. Your input is very much valuable to us. I will design it the
way you have specified.

          Followup:

doh -- do it the way that works best for you -- I just gave you ideas, run with
it.

      Rating: 5
Auditing
      procedures and functions when they compile  November 12,
      2003
Reviewer:  Arun Mathur  from Marietta,GA

Hi Tom,

I can't say enough good things about your site, and how much you're contributing
to those using Oracle. I'm looking at auditing for the first time, and see that
I can audit a procedure when it's executed.  However, is there a way I can audit
a procedure/function/package when it's been modified?

Take care,
Arun

          Followup:

you would be auditing creates and drops on them...

      Rating:
      5
Top  November 13, 2003
Reviewer:
      Orca  from Germany
Short and efficient
Orca

November 12, 2003 d -- Thanks for the question regarding "Wait
      Event stats", version 817

      You Asked
I am trying to determine what is the significant criteria when examining wait
events:
-average wait time or
-total wait time or
-total waits
Different orderings of these columns from v$system_event, and other wait tables,
give completely different pictures of the database - I am trying to find
comprehensive guidelines about which criteria are significant for which wait
events and or types of events.
The 'oracle 8i internal services' book does this to some degree, but also leaves
a number of events out.
Thanks

      and we said...
Of:

a) average wait time or
b) total wait time or
c) total waits

A is, no wait, make that B -- whoops -- it is C.  No, it is B & C, no -- wait,
it is A and C....... ;)

It depends.

Suppose you had and Average wait time of 0.1.  Pretty good -- until you consider
there was 1 wait event whose time was 1000 seconds but there were 10,000 waits!

On the other hand, you have 10,000 waits -- sounds pretty bad -- but that was
10,000 waits over a month with billions of operations.  Now, its pretty good.

You need to take a holistic approach to this.  These are just NUMBERS, and by
themselves any number is meaningless (out of context - they mean nothing).

For example -- if the system is running DANDY.  People are more then happy with
the performance.  Things are going great.  Then -- do it matter if you have
waits at all?  Probably not (people who tune to try and remove all waits --
which is impossible -- at this point have CDT, compulsive tuning disorder ;)

It is when you have a problem that you gather all of the relevant information
and analyze it -- I typically start with total wait time (looking for some
really low hanging fruit so I can "pick it" -- "fix it" -- and go back to doing
whatever else I was doing).  If I can solve the problem that was causing 50% of
the total wait time, that might advance me best.

But, then again, it is like the "whack a mole" childs game.  You know, the one
where you have a soft hammer and the moles pop up out of holes and you smash
them down.  No matter how many you smash down -- another pops up elsewhere.
Tuning can be alot like that as well.  Removing a given bottleneck and (and
often does) produce a brand new bottleneck elsewhere (maybe an even more SEVERE
one)...

So, I feel you need all of the above + alot more (domain knowledge of the
applications, knowledge of the system, knowledge of what you can affect and
cannot affect and so on).

At the application level, I start with TKPROF -- looking for statements that
consume the most time.  At the system, I start with statspack looking for big
bottlenecks.
Review & Followup

      Rating: 5
wait event
      stats  September 09, 2002
Reviewer:
      A reader  from san francisco
Thanks for your response. I had been wondering about the most significant gauge
of wait events, and started implementing the wait queries from the 'oracle8i
internal services' book, but soon realized that since the queries contained
specific wait events they were excluding all those not specified and maybe
missing a bunch, which led to my question. Your 'no easy answer' answer rings
true; in general it seems that the average wait time is the most useful or best
starting point, in conjuction with the current state of the database and
knowledge of what each wait event implies. Thanks again.

      Rating: 4
Wait
      events  May 30, 2003
Reviewer:  Rahul
       from India
Hi Tom

I am firing a query and generating the trace file using sql trace with level 12
(10046). In .trc file i am getting the wait events. I don't know how to over
come these wait events and why are they occuring?
Please explain me about following statements-
WAIT #1: nam='SQL*Net message to client' ela= 0 p1=1413697536 p2=1 p3=0
WAIT #1: nam='direct path write' ela= 0 p1=1025 p2=777 p3=23
WAIT #1: nam='direct path read' ela= 0 p1=1025 p2=915 p3=23

Also let me know
1) how to reduce the CPU time during FETCH operation in file generated by
TKPROF?
2) How to reduce the time disparity between CPU and Elapsed in file generated by
TKPROF?
Because in your book Expert 1 on 1 , you recommend to reduce the time disparity.

Thanks

          Followup:

direct path reads and writes are general "temporary tablespace operations" (or
lobs)

look at your sort_area_size/hash_area_size, is it too small for the typical
queries you run on your system.

1) buy much faster cpu's, or do less work
2) remove waits

      Rating:
      5
  May 30, 2003
Reviewer:
      A reader
Hi Tom

If elapsed time is zero that mean no wait then why do we care about that wait.

WAIT #1: nam='direct path write' ela= 0 p1=1025 p2=777 p3=23
WAIT #1: nam='direct path read' ela= 0 p1=1025 p2=915 p3=23

Thanks

          Followup:

actually, that just means the wait was shorter then the unit of measurement
(1/1000th of a second in 8i, 1/1000000th of a second in 9i).

That the wait happened is still relevant.  if you see 10,000 waits -- with a sum
of 0 for total time -- then, well, each wait took less then say 1/1000th of a
second.  But I'll bet it wasn't zero (we just couldn't measure them)

      Rating:
      5
  June 20, 2003
Reviewer:
      A reader
Hi Tom,
  I am using "alter session set events = '10046 trace name context forever,
level 8'" command to trace one sql statement .

After sql stmt is executed I trun off tracing by

alter session set events = '10046 trace name context forever off'

In Trace file I found two statements. One is the one I executed and another is

PARSING IN CURSOR #2 len=36 dep=1 uid=0 oct=3 lid=0 tim=2532409225 hv=1705880752
ad='84f5e060'
 =====>>  select file# from file$ where ts#=:1
END OF STMT
PARSE #2:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=3,tim=2532409225
EXEC #2:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=3,tim=2532409225
WAIT #2: nam='db file sequential read' ela= 0 p1=1 p2=100 p3=1
WAIT #2: nam='db file sequential read' ela= 0 p1=1 p2=60 p3=1
FETCH #2:c=0,e=0,p=2,cr=2,cu=0,mis=0,r=1,dep=1,og=3,tim=2532409225
FETCH #2:c=0,e=0,p=0,cr=1,cu=0,mis=0,r=0,dep=1,og=3,tim=2532409225
WAIT #1: nam='file open' ela= 0 p1=0 p2=0 p3=0
WAIT #1: nam='db file sequential read' ela= 0 p1=99 p2=5350 p3=1

I didn't run "select file# from file$ where ts#=:1 " sql . In trace file most of
the wait is on this one.

Could you explain me why I am getting this sql in tracefile.

      Rating: 4
direct path
      read/write (lob) contention  November 10, 2003

Reviewer:  A reader
Hi

I am reading direct path read wait definitions (we have tons of direct path
read/write (lob) waits)

it says:

Direct path reads are generally used by Oracle when reading directly into PGA
memory (as opposed to into the buffer cache). If asynchronous IO is supported
(and in use) then Oracle can submit IO requests and continue processing. It can
then pick up the results of the IO request later and will wait on "direct path
read" until the required IO completes.

I dont understand the part where it says read directly into PGA... For example
if Oracle read a blob does that goes to user´s PGA?

Also, I always thought direct path read/write is bypassing filesystem buffer. Am
I wrong?

          Followup:

the blobs have been defined as NOCACHE.

they do not go into the SGA

you use direct IO operations to read and write them.

they go directly into your users PGA to be returned to the client when they ask
for it.

direct path doesn't "bypass the buffer cache", they bypass the DATABASE cache.
we cannot bypass the OS buffer cache (you would have to disable it for us to
bypass that)

      Rating: 5
direct path
      read vs buffer cache read  November 11, 2003

Reviewer:  A reader
Hi

Is direct path faster than buffer cache read? Or LOBs are used so to NOT flood
the buffer cache?

If I define a recycle pool for LOBs and cache them  (so I wont flood the other
useful buffers) will I increase my read performance and reduce direct path
read/write (lob) waits?

          Followup:

depends....

but yes, a non-cached lob is used to avoid flooding the buffer cache.  It is
your choice -- to cache or not cache LOBS.  you decide and tell us when you
create the table.

it could, if you READ and REREAD and REREAD and REREAD the same lob over and
over and over again.  do you do that?  if not, then no, it won't do much for
you.  remember, a cache only works on the second read!

      Rating: 4
High Row cache
      locks  November 12, 2003
Reviewer:
      Shailandra  from NJ USA
Hi Tom,

One of my databases stated giving "WAITED TOO LONG FOR A ROW CACHE ENQUEUE LOCK!
pid=n" in my alert.log file. I have statspack installed and running on this. I
compared the statspack report in the normal period and the period when these
error were reported in the alert.log file. I found row cache lock misses very
high.

                                                                    Avg
                                                     Total Wait    wait  Waits
Event                               Waits   Timeouts  Time (cs)    (ms)   /txn
---------------------------- ------------ ---------- ----------- ------ ------
control file sequential read      105,264          0           0      0  392.8
db file scattered read             77,543          0           0      0  289.3
row cache lock                     35,677     35,644           0      0  133.1
refresh controlfile command        17,230          0           0      0   64.3
db file sequential read            15,594          0           0      0   58.2
latch free                          7,869      5,132           0      0   29.4

and also I found that dc_sequences_grants getmisses 91%. Under normal conditions
it were almost 0%.

                           Get         Pct     Scan   Pct      Mod  Final  Pct
Cache                    Requests     Miss Requests  Miss      Req  Usage  SGA
---------------------- ------------ ------ -------- ----- -------- ------ ----
dc_constraints                    0               0              0      0    0
dc_database_links                 0               0              0      0    0
dc_files                      8,311    0.0        0              0     25   71
dc_free_extents                 660    0.0        0              0     71   84
dc_global_oids                    0               0              0      0    0
dc_histogram_data                 0               0              0      0    0
dc_histogram_data_valu            0               0              0      0    0
dc_histogram_defs            15,730    0.9        0              0  1,374   99
dc_object_ids               253,461    0.0        0              0    881  100
dc_objects                    6,225    2.7        0              0    857  100
dc_outlines                       0               0              0      0    0
dc_profiles                      62    0.0        0              0      1   50
dc_rollback_segments          1,972    0.0        0              0     30   86
dc_segments                  27,152    0.1        0              0    687  100
dc_sequence_grants               34   91.2        0              0     17   25
dc_sequences                  3,152    0.2        0          3,174     55   90
dc_synonyms                   1,178    3.8        0              0    252  100
dc_tablespace_quotas              0               0              0      6   46
dc_tablespaces               12,505    0.0        0              0     11   73
dc_used_extents                   0               0              0    154   93
dc_user_grants               10,221    0.1        0              0     29   74
dc_usernames                  1,681    0.1        0              0     13   59
dc_users                     18,069    0.1        0              0     30   77
ifs_acl_cache_entries             0               0              0      0    0
          -------------------------------------------------------------

What could be the reasons? Do I need to increase the shared_pool size. During
this period users were lots of reports which delete lots of records from a table
and insert lots of records and then generate a report. This table has many
indexes. Your comment/inputs on this will be appreciated. It is important to
note that this weekend db_buffer size was increased by DBA. These errrors were
seen from monday morning when users started using the system.

Thanks

          Followup:

Check the number of locks in v$lock, and what kind of locks they are while the
reports are running.

you have blocking / blockee problems, reports are stomping on eachother.

these reports are using global temporary tables and not real tables right?

what do you have your sequence caches set to (if default of 20 and the reports
make heavy use of them, set to 100000 or something really big)

      Rating:
      5
  November 12, 2003
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
Tom,
How did you figure out that the reports were using GTTs?
Thanks

          Followup:

i didn't, i was asking a loaded question.  I said:

these reports are using global temporary tables and not real tables right?

i actually think they are not (and that would be part of the problem)

      Rating: 5
High Row cache
      locks   November 12, 2003
Reviewer:
      Shailandra  from NJ
Tom,

The application folks are not using global temporary table. They are using real
table for lots of inserts/delete. These waits are not happenning now, So I can
not see the blokee/bloker now. But when it was happenning yesterday, I checked
up there was no blokee/bloker locks. It is important to note that only thing
changed on weekend was increasing data block buffers.

I also checked up the sequence, this sequence has a cache of 0. it looks like to
me that cache size of 0 was the problem. But could increasing buffer cache will
aggreviate this problem. I do not think so. What is your opinion?

Regards

          Followup:

there had to have been, how did you "check" -- enqueues are -- well, they are
locks.

having a cache size of 0 (nocache basically) is a performance killer.

November 12, 2003 J -- Thanks for the question regarding "Dev
      Schema Versioning", version 9.2.0.3.0

      You Asked
Hi Tom.

The background:  The Data Modeling (DM) group controls the database objects in a
shared Development schema.  The database Developers (which is the role that I
currently play) do not have their own separate environments, but share a common
schema (scott/tiger !?!) which has SIUD privs against the official Dev schema.

The DM concern is that they don't want developers to build code based on
unapproved database objects (which is understandable). The DBAs like the fact
that they have a single Developer schema to manage.  The Developers have
conflict issues arising from the shared schema environment.

So the question becomes this:  At AskTom Worldwide headquarters (or other sites
that you have delt with), how do you successfully manage Development database
version control so that your Developers have the freedom to try, test, and
optimize new ideas, and yet maintain a consistent "single version of the truth"
schema that is approved by your DM/DA group?

I'm looking for a rational versioning methodology that I can put forth to all
three interested parties (DM, DBA, and Developer) that would address their FUD
concerns and help streamline the development process.

Many thanks!
- J

      and we said...
I have a database installed on every single computer I own.  I get to test and
play with ANYTHING ANYTIME (as does everyone that works for and with me).

You develop all you want in your play sandbox and then role it out into the
"real" environment.

You can download Oracle "personal edition" for this purpose "for free" for
single user use on a development machine.
Review &
      Followup

      Rating: 5
Do the have
      personal edition for Linux?  November 11, 2003

Reviewer:  A reader
Does oracle have or plan to have persobnal edition for linux just to put on a
laptop to play and develop with?

      Rating: 2
my 2
      cts  November 11, 2003
Reviewer:  Marc Blum
       from Aachen, Germany
Your problem seems to be, that the data modelers and the DBAs rely on a single
schema to be the definition of the database structure. In my view, a schema is
just an instantiation of the data model. Your work should not rely on the
existence of a specific instantiation. Consider this development environment:

- the data modeling group develops some data model (whiteboard, paper and pencil
are appropriate tools)

- the DBAs implement the data model as SQL-scripts (vi, notepad are appropriate
tools) and manage these scripts in some version control system (VCS, Visual
Source Safe etc.)

These scripts are the objects, your development process should rely on:

- each database developer has his own ORACLE database
- the DBAs provide SQL scripts, batches, shell scripts to (re)generate with some
simple commands (1) a whole new database, (2) all application specific objects,
(3) testdata

If the developer crashes the database/instance? rebuild it!
If the developer corrupts the data? rebuild it!
If the developer tries other table structures or ORACLE features? let him do it
and report his findings to the data modeling group!
If the developers corrupts testdata of other developer? not possible!

      Rating: 2
Dev Schema
      Versioning problem  November 11, 2003
Reviewer:
      J  from Vancouver
Hello! Thanks for the feedback.

Unfortunately, we have a "standard desktop configuration" which prevents us from
having our own PO9. Bummer. The problem is non-technical, but more
procedural/managerial in nature.  I was asking to see if there were some
tried-and-true schemes for controlling versioning in the Dev environment. The
sharing of a single schema amongst the Developers is a struggle.

One idea that was brough forth for is to:

1) Have a single master schema owned by the DM. This is the schema that will
make its way into QA test.

2) Have individual schemas owned by each Developer. They can copy the DM model
into their own schemas for dev purposes.

3) Before promoting any proposed database object (or modifications) to the
existing DM schema, the Developer must...
a) Work with the DM prior to laying out much code to insure that the changes are
appropriate for the need.
b) Get the proposed changes approved by the DM.
c) Keep in touch with other Developers to ensure that the proposed changes don't
adversely affect their development efforts.

However, in this scheme, the DM acts as the modeler, gatekeeper and QA. The last
two roles are activities that they do not wish to play because of workload and
staffing considerations.

Any thoughts on perhaps another scheme?

Thanks!
- J

          Followup:

get them to "change the standard desktop configuration".  squeaky wheels and
all...  change -- its constant.  you never know what you might be able to get if
you ask for it!

The DBA's will have more work -- lots more work, they might not like that.

I'd try really really hard to get the db on the desktop.  it will AMAZE you how
much you learn.

      Rating: 5
as does
      everyone that works for and with me  November 11, 2003

Reviewer:  Kal
We unlucky people... People here think playing with the db is a bad thing... :-)

      Rating:
      2
Followup...  November 12, 2003
Reviewer:
      J  from Vancouver
Well, the ideas were quickly shot down by the DBAs and DM team - "too much
workload already", "have to change our processes", etc...   Anyway, many thanks
for the encouragement and advice.

          Followup:

rock, hard place come to mind.

you need to have the database on your desktop, more then you need MS Word on
your desktop (i sure use it more then word :)

      Rating: 4
Just
      install  November 12, 2003
Reviewer:  Jim
       from Portland, OR
Just download and install the database.  If they are so overworked they will
never know.

November 12, 2003 -- Thanks for the question regarding "Fine
      Grained Auditing", version 9.2.0.1

      You Asked
Hi Tom

I am trying to audit some queries, I read about Fine Grained Auditing in Oracle
University notes. It says that if I set an audit condition such as

sal > 3000

then the triggering event is not that the user accessed emp records which
salaries are bigger than 3000 but the salary was actually returned to the user.

I dont understand this statement because I ran several sQL such as

select * from emp where sal > 3000 returned one row
select * from emp where sal > 5000 returned 0 row

both got recorded in dba_fga_audit_trail, if it´s saying the salary ACTUALLY
returned to the user then sal > 5000 shouldnt get recorded in
dba_fga_audit_trail no? Since no row is returned

      and we said...
did you analyze the table?

without using the CBO, you will get (documented fact) "false positives".  Only
the CBO is smart enough to tell when the predicates intersect.  The RBO just
audits everything.

Here is my example of this:

demo@ORA920.US.ORACLE.COM> connect sys as sysdba;
Enter password:
Connected.
demo@ORA920.US.ORACLE.COM> delete from fga_log$;

2 rows deleted.

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> connect demo/demo
Connected.
demo@ORA920.US.ORACLE.COM> set echo off
demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

Now, we'll create our application schema

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> create table emp as select * from scott.emp;

Table created.

demo@ORA920.US.ORACLE.COM> create table dept as select * from scott.dept;

Table created.

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

And set up auditing on it.  We are interested in the EMP table
and only when sal > 1500 and when you select the SAL column:

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> begin
  2          dbms_fga.add_policy
  3          ( object_schema   => user,
  4            object_name     => 'EMP',
  5            policy_name     => 'ENAME_SAL',
  6            audit_condition => 'sal > 1500',
  7            audit_column    => 'SAL' );
  8  end;
  9  /

PL/SQL procedure successfully completed.

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

if you do not use CBO, you will get "false positives", it
cannot check your predicate using RBO

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> analyze table emp compute statistics;

Table analyzed.

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

Now, we'll see that if we do not select SAL, no audit

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> select empno, ename from emp;

     EMPNO ENAME
---------- ----------
      7369 SMITH
      7499 ALLEN
      7521 WARD
      7566 JONES
      7654 MARTIN
      7698 BLAKE
      7782 CLARK
      7788 SCOTT
      7839 KING
      7844 TURNER
      7876 ADAMS
      7900 JAMES
      7902 FORD
      7934 MILLER

14 rows selected.

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> exec print_table( 'select * from dba_fga_audit_trail'
)

PL/SQL procedure successfully completed.

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

Now, we'll only statements that should be audited are (and fully at that)

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> variable sal number
demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> execute :sal := 1400

PL/SQL procedure successfully completed.

demo@ORA920.US.ORACLE.COM> select empno, ename from emp where sal < :sal;

     EMPNO ENAME
---------- ----------
      7369 SMITH
      7521 WARD
      7654 MARTIN
      7876 ADAMS
      7900 JAMES
      7934 MILLER

6 rows selected.

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> execute :sal := 1700

PL/SQL procedure successfully completed.

demo@ORA920.US.ORACLE.COM> select empno, ename from emp where sal < :sal;

     EMPNO ENAME
---------- ----------
      7369 SMITH
      7499 ALLEN
      7521 WARD
      7654 MARTIN
      7844 TURNER
      7876 ADAMS
      7900 JAMES
      7934 MILLER

8 rows selected.

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> exec print_table( 'select * from dba_fga_audit_trail'
)
SESSION_ID                    : 476
TIMESTAMP                     : 14-oct-2002 15:13:19
DB_USER                       : DEMO
OS_USER                       : tkyte
USERHOST                      : dhcp-reston-gen-3-west-120-222.us.oracle.com
CLIENT_ID                     :
EXT_NAME                      :
OBJECT_SCHEMA                 : DEMO
OBJECT_NAME                   : EMP
POLICY_NAME                   : ENAME_SAL
SCN                           : 3502768
SQL_TEXT                      : select empno, ename from emp where sal < :sal
SQL_BIND                      :  #1(4):1700
COMMENT$TEXT                  :
-----------------

PL/SQL procedure successfully completed.

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

The FGA works on the base tables, you cannot get around it via
a higher level view

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> create or replace view v
  2  as
  3  select emp.ename, emp.sal salary, dept.dname
  4    from emp, dept
  5   where emp.deptno = dept.deptno
  6  /

View created.

demo@ORA920.US.ORACLE.COM> pause

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

And now select from the view

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> select dname, salary from v;

DNAME              SALARY
-------------- ----------
ACCOUNTING           1300
ACCOUNTING           5000
ACCOUNTING           2450
RESEARCH             3000
RESEARCH             1100
RESEARCH             3000
RESEARCH             2975
RESEARCH              800
SALES                 950
SALES                1500
SALES                2850
SALES                1250
SALES                1250
SALES                1600

14 rows selected.

demo@ORA920.US.ORACLE.COM> exec print_table( 'select * from dba_fga_audit_trail'
)
SESSION_ID                    : 476
TIMESTAMP                     : 14-oct-2002 15:13:21
DB_USER                       : DEMO
OS_USER                       : tkyte
USERHOST                      : dhcp-reston-gen-3-west-120-222.us.oracle.com
CLIENT_ID                     :
EXT_NAME                      :
OBJECT_SCHEMA                 : DEMO
OBJECT_NAME                   : EMP
POLICY_NAME                   : ENAME_SAL
SCN                           : 3502776
SQL_TEXT                      : select dname, salary from v
SQL_BIND                      :
COMMENT$TEXT                  :
-----------------
SESSION_ID                    : 476
TIMESTAMP                     : 14-oct-2002 15:13:19
DB_USER                       : DEMO
OS_USER                       : tkyte
USERHOST                      : dhcp-reston-gen-3-west-120-222.us.oracle.com
CLIENT_ID                     :
EXT_NAME                      :
OBJECT_SCHEMA                 : DEMO
OBJECT_NAME                   : EMP
POLICY_NAME                   : ENAME_SAL
SCN                           : 3502768
SQL_TEXT                      : select empno, ename from emp where sal < :sal
SQL_BIND                      :  #1(4):1700
COMMENT$TEXT                  :
-----------------

PL/SQL procedure successfully completed.

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> set echo off
Review &
      Followup

      Rating: 1
Still obtaining
      false positive  February 12, 2003
Reviewer:
      wneu  from Mason OH
This thread was very enlightening, however, although I have the statistics on my
table (updated recently) and believe I am using the COST based optimizer, I
still have unexpected statements being reported to the DBA_FGA_AUDIT_TRAIL
table.  Perhaps you could assist me in finding my mistake.
These are the options employed on my database:
   optimizer_features_enable            string      9.2.0
   optimizer_mode                       string      CHOOSE
This is the table:
   Name                            Null?    Type
   ------------------------------- -------- -------------
   DX_ACCOUNT_ID                   NOT NULL NUMBER(10)
   LAB_ACCOUNT_NAME                NOT NULL VARCHAR2(100)
   PROVIDER_ID                     NOT NULL NUMBER(10)
   CREATED_BY                               VARCHAR2(30)
   CREATED_DATE                             DATE
This is the policy
BEGIN
   dbms_fga.add_policy
      ( object_schema   => 'DX_OWNER',
        object_name     => 'DX_ACCOUNT_LAB',
        policy_name     => 'GET_DX_ACCOUNT_LAB',
        audit_condition => 'dx_account_id > 0',
        audit_column    => 'LAB_ACCOUNT_NAME' );
END;
/
Here are the select statements I did not expect to appear in the
DBA_FGA_AUDIT_TRAIL table but they did:
   select count(*) from dx_account_lab;
   select dx_account_id, provider_id from dx_account_lab;

and finally, here are the statistics on my table:
   OWNER: DX_OWNER
   TABLE_NAME: DX_ACCOUNT_LAB
   TABLESPACE_NAME: MDDTA_DATA
   NUM_ROWS: 12
   BLOCKS: 1
   EMPTY_BLOCKS: 62
   AVG_SPACE: 7670
   CHAIN_CNT: 0
   AVG_ROW_LEN: 32
   AVG_SPACE_FREELIST_BLOCKS: 7670
   DEGREE: 1
   INSTANCES: 1
   CACHE: N
   SAMPLE_SIZE: 12
   LAST_ANALYZED: 12-FEB-03
   ROW_MOVEMENT: DISABLED
   GLOBAL_STATS: NO
   USER_STATS: NO
   SKIP_CORRUPT: DISABLED
   DEPENDENCIES: DISABLED

Thank you,
-wn

          Followup:

I would need method to reproduce.  Have you verified the timestamps to make sure
they did not come from some earlier iteration of your testing?  I cannot
reproduce this at all -- will need some help there.

should not generate any false positives as the column needed isn't even there.
You aren't using FGAC too are you with a predicate on LAB_ACCOUNT_NAME??

ops$tkyte@ORA920> create table dx_account_lab
  2  ( DX_ACCOUNT_ID                   NUMBER(10),
  3    LAB_ACCOUNT_NAME                VARCHAR2(100),
  4    PROVIDER_ID                     NUMBER(10),
  5    CREATED_BY                      VARCHAR2(30),
  6    CREATED_DATE                    DATE
  7  )
  8  /

Table created.

ops$tkyte@ORA920> insert into dx_account_lab values ( 10, 'x', 10, 'x', sysdate
);

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table dx_account_lab compute statistics;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> BEGIN
  2     dbms_fga.add_policy
  3        ( object_schema   => user,
  4          object_name     => 'DX_ACCOUNT_LAB',
  5          policy_name     => 'GET_DX_ACCOUNT_LAB',
  6          audit_condition => 'dx_account_id > 0',
  7          audit_column    => 'LAB_ACCOUNT_NAME' );
  8  END;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> set autotrace on explain
ops$tkyte@ORA920> select count(*) from dx_account_lab cbo;

  COUNT(*)
----------
         1

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'DX_ACCOUNT_LAB' (Cost=2 Card=1)

ops$tkyte@ORA920> select dx_account_id, provider_id from dx_account_lab cbo;

DX_ACCOUNT_ID PROVIDER_ID
------------- -----------
           10          10

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=4)
   1    0   TABLE ACCESS (FULL) OF 'DX_ACCOUNT_LAB' (Cost=2 Card=1 Bytes=4)

ops$tkyte@ORA920> alter session set optimizer_goal = rule;

Session altered.

ops$tkyte@ORA920> select count(*) from dx_account_lab rbo;

  COUNT(*)
----------
         1

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=RULE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'DX_ACCOUNT_LAB'

ops$tkyte@ORA920> select dx_account_id, provider_id from dx_account_lab rbo;

DX_ACCOUNT_ID PROVIDER_ID
------------- -----------
           10          10

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

ops$tkyte@ORA920> set autotrace off
ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from dba_fga_audit_trail;

no rows selected

      Rating: 5
Previous entry
      review should have been 5  February 12, 2003

Reviewer:  wneu  from Mason, OH

      Rating: 5
Hmmm...
      interesting  February 13, 2003
Reviewer:
      wneu  from Mason, OH
I am using FGAC, but, the predicate is on DX_ACCOUNT_ID, not the
LAB_ACCOUNT_NAME column.
Based on the Role I am using, the predicate checks to see what the Context has
for DX_ACCOUNT_ID and uses it to limit the rows returned where dx_account_id =
syscontext('DxHubApp', 'DxAccount').  The Role for this user is 1. No other
condition is added.

Interestingly, if I disable the FGAC policies, then the right statements are
audited and there are no false positives.  When I re-enable the policies, I am
able to get 'false positive' results again.

Here is the security policy I have on this table (it only has a select
predicate, no others):
FUNCTION on_dx_account_lab (p_schema IN VARCHAR2,
                            p_object IN VARCHAR2 )
RETURN VARCHAR2
IS
BEGIN
   IF (g_account_lab_pred IS NULL) THEN
-- for normal/admin users, they can see their dx_account
    IF (sys_context(g_app_ctx, 'RoleNumber') IN ('1', '2'))
    THEN
      g_account_lab_pred := ' dx_account_id =
               TO_NUMBER (sys_context('''||
                          g_app_ctx||
                          ''', ''DXACCOUNT''))';
-- for super users, they can see all rows
    ELSIF (sys_context (g_app_ctx, 'RoleNumber') = '3')
    THEN
      g_account_lab_pred := ' 1=1';
      -- if no role is set, they can see nothing
    ELSE
      g_account_lab_pred := ' 1=0';
    END IF;
   END IF;
   RETURN g_account_lab_pred;
END on_dx_account_lab;

I also checked the timestamp on the rows to make certain that I was looking at
the right thing.

Thanks again for your time.

          Followup:

I believe the way they rewrite

select x from t

if there is a predicate on T would be

select x from ( select * from t where .... );

so, you are getting this for that reason.  You are in fact "seeing" the column
you are not looking at due to the implementation whenever they have role 1, 2,
or 3..

Yup, just confirmed it -- the fgac is triggering it....

      Rating:
      4
  May 03, 2003
Reviewer:
      A reader
I've found no other way to access dba_fga_audit_trail under DEMO's account then
creating that "print_table" procedure as SYS and grant execute to DEMO. SELECT
ANY TABLE didn't suffice. did I miss something?

          Followup:

you would have to grant directly on it. Most SYS objects are not visible without
direct grants (security thing in 9i and up)

DO NOT, repeat DO NOT create things as sys, just don't even go there.

      Rating:
      4
  May 04, 2003
Reviewer:
      A reader
just to get it right: system privileges such as SELECT ANY don't suffice
(anymore) in 9i and up? explicit object privileges are required?

          Followup:

by default in 9i, correct.  by option in 8i and before

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:940042518290
for more details.

      Rating: 3
How do I
      capture the values in query as well?  June 25, 2003

Reviewer:  Charles Leung  from Hongkong
Hi Tom,

I found that Fine-Grained Audit captures only the SCN. I'm required to log the
queried values also. How would I archive this?

Besides, how can I log other info (such as module name) also? I can think of
dbms_application_info, but I have no idea how to put these things together.

          Followup:

You are kidding ?  You have to audit the actual values?  Just capturing the
query and the inputs to the query -- and knowing that you can reasonably use
flashback in a period of time you configure (a REASONABLE period of time) is not
enough?

I have some disk drives to sell to you!

Sorry, but I cannot fathom the system where every result set would have to be
written out to persistent storage, seems a bit over the wall?  Do you have a
real world business case for this -- do the people driving your requirements
understand what they have said?  the implications?

To get the 'module', the application must set the module.  then you can use
v$session to see the action/module and client info.

      Rating: 5
What is the
      View to show the policy  August 05, 2003
Reviewer:
      A reader
I apologize for picking up a 10-month long thread; but I coudn't find the
information anywhere else. What is the data dictionary view that shows the FGA
policies set on the tables? DBA_POLICIES shows only VPD policies, not FGA ones.

          Followup:

select * from DBA_AUDIT_POLICIES;

      Rating: 4
He may not be
      kidding  November 12, 2003
Reviewer:  David
      Gibbs  from Burnaby, BC Canada
Tom

A couple of posts earlier you replied:

"You are kidding ?  You have to audit the actual values?  Just capturing the
query and the inputs to the query -- and knowing that you can reasonably use
flashback in a period of time you configure (a REASONABLE period of time) is not

enough?

I have some disk drives to sell to you!

Sorry, but I cannot fathom the system where every result set would have to be
written out to persistent storage, seems a bit over the wall?  Do you have a
real world business case for this -- do the people driving your requirements
understand what they have said?  the implications? "

A real world example is in the healthcare field.  HIPAA and PIPEDA (in Canada)
require logging of all activity against Personal Information (PI) in the
database.  This means we require an audit trail of who looked at or updated PI.
How would you recommend doing this (since you are in the field)?  We are using
8.1.7 so would like you suggestions on that version too (I'm anticipating that
the best solution will require us to migrate to a newer version).

          Followup:

You need an audit trail that shows what people looked at personal information.
Using the

a) question
b) inputs to the question

you have that.  (i know about HIPAA, i work in OGEH -- Oracle Government
Education and Healthcare).

Or, you expose all but the persons identifier (anonymize the data) for general
SELECTS and you wrap all "personalized accesses (small result sets, a person at
a time)" in procedures that audit whatever they want (eg: you are NOT using SQL
in the application, you call a procedure, procedure audits whatever it needs).

this is not auditing EVERY bit of data coming out, this is excessively selective
auditing when a specific process to return data about a specific person is
invoked.  very different -- NO ad-hoc, all very controled.  the only ad-hoc is
against the anonymized data.

November 13, 2003 NK -- Thanks for the question regarding "sql
      trace and tkprof", version 8.1.6

      You Asked
hi tom,

i have seen lot of answers from you regarding sql trace and tkprof for sql
performance tuning.but i am still not clear with few things.
i hope you will explain me clearly those things

1.how does this sql trace and tkprof differ from set autotrace on.

2.if we use set autotrace on and explain plan, how do we analyze a
  particular query and tell whether the query is tuned or not

3.for sql trace and tkprof i want to know if

  if i do 'ALTER SESSION SET SQL_TRACE = TRUE'

  where does the trace file get created(my oracle server is running on unix). by
formatting the trace file using tkprof how do i analyze it to tell the query is
tuned or not?

4.is the order of tables in a query matters in performance?

  for this question i got an answer that it matters for RBO but not for CBO.but
who decides to go for RBO,CBO.can we tell oracle to go for CBO OR RBO.If we can
decide, when to go for RBO,when to go for CBO.

sorry for the flow of questions, but i am confused on the above issues.
please answer the above.

thanks a lot.
nk

      and we said...
1) autotrace will show you one of set of information for a SINGLE SQL statment.

tkprof shows you a different set of data for all sql statments executed by your
application as it runs.

autotrace = great way to test a single query in isolation.
tkprof = great way to test your APPLICATION.

2) oh boy.  well, if it runs really fast, its tuned.  if it exceeds your desired
response time its not tuned.  If you have already worked with it for a while and
have exhausted every avenue known to you and it still runs too slowly -- its
tuned by too slow.

I don't know how to answer this, how do you know when ANYTHING is tuned?

remember -- it can always go 1% faster (at least).  forever.  diminishing
marginal returns kick in at some point in the tuning process.  set goals and
stop when you achieve them

3) the trace files will go into the directory specified by the init.ora
parameter user_dump_dest.

see #2 for "how do I know its tuned"

4) If you are developing a new application in Oracle8i -- you should most
definitely be using the CBO.  Analyze your tables and it'll happen (as long as
you haven't set the optimizer goal to rule that is).  Just use it -- the order
of tables won't matter.

Review &
      Followup

      Rating: 4
Tuning SQL
        October 24, 2002
Reviewer:  Chris

Hello again Tom :)
I have this sql cursor :

CURSOR LocalDuration_cur(par1 number DEFAULT 1, par2 number DEFAULT 99, par3
number, par4 number) IS SELECT a.* FROM t_cdr_att a, t_profile_anumber b,
t_profile_yn c WHERE (SUBSTR(a.bnumber,1,1) != '0' AND  a.anumber = b.anumber
AND a.nmfile = c.batchNo AND c.batchID >= par1 AND c.batchID <= par2 AND
a.durasi > (b.dcrphlocal+(par3 * b.dcrphlocal)))  and c.batchNo not in (select
d.batchNo from t_fraudullent_yn d, t_profile_yn e where d.batchNo = e. batchNo
and d.fraudcodeid = par4) order by a.nmfile, a.anumber;

how to tune this SQL statement of that cursor ?
I mean .. should I change it into another way of logic..can u give me
example...? or do i have to put index or what should I do with this statement ?
This is just for example so that I can do similiar thing to lots more SQL
statements I had here :)

thx before :)

          Followup:

there are entire tomes written on tuning sql.

guy harrison has a good book you might be interested in.

http://www.amazon.com/exec/obidos/tg/detail/-/0130123811

      Rating: 3
On Tkprof and
      Query Tuning  November 10, 2002
Reviewer:  A
      Reader  from BG
Tom,
   Here is my understanding. Please do correct me if iam wrong. I havent used
Tkprof and i would like to know if i could find it useful in my case.

(1) If an application has performance issues, using SQL Trace and Tkprof,
identify the poorly performing areas.

(2) But, for tuning Queries in Oracle reports, we already know that a certain
query is taking much more time than it should take. Given such a situation, how
would you go about it. What tool do you use to further diagonise the query,
given that there is no redundant table or join in the query.

(3) In some cases, even for invalid bind variable values (when there are no
records to match) queries take 50 to 60 seconds. Would Tkprof be any useful in
this case because we know that this SQL needs to be tuned. With valid values,
the query runs for minutes.

(4) Some queries run faster when given the RULE hint. Why is that so? Could this
be due to CBO not using Indexes. What could be possibly done for that?

Thanks a lot for your time and patience...

          Followup:

1) correct

2) same as I would in #1 plus my head (my knowledge of how to apply SQL to
questions in order to get answers efficiently)

3) yes it would -- it would show you at what step of the plan the query starts
getting "no data" -- as each step of the plan will be listed there with the
number of rows flowing out of each step.  You could try to figure out how to
have it processed differently so it can stop sooner

4) and some queries run faster given the FIRST_ROWS and ALL_ROWS.  It is because
the piece of software that is the optimizer is a piece of software.

You should look at the optimizer_index_cost_adj parameter -- many times the
default of 100 is too high.

      Rating: 3
I Cant Find
      TKPROF in my 9i Installation?  March 16, 2003

Reviewer:  Anand H S  from New Delhi, India

Hi Tom,
I am not able to find the TKPROF utility in my Oracle 9i installation.
-- Has the name changed?
-- Is there a new way of viewing trace files? Or
-- Does it needs to be installed separately?

Kindly Elaborate.
Regards,
Anand H S

          Followup:

you must have 9.0.0 on windoze.  I guess they figured no one on windows used the
command line and they forgot to include tools that are command line driven (that
is a joke ;)

Either get the current release of the software (9iR2) or open a tar with support
-- you can get it from metalink as a "patch" of sorts.

      Rating: 3
sql_trace
      affecting performance?  April 03, 2003
Reviewer:
      Mark Williams
I had a particularly troublesome query that was running really slow, about 35
seconds.

I did a "alter session set sql_trace=true" to get a tkprof, and suddenly the
query starts running in a secode.  Turn sql_trace off, and it starts taking 35
seconds again.

I won't ask for help in the actual tuning here (I'll leave that till I get an
opportunity to ask a new question), but have you ever come across this
behaviour?  If so, what might be the reason?

          Followup:

what version?

      Rating:
      3
Version  April 03, 2003
Reviewer:  Mark
      Williams
8.1.7

          Followup:

bummer, no v$sql_plan to look at.

any chance of reproducing in 9i?

      Rating: 5
More than one
      of me?  April 03, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Hey!  There's more than one of me...

- Mark

          Followup:

geez -- i assumed it WAS you.  was going to send you an email ;)

      Rating: 5
RE: More than
      one of me?  April 03, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
> geez -- i assumed it WAS you...

Nah, I'd never give you only 3 stars... :)

- Mark

      Rating: 3
Mark J.
      Williams writes  April 04, 2003
Reviewer:  The
      other Mark Williams
Hey, its quite a common name.

No, I don't have access to a 9i DB unfortunately.

Anyway, needless to say, after last nights shutdown, cold backup and startup (I
know, I know, don't ask) the problems gone away.

      Rating: 5
RE: Mark J.
      Williams  April 04, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Hi The Other Mark Williams...

Yeah, I know it is common... just having a bit of fun...  Didn't mean any
offense, so I hope you didn't take it that way!

Cheers,

Mark

      Rating: 3
Course no
      offense  April 04, 2003
Reviewer:  Mark
      Williams
There may be many Mark Williams, but there's only 1 Tom Kyte!

      Rating: 3
TARs, Support
      etc.  September 05, 2003
Reviewer:  Richard
       from Oxfordshire, UK
Spleen vent, I'm afraid:

I bought the 9i (9.0.1) CD pack for W98 in order to teach myself Oracle 9i at
home.

Sadly, this version has a few bugs: most annoyingly (so far) is the absence of
TKPROF, but the effect that PAUSE has on one's SQL Plus session rivals David
Blaine's ability to make things disappear.

Do I really have to pay out for MetaLink support, in order to get a patch, or is
there another way round this?

I could understand *new* functionality failing, but not *oldies* like PAUSE and
TKPROF!

Spleen vent over!

          Followup:

i know the pause issue you are talking about -- i personally was never affected
by it due to the fact that sqlplus in a dos window is somewhat "superior" (and
is not affected by the pause issue).

Yes, in order to access metalink, you need a support contract -- however, I
might suggest that you could just download 9iR2 which is the currently supported
product and more relevant today in any case.

      Rating: 2
Patches should
      be freely available!  September 06, 2003
Reviewer:
      Richard  from Oxfordshire, UK
Thank you for your reply.

I guess I will have to follow your advice and download the latest version. It's
just as well I have access to a large bandwidth connection at work, though, else
I'd be reaching retirement age before the 9iR2 download dribbled through my 56k
home connection!

What a pity that patches for howlers like those I've encountered aren't freely
available.

Regards.

      Rating:
      5
trace  October 03, 2003
Reviewer:
      A reader
Tom,
We are trying to run trace on a few Oracle reports and on an EJB based
application.  The DBA has asked us to
start the trace and then give him the SID and process#, he then would then give
us back the trace files.
My questions
1) As a developer how can I find my session id and process#(no DBA privileges)
2) Is there a SQL that tells me how I am connected to the database(dedicated
server or shared server-MTS)

3) Can wait events be captured with sys.dbms_system.set_sql_trace_in_session?
(wait events as in alter sessionset events '10046' trace name context forever,
level 12
3) Can tracing be set up for an EJB based application where the connection are
being pooled.  If so how?
Also, the connection from the application server to the database is MTS

Thank you

          Followup:

1) you'll NEED access to v$session.  ask the DBA to create a view:

create view YOUR_SESSION
as
select * from v$session where sid = (select sid from v$mystat where rownum=1);

and grant select on that view to you -- that'll give you YOUR row and YOUR row
alone.

2) once you have access to v$session via that view -- you can look at the server
column in it for your session.  it'll either say dedicated or shared

3) you don't want to use shared server with connection pooling.  that is much
like caching a cache ( double buffering)

for sql_trace, you'll need to use dedicated server.

you can do it with a bean easily -- but the bean has to help.  The bean would

a) grab connection
b) alter session set sql_trace=true;
c) do bad stuff
d) alter session set sql_trace=false;
e) return connection

you can use dedicated server easily -- just put server=dedicated in the connect
string.  you must do this or your trace data will be scattered over many shared
server trace files on the server and putting that stuff back together is like
humpty dumpty.

      Rating:
      5
trace  October 03, 2003
Reviewer:
      A reader
Thank you

      Rating:
      5
TKPROF  October 29, 2003
Reviewer:
      A reader
Hi Tom,

In the session tkprof output, the top SQL statement is a procedure inserts new
records into the database.

Could you help me explain why the "cpu" and "disk" are all 0s and elapsed is
very big? Thanks.

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse     7624      0.00      69.17          0          0          0           0
Execute   7624      0.00       5.97          0          0          0        7624
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    15248      0.00      75.14          0          0          0        7624

          Followup:

looks like you tkprofed and incomplete trace file, thats all.

I can say -- the program has a bug.  for it PARSED the same statement over and
over and over and over and over again.  that parse count should be 1!!!

(look you spend more time parsing then executing, hmmmmmmm)

      Rating: 4
TKPROF
      Translation  November 11, 2003
Reviewer:
      Denise  from Virginia, USA
Hi Tom

We recently upgraded to a new version of our third party
fundraising application software.

We've noticed a considerable slowdown with performance
especially when users were posting, merging records,
and updating.

I set sql_trace on and then tkprof-ed the results..
then went through chapter 10 of your book on the TKPROF
translation information. The TKPROF file was a very large
file so I'm pasting the Summary Information in hopes that
you can decipher what is taking place.

Another quirky thing is...as soon as I set sql_trace=
true...everything started whizzing...users said "what did
you do....everything is now taking only seconds to complete instead of 30
minutes"?...when it is turned off everything
slows down again(like someone else here mentioned).

Any ideas?

anyhow here is the summary of the tkprof file:

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse      655      0.84       0.87          0          0          0           0
Execute    917      0.13       0.21          5       1175        206          48
Fetch      907      2.61       3.17         46     589079        833         787
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2479      3.58       4.25         51     590254       1039         835

Misses in library cache during parse: 2
Misses in library cache during execute: 1

  906  user  SQL statements in session.
   68  internal SQL statements in session.
  974  SQL statements in session.
********************************************************************************
Trace file: ora01612.trc
Trace file compatibility: 8.00.04
Sort options: default

       1  session in tracefile.
     906  user  SQL statements in trace file.
      68  internal SQL statements in trace file.
     974  SQL statements in trace file.
     224  unique SQL statements in trace file.
   17006  lines in trace file.

we are using 8.1.7(going to 9i real soon).
add this to my many thousand other thanks.

          Followup:

thats not a really useful extract there -- that is "recursive sql" -- just sql
that was executed on your behalf to run sql.  if you use lots of plsql with sql
-- it would be there -- but if your application just submits sql, that summary
isn't about your sql at all!

are you tracing the entire instance?  or a single application?

what is different when we trace?  we write that file, we create a serious
bottleneck.  If the creation of a bottleneck makes things go fast -- then there
is some other very serious bottleneck that we are bypassing by slowing down over
here.

I would suggest looking at a 10 minute statspack report at this point

      Rating: 4
TKPROF
      Followup  November 12, 2003
Reviewer:
      Denise  from Virginia, USA
thanks Tom....

in this case we set SQL_Trace on for the entire instance..
so we could get a report on what was happening when some
users posted, others merged...etc.

It is at that point when things started moving fast.

It has also been my understanding that tracing is not
good when set to true in a production environment...
and I find it perplexing that in our case..the opposite
is true and now the users want me to leave it on...
meaning set sql_trace=true???

I'll read your chapter on setting up the statspack utility
and see what I can produce from that.

as ever...thanks

          Followup:

i'm going to guess.

setting sql_trace=true introduced an artificial bottleneck that just made things
flow better.  remove bottleneck and things go bad.

Analogy:

premise: adding more CPU will make my system go faster.

test: double CPU capacity, go from 1ghz to 2ghz cpus.

outcome: system runs much much slower

reason:  system was mixed workload, lots of reports and OLTP users.  OLTP does
disk lots, reports did lots of disk and CPU.  reports were constrained as to
their use of the disks by the amount of cpu they got (did lots of formatting in
the report engine, they were starved for cpu).  Adding CPU made the reports able
to process the data faster, causing them to flood the IO system -- to the
obvious detriment of the OLTP users -- whom the CPU was bought for (erroneously)

So, you need to find the true bottleneck.  turn it off, statspack for a bit.  or
just trace a SINGLE application instance -- ONE session!!!

      Rating: 4
STATPACK
      Utility...and more  November 12, 2003
Reviewer:
      Denise  from Virginia, USA
exactly Tom..you hit the nail on the head.

I created the STATPACKS utility...followed instructions
in your book...even created a new tablespace to house
the user perfstat and files.

I have yet to execute the package due to immense conflict
from the software vendor indicating our support contract
will be nulled if we implement any statistics that could
cause serious performance issues.

We are already having serious performance issues that they
are unwilling or unable to deal with causing us(me)to
try and find the solution so we can get it fixed.
I am utterly so frustrated with these people!!!

I showed my boss your comments in the StatsPack WrapUp
of your book on page 494. She agrees but feels she cannot
compromise our support contract.

However one thing I failed to mention. We begin to feel
the bottlenecking in the afternoon hours..mainly after lunch.
It appears the mornings go fairly smooth with posting,merging...etc.

The slowdown then begins in the afternoon hours.

Could this be something to due with Multi-Disk Stacking
issue on NT4...we are running a RAID5 disk system.
I'm just thinking off the top of my head here.....sigh!

Tom..for the zillionth time..thanks for your immense patience, support and
guidance.

          Followup:

you turn on sql_trace and don't think this is "intrusive"???? man, that is like
so intrusive -- especially compared to statspack (which will not affect
performance at all -- not a bit -- except for the second it takes to execute
statspack.snap!!)

      Rating:
      4
Tom....  November 12, 2003
Reviewer:
      Denise  from Virginia, USA
you seem to echo my frustration..
however what good will it do to run it(statspack) if I
can't pass it on to the yo-yo's that will need to evaluate
it..then possibly become infuriated with our 'disobedience'.

My boss said she is going to contact some big-wig and
express her extreme dissatisfaction with tech support..
ho-hum.

you're the best...take care.

          Followup:

we can look at it here?

      Rating:
      4
Tom...  November 13, 2003
Reviewer:
      Denise  from Virginia, USA
I showed my boss your response and she said we will wait
till next week to see what solution the vendor proposes
to address the performance issue...if it remains an
ongoing issue she will authorize my running the statspack
utility and post the results here.

on a lighter note:
I now have my boss checking out your website for Oracle
solutions with issues...everytime something comes up I
tell her.."well Tom says this...and Tom says that"...and
"Read this part in Tom's book where he explains...etc".

Let me know if you have an "ASKTom" poster....I would
love one hanging in my office with a caption saying:
"Oracle powers the World....
              Tom powers the force behind Oracle".

;~D

ps: when I get those stats I'll post them.

November 13, 2003 Prince -- Thanks for the question regarding "IN
      LIST as TABLE", version 8.1.7

      You Asked
Hi Tom,

Is there any way I can treat the "IN LIST" as a table.

For example,

I want to list all the values from the "IN LIST" which is not in my table.

Say, I have the table T1 as

T1.Col1:
---------

12
14
16
18
25

IN LIST (15, 16,18,19).

My result should show :

Col1:
------
15 -- not in T1.COL1
19 -- not in T1.COL1

I couldn't figure out a way to do this just using SQL. Also I couldn't figure a
way to do this in PL/SQL without defining "TYPE".

I am sure this can be done using object types (Nested table and such). Could you
give me an example to do this? I prefer not to have any types defined (created)
in the DB, for this example.

Note: I don't want to create a temporary table and load the "IN LIST".

Thanks,

      and we said...
Sure, you can do this.  It would look like this:

ops$tkyte@ORA817.US.ORACLE.COM> variable inlist varchar2(25)
ops$tkyte@ORA817.US.ORACLE.COM> exec :inlist := '15,  16,18,19'
PL/SQL procedure successfully completed.

ops$tkyte@ORA817.US.ORACLE.COM> create table t ( x int );
Table created.

ops$tkyte@ORA817.US.ORACLE.COM> insert into t values ( 12 );
1 row created.
ops$tkyte@ORA817.US.ORACLE.COM> insert into t values ( 14 );
1 row created.
ops$tkyte@ORA817.US.ORACLE.COM> insert into t values ( 16 );
1 row created.
ops$tkyte@ORA817.US.ORACLE.COM> insert into t values ( 18 );
1 row created.
ops$tkyte@ORA817.US.ORACLE.COM> insert into t values ( 25 );
1 row created.

ok, that setup the example as you describe above, now -- first we need to get
a "virtual" table that has as many tuples in it as we have inlist elements:

ops$tkyte@ORA817.US.ORACLE.COM> select rownum
  2    from all_objects
  3   where rownum <= (length( :inlist ) - length(replace(:inlist,',','')) + 1)
  4  /

    ROWNUM
----------
         1
         2
         3
         4

that works by effectively counting the commas in the string.  Now, we can
start parsing the string, i'll do this bit by bit so you can see how it works.
We begin just by finding the i'th element in the string:

ops$tkyte@ORA817.US.ORACLE.COM> select rownum, substr( :inlist,
instr(','||:inlist||',',',',1,rownum) )
  2    from all_objects
  3   where rownum <= (length( :inlist ) - length(replace(:inlist,',','')) + 1)
  4  /

    ROWNUM SUBSTR(:INLIST,INSTR(','||:INLIS
---------- --------------------------------
         1 15,  16,18,19
         2   16,18,19
         3 18,19
         4 19

Now we are reading to get just the i'th element.  We'll use instr again to not
only find the BEGINNING but the END of the element to get the lenth -- substr
that out, get rid of the comma, and trim it:

ops$tkyte@ORA817.US.ORACLE.COM> select v
  2    from ( select rownum,
  3                  trim( both from
  4           replace( substr( :inlist, instr(','||:inlist||',',',',1,rownum),
  5                                instr(','||:inlist||',',',',1,rownum+1)
  6                               -instr(','||:inlist||',',',',1,rownum)
  7                           ),
  8                     ',', ''
  9                   )
 10                ) v
 11             from all_objects
 12            where rownum <= (length( :inlist ) -
length(replace(:inlist,',','')) + 1)
 13         )
 14   where not exists ( select null from t where x = to_number(v) )
 15  /

V
--------------------------------
15
19

And there you go.
Review & Followup

      Rating:
      5
Excellent..  August 16, 2001
Reviewer:
      Prince
Thank you Tom,

This is excellent. Not only the answer, but also the explanation is wonderful.
The step by step explanation is fantastic.

      Rating: 1
Not the
      solution for what was asked  November 10, 2003

Reviewer:  Deepak Sunchu  from Burlington, NJ

Hi Tom,

I dont think you provided what was asked for.
The original post asked for not going with a temp table or
TYPE, or object tables.

-------------------original Post -------------------------
Also I couldn't figure a
   way to do this in PL/SQL without defining "TYPE".

   I am sure this can be done using object types (Nested table and such). Could
you
   give me an example to do this? I prefer not to have any types defined
(created)
   in the DB, for this example.

   Note: I don't want to create a temporary table and load the "IN LIST".

------------- End -----------------------------------------

Regards
Deepak

          Followup:

read:

I am sure this can be done using object types (Nested table and such). Could
you
give me an example to do this? I prefer not to have any types defined
(created)
in the DB, for this example.

which part of that didn't I do, i heard the "i prefer" but hey, if you cannot do
it without types -- you cannot.

I gave them the example they asked for.   If you feel it is "wrong" or
"incomplete" -- feel free to "make it better".

(and hey, guess what, look up exactly one review, the one made by the guy who
actually ASKED THE QUESTION.  they seems fairly "happy" no?)

      Rating: 4
How to handle
      the dynamic table fields in this way?  November 11, 2003

Reviewer:  yaya  from china
I have designed three table to handle the dynamic table fields in this way:
tablename:attribute

AID    NAME    TYPE    TABLETYPE
-------------------------------------------
1    ADDCOL1    CHAR    TABLE1
1    ADDCOL2    CHAR    TABLE1
2    ADDCOL1    CHAR    TABLE2

tablename:attributevalue

FILEID    AID    NAME    TABLETYPE    VALUE
---------------------------------------------
100    1    ADDCOL1    TABLE1        AA
100    1    ADDCOL2    TABLE1        BB
2000    2    ADDCOL1    TABLE2        AA

tablename:table1

FILEID    col1    col2
------------------------
100    11    aa
200    22    bb

tablename:table2

FILEID    col1    col2
------------------------
1000    111    ww
2000    222    dd
when user add attribute from jsp page,how to use pl/sql to create a view or
procedure to list table1(table2) and their dynamic attributes and value as
follows:
tablename:table1

FILEID    col1    col2    ADDCOL1    ADDCOL2
----------------------------------------
100    11    aa    AA    BB
200    22    bb

tablename:table2

FILEID    col1    col2    ADDCOL1
-------------------------------------------------
1000    111    ww
2000    222    dd    AA

          Followup:

ugh, i wrote about this in my new book.

i wrote about how much i don't like it at all.  I call it the "funky data
model", "we didn't feel like figuring out what we needed to do, so we'll do it
really generic"

you are heading down a bad path, reconsider your approach!

      Rating: 4
thanks for the
      quick response!  November 12, 2003
Reviewer:
      yaya  from china
do in this way in that the add column is not important and will be added(or
deleted) unknown fields from jsp by systemuser,so we called dynamic fields for
table1,or table2..,and another way is we design 5-10 redundant fields now for
the unknown columns future,is it wise?
in fact,we would like void handling DDL in application,I also want to solve by
collection object:such as array,nest table or my own type.
so this case,what is the best way?

          Followup:

best way?  design the necessary columns into the application itself.

my preferred way - have a couple of varchar2(4000) columns "at the end of the
table".  call them STUFF_1, STUFF_2, .....   Use them for this purpose.

Cameron O'Rourke (devtrends.oracle.com) has described yet another way in the
Nov/Dec 2003 issue of Oracle Magazine (www.oracle.com/oramag) using xml

November 13, 2003 -- Thanks for the question regarding
      "optimizer_index_cost_adj", version 9.1.2

      You Asked
Tom,
On searching for 'optimizer_index_cost_adj' on your site there are a couple of
places where you recommend this parameter to be changed if required.

But the metaLink document 'Init.ora Parameter "OPTIMIZER_INDEX_COST_ADJ"
Reference Note', 'Doc ID: 62285.1'
has the follwoing to say
<quote>
Modifying this parameter could
  significantly degrade the performance of some queries if the perceived
  index cost is reduced to an unrealistic level and indexes are chosen when
  other access methods are more appropriate.
</quote>

I am running a query which is doing a full scan and takes about 25 seconds for
the results(about 600 records) to come up.
When I set optimizer_index_cost_adj to 50, the results come back in less than 1
second and the optimizer uses 'BY INDEX ROWID'.

My question is if I should change this parameter at all (taking into
consideration the metalink document)

Thank you

      and we said...
they are stating the obvious

the obvious being "there are no silver bullets.  nothing is entirely good, nor
is anything entirely bad"

indexes are good right?
not always -- sometimes they should be ignored.

remember -- i said "if required".  i did not say "this parameter should be
changed".

that same statement could be said about ANY parameter, ANY one of them.

I can say, if you would like a much more in depth discussion of it and other
optimizer related parameters -- i spent an entire chapter on it in my new book
"Effective Oracle By Design"
Review &
      Followup

      Rating:
      5
optimizer_index_cost_adj  September 14, 2003

Reviewer:  A reader
Tom,
I have not seen Metalink make any specific warnings about any other parameter in
particular like they did for "OPTIMIZER_INDEX_COST_ADJ".
Is there a particular range at which the value for "OPTIMIZER_INDEX_COST_ADJ"
becomes unrealistic for an OLTP application/database?
Under what conditions should the default value of "OPTIMIZER_INDEX_COST_ADJ" be
changed from 100 ?
For the query I am looking at altering this value at the session level to 50
showed significant performance improvement, but the documentation on Metalink is
making me think twice.  That's the reason I wanted your expert advice.

Thank you

          Followup:

if you get a copy of my new book -- i go into this in much more detail.

however, this is just a parameter, just a setting.  there are no unreasonable
values -- just the values that work best for you.  50 is a good one, 25 is
stronger, 100 the default.  somewhere between 0 and 100 is the number for you.

the note only states the obvious "a wrong setting for parameter X can be bad"

      Rating: 4
"Proper" value
      of OPTIMIZER_INDEX_COST_ADJ....  September 14, 2003

Reviewer:  Mark J. Bobak  from Ann Arbor, MI

I have read elsewhere (original source was a Tim Gorman paper,
I believe), that the purpose of the OPTIMIZER_INDEX_COST_ADJ
is to allow the CBO to understand the relative cost of
a db file sequential read vs. a db file scattered read.

As such, the recommendation I've read is to set your
OPTIMIZER_INDEX_COST_ADJ =
select round((select average_wait
                from v$system_event
               where event='db file sequential read')
            /
             (select average_wait
                from v$system_event
               where event='db file scattered read')
            * 100)
  from dual;

Tom,

I'm not sure I've ever heard of Oracle expressing this,
though.  Have you seen/heard this before?  Do you agree?

Thanks,

-Mark

          Followup:

that is just using the ratio of waits for indexed reads/full scan reads.  it is
a valid "start point".  but it'll have to be iteratively set as the very act of
setting it will change the ratio as you'll start doing more index reads (most
likely)

      Rating: 4
Seldom
      results  November 12, 2003
Reviewer:
      Michael  from Germany
Hello Tom!

I try to find out "good" values for the optimizer_index_cost_adj and
optimizer_index_caching parameters.

For the cost_adj parameter i used the following query to find out a starting
point (as you recommended):

SQL> l
  1  select round((select average_wait
  2                  from v$system_event
  3                 where event='db file sequential read')
  4              /
  5               (select average_wait
  6                  from v$system_event
  7                 where event='db file scattered read')
  8              * 100) as starting_point
  9*   from dual

When i execute this statement on database 1 i get:

SQL> /

STARTING_POINT
--------------
          7558

I was a little bit stunned by this value. As far as i know, this would mean that
an index access is much more "expensive" than an full table access.

Here are all the columns for the two events form the v$system_event table:

SQL> select * from v$system_event
  2  where event in ('db file sequential read', 'db file scattered read');

EVENT                      TOTAL_WAITS TOTAL_TIMEOUTS TIME_WAITED AVERAGE_WAIT
-------------------------- ----------- -------------- ----------- ------------
db file sequential read       92814138              0  3787666073   40,8091499
db file scattered read         4349122              0     2348427   ,539977264

Then i inspected database 2. Here, i found the other extreme:

SQL> select round((select average_wait
  2                  from v$system_event
  3                 where event='db file sequential read')
  4              /
  5               (select average_wait
  6                  from v$system_event
  7                 where event='db file scattered read')
  8              * 100) as starting_point
  9    from dual;

STARTING_POINT
--------------
             0

0!!! Then i looked into the v$system_event table (i thought, perhaps
timed_statistics is disabled), but:

SQL> select * from v$system_event
  2  where event in ('db file sequential read', 'db file scattered read');

EVENT                        TOTAL_WAITS TOTAL_TIMEOUTS TIME_WAITED AVERAGE_WAIT
---------------------------- ----------- -------------- ----------- ------------
db file sequential read        119504401              0     7598589   ,063584177
db file scattered read         368384847              0  1,1029E+10   29,9391977

So the ratio is correct (0.063584177 / 29.9391977 = .0021).

Are these values from the two databases realistic? How are these extreme ratios
possible? (I suppose i did something wrong.)
And: What should i do now with the optimizer_index_cost_adj parameter on these
databases?

          Followup:

well, don't put words in my mouth -- they recommended it, i'm not endorsing it.

looks like that database has been up a while (hope so!) -- aggregate numbers
over a long period are "meaningless".

try cost_adj = 10, or 25, or 90, or 100.

but wait -- first -- have you IDENTIFIED A PERFORMANCE PROBLEM that you are
actually trying to solve and have you (most important -->) identified the root
cause!

don't change anything until you do.

      Rating: 4
Reset of
      v$system_event possible?  November 13, 2003

Reviewer:  Michael  from Germany
Yes, database 2 is up since mid of august and database 1 is up since a week.
Is there a way to "reset" the v$system_event table (without a restart of the
database) to get the data of, for example, a single hour?

          Followup:

you would

create table x as select * from v$system_event;

wait a bit.......

create table y as select * from v$system_event;

now, join X to Y and diff the values -- that'll be the IO's and such during that
"wait a bit" period.

      Rating:
      5
Thanks!  November 13, 2003
Reviewer:
      Michael  from Germany

November 13, 2003 Sridevi -- Thanks for the question regarding
      "FGAC - Parent and Child Tables", version 9i - Release 2

      You Asked
We are thinking of implementing FGAC in a new project going to be developed on
9iDB and 9iAS Release 2. Before we even start, I have a basic question on FGAC:

Lets say I have a parent table EMP with a column "deptno". Lets say that I have
implemented FGAC on this table such that data is filtered out depending on the
deptno column.

Now I have another table which is the child table of the EMP Table ->
EMP_PERFORMANCE - To store details of the quarterly performance appraisal of
each employee.  Please note that this table does NOT contain the column "deptno"
since it is a normalised table. This table contains the following columns:
empno n(6)  - referencing the "EMP" table primary key.
quarter n(1) - will be 1,2,3,4 depending on which quarter the appraisal is for.
year n(2) - year of appraisal.
appraisal_marks_1 n(5,2)
appraisal_marks_2 n(5,2)
appraisal_marks_3 n(5,2)
remarks v(200);

In the above table, empno, quarter and year comprise the compound primary key.

My Question: Will the FGAC implemented on the parent table EMP apply to the
child tables also automatically?

Or should Acess Control be defined for the child tables also again?  If this is
the case, how can we implement the access contol on EMP_PERFORMANCE table
depending on the "deptno" column which is present only in the parent EMP table?

      and we said...
No, it will not be on the "child table" automagically.

One way you can get it there "automagically" would be:

create view emp_performance_v
as
select emp_performance.*
  from emp_performace, emp
 where emp_performance.empno = emp.empno
/

grant on emp_performance_v INSTEAD of emp_performance.

Now, you can insert/update/delete that view (it is key preserved) and it'll only
show you rows that have a match in EMP and since EMP is protected -- there you
go, you only see what you should see.

Alternatively, you can put FGAC on the emp_performance table using

empno in ( select empno from emp where <predicate on deptno> )

or

exists ( select null from emp where emp.empno = emp_performance.empno and
<predicate on deptno> )

The choice is yours - I would suggest trying them all and see which gives the
best results in your environment with your queries.

Review &
      Followup

      Rating: 5
How to avoid
      ORA-28108 (recursive loop)?  November 12, 2003

Reviewer:  Andy  from Germany
When I apply FGAC to my parent and child tables, I get an ORA-28108, as the
policy applied to the parent table references the child table, which in turn is
protected by a policy that refers to the parent table. Take the case where a
parent table holds rows that are marked confidential (child table contains list
of those that can read parent row), or unconfidential (open to all):

create table p (id number, isconfidential varchar2(1));
create table c (id number, tid number, releasedto varchar2(100))

alter table p add constraint pk primary key(id);
alter table c add constraint c_fk foreign key(tid) references p(id);

insert into p values(1,'Y');
insert into p values(2,'Y');
insert into p values(3,'N');

insert into c values(1,1,'joe');
insert into c values(2,1,'steve');
insert into c values(3,3,'pete');
insert into c values(4,3,'bob');

So if I'm Joe, I should be able to see rows 1 (p row 1 is confidential but
released for me) 3 and 4 (p row 3 is non-confidential) in table c.

I create my two policies:

create or replace function p_policy_function
(
p_schema in varchar2,
p_object in varchar2
)
return varchar2
as
begin
return 'upper(isconfidential) = ''N'' or exists (select id from c where
upper(c.releasedto) = upper(USER))';
end;

create or replace function c_policy_function
(
p_schema in varchar2,
p_object in varchar2
)
return varchar2
as
begin
return 'tid in (select id from p where upper(isconfidential) = ''N'' or exists
(select id from c where upper(c.releasedto) = upper(USER)))';
end;

And apply them using dbms_rls:

begin
dbms_rls.add_policy
(object_schema => 'MIRES',
object_name => 'p',
policy_name => 'p_policy',
function_schema => 'MIRES',
policy_function => 'p_policy_function',
statement_types => 'select, insert, update, delete',
update_check => TRUE,
enable => TRUE
);
end;

begin
dbms_rls.add_policy
(object_schema => 'MIRES',
object_name => 'c',
policy_name => 'c_policy',
function_schema => 'MIRES',
policy_function => 'c_policy_function',
statement_types => 'select, insert, update, delete',
update_check => TRUE,
enable => TRUE
);
end;

But a simple select against the parent table gives me an ORA-28113, which in the
dump file shows:

Error information for ORA-28113:
Logon user     : MIRES
Table/View     : MIRES.C
Policy name    : C_POLICY
Policy function: MIRES.C_POLICY_FUNCTION
RLS predicate  :
tid in (select id from p where upper(isconfidential) = 'N'
    or exists (select id from c where upper(c.releasedto) = upper(USER)))
ORA-28108: Zyklische Sicherheits-Policies festgestellt
Parent policy predicate:
Table/View     : MIRES.P
Policy name    : P_POLICY
Policy function: MIRES.P_POLICY_FUNCTION
RLS predicate  :
upper(isconfidential) = 'N' or exists (select id from c where
upper(c.releasedto) = upper(USER))

If I select from a view that combines c with non-confidential rows from p, won't
it still result in a SELECT against table p, setting the cycle off again? I'd
really appreciate any comments you could offer (apologies if I'm missing
something really obvious).

          Followup:

http://asktom.oracle.com/~tkyte/article2/index.html
ctl-f for chicken

      Rating: 5
Errors in last
      review  November 12, 2003
Reviewer:
      A reader
Sorry Tom - I noticed an error in my last post. The policies should have a link
to the foreign key linking the tables as well, i.e.:

create or replace function p_policy_function  (
p_schema in varchar2,
p_object in varchar2
)
return varchar2
as
begin
return 'upper(isconfidential) = ''N'' or exists (select id from c where
upper(c.releasedto) = upper(USER) and c.tid = p.id)';
end;

and:-

create or replace function c_policy_function  (
p_schema in varchar2,
p_object in varchar2
)
return varchar2
as
begin
return 'tid in (select id from p where upper(isconfidential) = ''N'' or exists
(select id from c where upper(c.releasedto) = upper(USER) and c.tid = p.id))';
end;

I'd left out the "c.tid = p.id" bit...

      Rating: 3
Not sure it's
      what I'm looking for  November 13, 2003
Reviewer:
      Andy  from Germany
I've got your book, Tom, and have read the chapter on FGAC. I've re-read and
re-read the section where you discuss the "chicken and egg" conflict, but I
honestly can't see how it helps solve the problem I outlined above. The
application context example you give deals with predicates applied to one object
(EMP), but how does one apply predicates to a pair of parent/child tables, where
each predicate must reference the other table? I'd really appreciate your
comments on this.

          Followup:

you would expose VIEWS that have FGAC on them.

the VIEWS FGAC policies would use the tables.

the tables "do not exist" except for the FGAC policies (no one uses them, no one
queries them, no one knows about them)

      Rating:
      5
  November 14, 2003
Reviewer:  Andy
       from Germany
Ah, I understand now. I hadn't appreciated how the FGAC policy can "bypass" the
restrictions placed on a table underlying a view. I've tried what you said and
it works just fine. Thank you for your help, Tom.

November 13, 2003 maher -- Thanks for the question regarding "three
      questions", version oracle 8i,developer 6

      You Asked
hi tom,
(1)i make a view that depend on 3 tables,
and i make a block on form which base on this view,
when i just write on one of feilds in that block i get this
message :could not reserve record(2 tries) try again?
i try to use ON-INSERT,ON-UPDATE,ON-DELETE but noting to happen,and
i make key-mode in the block updatable.
how can i make update,insert,delete on block based on view
my oracle version is Oracle8i Release 8.1.5.0.0 - Production.
(2) can i use decode with where clause like this:
select ........ from ...... where decode(x,1,no>100,no<100).
(3) can i make index in substr of column :
create index <name> on table_name(substr(student_no,1,1))
thanks in advance

      and we said...
1) you need to code the "on lock" trigger -- that is the one that is failing.
Before you can update the row on the screen forms issues:

  select * from t where c1 = :block.c1 and c2 = :block.c2 and ....
     and rowid = :block.rowid FOR UPDATE NOWAIT;

if this query returns ora-54, the row is locked by another session, you must
wait.

if this query returns 0 records, the row was changed by another session, you
must requery it.

if this query returns 1 record, it is locked and you own the lock and you can
update it.

In your case, the view doesn't have a rowid, and this fails.
You need to provide equivalent logic in the ON LOCK trigger -- you would for
exmaple perform the 3 queries it will take to verify the rows were not changed
in the 3 tables that comprise the view and lock them.

2) no, but in 816 you can use CASE

  1  select * from emp
  2* where (case when sal > 100 and sal < 100 then 'Yes' else 'No' end ) = 'No'
scott@ORA717DEV.US.ORACLE.COM> /

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
----------
      7369 A          CLERK           7902 17-DEC-80        800
  20
....

You can use SIGN as well -- but I cannot tell you how to use sign in this case
since your decode appears to be trying to return a boolean which doesn't make
sense (i don't know exactly what you are trying to do in the decode)

3) see

http://osi.oracle.com/~tkyte/article1/index.html
...
Review
      & Followup

      Rating: 5
maher
      shaqalaih  October 27, 2001
Reviewer:  maher
       from palestine
thanks for your fast response.
i write on-lock trigger null; and the update go ahead.
how this effect in DB processing.

          Followup:

You are losing data integrity.

You will have "lost updates" whereby user 1 blindly overwrites user 2's updates.

You must do what I described,

  select * from base_table1 where .... for update nowait;
  select * from base_table2 where .... for update nowait;
  select * from base_table3 where .... for update nowait;

you must lock the underlying rows, you must make sure they are the same as the
data you have on your screen.  If you do not do this (what forms does naturally)
you will be unpleasantly surprised when:

- your users complain about updates the "go away"
- complain the that form "locks up" when they hit save (you will wait for a lock
with an update.  the for update nowait gets the lock without waiting, without
blocking)

      Rating: 3
Master table
      records is not updated  November 13, 2003

Reviewer:  Godwin  from Ghana
Hi Tom,
I have 3 tables and based one block on each in forms6i.
Two of the base blocks have master-detail relationship.
If i perform query and do changes to records in the master and the detail blocks
only record in the detail block is updated, the master record; however, is not
updated.
At first I was getting that error " Couldn't reserve record for update! (...
tries)" until i have coded the ON-LOCK trigger in the master block as you did
recommend in the above discussion and the error stopped. But now the master
record can not be updated.
What is your suggestion.
Here is the code behind the ON-LOCK trigger:

declare
  x table_A%rowtype;
begin
   select * into x from table_A where table_A.id=table_B.id
   for update nowait;
end;
Thanks.

          Followup:

i didn't recommend coding an on lock trigger for getting around "cannot lock
records"!!!

you do not want an onlock trigger here, you need to find out who's got your
master record locked!!!!

(i doubt that is the entire trigger there -- you probably have an exceptions
when others then null -- since the record is locked, the nowait will cause that
query to fail immediately)

November 13, 2003 Mohammed -- Thanks for the question regarding
      "Migration", version 7.3.4

      You Asked
Tom,

What method oracle suggest to migrate the 7.3.4 to 8.0.5+.

1. Full Export/Import
           If we use this option,
           what will be the chronological order import will
           create the objects while import on 8.0.5+?

2. User by User Export/Import
           If we use this option,
           What will be the order users for importing on 8.0.5+

In either cases, what will all_objects 'view' contains?
Will it contain same number of records as on 7.3.4?
What will be differences in all_objects view?

What are the steps we need to follow in order for a successful
migration?

I will be thankful if i get an early advice.

regards,
Ahmed,

      and we said...
This document:

http://technet.oracle.com/doc/server.804/a58243/toc.htm
outlines your options.  I would opt for a migration -- not a
export/import if I could.  A migration is much faster.  I would
only use an export import if i really wanted to rebuild the
entire database.

All_objects, by user -- excluding SYS, SYSTEM and other internal
type account, should have the same count after any migration.
SYS and SYSTEM and others will have different counts (more
objects in the data dictionary with each release).

The above referenced document has all of the steps needed.

If you choose the export/import -- here are some of the
considerations:

o a user by user export/import will not export things like the
"create user" and system grants (eg: grant DBA, grant CREATE
TABLE, etc) for each user.  You will have to migrate the "users"
your self. (not their data -- their identity).

o a user by user export allows for parallel operations.  I can
export many at the same time, import many at the same time.  It
can be much much faster -- AND in the event of an error during
either imp or exp -- the amount of "lost work" is much smaller.
I would rather have trouble importing a single user rather then
have trouble importing the entire database.

I myself go for the user by user export even though you have to
be careful to recreate the user on the new database yourself.
It is easy enough with some queries on DBA_USERS to generate the
create user statements and DBA_ROLES and such to get the grants
for each user.
Review & Followup

      Rating: 4
Excellant
      approch  October 02, 2002
Reviewer:  Nathan
       from India
Hi Mr.Tom,
Oracle 8 DB in NO Archivelog mode
I want to transfer data(with one day transaction difference) using export
/import from prod to test machine in daily basis. I want to maintain same
tablespace definitions(for all users) in test machine ,i have 50 more users.( I
don't want to use DB cloning),because its taking more time to copy over to test
machine.
Guide me with steps...
Thanks in advance

          Followup:

well, it doesn't really matter here.  You don't care about the data in the
database since you are running in noarchivelog mode.

but anyway, just export and import.  although

o enabling archivelog mode
o doing a hot backup
o restoring it on the test machine

will be about a zillion times faster and will show that you actually *care*
about your data (it is important to you).

In noarchivelog mode on a transactional production system, you might as well be
using grep, sed and awk for a database.

      Rating: 4
I didn't get
      you  October 02, 2002
Reviewer:  Sampath

Hi Tom!
       I know you recomend archivelog mode and proved that is the way to run an
Oracle database by your arguments hands down.

 How ever I didn't get the following part of your update  .
Could you please explain it ..

"In noarchivelog mode on a transactional production system, you might as well be
using grep, sed and awk for a database. "

Thanks,
Sampath

          Followup:

my point was you have the same degree of protection with simple flat files as
you do with a noarchivelog mode database.  You have blown off one of the major
big reasons to use a database (recovery).   So, why bother...  Just use flat
files, you'll be just as protected.  (i was being sarcastic)

      Rating: 3
Oracle8i to
      Oracle9i migration qn  November 18, 2002
Reviewer:
      RB  from New Jersey, USA
I have an oracle8i DB ( 8.1.7 ) and would like to migrate to oracle9i Release 2.
 Please advise the steps or point me to a doc where I can find the steps to do.

          Followup:

The oracle9i migration guide

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-MIG
covers all of that

      Rating: 5
From 7.3 to
      9i  November 19, 2002
Reviewer:  Irfan
       from Kuwait
Hello Tom,
What way you will suggest if migrating from 7.3 to 9i directly? Thanks.

          Followup:

to read this document:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-MIG
from cover to cover, understand your options and pick the one that is best for
you in your environment.

      Rating: 3
Upgrade the
      database  November 19, 2002
Reviewer:  Tony
       from India
I want to upgrade the database from 8.0 to 8.1.7.
I don't want to install 8.1.7 and then do exp/imp of data.
Is there any script to do the same?. If yes, Where can I find that script?
Running that script alone should do the upgrade.

          Followup:

geez -- EVERY release of Oracle has this document called the "migration guide"

for 817 it is:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a86632/toc.htm
(this page is becoming the index to all of the migration guides isn't it -- you
can just goto otn -> documentation -> your database version -> migration guide!)

read that, you'll understand your options which include "upgrade my database in
place please"

      Rating:
      3
opinion  September 30, 2003
Reviewer:
      A reader
Hi Tom,
I was reading about new feature in 10g about migration, then I remember and
think it could be interesting to share my experience in migrations.
After trying the automatic migration feature included in Oracle and have
problems in the migration( 7.3 to 7.3.4, 7.3.4 to 8i, 7.3.4 to 9i, 8i to 9i),
performance lose after migrating, errors in the migratoin process, etc.
Then we see that a manual migration was better I will dare to suggest, maybe it
could interest to some one, you how Oracle migration could be improved (for
small database at least)
1. Open Old database
2. get strcture from tablespaces and datafiles
3. export full database
3a optionally export table,count(*), to verify there is the same amount of
tables and rown, and objects, than before.
4. detect information about features installed
5. drop old software install new software
6. rename datafiles from xx to xx_old
7. copy basic database (already created)
8. run additional features.
9. recreate structure.
10. import previously exported database into new database.
10a optionally verify there is the same amount of tables and rown, and objects,
than before.

I had seen several time after several problems that export and import is the
best way to migrate, even when I understand that for a big database could be a
titanic work.

:)

          Followup:

hate to be a doubting Thomas -- but I will be.

I've upgraded many many (lots) of databases.  I use exp/imp ONLY when forced to.
 I do inplace upgrades whenever possible.

I have not had the same experience as you.

      Rating:
      5
  September 30, 2003
Reviewer:
      A reader
Thanks Tom,
Maybe there was another element in the middle (until I didn't get my ocp, and
asktom I didn't understood the need of the statistics as I do now).
So the problem was the migration plus (other thing), but the full export and
import solved that. And I got and addition control of what is the migration
doing.

Maybe I'm damned because every time I try to apply a patch, from the 7.3 to the
7.3.4, some strange thing happens, one example is the problem in the function
tables and materialized views I asked about I recently have after applying a
small patch (to avoid a problem that locked tables and procedures, so you need
to disconnect everything before updating, otherwise it hanged), I'm planning to
reinstall my database.  :)

      Rating: 3
migration from
      solaris (9.2.0.3) to Linux (9.2.0.4)  November 12, 2003

Reviewer:  Baqir Hussain  from Emeryville, CA USA

Tom,
Oracle 9.2.0.3, production database in OLTP, is running on E4500 with Netapp.
We plan to move to ORACLE9i RAC on Linux with different storage attached to it.
The current size of the database is ~ 50G in which 3/4 of the data is text
messages in two tables.
exp/imp takes more than 2 days to complete migration (can't afford).
I would like to know different methods in which the migration can be achieved
without down time and/or minimum down time.
Please advise.
Thanks

          Followup:

well, exp/imp should not take 2 days, not for 50gig, not unless you wrote it by
hand that is (byte by byte).

you can CREATE TABLE AS SELECT over a dblink. that is one option.

you can write a custom program to dump and load.

you can export in parallel running many exp processes -- each with a query to
get a different slice of the table, imp in parallel.

you can install oracle on linux and create empty tables, then create
materialized views on prebuilt tables (use read only replication).  then when
you want to switch over -- just a) kill user connections to the solaris box, b)
refresh mv's, c) drop the mv's (since they are on prebuilt tables -- the tables
will stay, with all of their data)

      Rating: 5
Migration from
      EE to SE (Oracle 9,2)  November 13, 2003
Reviewer:
      Olaf
Tom,

I want to migrate our prod db from EE to Standard Edition.
What is the best approach to do this.
Will full export/import overwrite the sys and system objects in the target
database? I saw one database two years ago which become corrupt after full
export/import.
I make it always on the schemas (fromuser touser for each schema) basic, but
this database have too much schemas to do so.

Can you give me a good advice?

          Followup:

it'll be a full export import if you want to rebuild -- just create a "basic,
small database" and do the full import.

you can contact support and see what is involved in downgrading to SE from EE.

I've not seen a database "become corrupt" due to a full import (and even if it
did, well, it is a "seed" database, it didn't have anything in it, so just throw
it away)

November 13, 2003 Vivek -- Thanks for the question regarding
      "Update statement", version 7.3

      You Asked
Hi Tom
1. I have an Update statement which is taking too long
   It has a co-related subquery. How can I optimize it ?
2. This SQL also has a WHERE EXISTS clause, if I remove
   this will the columns that fail the join be set to null ?

thanks
Vivek

      and we said...
It sounds like you are trying to update a table T1 with data from T2 and you are
using a query like:

update T1
   set c1 = ( select c2 from T2 where T2.key = T1.key )
 where exists ( select c2 from T2 where T2.key = T1.key )
/

correct?

If my assumption is correct, then the answer to #2 above is "Yes, if you remove
the where exists -- the rows in T1 that have no mate in T2 will be set to NULL".

The way to optimize this, if T2 is relatively small and T1 is large will be to
update the join.  The reason (probably) this is taking long is because the
update above is forced to FULL scan T1 and for every row, run the where exists
query and then do it again if it succeeds and then do the update.  If the table
T1 is large, the full scan will take a while -- as well performing the where
exists query for each row.

What you can try is something along these lines:

scott@ORA734.WORLD> create table t1
  2  ( x int constraint t1_pk primary key,
  3    y int );

scott@ORA734.WORLD> create table t2
  2  ( x int constraint t2_pk primary key,
  3    y int );

scott@ORA734.WORLD> insert into t1 values ( 1, 1 );
scott@ORA734.WORLD> insert into t1 values ( 2, 1 );
scott@ORA734.WORLD> insert into t1 values ( 3, 1 );

scott@ORA734.WORLD> insert into t2 values ( 2, 2 );
scott@ORA734.WORLD> insert into t2 values ( 3, 2 );

scott@ORA734.WORLD> set autotrace on explain
scott@ORA734.WORLD> update
  2    ( select /*+ USE_NL(t1) INDEX( t1 t1_pk ) */
  3                     t1.y t1_y, t2.y t2_y
  4            from t1, t2
  5           where t1.x = t2.x )
  6     set t1_y = t2_y
  7  /

Execution Plan
----------------------------------------------------------
   0      UPDATE STATEMENT Optimizer=CHOOSE (Cost=83
   1    0   NESTED LOOPS (Cost=83 Card=68 Bytes=3536)
   2    1     TABLE ACCESS (FULL) OF 'T2'
   3    1     INDEX (UNIQUE SCAN) OF 'T1_PK' (UNIQUE)

scott@ORA734.WORLD> set autotrace off
scott@ORA734.WORLD> select * from t1
  2  /

         X          Y
---------- ----------
         1          1
         2          2
         3          2

I used hints to help the optimizer along -- they may not be necessary if
you've alot of rows and have analyzed the tables.

The USE_NL hint says to "nested loops join to T1", and the index hint helps it
pick the correct index for doing the join.

To see that the hints might not be necessary:

scott@ORA734.WORLD> insert into t1 select rownum+5, 1
  2    from all_objects
  3  /

1128 rows created.

scott@ORA734.WORLD> analyze table t1 compute statistics
  2  /

Table analyzed.

scott@ORA734.WORLD> analyze table t2 compute statistics
  2  /

Table analyzed.

scott@ORA734.WORLD> set autotrace on explain
scott@ORA734.WORLD> update
  2    ( select t1.y t1_y, t2.y t2_y
  3            from t1, t2
  4           where t1.x = t2.x )
  5     set t1_y = t2_y
  6  /

2 rows updated.

Execution Plan
----------------------------------------------------------
   0      UPDATE STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS (Cost=3 Card=2 Bytes=38)
   2    1     TABLE ACCESS (FULL) OF 'T2'
   3    1     INDEX (UNIQUE SCAN) OF 'T1_PK' (UNIQUE)

scott@ORA734.WORLD> set autotrace off

So, by putting a significant number of rows into T1 and letting the optimizer
know about them -- it picked the correct plan from the get go.

It should be NOTED that in order for the update of a join to work, T2 in the
above example must have a primary or unique key on it.  If T2 does not,
you'll not be able to update the join.  The optimizer needs to know that T1 will
be joined to AT MOST 1 row in T2 and the way it does this is by seeing that a
primary key or unique constraint exists on the join conditions
Review &
      Followup

      Rating: 5
Very
      Interesting syntax !!  August 07, 2001
Reviewer:
      Tom (another one)  from France
The UPDATE (SELECT... FROM... WHERE...) SET... syntax seems to be very powerful
!! I'm sure I can use it instead of nested cursors in my PL/SQL blocks, I'll
have to try this.

      Rating: 5
Update t with
      values from t1  October 26, 2001
Reviewer:
      A reader
update  t
set pbcur_code=
(select distinct a.pbcur_cd
from  t1 a, t b
where a.dpi_jq_id in (Select distinct  dpi_jq_id
                       from   t
                        where inty_cd='KLM_AA_SWA'
                        )
and a.pbcur_cd is not null
and a.dpi_jq_id=b.dpi_jq_id
   and t.inty_cd='KLM_AA_SWA')

DESC T
PQ_CODE
dpi_jq_id
MONTH_YEAR
inty_cd

DESC T1
PQ_CODE
dpi_jq_id

t CONTAINS  the column called  Pbcur_CODE, but we need to populate it  from t1
only for those records with inty_cd='KLM_AA_SWA'.

So I select all the  dpi_jq_id's with a inty_cd='KLM_AA_SWA',   and select the
distinct of pbcur_cd for these selected dpi_jq_id's from t1 ,  and then go
update the t table with
Pbcur_CODE WHERE  a.dpi_jq_id=b.dpi_jq_id i.e. t1.dpi_jq_id=t.dpi_jq_id

Iam getting the following error

[1]: (Error): ORA-01427: single-row subquery returns more than one row

How should I update table t  with pbcur_code, getting the pbcur_code's from t1
where t1.dpi_jq_id=t.dpi_jq_id and t.inty_cd='KLM_AA_SWA'

Thank you

          Followup:

well, you need to make sure your subquery returns a SINGLE record!

update t
   set pbcur_code = ( select pbcur_cd
                         from t1
                        where t1.dpi_jq_id = t.dpi_jq_id )
  where inty_cd = 'KLM_AA_SWA'
    and a.pbcur_id is not null
    and exists ( select pbcur_cd
                   from t1
                  where t1.dpi_jq_id = t.dpi_jq_id )

      Rating:
      4
Error  May 07, 2002
Reviewer:
      Adrian  from Lima, Peru
look at this
  1      UPDATE (SELECT a.vdc_saldo_ope saldo,
  2                     c.vec_mon_usa
  3                FROM admnnii.vd_operacioncomex a,
  4                     (SELECT max(vdcpk_cod_ope) oper,
  5                             mecpk_cod_prod,
  6                             vecpk_cod_cli
  7                        FROM admnnii.vd_operacioncomex
  8                      GROUP BY mecpk_cod_prod, vecpk_cod_cli) b,
  9                      admnnii.ve_lin_pro c
 10                WHERE b.vecpk_cod_cli = a.vecpk_cod_cli
 11                  AND b.mecpk_cod_prod = a.mecpk_cod_prod
 12                  AND b.oper = a.vdcpk_cod_ope
 13                  AND c.vecpk_cod_cli = a.vecpk_cod_cli
 14                  AND c.mecpk_cod_prod = a.mecpk_cod_prod
 15              GROUP BY a.vecpk_cod_cli, a.mecpk_cod_prod, c.vec_mon_usa)
 16*        SET vec_mon_usa = saldo
SQL>/
    UPDATE (SELECT a.vdc_saldo_ope saldo,
           *
ERROR at line 1:
ORA-01732: data manipulation operation not legal on this view
seems like materialized view but i don't have any

          Followup:

No, it is just saying "you cannot update this VIEW" -- the inline view - -the
select is a view.

It has a group by.  You cannot update a group by view (an aggregate view).

      Rating: 4
Optimizing
      update  March 01, 2003
Reviewer:  Vikas
      Sharma  from Delhi India
Hello Tom,

There is any way to optimize the following statment

update T1
   set c1 = ( select c2 from T2 where T2.key = T1.key )
 where exists ( select c2 from T2 where T2.key = T1.key )

by using hints. Suppose i have a restriction for adding primary keys to tables.
But i know my subquery will return one row at a time.

Regards,

Vikas Sharma

          Followup:

If t2(c2) has a primary key or unique constraint -- sure.

just update the join.

If not, you are limited in what you can do -- but you might find using "IN" to
be faster than exists (it'll join -- but you'll still be running that correlated
subquery for the set clause).

Also,

create global temporary table temp
( key PRIMARY KEY, c2 );

and then

insert into temp
select t2.key, t2.c2;

update ( select t1.c1 t1_c1, t2.c2 t2_c2
           from t1, t2
          where t1.key = t2.key )
    set t1_c1 = t2_c2;

will work

Or if you have 9i -- look at MERGE, you could use that as well.

      Rating: 4
Optimizng
        March 01, 2003
Reviewer:  Vikas Sharma
       from Delhi India
Hi,

Thanks for lighting reply.

I will test it under my condns. and implement accordingly.
because i have the restriction of creating primary key or unique key.

1. temporary table
2. using IN

Regards,

Vikas Sharma

      Rating: 3
update with big
      tables  March 04, 2003
Reviewer:  Sri
       from California USA
Tom,
I went through your first reply. After reading it I have a question.
I have a table 'A' containing 12 million rows and Table 'B' containing 9 million
rows. MY sql update is
update B
Set b_column=some_value
where EXISTS (SELECT 'x' from A where A.Match_id=B.Match_id).

Here Match_id is pk on table B and FK on table A.
Pl. let me know use of EXISTS is better than IN or not.
Also, pl.explain why.
thanks

          Followup:

you will run

where EXISTS (SELECT 'x' from A where A.Match_id=B.Match_id)

9,000,000 times.  Now, consider that maybe that subquery takes 0.001 seconds to
execute.  That is 9000 seconds just to run the subquery over and over and over.

Now, the database -- using the CBO can and will turn that into a semi-join,
doing a much more efficient "mass operation" that would be equival to an IN.

the real answer comes from explain plan and the actual performance.

      Rating: 5
The ORDERED
      hint processes the tables left to right in the from clause and not right
      to left  March 07, 2003
Reviewer:  Frank
      Zhou  from Braintree, MA
Hi Tom,

     Thanks for sharing your valuable knowledge with all the Oracle developers
and DBA. Your answers have solved many people's headache. I am looking forward
to read your new book.

       Your previous comments about ORDERED Hint is not correct. (***The ORDERED
hint processes the tables right to left in the from clause,**** the USE_NL hint
says to "nested loops join to T1", and the index hint helps it pick the correct
index for doing the join.)

       I think the ORDERED hint processes the tables left to right in the from
clause and not right to left

 Thanks again for your excellent answers!!

 Frank

          Followup:

you are correct, i've corrected that.

      Rating: 4
Update
      doubt  August 05, 2003
Reviewer:  Peter
       from India
Hi tom,

1.) One of my senior(he is a database guy but not in oracle) is saying that
Update is exactly like delete and insert?. is it true?. Please let me know how
oracle is handling. is it must that before updateing the data I should check the
record is exist or not?. Because If that row not exist I am simply ignoring one
update statement(because according to my senior's statement update is toooo
costly). At the same time what are the reverse statements will be created in the
undo segment for each update. In sql server If exists(select * from table name)
then... is available. This will check the record is exist or not. Is there any
better way to do this other than finding count(*) and putting into one variable
or using sql%found.

2). In my sp I have written like this
update the record....
if sql%found then return end if;
insert the record....
is it advicable to have different sp for insert and update.

My question may look like very simple. but please reply to me

Thanks in advance

          Followup:

1) huh?  not sure what you are asking.  It would seem "funny" to have to ask
"are there any rows for me to delete" before deleting would it not?

I mean -- you would just issue "delete" if you wanted to delete some.  If it
deleted zero rows -- so what?  that is not an "error" or anything.

the funny thing is between the time you look to see if there is a row to delete
and the time you delete it -- the answer could have changed

so that check seems to be "something designed to eat additional resources for no
benefit"

2) if you want to "merge", then you did it right. update, if no records updated
insert.

In 9i, you can actually use "merge" instead.

      Rating: 4
Update
      doubt  August 06, 2003
Reviewer:  peter
       from India
Hi Tom

Ok Tom. Sorry for the confusion.

Actually I didn't ask for delete statement.
I had asked that before updating row in the table I should check that record is
exist in the table or not?.
In my procedure I wrote like this
..
begin
update table_name set x=.....
end;

But my senior is saying that before using update statement you should check the
particular record is exist in the table or not (using select statement). He mean
to say that if that row does not exist in that table you can avoid that "UPDATE"
statement. According to his statement UPDATE statement is too costly (even if it
does not update any record). SO
He is asking me to write like this
begin
select count(*) into v_Variable from table_name wherer...
if v_Variable > 0 then
Update table_name set x......
end if;
end;
which way will be the efficient one?.
--------------------------------------------------------
Could you explain how update statement works internally in oracle?
Thanks

          Followup:

AGGG -- he needs to prove that.

It is one of the worst practices out there in general.  ok, lets simulate.  A
table T with 1 row identified by primary key (simple update).

Two procedures, one that just updates, the other that "looks" and then updates:

ops$tkyte@ORA920> create table t ( x int primary key, y char(20) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( 1, 'xxx' );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace procedure do_update( p_x in number, p_times
in number )
  2  as
  3  begin
  4      for i in 1 .. p_times
  5      loop
  6          update t set y = 'aaa' where x = p_x;
  7          commit;
  8      end loop;
  9  end;
 10  /
Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace procedure do_select_update( p_x in number,
p_times in number )
  2  as
  3      l_cnt number;
  4  begin
  5      for i in 1 .. p_times
  6      loop
  7          select count(*) into l_cnt from t where x = p_x;
  8          if ( l_cnt > 0 )
  9          then
 10              update t set y = 'aaa' where x = p_x;
 11              commit;
 12          end if;
 13      end loop;
 14  end;
 15  /
Procedure created.

ops$tkyte@ORA920>

First, we simulate what happens when about 1/2 of the updates can be
"avoided"

ops$tkyte@ORA920> exec runStats_pkg.rs_start

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec do_update( 0, 10000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec do_update( 1, 10000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runStats_pkg.rs_middle;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec do_select_update( 0, 10000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec do_select_update( 1, 10000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runStats_pkg.rs_stop(1000);
Run1 ran in 1864 hsecs
Run2 ran in 2089 hsecs
run 1 ran in 89.23% of the time

Name                                  Run1        Run2        Diff
STAT...redo size                 5,562,668   5,561,032      -1,636
LATCH.lgwr LWN SCN                   5,846       7,525       1,679
LATCH.mostly latch-free SCN          5,846       7,525       1,679
LATCH.Consistent RBA                 5,845       7,526       1,681
STAT...messages sent                 5,843       7,524       1,681
LATCH.redo allocation               32,244      35,574       3,330
LATCH.redo writing                  17,563      22,604       5,041
LATCH.messages                      29,087      37,471       8,384
LATCH.cache buffers chains         144,056     153,835       9,779
STAT...session logical reads        50,823      60,739       9,916
STAT...consistent gets              20,037      30,013       9,976
STAT...index fetch by key           20,000      30,000      10,000
STAT...consistent gets - exami      20,003      30,008      10,005
LATCH.session idle bit              20,027      10,019     -10,008
STAT...enqueue requests             30,029      20,005     -10,024
STAT...enqueue releases             30,030      20,005     -10,025
STAT...recursive calls              60,075      50,012     -10,063
LATCH.enqueues                      20,132      10,039     -10,093
LATCH.library cache                100,315      90,206     -10,109
LATCH.dml lock allocation           40,002      20,003     -19,999
LATCH.session allocation            40,018      20,002     -20,016
LATCH.enqueue hash chains           60,150      40,045     -20,105
STAT...session pga memory                0    -131,072    -131,072

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
674,042     614,743     -59,299    109.65%

PL/SQL procedure successfully completed.

so the select/update took longer AND it latched more.  A lose lose situation.
Now, what if ALL of the updates can be skipped?

ops$tkyte@ORA920> exec runStats_pkg.rs_start

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec do_update( 0, 10000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runStats_pkg.rs_middle;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec do_select_update( 0, 10000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runStats_pkg.rs_stop(1000);
Run1 ran in 614 hsecs
Run2 ran in 165 hsecs
run 1 ran in 372.12% of the time

Name                                  Run1        Run2        Diff
STAT...calls to get snapshot s      20,003      10,003     -10,000
LATCH.session idle bit              10,013          12     -10,001
STAT...execute count                20,006      10,005     -10,001
STAT...enqueue requests             10,003           1     -10,002
STAT...enqueue releases             10,004           0     -10,004
LATCH.shared pool                   20,065      10,058     -10,007
LATCH.enqueues                      10,011           0     -10,011
LATCH.dml lock allocation           20,002           0     -20,002
LATCH.session allocation            20,002           0     -20,002
STAT...recursive calls              30,009      10,001     -20,008
LATCH.library cache pin             40,060      20,050     -20,010
LATCH.enqueue hash chains           20,014           0     -20,014
LATCH.library cache                 50,083      20,069     -30,014

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
204,014      63,482    -140,532    321.37%

PL/SQL procedure successfully completed.

Maybe a point to be made there -- but -- tell me, do you believe you'll skip
them all really?  What about when NONE of the updates are skipped?

ops$tkyte@ORA920> exec runStats_pkg.rs_start

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec do_update( 1, 10000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runStats_pkg.rs_middle;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec do_select_update( 1, 10000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runStats_pkg.rs_stop(1000);
Run1 ran in 1497 hsecs
Run2 ran in 2298 hsecs
run 1 ran in 65.14% of the time

Name                                  Run1        Run2        Diff
LATCH.redo allocation               32,420      34,436       2,016
LATCH.redo writing                  17,876      20,865       2,989
STAT...redo size                 5,560,236   5,563,972       3,736
LATCH.messages                      29,538      34,541       5,003
STAT...index fetch by key           10,000      20,000      10,000
STAT...consistent gets - exami      10,004      20,005      10,001
STAT...execute count                20,007      30,016      10,009
STAT...calls to get snapshot s      30,006      40,035      10,029
STAT...consistent gets              10,012      20,050      10,038
LATCH.shared pool                   20,074      30,123      10,049
STAT...recursive calls              30,017      40,089      10,072
STAT...session logical reads        40,740      50,856      10,116
LATCH.cache buffers chains         133,794     144,062      10,268
LATCH.library cache pin             40,076      60,158      20,082
LATCH.library cache                 50,103      70,221      20,118

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
473,547     547,689      74,142     86.46%

PL/SQL procedure successfully completed.

big lose lose there.  So, ask yourself "will I update it more then 1/2 of the
times?  if so....

Now, my question to you is -- why are you updating ROWS you didn't select in the
first place?  could you be suffering from lost update issues and not even know
it?  You might have data integrity issues in the first place....

      Rating: 4
Update takes
      long time  August 07, 2003
Reviewer:  Tony
       from India
There are two big tables t1 and t2 having 30 million rows each in my pro*c
appliction. As I open two cursors (one for t1 and one for t2), bulk fetch and
process data, it takes  long time to update all rows. To speed up the update,
I've joind the two tables to update t1 as below:

update( select t1.y t1_y, t2.y t2_y
        from t1, t2
        where t1.x = t2.x )
set t1_y = t2_y

This approach is very very slower than the two cursor pro*c approach. Tables are
analyzed. I tried bulk fetch and bulk update approach in PL/SQL also. Why is the
single update and pl/sql bulk update are slower than pro*c update?
Can you suggest some alternative please?

          Followup:

not that I don't believe you -- but -- i don't believe you.

show us the TKPROF for the pro*c and the TKPROF for the update.  Including all
query plans in the tkprof report.

Need to see all of the relevent sql for the pro*c program (select + update)

      Rating: 5
To
      Tony  August 07, 2003
Reviewer:
      A reader
hi Tony
(Tom is on out till weekend)

When you say bulk fetch - do you mean bulk collect?
If so, you should use the "limit" clause to limit
the number of records fetched to 100 or so and go in a
loop..

see
http://asktom.oracle.com/pls/ask/f?p=4950:8:9220374210870174093::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:5918938803188,
Regards
Menon:)

      Rating: 3
A better,
      faster non-cursor update?  November 11, 2003

Reviewer:  AJ  from Southwest USA
Tom,

My update is also taking too long on Oracle 9i. I have two tables, table1 and
table2.  Table1 has 300 records
with an item_id.  Table 2 has 1.2 million records with an item_id.  The
item_id's in table1 are shorter than
the item_id's in table2 but some of the item_id's in table1 may match the first
part of the item_id's in table2.
I need to search through table2 for each occurrence of an item_id that exists in
table1.  If I find a
match then I need to update table2's item_id to the shortened version in table1.
 Right now I am issueing
300 update statements with PL/SQL code, is there a way to get this into a single
update statement and make it
go faster? Currently it takes about 2 hours to complete.

Table1 may have item_id's like 'XYZ123EFG'
Table2 may have item_id's like 'XYZ123EFG789QRS'

If I find an item_id in table2 whose substr matches the entire item_id in
table1, then I need to update
the table2 item_id to the shorter table1 item_id.  The length of the item_id in
table1 may vary from table1 record
to record.

---------------------------------------------

declare

--local variables
l_item_id    varchar2(25);
l_sql_str    varchar2(200);
l_item_len    number;

--cursor for 300 row table
cursor cur1 is
 select item_id
   from table1;

begin

     open cur1;

     loop
      fetch cur1 into l_item_id;

       exit when(cur1%notfound);

      l_item_len := length(l_item_id);

      l_item_id := trim(l_item_id);

      --update for 500K row table
      update table2
             set item_id = l_item_id
           where substr(item_id, 1, l_item_len) = l_item_id;

      dbms_output.put_line( sql%rowcount || ' rows updated' );

     end loop;

     close cur1;

end;
/
---------------------------------------------

          Followup:

so, what happens when table1 has:

Table1 may have item_id's like 'XYZ123'
Table1 may have item_id's like 'XYZ123EF'
Table1 may have item_id's like 'XYZ123EFG'
Table2 may have item_id's like 'XYZ123EFG789QRS'

is that ever "possible"?  can a row in table2 have more then one match in
table1??

      Rating: 5
absolutely
      right  November 12, 2003
Reviewer:
      A reader
Tom,

The "possible" case you mentioned is right on the money. You are really smart!
There is more than one instance in table1 where there is data like:

XXXEE888BB2
XXXEE888BB2LA
XXXEE888BB2LAV

Depending on the order of which item_id in table one is used first to update
table2, the current code would update some of the table2 item_id's incorrectly.
If I created my table1 cursor of item_id's as sorted desc:

XXXEE888BB2LAV
XXXEE888BB2LA
XXXEE888BB2

that would update the table2 item_id's correctly for the first pass thru table2,
but then subsequent passes with the similar but shortened item_id's would screw
up the records that have already been updated.  What can I do?  Add a temporary
"updated" column in table2 and add it to the where clause in the update?

Thanks in Advance

          Followup:

why would you not make the data in the table you are updating from
UNAMBIGOUS????

      Rating:
      5
agreed  November 12, 2003
Reviewer:
      AJ  from Southwest USA
The table1 data is being cleaned up (thanks to you).  Would it be possible to
now accomplish this update without a cursor, say, using an update with an inline
view?

      Rating: 5
went ahead and
      tried it  November 12, 2003
Reviewer:
      A reader
Tom,

It looks like the select that I would use in my in-line Update is not very
efficient. Neither item_id column has an index on it.  It is taking about 2
hours to run the trace on the select where table1 has 300 rows and table2 has
about 1.2 million.  The 129K rows is the right number of rows in table2 to be
updated.

SQL> ed
Wrote file afiedt.buf

  1  select a.item_id c1, b.item_id c2
  2      from table1 a,
  3           table2 b
  4*    where a.item_id = substr(b.item_id,1,length(a.item_id))
SQL> /

129823 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=245252 Card=3755343
          Bytes=123926319)

   1    0   NESTED LOOPS (Cost=245252 Card=3755343 Bytes=123926319)
   2    1     TABLE ACCESS (FULL) OF 'TABLE1' (Cost=2 Card=32
          7 Bytes=3924)

   3    1     TABLE ACCESS (FULL) OF 'TABLE2' (Cost=750 Ca
          rd=11484 Bytes=241164)

Statistics
----------------------------------------------------------
          7  recursive calls
          0  db block gets
    6285342  consistent gets
    6230403  physical reads
          0  redo size
    5229940  bytes sent via SQL*Net to client
    1134177  bytes received via SQL*Net from client
       8656  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     129823  rows processed

SQL>

          Followup:

do you have hash_join_enabled set to false or something?  that should not be
nested looping.

      Rating: 5
hash join
      enabled...  November 13, 2003
Reviewer:  AJ
       from Southwest USA
Tom,

There is no setting in the init.ora for hash_area_size on this instance.  Also,
I tried alter session set hash_join_enabled = true and the performance was
exactly the same as before.  The optimizer is not choosing to use a hash join.
Is this because there is no hash_area_size parameter in init.ora?

Thanks in advance...

          Followup:

ahh, i see now, its like a cartesian product -- every row in t1 must be compared
to every row in t2

  1  select a.item_id c1, b.item_id c2
  2      from table1 a,
  3           table2 b
  4*    where a.item_id = substr(b.item_id,1,length(a.item_id))
                                 ^^                 ^^

no hash join there.

I guess I might drop down to a procedure here

  for x in ( select * from small_table )
  loop
     update big_table set item_id = x.item_id where item_id like x.item_id||'%';
  end loop;

      Rating: 5
Thanks
      Tom  November 14, 2003
Reviewer:  AJ
       from Southwest USA
I appreciate your time Tom. I just placed my order on Amazon for Effective
Oracle by Design.

Thanks again!

November 13, 2003 Andre -- Thanks for the question regarding "How
      can I track the execution of PL/SQL and SQL?", version 8.1.6

      You Asked
Hi Tom,

How can I know what a given user is executing if his status is
ACTIVE ? How can I know which PL/SQL blocks or SQL statements
are being run by him ? As to SQL statemets, I can join
v$session.user# with v$sqlarea.parsing_user_id ( am I really
right ??? ), but I am not sure about PL/SQL blocks. How can I
track the execution of those objects and queries (something like
V$transaction for entire trasactions) ?

Regards.

      and we said...
I use the script at the bottom.  It shows everyone logged in and
if they are active, what they are doing and how long they've
been doing it.

If someone is executing PLSQL, what you will see will depend on
what the plsql is currently doing.  If the plsql is doing SQL,
you'll see the SQL.  if the plsql is doing lots of PLSQL work --
you'll see that code.  What I like to do is have everyone
"instrument" their code with calls to dbms_application_info
which can fill in the client_info, action, and module columns in
v$session.  In this fashion, you can see where in a procedure
someone is based on the values in these columns.  showsql
exposes this information to you as well.  sqlplus uses it to
show you what script someone is running for example...

---------------- showsql.sql --------------------------
column status format a10
set feedback off
set serveroutput on

select username, sid, serial#, process, status
from v$session
where username is not null
/

column username format a20
column sql_text format a55 word_wrapped

set serveroutput on size 1000000
declare
    x number;
begin
    for x in
    ( select username||'('||sid||','||serial#||
                ') ospid = ' ||  process ||
                ' program = ' || program username,
             to_char(LOGON_TIME,' Day HH24:MI') logon_time,
             to_char(sysdate,' Day HH24:MI') current_time,
             sql_address, LAST_CALL_ET
        from v$session
       where status = 'ACTIVE'
         and rawtohex(sql_address) <> '00'
         and username is not null order by last_call_et )
    loop
        for y in ( select max(decode(piece,0,sql_text,null)) ||
                          max(decode(piece,1,sql_text,null)) ||
                          max(decode(piece,2,sql_text,null)) ||
                          max(decode(piece,3,sql_text,null))
                               sql_text
                     from v$sqltext_with_newlines
                    where address = x.sql_address
                      and piece < 4)
        loop
            if ( y.sql_text not like '%listener.get_cmd%' and
                 y.sql_text not like '%RAWTOHEX(SQL_ADDRESS)%')
            then
                dbms_output.put_line( '--------------------' );
                dbms_output.put_line( x.username );
                dbms_output.put_line( x.logon_time || ' ' ||
                                      x.current_time||
                                      ' last et = ' ||
                                      x.LAST_CALL_ET);
                dbms_output.put_line(
                          substr( y.sql_text, 1, 250 ) );
            end if;
        end loop;
    end loop;
end;
/

column username format a15 word_wrapped
column module format a15 word_wrapped
column action format a15 word_wrapped
column client_info format a30 word_wrapped

select username||'('||sid||','||serial#||')' username,
       module,
       action,
       client_info
from v$session
where module||action||client_info is not null;
Review &
      Followup

      Rating:
      4
thanks  July 25, 2001
Reviewer:
      Olga  from Canada
The more I work with Oracle the more new things to learn I find ;)
Thank you for this script

      Rating: 4
How to
      register  May 14, 2002
Reviewer:  Anant
       from usa
Tom,
   You Say
""instrument" their code with calls to dbms_application_info "

Can you please explain how to do it? Suppose I have a code which SELECTs from
two different tables ( One table T1 has 177 million records and T2 has 5000
records) and then INSERTs into another (3rd) table. It is been running for 12
hours and I want to know what is happening? Will I run out of Sort area size or
TEMP space ( The Select is with 3-4 Group By clauses). I had run it about 2
months back and it completed in 3 hours. The data has mot increased much but I
don't know if the DBA has changed some parameters since then. We have a 13 GB
SGA and 80 GB TEMP tablespace. Can I predict with some certainty how much more
time will it take to insert the records. I am using Oracle 8.1.7.
Thanks for your help.

          Followup:

Hopefully it is a single insert into select

Look at v$session_longops.  You'll find info in there regarding where it is in
the processing.

      Rating: 4
How to
      register  May 14, 2002
Reviewer:  Anant
       from usa
Tom,
   You Say
""instrument" their code with calls to dbms_application_info "

Can you please explain how to do it? Suppose I have a code which SELECTs from
two different tables ( One table T1 has 177 million records and T2 has 5000
records) and then INSERTs into another (3rd) table. It is been running for 12
hours and I want to know what is happening? Will I run out of Sort area size or
TEMP space ( The Select is with 3-4 Group By clauses). I had run it about 2
months back and it completed in 3 hours. The data has mot increased much but I
don't know if the DBA has changed some parameters since then. We have a 13 GB
SGA and 80 GB TEMP tablespace. Can I predict with some certainty how much more
time will it take to insert the records. I am using Oracle 8.1.7.
Thanks for your help.

      Rating:
      3
Thanks  May 16, 2002
Reviewer:
      Anant  from USA
Thanks Tom I read it in your book - very nicely explained.

Thanks

      Rating: 4
Change
      plan  May 16, 2002
Reviewer:  A reader

If a SQL is executing and we do certain changes which will impact the
performance of this SQL. Then is there anyway we can force the executed SQL to
read these changes. Ofcourse without killing that session and re-running it.

T1 -> SQL joining A, B & C executed. Because of unavailable stats on A this SQL
will execute for 1hr.
T2 -> Updated stats on table A. This will improve the performance from 1hr to
2mins.
T3 -> Can I anyway force the SQL executed at time T1 to read the changes made at
time T2.

Thanks

          Followup:

No, think about it -- how could it?  say you changed it from using an index to
using a full scan (via the stats).  It couldn't just start full scanning as it
would undoubtably re-read rows it had already processed.

You need to restart it.

      Rating: 3
Can't
      Compile  November 14, 2002
Reviewer:  Ted
      Persky  from San Diego, CA USA
Tom:

Thanks for posting the script.  For some reason, however, I cannot get it to
compile.  Keep getting 'PLS-00103 Encountered the symbol "/"' at line forty,
which is the forward slash following the two "end loops" and the "end"
statement.  Can you please clarify what I'm doing wrong?  Thanks in advance.

          Followup:

make sure you don't have a blank in front of that slash:

ops$tkyte@ORA920.LOCALHOST> begin
  2    dbms_output.put_line( 'hello' );
  3  end;
  4   /
  5  /
 /
 *
ERROR at line 4:
ORA-06550: line 4, column 2:
PLS-00103: Encountered the symbol "/" The symbol "/" was ignored.

sounds like you do (from the cut and paste maybe)

      Rating: 4
You're
      Incredible!  November 15, 2002
Reviewer:  Ted
      Persky  from San Diego, CA USA
Tom:

That did it.  Thanks for an amazing turnaround time, even in the midst of
OracleWorld.  Just amazing!

Best wishes,

Ted P.

      Rating: 4
Very useful,
      but I have a question...  November 15, 2002

Reviewer:  Colin Davies  from Pasadena, California

What eactly is the column LAST_CALL_ET?  It's just a number, but I would like to
know what the number means.  The Oracle docs just say "LAST_CALL_ET   NUMBER
The last call"

          Followup:

If a session is currently ACTIVE, this is the number of seconds the statement it
is executing has been ACTIVE.

If a session is currently INACTIVE, this is how long its been inactive.

If timed statistics is TRUE, this number is very accurate.
If timed statistics is not enabled, this number is +/- many seconds.

      Rating: 3
Very Useful
      Query   January 16, 2003
Reviewer:  Stanley
       from us
Its a good piece of code.

      Rating: 4
How to find
      whether any specific package is being executed
      currently  March 06, 2003
Reviewer:  kumar
       from Escondido
Tom,

I have a different situation , I need to run some key purging process, in order
to do that I need to find and process whether one specific package is being
executed or not? Well this package is being called through another auto-job. Can
you hint me? or suggest me an easy SQL to find the specific pacakge or procedure
is being executed currently or not?

Thanks

          Followup:

see dba_ddl_locks, might have to run catblock.sql in $ORACLE_HOME/rdbms/admin in
order to have that view.

      Rating: 5
dba_ddl_locks
      entries remained till the session closed  March 06, 2003

Reviewer:  kumar  from Escondido
Tom,

Thanks and an Excellent clue you have given to me, I tried to use that view on
9iR2 and one interesting thing I found is, If I execute a procedure through
SQLPLUS then I query the below SQL:-

Select a.username, b.ower||'.'||b.name
from v$session a, dba_ddl_locks b
where a.sid=b.session_id
and b.type like '%/Procedure%/'.

This has given the exact information and shown that procedures name etc. But
when the procedure execution completed successfully, latter also I query the
above query, but it was returing the same Procedure entries untill I close the
SQLPLUS session. Once if I close SQLPLUS then if I query, then only I get the
results without that recently executed procedure. My need here is I need to get
the exact information whether the procedure is executing or not executing. Any
suggetions

          Followup:

You can add code to the package such that it:

o sets MODULE using dbms_application_info whenever you enter it and unsets it
upon exit.  then select * from v$session where module = 'XXXX' will tell you.

o or uses dbms_lock to create a user defined lock upon entry, releases it upon
exit.

either way will do it.

but now that I think about it -- dbms_lock is BEST.  the package could get the
lock in shared mode (many people run the package) but the process that cannot
have someone running the lock would get it in EXCLUSIVE mode -- that would
prevent anyone from starting the package whilst this process was running.

      Rating: 4
very useful,
      but here comes just another idea to test ...  March 07, 2003

Reviewer:  J. Laurindo Chiappa  from Sao Paulo, SP
      Brazil
Another possible idea : the following script was adapted from the original idea
& research from Steve Adams (www.ixora.com.au), I tested in Personal 8ir3 and it
worked as desired :

step a) in a quiet database, run the script :

sys@PO8IR3:SQL>@v8_running_sqls
**** Currently Executing Packages ****

no rows selected

sys@PO8IR3:SQL>get v8_running_sqls
  1  prompt **** Currently Executing Packages ****
  2  SELECT
  3    substr(DECODE(o.kglobtyp,
  4      7, 'PROCEDURE', 8, 'FUNCTION', 9, 'PACKAGE', 12, 'TRIGGER', 13,
  5  'CLASS'),1,15)  "TYPE",
  6    substr(o.kglnaown,1,30)  "OWNER",
  7    substr(o.kglnaobj,1,30)  "NAME",
  8    s.indx  "SID",
  9    s.ksuseser  "SERIAL"
 10  FROM
 11    sys.X$KGLOB  o,
 12    sys.X$KGLPN  p,
 13    sys.X$KSUSE  s
 14  WHERE
 15    o.inst_id = USERENV('Instance') AND
 16    p.inst_id = USERENV('Instance') AND
 17    s.inst_id = USERENV('Instance') AND
 18    o.kglhdpmd = 2 AND
 19    o.kglobtyp IN (7, 8, 9, 12, 13) AND
 20    p.kglpnhdl = o.kglhdadr AND
 21    s.addr = p.kglpnses
 22* ORDER BY 1, 2, 3

step b) in another session (not showing) I'm running a procedure who call
DBMS_LOCK.SLEEP, and with the other session running the proc :

sys@PO8IR3:SQL>@v8_running_sqls
 **** Currently Executing Packages ****

 TYPE      OWNER  NAME         SID     SERIAL
 -------   ------ ------------ --- ----------
 PACKAGE   SYS    DBMS_LOCK     12       3807
 PROCEDURE SCOTT  PROC_RUN      12       3807

step c) after the execution (BUT with the other session still conected/alive,
just idle) :

 sys@PO8IR3:SQL>@v8_running_sqls
 **** Currently Executing Packages ****

no rows selected

Regards,

 Chiappa

      Rating: 5
Sys.x$ tables
      how to grant select if I need to use by another user  March
      07, 2003
Reviewer:  Kumar  from Escondido
Chippa's invention is mind blowing, however if I need to run that query from
another user ( who is not sys), perhaps I can not get the select grants on those
sys.X$LOB tables. I tried to grant after login as sys but I got an error saying
that "ORA-02030: can only select from fixed tables/views". Any work around to
query these views from non sys user.

Thanks
Kumar

          Followup:

sorry -- x$ tables are magical and I give no help in that area at all.

YOU want to use dbms_lock, you NEED to use dbms_lock.  just knowing that at this
instant -- when you run that query -- no one is using the package, that is not
useful to you -- as the instant after you run that query the answer will change
(and you should not run your process)

You NEED to use a serialization device.

      Rating: 5
Excellent
      warning - I will complete the just for the record  March 07,
      2003
Reviewer:  J. Laurindo Chiappa  from SP, Sao
      Paulo, Brazil
Tom, I agree 101% - X$ are "magical", they CAN and WILL change anytime, without
warning, and worse, NO READ-CONSISTENCY here - so, in a very busy db, chances
are of X$ being "late", not refreshed, or alike.

SO, really, IF the asker wants to be always sure, he will need serialize (to
assure consistency), and make the needed alters in the code to "instrument".

BUT, just for the record : the common way of "grant" X$ things to another user
is : as SYS, make a view of the desired x$ and give grant of the view (as
developed by Stave Adams in
http://www.ixora.com.au/scripts/sql/create_xviews.sql
) :

sys@PO8IR3:SQL>create view X_$BH as select * from X$BH;

View created.

sys@PO8IR3:SQL>grant select on X_$BH to scott;

Grant succeeded.

-- connected as the "common" user ...

scott@PO8IR3:SQL>select file#, ts#, obj from sys.x_$bh where rownum < 3;

     FILE#        TS#        OBJ
---------- ---------- ----------
         1          0        110
         1          0         10

OF COURSE, do it at your own risk, no guarantees (I use it to "peek" program
executions on development databases, and so on).

 Regards,

  Chiappa

      Rating:
      5
  August 15, 2003
Reviewer:
      A reader

      Rating: 4
Track the
      Sql  August 23, 2003
Reviewer:  Sri
       from India
Dear Tom,

1. I have a third party application that is giving problem.  It connects to
oracle (8.1.5) using odbc.  When I click on a particular button it is giving,
"ora-00942: table or view does not exist".  Unfortunately, we don't have support
for this application now.  How to find out what sql it is trying to execute when
I click that button.  If I can find it, I may be able to replace the table that
it is not able to find.

2. Could you please explain regarding the status column in v$session.  When does
this become active.  In documentation, it is given Active means 'Currently
Executing SQL'.  Does that mean that the status will be active from the time I
start executing the sql, till the time it finishes fetching the results?  If so,
I tried the following: - I logged in to sql*plus with my username.  Gave 'select
* from all_objects;'.  As the sql was executing, I opened another sql*plus
window and logged in as system and queried v$session.  But still the status of
my user is shown as 'INACTIVE' though I am sure that the sql was still
executing.  Why is this so?  Kindly clarify.

Thanks a lot.

Sri

          Followup:

1) you can either use sys.dbms_system to set sql_trace in your session from
another session or for this user create a login database trigger that issues:

execute immediate 'alter session set sql_trace=true';

note that the owner of that trigger MUST have alter session granted directly to
it, not via a role, for that trigger to be successful.

2) status will be active when your client sends a request to the server.  if you
open a query -- status will be active during the parse phase.  when the query is
opened, you'll be inactive again.  fetch a row -- active, then not active.  and
so on.

your test case just means when you queried v$session, sqlplus was in the
processing of printing some of the rows it fetched, it was not fetching at that
point.  if you just kept hitting "/" to run that v$session query over and over
you would see your session go active/inactive/active/inactive and so on.

      Rating: 2
Not what I
      needed, but not bad  October 23, 2003
Reviewer:
      Evan  from Atlanta Ga USA
Tom,

I was hoping that the showsql script would show the current sql command so that
I could follow scripts as they execute. Even so, it is a great script!!!

Using "shotgun programing" type selects, I narrrowed it down to one SQL
statement in a script which goes into never never land.

explain plan shows a cost of about 750 (low). Running your showsql.sql script
shows me that it is there (which I get from OEM). OEM shows a steady increase in
phys/log reads, so it is doing something.

What can I do to find out WHAT it is doing?

Is there a script to see the current SQL running so that I can use a batch
capture rather than OEM to see the SQL?

I am looking for something on the order of:
user     SQL
-------- ------------------------
Joeuser  select blah,blah
         from tableX where boolean
         and soforth

Thanks

Evan

          Followup:

umm, that is what showsql does -- it shows the active sessions and the sql they
are running.

i'm very confused?  it seems to do exactly what you say you want it to do?

      Rating: 4
Last executed
      SQL  November 12, 2003
Reviewer:  houman
       from Canada
Tom,

Can I have my last executed SQL retrieved from the database?
just by passing the sid. I want those SQL executed in my application not the
onse executed by Oracle itself

          Followup:

you'll get the last sql executed by joining v$session to v$sql. if the last sql
executed was recursive SYS sql -- you'll get that (that would be the last sql).
we really don't discriminate between them

      Rating:
      3
  November 13, 2003
Reviewer:
      Houman
Tom,

I have a debugging mechanism in my application (c++) where I can dump the SQL
statements sent to the database into a file at the client side. Now I want to
add the same functionality for PL/SQL packages meaning that I want to dump the
queries ran in the cursors and basically all other SQL statement run in the
package body into the same file at client side. I want this happen at the same
time  or before each query is executed in the database.

In my application, c++ part, I have a query class that all queries have to go
through that class before be sent to the database therefore it is easy to grab
the SQL statement and dump it to the debug file. However in PL/SQL, I don't know
where to find the SQL statement being executed in the same order that were sent
to the database in a particular session. I know that Oracle somehow keeps track
of queries related to a session in a sequential order because this is the way
you generate trace file when SQL_TRACE is set to TRUE. Retrieving the current
and previous sql statement from V$session  joined with v$SQL does not solve my
problem either because, when run in the same session, these two SQL always point
to the same SELECT statement that is currently reading from V$session not the
one before. Consider the below procedure:

CREATE OR REPLACE PROCEDURE FOO AS
CURSOR C1 IS SELECT OBJECT_NAME FROM USER_OBJECTS;
V_USERNAME  USER_OBJECTS.OBJECT_NAME%TYPE;
V_DUMMY                    DUAL.DUMMY%TYPE;
c_STR              VARCHAR2(512);
BEGIN

 OPEN C1;
 C_STR := GET_CURRENT_SQL(MY_SID);         -- GET CURRENT SQL STATEMENT FROM
ORACLE

 DUMP_CURRENT_SQL(C_STR,FILE_NAME);    -- DUMP IT TO THE FILE

 LOOP
            FETCH C1 INTO V_USERNAME;
            EXIT WHEN C1%NOTFOUND;
            SELECT DUMMY INTO V_DUMMY FROM DUAL;
            C_STR := GET_CURRENT_SQL(MY_SID);          -- GET CURRENT SQL
STATEMENT FROM ORACLE

            DUMP_CURRENT_SQL(C_STR,FILE_NAME)      -- DUMP IT TO THE FILE
          .
          .
          .
 END LOOP;
 CLOSE C1;
END;
/

After the execution of the above procedure, I should have the following in the
debug file providing that USER_OBJECTS has only 8 records:

. Debug file FILE_NAME

.

SELECT OBJECT_NAME FROM USER_OBJECTS
SELECT DUMMY INTO :a FROM DUAL
SELECT DUMMY INTO :a FROM DUAL
SELECT DUMMY INTO :a FROM DUAL
SELECT DUMMY INTO :a FROM DUAL
SELECT DUMMY INTO :a FROM DUAL
SELECT DUMMY INTO :a FROM DUAL
SELECT DUMMY INTO :a FROM DUAL

.
.
.

Do you have any suggestions how to do that using SQL information stored in
Oracle tables/memory?

          Followup:

plsql running in the server cannot write to the client.

we invented this capability and gave it many great features.  it is called:

alter session set sql_trace=true;

that is what you want to do.  it has been done for you already.

November 13, 2003 Ak -- Thanks for the question regarding "chained
      rows", version 8.1.5

      You Asked
Hi tom,
   Regarding my question(chained rows),I have a tool which says 1.555% fetch ,is
chained rows, and also from cont. chained fetch rows of v$sysstat
I know this...But i don't know for which "TABLE" IT IS HAPPENING.
That's why i asked which table is having a chained rows in a "SCHEMA"
Your answer was

ANALYZE TABLE <TNAME> LIST CHAINED ROWS...
FOR THIS I SHOULD KNOW THE NAME OF THE TABLE...WHICH I DON'T KNOW,
BUT I AM VERY SURE(AFTER LOOKING V$SYSSTAT) THERE ARE SOME TABLE
WHICH IS GETTING CHAINED...i NEED TO KNOW THE NAME OF THE TABLE.
CAN YOU HELP ME ON THIS

HOPE IT IS CLEAR..

THANKS
REGARDS
ANIL

      and we said...
You need to analyze your tables to find the culprit.

You can automate this via:

for x in ( select table_name from user_tables )
loop
   execute immediate 'analyze table ' || x.table_name || ' .....'
end loop;

and then the table it lists the rowids into will have the table name.  You can
find the tables with the most chained rows there.

for example:

scott@ORA717DEV.US.ORACLE.COM> create table CHAINED_ROWS (
  2    owner_name         varchar2(30),
  3    table_name         varchar2(30),
  4    cluster_name       varchar2(30),
  5    partition_name     varchar2(30),
  6    subpartition_name  varchar2(30),
  7    head_rowid         rowid,
  8    analyze_timestamp  date
  9  );

Table created.

scott@ORA717DEV.US.ORACLE.COM> begin
  2     for x in ( select table_name from user_tables )
  3     loop
  4        execute immediate 'analyze table ' || x.table_name ||
  5                          ' list chained rows into chained_rows';
  6     end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

scott@ORA717DEV.US.ORACLE.COM>
scott@ORA717DEV.US.ORACLE.COM> select table_name, count(*)
  2    from chained_rows
  3   group by table_name
  4   order by 2
  5  /

TABLE_NAME                       COUNT(*)
------------------------------ ----------
T1                                     15
T2                                     48

shows me I have 15 rows in T1 and 48 rows in T2 that need investigating.  I can
determine that the rows in T1 are migrated:

scott@ORA717DEV.US.ORACLE.COM> desc t1
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 X                                            NUMBER(38)
 Y                                            CHAR(2000)
 Z                                            CHAR(2000)
 A                                            CHAR(2000)
 B                                            CHAR(2000)
 C                                            CHAR(2000)

scott@ORA717DEV.US.ORACLE.COM> @a
scott@ORA717DEV.US.ORACLE.COM> select nvl(vsize(x),0)+
  2         nvl(vsize(y),0)+
  3         nvl(vsize(z),0)+
  4         nvl(vsize(a),0)+
  5         nvl(vsize(b),0)+
  6         nvl(vsize(c),0)
  7    from t1
  8   where rowid in ( select head_rowid
  9                      from chained_rows
 10                     where table_name = 'T1' )
 11  /

NVL(VSIZE(X),0)+NVL(VSIZE(Y),0)+NVL(VSIZE(Z),0)+NVL(VSIZE(A),0)+NVL(VS
----------------------------------------------------------------------
                                                                  4002
                                                                  4002
                                                                  4002
                                                                  4002
                                                                  4002
                                                                  4002
                                                                  4002
                                                                  4002
                                                                  4002
                                                                  4002
                                                                  4002
                                                                  4002
                                                                  4002
                                                                  4002
                                                                  4002

15 rows selected.

and the ones in T2 are chained:

scott@ORA717DEV.US.ORACLE.COM> select nvl(vsize(x),0)+
  2         nvl(vsize(y),0)+
  3         nvl(vsize(z),0)+
  4         nvl(vsize(a),0)+
  5         nvl(vsize(b),0)+
  6         nvl(vsize(c),0)
  7    from t2
  8   where rowid in ( select head_rowid
  9                      from chained_rows
 10                     where table_name = 'T2' )
 11  /

NVL(VSIZE(X),0)+NVL(VSIZE(Y),0)+NVL(VSIZE(Z),0)+NVL(VSIZE(A),0)+NVL(VS
----------------------------------------------------------------------
                                                                 10001
                                                                 10002
                                                                 10002
                                                                 10002
                                                                 10002
                                                                 10002
                                                                 10002
                                                                 10002
...

since 10,002 is > 8k

I can fix the rows in T1 via:

scott@ORA717DEV.US.ORACLE.COM> create table tmp as
  2  select * from t1 where rowid in ( select head_rowid
  3  from chained_rows where table_name = 'T1' );

Table created.

scott@ORA717DEV.US.ORACLE.COM> delete from t1 where rowid in ( select head_rowid
  2  from chained_rows where table_name = 'T1' );

15 rows deleted.

scott@ORA717DEV.US.ORACLE.COM> insert into t1 select * from tmp;

15 rows created.

scott@ORA717DEV.US.ORACLE.COM> commit;

Commit complete.

scott@ORA717DEV.US.ORACLE.COM> delete from chained_rows;

63 rows deleted.

scott@ORA717DEV.US.ORACLE.COM> analyze table t1 list chained rows into
chained_rows;

Table analyzed.

scott@ORA717DEV.US.ORACLE.COM> select * from chained_rows;

no rows selected

but the rows in T2 are forever chained -- they do not fit on a block.

Review &
      Followup

      Rating:
      3
Sagi  October 30, 2001
Reviewer:
      A reader
Hi Tom,

I also one of the guys who thank you for your valuable time and efforts shown in
answering all our questions.

In the above explanation you said that T1 are migrated and T2 are chained after
you executed the Query using VSIZE.  How can we seeing the VSIZE 4002 or 10001 &
10002 say that they migrated or chained. Is it becuse the VSIZE is constant in
T1 where as its changed in T2.

Please explain.

Thank you.

          Followup:

sorry -- i should have been more clear.

I'm using an 8k block size.  I KNOW a row that is 4002 bytes fits in an 8k
block.  i KNOW Oracle will not chain a row that can fit on a block, it will
migrate it instead.  Hence, these rows that could fit on a block are migrated.

10001 is bigger then a block, hence I KNOW the row is chained, not migrated.

      Rating: 5
100% migrated..
      how is this possible  November 13, 2003
Reviewer:
      rob  from PA
Tom,

I came across a table that the recent statistics show 100% chaining.
avg_row_length is 1200 for an 8k block size for a segment with a 10 PCT_FREE.
statistically wouldn't at least a single row somewhere be in a block without a
pointer?

I guess it would have been possible that with a small pct_free (10%), an update
that affected every row could cause every row in the table to migrate?  does
that make sense?

part of this is the loader's fault i think also.  The loader inserts a pk, and
than updates 20 columns in subsequent statements.  since loading is a serial row
by row process (not multi-threaded, and another row isn't loaded until the last
one finished), I can see how a large percentage of a block would be migrated
based on this practice, but the first 4 or 5 rows in the block should have gone
in without migration. right?

This leads me back to the fact that it was probably an update, but that single
column update would have had to update a column with a length of at least 10% of
the block size, since 10% should have been free in the block anyway, and I don't
think that was the case.

          Followup:

well, they can all be migrated (simple test case below).

it is well within the realm of possible that they would all migrate.  that
"loader" is really bad

ops$tkyte@ORA920LAP> create table t ( x int, y varchar2(4000) ) tablespace
manual pctfree 10;

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> insert into t select rownum, null from all_objects where
rownum <= 660;

660 rows created.

ops$tkyte@ORA920LAP> select dbms_rowid.rowid_block_number( rowid ), count(*)
from t
  2  group by
  3  dbms_rowid.rowid_block_number( rowid )
  4  /

DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)   COUNT(*)
------------------------------------ ----------
                                  98        660

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA920LAP> select chain_cnt, avg_row_len from user_tables where
table_name = 'T';

 CHAIN_CNT AVG_ROW_LEN
---------- -----------
         0           7

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> update t set y = rpad( '*', 1200, '*' );

660 rows updated.

ops$tkyte@ORA920LAP> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA920LAP> select chain_cnt, avg_row_len from user_tables where
table_name = 'T';

 CHAIN_CNT AVG_ROW_LEN
---------- -----------
       660        1216

      Rating: 3
Pls.
      Explain  November 13, 2003
Reviewer:
      A reader  from NJ, USA
Tom,

Assuming the block size is 8k and pctfree is 10%. So free space available for
rows is 8k - 8k * 10% - Blk Overhead which should be around 7300 bytes.

Initially the total bytes occupied after the insert is 660 * 7 (avg_row_len) =
4620 bytes.
So around 2500 bytes will still be available after insert.
The update sets the column Y to 1200 bytes and so the remaining space after the
update of 1st row is 2500 - 1200 = 1300. So the second row will occupy another
1200 bytes which leaves around only 100 bytes in the first block. In this way,
two rows can live in the same block so that the chain count should only be 330.
Can you pls. explain how the chain_cnt became 660 after the update. ie. how all
the rows migrated after the update?

          Followup:

there is overhead for each row -- you are looking at avg_row_len, that is NOT
all that is there.  there is a "are we null or not null byte" for example and
other stuff.

you do not have that free space (else row 661 would fit on that block -- if you
insert 661 rows, you'll find 2 blocks are needed)

November 14, 2003 praveen -- Thanks for the question regarding
      "killing a session", version 8.1.6

      You Asked
How can I disconnect a particular user ? I try oradim –shutdown
at command line.
It shows me no error but doesnot disconnect user. If I have
multiple databases running how can  I disconnect or kill a
particular session of particular database. It can be possible
thorugh dba studio,instance manager but how can do I with sql?

      and we said...
alter system kill session 'sid,serial#';

where the sid and serial# are retrieved from v$session.
Review &
      Followup

      Rating: 4
killing then
      dropping the user  June 12, 2003
Reviewer:
      Phil  from Sydney
tom

I am using this command to kill sessions for a user. I am then wanting to Drop
that user.

as below ...

begin
   for i in (select sid,serial# from v$session where username  = 'UNITTEST' )
loop
        execute immediate 'alter system kill session ' ||''''|| i.sid ||','||
i.serial# ||''' immediate';
   end loop;
end;

drop user UNITTEST cascade;

however I am unable to drop the user because I get an ORA-01940.  When I check
the v$session I see the user a status of killed. At what point can i drop this
user ?.... the session seems to stay there until I go to the client (of
UNITTEST) and perform an action and get told my session has been killed. Then
when I look at the v$session the user has gone. I can then drop that user !

Seems odd but I am sure there is an explanation !

regards

          Followup:

we are waiting for the client application to do something in the database so we
can report back to it "i'm sorry, you are dead, you were killed", rather then
just report back "ora-3113 eof on communication channel" which most people would

interpret as "bug"

      Rating: 4
killing then
      dropping the user  June 12, 2003
Reviewer:
      Phil  from Sydney
thanks

can i get around this i.e. kill the sessions and then drop the user in one
script ?

regards

          Followup:

not really, seems to be an "unusual", "not standard" case here.  you would have
to kill at the OS level, something I would not suggest personally.  It is an
"odd" situation to say the least.

      Rating:
      5
  June 12, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Hi Tom,
In your earlier reply, you said:
<quote>
we are waiting for the client application to do something in the database so we
can report back to it "i'm sorry, you are dead, you were killed"
</quote>

What if the client then somehow vanishes. The client machine crashes or as
happens with few Operating Systems, the client gets a blue screen and presses
the reset button. In this case, will Oracle wait forever for a response from
client just to tell it that it has been killed (given that now the client has
actually already been killed by its own actions)?
Thanks,

          Followup:

yes.  unless tcp ip is kind enough to say "pipe broken"

      Rating: 4
killing then
      dropping the user   June 12, 2003
Reviewer:
      phil  from Sydney
thanks,

reasoning for wanting to do this is that, the database is rebuilt everynight on
the test servers, in order for automatic unit tests to run against the
application and database.

i need a clean database, for my rebuild (no tables, no indexes, no connections,
no users, no roles etc )... as long as there are sessions can I do this ?

regards... sorry if this all sounds very basic !

          Followup:

shutdown abort;
restore from production (test your backup)
startup;

done.

or

shutdown abort;
startup enabling restricted session
drop away
rebuild
turn off restricted session

(i like the "just restore from prod's backups" as it buys you that extra
knowledge that "yes, in fact not only do our backups work but we also know
exactly how to use them")

      Rating: 5
Not the best
      practice but may help  June 12, 2003
Reviewer:
      Jim
Phil,

This is not an ideal solution but given that your
databases are test machines.

I am pretty sure I have a script somewhere that
mimics the user hitting "CNTRL-C" and may do what you want.

I only used it for testing that ungraceful session ends
were hadnled correctly by Oracle and the application
but it may do what you want in that it should kill the session and let you drop
the user immediately

I'll see if I can dig it up

      Rating: 5
No go -
      Sorry  June 12, 2003
Reviewer:  Jim

Sorry Phil,

Just gave my idea a quick test and unfortunately
it behaves like killing the session as described above

Regards
Jim

      Rating: 5
Kill
      Session  September 30, 2003
Reviewer:
      A reader
Tom:

1) what is the difference between "disconnect session" and "kill session" ? The
manuals are not very clear I think...

2) This scenario happens to me:

o alter system kill session
o the session is marked for kill
o no rows selected from v$transaction
o drop user above cascade informs it cannot be droped because user is still
logged on -- though killed

My last resort was issuing a kill -9 in unix against the server process.

What is the rationale here ? I am using 9ir2 on Solaris.

          Followup:

1) disconnect is primarily for TAF (failing over), so you can fail a connection
over from one instance to another.  you can have it do that after the
transaction ends so as to not lose work.  It is "nicer" then kill in that
respect

2) that is the way KILL SESSION works.  the session is dead, killed, resources
released.  However the session remains -- this is so the client process which is
still connected can report gracefully to the end user "your session was killed,
goodbye".  What happened was that user had a session opened (maybe a sqlplus
session).  It was just sitting there -- and would continue to sit there until it
went to the database -- then the session would actually "disappear" -- after the
client got the message.

Otherwise, the client would just (did in your case since you kill -9'd it)
recieve "ora-3113 eof on communication channel".  People would report that as "a
bug"

      Rating:
      5
continued  September 30, 2003
Reviewer:
      A reader
ok, but

1) If it was killed (resources released, etc), why couldn't I drop the user
cascade (the msg was: user was still connected) ?

2) Why should I kill -9 it ? Isn't there another "nicer" way ?

          Followup:

1) because the user was was still connected!  when you kill a session, that
releases the resources.  the session stays and stays killed until the CLIENT
picks up the fact that it was killed.

2) you could have exited the client application this user was running, that
would have done it.

      Rating: 3
Killing a
      session  October 15, 2003
Reviewer:  Sandy
       from CA, USA
We are using Oracle 92.

In our application, we drop and create Oracle users to support multiple dataset
manipulation (each dataset is loaded into separate Oracle user). Our app users
can have multiple datasets in multiple Oracle users.

when application user closes one dataset we "exp" data.
when the user tries to load it again, we drop and create Oracle user, do "imp"
of "exp" data, then recreate indexes and seq.

Some times we are facing this session issue and the scripts are not able to drop
the user and create the Oracle user

Can you suggest a single script to kill the sessions of a Oracle user and drop
the user?

thanks,
Sandy

          Followup:

nope, not really.

you can kill the user session -- but it'll still be there (just killed).

suggestion: don't drop the user, just clean the schema out if you want.

      Rating: 3
Dropping
      connected user.  November 14, 2003
Reviewer:
      Robert  from Eire
Hi Tom
We are using oracle proxy authentication connection pooling.  Part of our
application has the ability to drop the user if he is no longer required.

However, we are getting an error where pseudo connections are sometimes being
left open by the connection pool and so we can't drop the user.  The code is in
the connection pool to close the proxy connection after sql execution and it
works when we test it normally so we reckon it only happens when something
strange happens (we cannot replicate it).

Is there anyway we can ensure closure of these connections at all times seeing
as we cannot force the dropping of connected users?

Thanks.
Robert.

          Followup:

well, to be dropping users on a regular basis like that seems "strange" -- but
you have a bigger issue here -- you need to figure out why and where you are
keeping a connection going.  sounds like an un-handled exception in your java
code -- you grab a connect and then "something bad happens" and you've just
orphaned that connection (it is lost forever, until you bounce the app server).

maybe you need a "when others" like handler in your java code, to make sure the
connections are not getting "leaked" like that.

November 14, 2003 V Siva -- Thanks for the question regarding "Can
      UTL_FILE package used to create ASCII files", version 7.3.2.3.0

      You Asked
Hi tom

Earlier I send question How to extract data from tables to text
files. In this connecton, I have another question. Can I use
UTL_FILE package to create ASCII files.

Thanks
Siva

      and we said...
Absolutely - in fact UTL_FILE can only create ascii (as
opposed to binary) text files.  Here is an example of a
procedure that takes most any query and will dump it to a
delimited file of your choice:

ops$tkyte@8i> set echo on
ops$tkyte@8i> set serveroutput on
ops$tkyte@8i>
ops$tkyte@8i> create or replace
  2  function  dump_csv( p_query        in varchar2,
  3                      p_separator in varchar2 default ',',
  4                      p_dir       in varchar2 ,
  5                      p_filename  in varchar2 )
  6  return number
  7  is
  8      l_output        utl_file.file_type;
  9      l_theCursor     integer default dbms_sql.open_cursor;
 10      l_columnValue   varchar2(2000);
 11      l_status        integer;
 12      l_colCnt        number default 0;
 13      l_separator     varchar2(10) default '';
 14      l_cnt           number default 0;
 15  begin
 16      l_output := utl_file.fopen( p_dir, p_filename, 'w' );
 17
 18      dbms_sql.parse(  l_theCursor,  p_query,
 19                                           dbms_sql.native );
 20
 21      for i in 1 .. 255 loop
 22          begin
 23              dbms_sql.define_column( l_theCursor, i,
 24                                      l_columnValue, 2000 );
 25              l_colCnt := i;
 26          exception
 27              when others then
 28                  if ( sqlcode = -1007 ) then exit;
 29                  else
 30                      raise;
 31                  end if;
 32          end;
 33      end loop;
 34
 35      dbms_sql.define_column( l_theCursor, 1,
 36                              l_columnValue, 2000 );
 37
 38      l_status := dbms_sql.execute(l_theCursor);
 39
 40      loop
 41          exit when ( dbms_sql.fetch_rows(l_theCursor) <= 0
);
 42          l_separator := '';
 43          for i in 1 .. l_colCnt loop
 44              dbms_sql.column_value( l_theCursor, i,
 45                                     l_columnValue );
 46              utl_file.put( l_output,
 47                            l_separator || l_columnValue );
 48              l_separator := p_separator;
 49          end loop;
 50          utl_file.new_line( l_output );
 51          l_cnt := l_cnt+1;
 52      end loop;
 53      dbms_sql.close_cursor(l_theCursor);
 54
 55      utl_file.fclose( l_output );
 56      return l_cnt;
 57  end dump_csv;
 58  /

Function created.

ops$tkyte@8i>
ops$tkyte@8i> declare
  2      l_rows    number;
  3  begin
  4      l_rows := dump_csv( 'select *
  5                             from all_users
  6                            where rownum < 5',
  7                          ',',
  8                          '/tmp',
  9                          'test.dat' );
 10      dbms_output.put_line( to_char(l_rows) ||
 11                            ' rows extracted to ascii file'
);
 12  end;
 13  /
4 rows extracted to ascii file

PL/SQL procedure successfully completed.

ops$tkyte@8i>
ops$tkyte@8i> host cat /tmp/test.dat
SYS,0,20-APR-99
SYSTEM,5,20-APR-99
OUTLN,11,20-APR-99
DBSNMP,18,20-APR-99

Review &
      Followup

      Rating:
      4
Thanks  June 04, 2001
Reviewer:
      A reader

      Rating: 4
CSV
      Dump  July 12, 2001
Reviewer:  Jessica
      Wilson  from Winston-Salem, NC
I was able to modify the code in a short period of time to what my company
needed done.

      Rating: 4
CSV
      Dump  July 12, 2001
Reviewer:  Jessica
      Wilson  from Winston-Salem, NC
I was able to modify the code in a short period of time to accomplish what my
company needed done.

      Rating: 5
function
      dump_csv  February 05, 2002
Reviewer:  Charles
      Bankes  from Surrey , UK
Superb solution

Is it possible using native dynamic sql though?

          Followup:

no, you need to know at compile time how many / what type of outputs you are
getting from NDS with plsql.  You need the procedural access afforded by
dbms_sql.

what would NDS buy you here?

      Rating: 5
Excellent
      Procedure  April 01, 2002
Reviewer:  Prakash
       from Mumbai, India
The solution is terrific. Can we have generic procedure to print output in fixed
length format.

          Followup:

read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:650224141571
....

      Rating:
      4
  April 02, 2002
Reviewer:
      A reader
is there any way to create directory with utl_file

          Followup:

No, there is not.

You would typically use a java stored procedure to perform that operation.

The "bad" news is that even if utl_file could create a directory -- it would
require a database RESTART in order to utl_file to read or write files into it.

      Rating: 5
UTL_FILE
      package used to create ASCII files- function dump_csv  April
      23, 2002
Reviewer:  vivek shenoy  from Mumbai

Thanks Tom for the great function for dumping file in
ascii format.

i require little help in the matter. i could dump my file
in ascii in the Server (we are using UNIX  platform) from
the clint side.  But i would like to dump the ascii file
at the client side.  how to do it.

          Followup:

http://osi.oracle.com/~tkyte/flat/index.html

      Rating:
      4
Success!!  June 27, 2002
Reviewer:
      jimmy  from Malaysia
I did this successfully..
The UTL_file_dir wasnt setup properly.
That was the problem.
I need to know some more information.
I want to retrieve data from database on some criteria and will be concatinating
it to a single string and have to move LOB file and from the LOB i should move
to a .csv file.
How should I accomplish this. Can you provide the sample.
Thanx a lot!!

          Followup:

You can use dbms_lob.substr to pick off pieces of your lob and utl_file to write
these pieces.

      Rating:
      4
Bfile  June 27, 2002
Reviewer:
      Jimmy  from Malaysia
Does the BFILE is a read-only??
Then how to create a BFILE where it is coming from??
How it is generated??

          Followup:

You put a file on the server.

You create a bfile object (which is nothing more then a directory and a
filename).

How does the file get to the server?  Anyway you would like to get it there --
that is outside of our control.

      Rating: 4
UTL_FILE
      buffer  June 27, 2002
Reviewer:  jimmy
       from MALAYSIA
I have a query regarding the buffer size of UTL_FILE.
This has a limitation of 32787.
I am reading a value thru cursor and PUTing into the UTL_FILE record-by-record
(cursor facility).
Once the record is PUT using UTL_FILE.PUT command. After this command will be
buffer will be flushed automatically or do I need to do it explicitly?

          Followup:

It'll be flushed with the internal IO buffer fills, or you call utl_file.fflush,
or you close the file.

      Rating: 3
The exact
      behavior of FFLUSH  July 30, 2002
Reviewer:
      Andrew  from ca, usa
Tom
  /*
  ** FFLUSH - Force physical write of buffered output
  **
  ** IN
  **   file - File handle (open in write/append mode)
  ** EXCEPTIONS
  **   invalid_filehandle - not a valid file handle
  **   invalid_operation  - file is not open for writing/appending
  **   write_error        - OS error occured during write operation
  */

I have a situation where I use utl_file to write a file and immediately after
that I call a Java stored procedure to access the file.

My question is - when I FFLUSH (and then close) a file, is it simply written to
the OS buffer - or is it physically flushed to disk by the OS too. I want to
ensure that the file is visible to the Java stored proc. I want to avoid having
to include a sleep() to wait for the file to become ready. The OS is Solaris.

          Followup:

You need not fflush if you are going to close.

As soon as you close the file, Java can "see it" -- definitely.  It is most
likely buffered by the OS in some way but that is beyond our control.

Close it and you can see it.

      Rating: 5
Handling the
      Tab separated data in a FIle  October 28, 2002

Reviewer:  Siva Kumar  from Bangalore , India

Hi Sir,

I have a problem like in  a file I will the header and contect , first line is
the header and from the second line data will be there . The data will be in tab
separated data.
Please let me know How do I read the header which contains some column names to
load the data in to the database , based on the header how can I handle the tab
separated data using UTL_FILE and load into the database . Or any other way to
do it .... Please it is very critical for me and very urgent . please give me
some tips to handle and if you any example of code to handle it let me know
.......

I will be grateful  if you help me on this regard

Thanks
Siva Kumar

          Followup:

If you know how to write procedural code, implement algorithms and logic -- and
you know PLSQL, this is something you need to do.  I mean, come on -- you have
an example that loads delimited data above, a 100% implementation, and you
cannot modify it to fit your needs????

If you do not know how to write procedural code, implement algorithms and logic,
you need to hire someone that does to help you out.

      Rating: 5
how to handle
      tab separated data using UTL_FILE  October 28, 2002

Reviewer:  Siva Kumar  from BANGALORE,INDIA

Hi Sir,

I have a problem like in  a file how to read the tab separated data using
UTL_FILE package.

Thanks
Siva Kumar

          Followup:

read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:464420312302
use chr(9)  -- the tab character -- as the delimiter.

      Rating:
      2
dump_csv  October 30, 2002
Reviewer:
      Paige  from Tyson's Corner, VA
I've been using the dump_csv function to extract a temp table from my database
for use as a text file for a word merge.  It's great.  However, I have one small
problem, MS Word keeps using the first row of data as the header row.  Is there
an easy way to modify the dump_csv function so that the text file is created
with the temp table column names as a header row in the text file?

thanks:)

          Followup:

You got lucky- i already made this modification for something else a while ago:

ops$tkyte@ORA920.US.ORACLE.COM> create or replace procedure dump_table_to_csv(
p_tname in varchar2,
  2                                                 p_dir   in varchar2,
  3                                                 p_filename in varchar2 )
  4  authid current_user  /* <<<== if you want... runs as "invoker" and runs
with ROLES */
  5  is
  6      l_output        utl_file.file_type;
  7      l_theCursor     integer default dbms_sql.open_cursor;
  8      l_columnValue   varchar2(4000);
  9      l_status        integer;
 10      l_query         varchar2(1000)
 11                      default 'select * from ' || p_tname;
 12      l_colCnt        number := 0;
 13      l_separator     varchar2(1);
 14      l_descTbl       dbms_sql.desc_tab;
 15  begin
 16      l_output := utl_file.fopen( p_dir, p_filename, 'w' );
 17      execute immediate
 18      'alter session set nls_date_format=''dd-mon-yyyy hh24:mi:ss'' ';
 19
 20      dbms_sql.parse(  l_theCursor,  l_query, dbms_sql.native );
 21      dbms_sql.describe_columns( l_theCursor, l_colCnt, l_descTbl );
 22
 23      for i in 1 .. l_colCnt loop
 24          utl_file.put( l_output,
 25              l_separator || '"' || l_descTbl(i).col_name || '"' );
 26          dbms_sql.define_column( l_theCursor, i, l_columnValue, 4000 );
 27          l_separator := ',';
 28      end loop;
 29      utl_file.new_line( l_output );
 30
 31      l_status := dbms_sql.execute(l_theCursor);
 32
 33      while ( dbms_sql.fetch_rows(l_theCursor) > 0 ) loop
 34          l_separator := '';
 35          for i in 1 .. l_colCnt loop
 36              dbms_sql.column_value( l_theCursor, i, l_columnValue );
 37              utl_file.put( l_output, l_separator || l_columnValue );
 38              l_separator := ',';
 39          end loop;
 40          utl_file.new_line( l_output );
 41      end loop;
 42      dbms_sql.close_cursor(l_theCursor);
 43      utl_file.fclose( l_output );
 44
 45      execute immediate 'alter session set nls_date_format=''dd-MON-yy'' ';
 46  exception
 47      when others then
 48          execute immediate 'alter session set nls_date_format=''dd-MON-yy''
';
 49          raise;
 50  end;
 51  /

Procedure created.

ops$tkyte@ORA920.US.ORACLE.COM> exec dump_table_to_csv( 'SCOTT.EMP', '/tmp',
'tkyte.emp' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> host cat /tmp/tkyte.emp
"EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO"
7369,smith,CLERK,7902,17-dec-1980 00:00:00,800,,20
7499,allen,SALESMAN,7698,20-feb-1981 00:00:00,1600,300,30
7521,ward,SALESMAN,7698,22-feb-1981 00:00:00,1250,500,30
7566,jones,MANAGER,7839,02-apr-1981 00:00:00,2975,,20
7654,martin,SALESMAN,7698,28-sep-1981 00:00:00,1250,1400,30
7698,blake,MANAGER,7839,01-may-1981 00:00:00,2850,,30
7782,clark,MANAGER,7839,09-jun-1981 00:00:00,2450,,10
7788,scott,ANALYST,7566,19-apr-1987 00:00:00,3000,,20
7839,king,PRESIDENT,,17-nov-1981 00:00:00,5000,,10
7844,turner,SALESMAN,7698,08-sep-1981 00:00:00,1500,0,30
7876,adams,CLERK,7788,23-may-1987 00:00:00,1100,,20
7900,james,CLERK,7698,03-dec-1981 00:00:00,950,,30
7902,ford,ANALYST,7566,03-dec-1981 00:00:00,3000,,20
7934,miller,CLERK,7782,23-jan-1982 00:00:00,1300,,10

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

      Rating: 4
File Handle is
      assignable ?  November 11, 2002
Reviewer:
      Robert  from PA
Tom, File handles are totally assignable, right ?
e.g:

l_Read       UTL_FILE.FILE_TYPE ;
l_OutSouth   UTL_FILE.FILE_TYPE ;
l_OutNorth   UTL_FILE.FILE_TYPE ;
l_OutTemp    UTL_FILE.FILE_TYPE ;
l_line       varchar2(1000);

BEGIN
l_Read :=
UTL_FILE.FOPEN('/user/home', 'usa.txt', 'R');

l_OutSouth :=
UTL_FILE.FOPEN('/user/home', 'south.txt', 'W');

l_OutNorth :=
UTL_FILE.FOPEN('/user/home', 'north.txt', 'W');

-- Loop thru and read each line
.....snip
IF SUBSTR(l_line, 10, 2) := 'VA' THEN
   l_OutTemp := l_OutSouth ;
ELSIF SUBSTR(l_line, 10, 2) := 'MA' THEN
   l_OutTemp := l_OutNorth ;
......snip

END IF;

UTL_FILE.PUT_LINE (l_OutTemp, l_line);
.......

END;

I want to verify with you that the file handle assignment
code:

l_OutTemp := l_OutSouth ;
l_OutTemp := l_OutNorth ;

are totally legal, and that file handle can be treated just like any
assignable/passable variable.

It's just I don't think I ever read about this specific action being OK.
I tested this and it seems to work as I expected.

Thanks

          Followup:

yes, they are just record structures -- they can be assigned.

      Rating: 4
UTL_FILE
      package   November 18, 2002
Reviewer:  Awesh
      Ahmad  from USA
Hi Tom,
I used UTL_FILE package example given by you and modify the code very easily as
per my requirement. Thanks for your valuable information. But I have one
problem, In my most of the tables few fields are notes around varchar2(2000) and
user had pressed ENTER key "carriage return-line feed" and because of this rest
of the fields of record started in new lines. For e.g below user entered "Test
for data before any stock splits." and then press enter and added "This is test
for pressing the "Enter" key.,". Output of .dat file is

110269,,Test - Jann 3.0,1,01-JAN-97,2,Test for data before any stock splits.

This is test for pressing the "Enter" key.,

          Followup:

Uh huh - and what would you like to have happen here?

You can use translate(l_output , chr(13)||chr(10), 'XY' )

to have carriage returns (chr(13)) turned into X (or whatever of course) and
linefeeds (newlines) turned into Y (or whatever)

      Rating:
      5
UTL_FILE  November 19, 2002
Reviewer:
      Awesh Ahmad  from USA
Thanks Tom,

I did change and working fine. But I have one more problem.My table size is very
big having >50 columns and one is notes field of varchar2(4000) and few of them
are varchar2 50-100 size. It's working for records those have few characters in
notes field but failing if notes field is bigger. is there any limit of file
size. Could you please help how to solve this issue. I am getting following
error
ORA-06510: PL/SQL: unhandled USER-defined EXCEPTION
ORA-06512: AT "SYS.UTL_FILE", line 100
ORA-06512: AT "SYS.UTL_FILE", line 222
ORA-06512: AT "EPROSPER.DUMP_TABLE_TO_CSV", line 46
ORA-06510: PL/SQL: unhandled USER-defined EXCEPTION
ORA-06512: AT line 2
Thnaks

          Followup:

 16      l_output := utl_file.fopen( p_dir, p_filename, 'w', 32000 );
 17

your output line is probably exceeding 1022 bytes -- the default max width.
change the open call.

      Rating: 5
Very
      Helpful  November 21, 2002
Reviewer:  Mike
       from St. Louis, Missouri
Tom:

This and other threads on utl_file have been very help to me.  Thank you.

However, I still have a high-level question regarding concurrency and the use of
utl_file.  My assumption is that the file opened by ult_file is only meant for a
single writer.  If my assumption is correct then should I worry about below
local code fragment using only USER to uniquely identify the file:

    -- Open file using newer fopen() to get around 1KB max. line size.
    m_sFileName := ‘cool_tool_’ || lower( user ) || ‘.txt’;
    m_hFile := utl_file.fopen( m_sFileDir, m_sFileName, m_sFileOpenMode, 32767
);

If user JOE is running multiple sessions then obviously the file name:
“cool_app_joe.txt is non-unique.  Will utl_file attempt multiple concurrent
opens/writes on the same file?  Are we using a poor file naming practice or is
it OK (i.e. maybe ult_file.putf waits for the blocking transaction to close the
file)?

I’m curious about your opinion on this.

Thanks for your time,

-Mike

          Followup:

yes it will (attempt and on many platforms succeed) in doing multiple file opens
on that same file.

If each SESSION is to have a unqiue file, use

  'cool_tool_' || userenv('sessionid') || '.txt'

instead.

      Rating: 5
Thanks
      again  November 25, 2002
Reviewer:  MIke
       from St. Louis
Tom:

> yes it will (attempt and on many platforms succeed)
> in doing multiple file opens on that same file.

Slick!  Another great Oracle feature.

I'm curious if you think it's a valid PL/SQL design direction (at our site) to
use UTL_FILE without worrying about the implimentation details of multiple
writers or multiple sessions opening the same file?  Delegate that to ULT_FILE
and cease to worry about unique file names/session?

Thanks,

-Mike

          Followup:

actually -- this:

> yes it will (attempt and on many platforms succeed)
> in doing multiple file opens on that same file.

is a bad thing -- happens all of the time on unix unless you explicitly use file
locking.  *its a bad thing*

You cannot delegate that to utl_file, sorry if my other answer wasn't clear.
You'll end up with garbage in that file -- one session will see another sessions
stuff.  It'll be nasty.  Thats why I recommend:

If each SESSION is to have a unqiue file, use

  'cool_tool_' || userenv('sessionid') || '.txt'

instead.

to AVOID that.

      Rating:
      5
Thanks  November 25, 2002
Reviewer:
      Mike  from St. Louis
Tom: this is crystal clear now.  Thank you.  -Mike

      Rating:
      5
utl_File  January 06, 2003
Reviewer:
      mo
Tom:

I have a requirement to convert several C programs that were used with informix
to create query and create fixed width data on unix to ones that work with
oracle.

Now, I am not familiar with C but looking at your above solution it looks to me
that it is much easier for me to take the queries from C program and plug them
into the procedure and get my output files.

Do you agree or there is something I am missing?

Thank you,

          Followup:

an informix esql c program should run almost unchanged (or at least the changes
will be the same time after time).

C is faster.

If you want a generic C program that dumps data -- you would have to modify it
-- it would be:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:459020243348
it dumps delimited data -- simply mods you can make to have it be fixed width.

      Rating:
      5
utl_file  January 06, 2003
Reviewer:
      mo
Tom:

OK but Can  I still  take the query from the C program, plug it into the above
procedure using utl_file and create an ASCII text file in unix for the query
results?

          Followup:

Yes, of course -- think about it -- it is just a stored procedure to dump a
query.

I'm afraid however people will look at it and say "oh, informix was faster"
cause that c program will be much faster.

      Rating: 5
utl
      file  January 06, 2003
Reviewer:  mo

TOm:

We are only talking about a database with hundreds of records. They should not
notice anything. PL/sql is more flexible and easier in my opinion.

Is C faster bcause it is already compiled to binary and you just run it, rather
than pl/sql which is interpreted language and you always have to compile/run?

          Followup:

C is faster because in this case, it is C and the major time consumption spent
on dumping to flat files is in the writing of the data -- something C is
slightly more efficient than PLSQL at doing.

      Rating: 5
fixed width
      text  January 06, 2003
Reviewer:  mo

Tom:

Your example show comma-delimited output. If I want to create fixed width data
output would I just take the ',' from the p_separator.

THanks

          Followup:

umm, no.  that would not do it unless you passed it a query like:

select rpad( decode( c1, null, ' ', c1 ), 20 ) ||
       rpad( decode( c2, null, ' ', c2 ), 15 ) ||
       .......
  from t;

then the routine will work "as is" unchanged.  If you just want to pass it:

select * from t;

and have it figure out how wide each field should be -- you'll be writing some
more code.

      Rating: 5
spool
      file  January 07, 2003
Reviewer:  mo

Tom:

Can't you get the same result by creating SQL*plus script where you simply spool
output to a file and then run the query.

To run both ways (utl_file or sqlplus script) from unix prompt you have to use
sqlplus anyway?

Thanks

          Followup:

see

http://asktom.oracle.com/~tkyte/flat/index.html
I have solutions using sqlplus, plsql, C.  I'm an equal opportunity tools user.

      Rating: 3
Can UTL_FILE
      package used to create ASCII files  January 08, 2003

Reviewer:  Ramesh Golkonda  from U.K
I thought writing to a text file using UTL_FILE package is much much slower than
Java Print statement because each "UTL_FILE.write" has to go to operating system
and append the file. Tom could shed some light if iam wrong.

Thanks
RameshG

      Rating: 5
Esql
      prgram  January 08, 2003
Reviewer:  mo

Tom:

I found that the C/informix program had fours cursors and more logic to create
each record in the final output. There is a lot of literal insertions based on
values of each record.

It seems to me now that utl_file is not the right tool since it is only
expecting one sql query. Am I wrong?

2.  You said that the C code should work with oracle.  Would I test that out by
running the DLL file from the unix prompt?  DO I need to read your chapter on
External Procedure s to figure out how to convert ESQL to PROC or PLSQL?

3. Can you point me to any reference material/books on conversion of esql to
proc or plsql.

Thank you,

          Followup:

1) i don't know -- use your power of analysis to determine the right path.  It
is our (your) job.  This is just really an exercise in programming after all.

2) You would read the pro*c documentation and port it to pro*c.  it would not be
an external routine, it is a command line, standalone program

3) they (pro*c and esql-c) are basically the same.  See otn.oracle.com, search
for esql

porting to plsql is known as "rewrite"

      Rating:
      5
utl_file  January 14, 2003
Reviewer:
      mo
Tom:

I am testing utl_file trying to write to a file in windows and I get an erros.
I also got it in unix. Any ideas?

  1  declare
  2          l_rows    number;
  3      begin
  4          l_rows := dump_csv( 'select *
  5                                 from all_users
  6                                where rownum < 5',
  7                              ',',
  8                              'c:\tmp',
  9                              'test.dat' );
 10         dbms_output.put_line( to_char(l_rows) ||
 11                               ' rows extracted to ascii file' );
 12* end;
 13  /
declare
*
ERROR at line 1:
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at "SYS.UTL_FILE", line 98
ORA-06512: at "SYS.UTL_FILE", line 157
ORA-06512: at "IMSADMIN.DUMP_CSV", line 15
ORA-06512: at line 4

          Followup:

and so, what is your utl_file_dir init.ora set to?

show parameter utl_file_dir

what does that return in plus/svrmgrl?

      Rating:
      5
utl_file  January 14, 2003
Reviewer:
      mo
Tom:

I added exceptions to check where the error is coming from and here are the
results.  I did change the parameter utl_file_dir.

SQL> exec test_utl;
BEGIN test_utl; END;

*
ERROR at line 1:
ORA-20003: INVALID_OPERATION: The file could not be opened or
operated on as requested.
ORA-06512: at "IMSADMIN.TEST_UTL", line 19
ORA-06512: at line 1

          Followup:

so, what is your utl_file_dir

      Rating:
      5
utl_file  January 14, 2003
Reviewer:
      mo
Tom:

I believe it is set to /tmp (not on server now).

but also I was trying to write to a local file on my pc.  Would I be able to do
this or I can not add a local directory to the parameter too?

          Followup:

but you are writing to c:\tmp...

utl_file writes to the SERVER only -- the file system must be visible to the
Oracle server process.  Your local filesystem, probably not visible.  You need a
CLIENT tool to write CLIENT files.

      Rating:
      5
utl_file  January 15, 2003
Reviewer:
      mo
Tom:

Here is my parameter. IT still does not work.

SQL> show parameter utl_file_dir

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
utl_file_dir                         string  /ppcs
IMSD> execute test_utl_file;
BEGIN test_utl_file; END;

      *
ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00201: identifier 'TEST_UTL_FILE' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

PROCEDURE TEST_UTL as
l_rows   number;
begin
  l_rows  := dump_csv('select * from all_users where rownum
<5',',','/ppcs','test.dat' );
dbms_output.put_line(to_char(l_rows) || 'rows extracted to ascii file' );
end;

          Followup:

read the error message again -- ok....

procedure test_utl

identifier 'TEST_UTL_FILE' must ....

(slow down...)

      Rating:
      5
utl_file  January 15, 2003
Reviewer:
      mo
Tom:

sorry I copied the wrong text.

SQL> execute test_utl;
BEGIN test_utl; END;

*
ERROR at line 1:
ORA-20003: INVALID_OPERATION: The file could not be opened or
operated on as requested.
ORA-06512: at "IMSADMIN.TEST_UTL", line 19
ORA-06512: at line 1

          Followup:

see the error - perhaps the oracle account does not have rwx on that directory
or the file exists and oracle doesn't have rw- on it.

ops$tkyte@ORA817DEV> set echo on
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> !rmdir /export/home/tkyte/tmp
rmdir: directory "/export/home/tkyte/tmp": Directory not empty

ops$tkyte@ORA817DEV> !mkdir /export/home/tkyte/tmp
mkdir: Failed to make directory "/export/home/tkyte/tmp"; File exists

ops$tkyte@ORA817DEV> !chmod a=,u=rwx /export/home/tkyte/tmp

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> declare
  2          l_output                utl_file.file_type;
  3  begin
  4          l_output := utl_file.fopen( '/export/home/tkyte/tmp', 'foo.txt',
'w', 32765 );
  5          utl_file.fclose( l_output );
  6  exception
  7          when utl_file.invalid_operation then
  8                  raise_application_error( -20003, 'lose' );
  9  end;
 10  /
declare
*
ERROR at line 1:
ORA-20003: lose
ORA-06512: at line 8

ops$tkyte@ORA817DEV> !chmod a=rwx /export/home/tkyte/tmp

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> declare
  2          l_output                utl_file.file_type;
  3  begin
  4          l_output := utl_file.fopen( '/export/home/tkyte/tmp', 'foo.txt',
'w', 32765 );
  5          utl_file.fclose( l_output );
  6  end;
  7  /

PL/SQL procedure successfully completed.

      Rating:
      5
utl_file  January 15, 2003
Reviewer:
      mo
Tom:

I wonder if the above problem is related to access privileges to the /ppcs
directory. When I telnet to it and create a text file it works.  However I am
logging in with a unix user account.  From oracle I do not know how is oracle
logging into unix or how unix checks for access privileges for that utl_file
user?

      Rating:
      5
utl_file  January 15, 2003
Reviewer:
      mo
Tom:

IT works.

SQL> !chmod a=rwx /ppcs

SQL> execute test_utl

PL/SQL procedure successfully completed.

SQL> !cat test.dat
SYS,0,10/12/2001
SYSTEM,5,10/12/2001
OUTLN,11,10/12/2001
IMSADMIN,12,10/12/2001

How is oracle connecting to unix. What account does it use. Is there a way to
know oracle user privileges on unix.

Thanks a lot,

      Rating: 5
What account
      does it use...  January 15, 2003
Reviewer:  Mark
      A. Williams  from Indianapolis, IN USA
Mo,

From the documentation for utl_file is this:

On UNIX systems, the owner of a file created by the FOPEN function is the owner
of the shadow process running the instance. Normally, this owner is ORACLE.

So, the answer to the question "what account does it use?" is "the oracle
software account."  As Tom said two answers above.  The 'ls' command will show
"owner", "group", and "other" permissions for directories and files, so you can
use it to surmise "oracle user privileges on unix"...

HTH,

Mark

      Rating: 4
NOrmally owner
      is oracle, but not always  January 15, 2003

Reviewer:  paul
On Sequent/dynix, and I suppose other unix, if you connect via sqlnet the shadow
process is owned by oracle.  If your client runs on the database server you have
the option of not using sqlnet, instead using what I vaguely remember as named
pipes, in which case the shadow process is run as the owner of the client
process.  More or less the process involves setting ORACLE_SID, running ora_env,
then connecting to oracle without using "@dbalias" in the connect string.  I
(vaguely again) recall that this process ownership behavior is optional,
controlled somehow by an Oracle parameter.   I prefer this mode especially in
development, because it allows one to clean up one's own shadow processes as
needed.  Not that I ever need to, of course.

I haven't used utl_file, but perhaps this would cause files to be created as a
user other than oracle.

      Rating: 4
Actually,
      always owned by oracle  January 15, 2003
Reviewer:
      A reader
Out of curiosity, did a small test, connected locally, shadow process owned by
me (pkelley) not by oracle.  The utl file output file is still owned by oracle.

onyx 212% whoami
pkelley
onyx 213% cora TS
onyx 214% sqlplus blah/blah

SQL*Plus: Release 8.1.7.0.0 - Production on Wed Jan 15 15:33:36 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.3.0 - Production
With the Partitioning option
JServer Release 8.1.7.3.0 - Production

SQL> !ps -ef | grep pkelley | egrep "sqlplus|oracle"
  pkelley 15787 15176   1  15:33:36 ttyAF/AGFB  0:00 sqlplus blah/blah
  pkelley 15788 15787   0  15:33:36 ?           0:00 oracleTS
(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))

SQL> set echo on
SQL> @utlfile
SQL> declare
  2
  3      l_write utl_file.file_type;
  4
  5  begin
  6
  7   l_write := utl_file.fopen('/usr/local/logs','delete.me.pkelley','W');
  8
  9   utl_file.put_line(l_write, 'delete.me - but first ,who owns the file');
 10
 11   utl_file.fclose(l_write);
 12  end;
 13  /

PL/SQL procedure successfully completed.

SQL>
SQL>
SQL> !ls -ltr /usr/local/logs/delete.me.pkelley
-rw-rw-r--    1 oracle   oracle        41 Jan 15 15:34
/usr/local/logs/delete.me.pkelley

So, even though I owned the shadow process, oracle owned the output file.

          Followup:

that is because the oracle binary will be "setuid" -- else it cannot attach the
shared memory segment.  The oracle binary runs with an effective uid of the
Oracle software.

$ ls -l $ORACLE_HOME/bin/oracle

-rwsr-s--x    1 ora920   ora920   49501171 Nov 26 13:33 oracle

      Rating: 5
but how to
      write from unix to NT  February 04, 2003
Reviewer:
      Tong Siew Pui  from SIN
I want to UTL_FILE (from unix) and output an ascii text file to an NT machine
directly, no ftp, no nothing, using nfs.
I already tried this, no pbm.
The file need to be viewed as any normal text file,
on a notepad, or opened in Excel, or any other application.

The pbm is, there is this special character at the end of each line (I think it
is a ^M).
I know I can use unix2dos to change/modify the file to remove the special
character, so that everything look OK on a notepad.

But, is there anyway that I can achive the same by doing it in one step, by
using 'UTL_FILE' alone, without using "unix2dos" or any other additiional steps
??

The text output program is to be a monthly automatic process. I do not wish to
call an external program to perform the stripping job.

thanks

          Followup:

try

utl_file.put_line( your_text || chr(13) );

We'll put the linefeed (char(10)),  bill gates long ago decided we needed two
characters CARRIAGE_RETURN/LINE_FEED  (chr(13)||chr(10)) to terminate a line.
Add the chr(13) and they windows people may be apeased.

      Rating: 5
Is any work
      around to write a file to local system  February 04, 2003

Reviewer:  Ashiq Shamsudeen  from Trichy, Tamil Nadu
      , India
Hello Tom,

from ur quote

"utl_file writes to the SERVER only -- the file system must be visible to the
Oracle server process.  Your local filesystem, probably not visible.  You need a

CLIENT tool to write CLIENT files. "

   My requirement is to write a file to any systems in the network.Is it any
work around way to accomplish this??Because FE(Front End) guys are requesting
this option and they're saying if this is possible in SQL server .In sql server
they're using BCP command ( i don't know how the way it works in SQL server and
i doesn't want to know also).
  The same functionallity they're asking in oracle.Is it possible thru PL/SQL or
any  other way??If yes give me example to get thru.
  From ur quote u said Clent tools are required to do,what do u  mean by Clent
tool??

   DB is Oracle8i(8.1.7) on NT.

          Followup:

besides a virus?

bcp is just a tool that runs on a client, it dumps data.

sqlplus is a tool we have that runs on a client.  it can be used to accomplish
the same feat of magic (which is NO MAGIC, they run bcp on a machine that can
see the file system, they are not running bcp from a server and writing to any
filesystem anywhere)

So, you need the client tools installed (just as you would with sqlserver to get
bcp, a client tool).  then just run sqlplus.

If you need a flat file (comma delimited) see:

http://asktom.oracle.com/~tkyte/flat/index.html
has a sqlplus script to do that nicely.

      Rating: 5
Again writing a
      file to local system   February 04, 2003
Reviewer:
      Ashiq Shamsudeen  from Trichy, Tamil Nadu, India
Hi Tom,

  As u  said they're not running bcp from server ,but what they're doing here is
writing file to another machine.To be clear there're 3 nodes A, B , and
C.They're running bcp in node A and writing the file to the folder of Node B.Of
course directory where they going to write should be shared.
  Here they run bcp from node A and write to the directory in node B(for eg
\\b\output) they'll writeand again they change the direcoty to write the file
from node B to C (for eg   \\c\output)
  Is this same functionallity possible in sqlloader.Where we can specify the
directory to write.If it is not possible then how can we do achieve this in
oracle??

          Followup:

that is using NETWORK DISKS.

if you can do \\b\output \\c\output -- you can write to it as well with
anything.

that is just a unc filename, a NETWORK DISK -- like NFS

      Rating: 5
how not to use
      unix2dos  February 04, 2003
Reviewer:  tong siew
      pui  from SIN
thanks very much.
your solution works.

thanks.

      Rating: 4
Dynamically
      pass sql statement  April 03, 2003
Reviewer:
      A reader
Tom,

I am trying to pass variable to dump_csv function, like that

v_sql:='select * from my_table where datecompleted between
:p_start_date and :p_end_date' using
p_start_date,p_end_date.

But it does not seem to be working...
So, is it possible to pass sql into dump_csv function, where
in sql you'd change dates dynamically?

Much Thanks

          Followup:

you could:

create context my_ctx using your_procedure;

and then in your_procedure code:

...

dbms_session.set_context
( 'my_ctx', start_date, to_char( p_start_date, 'dd-mon-yyyy' ) );
dbms_session.set_context
( 'my_ctx', end_date, to_char( p_end_date, 'dd-mon-yyyy' ) );

v_sql := 'select *
            from my_table
           where datecompleted between
             to_date( sys_context(''my_ctx'',''start_date''), ''dd-mon-yyyy'' )
             and
             to_date( sys_context(''my_ctx'',''end_date''), ''dd-mon-yyyy'' )';

x := dump_csv( v_sql, ....

OR
you can code:

execute immediate 'alter session set cursor_sharing=force';

v_sql := 'select *
            from my_table
           where datecompleted between
             to_date( ''' || to_char( p_start_date, 'dd-mon-yyyy' ) || '
                       , ''dd-mon-yyyy'' )
             and
             to_date( ''' || to_char( p_end_date, 'dd-mon-yyyy' ) || '
                       , ''dd-mon-yyyy'' );

dump_csv( v_sql ....

execute immediate 'alter session set cursor_sharing=exact';

I'm partial to the first one with sys_context, it is what I use on this site in
fact.

      Rating:
      4
  April 03, 2003
Reviewer:
      A reader
Tom,

I am getting following message:
15:31:57  Execution failed: ORA-00907: missing right parenthesis

I can not find where in following query:

v_sql:='SELECT
    ''Actual'' as SCENARIO,
    ''Data Load'' as VERSION,
    A.YEAR,
.....

and dtd.DTD_FULL_DATE BETWEEN  to_date(  to_char( ''' || p_start_date || ',
''dd-mon-yyyy'' ) , ''dd-mon-yyyy'' )
    and to_date( to_char( ''' || p_end_date  || ', ''dd-mon-yyyy'' )
                       , ''dd-mon-yyyy'' )

Please, help

          Followup:

sigh, decided to do it the wrong way eh.  Ok, you didn't use my example.  The
to_char() on the dates goes outside the string, the date formats for that
to_char go outside the string.. It looks like this:

ops$tkyte@ORA920> create or replace procedure p ( p_str in varchar2 )
  2  is
  3     l_str   long := p_str;
  4  begin
  5     loop
  6        exit when l_str is null;
  7        dbms_output.put_line( substr( l_str, 1, 250 ) );
  8        l_str := substr( l_str, 251 );
  9     end loop;
 10  end;
 11  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      p_start_date date := sysdate-5;
  3      p_end_date date := sysdate-5;
  4      v_sql long;
  5  begin
  6  v_sql := 'select *
  7              from my_table
  8             where datecompleted between
  9               to_date( ''' ||
 10                   to_char( p_start_date, 'dd-mon-yyyy' ) ||
 11                   ', ''dd-mon-yyyy'' )
 12               and
 13               to_date( ''' ||
 14                   to_char( p_end_date, 'dd-mon-yyyy' ) ||
 15                   ', ''dd-mon-yyyy'' )';
 16
 17      p(v_sql);
 18  end;
 19  /
select *
            from my_table
           where datecompleted between
             to_date( '29-mar-2003, 'dd-mon-yyyy' )

and
             to_date( '29-mar-2003, 'dd-mon-yyyy' )

PL/SQL procedure successfully completed.

      Rating: 5
(1): PLS-00307:
      too many declarations of 'TO_CHAR' match this call  April
      03, 2003
Reviewer:  A reader
Tom,

I am sorry, but now I am getting
(1): PLS-00307: too many declarations of 'TO_CHAR' match this call

Does it matter that I have group by like:

and dtd.DTD_FULL_DATE BETWEEN
    to_date( ''' ||
                    to_char( p_start_date, 'dd-mon-yyyy' ) ||
                    ', ''dd-mon-yyyy'' )
                and
                to_date( ''' ||
                    to_char( p_end_date, 'dd-mon-yyyy' ) ||
                    ', ''dd-mon-yyyy'' ))

group by
    dtd.dtd_year ,
    dtd.dtd_month_number_in_year,
   (CASE
        WHEN (cst.cst_chain_code in
(3,4,9,20,22,33,37,38,47,50,104,110,352,370,405,408,554,607,652,708,734)) THEN
(cst.cst_chain_code)
        ELSE (99999)
    END),
    pr.prd_item_code ,
   (CASE
        WHEN (pr.prd_stop_ship_date = ''0001-01-01'' and pr.prd_status_code =
''Y'') THEN (''D'')
        WHEN (substr(pr.prd_stop_ship_date,1,4) <= dtd.dtd_year) and
(pr.prd_stop_ship_date <> ''0001-01-01'') THEN (''D'')
        WHEN (substr(pr.prd_launch_date,1,4) >= dtd.dtd_year) THEN (''N'')
        ELSE (''E'')
    END) ,
   (CASE
        WHEN (pr.prd_in_out_code = ''Y'') THEN (''D'')
        WHEN (ord.ordd_prepack = ''N'') THEN (''O'')
        WHEN (ord.ordd_prepack = ''Y'' and ppr.prd_promotion_cycle_code =
''ASSORT'') THEN (''O'')
        ELSE (''D'')
    END)) A

          Followup:

p_start_date must not be a date then is it.

you must be trying to to_char a char.  I assummed that a variable named
P_start_date would be a date.

      Rating: 4
Is there any
      way we can generate CSV file from web Forms  April 03, 2003

Reviewer:  A reader
Hi Tom,
       This is great . Is there any way we can generate .CSV
or SYLK file from web forms ? Thanks in advance

          Followup:

http://otn.oracle.com/
has great discussion forums where people who use forms hang out...

      Rating: 5
create
      directory for utl_file  April 15, 2003
Reviewer:
      A reader
I am runing 9.2.0.1. The utl_file_dir parameter is static an is not defined:

SQL> select * from v$parameter where name like '%utl%';

       NUM NAME
 TYPE
---------- ----------------------------------------------------------------
----------
VALUE
---------------------------------------------------------------------------------
-------------------
ISDEFAULT ISSES ISSYS_MOD ISMODIFIED ISADJ DESCRIPTION
--------- ----- --------- ---------- -----
---------------------------------------------------------
UPDATE_COMMENT
---------------------------------------------------------------------------------
-------------------
       516 utl_file_dir
    2

TRUE      FALSE FALSE     FALSE      FALSE utl_file accessible directories list

I would have to stop the instance to set it. In the thread:
http://asktom.oracle.com/pls/ask/f?p=4950:8:40810802860339071::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:8506429011995,
you said:
> create directory directory1 as '/foo';
> create directory directory2 as '/bar';
> just create as many as you need.

So I tried:

SQL> create directory d_test as '/ora/92/utl_file';

Directory created.

SQL> var l number
SQL> exec :l := dump_csv ('select * from tab where rownum <
5',',','/ora/92/utl_file','test.txt')
BEGIN :l := dump_csv ('select * from tab where rownum <
5',',','/ora/92/utl_file','test.txt'); END;

*
ERROR at line 1:
ORA-29280: invalid directory path
ORA-06512: at "SYS.UTL_FILE", line 18
ORA-06512: at "SYS.UTL_FILE", line 424
ORA-06512: at "APP_TICKET_REDE.DUMP_CSV", line 15
ORA-06512: at line 1

File permissions seem ok:

$ ls -ld /ora/92/utl_file
drwxr-xr-x   2 oracle   dba          512 Apr 14 18:08 /ora/92/utl_file

I think I have to associate the created directory with my utl_file_dir
somehow...

What happened ?

          Followup:

try giving the NAME of the directory object you created.

exec :l := dump_csv ('select * from tab where rownum <
5',',','D_TEST','test.txt')

in uppercase.

      Rating: 5
string too long
      maybe ?  April 16, 2003
Reviewer:
      A reader
I am trying to run both your dump_csv and owa_sylk and I am getting the
following error, which I think has something to do with the string length. Is
that a problem with utl_file, dbms_sql or your procedures ?

I am trying the reproduce a small test case:

SQL> exec :v:=dump_csv('select rpad(''*'',2000,''*'') from
dual',',','TMPDIR','foo.txt');
BEGIN :v:=dump_csv('select rpad(''*'',2000,''*'') from
dual',',','TMPDIR','foo.txt'); END;

*
ERROR at line 1:
ORA-29285: file write error
ORA-06512: at "SYS.UTL_FILE", line 18
ORA-06512: at "SYS.UTL_FILE", line 562
ORA-06512: at "APP_TICKET_REDE.DUMP_CSV", line 54
ORA-06512: at line 1

I thought it had to do with the 2000-byte lenghts, but with a smaller size, the
same happens:

SQL> exec :v:=dump_csv('select rpad(''*'',700,''*''), rpad(''*'',1000,''*'')
from dual',',','TMPDIR','foo.txt');
BEGIN :v:=dump_csv('select rpad(''*'',700,''*''), rpad(''*'',1000,''*'') from
dual',',','TMPDIR','fo*
ERROR at line 1:
ORA-29285: file write error
ORA-06512: at "SYS.UTL_FILE", line 18
ORA-06512: at "SYS.UTL_FILE", line 562
ORA-06512: at "APP_TICKET_REDE.DUMP_CSV", line 54
ORA-06512: at line 1

Finally it works with 1000:

SQL> exec :v:=dump_csv('select rpad(''*'',1000,''*'') from
dual',',','TMPDIR','foo.txt');

PL/SQL procedure successfully completed.

          Followup:

 15  begin
 16      l_output := utl_file.fopen( p_dir, p_filename, 'w', 32000 );
                                                             ^^^^^^

default line width is "small"

      Rating: 4
its not working
      for me... help please  June 19, 2003
Reviewer:
      Sonali Kelkar  from waltham, ma
create or replace procedure dump_table_to_csv( p_tname in varchar2,
                                                   p_dir   in varchar2,
                                                   p_filename in varchar2 )
    authid current_user  /* <<<== if you want... runs as "invoker" and runs with
ROLES */
    is
        l_output        utl_file.file_type;
        l_theCursor     integer default dbms_sql.open_cursor;
        l_columnValue   varchar2(4000);
        l_status        integer;
       l_query         varchar2(1000)
                       default 'select * from ' || p_tname;
       l_colCnt        number := 0;
       l_separator     varchar2(1);
       l_descTbl       dbms_sql.desc_tab;
   begin
       l_output := utl_file.fopen( p_dir, p_filename, 'w' , 64000);
       execute immediate
       'alter session set nls_date_format=''dd-mon-yyyy hh24:mi:ss'' ';

       dbms_sql.parse(  l_theCursor,  l_query, dbms_sql.native );
       dbms_sql.describe_columns( l_theCursor, l_colCnt, l_descTbl );

       for i in 1 .. l_colCnt loop
           utl_file.put( l_output,
               l_separator || '"' || l_descTbl(i).col_name || '"' );
           dbms_sql.define_column( l_theCursor, i, l_columnValue, 4000 );
           l_separator := ',';
       end loop;
       utl_file.new_line( l_output );

       l_status := dbms_sql.execute(l_theCursor);

       while ( dbms_sql.fetch_rows(l_theCursor) > 0 ) loop
           l_separator := '';
           for i in 1 .. l_colCnt loop
               dbms_sql.column_value( l_theCursor, i, l_columnValue );
               utl_file.put( l_output, l_separator || l_columnValue );
               l_separator := ',';
           end loop;
           utl_file.new_line( l_output );
       end loop;
       dbms_sql.close_cursor(l_theCursor);
       utl_file.fclose( l_output );

       execute immediate 'alter session set nls_date_format=''dd-MON-yy'' ';
   exception
       when others then
           execute immediate 'alter session set nls_date_format=''dd-MON-yy'' ';
         raise;
 end;
 /

execute dump_table_to_csv( 'NGITT.mwebwork', 'd:/oracle/oradata',
'Sonalitest.txt' );

I get this error
SQLWKS> execute dump_table_to_csv( 'NGITT.mwebwork', 'd:/oracle/oradata',
'Sonalitest.txt' );
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at "SYSTEM.DUMP_TABLE_TO_CSV", line 49
ORA-06512: at line 2

in init.ora file I added this and restarted the server

utl_file_dir=D:\Oracle\oradata

AM I missing something ? NGITT is the schema name its on Newton instance...

          Followup:

try coding a teeny tiny UTL_FILE snipped that tries to open and close a file in
this directory.

But anyway, maybe it doesn't like you using unix / in one place and windows \ in
another.

simplify the example...
isolate the problem piece of code...
develop the tiny-est of test cases...
and you'll find your mistake (about 9999 times out of 10,000 anyway)....

not having windows installed anywhere, I cannot test that theory (that it is the
/\ issue) but you can certainly set up a small, isolated, 3 line of code example
to test with.

      Rating: 4
I guess I was
      missing oradba permission  June 20, 2003
Reviewer:
      sonali kelkar  from waltham, ma
I was missing oradba full permission on OS to do this.

      Rating: 4
regarding
      utl_file  June 26, 2003
Reviewer:  sam
       from NY USA
Tom,

I am facing a unique problem.
When I run procedure through toad a file gets created on unix box with oracle as
a owner. that's fine.

but when I try to run procedure from unix box itself it gives me following error

ERROR at line 1:
ORA-20001: invalid_operation
ORA-06512: at "U994983.UTL_EXP", line 122
ORA-06512: at line 1

Any clue??

          Followup:

your permissions don't sound like they are set entirely right.  "on the box", it
seems you might not be using "net" - hence you are forking the oracle executable
off and it is running as you.  when you use the net -- the listener (running as
oracle presumably) forks this process.

To test this theory, connect using "user/pass@db" on that machine.

Then, check your perms on the oracle binaries, should look something like:

[tkyte@tkyte-pc Desktop]$ ls -l $ORACLE_HOME/bin/oracle
-rwsr-s--x    1 ora920   ora920   51766646 Mar 31 13:03

if the setuid thingys aren't on, it is running "as you" and won't have write
privs.

      Rating:
      5
  August 18, 2003
Reviewer:
      A reader

      Rating: 5
java stored
      procedure version of dump_csv  September 03, 2003

Reviewer:  A reader
Tom, would you happen to have a java equivalent of the dump csv procedure?

          Followup:

sure, it'll be like one line of sqlj code  "begin dump_csv(:query); end"

:)

no, i don't have a full up java implementation, never will -- plsql is adept at
this and if plsql weren't fast enough, i'd be using either sqlplus or C at that
point.

      Rating: 5
very good, but
      is there a catalog view   October 24, 2003

Reviewer:  hannibal  from styria/austria/europe

hi tom,
thanks for the advice with the
create directory command. it proofed to be most usefull to me. much more than
editing the init.ora file. is there a catalog view to see all created directory
objects? i searched in user_object (and some others) for it but did not find a
'directory object' any way ...

thx

hannibal

          Followup:

all_directories
dba_directories

      Rating: 4
How to do
      this  November 14, 2003
Reviewer:  Pawan
       from US
Thanks. This utility is really great. I  have a small question. If my UTL FILE
directory is set to /data/out and this and other information is available in an
environment file (say myora.env on the unix box). This .env file is called by
the shell script that also calls the UTL_FILE script. Is there a way to say
l_output := utl_file.fopen( UNIX ENV FILE VARIABLE NAME, p_filename, 'w' );

The environment file has enteries like

   ORACLE_BASE=/opt/oracle;export ORACLE_BASE
   ORACLE_HOME=$ORACLE_BASE/ora817; export ORACLE_HOME
   ORACLE_BIN=$ORACLE_HOME/bin; export ORACLE_BIN
   ORACLE_DBA=$ORACLE_BASE/admin;export ORACLE_DBA
   ORACLE_SID=ORA;export ORACLE_SID

Can we add ORACLE_UTLFILE= /data/out; export ORACLE_UTLFILE

          Followup:

sqlplus u/p <<EOF
exec dump_csv( 'select * from emp', ',', '$ORACLE_UTLFILE', 'foo.dat' );
exit
EOF

November 15, 2003 Evara -- Thanks for the question regarding "case
      statement, single extent and raw device", version 8.1.7

      You Asked
Hi…tom
I have some question

1. .. Case Statement in Store Procedured

I want create some procedure that used case statement in my Query. Follow this
example

SQL> create or replace procedure proc1 is
  2  i integer;
  3  begin
  4      select case when 0 = 1 then 1 else 0
  5             end into i
  6      from dual;
  7  end;
  8  /

Warning: Procedure created with compilation errors.

SQL> show errors
Errors for PROCEDURE PROC1:

LINE/COL ERROR
-------- -----------------------------------------------------------------
4/12     PLS-00103: Encountered the symbol "CASE" when expecting one of
         the following:
         ( * - + all mod null <an identifier>
         <a double-quoted delimited-identifier> <a bind variable>
         table avg count current distinct max min prior sql stddev sum
         unique variance execute the forall time timestamp interval
         date <a string literal with character set specification>
         <a number> <a single-quoted SQL string>

SQL>

Why I Can't used case statement in stored procedure ?

2. Single Extent versus Multiple Extent

as far I know that single extent or multiple extent does not impact for the
performance. This is right or wrong ? If I wrong please give me your
explanation. I try to create some case. Follow this example

SQL> create tablespace ts_demo
  2  logging
  3  datafile 'C:\ORACLE8I\ORADATA\IDB1\TS_DEMO.ora' SIZE 300M reuse
  4  autoextend on next 128K maxsize 500M
  5  default storage ( initial 128K NEXT 128K minextents 1 maxextents 4096
  6           pctincrease 0 );

Tablespace created.

SQL> create table tbl_demo1 tablespace ts_demo as
  2  select * from all_objects where 0=1;

Table created.

SQL> create table tbl_demo2 tablespace ts_demo storage(initial 256m)
  2  as select * from all_objects where 0=1;

Table created.

SQL> begin
  2        insert  /*+ append */ into tbl_demo1 select * from all_objects
nologging;
  3        commit;
  4        for i in 1..8 loop
  5            insert  /*+ append */ into tbl_demo1 select * from tbl_demo1
nologging;
  6        commit;
  7        end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

SQL> begin
  2        insert  /*+ append */ into tbl_demo2 select * from all_objects
nologging;
  3        commit;
  4        for i in 1..8 loop
  5            insert  /*+ append */ into tbl_demo2 select * from tbl_demo2
nologging;
  6            commit;
  7        end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

SQL> select segment_name, extents from dba_segments
  2  where segment_name in ('TBL_DEMO1','TBL_DEMO2');

SEGMENT_NAME                      EXTENTS
------------------------------ ----------
TBL_DEMO1                             553
TBL_DEMO2                               1

2 rows selected.

SQL>

After this I trace my query

SQL> select * from tbl_demo1;

953600 rows selected.

Elapsed: 00:00:38.76

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

Statistics
----------------------------------------------------------
          0  recursive calls
        168  db block gets
      74345  consistent gets
       5707  physical reads
          0  redo size
   98029693  bytes sent via SQL*Net to client
    7057028  bytes received via SQL*Net from client
      63575  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     953600  rows processed

SQL> select * from tbl_demo2;

953600 rows selected.

Elapsed: 00:00:39.37

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

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
      74295  consistent gets
       8100  physical reads
          0  redo size
   98029693  bytes sent via SQL*Net to client
    7057028  bytes received via SQL*Net from client
      63575  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     953600  rows processed

Elapsed time on tbl_demo1 is 00:00:38.76 and
Elapsed time on tbl_demo2 is 00:00:39.37

I think this is not big difference.
But look in db block gets, consistent gets and physical reads. Why both
statistics is difference ? This is can drop performance or not ? How about the
SMON Activity ?

3. What benefit if I use raw device ? do you have step by step reference to
create raw device ?

Please give me your advise

regards

      and we said...
1) case is not a plsql construct until 9i.  It is available in sql in 816 and
817, not plsql.

2) 1 extent or 1,000 extents -- its OK.  As you've seen.

The numbers will be different because you have different structures, they are
not significantly different from where I'm sitting.

search for

single extent

on my site here if you want to read more about it.

3) raw devices MAY, MIGHT, SOMETIMES give a small edge over cooked filesystems
if you are I/O intensive.  They help the most with redo logs.  You can get most
of the benefit of raw just by mounting your cooked filesystems in 'direct' mode.
 how this works varies from platform to platform/os to os.  In solaris, i use:

/dev/vx/../vol05   /dev/..../vol05  /log2  ufs 3   yes  forcedirectio

to do this.

Refer to your Oracle OS guides for advice on using RAW with Oracle on your
specific platform.

Refer to your OS guides or if using something like veritas -- their docs,for
details on creating and using raw partitions.

Review &
      Followup

      Rating: 4
Raw
      device  November 28, 2001
Reviewer:  Evara
      Samsyiar  from Indonesia
I using Window 2000 Advanced Server and my Oracle version
is 8.1.7.
If I use Raw Device, this is difficult managed or not ?

          Followup:

see

http://technet.oracle.com/doc/oracle8i_816/nt.816/a73008/apd.htm#620714
for details.

      Rating: 3
is there any
      work around for case ?  July 08, 2002
Reviewer:
      A reader
Hi as now I know that case is NOT pl/sql construct,
is there any better way (like case) to update
around 4M rows ?

 I have a huge update to do every week
based on the age and sex and country
and it is very easy to do with the use of case stmt,

but I have to incorporated that in a package/procedure so that I can sumbmit a
job

 using "if" stmt. would be the alternate but, case stmt. mechenism is greate and
fast, is there any chance that I can use it in pl/sql(may be dynamically using
execute immediate ?

 execute immediate
  'update user set status = case  when (county = ''us'') then....

 can you please share your view on this ?

thanks in advance

          Followup:

Yes, you may use dynamic sql in order to use case in a SQL statement in 817 /
817.

      Rating: 3
Problem using
      case statement  November 11, 2003
Reviewer:
      Pramit  from USA
I'm using this query inside a trigger body and PL/SQL engine doesn't recognize
the case statement in 8i. Could you please help in using an alternative query.

INSERT INTO ab4p_build.worklist_counts_tbl
   SELECT   COUNT
               (DISTINCT (CASE
                     WHEN (CASE
                              WHEN (CASE
                                       WHEN t1.alloc_status_id = 6
                                       AND (w.source_ind IN ('W', 'S'))
                                          THEN 0
                                       ELSE -1
                                    END
                                   ) = -1
                                 THEN ar.alloc_units
                              ELSE 0
                           END
                          ) > 0
                        THEN ar.location_id
                     ELSE NULL
                  END
                 )
               ) COUNT,
            :NEW.alloc_id, :NEW.pack_id, :NEW.location_id,
            w.merch_hrchy_level1_id
       FROM worklist_packs_tbl w, alloc_results_tbl ar, allocs_tbl t1
      WHERE (ar.alloc_id = :NEW.alloc_id)
        AND (ar.location_id = :NEW.location_id)
        AND (ar.pack_id = w.pack_id)
        AND w.pack_id = :NEW.pack_id
        AND t1.alloc_id = :NEW.alloc_id
        AND NOT EXISTS (
               SELECT 'X'
                 FROM worklist_counts_tbl
                WHERE alloc_id = :NEW.alloc_id
                  AND pack_id = :NEW.pack_id
                  AND location_id = :NEW.location_id
                  AND merchandise_hierarchy_level = w.merch_hrarchy_level1_id)
   GROUP BY ar.alloc_id, ar.pack_id, ar.location_id, w.merch_hrchy_level1_id;

Thanks,
Pramit

          Followup:

execute immediate
'that insert statement replacing :new.alloc_id with :x1 and so on'
using :new.alloc_id, :new.pack_id, .....

use dynamic sql for right now -- leave a comment to the effect "make me static
when we upgrade"

      Rating: 3
Thanks.......I
      too got that after posting   November 12, 2003

Reviewer:  Pramit  from USA
Hi ,
I got that too after spending some time on it. Now that i'm able to compile it.
Enabling the trigger throws an
ora-03113 end of file on communication channel error while inserting into the
source table.

Is it because of the same bug in 8i or something else.
I'm also in the process of converting the code into a stored procedure and call
it from the trigger.
The trigger code goes like this

CREATE OR REPLACE TRIGGER dummy_worklist_trigger
   AFTER DELETE OR INSERT OR UPDATE
   ON dummy_alloc_results_tbl
   REFERENCING NEW AS NEW OLD AS OLD
   FOR EACH ROW
DECLARE
   lv_sqlcode   NUMBER;
   lv_sqlerrm   VARCHAR2 (1000);
BEGIN
   IF INSERTING
   THEN
      EXECUTE IMMEDIATE 'INSERT INTO ab4p_build.WORKLIST_COUNTS_TBL
                       select
                             COUNT(DISTINCT(CASE WHEN (CASE
                                         WHEN
                                                   (
                                                      CASE
                                                      WHEN t1.alloc_status_id =
6
                                                      AND (w.source_ind in
(''W'',''S''))
                                                     THEN 0
                                                      ELSE -1
                                                      END
                                                    )
                                                    = -1
                                                    THEN ar.alloc_units
                                                     ELSE 0
                                                      END
                                                     ) > 0
                                                       THEN ar.location_id
                                                        ELSE NULL
                                                     END
                                                    )
                                                      )
                                                    COUNT,
                                                    :NEW.ALLOC_ID,
                                                    :NEW.pack_id,
                                                    :NEW.location_id,
                                                    w.MERCH_HRCHY_LEVEL1_ID
                                                    from
                                                    worklist_packs_tbl w,
                                                    alloc_results_tbl ar,
                                                    allocs_tbl t1
                                                    where
                                                    (ar.alloc_id =
:NEW.ALLOC_ID)
                                                    and ( ar.location_id =
:NEW.LOCATION_ID)
                                                    and (ar.pack_id =W.PACK_ID)
                                                    and w.pack_id= :NEW.PACK_ID
                                                       and t1.alloc_id =
:NEW.alloc_id
                                                    AND NOT EXISTS
                                                        (SELECT ''X'' FROM
WORKLIST_COUNTS_TBL
                                                       WHERE
                                                      ALLOC_ID =:NEW.ALLOC_ID
                                                      AND PACK_ID =:NEW.PACK_ID
                                                      AND LOCATION_ID =
:NEW.LOCATION_ID
                                                      AND
MERCHANDISE_HIERARCHY_LEVEL = W.MERCH_HRCHY_LEVEL1_ID)
                                                    GROUP BY
                                                    ar.alloc_id,
                                                    ar.pack_id,
                                                    ar.location_id,
                                                    w.MERCH_HRCHY_LEVEL1_ID';

      EXECUTE IMMEDIATE 'INSERT INTO ab4p_build.WORKLIST_COUNTS_TBL
                select
                  COUNT
                           ( DISTINCT(CASE
                                        WHEN (CASE
                                         WHEN
                        (
                          CASE
                                  WHEN t1.alloc_status_id = 6
                                  AND (w.source_ind IN (''W'', ''S''))
                                     THEN 0
                                  ELSE -1
                              END
                        )
                        = -1
                                            THEN ar.alloc_units
                                         ELSE 0
                                          END
                                         ) > 0
                                       THEN ar.location_id
                                        ELSE NULL
                                     END
                                    )
                                  ) COUNT,
                :NEW.ALLOC_ID,
                :NEW.pack_id,
                :NEW.location_id,
                w.MERCH_HRCHY_LEVEL2_ID
                from
                worklist_packs_tbl w,
                alloc_results_tbl ar,
                allocs_tbl t1
                where
                (ar.alloc_id = :NEW.ALLOC_ID)
                and ( ar.location_id = :NEW.LOCATION_ID)
                and (ar.pack_id = W.PACK_ID)
                and w.pack_id= :NEW.PACK_ID
                   and t1.alloc_id = :NEW.alloc_id
                AND NOT EXISTS
                        (SELECT ''X'' FROM WORKLIST_COUNTS_TBL
                       WHERE
                      ALLOC_ID =:NEW.ALLOC_ID
                      AND PACK_ID =:NEW.PACK_ID
                      AND LOCATION_ID = :NEW.LOCATION_ID
                      AND MERCHANDISE_HIERARCHY_LEVEL = W.MERCH_HRCHY_LEVEL2_ID)
                GROUP BY
                ar.alloc_id,
                ar.pack_id,
                ar.location_id,
                w.MERCH_HRCHY_LEVEL2_ID';

      EXECUTE IMMEDIATE 'INSERT INTO ab4p_build.WORKLIST_COUNTS_TBL
                select
                  COUNT
                           ( DISTINCT(CASE
                                        WHEN (CASE
                                         WHEN
                        (
                          CASE
                                  WHEN t1.alloc_status_id = 6
                                  AND (w.source_ind IN (''W'', ''S''))
                                     THEN 0
                                  ELSE -1
                              END
                        )
                        = -1
                                            THEN ar.alloc_units
                                         ELSE 0
                                          END
                                         ) > 0
                                       THEN ar.location_id
                                        ELSE NULL
                                     END
                                    )
                                  ) COUNT,
                :NEW.ALLOC_ID,
                :NEW.pack_id,
                :NEW.location_id,
                w.MERCH_HRCHY_LEVEL3_ID
                from
                worklist_packs_tbl w,
                alloc_results_tbl ar,
                allocs_tbl t1
                where
                (ar.alloc_id = :NEW.ALLOC_ID)
                and ( ar.location_id = :NEW.LOCATION_ID)
                and (ar.pack_id = W.PACK_ID)
                and w.pack_id= :NEW.PACK_ID
                   and t1.alloc_id = :NEW.alloc_id
                AND NOT EXISTS
                        (SELECT ''X'' FROM WORKLIST_COUNTS_TBL
                       WHERE
                      ALLOC_ID =:NEW.ALLOC_ID
                      AND PACK_ID =:NEW.PACK_ID
                      AND LOCATION_ID = :NEW.LOCATION_ID
                      AND MERCHANDISE_HIERARCHY_LEVEL = W.MERCH_HRCHY_LEVEL3_ID)
                GROUP BY
                ar.alloc_id,
                ar.pack_id,
                ar.location_id,
                w.MERCH_HRCHY_LEVEL3_ID';

      EXECUTE IMMEDIATE 'INSERT INTO ab4p_build.WORKLIST_COUNTS_TBL
                select
                  COUNT
                           (DISTINCT(CASE
                                        WHEN (CASE
                                         WHEN
                        (
                          CASE
                                  WHEN t1.alloc_status_id = 6
                                  AND (w.source_ind IN (''W'', ''S''))
                                     THEN 0
                                  ELSE -1
                              END
                        )
                        = -1
                                            THEN ar.alloc_units
                                         ELSE 0
                                          END
                                         ) > 0
                                       THEN ar.location_id
                                        ELSE NULL
                                     END
                                    )
                                  ) COUNT,
                :NEW.ALLOC_ID,
                :NEW.pack_id,
                :NEW.location_id,
                w.MERCH_HRCHY_LEVEL4_ID
                from
                worklist_packs_tbl w,
                alloc_results_tbl ar,
                allocs_tbl t1
                where
                (ar.alloc_id = :NEW.ALLOC_ID)
                and ( ar.location_id = :NEW.LOCATION_ID)
                and (ar.pack_id = W.PACK_ID)
                and w.pack_id= :NEW.PACK_ID
                   and t1.alloc_id = :NEW.alloc_id
                AND NOT EXISTS
                        (SELECT ''X'' FROM WORKLIST_COUNTS_TBL
                       WHERE
                      ALLOC_ID =:NEW.ALLOC_ID
                      AND PACK_ID =:NEW.PACK_ID
                      AND LOCATION_ID = :NEW.LOCATION_ID
                      AND MERCHANDISE_HIERARCHY_LEVEL = W.MERCH_HRCHY_LEVEL4_ID)
                GROUP BY
                ar.alloc_id,
                ar.pack_id,
                ar.location_id,
                w.MERCH_HRCHY_LEVEL4_ID';

      EXECUTE IMMEDIATE 'UPDATE ab4p_build.WORKLIST_COUNTS_TBL wk
                SET COUNT = (select
                          COUNT
                               ( DISTINCT(CASE
                                        WHEN (CASE
                                         WHEN
                        (
                  CASE
                          WHEN t1.alloc_status_id = 6
                          AND (w.source_ind IN (''W'', ''S''))
                             THEN 0
                          ELSE -1
                      END
                        )
                        = -1
                                            THEN ar.alloc_units
                                         ELSE 0
                                          END
                                         ) > 0
                                       THEN ar.location_id
                                        ELSE NULL
                                     END
                                    )
                                  ) COUNT,
                from
                worklist_packs_tbl w,
                alloc_results_tbl ar,
                allocs_tbl t1
                where
                (ar.alloc_id = :NEW.ALLOC_ID)
                and ( ar.location_id = :NEW.LOCATION_ID)
                and (ar.pack_id = W.PACK_ID)
                and w.pack_id= :NEW.PACK_ID
                   and t1.alloc_id = :NEW.alloc_id
                GROUP BY
                ar.alloc_id,
                ar.pack_id,
                ar.location_id,
                w.MERCH_HRCHY_LEVEL1_ID)
                where
                    wk.alloc_id =:new.alloc_id
                    and wk.pack_id = :new.pack_id
                    and wk.location_id= :new.location_id
                    and wk.merchandise_hierarchy_level =
w.merch_hrchy_level1_id';

      EXECUTE IMMEDIATE 'UPDATE ab4p_build.WORKLIST_COUNTS_TBL wk
                SET COUNT = (select
                          COUNT
                               (DISTINCT (CASE
                                        WHEN (CASE
                                         WHEN
                        (
                  CASE
                          WHEN t1.alloc_status_id = 6
                          AND (w.source_ind IN (''W'', ''S''))
                             THEN 0
                          ELSE -1
                      END
                        )
                        = -1
                                            THEN ar.alloc_units
                                         ELSE 0
                                          END
                                         ) > 0
                                       THEN ar.location_id
                                        ELSE NULL
                                     END
                                    )
                                  ) COUNT,
                from
                worklist_packs_tbl w,
                alloc_results_tbl ar,
                allocs_tbl t1
                where
                (ar.alloc_id = :NEW.ALLOC_ID)
                and ( ar.location_id = :NEW.LOCATION_ID)
                and (ar.pack_id = W.PACK_ID)
                and w.pack_id= :NEW.PACK_ID
                   and t1.alloc_id = :NEW.alloc_id
                GROUP BY
                ar.alloc_id,
                ar.pack_id,
                ar.location_id,
                w.MERCH_HRCHY_LEVEL2_ID)
                where
                    wk.alloc_id =:new.alloc_id
                    and wk.pack_id = :new.pack_id
                    and wk.location_id= :new.location_id
                    and wk.merchandise_hierarchy_level =
w.merch_hrchy_level2_id';

      EXECUTE IMMEDIATE 'UPDATE ab4p_build.WORKLIST_COUNTS_TBL wk
                SET COUNT = (select
                          COUNT
                               ( DISTINCT(CASE
                                        WHEN (CASE
                                         WHEN
                        (
                  CASE
                          WHEN t1.alloc_status_id = 6
                          AND (w.source_ind IN (''W'', ''S''))
                             THEN 0
                          ELSE -1
                      END
                        )
                        = -1
                                            THEN ar.alloc_units
                                         ELSE 0
                                          END
                                         ) > 0
                                       THEN ar.location_id
                                        ELSE NULL
                                     END
                                    )
                                  ) COUNT,
                from
                worklist_packs_tbl w,
                alloc_results_tbl ar,
                allocs_tbl t1
                where
                (ar.alloc_id = :NEW.ALLOC_ID)
                and ( ar.location_id = :NEW.LOCATION_ID)
                and (ar.pack_id = W.PACK_ID)
                and w.pack_id= :NEW.PACK_ID
                   and t1.alloc_id = :NEW.alloc_id
                GROUP BY
                ar.alloc_id,
                ar.pack_id,
                ar.location_id,
                w.MERCH_HRCHY_LEVEL3_ID)
                where
                    wk.alloc_id =:new.alloc_id
                    and wk.pack_id = :new.pack_id
                    and wk.location_id= :new.location_id
                    and wk.merchandise_hierarchy_level =
w.merch_hrchy_level3_id';

      EXECUTE IMMEDIATE 'UPDATE ab4p_build.WORKLIST_COUNTS_TBL wk
                SET COUNT = (select
                          COUNT
                               ( DISTINCT(CASE
                                        WHEN (CASE
                                         WHEN
                        (
                  CASE
                          WHEN t1.alloc_status_id = 6
                          AND (w.source_ind IN (''W'', ''S''))
                             THEN 0
                          ELSE -1
                      END
                        )
                        = -1
                                            THEN ar.alloc_units
                                         ELSE 0
                                          END
                                         ) > 0
                                       THEN ar.location_id
                                        ELSE NULL
                                     END
                                    )
                                  ) COUNT,
                from
                worklist_packs_tbl w,
                alloc_results_tbl ar,
                allocs_tbl t1
                where
                (ar.alloc_id = :NEW.ALLOC_ID)
                and ( ar.location_id = :NEW.LOCATION_ID)
                and (ar.pack_id = W.PACK_ID)
                and w.pack_id= :NEW.PACK_ID
                   and t1.alloc_id = :NEW.alloc_id
                GROUP BY
                ar.alloc_id,
                ar.pack_id,
                ar.location_id,
                w.MERCH_HRCHY_LEVEL4_ID)
                where
                    wk.alloc_id =:new.alloc_id
                    and wk.pack_id = :new.pack_id
                    and wk.location_id= :new.location_id
                    and wk.merchandise_hierarchy_level =
w.merch_hrchy_level4_id';
   ELSIF UPDATING
   THEN
      EXECUTE IMMEDIATE 'UPDATE ab4p_build.WORKLIST_COUNTS_TBL wk
                SET COUNT = (select
                          COUNT
                               ( DISTINCT(CASE
                                        WHEN (CASE
                                         WHEN
                        (
                  CASE
                          WHEN t1.alloc_status_id = 6
                          AND (w.source_ind IN (''W'', ''S''))
                             THEN 0
                          ELSE -1
                      END
                        )
                        = -1
                                            THEN ar.alloc_units
                                         ELSE 0
                                          END
                                         ) > 0
                                       THEN ar.location_id
                                        ELSE NULL
                                     END
                                    )
                                  ) COUNT,
                from
                worklist_packs_tbl w,
                alloc_results_tbl ar,
                allocs_tbl t1
                where
                (ar.alloc_id = :NEW.ALLOC_ID)
                and ( ar.location_id = :NEW.LOCATION_ID)
                and (ar.pack_id = W.PACK_ID)
                and
                w.pack_id= :NEW.PACK_ID
                   and t1.alloc_id = :NEW.alloc_id
                GROUP BY
                ar.alloc_id,
                ar.pack_id,
                ar.location_id,
                w.MERCH_HRCHY_LEVEL1_ID)
                where
                    wk.alloc_id =:new.alloc_id
                    and wk.pack_id = :new.pack_id
                    and wk.location_id= :new.location_id
                    and wk.merchandise_hierarchy_level =
w.merch_hrchy_level1_id';

      EXECUTE IMMEDIATE 'UPDATE ab4p_build.WORKLIST_COUNTS_TBL wk
                SET COUNT = (select
                          COUNT
                               ( DISTINCT(CASE
                                        WHEN (CASE
                                         WHEN
                        (
                  CASE
                          WHEN t1.alloc_status_id = 6
                          AND (w.source_ind IN (''W'', ''S''))
                             THEN 0
                          ELSE -1
                      END
                        )
                        = -1
                                            THEN ar.alloc_units
                                         ELSE 0
                                          END
                                         ) > 0
                                       THEN ar.location_id
                                        ELSE NULL
                                     END
                                    )
                                  ) COUNT,
                from
                worklist_packs_tbl w,
                alloc_results_tbl ar,
                allocs_tbl t1
                where
                (ar.alloc_id = :NEW.ALLOC_ID)
                and ( ar.location_id = :NEW.LOCATION_ID)
                and (ar.pack_id = W.PACK_ID)
                and w.pack_id= :NEW.PACK_ID
                   and t1.alloc_id = :NEW.alloc_id
                GROUP BY
                ar.alloc_id,
                ar.pack_id,
                ar.location_id,
                w.MERCH_HRCHY_LEVEL2_ID)
                where
                    wk.alloc_id =:new.alloc_id
                    and wk.pack_id = :new.pack_id
                    and wk.location_id= :new.location_id
                    and wk.merchandise_hierarchy_level =
w.merch_hrchy_level2_id';

      EXECUTE IMMEDIATE 'UPDATE ab4p_build.WORKLIST_COUNTS_TBL wk
                SET COUNT = (select
                          COUNT
                               (DISTINCT (CASE
                                        WHEN (CASE
                                         WHEN
                        (
                  CASE
                          WHEN t1.alloc_status_id = 6
                          AND (w.source_ind IN (''W'', ''S''))
                             THEN 0
                          ELSE -1
                      END
                        )
                        = -1
                                            THEN ar.alloc_units
                                         ELSE 0
                                          END
                                         ) > 0
                                       THEN ar.location_id
                                        ELSE NULL
                                     END
                                    )
                                  ) COUNT,
                from
                worklist_packs_tbl w,
                alloc_results_tbl ar,
                allocs_tbl t1
                where
                (ar.alloc_id = :NEW.ALLOC_ID)
                and ( ar.location_id = :NEW.LOCATION_ID)
                and (ar.pack_id = W.PACK_ID)
                and w.pack_id= :NEW.PACK_ID
                   and t1.alloc_id = :NEW.alloc_id
                GROUP BY
                ar.alloc_id,
                ar.pack_id,
                ar.location_id,
                w.MERCH_HRCHY_LEVEL3_ID)
                where
                    wk.alloc_id =:new.alloc_id
                    and wk.pack_id = :new.pack_id
                    and wk.location_id= :new.location_id
                    and wk.merchandise_hierarchy_level =
w.merch_hrchy_level3_id';

      EXECUTE IMMEDIATE 'UPDATE ab4p_build.WORKLIST_COUNTS_TBL wk
                SET COUNT = (select
                          COUNT
                               ( DISTINCT(CASE
                                        WHEN (CASE
                                         WHEN
                        (
                  CASE
                          WHEN t1.alloc_status_id = 6
                          AND (w.source_ind IN (''W'', ''S''))
                             THEN 0
                          ELSE -1
                      END
                        )
                        = -1
                                            THEN ar.alloc_units
                                         ELSE 0
                                          END
                                         ) > 0
                                       THEN ar.location_id
                                        ELSE NULL
                                     END
                                    )
                                  ) COUNT,
                from
                worklist_packs_tbl w,
                alloc_results_tbl ar,
                allocs_tbl t1
                where
                (ar.alloc_id = :NEW.ALLOC_ID)
                and ( ar.location_id = :NEW.LOCATION_ID)
                and (ar.pack_id = W.PACK_ID)
                and w.pack_id= :NEW.PACK_ID
                       and t1.alloc_id = :NEW.alloc_id
                GROUP BY
                ar.alloc_id,
                ar.pack_id,
                ar.location_id,
                w.MERCH_HRCHY_LEVEL4_ID)
                where
                    wk.alloc_id =:new.alloc_id
                    and wk.pack_id = :new.pack_id
                    and wk.location_id= :new.location_id
                    and wk.merchandise_hierarchy_level =
w.merch_hrchy_level4_id';
   ELSIF DELETING
   THEN
      DELETE FROM worklist_counts_tbl
            WHERE alloc_id = :OLD.alloc_id
              AND pack_id = :OLD.pack_id
              AND location_id = :OLD.location_id;
   END IF;
EXCEPTION
   WHEN OTHERS
   THEN
      lv_sqlcode := SQLCODE;
      lv_sqlerrm := SQLERRM;

      INSERT INTO ab4p_build.worklist_counts_err_log_tbl
           VALUES (lv_sqlcode, lv_sqlerrm, SYSDATE);
END;
/

          Followup:

examples can (should/must) be small, concise, easy to read.  this is page after
page of code!

anyway, you did not do what I described.

  execute immediate 'query that uses :x, :y, :z -- NOT :new.xxx, :new.yyy'
  USING :new.x, :new.y, :new.z;

you are not binding properly

      Rating: 4
Thanks - I
      finally got it correct  November 14, 2003

Reviewer:  Pramit  from USA
I'm now binding the data properly and it is working as expected. But going back
to where we started pl/sql engine does not recognise 'case' or 'decode'in 8i.
Is that a bug?
when i tried binding a case statement initially I was getting a ora-00600 error
in the alert.log.(internal or fatal error).

is there something else to be looked for?

          Followup:

plsql does too see "decode"

it does not see "case"

it does in 9i

it is not "a bug" but an unfortunate "feature" in 8i

you showed me a totally invalid "bind" example so I cannot comment on the 600
(but support would)

      Rating: 4
PL/SQL and
      Decode  November 16, 2003
Reviewer:  Gary
       from Sydney, Australia
>>plsql does too see "decode"

It does in SQL statement, but not in the procedural bit.
eg you can't do
dbms_output.put_line(decode(v_val,'F','Fred','B','Bill'));

In 8i, you are stuck with  IF...ELSIF..END IF;

9i has associative arrays, so you'll be able to do
dbms_output.put_line(v_decode_val(v_val));
instead though (setting up a v_decode_val array for F-Fred and B-Bill), which is
even tidier than the decode.

      Rating: 4
Thanks ..That
      info was useful  November 21, 2003
Reviewer:
      Pramit  from USA

November 15, 2003 David -- Thanks for the question regarding "DDL
      Auditing - automatically including sql alt script file names", version
      8.1.7

      You Asked
Hi Tom,
I am trying to implement ddl auditing using new 8i schema level event triggers
on creates drops and alters.  I have read your example trigger and found it to
be very useful. see -'Frederic -- Thanks for the question regarding "DDL
triggers in 8.1.7", version 8.1.7
originally submitted on 4-feb-2001 15pm eastern time, lasted updated 11-apr-2002
7am'
My question specifically concerns documenting alters.  On our database, changes
to schema objects are done via 'alt scripts' run in sqlplus. A script to
add/drop a column to/from a table might be called "customer.1.1.alt" where
customer is the table name and the two digits represent a product release level
and alt script sequence number respectively.  Is there any way to get the actual
script name from an Oracle data dictionary table, system variable or context
variable, so that every time an alter script is run, the name of the script can
be included as one of the values inserted into the ddl_audit table?  This would
also be useful for creates and drops of course but these script names are easy
to reconstruct from the values in ora_sysevent, ora_dict_obj_name, and
ora_dict_obj_type.
I have been able to put the alt script name in
embedded comments in the alt script and then extracted them from the sql_text
field of v$open_cursor, however, the sql_text field is only 60 characters long
so I lose desired sql_text and I have to embed comments, which is inelegant.
e.g. 'alter table customer /*customer.1.1.alt*/
drop column obsolete_column;'
What I would really like to be able to do is elegantly extract whatever follows
the '@' symbol - e.g. @'customer.1.1.alt' into a variable in the trigger  for
whichever scripts are run in sqlplus that cause the schema level event trigger
to fire.

Thanks very much,

David Wilson.

      and we said...
As long as you use sqlplus to install -- "elegant r us" = true.

sqlplus stuffs the name and even the depth of nesting of the current script in
the MODULE column of v$session.  Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table log
  2  ( operation varchar2(25),
  3    script    varchar2(255),
  4    owner     varchar2(25),
  5    name      varchar2(25),
  6    extra     varchar2(4000) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace trigger ddl_trigger
  2  after create or alter or drop on SCHEMA
  3  declare
  4      l_sysevent varchar2(25);
  5      l_extra    varchar2(4000);
  6  begin
  7      select ora_sysevent into l_sysevent from dual;
  8
  9      if ( l_sysevent in ('DROP','CREATE') )
 10      then
 11          if l_sysevent = 'CREATE'
 12          then
 13          begin
 14              select 'storage ( initial ' || initial_extent ||
 15                     ' next ' || next_extent || ' .... )'
 16                into l_extra
 17                from all_tables
 18               where table_name = ora_dict_obj_name
 19                 and owner = user;
 20          exception
 21              when no_data_found then null;
 22          end;
 23          end if;
 24
 25          insert into log
 26          select ora_sysevent, module, ora_dict_obj_owner,
 27                         ora_dict_obj_name, l_extra
 28            from v$session
 29                   where sid = (select sid from v$mystat where rownum=1);
 30      elsif ( l_sysevent = 'ALTER' )
 31      then
 32          insert into log
 33          select ora_sysevent, module, ora_dict_obj_owner,
 34                         ora_dict_obj_name, sql_text
 35            from v$open_cursor,
 36                 (select module
 37                    from v$session
 38                   where sid = (select sid from v$mystat where rownum=1))
 39           where upper(sql_text) like 'ALTER%'  ||
 40                                ora_dict_obj_name || '%'
 41             and sid = ( select sid
 42                           from v$session
 43                          where audsid=userenv('sessionid') );
 44      end if;
 45  end;
 46  /

Trigger created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table cust ( x int primary key );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> @customer.1.1.alt
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table cust add new_column number;

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> column script format a25
ops$tkyte@ORA817DEV.US.ORACLE.COM> column extra format a50
ops$tkyte@ORA817DEV.US.ORACLE.COM> select script, extra from log;

SCRIPT                    EXTRA
------------------------- --------------------------------------------------
01@ test.sql
01@ test.sql              storage ( initial 524288 next 524288 .... )
02@ customer.1.1.alt      alter table cust add new_column number

Review &
      Followup

      Rating: 5
A most elegant
      solution indeed!  November 09, 2002
Reviewer:
      David Wilson  from Toronto, ON Canada
Hi Tom,
That is very useful.  If you don't mind a followup question, where is the best
place to look up what information the columns in the data dictionary contain?  I
would like to study them.  I have found the oracle documentation descriptions to
be somewhat terse, although I might be looking in the wrong place.
Thanks again for your help and for providing such a useful and informative site.

          Followup:

This is what I've always used:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-REF
however a tidbit such as

"oh by the way the MODULE column in v$session will have the sqlplus script name
in it when you look at it and the session is using sqlplus"

won't be found pretty much anywhere.  EG: the reference guide cannot say this as
this is a feature/function of SQLPlus.  The module column contains a value that
the application that is connected to the database just happens to have stuffed
in there.  I know from observation that sqlplus does this.  You won't "find"
this factoid anywhere.

      Rating: 4
Great
      Info  November 10, 2002
Reviewer:  Jeremy
      Smith  from Salt Lake City, UT
You know, it's surprising to me that of all of the Oracle books out there, no
one has ever written a reference that specifically just goes through the whole
data dictionary, V$ views, the few X$ views people occasionally use, and
explains not just what each one contains, but useful purposes it can be used
for, what each value really means, etc.  Right now there's the reference you
posted, but factoids like this, what "good" and "bad" values are for various
parameters, which one is applicable in which situation, etc.  Between your book,
the Oracle docs, JPL's book, Steve Adams' website, whatever, there's a lot of
information out there.

Ah well.  Can't have everything. :)

      Rating: 4
Any pre-built
      mechanism in 9i  November 11, 2002
Reviewer:
      Subrata  from Bangalore, India
Do you have any pre built mechanism to do the same in 9i?

          Followup:

this works in 9i, you might have to add

SQL> set appinfo on

to your script as the default value seems to have changed.

      Rating: 4
wrong log table
      create statement  May 17, 2003
Reviewer:  Edward
      Hayrabedian  from Bulgaria
Hi Tom,

I have found a little mistake in your script, which can lead to a problem, which
is not easy to investigate.

The "owner" and "name" columns in the "log" table could be more than 25 chars
long. The "log" table's definition should be:

create table log
    ( operation varchar2(25),
      script    varchar2(255),
      owner     varchar2(30),
      name      varchar2(30),
      extra     varchar2(4000) );

Thanks for your help!

Best regards,
Eddie

      Rating: 4
Auditing
      procedures, functions and packages  November 14, 2003

Reviewer:  A reader
Hi Tom, after reading your book I got your idea to enable audit, for all DLL
activity.

So I'm enabling but I can't enable auditing in all functions, procedures, and
packages with one command.
Is there a trick for it?

Thanks.

PD.
I enabled this auditing
AUDIT CREATE ON DEFAULT;
AUDIT ALTER ON DEFAULT;

AUDIT ALTER DATABASE;
AUDIT SYSTEM AUDIT;
AUDIT ALTER SYSTEM;

AUDIT ALTER TABLESPACE;
AUDIT DROP TABLESPACE;
AUDIT CREATE TABLESPACE;

AUDIT CREATE USER WHENEVER SUCCESSFUL;
AUDIT ALTER USER  WHENEVER SUCCESSFUL;
AUDIT DROP USER WHENEVER SUCCESSFUL;

AUDIT CREATE ROLE WHENEVER SUCCESSFUL;
AUDIT ALTER ANY ROLE  WHENEVER SUCCESSFUL;
AUDIT DROP ANY ROLE  WHENEVER SUCCESSFUL;

AUDIT DROP ANY TABLE WHENEVER SUCCESSFUL;
AUDIT CREATE ANY TABLE WHENEVER SUCCESSFUL;
AUDIT ALTER ANY TABLE WHENEVER SUCCESSFUL;

AUDIT CREATE ANY SEQUENCE WHENEVER SUCCESSFUL;
AUDIT ALTER ANY SEQUENCE WHENEVER SUCCESSFUL;
AUDIT DROP ANY SEQUENCE WHENEVER SUCCESSFUL;

AUDIT CREATE DATABASE LINK WHENEVER SUCCESSFUL;
AUDIT CREATE PUBLIC DATABASE LINK WHENEVER SUCCESSFUL;

AUDIT CREATE OR REPLACE PROCEDURE;

AUDIT DROP ANY VIEW WHENEVER SUCCESSFUL;
AUDIT CREATE ANY VIEW WHENEVER SUCCESSFUL;

AUDIT DROP ANY INDEX WHENEVER SUCCESSFUL;
AUDIT CREATE ANY INDEX WHENEVER SUCCESSFUL;
AUDIT ALTER ANY INDEX WHENEVER SUCCESSFUL;

AUDIT DROP ANY SYNONYM WHENEVER SUCCESSFUL;
AUDIT CREATE ANY SYNONYM WHENEVER SUCCESSFUL;

AUDIT DROP ANY TRIGGER WHENEVER SUCCESSFUL;
AUDIT CREATE ANY TRIGGER WHENEVER SUCCESSFUL;
AUDIT ALTER ANY TRIGGER WHENEVER SUCCESSFUL;

AUDIT DROP ANY CLUSTER WHENEVER SUCCESSFUL;
AUDIT CREATE ANY CLUSTER WHENEVER SUCCESSFUL;
AUDIT ALTER ANY CLUSTER WHENEVER SUCCESSFUL;

AUDIT DROP ANY CONTEXT WHENEVER SUCCESSFUL;
AUDIT CREATE ANY CONTEXT WHENEVER SUCCESSFUL;

AUDIT DROP ANY DIMENSION WHENEVER SUCCESSFUL;
AUDIT CREATE ANY DIMENSION WHENEVER SUCCESSFUL;
AUDIT ALTER ANY DIMENSION WHENEVER SUCCESSFUL;

AUDIT DROP ANY DIRECTORY WHENEVER SUCCESSFUL;
AUDIT CREATE ANY DIRECTORY WHENEVER SUCCESSFUL;

          Followup:

  1  SELECT * FROM STMT_AUDIT_OPTION_MAP WHERE NAME LIKE '%ANY%'
  2* order by name
ops$tkyte@ORA920>
ops$tkyte@ORA920> /

       192 EXECUTE ANY LIBRARY                     0
       144 EXECUTE ANY PROCEDURE                   0
       184 EXECUTE ANY TYPE                        0

      Rating: 4
Sorry
        November 14, 2003
Reviewer:  A reader

Sorry, my mistake, it was a problem with a letter.
Any way you can see in this table
all the %ANY% privileges you can give

SELECT * FROM STMT_AUDIT_OPTION_MAP WHERE NAME LIKE '%ANY%'

November 15, 2003 Ganesh -- Thanks for the question regarding "Undo
      Tablespaces.", version 9i

      You Asked
Hi Tom,

Waiting to ask u this question.

What is a Undo Tablespace in 9i. Is this similar to Rollback Segments.

What are NonStandard Block sizes Why that non-Standarad.

Why am i not able to create a RS on a Locally Managed Automatically Sized TS.

Till next Time.

Regards,
Ganesh R

      and we said...
An undo tablespace is a different way of managing rollback in 9i.  Instead of
you figuring out you need N rollback segments each of M bytes/extents in size --
you just create one undo tablespace.  In this one undo tablespace we'll manage
all undo.  You specify how long you want undo to be retained (eg: if your
longest running query is about 15minutes, you might say "please retain undo for
20minutes before overwriting it").  We'll create, manage, and grow this undo
space for you.

So, an undo tablespace is an alternative to a rollback segment.

As for the non-standard block sizes -- that refers to 9i's ability to have more
then one block size in a database.  When you create the database, SYSTEM will
have a block size.  That is the "standard" block size for that database.  All
other block sizes found in that database are "non-standard".

Rollback segments should have equi-sized extents.  Automatically sized extents
are by definition not equi sized.
Review &
      Followup

      Rating: 5
To The
      Point....  September 09, 2001
Reviewer:
      Ganesh  from India
Tom... But do u mean oracle is eliminating Rollback segments in 9i.

Why do i need Non Standard Block Sizes.

Hope to get this cleared.

          Followup:

An undo tablespace is an alternative -- rollback segments are still present.
You will pick whether you want to use an UNDO tablespace (default) or rollback
segments.

You might want a non-standard blocksize to support a mixed mode database (mixed
OLTP -- they want small blocks with DSS -- they want big blocks).

You would need a non-standard blocksize to transport datafiles from a 4k
database to an 8k blocksize database.

You might want a non-standard blocksize to test what the effect of using a 16k
blocksize over a 2k blocksize on storage and performance might have without
creating an entire new database.

      Rating: 4
Great
      Thomas.  September 09, 2001
Reviewer:
      Ganesh  from India.
Nothing More to SAY !!!

And Pls Look into the Indian Reprint of u'r book of which we are hearuing rave
reviews.

Regards,
Ganesh R

      Rating: 3
UNDO
      Tablespace  February 25, 2002
Reviewer:  Neeraj
      Mehta  from SINGAPORE
Its useful for beginers to understand the concepts

      Rating: 3
I am using
      UNDO, but still getting rollback errors  December 02, 2002

Reviewer:  Dean  from Nashville
I've checked several times.  I am running in AUTO mode.  I checked V$UNDOSTAT
and undo is being generated.  I can even see my UNDO tablespace changing size.

I have a package:

if I do
   alter package body compile;
no errors

if I save the source code to a file and try
   create or replace package body

I get

create or replace PACKAGE BODY pk_mypkg
*
ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 1
ORA-00376: file 3 cannot be read at this time
ORA-01110: data file 3:
'I:\DB\my\DBFILES\ROLLBACK\my_RBS_SMALL01.DBF'

I have taken all the rollback segments except SYSTEM (and their datafiles even)
offline.  So I know what the error means, I just don't know why I got it.
Sorry, this one has me in a pickle.

          Followup:

what does

select owner, segment_name, segment_type
  from dba_segments
 where tablespace_name = (select tablespace_name
                            from dba_data_files
                           where upper(file_name) like '%MY_RBS_SMALL01.DBF' )

return (and you have start a TAR with support right?)

      Rating: 3
I will
      now  December 03, 2002
Reviewer:  Dean
       from Nashville
Owner   Segment_Name Segment_type
SYS    RBS01          ROLLBACK
SYS    RBS02          ROLLBACK
SYS    RBS03          ROLLBACK
SYS    RBS04          ROLLBACK
SYS    RBS05          ROLLBACK

FYI the database was upgraded from 8.1.7

          Followup:

don't know -- you'll have to work through this one with support -- sorry.

      Rating: 3
thanks
      anyway  December 03, 2002
Reviewer:  Dean
       from Nashville
I found that there's no problem with shorter source code, but when trying to
compile a large package (2500 lines), it bombs.

Do I get a sticker if it's a bug!

Thanks.

      Rating:
      4
  December 03, 2002
Reviewer:
      A reader
Hi Tom,

Will rollback segments be deprecated in favor of Undo Tablespace in future
releases of Oracle.

          Followup:

hard to say but if I had to guess.... (in some future release, i could certainly
see it happening yes)

      Rating: 5
Rollback vs
      UNDOTBS  November 14, 2003
Reviewer:  Denise
       from Virignia, USA
Tom

just got my 9i up and running at work on my 'toy' server.

I tried setting up rollback segments(carryover from 8i)
and got the ora-30019 error(illegal Rollback Operation..blah,blah).

So what I am understanding is that Rollback Segments have
been replaced with a tablespace called UNDOTBS...and in
that tablespace are .dbf files(like regular datafiles)..
that function as rollback segments and are automatically managed by the system
if UNDO_Management=AUTO in init.ora file.

So if I needed more undo(rollback)storage I would simply create another .dbf
file(like in any other Tablespace)
and don't have to worry about "Snapshot too old" errors any more??..the undo
will "automatically" shrink and grow as needed...(man that is so cool!)

am I thinking this correctly?

thanks..

          Followup:

read up on it in the admin/concepts guide.

your concepts are correct.

November 15, 2003 Peter -- Thanks for the question regarding
      "Increasing Export and Import Speed?", version 8.X

      You Asked
Hello,

1)  I'm looking for hints and techniques for speeding up exports and imports of
large tables.  We have an application that has a few small tables and a large
table (a small system may have 30M rows, large 100M+).

Often we need to move databases to a faster machine or a different platform (i.e
AIX->Solaris). Some larger DBs take 20+ hours to load the data (not including
the index build).

We currently take advantage of:
Read/Write to a named pipe with compression
Export with Direct option, set large buffers
Use indexfile parameter to break import process into pieces
(table create, data load, index build)
Build indexes with multiple create processes
Build indexes with NOLOGGING PARALLEL options

Multiple export or import processes aren't much of a gain, because the
difference in sizes in tables is extreme.

2)  Are there any good references for Exp/Imp?  Most reference books only give
one or two pages of basic information.

      and we said...
1) Have you looked at using the sqlplus copy command over the network?  In this
fashion you can avoid putting stuff to disk and reading it back in from disk.
Instead of exporting that really big table, copy it.

short of that, I might use some pro*c to "flatten out" the data to a flat file
and sqlldr with direct path mode to reload it.  That'll by far be the fastest
load (but the overhead of having to write it and read it might make it similar
to a sqlplus copy).

for copy:

http://technet.oracle.com/doc/oracle8i_816/sqlplus.816/a75664/ch63.htm#1003261
for pro*c to flatten the data

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:459020243348
2) I have a 50 page chapter in my book on EXP/IMP

http://www.amazon.com/exec/obidos/ASIN/1861004826/002-0082367-4217640
the outline is:

Why you might use them
How they work
   the options
       exp
       imp
   large exports
       using the filesize parameter
       export smaller pieces
       export to a device that does not support seeking
   subsetting data
   transporting data
   getting the ddl
   as part of backup/recovery plan
   importing into different structures
   direct path exports
Caveats and errors
    cloning
    across versions
    where did my indexes go?
    named vs default named constraints
    NLS issues
    Multi-tablespace CREATES
Summary

Review &
      Followup

      Rating: 2
It does not
      answer the question...  August 24, 2001
Reviewer:
      Harm ten Napel  from Hoofddorp, Netherlands, Europe

A real way of improving speed of exp / imp is to link the
executables single task, of course you don't recommend
that because single task executables in potential can
corrupt your SGA.

          Followup:

Well, in my opinion it did answer the question (which was how can I speed up
moving the data) -- but anyway....

Single task linking is not generally supported with 8.x and for 8i -- not
supported for imp/exp/sqlldr at all.  The 8i documentation needed to be updated
in that regards.
That would be why i did not mention it.

      Rating: 4
Compiling
      Pro*C  August 24, 2001
Reviewer:
      A reader
Tom,

I like to make use of your pro*c program. I am a novice at Pro*C. How do I
compile it and use it ?

- Thanks

          Followup:

See

http://osi.oracle.com/~tkyte/proc_makefile/
for a generic unix makefile that can compile this easily.  Take the source code
from the first link and put it into a directory.

Then take the makefile from the above link and put it into that directory in a
file called "Makefile"

Set the TARGET and SOURCE as described and type "make" -- that should do it.

      Rating: 5
Some more hints
      & tips  August 24, 2001
Reviewer:
      Connor  from UK
A little out of date now, but
http://www.oradba.freeserve.co.uk/tips/import_speed.htm
should be assistance

      Rating: 3
Couldn't
      compile pro*c...  August 24, 2001
Reviewer:
      A reader
Tom,
Thanks for your prompt response. However, I still couldn't compile the pro*c.
This is what I did.
1) created file flatten.pc with a copy of your source code
2) create Makefile from the contents you provided
3) set up the env. variables as follows:
% setenv SOURCE flatten.pc
% setenv TARGET flatten
% setenv ORACLE_PROC_MAKEFILE $ORACLE_HOME/precomp/demo/proc/demo_proc.mk
% make
Make: /disk01/app/oracle/product/8.1.7/precomp/lib/env_precomp.mk: Must be a
separator on line 7.  Stop.

I have installed pro*c on the server on Tru64. Database version is 8172.

Your help in resolving this would be much appreciated as I would like to try
this out asap. (I have a very urgent need to copy tables regularly).

Rgds, Chris

          Followup:

Sorry -- I use makefiles so much I sometimes forget to mention the less than
obvious rules regarding them.

You need tabs:

$(TARGET): $(SOURCE) $(SOURCE:.pc=.c) $(SOURCE:.pc=.o)
<TAB> $(CC) $(LDFLAGS) -t -o $(TARGET) \
        $(SOURCE:.pc=.o) -L$(ORACLE_HOME)/lib $(PROLDLIBS)

include $(ORACLE_PROC_MAKEFILE)

PROCFLAGS= ireclen=255 lines=yes $(PROC_ENV_FLAGS) \
           include=$(ORACLE_HOME)/proc/lib
PROFLAGS=$(PROCFLAGS)

CFLAGS=-I. -g $(CC_ENV_FLAGS)

in there.  I see the error is being flagged in env_precomp.mk however which is
strange.  You can email that to me directly as an attachment and I'll have a
look (the env_precomp that is being referenced).  That should not be.

      Rating: 5
Your pro*c file
      compiled - Thanks   August 24, 2001
Reviewer:
      A reader
Thanks Tom, the .pc program compiled when a <tab> was inserted as you suggested.
Also I had to exlude the -t flag from the Makefile (Maybe -t is not recognised
under Tru64).

One final question. How do I exclude the column headings/feedback from the
output ?.

          Followup:

The headings and other stuff are written to stderr.  the data is written to
stdout.  Just use redirection to separate them:

$ ./t userid=scott/tiger 'sqlstmt=select * from emp' arraysize=1000 > output.dat

Connected to ORACLE as user: scott/tiger

Unloading 'select * from emp'
Array size = 1000
EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO
14 rows extracted

$ cat output.dat
7369,SMITH,CLERK,7902,17-DEC-1980 00:00:00,800,(null),20
7499,ALLEN,SALESMAN,7698,20-FEB-1981 00:00:00,1600,300,30
7521,WARD,SALESMAN,7698,22-FEB-1981 00:00:00,1250,500,30
7566,JONES,MANAGER,7839,02-APR-1981 00:00:00,2975,(null),20
7654,MARTIN,SALESMAN,7698,28-SEP-1981 00:00:00,1250,1400,30
7698,BLAKE,MANAGER,7839,01-MAY-1981 00:00:00,2850,(null),30
7782,CLARK,MANAGER,7839,09-JUN-1981 00:00:00,2450,(null),10
7788,SCOTT,ANALYST,7566,09-DEC-1982 00:00:00,3000,(null),20
7839,KING,PRESIDENT,(null),17-NOV-1981 00:00:00,5000,(null),10
7844,TURNER,SALESMAN,7698,08-SEP-1981 00:00:00,1500,0,30
7876,ADAMS,CLERK,7788,12-JAN-1983 00:00:00,1100,(null),20
7900,JAMES,CLERK,7698,03-DEC-1981 00:00:00,950,(null),30
7902,FORD,ANALYST,7566,03-DEC-1981 00:00:00,3000,(null),20
7934,miller,CLERK,7782,23-JAN-1982 00:00:00,1300,(null),10

Notice how the output does not have the headers/etc.  You can comment out the
fprintf(stderr, .... ) lines in the C code to get rid of them as well (just
leave the printf calls in there.

      Rating: 3
cannot
      compile  September 23, 2002
Reviewer:  David
      Le  from Honolulu, HI USA
Hi Tom,

I used your soource code & your Make file but I got this error.

make: Fatal error in reader: Makefile, line 4: Unexpected end of line seen

How can I fixed it?

Thanks.

          Followup:

well, post your makefile, lets see if it is the same or different and search
this page for TAB - -make sure you have TABS

do you know makefiles?  do you have someone that works with you that does?  this
is a pretty simple one!

      Rating: 4
spool vs PRO*C
      hostarray  September 24, 2002
Reviewer:
      A reader
hi

how fast is sqlplus spool command compared with PRO*C hostarray source fetching?

we have data extraction from our oltp databases to dwh, we use spool but it
takes pretty long (dealing with 25 ~ 40 million rows), would pro*c hostarray
overcome this problem?

          Followup:

Most likely, it would be faster and can be made even faster with a little tuning
of the C code, Pro*C would best sqlplus

Pro*c = specific, does one thing
SQLPlus = generic, big tool for everyone, does a bit of everything

      Rating: 5
Disappearing
      features...  September 25, 2002
Reviewer:
      Connor McDonald  from UK
9.2 docs:

"The COPY command will be obsoleted in future releases of SQL*Plus"

:-(

      Rating:
      4
  January 19, 2003
Reviewer:  Mike
       from TX
Does the sqlplus COPY is the same (performance wise) as
create table tab as select * from tab@reomte?

          Followup:

Nope.

Try this -- dialup from your PC to your servers.  On the network there is
machineA and machineB you can now reach.

Do a sqlplus copy from A to B.  What happens is that every row on A will come
down the phone line to your PC and sent backup the phone line to B.

Do a create table as select when connected to B - the data now goes from A to B
directly -- not down your phone line.

The sqlplus client can be a bottleneck in a copy.

Also, copy does inserts - -generates undo/redo.  CTAS wont do undo and may not
do redo (nologging or noarchivelog mode databases for example)

but copy can be very fast/efficient when used machine to machine over a good
network.

      Rating: 5
What is the
      future for Pro*C  January 20, 2003
Reviewer:
      Siva  from NJ, USA
I used to extensively code in pro*C. That was the only option for batch programs
. I still tend to write Pro*C, as I will it gives the flexibilty. The new bread
of people prefer PL/SQL batch programming. I also say ok for this, because
PL/SQL developers are easy to find, compared to Pro*C.

Is there still an advantage to hang on to Pro*C.

Thanks
Siva

          Followup:

sure, I still use it from time to time -- just used it last friday to solve a
real world problem (C based extproc, I find coding pro*c to be about 1,000,000
more efficient than OCI/jdbc/odbc/whatever API here)

      Rating: 4
import to raw
      device  January 20, 2003
Reviewer:  Robert
      Xuequn Xu
I found that import to a database residing on raw device is extremely fast.
Also, setting the "_disable_logging=true" instance parameter for the duration of
import can give about 20-30% time saving.

Between exp and imp, it is the imp that needs big improvement in speed, because
it is usually serveral times slower than exp, especially when there is a huge
amount of LOB data.

          Followup:

The disable logging =true could be the proverbial double edged sword.

Say you have a 10hour import.  You are 6 hours into it.  You have an hour to go
(since you are saving 30%).  Crash.  Bamm.  Boom.  You just lost siz hours due
to a software/power/whatever failure.   As the band Pink Floyd used to sing
"careful with that axe eugene"...

As for raw, I found that most unix file systems mounted in directio (or
equivalent thereof) perform about the same.

The key in my opinion -- it is all about parallel.  Dont' export a database with
full=y.  Export it with owner=username and do N exports in parallel and N
imports in parallel  If possible -- do it over a pipe so the data never even
hits a disk.  But -- do it in parallel.  Take your biggest objects and consider
doing them "special" (parallel index creates, nologging operations)....

      Rating:
      5
  January 20, 2003
Reviewer:  Trevor
       from UK
Tom said "
Also, copy does inserts - -generates undo/redo.  CTAS wont do undo and may not
do redo (nologging or noarchivelog mode databases for example)"

Tom I was surprised to read CTAS might not create redo.
I understand the no undoplus no redo for nologging
but why if not in archivelog mode?
I thought redo was still generated (except when no logging
specified) and noarchivelog mode simply controlled
if the redo logs were archived or not?

          Followup:

It is an optimization -- CTAS will do direct writes to the datafiles --
bypassing the need to generate REDO in noarchivelog mode.

ops$tkyte@ORA817DEV> select log_mode from v$database;

LOG_MODE
------------
NOARCHIVELOG

ops$tkyte@ORA817DEV> @mystat "redo size"
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('redo size')||'%'

NAME                                VALUE
------------------------------ ----------
redo size                            1000

ops$tkyte@ORA817DEV> create table t as select * from all_objects;
Table created.

ops$tkyte@ORA817DEV> @mystat "redo size"
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('redo size')||'%'

NAME                                VALUE
------------------------------ ----------
redo size                           44920

so, 43,920 bytes of redo...

ops$tkyte@ORA9I> select log_mode from v$database;

LOG_MODE
------------
ARCHIVELOG

ops$tkyte@ORA9I> @mystat "redo size"
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('redo size')||'%'

NAME                                VALUE
------------------------------ ----------
redo size                           33744

ops$tkyte@ORA9I> create table t as select * from all_objects;

Table created.

ops$tkyte@ORA9I> @mystat "redo size"
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('redo size')||'%'

NAME                                VALUE
------------------------------ ----------
redo size                         4822944

versus lots more then that....

      Rating:
      5
  June 18, 2003
Reviewer:
      A reader
Tom,

"Do a sqlplus copy from A to B.  What happens is that every row on A will come
down the phone line to your PC and sent backup the phone line to B."

Lets say if i create a sh file and store it on the server which uses copy
command and schedule it through crontab. will this be a better way of doing it.

Thanks.

          Followup:

the data flows through SQLPlus -- where sqlplus runs, the data will come to it
and go from it.  If sqlplus is run on a server -- the data will go through it.

      Rating: 4
Great program!
      When I tried to compile, I am getting the following
      error  November 11, 2003
Reviewer:  gs

Tom, I have oracle 9.2.0.3 and when I tried to compile the program I am getting
the following error. Is my ORACLE_PROC_MAKEFILE settings incorrect for Oracle
9i?

[gs@dev13 gs]$ export
ORACLE_PROC_MAKEFILE=$ORACLE_HOME/precomp/demo/proc/demo_proc.mk
[gs@dev13 gs]$ export TARGET=dump_tab
[gs@dev13 gs]$ export SOURCE=dump_tab.pc
[gs@dev13 gs]$ make
make -f /oracle/product/9.2.0/precomp/demo/proc/demo_proc.mk
PROCFLAGS="ireclen=255 lines=yes  include=/oracle/product/9.2.0/proc/lib"
PCCSRC=dump_tab I_SYM=include= pc1
make[1]: Entering directory `/home/gs'
proc ireclen=255 lines=yes  include=/oracle/product/9.2.0/proc/lib
iname=dump_tab include=. include=/oracle/product/9.2.0/precomp/public
include=/oracle/product/9.2.0/rdbms/public
include=/oracle/product/9.2.0/rdbms/demo
include=/oracle/product/9.2.0/plsql/public
include=/oracle/product/9.2.0/network/public

Pro*C/C++: Release 9.2.0.3.0 - Production on Tue Nov 11 17:54:06 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

System default option values taken from:
/oracle/product/9.2.0/precomp/admin/pcscfg.cfg

make[1]: Leaving directory `/home/gs'
/usr/bin/gcc -I. -g  -c dump_tab.c
/usr/bin/gcc -o dump_tab -L/oracle/product/9.2.0/precomp/lib/
-L/oracle/product/9.2.0/lib/ -L/oracle/product/9.2.0/lib/stubs/ -t -o dump_tab \
dump_tab.o -L/oracle/product/9.2.0/lib -lclntsh `cat
/oracle/product/9.2.0/lib/ldflags`   `cat /oracle/product/9.2.0/lib/sysliblist`
-ldl -lm
dump_tab.o: In function `sqlerror_hard':
/home/gs/dump_tab.pc:87: undefined reference to `sqlca'
/home/gs/dump_tab.pc:89: undefined reference to `sqlca'
dump_tab.o: In function `process_1':
/home/gs/dump_tab.pc:110: undefined reference to `sqlca'
/home/gs/dump_tab.pc:110: undefined reference to `sqlca'
/home/gs/dump_tab.pc:117: undefined reference to `sqlca'
dump_tab.o:/home/gs/dump_tab.pc:117: more undefined references to `sqlca' follow
/usr/bin/ld: link errors found, deleting executable `dump_tab'
/usr/bin/ld: mode elf_i386
/usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crt1.o
/usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crti.o
/usr/lib/gcc-lib/i386-redhat-linux/2.96/crtbegin.o
dump_tab.o
-lclntsh (/oracle/product/9.2.0/lib//libclntsh.so)
-ldl (/oracle/product/9.2.0/lib/stubs//libdl.so)
-lm (/oracle/product/9.2.0/lib/stubs//libm.so)
-lpthread (/oracle/product/9.2.0/lib/stubs//libpthread.so)
-lnsl (/oracle/product/9.2.0/lib/stubs//libnsl.so)
-ldl (/oracle/product/9.2.0/lib/stubs//libdl.so)
-lm (/oracle/product/9.2.0/lib/stubs//libm.so)
/oracle/product/9.2.0/lib/stubs/libc.so.6
/usr/lib/gcc-lib/i386-redhat-linux/2.96/crtend.o
/usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crtn.o
collect2: ld returned 1 exit status
make: *** [dump_tab] Error 1

          Followup:

can you successfully compile and link the demo programs?

      Rating: 4
Import
      Performance in 10g  November 12, 2003
Reviewer:
      Orca  from Germany
Hi,
i have read that the performace of importing data has increased 5x - 10x with
oracle 10g
can you explain how this is possible?
Orca

          Followup:

direct path parallel imports using the 10g data pump

(and its estimated higher then 10x even)

      Rating: 4
I could compile
      the demo programs  November 12, 2003
Reviewer:
      gs
Thanks Tom for your response,

I can compile the demo programs without any error, if I am in the same directory
where the demo programs are. Which include library has these sqlca?

[oracle@dev13 proc]$ make -f demo_proc.mk cpdemo1
make -f /oracle/product/9.2.0/precomp/demo/proc/demo_proc.mk
OBJS=cpdemo1.o EXE=cpdemo1 build
make[1]: Entering directory `/u01/oracle/product/9.2.0/precomp/demo/proc'
proc cpool=yes threads=yes iname=cpdemo1

Pro*C/C++: Release 9.2.0.3.0 - Production on Wed Nov 12 11:05:28 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

System default option values taken from:
/oracle/product/9.2.0/precomp/admin/pcscfg.cfg

/usr/bin/gcc  -O3  -trigraphs -fPIC -DPRECOMP -I.
-I/oracle/product/9.2.0/precomp/public -I/oracle/product/9.2.0/rdbms/public
-I/oracle/product/9.2.0/rdbms/demo -I/oracle/product/9.2.0/plsql/public
-I/oracle/product/9.2.0/network/public -DLINUX -D_GNU_SOURCE
-D_LARGEFILE64_SOURCE=1 -D_LARGEFILE_SOURCE=1 -DSLTS_ENABLE -DSLMXMX_ENABLE
-D_REENTRANT -DNS_THREADS    -c cpdemo1.c
/usr/bin/gcc -o cpdemo1 cpdemo1.o -L/oracle/product/9.2.0/lib/ -lclntsh `cat
/oracle/product/9.2.0/lib/ldflags`   `cat /oracle/product/9.2.0/lib/sysliblist`
-ldl -lm
make[1]: Leaving directory `/u01/oracle/product/9.2.0/precomp/demo/proc'

============================================
But, when I use the Makefile, I get the following error, eventhough I am in the
same directory where the demo programs are.

[oracle@dev13 proc]$ pwd
/oracle/product/9.2.0/precomp/demo/proc
[oracle@dev13 proc]$ export SOURCE=cpdemo1.pc
[oracle@dev13 proc]$ export TARGET=cpdemo1
[oracle@dev13 proc]$ export ORACLE_PROC_MAKEFILE=demo_proc.mk

[oracle@dev13 proc]$ make
demo_proc.mk:34: warning: overriding commands for target `cpdemo1'
Makefile:2: warning: ignoring old commands for target `cpdemo1'
make -f /oracle/product/9.2.0/precomp/demo/proc/demo_proc.mk
PROCFLAGS="ireclen=255 lines=yes  include=/oracle/product/9.2.0/proc/lib"
PCCSRC=cpdemo1 I_SYM=include= pc1
make[1]: Entering directory `/u01/oracle/product/9.2.0/precomp/demo/proc'
proc ireclen=255 lines=yes  include=/oracle/product/9.2.0/proc/lib iname=cpdemo1
include=. include=/oracle/product/9.2.0/precomp/public
include=/oracle/product/9.2.0/rdbms/public
include=/oracle/product/9.2.0/rdbms/demo
include=/oracle/product/9.2.0/plsql/public
include=/oracle/product/9.2.0/network/public

Pro*C/C++: Release 9.2.0.3.0 - Production on Wed Nov 12 11:17:08 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

System default option values taken from:
/oracle/product/9.2.0/precomp/admin/pcscfg.cfg

make[1]: Leaving directory `/u01/oracle/product/9.2.0/precomp/demo/proc'
proc cpool=yes threads=yes iname=cpdemo1

Pro*C/C++: Release 9.2.0.3.0 - Production on Wed Nov 12 11:17:09 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

System default option values taken from:
/oracle/product/9.2.0/precomp/admin/pcscfg.cfg

/usr/bin/gcc -I. -g  -c cpdemo1.c
make -f /oracle/product/9.2.0/precomp/demo/proc/demo_proc.mk OBJS=cpdemo1.o
EXE=cpdemo1 build
make[1]: Entering directory `/u01/oracle/product/9.2.0/precomp/demo/proc'
/usr/bin/gcc -o cpdemo1 cpdemo1.o -L/oracle/product/9.2.0/lib/ -lclntsh `cat
/oracle/product/9.2.0/lib/ldflags`   `cat /oracle/product/9.2.0/lib/sysliblist`
-ldl -lm
cpdemo1.o: In function `main':
/oracle/product/9.2.0/precomp/demo/proc/cpdemo1.c:223: undefined reference to
`sqlca'
/oracle/product/9.2.0/precomp/demo/proc/cpdemo1.c:255: undefined reference to
`sqlca'
/oracle/product/9.2.0/precomp/demo/proc/cpdemo1.c:276: undefined reference to
`sqlca'
/oracle/product/9.2.0/precomp/demo/proc/cpdemo1.c:276: undefined reference to
`sqlca'
/oracle/product/9.2.0/precomp/demo/proc/cpdemo1.c:326: undefined reference to
`sqlca'
cpdemo1.o:/oracle/product/9.2.0/precomp/demo/proc/cpdemo1.c:347: more undefined
references to `sqlca' follow
collect2: ld returned 1 exit status
make[1]: *** [build] Error 1
make[1]: Leaving directory `/u01/oracle/product/9.2.0/precomp/demo/proc'
make: *** [cpdemo1] Error 2

          Followup:

sqlca is just a data structure.

did you modify my code?

      Rating: 3
No! I did not
      do any modification.  November 12, 2003
Reviewer:
      gs
No Tom,

I just copy and pasted the source. Also the demo program was giving the same
error when I use the Makefile.

This is the Makefile I have.

% cat Makefile
$(TARGET): $(SOURCE) $(SOURCE:.pc=.c) $(SOURCE:.pc=.o)
        $(CC) $(LDFLAGS) -t -o $(TARGET) \
        $(SOURCE:.pc=.o) -L$(ORACLE_HOME)/lib $(PROLDLIBS)

include $(ORACLE_PROC_MAKEFILE)

PROCFLAGS= ireclen=255 lines=yes $(PROC_ENV_FLAGS) \
        include=$(ORACLE_HOME)/proc/lib
PROFLAGS=$(PROCFLAGS)

CFLAGS=-I. -g $(CC_ENV_FLAGS)

Note: I exported the SOURCE, TARGET and ORACLE_PROC_MAKEFILE as shell variables.

Thanks,

      Rating: 3
Sorry, the prev
      statement was not 100% true  November 12, 2003

Reviewer:  gs
I combined certain broken lines into one.

Now, I just copy and paste your source as it is and compiled tyhe program.
Absolutely no changes. Still I get the following error.

[gs@dev13 gs]$ export SOURCE=dump_tab.pc
[gs@dev13 gs]$ export TARGET=dump_tab
[gs@dev13 gs]$ export
ORACLE_PROC_MAKEFILE=/oracle/product/9.2.0/precomp/demo/proc/demo_proc.mk
[gs@dev13 gs]$ make
make -f /oracle/product/9.2.0/precomp/demo/proc/demo_proc.mk
PROCFLAGS="ireclen=255 lines=yes  include=/oracle/product/9.2.0/proc/lib"
PCCSRC=dump_tab I_SYM=include= pc1
make[1]: Entering directory `/home/gs'
proc ireclen=255 lines=yes  include=/oracle/product/9.2.0/proc/lib
iname=dump_tab include=. include=/oracle/product/9.2.0/precomp/public
include=/oracle/product/9.2.0/rdbms/public
include=/oracle/product/9.2.0/rdbms/demo
include=/oracle/product/9.2.0/plsql/public
include=/oracle/product/9.2.0/network/public

Pro*C/C++: Release 9.2.0.3.0 - Production on Wed Nov 12 15:04:18 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

System default option values taken from:
/oracle/product/9.2.0/precomp/admin/pcscfg.cfg

make[1]: Leaving directory `/home/gs'
/usr/bin/gcc -I. -g  -c dump_tab.c
/usr/bin/gcc -o dump_tab -L/oracle/product/9.2.0/precomp/lib/
-L/oracle/product/9.2.0/lib/ -L/oracle/product/9.2.0/lib/stubs/ -t -o dump_tab \
dump_tab.o -L/oracle/product/9.2.0/lib -lclntsh `cat
/oracle/product/9.2.0/lib/ldflags`   `cat /oracle/product/9.2.0/lib/sysliblist`
-ldl -lm
dump_tab.o: In function `sqlerror_hard':
/home/gs/dump_tab.pc:87: undefined reference to `sqlca'
/home/gs/dump_tab.pc:89: undefined reference to `sqlca'
dump_tab.o: In function `process_1':
/home/gs/dump_tab.pc:110: undefined reference to `sqlca'
/home/gs/dump_tab.pc:110: undefined reference to `sqlca'
/home/gs/dump_tab.pc:118: undefined reference to `sqlca'
dump_tab.o:/home/gs/dump_tab.pc:118: more undefined references to `sqlca' follow
/usr/bin/ld: link errors found, deleting executable `dump_tab'
/usr/bin/ld: mode elf_i386
/usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crt1.o
/usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crti.o
/usr/lib/gcc-lib/i386-redhat-linux/2.96/crtbegin.o
dump_tab.o
-lclntsh (/oracle/product/9.2.0/lib//libclntsh.so)
-ldl (/oracle/product/9.2.0/lib/stubs//libdl.so)
-lm (/oracle/product/9.2.0/lib/stubs//libm.so)
-lpthread (/oracle/product/9.2.0/lib/stubs//libpthread.so)
-lnsl (/oracle/product/9.2.0/lib/stubs//libnsl.so)
-ldl (/oracle/product/9.2.0/lib/stubs//libdl.so)
-lm (/oracle/product/9.2.0/lib/stubs//libm.so)
/oracle/product/9.2.0/lib/stubs/libc.so.6
/usr/lib/gcc-lib/i386-redhat-linux/2.96/crtend.o
/usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crtn.o
collect2: ld returned 1 exit status
make: *** [dump_tab] Error 1

      Rating: 3
with explicit
      make it is working  November 13, 2003
Reviewer:
      gs
I copied the file to the demo directory and tried the following. It worked. Now,
I will try chamging the Makefile and see whether it works.

% cd /oracle/product/9.2.0/precomp/demo/proc/
% cp -p ~gs/dump_tab.pc .
% make -f demo_proc.mk build EXE=dump_tab OBJS=dump_tab.o

Also, do you have any program which will dump the LOB also?

thanks,

          Followup:

no, i don't have a lob program right now.

      Rating: 3
Also, How would
      I delimit the columns?  November 13, 2003

Reviewer:  gs
Tom,

I have a need to enclose the char/varchar columns in ". I was thinking, while
sending the query input, make these columns as '"' || replace(charcol,'"','""')
|| '"'

ie, select intcol, '"' || replace(  charcol,'"','""') || '"', .. from table?

Can it be easily done in the Pro*C program rather than using "REPLACE" function
(which may be slow) when I have to dump 100Ks of rows.?

Thanks,

          Followup:

replace is going to be pretty darn fast -- given that YOU would have to
implement replace all by yourself!!!  just use replace.

      Rating: 4
Thanks Tom! I
      have few more questions  November 14, 2003

Reviewer:  gs
you are using ,

static int lengths[] =
{ -1, 0, 45, 0, 0, 0, 0, 0, 2000, 0, 0,
 18, 25, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 512, 2000 };

Is the data type mapping stored somewhere in data base as table or view, ie 1 ->
varchar, 2 -number, 12 -> 'DATE', 23 -> 'RAW'  etc? (I got the above from the
dba_tab_cols view definition). I don't see type code such as 23, 24. Are you
setting the length of LONG variables to 2000? what would be the maximum value I
can set for a long?

Which sqlda variable has the datattype code or data type name? Is it 'T'?

Thanks,

          Followup:

all this and more is in the pro*c manual (whats in the sqlda)

but yes, you can use user|all|dba_tab_columns to see the type codes as well

i wrote this in version 7, i used 2000 as the default length of a long in that
release.  you can adjust upto at least 4000 in SQL.

      Rating: 4
Direct I/O
      improved imp from 50 hours to 2 hours  November 14, 2003

Reviewer:  Clement Charbonnet  from Houston, TX USA

I was using exp/imp to upgrade a 21 GB database from 8.1.7.4 to 9.2 on Solaris.
The import was taking 50 hours to complete. The database has a table that is 20
GB and contains a blob column.  I had our system administrators turn direct I/O
on the data file system and reran the import.  It was able to complete in less
than two hours.

There is something about importing lobs that is extremely slow when direct I/O
is turned off.  We have imported other large databases that do not contain lobs
with no performance problems.

We normally keep direct I/O turned on for the redo log file systems, but not the
data file systems.

          Followup:

i would have transported that stuff -- it would be as fast as a copy.

your lobs were probably "nocache", you are caching them now.

      Rating: 5
RECORDLENGTH
      parameter  November 15, 2003
Reviewer:
      Sudhir  from Lexington, KY
Tom,

On this page I didn't see any reference made to RECORDLENGTH parameter for exp
command. I found speed is close to 8 times faster when I set it to about 65000
value (exact number is slightly smaller and exp does that automatically). Is
this your experience? Of course this is for direct only.

Thanks

November 16, 2003 Reddi -- Thanks for the question regarding
      "pragma exception init, and raise application error", version 8.1.7

      You Asked
Tom
What is the difference between pragma exception_init , and raise application
error( which is a subprogram from dbms_standard package).

In what situations do you use a Raise application_error subprogram.

Thanks

      and we said...
pragma exception init turns an Oracle Error into a named exception.  If a
database operation raises an ORA-00054 "resource busy", you would have to code:

....
exception
  when others then
      if (sqlcode=-54) then
            .... deal with it.
      else
            RAISE;
      end if;

if you pragma exception init'ed this error to some named exception RESOURCE_BUSY
, you can code:

....
    pragma exception_init( resource_busy, -54 );
begin
....
exception
   when resource_busy then
       .... deal with it.
end;

much cleaner (i hate when others -- should be outlawed).

Raise_application_error is used to RAISE an error - exception_init is used to
deal with errors (i guess you could say they are opposites in a way).

I use raise_application_error when I want to setup a custom error message to be
returned to a client application

   raise_application_error( -20001, 'You cannot do that!' );

If I just fling a named exception, the client gets the truly usless error
message "unhandled user defined exception".  If I use raise_application_error --
the client gets whatever I send back.
Review &
      Followup

      Rating:
      5
  May 14, 2001
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating: 4
Which table
      stores the mapping of user defined exception
      numbers  January 28, 2003
Reviewer:  ss
       from India
When we use pragma exception_init (ABCDXYZ, -2000n )
which table stores this mapping in the database?

Thanks

          Followup:

none, well, its in pcode stored in the diana netlibs in the data dictionary but
not in any sort of format you and I could get.

It is part of the code, it isn't stored anywhere relationaly

      Rating: 5
ORA-00054 in
      another session with PRAGMA AUTONOMOUS TRANSACTION  November
      14, 2003
Reviewer:  Vera  from D.C.
Hi, Tom,
As always, thank you for everything you do for us. I hate to bother you, but I
did not find an answer anywhere yet. In your book you listed some caveats, but
in my case it is probably something different.

I created a simple test package with 3 procedures. One of them PRAGMA
AUTONOMOUS_TRANSACTION. When I run the package  in one session everything is
fine. After it completed, I go to another session and try to execute it and I am
getting ORA-00054:

Session 1 (everything is fine):
13:02:53 SQL> show user
USER is "GRO_TEST2"
13:07:51 SQL> exec vera_test.main;
1
1

PL/SQL procedure successfully completed.

Session 2:

13:02:23 SQL> show user
USER is "GRO_TEST2"
13:08:31 SQL> exec vera_test.main;
BEGIN vera_test.main; END;

*
ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified
ORA-06512: at "IPVFB_TEST2.VERA_TEST", line 10
ORA-06512: at "IPVFB_TEST2.VERA_TEST", line 61
ORA-06512: at line 1

Line 10 is: EXECUTE IMMEDIATE 'TRUNCATE TABLE v'; and this line belongs to
"normal" procedure, not Autonomous. Line 61 is a call to this "normal"
procedure.

I am trying to create mechanism of checking on status of a huge process. Right
now it commits for each row and user can always see how many cases and customers
were processed, but finally I was allowed to get rid of commits and truncates
inside of loops so I replaced database tables with PL/SQL tables, "query" them
and commit only once - at the end of transaction. Tom! It used to run for 12-30
something hours, well, my first draft runs for 5 hours in absolutely identical
environment! However, since user still would like to have a mechanism of
checking on the process, I am trying to implement Autonomous Transaction and
from the very first try I encountered the above problem.
I do not use datanase links.
My questions are:
--Why Oracle gives me ORA-00054?
--Is Autonomous Transaction the only way to keep user   updated on transaction
status?
--Is it the best way?

I have
Oracle8i Enterprise Edition Release 8.1.7.4.1 - Production
PL/SQL Release 8.1.7.4.0 - Production
CORE    8.1.7.2.1       Production
TNS for 32-bit Windows: Version 8.1.7.4.0 - Production
NLSRTL Version 3.4.1.0.0 - Production

Thank you again

          Followup:

simple, small concise test case that fits on a screen

that is what one would need to make any sort of comments.

but in any case -- you are using the wrongest approach for what you are trying
to do.

look in my book "Expert one on one Oracle" for information about
dbms_application_info (in appendix).  it is exactly what you want.

      Rating: 5
ORA-00054 in
      another session with PRAGMA AUTONOMOUS   November 14, 2003

Reviewer:  A reader
One more thing, Tom.
The table I got an error about is locked by my first session even though it does
not do anything with it and within the first session I can truncate it whatever
I want, but from another session.

      Rating: 5
PRAGMA
      AUTONOMOUS   November 15, 2003
Reviewer:
      Vera  from DC
Test example:

Session 1:

SQL> SHOW USER
USER is "GRO_TEST2"
SQL> DESC VERA_TEST_TBL;
 Name                            Null?    Type
 ------------------------------- -------- ----
 ID                                       NUMBER

SQL> DESC VERA_TEST_AUTH;
 Name                            Null?    Type
 ------------------------------- -------- ----
 ID                                       NUMBER

SQL> CREATE OR REPLACE PACKAGE VERA_TEST
  2  AS
  3  PROCEDURE normal_proc;
  4  PROCEDURE pragma_proc;
  5  PROCEDURE nornal_proc1;
  6  PROCEDURE MAIN;
  7  v_global_id NUMBER;
  8  END;
  9  /

Package created.

SQL> CREATE OR REPLACE PACKAGE BODY vera_test
  2  AS
  3  PROCEDURE normal_proc
  4  AS
  5  BEGIN
  6  EXECUTE IMMEDIATE
  7  'TRUNCATE TABLE vera_test_tbl';
  8  insert into vera_test_tbl (id) values(1);
  9  SELECT id INTO v_global_id FROM vera_test_tbl;
 10  END;
 11  PROCEDURE pragma_proc
 12  AS
 13  PRAGMA AUTONOMOUS_TRANSACTION;
 14  BEGIN
 15  DELETE FROM vera_test_auth;
 16  INSERT INTO vera_test_auth VALUES(v_global_id);
 17  DELETE FROM vera_test_tbl;
 18  COMMIT;
 19  END;
 20  PROCEDURE nornal_proc1
 21  AS
 22  v_id NUMBER;
 23  v_cnt NUMBER;
 24  BEGIN
 25
 26  SELECT id INTO v_id FROM vera_test_auth;
 27  DBMS_OUTPUT.PUT_LINE (v_id);
 28  END;
 29  PROCEDURE MAIN
 30  AS
 31  BEGIN
 32  normal_proc;
 33  pragma_proc;
 34  nornal_proc1;
 35  END;
 36  END;
 37  /

Package body created.

SQL> SET SERVEROUTPUT ON
SQL> EXEC vera_test.MAIN;
1

PL/SQL procedure successfully completed.
-----------------------------------

SESSION 2:

SQL> SHOW USER
USER is "GRO_TEST2"
SQL> EXEC VERA_TEST.MAIN;
BEGIN VERA_TEST.MAIN; END;

*
ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified
ORA-06512: at "IPVFB_TEST2.VERA_TEST", line 6
ORA-06512: at "IPVFB_TEST2.VERA_TEST", line 32
ORA-06512: at line 1
------------------------------
Tom, you said:
but in any case -- you are using the wrongest approach for what you are trying
to do.
I think you mean I should use TYPEs, not PL/SQL tables? I will, but right now I
am trying to move all the logic out of database tables truncating and commiting
and demonstrate my team that their approach of commiting in order to not
generate rollback was wrong.
Thank you for pointing out at the Appendix!

          Followup:

umm, in session 1 -- did you "commit" before running session 2?  if not, session
2 is trying to truncate a table that session 1 has uncommited inserts and that
is a "no no"

I want you to use DBMS_APPLICATION_INFO and the set_session_longops API or
set_client_info/set_module_info apis.

we've already written all of the logic you need, you just need to use it.

no inserts
no types
no logic
just use the supplied API's to let other sessions know about your progress.

      Rating: 5
PRAGMA
      AUTONOMOUS   November 16, 2003
Reviewer:
      Vera
Tom,
I got you. You are an ace. Everybody knows it, but its worth repeating!

November 16, 2003 Baqir -- Thanks for the question regarding
      "tablespace fragmentation or something else", version 9.2.0.2

      You Asked
A tablespace  “indexes_ts” was created  to store indexes as follows:

create tablespace indexes_ts
datafile  ‘/oracle/home/indexes-ts.dbf’ size 1m reuse autoextend on next 500K
extent management local autoallocate
segment space management auto;

  This tablespace grew to ~ 18G due to some kind of fragmentation or some thing
else.  A couples of weeks back the tablespace size was ~ 5G. Since then ONLY few
new indexes were created.  OEM shows a lot of free space into this tablespace.

Some indexes were created with pctfree =0 and some with pctfree= 10. Is this an
issue?

Just to verify, the production database was exported and then imported to
another machine shows tablespace “indexes_ts” size to ~ 5G .
I have no idea what have caused this tablespace to grow to ~ 18G.
Since this happened on our production system.

Technically LMT should take care of free space/fragmentaion issue??

1. I used the following sql query to find out if any of the indexes have
"high blevel" and then rebuild them.

select index_name, blevel, decode (blevel, 0, 'OK BLEVEL', 1, 'OK BLEVEL',
2,'OK BLEVEL',3,'OK BLEVEL',4,'OK BLEVEL','BLEVEL HIGH') OK
from dba_indexes
where owner = 'BDR';

2. Yes, I do drop and create indexes.

      and we said...
The tablespace is not fragmented, you are just using that much space over time.

You are doing this to yourself.  Your indexes want to be big and fat.  You keep
putting them on a diet.  Whilst in the diet -- you need room for the before and
after image of that index (big fat index, smaller skinnier index)

Read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6601312252730
for my opinion on automated index rebuilds.   Tell me -- did you ever quantify
the POSITIVE and NEGATIVE impacts of you doing this???? I bet not -- you might
find that after a rebuild you too generate lots more redo, you too run slower
then before. Oh sure, for a day or two the LIO's on the index for reads go down
but the index maintenance that is taking place to let the index get big and fat
again more then offsets that.

The space is not fragmented.  Fragmented would mean there are holes in there
that cannot be reused.  They can ALL be reused - it is just that you -- by
rebuilding -- are using 2x at least the space needed.

I find 99% of the time, the index rebuilds just

o waste your time and energy
o waste system resources
o cause slower initial performance
o go right back to where they where in the first place

(bitmap indexes are the other 1% that don't fall into this category)....

So, if you want to get to the bottom of this -- quantify with hard numbers WHY
you are doing these rebuilds -- prove that each one is worth the time and energy
and additional space.  Then go for it.

Review &
      Followup

      Rating:
      5
fragmentation  December 22, 2002
Reviewer:
      Baqir Hussain  from Emeryville, CA USA
Tom,
I 100% agree with you on rebuilding indexes does not help at all and I stopped
doing it. I gather statistics on a table in every two weeks due to more than 10%
of DML activity. One of the 5 indexes shows "blevel high" and cost and elapsed
both go high. I tried to rebuild it but still showing "blevel high" and query
was taking more elapsed time with high cost. Then I have to drop and recreate it
and the particular query started to working fine. I have few question:
1. Please explain why I am getting this kind of behavior?
2. What is the best way to drop and recreate indexes by keeping in mind that the
tablespace would not grow exponentially?
3. Since the tablespace has grown too much due to indexes rebuilding.  How can I
fix this problem to get the tablespace into its actuall size??
Appreciate your help in this regard.

          Followup:

Don't know what to tell you as I cannot reproduce (well, actually, was having a
really hard time getting a blevel of 5 -- couldn't get it).

The rebuild would take the blevel down -- so I cannot explain that, perhaps you
did not reanalyze the index?  rebulid leaves the stats intact.

if you drop and create - the tablespace will not grow, it is when you rebuild
and for a moment have 2 of them that it will.

if you alter index rebuild NEW_TABLESPACE -- for each index (effectively moving
them), you'll be able to drop this tablespace, although -- why bother?  they are
just going to grow again!

      Rating: 5
indexes
      rebuild  December 23, 2002
Reviewer:  Baqir
      Hussain  from Emeryville, CA USA
Tom,
There is almost 10GByte of waste space,therefore, I would like to move to a new
tablespace as
alter index <index_name> rebuild tablespace <new_tablespace>;
My question is:
1. Do I have to gather statistics on a table which indexes have been moved to
new tablespace??

Thanks

          Followup:

you can if you like -- else the old stats will be in place.  you can alter index
rebuild compute statistics all in one step.

      Rating: 3
Bitmap Index in
      a LMT [Oracle9i Rel. 2 on Windows 2000]  November 15, 2003

Reviewer:  Prince  from Pakistan
Respected Tom,

I have a table VERIFIC with 24,000,000 rows in it. There is a bitmap index
VERIFIC_IS_USED_IDX on its column IS_USED [having data type CHAR(1)]. I have
created a LMT named INDX02 with uniform extent allocation of size 80K and issued
the following command:-

SQL> alter index VERIFIC_IS_USED_IDX
  2  rebuild tablespace INDX02
  3  storage (initial 80K)
  4  nologging;

Result information from dba_segments is as under

OWNER          = APPS1
SEGMENT_NAME   = VERIFIC_IS_USED_IDX
INITIAL_EXTENT = 80K
NEXT_EXTENT    = 80K
PCT_INCREASE   = 0
EXTENTS        = 117
BYTES          = 9,584,640

This index of size 9.14MB has been extended to 800MB just after one day. I am
really surprised to see the expansion behaviour of a bitmap index using LMT. I
was mentaly prepared for some expansion but this rate is too high :). Now I am
thinking if either there was any fault at my end like wrong initial extent size
(i.e. 80K) or anything else?

This table was created 2 years before. What extent size do you recommend for
such kind of index?

Regards,

          Followup:

are you updating that column?

if so, you have just done the worst thing on the planet with regards to bitmaps.
 they are known to deteriorate rapidly when updated a row at a time.  it is the
very NATURE of the bitmap index for that to happen.

Now, you tell me -- why are you indexing this column.  what ist he goal here?
is it that you need to find one of "where is_used = 'Y' " or where is_used = 'N'
rapidly?

if so, there is a much much better way.  tell us the GOAL of this here index.

      Rating:
      2
GOAL  November 15, 2003
Reviewer:
      Prince  from Pakistan
Well tom! good question. You're somehow right about the reason of index is to
fasten the reporting on the base of this column. Moreover when I have tried to
replicate this table asynchronously, replication errors occur like "No Data
Found." so when I put the bitmap index on that column it says nothing and hence
replication is working fine. yes this column is highly updateable and you are
right possible flag values are Y and N. so what do you recommend now to fullfil
both of the needs.

Regards,

          Followup:

what do you mean by "fasten the reporting on the base of this column".  what
question do you ask of this data that requires this index (then I can tell you
what you need to do)

this bitmap index could not in any way fix the "NO DATA FOUND" error with async
replication -- that had nothing to do with anything.

      Rating: 3
To make fast
      the reporting based on this column  November 15, 2003

Reviewer:  Prince  from Pakistan
I mean to say.. Yes I feel need to find one of "where is_used = 'Y' " or where
is_used = 'N' rapidly?
Secondly that column is also having a foreign key constraint on it. What do you
say Tom! should I not index this column for a successful replication process
too.

Thanks for your prompt response.

          Followup:

just full scan then and stop when you find the first one?  why do you need an
index at all??

you have a foreign key on a Y/N column??? sounds like

is_used   char(1) check (is_used in ('Y','N'))

would be more appropriate.

indexes have no bearing whatsoever on replication, none.

      Rating: 5
How to find
      Tablespace fragmentation?  November 21, 2003

Reviewer:  A reader

          Followup:

use LMT's (locally managed) and you'll never ever worry about it again.

November 16, 2003 ps -- Thanks for the question regarding
      "different states of integrity constraints", version 8.1.7

      You Asked
Tom

An integrity constraint can be in one of the following states ----
1. Disabled novalidate - 2. Disabled validate - 3. Enabled novalidate4. Enabled
validate

What is the difference between  3 and 4 .. when a constraint is enabled how can
it not validate , the same with 2, when a constrint is disabled then how can it
validate any inserts into the table.

and

shouddnt there be a deferred kind of constraint state..

Can you please answer my question in specific but also throw some light on
constraint states in general.

Please keep in mind that Iam a junior developer, while giving your answer..

Thank you

      and we said...
well, there can also be the RELY and NORELY attribute as well.

the difference between 3 and 4 is that #3 has the constraint enabled but the
data that was in the table is not verified for correctness:

ops$tkyte@ORA817.US.ORACLE.COM> create table t ( x int );

Table created.

ops$tkyte@ORA817.US.ORACLE.COM> insert into t values (0);

1 row created.

ops$tkyte@ORA817.US.ORACLE.COM> alter table t add constraint check_cons check (
x > 0 ) enable novalidate;

Table altered.

ops$tkyte@ORA817.US.ORACLE.COM> insert into t values (0);
insert into t values (0)
*
ERROR at line 1:
ORA-02290: check constraint (OPS$TKYTE.CHECK_CONS) violated

So, the data you loaded is in the "trust me mode".  It is typically used in a
data warehouse where you have loaded tons of data that was already scrubbed.

Enabled validate validates the data as well:

ops$tkyte@ORA817.US.ORACLE.COM> create table t ( x int );

Table created.

ops$tkyte@ORA817.US.ORACLE.COM> insert into t values (0);

1 row created.

ops$tkyte@ORA817.US.ORACLE.COM> alter table t add constraint check_cons check (
x > 0 ) enable validate;
alter table t add constraint check_cons check ( x > 0 ) enable validate
                             *
ERROR at line 1:
ORA-02293: cannot validate (OPS$TKYTE.CHECK_CONS) - check constraint violated

I don't know what you mean by a "deferred constraint state".  Constraints may be
deferrable meaning they are not checked until commit time.

Review &
      Followup

      Rating: 4
Not very
      intuitive on RELY/NORELY  December 18, 2001

Reviewer:  Mike  from Atlanta
I have been reading Oracle doc on the constraint states of RELY/NORELY,
There is no good example to illustrate these. Could give us some examples of
these?

          Followup:

<quote>

RELY/NORELY

specifies whether an enabled constraint is to be enforced. Specify RELY to
enable an existing constraint without enforcement. Specify NORELY to enable and
enforce an existing constraint. The default is NORELY.

Unenforced constraints are generally useful only with materialized views and
query
rewrite. Depending on the QUERY_REWRITE_INTEGRITY mode (see "ALTER SESSION" on
page 7-83), query rewrite can use constraints that are enabled with or without
enforcement to determine join information.
</quote>

RELY is sort of like a "trust me" mode.  You can use it in a data warehouse
where you have loaded "scrubbed" data.  You can have a constraint enabled
without being enforced (trust me -- that column  is UNIQUE, that foreign key --
its OK, that check constraint -- its accurate).  We can use that in query
rewrites against materialized views.  If you are interested in a larger example
showing the query rewrite (takes a bit of setup and walking through) I have a
chapter on materialized views in my book that describes the use of this setting
and why its relevant.

      Rating:
      2
Confusing  December 18, 2001
Reviewer:
      Kulguru
Iam confused

the following insert should succeed as, the the constraint is enable to no
validate , that is validation should not occur and as such the insert should
succeed.

ops$tkyte@ORA817.US.ORACLE.COM> alter table t add constraint check_cons check (
x > 0 ) enable novalidate;

Table altered.

ops$tkyte@ORA817.US.ORACLE.COM> insert into t values (0);
insert into t values (0)
*
ERROR at line 1:
ORA-02290: check constraint (OPS$TKYTE.CHECK_CONS) violated

Kindly clarify

          Followup:

novalidate is used to put a constraint on a table with EXISTING data and have it
get turned on without checking each row (eg: in a data warehouse where you
"scrubbed" the data)

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 0 );
1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t add constraint check_cons check
( x > 0 ) enable novalidate ;

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( -1 );
insert into t values ( -1 )
*
ERROR at line 1:
ORA-02290: check constraint (OPS$TKYTE.CHECK_CONS) violated

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

         X
----------
         0

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

What you might be looking for is a DISABLED constraint.

      Rating: 4
deferrable -
      but is it deferred or immediate right now...   December 18,
      2001
Reviewer:  A reader  from ca, usa

Having played with this (based on your previous writings), my understanding is
that the constraint you want to temporarily violate must be deferrable. To
temporarily violate it though, you need to put your session into a state where
the constraints are deferred, and then you can manually set them bad to
immediate enforcement, or let the commit/rollback restore the state for you once
you are done with the temporary violation.

SQL> CREATE TABLE dept
  2     (deptno   NUMBER NOT NULL PRIMARY KEY ,
  3      deptname VARCHAR2(30) NOT NULL );

Table created.

SQL>
SQL> CREATE TABLE emp
  2     (empno   NUMBER NOT NULL PRIMARY KEY,
  3      empname  VARCHAR2(20) NOT NULL,
  4      deptno   NUMBER,
  5         CONSTRAINT emp_fk1 foreign key (deptno) references dept(deptno)
DEFERRABLE);

Table created.

SQL>
SQL> insert into dept values (10, 'Sales');
1 row created.

SQL> insert into emp values (1, 'Joe', 10);

1 row created.

SQL> commit;

Commit complete.

SQL>
SQL> -- see if we can do it with just deferrable constraints
SQL> begin
  2   update dept set deptno = 100 where deptno = 10;
  3   update emp set deptno = 100 where deptno = 10;
  4  end;
  5  /
begin
*
ERROR at line 1:
ORA-02292: integrity constraint (SCOTT.EMP_FK1) violated - child record found
ORA-06512: at line 2

SQL> -- no - we need to set the constraints to deferred in this session first
SQL> -- in pl/sql block because most applications would typically use it this
way
SQL> begin
  2  Execute immediate 'SET CONSTRAINTS ALL DEFERRED';
  3  update dept set deptno = 100 where deptno = 10;
  4  update emp set deptno = 100 where deptno = 10;
  5   -- set them back on again now that we are done
  6   -- else they are reset to default (immediate)
  7   -- on commit/rollback
  8  Execute immediate 'SET CONSTRAINTS ALL IMMEDIATE';
  9  end;
 10  /

PL/SQL procedure successfully completed.

SQL> select * from dept;

    DEPTNO DEPTNAME
---------- ------------------------------
       100 Sales

SQL> select * from emp;

     EMPNO EMPNAME                  DEPTNO
---------- -------------------- ----------
         1 Joe                         100

SQL>

          Followup:

No -- this isn't anything to do with deferrable constraints.  Deferrable
constraints are those that you can have us check upon commit instead of when the
statement executes.

What we have here are constraints that are NOT CHECKED BY ORACLE EVER.  They are
used with data warehouses where you load the data (and you are 100% sure the
data is "clean").  You need the constraints to be in place for query rewrites
and other features -- but there is no reason to revalidate that a column is
unique or satisfies some check constraint, etc.  This lets us turn the
constraint ON without verifying the existing data is OK.  YOU must be 100% SURE
the data is OK or else you will get incorrect results.

      Rating: 4
I was off the
      point a bit  December 19, 2001
Reviewer:
      A reader  from ca, usa
Sorry about my misleading posting above. It was more to do with your statement
"Constraints may be deferrable meaning they are not checked until commit time".

I discovered that commit and "SET CONSTRAINTS ALL IMMEDIATE" both cause
validation at that point and that commit/rollback and "SET CONSTRAINTS ALL
IMMEDIATE" end the "SET CONSTRAINTS ALL DEFERRED" session setting. I guess
setting the state back to immediate (in a several statement transaction) as soon
as the it is no longer required (rather than waiting for the commit) allows
errors to be trapped as soon as they occur.

Thanks for your insightful original answer.

      Rating: 3
Locking
      ?  June 10, 2003
Reviewer:  Christo
      Kutrovsky  from Ottawa, ON Canada
I just happen to discover that when validating an enabled constraint does not
lock the table for DML, can you confirm this please?

is there any special case where the table is locked (for DML), when the
constraint has been enabled but not validated? Does that apply only to Foreign
Key (my case)?

And why is this not mentioned in the documention?

          Followup:

if you just "discovered it".. why would i need to validate it -- but it does
appear you have it backwards?  if I understand you.

but anyway, if you "alter table T add constraint check_cons check
(something...)", and look at v$lock (do it on a big table, so you can)

TY        ID1        ID2      LMODE    REQUEST
-- ---------- ---------- ---------- ----------
TM      31146          0          4          0

1 row selected.

it takes a TM mode 4 lock out on the table.  That is like issuing "lock table in
share mode".  The concepts guide (chapter 20 on data concurrency and
consistency) has a table that shows what locks are compatible with what other
locks.

Now, an INSERT is going to get a LMODE=3 on that table  (row-X, row exclusive
lock).  The table in the concepts guide shows that an "S" lock is incompatible
with a "RX" lock.

All of the locks are documented in the concepts guide.

I really need "test cases" to demonstrate what it is you are doing.  "validating
an enabled constraint" -- i don't truly know what you imply by that sentence --
what operation you were really doing.

      Rating: 3
Follow
      up.  June 10, 2003
Reviewer:  Christo
      Kutrovsky  from Ottawa, ON Canada
I don't have a test case, because I would need to create a big table (which
actuall takes some time to validate)

alter table X add constraint X_FK foreign key (col) references MASTER_TABLE
NOVALIDATE;

then,

alter table X modify constraint X_FK validate;

This is the command that takes time. I maybe did a premature conclusion, but i
*think* the table was available for selects and inserts/updates .

Please correct me if I am wrong.

          Followup:

correct (it doesn't take that much to create a big enough table, I do it all of
the time on my laptop).

The modify constraint validate does not take a table level lock out.  It is not
changing the structure/definition of the table (as the alter + validate would).
It only needs a read consistent view of the table to valid since it knows that
all modified data will already conform to the constraint.

      Rating: 4
Thank
      You.  June 10, 2003
Reviewer:  Christo
      Kutrovsky  from Ottawa, ON Canada
Thank You Tom.

I could not find a confirmation about that in the Oracle documenation. Did I
miss it? I am about to submit a documentation bug about this issue.

          Followup:

There is no doc bug, we don't list specifically what each and every variation of
each and every command takes as far as locks go.

*Many* DDL's take an LMODE=4 for their duration.  SELECTS are OK, modifications
are not.

Some DDL's take an LMODE=4 for a brief instant either at the end or the
beginning (alter index rebuild online for example) and release it.

This alter valid is one that takes the 4 mode lock briefly at the beginning and
then releases it.  If you modify the table in question and then in another
session attempt to validate the constraint, you would observe:

big_table@ORA920> alter table big_table modify constraint big_table_check2
validate;
alter table big_table modify constraint big_table_check2 validate
*
ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified

That the constraint cannot be -- that is one of the DDL's that get it for a
brief moment and then release it.

Also, bear in mind that the rules change over time -- they are constantly
looking at these things and changing them -- either making them more restrictive
(typically in reaction to an identified bug -- if we didn't lock more
restrictively, there would be a problem) or less restrictive (like the alter
index rebuild ONLINE for example) to make things "easier"

      Rating: 3
Follow
      up  June 10, 2003
Reviewer:  Christo
      Kutrovsky  from Ottawa, ON Canada
Well, maybe not as a "bug" but as an "adition" to the documentation.

Before posting, I was diging trough asktom and found the following:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:950829834856
and there is a book that does mention that first enabling novalidate, and then
validating doesn't lock your table which is a pretty important "feature". I do
consider it important, and I was very amazed that it's not pointed in the
documentation because it is an important point, your table is (almost) fully
available during this (sometimes) lenghty process...

Especially given the fact the 9i is going towards more "online" tasks this would
be a valuable adition.

      Rating: 5
How to make a
      constraint "deferrable"?  September 09, 2003

Reviewer:  Kamal Kishore  from New Jersey, USA

Hi Tom,
I'm trying to make a constraint (that is NOT DEFERRABLE) back to DEFERRABLE but
getting the following error. Looked at the documentation for the MODIFY
CONSTRAINT clause and it seems to suggest the same syntax that I am using. Is
there something that I missed (or did wrong)?

How else to do this?
Thanks,

SQL> alter table emp add constraint fk_emp_deptno foreign key (deptno)
references dept(deptno)
  2  not deferrable initially immediate ;

Table altered.

SQL>
SQL> alter table emp modify constraint fk_emp_deptno deferrable ;
alter table emp modify constraint fk_emp_deptno deferrable
                                                *
ERROR at line 1:
ORA-00933: SQL command not properly ended

SQL>

          Followup:

you cannot modiy a constraint to be deferrable (or non deferrable)

you would drop and recreate the constraint.  since you know the data is valid,
you can do it without actually revalidating the data.

      Rating: 5
Sorry, forgot
      to add version...  September 09, 2003
Reviewer:
      Kamal Kishore  from New Jersey, USA
SQL> disconnect
Disconnected from Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production
SQL>

      Rating: 4
why is
      deferrable not the default  September 10, 2003

Reviewer:  A reader
It would be nice to have deferrable as default when constraints are created,
since the change requires recreate constraints. Deferrable merely adds the
capability of being made deferred. Why oracle did not make it default?

          Followup:

it would change pre-existing behavior.

it would cause primary keys to not have unique indexes created.

it was not the way it always worked in the past.

new features like this are generally something "you choose to use"

      Rating: 5
No unique
      indexes?  September 27, 2003
Reviewer:  Just an
      egg...  from Seattle WA
Re: "cause primary keys to not have unique indexes created".

I hope I rightly appreciate the DEFERRABLE index will be created at the time of
the first commit, which would make a poor default....also that being unique,
this would apply to a constraint based PRIMARY KEY USING INDEX?

And I hope VALIDATE RELY would be a kind of oxymoron, or at least illogical?

But I know I need more info around INITIALLY IMMEDIATE, if you would be so
kind....

Thanks in anticipation :-)

          Followup:

sorry, i'm not really following the train of thought here....

....
I hope I rightly appreciate the DEFERRABLE index will be created at the time of
the first commit,
.....

no, the index will be created -- if it needs to be created at all (might
already exist) at the time the constraint is added.

...
which would make a poor default
....

default what?

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:10954765239682
is about initially immediate

      Rating:
      3
Clarification  September 27, 2003

Reviewer:  Tom  from Seattle WA
On "default what?", you said immediately above that DEFERREBLE was not the
default for four reasons.

I posited that your second reason "it would cause primary keys to not have
unique indexes created.", would make DEFERRABLE a poor default option, and asked
if it would apply to a PRIMARY KEY USING INDEX. Now I'm more confused by your
follow-on statement: "no, the index will be created -- if it needs to be created
at all (might already exist) at the time the constraint is added." - which
doesn't reconcile with "it would cause primary keys to not have unique indexes
created.".

I hope that's easily clarified.

Also, the difference between NORELY and VALIDATE remains unclear, and on
IMMEDIATE, should your statement that DEFERRABLE constraints are "not checked
until commit time.", be qualified instead to say INITIALLY DEFERRED DEFERRABLE
constraints are not checked until commit time?

Thanks in advance.

          Followup:

sorry -- i couldn't pick up that meaning from this:

I hope I rightly appreciate the DEFERRABLE index will be created at the time of
the first commit, which would make a poor default.

"which would make a poor default" seems to be going with "at the time of the
first commit"

When you create a (non deferrable) primary key on a table -- if the primary key
values are NOT already indexed -- Oracle will create a UNIQUE index on those
columns.

When you create a deferrable primary key on a table -- if the primary key values
are NOT already indexed -- Oracle will create a non-UNIQUE index on those
columns.

When you create a primary key on a table and the columns are already indexed,
Oracle will just rely on that index for primary key enforcement.

I said "an index" -- never saying "a unique index" or "a non-unique", just an
index will be created if needed.

"a constraint in deferred mode" is not checked until commit time.

      Rating: 4
Of course of
      course  October 04, 2003
Reviewer:  Tom
       from Seattle
Thanks also for the related supplemental information.

So non-unique indexes and deferred constraints go together, like this?

1) Create deferred constraint, 2) load bad data, 3) clean data with help from
non-unique index, 4) drop constraint, 5) drop index, 6) recreate constraint
non-deferred and get a unique index?

          Followup:

well, i would not be into doing steps 4, 5, 6!

it would be

"create constraint.  constraint just HAPPENS to be deferrable.  if you want the
constraint checking to be deferred, set constraint CNAME deferred.  If not, just
use it as it is."

and that is it.  you just leave it that way

      Rating: 3
Integrity check
      towards the materialized view [PK on M View, FK on another
      table]  November 15, 2003
Reviewer:  Prince
       from Pakistan
Respected Tom,

I have a materialized view named my_mview having the primary key column
employee_code in a database say Y. Suppose this my_mview is completely refreshed
every night and it gets the information of employees from several tables of any
other database say Z. In database Y there is a table emp_north having column
employee_code. I wanna reference this column to employee_code in my_mview
(my_mview_employee_code_pk) in the same database. When I establish this using
foreign key constraint emp_north_employee_code_fk. It suppresses the refresh of
the materialized view...

SQL> exec dbms_mview.refresh('my_mview');
BEGIN dbms_mview.refresh('my_mview'); END;

*
ERROR at line 1:
ORA-12008: error in materialized view refresh path
ORA-02266: unique/primary keys in table referenced by enabled foreign keys
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 794
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 851
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 832
ORA-06512: at line 1

How can this type of integrity be enforced on emp_north in Oracle9i?

Regards,

          Followup:

you would need to do a couple of things

a) create a snapshot group so the refresh is performed with a DELETE + INSERT
instead of a TRUNCATE + INSERT.  Just create another dummy MV (say a snapshot of
dual) and create a refresh group with both MV's in it -- that is what you'll
refresh

b) recreate the fkey constraint on the child table to be a DEFERRABLE constraint
-- one that is validated at the end of the refresh.

then, it can work -- but bear in mind that DELETE + INSERT is going to perform
slightly differently then TRUNCATE + INSERT does now!!!!!

      Rating: 3
DELETE + INSERT
      ?  November 15, 2003
Reviewer:  Prince
       from Pakistan
Would you like to please elaborate... How can I create a snapshot group so the
refresh is performed with a DELETE + INSERT instead of a TRUNCATE + INSERT.

Regards,

          Followup:

search for

"snapshot group" dual truncate

on this site

November 16, 2003 -- Thanks for the question regarding "copy
      command", version 8.1.6

      You Asked
hello Mr Tom,
i have 5 tables and a stored procedure which collects certain
data from all the 5 tables and dumps into a sixth table called
export table.
i want to copy this export table data into a dbf file ..

so thru sqlplus i can use the copy command and it works
fine...is it possible to use the same copy command from a stored
procedure?
if not, is there an alternate way to do this?(like maybe i have
to write a batch file?!)

also is there any limit on the length of the copy command?

suppose i use a copy command like the one below:
i get an error saying "missing comma or right paranthesis"

COPY FROM CANTEST/CANCANTEST@CANDB TO SYS/MAN@ODBC:FP INSERT
CAN_EXPORT(CONFIGNAME,MSGNAME,MSGID,DLC,SIGNAME,STARTBIT,SIGSIZE,
MINVAL,MAXVAL,OFFSET,FACTOR,UOM,SIGCOMMENT,MSGCOMMENT,CODEVALUES,
ISMOTOROLA,
ISSIGNED,ISMODE,ISMODEDEP,SIGMODE,PRIORITY,NODE1,NODE2,NODE3,NODE
4,
NODE5,NODE6,NODE7,NODE8,NODE9,NODE10,NODE11,NODE12,NODE13,NODE14,
NODE15) USING SELECT
CONFIGNAME,MSGNAME,MSGID,DLC,SIGNAME,STARTBIT,SIGSIZE,
MINVAL,MAXVAL,OFFSET,FACTOR,UOM,SIGCOMMENT,MSGCOMMENT,CODEVALUES,
ISMOTOROLA,ISSIGNED,ISMODE,ISMODEDEP,SIGMODE,PRIORITY,NODE1,NODE2
,NODE3,NODE4,NODE5,NODE6,NODE7,NODE8,NODE9,NODE10,NODE11,NODE12,N
ODE13,NODE14,NODE15,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
NULL FROM CAN_EXPORT;

so i try to spool out the results into a text file

and i see only half of the copy command in the text file which
is:

SQL> COPY FROM CANTEST/CANCANTEST@CANDB TO SYS/MAN@ODBC:FP
INSERT
CAN_EXPORT(CONFIGNAME,MSGNAME,MSGID,DLC,SIGNAME,STARTBIT,SIGSIZE,
MINVAL,MAXVAL,OFFSET,FACTOR,UOM,SIGCOMMENT,MSGCOMMENT,CODEVALUES,
ISMOTOROLA,
ISSIGNED,ISMODE,ISMODEDEP,SIGMODE,PRIORITY,NODE1,NODE
missing comma or right parenthesis
SQL> SPOOL OFF

is there a work around for this ?
please help.
basically i want to export the data in the table into a dbase
dbf file..is there any other way to do this?

thankyou,

      and we said...
the COPY command is a sqlplus command only.  It is facilitated
by the fact that sqlplus can connect to different databases
simultaneously.  PLSQL cannot.

If you want to use the COPY command to dbase, you will be using
SQLPLUS as that can use ODBC and open a separate connection to
ODBC.

As for the line length limit -- yes there is one, and it is
about 240 character per line.  You would use the line
continuation character to get around that:

ops$tkyte@ORA8I.WORLD> copy from scott/tiger@ora8i.us.oracle.com
-
> insert foo -
> (the_whole_view, textlength, thetext ) -
> using -
> select view_name, text_length, text -
>   from user_views;

Array fetch/bind size is 15. (arraysize is 15)
Will commit when done. (copycommit is 0)
Maximum long size is 5000. (long is 5000)
   7 rows selected from scott@ora8i.us.oracle.com.
   7 rows inserted into FOO.
   7 rows committed into FOO at DEFAULT HOST connection.

ops$tkyte@ORA8I.WORLD>

the script I used was:

copy from scott/tiger@ora8i.us.oracle.com -
insert foo -
(the_whole_view, textlength, thetext ) -
using -
select view_name, text_length, text -
  from user_views;

the "-" is a line continuation character.  You might have seen
the line continuation character pop up if you ever hit the ENTER
key after a minus sign:

ops$tkyte@ORA8I.WORLD> select 1-
> 1 from dual;
select 1 1 from dual
         *
ERROR at line 1:
ORA-00923: FROM keyword not found where expected

you might think that should return 0, but it does not.  It can
be even more confusing if the resulting SQL is valid but not
what you wanted:

ops$tkyte@ORA8I.WORLD> select 1-
> dummy from dual;

     DUMMY
----------
         1

ops$tkyte@ORA8I.WORLD>

Review &
      Followup

      Rating:
      4
  April 23, 2001
Reviewer:
      A reader  from Frankfurt
The answer was extremely helpful.

      Rating: 4
Copy comand
      where maximum characters is exceeded  February 05, 2002

Reviewer:  Jiten Padhiar
I am trying to use the COPY command and find that the statement is truncated. As
part of the 'USING SELECT..' clause i have to list all 150 columns. This is
because I have to assign the appropriate column alias before the statement
applies an insert from an oracle table to a SQL Server table.

Any thoughts (ps. I have used the continuation character)

          Followup:

create a view that does the correct conversion of column names on the SQL server
table and coyp the view.  Then the sqlplus copy command will be very short.

      Rating: 5
Good
      tip  June 04, 2002
Reviewer:  Tony
       from USA
Tom:

Does the COPY command return an sucess/error code when it is done?  What I would
like to do is to copy all data from the source to the sink.  If the process
completes without error, I want to update the status code of the source table so
that the script won't pick those records again.  Following is a short UNIX shell
script:
--------------------------------------------------------
#!/bin/sh

echo "Starting the update process: " `date`

sqlplus -s user/password@sink_db <<EOF

COPY FROM user/pass@source_db INSERT sink_table -
USING SELECT * FROM source_table WHERE status_code = 0

--  If NO error Then
--    UPDATE source_table SET status_code = 1
--  End If

exit

EOF

echo "Finished the update process: " `date`
--------------------------------------------------------

Can I do this?  I would appreciate your help.

Thanks,
Tony

          Followup:

See

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a82950/appa.htm#623300
That is an appendix titled "COPY Command Messages and Codes"...

At the very least, you can "grep" for CPY0 to see if it errored out.

      Rating: 5
Well I'll
      be!  June 05, 2002
Reviewer:  DBA  from
      Down Under
Like the answer Tom,
didn't know about the continuation character!

      Rating:
      5
copy  November 06, 2002
Reviewer:
      mo
Tom:

I am trying the copy in SQL*plus for windows accessing a unix server and I
amgetting an error.

IPST> copy from ipsadmin/ipsadmin@ipsd create test using select * from test;

Array fetch/bind size is 15. (arraysize is 15)
Will commit when done. (copycommit is 0)
Maximum long size is 80. (long is 80)

ERROR:
ORA--10710048: Message -10710048 not found;  product=RDBMS; facility=ORA

IPST> select * from test2@ipsd;

SSN        PANTS
---------- ----------
611        RED
456        YELLOW

2 rows selected.

Do you know why?

Thanks,

          Followup:

try pushing instead -- if I get a problem one way, i always try the other

connect to ipsd and then

copy TO ....

      Rating:
      4
copy  November 06, 2002
Reviewer:
      mo
Tom:

I did:

IPSD> copy to imsadmin/imsadmin@imst create test using select * from test;

Array fetch/bind size is 15. (arraysize is 15)
Will commit when done. (copycommit is 0)
Maximum long size is 5000. (long is 5000)

 select * from test
*
Error in SELECT statement: ORA--9715932: Message -9715932 not found;
product=RDBMS; facility=ORA

2.  Since the COPY does not copy any table constraints, and if you use the
REPLACE option it says they drop table in destination which means I will lose
constraints and triggers.  IS this correct?

Thank you,

          Followup:

ok -- i've seen this with that goofy "gui" sqlplus -- use the real one, on the
command line -- from a DOS window.

2) correct. if you don't want that, truncate and then copy.

      Rating:
      5
copy  November 06, 2002
Reviewer:
      mo
do you mean copy using insert rather than replace after truncating table

          Followup:

yes

      Rating:
      5
copy  November 07, 2002
Reviewer:
      mo
Tom:

why do you call sqlplus for windows 8.1.7 goofy. is not supposed to be the same
one run in unix.  this is what  i use.

C:\OraHome1\BIN\SQLPLUSW.EXE

It is much easier to use windows notepad than unix vi for
queries.

Thanks,

          Followup:

sqlplus.exe is the one I used to use when I ran windows. (good old character
mode)

and sorry but when I typed edit on windoze in plus -- it would fire up VI.  To
say notepad is "easier" then vi -- hmm, sure if "trivial" is what you wanted as
well.  Vi is a little more powerful perhaps then notepad.  I'll stick with vi
anyday.

      Rating: 4
Copy and Insert
      /* +Append */ ?  January 25, 2003
Reviewer:
      Robert  from PA
Can I

Insert /* +Append */ , or even Insert /* +Append */ ...NOLOGGING

with the copy command ?

          Followup:

copy command = conventional path (no direct path) loading.

      Rating: 5
COPY command
      obsolete?  May 27, 2003
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
Tom
In the Oracle 9i SQL*plus manual, it says that COPY command will become obsolete
in future release. What is the alternative if I want to insert from one database
to another without using database link?
Thanks

          Followup:

exp/imp i guess.

but -- if you can copy, you can dblink it as well

(but the manuals have been saying that about copy for a while...)

      Rating: 5
if you can
      copy, you can dblink it as well?  May 28, 2003

Reviewer:  kassim  from san francisco, ca
hi tom --
as of 9.2 i can no longer dblink to tables in a 7.3.4 instance ( icould do it in
9.01 and was able to take advantage of materialized views)
now i'm reduced to

connect to the 9.2 instance
truncate <table> resuse storage
drop indexes

run a script that resets env vars to use the 9.01 client
connect to the 7.3.4 instance
copy to <9.2 instance> insert <tablename> using <select statement>

reconnect to 9.2
build indexes

i do this as a cron job but would to learn a better way

thank you for any suggestions

          Followup:

you are lucky that even works -- there are many known issues with 73 clients
connecting to 92 and 92 clients connecting to 73.  that is very fragile what you
have there as it is.

      Rating:
      5
gosh  May 28, 2003
Reviewer:  kassim
       from san francisco, ca
thanks--
i'm none too happy with this either
the people who maintain the 7.3.4 instance are not likely to upgrade soon as
there are a bunch of legacy apps that depend on it

aside from creating a new 9.01 instance just to hold dblinks or materialized
views pointing to the tables in the 7.3.4 instance, what would you recommend?

can you send me link about the 7.3 <--> 9.2 issues?

should this be a new thread?

thanks again

          Followup:

metalink has the information about 73 to 92 and its being explicitly "not
supported, and not working"

I can't really recommend anything here short of getting 73 upto a release that
is supported.

      Rating: 5
a related
      question on copy command  May 29, 2003
Reviewer:
      Moh
We have oracle 7.0 and oracle 8i 815 on the same machine.
Now, porting oracle 7.0 data to oracle 8i 815 is no problem at all for us.
however, porting oracle8i data to oralcle 7.0 has become tough for us.  we use
sqlload to load data to ora7.  Copy command can be used for this purpose I
suppose.  But, we have oracle 8i listener running on port 1521.  And, no
listeners for oracle7.0  .  Kindly suggest us a way-out.

          Followup:

7.0?

anyway, the 8i listener can be used for 7.x -- just configure the proper oracle
home and sid and such in there.  You only need one listener for all databases.

but really, if you connect to 7.0 and initiate the copy from there, you don't
need a listener for 7.x, you are already connected.

      Rating: 5
copy
      command  May 30, 2003
Reviewer:  Moh

I use copy command from unix system.

The oracle_sid and oracle_home for oracle 7.0 and oracle 8i 815 are different.

I logged in as oracle 7.0 and tried "copy from" after getting connected.

I got this error :

ora-12203: TNS:Unable to connect to destination.

I assure you that the listener for oracle 8i is running.

Kindly help.

          Followup:

that just means you messed up the tnsnames.ora file

[tkyte@tkyte-pc Desktop]$ oerr ora 12203
12203, 00000, "TNS:unable to connect to destination"
// *Cause: Invalid TNS address supplied or destination is not listening.
// This error can also occur because of underlying network transport
// problems.
// *Action: Verify that the service name you entered on the command line
// was correct. Ensure that the listener is running at the remote node and
// that the ADDRESS parameters specified in TNSNAMES.ORA are correct.
// Finally, check that all Interchanges needed to make the connection are
// up and running.

      Rating: 5
very
      useful  July 24, 2003
Reviewer:  Bipin G
       from INDIA
Hi tom ,
I gone thro' complete copy command uses. Nowadays i'm extensively using copy
command for getting the data in other db Now, I facing one problem one of my
table has got long raw datatype bcos of which it is giving me inconsistent
datatype how can i resolve ?
and use copy command for copy such stru.
Imp. My long raw datatype column stores the images.

          Followup:

you cannot use copy on long raws -- period, no workarounds, nothing.

you should be using BLOBS

      Rating:
      4
  July 24, 2003
Reviewer:  Dave
       from Colorado
I'm saddened to learn from Metalink Note 171870.1 that the COPY command is to be
obsoleted in future SQL*Plus releases, presumably because it doesn't support
funky new data types.

Does this mean that it will actually not be available, or that it will continue
to exist with no added functionality?

          Followup:

well, i can say it is there at least in the next release after 9iR2...

you would have to ask support for a clarification (i use that feature heavily
myself)

      Rating: 5
Helpful but I
      get an error  October 24, 2003
Reviewer:  LB
       from New Jersey
What is the meaning of this error?

SQL> COPY FROM uet_read/password@CSCD001 INSERT SLA_LB USING SELECT * FROM
UETADMIN.SLA;

Array fetch/bind size is 15. (arraysize is 15)
Will commit when done. (copycommit is 0)
Maximum long size is 80. (long is 80)

ERROR:
ORA--6871402: Message -6871402 not found;  product=RDBMS; facility=ORA

          Followup:

means time to call support.

but you might try going the other way.  COPY TO (thats what I do when i hit an
issue "pulling", i connect to the other database and try "pushing")

      Rating: 5
your response
      time is great  October 24, 2003
Reviewer:
      LB  from New Jersey

      Rating: 5
another error -
      what is wrong?  October 24, 2003
Reviewer:
      LB  from New Jersey
I tried the copy to from the other database and I get a similar error.  Should I
contact the DBA?

SQL> COPY TO uetadmin/password@SHROLTDB INSERT SLA_LB USING SELECT * FROM
UETADMIN.SLA;

Array fetch/bind size is 15. (arraysize is 15)
Will commit when done. (copycommit is 0)
Maximum long size is 80. (long is 80)

 SELECT * FROM UETADMIN.SLA
*
Error in SELECT statement: ORA--33781842: Message -33781842 not found;
product=RDBMS; facility=ORA

          Followup:

yup, time for a tar.

      Rating: 3
use command
      line sqlplus  October 28, 2003
Reviewer:  LB
       from New Jersey
I was able to get this to work by putting the copy command into an sql file and
executing it from command line sqlplus.

      Rating: 5
beginner
      question  October 30, 2003
Reviewer:  LB
       from New Jersey
This has been working for me when I was using a small dataset like 7000 records.
 But then when I tried about 30,000 reocrds or more I keep getting the error
"failed to extend rollback segemnt"

I suspect it is something to do with the arraysize or copycommit settings, but I
am not sure what to change.
Sorry to ask such a beginner question for a site full of DBA's but what am I
doing wrong with the arraysize and copycommit?

I should also mention that I have been running sqlplus from my pc since I am
just a developer and do not have access to sqlplus on the database server as of
yet.

set arraysize 100
set copycommit 1000
COPY FROM uet_read/password@PRODUCTION -
INSERT UET_DATA -
USING -
SELECT * -
FROM UETADMIN.UET_DATA -
WHERE trunc(req_time) > '26-oct-03' -
AND BRN_I LIKE '%HK%'

          Followup:

don't feel you need to be "super experienced" to ask stuff.  the only way to get
that way is -- well -- to ask stuff!

This is a non-trivial question actually.

copycommit is how many fetches of N rows to perform between commits.

so, you are committing every 100,000 rows -- which, if that is blowing out your
RBS, i would state "your DBA's need to rethink some things".

So, your choices are

a) use a smaller copycommit.  make sure you can RESTART in the event of
failure!! (eg if 2 fetches succeed, you will have committed 200 rows -- if the
system fails at that point, you will still have those 200 rows.  Now what?)

b) get them to size rbs as it SHOULD be

other comments

o it takes AT LEAST 4 characters to accurate represent a YEAR.  2003, not 03,
never 03, ugh -- 1999 should have taught us that!!!!

o never compare a string to a date, a date to a string, a number to a string, a
string to a number and so on.

   to_date( '26-oct-2003', 'dd-mon-yyyy' )

o use explicit date formats when you have an explicit date like that.  avoids
issues with databases that have non default NLS_DATE_FORMATS

o avoid the use of a function on a column.  Here the trunc(req_time) is actually
not even needed!!!  if the trunc(req_time) is greater then your date, then
req_time by definition is greater then your date.  You might be missing out on a
useful index range scan on the remote system (or not, but the trunc isn't
needed)

      Rating: 5
COPY command Vs
      Database link  November 14, 2003
Reviewer:
      Saminathan Seerangan  from Jersey City,NJ,USA
Dear Tom,
I used both COPY command and database link. But I would like to know

1)How internally these two are different from each other

2)How COPY command is fater than dblink?

3)Is there any reason to obsolete COPY command? Most of the time I find very
useful command to copy data fom diff database.

Thanks Tom,
-Sami

          Followup:

dblink goes db to db

copy goes  db1 -> client -> db2 array by array.

so, say you have two databases connected by a LAN.  You dial up from home and
run sqlplus on your home computer.

using sqlplus copy -- every row will go from db1 to YOU to db2

using dblink -- every row will go from db1 to db2 over the lan

copy -- commits after every N array fetches
dblink -- commits when you say "commit"

      Rating: 5
No EXPort
      privilege but huge amount of data should be
      transferred  November 15, 2003
Reviewer:
      Saminathan Seerangan  from Jersey City,NJ,USA
Thanks a lot Tom.
So if I want to transfer bulk volume of data, then DBLINK should be the best
choice(in terms of data transfer speed compare to COPY) provided larger rollback
segment and assume that I do not have EXPort privilege on the source database.

          Followup:

depends, all depends.

benchmarking is always appropriate -- try both.

you don't need "export" priv on a database???  if you can read the data, you can
export the data.

      Rating: 5
if you can read
      the data, you can export the data?????  November 16, 2003

Reviewer:  Saminathan Seerangan  from Jersey
      City,NJ,USA
<quote>
you don't need "export" priv on a database???  if you can read the data, you can

export the data.
</quote>

Dera Tom, Please see below, what is wrong here?
I am getting "EXP-00009: no privilege to export testschema's table test_table"

$ exp userid=tomkyte tables=testschema.test_table

Export: Release 8.1.7.3.0 - Production on Sun Nov 16 11:19:31 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Password:

Connected to: Oracle8i Enterprise Edition Release 8.1.7.3.0 - Production
With the Partitioning and Parallel Server options
JServer Release 8.1.7.3.0 - Production
Export done in UTF8 character set and UTF8 NCHAR character set

About to export specified tables via Conventional Path ...
EXP-00009: no privilege to export testschema's table test_table
Export terminated successfully with warnings.

myserver tomkyte mydb /opt/home/tomkyte

$ sqlplus tomkyte

SQL*Plus: Release 8.1.7.0.0 - Production on Sun Nov 16 11:19:53 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Enter password:

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.3.0 - Production
With the Partitioning and Parallel Server options
JServer Release 8.1.7.3.0 - Production

SQL> select * from testschema.test_table where rownum <=2;

TYPE       CODE
---------- ----------
DESCRIPTION
--------------------------------------------------------------------------------
A CREATEDATE      UPDATEDATE
- --------------- ---------------
TWC               101
Payroll
Y 12-SEP-03       12-SEP-03

TWC               199
Other Payroll
Y 12-SEP-03       12-SEP-03

SQL>

          Followup:

doh!  sorry about that, you are 100% correct.  you need EXP_FULL_DATABASE, sorry
about that.  exp as THAT user won't work -- but exp AS that user would work (so
exp is still an "option", if that is the best approach, that source system
should facilitate that approach)

      Rating: 5
Dear Tom,thanks
      for your immediate response.  November 16, 2003

Reviewer:  Saminathan Seerangan  from Jersey
      City,NJ,USA

November 16, 2003 darlis -- Thanks for the question regarding
      "Mutating Tabel", version 8.1.6

      You Asked
Hi Tom ...

I want to update my 'Xrows' column by triger after delete or
insert or update of Xrows.
'Xrows' is sort by Xdate asc

table X
XRows   ParentID      XDate       ID
1       1             01-JAN-77  1 <-- delete
3       1             09-JAN-77  2
2       1             04-JAN-77  3
4       1             23-JAN-77  4

if i delete id = 1, the result suppose is :

XRows   ParentID      XDate       ID
2       1             09-JAN-77  2
1       1             04-JAN-77  3
3       1             23-JAN-77  4

my triger : -- error mutating table

create or replace trigger T_Update_Rows
after insert or update or delete
on tableX for each row
declare
    v_rows tabelX.XRows%type;
    v_id tabelX.Id%type;
    v_Date tabelX.Xdate%type;
    v_ParentId tabelX.ParentId%type;
    CURSOR c_Rows is
      select XRows,id
      from TableX
      where ParentId = v_ParentId
        and XDate in (select XDate
                      from TableX
                      where ParentId = v_ParentId);
  begin
    v_ParentId := :new.parentId;
    OPEN c_Rows;
    loop
        FETCH c_Rows INTO v_rows, v_Id;
        update TabelX set XRows = v_rows WHERE ID = v_id ;
        EXIT WHEN c_Rows%NOTFOUND;
    END LOOP;
    CLOSE C_Rows;
  end;

      and we said...
Well, it seems to be a waste of resources to resequence these
rows after every insert or update or delete -- especially when
you can just:

select rownum Xrows, parentId, Xdate, id
  from ( select parentId, Xdate, id
           from tableX
          where parentid = :x
          order by  xdate )
/

whenever you want to generate XRows, however....

There are a couple of issues with your trigger logic beyond the
mutating table.  consider:

o if the trigger worked, did not get the mutating table, then
you just coded an infinite loop.  The trigger is on UPDATE of
tableX and the trigger updates TableX.  It would recurse until
we ran out of cursors and then fail.

o Your query has a constraint on XDate that seems to be only
there to waste CPU cycles and IO.  You select every row from
TableX such that the parentId = SOME_VALUE and the XDate is in
the set of XDates for that value.  It should be written simply
as:

   where parentId = v_parentid
     and XDAte is NOT NULL;

they are equivalent.

o You fetch out Xrows into v_rows from the table and you set the
value of the row back to its original value -- you don't change
anything???  You selected XRows out by ID and you put it back by
ID.  You did not resequence anything.

I would encourage you to lose the Xrows and just have it happen
for you at select time (i'll demonstrate below) but if you do
follow the above course of action, you'll want to read

http://osi.oracle.com/~tkyte/Mutate/index.html
to understand the concept behind what I'm doing below and then
implement something like this:

ops$tkyte@ORA8I.WORLD> create table tableX
  2  ( xrows    int,
  3    parentid int,
  4    xdate    date,
  5    id       int
  6  )
  7  /

Table created.

ops$tkyte@ORA8I.WORLD> insert into tableX values ( 1, 1,
'01-jan-77', 1 );

1 row created.

ops$tkyte@ORA8I.WORLD> insert into tableX values ( 3, 1,
'09-jan-77', 2 );

1 row created.

ops$tkyte@ORA8I.WORLD> insert into tableX values ( 2, 1,
'04-jan-77', 3 );

1 row created.

ops$tkyte@ORA8I.WORLD> insert into tableX values ( 4, 1,
'23-jan-77', 4 );

1 row created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> insert into tableX values ( 1, 2,
'01-jan-77', 11 );

1 row created.

ops$tkyte@ORA8I.WORLD> insert into tableX values ( 3, 2,
'09-jan-77', 12 );

1 row created.

ops$tkyte@ORA8I.WORLD> insert into tableX values ( 2, 2,
'04-jan-77', 13 );

1 row created.

ops$tkyte@ORA8I.WORLD> insert into tableX values ( 4, 2,
'23-jan-77', 14 );

1 row created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select rownum XRows, parentid, xdate, id
  2    from ( select parentid, xdate, id
  3             from tableX
  4            where parentId = 2
  5            order by xDate )
  6  /

     XROWS   PARENTID XDATE             ID
---------- ---------- --------- ----------
         1          2 01-JAN-77         11
         2          2 04-JAN-77         13
         3          2 09-JAN-77         12
         4          2 23-JAN-77         14

ops$tkyte@ORA8I.WORLD> delete from tableX where id = 11;

1 row deleted.

ops$tkyte@ORA8I.WORLD> select rownum XRows, parentid, xdate, id
  2    from ( select parentid, xdate, id
  3             from tableX
  4            where parentId = 2
  5            order by xDate )
  6  /

     XROWS   PARENTID XDATE             ID
---------- ---------- --------- ----------
         1          2 04-JAN-77         13
         2          2 09-JAN-77         12
         3          2 23-JAN-77         14

That shows that we can just get xrows on the fly without
having to maintain it.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> create or replace package state_pkg
  2  as
  3      type myArray is table of char(1)
  4        index by binary_integer;
  5
  6      modified   myArray;
  7      empty      myArray;
  8
  9      fire       boolean default TRUE;
 10  end;
 11  /

Package created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> create or replace trigger tableX_biud
  2  before insert or update or delete on tableX
  3  begin
  4      if ( state_pkg.fire )
  5      then
  6          state_pkg.modified := state_pkg.empty;
  7      end if;
  8  end;
  9  /

Trigger created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> create or replace trigger tableX_aiud_fer
  2  after insert or update or delete on tableX
  3  for each row
  4  begin
  5      if ( state_pkg.fire )
  6      then
  7          if ( inserting or updating )
  8          then
  9              state_pkg.modified(:new.parentId) := 'Y';
 10          else
 11              state_pkg.modified(:old.parentId) := 'Y';
 12          end if;
 13      end if;
 14  end;
 15  /

Trigger created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> create or replace trigger tableX_aiud
  2  after insert or update or delete on tableX
  3  declare
  4      l_cnt        number;
  5      l_parentId   number;
  6  begin
  7      if ( state_pkg.fire )
  8      then
  9          state_pkg.fire := FALSE;
 10
 11          l_parentId := state_pkg.modified.first;
 12          for i in 1 .. state_pkg.modified.count
 13          loop
 14              l_cnt := 1;
 15              for x in ( select rowid rid
 16                           from tableX
 17                          where parentId = l_parentId
 18                          order by xDate FOR UPDATE )
 19              loop
 20                  update tableX set xRows = l_cnt
                      where rowid = x.rid;
 21                  l_cnt := l_cnt + 1;
 22              end loop;
 23              l_parentid :=
                       state_pkg.modified.next(l_parentId);
 24          end loop;
 25
 26          state_pkg.fire := TRUE;
 27      end if;
 28  exception
 29      when others then
 30          state_pkg.fire := TRUE;
 31          raise;
 32  end;
 33  /

Trigger created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select * from TableX order by parentId,
xrows;

     XROWS   PARENTID XDATE             ID
---------- ---------- --------- ----------
         1          1 01-JAN-77          1
         2          1 04-JAN-77          3
         3          1 09-JAN-77          2
         4          1 23-JAN-77          4
         2          2 04-JAN-77         13
         3          2 09-JAN-77         12
         4          2 23-JAN-77         14

7 rows selected.

ops$tkyte@ORA8I.WORLD> delete from tableX where id = 1;

1 row deleted.

ops$tkyte@ORA8I.WORLD> delete from tableX where id in ( 12,13 );

2 rows deleted.

ops$tkyte@ORA8I.WORLD> select * from TableX order by parentId,
xrows;

     XROWS   PARENTID XDATE             ID
---------- ---------- --------- ----------
         1          1 04-JAN-77          3
         2          1 09-JAN-77          2
         3          1 23-JAN-77          4
         1          2 23-JAN-77         14

Review &
      Followup

      Rating: 3
Deleting Rows
      in before insert trigger  January 16, 2003

Reviewer:  Riaz Shahid  from PRAL, Islamabad,Pakistan

Hello Tom !

desc emp

empno    number(5)
deptno   number(5)

I want to do the following:

If the row that is going to be inserted already exists then delete that row
before insertion.

For this i created a pre-inserted trigger and put delete in
that.....oooops.......that caused Mutating Error....
Ok...i solved that in way u advised.

here is a code from After Insert Trigger of table emp:

begin
for i in 1...state_pkg.newrows.count loop

delete emp
where empno=(select empno from emp
where rowid=state_pkg.newrows(i))
where rowid!=state_pkg.newrows(i)
end loop;
end;

It works fine. But i was wondering is this efficient way to do that ? Please
advise

          Followup:

you do understand that in a multi-user environment -- your approach is totally
flawed and will not prevent duplicates?

The answer to this is:

put a primary key on there and let the database do its job.

What is the goal here?  sounds very fishy to me???

      Rating:
      2
Explanation  January 17, 2003
Reviewer:
      Riaz Shahid  from PRAL, Islamabad, Pakistan
There is a primary key put on that table. But on the other side, there is no
such P.K. Consider following insert:

insert into emp@mydblink
select a,b from myTab;

Now if the table emp already constaints the row which is going to be inserted
from myTab, the whole insertion will failed. I want insert statement not to be
failed.

And i can't make changes to the other side...that is the main point.

Riaz

          Followup:

well, you won't be using a trigger.

insert into emp@mydblink
select ... from mytabl
minus
select ... from emp@mydblink;

insert into emp@mydblink
select ... from mytab where not exists (select null from emp@mydblink where ...)

are two approaches.

      Rating: 5
Excellent
      !!!  January 17, 2003
Reviewer:  Riaz Shahid
       from PRAL, Islamabad, Pakistan
That was excellent. The second solution was already in my mind but as i
described, the problem is that i can't modify the procedure (insert) on the
other side.

Isn't there anywat so that i can handle it on my side ?

Please suggest

Riaz

          Followup:

do you own the table that is being inserted into?

      Rating:
      5
Good  January 18, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Islamabad, Pakistan
Yes I own the table to which the rows are being inserted BUT i don't own the
table from which the rows are being inserted and i can't make changes to their
procedures.

Regards

Riaz

          Followup:

great -- rename your table to emp_table

create a view emp as select * from emp_table

create an instead of insert trigger on emp that does something like:

begin
  insert into emp_table ( ... ) values ( :new.empno, .... );
exception
  when dup_val_on_index then
        whatever code deals with that
end;

      Rating: 5
Excellent ! ! !
        March 15, 2003
Reviewer:  Riaz Shahid
       from PRAL, Islamabad, Pakistan
Tom !

you are really great.

The sloution worked.

Many thanks

Riaz

      Rating: 4
mutating
      trigger table  August 21, 2003
Reviewer:
      umesh  from bangalore india
create table e1 as select empno,ename,deptno  from emp;

create or replace trigger t1 after insert on e for each row
begin
insert into e values (:new.empno,:new.ename,:new.deptno+1);
end;

this causes a mutatiing trigger
i tried even the round about way with packages ( state_pkg) mentioned in one of
replies
Please advice

          Followup:

thank goodness it does!  think about what would happen if it did not!!!!

can you spell "infinite loop"

so, give me the business case you are trying to solve and we can discuss
solutions that are feasible.

      Rating: 4
Why mutating
      table in AFTER INSERT row triggers?  November 16, 2003

Reviewer:  Michel SALAIS  from France
Oracle does not fire mutating table error for BEFORE INSERT row triggers (with
VALUES syntax) but it does for AFTER INSERT row triggers. Logically, I fail to
see why it is necessary. Am I missing something or it is an internal
implementation problem?

          Followup:

it is the "rule", the table at that point in time is in fact "mutating" -- is in
the MIDDLE of being changed.

with a single row insert (values) -- we know the table is stable, consistent,
not modified, not in the middle of a change, not being mucked with in the
BEFORE.

In the AFTER -- we know that the

o table is not stable
o not consistent
o modified
o in the middle of a change
o being mucked with

      Rating: 3
Trigger
      execution modèle  November 16, 2003
Reviewer:
      Michel SALAIS  from France
Well, I'll try why I asked the question and how I finally have found the reply:
I "logically" thaught that "after the row" is the same thing as "after the
instruction" because it is one row which is concerned. I have forgotten that
some constraint checks can happen after the row trigger and so even in the case
of one row inserted with VALUES syntax:
AFTER INSERT row trigger != AFTER INSERT statement trigger.
So with the row trigger there is always a possibility that the instruction will
be rejected and so the trigger must not see it. This is not a problem for the
before trigger because any way it doesn't see the row.

To do some tests I have elaborated the following scenario and I need your
comment on the value of the Num column of Journal. It comes from :NEW.Num but it
acts as if the value of the sequence is used instead!

DROP SEQUENCE Seq_triggers;
DROP TABLE Test_Order CASCADE CONSTRAINTS;
DROP TABLE Prep CASCADE CONSTRAINTS;
DROP TABLE Journal CASCADE CONSTRAINTS;

CREATE SEQUENCE Seq_triggers;

CREATE TABLE Prep (Col1 Number);
CREATE TABLE Test_Order (num NUMBER, Col1 Number);
CREATE TABLE Journal (num NUMBER, Col1 Number, seq Number, Origin Varchar2(3));

CREATE OR REPLACE TRIGGER T_BIS
BEFORE INSERT
ON Test_Order
BEGIN
    INSERT INTO Journal
    VALUES (NULL, NULL, seq_triggers.nextval, 'BIS');
END;
/

CREATE OR REPLACE TRIGGER T_BIR
BEFORE INSERT
ON Test_Order
FOR EACH ROW
BEGIN
    INSERT INTO Journal
    VALUES (:NEW.Num, :NEW.Col1, Seq_triggers.nextval, 'BIR');
END;
/

CREATE OR REPLACE TRIGGER T_AIS
AFTER INSERT
ON Test_Order
BEGIN
    INSERT INTO Journal
    VALUES (Null, NULL, seq_triggers.nextval, 'AIS');
END;
/

CREATE OR REPLACE TRIGGER T_AIR
AFTER INSERT
ON Test_Order
FOR EACH ROW
BEGIN
    INSERT INTO Journal
    VALUES (:NEW.Num, :NEW.Col1, Seq_triggers.nextval, 'AIR');
END;
/

/* Test */
INSERT INTO Prep VALUES(100);
INSERT INTO Prep VALUES(200);

INSERT INTO Test_Order
SELECT seq_triggers.nextval, Col1
FROM  Prep;

SELECT * FROM Test_Order;

SELECT * FROM Journal;

          Followup:

please send that one onto support, reproduces in 9iR2 and is the wrong behavior.

November 17, 2003 Subrata -- Thanks for the question regarding
      "Performance issue for accessing table of a remote database", version
8.1.7

      You Asked
I need to access a table of a remote database. I have a number of options. Which
one
should be faster and why?

1. select from T1
   where (T1.f1, T1.f2) in (select T2.f1, T2.f2
                            from T2.@remote.world
                            where T2.f3 <= c1
                            and   T2.f3 >  c2)

2. select * from T1
   where exists            (select T2.f1, T2.f2
                            from T2.@remote.world
                            where T2.f3 <= c1
                            and   T2.f3 >  c2
                            and   T1.f1 = T2.f1
                            and   T1.f2 = T2.f2)

3. Copy the data from remote database to a temporary table and process either by
option 1 or 2.
   (This data is being required by three more SQL in the procedure)

Please note that
A.necessary indexes are there on
  T1(f1,f2)
  T2(f1,f2)
  T2(f3)

B.When I tried, option 1 (ie. the "in" statement) was much much faster than
option 2.

Thanks

      and we said...
option 1 and 3 are effectively the same.

1 and 2's performance is always dictated by the size of the inner and outer
query (even on a single machine).

#2 is like this

   for every row in T1
       run a subquery against T2

if T1 is big and T2 is small -- that is the wrong way to do it.  #1 would be
better.

This isn't really to do with dblinks or anything, it is the old "in vs exists"
question itself.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:953229842074
for a discussion on this.

Review &
      Followup

      Rating:
      4
  August 20, 2002
Reviewer:  Subrata
      Saha  from Bangalore, India
The link was very helpful.
But when I used explain plan it was not clear the way Oracle handles (or
converts) the "in" and "exists". Does the explain plan fails to show how the
query is rewritten in these cases, or did I fail to interprete the explain plan?

Thanks

          Followup:

The query is not "rewritten", the query is processed and processed differently
when you use an in vs where exists.  The plans show that.

If you have a specific question -- use an example, maybe that will make it more
clear.

      Rating:
      3
  February 04, 2003
Reviewer:  Juan
      Carlos Reyes  from Bolivia
Hi Tom, I has a question
I did this remote select

 SELECT
NVL( SUM( DB_TCS_CAMBIOMONEDA_SE( dFecha, EMN_MONEDA, cMon, ( HCA_PRVAL -
VEP_PRVEN ) * ( VEP_NUMTITS - VEP_LIQANT ) )), 0)

            FROM VENTA_PACTO@SOA.WORLD, CARTERA@SOA.WORLD, EMISION@SOA.WORLD
            WHERE VEP_LIQ = 'F'
              AND CAR_CODCART = VEP_CODCART
              AND CAR_CODCLI = nCodCli
              AND CAR_INST = EMN_CODINST
              AND CAR_SERIE = EMN_SERIE
              AND EMN_METODO_VALUACION IN ( 'CUP', 'CUK' );

It took about 8:00 mn

then I changed
DB_TCS_CAMBIOMONEDA_SE to  DB_TCS_CAMBIOMONEDA_SE@SOA.WORLD

 SELECT
NVL( SUM( DB_TCS_CAMBIOMONEDA_SE@soa.world( dFecha, EMN_MONEDA, cMon, (
HCA_PRVAL - VEP_PRVEN ) * ( VEP_NUMTITS - VEP_LIQANT ) )), 0)
.............

And it took 0.00 seconds

Why this happened

Thanks

          Followup:

well, the first one probably pulled all of the data locally, executed the query
and then ran the function.

the other probably ran the query in its entirety at the remote site, returning
only the answer.

an explain plan using autotrace traceonly explain would be most illuminating to
you...

      Rating:
      5
  February 05, 2003
Reviewer:
      A reader
:) Thanks

I only want to be sure, I supposed initially that the query returned the row
from the remote database and once then processed locally the row, but it's not
in this way.

here are the trace info.

Using remote function ( o seconds )
----------------------
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT (REMOTE) Optimizer=CHOOSE (Cost=248 Card=1
          Bytes=78)

   1    0   SORT (AGGREGATE)
   2    1     NESTED LOOPS (Cost=248 Card=1 Bytes=78)
   3    2       MERGE JOIN (CARTESIAN) (Cost=8 Card=2399 Bytes=88763)
   4    3         TABLE ACCESS (FULL) OF 'VENTA_PACTO' (Cost=2 Card=1  SAFI
          Bytes=21)

   5    3         BUFFER (SORT) (Cost=6 Card=2399 Bytes=38384)
   6    5           TABLE ACCESS (FULL) OF 'EMISION' (Cost=6 Card=2399 SAFI
           Bytes=38384)

   7    2       VIEW OF 'HICARTERA'                                    SAFI
   8    7         UNION-ALL (PARTITION)
   9    8           TABLE ACCESS (BY INDEX ROWID) OF 'HICARTERA_RW' (C SAFI
          ost=2 Card=1 Bytes=30)

  10    9             INDEX (RANGE SCAN) OF 'IDX_FEINSTSERIE_RO' (NON- SAFI
          UNIQUE) (Cost=1 Card=1)

  11    8           TABLE ACCESS (BY INDEX ROWID) OF 'HICARTERA_RO' (C SAFI
          ost=2 Card=1 Bytes=30)

  12   11             INDEX (RANGE SCAN) OF 'IDX_FEINSTSERIE' (NON-UNI SAFI
          QUE) (Cost=3 Card=1)

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

Using local function (8 mn)
--------------------

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=1 Bytes=146)
   1    0   SORT (AGGREGATE)
   2    1     NESTED LOOPS (Cost=6 Card=1 Bytes=146)
   3    2       NESTED LOOPS (Cost=4 Card=1 Bytes=83)
   4    3         REMOTE* (Cost=2 Card=1 Bytes=64)                     SOA.WORL
                                                                       D

   5    3         REMOTE*                                              SOA.WORL
                                                                       D

   6    2       REMOTE* (Cost=2 Card=1 Bytes=63)                       SOA.WORL
                                                                       D

   4 SERIAL_FROM_REMOTE            SELECT "HCA_FECHA","HCA_PRVAL","HCA_CODCART"
                                   ,"HCA_INST","HCA_SERIE","HCA_CODCLI"

   5 SERIAL_FROM_REMOTE            SELECT "EMN_SERIE","EMN_CODINST","EMN_MONEDA
                                   " FROM "EMISION" "EMISION" WHERE :1=

   6 SERIAL_FROM_REMOTE            SELECT "VEP_CODCART","VEP_FECHAVEN","VEP_PRV
                                   EN","VEP_NUMTITS","VEP_LIQ","VEP_LIQ

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

      Rating: 5
remote queries
      and temporary tablespaces (remote and local)  May 29, 2003

Reviewer:  A reader
hi

I have some doubts regading queries against a remote database using db links.

If I have a query say

select /*+ USE_MERGE(emp v) */ ename, dname
from emp, dept@db1 v
where emp.deptno = v.deptno

I understand the sort will take place in the local database

If I have a query says

select count(*)
from dept@db1
group by deptno, location;

in the remote v$sql I can see the query is rewritten to

select count(*)
from dept
group by deptno, location;

however is that group by using remote temporary tablespace or the whole table is
downloaded to my local database and doing the sort in my local temp tablespace?

          Followup:

if the remote sql is that -- then the group by happens there, on the remote
site.

if you saw "select ... from dept" on the remote site, then the group by would be
happening locally.

(group bys don't have to use temp by the way.  they don't even have to sort in
all cases)

      Rating: 5
doesnt aggreate
      function sort?  May 29, 2003
Reviewer:
      A reader
Hi

doesnt count(*) sort?

also group by?
from concepts guide

All operations that use sorts, including joins, index builds, ordering,
computing aggregates (GROUP BY), and collecting optimizer statistics, benefit
from temporary tablespaces. The performance gains are significant with Real
Application Clusters.

:-0

          Followup:

no, aggregates do not have to sort.

that should perhaps say "all operations that might sort, including....."

joins don't have to sort (in fact, I would say most DO NOT)
index creation doesn't always have to sort...
group bys don't have to sort (there is a group by NOSORT)

      Rating: 5
download the
      whole table  May 29, 2003
Reviewer:
      A reader
Hi

in this query

select /*+ USE_MERGE(emp v) */ ename, dname
from emp, dept@db1 v
where emp.deptno = v.deptno;

Entire table dept is transferred to local database right? Is it stored in local
temp tablespace? Or where is it stored?

          Followup:

don't know, don't see a plan.  the plan will tell you.

      Rating:
      3
  May 29, 2003
Reviewer:  Muhammad
       from NJ, USA
Hi,

I want known the codition for which the the remote table will be brought to
local database. if the remote table is brought to local database. then what
happen to blocks of remote table. is it copied to log_buffer of both the
database.

Thanks

          Followup:

the log buffer ?  that is for redo and that will be only on the instance that
does the modification.

there are many conditions, more then I'll willing to list that bring a remote
table locally.

The data is either used and discarded or it is put into temp to be used in the
processing of the query.

      Rating: 3
Accessing
      columns from remote database  November 17, 2003

Reviewer:  Balaji  from India Chennai
Hi
My req is slightly complicated.
I am accessing columns from the remote table in the select clause.
SO it is not possible for me to move the table to the subquery.
also replcation of the remote table is not feasible ( 2 milion records) and also
not allowed.
is there any was i can access the column.

select c.co1l
c.col2,
c1.col1,
c1.col1
where
c1.col1 in ( select col1 from c@dblink where..)

this cannot be done as c is in x database.
i need c.col1 and col2 in select query
and i am returning this output as a ref cursor.

          Followup:

join

select .... from c1, c@dblink c
where c1.col1 = c.col1

November 17, 2003 Renuka Prasad -- Thanks for the question
      regarding "Language Issue", version 8.1.7

      You Asked
Hi Tom,

   I have faced a problem with NUMBER datatype. Currently I am
working on a code dump from Finland. The dump contains
the command "REPLACE(total_sum,'.',',')". This code works in Finland.
But it does not work when I import the dump here. The error I got was
"character to number conversion error". Then I removed the "REPLACE"
command from the code, then it started working. What I feel is the
Oracle loaded in Finland and here might have some difference. What is the reason
do you feel? Is this problem related to NLS_LANGUAGE in
anyway?

Waiting for your reply....

Thanx and regards

Renuka Prasad.

      and we said...
Well, not knowing where "here" is ;)

Some languages use 999,999.00
Others use 999.999,00

it looks like they were taking a STRING "123.456" and turning it into "123,456"
and then converting into a number.  In your database "123,456" is not a number
but "123.456" is.

Yes, this is related to the NLS settings

ops$tkyte@ORA8I.WORLD> select * from nls_session_parameters;

PARAMETER                      VALUE
------------------------------ ----------------------------------------
NLS_LANGUAGE                   AMERICAN
NLS_TERRITORY                  AMERICA
NLS_CURRENCY                   $
NLS_ISO_CURRENCY               AMERICA
NLS_NUMERIC_CHARACTERS         .,
NLS_CALENDAR                   GREGORIAN
NLS_DATE_FORMAT                DD-MON-RR
NLS_DATE_LANGUAGE              AMERICAN
NLS_SORT                       BINARY
NLS_TIME_FORMAT                HH.MI.SSXFF AM
NLS_TIMESTAMP_FORMAT           DD-MON-RR HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT             HH.MI.SSXFF AM TZH:TZM
NLS_TIMESTAMP_TZ_FORMAT        DD-MON-RR HH.MI.SSXFF AM TZH:TZM
NLS_DUAL_CURRENCY              $
NLS_COMP                       BINARY

15 rows selected.

Specifically the nls_numeric_characters.
Review &
      Followup

      Rating: 2
NLS
      problem  October 25, 2002
Reviewer:
      zj411@yahoo.com
Hi Tom,

I run the follwoing sql code and got errors:
select count(*) from t1@abcd;

ERROR at line 1:
ORA-12705: invalid or unknown NLS parameter value specified
ORA-02063: preceding line from abcd

I am using 9.0.1 sqlplus. However my co-worker can run the select statement
above without any trouble.
By the way, The remote database abcd is oracle 7.1.5

Any ideas?

John

          Followup:

Problem:
========

You have Oracle Server version 7.2 or higher

  ORA-12705: invalid or unknown NLS parameter value specified
  ORA-1810: format code appears twice

The above errors are reported when you use any SQL*Net associated
activity.

Example:
--------

  $ SQLPLUS username/password@<alias>              -- A SQL*Net connection.

or

  SQLPLUS> CONNECT username/password@<alias>      -- A SQL*Net connection.

or

  SQLPLUS> SELECT * FROM table@<alias>;              -- Database links.

or

  SQLPLUS> COPY FROM username/password@<alias>... -- COPY command.

Solution:
=========

Change the local session parameters to settings understood by the
earlier version of Oracle that you are using.

1. For example, if you assume the above settings, use the following
   commands on the client:

     ALTER SESSION SET NLS_LANGUAGE = 'AMERICAN';
     ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MON-YYYY';
     ALTER SESSION SET NLS_DATE_LANGUAGE = 'AMERICAN';

2. Set the environment variable NLS_LANG to AMERICAN.

Example:
--------

  $ DEFINE NLS_LANG AMERICAN
  $ SQLPLUS SCOTT/TIGER
  SQLPLUS> ALTER SESSION SET NLS_LANGUAGE = 'AMERICAN';
  SQLPLUS> ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MON-YYYY';
  SQLPLUS> ALTER SESSION SET NLS_DATE_LANGUAGE = 'AMERICAN';
  SQLPLUS> SELECT * FROM table@<alias>;

Explanation:
============

Several new NLS parameter settings are introduced in Oracle 7.2
and above.  When you attempt to establish new NLS settings against
an earlier Oracle release, errors are generated because these settings
are not valid for earlier versions of Oracle.

Here are examples of settings that result in errors:

  NLS_LANGUAGE set to ENGLISH         - ORA-12705 error.
  NLS_DATE_FORMAT set to DD-Mon-RRRR  - ORA-1810 error.
  NLS_DATE_LANGUAGE set to ENGLISH    - ORA-12705 error.
  NLS_LANG set to ENGLISH          - ORA-12705 error.

When you establish a SQL*Net connection, the NLS parameters
associated with the local (client) session have to be established
against the resulting remote (server) session.

      Rating:
      3
login.sql  August 06, 2003
Reviewer:
      Marcio  from br
Tom, is possible set up nls_numeric_characters='.,' just to my session?

I mean want to keep database nls parameter as is. But in my session I must
nls_numeric_characters = ',.'

Look:
sre@MRP9I1> select * from nls_database_parameters;

PARAMETER                      VALUE
------------------------------ ----------------------------------------
NLS_LANGUAGE                   AMERICAN
NLS_TERRITORY                  AMERICA
NLS_CURRENCY                   $
NLS_ISO_CURRENCY               AMERICA
NLS_NUMERIC_CHARACTERS         .,
NLS_CHARACTERSET               WE8MSWIN1252
NLS_CALENDAR                   GREGORIAN
NLS_DATE_FORMAT                DD-MON-RR
NLS_DATE_LANGUAGE              AMERICAN
NLS_SORT                       BINARY
NLS_TIME_FORMAT                HH.MI.SSXFF AM
NLS_TIMESTAMP_FORMAT           DD-MON-RR HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT             HH.MI.SSXFF AM TZR
NLS_TIMESTAMP_TZ_FORMAT        DD-MON-RR HH.MI.SSXFF AM TZR
NLS_DUAL_CURRENCY              $
NLS_COMP                       BINARY
NLS_LENGTH_SEMANTICS           BYTE
NLS_NCHAR_CONV_EXCP            FALSE
NLS_NCHAR_CHARACTERSET         AL16UTF16
NLS_RDBMS_VERSION              9.2.0.2.1

Always I have to "alter session set nls_numeric_characters=',.'" on my sql*plus.
I've tried put this on login.sql, but doesn't seem work.

Any suggest?
Thks, Marcio

          Followup:

then you login.sql isn't firing as login.sql is just a script that you could
have typed in by hand.

run it manually:

SQL> @login

and see what happens.  if it doesn't change the nls settings, it is not the
right login.sql you changed!

      Rating: 5
May be a bug --
      My bug!  August 11, 2003
Reviewer:  Marcio
       from Br
It was missing ";"

...
set pagesize 9999
set linesize 140
set verify off
alter session set nls_date_format='dd-mo-yyyy'
alter session set nls_numeric_characters='.,'
...

When i put ";" semicolon works... Is it possible hide a message "session
altered" from output sql*plus? -- Just first time when I login.

C:\migracao\loads>plus /

SQL*Plus: Release 9.2.0.2.0 - Production on Mon Aug 11 10:03:59 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.2.1 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.2.0 - Production

Session altered.  <<--------------

Session altered.  <<--------------

Thks, Marcio

          Followup:

just stick a set termout off/set termout on before/after the alters

      Rating:
      3
ORA-12705  November 04, 2003
Reviewer:
      reader
I have oracle 8i and 9i developer in tow different homes.
now i installed oracle 9i developer suite complete installation.after
installation iam not able to access any of my oracle tools sql*plus or pl/sql
developer .
iam getting ORA-12705 error.
i modified NLS_LANG to AMERICAN using regedit. (is this valid )
but i couldnt get out of this error.
any advice ?
thanks in advance
reader

          Followup:

a valid setting would be something like

AMERICAN_AMERICA.WE8ISO8859P1

suggest you check out support note <Note:158654.1> on metalink.oracle.com

      Rating: 4
date language
      setting has no effect  November 17, 2003
Reviewer:
      A reader
I am using Oracle 9i.  I've set the nls_language=english and
nls_date_languaage=english in init.ora.  And I've checked the parameters are
loaded by using "show parameters".  But if I type "select to_char(sysdate,
'mon') the month is still not English.  However, if I use "alter session set
nls_language=english" then the month will be in English. Why does the language
settings in init.ora has no effect?

          Followup:

http://asktom.oracle.com/~tkyte/Misc/NLSDateFormat.html
client must have some NLS settings made and thus all init.ora NLS parameters are
re-defaulted (not used from the init.ora)

November 17, 2003 Pratap -- Thanks for the question regarding
      "Pipelined functions", version 9.0.0

      You Asked
Hi Tom

Can you please give an example of using pipelined functions in a *package*. I am
not able to make it run within a package. I want to return an array of records
from the pipelined function, say like

name_1, address_1, city_1, hash value 1
name_2, address_2, city_2, hash value 2
..........
..........

Everywhere the examples given are of standalone functions. Also please refer to
bug number 2136476 on Metalink regarding these pipelined functions. I
desperately want to use such a wonderful feature.

Thanks and regards

Pratap

      and we said...
Well, that bug has to do with trying to use a type inside of a package -- which
will never work (the fix to the bug is to pump out the correct error message).
So, if it is your desire to have the types created inside the package -- that
never has been and most likely never will be "supported".  We need to use SQL
types.

But -- just creating a pipelined in a package is easy:

tkyte@TKYTE9I.US.ORACLE.COM> create or replace type myScalarType as object
  2  ( a   int,
  3    b   date,
  4    c   varchar2(25)
  5  )
  6  /

Type created.

tkyte@TKYTE9I.US.ORACLE.COM>
tkyte@TKYTE9I.US.ORACLE.COM> create or replace type myTableType as table of
myScalarType
  2  /

Type created.

tkyte@TKYTE9I.US.ORACLE.COM>
tkyte@TKYTE9I.US.ORACLE.COM>
tkyte@TKYTE9I.US.ORACLE.COM> create or replace package my_pkg
  2  as
  3          function f return myTableType PIPELINED;
  4  end;
  5  /

Package created.

tkyte@TKYTE9I.US.ORACLE.COM> create or replace package body my_pkg
  2  as
  3          function f return myTableType
  4          PIPELINED
  5          is
  6          begin
  7                  for i in 1 .. 5
  8                  loop
  9                          pipe row ( myScalarType( i, sysdate+i, 'row ' || i
) );
 10                  end loop;
 11                  return;
 12          end;
 13  end;
 14  /

Package body created.

tkyte@TKYTE9I.US.ORACLE.COM>
tkyte@TKYTE9I.US.ORACLE.COM> select * from table( my_pkg.f() );

         A B         C
---------- --------- -------------------------
         1 29-JUN-02 row 1
         2 30-JUN-02 row 2
         3 01-JUL-02 row 3
         4 02-JUL-02 row 4
         5 03-JUL-02 row 5

tkyte@TKYTE9I.US.ORACLE.COM>
Review &
      Followup

      Rating:
      5
  June 28, 2002
Reviewer:
      A reader
Tom,

 Can you explain me PIPELINED function in simple words?
 It is really confusing. In which scenerio we cau use this function.

 Thnaks

          Followup:

search for pipelined on this site.

Basically, when you would like a PLSQL (or java or c) routine to be the "source"
of data -- instead of a table -- you would use a pipelined function.

Data transformation for example. (perform some procedural logic to transform the
data)

      Rating: 5
Using rownum
      with pipelined functions  March 27, 2003
Reviewer:
      Martin  from UK
Hi Tom,

quick question :

If I declare a pipelined function which does the old n-row table functionality :

SQL> CREATE OR REPLACE TYPE test_type AS TABLE OF NUMBER;
  2  /

Type created.

SQL> CREATE OR REPLACE FUNCTION test_func RETURN test_type
  2  PIPELINED
  3  AS
  4  BEGIN
  5    FOR i IN 1..100
  6    LOOP
  7      PIPE ROW(i);
  8    END LOOP;
  9  END test_func;
 10  RETURN;
 11  /

Function created.

But then add a rownum predicate to a query on this function, what does oracle do
in order to fulfil this? Is the logic of rownum somehow merged into the function
itself?

SQL> SELECT * FROM TABLE(test_func) WHERE rownum < 10;

COLUMN_VALUE
------------
           1
           2
           3
           4
           5
           6
           7
           8
           9

9 rows selected.

Thanks in advance

          Followup:

depends on the access plan totally -- whether Oracle "aborts" the query against
the table() function or if it has to do the entire thing AND THEN apply rownum.

ops$tkyte@ORA920> exec dbms_application_info.set_client_info(0);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from table(test_func) where rownum < 10;

COLUMN_VALUE
------------
           1
           2
           3
           4
           5
           6
           7
           8
           9

9 rows selected.

ops$tkyte@ORA920> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
10

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_application_info.set_client_info(0);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select *
  2    from table(test_func) full outer join
  3         dual on (to_char(column_value)=dummy)
  4   where rownum < 10;

COLUMN_VALUE D
------------ -
          72
          47
           3
          45
          89
          66
          70
          90
          37

9 rows selected.

ops$tkyte@ORA920> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
100

      Rating: 5
And the
      "infinite" solution?  March 27, 2003
Reviewer:
      Martin  from UK
Thanks Tom,

presumably then, declaring a pipelined function which would (if it were a
traditional function) NEVER stop, i.e.

SQL> CREATE OR REPLACE FUNCTION test_func RETURN test_type
  2  PIPELINED
  3  AS
  4    i  PLS_INTEGER := 0;
  5  BEGIN
  6    LOOP
  7      i := i + 1;
  8      PIPE ROW(i);
  9    END LOOP;
 10    RETURN;
 11  END test_func;
 12  /

Function created.

SQL> SELECT * FROM TABLE(test_func) WHERE rownum < 10;

COLUMN_VALUE
------------
           1
           2
           3
           4
           5
           6
           7
           8
           9

9 rows selected.

This will ALWAYS abort the query? (I presume it must do, since it'd be pretty
difficult to materialize this? ;-))

          Followup:

I would not count on it.  I would parameterize the function test_func to take
the number of rows to generate as INPUT

select * from table(test_func(:n));

and bind in 10.

      Rating: 5
Sorry, forgot
      something...  March 27, 2003
Reviewer:
      Martin  from UK
Sorry, Tom, forgot to ask...

if you were using pipelined functions for n-row functionality (which I
personally think are pretty neat), which would be the most efficient then?
passing the number of rows in the function call, TABLE(function(n)), or having
an "infinite" loop and use rownum?

Thanks in advance

          Followup:

i would (do) pass in the number of rows to synthesize.

      Rating: 4
Can I process
      multiple rows and then pipe one out?  April 09, 2003

Reviewer:  Matt  from Australia
Say,I have a table with data in, something like:

my_tab
ID NUMBER,
start DATE,
end DATE

example data is something like:

1 01/01/2000 03/01/2000
1 03/01/2000 08/01/2000
1 08/01/2000 21/01/2000
1 21/02/2000 05/05/2000
2 05/05/2000 25/05/2000
2 25/05/2000 26/05/2000
2 01/06/2000 10/06/2000
3 ...

So, I have 'consecutive' values.

What I need to do is pull these out of the table and process them in sets of
ID's so that I can work out the earliest start and latest end dates for each id.
The results in this case should be:

1 01/01/2000 05/05/2000
2 05/05/2000 26/05/2000
2 01/06/2000 10/06/2000
3 ...

Can I do this using a pipelined function? Say I have a REF CURSOR which defines
the result set above, can I retreive the data in sets of ID's to process, work
out the earliest start dates and latest end dates and then pipe back one or more
rows (as in the siuration above where Id 1 has four input rows and one output
row and ID 2 has 3 input rows and
two output rows...ID 3 may have many more output rows, say no more than 10).

My questions really are:

- How do I pull out the data in ID order in sets of ID's (do I just monitor the
fetch result looking for a change in ID?)?
- Can pipelined functions be used in this way - many rows in...processsing...and
many rows out?

A simple example would really be appreciated.

Best Regards.

          Followup:

In the "olden days" gone by -- we called this "control break processing".  You
loop until something changes -- and then output.

I'll do this two ways.  Once using pure SQL (cause its fun, gets the brain
going) and once as a pipelined function.

they do the same thing - just differently....

ops$tkyte@ORA920> create table t ( id number, start_date date, end_date date );
Table created.

ops$tkyte@ORA920> alter session set nls_date_format = 'dd/mm/yyyy';
Session altered.

ops$tkyte@ORA920> insert into t values ( 1,'01/01/2000','03/01/2000' );
ops$tkyte@ORA920> insert into t values ( 1,'03/01/2000','08/01/2000' );
ops$tkyte@ORA920> insert into t values ( 1,'08/01/2000','21/01/2000' );
ops$tkyte@ORA920> insert into t values ( 1,'21/01/2000','05/05/2000' );
I assumed that was a typo in the original input -- 21/01, not 21/02...
ops$tkyte@ORA920> insert into t values ( 2,'05/05/2000','25/05/2000' );
ops$tkyte@ORA920> insert into t values ( 2,'25/05/2000','26/05/2000' );
ops$tkyte@ORA920> insert into t values ( 2,'01/06/2000','10/06/2000' );

ops$tkyte@ORA920> insert into t values ( 4,'01/06/2000','02/06/2000' );

ops$tkyte@ORA920> begin
  2      dbms_output.put_line( trunc(sysdate+1) || '..' || (trunc(sysdate+10)+1)
);
  3      for i in 1 .. 10
  4      loop
  5          insert into t values ( 3, trunc(sysdate+i), trunc(sysdate+i)+1 );
  6      end loop;
  7
  8      dbms_output.put_line( trunc(sysdate+12) || '..' ||
(trunc(sysdate+100)+1) );
  9      for i in 12 .. 100
 10      loop
 11          insert into t values ( 3, trunc(sysdate+i), trunc(sysdate+i)+1 );
 12      end loop;
 13
 14      dbms_output.put_line( trunc(sysdate+200) || '..' ||
(trunc(sysdate+200)+1) );
 15      for i in 200 .. 200
 16      loop
 17          insert into t values ( 3, trunc(sysdate+i), trunc(sysdate+i)+1 );
 18      end loop;
 19
 20      dbms_output.put_line( trunc(sysdate+300) || '..' ||
(trunc(sysdate+325)+1) );
 21      for i in 300 .. 325
 22      loop
 23          insert into t values ( 3, trunc(sysdate+i), trunc(sysdate+i)+1 );
 24      end loop;
 25  end;
 26  /
10/04/2003..20/04/2003
21/04/2003..19/07/2003
26/10/2003..27/10/2003
03/02/2004..29/02/2004

PL/SQL procedure successfully completed.

There I wanted to test boundary value conditions (single row time frames, many
multi-row ones)

And now for the SQL.  If you are interested in the "how", take the inline views
one by one from the inside out and run them by themselves.  See what they do and
then see what adding the next inline view does to that...

ops$tkyte@ORA920> select *
  2    from (
  3  select id,
  4         start_date,
  5         nvl(lead(end_date) over (partition by id order by rn), end_date)
end_date,
  6         rn
  7    from (
  8  select t.*,
  9         row_number() over ( partition by id order by start_date ) rn
 10    from (
 11  select id,
 12         lag(end_date) over (partition by id order by start_date) prior_end,
 13         start_date,
 14         end_date,
 15         lead(start_date) over (partition by id order by start_date)
next_start
 16    from t) t, ( select rownum x from all_objects where rownum <= 2)
 17   where ( (prior_end is null or
 18            next_start is null or
 19            (prior_end <> start_date) or
 20            (end_date <> next_start))
 21           and x = 1)
 22         or
 23         (x = 2 and (prior_end is null AND next_start is null))
 24         or
 25         (x = 2 and (prior_end <> start_date AND end_date <> next_start ))
 26         )
 27         )
 28   where mod(rn,2) = 1
 29  /

        ID START_DATE END_DATE           RN
---------- ---------- ---------- ----------
         1 01/01/2000 05/05/2000          1
         2 05/05/2000 26/05/2000          1
         2 01/06/2000 10/06/2000          3
         3 10/04/2003 20/04/2003          1
         3 21/04/2003 19/07/2003          3
         3 26/10/2003 27/10/2003          5
         3 03/02/2004 29/02/2004          7
         4 01/06/2000 02/06/2000          1

8 rows selected.

and for the procedural approach, we'll need a collection type to return

ops$tkyte@ORA920> create or replace type myScalarType as object
  2  ( id number, start_date date, end_date date )
  3  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace type myArrayType as table of myScalarType
  2  /

Type created.

and I'll setup my function to take a cursor in -- it can by a query against
any table the returns an ID, DATE1, DATE2 sorted by ID, DATE1 and will do this
sort of processing for us

ops$tkyte@ORA920> create or replace function f( p_cursor in SYS_REFCURSOR )
return myArrayType
  2  PIPELINED
  3  as
  4      l_id number;
  5      l_start_date date;
  6      l_end_date   date;
  7      l_data       myScalarType;
  8  begin
  9      fetch p_cursor into l_id, l_start_date, l_end_date;
 10      if ( p_cursor%notfound )
 11      then
 12          close p_cursor;
 13          return;
 14      end if;
 15
 16      l_data := myScalarType( l_id, l_start_date, l_end_date );
 17          l_start_date := l_end_date;
 18      loop
 19          if ( l_id <> l_data.id OR l_start_date <> l_data.end_date )
 20          then
 21              pipe row (l_data);
 22              l_data := myScalarType( l_id, l_start_date, l_end_date );
 23          else
 24              l_data.end_date := l_end_date;
 25          end if;
 26
 27          fetch p_cursor into l_id, l_start_date, l_end_date;
 28          exit when p_cursor%notfound;
 29      end loop;
 30      pipe row( l_data );
 31          close p_cursor;
 32      return;
 33  end;
 34  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select *
  2    from TABLE( f(CURSOR(select id, start_date, end_date
  3                           from t
  4                          order by id, start_date) ) );

        ID START_DATE END_DATE
---------- ---------- ----------
         1 01/01/2000 05/05/2000
         2 05/05/2000 26/05/2000
         2 01/06/2000 10/06/2000
         3 10/04/2003 20/04/2003
         3 21/04/2003 19/07/2003
         3 26/10/2003 27/10/2003
         3 03/02/2004 29/02/2004
         4 01/06/2000 02/06/2000

8 rows selected.

      Rating:
      5
parallel_enable  April 09, 2003
Reviewer:
      Pratap  from India
Hi Tom

Can we parallel_enable pipelined functions with such "control break processing"
logic.

Thanks

Pratap

          Followup:

think about it....

can a pipelined function be run in parallel at all really.. how would that
work... what would it mean?  Looking at the virtual table function -- how would
Oracle know to take  "test_func(1000)" and break that into "test_func( 1..500 )"
and "test_func( 501..1000 )" -- how does it even know the lower bound is 1?  why
not -100000?  Similar sticky issues exist for the cursor()

so, while syntactically it might work (it might compile) -- it would not really
make sense, as it is the generator of data in this case, not a consumer of data
-- and you (we) didn't write it to be parallel (cannot in fact)

      Rating: 5
This is
      great!  April 09, 2003
Reviewer:  Matt
       from Australia
Great stuff. It took a while to get my head around the SQL, but I think I am
there. You appear to split the result set in two using a cartesian join and then
query each copy to pull out what you need. Can you please explain this part in a
few lines?

As usual there were a couple of hidden layers of complexity (that I hadn't
shared) which meant that I tried the pipelined function approach.

However, I am getting the following error on using the pipeline:

ORA-03001: unimplemented feature
ORA-02070: database  does not support some function in this context

This comes from my (complex) cursor. If I materialize the cursor into a table
the pipeline fn works fine, which suggests that the CURSOR operator might not
support all the available SQL options. In my cursor I use:

- LEFT OUTER JOIN syntax
- case..when..else
- NVL
- inline views

My guess is that one of these is not understood by the CURSOR operator.

Are you aware of such a restriction?

I'll try and get back to you and let you know what caused the SQL to fall over
(I'm on vacation in a few hours).

Thanks once again.

          Followup:

for the ora2070, we'd sort of need to see the query and understand the base
objects (eg: are they queried over a dblink -- does it work as a ref cursor by
itself).  All of those constructs are OK

ops$tkyte@ORA920> select *
  2    from TABLE( f(CURSOR(select nvl(empno,1234) empno,
  3                                case when sysdate = sysdate then hiredate
  4                                     else null
  5                                 end s, hiredate e
  6                           from (select empno, hiredate, dname
  7                                   from emp left outer join dept on
(emp.deptno = dept.deptno))
  8                          order by empno, hiredate ) ) );

        ID START_DAT END_DATE
---------- --------- ---------
      7369 17-DEC-80 17-DEC-80
      7499 20-FEB-81 20-FEB-81
      7521 22-FEB-81 22-FEB-81
      7566 02-APR-81 02-APR-81
      7654 28-SEP-81 28-SEP-81
      7698 01-MAY-81 01-MAY-81
      7782 09-JUN-81 09-JUN-81
      7788 09-DEC-82 09-DEC-82
      7839 17-NOV-81 17-NOV-81
      7844 08-SEP-81 08-SEP-81
      7876 12-JAN-83 12-JAN-83
      7900 03-DEC-81 03-DEC-81
      7902 03-DEC-81 03-DEC-81
      7934 23-JAN-82 23-JAN-82

14 rows selected.

but the error points to "something over a dblink" being an issue....

The cartesian product with the two row set is simply to duplicate singleton
entries like:

ops$tkyte@ORA920> insert into t values ( 4,'01/06/2000','02/06/2000' );

What I do is duplicate each row but only keep both if it turns out that it is
the only observation (id = 3 has one such row as well) -- meaning there is no
"lag" or "lead" row with the start_date/end_date matching to this current row.
I need two cause my logic required it -- I kept a row at the beginning of the
series and the row at the end and then used lead to pick off the values I needed
and kept just the first row of each pair -- if I didn't have the pair, the logic
fell apart.

      Rating:
      3
  May 04, 2003
Reviewer:
      A reader
hello tom,

after getting a little bit familiar with concepts of (pipelined) table functions
I'm looking now for "use cases" of SYS.ANYDATASET, SYS.ANYTYPE, and SYS.ANYDATA
types as results of those functions (as mentioned - but not explained - in
PL/SQL User's Guide and Reference for Release 2).

I've never used any of those types before. could you please provide a sample of
how to use these new types with table functions?

          Followup:

search for anydata for an example of that

as for the anytype and anydataset -- in 9i and before -- i haven't used them
myself.  They would normally be used for creating new generic datatypes or
generic routines.

      Rating:
      4
  May 18, 2003
Reviewer:
      A reader
I have two additional questions on table functions:

1st: is the order of rows returned from the table function within a SQL
statement the exact same order in which the entries were "piped" into the
internal collection (so that no order by clause is needed)?

2nd: there seems to be no need to let the internal collection GROW. apparently
it works fine to NOT extend that collection but "reuse" the one and only entry
over and over again to pipe its content out. but can one rely on such a
behaviour?

          Followup:

1) maybe, maybe not,  I would not count on it.  You should not count on the
order of rows in a result set without having an order by.  If you join or do
something more complex then simply "select * from table( f(x) )", the rows could
well come back in some other order.

empirically -- they appear to come back as they are piped.
I do not believe it is documented that this is so.

In fact, collections of type NESTED TABLE are documented to explicitly *not have
the ability to preserve order*.

2) that is the way pipelined functions should be coded, not with collections at
all -- but with just the scalar type you reuse over and over.  I cannot imagine
why you would use extend.

      Rating: 5
Order of
      elements  May 19, 2003
Reviewer:  Martin
       from UK
Hi Tom,

can I just clarify what you said earlier :

"In fact, collections of type NESTED TABLE are documented to explicitly *not
have
the ability to preserve order*."

I always thought that this related to PERSISTENTLY stored nested tables (i.e. a
nested table column). For locally declared collections, i.e. assigned as a local
"variable", I thought that the only difference between a nested table and a
VARRAY (since a VARRAY does retain order) was that the VARRAY is bounded?

Is this not true then?

Thanks in advance

          Followup:

empirically, they are ordered.  observation leads you to believe so.

however, no where is it documented this is so, in fact the documentation says
otherwise.  And especially in regards to pipelined functions I would say it
would be risky to rely on it "being so"

      Rating: 5
It MUST be
      true... surely?  May 19, 2003
Reviewer:
      Martin  from UK
It must be true that variables of a nested table type must retain their
ordering, since otherwise, what's the point of the index?

Placing a value in index 1 must always stay as index 1 surely... if not,
methinks it's time to get back to INDEX BY tables! ;-)

Regards

          Followup:

what index?

oh, you mean a subscript.  sure, as I said "empirically, it would be observed
they do"

however -- it is not documented ( i would assume that for a variable, it would
be safe to assume )

however -- the question was about PIPED output here -- here, I would not
make that assumption.  I would NOT rely on that.

from sql there is one sure way to get ordered data -- that is ORDER BY.  Don't
use it at your own risk.

      Rating:
      5
Thanks  May 19, 2003
Reviewer:
      Martin  from UK
Hi Tom,

yes, I was talking about variables (as I mentioned in the previous post).

It's a bit funny that it's not documented. I would think that there are numerous
systems out there which rely on the fact that "rows" in nested table variables
are in the same position as how they were put in....

Thanks for all your help.

      Rating: 4
A bit more on
      ordering...  May 19, 2003
Reviewer:  Adrian
      Billington  from UK
Tom / Martin

I would consider that the *order* of a collection is inherently related to the
way you access the elements. In a traditional loop through a collection variable
(first to last or vice versa), by its very nature you will be accessing the
elements in some sequential order (either forwards or backwards depending on the
direction of your loop). That you can pretty much guarantee to be consistent,
because as Martin says, to load an element in subscript one and then have it
turn out elsewhere later on would make collections unusable.

But when you wish to access the elements of a collection using SQL, then all
bets are off, as with standard table access. Nowhere does Oracle tell you it
will supply rows from a table back in the same order that they were entered - so
the same must apply from a SELECT against a cast collection - i.e. use an ORDER
BY to guarantee an order.

Regards
Adrian

      Rating:
      5
  July 25, 2003
Reviewer:
      A reader
There is something about pipelined functions in this paper,
please if you find something wrong please tellme.

http://www.geocities.com/juancarlosreyesp/OracleAskTom.pdf

      Rating: 5
Hi Tom, what
      I'm doing wrong?  August 08, 2003
Reviewer:
      juancarlosreyesp@yahoo.com  from Bolivia
Hi Tom, can you please explainme what I'm doing wrong.

DROP TYPE BBV.TYP_CUPNEG;
DROP TYPE BBV.TYO_CUPNEG;

CREATE TYPE BBV.TYO_CUPNEG AS OBJECT
( CUNX_SUBPROD INT,
  CUNX_CADENA  INT )
/

CREATE TYPE BBV.TYP_CUPNEG AS TABLE OF TYO_CUPNEG
/
CREATE PUBLIC SYNONYM TYP_CUPNEG FOR BBV.TYP_CUPNEG;
CREATE PUBLIC SYNONYM TYO_CUPNEG FOR BBV.TYO_CUPNEG;

CREATE OR REPLACE
FUNCTION bbv.fut_cupneg
RETURN TYP_CUPNEG PIPELINED
IS
BEGIN

 FOR A IN 1..3 LOOP
  PIPE ROW( TYO_CUPNEG( 1,1) );
 END LOOP;

RETURN;
END;
/

Then I got the 3 records but they are empty records
SQL> SELECT * FROM TABLE(FUT_CUPNEG);

CUNX_SUBPROD CUNX_CADENA
------------ -----------

SQL>
Thanks

          Followup:

ops$tkyte@ORA920> CREATE TYPE TYO_CUPNEG AS OBJECT
  2  ( CUNX_SUBPROD INT,
  3    CUNX_CADENA  INT )
  4  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> CREATE TYPE TYP_CUPNEG AS TABLE OF TYO_CUPNEG
  2  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> CREATE OR REPLACE
  2  FUNCTION fut_cupneg
  3  RETURN TYP_CUPNEG PIPELINED
  4  IS
  5  BEGIN
  6
  7   FOR A IN 1..3 LOOP
  8    PIPE ROW( TYO_CUPNEG( 1,1) );
  9   END LOOP;
 10
 11
 12  RETURN;
 13  END;
 14  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> SELECT * FROM TABLE(FUT_CUPNEG);

CUNX_SUBPROD CUNX_CADENA
------------ -----------
           1           1
           1           1
           1           1

I cannot reproduce.  do not use public synonyms (ugh), run the example as I
did and see what happens.

      Rating:
      5
solution  August 08, 2003
Reviewer:
      juancarlosreyesp@yahoo.com
Hi Tom, the previous problem seems to be a bug.

I did the same in this other way and worked.

DROP TYPE BBV.TYP_CUPNEG;
DROP TYPE BBV.TYO_CUPNEG;

CREATE TYPE BBV.TYO_CUPNEG AS OBJECT
( CUN_SUBPROD VARCHAR2(4),
  CUN_CADENA  VARCHAR2(50) )
/
CREATE TYPE BBV.TYP_CUPNEG AS TABLE OF TYO_CUPNEG
/
CREATE PUBLIC SYNONYM TYP_CUPNEG FOR BBV.TYP_CUPNEG;
CREATE PUBLIC SYNONYM TYO_CUPNEG FOR BBV.TYO_CUPNEG;
-- Start of DDL Script for Function ADM.FUT_CUPNEG
-- Generated 8-ago-2003 19:15:42 from ADM@SBBV.WORLD

CREATE OR REPLACE
FUNCTION bbv.fut_cupneg return
TYP_CUPNEG
is
    l_data TYP_CUPNEG;
    cAntSubProducto VARCHAR2(4);
    cCupones     VARCHAR2(50);
    i NUMBER := 0;
begin
    l_data := TYP_CUPNEG();
    for rCupon in ( SELECT a.cun_subprod, a.cun_nrocup, ROWNUM
                    FROM bbv.cupneg a
                    ORDER BY 1,2 ) loop
      IF cAntSubProducto IS NOT NULL AND
         NOT cAntSubProducto = rCupon.cun_subprod THEN
        l_data.extend;
        i := i + 1;
        l_data(i) := TYO_CUPNEG( cAntSubProducto,RTRIM(cCupones,',') );
        cCupones := NULL;
      END IF;
      cAntSubProducto := rCupon.cun_subprod;
      cCupones := cCupones || TRIM(TO_CHAR(rCupon.cun_nrocup)) || ',' ;
    end loop;
    l_data.extend;
    l_data(i) := TYO_CUPNEG( cAntSubProducto,RTRIM(cCupones,',') );
    return l_data;
end;
/

select *  from
TABLE( cast(
fut_cupneg as
TYP_CUPNEG ) )

      Rating: 5
Cannot
      reproduce...  August 11, 2003
Reviewer:
      juancarlosreyesp@yahoo.com
Thanks tom, running your example I got the same empty spaces, I suppose it's a
bug, we applied the patch that solved the "lock" when compiling functions, maybe
that's the reason.

CREATE TYPE TYO_CUPNEG AS OBJECT
( CUNX_SUBPROD INT,
   CUNX_CADENA  INT )
/
CREATE TYPE TYP_CUPNEG AS TABLE OF TYO_CUPNEG
/
CREATE OR REPLACE
FUNCTION fut_cupneg
RETURN TYP_CUPNEG PIPELINED
IS
BEGIN
FOR A IN 1..3 LOOP
PIPE ROW( TYO_CUPNEG( 1,1) );
END LOOP;
RETURN;
END;
/
SQL> SELECT * FROM TABLE(FUT_CUPNEG);

CUNX_SUBPROD CUNX_CADENA
------------ -----------
SQL>
But It's working in the other way, thank you :)

      Rating: 5
Definetively is
      a bug  August 21, 2003
Reviewer:
      A reader
I confirmed is a bug related with that database, your function was correct.
thanks :)

      Rating: 5
Why I have to
      use cast?  August 22, 2003
Reviewer:
      juancarlosreyesp@yahoo.com  from Bolivia
Hi Tom, could you please explainme why I have to use cast in this situation.

CREATE OR REPLACE
TYPE daz.typ_string_to_table AS TABLE OF VARCHAR2(50);
/

CREATE OR REPLACE
FUNCTION daz.fut_string_to_table( cString VARCHAR2, cSeparador VARCHAR2 )
RETURN TYP_STRING_TO_TABLE
PIPELINED IS
cItem VARCHAR2(50);
BEGIN

FOR nI IN 1..LENGTH(cString) LOOP
 cItem := cItem || SUBSTR( cString, nI,1 );
 IF NVL(SUBSTR( cString, nI,1 ),'~') = NVL(cSeparador,'~') THEN
  PIPE ROW(SUBSTR(cItem,1,LENGTH(cItem)-1) );
  cItem := NULL;
 END IF;
END LOOP;
 PIPE ROW(cItem);
RETURN;
END;
/

SQL> select * from table(daz.fut_string_to_table( 'a,b,c,d',',' ) );
select * from table(daz.fut_string_to_table( 'a,b,c,d',',' ) )
              *
ERROR en lÝnea 1:
ORA-22905: cannot access rows from a non-nested table item

SQL> select cast( column_value as varchar2(50)) from
table(daz.fut_string_to_table( 'a,b,c,d',',' ) );

CAST(COLUMN_VALUEASVARCHAR2(50
--------------------------------------------------
a
b
c
d

SQL>

          Followup:

might be a quirk of the release you are on

ops$tkyte@ORA920> CREATE OR REPLACE
  2  TYPE typ_string_to_table AS TABLE OF VARCHAR2(50);
  3  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> CREATE OR REPLACE
  2  FUNCTION fut_string_to_table( cString VARCHAR2, cSeparador VARCHAR2 )
  3  RETURN TYP_STRING_TO_TABLE
  4  PIPELINED IS
  5  cItem VARCHAR2(50);
  6  BEGIN
  7
  8  FOR nI IN 1..LENGTH(cString) LOOP
  9   cItem := cItem || SUBSTR( cString, nI,1 );
 10   IF NVL(SUBSTR( cString, nI,1 ),'~') = NVL(cSeparador,'~') THEN
 11    PIPE ROW(SUBSTR(cItem,1,LENGTH(cItem)-1) );
 12    cItem := NULL;
 13   END IF;
 14  END LOOP;
 15   PIPE ROW(cItem);
 16  RETURN;
 17  END;
 18  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from table(fut_string_to_table( 'a,b,c,d',',' ) );

COLUMN_VALUE
--------------------------------------------------
a
b
c
d

      Rating: 5
:)
      Thanks  August 22, 2003
Reviewer:
      A reader

      Rating: 4
Back to the
      original question ...  August 27, 2003
Reviewer:
      Mike Wilson  from Los Angeles, CA
I believe that Tom states in this article that
  "if it is your desire to have the types created inside the package -- that
never has been and most likely never will be supported. "

Possibly I misunderstand the question but I think this *does* actually work (at
least in 9203).  I had the same problem.  I don't like having anything
"dangling" about outside of my packages (thats what there there for right?).
With the introduction of Pipelined functions that don't require a CAST I think
you can have the best of both worlds now, package specific Types that aren't
exposed to SQL but still work from the CLI.

SQL> CREATE OR REPLACE
  2  PACKAGE pltest
  3    IS
  4    type virtual_table_type is table of number;
  5    function f( p_num_rows in number ) return virtual_table_type pipelined;
  6  END;
  7  /

Package created.

SQL>
SQL> CREATE OR REPLACE
  2  PACKAGE BODY pltest
  3  IS
  4    function f( p_num_rows in number ) return virtual_table_type PIPELINED
  5    is
  6    begin
  7        for i in 1 .. p_num_rows
  8        loop
  9            pipe row( i );
 10        end loop;
 11        return;
 12    end;
 13  END;
 14  /

Package body created.

SQL> select * from table(pltest.f(5));

COLUMN_VALUE
------------
           1
           2
           3
           4
           5

That being said, pipelined functions are cool.  Though I had the same problem
with the docs and, in general the examples I found.  Stand alone functions are
great to learn the concepts but there is almost always a "gotcha" when you go to
use this stuff in packages.  I wish more examples were packaged instead of
stand-alone for completeness sake.

Ok, this is the part where Tom rips my answer apart (cringing)...

          Followup:

I think it is a bug that is accidently worked.  (no ripping here).  I'll be
filing a bug for sure -- if you rerun your example, I get an internal error the
second time.

ops$tkyte@ORA920LAP> CREATE OR REPLACE
  2  PACKAGE pltest
  3  IS
  4  type virtual_table_type is table of number;
  5  function f( p_num_rows in number ) return virtual_table_type pipelined;
  6  END;
  7  /
CREATE OR REPLACE
*
ERROR at line 1:
ORA-00600: internal error code, arguments: [17012], [0x55C7C68C], [], [], [],
[], [], []

also, it won't work for anything other then scalar types of course..  not
suggested due to the above.

      Rating: 4
***To reader
      who posted following link  August 28, 2003

Reviewer:  Suvarna Patki  from Manchester, UK

Hi Tom,

Does this link point to some Asktom archives???

http://www.geocities.com/juancarlosreyesp/OracleAskTom.pdf
***Above link is no longer available.

Thanks!!!

Suvarna

          Followup:

it is not mine nor is it related to asktom

      Rating: 5
Packaged types
      and pipeline functions  October 08, 2003
Reviewer:
      Stefan Grefen  from Germany
I did run into the same problem as TOM using types declared in a package and a
function using the type in the same package (it works exactly once).
However declaring the pipelined function outside of the package (still using the
type out of the package) makes it work reliable. Is this a bug too (should I
avoid to use this?)

      Rating: 5
high number of
      rows  November 17, 2003
Reviewer:
      A reader
Hi

If the PL/SQL code returns massive amount of rows would we get ORA-04030 if we
want to pipe that results set?

Or this works like UNIX pipes? A simple data stream? (not stored in memory)

          Followup:

if you do -- it would be a bug.

pipe works by letting the client have the first row

waaayyyy before the client gets the last row.

consider:

ops$tkyte@ORA920LAP> create or replace type myTabletype as table of
varchar2(2000)
  2  /

Type created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create or replace function f return myTabletype
  2  PIPELINED
  3  as
  4  begin
  5          loop
  6                  pipe row( rpad('*',2000,'*') );
  7          end loop;
  8          return;
  9  end;
 10  /

Function created.

ops$tkyte@ORA920LAP> @connect /
ops$tkyte@ORA920LAP> set termout off
ops$tkyte@ORA920LAP> @&gname
ops$tkyte@ORA920LAP> set termout off
ops$tkyte@ORA920LAP> REM GET afiedt.buf NOLIST
ops$tkyte@ORA920LAP> set termout on
ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> column value new_val V
ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select a.name, b.value
  2    from v$statname a, v$mystat b
  3   where a.statistic# = b.statistic# and a.name = 'session pga memory max';

NAME                                VALUE
------------------------------ ----------
session pga memory max             220508

ops$tkyte@ORA920LAP> select count(*) from TABLE(f) where rownum <= 100;

  COUNT(*)
----------
       100

ops$tkyte@ORA920LAP> select a.name, b.value, b.value - &V diff
  2    from v$statname a, v$mystat b
  3   where a.statistic# = b.statistic# and a.name = 'session pga memory max';
old   1: select a.name, b.value, b.value - &V diff
new   1: select a.name, b.value, b.value -     220508 diff

NAME                                VALUE       DIFF
------------------------------ ---------- ----------
session pga memory max             433544     213036

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select count(*) from TABLE(f) where rownum <= 1000000;

  COUNT(*)
----------
   1000000

ops$tkyte@ORA920LAP> select a.name, b.value, b.value - &V diff
  2    from v$statname a, v$mystat b
  3   where a.statistic# = b.statistic# and a.name = 'session pga memory max';
old   1: select a.name, b.value, b.value - &V diff
new   1: select a.name, b.value, b.value -     433544 diff

NAME                                VALUE       DIFF
------------------------------ ---------- ----------
session pga memory max             433544          0

ops$tkyte@ORA920LAP>

no new memory allocated to do 1,000,000 pipes vs 100

      Rating: 5
Cool,
      thanks.  November 17, 2003
Reviewer:  Marc
       from Switzerland
I am happy to see you in Copenhagen in January 04.

      Rating: 5
how about
      hierarchy sql  November 17, 2003
Reviewer:
      Sudhir  from Lexington, KY
for the question
"   Reviews
Bookmark Review | Bottom | Top
  June 28, 2002
Reviewer:  A reader

Tom,

 Can you explain me PIPELINED function in simple words?
 It is really confusing. In which scenerio we cau use this function.

 Thnaks

Followup:
search for pipelined on this site.

Basically, when you would like a PLSQL (or java or c) routine to be the "source"

of data -- instead of a table -- you would use a pipelined function.

Data transformation for example. (perform some procedural logic to transform the

data)

Bookmark Review | Bottom | Top
Using rownum with pipelined functions  March 27, 2003
Reviewer:  Martin  from UK

Hi Tom,

quick question :

If I declare a pipelined function which does the old n-row table functionality :

SQL> CREATE OR REPLACE TYPE test_type AS TABLE OF NUMBER;
  2  /

Type created.

SQL> CREATE OR REPLACE FUNCTION test_func RETURN test_type
  2  PIPELINED
  3  AS
  4  BEGIN
  5    FOR i IN 1..100
  6    LOOP
  7      PIPE ROW(i);
  8    END LOOP;
  9  END test_func;
 10  RETURN;
 11  /

Function created.

But then add a rownum predicate to a query on this function, what does oracle do

in order to fulfil this? Is the logic of rownum somehow merged into the function

itself?

SQL> SELECT * FROM TABLE(test_func) WHERE rownum < 10;

COLUMN_VALUE
------------
           1
           2
           3
           4
           5
           6
           7
           8
           9

9 rows selected.

Thanks in advance

Followup:
depends on the access plan totally -- whether Oracle "aborts" the query against
the table() function or if it has to do the entire thing AND THEN apply rownum.

ops$tkyte@ORA920> exec dbms_application_info.set_client_info(0);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from table(test_func) where rownum < 10;

COLUMN_VALUE
------------
           1
           2
           3
           4
           5
           6
           7
           8
           9

9 rows selected.

ops$tkyte@ORA920> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
10

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_application_info.set_client_info(0);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select *
  2    from table(test_func) full outer join
  3         dual on (to_char(column_value)=dummy)
  4   where rownum < 10;

COLUMN_VALUE D
------------ -
          72
          47
           3
          45
          89
          66
          70
          90
          37

9 rows selected.

ops$tkyte@ORA920> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
100

Bookmark Review | Bottom | Top
And the "infinite" solution?  March 27, 2003
Reviewer:  Martin  from UK

Thanks Tom,

presumably then, declaring a pipelined function which would (if it were a
traditional function) NEVER stop, i.e.

SQL> CREATE OR REPLACE FUNCTION test_func RETURN test_type
  2  PIPELINED
  3  AS
  4    i  PLS_INTEGER := 0;
  5  BEGIN
  6    LOOP
  7      i := i + 1;
  8      PIPE ROW(i);
  9    END LOOP;
 10    RETURN;
 11  END test_func;
 12  /

Function created.

SQL> SELECT * FROM TABLE(test_func) WHERE rownum < 10;

COLUMN_VALUE
------------
           1
           2
           3
           4
           5
           6
           7
           8
           9

9 rows selected.

This will ALWAYS abort the query? (I presume it must do, since it'd be pretty
difficult to materialize this? ;-))

Followup:
I would not count on it.  I would parameterize the function test_func to take
the number of rows to generate as INPUT

select * from table(test_func(:n));

and bind in 10.

Bookmark Review | Bottom | Top
Sorry, forgot something...  March 27, 2003
Reviewer:  Martin  from UK

Sorry, Tom, forgot to ask...

if you were using pipelined functions for n-row functionality (which I
personally think are pretty neat), which would be the most efficient then?
passing the number of rows in the function call, TABLE(function(n)), or having
an "infinite" loop and use rownum?

Thanks in advance

Followup:
i would (do) pass in the number of rows to synthesize.

Bookmark Review | Bottom | Top
Can I process multiple rows and then pipe one out?  April 09, 2003
Reviewer:  Matt  from Australia

Say,I have a table with data in, something like:

my_tab
ID NUMBER,
start DATE,
end DATE

example data is something like:

1 01/01/2000 03/01/2000
1 03/01/2000 08/01/2000
1 08/01/2000 21/01/2000
1 21/02/2000 05/05/2000
2 05/05/2000 25/05/2000
2 25/05/2000 26/05/2000
2 01/06/2000 10/06/2000
3 ...

So, I have 'consecutive' values.

What I need to do is pull these out of the table and process them in sets of
ID's so that I can work out the earliest start and latest end dates for each id.

The results in this case should be:

1 01/01/2000 05/05/2000
2 05/05/2000 26/05/2000
2 01/06/2000 10/06/2000
3 ...

"

Can "connect by" method be used to find the outcome?

As usual, thanks for great insight and creativity!

November 17, 2003 Ken -- Thanks for the question regarding
      "Procedure and Function sharing the same name and parameter.", version
      Oracle 8

      You Asked
Hi Tom,

I encounted a package that has a procedure and function that
shares the same name and parameter.  How would Oracle
differentiate the two?  These procedure/function are called
within sqlplus like this:

exec status.update_staus (-
p_action=>status.step_started, -
p_step_id=>status.load_fnd, -
p_load_id=>to_date ('$FILE', 'YYYYMMDD_HH24MISS'), -
.
.
);

I just don't understand what is happening here..

many thanks
GoJo.

      and we said...
the way in which the packagd procedure/function is called tells
us whether the call the procedure or the function.  In your
example above, I can tell you the PROCEDURE is being called
since there is nothing to recieve the value of the function.
Here is a small example of your issue:

ops$tkyte@8i> create or replace package demo_pkg
  2  as
  3      procedure foo( x in number );
  4      function foo( x in number ) return varchar2;
  5  end;
  6  /

Package created.

ops$tkyte@8i>
ops$tkyte@8i> create or replace package body demo_pkg
  2  as
  3      procedure foo( x in number )
  4      is
  5      begin
  6       dbms_output.put_line( 'PROCEDURE called with ' || x );
  7      end;
  8
  9      function foo( x in number ) return varchar2
 10      is
 11      begin
 12       dbms_output.put_line( 'FUNCTION called with ' || x );
 13       return 'function called';
 14      end;
 15  end;
 16  /

Package body created.

Here I call the procedure version.  Since I did not ASSIGN
demo_pkg.foo to anything or attempt to pass it as a parameter --
it must call the procedure

ops$tkyte@8i>
ops$tkyte@8i> exec demo_pkg.foo(5)
PROCEDURE called with 5

PL/SQL procedure successfully completed.

Here I call the FUNCTION version.  I am using the return value
from the function as the input into another procedure.  Since I
need a return value -- it has to call the function

ops$tkyte@8i> exec dbms_output.put_line( demo_pkg.foo(10) )
FUNCTION called with 10
function called

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating:
      5
  January 16, 2003
Reviewer:
      A reader
Have some related questions, hope you don't mind if I post them here rather than
wait for your banner to change!

1. When performing a task in PL/SQL, when is it appropriate to use a procedure
and when is it appropriate to use function. I am used to create procedures for
most of
the purposes (eg: I have procedure which will rebuild all the indexes after a
load into our datawarehouse). I have used functions only for using within a
select statement.
But recently I came across a programming style of using functions for carriying
out tasks that we generally do using procedures.

Eg: A procedure for rebuilding indexes can be rewritten as a function and called
like this:

begin
if bnadmin.fn_rebuild_index('BNADMIN') then
  dbms_output.put_line('All indexes of BANADMIN rebuilt');
else
  dbms_output.put_line('Error...!! Please check error log for details');
end if;
end;
/

I wanted to know your openion on what you prefer.

2. Secondly, I have seen that while most functions can be executed by selecting
from dual, not all. Some of them have to be executed as above. Can you please
discuss this with some examples.

Thank you very much for hosting this wonderfully helpful forum.

          Followup:

1) it is more a matter of "common sense" then anything else.  If it makes more
sense to be a function, by all means - go for it.

The general "rot" on this is (compute science-ese here):

a function does not modify its inputs and returns a single output. it takes only
in parameters and always returns a value.

a procedure can modify and/all of its inputs and takes in, in/out, out
parameter.

I don't follow that tho, I use functions where they make sense, procedures where
they make sense.

2) you cannot select a function that uses IN OUT variables.
you cannot select a function that modifies the database (unless it is an
autonomous transaction)

      Rating: 3
Is/As any
      difference   October 10, 2003
Reviewer:
      reader
Hi Tom,
      is there any difference between is and as clause of procedure/function
creation I dont' think so and where is the compiled code kept in the database
and if you can please explain how Oracle works on procedure/function to be in
sga as it works on query by calculating hash value.

Thanks for your help as always.

          Followup:

It is purely semantics.

create or replace procedure p AS begin....

reads nicer in english then

create or replace procedure p IS begin....

but in a package:

create or replace package body pkg
as

procedure sounds_nice is begin ....

procedure doesnt_sound_so_nice as begin .....

so, i use them that way.

the code is kept in the idl$ tables in the sys schema.  The code is even easier
then a generic query for code has a fully qualified, unique name associated with
it.  makes it really easy to organize and store.

      Rating: 5
IS/AS
      syntax  October 10, 2003
Reviewer:  Ashiq
      Shamsudeen  from Trichy, Tamil Nadu, India
Tom,

 I believe even these syntax "IS/AS" are come up from ADA programming
language.Isn't it?

          Followup:

mmm, i cannot remember back that far -- it was 1990/1992 since I did Ada -- but
probably, yes.

      Rating:
      5
Overloading?  October 10, 2003
Reviewer:
      Padders  from UK
Tom,

Just a pedants' clarification regarding the original question / answer. When a
function exists with the same name and parameters as a procedure in the same
package would this constitute (be referred to as) an 'overloading'? Looking in
ALL / USER_ARGUMENTS Oracle records this situation as overloading 1 and 2 so
perhaps I answer my own question. Obviously overloadings are typically
determined by the parameters and their type - is there a specific term for this
form of overloading?

          Followup:

this is overloading -- overloading is when an "object" with the same name but a
different "signature" (signature being a function of the inputs/outputs) differ.

Just pretend the function is a procedure with an extra OUT parameter at the end
of the parameter list.

      Rating: 5
why would
      someone want to call same name to proc and func inside packages?
        November 17, 2003
Reviewer:  A reader

Hi

I wonder what´s the use or the point naming the procedures/function to same name
inside a package? (Overloading)

          Followup:

well, it would have been cool if the HTP package (from mod_plsql) would have a
function "escape_sc" as well as a procedure escape_sc

then we could code:

   htp.escape_sc( 'hello & world' );

   htp.p( 'hello ' || htp.escape_sc( 'hello & world' ) || ' world' );

instead of having to have an HTP and HTF (hyper text PROCEDURES, hyper text
FUNCTIONS) package as we do.  we really only needed one package -- not two as we
have.

      Rating: 3
Code
      maintenance  November 18, 2003
Reviewer:
      reader
To make the maintenance of the code easier one should call the other i.e. the
function calls the procedure and returns the value (or vice versa). The idea is
to maintain a single code base and not maintain it in both the function and
procedure. One of them is just a wrapper to the other making them easier to use
in the apps and easy to maintain as well.


Subject | Article | Last Updated (East coast time)
Partition - Modifying the Range Values - 14400 Error | article only | Nov 19, 2003 06:30 am
Refresh complete and rollback | article only | Nov 19, 2003 06:32 am
UTL_FILE pcakage(flat file creaion) | article only | Nov 19, 2003 06:41 am
Extracting text from long column using substr and instr | article only | Nov 21, 2003 07:25 am
Problem to connect as SYSDBA | article only | Nov 21, 2003 07:29 am
Problem to perform TSPITR | article only | Nov 21, 2003 07:30 am
Reducing LIOs | article only | Nov 21, 2003 07:31 am
MODAL Statistical function | article only | Nov 21, 2003 07:34 am
Counting the number of business days between 2 dates. | article only | Nov 21, 2003 07:41 am
Random Number Generation | article only | Nov 21, 2003 07:45 am
controlfile | article only | Nov 21, 2003 07:47 am
Bitmap indexes | article only | Nov 21, 2003 07:51 am
JDBC connections getting dropped by firewall | article only | Nov 21, 2003 07:56 am
"minimize records_per_block" and compact bitmap indexes | article only | Nov 21, 2003 10:47 am
referencing types in pl/sql package from java | article only | Nov 21, 2003 10:57 am
Rebuilding indexing--space consumption | article only | Nov 21, 2003 11:00 am
Multiple User Logons to an Application | article only | Nov 21, 2003 11:08 am
Security in Oracle | article only | Nov 21, 2003 11:11 am
Max string allowed in EXECUTE IMMEDIATE. | article only | Nov 21, 2003 11:25 am
Using Statspack report | article only | Nov 21, 2003 11:31 am
are first_rows, all_rows based on choose optimizer mode? | article only | Nov 21, 2003 11:43 am
consecutive transactions | article only | Nov 21, 2003 03:31 pm
Differences between Oracle Label security and FGAC... | article only | Nov 21, 2003 03:32 pm
How to read hexadecimal data | article only | Nov 21, 2003 03:34 pm
Recommendations for JAVA executing SQL | article only | Nov 21, 2003 03:39 pm
DBMS_SQL : using bind variables in insert stmt | article only | Nov 21, 2003 03:39 pm
Problem related to Import of data | article only | Nov 21, 2003 04:28 pm
Instead of Triggers - where to use? | article only | Nov 21, 2003 04:31 pm
Oracle Backup | article only | Nov 21, 2003 04:38 pm
Running os command/program from java | article only | Nov 21, 2003 04:44 pm
Escape Character | article only | Nov 21, 2003 04:45 pm
Creating hierarchial tree over a view | article only | Nov 21, 2003 04:47 pm
Mutating triggers | article only | Nov 21, 2003 04:50 pm
redo log buffer | article only | Nov 21, 2003 04:51 pm
Converting numbers. | article only | Nov 21, 2003 04:55 pm
largest number sequence | article only | Nov 21, 2003 05:25 pm
Few questions regarding oracle and unix | article only | Nov 21, 2003 05:32 pm
10046 and tkprof | article only | Nov 21, 2003 05:34 pm
Package/Session State Management - Mod PL/SQL | article only | Nov 21, 2003 05:37 pm
Total Required in Hierarchical Query | article only | Nov 21, 2003 05:45 pm
granting GRANT ANY ROLE | article only | Nov 21, 2003 10:37 pm
NUMBER datatype | article only | Nov 21, 2003 10:38 pm
SQL*Loader and multiple logical records from one physical record | article only | Nov 21, 2003 10:41 pm
Why to upgrade to 9i for Data Warehouse application? | article only | Nov 22, 2003 11:12 am
Conversion to String to hexadecimal | article only | Nov 22, 2003 06:59 pm
Web enabled application | article only | Nov 22, 2003 07:02 pm
Updating Partition key in a table | article only | Nov 22, 2003 07:03 pm
RMAN - Full vs. Incremental - performance problem | article only | Nov 23, 2003 08:28 am
Index tablespace size | article only | Nov 23, 2003 09:17 am
Audit / Replication | article only | Nov 24, 2003 07:50 am
MTS Configuration | article only | Nov 24, 2003 08:17 am
will delete reclaim disk space for the database? | article only | Nov 24, 2003 08:20 am
temporary tablespace | article only | Nov 24, 2003 08:21 am
anydata type | article only | Nov 24, 2003 08:30 am
view containing in ,or clause with outer join | article only | Nov 24, 2003 08:55 am
exp imp issue | article only | Nov 24, 2003 08:56 am
How can I get information of success or failure for individual record during bulk update and insert while using Pro*C | article only | Nov 24, 2003 12:47 pm
Web Application Testing | article only | Nov 24, 2003 03:26 pm
mutating table | article only | Nov 24, 2003 06:20 pm
RAID configuration | article only | Nov 24, 2003 06:23 pm
Avoiding overlap values... | article only | Nov 24, 2003 06:56 pm
How can i get column's names in cursor | article only | Nov 24, 2003 07:09 pm
Export/import moving data between different RDBMS versions | article only | Nov 25, 2003 07:38 am
Loop through sql script | article only | Nov 25, 2003 07:44 am
RBS vs Undo Tablespaces | article only | Nov 25, 2003 07:45 am
Oracle datas into Excell Sheet using pl/sql procedure in Oracle 8i | article only | Nov 25, 2003 07:51 am
Problems with Order by in a query | article only | Nov 25, 2003 07:59 am
Flashback Query | article only | Nov 25, 2003 08:10 am
extproc.exe causing memory problems on server | article only | Nov 25, 2003 09:05 am
Undo tablespace keeps growing | article only | Nov 25, 2003 12:35 pm
week | article only | Nov 25, 2003 03:33 pm
What's a freelist? | article only | Nov 25, 2003 03:43 pm
Do I have to change CHAR(1) to CHAR(1 CHAR) | article only | Nov 25, 2003 03:55 pm
Star Schema | article only | Nov 25, 2003 04:24 pm
Need help with the query | article only | Nov 25, 2003 06:39 pm
Base tables of snapshots | article only | Nov 25, 2003 07:23 pm
row(s) 1 - 76 of 76
November 19, 2003 Valar -- Thanks for the question regarding "Partition - Modifying the Range Values - 14400 Error", version 8.1.6
You Asked
Hi Tom,

I have a table with the following partitions,

CREATE TABLE DEMDATA
( DEM_ID INT,
  DLID   char(8),
  DLID_TYPE char(1)
)
PARTITION BY RANGE (DEM_ID)
(PARTITION DEMDATA_01 VALUES LESS THAN (500) TABLESPACE DEM01,
PARTITION DEMDATA_02 VALUES LESS THAN (1000) TABLESPACE DEM02,
PARTITION DEMDATA_03 VALUES LESS THAN (1500) TABLESPACE DEM03
);

Now when I insert values where dem_id greater than 1500, it
complains of the ORA-014400 error,

ORA-14400: inserted partition key is beyond highest legal
partition key

Is there a way to just modify the range attributes , ie. could I
change the last partition demdata_03 as follows,

PARTITION DEMDATA_03 VALUES LESS THAN (3500) TABLESPACE DEM03

As far as I read, alter table modify partition does not allow
the above. How could I achieve this kind of a partition? Is the
only way to split or add an extra partition.

We wanted to increase the upper limits of a particular partition
as there is sufficient amount of free space in the tablespace
DEM03, to hold another 2000 records.

Please do let us know of your suggestions.
Thanks,
Valar
and we said...
We can do the "curly shuffle" here.  We can

o create an empty table just like the partitioned table down to
the indexes.

o swap the empty table with demdat_03 - so the partition becomes
a table, the table an empty partition.

o drop the partition

o add a new partition with a higher upper bound

o swap the empty partition with the full table

No data will actually "move" during this, it is all a big
"rename" trick.  Here is an example (shows they do not move as
well)

tkyte@TKYTE816> CREATE TABLE DEMDATA
  2  ( DEM_ID INT,
  3    DLID      char(8),
  4    DLID_TYPE char(1)
  5  )
  6  PARTITION BY RANGE (DEM_ID)
  7  (PARTITION DEMDATA_01 VALUES LESS THAN (500) TABLESPACE
                                                     tools,
  8  PARTITION DEMDATA_02 VALUES LESS THAN (1000) TABLESPACE
                                                     users,
  9  PARTITION DEMDATA_03 VALUES LESS THAN (1500) TABLESPACE
                                                     data2_en
 10  );

Table created.

tkyte@TKYTE816> insert into demdata values ( 400, 'x', 'x' );
1 row created.

tkyte@TKYTE816> insert into demdata values ( 800, 'x', 'x' );
1 row created.

tkyte@TKYTE816> insert into demdata values ( 1400, 'x', 'x' );
1 row created.

tkyte@TKYTE816> insert into demdata values ( 1500, 'x', 'x' );
insert into demdata values ( 1500, 'x', 'x' )
            *
ERROR at line 1:
ORA-14400: inserted partition key is beyond highest legal
partition key

tkyte@TKYTE816> select segment_name, partition_name,
  2         extent_id, file_id, block_id
  3    from dba_extents
  4   where owner = USER
  5   order by 1, 2
  6  /

SEGMENT PARTITION_  EXTENT_ID    FILE_ID   BLOCK_ID
------- ---------- ---------- ---------- ----------
DEMDATA DEMDATA_01          0          5        138
DEMDATA DEMDATA_02          0          3        818
DEMDATA DEMDATA_03          0         11          2
EMPTY                       0         11          7

tkyte@TKYTE816> create table empty
  2  as
  3  select * from demdata where 1=0
  4  /
Table created.

tkyte@TKYTE816> REM index this table like demdata!

tkyte@TKYTE816> alter table demdata
  2  exchange partition demdata_03
  3  with table empty
  4  including indexes
  5  without validation
  6  /
Table altered.

tkyte@TKYTE816> alter table demdata
  2  drop partition demdata_03
  3  /
Table altered.

tkyte@TKYTE816> alter table demdata
  2  add partition demdata_03
  3  values less than (3500)
  4  tablespace data2_en
  5  /
Table altered.

tkyte@TKYTE816> alter table demdata
  2  exchange partition demdata_03
  3  with table empty
  4  including indexes
  5  without validation
  6  /
Table altered.

tkyte@TKYTE816> select segment_name, partition_name,
  2         extent_id, file_id, block_id
  3    from dba_extents
  4   where owner = USER
  5   order by 1, 2
  6  /

SEGMENT PARTITION_  EXTENT_ID    FILE_ID   BLOCK_ID
------- ---------- ---------- ---------- ----------
DEMDATA DEMDATA_01          0          5        138
DEMDATA DEMDATA_02          0          3        818
DEMDATA DEMDATA_03          0         11          2
EMPTY                       0         11          7

tkyte@TKYTE816>
tkyte@TKYTE816> insert into demdata values ( 1500, 'x', 'x' );

1 row created.

tkyte@TKYTE816>
Review & Followup
Rating: 4
Partitions Missing problem November 11, 2003
Reviewer: MV from india
Hi Tom
    I have a range partitioned table based on processing date.Normally this
table must have last 10 days(actually in our project, 90 days) data. So Totally
we have 11 partitions including one max partition.Everyday our procedure will
drop the lowest partition and it will add one new partition for sysdate. This
Lowest partition date, higher partition date, and the period i.e., 10 days,
these 3 values are stored in another table.

For example today is 11/11/2003, then this table should have partitions for
02/11/2003 to 11/11/2003 (10 partitions) and one max partition. Due to some
problem(we don't know exactly whether it is a logical or data problem), it is
created partitions for future date. for example today we have checked it, it
contains the partitions in the following order :

    02/11/2003
    03/11/2003
    06/11/2003
    07/11/2003
    08/11/2003
    10/11/2003
    11/11/2003
    12/11/2003
    13/11/2003
    14/11/2003
    max partition
        04th, 05th and 09th november partitions are missing and also it is
created for future dates. My question is, everyday we are dropping one lowest
level partition and creating new one. when i drop this 06/11/2003 partition, i
will lose the data for 3 days,but it shouldn't, because my client wants exactly
last 10 days data. I should keep my table in the following order :

    02/11/2003
    03/11/2003
    04/11/2003
    05/11/2003
    06/11/2003
    07/11/2003
    08/11/2003
    09/11/2003
    10/11/2003
    11/11/2003
    max partition

Can you suggest some alternatives?

Followup:
umm, you have a bug in your code that finds the "lowest" partition -- you need
to find the bug, fix it and problem goes away.  There is nothing builtin to
oracle that says "get rids of the lowest", so it has to be in your logic.

could easily be the way you seem to deal with dates using dd/mm/yyyy, if you use
that date format mask and are comparing STRINGS, not DATES, it could get all
messed up easily.

Rating: 4
can i increase the partition key width? November 17, 2003
Reviewer: Subu from India
Tom
    I have a table with 4 million records. table structure is
create table cheques
( chq_folio_no varchar2(13),
  chq_cheque_no varchar2(19),
  ...
  ...
  ...)
  partition by range(chq_folio_no)
  (partition part1 value less than ...
    ..
    ..
    );
  This table contains around 4 millions records. Now i wants to increase the
width of partition key "chq_folio_no" into varchar2(16). I tried to alter the
table, but it is giving error as "cannot alter the partition key".

can u suggest me some ideas to increase the width of this column. If we can't
increase in the existing table, then how to do?

Followup:
if you are using oracle9i, you can use the online redefinition package..

or you can

a) export the table
b) drop and recreate just the table with the new definition
c) import it

or you can

a) prevent dml from happening on this table somehow
b) create table a new with the modified definition
c) insert /*+ append */ into it, index it, etc
d) drop old
e) rename new to old

or you can use steps like this (you'd have to create indexes on t1/t2 to match
your local indexes and remember to add global indexes and grants to new before
using it)

ops$tkyte@ORA920LAP> CREATE TABLE t
  2  (
  3    pkey       varchar2(5),
  4    data       number
  5  )
  6  PARTITION BY RANGE (pkey) (
  7    PARTITION part1 VALUES LESS THAN ('M'),
  8    PARTITION part2 VALUES LESS THAN (MAXVALUE)
  9  )
 10  ;

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> insert into t values( 'A', 5 );

1 row created.

ops$tkyte@ORA920LAP> insert into t values( 'Z', 10 );

1 row created.

ops$tkyte@ORA920LAP> alter table t modify pkey varchar2(10)
  2  /
alter table t modify pkey varchar2(10)
                     *
ERROR at line 1:
ORA-14060: data type or length of a table partitioning column may not
be changed

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create table new_table
  2  (
  3  pkey       varchar2(15),
  4  data       number
  5  )
  6  PARTITION BY RANGE (pkey) (
  7  PARTITION part1 VALUES LESS THAN ('M'),
  8  PARTITION part2 VALUES LESS THAN (MAXVALUE)
  9  )
 10  ;

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create table t1 ( pkey varchar2(5), data number );

Table created.

ops$tkyte@ORA920LAP> create table t2 ( pkey varchar2(5), data number );

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> alter table t exchange partition part1 with table t1;

Table altered.

ops$tkyte@ORA920LAP> alter table t exchange partition part2 with table t2;

Table altered.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> alter table t1 modify pkey varchar2(15);

Table altered.

ops$tkyte@ORA920LAP> alter table t2 modify pkey varchar2(15);

Table altered.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> alter table new_table exchange partition part1 with table
t1 without validation;

Table altered.

ops$tkyte@ORA920LAP> alter table new_table exchange partition part2 with table
t2 without validation;

Table altered.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> drop table t;

Table dropped.

ops$tkyte@ORA920LAP> rename new_table to t;

Table renamed.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select * from t;

PKEY                  DATA
--------------- ----------
A                        5
Z                       10

ops$tkyte@ORA920LAP> desc t;
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 PKEY                                         VARCHAR2(15)
 DATA                                         NUMBER

ops$tkyte@ORA920LAP>

November 19, 2003 Andre, from Brazil -- Thanks for the question regarding "Refresh complete and rollback", version 816
You Asked
Hi.
I know that in a refresh complete the snap table is truncated. Is there any way
to do a refresh complete and use rollback having the data available during all
refresh time(like refresh fast) ? For example :

exec dbms_snapshot.refresh('XXX','C','MYRBSEG') -> this do that ?

Tks.
and we said...
Here is a support note on this topic that offers a solution:

Article-ID:         <Note:2077254.6>
Circulation:        PUBLISHED (EXTERNAL)
Folder:             server.Distributed.Snapshots
Topic:              Miscellaneous
Title:              WORKAROUND THE TRUNCATE BY CREATING A REFRESH GROUP
Document-Type:      SOLUTION
Impact:             LOW
Skill-Level:        CASUAL
Server-Version:     07.XX to 08.XX
Updated-Date:       12-SEP-2000 04:16:08
References:
Shared-Refs:
     <Problem:1029824.6> AVOIDING A TRUNCATE DURING COMPLETE SNAPSHOT REFRESH
Attachments:        NONE
Content-Type:       TEXT/PLAIN
Keywords:           SNAPSHOTINFO;
Products:           5/RDBMS (07.XX);
Platforms:          GENERIC;

Solution Description
====================
Complete refreshes of a single snapshot uses truncate for performance
reasons and also to reduce rollback segment requirements.  The truncate
does an auto-commit so if your complete refresh fails AFTER the truncate
of the base table, SNAP$_<snapshotname>, you cannot rollback or recover.

A workaround is to create a refresh group.

Solution Explanation
====================
A refresh group of multiple snapshots will use
"delete from SNAP$_<snapshotname>" instead of
"truncate SNAP$_<snapshotname>".  Unlike a single snapshot
refresh, we have to be able to rollback and recover the entire
refresh of a refresh group if any snapshot in the group fails to refresh.

Therefore, the above can be avoided if you create a refresh of a
snapshot group, grouping your single snapshot with a "dummy" snapshot
of dual.

ex:

Create snapshot snap_test
refresh complete
as select * from test@db_link;

create snapshot snap_dual
refresh complete
as select * from dual@db_link;

execute dbms_refresh.make(
name             =>'group1',
list             =>'snap_test,snap_dual',
next_date    =>sysdate,
interval         =>'sysdate+1/24',
implicit_destroy =>true);

This example creates 2 snapshots, SNAP_TEST, SNAP_DUAL
and a snapshot group, GROUP1.  The snapshot group
contains the 2 snapshots, SNAP_TEST, SNAP_DUAL that will
be refreshed complete.

If we turn sql_trace on during a single snapshot refresh,
we would see: truncate table "SCOTT"."SNAP$_SNAP_TEST"

During a consistent refresh of a snapshot group, we would see:
delete from "SCOTT"."SNAP$_SNAP_TEST" where m_row$$ = :1
and also a
delete from "SCOTT"."SNAP$_SNAP_DUAL

NOTE:  Because we are now doing a delete on the SNAP$_<snapshotname> instedad of
a truncate, you will have to increase your rollback
segment requirements.
Review & Followup
Rating: 3
problem with refresh group January 21, 2002
Reviewer: Tzvika Foyer from Israel
encounter problem during the refresh process DML like update ( auto by loader )
work on the same object and lock created .
if the refresh group just delete and insert why lock created

Rating: 5
One More Question September 10, 2002
Reviewer: Bharath from NYC
How to refresh the MV group manually whcih does the delete then insert

Followup:
dbms_refresh.refresh( 'GROUP_NAME' )

after making the snapshot group.

Rating: 5
a table with a mix -- remote data as well as application data November 04, 2002
Reviewer: sm from usa
In our application we want to use say a table from another database...so we can
create a snapshot say sn_emp.
sn_emp has information about employee.
Now we think of couple of columns that we want to add to this employee
table..these columns will be updateable through
our application.
So we are envisioning a table that is going to have some columns from a remote
database (they will need to be refreshed)
and some columns that are updateable on our side through application.
What is the best way to have this kind of mix.
The snapshot based side of this table has to be refreshed ever 2 hours...

My solution:
Create the snapshot..sn_emp.
create a table tab_emp that has the same primary key as of the snapshot (emplid)
as it's primary key.
add the columns that are updateable to this new table.
Create a trigger on the snapshot that will keep sn_emp and tab_emp in sync.

Before reading this article, I was also worried about having refreshes every two
hours...
I was thinking the application will go unstable during the time snapshot is
being refreshed...
I guess the solution (having snap_dual) will work....

Followup:
You will have two tables

a) snapshot -- on the remote table.  say the primary key is "EMPNO"
b) your auxillary table, primary key is "EMPNO" other attributes are whatever
you want.

do NOT recreate a trigger on the snapshot -- you have no way of knowing HOW the
snapshot is to be refreshed.  An update to the snapshot could well be "delete",
"insert" -- you would lose the data in your table.

You will have to "live" with the fact that your table might have EMPNO's that
don't exist in the snapshot.  You can either

a) ignore their existence
b) clean them out from time to time

their existence (these orphans) is not really material since you'll always join
snapshot to this table and they won't appear anyway.

Rating: 5
why not trigger November 05, 2002
Reviewer: sm from usa
Why not a trigger?
For example..say there is a snapshot table snap_emp
with columns emplid, lastname, firstname
and I create a table on my side mine_emp with columns
emplid, lastname, firstname, salary

trigger would be on snap_emp table
as snapshot rereshes (complete)..
trigger will check if the emplid exists in mine_emp table..
if it exists..it will see if lastname, firstname are the same..if not it will
update them..
if emplid does not exist then it will add that emplid to mine_emp table...
salary column won't be touched..because it is being updated by the user in the
application.
Doing this..I don't think..we will loose data in mine_emp table....

Followup:
why would you carry the firstname/lastname over to the other table?  that seems
wasteful...

If you wanted all new rows in the snapshot to be added to this other table
(again, not necessary but if that is what you want) I would:

a) refresh the snapshot manually by using DBMS_JOBS and scheduling the
dbms_mview.refresh myself

b) in that same job I would add:

   insert into your_table
   select ....
     from snapshot_table
    where emplid not in ( select emplid from your_table );

(search this site for HASH_AJ to see how to make that "fast")

and that is it.  that'll be faster, easier, cheaper then a trigger

Rating: 5
why a trigger? November 06, 2002
Reviewer: sm from usa
About lastname, firstname columns...they don't have to be
there...actually...only a table can pass the foriegn keys to other tables...but
I guess..that table can have just the emplid...and salary (emplid as PK)..and
pass emplid as foreign key..to other tables...

About the manual refreshes...there is a condition..that the snapshot has to be
refreshed every four hours...automatically...

"b) in that same job I would add:

   insert into your_table
   select ....
     from snapshot_table
    where emplid not in ( select emplid from your_table );"

I need to read up on this..I don't know how to do this in one single job that
refreshes the snapshot (forced)..and add the new emplids to the local table...it
will be ideal though..because forced will be much quicker (considering 4 hour
refreshes)

"If you wanted all new rows in the snapshot to be added to this other table
(again, not necessary but if that is what you want)"

we do want to keep them in sync...because the local table would pass foriegn
keys..to other tables...for extra ones in the local table...there can be a daily
job that will do the cleanup..

Followup:
dbms_job.submit( l_job,
                 'begin dbms_mivew.refrehsh( ''YOUR_SNAPSHOT'' );
                        insert into ......;
                        commit;
                  end;',
                 .......);

Rating: 5
why a trigger? contd... November 06, 2002
Reviewer: sm from usa
I was just thinking...why I added lastname, firstname...
to local table...
They were there because snapshot is refreshed every four hours..so I wanted a
way to keep all the columns still alive...while the snapshot is being
refreshed..
Also..there is a weired business rule..that some column..say userid...will be
populated first time (and consecutive new ones that comes with the new
employees) through the snapshot...and then it will be maintained through the
application....so if the admin quickly wants to change it he/she can change it
through the app..and then send the email to the remote database..to update the
userid there...I am fighting with that BR...but that's how they want to do
it....

So..
scenerios:
1. to have data available while snapshot is being refreshed
2. local table needs to pass the foreign keys....so have a local table...and
keep it in sync with the snapshot for new data
3. local table can be a mix of columns that only it has...
   i.e that columns that only app will maintain...
   and coulmns...that app and remote database both can   maintain...

Followup:
if you use the snap_dual I was talking about.... not a problem -- refresh will
be delete + insert, not truncate + insert.

1) handled by this original question, use a snapshot group
2) insert as shown above
3) be that as it may, you'll have to do what you have to do.

Rating: 5
are snapshots bad? November 07, 2002
Reviewer: sm from usa
(8.1.7.4)
Thanks Tom...
1.Could there have been another way to do accomplish the   above besides using
snapshots?
For Example we can have views based on remote database...
have procedures populating (from views) some tables at different times..during
the day..
2. Are snapshots bad?
I have read/heard/been told that snapshots are a lot of overhead.
One advantage that I do see with the snapshots is that local database can have a
static copy of the data at all times...while a view is always connected to the
remote database...so a view based on remote database might not be
available at all times...unlike snapshots..we can schedule or break the job
around the maintenance schedule of the remote database....

Please comment...

Thanks again

Followup:
1) snapshots = code is written for you -- just run a command

anything else = you write the code, you maintain the code, you debug the code,
you upgrade the code

me, I like having code written for me.  I try to use the database features
when/where I can.

2) no.

I heard that business travelers better not goto bars cause there are people that
will drug you and take out your kidneys to sell on the black market.
http://www.snopes.com/horrors/robbery/kidney.htm
it carries about about as much wait as "snapshots are bad, lots of overhead"

Rating: 5
SNAP_DUAL December 06, 2002
Reviewer: K from india
(8i)
Can we use snap_dual with more than one snapshot..or for every snapshot we have
to create a dummy snapshot to accomplish this..

Thanks.

Followup:
i would create a separate one - for concurrency reasons if nothing else.  to
make it so that more then one snapshot group at a time can refresh easily at the
same time.

Rating: 5
two jobs? December 09, 2002
Reviewer: George from Australia
hmmmmm...

"execute dbms_refresh.make(
name             =>'group1',
list             =>'snap_test,snap_dual',
next_date    =>sysdate,
interval         =>'sysdate+1/24',
implicit_destroy =>true);

dbms_job.submit( l_job,
                 'begin dbms_mivew.refrehsh( ''YOUR_SNAPSHOT'' );
                        insert into ......;
                        commit;
                  end;',
                 .......);"

Interval and next date parameters can be entered for both
dbms_refresh.make as well as dbms_job.submit.
to make the snapshot refresh every two hours or what ever time
we have to enter same next_date and interval in both of them
right?
I do not understand "dbms_mivew.refrehsh( ''YOUR_SNAPSHOT'' );"
Shouldn't that be dbms_refresh.refresh (group1).
I guess I am trying to understand how is this going to be done in one job.
Should the group1 be created without the next date and interval.

Followup:
I'm not sure I understand.

You are mixing two approaches here.

a) you can use dbms_refresh.make to make a group that refreshes all by itself
(no dbms_job.submit)

b) use dbms_job.submit to schedule the refresh to take place along with some
other stuff you want to have happen at the same time.

The are mutually exclusive.  I said  "refresh the snapshot manually by using
DBMS_JOBS and scheduling the dbms_mview.refresh myself".   dbms_job is in place
of dbms_refresh.make.

I was answering a related but subtly different question -- the person wanted to
do something in addition to refreshing a snapshot, that is what the dbms_job is
doing

Rating: 5
get both of them December 09, 2002
Reviewer: george from australia
I guess..I am trying to see how what you suggested hit both the issues in one
shot:
1. Delete+Insert instead of truncate+insert for snapshot A
2. Insert into another table while refreshing the snapshot A

For (1) we can make a group (DBMS_REFRESH.MAKE)...for (2) we have to submit a
job (DBMS_JOB.SUBMIT).....
If we just do submit a job..because with refreshing the snapshot and insert
steps in it...then that would not take care of the delete+insert...so that is
why I was wondering if we need to do dbms_refresh.refresh (group1) inside the
job that we are submitting...

Followup:
it didn't - it (the dbms_job suggestion) solved a specific issue for that
specific person.  They were doing something totally different.

Rating: 5
Problem in Snapshot Refresh February 03, 2003
Reviewer: Riaz Shahid from PRAL, Islamabad, Pakistan
Dear Tom !

I am facing problems regarding the snapshot refresh. I've created a snapshot of
master site's table and also created the snapshot log at master site. The
refresh time is after each hour.

Now the table on master site was updated but the snapshot couldn't refreshed
because the machine was off. We made the machine online (i.e; on the network)
but ooooooops.....the snapshot didn't refreshed (on its scheduled time) although
the rows exist in the snapshot log at master site. We manuualy refreshed the
snapshot.

My question is : Why the snapshot is not refreshed if the snapshot site is off
one time. Shouldn't it refresh the snapshot at next scheduled time ? And what is
the best solution for that?

Regards

Riaz

Followup:
the snapshot would try 16 times to refresh, if after 16 failures -- it could
not, it sets itself to be "broken"

next time, query dba_jobs and look for "broken" jobs, jobs that have failed 16
times.  you can use dbms_job to "fix" them and have them run again.

It is to prevent infinite retries of a job that will never succeed.

Rating: 5
snapshot November 16, 2003
Reviewer: A reader
Tom,

create snapshot snap_dual
refresh complete
as select * from dual@db_link;

should we have to create a primary key and snap shot log on the dual table? for
this concept to work?

Thanks,

Followup:
NO -- don't create a primary key or snapshot log on dual!!!

a refresh complete would never use a snapshot log, nor does it need a primary
key.

Rating: 3
November 17, 2003
Reviewer: Sree
Tom,

When we use the above

create snapshot snap_dual
refresh complete
as select * from dual@db_link;

Oracle complains about the missing primary key? How should we go about that?

Thanks,

Followup:
ops$tkyte@ORA920LAP> create snapshot snap_dual refresh complete as select * from
dual@ora920.us.oracle.com;

Materialized view created.

or

ops$tkyte@ORA817DEV> create snapshot snap_dual refresh complete WITH ROWID
  2  as
  3  select * from dual@ora817dev.us.oracle.com@loopback;

Materialized view created.

Rating: 3
version 8.1.7.4 November 17, 2003
Reviewer: A reader
  1  create snapshot snap_dual refresh complete as select * from
  2* dual@temp
SQL> /
create snapshot snap_dual refresh complete as select * from
                                                     *
ERROR at line 1:
ORA-12014: table 'DUAL' does not contain a primary key constraint

 1  create snapshot snap_dual refresh complete WITH ROWID
  2      as
  3*     select * from dual@temp
SQL> /

Materialized view created.

Does that mean in 8.1.7 i have to create with rowid?

Thanks

Followup:
yup

November 19, 2003 debabrata -- Thanks for the question regarding "UTL_FILE pcakage(flat file creaion)", version oracle 7.3
You Asked
declare
das UTL_FILE.FILE_TYPE ;
begin
das := UTL_FILE.FOPEN('/usr/d2k/debal','das.txt','r');
UTL_FILE.FCLOSE(das);
exception
when UTL_FILE.INVALID_PATH then
   dbms_output.put_line('invalid path');
when UTL_FILE.INVALID_MODE then
   dbms_output.put_line('invalid_mode');
when UTL_FILE.INVALID_OPERATION then
   dbms_output.put_line('invalid operation');
end;

I am trying to create flat using UTL_FILE package.OS is UNIX SCO
5,datbase is ORACLE 7.3 ,application tool is DEV 2000.
The above code is always raising UTL_FILE.INVALID_OPERATION
exception.
I don't know the reason. Because in initEXAM.ora,I have included
a line
UTL_FILE_DIR=*.But the problem has not been resolved.What is the
problem Mr. TOM?
and we said...
The file /usr/d2k/debal/das.txt either

o does not exist

o is not readable by the uid that Oracle is running under
(utl_file operates with the privs of the Oracle process).

putting * in utl_file_dir is an extremely bad idea.  Nothing
stopping someone from

   utl_file.fopen( '/d01/oradata/dbs/', 'SYSTEM01.DBF', 'w' );

hence erasing your system datafile.  Please consider listing
just the directories you need for your own safety!
Review & Followup
Rating: 3
But why I am getting this November 17, 2003
Reviewer: Pawan from USA
Tom,
Am really confused. Can you please help me. Here is my problem:
------
Using UTL_FILE to create 50 files in a directory /usr/cnet/scripts. Was able to
run the script successfully about a few weeks back. It started failing with

SQL> @test1
DECLARE
*
ERROR at line 1:
ORA-20003: INVALID_OPERATION: The file could not be opened or operated on as
requested.
ORA-06512: at line 313

Found that the permissions of the /usr/cnet/scripts directory had been changed
from 777 to 775. The job was running from scheduler with “owadm” id. Since
“oracle” was not part of the same group (dba/ops/users) it was not able to write
to the directory. But I added “oracle” to ofwr group. The DBA bounced the
database after the change.

PRIOR
(dvlcmdm1)/usr/cnet/scripts> groups owadm
ofwr
(dvlcmdm1)/usr/cnet/scripts> groups oracle
dba ops users

TODAY
(dvlcmdm1)/usr/cnet/scripts> groups owadm
ofwr
(dvlcmdm1)/usr/cnet/scripts> groups oracle
dba ofwr ops users

But it again fails – and gives the same error.

SQL> @test1
DECLARE
*
ERROR at line 1:
ORA-20003: INVALID_OPERATION: The file could not be opened or operated on as
requested.
ORA-06512: at line 313

Line 313 is

when utl_file.invalid_mode then
raise_application_error(-20002, 'INVALID_MODE: open_mode in FOPEN was
invalid.');

I have no comments or blank lines in my code.
The code is pretty long and I don’t want to paste it here. Some lines are like

l_cust:=cust_rec.cust_corp_id||'_Q.XLS';
l_file := UTL_FILE.FOPEN('/usr/cnet/scripts',l_cust,'w',32767);
UTL_FILE.PUT_LINE(l_file,'Customer Scorecard '||forecast_rec.c2||'|');
UTL_FILE.PUT_LINE(l_file,header_rec.h5||'|');
UTL_FILE.PUT_LINE(l_file,'VOLUME (Quota and Forecast)');
UTL_FILE.PUT_LINE(l_file,'|'||header_rec.h2);

Exception section is copied from one of your posts

exception
when utl_file.invalid_path then
raise_application_error(-20001, 'INVALID_PATH: File loc or name was invalid.');
when utl_file.invalid_mode then
raise_application_error(-20002, 'INVALID_MODE: open_mode in FOPEN was
invalid.');
when utl_file.invalid_filehandle then
raise_application_error(-20002, 'INVALID_FILEHANDLE: The file handle was
invalid.');
when utl_file.invalid_operation then
raise_application_error(-20003, 'INVALID_OPERATION: The file could not be opened
or operated on as requested.');
when utl_file.read_error then
raise_application_error(-20004, 'READ_ERROR: An OS error occurred during the
read operation.');
when utl_file.write_error then
raise_application_error(-20005, 'WRITE_ERROR: An OS error occurred during the
write operation.');
when utl_file.internal_error then
raise_application_error(-20006,'INTERNAL_ERROR: An unspecified error in
PL/SQL.');

The best part is that I ran this code earlier and it was working.

Thanks in advance.

Followup:
as the oracle software account (log into the os) can you "touch" files there -
can you create files there.

what does an ls -l show on that directory (use the ls variant that'll show us
perms and groups on your system)

Rating: 3
This is what I get November 19, 2003
Reviewer: Pawan from USA
Thanks Tom,
I don't have the Oracle account. But here is what I get when I do a ls -ltr.

-rw-rw-rw-    1 owadm    ofwr    12489 Nov 19 04:02 broker_lkup.txt
-rw-rw-rw-    1 owadm    ofwr    12489 Nov 19 04:02 broker_lkup.sort
-rw-rw-rw-    1 owadm    ofwr    4723 Nov 19 04:17 cube_ftp.txt
-rwxrwxr-x    1 operan    ops    3354 Nov 19 16:31 custscore_ftp.sh
-rwxrwxr-x    1 operan    ops    4460 Nov 19 16:31 custscore_rpt.sh

Rating: 4
Thanks November 21, 2003
Reviewer: Pawan from USA
It was a permissions issue only. Thanks for the help.

November 21, 2003 Scott -- Thanks for the question regarding "Extracting text from long column using substr and instr", version 8.1.6
You Asked
I am trying to extract data from a long datatype column that is being used for
multiple purposes.   I am trying to use the following SQL statement to extract
data in the string held between the following placeholders - <SHORT_START> and
<SHOR_END> -

select
substr(t.text_data,instr(t.text_data,'<SHORT_START>'),instr(t.text_data,'<SHORT_E
ND>'))
FROM text_storage t, product p
where p.product_code = 'ADJ' and
t.text_number = p.tech_notes_number

I know that the statement isn't yet complete, but I can't get beyond this stage
as I keep getting a ORA-00932 error at the first t.text_data portion of the
substr function.   The text_data column is set as LONG.
and we said...
You cannot perform functions on LONGS.

In Oracle8.0 and up, you should be using CLOBS instead, then you could:

select ...
 dbms_lob.substr( clob_col, ..dbms_lob.instr(clob_col,...) .. )

If the long is ALWAYS 32k or less, you can use the method outlined in:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:665224430110
whereby you create a procedure that takes a ROWID in, fetches the long out into
a 32k variable, parses it and returns upto 4000 characters of it to SQL.

That also shows you how to use DBMS_SQL to access the long piecewise as well if
need be.
Review & Followup
Rating: 5
March 27, 2001
Reviewer: A reader

Rating: 5
What if the LONG datatype is a view? February 21, 2003
Reviewer: A Reader from USA
Tom,
What if we cannot convert the datatype and still want to use function like
substr?  Example user_views:
select view_name, substr(text,1, 10)
  from user_views;

We want to get the first 10 characters in the long field.  We may copy the
contents of the base table into another table and convert that table's datatype.
Is there any esasier ways?

Thank you very much.

Followup:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:665224430110

      Rating: 4
Why Oracle
      don't use CLOB but use LONG in it's dictionary
      tables?  February 22, 2003
Reviewer:  A
      Reader  from USA
Just want to know the reason.  Thanks.

          Followup:

legacy reasons.

when you make a change, you affect yourself.

when we make a change, we affect -- well, more then just ourselves.  Changing
the datatypes in the dictionary will be a rather large change for us.  Backwards
compatability is a hard thing to achieve.  The smallest changes can have large
ripples.

      Rating: 3
How to use a
      Clob datatype as a variable  November 15, 2003

Reviewer:  Balaji  from Chennai India
Hi
I am forming a  dynamic sql in a proc.
the length of this sql goes more than 32 k.
so how am i to declare a clob variable and assign the query to it??

i also need to return the clob as output.

          Followup:

well, you would use dbms_lob.createtemporary and dbms_lob.writeappend to write
to it, BUT you will not be using native dynamic sql on it, you'll have to use
dbms_sql which allows you to call parse with a plsql table of strings -- each
string can be 255 characters but the strings are glued together to create an
entire sql statement.

      Rating: 3
Long into
      Varchar2  November 17, 2003
Reviewer:  Dan
      Clamage  from Go Steelers!
If you're doing this one row at a time, AND the Long value is under 32K, you can
fetch the Long into a PL/SQL VARCHAR2 and then use the standard string
functions.

          Followup:

(that was part of the original answer!)

November 21, 2003 M -- Thanks for the question regarding "Problem
      to connect as SYSDBA", version 8.1.7

      You Asked
Hi there,

I am running Oracle 8.1.7 EE on SUN Solaris. I created two databases
SUGAR and TestDb (I need one to do some testing). Both are set
remote_login_passwordfile = exclusive. The following shows how I had trouble to
connect as SYSDBA. The big problem is that I cannot connect to TestDb as SYSDBA.

$sqlplus /nolog
SQL> conn sys/change_on_install@testdb
connected
SQL> select * from v$pwfile_users;
username       sysdb    sysop
------------------------------
internal       true      true
sys            true      true
SQL> conn sys/change_on_install@testdb as sysdba
ERROR
ORA-01017: invalid username/password; logon denied
Warning: You are on longer connected to ORACLE
SQL> conn sys/change_on_install@SUGAR
connected
SQL> select * from v$pwfile_users;
username       sysdb    sysop
------------------------------
internal       true      true
sys            true      true
SQL> conn sys/change_on_install@SUGAR as sysdba
ERROR
ORA-01017: invalid username/password; logon denied
Warning: You are on longer connected to ORACLE
SQL> conn sys/change_on_install as sysdba
connected
SQL> select name from v$database;
name
------
SUGAR

Questions:
1. Why "conn sys/change_on_install@testdb/sugar as sysdba" always failed, but
"conn sys/change_on_install as sysdba" OK ?

2. Why I cannot connect to testdb as SYSDBA?

Thanks for any help.

      and we said...
what was the password you used when you created the password file and have you
tried that?

the sysdba trick is generally used to allow other NON-SYS users to connect btw,
best to use your OWN account that has been granted sysdba!

Anyway, hopeful this will clear up the confusion and show how this works:

$ orapwd file=orapw password=foobar entries=40

I just recreated my password file with a password foobar.  My sys password is
NOT foobar

$ svrmgrl

SVRMGR> connect internal
Connected.

SVRMGR> startup
ORACLE instance started.
Total System Global Area                        193073136 bytes
Fixed Size                                          69616 bytes
Variable Size                                   141639680 bytes
Database Buffers                                 45056000 bytes
Redo Buffers                                      6307840 bytes
Database mounted.
Database opened.

SVRMGR> connect sys/change_on_install@ora816dev
Connected.

that works OK, lets try as sysdba:

SVRMGR> connect sys/change_on_install@ora816dev as sysdba;
ORA-01017: invalid username/password; logon denied

there I got your error.  My SYS password is change_on_install but the password
file has foobar in it.  SYS is special -- sys connecting as sysdba is like
internal -- you have to use the password file password!  Lets try that:

SVRMGR> connect sys/foobar@ora816dev as sysdba;
Connected.

SVRMGR> grant sysdba to scott;
Statement processed.

now, we'll see this does not hold true for other users:

SVRMGR> connect scott/tiger@ora816dev as sysdba;
Connected.

That worked great, now lets just modify sys's password (not really, didn't
change it but Oracle won't notice that

SVRMGR> alter user sys identified by change_on_install;
Statement processed.

SVRMGR> connect sys/change_on_install@ora816dev as sysdba;
Connected.
SVRMGR>

Hey, now we can use change_on_install -- that is because altering SYS's
password will sync up the password in the password file -- sys is a very very
close cousin of INTERNAL.  sys's password will be burned into the password file
now.  the password foobar is obsolete

SVRMGR> connect sys/foobar@ora816dev as sysdba;
ORA-01017: invalid username/password; logon denied
SVRMGR>

Review &
      Followup

      Rating:
      5
  May 19, 2001
Reviewer:  Pauline
       from U.S.A.

      Rating:
      3
  May 21, 2001
Reviewer:  Tushar
      Abedin  from Delhi, India

      Rating: 5
Great ins &
      Outs  May 21, 2001
Reviewer:  Mark Kirk
       from Tallinn, Estonia
Thansk for going through this from several aspects. Most discussions of the
sysdba question elsewhere are too brief and directive without being explanatory.

Here you've given theory, script, explanation and added examples of how someone
might get twisted up.

Top Flight!

      Rating: 5
Very
      Useful  May 21, 2001
Reviewer:  Bj
       from Hayward

      Rating:
      5
Password  May 22, 2001
Reviewer:
      Anil  from India
Very good explanation

      Rating:
      5
  May 22, 2001
Reviewer:  Anil
       from India

      Rating:
      5
  July 27, 2001
Reviewer:  Sofie Ye
       from Canada

      Rating: 4
Alter user will
      *not* sync up password in file?  August 06, 2001

Reviewer:  Randy  from Austin, TX
"...-- that is because altering SYS's
password will not sync up the password in the password file..."  Did you mean to
say it *will* sync up the password file?

      Rating: 4
But a bit
      confusing ...at the end ..  October 07, 2001

Reviewer:  Jay  from India
start quote //.."-- that is because altering SYS's
password will not sync up the password in the password file "

sys's password will be burned into the password
file now. ....//end quote

A bit of contradiction ...Mr.Tom ??

          Followup:

funny how the addition of a SINGLE word can change the entire meaning ;)

Obviously it was "... altering SYS's password will sync up ..." -- thats what I
was demonstrating.  I corrected the text, thanks for pointing that out.

      Rating: 4
Useful
      Info  October 09, 2001
Reviewer:  Yogesh
      Bhandarkar  from PUNE, INDIA
The information  is very useful.

      Rating:
      4
differences?  October 10, 2001
Reviewer:
      Benny  from Irvine
You mentioned that sys is a very close cousin of internal,
what are the differences?

          Followup:

some permissions, like the ability to shutdown, startup.

      Rating: 5
Problem to
      connect as SYSDBA  October 11, 2001
Reviewer:
      Johnson  from India
The discussion was good.  It would be of greater use, if you can tell us more
about the role sysdba.

Thanks in advance.

          Followup:

see

http://technet.oracle.com/doc/oracle8i_816/server.816/a76965/c25acces.htm#6126
for details

      Rating: 5
Thanks
      alot!  February 01, 2002
Reviewer:
      Thunderbird  from Russia
Your answer was very usefull for me. Thus I solve my trouble.

      Rating:
      5
  August 20, 2002
Reviewer:  Junaid
      Azam  from CA, USA

      Rating: 3
password
      forgotten  April 23, 2003
Reviewer:  reader

tom,

i dont have the password for sys,system. how to recreate the password or to know
the password.

please help

Thanks

          Followup:

have the person who is able to log in

"internal"

"as sysdba"

log in and alter these users.  those accounts use OS authentication to id the
user and do not require a password.

or, if you have access to any dba account, use that and just alter the users.

      Rating: 4
Problem to
      connect as SYSDBA  April 23, 2003
Reviewer:
      reader
tom,
there is no other user with dba privileges. its running on Win2000(9iR2).

to connect as "internal" i need 'svrmgrl' - i dont have that in 9iR2 - right?

for "as sysdba" - i dont have any dba user.

Sorry for my ignorance. please help me

Thanks

          Followup:

as sysdba -- you don't need a DBA user, you just need an OS account (eg: try the
account that installed oracle)

then

sqlplus "/ as sysdba"

      Rating: 3
trying to
      change (lost) sys password  June 30, 2003

Reviewer:  A reader  from Virginia, USA

This is a cut and paste of what I have tried.  Oracle 9.2.0 on Linux

[oracle@db admin]$ whoami
oracle
[oracle@db admin]$ pwd
/opt/oracle/product/9.2.0/network/admin
[oracle@db admin]$ cat sqlnet.ora

NAMES.DEFAULT_DOMAIN = [blah]
NAMES.DIRECTORY_PATH= (TNSNAMES)
SQLNET.AUTHENTICATION_SERVICES=(NTS)

[oracle@db admin]$ sqlplus / as sysdba
Usage: SQLPLUS [ [<option>] [<logon>] [<start>] ]
where <option> ::= -H | -V | [ [-L] [-M <o>] [-R <n>] [-S] ]
     <logon>  ::= <username>[/<password>][@<connect_string>] | / | /NOLOG
     <start>  ::= @<URI>|<filename>[.<ext>] [<parameter> ...]
       "-H" displays the SQL*Plus version banner and usage syntax
       "-V" displays the SQL*Plus version banner
       "-L" attempts log on just once
       "-M <o>" uses HTML markup options <o>
       "-R <n>" uses restricted mode <n>
       "-S" uses silent mode

[oracle@db admin]$ sqlplus /nolog

SQL> connect / as sysdba
ERROR:
ORA-01031: insufficient privileges

SQL> connect internal as sysdba
Enter password:
ERROR:
ORA-09275: CONNECT INTERNAL is not a valid DBA connection

SQL>

Can you point me in the right direction.

          Followup:

lose the windoze

SQLNET.AUTHENTICATION_SERVICES=(NTS)

in the sqlnet.ora

      Rating:
      4
Great!  June 30, 2003
Reviewer:  Moiz
      Arviwala  from India
Tom , This Kind of anwer I want too....Are u a magician!!!!

Regards,

      Rating: 4
Unable to
      remote connect as sysdba  September 13, 2003

Reviewer:  Deepak Gupta  from Delhi, India

Hi Mr. Tom,

I am trying to connect as remote sysdba from my client PC.  The following error
is occuring. why so ?

SQL> select name,value from V$parameter
  2  where name like 'remote%'
  3  /

NAME                                                             VALUE
----------------------------------------------------------------
------------------
remote_os_authent                                                FALSE
remote_os_roles                                                  FALSE
remote_login_passwordfile                                        EXCLUSIVE
remote_dependencies_mode                                         TIMESTAMP

SQL> select * from v$pwfile_users;

USERNAME                       SYSDB SYSOP
------------------------------ ----- -----
INTERNAL                       TRUE  TRUE
SYS                            TRUE  TRUE
NS                             TRUE  TRUE

SQL> conn sys@orcl
Enter password: *******
Connected.
SQL> conn sys@orcl as sysdba
Enter password: *******
ERROR: ORA-12154: TNS:could not resolve service name

Warning: You are no longer connected to ORACLE.
SQL> conn internal@orcl
Enter password: ******
Connected.
SQL> conn internal@orcl as sysdba
Enter password: ******
ERROR: ORA-12154: TNS:could not resolve service name

Warning: You are no longer connected to ORACLE.
SQL>

Regards + Thanks

          Followup:

sigh, no versions...  must be 8i though if you are trying internal

sys@ORA920> connect ops$tkyte@ora920 as sysdba
Enter password:
Connected.

scott@ORA817DEV> connect ops$tkyte@ora817dev as sysdba
Enter password:
Connected.
scott@ORA817DEV>

but I cannot reproduce in any case.

      Rating: 5
RE:
      sysdba  September 13, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Are you possibly using 8.0.4 to attempt to connect as sysdba?

From a Metalink posting I found:

"Connection as SYSDBA can be made only using oracle8i - i.e ver 8.1.5 or above
on all platforms.  However, there is problem with the error message displayed.
8.0.4 gives ORA-12154: TNS: could not resolve service name which is misleading."

Not sure if that is your situation, but it seems close anyway.

- Mark

      Rating: 3
Thanks a
      lot!!  September 15, 2003
Reviewer:  Deepak
      Gupta  from Delhi , India
Mr. Mark  Thanks a lot !! for valuable information

      Rating: 3
deletion of
      password file ,   September 15, 2003
Reviewer:
      Deepak Gupta  from Delhi,India
Hi Mr. Tom,

 I am using Oracle8 Release 8.0.4.0.0 - Production.

after deleted my password file , I am not able to connect as internal ( Invalid
username/password )

NAME                                          VALUE
------------------------------------ ---------------------
remote_login_passwordfile                 EXCLUSIVE

I create again password file to get connect internal to starup my db ( internal
take same password what had put in password file )

why so ?

Is there any default password for internal ?

if I do not want use password file then what to do ( to connect internal )?

Thanks + Regards

          Followup:

if you log into the proper OS account on the same machine, you can do connect
internal (or "as sysdba") without it.

on unix, the DBA group (or whatever group you specified) for example can connect
internal locally without a password.

      Rating: 5
It's not giving
      error for me.Why ?  September 15, 2003
Reviewer:
      Ashiq Shamsudeen A  from Trichy, Tamil Nadu, India
Hi Tom,

 db--> oracle817 and OS --> W2k professional

 As you mentioned I've created password file and I got connected as sys with
SYSDBA .It haven't given me a error.See below

scott@ASHIQ> host orapwd file=orapw password=foobar entries=10

scott@ASHIQ> conn sys/change_on_install@ashiq
Connected.
scott@ASHIQ>
scott@ASHIQ>
scott@ASHIQ> conn sys/change_on_install@ashiq as sysdba
Connected

    Why its not giving error ?

          Followup:

the password file isn't just orapw

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/win.817/a73008/ch6.htm#1028152
describes the name and where this file goes.

      Rating: 4
Not able to
      connect as internal without password file!!!  September 18,
      2003
Reviewer:  Deepak Gupta  from Delhi, India

Hi Mr. Tom,

Database --->Oracle8 Release 8.0.4.0.0 -
O/S  ---> Windows NT 4.0

I  connected  as Administrator ( build-in NT account,  it all privilege) on NT
Oracle server machine (locally) and at command propmt
(if I removed password file )

M:\orant>svrmgr30
svrmgr> connect internal

I got following error

"invalid username/password : login denied "

Thanks + regards

          Followup:

sorry, don't have 8.0 anywhere.
don't have windows on any computers...

but it seems easy to solve -- create the password file?

log in as the guy who installed oracle - they should be in the right "group"

      Rating: 5
any user can
      connect as sysdba!  November 17, 2003
Reviewer:
      A reader  from Montreal, Canada
Hi Tom,

First, I worship you. Thank you for this website and being so cool. I have all
your books, read your website and pray that some day I can meet you and say many
thanks. Now, on to my question.

I have read and read and read your explanations on internal, os authentication,
sysdba, sysoper priviledges. I think I understand them.

On my Oracle 8.1.7 environment, I am able to login as sysdba when I have not
been granted the sysdba priviledge!

e.g. connect foo@someREMOTEmachine as sysdba, asks for the password and logs me
in as sysdba. once in, I can see any table owned by sys! Yikes!

I have checked that I am not in the password file (V$passwd_users). I am also
NOT logging into "someREMOTEmachine" locally. Can you please explain why this
would be so.

I have a feeling, that this is a REALLY stupid question. If so, apologies in
advance.

          Followup:

go talk to your OS admin, they've put everyone in the "power user group"

also, whats in your sqlnet.ora

      Rating: 5
any user can
      connect as sysdba  November 17, 2003
Reviewer:
      A reader
Hi Tom,

Sorry, but in the previous post I forgot to mention that the OS is Windows 2K.
Waiting for your reply. Thanks.

      Rating: 5
any user can
      connect as sysdba  November 18, 2003
Reviewer:
      A reader  from Montreal, Canada
Hi Tom,

Sorry to keep posting on your site. But I think I have figured out the answer to
the question I posted (anyone can login as sysdba).

Ofcourse, only the people who have the sysdba priviledge should be able to login
as sysdba. I verfied this by looking at the password file contents
v$pwfile_users to see only internal and sys.

Also checked the session_privs table to find only a handful of priviledges (not
the 100+ for sysdba).

Just wanted to keep you and anyone else who gets confused like I did, posted.
Thanks and God bless.

November 21, 2003 M -- Thanks for the question regarding "Problem
      to perform TSPITR", version 8.1.7

      You Asked
I was performing TSPITR with RMAN but got the following error during compiling
command "restore".

RMAN-06026: some targets not found - aborting restore
RMAN-06023: no backup or copy of datafile 3 found to restore
RMAN-06023: no backup or copy of datafile 1 found to restore

I am running 8.1.7 on UNIX and used recovery catalog. I didn't have any problem
to recover the tablespace to the current time point with the same backup.

Thanks for any help.

      and we said...
Recovering a single tablespace to the CURRENT point in time requires a backup of
just that tablespace.

Doing a TSPITR requires a back up of that tablespace, SYSTEM and all rollback
tablespaces.  Since datafile 1 is pretty much always SYSTEM -- looks like you
didn't back up system.  also, 3 is probably a rollback segment.

You need to have backed them up to do a pitr of a tablespace.  the process is
pretty much:

o restore system, rollback and affected tablespace to a mini instance.
o pitr this instance.
o transport the tablespace off of this instance
o trash this tiny instance
o transport the tablespace back onto the original database.

Make sure to backup SYSTEM and ROLLBACK.

Review &
      Followup

      Rating: 3
RMAN-06023 /
      RMAN-06026  July 03, 2002
Reviewer:  yogesh
       from Pune, India
I'm getting these errors when I'm trying to create a duplicate database from
catalog. For that I have created new service by ORADIM, started auxiliary
database in nomount mode.  Issued following command.

c:\rman target internal/oracle@qr catalog rman/rman@rcat auxiliary
internal/oracle@compaq

I got following

Recovery Manager: Release 8.1.7.0.0 - Production

RMAN-06005: connected to target database: QRM (DBID=393764689)
RMAN-06008: connected to recovery catalog database
RMAN-06020: connected to auxiliary database

Then I executed

run {
      allocate auxiliary channel ch1 type disk;
     duplicate target database to QRM nofilenamecheck;
}

I got

RMAN-03022: compiling command: allocate
RMAN-03023: executing command: allocate
RMAN-08030: allocated channel: ch1
RMAN-08500: channel ch1: sid=10 devtype=DISK

RMAN-03022: compiling command: Duplicate Db

RMAN-03027: printing stored script: Memory Script
{
   set until scn  818188;
   set newname for datafile  1 to
 "D:\ORACLE\ORADATA\QRM\SYSTEM01.DBF";
   set newname for datafile  2 to
 "D:\ORACLE\ORADATA\QRM\RBS01.DBF";
   set newname for datafile  3 to
 "D:\ORACLE\ORADATA\QRM\USERS01.DBF";
   set newname for datafile  4 to
 "D:\ORACLE\ORADATA\QRM\TEMP01.DBF";
   set newname for datafile  5 to
 "D:\ORACLE\ORADATA\QRM\TOOLS01.DBF";
   set newname for datafile  6 to
 "D:\ORACLE\ORADATA\QRM\INDX01.DBF";
   set newname for datafile  7 to
 "D:\ORACLE\ORADATA\QRM\QRM_DATA.DBF";
   set newname for datafile  8 to
 "D:\ORACLE\ORADATA\QRM\QRM_INDX.DBF";
   set newname for datafile  9 to
 "D:\ORACLE\ORADATA\QRM\TRANS_DATA.DBF";
   set newname for datafile  10 to
 "D:\ORACLE\ORADATA\QRM\OT_DATA.DBF";
   set newname for datafile  11 to
 "D:\ORACLE\ORADATA\QRM\OT_DATA1.DBF";
   set newname for datafile  12 to
 "D:\ORACLE\ORADATA\QRM\QRM_INDX1.DBF";
   restore
   check readonly
   clone database
   ;
   sql clone " CREATE CONTROLFILE REUSE SET DATABASE ^"QRM^" RESETLOGS
ARCHIVELOG
  MAXLOGFILES     32
  MAXLOGMEMBERS      2
  MAXDATAFILES      254
  MAXINSTANCES     1
  MAXLOGHISTORY      907
 LOGFILE
  GROUP  1 ( ''D:\ORACLE\ORADATA\QRM\REDO01.LOG'' ) SIZE    1048576  REUSE,
  GROUP  2 ( ''D:\ORACLE\ORADATA\QRM\REDO02.LOG'' ) SIZE    1048576  REUSE,
  GROUP  3 ( ''D:\ORACLE\ORADATA\QRM\REDO03.LOG'' ) SIZE    1048576  REUSE
 DATAFILE
  ''D:\ORACLE\ORADATA\QRM\SYSTEM01.DBF''
 CHARACTER SET WE8ISO8859P1";
}
RMAN-03021: executing script: Memory Script

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: restore

RMAN-03022: compiling command: IRESTORE
RMAN-03026: error recovery releasing channel resources
RMAN-08031: released channel: ch1
RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-03002: failure during compilation of command
RMAN-03013: command type: Duplicate Db
RMAN-03015: error occurred in stored script Memory Script
RMAN-03002: failure during compilation of command
RMAN-03013: command type: restore
RMAN-03002: failure during compilation of command
RMAN-03013: command type: IRESTORE
RMAN-06026: some targets not found - aborting restore
RMAN-06023: no backup or copy of datafile 12 found to restore
RMAN-06023: no backup or copy of datafile 11 found to restore
RMAN-06023: no backup or copy of datafile 10 found to restore
RMAN-06023: no backup or copy of datafile 9 found to restore
RMAN-06023: no backup or copy of datafile 8 found to restore
RMAN-06023: no backup or copy of datafile 7 found to restore
RMAN-06023: no backup or copy of datafile 6 found to restore
RMAN-06023: no backup or copy of datafile 5 found to restore
RMAN-06023: no backup or copy of datafile 4 found to restore
RMAN-06023: no backup or copy of datafile 3 found to restore
RMAN-06023: no backup or copy of datafile 2 found to restore
RMAN-06023: no backup or copy of datafile 1 found to restore

RMAN>

My backup is

RMAN> list backup;

RMAN-03022: compiling command: list

List of Backup Sets
Key     Recid      Stamp      LV Set Stamp  Set Count  Completion Time
------- ---------- ---------- -- ---------- ---------- ----------------------
40056   21         466306637  0  466304944  22         07/04/2002 01:37:17

    List of Backup Pieces
    Key     Pc# Cp# Status      Completion Time        Piece Name
    ------- --- --- ----------- ---------------------- ------------------------
    40057   1   1   AVAILABLE   07/04/2002 01:37:17
D:\RMAN\QRM\DB_T466304944_S22_P1

    List of Datafiles Included
    File Name                                  LV Type Ckp SCN    Ckp Time
    ---- ------------------------------------- -- ---- ---------- -------------
    1    D:\ORACLE\ORADATA\QRM\SYSTEM01.DBF    0  Full 818293     07/04/2002
01:09:08
    2    D:\ORACLE\ORADATA\QRM\RBS01.DBF       0  Full 818293     07/04/2002
01:09:08
    3    D:\ORACLE\ORADATA\QRM\USERS01.DBF     0  Full 818293     07/04/2002
01:09:08
    4    D:\ORACLE\ORADATA\QRM\TEMP01.DBF      0  Full 818293     07/04/2002
01:09:08
    5    D:\ORACLE\ORADATA\QRM\TOOLS01.DBF     0  Full 818293     07/04/2002
01:09:08
    6    D:\ORACLE\ORADATA\QRM\INDX01.DBF      0  Full 818293     07/04/2002
01:09:08
    7    D:\ORACLE\ORADATA\QRM\QRM_DATA.DBF    0  Full 818293     07/04/2002
01:09:08
    8    D:\ORACLE\ORADATA\QRM\QRM_INDX.DBF    0  Full 818293     07/04/2002
01:09:08
    9    D:\ORACLE\ORADATA\QRM\TRANS_DATA.DBF  0  Full 818293     07/04/2002
01:09:08
    10   D:\ORACLE\ORADATA\QRM\OT_DATA.DBF     0  Full 818293     07/04/2002
01:09:08
    11   D:\ORACLE\ORADATA\QRM\OT_DATA1.DBF    0  Full 818293     07/04/2002
01:09:08
    12   D:\ORACLE\ORADATA\QRM\QRM_INDX1.DBF   0  Full 818293     07/04/2002
01:09:08

on metalink I got one explaination saying that we should backup all archive logs
too. I even have them

RMAN> list backup of archivelog all;

RMAN-03022: compiling command: list

........ Partial Part .....

List of Backup Sets
Key     Recid      Stamp      LV Set Stamp  Set Count  Completion Time
------- ---------- ---------- -- ---------- ---------- ----------------------
40304   34         466307396  0  466307358  35         07/04/2002 01:49:56

    List of Backup Pieces
    Key     Pc# Cp# Status      Completion Time        Piece Name
    ------- --- --- ----------- ---------------------- ------------------------
    40318   1   1   AVAILABLE   07/04/2002 01:49:56
D:\RMAN\QRM\LOG_T466307358_S35_P1

    List of Archived Logs Included
    Thrd Seq     Low SCN    Next SCN   Low Time        Next Time
    ---- ------- ---------- ---------- --------------- ---------------
    1    1767    666247     667350     06/27/2002 20:07:39 06/27/2002 20:07:44
    1    1768    667350     668684     06/27/2002 20:07:44 06/27/2002 20:07:49
    1    1682    563905     565327     06/27/2002 20:01:17 06/27/2002 20:01:21
    1    1683    565327     566473     06/27/2002 20:01:21 06/27/2002 20:01:25
    1    1684    566473     567579     06/27/2002 20:01:25 06/27/2002 20:01:31
    1    1685    567579     568684     06/27/2002 20:01:31 06/27/2002 20:01:34
    1    1686    568684     570158     06/27/2002 20:01:34 06/27/2002 20:01:39
    1    1687    570158     571258     06/27/2002 20:01:39 06/27/2002 20:01:43
    1    1688    571258     572364     06/27/2002 20:01:43 06/27/2002 20:01:47
    1    1689    572364     573502     06/27/2002 20:01:47 06/27/2002 20:01:51
    1    1690    573502     574932     06/27/2002 20:01:51 06/27/2002 20:01:55
    1    1691    574932     576035     06/27/2002 20:01:55 06/27/2002 20:01:59
    1    1692    576035     577141     06/27/2002 20:01:59 06/27/2002 20:02:04
    1    1693    577141     578406     06/27/2002 20:02:04 06/27/2002 20:02:07
    1    1694    578406     579714     06/27/2002 20:02:07 06/27/2002 20:02:11
    1    1695    579714     580820     06/27/2002 20:02:11 06/27/2002 20:02:16
    1    1696    580820     581927     06/27/2002 20:02:16 06/27/2002 20:02:20
    1    1697    581927     583315     06/27/2002 20:02:20 06/27/2002 20:02:24
    1    1698    583315     584501     06/27/2002 20:02:24 06/27/2002 20:02:28
    1    1699    584501     585612     06/27/2002 20:02:28 06/27/2002 20:02:32
    1    1700    585612     586714     06/27/2002 20:02:32 06/27/2002 20:02:37
    1    1701    586714     588199     06/27/2002 20:02:37 06/27/2002 20:02:40
    1    1702    588199     589301     06/27/2002 20:02:40 06/27/2002 20:02:44

List of Backup Sets
Key     Recid      Stamp      LV Set Stamp  Set Count  Completion Time
------- ---------- ---------- -- ---------- ---------- ----------------------
40303   33         466307348  0  466307310  34         07/04/2002 01:49:08

    List of Backup Pieces
    Key     Pc# Cp# Status      Completion Time        Piece Name
    ------- --- --- ----------- ---------------------- ------------------------
    40317   1   1   AVAILABLE   07/04/2002 01:49:08
D:\RMAN\QRM\LOG_T466307310_S34_P1

    List of Archived Logs Included
    Thrd Seq     Low SCN    Next SCN   Low Time        Next Time
    ---- ------- ---------- ---------- --------------- ---------------
    1    1703    589301     590408     06/27/2002 20:02:44 06/27/2002 20:02:49
    1    1704    590408     591512     06/27/2002 20:02:49 06/27/2002 20:02:53
    1    1705    591512     592996     06/27/2002 20:02:53 06/27/2002 20:02:57
    1    1706    592996     594095     06/27/2002 20:02:57 06/27/2002 20:03:02
    1    1707    594095     595196     06/27/2002 20:03:02 06/27/2002 20:03:07
    1    1708    595196     596402     06/27/2002 20:03:07 06/27/2002 20:03:11
    1    1709    596402     597778     06/27/2002 20:03:11 06/27/2002 20:03:15
    1    1710    597778     598886     06/27/2002 20:03:15 06/27/2002 20:03:18
    1    1711    598886     599988     06/27/2002 20:03:18 06/27/2002 20:03:24
    1    1712    599988     601410     06/27/2002 20:03:24 06/27/2002 20:03:27
    1    1713    601410     602564     06/27/2002 20:03:27 06/27/2002 20:03:31
    1    1714    602564     603668     06/27/2002 20:03:31 06/27/2002 20:03:36
    1    1715    603668     604902     06/27/2002 20:03:36 06/27/2002 20:03:41
    1    1716    604902     606230     06/27/2002 20:03:41 06/27/2002 20:03:45
    1    1717    606230     607339     06/27/2002 20:03:45 06/27/2002 20:03:49
    1    1718    607339     608444     06/27/2002 20:03:49 06/27/2002 20:03:53
    1    1719    608444     609922     06/27/2002 20:03:53 06/27/2002 20:03:58
    1    1720    609922     611034     06/27/2002 20:03:58 06/27/2002 20:04:02
    1    1721    611034     612135     06/27/2002 20:04:02 06/27/2002 20:04:07
    1    1722    612135     613560     06/27/2002 20:04:07 06/27/2002 20:04:11
    1    1723    613560     614721     06/27/2002 20:04:11 06/27/2002 20:04:16
    1    1724    614721     615829     06/27/2002 20:04:16 06/27/2002 20:04:20
    1    1725    615829     616929     06/27/2002 20:04:20 06/27/2002 20:04:25
    1    1726    616929     618414     06/27/2002 20:04:25 06/27/2002 20:04:29
    1    1727    618414     619518     06/27/2002 20:04:29 06/27/2002 20:04:34
    1    1728    619518     620625     06/27/2002 20:04:34 06/27/2002 20:04:38
    1    1729    620625     622010     06/27/2002 20:04:38 06/27/2002 20:04:42
    1    1730    622010     623211     06/27/2002 20:04:42 06/27/2002 20:04:46
    1    1731    623211     624316     06/27/2002 20:04:46 06/27/2002 20:04:50
    1    1732    624316     625480     06/27/2002 20:04:50 06/27/2002 20:04:55
    1    1733    625480     626889     06/27/2002 20:04:55 06/27/2002 20:04:59
    1    1734    626889     627993     06/27/2002 20:04:59 06/27/2002 20:05:03
    1    1735    627993     629095     06/27/2002 20:05:03 06/27/2002 20:05:08
    1    1736    629095     630579     06/27/2002 20:05:08 06/27/2002 20:05:13
    1    1737    630579     631683     06/27/2002 20:05:13 06/27/2002 20:05:17
    1    1738    631683     632793     06/27/2002 20:05:17 06/27/2002 20:05:22
    1    1739    632793     634047     06/27/2002 20:05:22 06/27/2002 20:05:26
    1    1740    634047     635369     06/27/2002 20:05:26 06/27/2002 20:05:31
    1    1741    635369     636473     06/27/2002 20:05:31 06/27/2002 20:05:35
    1    1742    636473     637574     06/27/2002 20:05:35 06/27/2002 20:05:39
    1    1743    637574     638755     06/27/2002 20:05:39 06/27/2002 20:05:45
    1    1744    638755     640137     06/27/2002 20:05:45 06/27/2002 20:05:49
    1    1745    640137     641246     06/27/2002 20:05:49 06/27/2002 20:05:53
    1    1746    641246     642346     06/27/2002 20:05:53 06/27/2002 20:05:58
    1    1747    642346     643463     06/27/2002 20:05:58 06/27/2002 20:06:03
    1    1748    643463     644923     06/27/2002 20:06:03 06/27/2002 20:06:08
    1    1749    644923     646022     06/27/2002 20:06:08 06/27/2002 20:06:13
    1    1750    646022     647124     06/27/2002 20:06:13 06/27/2002 20:06:18
    1    1751    647124     648227     06/27/2002 20:06:18 06/27/2002 20:06:23
    1    1752    648227     649703     06/27/2002 20:06:23 06/27/2002 20:06:28
    1    1753    649703     650807     06/27/2002 20:06:28 06/27/2002 20:06:32
    1    1754    650807     651908     06/27/2002 20:06:32 06/27/2002 20:06:40
    1    1755    651908     653010     06/27/2002 20:06:40 06/27/2002 20:06:45
    1    1756    653010     654491     06/27/2002 20:06:45 06/27/2002 20:06:50
    1    1757    654491     655595     06/27/2002 20:06:50 06/27/2002 20:06:55
    1    1758    655595     656698     06/27/2002 20:06:55 06/27/2002 20:07:01
    1    1759    656698     657802     06/27/2002 20:07:01 06/27/2002 20:07:05
    1    1760    657802     659251     06/27/2002 20:07:05 06/27/2002 20:07:10
    1    1761    659251     660376     06/27/2002 20:07:10 06/27/2002 20:07:15
    1    1762    660376     661477     06/27/2002 20:07:15 06/27/2002 20:07:20
    1    1763    661477     662583     06/27/2002 20:07:20 06/27/2002 20:07:26
    1    1764    662583     663972     06/27/2002 20:07:26 06/27/2002 20:07:30
    1    1765    663972     665145     06/27/2002 20:07:30 06/27/2002 20:07:35
    1    1766    665145     666247     06/27/2002 20:07:35 06/27/2002 20:07:39

List of Backup Sets
Key     Recid      Stamp      LV Set Stamp  Set Count  Completion Time
------- ---------- ---------- -- ---------- ---------- ----------------------
40304   34         466307396  0  466307358  35         07/04/2002 01:49:56

    List of Backup Pieces
    Key     Pc# Cp# Status      Completion Time        Piece Name
    ------- --- --- ----------- ---------------------- ------------------------
    40318   1   1   AVAILABLE   07/04/2002 01:49:56
D:\RMAN\QRM\LOG_T466307358_S35_P1

    List of Archived Logs Included
    Thrd Seq     Low SCN    Next SCN   Low Time        Next Time
    ---- ------- ---------- ---------- --------------- ---------------
    1    1767    666247     667350     06/27/2002 20:07:39 06/27/2002 20:07:44
    1    1768    667350     668684     06/27/2002 20:07:44 06/27/2002 20:07:49

Where is the actual problem ?

          Followup:

Yogesh,

I am sorry to report that this site (asktom) really isn't a synonym for Oracle
support (you ask tons and tons of questions via the "followup" and they are
questions that are support type questions).  Please take a little more time on
your part to investigate some of these issues.  That is how I learned myself.

The mistake you have made should be pretty clear.  After seeing that it says
"hey, there is no datafile 1 I can use" in effect, I might look a little at the
information I have to try and figure this out.  Lets see:

List of Datafiles Included
File Name                                  LV Type Ckp SCN    Ckp Time
---- ------------------------------------- -- ---- ---------- -------------
1    D:\ORACLE\ORADATA\QRM\SYSTEM01.DBF    0  Full 818293     07/04/2002
01:09:08

Now, I look at my script....

...
{
   set until scn  818188;
   set newname for datafile  1 to
....

hmm, recover until SCN 818188, using a backup that is upto 818293.... Hmmmmm...

In effect, recover to BEFORE the file was backed up....  To BEFORE the file was
backed up....

that would be really cool wouldn't it?

Your backups POST DATE the time to which you want to restore.  We can restore
and roll forward.  (forward).  Not backwards.

Consider what if at SCN 818200 -- someone dropped a table (no redo, no rollback
for the acual TABLE DATA).  At SCN 818201 someone reused the SPACE that was
previously occupied by that dropped table.  Now, you backup the file at 818293
and later say "hey, make this file look like it did at change 818188".  Not
quite possible is it.

      Rating:
      4
Thanx  July 04, 2002
Reviewer:
      Yogesh  from Pune India
Thanx a lot Tom, I can understand your problem .... in small time you try to
answer as many questions ... I'm sorry about taking your valuable time ... but I
searched for this near about 2 days and then posted. I could not get the
solution .. I went through nearly all links from meta link .... searched many
forums... but could not find solution for my problem.

What you say about this solution is very correct ... but when I issue

run {
      allocate auxiliary channel ch1 type disk;
     duplicate target database to QRM nofilenamecheck;
}

I'm not specifying any SCN .... RMAN is taking it of its own ...

Will following script solve the problem ?

run {
     set until scn 818293;
     allocate auxiliary channel ch1 type disk;
     duplicate target database to QRM nofilenamecheck;
}

Thanx once again .....

          Followup:

it should.  try it.

      Rating: 3
Please
      help  November 11, 2003
Reviewer:  Duncan
       from UK
I have a smiliar problem but am not sure how to fix it?

RMAN> list backupset;

RMAN-03022: compiling command: list

List of Backup Sets
Key     Recid      Stamp      LV Set Stamp  Set Count  Completion Time
------- ---------- ---------- -- ---------- ---------- ----------------------
49411   48         509800698  0  509800545  58         11-NOV-03

    List of Backup Pieces
    Key     Pc# Cp# Status      Completion Time        Piece Name
    ------- --- --- ----------- ---------------------- ------------------------
    49415   1   1   AVAILABLE   11-NOV-03              /op4/backup/1qf65s31_1_1

    List of Datafiles Included
    File Name                                  LV Type Ckp SCN    Ckp Time
    ---- ------------------------------------- -- ---- ---------- -------------
    7    /op4/datafiles/drsys01.dbf            0  Full 164748820  11-NOV-03
    9    /op4/datafiles/DATA_S_2.dbf           0  Full 164748820  11-NOV-03
    24   /op4/datafiles/temp03.dbf             0  Full 164748820  11-NOV-03
    26   /op4/datafiles/DATA_M_1.dbf           0  Full 164748820  11-NOV-03

List of Backup Sets
Key     Recid      Stamp      LV Set Stamp  Set Count  Completion Time
------- ---------- ---------- -- ---------- ---------- ----------------------
49412   49         509800790  0  509800545  57         11-NOV-03

    List of Backup Pieces
    Key     Pc# Cp# Status      Completion Time        Piece Name
    ------- --- --- ----------- ---------------------- ------------------------
    49416   1   1   AVAILABLE   11-NOV-03              /op4/backup/1pf65s31_1_1

    List of Datafiles Included
    File Name                                  LV Type Ckp SCN    Ckp Time
    ---- ------------------------------------- -- ---- ---------- -------------
    1    /oracle/app/oracle/oradata/op4/system01.dbf 0  Full 164748819  11-NOV-0
3
    23   /op4/datafiles/temp02.dbf             0  Full 164748819  11-NOV-03
    25   /op4/datafiles/DATA_S_1.dbf           0  Full 164748819  11-NOV-03

List of Backup Sets
Key     Recid      Stamp      LV Set Stamp  Set Count  Completion Time
------- ---------- ---------- -- ---------- ---------- ----------------------
49413   50         509800803  0  509800545  59         11-NOV-03

    List of Backup Pieces
    Key     Pc# Cp# Status      Completion Time        Piece Name
    ------- --- --- ----------- ---------------------- ------------------------
    49417   1   1   AVAILABLE   11-NOV-03              /op4/backup/1rf65s31_1_1

    List of Datafiles Included
    File Name                                  LV Type Ckp SCN    Ckp Time
    ---- ------------------------------------- -- ---- ---------- -------------
    2    /op4/datafiles/tools01.dbf            0  Full 164748821  11-NOV-03
    5    /op4/datafiles/users01.dbf            0  Full 164748821  11-NOV-03
    15   /op4/datafiles/rbs02.dbf              0  Full 164748821  11-NOV-03
    27   /op4/datafiles/DATA_L_1.dbf           0  Full 164748821  11-NOV-03

List of Backup Sets
Key     Recid      Stamp      LV Set Stamp  Set Count  Completion Time
------- ---------- ---------- -- ---------- ---------- ----------------------
49414   51         509800859  0  509800545  60         11-NOV-03

    List of Backup Pieces
    Key     Pc# Cp# Status      Completion Time        Piece Name
    ------- --- --- ----------- ---------------------- ------------------------
    49418   1   1   AVAILABLE   11-NOV-03              /op4/backup/1sf65s31_1_1

    List of Datafiles Included
    File Name                                  LV Type Ckp SCN    Ckp Time
    ---- ------------------------------------- -- ---- ---------- -------------
    3    /op4/datafiles/rbs01.dbf              0  Full 164748822  11-NOV-03
    4    /op4/datafiles/temp01.dbf             0  Full 164748822  11-NOV-03

RMAN> run {
     set newname for datafile '/oracle/app/oracle/oradata/op4/system01.dbf' to '
/oracle/app/oracle/oradata/AUX/system01.dbf';
     allocate auxiliary channel chdev1 type disk;
     duplicate target database to AUX;
}2> 3> 4> 5>

RMAN-03022: compiling command: set

RMAN-03022: compiling command: allocate
RMAN-03023: executing command: allocate
RMAN-08030: allocated channel: chdev1
RMAN-08500: channel chdev1: sid=7 devtype=DISK

RMAN-03022: compiling command: Duplicate Db

RMAN-03027: printing stored script: Memory Script
{
   set until scn  164748817;
   set newname for datafile  1 to
 "/oracle/app/oracle/oradata/AUX/system01.dbf";
   set newname for datafile  2 to
 "/oracle/app/oracle/oradata/AUX/tools01.dbf";
   set newname for datafile  3 to
 "/oracle/app/oracle/oradata/AUX/rbs01.dbf";
   set newname for datafile  4 to
 "/oracle/app/oracle/oradata/AUX/temp01.dbf";
   set newname for datafile  5 to
 "/oracle/app/oracle/oradata/AUX/users01.dbf";
   set newname for datafile  7 to
 "/oracle/app/oracle/oradata/AUX/drsys01.dbf";
   set newname for datafile  9 to
 "/oracle/app/oracle/oradata/AUX/DATA_S_2.dbf";
   set newname for datafile  15 to
 "/oracle/app/oracle/oradata/AUX/rbs02.dbf";
   set newname for datafile  23 to
 "/oracle/app/oracle/oradata/AUX/temp02.dbf";
   set newname for datafile  24 to
 "/oracle/app/oracle/oradata/AUX/temp03.dbf";
   set newname for datafile  25 to
 "/oracle/app/oracle/oradata/AUX/DATA_S_1.dbf";
   set newname for datafile  26 to
 "/oracle/app/oracle/oradata/AUX/DATA_M_1.dbf";
   set newname for datafile  27 to
 "/oracle/app/oracle/oradata/AUX/DATA_L_1.dbf";
   restore
   check readonly
   clone database
   ;
}
RMAN-03021: executing script: Memory Script

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: set

RMAN-03022: compiling command: restore

RMAN-03022: compiling command: IRESTORE
RMAN-03026: error recovery releasing channel resources
RMAN-08031: released channel: chdev1
RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-03002: failure during compilation of command
RMAN-03013: command type: Duplicate Db
RMAN-03015: error occurred in stored script Memory Script
RMAN-03002: failure during compilation of command
RMAN-03013: command type: restore
RMAN-03002: failure during compilation of command
RMAN-03013: command type: IRESTORE
RMAN-06026: some targets not found - aborting restore
RMAN-06023: no backup or copy of datafile 27 found to restore
RMAN-06023: no backup or copy of datafile 26 found to restore
RMAN-06023: no backup or copy of datafile 25 found to restore
RMAN-06023: no backup or copy of datafile 24 found to restore
RMAN-06023: no backup or copy of datafile 23 found to restore
RMAN-06023: no backup or copy of datafile 15 found to restore
RMAN-06023: no backup or copy of datafile 9 found to restore
RMAN-06023: no backup or copy of datafile 7 found to restore
RMAN-06023: no backup or copy of datafile 5 found to restore
RMAN-06023: no backup or copy of datafile 4 found to restore
RMAN-06023: no backup or copy of datafile 3 found to restore
RMAN-06023: no backup or copy of datafile 2 found to restore
RMAN-06023: no backup or copy of datafile 1 found to restore

RMAN>

Is it a problem with my backup command, which was:

run {
     sql 'ALTER SYSTEM ARCHIVE LOG CURRENT';
     allocate channel ch1 type disk format '/op4/backup/%U';
     allocate channel ch2 type disk format '/op4/backup/%U';
     allocate channel ch3 type disk format '/op4/backup/%U';
     allocate channel ch4 type disk format '/op4/backup/%U';
     backup database;

}

please point me in the right direction .

Many thanks

          Followup:

are you trying to clone/duplicate a database?  chapter 12 of the recovery
managers users guide has the steps (different from what you are doing here)

      Rating: 2
thanks for the
      reply  November 11, 2003
Reviewer:
      A reader
I am trying to duplicate the database to an auxiliary instance and I believe
that i have followed the documentation correctly, except I'm not sure if the
archive logs are included in my backupset or not. I guess if not then that may
be causing the duplicate to fail

My backup script is:

run {
     sql 'ALTER SYSTEM SWITCH LOGFILE';
     sql 'ALTER SYSTEM ARCHIVE LOG CURRENT';
     allocate channel ch1 type disk format '/op4/backup/%U';
     allocate channel ch2 type disk format '/op4/backup/%U';
     allocate channel ch3 type disk format '/op4/backup/%U';
     allocate channel ch4 type disk format '/op4/backup/%U';
     backup database;

}

What do i need to add to this to ensure the archivelogs are included in the
backupset? Also can i include the controlfile in it too?

          Followup:

well, the docs I pointed you to don't "recover", they do something totally
different.

      Rating:
      1
thanks  November 12, 2003
Reviewer:
      A reader  from uk
The online documentation of the Recovery managers user guide only goes up to
chapter 11 (8i) after that it is the glossary. I do not have a hard copy as
oracle have stopped sending these out. Please tell me which chapter you are
referring to. Thank you.

          Followup:

well, never having seen a VERSION the thread above, i always go for the latest
version.

all docs are on otn.oracle.com...

it was in chapter 7 way back when...

http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76990/dupdb.htm#441820
(and they didn't use recover back then either to clone)

      Rating: 3
Not a DBA but
      just curious.  November 17, 2003
Reviewer:
      A reader
From the above thread I came to conclusion that tsiptr move forward in time from
the last backup. But Yogesh was trying to move tsiptr before the last backup. It
did'nt make any sense to me since the database is already restored to the last
backup why would he want to go back. Tom, if you can please explain. I'm trying
to learn something new  each day.

          Followup:

i told him it didn't make any sense and you cannot do it...

November 21, 2003 Uday -- Thanks for the question regarding
      "Reducing LIOs", version 8.1.7

      You Asked
If LIOs are bad,
1). is it possible to reduce the LIOs programatically ?
2). If yes, what steps/guidelines are to be followed, while coding, inorder to
reduce/avoid LIOs ?
3). what is a "OK" number of the LIOs (some percentage of SGA or PIO etc ?)

If possible, can you please demonstrate with an example ? (I will be using
PL/SQL for coding)

Thanks,
Uday

      and we said...
1) by rewriting your SQL, tuning your SQL, setting environmental things like
sort_area_size or db_file_multi_block_read_count or the optimize_index_*
parameters.

2) the piece of advice I have for everyone:  totally and forever forget the
concept that "if my query ain't using an index, it must be broken".  Massive
nested loop joins are the main culprit of this.  If you are joining a 1,000,000
row table to a 2,000,000 row table and trying to get 2,000,000 rows back -- it
is very doubtful that an index should be used.

3) ratios STINK ( i have stronger words but this is a public forum after all ).
There is one ratio I use -- soft parse ratio (the ratio of soft to hard parses).
 It should be near 100 for most systems.  All other ratios -- forget about them.
There is no magic "good number".  It is like the stupidest ratio of them all -
cache hit.  I have a theory that systems with high cache hit ratios, over 95,
96%  -- are among the most poorly tuned systems.  They are experiencing
excessive LIO's due to massive nested loop joins.  Yet, their DBA's sit there
and say "well, my cache hit is 99% so all is well in the world".

I have no ratios for you.  If you have a query that you need to execute and the
best we can do is 1,000,000 LIO's -- then SO BE IT, that is that.  However, if
that query could be executing without doing 1,000,000 LIO's then we need to fix
it.
Review
      & Followup

      Rating:
      3
  December 05, 2002
Reviewer:  Uday
       from USA

      Rating: 4
Good
      info...  December 05, 2002
Reviewer:  Robert
       from Memphis, USA
Tom,

Would you parse (no pun intended) a detailed explanation of your example...

<quote>
...Massive nested loop joins are the main culprit of this.  If you are joining a
1,000,000 row table to a 2,000,000 row table and trying to get 2,000,000 rows
back -- it is very doubtful that an index should be used.
<quote>

Thanks,

Robert.

          Followup:

Ok, here you go.  So, joe (or josephine) sql coder needs to run the following
query:

select t1.object_name, t2.object_name
  from t t1, t t2
 where t1.object_id = t2.object_id
   and t1.owner = 'WMSYS'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch    35227      5.63       9.32      23380      59350          0      528384
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    35229      5.63       9.33      23380      59350          0      528384

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 80

Rows     Row Source Operation
-------  ---------------------------------------------------
 528384  HASH JOIN
   8256   TABLE ACCESS FULL T
1833856   TABLE ACCESS FULL T

suppose they ran it or explain planned it -- and saw that plan.  "Stupid
stupid CBO" they say -- "I have indexes, why won't it use it.  We all know that
indexes mean fast=true! Ok, let me use the faithful RBO and see what happens":

select /*+ RULE */ t1.object_name, t2.object_name
  from t t1, t t2
 where t1.object_id = t2.object_id
   and t1.owner = 'WMSYS'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch    35227    912.07    3440.70    1154555  121367981          0      528384
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    35229    912.07    3440.70    1154555  121367981          0      528384

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: 80

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T'
   2    1     NESTED LOOPS
   3    2       TABLE ACCESS (FULL) OF 'T'
   4    2       INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE)

be careful of what you ask for!

It can be deadly...  Yes, that is 10 seconds vs 1 hour.  All *because of
indexes*

Oh -- but my cache hit ratio, really good!

  1  SELECT phy.value,
  2         cur.value,
  3         con.value,
  4         1-((phy.value)/((cur.value)+(con.value))) "Cache hit ratio"
  5  FROM   v$sysstat cur, v$sysstat con, v$sysstat phy
  6  WHERE  cur.name='db block gets'
  7  AND    con.name='consistent gets'
  8* AND    phy.name='physical reads'
ops$tkyte@ORA920.US.ORACLE.COM> /

   VALUE      VALUE      VALUE Cache hit ratio
-------- ---------- ---------- ---------------
 1277377      58486  121661490      .989505609

98.9%!  Boy did i do a good job or what ;)

      Rating: 5
For
      Robert  December 05, 2002
Reviewer:  Connor
      McDonald  from UK
create table one_mill ( x number, y number) tablespace users;

insert /*+ APPEND */ into one_mill
select dbms_utility.get_hash_value(rownum,1,1000000),rownum from x$ksmmem
where rownum < 1000000;

create table two_mill ( x number, y number) tablespace users;

insert /*+ APPEND */ into two_mill
select dbms_utility.get_hash_value(rownum,1,1000000),rownum from x$ksmmem
where rownum < 2000000;

create index ix1 on two_mill ( x ) nologging;

analyze table one_mill estimate statistics;
analyze table two_mill estimate statistics;

select count(*)
from (
  select /*+ NO_MERGE ORDERED FULL(o) INDEX(t ix1) USE_NL(t) */ t.y
  from one_mill o, two_mill t
  where o.x = t.x ) ;

took 75 seconds

select count(*)
from (
  select /*+ FULL(t) FULL(o) */ t.y
  from one_mill o, two_mill t
  where o.x = t.x ) ;

took 23 seconds

      Rating: 5
Thanks Tom and
      Connor! ...... now.....  December 05, 2002

Reviewer:  Robert  from Memphis, USA
Execellent examples!

Tom,

The explain plan in your example shows a full table scan on both tables... this
means Oracle has to store contents of both tables in memory (correct?).

What if we have two 50,000,000 row tables joined. This will require extensive
resources.

How would you suggest attacking this situation... would your philosophy be the
same as with large transactions/rollback-segments? (i.e. just allocate the
necessary resources and go for it)..

If so, what resources would we need to be aware of in joining, say, two
50,000,000 row tables as in your hash_join scenario?

Thanks,

Robert.

          Followup:

NO -- full scans -- nothing "gets stored in ram".  The database is a tad more
sophisticated then that.  Result sets are not "pre-made" and stored somewhere.
We do not stuff things into memory and assume ram is infinite.

We fetch data from tables as we need it.  Here -- for the nested loops join --
it was processed much like this:

  for x in ( select * from t t1 )
  loop
     for y in ( select * from t t2 where t2.object_id = X.OBJECT_ID )
     loop
        -- output to client --
     end loop
  end loop

that query needed very very little memory to work.

The other query was more complex in its processing, the hash join would use
hash_area_size chunks of memory (or in 9i with pga aggregate targets -- some
size would be assigned at runtime depending on resources) and the tables would
be piecewise hashed together if the hash partitions that were made up didn't
fit.

50,000,000 rows - 5 rows -- same thing.  No difference (well, 50,000,000 rows
would tend to use the entire hash area size where 5 rows probably won't)...

For 50,000,000 rows -- you might need some temp space.  See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6501620260742
for an example (using sort_area_size but same concept)

      Rating: 4
FTS not stored
      in Buffer Cache???  December 06, 2002
Reviewer:
      Anthony  from Phil.
<<QUOTE>>
NO -- full scans -- nothing "gets stored in ram".  The database is a tad more
sophisticated then that.  Result sets are not "pre-made" and stored somewhere.
We do not stuff things into memory and assume ram is infinite.
<<QUOTE>>

i red in OCP : Oracle8i DBA Arch and Administration (Sybex) on page 10 it
said...

<<QUOTE>>
When new data buffers are moved to the LRU list, they are copied to the MRU end
of the list, pushing out the buffers from the LRU end. An exception to this
occurs when a full table scan is done and the blocks from a full table scan are
written to the LRU end of the list.
<<QUOTE>>

in my own understanding (correct me if im wrong), blocks from a table (let's say
more than a million records) will be cached to the database buffer but on the
LRU end list. so this means all blocks from that table are actually cached?

can you share some lights on this?

Regards,
NOTNA

          Followup:

you are missing the point -- the RESULT SET of your query against a 2 billion
row table is not stored in memory somewhere waiting for you to fetch from it.

Sure, blocks are buffered in the buffer cache -- as individual separate blocks
waiting for any session to come along and peek at them.

Is that your result set?  No.

The question was:

"The explain plan in your example shows a full table scan on both tables... this

means Oracle has to store contents of both tables in memory (correct?)."

and the answer is definitely not.  I can query a entire 5gig table with a 5meg
buffer cache -- no problem.  I will not have "the table in ram"

      Rating: 5
LIO, Buffer
      gets, Consistent gets  December 06, 2002
Reviewer:
      Arun Gupta  from Harrisburg, PA USA
Tom
01. What is the difference between LIO, Logical Reads, Buffer gets and
Consistent gets?
02. As you have advised to reduce LIO, how can I find out LIO from statspack
report or is there any other script to find it out?
03. If I have reduced the number of buffer gets for a query, have I reduced LIO?

Thanks

          Followup:

1) that is the definition of LIO's -- logical io's, which are buffer reads which
might be consistent gets or current mode gets.

2) buffer gets in the top sql report of statspack.

3) yup

      Rating: 3
Connor's
      example  February 26, 2003
Reviewer:  Mikito
      Harakiri
OK, the time difference is 3 times. I rerun Connor's example and got 80 sec vs
40 sec. I would expect buffer gets statistics to reflect this differnce, but in
reality there is about 1000 times difference. Something is definitely wrong with
Oracle's buffer gets ariphmetics.

OK. We see the time difference 2-3 times. Big Deal! For the minuscule time
savings like that CBO risks missing the indexed nested loops in the reverse
situation when performance difference might be thousands times in favor of
nested loops. And cardinality/cost calculation is *always* wrong for any sql of
medium complexity. If you want to convince me otherwise, please, show me
execution statistics where you see number of rows returned matching the
cardinality estimation per each row source.

          Followup:

mikito - be scienfitic -- post your results exactly ( just like I do ) and point
out where your confusion is.

It is all mathematically provable (and you know, I'm not getting your attitude
or approach -- things like "analytics are digusting, readers block readers --
where are you going with all of this?)"

the cost cardinality is not always wrong.  that is so easy to prove I'll leave
that to you (cause to prove "always" wrong, I only need one case -- thats
trivial).........  If you want to prove me wrong - offer a scientific proof, not
andetocal "evidence"

I don't mind "devils advocates" but you just keep saying unsubstantiated things
with no proofs, making me spend an inordindate amount of time proviing the
opposite.

      Rating:
      2
  February 27, 2003
Reviewer:
      A reader
tom, you said: "I have a theory that systems with high cache hit ratios, over
95, 96% -- are among the most poorly tuned systems. They are experiencing
excessive LIO's due to massive nested loop joins. Yet, their DBA's sit there and
say 'well, my cache hit is 99% so all is well in the world'."

should this be read as: HIGH cache hit ratio (excessive LIO's) is BAD, if it is
the result of massive nested loop joins. how could one identify unnecessary
nested loops?

          Followup:

yes, it was a bit tongue in cheek.

A very very very poor cache hit *might* be an indicator of a problem

A high cache hit ratio is just meaningless.  Neither good, nor bad nor
indifferent -- it by itself is not meaningful.

That is is 99% doesn't mean good -- that is my point.

You would look for queries with lots of gets per execute.  You would make them
get the answer with less gets per execute.  The more rows they return -- the
more gets they are allowed but a query that returns say a single unaggregated
row but does 100 buffer gets might be in need of some help for example.

      Rating: 5
Connor's
      example detailed  February 27, 2003
Reviewer:
      Mikito hartakiri
Here is my statistics. I'm totally ignoring physical reads since I have a filer
with 4 Gig cache (so that god knows how many buffers are there between oracle
and disks;-)

db1>select count(*) from (
  3    select /*+ ORDERED FULL(o) INDEX(t ix1) USE_NL(t) */ t.y
  4    from one_mill o, two_mill t
  5    where o.x = t.x ) ;

Elapsed: 00:01:42.00
    2008774  consistent gets

db1>select count(*) from (
  3    select /*+ FULL(t) FULL(o) use_hash(t o)*/ t.y
  4    from one_mill o, two_mill t
  5    where o.x = t.x ) ;

Elapsed: 00:00:27.05
       6434  consistent gets

  1  select count(*) from (
  3    select /*+ FULL(t) FULL(o) use_merge(o t)*/ t.y
  4    from one_mill o, two_mill t
  5*   where o.x = t.x )

Elapsed: 00:01:21.04
       6434  consistent gets

The verdict: buffer gets is very crude representation of Logical IO. There is no
300x performance difference between the methods.

Example 2 (vision db):
SELECT count(ATTRIBUTE1)
FROM OE_ORDER_LINES_ALL
14077 intermediate rows processed (before aggregation -- that is the size of the
table)
15998 buffer gets

When creating a clone table with "create as select", the number of buffer gets
drops 12 times.

The Verdict is once again: there is something wrong with buffer gets count.

Finally, you can take any query with 3 or more tables. If the one happens to
have a decent matching between number of rows processed and estimated
cardinality, then I would easily add a couple of predicates that would make a
CBO plan completely ridiculous. Wanna bet?

          Followup:

The verdict is a good case of false causality here but anyway.

actually -- ignoring the PIO in this case was probably the cause of your
confusion.  You might have noticed that PIO exceeded LIO in the merge join --
big sort spilt to disk and we did lots of direct reads/writes which bypass the
buffer cache so there are extenuating circumstances.

whosoever promised you a 300x increase?  300% is what you got which -- hey, if I
could get that every day I'd be a very very happy camper.

Consider that 1:42 is small in the grand scheme of a really big DW query -- that
your example should be extrapolated up (Conners example was a 3x, just --
amazingly -- like yours) to something on the order of my example above -- you
might see 10seconds vs 1 hour (tkprof don't lie)

My verdict -- besides not fully understanding either your point or motivation
that is -- is that LIO is what I tune for but I ignore nothing cause it is
all relevant.

There is not something wrong with buffer gets -- it is your analysis process
that is flawed.  Show me the plans -- and i'll betcha

a) the query used something akin to first rows
b) the CTAS optimized for all rows

guess what -- first rows does nested loops type stuff.  All rows -- full scans,
hashing.  So, again, you drew a false conclusion based on lack of information.

put up or ......  show me the money -- show me that it happens every time, each
and every time -- that was your premise.  as I said, all i need to do with a
"every time" sort of comment is show one example that is false.  It is up to you
to prove that it "always happens"

I find the cardinality to be fairly well derived -- which if it were not, the
CBO would never ever get it right on a 3 table join--- hmmmm.

      Rating: 4
LIO's and star
      transformations  February 28, 2003
Reviewer:
      Dave  from Colorado Springs
I am fully into the idea of pretty much disregarding buffer cache hit ratios,
but following an upgrade to 9iR2 I saw my chance to look at why I should be
getting a 99%+ ratio on a data warehouse. Using the v$segment_statistics view
(which I have become a great fan of) i saw a mind-boggling large number of LIO's
on a very small date dimension table -- about a thousand rows.

It had 19 PIO's, and over 300,000,000 LIO's -- far in excess of the LIO's on any
one of the fact tables in the DW, and driving the cache hit ratio to
non-sensically high levels. Almost needless to say the optimizer is making
extensive use of star transformations, and this tiny table gets used in hash
joins for nearly every query.

Star transformations are doing a great job for us, and if the price to be paid
is a relatively massive number of LIO's then  I have no complaints. I do wonder
though whether there are particular steps i might take to reduce LIO's while
sticking with the star transforms.

Do you have any thoughts on this, Tom?

          Followup:

If you use it alot -- for lots of queries -- one would expect it to have lots of
LIO's.

It is a matter of "is it unreasonable given its use".  If I used it once in a
single query -- 300million would seem "excessive".

If I use it over and over and over and over -- it may not be.

The real question -- are you meeting your performance goals/expectations?

You might want to read

http://www.dbazine.com/jlewis6.html
to get a better understanding of what is going on as well -- JPL has a knack for
saying things in a digestable manner

      Rating:
      5
  April 24, 2003
Reviewer:  Rory
       from Philippines
Hi Tom,

Just a clarification when it comes to cache hit ratios. I have talked with other
DBA's and they still think that cache hit ratio is important coz base from their
experience, whenever it goes down from 99.99 to even just 99.98, then calls
would be coming in as to the performance of the system. They have associated the
cache hit ratio shld be 99.99% for them or a problem wld occur. How could I
prove to them that they are wrong? How can I find the real problem to their
system and prove them that cache hit raio doesnt have anything to do with that?
thanks.

          Followup:

show them this example and ask them which query would they want running on their
system?

the one that generated a 50% cache hit
or the one that generated the 98% cache hit?

I'd *love* to see the system where a 0.01% reduction would be meaningful -- just
love to.  It must be the most finely tuned system on the planet.

It is all about wait events, what are people WAITING on.

It is all about reducing LIO's -- if you watch the pennies, the dollars take
care of themselves.  If you reduce your LIO's, you'll reduce your PIO's (see
example above).

if you want a 99.9% cache, just run this procedure:

begin
   loop
      for x in ( select * from dual )
      loop
        null;
      end loop;
    end loop;
end;
/

run as many copies of that as you need to achieve your 99.9% cache hit.

Now what -- are you any faster?

      Rating:
      5
  April 24, 2003
Reviewer:  Rory
       from Philippines
Hi Tom,

Great example. My cache hit ratio did increase when I ran your script. If only I
had your knowledge then I wld be able to prove them wrong. Any suggestions or
technique you'd like to share on how to find out what the problem really is.
Like for example look for waits first with this kind of value or maybe run
statspack. Whatever. THanks again Tom.

          Followup:

well, basically -- you never have a problem until you are not meeting your
documented formal performance requirements.

After that -- there are two threads you can follow

o tune what you have
o fix what you have

tuning attempts to work inside the box -- we'll fix this query, that query,
remove this wait, that wait and hope for the best.

fixing says "i'm going to look at the application architecture, its algorithms
and approaches and first validate that they are right, correct and best
practice.  EG: I would take all bad code of the form

     loop over every record
             insert into another table
             commit every now and again
     end loop

and make it efficient, fast and correct via

     insert into another_table select ... from ...;

I'll make sure I parse once, execute many
That I use binds
That I don't do "select count(*)" just to see if a record exists

and so on....

      Rating:
      5
  April 28, 2003
Reviewer:  Rory
       from Philippines
Thanks TOm,

    Again, thank you so much for sharing such knowledge. Wish u all the best in
your site.

      Rating: 5
6 stars for
      this one...  May 02, 2003
Reviewer:  Alvin
       from Philippines
Wow.... refuted another cw !

"If my explain plan ain't using my indexes.... most definitely broken"

      Rating: 5
Tell me if i'm
      wrong....  May 02, 2003
Reviewer:  Alvin
       from Philippines
If i remember correctly in one of the oracle docs that an index would be of use
if the result set would be 15% or less than the query size.

i'm thinking...

The full table scan ran faster because of we expect 2,000,000 rows to be
returned. Which is the size of the bigger table.

The query that forced an index usage ran slower because of the unecessary index
reads both Logical and physical, since its unavoidable that the query will do a
full table anyways.

was any of my ramblings right ?

          Followup:

give or take 15% to 85% yes -- that would be accurate.

but no, the hash join ran faster because it is a brute force "big" thing. it hit
each block as little as possible

The nest loops was a way to make it take 3-4 LIO's to get to each block - rather
then just read them one by one.

      Rating: 5
Should we
      then...  May 03, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Hi Tom,
Should we then prefer Hash Join over Nested Loops?
Is there a reason to pick one compared to the other?
Thanks,

          Followup:

if one were infinitely superior to the other in all cases and every
circumstance, we would have saved ourselves lots of time, energy and money by
implementing only one of them.

they both are superior to eachother in different circumstances.

Say you were interested in getting just the first row back from this query or
the first N rows - nested loops would be awesome.

Say you were interested in getting ALL of the rows but were paging through them
in a client server type application (maintaining a stated connection to the
database and fetching 15 rows at a time for a user to look at).  Nested loops
would be awesome (that and they would never actually get to the bottom, last
page anyhow)

Say you were interested in getting ALL of the rows asap -- nested loops stink,
hash join awesome.

      Rating:
      5
  May 03, 2003
Reviewer:  Sam

Hi ,
In addition to what Tom has explained so far, I would like to present an
analogy.
Let's say, you are working on a project and you need to refer to syntax of
"CREATE INDEX" statement.
What will you do? Will you read oracle 8i complete reference cover to cover?
Perhaps, not. The better option is to refer to index, which points to the actual
page no. explaining the syntax of "CREATE INDEX". Here in this case, accessing
data is faster through index.
Now, let's say, you are preparing for OCP certification and the complete 8i
reference is part of the curriculum. In this scenario, if you refer to index for
every chapter/syntax etc., it will be painful (You will probably miss the
examination date). Better option will be to read the complete book cover to
cover (i.e FULL TABLE SCAN).
Similarly whether to use Hash Join or Nested Loop join depends on the
need/requirement.
Just to reparaphrase , what Tom said in another thread

"FULL TABLE SCAN is not evil. Indexes may not be GOOD"
"Nothing is black or white.Everything is a shade of grey"

Right Tom...?

          Followup:

yes.

      Rating: 3
This boogles my
      mind...  May 14, 2003
Reviewer:  Alvin
       from Philippines
We have a query that does a full table scan... i thought that i can help the
query by indexing all the columns (individually)in the predicate.

I created the indexed the individual columns, ran analyze table estimate
statistics and tested the full scan versus the optimizer=Choosen path. Here's
what i got...

05:22:01 SQL> SELECT calldetailrecordid
05:22:07   2    FROM (SELECT /*+ FULL (rtbetelcalldetailrecords) */ ROWNUM rn,
calldetailrecordid
05:22:07   3            FROM rtbetelephony.rtbetelcalldetailrecords
05:22:07   4           WHERE sys_proctime = 0
05:22:07   5             AND sys_procend = 0
05:22:07   6             AND billingrecordid = 0
05:22:07   7             AND duration > 0) temp
05:22:07   8   WHERE temp.rn >= 1
05:22:07   9     AND temp.rn <= 100
05:22:08  10  /

Elapsed: 00:00:56.32

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=41778 Card=1 Bytes=2
          6)

   1    0   VIEW (Cost=41778 Card=1 Bytes=26)
   2    1     COUNT
   3    2       TABLE ACCESS (FULL) OF 'RTBETELCALLDETAILRECORDS' (Cos
          t=41778 Card=1 Bytes=19)

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
     275553  consistent gets
     242217  physical reads
          0  redo size
       1663  bytes sent via SQL*Net to client
        731  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
        100  rows processed

02:17:31 SQL> SELECT calldetailrecordid
02:17:48   2    FROM (SELECT ROWNUM rn, calldetailrecordid
02:17:48   3            FROM rtbetelephony.rtbetelcalldetailrecords
02:17:48   4           WHERE sys_proctime = 0
02:17:48   5             AND sys_procend = 0
02:17:48   6             AND billingrecordid = 0
02:17:48   7             AND duration > 0) temp
02:17:48   8   WHERE temp.rn >= 1
02:17:48   9     AND temp.rn <= 100
02:17:49  10  /

Elapsed: 00:02:150.06

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=96 Card=1 Bytes=26)
   1    0   VIEW (Cost=96 Card=1 Bytes=26)
   2    1     COUNT
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'RTBETELCALLDETAILREC
          ORDS' (Cost=96 Card=1 Bytes=19)

   4    3         AND-EQUAL
   5    4           INDEX (RANGE SCAN) OF 'RTBETELCDR_SYS_PROCEND_IDX'
           (NON-UNIQUE) (Cost=31 Card=1)

   6    4           INDEX (RANGE SCAN) OF 'RTBETELCDR_SYS_PROCTIME_IDX
          ' (NON-UNIQUE) (Cost=31 Card=1)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     284004  consistent gets
     252388  physical reads
          0  redo size
       1607  bytes sent via SQL*Net to client
        731  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
        100  rows processed

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=2 Card=2 B
          ytes=52)

   1    0   VIEW (Cost=2 Card=2 Bytes=52)
   2    1     COUNT
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'RTBETELCALLDETAILREC
          ORDS' (Cost=2 Card=2 Bytes=130)

   4    3         INDEX (RANGE SCAN) OF 'RTBETELCDR_BILLINGRECORDID_ID
          X' (NON-UNIQUE) (Cost=1 Card=2)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     280238  consistent gets
     247007  physical reads
          0  redo size
       1663  bytes sent via SQL*Net to client
        731  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
        100  rows processed

I don't get it...

Full Elapsed: 00:00:56.32 <- it cost's more but it's faster
Index Elapsed: 00:02:150.06 <- more than twice the time
Index with First_rows hint Elapsed: 00:01:73.65

If i select count(*) the inner query i get around 2206 rows
the table itself has 550,000++... i thought that the % returned is reasonable
enough to index this column.

1. Will the optimizer choose the full scan if i compute the statistics instead ?

2. Will it be better if i group the predicate into one index ?

3. Why did the index use only 2 of my index ? when i have 4 ?

btw, the table is a flat file that they use in production.

          Followup:

having each column indexed individually for this query -- useless.

having all columns indexed in a single index -- potentially helpful.

btw: table is not a flat file -- it is a table.

      Rating: 5
thanks
      !  May 15, 2003
Reviewer:  Alvin  from
      Philippines
I thought that oracle would query the 4 index and 'AND' the results and not
touch the table.

Anyways i dropped the 4 indexes and used 1 index on 4 columns and it did help.

Thanks!

          Followup:

it would do that with bitmap indexes, they are the ones that can be "anded" and
"ored" together.  b*trees, they cannot.

(ps: that does NOT mean use bitmaps, if this table is modified, not in a data
warehouse,  bitmaps would be inappropriate)

      Rating: 5
LIO in
      PL/SQL  June 20, 2003
Reviewer:  Sam
       from Modesto, CA
Tom,

This discussion has been great.

I will never look at ratios the same way.

I have been asked to tune a slow running PL/SQL procedure which contains a
structure like this.

For x_rec in SELECT... From … LOOP
… <<some commands>>
END LOOP;

The table has 90MM rows.  The process takes hours to run.  Since each row is
fetched into the loop then processed, it would seem that there must be an LIO
for each record returned by the cursor.  It seems like this is the server side
example of the too small arraysize on the client.

Is this correct?

I am looking at using BULK COLLECT the minimize LIO and perhaps increase
performance.    Am I on the right track?

          Followup:

well, yes and no.

bulk collect (and forall to update if that is what you are doing) would be
faster but -- it is still going to be "slow".

Do anything 90 million times and it'll be slow.

I would look to see (in this order)

o do i NEED to do that process (surprises abound on that one)

o can i do this process in a SINGLE SQL statement (very often is found to be
true)

o can i paralellize this process -- by splitting the table up by rowid ranges
and running N copies of this procedure simultaneously (this can be huge) see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:10498431232211
for example

using bulking binds would be a way to make the 3rd option go even "faster"

      Rating:
      5
  July 28, 2003
Reviewer:
      A reader

      Rating: 3
hash
      joins  November 17, 2003
Reviewer:  kit
       from england
HI tom,
 I know in this threas a few people have covered hash joins. I was wondering
could you please have a look at the below plan

     HASH JOIN        8 M    596 M    293537

       TABLE ACCESS FULL    X    90      1 K    7

       TABLE ACCESS BY INDEX ROWID    Y    9 M    552 M    293457

         INDEX FULL SCAN    Y.A    9 M         30892

        How would the above work
            identify the Y rows via the index first and then do the hash join.
would this be better than hash join on two
            tables

Would it better just do full on both tables and then do the hash join

          Followup:

sorry, not really sure what I'm looking at.  hows about the autotrace traceonly
explain output (a standard format)

and the query is always useful.

      Rating: 2
reduicing lios
      via hash joins  November 17, 2003
Reviewer:
      kit  from england
As you can see its more of a question of what the above plan is telling me. My
understanding its carrying out index join of table
Y via the indexY.a and then the other full table on X. Once finds the records,
does it then do the hash join between the keys of the
two tables

My question was is it wise to mix the Nl with the HASH join. I know there is no
general answer, but I would apreciate the examples
you've come across.

some of your notes explain as below

can you please show an example to understand

"   It would seem more logical to me to hash the SMALLER table (less temp stuff
to
   store, might fit into memory and all)...."

   create a hash table on one of A or B (say A) on the join key creating temp_a.

   while NOT eof on B
      read a record in b
      hash the join key and look up into temp_a by that hash key for matching
      records
      output the matches
   end loop

So, a hash join can sometimes be much more efficient (one hash, not two sorts)

Hash joins are used any time a sort merge might be used in most cases.  If you
don't see hash joins going on, perhaps you have hash_join_enabled turned off...

   It would seem more logical to me to hash the SMALLER table (less temp stuff
to
   store, might fit into memory and all)....

          Followup:

i'd like an autotrace output -- i'm not sure what i'm looking at

      Rating: 5
OCP Performance
      Tuning uses ratios  November 17, 2003
Reviewer:
      Ryan Gaffuri  from Tysons Corner, VA
Tom,

 Your a VP at Oracle. Cant you contact Oracle Education and knock some sense
into them? Their Performance Tuning OCP tests for both 8i and 9i are LOADED with
Ratios and other inaccuracies. I know Oracle is a big company, but this is
pathetic. Alot of companies now require taking the OCP and the performance
tuning test is just a waste of money.

  Alot of entry level people study for these tests and are giving HORRIBLE
information by Oracle information itself. Its very difficult to get them to do
the write thing since the 'Official Oracle Certification Test' says to what is
TOTALLY INACCURATE.

I know your in a seperate group. but cant you do anything? That test is stupid.

      Rating: 3
LIO VS
      PIO  November 17, 2003
Reviewer:  Hien
       from Australia
Tom

Probably this question has been asked before but could you please explain the
difference between LIO and PIO in relation to database design and application
design (ie query, DML)

Thanks
Hien

          Followup:

nothing really directly to do with database/application design (indirectly sure)

LIO is a logical IO.  how many buffer gets did we need to do to answer your
query.
PIO is a physical IO.  how many times did we read disk to do that.

bad designs/sql can lead to excessive LIO's, excessive LIO's may lead to too
many PIO's

      Rating: 5
OCP Performance
      test  November 18, 2003
Reviewer:  Ryan
      Gaffuri  from Tysons Corner, VA
Sorry let me re-word what I said yesterday. I dont mean my post as an attack on
you. However, how come the OCP is all about hit ratios amongst alot of other
inaccurate information? You are a VP at Oracle. Dont you have any sway over
them? Its rather disheartening to have you tell us to do one thing(which I
believe to be correct) and another group that actually certifies Oracle DBAs
tell people to do the complete opposite.

Who writes these tests? Have you tried contacting them? It really looks bad for
Oracle Corporation to have contradictory information about tuning. It also makes
our jobs as DBAs harder since people often trust the 'official' word from Oracle
over what you say. Therefore we have to try to convince them to not do what
Oracle 'officially' tells them to do. This can cause alot of conflict on
projects.

      Rating: 3
OCP
      tests  November 19, 2003
Reviewer:  reader

I agree. The OCP tests and a lot of Oracle books give undue weightage to the
ratios. It is high time that somebody changes this.

Any beginner can learn from books specifically made for OCP and pass these tests
even though they don't have any real database experience.

      Rating: 5
Leave Tom
      Alone  November 21, 2003
Reviewer:  Michael
       from San Diego, CA, USA
This is not the forum to be discussing OCP issues.
Tom has graciously help as many as he can.
If you don't like OCP, don't get.
If you don't like the book, don't read it.
This is not a whine forum.

November 21, 2003 José -- Thanks for the question regarding "MODAL
      Statistical function", version 2.5

      You Asked
We are looking for un sql srip to calclate the statistical
parameter call MODAL (the most repetitive value in a list).
Do you happen to know where can we get it?

      and we said...
the most repetitive -- if that is what you need -- would be:

select X, COUNT(*)
  from T
 order by 2 desc;

and then fetch the first row.  In Oracle8i, release 8.1 - you
can get this via:

select X
  from (select X, COUNT(*)
          from T
         order by 2 desc )
  where rownum = 1;

Review &
      Followup

      Rating: 2
solution works
      for 1 value but I want to return many modes  November 17,
      2003
Reviewer:  Mark Mathews  from Los Angeles, CA

product ndays
a           1
a           2
a           1
a           1
a           2
b           3
b           3
b           2

I want to return multiple modes based on a grouped by column.  If mode were a
built in function it would look like this:

select product,mode(ndays) from tablex group by product.  This would return:

a 1
b 3

          Followup:

  1  select *
  2    from
  3  (
  4  select p, n, c, row_number() over (partition by p order by c desc) rn
  5    from
  6  (
  7  select p, n, count(*) c
  8    from t group by p, n
  9  )
 10  )
 11*  where rn = 1
ops$tkyte@ORA920LAP> /

P          N          C         RN
- ---------- ---------- ----------
a          1          4          1
b          3          2          1

ops$tkyte@ORA920LAP>

November 21, 2003 RAGHAVENDRA -- Thanks for the question regarding
      "Counting the number of business days between 2 dates.", version

      You Asked
how to get the number of days between
two dates excluding saturdays and sundays using pl/sql?

      and we said...
something like:

ops$tkyte@8i> @test 29-feb-2000 01-feb-2000
ops$tkyte@8i> set echo on
ops$tkyte@8i>
ops$tkyte@8i> select count(*)
  2    from ( select rownum rnum
  3             from all_objects
  4            where rownum <= to_date('&1') - to_date('&2')+1 )
  5   where to_char( to_date('&2')+rnum-1, 'DY' )
                      not in ( 'SAT', 'SUN' )
  6  /
old   4:   where rownum <= to_date('&1') - to_date('&2')+1 )
new   4:   where rownum <= to_date('29-feb-2000') -
                           to_date('01-feb-2000')+1 )
old   5:  where to_char( to_date('&2')+rnum-1, 'DY' )
                    not in ( 'SAT', 'SUN' )
new   5:  where to_char( to_date('01-feb-2000')+rnum-1, 'DY' )
                    not in ( 'SAT', 'SUN' )

  COUNT(*)
----------
        21

will do it.  Make sure the table in the inline view (all_objects in my example)
has at least as many rows as you plan on having dates span (eg: it needs at
least 366 rows to do 1 year, 3660 to do 10 and so on).  all_objects, typically
with thousands of rows, is a good generic candidate -- you can always build your
own very compact table as well to make this faster.

A logical extension would be to add 'holidays' and other non-business days. We
would use a table with dates to exclude.  For example:

ops$tkyte@8i> create table exclude_dates ( no_work date primary key )
organization index;

Table created.

ops$tkyte@8i> insert into exclude_dates values ( '14-FEB-2000' );

1 row created.

ops$tkyte@8i>
ops$tkyte@8i>
ops$tkyte@8i> select count(*)
  2    from ( select rownum rnum
  3             from all_objects
  4            where rownum <= to_date('&1') - to_date('&2')+1 )
  5   where to_char( to_date('&2')+rnum-1, 'DY' )
                not in ( 'SAT', 'SUN' )
  6     and not exists
        ( select null from exclude_dates where no_work =
  7               trunc(to_date('&2')+rnum-1) )
  8  /

old   4:  where rownum <= to_date('&1') - to_date('&2')+1 )
new   4:  where rownum <= to_date('29-feb-2000') -
                          to_date('01-feb-2000')+1 )
old   5:  where to_char( to_date('&2')+rnum-1, 'DY' )
                 not in ( 'SAT', 'SUN' )
new   5:  where to_char( to_date('01-feb-2000')+rnum-1, 'DY' )
                 not in ( 'SAT', 'SUN' )
old   7:    trunc(to_date('&2')+rnum-1) )
new   7:    trunc(to_date('01-feb-2000')+rnum-1) )

  COUNT(*)
----------
        20

Review &
      Followup

      Rating: 5
-1, +1, -1
      why?  October 07, 2001
Reviewer:  Sam

Tom, you are Golden

select count(*)
  2    from ( select rownum-1 rnum
  3             from all_objects
  4            where rownum <= to_date('&1') - to_date('&2')+1 )
  5   where to_char( to_date('&2')+rnum-1, 'DY' )
                      not in ( 'SAT', 'SUN' )

Why are we adding -1 to rownum, and then adding one below
where rownum <= to_date('&1') - to_date('&2')+1 ), and then
subtracting -1 again below
where to_char( to_date('&2')+rnum-1, 'DY' )
                      not in ( 'SAT', 'SUN' )

Thank you

IN

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:185012348071
How can we handle in the same Sql the following situation.

Uncle Jim was paid untill March 15th 2001, from Jan2001 but only half at a rate
of 45 dollars per hour. Now how can we accomodate the same caluculation in the
same sql , and get the total amount due to uncle JIM.

You seem to be quite busy these days, not to be seen much on ASKTOM.ORACLE.COM.

I have an interesting question, if you decide to take off from this project ,
and do something different , will ASKTOM.ORACLE.COM come to an end.

          Followup:

Hint: use a "UNION ALL", another inline view, and a SUM

select sum(sal)
 from ( select ... query above for jan-mar
        union all
        select .... query from above for mar-now )

or use decode/case in the select to determine the amount to be paid (left as an
exercise for the reader, since they didn't leave behind the link to the original
question ;)

I've been on site here as much as usual (look at the archives available from the
home page -- the counts are there).  I have been traveling, doing some keynotes,
which does obviously impact my ability to be here.  This is just sort of a
"hobby", I have my real job as well.

If I stop doing asktom, I don't know what would happen to it.  It would most
likely "go away".

      Rating: 5
Need to Correct
      the Query  October 08, 2001
Reviewer:  Asif
      Momen  from Riyadh
Hi Tom,

  The above query is just wonderful. But, you need to correct it. Instead of
using "rownum-1", you need to select only "rownum". The reason is, for the first
record "rownum-1" will return 0 and the Where Clause
to_char(to_date('&2')+rnum-1, 'DY') not in ('SAT', 'SUN') will evaluate for a
day before the range specified.

Lets take your example of 01-feb-2000 and 29-feb-2000. The Where Calsuse will
execute for 31-jan-2000. In the above case as 31-jan-2000 is neither a sunday
nor a saturday, the output no. of records is correct, but will fail if you
consider 01-may-2000 and 31-my-2000. for that matter any date range starting
from monday.

Regards,

Asif Momen

          Followup:

Yes, you are correct, thanks for pointing that out.  I've corrected the code.

      Rating:
      5
  October 08, 2001
Reviewer:
      A reader
Why are we adding -1 to rownum, and then adding one below
where rownum <= to_date('&1') - to_date('&2')+1 ), and then
subtracting -1 again below
where to_char( to_date('&2')+rnum-1, 'DY' )
                      not in ( 'SAT', 'SUN' )

          Followup:

I corrected that.

      Rating: 3
Will this work,
      one of our customer says that this is fastest one  June 04,
      2002
Reviewer:  Sonali Kelkar  from Newton, MA USA

CREATE OR REPLACE FUNCTION num_business_days(start_date IN DATE, end_date IN
DATE)
   RETURN NUMBER IS
busdays NUMBER := 0;
stDate DATE;
enDate DATE;

BEGIN

stDate := TRUNC(start_date);
enDate := TRUNC(end_date);

if enDate >= stDate
then
  -- Get the absolute date range
  busdays := enDate - stDate
        -- Now subtract the weekends
        --  this statement rounds the range to whole weeks (using
        --  TRUNC and determines the number of days in the range.
        --  then it divides by 7 to get the number of weeks, and
        --  multiplies by 2 to get the number of weekend days.
     - ((TRUNC(enDate,'D')-TRUNC(stDate,'D'))/7)*2
        -- Add one to make the range inclusive
     + 1;

  /* Adjust for ending date on a saturday */
  IF TO_CHAR(enDate,'D') = '7' THEN
    busdays := busdays - 1;
  END IF;

  /* Adjust for starting date on a sunday */
  IF TO_CHAR(stDate,'D') = '1' THEN
    busdays := busdays - 1;
  END IF;
else
   busdays := 0;
END IF;

  RETURN(busdays);
END;
/

          Followup:

Understand it,

Test it,

Validate it....

Then you will know "if it works"...  Looks similar to another example here on
asktom somewhere.

But -- as with all software -- before you use it make sure you

o Understand it
o Test it
o Validate it

      Rating: 2
It is good only
      for English Speaking databases  December 09, 2002

Reviewer:  Vladimir  from Albany, NY
The approach with 'SAT', 'SUN' will not work if you have the language other than
English. Moreover, you cannot use TO_CHAR(date, 'D') either, because some
countries start their week at Sunday, other - at Monday.
I think the approach with calendar table where you store your business days and
mark them as working days or non-working days and/or holidays would work.

          Followup:

sorry, if they cannot make the minor adjustment for their system -- well, they
probably shouldn't have a keyboard and be programming?

Anyway, I have demonstrated more then once on this site howto do this
"generically"

not in ( to_char( to_date('20000101', 'yyyymmdd' ), 'DY' ),
         to_char( to_date('20000102', 'yyyymmdd' ), 'DY' ) )

that'll work in any language, anytime.  Regardless of what day of week the week
starts on.

      Rating: 4
Asktom
      Archives  April 23, 2003
Reviewer:  Randy
      Schneider  from Warren
Hi Tom,
  Do you use something similiar to generate your dates in the archive section?
I'm trying to do something similar but not having much luck.

          Followup:

that's much easier, we just trunc(date,'w') to the week and count.

sat and sun don't have any special meaning to me other then I definitely work at
home instead of driving in ;)

what does "not much luck" mean

      Rating: 5
Is this the
      best way to do something similar...using the all_objects to drive it
      ?  April 28, 2003
Reviewer:  A Reader
       from UK
Tom,

Rather than use pl/sql loops etc. (which I know you hate if there's another
way)... I'm trying to do this:

Accounting Period table has a start and end date in it.  For April 2003 this
might be START: 28/03/03, END: 02/05/03.  I usually interpret the end date to be
23:59:59.

What I want to do is process each calendar month within that accounting period.
i.e. end up with a table with:

CALENDAR_PERIOD     ACCOUNTING_PERIOD     SOME_VALUE
MARCH 03            APRIL 03              100
APRIL 03            APRIL 03              150
MAY 03              APRIL 03              170

Based on what you've done above, I came up with the followign to give me the
dates to work with:

select ACC_MONTH, ACC_YEAR, GREATEST(ACC_FIRST_DAY_OF_PERIOD,
TRUNC(ADD_MONTHS(acc_first_day_of_period,rownum - 1),'MM')) start_of_period,
LEAST(LAST_DAY(ADD_MONTHS(acc_first_day_of_period,rownum -
1)),TRUNC(acc_last_day_of_period)) + 1 - 1/24/60/60  end_of_period
from   accounting_periods, all_objects
where  acc_month = 4 and acc_year = 2003
and    ADD_MONTHS(TRUNC(acc_first_day_of_period,'MM'),rownum - 1) between
TRUNC(acc_first_day_of_period,'MM') and TRUNC(acc_last_day_of_period,'MM')

Is this the best way to do that ?  I'm thinking I could create a view on this
across the accounting periods and use that in my sql statements to avoid PL/SQL.

Thanks in advance,

Paul

          Followup:

probably rather something like:

select
     ACC_MONTH, ACC_YEAR,
     GREATEST(ACC_FIRST_DAY_OF_PERIOD,
              TRUNC(ADD_MONTHS(acc_first_day_of_period,r),'MM'))
start_of_period,
     LEAST(LAST_DAY(ADD_MONTHS(acc_first_day_of_period,r)),
           TRUNC(acc_last_day_of_period)) + 1 - 1/24/60/60  end_of_period
  from accounting_periods,
       (select rownum-1 r from all_objects where rownum <= 24)
 where acc_month = 4
   and acc_year = 2003
   and ADD_MONTHS(TRUNC(acc_first_day_of_period,'MM'),r)
       between TRUNC(acc_first_day_of_period,'MM') and
               TRUNC(acc_last_day_of_period,'MM')

where 24 is "some reasonable" number that represents the max spread you would
see (eg: here I have 24 months as the max spread).

Rather then cartesian product with a sometimes 30,000 plus row table...

      Rating: 5
Damn......re:
      above response  April 28, 2003
Reviewer:  A
      Reader  from UK
Tom,

My idea above works fine if I restrict on a particular period but if I want the
results across several periods, it screws up because I need rownum to start from
0 for EACH accounting period.  For april it gives:

ACC_MONTH ACC_YEAR   START_OF_PERIOD END_OF_PERIOD
4         2003       29/03/2003      31/03/2003 23:59:59
4         2003       01/04/2003      30/04/2003 23:59:59
4         2003       01/05/2003      02/05/2003 23:59:59

However, if I take off the April 2003 restriction I get some weird results.  Any
ideas ?

          Followup:

you would have to give an entire example showing the good with the bad.

      Rating: 5
OK...I think I
      have the answer....but it may not be the most performant way of doing
      it...  April 28, 2003
Reviewer:  A Reader
       from UK
I know that in an accounting period, there can be at most 3 months involved - as
they are more-or-less months anyway so at most a period can start early and end
late spanning 3 months...so I use a sub-query to give me each accounting period
with 3 rownums:

SELECT ACC_MONTH,  ACC_YEAR,  GREATEST(ACC_FIRST_DAY_OF_PERIOD,
TRUNC(ADD_MONTHS(acc_first_day_of_period,  therownum - 1),  'MM'))
start_of_period,  LEAST(LAST_DAY(ADD_MONTHS(acc_first_day_of_period,
therownum)),  TRUNC(acc_last_day_of_period)) + 1 - 1/24/60/60  end_of_period
FROM   accounting_periods, (SELECT MOD(rownum,3) therownum FROM ALL_OBJECTS
WHERE rownum <= 3)
WHERE  ADD_MONTHS(TRUNC(acc_first_day_of_period,'MM'),therownum) between
TRUNC(acc_first_day_of_period,'MM') and TRUNC(acc_last_day_of_period,'MM')
ORDER BY ACC_YEAR, ACC_MONTH

Not the nicest query - any tips on making it better ?

Regards,

Paul

      Rating: 1
Query Returns 3
      instead of 2  June 04, 2003
Reviewer:
      A reader
This query returns 3 days instead of 2 days when i do a 16-14, mathemattically
speaking it should return 2
Following is the query,
Note:- Holidays is table where my Holidays are stored

    select count(*)
      from ( select rownum rnum
               from all_objects
              where rownum <= to_date('16-MAY-2003') - to_date('14-MAY-2003')+1
)
     where to_char( to_date('14-MAY-2003')+rnum-1, 'DY' )
                      not in ( 'SAT', 'SUN' )
        and       NOT EXISTS ( SELECT NULL FROM HOLIDAYS WHERE HOLIDAYDATE =
                     TRUNC(TO_DATE(NVL('16-MAY-2003',TO_DATE(SYSDATE)))+RNUM-1)
) ;

          Followup:

why should it return 2?

may 14, may 15, may 16

wed, thur, fri

3 days to me?

      Rating: 4
just put a
      solution to this too, TOM  June 04, 2003
Reviewer:
      debjit  from india
Can u tell how to select  all days between two date which are not business dates
in a single sql

          Followup:

you cannot figure that out from this?  I mean, we generate all of those days so
that in a where clause we can filter them -- it is not much more work then
removing the count(*) and replacing with the to_date in the predicate.

      Rating: 4
Go
      away?  June 04, 2003
Reviewer:  Kashif
       from Houston, TX
Tom,

Since for some reason I thought AskTom would exist always and forever, I never
thought I would need to know how to save this invaluable information. But now I
do. How do I save all these archives to a CD or something? And I sure hope that
'going away' thing happens, umm, never in my lifetime! Thanks again for the
invaluable service you provide.

Kashif

          Followup:

who said anything about going away?

there is an archive link on the home page, you can download by week.

      Rating:
      1
  July 09, 2003
Reviewer:
      A reader
The difference between 17 and 16 is 1 day the query returns 2,i am a little
amazed as how didnt anyone notice...or am i missing something.
posted below are 2 examples 1 with weekend and 1 without week end.

SQL> SELECT 17-16 FROM DUAL ;
     17-16
----------
         1

SQL> SPOOL OFF

SQL> SELECT COUNT(*)
  2         FROM ( SELECT ROWNUM-1 RNUM
  3          FROM ALL_OBJECTS
  4          WHERE ROWNUM <= TO_DATE(NVL(NULL,TO_DATE('17-JUN-2003'))) -
TO_DATE('16-JUN-2003') + 1 )
  5    WHERE TO_CHAR(TO_DATE(NVL(NULL,TO_DATE('17-JUN-2003')))+RNUM -1 , 'DY' )
NOT IN ( 'SAT', 'SUN' )
  6          AND NOT EXISTS ( SELECT NULL FROM HOLIDAYS WHERE HOLIDAYDATE =
  7              TRUNC(TO_DATE(NVL(NULL,TO_DATE('17-JUN-2003')))+RNUM-1) )
  8  /

  COUNT(*)
----------
         2

SQL> SELECT COUNT(*)
  2         FROM ( SELECT ROWNUM-1 RNUM
  3          FROM ALL_OBJECTS
  4          WHERE ROWNUM <= TO_DATE(NVL(NULL,TO_DATE('27-JUN-2003'))) -
TO_DATE('25-JUN-2003') + 1 )
  5    WHERE TO_CHAR(TO_DATE(NVL(NULL,TO_DATE('27-JUN-2003')))+RNUM -1 , 'DY' )
NOT IN ( 'SAT', 'SUN' )
  6          AND NOT EXISTS ( SELECT NULL FROM HOLIDAYS WHERE HOLIDAYDATE =
  7              TRUNC(TO_DATE(NVL(NULL,TO_DATE('27-JUN-2003')))+RNUM-1) )
  8  /

  COUNT(*)
----------
         2

SQL> spool off

          Followup:

somethings wrong -- but not the query.  I returned the number of days --
16-jun/17-jun is two days.  for the second query -- I return 3 (there is no
weekend there)

ops$tkyte@ORA920> @test 17-jun-2003 16-jun-2003
ops$tkyte@ORA920>   select rownum rnum, to_date( '&2')+rownum-1
  2             from all_objects
  3            where rownum <= to_date('&1') - to_date('&2')+1
  4  /
old   1:   select rownum rnum, to_date( '&2')+rownum-1
new   1:   select rownum rnum, to_date( '16-jun-2003')+rownum-1
old   3:           where rownum <= to_date('&1') - to_date('&2')+1
new   3:           where rownum <= to_date('17-jun-2003') -
to_date('16-jun-2003')+1

      RNUM TO_DATE('
---------- ---------
         1 16-JUN-03
         2 17-JUN-03

ops$tkyte@ORA920>
ops$tkyte@ORA920> select count(*)
  2    from ( select rownum rnum
  3             from all_objects
  4            where rownum <= to_date('&1') - to_date('&2')+1 )
  5   where to_char( to_date('&2')+rnum-1, 'DY' )
  6                   not in ( 'SAT', 'SUN' );
old   4:           where rownum <= to_date('&1') - to_date('&2')+1 )
new   4:           where rownum <= to_date('17-jun-2003') -
to_date('16-jun-2003')+1 )
old   5:  where to_char( to_date('&2')+rnum-1, 'DY' )
new   5:  where to_char( to_date('16-jun-2003')+rnum-1, 'DY' )

  COUNT(*)
----------
         2

ops$tkyte@ORA920> @test 27-jun-2003 25-jun-2003
ops$tkyte@ORA920>   select rownum rnum, to_date( '&2')+rownum-1
  2             from all_objects
  3            where rownum <= to_date('&1') - to_date('&2')+1
  4  /
old   1:   select rownum rnum, to_date( '&2')+rownum-1
new   1:   select rownum rnum, to_date( '25-jun-2003')+rownum-1
old   3:           where rownum <= to_date('&1') - to_date('&2')+1
new   3:           where rownum <= to_date('27-jun-2003') -
to_date('25-jun-2003')+1

      RNUM TO_DATE('
---------- ---------
         1 25-JUN-03
         2 26-JUN-03
         3 27-JUN-03

ops$tkyte@ORA920>
ops$tkyte@ORA920> select count(*)
  2    from ( select rownum rnum
  3             from all_objects
  4            where rownum <= to_date('&1') - to_date('&2')+1 )
  5   where to_char( to_date('&2')+rnum-1, 'DY' )
  6                   not in ( 'SAT', 'SUN' );
old   4:           where rownum <= to_date('&1') - to_date('&2')+1 )
new   4:           where rownum <= to_date('27-jun-2003') -
to_date('25-jun-2003')+1 )
old   5:  where to_char( to_date('&2')+rnum-1, 'DY' )
new   5:  where to_char( to_date('25-jun-2003')+rnum-1, 'DY' )

  COUNT(*)
----------
         3

ops$tkyte@ORA920>

Perhaps you loaded a date into the holidays table.

If you don't like my results, simply subtract 1.

      Rating:
      1
  July 10, 2003
Reviewer:
      A reader
Hi Tom,
subtracting one doesn't seem to resolve the problem,thats what i had done to
initially resolve the problem, but the users came back with this week end
scenario which i hadnt thought about.
Below are the eg, on days other then the week end it gives 0 days but on week
ends it gives 1.
Can you pls let me know if there is something i am doing wrong, i have posted
the query and the result below.

Thanks,

SQL> SELECT COUNT(*) - 1
  2             FROM ( SELECT ROWNUM-1 RNUM
  3              FROM ALL_OBJECTS
  4        WHERE ROWNUM <= TO_DATE(NVL(NULL,TO_DATE('17-JUN-2003'))) -
TO_DATE('16-JUN-2003') + 1 )
  5        WHERE TO_CHAR(TO_DATE(NVL(NULL,TO_DATE('16-JUN-2003')))+RNUM -1 ,
'DY' )
  6     NOT IN ( 'SAT', 'SUN' )
  7              AND NOT EXISTS ( SELECT NULL FROM HOLIDAYS WHERE HOLIDAYDATE =
  8                  TRUNC(TO_DATE(NVL(NULL,TO_DATE('16-JUN-2003')))+RNUM-1) )
  9
SQL>
SQL> /

COUNT(*)-1
----------
         0

SQL> SELECT COUNT(*) - 1
  2             FROM ( SELECT ROWNUM-1 RNUM
  3              FROM ALL_OBJECTS
  4        WHERE ROWNUM <= TO_DATE(NVL(NULL,TO_DATE('16-JUN-2003'))) -
TO_DATE('13-JUN-2003') + 1 )
  5        WHERE TO_CHAR(TO_DATE(NVL(NULL,TO_DATE('13-JUN-2003')))+RNUM -1 ,
'DY' )
  6     NOT IN ( 'SAT', 'SUN' )
  7              AND NOT EXISTS ( SELECT NULL FROM HOLIDAYS WHERE HOLIDAYDATE =
  8                  TRUNC(TO_DATE(NVL(NULL,TO_DATE('13-JUN-2003')))+RNUM-1) )
  9
SQL>
SQL> /

COUNT(*)-1
----------
         1

SQL> SELECT * FROM HOLIDAYS ;

HOLIDAYD
--------
01/01/02
02/01/02
06/02/02
29/03/02
01/04/02
25/04/02
03/06/02
28/10/02
25/12/02
26/12/02

10 rows selected.

SQL> SELECT 17-16 FROM DUAL;

     17-16
----------
         1

SQL> SPOOL OFF

          Followup:

something is not adding up here "reader"

you holiday table contains nothing relevant.  all of the data is for 2002, your
input dates are 2003.

If you run the queries:

ops$tkyte@ORA920>   select rownum rnum, to_date( '&2')+rownum-1
  2             from all_objects
  3            where rownum <= to_date('&1') - to_date('&2')+1
  4  /
old   1:   select rownum rnum, to_date( '&2')+rownum-1
new   1:   select rownum rnum, to_date( '16-jun-2003')+rownum-1
old   3:           where rownum <= to_date('&1') - to_date('&2')+1
new   3:           where rownum <= to_date('17-jun-2003') -
to_date('16-jun-2003')+1

      RNUM TO_DATE('16
---------- -----------
         1 16-jun-2003
         2 17-jun-2003

that one shows the dates that were generated

ops$tkyte@ORA920>
ops$tkyte@ORA920> select count(*)
  2    from ( select rownum rnum
  3             from all_objects
  4            where rownum <= to_date('&1') - to_date('&2')+1 )
  5   where to_char( to_date('&2')+rnum-1, 'DY' )
  6                   not in ( 'SAT', 'SUN' )
  7     AND NOT EXISTS ( SELECT NULL FROM HOLIDAYS WHERE HOLIDAYDATE =
  8            TRUNC(TO_DATE(NVL(NULL,TO_DATE('&2')))+RNUM-1) )
  9  /
old   4:           where rownum <= to_date('&1') - to_date('&2')+1 )
new   4:           where rownum <= to_date('17-jun-2003') -
to_date('16-jun-2003')+1 )
old   5:  where to_char( to_date('&2')+rnum-1, 'DY' )
new   5:  where to_char( to_date('16-jun-2003')+rnum-1, 'DY' )
old   8:           TRUNC(TO_DATE(NVL(NULL,TO_DATE('&2')))+RNUM-1) )
new   8:           TRUNC(TO_DATE(NVL(NULL,TO_DATE('16-jun-2003')))+RNUM-1) )

  COUNT(*)
----------
         2

and that gives the counts

You have a mistake somewhere in your test case here.  I'm not making this up --
look at my results, look at yours.  You scare me a bit with the reliance on
default date formats.  perhaps that be your bug -- I see alot of
nvl,to_date,trunc going on that is not necessary.  Most likely your bug
introduced by reliance in implicit date formats and not having the default date
format in use (you seem to have dd/mm/yy)

try using MY original queries.
add to them EXPLICIT to_dates -- using dd-mon-yyyy as the format mask.

Then post the results if you believe them to be "wrong" -- but use both queries
ok -- so we can see the data that is generated AND the result of counting such
data.

      Rating: 3
on a
      rownum  July 10, 2003
Reviewer:  Vj
       from in
In our webapplicabtion we have an lookup. We show 25 records each...there was a
debate on which is the right way to get those 25 records..

i suggested

SELECT * FROM (
   SELECT p.*, ROWNUM rnum
           FROM (SELECT * FROM <TABLENAME> ) p
          WHERE ROWNUM < <ENDROW>
) WHERE rnum >= <STARTROW> AND MYCOL1 = 'X' AND MYCOL2 = 'YES'

this shown an better plan, as compared to using MINUS Etc..but do you think i
get a much better plan using analytical functions...because the table which we
would be hitting would have some 1,000,000 rows !!!

Rgds

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064
I believe you have the predicate  AND MYCOL1 = 'X' AND MYCOL2 = 'YES' in the
wrong place.  but see that link, you are on the right path.

      Rating:
      1
  July 15, 2003
Reviewer:
      A reader
Hi Tom,
There is nothing wrong with either of the queries, the problem and solution are
diff, i am looking for the difference and you are giving me the count, is there
way i can get a difference between 2 business days.

          Followup:

*there is obviously something wrong with your queries.*

obviously wrong

Use mine, as is, as presented.

Yours has a BUG
Mine, does not.

You are totally relying on default date formats, implicit conversions -- nasty
stuff.

Proof that YOUR query is wrong:

ops$tkyte@ORA920LAP> SELECT COUNT(*)
  2             FROM ( SELECT ROWNUM-1 RNUM
  3              FROM ALL_OBJECTS
  4        WHERE ROWNUM <= TO_DATE(NVL(NULL,TO_DATE('17-JUN-2003'))) -
TO_DATE('16-JUN-2003') + 1 )
  5        WHERE TO_CHAR(TO_DATE(NVL(NULL,TO_DATE('16-JUN-2003')))+RNUM -1 ,
'DY' )
  6     NOT IN ( 'SAT', 'SUN' )
  7              AND NOT EXISTS ( SELECT NULL FROM HOLIDAYS WHERE HOLIDAYDATE =
  8                  TRUNC(TO_DATE(NVL(NULL,TO_DATE('16-JUN-2003')))+RNUM-1) )
  9  /

  COUNT(*)
----------
         1

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select count(*)
  2    from ( select rownum rnum
  3             from all_objects
  4            where rownum <= to_date('17-jun-2003') - to_date('16-jun-2003')+1
)
  5   where to_char( to_date('16-jun-2003')+rnum-1, 'DY' )
  6                   not in ( 'SAT', 'SUN' )
  7     AND NOT EXISTS ( SELECT NULL FROM HOLIDAYS WHERE HOLIDAYDATE =
  8            TRUNC(TO_DATE(NVL(NULL,TO_DATE('16-jun-2003')))+RNUM-1) )
  9  /

  COUNT(*)
----------
         2

see, different answer, it is not my query -- stop using YOURS and use MINE

that is how to get the difference between 2 business days.

I honestly don't know how else to say this -- you have a bug in your query,
stop using your query -- I'm baffled at constructs like this:

TRUNC(TO_DATE(NVL(NULL,TO_DATE('13-JUN-2003')))+RNUM-1)

no clues why you are going to such convulted lengths to perform implict and
explicit conversions

      Rating: 1
Replacing Null
      with sysdate  July 16, 2003
Reviewer:
      A reader
Hi tom,
The only thing i want different from your query is to be able to replace Null
with the sysdate, As per requirement it may be possible that the todate or the
fromdate may be Null in which case the system needs to use the sysdate, using
nvl function is the only difference.

          Followup:

so, you have a requirement to default the dates to null.

so, what was the point of your code???  it doesn't do that.

this is

select count(*)-1
  from ( select rownum rnum
           from all_objects
          where rownum <= nvl(to_date('&1'),sysdate) -
nvl(to_date('&2'),sysdate)+1 )
 where to_char( nvl(to_date('&2'),sysdate)+rnum-1, 'DY' )
           not in ( 'SAT', 'SUN' )
   and not exists
   ( select null from holidays where holidaydate =
             trunc(nvl(to_date('&2'),sysdate)+rnum-1) )
/

is it very very simple.

Now, your homework -- explain to us all what this does:

TO_DATE(NVL(NULL,TO_DATE('17-JUN-2003')))

that is your code, I can tell you -- but can you tell us, do you understand what
you coded there.  did you figure out the error you made, why your query returns
the wrong answer?

      Rating:
      1
  July 16, 2003
Reviewer:
      A reader
Hi tom,
My idea is to get the difference and the difference between 17Jun and 16Jun is
one day, Using ur query it gives me 2 days.

          Followup:

seems SIMPLE enought to me

"subtract one"

(oh wait, i already said that before.....)

      Rating:
      5
Genius  August 19, 2003
Reviewer:
      Mike  from England
I'd just like to thank you for saving me a few hours of mental strain.  I'll
toast your name while I'm down the boozer when I should be in the office coming
up with a solution to this problem.

Cheers!

      Rating: 4
Great, but how
      do I....  September 08, 2003
Reviewer:  Nick
       from NYC
Hi Tom,

I find your solution very helpful, but I'm having trouble with the
implementaion.
I have a requirement to count records from a status-history table where the
status transition takes X business days:

create table STATUS_HISTORY
(
  STATUS_ID    VARCHAR2(12) not null,
  APP_ID       VARCHAR2(12),
  STATUS_TYPE  VARCHAR2(10),
  STATUS       VARCHAR2(30),
  START_DATE   DATE,
  END_DATE     DATE
) tablespace tables;

create index IDX_NAMESTATUSTYPE on STATUS_HISTORY(STATUS,STATUS_TYPE,START_DATE)
tablespace tables;

create index IDX_NAMETYPE on STATUS_HISTORY (APP_ID,STATUS_TYPE) tablespace
tables;

create unique index PK_NAMESTATUS on STATUS_HISTORY (STATUS_ID) tablespace
tables;
alter table STATUS_HISTORY add primary key (STATUS_ID);

I created a function using your busuness day calculator:
---
create or replace function NUM_BUSINESS_DAYS (START_DATE IN DATE, END_DATE IN
DATE) return number as
n number;
begin
    if( start_date is null or end_date is null ) then
        n := NULL;
    else
        select count(*) into n
              from ( select rownum rnum
                       from all_objects
                      where rownum <= END_DATE-START_DATE +1 )
             where to_char( START_DATE+rnum-1, 'DY' )
                              not in ( 'SAT', 'SUN' );
    end if;

return n;
end;
---

The requirement is to give the percentage of YTD (actually Year to
End-Of-Previous-Month) applications (APP_ID) that started in a certain STATUS
and moved to the next STATUS in 2 days or less . Briefly, the STATUS_TYPE has to
be 'Program' and the application has to start in STATUS='M39' and move to 'M52'
within 2 days.

Here is what I came up with using the function:

SELECT n.total,d.total,DECODE (n.total, 0, 0, n.total / d.total * 100)
     FROM (SELECT COUNT (DISTINCT (s.app_id)) total
             FROM status_history s
            WHERE s.status_type = 'Program'
              AND s.status = 'M39'
              AND TRUNC (s.start_date, 'YY') = TRUNC (ADD_MONTHS (SYSDATE, -1),
'YY')) d,
        (SELECT COUNT (DISTINCT (strt.app_id)) total
              FROM status_history strt
             WHERE strt.status_type = 'Program'
               AND strt.status = 'M39'
               AND strt.start_date BETWEEN
            TRUNC (ADD_MONTHS (SYSDATE, -1), 'YY') AND TRUNC (ADD_MONTHS
(SYSDATE, -1),'MM')
               AND EXISTS (
                      SELECT NULL
                        FROM status_history end
                       WHERE end.app_id = strt.app_id
                         AND end.status = 'M52'
                         AND end.status_type = 'Program'
                         AND end.start_date >= strt.start_date
                         AND NUM_BUSINESS_DAYS (strt.start_date, end.start_date)
                                BETWEEN 0
                                    AND 2) ) n;

I'd now like to test this method against a straight SQL implementation (no
function).

The question (finally) is how can I remove the function and stick your original
query in there?

Thanks.

          Followup:

tell you what -- this looks like a new question.  I've got to cut back on taking
new -- BIG -- questions here.  takes too much time away from answering NEW
questions.

      Rating: 3
An Alternate
      Approach  October 10, 2003
Reviewer:  Andrew
      Rye  from Chicago, IL
Hi Tom -

First time writer and big fan of yours.  This thread caught my interest, and I
wanted to suggest an approach similar to one mentioned above that seems to have
significantly better performance than the initial suggestion (not so dependent
on interval length).

Not enough room to post the entire script here, but I've cut out the more
significant pieces and pasted below.  See embedded comments for further
explanation, but the basic idea is that there's no need to iterate through each
day in the interval if you only want to count the business days.  For an
interval of 1 year, my function shaves a couple of orders of magnitude off the
performance of the initial solution.  I've verified that the calculation is
equivalent, but left off those details for brevity's sake as well.  I also have
a "SQL Built-Ins"-only expression version of my function, but the performance
was virtually identical to that of the stored user-defined function, so again
left off for space reasons.

/*
|| holiday_table holds the dates to be excluded from the interval calculation
*/
CREATE TABLE holiday_table
 ( holiday_date DATE )
/*
|| Inserted 12/31/02, 1/1/03, 2/17/03, 5/26/03, 7/4/03, 9/1/03, 11/27/03,
|| 11/28/03, 12/24/03, 12/25/03, 12/31/03, and 1/1/04 into holiday_table
*/
CREATE TABLE test_interval
 ( start_date  DATE,
   end_date    DATE
 )
/*
|| Inserted 500 rows into test_interval, with dates ranging from 12/02 into 5/04
|| Tested intervals ranged from 1 day apart to beyond 1 year apart
*/
/*
|| The PL/SQL implementation of the business day logic
*/
CREATE OR REPLACE FUNCTION busdays_between
 ( p_start_dt DATE,
   p_end_dt   DATE )
RETURN NUMBER
IS
   cln_start_dt DATE        := TRUNC( p_start_dt );
   cln_end_dt   DATE        := TRUNC( p_end_dt );
   first_dt     DATE;
   last_dt      DATE;
   orientation  PLS_INTEGER := 1;
   first_dowk   PLS_INTEGER;
   count_first  PLS_INTEGER := 1;
   last_dowk    PLS_INTEGER;
   wks_btwn     PLS_INTEGER;
   sat_adjst    PLS_INTEGER := 0;
   count_hdays  PLS_INTEGER := 0;
   return_value NUMBER;
BEGIN
   IF cln_start_dt <= cln_end_dt
   THEN
      first_dt := cln_start_dt;
      last_dt := cln_end_dt;
   ELSE
      first_dt := cln_end_dt;
      last_dt := cln_start_dt;
      orientation := -1;
   END IF;
   SELECT COUNT( * )
     INTO count_hdays
     FROM holiday_table
    WHERE holiday_date BETWEEN first_dt
                           AND last_dt;
   first_dowk := TO_NUMBER( TO_CHAR( first_dt, 'D' ) );
   last_dowk := TO_NUMBER( TO_CHAR( last_dt, 'D' ) );
   wks_btwn := TRUNC( ( last_dt - first_dt ) / 7 );
   IF first_dowk > last_dowk
   THEN
      wks_btwn := wks_btwn + 1;
   END IF;
   IF first_dowk = 7
   THEN
      sat_adjst := 1;
      count_first := 0;
   ELSIF first_dowk = 1
   THEN
      count_first := 0;
   ELSE
      NULL;
   END IF;
   IF last_dowk = 7
   THEN
      sat_adjst := sat_adjst - 1;
   END IF;
   return_value := ( ( wks_btwn * 5 ) +
                     ( last_dowk - first_dowk + count_first ) +
                     sat_adjst - count_hdays
                   ) * orientation;
   RETURN return_value;
END; /* busdays_between */
/*
|| PL/SQL block to loop through the test_interval table and test performance
using
|| discussed method in the thread
*/
DECLARE
   next_interval NUMBER;
   max_interval  NUMBER := 0;
BEGIN
   FOR date_rec IN
    ( SELECT *
        FROM test_interval )
   LOOP
      SELECT COUNT( * )
        INTO next_interval
        FROM ( SELECT ROWNUM - 1 rnum
                 FROM ALL_OBJECTS
                WHERE ROWNUM <= date_rec.end_date - date_rec.start_date + 1
             )
       WHERE TO_CHAR( date_rec.start_date + rnum, 'DY' ) NOT IN ( 'SAT', 'SUN' )
         AND NOT EXISTS
             ( SELECT NULL
                 FROM holiday_table
                WHERE holiday_date = TRUNC( date_rec.start_date + rnum )
             );
      max_interval := GREATEST( next_interval, max_interval );
   END LOOP;
   DBMS_OUTPUT.PUT_LINE( TO_CHAR( max_interval ) );
END;
/*
344

Elapsed: 00:00:27.89 */
/*
|| Same basic PL/SQL block as above, using function instead
*/
DECLARE
   next_interval NUMBER;
   max_interval  NUMBER := 0;
BEGIN
   FOR date_rec IN
    ( SELECT *
        FROM test_interval )
   LOOP
      SELECT busdays_between( date_rec.start_date,
                              date_rec.end_date  )
        INTO next_interval
        FROM DUAL;
      max_interval := GREATEST( next_interval, max_interval );
   END LOOP;
   DBMS_OUTPUT.PUT_LINE( TO_CHAR( max_interval ) );
END;
/*
344

Elapsed: 00:00:00.21 */
/*
|| Showing dependence of performance upon interval length
*/
DECLARE
   /*
   || One day interval
   */
   date1         DATE   := TO_DATE( '030102', 'YYMMDD' );
   date2         DATE   := TO_DATE( '030103', 'YYMMDD' );
   next_interval NUMBER;
BEGIN
   FOR i IN 1..1000
   LOOP
      SELECT busdays_between( date1, date2 )
        INTO next_interval
        FROM DUAL;
   END LOOP;
END;
/* Elapsed: 00:00:00.31 */
DECLARE
   /*
   || One year interval
   */
   date1         DATE   := TO_DATE( '030102', 'YYMMDD' );
   date2         DATE   := TO_DATE( '040102', 'YYMMDD' );
   next_interval NUMBER;
BEGIN
   FOR i IN 1..1000
   LOOP
      SELECT busdays_between( date1, date2 )
        INTO next_interval
        FROM DUAL;
   END LOOP;
END;
/* Elapsed: 00:00:00.41 */
DECLARE
   /*
   || One day interval
   */
   date1         DATE   := TO_DATE( '030102', 'YYMMDD' );
   date2         DATE   := TO_DATE( '030103', 'YYMMDD' );
   next_interval NUMBER;
BEGIN
   FOR i IN 1..1000
   LOOP
      SELECT COUNT( * )
        INTO next_interval
        FROM ( SELECT ROWNUM - 1 rnum
                 FROM ALL_OBJECTS
                WHERE ROWNUM <= date2 - date1 + 1
             )
       WHERE TO_CHAR( date1 + rnum, 'DY' ) NOT IN ( 'SAT', 'SUN' )
         AND NOT EXISTS
             ( SELECT NULL
                 FROM holiday_table
                WHERE holiday_date = TRUNC( date1 + rnum )
             );
   END LOOP;
END;
/* Elapsed: 00:00:01.82 */
DECLARE
   /*
   || One year interval
   */
   date1         DATE   := TO_DATE( '030102', 'YYMMDD' );
   date2         DATE   := TO_DATE( '040102', 'YYMMDD' );
   next_interval NUMBER;
BEGIN
   FOR i IN 1..1000
   LOOP
      SELECT COUNT( * )
        INTO next_interval
        FROM ( SELECT ROWNUM - 1 rnum
                 FROM ALL_OBJECTS
                WHERE ROWNUM <= date2 - date1 + 1
             )
       WHERE TO_CHAR( date1 + rnum, 'DY' ) NOT IN ( 'SAT', 'SUN' )
         AND NOT EXISTS
             ( SELECT NULL
                 FROM holiday_table
                WHERE holiday_date = TRUNC( date1 + rnum )
             );
   END LOOP;
END;
/* Elapsed: 00:01:85.83 */

      Rating:
      4
  November 18, 2003
Reviewer:  vj
       from in
SELECT
      REPDATE,CLOSEDDATE,
      TRUNC(CLOSEDDATE)-TRUNC(REPDATE)+1 TOTDAYS,
       CRITICALITY
FROM ISSUE
WHERE
       TISSUE = 'Defect'

This is my select statement..

REPORTEDDATE    CLOSEDATE    TOTDAYS    CRITICALITY
05-Feb-02    05-Feb-02    1    High
08-Feb-02    08-Feb-02    1    Medium
30-Jan-02    09-Feb-02    11    Medium -->i want 9
09-Feb-02    09-Feb-02    1    Medium
11-Feb-02    11-Feb-02    1    Medium
11-Feb-02    11-Feb-02    1    Medium
11-Feb-02    11-Feb-02    1    Medium
28-Sep-02    28-Sep-02    1    Medium
30-Sep-02    30-Sep-02    1    Medium
30-Sep-02    03-Oct-02    4    Medium
30-Sep-02    03-Oct-02    4    Medium
30-Sep-02    30-Sep-02    1    Medium
30-Sep-02    04-Oct-02    5    Medium

This is the output...when i get the totdays i wanted to exclude the sat and
sun..
Rgds

          Followup:

great -- that is exactly what this page shows how to do??  just write a function
that does the logic?

November 21, 2003 Ramkumar -- Thanks for the question regarding
      "Random Number Generation", version version 8.1.5

      You Asked
Hi Tom,I would like to know whether Oracle can generate Random
Numbers and store in the database.My specific requirement is I
would like to have a table having a field which holds randomly
generated yet unique numbers.Can this be done in Oracle?
Thanks and Regards,
Ram.

      and we said...
Randomly generate and unique is sort of an oxymoron.

What I've done in the past is to combine 3 or so numbers
together to achieve this.  One that I use alot is:

sequence_name.nextval || to_char( sysdate, 'DDDSSSSS' )

You can do the same thing with a random number (replace the
to_char(sysdate) with a random number of a FIXED length -- eg:

sequence_name.nextval || to_char( random.rand,'fm00000' )

See

http://osi.oracle.com/~tkyte/Misc/Random.html
for howto generate a random number.

You need to combine a sequence or some other UNIQUE thing with
the random number to get uniqueness.

Another option is to use the sys_guid() function available in
8i.  For example:

ops$tkyte@ORA8I.WORLD> create table t as select sys_guid() u_id
from dual;

Table created.

ops$tkyte@ORA8I.WORLD> desc t;
 Name                          Null?    Type
 ----------------------------- -------- --------------------
 U_ID                                   RAW(16)

ops$tkyte@ORA8I.WORLD> select * from t;

U_ID
--------------------------------
722EF40A77F1386AE034080020A767E0

sys_guid() generates a 16byte globally unique key that is
non-sequential.

If you need it as a number, it can be converted:

ops$tkyte@ORA8I.WORLD> set numformat
999999999999999999999999999999999999999999999999

ops$tkyte@ORA8I.WORLD> select to_number(
'722EF40A77F1386AE034080020A767E0',
  'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' ) from dual;

ops$tkyte@ORA8I.WORLD> /

TO_NUMBER('722EF40A77F1386AE034080020A767E0','XXXXXXXXXXXXXX
------------------------------------------------------------
                     151775786912318261447473874650479945696

ops$tkyte@ORA8I.WORLD> select sys_guid(),
  2  to_number(sys_guid(),'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' )
from dual;

SYS_GUID()
--------------------------------
TO_NUMBER(SYS_GUID(),'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX')
--------------------------------------------------------
722EF40A77F2386AE034080020A767E0
                 151775786912320679299113103908829358048

ops$tkyte@ORA8I.WORLD> /

SYS_GUID()
--------------------------------
TO_NUMBER(SYS_GUID(),'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX')
--------------------------------------------------------
722EF40A77F4386AE034080020A767E0
                 151775786912323097150752333167178770400

Review &
      Followup

      Rating: 4
Great
      Help  July 06, 2001
Reviewer:  Pichaimani
      Balasubramanian  from San Jose, CA USA
Hi Tom,
   Thanks a lot. Using 'DDDSSSS' format for generating unique value in versions
prior to 8i is a great idea.

      Rating:
      5
  January 02, 2002
Reviewer:  Branka

      Rating: 5
getting
      error  July 22, 2002
Reviewer:  Ravi Kumar
       from Chicago, IL, USA
Hi Tom,

I ran all the scripts that you mentioned in the above link. But I am getting
error when using it in sql.

> select dbms_random.random from dual;
select dbms_random.random from dual
*
ERROR at line 1:
ORA-06571: Function RANDOM does not guarantee not to update database

what is the usage of random? I am on Oracle 8.0.5

Thanks,
Ravi.

          Followup:

dbms_random wasn't selectable from SQL in 805.  It is in 8i.  You can use my
random package until then.

      Rating: 3
Still Get
      Error....  July 23, 2002
Reviewer:  Riaz
      Shahid  from PRAL, Islamabad, Pakistan
Hi Tom!

I am using 8.1.5. I tried to select random no but got the following error:

SQL> select dbms_random.random from dual;
select dbms_random.random from dual
                   *
ERROR at line 1:
ORA-00904: invalid column name

Why it is giving me error?

Regards

          Followup:

read the link above.  As long as you are in 815, you'll get this error.  As I
said, use the random package written by me available via the link above, it is
selectable in SQL in 815

      Rating: 4
One more
      Question ....  July 30, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Lahore, Pakistan
Can we generate a random number between m and n (say between 1 and 2) ???

Riaz

          Followup:

dbms_random can do that yes.

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96612/d_random.htm#998100

      Rating: 1
Sorry
      But.....  July 30, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Lahore, Pakistan
Sorry to say but i couln't find any information regarding my question. Can you
please give me an example to how to do that ?

Thanks

          Followup:

doh, sorry about that -- i just realized the docs are a tad "lacking"

ops$tkyte@ORA920> select text from all_source where name = 'DBMS_RANDOM' and
type= 'PACKAGE' order by line;

TEXT
---------------------------------------------------------------------------------
--------------------------------------------------
PACKAGE dbms_random AS

    ------------
    --  OVERVIEW
    --
    --  This package should be installed as SYS.  It generates a sequence of
    --  random 38-digit Oracle numbers.  The expected length of the sequence
    --  is about power(10,28), which is hopefully long enough.
    --
    --------
    --  USAGE
    --
    --  This is a random number generator.  Do not use for cryptography.
    --  For more options the cryptographic toolkit should be used.
    --
    --  By default, the package is initialized with the current user
    --  name, current time down to the second, and the current session.
    --
    --  If this package is seeded twice with the same seed, then accessed
    --  in the same way, it will produce the same results in both cases.
    --
    --------
    --  EXAMPLES
    --
    --  To initialize or reset the generator, call the seed procedure as in:
    --      execute dbms_random.seed(12345678);
    --    or
    --      execute dbms_random.seed(TO_CHAR(SYSDATE,'MM-DD-YYYY HH24:MI:SS'));
    --  To get the random number, simply call the function, e.g.
    --      my_random_number BINARY_INTEGER;
    --      my_random_number := dbms_random.random;
    --    or
    --      my_random_real NUMBER;
    --      my_random_real := dbms_random.value;
    --  To use in SQL statements:
    --      select dbms_random.value from dual;
    --      insert into a values (dbms_random.value);
    --      variable x NUMBER;
    --      execute :x := dbms_random.value;
    --      update a set a2=a2+1 where a1 < :x;

    -- Seed with a binary integer
    PROCEDURE seed(val IN BINARY_INTEGER);
    PRAGMA restrict_references (seed, WNDS);

    -- Seed with a string (up to length 2000)
    PROCEDURE seed(val IN VARCHAR2);
    PRAGMA restrict_references (seed, WNDS);

    -- Get a random 38-digit precision number, 0.0 <= value < 1.0
    FUNCTION value RETURN NUMBER;
    PRAGMA restrict_references (value, WNDS);

    -- get a random Oracle number x, low <= x < high
    FUNCTION value (low IN NUMBER, high IN NUMBER) RETURN NUMBER;
    PRAGMA restrict_references (value, WNDS);

    -- get a random number from a normal distribution
    FUNCTION normal RETURN NUMBER;
    PRAGMA restrict_references (normal, WNDS);

    -- get a random string
    FUNCTION string (opt char, len NUMBER)
          /* "opt" specifies that the returned string may contain:
             'u','U'  :  upper case alpha characters only
             'l','L'  :  lower case alpha characters only
             'a','A'  :  alpha characters only (mixed case)
             'x','X'  :  any alpha-numeric characters (upper)
             'p','P'  :  any printable characters
          */
        RETURN VARCHAR2;  -- string of <len> characters (max 60)
    PRAGMA restrict_references (string, WNDS);

    -- Obsolete, just calls seed(val)
    PROCEDURE initialize(val IN BINARY_INTEGER);
    PRAGMA restrict_references (initialize, WNDS);

    -- Obsolete, get integer in ( -power(2,31) <= random < power(2,31) )
    FUNCTION random RETURN BINARY_INTEGER;
    PRAGMA restrict_references (random, WNDS);

    -- Obsolete, does nothing
    PROCEDURE terminate;

    TYPE num_array IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
END dbms_random;

86 rows selected.

so,

ops$tkyte@ORA920> select dbms_random.value(1,2) from all_users;

DBMS_RANDOM.VALUE(1,2)
----------------------
            1.19292441
            1.66853822
            1.04933897
            1.16628319
             1.0932129
            1.14666053
              1.712686
            1.39888293

......

is what you are looking for.

      Rating: 4
For
      Riaz  July 30, 2003
Reviewer:  Paul
       from UK
Come on, it's not rocket science

select mod(abs(dbms_random.random),m)+n from dual;

will produce a random number between n and (m+n-1)

e.g. mod(abs(dbms_random.random),4)+3 will produce a random number between 3 and
6.

      Rating: 5
Try
      this...  July 30, 2003
Reviewer:  Jose Cleto
       from Dominican Republic
Riaz, ty this one...

Using tom's RANDOM :

Select ( rand * ( Last_Value - First_Value) + 1 ) + First_Value
from dual;

Being First and Last values the ones tha you want to use like lower and higher
limits.

Hope it helps....

BTW: Using inline wiews those values could come from a table!!!

      Rating: 2
For
      Paul...  July 30, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Lahore, Pakistan
Dear Paul !

Thanks a lot. But i am still having problems. Consider:

  1* select mod(abs(dbms_random.random),4)+3 from dua
stax@STARR.LHR> /

MOD(ABS(DBMS_RANDOM.RANDOM),4)
==============================
                             4

Elapsed: 00:00:00.88
stax@STARR.LHR> /

MOD(ABS(DBMS_RANDOM.RANDOM),4)
==============================
                             6

Elapsed: 00:00:00.03
stax@STARR.LHR> /

MOD(ABS(DBMS_RANDOM.RANDOM),4)
==============================
                             4

Elapsed: 00:00:00.88
stax@STARR.LHR> /

MOD(ABS(DBMS_RANDOM.RANDOM),4)
==============================
                             3

Elapsed: 00:00:00.88
stax@STARR.LHR> /

MOD(ABS(DBMS_RANDOM.RANDOM),4)
==============================
                             5

Elapsed: 00:00:00.88
stax@STARR.LHR> /

MOD(ABS(DBMS_RANDOM.RANDOM),4)
==============================
                             5

Elapsed: 00:00:00.87
stax@STARR.LHR> /

MOD(ABS(DBMS_RANDOM.RANDOM),4)
==============================
                             5

Elapsed: 00:00:00.87

I tried to get a random number between 3 & 6 (and u know there are infinite nos.
betweeen 3 & 6 ???). But your code always gives me Whole no.

Riaz

      Rating: 5
Simple & to
      the point Answer...  July 30, 2003
Reviewer:
      Riaz Shahid  from PRAL, Lahore, Pakistan
Thanks a lot tom.

      Rating: 3
For Riaz Shahid
      from Pakistan   July 30, 2003
Reviewer:
      Prince  from Pakistan
SELECT dbms_random.value(m,n)
FROM   dual;

Where m and n could be any number. It works fine brother.

      Rating:
      4
Riaz  July 30, 2003
Reviewer:  Jim
       from Portland, OR
The mod function returns just integers.  mod is the iteger remainder of a
division of 2 numbers.  So that is why you are only returning integers.  Also
since we have limited precision in Oracle, albeit quite good, there are not an
infinite set of numbers between 3 and 4.  Theoretically there are, but since
Oracle is limited to 38 digits of precision there are a limited number of
numbers between 3 and 4.

      Rating: 5
Random number
      generation with bias  November 18, 2003
Reviewer:
      Hector  from uk
Hi Tom

Can you tell me if it is possible to generate random numbers that follow a
distribution curve.
(Yeah I know that it wouldn't be truly random)

Let's assume that I have a 1000 row table that has a column that contains 50
distinct values 1 - 50

Using the old mod(rownum,<num>) trick, I can easily get an even spread of data
into the column

Example
-------
create table my_table(
my_number number(2));

insert into mytable (
select mod(rownum,50) + 1
from all_objects
where rownum <= 1000);

This creates 20 occurrences of each value.

How can I manufacture the data so that I can pick a specific value that would be
at the peak of a distribution curve.

Say for example that I picked the value 30, the next most popular values should
then be 29 and 31, then 28 and 32 etc.

Also, how could I vary the extremes of the data

One time I might want  (roughly of course)

the value 30 occurrs 500 times
          29 and 31  60 times
          28 and 32  45 times
               etc

and the next time I might want

the value 30 occurrs 50 times
          29 and 31  47 times
          28 and 32  43 times
               etc
(9.2 by the way)

          Followup:

dbms_random.normal will return a "random" "normally distributed" number between
-1 and +1 (most at 0).

You can use this to generate that set in a normal distribution.

but to get the exact proportions you want -- don't know of anything "out of the
box" -- to get that level of control.

November 21, 2003 tejal -- Thanks for the question regarding
      "controlfile", version 7.3.2

      You Asked
Tom,
I am little bit confused why this following command is behaving
in a different manner.
"ALTER DATABASE BACKUP CONTROLFILE TO TRACE".If we issue this
command we can read the contents of the generated trace file.But
if the same command is given as
"ALTER DATABASE BACKUP CONTROLFILE TO FILENAME; it is not
allowing me to read the contents of the trace file
generated.what is the reason behind it?At the same time can u
explain me the difference between this command and the
controlfile creation command while we create a standby database
eventhough the name in the command reveals the story.I want to
know whether it contains any information related to logfiles as
we are not using anyof those while creating a standby database.
Thanks
Tejal

      and we said...
Note to Tejal:  your email addresss does not work.  i have no
way to notify you

The backup control file to filename command makes a binary
backup of a control file.

The backup control file to trace generates a script which can be
used to regenerate your control files.

From the SQL Ref manual:

<quote>
TO ’filename’ specifies the file to which the control file is
backed up. You must fully specify the filename using the
conventions for your operating system. If the specified file
already exists, you must specify REUSE.

TO TRACE writes SQL statements to the database’s trace file
rather than making a physical backup of the control file. The
SQL statements can start up the database, re-create the control
file, and recover and open the database appropriately, based on
the created control file.

You can copy the statements from the trace file into a script
file, edit the statements as necessary, and use the database if
all copies of the control file are lost (or to change the size
of the control file).
</quote>

Review &
      Followup

      Rating:
      4
controlfile  January 15, 2003
Reviewer:
      A reader
Great, that's what I was looking for.

      Rating: 5
control file
      and checkpoints  November 18, 2003
Reviewer:
      A reader
Tom,
1) Your book Expert one-on-one mentions the following in Chapter 2 (Pg 66)
<quote>
The control file also tells Oracle other things, such as information about
checkpoints thaat have taken place ....
</quote>
What information about checkpoints and redo logs is stored in the control file?

2) When we commit, the SCN generated for the transcation is written to the redo
log file(right!).  Is the latest SCN stored in the control file?

Thank you

          Followup:

1) what ones have happened -- what checkpoints have taken place and how that
relates to whats in the redo stream (so we know what logs need to be applied to
what files in the event of a crash recovery)

2) yes, written to the redo logs.  the control files are updated independently
-- not during each commit.

November 21, 2003 Rob -- Thanks for the question regarding "Bitmap
      indexes", version 8.1.7

      You Asked
Tom:

Is there any rule you would use to determine whether or not
to use a bitmap index vs. a btree index on a column with
relatively few distinct values (i.e. what percent of the rows
should be distinct for me to lean toward a btree index on a
particular column instead of a bitmap index.)
This is a data warehouse so I am only concerned with queries.

Thanks.

      and we said...
I will recommend a book and comment.

The book -- Practical Oracle8i -- building efficient databases by Jonathan
Lewis.  In addition to his really good explanation of bitmapped indexes, you'll
find many pieces of information in there you will put to use.

Bitmap indexes can be used on tables with high or low cardinality.  It is
b*trees that you need to stop using after you get to a certain percentage.
Bitmaps can go very high.

Bitmaps are useful for answering questions like:

show me information about:
   people who live in houses
   with 2 cars
   and ( have 4 kids or 2 pets )
   and like ice cream as their favorite food

given that you are querying a large set of data.  You would have bitmaps
(separate ones) on each of:

o housing type (house, apartment, condo, townhome, other)
o number of cars ( 1, 2, 3, 4, 5, 6, other)
o number of kids ( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, other)
o number of pets ( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, other)
o favorite food (a large list -- could be as large as the number of rows)

We would AND/OR these together very efficiently (something was cannot do with
the b*tree) and get you your result set fast.

So, its not the cardinality (although that says when to STOP using b*trees) in
as much the questions you will be asking.  In a data warehouse, bitmaps might be
more common then b*trees.  Again, I really suggest that book -- you'll find it
incredibly useful!

Review
      & Followup

      Rating: 4
Clarify when
      not to use btrees  February 02, 2002
Reviewer:
      Rob Balter  from Pittsburgh,PA
Tom:

Can you claify the stmt:
"It is b*trees that you need to stop using after you get to a certain
percentage."

How do you figure out the percentage?

          Followup:

if you are going to retrieve somewhere between 5-20% of rows of the table (using
index range scan/table access by rowid), the index is slower then a full scan.
depends on the cluster factor of the index.  If you have my book -- I have a
couple of example that show when a b*tree stops being useful.

      Rating: 5
Btree Vs
      Bitmaps  November 18, 2003
Reviewer:  John
      Ryan  from Cambridge, UK.
Interesting point about BTREEs Vs Bitmaps.

One point worth noting, because of the way Bitmaps work, I've found they're
"generally" only useful when a table has a number of bitmaps to combine.

A table with a single bitmap is seldom used.

However, assuming well analyzed table(s), won't the CBO simply decide upon the
best approach, and ignore unsuitable indexes?  Designers VERY seldom re-visit
their live data to see if data has high/low cardinality unless there's a serious
performance problem.

I've found a live warehouse table with 32,764 rows, and only 2,326 unique (7.5%
cardinality).  I'd considered droping the index, only to find in the past three
months it had over 1,000 index range scans.

I'm assuming the CBO knew when to use the index, and when not to (same table
also hit 4,000 full table scans).

Can you confirm this from your experience?

          Followup:

I'd say "wrong", don't agree.

It depends on what QUESTIONS you ask totally.

why do you believe that the index you wanted to drop was a "bad index" that
shouldn't be used??  (and how did you measure "1,000 index range scans" -- that
would be interesting)

November 21, 2003 Azman -- Thanks for the question regarding "JDBC
      connections getting dropped by firewall", version 8.1.6

      You Asked
JDBC connections getting dropped by firewall

My Application server is using thin drivers to for JDBC connections to my
database thru a firewall. It frequently logs "Connection timed out" and loses
connections to the database. I expect the firewall drops the connection if it is
idle for more than a few minutes since  there is no evidence of the timeouts in
the listener.ora or listener trace files.

Any idea of how I we could sustain the connection even if there is no traffic?

      and we said...
Ummm, get in touch with your network/security guys perhaps and ask them to turn
it off?  But then they might explain why it is so -- and why it is "good" that
you get timed out after X minutes of inactivity.

One thing that might work would be to enable dead client detection (DCD).  Here,
the database will send a "ping" out to the client ever N minutes and expects to
get a response back.  If no response -- the database will close your session.
This "ping" may be sufficient to keep the connection open (but does totally
defeat the purposes of timing out idle connections which are:

a) security -- to make sure people don't walk away from a terminal and leave a
hole open

b) better resource use -- if you ain't using it - get off of it.

Review &
      Followup

      Rating: 3
DCD and JDBC
      Thin  November 18, 2003
Reviewer:  igor
       from ME USA
Hi Tom,

I think that Dead Connection Detection is a Net8 feature. JDBC Thin driver does
not use Net8 Client. Therefore DCD will not work with JDBC Thin driver.

Am I right?

          Followup:

the thin driver does not support many features.

the thin driver should only be used when you deploy an applet to the desktop
(very very rare)

not sure if it does do DCD or not.  It would be easy enough to test on your
system, just write a small java app, connect to the database (with dcd enabled),
turn off your laptop and see what happens.

      Rating: 5
DCD and JDBC
      Thin  November 18, 2003
Reviewer:  igor
       from ME USA
Tom,

After searching the documentation it seems that I was wrong declaring DCD will
not work with JDBC Thin.

What made me think so was the fact that DCD apparently didn't work for me so
far. We have Tomcat on Windows with JDBC Thin driver and Oracle 9.2 database on
Solaris with DCD configured to 60 min. Sometimes Tomcat starts to malfunction,
and the guys restart it without rebooting the Windows box. Every time after that
I see orphaned inactive tomcat connections in the database and DCD never
terminates these sessions.
Now I think that I should request that every time when Tomcat is restarted, the
whole Windows box must be rebooted. Then DCD probably will work.

Thank you for your great site anyway.

November 21, 2003 Logan -- Thanks for the question regarding
      ""minimize records_per_block" and compact bitmap indexes", version 8.1.6

      You Asked
Tom,

Oracle SQL Reference manual says, when we use "records_per_block" clause of the
alter table statement, the bitmap indexes will be compact (compressed). But it
doesn't seem to be so, in my case.

I have two similar tables t (with records_per_block) and t1 (without
records_per_block). Consequently, table T is much bigger than T1.

Then I created bit map indexes t_bmi and t1_bmi. But both of them have the same
size. What could be the reason? Also, what makes the bitmap maps indexes to be
compact when records_per_block is specified?

Thanks.

SQL> drop table t;

Table dropped.

SQL> create table t (c1 varchar2(2), c2 number);

Table created.

SQL> insert into t values ('a', 1);

1 row created.

SQL> insert into t values ('b', 2);

1 row created.

SQL> insert into t values ('c', 3);

1 row created.

SQL> insert into t values ('d', 4);

1 row created.

SQL> alter table t minimize records_per_block;

Table altered.

SQL> insert into t select * from t;

4 rows created.

SQL> insert into t select * from t;

8 rows created.

...
...

SQL> insert into t select * from t;

4096 rows created.

SQL> create bitmap index t_bmi on t(c1);

Index created.

SQL>
SQL>
SQL> drop table t1;

Table dropped.

SQL> create table t1 (c1 varchar2(2), c2 number);

Table created.

SQL> insert into t1 values ('a', 1);

1 row created.

SQL> insert into t1 values ('b', 2);

1 row created.

SQL> insert into t1 values ('c', 3);

1 row created.

SQL> insert into t1 values ('d', 4);

1 row created.

SQL> insert into t1 select * from t1;

4 rows created.

SQL> insert into t1 select * from t1;

8 rows created.

...
...

SQL> insert into t1 select * from t1;

4096 rows created.

SQL> create bitmap index t1_bmi on t1(c1);

Index created.

SQL>
SQL> select avg( cnt), min(cnt), max(cnt)
  2  from ( select dbms_rowid.ROWID_BLOCK_NUMBER( rowid ), count(*)  cnt from t
  3  group by dbms_rowid.ROWID_BLOCK_NUMBER( rowid ));

  AVG(CNT)   MIN(CNT)   MAX(CNT)
---------- ---------- ----------
         4          4          4

SQL>
SQL> select avg( cnt), min(cnt), max(cnt)
  2  from ( select dbms_rowid.ROWID_BLOCK_NUMBER( rowid ), count(*)  cnt from t1
  3  group by dbms_rowid.ROWID_BLOCK_NUMBER( rowid ));

  AVG(CNT)   MIN(CNT)   MAX(CNT)
---------- ---------- ----------
630.153846        248        662

SQL>
SQL> exec show_space('T');
Free Blocks.............................2
Total Blocks............................2065
Total Bytes.............................16916480
Total MBytes............................16
Unused Blocks...........................15
Unused Bytes............................122880
Last Used Ext FileId....................291
Last Used Ext BlockId...................40046
Last Used Block.........................50

PL/SQL procedure successfully completed.

SQL> exec show_space('T1');
Free Blocks.............................2
Total Blocks............................20
Total Bytes.............................163840
Total MBytes............................0
Unused Blocks...........................5
Unused Bytes............................40960
Last Used Ext FileId....................291
Last Used Ext BlockId...................190130
Last Used Block.........................5

PL/SQL procedure successfully completed.

SQL> exec print_table('select * from user_segments where segment_name like
''T%BMI''');
SEGMENT_NAME                  : T_BMI
PARTITION_NAME                :
SEGMENT_TYPE                  : INDEX
TABLESPACE_NAME               : FPCD
BYTES                         : 40960
BLOCKS                        : 5
EXTENTS                       : 1
INITIAL_EXTENT                : 40960
NEXT_EXTENT                   : 40960
MIN_EXTENTS                   : 1
MAX_EXTENTS                   : 505
PCT_INCREASE                  : 1
FREELISTS                     : 1
FREELIST_GROUPS               : 1
BUFFER_POOL                   : DEFAULT
-----------------
SEGMENT_NAME                  : T1_BMI
PARTITION_NAME                :
SEGMENT_TYPE                  : INDEX
TABLESPACE_NAME               : FPCD
BYTES                         : 40960
BLOCKS                        : 5
EXTENTS                       : 1
INITIAL_EXTENT                : 40960
NEXT_EXTENT                   : 40960
MIN_EXTENTS                   : 1
MAX_EXTENTS                   : 505
PCT_INCREASE                  : 1
FREELISTS                     : 1
FREELIST_GROUPS               : 1
BUFFER_POOL                   : DEFAULT
-----------------

PL/SQL procedure successfully completed.

      and we said...
just asking - -would you happen to have Jonathan Lewis's most excellent book
"Practical Oracle8i -- building efficient databases"?  He has an excellent
discussion on this in there -- else, I'll paraphrase.
Review &
      Followup

      Rating: 5
Yes. I have
      Jonathan Lewis's book  March 31, 2003
Reviewer:
      Logan Palanisamy  from Santa Clara, CA USA
Tom,

Thanks. I will go through Jonathan Lewis' book.

          Followup:

page 113

      Rating: 5
Don't
      understand what Jonathan says  April 01, 2003

Reviewer:  Logan Palanisamy  from Santa Clara CA USA

Tom,

I read pages 113 and 114 of 'Practical Oracle 8i' book by Jonathan Lewis.
Understood only a part of it. Couldn't understand the following two items, even
after reading about bitmap indexes on chapter 6 of the same book.

1. his conjecture about block_size/12 on page 113
2. the maths in the paragraph "The following sample ..." on page 114

So, I still don't know how 'minimize records_per_block' produces compact bitmap
indexes.

Thanks

          Followup:

1) is not conjecture.  He is stating the fact that "Oracle assumes there will be
block_size/12 rows per block"

2) isn't really math -- but the result of a block dump he performed.

Here is a concrete example -- I have put 5-7 rows per block and have 1.8 million
rows ...

ops$tkyte@ORA920> drop table t;

Table dropped.

ops$tkyte@ORA920> create table t ( x int, y char(1) ) pctfree 99 pctused 1;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t
  2  select mod(rownum,3), 'x'
  3    from big_table.big_table;

1833792 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create bitmap index t_idx on t(x);

Index created.

ops$tkyte@ORA920> exec show_space( 'T_IDX', USER, 'INDEX' )
Free Blocks.............................
Total Blocks............................384
Total Bytes.............................3145728
Total MBytes............................3
Unused Blocks...........................33
Unused Bytes............................270336
Last Used Ext FileId....................9
Last Used Ext BlockId...................70792
Last Used Block.........................95

PL/SQL procedure successfully completed.

so, that is 351 blocks... now we do the alter

ops$tkyte@ORA920> drop index t_idx;

Index dropped.

ops$tkyte@ORA920> alter table t minimize records_per_block;

Table altered.

ops$tkyte@ORA920> create bitmap index t_idx on t(x);

Index created.

ops$tkyte@ORA920> exec show_space( 'T_IDX', USER, 'INDEX' )
Free Blocks.............................
Total Blocks............................256
Total Bytes.............................2097152
Total MBytes............................2
Unused Blocks...........................117
Unused Bytes............................958464
Last Used Ext FileId....................9
Last Used Ext BlockId...................70664
Last Used Block.........................11

PL/SQL procedure successfully completed.

and that is 139 blocks (256 - 117).  The second index is 39% the size of the
first -- same data, same order -- just let Oracle in on the fact that there were
only a couple of rows per block

      Rating:
      4
But..  April 01, 2003
Reviewer:  Ganesh
      Raja  from Singapore
How does oracle reduce the size of a bitmap index when we say that we are not
storing (Blocks/12) rows in the Block but only 10 Rows or 12 Rows per Block.

If i am not wrong then Alter Table Minimize Records_Per_Block just says that
This is the maximum number of rows that this Table can have per block.

Jonathan gives a good example on how to Rebuild a Table that has rows at
diffrent stages in the application workflow .. Like Just Inserted, 50%Updated
and 100% Updated using the alter Table Minimize Records_per_block.

Thanks.

Regards,
Ganesh R

          Followup:

it can better manage the space in the bitmaps.  Before 8.1.5, Oracle looks at
the table definition to compute the maximum possible number of records which can
be stored in a block of that table. This value can be found in the column
TAB$.SPARE1 but the value can be much larger than what will be really stored in
the table.
Therefore, bitmap indexes using this value have lots of unnecessary artificial 0
bits on the end of every block, which have to be compressed away at some cost.

It is that "at some cost" you are saving on here.

      Rating: 5
maximum number
      of records per block  April 02, 2003
Reviewer:
      A reader
SQL> create table t0 (c1 varchar2(10)) pctfree 0;

Table created.

SQL> create table t1 (c1 varchar2(10)) pctfree 0;

Table created.

SQL> create table t2 (c1 varchar2(10)) pctfree 0;

Table created.

SQL> create table t3 (c1 varchar2(10)) pctfree 0;

Table created.

SQL>
SQL> insert into t0 select null from all_objects where rownum < 6001;

6000 rows created.

SQL> insert into t1 select 'a' from all_objects where rownum < 6001;

6000 rows created.

SQL> insert into t2 select 'aaaaa' from all_objects where rownum < 6001;

6000 rows created.

SQL> insert into t3 select 'aaaaaa' from all_objects where rownum < 6001;

6000 rows created.

SQL>
SQL> analyze table t0 compute statistics;

Table analyzed.

SQL> analyze table t1 compute statistics;

Table analyzed.

SQL> analyze table t2 compute statistics;

Table analyzed.

SQL> analyze table t3 compute statistics;

Table analyzed.

SQL>
SQL> select table_name tbl_name, avg_row_len, blocks blks,
  2  empty_blocks empty_blks, NUM_FREELIST_BLOCKS freelist_blks,
  3  AVG_SPACE_FREELIST_BLOCKS avg_space_freelist_blks
  4  from user_tables
  5  where table_name in ('T0', 'T1', 'T2', 'T3');

TBL_NAME   AVG_ROW_LEN BLKS EMPTY_BLKS FREELIST_BLKS AVG_SPACE_FREELIST_BLKS
---------- ----------- ---- ---------- ------------- -----------------------
T0                   3    9          0             1                    6870
T1                   5    9          0             1                    6870
T2                   9    9          0             1                    6870
T3                  10    9          0             1                     902

SQL>
SQL> select 'T0' tbl_name, rows_per_block, count(*) number_of_such_blocks from (
  2  select dbms_rowid.ROWID_BLOCK_NUMBER( rowid ), count(*)  rows_per_block fr0
  3  group by dbms_rowid.ROWID_BLOCK_NUMBER( rowid ))
  4  group by 'T0', rows_per_block
  5  union all
  6  select 'T1' tbl_name, rows_per_block, count(*) number_of_such_blocks from (
  7  select dbms_rowid.ROWID_BLOCK_NUMBER( rowid ), count(*)  rows_per_block fr1
  8  group by dbms_rowid.ROWID_BLOCK_NUMBER( rowid ))
  9  group by 'T1', rows_per_block
 10  union all
 11  select 'T2' tbl_name, rows_per_block, count(*) number_of_such_blocks from (
 12  select dbms_rowid.ROWID_BLOCK_NUMBER( rowid ), count(*)  rows_per_block fr2
 13  group by dbms_rowid.ROWID_BLOCK_NUMBER( rowid ))
 14  group by 'T2', rows_per_block
 15  union all
 16  select 'T3' tbl_name, rows_per_block, count(*) number_of_such_blocks from (
 17  select dbms_rowid.ROWID_BLOCK_NUMBER( rowid ), count(*)  rows_per_block fr3
 18  group by dbms_rowid.ROWID_BLOCK_NUMBER( rowid ))
 19  group by 'T3', rows_per_block
 20  order by 1, 2;

TBL_NAME   ROWS_PER_BLOCK NUMBER_OF_SUCH_BLOCKS
---------- -------------- ---------------------
T0                    112                     1
T0                    736                     8
T1                    112                     1
T1                    736                     8
T2                    112                     1
T2                    736                     8
T3                    600                     1
T3                    675                     8

8 rows selected.

      Rating: 5
Maximum number
      of records per block  April 02, 2003
Reviewer:
      Logan Palanisamy  from Santa Clara, CA USA
Tom,

I am trying to find out why the maximum number of records per block is
approximately (block_size/12). My block size is 8192.

I create 4 identical tables. I purposely set pctfree to 0 so that I can stuff
the maximum possible number of records. I insert 6000 rows each, but with
different column values for each table to get different avg_len for the records.

Here is my test results:

SQL> create table t0 (c1 varchar2(10)) pctfree 0;

Table created.

SQL> create table t1 (c1 varchar2(10)) pctfree 0;

Table created.

SQL> create table t2 (c1 varchar2(10)) pctfree 0;

Table created.

SQL> create table t3 (c1 varchar2(10)) pctfree 0;

Table created.

SQL>
SQL> insert into t0 select null from all_objects where rownum < 6001;

6000 rows created.

SQL> insert into t1 select 'a' from all_objects where rownum < 6001;

6000 rows created.

SQL> insert into t2 select 'aaaaa' from all_objects where rownum < 6001;

6000 rows created.

SQL> insert into t3 select 'aaaaaa' from all_objects where rownum < 6001;

6000 rows created.

SQL>
SQL> analyze table t0 compute statistics;

Table analyzed.

SQL> analyze table t1 compute statistics;

Table analyzed.

SQL> analyze table t2 compute statistics;

Table analyzed.

SQL> analyze table t3 compute statistics;

Table analyzed.

SQL>
SQL> select table_name tbl_name, avg_row_len, blocks blks,
  2  empty_blocks empty_blks, NUM_FREELIST_BLOCKS freelist_blks,
  3  AVG_SPACE_FREELIST_BLOCKS avg_space_freelist_blks
  4  from user_tables
  5  where table_name in ('T0', 'T1', 'T2', 'T3');

TBL_NAME   AVG_ROW_LEN BLKS EMPTY_BLKS FREELIST_BLKS AVG_SPACE_FREELIST_BLKS
---------- ----------- ---- ---------- ------------- -----------------------
T0                   3    9          0             1                    6870
T1                   5    9          0             1                    6870
T2                   9    9          0             1                    6870
T3                  10    9          0             1                     902

SQL>
SQL> select 'T0' tbl_name, rows_per_block, count(*) number_of_such_blocks from (
  2  select dbms_rowid.ROWID_BLOCK_NUMBER( rowid ), count(*)  rows_per_block
  3  from t0
  4  group by dbms_rowid.ROWID_BLOCK_NUMBER( rowid ))
  5  group by 'T0', rows_per_block
  6  union all
  7  select 'T1' tbl_name, rows_per_block, count(*) number_of_such_blocks from (
  8  select dbms_rowid.ROWID_BLOCK_NUMBER( rowid ), count(*)  rows_per_block
  9  from t1
 10  group by dbms_rowid.ROWID_BLOCK_NUMBER( rowid ))
 11  group by 'T1', rows_per_block
 12  union all
 13  select 'T2' tbl_name, rows_per_block, count(*) number_of_such_blocks from (
 14  select dbms_rowid.ROWID_BLOCK_NUMBER( rowid ), count(*)  rows_per_block
 15  from t2
 16  group by dbms_rowid.ROWID_BLOCK_NUMBER( rowid ))
 17  group by 'T2', rows_per_block
 18  union all
 19  select 'T3' tbl_name, rows_per_block, count(*) number_of_such_blocks from (
 20  select dbms_rowid.ROWID_BLOCK_NUMBER( rowid ), count(*)  rows_per_block
 21  from t3
 22  group by dbms_rowid.ROWID_BLOCK_NUMBER( rowid ))
 23  group by 'T3', rows_per_block
 24  order by 1, 2;

TBL_NAME   ROWS_PER_BLOCK NUMBER_OF_SUCH_BLOCKS
---------- -------------- ---------------------
T0                    112                     1
T0                    736                     8
T1                    112                     1
T1                    736                     8
T2                    112                     1
T2                    736                     8
T3                    600                     1
T3                    675                     8

8 rows selected.

SQL>
SQL> spool off

My questions are:

1. For tables t0, t1 and t2, the avg_len of the row is 3, 5 and 9 respectively
with a maximum of only 736 rows per block. For t2, I can understand that (736 *
9) comes closer to 8192 with some block over head. But why does it stuff the
same 736 rows per block when the avg_len is only 3 or 5.

It certainly could have packed more rows per block. But it couldn't because, the
block header has space only for 736 entries even though there is space in the
rest of the block. Am I correct?

2. T2 has one freelist block with a freespace of 6870. T3 has one free list
block also with a freespace of 902 bytes. Since the avg_len of T2 is 9, and T3
is 10, T3 should have used up 6000 more bytes for 6000 rows. So for T3, I was
expecting a free space of only 870 (6870 - 6000). But how come it is 902?

Thanks

      Rating: 5
Why blocksize /
      12  April 05, 2003
Reviewer:  Jonathan Lewis
       from UK
In answer to just this specific question - and avoiding the detail of reworking
the example and counting bytes - the approximation of blocksize/12 comes from
Oracle using a worst-case scenario.

Assume that every single row in a block has migrated. What is the largest
possible number of migrated row entries that could appear in a single block ?
Count bytes as follows for each row:

  2 bytes for the row index (offset) entry
  1 byte for the row length
  1 byte for the flags (which will say 'migrated')
  1 byte for the lock flag
  6 bytes for the "rowid" that the row has been migrated to.

Total 11 bytes.  In the extreme case, therefore, the maximum number of rows you
could squeeze into a block would use 11 bytes per row - but there is some
overhead taken out of the block for the cache header, transaction header and so
on.  Hence my "approximately blocksize / 12"

It might have been more sensible if I had been more precise and said (blocksize
- XXX) / 11, or even 'slightly less than blocksize / 11' - but I just happened
to pick a very simple way of expressing the result.

For an 8K block with INITRANS=1, the answer happens to be 736 - so if you
actually have rows that use, say, 400 bytes each (for a total of 20 rows per
block) Oracle has to include a (compressed/encoded) string of 716 extra zero
bits which might turn into one bits at some time in the future. And this extra
716 bits has to exist in the index for each recorded key value, for each block
in the table.  (Luckily the compression/encoding strategy is very good).

      Rating: 5
Where correct
      number of rows per block is stored after minimiza records_per_block
      command?  October 22, 2003
Reviewer:  Boris
       from Slovenia
Why max number of rows per block is not correct in sys.tab$? Jonathan Lewis
wrote "The value stored in SPARE1 is "maximum number of rows per block plus
32767". This is not true in my case, i got 269 instead of 166?

05:30:12 db@IASDB.DELOA.DELO_PRODAJA.SI_901> alter table db_dobavnica_gla
minimize records_per_block
;

Table altered.

Elapsed: 00:00:00.09
05:30:18 db@IASDB.DELOA.DELO_PRODAJA.SI_901> select spare1 - 32767
max_rows_per_block from sys.tab$
where obj# in (select object_id from dba_objects where owner = 'DB' and
object_name = 'DB_DOBAVNICA_
GLA' and object_type='TABLE');

MAX_ROWS_PER_BLOCK
------------------
               269

1 row selected.

Elapsed: 00:00:00.03
05:30:24 db@IASDB.DELOA.DELO_PRODAJA.SI_901> SELECT MAX (rows_per_block)
05:30:32   2    FROM (SELECT   DBMS_ROWID.rowid_block_number (ROWID),
05:30:32   3                   COUNT (*) rows_per_block
05:30:32   4              FROM db_dobavnica_poz
05:30:32   5          GROUP BY DBMS_ROWID.rowid_block_number (ROWID));

MAX(ROWS_PER_BLOCK)
-------------------
                166

1 row selected.

Elapsed: 00:00:20.01
05:30:52 db@IASDB.DELOA.DELO_PRODAJA.SI_901>

          Followup:

things change.

sys.tab$ is 110% undocumented.

you cannot count rows/block using rowids!!!  (chained rows, migrated rows -- you
cannot use a rowid to do anything other then get a "hazy" guess)

You would have to test like this:

ops$tkyte@ORA920> create table t ( x int );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t select rownum from all_objects where rownum <=
100;

100 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table t minimize records_per_block;

Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select spare1 - 32767 max_rows_per_block from sys.tab$
  2  where obj# = ( select object_id from user_objects where object_name = 'T'
);

MAX_ROWS_PER_BLOCK
------------------
               100

      Rating: 3
Pl.
      clarify  November 18, 2003
Reviewer:
      A reader  from NJ, USA
Tom,

I have the following set of SQL statements

drop table tt
/
create table tt (col1 number)
/
insert into tt values(5)
/
alter table tt minimize records_per_block
/
insert into tt values(6)
/
insert into tt values(7)
/
analyze table tt compute statistics for table for all indexes
/

After running the above script, I ran the script

select a.blocks,num_rows,b.blocks,empty_blocks
  from user_tables a,
       user_segments b
 where a.table_name = 'TT'
   and b.segment_name = 'TT'

and expected that the 3 rows will be placed in 3 different blocks as a result of
minimize records_per_block statement after the first INSERT. Surprisingly, I see
that there are only 2 blocks that hold table data (1 record in Block 15882 and 2
records in Block 15883. However, this is the following excerpt from Oracle
documentation.
"Specify MINIMIZE to instruct Oracle to calculate the largest number of records
in any block in the table, and limit future inserts so that no block can contain
more than that number of records".
Could you pls. clarify why the table rows are stored only in 2 blocks instead of
3 (in spite of minimize record per block clause after the first INSERT) and what
does the "minimize records per block" mean.

          Followup:

2 is the minimum number of "minimum" rows, it'll not just do one, it'll do 2 and
up.  minimize will minimize to the greatest( 2, MAX_ROWS_ON_BLOCK_RIGHT_NOW )

November 21, 2003 keith -- Thanks for the question regarding
      "referencing types in pl/sql package from java", version 8.1.7

      You Asked
hi, i've read all (i think) your examples concerning pl/sql varrays, nested
tables etc., to and from java (with the JVM (JSDK 1.3.1) outside of oracle via
thin jdbc (classes12.zip)), but one thing that i can't seem to find out from the
examples or the docs is how to, when calling in the java code
ArrayDescriptor.createDescriptor(blah, conn) reference a user-defined type if
it's defined in a package.

e.g., i have

create or replace package blahpkg as
type blahtype is table of number;
end;

and then let's say i create a procedure (inside or pkg or not, doesn't matter)

procedure blahproc (p in blahpkg.blahtype) as
i int;
begin
-- create table test_1 (x number);
for i in 1..p.count loop
   insert into test_1 (x) values (p(i));
end loop;
end;

the procedure works fine and it behaves as expected if i execute it from within
another pl/sql block.

however, when i try to execute the sp from java using OracleCallableStatement
and then passing in a oracle.sql.ARRAY, i get an error when i do the
aforementioned createDescriptor.

e.g., i try

ARRAY blahArray = new ARRAY(ArrayDescriptor.createDescriptor("BLAHPKG.BLAHTYPE",
conn), conn, elements);

but i get

Exception in thread "main" java.sql.SQLException: invalid name pattern:
BLAHPKG.BLAHTYPE
<stack trace>

everything works fine and dandy if i create the type outside of a package.  any
ideas? tia,

k.

      and we said...
See

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/java.817/a83724/indexby.htm#1000888
it shows how in 817 and up using the OCI (thick -- not thin) drivers you can do
this.
Review
      & Followup

      Rating: 1
The link is
      broken  November 18, 2003
Reviewer:
      A reader

          Followup:

http://download-west.oracle.com/docs/cd/B10501_01/java.920/a96654/oci_func.htm#1017512

November 21, 2003 Chad -- Thanks for the question regarding
      "Rebuilding indexing--space consumption", version 8.1.6

      You Asked
We have a situation where when we go to rebuild certain indexes within the
database, it fails with a "Unable to extend temp segment...". What I don't
understand is, before this program runs at night, we double check to make sure
there is plenty of room in the "Indexes" tablespace, but it continues to fail
with the above error. We are rebuilding these indexes sequentially and are NOT
using the "online" clause. For instance, the last time this occurred we had over
1.5gb in the "indexes" tablespace and the largest index that we attempted to
rebuild was only 500mb. This program just loops through a rebuild statement
based off of a cursor that selects the indexes to rebuild for a given table. If
there are 6 indexes on a table, should we build in a "pause" so that Oracle can
coalesce the tablespace before moving on?

Please explain if there are any other variables that would come into play when
Oracle starts to rebuild an index.

Thank you, as always, for any input you may have.

      and we said...
why are you rebuilding indexes?

what is the goal behind that?

do you have metrics that prove you are doing more good then harm?

did you know that rebuilding indexes can have a massive negative impact on your
performance?

Anyway, the space is not reclaimed until AFTER the call has completed. if you
rebuild 5 indexes in a single plsql call -- you need room for 10 indexes (5
original, 5 new)

But -- i would recommend you just stop -- just don't do it.

but, if you persist.  do this

declare
   l_job number;
begin
   for x in ....select out indexes
   loop
      dbms_job.submit
      ( l_job,
       'execute immediate ''alter index ' || index name || ' rebuild'';' );
   end loop;
   commit;
end;
/

and use alter system set job_queue_processes = N where N is the "number of
concurrently running index rebuilds you want"

But -- I would just stop doing it.
Review &
      Followup

      Rating:
      4
  June 04, 2003
Reviewer:
      A reader
HI Chad,
Since the index is failing in temp segment, you should ensure that there is
enough space available in temporary tablespace of the user along with the Index
tablespace.However as tom said, there may not be any need to rebuild indexes.

          Followup:

It is that when you rebuild (as opposed to coalesce -- should have mentioned
that, if you are just trying to "reclaim space" -- coalesce them, that happens
IN PLACE) we create TEMP extents in the real tablespace.

That way if the system fails in the middle of a rebuild -- SMON will see lots of
orphaned TEMP extents and clean them up.

At the end of the rebuild we turn these temp extents into REAL extents and the
old real extents into TEMPORARY ones -- they are not in temp really, they are
temporary extents in your permanent tablespace -- they are the index data (we
don't use true temp for sorting during a rebuild, the data is sorted)

Their problem is that if you:

begin
   execute immediate 'alter index i1 rebuild';
   execute immediate 'alter index i2 rebuild';
   execute immediate 'alter index i3 rebuild';
   execute immediate 'alter index i4 rebuild';
   execute immediate 'alter index i5 rebuild';
end;
/

the temporary extents we made are not freed until AFTER the statement executes
and the statement here is begin/end

So, that tablespace holding i1..i5 needs to have room for two i1's, two i2's and
so on.  That is what is happening here.

the solution is of course "stop doing that"

      Rating: 5
Thanks !!
        June 04, 2003
Reviewer:  A reader

      Rating: 5
VERY GOOD
      ANALYSIS  June 04, 2003
Reviewer:  Reader
       from Houston, TX
If i used 'alter index i1 rebuild tablespace new;', then, oracle does not need
space for two indexes in the 'old' tablespace. if the index size is 500MB, we
need that much space at the minimum in tablespace 'new'. after this index is
rebuilt in tablespace 'new', then, 500 MB space is freed from the 'old'
tablespace. I hope i understood this.

          Followup:

if you are still rebuilding on a schedule, you didn't understand....

coalesce if you want (in place operation)

rebuild, not.

      Rating: 3
A case to
      rebuild?  June 04, 2003
Reviewer:  Peter
       from uk
We have a DW system. In our daily batch dataload + summarisation we disable
bitmap indexes before the load (of course!), load the data and then the bitmaps
are rebuilt using ALTER INDEX bm1 REBUILD. For partitioned and sub-partitioned
indexes we just rebuild the (sub) partition.

Perhpas the rebuild is the wrong thing to do!

And yes we do run out of temp tablespace sometimes!

          Followup:

bitmaps are the exception -- as are text indexes.

data warehousing is a bit different as well.  Many times you direct path with
skip index maintanence and then rebuild.

If the indexes are "healthy", no need to rebuild.

bitmaps can break down from lots of dml.
text indexes are a special kind of bitmap.

b*trees are very very robust.

      Rating: 4
Explanation for
      why we rebuild.  June 04, 2003
Reviewer:  Chad
      Burney  from Connecticut
The reason why we are rebuilding indexes is because we found that our nightly
batch jobs run significantly faster after the rebuilds were done. We have
databases that are associated with a "Banking" environment(OLTP & Batch), and
every month we must purge millions of rows from some of our largest history
tables. I don't have specific metrics on our testing, but what we have found was
that after purging and rebuilding indexes, most of our "nightly queues" ran 40%
faster.

I have read many of your articles on why people shouldn't rebuild tables and
indexes and have found them very interesting and informative. What I have found
difficult is trying to explain your reasoning to my management--since they
always come back with "so why did our testing show a 40% improvement". Even if I
explain to them that this 40% improvement will quickly fade, they respond with
"then we will just rebuild them again". As you can see I am fighting a "no win"
argument.

          Followup:

there is the missing point -- purge.

have you considered "coalesce" to achieve the same without doubling the space
needed?

but you might have missed one of key points in my discussions:

o unless you have metrics that show you are doing more good them harm, STOP
until you have them.

You have a justifiable reason here -- a purge (i would use partitioning probably
-- avoids the need to reorg)

you should consider a coalesce instead.

      Rating: 3
Best of
      all  June 05, 2003
Reviewer:  krish parmar
       from mumbai
What if instead of getting into business of
rebuilding an existing indexes and increasing room
for it, is RE-creaing an index as it created is a better
solution ?
I my self feel re-creating is best of all.
do you ?

          Followup:

rebuild is better over drop/create.

0% chance of "losing" an index with a rebuild.

100% chance of some day "losing" an index with a drop/create as they are two
separate statements that each commit.  Someday -- the drop will succeed, the
create will fail and no one will notice -- until performance goes down the tubes
later that day OR worse - data integrity gets thrown out the window.

      Rating: 5
For krish
        June 05, 2003
Reviewer:  Connor  from
      Oz
You have to be joking ?!

Recreate means:

a) there is a period of time when there is NO index...if its a unique index,
then bingo, you might have a corruption

b) you need to store the ddl somewhere or regen it totally every time

Ugh!

      Rating: 5
index
      rebuild  June 05, 2003
Reviewer:  Chris
      Major  from Hungary
Tom, you are asking, why people are rebuilding indexes? This is from Oracle
University's "Oracle9i: SQL Tuning Workshop", page 6-5:

"You should reorganize your indexes regulary to guarantee optimal storage
efficiency and performance."

So you (your company) are telling people to do this.

          Followup:

I also say "Question Authority", "test it for yourself", "keep metrics, prove
you are doing 'good'"

Myths, Conventional Wisdom, "of course it must be true" are very hard to defeat
soundly.

It is all about proofs.

Nothing is 100% true all of the time -- except maybe that "Nothing is 100% true
all of the time"

      Rating: 4
Coalesce vs
      Rebuild?  June 05, 2003
Reviewer:  Chad
       from Connecticut
This might be a stupid question, but I will ask it anyway. What is the major
differences between coalescing vs. rebuilding--I now know one is "the space
needed". What I don't understand is why rebuild when we can just coalesce. Does
this get rid of the "brown leaves" within the index?

          Followup:

define "brown leaves" -- but anyway...

coalesce just takes leaf blocks and merges them together -- putting the one now
empty block onto the freelist for the index to be reused elsewhere (space
reclaimed).

rebuild -- well, it rebuilds it.  You need 2x the space -- index goes back to
what the index would look like if you dropped and created it.

      Rating: 4
"Brown
      leaves"?  June 05, 2003
Reviewer:  Chad
       from Connecticut
I am sorry, I once heard someone refer to index entries pointing to
non-existant(purged) rows as "brown leaves". Maybe I should never have picked up
that phrase.

Thank you for all of your help. It definitely sounds like what we should be
doing is coalescing--since then the index doesn't then have to go through the
entire "growth process" again.

          Followup:

I've heard it too -- it is just "at which stage do them become brown" that is
ill defined.

      Rating: 4
How about bulk
      inserts   June 05, 2003
Reviewer:  yogesh
       from Pune India
Agreed that coalesce is better than rebuild... but this is true in case of
reclaiming the space .. if the scenario is every day some 50000 records are
being added in the table ... so monthly 30*50000=1500000 ... and existing record
count is 100000000 ...

Now in every insert index is also getting changed / rebuild (may be I'm wrong
here) !! so in this case how / why rebuild is helpful ..

          Followup:

i don't believe in general it would be for a b*tree index.

      Rating:
      5
Great  June 05, 2003
Reviewer:
      Reader
If I have a table say, orders with order number as a column. Once I complete
delivering orders to customers, I delete those rows from the table. I have a B
tree index on the order number.  My order number always increases and therefore,
I would never use the same order number again. Assume that my business is
growing and I am deleting thousands of rows every week after fulfilling the
orders. Is the index on order number a candidate for coalesce or rebuild, say
after a month or so? Thanks.

          Followup:

you delete your orders?   :)

But anyway -- if you delete all of them -- meaning that if orders 1 .. 100 were
on the first leaf block and you eventually delete 1..100 -- that leaf block will
be put on the freelist all by itself.

It would be if you deleted MOST of 1..100 and left some, and would never delete
them, that you would consider coalescing over time to combine blocks with mostly
free space and release some others back to the freelist.

      Rating: 4
Our Rebuilding
      Procedure?  June 06, 2003
Reviewer:  Chad
       from Connecticut
I was finally able to get the procedure we use to rebuild our indexes. Would you
say this is comprable to what you suggested?

declare
 cursor theList is select index_name, tablespace_name
                   from all_indexes
                   where (owner='OSIBANK')
                   and (table_name in
('ACCTBALHIST','ACCTRATEHIST','ACCTRCVB','ACCTSTATISTICHIST','ACCTYTDHIST','ACCTA
VAILAMTHIST','ACTV','ACTVSUBACTV','CARDTXN','FILEWAREHOUSE','FILERECORD','ACCTCAR
DTXNHOLD','ACCTACCTSTATHIST','ACCTSUBACCT','ACCTINTHIST','RTXN','RTXNAGREEMENT','
RTXNFUNDTYP','RTXNSTATHIST','RTXNBAL'));
 theSegmentName all_indexes.index_name%TYPE;
 theSegmentTablespace all_indexes.tablespace_name%TYPE;
 theCommand varchar2(255);
 theCursor integer;
 ret_val   number;
begin
 -- Open the cursor
 open theList;

 -- Loop over the list of troublesome indices
 loop
  -- Get the characteristics of an index
  fetch theList into theSegmentName, theSegmentTablespace;
  exit when theList%NOTFOUND;

  theCommand := 'ALTER INDEX OSIBANK.' || theSegmentName || ' REBUILD TABLESPACE
' || theSegmentTablespace;

  -- Use an Oracle-provided package to execute the DDL and clean up
  theCursor := DBMS_SQL.OPEN_CURSOR;
  BEGIN
    -- DBMS_OUTPUT.PUT_LINE('... Rebuilding ' || theSegmentName || '...');
    DBMS_SQL.PARSE(theCursor, theCommand, DBMS_SQL.NATIVE);
    ret_val := DBMS_SQL.EXECUTE(theCursor);
  EXCEPTION
    WHEN OTHERS THEN
      --DBMS_OUTPUT.PUT_LINE('... Unable to rebuild ' || theSegmentName ||
'...moving on');
      DBMS_OUTPUT.PUT_LINE(theSegmentName);
  END;
  DBMS_SQL.CLOSE_CURSOR(theCursor);
 end loop;

 -- Close the cursor
 close theList;
end;
/

          Followup:

no, i think you might want to try coalesce still :)

      Rating: 5
Rebuild or
      coalesce?  November 06, 2003
Reviewer:  Tracy
      Tupman  from England
If you have a table containing 20 million rows and you delete 15 million of them
and then you intend to maintain that table at around five million rows
presumably it is better to rebuild the indexes on the table after deleting the
15 million otherwise you are never really going to reclaim the space taken up by
the indexes, are you? Even if you coalesce until you are blue in the face the
overall 'size' of an index will be as it was when the table had 20 million rows
and thus a lot of space will be wasted. If it had 10,000 blocks when there were
20 million rows, it will still have 10,000 when it goes down to 5 million.
Correct or not - that is the question? Coalescing may squeeze some of the index
entries into fewer blocks and so put more blocks back on the freelist but there
will still be just as many blocks allocated. Is that right and if so, is there
any other way of really reclaiming the free space other than doing a rebuild or
dropping and recreating? (That's the second question.)

          Followup:

wasted is in the eye of the beholder isn't it?  i assume the table is going to
grow again -- so, the space is just "reserved"

but, if I were to delete 3/4's of a 20million row table -- I might WELL be
looking at other approaches

a) partitioning to avoid the delete
b) create table as select rows to keep, drop old table, rename new

for example.

free space is a reletive term here.

      Rating: 4
This is great
      stuff.  November 18, 2003
Reviewer:  Mark
       from Virginia
What caught my eye on this thread was a brief discussion of what happens to a
b-tree index when deleting table rows from the left, if you will.  That is, if
my PK column is fed by a sequence and always increases, never repeats values,
then I imagine new data being inserted on the right and old data being deleted
from the left.  And, as you reminded us, only after ALL rows in a block are gone
does Oracle put the block back on the freelist.

Well, were do new index entries go now, and does it matter?  I picuture my index
always splitting to the right since I have ever increasing values, but my
available blocks are back on the left.  I would really like to know the scoop on
this.

Just for what it's worth, I recently attended an Oracle Education course
"Oracle9i Perf Tuning".  They brand new books suggest we rebuild all indexes
when the ratio of del_lf_rows/lf_rows > 0.30, or when the index height is > 4.

          Followup:

I wish they would RECOMMEND COALESCE, but anyway...

new index entries go on the right hand side.  when we populate a pkey via an
increasing sequence -- we detect that and do a "90/10" split.  when the block on
the right fills up -- we put 90% of the rows to the left and 10% to the right
and continue on.

As blocks empty on the far left -- they go onto the free list for the index.  As
we add blocks on the right -- we get them from the free list.  so a block that
was on the left yesterday might be on the right tomorrow.

November 21, 2003 Scott -- Thanks for the question regarding
      "Multiple User Logons to an Application", version 8.1.7

      You Asked
Dear Tom,

I have a schema on an Oracle 8.1.7 Database with which I am using Oracle Forms
6i as the user interface. The Forms interface allows users to insert, update,
delete etc through stored procedures. At the moment It is not in a production
environment and only has one user logon, the schema that the tables etc have
been created under.

I have read some of the questions posted on your forum but I am still unsure
which is the best, most robust way to allow users with different logons to use
my application using the Oracle Databases built in utilities to achieve this.

I have been told that I will most likely have to use synonyms if I have multiple
logons for the users to be able to run the application as themselves, as to
execute a procedure they need to have <schema>.<procedure>. Is this a true?

I have read that you do not need public synonyms to allow users to use the
application if they logon as themselves. As I would have to get the DBA to
create a large number of synonyms which I don't want to do unless I have to. The
application calls to my stored procedures are not prefixed with the schema name,
does this need to be done to get around the using synonyms?

Could you please let me know how you would allow access through a Forms
application for multiple users to the database schema?

Looking forward to your reply.

Regards,

Scott.

      and we said...
well, there is absolutely NOTHING wrong with your application using
"schema.procedure".

In fact, I think it is a good practice.

But, if you haven't done that and you won't do that, you can add to your code:

alter session set current_schema=your_application_schema;

consider:

ops$tkyte@ORA920> desc p
ERROR:
ORA-04043: object p does not exist

ops$tkyte@ORA920> alter session set current_schema=scott;

Session altered.

ops$tkyte@ORA920> desc p
PROCEDURE p
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 P_STR                          VARCHAR2                IN

ops$tkyte@ORA920> exec p('hello world');
hello world

PL/SQL procedure successfully completed.

this does not affect security in any way shape or form, it only affects the
default schema name used.  when i described p after the alter session -- it was
really as if I typed desc scott.p

Review &
      Followup

      Rating: 5
Thanks for your
      input.  November 02, 2003
Reviewer:  Scott
      Pedersen  from Auckland, NZ
Thanks Tom, I really appreciate your input.

Regards,

Scott.

      Rating: 2
Remember the
      user still needs GRANTS  November 03, 2003

Reviewer:  Hemant K Chitale  from Singapore

Surely, the user still needs to be GRANTed
the SELECT/INSERT/UPDATE/DELETE privileges
on the Tables as they are not really in his
schema ?

          Followup:

surely *not*

they stated:

The Forms interface allows users to insert, update,
delete etc through stored procedures.

so, they only need execute on the stored procedures, they need NO access to the
base tables whatsoever.

      Rating: 4
Performance
      problem via procedures?  November 03, 2003

Reviewer:  A reader
Hi Tom,

   If all select/delete/update/insert operations are using stored procedures, do
you think there is performance problem in using the applications?

David

          Followup:

nope, there would be performance issues if you did it any other way!!

      Rating: 3
questionable
      response re: absolutely NOTHING wrong with your application using
      "schema.procedure"  November 03, 2003
Reviewer:
      Stan Grabowski  from Windsor, CT USA
Tom -- I'm currently on contract on an application that has four different
databases (development, system integration test, customer acceptance test, and
production).  Each of the environments has a different schema owner.  Therefore,
from our perspective, using the owning schema as an identifier would not make
the application portable across the four environments.

          Followup:

why would you do that?

thats an issue with your processes, not the "nothing wrong with your application
using schema.procedure"

4 different databases = 1 consistent schema made easy.

      Rating: 4
Where to place
      the alter session set current_schema in forms?  November 18,
      2003
Reviewer:  Scott Pedersen  from New Zealand

Hi Tom,

I have tried to place the alter session set current_schema=scott; in the
ON-LOGON trigger and forms complains "Encountered the symbol ALTER when
expecting one of the following: begin declare ..." I have added the following
but to no avail. What do I need to do to get this to work? Your input is
appreciated.

Thanks in advance.

Regards,

Scott.

          Followup:

begin
  forms_ddl( 'alter ....' );
end;

      Rating: 5
Thanks
      Again  November 21, 2003
Reviewer:  Scott
      Pedersen  from New Zealand
Dear Tom,

Thanks again for your prompt reply and excellent answer. Much appreciated.

Regards,

Scott.

November 21, 2003 Preeti -- Thanks for the question regarding
      "Security in Oracle", version 8.1.7

      You Asked
Hi Tom,
we have a web application that requires the users to login. The userids and
passwords are maintained in an Oracle table.

We have the follwing requirements:
1. Change the password every 90 days.
2. Prohibit reuse of passwords. Cannot use last 5 passwords.
3. Store the passwords in a encrypted format.

Can these functionalties be taken care of in Oracle or do these need to be coded
in our front end. Could you provide me a specific example on using the
DBMS_OBFUSCATION toolkit.

Thanks

      and we said...
Well..... If you used DATABASE usernames and passwords -- we could do ALL OF THE
ABOVE (and more)...

If you do it yourself, well, then -- you do it yourself.

You will need to write the code to "expire" the password.
You will need to write the password history routines..
You will need to encrypt (although I would NOT encrypt, I would HASH)...

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:95412348059

for that last point.

Review &
      Followup

      Rating:
      4
  April 30, 2002
Reviewer:  preeti
      dhir  from USA

      Rating:
      3
  April 30, 2002
Reviewer:  SURESH
       from CHENNAI

      Rating:
      3
  April 30, 2002
Reviewer:
      BALACHANDAR  from GANDARVAKOTTAI
very good

      Rating: 5
Balance
      workload between employees - help with update query  May 15,
      2003
Reviewer:  Preeti  from USA
Tom,
I have the following question about update query:

we have a loan table: state_cd,
                      user_id,
                      status_cd,
                      ....

user_state_served: user_id,
                   state_cd,
                   last_asgnmt_dt

We want to distribute the loans equally to employees authorized to work in the
state. I do that by updating last_asgnmt_dt  = sysdate and taking the user_id
with min(last_asgnmt_dt) for next assignment.

However, after the initial assignment the workload may be different between
employees based on number of loans processed in a state.
How do I take the average number of loans in a state and divide equally among
employees

for example for the state of TX, there are 400 loans. There 4 workers in that
state. This the distribution right now:

emp 1:   150
emp2 :      50
emp3:      75
emp 4 : 0

unassigned: 125.

I want to equally divide all the active loans among 4 employees.

          Followup:

how does this work:

ops$tkyte@ORA920LAP> create table user_state_served
  2  as
  3  select rownum user_id, 'TX' state_cd from all_objects where rownum <= 4
  4  union all
  5  select rownum+4 user_id, 'VA' state_cd from all_objects where rownum <= 3;
Table created.

ops$tkyte@ORA920LAP> create table loan_table
  2  as
  3  select 'TX' state_cd, 1 user_id from all_objects where rownum <= 150
  4  union all
  5  select 'TX', 2 from all_objects where rownum <= 50
  6  union all
  7  select 'TX', 3 from all_objects where rownum <= 75
  8  union all
  9  select 'TX', null from all_objects where rownum <= 125;
Table created.

ops$tkyte@ORA920LAP> insert into loan_table
  2  select 'VA', 5 from loan_table
  3  /
400 rows created.

ops$tkyte@ORA920LAP> select user_id, count(*) from loan_table group by user_id
  2  /

   USER_ID   COUNT(*)
---------- ----------
         1        150
         2         50
         3         75
         5        400
                  125

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create or replace procedure redistribute_state( p_state_cd
in varchar2 )
  2  as
  3      type rowidArray is table of rowid;
  4
  5      l_user_id  dbms_sql.number_table;
  6      l_rowids   rowidArray;
  7  begin
  8
  9  select b.user_id, a.rid BULK COLLECT into l_user_id, l_rowids
 10    from (
 11  select state_cd,
 12         ntile(buckets) over ( partition by buckets, state_cd order by
state_cd ) idx,
 13         loan_table.rowid rid
 14    from loan_table,
 15         (select count(*) BUCKETS
 16            from user_state_served
 17           where state_cd = p_state_cd)
 18   where state_cd = p_state_cd
 19         ) A,
 20         (select user_id, row_number() over ( order by user_id ) rn
 21            from user_state_served
 22           where state_cd = p_state_cd ) b
 23   where a.idx = b.rn;
 24
 25   forall i in 1 .. l_rowids.count
 26       update loan_table set user_id = l_user_id(i) where rowid =
l_rowids(i);
 27  end;
 28  /

Procedure created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> exec redistribute_state( 'TX' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec redistribute_state( 'VA' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select user_id, count(*) from loan_table group by user_id
  2  /

   USER_ID   COUNT(*)
---------- ----------
         1        100
         2        100
         3        100
         4        100
         5        134
         6        133
         7        133

7 rows selected.

now, in 8i ntile won't fly in plsql, so here is how this would be 8i'ized:

ops$tkyte@ORA920LAP> rollback;

Rollback complete.

ops$tkyte@ORA920LAP> select user_id, count(*) from loan_table group by user_id
  2  /

   USER_ID   COUNT(*)
---------- ----------
         1        150
         2         50
         3         75
         5        400
                  125

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create or replace view V
  2  as
  3  select b.user_id, a.rid
  4    from (
  5  select state_cd,
  6         ntile(buckets) over ( partition by buckets, state_cd order by
state_cd ) idx,
  7         loan_table.rowid rid
  8    from loan_table,
  9         (select count(*) BUCKETS
 10            from user_state_served
 11           where state_cd = userenv('client_info'))
 12   where state_cd = userenv('client_info')
 13         ) A,
 14         (select user_id, row_number() over ( order by user_id ) rn
 15            from user_state_served
 16           where state_cd = userenv('client_info') ) b
 17   where a.idx = b.rn;

View created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create or replace procedure redistribute_state( p_state_cd
in varchar2 )
  2  as
  3      type rowidArray is table of rowid;
  4
  5      l_user_id  dbms_sql.number_table;
  6      l_rowids   rowidArray;
  7  begin
  8      dbms_application_info.set_client_info(p_state_cd);
  9      select user_id, rid BULK COLLECT into l_user_id, l_rowids
 10        from v;
 11
 12      forall i in 1 .. l_rowids.count
 13          update loan_table set user_id = l_user_id(i) where rowid =
l_rowids(i);
 14  end;
 15  /

Procedure created.

ops$tkyte@ORA920LAP> exec redistribute_state( 'TX' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec redistribute_state( 'VA' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select user_id, count(*) from loan_table group by user_id
  2  /

   USER_ID   COUNT(*)
---------- ----------
         1        100
         2        100
         3        100
         4        100
         5        134
         6        133
         7        133

7 rows selected.

      Rating:
      5
Vow!!!!  May 15, 2003
Reviewer:
      A reader

      Rating: 5
Tom this is
      great  May 16, 2003
Reviewer:  preeti
       from USA
How in the world a single person can learn all that

      Rating: 5
Oracle 8i gives
      an error  May 16, 2003
Reviewer:  Preeti
       from USA
I get the following error:

PLS-00531: Unsupported type in a VARRAY or TABLE type: 'ROWID'.

This is because I am on 8.1.7

          Followup:

  3      type rowidArray is table of rowid INDEX BY BINARY_INTEGER;
  4

should clear that up.

      Rating: 5
User Workload
      Balance  June 24, 2003
Reviewer:  Preeti
       from USA
Tom,
The stored proc did not work exactly the way I wanted. for example,

I have 6 loans in TX

user_id         loan_id
2               1
3               2
3               3
3               4
3               5
                6

When I ran the stored procedure, it equally distributed the 6 loans amongst 2
users in that group but it disturbs the original work basket for that user. For
example in the above query, it may take loan id number 1 and reassign to user_id
3. Althought there is no need to do that - we should only take loans from users
with extra workload and reassign it to users with less, without disturbing the
loans that they are already assigned to.

      Rating: 5
stored proc
      didnt work exactly  June 25, 2003
Reviewer:
      Preeti  from USA
Tom, I had asked you this question about workload balance without disturbing the
original workload, just taking away the extra loans from the users and
reassigning it to users with less loans.

Thanks

          Followup:

so, you have the concept, did you try yourself?

      Rating: 5
Can't figure it
      out  June 26, 2003
Reviewer:  Preeti
       from USA
I did Tom, I can't figure it out.

          Followup:

actually, in looking at it, the requirements are too vague.

eg: 400 jobs to assing.
    4 people
    each should have 100

however, one already has 150.  bummer. what now.

don't know what to tell you -- you'll have to figure it out.

      Rating: 4
How about
      migrate users from 8i to 9i without changing their
      passwords  November 18, 2003
Reviewer:  jo
      chuan  from VA USA
Tom

    I face different situation.  I am migrating from 8i to 9i.  I plan to create
a driver script that recreate all users from 8i to 9i by reading DBA_USERS table
and set default password to something.    But we have a lot of users, my boss
does not want to go through all the password change publicity.  Do you think it
will work if I grab all user passwords from the sys.USER$ in 8i and update the
corresponing user records in sys.USER$ in 9i.  This should give the users their
old passwords. Will this work? Thanks so much.

          Followup:

select 'create user ' || username || ' identified by VALUES ''' || password ||
''';' from dba_users;

November 21, 2003 Francisco -- Thanks for the question regarding
      "Max string allowed in EXECUTE IMMEDIATE.", version 8.1.7.4

      You Asked
Hi Tom.

I am trying to pass a string like 2151 characters in length, to the EXECUTE
IMMEDIATE command.

Example:

EXECUTE IMMEDIATE 'string with 2151 characters'; /* All the grants from user */

I am trying to replicate the system privileges from a user which was granted
with the ALL PRIVILEGES.

SQL> GRANT ALL PRIVILEGES TO FRANK;

There are like 115 privileges granted to this user.

What can I do to replicate these privileges using PL/SQL?

Any advice will be really appreciated.

Best regards.

Francisco Mtz.
Oracle DBA & Developer
OCP DBA 8 & 8i

      and we said...
A string of upto 32k is allowed (biggest plsql variable string length).

However, in order to grant more then one thing in a single statement, the
statement would have to look like:

begin
  l_string := 'begin
                 execute immediate ''grant select on t to scott'';
                 execute immediate ''grant update on t to scott'';
                 ....
               end;';

  execute immediate l_string;
end;

I'm guessing you have:

    l_string := 'grant select on t to scott;
                 grant select on x to scott;
                .....';

that would be a sqlplus script - not an executable sql statement.

But -- given as I have no example from you of the problem -- thats just a guess.

Review &
      Followup

      Rating: 5
It is that I am
      doing.  October 26, 2002
Reviewer:  Francisco
      Martinez  from Monterrey, N.L., Mexico
Thanks a lot Tom.

My code is as follow:

CREATE OR REPLACE PROCEDURE REPLICATE_SQL_STATEMENT (pDatabase IN VARCHAR2,
pSQL_statement IN VARCHAR2) IS
   vSQL_statement VARCHAR2(3000);
BEGIN
   IF pSQL_statement IS NOT NULL
   THEN vSQL_statement := 'BEGIN
ARBOR.Execute_SQL_Statement@'||pDatabase||'('''||pSQL_statement||'''); END;';
        EXECUTE IMMEDIATE vSQL_statement;
   END IF;
END;
/

CREATE OR REPLACE FUNCTION GET_SYS_PRIVS_STMT (pDatabase IN VARCHAR2, pUsername
IN VARCHAR2, pEvent IN VARCHAR2, pAdmin IN VARCHAR2) RETURN VARCHAR2 IS

   vSQL_statement VARCHAR2(3000);

   vSysprivs_statement VARCHAR2(1000);
   TYPE EmpCurTyp IS REF CURSOR;
   cSysprivs     EmpCurTyp;
   vSyspriv      VARCHAR2(100);
   vSyspriv_list VARCHAR2(1000);

BEGIN
   IF (pAdmin = 'ADMIN') OR (pAdmin = 'NOADMIN')
   THEN IF pEvent = 'GRANT'
        THEN  vSysprivs_statement := 'SELECT A.PRIVILEGE'||
                                      ' FROM DBA_SYS_PRIVS A,
DBA_SYS_PRIVS@'||pDatabase||' B'||
                                     ' WHERE B.GRANTEE   (+) = A.GRANTEE'||
                                       ' AND B.PRIVILEGE (+) = A.PRIVILEGE'||
                                       ' AND B.GRANTEE      IS NULL'||
                                       ' AND B.PRIVILEGE    IS NULL'||
                                       ' AND A.GRANTEE       =
'''||pUsername||''''||
                                       ' AND A.ADMIN_OPTION  =
DECODE('''||pAdmin||''', ''ADMIN'', ''YES'', ''NO'')';
              OPEN cSysprivs FOR vSysprivs_statement;
              LOOP
                 FETCH cSysprivs INTO vSyspriv;
                 EXIT WHEN cSysprivs%NOTFOUND;
                 SELECT vSyspriv_list||DECODE(vSyspriv_list, NULL, NULL, ',
')||vSyspriv INTO vSyspriv_list FROM DUAL;
              END LOOP;
              SELECT DECODE( vSyspriv_list, NULL, NULL, 'GRANT
'||vSyspriv_list||' TO '||pUsername||DECODE(pAdmin, 'ADMIN', ' WITH ADMIN
OPTION', NULL)) INTO vSQL_statement FROM DUAL;
        ELSIF pEvent = 'REVOKE'
        THEN  vSysprivs_statement := 'SELECT B.PRIVILEGE'||
                                      ' FROM DBA_SYS_PRIVS A,
DBA_SYS_PRIVS@'||pDatabase||' B'||
                                     ' WHERE B.GRANTEE       = A.GRANTEE
(+)'||
                                       ' AND B.PRIVILEGE     = A.PRIVILEGE
(+)'||
                                       ' AND B.GRANTEE       =
'''||pUsername||''''||
                                       ' AND B.ADMIN_OPTION  =
DECODE('''||pAdmin||''', ''ADMIN'', ''YES'', ''NO'')'||
                                       ' AND A.GRANTEE      IS NULL'||
                                       ' AND A.PRIVILEGE    IS NULL'||
                                     ' UNION '||
                                     'SELECT A.PRIVILEGE'||
                                      ' FROM DBA_SYS_PRIVS A,
DBA_SYS_PRIVS@'||pDatabase||' B'||
                                     ' WHERE B.GRANTEE       = A.GRANTEE'||
                                       ' AND B.PRIVILEGE     = A.PRIVILEGE'||
                                       ' AND B.ADMIN_OPTION <> A.ADMIN_OPTION'||
                                       ' AND A.GRANTEE       =
'''||pUsername||'''';
              OPEN cSysprivs FOR vSysprivs_statement;
              LOOP
                 FETCH cSysprivs INTO vSyspriv;
                 EXIT WHEN cSysprivs%NOTFOUND;
                 SELECT vSyspriv_list||DECODE(vSyspriv_list, NULL, NULL, ',
')||vSyspriv INTO vSyspriv_list FROM DUAL;
              END LOOP;
              SELECT DECODE(vSyspriv_list, NULL, NULL, 'REVOKE
'||vSyspriv_list||' FROM '||pUsername) INTO vSQL_statement FROM DUAL;
        ELSE  RAISE_APPLICATION_ERROR(-20101, 'ERROR: pEvent parameter value is
invalid in procedure Get_Role_Privs_Stmt.');
        END IF;
   ELSE RAISE_APPLICATION_ERROR(-20101, 'ERROR: pAdmin parameter value is
invalid in procedure Get_Role_Privs_Stmt.');
   END IF;
   RETURN (vSQL_statement);
END;
/

I have to exclude some of privileges like "ADMINISTER RESOURCE MANAGER",
"DEQUEUE ANY QUEUE", "ENQUEUE ANY QUEUE" and "MANAGE ANY QUEUE", which are
granted with packages.

Note: I haven't modified my function(GET_SYS_PRIVS_STMT) with this conditions.

The REPLICATE_SQL_STATEMENT is called from an other main procedure.

The returned value from the GET_SYS_PRIVS function is passed to the
REPLICATE_SQL_STATEMENT, which is executed in the remote database.

I get from my function a tring like this one:

GRANT DROP ANY TABLE, EXECUTE ANY PROCEDURE, ... until 115 privileges.

I have just tested this function and it is working, returning me all the 115
privileges without any error.

But the problem is when the string is sent to the EXECUTE IMMEDIATE command in
the remote database.

Any advice will be really appreciated.

Sincerely I appreciate your valuable help.

Best regards.

          Followup:

well, seeing as how DDL over dblinks isn't a permitted operation -- that could
have something to do with it.

You'll want to use dbms_job@remote site to schedule a stored procedure to
execute after you commit -- so the ddl is a local transaction.

And - you always want to include error messages and codes!

Also -- please use bind variables -- it is easier to use binds then NOT use
binds with execute immediate!!

execute immediate 'begin procedure( :x ); end;' using some_string;

      Rating: 3
Max string
      length in execute immediate  March 05, 2003

Reviewer:  Ray White  from Ottawa,Ont
Please excuse tacking on this question but ...
You have indicated that the max string length for execute immediate is 32K.

We are using execute immediate to create generated packages and we are currently
passing it > 35000 chars

by execute immediate v_myvc1 || my_vc2

vc1 and vc2 are 32 k varchar2 vars.
whose combined length is currently > 35000

All on 8.1.7

My Question is what is the maximum length for the execute immediate string cause
I was worried it was 32k and we are already over it, and I'm not sure when I'm
going to hit the wall.

          Followup:

interesting -- never would have thought to do it that way.

That appears to work -- will it hit a wall?  not sure, I would never have gone
over 32k.

looks like it can go pretty large:

ops$tkyte@ORA817DEV> declare
  2          l_str1 long := 'select /* ' || rpad( '*', 20000, '*' ) || ' */ * ';
  3          l_str2 long := 'from /* '   || rpad( '*', 15000, '*' ) || ' */
dual';
  4          l_str3 long := '/* '   || rpad( '*', 32000, '*' ) || ' */ ';
  5          l_result dual.dummy%type;
  6  begin
  7          execute immediate l_str1 || l_str2 || l_str3 || l_str3 || l_str3 ||
' d' into l_result;
  8          dbms_output.put_line( l_result );
  9  end;
 10  /
X

PL/SQL procedure successfully completed.

      Rating:
      5
Interesting  March 05, 2003
Reviewer:
      trevor
Never had to go >32k but
interesting that this works..

      Rating: 5
For no
      particular reason....  March 06, 2003
Reviewer:
      Mike Jones  from England
Oracle8i Enterprise Edition Release 8.1.7.3.0 - Production on HP-UX seems to
accept an sql statement of 143,799 charecters

While running Personal Oracle9i Release 9.2.0.1.0 - Production on a ropey Win2K
laptop only gets up to 65,535 charecters

I hazard a guess that it's either platform dependent or has been shrunk to a
sensible length in 9i (or is a diff between Personal and Enterprise Oracle, or
is a Unix / Win thing or is a ... )

Why anyone would want to execute 143K sql statement is beyond me though...

          Followup:

I would suggest this:

o keep it below 32k for execute immediate
o use DBMS_SQL to parse anything over 32k as it supports a documented "pass me
  a sql statement of any length in an array, a line at a time and I'll do it for
  you"

I would consider it unreliable to use execute immediate with more then 32k.

      Rating: 5
Max Length of
      execute immediate  March 06, 2003
Reviewer:  Ray
      White  from Ottawa,ON,CA
Thanks for the feedback
Great site.

      Rating:
      4
Great  November 19, 2003
Reviewer:
      Kalyan A Samaddar  from Chennai, India
So far I was thinking that size is the only difference between EXECUTE IMMEDIATE
and DBMS_SQL. Now it's pretty clear that size of the string is not a limitation
of EXECUTE IMMEDIATE in any practical situation.

Is there any difference between EXECUTE IMMEDIATE and DBMS_SQL then ?

          Followup:

dbms_sql can

a) parse once
b) execute over and over and over

execute immediate cannot do that. it parse/executes each time.

November 21, 2003 Yogeeraj -- Thanks for the question regarding
      "Using Statspack report ", version 8.1.7

      You Asked
Hello,

I have been going through instructions in your book about how to install the
statspack and run statspack reports (Expert One-on-one oracle: Chapter 10 page
477 to 482).

Could you please check if my observation are correct and advise on anything i
can do to fine-tune my system for better performance.

Below, two snap which i took at different high load period.
SNAP1
======================================================================
                Snap Id     Snap Time      Sessions
                ------- ------------------ --------
 Begin Snap:          1 29-Apr-02 12:38:43      266
   End Snap:         11 29-Apr-02 13:15:12      266
    Elapsed:                  36.48 (mins)

Cache Sizes
~~~~~~~~~~~
  db_block_buffers:      10000          log_buffer:     163840
     db_block_size:       8192    shared_pool_size:   82428800

Load Profile
~~~~~~~~~~~~                   Per Second       Per Transaction
                          ---------------       ---------------
         Redo size:              7,699.65              7,052.11
     Logical reads:              7,180.68              6,576.78
     Block changes:                 48.16                 44.11
    Physical reads:                224.90                205.98
   Physical writes:                  5.62                  5.15
        User calls:                180.12                164.98
            Parses:                 15.55                 14.24
       Hard parses:                  1.19                  1.09
             Sorts:                 22.25                 20.38
            Logons:                  0.45                  0.42
          Executes:                 81.30                 74.46
      Transactions:                  1.09

  % Blocks changed per Read:    0.67    Recursive Call %:   38.27
 Rollback per transaction %:   69.41       Rows per Sort:   10.52

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:  100.00
            Buffer  Hit   %:   96.87    In-memory Sort %:   99.86
            Library Hit   %:   97.35        Soft Parse %:   92.33
         Execute to Parse %:   80.87         Latch Hit %:  100.00
Parse CPU to Parse Elapsd %:             % Non-Parse CPU:

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   88.06   85.38
    % SQL with executions>1:   88.19   92.01
  % Memory for SQL w/exec>1:   82.69   93.67
======================================================================
SNAP2
======================================================================
                Snap Id     Snap Time      Sessions
                ------- ------------------ --------
 Begin Snap:         61 02-May-02 14:55:56      237
   End Snap:         62 02-May-02 15:17:55      237
    Elapsed:                  21.98 (mins)

Cache Sizes
~~~~~~~~~~~
 db_block_buffers:      10000          log_buffer:     163840
    db_block_size:       8192    shared_pool_size:   82428800

Load Profile
~~~~~~~~~~~~                  Per Second       Per Transaction
                         ---------------       ---------------
        Redo size:              4,658.84              5,288.31
    Logical reads:              4,548.81              5,163.40
    Block changes:                 26.18                 29.72
   Physical reads:                150.39                170.71
  Physical writes:                 40.72                 46.22
       User calls:                208.41                236.57
           Parses:                 20.45                 23.21
      Hard parses:                  0.70                  0.80
            Sorts:                 17.50                 19.86
           Logons:                  0.37                  0.42
         Executes:                 85.30                 96.83
     Transactions:                  0.88

  % Blocks changed per Read:    0.58    Recursive Call %:   25.15
 Rollback per transaction %:   64.03       Rows per Sort:   70.30

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:   99.99
            Buffer  Hit   %:   96.69    In-memory Sort %:   99.78
            Library Hit   %:   98.61        Soft Parse %:   96.56
         Execute to Parse %:   76.03         Latch Hit %:  100.00
Parse CPU to Parse Elapsd %:             % Non-Parse CPU:

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   94.52   94.11
    % SQL with executions>1:   85.31   86.43
  % Memory for SQL w/exec>1:   83.11   86.72
======================================================================

My observations:
-
- Memory usage % is very high! this might cause performance degradation in some
cases due to components being aged out by the shared pool and SQL hard passes
when re-executed.
- "SQL with executions>1" and "% memory for SQL w/exec>1" are high and good in
my case.
- "Rollback per transaction %" is too high! (any tip on how i can investigate
into it? I have hundreds of forms currently being used.)
- "Instance Efficieny percentages" are very good.
- My "parse-related" statistics are very good.

Doubts (need clarifications):
- Cache Sizes: I was just comparing the example given in you book and my current
configuration. Seems like my db_block_buffers and log_buffer are not properly
set.
- Redo size in the "Load Profile" section: In most cases i have seen them to be
high and "per second" values close to "per transaction" values. Is there
something wrong?

Thank you for your precious time (i guess we all here realize it now, you as a
VP and also replying to question during week-ends! ;)...and you wrote a 1000's
page book too!).

Best Regards
Yogeeraj

      and we said...
o yes, memory usage is high and it is due to the excessive hard parsing.  I like
a soft parse % much close to 100% myself.  Rather then making memory larger
(that would be the "common" approach), recommend you seek out and destroy the
applications that are doing this to you.

o yes.

o rollback / transaction is way too high.  You can find out if it is a specific
session doing it or if all sessions do it via v$sesstat.  If one session does it
-- you can investigate why.  If all do it - then you have a very broad problem
in your application and you'll need to investigate why this might be the case
(there is nothing we can really do to help you pinpoint it more then that)

o i would say the execute to parse is low, the soft parse is low.  The others
look ok.

o disagree, you parse too much and you hard parse WAY too much.

I would say your shared pool looks sized OK, block buffers might be smallish at
78m but that is hard to say.  One would have to see the top 5 waits to see if
your log buffer is too small (probable).  We would be looking for log related
waits there.

4/5k of redo per transaction is very reasonble (small even).  Given that per
second is about the same as per transaction -- that just means you do 1 tps,
nothing to be derived from that.

Review &
      Followup

      Rating: 5
most useful.
      some more clarifications would be most welcomed.  May 07,
      2002
Reviewer:  Yogeeraj  from Mauritius
Thank you for your precious time in looking at my stats.

<quote>
I would say your shared pool looks sized OK, block buffers might be smallish at
78m but that is hard to say.  One would have to see the top 5 waits to see if
your log buffer is too small (probable).  We would be looking for log related
waits there.
</quote>

It's hard understanding this part (please bear with me..)
Below additional info from the same 2 statspack report above:
SNAP1
============================================================
Top 5 Wait Events
~~~~~~~~~~~~~~~~~                       Wait     % Total
Event                         Waits  Time (cs)   Wt Time
---------------------------- ------ ------------ -------
db file scattered read       56,100            0     .00
db file sequential read      38,457            0     .00
SQL*Net message to dblink    19,606            0     .00
SQL*Net message from dblink  19,600            0     .00
direct path read             11,006            0     .00
============================================================
SNAP2
============================================================
Top 5 Wait Events
~~~~~~~~~~~~~~~~~                  Wait      % Total
Event                       Waits  Time (cs) Wt Time
--------------------------- ------ --------- -------
db file sequential read     38,542         0     .00
direct path read            26,445         0     .00
SQL*Net message to dblink   20,709         0     .00
SQL*Net message from dblink 20,701         0     .00
db file scattered read      13,592         0     .00
============================================================

Also, i extracted all "log related waits" information:
SNAP1
============================================================
Wait Events:
                                                                    Avg
                                                     Total Wait    wait  Waits
Event                               Waits   Timeouts  Time (cs)    (ms)   /txn
---------------------------- ------------ ---------- ----------- ------ ------
log file parallel write             1,355          0           0      0    0.6
log file sync                         862          0           0      0    0.4
log file sequential read              322          0           0      0    0.1
log buffer space                       18          0           0      0    0.0
log file single write                   2          0           0      0    0.0
log file switch completion              2          0           0      0    0.0

Background Wait Events:
log file parallel write             1,355          0           0      0    0.6
log file sequential read              322          0           0      0    0.1
log file single write                   2          0           0      0    0.0
============================================================

SNAP2
============================================================
Wait Events:

log file parallel write               723          0           0      0    0.6
log file sync                         520          0           0      0    0.4
log file sequential read              322          0           0      0    0.3
log buffer space                       12          0           0      0    0.0
log file single write                   2          0           0      0    0.0
log file switch completion              1          0           0      0    0.0

Background Wait Events:
log file parallel write               722          0           0      0    0.6
log file sequential read              322          0           0      0    0.3
log file single write                   2          0           0      0    0.0

============================================================

Another 2 questions:
a. If with the same setup, we add 10% more users. What will we observe in the
Load Profile?

b. If i was to add another application to my system (mainly batch processing
oriented and which would normally ocurr between 8:00 a.m to 10:30 a.m.), how
would i go about my analysis? How can i forecast the amount of performance
degradation (if any) and what are the values that i would eye so as to take
corrective measures? Assuming the new application have the same flaws as my
current one.

Thank you again for your precious guidance.

Best Regards
Yogeeraj
PS. I did not get any notification message about your reply. Do i assume a
problem with my mail server?

          Followup:

A statspack report done without timed_statistics is virtually useless.

All I was saying was

a) your shared pool looks fine
b) your buffer cache is on the small side
c) your log buffer is small but to see if that is a problem, we'd need to see if
you are waiting on the logs.  We cannot tell if you are since you have NO
TIMINGS.

As for "add another 10%" -- I've no idea.  You cannot tell that from a
statspack.  You have to look at the OS.  If the OS is fully consumed, adding 10%
will kill your system.  If you are running at 50% capacity, adding 10% will make
you run at 60-65%.

As for the other thing -- again, you cannot tell from a statspack.  you have to
look at the OS.  What will the batch job do -- eat CPU?  chew up IO?

I sent you two followups yesterday:

06-MAY-02
Date: 06 May 02 08:16:07 | MIME-Version: 1.0 | To: ydegambur@cmt.mu | From:
thomas.kyte@oracle.com |
 Subject: [RE] Using Statspack report  | Reply-To: thomas.kyte@oracle.com |
Content-Type: text/plain
 |
Mon MAY 06  08:16:17 2002

06-MAY-02
Date: 06 May 02 08:16:12 | MIME-Version: 1.0 | To: ydegambur@cmt.mu | From:
thomas.kyte@oracle.com |
 Subject: [RE] Using Statspack report  | Reply-To: thomas.kyte@oracle.com |
Content-Type: text/plain
 |
Mon MAY 06  08:16:22 2002

times are east coast of the US.

      Rating: 5
thank
      you  May 07, 2002
Reviewer:  Yogeeraj
       from Mauritius
thank you very much for the reply.

I will have to spend more time evaluating the impact of the new application
going live and take appropriate pro-active measures.

regards
Yogeeraj
PS. still sorting out the email problem with my mail server admin.

          Followup:

turns out it was on my end (the email issue).  It is corrected now.

      Rating: 5
Negative
      Execute to Parse  May 07, 2002
Reviewer:
      A reader
I am seeing a negative value in the Execute to Parse %.

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:   99.92       Redo NoWait %:  100.00
            Buffer  Hit   %:   97.15    In-memory Sort %:   96.11
            Library Hit   %:   99.89        Soft Parse %:   99.92
         Execute to Parse %:  -63.17         Latch Hit %:   99.95
Parse CPU to Parse Elapsd %:   62.72     % Non-Parse CPU:  100.00

What is wrong?

          Followup:

You are parsing MORE then you execute.

The formula is:

Execute to Parse %:'                  dscr
      , round(100*(1-:prse/:exe),2)                    pctval

If prse (parse) is greater then exe (execute), then prse/exe is greater then one
and the number goes negative.

Indicates a really BAD situation.  You are parsing queries two or more times
without executing them!

      Rating:
      3
Statpack  May 07, 2002
Reviewer:
      kiro
Tom
May be this is not in line of discussion, but how to install this Statpack( ver.
8.1.6 on NT)?
Thanks

          Followup:

Same as on unix ;)

A short excerpt from chapter 10 of my book "Expert one on one" is:

....
Setting up StatsPack

StatsPack is designed to be installed when connected as INTERNAL or more
appropriately, as SYSDBA (connect sys/manager as SYSDBA) although it will
execute a connect internal. In order to install, you must be able to perform
that operation. In many installations, this will be a task you must ask the DBA
or administrators to perform.

Once you have the ability to connect internal, installing StatsPack is trivial.
You simply run statscre.sql in 8.1.6 or spcreate.sql in 8.1.7. These will be
found in $ORACLE_HOME/rdbms/admin when connected as internal via SQL*Plus. It
would look something like this:
..............

      Rating:
      5
timeouts  September 11, 2002
Reviewer:
      Todd Laubach  from Bloomington, MN
Tom,
What is the meaning of the timeouts column in the Wait Events section of the
statspack report?

          Followup:

some things try to do something and wait for a fixed amount of time.  If no joy
in that time, they "timeout" and perhaps try again or fail.  So, for example:

                                                                    Avg
                                                     Total Wait    wait  Waits
Event                               Waits   Timeouts  Time (cs)    (ms)   /txn
---------------------------- ------------ ---------- ----------- ------ ------
...
rdbms ipc message                     215        199      61,260   2849  107.5
...

<quote src=reference manual>
rdbms ipc message

The background processes (LGWR, DBWR, LCK0) use this event to indicate that they
are idle and are waiting for the foreground processes to send them an IPC
message to do some work.

Wait Time: Up to 3 seconds. The parameter timeout shows the true sleep time.
</quote>

so, that just shows the rdbms ipc message wait event happened 215 times during
my statspack -- 199 of them "timed out" (eg: DBWR or someone waited for 3
seconds for something to happen, nothing did so they timed out -- looked around
and seeing nothing important to do, went back and waited again (or did
something) )

      Rating: 5
SQL in
      statspack  April 25, 2003
Reviewer:  Tatiane

From the SQL report:
                                  CPU      Elapsd
  Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
      9,192,936       22,045          417.0    2.5   946.15   1062.41  238087931
select t.schema, t.name, t.flags, q.name from system.aq$_queue_tables t,
sys.aq$_queue_table_affinities aft,      system.aq$_que
ues q where aft.table_objno = t.objno and aft.owner_instance = :
1 and        q.table_objno = t.objno and q.usage = 0 and       b
itand(t.flags, 4+16+32+64+128+256) = 0 for update of t.name, aft

...

      3,406,887      484,990            7.0    0.9   235.18    291.73 3460529092
select t.name,  (select owner_instance from sys.aq$_queue_table_
affinities   where table_objno = t.objno)  from system.aq$_queue
_tables t where t.name = :1 and t.schema = :2 for update skip lo
cked

This report is for the interval between a couple of days. 3 questions:

1. Why is it reporting on AQ ? I don't use it, as far as I know.

2. Why do they have so high buffer gets ?

3. Since the report spans the time between the snaps, to what period do the sql
commands belong ? Are they only those sql commands that persisted across the
snaps ?

Thanks, Tom

          Followup:

a couple of days?  that is useless -- 15-30 minutes max on a statspack report.

what does

show parameter aq_tm_processes

show you -- if it is > 0, yes, yes you are using AQ.

only the statements that happened between snapshots are shown.

      Rating:
      3
  May 12, 2003
Reviewer:
      A reader
show parameter aq_tm_processes
NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
aq_tm_processes                      integer     1

I have the default install of 9i R1, and I did not set up AQ. Why does it come
out as default?

Thanks

          Followup:

because AQ is there always.  if you don't want it, set it to zero.

      Rating: 5
the "SQL
      ordered by Gets " section  July 03, 2003
Reviewer:
      Yogeeraj  from Mauritius, Indian Ocean
Hi,

I am used to analyse my STATSPACK report once every week (especially when my
users starts complaining about "performance" problems - well, fortunately most
of the time, it has been network problem over our Frame Relay WAN.

Currently, there is one SQL in the "SQL ordered by Gets for DB: " section of the
reports that keeps giving me the same SQL on top, namely:
================================================================
select parent_owner,parent_name,parent_link_name,parent_type,par
ent_timestamp,property from ora_kglr7_dependencies where owner=:
1 and name=:2 and type=:3 and obj#=:4 order by order_number
================================================================

For example:

Day 1 at 09:06:59: [Elapsed: 15.67 (mins)]
  Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
      1,355,159          719        1,884.8     7.4   2838017588
select parent_owner,parent_name,parent_link_name,parent_type,par
ent_timestamp,property from ora_kglr7_dependencies where owner=:
1 and name=:2 and type=:3 and obj#=:4 order by order_number

Day 2 at 12:29:36: [Elapsed:15.18 (mins) - a batch job was running at that time]
      6,908,475            1    6,908,475.0    48.0   2924315239
UPDATE OPT320 SET ( CRTQTY,EXPQTY  )=(SELECT NVL(SUM(DECODE(CRT2
00V_2.TRNCODE,'IN',CRT200V_2.PCECRT)),0),NVL(SUM(DECODE(CRT200V_
2.TRNCODE,'OUT',CRT200V_2.PCECRT)),0)   FROM SWPROD.CRT200V_2  W
HERE ORDNUM = OPT320.ORDNUM  AND CLRSEQ = OPT320.CLRSEQG  AND SZ
ESEQ = OPT320.SZESEQ  AND GMTSEQ = OPT320.GMTSEQ ),( RBPQTY,RAPQ

      2,017,958        1,062        1,900.1    14.0   2838017588
select parent_owner,parent_name,parent_link_name,parent_type,par
ent_timestamp,property from ora_kglr7_dependencies where owner=:
1 and name=:2 and type=:3 and obj#=:4 order by order_number

Day 2 at 13:42:26: [Elapsed: 15.28 (mins)]
  Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
      1,749,879          924        1,893.8    15.4   2838017588
select parent_owner,parent_name,parent_link_name,parent_type,par
ent_timestamp,property from ora_kglr7_dependencies where owner=:
1 and name=:2 and type=:3 and obj#=:4 order by order_number

Day 3 (today): [Elapsed: 38.93 (mins)]
  Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
      4,578,962        2,420        1,892.1    14.1   2838017588
select parent_owner,parent_name,parent_link_name,parent_type,par
ent_timestamp,property from ora_kglr7_dependencies where owner=:
1 and name=:2 and type=:3 and obj#=:4 order by order_number

Does this "smell" any problem?

thank you in advance for your time and reply

regards
Yogeeraj

          Followup:

that is an internal query, you'll want to contact support if you feel this is a
performance issue for you.  reference the number "1944773" (it is closed as not
a bug), there is a possible solution, but given I know nothing about your site,
best to discuss with them

      Rating:
      5
  July 07, 2003
Reviewer:
      A reader
Hi Tom,

I have lots of timeouts on log file parallel write wait event. Is it because my
log files are in the same RAID5 diskset?

What can I do in the database to make the situation better on this existing
hardware? Hardware configuration:

Disk #1, #2: RAID 1+0 for OS (Win2K)
Disk #3,#4,#5: RAID 5 for Oracle database (including software)
2G Memoery with 1.8Ghz Xeon processor

Thanks.

STATSPACK report for

DB Name         DB Id    Instance     Inst Num Release     Cluster Host
------------ ----------- ------------ -------- ----------- ------- ------------
PAGO04        1502736998 pago04              1 9.2.0.3.0   NO      KTCSRV005

            Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:     781 07-Jul-03 14:38:03       30       4.0
  End Snap:     782 07-Jul-03 15:03:16       39       5.5
   Elapsed:               25.22 (mins)

Cache Sizes (end)
~~~~~~~~~~~~~~~~~
               Buffer Cache:       120M      Std Block Size:         8K
           Shared Pool Size:        56M          Log Buffer:       512K

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:              1,284.15            176,628.36
              Logical reads:                213.57             29,375.00
              Block changes:                  7.50              1,031.09
             Physical reads:                  0.15                 21.09
            Physical writes:                  0.65                 89.00
                 User calls:                  1.04                143.36
                     Parses:                  1.95                268.09
                Hard parses:                  0.02                  2.55
                      Sorts:                  0.69                 94.27
                     Logons:                  0.04                  5.45
                   Executes:                  3.30                453.27
               Transactions:                  0.01

  % Blocks changed per Read:    3.51    Recursive Call %:    90.43
 Rollback per transaction %:    0.00       Rows per Sort:    16.53

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:  100.00
            Buffer  Hit   %:   99.93    In-memory Sort %:  100.00
            Library Hit   %:   97.79        Soft Parse %:   99.05
         Execute to Parse %:   40.85         Latch Hit %:   99.98
Parse CPU to Parse Elapsd %:   94.20     % Non-Parse CPU:   83.20

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   95.66   95.81
    % SQL with executions>1:   81.51   82.97
  % Memory for SQL w/exec>1:   77.44   79.50

Top 5 Timed Events
~~~~~~~~~~~~~~~~~~                                                     % Total
Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
CPU time                                                            4    59.02
db file sequential read                               240           1    13.38
log file parallel write                             1,829           1     8.92
control file parallel write                           492           1     8.36
db file parallel write                                 66           0     5.15
          -------------------------------------------------------------
 Wait Events for DB: PAGO04  Instance: pago04  Snaps: 781 -782
-> s  - second
-> cs - centisecond -     100th of a second
-> ms - millisecond -    1000th of a second
-> us - microsecond - 1000000th of a second
-> ordered by wait time desc, waits desc (idle events last)

                                                                   Avg
                                                     Total Wait   wait    Waits
Event                               Waits   Timeouts   Time (s)   (ms)     /txn
---------------------------- ------------ ---------- ---------- ------ --------
db file sequential read               240          0          1      4     21.8
log file parallel write             1,829      1,705          1      0    166.3
control file parallel write           492          0          1      1     44.7
db file parallel write                 66          0          0      5      6.0
control file sequential read          371          0          0      0     33.7
latch free                             15          0          0      8      1.4
log file sync                          66          0          0      0      6.0
SQL*Net break/reset to clien            4          0          0      0      0.4
SQL*Net more data to client             6          0          0      0      0.5
LGWR wait for redo copy                 1          0          0      0      0.1
SQL*Net message from client         1,676          0     16,047   9575    152.4
virtual circuit status                 51         51      1,524  29886      4.6
wakeup time manager                    48         48      1,465  30528      4.4
SQL*Net message to client           1,685          0          0      0    153.2
          -------------------------------------------------------------
 Background Wait Events for DB: PAGO04  Instance: pago04  Snaps: 781 -782
-> ordered by wait time desc, waits desc (idle events last)

                                                                   Avg
                                                     Total Wait   wait    Waits
Event                               Waits   Timeouts   Time (s)   (ms)     /txn
---------------------------- ------------ ---------- ---------- ------ --------
log file parallel write             1,829      1,705          1      0    166.3
control file parallel write           492          0          1      1     44.7
db file parallel write                 66          0          0      5      6.0
control file sequential read          321          0          0      0     29.2
db file sequential read                 2          0          0      6      0.2
LGWR wait for redo copy                 1          0          0      0      0.1
rdbms ipc message                   4,988      3,213      7,564   1516    453.5
pmon timer                            514        514      1,511   2940     46.7
smon timer                              5          5      1,279 ######      0.5
          -------------------------------------------------------------
 SQL ordered by Gets for DB: PAGO04  Instance: pago04  Snaps: 781 -782
-> End Buffer Gets Threshold:   10000
-> Note that resources reported for PL/SQL includes the resources used by
   all SQL statements called within the PL/SQL code.  As individual SQL
   statements are also reported, it is possible and valid for the summed
   total % to exceed 100

                                                     CPU      Elapsd

          Followup:

well...  hmmm... looking at ALL of the numbers...

in 25 minutes....

  you had 1 second of wait....
  with 4 whole seconds of cpu time used....
  to perform 0.01 TPS...

    and this is a problem why?.....

ctd (compulsive tuning disorder) kicking in?

but yes, that wait is due at least in part to you using RAID 5 for a database (a
somewhat write IO intensive thing, raid 5 being about the slowest thing possible
for write IO)....

but you gave the fast disks to the thing that needed a boost the most ;)

      Rating:
      4
terminology  July 28, 2003
Reviewer:
      Reader
Tom, If time permits, could you please explain the meaning of the following
terminology to understand the information in statspack.

db file parallel write
control file parallel write
db file sequential read
db file scattered read
log file parallel write.

          Followup:

those are "wait events" and we have them documented here:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96536/apa.htm#968373

      Rating: 5
statspack
      window  October 29, 2003
Reviewer:
      A reader
Hi Tom
I know you advocate a window of 30 minutes max. One thought
is - if the reported SQL accounts for a large % of
the reported time then it is ok to go for a longer
period - an hour, 2, 5 hours even may be. This does not
seem correct to me (esp. after reading Cary's book:)).

What are your thoughts?

Love your site and books!

          Followup:

if you have a query that is taking 2-5 hours, statspack is not a tool you'll be
using to manage it.  sql_trace for that query -- sure.  statspack -- no,
statspack is an aggregate view for a window of time, nothing more, nothing less.
 it is not appropriate for tuning "a query"

      Rating:
      5
thanx!  October 30, 2003
Reviewer:
      A reader
what you say makes sense - I guess what they mean is
that if the top sql of statspack points to a query that has been running for say
2 hours or so then may be the
statspack window time is ok (even if it is 2 hours)

Anyways, intuitively it does not make sense to me!

      Rating:
      5
  November 07, 2003
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
Tom,
I have very high Pct miss in library cache and dictionary cache. I have looked
at the statspack reports for the same period for last two months for this
database. The Pct miss for SQL AREA is always more that 50%. Does this mean the
shared pool size it too low?

Library Cache Activity for DB: MCIP  Instance: MCIP  Snaps: 1640 -1641
->"Pct Misses"  should be very low

                         Get  Pct        Pin        Pct               Invali-
Namespace           Requests  Miss     Requests     Miss     Reloads  dations
--------------- ------------ ------ -------------- ------ ---------- --------
BODY                   1,869    0.1          1,869    0.1          0        0
CLUSTER                   57    0.0             61    0.0          0        0
INDEX                      6   50.0              6   50.0          0        0
SQL AREA               8,895   49.6        342,260    2.7        212        0
TABLE/PROCEDURE       47,221    0.2        116,099    0.5        395        0
          -------------------------------------------------------------

          Followup:

it could be

it could be something else -- revokes, ddl, invalidations, compilations, lack of
binds

      Rating: 5
######
      values  November 08, 2003
Reviewer:
      A reader
Hi

in statspack reports I see many time values such as

smon timer                              5          5      1,279 ######      0.5

can we modify the spreport.sql to change that?

          Followup:

sure, it is just a sqlplus script

      Rating: 5
response time
      bigger than snap interval?  November 10, 2003

Reviewer:  A reader
Hi

following note 223117.1 from Metalink, tuning I/O related waits I found my
response time is bigger than the snap interval... Is this possible?!

The report is below:

               Snap Id     Snap Time      Sessions
                ------- ------------------ --------
 Begin Snap:       2551 10-Nov-03 13:13:52      514
   End Snap:       2552 10-Nov-03 13:35:58      514

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
latch free                                         51,692       52,872   22.82
direct path write (lob)                            59,228       47,489   20.50
direct path read (lob)                             62,323       47,076   20.32
db file sequential read                            36,120       25,951   11.20
buffer busy waits                                   8,496       20,879    9.01

Statistic                                    Total   per Second    per Trans
--------------------------------- ---------------- ------------ ------------
CPU used by this session                   215,903        162.8          2.9

according to the note:

Wait time = 52,872 x 100% / 22.82% = 231,691.40 cs
Service time = 215,903 cs
Response time = 215,903 + 231,691.40 = 447,594.40 cs = 4475 seconds aprox

Is this possible...?

          Followup:

suppose we have the following timeline:

         T1                 T2                      T3                    T4
         xxxxxxxxxxxxxxxxxxxxxxxxxxxx
                               yyyyyyyyyyyyyyyyyyyyyyyyyy
                                     zz
         aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

xxxx yyyy zzzz aaa are "calls" -- calls to the database (a session doing a call,
say they were stored procedure calls)

Say you take a snapshot from T2 to T3

well, XXXX will contribute ALL of the cpu time from T1 to T2++
      YYYY will contribute ZERO cpu even though it ran from T2++ to T3++
      ZZ will contribute only the little bit of time it ran for
      AAAA will contribute ZERO cpu time

CPU time is added at the END of the call -- so if you have lots of long running
processes (or just one really long one), it can skew the CPU time by lots

      Rating: 5
so the note is
      totally useless then?  November 10, 2003
Reviewer:
      A reader
Hi

So how can we find the service time in a multi-user environment? I guess the
note's approach only works for single processes.... huh

          Followup:

no, it is not useless.

On a system you care about service times, you would not have egregiously long
running processes (batch).  You would have short sweet OLTP stuff going on.

but also -- your waits can definitely add up to way way way way more then your
elapsed time.

50 people waiting 50 seconds for an enqueue.  2500 seconds of wait.  window
measured in -- 10 minutes (600 seconds)......

also, they are doing things in the aggregate -- at the instance level, so maybe
the note is somewhat "not useful" in that regard.  You cannot realistically
compute service times for individuals from a statspack report like that.

use tkprof and sql_trace -- it is at the session level

      Rating: 3
Statspack
      report in Apps Environment  November 19, 2003

Reviewer:  Sriram  from Florida
Tom,
    How can i make effective use of statspack in a Apps 11i environment when
users complain that there is total system slowdown and CPU pegged at 100%?

Can you please take a look at my statspack report and let me know if there are
any obvious issues?

Thanks,
Sriram

STATSPACK report for

DB Name         DB Id    Instance     Inst Num Release     OPS Host
------------ ----------- ------------ -------- ----------- --- ------------
xxx           785005184 prod                1 8.1.7.4.1   NO  xxx

                Snap Id     Snap Time      Sessions
                ------- ------------------ --------
 Begin Snap:       1084 18-Nov-03 14:26:43      196
   End Snap:       1086 18-Nov-03 14:42:53      196
    Elapsed:                  16.17 (mins)

Cache Sizes
~~~~~~~~~~~
           db_block_buffers:      30000          log_buffer:    1048576
              db_block_size:       8192    shared_pool_size:  300000000

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:             78,822.39             41,871.70
              Logical reads:             30,263.57             16,076.48
              Block changes:                527.92                280.44
             Physical reads:                799.79                424.86
            Physical writes:                 35.61                 18.92
                 User calls:                163.00                 86.59
                     Parses:                 42.68                 22.67
                Hard parses:                  1.27                  0.67
                      Sorts:                284.89                151.34
                     Logons:                  0.17                  0.09
                   Executes:                852.63                452.93
               Transactions:                  1.88

  % Blocks changed per Read:    1.74    Recursive Call %:   91.06
 Rollback per transaction %:    4.22       Rows per Sort:   11.75

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:   99.99       Redo NoWait %:  100.00
            Buffer  Hit   %:   97.36    In-memory Sort %:   99.97
            Library Hit   %:   99.75        Soft Parse %:   97.03
         Execute to Parse %:   94.99         Latch Hit %:   99.97
Parse CPU to Parse Elapsd %:   27.82     % Non-Parse CPU:   99.07

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   91.15   91.62
    % SQL with executions>1:   80.94   75.94
  % Memory for SQL w/exec>1:   66.93   74.37

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
db file sequential read                           208,439      476,250   58.84
db file scattered read                             79,090      285,145   35.23
pipe put                                               79        8,075    1.00
log file sequential read                            4,804        7,504     .93
latch free                                          3,192        7,410     .92
          -------------------------------------------------------------
 Wait Events for DB: PROD  Instance: prod  Snaps: 1084 -1086
-> cs - centisecond -  100th of a second
-> ms - millisecond - 1000th of a second
-> ordered by wait time desc, waits desc (idle events last)

                                                                    Avg
                                                     Total Wait    wait  Waits
Event                               Waits   Timeouts  Time (cs)    (ms)   /txn
---------------------------- ------------ ---------- ----------- ------ ------
db file sequential read           208,439          0     476,250     23  114.2
db file scattered read             79,090          0     285,145     36   43.3
pipe put                               79         76       8,075   1022    0.0
log file sequential read            4,804          0       7,504     16    2.6
latch free                          3,192      2,869       7,410     23    1.7
enqueue                                20         18       5,586   2793    0.0
log file sync                       1,740          0       5,169     30    1.0
file open                             930          0       5,023     54    0.5
buffer busy waits                   3,933          1       4,314     11    2.2
queue messages                         19         19       2,015   1061    0.0
log file parallel write             2,237          0       1,340      6    1.2
control file sequential read          328          0         489     15    0.2
direct path read                      418          0         358      9    0.2
log file switch completion              3          0         164    547    0.0
SQL*Net more data to client         7,499          0         136      0    4.1
direct path write                     254          0          99      4    0.1
db file parallel write                216          0          91      4    0.1
control file parallel write           325          0          86      3    0.2
refresh controlfile command            20          0          56     28    0.0
file identify                           8          0          26     33    0.0
SQL*Net break/reset to clien          252          0          21      1    0.1
log buffer space                        8          0          20     25    0.0
library cache pin                       5          0          16     32    0.0
LGWR wait for redo copy                48          4           8      2    0.0
log file single write                   4          0           0      0    0.0
buffer deadlock                         1          1           0      0    0.0
SQL*Net message from client       160,226          0   9,678,556    604   87.7
wakeup time manager                    33         31      94,933  28768    0.0
SQL*Net more data from clien        2,323          0          98      0    1.3
SQL*Net message to client         160,221          0          20      0   87.7
          -------------------------------------------------------------
 Background Wait Events for DB: PROD  Instance: prod  Snaps: 1084 -1086
-> ordered by wait time desc, waits desc (idle events last)

                                                                    Avg
                                                     Total Wait    wait  Waits
Event                               Waits   Timeouts  Time (cs)    (ms)   /txn
---------------------------- ------------ ---------- ----------- ------ ------
log file sequential read            4,804          0       7,504     16    2.6
log file parallel write             2,237          0       1,340      6    1.2
db file scattered read                333          0         583     18    0.2
db file parallel write                216          0          91      4    0.1
control file sequential read           91          0          89     10    0.0
control file parallel write           325          0          86      3    0.2
db file sequential read                19          0          49     26    0.0
file identify                           8          0          26     33    0.0
latch free                              3          3          16     53    0.0
LGWR wait for redo copy                48          4           8      2    0.0
file open                               7          0           8     11    0.0
rdbms ipc reply                        10          0           2      2    0.0
log file single write                   4          0           0      0    0.0
rdbms ipc message                   7,469        917     549,679    736    4.1
smon timer                              4          3     100,292 ######    0.0
pmon timer                            312        312      97,124   3113    0.2
          -------------------------------------------------------------
 SQL ordered by Gets for DB: PROD  Instance: prod  Snaps: 1084 -1086
-> End Buffer Gets Threshold:   10000
-> Note that resources reported for PL/SQL includes the resources used by
   all SQL statements called within the PL/SQL code.  As individual SQL
   statements are also reported, it is possible and valid for the summed
   total % to exceed 100

  Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
     12,404,196       25,943          478.1    42.3    470331148
SELECT 'Y'   FROM FND_ATTACHED_DOCUMENTS T1,FND_DOCUMENTS_TL T2,
FND_DOCUMENTS T3  WHERE T1.ENTITY_NAME = :b1  AND T1.PK1_VALUE =
 :b2  AND T1.DOCUMENT_ID = T2.DOCUMENT_ID  AND T2.DOCUMENT_ID =
T3.DOCUMENT_ID  AND T3.DATATYPE_ID = 100

      5,745,997            2    2,872,998.5    19.6   1162758779
declare  rc__ number; begin  owa.init_cgi_env(:n__,:nm__,:v__);
 htp.HTBUF_LEN := 255;  null;  arw_pages.invoice_table;  if (wpg
_docload.is_file_download) then   rc__ := 1;   wpg_docload.get_d
ownload_file(:doc_info);   null;   commit;  else   rc__ := 0; nu
ll;   commit;   owa.get_page(:data__,:ndata__);  end if;  :rc__

      4,376,840            3    1,458,946.7    14.9    328885768
SELECT CONCURRENT_PROGRAM_ID,PROGRAM_APPLICATION_ID,PRINTER,PROG
RAM_SHORT_NAME,ARGUMENT_TEXT,PRINT_STYLE,USER_PRINT_STYLE,SAVE_O
UTPUT_FLAG,ROW_ID,ACTUAL_COMPLETION_DATE,COMPLETION_TEXT,PARENT_
REQUEST_ID,REQUEST_TYPE,FCP_PRINTER,FCP_PRINT_STYLE,FCP_REQUIRED
_STYLE,LAST_UPDATE_DATE,LAST_UPDATED_BY,REQUESTED_BY,HAS_SUB_REQ

      2,039,585        7,587          268.8     6.9   3873667830
begin AP_APPROVAL_PKG.APPROVE(:P_RUN_OPTION, :P_INVOICE_BATCH_ID
, :P_BEGIN_INVOICE_DATE, :P_END_INVOICE_DATE, :P_VENDOR_ID, :P_P
AY_GROUP, :P_INVOICE_ID, :P_ENTERED_BY, :P_SET_OF_BOOKS_ID, :P_T
RACE_OPTION, :P_CONC_FLAG, :P_HOLDS_COUNT, :P_APPROVAL_STATUS, :
P_CALLING_SEQUENCE); end;

      1,587,220            1    1,587,220.0     5.4   4160269578
SELECT SUBSTR(akq.AL_CLASS_MEANING,1,4000), SUBSTR(akq.TRX_NUMBE
R,1,4000), SUBSTR(akq.AL_STATUS_MEANING,1,4000), SUBSTR(akq.TRX_
DATE,1,4000), SUBSTR(akq.RAC_CUSTOMER_NAME,1,4000), SUBSTR(akq.C
T_PURCHASE_ORDER,1,4000), SUBSTR(akq.AMOUNT_DUE_ORIGINAL,1,4000)
, SUBSTR(akq.AMOUNT_DUE_REMAINING,1,4000), SUBSTR(akq.CUSTOMER_T

      1,284,729           14       91,766.4     4.4   2586771312
declare x_message_code varchar2 ( 100 ) ; BEGIN x_message_code :
= null ; pa_billing . bill_ext_driver ( :par_project_id , :par_c
alling_process , :par_calling_place , :par_acc_thru_dt , :par_re
quest_id , x_message_code ) ; :message:i_message := x_message_co
de ; END ;

      1,241,280            6      206,880.0     4.2   1696222480
UPDATE PA_EVENTS PE SET REVENUE_DISTRIBUTED_FLAG='Y' WHERE EXIST
S  (SELECT 1   FROM GSI_COST_OF_SALES COS  WHERE LINE_NUM IS NUL
L  AND COS.EVENT_ID = :b1  AND COS.MANUAL_EVENT_ID = PE.EVENT_ID
 )

      1,085,511            1    1,085,511.0     3.7    362512440
SELECT             scope,     project_num,     sum(currev) Sales
,     (sum(currev) - sum(curcost)) CV,     decode(sum(currev),0,
0,(sum(currev) - sum(curcost))*100/sum(currev)) CV_percent,
 SQL ordered by Gets for DB: PROD  Instance: prod  Snaps: 1084 -1086
-> End Buffer Gets Threshold:   10000
-> Note that resources reported for PL/SQL includes the resources used by
   all SQL statements called within the PL/SQL code.  As individual SQL
   statements are also reported, it is possible and valid for the summed
   total % to exceed 100

  Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
sum(reg_amt) reg_amt,     sum(ovt_amt) ovt_amt,     sum(dot_amt)
 dot_amt,     location,     project_id,     address_id FROM

        834,434           14       59,602.4     2.8   1255574089
declare  rc__ number; begin  owa.init_cgi_env(:n__,:nm__,:v__);
 htp.HTBUF_LEN := 255;  null;  OracleSSWA.Execute(E=>:E);  if (w
pg_docload.is_file_download) then   rc__ := 1;   wpg_docload.get
_download_file(:doc_info);   null;   commit;  else   rc__ := 0;
null;   commit;   owa.get_page(:data__,:ndata__);  end if;  :rc_

        796,528           10       79,652.8     2.7    423950914
BEGIN WF_ENGINE.BACKGROUNDCONCURRENT( :errbuf, :rc,:A0,:A1,:A2,:
A3,:A4,:A5); END;

        775,604           12       64,633.7     2.6   1335613946
SELECT /*+ ORDERED USE_NL (WIASP WI WPAP WAP)             INDEX
(WIASP WF_ITEM_ACTIVITY_STATUSES_N1)  */WIASP.ROWID ROW_ID   FRO
M WF_ITEM_ACTIVITY_STATUSES WIASP,WF_ITEMS WI,WF_PROCESS_ACTIVIT
IES WPAP,WF_ACTIVITIES WAP  WHERE WIASP.ITEM_TYPE = :b1  AND WIA
SP.PROCESS_ACTIVITY = WPAP.INSTANCE_ID  AND WPAP.ACTIVITY_ITEM_T

        752,819          407        1,849.7     2.6   1857750303
SELECT NVL(SUM(DECODE(ITEM.EXPENDITURE_TYPE,'REGULAR TIME',ITEM.
QUANTITY,0.00)),0) REG_AMT,NVL(SUM(DECODE(ITEM.EXPENDITURE_TYPE,
'OVERTIME',ITEM.QUANTITY,0.00)),0) OVT_AMT,NVL(SUM(DECODE(ITEM.E
XPENDITURE_TYPE,'DOUBLE TIME',ITEM.QUANTITY,0.00)),0) DOT_AMT
FROM PA_TASKS PAT,PA_EXPENDITURE_ITEMS ITEM,PA_PROJECT_CLASSES P

        731,939       56,303           13.0     2.5    885722162
(SELECT STATUS_FLAG   FROM IGI_GCC_INSTALLED_OPTIONS  WHERE UPPE
R(OPTION_NAME) = UPPER(:b1) ) UNION (SELECT STATUS_FLAG   FROM I
GI_GCC_GL_FA_INST_OPS  WHERE UPPER(OPTION_NAME) = UPPER(:b1) )

        548,295            3      182,765.0     1.9   3495154319
begin PA_SELF_SERVICE_HANDLER_PVT.CREATENEWTIMECARD; end;

        410,188          211        1,944.0     1.4   2838017588
select parent_owner,parent_name,parent_link_name,parent_type,par
ent_timestamp,property from ora_kglr7_dependencies where owner=:
1 and name=:2 and type=:3 and obj#=:4 order by order_number

        371,733            5       74,346.6     1.3    132022523
SELECT ET.EXPENDITURE_TYPE EXPENDITURE_TYPE,ET.SYSTEM_LINKAGE_FU
NCTION SYS_LINKAGE_CODE,GREATEST(ET.EXPND_TYP_START_DATE_ACTIVE,

          -------------------------------------------------------------
 SQL ordered by Reads for DB: PROD  Instance: prod  Snaps: 1084 -1086
-> End Disk Reads Threshold:    1000

 Physical Reads  Executions  Reads per Exec % Total  Hash Value
--------------- ------------ -------------- ------- ------------
        169,260           51        3,318.8    21.8   1233221062
SELECT T2.FILE_NAME   FROM FND_ATTACHED_DOCUMENTS T1,FND_DOCUMEN
TS_TL T2  WHERE T1.PK1_VALUE = :b1  AND T1.DOCUMENT_ID = T2.DOCU
MENT_ID  AND ROWNUM <= 1

         61,930            2       30,965.0     8.0   1855731242
select decode ( : p_show_country , 'Y' , fnd . territory_short_n
ame , 'Dummy_Country' ) country , party . category_code category
 , cust_acct . attribute1 dr4code , substrb ( party . party_name
 , 1 , 50 ) cust_name , cust_acct . account_number cust_no , nvl
 ( proj . segment1 , '~' ) project_number , nvl ( task . task_nu

         55,413           53        1,045.5     7.1   1441530861
begin WF_XML.GetShortMessage(:q, :n, :r, :s, :t, :er); end;

         55,401           22        2,518.2     7.1    633115946
select /*+ FIRST_ROWS */  qt.rowid, qt.msgid, qt.corrid, qt.prio
rity, qt.delay, qt.expiration,         qt.retry_count, qt.except
ion_qschema, qt.exception_queue, qt.chain_no,        qt.local_or
der_no, qt.enq_time, qt.time_manager_info, qt.state,         qt.
enq_tid, qt.step_no, qt.sender_name, qt.sender_address,  qt.send

         41,349          407          101.6     5.3   1857750303
SELECT NVL(SUM(DECODE(ITEM.EXPENDITURE_TYPE,'REGULAR TIME',ITEM.
QUANTITY,0.00)),0) REG_AMT,NVL(SUM(DECODE(ITEM.EXPENDITURE_TYPE,
'OVERTIME',ITEM.QUANTITY,0.00)),0) OVT_AMT,NVL(SUM(DECODE(ITEM.E
XPENDITURE_TYPE,'DOUBLE TIME',ITEM.QUANTITY,0.00)),0) DOT_AMT
FROM PA_TASKS PAT,PA_EXPENDITURE_ITEMS ITEM,PA_PROJECT_CLASSES P

         41,005           14        2,928.9     5.3   2586771312
declare x_message_code varchar2 ( 100 ) ; BEGIN x_message_code :
= null ; pa_billing . bill_ext_driver ( :par_project_id , :par_c
alling_process , :par_calling_place , :par_acc_thru_dt , :par_re
quest_id , x_message_code ) ; :message:i_message := x_message_co
de ; END ;

         38,427            6        6,404.5     5.0   1696222480
UPDATE PA_EVENTS PE SET REVENUE_DISTRIBUTED_FLAG='Y' WHERE EXIST
S  (SELECT 1   FROM GSI_COST_OF_SALES COS  WHERE LINE_NUM IS NUL
L  AND COS.EVENT_ID = :b1  AND COS.MANUAL_EVENT_ID = PE.EVENT_ID
 )

         27,033          192          140.8     3.5   1542130467
declare event_info          SF_Mgr.EventInfo_T; e_prop_types
    SF_Mgr.EventPropertyTypeList_T; e_prop_values       SF_Mgr.E
ventPropertyValueList_T; triggered_rules SF_Mgr.TriggeredRuleLis
t_T; i                   Number; rule_inst_id        Number; res
ult              Number; begin :EVENT_COUNT := 0; :TRIG_MSG := '

         23,204            1       23,204.0     3.0    362512440
SELECT             scope,     project_num,     sum(currev) Sales
,     (sum(currev) - sum(curcost)) CV,     decode(sum(currev),0,
0,(sum(currev) - sum(curcost))*100/sum(currev)) CV_percent,
sum(reg_amt) reg_amt,     sum(ovt_amt) ovt_amt,     sum(dot_amt)
 dot_amt,     location,     project_id,     address_id FROM
 SQL ordered by Reads for DB: PROD  Instance: prod  Snaps: 1084 -1086
-> End Disk Reads Threshold:    1000

 Physical Reads  Executions  Reads per Exec % Total  Hash Value
--------------- ------------ -------------- ------- ------------

         22,652           45          503.4     2.9   3220411513
select NODE_NAME ,CHILD_PROCESS_ID ,CHILD_THREAD_ID ,CHILD_EXE_N
AME ,TO_CHAR(CHILD_START_TIME,'YYYYDDMM HH24MISS') into :b0,:b1,
:b2,:b3,:b4  from FND_CONC_NT_PROCESSES where (CHILD_PROCESS_ID=
:b5 and PARENT_PROCESS_ID=:b6)

         22,109            2       11,054.5     2.8   2031957757
/*03 Aug 2001 14:44*/  SELECT         lpad(rta.trx_number,15) so
rt_invoice,        rta.org_id org_id,        rta.customer_trx_id
 customer_trx_id,        pdi.project_id project_id,        pdi.d
raft_invoice_num draft_invoice_num,        ppa.name project_name
,        rac.customer_id customer_id,        rta.trx_number invo

         19,991            3        6,663.7     2.6   3836499246
SELECT DISTINCT AERHV.FULL_NAME FULL_NAME,   AERHV.EMPLOYEE_ID E
MPLOYEE_ID,   AERHV.INVOICE_NUM REPORT_NUMBER,   AERHV.REPORT_HE
ADER_ID REPORT_HEADER_ID,   AERHV.INVOICE_DATE REPORT_DATE,   AE
RHV.STATUS STATUS,   AERHV.GR

          Followup:

i might start by looking at things like this:

      5,745,997            2    2,872,998.5    19.6   1162758779
declare  rc__ number; begin  owa.init_cgi_env(:n__,:nm__,:v__);
 htp.HTBUF_LEN := 255;  null;  arw_pages.invoice_table;  if (wpg
_docload.is_file_download) then   rc__ := 1;   wpg_docload.get_d
ownload_file(:doc_info);   null;   commit;  else   rc__ := 0; nu
ll;   commit;   owa.get_page(:data__,:ndata__);  end if;  :rc__

      4,376,840            3    1,458,946.7    14.9    328885768
SELECT CONCURRENT_PROGRAM_ID,PROGRAM_APPLICATION_ID,PRINTER,PROG
RAM_SHORT_NAME,ARGUMENT_TEXT,PRINT_STYLE,USER_PRINT_STYLE,SAVE_O
UTPUT_FLAG,ROW_ID,ACTUAL_COMPLETION_DATE,COMPLETION_TEXT,PARENT_
REQUEST_ID,REQUEST_TYPE,FCP_PRINTER,FCP_PRINT_STYLE,FCP_REQUIRED
_STYLE,LAST_UPDATE_DATE,LAST_UPDATED_BY,REQUESTED_BY,HAS_SUB_REQ

arw_pages -- 2.8 MILLION logical IOs to generate that web page!

1.4 million LIOS to run that other query!

you have some significant waits on IO there.

basically, you have to look at a PROCESS though, get a trace on the PROCESS and
start from there.  find the most important process, diagnose why it is slow, fix
it.

November 21, 2003 -- Thanks for the question regarding "are
      first_rows, all_rows based on choose optimizer mode?", version 8.1.7.2

      You Asked
Hi Tom

I have never been clear with optimizer_goal first_rows and all_rows, I suspect
they use CBO (or they are CBO?) but if that was the case by default what
optimizer are we using? first_rows, all_rows or choose?

Basically is I dont understand the difference between rows (first and all)
optimizer and cost based optimizer or they are the samething?

      and we said...
first_rows and all_rows are CBO only modes, this is true.

In choose mode, the optimizer will be closer to an all_rows optimization.

First_rows attempts to optimize the query to get the very first row back to the
client as fast as possible.  This is good for an interactive client server
environment where the client runs a query and shows the user the first 10 rows
or so and waits for them to page down to get more.

All_rows attempts to optimize the query to get the very last row as fast as
possible.  This makes sense in a stored procedure for example where the client
does not regain control until the stored procedure completes.  You don't care if
you have to wait to get the first row if the last row gets back to you twice as
fast.  In a client server/interactive application you may well care about that.
Review &
      Followup

      Rating:
      4
  April 06, 2002
Reviewer:
      A reader  from madrid, kpmg spain
so by default if we set optimizer_mode=CHOOSE we are using ALL_ROWS?

          Followup:

generally -- IF statistics exist (else you are using RBO) -- plsql does all
rows, other environments might do other modes.

      Rating: 4
optmizer mode
      types  April 07, 2002
Reviewer:  Lakshmi
      Narasimhan R  from Dubai, UAE
Hi tom

at any version of oracle before,  there exist
a optmizer mode or goal in the name of COST. as far as i knew only RULE and
CHOOSE but i heard few people saying optmizer mode = COST.  Is it true.

          Followup:

they are "wrong".

This init.ora parameter is documented in the Oracle Reference guide and can take
values:

rule, choose, first_rows, all_rows

and in 9i, forst_rows_<N> where n in ( 1, 10 , 100, 1000 )

refer them to the docs.

Many people refer to "all_rows" or "first_rows" as "cost mode"

      Rating: 5
And a worthy of
      note...  April 08, 2002
Reviewer:  Connor
      McDonald  from UK
Just noting for readers that under 9i, FIRST_ROWS_1 is not synonymous with
FIRST_ROWS

Cheers
Connor

      Rating:
      4
  April 29, 2002
Reviewer:  Mike
       from Atlanta
Oracle will use CBO and optimizer_mode set to FIRST_ROWS or ALL_ROWS in
init.ora, If there is NO FOUND stats?

          Followup:

If the optimize mode is set to first or all rows, the CBO will be used for ALL
queries, even if there are no statistics.

      Rating: 2
Please confirm
      CBO when there are no stats  April 29, 2002

Reviewer:  Andrew  from CA, USA
Tom

I understood that if statistics where absent, then RBO kicked in even if CBO
enabled. Is your previous statement accurate? Is this a difference in behaviour
between 8.1.6 and 8.1.7?

In
http://asktom.oracle.com/pls/ask/f?p=4950:8:752344::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:2020768125166
you state that deleting the statistics invokes RBO.

          Followup:

I am 100% on this one.  Consider:

ops$tkyte@ORA815.US.ORACLE.COM> create table t ( x int );

Table created.

ops$tkyte@ORA815.US.ORACLE.COM>
ops$tkyte@ORA815.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA815.US.ORACLE.COM> alter session set optimizer_goal=choose;

Session altered.

ops$tkyte@ORA815.US.ORACLE.COM> select * from t;

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

ops$tkyte@ORA815.US.ORACLE.COM> alter session set optimizer_goal=first_rows;

Session altered.

ops$tkyte@ORA815.US.ORACLE.COM> select * from t;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=1 Card=82 Bytes=1066)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=1 Card=82 Bytes=1066)

ops$tkyte@ORA815.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA815.US.ORACLE.COM>

You can see from the autotrace that the CBO is used (whenever you see COST, that
means CBO was involved)...

I said in that referenced note that you could delete the stats and (this was
assumed) given that the optimizer_goal was left to its default of CHOOSE -- rbo
will be used.  (they did say "Also, we are currently using the default CHOOSE.",
meaning if you delete stats, RBO is used.  Have stats, CBO is used)

I see nothing in there to contradict what is said here.

If your optimizer goal is set to something OTHER THEN CHOOSE OR RULE -- the CBO
will be used regardless.

If the optimizer goal is set to RULE, the RBO will be used for all queries
EXCEPT those that access objects that require the CBO (partitioned tables,
tables with non-default degrees of parallelism, IOTs, etc)

If the optimizer goal is set to CHOOSE, then CBO will be used for all queries
that touch AT LEAST one table that was analyzed (or access an object that
requires the CBO as above).  RBO will be used otherwise.

      Rating: 4
SQL
      Optimization Techniques  April 30, 2002
Reviewer:
      Shajan Joseph  from Chennai, India
Useful and Informative

Thanks Tom

      Rating: 5
Is setting
      optimizer_goal not supported anymore in 10g?  November 19,
      2003
Reviewer:  Su Baba  from Ca, USA
We found in our environment after we migrated our application to Oracle 10g, one
of the the PL/SQL pacakage returns the following error:

alter session set optimizer_goal = first_rows
                                *
ERROR at line 1:
ORA-01986: OPTIMIZER_GOAL is obsolete

Is this not supported anymore in 10g?  How do we resolve this problem?

thanks

          Followup:

ops$tkyte@ORA10G> alter session set optimizer_goal = first_rows
   2  /
alter session set optimizer_goal = first_rows
                                 *
ERROR at line 1:
ORA-01986: OPTIMIZER_GOAL is obsolete

ops$tkyte@ORA10G> c/goal/mode
  1* alter session set optimizer_mode = first_rows
ops$tkyte@ORA10G> /

Session altered.

      Rating:
      5
  November 26, 2003
Reviewer:
      A reader

      Rating:
      3
Helpful  December 01, 2003
Reviewer:
      Paresh N.  from Mumbai-INDIA
it was quiet helpful still require more descriptive

      Rating: 4
Very
      Useful  December 11, 2003
Reviewer:  Riyaz K
       from Kochi
It was very useful and informative

November 21, 2003 Solomon -- Thanks for the question regarding
      "consecutive transactions", version 8.1.7

      You Asked
Hi Tom,

I have a table of the following structure
Create Table My_transactions
(
Trans_id         Number(11),
Emplid           varchar2(11),
Trans_start_date Date,
Trans_end_date   Date,
Trans_type       Varchar2(1) CHECK (Trans_type IN ('C','D'))
)

Alter Table My_transactions
add constraint pk_my_transactions primary key (Trans_id);

Now my table constains following details for an emplid A745

    TRANS_ID EMPLID      TRANS_START_DATE TRANS_END_DATE TRANS_TYPE
------------ ----------- ---------------- -------------- ----------
      128734 A745        1/29/2002        2/7/2002       C
      128736 A745        2/8/2002         2/22/2002      C
      128745 A745        2/23/2002        2/27/2002      C
      128748 A745        2/18/2002        3/29/2002      C
      128756 A745        4/8/2002         4/9/2002       C
      128757 A745        4/10/2002        4/13/2002      D
      128768 A745        4/14/2002        4/17/2002      D
      128779 A745        4/28/2002        5/18/2002      C
      128720 A745        1/18/2002        1/27/2002      C

I want a query that will return the following result sets which gives me
consecutive transactions :-

      128734 A745        1/29/2002        2/7/2002       C
      128736 A745        2/8/2002         2/22/2002      C
      128745 A745        2/23/2002        2/27/2002      C

      128757 A745        4/10/2002        4/13/2002      D
      128768 A745        4/14/2002        4/17/2002      D

Thanks in advance.

      and we said...
man, it just seems like analytics are the answer to 50% of the questions these
days:

ops$tkyte@ORA817DEV> select trans_id, emplid, trans_start_date,
  2         trans_end_date, trans_type
  3    from (
  4  select trans_id, emplid, trans_start_date,
  5         trans_end_date, trans_type,
  6         lag(trans_end_date+1) over
  7            ( partition by emplid, trans_type
  8                  order by trans_end_date ) lag_ed,
  9         lead(trans_start_date-1) over
 10            ( partition by emplid, trans_type
 11                  order by trans_start_date ) lead_sd
 12    from t
 13         )
 14   where trans_start_date = lag_ed
 15      or trans_end_date = lead_sd
 16  /

  TRANS_ID EMPL TRANS_STAR TRANS_END_ T
---------- ---- ---------- ---------- -
    128734 A745 01/29/2002 02/07/2002 C
    128736 A745 02/08/2002 02/22/2002 C
    128745 A745 02/23/2002 02/27/2002 C
    128757 A745 04/10/2002 04/13/2002 D
    128768 A745 04/14/2002 04/17/2002 D

Review &
      Followup

      Rating:
      5
  January 24, 2003
Reviewer:
      A reader
excellent , that was exactly what i wanted.

      Rating:
      5
  January 24, 2003
Reviewer:
      Solomon
Hi Tom could you please explain those innder queries a bit?

          Followup:

if you have my book "expert one on one Oracle" -- read the chapter on analytic
functions, else see

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76994/analysis.htm#11237

      Rating: 3
How to get the
      earliest start and latest ends for consecutive jobs?  March
      05, 2003
Reviewer:  Matt  from Australia
For the results of your query:

  TRANS_ID EMPL TRANS_STAR TRANS_END_ T
---------- ---- ---------- ---------- -
    128734 A745 01/29/2002 02/07/2002 C
    128736 A745 02/08/2002 02/22/2002 C
    128745 A745 02/23/2002 02/27/2002 C
    128757 A745 04/10/2002 04/13/2002 D
    128768 A745 04/14/2002 04/17/2002 D

How would you build on this to retrieve the earliest start and the latest end
for 'consecutive' tasks with the same EMPLID?

The result would be:

  EMPL EARLIEST_S LATEST_END T
  ---- ---------- ---------- -
  A745 01/29/2002 02/27/2002 C
  A745 04/10/2002 04/17/2002 D

I'm trying to generalise this result so that I can cope with data that looks
kind of like this (for many EMPLIDs):

  |-------|
          |-----------|
                      |-----|
                                  |----|
                                       |-------|

                                                      |---|
 SD1                        ED1   SD2         ED2    SD3 ED3

and pull out the results looking something like:

  EMPL EARLIEST_S LATEST_END T
  ---- ---------- ---------- -
     1 SD1        ED1
     1 SD2        ED2
     2 SD3        ED3

I've tried MAX and MIN with partioning clauses along with LAG and LEAD, and I
can't quite get all the way there...

Can you please help?

Thanks and Regards

          Followup:

this be where analytics break down and don't work.

The result set requires recursive processing - we need to dynamically group
groups based on some unknown number of preceding and following rows.

We can find the rows that "start" the the group - but there is no way to
partition on the fly.

      Rating: 4
just
      maybe...  March 06, 2003
Reviewer:  Padders
       from UK
Matt, try using COUNT with RANGE UNBOUNDED PRECEDING (cumulative count) to break
the rows into groups based on the start of each period (at least I think this
works!), e.g.

SQL*Plus: Release 9.2.0.1.0 - Production on Thu Mar 6 20:19:23 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

SQL> CREATE TABLE table_name (
  2    id NUMBER (10),
  3    start_date DATE,
  4    end_date DATE);

Table created.

SQL> ALTER SESSION SET cursor_sharing = FORCE;

Session altered.

SQL> INSERT INTO table_name
  2              (id, start_date,
  3               end_date)
  4       VALUES (1, TO_DATE ('01/28/2002', 'MM/DD/YYYY'),
  5               TO_DATE ('02/07/2002', 'MM/DD/YYYY'));

1 row created.

SQL> INSERT INTO table_name
  2              (id, start_date,
  3               end_date)
  4       VALUES (1, TO_DATE ('02/08/2002', 'MM/DD/YYYY'),
  5               TO_DATE ('02/22/2002', 'MM/DD/YYYY'));

1 row created.

SQL> INSERT INTO table_name
  2              (id, start_date,
  3               end_date)
  4       VALUES (1, TO_DATE ('02/23/2002', 'MM/DD/YYYY'),
  5               TO_DATE ('02/27/2002', 'MM/DD/YYYY'));

1 row created.

SQL> INSERT INTO table_name
  2              (id, start_date,
  3               end_date)
  4       VALUES (1, TO_DATE ('02/18/2002', 'MM/DD/YYYY'),
  5               TO_DATE ('03/29/2002', 'MM/DD/YYYY'));

1 row created.

SQL> INSERT INTO table_name
  2              (id, start_date,
  3               end_date)
  4       VALUES (1, TO_DATE ('04/08/2002', 'MM/DD/YYYY'),
  5               TO_DATE ('04/09/2002', 'MM/DD/YYYY'));

1 row created.

SQL> INSERT INTO table_name
  2              (id, start_date,
  3               end_date)
  4       VALUES (1, TO_DATE ('04/10/2002', 'MM/DD/YYYY'),
  5               TO_DATE ('04/13/2002', 'MM/DD/YYYY'));

1 row created.

SQL> INSERT INTO table_name
  2              (id, start_date,
  3               end_date)
  4       VALUES (1, TO_DATE ('04/14/2002', 'MM/DD/YYYY'),
  5               TO_DATE ('04/17/2002', 'MM/DD/YYYY'));

1 row created.

SQL> INSERT INTO table_name
  2              (id, start_date,
  3               end_date)
  4       VALUES (1, TO_DATE ('04/28/2002', 'MM/DD/YYYY'),
  5               TO_DATE ('05/18/2002', 'MM/DD/YYYY'));

1 row created.

SQL> INSERT INTO table_name
  2              (id, start_date,
  3               end_date)
  4       VALUES (1, TO_DATE ('01/18/2002', 'MM/DD/YYYY'),
  5               TO_DATE ('01/27/2002', 'MM/DD/YYYY'));

1 row created.

SQL> INSERT INTO table_name
  2              (id, start_date,
  3               end_date)
  4       VALUES (2, TO_DATE ('01/28/2002', 'MM/DD/YYYY'),
  5               TO_DATE ('02/07/2002', 'MM/DD/YYYY'));

1 row created.

SQL> INSERT INTO table_name
  2              (id, start_date,
  3               end_date)
  4       VALUES (2, TO_DATE ('02/08/2002', 'MM/DD/YYYY'),
  5               TO_DATE ('02/22/2002', 'MM/DD/YYYY'));

1 row created.

SQL> INSERT INTO table_name
  2              (id, start_date,
  3               end_date)
  4       VALUES (2, TO_DATE ('02/23/2002', 'MM/DD/YYYY'),
  5               TO_DATE ('02/27/2002', 'MM/DD/YYYY'));

1 row created.

SQL> INSERT INTO table_name
  2              (id, start_date,
  3               end_date)
  4       VALUES (2, TO_DATE ('04/08/2002', 'MM/DD/YYYY'),
  5               TO_DATE ('04/09/2002', 'MM/DD/YYYY'));

1 row created.

SQL> INSERT INTO table_name
  2              (id, start_date,
  3               end_date)
  4       VALUES (2, TO_DATE ('04/10/2002', 'MM/DD/YYYY'),
  5               TO_DATE ('04/13/2002', 'MM/DD/YYYY'));

1 row created.

SQL> INSERT INTO table_name
  2              (id, start_date,
  3               end_date)
  4       VALUES (2, TO_DATE ('04/14/2002', 'MM/DD/YYYY'),
  5               TO_DATE ('04/17/2002', 'MM/DD/YYYY'));

1 row created.

SQL> INSERT INTO table_name
  2              (id, start_date,
  3               end_date)
  4       VALUES (2, TO_DATE ('04/18/2002', 'MM/DD/YYYY'),
  5               TO_DATE ('05/18/2002', 'MM/DD/YYYY'));

1 row created.

SQL> INSERT INTO table_name
  2              (id, start_date,
  3               end_date)
  4       VALUES (2, TO_DATE ('01/18/2002', 'MM/DD/YYYY'),
  5               TO_DATE ('01/27/2002', 'MM/DD/YYYY'));

1 row created.

SQL> INSERT INTO table_name
  2              (id, start_date,
  3               end_date)
  4       VALUES (3, TO_DATE ('01/03/2002', 'MM/DD/YYYY'),
  5               TO_DATE ('05/16/2002', 'MM/DD/YYYY'));

1 row created.

SQL> COMMIT;

Commit complete.

SQL> SELECT id, period,
  2         MIN (start_date) start_date,
  3         MAX (end_date) end_date
  4  FROM  (SELECT id, start_date, end_date,
  5                COUNT (CASE WHEN prev_end_date IS NULL OR
  6                  start_date > prev_end_date + 1 THEN 1 END) OVER (
  7                    PARTITION BY id
  8                      ORDER BY start_date
  9                        RANGE UNBOUNDED PRECEDING) period
 10         FROM (SELECT id, start_date, end_date,
 11                      LAG (end_date) OVER (
 12                        PARTITION BY id
 13                          ORDER BY start_date) prev_end_date
 14               FROM   table_name))
 15  GROUP BY id, period
 16  /

        ID     PERIOD START_DAT END_DATE
---------- ---------- --------- ---------
         1          1 18-JAN-02 29-MAR-02
         1          2 08-APR-02 17-APR-02
         1          3 28-APR-02 18-MAY-02
         2          1 18-JAN-02 27-FEB-02
         2          2 08-APR-02 18-MAY-02
         3          1 03-JAN-02 16-MAY-02

6 rows selected.

SQL>

What this still ignores, however, is concurrent instances for the same id. Back
in your court Tom :)

      Rating: 4
Thanks Padders
      and Tom  March 09, 2003
Reviewer:  Matt
       from Australia
Thanks Tom and Padders.

Padders, all my tries have fallen at the same fence. There just simply does not
seem to be able to do what I want, which is pretty much to find the max end date
and the min start date for the set of txs where the start date is the same as
the previous txn end date.

Here is some more detail about my problem:

I need to process about 800,000 of these 'consecutive' transactions between two
'interested' dates. Once I have found each set of start and end dates I then
need to compare these with another table of start and end dates (another 800,000
entries) to pick out the overlaps. When I have a full set of overlaps I can then
carry out a simple calculation and insert the calculation result for each start
and end date in a results table.

I need this process to be performant.

Can you describe some high level approaches that will help me to achieve the
performance I need?

One approach that I have thought about (and will need to flesh out in more
detail now!), is to use globally temporary tables to store intermediate results
(ie: cope with the recursion). I would use SQL to move the source data closer to
the required result (the actual start and the actual end of the consecutive
transactions). When I have my table of results I can then use (hopefully) one
select to work out the required intersection and carry out the calculation work
as described.

I am expecting to have to parallelise the PL/SQL process (something like you
describe in your draft chapter 3) to get the required performance.

Can you please comment on the above. Does this sound like a feasible approach to
getting the required performance?

If you are aware of a better/simpler approach then please let me know?

BTW - Is there anything coming in 10i that might make this process easier?

Thanks again.

          Followup:

it sounds like procedural code.  Other then 10i running plsql tons faster,
nothing other then a really good algorithm using all of your domain knowledge
about the problem will really do it...

It is not a "set" problem unfortunately.  One of those rare times you cannot do
it simply in a query.

      Rating: 3
A procedural
      approach...  March 09, 2003
Reviewer:  Matt
       from Australia
Thanks.

Can you please comment on my approach?

I am thinking of using some global temp tables so that I can leverage SQL using
"insert into tab as select * from" to repeatedly "coalese" consecutive
transaction pairs. Eventually I will get no data found returned from my select
and this will be the stopping condition.

A variation would be to use "create globally temp table parallel <n> as select"
I would then have to drop the tables at the end of the processing (as well as
making all my PL/SQL dynamic so that it will compile).

The above CTAS will go into a PL/SQL loop.

Is there a better way?

          Followup:

don't know -- i would have to sit down and actually implement the algorithm in
two, three, or more ways and benchmark them.....

      Rating:
      3
Thanks  March 09, 2003
Reviewer:
      Matt  from Australia
I was looking to get a gut feel from you as to what might work best. I think
that an SQL based approach might be quickest, but I haven't excluded anything at
the moment. As you say, try it and benchmark...

      Rating:
      5
  November 19, 2003
Reviewer:  Vipin
       from NYC
Hi Tom

I have a similar requirement in which there is a table having the following
structure:-

Trans
(
emplid varchar2(5),
trans_date date
)

Now this table will be populated with dates for many employees. Some of these
dates may be consecutive and some migth not. I have to write a query which will
derive a record out of this table in such a way that if consecutive dates exists
the min of these consecutive dates would be the start date for the record and
max would be the end date. Please see the example below:-

Emplid   Date
--------------
ABCD    11/4/2003
ABCD    11/10/2003
ABCD    11/18/2003
ABCD    11/19/2003
ABCD    11/20/2003
ABCD    11/24/2003

The output must be :-

Emplid  Start_date  End_date
--------------------------------
ABCD    11/4/2003   11/4/2003
ABCD    11/10/2003  11/10/2003
ABCD    11/18/2003  11/20/2003
ABCD    11/24/2003  11/24/2003

With what ever analytics I know the best of what I could write is :-

SELECT tr_date, MAX(trans_date)
FROM
(
SELECT emplid,
       tr_date,
       lead(tr_date)  over (ORDER BY tr_date) nxt_strt_d
FROM
(
SELECT emplid,
       trans_date tr_date,
       lag(trans_date)  over (PARTITION BY emplid ORDER BY trans_date) prev_date
FROM   trans tr
WHERE  emplid = 'ABCD'
ORDER  BY tr.trans_date
)
WHERE  tr_date - 1 <> nvl(prev_date, to_date('01/01/1900','mm/dd/yyyy'))
) qry, trans
WHERE trans.emplid = qry.emplid
AND   trans.trans_date >= tr_date
AND   trans.trans_date <  nvl(nxt_strt_d, to_date('01/01/2500','mm/dd/yyyy'))
GROUP BY tr_date

Could you please comment on this query. Is there any better way to write this.

          Followup:

ops$tkyte@ORA920LAP> select start_date, end_date
  2    from (
  3  select case when last_dt is null and next_dt is null then dt
  4                  else lag(dt) over ( partition by empno order by dt)
  5                  end start_date,
  6          dt end_date,
  7                  last_dt,
  8                  next_dt
  9    from (
 10  select empno, dt,
 11         decode( lag(dt) over (partition by empno order by dt),
 12                     dt-1, lag(dt) over ( partition by empno order by dt),
null ) last_dt,
 13         decode( lead(dt) over (partition by empno order by dt),
 14                     dt+1, lead(dt) over ( partition by empno order by dt),
null ) next_dt
 15    from t
 16         )
 17   where last_dt is null or next_dt is null
 18         )
 19   where next_dt is null
 20  /

START_DATE END_DATE
---------- ----------
11/04/2003 11/04/2003
11/10/2003 11/10/2003
11/18/2003 11/20/2003
11/24/2003 11/24/2003

is my approach

November 21, 2003 Kamal -- Thanks for the question regarding
      "Differences between Oracle Label security and FGAC...", version 9.2.0.3

      You Asked
Hi Tom,
Recently I was asked by my client following questions:
1). We want to implement Oracle Label Security. But we also want to switch from
DEDICATED server setup to the SHARED (MTS) setup. Will there be any issues
and/or incompatibility between the two going togather?
My opinion -- I do not think they will have any issues with each other going
togather? What is your opinion? Should we be concerned at all?

2).As I understand, Oracle Label Security does much of the same thing what FGAC
may provide you. Any reasons why would one choose Oracle Label Security over
implementing FGAC? To me, implementing Oracle Label Security seems a bit more
involved than what may be required for FGAC. Also should we be concerned with
the label information that will be stored along with the each row (space
considerations etc...).

Any inputs would be appreciated.
Thanks,

      and we said...
1) no, there are not any issues I am aware of.

2) OLS is a technology built on top of FGAC.  With FGAC, you are in "do it
yourself" mode, building your own implementation.  With FGAC, you can build OLS.

This is like asking "should I buy an HR product or should I buy Oracle the
database".  You can use Oracle the database to build an HR product -- but they
are not the same.

OLS is an implementation, something you configure and use.
FGAC is a tool, something you build an implementation with that you would then
configure and use.
Review & Followup

      Rating: 4
OLS and
      VPD  November 19, 2003
Reviewer:  Richard
       from Reston VA
Also keep in mind using a home grown VPD solution can be very taxing on your
performance.  OLS is part of the kernel so performance is better.

          Followup:

well, not really.  it is all predicate rewrite.

home grown VPD can be brutally efficient -- remember, it is just SQL being
added, just SQL.

you can write BAD sql
you can write GOOD sql

you can have a predicate policy that works great for OLTP, but stinks for DW and
vice versa.

It is all about designing your physical structures for performance

November 21, 2003 KK -- Thanks for the question regarding "How to
      read hexadecimal data", version 8.1.6

      You Asked
I've to read a file which contains binary data,
how can I convert the data into the hexidecimal value?

For example:null->00

Moreover, can Oracle read null character from a file?

      and we said...
You would use BFILES to read the data into a RAW variable.  Once
in a RAW variable -- a simple assignment of that data to a
VARCHAR variable will assign a HEX string to the varchar.

for example, I wrote a small C program:

void main()
{
int     c;

        for( c = 0; c <256; putchar(c++) );
}

and ran it:

$ test > test.dat

And then the plsql block:

ops$tkyte@ORA8I.WORLD> create or replace directory
  2  my_files as '/export/home/tkyte/public_html';

Directory created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> declare
  2      l_raw raw(4000);
  3      l_bfile bfile default bfilename( 'MY_FILES', 'test.dat'
);
  4  begin
  5      dbms_lob.fileopen( l_bfile );
  6      l_raw := dbms_lob.substr( l_bfile,4000,1 );
  7      dbms_lob.fileclose( l_bfile );
  8
  9      for i in 1 .. utl_raw.length(l_raw)
 10      loop
 11          dbms_output.put_line( 'Byte ' || i || ' = ' ||
 12                                 utl_raw.substr(l_raw,i,1) );
 13      end loop;
 14  end;
 15  /
Byte 1 = 00
Byte 2 = 01
Byte 3 = 02
Byte 4 = 03
Byte 5 = 04
Byte 6 = 05
Byte 7 = 06
Byte 8 = 07
Byte 9 = 08
Byte 10 = 09
Byte 11 = 0A
....
Byte 254 = FD
Byte 255 = FE
Byte 256 = FF

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD>

UTL_RAW might not be installed on your system -- it comes with
Oracle7.1.6 and up.  To install if you do not have it:

o cd $ORACLE_HOME/rdbms/admin
o connect as SYS or INTERNAL only using SVRMGRL
o @utlraw
o @prvtrawb.plb

Review &
      Followup

      Rating: 5
convert hex
      file to binary/raw file  November 11, 2003

Reviewer:  A reader
Hi

There was this developer in our company who wrote some perl program to convert
jpeg files into hexadecimal files then using utl_file.get_line concatenate line
by line and store as a raw variable and store as BLOB (using dbms_lob.write) in
the database. It stinks but I am not allowed to change the whole process, the
perl program is untouchble.

So my question is, is there a way to convert in a more efficient way a hex file
into binary/raw format instead of concatenating line after line...?

          Followup:

you don't need to concat, you can

loop
   read varchar2 string
   assign to raw (will auto-convert hex to raw)
   dbms_lob.writeAppend

      Rating: 5
reset raw
      variable to zero  November 11, 2003
Reviewer:
      A reader
Hi

If a variable is of type RAW and it's value is increased inside a loop is there
anyway to reset it to zero after X loop iterations?

          Followup:

huh?

what do you mean by "zero" with regards to a "raw"?

      Rating: 4
Fun with
      Raws  November 12, 2003
Reviewer:  Dan
      Clamage  from Pittsburgh, PA
declare
  v_raw raw(4000);
begin
  for i in 0 .. 255 loop
    v_raw := v_raw || utl_raw.cast_to_raw(chr(i));
--    equivalent expression without || operator
--    v_raw := utl_raw.concat(v_raw, utl_raw.cast_to_raw(chr(i)));
  end loop;
  for i in 1 .. utl_raw.length(v_raw) loop
    dbms_output.put_line('raw(' || i || ')=[' ||
                          utl_raw.substr(v_raw, i, 1) || ']');
  end loop;
end;
/
raw(1)=[00]
raw(2)=[01]
raw(3)=[02]
...
raw(254)=[FD]
raw(255)=[FE]
raw(256)=[FF]

      Rating:
      2
Hexadecimal  November 18, 2003
Reviewer:
      Eva  from Chicago
This was somewhat helpful.  It gave me some idea.
What I want to do is how I can save my file in .txt
and then covert my string data-characters and numbers
to hexidecimal.  I can't seem to find a pl/sql script that
does it or what I need to write this script.

Any guidance would be helpful.

          Followup:

utl_file is the tool for plsql to write files.

numbers can be converted to hex using "to_char( number, 'XXXXXXX' )"

strings via:

   hex_string := utl_raw.cast_to_raw( orig_string );

make sure hex_string is 2x as long as orig_string!!!

      Rating: 3
Conversion HEX
      to RAW and RAW to hex  November 19, 2003
Reviewer:
      Vikas Sharma  from Delhi India
Hi Tom,

I have a table which store a binary file content(ms word doc file ) in a blob
column. The data is inserted by using LOADFROMFILE of dbms_lob.

I am reading the blob in chunks of 57 using dbms_lob.substr which return the raw
data.  then i writes it to a text file hex. I use the utl_file.put_line to write
file. when a raw data is assign to varchar2 variable it automatically get
converted to hex. Am I right? So my text file is writen in hex. Pl correct me if
worng.

Now i run a utilities available on internet hex2bin.exe and bin2hex.exe to
perform conversion. these utility works fine when i convert a doc file to hex
using bin2hex.exe and then again hex2bin.exe i get the same back. But does not
work well with the file created by using oracle UTL_FILE. and does not create
the binary file (doc file).

When i load the same doc file to database and write a hex file using utl_file
and compare the hex file create by utl_file and bin2hex.exe utility. They are
different.

Why the 2 file are differernt Does the automatic conversion from raw to varchar
of utl_file generate different HEX data.

I am not able tounderstand this behaviour.

Actually my requirment is to display the data ie file store in the database on
client FORM6i. The only way suggested by oracle metalink, is read from database
write a text file(in hex) at client using text_io then run a utility to convert
it to binary file back at client so that way your file is transfer from database
to client. then you can open it on client using host cammand..

Any kind of help or suggestions will be higly appreciated.

Thanks,

Reagrds

Vikas Sharma

          Followup:

assign a raw to a string and you have hex, yes.

utl_file has a 32k limit on a line length.  you are injecting (and have no way
not to) a linefeed in there.

if you can use the web, we can do this trivially with iAS and mod_plsql.  a
simple URL would retreive the document.

      Rating:
      3
Thanks  November 24, 2003
Reviewer:
      vikas Sharma  from Delhi India
Hi Tom,

Thanks,

Regards

Vikas Sharma

November 21, 2003 Matt -- Thanks for the question regarding
      "Recommendations for JAVA executing SQL", version 9.0.1

      You Asked
I have been looking for some good Java coding standards to be used when using
Java with an Oracle database. Specifically some 'best practices' for Java
running SQL against Oracle. I have very little Java exposure, but I think that I
am looking for an understanding of when to use the following constructs:

Statement  (<- never, since cannot use bind variables)
PreparedStatement
CallableStatement

I would like to understand when it is most efficient to:

open cursors
close cursors

Additionally, any issues surrounding connections to the database, such as:

Connection Pooling

Finally:

I am looking at enhancing an existing J2EE application. I would like also like
to understand where PL/SQL might fit into this standard (if at all).

Are ther eany other considerations when trying to build an efficient Java
application using the Oracle RDBMS?

How does the Java package oracle.sql.* fit into all of this?

I appreciate that this might be a bit of a large topic. So, if you can focus on
the SQL issues (open, parse, execute, fetch, close) and reference the Oracle
docuementation where possible. I have been reading the "Oracle9i JDBC
Developer’s Guide and Reference", but have not found any "For performance,
consider using these approaches in these situations" type sections.

Can you recommend and "accurate" books on Java development with Oracle? Having
read this forum, I am well aware that there are some books out there on this
topic that are misleading, if not just incorrect.

Thanks in advance,

Best Regards,

Mat.

      and we said...
Java is just a language.

Java is just a language.

Java is not any different than C, VB, perl, etc -- it is JUST a language.

I would say my new book "Effective Oracle By Design" is what you are looking
for, or "Expert one on one Oracle", or Jonathan Lewis's Practical Oracle8i, or
Cary Millsap's Optimizing Oracle Performance.

These are books about how to build good database base applications -- regardless
of language.

Java is JUST a 3gl programming language, nothing more, nothing less.  The
questions you are asking about -- parsing, cursors, stored procedures -- are
language independent.

For performance, consider outlawing "select, insert, update, delete, merge,
create" in all <any 3gl language goes here> and only permitting such statments
in stored procedures.  The best 3gl has nothing more then "begin/end" blocks in
it (and uses bind variables to invoke those!)

It'll be more performant

It'll be more portable (seems counter intuitive but since the major diffs
between databases is NOT our sql syntax but our very core concurrency and
integrity models -- this is true)

It'll be easier to tune

It'll be easier to manage

probably not the answer you wanted but -- remember -- java is just a language,
just a language.
Review & Followup

      Rating: 3
My $0.02 on
      Matt's questions  September 28, 2003
Reviewer:
      Peter Tran  from Houston, TX USA
Matt,

Tom is right...

Here's my experience with JDBC and some rambling thrown in.

1)  If you can afford it, buy all the above books.  I have them all and they're
excellent.
2)  Search this site for the word "Menon".  He's shared a lot of JAVA code
w.r.t. performance.  There's also a discussion regarding connection pooling and
the ability to reuse SQL statements.  Connection pooling makes it harder, but
not impossible.

Do not buy any JDBC specific books.  They're a waste of money and time.  In
fact, one of them
http://www.oreilly.com/catalog/jorajdbc/
gives the wrong information regarding performance.  Oreilly books are usually
very good, but the two they have on JDBC are pretty bad.  I hestitate to even
give this link:
http://www.onjava.com/pub/a/onjava/2001/12/19/oraclejdbc.html
but #3 is an example of just WRONG information.  This author must have done all
his testing on a single CPU Oracle box using one connection to the database.
Arghhh....

We had the unfortunate experience of following his advice on a multi-threaded
application hitting a 12 processors Oracle box, and needless to say we got
hammered.  Almost 90% of Oracle's time was spent thrashing the shared pooled
reparsing each unique SQL, because we read that Statement performed better.

It took a while to convert the application to use PreparedStatement.  Better
yet, go with Tom's advice and do it on the database side.

Finally, whatever you do benchmark your JAVA application with real volume in a
multi-threaded environment.  A small dataset is good to test functionality, but
doesn't tell you squat about how your application will scale.  I'm learning that
we pay to huge price when delay scalabiility testing and we end up letting the
clients do it.

HTH,
-Peter

      Rating: 4
I think that I
      am with you guys on this one.  September 28, 2003

Reviewer:  Matt  from Australia
I think that I am with you guys on this one.

I'm about 2/3 of the way through "Effective Oracle By Design", I have "Expert
one on one Oracle", and Cary Millsap's Optimizing Oracle Performance is in
transit from Amazon, (and I've read Steve Adams and James Morle and ..... and
thay (the books)are on my bookshelf).

I have also trawled this site to cover what I think comprises the following
receommendations from Tom:

* open cursors in your constructors
* close your cursors in your destructors
* ONLY USE BINDS - PreparedStatement or CallableStatement objects
* Don't call SQL directly, wrap your insert, select, update, delet, merge in
PL/SQL. This will allow you to take advantage of some of teh performance smarts
in PL/SQL as well as know exactly where to find all your SQL for tuning - when
you have found it, fix it, recompile your package and your change is implemented
simply without any 'major' application re-builds.

Finally, bear in mind that there are no rules of thumb - all 'rules' *may* be
broken/ bent/ disregarded in particular circumstances.

This existing application currently uses the Statement construct and therefore
no binds. The application itself processes file data that arrive from users. The
application processes all of these whilst connected to the DB as a single user.
At the moment then, my understanding is that this application is effectively a
single user system. It is an interesting conversation to try and convince a
change of development approach in this environment - even when you take the best
practice argument. ie: When it is easier to write efficient code, why continue
with the existing inefficient methods.

I expect that the app is running in hardware that is much meatier that it needs
to be.

I'm intersted to hear any other comments regarding J2EE development on Oracle.

Oh, and a decent book reference, of the same calibre as those listed at the
beginning of this review would be nice!

Regards all,

Mat.

          Followup:

you cannot rely on destructors in java, only in Ada can you rely on them....

Cary's book is awesome, i only just got it and I'm on page 100.  I'm really
enjoying it.

Even in single user mode, they are spending MORE TIME PARSING (probably between
3-5 times) then they are spending actually executing the sql!!!

      Rating: 3
Only page
      100!  September 28, 2003
Reviewer:  Matt
       from Australia
So I assume then, that if I am not going down the PL/SQL path, that Java woudl
close the cursor explicitly when the cursor has been re-used and DB processing
has completed for the particular "object".

Looking forward to Cary's book too. I have just been bitten, by yours though.
It's amazing how quickly one can plough through a well written technical book,
if it is well written.

I agree - more time parsing and more CPU used than necessary. Less CPU time to
do productive work.

In a STATSPACK I expect that I would be seeing lots of hard parses and a high
parse/cpu ratio. Any others that I can look out for?

I don't suppose Cary comes up with a way for me to be able to say "Hey, Change
this and we will see this measurable benefit".

Thanks again.

          Followup:

java would close the cursor when it was absolutely 100% done with it in that
session/ connection....

statpack would show a low or even negative execute to parse ratio.

Cary's book isn't going to give you that nugget, it is going to give you
something better -- a methodology, a repeatable one, that can be applied all
over the way.  so, in a way -- maybe it does in hindsight.

      Rating: 5
A pure Java
      approach  September 29, 2003
Reviewer:  Peter
      Tran  from Houston, TX USA
Matt,

With a pure JAVA approach, you have to do a lot more work.  Use connection
pooling because opening and closing connections to the database are expensive.
Connection pooling makes reusing PreparedStatement more of a challenge, but not
impossible.  You have to always explicitly close your connection or you'll end
up with cursor leaks.  The performance won't be as great as having it in the
database.

The only argument I've seen put forth for doing it in all JAVA is that it's
easier to port the application to a different database (highly arguable).  I
think this argument is put forth by people (application developers) who are
intimidated with database development.  Ask a person who's comfortable and
knowledgeable about database development (like Tom Kyte), and he'll say do it
the database.  Ask a person who's done all Java, and he would say do it in Java.
 Unlike the JAVA guy, Tom gives very convincing arguments for why you should be
doing it in the database.

Here's an example statspack of an OLTP multi-threaded application using way too
many statements.

DB Name         DB Id    Instance     Inst Num Release     Cluster Host
------------ ----------- ------------ -------- ----------- ------- ------------
                                             1 9.2.0.3.0   NO

            Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:      21 27-Sep-03 00:57:19      307        .3
  End Snap:      22 27-Sep-03 01:20:04      304        .3
   Elapsed:               22.75 (mins)

Cache Sizes (end)
~~~~~~~~~~~~~~~~~
               Buffer Cache:    10,240M      Std Block Size:        16K
           Shared Pool Size:       304M          Log Buffer:     1,024K

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:            265,769.56             92,828.93
              Logical reads:             26,635.20              9,303.24
              Block changes:              1,996.81                697.45
             Physical reads:                 49.65                 17.34
            Physical writes:                 20.66                  7.21
                 User calls:              1,508.87                527.02
                     Parses:                282.46                 98.66
                Hard parses:                 19.60                  6.85
                      Sorts:                215.00                 75.10
                     Logons:                  0.02                  0.01
                   Executes:                409.13                142.90
               Transactions:                  2.86

  % Blocks changed per Read:    7.50    Recursive Call %:    18.56
 Rollback per transaction %:    0.00       Rows per Sort:     0.81

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:  100.00
            Buffer  Hit   %:   99.82    In-memory Sort %:  100.00
            Library Hit   %:   94.67        Soft Parse %:   93.06
         Execute to Parse %:   30.96         Latch Hit %:   99.87
Parse CPU to Parse Elapsd %:             % Non-Parse CPU:

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   90.74   94.71
    % SQL with executions>1:   55.27   60.22
  % Memory for SQL w/exec>1:   48.75   47.99

Top 5 Timed Events
~~~~~~~~~~~~~~~~~~                                                     % Total
Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
latch free                                        164,859       6,038    85.81
db file sequential read                            64,529         812    11.54
db file parallel write                              1,859          67      .95
log file sync                                       3,878          63      .90
log file parallel write                             4,153          29      .41
          -------------------------------------------------------------

A couple of things I can point out.  If you've read Tom's OOO Expert, you'll
notice that the Hard Parse and Parse counts are through the roof.  My top wait
is "latch free" of which I'm spending 85.6% of my time doing.  Yep...I would say
we have a reparshing too many SQL statements issue.  We did have
cursor_sharing=force on this database, and it helped a lot.  Unfortunately, due
to an Oracle bug which crashes the database we had to set the
cursor_sharing=exact until the bug is fixed.

-Peter

      Rating: 3
Would this be a
      reason to user a Statement?  September 29, 2003

Reviewer:  Matt  from Australia
Would the following be e reason to replace Prepared Stements with Statements and
concatenated strings parsed into SQL?

"Use connection pooling because opening and closing connections to the database
are expensive.  Connection pooling makes reusing PreparedStatement more of a
challenge, but not impossible.  You have to always explicitly close your
connection or you'll end  up with cursor leaks.  The performance won't be as
great as having it in the  database. "

I'm told that something like this happened on this system. What is the work
around - there must have been an easier solution?

          Followup:

you use prepared statements in order to BIND

if you do not BIND, you die, crash and burn.  game over player 1.

you can use prepared statements further to avoid over parsing, but that is
incidental.

USE PREPARED STATEMENTS no matter what, even if you just

parse
bind
execute
close

optimally you would

parse once

bind + execute over and over

close

      Rating: 3
I think you
      misunderstand me.  September 29, 2003
Reviewer:
      Matt  from Australia
I understand and appreciate the need for binds.

The previous poster hinted at a known consideration when using
CallableStatements. I beleive that something like this caused the developers of
the app I am refering to re-write their binds to use Statements (no binds). I'm
told there was some cursor leak.

At the moment this is all rumour. I believe that they must have hit a problem,
but that if the re-write was their solution, then it was definately the wrong
one.

I would like to understand any "cursor leak" type problems that could be
encountered and what I expect would be a relatively simple workaround if I came
across this in the future.

Ta,

      Rating: 5
Cursor leak is
      not because of using Statement vs PS  October 03, 2003

Reviewer:  Peter Tran  from Houston, TX USA

Matt,

Cursor leaks is because the developer did not call close on the Statement or
PreparedStatement.  If you use connection pooling and you call close() on the
connection, but you don't explicit close the PreparedStatement or Statement then
you'll have a cursor leak.  The connection pool doesn't really close the
physical connection, so all those unclosed Statements or PreparedStatment will
eventually come back and haunt you.

As Tom says,

use Statements
 * for DDL.
 * in a OLAP application where the SQL is excuted once.

use PreparedStatements every where else.

HTH,
-Peter

      Rating: 3
Thin JAVA
      Database Code Layer  November 20, 2003
Reviewer:
      Orca777  from Germany
if i see JAVA based application running against the database
often there is a configuration like this :

One RAC ( two nodes ) 4G MeM ...
8..16 Application servers ( due to number of seessions )
every 2G MEM ...

I believe if you can use more PL/SQL to reduce Network, and JAVA session memory
the number of Application Server could shrink.

But when all SQL is executed by JAVA directly, you need huge resources to to
this.

A problem is not solved with object mapping tools like TopLink, i think PL/SQL
Usage is not possible.

When i (small)talk with JAVE Experts they do not like to put their attention on
database stuff. So the modelling part is perfect, but the perfoamnce part and
resources part is under water.

So i am not an enemy of JAVA but if performance is the point you need look over
the JAVA universum.

Orca

          Followup:

(if you use plsql, you do not need toplink -- think about that.  they could be
considered mutually exclusive)

who cares of the form is beautiful if the function stinks.

November 21, 2003 Gunjeet -- Thanks for the question regarding
      "DBMS_SQL : using bind variables in insert stmt", version v8.1.6

      You Asked
Hi Thomas,

I understand that using bind variables in an insert statement
can speed up the performance.

Can it be done the same way using dbms_sql ? If yes, could you
please provide me with an example of inserting a bunch
of rows in a table (table-name is known) using dbms_sql and
bind variables ?

Also, will performance be better if the cursor is opened once
at the begining, then all inserts done and then closed only
once at the end ?

Thanks,

Gunjeet

      and we said...
There are many ways to tackle this problem.  I'll show you at
least 5 of them that use dynamic sql to insert into a table and
that all use bind variables.  The performance to insert 1000
rows using these methods on my machine varied from over 2.5
seconds at the worst case to under 3/4 of a second in the best
case.

The 5 methods I used were:

o dbms_sql where I opened, parsed, bound, executed and closed
  the cursor in a loop.  This is by far the worst performing
  method.  We do not need to open, parse and close in the loop
  and doing so added about 1 (out of 2.6) seconds to our runtime

o dbms_sql where I opened, parsed and closed the cursor once
  for 1000 rows.  In a loop I bound and executed 1000 times.
  This was much faster then above.

o dbms_sql where I filled an array with 1000 elements and then
  open, parsed, bound arrays, executed and closed 1 time.  This
  was much faster then the second one above and over 2 times
  faster then the first approach.

o EXECUTE IMMEDIATE (i see you are using 8.1.6, execute
  immediate is an "easier dynamic sql".  Execute immediate done
  inside the cursor for loop was faster then #1 and #2 above
  but since it does single row inserts, was marginally bested by
  #3 above.

o EXECUTE IMMEDIATE to execute a plsql block that BULK inserted
  1000 rows.  This was by far the fastest.

If I had to rate the above on complexity of code I would say:

#1  - medium
#2  - medium
#3  - complex
#4  - trivial
#5  - complex

So, my 2 best performers were somewhat complex in their
implementation.  A trivial (easy to code) implementation faired
very well though (numbers below for comparision).

Here is the example I used:

ops$tkyte@DEV8I.WORLD> create table t ( x int, y date, z
varchar2(255) );
Table created.

thats my sample table.  I will insert 1,000 rows into this
table and time it.

ops$tkyte@DEV8I.WORLD> create or replace procedure p1
  2  as
  3      l_cursor    int;
  4      l_status    int;
  5  begin
  6      for i in 1 .. 1000
  7      loop
  8          l_cursor := dbms_sql.open_cursor;
  9          dbms_sql.parse( l_cursor,
 10                         'insert into t (x,y,z)
 11                          values (:x,:y,:z)',
 12                          dbms_sql.native );
 13          dbms_sql.bind_variable( l_cursor, ':x', i );
 14          dbms_sql.bind_variable( l_cursor, ':y', sysdate+i
);
 15          dbms_sql.bind_variable( l_cursor, ':z',
 16                                  'this is row ' || i, 255 );
 17          l_status := dbms_sql.execute( l_cursor );
 18          dbms_sql.close_cursor(l_cursor);
 19      end loop;
 20  end;
 21  /

Procedure created.

P1 is the most inefficient.  At least it uses bind variables
but it unneccessarily opens and parses and closes inside the
loop.  We will see below how much this affected us

ops$tkyte@DEV8I.WORLD> create or replace procedure p2
  2  as
  3      l_cursor    int default dbms_sql.open_cursor;
  4      l_status    int;
  5  begin
  6      dbms_sql.parse( l_cursor,
  7                     'insert into t (x,y,z)
  8                      values (:x,:y,:z)',
  9                      dbms_sql.native );
 10
 11
 12      for i in 1 .. 1000
 13      loop
 14          dbms_sql.bind_variable( l_cursor, ':x', i );
 15          dbms_sql.bind_variable( l_cursor, ':y', sysdate+i
);
 16          dbms_sql.bind_variable( l_cursor, ':z',
 17                                 'this is row ' || i, 255 );
 18          l_status := dbms_sql.execute( l_cursor );
 19      end loop;
 20
 21      dbms_sql.close_cursor(l_cursor);
 22  end;
 23  /

Procedure created.

P2 is a simple optimization of P1.  moved the open and parse
outside of the loop as well as the close

ops$tkyte@DEV8I.WORLD> create or replace procedure p3
  2  as
  3      l_cursor    int;
  4      l_status    int;
  5      l_x            dbms_sql.number_table;
  6      l_y            dbms_sql.date_table;
  7      l_z            dbms_sql.varchar2_table;
  8  begin
  9      for i in 1 .. 1000
 10      loop
 11          l_x(i) := i;
 12          l_y(i) := sysdate+i;
 13          l_z(i) := 'this is row ' || i;
 14      end loop;
 15      l_cursor := dbms_sql.open_cursor;
 16      dbms_sql.parse( l_cursor,
 17                     'insert into t (x,y,z)
 18                      values (:x,:y,:z)',
 19                      dbms_sql.native );
 20      dbms_sql.bind_array( l_cursor, ':x', l_x );
 21      dbms_sql.bind_array( l_cursor, ':y', l_y );
 22      dbms_sql.bind_array( l_cursor, ':z', l_z );
 23      l_status := dbms_sql.execute( l_cursor );
 24      dbms_sql.close_cursor(l_cursor);
 25  end;
 26  /

Procedure created.

P3 is a complex optimization of P2.  We will use array
processing to send to the kernel 1000 rows to be inserted --
instead of calling into the kernel 1000 times to insert a row.

ops$tkyte@DEV8I.WORLD> create or replace procedure p4
  2  as
  3  begin
  4      for i in 1 .. 1000
  5      loop
  6          execute immediate
  7          'insert into t (x,y,z) values (:x,:y,:z)'
  8           using i, sysdate+i, 'this is row ' || i;
  9      end loop;
 10  end;
 11  /

Procedure created.

P4 -- so easy to code, uses bind variables -- pretty
efficient.  Unfortunately, internally its a little like P1. The
open, parse, close calls are inside of the loop.  Its faster
then DBMS_SQL is (much) but still, not as fast as DBMS_SQL using
array processing.  Lets add that in the next example:

ops$tkyte@DEV8I.WORLD> create or replace package state_pkg
  2  as
  3      g_x            dbms_sql.number_table;
  4      g_y            dbms_sql.date_table;
  5      g_z            dbms_sql.varchar2_table;
  6  end;
  7  /

Package created.

Here are the arrays we have.  We cannot "bind" arrays using
execute immediate so what we do is put them in a package spec so
they are globally visible.  We can reference a plsql package
spec variable dynamically so we can still "bind" them
indirectly

ops$tkyte@DEV8I.WORLD> create or replace procedure p5
  2  as
  3  begin
  4      for i in 1 .. 1000
  5      loop
  6          state_pkg.g_x(i) := i;
  7          state_pkg.g_y(i) := sysdate+i;
  8          state_pkg.g_z(i) := 'this is row ' || i;
  9      end loop;
 10      execute immediate
 11      'begin
 12          forall i in 1 .. :N
 13              insert into
 14              t (x,y,z)
 15              values (state_pkg.g_x(i),
 16                      state_pkg.g_y(i),
 17                      state_pkg.g_z(i));
 18       end;'
 19       USING 1000;
 20  end;
 21  /

Procedure created.

Here we combine 2 Oracle8i features -- FORALL (array
processing for plsql inserts/updates and deletes) with EXECUTE
IMMDIATE.  While this is somewhat complex to code -- takes a
little thought, it is by far the best performer.

ops$tkyte@DEV8I.WORLD> exec p1

PL/SQL procedure successfully completed.

Elapsed: 00:00:02.58

ops$tkyte@DEV8I.WORLD> select count(*) from t;
  COUNT(*)
----------
      1000
Elapsed: 00:00:00.01

ops$tkyte@DEV8I.WORLD> truncate table t;
Table truncated.
Elapsed: 00:00:00.21

ops$tkyte@DEV8I.WORLD> exec p2

PL/SQL procedure successfully completed.

Elapsed: 00:00:01.65

ops$tkyte@DEV8I.WORLD> select count(*) from t;
  COUNT(*)
----------
      1000
Elapsed: 00:00:00.00

ops$tkyte@DEV8I.WORLD> truncate table t;
Table truncated.
Elapsed: 00:00:00.24

ops$tkyte@DEV8I.WORLD> exec p3

PL/SQL procedure successfully completed.

Elapsed: 00:00:01.07

ops$tkyte@DEV8I.WORLD> select count(*) from t;
  COUNT(*)
----------
      1000
Elapsed: 00:00:00.01

ops$tkyte@DEV8I.WORLD> truncate table t;
Table truncated.
Elapsed: 00:00:00.39

ops$tkyte@DEV8I.WORLD> exec p4

PL/SQL procedure successfully completed.

Elapsed: 00:00:01.36

ops$tkyte@DEV8I.WORLD> select count(*) from t;
  COUNT(*)
----------
      1000
Elapsed: 00:00:00.01

ops$tkyte@DEV8I.WORLD> truncate table t;
Table truncated.
Elapsed: 00:00:00.45

ops$tkyte@DEV8I.WORLD> exec p5

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.69

ops$tkyte@DEV8I.WORLD> select count(*) from t;

  COUNT(*)
----------
      1000

Elapsed: 00:00:00.01

Review &
      Followup

      Rating: 5
"DBMS_SQL :
      using bind variables in insert stmt"  March 12, 2001

Reviewer:  Chris Barr  from Cambridge, MA USA

Extremely helpful.
Wonderfuly helpful.
Amazingly helpful.
An overview, 5 coded examples, with runtimes, ...
Concise, brief but not too brief.
Well-formated i.e. readable.

As usual ... Tom (this must be Tom's crew, not just 1 person) .. hits a
bulls-eye.

      Rating: 5
Wonderful
      example  May 14, 2001
Reviewer:  Pichaimani
      Balasubramanian  from San Jose, CA, USA
Hi Tom,
   This is really an excellent piece to understand and use
bulk variables and arrays. A very educative one.

      Rating: 5
Excellent
      work  July 12, 2001
Reviewer:  Pradeep
       from Houston Texas USA
This example really helped us. Thank you very much

      Rating: 5
Excellent
      Explanation  September 10, 2001
Reviewer:
      Sivakumar Ponnuswamy  from USA
Tom,

You are "THE" person.

Thanks alot.

      Rating: 5
Just what I
      needed  October 12, 2001
Reviewer:  Jaideep
      Shah  from San Jose, California, USA
I was looking for an example to properly use String data in my scripts for a
data migration script. The particular problem I was confronted with was using a
VARCHAR column in the where clause of my query and the data for this column
could contain single and double apostrophes, in which case the script was
failing. The information on bind_variable showed here pointed me in the right
direction and the examples made the usage cystal clear. Following this, I was
able to successfully run my script in the first attempt after the change to
using bind_variable.

      Rating:
      5
  December 11, 2001
Reviewer:  Tania
      W.  from South Africa
Brilliant example ... and ... explained

      Rating: 4
result of
      tkprof Using this  January 11, 2002
Reviewer:
      Ak  from India
I Have run p4 and, seen tkprof ..here is the info

1)fist time i used sqlplus to execute this p4..it did following...
a)create or replace procedure p4
  as
    begin
        for i in 1 .. 10
       loop
            execute immediate
            'insert into test (eno,ename) values (:x,:y)'
             using i,'name ' || i;
      end loop;
   end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          1           0
Execute      1      0.14       0.16          0          0          1           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.14       0.16          0          0          2           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 32
b)BEGIN p4; END;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.01       0.03          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.02       0.04          0          0          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 32

c)insert into test (eno,ename)
values
 (:x,:y)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       10      0.01       0.01          0          0          0           0
Execute     10      0.01       0.01          0         10         12          10
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       20      0.02       0.02          0         10         12          10

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 32     (recursive depth: 1)

Frankly speaking i would like to see part C that too with one parse and 10
execute.Can You tell me what's happening here ???
Regards
   Ak

          Followup:

did you read my discussion?

I said:

<quote>
P4 -- so easy to code, uses bind variables -- pretty efficient.
Unfortunately, internally its a little like P1. The open, parse, close calls are

inside of the loop.  Its faster then DBMS_SQL is (much) but still, not as fast
as DBMS_SQL using array processing.  Lets add that in the next example:

</quote>

With ease of programming comes inefficiencies sometimes.  This is one of them.
You are using a very very simple construct "execute immediate".  Everytime you
call execute immediate -- you could be sending it a wholly different string --
it is a variable string that is passed into it.  It cannot parse it once, it has
to parse it each and every time.

execute immediate is good for ONE TIME execution of a dynamic sql statement.

dbms_sql is good for REPEATED executions.

If you are interested in yet more details, I have an expansive chapter on this
in my book.

      Rating:
      4
tkprof  January 11, 2002
Reviewer:
      Ak  from India
Hi,
I even tried calling this proc p4 from plsql,and it was same
result,please clarify(I even don't want soft parse,because soft parse is less
expansive hard parse).I am in a process to change my complete application in
order to Use bind variable,we are planning to put all sql statement in a store
proc and call store proc from front end...because vb doesn't seems to doing
what we want ,and our vb programmer have given Up.Please guide me.
Thanks
  Ak

Thanks,
  Ak

          Followup:

Well, first and foremost -- DO YOU NEED TO USE DYNAMIC SQL.   I think the answer
is "no, I do not need to use dynamic sql, static sql is what I will be using"

If that is the case, then don't worry -- plsql does all of the cursor caching
for you.  Just use static sql, code:

create or replace procedure p4
  as
    begin
        for i in 1 .. 10
       loop
            insert into test (eno,ename) values ( i, 'name ' );
      end loop;
   end;

that'll parse the insert once per session, not once per iteration.

else, use DBMS_SQL and array binds.

      Rating:
      4
tkprof  January 12, 2002
Reviewer:
      Ak  from India
Hi,
  IN addition to the previous two comments...Please tell me
what is best way to insert,select,delete a record thro
stored proc(we will be passing the parameter at run time).
And this proc will be called again and again simultanuosly
from diff session(diff workstation).Please give me One complete example.

Thanks,
 Ak

          Followup:

read the plsql manual.  There is but one way to insert and delete a record
(execute the insert/delete).

My prefered method for selection is a cursor for loop:

   for x in ( select * from t where ... )
   loop
      ....
   end loop

if I expect 0, one or more records and select into if I expect 0 or 1 records:

   begin
       select * into l_record from t where ....;
   exception
       when no_data_found then
            ....
   end;

      Rating: 4
pl/sql
      optimization  January 12, 2002
Reviewer:  Mikito
      harakiri
Isn't that the part that pl/sql optimiser supposed to do? For example, moving
expensive expression outside the loop, transforming P1 into P2 and P4 into P5?
That's what other procedural language optimizers routinely do (admittedly, for
simpler, expressions, though). After all, if case P4 is easiest to write, why
deprive user of doing so?

          Followup:

You missed the point.

it is in general:

  loop
    execute immediate some_variable;
  end loop;

If you have code like:

   loop
      execute immediate 'insert into t values ( :x )' using y;
   end loop;

you should NOT be using dynamic sql at all -- so its your fault.  If you are
using dyanmic sql, it typically involves code like:

  loop
     execute immediate some_variable_that_could_change_at_any_moment;
  end loop

it is NOT POSSIBLE for us to 'optimize' that unless you want us to do the wrong
thing.

If you can benefit from the optimization because you know that the statement is
invariant in the loop -- you use DBMS_SQL, not execute immediate.

(i give up)....

      Rating:
      4
tkprof  January 13, 2002
Reviewer:
      ak  from India
Hi tom,
  I did a test
create or replace procedure p3(first number,sec char)is
begin
   for i in 1..10 loop
  insert into test values(first,sec);
  commit;
end loop;
end;
sql>exec proc(1,'aa');

Tkprof was showing perfactly ok..but every time i execute p3 i see
begin p3(:b1,:b2) end;----this parse and execute 1 times each..why ?? first time
ok...after that it shouldn't ??
Isn't ???

please clarify.

Thanks,
 Ak

          Followup:

SQLPlus must parse each and every statment -- sqlplus is alot like using EXECUTE
IMMEDIATE in plsql.  It cannot cache the cursor since every time you enter a new
statement -- it is potentially different from the prior statement.

In a WELL WRITTEN program in any language, you would see a parse of P3 once per
session with many executes.  In SQLPlus, a very very simple command line tool
used to test ideas, you will not since everything is dynamic.

      Rating: 4
create inside
      execute immediate  January 14, 2002
Reviewer:
      ak  from India
Hi tom,
In addition to previous question , I want to do following..
create or replace procedure try
as
t1 varchar2(10);
begin
t1:='Good';
  execute immediate 'create table :x(eno number)' using t1;
end;
Meaning ,Table name i want to Generate at run time...can You give me One
example.

Thanks,
 regards
   Ak

          Followup:

it would be

  execute immediate 'create table ' || t1 || '(eno number)';

you cannot BIND DDL ever -- DDL does not do bind variables.

      Rating:
      5
  January 14, 2002
Reviewer:
      A reader
Which is "THE" best method among five of above?

          Followup:

if there was THE best method -- there would be ONE way.

Use the one that is most appropriate (going to insert a single row into a table
dyanically??  use execute immediate -- its trivial.  going to insert 1,000 rows
into it?  dbms_sql might be the easiest to code and gets very good
performance)....

      Rating:
      5
  January 14, 2002
Reviewer:
      A reader
TOM!!!! Is the best

      Rating: 5
dynamic sql
      insert realistic example  January 14, 2002

Reviewer:  Mikito Harakiri
Can you give a realistic example when insert performed in a loop can't be
reduced to static sql? I mean, if sql is dynamic, the user probably wants to
build the statement by concatenation, if it's static, then he uses binds. Why
are we discussing "the middle ground" case; it doesn't exist!?

          Followup:

You write a generic load utility in PLSQL.  It is passed the name of a file, the
name of a table.

This case happens MANY times -- sqlldr is perhaps the perfect example of it.

You write a generic "reformat" utility -- it is passed the owner.table_name to
read from and the owner.table_name to write to.

There are two cases then where an insert performed in a loop can't be reduced to
static SQL.  You do not know the names of the table you are inserting into, you
might not know the columns you are inserting into (use your imagination -- I'm
sure there are others)

Never mind the fact that the user NEVER EVER in a BILLION YEARS wants to build
the string by concatenation.

reason #1: the performance of that (as i've said many a time here) stinks.  It
kills performance, kills scalability, kills the shared pool, is 100% the wrong
approach

reason#2: it is actually (get this) HARDER then using bind variables to
successfully concatenate a string together.  You have to worry about things
like:

   - does the character string literal exceed 4000 characters??  if so, you
won't
     be using concatenation in SQL.
   - does the character string literal include quotes?? if so, be ready to
double
     them all up before concatenating
   - what is the length of the result insert statement going to be?  If I have
     10 columns that are varchar2(4000) that could easily exceed 32k!! Now
     I have to use dbms_sql.parse with a PLSQL table type instead of a string,
     I have to make sure the sql statement does not exceed 32k.  If I used binds
     no such worries -- even in the extreme cases.

      Rating: 4
without
      "execute immediate"  January 14, 2002
Reviewer:
      Barry Ludwig  from Netherlands
Hi Tom,

You could skip the "execute immediate"

create or replace procedure p5
as
begin
   for i in 1 .. 1000
      loop
      state_pkg.g_x(i) := i;
      state_pkg.g_y(i) := sysdate+i;
      state_pkg.g_z(i) := 'this is row ' || i;
   end loop;

   -- 1..1000 or 1..state_pkg.g_x.COUNT or
   forall i in state_pkg.g_x.FIRST..state_pkg.g_x.LAST
      insert into t (x,
                     y,
                     z)
             values (state_pkg.g_x(i),
                     state_pkg.g_y(i),
                     state_pkg.g_z(i));
end;
/

          Followup:

sure, but it would not be a dynamic sql example anymore (the point of this was
to compare/contrast the different approaches)...

      Rating: 5
Excellent
      solution and explanation  January 15, 2002

Reviewer:  jiten padhiar  from UK
Your examples are just excellent and useful. Good work again...

I have often coded similar to p1. How can I adapt your example of p2 below. The
'from' clause has to be dyanmically built based on the cursor loop - I need to
select data from different user's table depending on the actual user.

    FOR rec IN curGeneralInvoice (ld_StartDate) LOOP
        BEGIN
            -- open cursor for processing
            ln_CursorId := DBMS_SQL.OPEN_CURSOR;

            -- Identify statement to process

            ls_SelectStmt := 'SELECT dlcus_code2
                                   ,dlcus_area
                                   ,dlcus_code7
                                    ...
                          FROM     '||rec.soh_company||'.dlcust@stx_york
                             WHERE    dlcus_customer = :CustAcct';

            -- parse the statement
            DBMS_SQL.PARSE (ln_CursorId, ls_SelectStmt, DBMS_SQL.V7);

            -- bind variables in the statement
            DBMS_SQL.BIND_VARIABLE (ln_CursorId, ':CustAcct', rec.soh_account);

            -- define the output variables
            DBMS_SQL.DEFINE_COLUMN (ln_CursorId, 1,
...            DBMS_SQL.DEFINE_COLUMN (ln_CursorId, 8, ls_code7 ,4);

            -- execute the insert statement
            ln_dummy := DBMS_SQL.EXECUTE (ln_CursorId);

            -- determine number of rows fetched
            ln_rows := DBMS_SQL.FETCH_ROWS (ln_CursorId);
.....

          Followup:

You could add "ORDER BY SOH_COMPANY" to the query so the data comes out sorted
by company.

then,

is
   l_last_company varchar2(255) default 'x';
   ...
   l_cursorId     number := dbms_sql.open_cursor;
begin
   ...
   for rec in curGeneralInvoice( ld_startDate )
   loop
       if ( l_last_company != rec.soh_company )
       then

            ls_SelectStmt := 'SELECT dlcus_code2
                                   ,dlcus_area
                                   ,dlcus_code7
                                    ...
                          FROM     '||rec.soh_company||'.dlcust@stx_york
                             WHERE    dlcus_customer = :CustAcct';

            -- parse the statement
            DBMS_SQL.PARSE (ln_CursorId, ls_SelectStmt, DBMS_SQL.native );

      end if;
      dbms_sql.bind_variable...
      dbms_sql.define_column....
      ......

That way, you'll parse a query once per company and execute it over and over and
over.  Also, just get a dbms_sql cursor ONCE.

      Rating: 4
One
      More!  January 15, 2002
Reviewer:  Ashok
       from USA,JPMORGAN
If i know table name to be inserted and the col name and values in the pl/sql
procedure and if i want to insert 3000 rows in the loop which one is the best.
i.e

Insert into table High_yield values(var1,Var2) [STATIC SQL]

or DBMS_SQL.Parse
DBMS_SQL.Bind(Var1,Col1)
DBMS_SQL.Bind(Var2,Col2)

Thx
Ashok

          Followup:

STATIC sql is always preferable to dynamic sql in PLSQL -- without question.
 If you can do it statically -- do it.

      Rating: 5
what is the
      best way ?  February 25, 2002
Reviewer:
      vivek  from NY,USA
Hi Tom,

 I have a vary big table having 10 Mil rows

i have uri which contains uri and query in the uri field

so I wrote a script
as follows

/*************************************/
Table name :  web_logs100

file_name  varcahr2(2000),
uri        varcahr2(2000),
query      varcahr2(2000),
ip         varcahr2(15)

There is no primiary key or any other indexes
on this table
/*************************************/

/*********************************************/
create or replace
procedure get_id_info_1 is

cursor uriCur is
         select rowid,uri from web_logs100
          where uri like '/view%/%';
        check_name_id number;
        check_name_id1 number;

Begin
FOR  uriRec in uriCur
loop
exit when uriCur%NOTFOUND;
    /**********  seperation *************************/
        check_name_id := instr(uriRec.uri,'/',-1);
        Begin
        update web_logs100
                    set uri = substr(:x,1,:z),
               query = substr(:x,:z+1) WHERE rowid LIKE :y'

               commit;
         Exception
                when others then
         dbms_output.put_line('Error :   '|| sqlerrm);
         dbms_output.put_line('URI :'|| uriRec.uri);

      End;
end loop;
end get_id_info_1;
/**********************************************/

  It took two days already. will dynamic sql do it faster ?
can you tell me how to do that ?

thanks,
vivek

          Followup:

your update statement is not even close to being valid.

No, dynamic sql will not do it faster.

Why would you have "where rowid like" when you have the exact rowid???

      Rating: 5
what is the
      best way ?  February 25, 2002
Reviewer:
      vivek  from NY,USA
Hi Tom,
Sorry for above update statement !!

here is the correct one
so I wrote a script
as follows

/*************************************/
Table name :  web_logs100

file_name  varcahr2(2000),
uri        varcahr2(2000),
query      varcahr2(2000),
ip         varcahr2(15)

There is no primiary key or any other indexes
on this table
/*************************************/

/*********************************************/
create or replace
procedure get_id_info_1 is

cursor uriCur is
         select rowid,uri from web_logs100
          where uri like '/view%/%';
        check_name_id number;
        check_name_id1 number;

Begin
FOR  uriRec in uriCur
loop
exit when uriCur%NOTFOUND;
    /**********  seperation *************************/
        check_name_id := instr(uriRec.uri,'/',-1);
        Begin

         update web_logs100
         set uri = substr(uriRec.uri,1,check_name_id),
         query = substr(uriRec.uri,check_name_id+1)
         WHERE rowid = uriRec.rowid;
               commit;

         Exception
                when others then
         dbms_output.put_line('Error :   '|| sqlerrm);
         dbms_output.put_line('URI :'|| uriRec.uri);

      End;
end loop;
end get_id_info_1;
/**********************************************/

  It took two days already. will dynamic sql do it faster ?
can you tell me how to do that ?

thanks,
vivek

          Followup:

Well -- your logic is:

  loop over 10,000,000 records
      update a single row
      commit
  end loop

that has got to be the slowest approach ever.  It'll generate the MOST redo,
it'll take the longest to run (pretty much each commit must wait for a log file
sync).

You should just issue:

  update web_logs100
     set uri = substr( uri, 1, instr(uri,'/',-1),
         query = substr(uri, 1, instr(uri,'/',-1)
   where uri like '/view%/%';

The problem with your approach (in addition to being slllloooowwwww) is that it
is not RESTARTABLE.  What if halfway through you "fail".  If you just re-run it,
you'll corrupt already updated rows (eg: the url /view/something/else/was/here/x
would be updated to /view/something/else/was/here/ on the first go around and
the second time it would be /view/something/else/was/  the next time.

Just a single update, its all you need.  You are wasting resources by committing
when you should not be.  If you are worried about rollback -- add more.  Don't
do row at a time processing when a single update would do it.

dynamic sql CANNOT be faster - the code path is longer.  It will certainly be
MUCH longer.  The single update will be fastest.

      Rating:
      5
  February 25, 2002
Reviewer:  vivek
       from NY,USA
Thanks,
     I was just trying to use dynamic sql here.
Why will the dynamic sql not help here ?

Can you please tell ?

 will the statement get parsed only once  here ?

Thanks,

          Followup:

static sql in plsql is much faster then dynamic sql.

In your case, plsql isn't even needed, just do the update.

      Rating:
      5
Excellent  February 25, 2002
Reviewer:
      Raza  from Canada
Thats what I was looking! You are impressive

      Rating: 4
I want it to be
      automated  February 25, 2002
Reviewer:
      vivek
Thanks tom,

  Now my table web_logs1000 is get loaded by a procedure and then authomatically
it should populate all dimention tables and fact every night. so I have to have
some thing I can call (like function or porcedure) this is not one time process.

   on the other hand if I issue just update statement
and in the middle If I had to kill the session or something
I think I will loose all processed rows that is why commit is there ? what do
you say ?

so how will I execute only update statement only ?

          Followup:

my point is -- if you run your process 2 times, it will change the data it
already changed! look at your logic. Every day you will update the rows you've
already updated.

Why not just put a trigger on the table that is:

create or replace trigger trigger_name
before insert on t
for each row
begin
   if ( :new.url like '/view%/%' )
   then
     :new.uri := substr( :new.uri, 1, instr(:new.uri,'/',-1) );
     :new.query := :new.uri;
   end if;
end;

Update the existing data ONCE, fix the newly added rows as they come in.  Else
every day your update will take longer then the day before.

Your "update row/commit/update row/commit" is the SLOWEST way to do this, it is
the way that generates the MOST redo, it wastes resources.  If thats how you
want to do it -- so be it -- it'll be slow.  Just how often does your machine
crash?  if that is your overriding concern, I would expend my energies on making
it so my machine doesn't crash -- not by committing.  Also, since your process
is not restartable from where it left off -- it will update all of the rows it
already updated ANYWAY (you haven't saved a bit of time!).

      Rating:
      4
thanks,  February 26, 2002
Reviewer:
      vivek
you are right !!

 1.) I can eleminate those rows by adding one predicate to the update statement
ie.
/*******************************************/
update web_logs
     set uri = substr( uri, 1, instr(uri,'/',-1)),
         query = substr(uri,instr(uri,'/',-1)+1)
   where uri like '/view%/%'
   and   query is null;
/****************************************/

   Also this table is tamporary table so as soon as I do
a little porcessing on it and move data to other tables
this table gets truncated and new sever logs is get loaded
(there are 4 servers -- I process 1 server logs at a time
  and has 1 mil rows)

 But still your suggestion is far batter and I have changed
it to simple update statement !!
  one more thin here, so when do we use a static cursor and
when the dynamic cursor with the bind variable ?

  because if  I say
/*******************************************/
  declare
     cursor empcur is
       select empid,empname,deptcd;

/***************************************/
--case 1
begin
  for empRec in empCur
  execute immidiate
    'insert into emp1(:x,:y,:z)'
         using emprec.empid,emprec.ename,emprec.deptcd;
 end loop;

end;
/****************************************/
--case 2
begin
  for empRec in empCur
   insert into emp1
    (emprec.empid,emprec.ename,emprec.deptcd);
 end loop;
end;
/**************************************/

what is batter ?

          Followup:

to answer:

 But still your suggestion is far batter and I have changed
it to simple update statement !!
  one more thin here, so when do we use a static cursor and
when the dynamic cursor with the bind variable ?

is simple.

If you can use static sql, use it.  If not, if what you are doing demands
dynamic sql, use that.

If you have my book -- i cover this in detail in the chapter on dynamic sql
showing the differences in performance/functionality between the two.

      Rating: 3
DBMS_SQL -
      Insert into the same table   April 29, 2002

Reviewer:  SL  from MA, USA
Hi Tom,

I am tring to insert records into the same table as a part of SELECT. The table
has 1.3 million records

SQL> select count(*) from scenarioadjustment;

  COUNT(*)
----------
   1384637

Here is the code.

DECLARE
  endnum                   NUMBER;
  c_quantity        CONSTANT INTEGER := 1000;
BEGIN

   -- Fill the scenarioadjustment table

   SELECT nvl((max(laneid)-1)/c_quantity,-1)
     INTO endnum
     FROM lane
    WHERE rfpid = 1148;

   FOR timethrough in 0..endnum

   LOOP

     INSERT INTO scenarioadjustment
       ( laneequipmenttypeid, obcarriercodeid, scenarioid, packageid, rfpid,
laneid,
     equipmenttypeid, servicetype, protectionlevel, incumbent,
     inboundfacilitycode, outboundfacilitycode, costperload, globaladjpct,
facilityadjpct,
     laneadjpct, carrieradjpct, manualawardamount, autoawardamount,
     packageawardamount, historicalcost, needsautoaward, lanevolume,
     capacity, createddttm, numdays
       )
    SELECT * FROM
         (
        SELECT laneequipmenttypeid, obcarriercodeid, 8, packageid, rfpid,
laneid,
           equipmenttypeid, servicetype, protectionlevel, incumbent,
           inboundfacilitycode, outboundfacilitycode, costperload, globaladjpct,
facilityadjpct,
           laneadjpct, carrieradjpct, manualawardamount, autoawardamount,
           packageawardamount, historicalcost, needsautoaward, lanevolume,
           capacity, createddttm, numdays
          FROM scenarioadjustment
         WHERE rfpid = 1148
               AND scenarioid = 0
               AND laneid >= timethrough * c_quantity + 1
               AND laneid <= timethrough * c_quantity + c_quantity

           ORDER BY RFPID, SCENARIOID, LANEID, LANEEQUIPMENTTYPEID,
                       OBCARRIERCODEID, PACKAGEID
     );

   END LOOP;

   COMMIT;

END;
/

Attached is the tkprof output

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      2     28.30      56.93          0     231533     249943      225000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3     28.30      56.93          0     231533     249943      225000

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: 86     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
 225002  VIEW
 225002   TABLE ACCESS BY INDEX ROWID SCENARIOADJUSTMENT
 225002    INDEX RANGE SCAN (object id 77405)

********************************************************************************

I tried implementing the code with dynamic SQL as array insert and the code is
much slower ??

set serveroutput on size 20000
-- Tring the insert as an array insert to improve the performance

DECLARE

v_batch_size             CONSTANT INTEGER :=1000;

v_LANEEQUIPMENTTYPEID    DBMS_SQL.NUMBER_TABLE;
v_OBCARRIERCODEID        DBMS_SQL.NUMBER_TABLE;
v_SCENARIOID             DBMS_SQL.NUMBER_TABLE;
v_RFPID                  DBMS_SQL.NUMBER_TABLE;
v_LANEID                 DBMS_SQL.NUMBER_TABLE;
v_PACKAGEID              DBMS_SQL.NUMBER_TABLE;
v_NEEDSAUTOAWARD         DBMS_SQL.NUMBER_TABLE;

v_cursor1                INTEGER;
v_cursor2                INTEGER;
v_return_code            INTEGER;
v_num_rows               INTEGER;
v_init                   INTEGER :=0;

v_insert_stmt            VARCHAR2(2000);
v_select_stmt            VARCHAR2(2000);

BEGIN

    v_cursor1 := DBMS_SQL.OPEN_CURSOR;
    v_cursor2 := DBMS_SQL.OPEN_CURSOR;

    v_select_stmt := 'SELECT laneequipmenttypeid, obcarriercodeid, 8, rfpid,
laneid, packageid, needsautoaward ';
    v_select_stmt := v_select_stmt || ' FROM scenarioadjustment WHERE rfpid =
1148 AND scenarioid = 0 ';

    v_select_stmt := v_select_stmt || ' ORDER BY RFPID, SCENARIOID, LANEID,
LANEEQUIPMENTTYPEID, OBCARRIERCODEID, PACKAGEID';

    v_insert_stmt := 'INSERT INTO scenarioadjustment (laneequipmenttypeid,
obcarriercodeid, scenarioid, rfpid, laneid, packageid, needsautoaward ) ';
    v_insert_stmt := v_insert_stmt ||  ' values ( :laneequipmenttypeid,
:obcarriercodeid, :scenarioid,  :rfpid, :laneid, :packageid, :needsautoaward )
';

    DBMS_SQL.PARSE(v_cursor1,  v_select_stmt, dbms_sql.native);
    DBMS_SQL.PARSE(v_cursor2,  v_insert_stmt, dbms_sql.native);

   -- Use DEFINE_ARRAY to specify the output variables for the Select

   DBMS_SQL.DEFINE_ARRAY (v_cursor1, 1, v_LANEEQUIPMENTTYPEID, v_batch_size, 1);
   DBMS_SQL.DEFINE_ARRAY (v_cursor1, 2, v_OBCARRIERCODEID, v_batch_size, 1);
   DBMS_SQL.DEFINE_ARRAY (v_cursor1, 3, v_SCENARIOID, v_batch_size, 1);
   DBMS_SQL.DEFINE_ARRAY (v_cursor1, 4, v_RFPID, v_batch_size, 1);
   DBMS_SQL.DEFINE_ARRAY (v_cursor1, 5, v_LANEID, v_batch_size, 1);
   DBMS_SQL.DEFINE_ARRAY (v_cursor1, 6, v_PACKAGEID, v_batch_size, 1);
   DBMS_SQL.DEFINE_ARRAY (v_cursor1, 7, v_NEEDSAUTOAWARD, v_batch_size, 1);

   -- Execute the select statement

   v_return_code := DBMS_SQL.EXECUTE(v_cursor1);

   -- DBMS_OUTPUT.PUT_LINE(' Return Code '  || TO_NUMBER(v_return_code));

   LOOP

      v_num_rows  := DBMS_SQL.FETCH_ROWS(v_cursor1);

     -- DBMS_OUTPUT.PUT_LINE('No. of rows Fetched '  || TO_NUMBER(v_num_rows));

     DBMS_SQL.COLUMN_VALUE(v_cursor1, 1, V_LANEEQUIPMENTTYPEID);
     DBMS_SQL.COLUMN_VALUE(v_cursor1, 2, v_OBCARRIERCODEID);
     DBMS_SQL.COLUMN_VALUE(v_cursor1, 3, v_SCENARIOID);
     DBMS_SQL.COLUMN_VALUE(v_cursor1, 4, v_RFPID);
     DBMS_SQL.COLUMN_VALUE(v_cursor1, 5, v_LANEID);
     DBMS_SQL.COLUMN_VALUE(v_cursor1, 6, v_PACKAGEID);
     DBMS_SQL.COLUMN_VALUE(v_cursor1, 7, v_NEEDSAUTOAWARD);

    IF v_num_rows = 0 THEN
       EXIT;
    END IF;

    -- Use Bind Array to specify the input variables for insert

    DBMS_SQL.BIND_ARRAY(v_cursor2, ':laneequipmenttypeid',
v_laneequipmenttypeid, v_init + 1, v_init + v_Num_Rows );
    DBMS_SQL.BIND_ARRAY(v_cursor2, ':obcarriercodeid', v_obcarriercodeid, v_init
+ 1, v_init + v_Num_Rows );
    DBMS_SQL.BIND_ARRAY(v_cursor2, ':scenarioid', v_scenarioid, v_init + 1,
v_init + v_Num_Rows );
    DBMS_SQL.BIND_ARRAY(v_cursor2, ':rfpid', v_rfpid, v_init + 1, v_init +
v_Num_Rows );
    DBMS_SQL.BIND_ARRAY(v_cursor2, ':laneid', v_laneid, v_init + 1, v_init +
v_Num_Rows );
    DBMS_SQL.BIND_ARRAY(v_cursor2, ':packageid', v_packageid, v_init + 1, v_init
+ v_Num_Rows );
    DBMS_SQL.BIND_ARRAY(v_cursor2, ':needsautoaward', v_needsautoaward, v_init +
1, v_init + v_Num_Rows );

    v_init := v_init + v_Num_Rows;

    -- Execute the insert statement

    v_return_code := DBMS_SQL.EXECUTE(v_cursor2);

    EXIT WHEN v_num_rows < v_batch_size;

  END LOOP;

  DBMS_SQL.CLOSE_CURSOR(v_cursor1);
  DBMS_SQL.CLOSE_CURSOR(v_cursor2);

END;
/

Please suggest

          Followup:

To go faster, just make it be:

 INSERT INTO scenarioadjustment
       ( laneequipmenttypeid, obcarriercodeid, scenarioid, packageid, rfpid,
laneid,
     equipmenttypeid, servicetype, protectionlevel, incumbent,
     inboundfacilitycode, outboundfacilitycode, costperload, globaladjpct,
facilityadjpct,
     laneadjpct, carrieradjpct, manualawardamount, autoawardamount,
     packageawardamount, historicalcost, needsautoaward, lanevolume,
     capacity, createddttm, numdays
       )
    SELECT * FROM
         (
        SELECT laneequipmenttypeid, obcarriercodeid, 8, packageid, rfpid,
laneid,
           equipmenttypeid, servicetype, protectionlevel, incumbent,
           inboundfacilitycode, outboundfacilitycode, costperload, globaladjpct,

facilityadjpct,
           laneadjpct, carrieradjpct, manualawardamount, autoawardamount,
           packageawardamount, historicalcost, needsautoaward, lanevolume,
           capacity, createddttm, numdays
          FROM scenarioadjustment
         WHERE rfpid = 1148
               AND scenarioid = 0
           ORDER BY RFPID, SCENARIOID, LANEID, LANEEQUIPMENTTYPEID,
                       OBCARRIERCODEID, PACKAGEID
     );

I don't see why you would "chop up" the insert and insert 1000 rows at a time.

My mantra:

o if you can do it in a single sql statement DO IT.

You can -- you need no procedural code. Procedural Code = extra overhead = more
runtime.  Your DBMS_SQL solution is a great case of this -- you are doing MORE
work in plsql, slowing down the SQL.  Just let SQL do it.

Never use dynamic sql where static sql will do just as well (fetch bulk collect
and FORALL inserts would have been better then DBMS_SQL but still NOT AS GOOD as
just doing it in SQL!!!!!

      Rating: 5
DBMS_SQL :
      using bind variables in insert stmt  April 29, 2002

Reviewer:  Geet Sachidananda  from MD, USA

Tom,
    This is excellent. I am using Oracle 9i(9.0.1.1.1), I can use the same
procedures. But, is there any way that I could enhance with 9i PL/SQL new
features.

  I attended your seminar at AOTC it was really good.

Thanks,

Geet

          Followup:

Yes, as a matter of fact, there is a new way to use native dynamic sql with bulk
operations:

ops$tkyte@ORA9I.WORLD> create table t ( x int );

Table created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> declare
  2          type array is table of number index by binary_integer;
  3
  4          l_data array;
  5  begin
  6     for i in 1 .. 100
  7     loop
  8        l_data(i) := i;
  9     end loop;
 10
 11     forall i in 1..l_data.count
 12        execute immediate 'insert into t (x) values( :x)' using l_data(i);
 13  end;
 14  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> select count(*) from t;

  COUNT(*)
----------
       100

ops$tkyte@ORA9I.WORLD>

thanks for the feedback -- it was fun to be able to do a non "sales"
presentation for once ;)

      Rating: 4
DBMS_SQL -
      Insert into the same table   April 29, 2002

Reviewer:  SL  from MA, USA
Thanks Tom,

I tried inserting as a part of select and it took 51 seconds as compared to 56
before.

Explain Plan looks like:-

INSERT INTO scenarioadjustment
       ( laneequipmenttypeid, obcarriercodeid, scenarioid, packageid, rfpid,
laneid,
         equipmenttypeid, servicetype, protectionlevel, incumbent,
         inboundfacilitycode, outboundfacilitycode, costperload, globaladjpct,
facilityadjpct,
         laneadjpct, carrieradjpct, manualawardamount, autoawardamount,
         packageawardamount, historicalcost, needsautoaward, lanevolume,
         capacity, createddttm, numdays
       )
  SELECT * FROM
         (
            SELECT laneequipmenttypeid, obcarriercodeid, 8, packageid, rfpid,
laneid,
                     equipmenttypeid, servicetype, protectionlevel, incumbent,
                     inboundfacilitycode, outboundfacilitycode, costperload,
globaladjpct, facilityadjpct,
                     laneadjpct, carrieradjpct, manualawardamount,
autoawardamount,
                     packageawardamount, historicalcost, needsautoaward,
lanevolume,
                     capacity, createddttm, numdays
              FROM scenarioadjustment
             WHERE rfpid = 1148
             AND scenarioid = 0
           ORDER BY RFPID, SCENARIOID, LANEID, LANEEQUIPMENTTYPEID,
OBCARRIERCODEID, PACKAGEID
        )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1     24.24      51.94          0     231523     249992      225000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2     24.24      51.94          0     231523     249992      225000

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: 86

Rows     Row Source Operation
-------  ---------------------------------------------------
 225001  VIEW
 225001   TABLE ACCESS BY INDEX ROWID SCENARIOADJUSTMENT
 225001    INDEX RANGE SCAN (object id 77405)

********************************************************************************

 Is there any other way to make it run much faster?? It is large table with more
than million records.

Seg Info:-

SEGMENT_NAME         TABLESPACE_NAME           BYTES     BLOCKS    EXTENTS
INITIAL_EXTENT NEXT_EXTENT
-------------------- -------------------- ---------- ---------- ----------
-------------- ----------
MIN_EXTENTS MAX_EXTENTS PCT_INCREASE BUFFER_
----------- ----------- ------------ -------
SCENARIOADJUSTMENT   OBLBNT                197656576      24128        377
80216064      524288
          1  2147483645            0 DEFAULT

PCT_FREE: 10
PCT_USED: 40
EXTENT_MANAGEMENT: LOCAL

Please suggest!

          Followup:

Consider using the /*+ append */ hint
Consider losing the order by in the subquery
Consider using the CBO (an index might not be the right way to access this
table)
Consider using a parallel direct path insert

Make sure to read the chapter in the concepts guide on direct path inserts
(append) before using it so as to fully understand it.

(51 seconds, depending on your system, doesn't sound horrible.  What kind of
speed are you going for here?)

      Rating: 2
But what about
      setting up a cursor....  February 10, 2003

Reviewer:  Ivan  from USA
I have to populate a cursor (afield_c) with three variables which is giving me
headaches. As you can see from my code below I have tried to call afield_c and
failed and also tried to create a select statement with execute immediate but it
failed due to the INTO phrase.

DECLARE

  v_field  VARCHAR2(254);

  FUNCTION update_fields (p_keyid crm.keyid%TYPE,
       p_source_name  crm.source_name%TYPE,
       p_field VARCHAR2 )
  RETURN VARCHAR2 IS

    CURSOR afield_c
    IS
      SELECT RTRIM(LTRIM(CHR(39) || p_field || CHR(39))) as q_field
      FROM   crm
      WHERE  RTRIM(LTRIM(keyid)) = p_keyid
      AND    source_name = p_source_name
      AND    p_field IS NOT NULL;

    v_return     VARCHAR2(254);
    v_vfield     VARCHAR2(254);
    v_phrase     VARCHAR2(254);
   BEGIN

 --  v_phrase :=  ' SELECT RTRIM(LTRIM(' || p_field ||
 --               ')) INTO v_vfield FROM crm WHERE keyid = ' || p_keyid  ||
 --               ' AND    source_name = ' || CHR(39) ||  p_source_name ||
CHR(39) || ' AND ' ||  p_field || ' IS NOT NULL' ;

   v_phrase :=  ' SELECT RTRIM(LTRIM(' || p_field ||
                ')) FROM crm WHERE keyid = ' || p_keyid  ||
                ' AND    source_name = ' || CHR(39) ||  p_source_name || CHR(39)
|| ' AND ' ||  p_field || ' IS NOT NULL' ;
   dbms_output.put_line(v_phrase);

    EXECUTE IMMEDIATE (v_phrase);

    dbms_output.put_line('done');
    /*
    v_field := NULL;

    FOR recfield in afield_c LOOP
      dbms_output.put_line(recfield.q_field);
      v_return := recfield.q_field;
    END LOOP;
    */
    dbms_output.put_line(v_vfield);
    RETURN v_vfield;
  END update_fields;

  FUNCTION call_update_fields (p_keyid  crm.keyid%TYPE)
  RETURN VARCHAR2 IS
    v_return2    crm.aname%TYPE;
  BEGIN

    IF update_fields(p_keyid,'PMD','ANAME') IS NULL THEN
      IF update_fields(p_keyid,'Segmentation','ANAME') IS NULL THEN
        IF update_fields(p_keyid,'ETI','ANAME') IS NULL THEN
          IF update_fields(p_keyid,'QQ','ANAME') IS NULL THEN
            IF update_fields(p_keyid,'ESP','ANAME') IS NULL THEN
              IF update_fields(p_keyid,'Asia','ANAME') IS NULL THEN
                NULL;
              ELSE v_return2 := update_fields(p_keyid,'Asia','ANAME');
              END IF;
            ELSE v_return2 := update_fields(p_keyid,'ESP','ANAME');
            END IF;
          ELSE v_return2 := update_fields(p_keyid,'QQ','ANAME');
          END IF;
        ELSE v_return2 := update_fields(p_keyid,'ETI','ANAME');
        END IF;
      ELSE v_return2 := update_fields(p_keyid,'Segmentation','ANAME');
      END IF;
    ELSE v_return2 := update_fields(p_keyid,'PMD','ANAME');
    END IF;

    RETURN v_return2;
  END call_update_fields;

BEGIN

  v_field := call_update_fields(381);
  dbms_output.put_line(v_field);

END;
/

Thanks as always,

Ivan

          Followup:

Umm, since it just appears you want the first ANAME for a key ordered by
source_name in a special way -- why do you not just code:

is
   l_return varchar2(255);
begin
   for x in ( select *
                from crm
               WHERE keyid = p_keyid
                 and source_name in
                 ( 'Asia', 'ESP', 'QQ', 'ETI', 'Segmentation', 'PMD' )
               order by decode( sournce_name, 'PMD', 1, 'Segmentation', 2
                                'ETI', 3', 'QQ', 4, 'ESP' 5, 'Asia', 6 )
             )
   loop
       l_return := x.aname;
       exit;
   end loop;
   return l_return;
end;

but the execute immediate would be something like:

   execute immediate l_query INTO l_field using bind var, bind var;

USING BIND VARIABLES!!!  something like this:

ops$tkyte@ORA920> declare
  2          l_query long := 'select * from dual where 1 = :key and 2 =
:source';
  3          l_key   number default 1;
  4          l_source number default 2;
  5          l_val    varchar2(25);
  6  begin
  7          execute immediate l_query
  8            INTO l_val
  9            USING l_key, l_source ;
 10
 11          dbms_output.put_line( 'code for rows, val = ' || l_val );
 12  exception
 13          when no_data_found then
 14                  dbms_output.put_line( 'code for no rows' );
 15  end;
 16  /
code for rows, val = X

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> declare
  2          l_query long := 'select * from dual where 1 = :key and 2 =
:source';
  3          l_key   number default 1;
  4          l_source number default 1;
  5          l_val    varchar2(25);
  6  begin
  7          execute immediate l_query
  8            INTO l_val
  9            USING l_key, l_source ;
 10
 11          dbms_output.put_line( 'code for rows, val = ' || l_val );
 12  exception
 13          when no_data_found then
 14                  dbms_output.put_line( 'code for no rows' );
 15  end;
 16  /
code for no rows

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>

      Rating: 3
I missed some
      of my point  February 20, 2003
Reviewer:
      Ivan  from USA
In my example above the aname was just one of several fields that I needed to
update.  My question should have been more explicit in asking how I could code
one function to take the arguments and ordering of several fields not just
aname.  I do not want to create 12 of the same sort of functions that differ by
just field name and different order that I need the data sorted.

I like the decode method (much easier code then mine). So would I just send
three bind variables for the field name, in clause and the decode clause?

I have been wondering if you know of any programming courses that teach the
"Advanced best practices of programming"? My company allows me to buy books (I
have yours) but the programming classes seem to be to basic. Maybe you should
run a boot camp for programmers (just a thought).

Thanks as always,

Ivan

          Followup:

yes, you can do that.....

      Rating: 4
Bulk fetch and
      inserting for every rows fetch  March 04, 2003

Reviewer:  Vikas Sharma  from Delhi India
Hi Tom,

I have a requirement of selecting some rows from a table in insert and update
another table.

I could have  done insert by using following dynamically created string.

 INSERT /*+ APPEND */ INTO ACCOUNT_TYPES(ACCOUNT_TYPE,ATYP_NAME) SELECT
NAME,DATA_TYPE_ID FROM CONV_DATA_TYPES

This string is dynamically created I don’t know the number of columns also in
the string. So I have to use dbms_sql for executing this.

But I want to perform it procedurally with pl/sql by opening a cursor from the
source table ie : SELECT NAME,DATA_TYPE_ID FROM CONV_DATA_TYPES
And then dynamically execute
 INSERT  INTO ACCOUNT_TYPES(ACCOUNT_TYPE,ATYP_NAME)
VALUES (:bv,bv1);

as the columns are not known but the number of col will be always equal to
number of col in select.

This way if my insert statement fail for one row I can log the error into
another table like rowid , reason..for failing and then continuing the process
further rows. Because once I start this process of insert I want to complete it
for all the selected rows.

Same way update also like I don’t want to execute the

UPDATE ACCOUNT_TYPES SET (ACCOUNT_TYPE,ATYP_NAME) = (SELECT DATA_TYPE_ID,NAME
FROM CONV_DATA_TYPES,ACCOUNT_TYPES WHERE = DATA_TYPE_ID) WHERE EXISTS (SELECT
DATA_TYPE_ID,NAME FROM CONV_DATA_TYPES,ACCOUNT_TYPES WHERE = DATA_TYPE_ID)

I want to process every row and log the rowid into a log table with reason for
fail.

I have to use dbms_sql for both operations. As my select col list in not known.

I am giving you the psudo code

Declare
        l_theCursor     integer default dbms_sql.open_cursor;
        l_columnValue   varchar2(4000);
        l_status        integer;
        l_descTbl       dbms_sql.desc_tab;
        l_colCnt        number;
        l_array         dbms_sql.Varchar2_Table;
        v_values        varchar2(3000);
        cur  integer default dbms_sql.open_cursor;
BEGIN
     dbms_sql.parse(l_theCursor,dynamic_select_string,dbms_sql.native);
     dbms_sql.describe_columns (l_theCursor,l_colCnt,l_descTbl );
     for i in 1 .. l_colCnt loop
     dbms_sql.define_column(l_theCursor, i,l_columnValue, 4000 ); -- here I want
to fetch the rows in bulk say
        --(1000) at a time how to do it by using define_array?
       dbms_sql.define_array(l_theCursor, i,l_array,1000, 1);
       dbms_sql.column_value(l_theCursor, i,l_array );
     end loop;
l_status := dbms_sql.execute_and_fetch(l_theCursor);
--** now open the insert cursor for inserting into target table how to move
further? **** i am stuck here.

But I am doing like:

create or replace procedure prc_temp(p_insert_statment varchar2) IS
          l_theCursor     integer default dbms_sql.open_cursor;
          l_columnValue   varchar2(4000);
          l_status        integer;
          l_descTbl       dbms_sql.desc_tab;
          l_colCnt        number;
          l_array         dbms_sql.Varchar2_Table;
          v_values        varchar2(3000);
          l_cur          INTEGER;
          ins_cur  integer default dbms_sql.open_cursor;
          v_insert_statment VARCHAR(32000);
  BEGIN
       dbms_sql.parse(l_theCursor,'SELECt v1,n1 from temp_b',dbms_sql.native);
       dbms_sql.describe_columns (l_theCursor,l_colCnt,l_descTbl );
       for i in 1 .. l_colCnt loop
         dbms_sql.define_column(l_theCursor, i,l_columnValue, 4000 );
       end loop;
       l_status := dbms_sql.execute(l_theCursor);
       WHILE  ( dbms_sql.fetch_rows(l_theCursor) > 0 ) LOOP
          FOR i IN 1 .. l_colCnt LOOP
           dbms_sql.column_value(l_theCursor, i, l_columnValue );
           v_values := v_values ||l_columnValue||',';
          END LOOP;
           v_values :=  substr(v_values,0,instr(v_values,',',-1)-1);
           dbms_output.put_line(v_Values);
       -- Here i can open the insert cursor after creating a insert string like
          v_insert_statment:= p_insert_statment||'values('||v_values||')';
          v_insert_statment:= 'insert into temp_a values('||v_values||')';
          begin
            dbms_sql.parse(ins_cur,v_insert_statment,dbms_sql.native);
            l_cur  := dbms_sql.execute(ins_cur);
          EXCEPTION
              when others then
              -- insert into log table.....
             null;
          END;
           v_Values := null;
      end loop;
    dbms_output.put_line(v_insert_statment);
    DBMS_SQL.CLOSE_CURSOR(ins_cur);
     DBMS_SQL.CLOSE_CURSOR(l_theCursor);
END;

Assume the insert and the select string are dynamically created.
Pl suggest if there is any better way of doing it.

Regards,

Vikas Sharma

          Followup:

yuck.  wouldn't want to do it at all.

if you are moving any serious volume of data -- use array fetches and inserts.

      Rating: 2
yes but what
      about...  March 11, 2003
Reviewer:  Ivan
       from USA
Hi Tom,

Here is my problem:
I have loaders running against a database changing only certain fields.  In
order to use streams (thanks for the hint) I need to update only those fields
that have changes.  This means that my update statement will change per company
depending on if the data already exists in the table.

How do I do an dynamic SQL statement that changes the fields that need to be
updated and still pass in the values?
Here is an example of just two of the 14 fields that need to be used to
construct the statement and the using clause.

-- address_1 data
IF v_address_1_check THEN
  v_update_company_record := TRUE;
  IF RecExist.address_1 <> v_address_1 THEN
    v_sql_statement   :=  v_sql_statement || ' co.address_1   = :a
,co.address_1_lupd = :aa,co.address_1_upd = :aaa ';
   v_using_statement := v_using_statement || v_address_1   || ',' || SYSDATE ||
',' || USER || ',' ;
ELSE
          v_sql_statement   := v_sql_statement || '  co.address_1_lupd =:aa,
co.address_1_upd = :aaa ';
          v_using_statement := v_using_statement || ',' || SYSDATE || ',' ||
USER || ',' ;
  END IF;
END IF;

-- address_2 data
IF v_address_2_check THEN
  v_update_company_record := TRUE;
  IF RecExist.address_2 <> v_address_2 THEN
    v_sql_statement   :=  v_sql_statement || ' co.address_2 = :b
,co.address_2_lupd = :bb, co.address_2_upd = :bbb ';
          v_using_statement := v_using_statement || v_address_2 || ',' ||
SYSDATE || ',' || USER || ',' ;
ELSE
  v_sql_statement   := v_sql_statement || '  co.address_2_lupd =:bb,
co.address_2_upd = :bbb ';
          v_using_statement := v_using_statement || ',' || SYSDATE || ',' ||
USER || ',' ;
  END IF;
END IF;

Thanks as always,
Ivan

          Followup:

I don't understand the context here or why you would use dynamic sql vs static
sql....

      Rating: 1
How could I use
      static sql?  March 11, 2003
Reviewer:  Ivan
       from USA
How could I use static sql when I have to build a update statement for each
record and the update statement can change per record.

Example:
If the address_1 exists then I only need to update the date and user while if it
doesnt exist then I have to update the actual address_1 field, the date and the
user.  Times this by 14 fields and the update statement is going to be different
for each record. For just two of the 14 fields here would be the possible
statements:

-- if address_1 and address_2 are new
update table
 set address_1 = v_address_1,
     address_1_date = v_address_date,
     address_1_user = v_address_1_user,
     address_2 = v_address_2,
     address_2_date = v_address_2_date,
     address_2_user = v_address_2_user
 where rowid = 'ABC';

or it could be

-- if address_1 and address_2 already exist
update table
 set address_1_date = v_address_date,
     address_1_user = v_address_1_user,
     address_2_date = v_address_2_date,
     address_2_user = v_address_2_user
 where rowid = 'ABC';

or

-- address_1 is new and address_2 is new
update table
 set address_1 = v_address_1,
     address_1_date = v_address_date,
     address_1_user = v_address_1_user,
     address_2_date = v_address_2_date,
     address_2_user = v_address_2_user
 where rowid = 'ABC';

etc....

The end result will be one update statement for each record instead of 14
updates to one record.

Thanks,
Ivan

          Followup:

you will have to benchmark:

o the effect of a softparse (at least) per row updated (DYNAMIC) vs
o executing an update per column, for the columns you want to update (STATIC) vs
o a single update of all columns and let streams sort it out later (STATIC)

      Rating: 4
DBMS_SQL vs,
      EXECUTE IMMEDIATE in dynamic SQL  March 26, 2003

Reviewer:  Pallabi  from India
Very helpful. Just what i was looking for!!

But I am facing a dilemma. Query performance is my one and only criteria (never
mind the complexity of the writing of code). So how do I decide between using
DBMS_SQL or EXECUTE IMMEDIATE? I am doing an insert into...select. The query is
built dynamically. The data volume is quite large and indefinite. The result may
procedure anywhere from hundreds or thousands to millions of rows. So from a
purely performance point of view which would be a better option?

I am also in some places doing a bulk collect into index by tables followed by a
bulk insert/update. Is it wise to store so much data in the arrays given the
volume of data I described above? Should a LIMIT be placed on the bulk collect?
If yes, then how do we decide on the optimal limit to place on the collects?

Also as all my sql's are dynamically built (the table names and column names are
variable), while doing a bulk collect I have had to use static packages with
EXECUTE IMMEDIATE (workaround you described in another discussion - I am using
oracle 8.1.7). Will this incur any overhead? Or will it be better to use
DBMS_SQL in such cases. Again performance is the only issue and data volume is
high.

The application performance is giving me headaches. Please help!!!

          Followup:

execute immediate -- seems to me you execute the statement ONCE and then throw
it away.

dbms_sql is appropriate when you will execute the same dynamically generated
statement 10's or more times.

execute immediate is appropriate when you execute it once.

I would use limit, and limit myself to between 100 and 500 rows at a time.
Else, you'll be using quite a huge amount of ram and you'll find diminishing
marginal returns (I've even seen it go negative returns) on larger array sizes.

the execute immediate trick -- building a block of code that uses global package
variables -- works nicely.  If you have that going, no need to record using
dbms_sql.

      Rating: 5
improving
      native dynamic sql  April 18, 2003
Reviewer:
      Senthil
Tom,

I posted here because my question seems closer to op's question.

we have an performance issue running this query...

declare
   l_inserted number;
   l_ins_stmt varchar2(4000);
   l_sql_stmt varchar2(4000);
   l_date_string varchar2(10) := null;
begin
         l_ins_stmt := '
             INSERT /*+ APPEND */ INTO d14248_42_y1
                SELECT * FROM stg_42_29 stg
                   WHERE NOT EXISTS (SELECT 1
                      FROM d14248_42_y1 prd
                      WHERE prd.Z_DOC_ID = stg.Z_DOC_ID)
                   AND rownum <= 5120
         ;';

         l_sql_stmt := '
            declare
               l_counter   number := 1;
               l_row_count number := 0;
            begin
               while (l_counter > 0)
               loop
                  ' || l_ins_stmt || '
                  l_counter := sql%rowcount;
                  l_row_count := l_row_count + l_counter;
                  commit;
               end loop;
               :row_processed := l_row_count;
            end;
         ';
         EXECUTE IMMEDIATE l_sql_stmt using out l_inserted;
   dbms_output.put_line(l_inserted);
end;
/

To give you a little bit information, using 8.1.7. Staging and production table
has index on Z_DOC_ID columns.

it takes approximately 6 minutes to load 15000 rows to move data from staging
table to production. when i run the same insert statement alone, it takes only
2-3 seconds. That seems to be a huge difference. Is there a way to tune this
query or any workaround would be appreciated.

thanks,
senthil

          Followup:

tell me, why would you even consider obscure, hard to understand, hard to code,
hard to maintain code -- over a simple, single, insert into?

my mantra:

if you can do it in a single sql statement -- do it (dont even CONSIDER anything
else)

      Rating:
      5
agreed...but...  April 18, 2003
Reviewer:
      Senthil
you got me there Tom, i should've mentioned that in the first place. This
begin..end block is an excerpt from one of our stored procedures, got generated
on the fly(dont ask me why we even do that). we know the staging and production
table name and the structure when we call this procedure. so, we are forced to
use 'execute immediate', unless we have missed anything. fortunately we found
this in our regression test.

thanks,
senthil

          Followup:

but what?  you are in test, test is when you fix bugs, this is a bug.

Think about it -- look at the logic.  Every time you run the procedural block,
it HAS to process the rows it did last time and the time before and the time
before and the time before (so on...).  Not only that, but it runs a single row
subquery for each and every row.

Everytime you run the insert -- it processes ALL prior data AND runs a subquery
for each row.  it gets slower and slower and slower (and so on)

      Rating:
      5
Perfect!!  August 12, 2003
Reviewer:
      Jim  from MA
Tom, you are a machine!

      Rating:
      4
Help!  September 02, 2003
Reviewer:
      SD  from UK
My requirement ins like below:

FOR DAY IN (sun, mon, tue, wed, thu, fri, sat )
  RUN THE FOLLOWING DYNAMIC SQL (replacing DAY WITH sun, mon, tue, etc)  TO
populate THE activities TABLE.

        INSERT INTO activities (ein, data_source, tsdate,activity, HOURS)
      SELECT SUBSTR(trim(raw_ein),-9,9), data_source,
       TO_DATE(SUBSTR(day_date,-8,8),'dd/mm/yy') tsdate,
       day_activity activity,
       SUM(MOD(SUBSTR(day_end,1,2)-SUBSTR(day_start,1,2)+24,24)*3600
       +(SUBSTR(day_end,4,2)-SUBSTR(day_start,4,2))*60) HOURS
   FROM  DATA_A
   GROUP BY raw_ein, data_source, day_date, day_activity

issue a COMMIT

Now this insert will take plaace only once for a day i.e. once for sun then once
for sat..etc..
I'm thinking of using Execute Immediate for this, is it Ok?

          Followup:

why do you believe you need dynamic sql when all it appears you need is static
sql with a bind variable????

      Rating: 4
Above
      Query!!  September 02, 2003
Reviewer:  SD
       from UK
I've tried this...Please give me your comments..
----------------------------------------------------
DECLARE
TYPE day_table IS TABLE OF VARCHAR2(3) INDEX BY BINARY_INTEGER;
days day_table;
x VARCHAR2(3);
stmt VARCHAR2(4000);
BEGIN
        FOR i IN 1..7
        LOOP
         days(i):=TO_CHAR(SYSDATE-i,'dy');
        END LOOP;
    FOR i IN 1..DAYs.COUNT
    LOOP
    stmt:= 'INSERT INTO activities (ein, data_source, tsdate,activity, HOURS)
            SELECT SUBSTR(trim(raw_ein),-9,9), NVL(data_source,''NULL''),
TO_DATE(SUBSTR('||days(i)||'_date,-8,8),''dd/mm/yy'') tsdate,
'||days(i)||'_activity activity,

SUM(MOD(SUBSTR('||days(i)||'_end,1,2)-SUBSTR('||days(i)||'_start,1,2)+24,24)*3600
 +(SUBSTR('||days(i)||'_end,4,2)-SUBSTR('||days(i)||'_start,4,2))*60) HOURS
            FROM data_A p  WHERE '||days(i)||'_start IS NOT NULL AND
'||days(i)||'_end IS NOT NULL GROUP BY raw_ein, data_source, '||days(i)||'_date,
'||days(i)||'_activity';
    EXECUTE IMMEDIATE stmt ;
    END LOOP;
END;

          Followup:

there are only 7 statements.  use static sql.

faster
more efficient
more managable
more readable
easier to debug
all around better.

      Rating: 5
P4 -
      above  September 08, 2003
Reviewer:  Bala
       from Kuala Lumpur, Malaysia
"P4 -- so easy to code, uses bind variables -- pretty efficient.
Unfortunately, internally its a little like P1. The open, parse, close calls are
"

Why does it parse every time, if the variables are bound and the statement
doesnt change(like the insert in your example) then the execution plan is
already available, can't the engine do the trivial check (shared pool)

          Followup:

it does a "trivial check" -- called a soft parse.

it is still a parse however, a parse is a parse is a parse.  the more you parse,
the worse off you are.

      Rating: 4
Please help
        September 25, 2003
Reviewer:  John
       from new york
Hi tom,

I am trying to implement the following using your p5. Any ideas I have tried
many times. Please help.

FUNCTION ADD_NEW_SAMPLE_COMPONENT(P_COMPONENT IN TP_COMPONENT_ARR,
                                    P_NUMBER_OF_PREPS IN VARCHAR2)
RETURN VARCHAR2
IS
V_COUNTER VARCHAR2(10);
STEP_NA     CONSTANT BOOLEAN  DEFAULT  P_COMPONENT(1).na_option='Y';

       COMPONENT_fk_id                 varchar_arr:=varchar_arr();
       id                              varchar_arr:=varchar_arr();
       date_created                    varchar_arr:=varchar_arr();
       created_by                      varchar_arr:=varchar_arr();
       record_status                   varchar_arr:=varchar_arr();
       comments                        varchar_arr:=varchar_arr();
       template_prep_fk_id             varchar_arr:=varchar_arr();
       template_prep_process_fk_id     varchar_arr:=varchar_arr();
       counter                         varchar_arr:=varchar_arr();
       COMPONENT_type                  varchar_arr:=varchar_arr();
       EXPIRATION_DATE                 varchar_arr:=varchar_arr();
       na_option                       varchar_arr:=varchar_arr();
       ----------------------------------------------------
       template_step_#                 varchar_arr:=varchar_arr();

BEGIN
  ------set up new -----------------------------------
  TP_PROCESS_REC.id:=TEMPLATE_PREP_PROCESS_PKG.GET_NEXT_PROCESS;
  TP_PROCESS_REC.date_created:=SYSDATE;
  TP_PROCESS_REC.created_by:=USER;
  TP_PROCESS_REC.record_status:='CURRENT';
  TP_PROCESS_REC.comments:=P_COMPONENT(1).COMMENTS;
  TP_PROCESS_REC.reason_for_change:=P_COMPONENT(1).reason;
  TP_PROCESS_REC.updated:='N';
  TP_PROCESS_REC.template_prep_fk_id:=P_COMPONENT(1).TEMPLATE_PREP_FK_ID;
  TP_PROCESS_REC.template_step_#:=P_COMPONENT(1).template_step_#;

  TP_PROCESS_REC.progress_flag:='
  <font color="#006600" SIZE="1"><i>Entered by '||V_USER||' on
'||TO_CHAR(SYSDATE,'MM/DD/YY HH:MI A.M.')||'</i></font>
  <BR> You have generated the following ID''S';

  INSERT INTO TEMPLATE_PREP_PROCESS
  VALUES TP_PROCESS_REC;
--------GATHER EXISTING
INFORAMTION-----------------------------------------------------------

  SELECT ID INTO TP_PROCESS_REC.ID
  FROM TEMPLATE_PREP_PROCESS TP1 WHERE DATE_CREATED=(
  SELECT MAX(DATE_CREATED) FROM TEMPLATE_PREP_PROCESS TP
  WHERE TEMPLATE_PREP_FK_ID=TP_PROCESS_REC.template_prep_fk_id
  AND TEMPLATE_STEP_#=TP_PROCESS_REC.template_step_#
  AND RECORD_STATUS='CURRENT');

  SELECT PROGRESS_FLAG INTO TP_PROCESS_REC.PROGRESS_FLAG
  FROM TEMPLATE_PREP_PROCESS
  WHERE ID=TP_PROCESS_REC.ID;

 FOR i in 1..P_COMPONENT.COUNT LOOP
   FOR a in 1..P_NUMBER_OF_PREPS loop
       COMPONENT_FK_ID.EXTEND;
       id.EXTEND;
       date_created.EXTEND;
       created_by.EXTEND;
       record_status.EXTEND;
       comments.EXTEND;
       template_prep_fk_id.EXTEND;
       template_prep_process_fk_id.EXTEND;
       counter.EXTEND;
       COMPONENT_type.EXTEND;
       EXPIRATION_DATE.EXTEND;
       na_option.EXTEND;
       --------------------------------------------
       template_step_#.EXTEND;

COMPONENT_fk_id(a):=TP_COMPONENTS_PKG.GENERATE_COMPONENT_ID(P_COMPONENT(i),V_COUN
TER);

       IF a=1  then
         counter(a):=V_COUNTER;
       else
         if v_counter=1 then
           counter(a):=a;
         else
           counter(a):=V_COUNTER+a-1;
         end if;
       end if;

       COMPONENT_fk_id(a):=COMPONENT_fk_id(a)||'_'||counter(a);

TP_PROCESS_REC.progress_flag:=TP_PROCESS_REC.progress_flag||'<BR>'||COMPONENT_fk_
id(a);

       id(a):=P_COMPONENT(i).id;
       date_created(a):=SYSDATE;
       created_by(a):=USER;
       record_status(a):='CURRENT';
       comments(a):=P_COMPONENT(1).COMMENTS;
       template_prep_fk_id(a):=P_COMPONENT(1).TEMPLATE_PREP_FK_ID;
       template_prep_process_fk_id(a):=TP_PROCESS_REC.id;

       COMPONENT_type(a):=P_COMPONENT(1).COMPONENT_type;
       EXPIRATION_DATE(a):=P_COMPONENT(1).expiration_date;
       na_option(a):=P_COMPONENT(1).na_option;
    -----KEEP TRACK OF COMPONENTS
PROGRESS-------------------------------------------------------------------------
-----
       date_created(a):=SYSDATE;
       created_by(a):=USER;
       record_status(a):='CURRENT';
       template_prep_fk_id(a):=TP_COMPONENT_REC.template_prep_fk_id;
       template_step_#(a):=TP_PROCESS_REC.template_step_#;
       --component_fk_id(a):=TP_COMPONENT_REC.COMPONENT_fk_id;

    end loop;
  END LOOP;

begin
        forall i in 1 .. COMPONENT_FK_ID.COUNT
        INSERT INTO TP_COMPONENTS         (  COMPONENT_fk_id  ,
                                             id               ,
                                             date_created     ,
                                             created_by           ,
                                             record_status     ,
                                             comments          ,
                                             template_prep_fk_id     ,
                                             template_prep_process_fk_id ,
                                             counter           ,
                                             COMPONENT_type     ,
                                             EXPIRATION_DATE   ,
                                             NA_OPTION )
        VALUES
                                          (  COMPONENT_fk_id(i)  ,
                                             id(i)              ,
                                             date_created(i)     ,
                                             created_by(i)           ,
                                             record_status(i)     ,
                                             P_COMPONENT(1).comments ,
                                             P_COMPONENT(1).TEMPLATE_PREP_FK_ID,
                                             template_prep_process_fk_id(i) ,
                                             counter(i)           ,
                                             COMPONENT_type(i)     ,
                                             EXPIRATION_DATE(i)  ,
                                             NA_OPTION(i)  );
  end;

/*        execute immediate
       'begin
        forall i in 1 .. :N
        INSERT INTO TEMP_PROCESS_COMPS       (template_step_# ,
                                              date_created   ,
                                              created_by     ,
                                              record_status      ,
                                              template_prep_fk_id    ,
                                              component_fk_id      )
        VALUES                                (template_step_#(i) ,

                                              date_created(i)   ,
                                              created_by(i)     ,
                                              record_status(i)      ,
                                              template_prep_fk_id(i)    ,
                                              component_fk_id(i) );
       end;'
       USING P_COMPONENT.COUNT;*/

 --write expiration ---------------------------------------
    TP_PROCESS_REC.progress_flag:=TP_PROCESS_REC.progress_flag||'<BR>Expiration
Date :'||p_component(1).expiration_date;
    --------------------------------------------------------------

    UPDATE TEMPLATE_PREP_PROCESS
    SET progress_flag=TP_PROCESS_REC.progress_flag
    WHERE ID=TP_PROCESS_REC.ID;

 --COMMIT;
 RETURN '1';

END ADD_NEW_SAMPLE_COMPONENT ;

          Followup:

hmm

a ton of code

that i cannot run (big, yet incomplete - SMALL, yet complete is what we are
looking for in test cases)

without any indication as to the issue you are encountering other then "it
doesn't work"

      Rating: 5
plsql IF/ELSE
      Vs, sql decode  November 13, 2003
Reviewer:
      LB  from NJ
Tom,

Is there any difference between using the following two formats other than the
second one using less lines of code?

V_DOW  VARCHAR2(3) := TO_CHAR(SYSDATE,'DY');

1)    IF V_DOW = 'SAT' THEN
        V_DATE_D := SYSDATE - 1;
      ELSIF V_DOW = 'SUN' THEN
        V_DATE_D := SYSDATE - 2;
      ELSIF V_DOW = 'MON' THEN
        V_DATE_D := SYSDATE - 3;
      ELSIF V_DOW = 'TUE' THEN
        V_DATE_D := SYSDATE - 4;
      ELSIF V_DOW = 'WED' THEN
        V_DATE_D := SYSDATE - 5;
      ELSIF V_DOW = 'THU' THEN
        V_DATE_D := SYSDATE - 6;
      ELSE
        V_DATE_D := SYSDATE - 7;
      END IF ;

2) SELECT DECODE(V_DOW, 'SAT', sysdate-1,
                        'SUN', sysdate-2,
                        'MON', sysdate-3,
                        'TUE', sysdate-4,
                        'WED', sysdate-5,
                        'THU', sysdate-6,
                        'FRI', sysdate-7)
     INTO V_DATE_D
     FROM DUAL;

      Rating: 4
Binding Table
      names  November 20, 2003
Reviewer:  Shahrouz
      DARVISHZADEH  from Austria
Hi
What happens if I use a variable for table name? Following example:
EXECUTE IMMEDIATE 'insert into :t values(1,2,3)' using table_name;
As I know it dosen't work, as I can only bind "variables". But what happens
really if I use a variable for table name.
EXECUTE IMMEDIATE 'insert into '||table_name||' values(1,2,3)';
I afraid it will be hardparsed everytime. Is it true?

Shahrouz DARVISHZADEH

          Followup:

you cannot bind an identifier.

if table_name changes EVERY time, it'll be hard parsed every time.

if table_name is the same two times, then one will be hard and one will be soft.

      Rating:
      5
Thanks  November 24, 2003
Reviewer:
      A reader

November 21, 2003 Nilam -- Thanks for the question regarding
      "Problem related to Import of data", version 8.1.7

      You Asked
I am getting following error while importing tables.

. . importing table         "EGM_FILELEVEL_COUNTS"
IMP-00019: row rejected due to ORACLE error 2291
IMP-00003: ORACLE error 2291 encountered
ORA-02291: integrity constraint (NBTEST1.FK_EGM_FILECOUNT_FILE_ID) violated -
parent key not found
Column 1 2
Column 2 1
Column 3 1
Column 4 fcnew2
Column 5 1
Column 6 3
Column 7 1.1

But I checked the parent table after import and it has the values in its columns
which were refered by table EGM_FILELEVEL_COUNTS as foreign key.

The possible reason looks to me from the log file is that it is trying to import
the rows in  table EGM_FILELEVEL_COUNTS before it imports in table EGM_FILE_INFO
and the primary key of EGM_FILE_INFO is foreign key in
EGM_FILELEVEL_COUNTS(FK_EGM_FILECOUNT_FILE_ID).

Please, let me know what could be the actual problem and how can I resolve that.

      and we said...
Did the tables already exist with constraints?  If so, you'll need to drop the
fkey constraint OR do a table by table import.

Normally, import will import all of the data and THEN add the constraints.  What
happened to you can only happen when importing into existing structures and then
the only ways to "solve" it are

a) disable the constraint whilst importing
b) import the tables one by one manually.
Review &
      Followup

      Rating:
      5
Error  November 20, 2003
Reviewer:
      atul  from India
Hi,

I'm getting following error while running a batch program

"_l2: Non Fatal errorORA-02291: integrity constraint (ECE_CUP_FK)
violated - parent key not found><121><0>
MMS0010B.pc(4458): load_other_messages: Number of AUdit master is <4> and Detail
<8>"

How can i find out the rows in two tables having primary key
and foreign key relationship which give such problem

Thanks.
Atul

          Followup:

you won't find any rows -- the error message means "we rejected the row"

with primary/foreign keys in place, you won't FIND any rows in violation (if you
do, it is "a bug")

November 21, 2003 Rupali -- Thanks for the question regarding
      "Instead of Triggers - where to use?", version 8.1.5

      You Asked
Dear Mr. Tom,

1)    First of all when I tried creating an INSTEAD OF Trigger
    it gave me

    ORA-00439: feature not enabled:  error

    Kindly explain why so....?

2)    Where exactly would u advise me to use Instead Of trigger
    Kindly give an example.

Rupali

      and we said...
In Oracle8.1.5 and before-- instead of triggers were included in
the Enterprise Edition, and not in the Standard Edition (see

http://technet.oracle.com/doc/server.815/a68020/ch4.htm#58509
for a matrix of what features/functions are available in 8.1.5
SE vs EE).

In 8.1.6, instead of triggers are available for all releases.

See

http://asktom.oracle.com/pls/ask/f?p=4950:7:::::F4950_P7_SEARCH_CRITERIA:create+replace+trigger+instead+of
for some example uses.  Anytime you want to insert into a
complex view (or even a simple one at times) and want to supply
the correct logic for doing so -- you will find instead of
triggers useful.

Review &
      Followup

      Rating: 3
yeah fine
      but...  December 30, 2002
Reviewer:  Sekhar
       from India
if it is an " instead of update " trigger then when would this fire is it before
update or after update ..?

          Followup:

it is INSTEAD OF, neither before nor after -- but INSTEAD OF....

The update isn't happening -- your code is running INSTEAD OF the update.

      Rating: 5
Why the trigger
      disappears...  May 28, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Hi Tom,
After you re-create the view definition using CREATE OR REPLACE (maybe to change
its condition), the trigger on the view disappears. Is this expected behaviour?

SQL> create or replace view emp_view
  2  as
  3  select * from emp
  4
SQL> /

View created.

SQL> create or replace trigger trig_emp_view
  2  instead of insert or update on emp_view
  3  for each row
  4  begin
  5    Null ;
  6  end ;
  7  /

Trigger created.

SQL> show errors
No errors.
SQL> select ut.trigger_name, ut.table_owner, ut.table_name
  2  from user_triggers ut where trigger_name = 'TRIG_EMP_VIEW'
  3  /

TRIGGER_NAME                   TABLE_OWNER                    TABLE_NAME
------------------------------ ------------------------------
------------------------------
TRIG_EMP_VIEW                  KKISHORE                       EMP_VIEW

1 row selected.

SQL> create or replace view emp_view
  2  as
  3  select * from emp
  4  /

View created.

SQL> select ut.trigger_name, ut.table_owner, ut.table_name
  2  from user_triggers ut where trigger_name = 'TRIG_EMP_VIEW'
  3  /

no rows selected

SQL>
SQL>

          Followup:

the "or replace" is replacing the view and all related things.  the create or
replace preserves grants -- not the triggers.  it is a "new view"

      Rating: 4
Instead of
      trigger and returning clause doesn't work  October 26, 2003

Reviewer:  Piotr Jarmuz  from Poznan, Poland

The "returning into clause" in an insert statement on views which have instead
of trigger doesn't work.

I get ORA-22816: unsupported feature with RETURNING clause

I want to provide a "pure SQL" interface to complex stored procedures via
instead of triggers and I want to save on one network roundtrip by getting
surogate primary key in returning clause. My stored procedure creates an object,
maintains business rules, and returns object's surogate PK.

Is there any workaround to do that?

Thank you in advance.

          Followup:

why?  stored procedures are not evil.

levels and layers of obscurity can be.

"pure sql" is usually raised up for database independence but since that cannot
be the goal here (you are doing too many things only Oracle can do) so thats not
the reason.

why "pure sql" in this case?  what is the benefit you percieve from that?

      Rating: 5
You are
      right  October 26, 2003
Reviewer:  Piotr
      Jarmuz  from Poznan, Poland
I wanted this "pure sql" because there could be some ODBC programs that might
need partial access to my system. And I am not sure that they can understand
procedure call (never worked with ODBC). But this is not so crucial. It is
supposed to be just-in-case-feature.
  Stored procedures are the best. Without them polimorphism the way I
implemented would not work, anyway. Only fraction of functionality would be
avaialble for classes known at compile time.

Thank you very much for sharing your thoughts.

Regards,
Piotr

          Followup:

ODBC can do stored procedure calls, definitely

      Rating: 5
Thank you very
      much  October 26, 2003
Reviewer:  Piotr
      Jarmuz  from Poznan, Poland

      Rating:
      4
trigger  November 20, 2003
Reviewer:
      A reader
Hi

Let's say I have table A, B, C, I would like to write a trigger on A so when a
row is inserted if that row's PK exists in B instead of inserting to A it
inserts to C. Is this possible ?

Tried instead of trigger but it says only works on VIEWS!

          Followup:

correct, it only works on views.

it would be (in my opinion) a really utterly truly horrible idea to do what you
ask for. "magical side effects happening by accident".

I would use a stored procedure that does your processing -- don't try to get
magically fancy with a trigger like this.

(i could show you how to do it, but I feel it is so utterly bad in its concept
that I'm not going to)....

November 21, 2003 San -- Thanks for the question regarding "Oracle
      Backup", version 8.1.5

      You Asked
Hi Tom,

I have some questions here:

1. How big can be archive log file, is there any parameter in
init.ora for maximum archive file or Oracle has to decide,how
big it's going to be? Also how many archive files are going to
be there for a specific backup. The number of archive files,
(going to be create in a specific backup) depending on what?

2. In Oracle documentaion I found, While recovery "We may need
to apply arcive log, redo log or the combination of both". My
question is: Oracle itself is recomandad don't take the backup
of redo log while your database is running in archive mode. So I
think we should not take the backup of redo log even though if
we are taking backup for redo log also in archive mode when we
have to apply:
a. Archive log only
b. Redo logs only
c. Combination of both

3. I am not taking any export, just taking hot backup and I have
to recover a table from hot backup. How I am going to find out
which specific archive log I need to apply.

4. What is the use of archive_log_dest_duplex parameter? Weather
it's going to create duplicate archive log in different location
OR after finishing space in archive_log_dest Oracle going to
write archive log archive_log_dest_duplex in this location.

5. I know there is a kind of size limitation for data file size
like in Unix it's better to keep datafile size less then 2 GB. I
am wondering do we have something like that size constraints in
NT also. If yes how big data file we can have in NT environment.

Thanks
San

      and we said...
1) the archive log file will be typically as big (or smaller)
then your ONLINE redo log files.  The size of them dictate the
maximum size of your archive files.

Archive files are not associated with a backup.  You backup the
database and save any archives generated during and after the
backup.  If you do not backup for a month, you need a month of
archives.  If you backup every hour, you need an hours worth.
It is a function of the period of your backups and the amount of
redo you generate in that time.  It is not a function of your
backup.

2) you should never backup ONLINE redo log.

during INSTANCE (crash) recovery, we will require ONLINE redo
logs only.  If you power the machine off and on, we will perform
crash recovery and will require ONLY online redo logs.

during MEDIA recovery (when you restore a single or multiple
files from a backup) we will require all archived redo logs
since the time of the backup of that file PLUS all unarchived
online redo log files in order to "catch" that file up.

Never backup the online redo log files -- the only thing that
could happen if you do that is you might accidently OVERWRITE
your REAL online redo log files during a restore, losing the
ability to recover the data they held.  Only backup ARCHIVES.

3) the system will tell you.  It knows the log sequence/thread
that was current when the backup started.  It knows the current
log sequence/thread.  It knows the naming convention.  It'll
tell you what files it needs.  You just need to supply them

Tools such as RMAN will not only remember what files you need
but it'll remember where they are and do it for you.

4) it'll write to both locations.  It is a mirror function.

5) you can have large files in Unix and NT.  The limits are OS
dependent.  You should refer to the OS admin/install guide for
your platform or contact support.  Here is the limit for NT with
8.1.6 for example:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:500021777650
...

Review &
      Followup

      Rating:
      5
  May 07, 2001
Reviewer:
      A reader

      Rating: 3
But what about
      transactions that were just committed?  November 20, 2003

Reviewer:  Jamie  from Reston, VA
Tom - Thanks for the explanation, but I have a question.

We have been practicing our backup strategy in the test environment, and have
run into a snag.  We are running in ARCHIVELOG mode, and are taking hot backups.

We copy our hot backup files to the directory where we will restore, as well as
the archived redo logs.  We do not backup the online redo logs.

1. We first take a hot backup.
2. We then perform an insert of maybe 100000 rows into a table, commit, and then
to simulate the db going down do a shutdown abort.
3. We then copy over all of the archived redo logs and hot backup files, and
begin media recovery.
4. We:
  startup mount
  recover database using backup controlfile until cancel
  auto
5. It will restore the database but the LAST comitted transaction does not
restore!

I assume this is because the archive log has not been written to disk.

Unfortunately, we are having trouble figuring this out.

What are we missing?

(BTW, I love your books!)

Thanks - Jamie

          Followup:

redo logs are to be multi-plexed. you have more then one copy.  in the event of
a disk failure -- you have more then one copy.  You use one of the other copies
to restore with in the event the redo takes a hit.

What you seem to be looking for is a disaster recovery -- "hey, i've lost all
disks"

that is a standby database -- using data guard in 9i.  See the data guard docs
on OTN for details.  you can make it so that the redo is safely stored on TWO
geographically separate computers.

November 21, 2003 Mahomed -- Thanks for the question regarding
      "Running os command/program from java", version 8.1.7.0.0

      You Asked
Hi Tom,

I am trying to run a c program that outputs semaphore values using java stored
procedure on Unix OSF V4.0 alpha.

I have the following java stored procedure :

CREATE OR REPLACE and COMPILE JAVA SOURCE
NAMED "MyCommand"
AS
import java.io.*;
import java.lang.*;

public class MyCommand extends Object
{

  public static int RunThis(String[] args)
  {
    Runtime rt = Runtime.getRuntime();
    int        rc = -1;

    try
    {
       Process p = rt.exec(args[0]);

       int bufSize = 4096;
       BufferedInputStream bis =
        new BufferedInputStream(p.getInputStream(), bufSize);
       int len;
       byte buffer[] = new byte[bufSize];

       // Echo back what the program spit out
       while ((len = bis.read(buffer, 0, bufSize)) != -1)
          System.out.write(buffer, 0, len);

       rc = p.waitFor();
    }
    catch (Exception e)
    {
      e.printStackTrace();
      rc = -1;
    }
    finally
    {
      return rc;
    }
  }
}
/

create or replace function RUN_CMD( p_cmd  in varchar2)
return number
AS LANGUAGE JAVA
NAME  'MyCommand.RunThis(java.lang.String[]) return integer';
/

create or replace procedure CMD(p_cmd in varchar2)
as
  x number;
begin
  x := RUN_CMD(p_cmd);
end;
/

I have granted the following permissions :

 begin
   dbms_java.grant_permission
      ('MIS',
      'java.io.FilePermission',
      '/usr2/vfk_dev/exe/*',
      'execute');

   dbms_java.grant_permission
      ('MIS',
      'java.io.FilePermission',
      '/tmp',
      'write');

   dbms_java.grant_permission
      ('MIS',
       'java.lang.RuntimePermission',
       '*',
       'writeFileDescriptor' );
 end;
/

When I run the following command, I do not see the output file tstsem.out in dir
/tmp
select RUN_CMD('/usr2/vfk_dev/exe/tstsem | head > /tmp/tstsem.out ') from dual;

RUN_CMD('/USR2/VFK_DEV/EXE/TSTSEM|HEAD>/TMP/TSTSEM.OUT')
--------------------------------------------------------
                                                       0

SQL> !ls /tmp
OraInstall       plnlay12545.log  tmp30410.sql
plnlay12545.bad  tmp25804.sql     tmp31246.sql

SQL> !/usr2/vfk_dev/exe/tstsem | head
tstsem: Semaphore values are :
    sem 00 = 1 pid = 25700 ncnt = 0 zcnt = 0
    sem 01 = 1 pid = 25700 ncnt = 0 zcnt = 0
    sem 02 = 1 pid = 25700 ncnt = 0 zcnt = 0
    sem 03 = 1 pid = 18450 ncnt = 0 zcnt = 0
    sem 04 = 1 pid = 25700 ncnt = 0 zcnt = 0
    sem 05 = 1 pid = 25700 ncnt = 0 zcnt = 0
    sem 06 = 1 pid = 6639 ncnt = 0 zcnt = 0
    sem 07 = 1 pid = 25700 ncnt = 0 zcnt = 0
    sem 08 = 1 pid = 25700 ncnt = 0 zcnt = 0

What is the problem?  Can you help?

Thanks
Mahomed

      and we said...
Quite simply because you are confusing "rt.exec" with the UNIX shell!

>, !, <, etc -- they are all things that mean something to /usr/bin/sh or
/usr/bin/csh and so on.  They mean *nothing* normally.  Unless you have a
command shell in place to interpret them -- all you have it a garbage command.

You could run the commands via the shell -- but that would be a terribly BAD
idea (you would have to grant permissions to run /usr/bin/sh -- that would mean
you could run ANY COMMAND on the machine).

Suggest you set up a shell script like this:

$ cat test.sh
#!/bin/sh

/usr/bin/ipcs -a | /usr/ucb/head > /tmp/ipcs.out

$

and then use these perms and command to run it:

test@ORA817DEV.US.ORACLE.COM> begin
  2     dbms_java.grant_permission
  3        ('TEST',
  4        'java.io.FilePermission',
  5        '/export/home/ora817/bin/test.sh',
  6        'execute');
  7
  8     dbms_java.grant_permission
  9        ('TEST',
 10         'java.lang.RuntimePermission',
 11         '*',
 12         'writeFileDescriptor' );
 13   end;
 14  /

PL/SQL procedure successfully completed.

test@ORA817DEV.US.ORACLE.COM>
test@ORA817DEV.US.ORACLE.COM> set serveroutput on size 1000000
test@ORA817DEV.US.ORACLE.COM> exec dbms_java.set_output( 1000000 );

PL/SQL procedure successfully completed.

test@ORA817DEV.US.ORACLE.COM> !ls -l /tmp/ipcs.out
/tmp/ipcs.out not found

test@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( run_cmd(
'/export/home/ora817/bin/test.sh' ) );
Normal return, rc = 0
0

PL/SQL procedure successfully completed.

test@ORA817DEV.US.ORACLE.COM> !ls -l /tmp/ipcs.out
-rw-r--r--   1 ora817       1020 Mar 15 09:32 /tmp/ipcs.out

Review &
      Followup

      Rating:
      5
Mahomed  March 15, 2002
Reviewer:
      A reader
Yep!  Tom you have solved the problem, Thanks very much !

      Rating: 4
running on
      Windows NT  April 04, 2002
Reviewer:  Chris
      Soza  from England
Tom,

Is it possible to use a similar java procedure to copy files using UNC paths ?.
If so can you give an example. I used the same sp as in this example and
although it allows to copy files from the same server it doesn't seem to be able
to use UNC paths.

Rgds, Chris

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:241814624807
for what the issue is and think 13 times before doing this.  On NT -- network
drives are done very differently then on Unix.  It's not as easy as a mount.

      Rating: 5
Problem with
      java.lang.ArrayIndexOutOfBoundsException  April 10, 2002

Reviewer:  Zhuang Li  from Liverpool, UK
This example will be very useful for us for running a OS command with in a plsql
program.

I followed the example and created all the java stored procedure and functions
successfully. Also I have given the permissions as advised in the note. But on
the last step I got error:

=====================================================
legd> !cat /tmp/zl.sh
#!/bin/ksh
/usr/bin/ipcs -a | /usr/ucb/head >/tmp/ipcs.out

legd> !ls -l /tmp/ipcs.out
/tmp/ipcs.out: No such file or directory

legd> exec dbms_output.put_line(run_cmd('/tmp/zl.sh'));
java.lang.ArrayIndexOutOfBoundsException
at MyCommand.RunThis(MyCommand:13)
-1
=====================================================
My MyCommand java stored procedure is exactly same as the example as below:
--------------------------------------------------
CREATE OR REPLACE and COMPILE JAVA SOURCE
NAMED "MyCommand"
AS
import java.io.*;
import java.lang.*;

public class MyCommand extends Object
{

  public static int RunThis(String[] args)
  {
    Runtime rt = Runtime.getRuntime();
    int        rc = -1;

    try
    {
       Process p = rt.exec(args[0]);

       int bufSize = 4096;
       BufferedInputStream bis =
        new BufferedInputStream(p.getInputStream(), bufSize);
       int len;
       byte buffer[] = new byte[bufSize];

       // Echo back what the program spit out
       while ((len = bis.read(buffer, 0, bufSize)) != -1)
          System.out.write(buffer, 0, len);
       rc = p.waitFor();
    }
    catch (Exception e)
    {
      e.printStackTrace();
      rc = -1;
    }
    finally
    {
      return rc;
    }
  }
}
/
============================================
The line 13 is ==>     int        rc = -1;

Please advise.

Thank you

Zhuang

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:952229840241
and look for the word bounds on that page.

      Rating:
      5
  April 10, 2002
Reviewer:  Mahomed
       from U.K
Followup to previous question.

How would you pass parameters to the shell script.

          Followup:

just add them after the script name, as normal.

if you look at the page I referenced above (fixed the array bounds issue) -- I
do an ls on a specific directly -- passing parameters to ls

      Rating:
      3
  December 17, 2002
Reviewer:
      A reader
Hi Tom,
I am having problem with executing OS command.

gaurang@oracle9>EXEC RC ('exp userid=gaurang/gaurang tables=external_table
file=E:\ADMIN');
java.security.AccessControlException: the Permission (java.io.FilePermission
<<ALL FILES>> execute)
' )
at java.security.AccessControlContext.checkPermission(AccessControlContext.java)
at java.security.AccessController.checkPermission(AccessController.java)
at java.lang.SecurityManager.checkPermission(SecurityManager.java)
at
oracle.aurora.rdbms.SecurityManagerImpl.checkPermission(SecurityManagerImpl.java)

at java.lang.SecurityManager.checkExec(SecurityManager.java)
at java.lang.Runtime.exec(Runtime.java)
at java.lang.Runtime.exec(Runtime.java)
at java.lang.Runtime.exec(Runtime.java)
at java.lang.Runtime.exec(Runtime.java)
at Util.RunThis(Util.java:11)

PL/SQL procedure successfully completed.

I gave user GAURANG all required permission on server.

EXEC
DBMS_JAVA.GRANT_PERMISSION(USER,'java.io.FilePermission','E:\ADMIN','read,write')
;

EXEC
DBMS_JAVA.GRANT_PERMISSION(USER,'java.io.FilePermission','E:\ADMIN','read,write')
;

EXEC  dbms_java.grant_permission(USER, 'java.lang.RuntimePermission','*',
'writeFileDescriptor' );

We have ORACLE HOME directory E:\ORACLE on server

I have checked DBA_JAVA_POLICY table and it is showing me all permission.

Please advice.

Thanks

          Followup:

don't see any execute privs on exp being given?  Look at the original example:

test@ORA817DEV.US.ORACLE.COM> begin
  2     dbms_java.grant_permission
  3        ('TEST',
  4        'java.io.FilePermission',
  5        '/export/home/ora817/bin/test.sh',
  6        'execute');

you are missing the execute -- and suggest you only use explicit paths (to avoid
trojan horses and to make sure you are doing what you thing you are doing)

      Rating:
      5
  December 17, 2002
Reviewer:
      A reader
Tom ,

I forgot to add one permission

EXEC
DBMS_JAVA.GRANT_PERMISSION(USER,'java.io.FilePermission','E:\ORACLE\BIN\EXP.EXE,'
EXECUTE');

Thanks,

      Rating: 5
Hi Tom is a way
      to give permission to all the directories  August 21, 2003

Reviewer:  juancarlosreyesp@yahoo.com  from Bolivia

Hi Tom is a way to give permission to any file and directory, instead of file by
file
Instead

EXEC
DBMS_JAVA.GRANT_PERMISSION(USER,'java.io.FilePermission','E:\ORACLE\BIN\EXP.EXE'
EXECUTE');

Something like
EXEC
DBMS_JAVA.GRANT_PERMISSION(USER,'java.io.FilePermission','%'
EXECUTE');

Thank you

          Followup:

OUCH

'*' works too -- but -- do you "grant select any table to public" in your
database?  (lets hope not).  you "grant select on emp to <role|user>"

wildcards are "big"

hmm, dbshut be in $OH/bin....
hmm, rm be in /usr/bin...

do you really want that?

      Rating: 5
Thank
      :)  August 22, 2003
Reviewer:  A reader

Yes that is what I wanted to know

      Rating:
      5
  November 20, 2003
Reviewer:
      RICHARD  from WASHINGTON, USA
Hi Tom,

Thank you very much for your previous help.

Regarding your wonderful RC routine (java stored procedure) to run OS command in
your book.  I have deployed all related objects and everything run perfectly.

Due to security concern, we would like to use dbms_java.grant_permission as
secured as possible,

For moving files from one directory to the other, we only talked about move file
here,
begin
rc('c:\winnt\system32\cmd.exe /c move c:\from\fromtest1.xml
 c:\to\fromtest1.xml');
end;

What will be most secured way to do DBMS_JAVA.GRANT_PERMISSION
instead of
BEGIN
DBMS_JAVA.GRANT_PERMISSION
             ('USER',
              'java.io.FilePermission',
              'c:\winnt\system32\cmd.exe,
              'execute');
END;

For example, can we do

BEGIN
DBMS_JAVA.GRANT_PERMISSION
('USER',
 'java.io.FilePermission',
 'c:\winnt\system32\cmd.exe /c move c:\from\*.xml :\to\*.xml,
 'execute');
END;

Please advise the most secured way...

Thanks for help.

          Followup:

no no -- don't give cmd.exe perms!!! you can do anything then!

anything.

either

a) write a script that does this
b) use xcopy and grant on xcopy -- so you can only copy files (and be careful
someone doesn't do "xcopy foo.dat c:\oracle\dbs\system.dbf"!!!)

November 21, 2003 bhabani -- Thanks for the question regarding
      "Escape Character", version 8.1.7.4

      You Asked
Hi Tom

I have this interesting observstion on SQL*Plus:

SQL>   select * from t1 where name like 'j%\%%' escape '\';

         A NAME
---------- ---------------
        23 jamshia%gan
        45 jamshia%kid

SQL> set escape '\';
SQL>  select * from t1 where name like 'j%\%%';

         A NAME
---------- ---------------
        10 jamshid
        10 jamshid
        23 jamshia%gan
        45 jamshia%kid
        67 jamshid*jid

SQL> select * from t1;

         A NAME
---------- ---------------
        10 jamshid
        10 jamshid
        23 jamshia%gan
        45 jamshia%kid
        67 jamshid*jid

Why SETting ESCAPE is not working properly on SQL* Plus

Thanks and Regards
BSPradhan

      and we said...
because "set escape", the sqlplus command, has NO EFFECT whatsoever on SQL.

it is for totally different purposes.  "set escape" is used to escape the
substitution character in sqlplus -- it has NO effect on SQL processing.
consider:

ops$tkyte@ORA920> set escape \
ops$tkyte@ORA920> select '\&1' from dual;

'&
--
&1

see, plus didn't ask me for the value of &1, nor did plus pass the \ onto SQL
itself -- we escaped the & in that query... putting escape back off as normal:

ops$tkyte@ORA920> set escape off
ops$tkyte@ORA920> select '\&1' from dual;
Enter value for 1: fda
old   1: select '\&1' from dual
new   1: select '\fda' from dual

'\FD
----
\fda

so, escape worked as designed in sqlplus.

don't forget -- sqlplus is NOT sql -- sqlplus is a simple command line tool that
you or I could write ourselves.  It has nothing to do with SQL really -- it is
just a rudimentary interface that lets us type in dynamic sql and have it
execute.
Review
      & Followup

      Rating:
      5
Thanks  September 21, 2003
Reviewer:
      b  from bangalore
I donot have any other question related to that.

Regards

      Rating:
      5
  September 21, 2003
Reviewer:  A
      Reader  from China
Then how could we do the same (escape) in sql queries?

          Followup:

you cannot do the "same" escape in SQL since it is SQLPlus that does
"substitution" (so there is no need for the "same" escape in SQL)

There is an escape in SQL -- for escaping the meaning of % and _ in a LIKE
predicate.  And it is demonstrated in the original question above.

      Rating: 3
Escaping for
      SQL-Engine  September 22, 2003
Reviewer:
      McJan  from GERMANY
Hi (just copy and past from my own investigations ...)
There are 2 different ESCAPE's, one in the SQL*PLUS shell and one for
the SQL Engine. Both have nothing to do with each other.

1. SQL*PLUS Shell
-----------------

....

2. SQL Engine
-------------

Now the second part, the SQL Engine.

- Searching for rows in tables with an equal (=) operator
  The column from the table have to match exactly the search string you
submitted.

      ... where s = 'D&W';

      set escape Ö
      select 'ÖkkÖ&' my, i, s from b1 where s = 'DÖ&W';

      MY           I S
      --- ---------- ------------------------------
      kk&          5 D&W

- like searching

  The like operator provides a speacial handling of wildcards.
  '%' matches any string, '_' a single character.

      select 'ÖkkÖ&' my, i, s from b1 where s like 'DÖ&W';
      ->
          MY           I S
          --- ---------- ------------------------------
          kk&          5 D&W

      2 new rows:
      - A_B\C%D
      - AKBO

      select * from b1 where s like 'A_B%';

               I S
      ---------- ------------------------------
               3 A_B\C%D
               4 AKBO

    Because '_' matches any single character, column 3 and 4 will be found.
    But if you want to suppress the wildcard and exactly want the rows
    starting with an 'A_' followd by any string, you need to ESCAPE the '_'
    wildcard by a self defined escape character other then '%' and '_':

    select * from b1 where s like 'A\_B%' ESCAPE '\';

             I S
    ---------- ------------------------------
             3 A_B\C%D

    select * from b1 where s like 'AP_B%' ESCAPE 'P';

             I S
    ---------- ------------------------------
             3 A_B\C%D

3.) Examples:

    set escape Ö
    drop table b1;
    create table b1 (i number(2), s varchar2(30));
    insert into b1(i,s) values(1,'trias\-thieme\, hippokrates\');
    insert into b1(i,s) values(2,'updated');
    insert into b1(i,s) values(3,'A_B\C%D');
    insert into b1(i,s) values(4,'AKBO');
    insert into b1(i,s) values(4,'A\_BL');
    insert into b1(i,s) values(5,'DÖ&W');
    insert into b1(i,s) values(6,'Jan O''Conner');
    select * from b1;

    select * from b1 where s like 'A_B%';
    select * from b1 where s like 'Jan ''O%';
    select * from b1 where s like 'Jan O'%';
    select * from b1 where s like 'Jan O''%' or s = 'A\_BL';
    select * from b1 where s like 'Jan O''%' or s = 'DÖ&W';

      Rating: 5
Escape (
      Multiple )  September 23, 2003
Reviewer:  A
      reader from india  from China
How to use {} as the escape for any where clause like operator

eg: select * from a where ref like '%\%%' escape '\';

like above how we can do it for

select * from a where ref like '%{%_@...}...

please let me know. I did search in your web site and also in OTN but i didnt
get an answer.

Thanks in advance

          Followup:

you cannot, that is not the syntax in SQL.

you would query

like '%\%\_@.....'

      Rating:
      3
OK  November 21, 2003
Reviewer:  John
       from Bangalore,India
Dear sir,
Is there any 'chr' sequence for tab(one space between each
character) like the one  we have chr(10) for newline?

          Followup:

chr(9)

November 21, 2003 Pichaimani -- Thanks for the question regarding
      "Creating hierarchial tree over a view", version 8.0.6

      You Asked
Hi Tom,
   Could u kindly enlight me whether can we build a hierarchial tree
over a view. i.e. My view will do the filtering ,join  etc once the view has
been created successfully can I create the hierarchial tree on that view using
connect by clause. I know we can't do that in Oracle 8.0 and prior Can we do it
in 8i or later.
   When I searched the manual I could not find any hint for it.

Thanks and Regards

      and we said...
This is a feature of 9i.  Prior to that, you quite simply cannot connect by a
join.

In 9i you can

o order the hierarchy in a connect by
o join a connect by
o get the "connect by path"

It is not available before.

Here is an example from 9i

scott@ORA9I.WORLD> create or replace view v
  2  as
  3  select emp.ename, emp.empno, emp.mgr, dept.dname from emp, dept
        where emp.deptno = dept.deptno
  4  /

View created.

scott@ORA9I.WORLD> column EmpName format a30
scott@ORA9I.WORLD> select rpad('*',2*level,'*')||ename EmpName, dname
  2    from v                   connect by a join...
  3   start with mgr is null
  4   connect by prior empno = mgr
  5  /

EMPNAME                        DNAME
------------------------------ --------------
**KING                         ACCOUNTING
****CLARK                      ACCOUNTING
******MILLER                   ACCOUNTING
****JONES                      RESEARCH
******FORD                     RESEARCH
********SMITH                  RESEARCH
******SCOTT                    RESEARCH
********ADAMS                  RESEARCH
****BLAKE                      SALES
******ALLEN                    SALES
******JAMES                    SALES
******WARD                     SALES
******TURNER                   SALES
******MARTIN                   SALES

14 rows selected.

scott@ORA9I.WORLD>
scott@ORA9I.WORLD>
scott@ORA9I.WORLD>
scott@ORA9I.WORLD> select rpad('*',2*level,'*')||ename EmpName, dname
  2    from v
  3   start with mgr is null
  4  connect by prior empno = mgr
  5   order SIBLINGS by ename              Order the hierarchy
  6  /

EMPNAME                        DNAME
------------------------------ --------------
**KING                         ACCOUNTING
****BLAKE                      SALES
******ALLEN                    SALES
******JAMES                    SALES
******MARTIN                   SALES
******TURNER                   SALES
******WARD                     SALES
****CLARK                      ACCOUNTING
******MILLER                   ACCOUNTING
****JONES                      RESEARCH
******FORD                     RESEARCH
********SMITH                  RESEARCH
******SCOTT                    RESEARCH
********ADAMS                  RESEARCH

14 rows selected.

scott@ORA9I.WORLD>
scott@ORA9I.WORLD> column cbp format a30
scott@ORA9I.WORLD>
scott@ORA9I.WORLD> select rpad('*',2*level,'*')||ename EmpName, dname,
  2             sys_connect_by_path( ename, '/' ) cbp
  3    from v
  4   start with mgr is null
  5   connect by prior empno = mgr
  6   order SIBLINGS by ename
  7  /

EMPNAME                        DNAME          CBP
------------------------------ -------------- ------------------------------
**KING                         ACCOUNTING     /KING
****BLAKE                      SALES          /KING/BLAKE
******ALLEN                    SALES          /KING/BLAKE/ALLEN
******JAMES                    SALES          /KING/BLAKE/JAMES
******MARTIN                   SALES          /KING/BLAKE/MARTIN
******TURNER                   SALES          /KING/BLAKE/TURNER
******WARD                     SALES          /KING/BLAKE/WARD
****CLARK                      ACCOUNTING     /KING/CLARK
******MILLER                   ACCOUNTING     /KING/CLARK/MILLER
****JONES                      RESEARCH       /KING/JONES
******FORD                     RESEARCH       /KING/JONES/FORD
********SMITH                  RESEARCH       /KING/JONES/FORD/SMITH
******SCOTT                    RESEARCH       /KING/JONES/SCOTT
********ADAMS                  RESEARCH       /KING/JONES/SCOTT/ADAMS

14 rows selected.
Review & Followup

      Rating: 4
Desired
      features are in 9i  July 19, 2001
Reviewer:
      Pichaimani  from San Jose, CA USA
Thanks a lot if all these features had been available earlier itself I could
have eliminated the need for
runtime tables(which are populated and deleted.)

      Rating: 2
Creating
      hierarchial tree over a view  February 26, 2002

Reviewer:  Shatha  from Jordan-Amman
The question "Creating hierarchial tree over a view " was answered using
Oracle9i and didn't answer if it could be done using Oracle8.

          Followup:

re-read the very first sentence:

This is a feature of 9i.  Prior to that, you quite simply cannot connect by a
join.

I answered it.

      Rating: 4
Creating
      hierarchial tree over a view   February 26, 2002

Reviewer:  Harvey  from MO USA
A question about functions lpad and rpad in this case.  I tried lpad and rpad
here, and they give the same output. Could you tell why?

Thanks!

Harvey

          Followup:

because

lpad( '*', 32, '*' ) = rpad( '*', 32, '*' )

thats why.... it doesn't matter if you put 31 *'s on the LEFT or the RIGHT of a
single *, you get 32 *'s

;)

      Rating: 3
Creating
      hierarchial tree over a view  February 27, 2002

Reviewer:  viswanath  from usa
Problem Description:
====================

The following error occurs when the user executes a select statement that
contains a connect by clause:

ORA-1437: Cannot have join with connect by statement.

Problem Explanation:
====================

The connect by clause is used in a tree-structured query, where the returned
records are organized by their relationship with one another.

Solution Description:
====================

The select statement is attempting a tree-structured query on a view which is,
in turn, based upon a join.  There cannot be a join across the CONNECT BY. The
hierarchical query can only organize rows of a single table.  This is a SQL
limitation.

Workaround:  Create a view with the connect by, then join with another table.

Example:
The following view is based upon Example III p.3-364 SQL Oracle 7 Server
SQL*Language Reference Manual.  It uses the EMP table as the hierarchical
query source, then is followed by a select statement that joins it with the
DEPT table. This join would work with any number of tree-structured views.

SQL>
Create view ORG as
SELECT
    LPAD(' ',2*(LEVEL -1)) || ename Org_chart, empno, mgr, job, deptno
    FROM emp
    START WITH
     job = 'PRESIDENT'
    CONNECT BY PRIOR empno=mgr;    /* Retrieve the record only
                    /* if the prior empno value
                    /* (in the previous returned row)
                    /* had the same value as this
                    /* row's mgr number

Use the following join condition to get the second table's information.

SQL>
SELECT org_chart, empno, mgr, job, o.deptno, dname FROM org O, dept D WHERE
O.deptno = D.deptno;

Solution Explanation:
====================

The error occurs because the user is attempting to execute a select statement
on a view which is the product of a join of multiple tables.  The select
statement fails because the CONNECT BY cannot integrate two separate tables.
This is a SQL limitation.

Workaround:
By joining after creating a view with a CONNECT BY clause, no error results.
However, only one table can be hierarchically organized.  The user can create
several tree structured views separately and then join them together with a
select statement.

          Followup:

That SOLUTION is no SOLUTION.

If you do that -- the optimizer is 100% free to totally destroy your hierarchy!
You are concerned with keeping the hierarchy right?  Well, there is NOTHING that
says the order of the hierarchy in the view will be preserved.  the author of
that note was mistaken in their belief.

Here is a simple proof showing this technique is flawed.  DO NOT use this
approach in your applications as some day you will in fact get the WRONG answer
(well, you'll think it is wrong, it'll be correct, just not what you expected)

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t1 ( id int, pid int, fk int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t2 ( pk int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t1 values ( 1, null, 4 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t1 values ( 2, 1,         3 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t1 values ( 3, 1,         2 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t1 values ( 4, 2,         1 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t2 values ( 1 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t2 values ( 2 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t2 values ( 3 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t2 values ( 4 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> column idstr format a20
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace view v
  2  as
  3  select rpad( '*', level*2, '*' ) || '-' || id idstr, pid, fk, level lvl
  4    from t1
  5  connect by prior id = pid
  6  start with pid is null
  7  /

View created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from v;

IDSTR                       PID         FK        LVL
-------------------- ---------- ---------- ----------
**-1                                     4          1
****-2                        1          3          2
******-4                      2          1          3
****-3                        1          2          2

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from v, t2 where v.fk = t2.pk;

IDSTR                       PID         FK        LVL         PK
-------------------- ---------- ---------- ---------- ----------
******-4                      2          1          3          1
****-3                        1          2          2          2
****-2                        1          3          2          3
**-1                                     4          1          4

See, select from the view and the hierarchy is there. join the view and bamm --
there it goes -- the hierarchy is totally lost.

      Rating: 4
Pls try
      this  February 27, 2002
Reviewer:  LCoppola
       from Hartford, CT
--Include RowNum w/in the view creation
--and order by of the select/join

create table t1 ( id int, pid int, fk int );
create table t2 ( pk int );

insert into t1 values ( 1, null, 4 );
insert into t1 values ( 2, 1,3 );
insert into t1 values ( 3, 1,2 );
insert into t1 values ( 4, 2,1 );
insert into t2 values ( 1 );
insert into t2 values ( 2 );
insert into t2 values ( 3 );
insert into t2 values ( 4 );

create or replace view v
as
select rownum rnum,rpad( '*', level*2, '*' ) || '-' || id idstr, pid, fk, level
lvl
from t1
connect by prior id = pid
start with pid is null

column idstr format a20
column rnum format a5
select * from v;

select * from v, t2 where v.fk = t2.pk order by rnum;

Table created.

Table created.

1 row created.

1 row created.

1 row created.

1 row created.

1 row created.

1 row created.

1 row created.

1 row created.

      RNUM IDSTR                      PID        FK       LVL

---------- -------------------- --------- --------- ---------

########## **-1                                   4         1

########## ****-2                       1         3         2

########## ******-4                     2         1         3

########## ****-3                       1         2         2

      RNUM IDSTR                      PID        FK       LVL        PK

---------- -------------------- --------- --------- --------- ---------

########## **-1                                   4         1         4

########## ****-2                       1         3         2         3

########## ******-4                     2         1         3         1

########## ****-3                       1         2         2         2

          Followup:

sure, now -- do it on a sizable result set.....

Including rownum in the view forces the ENTIRE view to be materialized and then
joined to.  Works great on a piddling amount of data, works really really slow
on normal sized result sets.

      Rating: 4
Addt'l
      Comments  February 28, 2002
Reviewer:
      LCoppola  from Htfd, Ct
Yes, I agree with your reply.
I created a materialized view using a similar setup (embedding rownum w/in
materialized view) and then joining materialized view and other tables in other
queries as needed

      Rating: 4
Addt'l
      Info  February 28, 2002
Reviewer:  LCoppola
       from Htfd, Ct
Pls note that my materialized view's result set contained less than 150 rows.

      Rating: 4
how about
      this?  July 18, 2002
Reviewer:  Alex
      Rakhalski  from Moscow, Russia
CREATE OR REPLACE VIEW v (
   idstr,
   pid,
   fk,
   lvl )
AS
SELECT
  RPAD( '*', LEVEL*2, '*' ) || '-' || id idstr
, pid
, fk
, LEVEL lvl
FROM t1
CONNECT BY PRIOR id = pid
START WITH pid IS NULL/
(view WITHOUT ROWNUM)

SELECT /*+ ORDERED*/ *
FROM v, t2
WHERE v.fk = t2.pk

          Followup:

what about it?  there is no order by, the data will come out as we see fit --
subject to change whenever we feel like it.

You want an ordered hierarchy?  In 8i, better do it procedurally.  In 9i, order
by siblings.

Everything else is like a house of cards built on quicksand (and an earthquake
is coming)

      Rating: 4
don't
      understand  July 18, 2002
Reviewer:  Alex
      Rakhalski  from Moscow, Russia
Tom, you means new release of Oracle server and so on? (Because this approach
not documented nor supported and so on  )

          Followup:

No, I mean because you seem to think that your query above will return data in
some specific order.

It won't.
It doesn't have to.
It can return the data however it feels.
It might work in some cases, not others.
It might work one way one day, another the next.
It is unreliable.

      Rating: 4
What about
      this?  July 22, 2002
Reviewer:  Michael

In 8.1.7:

select level, ename, dname
from emp, dept
where emp.deptno = dept.deptno
start with mgr is null connect by prior empno = mgr;

ORA-01437: cannot have join with CONNECT BY

Now with the join in the select:

select level, ename,
        (select dname
         from dept
         where dept.deptno = emp.deptno) dname
from emp
start with mgr is null connect by prior empno = mgr;

     LEVEL ENAME      DNAME
---------- ---------- --------------
         1 KING       ACCOUNTING
         2 JONES      RESEARCH
         3 SCOTT      RESEARCH
         4 ADAMS      RESEARCH
         3 FORD       RESEARCH
         4 SMITH      RESEARCH
         2 BLAKE      SALES
         3 ALLEN      SALES
         3 WARD       SALES
         3 MARTIN     SALES
         3 TURNER     SALES
         3 JAMES      SALES
         2 CLARK      ACCOUNTING
         3 MILLER     ACCOUNTING

Perhaps with helps in some cases.

          Followup:

absolutely.  if all you need is dependent data that works great.

If you need the join to filter the data, that'll not work tho.

      Rating: 3
This
      hierarchical query isn't working  August 15, 2003

Reviewer:  ht  from california
Tom,
I'm trying to query a user and title table to display a hierarchy.  Can you help
me figure out what I'm missing?

Thank you.

Oracle9i Enterprise Edition Release 9.2.0.3.0 - 64bit Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production

Enter password:
Connected.
>
>drop table tuser;

Table dropped.

>create table tuser
  2          (
  3          id number,
  4          name varchar2(10),
  5          reportsto_id number,
  6          title_id number
  7          );

Table created.

>
>insert into tuser values(1,'user1',null,1);

1 row created.

>insert into tuser values(2,'user2',1,2);

1 row created.

>insert into tuser values(3,'user3',2,3);

1 row created.

>insert into tuser values(4,'user4',3,4);

1 row created.

>insert into tuser values(5,'user5',3,4);

1 row created.

>
>drop table ttitle;

Table dropped.

>create table ttitle
  2          (
  3          id number,
  4          name varchar2(10)
  5          );

Table created.

>
>insert into ttitle values(1,'VP');

1 row created.

>insert into ttitle values(2,'Dir');

1 row created.

>insert into ttitle values(3,'Mgr');

1 row created.

>insert into ttitle values(4,'DBA');

1 row created.

>
>select
  2          u.id,
  3          lpad('x',7 *(level-1),'x')||u.name,
  4          level,
  5          level-1,
  6          t.name title,
  7          t.id title_id,
  8          u.reportsto_id
  9  from
 10          tuser u,
 11          ttitle t
 12  where
 13          u.title_id=t.id
 14  start with u.reportsto_id = null
 15  connect by prior u.id=u.reportsto_id
 16  order siblings by u.name;

no rows selected

          Followup:

this demo will help:

ops$tkyte@ORA920> create table t ( x int );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values(NULL);

1 row created.

ops$tkyte@ORA920> insert into t values(1);

1 row created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t where x = null;

no rows selected

ops$tkyte@ORA920> select * from t where x <> null;

no rows selected

ops$tkyte@ORA920> select * from t where x IS NULL;

         X
----------

ops$tkyte@ORA920> select * from t where x IS NOT NULL;

         X
----------
         1

ops$tkyte@ORA920>

nothing but nothing is equal to null.

nothing but nothing is NOT equal to null.

things "are null"
things "are not null"

try starting with "reportsto_id is null"

      Rating:
      3
  August 15, 2003
Reviewer:  ht
       from california
Figured it out, changed the "=null" to "is null".

      Rating: 2
Hierarchical
      query not working.  August 16, 2003
Reviewer:
      ht  from california
Tom,
I know I'm missing something obvious here but I'm not able to figure out why my
"lpad" doesn't display correctly.  If I remove the ttitle part of the query,
everything works fine.

Thanks,
ht

SQL*Plus: Release 9.2.0.3.0 - Production on Fri Aug 15 21:03:10 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.3.0 - 64bit Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production

Enter password:
Connected.
>drop table tname;

Table dropped.

>drop table ttitle;

Table dropped.

>
>create table ttitle
  2          (
  3          id number               constraint pk_ttitle primary key disable,
  4          name varchar2(50)       constraint uq_ttitle1 unique
  5          )
  6          tablespace users
  7                  storage
  8                  (
  9                  initial         1m
 10                  next            1m
 11                  maxextents      unlimited
 12                  pctincrease     0
 13                  );

Table created.

>
>alter table ttitle enable constraint pk_ttitle using index
  2          tablespace indx
  3                  storage
  4                  (
  5                  initial         100k
  6                  next            100k
  7                  maxextents      unlimited
  8                  pctincrease     0
  9                  );

Table altered.

>
>drop sequence ttitleseq;

Sequence dropped.

>
>create sequence ttitleseq start with 1 increment by 1;

Sequence created.

>-------------------------------------------------------------------
>insert into ttitle (id,name)
  2          values (ttitleseq.nextval,'title1');

1 row created.

>insert into ttitle (id,name)
  2          values (ttitleseq.nextval,'title2');

1 row created.

>insert into ttitle (id,name)
  2          values (ttitleseq.nextval,'title3');

1 row created.

>-------------------------------------------------------------------
>
>create table tname
  2          (
  3          id              number          constraint pk_tname primary key
disable,
  4          username        varchar2(25),
  5          reportsto_id    number          default 1,
  6          title_id        constraint fk_ttitle references ttitle(id)
  7          )
  8          tablespace users
  9                  storage
 10                  (
 11                  initial         1m
 12                  next            1m
 13                  maxextents      unlimited
 14                  pctincrease     0
 15                  );

Table created.

>
>--alter table tname modify title_id default 1;
>
>alter table tname enable constraint pk_tname using index
  2          tablespace indx
  3                  storage
  4                  (
  5                  initial         100k
  6                  next            100k
  7                  maxextents      unlimited
  8                  pctincrease     0
  9                  );

Table altered.

>
>drop sequence tnameseq;

Sequence dropped.

>
>create sequence tnameseq start with 1 increment by 1;

Sequence created.

>-------------------------------------------------------------------
>insert into tname (id,username,title_id,reportsto_id) values
(tnameseq.nextval,'user1',1,null);

1 row created.

>insert into tname (id,username,title_id,reportsto_id) values
(tnameseq.nextval,'user2',2,1);

1 row created.

>insert into tname (id,username,title_id,reportsto_id) values
(tnameseq.nextval,'user3',3,2);

1 row created.

>-------------------------------------------------------------------
>    select
  2                  n.id,
  3                  lpad('.',7 *(level-1),'.')||n.username username,
  4                  level,
  5                  t.name title
  6          from
  7                  tname n,
  8                  ttitle t
  9          where
 10                  n.title_id=t.id(+)
 11          start with n.reportsto_id is null
 12          connect by prior n.id=n.reportsto_id
 13          order siblings by username      ;

        ID
----------
USERNAME
--------------------------------------------------------------------------------
     LEVEL
----------
TITLE
--------------------------------------------------------------------------------
         1
user1
         1
title1

         2
user2
         2
title2

         3
user3
         3
title3

3 rows selected.

          Followup:

use a different alias, it was a bug.  try ||n.username uname

      Rating: 3
Hierarchy query
      still doesn't work with another alias.  August 16, 2003

Reviewer:  ht  from california
Hi Tom,
I used another column alias but the same results are returned.
Thanks,
ht
>-------------------------------------------------------------------
>    select
  2                  n.id,
  3                  lpad('.',7 *(level-1),'.')||n.username xxxname,
  4                  level,
  5                  t.name title
  6          from
  7                  tname n,
  8                  ttitle t
  9          where
 10                  n.title_id=t.id(+)
 11          start with n.reportsto_id is null
 12          connect by prior n.id=n.reportsto_id
 13          order siblings by xxxname;

        ID
----------
XXXNAME
--------------------------------------------------------------------------------
     LEVEL
----------
TITLE
--------------------------------------------------------------------------------
         1
user1
         1
title1

         2
user2
         2
title2

         3
user3
         3
title3

          Followup:

i meant just the correlation name -- not the order by siblings clause -- that is
the problem.  consider:

ops$tkyte@ORA920> select n.id,
  2         lpad('.',7 *(level-1),'.')||n.username username,
  3         lpad('.',7 *(level-1),'.')||n.username xxxname,
  4         level,
  5         t.name title
  6    from tname n,
  7         ttitle t
  8   where n.title_id=t.id(+)
  9   start with n.reportsto_id is null
 10  connect by prior n.id=n.reportsto_id
 11  order siblings by username;

 ID USERNAME        XXXNAME                   LEVEL TITLE
--- --------------- ------------------------- ----- ------
  1 user1           user1                         1 title1
  2 user2           .......user2                  2 title2
  3 user3           ..............user3           3 title3

order siblings by username
select xxxname

that is really what you want anyway.

You want to order by username (not with the lpads, that would screw up the
sort!) but show xxxname.  If it did what you asked -- it would be giving you
data sorted not as you expected!

      Rating: 3
Hierarchical
      query works without a join.  August 16, 2003

Reviewer:  ht  from california
Tom,
The query works if 1 table is queried, not 2.  Is there something preventing me
from querying 2 tables in a hierarchical query?  Below, I created table tname2
and added the title column.
Thanks for your help with this.  It's been stumping me for a couple days.
ht

>select * from tname2;

        ID USERNAME   REPORTSTO_ID   TITLE_ID TITLE
---------- ---------- ------------ ---------- ----------
         1 user1                            1 title1
         2 user2                 1          2 title2
         3 user3                 2          3 title3

3 rows selected.

>select
  2                  n.id,
  3                  lpad('.',7 *(level-1),'.')||n.username xxxname,
  4                  level,
  5                  n.title title
  6          from
  7                  tname2 n
  8                 start with n.reportsto_id is null
  9          connect by prior n.id=n.reportsto_id
 10          order siblings by xxxname;

        ID XXXNAME                   LEVEL TITLE
---------- -------------------- ---------- ----------
         1 user1                         1 title1
         2 .......user2                  2 title2
         3 ..............user3           3 title3

3 rows selected.

>select * from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.3.0 - 64bit Production
PL/SQL Release 9.2.0.3.0 - Production
CORE    9.2.0.3.0       Production
TNS for Solaris: Version 9.2.0.3.0 - Production
NLSRTL Version 9.2.0.3.0 - Production

5 rows selected.

      Rating: 3
Even using a
      view on 2 tables doesn't fix the hierarchical query.  August
      16, 2003
Reviewer:  ht  from California
Tom,
Even creating a view on the 2 tables doesn't seem to allow for a formatted
display.  Do you have any ideas on what's causing this problem?  I won't be able
to move the user's title info into the user table so I'm looking for some sort
of workaround.
Thanks,
ht

>create view tview1 as
  2          select n.id id,n.username username,n.reportsto_id
reportsto_id,t.name title
  3          from ttitle t, tname n
  4          where n.title_id=t.id;

View created.

>    select
  2                  id,
  3                  lpad('.',7 *(level-1),'.')||username xxxname,
  4                  level,
  5                  title
  6          from
  7                  tview1
  8          start with reportsto_id is null
  9          connect by prior id=reportsto_id
 10          order siblings by xxxname;

        ID
----------
XXXNAME
--------------------------------------------------------------------------------
     LEVEL
----------
TITLE
--------------------------------------------------------------------------------
         1
user1
         1
title1

         2
user2
         2
title2

         3
user3
         3
title3

          Followup:

please -- read the above -- you do NOT want to order by xxxname, you want to
order by username!!!!!

      Rating:
      5
Thanks.  August 16, 2003
Reviewer:
      ht  from California
Tom,
Thanks, as usual, you fixed the problem.  The strange thing is that I still
can't figure out why I encountered this error.  Maybe it's because of the data I
populated in the tables?  In other words, using the column alias worked fine in
schema1.1 but, when I built schema1.2, it broke.

I had this code in both schemas:
... lpad('.',7 *(level-1),'.')||p.fname||' '||p.lname personname,...
...order by siblings personname...

By using ...order by siblings p.fname,p.lname... in both schemas, the results
are as I expect.

Thanks again and have a good weekend.
ht

      Rating: 5
didn't refresh
      my page  August 16, 2003
Reviewer:  ht
       from California
Tom,
I just read your reply, I should refresh my page before submitting future
questions.
Thanks for the explanation.
ht

      Rating: 4
Prior to
      9i  August 21, 2003
Reviewer:  Jagjeet Singh
       from INDIA
hello sir ..

I was trying to do the same code in ORACLE 8.1.5 ..
Please tell me ..whether this is the right way or not ?

SQL>
SQL> Create or replace  function Tree (
  2                                     p_ename       varchar2,
  3                                     p_mgr         number,
  4                                     p_level       number
  5                                   )
  6                                     return varchar2
  7  as
  8  v_ret     varchar2(80) ;
  9  v_ename   varchar2(30);
 10  v_empno   number;
 11  v_mgr     number;
 12  begin
 13  ---
 14  If p_level  = 1 then
 15  V_ret  :=   p_ename ;
 16  Else
 17  Select ename,empno,mgr into v_ename,v_empno,v_mgr from emp where  empno =
p_mgr ;
 18  V_ret  := tree(v_ename||'/'||p_ename,v_mgr,p_level-1);
 19  End if;
 20  ---
 21  return v_ret;
 22  end;
 23  /

Function created.

SQL> col ename for a30
SQL> col tree  for a30
SQL>
SQL>
SQL> select  level,rpad(' ',(level*3)+2,' ')||ename ename,tree(ename,mgr,level)
Tree from emp
  2  connect by prior empno=mgr
  3  start with ename = 'KING'
  4  /

     LEVEL ENAME                          TREE
---------- ------------------------------ ------------------------------
         1      KING                      KING
         2         JONES                  KING/JONES
         3            SCOTT               KING/JONES/SCOTT
         4               ADAMS            KING/JONES/SCOTT/ADAMS
         3            FORD                KING/JONES/FORD
         4               SMITH            KING/JONES/FORD/SMITH
         2         BLAKE                  KING/BLAKE
         3            ALLEN               KING/BLAKE/ALLEN
         3            WARD                KING/BLAKE/WARD
         3            MARTIN              KING/BLAKE/MARTIN
         3            TURNER              KING/BLAKE/TURNER
         3            JAMES               KING/BLAKE/JAMES
         2         CLARK                  KING/CLARK
         3            MILLER              KING/CLARK/MILLER

14 rows selected.

SQL>
SQL>
SQL> select  level,rpad(' ',(level*3)+2,' ')||ename ename,tree(ename,mgr,level)
Tree from emp
  2  connect by prior empno=mgr
  3   start with ename = '&Ename';
Enter value for ename:JONES

     LEVEL ENAME                          TREE
---------- ------------------------------ ------------------------------
         1      JONES                     JONES
         2         SCOTT                  JONES/SCOTT
         3            ADAMS               JONES/SCOTT/ADAMS
         2         FORD                   JONES/FORD
         3            SMITH               JONES/FORD/SMITH

5 rows selected.

SQL> /
Enter value for ename: BLAKE

     LEVEL ENAME                          TREE
---------- ------------------------------ ------------------------------
         1      BLAKE                     BLAKE
         2         ALLEN                  BLAKE/ALLEN
         2         WARD                   BLAKE/WARD
         2         MARTIN                 BLAKE/MARTIN
         2         TURNER                 BLAKE/TURNER
         2         JAMES                  BLAKE/JAMES

6 rows selected.

          Followup:

ops$tkyte@ORA920> create or replace function scbp( p_ename in varchar2 ) return
varchar2
  2  is
  3      l_return varchar2(4000);
  4  begin
  5      for x in ( select ename
  6                   from emp
  7                  start with ename = p_ename
  8                 connect by prior mgr = empno )
  9      loop
 10          l_return := x.ename || '/' || l_return;
 11      end loop;
 12
 13      return rtrim(l_return,'/');
 14  end;
 15  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> column tree format a30
ops$tkyte@ORA920> column ename format a20
ops$tkyte@ORA920>
ops$tkyte@ORA920> select level,rpad(' ',(level*3)+2,' ')||ename
ename,scbp(ename) tree
  2    from emp
  3    connect by prior empno=mgr
  4    start with ename = 'KING'
  5  /

     LEVEL ENAME                TREE
---------- -------------------- ------------------------------
         1      KING            KING
         2         JONES        KING/JONES
         3            SCOTT     KING/JONES/SCOTT
         4               ADAMS  KING/JONES/SCOTT/ADAMS
         3            FORD      KING/JONES/FORD
         4               SMITH  KING/JONES/FORD/SMITH
         2         BLAKE        KING/BLAKE
         3            ALLEN     KING/BLAKE/ALLEN
         3            WARD      KING/BLAKE/WARD
         3            MARTIN    KING/BLAKE/MARTIN
         3            TURNER    KING/BLAKE/TURNER
         3            JAMES     KING/BLAKE/JAMES
         2         CLARK        KING/CLARK
         3            MILLER    KING/CLARK/MILLER

14 rows selected.

would be my approach.

      Rating: 5
Connect by with
        September 08, 2003
Reviewer:  Pooja
       from USA
Hi Tom,

I've my child id and parent id in separate tables. What are my options prior to
9i if I need to get hierarchial structure starting from a parent?

I cannot avoid the joins as they are in two different tables and I need to join
at least 4 tables to join child and parent ids.

Could you please suggest what would be the better approach in this case?

Thanks
Pooja

          Followup:

how can you have them in separate tables?

      Rating: 4
Solution for 8i
      developers?  November 21, 2003
Reviewer:  Scott
      Wesley  from Perth, Western Australia
G'day Tom

Do you see any issues with this approach?

15:17:40 SCOTT@devdb> create or replace view v
15:17:40   2      as
15:17:40   3      select rownum aha, rpad( '*', level*2, '*' ) || '-' || id
idstr, pid, fk, level lvl
15:17:40   4        from t1
15:17:40   5      connect by prior id = pid
15:17:40   6      start with pid is null;

View created.

 real: 47
15:17:40 SCOTT@devdb>  select * from v;

       AHA IDSTR                       PID         FK        LVL
---------- -------------------- ---------- ---------- ----------
         1 **-1                                     4          1
         2 ****-2                        1          3          2
         3 ******-4                      2          1          3
         4 ****-3                        1          2          2

4 rows selected.

 real: 16
15:17:40 SCOTT@devdb>  select * from v, t2 where v.fk = t2.pk ;

       AHA IDSTR                       PID         FK        LVL         PK
---------- -------------------- ---------- ---------- ---------- ----------
         3 ******-4                      2          1          3          1
         4 ****-3                        1          2          2          2
         2 ****-2                        1          3          2          3
         1 **-1                                     4          1          4

4 rows selected.

 real: 15
15:17:48 SCOTT@devdb> i order by aha
15:17:54 SCOTT@devdb> /

       AHA IDSTR                       PID         FK        LVL         PK
---------- -------------------- ---------- ---------- ---------- ----------
         1 **-1                                     4          1          4
         2 ****-2                        1          3          2          3
         3 ******-4                      2          1          3          1
         4 ****-3                        1          2          2          2

4 rows selected.

Thanks

          Followup:

that works -- as long as you always need the entire hierarchy.

scalar subqueries work dandy too.

scott@ORA920LAP> l
  1  select rpad( '*',level*2,'*') || ename empname,
  2         (select dname from dept where dept.deptno = emp.deptno)dname
  3  from emp
  4  start with mgr is null
  5* connect by prior empno = mgr
scott@ORA920LAP> /

EMPNAME         DNAME
--------------- --------------
**KING          ACCOUNTING
****JONES       RESEARCH
******SCOTT     RESEARCH
********ADAMS   RESEARCH
******FORD      RESEARCH
********SMITH   RESEARCH
****BLAKE       SALES
******ALLEN     SALES
******WARD      SALES
******MARTIN    SALES
******TURNER    SALES
******JAMES     SALES
****CLARK       ACCOUNTING
******MILLER    ACCOUNTING

14 rows selected.

November 21, 2003 Ravi -- Thanks for the question regarding
      "Mutating triggers", version Oracle8

      You Asked
What are mutating triggers and how can they be avoided?

      and we said...
triggers don't mutate -- tables do.

see

http://osi.oracle.com/~tkyte/Mutate/index.html
...
Review
      & Followup

      Rating: 1
Triggers
      matates  November 21, 2003
Reviewer:
      Dhirendra  from India
Triggers are mutates.

          Followup:

huh?

(but you are wrong, tables are mutating, triggers fail when a table is
mutating).

ops$tkyte@ORA920LAP> create table t ( x int );

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create trigger t_trigger
  2  after insert on t for each row
  3  begin
  4          for x in (select * from t) loop null; end loop;
  5  end;
  6  /

Trigger created.

ops$tkyte@ORA920LAP> insert into t values ( 1 );
insert into t values ( 1 )
            *
ERROR at line 1:
ORA-04091: table OPS$TKYTE.T is mutating, trigger/function may not see it
ORA-06512: at "OPS$TKYTE.T_TRIGGER", line 2
ORA-04088: error during execution of trigger 'OPS$TKYTE.T_TRIGGER'

see -- table T is mutating, the trigger -- no, it is not mutating.

November 21, 2003 chng -- Thanks for the question regarding "redo
      log buffer", version 7.3.4

      You Asked
My database redo log size is 300MB and my redo log buffer is set
to 100M (1/3 of redo log file). My redo log switch every 20
minutes.

Do oracle make used of the 100M memory or just 1M? And what is a
good size of redo log buffer?

Thanks
RC
23/08/2000

      and we said...
The redo log buffer is flushed

o every 3 seconds
o when 1/3 full
o when you commit.

It is doubtful that you are using 100meg of redo log buffer.  In
order to do so, you would have to have enough concurrent
transactions to generate 300meg of redo without any of them
committing in under 3 seconds.

Since your online redo logs of 300 meg switch every 20 minutes
-- this is not the case.

In my opinion, your redo log buffers are much too large, you are
only wasting 99 to 99.5 meg of RAM on them.  I would prefer to
see them in the 1/4-1/2 meg range myself to keep a steadier flow
of data being written to the online redo log (over buffering
data is just as bad as not caching at all in many cases).

If you are trying to get your redo logs to switch less
frequently (although a steady state at 20 minutes is
perfection), you would have to either

o reduce the number of transactions you execute or change the
amount of data they affect

o increase the size of your online redo log.

Review &
      Followup

      Rating: 4
Redo log buffer
      and redo log resize  July 05, 2002
Reviewer:
      NP  from Th
Hi Tom
Great help
Thanks,
Our database (9.0.1.0.1) is running in non archive mode
its redo log buffer size is 52 k(532480) and redo log size is 100 mb as default
on installation.
Now we want to switch it to archive log mode with reduced redo log size either
1-4 mb,
How to proceed ,is deleteing all the redo group and recreate it of size 1 or 2
mb?any special procedure or precaution.
Is there any need to change the size of redo buffer also from 52 k to .....,or
its ok.
Thanks

          Followup:

changing the log buffer is a simple init.ora change, that can be done anytime
you have scheduled a restart of the db.

The redo log size change (but why goto 1-4meg?  that is pretty small) can be
done anytime.  Just add N new logs of the size you want, and then drop the old
logs you don't want anymore.  You might have to do an alter system switch
logfile to advance the logs off of the old ones but thats it.

      Rating: 4
Redo log file
      size  July 05, 2002
Reviewer:  NP  from
      TH
Hi Tom.
Thanks,
In my previous question u add :-
"The redo log size change (but why goto 1-4meg?  that is pretty small) can be
done anytime. "

Is 1-4 mb is not a ideal size for a moderate size databse
with high insert and low updates.is any proformence problem?
Could you please suggest a fair enough redo log size,
also how to know log how frequent the logs are switching.
Thanks
NP

Original question.
Hi Tom
Great help
Thanks,
Our database (9.0.1.0.1) is running in non archive mode
its redo log buffer size is 52 k(532480) and redo log size is 100 mb as default
on installation.
Now we want to switch it to archive log mode with reduced redo log size either
1-4 mb,
How to proceed ,is deleteing all the redo group and recreate it of size 1 or 2
mb?any special procedure or precaution.
Is there any need to change the size of redo buffer also from 52 k to .....,or
its ok.
Thanks

Followup:
changing the log buffer is a simple init.ora change, that can be done anytime
you have scheduled a restart of the db.

The redo log size change (but why goto 1-4meg?  that is pretty small) can be
done anytime.  Just add N new logs of the size you want, and then drop the old
logs you don't want anymore.  You might have to do an alter system switch
logfile to advance the logs off of the old ones but thats it.

          Followup:

size your logs so they switch no more frequently then you would like them to
during peak.  Many people like 5 minutes at least between switches.

      Rating: 4
Resizing Redo
      Log  July 12, 2002
Reviewer:  NP  from
      Th
Hi Tom
Thanks for previous help.
Recently we switched our Database to achvive log mode and resize redo log from
100 mb to 10 mb and now to 1 mb(presently) with three redo group mirror on 2
disk.

As our production environment is opertae under uneven load 24x 7.
Heavily in Morning (8 to 10 am) and afternoon (2 to 4 pm)
during this period it generate 20 mb of redos.(total 40 mb/day).
I am writing the exact timing of archvival
date           time
10/07/02       4.04 pm
11/07/02       1.40 am
11/07/02       9.45 am
11/07/02       11.14am
11/07/02       2.56 pm
redo size (10 mb.
Now my question is that weather creating a larger redo (10 mb to 100 mb) is
worth in this case or not.?
Is shifting to 1 mb size is a correct decision?
Is in case of crash / recovery and maintaning standby database smaller redo size
is of worth under uneven load?
Thanks

          Followup:

depends on your version.  If you have the fast_start_io_target or
fast_start_mttr_target init.ora parameters available, you don't need to use
small redo size to limit recovery time.

      Rating: 5
re: redo log
      buffer  June 28, 2003
Reviewer:
      A reader  from Houston
Tom,

Your answer
>>The redo log buffer is flushed
>>
>>o every 3 seconds
>>o when 1/3 full
>>o when you commit.

Say, the contents of the redo log buffer are flushed to the redo log files
because of the following 2 reasons
o every 3 seconds
o when 1/3 full
-not because of a commit

after the above scenario(flushing redo log buffer to the redo log files) happens
a couple of times and there has been no commit done
We have uncommited data in the redo log files and when a log switch occurs(since
the redo log file is full), the contents of the db buffer are written to the db.
a) if we do a rollback at this point what happens?
b) If we don't do a rollback and continue with the changes and if all the redo
log files are filled up, what would happen?
c) At what point are the redo logs copied to the archive logs?
d) Since redo logs could contain uncommited data, will the archive logs also
have uncommited data?

Thanks in advance for clearing my confusion.

          Followup:

a) we read the rollback data and undo the changes you made to the blocks on
disk.

b) we don't need the redo for checkpointed (on disk) blocks, so after the
checkpoint completes, we'll just advance into the log file and continue.  You
can easily run a transaction that generates 1gig of redo with 10m of online redo
log -- we'll just keep on rolling rolling rolling.

c) when we switch out of it to the next (assuming automatic archiving is enable
of course)

d) sure, nothing wrong with that.  redo is redo -- not really "uncommitted
changes" but a history of what happened, a replayable history.

      Rating:
      3
Good  June 29, 2003
Reviewer:
      R.Chacravarthi  from Chennai,India
As you said redo to be a replayable history but the most apt
will be replayable restricted log history.Since committed
transactions can be redone but uncommitted transactions can
only be rolled back(the basic principle of ARIES/RRH which means Algorithms for
Recovery and Isolation Exploiting Seman
-tics/Repeating Restricted History)
Thanks
R.chacravarthi

          Followup:

Uncommitted transactions are both redone AND undone by the way.  If you mean
"hey, you could theoretically skip some of the redo" -- it is not possible in
reality in Oracle (or in a real world database really) as you would not be able
to flush dirty blocks to disk before commit -- meaning the buffer cache would be
a constraint on the size of transactions, the number of concurrent transactions.
 The redo logs would be as well.

for you see, if you flush some of the dirty blocks, we must now be able to undo
the changes to them.  In order to undo the changes, we need the undo data but
that as well is protected by redo -- meaning we need to apply the redo to get
the undo -- catch 22.

      Rating: 5
re: redo log
      buffer   June 29, 2003
Reviewer:
      A reader  from Houston
For the answer to the reader 'A reader  from Houston'

The redo logs are filled up and then copied over as archived redo logs, since
redo logs have uncommitted data, this uncommitted goes into archived redo logs.
Is that correct?
The uncommitted data present in the arched redo logs is copied over to a standby
database.  If we logon to the standby database, wouldn't it be showing
uncommitted data?  What am I missing here?

          Followup:

a physical standby is either

a) in managed recovery mode (you are not reading it)
b) opened read only (it is "recovered" to a point in time, you see only
committed work)

a logical standby uses SQL apply --meaning the redo is turned into
insert/update/deletes and is not visible to you until you COMMIT.

      Rating: 5
re: redo log
      buffer   June 29, 2003
Reviewer:
      A reader  from Houston
Thanks a lot.

There is so much activity on this site even on a weekend.  Don't you take a
break?

      Rating: 5
Redo logs and
      DDL'S  November 18, 2003
Reviewer:
      A reader
Tom,
Are DDL changes made to the redo logs?  Is redo and undo maintained for DDL
changes?

          Followup:

DDL just causes recursive sql to happen (inserts/updates/deletes to the real
data dictionary)

so yes, ddl makes changes to the redo stream since the recursive sql is just sql
and generates redo as normal

you can also enable supplemental logging (in current versions) to have the
actual DDL stored in the redo stream (used by standby databases and such).

      Rating: 5
Redo logs and
      DDLs  November 21, 2003
Reviewer:
      A reader

November 21, 2003 Michael -- Thanks for the question regarding
      "Converting numbers.", version 7.3.4

      You Asked
I need to be able to write a file that contains binary data.
This file used to be written by a COBOL program and this one
field was written using COMP-3.  How can I convert a number to a
COMP-3 style output.

Thanks.

Michael

      and we said...
After reseaching what the format of a comp-3 number is, we can
develop a package that looks like this.  It'll take either a
number or a string that is a number and convert it into comp-3
for you.  It can be called from SQL so you can use it in your
queries or you can just call it as a plsql function.  The
calling program would bind a RAW type to this column.

tkyte@ORA734.WORLD> create or replace package comp3
  2  as
  3      function convert( p_num in number   ) return raw;
  4      pragma restrict_references( convert,
  5                                  wnds, rnds, wnps, rnps );
  6      function convert( p_str in varchar2 ) return raw;
  7      pragma restrict_references( convert,
  8                                  wnds, rnds, wnps, rnps );
  9
  9      pragma restrict_references( comp3,
 10                                  wnds, rnds, wnps, rnps );
 11  end;
 12  /

Package created.

tkyte@ORA734.WORLD> create or replace package body comp3
  2  as
  3
  3  function convert( p_num in number   ) return raw
  4  is
  5  begin
  6      return comp3.convert( to_char(p_num) );
  7  end;
  8
  8
  8  function convert( p_str in varchar2 ) return raw
  9  is
 10      l_str    varchar2(1024);
 11      l_num    number default to_number( p_str );
 12
 12      -- could be either C or F according to the
 13      -- spec for comp-3 numbers
 14      l_sign   char(1) := 'C';
 15  begin
 16
 16      -- get rid of any +- by turning +- into - and
 17      -- then turning - into nothing.  strip
 18      -- any leading and trailing blanks as well
 19
 19      l_str := ltrim(
 20                 rtrim(
 21                   replace(
 22                      translate( p_str, '+-', '-' ), '-', ''
 23                          )
 24                      )
 25                    );
 26
 26      -- add the sign byte.  'D' is for negative.
 27      -- either C or F is acceptable for positive
 28      -- numbers
 29
 29      if ( l_num < 0 ) then
 30          l_str := l_str || 'D';
 31      else
 32          l_str := l_str || l_sign;
 33      end if;
 34
 34      -- make sure there are an even number of bytes
 35      -- for the Hext to RAW conversion:
 36
 36      if ( mod( length(l_str), 2 ) = 1 ) then
 37          l_str := '0' || l_str;
 38      end if;
 39      return hextoraw( l_str );
 40  end;
 41  end;
 42  /

Package body created.

Now test it against the output gleened from

http://www.room42.com/store/computer_center/packed_decimal.shtml
tkyte@ORA734.WORLD> column a format a10

tkyte@ORA734.WORLD> select comp3.convert( 123 ) a from dual;

A
----------
123C

tkyte@ORA734.WORLD> select comp3.convert( -4321 ) a from dual;

A
----------
04321D

tkyte@ORA734.WORLD> select comp3.convert( '+000050' ) a from
dual;

A
----------
0000050C

tkyte@ORA734.WORLD> select comp3.convert( '-7' ) a from dual;

A
----------
7D

tkyte@ORA734.WORLD> select comp3.convert( '00000' ) a from dual;

A
----------
00000C

Review &
      Followup

      Rating: 5
Good
      Stuff  October 16, 2003
Reviewer:  Ashraf
      Kazi  from Ireland
Tom,
I like the stuff you did !! Excellent.
Would you please let us know, how to convert a number with decimal Points eg.
-100.25

If I try converting this it gives Error. Numeric or value error.
Thanks & Regards,
Ashraf

          Followup:

comp-3's don't have decimal places

but anyway -- you would do what I described I did

research the format (no clue what format you are wanting) and then write a
snippet of code to do it.....

      Rating: 5
I will use your
      package in my project  November 21, 2003
Reviewer:
      Max Yao  from Toronto, Canada
Tom,

I am working on one project to extract Oracle data to a flat text file for
mainframe. In the data file there are three COMP-3 fields. I am going to modify
your package for my data extract.

I have a question for you. There is a column in the extract data file user
requirement. Its field size (format) is (09)v99 and its position is 203-208. The
user wants the field to be a COMP-3 field. F appears in the rightmost byte
indicating an absolute value. (09)v99 means there will have as many as 11
digits. But position 203-208 just can hold 6 digits.
I know in COMP-3 data type, one byte can store 2 digits. When I try to convert
an 11 digits number, your function returns 12 digits. In other words, the COMP-3
data will use 12 positions.

select comp3.convert( '12345678901' ) a from dual;

A
------------------------------------------------------------
12345678901C

Please help me to resovle the above format vs. positions problem.

Thanks a lot!

Max

          Followup:

(09)v99 means 11??  i see 4 digits, an implied decimal and a sign?  but it's
been since high school that I did cobol so.....

all i did was take the definition (url referenced up there) and codify the
definition.  I don't really know that much about comp3 beyond that.

      Rating: 5
comp-3 fields
        November 22, 2003
Reviewer:  a reader
       from UK
Hi Max,

In the packed format, each byte contains two decimal digits, except for the
rightmost byte, which contains the sign code in the right half.

Prabably you meant your field size (format) to be S9(09)v99 and its position is
203-208. The equivalent field size in oracle would be number(11,2). Which means
your data in ascii would look something like this.

-999999999.99 to +999999999.99

position (203-208) holds six bytes - each byte contains two decimal digits,
except the right most byte, which contain the sign code in the right half. That
means you can have 2n-1 digits plus the sign bit. Here n=6 (for the field
positins 203-208).

To fit in your example below your field size format should have been s9(11) and
not S9(09)v99
select comp3.convert( '12345678901' ) a from dual;

Thanks,

November 21, 2003 muhammad ahmad -- Thanks for the question
      regarding "largest number sequence", version 8.1.7

      You Asked
I have developer question...

Consider the following sample data:

SQL> select * from kas;

R           T1         T2         T3         T4         T5
--- ---------- ---------- ---------- ---------- ----------
R1         1.3          2       3.36        3.3          4
R2          23        2.5          2          2         34
R3          31          2          2          2        5.5
R4          23          6          2         22          3

Now I use a query to get the average of each column as

AVG       19.58     3.13       2.34       7.33      43.99

The results are accurate but I want the results in descending order, it does not
matter that a value is from t1 or t2 or so on.
( I want a query or some other that i use in the report builder { Just to
display })

The results should look like this:

43.99    19.58    7.33    3.13    2.34

--OR--

43.99
19.58
7.33
3.13
2.34

Whenever data changed the results should again formatted in descending order

/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\

Then I want another query that gives following results from the same data as
follows

"Every lowest values is subtracted from each of its highest value"

The results should look like this:

43.99 minus 19.58 results 24.21
43.99 minus 7.33 results 36.66
43.99 minus 3.13 results 40.86
43.99 minus 2.34 results 41.65

19.58 minus 7.33 results 12.25
19.58 minus 3.13 results 16.45
19.58 minus 2.34 results 17.24

7.33 minus 3.13 results 4.20
7.33 minus 2.34 results 4.99

3.13 minus 2.34 results 0.79

      and we said...
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( r char(2), t1 number, t2
number, t3 number, t4 number, t5 number );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec gen_data( 'T', 5 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

R          T1         T2         T3         T4         T5
-- ---------- ---------- ---------- ---------- ----------
jT 2133078201 7457735607 5118138692 1355387480 3702215649
f` 2490166671 6618151030 7772757320 2739736264 5131470896
Wi 6876325753 5747866798  351954032 3065548811 2411512446
nk 4883145883 1790015196 2691363264  256653111 8924466151
XB 1425179721 8553536204 3068665879 7321531755 5398934263

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select avg(t1) t1, avg(t2) t2, avg(t3) t3,
avg(t4) t4, avg(t5) t5
  2    from t
  3  /

        T1         T2         T3         T4         T5
---------- ---------- ---------- ---------- ----------
3561579246 6033460967 3800575837 2947771484 5113719881

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select r, decode( r, 1, t1, 2, t2, 3, t3, 4,
t4, 5, t5 ) data
  2    from (select avg(t1) t1, avg(t2) t2, avg(t3) t3, avg(t4) t4, avg(t5) t5
  3             from t),
  4          (select rownum r from all_objects where rownum <= 5)
  5   order by 1 desc
  6  /

         R       DATA
---------- ----------
         5 5113719881
         4 2947771484
         3 3800575837
         2 6033460967
         1 3561579246

so, thats question one and then...

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select data,
  2          lead(data,1) over (order by data DESC) one_ahead,
  3          data-lead(data,1) over (order by data DESC) one_ahead_diff,
  4          lead(data,2) over (order by data DESC) two_ahead,
  5          data-lead(data,2) over (order by data DESC) two_ahead_diff
  6    from (
  7  select r, decode( r, 1, t1, 2, t2, 3, t3, 4, t4, 5, t5 ) data
  8    from (select avg(t1) t1, avg(t2) t2, avg(t3) t3, avg(t4) t4, avg(t5) t5
  9             from t),
 10          (select rownum r from all_objects where rownum <= 5)
 11          )
 12  /

      DATA  ONE_AHEAD ONE_AHEAD_DIFF  TWO_AHEAD TWO_AHEAD_DIFF
---------- ---------- -------------- ---------- --------------
6033460967 5113719881      919741086 3800575837     2232885130
5113719881 3800575837     1313144043 3561579246     1552140635
3800575837 3561579246      238996592 2947771484      852804353
3561579246 2947771484      613807762
2947771484

shows the way to get to q2 -- here is the whole query:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select data,
  2          lead(data,1) over (order by data DESC) one_ahead,
  3          data-lead(data,1) over (order by data DESC) one_ahead_diff,
  4          lead(data,2) over (order by data DESC) two_ahead,
  5          data-lead(data,2) over (order by data DESC) two_ahead_diff,
  6          lead(data,3) over (order by data DESC) three_ahead,
  7          data-lead(data,3) over (order by data DESC) three_ahead_diff,
  8          lead(data,4) over (order by data DESC) four_ahead,
  9          data-lead(data,4) over (order by data DESC) four_ahead_diff
 10    from (
 11  select r, decode( r, 1, t1, 2, t2, 3, t3, 4, t4, 5, t5 ) data
 12    from (select avg(t1) t1, avg(t2) t2, avg(t3) t3, avg(t4) t4, avg(t5) t5
 13             from t),
 14          (select rownum r from all_objects where rownum <= 5)
 15          )
 16  /

      DATA  ONE_AHEAD ONE_AHEAD_DIFF  TWO_AHEAD TWO_AHEAD_DIFF THREE_AHEAD
THREE_AHEAD_DIFF FOUR_AHEAD FOUR_AHEAD_DIFF
---------- ---------- -------------- ---------- -------------- -----------
---------------- ---------- ---------------
6033460967 5113719881      919741086 3800575837     2232885130  3561579246
2471881721 2947771484      3085689483
5113719881 3800575837     1313144043 3561579246     1552140635  2947771484
2165948397
3800575837 3561579246      238996592 2947771484      852804353
3561579246 2947771484      613807762
2947771484

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

Review &
      Followup

      Rating: 5
Das ist
      hammer  October 16, 2002
Reviewer:  k

SQL is powerfull.

      Rating: 3
Not Exact
      Format  October 17, 2002
Reviewer:  Muhammad
      Ahmad IMran  from pakistan
Hi Tom,

 You probably not read the last lines of my question, Please review and see what
i want here thanks for answering.

The result should look like this:

43.99 minus 19.58 results 24.21
43.99 minus 7.33 results 36.66
43.99 minus 3.13 results 40.86
43.99 minus 2.34 results 41.65

19.58 minus 7.33 results 12.25
19.58 minus 3.13 results 16.45
19.58 minus 2.34 results 17.24

7.33 minus 3.13 results 4.20
7.33 minus 2.34 results 4.99

3.13 minus 2.34 results 0.79

          Followup:

ops$tkyte@ORA920.US.ORACLE.COM> select * from t;

R          T1         T2         T3         T4         T5
-- ---------- ---------- ---------- ---------- ----------
R1        1.3          2       3.36        3.3          4
R2         23        2.5          2          2         34
R3         31          2          2          2        5.5
R4         23          6          2         22          3

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select data || ' minus ' ||
  2         decode( r2, 1, one_ahead, 2, two_ahead, 3, three_ahead, 4,
four_ahead ) ||
  3         ' results ' ||
  4         decode( r2, 1, one_ahead_diff, 2, two_ahead_diff,
  5                     3, three_ahead_diff, 4, four_ahead_diff )
  6  from (
  7  select data,
  8          lead(data,1) over (order by data DESC) one_ahead,
  9          data-lead(data,1) over (order by data DESC) one_ahead_diff,
 10          lead(data,2) over (order by data DESC) two_ahead,
 11          data-lead(data,2) over (order by data DESC) two_ahead_diff,
 12          lead(data,3) over (order by data DESC) three_ahead,
 13          data-lead(data,3) over (order by data DESC) three_ahead_diff,
 14          lead(data,4) over (order by data DESC) four_ahead,
 15          data-lead(data,4) over (order by data DESC) four_ahead_diff
 16    from (
 17  select r, decode( r, 1, t1, 2, t2, 3, t3, 4, t4, 5, t5 ) data
 18    from (select avg(t1) t1, avg(t2) t2, avg(t3) t3, avg(t4) t4, avg(t5) t5
 19             from t),
 20          (select rownum r from all_objects where rownum <= 5)
 21          )
 22  ), ( select rownum r2 from all_objects where rownum <= 4 )
 23  where decode( r2, 1, one_ahead, 2, two_ahead, 3, three_ahead, 4, four_ahead
) is not null
 24  order by data desc,
 25         decode( r2, 1, one_ahead, 2, two_ahead, 3, three_ahead, 4,
four_ahead ) desc
 26  /

DATA||'MINUS'||DECODE(R2,1,ONE_AHEAD,2,TWO_AHEAD,3,THREE_AHEAD,4,FOUR_AHEAD)||'RE
SULTS'||DECODE(R2,1,ONE_AHEAD_DIFF,2,TWO_AHEAD_DIF
---------------------------------------------------------------------------------
--------------------------------------------------
19.575 minus 11.625 results 7.95
19.575 minus 7.325 results 12.25
19.575 minus 3.125 results 16.45
19.575 minus 2.34 results 17.235
11.625 minus 7.325 results 4.3
11.625 minus 3.125 results 8.5
11.625 minus 2.34 results 9.285
7.325 minus 3.125 results 4.2
7.325 minus 2.34 results 4.985
3.125 minus 2.34 results .785

10 rows selected.

just a variation on a theme -- we already saw how to transpose in there, we
just needed to get each row pumped out 4 times, keep the ones we wanted...

      Rating:
      4
  October 19, 2002
Reviewer:  imran
       from pakistan
Hi tom thanks for helping me would you tell me in details how this query works.

          Followup:

I did it step by step -- just work through how it builds on itself.

I use common tricks like "join to a table with 4 rows using a cartesian product"
to turn each row into 4 rows (so we can use DECODE and such to pivot the
results).

It really isn't that tricky -- just take the example and watch the queries build
as we go through -- note what was added and you'll see what I did.  Very common
pivots basically.

      Rating: 4
SQL is really
      powerful  May 24, 2003
Reviewer:  Neeha
       from India
TOM Sql is really powerfull I read this question and learn a lot of things

from your first follow up I quote this

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( r char(2), t1 number, t2
number, t3 number, t4 number, t5 number );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec gen_data( 'T', 5 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

R          T1         T2         T3         T4         T5
-- ---------- ---------- ---------- ---------- ----------
jT 2133078201 7457735607 5118138692 1355387480 3702215649
f` 2490166671 6618151030 7772757320 2739736264 5131470896
Wi 6876325753 5747866798  351954032 3065548811 2411512446
nk 4883145883 1790015196 2691363264  256653111 8924466151
XB 1425179721 8553536204 3068665879 7321531755 5398934263

WOULD YOU LIKE TO POST THE CODE FOR procedure
gen_data( 'T', 5 )

I think it is helpful for me

Thanks in advance

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2151576678914

      Rating: 5
Another
      way.  May 26, 2003
Reviewer:  Saibabu
      Devabhaktuni  from Palo Alto, CA USA
Here is another kind of sql query for the first question of original thread:

select substr(value,instr(value,' ',1,row_num)+1,
(instr(value,' ',1,row_num +1)-instr(value,' ',1,row_num))-1)
from
 (select row_num,' '||avg(t1)||' '||avg(t2)||' '||avg(t3)||' '||avg(t4)||'
'||avg(t5)||' ' value
 from
 kas,(select rownum row_num from all_objects where rownum  <=5))
order by 1 desc;

      Rating:
      5
Correction.  May 26, 2003
Reviewer:
      Saibabu Devabhaktuni  from Palo Alto, CA uSA
Sorry, here is the corrected query:

select substr(value,instr(value,' ',1,row_num)+1,
(instr(value,' ',1,row_num +1)-instr(value,' ',1,row_num))-1)
from
 (select row_num,' '||t1||' '||t2||' '||t3||' '||t4||' '||t5||' ' value
 from
 (select avg(t1) t1,avg(t2) t2,avg(t3) t3,avg(t4) t4,avg(t5) t5 from
kas),(select rownum row_num from all_objects where rownum  <=5))
order by 1 desc;

      Rating:
      5
Q  November 21, 2003
Reviewer:  USER
       from NY, USA
Can we use lead function to display

1 A
1 B
1 C
2 B
2 E
3 F
4 Y
4 u
4 I

to

1 A$$B$$C
2 B$$E
3 F$$
4 Y$$U$$I

?

          Followup:

search this site for stragg...

      Rating: 5
just a minor
      question  November 21, 2003
Reviewer:
      Sudhir  from Lexington, KY
From your note

"ops$tkyte@ORA817DEV.US.ORACLE.COM> select r, decode( r, 1, t1, 2, t2, 3, t3, 4,

t4, 5, t5 ) data
  2    from (select avg(t1) t1, avg(t2) t2, avg(t3) t3, avg(t4) t4, avg(t5) t5
  3             from t),
  4          (select rownum r from all_objects where rownum <= 5)
  5   order by 1 desc
  6  /

         R       DATA
---------- ----------
         5 5113719881
         4 2947771484
         3 3800575837
         2 6033460967
         1 3561579246
"

Did you mean 'order by 2' ? because DATA values are not really bigger to smaller

November 21, 2003 vrishali -- Thanks for the question regarding
      "Few questions regarding oracle and unix", version 8.1.6.2.0

      You Asked
Hi Tom,

   I would like to ask you few questions.

1. On multiple cpu operating sytem(Solaris 2.6) is it possible
to find out which oracle process is running on which cpu?
Is it possible to distribute processes on multiple cpus
manually?

2. We are not using replication but still top command in unix
shows that  ora_snp0..n_SID process is taking 40% of cpu
Why it takes so much cpu?

3. Is there any documentation which explains the use of

 oracle 19882     1  6 19:44:47 ?       113:44 oracleDIALOG
(DESCRIPTION=(LOCAL=no)(ADDRESS=(PROTOCOL=BEQ)))

this process is also taking  lot of cpu.
If it is bad how to prevent it?

4.  oracle 19882     1  6 19:44:47 ?       113:44 oracleDIALOG
(DESCRIPTION=(LOCAL=no)(ADDRESS=(PROTOCOL=BEQ)))

how this process is different from

 oracle 19882     1  6 19:44:47 ?       113:44 oracleDIALOG
(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=BEQ)))

5. I know that by usning dbms_resource_manger package I can
allocate different % of cpu to different user. Is there any
method in Oracle which can be useful to allocate different % of
memory to different Oracle User.

Thanks
Vrishali

      and we said...
1) the OS will schedule the various Oracle processes on the
CPU's as it sees fit.  On Solaris, you can use a GNU tool like
"top" to see whats on the CPU right now.  A given process will
hop from cpu to cpu continously.

While you techinically could use processor affinity to force the
OS to run a process on a given CPU this would be ill advised and
could lead to severe problems.  It is neither recommended nor
supported.  The OS schedules things best.

In Oracle8i, you do have resource profiles.  Using these, if you
goal is to ensure prioritization or make sure some users don't
consume excessive resource -- you can constrain groups of users
to certain amounts of CPU usage.

2) What does:

select job, what from user_jobs;

return?  snp is the process that runs jobs in the job queue.
Replication uses this but you (or other apps you are running)
will too.  See what jobs you have running in the system.  They
are run by snp.  snp will accumulate these jobs cpu times.

3) that is the Oracle dedicated server.  The concepts manual
explains the architecture in some detail -- this process maps to
the dedicated server process.

it is normal for this process to have lots of cpu associated
with it.  It is the process the does your "selects, inserts,
updates, deletes" and so on.  It is what responds to all of your
sql commands for a given session.

4) local=no means it was spawned via Net8 (over the network)
whereas local=yes means is was spawned by a process that is
Local to the machine (the client, say sqlplus, is running on the
server).

consider this example:

$ sqlplus scott/tiger

SQL*Plus: Release 8.1.6.0.0 - Production on Tue Jan 23 19:02:31
(c) Copyright 1999 Oracle Corporation.  All rights reserved.
Connected to:
Oracle8i Enterprise Edition Release 8.1.6.2.0 - Production
With the Partitioning option
JServer Release 8.1.6.2.0 - Production

scott@DEV816> select spid
  2    from v$process
  3   where addr = ( select paddr
  4                    from v$session
  5                   where audsid = userenv('sessionid')
  6                )
  7  /

SPID
---------
10063

scott@DEV816> !ps 10063
   PID TT       S  TIME COMMAND
 10063 ?        S  0:00 oracleora816dev
(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))

I logged in and found the process id of my dedicated server.
I ps it and find "local=yes".  I did not use net8, I used a
direct connection.  Now, I do it again:

$ sqlplus scott/tiger@ora816dev.us.oracle.com

SQL*Plus: Release 8.1.6.0.0 - Production on Tue Jan 23 19:02:55
(c) Copyright 1999 Oracle Corporation.  All rights reserved.
Connected to:
Oracle8i Enterprise Edition Release 8.1.6.2.0 - Production
With the Partitioning option
JServer Release 8.1.6.2.0 - Production

scott@DEV816> @getspid

SPID
---------
10074

scott@DEV816> !ps 10074
   PID TT       S  TIME COMMAND
 10074 ?        S  0:00 oracleora816dev
(DESCRIPTION=(LOCAL=no)(ADDRESS=(PROTOCOL=BEQ)))

and since I used net8, local=no is true.  I'm coming in via
the listener now.

5) memory is doled out on an as needed basis.  If a session
needs 5meg of ram to do something -- it needs it.  RAM is not
like CPU.  CPU can be given out over an extended period of time.
 The end result is the same -- it just takes more or less time.
RAM must either be there -- or not.  If there is no more RAM, we
cannot run.  It is not like CPU.

Review &
      Followup

      Rating: 5
Tks very much
      !!!  November 21, 2001
Reviewer:  Sven
      Bleckwedel  from Brazil
Tom,

Your explanation about the dedicated server processes (and also the local= [ yes
| no ] ) was very useful to me, to understand what was happening with my
server...

Sven

      Rating:
      5
  November 18, 2003
Reviewer:  Amit
       from Ohio
Hi Tom,

One of my net8 ( LOCAL=NO) processes is taking up all of my CPU resource.

On doing top , I get this :

   PID USERNAME THR PRI NICE  SIZE   RES STATE    TIME    CPU COMMAND
 15171 oracle     1  20    0    0K    0K run    190.8H 99.02% oracle

On ps -ef , I get this :

 oracle 15171     1 99   Nov 10 ?       11446:32 oracleGSO (LOCAL=NO)

Infact, my ps -ef shows a host of other net8 processes but I know that no one is
logged in.

My typical users are  Forms Client and internal web users.

Is there a way I can figure out why the process didnt get terminated or what is
it currently executing ?

as an urgent measure can I kill any thread with LOCAL=No ?

Thanks,

Amit

          Followup:

someone is logged in -- you just proved it!

have you identified the session in v$session? then alter system kill session.

      Rating: 5
More info on
      above  November 21, 2003
Reviewer:  Amit
       from Ohio
Hi Tom,

I did figure out the session ID. Infact using OEM, I also figured out that this
is a web client(PROGRAM=JDBC Thin Client).

The SQL it is  executing(still !!! ) is :

SELECT count(*)
    FROM s_basic_info a, s_admission_info b, s_application_detail c
    WHERE a.student_id = b.student_id
      AND a.student_id = c.student_id
      AND quarter_apply_enroll = :b2
      AND c.file_completed_date IS NOT NULL
      AND b.admit_code IS NULL
      AND a.major = :b1

and on the statistics side :

Recursive calls = 90232008
recursive cpu usage= 86681075

System uga memory=209240
system uga memory max=209240

Does the above give any hint ?

The above query is part of a PL/SQL procedure which has other queries too, and I
might not have used bind variables in all
of them ( I wrote it before I started reading asktom :) )

I can kill this one, but what can I do to avoid its occurring again ? Is my uga
memory ok ? If not, how can I increase it.

Thanks

Amit

          Followup:

it is almost impossible to NOT use binds correctly in PLSQL -- that is the
beauty of that language!

You can use resource profiles in the database to limit the amount of resources a
session can use.

November 21, 2003 Alex -- Thanks for the question regarding "10046
      and tkprof", version 8.1.7

      You Asked
Hello Tom,

Thanks for the great job that you are doing maintaining this site.
My question is : Can I use tkprof to format trace file generated by setting
event 10046 ? I checked couple of sites ( www.hotsos.com and www.oraperf.com )
and seems like that they advice to read the "raw" trace file generated instead
of formatting it first with tkprof. What is your take on that ?

Regards,
Alex

      and we said...
If you have my book "Expert one on one Oracle", I walk through this in detail.

There I explain the 10046 event and describe how to read the raw trace file.  If
you use tkprof in 8i and before, it'll ignore the binds and wait events in
there.

If you use 9i, it'll show you the wait events IN the tkprof report (very nice).
You still have to mine the raw trace file to get the binds tho.
Review &
      Followup

      Rating:
      5
Thanks  October 17, 2002
Reviewer:
      Alex  from NY
Thanks for the reply. I will get your book today and read it ( I should have
done this long time ago ).

      Rating: 2
But waits do
      not show up individually  October 17, 2002

Reviewer:  Areader  from LA
Tom,

Please correct me if I am wrong, but doesn't the tkprof output show ONLY the
summary of wait events - how many times each occured, etc. In order to diagnose
a problem - say to find out the segments experiencing buffer busy waits, we
still have to look at teh raw trace files and somehow parse the WAIT lines. The
purpose of 10046 event is to provide the segment related information, and tkprof
does not provide that. Or is there some setting in tkprof I'm missing?

          Followup:

It shows you -- at a glance -- what wait is causing the issue.

Do you want to look at 1000 lines of trace, with dozens of wait events and
figure out "ok, which one is the really important one?"

Not me, 9i's tkprof is really nice like that.  Most of the time all you need to
know is "I waited on X".  Most of the time - given that -- you are well on your
way to fixing "X".

Yes, you can go back to the trace and get the hundreds or thousands of lines to
see the details when and if you like.

      Rating: 4
Can you see
      wait events with 9.0.1.4.0 tkprof?  January 20, 2003

Reviewer:  Matt  from Australia
I don't seem to be able to see wait events in my tkprof output. say for instance
I have  an output like:

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.72       1.98        508        523          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.73       1.98        508        523          0           1

from tkprof. I expect that the difference between CPU total and elapsed total
(there are examples of larger differences)is attributed to waits. However, I
don't see any in the trace files nor in the tkprof output. Might this be a DB
configuration issue (my guess is that the customer who sent me the trace has
some init.ora parameter unset (timed_stats maybe) so these are not included in
the trace).

BTW: The app enforced times_statistics=true as the session level when switching
on tracing.

Any idea what the problem is?

          Followup:

you need to use the 10046 trace event to get waits in the trace file.

alter session set events '10046 trace name context forever, level 12'

instead of sql_trace=true;

      Rating: 3
Makes
      Sense  January 21, 2003
Reviewer:  Matt
       from Australia
I inferred from your comment above that this came out of the box with tkprof and
sql_trace=true. I now understand that the 10046 event needs to be set.

I had a search through the 9.0.1.4.0 doco and could not find a reference to this
event. Can you reference some Oracle Corp documentation? (I already have your
book :o) )

What is the difference between sql_trace=true and the 10046 event in terms of
performance degradation of the process? (I can hear what you have to say about
this comment already)

What I mean is, I have a choice between event 10046 tracing and sql_trace (only
one of which seems to be formally documented by Oracle). Which do I choose?
Typically you don't know you need the WAITS until you see a trace.

Is there any reason NOT to always generate a 10046 trace?

Thanks and Regards

          Followup:

It is an undocumented but widely acknowledged feature...

the 10046 trace can be orders of magnitude larger then sql_trace=true.  I do it
when I need it, using sql_trace=true for most stuff.

      Rating: 3
Additional to
      previous Post  January 21, 2003
Reviewer:
      Matt  from Australia
That should have read:

"Is there any reason NOT to always generate a 10046 trace when investigating
performance issues?"

      Rating: 4
What
      granularity for waits ?  July 04, 2003
Reviewer:
      Adrian Billington  from UK
I've been looking at waits in tkprof and it's very useful information, but I
can't find anywhere the granularity of the waits timings - if you don't know how
long you've waited, then the information is of no use ;o)

Could you tell me the 9.2.0 wait time unit ?

Thanks

      Rating: 5
Sorry, just
      found it !!!  July 04, 2003
Reviewer:  Adrian
      Billington  from UK
Ignore last review, I just found it on one of your pages...

Adrian

      Rating: 5
How to read the
      raw trace file  October 16, 2003
Reviewer:
      A reader
Hi Tom,
I have the session trace on with bind variable option on the Oracle APPS.
The following is from the trace file:
How do we match b1:, b2:..?
bind 1 had value=1, but non for other bind x

PARSE #52:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=3552165300
BINDS #52:
 bind 0: dty=1 mxl=2000(2000) mal=00 scl=00 pre=00 oacflg=13 oacfl2=1 size=2000
offset=0
   bfp=02060f30 bln=2000 avl=00 flg=09
 bind 1: dty=2 mxl=22(22) mal=00 scl=00 pre=00 oacflg=13 oacfl2=1 size=24
offset=0
   bfp=02075470 bln=22 avl=02 flg=09
   value=1
 bind 2: dty=1 mxl=32(00) mal=00 scl=00 pre=00 oacflg=13 oacfl2=1 size=32
offset=0
   bfp=00000000 bln=32 avl=00 flg=09
 bind 3: dty=2 mxl=22(22) mal=00 scl=00 pre=00 oacflg=13 oacfl2=1 size=24
offset=0
   bfp=0206ff34 bln=22 avl=00 flg=09
EXEC #52:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=1,og=4,tim=3552165301
=====================
PARSING IN CURSOR #52 len=89 dep=1 uid=173 oct=3 lid=173 tim=3552165301
hv=3341915018 ad='8ce97ba0'
SELECT TO_CHAR(:b1)   FROM PROJWBS@P3E WBS  WHERE WBS.WBS_ID = :b1  AND
WBS.PROJ_ID = :b3
END OF STMT
PARSE #52:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=1,og=4,tim=3552165301
EXEC #52:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=3552165301
FETCH #52:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=3552165301
=====================
PARSING IN CURSOR #53 len=51 dep=1 uid=173 oct=3 lid=173 tim=3552165302
hv=4266541333 ad='8ce97a70'
SELECT COUNT(*)   FROM TASK@P3E  WHERE WBS_ID = :b1
END OF STMT

          Followup:

ops$tkyte@ORA920> variable x varchar2(2000)
ops$tkyte@ORA920> variable y number
ops$tkyte@ORA920> variable z varchar2(32)
ops$tkyte@ORA920> variable a number
ops$tkyte@ORA920> exec :y := 1

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> @trace

Session altered.

ops$tkyte@ORA920> select :x, :y, :z, :a from dual;

:X
---------------------------------------------------------------------------------
--------------------------------------------------
        :Y :Z                                       :A
---------- -------------------------------- ----------

         1

PARSE #3:c=1953,e=955,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=0,tim=1041339395494142
BINDS #3:
 bind 0: dty=1 mxl=2000(2000) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=2000
offset=0
   bfp=4051661c bln=2000 avl=00 flg=05
 bind 1: dty=2 mxl=22(22) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=80
offset=0
   bfp=4051bee0 bln=22 avl=02 flg=05
   value=1
 bind 2: dty=1 mxl=32(32) mal=00 scl=00 pre=00 oacflg=03 oacfl2=10 size=0
offset=24
   bfp=4051bef8 bln=32 avl=00 flg=01
 bind 3: dty=2 mxl=22(22) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=0
offset=56
   bfp=4051bf18 bln=22 avl=00 flg=01
EXEC #3:c=0,e=357,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=1041339395495142

they were just null, no values.

      Rating: 5
time unit of
      cpu/parse seconds in tkprof output (not raw trace
      file)  November 21, 2003
Reviewer:
      A reader
Tom
what are the time unit of cpu seconds and/or parse
seconds? Are they in seconds? If you can answer for
8i and 9i, it would be great!

thanx a bunch!!

          Followup:

in tkprof -- seconds.

November 21, 2003 Scott -- Thanks for the question regarding
      "Package/Session State Management - Mod PL/SQL", version 8.1.7

      You Asked
G'day Tom

Boy, you're a hard man to catch at the moment - even harder for us Aussies!

I have a question regarding Package/Session State Management of the PL/SQL
Gateway.

Our current setting is Stateless (Reset package state).
Sometimes, I can find this painful when trying to communicate between pages.

I'm curious to know if you have an opinion regarding any problems, programming
quirks etc between resetting the package state and preserving it.
I'm a fan of using persistant variables in packages, but being new to Mod
PL/SQL, I fear there may be some pitfalls with using this method.

And what is the difference between preserving the package state, and the
"stateful" option?
Would stateful affect connection pooling? (we are operating with a multiple user
system) And retaining information in package body global variables specific to
each user?

Thanks.

      and we said...
The entire beauty of a Ntier architecture is the scalability you can achieve --
IF you remain pretty much entire STATELESS.

Keep a state -- that means you are chomping up resources on my machine when you
are NOT even using it!

I've never used the "stateful" mode of mod_plsql -- ever.  I do not recommend
it.

here is what we do on asktom.  you get a session id.  We make it so the
sessionid is pretty much unique to you (you cannot kidnap someones session).  We
have a state table that is keyed by -- well, that sessionid.  You want to keep a
state, we keep it in a table.

I know, that is an select or an update or an insert for each page view -- but,
you know databases where born to read and write data -- so I use them that way!

As you work through a question on this site, we store various bits and pieces in
a table for you.  when you are done, we move it from table "state" to
"real-table".

works very nicely, scales awesome, I can bounce the webserver without killing
sessions, you do not use my machine EXCEPT when I'm generating a page for you --
it is the way I think it should be done.

if you go stateful, you'll bind a SESSION to a mod_plsql instance -- one to one.
 You have a thousand concurrent users, you would need 1000 sessions.  Using
stateless, I might have the same 1000 concurrent users with 10 sessions.

Review &
      Followup

      Rating:
      5
stateful  February 12, 2003
Reviewer:
      mo
Tom:

how do you do this:
<I can bounce the webserver without killing
sessions>

Thanks,

          Followup:

apachectl stop
apachectl start

I don't have stateful sessions maintained in the middle tier.  In fact -- I can
bounce my entire SERVER -- apache, database, os, everything and you might never
ever know from page to page -- you would get an error if you tried to hit a page
whilst I was rebooting but eventually you would be right back where you left
off.  I am stateless.  my state persists in this thing called a database.  As
long as the database is there, my session is OK.

      Rating: 3
Not quite
      understood  February 12, 2003
Reviewer:  Scott
      Wesley  from Perth, Western Australia
G'day

When you say
"We make it so the sessionid is pretty much unique to you (you cannot kidnap
someones session)"
and
"Using stateless, I might have the same 1000 concurrent users with 10 sessions"
I must be getting confused with your terminology, because these two statements
seem to contradict each other.
It is my understanding the your sessionid forms part of the address at AskTom,
so I presume when you bounce the server when I'm on one page, and then I
navigate to the next, the sessionid you store in that table you refer to
remains.

Does the statement
"I might have the same 1000 concurrent users with 10 sessions"
still hold true when people are signing on with different usernames?

And I'm not sure you covered the difference between
Stateless (Reset package state)
and
Stateless (Preserve package state)
and the use of package global variables.
I think I understand why not to use Stateful, so I'll avoid that...

Thanks.

          Followup:

I should have said with 10 DATABASE SESSIONS -- sorry about that.

Each user is assigned a sessionid.

they share about 10 database sessions -- regardless of the number of asktom
users there are at a point in time.

as for the stateless -- that is all i use.  I don't know what you mean by
"preserve package state".  stateless means -- "package variables gone, wiped
out"

      Rating:
      4
Clarification  February 12, 2003
Reviewer:
      Scott Wesley  from Perth, Australia
G'day

I thought that was what you meant.
My understanding is that after a page is generated for me, that db session is
not needed anymore, and can be used by someone else. When I navigate to another
page, I grab a spare db session to generate the code for me.

You said the assigned sessionid is inserted into a state table.
If you are sharing sessions, is the only way to obtain this information from the
state table through the sessionid parameter passed to f?
(this is the alternative to global package variables, I presume, and also why
you have a bookmark link).

And as a clarification, when I refered to preserve package state, in the DAD
admin,
/pls/<dad_name>/admin_/adddad.htm?<dad_name>
There is an option for Package/Session State Management.
In this poplist, there is 3 options...
1) Stateless (Reset package state)
2) Stateless (Preserve package state)
3) Stateful

All the information I found in doco is in the help:
/admin_/help/feature.htm#1018347
<quote>
1.2.2 Stateless (Preserve Package)
In this state, the mod_plsql calls htp.init after each request to only clear OWA
packages, and not any application-specific variables and cursors. Furthermore,
an implicit commit is done if there are no errors in processing the request.
PL/SQL applications are responsible for cleaning out the session at the end of
request, but are not responsible for issuing commit in their code.
</quote>
Mind you, in the last sentence, it's a little ambiguous to whether sessions are
reused by other users or not - but it does say variables are retained - but
thinking further, it said app specific, not user specific - which kinda relates
to our initial testing.

By the way, to let you know, I've been receiving two e-mails each time for your
updates to the question.

Thanks.

          Followup:

yes, the sessionid is maintained by the browser and sent back to us, so we can
"rejoin" our state -- our data in the table.

#2 looks very very dangerous.  I would utterly and entirely 100% discourage it's
use in 100% of all cases.  I never speak in absolutes -- (hey, I just did tho,
didn't I) but this time I will - that is the most dangerous thing I've ever
heard of.  Don't do it.

The variables, transactions -- everthing are "saved" and reused by anyone that
comes along.

You recieve one email from me, and one email from you.  If you update this q
with a review -- since you FILED the question -- that counts as an "update" to
the question ;)

My software doesn't know its really you -- as the reviews thingy doesn't ask for
an email address -- so I've no way to know that it is you following up your own.

      Rating: 3
Real
      table?  February 13, 2003
Reviewer:  Vikas
      Sharma  from Delhi India
Hi Tom,

Please explain

"  when you are done, we move it from table "state" to "real-table".

i know about state table but what is the real table why do you store values from
the state table to real table.

Reagrds

Vikas Sharma

          Followup:

when you go through the process of

o asking me a question
o shopping on amazon
o filling out a complex form on the web

you generate a "state".  when you ask me a question -- It takes two or three
windows.  You give me basic information -- then the question -- then you review
the question.  When you are happy with the question, you submit it.

It is at that point it moves from the STATE table (where it is just "stuff you
are working on") into my "real" question table where I for the first time see
it.  It is not a real question until it is in the "real" quesiton table.  Before
then it is just stuff in the state table.

      Rating:
      5
statefull  February 13, 2003
Reviewer:
      mo
Tom:

1. As a developer, can I query something that tells me how the mod_plsql is
configured in an instance.
1) Stateless (Reset package state)
2) Stateless (Preserve package state)
3) Stateful

2.  You always recommend to use #1?

3.  When you say you maintain state in a database table by tracking session id
and state.  If you have 1000 users and 10 database sessions would the 1000 users
have the same 10 session ids or each one would have a unique number since they
all using 10 sessions.

4. Do you only have two columns in your session table (session_id,state)?  Do
you update the state every time the user calls a page? Do you save also variable
values from each page? any sample record for you state table?

5. Just curious, why do people implement J2EE 4 tier complex stuff (like amazon)
when you can do it simply in the database (two tier no app server)? why do you
need to have an app server that maintains state and burden the system with a lot
of resources when you can do it simply like your way? Is it lack of knowledge on
their behalf (going with the tech whiz) or there is actual benefits that I am
not aware off?

Thank you,

          Followup:

1) you have to peek at the config files for mod_plsql

2) yes

3) they would each and every one have a separate, non-guessable, unique session
identifier.  so their "state" is "their state", not "our state"

4) depends, sometimes we have a row

   sessionid   column1  column2 column3

using marvel (the software behind asktom) we have

   sessionid variable_name variable_value

so we can generically store as many name/vale pairs as we need.

we update the state when the state changes -- only "changed values" are
saved/resaved (updated)

5) you eventually would run out of steam.  mod_plsql goes far -- very very far
-- much farther then many (i would suggest even most but that is an opinion)
then most people ever need to go.

amazon -- they would not be able to do it all without a monolithic architecture.

Asktom -- it would be silly to have 14 tiers for such a thing.

It is a matter of using the right tool for the right job, but most people only
know how to use a "tool" so that is what they use for all jobs.

      Rating:
      5
state  February 13, 2003
Reviewer:
      mo
Tom:

<It is a matter of using the right tool for the right job, but most people only
know how to use a "tool" so that is what they use for all jobs.>

This is exactly what I am asking. How do you know which tool is the right answer
for project. For example why would not you design AMAZON with several mod_plsql
rather than several app servers?

2. in your 10,000users/10 sessions config what happens when 50 people call a
page at the same time? DO you increase sessions to 50 or persons will wait till
the other is finished?

Thanks,

          Followup:

because mod_plsql means everything -- 100% of everything is running in the DB.
It would most likely run out of steam for oh say, 1 billion users or so ;)

It is a matter of scale.  I know on my 4 cpu hardware (from 1996/97 btw -- not
state of the art at all) I know I can do about 16page views/second (our
threshold is about 0.25 cpu seconds per page) if they arrive randomly....

I could get that to 100's or 1,000's of hits per seconds with the right hardware
-- clustering and design, but I would probably hit a wall eventually (only so
many nodes in a rac cluster, only so many cpu seconds on a single machine).

Now, if I can front end that with an infinite number of app servers -- even
though individually they cannot do as much as a single database could do - they
can scale out better in this extreme case.

2) depends on the machine -- can the machine do 50 concurrent sessions without
getting saturated?

      Rating: 5
clean
      up  February 13, 2003
Reviewer:  Scott
      Wesley  from Perth, Western Australia
Thankyou for your assistance.

When do you clean up your state table?

ie - a user has browsed for a while, asked a question, you've used his/her
sessionid in the background, but once the user closes the browser, the sessionid
is still in the state table.
When is it cleaned? Does it expire after a while?

          Followup:

expires after a bit...  job cleans it out

      Rating: 4
Do it using
      cookies  February 16, 2003
Reviewer:  Gabriel
      Ilarda  from Perth, Australia
Thought I'd mention I maintain session states using a cookie. I store the
username, password (encrypted)that was entered in at login, and the expiry time
(15 minutes) in the cookie and at the beginning of every procedure I check that
the cookie hasn't expired. If it has then the user views the 'Your session has
expired message.' If not they are allowed to keep surfing and the expiry time is
reset. I store the password in the cookie because I pass the username through
every procedure (sort of like a session ID) and then at the start of each
procedure when it checks if the cookie has expired, it also checks that the
password in the cookie matches the username passed into the procedure. This is
primarily done to stop if a user logs in and figures out how to call my
procedure, and they go ahead and try to call it passing in a different username
in order to get different access to my system. My procedures will check the
password in the cookie and if it doesn't match the username that is passed to
the procedure they are thrown out of the system. Probably could have done this
using a table but system working fine and I developed this a while ago when i
couldn't find much help on mod_plsql. Might change it in the future if I think
of using tables to stores session variables.

      Rating: 3
mod_plsql --
      who developed it ?  August 28, 2003
Reviewer:
      Robert  from CT
Tom, do you know who developed mod_plsql ?
I thought  it's Oracle Corp until I hit these 2 links:

http://www.total-knowledge.com/progs/mod_plsql/
http://freshmeat.net/projects/mod_plsql/?topic_id=66%2C92

          Followup:

something near and dear to me...

Here is the history.....

in the beginning there was this thing called the "WOW gateway" (Web Oracle Web).
 It was a cgi-bin that crudely translated a url into a stored procedure call
(didn't use bind variables!!! that is why I call it "crude")....  It was written
originally by Magnus Lonnroth of Sweeden.

This became OWA (Oracle Web Agent) in version 1 of OIS (the Oracle Internet
Server).  It too was a cgi-bin.

Later, with version 2 of OWS (OIS -> OWS, the Oracle Web Server), this OWA
became a "cartridge" in OWS speak.  Like a module for apache but ran in the
spyglass mosaic webserver (which is what we used).

At that time, some guy named -- oh wait, that was me -- wrote this thing called
OWAREPL (historically archived here:

http://asktom.oracle.com/~tkyte/owarepl/doc/
).  It greatly extended the capabilities of OWA giving it file upload/download,
db authentication and many of the features you take for granted in mod_plsql
today.

We are now at OWAS version 3.0 (Oracle Web Application Server)....

Then, came along this thing called "webdb" (our group wrote that initially).  We
wanted a smaller footprint then OWAS for simple installs so we took the OWAREPL
code and built a small lightweight listener around it - this was the webdb
listener.  It did http and "owa" functionality.

Then, OAS (Oracle Application Server) v4.0 came out -- they had been
incorporating many of the ideas of OWAREPL into their OWA but differently (file
upload and download for example is "there" in OWA but done very very differently
then either OWAREPL or webdb)....

Flash forward a bit -- spyglass goes away, cartridges go with it -- it is now
9iAS using apache.  The decision was made to use the webdb functionality in
mod_plsql -- the code was "merged" if you will.  So, OWA+OWAREPL+webdb ->
mod_plsql, with owarepl/webdb (which was really owarepl written in the Oracle
coding standard instead of "toms standard") being the predominant theme.

And here we are.

The others you refer to are "imposters" if you will.  OWA begat OWAREPL begat
webdb -> mod_plsql.

      Rating: 4
History of
      mod_plsql  August 29, 2003
Reviewer:  Robert
       from CT
Thanks for the gory details, Tom :)

Wow...lots of cooks been stirring this pot, huh ?

Have you ever heard of this pl/sql web-app development framework called "Orbit"
(www.runnertechnologies.com)

Thanks

          Followup:

no i hadn't -- interesting, looks just like marvel -- aka html db -- aka a
feature of the next release of the database.

      Rating: 5
When and When
      not the use of MOD_PLSQL  August 29, 2003

Reviewer:  Mark Wooldridge  from Vienna, VA.

Tom,

Interesting history of mod_plsql.  I can relate to it since I had also been
working with WOW back in  1995 but we implemented multiple HTTP listeners on
Trusted Solaris and hooked into Trusted Oracle to get different results at
different levels.

Later when OLS was developed we created a 'c' function to "hijack" the mod_plsql
oci connect call to get the level of the http request and set the necessary OLS
level/compartment information on the context and continue to the original oci
connect call in mod_plsql.  This allowed us to run with Oracle and OLS on
Trusted Solaris and have connections to the database that matched the level of
the network connection.  This function also worked when using SQL*Plus on the
host machine and an attempt ws made to get it working with SQL*Net but we really
needed a multi-level SQL*Net listener as Trusted Oracle had.

Sorry for the extra comments.

Given the monolithic nature of mod_plsql, your comments about Amazon not being
able to do it all, and how many users you feel mod_plsql could support with the
right hardware, how does one decide if mod_plsql is the "right" tool for right
job? What portions of amazon could not be done with mod_plsql?

p.s.  hopefully mod_plsql will be updated to support posting clobs since 9i has
been updated with better handling of clobs.

          Followup:

mod_plsql is constrained by the number of database instances you have running on
your database -- so RAC extends this number out, as it gives you more cpu
cycles, but cannot extend it out as far as an infinite number of app servers
could.

plsql uses cpu -- say it used 0.10 cpu second per page (pretty good goal on a
conventional machine today).

that means it can do 10 requests per cpu if they all arrive "evenly".

so, in theory, a 4 cpu machine might be able to do 40tps -- if they arrive
evenly (and then never do, so it is less then that)...

it is just math really after that -- what is the arrival pattern, what is the
amount of cpu resource consumed by plsql -- how many cpus do you have.

at some point, you cannot get enough cycles.  That is when moving to a
physically separate tier makes sense.  When this is -- depends on your setup and
how efficient your code is to a degree.

it is "big" to say the least.

      Rating: 5
What exactly is
      DAD ?  September 02, 2003
Reviewer:  robert
       from CT
Please explain in "Tom Speak"
what exactly Database Access Descriptor (DAD) is.
Why is it an important piece of info for mod_plsql/apache.
Where is it stored, how it is used...

I read the official iAS doc but not sure I understand it.

Thanks

          Followup:

it is to mod_plsql what a tnsnames.ora entry is to sqlplus.

sqlplus scott/tiger@database
                    ^^^^^^^^  big mess of data in a tnsnames.ora file
                              that says how to connect to a database

A DAD (formally known as a DCD, database connection descriptor) tells mod plsql
"how to" connect to a database.  In my URL for example:

http://asktom.oracle.com/pls/ask/f?p=4950:1:
                             ^^^

ask is my "DAD" and it consists of information like this:

[DAD_ask]
connect_string   =  AskUs
password   =  nope_not_a_password
username   =  public_user
default_page   =  f?p=4950
reuse          =  Yes
enablesso      =  No
stateful       =  No

that tells mod_plsql to connect as public_user/nope_not_a_password@AskUs, the
default page will be the function f with inputs p=4950, we are reusing
connections (pooling) and sso (single sign on) is disabled and stateful sessions
are not needed/supported.

it is stored in a config file -- wdbsvr.app in the iAS home/modplsql/cfg
directory.

      Rating: 4
Package/Session
      Management Type  September 15, 2003
Reviewer:
      Robert  from CT
Tom, for "Package/Session Management Type" in DAD config
, for Marvel, do you "Preserve State" or "Reset State" ---
what's the difference (to Marvel app) anyway in terms of functionality,
performance  ?

Thanks
Robert

          Followup:

reset, always.

i never use a stateful model on the web, never.  too many resources consumed for
too long.

in marvel, the state is managed 100% in the database, you always want reset.

      Rating: 4
Getting the
      Session Id  November 21, 2003
Reviewer:
      Bill  from Norman, OK USA
Tom,
You stated above that "the sessionid is maintained by the browser and sent back
to us"

How do you get the sessionid from the browser using mod_plsql

          Followup:

you make one up

we use sequences and random numbers to generate session ids.  they are just made
up unique numbers.

November 21, 2003 Pertisth -- Thanks for the question regarding
      "Total Required in Hierarchical Query", version 8.1.6

      You Asked
Dear Tom,

I have a tables named TEST containes

------------------------------------
MAIN        SUB          AMOUNT
------------------------------------
START
START       ACCOUNT
START       IT
START       MARKETING
START       PERSONNEL
ACCOUNT     KULDEEP      100.00
ACCOUNT     VERMA        100.00
ACCOUNT     PRAGYA       100.00
ACCOUNT     VIPIN        100.00
IT          PERTISTH     100.00
IT          ANAND        100.00
IT          MOHAN        100.00
IT          DINESH       100.00
MARKETING   ANUPAM       100.00
MARKETING   RAKESH       100.00
MARKETING   VASANT       100.00
MARKETING   YOGESH       100.00
PERSONNEL   SINGH        100.00
PERSONNEL   SANJAY       100.00
PERSONNEL   YADAV        100.00
PERSONNEL   SANGEETA     100.00

and I need output like

------------------------------------
SUB               AMOUNT
------------------------------------
START             1600.00

 ACCOUNT           400.00
  KUPDEEP          100.00
  VERMA            100.00
  PRAGYA           100.00
  VIPIN            100.00

 IT                400.00
  PERTISTH         100.00
  ANAND            100.00
  MOHAN            100.00
  DINESH           100.00

 MARKETING         400.00
  ANUPAM           100.00
  RAKESH           100.00
  VASANT           100.00
  YOGESH           100.00

 PERSONNEL         400.00
  SINGH            100.00
  SANJAY           100.00
  YADAV            100.00
  SANGEETA         100.00
------------------------------------

I have written a query
select sub,amount(amount) from TEST start with main='START' connect by prior
sub=main group by sub;

but this command does not meet my result.
Please help me.

Thanks

pertisth

      and we said...
well, your hierarchy is stored in an unorthodox manner -- usually people have a
parent column and a child column (like EMPNO and MGR).  Having that special
"start" record with a NULL messes things up.  we can do it -- it could look like
this:

tkyte@TKYTE816> select rpad(' ',(level-1)*2, ' ' ) || nvl(sub,main) sub,
  2         decode( level, 1,
  3                  (select sum(amount)
  4                     from test  t2
  5                    where t2.main = test.sub
  6                       or t2.main in ( select nvl(t3.sub ,'xx')
  7                                         from test t3
  8                                      where t3.main = nvl(test.sub,test.main)
  9                                          and t3.sub is not null) ),
 10                  amount ) amt
 11    from test
 12   start with main = 'START'
 13  connect by prior sub = main
 14  /

SUB                    AMT
--------------- ----------
START                 1600
ACCOUNT                400
  KULDEEP              100
  VERMA                100
  PRAGYA               100
  VIPIN                100
IT                     400
  PERTISTH             100
  ANAND                100
  MOHAN                100
  DINESH               100
MARKETING              400
  ANUPAM               100
  RAKESH               100
  VASANT               100
  YOGESH               100
PERSONNEL              400
  SINGH                100
  SANJAY               100
  YADAV                100
  SANGEETA             100

21 rows selected.
Review & Followup

      Rating: 4
It saved my lot
      of time  May 07, 2001
Reviewer:  Pertisth
      Mankotia  from Delhi,India
Thanks for quick response and it saved my lot of time. Earlier to acheive this I
had written a code of 100 lines , now that 100 lines is replaced with one sql
statement,plus the performance is improved.

      Rating: 2
I face error
      with 8i : ORA-01472: cannot use CONNECT BY on view with DISTINCT, GROUP
      BY, etc.   November 20, 2001
Reviewer:  Vijay
      Chandra Sekhar Parepalli  from Hyderabad,India
Hi Tom,

But what if I have data in a view (using Oracle 8i) this way

Name of the view : v_frm_Liabilities

ACGRPID    ACGRPDESC                                BALANCE     LevelAC
PARENT
-------    -------------------------------------    --------    -----    -------
GR0022     Shareholders Funds                             0     1
GR0023     -----Share Capital                             0     2     GR0022
GR0024     -----Reserves and Surplus                      0     2     GR0022
GR0025     -----Secured Loans                             0     2     GR0022
GR0026     ----------Bank Loans                        -200     3     GR0025
GR0027     Current Liabilities & Provisions               0     1
GR0028     -----Current Liabilities                       0     2     GR0027
GR0030     ----------Creditors for Capital Goods          0     3     GR0028
GR0031     ----------Deposits from Employees              0     3     GR0028
GR0032     ----------Deposits from customers              0     3     GR0028
GR0033     ----------Creditors for Expenses               0     3     GR0028
GR0034     ----------Creditors for Purchases            150     3     GR0028
GR0035     ----------Outstanding Expenses                 0     3     GR0028
GR0029     -----Provisions                                0     2     GR0027
GR0036     ----------Provision for Taxation               0     3     GR0029

and I need the following 2 formats of output.

format - 1 :
---------------

ACGRPID    ACGRPDESC                                BALANCE     LevelAC
PARENT
-------    -------------------------------------    --------    -----    -------
GR0022     Shareholders Funds                             0     1
GR0023     -----Share Capital                             0     2     GR0022
GR0024     -----Reserves and Surplus                      0     2     GR0022
GR0025     -----Secured Loans                             0     2     GR0022
GR0026     ----------Bank Loans                        -200     3     GR0025
GR0025       -----Total for Secured Loans               -200        2    GR0022
GR0022     Total for Shareholders Funds                    -200     1
GR0027     Current Liabilities & Provisions               0     1
GR0028     -----Current Liabilities                       0     2     GR0027
GR0030     ----------Creditors for Capital Goods          0     3     GR0028
GR0031     ----------Deposits from Employees              0     3     GR0028
GR0032     ----------Deposits from customers              0     3     GR0028
GR0033     ----------Creditors for Expenses                      0     3
GR0028
GR0034     ----------Creditors for Purchases            150     3     GR0028
GR0035     ----------Outstanding Expenses                 0     3     GR0028
GR0028     -----Total for Current Liabilities               150     2     GR0027

GR0027     Total for Current Liabilities & Provisions          150     1

GR0029     -----Provisions                                0     2     GR0027
GR0036     ----------Provision for Taxation               0     3     GR0029
GR0029     -----Total for Provisions                          0     2     GR0027

format - 2 :
-----------------

ACGRPID    ACGRPDESC                                BALANCE     LevelAC
PARENT
-------    -------------------------------------    --------    -----    -------
GR0022     Total for Shareholders Funds                    -200     1

GR0023     -----Share Capital                             0     2     GR0022
GR0024     -----Reserves and Surplus                      0     2     GR0022
GR0025       -----Total for Secured Loans               -200        2    GR0022
GR0026     ----------Bank Loans                        -200     3     GR0025
GR0027     Total for Current Liabilities & Provisions          150     1

GR0028     -----Total for Current Liabilities               150     2     GR0027

GR0030     ----------Creditors for Capital Goods          0     3     GR0028
GR0031     ----------Deposits from Employees              0     3     GR0028
GR0032     ----------Deposits from customers              0     3     GR0028
GR0033     ----------Creditors for Expenses                      0     3
GR0028
GR0034     ----------Creditors for Purchases            150     3     GR0028
GR0035     ----------Outstanding Expenses                 0     3     GR0028
GR0029     -----Total for Provisions                          0     2     GR0027
GR0036     ----------Provision for Taxation               0     3     GR0029

When I tried the for a query to achieve for format -2 as follows ,

select
rpad(' ',(levelAC-1)*2, ' ' ) || nvl(acgrpid,parent) ACGRPDESC,
decode( levelAC, 1,
(
    select sum(balance) from v_frm_Liabilities  t2
    where
    t2.parent = v_frm_Liabilities.acgrpid
    or
    t2.parent in (
            select nvl(t3.acgrpid ,'xx')
            from
            v_frm_Liabilities t3
            where
            t3.parent = nvl(v_frm_Liabilities.acgrpid,v_frm_Liabilities.parent)
            and t3.acgrpid is not null)
            ),
    Balance
) balance
from
v_frm_Liabilities
start with parent is null
connect by prior acgrpid = parent

I got the following error.

ORA-01472: cannot use CONNECT BY on view with DISTINCT, GROUP BY, etc.

Can I have the queries for both the formats?

      Rating: 4
"Total Required
      in Hierarchical Query"  July 16, 2002
Reviewer:
      Alan Kirchoff  from Overland Park, KS USA
This was very useful.  However, a response to the last entry regarding Oracle
error ORA-01437, would be great.

Thanks,
Alan
Overland Park, KS

          Followup:

01437, 00000, "cannot have join with CONNECT BY"
// *Cause:
// *Action:

Well -- the error message seems to say it all.

Interesting to note:  oracle9i release 1 and up allows for a join in a connect
by.

      Rating: 3
How does this
      work for n level hierarchy ?  November 20, 2003

Reviewer:  Anurag Doshi  from Irvine, CA USA

I don't understand how this query can work for n level hierarchy.  Is there any
way out for doing this for n level hierarchy using pl/sql

          Followup:

what part is causing confusion?  don't know what you are referring to.

      Rating: 3
n Level
      Hierarchy  November 21, 2003
Reviewer:  Anurag
      Doshi  from Irvine, CA USA
I don't understand how the query works if there are 5 levels.  If the level is
not 1 then you are just displaying the amount field.

          Followup:

this query was written for their very specific and somewhat unorthodox schema.

you would have to modify the concept for a different schema

      Rating: 3
Generate a
      complete ancestory tuple  November 21, 2003

Reviewer:  Anurag Doshi  from Irvine, CA USA

Suppose sample data is like this:

Node ParentNode
0    Null
1    0
2    1
3    2

There are no restrictions in number of levels

I want to generate
Node AncestorNode
0    0
1    0
2    0
3    0
1    1
2    1
3    1
2    2
3    2
3    3

November 21, 2003 alvaro -- Thanks for the question regarding
      "granting GRANT ANY ROLE ", version 7.3.4.0.0

      You Asked
Hi Tom,

I have a user that will be the responsible for granting and revoking
application's roles to others users (this user will be the application's
administrator) . This task will be done using a package, and I've granted "GRANT
ANY ROLE" to this user for this purpose.

I want to know what kind of problems could I have for granting "GRANT ANY ROLE"
to a user that is not DBA.

I Know that this user couldn't grant the role DBA to others users, but I'm not
sure if there are dangerous exceptions about it.

In addition, this user only will be able to grant and revoke roles  through the
package.

thanks

Acueto

      and we said...
If the GRANT statment is in a package the OWNER of the package needs the ability
to grant the role, the user running the package needs no special privs.

The invoker of the procedure does not need this privilege, only the OWNER
(definer) does.
Review & Followup

      Rating: 1
GRANT ANY
      ROLE  January 03, 2002
Reviewer:  Joe
       from Denver, Co. - USA
I was hoping to understand if I granted GRANT ANY ROLE to a user, if that would
create a security problem for me, the Production DBA.  I'm responsible for
system owned objects, not other schema owned objects in the database.  I
wouldn't want to give anyone other than sys or system the ability to grant roles
such as DBA to themselves or anyone else.  Any clarification would be
appreciated.

          Followup:

Don't grant "GRANT ANY ROLE", that will create a huge security problem.

I was suggesting to the person here, that asked the question, that they NEED NOT
grant this super powerful priv.  That all they needed to do was create a package
that gave the grants as the owner of the objects -- grant execute on that
package to this other user  and they are done.

Don't grant GRANT ANY ROLE, that would be a really bad idea.

      Rating: 3
How Powerfull
      of GRANT ANY ROLE?  January 03, 2002
Reviewer:
      Mike  from ATLANTA
Case, SYSTEM grant GRANT ANY ROLE to scott. SCOTT no able to grant DBA to self
or any other user.

system@o817> grant grant any role to scott;

Grant succeeded.

scott@o817> select * from dba_users;
select * from dba_users
              *
ERROR at line 1:
ORA-00942: table or view does not exist

scott@o817> grant dba to scott;
grant dba to scott
*
ERROR at line 1:
ORA-01031: insufficient privileges

scott@o817> grant dba to SYSTEM;
grant dba to SYSTEM
*
ERROR at line 1:
ORA-01031: insufficient privileges

scott@o817>

          Followup:

Try any role other then DBA or RESOURCE.  They fail because they implicity
attempt to grant the privilege UNLIMITED TABLESPACE in addition to the ROLE
(they are "magic", special).  You'll find that scott can grant important stuff
like JAVASYSPRIV to anyone (including himself).  Give me the ability to create a
java stored procedure and GRANT ANY ROLE and I'll wreak havoc on your database.
Or, if you have DBA-like roles you've created (as you should), they can grant
those.

Powerful?  Yes, definitely.

      Rating: 3
Tom is right
      that GRANT ANY ROLE is Very Dangerous  January 04, 2002

Reviewer:  Chip  from Denver CO
GRANT ANY ROLE does allow 2 roles that are granted to DBA
to be granted: EXP_FULL_DATABASE and IMP_FULL_DATABASE.
The IMP_FULL_DATABASE role has the ability to drop any
database object and create any database object.  Also,
the privilege to EXECUTE ANY PROCEDURE opens up more
possibilities (as does BECOME USER).

          Followup:

Good point -- if you give me:

o create session
o execute any procedure

I can take over your database and get ANY priv I want.  It would take about 30
seconds for me to have DBA and anything else I wanted.

      Rating: 5
You Think That
      Is Bad...  January 04, 2002
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Here's a list of the *required* (according to the totally clued-in vendor -
sarcasm) of the grants/privileges to be given to the 'SA' account.  (Maybe this
is a ported MS SQL Slower application?)  Vendor's name withheld to protect the
guilty.  Anyway, here's the list straight off their support website:

=====================
PROBLEM SYMPTOM:
Oracle privileges required for sa account

Customers have requested a list of what privileges the SA account needs for an
Oracle database.
=====================
DIAGNOSIS:
List of privileges required for sa account

The "sa" account is created as:
grant dba to sa identified by sa with admin option

Additional System Privileges

alter session
alter user
create cluster
create database link
create procedure
create profile
create public database link
create public synonym
create role
create session
create sequence
create synonym
create table
create trigger
create user
create view
drop profile
drop public database link
drop public synonym
drop user
grant any privilege (or "with admin option")
restricted session

sa must also have access to all of the "DBA_" views.

I don't see any problems there... (big sarcasm)

This vendor needs help.  Plus you should see a StatsPack report of their sql
executing...

          Followup:

alter user -- hmm, with that I rule the WORLD ;)  I can become SYS or anyone I
want to....

      Rating: 5
Curious to
      know  January 04, 2002
Reviewer:  Reader
       from USA
Tom,

Curious to know, with just "execute any procedure"
How will you get "DBA" priv in about 30
seconds.

Thanks

          Followup:

I'd rather not say.  The knowledge that it is so should be more then enough
hopefully to make people go out there and reconsider how they grant privs and
what privs they grant.

Think about it -- you can run ANY procedure in the database, any SYS owned
procedures, whatever you want...

      Rating: 5
RE: I'd rather
      not say...  January 05, 2002
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Tom:

Thanks for not publishing how to do this...  As you know from my previous
posting, one of my PROD environments is already way more open than I would
prefer.  Now, if it was just in a DEV environment, I am much more open...

One of the last things we need is for some script kiddie to read about execute
any procedure and start trying to hack around a database.

- Mark

          Followup:

Yes, thats they way I feel as well.  The knowledge that the ability exists is
more then enough knowledge.  It's like the guys who find the holes and not only
say "hey, there is a hole over there" but then proceed to publish "and here is
how to drive the truck right through it" -- they are totally misguided.  They
believe (or at least CLAIM to believe) they are just being helpful -- all they
need to do is publicize that there is a hole, not how to exploit it.

This really isn't a hole, it is a natural side effect of having this very
POWERFUL privilege.

      Rating: 5
RE: RE: I'd
      rather not say...  January 05, 2002
Reviewer:
      Mark A. Williams  from Indianapolis, IN USA
Tom:

100% with you - what a "service" it is to find "exploits" and then broadcast
them everywhere.

I don't want to give too much away (because I do know how to do this), but one
thing I did want to mention is that setting o7_dictionary_accessibility=false
will "close the hole" (even though it really isn't a hole as you point out).

- Mark

          Followup:

Correct -- and hopefully people will live with the
7_dictionary_accessibility=false setting INSTEAD of setting it true (as I see
people do)...

Now, give me create session, execute any procedure, xxxxx xxx xxxxxxxxx -- and
I'm back in business ;)

      Rating: 5
RE: RE: RE: I'd
      rather not say...  January 05, 2002
Reviewer:
      Mark A. Williams  from Indianapolis, IN USA
*IF* I was going to give some knowledgeable individual create session, execute
any procedure, and xxxxx xxx xxxxxxxxx (left 'blank' on purpose), I might as
well just give them DBA...  You seem pretty trustworthy, though :)  I run my
PROD db's with o7_... set to false as a rule (glad to see that is the default in
9i).  I regularly check dba_role_privs just to be safe (paranoid?) all the same.

- Mark

      Rating:
      3
  October 22, 2002
Reviewer:
      A reader
Hi Tom,

I tried to connect to my instance. But I could not log as even SYS user.

SQL> connect sys/change_on_install as sysdba
ERROR:
ORA-01031: insufficient privileges

I tried with the SYSTEM and INTERNAL user accounts also. But still the same
error.

Then I recreate the password file. but still no help. Could you please tell me
what's wrong, that I had done.

          Followup:

without a version
without an os
without knowing who you are logged in as
without knowing how you created the password file
without knowing your setting of the remote_login_passwordfile init.ora setting

nope.

make sure the password you are using for SYS matches the one that you used in
creating the password file (thats the password it is looking for)

make sure the password file is named as it should be for your operating system
(refer to your OS admin guide)

make sure the remote_login_passwordfile is set

      Rating: 5
Another
      parameter to check...  October 22, 2002
Reviewer:
      Mark A. Williams  from Indianapolis, IN USA
"A reader"...  I tried to connect...

In addition to the parameters from Tom, make sure SQLNET.AUTHENTICATION_SERVICES
is set correctly if you are using Windows...

HTH,

Mark

      Rating: 5
Follow-up to
      myself...  October 22, 2002
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
The SQLNET.AUTHENTICATION_SERVICES won't directly affect this situation, but
just wanted to throw it out there in case it needed to be looked at too...

Sorry...  if that was misleading...

- Mark

      Rating: 4
Security
      risk?  January 31, 2003
Reviewer:  Martin
       from The Netherlands
Tom,

I noticed in one of our databases that for all (packaged)procedures execute is
granted to PUBLIC. Reading about the danger of users having the EXECUTE ANY
PROCEDURE privilege I was wondering if this does not mean the same dangerous
situation? Should I remove these grants? And if I do won't that mean custom
packages that for instance call DBMS_SQL will suddenly fail?

          Followup:

EXECUTE ANY is different then having EXECUTE on specific packages.

I cannot tell if you are talking about the supplied DBMS_* packages -- which are
MOSTLY granted to public (but some like dbms_sys_sql, dbms_lock, are not)

or your own packages.

If they are you OWN packages -- do what you want as far as securing them.

If they are the DBMS_* packages, just verify that the ones we need to have
granted to public are (dbms_output, dbms_sql, etc) and the ones we do not grant
by default are not.  You can grep the original install scripts in
$ORACLE_HOME/rdbms/admin to see which are to be granted to public

$ cd $ORACLE_HOME/rdbms/admin
$ grep -i 'grant.*execute.*on.*to.*public' *

      Rating: 4
Sorry I didn't
      state my question very clear  January 31, 2003

Reviewer:  Martin  from The Netherlands
Tom,

What I meant to ask was if ALL packages/procedures/functions in the SYS schema
are granted execute to Public, wouldn't that create a similar problem as
granting the EXECUTE ANY PROCEDURE to a user or even public. From reading your
comment on my original queston I learn that it should be save when I remove
those grants which oracle does not grant on installation by default, right?

          Followup:

If things like dbms_sys_sql, which are not granted by our stuff in
$ORACLE_HOME/rdbms/admin, are granted to public -- you have a problem and need
to correct it -- yes.

      Rating:
      4
  February 12, 2003
Reviewer:
      A reader
If O7_DICTIONARY_ACCESS is set to FALSE, then even if we grant EXECUTE ANY
PROCEDURE privilege, they won't be able to execute the DBMS_ procedures.
Unfotunately, until 9i, the default value for that parameter was TRUE.

          Followup:

as mentioned above -- yes, that helps much

      Rating:
      3
  November 21, 2003
Reviewer:
      reader  from India
Hi Tom,
      while reading Oracle8i,Administrator’s Guide,Release 2 (8.1.6),Part No.
A76956-01 I got this point "Privileges and roles can also be granted to and
revoked from the user group PUBLIC. Because PUBLIC is accessible to every
database user, all privileges and roles granted to PUBLIC are accessible to
every database user." I want to know what exactly is user group and PUBLIC,
apart from public are there any other user groups and what's the advantage of
having user groups, also how does granting of privileges and roles to a user
group can be made accessible to all.

Thanks in advance for your help.

          Followup:

the "user group public" is simply a set of privs that EVERYONE has.

if you "grant create select any table to public", everyone - everyone that can
log in has "select any table" (so -- do NOT do that!)

      Rating: 5
Become a
      DBA  November 21, 2003
Reviewer:
      A reader
Hi Tom,

The 'O7_dictonary_accessibility' parametere must be TRUE for a normal user who
is assigned 'Execute Any Procedure' privilege to get DBA privileges.

Is this correct?

          Followup:

to get DBA, you must be granted DBA-- that is all.

November 21, 2003 Jon -- Thanks for the question regarding "NUMBER
      datatype", version 8.1.7

      You Asked
Hi Tom,

I have a few questions about the following:

SQL> drop table a
  2  /

Table dropped.

SQL> create table a(x integer, y number(38), z number(5));

Table created.

SQL> select column_name, data_length, data_precision, data_scale
  2  from user_tab_columns where table_name = 'A';

COLUMN_NAME                    DATA_LENGTH DATA_PRECISION DATA_SCALE
------------------------------ ----------- -------------- ----------
X                                       22                         0
Y                                       22             38          0
Z                                       22              5          0

1) The data_length is 22 for each column.  Does that mean that all number
columns use 22 bytes regardless of precision?

2) Column X has a null data_precision - is that a bug; should it read 38?
Internally is there any difference between an INTEGER and NUMBER(38).  Is not
INTEGER just a subtype?

3) Assuming the column is large enough for the data, is there any difference in
performance between say a NUMBER(5) and NUMBER(38)?  I had thought there could
be more LIO required for a larger precision, but maybe this is not the case.

4) Similarly, is there any difference in performance when storing integer data
in a column with a scale > 0 or storing in a column with scale = 0.  Internally
is this floating point arithmentic or all library integer arithmetic?

5) Closely related to 4, if I store 100 in NUMBER(5) is the internal
representation different than if I stored 100 in NUMBER(3,2)?

Thanks
Jon

      and we said...
1) they use UP TO 22 bytes - that is the maximum length.  All numbers are stored
in a varying width pack decimal like format.

2) Nope, not a bug, just the way it is.

3) A number(5) is not any different from a number(38) if the number(38) only
holds data the number(5) can.  the 5 is just an "edit", a format, an integrity
constraint.  nothing more, nothing less.  It affects the physical storage not at
all.

4) it is all oracle NUMBER math.

5) trick question -- 100 won't fit there:

ops$tkyte@ORA920> create table t ( x number(3,2) );

Table created.

ops$tkyte@ORA920> insert into t values ( 100 );
insert into t values ( 100 )
                       *
ERROR at line 1:
ORA-01438: value larger than specified precision allows for this column

But assuming you mean 5,2 -- not any different .

use number(s,p) as an integrity constriant, a business rule, an edit.  That is
what they are good for.

Review &
      Followup

      Rating: 5
Trick
      Question...  March 06, 2003
Reviewer:  Jon
       from Sydney
Good to see you're awake and alert first thing in the morning... obviously you
didn't stay up late playing pool last night :)

      Rating: 4
NUMBER vs
      NUMBER(6)  November 21, 2003
Reviewer:
      A reader
Hi, Tom,
Our database tables have a common column whose datatype is NUMBER(6). Normally,
it is never exceeding 6 digits. But now because of the business rule changes, we
have to increase it to NUMBER(10). In order to avoid this suituation in the
future(maybe increase to NUMBER(20) again), and considering there are lots of
same coulmns in different tables(some tables already have millions of rows), is
it a good idea to just define the column as NUMBER without any precision defined
across the board? Are there any physical and logic space differences(concerns)
between NUMBER and say NUMBER(10), especially considering a well existing large
database?

Thanks

          Followup:

you can alter a number up in size, it is instantaneous.

use the right datatype.

a number(10) is simply a NUMBER with an edit that says -- YOU BETTER BE 10
digits or less!

it is a constraint, use it.

      Rating: 4
More regarding
      space  November 21, 2003
Reviewer:
      A reader
Hi, TOm,

Appreciate your input.
I agree the following 100%:
"use the right datatype.
a number(10) is simply a NUMBER with an edit that says -- YOU BETTER BE 10
digits or less!"

But I want to know if it will affect database both space and performance wide.

Thanks

          Followup:

space -- no

performance -- why even consider it, you NEED (desire, want, wish for, must
have, etc etc etc) to have integrity....

it will add such a teeny tiny trivial small miniscule amount of "checking" that
you won't even see it.

November 21, 2003 gary -- Thanks for the question regarding
      "SQL*Loader and multiple logical records from one physical record",
      version 8.1.7

      You Asked
In SQL*Loader:

I have a string in a text file that is delimited, not fixed format. In addition
to loading into a table, I would like to load into a second table, one row for
each word making up this string.

For example, let's say in it's simplest form, I have file with 3 records (listed
below). It has a single field called title in my table. I want the data to go
into the first table with one row for each record (I have this part). Then I
want to load another table with each of the separate words. The "simplest" form
is not my case, as I have multiple fields in the datafile and table, so breaking
on whitespace won't work here.

The Long And Winding Road
Let It Be
Paperback Writer

So, the second table will have 10 rows with each of the words above.

I know I can write a trigger to loop through the words with INSTR and SUBSTR,
but can I do this right in the control file so as to prevent the trigger from
executing if something other than SQL*Loader put data into the table? I am
having trouble with the WHEN clause as it doesn't seem to like functions.

      and we said...
You can try loading into a view with an instead of trigger on it, eg:

drop table t1;
drop table t2;

create table t1 ( txt varchar2(2000) );
create table t2 ( word varchar2(20) );

create or replace view v
as
select * from t1;

create or replace trigger v_instead_of
instead of insert on v
declare
    l_tmp varchar2(4000) := replace( :new.txt, ' ', '|' ) || '|';
    n     number;
begin
    insert into t1 (txt) values (:new.txt);
    loop
        exit when (l_tmp is null);
        n := instr( l_tmp, '|' );
        insert into t2 (word) values ( substr(l_tmp,1,n-1) );
        l_tmp := ltrim( substr(l_tmp,n+1), '|' );
    end loop;
end;
/

When I run the control file:

LOAD DATA
INFILE *
INTO TABLE v
REPLACE
( txt char(255) )
BEGINDATA
how now brown cow
the fox jumped over the moon

t1 gets the lines of text, t2 gets the "words"
Review &
      Followup

      Rating: 4
How do I load
      multiple lines into one physical db record ?  November 21,
      2003
Reviewer:  Friend  from Bay Area, CA
I have a scenario, where in the data file contains multiple lines in the format
of  EMPID | COLNAME | VALUE
the data would look like

1 | NAME | PETER
1 | SSN  | 111111111
1 | PHONE | 1111111111
1 | CITY | MOUNTAIN VIEW
2 | NAME | BILL
2 | CITY | SUNNYVALE
3 | NAME | GILL
...
and so on.
The data file may not contain every field/value pair.
Is there any way this data could be sql*loaded (in Oracle 8i) into a table with
columns like :
EMPID, NAME, PHONE, SSN, ADDRESS1, ADDRESS2, CITY, STATE

Thanks,

          Followup:

something like concatenate 7 -- see the server utilities guide, there is a
chapter on creating physical records from logical records.

November 22, 2003 Tony -- Thanks for the question regarding "Why to
      upgrade to 9i for Data Warehouse application?", version 8.1.6

      You Asked
Hi Tom,
Could you help me in answering the question below (asked by my team mate in
relation to upgrade to 9i for data warehousing application):

There are lots of "new features" which is just Oracle
bundling their old products eg OLAP server is just Express I think,
dw builder is old, data mining is just the algorithms, not visualisation, but
not too much extra except for some more performance tuning capability around
views (bitmap join index), lead&lag function upgrades and extra partitioning
capability which were first introduced in 8i.

Regards,

      and we said...
food for thought (hey, the database is old too, OWB is pretty up to date):

o Compressed tables (very impressive).  Potentially huge savings.

o List partitioning

o external tables (cool, very very cool -- parallel direct path loader in SQL.
  I love this feature for DW)

o merge command, multi-table inserts

o direct path loads with SQL functions

o pga aggregate target -- no more sort area size, hash area size, this size,
  that size -- just "Oracle use this much ram for PGA's, divvy it up as you see
  fit"

o pipelined functions

o with clause in select (for performance, reduces the number of times a
  subquery or inline view would have to be materialized to process a query)

o user defined aggregates (very fast, very cool)

o natively compiled plsql (plsql turned into Object code and executed
  natively on the operating system - similar to a JIT compiler for java)

o associative arrays for doing lookups/scrubbing (plsql table types where the
  index is a string -- so   state_name := state_lookup( 'AK' );   would be
  valid -- and fast

o RAC - but only if you are serious about availability and scaling out as well
  as up

o online operations for everything (rebuilds, reorgs)

o block level recovery -- for when a single 16k block in a 32gig datafile goes
  "bad" and you need to be up

o significant enhancements to the resource manager;  N-active users in a group,
  "niceing" down of long running queries, predicative query exection time
  estimation (eg: you can say "you cannot run a query that will take more
  then 3 hours", if they try, it will fail - not run for three hours until
  you kill it) -- among others.

o bitmap join indexes

o bitmap indexes on IOT's (index organized tables)

o multi-block size databases -- transport that 4k block size OLTP set of
  already formatted and indexed tables to your data warehouse and do the ETL
  using "create table as select" or "insert /*+ append */" with complex
  queries against the OLTP data (since your DW will have a 16 or 32k blocksize
  and your OLTP will have 2, 4 or 8k -- this is relevant)

o fine grained auditing -- efficiently and effectively capture the questions
  people are asking, along with binds.  Not only good for auditing but for
  performance analysis and "what the heck do people ask, and what is Bob
  specifically asking" analysis.

o resumable space allocation -- scenario, you are rebuilding that 15 gigawad
  bitmap index, it is in it's 2cnd hour and you get "out of temp" or "out of
  rbs" or "unable to extend" or "any space related error".  Bamm -- start over,
  2 hours wasted -- or wait, was I using 9i?  then the statement can suspend
  instead of fail -- I can fix the space problem (add a file to temp for exmaple
  and have it pick up where it left off).  Instead of 2 hours of my life wasted,
  i finish early and go home.

o index monitoring -- so, does anyone really use that index

o update of GLOBAL indexes during partition management -- instead of them
  just going invalid and having to be rebuilt.

o resize the SGA online (add block buffers, take them away)

o buffer cache/ shared pool/ etc advisors.  v$ tables that tell you -- you know,
  if you increased the buffer cache by this much, we think we would get X%
  more logical ios.  Or, if you increased the shared pool -- your library
  cache hit would improve by this much. (removes the guessing)

o Oracle streams -- to capture information easily from transactional systems
  and feed it into your DW.

o lots of new options with MVs (materialized views).  more rewrites, more
  constructs supports

o PDML on non-partitioned objects

ouch -- my hand is hurting from typing.  that's a partitial list -- there are
more I undoubtably forgot.  If you would like to see some examples/slides --
goto

http://asktom.oracle.com/~tkyte/
and take a look at some of the presentations at the bottom (new features stuff).
 I have scripts that show you what most of the stuff looks like when appropriate
but its a big overview of whats new in 9i.

Review &
      Followup

      Rating:
      5
Amazing!  December 02, 2002
Reviewer:
      A reader
Hi Tom,
Thanks for you quick response. It took me more time to prepare and ask question
(few lines), then to see your huge list from other site of the wire. You are
great!!!

      Rating:
      5
  December 03, 2002
Reviewer:
      A reader  from Barcelona

      Rating:
      5
  December 11, 2002
Reviewer:
      A reader
Hi Tom,

Can you tell me where I can find information about
"buffer cache/ shared pool/ etc advisors.  "
in the oracle documentation?

Thanks

          Followup:

see

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96536/toc.htm
and ctl-f for "advice" to see the one init.ora for the buffer cache and then
descriptions of the v$ views for the rest.  OEM is probably the easiest
(prettiest too) way to see this.

      Rating:
      5
  May 20, 2003
Reviewer:
      A reader
Hi Tom,

 I heard a lot about bitmap join indexes. We are using datawarehouse. Could you
give me a good test case on how we can improve performance by using bitmap join
indexes ?

Thnaks

          Followup:

do you have queries of the form:

select t2.stuff....
  from t1, t2
  where t1.key = t2.fkey
    and t1.value = 'X'

or have you denomalized your structures such that T2 has columns that rightly
belong in T1?

if so, bitmap join indexes are for you.

      Rating:
      4
datawarehouse  May 21, 2003
Reviewer:
      A reader

      Rating:
      5
Excellent!!  May 21, 2003
Reviewer:
      JP  from UK
Thanks a Million Tom.

Best Regards
JP

      Rating: 5
Data mining in
      oracle 9i  June 04, 2003
Reviewer:  Anil
       from Malaysia
Dear Tom,
I had upgraded my datawarehouse database from 8i to 9i. I want to use datamining
option. When i query v$option it says data mining option is installed.

I went to ODM installation guide. It says i should use DBCA to configure data
mining. When i use DBCA , the Data Mining option is greyed out.

Can u pls tell me the way how to use data mining option in an upgraded database
? Since the database is upgraded it doesnot have ODM user. Do i need to create a
separate database other than my production database.

          Followup:

check out

$ORACLE_HOME/dm/admin/dminst.sql

      Rating:
      5
features  June 05, 2003
Reviewer:
      A reader
excellent

      Rating: 4
A few questions
      about the list  July 04, 2003
Reviewer:
      John  from San Jose, CA
Hi Tom,

Have a few questions about the features on your list.

* What's the catch with data compression? Would compression slow down the DML
operations? If there is no catch, why doesn't RACLE simply set "compress" as the
default?

* Can you clarify
  direct path loads with SQL functions
  ?

* Can you give a practical case of using the
  index by varchar2 associative arrays?
  You can not bulk load into it. Possibly you can use dynamic
  SQL and cursor to populate the arrays. Do you think it is really worth the
effort?

John

          Followup:

o compressed tables work only when you bulk load them (direct path load, alter
table move, insert /*+ APPEND */, create table as select).

they do not make sense for heavily modified tables.

o you can perform a direct path load using a sql function in sqlldr or via an
external table.  you could not in earlier releases.

o lookup tables for a data load process whereby you must load data row by row --
it is a programming construct, not a "disk based table"

      Rating: 4
bitmap join
      indexes  August 07, 2003
Reviewer:  A reader
       from India
Your followup for one of the Questions was:
do you have queries of the form:

select t2.stuff....
  from t1, t2
  where t1.key = t2.fkey
    and t1.value = 'X'

or have you denomalized your structures such that T2 has columns that rightly
belong in T1?

if so, bitmap join indexes are for you.
---------------------------------------

My question is :I build bitmap join indexes on the specified join
-----------------
t1.key = t2.fkey
-----------------
of the example mentioned by you. Now in my query I do not want to use this
predicate. Will the star transformation algorithm transform the above query with
the help of the bitmap join index created. Or will this query result in a
cartesian join.

      Rating:
      5
Informative.  August 18, 2003
Reviewer:
      Kashif  from Reston, VA

      Rating: 5
Of Great
      Help.  October 05, 2003
Reviewer:  Richard
       from Oxfordshire, UK

      Rating: 4
OWB and unix
      process  November 19, 2003
Reviewer:  Victor
       from NJ
Tom,

I am working on a new datawarehouse with Oracle 9.2.
For ETL processes I would like to recommend OWB. I built a light prototype and
it works great especially change management including metadata changes.
Now my question - is it possible to integrate 3-rd party utility into OWB
process.
For example, currently we are using Syncsort that is called from KSH.
It is very fast sort and aggregation and I would like to incorporate it into
process under OWB.

Thanks, Victor

          Followup:

I asked Sam Khoury -- my OWB expert and he says:

Yes you can incorporate external processes into OWB via PFE (Process Flow
Editor). Once you complete your maps, you can model the processes and insert
external ones to be included in the map. Here are the steps and a screen shot:

1. Under the node "Process Flows" , create a process flow module, package and
process
2. Drag and drop your maps, functions, procedures...etc on the canvas
3. Drag and drop the "External Process" Icon onto the canvas
4. define the parameters in the lower half of the PFE screen
5. Once finished, deploy the process like any regular map

http://asktom.oracle.com/~tkyte/owb.jpg
P.S. you can also incorporate whatever functions, procedures..etc  you create as
user-defined

      Rating: 5
OWB and unix
      process  November 23, 2003
Reviewer:  Victor
       from NJ
Tom and Sam,

Thanks a lot.

November 22, 2003 eva -- Thanks for the question regarding
      "Conversion to String to hexadecimal", version 8.0.6

      You Asked
Hi Tom,

  I am trying to convert a string including numbers and characters
to hexadecimal.

For example:  Hello World, I am 50!  ----->  0110101010101

Can you help me out with either the SQL or the PL/SQL?

Thanks!

      and we said...
that looks like binary -- but to goto hex is pretty simple:

ops$tkyte@ORA920> exec dbms_output.put_line( utl_raw.cast_to_raw( 'Hello World'
) );
48656C6C6F20576F726C64

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>

you may have to install utl_raw (from $ORACLE_HOME/rdbms/admin) in 8.0
November 22, 2003 Mahendra -- Thanks for the question regarding
      "Web enabled application", version 9.2

      You Asked
Dear Tom,

I have an application running on Forms 6i / Oracle 9.2.  This Application is
like "metalink.oracle.com" in nature.

I would like to make this application web enabled so it can be used by all users
of my company which are located throughout
the country.

As such, We do not have much experience of Web Development. It would be a grate
help to us if you let us know basic steps
of web enabling & deployment of this application and prefered software
environment.

Regards,

Mahendra.

      and we said...
well "preferred" is a matter of choice -- what languages you know and use is
important.

as you are a forms developer, I KNOW you KNOW plsql -- so

http://htmldb.oracle.com/
is 100% relevant (it is the software that built this site).  You would be
interested in it.

Other technologies include java, vb, perl, php, etc -- but I prefer for database
applications -- a database language.  PLSQL fits the bill quite nicely.
Review &
      Followup

      Rating: 5
9ias Forms and
      Reports  November 22, 2003
Reviewer:  rob
       from pa
Oracle 9iAS can make your forms based desktop apps web based for you
automatically.  no need to rewrite anything.

from the docs:

Oracle9iAS Forms Services is a middle-tier application framework for deploying
complex, transactional forms applications to the Internet. Developers can build
new applications with Oracle9i Forms Developer and deploy them to the Internet
with Oracle9iAS Forms Services. Developers can also take current applications
that were previously deployed in client/server and move them to a three-tier
architecture without changing the application code.

as Tom suggested, if you are going to recreate from the ground up, consider
htmldb, but if you intend to recreate the functionality of your forms app, you
are better off having it done for you with 9iASR2 Forms Service or the upcoming
10g capabilities

November 22, 2003 Mahendra -- Thanks for the question regarding
      "Updating Partition key in a table", version 9.2

      You Asked
Dear Tom,

It would be great help to me if you let me know what oracle does internally when
we update partition key in a table.

Does it insert into partition mapped to new key value and delete from old
partition?
Does it generate rollback?
What happens if abnormal shutdown occurs in-between?

Best Regards,

Thanks,

Mahendra Jadav.

      and we said...
it does the logical equivalent of

a) delete old
b) insert new

without firing triggers

yes, rollback, undo, etc are generated as they would be for ANY update.

you can rollback, it is 100% recoverable.
November 23, 2003 Judy -- Thanks for the question regarding "RMAN -
      Full vs. Incremental - performance problem", version 8.1.7.0.0

      You Asked
Hi.  I am testing RMAN.  I have run an incremental 0 and an incremental 1
cumulative test without having any database activity in-between these test.  I
am using OmniBack media manager with RMAN.  I have three databases.  Here are
the timing results and other stats:

TYPE                       INSTANCE    TIME     MB            MB
                                                BACKED UP     per Hr

incremental 0                  1       12 min   2492          12460
incremental 0                  2       13 min   3481          16071
incremental 0                  3       11 min   2539          13852

incremental 1 cumulative       1       12 min   74            370
incremental 1 cumulative       2        9 min   13            84
incremental 1 cumulative       3        9 min   37            247

Why would the timings be basically the same?  Why are the other stats so
different and apparently poor for the incrementals?

      and we said...
Incremental is useful to conserve space -- much like an incremental in Unix
against a file system -- it is not significantly faster.

Incremental backups must scan each and every block in the database -- read it,
inspect it and see if it needs to be backed up.  They can save tons of space but
they read the entire instance anyway.

Unless you were really IO bound on the tape unit/backup device, it won't be any
faster.

From the RMAN guide:

<quote>
Incremental Backups

An incremental backup reads the entire file and then backs up only those data
blocks that have changed since a previous backup. Oracle allows you to create
and restore incremental backups of datafiles, tablespaces, or the whole
database. Note that RMAN can include a control file in an incremental backup
set, but the control file is always included in its entirety?no blocks are
skipped.

The primary reasons for making an incremental backup are:

o To save tape when using a media manager.
o To save network bandwidth when backing up over a network.
o When the aggregate tape bandwidth available for tape write I/Os is much less
than the aggregate disk bandwidth for disk read I/Os.
o To be able to recover changes to objects created with the NOLOGGING option.

If none of these criteria apply, then full backups are usually preferable
because the application of the incremental backup increases recovery time while
the cost savings is negligible.
</quote>
Review & Followup

      Rating: 4
Is this
      performance acceptable?  August 22, 2002
Reviewer:
      Juan Carlos Reyes
Hi Tom, I have a question about performance and compression

looking for a best backup method I timed the following backups methods

COMPRESING WITH WINRAR
Time 18mn
Size 180MB

ORACLE FULL EXPORT
TIME 6MN
SIZE  136M

RMAN FULL BACKUP
TIME 33:00
SIZE 937MB

I don´t know if I´m doing something wrong.
Is there a way to optimize the time and compression of the RMAN full backup, or
it is always so.

Thank you in advance Tom.

          Followup:

I have no idea what I'm comparing here but.

RMAN compression isn't really compression -- it simply skips blocks that aren't
used by anything (so in a "full database" it'll all be there).  we don't
compress the blocks themselves.

You did a full export of a database in 6 minutes and it was only 136m of data?
Wow.  Not a very big database is it?

Not enough info here to speculate however on whether this is good or not (and
not knowing what winrar is.....)

In any case, 33 minutes isn't so bad considering the database is never down
anyhow.

      Rating:
      3
  August 23, 2002
Reviewer:
      A reader
Thank you Tom

      Rating: 5
incrementals,
      better or wose to recover with?  November 22, 2003

Reviewer:  rob  from pa
You wrote this with regards to the criteria for which incrementals are
beneficial (from the docs):

"If none of these criteria apply, then full backups are usually preferable
because the application of the incremental backup increases recovery time while
the cost savings is negligible."

The docs seem to indicate it is advantageous to only have to apply the minimum
amount of logs as possible by having available daily incrementals.  What should
be considered when discussing whether it would be quicker to apply several days
of archives to a full backup vs the time it would take to apply multiple levels
of incrementals from the week to an initial level 0?  I am sure it depends on
the level of activity, but isn't the volume of archive logs, likely to be less
than space consumed by the incremental?  The incremental backs up the entire
block, where my archive logs may only hold a single transcation on that block.
Yet the archives have to apply a transaction, while the incremental only has to
copy a block.

I am trying to decide if the incrementals we take during the week are really
worth it, or if we might be better served with just our weekly full backups.
The total changed data is small relative to the overall system level data.

          Followup:

(and remember -- things change, in 10g my comment is false -- incrementals are
the way to go in that release!)

Lets put it this way -- you could:

sunday -- full backup
monday -- incremental
tuesday -- FAILURE

to recover:  restore sunday, apply monday incremental, roll forward thru logs

OR

sunday -- full backup
monday -- full backup
tuesday -- FAILURE

to recover: restore monday,                            roll forward thru logs

my point can only be taken if the incremental was IN PLACE OF a FULL backup.  I
am assuming "you are going to backup, will it be incremental or full".  Given
the choice in 9i and before -- if nothing in that list means anything to me, i
would probably opt for FULL.

November 23, 2003 Daniel -- Thanks for the question regarding
      "Index tablespace size", version 7.3.4

      You Asked
Hi Tom,

I think a have a problem with my index tablespace. It is 2 times the size of my
tables tablespace. Is that normal? My database is OLTP and the data from the
tables is frequently inserted, deleted and updated.

My Tables Tablespace has 4 GB with 765 MB occupied.
My Index Tablespace has also 4 GB, but has 1,60 GB occupied.

If I rebuild the indexes, this will be smaller?
There is a problem if the index tablespace became larger then the table
tablespace ?

thank you in advance

Daniel Sigrist

      and we said...
PROBABLY it'll be smaller (for a short while).

Then it'll grow again.

and you can rebuild and it'll be smaller

Then, it'll grow again.

and you can rebuild and it'll be smaller

<goto "then it'll grow again.">

Indexes are like people, most of them reach a steady state size -- with some
degree of "fat" (typically in our middle age we get there).  The "fat" in the
index is due to updated or deleted rows that have left holes.  Holes that will
be filled again later by some other update or by having all of the entries on a
leaf block get deleted.

Rebuilding or coalescing the index can reclaim some of this space-- TEMPORARILY.
 It'll just come back, and you know what, it might even make your system run
slower (might not).  Reason it could run slower -- you have to put the fat back
on.  Your index you rebuild will have to split and fragment and degenerate right
back to where it is.

It is the rare index that needs constant attention.

Now, I'm making an assumption here:

you didn't way over index your data.  If you have half of the columns indexed --
I could easily see your indexes even in the most compact form taking X times the
space of your table data

Some DBA's love to rebuild indexes continously, makes them feel as if they are
doing something I suppose.  If you do rebuild -- make sure you collect metrics
BEFORE so that when you are done and it's a week later -- you can collect the
same metrics and say "see, I improved the system performance by X" in a
measurable fashion, else don't bother.
Review &
      Followup

      Rating: 3
Q on Index
      Rebuilding  November 23, 2003
Reviewer:  Anil
      Pant  from India
You mean to say life with Indexes is not so easy and one has to keep monitoring
it.
Take an ideal situation. If suppose I've a index on a big table and only Inserts
and Updates takes places with less of deletes. I may do some bulk data loading
without disabling the index (this is just for an example).
I dont think the index will grow bigger than the table as very few deletes takes
place and updates are no bother to the index, unless the column on which it's
indexed is updated.
Is my assumption correct pls calrify.

          Followup:

well, that would be putting words in my mouth -- I never said that.

I never said lift with indexes is not so easy.
I never said they require constant monitoring.

you could be right -- or wrong in your assumptions.  it all depends on the
nature of the indexed data.  If we do 60/40 block splits, your index might be
almost 50% white space over time, or it could be entirely full.  If we are lucky
enough to be able to detect an increasing value only in the column -- we might
do a nice 90/10 split -- your index would be nicely packed.

but in general -- you need not over monitor your b*trees, they are fairly
efficient structures that take care of themselves for the most part.

November 24, 2003 Sriram -- Thanks for the question regarding
      "Audit / Replication", version 9.0.1.1

      You Asked
I want to build a generic procedure that can be used for maintianing a
transaction log for audit / offline replication purposes. The procedure should
be generic enough to be called by a trigger on any table . But I am running into
the problem of mutating table. I am giving below the partial code for this
generic procedure and the trigger on a sample table. Is there any other way I
can achieve the same thing.
Best regards

CREATE PROCEDURE TEST
(IN_TABLE IN VARCHAR2,
IN_FLAG IN VARCHAR2,
IN_ROWID IN VARCHAR2) IS
SQLSTRING VARCHAR2(200);
V_TABLE VARCHAR2(100);
BEGIN
V_TABLE := 'TMP_' || IN_TABLE;
SQLSTRING := 'INSERT INTO ' || V_TABLE || ' (SELECT * FROM   ' || IN_TABLE || '
WHERE ROWID = '''
|| IN_ROWID || ''')';
EXECUTE IMMEDIATE SQLSTRING;
END;

CREATE OR REPLACE TRIGGER TEST
AFTER INSERT OR DELETE OR UPDATE ON TMP FOR EACH ROW
DECLARE
V_FLAG VARCHAR2(1);
BEGIN
IF INSERTING THEN V_FLAG := 'I';
ELSIF UPDATING THEN V_FLAG := 'U';
ELSE V_FLAG := 'D';
END IF;
TEST('TMP',V_FLAG,:NEW.ROWID);
END ;

UPDATE TMP SET LOTNO='TESTING' WHERE LOTNO='401515';

ORA-04091: table MYL.TMP is mutating, trigger/function may not see it

ORA-06512: at "MYL.TEST", line 11

ORA-06512: at "MYL.TEST", line 8

ORA-04088: error during execution of trigger 'MYL.TEST'

      and we said...
First, thank GOODNESS it didn't work.

No bind variables, none at all, boy -- would that be the worst thing in the
world you could ever consider doing?

Second, the database supports replication out of the box, give that some serious
thought.  It took years to perfect that technology, lots of people hours of
design, thought and test has already taken place -- consider using it instead of
trying to roll your own.

Third, your approach will not work.  What you might consider doing is once again
using a built-in feature of the product.  Workspace management in this case.
Consider:

ops$tkyte@ORA920> CREATE USER wsmgmt IDENTIFIED BY wsmgmt;

User created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> GRANT connect, resource, create table to wsmgmt;

Grant succeeded.

ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2          DBMS_WM.GrantSystemPriv
  3      ('ACCESS_ANY_WORKSPACE, MERGE_ANY_WORKSPACE, ' ||
  4       'CREATE_ANY_WORKSPACE, REMOVE_ANY_WORKSPACE, ' ||
  5       'ROLLBACK_ANY_WORKSPACE', 'WSMGMT', 'YES');
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> @connect scott/tiger
ops$tkyte@ORA920> set termout off
scott@ORA920> REM GET afiedt.buf NOLIST
scott@ORA920> set termout on
scott@ORA920> grant select on emp to wsmgmt;

Grant succeeded.

scott@ORA920> grant select on dept to wsmgmt;

Grant succeeded.

scott@ORA920>
scott@ORA920> @connect wsmgmt/wsmgmt
scott@ORA920> set termout off
wsmgmt@ORA920> REM GET afiedt.buf NOLIST
wsmgmt@ORA920> set termout on
wsmgmt@ORA920>
wsmgmt@ORA920> create table emp as select * from scott.emp;

Table created.

wsmgmt@ORA920> create table dept as select * from scott.dept;

Table created.

wsmgmt@ORA920>
wsmgmt@ORA920> alter table emp add constraint emp_pk primary key(empno);

Table altered.

wsmgmt@ORA920> alter table dept add constraint dept_pk primary key(deptno);

Table altered.

wsmgmt@ORA920> alter table emp add constraint emp_fk_dept foreign key(deptno)
references dept(deptno);

Table altered.

wsmgmt@ORA920> alter table emp add constraint emp_fk_emp foreign key(mgr)
references emp(empno);

Table altered.

wsmgmt@ORA920>
wsmgmt@ORA920> begin
  2          DBMS_WM.EnableVersioning ('emp',  'VIEW_WO_OVERWRITE');
  3          DBMS_WM.EnableVersioning ('dept', 'VIEW_WO_OVERWRITE');
  4  end;
  5  /

PL/SQL procedure successfully completed.

wsmgmt@ORA920>
wsmgmt@ORA920> update emp set sal = sal * 1.10;

14 rows updated.

wsmgmt@ORA920> commit;

Commit complete.

wsmgmt@ORA920>
wsmgmt@ORA920>
wsmgmt@ORA920> select ename, sal, comm, workspace, type_of_change,
  2         to_char(createtime,'dd-mon hh24:mi:ss') created,
  3         to_char(retiretime,'dd-mon hh24:mi:ss') retired
  4    from emp_hist
  5   where ename = 'KING'
  6  /

ENAME             SAL       COMM WORKSPACE                      T CREATED
 RETIRED
---------- ---------- ---------- ------------------------------ -
--------------- ---------------
KING             5000            LIVE                           I 06-jul
08:09:36 06-jul 08:09:50
KING             5500            LIVE                           U 06-jul
08:09:50

wsmgmt@ORA920> select ename, sal, comm
  2    from emp
  3   where ename = 'KING'
  4  /

ENAME             SAL       COMM
---------- ---------- ----------
KING             5500

wsmgmt@ORA920>
wsmgmt@ORA920> update emp set comm = 0.1 * (greatest(0,sal-1000));

14 rows updated.

wsmgmt@ORA920> commit;

Commit complete.

wsmgmt@ORA920>
wsmgmt@ORA920> select ename, sal, comm, workspace, type_of_change,
  2         to_char(createtime,'dd-mon hh24:mi:ss') created,
  3         to_char(retiretime,'dd-mon hh24:mi:ss') retired
  4    from emp_hist
  5   where ename = 'KING'
  6  /

ENAME             SAL       COMM WORKSPACE                      T CREATED
 RETIRED
---------- ---------- ---------- ------------------------------ -
--------------- ---------------
KING             5000            LIVE                           I 06-jul
08:09:36 06-jul 08:09:50
KING             5500            LIVE                           U 06-jul
08:09:50 06-jul 08:09:50
KING             5500        450 LIVE                           U 06-jul
08:09:50

wsmgmt@ORA920> select ename, sal, comm
  2    from emp
  3   where ename = 'KING'
  4  /

ENAME             SAL       COMM
---------- ---------- ----------
KING             5500        450

wsmgmt@ORA920>

You get automagic row level versioning -- your complete audit trail -- with a
single command for any table!!! It too is built in.  See

http://206.204.21.139/otndoc/oracle9i/901_doc/nav/docindex.htm#index-APP
Application Developer's Guide - Workspace Manager

(lastly, your approach will not work as you have discovered.  There are "ways"
to do it but they are fairly complex.  see

http://asktom.oracle.com/~tkyte/Mutate/index.html
but only see that to see that you could write tons of code -- and then don't
write the tons of code, just use the database functionality)

Review &
      Followup

      Rating:
      5
  July 07, 2002
Reviewer:
      A reader
Hi tom,

 I came to know new word "Workspace Manager" in oracle database.

 Just explain in few words what is it? and What we can you whis this new
feature?

Thanks

          Followup:

http://206.204.21.139/otndoc/oracle9i/901_doc/appdev.901/a88806/long_int.htm#1002069
<quote>

 Workspace management refers to the ability of the database to hold different
versions of the same record (that is, row) in one or more workspaces. Users of
the database can then change these versions independently. There are two
fundamental benefits of versioning in a database system:

    * Versioning improves concurrent access of data in the database. In a
database without the versioning ability, users wanting to change the same record
are serialized by means of locks. Relaxing the locking strictness to improve
concurrency leads to undesirable side effects, such as cascading rollbacks.

    * Multiple what-if analyses can be run against the data simultaneously. Each
analysis works on a separate version of the data. After the analyses are
complete, the results can be stored in the database for quick lookup.

The unit of versioning in the product is a database table. A table in the
database can be version-enabled, which means that all rows in the table can now
support multiple versions of data. The versioning infrastructure is not visible
to the users of the database. After a table has been version-enabled, users
automatically see the correct version of the record in which they are
interested.

</quote>

      Rating:
      3
Excellent  November 06, 2002
Reviewer:
      Sanjay  from USA
Wonder when my company will start using 9iR2 - we are still on 8i.
Thanks Tom

      Rating: 3
Performance
      Impact & Audit for Specific Columns   January 23, 2003

Reviewer:  Tony  from India
1.Is there any performance impact in using workspace management?

2.Is it possible to audit specific columns of a table?

          Followup:

1) of course there is.  benchmark it.  nothing but nothing is "free"

2) all or nothing, it does rows

      Rating:
      4
Limitation?  February 04, 2003
Reviewer:
      Doug C  from CT, USA
I just got this error - no hits on metalink -self explanatory

ORA-20171: WM error: version enabled tables cannot have non-primary key unique
index
Is this a hard limitation? Do you know of a workaround?

          Followup:

looks like a hard limitation - still there in 9ir2

      Rating: 4
Best way to
      'implement' this functionality without moving to
      9i  February 05, 2003
Reviewer:  A Reader
       from UK
Tom,

Moving to 9i is being discussed here, but until that is definite I have a
requirement to keep histories of tables.  Unfotunately the tables are in a
third-party application so I would like at all costs to avoid adding triggers to
that application.

I had envisaged some generic package (so we don't have to customise it for every
table, maintain when tables change etc.) so my current thoughts are it might
have to be dynamic sql.

I was playing around with SET operations to see if this may do what I want i.e.:
if I have a table DEPT and a DEPT_YESTERDAY and I do a SELECT * FROM DEPT MINUS
SELECT * FROM DEPT_YESTERDAY, then do them the other way around, I get the
before and after records.  I could then process these using dynamic sql (?) to
end up with a result like:

DEPT_CHANGES (table)
CHANGE_DATE  CHANGED_BY      OLD_VALUE         NEW_VALUE

I'd need to have a strategy for handling changes to the 3rd party app - as my
_YESTERDAY table could potentially be different to my "today" table then - and
that strategy would need to involve minimum maintainance or down-time.

Any thoughts ?  Is this heading in the right direction ?

Regards,

Paul

          Followup:

I don't see how you can keep a history without a trigger.

the minus could help to get a delta -- but if someone updated a record 50 times
in a day and deleted it, you would miss the 50 updates?

You would miss the "who" as well...

If you just need the delta, don't care about the who -- you could consider
creating snapshot logs on the tables (materialized view logs).  This'll give you
a nice table of primary keys -- dml operation (IUD) and time of modification.
You could use that to "speed up" the delta capture (and then purge the logs)

      Rating: 4
More info for
      "Best way to 'implement' this functionality without moving to 9i "
      review  February 05, 2003
Reviewer:  A
      Reader  from UK
Of course, i'd need to have the column name that changed in that example table
also for it to be meaningful !!!

      Rating: 4
RE:
      Implementing without 9i  February 05, 2003

Reviewer:  A Reader  from UK
Tom,

Exactly, the history won't be a complete history - so if "Employee Salary" gets
modified from 30k to 40k back to 30k, we won't know...but thats ok - we're
interested in deltas between one day and another at the end of that day.

The snapshot log etc. - that sounds like it would be as intrusive as adding
triggers though unless i'm mistaken as to how that would work ?  I don't want to
switch on logging, add any objects etc. etc. in the source schema.

Given this - and the need for it to be i) minimum coding/maintainance (including
when the source app changeS) and ii) to work for many tables without lots of
coding and iii) to get the data into the format above....(ok so i've really
narrowed my options by this stage) how would you  do it ?

Thanks in advance,

Paul

          Followup:

then you will end up diffing the entire database every night which sounds
onerous to me.

but only you know if you have a window of time large enough to do it.

I wouldn't do it -- I mean I would SERIOUSLY question why I was doing it.  I
would ask "well, why doesn't the application do it".  I would push back so hard
-- they wouldn't want to make me do it anymore.

but if forced to do it -- i would might a schema "s1".  I would just:

create table t1 as select sysdate dt, t1.* from app.t1 where 1=0;

for each table.

And then

insert into t1 select ... minus union all select ... minus ....

to get the diffs (might give serious consider to partitioning by date -- a day
per partition or something like that).  Then, when "upgrade" comes along or just
ever 6 months -- create schema S2 and do it all over again.

      Rating: 4
So how to audit
      if you want 50 updates and a delete  February 05, 2003

Reviewer:  Doug C  from CT, USA
So Tom - if I need to keep a history of many tables - and workspace manager
isn't going to work because I have a lot of non-primary key unique indexes (I
have filed an enhancement request (can I push that at all or is it just a matter
of how many people want it?) ).. and I can't diff the database because if a
record is updated 50 times and deleted then I  need all 50 updates and the
delete - am I back to good old fashioned triggers and audit tables? with a
little 'I','U','D' flag ?

          Followup:

enhancement requests -- function of business case, number of requests and user
voting.

yup on the trigger.

      Rating: 3
Feature is
      useless  February 06, 2003
Reviewer:  Tony
       from INDIA
I thought workspace manager is a wonderful stuff to be used for auditing. After
knowing the limitations such as version enabled tables cannot have non-primary
key uique index and  specific columns cannot be audited, I realize that the
feature is not of much use. one limitation could stop the use of a good feature.

Oracle corp didn't think before adding these features?

      Rating: 4
RE:
      Implementing without 9i February 05, 2003   February 06,
      2003
Reviewer:  A Reader  from UK
Tom,

Many thanks as always for your advice on this.

Believe me, if there was a way not to do this I would but as I'm sure you're
aware things aren't always so clear-cut in the real world...'pushing back' works
fine if what you are pushing for can be achieved at reasonable cost and there's
no political issues (that can't be overcome - say for example "we're not giving
any non-essential work to the third party provider" - if I can do this another
way and it doesn't cost the earth or cause major problems, the third-party app
change work becomes 'non-essential' and therefore won't get approved.  (hope
that makes sense)

So...there is a third-party app, I don't *want* to add any objects to its schema
(no-one would actually stop me) - I'm sure I don't need to list the reasons for
not wanting to do that.  Now... I *need* to refresh a data warehouse daily from
this 3rd party app anyway, so I have 'yesterdays' table.  'todays' table will
have a 'last modified' column so I don't need to diff the entire database.. and
this is a few specific tables....26000 rows today - maybe double every year but
probably not that aggressive growth.

So I guess what I'm saying is - I'm not doing this thinking its the best
solution, I'm taking the political, technical, economic factors into account and
saying 'this is the best job I can do'.

Given that, and a table full of 'diffs' as we've discussed using MINUS's...I
guess the bit i'm 'stuck' on is how to turn this into a single change record.

i.e. FROM:

Mod_Date  Mod_By pk_field number  colour
17-FEB-02 JD     UID1     3       Green
06-JAN-03 PS     UID1     3       Blue

(where pk_field is the primary key say - so we see that PS changed Field 3 from
Green to Blue today)

TO:

Mod_Date    Mod_By     Mod_Column     Old Value   New Value
06-JAN-03   PS         colour         Green       Blue

Dynamic SQL ?  I was thinking if I join the 'minus' queries to each other on
primary key I would have one row with the old and new records on it, and if I
aliased the tables 'old' and 'new' there might be some way I can go through the
columns comparing values on that single record and 'spit out' the changes I
find.....not sure where to start in Dynamic SQL on this though.

In terms of schema changes, I had some more thoughts.... in my experience 3rd
party apps rarely drop columns.  If they drop tables, I should know about it
beforehand and can 'remove' that table from the compare (leaving the old
histories in-tact), if they add a table, no harm done - I can start capturing it
as soon as I wish, and if they add a column - same.  So schema changes
*shouldn't* (in theory) be as big a deal as I thought as long as I have some
warning.

Thanks Tom,

"A Reader"

          Followup:

Ahh -- data warehouse -- now that is a horse of a different color (or colour as
you would spell ;)

I for some reason thought this was about AUDITING.  for auditing, i wouldn't
like this, for capturing changes to feed a DW, thats something more
interesting....

well, the primary keys should always come in pairs in this mod table so

ops$tkyte@ORA920> select pk_field,
  2         max( decode( rn, 2, mod_date, null ) ) mod_date,
  3         max( decode( rn, 2, mod_by, null ) ) mod_date,
  4         max( decode( fnum, 3, 'color', 4, 'something else', 5, 'etc...' ) )
mod_column,
  5             max( decode( rn, 1, val, null )) old_val,
  6             max( decode( rn, 2, val, null )) new_val
  7    from (
  8  select t.*,
  9         row_number() over (partition by pk_field order by mod_date) rn
 10    from t
 11         )
 12   group by pk_field
 13  /

PK_FI MOD_DATE  MO MOD_COLUMN     OLD_VAL    NEW_VAL
----- --------- -- -------------- ---------- ----------
UID1  06-JAN-03 PS color          Green      Blue
UID2  16-JAN-03 JD color          Red        Yellow

ops$tkyte@ORA920>
ops$tkyte@ORA920>

      Rating:
      5
Excellent  February 06, 2003
Reviewer:
      A Reader  from UK
Hi,

Yes its about Data Warehousing, but kind of Auditing too as people can't see
'what has changed' in the third party app, so I offered them that feature in the
warehouse (as we control that).

Excellent answer....what if I don't have enterprise edition (we're getting it!)
- how can I do that without the analysis functions ?  From what I can see the
analysis function will alternate the row number as 1, 2, 1, 2 so we can decode
the rows into OLD and NEW ?

Also - where does fnum come from ?

We also may have hundreds of columns on the third party app tables that we need
to track so that max, decode etc. could get very messy - what I'm trying now is,
I've got a table with 'before-and-after-picture' side-by-side:

emp_num    last_modified   last_modified_by   old.fielda   new.fielda
old.fieldb   new.fieldb

and I was thinking I could go through each of these change rows (in dynamic
sql), and once I hit 'old.something', get the next value 'new.something', and
create a change record from that (storing the emp_num, last_modified etc. from
the start of the row).  Nasty but achieves the desired effect and I can check
for performance.

I'm doing this at the moment for one table with 1 months changes (will usually
be each night) and it is only taking a few seconds to create the
'before-and-after-side-by-side' picture (and that can be improved on).  So i'm
not too worried about that performance.  I know I can achieve this in dynamic
sql .... but let me know if you think it will fall over and die ! (I'll keep
moving towards that in the mean time)

          Followup:

If you KNOW that there are pairs (each PK_FIELD is in there two times), rownum
after an order by will work

ops$tkyte@ORA920> select t.*,
  2         mod(rownum+1,2)+1 rn
  3    from (select * from t order by pk_field, mod_date) t
  4  /

MOD_DATE  MO PK_F       FNUM VAL                RN
--------- -- ---- ---------- ---------- ----------
17-FEB-02 JD UID1          3 Green               1
06-JAN-03 PS UID1          3 Blue                2
17-FEB-02 PS UID2          3 Red                 1
16-JAN-03 JD UID2          3 Yellow              2

ops$tkyte@ORA920> select pk_field,
  2          max( decode( rn, 2, mod_date, null ) ) mod_date,
  3          max( decode( rn, 2, mod_by, null ) ) mod_date,
  4          max( decode( fnum, 3, 'color', 4, 'something else', 5, 'etc...' ) )
mod_column,
  5          max( decode( rn, 1, val, null )) old_val,
  6          max( decode( rn, 2, val, null )) new_val
  7    from (
  8  select t.*,
  9         mod(rownum+1,2)+1 rn
 10    from (select * from t order by pk_field, mod_date) t
 11         )
 12   group by pk_field
 13  /

PK_F MOD_DATE  MO MOD_COLUMN     OLD_VAL    NEW_VAL
---- --------- -- -------------- ---------- ----------
UID1 06-JAN-03 PS color          Green      Blue
UID2 16-JAN-03 JD color          Red        Yellow

fnum was your "number" field from your table...

      Rating: 3
Re:
      Pairs  February 06, 2003
Reviewer:  A Reader
       from UK
Tom,

I know that there will be pairs, but I think the number was misleading in my
example.  That was just a field.  So as per my review above, there could be
hundreds of fields returned by the set operation so I'll have

old.field1 new.field1 old.field2 new.field2

I don't think this will work in that case and I'm back to my dynamic sql
question above unless you can think of a better way ?

          Followup:

it'll work -- you would just need dynamic sql to apply.

why won't it work?

      Rating: 3
Why it wouldn't
      work  February 06, 2003
Reviewer:  A Reader
       from UK
The example appears to assume that when fnum = 3 the field's value is colour,
but my original example the column was just a number field that happened to be 3
throughout (bad example maybe).... when there are 200 columns on that table
(pretend fnum doesn't exist - ignore it) how will the decode work to show which
column changed and what its value was?

as above.... the table I will have is

pk_field (modified date, modified by etc.) old.column1 new.column1 old.column2
new.column2 old.column3 new.column3 etc.

I could have:

pk_field old.column1 old.column2 old.column3
pk_field new.column1 new.column2 new.column3

But I thought it would be easier to have a single row with the before and after
picture on it.  I think we're probably both working from a different source
result now - sorry for any confusion!

I'll continue to play and post my results to see if its the best way of doing it
(unless its clear from this what I'm suggesting!?)

Regards,

Paul

          Followup:

Mod_Date  Mod_By pk_field number  colour
17-FEB-02 JD     UID1     3       Green
06-JAN-03 PS     UID1     3       Blue
                                  ^^^^^^^^

apparently i didn't need the decode, my "val" field is your "colour" field.

ops$tkyte@ORA920> select pk_field,
  2          max( decode( rn, 2, mod_date, null ) ) mod_date,
  3          max( decode( rn, 2, mod_by, null ) ) mod_date,
  5          max( decode( rn, 1, colour, null )) old_colour,
  6          max( decode( rn, 2, colour, null )) new_colour,
             max( decode( rn, 1, column2, null )) old_column2,
             max( decode( rn, 2, column2, null )) old_column2,
             .........
  7    from (
  8  select t.*,
  9         mod(rownum+1,2)+1 rn
 10    from (select * from t order by pk_field, mod_date) t
 11         )
 12   group by pk_field
 13  /

      Rating: 5
A
      question  April 15, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Islamabad, Pakistan
Hello Tom !

From "Oracle Workspace Manager - Release 9.2.0.2.0 README"

This Oracle Workspace Manager 9.2.0.2.0 patch set replaces your existing Oracle
Workspace Manager installation. It should be used with the following supported
Oracle Database Releases: 9.2.0.2, 9.0.1.4 and 8.1.7.4.

The minimum supported version of the Oracle database is 8.1.7.

And

cr@STARR.LHR> select * from V$version
  2  ;

BANNER
============================================================
Oracle8i Enterprise Edition Release 8.1.7.2.1 - Production
PL/SQL Release 8.1.7.2.0 - Production
CORE    8.1.7.2.1       Production
TNS for 32-bit Windows: Version 8.1.7.2.0 - Production
NLSRTL Version 3.4.1.0.0 - Production

Does that means that i can't use Workspace Manager ???

          Followup:

correct -- you are running unsupported software.  8174 is the only supported 817
release right now.

      Rating: 4
Workspace
      Management Clarification.  June 16, 2003
Reviewer:
      Matt  from Australia
I need to audit changes to table in a system. So, when any data is changed I can
see who made the change and identify the data that changed. This is purely for
audit purposes, although in some cases it might be necessary  to display the
earlier versions of data to the user alongside the latest data.

In the doco, I took it that the old versions are stored in the same table as the
original data. Is this the case, some examples I have seen appear to store the
original data in emp and the earlier versions in emp_hist.

Is there any (software) limit on the number of versions of data that can be
stored in this way? I am expencting to be able to set of a workspavce for the
duration of my system (10+ years)

Is it possible to write reports on the historic data, so that you can see the
contents of a report at some point in time?

THanks in advance.

          Followup:

EMP and EMP_HIST are views of a table.

there is a "goto" function in the DBMS_WM package to let you "goto" a point in
time and queries against the data will be "as of" that time.

You need to read:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-APP
    Application Developer's Guide - Workspace Manager

from cover to cover and you must benchmark this feature to ensure it meets
your needs before designing a system around it! (as it true of any
feature/function)

      Rating: 3
Additional to
      previous post  June 16, 2003
Reviewer:  Matt
       from Australia
I would also like to store a free text field to store a comment against the data
change. Can this be done using workspace management or do I need to re-consider
my approach?

Also, how does workspace managements impact in FK contraint definition? Does the
FK enforce integrity between the correct rows of versioned data in two tables
for instance?

THanks again

          Followup:

lobs are supported if that is what you mean by "free text field"

foreign keys are properly handled, yes.

      Rating: 3
Workspace
      Manager Pain Reliever Needed...  October 03, 2003

Reviewer:  Robert  from CT
9.2.0.1.0 on Linux

Tom, which part of Oracle is right here ?
Thanks

SQL> drop table BNFCY_CVG_LT ;
drop table BNFCY_CVG_LT
*
ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 1
ORA-20061: versioned objects have to be version disabled before being dropped
ORA-06512: at "SYS.WM_ERROR", line 288
ORA-06512: at "SYS.WM_ERROR", line 297
ORA-06512: at "SYS.NO_VM_DROP_PROC", line 60
ORA-06512: at line 3

SQL> exec dbms_wm.disableversioning('BNFCY_CVG_LT')
BEGIN dbms_wm.disableversioning('BNFCY_CVG_LT'); END;

*
ERROR at line 1:
ORA-20132: table 'BNFCY_CVG_LT' is not version enabled
ORA-06512: at "SYS.WM_ERROR", line 288
ORA-06512: at "SYS.WM_ERROR", line 305
ORA-06512: at "SYS.LT", line 695
ORA-06512: at "SYS.LT", line 8114
ORA-06512: at line 1

          Followup:

please contact support.

apparently they are both right.

      Rating: 3
workspace
      manager - please help!  October 23, 2003
Reviewer:
      Charlie
Hi Tom,

Do you have a workaround for the following issue?

ORA-20109: a table with unique constraints cannot be version enabled

It seems that workspace manager cannot work with a table with unique
constraints.  But we have lots of tables with unique constraints.  That means we
cannot use workspace manager at all?

thanks,

          Followup:

10g can, 9i cannot.

      Rating: 3
workspace
      manager - please help!  October 23, 2003
Reviewer:
      Charlie
Oh, no.  How frustrated!

      Rating: 4
WM for auditing
      in 9i  November 07, 2003
Reviewer:  Oleg
      Oleander  from Hungary
Robert: try
exec dbms_wm.disableversioning('BNFCY_CVG')
instead of
exec dbms_wm.disableversioning('BNFCY_CVG_LT'). since BNFCY_CVG is the name of
your version-enabled table.

Tom,

It seems to me that you cannot have unique constraints because oracle-wm stores
all versions of a row in the same table (tabname_LT). For the primary key of the
original table the VERSION and DELSTATUS columns are added. What if we add
unique constarints manually in the same fashion AFTER the table would have been
verion-enabled?
1: Do you think Tom, it's a possible solution for having unique constraints on
version-enabled tables?
2: Is there a way using WM to separate historical data from current? (store
separately)

Thanks
Oleg

          Followup:

in 10g this restriction was removed (you can have unique constraints in addition
to the primary key)

1) in 10g, yes, in 9i, no
2) no

      Rating: 5
FlashbackQuery
      instead of WM.gotodate  November 11, 2003

Reviewer:  Oleg Oleander  from Hungary
If I set the Undo_retention sufficiently high and ensure disk space for the undo
segment, could  I use flashback query to view the db as it existed, say, two
month ago?
(suppose low DML activity)

          Followup:

theoretically -- by SCN -- (not by time, we only keep the last 1440 5 minute scn
to timestamp mappings)

      Rating: 4
Workspace
      Manager could be the future / limitation?   November 23,
      2003
Reviewer:  Doug  from CT, USA
Hey Tom - I think the enhancement of workspace manager is probably the future of
databases.   Here's another limitation I found.  When enabling versioning, it
turns my table into a VIEW according to the data dictionary.  Unforunately, I
now cannot implement a trigger on the view (which used to be a table).
Do you concur?

create table salary (
   name varchar2(10),
   salary number(10),
   title varchar2(20),
    dateofbirth date,
    starttime date,
   stoptime date);

SQL> alter table salary add constraint aprimarykey primary key
   (name, dateofbirth);

SQL> begin
            DBMS_WM.EnableVersioning ('salary',  'VIEW_WO_OVERWRITE');
end;  2    3
  4  /

  1  create trigger x before update of salary
  2  on salary
  3  for each row
  4  begin
  5  null;
  6* end;
SQL> /
create trigger x before update of salary
               *
ERROR at line 1:
ORA-25001: cannot create this trigger type on views

How do I file an enhancement request?

          Followup:

you put the trigger on the base TABLE.

November 24, 2003 Sagi -- Thanks for the question regarding "MTS
      Configuration", version 8.1.7

      You Asked
Hi Tom !

I have configured MTS. My INIT.ORA and tnsnames.ora file configurations are
given below.

I have given PORT=1528 in the INIT.ORA file and when I query the V$DISPATCHER, I
am surprised to see that the PORT numbers are chaning always i.e if I restar the
DB.

If this is a normal behaviour then what PORT no should I give int he TNSNAME.ora
agains the mts_conn (my host string). With My setting when I gave PORT nos
between 1528 to 1532, I am not able to establish a connection using SQLPLUSW.
But if I change the PORT to any no between 1741 .. 1746. It works.

So I wanted you to clarify the below doubts:
1. Is my MTS configuration working.
2. Why does the PORT nos not the same what i gave in INIT.ORA file and change
whenever I start the DB.
3. If this is normal then what port number should i give in TNSNAMES.ora against
my connect string

I have given the output of V$DISPATCHER, V$SESSION and V$QUEUE

Parameters added for MTS in INIT.ora File
=========================================
mts_service=ORCL
mts_dispatchers="(address=(protocol=tcp)(host=10.11.225.203))(dispatchers=5)(port
=1528)"

Entry in TNSNAME.ORA File
=========================
mts_conn =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = 10.11.225.203)(PORT=1528))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = ORCL) (server=shared)
    )
  )

Output:
=======

1. select * from v$dispatcher
NAME NETWORK
---- ----------------------------------------------------------------------
D000 (ADDRESS=(PROTOCOL=tcp)(HOST=10.11.225.203)(PORT=1741))
D001 (ADDRESS=(PROTOCOL=tcp)(HOST=10.11.225.203)(PORT=1742))
D002 (ADDRESS=(PROTOCOL=tcp)(HOST=10.11.225.203)(PORT=1744))
D003 (ADDRESS=(PROTOCOL=tcp)(HOST=10.11.225.203)(PORT=1745))
D004 (ADDRESS=(PROTOCOL=tcp)(HOST=10.11.225.203)(PORT=1746))

2. select username, server, status from v$session ;

USERNAME                       SERVER    STATUS
------------------------------ --------- --------
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
SYSTEM                         SHARED    ACTIVE

3. SELECT * FROM V$QUEUE ;

PADDR    TYPE           QUEUED       WAIT     TOTALQ
-------- ---------- ---------- ---------- ----------
00       COMMON              0          0        121
0264E194 DISPATCHER          0          0        130
0264E4BC DISPATCHER          0          0          0
0264E7E4 DISPATCHER          0          0          0
0264EB0C DISPATCHER          0          0          0
0264EE34 DISPATCHER          0          0          0

Thanx in advance.

Regards,
Sagi.

      and we said...
The dispatches start on "wildcard" ports.

The listener is running on port 1528.

The dispatchers contact the listener (running on a well known IP+PORT) and tell
it "hey, we are on port XXXXX".

The client then connects to the listner running on the well known IP+PORT
combination and the listener tells the client "hey, connect to IP+XXXX" (they
get redirected)...

Now, since you have 5 dispatchers and since by definition only a SINGLE
process/thread can ever listen on a given IP+PORT combination -- you would
already have an issue (5 dispatchers need 5 ports!)

What you are seeing is normal and correct.  Besides, the port should be in the
ADDRESS to control the port of the dispatchers (cut and pasted from the docs):

Setting Dispatcher Addresses

Example 1

To force the IP address used for the dispatchers, set the following:

mts_dispatchers="(address=(protocol=tcp)(host=144.25.16.201)) (dispatchers=2)"

This starts two dispatchers that listen on HOST=144.25.16.201. Note that Net8
dynamically selects the TCP/IP port for the dispatcher.

Example 2

To force the exact location of the dispatchers, add the PORT as follows:

mts_dispatchers="(address=(protocol=tcp)
(host=144.25.16.201)(port=5000))(dispatchers=1)"
mts_dispatchers="(address=(protocol=tcp)
(host=144.25.16.201)(port=5001))(dispatchers=1)"

Review &
      Followup

      Rating:
      3
  September 16, 2002
Reviewer:  Sagi
       from India
Thank for the explanation. I Got it.

Regards,
Sagi.

      Rating:
      3
  September 16, 2002
Reviewer:  Sagi
       from India
I have one more confussiong. You said

"Now, since you have 5 dispatchers and since by definition only a SINGLE
process/thread can ever listen on a given IP+PORT combination -- you would
already have an issue (5 dispatchers need 5 ports!)"

Did you mean to say that in TNSNAMES.ora file, I whould have had 5 PORTs defined
like:

mts_conn =
(DESCRIPTION =
(ADDRESS_LIST =
 (ADDRESS = (PROTOCOL = TCP)(HOST = 10.11.225.203)(PORT=1528))
 (ADDRESS = (PROTOCOL = TCP)(HOST = 10.11.225.203)(PORT=1529))
 (ADDRESS = (PROTOCOL = TCP)(HOST = 10.11.225.203)(PORT=1530))
 (ADDRESS = (PROTOCOL = TCP)(HOST = 10.11.225.203)(PORT=1531))
 (ADDRESS = (PROTOCOL = TCP)(HOST = 10.11.225.203)(PORT=1532))
 )
 (CONNECT_DATA =
   (SERVICE_NAME = ORCL) (server=shared)
  )
)

or Should I modify INIT.ora as

mts_dispatchers="(address=(protocol=tcp)
(host=10.11.225.203)(port=1528))(dispatchers=1)"
mts_dispatchers="(address=(protocol=tcp)
(host=10.11.225.203)(port=1529))(dispatchers=1)"
mts_dispatchers="(address=(protocol=tcp)
(host=10.11.225.203)(port=1530))(dispatchers=1)"
mts_dispatchers="(address=(protocol=tcp)
(host=10.11.225.203)(port=1531))(dispatchers=1)"
mts_dispatchers="(address=(protocol=tcp)
(host=10.11.225.203)(port=1532))(dispatchers=1)"

Also I need to have my LISTNER.ora modified to listen on other PORTS ie. 1529 to
1532. Right?

Regards,
Sagi

          Followup:

No, LISTENER = one IP+PORT needed (one process, one thing)

5 dispatchers = you need 5 ports on a single IP since no more then one
dispatcher can listen on any given PORT+IP address.

No modifications to the listener.ora file please!  Nothing wrong with what you
had here, you are trying to configure DISPATCHERS, not listeners.

      Rating: 2
why my init.ora
      is different?  September 16, 2002
Reviewer:
      zl  from china
my init.ora is:
mts_dispatchers="TCP,5"
mts_max_dispatchers=20
mts_servers=10
mts_max_servers=80
mts_service=itsm
mts_listener_address="(ADDRESS=(PROTOCOL=TCP) (HOST=134.163.6.21))"

and the port is fixed, equal the define of listener.ora, no change need to
tnsnames.ora.

          Followup:

your init.ora is different cause you are not Sagi.

Why would you expect it to be the same?

      Rating:
      3
  September 17, 2002
Reviewer:  Sagi
       from India
Hi Tom!

I am sorry, could not get your point. You said:

"
No, LISTENER = one IP+PORT needed (one process, one thing)

5 dispatchers = you need 5 ports on a single IP since no more then one
dispatcher can listen on any given PORT+IP address.

No modifications to the listener.ora file please!  Nothing wrong with what you

"

So you mean to say that I will have to modify like this

a) My INIT.ORA as
mts_dispatchers="(address=(protocol=tcp)
(host=10.11.225.203)(port=1528))(dispatchers=1)"
mts_dispatchers="(address=(protocol=tcp)
(host=10.11.225.203)(port=1529))(dispatchers=1)"
mts_dispatchers="(address=(protocol=tcp)
(host=10.11.225.203)(port=1530))(dispatchers=1)"
mts_dispatchers="(address=(protocol=tcp)
(host=10.11.225.203)(port=1531))(dispatchers=1)"
mts_dispatchers="(address=(protocol=tcp)
(host=10.11.225.203)(port=1532))(dispatchers=1)"

or

b) My TNSNAMES.ora should have
mts_conn =
(DESCRIPTION =
(ADDRESS_LIST =
 (ADDRESS = (PROTOCOL = TCP)(HOST = 10.11.225.203)(PORT=1528))
 (ADDRESS = (PROTOCOL = TCP)(HOST = 10.11.225.203)(PORT=1529))
 (ADDRESS = (PROTOCOL = TCP)(HOST = 10.11.225.203)(PORT=1530))
 (ADDRESS = (PROTOCOL = TCP)(HOST = 10.11.225.203)(PORT=1531))
 (ADDRESS = (PROTOCOL = TCP)(HOST = 10.11.225.203)(PORT=1532))
 )
 (CONNECT_DATA =
   (SERVICE_NAME = ORCL) (server=shared)
  )
)

q1) Which one is correct. Bullet a or b?

q2) Since I wanted to have one dedicated port and the other one for MTS. I need
to have my LISTNER.ora as below. Right?

LISTENER =
  (DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS_LIST =
        (ADDRESS = (PROTOCOL = TCP)(HOST = 10.11.225.203)(PORT = 1521))
        (ADDRESS = (PROTOCOL = TCP)(HOST = 10.11.225.203)(PORT = 1528))
      )
      (ADDRESS_LIST =
        (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC0))
      )
    )
  )

Thanx in advance.

Regards,
Sagi.

          Followup:

a and b are apples and zebras.

one has *nothing* to do with the other.

a) will be fine - ONCE you stop using 1528 which is already being used by your
LISTENER

b) would break your system. clients would not be able to connect.

Your client uses the tnsnames.ora to find a listener.  Your database, after it
starts the dispatchers, registers their addresses with the listener.

Your client will connect to the listener.  The listener is running on some fixed
IP address with a well known Port.  The listener will look at the pool of
dispatchers it has available and assign one to your client.  Your client will
stop talking to the listener and will connect to the dispatcher.

Please -- read the networking guide.  All of this is actually explained in
there.

(and no, you don't need two ports to do dedicated versus shared either, you can
but you don't NEED to.  A single listener port can do both)

      Rating:
      2
  September 17, 2002
Reviewer:  zl
       from china
you say "your init.ora is different cause you are not Sagi", but what's the
meaning for "sagi"

          Followup:

Sagi is the person who posted the original init.ora -- they asked the question.

      Rating:
      5
(.)(.)  September 17, 2002
Reviewer:
      Juan Guascarancas Pena  from Mexico
>>>> QUOTE:

To force the exact location of the dispatchers, add the PORT as follows:
mts_dispatchers="(address=(protocol=tcp)
(host=144.25.16.201)(port=5000))(dispatchers=1)"
mts_dispatchers="(address=(protocol=tcp)
(host=144.25.16.201)(port=5001))(dispatchers=1)"

>>>>> END QUOTE.

And this is the best way to set up Oracle to be used through a firewall. You
must know which ports to allow...

      Rating:
      5
  December 20, 2002
Reviewer:
      A reader
Hi Tom,

I installed oracle 9iR2 and started the database.I found it is using MTS (shared
server) configuration.

Here is the parameters
SQL> sho parameter mts

NAME                                 TYPE    VALUE
------------------------------------ ------- -------------------
mts_circuits                         integer 170
mts_dispatchers                      string  (protocol=TCP)
mts_listener_address                 string
mts_max_dispatchers                  integer 5
mts_max_servers                      integer 20
mts_multiple_listeners               boolean FALSE
mts_servers                          integer 1
mts_service                          string  Zebra
mts_sessions                         integer 165

I want to use DEDICATED configuration.
How can I do that ?

Thanks

          Followup:

unset the parameters

or

put (server=dedicated) in the tnsnames.ora entry

      Rating:
      5
  December 21, 2002
Reviewer:
      A reader
Hi ToM,
 I set entry IN TNSNAMES.ORA but i am getting error message

SQL> conn gaurang/gaurang@ora9
ERROR:
ORA-12500: TNS:listener failed to start a dedicated server process

Warning: You are no longer connected to ORACLE.

Hre is my TNSNAMES entry--

ora9.WORLD=
  (DESCRIPTION =
    (ADDRESS_LIST =
        (ADDRESS =
          (PROTOCOL = TCP)
          (HOST = techsupport)
          (PORT = 1521)
        )
    )
    (CONNECT_DATA = (SID=zebra)(SERVICE_NAME=zebra)
    (SERVER = DEDICATED)
    )
  )

If I remove (SERVER = DEDICATED) it is connecting to database is shared server
mode.

Thanks.

          Followup:

get rid of either the sid or the servicename -- you use either or, not both.

use net assistant to get a well formed TNSNAMES.ORA entry.

      Rating:
      5
Reader  January 08, 2003
Reviewer:
      A reader
Tom,

How does "dynamic service registration" works

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/paraserv.817/a76934/chap5.htm
The Example shows:

listener=
 (description=
  (address=
   (protocol=tcp)
   (host=idops1)
   (port=1521)))

is an adequate listener.ora configuration

Thanks

          Followup:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/paraserv.817/a76934/glos.htm#1000215
tells you how dynamic service registration works (pmon contacts a listener,
tells listener about server)

It could be adequate.

      Rating: 5
if tnsnames.ora
      not presnt on client?  January 30, 2003
Reviewer:
      learningOracle  from Houston
If the client machine does not have a tnsnames.ora (ex. a browser based
application), how would one find out the mode they are connected in?

ie. connected in dedicated server mode or shared server mode?
Is there a tnsnames.ora on the server that would have this info?
or is it present in the oracle ini file, if so what parameter is it?

Thank you

          Followup:

the client isn't the browser then.

The client is an application server.  The browser is the client of the app
server, the app server IS the client.

just select SERVER from v$session as shown above -- that is the only way to
truly know.  The tnsnames.ora is the way to request a connection, SERVER shows
exactly how a session is established.

      Rating: 3
Roll of
      dispatcher and server process.  April 28, 2003

Reviewer:  Reader  from USA
Hi Tom

This might be a silly and very basic question but pls. let me ask.
In your book you have written that when user request for a connection, TNS
service sends the request to Listener and Listener connects it to the dispatcher
and then dispatcher sends the request to any free shared server process.
Suppose If we have 10 dispatcher and 10 shared server and 10 users are connected
to 10 dispatchers and processing is going on for all the connections then what
will happen with 11th user. Would it get connection or not?

Pls explain me the roll and functionality of dispatchers and shared server
processs?

Thanks.

          Followup:

you would in this configuration have a single dispatcher and 10 shared servers.
I cannot imagine having parity between dispatchers and shared servers.

A single dispatcher is designed to handle hundreds of concurrent connections.

The dispatcher takes a request from a client and puts it on a queue.
The dispatcher sees responses in the queue and sends them to the client.

A shared server sees a request in the queue and processes it.
A shared server puts a response back on the queue.

      Rating: 4
Great, can you
      help me about this problem?  June 12, 2003

Reviewer:  Jack  from Chicago, IL USA
Hi Tom,
I use intermedia text with Oracle 8.1.7.4.
1. Do I need to set MTS for IPC in order to make intermedia text work? Or I just
need to use TCP for MTS. Oracle told me there is no need to use IPC from 8.1.7
for MTS?
2. After I setup MTS and use "select username,server,status from v$session",
it's shared server. But Toad in my local PC doesn't work now and also the Net8
Assistant service naming testing shows a problem to connect to server1 with
error: ORA-12535 TNS:operation timed out.
tnsping works fine from local PC.
Here are my config files:
initORCL.ora:
db_name = "ORCL"
db_domain = SERVER1
instance_name = ORCL
service_names = ORCL.SERVER1
...
mts_dispatchers="(address=(protocol=tcp)(host=server1))(dispatchers=1)"
mts_max_dispatchers=10
mts_servers=2
mts_max_servers=10
mts_listener_address="(ADDRESS=(PROTOCOL=tcp)(host=server1)(port=1522))"

listener.ora:
LISTENER =
  (DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS_LIST =
        (ADDRESS = (PROTOCOL = TCP)(HOST = server1)(PORT = 1522))
      )
      (ADDRESS_LIST =
        (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC0))
      )
    )
  )

SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (SID_NAME = PLSExtProc)
      (ORACLE_HOME = /u01/app/oracle/product/8.1.7)
      (PROGRAM = extproc)
    )
    (SID_DESC =
      (GLOBAL_DBNAME = ORCL.SERVER1)
      (ORACLE_HOME = /u01/app/oracle/product/8.1.7)
      (SID_NAME = ORCL)
    )
  )

tnsnames.ora:
ORCL.SERVER1 =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.8.25)(PORT = 1522))
    )
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = SERVER1)(PORT = 1522))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = ORCL.SERVER1
      (SERVER = SHARED)
    )
  )

LISTENER_ORCL =
  (DESCRIPTION =
     (ADDRESS_LIST =
        (ADDRESS = (PROTOCOL = TCP)(HOST = SERVER1)(PORT = 1522))
     )
     (CONNECT_DATA =
        (SERVICE_NAME = ORCL.SERVER1)
        (SERVER = SHARED)
     )
   )

INST1_HTTP =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = SERVER1)(PORT = 1522))
    )
    (CONNECT_DATA =
      (SERVER = SHARED)
      (SERVICE_NAME = ORCL.SERVER1)
      (PRESENTATION =
http://admin/
    )
  )

extproc_connection_data =
  (DESCRIPTION=
     (ADDRESS_LIST =
         (ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC0))
     )
     (CONNECT_DATA=
         (SID=PLSExtProc)
         (PRESENTATION = RO)
     )
  )

Local tnsnames.ora:
SERVER1 =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.8.25)(PORT = 1522))
    )
    (CONNECT_DATA = (SERVICE_NAME = ORCL.SERVER1)(SERVER = SHARED))
  )

We use firewall to open only port 1522. It works fine from web application.
Please help. Thanks a lot!

Jack

          Followup:

1) if they said specifically "no", that would be the answer.  They are fairly
accurate with setup and configuration issues.

2)

your firewall has 1522 open.  great, tnsping can get to a listener.

HOWEVER, the dispatcher, the thing you really ultimately need to get to -- isn't
(by definition cannot be) on 1522.  It is on a wildcard port (some number,
unknown, the OS gives it to us).  You can LIMIT this range of ports, but that is
about it.

You would be looking at setting up CMAN (connection manager) at that point to
connect via shared server thru a firewall.

      Rating: 4
Thanks Tom,
      just want to confirm something?  June 13, 2003

Reviewer:  Jack  from Chicago, IL USA
Hi Tom,
Thanks, I just want to confirm something from you:
1. MTS dispatcher must use different port number with Listener?
2. If I use a special port for dispatcher, such as 1525, and open that port in
firewall, is that ok?
3. CMAN will fix the client PC Net8 connection. I don't need to change
configuration files in server except updating the tnsnames.ora file with en
entry using the CMAN.

Thanks,

Jack

          Followup:

1) yes, they are separate processes
2) probably
3) using cman will fix the port redirection that would otherwise occur.  You run
cman behind the firewall, it connects as normal on your behalf (it gets
redirected).  you use a single port to connect to it.  you would just change the
tns connect string, yes.

      Rating: 4
Thanks, it
      works with dedicated server for PC client.  June 13, 2003

Reviewer:  Jack  from Chicago, IL USA
Hi, Tom,
I changed the local PC's tnsnames.ora to use (CONNECT_DATA = (SERVICE_NAME =
ORCL.SERVER1)(server=dedicated))
Now, the client PC(Toad and sqlplus etc.) can connect to server.
But just find something like below:
SQL> select username,server,status from v$session:
ELI                            SHARED    INACTIVE
ELI                            NONE      INACTIVE
ELI                            NONE      INACTIVE
ELI                            SHARED    INACTIVE

I remember in one of your post, you said the inactive with shared is not
correct. I am searching your post now.

Thanks,

Jack

          Followup:

i've not seen inactive from a shared connection, no.

it would be especially confusing since you KNOW at least one must be active --
the one running the query.

(and oh, none of your connections there are dedicated, they are all shared...)

      Rating: 5
shared and
      dedicated  August 18, 2003
Reviewer:
      A reader
When would you suggest using a shared connection
as well as dedicated connections? Is this a common
configuration?

Thanx!

          Followup:

you use shared when you run out of "ompff" on the machine -- when you cannot get
another dedicated server connection. when you need to connect one more session
but cannot do so using a process/user

it takes about the same ram (shared server just moves the UGA from the PGA into
the SGA) but less processes.  It can reduce the resources on tha machine in that
fashion -- but, the code path is longer, you don't want to use it till you have
to.

Or, if you are using a feature that requires it -- like database link
concentration for example...

      Rating: 4
mixed
      environment  August 19, 2003
Reviewer:
      Vipin  from New york
Hi Tom,

Suppose my system is 90% strictly OLTP and 10% "not much" of an OLTP (like it as
a report rendering functionality which runs a big query), how can I go for mixed
enviroment here.

Basically my doubt is how can we configure both MTS and decicated together.

          Followup:

it is always that way if you have mts setup?  if you have mts setup, you can
connect dedicated

just use server=dedicated in the tns setup

      Rating: 5
Will response
      time be noticeably slower?  August 19, 2003

Reviewer:  Kamal Kishore  from New Jersey, USA

You said above:

<quote>
but, the code path is longer, you don't want to use it till you have
to.
</quote>

Would that mean that applications that used to connect using DEDICATED
connection (and now switch to SHARED connection mode) will see a noticeable
difference in application response time?
Is that a concern we should be worried about?
Thanks,

          Followup:

if you are on a machine that is running "top speed" with dedicated server --
going shared will only make it "slower".

shared server is for when you need to get that extra connection.

      Rating:
      3
  August 20, 2003
Reviewer:  Vipin
       from NY
Hi Tom,

You said we have to use server=dedicated in tns, but how will ensure that for
certain package executions I must use dedicated in my Application (like the one
which I indicated as a report generation) and for all others I need to use MTS
(say for online workflow activities.)

If I set up server = shared in the tnsnames, in Application server it will
always use MTS (provided MTS_ parameters are also set in init.ora file) and
server=dedicated will always use dedicated right? Or if I am wrong here please
correct me.

          Followup:

you will have two tns connect strings.

one with server=dedicated
one without.

they (the app) will pick which connection type they want based on which tns name
they use.

      Rating: 5
ORA-24777 use
      of non-migratable database link not allowed  August 20, 2003

Reviewer:  Kamal Kishore  from New Jersey, USA

Hi Tom,
The reason I asked the question about the switch to MTS configuration is because
at client site they hit the error:

=========================================================
ORA-24777 use of non-migratable database link not allowed

Cause: The transaction, which needs to be migratable between sessions, tried to
access a remote database from a non-multithreaded server process.

Action: Perform the work in the local database or open a connection to the
remote database from the client. If the multithreaded server option is
installed, connect to the Oracle instance through the dispatcher.
==========================================================

The only solution around this seems that MTS be configured in the database (as
suggested) and the application establish a SHARED connection.

This would then mean that the applications that used to connect using DEDICATED
server would now connect using SHARED server instead.

This switch seems inevitable. That is the reason I'm concerned.
Thanks,

          Followup:

what exactly are you doing that requires it?  XA?

      Rating:
      5
MTS???  August 21, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
Hi Tom,
The web application uses the Microsoft Transaction Server (also called MTS) and
MSDTC (Microsoft Transaction Coordinator) to establish a connection to Oracle.

What is happening is that the Microsoft MTS property is set to require
transaction and then it calls a Oracle stored procedure. This stored procedure
then calls another stored procedure in a remote database using database link.
This is when we get the above mentioned error.

The end result is that Microsoft MTS (with transaction support enabled) with
Oracle stored procedure calls using database link under the Oracle DEDICATED
server setup - does not work. It requires that the connection be esablished
using the Oracle SHARED server configuration.

We are currently looking into enabling Oracle SHARED server to circumvent this
issue (or any other approach to avoid this error and still keep the DEDICATED
server setup).
Thanks,

      Rating: 4
4 dispatchers
      on one ip+port   November 21, 2003
Reviewer:  A
      reader  from IND
hi

using 9.2.0

 4 dispatchers working with One PROT ??????

Dispatchers='(ADDRESS=(PROTOCOL=TCP)(HOST=LC)(PORT=1430))(Dispatchers=4)'

SQL> Select * From v$dispatcher;

NAME NETWORK
---- --------------------------------------------------------
D000 (ADDRESS=(PROTOCOL=TCP)(HOST=LC)(PORT=1430))
D001 (ADDRESS=(PROTOCOL=TCP)(HOST=LC)(PORT=1430))
D002 (ADDRESS=(PROTOCOL=TCP)(HOST=LC)(PORT=1430))
D003 (ADDRESS=(PROTOCOL=TCP)(HOST=LC)(PORT=1430))

Thanx

          Followup:

sure?  why not?

      Rating: 3
Re
      :  November 21, 2003
Reviewer:  a reader
       from india
hi .. You said

"5 dispatchers = you need 5 ports on a single IP since no more then one
dispatcher can listen on any given PORT+IP address."

Has this restriction been lifted in 9iR2.

Q2. Sir, If system is loaded with many concurrent connections [ PGA ]  then we
can use pga aggr target then should I go for shared Server.
Actulay I am very much confused with pga/uga/mts.

Thanx

          Followup:

platform?

q2) do you have expert one on one oracle?

      Rating:
      2
Re:  November 22, 2003
Reviewer:  a
      reader  from india
I am using winnt2000 / 91R2.

          Followup:

things are different on windows, its all one big process with threads.

      Rating: 2
artificial
      deadlocks with MTS  November 22, 2003
Reviewer:
      a reader  from INDIA
hello Sir,

using win2000/9ir2

I was reading the third chapter of your new book. In which you mentioned ..

" There is a potential for artificial deadlocks. These occur because once a
shared server
starts processing a request, it will not return until it is finished with it.
Hence, if the
server will block. Suppose the blocking session was idle for a period of time
(not doing
any work in the database so it doesn&#8217;t have a shared server allocated to
it) and all of
the other shared servers get blocked as well by this idle session (or any other
session), then
when the session holding the lock attempts to commit or rollback (which would
release the
locks), it will hang because there are no more free shared servers. This is an
artificial
deadlock situation."

And I did this scenario, but couldn&#8217;t get the desired result.

SQL>
SQL> Select count(*) from a;

  COUNT(*)
----------
      1256

SQL> delete from a;

1256 rows deleted.

SQL> select count(*),server,status from v$session group by server,status;

  COUNT(*) SERVER    STATUS
---------- --------- --------
         1 SHARED    ACTIVE
         6 DEDICATED ACTIVE

SQL> show parameter server

fal_server                           string
max_shared_servers                   integer     20  <<--
mts_max_servers                      integer     20
mts_servers                          integer     3
  parallel .... deleted some text ...
shared_server_sessions               integer     33
shared_servers                       integer     3

Then I made 20+ separate sql*plus sessions and issued this command 'delete from
a'
They all got blocked.

SQL> select count(*),server,status from v$session group by server,status;

  COUNT(*) SERVER    STATUS
---------- --------- --------
         5 NONE      INACTIVE
        20 SHARED    ACTIVE
         6 DEDICATED ACTIVE

Then in the original session I issued this command ...

"Rollback"
my session didn&#8217;t get blocked.

Where was I wong?

Thanks..

          Followup:

you are using a mix of dedicated and shared servers.

You need to make sure you are using only shared servers for your testing -- I
cannot tell that your current session (the blocker) was in fact using shared
server.

      Rating:
      3
re:  November 24, 2003
Reviewer:  a
      reader  from IND
sir,
All the sessions and blocker are connected through shared server
except 6 dedicated conn which are background processes.

          Followup:

have the session that did the rollback do the query on v$session instead -- see
if you don't have 21 shared connections

November 24, 2003 David -- Thanks for the question regarding "will
      delete reclaim disk space for the database?", version 7.3.4

      You Asked
Dear Tom,

I deleted millions of rows in my database. In somewhere, I read
that delete will not release data block space, that is, the
deleted data are still occupying the data block. We have to use
export/import to relcaim the space. It that true?

If we want to purge data older than certain date, sucha as 6
months, periodically, what is the best strategy to do it?

Thanks a lot for your earlier and this help!

      and we said...
When an extent is allocated to a table (or index, etc) it
belongs to that table until you

o drop the table
o truncate the table

When you delete the data from the table -- the blocks will go
onto the freelist for that table (assuming the amount of space
on a block that was freed fell below the pctused).  These blocks
will be used for subsequent inserts and updates into this table.

When you delete data from the index -- if the block the index
entry was on is now "empty" -- that block will go back onto the
freelist to be used anywhere in the index struct.  Else that
block stays where it is and data that would naturally go onto
that block (because of where it is in the b*tree) will go there.

Space is effectively reused when you delete.  Your database will
not show any new free space in dba_free_space -- it will have
more blocks on freelists and more empty holes in index
structures.

Now, as for the purge issues -- the best strategy really lies in
Oracle8i with partitions.  Doing a rolling window is trivial
using partitions and aging old data out is a simple DDL
operation that takes seconds to perform.
Review &
      Followup

      Rating: 5
will delete
      reclaim database free space ?  March 11, 2001

Reviewer:  Raj  from Malaysia
 I was currently at the verge of running a report to see how much space I have I
one of my databases. My colleague complained that he deleted about 1 - 2 gig of
data and the free space report is still showing the same count. He was wondering
how. The answer for the question helped me to explain the situation to him. Very
helpful. Thanks.

      Rating:
      4
  September 04, 2002
Reviewer:
      A reader
Tom:
Is there anyway to reclaim the space(ie. reset the HWM) after a large delete?

I have inherited a database that had massive deletes. These tables are going to
be static now. How can I reclaim some of the space allocated to this database to
make room for another. My 'alter database datafile resize' returns data above
size error. Is it not due to the HWM still high? Please advice. Thanks.

          Followup:

alter table T move;
(and then rebuild the indices on that table)

I would:

o create a new locally managed tablespace with autoextend datafiles (so the
files will grow just as big as needed)
o alter table T move tablespace that_new_one
o alter index I rebuild tablespace that_new_one
o drop tablespace that_old_one;

      Rating:
      4
  September 09, 2002
Reviewer:
      A reader
I moved the tables and was able to reset the HWM. However, I am still not able
to resize the datafiles. Please see the following:

  1  select file_name, bytes/1024/1024 file_size, fs.free_spc,
  2  ((bytes/1024/1024-fs.free_spc)) diff
  3    from dba_data_files df, ( select file_id, sum(bytes)/1024/1024 free_spc
  4   from dba_free_space group by file_id ) fs
  5   where df.file_id=fs.file_id
  6   and df.tablespace_name='MOBIUS'
  7* order by df.tablespace_name, 1
SQL> /

FILE_NAME                                      FILE_SIZE   FREE_SPC       DIFF
--------------------------------------------- ---------- ---------- ----------
/u13/oradata/PPIIMG/mobius01.dbf                    2000 1966.40234 33.5976563
/u14/oradata/PPIIMG/mobius02.dbf                    2000 1949.11719 50.8828125
/u14/oradata/PPIIMG/mobius10.dbf                    1024 989.328125  34.671875
/u15/oradata/PPIIMG/mobius03.dbf                    2000 1626.30469 373.695313
/u16/oradata/PPIIMG/mobius04.dbf                    2000 1766.30469 233.695313
/u16/oradata/PPIIMG/mobius05.dbf                    1200 1160.34766 39.6523438
/u17/oradata/PPIIMG/mobius06.dbf                    1200 1160.35156 39.6484375
/u17/oradata/PPIIMG/mobius08.dbf                    1024 991.355469 32.6445313
/u18/oradata/PPIIMG/mobius07.dbf                    1100 1080.34766 19.6523438
/u18/oradata/PPIIMG/mobius09.dbf                    1024  995.34375   28.65625

10 rows selected.

SQL> alter database datafile '/u13/oradata/PPIIMG/mobius01.dbf' resize 200M;
alter database datafile '/u13/oradata/PPIIMG/mobius01.dbf' resize 200M
*
ERROR at line 1:
ORA-03297: file contains used data beyond requested RESIZE value

Why can't I resize this datafile to 200M when the only 33 meg of it is used? (as
per dba_free_space). What is the mistake I am making? Thanks.

          Followup:

search this site for

maxshrink

I have a script that'll generate an alter statement that'll make the files as
small as they can go.

You would want to move the files to another tablespace.  The high water mark of
the files is still "high".  You might have 170meg of free space followed by
30meg of used space (and hence the file cannot be shrunk AT ALL).

      Rating: 4
Moving a table
      within the same tablespace to reclaim free space  September
      09, 2002
Reviewer:  reader  from USA
Tom,

Instead of moving the table to a new tablespace,
can I move the table to the tablespace that it currently
belongs, ie. doing nothing but just moving it to itself for reclaiming the free
space, is it possible ?

          Followup:

maybe, maybe not.

If it is an LMT (locally managed tablespace), we tend to move the data to the
"front" of the file (meaning, yes, it would work as you intended, allowing you
to shrink the file)

If it is a DMT (dictionary m.t.), we tend to move it where ever fet$ (a database
table that returns rows in random order) says to go.  It could actually end up
moving FURTHER out in the file or nearer to the front, it is not possible to
say.

      Rating: 3
After deleting
      rows, the space still has not been reused  May 05, 2003

Reviewer:  slmak  from Hong Kong
I use 8.0.4 database. After deleting a large amount of rows, I suppose that the
tablespace will not grow for a period of time, as the space of the deleted rows
should be filled by the new inserted rows. But the truth is that the tablespace
keeps growing large, and run out of space.

This happens to both the data and index tablespaces. Should I move the table to
a new tablespace and also rebuild the index to a new tablespace, so as to
reclaim space?

Thanks

          Followup:

tell us how you "fill" the table.  insufficient data to say anything about
anything.

      Rating: 3
RE:After
      deleting rows, the space still has not been reused   May 05,
      2003
Reviewer:  slmak  from Hong Kong
We use Oracle Application. We followed the instructions from Oracle to archive
and purge the old data of our inventory system. After purging, we expect this
will free enough space for new data. But our inventory tablespace continues to
run out of space. I don't know whether the new data use those free space or not.

Thanks in advance.

          Followup:

you must contact support for assistance with this.  moving applications tables
and reorging will need their approval.

if the rows were deleted and the blocks the rows were on caused the row to fall
below the pctused setting -- they would be on the freelist and available for
reuse.

      Rating: 4
'delete' and
      reclaiming of space released - Ver 8.1.7.3  May 06, 2003

Reviewer:  Deepak Bhaskaran  from NH, us
Tom,

I appreciate your contribution to the Oracle community which has helped us
get a better understanding of the Oracle database.

reading this thread, i believe i have a similar issue wherein i couldn't see the
data blocks being reused,
despite a 'delete' following an 'insert' of 1 rec.
After using block# 48072, it goes on to use block # 48073 and so on.....
If the rows are being deleted immediately, shouldn't block#48072 be the only
block
that takes inserts?

show_space does show 3 blocks on the free-list.

Could it be that i'm missing something here?

Loop (for each ee being processed)
    insert 1 rec in table <dpk_tmp1>;
    process <......>;
    delete from table <dpk_tmp1>;
End loop;

-- here is the table definition

CREATE TABLE dpk_tmp1
 (
  employee_number       VARCHAR2(10),
  count                          NUMBER,
  jn_date                        DATE
 )
 PCTFREE    10
 PCTUSED    40
 INITRANS   1
 MAXTRANS   255
 TABLESPACE tmt_tab
 STORAGE   (
      INITIAL     40960
      NEXT        40960
      PCTINCREASE 50
      MINEXTENTS  1
      MAXEXTENTS  505
   )

Here is the plsql code that I used for the test:

declare
        v_blk_no        number;
        v_row_no        number;
begin

        dbms_output.put_line('*************************');
        dbms_output.put_line('Calling show_space BEFORE');
        dbms_output.put_line('*************************');
        show_space('DPK_TMP1');

        for i in 1..4000 loop

                insert into dpk_tmp1 (employee_number, count, jn_date) values
('Emp '||i, i, sysdate);

                      select     dbms_rowid.ROWID_BLOCK_NUMBER(rowid),
            dbms_rowid.ROWID_ROW_NUMBER(rowid)
                      into      v_blk_no, v_row_no
                      from     dpk_tmp1
                      where     employee_number = 'Emp '||i;

         -- ----------------------------------------------
         -- Dump the block# and row# before the delete
         -- -----------------------------------------------
                        insert into dpk_tmp_tab1 (block_no, row_no,
employee_number)
                         values (v_blk_no, v_row_no, 'Emp '||i);

                        --
--------------------------------------------------------
                        -- Now, lets delete the inserted rec from dpk_tmp1
                        --
--------------------------------------------------------
                        delete from dpk_tmp1;
        end loop;

        dbms_output.put_line('*************************');
        dbms_output.put_line('Calling show_space AFTER');
        dbms_output.put_line('*************************');

        show_space('DPK_TMP1');

exception
        when others then
                dbms_output.put_line('Error : '||sqlerrm);
end;
/

And here is the result from running the above plsql code:

MYDB> @1
"truncate table dpk_tmp1

Table truncated.

truncate table dpk_tmp_tab1

Table truncated.

select count(*) from dpk_tmp1

  COUNT(*)
----------
         0

select block_no, count(*) from dpk_tmp_tab1 group by block_no

no rows selected

Running the plsql code
*************************
Calling show_space BEFORE
*************************
Free Blocks.............................0
Total Blocks............................5
Total Bytes.............................40960
Unused Blocks...........................4
Unused Bytes............................32768
Last Used Ext FileId....................10
Last Used Ext BlockId...................48071
Last Used Block.........................1
*************************
Calling show_space AFTER
*************************
Free Blocks.............................3
Total Blocks............................5
Total Bytes.............................40960
Unused Blocks...........................1
Unused Bytes............................8192
Last Used Ext FileId....................10
Last Used Ext BlockId...................48071
Last Used Block.........................4

PL/SQL procedure successfully completed.

"select block_no, count(*) from dpk_tmp_tab1 group by block_no;"
  BLOCK_NO       COUNT(*)
----------         ----------
     48072           1817
     48073           1817
     48074            366

*****************BLOCK/EMP/ROW******************
select * from dpk_tmp_tab1 where (block_no, row_no) in
    (select block_no, max(row_no) row_no from dpk_tmp_tab1 group by block_no);

  BLOCK_NO     EMPLOYEE_NU         ROW_NO
-----------     ----------            ----------
     48072     Emp 1817              1816
     48073     Emp 3634              1816
     48074     Emp 4000                365

thanks
deepak

          Followup:

throw a couple of commits in there and see what happens.  you have an "atypical"
case.

      Rating: 5
Inventory
      Tablespace growing even after purge  May 07, 2003

Reviewer:  Logan Palanisamy  from Santa Clara, CA USA

slmak from Hong Kong,

You are saying the Inventory tablespace is growing even after running the purge
routines. There could be other tables in the inventory tablespace that are
growing contributing to the out of tablespace condition! Are you sure the purged
tables are the ones that are growing?

Check the dba_segments and dba_extents tables for the purged table to see
whether they are still growing or reusing the deleted space.

      Rating: 5
'delete' and
      reclaiming of space  May 07, 2003
Reviewer:
      Deepak Bhaskaran  from NH, USA
thanks tom....it works when i issue a commit.

then again, it appears to be a function of the commit-size.

( I hope i have presented sufficient information for your analysis).

With a commit-size of 1 (ie., if i issue a commit after each delete),
the number of blocks on the freelist stays at 1 (and all records are inserted in
block # 48072)

With a commit-size of 1500 and above, however, the number of blocks on the
freelist increases with each subsequent run (and the records are inserted
in multiple blocks.....9 blocks, to be precise)

For each commit-size, this is how i ran the test:
-------------------------------------------------
after choosing a commit-size (say, 2000) , i would run the 1st test with the
"truncate table" in place, and all subsequent runs for the same commit-size will
not do the truncate table.

The number of blocks on the freelist stays at 1 upto a commit-size of 1000.

Here's what i mean:

////////////////////////////////////////////////////////////
when COMMIT SIZE = 1000
////////////////////////////////////////////////////////////

at the end of the 4th pass (ie., 4000 * 4 = 16000 records inserted/deleted)
*************************
Calling show_space BEFORE
*************************
Free Blocks.............................1
Total Blocks............................5
Total Bytes.............................40960
Unused Blocks...........................3
Unused Bytes............................24576
Last Used Ext FileId....................10
Last Used Ext BlockId...................48071
Last Used Block.........................2

COMMITing, i = 1000
COMMITing, i = 2000
COMMITing, i = 3000
COMMITing, i = 4000

*************************
Calling show_space AFTER
*************************
Free Blocks.............................1
Total Blocks............................5
Total Bytes.............................40960
Unused Blocks...........................3
Unused Bytes............................24576
Last Used Ext FileId....................10
Last Used Ext BlockId...................48071
Last Used Block.........................2

PL/SQL procedure successfully completed.

"select block_no, count(*) from dpk_tmp_tab1 group by block_no;"

  BLOCK_NO       COUNT(*)
----------         ----------
     48072          16000

////////////////////////////////////////////////////////////
when COMMIT SIZE = 2000
////////////////////////////////////////////////////////////

at the end of the 4th pass (ie., 4000 * 4 = 16000 records inserted/deleted)

Running the plsql code
*************************
Calling show_space BEFORE
*************************
Free Blocks.............................19
Total Blocks............................20
Total Bytes.............................163840
Unused Blocks...........................0
Unused Bytes............................0
Last Used Ext FileId....................8
Last Used Ext BlockId...................69064
Last Used Block.........................10

COMMITing, i = 2000
COMMITing, i = 4000

*************************
Calling show_space AFTER
*************************
Free Blocks.............................29
Total Blocks............................35
Total Bytes.............................286720
Unused Blocks...........................5
Unused Bytes............................40960
Last Used Ext FileId....................10
Last Used Ext BlockId...................74645
Last Used Block.........................10

    (Tom: does this mean the HWM is at block 30 ? )

PL/SQL procedure successfully completed.

"select block_no, count(*) from dpk_tmp_tab1 group by block_no;"

  BLOCK_NO   COUNT(*)
---------- ----------
     48072       1817
     48073       2000
     48074       2000
     48075       2000
     69064       2000
     69069       2000
     74645       2000
     74650        183
    178361       2000

 (ie., the 16K records are spread across multiple blocks)

************** USER_EXTENTS ************
select extent_id, bytes, blocks
from user_extents where segment_name = upper('dpk_tmp1') order by extent_id

 EXTENT_ID      BYTES     BLOCKS
---------- ---------- ----------
         0      40960          5
         1      40960          5
         2      81920         10
         3     122880         15

************** USER_EXTENTS ************

thanks
deepak

      Rating: 4
Partial Delete
      of a Very Large Table  November 24, 2003
Reviewer:
      John Gilmore  from United Kingdom
Hi Tom,

We have a table with about 26 million rows and have decided to archive
everything older than three months.  This means deleting about 21 million rows.

I would expect that a straight "delete" statement would take a considerable
amount of time and, as mentioned above, would still not automatically release
the disk space allocated to this table.

My current plan is to create a copy of the table in a new tablespace, truncate
the old table and then populate it from the copy as required.  The new table,
together with its tablespace, can then be dropped.

Any thoughts on this approach?

Thanks for your assistance.

          Followup:

why not:

a) create table NEW as select rows to keep, index it, grant it
b) drop table old
c) rename new to old

and then look at partitioning to permit you to roll data off using DDL in the
future.

November 24, 2003 kathy -- Thanks for the question regarding
      "temporary tablespace ", version 815

      You Asked
i have two questions about the temporary tablespace.
1.) if i'm running out space on temporary, how could i know which user or
    transaction is using the temporary tablespace.

2.) Sometime, it happens to me that, obviously no transactions
    running, but the temporary tablespace never get released unless shutdown the
database. Seem to me like, some dead
    transaction never release the resource, and their status is "pseudo",  any
idea what's happened and how should i handle
    this except shutdown database ?

      and we said...
Temporary tablespaces should appear "full" after a while in a normally running
database.  Extents are allocated once and then managed by the system.  Rather
then doing the rather expensive operation of "space management" (data dictionary
updates), the system will allocate an extent in TEMP and then keep it and manage
it itself.  This is normal and to be expected and is not an indication that
you do not have any temporary space.

See the dynamic performance views V$SORT_USAGE and V$SORT_SEGMENT for more
information regarding the usage of space within these temporary segments.
V$SORT_USAGE will tell you who's using what.

followup to the comment below

V$SORT_USAGE contains the session address.  Perhaps an easier and more supported
method then the one suggested by Jonathan would be:

create view my_v$sort_usage view
as
select ( select username from v$session where saddr = session_addr) uname,
          v.* from v$sort_usage v
/

fact remains that v$sort_usage always has told you who is using the temporary
segment.

In 9i, the user column is replace with the username column and this will not be
necessary.

Review &
      Followup

      Rating:
      2
V$SORT_USAGE  March 27, 2001
Reviewer:
      Marco Gralike  from Netherlands
Sort_usage doesn't give you a correct answer of the username. See
http://www.jlcomp.demon.co.uk/sort_usage.html
for a
workaround.

      Rating:
      3
ORA-1652  September 23, 2002
Reviewer:
      I Singh  from Kansas City, MO
Hi Tom,
I am unable to understand why I am getting
ORA-1652: unable to extend temp segment by 256 in tablespace         TEMP.

SQL> select tablespace_name,next_extent,max_extents from dba_tablespaces where
tablespace_name='TEMP';

TABLESPACE_NAME                NEXT_EXTENT MAX_EXTENTS
------------------------------ ----------- -----------
TEMP                               1048576
SQL> select
tablespace_name,extent_size,total_extents,total_blocks,max_size,max_blocks from
v$sort_segment;

TABLESPACE_NAME                 EXTENT_SIZE TOTAL_EXTENTS TOTAL_BLOCKS
------------------------------- ----------- ------------- ------------
  MAX_SIZE MAX_BLOCKS
---------- ----------
TEMP                                    256           295        76700
       295      76700

SQL> select blocks from dba_data_files where file_id=3;

    BLOCKS
----------
     76800

My questions are:
If max_size reached is 76700 blocks then going by extent size of 256 blocks
there should have been 76700/256=299 total_extents. Why is it 295 ?

          Followup:

You should be using true temporary tablespaces (you have a permanent tablespace
marked temporary -- you have "data files", not "temp files")

Anyway, dump dba_free_space for that tablespace and see whats "free".

      Rating: 3
2 temp
      tablespaces  October 14, 2002
Reviewer:  Jan
       from SG
Our Database (Oracle 8.1.7) has 2 temporary tablespaces - TEMP (DMT) and
TEMP_TRUE (true temporary LMT). All application users have still default
temporary tablespace TEMP. I found in v$sort_usage that if the user perform some
expansive SELECT operation with sorting - the TEMP tablespace is used. But if he
run the application where temporary tables are used - then TEMP_TRUE is used for
these data. Why? Why it is not used Default tablespace?

Thanks,

          Followup:

The temporary tablespace would be taken from the CURRENT_SCHEMA -- so I'll have
to assume you are accessing the temp tables (filling them) via a stored
procedure.

It'll use the temp tablespace of the OWNER of the procedure in that case.

      Rating:
      5
READER  December 06, 2002
Reviewer:
      A reader
Tom,

"
select ( select username from v$session where saddr = session_addr) uname,
          v.* from v$sort_usage v
/
"

These construct of queries are very useful. I have a query
that computes i/o distribution from v$bh at a point in time

select file#, count(block#), count (distinct file# || block#)
    from v$bh group by file# ;

Is it possible to list the segments that are belonging to
the file along with the file# in the same query by joining
dba_segments

Thanks

          Followup:

not dba_segments -- segments span files, hence there is no file in dba_segments.
 You would have to join to something like

( select distinct owner, segment_name, file_id
    from dba_extents
)

      Rating:
      4
Reader  December 07, 2002
Reviewer:
      A reader
I was actually going to equate file# = header_file.

I like to use your approach. is it possible to combine
the queries

select file#, count(block#), count (distinct file# || block#)
    from v$bh
    group by file# ;
and

( select distinct owner, segment_name, file_id
    from dba_extents
)

into one SQL statement

          Followup:

yes, just make them both inline views and join

select ..
  from ( v$bh query ), ( dba_extents query )
 joining by file

      Rating:
      5
Thanks  December 08, 2002
Reviewer:
      A reader

      Rating: 5
which query
      blew it  December 09, 2002
Reviewer:  george
       from australia
Tom,
We are using 8.1.7.4 on HP UX.
I have been getting this message abou the temp segment...very often...
my_v$sort_usage_view will tell you who is using it currently...
Our system throws an error once temp segment can not be extended any more...but
by that time the query that blew it is gone.
I think it is because of a bad query..may be a cartesian join...or something...
How do we catch the query that blew it..because once we fix it..the problem will
go away...I assume..
We have a small database about 3 gigs...Java front end..
about 2000 users...
I have given 1 gig for my temp tablespace (LMT)...
auto extended to 2 gigs...

Thanks.

          Followup:

Question:  why the heck doesn't your java front end have any logging or
instrumentation?  I would hope that a program developed in house would have
COPIOUS amounts of capabilities in this area to help track down things like
this.  A generic error handler at least that logs unexpected errors.

Well, in 817, we can use a SEVERERROR trigger to capture information when this
happens.  This one captures all of the SQL cursors the current session that hits
this problem has open:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( msg varchar2(4000) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace trigger
failed_to_extend_temp
  2  after servererror on database
  3  declare
  4
  5
  6  begin
  7      if ( is_servererror(1652) )
  8      then
  9          insert into t values ( 'ora_sysevent = ' || ora_sysevent );
 10          insert into t values ( 'ora_login_user = ' || ora_login_user );
 11          insert into t values ( 'ora_server_error = ' || ora_server_error(1)
);
 12
 13          insert into t select 'open cursor ' || rownum || ' ' || sql_text
 14                          from v$open_cursor where sid = (select sid from
v$mystat where rownum=1);
 15      end if;
 16  end;
 17  /

Trigger created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create temporary tablespace test_temp
  2  tempfile '/tmp/test_temp.dbf' size 512k reuse
  3  extent management local
  4  uniform size 64k
  5  /

Tablespace created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter user ops$tkyte temporary tablespace
test_temp
  2  /

User altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from all_objects
  2  order by 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12;
select * from all_objects
              *
ERROR at line 1:
ORA-01652: unable to extend temp segment by 8 in tablespace TEST_TEMP

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t;

MSG
---------------------------------------------------------------------------------
--------------------------------------------------
ora_sysevent = SERVERERROR
ora_login_user = OPS$TKYTE
ora_server_error = 1652
open cursor 1 INSERT INTO T SELECT 'open cursor ' || ROWNUM  || ' '  || SQ
open cursor 2 insert into sys.aud$( sessionid,entryid,statement,timestamp#
open cursor 3 declare     l_sql_text ora_name_list_t;     l_n        numbe
open cursor 4 INSERT INTO T VALUES ( 'ora_login_user = ' || ORA_LOGIN_USER
open cursor 5 INSERT INTO T VALUES ( 'ora_server_error = ' || ORA_SERVER_E
open cursor 6 select * from all_objects order by 1, 2, 3, 4, 5, 6, 7, 8, 9
open cursor 7 INSERT INTO T VALUES ( 'ora_sysevent = ' || ORA_SYSEVENT   )

10 rows selected.

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

you'll be able to narrow it down from there (hopefully)..

In 9i -- there is a new function that will pinpoint this exactly:

ops$tkyte@ORA920> create or replace trigger failed_to_extend_temp
  2  after servererror on database
  3  declare
  4      l_sql_text ora_name_list_t;
  5      l_n        number;
  6  begin
  7      if ( is_servererror(1652) )
  8      then
  9          insert into t values ( 'ora_sysevent = ' || ora_sysevent );
 10          insert into t values ( 'ora_login_user = ' || ora_login_user );
 11          insert into t values ( 'ora_server_error = ' || ora_server_error(1)
);
 12
 13                  l_n := ora_sql_txt( l_sql_text );
 14                  for i in 1 .. l_n
 15                  loop
 16                          insert into t values ( 'l_sql_text(' || i || ') = '
|| l_sql_text(i) );
 17                  end loop;
 18      end if;
 19  end;
 20  /

Trigger created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create temporary tablespace test_temp
  2  tempfile '/tmp/test_temp.dbf' size 512k reuse
  3  extent management local
  4  uniform size 64k
  5  /

Tablespace created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter user ops$tkyte temporary tablespace test_temp
  2  /

User altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from all_objects
  2  order by 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12;
select * from all_objects
              *
ERROR at line 1:
ORA-01652: unable to extend temp segment by 8 in tablespace TEST_TEMP

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t;

MSG
---------------------------------------------------------------------------------
--------------------------------------------------
ora_sysevent = SERVERERROR
ora_login_user = OPS$TKYTE
ora_server_error = 1652
l_sql_text(1) = select * from all_objects
order by 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

l_sql_text(2) = , 11, 12

ops$tkyte@ORA920>

      Rating:
      4
  March 03, 2003
Reviewer:
      A reader
Veru useful info, thanks a lot

      Rating: 4
SYSTEM managed
      LMT or UNIFORM sized LMT  April 16, 2003
Reviewer:
      A reader
Hi Tom,

1) We can see that in a permanent system managed LMT the extents are allocated
like 64k, then 1M , then 8M and so on...

But do the system managed Temporary LMT behave the same way as a system managed
permanent LMT would ? ie would the extents be allocated like 64k,1M,8M in a
system managed Temporary LMT as it is in a system managed permanent LMT ? Can
you show an example as i'm not able to find out how to see the temporary extents
 allocated.

2) Is system managed temporary LMT better or a Uniform sized temporary LMT ?

Thanks and Regards

          Followup:

1) it is purposefully not documented.

anyway, you want to use fixed (uniform) sized extents in temp -- period.

2) see #1

      Rating: 3
Why use
      uniformed sized LMT for temporary, why not
      autoallocate?  April 17, 2003
Reviewer:
      A reader
Hi Tom,

In your chapter 3 (pdf) from expert one on one performance,
you have mentioned that autoallocate LMT should be used whenever the size of the
objects is not known. Then why not use autoallocate LMT for a temporary
tablespace as we never know what size a temporary extent is allocated.

Ok we can know the size if we are using sort_area_size (we make extents equal to
or a multiple of sort_area_size), but when we use workarea_polisy_size=auto and
pga_aggregate_target the memeory (extents if disk is used for sorting) allocated
will be handled by oracle and we wont know the size of the extents in that case.
So is autoallocate temporary LMT is good or is it a bad idea ?

Thanks and Regards

          Followup:

but you do know -- sort_area_size.  When using pga_aggregate_target in 9i -- 1m
is the recommendation.

1m uniform extents -- as said above.

It is somewhat of a MOOT discussion -- create temporary tablespace ... uses one
extent management policy:

extent management local UNIFORM

there is no dictionary managed
there is no autoallocate.

      Rating: 3
But isn't the
      value of sort_area_size ignored ??  April 24, 2003

Reviewer:  adewri  from INDIA
Hi TOM,

This gets more confusing ?? When we are using WORKAREA_POLICY_SIZE=AUTO with
PGA_AGGREGATE_TARGET then will not the value of sort_area_size get ignored.

I did a small test.

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
First i change the workarea_size_policy to manual;

SYS@ACME.WORLD> alter system set workarea_size_policy=manual scope=both;

System altered.

SYS@ACME.WORLD> show parameter workarea

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
workarea_size_policy                 string      MANUAL
SYS@ACME.WORLD>

In a new session.

SQL*Plus: Release 9.2.0.3.0 - Production on Thu Apr 24 14:51:21 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, Oracle Label Security, OLAP and Oracle Data Mining
options
JServer Release 9.2.0.3.0 - Production

SCOTT@ACME.WORLD> create table obj as select * from all_objects;

Table created.

SCOTT@ACME.WORLD> select count(*) from obj;

  COUNT(*)
----------
      3386

SCOTT@ACME.WORLD> alter session set sort_area_size=1024000;

Session altered.

SCOTT@ACME.WORLD> set autot trace exp stat
SCOTT@ACME.WORLD> select * from obj order by object_id;

3386 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (ORDER BY)
   2    1     TABLE ACCESS (FULL) OF 'OBJ'

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         45  consistent gets
          0  physical reads
          0  redo size
     153997  bytes sent via SQL*Net to client
       2974  bytes received via SQL*Net from client
        227  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
       3386  rows processed

SCOTT@ACME.WORLD> alter session set sort_area_size=10;

Session altered.

SCOTT@ACME.WORLD> select * from obj order by object_id;

3386 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (ORDER BY)
   2    1     TABLE ACCESS (FULL) OF 'OBJ'

Statistics
----------------------------------------------------------
          0  recursive calls
         32  db block gets
         45  consistent gets
        201  physical reads
          0  redo size
     153997  bytes sent via SQL*Net to client
       2974  bytes received via SQL*Net from client
        227  SQL*Net roundtrips to/from client
          0  sorts (memory)
          1  sorts (disk)
       3386  rows processed

SCOTT@ACME.WORLD>

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Now i change the workarea_size_policy to auto.

SYS@ACME.WORLD> alter system set workarea_size_policy=auto scope=both;

System altered.

SYS@ACME.WORLD> show parameter workarea

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
workarea_size_policy                 string      AUTO
SYS@ACME.WORLD>

In a new session.

SQL*Plus: Release 9.2.0.3.0 - Production on Thu Apr 24 14:54:10 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, Oracle Label Security, OLAP and Oracle Data Mining
options
JServer Release 9.2.0.3.0 - Production

SCOTT@ACME.WORLD> drop table obj;

Table dropped.

SCOTT@ACME.WORLD> create table obj as select * from all_objects;

Table created.

SCOTT@ACME.WORLD> select count(*) from obj;

  COUNT(*)
----------
      3386

SCOTT@ACME.WORLD> alter session set sort_area_size=1024000;

Session altered.

SCOTT@ACME.WORLD> set autot trace exp stat
SCOTT@ACME.WORLD> select * from obj order by object_id;

3386 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (ORDER BY)
   2    1     TABLE ACCESS (FULL) OF 'OBJ'

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         45  consistent gets
          0  physical reads
          0  redo size
     153997  bytes sent via SQL*Net to client
       2974  bytes received via SQL*Net from client
        227  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
       3386  rows processed

SCOTT@ACME.WORLD> alter session set sort_area_size=10;

Session altered.

SCOTT@ACME.WORLD> select * from obj order by object_id;

3386 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (ORDER BY)
   2    1     TABLE ACCESS (FULL) OF 'OBJ'

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         45  consistent gets
          0  physical reads
          0  redo size
     153997  bytes sent via SQL*Net to client
       2974  bytes received via SQL*Net from client
        227  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
       3386  rows processed

SCOTT@ACME.WORLD>
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Here when i changed workarea_policy_size to auto, then even after changing the
value sort_area_size the sorting takes place in memory. if i'm not wrong then
sort_area_size was ignored completely.

Now back to my question about having temp tablespaces as autoallocate. As we saw
that sort_area_size was ignored completely. That means that if my output from
v$sql_workarea_histogram is like this

PGA_SIZE  OPTIMAL_EXECUTIONS ONEPASS_EXECUTIONS MULTIPASSES_EXECUTIONS
--------- ------------------ ------------------ ----------------------
  8kb <= PGA < 16kb  4634932                  0                      0
 16kb <= PGA < 32kb     9469                  0                      0
 32kb <= PGA < 64kb      920                  0                      0
 64kb <= PGA < 128kb     771                  0                      0
128kb <= PGA < 256kb     315                  0                      0
256kb <= PGA < 512kb     132                  0                      0
512kb <= PGA < 1024kb   2717                  0                      0
1mb <= PGA < 2mb        2437                331                      0
2mb <= PGA < 4mb           0                 25                      0
4mb <= PGA < 8mb           0                 58                     36

Which shows that only sorts between 4M and 8M were sent to disks for sorting.

So my point was that when only larger chunks of sorts are sent to disks then why
not create temporary tablespace with greater uniform sized extents or better
keep them autoallocate.

Tom, either im right or im totally confused ??? Please enlighten me.

Regards
Amar

          Followup:

that is the entire goal of AUTO - to render *_size parameters obsolete, to make
automatic what was once manual.

temp MUST be uniform (it is a rule -- it cannot be system allocated, it doesn't
work that way)

And as I said -- use 1meg -- it works well.

      Rating: 5
All extents are
      of 1M for autoallocate (so there is no auto allocate)  April
      24, 2003
Reviewer:  adewri
Hi Tom,

While testing for allocation of extents in tempfiles i see that extents
allocated are 1M in size for an autoallocated temp tablespace. So that means
even if in the create clause i do not give uniform size it will by default
become a LMT with uniform size of 1M.

SYS@ACME.WORLD> create temporary tablespace temp tempfile
'E:\ORACLE\ORADATA\ACME\TEMP01.DBF' size 300M
  2  extent management local;

Tablespace created.
SYS@ACME.WORLD> SELECT tablespace_name, file_id, block_id, BYTES / 1024 / 1024,
owner
  2    FROM v$temp_extent_map
  3  /

TABLESPACE_NAME                   FILE_ID   BLOCK_ID BYTES/1024/1024      OWNER
------------------------------ ---------- ---------- --------------- ----------
TEMP                                    1          9               1          1
TEMP                                    1        137               1          1
TEMP                                    1        265               1          1
TEMP                                    1        393               1          1
TEMP                                    1        521               1          1
TEMP                                    1        649               1          1
TEMP                                    1        777               1          1
TEMP                                    1        905               1          1
TEMP                                    1       1033               1          1
.
.
TEMP                                    1      36617               1          0
TEMP                                    1      36745               1          0
TEMP                                    1      36873               1          0
TEMP                                    1      37001               1          0
TEMP                                    1      37129               1          0
TEMP                                    1      37257               1          0
TEMP                                    1      37385               1          0
TEMP                                    1      37513               1          0
TEMP                                    1      37641               1          0
TEMP                                    1      37769               1          0
TEMP                                    1      37897               1          0
TEMP                                    1      38025               1          0
TEMP                                    1      38153               1          0

299 rows selected.

SYS@ACME.WORLD>

The datafile size is 300M and each and every extent is 1M in size and these
extents are pre-allocated, ie as soon as i resize the tempfile they get
allocated, they are not allocated like permanent LMTs.

I tested this with a temporary uniform LMT of 5M with 300M tempfile;

SYS@ACME.WORLD> create temporary tablespace temp tempfile
'E:\ORACLE\ORADATA\ACME\TEMP01.DBF' size 300M
  2  extent management local uniform size 5M;

SYS@ACME.WORLD> SELECT tablespace_name, file_id, block_id, BYTES / 1024 / 1024,
owner
  2    FROM v$temp_extent_map
  3  /

TABLESPACE_NAME                   FILE_ID   BLOCK_ID BYTES/1024/1024      OWNER
------------------------------ ---------- ---------- --------------- ----------
TEMP                                    1          9               5          0
TEMP                                    1        649               5          0
TEMP                                    1       1289               5          0
TEMP                                    1       1929               5          0
TEMP                                    1       2569               5          0
TEMP                                    1       3209               5          0
TEMP                                    1       3849               5          0
TEMP                                    1       4489               5          0
TEMP                                    1       5129               5          0
TEMP                                    1       5769               5          0
TEMP                                    1       6409               5          0
TEMP                                    1       7049               5          0
TEMP                                    1       7689               5          0
.
.
TEMP                                    1      32649               5          0
TEMP                                    1      33289               5          0
TEMP                                    1      33929               5          0
TEMP                                    1      34569               5          0
TEMP                                    1      35209               5          0
TEMP                                    1      35849               5          0
TEMP                                    1      36489               5          0
TEMP                                    1      37129               5          0

59 rows selected.

SYS@ACME.WORLD>

So this means that temporary LMTs are always uniform sized as you mentioned
before. So i would stick to your advice of 1M :)

Thanks and Regards

          Followup:

that is NOT a SYSTEM allocated tablespace -- if you query dba_tablespaces --
you'll find the extent allocation type for that tablespace is in fact UNIFORM in
both cases (your original premise that:

...
While testing for allocation of extents in tempfiles i see that extents
allocated are 1M in size for an autoallocated temp tablespace.
......

is not really correct -- the tablespace is NOT system allocated, it is in fact
uniform)

      Rating: 5
Ok, I get the
      message  April 24, 2003
Reviewer:  adewri

So i have been doing the wrong tests :)
You are great as usual
Thanks and Regards

      Rating: 5
how to
      understand v$sort_usage  May 20, 2003
Reviewer:
      Jerry
Tom, could you please help me understanding the result of the following query.
I was trying to find out which user is using how much temp tablespace.

SELECT s.username, s.sid,  u.TABLESPACE, u.CONTENTS, u.extents, u.blocks
  FROM v$session s, v$sort_usage u
 WHERE s.saddr = u.session_addr;

Results:

USERNAME    SID    TABLESPACE    CONTENTS    EXTENTS    BLOCKS
MGR    35    TEMP    TEMPORARY    51    1020
MGR    35    TEMP    TEMPORARY    7    140
MGR    35    TEMP    TEMPORARY    14    280
MGR    35    TEMP    TEMPORARY    8    160
MGR    35    TEMP    TEMPORARY    51    1020
MGR    35    TEMP    TEMPORARY    7    140
MGR    35    TEMP    TEMPORARY    14    280
MGR    35    TEMP    TEMPORARY    12    240
MGR    35    TEMP    TEMPORARY    13    260
MGR    35    TEMP    TEMPORARY    51    1020
MGR    35    TEMP    TEMPORARY    7    140
MGR    35    TEMP    TEMPORARY    14    280
MGR    35    TEMP    TEMPORARY    5    100
MGR    35    TEMP    TEMPORARY    51    1020
MGR    35    TEMP    TEMPORARY    7    140
MGR    35    TEMP    TEMPORARY    14    280
MGR    35    TEMP    TEMPORARY    8    160
MGR    35    TEMP    TEMPORARY    8    160
MGR    35    TEMP    TEMPORARY    7    140

Does it mean if I sum up the blocks, I'll get the totoal sort usage?  Why so
many entries for one session?

Thanks! Your web site is a great help!

          Followup:

sum the blocks and you'll find the total temp space on disk used.  is that total
sort?  no, not really (could be a global temporary tables, could be hash) and
doesn't count sorts done in memory.

why so many entries?  only YOU can answer that..  MGR in sid=35 must have many
cursors open, or global temp tables in play.  Consider:

scott@ORA817DEV> alter session set sort_area_size = 32765;
Session altered.

scott@ORA817DEV> create global temporary table gtt
  2  as
  3  select * from all_objects where 1=0;
Table created.

scott@ORA817DEV> create or replace package demo_pkg
  2  as
  3          cursor c1
  4          is
  5          select *
  6            from all_objects
  7           order by 1,2,3,4,5,6,7,8,9;
  8
  9          cursor c2
 10          is
 11          select *
 12            from all_objects
 13           order by 1,2,3,4,5,6,7,8,9;
 14  end;
 15  /

Package created.

those cursors will definitely sort to disk for me...

scott@ORA817DEV> SELECT s.username, s.sid,  u.TABLESPACE, u.CONTENTS, u.extents,
u.blocks
  2    FROM v$session s, v$sort_usage u
  3     WHERE s.saddr = u.session_addr;

no rows selected

scott@ORA817DEV> declare
  2          l_rec all_objects%rowtype;
  3  begin
  4          open demo_pkg.c1;
  5          fetch demo_pkg.c1 into l_rec;
  6  end;
  7  /

PL/SQL procedure successfully completed.

scott@ORA817DEV>
scott@ORA817DEV> SELECT s.username, s.sid,  u.TABLESPACE, u.CONTENTS, u.extents,
u.blocks
  2    FROM v$session s, v$sort_usage u
  3     WHERE s.saddr = u.session_addr;

USERNAME        SID TABLESPACE CONTENTS     EXTENTS     BLOCKS
-------- ---------- ---------- --------- ---------- ----------
SCOTT            15 TEMPORARY  TEMPORARY          7        448

and apparently, that result set is in temp now and takes 448 blocks.  It'll
stay that way till I close the cursor -- and furthermore:

scott@ORA817DEV>
scott@ORA817DEV> declare
  2          l_rec all_objects%rowtype;
  3  begin
  4          open demo_pkg.c2;
  5          fetch demo_pkg.c2 into l_rec;
  6  end;
  7  /

PL/SQL procedure successfully completed.

scott@ORA817DEV>
scott@ORA817DEV> SELECT s.username, s.sid,  u.TABLESPACE, u.CONTENTS, u.extents,
u.blocks
  2    FROM v$session s, v$sort_usage u
  3     WHERE s.saddr = u.session_addr;

USERNAME        SID TABLESPACE CONTENTS     EXTENTS     BLOCKS
-------- ---------- ---------- --------- ---------- ----------
SCOTT            15 TEMPORARY  TEMPORARY          7        448
SCOTT            15 TEMPORARY  TEMPORARY          7        448

each cursor I open that does a sort, will do that -- and further:

scott@ORA817DEV>
scott@ORA817DEV> insert into gtt
  2  select * from all_objects;

26804 rows created.

scott@ORA817DEV>
scott@ORA817DEV> SELECT s.username, s.sid,  u.TABLESPACE, u.CONTENTS, u.extents,
u.blocks
  2    FROM v$session s, v$sort_usage u
  3     WHERE s.saddr = u.session_addr;

USERNAME        SID TABLESPACE CONTENTS     EXTENTS     BLOCKS
-------- ---------- ---------- --------- ---------- ----------
SCOTT            15 TEMPORARY  TEMPORARY          7        448
SCOTT            15 TEMPORARY  TEMPORARY          7        448
SCOTT            15 TEMPORARY  TEMPORARY          6        384

my global temp tables will do that...

scott@ORA817DEV>
scott@ORA817DEV> pause

scott@ORA817DEV>
scott@ORA817DEV>
scott@ORA817DEV> begin
  2          close demo_pkg.c1;
  3          close demo_pkg.c2;
  4  end;
  5  /

PL/SQL procedure successfully completed.

scott@ORA817DEV> SELECT s.username, s.sid,  u.TABLESPACE, u.CONTENTS, u.extents,
u.blocks
  2    FROM v$session s, v$sort_usage u
  3     WHERE s.saddr = u.session_addr;

USERNAME        SID TABLESPACE CONTENTS     EXTENTS     BLOCKS
-------- ---------- ---------- --------- ---------- ----------
SCOTT            15 TEMPORARY  TEMPORARY          6        384

but when I close them -- poof, they go away

      Rating: 5
What's the best
      way to measure temp tablespace usage  May 21, 2003

Reviewer:  Jerry
Thanks Tom!  That's very clear and helpful.

One more question about how to best measure the temporary tablespace usage.
Since the v$sort_usage comes and goes with the application queries, what's the
best way to measure how much temp an application uses.  We have a web-based
application that runs a fix set of queries.  For rollback, I can have a single
rbs online and measure its growth.  But for temp usage, it seems to be quite
difficult to quantify.

          Followup:

use a temporary tablespace per application schema and see how big each one is?

      Rating:
      5
Great!  May 21, 2003
Reviewer:
      Jerry
Thanks Tom! Your answer is always great and enlightening.   You are the best!

      Rating: 4
Temp tablespace
      history  May 21, 2003
Reviewer:  houman
       from Toronto, ON, Canada
I have this situation that temporary tablespace sometimes grows very fast and I
need to know what query or temporary  table exhausted the temporary tablespace.
the v$sort_usage only provide the information for currently running queries. The
failed_to_extend_temp trigger described above only show the last query that has
blown it up but this query may not be the "gulty" one and it just happen to be
executed when no more space is available in the temp table space.
Is there a way to know how much space (in terms of extents) has each query
consumed from temp tablespace and when?

          Followup:

you would have to "poll" for that information -- or when it "blows up" capture
all of that information joining v$sort_usage to v$session to v$sql

      Rating: 4
How large
      should temporary tablespace be?  July 01, 2003

Reviewer:  Paul  from UK
Creating tables/MVs from a join on 4 tables (with 5 to 15 million rows,
producing a table of 15 million) , I keep getting (eventually):

ORA-01652: unable to extend temp segment by 1250 in tablespace TEMP

The DBA reckons this tablespace is about 4Gig and doesn't want to make it any
bigger.  Is this reasonable?

Thanks

          Followup:

the DBA doesn't want you to be able to perform your job?  I'm confused - are
they offering you an alternative?

4gig is pretty small -- even on my little desktop machine, I run with between
1/2 and 1.5 gig depending.  4gig on what sounds like a DW, is very small.

Ask the DBA how much 36gig of disk costs and explain to them that the time
you've wasted costs alot more then that.

goto www.tpc.org, download the full disclosure for our 100gig tpc-h (small one).

we used 36x8 gig files for temp.

"reckons" -- don't they "know"?

      Rating:
      5
  August 06, 2003
Reviewer:  MG
       from Germany
Hi Tom,
I have confused on following :

While creating an index on table 'T', I gathered the temporary segment
information as follows:

create index idx1 on t(object_name);

select username, tablespace, contents, extents, blocks from v$sort_usage;

USERNAME    TABLESPACE      CONTENTS   EXTENTS     BLOCKS
---------- ------------     --------   -------    --------
MG          TEMP1           TEMPORARY      3        384

select OWNER,SEGMENT_NAME,SEGMENT_TYPE,TABLESPACE_NAME from dba_segments where
tablespace_name;

 OWNER   SEGMENT_NAME   SEGMENT_TYPE   TABLESPACE_NAME
-----   ------------   ------------   ---------------
MG       6.585         TEMPORARY        USERS

the user 'MG's temporary tablespace =TEMP1.

Why Tom, these 2 view says different tablespaces for their temp segment.

Thank you.

          Followup:

the index is placed into 'temporary' extents in the target tablespace during the
index creation.

after the index is done 'creating', the last step is to convert the temporary
extents into permanent.

the reason they are temporary in users is that they are destined to become the
permanent ones at the end.  they are temporary so that if the index create gets
aborted (eg: someone does a shutdown abort) SMON will naturally clean them up
upon restart without any additional steps on part of the DBA.

so, the index is placed into "temp" extents which are destined to become
"permanent" at the end.

      Rating:
      5
  August 07, 2003
Reviewer:  MG
       from Germany
Thank you so much Tom. As usual your answers are more valuable.

      Rating:
      5
  August 07, 2003
Reviewer:  MG
       from Germany
Hi Tom,

You mean

select OWNER,SEGMENT_NAME,SEGMENT_TYPE,TABLESPACE_NAME from dba_segments where
tablespace_name;

 OWNER   SEGMENT_NAME   SEGMENT_TYPE   TABLESPACE_NAME
-----   ------------   ------------   ---------------
MG       6.585         TEMPORARY        USERS

Is This segment '6.585' will become the (after finish creating index), Index
Segment as follows in USERS tablespace?

 OWNER   SEGMENT_NAME   SEGMENT_TYPE   TABLESPACE_NAME
-----   ------------   ------------   ---------------
MG       IDX1           INDEX          USERS

Thank you

          Followup:

yes.

      Rating: 3
temp
      tablespace  September 08, 2003
Reviewer:  vj
       from in
i am using 9ir2

My temp tablespace looks like below. The temp tablespace is been used only by
SYS and SYSTEM. My temp tablespace size has grown OVER 2 GB now..How can i
reduce it ?? i cant use offline drop or resize..any help highly appreciated..

BLOCK_SIZE        8192
INITIAL_EXTENT        1048576
NEXT_EXTENT        1048576
MIN_EXTENTS        1
MAX_EXTENTS        0
PCT_INCREASE        0
MIN_EXTLEN        1048576
STATUS            ONLINE
CONTENTS        TEMPORARY
LOGGING            NOLOGGING
FORCE_LOGGING        NO
EXTENT_MANAGEMENT    LOCAL
ALLOCATION_TYPE        UNIFORM
PLUGGED_IN        NO

Rgds

          Followup:

create new
make this new one the default temporary tablespace for the database
drop old

it has been used by EVERYONE, not just sys/system.

      Rating: 5
trigger for
      many server errors  September 18, 2003
Reviewer:
      mary
Tom, is there a way to code a trigger like your FAILED_TO_EXTEND_TEMP trigger
above ("FAILED_TO_EXTEND_TEMP") so that it catches all server errors (or is this
not a good idea)?

What I wanted to do was to use your trigger and mail pkg to send me an email
everytime an alert log error occurs (using 9i and 8i).  Would I need to code in
every error number or is there like a wildcard or something that would work?
What do you recommend?

          Followup:

the trigger looks like this in my example:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace trigger
failed_to_extend_temp
  2  after servererror on database
  3  declare
  4
  5
  6  begin
  7      if ( is_servererror(1652) )
  8      then
  9          insert into t values ( 'ora_sysevent = ' || ora_sysevent );
 10          insert into t values ( 'ora_login_user = ' || ora_login_user );
 11          insert into t values ( 'ora_server_error = ' || ora_server_error(1)

);
 12
 13          insert into t select 'open cursor ' || rownum || ' ' || sql_text
 14                          from v$open_cursor where sid = (select sid from
v$mystat where rownum=1);
 15      end if;
 16  end;
 17  /

it fired for all errors -- i had to ask it "is this failed to extend temp?"

So, it catches all server errors.

you'll probably get lots more emails then you want, you should pick the errors
you want to know about.

      Rating: 5
alert log error
      list  October 16, 2003
Reviewer:
      A reader
Tom, is there a list somewhere within oracle documentation of just the ORA-
errors that are recorded in the alert log?  I have requested thru metalink, but
the support technician told me there is no such list and to go back thru my old
alert logs to see what errors have occurred.  I am afraid I will miss the ones
that haven't occurred when implementing the server error trigger.  I have the 3
books of error messages from the oracle documentation, but the errors are sorted
alpha/numeric, so I have to read thru hundreds of pages of ora- errors to see if
they're the ones I want to put in my trigger.

Do you know of a list of only the errors that are normally recorded in the alert
log?

          Followup:

there is no list that I'm aware of.

basically -- people keep "anti lists" -- messages they know they don't care
about and report all others (and just update the anti list if an innocent
message gets emailed to them)

      Rating: 5
shrink
      temporary tablespace  November 11, 2003
Reviewer:
      igor  from ME USA
Hi Tom,

you described how to shrink the temporary tablespace in general:

create new
make this new one the default temporary tablespace for the database
drop old

Can you provide more details please? In my understanding, before dropping the
old tablespace, we have to make sure neither any session is currently using the
old temporary tablespace, nor any session will try to use it. How to check it?
Even if at the moment V$SORT_USAGE shows no usage, and there are no users having
the old tablespace as default temporary tablespace, any existing session which
was started when the old tablespace was in effect, may "remember" it and try to
use it later. This will cause an application error.

In fact I had this situation once and I don't want to have it again. Here is
what I did:

1. Changed temporary tablespace from TEMP1 to TEMP for all database users having
TEMP1 as temporary tablespace.
alter user <username> temporary tablespace TEMP;

2. Waited until there is no usage of tablespace TEMP1 - the following query
returned 0.
select count(*) from v$sort_usage where tablespace='TEMP1';

3. Deleted the tablespace TEMP1.
drop tablespace TEMP1 including contents;

But after that, one of the long-running tasks failed, so I realized that my
method was wrong. (The failed task was a concurrent Oracle Applications job, but
I think it doesn't matter).

The question is: How to drop safely a temporary tablespace without bouncing a
database? Is that possible?

Thank you.

          Followup:

(9ir2) so, in one session I:

ops$tkyte@ORA920LAP> create temporary tablespace temp2
  2  tempfile size 10m
  3  autoextend on next 1m
  4  maxsize 2048m
  5  /

Tablespace created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> prompt go into another session and insert into a GTT
go into another session and insert into a GTT
ops$tkyte@ORA920LAP> pause

in that other session I:

ops$tkyte@ORA920LAP> insert into gtt select * from all_objects;

30724 rows created.

coming back -- i:

ops$tkyte@ORA920LAP> drop tablespace temp
  2  including contents and datafiles
  3  /

and that blocked -- it was waiting for the other session to finish using

so, it'll not let you drop an active one.  I suppose your concurrent manager was
a query that was parsed and ready to go -- but didn't have a need for temp yet
-- but its "temp" was fixed.  For that -- you would have to simply "wait" for
what you deem a sufficiently long enough time in order to avoid having long
running queries that were parsed but not yet in need of temp to get to that
point.  eg: create new temp in the morning, drop old temp when you get ready to
leave -- or schedule this sort of "shrink" during off peak times

      Rating: 5
shrink
      temporary tablespace  November 12, 2003
Reviewer:
      Igor  from ME USA
Yes, it explains what happened to me.
Very helpful. Thank you Tom.

      Rating: 4
Temporary
      tablespace  November 24, 2003
Reviewer:
      Yateendra Chaturvedi  from DEWAS, INDIA
Dear Tom,

I have gone through the entire page it is very useful. But I am facing some
typical problem. I have Oracle8 Enterprise Edition Release 8.0.4.1.0 -
Production. In which if I create temprary tablespace of permanent nature it
works fine. The moment I recreate this as a temporary tablespace, it is start
giving problem. Most of the LOV's of forms show working and does not respond.
Please help.

          Followup:

"start giving problem"

hmm.  not very clear.

as 804 is somewhat ancient history -- I guess I would recommend you leave it "as
is".  don't change anything.  you cannot really develop or change anything on
that system -- you have to "freeze it" as there is no support for it.

November 24, 2003 mathew -- Thanks for the question regarding
      "anydata type", version 9i

      You Asked
hi,
  Oracle9i seems to have a new datatype anydata type. How and where could you
use this? If someone puts date values and someone else uses number values in
such columns how can this be extracted and interpreted?
thanks,
 mathew

      and we said...
Here is an example.  It could be potentially useful in an application that
stores generic attributes -- attributes you don't KNOW what the datatypes are
until you actually run the code.  In the past, we would have stuffed everything
into a VARCHAR2 -- dates, numbers, everything.  Now, you can put a date in and
have it stay as a date (and the system will enforce it is in fact a valid date
and let you perform date operations on it -- if it were in a varchar2 -- someone
could put "hello world" into your "date" field)

tkyte@TKYTE9I.US.ORACLE.COM> create table t ( x sys.anyData );
Table created.

tkyte@TKYTE9I.US.ORACLE.COM> insert into t values ( sys.anyData.convertNumber(5)
);

1 row created.

tkyte@TKYTE9I.US.ORACLE.COM> insert into t values (
sys.anyData.convertDate(sysdate) );

1 row created.

tkyte@TKYTE9I.US.ORACLE.COM> insert into t values (
sys.anyData.convertVarchar2('hello world') );

1 row created.

=================================================================

Use the getTypeName method of the ANYDATA function to see whats
in there....

tkyte@TKYTE9I.US.ORACLE.COM> select t.x.gettypeName() typeName from t t;

TYPENAME
--------------------
SYS.NUMBER
SYS.DATE
SYS.VARCHAR2

=================================================================

Unfortunately, they don't have a method to display the contents of
ANYDATA in a query (most useful in programs that will fetch the data,
figure out what it is and do  something with it -- eg: the application
has some intelligence as to how to handle the data)

Fortunately we can write one tho:

tkyte@TKYTE9I.US.ORACLE.COM> create or replace function getData( p_x in
sys.anyData ) return varchar2
  2  as
  3      l_num number;
  4      l_date date;
  5      l_varchar2 varchar2(4000);
  6  begin
  7      case p_x.gettypeName
  8          when 'SYS.NUMBER' then
  9              if ( p_x.getNumber( l_num ) = dbms_types.success )
 10              then
 11                  l_varchar2 := l_num;
 12              end if;
 13          when 'SYS.DATE' then
 14              if ( p_x.getDate( l_date ) = dbms_types.success )
 15              then
 16                  l_varchar2 := l_date;
 17              end if;
 18          when 'SYS.VARCHAR2' then
 19              if ( p_x.getVarchar2( l_varchar2 ) = dbms_types.success )
 20              then
 21                  null;
 22              end if;
 23          else
 24              l_varchar2 := '** unknown **';
 25      end case;
 26
 27      return l_varchar2;
 28  end;
 29  /

Function created.

tkyte@TKYTE9I.US.ORACLE.COM>
tkyte@TKYTE9I.US.ORACLE.COM> select getData( x ) getdata from t;

GETDATA
--------------------
5
19-MAR-02
hello world

tkyte@TKYTE9I.US.ORACLE.COM>
Review &
      Followup

      Rating: 5
anydata
      type  March 19, 2002
Reviewer:  Paul Nguyen
       from Texas USA
Your answer is adequate in term of giving me a profound understanding of the
topic.
Good work , Kyte.

      Rating:
      5
Great!  March 19, 2002
Reviewer:  DV
       from India
Thanks Tom for the wonderful explaination.

      Rating: 4
simple
      explanation  March 19, 2002
Reviewer:
      A reader

      Rating: 5
Best way of
      explanation  March 20, 2002
Reviewer:
      Rajender  from Pune,Maharastra,India

      Rating: 5
more
      complicated reflection  May 10, 2002
Reviewer:
      Mikito harakiri
I'm trying to use 9i reflection in PL/SQL to extract data that I feeded into
MY_FUNCTION like this:

SELECT * FROM TABLE(MY_FUNCTION(CURSOR(
        select ename, sal from emp
)))

In MY_FUNCTION i want to be able to generically extract each record data:

CREATE or replace FUNCTION MY_FUNCTION( cur SYS_REFCURSOR )
RETURN IntegerSet PIPELINED IS
    field1 VARCHAR2(100);
    field2 INTEGER;
begin
    loop
        fetch cur into ...;
        exit when cur%NOTFOUND;
        -- get 2 fields
    end loop;
END;

Now I'm trying to figure out what code do I need in MY_FUNCTION that would
extract those 2 fields for each record...

          Followup:

Not going to happen.  Fetch cursor into ..... -- the ..... has to be an EXPLICIT
list of variables.

You have to at least KNOW the number of columns -- then you could fetch
everything into a varchar2(4000) and everything that can be converted into a
varchar2 -- will be.

      Rating: 5
Too good way of
      Explanation.  May 11, 2002
Reviewer:  Parag
       from India
I am highly impressed by your answere Kyte.
Keep going ON.

      Rating:
      4
Enlightening!!!  May 13, 2002
Reviewer:
      Ashok

      Rating:
      3
  May 13, 2002
Reviewer:  Sagi
       from India
Hi Tom!

You are simply great with a pool full of knowledge.

Cheers,

      Rating: 4
Finally the mud
      settles from the water  July 17, 2002
Reviewer:
      Richard Goulet  from Andover, MA
Good explanation, and potential implimentation usage, but it mainly looks like
an unstructured data store.  One would have to be careful on using it else it
turn into a pile of brown smelly stuff.

      Rating: 4
Thanks for
      concise and useful description.  July 18, 2002

Reviewer:  Paul O'Neill
Much better explanantion than documentation. I understood the concepts after
reading the article once. I wish all technical documentation was so straight
forward. Thanks.

      Rating: 4
Oracle 9i Date
      Data type  August 13, 2002
Reviewer:  Siva
       from INDIA
Hello TOM,

        I have following queries which i couldn't understand.  Please explain
me.

   1. SELECT mgr, ename, hiredate,
       COUNT(*) OVER (PARTITION BY mgr ORDER BY hiredate
       RANGE NUMTODSINTERVAL(100, 'day') PRECEDING) AS t_count
      FROM emp
   2. SELECT last_name, hire_date, salary, SUM(salary)
   OVER (ORDER BY hire_date
   RANGE NUMTOYMINTERVAL(1,'year') PRECEDING) AS t_sal
   FROM employees;

   3.

  1  SELECT empno,ename,hiredate FROM emp
  2    WHERE hiredate - TO_DSINTERVAL('100 10:00:00')
  3*   > DATE '1985-01-01'
SQL> /

     EMPNO ENAME      HIREDATE
---------- ---------- ---------
      7788 SCOTT      19-APR-87
      7876 ADAMS      23-MAY-87

    4. How can i manipulate data with this table?.

  SQL> drop table tr;

   Table dropped.

  SQL>  create table tr(t1 INTERVAL YEAR TO MONTH, t2 INTERVAL DAY TO SECOND );

  Table created.

    5. I want to give my hire date in the following query. Is it possible?.

      SELECT EXTRACT(YEAR FROM DATE '2002-08-12'  )  from dual;

Thank you.

regards,
Siva

          Followup:

You wrote them and cannot understand them??

But anyway, these questions have nothing to do with the original question -- so,
I'll ignore it.

Ask this as a question sometime....

(or get my book "expert one on one" where I spend a good 40 pages on analytic
functions and how to use them, how to write them)..

      Rating: 5
can I query
      ANYDATA?  November 21, 2002
Reviewer:  mikito
      harakiri
I need to store bind values, which are used in JDBC statements like this:
select * from emp where empno = ? and ename = ?
Bind variables come in different shapes and colors: in my example, they would be
java.math.BigDecimal and java.land.String.  Therefore, I have 2 options
1. ANYDATA column
2. serialize java object into CLOB
I wonder which method is better.

The second question is that after filling out my database with bind values I
would like to query it. I guess that I wouln't be able to query anything with
method #2, but suspect that it's the same with method #1. What is your opinion?

          Followup:

I would be using strings (varchar2's) to store binds -- period.  pretty much
everything can be cast to a string that you would need.

then, it is queryable as well.

      Rating: 5
Negative
      Numbers in VARCHAR2  May 26, 2003
Reviewer:
      Jan  from Europe
I want to store Dates, Numbers and texts in VARCHAR2. The table which will store
those data hast to be compact and as efficient as possible due to frequent and
fast SELECTs (I use IOT):

array_id         NUMBER,
param_name       VARCHAR2(15),
min_val          VAERCHARE2(30),
max_val          VARCHAR2(30))

where the first 3 columns are PK.
Data could be ranges (for dates and numbers) or discret (for texts only):

array_id     param_name     min_val      max_val
1             GENDER         M
1             COUNTRY        US
1             DOB          19500101      19991231
1             POINT        -10.00         +20.00

and SELECT has:

WHERE param_name=p_param_name
  AND (p_val = min_val OR p_val BETWEEN min_val AND max_val)

The problem is that I want to use number comparision on text which does not work
for negative numbers. And I don't want to use TO_NUMBER conversion on database
columns due to decreasing performance.

My question is if there any way (e.g. number formats) which can deal with
negative numbers in VARCHAR2?

          Followup:

No you don't -- please don't do that, you'll be so sorry you did.  Over and over
you'll be sorry, or worse yet, you'll move onto a different project and the
person that inherits your stuff will be cursing you up and down constantly.

NULLS are very compact, they take at most a single byte!  If they are trailing
columns on a table, they take ZERO bytes.

store numbers as numbers, dates as dates, strings as strings.

you'll be fast
you'll be correct
you'll be happier
your ancestors will not curse you

      Rating: 3
Negative
      Numbers in VARCHAR2 - not just about NULL  May 26, 2003

Reviewer:  Jan
Tkanks, but your solution will not help me. It is not the question only of
saving space but reducing of steps during an evaluation of query. E.g. if the
incomming parameter is:

p_param_name='GENDER'
p_val=  'X'

then my query has to by much more complex otherwise it will raise an error:

WHERE param_name=p_param_name
  AND (p_val = text_val    -- Not found then continue
      OR p_val BETWEEN min_val AND max_val) -- Implicit Conversion Raise an
Error

          Followup:

sorry but by going down this generic model, your system is already doomed to be
very slow.

forget about conversions -- you would need NO conversions, if you use the
specific type.

You can dream up a way to store negative numbers and have them sort "properly"
in ASCII  -- but everything I said here and above will continue to be true.  I
know I would never want to pick up your system and have to maintain it.

Think about the positive numbers even -- you have to store them in a fixed width
field (else they do NOT sort).

So, you have to figure out, hmm, what is the maximum width this will EVER be.
Then make sure to store EACH AND EVERY STRING that is a number in a field of
that width (talk about wasted space -- you have to be able to store numbers
between 1 and 10000 say -- EACH number will take 6 characters nomimally -- even
the number 1 since it must be stored as:

     1
^^^^^

in order to compare properly.

Sorry -- I so strongly disagree with your approach that I'm not going to come up
with a fancy encoding scheme to make negative numbers "sort" in a string.

I've said it so many times

compare numbers to numbers
dates to dates
strings to strings

and do NOTHING else.

      Rating:
      4
  May 26, 2003
Reviewer:
      A reader
If I may just say so, perhaps ...

 14              if ( p_x.getDate( l_date ) = dbms_types.success )
 15              then
 16                  l_varchar2 := l_date;
 17              end if;

... might be better as ...

 14              if ( p_x.getDate( l_date ) = dbms_types.success )
 15              then
 16                  l_varchar2 := to_char(l_date,'DD-Mon-YYYY HH24:mi:ss');
 17              end if;

Seem fair? ;)

          Followup:

depends.

my approach behaves like select sysdate from dual would.  it uses the sessions
default date format to format the date.

Yours imposes your format on everyone.

      Rating:
      4
  May 26, 2003
Reviewer:  Dave
       from Colorado Springs
Sorry, forgot to sign that.

Anyhoo, that's partly true except that "select sysdate from dual" preserves the
time and the undisplayed century data, if you are selecting into a date
variable, or as an in-line view. The getData() function is returning a varchar2,
so the information is lost.

I'm just being reactionary here -- as a regular reader i know how you always
insist that such un-pictured conversions are a bad thing, so i thought i'd hit
you with your own stick as it were.

      Rating: 4
some new
      question  November 20, 2003
Reviewer:
      Hannibal  from austria/europe
hi tom,

thanks, the example was very helpfull and i use the sys.anydata regular. i would
like to ask you some open questions on this issue.

1) how does oracle store the anydata internal (blob, varchar2 .. ) in memory and
in table?
2) is there a way to find out how big (in bytes) the 'thing' in anydata  is?
like i can get the type-name using the methode
gettypename() without 'casting' before?
2) i use the ConvertObject methode very often ... it is very usefull when you
deal with objects. i would also like to store some collections in the same way
using ConvertCollection methode for the sys.anydata object but i cant figure out
how to do this. (the oracle-docu is a little bit short - just giving us the
'signature' of the methode but nothing else). may be you could provide us with a
little example so we can see hwo to use it.

thx in advance

hannibal

p.s.: congradulations to your new book!!! very GOOD and UESFULL!!!! i like
it!!!!

h.

          Followup:

1) it is an invisible type -- magic.

2)
ops$tkyte@ORA920LAP> create table t ( a int, x sys.anydata );

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> insert into t values ( 1, sys.anyData.convertNumber(5) );

1 row created.

ops$tkyte@ORA920LAP> insert into t values ( 2, sys.anyData.convertDate(sysdate)
);

1 row created.

ops$tkyte@ORA920LAP> insert into t values ( 3,
sys.anyData.convertVarchar2('hello world') );

1 row created.

ops$tkyte@ORA920LAP> insert into t values ( 4,
sys.anyData.convertVarchar2('hello world hello world hello world') );

1 row created.

ops$tkyte@ORA920LAP> select a, vsize(x) from t;

         A   VSIZE(X)
---------- ----------
         1         48
         2         53
         3         57
         4         81

ops$tkyte@ORA920LAP>

      Rating: 3
still some
      unanswered questions  November 24, 2003
Reviewer:
      Hannibal  from austria/europe
Hello Tom,

thx for all your help. i must comeback with some questions ... as you can see i
tested the sys.anydata
with some objects and tables of objects. by using my little look-function i
could look into the dump and
i guess i got a glimps on how your are storing the data magically :-). however,
i looks like the results
of vsize and dump are only correct as long as the data is not too big???? the
result of vsize on column
11 should be much bigger then column 10 but it is'nt, so i guess you shift the
data to an other place
when it gets to big for a LONG or what ever. so i start to think vsize does only
work for small(er) items.
is this correct? how can i get the real size of a bigger object if possible? is
there a at least chance
to recognize that the value given by vsize is not the real value but a much
smaller one. f.e., by examening
the dump?

i dislike it to ask you again if you could give an example for the correct usage
of
sys.anydata.ConvertCollection. i know you are very buisy ... may be you give me
a link where i can
read more on this topic.

regards

hannibal

== just creating some entryies ..

jom@NINA.US.ORACLE.COM> desc t
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
------------------------------------
 ID                                                             NUMBER(38)
 LDATA                                                          SYS.ANYDATA

jom@NINA.US.ORACLE.COM> insert into t values ( 1 ,
sys.anydata.convertnumber(1));

1 row created.

jom@NINA.US.ORACLE.COM> insert into t values ( 2 ,
sys.anydata.convertvarchar2('tom'));

1 row created.

jom@NINA.US.ORACLE.COM> insert into t values ( 3 ,
sys.anydata.convertdate(sysdate));

1 row created.

jom@NINA.US.ORACLE.COM> desc myobj
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
------------------------------------
 HEADER                                                         VARCHAR2(256)
 BODY                                                           VARCHAR2(1024)

== taking a object type
jom@NINA.US.ORACLE.COM> r
  1  declare
  2  obj myobj;
  3  begin
  4  obj := myobj(null,null);
  5  obj.header := 'this is the header';
  6  obj.body := 'this is the body - just a text ... ';
  7  insert into t values ( 6 , sys.anydata.convertobject(obj));
  8* end;

jom@NINA.US.ORACLE.COM> r
  1  declare
  2  obj myobj;
  3  begin
  4  obj := myobj(null,null);
  5  obj.header := 'this is the header';
  6  obj.body := 'this is the body - just a
loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
ooooooo
ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
ooooooooooooooooooooooooooooooooooooooooooooooooooo
og text ... ';
  7  insert into t values ( 7 , sys.anydata.convertobject(obj));
  8* end;

PL/SQL procedure successfully completed.

==taking a more complex object type
jom@NINA.US.ORACLE.COM> desc obj_test_tab;
 obj_test_tab TABLE OF OBJ_TEST
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
------------------------------------
 X                                                              NUMBER(5)
 S                                                              VARCHAR2(32)

jom@NINA.US.ORACLE.COM> desc obj_test
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
------------------------------------
 X                                                              NUMBER(5)
 S                                                              VARCHAR2(32)

jom@NINA.US.ORACLE.COM> create or replace type myobj2 as object ( payload
obj_test_tab );
  2  /

Type created.

jom@NINA.US.ORACLE.COM> desc myobj2
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
------------------------------------
 PAYLOAD                                                        OBJ_TEST_TAB

jom@NINA.US.ORACLE.COM> r
  1  declare
  2  obj2 myobj2;
  3  tab obj_test_tab;
  4  obj obj_test;
  5  begin
  6  obj := obj_test( null, null);
  7  tab := obj_test_tab(null);
  8  obj.x := 1;
  9  obj.s := 'hallo';
 10  tab.extend;
 11  tab(1) := obj;
 12  obj.x := 2;
 13  obj.s := 'hallo2';
 14  tab.extend;
 15  tab(2) := obj;
 16  obj2 := myobj2(null);
 17  obj2.payload := tab;
 18  insert into t values ( 10 , sys.anydata.convertobject(obj2));
 19* end;

PL/SQL procedure successfully completed.

jom@NINA.US.ORACLE.COM> r
  1  declare
  2  obj2 myobj2;
  3  tab obj_test_tab;
  4  obj obj_test;
  5  v_int int;
  6  begin
  7  obj := obj_test( null, null);
  8  tab := obj_test_tab(null);
  9  for v_int in 1..1000
 10  loop
 11  obj.x := v_int;
 12  obj.s := 'hallo'||to_char(v_int);
 13  tab.extend;
 14  tab(v_int) := obj;
 15  end loop;
 16  obj2 := myobj2(null);
 17  obj2.payload := tab;
 18  insert into t values ( 11 , sys.anydata.convertobject(obj2));
 19* end;

PL/SQL procedure successfully completed.

== the size is o.k., this is a much bigger object type.
jom@NINA.US.ORACLE.COM> r
  1  declare
  2  obj2 myobj2;
  3  tab obj_test_tab;
  4  obj obj_test;
  5  v_int int;
  6  begin
  7  obj := obj_test( null, null);
  8  tab := obj_test_tab(null);
  9  for v_int in 1..1000
 10  loop
 11  obj.x := v_int;
 12  obj.s := 'hallo'||to_char(v_int);
 13  tab.extend;
 14  tab(v_int) := obj;
 15  end loop;
 16  obj2 := myobj2(null);
 17  obj2.payload := tab;
 18  dbms_output.put_line(tab(600).s);
 19  dbms_output.put_line(tab.count);
 20* end;
hallo600
1001

PL/SQL procedure successfully completed.

jom@NINA.US.ORACLE.COM> r
  1* select t.id , t.ldata, t.ldata.gettypename(), vsize(t.ldata) ,
dump(t.ldata), look(substr(dump(t.ldata),instr(dump(t.ldata),':'
,1,1)+2)) from t t

        ID
----------
LDATA()
---------------------------------------------------------------------------------
-------------------
T.LDATA.GETTYPENAME()
---------------------------------------------------------------------------------
-------------------
VSIZE(T.LDATA)
--------------
DUMP(T.LDATA)
---------------------------------------------------------------------------------
-------------------
LOOK(SUBSTR(DUMP(T.LDATA),INSTR(DUMP(T.LDATA),':',1,1)+2))
---------------------------------------------------------------------------------
-------------------
         1
ANYDATA()
SYS.NUMBER
            48
Typ=58 Len=48:
0,1,0,0,0,0,0,1,0,0,0,0,148,115,0,32,9,0,0,0,0,0,0,16,0,0,0,0,0,1,133,1,16,1,1,0,
2,0,
2,0,0,0,0,2,193,2,0,0
 ?     ?    ös                ?     ?à???? ? ?    ?-?

         2
ANYDATA()
SYS.VARCHAR2
            49
Typ=58 Len=49:
0,1,0,0,0,0,0,1,0,0,0,0,148,116,0,33,9,0,0,0,0,0,0,17,0,0,0,0,0,1,133,1,17,1,1,0,
2,0,
9,0,0,0,0,3,116,111,109,0,0
 ?     ?    öt !              ?     ?à???? ?        ?tom

         3
ANYDATA()
SYS.DATE
            53
Typ=58 Len=53:
0,1,0,0,0,0,0,1,0,0,0,0,148,117,0,37,9,0,0,0,0,0,0,21,0,0,0,0,0,1,133,1,21,1,1,0,
2,0,
12,0,0,0,0,7,120,103,11,24,12,12,59,0,0
 ?     ?    öu %              §     ?à?§?? ? ?    xg????;

         5
ANYDATA()
JOM.MYOBJ
            69
Typ=58 Len=69:
0,1,0,0,0,0,0,1,0,0,0,0,148,118,0,53,9,0,0,0,0,0,0,37,0,0,0,0,0,1,133,1,37,1,1,0,
4,0,
108,194,204,83,80,126,191,69,39,165,152,44,62,150,104,32,64,0,1,0,0,0,0,5,132,1,5
,255,255,0,0
 ?     ?    öv 5              %     ?à?%?? ? l-¦SP~+E'Ñÿ,>ûh @ ?    ?ä??

         6
ANYDATA()
JOM.MYOBJ
           122
Typ=58 Len=122:
0,1,0,0,0,0,0,1,0,0,0,0,148,119,0,106,9,0,0,0,0,0,0,90,0,0,0,0,0,1,133,1,90,1,1,0
,4,
0,108,194,204,83,80,126,191,69,39,165,152,44,62,150,104,32,64,0,1,0,0,0,0,58,132,
1,58,18,116,104,105
,115,32,105,115,32,116,104,101,32,104,101,97,100,101,114,35,116,104,105,115,32,10
5,115,32,116,104,10
1,32,98,111,100,121,32,45,32,106,117,115,116,32,97,32,116,101,120,116,32,46,46,46
,32,0,0
 ?     ?    öw j              Z     ?à?Z?? ? l-¦SP~+E'Ñÿ,>ûh @ ?    :ä?:?this is
the header#this is the bod
y - just a text ...

         7
ANYDATA()
JOM.MYOBJ
           357
Typ=58 Len=357:
0,1,0,0,0,0,0,1,0,0,0,0,148,120,1,85,9,0,0,0,0,0,1,69,0,0,0,0,0,1,133,1,254,0,0,1
,69
,1,1,0,4,0,108,194,204,83,80,126,191,69,39,165,152,44,62,150,104,32,64,0,1,0,0,0,
1,33,132,1,254,0,0,
1,33,18,116,104,105,115,32,105,115,32,116,104,101,32,104,101,97,100,101,114,254,0
,0,1,2,116,104,105,
115,32,105,115,32,116,104,101,32,98,111,100,121,32,45,32,106,117,115,116,32,97,32
,108,111,111,111,11
1,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111
,111,111,111,111,11
1,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111
,111,111,111,111,11
1,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111
,111,111,111,111,11
1,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111
,111,111,111,111,11
1,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111
,111,111,111,111,11
1,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111
,111,111,111,111,11
1,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111
,111,111,111,111,11
1,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111
,111,111,111,111,11
1,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,103,32,116,101,
120,116,32,46,46,46
,32,0,0
 ?     ?    öx?U             ?E     ?à?¦  ?E?? ? l-¦SP~+E'Ñÿ,>ûh @ ?   ?!ä?¦
?!?this is the header¦  ??thi
s is the body - just a
loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
ooooooooooooooooooo
oooooooooooooooooooooooooooooooooooooooooooog text ...

        10
ANYDATA()
JOM.MYOBJ2
           103
Typ=58 Len=103:
0,1,0,0,0,0,0,1,0,0,0,0,148,167,0,87,9,0,0,0,0,0,0,71,0,0,0,0,0,1,133,1,71,1,1,0,
4,0
,108,102,5,16,101,175,197,73,143,149,81,147,71,42,254,98,132,0,1,0,0,0,0,39,132,1
,39,35,136,1,35,1,3
,0,3,12,132,1,12,2,193,2,5,104,97,108,108,111,13,132,1,13,2,193,3,6,104,97,108,10
8,111,50,255,0,0
?-??hallo2  öº W              G     ?à?G?? ? lf??e»+IÅòQôG*¦bä ?    'ä?'#ê?#??
??ä???-??hallo

        11
ANYDATA()
JOM.MYOBJ2
            44
Typ=58 Len=44:
0,1,0,0,0,0,0,1,0,0,0,0,148,168,0,28,5,0,0,0,0,2,2,72,0,0,0,0,0,2,2,192,0,20,2,19
2,0,
21,2,192,0,22,0,0
 ?     ?    ö¿ ??    ??H     ??+ ¶?+ §?+ ?

8 rows selected.

          Followup:

it is going to be LOB storage and once the LOB gets bigger then about 4,000
bytes -- it goes out of line.

there is no capability beyond what I've shown for "getting the size" (not really
sure why you "need" the size)

November 24, 2003 jamie -- Thanks for the question regarding "view
      containing in ,or clause with outer join", version 8.1.7

      You Asked
Hi Tom,
I am currently rewriting sql server views into our oracle for an application.
One of the views that I have rewritten contains outer joins in the where clause
along with the "or" clause.This will not compile in the database it gives me
ORA-01719: outer join operator (+) not allowed in operand of OR or IN.I rewrote
the where portion that failed into a select statement in the from clause in
order to have this compile.Could you explain to me why it will not let me have
outer join and or in where portion of statement.
It also appears that there
are left outer joins on primary key columns.Shouldn't these be equi join or have
the (+) on the column other then pkey.Would there be an instance where I would
want (+) join on a primary key column.
Could you please clarify this area for me so I can better understand
under what conditions (+) join is appropriate.
Below is the original sql server view followed by it being rewritten with the
"or" clause failing and then the compiled copy with the "or" portion written in
the from clause.
As usual any advice is greatly appreciated and thanks for your help.

(sql server)
CREATE VIEW imrpt_work_load_schd_by_craft
AS
SELECT
    wor.pkey,
    wor.id ,
    wor.wo_num,
    wor.date_scheduled,
    wor.date_completed,
    wos.id as status_id,
    wor.description as wo_description,
    CASE WHEN (wor.id IS NULL OR wor.id =  ' ')
            THEN CONVERT(NVARCHAR(25), wor.wo_num)  ELSE
            RTRIM(wor.id) + '-' + CONVERT(NVARCHAR(25), wor.wo_num) END AS
WO_ID,
    c.id as resource_id,
    cnt.id as 'craft_id',
    st.id AS site_id,
    st.description AS site_description

FROM    imtbl_woap_est_res war LEFT OUTER JOIN
    imtbl_craft cnt ON cnt.pkey = war.craft LEFT OUTER JOIN
    imtbl_contact c ON war.resources = c.pkey LEFT OUTER JOIN
    imtbl_woap woa ON woa.pkey = war.work_order_asset_procedure   LEFT OUTER
JOIN imtbl_work_order wor ON wor.pkey = woa.work_order LEFT OUTER JOIN
imtbl_work_order_status wos ON wor.status = wos.pkey,
    imtbl_site st
WHERE wos.category = 1 OR wos.category = 2  OR wos.category = 3 AND
war.Remaining_Hours > 0

(oracle failed view)
CREATE OR REPLACE VIEW imrpt_work_load_schd_by_craft (
   pkey,
   id,
   wo_num,
   date_scheduled,
   date_completed,
   status_id,
   wo_description,
   wo_id,
   resource_id,
   craft_id,
   site_id,
   site_description )
AS
SELECT
    wor.pkey,
    wor.id ,
    wor.wo_num,
    wor.date_scheduled,
    wor.date_completed,
    wos.id as status_id,
    wor.description as wo_description,
    CASE WHEN (wor.id IS NULL OR wor.id =  ' ')
            THEN to_char(wor.wo_num)  ELSE
            RTRIM(wor.id)||'-'||to_char( wor.wo_num) END AS WO_ID,
    c.id as resource_id,
    cnt.id as craft_id,
    st.id AS site_id,
    st.description AS site_description
FROM    imtbl_woap_est_res war,
    imtbl_craft cnt,
    imtbl_contact c,
    imtbl_woap woa,
    imtbl_work_order wor,
    imtbl_work_order_status  wos,
    imtbl_site st
WHERE

      WOS.CATEGORY = 1 OR WOS.category = 2  OR WOS. category = 3 AND
      war.Remaining_Hours > 0 AND
      cnt.pkey(+) = war.craft AND
      war.resources = c.pkey(+) AND
      woa.pkey(+) = war.work_order_asset_procedure AND
      wor.pkey(+) = woa.work_order AND
      wor.status = wos.pkey

(or claues moved into from clause)

CREATE OR REPLACE VIEW imrpt_work_load_schd_by_craft (
   pkey,
   id,
   wo_num,
   date_scheduled,
   date_completed,
   status_id,
   wo_description,
   wo_id,
   resource_id,
   craft_id,
   site_id,
   site_description )
AS
SELECT
    wor.pkey,
    wor.id ,
    wor.wo_num,
    wor.date_scheduled,
    wor.date_completed,
    wos.id as status_id,
    wor.description as wo_description,
    CASE WHEN (wor.id IS NULL OR wor.id =  ' ')
            THEN to_char(wor.wo_num)  ELSE
            RTRIM(wor.id)||'-'||to_char( wor.wo_num) END AS WO_ID,
    c.id as resource_id,
    cnt.id as craft_id,
    st.id AS site_id,
    st.description AS site_description
FROM    imtbl_woap_est_res war,
    imtbl_craft cnt,
    imtbl_contact c,
    imtbl_woap woa,
    imtbl_work_order wor,
    (select id,pkey from imtbl_work_order_status where CATEGORY 1 OR category =
2  OR category = 3) wos,
    imtbl_site st
WHERE

      war.Remaining_Hours > 0 AND
      cnt.pkey(+) = war.craft AND
      war.resources = c.pkey(+) AND
      woa.pkey(+) = war.work_order_asset_procedure AND
      wor.pkey(+) = woa.work_order AND
      wor.status = wos.pkey

      and we said...
No chance of using a database written this century?

but anyway, there is NO outer join using an OR in the original query???

That, and the use of the OUTER JOIN is in VAIN in this case (eg: the original
author did not understand what they were doing!  hope it wasn't you ;)

using OR's and AND's without paren's is a recipe for disaster as well! Your
where clause should use parens so it is obvious that:

WHERE wos.category = 1 OR wos.category = 2  OR wos.category = 3 AND
war.Remaining_Hours > 0

is being intrepreted as:

WHERE (wos.category = 1 OR wos.category = 2  OR wos.category = 3) AND
war.Remaining_Hours > 0)

(hope that's what you meant to have it mean!!!)

FROM
imtbl_woap_est_res war LEFT OUTER JOIN
 imtbl_craft cnt ON cnt.pkey = war.craft LEFT OUTER JOIN
  imtbl_contact c ON war.resources = c.pkey LEFT OUTER JOIN
   imtbl_woap woa ON woa.pkey = war.work_order_asset_procedure LEFT OUTER JOIN
    imtbl_work_order wor ON wor.pkey = woa.work_order LEFT OUTER JOIN
     imtbl_work_order_status wos ON wor.status = wos.pkey,
imtbl_site st
WHERE wos.category = 1
   OR wos.category = 2
   OR wos.category = 3
  AND war.Remaining_Hours > 0

Looking at that, you must understand that the WHERE clause is done AFTER the
joining.  So, just using THREE tables (to make it readable):

ops$tkyte@ORA920.US.ORACLE.COM> create table t1 ( x int );
Table created.

ops$tkyte@ORA920.US.ORACLE.COM> create table t2 ( x int );
Table created.

ops$tkyte@ORA920.US.ORACLE.COM> create table t3 ( x int );
Table created.

ops$tkyte@ORA920.US.ORACLE.COM> insert into t1 values ( 1 );
1 row created.

ops$tkyte@ORA920.US.ORACLE.COM> insert into t3 values ( 2 );
1 row created.

ops$tkyte@ORA920.US.ORACLE.COM> insert into t3 values ( 3 );
1 row created.

ops$tkyte@ORA920.US.ORACLE.COM> select *
  2    from t1 LEFT OUTER JOIN t2 wos on (t1.x = wos.x), t3
  3  /

         X          X          X
---------- ---------- ----------
         1                     2
         1                     3

So, this is similar to your query.  We have a bunch of tables "left outer
joined" to each other and the CARTESIAN producted with a last table (imtbl_site
st in your case).

Without a where clause, we get values back (t2 wos -- has NO rows).  Now, we add
the where clause -- which HAPPENS AFTER THE OUTER JOIN REMEMBER!!!

ops$tkyte@ORA920.US.ORACLE.COM> select *
  2    from t1 LEFT OUTER JOIN t2 wos on (t1.x = wos.x), t3
  3   where wos.x = 1 or wos.x = 2
  4  /

no rows selected

and since we have a predicate on a column that would be NULL if we actually
outer join to it -- it immediate drops out of the result set.  The outer join to
WOS in your case is *totally and 100% a waste of time and resources*.  The only
thing it is doing is removing other possibly better access paths from
consideration by the optimzer!!!!!

If we EVER outer join to WOS, it can *never ever* satisfy the where clause
(cause the columns are NULL).

So -- three things here:

o you are not having an outer join in the JOINS at all
o your outer join to WOS is totally useless here
o if you used the current production release of Oracle -- the outer join syntax
would just "go", no rewrite.

This is the 8i rewrite:

  FROM imtbl_woap_est_res war,
       imtbl_craft cnt ,
       imtbl_contact c ,
       imtbl_woap woa ,
       imtbl_work_order wor ,
       imtbl_work_order_status wos ,
       imtbl_site st
 WHERE cnt.pkey(+) = war.craft
   and war.resources = c.pkey (+)
   and woa.pkey(+) = war.work_order_asset_procedure
   and wor.pkey(+) = woa.work_order
   and wor.status = wos.pkey(+)
   and wos.category in (1,2,3)
   and war.Remaining_Hours > 0

Although, as I mentioned -- the outer join to WOS is toally not needed -- you
can drop the (+) on the wos.pkey join condition without changing the meaning at
all!!!  In fact, that'll make the optimizer have a much easier go at it.

Review &
      Followup

      Rating: 5
view outer
      join, or clause  September 20, 2002
Reviewer:
      A reader
Tom,
Thanks for the review. Your final solution
FROM imtbl_woap_est_res war,
       imtbl_craft cnt ,
       imtbl_contact c ,
       imtbl_woap woa ,
       imtbl_work_order wor ,
       imtbl_work_order_status wos ,
       imtbl_site st
 WHERE cnt.pkey(+) = war.craft
   and war.resources = c.pkey (+)
   and woa.pkey(+) = war.work_order_asset_procedure
   and wor.pkey(+) = woa.work_order
   and wor.status = wos.pkey(+)
   and wos.category in (1,2,3)
   and war.Remaining_Hours > 0

is what I originally had but for some reason I had problems compiling that as
well.As for the original author no it's not me and trying to get reason as to
why they are writing the (250+)views in sql server this way is impossible.The
original creators cannot actually explain or give reason why they are doing this
so I am left to figure out their work and then try and fix it as I rewrite it
into oracle.
Hope you can understand my situation and why there is some confusion.

          Followup:

did the above work for you or not?  If not, supply the error code/message.

I guess it is because SQLServer is so incredibly easy that they don't even need
to learn SQL and understand what they are doing.  The software just must
intuitively know what they want and give it to them ;)  (totally tongue in cheek
there)

That was not my final solution however, I looked at this more and it turns out
that more of the outer joins are an utter waste (I suppose they didn't really
understand the question they were asking, just some syntax).

If the outer join to WOR happens, WOS.* will be null.
If the outer join to WOA happnes, WOR will be null and outer joined to and WOS.*
will be null.

So, you should find that:

 WHERE cnt.pkey(+) = war.craft
   and war.resources = c.pkey (+)
   and woa.pkey = war.work_order_asset_procedure
   and wor.pkey = woa.work_order
   and wor.status = wos.pkey
   and wos.category in (1,2,3)
   and war.Remaining_Hours > 0

is what you need.  No outer joins to WOA, WOR, WOS -- for if they actually
happen the WHERE clause will remove them!

      Rating: 5
how left outer
      join work  November 23, 2003
Reviewer:
      lizhuohua  from china
hi,tom
  look this
create table t1(a int,b int);
create table t2(a int,b int);
insert into t1 values(1,2);
insert into t1 values(2,2);
insert into t1 values(3,2);
insert into t1 values(4,2);
insert into t2 values(1,1);
insert into t2 values(2,1);
insert into t2 values(3,2);
insert into t2 values(4,2);

select *
from t1 left outer join t2 on t1.a=t2.b and (t2.b=1 or t2.b=2)
where t2.b is not null;
a    b    a    b
------------------------------------
1    2    1    1
1    2    2    1
2    2    3    2
2    2    4    2
3    2
4    2

select *
from t1 left outer join t2 on t1.a=t2.b and (t2.b=1 or t2.b=2)
where t2.b is null;
a    b    a    b
-----------------------------------
1    2
2    2
3    2
4    2

this is right:
select *
from t1 left outer join (select * from t2 where (t2.b =1   or t2.b=2)) t on
t1.a=t.b
where t.a is null;

         A          B          A          B
---------- ---------- ---------- ----------
         4          2
         3          2
could you explain to me how the outer join work.

(
  Actually t2 maybe has lots of other data,
  so I use (t2.b=1 or t2.b=2)
)
(sorry!My english is very poor ,I hope you can understand me)

          Followup:

I do NOT get the results you do in 9.2.  I get very different answers (cut and
pastes from sqlplus are always appreciated).  In 9iR1, there does seem to be an
issue with these queries (recommend 9iR2 for sure, at the very least, contact
support please)

conceptually  T1 left outer join T2 on (any condition)
will be processed using the following logic. below are the outputs I recieve
from your queries and the procedural code that emulates the behaviour

ops$tkyte@ORA920> select *
  2  from t1 left outer join t2 on t1.a=t2.b and (t2.b=1 or t2.b=2)
  3  where t2.b is not null;

         A          B          A          B
---------- ---------- ---------- ----------
         1          2          1          1
         1          2          2          1
         2          2          3          2
         2          2          4          2

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      l_found boolean := false;
  3      l_made_up  t2%rowtype;
  4  begin
  5      for x in ( select * from t1 )
  6      loop
  7          l_found := FALSE;
  8          for y in ( select *
  9                       from t2
 10                      where x.a=t2.b
 11                        and (t2.b=1 or t2.b=2) )
 12          loop
 13              l_found := true;
 14              if ( y.b is not null )
 15              then
 16                  dbms_output.put_line
 17                  ( 'joined ' || x.a ||','||x.b||','||
 18                                 y.a||','||y.b );
 19              end if;
 20          end loop;
 21          if ( NOT l_found )
 22          then
 23              if ( l_made_up.b is not null )
 24              then
 25                  dbms_output.put_line
 26                  ( 'made up ' ||
 27                     x.a ||','||x.b||','||
 28                     l_made_up.a||','||l_made_up.b );
 29              end if;
 30          end if;
 31      end loop;
 32  end;
 33  /
joined 1,2,1,1
joined 1,2,2,1
joined 2,2,3,2
joined 2,2,4,2

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select *
  2  from t1 left outer join t2 on t1.a=t2.b and (t2.b=1 or t2.b=2)
  3  where t2.b is null;

         A          B          A          B
---------- ---------- ---------- ----------
         3          2
         4          2

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      l_found boolean := false;
  3      l_made_up  t2%rowtype;
  4  begin
  5      for x in ( select * from t1 )
  6      loop
  7          l_found := FALSE;
  8          for y in ( select *
  9                       from t2
 10                      where x.a=t2.b
 11                        and (t2.b=1 or t2.b=2) )
 12          loop
 13              l_found := true;
 14              if ( y.b is null )
 15              then
 16                  dbms_output.put_line
 17                  ( 'joined ' || x.a ||','||x.b||','||
 18                                 y.a||','||y.b );
 19              end if;
 20          end loop;
 21          if ( NOT l_found )
 22          then
 23              if ( l_made_up.b is null )
 24              then
 25                  dbms_output.put_line
 26                  ( 'made up ' ||
 27                     x.a ||','||x.b||','||
 28                     l_made_up.a||','||l_made_up.b );
 29              end if;
 30          end if;
 31      end loop;
 32  end;
 33  /
made up 3,2,,
made up 4,2,,

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select *
  2  from t1 left outer join (select * from t2 where (t2.b =1   or t2.b=2)) t on
 3  t1.a=t.b
  4  where t.a is null;

         A          B          A          B
---------- ---------- ---------- ----------
         4          2
         3          2

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      l_found boolean := false;
  3      l_made_up  t2%rowtype;
  4  begin
  5      for x in ( select * from t1 )
  6      loop
  7          l_found := FALSE;
  8          for y in ( select *
  9                       from (select * from t2 where (b=1 or b=2)) t2
 10                      where x.a=t2.b )
 11          loop
 12              l_found := true;
 13              if ( y.a is null )
 14              then
 15                  dbms_output.put_line
 16                  ( 'joined ' || x.a ||','||x.b||','||
 17                                 y.a||','||y.b );
 18              end if;
 19          end loop;
 20          if ( NOT l_found )
 21          then
 22              if ( l_made_up.a is null )
 23              then
 24                  dbms_output.put_line
 25                  ( 'made up ' ||
 26                     x.a ||','||x.b||','||
 27                     l_made_up.a||','||l_made_up.b );
 28              end if;
 29          end if;
 30      end loop;
 31  end;
 32  /
made up 3,2,,
made up 4,2,,

PL/SQL procedure successfully completed.

      Rating:
      5
Thx  November 24, 2003
Reviewer:
      lizhuohua  from China
Tom,
Thanks for the review.
I try it again,and i get this:

SQL*Plus: Release 9.2.0.1.0 - Production on &#26143;&#26399;&#19968; 11&#26376;
24 19:10:12 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

&#36830;&#25509;&#21040;:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

lizh@lizhdb>select * from t1;

         A          B
---------- ----------
         1          2
         2          2
         3          2
         4          2

lizh@lizhdb>select * from t2;

         A          B
---------- ----------
         1          1
         2          1
         3          2
         4          2

lizh@lizhdb>select *
  2  from t1 left outer join t2 on t1.a=t2.b and (t2.b=1 or t2.b=2)
  3  where t2.b is not null;

         A          B          A          B
---------- ---------- ---------- ----------
         1          2          1          1
         1          2          2          1
         2          2          3          2
         2          2          4          2
         3          2
         4          2

lizh@lizhdb>select *
  2  from t1 left outer join t2 on t1.a=t2.b and (t2.b=1 or t2.b=2)
  3  where t2.b is null;

         A          B          A          B
---------- ---------- ---------- ----------
         1          2
         2          2
         3          2
         4          2
lizh@lizhdb>select *
  2  from t1 left outer join t2 on t1.a=t2.b and t2.b in (1,2)
  3  where t2.b is null;

         A          B          A          B
---------- ---------- ---------- ----------
         1          2
         2          2
         3          2
         4          2

If IN or OR in the condition the result is wrong.
I try it on another machine(Release 9.2.0.1.0),I get the same results.
(I try that procedure ,I get same results as you.)

          Followup:

please contact support -- also, it does not reproduce in 9204 - the current
patch set for 92

ops$tkyte@ORA920PC> select *
  2    from t1 left outer join t2 on t1.a=t2.b and (t2.b=1 or t2.b=2)
  3    where t2.b is not null;

         A          B          A          B
---------- ---------- ---------- ----------
         1          2          1          1
         1          2          2          1
         2          2          3          2
         2          2          4          2

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select *
  2    from t1 left outer join t2 on t1.a=t2.b and (t2.b=1 or t2.b=2)
  3    where t2.b is null;

         A          B          A          B
---------- ---------- ---------- ----------
         3          2
         4          2

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select *
  2    from t1 left outer join t2 on t1.a=t2.b and t2.b in (1,2)
  3   where t2.b is null;

         A          B          A          B
---------- ---------- ---------- ----------
         3          2
         4          2

ops$tkyte@ORA920PC> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
PL/SQL Release 9.2.0.4.0 - Production
CORE    9.2.0.3.0       Production
TNS for Linux: Version 9.2.0.4.0 - Production
NLSRTL Version 9.2.0.4.0 - Production

      Rating:
      1
:-)  November 24, 2003
Reviewer:
      lizhuohua  from China
&#26143;&#26399;&#19968; 11&#26376;  is Chinese.

      Rating: 5
Thank
      you.  November 25, 2003
Reviewer:  lizhuohua
       from China
tom,
  Thank you very much.:-)

November 24, 2003 -- Thanks for the question regarding "exp imp
      issue", version 9.2

      You Asked
Hi Sir,
       I recently did an exp of one of our production schema version 817. I want
to imp this on my local laptop where Database server is installed version 9.2.
I created the same user with LMT and auto extend of size 500 MB.The production
DB schema was not LMT but DMT.
 I kept getting this error during imp like
IMP-00003: ORACLE error 1659 encountered
ORA-01659: unable to allocate MINEXTENTS beyond 4 in tablespace USER_DATA
IMP-00017: following statement failed with ORACLE error 1658:
 "ALTER TABLE "XYZ" ADD  PRIMARY KEY ("XYZ_PK") USING INDEX PCTFREE 10 I"
 "NITRANS 2 MAXTRANS 255 STORAGE(INITIAL 4194304) TABLESPACE "USER_DATA" ENAB"
 "LE".
or some errors like
IMP-00003: ORACLE error 1658 encountered
ORA-01658: unable to create INITIAL extent for segment in tablespace USER_DATA
IMP-00017: following statement failed with ORACLE error 1659:
 "CREATE TABLE "DEPT" ("DEPT_NUM" NUMBER(22, 0) NOT NULL ENABLE, "USERI"
 "D" NUMBER(22, 0), "LASTMOD" DATE, "COL_NUM1" NUMBER(22, 0) NOT NULL ENABLE"
 ", "COL_NUM2" NUMBER(22, 0) NOT NULL ENABLE, "COL_NUM3" NUMBER(22, 0))  PCTF"
 "REE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 LOGGING STORAGE(INITIAL 4194304) "
 "TABLESPACE "USER_DATA""
IMP-00003: ORACLE error 1659 encountered
ORA-01659: unable to allocate MINEXTENTS beyond 3 in tablespace USER_DATA

So I had to increase the datafile size associated with the Tablespace.
I kept getting this error until I created 10 datafiles of 1 GB each for the same
tablespace. All the objects are in one tablespace user_data. Only then the
Import was successful.

Question
 1)If i used LMT with auto extend why did it run out of space and not extend
itself automatically.As it is LMT should it not ignore the initial extents etc
etc clause.( I did not use uniform allocation)
 2)I know there is not much data in the schema but the way the create tables
have done it takes some many extents resulting in 10 Gb tablespace.only 4 tables
have about 30000 rows.
  Am i correct inthis assumption.
How to reduce this extra tablespace consumption of 10 Gb total datafiles.
 3)wrt point 2 I am sure schema will fit in datafile of just 300 MB how to do
this.When I did the exp i used direct path and compress=n

Thank you Sir.

      and we said...
1) lmts do not IGNORE initial, next, minextents, pctincrease.  they just use
them differently.  Watch:

ops$tkyte@ORA920> create tablespace demo
  2  datafile
  3  uniform size 64k;
Tablespace created.

64k uniform extents..

ops$tkyte@ORA920> create table t ( x int ) storage (initial 2m ) tablespace
demo;
Table created.

we ask for an initial of 2m, in a DMT it would look for 2m of contigous space.
 In an uniform LMT, it looks for 2m of space using many uniform extents:

ops$tkyte@ORA920> select sum(bytes), count(*) from user_extents where
segment_name = 'T';

SUM(BYTES)   COUNT(*)
---------- ----------
   2097152         32

and it takes 32 64k extents to give us 2m

ops$tkyte@ORA920> drop table t;
Table dropped.

ops$tkyte@ORA920> create table t ( x int ) storage ( minextents 50 ) tablespace
demo;
Table created.

now, we specify no initial, just minextents -- and we got it:

ops$tkyte@ORA920> select sum(bytes), count(*) from user_extents where
segment_name = 'T';

SUM(BYTES)   COUNT(*)
---------- ----------
   3276800         50

ops$tkyte@ORA920> drop table t;

Table dropped.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t ( x int ) storage ( initial 2m minextents 50 )
tablespace demo;
Table created.

now it gets interesting.  A 2m initial extent (we know that'll take 32) and
minextents 50.  What'll do?

ops$tkyte@ORA920> select sum(bytes), count(*) from user_extents where
segment_name = 'T';

SUM(BYTES)   COUNT(*)
---------- ----------
   5308416         81

81 extents -- well, 81-32 = 49.  so what it did was:

o got our initial
o determined that now that the single initial was gotten -- we needed 49 more

just like it would have done in a DMT....

ops$tkyte@ORA920> drop table t;

Table dropped.

ops$tkyte@ORA920> create table t ( x int ) storage ( initial 128k minextents 5
pctincrease 100 ) tablespace demo;

Table created.

Ok, now what.  well, it'll be:

128 initial (2 extents) plus four more extents... no next specified so it used
64k -- it'll add:

64+128+256+512

ops$tkyte@ORA920> select sum(bytes), count(*) from user_extents where
segment_name = 'T';

SUM(BYTES)   COUNT(*)
---------- ----------
   1114112         17

and that is what it did:

ops$tkyte@ORA920>
ops$tkyte@ORA920> select 128 + 64 + 128 + 256 + 512 from dual;

128+64+128+256+512
------------------
              1088

ops$tkyte@ORA920> select 1114112/1024 from dual;

1114112/1024
------------
        1088

2) you might want to extract the ddl from the dmp file (use indexfile=foo.sql
and foo.sql will have it) and precreate the objects WITHOUT a storage clause.

Review &
      Followup

      Rating: 5
Excellant Sir.
      and a Q  August 04, 2003
Reviewer:
      A reader
Thanks for your convincing Answer.
Wrt answer 2: This means
1)I extract the DDLs for table creation using indexfile option
2)Modify it by removing the storage clause
3)Run that on the user
4)Run imp again with ignore=y. This will get the data in the precreated tables.
  Q) What to do about Table constraints will the IMP take care of it or I need
to precreate those also ?

Please correct me I am wrong.

          Followup:

imp will put the constraints on for you.  although you might find you want to
put on the indexes manually as well since they too have storage clauses.

      Rating: 5
Thank You sir
        August 04, 2003
Reviewer:  A reader

You are a Greatest service to the ORACLE CLAN

      Rating: 5
IMP-00013: only
      a DBA can import a file exported by another DBA  November
      24, 2003
Reviewer:  Yogeeraj  from Mauritius

hi sir,

Since only a DBA can import a file exported by another DBA, what can be the
workaround that can allow non-dba users to IMP from the DMP file created by the
DBA (Full Export)?

We do not want to grant either of DBA or IMP_FULL_DATABASE roles to our
developerss yet allow them import from the weekly dump of our dev database.

thank you for your guidance.

best regards
Yogeeraj
=========================
SQL> create user demo_imp identified by demo_imp;
User created.

Elapsed: 00:00:00.01
SQL> grant connect, resource to demo_imp;
Grant succeeded.

Elapsed: 00:00:00.04
SQL> connect demo_imp/demo_imp@dev
Connected.
SQL> create table t (a number(1));

Table created.

SQL> exit
Disconnected from Oracle8i Enterprise Edition Release 8.1.7.4.0 - 64bit Producti
on
With the Partitioning option
JServer Release 8.1.7.4.0 - 64bit Production
$ exp system/manager@dev file=demo_imp.dmp owner=demo_imp

Export: Release 8.1.7.4.0 - Production on Mon Nov 24 15:42:09 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.4.0 - 64bit Production
With the Partitioning option
JServer Release 8.1.7.4.0 - 64bit Production
Export done in US7ASCII character set and US7ASCII NCHAR character set

About to export specified users ...
. exporting pre-schema procedural objects and actions
. exporting foreign function library names for user DEMO_IMP
. exporting object type definitions for user DEMO_IMP
About to export DEMO_IMP's objects ...
. exporting database links
. exporting sequence numbers
. exporting cluster definitions
. about to export DEMO_IMP's tables via Conventional Path ...
. . exporting table                              T          0 rows exported
. exporting synonyms
. exporting views
. exporting stored procedures
. exporting operators
. exporting referential integrity constraints
. exporting triggers
. exporting indextypes
. exporting bitmap, functional and extensible indexes
. exporting posttables actions
. exporting snapshots
. exporting snapshot logs
. exporting job queues
. exporting refresh groups and children
. exporting dimensions
. exporting post-schema procedural objects and actions
. exporting statistics
Export terminated successfully without warnings.
$ imp demo_imp/demo_imp@dev file=demo_imp.dmp ignore=yes

Import: Release 8.1.7.4.0 - Production on Mon Nov 24 15:43:19 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.4.0 - 64bit Production
With the Partitioning option
JServer Release 8.1.7.4.0 - 64bit Production

Export file created by EXPORT:V08.01.07 via conventional path
IMP-00013: only a DBA can import a file exported by another DBA
IMP-00000: Import terminated unsuccessfully
$

          Followup:

you'll either

a) import for them
b) do the export in a format that they can use.

November 24, 2003 tamir -- Thanks for the question regarding "How
      can I get information of success or failure for individual record during
      bulk update and insert while using Pro*C", version version 8.1.6

      You Asked
How can I get information of success or failure for individual
record during bulk update and insert while using Pro*C. For
instance, I have a table T1, which has 1000 records initially.
In the application, the data buffer contains 1200 records to be
written into T1. I can use update followed by insert in OCI
using returning clause. After update, I know there are 200
records not updated successfully by checking returning value,
then I can use the insert for these 200 records (Note: there is
no bad data in the case). I know how to do it using loop on the
1,200 records, I wonder if there is a better and more efficient
way to do it without using loop.

I will appreciate if you can provide a simple example code on
how to achieve that goal.

      and we said...
You can use the same method in Oracle8i release 2 with proc.
It'll look like this:

main( argc, argv )
int argc;
char *  argv[];
{
exec sql begin declare section;
char        oracleid[50] = "scott/tiger";
int         i;
int         deptno[4] = { 100, 10, 20, 30 };
char        dname[4][10] = { "dept1", "dept2", "dept3", "dept4"
};
int         return_deptno[4];

exec sql end declare section;

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();
    EXEC SQL WHENEVER SQLWARNING DO sqlerror_hard();
    EXEC SQL CONNECT :oracleid;

    EXEC SQL
      UPDATE DEPT
         SET DNAME = :dname
       WHERE DEPTNO = :deptno
   RETURNING DEPTNO into :return_deptno;

    printf( "sqlca.sqlcode = %d, sqlca.sqlerrd[2] = %d\n",
             sqlca.sqlcode, sqlca.sqlerrd[2] );

    for( i = 0; i < sqlca.sqlerrd[2]; i++ )
        printf( "Updated deptno = %d\n", return_deptno[i] );
}

If you run that, it'll print out:

$ ./t
sqlca.sqlcode = 0, sqlca.sqlerrd[2] = 3
Updated deptno = 10
Updated deptno = 20
Updated deptno = 30

when run against the scott/tiger DEPT table.  It'll show you the
rows you've updated by primary key value.
Review &
      Followup

      Rating:
      4
well  September 06, 2001
Reviewer:
      A reader
How about telling us how to do the same in pl/sql

          Followup:

Sure, it looks very much the same:

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      type array is table of varchar2(20);
  3
  4      l_deptno array := array( 100,10,20,30 );
  5      l_return_deptno array;
  6      l_dname  array := array( 'dept1', 'dept2', 'dept3', 'dept4' );
  7  begin
  8      forall i in 1 .. l_deptno.count
  9        UPDATE DEPT
 10           SET DNAME = l_dname(i)
 11         WHERE DEPTNO = l_deptno(i)
 12     RETURNING DEPTNO BULK COLLECT INTO l_return_deptno;
 13
 14      for i in 1 .. l_return_deptno.count
 15      loop
 16          dbms_output.put_line( 'updated deptno = ' || l_return_deptno(i) );
 17      end loop;
 18  end;
 19  /
updated deptno = 10
updated deptno = 20
updated deptno = 30

PL/SQL procedure successfully completed.

      Rating: 4
Thanks
      Tom  October 04, 2001
Reviewer:  Robin
       from Providence, RI USA
Most likely the next thing we want to do is an INSERT
of the rows that weren't updated.
As far as I know, the only way to do this is by checking
each key returned by the RETURNING clause against the keys
we sent for UPDATE.  The ones that DIDN'T come back are the
ones we need to INSERT.  Copy those into another array,
then do a bulk INSERT.

This requires going through the arrays in a loop,
whether you use PRO*C or PL/SQL, comparing one key per
iteration.  I believe that the keys are returned
in the same order they were sent, or this wouldn't work.

It would be nice if Oracle could return the row numbers
that were updated (1,2,3 in your DEPT example), then the
program could more easily copy the other rows to the
array used for insert.

Or have I missed something?

          Followup:

Always something more ;)

Ok, here is a quickie example in PLSQL that shows one alternative method:

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

Table dropped.

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

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from dept;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING     NEW YORK
        20 RESEARCH       DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS     BOSTON

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      type array is table of varchar2(20);
  3
  4      l_deptno array := array( 99,10,20,30 );
  5      l_return_deptno array;
  6      l_dname  array := array( 'dept1', 'dept2', 'dept3', 'dept4' );
  7  begin
  8      forall i in 1 .. l_deptno.count
  9        UPDATE DEPT
 10           SET DNAME = l_dname(i)
 11         WHERE DEPTNO = l_deptno(i)
 12     RETURNING DEPTNO BULK COLLECT INTO l_return_deptno;
 13
 14     forall i in 1 .. l_deptno.count
 15        insert into dept ( deptno, dname )
 16        select l_deptno(i), l_dname(i)
 17          from dual
 18         where NOT EXISTS (select null from dept where deptno = l_deptno(i));
 19
 20      for i in 1 .. l_return_deptno.count
 21      loop
 22          dbms_output.put_line( 'updated deptno = ' || l_return_deptno(i) );
 23      end loop;
 24  end;
 25  /
updated deptno = 10
updated deptno = 20
updated deptno = 30

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from dept;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 dept2          NEW YORK
        20 dept3          DALLAS
        30 dept4          CHICAGO
        40 OPERATIONS     BOSTON
        99 dept1

and if you have 9i, you can do it in one statment:

ops$tkyte@ORA9I.WORLD> drop table dept;

Table dropped.

ops$tkyte@ORA9I.WORLD> create table dept as select * from scott.dept;

Table created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> select * from dept;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING     NEW YORK
        20 RESEARCH       DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS     BOSTON

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> declare
  2      type array is table of varchar2(20);
  3
  4      l_deptno array := array( 99,10,20,30 );
  5      l_return_deptno array;
  6      l_dname  array := array( 'dept1', 'dept2', 'dept3', 'dept4' );
  7  begin
  8      forall i in 1 .. l_deptno.count
  9        merge into dept
 10        using ( select l_deptno(i) deptno, l_dname(i) dname from dual ) d
 11        on ( dept.deptno = d.deptno )
 12        when matched then
 13             update set dept.dname = d.dname
 14        when NOT matched then
 15             insert ( deptno, dname ) values ( d.deptno, d.dname );
 16  end;
 17  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> select * from dept;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 dept2          NEW YORK
        20 dept3          DALLAS
        30 dept4          CHICAGO
        40 OPERATIONS     BOSTON
        99 dept1

      Rating: 4
Error in Bulk
      "insert"  October 05, 2001
Reviewer:  READER
       from USA
HI, Tom,

In the above example, if there is any exception happened in the course of bulk
"insert", let say ORA-1400 and/or others errors happened for one may be two
rows(but for the other rows, they are succeed), what is the best way and how we
handle these potential error? That is we let them continue insert or rollback
the whole process for all rows? And how?
Please address both in proC and pl/sql environment and 9i by using "merge".

Thanks

          Followup:

You tell ME what is acceptable.  Me, I would reject the batch (easiest to code,
never understood the benefit of a partial batch).  In that case -- just ROLLBACK
when you get the error and all is UNDONE.  You could go a step further and use
savepoints:

   begin
     savepoint foo;
     insert
     update
     merge
     whatever.....
   exception
    when others then rollback to savepoint; RAISE;
   end;

      Rating: 4
insert with
      array of structures stops at the first error  May 22, 2002

Reviewer:  somala subba
100 records in the array to be inserted. Some error in record 5 but the rest are
fine.

When i do insert into temp_table values(:array)

it stops on record 5. returns sqlerrd[2]= 4.
Is there anyway to get all 99 records inserted except for the error rows in the
above statement.

          Followup:

You have to retry the insert, passing array+5 as the host variable (to skip over
the rows loaded and the bad row).

Even SQLLDR does this.

      Rating: 5
why this
      happening -- 8.1.6  April 02, 2003
Reviewer:
      John
Tom,

in the below, pl/sql block, i see the the variable v_total_amount(which is
declared in other part of the code) becoming null when there is no rows found by
the update statement.

Begin
   --
   update sal = sal + least(sal *.25,v_total_amount)
   where empno = :i_empno
   and   active = 'Y'
   returning v_total_amount - nvl(least(sal*.25,v_total_amount),0) into
v_total_amount;
   --
end;

am i doing wrong anywhere?

          Followup:

the update suceeded.

hence, it returned the value -- NULL (unknown).

No but, desired, expected behaviour.

If not what you want, code:

  update ... returning into some_temp_variable;
  if (sql%rowcount > 0 ) then
     v_total_amount := some_temp_variable;
  end if;

"no rows found" is not an error, no problem, not an issue with an update.  It is
perfectly normal in fact.

      Rating: 4
savepoint
      information  April 28, 2003
Reviewer:
      Anurag  from INDIA
Tom , I was issuing savepoint while doing certain transactions, I wanted to know
that Can I retrieve the savepoint information , if Yes which view should I
query. Also, If I commit, does this information is lost or marked as committed.

Regards,

          Followup:

savepoints are internal to your logic (hopefully, you would never need query
them as transaction control is so vital, so important -- that you would know
exactly where they are)

When you commit -- this information is "not relevant".  It is not that it is
lost, it quite simply isn't relevant.

A savepoint is just the ability for you to rollback a subset of your transaction
-- it affects commits not at all, not even a little bit.

      Rating:
      5
savepoint  April 29, 2003
Reviewer:
      Anurag  from India
Thank you Tom, for clearing my doubts. I appreciate, your quick reply. Thanks
once again.

      Rating: 5
returning thing
      I inserted  November 24, 2003
Reviewer:
      A reader
Hi

I would like to know what rows I inserted in a table, I tried to use

declare
   type array is table of dept.deptno%type;
   l_deptno array;
begin
   insert into dept
   select * from x where not exists (select null from dept where x.deptno =
dept.deptno)
   RETURNING DEPTNO BULK COLLECT INTO l_deptno;
   for i in 1 .. l_deptno.count
   loop
      dbms_output.put_line(l_deptno(i));
   end loop;
end;
/

but gives errors... I dont know why :-0

          Followup:

it does not appear to be a supported construct -- it only works with "values",
not select (which seems backwards since values can only insert a row -- bulk
collect doesn't make sense)

      Rating: 5
bulk collect
      into records  November 24, 2003
Reviewer:
      A reader
Hi

Can do we return records into collections in 8.1.7

such as

insert into a select * from b
returning record bulk collect into array

          Followup:

nope.

      Rating: 3
returning
      things i inserted  November 24, 2003
Reviewer:
      A reader
Hi

It sounds odd that bulk collect does not work with insert select. Insert select
is bulk!

Anyway, is there anyway to know what rows I inserted?

Cheers

          Followup:

short of

a) bulk collect them
b) forall i insert them

instead of insert into as select -- not really.

November 24, 2003 mo -- Thanks for the question regarding "Web
      Application Testing", version 8.1.7

      You Asked
Tom:

1. If you have a web application that is performing well when accessed by one
borwser, how would you do testing in a real production mode with thousands of
users using it?

2.  If it slows down sometimes, how would you know if it is from the Network,
Netscape, application, or server box? It seems Netscape 4.7 is slower than IE
6.0?

3.  What is your criteria on when to use MTS (Shared server configuration). Is
it when you have more than 1000 users?  It seems in your book you always
recommend to use dedicated server mode.

4.  Since the client here is the application server, is it when everytime a user
uses it using the browser, the app server creating a connection to the database
even though it is the same userid/password?
How is the connection (server process) get closed or deleted? is it when the
user closes his browser? would the app server(mod_plsql) have a maximum number
of browser requests or database connections it can handle.

Thank you,

      and we said...
1) I have a custom "grab" and "hurl" program that flings URLs at the web server
(grab works like a proxy server, records a session, hurl -- well, you can
imagine by the name what it does).

You could either build your own infrastructure or buy one -- like mecury
loadrunner.  there are other public domain web "flingers" out there as well you
can research (on your own)

2) netscape 4.7 has issues with tables in tables in tables in tables.  Netscape
4.7 is older then dirt.  Netscape 6 and 7 are faster then IE in my experience
and Mozilla is better then the lot (I love that I never see ads anymore, never
have annoying pop-ups, no more animated silly gifs -- all thanks to mozilla.  My
favorite feature "block images from this server" -- ads begone!)

read

http://technet.oracle.com/docs/products/ias/doc_library/90200doc_otn/core.902/a95102/toc.htm
instruementing your code helps.  For example, if you see a URL on asktom with NO
in it, change it to YES and see what happens.  Goto the asktom home page, click
on the first article there.  You'll see timings down the side of the page --
very very helpful (mandatory in fact) for finding out what part of a page is
causing a slowdown (if the PAGE is the bottleneck)

auditing helps -- eg: all of my pages on asktom are measured and every request
is an audit trail entry in a nice little log.  I can tell you anything I need -
how many hits (page views - minus images and such), hits/second, hits/whatever,
how many distinct users, how many sessions, and most importantly perhaps -- how
long each and every page took to generate in the database.  I can tell you for
example that in the last couple of hours (this was a holiday weekend) that:

I had 4,277 pages views.
from 584 ip addresses
representing 694 users (firewalls/nat make some users look the "same")
they hit an average of 6-7pages
they hit 21 distinct page types
the longest it took to generate a page was 6.71 seconds (that was me, running
this report)
the fastest was 0.11
the average was in the 0.27 range

Now, say asktom was having a performance issue, I can tell you from that simple
report that:

a) it wasn't the database, the pages were returning from the db as fast as
expected.

b) if it WAS the database (say the max response time was thru the roof or the
average krept up to 1 second or something), I have a detailed log of how long
each page was taking -- I could ISOLATE quickly the page in question and then I
could drill down on that with the NO-YES parameter to see if it was a specific
query on that page.

This all assumes you thought about these things before you set down to develop
your application of course!!!  Designing for performance and tuning is
something that you need to build into the code from day one (read chapter 10 of
my book "Expert one on one Oracle" for more details on this philosophy.

there are those that will say "but tom, that stuff you have in your pages -- the
NO/YES trick, that is overhead".  Yes, you know what -- they are right.  But
there are two types of overhead in this world -- the type you don't need and can
get rid of and the type that if you don't have, you are totally lost.  Guess
which category I put this stuff into!

Now, if you add into this mix the Web Cache

http://technet.oracle.com/docs/products/ias/doc_library/90200doc_otn/caching.htm
you can start looking at network response times and bottlenecks as well - how
long the database takes to generate a page is one thing, how long the app server
is taking to dish it out is another -- and how long the end user is taking to
get the pages -- these are things a man in the middle like the web cache can
help you with (and it'll cache things to boot).

3) It seems in my book -- in the architecture chapter -- I outlined when to use
MTS.  When you physically cannot run dedicated server anymore because you are
out of resources.  Plain and simple.  You use MTS when you need to get the N'th
user logged into the database and performance goes down the tubes due to too
many OS processes from dedicated server (or when that nth user fails cause
you've exceeded some OS limit).

Re-read the architecture write up I have on that.  There is even a little
"graph" for that -- when you hit the point of dimishing marginal returns.

it may be 100 users
it may be 1000 users
it may be 5000 users
it may be 423 users

In short, it is not a function of users.

4) quote

"Since the client here is the application server, is it when everytime a user
uses it using the browser, the app server creating a connection to the database
even though it is the same userid/password?"

response "parse error -- I don't understand"

I think you are asking about connection pooling at the app server level.
see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6273432647961
Mod_plsql will reuse a connection -- so, if a connection already exists in the
pool maintained at the app server, no new connection.  mod_plsql might have to
create a new connection if none exist.

connection pooling is available for pretty much every language out there that
you can use in the middle tier.  with plsql it "just happens", with java you use
a jdbc connection pool.

Review &
      Followup

      Rating: 5
web
      testing  December 15, 2002
Reviewer:  mo

Tom:

Thanks for this info.  I always learn from the sea of info you have better than
any other source. however as a followup:

1. I never heard of "hurl" and "grab" or flingers as you call them.  However I
am curious how do those really fill up a html form and all save it at the same
time.  Would they replicate exactly an environment where 200 users log in, fill
up a form and try to save at same time.

2. You say you have a custom program for doing this.  Is this available
anywhere?

3. How do I instrument Pl/sql code.  any example?  I do not see timing when I
click on a link on your page like you said?  Do you insert things in pl/sql that
show up on status bar whilerunning code?

4. You showed me a neat auditing program results I like to do the same.  Is this
in pl/sql? is it posted anywhere? do you save it in db?

5.  Are you saying that for monitoring there are three parts: database, app
server, and network.  to monitor the last two you have to build utility in the
web cache?

6.  How do i know how many connections are available between app server and
database for users.  Is there a formula like for every 100 users create 10
connections or it really depends on usage too?

7.  Are you saying that difference between mod-plsql and servelts that in java
you have better control on the length of these connections and you can
create/shut while in oracle it is pre-configured?

Thank you,

          Followup:

1) hurl and grab, as I said, are things I wrote myself.

you don't "fill up an HTML form", you send an HTTP request.  You can write these
sorts of programs only if you understand the HTTP protocol itself and how is all
really works.

Yes, you can use this to replicate such a situation.

You might be amazed at what pops up on google in response to a simple search
like:

web stress testing tool

2) nope.

3) you change the URL to have YES where it currently says NO - then you do see
timings.

instruementing code just means putting STUFF in it - dbms_output, utl_file,
conditional htp.p's (like i have here).  See "Beginning Oracle Programming" --
the other book on my home page.  In there, we describe and build a package we
call "debug" that is a nice layer on top of utl_file for instumenting your code
with trace stuff

4) we have a table:

flows@OSI> desc WWV_FLOW_ACTIVITY_LOG1$
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
------------------------------------
 TIME_STAMP                                                     DATE
 COMPONENT_TYPE                                                 VARCHAR2(255)
 COMPONENT_NAME                                                 VARCHAR2(255)
 COMPONENT_ATTRIBUTE                                            VARCHAR2(4000)
 INFORMATION                                                    VARCHAR2(4000)
 ELAP                                                           NUMBER
 NUM_ROWS                                                       NUMBER
 USERID                                                         VARCHAR2(255)
 IP_ADDRESS                                                     VARCHAR2(4000)
 USER_AGENT                                                     VARCHAR2(4000)
 FLOW_ID                                                        NUMBER
 STEP_ID                                                        NUMBER
 SESSION_ID                                                     NUMBER
 SECURITY_GROUP_ID                                     NOT NULL NUMBER

every page that runs inserts a row into this table.  That is my audit trail.
Then, you just write SQL on it.

5) the web cache has the functionality built INTO it already.

6) as with everything in this world -- it depends on your conditions.  do you
have long think times?  then the ratio of USERS/CONNECTIONS is large.  do you
have very short think times?  then the ratio of users/connections gets smaller
-- goes towards 1

7) well, there are other OBVIOUS differences -- servlets = java, mod_plsql -
plsql.  servlets = run in middle tier, mod_plsql = run in database.

But yes, servlets = I consume resources on your app server even while I'm not
doing any work (while I'm looking at a page).   mod_plsql = I consume resources
on your server ONLY when I'm asking you to get me a page.

      Rating: 5
web
      testing  December 15, 2002
Reviewer:  mo

Tom:

1. as a followup to answer 4, have you ever explained on this web site or any
other book what values you insert into the audit trail and where get them? from
Are you basically  grabbing result of timing in seconds and saving it?  would
this be an overhead or it happens in milliseconds? DO i have to go to every
procedure/page and insert this or I can implement it in after-procedure-execute
trigger?

2. You say mod_plsql has a pool of connections available?  So 10 users may use
the same connection  (scott/tiger) which is defined in a DAD.  However let us
say you want to control the user acces by database which means each user has a
different login.  Then you have to login userA with a different connection that
a userB.  The pool would be meaningless since you do not know who is going to
login, and since each requires a different login?  This case mean you do not
specify parameters in the DAD.

3.  You say that using servlets in middle tier you always have a session between
browser and app server and session between app server and database?  My
understanding is HTTP is stateless protocol?  You can not maintain a session
between browser and app server?  AM I WRONG?

4.  do you use the debug package in the other book to show the status when
downloading the page? Would the other book be useful for intermediate level or
your book is enough?

5.  When you use beans, jSP or servlet in middle tier do you have to run oracle
in MTS mode? or that was only if they are in database?

Thank you,

          Followup:

1) i gave you the describe right above...

it is time in hsecs -- dbms_utility.get_time

overhead is something you can and should remove from your code.  I do not
believe this information, which I make a REQUIRMENT to be overhead.  It is like
calling "auditing" overhead -- it is not, you either have to audit or you don't.
 If you do, you do -- period.  It isn't overhead, it is a requirement.  Calling
it overhead would be like saying "isn't that overhead inserting into the ORDERS
table whenever someone wants to place an order?"

You have to put it in where YOU see fit.  Fortunately for me -- everything runs
from a procedure "F" -- very easy to audit.

2) the pool is not meaningless -- it will look for a connection you already own.

3) no, I didn't say that.  You need to read about servlets in order to
understand how they work.  HTTP is stateless.  A servlet maintains a state from
call to call and the app server hooks you back up with your servlet instance.
The servlet may or may not maintain a state with the database -- you decide that
when you program it.

4) "show the status when downloading the page" does not compute.  The Beginning
Oracle Progamming book is the "prequel" to Expert one on one and covers up
through 9i

5) I never personally use those technologies...  Generally speaking if the
middle tier is doing connection pooling -- you don't want to add yet another
connection pool (MTS).  That is like caching the cache -- overhead to be
removed.

      Rating: 5
web
      testng  December 21, 2002
Reviewer:  mo

Tom:

1.  Connection pooling means you have connections available at the app server t
oservice the next browser.  BUT, the browser user can have a different
userid/password than another user.  What I am saying is that how do you
implement connection pooling since you do not know who is connecting. do you
connect him and then disconnect/reconnect with his login.userid?

2. what I mean by the data inserted in your table for audit is what is component
type and compnent attribute.  how do i know what these are and where do i get
the values of these from?

3. do you know of any articles on problems with NEtscape 4.7 since you said it
has some issues with tables?
THank you,

          Followup:

1) it attempts to hook you back up with a connection that is good for you if at
all possible.

2) they are meaningless to you -- they are meaningful to me.  You audit what you
want/need to audit.  That's all

3) it is ancient history.  the issues are "speed", it is dog slow doing tables
in tables in tables.

      Rating:
      5
trigger  January 02, 2003
Reviewer:
      mo
Tom:

1. Is there a way in oracle to create a trigger after each page creation instead
of going to each procedure and insert statements for audit trail.  ie.
after-execute-of-any-procedure?

2. how do you get the value of the i.p address in oracle?

Thank you,

          Followup:

1) no
2) ip address of what
  sys_context('userenv','ip_address') for the client
  host_name from v$instance

      Rating:
      5
ip  January 02, 2003
Reviewer:  mo

Tom:

WHen I do:

SQL> select sys_context('userenv','ip_address') from dual;

I get totally different ip than

c:\WINDOWS\ipconfig

SHould not they be the same for the PC always?

          Followup:

select sys_context('usere....) returns the IP address of a client connected over
net8 -- where ever the client is -- that is there ip address.

it is not the ip address of the server

      Rating:
      5
tom  January 02, 2003
Reviewer:  mo

TOm:

1.  Did you mean to say that is not the ip address of the client PC in your
second statement?

2.  In my web audit trail I want to track the ip address of the client
pc/browser user logs from. can I do:

insert into audit_log(user_id,log_date,ip_address) values
  (p_user_id,sysdate,sys_context('userenv','ip_address');

or this would give me the ip address for the app server?

Thank you,

          Followup:

select sys_context('userenv', 'ip_address' ) from dual

returns the IP address of the "thing" that connected to the database.  In
MOD_PLSQL, that'll be the ip address of the app server.  In client server,
that'll be the ip address of the CLIENT's pc.

with mod plsql to get the clients (browser) address, use
owa_util.get_cgi_env('remote_addr');

2) use owa_util then.

      Rating: 5
Curious for
      elaboration  January 02, 2003
Reviewer:
      Scott  from Perth, Western Australia
G'day Tom

I'm curious about the sort of information you store in your activity table
(WWV_FLOW_ACTIVITY_LOG1).

Would you be able to elaborate on the sort of information you store in the
various fields in this table to gather the statistics you use?

I know it usage and structure of such an activity log table would vary from site
to site, but think of this as a sort of case study... I'm interested to know
more information.

Thanks

          Followup:

 TIME_STAMP                                                     DATE
 COMPONENT_TYPE                                                 VARCHAR2(255)
 COMPONENT_NAME                                                 VARCHAR2(255)
 COMPONENT_ATTRIBUTE                                            VARCHAR2(4000)
 INFORMATION                                                    VARCHAR2(4000)
 ELAP                                                           NUMBER
 NUM_ROWS                                                       NUMBER
 USERID                                                         VARCHAR2(255)
 IP_ADDRESS                                                     VARCHAR2(4000)
 USER_AGENT                                                     VARCHAR2(4000)
 FLOW_ID                                                        NUMBER
 STEP_ID                                                        NUMBER
 SESSION_ID                                                     NUMBER
 SECURITY_GROUP_ID                                     NOT NULL NUMBER

is the describe of the table.

A sample row might look like:

flows@OSI816> @printtbl8 'select * from WWV_FLOW_ACTIVITY_LOG1$ where rownum =
1'
TIME_STAMP                    : 27-dec-2002 12:30:39
COMPONENT_TYPE                :
COMPONENT_NAME                :
COMPONENT_ATTRIBUTE           : SEARCH (7)
INFORMATION                   :
ELAP                          : .26
NUM_ROWS                      : 11
USERID                        : PUBLIC_USER
IP_ADDRESS                    : xxx.yyy.31.130
USER_AGENT                    : Mozilla/4.0 (compatible; MSIE 5.01; Windows 98)
FLOW_ID                       : 4950
STEP_ID                       : 7
SESSION_ID                    : 1780458
SECURITY_GROUP_ID             : 0
-----------------

PL/SQL procedure successfully completed.

That shows the date/time of the acces.  component type/name are optional (I
don't use them -- I have a small application -- just 20 or so pages.  I might
use this if I were a 200 page application to further refine "what page" this is
-- you know, like module, action, client_info in v$session set via
dbms_application_info.

component attribute is the page -- this record was a search page.

information -- optional extra stuff
elap -- time in seconds to run the page
num_rows -- arbitrary number that means different things for different pages.
in this case, 11 was the estimated number of hits
userid -- the user associated with the page, as I don't make people log in,
always the same
client ip address -- self explanatory
user_agent -- the browser they used
flow-id = application identifier
step-id = application step
session-id= session id we assigned to this session
security group id = another thing I'm not using

But -- you should just keep whatever you WANT to keep. others might find the
REFERER to be important for example.

      Rating:
      5
ip  January 03, 2003
Reviewer:  mo

Tom:

1.
select sys_context('userenv', 'ip_address' ) from dua

and x:=owa_util.get_cgi_env('remote_addr');

both gave me the same ip address. I tried that too from two different
PCs/browsers.  I think it is the app server i.p. not browser? when I do
"ipconfig" on my pc I get a totally different number and this is what i am
trying to get in pl/sql.

2. Id printtbl8 something you wrote to print table record in for layout? it is
pretty neat. Is procedure available anywhere?

3.  you printed time with date.  I always had to use the to_char(date,'mm/dd/yy
hh24:mi:ss') to get that. Is this the same way?

          Followup:

remote addr gives you the client (browser) ip address.

do a web page

begin
   htp.p( sys_context('userenv','ip_address') );
   htp.br;
   htp.p( owa_util.get_cgi_env('remote_address' ) );
end;

unless you are RUNNING on the app server -- they will return different values.

2)
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1035431863958
3) my printtbl routine does an alter session to get the nls_date_format changed.

      Rating: 5
connection
      pooling  January 05, 2003
Reviewer:  Scott
       from Perth, Western Australia
G'day

Thanks for the info in regards to the activity logging.

As a developer new to mod sql, I am still coming to understand connection
pooling.

You said in your summary, your visitors hit an average of 6-7 pages.
I performed a quick test on our development site, and after I logged in, the
session ID being used changed on occasion, presumably because of the connection
pooling.

How is it you know how many pages a visitor hits if you are using connection
pooling?

Thanks.

          Followup:

I am not privy to the internal algorithm - not sure how many times they reuse a
connection before restarting it.  Sorry.

      Rating: 5
web page
      timing  January 14, 2003
Reviewer:  mo

Tom:

I inserted an audit record (dbms_utility.get_time) at the begining of each
pl/sql procedure and round ((dbms_utility.get_time-l_start)/100,2) at the end
that generate html reports. Results are as this:

 SQL> /

PAGE                 TIME                 CREATION_DATE
-------------------- -------------------- --------------------
Report 1             4.87 seconds.
Report 1             2.87 seconds.
Report 1             8.26 seconds.        14-jan-2003 11:21:20
Report 1             6.59 seconds.
Report 2             1.8 seconds.
Report 2             2.4 seconds.         14-jan-2003 11:22:12
Report 3             .48 seconds.         14-jan-2003 11:22:24
Report 4             25.9 seconds.
Report 4             24.21 seconds.       14-jan-2003 11:22:52
Report 4             24.78 seconds.

10 rows selected.

1.  Sometimes a page takes 8 seconds to appear while the pl/sql program logs in
4 seconds. How do you explain this?

2.  How do you I get different results for the same report sometimes run the
same way?

3.  How do you know if this is the best time you can get a query to perform? For
example, the 4th report is a pivot query that has about 8 calculated functions
per row. there is about 1000 records?  How would I know if I can get this to run
in 10 seconds or better or that this the best you can get it at? is it by trying
different things?

THanks

          Followup:

1) middle tier, network, browser latency.  the db is just a piece of the pie.

2) different user loads, more or less PIO's during one run then the other....

3) you would know because someone said "you have a requirement to run this page
this fast".  Else, you never know.

I have a theory -- I can make anything go 1-5% faster.  However, each 1% takes
10 times as long as the prior 1% and since it is 1% of a SMALLER number, it is
less and less worth it.

You have to look at something and say "I'm happy with its performance".  Look at
the LIO's (logical io's), cpu time and justify that they seem right given the
data.

      Rating:
      5
excellent  March 12, 2003
Reviewer:
      A reader
this was excellent and very helpful, especially the initial response.

      Rating: 5
web
      app  July 16, 2003
Reviewer:  mo
Tom:

1.  If I have a web app running on oracle 8i database. It is accessed from
browser using mod-plsql.  Is it possible to make a copy of this app and install
on a laptop for travel and modify the code on the laptop and then send it to the
server rather than spend a lot of time over a dial up connection.  If yes, what
do you need to install on the laptop? oracle 8i enterprise edition and anything
else?

2.  If I have an instance for a web application which will be a portal for all
apps in the future.  Now I need to add another small application.  Would it be
beneficial to have a separate instance or just add 10 tables to the current one.
Any disadvantages to that?

Thank you,

          Followup:

1) se will do, pe will do.  I do it all of the time.

2) an INSTANCE per machine, do NOT create a new database. stuff as much into one
as possible

      Rating: 5
Grab and
      Hurl  July 18, 2003
Reviewer:  ray
       from ottawa
Is your grab and hurl program available (public).

Would there be any chance I can borrow it.

thanks

          Followup:

No, i haven't made it public - there are lots of them out there tho, better then
grab and hurl (which are so rudimentary as to make 'supporting' them out of the
question)

      Rating: 5
oracle on
      laptop  July 26, 2003
Reviewer:  mo

Tom:

I installed 8i R 8.1.7 on a laptop with XP o/s and everything went smooth.

I did and export on the unix prompt and got a dump of the database anap.dmp. I
GZIP the file and copied it over to windows PC and emailed it to the laptop.  I
unzipped ion windows. When I tried to import the files i saw all kinds of
problems.

1.  I did
imp userid=u/p  full=y
if i want to check all the errors occused do i just add log=logfile.

2.  After I did that the tables were there.  A lot of proedure erred (dont know
why) but I realized I may need to create some database links since code
references those which means installing another database. Do I  have any other
options here for database links?

3.  Microsoft gave me a severe error message after the import.  IT has some
binary addresses? Do you recommend installing 8i on windows 98 or is this an XP
bug.

4.  Even though I managed to see a lot of procedures in database using SQL
Navigator, I could not call them using the DAD I created and accessed using

http://127.0.0.1/pls/anap/myprocedure
after I copy the code and create a text file and compile it using SQL plus then
I would see it. What do you think the reason for that?

5. Do you recommend me deleting everything out and re-installing oracle or just
re-importing again after creatingg link or other stuff? If yes how do i clean
everything the imp  did?

Thank you.

          Followup:

1) yes.

2) well, if you want the code that references the database links to become
valid, you better have the db links present?  what else can we do?

3) contact support, that shouldn't happen.

4) my car won't start..  I'm really bummed.  Any ideas?  really -- what else can
i say?

5) I personally recommend:

   o fdisk to repartition the entire laptop hard disk to remove windows
   o install linux

but that is me.  8i works on XP just fine in general.  to "clean" what the imp
did is either:

   o drop the user(s) that imp imported into
   o drop the database and recreate it using dbca if it was a full database
     export you were importing

      Rating: 5
web app
        July 26, 2003
Reviewer:  mo
Tom:

1.  The user I imported to is the default "system/manager". Can you drop this
and re-create it?

2.  DO you advise of creating a user account by loggin in as system/maanger and
then importing to that user in the future.

3.  How do i drop database using dbca? is this an oracle provided tool? ANy
hints.

4.  In order for the links to work I guess I have to create other databases? IS
there a workaround other than changing the code not to use links?

5.  The option of fidk and using linux is hard because it is company borrowed
laptop and not personal?

Thank you very much,

      Rating: 5
One instance
      versus multiple  September 05, 2003
Reviewer:
      mo
Tom:

1.  Can you tell me why technically you support this:
<an INSTANCE per machine, do NOT create a new database. stuff as much into one
as possible>.

2.  I have one server with three oracle instances/databases and I use database
links between them. Each instance is for a different application e.g
(inventory/financial).

Two applications will be migrated to another server.  Would this affect the
database links in any way or they would still work.

3.  Would you be in favor of merging all three applications into one
instance/database.  Do you know of any article that discusses
advantages/disadvantages of have multiple instances/databases for many
applications versus one instance/database for all applications.

Thank you,

          Followup:

1) it is easier, more managable, consumes less resources, gives me the ability
to actually TUNE something.

2) it will not affect the database links, other then they might be slower

3) if they were custom apps (you wrote them) YES, 100% YES, 110% yes even.

if they are cots apps -- stuff you buy, maybe not -- they have their specific
version/configuration setup issues that can preclude this.

There are no advantages to many instances on a single machine, unless you are
FORCED into it.

      Rating:
      5
instance  September 08, 2003
Reviewer:
      mo
Tom:

Thanks.

1.  Does oracle have any special tools for merging databases or you would just
use export from one database and import into the other?

2. Can you change an instance name or is it easier to create a new one and do
exp/imp?

          Followup:

1) there are many methods

o streams
o replication
o sqlplus copy
o dump and load (sqlldr)
o database links and sql
o export and import

to name a few.

2) the instance name is just a sid really in single instance oracle.  very easy
to change.

just export a new sid (i'm talking unix, windoze -- read the windows admin guide
to figure out how to create a new service and so on with oradim)...

rename your init.ora
mv your $ORACLE_HOME/admin/$ORACLE_SID directory to the new sid

and pretty much start up.

      Rating:
      5
9iAS  November 23, 2003
Reviewer:
      mo
Tom:

I started using 9iAS with 9i database and foiund the following:

1.  app server does not display error messages (just 404 error) or parameters
submitted if you have an error on the next page displayed like 8iAS.  How do you
configure 9iAS to display parameters and error messages?

2.  I created two procedures and had one page submitting to the other.  When I
submit the first one I keep getting the "page not found" message even though the
procedure is there.    Do you know what could be happening here?

Thank you,

          Followup:

by default, mod_plsql will return 404 not found when there is an error -- rather
then displaying tons of information that your typical end user should NOT see
(it is a security thing here, to supress the messages like that)

check out support note:

Article-ID:         <Note:179530.1>
Alias:              SOL:OSS147396
Circulation:        PUBLISHED (EXTERNAL)
Folder:             internet.Applicationserver.HTTPserver
Topic:              PL/SQL Gateway (mod_plsql)
Title:              How to Get More Diagnostic Information From ModPLSQL In
                    Case of HTTP-errors

in short.....

The reporting mode from the PL/SQL gateway is defined by the Error_Style
directive in the configuration file wdbsvr.app.

This parameter can be specified at the DAD level or at the Global level and
must be changed by manually editing the file. This parameter accepts the
following values:

1. error_style = WebServer (default for mod_plsql)

    PL/SQL Gateway indicates to the Web Server what HTTP error was encountered.
    The Web Server then generates the error page. This can be used with
    Apache's ErrorDocument directive to produce customized error messages.

2. error_style  = Gateway

    The PL/SQL Gateway generates the error pages, usually a short message
    indicating the PL/SQL error that was encountered (e.g. "scott.foo
    PROCEDURE NOT FOUND").

3. error_style = GatewayDebug

    The PL/SQL Gateway generates information specified by the "Gateway&
quot;
    setting and also produces server configuration information. This mode is
    for debugging purposes only. Do not use in a production system since
    displaying internal server variables could produce a security risk.

Note: If the PL/SQL Application generates its own error page, then the
error_style setting is ignored and the page generated by the application is
displayed.

To customise the error page returned with the setting of WebServer,
edit the plsql.conf file located in the 9iAS ORACLE_HOME/Apache/modplsql/cfg
directory and add the following declaration between the <LOCATION /pls>
and </LOCATION> tags:

      Rating: 4
9iAS
      Errorrs  November 24, 2003
Reviewer:  mo

Tom:

DBA did this and he thinks wdbsvr.app was part of Oracle 9iAS Release 1.  It did
not affect anything.

1.  Is there a dfferent file for 9iR2?

2.  The article you posted seems to be cut off.  How do I get to this article in
support? Is there a web site for this?

Thank you,

          Followup:

metalink.oracle.com

      Rating: 5
9iAS
      Errors  November 24, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Munzer,

1 - the file is called dads.conf under 9iAS R2 and is located at
oracle_home\Apache\modplsql\conf\dads.conf

The parameter error_style is now called PlsqlErrorStyle.  Valid values are:
ApacheStyle, ModplsqlStyle, and DebugStyle.

2 - Login to MetaLink, click the advanced button, enter the note id from above
(179530.1) in the text box field, click the 'Doc ID' radio button, then click
the 'Search' button.

HTH,

Mark

November 24, 2003 Damon -- Thanks for the question regarding
      "mutating table", version 8.1.7

      You Asked
Hi Tom,

I have what is probably a trivial question but I'm stumped.

I have two tables.

create table myParent (
    id   number(10) constraint myparent_pk primary key,
    desc varchar2(50));

create table myChild (
    id   number(10) constraint mychild_pk primary key,
    myparentid number(10),
    effective_date date not null,
    end_date;

alter table mychild add constraint myparent_fk foreign key (myparentid)
references myparent(id);

As an example, the mychild table might look like this at a given point in time:

id    myparentid    effective_date    end_date
1     1             sysdate           null                      <-- date window
open
2     1             sysdate - 1       sysdate
3     1             sysdate - 2       sysdate - 1

And when we
      insert into mychild values (4,1,sysdate+1,null);

mychild will now look like this

id    myparentid    effective_date    end_date
1     1             sysdate           sysdate + 1   <-- date window is now
closed
2     1             sysdate - 1       sysdate
3     1             sysdate - 2       sysdate - 1
4     1             sysdate + 1       null             <-- inserted row - date
window open

When a user inserts a new row, any row

     where end_date is null and :new.effective_date > effective_date

must

     set end_date = :new.effective_date

So that we only ever have one open date window -- only one end_date is null for
any given myparentid.

Since I need to modify a table that is in an inconsistent state I run into the
mutating table error.

I have one question:  am I correct about the mutating table error (i.e., is
there a simple way around the problem that I am overlooking).

I'm looking for help in design/theory more so than code examples.  I have a lot
of cases where I have to implement the above concept within our system and if I
am incorrect about the mutating tables then things might be simplified.

Hope this is a little clearer.  I can only imagine how many questions you get
where your first response is, "What are you talking about?"  ;)

Thanks so much for your prompt response and help.
Damon Torgerson

      and we said...
too bad you are not using software written this century, this functionality is
builtin to the database already -- could save tons of keystrokes:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4529781014729
but anyway...  Here is what I would suggest I suppose:

ops$tkyte@ORA920> create table myParent
  2  ( id   number(10) constraint myparent_pk primary key,
  3    descript varchar2(50)
  4  );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table myChild
  2  ( id             number(10) constraint mychild_pk primary key,
  3    myparentid        number(10) references myParent,
  4    effective_date date not null
  5  );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace view myChild_view
  2  as
  3  select id,
  4         myparentid,
  5         effective_date,
  6         lead(effective_date) over (partition by myparentid order by
effective_date) end_date
  7    from myChild
  8  /

View created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into myParent values ( 1, 'Hello' );

1 row created.

ops$tkyte@ORA920> insert into myChild  values ( 1, 1, sysdate-5 );

1 row created.

ops$tkyte@ORA920> insert into myChild  values ( 2, 1, sysdate-4 );

1 row created.

ops$tkyte@ORA920> insert into myChild  values ( 3, 1, sysdate-3 );

1 row created.

ops$tkyte@ORA920> insert into myChild  values ( 4, 1, sysdate-2 );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from myChild_view
  2  /

        ID MYPARENTID EFFECTIVE END_DATE
---------- ---------- --------- ---------
         1          1 17-JAN-03 18-JAN-03
         2          1 18-JAN-03 19-JAN-03
         3          1 19-JAN-03 20-JAN-03
         4          1 20-JAN-03

No muss, no fuss...  No triggers -- no denormalized, repetitive data.  For the
same reason you would never store AGE in a table -- but rather DATE_OF_BIRTH,
end_date should not be stored here.

Review &
      Followup

      Rating:
      4
excellent  January 22, 2003
Reviewer:
      Damon Torgerson  from Victoria, BC Canada
Hi Tom,

Thank you so much for your answer.  I did not know about the "lead" function and
I really appreciate the simplicity.  I did manage to get it working with
triggers (4) so at the very least I learned a great deal about mutating tables.

Thank you very much,
Damon Torgerson

          Followup:

I'll betcha there are some multi-user issues with your triggers -- careful with
that.

      Rating: 4
quick
      followup  January 22, 2003
Reviewer:  Damon
      Torgerson  from Victoria, BC Canada
I cannot update the view directly

ORA-01732: data manipulation operation not legal on this view

My java developers will revolt if they have to work with two different
structures (one for writing one for reading).  Would you suggest using Instead
Of triggers on the view?

Thanks,
Damon Torgerson

          Followup:

This is one way -- I would benchmark the approaches under various conditions to
see which works best for you:

ops$tkyte@ORA920> create or replace trigger myChild_trigger
  2  instead of insert or update or delete on myChild_view
  3  begin
  4          if ( inserting )
  5          then
  6                  insert into myChild (id, myparentid, effective_date )
  7                  values (:new.id, :new.myparentid, :new.effective_date );
  8          elsif (updating)
  9          then
 10                  update myChild
 11                     set myparentid = :new.myparentid, effective_date =
:new.effective_date
 12                   where id = :new.id;
 13          else
 14                  delete from myChild where id = :old.id;
 15          end if;
 16  end;
 17  /

Trigger created.

ops$tkyte@ORA920> select * from myChild_view;

        ID MYPARENTID EFFECTIVE END_DATE
---------- ---------- --------- ---------
         1          1 17-JAN-03 18-JAN-03
         2          1 18-JAN-03 19-JAN-03
         3          1 19-JAN-03 20-JAN-03
         4          1 20-JAN-03

ops$tkyte@ORA920> update myChild_view set effective_date = sysdate where id = 4;
1 row updated.

ops$tkyte@ORA920> select * from mychild_view;

        ID MYPARENTID EFFECTIVE END_DATE
---------- ---------- --------- ---------
         1          1 17-JAN-03 18-JAN-03
         2          1 18-JAN-03 19-JAN-03
         3          1 19-JAN-03 22-JAN-03
         4          1 22-JAN-03

ops$tkyte@ORA920>

      Rating: 5
How do you find
      the time?  January 22, 2003
Reviewer:  Damon
      Torgerson  from Victoria, BC Canada
Tom,

As you can probably tell I'm fairly new to database development and I want to
thank you for your help and patience with me.  Your book is an excellent
reference which I use almost everyday.

You said, "...betcha there are some multi-user issues with your triggers..."

When implementing my triggers I followed your example on mutating triggers
extremely closely.  Where might I look for multi-user issues?  Is it possible
for multiple users to change the package variable (pl/sql table) that stores the
ROWIDs for the changing rows?  I thought that each  session got their own
"instance" of a package variable.

Rather than wasting your time with these theory related questions perhaps you
could recommend a good reference on pl/sql / Oracle (Expert One-on-One II?)

Thanks again,
Damon Torgerson

          Followup:

It is tricky -- I don't have a page to reference you -- but look in your after
trigger, think about the effects of multi-versioning and read consistency and
see if two users doing something similar at about the same time might not bump
into eachother.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7144179386439
for some examples...

      Rating:
      5
Indexes?  November 24, 2003
Reviewer:
      vll  from usa
Hi, Tom!
Honestly, for me there is one rule that I always follow: first check Tom's
archive for ANY question!!! Thanks!

For your example - I played with it a little bit and it looks like indexes are
not used when querying view like select * from mychild_view t where
myparentid=2. I have an index on myparentid. Am I missing something?
Thanks a lot!!!

          Followup:

you'll want to skip the view -- analytics do not facilitate predicate pushing
nicely (they cannot, it can change the meaning of the query itself if it did)

      Rating:
      5
indexes?  November 24, 2003
Reviewer:
      vll
Tom,
but as I understood the whole idea of this view was to provide an easy way of
getting the effective values for the specified date. (OK, I understand that the
idea of the view was to show how to implement some solution for the question
that was originally asked, but anyway). If you have this view you can easily
query like: select * from mychild_view t where myparentid=2 and {date
specified}>=EFFECTIVE and {date specified}<=END_DATE (or something like this).
In general, what is in your opinion the best way of implementing some solution
for getting it? I agree completely that you don't have to store END_DATE in the
table, but is there effective way of querying such a table having parentid and
effective date as a parameters? I was absolutely happy when I saw your solution,
but if indexes cannot be used...
Thank you very much!

          Followup:

let me revise that -- you probably tested with too little data:

ops$tkyte@ORA920> create or replace view myChild_view
  2  as
  3  select id,
  4         myparentid,
  5         effective_date,
  6         lead(effective_date) over (partition by myparentid order by
effective_date) end_date
  7    from myChild
  8  /

View created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_stats.set_table_stats( user, 'MYCHILD', numrows =>
1000000, numblks => 100000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select * from mychild_view where myparentid = 2;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=10000 Bytes=440000)
   1    0   VIEW OF 'MYCHILD_VIEW' (Cost=6 Card=10000 Bytes=440000)
   2    1     WINDOW (SORT) (Cost=6 Card=10000 Bytes=350000)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'MYCHILD' (Cost=3 Card=10000
Bytes=350000)
   4    3         INDEX (RANGE SCAN) OF 'MYCHILD_IDX' (NON-UNIQUE) (Cost=2
Card=4000)

ops$tkyte@ORA920> set autotrace off

it would be predicates against ANY OTHER COLUMN (other then what is partioned
by) that would not be 'pushable' as that would change the answer.

      Rating:
      5
OOPS!  November 24, 2003
Reviewer:
      vll
Sorry,
didn't understand that you were talking about view itself - query is using
indexes, never mind!

November 24, 2003 Steven -- Thanks for the question regarding "RAID
      configuration", version Oracle 8.0.4.1

      You Asked
We are looking to buy a new database server. What is the best
RAID configuration for databases and why? The current server we
have has a RAID configuration of RAID 5.

      and we said...
depends on the database, depends on the part of the database.
Depends also on who you ask ;)

Here is what I like (raid 0 = stripes, raid 1 = mirrors, raid 5
= striping+parity):

o no raid, raid 0 or raid 0+1 for online redo logs AND
control files.  You should still let us multiplex them
ourselves even if you mirror them.  We have more
opportunities for failure if the raid subsystem reports a
"warning" back to us -- if we have multiplexed them -- we are OK
with that.

o no raid or raid 0 for temporary datafiles (used with
temporary tablespaces).  no raid/raid 0 is sufficient.  If you
lose these, who cares?  You want speed on these, not
reliability.  If a disk fails, drop and recreate temp elsewhere.

o no raid, raid 0 or raid 0+1 for archive.  Again, let us
multiplex if you use no raid or raid 0, let the OS do it
(different from online redo log here) if you use 0+1.

o raid 0+1 for rollback.  It get written to lots.  It is
important to have protected.  We cannot multiplex them so let
the OS do it.  Use this for datafiles you believe will be
HEAVILY written.  Bear in mind, we buffer writes to datafiles,
they happen in the background so the poor write performance of
raid 5 is usually OK except for the heavily written files (such
as rollback).

o raid 5 (unless you can do raid 0+1 for all of course) for
datafiles that experience what you determine to be "medium" or
"moderate" write activity.  Since this happens in the background
typcially (not with direct path loads and such) -- raid 5 can
typically be safely used with these.  As these files represent
the BULK of your database and the above represent the smaller
part -- you achieve most of the cost saving without impacting
performance too much.

Try to dedicate specific devices to

o online redo
o archive
o temp

they should not have to share their devices with others in a
"perfect" world (even with eachother).
Review &
      Followup

      Rating: 5
Best
      arcticle!!!!  April 27, 2001
Reviewer:  Den
       from Russia
Best arcticle!!!!

      Rating: 4
raid and file
      layouts  October 10, 2001
Reviewer:  mike
       from Atlanta
I am not very sure about the relationship of raid and the file layouts.
For example there are 10 disks and the 1+0 raid adopted, do we still need to
consider such as put rodo files, rollcack file in the disk x, y? I think 0 means
striping on all the disks, we really can not tell the files in which perticalar
disks. They are over the disks.

          Followup:

You set up the relationships, You configure the raid sets.  Striping can be
across 2 devices, it can be across N devices.  You decide, you configure.  It is
totally 100% up to you.

So, 0 does not mean striped across all, it just means striped across some.

      Rating:
      4
Reader  October 10, 2001
Reviewer:
      Reader  from USA
What is the stripe width would you recommend in terms
of db_block_size and db_file_multiblock_read_count.
Does db_block_size or (db_block_size X db_file_multiblock_read_count)
preferrable

Is it not preferrable to use their own device for rollback
segments

Thanks

      Rating:
      4
  October 10, 2001
Reviewer:  mike
       from atlanta
My concern was if really needs to consider the files distribution as the 0+1
adopted, the files well striped over disks allraedy?

          Followup:

does not syntatic sense make ;)

I don't understand.

      Rating:
      3
  October 10, 2001
Reviewer:  mike
       from atlanta
Assume there were 10 disks, someone wanted to distribute files as follow, I
would say that it does not matter because the files stripped over 10 disks if
0+1 used. One disk could contain the  portions of all the files. Could you clear
on this?

disk 1 Oracle Software
2 SYSTEM tablespace, control file 1
3 RBS tablespace, RBS_2 tablespace, control file 2
4 DATA tablespace, control file 3
5 INDEX tablespace
6 TEMP tablespace, DATA_2 tablespace
7 TOOLS tablespace, INDEX_2 tablespace
8 Online redo logs 1, 2 and 3, Export dump destination
9 Application software, archived redo logs destination

          Followup:

If there were 10 disks and you used raid 1, you would really have 5 disks
(pairs).  Insufficient to really be picky about anything.

So, say it's really 20 disks so you have 10 mirrored pairs.

If you wanted the degree of control you have above -- you would not use
striping, you would just mirror.

You could strip perhaps the pairs 2, 3, 4, 5, and 7 and just spread the io for
those files out.

You don't have to stripe ALL of them.

      Rating:
      3
raid  April 29, 2002
Reviewer:  Mahbuba
      Rahman  from Sharjah,UAE
Would you please explain what are the differences between Stripping and
Mirroring and the advantages of each one over the other .

          Followup:

Search on the internet. Tons of material on this.

Striping -- good for performance.  No availability features.  Achieves even IO
across many devices.

Mirroring -- good for availability.  You have to lose both (or three or
whatever) disks before you lose data.

      Rating: 4
multiplexing
      on-line redo  April 30, 2002
Reviewer:
      harish  from uk
Some people suggest thet on-line redo should be multiplexed using OS facilities
( ie mirror ) and not Oracle. There appers to be conflicting views on this
subject.. Are you able to clarify why you recommend Oracle based multiplexing ?

          Followup:

Some OS's return a "warning" when one of the mirrors cannot be written to.  This
could cause us to think "error".

Also, if a mirror goes down, you are unprotected at that point.

      Rating:
      4
RAID  April 30, 2002
Reviewer:  Shajan
      Joseph  from Chennai, India
It is to be decided on the basis of the following parameters namely,
Throughput/Response Time (Performance),Scalability & Backup strategy (which
determines database availability)

          Followup:

what is to be decided?  Nice statement, no context.

      Rating: 5
This might
      clear up some things.  April 30, 2002
Reviewer:
      Michael S. Durham  from Lexington, KY
http://technet.oracle.com/deploy/availability/pdf/oow2000_sane.pdf
I have switched to the methods outlined in this document and I must say that my
life is now much easer.  It should answer many of the questions people are
asking about RAID for an Oracle database.

Regards,
Mike

      Rating: 4
raid (0+1) or
      (1+0)  April 30, 2002
Reviewer:  Phil
      Williams  from Palo ALto ,CA USA
I am not sure if I was dreaming (or just drunk) but I heard that there was a
diff in performace between ROAID 0+1 compared with RAID 1+0. Does anyone know if
this is true or not and if so is there any data to back it up.

Cheers

Phil

          Followup:

There shouldn't be a perf difference - but an availability difference exists.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2403901570002

      Rating: 4
A
      reader  May 10, 2002
Reviewer:  ak
       from India
Hi,
  1)Regarding Raid 5 ,minimum three disk,how many is the
   maximum here,Keeping good performance in mind of a
   terabytes database and heavy oltp system.

   2)All the Raid should have diff. controller,keeping
   good performance ,controller corrupting the datafiles
   in mind??

   3)How about channel here ,keeping performance in mind.
     Is there any effect of single channel handling
      everything ??

Your comments on above 3 points please .
Thanks

Regards
  Ak

      Rating:
      5
  August 12, 2002
Reviewer:
      A reader
You said:
<quote>
o no raid, raid 0 or raid 0+1 for online redo logs AND control files.  You
should still let us multiplex them ourselves even if you mirror them.  We have
more opportunities for failure if the raid subsystem reports a
"warning" back to us -- if we have multiplexed them -- we are OK with that.

o no raid, raid 0 or raid 0+1 for archive.  Again, let us multiplex if you use
no raid or raid 0, let the OS do it (different from online redo log here) if you
use 0+1.
<quote>

How can we do in order to let Oracle multiplex the online redo log , control
files and archive logs? CAn you tell how to do this, which command should I use?

Thanks

          Followup:

see the alter database command for how to create a log group with more then one
member (add some members to the redo log groups)

Just copy the controlfiles across many (2 or 3) mount points on different
physical devices and use the controlfiles= init.ora parameter to tell us to
maintain them (whilst the data is shutdown)

use the log_archive_dest_1                   string
log_archive_dest_10                  string
log_archive_dest_2                   string
log_archive_dest_3                   string
log_archive_dest_4                   string
log_archive_dest_5                   string
log_archive_dest_6                   string
log_archive_dest_7                   string
log_archive_dest_8                   string
log_archive_dest_9                   string

init.ora parameters to tell us where to put more then one copy of archive.

      Rating: 5
..about online
      redolog files  August 13, 2002
Reviewer:
      Yogeeraj  from Mauritius
Hello,

from above:
<quote>
Try to dedicate specific devices to

o online redo
o archive
o temp

they should not have to share their devices with others in a "perfect" world
(even with eachother).
</quote>

..and from "OPTIMAL STORAGE CONFIGURATION MADE EASY" by Juan Loaiza, Oracle
Corporation
<quote>
...
LOG FILES
It is generally more efficient and flexible to parallelise IO operations using
parallel execution at the Oracle level than using small stripe widths at the
storage level. However, online log file writes cannot be parallelised at the
Oracle level. They must be parallelised at the storage system level. If an
online log file is located on a single disk, then operations that make changes
very rapidly such as parallel updates, parallel index creations, parallel loads,
etc. may become bottlenecked on the log disk. Therefore the online log file
should be spread across multiple disks using striping.
...
Striping all files across all disks ensures that the full bandwidth of all the
disk drives is available for any operation. Therefore, parallel execution and
other IO intensive operations will not be unnecessarily bottlenecked because of
the disk configuration. You cannot reconfigure your storage without a great deal
of effort, so striping across all disks is the safest and most future-proof
choice you can make.
...
</quote>

According to this document, it is better to stripe datafiles across the multiple
disks!

In my case, i have an external storage area with 12 disks (excluding 2 for
hotspare), i am thinking about mirroring 6 disks over the rest of 6 disks and
striping my datafiles across them.

Also, at the Oracle level, i will be multiplexing the files forming part of my
recovery set (online logs, control file, and archive logs) so that one plex is
found in my internal disks.

Is that ok? Did i miss something?

Please guide.

Best Regards
Yogeeraj

          Followup:

with just 6 disks at the end of the day -- you do not have much opportunity for
separating stuff out at all.  You'll have to just stripe away and hope for the
best.  You might consider setting 2 of the mirrored disks aside just for log if
you are really log intensive.

      Rating:
      5
  August 14, 2002
Reviewer:
      A reader
Hi Tom,

I am confuse on how many disks are needed at least to implement raid 1+0 or 0+1.
Imaging that you have 30 disks (including one hotspare) and you want to
implement raid 1+0, and imaging that you want a strip width of 4. Can
configurate raid 1+0 over these disks or not?.
If understood well I need at least 4 drives in order to implement raid 1+0, so
if I have 30 disk (one hotspare) and I want a strip width of 4 I will not able
to do it because I need two more disks that I don't have.

Can you help me with this

Thanks in advance.

          Followup:

I'll recommend you talk to your hardware supplier.  I don't really do hardware
stuff personally.

I usually go here:

http://www.acnc.com/04_01_00.html
for a good overview.  You need a minimum of 4 drives and you have 30 so I don't
see the problem?

      Rating:
      5
  August 14, 2002
Reviewer:
      A reader
Hi Tom,
I understood what your told me, but my doubt (after reading www.acnc.com/04 01
00.html) is the following:
If I understood well the idea of raid 10 or 01, I need at least 4 drives to
implement raid 10 or 01, so if I have 30 disk, actually 29 (one is a hotspare)
and I want to have a stripe width of 4 (this means a deep of 4 disk by group ),
so each group will need to have 16 disks by group (8 for mirroring and 8 for
stripping). Am I wrong?

Thanks for any help you can give me.

          Followup:

Sorry -- I just don't do this stuff, I'm out of my league.  Not my cup o' tea if
you know what I mean.

Maybe someone out there reading this knows, but your best bet is to talk to your
Veritas technical Sales consultant.  They should know this stuff like I know the
database.

      Rating: 5
WRT S.A.M.E.
      methodology  August 21, 2002
Reviewer:  Graham
      Halsey  from UK
Hi Tom,

As Michael S. Durham mentions in this strand, the SAME document is a v. useful
reference (yes, my keyboard is broken).

One of the recommendations in there is to place frequently accessed data on the
outside half of the disks. I understand the reasoning behind this, but don't
actually know how it's done. Is this purely an OS thing, or is there anything I
can do in Oracle to control which tracks on the disk are used when creating
datafiles etc...?

Sorry to ask you to comment on someone else's material!

Regards,
Graham.

          Followup:

I believe you would be creating physical disk partitions, which you control the
phyiscal location of, and using them where appropriate.  So, it is an "OS"
thing.

      Rating: 5
Watch how your
      controlers are set up.  August 21, 2002
Reviewer:
      Chuck Jolley  from Oklahoma City, OK USA
The biggest performance increase we have seen tuning this stuff is getting the
data and indexes on different controlers, regardless of how the raids are set
up.
We literaly doubled the performance of one of our databases simply by getting
another controler into it's server to drive the data and index arrays
separately.
This is on a Compac/Win2k server, so it might not be an issue in other setups.

          Followup:

Umm, probably more likely you finally accidently achieved "even IO
distribution".

You see, for a given single query -- we access the index AND THEN the data
sequentially -- not in parallel.  So, one controller, 15 controllers -- it would
not matter for the query in isolation.

Getting an even IO spread across controllers to scale up lots of users -- thats
key.  Check your iostats and you'll see that what you've done is just spread it
out.

      Rating: 5
Helped
      multi-user and...  August 21, 2002
Reviewer:
      Chuck Jolley  from Oklahoma City, OK USA
Tom,
It also greatly helped a large nightly summary we create that inserts about
1,200 rows into a report table using a structure like:

insert into report_table(col1, col2,....)
select report_name, sysdate, sum(bla1), sum(bla2),...
from 400_lines_of_sql;

Does this make sense?

Thanks,
Chuck

PS It is entirly possible that he HW people did something else like putting back
"borrowed" memory without telling us too. :)

          Followup:

We would have to see a statspack and tkprof from before and after but... another
thought just came to mind:

in order to separate, you had to rebuild.  You might have been removing tons of
dead whitespace that accumulated.  A simple rebuild of the objects may well have
delivered the same effect.

But this does point out why it is good to use statspack (we would have the
history to compare to) and to occasionally tkprof an application or two.
Otherwise, we have nothing to compare to (other then our "memories") and can
only sit around and speak hypothetically.

;)   we'll never really know in this case (unless you are willing of course to
unseparate the data again !)

      Rating: 4
Data Loading
      Slow in RIAD 5  May 30, 2003
Reviewer:  Tony
       from INDIA
Our company uses RAID 5 DB for our Decision Support System. There is no
performance problem  reported by the application users as we have RAID 5 that is
good for DSS. But the problem is with the data loading. As the size of the data
to be loaded  every night increases day by day, the loading time also increases
day by day. Now it takes 7 to 8 hrs for data loading. We already enabled
parallel processing, nologging ..etc. Our investigation shows lots of I/O waits
during the loading. As we know, RAID 5 is not good for write intensive data
loading. I think we can not have one RAID system for data loading and another
for data access by the DSS application. What is the solution for this problem
Tom?.

          Followup:

don't use raid 5?

are your IO waits for write or read -- if you do lots of direct path options,
they'll be waits for write and the only think you can do is spread the work out
over as many logical disks as possible and just nail the system.

sorry, without in depth knowledge of your system and processes, I'm not going to
be able to suggest something.  Not knowing your processing needs makes it hard
(NOT that you can enter all of that information here, please don't)

      Rating:
      5
  August 24, 2003
Reviewer:  Reader

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/iodesign.htm#PFGRF015
"On low-concurrency (sequential) systems, ensure that no single I/O visits the
same disk twice. For example, assume that the stripe width is four disks, and
the stripe depth is 32k. If a single 1MB I/O request (for example, for a full
table scan) is issued by an Oracle server process, then each disk in the stripe
must perform four I/Os to return the requested data. To avoid this situation,
the size of the average I/O should be smaller than the stripe width multiplied
by the stripe depth. If this is not the case, then a single I/O request made by
Oracle to the operating system results in multiple physical I/O requests to the
same disk."

Tom, how did they come up with "each disk in the stripe must perform four I/O."
Thanks.

          Followup:

i don't know, not really a hardware/disk guy myself.  I don't do that stuff.

      Rating:
      4
Thanks  August 26, 2003
Reviewer:
      Anthony Reddy  from KL, Malaysia.

      Rating: 4
Partition in
      RAID  August 26, 2003
Reviewer:  Tony
       from India
Its essential, if I'm not wrong, to store data of a table in different disk so
that table partition and parallel operation will be more effective to increase
the performance. Do we have to follow this for RAID systems also? (Data are
already stripped/mirrored in RAID systems)

      Rating: 5
Raid
      configuration  September 14, 2003
Reviewer:
      A reader  from orlando,FL
"
no raid or raid 0 for temporary datafiles (used with temporary
tablespaces).  no raid/raid 0 is sufficient.  If you lose these, who cares?  You

want speed on these, not reliability.  If a disk fails, drop and recreate temp
elsewhere"

Tom,
Reviewing your quote above:
From a point of view of *good practices* would you recommend putting the temp
files also in the 1+0 area ( in a raid 1+0 shop ). Because this helps from the
point of availability. Agreed data is is safe but somebody has to re-create the
temp files which causes  delay for the application functioning.  If the
organization can spend the big bucks on the raid 1+0 setup, saving( if at all)
on non-mirroring of the temp files exposes it as the weakest link when murphy's
law asserts itself.

Thanks as always...

          Followup:

depends on what you are using the raid for -- yes, it has been pointed out that
protecting temp can increase availability (not on this specific page, but i've
had this discussion)

temp can be lost, and losing temp will not lose any data - only time.

so, if the goal is "no data loss", the above is good.

if the goal is "reduce downtime", protect temp.  consider what happens when the
disk TEMP is on fails 5 hours into a 5 hour and 1 minute process otherwise!

      Rating: 4
Is your RAID
      recomandation valid for ASM also?  September 15, 2003

Reviewer:  Tony  from India
I think Oracle 10g's Automatic Storage Management internally usese SAME (Strip
All Mirror Everything) concept.
If I'm correct, what is the relevance of the above RAID recomandation for ASM
implemented Oracle 10g.?
Can you please clarify?

          Followup:

it is all about implementing SAME, yes.

The above recommendation is for where you can use the various types somewhat
safely.  remember i said "raid 5 (unless you can do raid 0+1 for all of
course)"!!

      Rating: 5
Distribute
      IO  November 24, 2003
Reviewer:
      A reader
Hi Tom,

Say if my disks are already RAID 1+0. Do you think it's still necessary to
distribute all indexes into several INDEX tablespaces? Or just use one INDEX
tablespace?

          Followup:

use tablespaces for "book-keeping" and "administrative ease of use".

I do this - i have a tablespace per application, everything into the same bin.
Sometimes two tablespaces (or more) depending on size -- but in general, its all
in one bucket.

      Rating: 5
OS or Manual
      Striping  November 24, 2003
Reviewer:
      A reader
Hi Tom,

My general impression is that OS/Hardware striping is more beneficiary than
manual striping (e.g. assign different TS to each table partition). Is this
correct?

Thanks.

          Followup:

I like it better because I don't have to think about it.  It has been years
since I've worried about things like that.  Just let the hardware do it.

All you have to do then is make sure you use the raid sets effectively
(spreading the load out over all of them)

November 24, 2003 -- Thanks for the question regarding "Avoiding
      overlap values...", version 8.0

      You Asked
Hai Mr Tam,

U said U have a 'trick' for the following problem.  It couldbe
nice if u tell me that..thanks.

A form(5.0) with tabular style is displayed like below..

to  from    discount
--   ---    -----
10   40     1.5
50   65     2.5
70   90     1.2
.    .
.    .
60   99             ----> should not be allowed.
65   80             ----> should not be allowed. Is there a way
.    .
.    .

But I would like to stop OVERLAPPING range like above shown with

arrow marks. How can I do it.

Thanks once again

rgs
priya

      and we said...
Here it is.  We use dbms_lock to serialize access to the table
(necessary for a multi-user environment to avoid overlaps by 2
people concurrently in your case above -- IF there are other
columns in the table, we might be able to serialize on a
much lower level of granularity).  We also must defer checking
the rule until AFTER the insert has taken place.  In order to do
that, we follow the logic we would for a mutating table (see

http://osi.oracle.com/~tkyte/Mutate/index.html
for details on that).

The AFTER INSERT trigger does all of the work -- you'll have to
tune that query for your environment.  It catches all 4 cases we
need to worry about with overlaps.  There are other ways to code
that query, depending on your indexes and such you might explore
the others for performance reasons.  This should be enough to
show you the technique however:

ops$tkyte@DEV8I.WORLD> create table discount_info
  2  (xto number(2), frm number(2) );

Table created.

ops$tkyte@DEV8I.WORLD> create or replace package state_pkg
  2  as
  3      type myArray is table of rowid
  4            index by binary_integer;
  5
  6     newones  myArray;
  7     empty    myArray;
  8
  9  end;
 10  /

Package created.

ops$tkyte@DEV8I.WORLD> create or replace trigger
discount_info_bi
  2  before insert on discount_info
  3  begin
  4      state_pkg.newones := state_pkg.empty;
  5  end;
  6  /

Trigger created.

ops$tkyte@DEV8I.WORLD> create or replace trigger
discount_info_aifer
  2  after insert on discount_info for each row
  3  begin
  4      state_pkg.newones(state_pkg.newones.count+1) :=
  5           :new.rowid;
  6  end;
  7  /

Trigger created.

REM the owner of this trigger MUST have EXECUTE on
REM DBMS_LOCK granted to them (not a role) by SYS!!
REM the choice of 123 was totally arbitrary here in the
REM dbms_lock call....

ops$tkyte@DEV8I.WORLD>
     create or replace trigger discount_info_ai
  2  after insert on discount_info
  3  declare
  4      status int;
  5      l_rec  discount_info%rowtype;
  6      l_cnt  number;
  7  begin
  8      status := dbms_lock.request
  9               ( id                => 123,
 10                 lockmode          => dbms_lock.x_mode,
 11                 release_on_commit => TRUE );
 12
 13      for i in 1 .. state_pkg.newones.count
 14      loop
 15          select * into l_rec
 16            from discount_info
 17           where rowid = state_pkg.newones(i);
 18
 19          select count(*)
 20            into l_cnt
 21            from discount_info
 22           where rowid <> state_pkg.newones(i)
 23             and ( xto between l_rec.xto and l_rec.frm
 24                OR frm between l_rec.xto and l_rec.frm
 25                OR ( xto < l_rec.xto and l_rec.xto < frm ) );
 26
 27          if ( l_cnt > 0 ) then
 28              raise_application_error( -20001,
 29                'overlap -- ' || l_rec.xto || ', ' ||
 30                l_rec.frm || ' rejected' );
 31          end if;
 32      end loop;
 33  end;
 34  /

Trigger created.

SQL> insert into discount_info values ( 5, 10 );

1 row created.

SQL> insert into discount_info values ( 1, 11 );
insert into discount_info values ( 1, 11 )
            *
ERROR at line 1:
ORA-20001: overlap -- 1, 11 rejected
ORA-06512: at "OPS$TKYTE.DISCOUNT_INFO_AI", line 26
ORA-04088: error during execution of trigger
'OPS$TKYTE.DISCOUNT_INFO_AI'

SQL> insert into discount_info values ( 1, 7 );
insert into discount_info values ( 1, 7 )
            *
ERROR at line 1:
ORA-20001: overlap -- 1, 7 rejected
ORA-06512: at "OPS$TKYTE.DISCOUNT_INFO_AI", line 26
ORA-04088: error during execution of trigger
'OPS$TKYTE.DISCOUNT_INFO_AI'

SQL> insert into discount_info values ( 7, 8 );
insert into discount_info values ( 7, 8 )
            *
ERROR at line 1:
ORA-20001: overlap -- 7, 8 rejected
ORA-06512: at "OPS$TKYTE.DISCOUNT_INFO_AI", line 26
ORA-04088: error during execution of trigger
'OPS$TKYTE.DISCOUNT_INFO_AI'

SQL> insert into discount_info values ( 7, 11 );
insert into discount_info values ( 7, 11 )
            *
ERROR at line 1:
ORA-20001: overlap -- 7, 11 rejected
ORA-06512: at "OPS$TKYTE.DISCOUNT_INFO_AI", line 26
ORA-04088: error during execution of trigger
'OPS$TKYTE.DISCOUNT_INFO_AI'

SQL> insert into discount_info values ( 11, 20 );

1 row created.

Review &
      Followup

      Rating: 5
How about
      Avoiding Overlapping Dates   April 08, 2002

Reviewer:  Pascal  from Hamburg, Germany
Hi Tom ,

I have been reading this  and i would like to write a Tigger to avoid
Overlapping DATE  values in my Table...

for Example :

date_from   date_to
01.04.2002  30.04.2002
04.04.2002  01.05.2002   Not allowed here..because it's overlapping ..

I have tried the above logic but it doesn't work for Date values .

Thanks

Pascal

          Followup:

Sure it does -- numbers, dates, strings -- between works on all of them.

You'll have to be a little more "exact" in your question.  "doesn't work" is
about as vague as you can get.

All I did (really ALL I DID) was change the datatype on the discount info table
from number(2) to a date and:

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into discount_info values (
'01-apr-2002', '30-apr-2002' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into discount_info values (
'04-apr-2002', '01-may-2002' );
insert into discount_info values ( '04-apr-2002', '01-may-2002' )
            *
ERROR at line 1:
ORA-20001: overlap -- 04-APR-02, 01-MAY-02 rejected
ORA-06512: at "OPS$TKYTE.DISCOUNT_INFO_AI", line 26
ORA-04088: error during execution of trigger 'OPS$TKYTE.DISCOUNT_INFO_AI'

      Rating: 5
Could you
      please try this ....  April 08, 2002
Reviewer:
      Pascal  from Hamburg, Germany
Hi Tom

Thanks very much for your quick response..

I tried this and it didn't refuse the values:

Suppose i already have :

date_from   01.04.2002
date_to     30.04.2002

Now i want to insert

date_from   05.04.2002
date_to     20.04.2002

The second set  of Dates should not be allowed , but they were  allowed when i
tested  on my Computer..

Could you please test these values to see if it's something to do with my
triggers..

Thanks in advance

          Followup:

I did --

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into discount_info values (
'01-apr-2002', '30-apr-2002' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into discount_info values (
'05-apr-2002', '20-apr-2002' );
insert into discount_info values ( '05-apr-2002', '20-apr-2002' )
            *
ERROR at line 1:
ORA-20001: overlap -- 05-APR-02, 20-APR-02 rejected
ORA-06512: at "OPS$TKYTE.DISCOUNT_INFO_AI", line 26
ORA-04088: error during execution of trigger 'OPS$TKYTE.DISCOUNT_INFO_AI'

All i did -- all i did -- was change the datatype from number(2) to DATE.

Try again -- you missed something -- just run this script AS IS and you'll see:

drop table discount_info;

create table discount_info
(xto date, frm date );

create or replace package state_pkg
as
    type myArray is table of rowid
          index by binary_integer;

   newones  myArray;
   empty    myArray;

end;
/

create or replace trigger discount_info_bi
before insert on discount_info
begin
    state_pkg.newones := state_pkg.empty;
end;
/

create or replace trigger discount_info_aifer
after insert on discount_info for each row
begin
    state_pkg.newones(state_pkg.newones.count+1) :=
         :new.rowid;
end;
/

create or replace trigger discount_info_ai
after insert on discount_info
declare
    status int;
    l_rec  discount_info%rowtype;
    l_cnt  number;
begin
    status := dbms_lock.request
             ( id                => 123,
               lockmode          => dbms_lock.x_mode,
               release_on_commit => TRUE );

    for i in 1 .. state_pkg.newones.count
    loop
        select * into l_rec
          from discount_info
         where rowid = state_pkg.newones(i);

        select count(*)
          into l_cnt
          from discount_info
         where rowid <> state_pkg.newones(i)
           and ( xto between l_rec.xto and l_rec.frm
              OR frm between l_rec.xto and l_rec.frm
              OR ( xto < l_rec.xto and l_rec.xto < frm ) );

        if ( l_cnt > 0 ) then
            raise_application_error( -20001,
              'overlap -- ' || l_rec.xto || ', ' ||
              l_rec.frm || ' rejected' );
        end if;
    end loop;
end;
/

insert into discount_info values ( '01-apr-2002', '30-apr-2002' );
insert into discount_info values ( '05-apr-2002', '20-apr-2002' );

      Rating: 5
Thanks...i was
      mixing between Arguments  April 09, 2002
Reviewer:
      Pascal  from hamburg,Germany
Hi Tom
Thanks for your Response..
I was actually interchanging/mixing the Between Arguments...

I was actually coding   :

where rowid <> state_pkg.newones(i)
           and ( xto between l_rec.frm and l_rec.xto
              OR frm between l_rec.frm and l_rec.xto
              OR ( xto < l_rec.xto and l_rec.xto < frm ) );

instead of :

where rowid <> state_pkg.newones(i)
           and ( xto between l_rec.xto and l_rec.frm
              OR frm between l_rec.xto and l_rec.frm
              OR ( xto < l_rec.xto and l_rec.xto < frm ) );

Does it really matter which argument comes first in the Between   construct ?

Thanks , now it works perfectly

          Followup:

Amazing -- you change the code -- you don't mention "i changed the code" -- and
expected it to work.  hmmmm

Yes, between is very sensitive to the order of the operands.  It is

   between LO_VAL and HI_VAL

if you say between 10 and 5 you'll NEVER get a result, between 5 and 10 -- you
might get one.

a@ORA817DEV.US.ORACLE.COM> select * from dual where 6 between 10 and 5;

no rows selected

a@ORA817DEV.US.ORACLE.COM> select * from dual where 6 between 5 and 10;

D
-
X

a@ORA817DEV.US.ORACLE.COM>

If you are going to take my samples, modify them and then say "they don't work"
-- make sure to POST your changes.  It would have taken about 2 seconds to
diagnose that one.

      Rating: 5
How to
      serialize trigger at lower level  May 01, 2003

Reviewer:  Chuck Erickson  from San Francisco, CA

How would you serialize on a lower level? Say you had product, to, from,
discount in the table and wanted to assure no overlapping ranges for each
product--but not lock the whole table.

I thought I could use dbms_utility.get_hash_value to get a unique lock id for
the product value--as shown in Appendix A/DBMS_LOCK in Expert One on One (best
Oracle book ever written by the way!)

We'd have to create/check the lock for each row as you process it in the after
trigger--but actually we only want to create/check the lock for each "product"
which may be a set of rows.  Do we modify the trigger to process rows in product
order and break on product to check the lock?

Am I missing a simpler approach?

Thanks.

          Followup:

That is how you would do it -- to get a semi-unique (hashing doesn't guarantee
uniqueness) lock id to serialize access by product id.

you create the lock once -- each subsequent "get" of the lock after you got it
won't create a new lock -- you can get that lock over and over.

      Rating:
      5
  August 21, 2003
Reviewer:  Yogesh
      Chaudhari  from New York, NY, USA
Hi Tom,

I would like to split time ranges to find availability to schedule meeting for
user defined time period.

e.g.
Resource_Id  Start_Time  End_Time
-----------  ----------  --------
1            9.00        17.00
2            14.00       16.00
3            12.00       16.00

if user enters 30 mins.  Program should return following result

Start_Time End_Time
---------- --------
14.00      14.30

If we can do this in SQL using analytical functions it would be great, if not a
procedure or function would also be fine. I am using oracle 9iR2.

Thanks,

          Followup:

well, i looked at this and looked at it, and I haven't any idea why the answer
is 14.00 to 14.30?

to me, it looks like all 3 resources are already booked?

i don't get your logic

      Rating: 4
Find earliest
      available time slot  August 22, 2003
Reviewer:
      Yogesh Chaudhari  from New York, NY, USA
Hi Tom,

Thanks for the prompt reply.  Sorry I did not explain properly.

Table : Resource_availability

Resource_id   Date      Start_time   End_time
----------------------------------------------
1             9/1/2003  9.00         17.00
2             9/1/2003  14.00        16.00
3             9/1/2003  12.00        16.00

Listed above are the  availability of resources for a particular day.( This
availability has already factored in the already scheduled meetings of all
resources )

Given this, how can we come up with a earliest common available time to schedule
a meeting

for ex : If Meeting organizer wants the meeting to be held for 30 minutes on
9/1/2003 to be attended by resource 1 ,2 and 3 . The earliest available time
slot of 30 mins would be 14.00 - 14.30.

          Followup:

well, that is just max(start_time)

select max(start_time) from resource_availability where start_time between
to_date( '9/1/2003', 'mm/dd/yyyy' ) and to_date( '9/1/2002 23:59:59',
'mm/dd/yyyy hh24:mi:ss' )

but it is probably more complex then that -- you probably have multiple records
for each resource and such.  if so, give a real example.

      Rating:
      5
  August 22, 2003
Reviewer:
      A reader
I like the name referenced by original poster "Mr Tam"
by the way who is Mr.Tam??

      Rating: 4
Find Resource
      Avaibility  August 22, 2003
Reviewer:  Yogesh
      Chaudhari  from New York, NY, USA
Hi Tom,

Thanks for prompt reply, here is my table defination where we are storing
availibity of
external resources, who will be attending the meeting. Resources could be
persons and rooms .
The example shown below explains that we can have more then 1 record
for a resource for a given day in this table . One thing is sure though , that
they wont have
overlapping availability for a given day for a resource , in other words they
will always be
mutually exclusive

CREATE TABLE AVAILABILITY
(
  RESOURCE_ID       NUMBER(13)                  NOT NULL,
  AVAILABILITY_ID   NUMBER(13)                  NOT NULL, ( Primary key )
  START_TIME        DATE                        NOT NULL,
  END_TIME          DATE                        NOT NULL,
  CREATED_BY        VARCHAR2(30 BYTE)           NOT NULL,
  CREATION_DATE     DATE                        NOT NULL,
  LAST_UPDATED_BY   VARCHAR2(30 BYTE)           NOT NULL,
  LAST_UPDATE_DATE  DATE                        NOT NULL,
  RESCHEDULED       VARCHAR2(1 BYTE)            DEFAULT 'N'
NOT NULL
)

Resource_id   Date      Start_time   End_time
----------------------------------------------
1             9/1/2003  9.00         10.00
1             9/1/2003  12.00        15.00
1             9/1/2003  16.00        17.00

2             9/1/2003  9.30         11.00
2             9/1/2003  14.00        16.00
2             9/1/2003  16.30        17.00

3             9/1/2003  12.00        15.00

For this scenario if user is looking for 30 min slot (meeting time is user
defined).
System should return 14:00 – 14:30.

          Followup:

well, rdbms's really hate to make up data -- and that is what we have to do.
Not that we cannot do it, but we'll have to.

I'll take the long and winding explanation here -- it is a deceptively simple
looking query:

ops$tkyte@ORA920> select distinct real_date, cnt_dates, cnt_resources
  2    from (
  3  select real_date,
  4         count(*) over (partition by real_date) cnt_dates,
  5             count(distinct resource_id) over () cnt_resources
  6    from (
  7  select resource_id, start_time, end_time, real_date
  8    from availability,
  9         (select to_date(:DAY) + (rownum-1) * :LENGTH * (1/24/60) real_date
 10                from all_objects
 11                   where rownum <= 24*60 / :LENGTH ) B
 12   where start_time between to_date( :DAY ) and to_date( :DAY ) +
1-1/24/60/60
 13     and real_date between start_time and end_time
 14     and real_date + :LENGTH * (1/24/60) <= end_time
 15         )
 16             )
 17   where cnt_dates = cnt_resources
 18   order by real_date
 19  /

REAL_DATE           CNT_DATES CNT_RESOURCES
------------------ ---------- -------------
09/01/2003 02:00pm          3             3
09/01/2003 02:30pm          3             3

ops$tkyte@ORA920>
ops$tkyte@ORA920>

that generates the set of all possible meeting times for all three resources...

What I did was start by generating a set of all N minute intervals:

ops$tkyte@ORA920> variable DAY varchar2(20)
ops$tkyte@ORA920> variable LENGTH number
ops$tkyte@ORA920> exec :DAY := '9/1/2003'

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec :LENGTH := 30

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> break on resource_id skip 1
ops$tkyte@ORA920> break on start_time skip 1
ops$tkyte@ORA920>
ops$tkyte@ORA920> select to_date(:DAY) + (rownum-1) * :LENGTH * (1/24/60)
real_date
  2    from all_objects
  3   where rownum <= 24*60 / :LENGTH
  4  /

REAL_DATE
------------------
09/01/2003 12:00am
09/01/2003 12:30am
09/01/2003 01:00am
09/01/2003 01:30am
......

in the day.  We were looking for 1/2 hour periods.  I took the simplification
that everything started on N minute boundaries as well (eg: this will find 30
minute windows that start on the top or the bottom of the hour only -- you would
have to adjust for accomodate say 15 minute windows -- so that you could have
meetings starting at 12 or 12:15 or 12:45.  whatever you generated in this set
will dictate when meetings can in fact start).....

Now, we do a non-equi join of this set with our set of resources:

ops$tkyte@ORA920> select resource_id, start_time, end_time, real_date
  2    from availability,
  3         (select to_date(:DAY) + (rownum-1) * :LENGTH * (1/24/60) real_date
  4                from all_objects
  5                   where rownum <= 24*60 / :LENGTH ) B
  6   where start_time between to_date( :DAY ) and to_date( :DAY ) +
1-1/24/60/60
  7     and real_date between start_time and end_time
  8     and real_date + :LENGTH * (1/24/60) <= end_time
  9   order by resource_id, real_date
 10  /

RESOURCE_ID START_TIME         END_TIME           REAL_DATE
----------- ------------------ ------------------ ------------------
          1 09/01/2003 09:00am 09/01/2003 10:00am 09/01/2003 09:00am
          1                    09/01/2003 10:00am 09/01/2003 09:30am

          1 09/01/2003 12:00pm 09/01/2003 03:00pm 09/01/2003 12:00pm
          1                    09/01/2003 03:00pm 09/01/2003 12:30pm
          1                    09/01/2003 03:00pm 09/01/2003 01:00pm
          1                    09/01/2003 03:00pm 09/01/2003 01:30pm
          1                    09/01/2003 03:00pm 09/01/2003 02:00pm
          1                    09/01/2003 03:00pm 09/01/2003 02:30pm

          1 09/01/2003 04:00pm 09/01/2003 05:00pm 09/01/2003 04:00pm
          1                    09/01/2003 05:00pm 09/01/2003 04:30pm

          2 09/01/2003 09:30am 09/01/2003 11:00am 09/01/2003 09:30am
          2                    09/01/2003 11:00am 09/01/2003 10:00am
          2                    09/01/2003 11:00am 09/01/2003 10:30am

          2 09/01/2003 02:00pm 09/01/2003 04:00pm 09/01/2003 02:00pm
          2                    09/01/2003 04:00pm 09/01/2003 02:30pm
          2                    09/01/2003 04:00pm 09/01/2003 03:00pm
          2                    09/01/2003 04:00pm 09/01/2003 03:30pm

          2 09/01/2003 04:30pm 09/01/2003 05:00pm 09/01/2003 04:30pm

          3 09/01/2003 12:00pm 09/01/2003 03:00pm 09/01/2003 12:00pm
          3                    09/01/2003 03:00pm 09/01/2003 12:30pm
          3                    09/01/2003 03:00pm 09/01/2003 01:00pm
          3                    09/01/2003 03:00pm 09/01/2003 01:30pm
          3                    09/01/2003 03:00pm 09/01/2003 02:00pm
          3                    09/01/2003 03:00pm 09/01/2003 02:30pm

24 rows selected.

That gives us every 30 minute window they each have available.

Using the analytic function:

count(*) over (partition by real_date) cnt_dates,

we are able to count how many people have the same start time available for the
required duration and then using:

 count(distinct resource_id) over () cnt_resources

we are able to see how many resources we are in fact coordinating...

Once we have that, the predicate:

where cnt_dates = cnt_resources

just keeps the times that we have as many people free as people.  Order by that
and take the first row (or any of the rows, they are all equally OK at that
point).....

btw: this was my setup for this example:

drop TABLE AVAILABILITY;
CREATE TABLE AVAILABILITY
(
  RESOURCE_ID       NUMBER(13)                  NOT NULL,
  START_TIME        DATE                        NOT NULL,
  END_TIME          DATE                        NOT NULL
)
/

alter session set nls_date_format = 'mm/dd/yyyy hh:miam';

insert into availability values ( 1, '9/1/2003  9:00am', '9/1/2003 10:00am' );
insert into availability values ( 1, '9/1/2003 12:00pm', '9/1/2003  3:00pm' );
insert into availability values ( 1, '9/1/2003  4:00pm', '9/1/2003  5:00pm' );

insert into availability values ( 2, '9/1/2003  9:30am', '9/1/2003 11:00am' );
insert into availability values ( 2, '9/1/2003  2:00pm', '9/1/2003  4:00pm' );
insert into availability values ( 2, '9/1/2003  4:30pm', '9/1/2003  5:00pm' );

insert into availability values ( 3, '9/1/2003 12:00pm', '9/1/2003  3:00pm' );

      Rating:
      4
  August 22, 2003
Reviewer:  Vipin
       from NY
Hi Tom,

In the query which you mentioned :-

 select count(*)
 into l_cnt
 from discount_info
 where rowid <> state_pkg.newones(i)
 and ( xto between l_rec.xto and l_rec.frm
       OR frm between l_rec.xto and l_rec.frm
     OR ( xto < l_rec.xto and l_rec.xto < frm ) );

Can't the overlapping be checked with following predicate:-
 select count(*)
 into l_cnt
 from discount_info
 where rowid <> state_pkg.newones(i)
 and (xto between l_rec.xto and l_rec.frm
      OR
      l_rec.xto between xto and frm )

Please correct me if I am wrong.

          Followup:

there are a possibly infinite number of ways to express the predicate.  if you
want, feel free to prove yours out.  could be.  I just used the first one that
popped into my head that I knew would work.

      Rating: 4
Please, need
      help again...  October 15, 2003
Reviewer:
      A reader
Tom,

How can I achive following:
I have a following table:

DROP TABLE test_trigger
/

CREATE TABLE test_trigger
    (userid                         NUMBER(10,0),
    datecompleted                  VARCHAR2(10),
    timein                         VARCHAR2(250),
    timeout                        VARCHAR2(250),
    projectid                      NUMBER(10,0) NOT NULL,
    outletid                       NUMBER(10,0) NOT NULL,
    exception                      NUMBER)
  PCTFREE     10
  PCTUSED     40
  INITRANS    1
  MAXTRANS    255
  TABLESPACE  msp_rpt
  STORAGE   (
    INITIAL     40960
    NEXT        5242880
    PCTINCREASE 50
    MINEXTENTS  1
    MAXEXTENTS  2147483645
  )
/

Following are the values to populate the table:

INSERT INTO &&table_name
VALUES
(6,'09/15/2003','10:03:20','13:25:20',39,48936,0)
/
INSERT INTO &&table_name
VALUES
(6,'09/15/2003','14:00:12','15:00:52',16,48936,0)
/
INSERT INTO &&table_name
VALUES
(6,'09/15/2003','14:52:51','14:52:51',39,24519,0)
/
INSERT INTO &&table_name
VALUES
(6,'09/15/2003','15:20:42','16:39:42',39,26100,0)
/
INSERT INTO &&table_name
VALUES
(6,'09/15/2003','17:02:01','17:42:41',16,24574,0)
/
INSERT INTO &&table_name
VALUES
(6,'09/15/2003','17:43:11','17:43:11',39,24574,0)
/
INSERT INTO &&table_name
VALUES
(6,'09/16/2003','08:49:32','10:13:32',39,26099,0)
/
INSERT INTO &&table_name
VALUES
(6,'09/16/2003','10:33:39','12:33:02',16,24202,0)
/
INSERT INTO &&table_name
VALUES
(6,'09/16/2003','14:29:21','17:02:21',39,12085,0)
/
INSERT INTO &&table_name
VALUES
(6,'09/16/2003','17:07:03','17:51:21',23,23556,0)
/
INSERT INTO &&table_name
VALUES
(9,'09/15/2003','10:00:05','12:21:48',17,31205,0)
/
INSERT INTO &&table_name
VALUES
(9,'09/15/2003','14:00:01','16:20:22',17,38544,0)
/
INSERT INTO &&table_name
VALUES
(9,'09/15/2003','16:42:02','17:54:23',17,24446,0)
/
INSERT INTO &&table_name
VALUES
(9,'09/16/2003','08:30:17','09:30:17',60,1,0)
/
INSERT INTO &&table_name
VALUES
(9,'09/16/2003','10:31:45','12:43:45',39,26150,0)
/
INSERT INTO &&table_name
VALUES
(9,'09/16/2003','13:22:04','14:12:20',23,26149,0)
/
INSERT INTO &&table_name
VALUES
(9,'09/16/2003','14:23:07','16:35:04',16,24612,0)
/

I need to populate exception value with 1 in case there are overlapping TimeIn
and TimeOut on the immediate subsequent calls (group by userid,datecompleted).
For example, the timeIn in the third record is less then timeOut in the second
record, which should not happend, because user cannot start next call earlier
then he finished his prior call.

Please help

          Followup:

you cannot apply the logic above to your problem?  it is a straightforward
application of the above technique?  all you have to do is figure out "what do I
need to look for in order to flag an error"

      Rating: 4
Resource Time
      allocation   October 16, 2003
Reviewer:  Yogesh
      Chaudhari  from New York, NY
Hi Tom,

Thanks for prompt reply to my earlier question, I am using your query in my
application to derive the following result set of the resources and the time
slots they can be scheduled at.

Resource available  Time slot#
-------------------------------
c1    1
c1    2
c1    3
c1    4
c2    1
c2    2
c2    4
c3    1
c3    3
c3    4
c4    2
c4    3
c4    4

We need to allocate consecutive time slot# to all 4 resources . The # of
resources and timelots could vary .

so result will look like

c1           2
c2           1
c3           3
c4           4

to achieve this I am using following query in loop to select least available &
allocate time slot to it and update that record with sch = 'Y' and then requery
the next set of remaing slots with the remaining resources

Could you please let us know if this could be achieved without having to go for
looping and do it in single query set.

select distinct caseid, count(time_slot) over (partition by caseid),
first_value(time_slot) over(partition by caseid)
from temp
where caseid not in (select caseid from temp where sch is not null) and
time_slot not in (select time_slot from temp where sch is not null)
order by 2

Table definition is:

CREATE TABLE TEMP
(
  CASEID     VARCHAR2(13),
  TIME_SLOT  NUMBER,
  SCH        VARCHAR2(1)
)

thanks,
Yogesh

          Followup:

well, you could cartesian product the set with itself to find all possible ones
-- using rownum = 1 to get just one if that is what you want.

ops$tkyte@ORA920> with V as (select * from t)
  2  select *
  3    from v t1, v t2, v t3, v t4
  4   where t1.ts = 1
  5     and t2.ts = 2
  6     and t3.ts = 3
  7     and t4.ts = 4
  8     and t1.res <> t2.res and t1.res <> t3.res and t1.res <> t4.res
  9     and t2.res <> t3.res and t2.res <> t4.res
 10     and t3.res <> t4.res
 11  /

RE         TS RE         TS RE         TS RE         TS
-- ---------- -- ---------- -- ---------- -- ----------
c2          1 c4          2 c3          3 c1          4
c3          1 c2          2 c4          3 c1          4
c3          1 c4          2 c1          3 c2          4
c1          1 c4          2 c3          3 c2          4
c3          1 c1          2 c4          3 c2          4
c2          1 c4          2 c1          3 c3          4
c2          1 c1          2 c4          3 c3          4
c1          1 c2          2 c4          3 c3          4
c3          1 c2          2 c1          3 c4          4
c2          1 c1          2 c3          3 c4          4
c1          1 c2          2 c3          3 c4          4

11 rows selected.

      Rating: 5
How about on a
      view?  November 24, 2003
Reviewer:  Doug C
       from CT, USA
Tom - your implementation requires a row level and a table leve trigger.   I was
wondering, if I use Workspace Manager which when versioning, turns my table into
a view, can I still validate overlapping dates in other columns that createtime
and retiretime even though I am limited to INSTEAD OF triggers exclusively?

          Followup:

give it a try!

      Rating: 4
OK - Tried
      it  November 24, 2003
Reviewer:  Doug
       from CT, USA
Ok - no reason Tom should do everything :-) but I am confused - here's the setup
-

create table salary (
   name varchar2(10),
   salary number(10),
   title varchar2(20),
    dateofbirth date,
    starttime date,
   stoptime date);

SQL> alter table salary add constraint aprimarykey primary key
   (name, dateofbirth);

SQL> begin
            DBMS_WM.EnableVersioning ('salary',  'VIEW_WO_OVERWRITE');
end;  2    3
  4  /

insert into salary values ('Bob',6000,'Assist
Provost',to_date('09-APR-45','DD-MON-RR'),to_date('01-JAN-95','DD-MON-RR'),to_dat
e('01-JUN-95','DD-MON-RR'))

update salary set salary=7000,
              title = 'Assist Provost',
              starttime=to_date('01-JUN-95','DD-MON-RR'),
              stoptime=to_date('01-OCT-95','DD-MON-RR')
              where name='Bob'
              and dateofbirth=to_date('09-APR-45','DD-MON-RR')
/
Commit complete.

SQL> select * from salary;

NAME           SALARY TITLE                DATEOFBIR STARTTIME STOPTIME
---------- ---------- -------------------- --------- --------- ---------
Bob              7000 Assist Provost       09-APR-45 01-JUN-95 01-OCT-95

SQL>

Now get the trigger -

SQL> get tom.sql
  1  create or replace trigger salary_ai
  2      INSTEAD OF update on salary
  3      declare
  4          status int;
  5          l_rec  salary%rowtype;
  6          l_cnt  number;
  7      begin
  8             select count(1) into l_cnt
  9               from salary
 10           where
 11           :new.stoptime between :old.stoptime and :old.starttime
 12           OR :new.starttime between :old.stoptime and :old.starttime
 13           OR :new.stoptime < :old.stoptime and :old.stoptime < :new.starttim
e ;
 14             if ( l_cnt > 0 ) then
 15                 raise_application_error( -20001,
 16                   'overlap -- ' || l_rec.stoptime  || ', ' ||
 17                   l_rec.starttime || ' rejected' );
 18             end if;
 19*    end;
SQL>

Trigger created.

SQL>

SQL>  update salary set starttime=to_date('01-JUL-95','DD-MON-RR');

1 row updated.

SQL> select * from salary;

NAME           SALARY TITLE                DATEOFBIR STARTTIME STOPTIME
---------- ---------- -------------------- --------- --------- ---------
Bob              7000 Assist Provost       09-APR-45 01-JUN-95 01-OCT-95

SQL>

It doesn't appear to be working. just tossing the update.
This may be a goofy question.. not sure.:-)  I am finding workspace manager a
lot of fun.

          Followup:

hmmm -- ok - hope you don't mind, but I'm going to indulge my favorite rant!

it takes at most and at least 4 characters to represent a year!!!  always!! (i
know, its a demo -- but your asst provost is yet to be born!  he is from the
year 2045!!!)

but look at your predicate.  it only references :new and :old??

and it has between STOP and START, where STOP > START (where between 2 and 1 is
patently "false")

You just rushed the example :)

November 24, 2003 alexb -- Thanks for the question regarding "How
      can i get column's names in cursor", version 9i

      You Asked
I get cursor and i try to get column's values and column's names.
I now how can i get column's values but column's names not.

LOOP
FETCH Cursor INTO VALUE1,VALUE2;
EXIT WHEN Cursor%NOTFOUND;
ColumnName:=????? (column names of VALUE1,VALUE2)

What is a best solution for it
Please, help.

      and we said...
well, you wrote the cursor?  you *know* the names, you coded it.

there are no facilities to "describe" a cursor like that -- the answer is "you
knew at compile time what the names are"

If this is a dynamic SQL query -- you would use DBMS_SQL to parse and describe
it.
November 25, 2003 Tamas -- Thanks for the question regarding
      "Export/import moving data between different RDBMS versions", version
      8.0.5. + 7.3.4 + 8.1.6R2

      You Asked
Dear Tom,

we have different Oracle RDBMS version - production, testing,
developing. In this situation we use only Oracle 7.3.4 features,
nothing extra. We would like to move data between Oracle 7.3.4,
Oracle 8.0.5 and Oracel 8.1.6R2. How do we do it? E.g. when we
want to export from 8.0.5 to 7.3.4 we run catexp7.sql and use
the exp80 utility? Or should we use exp73.exe copied to the
Oracle 8.0.5 site?

Just a link to some technical papers should suffice.

Thank you in advance.

Regards

Tamas

      and we said...
To move data DOWN a version(s), you need to export using that
lower versions EXP tool and IMP using that lower versions tool.

To move data UP a version, you export using the EXP of the
database that contains the data and you IMP using the imp that
ships with the TARGET database.

Eg:

to move data from 8.0 to 7.3:

o run catexp7 in the 8.0 database.
o exp73 over sqlnet to the 8.0 database.
o imp73 natively into the 7.3 database.

to move data from 7.3 to 8.1

o exp73 natively on the 7.3 instance
o imp natively using the 8.1 imp.exe

You might find the sqlplus COPY command (example at

http://osi.oracle.com/~tkyte/Misc/MoveLongs.html
) to be a nice tool as well as it avoids the intermediate files
and just copies data about.  Its pretty fast too.

Review &
      Followup

      Rating:
      2
Hmmm  February 04, 2002
Reviewer:
      Russell Smith  from UK
Was ok, but i need to do the same but between 9i and 8i...is it possible?

          Followup:

yes.  try it.

$ exp userid=scott/tiger@ora9i owner=scott

Export: Release 8.1.7.2.0 - Production on Mon Feb 4 10:19:16 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle9i Enterprise Edition Release 9.0.1.0.0 - Production
With the Partitioning option
JServer Release 9.0.1.0.0 - Production
Export done in US7ASCII character set and UTF8 NCHAR character set
server uses WE8ISO8859P1 character set (possible charset conversion)

About to export specified users ...
. exporting pre-schema procedural objects and actions
. exporting foreign function library names for user SCOTT
. exporting object type definitions for user SCOTT
About to export SCOTT's objects ...
. exporting database links
. exporting sequence numbers
. exporting cluster definitions
. about to export SCOTT's tables via Conventional Path ...
. . exporting table                          BONUS          0 rows exported
. . exporting table                              C          1 rows exported
. . exporting table                           DEPT          4 rows exported
. . exporting table                          DUMMY          1 rows exported
. . exporting table                            EMP         14 rows exported
. . exporting table                       SALGRADE          5 rows exported
. . exporting table                              T          0 rows exported
. . exporting table                            TTT         14 rows exported
. exporting synonyms
. exporting views
. exporting stored procedures
. exporting operators
. exporting referential integrity constraints
. exporting triggers
. exporting indextypes
. exporting bitmap, functional and extensible indexes
. exporting posttables actions
. exporting snapshots
. exporting snapshot logs
. exporting job queues
. exporting refresh groups and children
. exporting dimensions
. exporting post-schema procedural objects and actions
. exporting statistics
Export terminated successfully without warnings.

$

      Rating: 1
Does platform
      diference cause a problem?  February 05, 2002

Reviewer:  Will  from Victoria, BC
In my case I am going from 8.1.7.1.0 to 8.1.7.2.1 and get a cannot read header
error (IMP-00010) message. The other factor I can identify is the source runs on
Compaq Alpha 4100 and the targets tried are either Windows 2000 or Solaris 8
builds.

          Followup:

you most likely forgot to set "binary" on in an ftp -- check the size of the DMP
file and compare it to the size of the original DMP from the source system.  If
they are different -- the file was corrupted.

      Rating: 5
IMP
      ERROR  October 17, 2003
Reviewer:  atul
       from India
Hi TOM,

I am importing a file into 8.0.4 which i got from 8.0.5

I'm getting error:

Connected to: Oracle8 Enterprise Edition Release 8.0.4.3.1 - Production
With the Partitioning option
PL/SQL Release 8.0.4.3.1 - Production

IMP-00010: not a valid export file, header failed verification
IMP-00000: Import terminated unsuccessfully
=========================

I have correctly FTP'ed file in binary format,also i checked
size of the files they are same.

What should i do now?

Thanks.
atul

          Followup:

read the original answer.

"To move data DOWN a version(s), you need to export using that lower versions
EXP
tool and IMP using that lower versions tool."

8.0.4 < 8.0.5, hence 8.0.4 cannot read an 8.0.5 dmp file.

      Rating:
      5
Thanks  October 21, 2003
Reviewer:
      atul  from India
Thanks Tom,

We are getting exp file through designer front-end (8.0.5),
We have 8.0.4 client,

So what could be the solution?
How could we get exp dump file of 8.0.4.

Thanks.
Atul

          Followup:

use the 804 exp -- not 805.

      Rating: 4
how about cross
      platform export?   November 24, 2003
Reviewer:
      Eugene  from NJ, USA
Hi Tom: I think your answer to the export issue is very helpful indeed.  An
issue that I encountered recently is doing export crossing platforms.  Our
database server is a Unix machine and I use a Oracle Client on Winodws OS to
manage data.  I have no problem export the database for a while and then this
morning the export process hangs when it reaches "exporting cluster
definitions..".  Is there a partial corruption of the database or it is not good
to do cross OS export.
Thank you for your answer.

Eugene

          Followup:

export works excellent over cross platforms.

there is some other cause.

check out the v$ tables to see what the session was waiting on, what query it
may have been running, why it appeared "stuck"

      Rating:
      4
  November 25, 2003
Reviewer:
      A reader  from NJ, USA
It is a good answer indeed.  I am checking the session detail and it now shows
the current state of EXE application and the statistic of each exporting object.
 I can use this to debug the issue.

Thanks,

Eugene

November 25, 2003 dhaval -- Thanks for the question regarding "Loop
      through sql script", version 8.1.7

      You Asked
declare
v_var varchar2(10) := '0';
begin
    while v_var <> '-1' loop -- code enters the loop
      v_var :='&test';-- takes input from user
     --- it should check the value of v_var
     -- if it is not -1 then ask for one more input and so on
     --- but this does not work
     -- code hangs, Have to kill the session
    end loop;
end;
/

-- if this is not possible the how can we do it using pl/sql?
-- why this is not possible? Is it because pl/sql code executed on
-- server side?

Thanks

dhaval shah

      and we said...
You cannot do this because....

The plsql runs in the server -- the input comes from the client.  Once the plsql
is submitted to the server -- it is running IN the server, no more client
interaction is possible.  Addittionally, sqlplus does the substitution, not
plsql

The way the above block would work in sqlplus is:

a) you would enter the block of code.  sqlplus would get the entire thing
b) sqlplus would see the &test in there and do the substitution, say &test had
the value 'hello', the block would now be:

declare
v_var varchar2(10) := '0';
begin
    while v_var <> '-1' loop -- code enters the loop
      v_var :='hello';-- takes input from user
     --- it should check the value of v_var
     -- if it is not -1 then ask for one more input and so on
     --- but this does not work
     -- code hangs, Have to kill the session
    end loop;
end;
/

c) sqlplus now submits this block (no more &test in it) to the database and you
have what is known as an infinite loop on your hands.

PLSQL cannot interact with the user unless it is running outside of the database
in a tool like Oracle Forms.
Review &
      Followup

      Rating: 4
How can a DBA
      kill this session running in a shared server?  October 16,
      2003
Reviewer:  a reader  from Germany
You showed why the program runs in a loop.

But what can I do as a DBA if such a program is already running in a productive
environment and I have to stop the session without killing the shared server and
without stopping the instance?

ALTER SYSTEM KILL SESSION ... only marks the session as "KILLED" but the session
keeps running.

          Followup:

when the session is killed, the session is killed.  it is idle at that point.
it'll stay there until the client process tries to access the database again but
it is "killed" when v$session say "killed"

      Rating: 2
Session is
      killed but not idle  October 17, 2003
Reviewer:
      a reader  from Germany
Because the client is looping, he can't retry to communicate with the database.
The shared server process is still using CPU. Even aborting the Client via task
manager doesn't help.
DCD detects that client dead, but the PL/SQL loop is still active.

      Rating: 3
Loops in
      sqlplus  November 21, 2003
Reviewer:
      vaibhav  from India
i want to connect to 9 servers and fire one sql script. my problem is that i
want to do it using a single script that connects to the various servers and
fires that script. as the servers are production servers, i cant create
procedures,functions out there and not even database links. the version of
oracle is 7.3 and that makes life even worse.

how do I connect to all the servers in a loop, as I don&#8217;t want to hardcode
the 9 servers&#8217; credentials.
Further, number of servers is not fixed, they can increase or decrease.
Morevoer, I want to fire the script from any server, not from only a particular
server.
So I just run a common script on any box and voila !! I get the o/p in a jiffy.

Vaibhav.

          Followup:

write a script then that iterates over the "servers" in shell and runs your
script.

      Rating:
      2
connection  November 23, 2003
Reviewer:
      vaibhav  from India
That means i have to alter the tnsnames.ora file on each server to achieve this.
the sysadmin/dba team will not allow access to the servers. can i specify the
connection in some other way...

is  there a way in oracle where we can connect like this
Connect username/password@(DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = 10.16.0.216)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SID = oweb)
    )
  )

so that we do not need the tnsnames.ora file

Regards,
Vaibhav.

          Followup:

yes, and you can even use your own

~/.tnsnames.ora

in your home directory.  but

sqlplus 'scott/tiger@(description=......)'

works.

      Rating: 4
Connect using
      tns description  November 24, 2003
Reviewer:
      Vaibhav  from India
The suggestion you gave works very well on my oracle client in windows but when
i issue the same command (sqlplus 'user/pwd@description') on the unix prompt, it
raises an error stating "ERROR: ORA-12154: TNS:could not resolve service name"

alternatively, after storing my tnsnames.ora file in the home directory, the
error persists. is it because there may be a protocol mismatch. how do i find
out which protocol is to be used ?

is there any environment variable to be set in unix which will do refer the
tnsnames.ora file ?

sorry for the basic doubts..
Vaibhav

          Followup:

it works fine on unix and if you are getting the error in both the command line
and tnsnames.ora -- you have made a typo somewhere.  Here is a "for example":

[tkyte@tkyte-pc tkyte]$ sqlplus
'scott/tiger@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=aria-dev.us.
oracle.com)(PORT=1541)))(CONNECT_DATA=(SERVICE_NAME=ora920)))'

SQL*Plus: Release 9.2.0.4.0 - Production on Tue Nov 25 07:48:26 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production

scott@ORA920>

      Rating: 5
connect using
      tns description  November 26, 2003
Reviewer:
      Vaibhav  from India
Dear All,
As you had pointed out, there must have been some typo as i tried connecting
today morning issuing the same command and it got connected in a jiffy..

Thanx a lot..

Regards,
Vaibhav.

November 25, 2003 -- Thanks for the question regarding "RBS vs Undo
      Tablespaces", version 9.2

      You Asked
Which is the best to use, are there advanteges from one over the other like is
performance better or easier administration etc

Thanks in advance

Craig

      and we said...
In my latest book, "Effective Oracle By Design", i covered this very topic.  My
"wrapup" was:

UNDO Tablespace Wrap-up

For operational simplicity, and for the needs of most systems, the UNDO
tablespace cannot be beat. However, for some systems-high-end, high-throughput
systems-the manual sizing you can achieve with rollback segments may be
beneficial. It might be telling that when Oracle developers run a TPC-C
benchmark (mixed workload, emphasis on OLTP), they use 1,000 hand-configured
rollback segments. When they run a TPC-H (data warehouse benchmark), they use an
UNDO tablespace.

Determining the proper number of rollback segments, as well as sizing them
properly, is something that can typically be done on only the most predictable
systems, such as transaction-oriented systems. Most implementations will not
materially benefit from the fine-tuning of rollback segments and should
generally use the UNDO tablespace. It will be the exception, not the rule, that
uses rollback segments manually. Since you can easily move back and forth
between the two styles of undo management (albeit with a database restart), it
is a decision that is easy to make and unmake.

Review &
      Followup

      Rating: 3
Allocating RBS
      to an Transaction  November 22, 2003
Reviewer:
      Bobby  from India
Hello,
In 8i we used to set an seperate RBS to an transaction in the application. In 9i
is there any change as RBS is obslete. I know internally Undo TS is nothing but
RBS but from an application point of view will command 'Set Transaction to RBS'
(I forgot the syntax) will still work or do I need to change it ?
Also pls tell me if proc, func, pack wriietn in 8i using this statement will it
do any harm?

          Followup:

you can set an init.ora parameter to allow the set transaction statement to
silently "fail" (so it will not raise an error).

undo_suppress_errors                 boolean     TRUE

Meaning -- if you are using an UNDO tablespace -- the statement will by default
fail (there will be no such rbs) but there is a parameter to allow it to APPEAR
to succeed silently so your program will continue to function (albeit without
using that rbs -- which in my opinion is a good thing)

      Rating: 3
Allocating RBS
      to an Transaction  November 22, 2003
Reviewer:
      Bobby  from India
Hello,
In 8i we used to set an seperate RBS to an transaction in the application. In 9i
is there any change as RBS is obslete. I know internally Undo TS is nothing but
RBS but from an application point of view will command 'Set Transaction to RBS'
(I forgot the syntax) will still work or do I need to change it ?
Also pls tell me if proc, func, pack wriietn in 8i using this statement will it
do any harm if used in Oracle 9i?

      Rating: 3
Can u more
      clear  November 24, 2003
Reviewer:  Bobby
       from India
Thks for the response. Can you be more clear abt this sentence "without using
that rbs -- which in my opinion is a good thing".

          Followup:

i've never approved of the "set transaction use rollback segment" clause.  I've
always felt you should use equi-sized rollback segments (any of them would be
"good enough")

November 25, 2003 Muthuraj -- Thanks for the question regarding
      "Oracle datas into Excell Sheet using pl/sql procedure in Oracle 8i",
      version version 8i

      You Asked
Hello Sir,
         My problem is thru stored procedure or pl/sql annonyms
block how to write the records into Excell sheet in Oracle 8i.My
collique is doing thru vb.But We want to achieve thru oracle
pl/sql.For ex,I am getting all employee records from employee
table and instead of spooling into a text file I want to write
into excell sheet.
         You were asked the working environment
Windows NT Workstation
Oracle 8i
Server Located in same workstation
There have no cartridges and app server or web server even not
from forms.

Our Achievement has to be done thru pl/sql procedure.
For extreem  case if not possible only in database
server...please explain how to do in Oracle Web server 3.

Thank You

      and we said...
We can do this with a straight plsql procedure.  There are more
then one format we can use to write an excel file -- from CSV to
SYLK.  I will demonstrate the SYLK format as I already have the
code and it offers the ability to do much fancier stuff like
fonts, headings, formulas and such.

We will use UTL_FILE (see the supplied packages guide for setup
info on that package.  You need an init.ora parameter set for
this to work correctly).  UTL_FILE allows us to write a file on
the server and since your workstation = server, this should work
nicely for you.

Here is the code with an example.  It should get you going:

Rem
Rem $Id$
Rem
Rem  Copyright (c) 1991, 1996, 1997 by Oracle Corporation
Rem    NAME
Rem      owasylk.sql - Dump to Spreadsheet with formatting
Rem   DESCRIPTION
Rem     This package provides an API to generate a file in the
Rem     SYLK file format.  This allow for formatting in a
Rem     spreadsheet with only a ascii text file.  This version
Rem     of owa_sylk is specific to Oracle8.
Rem   NOTES
Rem
Rem   MODIFIED     (MM/DD/YY)
Rem     clbeck      04/08/98  - Created.
Rem     tkyte       09/10/00  - Made it use UTL_FILE.
Rem
Rem

/*
  This package allows you to send the results of any query to
  a spreadsheet using UTL_FILE

  parameters:
    p_query        - a text string of the query.  The query
                     can be parameterized
                     using the :VARAIBLE syntax.  See example
                     below.

    p_parm_names   - an owaSylkArray of the paramter names
                     used as bind variables in p_query

    p_parm_values  - an owaSylkArray of the values of the
                     bind variable names.  The values
                     muse reside in the same index as the
                     name it corresponds to.

    p_cursor       - an open cursor that has had the query
                     parsed already.

    p_sum_column   - a owaSylkArray of 'Y's and 'N's
                     corresponding to the location
                     of the columns selected in p_query.
                     A value of NYNYY will result
                     in the 2nd, 4th and 5th columns being
                     summed in the resulting
                     spreadsheet.

    p_max_rows     - the maxium number of row to return.

    p_show_null_as - how to display nulls in the spreadsheet

    p_show_grid    - show/hide the grid in the spreadsheet.

    p_show_col_headers - show/hide the row/column headers
                         in the spreadsheet.

    p_font_name    - the name of the font

    p_widths       - a owaSylkArray of column widths.  This
                     will override the default column widths.

    p_headings     - a owaSylkArray of column titles.
                     This will override the default column
                     titles.

    p_strip_html   - this will remove the HTML tags from the
                     results before
                     displaying them in the spreadsheet cells.
                     Useful when the
                     query selects an anchor tag. Only the
                     text between <a href>
                     and </a> tags will be sent to the
                     spreadsheet.

  examples:

    This example will create a spreadsheet of all the MANAGERS
    in the scott.emp table and will sum up the salaries
    and commissions for them.  No grid will be in the
    spreadsheet.

declare
    output utl_file.file_type;
begin
    output := utl_file.fopen( 'c:\temp\', 'emp1.slk', 'w',32000
);

    owa_sylk.show(
        p_file => output,
        p_query => 'select empno id, ename employee,
                           sal Salary, comm commission ' ||
                   'from scott.emp ' ||
                   'where job = :JOB ' ||
                   'and sal > :SAL',
        p_parm_names =>
               owa_sylk.owaSylkArray( 'JOB', 'SAL'),
        p_parm_values =>
                 owa_sylk.owaSylkArray( 'MANAGER', '2000' ),
       p_sum_column =>
                 owa_sylk.owaSylkArray( 'N', 'N', 'Y', 'Y'),
        p_show_grid => 'NO' );

    utl_file.fclose( output );
end;

    This example will create the same spreadsheet but will
    send in a pre-parsed cursor instead

declare
    l_cursor number := dbms_sql.open_cursor;
    output utl_file.file_type;
begin
    output := utl_file.fopen( 'c:\temp\', 'emp2.slk', 'w',32000
);

    dbms_sql.parse( l_cursor,
        'select empno id, ename employee,
                sal Salary, comm commission ' ||
          'from scott.emp ' ||
          'where job = ''MANAGER'' ' ||
          'and sal > 2000',
        dbms_sql.native );

    owa_sylk.show(
        p_file => output ,
        p_cursor => l_cursor,
        p_sum_column =>
            owa_sylk.owaSylkArray( 'N', 'N', 'Y', 'Y' ),
        p_show_grid => 'NO' );
    dbms_sql.close_cursor( l_cursor );
    utl_file.fclose( output );
end;

*/

create or replace
package owa_sylk as
--
  type owaSylkArray is table of varchar2(2000);
--
  procedure show(
      p_file          in utl_file.file_type,
      p_query         in varchar2,
      p_parm_names    in owaSylkArray default owaSylkArray(),
      p_parm_values   in owaSylkArray default owaSylkArray(),
      p_sum_column    in owaSylkArray default owaSylkArray(),
      p_max_rows      in number     default 10000,
      p_show_null_as  in varchar2   default null,
      p_show_grid     in varchar2   default 'YES',
      p_show_col_headers in varchar2 default 'YES',
      p_font_name     in varchar2   default 'Courier New',
      p_widths        in owaSylkArray default owaSylkArray(),
      p_titles        in owaSylkArray default owaSylkArray(),
      p_strip_html    in varchar2   default 'YES' );
--
  procedure show(
      p_file          in utl_file.file_type,
      p_cursor        in integer,
      p_sum_column    in owaSylkArray  default owaSylkArray(),
      p_max_rows      in number     default 10000,
      p_show_null_as  in varchar2   default null,
      p_show_grid     in varchar2   default 'YES',
      p_show_col_headers in varchar2 default 'YES',
      p_font_name     in varchar2   default 'Courier New',
      p_widths        in owaSylkArray default owaSylkArray(),
      p_titles        in owaSylkArray default owaSylkArray(),
      p_strip_html    in varchar2   default 'YES' );
--
end owa_sylk;
/
show error

create or replace
package body owa_sylk as
--
  g_cvalue  varchar2(32767);
  g_desc_t dbms_sql.desc_tab;

  type vc_arr is table of varchar2(2000) index by
binary_integer;
  g_lengths vc_arr;
  g_sums vc_arr;
--
--

  g_file  utl_file.file_type;

  procedure p( p_str in varchar2 )
  is
  begin
    utl_file.put_line( g_file, p_str );
  exception
    when others then null;
  end;

  function build_cursor(
      q in varchar2,
      n in owaSylkArray,
      v in owaSylkArray ) return integer is
    c integer := dbms_sql.open_cursor;
    i number := 1;
  begin
    dbms_sql.parse (c, q, dbms_sql.native);
    loop
      dbms_sql.bind_variable( c, n(i), v(i) );
      i := i + 1;
    end loop;
    return c;
  exception
    when others then
      return c;
  end build_cursor;
--
--
  function str_html ( line in varchar2 ) return varchar2 is
    x       varchar2(32767) := null;
    in_html boolean         := FALSE;
    s       varchar2(1);
  begin
    if line is null then
      return line;
    end if;
    for i in 1 .. length( line ) loop
      s := substr( line, i, 1 );
      if in_html then
        if s = '>' then
          in_html := FALSE;
        end if;
      else
        if s = '<' then
          in_html := TRUE;
        end if;
      end if;
      if not in_html and s != '>' then
        x := x || s;
      end if;
    end loop;
    return x;
  end str_html;
--
  function ite( b boolean,
                t varchar2,
                f varchar2 ) return varchar2 is
  begin
    if b then
      return t;
    else
      return f;
    end if;
  end ite;
--
  procedure print_comment( p_comment varchar2 ) is
  begin
    return;
    p( ';' || chr(10) || '; ' || p_comment || chr(10) || ';' );
  end print_comment;
--
  procedure print_heading( font in varchar2,
                           grid in varchar2,
                           col_heading in varchar2,
                           titles in owaSylkArray )
  is
    l_title varchar2(2000);
  begin
    p( 'ID;ORACLE' );
    print_comment( 'Fonts' );
    p( 'P;F' || font || ';M200' );
    p( 'P;F' || font || ';M200;SB' );
    p( 'P;F' || font || ';M200;SUB' );
    --
    print_comment( 'Global Formatting' );
    p( 'F;C1;FG0R;SM1' ||
           ite( upper(grid)='YES', '', ';G' ) ||
           ite( upper(col_heading)='YES', '', ';H' )  );
    for i in 1 .. g_desc_t.count loop
      p( 'F;C' || to_char(i+1) || ';FG0R;SM0' );
    end loop;
    --
    print_comment( 'Title Row' );
    p( 'F;R1;FG0C;SM2' );
    for i in 1 .. g_desc_t.count loop
      g_lengths(i) := g_desc_t(i).col_name_len;
      g_sums(i) := 0;
      begin
        l_title := titles(i);
      exception
        when others then
          l_title := g_desc_t(i).col_name;
      end;
      if i = 1 then
        p( 'C;Y1;X2;K"' || l_title || '"' );
      else
        p( 'C;X' || to_char(i+1) || ';K"' || l_title || '"' );
      end if;
    end loop;
  end print_heading;
--
  function print_rows(
      c            in integer,
      max_rows     in number,
      sum_columns  in owaSylkArray,
      show_null_as in varchar2,
      strip_html   in varchar2 ) return number is
    row_cnt number          := 0;
    line    varchar2(32767) := null;
    n       number;
  begin
    loop
      exit when ( row_cnt >= max_rows or
                  dbms_sql.fetch_rows( c ) <= 0 );
      row_cnt := row_cnt + 1;
      print_comment( 'Row ' || row_cnt );
      --
      p( 'C;Y' || to_char(row_cnt+2) );

      for i in 1 .. g_desc_t.count loop
        dbms_sql.column_value( c, i, g_cvalue );
        g_cvalue := translate( g_cvalue,
                            chr(10)||chr(9)||';', '   ' );
        g_cvalue := ite( upper( strip_html ) = 'YES',
                             str_html( g_cvalue ),
                             g_cvalue );
        g_lengths(i) := greatest( nvl(length(g_cvalue),
                                  nvl(length(show_null_as),0)),
                                  g_lengths(i) );
        line := 'C;X' || to_char(i+1);
        line := line || ';K';
        begin
          n := to_number( g_cvalue );
          if upper( sum_columns(i)) = 'Y' then
            g_sums(i) := g_sums(i) + nvl(n,0);
          end if;
        exception
          when others then
            n := null;
        end;
        line := line ||
                 ite( n is null,
                      ite( g_cvalue is null,
                               '"'||show_null_as||
                                  '"', '"'||g_cvalue||'"' ),
                             n );
        p( line );
      end loop;
      --
    end loop;
    return row_cnt;
  end print_rows;
--
  procedure print_sums(
      sum_columns  in owaSylkArray,
      row_cnt      in number ) is
  begin
    if sum_columns.count = 0 then
      return;
    end if;
    --
    print_comment( 'Totals Row' );
    p( 'C;Y' || to_char(row_cnt + 4) );
    p( 'C;X1;K"Totals:"' );
    --
    for i in 1 .. g_desc_t.count loop
      begin
        if upper(sum_columns(i)) = 'Y' then
          p( 'C;X' || to_char(i+1) || ';ESUM(R3C:R' ||
                  to_char(row_cnt+2) || 'C)' );
        end if;
      exception
        when others then
          null;
      end;
    end loop;
  end print_sums;
--
  procedure print_widths( widths owaSylkArray ) is
  begin
    print_comment( 'Format Column Widths' );
    p( 'F;W1 1 7' );
    for i in 1 .. g_desc_t.count loop
      begin
        p( 'F;W' || to_char(i+1) || ' ' ||
            to_char(i+1) || ' ' ||
            to_char(to_number(widths(i))) );
      exception
        when others then
          p( 'F;W' || to_char(i+1) || ' ' ||
               to_char(i+1) || ' ' ||
               greatest( g_lengths(i), length( g_sums(i) )));
      end;
    end loop;
    p( 'E' );
  end print_widths;
--
  procedure show(
      p_file          in utl_file.file_type,
      p_cursor        in integer,
      p_sum_column    in owaSylkArray default owaSylkArray(),
      p_max_rows      in number     default 10000,
      p_show_null_as  in varchar2   default null,
      p_show_grid     in varchar2   default 'YES',
      p_show_col_headers in varchar2 default 'YES',
      p_font_name     in varchar2   default 'Courier New',
      p_widths        in owaSylkArray default owaSylkArray(),
      p_titles        in owaSylkArray default owaSylkArray(),
      p_strip_html    in varchar2   default 'YES' ) is
  --
    l_row_cnt number;
    l_col_cnt number;
    l_status  number;
  begin
    g_file := p_file;
    dbms_sql.describe_columns( p_cursor, l_col_cnt, g_desc_t );
    --
    for i in 1 .. g_desc_t.count loop
      dbms_sql.define_column( p_cursor, i, g_cvalue, 32765);
    end loop;
    --
    print_heading( p_font_name,
                   p_show_grid,
                   p_show_col_headers,
                   p_titles );
    l_status := dbms_sql.execute( p_cursor );
    l_row_cnt := print_rows(
                   p_cursor,
                   p_max_rows,
                   p_sum_column,
                   p_show_null_as,
                   p_strip_html );
    print_sums( p_sum_column, l_row_cnt );
    print_widths( p_widths );
  end show;
--
  procedure show(
      p_file          in utl_file.file_type,
      p_query         in varchar2,
      p_parm_names    in owaSylkArray default owaSylkArray(),
      p_parm_values   in owaSylkArray default owaSylkArray(),
      p_sum_column    in owaSylkArray default owaSylkArray(),
      p_max_rows      in number     default 10000,
      p_show_null_as  in varchar2   default null,
      p_show_grid     in varchar2   default 'YES',
      p_show_col_headers in varchar2 default 'YES',
      p_font_name     in varchar2   default 'Courier New',
      p_widths        in owaSylkArray default owaSylkArray(),
      p_titles        in owaSylkArray default owaSylkArray(),
      p_strip_html    in varchar2   default 'YES' ) is
  begin
    show( p_file => p_file,
          p_cursor => build_cursor( p_query,
                                    p_parm_names,
                                    p_parm_values ),
          p_sum_column => p_sum_column,
          p_max_rows => p_max_rows,
          p_show_null_as => p_show_null_as,
          p_show_grid => p_show_grid,
          p_show_col_headers => p_show_col_headers,
          p_font_name => p_font_name,
          p_widths => p_widths,
          p_titles => p_titles,
          p_strip_html => p_strip_html );
  end show;
--
end owa_sylk;
/
show error

Review &
      Followup

      Rating: 3
Please bear
      with me i made mistake in the question  December 12, 2001

Reviewer:  Richard  from Ghana
    My question is that I have 3 tables, 2 of which contain many records. I have

to write a code(plsql) that will select all matching records and insert them
into the 3rd table(C) .The real problem is that table A(not table C) must
contain records in
either Excel or Access  file format which is saved on a diskette. Thus apart
from selecting records from table B to be inserted into table C, I should also
find a way of reading or selecting matching records(in table A ) saved on the
floppy
diskette.
Can this be achievable in oracle7.3.2? Can you give me a simple code to do that?
NB. Table A(studnumber,subject,level)
        Table B(studnumber,dept,year)
         Table C(studnumber,subject,level,dept,year).
Thanks a lot.
Please bear with me.

          Followup:

not understanding the difference here.

I showed you how to populate table C with an insert into select.

I showed you how to dump any query to a file using UTL_FILE, be it against table
A, table C, table foobar.

      Rating:
      5
  January 23, 2002
Reviewer:
      A reader
hi Tom.

I want to give heading to excel report inside spreadsheet?

e.g

 REPORT FOR SALES DEPT.

then ...........all data

Can you help me?

          Followup:

the SYLK file format is an open, documented standard.  You can search
www.google.com for references to it, they can show you how to format a sylk file
in that fashion.  then you just have to modify the code....

      Rating: 4
Exporting in
      Lotus 1-2-3  January 24, 2002
Reviewer:
      Arvind  from India
This is very useful for use

Is this method is also applicable for Lotus 1-2-3 spreadsheets. Secondly can we
import data from Lotus 1-2-3 sheets into oracle

          Followup:

if lotus can read sylk files, sure (i think it can, at least the last time I saw
it about 8 years ago it could, didn't even know it was still around).

You would EXPORT the data from lotus into a delimited file and use sqlldr to
load it.

      Rating:
      5
  January 24, 2002
Reviewer:
      A reader
Tom

i search www.googli.com web site for SYLK utility.
and i found sylk FILE FORMAT. I change you program for report heading.

ThnakS a lot

YOU ARE THE "BEST"

      Rating: 5
Good One
      But  January 24, 2002
Reviewer:  Parag
       from India
Hi :

I am using Oralce 8.1.7 on SUN. I have Fired the code give by U . After Creating
Package , When i try to execute the code (where we used to call package ) it's
giving me foll error.

ERROR at line 1:
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at "SYS.UTL_FILE", line 101
ORA-06512: at "SYS.UTL_FILE", line 192
ORA-06512: at line 4

Please help me out.

          Followup:

you have not setup the necessary INIT.ORA parameters for utl-file.  Please refer
to the supplied packages guide, you need to setup the utl_file_dir init.ora
parameter.

      Rating: 4
Why you get
      ORA-06510: PL/SQL: unhandled user-defined exception
      ...  January 24, 2002
Reviewer:  Ravi
       from NY,NY
You get this error if:
1 You have not specified utl_file_dir init parameter (as Tom warned)
2 You have specified it, but as a specific directory c:\temp

Solution to 2 is :
- Specify it as utl_file_dir = *
- Or change the directory to 'c:\temp' from 'c:\temp\' in Tom's code

I tested on Windows.

      Rating: 2
SYS.UTL_FILE",
      line 98  January 25, 2002
Reviewer:  Reddy
       from Bangalore, India
Hi tom

I copied and pasted in our database  the package and package body has been
created. After When I run the follwing code SYS.UTL_FILE line 98 error is
comming.

I am comming from the server using Administrator login.
User

Reddy@Oracle815>  SELECT STATUS,OBJECT_NAME FROM USER_OBJECTS WHERE
  2  OBJECT_NAME LIKE 'OWA_SYLK%' AND
  3* OBJECT_TYPE IN ('PACKAGE','PACKAGE BODY')
/
STATUS             OBJECT_NAME
-------------------------------------------------------------------
VALID            OWA_SYLK
VALID            OWA_SYLK

Reddy@Oracle815>

declare
    output utl_file.file_type;
begin
    output := utl_file.fopen( 'E:\ORADEV\', 'emp1.slk', 'w',32000 );

    owa_sylk.show(
        p_file => output,
        p_query => 'select empno id, ename employee,
                           sal Salary, comm commission ' ||
                   'from scott.emp ' ||
                   'where job = :JOB ' ||
                   'and sal > :SAL',
        p_parm_names =>
               owa_sylk.owaSylkArray( 'JOB', 'SAL'),
        p_parm_values =>
                 owa_sylk.owaSylkArray( 'MANAGER', '2000' ),
       p_sum_column =>
                 owa_sylk.owaSylkArray( 'N', 'N', 'Y', 'Y'),
        p_show_grid => 'NO' );

    utl_file.fclose( output );
end;

/
declare
*
ERROR at line 1:
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at "SYS.UTL_FILE", line 98
ORA-06512: at "SYS.UTL_FILE", line 192
ORA-06512: at line 4

What could be the reason. Pls clarify with some small example how this error is
comming and how to eliminate.

          Followup:

see the Oracle server reference manual and read about the utl_file_dir init.ora
parameter which you NEED to set up

      Rating: 5
need suggestion
        January 27, 2002
Reviewer:  Mohd Ahmed

What are your suggestions in order to have this working on 7.3.4.4 instead of
8i?

          Followup:

You'll have to recode it to not use the new collection type syntax available
with 8.0 and up -- use plsql index by table types instead.  So, thats really the
only changes you'll have to make.  It's not as easy to initialize plsql index by
tables as it is collection types -- so, instead of the caller coding:

declare
    output utl_file.file_type;
begin
    output := utl_file.fopen( 'c:\temp\', 'emp1.slk', 'w',32000 );

    owa_sylk.show(
...
        p_parm_names => owa_sylk.owaSylkArray( 'JOB', 'SAL'),
...
        p_show_grid => 'NO' );

    utl_file.fclose( output );
end;

they'll have to code (after you rewrite the owasylk package)
declare
    output utl_file.file_type;
    l_parm_names owa_sylk.owaSylkArray;
begin
    output := utl_file.fopen( 'c:\temp\', 'emp1.slk', 'w',32000 );

    l_parm_names(1) := 'JOB';
    l_parm_names(2) := 'SAL';

    owa_sylk.show(
...
        p_parm_names => l_parm_names,
...
        p_show_grid => 'NO' );

    utl_file.fclose( output );
end;

      Rating: 5
Very
      neat.  January 28, 2002
Reviewer:  Tony Reed
       from Oslo, Norway
At present I create reports as  txt files which are opened as delimited in
Excel.

This is much better!
No need for formatting etc.

Thanks.

      Rating: 5
Bind variables
      in Owa_SYLK  August 09, 2002
Reviewer:  Tom
       from UK
Tom,

One question.  I notice that Owa_sylk passes in a table of bind name and a table
of bind values as varchar2 data, although the columns to be bound may be numbers
/ dates.  Am I misunderstanding how dbms_sql.bind_value works, or will this not
cause each variable to be bound as a string, thus meaning that indexes will be
ignored on predicates where the datatype is not a string.

          Followup:

you should use to_number and to_date where appropriate, eg:

select * from emp where empno = to_number(:x)

select * from emp where hiredate = to_date( :x )

select * from emp where ename = :x

would all be appropriate for example.

      Rating: 4
for TEXT_IO how
      to work  September 12, 2002
Reviewer:
      A reader
Hi, Tom,

I want to write the file into client machine by TEXT_IO.
You told us we can change from UTL_FILE to TEXT_IO?
And I create the package "owa_sylk" in the "program unit"
within form, and also change all the UTL_FILE to
TEXT_IO type. But when I compiled it, there is an error:
DBMS_SQL(...., native)<---- error.
How can I solve that?

Thanks

          Followup:

in forms, you probably want to use EXEC_SQL, you can use DBMS_SQL but you cannot
in some releases of forms reference a packaged variable like that.

Just use the number 1

.....
  --  CONSTANTS
  --
  v6 constant integer := 0;
  native constant integer := 1;
  v7 constant integer := 2;
  --

      Rating: 5
Ignore
      that  September 12, 2002
Reviewer:
      A reader
Hi, Tom

I solved that "dbms_sql.native" problem on the client side.
So, please ignore that question.

Thanks and have a nice day.

      Rating: 4
"Text" cell
      format, how?  September 12, 2002
Reviewer:
      A reader
Hi, Tom,

Thanks for you excellent works.
My question is: we got data write into the excel file, but
this is actually in "general" cell format. What i want to do is generate the
data in "TEXT" format. In you "print_row" codes, can we implement that? Could
you shed
a light on this lease?

Thanks,

          Followup:

You'll have to dig up the SYLK file format on the web -- google it, it is a
documented standard.

      Rating: 4
Dynamic Data
      type  September 15, 2002
Reviewer:
      A reader  from India
Hi, Tom,

Appreciate for the solution.
There are basically 3 types of data in the database:
VARCHAR2, NUMBER and Date. I want to dynamically write them
into excel datasheet. So I have to dynamically get the data type of each column
and then wirte them into the column(row) based on the derived data type. Could
you shed some light on this topic please? Also for the Number, how do I know it
is just NUMBER or NUMBER(6,2)? Are there any difference to process that?
At last, we want to use JAVA implement the same function(
write data into excel), for example, SQLJ or JDBC. Could you compare your
solution and JAVA solution in the
aspect of performance and easy of use as well as security
please?

Thanks a million

          Followup:

No you don't.

A number can be fetched into a varchar2.
A date can be fetched into a varchar2.

When you print -- they will all be converted into varchar2 regardless.

Hence, I just fetch them into varchar2.  This utility handles dates/numbers "as
is".

A comparision of this to java

o this is written, the java one is not
o this is easy to code, performant, and will never throw a Null pointer
exception
o you need to setup utl_file_dir to make this plsql version work (in releases
before 9iR2, in 9iR2 you can use a directory object instead).  You have to use
dbms_java.grant_permission to make this work in java -- more or less the same.

Java will not make this any "better", trust me on that one.  Why do you care
what language something is written in.

      Rating: 4
Bear with
      me  September 16, 2002
Reviewer:
      A reader  from India
Hi, Tom,

Please bear with me, I'm kinda insist my opinion.
Because what I want to do is "pre-format" each column when
write them to the excelsheet. The person who receives
the excelsheet probably doesn't know the datatype for
each column(beacuse it is always "GENERAL" format when he/she opens the
column(cell) format property). So by this "pre-format", each column will be in
the right format when the guy open the column(cell) format and know what the
"original" format of the data. That means we are not only write data into excel
but also prepare the file(format column) for the person with no/little DB
knowledge.
We are aware of we can do this by using PRO/C* or JAVA because we can write them
into excel by correct format---
"%s, %i, %d" and it is in the right format and ready to
be checked by opening the format property in the excelsheet.
I remember there are some lines of codes which handle
finding datatypes of the outcome from a query dynamically,
but I can not find it, would you give me that pointer please?

Thanks a million

          Followup:

you can modify the code, which uses dbms_sql, to call the describe routine in
dbms_sql to discover the datatype, scale and precision and do whatever
formatting output you like.

      Rating: 4
Date
      format  September 17, 2002
Reviewer:
      A reader  from India
Hi, Tom,

I already solved the problem(datatype) by myself.
One more problem;
How can I find the DATE value including time?
I try to extract the date value(including time) and
manipulate it with the other times and write the result
to excel.
I can not use "gcvalue" because it don't allow me to
 for example:
SELECT to_date(gcvalue, 'DD-MON-YYYY HH24:MI:SS')
       - to_date(other_date, 'DD-MON-YYYY HH24:MI:SS')
FROM DUAL;
It can only do this(with YY), but it doesn't give time portion:
SELECT to_date(gcvalue, 'DD-MOC-YY HH24:MI:SS')
       - to_date(other_date, 'DD-MON-YY
       HH24:MI:SS')
I tried to define another variable(for date):
g_dvalue DATE and use:
dbms_sql.column_value( c, i, g_dvalue ) if it is
a date type, but it gives me error(not match col type).
Again, I tried to do:
dbms_sql.define_column( p_cursor, i, g_dvalue, 32765)
brfore getting date value but it give me another error.
So, I'm running out gas, and could you let me know how
can I get exectly date value(including time) and write
them into excel by correct format(I can do this part)?

Thanks a million

          Followup:

looks like you are trying to get the difference between two dates right?  if so,
see:

http://asktom.oracle.com/~tkyte/Misc/DateDiff.html

      Rating: 2
Time
      portion  September 17, 2002
Reviewer:
      A reader  from india
Nop, SIr,

The problem is that I can not get the time portion out of the variable
"g_cvalue"(only date portion with two digits
of year show up no matter what format I specify, e.g even
I use 'DD-MON-YYYY HH24:MI:SS' to explicitly covert it and
display to the output).
I can handle the diff between two dates.
Is VARCHAR2 variable such as "g_cvalue" capable of getting
the value from binded column even the type is date?

Thanks,

          Followup:

if you select

to_char( g_cvalue, 'dd-mon-yyyy hh24:mi:ss' )

you will most certainly get that string with TIME in it..  You would really
really need to provide a complete YET very concise example of your issue in
order for me to comment, because I just don't understand what your problem could
be.  CONCISE being an important word in that sentence.

      Rating:
      4
Explain  September 18, 2002
Reviewer:
      A reader  from India
Hi, TOm,

Thanks for your time.
Ok, here is the problem:
if I call the package by passing a query which includes a column having a date
type, then the time portion is not show up and only comes with two digits year
format even I specified explicitly the date format that i want.
For example:
dbms_sql.parse( l_cursor, 'select * from  emp', DBMS_SQL.NATIVE);
owa_sylk.show(p_file => output , p_cursor => l_cursor,
              p_show_grid => 'YES' );
in the above format, there is a "hiredate" date type field
which can not be correctly displayed in excel after generating the file. But if
I do:
dbms_sql.parse( l_cursor, 'select empno, ename, mgr...
to_char(credate, 'DD-MM-YYYY HH24:MI:SS') hd from  emp', DBMS_SQL.NATIVE);
it can displayed the date correctly in excel but it is in the format of "TEXT"
because it belongs to VARCHAR2.
So, my problem is: I still can not extract the time portion for a date type
column, could you test the code by just passing query "select * from emp", write
hiredate as a time format(col_type=12), and finally you will find the hiredate
column is not correctly displayed if you open the excel sheet.
Can the variable g_cvalue be used to get the value of date format(i mean
including TIME portion and be any format if i specified the date format
explicitly) if we know the type of the column is 12(col_type=12)?

Thanks

          Followup:

pass it a query that formats the date as you like (eg: don't use *, use empno,
ename, to_char(hiredate, 'dd-mon-yyyy hh24:mi:ss' ), .....

OR issue:

execute immediate 'alter session set nls_date_format = ''dd-mon-yyyy
hh24:mi:ss'' ';

before calling owa_sylk to change the default date format.

If you want to do this in owa_sylk you will have to

a) check for the datatype are you are
b) make sure you bind a date datatype -- not a character string
c) format it however you like after getting it

I'll not be of any more use on this one cause

a) you get the time component (you said so -- when (I use to_char......)
b) it is how you need to output it for excel that you need to discover
c) I don't know squat about excel ;)

      Rating: 3
Is it possible
      to create multiple sheets in Excel file  September 18, 2002

Reviewer:  John  from Canada
Hi Tom,

How can I create multiple worksheets in Excel file? Seems sylk only supports
single sheet.

Thanks,
John

          Followup:

sylk is sylk.  worksheets are MS's thing.  At this point, you would have to ask
MS -- what is the standard flat file format I can write that lets me use all of
your features.

      Rating: 4
We
      can  September 18, 2002
Reviewer:
      A reader  from USA
Hi,

We can create separate worksheet by learning from sample files. All we lack is
the current standard format file of
SYLK supported by MICROSOFT, the lastest one I can find is still for EXCEL 2.0
in published in 1985-1986. But as I said we CAN create separate worksheet.

Thanks

      Rating:
      4
Thanks  September 18, 2002
Reviewer:
      A reader
Hi, TOm,

I solved the problem of not showing time portion by g_cvalue, because it is
VARHCAR2 type(g_cvalue)--maybe
it is a bug. So I bind the date format by declare another
date variable g_dvalue in order to match them when i
get the column value.

Thanks anyway for your useful codes.

BTW, we can do most of the thing to excel format by using
this code, but you must to modify the code to your own needs.

      Rating: 4
Number of
      records in SYLK  September 18, 2002
Reviewer:
      A reader
Hi, Tom,

What is the largest number of records we can write into
excel by using this code?

I have tried to write about 450,000 records(from a table, pure query) into
excel(SYLK) by this method, but I only got 10001 reocrds showup after I open the
SYLK file under MS excel.

Please clarify.

THanks

          Followup:

the question is HOW MANY RECORDS can excel handle (and that is 64k)

450k -- no way, they don't do that

      Rating:
      4
Speed  October 04, 2002
Reviewer:
      Parasheh  from India
Hi, Tom,

Appreciate you code.

Ine thing I want to ask is that the speed of code execution
is very slow if the table is fairly large(both # of columns
and # of records are very large, e.g: million records and
100 columns). it will take at least 5~20 minutes to finish
the job). Would mind tell us how to increase the speed
please?

Thanks

          Followup:

You could recode to use array processing....

You could use a faster method such as C (search for array_flat on this site,
code already exists)

      Rating: 3
Why not use MS
      Query?  November 15, 2002
Reviewer:  Paul Dal
      Bianco  from Toronto, ON Canada
I create an MS Query DQY file and open excel.
I have created a menu item that works with the last_query from a block.

PROCEDURE FP_CREATE_DQY (p_qry in varchar2) IS

v_filename    VARCHAR2(200);
v_file        VARCHAR2(200);
v_qry           VARCHAR2(2000);
my_file        text_io.file_type;

BEGIN

-- set location of .dll
Win_Api_Preload.Set_Load_Loc('G:\system\sql_libs');

v_filename := Win_Api_Dialog.Save_File('*.dqy','Save File Dialog', 'C:\', 'DQY
Files(*.dqy)|*.dqy|', TRUE, WIN_API.OFN_FLAG_DEFAULT, TRUE);

if v_filename is not null then     -- "SAVE" button clicked
v_qry := replace( p_qry, 'ROWID,');

-- clear file if already exists, or create new one
my_file := text_io.fopen( v_filename, 'W' );

text_io.put_line (my_file, 'XLODBC');
text_io.put_line (my_file, '1');
text_io.put_line (my_file, 'DRIVER={Microsoft ODBC for
Oracle};UID='||user||';PWD='||get_application_property(PASSWORD)||';SERVER=TORPRO
D;');
text_io.put_line (my_file, v_qry);
text_io.put_line (my_file, ' ');
text_io.put_line (my_file, :control.column_headings );

text_io.fclose( my_file );

Win_API_Shell.startfile ( v_filename, WIN_API.SW_SHOWMAXIMIZED, TRUE);

end if;

END;

          Followup:

gotta love stuffing passwords in clear text into files -- fits right into the
entire MS architecture.

      Rating: 4
Why not use MS
      Query?  November 15, 2002
Reviewer:  Paul Dal
      Bianco  from Toronto, ON Canada
I create an MS Query DQY file and open excel.
I have created a menu item that works with the last_query from a block.

-- oops I left in some extra code from my form referenceing
:control.Column_headings.

      Rating: 5
Very
      handy  December 23, 2002
Reviewer:  Scott
       from Perth, Western Australia
G'day

You should add this package to your tidbit section, next to the dump_csv
function.

http://asktom.oracle.com/~tkyte/

          Followup:

add this to

http://asktom.oracle.com/~tkyte/flat/index.html
the "unloader" topic -- added all of the options actually, owa sylk, dump csv,
array flat and the sqlplus method.

      Rating: 3
What about
      query with join, union and alias?  March 18, 2003

Reviewer:  Dave  from Taiwan
Hi Tom,

I ran the following code and received errors:

declare
*
ERROR at line 1:
ORA-01003: no statement parsed
ORA-06512: at "SYS.DBMS_SYS_SQL", line 1518
ORA-06512: at "SYS.DBMS_SQL", line 614
ORA-06512: at "SAIYA.OWA_SYLK", line 239
ORA-06512: at "SAIYA.OWA_SYLK", line 275
ORA-06512: at line 7

I ran your example, it worked, but while replaced with the query below errors
occurred.
Which part caused these errors? Can't this package accept more complex queries?
Please advise, thanks.

--------------------------------------------------------
declare
    output utl_file.file_type;
    query  varchar2;
begin
    output := utl_file.fopen( 'f:\utl_file_dir', 'rejectapp.slk', 'w',32000 );

    owa_sylk.show(
        p_file => output,
        p_query => 'select  * from ('||
        'select c.salegroupname SALESGROUP,e.createdt
REJECTDATE,trunc(applydate) APPDATE,trunc(approvdate) APPROVEDATE,
        a.appformno APPNUM,CUSTREGNAME CUSTOMER,a.ACTUALTOTAL
CONTRACTAMT,a.PREPAY DOWNPAYMENT,a.paidby PAYMETHOD,
        b.agentcode SALELOCCODE,b.agentname SALELOC,f.paramdesc
REJECTCAUSE,e.REASON'||
        'from appforms a, salesagents b, salesgroup c,apprtnlog e,sysparams f'||
        'where a.appformno = e.APPFORMNO'||
        'and a.agentiid=b.agentiid  and b.salegroupid=c.salegroupid'||
        'and f.PARAMGROUPID = :PARAMGROUPID and e.RTNCODE =f.PARAMVAL'||
        'and trunc(e.createdt) = trunc(sysdate)'||
        'union'||
        'select c.salegroupname SALESGROUP,e.createdt
REJECTDATE,trunc(applydate) APPDATE,trunc(approvdate) APPROVEDATE,
        a.appformno APPNUM,CUSTREGNAME CUSTOMER,a.ACTUALTOTAL
CONTRACTAMT,a.PREPAY DOWNPAYMENT,a.paidby PAYMETHOD,
        b.agentcode SALELOCCODE,b.agentname SALELOC,f.paramdesc
REJECTCAUSE,e.REASON'||
        'from iypappforms a, salesagents b, salesgroup c,apprtnlog e,sysparams
f'||
        'where a.appformno = e.APPFORMNO'||
        'and a.agentiid=b.agentiid  and b.salegroupid=c.salegroupid'||
        'and f.PARAMGROUPID = :PARAMGROUPID and e.RTNCODE =f.PARAMVAL'||
        'and trunc(e.createdt) = trunc(sysdate))'||
        'order by SALESGROUP ,REJECTDATE',
    p_parm_names =>
               owa_sylk.owaSylkArray('PARAMGROUPID'),
    p_parm_values =>
                 owa_sylk.owaSylkArray('APPRTNCODE'),
        p_show_grid => 'YES');

    utl_file.fclose( output );
end;

/

          Followup:

dbms_sql can parse infinitely complex queries -- it is just an interface to the
database.

hows about this -- you add a little debug -- or take your query and try:

declare
   type rc is ref cursor;
   l_cursor rc;
   <other variables>
begin
   open L_cursor for 'select * from ( ' || .......
    using variable, variable, ....;
end;
/

just to see if you have it right.  I see immediately:

 'and trunc(e.createdt) = trunc(sysdate)'||
        'union'||
        'select c.s

for example which will result in :

 'and trunc(e.createdt) = trunc(sysdate)unionselect c.s

suggestion:

don't concatenate, just let it flow:

  p_query => 'select *
                from ( select *
                         from ( select *
                                  from dual )
                     )
              ', .....

you don't need to break it up as you have -- but if you do -- watch the
whitespace!!!!

      Rating:
      3
  May 03, 2003
Reviewer:  peter

Hello,
Your solutioms are good.
1 .How to add another worksheet in the same file?
2. It seems doesn't work for Big5(tranditional Chiese words)
how to solve?

Thank you.

          Followup:

1) you would need to research the SYLK file format to see if that is even
possible.  SYLK is open systems, portable, generic.  worksheets -- are not

2) "it doesn't work" is ambigous as "my car won't start -- why".  Sure it works,
we can write a big5 file -- can excel read it?

      Rating:
      5
Excellent.  May 20, 2003
Reviewer:
      Chandra S.Reddy  from India
Greate solution.

      Rating:
      4
  June 04, 2003
Reviewer:  Tamas
      Szecsy  from Budapest, Hungary
Just a small comment on my part: I faced the same problem and decided to the
other way: while it is somewhat more difficult and less portable to other
spreadsheets, I wrote a small package that writes Excel 2000 specific HTML file
with xls extension. This way I can make use of the many features of Excel - SYLK
is somewhat limited - and still need no OLE or Excel installed and can use
utl_file to write a simple ASCII file.

      Rating: 2
Here is my easy
      way to pump html to excel  August 10, 2003

Reviewer:  Yong Ke Wu  from USA
run this procedure with and without format parameter

create or replace procedure test(format varchar2:='excel') is
begin
  if format='excel' then
owa_util.mime_header('application/vnd.ms-excel');
  end if;
htp.p('<table><tr><td>ID</td><td>Name</td></tr></table>');
end;

      Rating: 5
pls read
      this  September 12, 2003
Reviewer:  suresh
       from India
  hi tom,

     i am generated excel report on web page by using "htp"
package(htp.p(parameters)), but i am interested on that excel sheet to highlight
the particular row border
 (not row data(cells)), how to do this ?

   pls reply immd ...

          Followup:

i will reply immediately that i don't use excel, so -- i don't know how to
"highlight" stuff in it :)

however, that said -- SYLK is an open file format -- documented.  google it!

      Rating: 5
pls reply immd
      web report in excel using htp.p only.  September 13, 2003

Reviewer:  Suresh  from India
hi tom,

     i am generated excel report on web page by using "htp"
package(htp.p(format string)), PL/SQL Cartridge and
Oracle 9iAS but i am interested on that web excel sheet to highlight the
particular row border(not row data(cells)), how to do this ?

   In PL/SQL Procedure i am using this statements...

   OWA_UTIL.MIME_HEADER('application/vnd.ms-excel');
       (Invoke excel format application)   ok,

    next i am using these statements :
             (what below statements means pls explain )

         htp.p('F;C1;FG0L;SM0');
       htp.p('F;C2;FG0R;SM0');

         htp.p('P;FArial;M200');
       htp.p('P;FArial;M200');

         htp.p('P;EArial;M200;L11');
       htp.p('P;EArial;M200;SB');

       htp.p('F;SDM6;R1'); ( i think this rows bold )
       htp.p('F;SDM6;R2');

   Imp :  and how to hightlight the particular row border in web report of excel
sheet ?.

    pls reply immd ... tom,

  Thanx
     suresh

          Followup:

please, hit page up and read that response.

      Rating: 4
Quck question..
      no 1st column  November 05, 2003
Reviewer:
      rd  from sweden
Hi,

I need a solution quickly.
So i am asking you if you can quickly point out how to not print the first
column.
i.e i dont want the sum column(1st column) at all.

TIA

          Followup:

the fastest way?

DON'T SELECT IT

hmmm?

      Rating: 5
Excellant BUT
      Getting an error :(  November 06, 2003
Reviewer:
      Arindam Mukherjee  from NY, USA
Tom,
  I tried to run the proc as shown below but got the following error. Please
help.
declare
    output utl_file.file_type;
begin
    output := utl_file.fopen( '/pdb01/dell/utl_file_dir', 'test.slk', 'w',32000
);

    owa_sylk.show(
        p_file => output,
        p_query => 'select * from rep_1072407PDB0001',
        p_parm_names =>
               owa_sylk.owaSylkArray( 'SHORT_NAME', 'SOURCE_ID', 'ACTIVE_RIDS',
'INACTIVE_RIDS', 'PRIOR_INACTIVE_RIDS',
                                     'ACTIVE_CIDS', 'PCT_ACTIVE_CIDS',
'TOTAL_INACTIVE_CIDS', 'PRIOR_TOTAL_INACTIVE_CIDS',
                                     'PCTCHG_TOTAL_INACTIVE_CIDS',
'INACTIVE_CIDS_REFR', 'INACTIVE_CIDS_EXPIRE',
                                     'INACTIVE_CIDS_USAGE',
'INACTIVE_CIDS_REFR_16', 'INACTIVE_CIDS_EXPIRE_16',
                                     'INACTIVE_CIDS_USAGE_16'),
        p_parm_values =>
                 NULL,
       p_sum_column =>
                 NULL,
        p_show_grid => 'NO' );

    utl_file.fclose( output );
end;

The following error has occurred:

ORA-06531: Reference to uninitialized collection
ORA-06512: at "DELL_WARE_OCT.OWA_SYLK", line 181
ORA-06512: at "DELL_WARE_OCT.OWA_SYLK", line 258
ORA-06512: at "DELL_WARE_OCT.OWA_SYLK", line 277
ORA-06512: at line 6

          Followup:

well, time to see what line 181, 258, 277 of your version of owa-sylk is :)

use a select again user_source to see -- it might be obvious.

      Rating: 4
Thnaks I got
      it  November 06, 2003
Reviewer:  Arindam
      Mukherjee  from NY, USA
Tom,
  I screwed I admit. The package runs but now I have a different problem. The
SYLK file that it makes is invalid, it says that Excell convert some of the
cells. Now after further debugging I got the root of the problem. Please refer
to your print_heading procedure, there you have a line as :  p('ID;ORACLE' );
which basically writes the first record as ID which according to the Standard is
for identifying the file as SYLK. If I comment off that then I don't get any
formats/fonts etc. But if I keep it and generate the file, although it gives me
the above mentioned error , but it keeps the fonts for the report. I am at a
total loss here. Could you help me in this regard.

Thanks
Arindam

          Followup:

go back to the original code -- unedited -- as provided.

can you reproduce with that?

if not, figure out what you changed to make it so.

if yes, then give us a test case -- create table, insert into table just enough
data, the call to owa_sylk.  then we can look at it.

      Rating: 5
I have got the
      crux!!  November 07, 2003
Reviewer:  Arindam
      Mukherjee  from NY, USA
Tom,
  I could figure out what was the problem. I changed the code which inserted
invalid characters in the SYLK. Sorry I troubled you. I have a related question
though.
In the package :
  procedure show(
      p_file          in utl_file.file_type,
      p_cursor        in integer,
      p_sum_column    in owaSylkArray  default owaSylkArray(),
      p_max_rows      in number     default 10000,
      p_show_null_as  in varchar2   default null,
      p_show_grid     in varchar2   default 'YES',
      p_show_col_headers in varchar2 default 'YES',
      p_font_name     in varchar2   default 'Courier New',
      p_widths        in owaSylkArray default owaSylkArray(),
      p_titles        in owaSylkArray default owaSylkArray(),
      p_strip_html    in varchar2   default 'YES',
      p_report_name      in varchar2,
      p_update_date      in varchar2,
      p_job_number      in varchar2  );

p_sum_column is an Table array. Now when we call it from anonymous block we have
to give the array elements as you have shown in your example. My problem is I
don't know upfront what will be the elements would be, only at runtime it will
be determined whether I want to sum on an column or not.  For eg. I'll call onto
a table to find out whether there is a particular flag set. If its set to 1 then
I'll sum on that column or else not. How can I possibly do this ? Or can it be
at all done ? Please help.

Regards
Arindam

          Followup:

yes, you can do this  -- arrays are just variables -- so in the same manner you
pass in different queries, you can fill an array variable with anything you want
and pass it in.

      Rating: 4
Help
      !!  November 08, 2003
Reviewer:  Arindam
      Mukherjee  from NY, USA
Tom,
  I tried quite a few ways but did not get it working. Could you help by giving
one example, I would appreciate any help from you. My goal is to pass an array
elements into the show() procedure from the anonymous block. Please help!!

Regrads
Arindam

          Followup:

you just declare an array variable and fill it up?  i don't see what is "hard"
or "obscure" here.

maybe you post an example of what you tried and we'll take a look at it.

declare
  l_var owa_sylk.owaSylkArray := owa_sylk.owaSylkArray();
begin
  l_var.extend( number_of_columns_in_your_query );
  for i in 1 .. l_var.count
  loop
    l_var(i) := 'N';
  end loop;
  -- now query your table and set the i'th element in the array to Y
  -- then pass it on in.

      Rating: 4
Thanks Tom
      BUT  November 08, 2003
Reviewer:  Arindam
       from NY, USA
carete or replace procedure CreateTest(p_table_name VARCHAR2)
 is
       TYPE VarcharList IS TABLE OF char(1);
       l_cursor     number := dbms_sql.open_cursor;
       output         utl_file.file_type;
 begin

v_qry_str := 'select sum_flag, '||
         'from test_header '||
         'where report_number = ' ||''''||report_number ||'''';

  OPEN v_column_name FOR
    v_qry_str;
       FETCH v_column_name BULK COLLECT INTO v_flag_list;
  CLOSE  v_column_name;

   output := utl_file.fopen( '\test\utl_file_dir', 'test.slk', 'w',32000 );

    dbms_sql.parse( l_cursor,
        'select * from '|| p_table name,
        dbms_sql.native );

    owa_sylk.show(
        p_file => output ,
        p_cursor => l_cursor,
        p_sum_column =>
            owa_sylk.owaSylkArray( v_flag_list),-- here is
    the problem I have to give something like v_flag_list(1) ,v_flag_list(2) but
I don't                                                -- know how many elements
will come here ???      p_show_grid => 'NO' );
    dbms_sql.close_cursor( l_cursor );
    utl_file.fclose( output );
end;

I have to pass the elements into the p_sum_column => owa_sylk.owaSylkArray(
v_flag_list) without even knowing how many elements would really come in to the
same. If I use the above it gives me an error. Could you help ? I know am doing
something wrong here.

Thanks
Arindam

          Followup:

the problem is YOU DO NOT have to give something like v_flag_list(1), ....

you have to FILL IN a variable v_flag_list (as demoed above) and then pass the
single array v_flag_list

(please -- begging you -- use bind variables -- at the very very very least, use
alter session set cursor_sharing=force!!!)

do you have a pl/sql programmer around?  someone whose used arrays before?

      Rating: 2
Adding header
      information to SYLK sheet  November 25, 2003

Reviewer:  Prem Kumaar  from India
I found the option of generating a excel(SYLK) format
from the below link
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:728625409049
But additionaly I would like to know if there is any possibility to put
additional header information to the sheets.

Eg.

Create User  : Scott
Created Date : 27-Aug-2003
Description  : Testing

col1   col2   col3  col4  col5
-----  ----   ----  ----  ----
A      B      C     D     E
F      G      H     I     J
..     ..     ..    ..    ..

So in the above format could you please let us know how to add any type of
headers as shown in the example( Created Date, Created User, Description etc).

Also is it possible to position the cell position to display contents eg
cell(1,3) 1st row, 3rd column.

Set background color for heading, setting allignments etc.

I could not find any information on google.com about the SYLK format, could you
please let me now.

Thankyou

          Followup:

absolutely there is

the cool thing is -- SYLK is an open system, documented, fully accessible to
you, me and everyone standard!

You could not find info about sylk on google????????  hmmm, wonder where i got
my info from?

i searched for

"sylk file format"

and got some meaningful hits on the very first page.

GFF Format Summary: Microsoft SYLK
The SYLK File Format SYLK format is an ASCII text file designed ...

for example where there, check them out.

      Rating:
      5
  December 29, 2003
Reviewer:
      A reader

November 25, 2003 Thiruvaiyaru -- Thanks for the question regarding
      "Problems with Order by in a query", version 8.1.6

      You Asked
Tom,

I have a question on "order by" on a query. Let me give u an
example of how the query looks :-

select empno||chr(9)||emp_name||chr(9)||join_date from temp
union
select empno||chr(9)||emp_addr||chr(9)||join_date from temp1
order by join_date

when i execute the query it says
ORA-01785: ORDER BY item must be the number of a SELECT-list
expression

I figured out sql*plus wasn't considering the || as a field
separator, and that the entire statement was considered as one
column. Can u tell me a way where i could have the order by
in this type of a query.

Thanks
Sai

      and we said...
(investigate whether you can use UNION ALL vs UNION).  Union all
= A+B.  UNION = A + ( b-a ) which is expensive to compute.)

select join_date, empno||chr(9)||emp_name||chr(9)||join_date
  from temp
union
select join_date, empno||chr(9)||emp_addr||chr(9)||join_date
  from temp1
 order by 1
/

or, if you can't deal with 2 columns,

select data
  from (
select join_date, empno||chr(9)||emp_name||chr(9)||join_date
data
  from temp
union
select join_date, empno||chr(9)||emp_addr||chr(9)||join_date
data
  from temp1
       )
 order by join_date
/

Review &
      Followup

      Rating:
      3
Ora-01785  November 24, 2003
Reviewer:
      Naveen  from India
Hi Tom,

Whenever i have a problem i seem to find the solution in your site. But the
problem i am posting is typical. I did find the solution for this as well in
your site. But i am not able to work it out because of the requirement.

Problem:

I have the below query for which i have to specify the Order by clause. Till now
i used to set the column position (say Order by1 etc). Now the requirement is
such that the sort has to be case insensitive. So for strings i tried to specify
Upper(column_name). But it is returning the ORA-01785. Kindly help me. I am
posting my query. Oracle version is 9.2.1.0. O/s WIN2000

SELECT c.contact_id, co.auth_level_ind, ic.last_name, ic.first_name,cc.name,
        (SELECT last_name ||', '|| first_name FROM SECURED_INTERNAL_USER sr,
CONTACT_OWNERSHIP ct
         WHERE sr.secured_user_id=ct.secured_user_id and
ct.auth_level_ind='Owner' and ct.contact_id=c.contact_id) "Owner",
        (SELECT dchoicename FROM CHOICELISTITEM WHERE dchoiceid=(select
country_id
        from location where contact_id=c.contact_id and address_indicator='A'))
"Country",
        c.email_address, c.telephone_no,to_char(c.record_date,'DD-Mon-YYYY'),
cc.company_contact_id,
        (SELECT cw.secured_user_id FROM CONTACT_OWNERSHIP cw WHERE
cw.auth_level_ind='Owner' AND cw.contact_id=c.contact_id)
FROM CONTACT c, INDIVIDUAL_CONTACT ic, COMPANY_CONTACT cc,
SECURED_INTERNAL_USER su, CONTACT_OWNERSHIP co
where c.contact_id=ic.contact_id
and ic.company_contact_id=cc.company_contact_id(+)
and c.contact_id=co.contact_id
and co.secured_user_id=su.secured_user_id
and c.current_status_ind='Active'
and (co.auth_level_ind='Designated Reader' OR co.auth_level_ind='Owner' OR
co.auth_level_ind='Designated Author')
and co.secured_user_id='AL3201'
UNION
SELECT c.contact_id, co.auth_level_ind, null,null, cc.name,
        (SELECT last_name ||', '|| first_name FROM SECURED_INTERNAL_USER sr,
CONTACT_OWNERSHIP ct
         WHERE sr.secured_user_id=ct.secured_user_id and
ct.auth_level_ind='Owner' and ct.contact_id=c.contact_id) "Owner",
        (SELECT dchoicename FROM CHOICELISTITEM WHERE dchoiceid=l.country_id)
"Country",
        c.email_address, c.telephone_no,to_char(c.record_date,'DD-Mon-YYYY'),
cc.company_contact_id,
    (SELECT cw.secured_user_id FROM CONTACT_OWNERSHIP cw WHERE
cw.auth_level_ind='Owner' AND cw.contact_id=c.contact_id)
FROM CONTACT c, COMPANY_CONTACT cc, LOCATION l, SECURED_INTERNAL_USER su,
CONTACT_OWNERSHIP co
WHERE c.contact_id=cc.company_contact_id
AND c.contact_id=co.contact_id
AND co.secured_user_id=su.secured_user_id
AND c.postal_address_preference=l.location_id(+)
AND c.current_status_ind='Active'
AND (co.auth_level_ind='Designated Reader' OR co.auth_level_ind='Owner' OR
co.auth_level_ind='Designated Author')
and co.secured_user_id='AL3201'
order by upper(cc.name)
SQL>/
order by upper(cc.name)
         *
ERROR at line 31:
ORA-01785: ORDER BY item must be the number of a SELECT-list expression

Thank you.
Nav

          Followup:

use an inline view

ops$tkyte@ORA920PC> select cc.ename
  2    from scott.emp cc
  3   union
  4  select cc.ename
  5    from scott.emp cc
  6   order by upper(ename)
  7  /
 order by upper(ename)
          *
ERROR at line 6:
ORA-01785: ORDER BY item must be the number of a SELECT-list expression

ops$tkyte@ORA920PC> select *
  2    from (
  3  select cc.ename
  4    from scott.emp cc
  5   union
  6  select cc.ename
  7    from scott.emp cc
  8         )
  9   order by upper(ename)
 10  /

ENAME
----------
ADAMS
ALLEN
BLAKE
CLARK
FORD
JAMES
JONES
KING
MARTIN
MILLER
SCOTT
SMITH
TURNER
WARD

14 rows selected.

ops$tkyte@ORA920PC>

      Rating: 3
GROUP BY and
      SORT  November 24, 2003
Reviewer:
      A reader  from NJ, USA
Tom,

I had the following query :

select src_intfc_inst, effective_date, count(*) from securitydbo.fx_rates
where effective_date > '10-NOV-2003'
group by src_intfc_inst, effective_date

src_intfc_inst    effective_date    count(*)
68    Nov-14-2003     27889
68    Nov-16-2003     30625
68    Nov-12-2003     27889
68    Nov-13-2003     27889
68    Nov-17-2003     30625
68    Nov-15-2003     30625

I was always under the impression that a GROUP BY in a query automatically sorts
the records (from some of the Oracle books I read earlier). But here the data is
not sorted according to src_intfc_inst and effective_date.
1. Can you explain me under what situations a GROUP BY performs an automatic
sort without the need to use the ORDER BY clause
2. Pls. clarify if my understanding is true or not

          Followup:

that is the wrong idea

one thing -- and one thing only -- sorts data

ORDER BY

without it -- all bets are off, we can return the data in any order we feel like
it.

It has always been so

It will always be so

there is no need to understand "when it will" since it could change it's mind at
the drop of a hat (eg: there are NO use cases you can "rely" on it sorting)

      Rating: 5
Thanks
      Tom..  November 24, 2003
Reviewer:  Naveen
       from India
Hi Tom,

Thanks. It worked. You are Great.

Nav

      Rating: 4
As an after
      thought..  November 25, 2003
Reviewer:
      Naveen  from India
Hi Tom,

How to specify the column that is used in inline query in the order by clause.
For example

select * from(
select c.contact_id,(select dchoicename from choicelistitem where
dchoiceid=(select country_id from location where
location_id=c.postal_address_preference)) from contact c)
order by upper(dchoicename)
/
how can i specify the Order by clause on dchoicename as the column is part of an
inline query. Kindly help me.

Thank you.
Nav.

          Followup:

alias the column

select *
  from ( select c.contact_id, (your SCALAR SUBQUERY) foobar
           from contact c )
 order by upper(foobar)

      Rating: 5
Thanks
      Tom..  November 25, 2003
Reviewer:  Naveen
       from India
Hi Tom,

Thanks again.It worked. Hope i'll get a chance to post a question to you
directly.

Thank you.
Nav.

November 25, 2003 Pushparaj -- Thanks for the question regarding
      "Flashback Query", version 9.2.0

      You Asked
Tom,

Can you please tell me if there is any performance penalty
on the database by enabling Flashback query feature.

Is it adivsable to enable this feature in a database where
more than 100 transaction tables will undergo lots of DML activities
by the application users everyday. Some of the tables are realy
a temporary tables which will be inserted and deleted within the same
transaction and so I really do not need this feature for those tables.

Is it possible to enable this feature only for certain tables
(only for very very important tables) instead for the whole database.

Thanks
Pushparaj

      and we said...
Surprise -- flashback query has been enabled in the database for many many many
years.  We call it Multi-versioning and it gives us our consistent read and
non-blocking reads.

That means -- you've been doing flashback query for years.

You do not need to "enable it", it is an intrisic feature/function of the
database that you cannot in any way "disable"!

It is ALWAYS on.

Review &
      Followup

      Rating: 5
Does Flashback
      Query require undo tablespace?  October 24, 2002

Reviewer:  Jeremy Smith  from Salt Lake City, UT

From what I understand, Flashback query requires that you use undo tablespaces,
or at least setting the UNDO_RETENTION parameter does.  Is that true?

If it is, doesn't using flashback imply that you're going to be using UNDO
tablespaces vs. RBS?  Or that you'd potentially be keeping undo around a lot
longer than you'd normally have to just to handle consistent reads.  Can either
of these two changes can affect application performance?  (the second,
obviously, means greater disk usage)

          Followup:

Nope -- not true.

With UNDO tablespaces you can deterministically say for how long you will be
able to flash back (undo_retention seconds ago at least)

with RBS's (old fashioned ones), it is a crap-shoot.  Maybe you can flashback
1/2 hour -- maybe not.

      Rating: 5
It was this
      section that makes it ambiguous...  October 24, 2002

Reviewer:  Jeremy Smith  from Jeremy Smith

From the 9i Application Developer's Guide, section 7:

<quote>
Setting Up the Database for Flashback Query

Before you can perform flashback queries, enlist the help of your DBA. Ask them
to:

Use automatic undo management to maintain read consistency, rather than the
older technique using rollback segments. In particular, the DBA should:

Set the UNDO_RETENTION initialization parameter to a value that represents how
far in the past you might want to query. The value depends on your needs. If you
only need to recover data immediately after a mistaken change is committed, the
parameter can be set to a small value. If you need to recover deleted data from
days before, you might need several days worth of data.

Set the initialization parameter UNDO_MANAGEMENT=AUTO.

Create an UNDO tablespace, with enough space to keep the required data. The more
often the data is updated, the more space is required. Calculating the space
requirements is a job for the DBA; you can find the formula in the Oracle9i
Database Administrator's Guide.
</quote>

It's unclear as to whether they're saying it's just a good idea or required to
use auto-undo-management.  That ambiguity has caused at least a few people to
declare it's required.  Of course, anyone writing an article should have
checked, but...

Thanks for clearing things up as usual.

          Followup:

Yes, that would be the best way (so you KNOW how far you can flash) but it is
not the ONLY way:

ops$tkyte@ORA920.US.ORACLE.COM> show parameter undo_m

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
undo_management                      string      MANUAL

ops$tkyte@ORA920.US.ORACLE.COM> connect scott/tiger

scott@ORA920.US.ORACLE.COM> update emp set sal = sal * 1.5;
14 rows updated.

scott@ORA920.US.ORACLE.COM> commit;
Commit complete.

scott@ORA920.US.ORACLE.COM> select a.ename, a.sal new_sal, b.sal old_sal
  2    from emp a, emp as of timestamp sysdate-5/24/60 b
  3   where a.empno = b.empno
  4  /

ENAME         NEW_SAL    OLD_SAL
---------- ---------- ----------
SMITH            1200        800
ALLEN            2400       1600
WARD             1875       1250
JONES          4462.5       2975
MARTIN           1875       1250
BLAKE            4275       2850
CLARK            3675       2450
SCOTT            4500       3000
KING             7500       5000
TURNER           2250       1500
ADAMS            1650       1100
JAMES            1425        950
FORD             4500       3000
MILLER           1950       1300

14 rows selected.

scott@ORA920.US.ORACLE.COM> update emp
  2     set sal = ( select sal from emp as of timestamp sysdate-5/24/60 b
where b.empno = emp.empno )
  3  /

14 rows updated.

scott@ORA920.US.ORACLE.COM> commit;
Commit complete.

scott@ORA920.US.ORACLE.COM> select ename, sal from emp;

ENAME             SAL
---------- ----------
SMITH             800
ALLEN            1600
WARD             1250
JONES            2975
MARTIN           1250
BLAKE            2850
CLARK            2450
SCOTT            3000
KING             5000
TURNER           1500
ADAMS            1100
JAMES             950
FORD             3000
MILLER           1300

14 rows selected.

scott@ORA920.US.ORACLE.COM>

shows that using old fashioned RBS works as well.

      Rating:
      3
  October 25, 2002
Reviewer:  Sagi
       from India
Hi Tom,

Would this work in 8i?

Because TIMESTAMP exists only in 9.

Regards,
Sagi

          Followup:

and so does flashback (only exists in 9i) so the question is somewhat moot.  No,
it will not work in 8i because flashback query didn't exist back then

      Rating:
      5
excellent  July 09, 2003
Reviewer:
      A reader
May be I am confusing things but how does this differ
from the workspace management ability to go to any
point of time and do a select.I know that workspace
offers much more but in terms of the ability to go back
in time and select records, are there any differences.
If we need to maintain history of table data can
we just use flashback. As I am typing in one thing that
comes to my mind is that flashback probably depends on
the undo retention periof where as WM does not?

Please elaborate.

And hats off to you for your awesome support to all of us!

          Followup:

flashback = hours, maybe days at most (but hours is practical)

flashback relies on undo being there.

workspace mgmt = the sky be the limit, the data is versioned in the table, keep
it for as long as you like.

      Rating: 5
one difference
      between workspace and flashback  July 09, 2003

Reviewer:  A reader
is that with workspace you could go back forever?

Is that true, Tom?

Thanx!

          Followup:

yes.

      Rating: 5
workspace
      management  July 10, 2003
Reviewer:  Reader

Could you please provide a link to learn how to implement using workspace
management? Thanks.

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-APP
application developers guide to the workspace manager.

      Rating: 5
flashback for
      CLOB and BLOB?  October 27, 2003
Reviewer:
      Reader
Tom, does flashback work for CLOB and BLOB also? Can I flashback to sometime if
somebody deleted rows containing BLOBs and CLOBs? Thanks.

          Followup:

yes, as long as the prior versions of the clobs exist.  lobs support read
consistency.

      Rating: 4
What about
      ALL_SOURCE?  November 25, 2003
Reviewer:  Andrew
      Clarke  from London, England
Someone on the OTN Forums suggested using Flashback Query against ALL_SOURCE as
a way of retrieving the text of an accidentally (and not backed-up) procedure.
However, it doesn't seem to work.

I would hazard that this is because ALL_SOURCE isn't really a table and data
wasn't deleted as such: it was erased as a side effect of an act of DDL.
Flashback query is not going to work in this case because the source code of the
procedure is not written to the undo tablespace.

Is this correct or do you have a better expalnation?

Cheers, APC

          Followup:

ops$tkyte@ORA920PC> select text from all_source where name = 'HK_ANALYZE';

TEXT
---------------------------------------------------------------------------------
--------------------------------------------------
procedure hk_analyze
(v_schema in varchar2,
 v_estimate_pct in number default 0,
....
    DBMS_OUTPUT.PUT_LINE('Analysointi alkoi: ' || start_time);
    DBMS_OUTPUT.PUT_LINE('Analysointi loppui: ' || stop_time);
end hk_analyze;

43 rows selected.

ops$tkyte@ORA920PC> drop procedure hk_analyze;
Procedure dropped.

ops$tkyte@ORA920PC> @connect "/ as sysdba"

sys@ORA920PC>  select text from all_source where name = 'HK_ANALYZE';
no rows selected

sys@ORA920PC>  select text from all_source as of timestamp sysdate-5/24/60 where
name = 'HK_ANALYZE';

TEXT
---------------------------------------------------------------------------------
--------------------------------------------------
procedure hk_analyze
(v_schema in varchar2,
 v_estimate_pct in number default 0,
 v_degree in number default 1)
...
    DBMS_OUTPUT.PUT_LINE('Analysointi alkoi: ' || start_time);
    DBMS_OUTPUT.PUT_LINE('Analysointi loppui: ' || stop_time);
end hk_analyze;

43 rows selected.

what issue did you encounter?

      Rating: 4
ALL_SOURCE
      revisited  November 25, 2003
Reviewer:  A P
      Clarke  from London, England
Apparently using Flashback Query against ALL_SOURCE works with SYSDBA accounts
but not mere mortals.  So my guess was wrong.

Does this mean the contents of data dictionary tables are written to UNDO when
we execute DDL?

Cheers, APC

          Followup:

absolutely -- 100%

November 25, 2003 Brian -- Thanks for the question regarding
      "extproc.exe causing memory problems on server", version 8.1.5

      You Asked
Tom,

We have a C++ DLL wrapped in an Oracle procedure that is
called by a trigger firing when a certain data condition
is met.  Our external procedure executes as/when expected,
except that we notice many extproc.exe processes build up
and eventually cause sluggish performance from our database
server (due to the high number of extproc processes).  Is there a
way to have all external procedure calls use the same extproc
or is there a way to force the process to close when
finished executing?

      and we said...
No there is not -- but there are other architectures that may well work nicely
in your environment.

One that comes to mind immediately is AQ (advanced queues).  Here you would
start "N" background processes (your code).  These backgrounds would listen on a
queue for messages (parameters).  When they recieve them -- they do whatever and
return an answer on a response queue.  Your plsql routine would

   enqueue a message
   wait to deque the response

instead of

   call a procedure

(well, it would call a procedure -- the procedure would enqueue/dequeue -- so no
code change really, the plsql wrapper would be rewritten).

In that fashion, you control exactly how many are used/running at any point in
time.

November 25, 2003 -- Thanks for the question regarding "Undo
      tablespace keeps growing", version 9.0.1

      You Asked
Tom,

I created an undo tablespace with a single datafile as
AUTOEXTEND ON MAXSIZE UNLIMITED.
The undo related init parameters are:
undo_management                      string      AUTO
undo_retention                       integer     900
undo_suppress_errors                 boolean     FALSE
undo_tablespace                      string      UNDOTBS

I ran a batch procedure overnight and the datafile grows from
100M to 3.5G.
My question is
I thought Oracle would reuse the undo space where
the transaction is over undo_retention time.
How do I know if I actually do need 3.5G undo space
or Oracle is not reusing the undo space where it should
be?
If I set a maxsize limit 2G, and when the datafile grows
to this limit, what will happen? Will it be forced to
reuse undo space or stop the transaction because of no
undo space.

Thank you,

      and we said...
We will not reuse space (wrap the rollback segment back around over itself)
while there is an active transaction there.

So, even though your undo retention is 15 minutes, if you have a 3 hour process,
a 3 hour transaction -- we will not (cannot) wrap around an reuse the space --
that rollback segment in the undo tablespace will continue to grow.

If you limit to 2gig -- you will get a failure to extend error and the
transaction will roll back.

3.5 gig of rollback for a long running batch process is not unreasonable, I've
seen much much larger.
Review & Followup

      Rating:
      5
  December 13, 2002
Reviewer:
      A reader
Is there a way to issue some SQL statement
to shrink the datafile for UNDO tablespace
after the batch process while the database is online?
I tried alter database datafile .. resize
But always get
ORA-03297: file contains used data beyond requested RESIZE value

          Followup:

why?  it is just going to grow again - it is a waste of time???  what is the
point here.  You will just end up growing it all over again tonight.

      Rating:
      1
  December 13, 2002
Reviewer:
      A reader
The batch process is run probably once a year or so.
I don't want to keep a huge undo file around for
the rest of the 364 days and I don't want to go out
to buy disks.
So please just treat it as a theoretical interest
and give me a straight answer.
Thank you,

          Followup:

ok, of theoretical interest.  Read the chapter in the admin guide on managing
undo.  there you can find out about

ALTER SYSTEM SET UNDO_TABLESPACE = ....

whereby you can create your tiny undo, switch to it, drop the old.

straight enough?

      Rating: 5
Oh,
      Tom!  December 13, 2002
Reviewer:  CJ
       from Albany, NY USA
Tom, how do you expect to get those 5-star ratings when you only give the
best/right answer? You need to start giving the answers people want! :->

      Rating:
      5
  December 13, 2002
Reviewer:  Steve
       from Concord, N.H.
Thank You Tom , you hit the nail on the head.  WHAT IS the point!! The Undo
tablespace is only going to grow again, if not tomorrow, then in 364 days when
the large batch file is executed again.....

      Rating: 5
3.5 Gig
      :-)  December 13, 2002
Reviewer:  Jan van
      Mourik  from Houston, Texas
http://www.amazon.com/exec/obidos/tg/detail/-/B00006EDOU/ref=cm_wl_ovu-pg.1-pos.1/104-8017983-8375956?v=glance&coliid=I12U118C2QB1J4

      Rating:
      2
  December 21, 2002
Reviewer:  hk
       from Finland
Original questioner asked also:
"How do I know if I actually do need 3.5G undo space
or Oracle is not reusing the undo space where it should
be?"

That is good question anyhow and it's still open here?

If undo_tablespace size is 100Megs and we run some batch job, which take 95megs
undo space. Now, batch job is done and undo_tablespace is 95% full. Then
immediately starts another transaction and it needs over 5 megs undo space. Is
undo_retention ignored and transaction reuses undo_space?

If undo_retention is not ignored and we have autoextend on, we may want decrease
undo_retention when we run many batch jobs. Or we have to configure dedicated
undo_tablespace for batch jobs.

          Followup:

it is not open -- they used 3.5 gig of undo space, they need 3.5 gig of undo
space. sorry, thought that was clear.

As for the 95% question -- IF you said "retain undo for 5 minutes" and 5 minutes
hasn't gone by -- well, we won't reuse it yet.

Or third option: you could size undo to satisfy your needs year round and be
done with it?

      Rating:
      4
  December 21, 2002
Reviewer:  hk
       from Finland
OK, it's not open - but if you handle it like new question: how do we know if
undo_tablespace is used for "actual" undo or if it is because of retention time
hasn't gone yet?

Thank's for the answering to "95% question". Why I asked this is because i have
heard undo_retention parameter is only "directive" and undo space is reused if
undo_tablespace is full regardless of value of undo_retention parameter.

          Followup:

it doesn't matter if it is used for "actual" undo or because of the retention
period -- it is all "actual" undo.

But anyway, select sum(used_ublk) from v$transaction will tell you how much undo
is being used for current, right now, transactions.

And -- allow me to clarify.  IF the undo tablespace can grow to accomidate the
undo retention period -- it will.  If it cannot -- it will not.  So consider
this example:

ops$tkyte@ORA920> @test

shows my undo tablespace is 1m right now.

The biggest it can autoextent to is 2gig and it'll grow in 1m increments (i know
that cause I created it that way, this report doesn't show that 1m increment)

                                                                  MaxPoss    Max
Tablespace Name        KBytes         Used         Free   Used     Kbytes   Used
---------------- ------------ ------------ ------------ ------ ---------- ------
...
*UNDOTBS                1,024          960           64   93.8  2,088,960     .0
.....
                 ------------ ------------ ------------
sum                 2,001,920    1,551,936      449,984

13 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> show parameter undo

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
undo_management                      string      AUTO
undo_retention                       integer     10800
undo_suppress_errors                 boolean     FALSE
undo_tablespace                      string      UNDOTBS
ops$tkyte@ORA920>
ops$tkyte@ORA920> drop table t;

Table dropped.

 my undo retention is 3 hours -- 10,800 seconds...

ops$tkyte@ORA920> create table t ( x char(2000), y char(2000), z char(2000) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( 'x', 'x', 'x' );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2          for i in 1 .. 500
  3          loop
  4                  update t set x = i, y = i, z = i;
  5                  commit;
  6          end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

now each of those transactions is 6+ kbytes of undo -- 3 * 2000 byte "before
images" to save off...  That should generate well over 3meg of undo by the time
it is done BUT in 500 tiny transactions.

If the undo retention period is 3hours and I have 1meg of undo and that 1meg of
undo can grow to 2gig -- Oracle will grow it and we can see that:

ops$tkyte@ORA920> set echo off

                                                                  MaxPoss    Max
Tablespace Name        KBytes         Used         Free   Used     Kbytes   Used
---------------- ------------ ------------ ------------ ------ ---------- ------
...
*UNDOTBS                5,120        4,608          512   90.0  2,088,960     .2
.....

13 rows selected.

the RBS is now 5m with 4.6 meg "used" (well, none of the undo is really used
right now, it is just going to sit there for 3 hours waiting to be reused).

Now I do this:

ops$tkyte@ORA920> create undo tablespace undotbl_new datafile size 1m;
Tablespace created.

ops$tkyte@ORA920> alter system set undo_tablespace = undotbl_new scope=both;
System altered.

ops$tkyte@ORA920> drop tablespace undotbs;
Tablespace dropped.

ops$tkyte@ORA920> exec print_table( 'select * from dba_data_files where
tablespace_name = ''UNDOTBL_NEW'' ' );
FILE_NAME                     :
/usr/oracle/ora920/OraHome1/oradata/ora920/o1_mf_undotbl__z0936pcx_.dbf
FILE_ID                       : 2
TABLESPACE_NAME               : UNDOTBL_NEW
BYTES                         : 1048576
BLOCKS                        : 128
STATUS                        : AVAILABLE
RELATIVE_FNO                  : 2
AUTOEXTENSIBLE                : NO
MAXBYTES                      : 0
MAXBLOCKS                     : 0
INCREMENT_BY                  : 0
USER_BYTES                    : 983040
USER_BLOCKS                   : 120
-----------------

PL/SQL procedure successfully completed.

And I rerun the test:

ops$tkyte@ORA920> drop table t;
Table dropped.

ops$tkyte@ORA920> create table t ( x char(2000), y char(2000), z char(2000) );
Table created.

ops$tkyte@ORA920> insert into t values ( 'x', 'x', 'x' );
1 row created.

ops$tkyte@ORA920> begin
  2          for i in 1 .. 500
  3          loop
  4                  update t set x = i, y = i, z = i;
  5                  commit;
  6          end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> set echo off
old  29: order by &1
new  29: order by 1

                                                             %  MaxPoss    Max
Tablespace Name        KBytes         Used         Free   Used   Kbytes   Used
---------------- ------------ ------------ ------------ ------  ------- ------
*UNDOTBL_NEW            1,024        1,024            0  100.0        0     .0

13 rows selected.

and here, we can see that the undo tablespace is still 1m.  Oracle could not
grow the undo -- but it did not fail the transactions.

So, in that respect, yes, the undo retention can be thought of as a "desire" --
if there is no way to get the undo space AND the undo space can be reused - it
will reuse it.  If the datafiles are autoextend or the undo tablespace is big
enough all by itself, it will not reuse it

      Rating:
      5
  December 21, 2002
Reviewer:  hk
       from Finland
Thanks Tom! I think this issue is very clear now.

      Rating: 5
Undo retention
      and flashback queries  February 11, 2003
Reviewer:
      Logan Palanisamy  from Santa Clara, CA, USA
Tom,

I was under the impression that, if the undo_retention=1800, I can "always"
flashback 30 minutes into the past.

Looks like that is not the case if the undo_tablespace reaches its maxsize and
can not grow any further, and hence the undo space is reused even before the
undo_retention time is reached.

Am I correct?

Thanks in advance

          Followup:

correct.

      Rating: 3
HELP WANTED,
      PLEASE.  June 14, 2003
Reviewer:  David
      Jiang  from Houton, Texas USA
Tom,

We got stuck with our import process with a huge database, one of the tables is
about 7 gig. In the middle of importing, it failed on this giant table with UNDO
table space reaching around 32.7 gig. We created another small, new UNDO table
space and tried drop the origianl UNDO table space but it give an error message
(SYSSMU7$ is in use).
I am using the following init file parameters for this instance:
undo_management=AUTO
undo_retention=180
undo_tablespace=UNDOTBS1
1. How do I shrink Originale UNDO tablespace back to 300 MB. Right now it shows
32.7 GB full and it is not getting empty?
2. How Do I get rid of the above original message?
3. What do I need to do to impor this very big table that my UNDO tablespace
size won't increase to around like 200 MB?
Also when I tried to switch new small UNDO tablespace, I got this error message
in alert log:
SMON: mark undo segment 7 as available
SMON: about to recover undo segment 7

          Followup:

1) wait for the rollback to finish, create a new undo tablespace, switch over to
it and drop the old.  the "busy" rbs will eventually become "unbusy"

2/3) commit=y on the insert might help.  sounds like you must be importing into
an indexed table -- suggest you don't do that.  import into an UNINDEXED table.
If you can, put the db in noarchivelog mode for the duration of the import
(avoids redo generation on the index creates) or create the indexes yourself
using NOLOGGIN

      Rating: 5
Peculiar
      Instance Behavior  July 09, 2003
Reviewer:
      A reader
Tom,

I executed a large number of direct load inserts, and the UNDO tablespace
extended to about 33gig.

My undo management is AUTO, and my UNDO_RETENTION was 10800.

I left work while it was still running, and when I came in the next day, I saw
that the machine was rolling redo logs (10m) about 1 every 2 seconds. So I
bumped the redo log sizes to 100m to reduce the overhead. Then I got to
thinking. What is Oracle doing... The loading was done. No one was connected to
the machine, and no jobs were running at all.
I looked in Top Sessions, and saw a P000 process executing 98% of the I/O, and
the system was under a load of about 25% of the single CPU.
I queried v$undostat and saw the TXNCOUNT column for one segment increasing
steadily.
I issued a shutdown immediate, but it hung obviously because the ongoing
processing needed to finish before shutdown.

I then changed the UNDO_RETENTION to 120, thinking that Oracle was performing
some sort of admin process on the vast amount of undo, and changing that value
may tell it to forgo that process.

It continued to spin through redos... ..   .

Can you give me an indication of what was going on?
If you need more info, just ask.
Thanks much

          Followup:

P000 is a pq slave, someone was running something somewhere.

      Rating: 4
UNXP* Columns
      in v$undostat  October 09, 2003
Reviewer:  Vivek
      Sharma  from Bombay, India
Dear Tom,

Could you please tell us the significance of UNXPSTEALCNT, UNXPBLKRELCNT,
UNXPBLKREUCNT columns in v$undostat. I am not able to get much information from
anywhere.

Metalink Note : 180946.1 says that

UNXPSTEALCNT
 NUMBER
 The number of attempts to obtain undo space by stealing unexpired extents from
other transactions.

UNXPBLKRELCNY
 NUMBER
 The number of unexpired blocks removed from certain undo segments so they can
be used by other transactions.

UNXPBLKREUCNT
 NUMBER
 The number of unexpired undo blocks reused by transactions

But what is the difference between the three.

Thanks in Advance.
Regards
Vivek Sharma

          Followup:

first is extents (extents are groups of blocks)

the second is blocks that were made available to be reused (since each extent
could have a different number of blocks...)

third is blocks actually reused

      Rating: 4
Different
      Number of Blocks in 2 Extents  October 10, 2003

Reviewer:  Vivek Sharma  from Bombay, India

Dear Tom,

Thanks for your response. It was very helpful.

But for your comments

"the second is blocks that were made available to be reused (since each extent
could have a different number of blocks...)"

How is it possible that 2 extents can different number of blocks. Since
undo_tablespace is created with a standard block size, suppose 4k, then an
extent size of 1 mb will have 256 blocks.

Please clarify my doubt to help me understand this concept in detail.

Regards
Vivek Sharma

          Followup:

but AUM uses locally managed tablespaces with system allocated extents.  They
are not UNIFORM.

      Rating: 5
simple undo
      quesiton.  November 05, 2003
Reviewer:
      A reader
case 1: Insert 1000 rows in a loop with an insert
  statement in between.

case 2: Insert same 1000 rows in a single insert statement.

The undo generated in case 1 is more than in case 2.
I know that row by row is same as "slow by slow" but
can you give me the exact reason what kind of overhead
causes this. It has to be some undo overhead with
each statement but what exactly is it?

thanx!

          Followup:

each statement will generate some undo just as "part of being"

1000 statements = 1000 "parts of being" vs just 1 "parts of being"

every statement generates some amount of undo and redo.  multiply that by 1,000

      Rating: 5
Cursor instead
      of for loop...  November 14, 2003
Reviewer:
      Steve  from Montreal
Hi Tom,

Platform:  Solaris 8 64 bit.  Oracle 9.2.0.1
=========
           UNDO TABLESPACE:  3 Gigs
           UNDI Management -> AUTO

I have your book and read very carefully on ORA-01555.
And I understand (at least I think I do) why it occurs
when committing inside cursors.

But I'm still a little confused why we sometimes get
"Unable to extend RBS" (even when no cursors are involved).
It's not consistent and we don't see a pattern.

So I ran some tests. First of all, I'm all alone
on the server and I have the following table  (T)
with 100 000 rows:

   SQL> desc t

    Name    Type
    ------- -----------
    A       CHAR(2000)
    B       CHAR(2000)
    C       CHAR(2000)
    D       CHAR(2000)
    E       CHAR(2000)
    F       CHAR(2000)
    G       CHAR(2000)

Then I did a bunch of statements as follows:

   SQL> update t set a=b, b=c,c=d,d=e,e=f;
   SQL> commit;
   SQL> update t set a=b, b=c,c=d,d=e,e=f;
   SQL> commit;
   SQL> update t set a=b, b=c,c=d,d=e,e=f;
   SQL> commit;
   .
   .
   .

While these were running, I was monitoring the RBS.
I saw the transactions go from one RBS to the next.
The active RBS would grow, somtimes as much as 800M.
After the commit, another RBS would be chosen and
grow, etc....

Eventually, it got to the point where more space was
needed for the current active transaction/RBS. That's
when I saw the other RBS start to shrink. So far so
good. In fact, I had trouble reproducing the
"Unable to Extend" error.

So I created a shell script that did the following

  LOOP
     print LOOP number
     Take snapshot of RBS
     update t set a=b, b=c,c=d,d=e,e=f where
         rownum < 50000;
     commit;
     Take snapshot of RBS
  END LOOP

I let it run all night. It took 30 iterations
before getting the first "Unable to Extend" error.
In the whole 545 iterations, it happened 41
times.

Since I'm alone on the system:

   o I don't see first of all why it is happenning
   o I don't see a pattern.

Can you please help me understand this one and also
understand when Oracle decides to shrink other RBS
to allow the current one to extend.

Thanks Tom.

Here's a snapshot the first 2 times when it occurs
(on Loop # 30 and 95).

==========================================

RUN Number 29

RBS Before:
===========

NAME          EXTENTS    RSSIZE  EXTENDS WRAPS   HWMSIZE
-----------  -------- --------- -------- ----- ---------
SYSTEM              7       .42        0     0       .42
_SYSSMU1$          79    589.11      469   657    792.17
_SYSSMU2$           2       .11      663   695   1717.98
_SYSSMU3$           2       .11      740   832   1743.98
_SYSSMU4$           2       .11      211   212    782.86
_SYSSMU5$          52    400.11      725   812   1733.98
_SYSSMU6$         103    789.11      903  1011   1759.17
_SYSSMU7$          89    671.11      918   939   1717.98
_SYSSMU8$          43    311.11      795  1033   1721.98
_SYSSMU9$          26    192.11      688   724    787.86
_SYSSMU10$          6     32.11      661   662   1728.98

49999 rows updated.

Commit complete.

RBS After:
===========

NAME          EXTENTS    RSSIZE  EXTENDS WRAPS   HWMSIZE
-----------  -------- --------- -------- ----- ---------
SYSTEM              7       .42        0     0       .42
_SYSSMU1$          63    461.11      469   657    792.17
_SYSSMU2$           2       .11      663   695   1717.98
_SYSSMU3$         199    787.86      937  1030   1743.98
_SYSSMU4$           3       .17      212   212    782.86
_SYSSMU5$          36    272.11      725   812   1733.98
_SYSSMU6$          88    669.11      904  1011   1759.17
_SYSSMU7$          71    539.11      918   939   1717.98
_SYSSMU8$          28    191.11      795  1033   1721.98
_SYSSMU9$          11     72.11      688   724    787.86
_SYSSMU10$          2       .11      661   662   1728.98

==========================================

RUN Number 30

RBS Before:
===========

NAME          EXTENTS    RSSIZE  EXTENDS WRAPS   HWMSIZE
-----------  -------- --------- -------- ----- ---------
SYSTEM              7       .42        0     0       .42
_SYSSMU1$          63    461.11      469   657    792.17
_SYSSMU2$           2       .11      663   695   1717.98
_SYSSMU3$         199    787.86      937  1030   1743.98
_SYSSMU4$           3       .17      212   212    782.86
_SYSSMU5$          36    272.11      725   812   1733.98
_SYSSMU6$          88    669.11      904  1011   1759.17
_SYSSMU7$          71    539.11      918   939   1717.98
_SYSSMU8$          28    191.11      795  1033   1721.98
_SYSSMU9$          11     72.11      688   724    787.86
_SYSSMU10$          2       .11      661   662   1728.98
update t set a=b, b=c,c=d,d=e,e=f where rownum < 50000
*
ERROR at line 1:
ORA-30036: unable to extend segment by 512 in undo tablespace 'UNDOTBS'

Commit complete.

RBS After:
===========

NAME          EXTENTS    RSSIZE  EXTENDS WRAPS   HWMSIZE
-----------  -------- --------- -------- ----- ---------
SYSTEM              7       .42        0     0       .42
_SYSSMU1$          81    565.17      487   737    792.17
_SYSSMU2$           2       .11      663   695   1717.98
_SYSSMU3$          92    715.11      937  1030   1743.98
_SYSSMU4$           2       .11      212   212    782.86
_SYSSMU5$          36    272.11      725   812   1733.98
_SYSSMU6$          87    661.11      904  1011   1759.17
_SYSSMU7$          70    531.11      919   939   1717.98
_SYSSMU8$          27    183.11      795  1033   1721.98
_SYSSMU9$          10     64.11      688   724    787.86
_SYSSMU10$          2       .11      661   662   1728.98

==========================================

.
.
.
All runs up to run # 95 worked. Here's run #95:
.
.
.

RUN Number 95

RBS Before:
===========

NAME          EXTENTS    RSSIZE  EXTENDS WRAPS   HWMSIZE
-----------  -------- --------- -------- ----- ---------
SYSTEM              7       .42        0     0       .42
_SYSSMU1$           2       .11     1064  1437    792.17
_SYSSMU2$          76    513.11     1498  1834   1717.98
_SYSSMU3$          90    672.11     1557  1945   1743.98
_SYSSMU4$          23    168.11      819   896    785.11
_SYSSMU5$          12     80.11     1311  1461   1733.98
_SYSSMU6$          56    424.11     1800  2545   1759.17
_SYSSMU7$           2       .11     1734  2211   1717.98
_SYSSMU8$          52    348.11     1594  2044   1721.98
_SYSSMU9$         230    787.86     1407  1452    787.86
_SYSSMU10$          2       .11     1476  1691   1728.98

update t set a=b, b=c,c=d,d=e,e=f where rownum < 50000
*
ERROR at line 1:
ORA-30036: unable to extend segment by 512 in undo tablespace 'UNDOTBS'

Commit complete.

RBS After:
===========

Session altered.

SYSDATE
-------------------
13-11-2003 19:33:47

NAME          EXTENTS    RSSIZE  EXTENDS WRAPS   HWMSIZE
-----------  -------- --------- -------- ----- ---------
SYSTEM              7       .42        0     0       .42
_SYSSMU1$           2       .11     1065  1437    792.17
_SYSSMU2$          68    449.11     1498  1834   1717.98
_SYSSMU3$          80    595.11     1557  1945   1743.98
_SYSSMU4$          14     96.11      819   896    785.11
_SYSSMU5$           3      8.11     1311  1461   1733.98
_SYSSMU6$          47    352.11     1800  2545   1759.17
_SYSSMU7$         159    566.98     1891  2368   1717.98
_SYSSMU8$          41    266.11     1594  2044   1721.98
_SYSSMU9$          86    659.11     1407  1452    787.86
_SYSSMU10$          2       .11     1476  1691   1728.98

      Rating: 3
ORA:-
      30036  November 14, 2003
Reviewer:  Arindam
       from NY,USA
Hi Tom,
 I got this opportunity to ask you this question regarding tablespace:
Oracle version :
Oracle9i Enterprise Edition Release 9.2.0.2.0 - 64bit Production
PL/SQL Release 9.2.0.2.0 - Production
CORE    9.2.0.2.0    Production
TNS for Solaris: Version 9.2.0.2.0 - Production
NLSRTL Version 9.2.0.2.0 - Production

UNDO Tablespace :- 10 gigs.

Now my question is we needed to update a table(200M records) . The table
definition is :-

CREATE TABLE WARE_PROMO
(
  CONTACT_ID             NUMBER(20),
  SITE_ID                NUMBER(20),
  CORP_ID                NUMBER(20),
  CONTACT_ID_PROMO       NUMBER(20),
  CONT_NAME_FLAG         CHAR(1),
  BUS_NAME_FLAG          CHAR(1),
  ZIP4_FLAG              CHAR(1),
  POBOX_FLAG             CHAR(1),
  HIGHRISE_FLAG          CHAR(1),
  BUS_RES_FLAG           CHAR(1),
  QUALITY_CODE           CHAR(2),
  STATE                  CHAR(2),
  ZIP                    CHAR(5),
  GENDER                 CHAR(1),
  PREFERENCE_FLAG        CHAR(1),
  CAMPAIGN_ID            NUMBER(6),
  VERSION_ID             NUMBER(8),
  SEGMENT_ID             NUMBER(10),
  LIST_SRC_ID            NUMBER(8),
  ALLOC_SRC_ID           NUMBER(8),
  PROMO_SUB_TYPE         CHAR(1),
  LIST_NUM               NUMBER(4),
  DOMS_KEY_CODE          NUMBER(5),
  CO_NUM                 CHAR(2),
  HOLDOUT_FLAG           NUMBER(1),
  CONT_EMAIL_FLAG        CHAR(1),
  CONT_REP_FLAG          CHAR(1),
  ASSOC_SRC_COUNT        NUMBER(2),
  COMPILED_SRC_COUNT     NUMBER(2),
  COMARKET_SRC_COUNT     NUMBER(2),
  DELL_SRC_COUNT         NUMBER(1),
  RENT_IN_SRC_COUNT      NUMBER(2),
  RENT_OUT_SRC_COUNT     NUMBER(2),
  PRIORITY_SRC_GROUP     NUMBER(1),
  CONT_MULTIBUYER        NUMBER(2),
  LAST_CONT_UPD_IN_DB    DATE,
  MOS_SINCE_LAST_DB_UPD  NUMBER(2),
  CONT_DELL_BUS_SEG      CHAR(1),
  CONT_DELL_DART_SEG     CHAR(1),
  MOS_CONT_ACTIVE        NUMBER(2),
  CONTS_PROMO_PER_SITE   NUMBER(2),
  SITE_REVENUE           NUMBER(8),
  CONT_LAST_PROMO_DATE   DATE,
  OUT_OF_BUS_FLAG        CHAR(1),
  CONT_DELL_UNITS        NUMBER(5),
  CONT_DELL_REVENUE      NUMBER(8),
  CONT_DELL_STATUS       CHAR(1),
  SITE_DELL_STATUS       CHAR(1),
  PENETRATION_NUM        NUMBER(3),
  FIRST_LOB_PURCH        CHAR(3),
  DECILE_SCORE_ABI       CHAR(2),
  DECILE_SCORE_DUNS      CHAR(2),
  SIC4_CODE_FINAL        CHAR(5),
  PCSPEND_FINAL          NUMBER(8),
  NUM_EMP_FINAL          NUMBER(4),
  NUM_EMP_FINAL_CODE     CHAR(1),
  PURCH_FLAG             CHAR(1),
  COUPON_FLAG            NUMBER(1),
  BACKFEED_FLAG          NUMBER(1),
  ALC_ORD_NUM            CHAR(5),
  STREET_DATE            DATE,
  RECENCY_NUM_MOS        NUMBER(2)
)
TABLESPACE DELL_WARE_TABLE_8M
PCTUSED    0
PCTFREE    0
INITRANS   1
MAXTRANS   255
NOLOGGING
PARTITION BY HASH (CONTACT_ID_PROMO)
  PARTITIONS 128
  STORE IN (DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M, DELL_WARE_TABLE_8M,
DELL_WARE_TABLE_8M)
NOCACHE
NOPARALLEL
ENABLE ROW MOVEMENT;

We have indexes on bitmap contact_id/site_id/corp_id.
We have to update the column holdout_flg based on 14 values of version_id
column. if match then update holdout_flg = 1 else 0.
We tried to do in three different ways as listed below :-
i) update ware_promo
   set holdout_flag = 0;
   /
   update ware_promo
   set holdout_flag = 1
   where version_id in (134,3456,136576....14 values)
ii)Writing PL/SQL (this one written by our DBA)
   DECLARE
  v_version_id   ware_promo.version_id%TYPE;
  v_holdout_flag ware_promo.holdout_flag%TYPE;
  v_counter NUMBER(10) := 1;
CURSOR c_cursor IS
  SELECT version_id, holdout_flag
  FROM ware_promo
  FOR UPDATE OF holdout_flag;
BEGIN
  FOR v_ANYTHING in c_cursor LOOP
    FETCH c_cursor INTO v_version_id, v_holdout_flag;
    IF v_version_id in (
      3090702,
      3100123,
      3100124,
      3100125,
      3100126,
      3100404,
      3100405,
      3100502,
      3100603,
      3100703,
      3100704,
      3110119,
      3110120,
      3110122)
    THEN
      UPDATE ware_promo
      SET holdout_flag = 1
      WHERE CURRENT OF c_cursor;
    ELSE
      UPDATE ware_promo
      SET holdout_flag = 0
      WHERE CURRENT OF c_cursor;
    END IF;
    v_counter := v_counter + 1;
    IF MOD(v_counter, 10000) = 0 THEN
      COMMIT;
    END IF;
  END LOOP;
  CLOSE c_cursor;
  COMMIT;
END;
/
EXIT

iii) Writing PL/SQL (written by another developer)

DECLARE
 TYPE t_ref_cur IS REF CURSOR;
 TYPE NumList IS TABLE OF NUMBER;
 v_ref_cur      t_ref_cur;
 v_ver_id_list            NumList;
 v_hold_flag_list        NumList;

BEGIN

          UPDATE ware_promo
            SET holdout_flag = 0;

          commit;
 open v_ref_cur for
  select version_id,holdout_flag
  from ware_promo
  where version_id in (3090702,3100123,3100124,3100125,3100126,3100404,3100405,

3100502,3100603,3100703,3100704,3110119,3110120,3110122);

       fetch v_ref_cur bulk collect into v_ver_id_list, v_hold_flag_list LIMIT
1000;

       forall i in 1 .. v_ver_id_list.count

          UPDATE ware_promo
            SET holdout_flag = 1
          WHERE version_id =v_ver_id_list(i);

       commit;
END;
/
EXIT

ALL OF THE PROCESSES FAILED GIVING ORA:-30036 which means we ran out of UNDO.
i)   My question is why it "BOMBS OUT"?
ii)  What could have gone wrong ?
iii) Is our 10 Gigs of UNDO really SHORT ? Which on
iv)  What is your adevice on what way we should  approach in achieving the same
?   in the above theree example .
v) I have suggested to create a temp table having all the version_id's and
holdout_flag updated and then use MERGE to get the BASE table updated. What is
your opinion on this ?

PLEASE HELP !!!
Thanks
Arindam

          Followup:

the right way:

update ware_promo
   set holdout_flag = case when version_id in ( .... ) then 1 else 0 end;

10gigs may well be way too small, especially if there are lots of other
transactions going on in there using rbs as well (especially long running ones).

      Rating: 5
Comment on
      previous question  November 15, 2003
Reviewer:
      Steve  from Montreal
Hi Tom,

I was wondering if you could comment on the question before Arindam 's (before
it gets burried in this thread...)

Thanks Tom

Steve

          Followup:

i've got a test case setup waiting to run overnight :)

that one takes a bit of working to reproduce.  they are huge transactions.  I
let it run for 4 hours yesterday while I was meeting with someone (it sort of
consumes my machine doing those massive updates)....  didn't hit the problem.
trying with a longer test now.

      Rating:
      5
Thanks!  November 15, 2003
Reviewer:
      Steve  from Montreal
Thanks Tom, I really appreciate it.

          Followup:

well, i ran overnight -- i have a rather small machine, i was not able to
reproduce -- but I could only do 62 iterations (not enough -- but I sort of
needed my machine back :)

i'll have to try again later when I have more cycles.

      Rating: 5
Great
      BUT...  November 15, 2003
Reviewer:  Arindam
       from NY,NY
This is an datawarehouse and no other transaction is going on when THIS UPDATE
is taking place. We que up every process of update and each one is done
sequentially. Do you still think the UNDO is kind of small ?
Please advice ?

Thanks
Arindam../

          Followup:

in one word -- yes, apparently it is.

that 10gig is used by a couple (more then one) rbs.  you can only use one rbs.
10gig for a DW seems pretty small to me.  let it grow (and use the SINGLE
update, not the two step, not the procedural code)

      Rating: 5
I'm also
      getting ORA-30036  November 18, 2003
Reviewer:
      Linda  from Ann Arbor, MI USA
This is a great site!  Thanks for explaining things so well (I'm amazed at your
patience!).

I'm testing a year-end update that updates lots of columns in a large table. We
commit the update when the summary type and level change (part of the primary
key), so the transactions have different sizes. If I use a small undo tablespace
(250MB) and undo_retention is set to 10800, the program dies with ORA-30036
after less than a minute(52 transactions were committed and 61,246 records were
updated).  I watch v$rollstat, v$undostat and dba_free_space while the process
is running and see that some unexpired undo is being reused.  I've run this test
several times and always get the error, although not always at the same spot.

When I reset undo_retention to 180, the program runs fine.  With a larger undo
tablespace and undo_retention set to 1800 it works some of the time.

Steve from Montreal's test would probably fail faster with a smaller undo
tablespace and (possibly with) smaller transactions.  I'm testing with a small
test database and my program fails every time with a 250MB undo tablespace and
undo_retention set to 10800.

Thanks!

          Followup:

your undo tablespace is just plain "too small" for what you've asked for!

      Rating: 2
Sorry, now I'm
      confused  November 24, 2003
Reviewer:  Linda
       from Ann Arbor, MI
My program works with a short undo_retention (180), but fails with a longer
undo_retention (10800).  If the undo tablespace was too small, wouldn't it also
fail with a short undo_retention?

          Followup:

[tkyte@tkyte-pc tkyte]$ oerr ora 30036
30036, 00000, "unable to extend segment by %s in undo tablespace '%s'"
// *Cause:   the specified undo tablespace has no more space available.
// *Action:  Add more space to the undo tablespace before retrying
//           the operation. An alternative is to wait until active
//           transactions to commit.
[tkyte@tkyte-pc tkyte]$

with a small undo retention, we didn't have to save as much, no need to extend
-- we are not trying to keep undo -- we reuse it fast.

you asked to save a boatload of undo -- we grew the undo segments.  Eventually
-- we needed to grow some undo segment you were using but were unable to and
hence you got the 30036 error.

when you said "don't save lots of undo", we never really grew or needed to grow
the undo segments so we did not.  hence, plenty of space for when we did need to
grow them in reaction to a transaction.

      Rating: 1
Yes, I think I
      understand how it is supposed to work  November 25, 2003

Reviewer:  Linda  from Ann Arbor, MI
I think that my case is similar to Steve from Montreal's case, but I want to
make sure that I'm not missing something.  I'm alone on the system.  There is
only one transaction at a time.  My understanding is that if there's not enough
expired undo, the undo_retention 'suggestion' is ignored, and some of the
unexpired undo is used.

I recreated the undo tablespace at 250M with autoextend off and left
undo_retention set to 10800.  I ran my test this morning and again monitored the
undo info.  This time the program finished, so I was wrong when I said that it
always failed (I'd only run it twice before - my bad).  I monitored several
things while the program was running.  Here is one of the selects:

SQL> select status, count(1)
  2  from dba_undo_extents
  3  group by status;

STATUS      COUNT(1)
--------- ----------
EXPIRED          170
UNEXPIRED         26

Started the program ---

STATUS      COUNT(1)
--------- ----------
ACTIVE            21
EXPIRED           14
UNEXPIRED        332

SQL> /

STATUS      COUNT(1)
--------- ----------
ACTIVE            26
EXPIRED           14
UNEXPIRED        317

SQL> /

STATUS      COUNT(1)
--------- ----------
ACTIVE            25
EXPIRED           15
UNEXPIRED        325

I think this shows that some of the unexpired extents were used (we didn't reach
the undo_retention time during the program).

After the program finished:
STATUS      COUNT(1)
--------- ----------
EXPIRED            1
UNEXPIRED        374

So, I ran the program again.  This time it failed on an update of 18,800 rows.
A transaction of 18,784 had completed a minute earlier and many smaller
transactions had completed in between.

SQL> l
  1  select status, count(1)
  2  from dba_undo_extents
  3* group by status
SQL> /

STATUS      COUNT(1)
--------- ----------
EXPIRED            1
UNEXPIRED        415

Questions:

The error is:
Error:   ORA-30036: unable to extend segment by 128 in undo tablespace
'UNDOTBS3'

1. Is this 128 bytes, blocks, or what?  If there isn't room to extend the
tablespace, does Oracle look for an unexpired undo segment of the size it wants
to expire?

2. Should I be watching other things?

Thanks.

Linda

          Followup:

just remember -- you are NEVER the only one on in Oracle - NEVER.  backgrounds
are always there too. but anyway.

we can only expire UNDO in the current rollback segment you are using, the other
N rollback segments -- for your transaction -- don't count.

counting extents doesn't tell us much -- we use system allocated extents, you'd
want to look at the SIZE of the rbs's -- not the number of extents they are in.

your rollback segments (you have many in there) are all of different sizes (they
grew at different rates).  the one you got when it failed just "was not large
enough" and there was no more room to extend it.  The other rbs's -- didn't even
come into consideration at that point in time!

      Rating: 5
Thanks - now I
      understand!  November 25, 2003
Reviewer:
      Linda  from Ann Arbor, MI

November 25, 2003 oracle -- Thanks for the question regarding
      "week", version 8.1.7

      You Asked
Tom

How was your vacation. Where did you go.

Now

I want to retrieve all the weekdates, week days for a whole year. The routine
should take into consideration the difference in the number of days in different
months.

Iam having problem becasue Iam not able to cover for months having more the 30
days.

can you help me.

when I say weekdates I mean dates and when I said week days I meant mon, tue
etc.

I dont want the weekends.

      and we said...
Well, I don't understand what the difference between weekdates and week days are
-- a DATE can be converted using to_char into anything you want.

Are you looking for a query that returns to you all of the days in a year minus
weekends??? If so,

select trunc(sysdate,'year')+r-1
  from (select rownum r from all_objects where rownum <= 366)
 where to_char(trunc(sysdate,'year')+r-1,'Dy') not in ( 'Sat', 'Sun' )
   and to_char(trunc(sysdate,'year')+r-1,'yyyy') = to_char(sysdate,'yyyy')
 ;

will give you all of the weekdays in a given year (change SYSDATE to be a date
in any year).  Change 'Sat', 'Sun' to be the spelling of the weekend days in
your language of course.  Use to_char on the "trunc(sysdate,'year')+r-1" to
extract the week days "mon", "tue" and so on.
Review &
      Followup

      Rating:
      4
  April 02, 2002
Reviewer:
      A reader

      Rating: 5
Related
      question, about days counting.  June 12, 2002

Reviewer:  Ian Matyssik  from Japan
I was searching all your answers and could not find that one, I think it is
really easy but after 1week+ of time to time playing with it still can not get
it.
My question is:
If I know D.O.B. of the person and would like to know when his/her next birthday
is, how many days till next birthday and how old person is.

So far I came up with following query but still could not be Feb 29 safe:

SELECT
        id,
        name,
        to_char(dob,'SYYYY - Month, DD - DAY') as DOB,
        DECODE (UPPER(sex), 'F', 'Female',
                     'M', 'Male') as Sex,
        floor(((sysdate - dob)*24*60*60)/3600)
        || ' HOURS ' ||
        floor((((sysdate - dob)*24*60*60) -
        floor(((sysdate - dob)*24*60*60)/3600)*3600)/60)
        || ' MINUTES ' ||
        round((((sysdate - dob)*24*60*60) -
        floor(((sysdate - dob)*24*60*60)/3600)*3600 -
        (floor((((sysdate - dob)*24*60*60) -
        floor(((sysdate - dob)*24*60*60)/3600)*3600)/60)*60)))
        || ' SECS ' as \" Time Alive\",
        round(sysdate - dob) as \"Days alive.\",
        floor((MONTHS_BETWEEN(sysdate,dob))/12) as \"Years old.\",
        round(
                (
                round(
                        (sysdate + numtoyminterval( 1 , 'year'))
                        ,'year'
                        ) -
                round(
                        sysdate,
                        'year'
                        )
                ) * (
                ceil(
                        (MONTHS_BETWEEN(sysdate,dob) ) / 12
                        ) -
                        (MONTHS_BETWEEN(sysdate,dob) ) / 12
                )
                , 2) as \"Days till next Birthday\"
FROM
        people
ORDER BY
        dob DESC

Some of it was taken from the web. If it is not hard could you please give me
some help.

Thank you.

          Followup:

this answer is specific to 9i and up -- which I see you have by the use of the
numtoyminterval function

You can use this as well:

SELECT
        id,
        name,
        to_char(dob,'SYYYY - Month, DD - DAY') as DOB,
        DECODE (UPPER(sex), 'F', 'Female',
                     'M', 'Male') as Sex,
        (sysdate-dob) day(5) to second "days/hours/mins/seconds",
        (sysdate-dob) year to month "Years old",
        round(
        case when (to_char(dob,'dd-mm') = '29-02')
             then case when to_date(last_day(to_date('01-02','dd-mm'))) <
sysdate
                       then
add_months(to_date(last_day(to_date('01-02','dd-mm'))),12)
                       else to_date(last_day(to_date('01-02','dd-mm')))
                  end
             when to_date(to_char(dob,'dd-mm'),'dd-mm') < sysdate
             then add_months(to_date(to_char(dob,'dd-mm'),'dd-mm'),12)
             else to_date(to_char(dob,'dd-mm'),'dd-mm')
        end - sysdate ) days_to_birthday
FROM
        t
ORDER BY
        dob DESC
/

it is feb-29 'safe' -- and it gives basically equivalent information

      Rating: 5
Thank you so
      much on that, it is really amazing and simple!!!  June 12,
      2002
Reviewer:  Ian Matyssik  from Japan

      Rating: 4
Calender
      Query  June 13, 2002
Reviewer:  Praveen
       from India
Tom,

I want to create my own calender query. Suppose I want to display a calender for
ten days of Jan-2002 (sysdate is 13-Jun-2002). I wrote the query as

select
decode(to_char(trunc(sysdate,'year')+r-1,'Dy'),'Mon',to_char(trunc(sysdate,'year'
)+r-1,'dd'),null) Mon,

decode(to_char(trunc(sysdate,'year')+r-1,'Dy'),'Tue',to_char(trunc(sysdate,'year'
)+r-1,'dd'),null) Tue,

decode(to_char(trunc(sysdate,'year')+r-1,'Dy'),'Wed',to_char(trunc(sysdate,'year'
)+r-1,'dd'),null) Wed,

decode(to_char(trunc(sysdate,'year')+r-1,'Dy'),'Thu',to_char(trunc(sysdate,'year'
)+r-1,'dd'),null) Thu,

decode(to_char(trunc(sysdate,'year')+r-1,'Dy'),'Fri',to_char(trunc(sysdate,'year'
)+r-1,'dd'),null) Fri,

decode(to_char(trunc(sysdate,'year')+r-1,'Dy'),'Sat',to_char(trunc(sysdate,'year'
)+r-1,'dd'),null) Sat,

decode(to_char(trunc(sysdate,'year')+r-1,'Dy'),'Sun',to_char(trunc(sysdate,'year'
)+r-1,'dd'),null) Sun
   from (select rownum r from all_objects where rownum <= 10)

The result which I got is :

MO TU WE TH FR SA SU
-- -- -- -- -- -- --
   01
      02
         03
            04
               05
                  06
07
   08
      09
         10

10 rows selected.

How can I bring all the days for a week in a single line, as from the above
query its displaying every day on the next line. Is anything wrong in the
query?

          Followup:

Well, we can use the IW format to determine the week of the year and use that to
group by:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select dt, to_char(dt,'iw'),
  2             decode( to_char(dt,'d'), 2, to_char(dt,'dd'), null ) Mo,
  3             decode( to_char(dt,'d'), 3, to_char(dt,'dd'), null ) Tu,
  4             decode( to_char(dt,'d'), 4, to_char(dt,'dd'), null ) We,
  5             decode( to_char(dt,'d'), 5, to_char(dt,'dd'), null ) Th,
  6             decode( to_char(dt,'d'), 6, to_char(dt,'dd'), null ) Fr,
  7             decode( to_char(dt,'d'), 7, to_char(dt,'dd'), null ) Sa,
  8             decode( to_char(dt,'d'), 1, to_char(dt,'dd'), null ) Su
  9    from ( select trunc(sysdate,'year')+rownum-1 dt
 10                     from all_objects
 11                    where rownum <= 10
 12             )
 13  /

DT        TO MO TU WE TH FR SA SU
--------- -- -- -- -- -- -- -- --
01-JAN-02 01    01
02-JAN-02 01       02
03-JAN-02 01          03
04-JAN-02 01             04
05-JAN-02 01                05
06-JAN-02 01                   06
07-JAN-02 02 07
08-JAN-02 02    08
09-JAN-02 02       09
10-JAN-02 02          10

10 rows selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> column week noprint
ops$tkyte@ORA817DEV.US.ORACLE.COM> select to_char(dt,'iw') week,
  2             max(decode( to_char(dt,'d'), 2, to_char(dt,'dd'), null )) Mo,
  3             max(decode( to_char(dt,'d'), 3, to_char(dt,'dd'), null )) Tu,
  4             max(decode( to_char(dt,'d'), 4, to_char(dt,'dd'), null )) We,
  5             max(decode( to_char(dt,'d'), 5, to_char(dt,'dd'), null )) Th,
  6             max(decode( to_char(dt,'d'), 6, to_char(dt,'dd'), null )) Fr,
  7             max(decode( to_char(dt,'d'), 7, to_char(dt,'dd'), null )) Sa,
  8             max(decode( to_char(dt,'d'), 1, to_char(dt,'dd'), null )) Su
  9    from ( select trunc(sysdate,'year')+rownum-1 dt
 10                     from all_objects
 11                    where rownum <= 10
 12             )
 13   group by to_char(dt,'iw')
 14   order by 1
 15  /

MO TU WE TH FR SA SU
-- -- -- -- -- -- --
   01 02 03 04 05 06
07 08 09 10

(beware -- I thing 'd' might return different numbers in some languages -- but
it is more portable then using 'mon', 'tue' and so on)...

      Rating: 4
Does IW
      conflict with NLS_TERRITORY ?  November 25, 2003

Reviewer:  Doug  from CT, USA
According to note 176914.1, an NLS_TERRITORY of AMERICA implies that weeks start
on Sunday.  The iw format looks like they start on Monday even with this
setting. Am I seeing this right?

          Followup:

IW is the ISO week -- it is whatever ISO told us it should be.

November 25, 2003 Mark -- Thanks for the question regarding "What's
      a freelist?", version 8i

      You Asked
Hi Tom,

Speaking as someone who did C/Pro*C development for several years,
and now being new to the DBA world, I'm trying to get a better handle
on Oracle's internal storage model and the datastructures used to
control it.

I've been doing some research trying to understand freelists and
freelist groups: what they are, when and how they are used.

In my reading, I ran across a MetaLink paper (DocID: Note:1029850.6)
and I'm afraid it's done more to confuse than clarify the issues.

In particular, under the heading "Transaction Free Lists algorithm",
it discusses an algorithm that sounds much like the algorithm used
in managing the Interested Transaction List on a data block.

I guess I'm confused.  A "Transaction Free List" is different than
an "Interested Transaction List", right?

In summary:

What's a freelist?
What's a freelist group?
What's the difference between a transaction freelist, process freelist,
and a master freelist?

Do any of them have anything to do w/ the Interested Transaction List
on a datablock?

Thanks for any insight you can offer!

-Mark
Mark J. Bobak
ProQuest Company
Oracle DBA
mbobak@proquest.com
mark@bobak.net

      and we said...
It is easier to understand if you ignore the algorithms for the most part.

Every table has some free space.  The free space is either BELOW the high water
mark (the point at which data has been inserted into the table) or ABOVE the
HWM.  Blocks above the HWM are "just there" -- they are not managed on a free
list of any sort -- we just know that every block above block N in a given
extent is above the HWM (and hence, free to be used).

The free space BELOW the HWM is maintained in freelists.  By default a segment
has 1 freelist.  All blocks belonging to a segment that have sufficient free
space on them (pctfree and pctused control when blocks come on and off of the
freelist) are in this freelist.  When you goto INSERT a new row (or a row
migrates due to an update), we will goto this freelist to find a block for you
to insert into.  If your insert makes the block go over its pctfree, that block
will be removed from the freelist -- else it'll stay on there.

Now, getting blocks off of the freelist is a serial process.  In order for
multiple processes to concurrently access and modify this linked list, we must
ensure only one session at a time modifies it.  Hence, in a heavy insert/update
system -- the single freelist can become a point of contention -- one data
structure for ALL inserters.  This is where multiple freelists come into play.
Oracle will take the free list and break it into many freelists.  You are
assigned a freelist for a segment in some fashion and other sessions will use
other freelists for that same segment (sort of like how we balance transactions
across rollback segments).  Now, instead of having one data structure for 5
concurrent inserts into a table -- we could have five data structures -- no
contention now.

In my book -- i demonstrate this with a multi-user example showing the before
and after effects of using multiple freelists.  The result can be quite
dramatic.  Even though we permit (naturally) concucrrent inserts into a table --
there can be some serious free list contention in a heavily inserted into table,
like an audit trail or such.  Setting freelists to the estimated number of
concurrent insert request can dramatically improve performance.

Ok, so thats for single instance Oracle -- we can take a single freelist for a
single segment and break it into multiple freelists for that segment, increasing
concurrency.  Now, look at Oracle Parallel Server where there is more then one
instance mounting and opening the same database.  This is where freelist groups
come into play.

Freelist groups allow us to allocate freelists to specific instances -- to
reduce cross-instance contention for space.  This allows instance 1 to have its
freelists for its sessions, instance 2 to have its freelists for its sessions --
and they will not have to coordinate with eachother on space allocation.

The transaction free lists, while interesting technically, are not really
relevant to you and me.  The are temporary holding places for blocks our
transactions free up and will eventually be reused.

Review &
      Followup

      Rating: 5
Just Excellent
      Reply  July 12, 2001
Reviewer:  KP
       from India
Yr reply is excellent

      Rating: 5
Now I know what
      a freelist is!  July 12, 2001
Reviewer:  Mark J.
      Bobak  from Ann Arbor, MI
Thanks Tom, for an excellent response, and the quick
turnaround time on my question.

      Rating: 5
Freelists
      explained --- Freed from headache  July 12, 2001

Reviewer:  Aslam Momin  from Woodbridge, NJ USA

Tom, this is an excellent -technical response.
You are simply Great !

      Rating:
      4
Terrific  July 12, 2001
Reviewer:
      Rajesh Hassija  from FL, USA
No better words can be said to explain free lists.

      Rating:
      5
Excellent  July 12, 2001
Reviewer:
      Murthy P. N.  from Department of Education,Columbus, OH, USA

Thats an excellent explanation.

      Rating: 5
Dot excellent
      .... but one question  July 13, 2001
Reviewer:
      Vidyesh  from Pune , India
That makes very clear. For total clear I have one question.

In parallel server:

 If one of the instance needs more space than at present
 available in the freelist group, what happnes???

 Does a new space is acquired eventhough on total basis,
 there is a free space available ?
                       OR
 Oracle breaks the available freelist group(s) and makes
 the space available ??

 Thanks in advance

      Rating:
      4
gr8  July 13, 2001
Reviewer:  Vijay
       from Bangalore, India
This is excellent reply. Cleared all the doubts reg. freelists.
Thanks Tom you are doing good job.

      Rating: 4
excellant
      explanation. no comment  July 13, 2001
Reviewer:
      A reader

      Rating: 4
Pretty
      Helpful  December 18, 2001
Reviewer:  VS
       from Philadelphia

      Rating: 4
How to set
      Freelists - Feedback requested  February 07, 2002

Reviewer:  Pawan  from USA
Thanks Tom for the wonderful explanation. I understand it much better now.
However I have a question. In a Data Warehouse environment ( running on N class
HP box with HP UX 11 and 64 bit Oracle 8.1.7 with 20 GB SGA) I perceive that
loading data is very slow ( relative ?). The DBA says he can increase the
FREELISTS but then I want to know if there is an upper limit to what it should
be set before there no further gain in performance. I think I read somewhere
that we should optimally set it to 20 for heavy inserts. What is yr opinion and
how does one go about calculating what the FREELIST (and may be INITRANS) should
be set to. Assume that only 5 users are using the datawarehouse (1.% TB in size)
and there is only user inserting data (500 million records) into a NON PARTIONED
table.
Thanks

          Followup:

you should set it to the number of load processes (so the advice to set to "20"
is not correct UNLESS you just happen to be using that number of load
processes).

Loading data in a data warehouse is typically done in direct path mode, meaning
freelists are not even considered or used.  Use the direct path loader and
freelist contention is not an issue (and the load goes faster)

One user loading -- 500 million records -- freelists are NOT your issue.

      Rating: 5
" very very
      uderstandable, plain language.  February 09, 2002

Reviewer:  oralearner  from us
1. i need to know the name of your book you mentioned in your answer.
2. is the breaking up of freelist equal, what if one needs more space.
3.  please explain a bit ---If your insert makes the block go over its pctfree,
that block will be removed from the freelist -- else it'll stay on there---

please answer

          Followup:

1)
http://www.amazon.com/exec/obidos/ASIN/1861004826
2) space, when freed, goes into a "pool", as each freelist runs out of room --
it'll grab a couple (like 5) blocks from this "pool" and put them on its
freelist.  So, the free space is allocated across the freelists as needed.

3) well -- it is pretty straightforward.  If there is to be 10% "free" on the
block for updates and your insert makes it go over that 10% free, that block is
removed from the freelist -- its no longer a candidate for subsequent
insertions, its removed from consideration.  After a delete or an update (that
shrinks a row), it may go back onto the freelist after getting under the pctused
threshold.

      Rating: 5
Very
      nice  February 10, 2002
Reviewer:  Abdal-Rahman
      Agha  from Sudan
Hi Tom,
  Its really what we need always, "to make these Oracle concepts simpler". I am
really appreciate your nice help to explain what, I think, Oracle Documentations
couldn't make it simple. Hay Tom, I will be back asking questions after this
long period in which I didn't send you a question.
THANX

Abdal-Rahman Agha,
Developer & DBA

      Rating:
      5
  July 16, 2002
Reviewer:
      A reader
Can you show me how to check a table to see if I need to add freelist to it.
Our application is mostly read except for one table CHANGELOG.  It only gets
inserts but does have two indexes.  Rows inserted average about 500 - 1000 rows
an hour.  But can get hit real heavy during peak times.

Thanks

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:865627341240

      Rating: 4
how about the
      indexes?  October 22, 2002
Reviewer:  Prince

If I increase the number of freelists on a table, do I need to increase the same
on ALL the indexes of that table?

Thanks,

          Followup:

pretty much, yes.

      Rating:
      5
  October 22, 2002
Reviewer:  Bhagat
      Singh  from Delhi,India
Although I have fair Idea about the freelist from your book only, but the answer
was excellent and I say I have learnt more out of it.

      Rating:
      5
  October 23, 2002
Reviewer:  Dave
       from Ohio
In order to pack as many rows as possible into a data warehouse fact table,
where there will be no future updates or deletes, do we need to be careful not
to be too aggressive with pctused?

Suppose that due to the  TS block size and table row length the table cannot be
filled to more than 98.5%, for example. Presumably a pctused 99 would result in
having many blocks on the freelist that cannot accept another row, and
potentially degraded insert performance might be experienced (except with INSERT
/*+ APPEND */ of course).  Should we check that the number of blocks on the
freelist(s) of such tables is not unexpectedly high?

          Followup:

It won't be too much of an issue unless you update the data after inserting it.
The block will be taken off and not put back on.  Consider something simple like
this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( a char(2000), b char(2000),
c char(2000) ) pctused 99 pctfree 0;

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2          for i in 1 .. 100
  3          loop
  4                  insert into t values ( 'x', 'x', 'x' );
  5          end loop;
  6          commit;
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select count( DISTINCT
dbms_rowid.rowid_block_number( rowid )) from t;

COUNT(DISTINCTDBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID))
---------------------------------------------------
                                                100

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec show_space( 'T' )
Free Blocks.............................4
Total Blocks............................128
Total Bytes.............................1048576
Unused Blocks...........................24
Unused Bytes............................196608
Last Used Ext FileId....................7
Last Used Ext BlockId...................28041
Last Used Block.........................40

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> update t set a=b;

100 rows updated.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec show_space( 'T' )
Free Blocks.............................103
Total Blocks............................128
Total Bytes.............................1048576
Unused Blocks...........................24
Unused Bytes............................196608
Last Used Ext FileId....................7
Last Used Ext BlockId...................28041
Last Used Block.........................40

PL/SQL procedure successfully completed.

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

See how the blocks after the insert are not on the freelist until AFTER the
update?

      Rating:
      4
  October 23, 2002
Reviewer:  Dave
       from Ohio
That's a great demonstration. I ran your script and got pretty much the same
numbers, and noted that when another hundred rows are added it drops Free Blocks
right down again, giving ...

SQL> exec show_space( 'T' )
Free Blocks.............................13
Total Blocks............................224
Total Bytes.............................1835008
Unused Blocks...........................22
Unused Bytes............................180224
Last Used Ext FileId....................6
Last Used Ext BlockId...................5769
Last Used Block.........................10

Here's my understanding of this, perhaps you could let me know if this is
correct ...

i) If a block is on the freelist, but Oracle finds that there is not enough
space to add another row, then it is taken off the freelist.

ii) During a subsequent update operation it is noted that the "actual" pctused
is less than the table pctused of 99, and the blocks affected by the update go
back on the freelist.

iii) So nothing to worry about, except if a table maintenance operation requires
an update to all the rows (even if they do not get longer), and the blocks could
be cleared off the freelist (if you were really fussy about this) by the insert
and delete of a single row?

          Followup:

yes -- and if you use ASSM (auto segment space mgmt) in 9i -- all bets are off
cause the algorithms are all *totally* different for space management ;)

      Rating: 5
Thanks! One
      more topic in which I have much better and clearer
      understanding!  October 23, 2002
Reviewer:
      Robert  from Memphis, USA

      Rating:
      5
Freelists  January 12, 2003
Reviewer:
      sush  from SJ,CA
Tom...Thanks for the excellent answers.Once when I increased the freelists to 8
as I'm running 8 concurrent sessions to load the data into a table.A colleague
of mine(who always suggests to work out of the box)  tells look we are using LTS
it will automatically take care of freelists and told me that I don't need to
increase the freelists to 8.Please comment whether this is a true statement.

Thanks Sush

          Followup:

what is an LTS?

do you mean "LMT" for locally managed tablespace?  If so, that does not do
anything for freelist contention.

if you mean "ASSM" for automatic segment space management -- then you are
correct, it will remove the need to fine tune the freelists, but it is only
available in 8i and up -- and you don't have a version listed and ASSM is not
even close to LTS so I don't know.

      Rating:
      5
freelists  January 12, 2003
Reviewer:
      sush  from sj,ca
yes tom it is LMT..(locally managed tablespace)...

          Followup:

LMTs don't do freelists -- so freelists would still be something to look at in
8i.  In 9i, you can consider ASSM.

      Rating: 5
Could you
      please explain the following in this connection:  January
      14, 2003
Reviewer:  A reader
"we must ensure only one session at a time modifies it. Hence, in a heavy
insert/update system -- the single freelist can become a point of contention --
one data
structure for ALL inserters. "

1.When two or more sessions are under contention how long is the contention? I
mean a session estimates how many blocks to reserve for its insert and yield for
the next session eager to modify the freelist for insertion? Hopefully it does
not wait for the first session's insertion to complete.

2. Can Sessions for Deletes also cause contention. Or is it that such sessions
do not make any modification to the free list because the freed blocks are
returned to pool?

Thanks

          Followup:

1) short, brief.  not the life of the transaction -- just long enough to get the
free block, modify it, and let someone else use it.

2) inserts/updates grab from the freelist.  deletes just throw things back onto
a master freelist.  contention for deletes -- a little but since it is just
putting ONTO the freelist, it is very very short

      Rating: 3
New Query
      raised by your answer !  April 01, 2003
Reviewer:
      Asim Ahmed.  from Karachi, Pakistan.
Hi Tom,
I'm an Oracle 8i DBA in KHI-Pakistan. I frequently visit this site just to
increase my knowledge base. your recent answer refreshed many concepts regarding
FreeLists. but raised a trivial one ! How does Oracle cope with the problem of
managing multiple freelists because if multiple inserts are using multiple
freelists, then any one of them can potentially damage the integrity of
underlying data structures by allocating a block on FreeList to the request they
are working for ? same question arises in case of FreeLists groups.
thanks in advance for this query. i hope u'll give a response.

          Followup:

we use latches to coordinate (eg: serialize) access to the freelists.  That is
the point of multiple freelists actually -- that way we can have many freelists
being concurrently used.  No more then one session is grabing (modifying) a
given freelist at a time however.  lightweight latches are used to protect these
structures.

freelist groups spread it out even more is all.  but not to worry -- we protect
these shared datastructures -- just like we protect the structures in the SGA
and all.

      Rating: 5
Can set
      freelist a higher number for all the tables?  April 02, 2003

Reviewer:  A Reader
Hi Tom,
Can we simply set freelist for all the tables and their indexes in the database
to a higher number, say 20 (Oracle default is 1).  If so, what is the drawback
of doing so?  If not, why and how big the effect would be?
Thank you.

          Followup:

space utilization.  There could be 95 free blocks (5 each on the other 19
freelists) that you would not reuse in your session -- and so you might cause
the table to extend un-necessarily.  so, it could use space less efficiently.

      Rating:
      4
freelist  April 06, 2003
Reviewer:
      Vikas Sharma  from Delhi India
Hi Tom,

Thanks for explaining. Pl clear me the following.

If I have a table in which the heavy data is getting inserted for about 100000
to 500000 but only buy one user/session. Then if I alter the table to 2 freelist
then will I have performance gain?

What if the data is more and there are concurrent insert by two or more
user/session then I should increase the freelist.

Regards,

Vikas Sharma

          Followup:

multiple freelists are good for concurrent inserts by separate sessions.

If you have but one session -- only one freelist will be used.

If you have more then one session all inserting, you would want more then one
freelist to help spread the work around and descrease waits for blocks with room
to insert into.

      Rating:
      4
Thanks  April 06, 2003
Reviewer:  Vikas
      Sharma  from Delhi India
Hi Tom,

Thanks for immediate response.

Reagrds,

Vikas Sharma

      Rating: 3
More about this
      "pool" ?  April 06, 2003
Reviewer:  Christo
      Kutrovsky  from Ottawa, ON Ontario
<quote>
All blocks belonging to a segment that have sufficient free
space on them (pctfree and pctused control when blocks come on and off of the
freelist) are in this freelist.
</quote>
later on:
<quote>
2) space, when freed, goes into a "pool", as each freelist runs out of room --
it'll grab a couple (like 5) blocks from this "pool" and put them on its
freelist.  So, the free space is allocated across the freelists as needed.
</quote>

Can you explain more about this speacial "pool". Why have this "pool" and
freelists ?

And also, what happens if you delete a large amount of rows? Does this mean that
there will be lots of "blocks" used for the freelists ?
Will this overwhelm the freelists ?

          Followup:

the freelists contain blocks with sufficient space for new inserts on them.
Whether a block is on the freelist is controlled by pctused and pctfree (or with
auto-segment space mgmt in 9i, it is controlled by internal algorithms)...

In order to ensure maximum concurrency -- you may have more then one freelist.
In this fashion, more then one session at a time may be pulling blocks off of
the freelist of the segment at the same exact point in time.  If you had but one
freelist, the sessions would momentarily serialize at the freelist as this
shared data structure cannot be modified by more then one session at a time
(hence, by having more then one freelist, you can have more then one session
pulling from a freelist concurrently).

Now, the question is how to manage blocks that are updated or deleted from and
must go onto the freelist.  If we simply put them on one of these freelists --
one freelist could easily end up with ALL of the free blocks -- and other
sessions would not be able to use them.

So, we put them on a "master freelist" from which the other process or session
freelists pull from.  Each freelist pulls a couple blocks at a time from this
master freelist -- spreading the free space around.

Think of it like the security checkpoint at the airport.  You have 5 metal
detectors.  One master line feeds the 5 metal detectors and as you get closer to
them, you are routed to one of the five lines.  If one of the five lines is
moving slowly (that would be the one I'm in if history keeps repeating itself),
it consumes less blocks (people) from the master line.  If one of the five lines
is moving really fast (usually the line found next to the one I get put into),
it consumes blocks faster.  So, you end up with no single long line at a metal
detector -- the wealth of blocks is spread out over all possible "freelists"
(metal detectors)

Overwhelm is a strong term -- it will not overwhelm (that would imply "breaks").
 It will however put all of those blocks onto the freelist, yes.

      Rating: 4
Aha
      !  April 07, 2003
Reviewer:  Christo
      Kutrovsky  from Ottawa, ON Ontario
Aha, I understand now.

The question was more oriented to the size of the master pool, but this clears
it enought.

When you say, that it'l "grab a couple (like 5) blocks " I now figured out that
you ment 5 blocks containing free rowids, not 5 free rowids.

I think my imagination of the exact way this is happening is close enought to
the real one.

Thaks again Tom.

      Rating: 5
Default 1
      Freelist per segment. How about partition?  July 08, 2003

Reviewer:  Chuan Sat  from VA USA
Tom

    You mentioned that the default is one freelist per segment.  For a table
with partition and subpartition that are being inserted through conventional
path, are all the pararell partition insert processes share one segment
freelists?  Or is there one free list per partition and subpartition?

Thanks

          Followup:

in the case of partition tables -- each partition is the segment, each will have
its own freelists.

      Rating: 4
free lists
      ???  July 09, 2003
Reviewer:  Reader
       from India
Hi Tom.

How can i add more free lists to my table. Can u tell me the syntax ? If, table
is currently using.

          Followup:

ops$tkyte@ORA920> alter table t storage ( freelists 5 );

Table altered.

      Rating:
      5
Syntax  September 04, 2003
Reviewer:
      Raj  from USA
Hi Tom,

What is the syntax to add freelist for a current read/write only partition in a
partitioned table?

Alter table <table name> modifty partition <partition name> storage ( freelists
<n> );

Something like this?

          Followup:

ops$tkyte@ORA817DEV> alter table t modify partition part1 storage (freelists 20
);

Table altered.

(could have been figured out just the way i did it -- by "trying it" or by
reading it........)

      Rating: 5
Thanks, Tom.
      This was the best Article I've seen on FreeLists.  September
      24, 2003
Reviewer:  A reader  from Ohio

      Rating: 5
Can freelists
      span multiple blocks  November 25, 2003
Reviewer:
      Arun Gupta  from Harrisburg, PA USA
I have some questions about freelists:

a) When documentation says that the block goes on freelist, which attribute of
block is recorded in the freelist? Is it the database block address? How much
space (bits/bytes) does this occupy?

b) Are all the freelists, master, process and transaction contained within one
block which is the segment header?

Thanks

          Followup:

a) the dba (data block address).  it is a file/block combo.  just a couple of
bytes.

b) they can span blocks as the size of the number of "free" blocks is somewhat
unlimited (bigger then would fit on a block)

      Rating:
      5
  November 25, 2003
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
Tom,
Please see this posting on Metalink from Jonathan Lewis which prompted my
question:
<quote>
TX locks in mode 4 can appear because freelists
is too high. The segment header block has space
for a fixed number of freelist structures which
are split between SEG and XCT lists. The SEG
lists get called the Process Free Lists (and
the number of these is set by the FREELIST parameter)
plus one Master Free list. The XCT free lists are
the remainder and get called transaction free lists.

When a transaction makes enough free space in a
block that the block has to go 'on the free list',
it has to go on a transaction free list until the
transaction commits. So if lots of transactions
free up space in blocks concurrently without committing
you need lots of transaction free lists - and if you
run out of transaction freelists, then the next new
transaction to free some space has to wait for one
of the current transactions to commit/rollback and
release its transaction free list. The waiting
transaction picks a transaction freelist, and waits
on the holder using a mode 4 TX lock.

However, since there are (I think) about 80 structures
in a 4K block (and I guess at least twice that in an
8K block), you have to set freelists very high, or
have a lot of concurrent deletes going on to see this
happen. (It can be engineered quite easily, of course).
</quote>

This implies that there is only one block available for freelists, so if I
increase the process freelists, transaction freelists can potentially run out of
space. The Metalink article 1029850.6 is also very informative, but I still
cannot understand that how they say that freelists occupy only one block? Please
bridge this gap in my understanding. Has something changed between 8i and 9i
(except for ASSM)?

Thanks

          Followup:

no, it implies there is a root block that points to other blocks and this root
block can in excessively extreme cases be a point of contention.

it does not imply "there is only one block for freelists"

think of it like a root index block, the one we all must start at in order to
access index data.

November 25, 2003 Reddy -- Thanks for the question regarding "Do I
      have to change CHAR(1) to CHAR(1 CHAR)", version 9.0.1

      You Asked
I have a table which is expected to hold multi-lingual data.

Part of the table definition.

STATEMENT_STATE
STATEMENT_STATE_ID
STATEMENT_NAME    VARCHAR2(50 CHAR)
STATEMENT_STATE_CHANGE    CHAR(1)
State change info. Allowed values 'C' (created), 'U' (updated) and 'D'
(deleted).
STATEMENT_PENDING_IND    CHAR(1)
Pending Status Indicator Y Yes pending  N Not pending
:
:

Check Constraints
CKSTATEMENT_PENDING_IND   -->     STATEMENT_PENDING_IND IN ('Y', 'N')
CKSTATEMENT_STATE_CHANGE  -->    STATEMENT_STATE_CHANGE IN (’C’,’U’,’D’)

Database NLS_LANG set to UTF-8.

We use a multitier Unicode solution. i.e  all-UTF8 architecture. Similar to the
one explained in 9.0.1 Oracle documentation ref. # a90236, Globalization Support
Guide ref nls81030.gif

My question is do I have to change CHAR(1)  to CHAR(1 CHAR) ?

What value will be populated in the db if a Japanese client selects 'Y' in the
on-line.
Is that still 'Y' or equivalent Japanese character?

I guess following should be considered.

Restrictions due to constraint
·    Check Constraint will not allow other values in that column

Code restrictions
In the code I guess we always use actual English characters.
IF STATEMENT_PENDING_IND = ’N’
    DO SOME THING
ELSE
    DO NOTHING.

Thanks
Reddy

      and we said...
You don't have to but if you hit a language where C, U or D takes more then one
byte -- you will WISH you did.

It would be a good idea to use the char(N char) and varchar2(N char) everywhere
in new development.
Review & Followup

      Rating: 5
One
      clarification please  July 12, 2002
Reviewer:
      reddy ks  from Atlanta, GA USA
I guess, the character Y goes into db as Y or C as C.
But it may take more than 1 byte if a Japanese client or a Chinese client
enter's the value instead of English Client.

      Rating: 5
Which Data
      Dictionary object stores CHAR ?  July 12, 2002

Reviewer:  Reddy KS  from Atlanta, GA USA
I was able to see the details (in specific CHAR ) in SQL Plus when I did

SQL>desc STATEMENT_STATE

But when I do select * from cols, it is not showing CHAR.

These are the additional cols in 9.0.1 vs 8.1.7.
CHAR_LENGTH
CHAR_USED
V80_FMT_IMAGE
DATA_UPGRADED

But none of these columns are storing the string CHAR.
Where do Oracle stores CHAR.

Thanks
Reddy

          Followup:

quick peek into the documentation for the meaning and we find (surprisingly)

CHAR_USED VARCHAR2(1)
B | C.

B indicates that the column uses BYTE length
semantics. C indicates that the column uses CHAR length
semantics. NULL indicates the datatype is not any of the
following:

n CHAR
n VARCHAR2
n NCHAR
n NVARCHAR2

so, char_used is what you are looking for and a B or C will tell you Bytes or
Characters.

      Rating: 4
char(1) and
      char (1 char)  December 24, 2002
Reviewer:
      Brijesh
Mr. Tom I downloaded you presentation slides of oracle 9i

http://asktom.oracle.com/~tkyte/9iClass/index.html
in that demo0013.sql gave me the following :

demo@ORA92> @E:\mak\oracle\ask-tom\Presentations\9iTrain\9iTrain\demo013
demo@ORA92>
demo@ORA92> create table t ( a char(1), b char( 1 char ), c char(2000 char) );

Table created.

demo@ORA92>
demo@ORA92> insert into t (a) values ( unistr( '\00d6') );

1 row created.

demo@ORA92>
demo@ORA92> insert into t (b) values ( unistr( '\00d6') );

1 row created.

demo@ORA92>
demo@ORA92> select length(b), lengthb(b), dump(b) dump from t;

 LENGTH(B) LENGTHB(B)
---------- ----------
DUMP
---------------------------------------------------------------------------------
-----------

NULL

         1          1
Typ=96 Len=1: 63

demo@ORA92>
demo@ORA92> insert into t (c) values ( rpad( unistr( '\00d6'), 2000,
unistr('\00d6') ) );

1 row created.

demo@ORA92>
demo@ORA92> insert into t (c) values ( rpad( unistr( '\00d6'), 1001,
unistr('\00d6') ) );

1 row created.

demo@ORA92>
demo@ORA92> insert into t (c) values ( rpad( unistr( '\00d6'), 1000,
unistr('\00d6') ) );

1 row created.

demo@ORA92>
demo@ORA92> drop table t;

Table dropped.

But according to your slides when you enter a unicode character which is more
than one byte into a char(1) column it should give an error which is'nt the case
here.

My DB CharSet is :
AMERICAN_AMERICA.WE8MSWIN1252

Any comments,
Thanks.

          Followup:

You need a UTF8 database for that demo to make sense.

sorry that was not more clear on these two slides:

http://asktom.oracle.com/~tkyte/9iClass/img23.html
It is in the sql prompt and it is the reason why I have a cut and paste of the
sqlplus sesssion on those slides.  Unless you have a UTF8 database -- the demo
won't show you anything as the characters will all be convered to single byte
data in your database anyway.

      Rating:
      3
OK  November 25, 2003
Reviewer:
      Mary  from Boulder,USA
Dear Sir,
I tried the following code snippet but got errors.Could you
please correct this? and is there any other way that this
code can be made to work?
SQL> begin
  2   for c in chr(ascii('a')) .. chr(ascii('z')) loop
  3    dbms_output.put(c);
  4   end loop;
  5   dbms_output.put_line(' ');
  6  end;
  7  /
begin
*
ERROR at line 1:
ORA-06550: line 2, column 11:
PLS-00364: loop index variable 'CHR' use is invalid
ORA-06550: line 2, column 2:
PL/SQL: Statement ignored

I want the alphabets a..z to be printed.Could you please help?
Thanks in advance

          Followup:

that loop is not really any differen then:

ops$tkyte@ORA920PC> begin
  2     for c in 'a' .. 'z'
  3     loop
  4        dbms_output.put_line( c );
  5     end loop;
  6  end;
  7  /
begin
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at line 2

 you need to iterate over a NUMBER.  perhaps this is what you want:

ops$tkyte@ORA920PC> begin
  2     for i in ascii('a') .. ascii('z')
  3     loop
  4        dbms_output.put_line( chr(i) );
  5     end loop;
  6  end;
  7  /
a
b
c
d

      Rating: 3
Are you
      sure...  November 25, 2003
Reviewer:  Nick
       from Richmond, VA
"It would be a good idea to use the char(N char) and varchar2(N char) everywhere
in new development."

This statement seems to violate the concept of using data types to help enforce
integrity. If I have a field that I know will never hold anything other than US
ascii I would use varchar2(N) and if I have a field than will be used to store
international characters I would use varchar2(N char).

Our application under development right now is a perfect example of this. We use
many alphanumic codes and these are all defined as varchar2(N). International
characters as codes wouldn't make sense to us. We have other fields that can
store names from around the world and these are varchar2(N char). Isn't this an
appropriate approach?

          Followup:

if you have a field

that you know will only hold US ASCII 7

then varchar2(10 char) is plug identical to varchar2(10)

how does that violate any concepts?????  In a usascii7 database -- a char is a
byte.  this only comes to play in a multi-byte database (and then you cannot SAY
that the ascii characters will all be SINGLE BYTE)

a character is a character, that is all.  this does not "violate" anything.

      Rating: 2
Trying
      again...  November 25, 2003
Reviewer:  Nick
       from Richmond, VA
The database supports multi-byte data.

One field will only hold data like 'abc' so it is varchar2(3). It will never
have international characters. Another field can hold international characters
so it is varchar2(3 char).

Why is this not a better approach than your recommendation of using varchar2(N
char) throughout the application?

          Followup:

do you know for a fact that in the characterset of your clients, abc will be all
single byte data (i cannot say that, I don't even know if it is true)

i can say

"the field is to hold 3 characters"

varchar2(3 char)

holds -- well, 3 characters.

varchar2(3)

holds -- 3 bytes, which is going to be 3 or LESS characters.

there is no such thing as "regular" and "international" characters, the client
has a characterset.

      Rating:
      5
  January 02, 2004
Reviewer:
      A reader

November 25, 2003 gaurang -- Thanks for the question regarding
      "Star Schema", version 8.1.7

      You Asked
Hi Tom,

  Can you explian about STAR schema and data warehouse,dimension concept in few
words? There are books available on OTN but before i start reading them explain
some terminology i mention above.

      and we said...
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96520/logical.htm#97642
does that exactly...  with a picture and everything ;)

Review &
      Followup

      Rating: 3
Advantage of
      Snowflaking  July 10, 2003
Reviewer:  Sagar
       from India
Hi Tom,
I was asked at a interview the advantage of snowflaking..Other than normalized
structure and space saving, I could not come up with an answer. Please let me
know if there are other advantages of snowflaking.
NOTE: Even Ralph Kimball and Oracle docs recommend NOT using snowflaking

          Followup:

well, in the city when snowfalls -- before the cars start moving, it looks
really nice.

I'll let others comment on this, star yes, snowflakes, ouch.  not only hard to
query by end users, but if you create a view to ease it, you'll wait years for
the answers.

      Rating:
      4
  July 10, 2003
Reviewer:  Dave
       from Colorado
Snowflaking is generally needed to support that type of aggregation table where
you are aggregating to a higher level in a dimension.

eg., fact table is at a level of "postal_code", and there is an aggregation
level at the "city" level. "City" might be snowflaked out from the "postal_code"
table, although you generally benefit from having all the attributes of city and
it's parent data elements in the postal_code table also. (Redundant data).

          Followup:

and I would just roll that redudant data down into the dimension resulting in a
star again.

      Rating: 5
Star,
      snowflake, 9i  July 11, 2003
Reviewer:
      Dušan  from London, UK
Tom,

Q: Where and how do 9i bitmap join indexes fit in this story? Can they be used
to improve performance of a snowflake, if someone decides to ignore all the
warnings about such a design?

Q: Imagine a requirement to design a dimension which is for some queries fully
historical, i.e. SCD Type 2 (as defined by Kimball) and for other queries it
should just show the current state of a dimension record, just like SCD Type 1.
And then, someone might ask to see a mix of current and historical values of
columns in another query.
What sort of hybrid approach would you use to tackle this requirement?
(Simple approach is to review and question such a requirement, surely.)

Many thanks

Now, the informal bit:
--------------------------
A simple test case:

select sum(pints_owed) from all_uk_readers;
       *
ERROR at line 1:
ORA-01426: overflow

To me, it looks that this test case shows you should visit UK more often. What
do you reckon?

          Followup:

q) i haven't tested it enough myself (bitmaps+snowflake+star transformation).
so much to do, so little time to do it all...

q) might two dimensions -- one with historical values and the other with current
values repeated in place of history -- do it?  or even one dimension where you
have a "current_value" column paired with each "historical_value" column and
your load routines maintain the "current_value" column as they add new entries?

      Rating: 3
Bitmap
      joins  July 11, 2003
Reviewer:  Peter
       from Northampton, UK
Dušan

You could use bitmap joins to connect fact data to dimension attributes.

In our DW we have Product number as a FK on the fact tables. Say Product has
Product number as the PK and product type as an attribute. The bitmap join could
answer product type queries against fact tables.

I can't see a convincing case to use bm joins on just a reference hierarchy
though.

Tom

When are you coming to the UK?

          Followup:

i'm coming to Denmark in january, no current plans to be in the UK anytime soon

      Rating: 5
What about
      Brazil ?  July 11, 2003
Reviewer:  Joao Lemos
      Vasconcellos  from Sao Paulo, Brazil
By any chance, are you coming to South America, perhaps Brazil ? We'd love to
have you here ! There is a big Oracle community that will love to see you. Btw,
your book Oracle 101 is a big hit here, in Portuguese, although there is a
laughable lot of poorly translated terms that break the reading rythm and the
fluency of your agreeable way of writing.

Joao Vasconcellos
Oracle Brasil - SP

          Followup:

Gaja wrote the Oracle 101 book -- I did "Expert One on One Oracle" :)

sorry, no current plans to visit Brazil

      Rating: 5
Any plans for
      Australia (Sydney)?  July 12, 2003
Reviewer:
      A reader
Tom,
AUSOUG is planning to have Oracle conference in August/September'2003. Last
year, it was a big success. Why not you visit this time?, and also perfect time
for your new book launch in Australia!
Another fan of yours!

          Followup:

isn't that winter there?  brrr.....

      Rating: 4
Open
      Invitation  July 12, 2003
Reviewer:  reaz

First of All I would like to  Invite you to visit Bangladesh, where the  number
people interested to use Oracle is growing very fast.

Your visit will be a Great contribution to the Oracle users & lovers of this
distant country.

How can I find more about the publication of Mr. Kimbal.

thanks.
Reaz.

          Followup:

http://www.amazon.com/exec/obidos/search-handle-url/index=books&field-author=Kimball,%20Ralph/104-9964071-7149518

      Rating: 5
Hmmm... winter
      in Sydney....  July 12, 2003
Reviewer:
      A reader
Tom,
Sydney is having one of the world's best weather. No extreme side of any season.

"For many, the best time to visit Sydney is in the Australian spring. This
covers the period from September 1 to November 30"
So, I don't think this will be genuine excuse...

      Rating: 4
Level Attribute
      in Star Schema  August 06, 2003
Reviewer:
      A reader
Hi Tom,

We are in the process of designing DW. We asked a consultant to help us in
design.
Just to make it easy, I give you a small example of what I’ve done so far.

I have one fact table, which has 3 dimensions. And each dimension has 4 levels.
For example Time dimension. (Year-Month-Week-Day). I had Level column in all of
the dimensions. Because I wanted to pre-calculate all sales eg. (the first week
of the first month of the year 93) via ETL process during night and when a query
is like (show me the total sales for the first week of first month of the year
93, just go to Time dimension and find the time_key and join it to fact table
then find the total sales.

Our consultant, insist that we don’t need level column in any dimensions! But
I’ve read many times that Star Schema needs to have level column. Could you
please let me know your idea? Is that right that we need a level column in
dimensions?

Another thing that the consultant mentioned is that, we are not going to
pre-calculate, we will keep all the detail (Daily Sales) in fact table and then
we use group by for those kind of queries. And in the case that was slow, we
create another fact table(I would assume Summery Table) to keep the aggregate
data.

Isn’t it a poor design?

Could you please, let me know your valuable idea?

Thanks millions of tons,
Arash

          Followup:

not sure I follow you on the "level" column.  A typical dimension might be:

DAY  -> QUARTER ->   WEEK  ->  FY

for example?

You use materialized views in Oracle and data warehouses like you might use
indexes in OLTP.  You would store the details and in the event of tuning a
frequently asked quesiton -- you would create materialized views (precomputed
summaries) that the optimizer would use for you (like an index).  You don't want
to set up yet another star.

      Rating:
      4
  August 12, 2003
Reviewer:
      A reader
Thanks Tom.

If I understood well, column that shows the 'LEVEL' is logical. I mean, we don't
need to have a column in our physical structure(dimension) that represent
'LEVEL'. For example like the following:

year number,
month number,
quarter number,
week number,
day number,
LEVEL number (We don't need this column!) Am I right?

Regarding the summery data, I got your point. But in the case that I don't need
detail in DW, isn't it better that I just save summery data in Fact table ?

Thanks again for your valuable information.

          Followup:

i don't know what purpose level would provide here, so I cannot really comment
(can say, LEVEL is a psuedo column in our SQL so that name would be a bad
choice)

of course, you should make your facts as aggregated as you can, I just would not
replicate start schemas at differing levels of detail for the same data -- MV's
are for that.

      Rating: 4
Any books that
      you recomend ..  August 12, 2003
Reviewer:
      A reader  from MA
Hi Tom,
      Do you recomend any books other than Oracle Documentation to learn about
Datawarehousing Concepts and design .

Thanks in advance,
another fan of your's

          Followup:

the stuff by Ralph Kimball is fairly well respected.

      Rating: 4
Coming back to
      you visiting UK!!!  August 13, 2003
Reviewer:
      A reader  from UK
Hi Tom,

 While browsing through Oracle World bookmark, somewhere I read about Oracle
World in Paris from Oct 20 - Oct 23. Guess this is a good reason for you to come
to Paris and UK/Europe fans of yours will have a chance of meeting you ....

Regards,
Yet another person learning Oracle tips/trick from you!!!

      Rating: 5
create
      dimension  August 13, 2003
Reviewer:  bob
       from tx
shouldn't you be using Oracle dimensions, not your own version?
do your dw designs make use of Oracle defined dimensions Tom?
I don't think I have ever seen them mentioned here, although if I search I am
sure i will.

from the dw guide.

You create a dimension using either the CREATE DIMENSION statement or the
Dimension Wizard in Oracle Enterprise Manager. Within the CREATE DIMENSION
statement, use the LEVEL clause to identify the names of the dimension levels.

          Followup:

i think we are confusing terms here -- they seemed to be using level as a column
in a dimension table

you WOULD use a create dimension statement to help query rewrite and MV's

      Rating: 4
The usage of
      LEVEL in dimension  August 19, 2003
Reviewer:
      A reader
Hi Tom,

  I am back again. I didn't have access to Internet (out of town).

  You are right. I want to have a column that represents Level in Dimension. Let
me ask my question in another way.

The End Users want to query:
1- Total sales in a day
2- Total sales in each week of a specific month in a year

Please correct me if I am wrong! For supporting these queries, I'll create one
Time dimension and one Fact table as follows:

Create table Time_Dim
(
time_key number(sequence),
Year   number,
month  number,
week   number,
day      number,
level  number
);

insert into time_dim(time_key,year,level)
              values(1,2001,1);

insert into time_dim(time_key,year,month,level)
             values (2,2001,1,2);
insert into time_dim(time_key,year,month,level)
             values (3,2001,2,2);
insert into time_dim(time_key,year,month,level)
             values (4,2001,3,2);

insert into time_dim(time_key,year,month,week,level)
             values (5,2001,1,1,3);
insert into time_dim(time_key,year,month,week,level)
             values (6,2001,1,2,3);
insert into time_dim(time_key,year,month,week,level)
             values (7,2001,1,3,3);
insert into time_dim(time_key,year,month,week,level)
             values (8,2001,1,4,3);

insert into time_dim(time_key,year,month,week,day,level)
             values (9,2001,1,1,1,4);
insert into time_dim(time_key,year,month,week,day,level)
             values (10,2001,1,1,2,4);

Now, the fact table:

Create table fact_table
( fact_key number(sequence),
  time_key number,
  total_sales number
)

Now, ETL can calculate and insert total sale for e.g. the second week of the
first month of Jan 2001 and also for each day. For example:

insert into fact (fact_key,time_key,total_sale)
          values (1, 6 , 100000);
insert into fact (fact_key,time_key,total_sale)
          values (2, 9 , 2000);
insert into fact (fact_key,time_key,total_sale)
          values (3, 10 , 5000);

Now, if the query would be like ‘give me the total sales of each week in January
2001’ , it would be like this:

Select time_dim.week , total_sales
From fact_table,time_dim
Where fact_table.time_key = time_dim.time_key
AND time_dim.year = 2001
AND time_dim.month = 1
AND time_dim.level = 3
/

As you see, the ‘LEVEL’ helped me to :
1-    Be able to save aggregate data in fact table
2-    Be able to retrieve it correctly and easily

I’ve read this in a white paper that without ‘LEVEL’ in dimension, it’s
impossible to support these kinds of queries, unless you create a Snowflake
schema!

Now my questions are:
1-    Is that true that we need ‘Level’ in dimension?
2-    Is it that the only and the best way to support these kind of queries? (I
mean the design)
3-    Is that ok to save aggregate data and daily data in one fact table?

Hope, I could explain it better this time. I really don’t know, how to thank you
! Just thanks a million for your time.
Arash

          Followup:

‘give me the total sales of each week in January
2001’

Create table Time_Dim
(
time_key number(sequence),
Year   date,
month  date,
week   date,
day    date
);

Select time_dim.week , total_sales
From fact_table,time_dim
Where fact_table.time_key = time_dim.time_key
AND time_dim.month = to_date( '01-jan-2001' )
/

seems easier to me?

      Rating:
      3
  August 20, 2003
Reviewer:
      A reader
Hi Tom,

Correct me if I am wrong again. The following query

Select time_dim.week , total_sales
From fact_table,time_dim
Where fact_table.time_key = time_dim.time_key
AND time_dim.month = to_date( '01-jan-2001' )
/

Retrieve all records in 'Jan 2000'. I mean daily and weekly data ! Because in
Tim_Dim I inserted one row for each day of each month and also one row for each
week of each month and one row for each month of each year.

If I define all attributes as DATE, I don’t know what to insert for year, month,
week ?  Probably
·    The first day of each year represent ‘YEAR’ e.g (‘01-jan-2001’ represent
Year 2001). I mean, if I want to insert the total sales for year 2001 in Fact
table, I should

Select time_key
From  time_dim
where time_dim.year = to_date( '01-jan-2001' )

and then insert into fact_table values (time_key,total_sales)

·    The first day of each month of each year represent that month in that year.
e.g  value of (01-AUG-2001) in month column represent August of 2001
·    Don’t know about week !
·    Day is clear

I don’t know how to write the query with your design for example “give me the
total sales for the second week of January 2001? Because as I told you I want to
calculate the total sales for ‘the second week of January 2001’ via ETL process
during night and just insert one row in the Fact table.

In general how the data looks like in time_dim table?

Sorry if I confused you.
Thanks in advance.

          Followup:

so you want details and aggregates?

if you are fetching the details -- might as well just group by rollup?  you
don't need to compute the aggregates ahead of time?

      Rating: 4
Where are you
      today??  August 20, 2003
Reviewer:  DD
       from MN, USA
 its 20-AUG-2003. dont see any updates on this page. hope to see u soon.

          Followup:

geez...

i had to get up at 4:30 that morning...
fly to north carolina...
speak all day long...
then fly back home....

i was really tired.  really really tired.

asktom is just a hobby :)

      Rating:
      3
  August 22, 2003
Reviewer:
      A reader
Thanks Tom.
Arash

      Rating: 5
star and
      snowflake  November 24, 2003
Reviewer:
      A reader
Hi

I have read several Datawarehouse Books, Wiley the datawarehouse toolkit 2nd
edition by Kimball and Essential Oracle 8i Datawarehousing Gary Odge/Tim Gorman.
I am worried because these two books contradict each other in the aspect of
differntiate star and snowflake schema

According to Kimball, snowflake is star schema with dimensions normalized

According Gary Dodge/Tim Gorman snowflake is denormalized star schema

Confusing isnt it??? According to Oracle documentation snowflake is star schema
with dimensions normalized

Who is correct?!?!?!?

          Followup:

did they both have the same pictures?  the name isn't really relevant -- the
implementation is.  I bet they both describe the same thing.

I've had people call implementations normalized whilst others called it
denormalized at the same time.  At the end of the day -- a rose is a rose by any
name.

      Rating: 5
star query and
      FKs  November 25, 2003
Reviewer:
      A reader
Hi

Does Oracle need the relationships between Fact Tables and Dimension Tables in
order to perform Star Query? Primary keys and Foreign keys etc...

I think so?

          Followup:

it should -- it certainly "makes sense".  there would not be any reason why you
would not have the primary/fkeys there.

see

http://www.dbazine.com/jlewis6.html
nice article on them

      Rating: 5
relationships
      in DWH  November 25, 2003
Reviewer:
      A reader
Hi

I always thought relations are redundant in DWH because the data coming from
operational database *MUST* always be valid....

Am I wrong?

          Followup:

there would not be an entire cottage industry built around ETL and cleansing
tools if what you said were true :)

the data should be valid.

it rarely is.

but in any case, the relationships are not redundant, they are meta data used by
the optimizer to more efficiently optimize your queries -- in addition to
validation.

      Rating: 4
question about
      modifying datawarehouse model  November 25, 2003

Reviewer:  A reader
Hello

Recently our company merged with another two companies. We must now change our
Datawarehouse data model to suit this merge.

Our datawarehouse design is very simple. One sales fact table, one customer
table, one product table and one time table. After the merge one customer id can
appear under three company (the customer share same code across three companies)
so basically now we need to add another company table in datawarehouse.

The question is should this company table a parent table of customer table in
datawarehouse? Or it can be a new dimension to the fact (sales) table?

The first approach making company table parent of customer table this would
change ur model from Star to Snowflake. This controls the data integrity.

The second approach of making company table a dimension of sales table this
could compromise the data integrity. There might be cases where in the sales
table there are customers who are only customers of company_1 also appears as
customers of company_2

I was thinking the approach 2 since we also assume the data should always be
valid (it comes from SAP) but hmm after read your last reply that we shouldnt
assume data are always valid, this put me in doubt.....

          Followup:

why would you have multiple company tables?  you are one company now?

      Rating: 5
we are a
      group  November 25, 2003
Reviewer:
      A reader
Hello

We are under same group but different companies. Each company keep their brands.

That is why the new customer PK would be the combination of customer_id and
company_id....

Cant argue with management about these issues (making one company instead of 3)

          Followup:

add a company column to the company table?

your customers won't "span" companies?  (i would HOPE they would).  if they do,
you are heading for a big mess.

      Rating: 5
ah I think you
      confused  November 25, 2003
Reviewer:
      A reader
Hello

I think you missunderstood, we have a company table which has three rows, one
for each company my question was about these two approaches

=========================
The first approach making company table parent of customer table this would
change ur model from Star to Snowflake. This controls the data integrity.

The second approach of making company table a dimension of sales table this
could compromise the data integrity. There might be cases where in the sales
table there are customers who are only customers of company_1 also appears as
customers of company_2

Which approach would you use?

          Followup:

to me -- a customer is a customer is a customer

they would not be in there three times if they were a customer of all three
'companies'

I'm saying "don't duplicate the customer data -- truly bad idea, it'll be 100% a
mess"

November 25, 2003 Paul -- Thanks for the question regarding "Need
      help with the query", version 9.2.0.2.0

      You Asked
Tom,

I don't seem to find how to write the correct query. Here is what I have:

drop table test;
create table test
  (id number,
   app varchar2(10),
   org varchar2(10),
   comments varchar2(15));

insert into test (id,app,org,comments)
values (1,'App 1','DEFAULT','Default org');

insert into test (id,app,org,comments)
values (2,'App 2','DEFAULT','Default org');

insert into test (id,app,org,comments)
values (3,'App 2','IT','IT org');

insert into test (id,app,org,comments)
values (4,'App 2','HR','HR org');
commit;

This table contains exactly one row per combination of app and org='DEFAULT',
and may have any number of app + org combinations (where org != 'DEFAULT').
Column Id is PK.

I need to create a view on this table. Later there will be generated query
against this view with the following parameters:
- id (always for app + org='DEFAULT')
- org (!= 'DEFAULT')

and return exactly one row while satisfying the following conditions:

- if record is found than return info from both records (with provided org and
org='DEFAULT')
- if record is NOT found than return info from the record with org='
DEFAULT'

The first step is obvious: join table to itself:

create or replace view v_test as
select app,id,org,def_comments,nondef_comments,nondefaultid
  from (select a.app,a.comments def_comments,a.id,b.org,b.comments
nondef_comments,decode(b.id,a.id,'DEFAULT',b.id) nondefaultid
         from test a, test b
         where a.app=b.app
           and a.org='DEFAULT');

pdruker@DEVL> select * from v_test;

APP         ID ORG        DEF_COMMENTS    NONDEF_COMMENTS NONDEFAULT
---------- --- ---------- --------------- --------------- ----------
App 1        1 DEFAULT    Default org     Default org     DEFAULT
App 2        2 DEFAULT    Default org     Default org     DEFAULT
App 2        2 IT         Default org     IT org          3
App 2        2 HR         Default org     HR org          4

Don't see any problem if org exists:

pdruker@DEVL> select * from v_test where id=2 and org='HR';

APP         ID ORG        DEF_COMMENTS    NONDEF_COMMENTS NONDEFAULT
---------- --- ---------- --------------- --------------- ----------
App 2        2 HR         Default org     HR org          4

However, for non-existing org, don't get any rows back.
pdruker@DEVL> select * from v_test where id=2 and org='WHATEVER';

no rows selected

Looks like, I need to add another OR conditions:

pdruker@DEVL> select * from v_test where id=2 and (org='HR' or org='DEFAULT');

APP         ID ORG        DEF_COMMENTS    NONDEF_COMMENTS NONDEFAULT
---------- --- ---------- --------------- --------------- ----------
App 2        2 DEFAULT    Default org     Default org     DEFAULT
App 2        2 HR         Default org     HR org          4

pdruker@DEVL> select * from v_test where id=2 and (org='WHATEVER' or
org='DEFAULT');

APP         ID ORG        DEF_COMMENTS    NONDEF_COMMENTS NONDEFAULT
---------- --- ---------- --------------- --------------- ----------
App 2        2 DEFAULT    Default org     Default org     DEFAULT

Now the second query is ok, but the first one returns two rows instead of one.

I also tried to use dense_rank function, but don't seem to find a solution
either:

pdruker@DEVL> create or replace view v_test as
select app,id,org,def_comments,nondef_comments,nondefaultid,dr
  from (select a.app,a.comments def_comments,a.id,b.org,b.comments
nondef_comments,decode(b.id,a.id,'DEFAULT',b.id) nondefaultid,
dense_rank() over  (partition by a.app order by decode(b.org,'DEFAULT',1,2) desc
) dr
from test a,test b
where a.app=b.app
 and a.org='DEFAULT')
;

View created.

pdruker@DEVL> select * from v_test where id=2 and (org='HR' or org='DEFAULT');

APP         ID ORG        DEF_COMMENTS    NONDEF_COMMENTS NONDEFAULT         DR
---------- --- ---------- --------------- --------------- ---------- ----------
App 2        2 HR         Default org     HR org          4                   1
App 2        2 DEFAULT    Default org     Default org     DEFAULT             2

pdruker@DEVL> select * from v_test where id=2 and (org='Whatever' or
org='DEFAULT');

APP         ID ORG        DEF_COMMENTS    NONDEF_COMMENTS NONDEFAULT         DR
---------- --- ---------- --------------- --------------- ---------- ----------
App 2        2 DEFAULT    Default org     Default org     DEFAULT             2

Is there a way to create this kind of view? I'm trying to find a simple SQL
solution that would fit into our existing standard solution, and not to write a
custom PL/SQL procedure if possible

Thanks in advance,
Paul

      and we said...
I think you are approaching it wrong on two counts.

first, ID is sort of meaningless -- you should just be using APP, you want to
get the default and specific comment for an APP -- not an ID (what is ID=2?  app
= 'App 2' means something...)

second, a view won't be appropriate (eg: performant) in this case.  If you want
to hide the complexity of the query -- we'll use a ref cursor

http://asktom.oracle.com/~tkyte/ResultSets/index.html
and the developers will pass in the ORG and the APP and get a result set back
(or if it is always a single row -- just the outputs!)

Then a query could look like this:

ops$tkyte@ORA920> variable bv1 varchar2(25)
ops$tkyte@ORA920> variable bv2 varchar2(25)
ops$tkyte@ORA920> exec :bv1 := 'HR'

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec :bv2 := 'App 2'

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select app,
  2         max( decode( org, 'DEFAULT', comments ) ) xdefault,
  3         max( decode( org, :bv1, comments ) ) non_default
  4    from test
  5   where org in ( 'DEFAULT', :bv1 )
  6     and app = :bv2
  7   group by app
  8  /

APP        XDEFAULT        NON_DEFAULT
---------- --------------- ---------------
App 2      Default org     HR org

If you persist with the ID= we would use:

ops$tkyte@ORA920> select id, app, comments,
  2         (select t2.comments
  3                from test t2
  4                   where t2.app = t1.app
  5                     and t2.org = :bv1 ) non_default
  6    from test t1
  7   where id = :bv3
  8  /

        ID APP        COMMENTS        NON_DEFAULT
---------- ---------- --------------- ---------------
         2 App 2      Default org     HR org

instead....

CUSTOM PLSQL is not evil -- refcursors could be very useful here.  But these
queries work well.
Review & Followup

      Rating: 5
I love PL/SQL
      :)  April 29, 2003
Reviewer:  Paul Druker
       from Fairfax, VA USA
Tom,

Thanks a lot for your response. In fact, I don't consider PL/SQL as "evil", and
write a lot of custom code. In this particular case I was trying to re-use some
of the existing packages and was wondering if you have a "magic bullet"
solution...

      Rating: 5
Comments Within
      SQL statement  November 25, 2003
Reviewer:
      A reader
Hi Tom,

I am a DBA for a company building small ERP products.

I requested all developers to add standard comments (e.g. code source) to the
SQL statements in C#, for ease of debugging.

For example:

SELECT /* data.oracle.orders.get_order_items */ ... FROM orders where ...

Will there be any impact/overhead on the Shared Pool?

We use cursor_sharing = similar.

          Followup:

it'll be just fine -- no real affect.

November 25, 2003 Randy -- Thanks for the question regarding "Base
      tables of snapshots", version 8.1.6

      You Asked
In general, can you treat the base tables of snapshots the same as a "regular"
table?  Are they just normal tables that happen to have a relationship with the
snapshot, and basically sort of used by the snapshot?

I want to move the tables and indexes of these tables, and wondered what
implications there are.  Also, why don't the snapshots get dropped when the base
table is dropped?

Thanks.

      and we said...
Yes, you can treat them as "regular" tables in general.  If you add constraints
-- make sure the constraints are DEFERRABLE for the refresh to succeed.

Snapshots typically are NOT on the same site with the base table.  There is no
distributed "drop".  They are independent entities.

Much like a view and procedure -- they do not get dropped when their base tables
are dropped.
Review & Followup

      Rating: 3
I meant the
      table on the replicated site  September 19, 2001

Reviewer:  Randy  from Texas
When you do a CREATE MATERIALIZED VIEW, it will automatically create a table to
store the replicated data on the snapshot side, right?  This is the table I'm
referring to.

          Followup:

Yes, you can do things to the base table of the snapshot AKA materialized view.

      Rating: 2
one important
      case not covered  November 25, 2003
Reviewer:
      igor romansky  from germany
hi,

you wrote:

Snapshots typically are NOT on the same site with the base table.  There is no
distributed "drop".  They are independent entities.

But you didn't mention the following situation.

I created a snapshot, e.g.

create snapshot test as select 1 num from dual;

and then renamed the "base table" of it:

rename test to test2;

then i dropped the snapshot:

drop snapshot test.

And now Icannot drop the base table test2.

Is it possible to drop it at all? And how?

          Followup:

well, i don't see this as an "important case" -- rather as a "not good idea on
your part"  :)

not a very common occurence i would believe.

I can say that current releases do not permit such a thing:

ops$tkyte@ORA920> create snapshot test as select 1 num from dual;
Materialized view created.

ops$tkyte@ORA920> rename test to test2;
rename test to test2
*
ERROR at line 1:
ORA-32318: cannot rename a materialized view

but, out of curiosity, i found a workaround for you:

ops$tkyte@ORA817DEV> create snapshot test as select 1 num from dual;
Materialized view created.

ops$tkyte@ORA817DEV> rename test to test2;
Table renamed.

ops$tkyte@ORA817DEV> drop snapshot test;
Materialized view dropped.

ops$tkyte@ORA817DEV> drop table test2;
drop table test2
           *
ERROR at line 1:
ORA-12083: must use DROP MATERIALIZED VIEW to drop "OPS$TKYTE"."TEST2"

ops$tkyte@ORA817DEV> drop snapshot test2;
drop snapshot test2
*
ERROR at line 1:
ORA-12003: snapshot "OPS$TKYTE"."TEST2" does not exist

ops$tkyte@ORA817DEV> create snapshot test2 on prebuilt table as select 1 num
from dual;
Materialized view created.

ops$tkyte@ORA817DEV> drop snapshot test2;
Materialized view dropped.

ops$tkyte@ORA817DEV> drop table test2;
Table dropped.

all cleaned up...

      Rating: 5
Great! That
      works!  November 26, 2003
Reviewer:  igor
      romansky  from germany
hi tom,

thanks a lot, your workaround was very helpful (i tested with 8i)!

and my answer to your comment:
>well, i don't see this as an "important case" -- rather as >a "not good idea on
your part"  :)
>not a very common occurence i would believe.

one oracle user renamed one of his snapshots, then dropped all snapshots with
pl/sql block:

BEGIN
  FOR r IN (SELECT name FROM USER_SNAPSHOTS) LOOP
    EXECUTE IMMEDIATE 'drop snapshot ' || r.name;
  END LOOP;
END;
/

and couldn't drop the "base" table on the renamed snapshot.


Subject | Article | Last Updated (East coast time)
db_file_multiblock_read_count and different read rates | article only | Nov 26, 2003 07:38 am
Poor performance of Java stored code vs PLSQL | article only | Nov 26, 2003 07:47 am
Standby database | article only | Nov 26, 2003 07:48 am
Inline Views Vs. Materialized Views | article only | Nov 26, 2003 07:58 am
Full sql text from v$sqltext | article only | Nov 26, 2003 08:47 am
What tables have been accessed in last n number of hours? | article only | Nov 26, 2003 08:57 am
lost control files / inconsisitent controlfiles | article only | Nov 26, 2003 09:41 am
How can we execute specific block of code of a Package only once when we first call that package | article only | Nov 26, 2003 10:38 am
A matter of access | article only | Nov 26, 2003 10:39 am
BULK COLLECT fetch is not faster | article only | Nov 26, 2003 11:52 am
Backup and Recovery -- why extra redo is generated. | article only | Nov 26, 2003 11:57 am
Separating comma separated words | article only | Nov 26, 2003 12:08 pm
Materialized Views | article only | Nov 26, 2003 02:40 pm
Forms Matrix Style Form | article only | Nov 26, 2003 03:13 pm
unix login and security | article only | Nov 26, 2003 03:20 pm
PASSWORD HASH | article only | Nov 26, 2003 03:28 pm
SQL to identify executing sql statements that are not using indices. | article only | Nov 27, 2003 09:16 am
MTS CONFIG | article only | Nov 27, 2003 10:35 am
Why am i getting negative 'execute to parse' ratio in my statspack report? | article only | Nov 27, 2003 10:36 am
disaster recovery | article only | Nov 27, 2003 10:39 am
compatible init.ora parameter | article only | Nov 27, 2003 11:03 am
dbms_job is writing files to archiver frequently | article only | Nov 27, 2003 11:46 am
Formatted weekdays in use | article only | Nov 28, 2003 09:25 am
I am facing problems in writing stored procedure using Oracle Object Table. | article only | Nov 28, 2003 09:27 am
DROP USER | article only | Nov 28, 2003 10:11 am
session_cached_cursor values | article only | Nov 28, 2003 10:26 am
commit | article only | Nov 28, 2003 10:34 am
Java Stored Procedures returning result sets in 9i??? | article only | Nov 28, 2003 12:03 pm
Does the index work on view if base table has the index | article only | Nov 28, 2003 12:53 pm
LOB Space | article only | Nov 28, 2003 01:26 pm
difference between soft parse and hard parse | article only | Nov 28, 2003 02:18 pm
Formatting output of DESC | article only | Nov 28, 2003 02:42 pm
Update Statement and Exception Handling | article only | Nov 28, 2003 05:34 pm
Equivalent to SESSION_ROLES that works in stored proc | article only | Nov 29, 2003 11:00 am
incomplete recovery | article only | Nov 29, 2003 12:42 pm
Not able to FTP from oracle server. Get the error : The filename, directory name, or volume label syntax is incorrect. | article only | Nov 29, 2003 01:01 pm
Intermedia text search from a union of two tables | article only | Nov 29, 2003 03:16 pm
Re:add partition | article only | Nov 30, 2003 12:15 pm
Function | article only | Nov 30, 2003 07:45 pm
SQL Performance | article only | Dec 01, 2003 07:14 am
Referencing %rowtype variables without using column names | article only | Dec 01, 2003 05:51 pm
Combining Multiple REF CURSORs into one | article only | Dec 01, 2003 05:52 pm
checkpoint | article only | Dec 02, 2003 08:10 am
insert | article only | Dec 02, 2003 08:13 am
Jobs in RMAN | article only | Dec 02, 2003 08:26 am
How can we release a lock held by another user? (kill them) | article only | Dec 02, 2003 08:27 am
how to use dbms_stats.gather_databse_stats | article only | Dec 02, 2003 08:33 am
Database upgrades. | article only | Dec 02, 2003 08:37 am
Calling External Procedures | article only | Dec 02, 2003 08:39 am
Index Organised Tables Vs Ordinary Tables | article only | Dec 02, 2003 08:44 am
Integrity constraints | article only | Dec 02, 2003 09:25 am
V$session_wait | article only | Dec 02, 2003 10:59 am
connect by | article only | Dec 02, 2003 12:25 pm
table analysis | article only | Dec 02, 2003 02:07 pm
ORA-01417: a table may be outer joined to at most one other table | article only | Dec 02, 2003 02:16 pm
Sql Quey | article only | Dec 02, 2003 09:23 pm
row(s) 1 - 56 of 56
November 26, 2003 Gustavo -- Thanks for the question regarding "db_file_multiblock_read_count and different read rates", version 8.1.7
You Asked
Hello Tom,

I trying to set up an optimal value for db_file_multiblock_read_count on two
Oracle instances I'm running on the same box (O.S. version is Compaq Tru64
V5.1A.) and both instances are 8.1.7.3..
For one hand ODS instance has db_block_size = 8K whereas  MOVICS3 instance has
db_block_size = 32 K.
For the other hand, Tru64 max_iosize_read is 65536.
I run the following commands on ODS instances:

ODS>alter session set db_file_multiblock_read_count = 500;

Session altered.

ODS>select value from v$parameter where name='db_file_multiblock_read_count';

VALUE
----------------------------
32

1 row selected.

ODS>alter session set events '10046 trace name context forever, level 8';

Session altered.

ODS>select /*+ FULL(a) */ count(*) from sys.source$ a;

  COUNT(*)
----------
     40712

1 row selected.

Viewing the trace generated above, I see ODS is reading 10 blocks by trip :

...
WAIT #1: nam='db file scattered read' ela= 2 p1=1 p2=762 p3=10
WAIT #1: nam='db file scattered read' ela= 2 p1=1 p2=948 p3=10
WAIT #1: nam='db file scattered read' ela= 2 p1=1 p2=966 p3=10
...

Then, I go ahead with MOVICS3 instance:

MOVICS3>alter session set db_file_multiblock_read_count=500;

Session altered.

MOVICS3>select value from v$parameter where
name='db_file_multiblock_read_count';

VALUE
------------------------
128

1 row selected.

MOVICS3>alter session set events '10046 trace name context forever, level 8';

Session altered.

MOVICS3>select /*+ FULL(a) */ count(*) from sys.source$ a;

  COUNT(*)
----------
    436772

1 row selected.

Now, watching the trace generated I see MOVICS3 instance is reading 25 blocks
per trip:
...
WAIT #1: nam='db file scattered read' ela= 13 p1=1 p2=1859 p3=25
WAIT #1: nam='db file scattered read' ela= 13 p1=1 p2=1909 p3=25
WAIT #1: nam='db file scattered read' ela= 10 p1=1 p2=1949 p3=25
...

My question is:

Why ODS instance is able to read 320K (10 blocks * 32K) while instance
MOVICS3 reads 200K (25 block * 8K) since they are running simultaneously on the
same box?.
Is there any way for reaching the 1MB limit defined by SSTIOMAX on both
instances?

Your answer will help. Thanks

And your answer really helped me...

Here you are sizes for extents:

ODS>select bytes/1024, count(*)
  2  from dba_extents where owner='SYS' and
  3  segment_name='SOURCE$'
  4  group by bytes/1024
  5  order by 1;

BYTES/1024   COUNT(*)
---------- ----------
        64          1
       320         36

2 rows selected.

MOVICS3>select bytes/1024, count(*)
  2  from dba_extents where owner='SYS'
  3  and segment_name='SOURCE$'
  4  group by bytes/1024
  5  order by 1;

BYTES/1024   COUNT(*)
---------- ----------
        16          1
       200        281
       224          1

3 rows selected.

Thanks a lot.
and we said...
what are the EXTENT SIZES for sys.source$ on your systems?

For example, on my one 817 system with 8k blocks:

  1  select bytes/1024, count(*)
  2  from dba_extents where owner = 'SYS'
  3  and segment_name = 'SOURCE$'
  4  group by bytes/1024
  5* order by 1
sys@ORA817DEV> /

BYTES/1024   COUNT(*)
---------- ----------
        16          1
       200        147
       224          1

200k represents most of the table as far as extents go.  We read maximally by
EXTENT size (eg: a read never spans extents).  perhaps your are being restricted
by your extent size.
Review & Followup
Rating: 5
May 31, 2003
Reviewer: A reader
Hi Tom,

I have some confusion on trace 10046 level 8.

SQL> alter session set db_file_multiblock_read_count =128;

Session altered.

Elapsed: 00:00:00.50
SQL>  alter session set events '10046 trace name context forever, level 8';

Session altered.

Elapsed: 00:00:00.60
SQL> select /*+ FULL(a) */ count(*) from sys.source$ a;

  COUNT(*)
----------
    853391

Elapsed: 00:00:09.30
SQL>  alter session set events '10046 trace name context off';

Total Time to ecxecute statement is 00:00:09.30 If you see trace file

PARSING IN CURSOR #1 len=50 dep=0 uid=18 oct=3 lid=18 tim=0 hv=165126988
ad='4385298'
select /*+ FULL(a) */ count(*) from sys.source$ a
END OF STMT
PARSE #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=0
EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=0
WAIT #1: nam='SQL*Net message to client' ela= 0 p1=1111838976 p2=1 p3=0
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=584 p3=4
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=2557 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=6013 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=6365 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=6621 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=6877 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=7069 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=7453 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=7869 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=8157 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=8605 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=8765 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=8893 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=9213 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=9533 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=9725 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=9853 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=9981 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=10109 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=10237 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=10368 p3=125
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=10493 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=11133 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=11389 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=12669 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=12829 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=12957 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=13213 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=13405 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=13533 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=14045 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=14173 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=14365 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=14493 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=14621 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=15005 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=15453 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=16989 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=17181 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=17437 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=17629 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=17949 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=18077 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=18461 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=18653 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=18973 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=19293 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=19549 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=19805 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=19933 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=20061 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=20381 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=20765 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=20893 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=21277 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=21533 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=21661 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=21789 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=21917 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=22045 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=22173 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=22813 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=23069 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=23197 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=23517 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=27325 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=27773 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=27965 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=28221 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=28349 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=28669 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=28797 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=29117 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=29245 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=29501 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=29629 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=29949 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=30269 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=30525 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=30653 p3=4
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=30913 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=31041 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=31297 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=31617 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=31745 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=31873 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=32001 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=32449 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=32641 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=32769 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=32897 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=33025 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=33153 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=33281 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=33921 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=34049 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=34177 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=34305 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=34433 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=35009 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=35137 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=35521 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=35649 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=35905 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=36353 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=36673 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=36929 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=37057 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=37313 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=37441 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=37569 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=38081 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=38209 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=38337 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=38657 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=38785 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=38913 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=39041 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=39169 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=39617 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=39745 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=40193 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=40386 p3=127
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=40641 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=40769 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=41089 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=41345 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=41473 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=41793 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=42049 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=42177 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=42305 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=42689 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=42945 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=86017 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=98529 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=103521 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=104801 p3=5
FETCH #1:c=0,e=0,p=17289,cr=17292,cu=18,mis=0,r=1,dep=0,og=4,tim=0
WAIT #1: nam='SQL*Net message from client' ela= 0 p1=1111838976 p2=1 p3=0
FETCH #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=0,tim=0
WAIT #1: nam='SQL*Net message to client' ela= 0 p1=1111838976 p2=1 p3=0
WAIT #1: nam='SQL*Net message from client' ela= 0 p1=1111838976 p2=1 p3=0
STAT #1 id=1 cnt=1 pid=0 pos=0 obj=0 op='SORT AGGREGATE '
STAT #1 id=2 cnt=853391 pid=1 pos=1 obj=64 op='TABLE ACCESS FULL SOURCE$ '
=====================
PARSING IN CURSOR #1 len=57 dep=0 uid=18 oct=42 lid=18 tim=0 hv=1975444627
ad='441e848'
 alter session set events '10046 trace name context off'
END OF STMT
PARSE #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=0
EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=0

Elapsed Time is zero in all waits..

Question => If elapsed time is zero in all wait then why i am getting 9 second
elapsed time in sql*plus.

Thanks

Followup:
do you have timed statistics enabled.

and ela will be in 1/1000th (in 8i) or 1/1000000th of a second.  If they all
complete in less than that period of time, they'll all be zero as well.

Rating: 5
db_file_multiblock_read_count behaviour in 9i September 16, 2003
Reviewer: A reader from orlando,FL
Tom,
DB_FMRC is suggested to be set  to maxphys/db_block_size so that FTS and Fast
Index scans can  scoop in optimum chunks. If the table/index happens to be in a
tablespace having a different  db_block_size than the default ( using the 9i
feature of having multiple block size tablespaces ) how do we drive oracle to
derive the parameter based on the object tablespace's block size? OR we have to
take care thru alter session?

Thanks as always...

Followup:
I cover this anomoly in my new book "Effective Oracle by Design".  There I show
that db_file_multiblock_read_count is used at start up to compute "default block
size" * "mb read count" to come up with a "size"

so, say your block size is 8k and you set your mb read count to 64. We'll read
512k (if the OS permits it) at a time.

We'll read 512k from a 2k blocksize tablespace in that database
We'll read 512k from a 16k blocksize tablespace in that database

all of them will read 512k at a time -- since we effectively computed a read
size based on the mb read count * default block size.

Rating: 5
September 17, 2003
Reviewer: A reader from orlando,FL
Tom,
If *extent size* is less than the "size" does oracle breakup the reads.Say, just
for example, if "size" is 512k and extent size is 64k and if oracle has to do a
1 MB FTS,
would it take 16 (would small extent size become speed bumps?) or 2  reads?

Thanks as always...

Followup:
we read in but not across extents, so yes, that small extent would cause the
read size to go down.

Rating: 5
September 18, 2003
Reviewer: A reader from orlando,FL
so can we derive:
For big objects the extent size should be "size" or its multiples.

Going by your good recommendation of small, medium, large TBS, we should ensure
that atleast the large TBS sastisfies the above derivative

Thanks as always...

Followup:

for big objects you plan on full scanning.....

Rating: 5
November 25, 2003
Reviewer: A reader
Hi Tom,

 Question on setting db_file_multiblock_read parameter in RULE base optimizer..

 We are using siebel CRM application and it is using RULE based optimizer. We've
lots of siebel query doing Index range scan. We have db_file_multiblock_read
parameter set to 16 (Default).

We are using Oracle 8.1.7.4 with sun solaris 2.8. We've 8k block size. MAX. i/o
performed by solaris is 64(block)*8 ..

Question : Can I set db_file_multiblock_read to 64 or 128 ... in rule base
optimizer since this is not counted in execution plan (like cost based optm..) ?

If yes, what are side effects of high db_file_multiblock_read  ?

Thanks

Followup:
the RBO doesn't care about anything other then its rules (and an important rule
to it is "if there is an index we'll use it").

it cares not at all about dfmbrc.

dfmbrc will only come into play when the plan includes a full scan -- since you
are using index range scans -- it matters not what you set it to as it

a) isn't being used
b) won't affect the rbo

suggest you leave it out of the init.ora all together.

November 26, 2003 Alb -- Thanks for the question regarding "Poor performance of Java stored code vs PLSQL", version 8.1.7
You Asked
Hello Tom,

Can you shed some light on why a java stored function performs badly compared to
a similar PLSQL function in an 8i db. I've created a Java function that simply
creates and returns a Date and compared it to the PLSQL function Sysdate.

Java Function:-

create or replace and resolve java source named "JavaTest"
as
public class JavaTest
{
   public static java.sql.Timestamp getDate()
   {
      java.util.Date d = new java.util.Date();
      java.sql.Timestamp t = new java.sql.Timestamp( d.getTime());

      return t;
   }
}
/

PLSQL Wrapper of Java:-

create or replace function get_java_date
      Return Date As
   Language Java
      Name 'JavaTest.getDate() return java.sql.Timestamp';
/

The two Functions compared:-

-- PLSQL ------------------------

SQL> Select 'x'
  2    From all_objects
  3   Where created = sysdate
  4     And rownum < 30001;

no rows selected

Elapsed: 00:00:00.50

-- Java ---------------------------

SQL> Select 'x'
  2    From all_objects
  3   Where created = get_java_date
  4     And rownum < 30001;

no rows selected

Elapsed: 00:00:06.90

Here's my SGA info:-

SQL> show sga

Total System Global Area  154716188 bytes
Fixed Size                    75804 bytes
Variable Size             121008128 bytes
Database Buffers           33554432 bytes
Redo Buffers                  77824 bytes

Where Shared Pool and Java Pool are both set to 56M.

Is there anything I can do to speed up the Java?

Thanks.
and we said...
well, created = sysdate is the same as "CREATED = CONSTANT"

sysdate is evaluated once and used over and over.

created = get_java_date is NOT the same as "CREATED = CONSTANT"

you will find your java function is called 30,000 times.  so, some 60,000 "new"
calls and 60,000 destroys and 30000 context switches from sql to java....

try:

select 'x' from all_objects where created = (select get_java_date from dual
where rownum=1 )
and rownum < 30001;

ops$tkyte@ORA920.LOCALHOST> set timing on
ops$tkyte@ORA920.LOCALHOST> Select 'x'
  2    From all_objects
  3   Where created = sysdate
  4      And rownum < 30001;

no rows selected

Elapsed: 00:00:00.23
ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> Select 'x'
  2    From all_objects
  3   Where created = get_java_date
  4      And rownum < 30001;

no rows selected

Elapsed: 00:00:04.09
ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> select 'x'
  2    from all_objects
  3   where created = (select get_java_date from dual where rownum=1 )
  4      and rownum < 30001;

no rows selected

Elapsed: 00:00:00.25
Review & Followup
Rating: 5
Can I reset a package variable each time it is used by a query? September 26, 2002
Reviewer: Alb from Australia
Thanks Tom. I had tried something similar using a package variable in that the
package var was set in the initialisation of the package and therefore the Java
function would only be called once when used in a query:-

create or replace package pkg is

   g_date   date;

   function get_date
      return date;

end pkg;
/

create or replace package body pkg as

   function get_date
      return date is
   begin
      return g_date;
   end get_date;

begin
   -- call the java function
   g_date := get_java_date;
end pkg;
/

The problem was that the package var "g_date" was set for the life of the
session. Can I force the var to be reset on a per query basis?

Followup:
You don't want to do it that way, you want to do it the way I did it

the function should return get_java_date.

The query should be written so as to call the function only ONCE.

Rating: 5
Ok here's the REAL problem. November 13, 2002
Reviewer: Alb from Perth, Australia
We store dates in GMT and use Java to calculate the dates into the querying
user's time zone dynamically when the data is queried. We therefore need to call
the Java function for every row in a table/view and we are finding that the
performance is slow.

Here's the proto type I've developed to test.

-- Java function ( our JVM is set to GMT ):-
CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED "DateUtility" AS
  import java.text.*;
  import java.util.*;
public class DateUtility
{
   public static java.sql.Timestamp applyTimeZoneToGMTDate(java.sql.Timestamp
dTimeStamp, String sTimezone) throws java.text.ParseException
   {
      if ((dTimeStamp != null) && (sTimezone != null)){

         DateFormat formatter = new SimpleDateFormat("MM-dd-yyyy HH:mm:ss a");
         formatter.setTimeZone(TimeZone.getTimeZone(sTimezone));

         String s = formatter.format(dTimeStamp);

         java.util.Date d = null;

         DateFormat formatter2 = new SimpleDateFormat("MM-dd-yyyy HH:mm:ss a");
         d = formatter2.parse(s);

         java.sql.Timestamp t = new java.sql.Timestamp( d.getTime());

         return t; }
      else {
         return null; }
   }

}
/

- PLSQL wrapper of Java func:-
create or replace function get_java_date(
      p_gmt_timestamp   Date,
      p_timezone        Varchar2)
      Return Date As
   Language Java
      Name 'DateUtility.applyTimeZoneToGMTDate(java.sql.Timestamp,
java.lang.String) return java.sql.Timestamp';
/

-- View that doesn't call the Java function:-
Create View v_all_objects
As
   Select owner,
          object_name,
          created
     From all_objects
/

-- View that does call the Java function:-
Create View v_all_objects_java
As
   Select owner,
          object_name,
          get_java_date(created,'Australia/Sydney') created
     From all_objects
/

-- Views compared:-

Select Count(*)
  From (Select   *
            From v_all_objects  /* Non Java */
           Where rownum < 30001
        Order By created);

  COUNT(*)
----------
     30000

Elapsed: 00:00:02.04

Select Count(*)
  From (Select   *
            From v_all_objects_java /* With Java */
           Where rownum < 30001
        Order By created);

  COUNT(*)
----------
     30000

Elapsed: 00:01:92.84

How can we speed up the java function?

Followup:
Oracle9i does this NATIVELY  -- timestamp with timezone.

I'm not much of a java programmer -- but look for ways to move new's and object
creations out of there and into static variables -- to avoid the overhead of
managing the memory and all...

Rating: 3
Improved by Java Programmer November 13, 2002
Reviewer: Alb from Perth, Australia
Same here ( not being much of a Java programmer ). One of our Java guys managed
to improve it by using static variables:-

CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED FPOWNER.DateUtility AS
  import java.text.*;
  import java.util.*;
public class DateUtility
{
   private static DateFormat localTimezoneFormatter = new
SimpleDateFormat("MM-dd-yyyy HH:mm:ss a");
   private static Map formatterCache = new HashMap(17);

   public static java.sql.Timestamp applyTimeZoneToGMTDate(java.sql.Timestamp
dTimeStamp, String sTimezone) throws java.text.ParseException
   {
      if ((dTimeStamp != null) && (sTimezone != null)){

         DateFormat formatter  = (DateFormat) formatterCache.get( sTimezone );
         if( formatter == null ) {
              formatter = new SimpleDateFormat("MM-dd-yyyy HH:mm:ss a");
              formatter.setTimeZone(TimeZone.getTimeZone(sTimezone));
              formatterCache.put( sTimezone, formatter );
         }

         String s = formatter.format(dTimeStamp);
         java.util.Date d = localTimezoneFormatter.parse(s);
         java.sql.Timestamp t = new java.sql.Timestamp( d.getTime());

         return t;
      }
      else {
         return null;
      }
   }
}
/

SQL> Select Count(*)
  2    From (Select   *
  3              From v_all_objects_java /* With Java */
  4             Where rownum < 30001
  5          Order By created);

  COUNT(*)
----------
     30000

Elapsed: 00:00:44.16

( down from 00:01:92.84 )

Alas, it's still not fast enough. We'll have to find another way. Many thanks
anyway Tom.

Rating: 4
Timezone and JDBC classes November 26, 2003
Reviewer: Jeroen from Singapore
Tom,

In order to make use of the data type TIMESTAMP WITH TIMEZONE within a Java
application you would need to use the oracle.sql.TIMESTAMPTZ classes in the JDBC
driver. The system architect in my company argues against this approach as he
claims open source tools (Hibernate) are not 'open' if they use vendor specific
extensions to the JDK. Instead the approach he is promoting is to store the GMT
date in a DATE column and the timezone in a VARCHAR2 column and perform the
conversion to local time inside the application. I pointed out that this would
preclude any third party application such as a reporting tool from being able to
independently access a local timezone date without also performing the same
logic. Apart from this argument is there any other you know of that can help me
persuade the architect to consider using Oracle's TIMESTAMP WITH TIMEZONE data
type ?

Followup:
hmm, "open"

"open" to me means many things.

but anyway, the dogma of "the database is a bit bucket", "we run on anything"
really irks me.  You cannot battle dogmatism, you can only squash it.

It would be interesting if your system "architect" were to design a bridge, or a
building.  I can see it now

"Nope, you cannot use that pre-fabricated door.  We'll make our own, its more
open you see"

"Nope, you cannot use that ready mix concrete.  We might not be able to get it
from somewhere else.  We'll build a mine and do it ourselves"

"No way, you cannot just buy that cable -- it'll be much better for us to have
these two guys build it for us in their spare time"

sigh, oh well.

Tell him to use strings -- string the data on the way in, string it on the way
out.  do whatever they want to it in java then.

Rating: 5
database death march November 26, 2003
Reviewer: reader from USA
recently saw an old software engineering book entitled "Death March" that
discussed how to handle the project you know is doomed to fail.  Maybe you can
publish  a one page "Database Development Death March" book.  We all know what
it would say.. Don't use bind variables and use the database as a bit bucket,
page three could say "Strive for Database Independence at all costs"

In conversations with infrastructure and systems guys, they never want the
database to be the sole source of content.  They all live in this great fear,
that even if the database is down, they want work to be able to continue.  So
all architects that design tools that are developed ensure these tools interact
with the file system first and foremost, and the database interactoin is a only
a second thought.  This is a difficult mentality to fight.

November 26, 2003 -- Thanks for the question regarding "Standby database", version 8.1.5
You Asked
Hi, Tom

I want to install a standby database. To ensure consistency
among the datafiles, the control file and the redo log files, I
have to make a consistent offline backup of my datafile. But I
cannot do it, because we have 7*24 database. Can I instead make
online backup of primary site (with begin backup and end backup
statements) and transfer copies of datafiles and archived log
files to standby site. When should I create control file for
standby: after begin backup or before?
What's happen when I set the Oracle database in begin backup
mode? What writes Oracle
in control file, redo log files and datafiles? I guess, Oracle
writes in control file, that online backup was began and Oracle
needs this entry in control file for recovery later. When I
create  a new control file for standby this entry is lost.
How can I guaranty the consistence of entire datafile with
archived redo log file without shutdown the database? In online
backup mode Oracle writes entire Oracle blocks in redo log file
and I can not become an inconsistent block later by recovery (on
standby site
too). Is it enough?
To my understanding. What writes Oracle in datafiles in online
backup mode.

Best regards.

Oleg.
and we said...
Please read
http://technet.oracle.com/doc/oracle8i_816/server.816/a76995/toc.htm

You do NOT need a consistent backup,  hot (inconsistent) backup
is sufficient.

You will get the standby controlfile AFTER you make the backup.
You will use the alter database create standby controlfile
statement to do it.  it is documented in the above document.

In hot backup mode, the only difference is

a) Oracle will not update the data file headers with checkpoint
information.
b) Oracle will generate more redo for a block in a tablespace
that is in backup mode the first time the block it modified.

Oracle writes normally to datafiles otherwise during a backup.

The standby database guide above walks through each step and
what is required in the setup of a standby database.
Review & Followup
Rating: 4
March 16, 2001
Reviewer: A reader from Hamburg, Germany

Rating: 3
Drawback of standby... November 26, 2003
Reviewer: Anuag from INDIA
Hi!

I am going thru RMAN. I've a doubt. Suppose my RMAN utility is corrupted, Is
there any way that I can recover thru rman backup...

Thanks and regards

Followup:
huh?

how does a "utility" become corrupt?

even so, just reinstall the software, you have the CD's?

November 26, 2003 -- Thanks for the question regarding "Inline Views Vs. Materialized Views", version 9i
You Asked
Hi Tom,

Can you please clarify the following:
1. What is the difference (in terms of performance) between using and inline
view in an SQL statement and putting the query of the inline view in a
materialized view and then using the materialized in the SQL statement?
2. What is the difference between using inline view in an SQL statement and
using WITH clause in the SQL statement using 9i?

Thanks in advance...

Basheer
and we said...
1) un-answerable and a total and complete apples to toaster oven comparision.

an inline view is just a query, just a sql statement.

a materialized view is a "pre-answered" query.

So, if you:

  select * from ( some-really-hard-to-execute-query-that-takes-5-minutes )

vs

create materialized view mv as
( some-really-hard-to-execute-query-that-takes-5-minutes )

and then

  select * from ( select * from mv )

it is going to be night and day different of course.  One answered the query at
runtime, the other answered the query last night and we just browse the answer

2)
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4423923392083
Review & Followup
Rating: 3
inlineview vs materialized view July 09, 2003
Reviewer: basheer from chennai, india
Thanks for your answer tom,

actually what i want is....

i am creating a mv,
CREATE MATERIALIZED VIEW MV_ACCOUNT_DELINQUENCY_CYCLE AS SELECT
    B.PORTFOLIO_GROUP_ID,
    A.SITE_ID,
    A.ACCOUNT_ID,
    A.CYCLE_NUMBER,
    TRIM(C.CAL_MONTH_STR) REPORT_PERIOD,
    LAG(A.DELINQUENCY_STATUS_ID) OVER (PARTITION BY
        B.PORTFOLIO_GROUP_ID,
        A.SITE_ID,
        A.CYCLE_NUMBER,
        TO_NUMBER(TO_CHAR(A.CYCLE_END_DATE, 'YYYYMM')) ORDER BY
        B.PORTFOLIO_GROUP_ID,
        A.SITE_ID,
        A.CYCLE_NUMBER,
        TO_NUMBER(TO_CHAR(A.CYCLE_END_DATE, 'YYYYMM')) ASC
        ) AS PRIOR_DELINQUENCY_STATUS_ID,
    A.DELINQUENCY_STATUS_ID,
    (CASE WHEN A.DELINQUENCY_STATUS_ID = 10 AND BANKRUPTCY_CHGOFF > 0 THEN
        'Y'
        ELSE 'N' END) BANKRUPTCY_FLAG,
    NVL(A.CYCLE_CLOSING_BAL, 0) CLOSING_BAL
FROM CYCLE_SUMMARY_FACT A,
     PORTFOLIOS_DIM B,
     CCRT_CALENDAR_DIM C
WHERE A.PORTFOLIO_ID = B.PORTFOLIO_ID
    AND A.CYCLE_END_DATE = C.CAL_DAY_DT
    AND A.ACCOUNT_STATUS_ID NOT IN (1, 2);

from that MV i am selecting some records,

see the following select statement
SELECT A.PORTFOLIO_GROUP_ID,
    A.SITE_ID,
    A.CYCLE_NUMBER,
    A.REPORT_PERIOD,
    A.PRIOR_DELINQUENCY_STATUS_ID,
    1000003 DELINQUENCY_STATUS_ID,
    'GWO' DELINQUENCY_STATUS_DESC,
    NVL(SUM(A.CYCLE_CLOSING_BAL),0) CLOSING_BAL,
    COUNT(*) AS NUM_ACCOUNTS,
    MAX(A.UPDATED_DATE) UPDATED_DATE
FROM MV_ACCOUNT_DELINQUENCY_CYCLE A,
    delinquency_status_dim B
WHERE A.DELINQUENCY_STATUS_ID = B.DELINQUENCY_STATUS_ID
    AND A.DELINQUENCY_STATUS_ID = 10
GROUP BY A.PORTFOLIO_GROUP_ID,
    A.SITE_ID,
    A.CYCLE_NUMBER,
    A.REPORT_PERIOD,
    A.PRIOR_DELINQUENCY_STATUS_ID,
    1000003,
    'GWO';
instead of selecting from MV can i go for INLINE VIEW?

or what i am doing is correct?

which one is faster?

thanks and regards
basheer

Followup:
Yes, you can go for an inline view -- however, a materialized view is just that
-- the query already RAN some time ago. when you query a MV, you don't have to
re-matierialize the view, it is there.

Using an inline view -- it would execute the query to gather the data at that
point in time.

Rating: 3
thanks July 11, 2003
Reviewer: s.basheer from chennai india
thanks tom,

can you tell which one is faster?
in your previous followup you didnt' tell which one is faster.
you told to go for inline view.

ok i am going for inline view,
but i am eager to know which is faster.

s.basheer

Followup:
*think about it*

mv = we ran the query yesterday, you are just querying the results of the work
we did yesterday.

inline view = we run the query right now, as you wait.

The answer to "which is faster" is IT DEPENDS, I can concoct examples that show
the MV is faster (that is the *most likely* outcome).  I can concoct examples
that show the inline view is faster (this would be less likely in general)

Rating: 3
What is a materialized view July 11, 2003
Reviewer: Peter from UK
Basheer

Perhaps the word 'view' is giving rise to your confusion. A materialized view
exists in exactly the same way as a table. It has size, it sits on the disk, it
could be indexed, it could be partitioned and so on. The select statement in
your example could just as easily start as
CREATE TABLE as SELECT ...

Your question could be restated as "what's faster - select from a view or select
from a table?"

Perhaps you should ask "am I better off creating a MV (or table) with the risk
that my data is not always up to date or run a slower query that is always in
sync with the underlying data?"

Rating: 1
MV July 11, 2003
Reviewer: Rich from Gregoria, NSW
In general, I like regular views better than mvs. MVs are too complicated
sometimes.

Followup:
gee, thats like saying "you know, the create index syntax is pretty complicated
- I don't like that so I'm not going to use them"

you cannot even being to remotely compare MV's with "regular views".  total
apples and oranges.

You can compare MV's to indexes -- MV's are like the indexes of your data
warehouse.  If you have a really big table and people do aggregates on it all
day long -- which would you rather use:

o the really big table.
o a table with 500 rows in it that already contained the aggregated data.

Rating: 5
MV versus tables July 14, 2003
Reviewer: A reader
hi Tom
was wondering what the difference is if we use MVs
versus if we just precalculate the results and store
them in a different table.
I can think of following differences
1. MVs - apps dont need to know, tables - they need to know
2. You loose on the "intelligence" built into MV - for example, if you ude
dimensions, you could get MVs to be used for queries that are not directly there
in the MV itself.
3. You loose on automatic refresh options and control
over them?

Any other major differences? Would there be any difference
in performance - I am thinking not.

thanx!:)

Followup:
1) correct
2) correct
3) correct

it is all about manageability and usability.

do you want to have to "train" all of your applications to use some new MV you
add tomorrow?  No, you just want them to start using them.

Rating: 3
materialized view November 26, 2003
Reviewer: Nikunj from India
Dear Tom,

I would like to understand more abt. materialized view.

i have below tables

SQL> desc invoice_mast
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 SRNO                                      NOT NULL NUMBER(20)
 INV_TYPE                                           VARCHAR2(5)
 INV_C_CODE                                         VARCHAR2(5)
 INV_N_CODE                                         NUMBER(10)
 INV_DATE                                           DATE
 CUSTCODE                                  NOT NULL NUMBER(10)
 G_PASS_NO                                          NUMBER(10)
 G_PASS_DATE                                        DATE
 INV_STATUS                                NOT NULL VARCHAR2(1)
 VEH_NO                                             VARCHAR2(20)

SQL> desc invoice_tran
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 SRNO                                      NOT NULL NUMBER(20)
 ITCODE                                    NOT NULL VARCHAR2(60)
 NO_OF_CARTON                              NOT NULL NUMBER(13,3)
 U_PER_CARTON                              NOT NULL NUMBER(12,2)
 KG_PER_CARTON                             NOT NULL NUMBER(13,3)
 UNITRATE                                  NOT NULL NUMBER(12,2)
 ASS_VALUE                                 NOT NULL NUMBER(12,2)

SQL> desc custmast
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 CUSTCODE                                  NOT NULL NUMBER(10)
 CUSTNAME                                  NOT NULL VARCHAR2(80)
 ADDRESS                                            VARCHAR2(150)
 CITY                                               VARCHAR2(80)
 DISTRICT                                           VARCHAR2(80)
 STATE                                              VARCHAR2(80)
 CSTNO                                              VARCHAR2(80)
 STNO                                               VARCHAR2(80)

SQL> desc item
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 ITCODE                                    NOT NULL VARCHAR2(60)
 ITDES                                     NOT NULL VARCHAR2(100)
 BRAND                                     NOT NULL VARCHAR2(20)
 SILVER                                    NOT NULL VARCHAR2(1)
 SAFFRON                                   NOT NULL VARCHAR2(1)
 PACKAGING                                 NOT NULL VARCHAR2(20)
 UNITPACK                                  NOT NULL NUMBER(12,2)
 RATE                                      NOT NULL NUMBER(12,2)

i had created mv as below

CREATE MATERIALIZED VIEW SALES_SUMMARY
BUILD IMMEDIATE
REFRESH FORCE
ON DEMAND
ENABLE QUERY REWRITE
AS
select a.custcode,d.brand,d.unitpack,d.packaging,
to_date(to_char(inv_date,'Mon-YYYY'),'Mon-YYYY') inv_date,
sum(no_of_carton) no_of_carton
from invoice_mast a,invoice_tran b,custmast c,item d
where a.srno=b.srno and a.custcode=c.custcode and b.itcode=d.itcode
group by a.custcode,to_date(to_char(inv_date,'Mon-YYYY'),'Mon-YYYY'),
d.brand,d.unitpack,d.packaging;

Now i am executing below query.

SQL> select custname,d.brand,d.unitpack,d.packaging,
  2  to_date(to_char(inv_date,'Mon-YYYY'),'Mon-YYYY'),sum(no_of_carton)
  3  from ex2003.invoice_mast a,ex2003.invoice_tran b,ex2003.custmast
c,ex2003.item d
  4  where a.srno=b.srno and a.custcode=c.custcode and b.itcode=d.itcode
  5  group by custname,to_date(to_char(inv_date,'Mon-YYYY'),'Mon-YYYY'),
  6  d.brand,d.unitpack,d.packaging
  7* order by 1,2;

my execution plan as below.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=81 Card=11820 Bytes=
          2564940)

   1    0   SORT* (ORDER BY) (Cost=81 Card=11820 Bytes=2564940)        :Q112560
                                                                       05

   2    1     SORT* (GROUP BY) (Cost=81 Card=11820 Bytes=2564940)      :Q112560
                                                                       04

   3    2       SORT* (GROUP BY) (Cost=81 Card=11820 Bytes=2564940)    :Q112560
                                                                       03

   4    3         HASH JOIN* (Cost=17 Card=11820 Bytes=2564940)        :Q112560
                                                                       03

   5    4           TABLE ACCESS* (FULL) OF 'CUSTMAST' (Cost=2 Card=93 :Q112560
          8 Bytes=51590)                                               00

   6    4           HASH JOIN* (Cost=15 Card=11820 Bytes=1914840)      :Q112560
                                                                       03

   7    6             TABLE ACCESS* (FULL) OF 'INVOICE_MAST' (Cost=7 C :Q112560
          ard=1952 Bytes=68320)                                        01

   8    6             HASH JOIN* (Cost=8 Card=11820 Bytes=1501140)     :Q112560
                                                                       03

   9    8               TABLE ACCESS* (FULL) OF 'ITEM' (Cost=1 Card=18 :Q112560
          8 Bytes=12972)                                               02

  10    8               TABLE ACCESS* (FULL) OF 'INVOICE_TRAN' (Cost=7 :Q112560
           Card=11820 Bytes=685560)                                    03

   1 PARALLEL_TO_SERIAL            SELECT A1.C0 C0,A1.C1 C1,A1.C2 C2,A1.C3 C3,A
                                   1.C4 C4,A1.C5 C5 FROM :Q11256004 A1

   2 PARALLEL_TO_PARALLEL          SELECT /*+ CIV_GB */ A1.C0 C0,A1.C1 C1,SUM(S
                                   YS_OP_CSR(A1.C5,0)) C2,A1.C4 C3,A1.C

   3 PARALLEL_TO_PARALLEL          SELECT /*+ PIV_GB */ A1.C0 C0,A1.C4 C1,TO_DA
                                   TE(TO_CHAR(A1.C2,'Mon-YYYY'),'Mon-YY

   4 PARALLEL_COMBINED_WITH_PARENT
   5 PARALLEL_TO_PARALLEL          SELECT /*+ NO_EXPAND ROWID(A1) */ A1."CUSTCO
                                   DE" C0,A1."CUSTNAME" C1 FROM "EX2003

   6 PARALLEL_COMBINED_WITH_PARENT
   7 PARALLEL_TO_PARALLEL          SELECT /*+ NO_EXPAND ROWID(A1) */ A1."SRNO"
                                   C0,A1."CUSTCODE" C1,A1."INV_DATE" C2

   8 PARALLEL_COMBINED_WITH_PARENT
   9 PARALLEL_TO_PARALLEL          SELECT /*+ NO_EXPAND ROWID(A1) */ A1."ITCODE
                                   " C0,A1."UNITPACK" C1,A1."BRAND" C2,

  10 PARALLEL_COMBINED_WITH_PARENT

i would like to know why all tables are accessing full ?

And in my below query why only sales_summary mv is accessing ?

SQL> select a.custcode,d.brand,d.unitpack,d.packaging,
  2  to_date(to_char(inv_date,'Mon-YYYY'),'Mon-YYYY') inv_date,
  3  sum(no_of_carton) no_of_carton
  4  from invoice_mast a,invoice_tran b,custmast c,item d
  5  where a.srno=b.srno and a.custcode=c.custcode and b.itcode=d.itcode
  6  group by a.custcode,to_date(to_char(inv_date,'Mon-YYYY'),'Mon-YYYY'),
  7* d.brand,d.unitpack,d.packaging;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=4814 Bytes=15
          8862)

   1    0   TABLE ACCESS (FULL) OF 'SALE_SUMMARY' (Cost=4 Card=4814 By
          tes=158862)

In oracle magazine of sept/oct 2003 there are the example on mv in there example
if they select diff.
column of the same table still mv is accessing so why in my query it accessing
all tables ?

Regards,
Nikunj

Followup:
your mv groups by custcode, .....

your query groups by custname, .......

There is no rule saying the results would be the SAME.  It would be a bug if the
MV was used when you grouped by a completely different set of columns.

If you group by a, b, c, d in the MV, you can group by

a,b,c,d
a,b,c
a,b
a,c
a,d
a
b,c,d
b,c
b,d
b
..... and so on in your queries and reuse the MV in many cases -- however, you
CANNOT

group by Z,b,c,d

and expect the MV to be used!!!  Z is "gone", not in available in the MV, the MV
aggregated it away

ps:

this
to_date(to_char(inv_date,'Mon-YYYY'),'Mon-YYYY')
is a slow way to say
trunc(inv_date)

select trunc(created) from all_objects

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.03          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     2108      3.96       3.95          0     149340          0       31601
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2110      3.98       3.98          0     149340          0       31601
********************************************************************************
select to_date(to_char(created,'Mon-yyyy'),'Mon-yyyy') from all_objects

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.06          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     2108      4.91       4.74          0     149340          0       31601
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2110      4.92       4.81          0     149340          0       31601

just use TRUNC(), don't do date to string to date conversions.

November 26, 2003 -- Thanks for the question regarding "Full sql text from v$sqltext", version 9
You Asked
Hi,

When querying v$sqltext the sql statement is broken up into "pieces".  I want to
write a query that will show all of the text as one statement and not broken up
by pieces.  So, insead of showing:

piece  sql_text
-----  --------
0       select user
1       from dual
0       select sysdate
1       from dual

I want my query to return:

sql_statement
--------------
select user from dual
select sysdate from dual

Thanks.
and we said...
varchar's are limited to 4000 bytes, so this solution will be limited to those.
you could possibly rewrite using clobs if you like:

ops$tkyte@ORA920> create or replace function sql_query( p_address in
v$sqltext.address%type,
  2                                        p_hash_value in
v$sqltext.hash_value%type )
  3  return varchar2
  4  as
  5          l_text  long;
  6  begin
  7          for x in ( select sql_text from v$sqltext where address = p_address
and hash_value = p_hash_value order by piece )
  8          loop
  9                  l_text := l_text || x.sql_text;
 10                  exit when length(l_text) > 4000;
 11          end loop;
 12          return substr( l_text, 1, 4000 );
 13  end;
 14  /

Function created.

ops$tkyte@ORA920> select a.*, sql_query(address,hash_value) sql_query
  2    from (
  3  select address, hash_value, count(*) cnt
  4    from v$sqltext
  5   group by address, hash_value
  6   having count(*) > 1
  7   order by 3 desc
  8         ) a
  9   where rownum <= 10
 10  /

ADDRESS  HASH_VALUE        CNT
-------- ---------- ----------
SQL_QUERY
--------------------------------------------------------------------------------
5CA92E6C 4027377770         26
select '"'||name||'" '||decode(type#,1,decode(charsetform,2,'NVARCHAR2('||decode
(bitand(property,8388608),8388608,decode(spare3,0,1,spare3)||')','BYTECOUNT=>'||
decode(length,0,1,length)||')'),'VARCHAR2('||decode(bitand(property,8388608),838
8608,decode(spare3,0,1,spare3)||' char)',decode(length,0,1,length)||' byte)')),2
,decode(scale,null,decode(precision#,null,'NUMBER','FLOAT('||precision#||')'),'N
UMBER('||decode(precision#,null,'38', precision#)||','||scale||')'),8,'LONG',9,'
VARCHAR('||(decode(length,0,1,length))||')',12,'DATE',23,'RAW('||length||')',24,
'LONG RAW',69,'ROWID',96,decode(charsetform,2,'NCHAR('||decode(bitand(property,8
388608),8388608,decode(spare3,0,1,spare3)||')','BYTECOUNT=>'||decode(length,0,1,
length)||')'),'CHAR('||decode(bitand(property,8388608),8388608,decode(spare3,0,1
,spare3)||' char)',decode(length,0,1,length)||' byte)')),97,'VARCHAR('||(decode(
length,0,1,length))|| ')',105,'MLSLABEL',106,'MLSLABEL',112,decode(charsetform,2
,'NCLOB','CLOB'),113,'BLOB',114,'BFILE',115,decode(charsetform,2,'NCFILE','CFILE
'),178,'TIME('||scale||')',179,'TIME('||scale||') WITH TIME ZONE',180,'TIMESTAMP
('||scale||')',181,'TIMESTAMP('||scale||') WITH TIME ZONE',231,'TIMESTAMP('||sca
le||') WITH LOCAL TIME ZONE',182,'INTERVAL YEAR('||precision#||') TO MONTH',183,
'INTERVAL DAY('||precision#||') TO SECOND('||scale||')',208,'UROWID('||spare1||'
)','UNDEFINED')||decode(null$,0,'',' NOT NULL')||decode(bitand(property, 32), 0,
 ',', ' HIDDEN,'),name, type#, intcol#, null$, bitand(property, 32) from col$ wh
ere obj#=:1 and (bitand(property,32) = 0 or bitand(property, 4194304) <> 0) orde
r by col#

...............

Alternatively, if you assume some reasonable maximum for the number of pieces
(eg: 62.5 * 64 = 4000, so 62 is "reasonable")

ops$tkyte@ORA920> select *
  2    from (
  3  select address, hash_value, count(*) cnt,
  4         max(decode(piece,0,sql_text))||
  5         max(decode(piece,1,sql_text))||
  6         max(decode(piece,2,sql_text))||
  7         max(decode(piece,3,sql_text))||
  8         max(decode(piece,4,sql_text))||
  9         max(decode(piece,5,sql_text))||
 10         max(decode(piece,6,sql_text))||
 11         max(decode(piece,7,sql_text))||
 12         max(decode(piece,8,sql_text))||
 13         max(decode(piece,9,sql_text))||
 14         max(decode(piece,10,sql_text))||
 15         max(decode(piece,11,sql_text))||
 16         max(decode(piece,12,sql_text))||
 17         max(decode(piece,13,sql_text))||
 18         max(decode(piece,14,sql_text))||
 19         max(decode(piece,15,sql_text))||
 20         max(decode(piece,16,sql_text))||
 21         max(decode(piece,17,sql_text))||
 22         max(decode(piece,18,sql_text))||
 23         max(decode(piece,19,sql_text))||
 24         max(decode(piece,20,sql_text))||
 25         max(decode(piece,21,sql_text))||
 26         max(decode(piece,22,sql_text))||
 27         max(decode(piece,23,sql_text))||
 28         max(decode(piece,24,sql_text))||
 29         max(decode(piece,25,sql_text))||
 30         max(decode(piece,26,sql_text))||
 31         max(decode(piece,27,sql_text))||
 32         max(decode(piece,28,sql_text))||
 33         max(decode(piece,29,sql_text))||
 34         max(decode(piece,30,sql_text))||
 35         max(decode(piece,31,sql_text))||
 36         max(decode(piece,32,sql_text))||
 37         max(decode(piece,33,sql_text))||
 38         max(decode(piece,34,sql_text))||
 39         max(decode(piece,35,sql_text))||
 40         max(decode(piece,36,sql_text))||
 41         max(decode(piece,37,sql_text))||
 42         max(decode(piece,38,sql_text))||
 43         max(decode(piece,39,sql_text))||
 44         max(decode(piece,40,sql_text))||
 45         max(decode(piece,41,sql_text))||
 46         max(decode(piece,42,sql_text))||
 47         max(decode(piece,43,sql_text))||
 48         max(decode(piece,44,sql_text))||
 49         max(decode(piece,45,sql_text))||
 50         max(decode(piece,46,sql_text))||
 51         max(decode(piece,47,sql_text))||
 52         max(decode(piece,48,sql_text))||
 53         max(decode(piece,49,sql_text))||
 54         max(decode(piece,50,sql_text))||
 55         max(decode(piece,51,sql_text))||
 56         max(decode(piece,52,sql_text))||
 57         max(decode(piece,53,sql_text))||
 58         max(decode(piece,54,sql_text))||
 59         max(decode(piece,55,sql_text))||
 60         max(decode(piece,56,sql_text))||
 61         max(decode(piece,57,sql_text))||
 62         max(decode(piece,58,sql_text))||
 63         max(decode(piece,59,sql_text))||
 64         max(decode(piece,60,sql_text))||
 65         max(decode(piece,61,sql_text))||
 66         max(decode(piece,62,sql_text)) sql_text
 67    from v$sqltext
 68   group by address, hash_value
 69   order by 3 desc
 70   )
 71   where rownum = 1
 72  /

ADDRESS  HASH_VALUE        CNT
-------- ---------- ----------
SQL_TEXT
-------------------------------------------------------
5EE4FB8C 2093455374         42
select *   from ( select address, hash_value, count(*)
cnt,        max(decode(piece,0,sql_text))||
max(decode(piece,1,sql_text))||
max(decode(piece,2,sql_text))||
max(decode(piece,3,sql_text))||
max(decode(piece,4,sql_text))||
max(decode(piece,5,sql_text))||
max(decode(piece,6,sql_text))||
max(decode(piece,7,sql_text))||
max(decode(piece,8,sql_text))||
max(decode(piece,9,sql_text))||
max(decode(piece,10,sql_text))||
max(decode(piece,11,sql_text))||
max(decode(piece,12,sql_text))||
max(decode(piece,13,sql_text))||
max(decode(piece,14,sql_text))||
max(decode(piece,15,sql_text))||
max(decode(piece,16,sql_text))||
max(decode(piece,17,sql_text))||
max(decode(piece,18,sql_text))||
max(decode(piece,19,sql_text))||
max(decode(piece,20,sql_text))||
max(decode(piece,21,sql_text))||
max(decode(piece,22,sql_text))||
max(decode(piece,23,sql_text))||
max(decode(piece,24,sql_text))||
max(decode(piece,25,sql_text))||
max(decode(piece,26,sql_text))||
max(decode(piece,27,sql_text))||
max(decode(piece,28,sql_text))||
max(decode(piece,29,sql_text))||
max(decode(piece,30,sql_text))||
max(decode(piece,31,sql_text))||
max(decode(piece,32,sql_text))||
max(decode(piece,33,sql_text))||
max(decode(piece,34,sql_text))||
max(decode(piece,35,sql_text))||
max(decode(piece,36,sql_text))||
max(decode(piece,37,sql_text))||
max(decode(piece,38,sql_text))||
max(decode(piece,39,sql_text))||
max(decode(piece,40,sql_text))||
max(decode(piece,41,sql_text))||
max(decode(piece,42,sql_text))||
max(decode(piece,43,sql_text))||
max(decode(piece,44,sql_text))||
max(decode(piece,45,sql_text))||
max(decode(piece,46,sql_text))||
max(decode(piece,47,sql_text))||
max(decode(piece,48,sql_text))||
max(decode(piece,49,sql_text))||
max(decode(piece,50,sql_text))||
max(decode(piece,51,sql_text))||
max(decode(piece,52,sql_text))||
max(decode(piece,53,sql_text))||
max(decode(piece,54,sql_text))||
max(decode(piece,55,sql_text))||
max(decode(piece,56,sql_text))||
max(decode(piece,57,sql_text))||
max(decode(piece,58,sql_text))||
max(decode(piece,59,sql_text))||
max(decode(piece,60,sql_text))||
max(decode(piece,61,sql_text))||
max(decode(piece,62,sql_text)) sql_text   from
v$sqltext  group by address, hash_value  order by 3
desc  )  where rownum = 1

1 row selected.

ops$tkyte@ORA920>
Review & Followup
Rating: 3
silly design December 01, 2003
Reviewer: Mikito Harakiri
They have v$sql.sql_fulltext clob in 10g. Why wasn't it there from the very
beginning is a mystery to me.

November 26, 2003 Tony -- Thanks for the question regarding "What tables have been accessed in last n number of hours?", version 8.1.6
You Asked
Tom,
I am working in DW application. Users submit ad-hoc queries from third party
tool to Oracle DB (8.1.6.3). I want to know what tables are being accessed for
specific period of time (if possible, along with query - some of the queries are
quite large in size i.e., > 1000 characters). How can I do this?
Thanks
and we said...
In 816, you can only enable auditing (audit select).

In 9i, you can capture the queries using fine grained auditing to boot.

In 816, you could use statpack to sort of see the sql that was executed in a
given window of time.  Or you can do it yourself -- copy v$sql, then copy it
later.  Join the two copies together and only show results where the executes
have gone up.  It'll get "most of them"  (using v$sqltext_with_newlines, you can
get the entire query)
November 26, 2003 raji -- Thanks for the question regarding "lost control files / inconsisitent controlfiles", version 8.1.7
You Asked
Hi,

i have a doubt about bringing up the database when controlfiles are lost.

suppose if i have not taken alter database backup control file to trace and i
lost the controlfiles, is there  a way to bringup the database?
can we recreate the controlfile by any means?

One more issue very recently i encountered is,

power went off. obviously shutdown abort happend in the databse. when the power
came and when i tried to make the database up, i got an error saying
inconsistenet controlfiles. ctrl01.ora differ from ctrl02.ora

i didn't know the reason why it gave such error. users were waiting so i edited
the init.ora to point to other two controlfiles which were consisitent and made
the database up.

But my question is why such thing happend? what is the solution to avoide such
errors?

please reply. thanks in advance
raji
and we said...
you would issue a CREATE CONTROLE sql statment to tell us where everything is.
To see what it looks like, you would alter database backup controlfile to trace;
 that'll show you what would be needed.

NOTHING however beats correct and proper backups (which include of course a
backup of the controlfile TO TRACE for just such a contigency!)

There is exactly one thing a DBA is not allowed to mess up -- it is RECOVERY.
If you cannot recover, from pretty much any circumstance (loss of a single file,
bad block, total loss of system, whatever) -- you aren't a DBA yet :)

When the power went off -- we were writing to the controlfiles. One of them got
updated -- the other not.  That is exactly why we want more then one controlfile
-- in the event one of them gets whacked.  You did exactly what you should have
-- you point to the good controlfiles.
Review & Followup
Rating: 3
thanks a lot. November 26, 2003
Reviewer: A reader

November 26, 2003 Ravi -- Thanks for the question regarding "How can we execute specific block of code of a Package only once when we first call that package", version 8.1.7
You Asked
Hi Tom,

Is there any way of executing a speicific block of code only once when we first
call a pacakage, like a global variable in a Package. Can we add that small
block of code in either package specification or body.

Hope I am clear with my question.

Thanks in advance,
Ravi Kumar.
and we said...
it is called "elaboration code" in Ada (a language some people might be familar
with...)

In PLSQL you can code like this:

create package body foo
as

  ... variables

   .... procedures/functions

BEGIN
   .... block of code to be run the first time the package
   .... is referenced in a session
end;
/

For example:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package demo_pkg
  2  as
  3      procedure p;
  4  end;
  5  /

Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package body demo_pkg
  2  as
  3
  4  x number;
  5
  6
  7  procedure p
  8  is
  9  begin
 10      dbms_output.put_line( 'Hi, from procedure p!' );
 11  end;
 12
 13
 14  begin
 15      select count(*) into x from all_users;
 16
 17      dbms_output.put_line( 'Hi, from elaboration code! we set x = '  || x );
 18  end;
 19  /

Package body created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec demo_pkg.p
Hi, from elaboration code! we set x = 23
Hi, from procedure p!

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec demo_pkg.p
Hi, from procedure p!

PL/SQL procedure successfully completed.

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

the second time we call P, no elaboration code was executed.
Review & Followup
Rating: 5
December 08, 2001
Reviewer: A reader

Rating: 5
How does it work like this way? December 08, 2001
Reviewer: Sean Bu from atlanta

Followup:
sorry, that comment does not compute -- what do you mean.

Rating: 5
December 09, 2001
Reviewer: Sean Bu from Atlanta
I wanted to say, how Oracle only executes one block instead of the two in the
procedure as the user exec a more than once?

Followup:
The anonymous block in the package is "startup" code.

By design, by the documented behavior, this code runs ONCE upon the
initialization of the package.  Only the first time you touch the package in
your session will this code run.

Rating: 5
Wonderful December 09, 2001
Reviewer: Pullaiah Yetagella from Chicago

Rating: 5
Clever December 10, 2001
Reviewer: Jim from Australia
Pretty tricky Tom....
Might throw that one in for an interview question
I wonder how many people would get a question about when
are both dbms_output executed ?

Rating: 5
December 10, 2001
Reviewer: A reader

Rating: 5
Nifty. November 04, 2002
Reviewer: Kashif from Houston, TX
Way cool. Great way to initialize pl/sql tables/arrays as well. Any performance
considerations on this? Thanks.

Kashif

Followup:
no, not really - other then of course the code will run and if the code is
slow....  the first hit on the package will be slow.

Rating: 4
Nothing wrong with calling its own procedure in init ? November 04, 2002
Reviewer: Robert from PA
Tom, there is nothing wrong, logically or technically, with
running "foo" in initialization like below ?

create package body foo
as

procedure foo
.... ;

BEGIN
   .... block of code to be run the first time the package
   .... is referenced in a session

   foo;

end;
/

Followup:
no problem.

ops$tkyte@ORA920.US.ORACLE.COM> create or replace package demo_pkg
  2  as
  3          procedure p;
  4  end;
  5  /

Package created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace package body demo_pkg
  2  as
  3
  4  procedure foo
  5  is
  6  begin
  7          dbms_output.put_line( 'First time!!!' );
  8  end;
  9
 10  procedure p
 11  is
 12  begin
 13          dbms_output.put_line( 'p to the rescue' );
 14  end;
 15
 16  begin
 17          foo;
 18  end;
 19  /

Package body created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> exec demo_pkg.p
First time!!!
p to the rescue

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> exec demo_pkg.p
p to the rescue

PL/SQL procedure successfully completed.

Rating: 4
November 04, 2002
Reviewer: Bhagat Singh from Delhi,India.
Simply great.......

Big problem solved in ticks. Excellent.

Rating: 2
Error February 28, 2003
Reviewer: priyaramnan2k2 from TN,India
i am getting an error what shall i do ??

create or replace package pkg_samp as
procedure a;
end;
/
create or replace package body pkg_samp as
procedure a
begin
   null;
end;
end;
/
Warning: Package Body created with compilation errors.

SQL> show error
end;
 *
ERROR at line 6:
ORA-00942: table or view does not exist
exec pkg_samp.a;
begin pkg_samp.a; end;

*
ERROR at line 1:
ORA-04068: existing state of packages has been discarded
ORA-04063: package body "CMKTG.PKG_SAMP" has errors
ORA-06508: PL/SQL: could not find program unit being called
ORA-06512: at line 1

Followup:
get  your dba to correctly install your database.  If you get a ora 942 on show
errors, that sort of means that your database isn't quite right.

Also -- one would expect a "Package Created" message after the create or replace
package pkg_same command - so, i would guess the error is that you are somehow
improperly submitting the code -- so that sqlplus if that is what you are using
is seeing the entire

create or replace package pkg_samp as
procedure a;
end;
/
create or replace package body pkg_samp as
procedure a
begin
   null;
end;
end;
/

as a single piece of code -- perhaps you have a blank in column one or something
so that the first / is not actually running that create statement.

Rating: 5
Part of a job October 06, 2003
Reviewer: Pat
Can this work as part of a package that is run by a dbms job (the job running
say every 5 seconds ) ?

You said "Only the first time you touch the package in
your session will this code run. " , therefore how will a sheduled job handle
this ?

Thanks

Followup:
the package states are reset, the entire session state is reset, after a job is
run.  consider:

ops$tkyte@ORA920> create table msg( txt varchar2(80) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package foo
  2  as
  3          procedure bar;
  4  end;
  5  /

Package created.

ops$tkyte@ORA920> create or replace package body foo
  2  as
  3          procedure bar
  4          is
  5          begin
  6                  null;
  7          end;
  8  begin
  9          insert into msg values ( 'package loaded at ' || systimestamp );
 10  end;
 11  /

Package body created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> variable n number
ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from msg;

no rows selected

ops$tkyte@ORA920> exec dbms_job.submit( :n, 'foo.bar;', sysdate,
'sysdate+1/24/60/60' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_lock.sleep(60)

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from msg;

TXT
--------------------------------------------------------------------------------
package loaded at 06-OCT-03 01.37.53.818367 PM -04:00
package loaded at 06-OCT-03 01.37.59.024645 PM -04:00
package loaded at 06-OCT-03 01.38.04.132548 PM -04:00
package loaded at 06-OCT-03 01.38.09.253830 PM -04:00
package loaded at 06-OCT-03 01.38.14.374901 PM -04:00
package loaded at 06-OCT-03 01.38.19.495797 PM -04:00
package loaded at 06-OCT-03 01.38.24.616762 PM -04:00
package loaded at 06-OCT-03 01.38.29.738252 PM -04:00
package loaded at 06-OCT-03 01.38.34.859002 PM -04:00
package loaded at 06-OCT-03 01.38.39.980311 PM -04:00
package loaded at 06-OCT-03 01.38.45.101583 PM -04:00
package loaded at 06-OCT-03 01.38.50.222504 PM -04:00

12 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_job.remove( :n );

PL/SQL procedure successfully completed.

see how the package startup code was run over and over again...

Rating: 4
Sub Procedures November 03, 2003
Reviewer: Mohan from India
A friend asked me this question, whether it is possible to create a sub
function/program inside a function/program like in Pascal.

Mohan

Followup:
yes

create procedure p1
as
   procedure p2
   is
   begin
       ...
   end;
begin
   p2;
end;
/

Rating: 5
Memory issues when populating arrays in the package init November 26, 2003
Reviewer: Stephane from Ottawa, ON
Hi Tom,

I've been using the package init section in some of my packages to populate some
associative arrays for things like my gui text snippets, things that are
retrieved on every page call... to avoid context switches between PL/SQL and
SQL, kind of like a data cache I guess (a typical PSP page can lookup the array
10 - 15 times) I bulk collect the data.

We're probably dealing with about 100K of data in total...

We're running 9iAS (9.0.3) and 9iR2 database on AIX. My concern is regarding
memory. I read in this thread that the package init code is run the first time
the package is invoked in a session. Is there a chance we run into a situation
with our configuration where we may use up all available memory? How are
sessions handled in a stateless web app when dealing with multiple concurrent
users? Is there a standard ratio, ie 10 sessions for every 1000 users.. is this
something that can be controlled?

Thanks for the info

Followup:
of course you can use up all of your memory -- memory is a finite resource.

I cannot answer the last question as it depends entirely on the technology used
to connect to the database (using a java connection pool?  mod_plsql?  something
else?) and how ACTIVE you are.  Asktom (which runs mod_plsql 100% and supports
many applications) does about 5,000 'concurrent' users (if you look at a one
hour window), but:

sys@ASKUS> select * from v$license
  2  /

SESSIONS_MAX SESSIONS_WARNING SESSIONS_CURRENT SESSIONS_HIGHWATER  USERS_MAX
------------ ---------------- ---------------- ------------------ ----------
           0                0               49                 79          0

at most, I have had 80 sessions since our last bounce 6 weeks ago.  Other
systems might have more, others less.

Rating: 5
Thanks November 26, 2003
Reviewer: Stephane
Sorry, I forgot to include some important details. We are using mod_plsql 100%
as well... no JAVA (trying to keep it simple)

I don't think we'll ever have as many concurrent sessions as asktom... so I
think we'll be ok.

I'll take a look a the view and see what we get...

Thanks

November 26, 2003 Kishan -- Thanks for the question regarding "A matter of access", version 8.1.7
You Asked
Hi Tom:

Is it possible to grant access privilege in such a way that a person will be
able to describe a table/view, but not be able to query, across many schemas in
the database?

Its for a data-modeler for our organization We have various departments and
users, and our database has many schemas (inherited from our older legacy
systems), hence, this issue is quite important for us.

We're planning to purchase a software which will reverse engineer our database
to produce ER diagram of the datamodel, to be published in the intranet for our
developers.  We would like this person to have access to the structure of the
tables,views and other objects necessary to build/modify the datamodel, but have
no privilege to query.

Thank you for any help in this regard.

Take care,
Kishan.
and we said...
You can use fine grained access control.  You would put a policy on the table
that basically says:

begin
   if ( user = 'that guy' ) then
       return 1=0;
   else
       return null;
   end if;
end;
/

see
http://asktom.oracle.com/~tkyte/article2/index.html
Review
      & Followup

      Rating:
      4
Thanks....  September 18, 2002
Reviewer:
      Kishan  from Singapore
Hi Tom:

I concur with your suggestion. We've 1000+ tables in our database. How to assign
FGAC to all those tables? One at a time or is there any other way we can assign
it to all tables at one go, since all the user tables in the database are
required for this user to reverse engineer.

Thank you.

          Followup:

you can create one function.

You can write a query that generates all of the 1000+ dbms_rls.add_policy calls
you need to make.  Like a trigger, this is done at the object level

      Rating: 4
How about the
      simpler...  September 18, 2002
Reviewer:
      Kevin  from Reston, VA
export --> import rows=N ????

Why would you want to muck with your production schema when it is so easy to
make an exact copy?

In any case, if you wanted to implement the policy function, you'd only have to
create the function once, but you would have to script it to apply it to all
tables...

          Followup:

that would work -- if they setup another database for this guy and did a full
export with rows=n and then imported that.

you would get the structure, but no data.

      Rating:
      4
Reader  September 19, 2002
Reviewer:
      A reader
It might be simpler to grant select on the table/views
to the user. Then insert a row in
system.product_user_profile to DISABLE select on that
particular USER

A Simple Test
==============

SQL> create user a identified by a;

User created.

SQL> grant create session to a;

Grant succeeded.

SQL> grant dba to b identified by b;

Grant succeeded.

SQL> connect b/b
Connected.

SQL> create table b_t (x int);

Table created.

SQL> grant select on b_t to public;

Grant succeeded.

SQL> connect a/a
Connected.

SQL>  desc b.b_t
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 X                                                  NUMBER(38)

SQL> select  * from b.b_t;

no rows selected

SQL> l
  1  insert into system.product_user_profile values
  2* ('SQL*Plus','A','SELECT',null,null,'DISABLED',null,null)
SQL> /

1 row created.

SQL> commit;

Commit complete.

SQL>
SQL> connect a/a
Connected.
SQL> desc b.b_t
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 X                                                  NUMBER(38)

SQL> select * from b.b_t;
SP2-0544: invalid command: select
SQL>

          Followup:

that'll only succeed in giving you a false sense of security and not much else.

(try begin for x i ( select ..... ) and see what happens, then turn off PLSQL as
well but....)

that data modeler will just grab TOAD or something and away they go -- Product
user profile is pretty much virtually useless with the plethora of tools out
there today.  Maybe way back when -- when there was SQLPlus and Forms and that
was about it as far as accessing the database, this was useful.  Today -- there
are way too many ways around it. (FGAC that I demonstrated is the method that
works with ALL tools ALL the time, no way around it)

      Rating: 5
What can Oracle
      Standard Edition do?  October 17, 2002
Reviewer:
      A Reader  from USA
As stated in your book and in Oracle document, the FGAC only works for EE and PE
not for SE.  We have a SE database that needs the access control, eg., other
than our application programs, we don't want users access our database via
SQLPLUS, TOAD or other SQL tools.  Can you help us with some suggestions.
Please don't say "buy a EE".
Thanks you very much in advance.

          Followup:

crickets then... chirp chirp chirp....

sorry -- don't know what to say.  guess you could use a context, and create
views of all tables that look like:

select * from t where sys_context('your_ctx','xxx') = 1;

if the context isn't set by your application, no data found.

      Rating: 4
How is this
      done exactly?  November 26, 2003
Reviewer:
      Andy
Tom, you said:

"if the context isn't set by your application..."

How exactly is this done? I've looked on this site, and haven't been able to
find an example. I've seen you set the user and various other information, but
not the application name. I've tried using the "program" entry from my session
view, but have noticed that that can be easily changed by re-naming the .exe to
something else.

          Followup:

dbms_session.set_context

dbms_application_info (to set client_info, action, module in v$session)

November 26, 2003 Robert -- Thanks for the question regarding "BULK
      COLLECT fetch is not faster", version 8.1.7

      You Asked
Hi Tom,

I am doing experiments with BULK COLLECT fetching, comparing single row fetches
with BULK COLLECT fetches. After running many tests using different sizes of
'LIMIT' parameter, I am finding that the bulk collect runs slower in some cases
than the single row fetch. I am running the comparisons interleaved with each
other to make sure the differences don't have to do with data being in the
buffer, server load, etc. It appears that the worst running bulk collect fetches
are those with joined tables returning many bytes of data (and/or lots of
columns (?)).

Based on the above introduction, I have two questions, please.
1. Are you aware of any conditions that would cause BULK COLLECT fetches to run
slower on average than single row selects?
2. Is it possible for developers to 'hose' the database by creating large pl/sql
tables in thier code?

Thanks!

Robert.

      and we said...
I've never seen it take longer personally...

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      type onameArray is table of all_objects.object_name%type index by
binary_integer;
  3
  4      l_owner onameArray;
  5      l_object_name onameArray;
  6      l_subobject_name onameArray;
  7      l_object_type onameArray;
  8      l_timestamp onameArray;
  9      l_status onameArray;
 10      l_temporary onameArray;
 11      l_generated onameArray;
 12      l_secondary onameArray;
 13
 14      cursor c is select owner, object_name, subobject_name,
 15                         object_type, timestamp, status, temporary,
 16                         generated, secondary
 17                    from all_objects;
 18
 19      l_start number;
 20  begin
 21      insert into run_stats select 'before', stats.* from stats;
 22
 23      l_start := dbms_utility.get_time;
 24      for i in 1 .. 1
 25      loop
 26          for x in c
 27          loop
 28              null;
 29          end loop;
 30      end loop;
 31      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 32
 33
 34      insert into run_stats select 'after 1', stats.* from stats;
 35      l_start := dbms_utility.get_time;
 36      for i in 1 .. 1
 37      loop
 38          open c;
 39          loop
 40              fetch c BULK COLLECT into l_owner, l_object_name,
l_subobject_name,
 41                                        l_object_type, l_timestamp, l_status,
 42                                        l_temporary, l_generated, l_secondary
limit 100;
 43              exit when c%notfound;
 44          end loop;
 45          close c;
 46      end loop;
 47      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 48
 49      insert into run_stats select 'after 2', stats.* from stats;
 50  end;
 51  /
667 hsecs
473 hsecs

PL/SQL procedure successfully completed.

Can you provide a case that does? (all_objects is a nasty join, returns tons of
data)....

so

1) no

2) yes, definitely.  RAM cram.  Those tables take lots of storage.
Review &
      Followup

      Rating: 3
Depends how
      many columns you fetch...  April 26, 2002

Reviewer:  Adrian Billington  from UK
Tom

I have seen BULK COLLECT take longer than row-by-row in my "stress tests" of it
way back when I first got 8i (8.1.6.0). I think with bulk collect in 8i
requiring a scalar array per column fetched, we eventually hit diminishing
returns as our column count gets higher. On the full size database I was working
on at the time, this was only about 15 columns, but I'm sure that for many
people the "break-even" point will be higher. With each scalar array requiring
its own subscript lookup post-fetching, I think this was possibly where some of
the performance lag occurred as the column count crept up. This was certainly a
restriction on bulk fetching those big, wide production tables we all have and
meant that I have sat tight waiting for 9i so I can bulk collect into a table of
records and therefore have one subscript lookup for all attributes in the array.
Of course, I haven't managed to get 9i installed yet so I can't verify this last
bit just yet...

But I still use it wherever possible...:)

Adrian

          Followup:

well, I went as high as I was willing to type:

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      type onameArray is table of all_objects.object_name%type index by
binary_integer;
  3
  4      l_arr1    onameArray;
  5      l_arr2    onameArray;
  6      l_arr3    onameArray;
  7      l_arr4    onameArray;
  8      l_arr5    onameArray;
  9      l_arr6    onameArray;
 10      l_arr7    onameArray;
 11      l_arr8    onameArray;
 12      l_arr9    onameArray;
 13      l_arr10   onameArray;
 14      l_arr11   onameArray;
 15      l_arr12   onameArray;
 16      l_arr13   onameArray;
 17      l_arr14   onameArray;
 18      l_arr15   onameArray;
 19      l_arr16   onameArray;
 20      l_arr17   onameArray;
 21      l_arr18   onameArray;
 22      l_arr19   onameArray;
 23      l_arr20   onameArray;
 24      l_arr21   onameArray;
 25      l_arr22   onameArray;
 26      l_arr23   onameArray;
 27      l_arr24   onameArray;
 28      l_arr25   onameArray;
 29      l_arr26   onameArray;
 30      l_arr27   onameArray;
 31      l_arr28   onameArray;
 32      l_arr29   onameArray;
 33      l_arr30   onameArray;
 34      l_arr31   onameArray;
 35      l_arr32   onameArray;
 36      l_arr33   onameArray;
 37      l_arr34   onameArray;
 38      l_arr35   onameArray;
 39      l_arr36   onameArray;
 40      l_arr37   onameArray;
 41      l_arr38   onameArray;
 42      l_arr39   onameArray;
 43      l_arr40   onameArray;
 44      l_arr41   onameArray;
 45      l_arr42   onameArray;
 46      l_arr43   onameArray;
 47      l_arr44   onameArray;
 48      l_arr45   onameArray;
 49      l_arr46   onameArray;
 50      l_arr47   onameArray;
 51      l_arr48   onameArray;
 52      l_arr49   onameArray;
 53
 54      cursor c is select object_name c1, object_name c2, object_name c3,
object_name c4, object_name c5,
 55                         object_name c6, object_name c7, object_name c8,
object_name cg, object_name cn,
 56                         object_name c11, object_name c12, object_name ca,
object_name ch, object_name co,
 57                         object_name c16, object_name c17, object_name cb,
object_name ci, object_name cp,
 58                         object_name c21, object_name c22, object_name cc,
object_name cj, object_name cq,
 59                         object_name c26, object_name c27, object_name cd,
object_name ck, object_name cr,
 60                         object_name c31, object_name c32, object_name ce,
object_name cl, object_name cs,
 61                         object_name cx26, object_name cx27, object_name cxd,
object_name cxk, object_name cxr,
 62                         object_name cy26, object_name cy7, object_name cyd,
object_name cyk, object_name cyr,
 63                         object_name c36, object_name c37, object_name cf,
object_name cm
 64                    from all_objects;
 65
 66      l_start number;
 67  begin
 68      insert into run_stats select 'before', stats.* from stats;
 69
 70      l_start := dbms_utility.get_time;
 71      for i in 1 .. 1
 72      loop
 73          for x in c
 74          loop
 75              null;
 76          end loop;
 77      end loop;
 78      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 79
 80
 81      insert into run_stats select 'after 1', stats.* from stats;
 82      l_start := dbms_utility.get_time;
 83      for i in 1 .. 1
 84      loop
 85          open c;
 86          loop
 87              fetch c BULK COLLECT into
 88                   l_arr1, l_arr2, l_arr3, l_arr4, l_arr5, l_arr6, l_arr7,
l_arr8,
 89                   l_arr9, l_arr10, l_arr11, l_arr12, l_arr13, l_arr14,
l_arr15, l_arr16,
 90                   l_arr17, l_arr18, l_arr19, l_arr20, l_arr21, l_arr22,
l_arr23, l_arr24, l_arr25,
 91                   l_arr26, l_arr27, l_arr28, l_arr29, l_arr30, l_arr31,
l_arr32, l_arr33, l_arr34,
 92                   l_arr35, l_arr36, l_arr37, l_arr38, l_arr39 , l_arr40,
l_arr41, l_arr42, l_arr43,
 93                                   l_arr44, l_arr45, l_arr46, l_arr47,
l_arr48, l_arr49
 94                                   LIMIT 100;
 95              exit when c%notfound;
 96          end loop;
 97          close c;
 98      end loop;
 99      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
100
101      insert into run_stats select 'after 2', stats.* from stats;
102  end;
103  /
908 hsecs
811 hsecs

PL/SQL procedure successfully completed.

and while they start to get very close -- so I can see how it might go the other
way....  at that point I wouldn't want to bulk fetch as the time saved would be
more then offset by the time typing!

so, they are less advantageous with a really really wide table (which I tend to
not deal with day to day myself -- don't like em myself, try to find something
else -- less columns)

      Rating: 2
memory
      leak  April 26, 2002
Reviewer:  Leon
      Rzhemovskiy  from Vancouver, BC Canada
I have been experimented with bulk collect a year ago and came to same
conclusion. It runs slower with stress test (millions of executions). If you
take a look on server memory you would find the reason. It is an oracle bug
related to memory leak. It is fixed in 9i and there are patches available.

I did not try to use bulk collect since then. I think it should work faster. At
lease bulk insert is what I found very useful

Practical experience with running bulk collect 100000000 times is appreciated to
compare speed with individual fetching

Thanks

      Rating: 4
What about
      using the arrays?  April 29, 2002
Reviewer:
      Adrian Billington  from UK
Tom

Thanks for being bothered to type out 49 arrays... :). Firstly, I'm sure LIMIT
size will make a difference - I guess the more arrays you collect, the lower (or
more sensible) your limit should become (general assumption). Your test does not
include actually using the arrays as my original test did way back (i.e. having
to reference each array with its binary_integer lookup), so I tested it by
passing the retrieved values concatenated to a procedure one "row" at a time
(procedure had a null call only). So the implicit cursor was already looping a
row at a time, but the arrays were bulk loaded then had to be looped through,
which you would expect to slow things down a bit. Not at all, BULK COLLECT still
wins !!

Like you say, if you can be bothered to type it all...

Regards

Adrian

      Rating: 4
Which bug
      number?  May 16, 2002
Reviewer:  Peter
       from Germany
Leon,
could you please give me bug number and patchsets?

      Rating: 4
Pardon the
      stupid question  November 26, 2003
Reviewer:
      Arun Mathur  from Marietta,GA
Do you have scripts for creating run_stats and stats?

Thanks.
Arun

          Followup:

http://asktom.oracle.com/~tkyte/runstats.html

      Rating: 3
BULK COLLECT
      LIMIT  November 28, 2003
Reviewer:  Dan
      Clamage  from Pittsburgh, PA
I was testing the performance of a batch application that bulk collected only a
handful (3 - 5) columns into parallel arrays. This was on a Compaq Tru64 UNIX
platform with 16GB RAM.

Because I was working with 10's of millions of rows I quite sensibly used a
LIMIT clause. I started with 10,000 and worked my way up to 500,000. I found
that values in the range of 100's of thousands performed better that those in
the 10's of thousands. 50,000 was not measurably better or worse than 10,000.
Ditto at the high end.

I made the LIMIT user-configurable, since the Production platform was somewhat
different (more powerful) than my development server. My recommendation was to
enter values around 200,000.

The amount of PGA memory available to you is crucial.

On 9.2.0.1 I ran into a memory leak bug using the SAVE EXCEPTIONS clause (fixed
in 9.2.0.3). It would bomb after consuming 1GB of PGA.

November 26, 2003 Rajesh -- Thanks for the question regarding
      "Backup and Recovery -- why extra redo is generated.", version Version
      7.3.2.1/8.0.4

      You Asked
Why is excessive redo generated during an Online/Hot Backup

      and we said...
There is not excessive redo generated, there is additional
information logged into the online redo log during a hot backup
the first time a block is modified in a tablespace that is in
hot backup mode.

in hot backup mode only 2 things are different:

o the first time a block is changed in a datafile that is in hot
backup mode, the ENTIRE BLOCK is written to the redo log files,
not just the changed bytes.  Normally only the changed bytes (a
redo vector) is written. In hot backup mode, the entire block is
logged the FIRST TIME.  This is because you can get into a
situation where the process copying the datafile and DBWR are
working on the same block simultaneously.  Lets say they are and
the OS blocking read factor is 512bytes (the OS reads 512 bytes
from disk at a time).  The backup program goes to read an 8k
Oracle block.  The OS gives it 4k.  Meanwhile -- DBWR has asked
to rewrite this block.  the OS schedules the DBWR write to occur
right now.  The entire 8k block is rewritten.  The backup
program starts running again (multi-tasking OS here) and reads
the last 4k of the block.  The backup program has now gotten an
impossible block -- the head and tail are from two points in
time.  We cannot deal with that during recovery.  Hence, we log
the entire block image so that during recovery, this block is
totally rewritten from redo and is consistent with itself at
least.  We can recover it from there.

o the datafile headers which contain the SCN of the last
completed checkpoint are NOT updated while a file is in hot
backup mode.  This lets the recovery process understand what
archive redo log files might be needed to fully recover this
file.

To limit the effect of this additional logging, you should
ensure you only place one tablepspace at a time in backup mode
and bring the tablespace out of backup mode as soon as you have
backed it up.  This will reduce the number of blocks that may
have to be logged to the minimum possible.
Review &
      Followup

      Rating:
      4
xcellent  December 20, 2001
Reviewer:
      vel  from Moon
i like this site very much.it very usefull for me
thax tom

      Rating:
      5
  October 14, 2002
Reviewer:
      Jayakumar  from Dubai
Simply Great ...

      Rating:
      4
  February 04, 2003
Reviewer:
      A reader
"In hot backup mode, the entire block is logged the FIRST TIME. "

--Can you please explain what you mean by FIRST TIME above?

          Followup:

the first time a block is modified AFTER a hot backup for the tablespace it is
in starts causes the full block image to be logged.

Subsequent modifications to that block will go back to changes only logging.
Until the next hot backup of course.

      Rating:
      4
  February 04, 2003
Reviewer:
      A reader
Xcellent. Know I got the clear idea what inside is happening at hotbackup time.

Very thankful to u..............

      Rating:
      5
Reader  February 16, 2003
Reviewer:
      A reader
During Hot Backup Mode, file is being backed up (unix copy)

1. the block being copied may be in the  buffer pool and
   being changed

2. The block may be read from the disk

How does Oracle handle the redo in these cases. I am sure
you have explained this , in another posing, I just
couldn't locate it

          Followup:

when you place a tablespace in hot backup mode, oracle will log extra
information for a block the first time it is modified whilst the tablespace it
belongs to is in hot backup mode.

Say tablespace X containing file 55 is put into hot backup mode.

You modify block 123 in file 55 -- this generates redo.  Oracle will log the
ENTIRE block image instead of just changed bytes.

You commit.

Someone else modifies blocks 123 and 124 in file 55.  Oracle will log just
changed bytes for block 123 but a full block image copy for 124.

      Rating: 4
Confusing
      Term  February 21, 2003
Reviewer:  Amarjyoti
      Dewri  from INDIA
Hi,
The line
-------------------
the datafile headers which contain the SCN of the last completed checkpoint
are NOT updated while a file is in hot backup mode.  This lets the recovery
process understand what archive redo log files might be needed to fully recover
this file.
-------------------
is some what confusing? What i want to ask is "How does this lets the recovery
process understand what archive redo log files might be needed to fully recover
this file." a similar question was asked by some one and what i answered was

"When you give alter tablespace begin backup command, checkpoint occurs for all
datafiles in the HOT BACKUP. the datafile header checkpoints are advanced to the
SCN captured when the begin backup command is issued. And this SCN goes in the
backup file as well.

Alter tablespace end backup command creates a redo record containing the begin
backup checkpoint SCN. Also the datafile checkpoint is advanced to the database
checkpoint.

So from this redo record and the initial SCN in the backup datafile, oracle
knows which all redo needs to be applied to the restored backup file to make it
consistent.

Also my understanding is that when we give the alter tablespace end backup
command all the redo generated during the backup is applied to the datafile to
advance the datafile SCN to database SCN.

The same has to be done to the backup file to bring it to consistent state,
hence the header is freezed so that oracle knows that redo generated between the
SCN at begin backup and end backup need to be applied. (More ever the blocks
changed during the backup are rewritten from the redo)

Or in other words if the SCN were not freezed, they would also be copied in the
backup datafile. So how would oracle know if the datafile is inconsistent when
its restored, as the SCN of the backup datafile would match the SCN at the alter
tablespace end backup command. And Oracle uses SCN numbers to apply the redo."

Am i right or wrong??

          Followup:

this:

<quote>
Also my understanding is that when we give the alter tablespace end backup
command all the redo generated during the backup is applied to the datafile to
advance the datafile SCN to database SCN.
</quote>

is absolutely wrong -- have you ever had an end backup ask you for an archive?
Nope, never will - we constantly write to the datafiles during the hot backup.
The SCN recorded in the header tells us how far back in the redo stream one
needs to go to recover this file.

Read the answer above again -- things like:

...
  Lets say they are and the OS blocking read factor is
512bytes (the OS reads 512 bytes from disk at a time).  The backup program goes
to read an 8k Oracle block.  The OS gives it 4k.  Meanwhile -- DBWR has asked to

rewrite this block.  the OS schedules the DBWR write to occur right now.  The
entire 8k block is rewritten.  The backup program starts running again
(multi-tasking OS here) and reads the last 4k of the block.
.....

should definitely make it clear that the datafiles are WRITTEN do whilst they
are being backed up (else we would not have this redo issue at all -- the
logging of the full block image)

      Rating: 5
Thanks that
      clears my doubts.  February 21, 2003
Reviewer:
      Amarjyoti Dewri  from INDIA
What a relief... :-)

      Rating: 3
hot backup and
      datafile writing  May 29, 2003
Reviewer:
      gaurav  from Ipswich, UK
 Hello Tom,

As you mentioned that while the hot backup is running oracle will keep on
writing to datafiles. However, I am unable to understand this , my doubt is-
1. I started the hot backup of tbs X
2. Some one has inserting a large number of records (say more than 10,000) .
3. Where will oracle write these records . Will it be redo logs and data files
both ? what is the reason of writing on both places?
3. If some one has issued the query from where oracle will read the records?

Regards
Gaurav

          Followup:

oracle writes them to the datafiles as normal.

everything happens "just as normal".

Oracle deals with fuzzy files all of the time, it understands what to do with a
file upon recovery.  the only thing hot backup mode does really is to freeze the
datafile header and generate a little extra redo for a block the first time it
is modified.

otherwise, everything happens "as normal"

      Rating: 3
some
      confusion  September 22, 2003
Reviewer:
      Anurag  from INDIA
Dear Tom,
Please clear my doubts. Why - during online backup we put tablespace in a backup
mode. I wrote a script to insert and delete few thousand records in 'X' table.
during execution I simply copied the datafiles in bkup folder then after
committing and shutting db. I corrupted relevant datafile Then startup ...error
message.....copied dbf from bkup and recover datafile 8 ......auto archive
applied....log applied...media recovery complete.

When backup and recovery is done...why then begin backup and end backup.....

          Followup:

you got lucky -- hope you don't do that for real.

Oracle works with database blocks in 2, 4, 8, 16, or 32k pieces.  Say 8k is what
you are using.

operating systems read and write files using their own chunk size.  say 512bytes
(1/2 k) for example -- a common size.

So, you are copying a datafile -- you are using the OS to do that.  You are
reading it in 512byte chunks.

You start reading block 55 in file 5 using "copy".  You got 1/2 of the way
through it (meaning you read 4k of data).  You get pre-empted and along comes
DBWR who decides to lay down a new version of that block on disk (checkpoint,
flush dirty buffers).  DBWR was lucky enough to write all 8k.  Your copy picks
back up and reads the next 4k of the block -- but -- it is 4k of the block at a
totally different point in time.

This is known as a fractured block -- happens easily on a system with many
people reading and writing the same file at the same time.

Now, you restore this copy -- we have a block that is half at time T0 and half
at time T1 -- we cannot recover that block -- UNLESS we just happened to keep
the full block image from the first change after starting the backup elsewhere
-- which is what we did.  So, we are able to recover this block using that
image.

Note that if you use RMAN, this isn't an issue.  RMAN is not affected by OS vs
DB block sizes -- it knows how to read an Oracle datafile safely, with RMAN
backups, you don't put a tablespace into backup mode, it is not necessary.

      Rating: 5
hot backup
      ....  October 11, 2003
Reviewer:  Shankar
       from Houston, TX
Tom, If the OS block size and Oracle block size are the same, does oracle still
log entire block image when the tablespace is in backup mode? Thanks.

          Followup:

yup.

      Rating:
      5
Excellent  November 26, 2003
Reviewer:
      Venkata krishnan  from India
Very nice questions and answers. Cleared my most of the doubts regarding Hot
backup.

      Rating: 4
During hot
      backup  November 26, 2003
Reviewer:  Ramesh
       from Atlanta, GA USA
If the Tablespace in hot backup mode and the files are deleted while in backup
mode,
Still we can create table, insert rows into that table..

Is that mean, that the data is not written to Datafile while in backup mode ?

Thanks
Ramesh

          Followup:

no, you've got it wrong. files are continously written to, in hot backup mode or
not.

first, under unix, if a process has a file opened and you erase it elsewhere --
that process still has a file!

second, the file need not be accessed right away.  DBWR does the writes to the
files in the background.

third, did you think to try the test NOT in backup mode (which would "prove"
that files are apparently NEVER written to :)

November 26, 2003 Pradeep -- Thanks for the question regarding
      "Separating comma separated words ", version 8.0.5

      You Asked
In my database table, there is one field for the employee name in the order of (
First_name,Last_name,Middle_initial).
Example (Brian,Robbin,D).
I want to separate each of these words and put it into a new table
with three fields (First_name, Last_name, Middle_name).

I want to write a PL/SQL , so for all the employees, it would be done at a time.

      and we said...
instr and substr is all you need.  don't use PLSQL:

variable x varchar2(25)

exec :x := 'Brian,Robbin,D'
select substr( :x||',', 1, instr(:x,',')-1 ) first_name,
       substr( :x||',,', instr( :x||',,', ',') +1,
                         instr( :x||',,', ',', 1, 2 )-instr(:x||',,',',')-1 )
last_name,
       rtrim(substr( :x||',,', instr( :x||',,',',',1,2)+1),',') middle_init
 from dual
/

Review &
      Followup

      Rating: 4
||','
      ??  December 11, 2001
Reviewer:  Jim

Tom,
    I'm not sure why you are concatenating ',' and in some cases ',,' to :x?
I'll keep testing but at the moment it works without
them
Regards
Jim

          Followup:

It is for strings that are malformed (that don't have 2 commas in them)

and I just noticed I missed one case!  see below in bold, we need that extra
comma as well

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x varchar2(15) );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 'tom' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 'tom,kyte' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 'tom,kyte,j' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select substr( x||',', 1,
instr(x||',',',')-1 ) first_name,
  2         substr( x||',,', instr( x||',,', ',') +1,
  3                           instr( x||',,', ',', 1, 2 )-instr(x||',,',',')-1 )
  4  last_name,
  5         rtrim(substr( x||',,', instr( x||',,',',',1,2)+1),',') middle_init
  6   from t
  7  /

FIRST_NAME       LAST_NAME         MIDDLE_INIT
---------------- ----------------- -----------------
tom
tom              kyte
tom              kyte              j

without the extra commas, we parse wrong

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select substr( x, 1, instr(x,',')-1 )
first_name,
  2         substr( x, instr( x, ',') +1,
  3                           instr( x, ',', 1, 2 )-instr(x,',')-1 )
  4  last_name,
  5         rtrim(substr( x, instr( x,',',1,2)+1),',') middle_init
  6   from t
  7  /

FIRST_NAME      LAST_NAME       MIDDLE_INIT
--------------- --------------- ---------------
                                tom
tom                             tom,kyte
tom             kyte            j

      Rating: 5
INSTR a whole
      word  May 14, 2003
Reviewer:  Sachin
       from Germany
Database Version: 8.1.7.4
Tom,
I want to search a comma delimited string for a whole word..instead of parts of
word..

Example:
Vstring varchar2(50) := 'THISTHAT,THIS'

IF INSTR(Vstring, 'THISTHAT') <> 0 AND INSTR(Vstring, 'THIS') <> 0 THEN
.......

Now above statement will be true also for
Vstring = 'THISTHAT'...
so, Is there a way that we can look for whole words...

Thanks a bunch...

          Followup:

if instr( ','||vstring||',', ',THISTHAT,') <> 0 .....

put a leading/trailing , and look for that as part of the keyword as well

      Rating: 4
split
      names  May 15, 2003
Reviewer:  Lakshmi
       from Bangalore India
Excellent

      Rating: 5
Simply
      superb  May 15, 2003
Reviewer:  Sachin
       from Germany
Thanks Thanks Thanks....

      Rating: 5
INSTR
      LIMIT  May 15, 2003
Reviewer:  A reader

What is the character limit on string that is being searched...can it be more
than 4000...
Looked in docs..couldn't find it there...

          Followup:

4000 in SQL

32767 in PLSQL

4gig in DBMS_LOB

4gig if you use contains instead of instr (and it'll be faster to boot probably)

      Rating:
      4
  July 02, 2003
Reviewer:  Vasukh
       from Richmond, VA
Tom,
Thanks for your help!

Oracle8.1.7
I have a varchar2(1500) field with 45 tab-delimited elements that I want to
split and insert into a 45 column table.

Will the above approach give me the best performance for about 200,000 rows?

Is there a way to identify the 45 positions once, in a before-insert trigger and
substr() each element in an after-insert trigger for faster performance? Are any
other mehods available?

Thanks !!

          Followup:

I would dump it to a file and use sqlldr to load the delimited file.

it should take all of a minute or two.

see

http://asktom.oracle.com/~tkyte/flat/index.html
you can use "flat" to unload the table with a big string.

you can use sqlldr_exp on the 45 column table to have a ctl file built for you
easily -- then sqlldr it.

      Rating: 5
Separating
      Tab-delimited String  July 02, 2003
Reviewer:
      Vasukh  from Richmond, VA
Tom,

The process (to separate the Tab-delimited string) needs to be executed from a
stored-procedure.

Sorry, I did not fully understand the solution. Should sqlldr be called as an
external procedure?

thanks,

Vasukh

          Followup:

you asked:

Will the above approach give me the best performance for about 200,000 rows?

A stored procedure would not be part of my answer on that.  sqlldr cannot really
be called as an extproc (you could run a host command).

You'll have to parse the string.  However, calling that plsql function 45 *
200,000 times will not fall into my definition of "fast".

so, a single insert into modelled after this:

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select x data,
  2             substr( x, 1, x1-1 ) data,
  3         substr( x, x1+1, x2-x1-1 ) data,
  4         substr( x, x2+1, x3-x2-1 ) data,
  5         substr( x, x3+1, x4-x3-1 ) data,
  6         substr( x, x4+1 ) data
  7    from (select x,
  8                decode( x1, 0, to_number(null), x1 ) x1,
  9                decode( x2, 0, to_number(null), x2 ) x2,
 10                decode( x3, 0, to_number(null), x3 ) x3,
 11                decode( x4, 0, to_number(null), x4 ) x4
 12    from (select x,
 13                 instr(x||',',',',1,1) x1,
 14                 instr(x||',',',',1,2) x2,
 15                 instr(x||',',',',1,3) x3,
 16                 instr(x||',',',',1,4) x4
 17            from t1
 18          )
 19          );

DATA       DATA       DATA       DATA       DATA       DATA
---------- ---------- ---------- ---------- ---------- ----------
1,2,3,4,5  1          2          3          4          5
1,2,3,4    1          2          3          4
1,2,3      1          2          3
1,2        1          2
1          1

just add x5..x44
use chr(9) instead of ','
use insert /*+ append */ into .... select .....;

      Rating: 5
Separating
      Tab-delimited String   July 02, 2003
Reviewer:
      Vasukh  from Richmond, VA
Thanks!

      Rating: 5
parsing a
      string  October 16, 2003
Reviewer:  Anita

Tom,
Can you please help me with this....

Availabe Input (comma separted values in a table):
Two columns are:
data_points: 1,1,1,1,2,2,3,3,3,1,1,4,4
data_dates:07/08,07/15,07/22,07/29,08/05,08/12,08/19,08/26,09/02,09/09,09/16,09/2
3,09/30

Required Output:( Group by consecutive dates and data.)

new_data_points , start_date, end_date
1               ,   07/08,    07/29
2               ,   08/05,    08/12
3               ,   08/19,    09/02
1               ,   09/09,    09/16
4               ,   09/23,    09/30

The date is always a week apart.
Grouping by the data_point to get the new_data_point and get the start and end
dates using the data_dates.

Can you please give me a solution for this.

Thanks,
Anita.

          Followup:

got a particular version in mind?

      Rating:
      5
  October 16, 2003
Reviewer:
      peddua@yahoo.com
Not particular with the version
But we are using 9i.

Thanks,
Anita.

          Followup:

well, I can get you this far:

ops$tkyte@ORA920> create type myScalarType as object
  2  ( dp int, dd date )
  3  /

Type created.

ops$tkyte@ORA920> create type myTableType as table of myScalarType
  2  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace function parse( p_dp in varchar2, p_dd in
varchar2 )
  2  return myTAbleType
  3  PIPELINED
  4  as
  5          l_dp long := p_dp || ',';
  6          l_dd long := p_dd || ',';
  7      n  number;
  8          l_rec myScalarType := myScalarType(null,null);
  9  begin
 10          loop
 11                  n := instr( l_dp, ',' );
 12                  if ( nvl(n,0) > 0 )
 13                  then
 14                          l_rec.dp := substr( l_dp, 1, n-1 );
 15                          l_dp := substr( l_dp, n+1 );
 16                  else
 17                          l_rec.dp := null;
 18                  end if;
 19                  n := instr( l_dd, ',' );
 20                  if ( nvl(n,0) > 0 )
 21                  then
 22                          l_rec.dd := to_date( substr( l_dd, 1, n-1 ),
'mm/dd' );
 23                          l_dd := substr( l_dd, n+1 );
 24                  else
 25                          l_rec.dd := null;
 26                  end if;
 27                  exit when l_rec.dd is null and l_rec.dp is null;
 28
 29                  pipe row( l_rec );
 30          end loop;
 31          return;
 32  end;
 33  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select *
  2    from TABLE( parse('1,1,1,1,2,2,3,3,3,1,1,4,4',
  3
'07/08,07/15,07/22,07/29,08/05,08/12,08/19,08/26,09/02,09/09,09/16,09/23,09/30'
) )
  4   order by dd
  5  /

        DP DD
---------- ---------
         1 08-JUL-03
         1 15-JUL-03
         1 22-JUL-03
         1 29-JUL-03
         2 05-AUG-03
         2 12-AUG-03
         3 19-AUG-03
         3 26-AUG-03
         3 02-SEP-03
         1 09-SEP-03
         1 16-SEP-03
         4 23-SEP-03
         4 30-SEP-03

13 rows selected.

but I don't see how to turn that into your result -- i don't see how you got the
numbers/dates you did?

      Rating: 5
In some
      contects varchar2 variable limit is 32512
      characters...  October 16, 2003
Reviewer:  Piotr
      Jarmuz  from Poznan, Poland
Interesting to note is the fact that varchar2 variables as parameters to stored
procedures (in in/out out) may be "only" 32512 bytes long.

I've checked this in Java and Perl. 32512 is the last value that works, for any
bigger it throws:

Exception in thread "main" java.sql.SQLException: ORA-01460: unimplemented or
unreasonable conversion requested

But in PL/SQL as you said 32767

Regards,
Piotr

      Rating: 5
It is even
      stranger...  October 16, 2003
Reviewer:  Piotr
      Jarmuz  from Poznan, Poland
create or replace procedure print_length(a varchar2) is
begin
    dbms_output.put_line(length(a));
end;
/

declare
x varchar2(32767);
begin
     for i in 1..32767 loop
          x:=x || 'a';
     end loop;
     one(x || x || 'a');
end;

will give 65535 and this is the ultimate limit...
strange indeed.

Tom, can you explain that?

Regards.

      Rating: 5
RE: In Some
      Contexts  October 16, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
A synopsis of this from MetaLink:

Although in PL/SQL a VARCHAR2 has a maximum size of 32767 characters, some
overhead is needed when the PL/SQL value is bound to a host variable.  Thus, if
you are binding PL/SQL values to a host variable, the maximum allowance of a
VARCHAR2 variable is 32512 characters.

- Mark

      Rating: 5
I thoght
      so...  October 16, 2003
Reviewer:  Piotr
      Jarmuz  from Poznan, Poland
Mark,

Thanks for your explanation... I thought so but now I am sure.

Yet another test I have done:

In an OCI external procedure you can create a C string with
OCIExtProcAllocCallMemory (which I guess <this may be wrong> corresponds to
varchar2 string in PL/SQL) as big as you like (I have created successfully ~ 100
MB string) but when the value is returned to SQL layer it throws the exception:

ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at "P_JARMUZ.RUN_PROGRAM3", line 7
ORA-06512: at line 2

Regards

      Rating: 5
Hi
      Piotr  October 16, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Hi Piotr,

I am intrigued by your 65536 case and am looking at a trace of the execution of
that right now... very interesting indeed.  If I find anything worthwhile in
that I will post it here.  I don't do much OCI programming, but that sounds
interesting as well.

Thanks!

- Mark

      Rating:
      5
Anita  October 16, 2003
Reviewer:
      A reader
Thanks Tom.

      Rating: 4
Comma Delimited
      Data  November 26, 2003
Reviewer:  Anuj
       from USA
Hi!!! Tom

I want to extract data from table which has comma delimited data
123,456,789....it could be from 1 to N.It could be same or different for each
row and now i want to extract this data and insert it into table with different
rows ,ofcourse other columns will have same data. ex : Tab1 is Source table and
Tab2 is Target table

Tab1
col1      col2         col3
1          123,456,78    abc
2          902,87,123    xyz
3          12,13         uvw

-------------------

Tab2

col1    col2        col3

1         123         abc
1         456         abc
1          78         abc
2         902         xyz
2          87         xyz
2         123         xyz
3          12         uvw
3          13         uvw
Thanks!

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2460648511591#13951062520752

November 26, 2003 Daryl -- Thanks for the question regarding
      "Materialized Views", version 8.1.7

      You Asked
Tom,

What is the difference between using a "regular" view and a
materialized view? What are the benefits of materialized views?

Thanks!
Daryl

      and we said...
A view is simply a stored query.  If I have a view such as:

create view avg_sal_by_dept
as
select deptno, avg(sal)
  from emp
 group by deptno
/

and I select * from avg_sal_by_dept -- Oracle will process all
of the rows in the emp table, maybe millions of them, to return
the average salary for your 100 departments.

On the other hand, if I created a materialized view -- Oracle
would actually store the answer.  I would not select from
avg_sal_by_dept but rather, some tool would submit the query:

select deptno, avg(sal) from emp group by deptno

and Oracle would automagically rewrite that query to use the
"answer" instead of the base (big) dataset.  The answer to your
query would come back instantly -- instead of taking a long time
to aggregate.

This is normally called "summary table management" and the
optimizer has the ability to automatically rewrite queries to
make use of these summaries when appropriate.
Review &
      Followup

      Rating: 3
Benefits of
      Materialized Views  November 26, 2003
Reviewer:
      Hector M. Munoz  from USA
The response was simple and straight to the point. Are there any presentations
done on Materialized Views that I could refer to? I am specifically performing a
research on MV and what benefits do they offer in commercial systems. I also
looking for strong arguments for advocating their use.

          Followup:

have you read the data warehousing guide?  nice chapter in there on them.

      Rating: 5
Materialized
      Views - "A view from the trenches"  November 26, 2003

Reviewer:  John Ryan  from Cambridge, UK
Hi there,

Having struggled with MVIEWS for nearly six months I have a few comments:-

1. MVIEWS can provide blistering performance.  Querying a 100,000 row summary is
around 100 times faster than a billion row table.

2.  You first need to identify the columns to "group by".  Miss one out, and
MVIEW will be ignored.  eg. "Select GRADE, count(*) from BIG_TABLE group by
GRADE" won't use an MView unless it includes the GRADE column.

3.  The "Query Re-write" technology which enables MVIEWS to be used seamlessly
are amazing.  Too complex for humans to understand, but astonishingly good.

4.  However (and it's a big one), finding out whether (and why) Query Rewrite is
not used is like:-

     "A blind man in a dark room looking for a black cat
      that isn't there".

There are many complex rules which drive the Query Rewrite, and it's impossible
to understand and remember them all.  There is a minor utility available, but
it's hard to use, and provides cryptic results.

MViews in summary:-

With Query Rewrite, absolutely incredible technology.  But approach with
caution.

          Followup:

4) 10g should address your last concern ;)

November 26, 2003 John -- Thanks for the question regarding "Forms
      Matrix Style Form", version 6.0.5.0.2

      You Asked
Is there an EASY way to create a matrix style form.  I have an
application which I want to show a 24X7 work schedule on a form
for many employees.

for example
I have a table:

emps( empno number, start_time date, end_time date );

And I'd like to have a form that looks like:

EMPNO   8am   9am   10am   11am  12pm   1pm   2pm
    1     -     x      x      x     x     -     x   ....
    2     x     x      x      x     -     x     x   ....

that shows empno = 1 works from 9am-12pm, is off for 1 hour (and
so on...)

      and we said...
Well, once we create a view, this is easy.  The view isn't easy
to come up with perhaps but once there -- its trivial to use.

We'll start with an extra table:

ops$tkyte@DEV8I.WORLD> create table hours
  2  as
  3  select rownum-1 hour
  4    from all_objects
  5   where rownum <= 24;

Table created.

That has a row for every hour in the day...

Here is my emp table.  I have a column DAY that is for
illustrative purposes -- you might not need it (you could also
substitute TRUNC(start_date) where I reference DAY in the view
below).  I made it a column so I could index it and query it
easily...

ops$tkyte@DEV8I.WORLD> create table emp
  2  ( empno       number,
  3    day         date,
  4    start_time  date,
  5    end_time    date
  6  )
  7  /

Table created.

ops$tkyte@DEV8I.WORLD> create index emp_idx on emp(empno);
Index created.

ops$tkyte@DEV8I.WORLD> create index start_idx on emp(day);
Index created.

ops$tkyte@DEV8I.WORLD> alter session set
nls_date_format='dd-mon-yyyy hham';

Session altered.

ops$tkyte@DEV8I.WORLD>
ops$tkyte@DEV8I.WORLD> insert into emp values
  2  ( 1, '07-jul-2000', '07-jul-2000 9am', '07-jul-2000 12pm'
);

1 row created.

ops$tkyte@DEV8I.WORLD> insert into emp values
  2  ( 1, '07-jul-2000', '07-jul-2000 1pm', '07-jul-2000 6pm' );

1 row created.

ops$tkyte@DEV8I.WORLD>
ops$tkyte@DEV8I.WORLD> insert into emp values
  2  ( 2, '07-jul-2000', '07-jul-2000 8am', '07-jul-2000 11am'
);

1 row created.

ops$tkyte@DEV8I.WORLD> insert into emp values
  2  ( 2, '07-jul-2000', '07-jul-2000 12pm','07-jul-2000 5pm' );

1 row created.

ops$tkyte@DEV8I.WORLD>
ops$tkyte@DEV8I.WORLD> insert into emp
  2  select empno, day-1,
  3         start_time-(1+1/24), end_time-(1+1/24)
  4    from emp;

4 rows created.

ops$tkyte@DEV8I.WORLD>
ops$tkyte@DEV8I.WORLD> select * from emp;

 EMPNO DAY              START_TIME       END_TIME
------ ---------------- ---------------- ----------------
     1 07-jul-2000 12am 07-jul-2000 09am 07-jul-2000 12pm
     1 07-jul-2000 12am 07-jul-2000 01pm 07-jul-2000 06pm
     2 07-jul-2000 12am 07-jul-2000 08am 07-jul-2000 11am
     2 07-jul-2000 12am 07-jul-2000 12pm 07-jul-2000 05pm
     1 06-jul-2000 12am 06-jul-2000 08am 06-jul-2000 11am
     1 06-jul-2000 12am 06-jul-2000 12pm 06-jul-2000 05pm
     2 06-jul-2000 12am 06-jul-2000 07am 06-jul-2000 10am
     2 06-jul-2000 12am 06-jul-2000 11am 06-jul-2000 04pm

8 rows selected.

So that is the data we have to work with.  2 days worth for 2
emps. Hours are different by day...

ops$tkyte@DEV8I.WORLD> alter session set
nls_date_format='dd-mon-rr';

Session altered.

Here comes the view:

ops$tkyte@DEV8I.WORLD> create or replace view empV
  2  as
  3  select empno, day,
  4         max( decode( hour, 0, flag, null ) ) "0",
  5         max( decode( hour, 1, flag, null ) ) "1",
  6         max( decode( hour, 2, flag, null ) ) "2",
  7         max( decode( hour, 3, flag, null ) ) "3",
  8         max( decode( hour, 4, flag, null ) ) "4",
  9         max( decode( hour, 5, flag, null ) ) "5",
 10         max( decode( hour, 6, flag, null ) ) "6",
 11         max( decode( hour, 7, flag, null ) ) "7",
 12         max( decode( hour, 8, flag, null ) ) "8",
 13         max( decode( hour, 9, flag, null ) ) "9",
 14         max( decode( hour, 10, flag, null ) ) "10",
 15         max( decode( hour, 11, flag, null ) ) "11",
 16         max( decode( hour, 12, flag, null ) ) "12",
 17         max( decode( hour, 13, flag, null ) ) "13",
 18         max( decode( hour, 14, flag, null ) ) "14",
 19         max( decode( hour, 15, flag, null ) ) "15",
 20         max( decode( hour, 16, flag, null ) ) "16",
 21         max( decode( hour, 17, flag, null ) ) "17",
 22         max( decode( hour, 18, flag, null ) ) "18",
 23         max( decode( hour, 19, flag, null ) ) "19",
 24         max( decode( hour, 20, flag, null ) ) "20",
 25         max( decode( hour, 21, flag, null ) ) "21",
 26         max( decode( hour, 22, flag, null ) ) "22",
 27         max( decode( hour, 23, flag, null ) ) "23"
 28    from ( select empno, day, hour,
 29                  decode( sign(
start_time-(trunc(start_time)+
 30                                hour/24)), 1, NULL,
 31                  decode( sign(end_time-(trunc(end_time)+
 32                          hour/24)), 1, 'X', NULL ) ) flag
 33             from ( select * from hours ),
 34                  emp
 35         )
 36   group by empno, day
 37  /

View created.

The view works by:

o "from (select * from hours ), emp" creates 24 rows for each
row in the emp table we are interested in.  Each of these rows
has an hour associated with it now.  So, where we had 8 rows,
we'll get 8x24 rows (less if we use a predicate on the view as
see below)...

o the decode in the from list says:

  if ( start_time - HOUR ) > 0
  then
     -- start time is bigger then hour, we did not work in that
     -- hour
     return NULL
  else
     if ( end_time - HOUR ) > 0
     then
        -- end_time happens AFTER that hour hence we WORKED in
        -- that hour
        return 'X'
     else
        return NULL
     end if
  end if

o we now have a set such that for each row in emp we have 24
rows with the numbers 0..23 and a flag that says whether we
worked in that hour or not...

o we can use a simple decode and group by to pivot on empno and
day...

Now lets see how it works and how it performs

ops$tkyte@DEV8I.WORLD> select * from empV;

 EMPNO DAY       0 1 2 3 4 5 6 7 8 9 1 1 1 1 1 1 1 1 1 1 2 2 2 2
------ --------- - - - - - - - - - - - - - - - - - - - - - - - -
     1 06-jul-00                 X X X   X X X X X
     1 07-jul-00                   X X X   X X X X X
     2 06-jul-00               X X X   X X X X X
     2 07-jul-00                 X X X   X X X X X

That shows empno=1 works from 8am-11am (upto not including
11am) and 1pm-5pm on 6/jul (easier to read with a wider screen
;)

The following shows that we can efficiently query this as
well:

ops$tkyte@DEV8I.WORLD> set autotrace on explain
ops$tkyte@DEV8I.WORLD> select * from empV where empno = 1;

 EMPNO DAY       0 1 2 3 4 5 6 7 8 9 1 1 1 1 1 1 1 1 1 1 2 2 2 2
------ --------- - - - - - - - - - - - - - - - - - - - - - - - -
     1 06-jul-00                 X X X   X X X X X
     1 07-jul-00                   X X X   X X X X X

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   VIEW OF 'EMPV'
   2    1     SORT (GROUP BY)
   3    2       NESTED LOOPS
   4    3         TABLE ACCESS (FULL) OF 'HOURS'
   5    3         TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
   6    5           INDEX (RANGE SCAN) OF 'EMP_IDX' (NON-UNIQUE)

Even though the view looks too complex to merge predicates
into -- it can.  It used the index on emp to find empno 1 and
then did the work.  The following shows the same can be true of
the other indexed columns:

ops$tkyte@DEV8I.WORLD> select * from empV where day = to_date(
'07-jul-00' );

 EMPNO DAY       0 1 2 3 4 5 6 7 8 9 1 1 1 1 1 1 1 1 1 1 2 2 2 2
------ --------- - - - - - - - - - - - - - - - - - - - - - - - -
     1 07-jul-00                   X X X   X X X X X
     2 07-jul-00                 X X X   X X X X X

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   VIEW OF 'EMPV'
   2    1     SORT (GROUP BY)
   3    2       NESTED LOOPS
   4    3         TABLE ACCESS (FULL) OF 'HOURS'
   5    3         TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
   6    5           INDEX (RANGE SCAN) OF 'START_IDX'
(NON-UNIQUE)

Review &
      Followup

      Rating: 4
Matrix
      form  September 18, 2003
Reviewer:  Kunjan
      Mehta  from Bangalore, India
Dear Tom,
Thanks for a very nice solution. But I tried it and it works great till i do it
in SQL Plus.
As soon as i try to create a block in the Forms it's not allowing me to select
the columns 0,1,2,3,4, etc. of the view (perhaps because the column name here
starts with a numeric value and not an alphabetic value)
I tried to change the column name with a,b,c and it works fine.
Is this really a restriction in the forms or I'm missing something? If a
restriction, why so?
Regards,

          Followup:

sorry, i haven't touched forms since 1995 -- don't have it installed.

it should support quoted identifiers just dandy though.  the column names are
"1", not 1

      Rating: 3
Matrix
      Form  September 18, 2003
Reviewer:  Kunjan
      Mehta  from Bangalore, India
Hi Tom
While changing the names of the view columns, i am able to build the block in
the form. but still i'm not able to query the block in form. also, if i put
selective query even in the sqlplus, it doesn't work.
e.g. if i query as
select empno, day, 0, 1 from empv;
Or select empno, day, a, b from empv; --after changing name
then sqlplus or form doesn't recognise the columns 0,1,a OR b.
Please reply as to what to do in such cases to create form?

          Followup:

select empno, day, "0", "1" from empv;

      Rating: 5
It works for me
      in forms as well  September 18, 2003
Reviewer:
      Kalita
after changing the column names though.

      Rating: 3
Matrix
      Form  September 24, 2003
Reviewer:  Kunjan
      Mehta  from Bangalore, India
Hi Tom,
I tried to query using double quotes in sql plus and it works ok. but it's not
working in form. anyway thanks for a nice approach. I would be grateful if you
suggest some other source from where i can get answer of this.

Hi Kalita,
I'm still not able to do it in forms. I tried following things.
(1) with view columns as "0","1" etc.
the data block wizard does not recognize the columns. can't select columns
through wizard. even if created block manually, not able to query.
(2) with view columns as "a", "b" etc.
The data block wizard works. but when I run the form, the error 'FRM-40505:
ORACLE error: Unable to perform query' is displayed. also, in this case in
property palette, the column name is a,b, c etc. i also tried to change it to
"a", "b", "c" etc. but still query is not performed.
(3) with view coluns as a, b, c etc. (without double quotes)
Here also the data block wizard works, but form is unable to perform query.

Is the query performed in your form? If yes, have u made any other setting or
changes? I would be grateful if u revert to this ASAP.
(A workaround i've found is to create a empty table based on this view, build
block based on this table and populate this table in new-form-instance or
new-block-instance from view - in this case, i'm able to display the query
results, but as i said, this is a fix and not the solution.)

Rgds,
Kunjan.

          Followup:

it should 100% work in forms, if it is not it would be a bug, you would want to
contact support.  it is standard SQL.

      Rating:
      5
  September 24, 2003
Reviewer:
      Kalita
I didn't change anything really. I was also not able to select the columns when
the names were in numbers. Once I changed it to a,b,c etc it worked fine for me.
I tried with forms 6i and server 9.2.0.

      Rating: 3
Debugging
      forms  September 24, 2003
Reviewer:  Gary
       from Sydney, Australia
'FRM-40505: ORACLE error: Unable to perform query'

Basically this means Forms ran the query and got an error.
Several approaches to debugging.
Firstly, look at DBMS_ERROR_TEXT to see the error returned by Oracle. Could be a
table or view does not exist, insufficient privileges on underlying table....

Secondly use SYSTEM.LAST_QUERY to see the text of the query it ran, and try
running it from SQL*Plus in the same Oracle user against the same database.

If, for any reason, you can't amend the code, then a trace would be able to
identify the query sent to the database and the error returned from it.

Using numeric column names doesn't work with the data block wizard in Forms 6i.
If you define the column name attribute with the double quotes in the item
property, it should work. (It does for me)

      Rating: 3
What happen if
      the qty of the cols increase.....  November 26, 2003

Reviewer:  Ingrid  from Guatemala, Central America

I found this article very interesting and useful....
but I have a problem now.... what happen if the quantity of columns increase....

The value of this colums depends from a values of some table... and the values
of this table will be increase.....

Do you have any suggestions.?????

Thanks a lot....

          Followup:

beyond "recreate your view?"

if you have my first book "expert one on one Oracle" -- see the analytics
chapter, i have a dynamic sql method in there that uses ref cursors.

      Rating:
      3
  November 26, 2003
Reviewer:
      Ingrid  from Guatemala C.A.
thanks,
i'll check notes about ref cursor.

      Rating: 5
Forms and NULL
      values  November 27, 2003
Reviewer:  Gregor
       from Slovenia
There is a problem with forms when using MAX function that returns NULL. The
only workaround I was able to find was not to return NULL value vith MAX
function.
For example :
max( decode( hour, 0, flag, '0' ) ) "0", ...

This shuld solve your problems in form.

      Rating: 5
base form on
      matrix view   November 27, 2003
Reviewer:
      Tarun  from India
I tried creating a form based on the view. Its working fine in Forms6i. Take the
column names as h1, h2 so on, without out double quotes. I too encountered
similar situation.

Thanks for nice solution tom.

November 26, 2003 Mo -- Thanks for the question regarding "unix
      login and security", version 8.1.7

      You Asked
Tom:

1.  Is there anything in oracle to tie in the unix userid login to an oracle
login? SOmeone was telling me that in informix they were setting privileges
based on unix id?

2.  Since you are in favor of implementng security in the database rather than
the application, do not you think it is really hard to maintain? i.e creatin 200
oracle user accounts or would you just create one per group and use that as a
login.

3. Still, with application userid/password I can set up different menu for each
user and different functionality.  This would be a nightmare to do using oracle
userid/password if you want to grant selects,updates etc for packages and tables
to each user.

Thank you,

      and we said...
1) yes, it is called OS authentication.

Just

create user USERNAME identified externally;

then you can "sqlplus /"

It is what I use.  I set my:

os_authent_prefix                    string      ops$

to ops$ actually, so that when I

create user ops$tkyte identified by password;

I have the OPTION of either

a) using "sqlplus /"
b) "sqlplus ops$tkyte/password"

as I choose (eg: when logged in locally, I use /, coming in over the network --
I use the password)

2) I'll let my database instance I run interally speak for me:

ops$tkyte@ORA.WORLD> select count(*) from all_users;

  COUNT(*)
----------
     49068

3) we have this thing called "roles" that are groupings of privileges.

ops$tkyte@ORA.WORLD> select count(*) from dba_roles;

  COUNT(*)
----------
       143

I have 143 different roles that I grant to users as needed (most of the apps
have admin screens that the admins for the APPS themselves just deal with this
-- I actually don't create/grant anything myself)

Review &
      Followup

      Rating: 4
unix login and
      security  September 09, 2002
Reviewer:
      A reader

      Rating: 5
unix
      login  September 09, 2002
Reviewer:  mo
       from vienna,va
Tom:

1.  I am truly confused on this answer. Is there any oracle documentation on
this OS authentication and its benefits.

what does this do:
create user USERNAME identified externally;

and where do you define this:
os_authent_prefix                    string      ops$

Is the idea here is to make your oracle userid/password the same as unix
userid/password  or windows userid/password.

2. OK I see that you set all of users at the database level through a menu
rather than using SQL*plus? Correct

If you are doing this for a web application do you set up 143 DADs one for each
role?

How do you determine what the user role is before he gets into the database? DO
you create a basic connect generic userid/password to connect everyone and then
find what his role is and then reconnect with that role?

Can you log the user in using his unix userid/login right away?

Thank you,

          Followup:

1) amazingly enough -- yes, we actually document this stuff (that is tongue in
cheek, pretty much everything is documented -- no one ever seems to read it
though)

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76956/dba.htm#1064
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76956/users.htm#682
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76956/privs.htm#15555
are all pointers into the single Admin guide that covers such topics.  Your OS
specific guide may have further details (eg: the windows one does)

the idea is to make it so you don't have an oracle password at all.  you log
into the OS and the database trusts you are who the OS says you are.

2) a menu?  No, I have a webpage.  People go there and request an account, they
get said account created and a password is generated and emailed to them.  This
account has CREATE SESSION and access to PUBLIC things.

Now, they can go to an application and if the application requires privileges
beyond CREATE SESSION and PUBLIC, they use the application screens to request
that access.  That generates an EMAIL to the application owner who can either
accept or reject such a request.  If they accept, a role is granted and email
sent.  If they reject -- an email is sent with the reason why.

We only need one DAD -- one that doesn't have the username/password stored with
it so the end user provides theirs.

Now, over the web, you will NOT be using OS authentication.  The client of the
database is the webserver, you want the client identified in the database to be
the browser.

      Rating: 5
unix
      login  September 18, 2002
Reviewer:  mo

TOm:

As a followup to this:
<quote>
On a user by user basis, you can use OS or External authentication.  For
example, I've setup my OS_AUTHENT_PREFIX in my init.ora to be "ops$".  I have a
Unix account "tkyte".  I've created a database user:

SQL> create user ops$tkyte identified EXTERNALLY;

Now, as long as I'm logged into Unix, I can simply:

$ sqlplus /

SQL*Plus: Release 8.1.5.0.0 - Production on Tue Jun 6 13:25:26 2000
(c) Copyright 1999 Oracle Corporation.  All rights reserved.
Connected to:
Oracle8i Enterprise Edition Release 8.1.5.0.0 - Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.0.0 - Production
ops$tkyte@DEV8I.WORLD>

The database picks up my username from the OS -- not from the command line.  It
trusts that I am TKYTE (ops$tkyte in the database as I've set it up) since I'm
logged into the Unix system.
</quote>

1.  What is the benefit of using unix account for an oracle login? IS this used
in web or more for client server applications?

2. How can a web user use this since he does not login to unix? I guess he
connects to a web server which logs in to the unix database? Do not you login in
to database directly from the web?

3. For each unix account you do:
SQL> create user ops$tkyte identified EXTERNALLY;

you always have to use "ops$xxx" which is the string you used in the init.ora.

4. Are you creating an oracle account without a password? or oracle is trusting
that your unix password is enough?

5.  What is ops$ stands for.  Can it be replace with any string like "dev" or
else?

THank you,

          Followup:

1) I use it for convienence and security.

$ sqlplus scott/tiger

well, it is trivial for someone to ps -aef | grep sqlplus and see my
credentials.  sqlplus / -- they get nothing

In my scripts, run in the background (backups and what not), we use sqlplus /,
we don't HAVE any passwords encoded in there.

2) cannot, there is a man in the middle.  the db client is the webserver, not
the browser.

3) if you set it to ops$, otherwise use what it is set to.

4) creating an oracle account that is identified by the OS, not by Oracle -- the
OS tells us the identity

5) OPS = OPerating System.  you can use what you like, ops$ is the convention.

      Rating: 4
Trapping a user
      name  May 02, 2003
Reviewer:  Prashant
      Munshi  from London, UK
Through a web page created through a procedure a user is logging in. He is
supposed to provide some comments or message like a post say. I want to trap the
username who posted such message as the chances are he can use some offending
language. Can I trap the username?

          Followup:

you wrote the application didn't you?

If you make them log in -- you know who they are -- you should be providing this
identity to all other procedures in your application.  I cannot tell you how to
"get" this data since i don't know how YOU are passing this data.

      Rating: 3
How about NT
      Authentication  June 19, 2003
Reviewer:  rae
       from singapore
hi,

i'm currently implementing this OS authentication for our system. basically,
those processes in Unix will login to oracle database via the "sqlplus /".
i'm just concerned, say, i have a unix user "SUPERMAN", then i create the oracle
user "SUPERMAN" which is identified externally. in effect, as long as i'm logged
in to unix using this "SUPERMAN", i just do sqlplus / then i'm already logged in
to the database. but what about, if i have an NT user "SUPERMAN"... i can
configure this so that i can also log in to oracle database right? but i only
Unix to be able to login to oracle using this name. any advise? thanks.

          Followup:

you won't have remote_os_authent set so the nt user will not be able to log in
over the network.

      Rating:
      4
remote_os_authent  June 20, 2003
Reviewer:
      rae  from singapore
hi tom,

thanks for taking time out to follow up on my query.

currently, the value of our "remote_os_authent" is TRUE. so that means i'll just
set it to FALSE right? but please verify if my understanding is correct for this
scenario... say the oracle server is in SYS_SERVER domain. then in NT_SERVER
domain, there is an NT user "SUPERMAN". if NT_SERVER is a trusted domain of
SYS_SERVER, then "SUPERMAN" can still login using OS authentication even if
remote_os_authent is FALSE.

another thing is, can you point me to a link where it tells me how I can
configure my NT/Win2k to be able to login to oracle through OS authentication? i
just want to play around with the value of remote_os_authent and other
parameters and check whether i can login to oracle or not.

appreciate your help on this.

          Followup:

check out the handy dandy windows NT Oracle admin guide:

 Authenticating Database Users with Windows

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/win.817/a73008/ch7.htm#1036322

      Rating: 4
impact of
      remote_os_authent  July 22, 2003
Reviewer:
      rae  from singapore
hi tom,

i'm using oracle v7x. i have changed the parameter remote_os_authent to FALSE.
Now i'm connecting through unix authentication. I just want to ask why
"@database_name" won't work in shell script (e.g. $userId@$database_name)? And
also, why "AT DATABASE_NAME" won't work in Pro*C (e.g. EXEC SQL AT DB_NAME
Select.....)?
Previously, when I'm connecting using user/password@database.. the
@database_name and AT DATABASE_NAME works.

Thanks.

          Followup:

it has nothing to do with remote_os_authent, that would affect ONLY

sqlplus /@database_name

you provide no error messages, no error text -- no inputs = no outputs.  I can
only guess you've changed other things as well

      Rating: 5
impact of
      remote_os_authent  July 23, 2003
Reviewer:
      rae  from singapore
Hi Tom,

Yes, it does not work for sqlplus /@database_name in shell script.
Regarding Pro*C... the program that I have below does not work... but if I
remove all the statement "AT DB_NAME" in my code e.g.

EXEC SQL SELECT shUserName, shPassword INTO :VANuser_var, :VANpassword_var From
SH_LOGIN Where shProcessName = :ProgramName;

then it works. We used to have this "AT DB_NAME" because we have multiple
environments in one server. And it used to work except now that we changed the
remote_os_authent to false.

-----------------------------
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <strings.h>
#include <sqlca.h>
#include <sqlcpr.h>
#include <time.h>
#include "vanapi.h"

char * USERID ="/";
char *tempArr;
char v_Password[20];

EXEC SQL INCLUDE sqlda;
EXEC SQL DECLARE DB_NAME DATABASE;

int GetUserPwd (char *ORADataBase, char *ProgramName, char **tmpRec)
{
   EXEC SQL BEGIN DECLARE SECTION;
      VARCHAR VANuser_var[LEN_VAN_USER];
      VARCHAR VANpassword_var[LEN_VAN_PWD];
      int b;
      int r;
      int s;
   EXEC SQL END DECLARE SECTION;

   short vantiveuser_ind;
   short vantivepwd_ind;

   tempArr = (char *)calloc(LEN_VAN_USER, sizeof(char));

   EXEC SQL CONNECT :USERID AT DB_NAME USING :ORADataBase;
   EXEC SQL WHENEVER SQLERROR DO return ( 1 );

   EXEC SQL AT DB_NAME SELECT shUserName, shPassword INTO :VANuser_var,
:VANpassword_var From SH_LOGIN Where shProcessName = :ProgramName;

   VANuser_var.arr[VANuser_var.len] = '\0';
   VANpassword_var.arr[VANpassword_var.len] = '\0';

   strcpy(tempArr, (char *) VANuser_var.arr);
   tmpRec[0] = strdup(tempArr);
   strcpy(tempArr, (char *) VANpassword_var.arr);
   tmpRec[1] = strdup(tempArr);

   return (0);
}  /*end GetUserPwd*/

          Followup:

well, of course it won't work for sqlplus /@dbname -- that is the entire sole
purpose of remote_os_authent

it should be false

/@dbname won't work -- should not work -- if it does work it is generally a huge
security issue.

My car "does not work" -- why not?  I'll not give you any diagnostic error
messages, none of the information from the dashboard.  I won't tell you about
any funny noises or anything.  now "guess" :)

get my point?

      Rating:
      4
reader  September 10, 2003
Reviewer:
      reader
Hi TOm

I want to spool a txt file.The platform is unix. I want
to use a path in the sql script but can not be hard coded.
like spool path/file1.txt. I try to use &1 to pass in or
export a environment, but both are not successful. Can you give me a idea?

Thank you!

          Followup:

spool /tmp/&1..txt

works??

      Rating:
      4
reader  September 10, 2003
Reviewer:
      reader
Tom

Thanks.

I hope the following works
spool &1./filename.txt

&1 can be pasted in as a path like /tmp
But this does not work. Where am I wrong?

          Followup:

ops$tkyte@ORA920LAP> @test /tmp
ops$tkyte@ORA920LAP> spool &1./filename.txt
ops$tkyte@ORA920LAP> prompt foo
foo
ops$tkyte@ORA920LAP> spool off
ops$tkyte@ORA920LAP> !cat /tmp/filename.txt
ops$tkyte@ORA920LAP> prompt foo
foo
ops$tkyte@ORA920LAP> spool off

seems to work for me.

      Rating: 3
How to trap
      SQL*PLUS / TOAD logins?  November 24, 2003

Reviewer:  Arul kumar  from United Kingdom

Tom,

Thanks for the info.

Could you please tell us how to trap "non-application" logins in production
environment.

For example, one of my application has a one to one mapping of application
userids and database userids (password same!). Now, in such a scenario, with
little more knowledge any application user could get hold of tools like TOAD /
SQL*PLUS and connect to the database.

I would like to identify such LOGINs and disconnect them. I tried 8i - Database
- LOGON Trigger(using SYS_CONTEXT and V$Session info). It did work, but the
problem is that "any executable" could be "renamed" as the application name.
Isnt it ??

Many Thanks

          Followup:

that is correct -- there is no way to do this.

if you are using 3 tier apps, you can limit logons to specific machines --
effectively locking it down.

if you are client server, you will basically have to permit them.  You've
basically disabled security on your system with your current user/pass approach.

      Rating: 4
Unix and
      SQLPLUS   November 26, 2003
Reviewer:
      Reader
Hi Tom,

I have a simple shell script to call the sql script like this:
sqlplus username/password  @test.sql

I want to track the error if the above is not successful using the $? in the
shell script.

Two questions:
1, If the username/password(or database is down)  is not correct how to track
the error( I use the Korn shell, I can not track the error in the shell script)
?

2, If the test.sql is not there can I have a  way to track inseated of checking
the file if exist in the shell script ?

Thanks!

A reader

          Followup:

sqlplus /nolog and put the connect in the script -- then you can exit on
failure.

like this:

sqlplus /nolog <<EOF
whenever sqlerror exit failure
connect bob/mary
EOF
echo $?

sqlplus /nolog <<EOF
whenever sqlerror exit failure
connect /
EOF
echo $?

my output was 1 and 0....

you use script to see of files exist (i sort of like the EOF thing tho, keeps it
all in one file)

November 26, 2003 Gladiola -- Thanks for the question regarding
      "PASSWORD HASH", version 817

      You Asked
We have a situation where web enable application si build using oracle database
as backend.
We want to know which is  a best way of hashing a password as  both (oracle and
vb.net  supports) We use vb.net as front end

Thanks very much

      and we said...
hash_password :=
   dbms_obfuscation_toolkit.md5( utl_raw.cast_to_raw( username || '/' ||
                                                          :password) );

that'll give you a 32 character hex string that is the hashed representation of
the user/password for that user.

Store that and all you need to do is validate against it in the future.
Review &
      Followup

      Rating: 4
I know
      that  November 26, 2003
Reviewer:  Gladiola
       from canada
..bUT My question is which is the best way
to hash in which tier
pross and cons?

Sorry for bugging you again

          Followup:

I do everything I can in the database ;)

so, for me, that is better.  That way -- when you decide to through out VB and
go for a portable open systems approach, you are well on your way -- the less VB
you have the more portable you'll be!

November 27, 2003 Hector -- Thanks for the question regarding "SQL
      to identify executing sql statements that are not using indices.", version
      8.1.7

      You Asked
Tom,

Do you have an SQL script that will identify sql statements (that are currently
executing or have executed in the past) that are not using indices in the query
plan? Thanks in advance.

      and we said...
why?  do you believe indexes are "good things 100%" (they are not)

I would use statspack to identify HIGH LOAD sql (sql with the most LIO's per
execution)

that is what you need -- not to find queries that are not using an index, but
rather queries that are doing the most work.

fix those and who cares if the others are not using an index, they are not
contributing appreciably to your workload! (or they will be the top sql and
you'll find them that way)
Review & Followup

      Rating: 1
Just a tough
      ...  November 26, 2003
Reviewer:  Christo
      Kutrovsky  from Ottawa, ON Canada
you have 8i ... so it won't work

but in 9i, you can use v$sql_plan to get that:

break on address skip 1
select address,sql_text||'_DUMMYNEWLINE' from v$sqltext_with_newlines
where (address,hash_value) in
(select address, hash_value
from v$sql_plan where OPTIONS='FULL' and OPERATION='TABLE ACCESS'
)
order by address,piece

I usually do a find/replace on "_DUMMYNEWLINE\n" with "" i.e. pieces together
the original sql.

However, sometimes HASH_JOINS is the only way to go, if you play arround you can
have this report only sql where the parent step of the full table scan is not a
hash_join for example ...

          Followup:

and what do you get from that

.... nothing useful ....

if you follow the simple minded approach of "tune things that full scan", you'll
miss the real low hanging fruit -- the things that impact your system.

      Rating:
      3
  November 26, 2003
Reviewer:
      Hector  from NJ
Tom, thanks for your response. Agree, indexes are not necessarily a good thing.
However, it would be good to
1. identify if an index has been created and is not being used so it can be
deleted.
2. identify concatenated indices that may have been created and are not being
used correctly.
2. Not rely on the DBA (too much) for stats pack data.

          Followup:

1) in 9i, this is simple -- alter table t monitoring.  but your approach of
looking for queries that do not use an index would NOT help you with this!!!

2) same as #1, finding full scans would not help you with this (but 9i will)

3) you need access to the v$ tables to do these things regardless -- in for a
penny, in for a pound.  You'd need them anyhow

      Rating: 2
About full
      table scans  November 26, 2003
Reviewer:
      Christo Kutrovsky  from Ottawa, ON Canada.
Just as full table scans can be good, they can be bad.

It's not a matter of whether full scans are good or bad, it's a matter of
finding which sql does a full table scan.

Maybe he is looking for specific queries. Maybe he is looking for full table
scans on a specific table.

As useless knowing which queries are doing full table scans is, as usefull it is
to know that query N is doing a full table scan of table P because of implicit
datatype conversion.

It may not be the single thing that's killing the system, but sure is a fast way
of finding at least one of them.

Agreed that many people thing that FULL TABLE SCAN = bad, but maybe Hector will
use this information "the right way".

          Followup:

it is not useful to look for full scans.

No more so then racial profiling is useful for finding bad guys.

It is the wrong approach  -- plain and simple.  He came back and stated his
goals, *none* of which can be even partially met by finding full scans!!!

You look for high load sql, not for sql doing "operation X"

there is no "right way" to use this information, it is like a cache hit ratio.
Suppose I have a 99.5% cache hit.  Is that

a) good
b) bad
c) indifferent

the answer is d) all of the above, none of the below, you have no idea what it
means.  In itself, it is a useless piece of information.

Now, if you find a high load SQL and you discover it is doing a full scan
then perhaps (maybe) the full scan is the culprit and some query tuning can
help.

I should have pointed out a method to achieve Hectors stated goals of:

1. identify if an index has been created and is not being used so it can be
deleted.
2. identify concatenated indices that may have been created and are not being
used correctly.
2. Not rely on the DBA (too much) for stats pack data.

in 8i.  If you have my book "Expert one on one Oracle" -- i describe the
technique in full in the chapter on Stored Outlines.

Basically, you could use a trigger (on logon) to alter session and set stored
outlines on.  This would capture the hints needed to stabalize a query plan into
the outline tables.  After you run for a while, all of the necessary information
would be in there -- what queries are using what indexes which satisfies #1 and
#2.  #3 is implicit since you only need the dba to create the trigger and then
drop the trigger later.

November 27, 2003 mohan -- Thanks for the question regarding "MTS
      CONFIG", version 8.1.6

      You Asked
Hai
I am facing the same problem.It was puzzling to find out where i made mistake .I
had set every thing according to oracle doc and showing status "ACTIVE" and
server "SHARED" if using "SYSTEM"(user) and "SYS" session,but using any other
session except "SYSTEM"and "SYS", status "INACTIVE" server "NONE"..I am giving
as below how to follwed the steps

 Add parametrs for connection pooling  In  Init.ora file
 ======================================================
db_name = "demo"
instance_name = demo
service_names = demo
mts_service="demo"

mts_dispatchers =
"(protocol=TCP)(disp=4)(conn=500)(host=mohan-2k)(list=MOHAN)(pool=on)(tick=1)
(sessions=4000)"

 MTS_MAX_DISPATCHERS =16
 MTS_SERVERS =2
 MTS_MAX_SERVERS =10

Tnsnames.ora
=============
(The below netservice name used at server where database resides)

MTS =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = 10.0.0.62)(PORT = 1528))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = demo)
      (SRVR = SHARED)
    )
  )

(The below netservice name used at Client side )

MM =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = mohan-2k)(PORT = 1528))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = demo)
      (SRVR = SHARED)
    )
  )

Listener.ora
============:

MOHAN =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = mohan-2k)(PORT = 1528))
  )

SID_LIST_MOHAN =
  (SID_LIST =
    (SID_DESC =
      (GLOBAL_DBNAME = DEMO)
      (ORACLE_HOME = D:\ORACLE\ORA81)
      (SID_NAME = DEMO)
    )
  )

Status of dispatchers
=====================:
LSNRCTL> services mohan
Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=mohan-2k)(PORT=1528)))
Services Summary...
Service "DEMO"          has 2 instances.
    Instance "DEMO"
      Status: READY  Total handlers: 1  Relevant handlers: 1
      Class: ORACLE
        DEDICATED established:0 refused:0 current:0 max:0 state:ready
        (ADDRESS=(PROTOCOL=beq)(PROGRAM=oracle)(ENVS=)(ARGV0=oracleDEMO)(ARGS='(
LOCAL=NO)'))
    Instance "demo"
      Status: READY  Total handlers: 4  Relevant handlers: 4
      Class: ORACLE
        D003 established:0 refused:0 current:0 max:4000 state:ready
        (ADDRESS=(PROTOCOL=tcp)(HOST=mohan-2k.nirbeeja.com)(PORT=1272))
            Session: NS
        D002 established:0 refused:0 current:0 max:4000 state:ready
        (ADDRESS=(PROTOCOL=tcp)(HOST=mohan-2k.nirbeeja.com)(PORT=1271))
            Session: NS
        D001 established:0 refused:0 current:0 max:4000 state:ready
        (ADDRESS=(PROTOCOL=tcp)(HOST=mohan-2k.nirbeeja.com)(PORT=1270))
            Session: NS
        D000 established:0 refused:0 current:0 max:4000 state:ready
        (ADDRESS=(PROTOCOL=tcp)(HOST=mohan-2k.nirbeeja.com)(PORT=1269))
            Session: NS
The command completed successfully
LSNRCTL>

SQL> conn mohan/mohan@MM;
Connected.

SQL> conn system/manager@mts;

SQL> select username,server,status from v$session;

USERNAME                       SERVER    STATUS
------------------------------ --------- --------
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE

USERNAME                       SERVER    STATUS
------------------------------ --------- --------
SYSTEM                         SHARED    ACTIVE
MOHAN                          NONE      INACTIVE

Thanks in advance
mohan

      and we said...
that just means MOHAN is connected - but not doing anything.  MOHAN doesn't have
a server -- this is normal.

the reason SYS and SYSTEM always SEEM to show shared is because you are actually
running a query under those accounts (the output of which we are seeing).  they
have a shared server -- hence their server is none.

run a query in mohan's session -- make it a long running query -- and then check
out the server and status.
Review & Followup

      Rating: 3
MTS
      CONFIG  May 15, 2003
Reviewer:  mohan
       from india
Thanks for your valuable information

1)As for you say i ran a query(long running query ) in mohan's session and then
checked out the server and status at system schema its showing SERVER IS NONE
2)Connection pooling is necessery on  oracle8i on windows 2000 because oracle 8i
supporting 32 k sessions for single instance and windows 2000 operating system
supports 200k tcp network sessions .Is it necessery on oracle8i?.Give me some
suggestions when it required to implement connection pooling.
Thanks in advance
mohan
mohan

          Followup:

1) you did something wrong then.  trust me, really, truthfully, 100% -- you did
something wrong in your testing.

That SYS and SYSTEM show up as shared proves that.  Especially SYSTEM.  SYSTEM
is just a normal guy.

Tell you what, have MOHAN run the query -- then you'll be assured it is active
and you'll see!

2) 32k sessions on windows -- must be some box you have there to compensate.

have you really thought this through?

      Rating: 2
mts
      config  May 16, 2003
Reviewer:  mohan
       from india
Thanks for your prompt reply

As for you say i made some where wrong .Really implement mts in my oraganization
.I had do every thing acording to documentation. i had  run query  (long running
query at mohan session it tooks me around 20 minutes).
Please let me know with some examples where i made mistake .
If i done any mistakes in any configuration files.
Thnak you your articles helped a lot in my carrer.

SQL> conn mohan/mohan@MM;
Connected.

sql>select * from tt;

It took me around 20 minutes to get all rows

Then connect to
SQL> conn system/manager@mts;

SQL> select username,server,status from v$session;

USERNAME                       SERVER    STATUS
------------------------------ --------- --------
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE

USERNAME                       SERVER    STATUS
------------------------------ --------- --------
SYSTEM                         SHARED    ACTIVE
MOHAN                          NONE      INACTIVE

2)As for your second query  i thinks so.Is it really through?

I red the documentation its given ,connection pooling is can justified to use
the following cases
Your operating consuming more memory for each tcp connections ,you may reduce
the connections in oredr to release the memory

Oracle 8i supports 32 k sessions for single instance,but your supporting less
number of tcp connections ,you can think about connection pooling

Am i right?

Thanks in advance
mohan

          Followup:

1) look -- two different connect strings -- mm vs mts.

look MOHAN is *obviously not doing anything* in the "mts" database.  he is NOT
running a query as you say.  maybe he is in another database, but not in this
one.  not at the time you check.

when I say "you've made a mistake", I'm talking about how you are analyzing this
problem.  do what I said -- run the v$session query as mohan and you'll see

a) mohan is shared, active and...
b) system is none, inactive....

2) I cannot imagine getting to 32k connections on a windows machine.  give me a
real case to talk about, not some hypothetical case you'll never actually get
to.

      Rating: 2
mts
      config  May 17, 2003
Reviewer:  mohan
       from india
Hai tom

Sorry in hurry i have done some mistakes in my early reply

1)As for you say i made some where wrong .Really will implement mts in my
oraganization
.I had do every thing acording to documentation. i had  run query  (long running
query at mohan session it tooks me around 20 minutes).It still showing status
"INACTIVE"(mohan,sys sessions) at system session .I will trust you .Please let
me know with some examples that will come to know where i made wrong.Thnak you
your articles helped me a lot

SQL> conn mohan/mohan@MM;
Connected.

sql>select * from tt;

It took me around 20 minutes to get all rows

At system i had the following query
SQL> select username,server,status from v$session;

USERNAME SERVER STATUS
------------------------------ --------- --------
MOHAN  NONE   INACTIVE
SYSTEM SHARED  ACTIVE
SYS    SHARED    INACTIVE

Now "sys" status also showing INACTIVE.If i done any mistakes in any
configuration files OR The procedure i follwed is completely wrong

2)connection pooling is unlikely required on oracle 8i on windows 2000 as for
the doc described

I red the doc
Connection pooling can be justified the folowing cases

Operating system consumes more memomery for single tcp connections you may
reduce the number of connections in order to release the meomery.The connection
pooling reclaim the resources that was consumed by idle network connection to
database

Operating system not supporting large number of tcp connections ,you may think
about connection pooling .Am i right?

Thanks in advance
mohan

          Followup:

1) see above, you are just doing something wrong.  I've never seen shared when
status = inactive.  when status = inactive you do not by definition have a
shared server process!  Not that I don't believe you but -- well, I don't at
this point.  You connect to MM, you connect to MTS -- etc.

In short, you are using shared server (that server = none for mohan is enough to
see that).  Your testing is flawed, mohan cannot be running a 20 minute query at
the time you do this query - else mohan would be active.  run that query over
and over and over -- you need to catch mohan WHEN HE IS ACTIVE.

      Rating: 5
I
      Think...  May 17, 2003
Reviewer:  Kamal
      Kishore  from New Jersey, USA
I think that he is waiting for the 20 minute query to finish completely and then
login to SYS/SYSTEM to check the status.
This seems a possibility to me.
Is this correct, or you actually have two SQL*Plus sessions going and the query
is actually taking 20 minutes.

What I found during my testing was that when I run the following query:

select x.*, y.* from all_objects x, all_objects y ;

from my shared server connection, and go back to my SYS login session to see the
status, I always get the following
output:

USERNAME                       SERVER    STATUS
------------------------------ --------- --------
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
SYS                            DEDICATED ACTIVE
KKISHORE                       NONE      INACTIVE

14 rows selected.

Note that the connection for KKISHORE is shown as INACTIVE even though the
SQL*Plus session was still displaying the results. This indicates that the
Oracle system actually finished creating the resultset much before we got the
chance to go back to SYS session and query V$SESSION and that is the reason of
session shown as INACTIVE.

In order to see if this was actually the case, I needed a query that should
actually take some time to create the resultset (note that we are not counting
the time it takes for the query result to finish displaying on screen, but the
time it takes for the server to actually generate the resultset). I took the
following query:

select x.*, y.* from all_objects x, all_objects y where x.object_id =
y.object_id * 0 ;

This query joins ALL_OBJECTS with itself and the condition will result in no
matches. This will give the Oracle server some time to process the query and
enough time for us to go to the SYS session and query again. The result is as
follows:

USERNAME                       SERVER    STATUS
------------------------------ --------- --------
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
                               DEDICATED ACTIVE
SYS                            DEDICATED ACTIVE
KKISHORE                       SHARED    ACTIVE

14 rows selected.

Now we see that session for KKISHORE is actually shown as SHARED/ACTIVE.

So, it shows that if you run a query that takes 20 minutes to execute, it might
be that it takes 20 minutes to complete the output on screen, but that does not
necessarily mean that server is busy for the duration of 20 minutes. It might
have already generated the result into the response queue.

You need a query that makes the server busy for sufficiently long period for you
to go back to the other session and query the status.

I hope I'm correct in my interpretation.
Is this correct, Tom?

          Followup:

No, it doesn't mean that --

"This indicates that the
Oracle system actually finished creating the resultset much before we got the
chance to go back to SYS session and query V$SESSION and that is the reason of
session shown as INACTIVE."

is not accurate.  If you just kept hitting "slash" and running that v$session
query -- you would see that user go in and out of ACTIVE/INACTIVE over and over.

That query can return the first 15 rows very fast and does.  That session will
ONLY appear active when a fetch call is being processed -- when sqlplus is
processing the data (printing 15 rows -- which takes many times longer then
Oracle fetching 15 rows) the session will appear inactive.  In this case, that
session will appear MOSTLY inactive due to the very nature of the query.

but in general, yes, you are dead on.  You have to catch the session in ACTIVE
in order to see server=shared.  the fact this guy always saw INACTIVE implies
his test was flawed, not testing what he wanted to test.

      Rating: 5
Excellent
      insight, but It makes me wonder...  May 17, 2003

Reviewer:  Kamal Kishore  from New Jersey, USA

Hi Tom,
Just when you think you are begining to understand the given concept, you
suddenly realize that there is so much more to it than you ever imagined...
Your review just explained to me this concept in more details. Now I understand
that for the same query, the server will go from ACTIVE to INACTIVE and back and
will stay INACTIVE for a longer period (compared to ACTIVE period) due to the
slow nature of the connected client.

Does this mean that for subsequent FETCHes of the same query we may not get the
same shared server? It might be that some other free shared server does the next
fetch. Correct?

But it makes me wonder about the idea of seeing the session as SHARED/ACTIVE in
the V$SESSION view? Is it that critical for the confirmation of the correct
setup of MTS?

I thought that the query from the V$CIRCUIT view should be enough to actually
see that you are connected via a shared server:

select circuit, dispatcher, status from v$circuit ;

CIRCUIT  DISPATCH STATUS
-------- -------- ----------------
1467E370 13A3AB08 NORMAL

1 row selected.

If I connect via a DEDICATED connection, the above query gives no rows selected
(indicating no connections are using SHARED servers):

select circuit, dispatcher, status from v$circuit ;

no rows selected

If the idea is to make sure that MTS setup is correct, is the query from
V$CIRCUIT view not sufficient enough to establish the fact that, yes, the MTS is
setup correctly?

          Followup:

using shared server you might find that

a) you parse your query with s001
b) you fetch the first row with s002
c) you fetch the second row with s003
d) you close the cursor with s004

with shared server, you might never use the same shared server twice (you
probably do, but you don't need to).  Your session migrates from server to
server -- and only uses one when it is ACTIVE -- meaning you are "fetching" or
processing

You just need to see server = none or shared to know you are using shared
server.

      Rating: 5
Can you draw
      the same conclusion...  May 17, 2003
Reviewer:
      Kamal Kishore  from New Jersey, USA
Hi Tom,
In your follow-up you said:
"You just need to see server = none or shared to know you are using shared
server."

Can you draw the same conclusion from the V$CIRCUIT query?

          Followup:

it would be more difficult, you would still need to get back to v$session to see
WHO the circut is for.

      Rating: 5
Mts
      config  May 18, 2003
Reviewer:  mohan
       from india
Hai tom
Thanks for your  information .My problem is solved now .As for you say my
testing is flawed that is correct.Anyhow i got clear my doubts regarding mts  as
the way given precise explanation .

Thanks
mohan

      Rating: 5
MTS/Oracle
      Expert  May 19, 2003
Reviewer:  Mike

Tom,
We used the Oracle Expert to find out the issues of the database.
The Oracle Expert found out:

"

       The Shared Server (formerly known as MTS) configuration has a major
       impact on how memory is utilized within the SGA.  Oracle Expert takes
       this into account during the evaluation.

       This instance is currently configured to use Shared Server.
"

We have never setup the MTS "intentionally".  The following were the
verification of if the MTS used, apparently MTS has not been setup.
How did Oracle Expert found the db is using MTS?

Thanks

Mike

SQL> show parameter mts

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
mts_circuits                         integer     225
mts_dispatchers                      string      (PROTOCOL=TCP) (SERVICE=tol32d
                                                 b1XDB)
mts_listener_address                 string
mts_max_dispatchers                  integer     5
mts_max_servers                      integer     20
mts_multiple_listeners               boolean     FALSE
mts_servers                          integer     1
mts_service                          string      tol32db1
mts_sessions                         integer     220
SQL>

$ ps -ef|grep ora_s
  oracle 25867     1  0   Apr 26 ?        3:00 ora_smon_tol32db1
  oracle 25876     1  0   Apr 26 ?        0:05 ora_s000_tol32db1

SQL> select username, server from v$session;

USERNAME                       SERVER
------------------------------ ---------
                               DEDICATED
                               DEDICATED
                               DEDICATED
                               DEDICATED
                               DEDICATED
                               DEDICATED
                               DEDICATED
                               DEDICATED
                               DEDICATED
                               DEDICATED
TTO                            DEDICATED

USERNAME                       SERVER
------------------------------ ---------
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED

USERNAME                       SERVER
------------------------------ ---------
TTO                            DEDICATED
SBU                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED

USERNAME                       SERVER
------------------------------ ---------
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED
TTO                            DEDICATED

44 rows selected.

SQL>

      Rating:
      5
circuits  May 26, 2003
Reviewer:
      Reader  from Houston, TX
In shared server configuration, each and every session will have a "circuit"
that my understanding is of a piece of memory in the SGA particulary in the
respective UGA. Is it correct? Even though my session will use different server
processes during the life of my session, the circuit that consumes some memeory
will always be there in my UGA until my session is active? Is it correct? Is
there any fixed bytes of memory that Oracle would allocate for each session for
a circuit in UGA? If so, is there a view to find out how much memory each
circuit consumes? Is the number of circuits is directly(?) related to number
concurrent sessions that I can have in my shared server configuration? I wanted
to clarify my understanding about shared servers. Please help. I have read your
book explaining about shared server architecture. It helped me a lot.

Thanks for your time.

          Followup:

a circuit is more a logical thing, it is a "circuit" -- connection.

It describes the state of a shared server connection.

with shared server -- there are message queues associated with dispatchers and
shared servers (client gives a request to a dipatcher, dispatcher queues the
message, shared server dequeues and processes the message, shared server places
response back onto the message queue in the sga, dispatcher dequeues it and
gives it to a client)

v$circuit describes where in this process the connection is.  it doesn't really
consume memory above the row in the v$ table.

      Rating: 4
message
      queue  May 27, 2003
Reviewer:  Reader
       from Houston, TX
Thanks. Is there a way to estimate how much memory we need to allocate in the
SGA for the request and response queues? Which part of SGA these queues reside?
From your book, my understanding is that if we use MTS, we should configure
large pool for session ugas. but uga will have other structures also, for
example sort_area_retained_size among others. My question is where are these
queues in the SGA and which part of SGA i must configure for these queues?
Thanks again for your time.

      Rating: 4
more info about
      circuits  May 27, 2003
Reviewer:  Reader
       from Houston, TX
I got a chance to read oracle university DBA Fund II course book.
<quote>
Virtual circuits are user connections to the database through dispatchers and
servers. The CIRCUITS parameter specifies the total number of virtual circuits
that are available for inbound and outbound network sessions. This parameter is
of interest because it is one of the several parameters that contribute to the
total SGA requirements of an instance.
<quote>
If you come across any info about how much memory we need for these circuits
(one circuit for one session?), please let us know. Logically, it makes sense
that uga may be the location for the circuit for a session. Thanks for your
time.

          Followup:

v$circuit is the memory they are talking about -- v$ tables are memory (in
general, most of them are)

      Rating: 5
What does this
      mean ?  June 04, 2003
Reviewer:  Robert
       from SIN
1) My db is configured with the MTS server. I saw this from v$session and could
not really understand :

USERNAME                       STATUS   SERVER
------------------------------ -------- ---------
OPS$USER1                      INACTIVE NONE
                               ACTIVE   DEDICATED

Why there is this "additional" entry of "ACTIVE   DEDICATED" ?

My tnsnames.ora entry :

DBTCP =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = server1)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SID = prod)
      (SERVER = SHARED)
    )
  )

2) Is it possible to increase the MTS_CIRCUIT param in the init.ora > sessions
(2x) ? any side effect. I got the problem of "virtual circuit creation error"
but the Oracle Max sessions is not reach. We use MTS and trigger to remote db
via dblink.

Thank You.

          Followup:

  1* select username, program, status, server from v$session
ops$tkyte@ORA920> /

USERNAME  PROGRAM                        STATUS   SERVER
--------- ------------------------------ -------- ---------
          oracle@tkyte-pc (PMON)         ACTIVE   DEDICATED
          oracle@tkyte-pc (DBW0)         ACTIVE   DEDICATED
          oracle@tkyte-pc (LGWR)         ACTIVE   DEDICATED
          oracle@tkyte-pc (CKPT)         ACTIVE   DEDICATED
          oracle@tkyte-pc (SMON)         ACTIVE   DEDICATED
          oracle@tkyte-pc (RECO)         ACTIVE   DEDICATED
OPS$TKYTE sqlplus@tkyte-pc (TNS V1-V3)   ACTIVE   DEDICATED

7 rows selected.

you'll find them for the background processes.

one would really need to provide the ORA-XXXXX error code.  don't know what
error you are getting really.

      Rating: 5
Virtual Circuit
      Error  June 04, 2003
Reviewer:  robert
       from SIN
1) thanks for answering part 1.

2) The error I got in the trace file :

*** 2003-05-28 06:00:03.335
error 18 creating virtual circuit

That's the reason I want to increate the MTS_Circuit para

          Followup:

and where do you get that error -- it does not appear to be an ORA-XXXXX error.

      Rating: 5
Virtual Circuit
      Error   June 09, 2003
Reviewer:  robert
       from SIN
The error is from the trace file of the bdump directory :

prod__d004_4821.trc

          Followup:

tell you what, contact support and open an itar.

      Rating:
      3
  October 22, 2003
Reviewer:  mohan
       from india
Hai tom
I have some questions which have faced in interviews

1) How many max number of dispatchers supports mts in oracle?
2)Difference between odbc and oledb?
3)When connecting to database from oledb ,which type of protocal use?
4)In clent server technology how to improve the network performance?
example:In MTS 10 clents concurrently using batch process in that case how to
improve the network performance?
5)In distributed enviornment how to improve the network performance?
6)What is the maxmium limit for open_cursors ?

7)what is the pros and cons of cold backup and hot backup?

Please answer the above queries
Thanks in advance

          Followup:

hmm, you know what -- if I had interview questions like that and didn't know the
answer, I would -- well, look them up!

but anyway-- they are mostly "silly" questions.  not really "relevant".

1) well, as always since things change, I would refer immediately to the handy
dandy Reference guide.  ta dah -- it is OS specific.  now what?  i'm supposed to
know the values of an obscure parameter that I would read about before using for
all platforms?

2) they are just different apis and used by developers.  I thought I was
interviewing for a DBA slot??  I mean, if #1 was a question, #2 isn't really
relevant to me? or if it is -- #1 was really not relevant.

3) Oracle net -- everything uses Oracle net.

4) If you are interviewing with someone who uses MTS to run batch processes --
run, do not walk, run towards the front door and get away.  they have
fundementally misunderstood the most basic technology (or never bothered to read
about it!)

answer they were maybe looking for:  I would use plsql, no network traffic now,
everything stays local to the server.

5) see #4.

6) see #1, and who cares!

7) cold backups are done by people who don't know better or have an irrational
fear of the word "hot".  there are no pros to a cold.  hot is the only way to
go.  as all databases that contain any data worth backing up are in archive log
mode, they can all be done hot.

      Rating: 4
purpose of MTS
      is getting defeated,isn't it  October 22, 2003

Reviewer:  Ashiq Shamsudeen A  from Trichy, Tamil
      Nadu, India
Hi Tom,

 The 4th questions of Mohan, ie
"In clent server technology how to improve the network performance? example:In
MTS 10 clents concurrently using batch process in that case how to improve the
network performance?"

    As for as MTS the transaction should be very small,so that user process will
not wait for server process to do the task. If they run batch process then
every user process will wait for the server process to do its task ,isn't it?

  Even you've mentioned in your book (expert one on one) "trasaction should be
very small" if the db going be MTS, isn't it?

  If we going to run batch program ,then we're defeating the  purpose of MTS. Am
I right ,Tom?

          Followup:

correct, that is why i said "run, do not walk, run run away" is the correct
answer for #4 :)

      Rating:
      3
clusters  October 30, 2003
Reviewer:
      mohan  from india
Hai tom
I have a problem in cluster .When inserting into cluster table i got the
follwing error
 Clustered tables cannot be used before the cluter index is built

I follwed as

ceate cluster emp_clust(empno number,deptno number) size 250 tablespace t1
storage(initial 10k next 20k ,minextents 1 maxextents 50 pctincrease 10);

Then
Create table emp1(empno number,ename varchar2(20),sal number,deptno number)
cluster emp_clust(empno,deptno);

when inserting data into above table i got the above error

2)How to insert data into hash cluster table?give me with some examples..
Thanks inadvance
MOhan

          Followup:

hmm, this has what to do with networking stuff exactly?

if you have my book "expert one on one Oracle" -- i walk thru this stuff in
detail.

the error message is sort of self explainatory.  create the index on the key
columns in the cluster!

short of that, the docs

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96540/statements_52a.htm#2094463
actually have complete examples.

there is no difference between inserting into a hash cluster and a "normal"
table.  it is 100% transparent to the application.

      Rating: 5
Some more Q on
      connection types  November 26, 2003
Reviewer:
      Oleg Oleander  from Hungary
Dear Tom,

In your book Effective Oracle By Design in Chapter 3 you say:
"Its possible for a single application, using a single physical connection
(dedicated server) to have many sessions concurrently active."
I think I understand dedicated and shared server connections, but I dont
understand the statment above.
Thanks is advance
Oleg

          Followup:

you can have a single physical connection

that connection may have as many sessions as you want to create going in it.

      Rating: 4
sorry, but
      still dont get it  November 27, 2003
Reviewer:
      Oleg Oleander
For example, we have a clinet/server app. The client is java. it creates a
connection with a connect_string. At this point we have one physical connection,
one session, one session-state. Now, can I create an other separate session with
its session-state without creating another physical connection? It would be very
useful, for the client while filling a long list, would allow the user to do
other things. Tell me please, how?

ThankxXXX

          Followup:

ok, maybe I should say it differently

a single connection may have many concurrent sessions

drop the word "active".

But to achieve your goal, I would not have long running processes in a client
application.  It screams for:

a) use dbms_job and run it in the background
b) use AQ and messaging to have it, well, run in the background

November 27, 2003 Saminathan -- Thanks for the question regarding
      "Why am i getting negative 'execute to parse' ratio in my statspack
      report?", version 8.1.7

      You Asked
Dear Tom,

Why am i getting negative  'execute to parse' ratio in my statspack report?  The
problem is happening in one of our(i2) product. This query has been generated by
Weblogic Application Server.

For example, attached is a sample query (almost all the queries are giving the
same kind of result)

The other ratio like  Buffer Hit, Library Hit, Latch Hit ,etc looks all right.

Could you please give me some info on this?

Thanks Tom.

********************************************************************************

SELECT TOTAL_AMOUNT, COMMODITY_DESC, BOM_FLAG, BOM_TOP_ASSEMBLY,
  DESIGN_PROJ_NAME, COMMENTS, BIDDER_UOM, BID_STATUS, TOTAL_QUANTITY,
  AWARDED_QTY, RFX_ID, TERM_ID, COST_ID, ITEM_CAT_ID, LINE_NUMBER, ITEM_NAME,
  ITEM_TYPE, ITEM_LOCALE, ITEM_CURRENCY, UNIT_OF_MEASURE, SUB_ITEM_FLAG,
  CATALOG_ITEM_ID, SKU, COMMODITY_CODE, CAT_CONTRACT_ID, INTERNAL_PART_NUM,
  MANUF_PART_NUMBER, SUPPLIER_PART_NUMB, MANUF_PART_ID, SUPPLIER_PART_ID,
  QUANTITY, UNIT_PRICE, AMOUNT, UNIT_COST, DISCOUNT_AMOUNT, NET_TOTAL_AMOUNT,
  AUTO_ORDER_AMOUNT, EXPENSE_TYPE, CATALOG_SERVICE_ID, WORK_ORDER,
  WORK_CENTER, RUN, PROD_ACCT_NUMBER, ITEM_NAME_ORG_LOC, ITEM_DESC_ORG_LOC,
  PAYMENT_ACCT_ID, CONFIG_HEADER_ID, CHARGE_DIST_ID, REF_NUMBER, DESCRIPTION,
  STATUS, CREATED_DATE, CREATED_BY, CREATED_BY_NAME, LAST_MODIFIED_DATE,
  LAST_MODIFIED_BY, LAST_MOD_BY_NAME, ATTACHMENT_FLAG, NOTE_FLAG, CHECKOUT_ID,
   CHECKOUT_NAME, CHECKOUT_DATE, DOMAIN_ORG_ID, REV_START, REV_END, ID, OCA
FROM
 RW_RFX_ITEM  WHERE (:1 = ID)

call     count       cpu    elapsed       disk      query    current        rows

------- ------  -------- ---------- ---------- ---------- ----------  ----------

Parse        4      0.00       0.00          0          0          0           0

Execute      2      0.00       0.00          0          0          0           0

Fetch        2      0.00       0.00          0          6          0           2

------- ------  -------- ---------- ---------- ---------- ----------  ----------

total        8      0.00       0.00          0          6          0           2

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 35
********************************************************************************

      and we said...
it is parsing it more often then it actually executes it.

It means the programmers didn't know very much about database programming.

The "bug" is in the code, the only fix is to fix the code.

You can lessen the impact by setting session_cached_cursors = 100, but until i2
fixes their code, you have to live with this inefficient use of your resources.
Review &
      Followup

      Rating: 5
How do you
      parse more than execute  May 05, 2003
Reviewer:
      Scott
Tom,

How could you parse something more that you execute it?  Since they are using
weblogic I will go out on a limb and say the app is coded in java.  Now in order
to have the parse > execute would that mean that a statement is being prepared
and just not executed?  Why would you do this?

Would this technique be usefull on some other database (I don't know why it
would but I am trying to look for a reason why the developers would have coded
the app this way)

Thanks

          Followup:

correct -- they are preparing a statement and never executing it.  generic
"wrapper" API's designed to protect the programmer from the horrible complexity
(my tongue is stuck firmly in my cheek) of SQL database programming do this
quite often.

They'll prepare to describe a query (figure out the number/types of columns) and
then close it.

They'll prepare again to actually execute it and then close it.

When in fact they should have -- opened the query ONCE per session and
bind/execute it over and over and over.

It is a useful technique on approximately ZERO databases.  It is a performance
hit on all of them.

      Rating: 5
Same problem
      with PowerBuilder  May 06, 2003
Reviewer:
      Philippe  from Louvain-la-Neuve, Belgium
I have the same kind of problem with a PowerBuilder application : a parse
request is done for each execute.

Sybase claims that the OCI is providing a statement cache and that they are
using it... but this doesn't seem to be true.

Here is a simple powerscript :

//---------------------
ls_ent='BSB'
ls_dom='PR'

for li_i = 1 to 20
select ent_ent into :l_ent from ent where ent_ent=:ls_ent and ent_dom=:ls_dom
using SQLCA;
next
//---------------------

And here is the OCI trace provided by OCISpy(
http://www.geocities.com/ocispy/
:

---------------------------
OCIStmtPrepare stmt:
[select ent_ent from ent where ent_ent =:1 and ent_dom =:2 ]

OCIBindByName name: ':1' type: SQLT_AVC mode: OCI_DEFAULT
OCIBindByName name: ':2' type: SQLT_AVC mode: OCI_DEFAULT
OCIStmtExecute iters: 0, mode: OCI_DESCRIBE_ONLY
OCIStmtExecute iters: 0, mode: OCI_DEFAULT
OCIDefineByPos pos: 1 type: SQLT_STR mode: OCI_DEFAULT
OCIStmtFetch nrows: 50
---------------------------

These steps are repeated 20 times...

Is there anything I can do to avoid these OCIStmtPrepare... and "force" the
statement caching... without having access to the PowerBuiler source code...

          Followup:

they are saying "it is ok to over parse, Oracle has this shared pool thing"

That is why sybase developers do not make good Oracle programmers.

as I said -- this is something that is fixable only in the source code itself.
If you ask us to parse 1000 times, well, we have to do it.

you can lessen the impact by using session cached cursors.

      Rating: 5
Same problem
      with PowerBuilder ...  May 06, 2003
Reviewer:
      Philippe  from Louvain-la-Neuve, Belgium
... and all these parse requests are travelling on the network. In our
application this really a lot SQL*Net bytes from client is more than 250MB for a
client/server PowerBuilder application executing a "batch" (approx 6 hours).

          Followup:

i cannot believe someone would think to write a batch process in powerbuilder.
amazing...

you might read about this language called PLSQL -- oh wait, you'll be "database
dependent".... I guess 6 hours is the price your customers pay for that design
decision -- never mind that if you used TSQL on sqlserver and PLSQL on Oracle it
would run incredibly faster on both -- at minimal development effort on your
part.

      Rating: 5
Bug #1727475
      (Oracle Provider for ADO/OLEDB)?  May 06, 2003

Reviewer:  Mark A. Williams  from Indianapolis, IN
      USA
******
They'll prepare to describe a query (figure out the number/types of columns) and
then close it.

They'll prepare again to actually execute it and then close it.
******

Bug #1727475 (Oracle Provider for ADO/OLEDB)

Oracle Support:

"Apparently what is happening is the provider (ADO/OLEDB) calls OCIStmtExecute
twice. Once with DESCRIBE_ONLY to do an explicit describe and again to execute.
The parse stat count is being incremented twice, once for each execute call. The
parse happens only during the first call which is a hard parse. On the second
call, the count is being incremented even though a hard parse is not made. As a
result, the parse count shows 2 even though it is actually being parsed only
once, the first time."

This bug used to be marked public and is now not...  Not sure if this is
applicable in either of these cases.  But it looks like that parse is really
only happening once, but the count is being incremented twice.  We have two apps
that use the ADO/OLEDB provider and they both exhibit this behavior.  It appears
to be fixed in 9.2 for what it is worth.

Tom, any way you can confirm this?

Thanks,

Mark

          Followup:

yes, it was a "hard" parse the first time, a soft parse the second time -- it
was a bug and it is fixed in 9.2 (so it no longer does that)

      Rating: 5
RE: Bug
      #1727475  May 06, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Thanks Tom!

      Rating: 5
Same problem
      with PowerBuilder ...   May 07, 2003
Reviewer:
      Philippe  from Louvain-la-Neuve, Belgium
I also couldn't believe that a batch application was written as client/server in
PowerBuilder. But this is the price of history. At the very beginning the
application was a simple little client/server application where one operation
was done (by a human) one at a time.

Now we have bigger clients asking for more automation. So the same operations
are now executed in automated batch with, of course, lots of performance
problems. All this was to keep the existing business logic developed in
PowerBuilder.

... and now my boss is asking me to enhance the performance...

Anyway... thanks Tom !

          Followup:

at least you can tell your boss how to enhance performance!  it can only get
better from here.

      Rating: 4
Not
      Clear  May 07, 2003
Reviewer:  Bharath
       from NYC
Still i am not clear why this issue with powerbuilder rather than oracle .If you
set the disablebind DPparm in powerbuilder set to false it's going to send the
actual sql with the bind variable and oracle will see wether this statement
exist in shared pool   if it's there it won't do any hard parse it will just
bind the value and execute it.
if powerbuilder sends the sql with value attached to the variable then it's the
problem with powerbuilder.

          Followup:

they are parsing twice.

both may well be soft parses.

one may be hard, the other soft

regardless a parse is a parse is a parse and the fewer you do, the better.

parse once, execute many I always say.  The best way to speed something up?
Don't do it at all -- you cannot get any better then that

      Rating: 4
RE: Not Clear
        May 08, 2003
Reviewer:  Philippe
       from Louvain-la-Neuve, Belgium
In case of a client/server PowerBuilder application, a parse request is a
network roundtrip. I guess PB sends the text of the SQL request to Oracle and
Oracle replies with the description of the columns of the result.

When the same request is executed more than 75000 times in a PowerBuilder
"batch", all these "useless" parse requests make lots of roundtrips, lots of
bytes, lots of time !

      Rating: 4
Bug #1727475
      (Oracle Provider for ADO/OLEDB)?   November 27, 2003

Reviewer:  yazid  from France
Hi Tom,
I'm confused.
We've a Web application using Oracle OLEDB Provider V8i. At the moment, we
cannot use OLEDB9i. Tkprof generates always 2 parses for one execute.
Above it is said :

"Oracle Support:

"Apparently what is happening is the provider (ADO/OLEDB) calls OCIStmtExecute
twice. Once with DESCRIBE_ONLY to do an explicit describe and again to execute.
The parse stat count is being incremented twice, once for each execute call. The
parse happens only during the first call which is a hard parse. On the second
call, the count is being incremented even though a hard parse is not made. As a
result, the parse count shows 2 even though it is actually being parsed only
once, the first time."

My confusion is about :
"Oracle Support say : the parse count shows 2 even though it is actually being
parsed only once, the first time."
=> Only One parse Cost (The first one. May be hard or soft)

But you say :
"yes, it was a "hard" parse the first time, a soft parse the second time -- it
was a bug and it is fixed in 9.2 (so it no longer does that)"
=> Cost = "Cost of the first parse (soft or hard)" + "Cost of the second Parse
(Soft)"

My questions :
1) Where is the truth ? (Cost of One or 2 parses ?). And how can i check this ?
2) Is it possible that can be an Oracle server Bug (cost only one parse, but the
count is upgrated twice? who is the culpable : OLEDB 8i, or Oracle Server ?

thanks

          Followup:

it did two parses.  both may have been soft, at most one is hard.  but it was 2.

culpability here is meaningless, it is a "fact" -- one that is fixed.

November 27, 2003 Abdullah -- Thanks for the question regarding
      "disaster recovery ", version

      You Asked
I need to know what is the Disaster Recovery Plan do
you have for a Oracle Database?
Knowing that the database is Oracle 7.3 and it is mounted on a
SunOS 5.5.1 box.

best regards,

      and we said...

There are at least 5 options to consider:

o OPS (Oracle Parallel Server)
o Replication
o Standby database
o OS oriented solutions
o Architectural Solutions

We'll look at some of the pro's and con's of each.  I won't be
able to directly answer your question "which one" as there are
many variables to consider.

o OPS (Oracle Parallel Server)

In this scenario we are are using Oracle's ability to have >1
computer, more then 1 instance mount and open a single database.
 In the event a machine fails -- the other machines continue
processing with little to no interuption.  We might for example
use a configuration with 2 nodes.  If one node fails, the other
node will recover any transactions the failed node had
outstanding and rollback any in progress transactions from the
failed node.  The system will never go down (but users that were
processing a transaction on the failed node will recieve an
error and will have to resubmit their transaction, your software
could of course detect the failure and automatically resubmit as
well).

The upside to this is:

- increased scalability horizontally.  If you need more
capacity, you can add another machine to the node.  You do not
need to necessarily buy bigger machines -- you need more
machines.

- proven technology.  OPS has been around for a long time, we
have many people using this -- it is well known and understood
by many people.  There are design considerations you have to be
aware of -- but they are well known (which is half the battle).
For example, partitioning will play a key role in designing an
application with OPS.

- fairly transparent.  As opposed to replication for example --
replication will impact how the application interacts with data.
 OPS will not.  At the application level, the fact you are using
OPS is not as relevant as it would be with replication.  OPS
will impact the DBA but not necessarily the programmer.
Replication will impact both.

Some of the downsides are:

- you have to design to work with OPS.  You have to understand
it.  You need to be aware of the potential performance
bottlenecks.  In 8.1.6 (Oracle8i, release 2) there is a special
mode of OPS in a 2 node cluster where 100% of the performance
issues are a NON-issue.  Both nodes are used to read data --
only one at a time to modify.  The 2'cnd node will take over for
the first node in the event of a failure.

- performing upgrades will most likely cause server downtime.
Typically (there are exceptions) all nodes would be offline to
upgrade.  The time to perform the upgrade will vary but may be
as long as 1 hour.  These are upgrades to the Oracle software --
not to the OS.

- DBA must be fully aware of an OPS environment.  Demands a DBA
who knows OPS, not just an Oracle DBA.

o Replication

Replication can be used to perform failover -- but it wasn't
really designed for failover.  It was designed to make data
available in more then one location.  The way replication works
is that a transaction takes place at 'db1'.  That transaction is
stored in a local queue at 'db1'.  when the user commits, the
database will (in the background, not as part of the user
transaction) forward the transaction stored in the queue to
other databases interested in that data.  This transaction is
then performed at these other sites.  Replication is asyncronous
in nature - you have to assume that at any point in time, the
various databases can and will contain different data as they
are all at different points in time.  This fact can make
application development somewhat harder.  Lets say the site you
are building is a site to sell stuff.  You have an inventory.
You have X widgets in inventory.  At 2 different sites, people
simultaneously order X widgets.  As far as your software is
concerned, both orders can be filled.  It is only AFTER the
transactions at both sites have been performed that you discover
in the background that you now have -X widgets in inventory (you
had an order for 2X widgets).  This is known as an update
conflict and it is up to you to 'fix it'.  Oracle discovers it
for you but will then invoke user defined code to fix the
problem.  This process must be part of your application design.

You can decide to avoid conflicts by partitioning data (eg: west
coast data and east coast data, you need to goto the west coast
server to affect west coast data and vice versa).  Many people
do this however if you fail over from the west coast to the east
coast -- you must remember that there probably are transactions
queued in the failed west coast machine that will be propagated
to the east coast when you fix the problem.  You still have the
potential for update conflicts when using replication for
failover -- you still need to design for how you will handle
these issues.

if failover and availability are the key issues -- OPS wins out
in my opinion.  if redundancy is the main issue -- replication
is the answer (as OPS is not a redudant solution -- there is
only one database).  They are equal in in the backup area (same
issues, same considerations).

Both replication and OPS have certain performance implications.
Both will have to be designed for in many respects.  With OPS
and a 2 node configuration -- we can eliminate the performance
considerations.  With OPS and a single write node and many (more
then 2) read nodes, we can eliminate most of the performance
considerations.  With replication, a client transaction will
generate about 2x the IO for any given WRITE transaction and
will not affect read transactions at all.

In short -- both will have issues.

Some of the upsides of replication are:

- replication works over long distances and WANS.

- replication is tolerant of short network outages between
database servers (short is relative here and is a function of
the amount of changes performed in a given period of time.  you
do not want the servers to get too far out of sync.  it could
affect the application as well if the data diverges too much).

- replication imposes a 'fixed' overhead (2X the data modified
for inserts/deletes, 3X for updates (before and after images are
stored in the queue).

- replication provides you with a copy of the data elsewhere.
with OPS -- there is one database.  An earthquake can take out
the only copy.

Some of the downsides are:

- conflict resolution and its impact must be carefully studied
and designed for.
- you need a DBA familar with this technlogy.
- your developers need to be familar with the impact of this
technology.

(NOTE: you can use OPS and replication together.  OPS for rapid
failover, replication for a hot spare.  something else to
consider).

o Standby Database

the standby database is a nice solution for failover.  In this
mode, the logs generated by Oracle are forwarded to another
computer in the network and applied to a database instance that
is in constant recovery mode.  This standby database can be
opened for reporting and such (read only) but generally is
unavailable while in recovery mode.  This standby database lags
behind the real database by some small amount (governed by the
size of your redo files) but can be brought on line rapidly
after a failure and can be totally caught up to the master
database if you can get to the file system on the down machine
(eg: plug the disks that contain the redo into another machine).
 You should be aware the replication has the same sort of lag
time as a standby database would.

Some of the upsides of standby databases are:

- they are extremely easy to setup.
- they have no performance impact on the master machine.
- they impose no design considerations on the application.

Some of the downsides are:

- the DBA must be aware of the standby database.  If you make
structural changes to the master, they must be coordinate to the
standby.  This is minimal.

- once you failover to the standby -- there is *no* failback.
The standby will become the master.  You will build a new
standby to cover this new master.

o OS oriented solutions.

Here I am talking about dual ported raid devices or other types
of operating system failover solutions.  the concept here is
that 2 machines have the same set of disks mounted.  In the
event one machine fails, the second machine takes over the IP
address and starts a recovery script of some sort.  This script
would startup the Oracle instance, let it recover and you'd be
good to go.

Talk to your OS vendor about these types of solutions.

o Architectural Solutions.

this is what i call the BIG server versus many SMALL server
solution.  BIG is a giant single server running on an E-10000.
When they have a failure -- everything is down (their failures
have to date mostly been operator error.  failure to install
recommended patches, failure to follow procedures and such).  A
database failure at BIG is visible to everyone immediately.  It
is all or nothing.

SMALL on the other hand has lots of little machines with lots of
little databases.  You want stocks -- you goto quote.small.com.
You want some business news -- you goto that server.  Tech news
- else where.  You want a random site, thats random.small.com.
And so on.  SMALL is a whole bunch of databases working
together.  If part of SMALL is down -- hardly anyone notices
since each server is just doing a little part of the whole.
SMALL never goes down but at any point in time, part of it might
be.  This requires lots more design and a really good /
redundant directory service (to find stuff) but is doable.

Whether this can work for you depends on the nature of your
site.

Sorry I cannot answer your question with a "use this
technology".  It is just that there are too many variables and
it depends on your needs.  I've worked on projects where OPS was
right, replication was right, OPS+replication was right, standby
was right, yahoo architecture was right and so on.  This email
will probably raise more questions then answer....

Review &
      Followup

      Rating: 5
Its
      Awesome  July 12, 2001
Reviewer:
      A reader

      Rating:
      4
  July 15, 2001
Reviewer:
      Pichaimani  from San Jose, CA USA
Thanks for explaning and rolling out all the options.

      Rating:
      5
  May 02, 2002
Reviewer:
      A reader
Hello Tom,

I was reading up on the dataguard (standby) feature of 9i. Using dataguard the
standby database can be configured to be at no lag (there are other options too)
with the primary by appliying the ONLINE REDO to the standby database. But it
has very big downsides.

1. The synchronous transmission and affirmation of redo-apply will surely cast a
big performance impact on the primary database.

2. When due to any (network or other) problems, if the redo transmission is
suspended, Oracle will shutdown the Primary database! (just like when there is
no space for redo writes). That is outrageous!! What is high availability then?

I wonder if the standby database is that good an option for high availability.
If you go for 100% sync with the primary database you have to take the above
risks. On the other hand, if you adapt the 8i standby architecture, you will
always lag behind. Oracle's RAC, Replication or o/s level mirroring all looks
much better options to me. Could you comment on that?

Also, does Oracle support anything like below:

The Primary database files are on a tripple mirrored RAID array. The array can
be mounted on to another box easily. That box has a similary configured instance
(with same name) running -but no database is mounted on it. When the primary
becomes unavailable, mount the the array to the second box and mount the
database. Database should do instance recovery upon opening. Will it not have
100% transactions available?

Is this possible? Or am I day dreaming? Waiting for your comments.

Thanks

          Followup:

1) you get what you pay for this much is true -- yes.  If you CANNOT stand to
lose a single committed transaction ever (say you are a BANK), then you will

   a) design for this
   b) size for this
   c) pay for this (in network bandwidth and low latency)

Also bear in mind that dataguard supports 4 levels:

GUARANTEED:
   o lgwr does a syncronous write over the network to the standby redo
   o if the standby fails (network/whatever) PRIMARY STOPS

INSTANT:
   o lgwr does a syncronous write over the network to the standby redo
   o if the standby fails (network/whatever) PRIMARY CONTINUES and data
     guard falls back to the "send the archive redo logs later" mode

RAPID
   o lgwr does an asyncronous write over the network to the standby redo
   o if the standby fails (network/whatever) PRIMARY CONTINUES and data
     guard falls back to the "send the archive redo logs later" mode

DELAYED
   o arch does the work, transmitting archive redo logs as standby in 8i
     and before did
   o if the standby fails (network/whatever) PRIMARY CONTINUES and data
     guard falls back to the "send the archive redo logs later" mode

So -- you tell us -- what level do you want?  what level do you need?  Me, I see
GUARANTEED for people who cannot afford to lose a BYTE of data EVER (banks,
financial institutions pop into my head -- my HR database, no, my 401k -- YES)

I don't see instant as being commonly used.

RAPID for most all other implementations

DELAYED for people who can afford to lose relatively large numbers of
transactions when the building housing the data center blows up, burns down,
whatever.

2) Outrageous?  Not if you are a financial institution whose design spec states
"a transaction is not to be considered committed until the transaction is safely
stored in at least two geographically disperse locations" (and most of them do).

Guaranteed mode is like a really really really fast two phase commit.  In the
past, people used a syncronous replication technology (both sites MUST be
available).  Guaranteed mode is a no data loss mode, it is NOT necessary for HA
-- it is for continuity of operations.  Once the primary or standby is taken out
(remember 9/11), the other shutsdowns UNTIL you start up a standby for it.  This
is used by people who cannot afford to ever lose a transaction (what do you mean
that million dollars you transferred disappeared??)

You need to look at RAPID protection I think.  Actually, you need to look at all
four, understand when and where you would use them and then apply the
appropriate technology in the right cases!

Here is how one would design an HA system that can afford to lose a very small
amount of transactional data in the event of a truly catastrophic failure:

a) RAC in a room, because you NEVER want to failover unless the room goes away.
b) data guard in rapid mode, because you don't want to lag behind too much and
you want to impact the system as little as possible.  You will work to fix
standby issues ASAP when they occur.

If you cannot afford to lose a single transaction -- change rapid to guaranteed
and pay for the redundant networks to ensure they are not the issue with the
standby.  You will design for and pay for this extra level of HA'ness.

Yes, you can do that last paragraph and in fact there are many OS features that
facilitate it.  It is much like "RAC in a ROOM" and doesn't give you any data
guard like features.

      Rating:
      4
  May 03, 2002
Reviewer:
      A reader
You can also use storage replication like EMC's SRDF.  It replicates on block
level.  You can set how out of phase the two sites can be or not at all.  But
you will pay big time for it.  But its not cheap.

http://www.emc.com/products/networking/srdf.jsp

          Followup:

And IT IS NOT the best way to do this either.

Consider how a block is changed.  We will:

o generate a change to the block
o generate a change to the undo block
o generate two copies of redo at least
o and probably two archives

you have 6 IO's eventually in all probability.  These will all be transmitted
across the network.

With data guard, you'll send 1 redo log change, period.  That is a significantly
reduced amount of network traffic -- significant.

Addditionally, with SRDF, that other copy is useless.  You cannot really use it
for anything EXCEPT as failure.  With data guard you can use it to offload
backups or as a reporting instance (even in guaranteed mode, you can use the
standby as a reporting instance)

      Rating:
      3
  May 03, 2002
Reviewer:
      A reader
Exactly. I agree one has to pay for it. But I am not convinced standby database
(dataguard) has any advantage over an SDRF kind of replication. That is what I
wanted to findout. If we can mount the replicated filesystem to another instance
and open the database with 100% transactions, that looks a better option to me
than data guard with 2 phase commit, unless I am missing some other feature of
standby database.

Thanks anyway Tom for your time.

          Followup:

Data guard is INSTEAD OF 2PC.  It is not 2PC.

See comment right above as to why data guard would be better then SRDF (less
costly, more functionality)

SRDF has its place, just not in mirroring datafiles.

      Rating:
      5
  May 03, 2002
Reviewer:
      A reader
There you go! Thanks Tom for pointing out the advantages. That's exactly what I
wanted to know.

You may be incurring more network traffic with SDRF. But with standby you also
incur time for applyig the re-do in the standby instance, then confirm that back
to the primary. And your transactions are waiting on the primary all this time.
Also if the redo transmission is interrupted you have a business disruption!

You are correct. Oracle has given a number of options and you pick the most
suitable for you. Thank you verymuch for helping us to make that choice.

          Followup:

The standby DOES NOT APPLY THE REDO.

A data guard standby recieves the redo, writes it to disk and returns.  It does
NOT apply it.

The redo on the standby is NOT APPLIED until after the redo log file fills up.

You only have a business disruption IF YOU CANNOT MIRROR THE REDO (not apply)
AND IF YOU CONFIGURE IT TO STOP (as I would hope SRDF would give you).

So, in short, LGWR transmits the redo, redo is saved on standby and we return
(guaranteed and instant modes).  You DO NOT APPLY IT at remote.

LGWR transmits the redo (rapid mode) and you do not wait at all.  Standby
recieves it in the background and writes it to standby redo.  You do NOT APPLY
IT at remote.

The redo is not applied until a full redo has been sent over.

      Rating: 5
Good Overview
      of the options  August 02, 2002
Reviewer:
      A reader

      Rating:
      5
Reader  November 03, 2002
Reviewer:
      A reader
"
(even in guaranteed mode, you can use the
standby as a reporting instance)
"
Will the recovery of standby take place in the background even while the
standby is being used for reporting purposes

"
The redo on the standby is NOT APPLIED until after the redo log file fills up.
"

What happens if the someone did "alter system switch logfile" at the PRIMARY
database

          Followup:

same thing as would happen as when the logfiles switch by themselves.

In guaranteed (instant and rapid) mode -- the redo is transmitted by lgwr in
real time.  the redo was already at the standby site when you switched the logs.
 The redo is accumulated there.  If you are using a physical standy the database
is either

o in recovery mode, applying the redo
o opened for read only access - redo is just getting queued up, not applied but
safely tucked away on the standby.

In a logical standby (new with 9iR2) the redo can be applied even when the
database is being used for reporting (or it can be applied in a DELAYED mode --
so that the standby runs an hour or two or whatever behind production -- to
protect from that "accidently" dropped table)

      Rating: 5
SEVERAL SMALL
      vs ONE BIG  February 10, 2003
Reviewer:  Abu
      Ahmadh  from Singapore
One of our table will grow up to 2TB in size.  We wanted to split it into
several pieces of databases as you mentioned in Architectural solution for
better manageability and performance.  Of course with partition.  But, the
application vendor does not certify to run on multiple small databases.

Now, our solution turned to be RAC.  That means, we will have one single BIG
database with multiple instances on different nodes.  My questions are:

(a)    What will be the impact on performance with our proposed approach?
(b)    If batch jobs are running against the big table, how does it affect
on-line performance?
(c)    How do we size for sorting?  Will RAC utilize temporary tablespaces of
all running instances for a single process?
(d)    Does the vendor need to certify if we deploy RAC when Oracle claims that
“Requirement to code change is NIL”?
(e)    Can we manually control RAC’s distributed processing to say, batch jobs
will always run against a single instance?

There could be some stupid questions, but I need your expert advice to all my
queries.

Thanks Tom

          Followup:

a) it'll be positive in general.  At the time of writing this particular article
(jan 2000), RAC did not exist.  It is not OPS, it is quite different.

b) like any other thing would.  increased contention for IO resources -- but
with RAC you can split out the workload by instance so the cpu's don't contend.
But in general -- just like any other system with mixed workload

c) pretty specific here -- given you are at the information gathering level.
suggest you give the RAC concepts guide a read through

d) the VENDOR must be willing to support your configuration - yes, 100%,
absolutely.  If they do not, what happens when you call their support line?

e) yes you can.

      Rating: 3
VLDB impact to
      startup & shutdown  February 12, 2003

Reviewer:  A reader
Will there be any impact to startup/shutdown DB due to multiple instances?  Let
us say there are 10 nodes in the cluster.  What will be the impact in terms of
time?

          Followup:

each instance is independent of the others, there really isn't an impact.

      Rating: 5
First-Class
      article about choosing the best Disaster-Recovery
      Plan  April 28, 2003
Reviewer:  Sami
       from CA,USA

      Rating:
      5
  June 04, 2003
Reviewer:  John
       from Canada
Hi Tom,

We have a database 9.0.1 enterprise version on a windows 2000 server. We may
want to purchase another windows 2000 server as standby server. Probablly we
will implement data guard for them. However I am not sure when the master goes
down, how can we let the client know the standby server takes over. Is it
possible to let standby server use the master's IP address through failover
procedure? Or you may have other better options. Our database client is a
application server using java thin jdbc.

Thanks in advance.

          Followup:

normally this is done via TNS.

you can specify a list of hosts to try in order when connecting in tnsnames.ora
files.

you can use ldap or OracleNames as a nameserver to change the address centrally.

you can use IP failover as well -- but it wouldn't generally be necessary.

If you are big enough to run a failover site, you are probably big enough to use
a centralized nameserver instead of little tnsnames.ora files all over the
place.  You would control is centrally and a simple update would repoint the tns
entry.

      Rating: 5
lgwr and arch
      behavior  September 02, 2003
Reviewer:  Martin
      Guillen  from Buenos Aires, Argentina.
Hi Tom:
Let me know if I understand this:
If I'm using lgwr to send redo to standby it is NOT applied until a log switch
occurs on primary, right?
I was thinking that lgwr apply changes immediately but it seems to be wrong
(seeing your response).
So what's the advantage of using lgwr instead of arch?
Also I want to to know what is the purpose of standby redo logs.
Thank you,
Martin.

          Followup:

the standby redo logs get filled AND THEN applied at the standby.

The advantage of using lgwr?  ok, you are on production.  production has 50meg
log files.  You have just filled up 49.9meg of log.  production is blown up.

that 49.9 meg of log is probably gone, forever. will never become an archive
file, will never get over to standby.

so, you use standby -- same situation but lgwr writes to the online redo log and
sends to the standby.  production is blown up.  no worries, the 49.9 meg of
committed transaction data is on the standby -- apply it, open the database and
you are back in business.

      Rating:
      5
  September 16, 2003
Reviewer:
      A reader
Hi Tom,

I was reading the below paragraph in one of the followups and you answered that
it is possible. Can I get some additional info on this please?

<QUOTE>
The Primary database files are on a tripple mirrored RAID array. The array can
be mounted on to another box easily. That box has a similary configured instance
(with same name) running -but no database is mounted on it. When the primary
becomes unavailable, mount the the array to the second box and mount the
database. Database should do instance recovery upon opening. Will it not have
100% transactions available?
<END QUOTE>

Is it possible to do a recovery like that if the tablespaces were not in backup
mode? Isn't it just like a hot backup where you can have split blocks in the
backup copy and you need full block copies in the logfile to fix them?

          Followup:

the disks would just appear to have a "crashed oracle database" on them.  it is
no different then if you DIDN'T mount them to another machine, but just "started
up oracle" again.

all they did was move the disks from computer 1 to computer 2.  it is not a
media recovery situation, just an instance recovery.

      Rating:
      5
  September 16, 2003
Reviewer:
      A reader
Hi Tom,

Please see
http://asktom.oracle.com/pls/ask/f?p=4950:8:4496575172726829804
There you say that splitting the mirrors requires putting the
tablespaces in backup mode.

Here is my thinking:

When you suspend the i/o, the database queues up info in the buffer and does not
write to the files. Now you split the mirrors. The datafiles are fuzzy and for
recovering you need full images of the blocks in your re-do stream. Can you
please explain how this is different from the above referenced thread.

thanks.

          Followup:

it is different because that thing (split mirrors + backup mode) was to BACKUP
the database.

this, this is just failing the hardware over to another computer.  it is not
about backing up at all.  it is about failing over.

      Rating:
      5
  September 16, 2003
Reviewer:
      A reader
Sorry, I this is the link I referred to:

http://asktom.oracle.com/pls/ask/f?p=4950:8:4496575172726829804::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:373618158622,
(Having a hardtime with these links, sorry I put it at the wrong place
twice....)

      Rating: 5
Test Disaster
      recovery using RMAN ....  September 18, 2003

Reviewer:  READER
when I backed up my database using rman, it did backup readonly datafiles along
with others. However, when I practiced disaster recovery after removing all of
the files, rman did not restore read only datafile. Why? THANKS.

RMAN> backup database; <------- IT DID BACKUP READONLY DATAFILE

Starting backup at 18-SEP-03
allocated channel: ORA_DISK_1
channel ORA_DISK_1: sid=12 devtype=DISK
channel ORA_DISK_1: starting full datafile backupset
channel ORA_DISK_1: specifying datafile(s) in backupset
input datafile fno=00001 name=/home2/user18/ORADATA/u01/system01.dbf
input datafile fno=00005 name=/home2/user18/ORADATA/u02/sample01.dbf
input datafile fno=00002 name=/home2/user18/ORADATA/u02/undotbs01.dbf
input datafile fno=00003 name=/home2/user18/ORADATA/u03/users01.dbf
input datafile fno=00004 name=/home2/user18/ORADATA/u03/indx01.dbf
input datafile fno=00006 name=/home2/user18/ORADATA/u01/querydata01.dbf <---
READONLY
channel ORA_DISK_1: starting piece 1 at 18-SEP-03
channel ORA_DISK_1: finished piece 1 at 18-SEP-03
piece handle=/home2/user18/oracle/dbs/01f1kuho_1_1 comment=NONE
channel ORA_DISK_1: backup set complete, elapsed time: 00:00:26
Finished backup at 18-SEP-03

Starting Control File and SPFILE Autobackup at 18-SEP-03
piece handle=/home2/user18/oracle/dbs/c-411229649-20030918-00 comment=NONE
Finished Control File and SPFILE Autobackup at 18-SEP-03

PLEASE SEE BELOW THE STEPS THAT I FOLLOWED TO PERFORM DISASTER RECOVERY.

RMAN> connect target;

connected to target database (not started)

RMAN> set dbid 411229649

executing command: SET DBID

RMAN> STARTUP FORCE NOMOUNT;

startup failed: ORA-01078: failure in processing system parameters
LRM-00109: could not open parameter file '/home2/user18/oracle/dbs/initU18.ora'

trying to start the Oracle instance without parameter files ...
Oracle instance started

Total System Global Area     161187360 bytes

Fixed Size                      730656 bytes
Variable Size                109051904 bytes
Database Buffers              50331648 bytes
Redo Buffers                   1073152 bytes

RMAN> RUN
2> {
3> ALLOCATE CHANNEL C1 DEVICE TYPE DISK;
4> RESTORE SPFILE FROM AUTOBACKUP;
5> }

using target database controlfile instead of recovery catalog
allocated channel: C1
channel C1: sid=9 devtype=DISK

Starting restore at 18-SEP-03

channel C1: looking for autobackup on day: 20030918
channel C1: autobackup found: c-411229649-20030918-01
channel C1: SPFILE restore from autobackup complete
Finished restore at 18-SEP-03
released channel: C1

RMAN> STARTUP FORCE NOMOUNT;

Oracle instance started

Total System Global Area      26968864 bytes

Fixed Size                      729888 bytes
Variable Size                 20971520 bytes
Database Buffers               4194304 bytes
Redo Buffers                   1073152 bytes

RMAN>

RMAN> RUN
2> {
3> ALLOCATE CHANNEL C2 DEVICE TYPE DISK;
4> RESTORE CONTROLFILE FROM AUTOBACKUP;
5> ALTER DATABASE MOUNT;
6> RESTORE DATABASE;
7> RECOVER DATABASE;
8> }

allocated channel: C2
channel C2: sid=11 devtype=DISK

Starting restore at 18-SEP-03

channel C2: looking for autobackup on day: 20030918
channel C2: autobackup found: c-411229649-20030918-01
channel C2: controlfile restore from autobackup complete
replicating controlfile
input filename=/home2/user18/ORADATA/u01/ctrl01.ctl
Finished restore at 18-SEP-03

database mounted

Starting restore at 18-SEP-03

datafile 6 not processed because file is read-only
channel C2: starting datafile backupset restore
channel C2: specifying datafile(s) to restore from backup set
restoring datafile 00001 to /home2/user18/ORADATA/u01/system01.dbf
restoring datafile 00002 to /home2/user18/ORADATA/u02/undotbs01.dbf
restoring datafile 00003 to /home2/user18/ORADATA/u03/users01.dbf
restoring datafile 00004 to /home2/user18/ORADATA/u03/indx01.dbf
restoring datafile 00005 to /home2/user18/ORADATA/u02/sample01.dbf <--- NOTE, IT
DID NOT RESTORE READONLY DATAFILE
channel C2: restored backup piece 1
piece handle=/home2/user18/oracle/dbs/01f1kuho_1_1 tag=TAG20030918T120824 paramL
channel C2: restore complete
Finished restore at 18-SEP-03

Starting recover at 18-SEP-03
datafile 6 not processed because file is read-only

starting media recovery

archive log thread 1 sequence 3 is already on disk as file /home2/user18/ORADATc
archive log filename=/home2/user18/ORADATA/ARCHIVE1/arch_3.arc thread=1 sequenc3
unable to find archive log
archive log thread=1 sequence=4
Oracle Error:
ORA-01547: warning: RECOVER succeeded but OPEN RESETLOGS would get error below
ORA-01157: cannot identify/lock data file 6 - see DBWR trace file
ORA-01110: data file 6: '/home2/user18/ORADATA/u01/querydata01.dbf'

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96565/rcmsynta39.htm#92274

      Rating:
      5
Failover  November 27, 2003
Reviewer:
      Douglas  from NYC
Tom, the docs are rather reticent on this... Maybe you can give me a practical
advice. It's quite simple, I think. Though, I can't find much info on it.

I have a typical data guard configuration with two machines/instances.

After carrying out the procedures to failover from machine A to B, how should I
"re-point" the clients to machine B ?

Either:

1) Use the FAILOVER clause in the address descriptor in tnsnames.ora

or

2) Swap A's IP address (and/or hostname) with B's. That is -- fully change the
machine roles.

?

          Followup:

what advice are you looking for?

the docs are saying you can either:

a) configure the tnsnames entry so that it tries production and if it cannot
connect to production, it connects to standby (this will take longer)

b) change the IP addresses so the ip address of the failed prod machine becomes
the standby IP address and vice versa

Other options are:

c) use Names and update the Names server (LDAP in current releases of Oracle,
use LDAP to resolve service names)

d) run a listener on a 3rd machine and have the servers register with this
listener as a "service".  clients connect to listener, listener forwards to a
database that serves that service.

November 27, 2003 -- Thanks for the question regarding "compatible
      init.ora parameter", version 7.3.4

      You Asked
need extensive documentation on compatible parameter
any pointers ?

Thanks

      and we said...
This is support note:

Article-ID:         <Note:30699.1>
Alias:              PARAMETER:COMPATIBLE
Circulation:        PUBLISHED (EXTERNAL)
Folder:             server.Rdbms.Reference.Parameters
Topic:              Standard INIT.ORA Parameters
Title:              PARAMETER: INIT.ORA: COMPATIBLE
Document-Type:      REFERENCE
Impact:             LOW
Skill-Level:        ACCOMPLISHED

Parameter: COMPATIBLE
~~~~~~~~~~~~~~~~~~~~~

 Parameter type:             string
 Parameter class:            static
 Default value:              Oracle8i is 8.1.0
                 Oracle 8 is 8.0.0
                 Oracle 7 is 7.0.x
 Range of values:            default release to current release
 Multiple instances:         must have the same value
 Related:             <Parameter:COMPATIBLE_NO_RECOVERY>

Description:
~~~~~~~~~~~~
This parameter allows you to use a new release, while at the
same time  guaranteeing backward compatibility with an earlier
release (in case  it becomes necessary to revert to the earlier
release). This parameter specifies the release with which
Oracle7 Server must maintain compatibility. Some features of the
current release may be restricted. For example, if you are
running release 7.2.2.0 with compatibility set to 7.1.0.0 in
order to guarantee compatibility, you will not be able to use
7.2 features.

When using the standby database and feature, this parameter must
have the same value on the primary and standby databases, and
the value must be 7.3.0.0.0 or higher.

This parameter allows you to immediately take advantage of the
maintenance improvements of a new release in your production
systems without testing the new functionality in your
environment.

The default value is the earliest release with which
compatibility can be guaranteed.  Ie: It is not possible to set
COMPATIBLE to 7.3 on an Oracle8 database.

Extra Notes:
    Features enabled at different levels include:
          7.1.0     read-only tablespaces, multiple same-type
                    triggers on a  table
          7.1.5     direct reads
          7.2.0     improved hash function for new hash
                    clusters, user defined
                    hash expression on hash clusters, resizable
                    datafiles
          7.3.0     standby database, unlimited extents,
                    temporary tablespace (sort segments)
          7.3.x     bitmap indexes
      8.0.0        Lots of things
      8.1.0        UROWID and ROWIDs for IOT tables

Review &
      Followup

      Rating: 5
setting
      compatble parameter  November 27, 2003
Reviewer:
      A reader
Hi

We are using 9.2.0.1 but compatible is set to 9.0.0, do we have to set it to
9.2.0? Any gains?

          Followup:

compatible tells us what features we are allowed to use that affect how stuff is
stored on disk.

you do not "have" to set it.

you might find that some new features are not available to you (you'll get an
error message to that effect)

note that if you set it up, you in general cannot set it back (it modifies how
we store, persist data on disk -- this is not reversible).... SO, test it first
on a test system.

November 27, 2003 Sridevi -- Thanks for the question regarding
      "dbms_job is writing files to archiver frequently", version Oracle 7.x

      You Asked
In Oracle 7.x, I have scheduled a job via dbms_job in oracle.
In the init.ora file I have set the parameters :
job_queue_processes = 5
job_queue_interval = 300

Then I have submitted the job using dbms_job.submit.
I have scheduled the job to run every day at 1:30 pm.
The syntax being:
dbms_job.submit(p_job_number, 'myproc', sysdate, trunc(sysdate)+13.5/24, false);

The job runs as scheduled at 1:30 pm. But from 1:30 pm onwards it startes
writing one file to the archiver  log every minute or so. However it does not
run my procedure every minute.

I have tried this even with a simple null procedure.
In other words, I have tried this with a procedure like this:
Procedure myproc is
Begin
null;
End;

It still writes to the archiver, after it runs once on the scheduled time. And
continues to write to the archiver every minute or so. I have even tried this on
a just started database with no writes/reads
being done to it from any logins. So the culprit is the job.

This writing to the archiver stops if I kill the job using dbms_job.remove or I
shutdown the database.

Why does this happen? How can i prevent these frequent entries into the archiver
log.

      and we said...
what do you mean by "writing one file to the archiver log every minute of so"???

You do realize that you have somewhat of an infinite loop going there.

scott@ORA815.US.ORACLE.COM> l
  1* alter session set nls_date_format = 'dd-mon-yyyy hh24:mi:ss'
scott@ORA815.US.ORACLE.COM> select trunc(sysdate)+13.5/24 from dual;

TRUNC(SYSDATE)+13.5/
--------------------
26-nov-2001 13:30:00

you do realize that INTERVAL should be a string -- that should be

dbms_job.submit
(p_job_number, 'myproc', sysdate, 'trunc(sysdate)+13.5/24', false);
                                  ^^^                   ^^^

And that it probably should actually be:

dbms_job.submit
(p_job_number, 'myproc', sysdate, 'trunc(sysdate)+1+13.5/24', false);
                                  ^^^            ^^^       ^^^

if you want it to run every DAY at 1:30pm.  You constantly reschedule it for
1:30pm TODAY.  Everytime we run the job, it FAILS.  Consider (if you schedule
this job AFTER 1:30 in the afternoon)

scott@ORA815.US.ORACLE.COM> exec dbms_job.submit( :n, 'null;', sysdate,
'trunc(sysdate)+13.5/24', false );
BEGIN dbms_job.submit( :n, 'null;', sysdate, 'trunc(sysdate)+13.5/24', false );
END;

*
ERROR at line 1:
ORA-23420: interval must evaluate to a time in the future
ORA-06512: at "SYS.DBMS_JOB", line 50
ORA-06512: at "SYS.DBMS_JOB", line 138
ORA-06512: at line 1

You need to add one to the date.

What I believe is happening (and your alert log will confirm) is that we are
trying to run the job -- but we cannot since the NEXT evaluates to a time in the
PAST (until midnight of course -- then it works, all will be quiet until 1:30pm
at which point the whole cycle begins again).

Review &
      Followup

      Rating:
      3
ORA-23420  July 01, 2002
Reviewer:
      A reader
Hi Tom

I am getting ORA-23420 when trying to submit a job with interval of 30 minutes.

begin
 select instance_number into :instno from v$instance;
 dbms_job.submit(:jobno, 'statspack.snap;', trunc(sysdate+1/24,'HH'),
'trunc(SYSDATE,''HH'')+1/48', TRUE, :instno);
 commit;
 end;
/

how can we avoid this error and submit a job with 30 minutes interval?

          Followup:

> oerr ora 23420
23420, 00000, "interval must evaluate to a time in the future"
// *Cause: The parameter "interval" evaluates to a time earlier than SYSDATE.
// *Action: Choose an expression that evaluates to a time later than SYSDATE.
//

There is an error in your logic.

Suppose it is 1:45pm right now.  Well, the trunc of 1:45pm to the hour would be
-- 1:00pm, you add 30 minutes to that and get 1:30pm -- today -- a date in the
past!

Assuming that if it is between :00 and :29 after the hour that you want to run
again at :30 and if it is between :30 and :59 past the hour, you want to run on
the next hour, you can use:

trunc(sysdate,'hh')+(1/24/60*30)*(1+trunc(to_char(sysdate,'mi')/30))

That'll add either 30 or 60 minutes to the trunc of sysdate to the hour
depending on whether the minutes are between 0 and 29 or 30 and 59...

      Rating: 5
runs job every
      15 minutes  November 27, 2003
Reviewer:
      A reader
Hi

From your last solution the jobs runs every 30 minutes, 00:00, 00:30 for example

Can we run jobs every 15 minutes? Such as

12:34, 12:49, 13:04

:-?

          Followup:

sure, interval of

sysdate+15*(1/24/60)

does that with a "little slide" possible -- meaning the job will be scheduled to
run 15 minutes after the time it was last started to run.

If you don't want it to slide -- you want 00, 15, 30, 45 -- just do the math
like I did above for 00 and 30

November 28, 2003 Gary -- Thanks for the question regarding
      "Formatted weekdays in use", version 8.1.7

      You Asked
I have the following:

create table CHOSEN_DAYS
(
  CODE       VARCHAR2(10) not null,
  DAY_NUMBER NUMBER(1) not null
);

insert into CHOSEN_DAYS (CODE, DAY_NUMBER)
values ('first', 1);
insert into CHOSEN_DAYS (CODE, DAY_NUMBER)
values ('first', 2);
insert into CHOSEN_DAYS (CODE, DAY_NUMBER)
values ('first', 3);
insert into CHOSEN_DAYS (CODE, DAY_NUMBER)
values ('first', 5);
insert into CHOSEN_DAYS (CODE, DAY_NUMBER)
values ('second', 3);
insert into CHOSEN_DAYS (CODE, DAY_NUMBER)
values ('second', 4);
insert into CHOSEN_DAYS (CODE, DAY_NUMBER)
values ('second', 6);
insert into CHOSEN_DAYS (CODE, DAY_NUMBER)
values ('third', 1);
insert into CHOSEN_DAYS (CODE, DAY_NUMBER)
values ('third', 2);
insert into CHOSEN_DAYS (CODE, DAY_NUMBER)
values ('third', 3);
insert into CHOSEN_DAYS (CODE, DAY_NUMBER)
values ('third', 4);
insert into CHOSEN_DAYS (CODE, DAY_NUMBER)
values ('third', 5);
insert into CHOSEN_DAYS (CODE, DAY_NUMBER)
values ('third', 6);
insert into CHOSEN_DAYS (CODE, DAY_NUMBER)
values ('third', 7);
commit;

The output I am looking for is:

CODE    RETURN_STRING
first   Monday-Wednesday, Friday    <--- Corrected
second  Wednesday, Thursday, Saturday
third   Monday-Sunday

The rule being that consecutive days (3 or more) would have the first and last
days seperated by a dash and non-consecutive days seperated by a comma.  A
combination of the two for a particular code is possible.

Other than looping through manually I can't see how I can do this efficiently.
It would seem that I might be able to use analytics however I'm not able to wrap
my brain around meeting all of the conditions.  I would like to have this as a
function that would return the string as this value would be just one field of a
result set.

Any help would be appreciated.  Thanks.

(Sorry, the output of "first" should have had Friday instead of Thursday based
on the data.)

The way the data is decoded for the "first" code would be:
- contains days 1,2,3,5 (day number equivalent to day of the week, 1=Monday,
2=Tuesday, 3=Wednesday, 5=Friday
- 1,2,3 are consecutive so only the first and last day should be included as a
range (Monday-Wednesday)
- 5 is not consecutive with the previous record and thus needs to be displayed
with a comma seperating it.

      and we said...
here is one approach (fun with SQL!)

ops$tkyte@ORA920> select code,
  2         replace(
  3         replace(
  4         replace(
  5         replace(
  6            trim(leading ',' from str),
  7            '-,','-'),
  8            '----', '-'),
  9            '---','-'),
 10            '--', '-') str
 11    from (
 12  select code, mon, tue, wed, thu, fri, sat, sun,
 13    decode( mon, 1, 'Mon' ) ||
 14    decode( tue, 1, case when mon=1 and wed=1 then '-' else ',Tue' end ) ||
 15    decode( wed, 1, case when tue=1 and thu=1 then '-' else ',Wed' end ) ||
 16    decode( thu, 1, case when wed=1 and fri=1 then '-' else ',Thu' end ) ||
 17    decode( fri, 1, case when thu=1 and sat=1 then '-' else ',Fri' end ) ||
 18    decode( sat, 1, case when fri=1 and sun=1 then '-' else ',Sat' end ) ||
 19    decode( sun, 1, 'Sun' ) str
 20    from (
 21  Select code,
 22         max(decode(day_number,1,1,0)) mon,
 23         max(decode(day_number,2,1,0)) tue,
 24         max(decode(day_number,3,1,0)) wed,
 25         max(decode(day_number,4,1,0)) thu,
 26         max(decode(day_number,5,1,0)) fri,
 27         max(decode(day_number,6,1,0)) sat,
 28         max(decode(day_number,7,1,0)) sun
 29    from chosen_days
 30   group by code
 31         )
 32         )
 33  /

CODE       STR
---------- ----------------------------
first      Mon-Wed,Fri
second     Wed,Thu,Sat
third      Mon-Sun

Review &
      Followup

      Rating:
      5
Magic  November 26, 2003
Reviewer:
      A reader
Tom,
Please consider writing a book on writing SQL !
More power to you...

      Rating: 5
Excellent,
      perfect, great, etc...  November 26, 2003

Reviewer:  Gary

      Rating: 3
another
      approach  November 26, 2003
Reviewer:
      Daniel  from Toronto
select code,
max(decode(day_number, 1, 'Monday'||sep, null))||
max(decode(day_number, 2, 'Tuesday'||sep, null))||
max(decode(day_number, 3, 'Wednesday'||sep, null))||
max(decode(day_number, 4, 'Thirsday'||sep, null))||
max(decode(day_number, 5, 'Friday'||sep, null))||
max(decode(day_number, 6, 'Saturday'||sep, null))||
max(decode(day_number, 7, 'Sunday', null))
 from (
select t.*,
decode(lead(cnt) over (partition by code order by day_number), 3, '-', ',') sep
from (
select t.*, count(*) over ( partition by code
order by day_number
range between 1 preceding and 1 following ) cnt
from chosen_days t
) t
)
where cnt != 3
group by code

          Followup:

ops$tkyte@ORA920> select code,
  2  max(decode(day_number, 1, 'Monday'||sep, null))||
  3  max(decode(day_number, 2, 'Tuesday'||sep, null))||
  4  max(decode(day_number, 3, 'Wednesday'||sep, null))||
  5  max(decode(day_number, 4, 'Thirsday'||sep, null))||
  6  max(decode(day_number, 5, 'Friday'||sep, null))||
  7  max(decode(day_number, 6, 'Saturday'||sep, null))||
  8  max(decode(day_number, 7, 'Sunday', null))
  9   from (
 10   select t.*,
 11   decode(lead(cnt) over (partition by code order by day_number), 3, '-',
',') sep
 12   from (
 13   select t.*, count(*) over ( partition by code
 14   order by day_number
 15   range between 1 preceding and 1 following ) cnt
 16   from chosen_days t
 17   ) t
 18   )
 19   where cnt != 3
 20   group by code
 21  /

CODE       MAX(DECODE(DAY_NUMBER,1,'MONDAY'||SEP,NULL))||MAX(DECODE
---------- --------------------------------------------------------
first      Monday,Tuesday,Wednesday,Friday,
second     Wednesday,Thirsday,Saturday,
third      Monday,Tuesday,Wednesday,Thirsday,Friday,Saturday,Sunday

????

I'm sure there is a way to do this with analytics (only need to look ahead and
behind by a day, however - this query is not it

      Rating: 4
It seemed to
      work for me  November 27, 2003
Reviewer:
      Gary
except for the trailing seperators, it seems to have worked for me.

SQL> select code,
  2  max(decode(day_number, 1, 'Monday'||sep, null))||
  3  max(decode(day_number, 2, 'Tuesday'||sep, null))||
  4  max(decode(day_number, 3, 'Wednesday'||sep, null))||
  5  max(decode(day_number, 4, 'Thirsday'||sep, null))||
  6  max(decode(day_number, 5, 'Friday'||sep, null))||
  7  max(decode(day_number, 6, 'Saturday'||sep, null))||
  8  max(decode(day_number, 7, 'Sunday', null))
  9   from (
 10  select t.*,
 11  decode(lead(cnt) over (partition by code order by day_number), 3, '-', ',')
sep
 12  from (
 13  select t.*, count(*) over ( partition by code
 14  order by day_number
 15  range between 1 preceding and 1 following ) cnt
 16  from chosen_days t
 17  ) t
 18  )
 19  where cnt != 3
 20  group by code ;

CODE       MAX(DECODE(DAY_NUMBER,1,'MONDAY'||SEP,NULL))||MAX(DECODE
---------- --------------------------------------------------------
first      Monday-Wednesday,Friday,
second     Wednesday-Sunday
third      Monday-Sunday

          Followup:

ahh -- i see what happened :)

I reran the load script supplied, but did not notice the create table failed
(already was there from yesterday).  So, i had two for one -- double the data :)

When you double the data, the analytic function query did not "work" right --
that was the cause of the confusion!

So, unless code/day_number is the primary key (unique at least), the analytic
query needs a little tweak to make it right.

      Rating:
      1
  November 27, 2003
Reviewer:
      Daniel
It's very interesting, how did you get this result? Here is what I got although
I forgot to trim trailing comma.

SQL> l
  1  select code,
  2  max(decode(day_number, 1, 'Monday'||sep, null))||
  3  max(decode(day_number, 2, 'Tuesday'||sep, null))||
  4  max(decode(day_number, 3, 'Wednesday'||sep, null))||
  5  max(decode(day_number, 4, 'Thirsday'||sep, null))||
  6  max(decode(day_number, 5, 'Friday'||sep, null))||
  7  max(decode(day_number, 6, 'Saturday'||sep, null))||
  8  max(decode(day_number, 7, 'Sunday', null))
  9   from (
 10  select t.*,
 11  decode(lead(cnt) over (partition by code order by day_number), 3, '-', ',')
sep
 12  from (
 13  select t.*, count(*) over ( partition by code
 14  order by day_number
 15  range between 1 preceding and 1 following ) cnt
 16  from chosen_days t
 17  ) t
 18  )
 19  where cnt != 3
 20* group by code
SQL> /

CODE       MAX(DECODE(DAY_NUMBER,1,'MONDAY'||SEP,NULL))||MAX(DECODE
---------- --------------------------------------------------------
first      Monday-Wednesday,Friday,
second     Wednesday,Thirsday,Saturday,
third      Monday-Sunday

          Followup:

see above, if code/day_number is repeated - that caused it.  so my mistake on
saying "blanket" it does not work -- but, it sometimes does not work if you
allow for more than one code/day_number.

ops$tkyte@ORA920> select code,
  2  max(decode(day_number, 1, 'Monday'||sep, null))||
  3  max(decode(day_number, 2, 'Tuesday'||sep, null))||
  4  max(decode(day_number, 3, 'Wednesday'||sep, null))||
  5  max(decode(day_number, 4, 'Thirsday'||sep, null))||
  6  max(decode(day_number, 5, 'Friday'||sep, null))||
  7  max(decode(day_number, 6, 'Saturday'||sep, null))||
  8  max(decode(day_number, 7, 'Sunday', null))
  9   from (
 10  select t.*,
 11  decode(lead(cnt) over (partition by code order by day_number), 3, '-', ',')
sep
 12  from (
 13  select t.*, count(*) over ( partition by code
 14  order by day_number
 15  range between 1 preceding and 1 following ) cnt
 16  from chosen_days t
 17  ) t
 18  )
 19  where cnt != 3
 20  group by code
 21  /

CODE       MAX(DECODE(DAY_NUMBER,1,'MONDAY'||SEP,NULL))||MAX(DECODE
---------- --------------------------------------------------------
first      Monday,Tuesday,Wednesday,Friday,
second     Wednesday,Thirsday,Saturday,
third      Monday,Tuesday,Wednesday,Thirsday,Friday,Saturday,Sunday

ops$tkyte@ORA920> select code,
  2  max(decode(day_number, 1, 'Monday'||sep, null))||
  3  max(decode(day_number, 2, 'Tuesday'||sep, null))||
  4  max(decode(day_number, 3, 'Wednesday'||sep, null))||
  5  max(decode(day_number, 4, 'Thirsday'||sep, null))||
  6  max(decode(day_number, 5, 'Friday'||sep, null))||
  7  max(decode(day_number, 6, 'Saturday'||sep, null))||
  8  max(decode(day_number, 7, 'Sunday', null))
  9   from (
 10  select t.*,
 11  decode(lead(cnt) over (partition by code order by day_number), 3, '-', ',')
sep
 12  from (
 13  select t.*, count(*) over ( partition by code
 14                              order by day_number
 15                              range between 1 preceding and 1 following ) cnt
 16  from (select distinct * from chosen_days) t
 17  ) t
 18  )
 19  where cnt != 3
 20  group by code
 21  /

CODE       MAX(DECODE(DAY_NUMBER,1,'MONDAY'||SEP,NULL))||MAX(DECODE
---------- --------------------------------------------------------
first      Monday-Wednesday,Friday,
second     Wednesday,Thirsday,Saturday,
third      Monday-Sunday

      Rating: 5
This is just
      too good, I need some help  November 27, 2003

Reviewer:  Sudhir
SQL> select code,
  2         case when f=-1 then '' else ''||min(prev_day_number) end min,
  3         max(day_number) max
  4  from
  5  (
  6  select code, day_number, prev_day_number,
  7         case when day_number-prev_day_number>1 then -1 else 1 end f
  8  from
  9  (
 10  select code, day_number, min(day_number) over
 11  (order by code, day_number rows 1 preceding) prev_day_number
 12  from chosen_days
 13  order by code, day_number
 14  )
 15  )
 16  group by code, f
 17  /

CODE       MIN                                             MAX
---------- ---------------------------------------- ----------
first      1                                                 3
first                                                        5
third      1                                                 7
second     3                                                 4
second                                                       6

How to get them on one line? I guess I need to add order by 1,2 as well

first 1,3-5

Thanks for the flood of new ideas, it really is amazing that one person can help
oracle community so much!

          Followup:

use 1 instead of 'mon'???

ops$tkyte@ORA920> select code,
  2  rtrim(
  3  max(decode(day_number, 1, '1'||sep, null))||
  4  max(decode(day_number, 2, '2'||sep, null))||
  5  max(decode(day_number, 3, '3'||sep, null))||
  6  max(decode(day_number, 4, '4'||sep, null))||
  7  max(decode(day_number, 5, '5'||sep, null))||
  8  max(decode(day_number, 6, '6'||sep, null))||
  9  max(decode(day_number, 7, '7', null)),',')
 10   from (
 11  select t.*,
 12  decode(lead(cnt) over (partition by code order by day_number), 3, '-', ',')
sep
 13  from (
 14  select t.*, count(*) over ( partition by code
 15  order by day_number
 16  range between 1 preceding and 1 following ) cnt
 17  from chosen_days t
 18  ) t
 19  )
 20  where cnt != 3
 21  group by code
 22  /

CODE       RTRIM(MAX(DEC
---------- -------------
first      1-3,5
second     3,4,6
third      1-7

ops$tkyte@ORA920>
ops$tkyte@ORA920> select code,
  2         replace(
  3         replace(
  4         replace(
  5         replace(
  6            trim(leading ',' from str),
  7            '-,','-'),
  8            '----', '-'),
  9            '---','-'),
 10            '--', '-') str
 11    from (
 12  select code, mon, tue, wed, thu, fri, sat, sun,
 13    decode( mon, 1, '1' ) ||
 14    decode( tue, 1, case when mon=1 and wed=1 then '-' else ',2' end ) ||
 15    decode( wed, 1, case when tue=1 and thu=1 then '-' else ',3' end ) ||
 16    decode( thu, 1, case when wed=1 and fri=1 then '-' else ',4' end ) ||
 17    decode( fri, 1, case when thu=1 and sat=1 then '-' else ',5' end ) ||
 18    decode( sat, 1, case when fri=1 and sun=1 then '-' else ',6' end ) ||
 19    decode( sun, 1, '7' ) str
 20    from (
 21  Select code,
 22         max(decode(day_number,1,1,0)) mon,
 23         max(decode(day_number,2,1,0)) tue,
 24         max(decode(day_number,3,1,0)) wed,
 25         max(decode(day_number,4,1,0)) thu,
 26         max(decode(day_number,5,1,0)) fri,
 27         max(decode(day_number,6,1,0)) sat,
 28         max(decode(day_number,7,1,0)) sun
 29    from chosen_days
 30   group by code
 31         )
 32         )
 33  /

CODE       STR
---------- ------------
first      1-3,5
second     3,4,6
third      1-7

      Rating: 5
Thanks for
      finding the bug of the query  November 27, 2003

Reviewer:  Daniel

      Rating: 5
Formatted
      weekdays in use", version 8.1.7  December 22, 2003

Reviewer:  Leonard Anukam  from Finland
Yes, Tom we need your book on SQL as soon as you can do it. When will your
latest one-on-one be out for 9i. I have for 8i. keep us informed when it is on
the book shelves.

Thanks

November 28, 2003 Rajeev -- Thanks for the question regarding "I am
      facing problems in writing stored procedure using Oracle Object Table.",
      version version 8i

      You Asked
Hi Tom,

How are you?

I am Rajeev and am using Oracle8i for writing stored procedures.

The problem that I am facing is, in my stored procedure I am
passing a Java object.

Let me explain the type of object that I want to pass to my
stored procedure.

Say I have oracle objects O1,O2, O3. O1 object contains object
table of objects O2 and O3 as its member variables.
Lets name the object table of object O2 as OT2 and for object O3
as OT3. So in short O1 will have OT2 and OT3 as its member
variables.

Now in my Stored Procedure I would like to know how many rows
does OT2 and OT3 contain, means I would like to know number of
objects OT2 and OT3 has.

For this I tried using O1.OT2.COUNT, but in vain.

Next I tried using REF of O2, here I used my own global variable
in stored procedure which was of type REF O2 and on that I tried
using COUNT but that also gave me compilation error.

Please guide me on this particular puzzle. Also if possible
please suggest me any particular document you are having that
may help me to proceed further.

I would also like to know how do I make Java mapping class for
such an Object.

Please reply me on rshah@systemax.com or dearrajeev@hotmail.com.

Your prompt reply will be highly appreciated.

Regards and Thanks
Rajeev.

--

      and we said...
If I understand (and if I've misunderstood, please use a test
case to demonstrate your issue), you have the following:

ops$tkyte@8i> create or replace type otype_3 as object
  2  ( x int )
  3  /
Type created.

ops$tkyte@8i> create or replace type otype_3_array
  2  as table of otype_3
  3  /
Type created.

ops$tkyte@8i> create or replace type otype_2 as object
  2  ( x int )
  3  /
Type created.

ops$tkyte@8i> create or replace type otype_2_array
  2  as table of otype_2
  3  /
Type created.

ops$tkyte@8i> create or replace type otype_1 as object
  2  ( o2   otype_2_array,
  3    o3   otype_3_array
  4  )
  5  /
Type created.

ops$tkyte@8i> declare
  2      var otype_1;
  3  begin
  4      var := otype_1( otype_2_array(),
  5                      otype_3_array() );
  6      dbms_output.put_line( var.o2.count );
  7
  8      dbms_output.put_line( '-------------' );
  9
 10      var := otype_1( otype_2_array(otype_2(1),otype_2(2)),
 11                      otype_3_array(otype_3(5) ) );
 12      dbms_output.put_line( var.o2.count );
 13      dbms_output.put_line( var.o3.count );
 14  end;
 15  /
0
-------------
2
1

PL/SQL procedure successfully completed.

It works -- what error are you getting??

to get the java classes, see

http://technet.oracle.com/doc/oracle8i_816/java.816/a81357/toc.htm
thats what jpublisher is about...

Review &
      Followup

      Rating: 4
calling custom
      collections from java  May 21, 2002
Reviewer:
      Charlie
Hi Tom,

So if I want to use "type otype_1" in JAVA, I have to use JPublisher??  Is there
another way?

Thanks,

          Followup:

You could write the coded by hand but why would you want to??

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/java.817/a83724/oraoot.htm#1000888
shows how to code it yourself.

      Rating: 4
table of
      table  June 03, 2002
Reviewer:  A reader

Hi Tom,

"create or replace type otype_1_collection as table of otype_1;" gave me the
following error.

LINE/COL ERROR
-------- -----------------------------------------------------------------
0/0      PL/SQL: Compilation unit analysis terminated
1/42     PLS-00534: A Table type may not contain a nested table type or VARRAY.

Is there any workround for this situation?

Thanks,

          Followup:

Upgrade to 9i which fully supports collections of collections.

      Rating: 4
Comma sperated
      value Vs VARRAY  November 27, 2003
Reviewer:
      Dan  from India
I got a suggestion not to use VARRAY as parameter to stores procedures for
sending dynamic list of values, and a better approach would be to use a string
of comma separated values to be parsed and processed in stored procedures.

Please suggest which approach to take, for passing a dynamic list of values to
oracle SP from a java program

          Followup:

which is easier and more maintainable and more then performant enough for you?

If you have an array in Java and must turn it into a string and in plsql turn
the string back into an array -- I might be tempted to "leave it be" as an array
in java and and array in plsql (less code)

If you have a string in the first place in java, I would send the string to
PLSQL and let plsql deal with it.

November 28, 2003 GAURANG -- Thanks for the question regarding
      "DROP USER", version 8.1.5

      You Asked
hi tom

i have this problem

  1  begin
  2  for x in (select username from all_users where username not
in('SYS',
  3  'SYSTEM','OUTLN','DBSNMP','ERT')) LOOP
  4  SYS.DBMS_UTILITY.EXEC_DDL_STATEMENT('SET TRANSACTION USE
ROLLBACK SEGMENT RB1');
  5  SYS.DBMS_UTILITY.EXEC_DDL_STATEMENT('DROP USER
'||X.USERNAME||' CASCADE');
  6  SYS.DBMS_UTILITY.EXEC_DDL_STATEMENT('ALTER ROLLBACK SEGMENT
RB1 SHRINK');
  7  END LOOP;
  8* END;
ert@MIGR> /
begin
*
ERROR at line 1:
ORA-01555: snapshot too old: rollback segment number 2 with name
"RB0" too small
ORA-06512: at line 2

i am assigning rb1 explicitely
and shrinking  it after dropping user

1) so why i am getting error about RB0 too small

2) why too much  undo is generated when we drop objects
(user,table etc..) ? please explain in detail.

thanks

      and we said...
First -- you cannot pick a rollback segment for DDL -- the first
thing DDL does is "commit" -- meaning that you set the rollback
segment for the transaction but the DDL commits right away --
causing a new rollback segment to be used and then commits
again.  You cannot pick the RBS for the DDL.

Second -- a snapshot too old is caused typically by OTHER people
affecting your transaction -- not by you.  Your transaction will
not cause itself to get a 1555 (unless you commit in a cursor
for loop but thats another story).  Actually -- now that I look
at it -- you are probably causing yourself the ORA-1555.  You
are in fact committing in a cursor for loop that is changing the
table in the cursor for loop.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:275215756923
for a good explaination of ORA-1555's.

What is happening in your case above is that you are doing ddl
in that database -- and you need to have a read consistent view
of some of the data dictionary for the query on ALL_USERS.  You
are generating changes in the data dictionary that our outer
query must not "see".  It needs to see the data dictionary as it
existed at the point in time the query begin.  Your rollback
segments are not sized large enough to avoid wrapping during the
execution of this loop.  So at some point, some undo got
generated and overwritten.  We needed that undo but it was gone.

The 2 ways to fix your particular problem:

o statitically increase the size of your RBS -- you need the
undo data for the entire cursor for loop.  You need bigger
rollback segements, ones that will not wrap during our
execution.

o read the list of users out into an array -- and then process
the array.  You will avoid the 1555 by processing the entire
query before committing even once.  To do this in 8.1.5, you
would simply code:

ops$tkyte@ORA8I.WORLD> declare
  2          type array is table of varchar2(40) index by
                        binary_integer;
  3          user_names array;
  4  begin
  5        select username BULK COLLECT INTO user_names from
                    all_users;
  6
  7          for i in 1 .. user_names.count
  8          loop
  9                 process data...
 10
 11          end loop;
 12  end;
 13  /

2) there is not TOO much undo generated -- just the right amount
;)

The problem is -- more undo is generated then you have rollback
space for.  We are wrapping around and we need the undo to
process the QUERY (common misconception -- we have not RUN OUT
of rollback -- we have overwritten rollback that a query needs).

The undo is to protect the data dicationary.  When you drop a
user cascade -- we must delete all of the data dictionary
entries for every SEGMENT (table/index/etc) they had, every
extent in that segment -- any contraints, indexes, triggers
(code), packages, procedures, etc.  You might be deleting
megabytes or hundreds of megabytes of stuff with the drop of a
single user.  If the database crashes due to a power failure
halfway through -- we must be able to "undelete" that stuff upon
startup.  We protect the data dictionary updates via the same
mechanisms we use to protect your updates.

Review &
      Followup

      Rating: 4
How to change
      username in database?  December 11, 2002
Reviewer:
      Ravi
Tom
What is the easy of changing usernames in database keeping all the objects
he/she owned intact.
Thanks for your help

          Followup:

there isn't one.

exp userid=x/y owner=foo
imp userid=x/y fromuser=foo touser=bar
drop user foo cascade;

      Rating:
      4
Thanks  December 12, 2002
Reviewer:
      A reader
Thanks Tom

      Rating: 4
Cannot Drop
      user  August 12, 2003
Reviewer:  Mohan K
       from Bangalore, India
I tried to drop the user with cascade option and gave the following error

*
ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 1
ORA-22864: cannot ALTER or DROP LOB indexes

When I queried the schema  using select * from tab it says
no rows selected

When I queried user_segments I found on CLOB index is being present and the
table is missing. I am not able to drop the index/segment. I had imported the
particular table from another schema and had renamed the table.

Now I want to drop the user and create a new user.

Mohan

          Followup:

please contact support and file a tar.

      Rating: 5
Drop All Schema
      Objects  November 27, 2003
Reviewer:
      A reader
Hi Tom,

Is there an easy way to drop all schema objects in a user's schema, without
dropping the user?

Thanks.

          Followup:

you would have to write a stored procedure/anonymous block to do so.

something like (like -- not fully baked here)

create or replace procedure drop_all
authid current_user
as
begin
  for x in ( select 'drop ' || object_type || ' ' || object_name ||
                    decode( object_type, 'TABLE', ' cascade constraints' ) stmt
               from user_objects
              where object_type not in ( 'PACKAGE BODY' , 'TYPE BODY',
'TRIGGER', 'INDEX', 'INDEX PARTITION',
                                         'TABLE PARTITION', 'MATERIALIZED VIEW
LOG', 'LOB'  )
                and object_name not like 'DR$%$_'
                and object_name not like 'DROP_ALL' /* :) see what happens if
you leave that out! */
              order by decode( object_type, 'TABLE', 1, 2 ) )
  loop
     begin
        execute immediate x.stmt;
     exception
        when others then
            dbms_output.put_line( x.stmt );
            dbms_output.put_line( sqlerrm );
     end;
  end loop;
  for x in ( select object_type, object_name from user_objects )
  loop
    dbms_output.put_line( x.object_type || ' ' || x.object_name || ' still
exists...' );
  end loop;
end;
/

November 28, 2003 Baqir -- Thanks for the question regarding
      "session_cached_cursor values", version 9.2.0.2

      You Asked
1. What value of session_cashed_cursor start with, in oralce 9.2.0.2?
2. Is there any way to set this value at system level (for all sessions) instead
of putting into init.ora?
Thanks

      and we said...
1) documentation says.....

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96536/ch1187.htm#1023765
2)
you could use an on-login trigger but putting into the init.ora (spfile) would
be best.

create or replace trigger SCC_trigger after logon on database
begin
    execute immediate 'alter session set session_cached_cursors = 100';
end;
/

Review &
      Followup

      Rating:
      4
session_cached_cursors  December 04, 2002

Reviewer:  Baqir Hussain  from Emeryville, USA

Tom,
Thanks for the prompt reply.
Could you please shed some light what do you mean by "after logon database"?
Each user who will logon to the system will set this value to 100 or it will be
set ONLY once?
What parameter should I look into verify that
session_cached_cursors = 100 needs to decresed or increased or it is working
fine.
What advantage do we get to put this trigger into spfile?
Thanks again for doing an exellent job for the oralce community.

          Followup:

The after login trigger fires after each and every logon -- so it will be once
per session.

It'll be set ONCE per session.

100 is a good value, there has been some andedoctal evidence to show thats the
point of diminishing marginal return.  Just watch the increased ram in the
dedicated servers.

you cannot put a trigger into an spfile.  This is a bad way to set an init.ora
paramter.  You would put the session_cached_cursors = 100 INTO the paramter file
INSTEAD of using a trigger to do an alter session for each and every logon

      Rating:
      5
session_cached_cursors  January 07, 2003

Reviewer:  Baqir Hussain  from Emeryville, CA USA

Tom,
Setting session_cached_cursors is a great help in getting more soft cursor
parsing which in turns almost no latch contention.
This parameter is continuing hogging a lot of RAM. My questios are:
1. Would it be continue to have more space in RAM or it will stop hogging at
some point??
2. Is there any way to find out how much memory is being used exclusively by
this parameter?

3. How can we prevent to have more memory consumed by this parameter??
Looks like 2 1/2 G of memory is occupied (and increasing day by day) by this
parameter.

          Followup:

session cached cursor is NOT a great help in getting MORE soft cursor parsing.

session cached cursors is a great help in reducing latching that takes place due
to excessive soft parsing (where a program parses, executes, closes a statement
over and over)

This parameter is not hogging alot of ram (tell me -- how exactly did you
measure that, what is "hogging" -- quantify that for me)

2.5 gig of memory WHERE?

      Rating:
      5
session_cached_cursors  January 08, 2003

Reviewer:  Baqir Hussain  from Emeryville, CA USA

When this parameter was added into the parameter file, the ram size was "real
1500M free", "swap 2500M free" using top (unix utility).  In three weeks or so
memory size came to "680M free" and "swap 1300M free".  I have been watching
sequential increase in memory usage over this 3 weeks period of time.
You also mentioned in your reply
"Just watch the increased ram in the dedicated servers."
Is this the known fact when setting this parameter or some thing else I need to
look at.
Please advise. thanks

          Followup:

running solaris?

solaris uses the entire set of free memory to cache disk stuff.  it is normal,
you cannot look at free and such and draw any conclusions.

Besides -- you have FREE memory -- you cannot put it into the bank and save it
for later.  Use it or lose it.

did you measure the dedicated servers?  You cannot USE TOP for that -- as it'll
report the sga.  if you are using solaris, look at PMAP.

But -- as you have free memory (and lots of it) -- what is the problem?

      Rating:
      3
pga_aggregate_target  January 09, 2003

Reviewer:  harri  from Finland
If session_cashed_cursor is set at system level does pga_aggregate_target affect
to memory size of sessions cursor cache?

If so, can we set session_cashed_cursor to relative big value and then limit
users memory usage by pga_aggregate_target.

          Followup:

depends -- first pga_aggregate_target is just that -- a target, not a hard
limit.

second, this will increase UGA memory which is in the PGA in dedicated server
mode and in the SGA in shared server mode.  So, depending on how you run will
affect where this memory actually lives.

andedoctal tests show that setting this about 100 or thereabouts is not useful
and in fact shows diminishing marginal returns.  So, bigger is not better here.
It is useful as a bandage, a crutch, for an application that has a bug.  The bug
being that the developer slammed the cursors closed ASAP -- even though they are
being used over and over and over and over again.  You can use this to put a
stop to the slamming close of the cursor -- so it can be reused nicely in the
same session.  You can use it until the developer fixes their bug.  If you do
not suffer from this malady -- you wouldn't be using it as it would only add
overhead to normal processing (eg: you do not take anti-biotics when you are
healthy, that would make you sick).

      Rating: 5
Which should be
      our goal session_cached_cusor =0  March 25, 2003

Reviewer:  Juan Carlos Reyes Pacheco  from Bolivia

Hi Tom,
As I understand about session_cached_cursor, as in cursor sharing, we should
look to use the = EXACT, while the other values are for fixing temporaly
problems in binding.

So our goal is to fix the problems and then set the session_cached_cusor =0?

          Followup:

No, not really as session_cached_cursor doesn't materially affect anything as
cursor sharing does.

It'll help when it can.
It does nothing if it is not needed.

I can provide benefits for recursive sql (something over which you have no
control) from time to time as well.

      Rating: 5
Is there a way
      to estimate the value for session_cached_cursor  March 25,
      2003
Reviewer:  Juan Carlos Reyes P  from Bolivia

Thanks Tom

Recently changing the value of SESSION_CACHED_CURSORS from 100 to 2000, I got a
great performance improvement in a specific process. But I guessed, I tried a
big amount to see what happened, this was not scientific.

Oracle documentationsays
"To determine whether the session cursor cache is sufficiently large for your
instance, you can examine the session statistic session cursor cache hits in the
V$SYSSTAT view. This statistic counts the number of times a parse call found a
cursor in the session cursor cache. If this statistic is a relatively low
percentage of the total parse call count for the session, then consider setting
SESSION_CACHED_CURSORS to a larger value."

1. Here is not very clear about when and how increase this value, what means
"relatively low" . About which number or percentage specifically.
2- Could you please helpme to build a query to get it.
SELECT * FROM V$SYSSTAT WHERE NAME = 'session cursor cache hits'.....
:) Thank you

          Followup:

well, being a despiser of ROT and RATIOs in general, I'll take a whack at this
one.

You cannot come up with a meaningful ratio with the supplied statistics.

It would take:

(a) count of UNIQUE sql statements parsed <<=== don't have that anywhere
(b) number of cache hits <<=== got it.
(c) parse count <<<=== got it.

Then, we would like to see

c-a = b

That is take the total parses (c), subtract out the number of unique statements
(a) we do have to parse them at least once, then we want the rest of the parsed
to be cache hits (b)

But you don't know how many unique sql statements you have.  If you have an
application that runs 500 unique sql statements ONCE -- cursor cache hits =
ZERO.

I would say -- increase while you still see positive improvement and an uptick
in the cursor cache hits..

      Rating: 5
:)
      Thanks  March 25, 2003
Reviewer:
      A reader

      Rating: 5
As you said it
      seems session_cached_cursor values the same as
      open_cursors  March 25, 2003
Reviewer:  Juan
      Carlos Reyes P  from Bolivia
Hi Tom I'm a little confused.

You said in

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:865497961356
"
So, if open_cursors was set to 1000 and you execute a plsql routine with 1000
statments -- peek at v$open_cursor -- you'll find its chock full of open
statements.  PLSQL caches them for us.  Session cached cursors is useful for
environments that don't (or can't) cache the cursors (eg: DYNAMIC SQL in plql
CANNOT cache the cursor).
"

This seems the same as say:
" Is the same for you, if You use session_cached_cursor or open_cursors, if you
don't use dynamic sql."

Is this right, thanks :)

          Followup:

huh, not following your train of thought there.

      Rating:
      5
  March 26, 2003
Reviewer:  Juan Carlos
      Reyes P  from Bolivia
Sorry I forgot the ,etc. after dynamic in the previous question, but all is
clear now.

:) Thanks.

      Rating: 5
Can't set
      session_cached_cursors in spfile  May 11, 2003

Reviewer:  Kamal Kishore  from New Jersey, USA

Hi Tom,
I'm trying to change the value of session_cached_cursors in my spfile, but I
keep getting this error:

sys @ ORA92.LAPTOP > alter system set session_cached_cursors = 100 scope = both
;
alter system set session_cached_cursors = 100 scope = both
                 *
ERROR at line 1:
ORA-02096: specified initialization parameter is not modifiable with this option

sys @ ORA92.LAPTOP > alter system set session_cached_cursors = 100 scope =
spfile ;
alter system set session_cached_cursors = 100 scope = spfile
                 *
ERROR at line 1:
ORA-02096: specified initialization parameter is not modifiable with this option

sys @ ORA92.LAPTOP >

How to change it in the SPFILE? none of the above seems to work. I'm Running
Oracle 9.2 on RedHat Linux.
Thanks,

          Followup:

it is fixed in 9iR2 9.2.0.3

until then,

create pfile from spfile;
edit the pfile, add it
then startup with the pfile and create spfile from pfile

      Rating: 5
interaction
      with cursor_sharing  July 10, 2003
Reviewer:
      Scott  from Chicago, IL USA
We have a vendor whose scripting tools don't allow bind variables.  To offset
this we've created a logon trigger that alters the session to set cursor_sharing
to FORCE for this particular user.  Would we also benefit from giving them a
non-zero number of session_cached_cursors?  The docs for cursor_sharing

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96536/ch131.htm#1015593
state that "FORCE -- Forces statements that may differ in some literals, but are
otherwise identical, to share a cursor, unless the literals affect the meaning
of the statement."  However, this doesn't really tell me whether there are soft
parses that we can mitigate with session_cached_cursors.

Many thanks,
Scott

          Followup:

the two work well together actually -- if you are using one, the other almost
demands to be used!  Consider:

ops$tkyte@ORA920> alter session set cursor_sharing=force;

Session altered.

ops$tkyte@ORA920> exec runStats_pkg.rs_start;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> alter session set session_cached_cursors=0;

Session altered.

ops$tkyte@ORA920> declare
  2          l_cursor sys_refcursor;
  3  begin
  4          for i in 1 .. 10000
  5          loop
  6                  open l_cursor for 'select * from dual where dummy = ''' ||
i || '''';
  7                  close l_cursor;
  8          end loop;
  9  end;
 10  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runStats_pkg.rs_middle;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> alter session set session_cached_cursors=100;

Session altered.

ops$tkyte@ORA920> declare
  2          l_cursor sys_refcursor;
  3  begin
  4          for i in 1 .. 10000
  5          loop
  6                  open l_cursor for 'select * from dual where dummy = ''' ||
i || '''';
  7                  close l_cursor;
  8          end loop;
  9  end;
 10  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec runStats_pkg.rs_stop(100)
Run1 ran in 388 hsecs
Run2 ran in 268 hsecs
run 1 ran in 144.78% of the time

Name                                  Run1        Run2        Diff
STAT...Elapsed Time                    398         277        -121
LATCH.SQL memory manager worka         134           0        -134
STAT...redo size                    65,192      64,920        -272
STAT...session cursor cache hi           1       9,999       9,998
LATCH.shared pool                   30,067      10,109     -19,958
LATCH.library cache pin             40,058      20,065     -19,993
LATCH.library cache pin alloca      40,026          22     -40,004
LATCH.library cache                 80,088      30,112     -49,976
STAT...session pga memory                0      65,536      65,536

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
193,684      63,495    -130,189    305.04%

PL/SQL procedure successfully completed.

      Rating: 4
Bjorn
      disagrees?  July 11, 2003
Reviewer:  Mark J.
      Bobak  from Ann Arbor, MI
Hi Tom,

Those are interesting results, particularly since they seem
to directly conflict with Bjorn Engsig's paper, available on
OTN.

A short quote:
Application category:  Not using bind variables at all.

General behavior:  Generally poor response time and
scalability.  Is however, recommended for DSS applications,
that have little or no repitition of SQL statements.

cursor_sharing force or similar:  Good improvement of
response time and scalability; although not quite as good
as re-writing the application.

session_cached_cursors sufficiently high:  No extra benefit.

cursor_space_for_time=true:  no effect.

He then goes through three other major categories of
application, each progressively better behaved with respect
to SQL and cursor handling, and describes which parameters
are a benefit.

However, his result clearly disagrees with yours on the
cursor_sharing combined w/ session_cached_cursors question.

Any thoughts, Tom?

          Followup:

You would have to ask him -- perhaps he tested cursor_sharing INDEPENDENT of
session_cached_cursors.

If he did -- then it would be accurate, sesssion_cached_cursors would do NOTHING
since you never execute the same statement more then once.

If you use them *together*, as you can see, measurably different.

      Rating:
      5
Sigh.  July 11, 2003
Reviewer:
      Scott  from Chicago, IL USA
Thank you much.  So from this I conclude that the scalar replacement that
cursor_sharing=FORCE imposes takes place prior to a more standard soft parse,
which the session_shared_cursors then helps along.

          Followup:

yup, it would sort of have to -- else, the benefit of the soft parse would not
be there!

Just to make sure, I run this in 8i as well since "things change", just to make
sure it applied in the past as well as now (it does)

ops$tkyte@ORA817DEV> alter session set cursor_sharing=force;
Session altered.

ops$tkyte@ORA817DEV> exec runStats_pkg.rs_start;
PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> alter session set session_cached_cursors=0;
Session altered.

ops$tkyte@ORA817DEV> declare
  2          type sys_refcursor is ref cursor;
  3          l_cursor sys_refcursor;
  4  begin
  5          for i in 1 .. 10000
  6          loop
  7                  open l_cursor for 'select * from dual where dummy = ''' ||
i || '''';
  8                  close l_cursor;
  9          end loop;
 10  end;
 11  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> exec runStats_pkg.rs_middle;
PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> alter session set session_cached_cursors=100;
Session altered.

ops$tkyte@ORA817DEV> declare
  2          type sys_refcursor is ref cursor;
  3          l_cursor sys_refcursor;
  4  begin
  5          for i in 1 .. 10000
  6          loop
  7                  open l_cursor for 'select * from dual where dummy = ''' ||
i || '''';
  8                  close l_cursor;
  9          end loop;
 10  end;
 11  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> exec runStats_pkg.rs_stop(100)
Run1 ran in 459 hsecs
Run2 ran in 345 hsecs
run 1 ran in 133.04% of the time

Name                                  Run1        Run2        Diff
STAT...Elapsed Time                    462         348        -114
STAT...CPU used by this sessio         466         347        -119
STAT...CPU used when call star         466         347        -119
STAT...redo size                     1,428       1,620         192
STAT...session cursor cache hi           1       9,999       9,998
LATCH.shared pool                   20,097          28     -20,069
LATCH.library cache                200,138      50,115    -150,023

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
220,425      50,339    -170,086    437.88%

PL/SQL procedure successfully completed.

      Rating: 4
Tom,Where can I
      get runStats_pkg package?  November 27, 2003

Reviewer:  A reader

          Followup:

http://asktom.oracle.com/~tkyte/runstats.html

November 28, 2003 -- Thanks for the question regarding "commit",
      version 8.1.6

      You Asked
1)is it required that all the sql statements in my application
(consisting of beans and servlets and jsp's) be followed by a
commit statement...

what happens if i donot give any commit after each sql
statements? do they commit when the session ends..
is there any feature where i can set the auto commit to true or
false in the database server itself..
how should it be actually?

2)i have a schema with all tables with
keys,views,sequences,triggers etc...

i implement fine grained access control on this and my policy
and context also exist in this schema(securitywise is it ok or
should i keep the securrity policy in system)

i have different users who use these table,views etc..
For which objects should i create pvt. synonyms in those
users/schemas and for which objects should i give grants?

i understand that even if i create a sequence i have to give
grant to the user..
but for keys and indexes do i need to create sequences ?

my understanding is i have to create synonymns for
dblinks,functions,packages,procedures,sequences,tables,views,trig
gers and then give grants accordingly...
did i miss out something?

3)i am using multimaster replication for the above scenario, so
what else do i need to take care of?.. like what are the objects

for which i have to create snapshots and groups etc...

regards
muralidhar

      and we said...
1) if you are using JDBC, it unfortunately "auto commits" after
each and every statement.  It doesn't do transactions.  I
believe the first line of code after connecting in JDBC should
always be:

   conn.setAutoCommit (false);

Here are the rules for when to commit:

o you should commit as soon as you can
o you should NEVER commit earlier then you should.

You would only commit after each statement if in fact EACH
statement was an entire transaction.  If you were going an ATM
transfer for example and needed to do the SQL:

    update accounts set balance = balance - 1000 where
      account_id = 123;
    update accounts set balance = balance + 1000 where
      account_id = 456;

to transfer $1000 from savings into checking, you would NOT want
to auto commit or commit at all between statements (what if the
second update fails, you just "lost" $1,000).  You would want to
commit right after the transfer was complete in order to make
the transaction "durable" -- permanent.

You should ALWAYS commit explicitly and you should always commit
when your business logic dictates it -- not after every
statement, not relying on the database to do it when you log off
(if you log off, Oracle will commit your work before it does so,
yes)

2) You only need to grant on the objects they will be accessing.
 If you grant execute on a procedure, they need NO grants on the
tables the procedure accesses.  If you grant select on a table,
they need NO access to the indexes or constraints.  Just the top
level object is granted on.

Synonyms are just aliases and have no privileges associated with
them.  You always need to grant on the objects to which they
refer for them to work.

You do not have to create synonyms at all, they are optional.
You can use "alter session set
current_schema=APPLICATION_SCHEMA" instead.

3) I cannot answer that since I didn't write your application ;)

If you are using MM replication -- you don't need to have
snapshots and snapshot groups (those are different from
Multi-Master)....

Review &
      Followup

      Rating: 5
Very helpful,
      and complete  April 05, 2001
Reviewer:  Harrison
      Picot  from Haymarket, VA USA

      Rating: 5
Control the DB
      commit at the Java level  May 20, 2003
Reviewer:
      Bala  from NJ, USA
Hi Tom,

One question.

This is the process the application is currently doing.

During Order creation, Java code populates the user data into result bean and
each result bean is mapped to the database tables. During Order check out, the
individual stored procedure is called and the database insert/update is done and
committed at each stored procedure level.

But logically the transaction should be committed only after all the stored
procedure calls are successful with out any errors.

Also the stored procedure (back end) is returning the unique id (sequence no)
back to the client at every stored procedure completion.

My questions are :
==================

Say i call 5 stored procedures from Java code. Each stored procedure is creating
records in some tables. In case, if the 5th stored procedure call fails (due to
some reason), i have to rollback all the records upto the point of time.

Can i do this ?

I will remove the commit at the stored procedure level. The individual stored
procedure is returning success or failure back to the caller in JDBC.

I want to control the commit at the Java code level.
If all the stored procedure call is successful then i will do commit at the Java
side else rollback all the records since begining of the transaction.

Do you see any drawback with this approach.

Please advice .

Thanks

In fact

          Followup:

yes you can do this.

I wish PLSQL didn't support commit/rollback.  I firmly believe transaction
control MUST be done at the topmost, invoker level.  That is the only way you
can take these N stored procedures and tie them together in a transaction.

      Rating: 4
Thank you for
      your confirmation.  May 21, 2003
Reviewer:
      A reader

      Rating: 4
further
      clarification  May 29, 2003
Reviewer:  Vijay
      Sehgal  from India
Hi Tom,
      Good day please suggest on this. In the project I am working on the
procedures in database are called from servlets using jdbc , auto commit is off
and the commit/rollback is done in procedures after successful/failure of
transaction. What is your suggestion should the commit be always issued from
servlets or front end technology used or if done in procedures for complete
transaction it is ok and what is the best way of these.

Thanks as always.

Regards,
Vijay

          Followup:

well, will you someday want to call procedure "P1" and "P2" as an atomic unit of
work?  If so, p1 and p2 should not commit or rollback.

Me, I believe the client should be responsible for this.

      Rating: 4
Commit big
      amount of data  May 29, 2003
Reviewer:  Danny
      Wang  from NY
Hi Tom,

We have a single transaction which will insert 40M+ rows into a table (6 indexes
associated with it) and it needs 50GB+ rollback segment. Also, there may be more
rows in the future.

Here is my question.

What's your suggestion about the maximum rollback segment usage per atomic
transaction? It couldn't be unlimited, right?

Thanks

          Followup:

I wouldn't have the indexes enabled.  I would need about 0 bytes of rollback to
do this.

I would disable indexes
I would nologging the table
I would direct path insert
I would rebuild the indexes in parallel/nologging
I would backup the affected tablespaces

      Rating: 5
commit
      issue  July 30, 2003
Reviewer:  Reader

I have an issue. My script updates a table and commits every 15 minutes.
However, after about 75% of the update is complete, the script failed. When I
rerun the script, I don't want to do the updates that were already committed.
How would I do it? I am sure you have some ideas in the way I have to code to
avoid running the updates that were already committed. could you please help?
thanks.

          Followup:

you do that by removing the commit....

or, writing lots of code to make yourself restartable.

is there a generic way to do this?  of course not, I've no idea or clue what
your UNIQUE process that on one else in the world does...

      Rating: 4
Commits and
      Savepoints  July 31, 2003
Reviewer:  Darren
       from London, UK
Tom,

Quick question on commits. I know that commits are synched to the LGWR process
and hence are bad not only from an overall process flow but bad from a
perfomance point of view too. Do savepoints also synch to the LGWR process and
hence suffer performance hits?

Also in a web application like asktom (mod_plsql). Would you rely on mod_plsql
doing the commit (it bieng the client) or would you do it in your top most
procedure?

Regards.

          Followup:

savepoints are logical things only -- they let you group N statements together
so as to allow you to rollback to savepoint (but not "commit to savepoint")

so no, they do not sync to disk.

We commit explicitly at the top level.

      Rating: 5
Very
      interesting and useful ...  July 31, 2003

Reviewer:  Helena Marková  from Bratislava, Slovakia

      Rating:
      4
commits  November 28, 2003
Reviewer:
      Duncan  from UK
If using jdbc and setting autocommit off. Then running a pl/sql procedure that
passes success or failure back to the app so the app has transaction control
over commit/rollback.

What happens though if the transaction fails and sends failure back to the app
which is then supposed to send the rollback command to the db but what if the
connection is severed before this rollback command can get to the db? Does the
db do a commit? If so is this not a bad thing?

          Followup:

the db will rollback uncommitted work in reaction to a failure.

November 28, 2003 Dave -- Thanks for the question regarding "Java
      Stored Procedures returning result sets in 9i???", version 9i

      You Asked
According to the marketing literature, Oracle 9i Java Stored Procs support
returning multiple rows.  Previosuly this was only possible from PL/SQL stored
procs.

For the life of me, I can't find ANY reference in the Oracle documentation, on
Internet news groups, or on the Oracle website that actually documents how this
is done!!!

To put this in context, we have some formatted data stored in a LONG RAW that
we'd like to use a Java stored proc to pull out, parse it in memory, and somehow
make this accessible to a regular SQL query as a result set.

Any ideas or suggestions?

      and we said...
well, first of all -- why the heck would you use a LONG RAW in 9i???? Yuck.

My first suggestion to you is:

alter table t modify ( y blob );

and turn that long raw into a blob.

My second suggestion would be to then use a pipelined PLSQL function to very
very efficiently and effectively return a result set from the blob.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2270447621346
for an example.

That would be the best approach.  Another method would be to (since PLSQL is
involved to some extent here, you must use PLSQL at SOME level with java stored
procedures) do something similar to the following:

REM -- this is the structure of your "result set"
create type myScalarType as object
( x int, y date, z varchar2(25) )
/

create type myTableType as table of myScalarType
/

create or replace procedure get_result_set( x in out SYS_REFCURSOR )
as
   l_data myTableType := myTableType();
begin
   java_stored_procedure( l_data );
   open x for select * from table ( cast( l_data as myTableType ) );
end;
/

And have the java stored procedure fill up the collection.

Or, just "select * from (java stored procedure)" as demonstrated in this link:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:799226268269

Again -- I would suggest that

o a blob is INFINTELY better (and in 9i, long raws are easily turned into blobs)
o PLSQL with pipelined functions would be the best approach.

Review &
      Followup

      Rating: 3
OK, long raws
      are bad, but ;)   January 28, 2002
Reviewer:
      Dave Churchville  from San Diego, CA
I still don't quite get how to return result sets from Java stored procs...I
have a pretty strong need to manipulate the information in the BLOB (its
basically XML data) in a flexible way, and would like to use a Java proc.

The ultimate goal is to make the information from the BLOB, RAW or whatever,
available to a 3rd party reporting tool using straight SQL.

So is the only way to return a result set to use an ARRAY type (single column)?
And/or declare a new type, and have the stored proc return an array of these?
THis is OK, as long as the normal SQL query can treat the results as individual
columns somehow...possible?

          Followup:

the array type can be a complex collection type (a table of objects), it need
not be a single scalar type.

Global Temporary tables are a good option as well -- have the java stored proc
fill up a global temporary table -- the caller just "select * from temp_table"
after running the procedure.

      Rating: 3
Global
      Temporary tables are a good option but dear  November 28,
      2003
Reviewer:  A reader
Hello Tom,

of course the array type can be a table of objects,
or you can use a global temporary table.

the disadvantage of the 1st. may be memory consumption,
the disadvantage of the 2nd file i/o (undo log and
redo log for undo log)
the disadvantage of both is lack of parallelism
(the calling pl/sql has to wait until java-procedure
has finished processing and cannot pipe a row as far
as java has it available).

my scenario is the following:

- I have some external datasources
  (mainly XML accessible via http, but also
   non-Oracle-databases accessible via JDBC)

- I embed each of them into Oracle-database
  via a view calling a PL/SQL-pipelined-function
  which calls a Java-Method which collects
  the data.
  (Java either uses oracle.xml.parser.v2.XMLParser in
   the first case or legacy-jdbc-driver in the second case)

Works great, it is very handy to have these sources
available in Oracle-SQL, but:

sometimes amount of data is large and I would *love*
to have a row piped as far as it is parsed and
*not only when the whole stuff is parsed*

will there such a feature be available in 10g ?

thanx,
matthias

          Followup:

we have it already in 9i -- it is called a pipelined function.  been in the
database for years.

      Rating: 1
so how do I
      actually *PIPE* a row from java  November 28, 2003

Reviewer:  Matthias Rogel  from Germany
not from PL/SQL !

again:
I have a pipelined function in PL/SQL
which calls a Java Method which calls a
SAXParser.
How can the SAXParser pipe a row back
in then endElement-Method of its
ContentHandler ?

Thanx
Matthias

          Followup:

it cannot -- you would have to iteratively call java routines like
"getfirstPiece", "getNextPiece", the pipelining works back from the server to
the ultimate client (the guy who connected to the database in the first place)

between plsql and java -- you would be simply calling functions.

      Rating: 4
thank
      you  November 28, 2003
Reviewer:
      A reader
for your quick responses
have a nice weekend.

matthias

November 28, 2003 Pertisth -- Thanks for the question regarding
      "Does the index work on view if base table has the index", version oracle
      8i

      You Asked
Dear Tom,

I must thank you for helping oracle community developer. Due to
you my system has given better performance.

I have one more question that If i a make view such as
Create or replace view view_voucher as ((Select
dvn_cd,vch_no,vch_dt,amount_dr,amount_cr from u21.voucher)
Union all  (Select  dvn_cd,vch_no,vch_dt,amount_dr,amount_cr
from u26.voucher) union all (Select
dvn_cd,vch_no,vch_dt,amount_dr,amount_cr from u28.voucher));

Now lets say we make index on dvn_cd,vch_no,vch_dt on
u21.voucher,u26.voucher,u28.voucher.

My question is that my index will work on view also.

One more question if make my view from other view and the base
table has the index , still when I select the data will my index
work.

Pertisth

      and we said...
Yes, if the index is called for, it'll be used.  For example:

ops$tkyte@ORA8I.WORLD> create table t1
  2  ( x int, y date, z varchar2(5),
  3    a int, b date, c varchar2(200));

Table created.

ops$tkyte@ORA8I.WORLD> create table t2
  2  ( x int, y date, z varchar2(5),
  3    a int, b date, c varchar2(200));

Table created.

ops$tkyte@ORA8I.WORLD> create table t3
  2  ( x int, y date, z varchar2(5),
  3    a int, b date, c varchar2(200));

Table created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> create index t1_idx on t1(x,y,z);

Index created.

ops$tkyte@ORA8I.WORLD> create index t2_idx on t2(x,y,z);

Index created.

ops$tkyte@ORA8I.WORLD> create index t3_idx on t3(x,y,z);

Index created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> create or replace view v
  2  as
  3  select x,y,z,a,b,c
  4    from t1
  5  union all
  6  select x,y,z,a,b,c
  7    from t2
  8  union all
  9  select x,y,z,a,b,c
 10    from t3
 11  /

View created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> set autotrace on explain
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select * from v where x = 5;

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   VIEW OF 'V'
   2    1     UNION-ALL
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'T1'
   4    3         INDEX (RANGE SCAN) OF 'T1_IDX' (NON-UNIQUE)
   5    2       TABLE ACCESS (BY INDEX ROWID) OF 'T2'
   6    5         INDEX (RANGE SCAN) OF 'T2_IDX' (NON-UNIQUE)
   7    2       TABLE ACCESS (BY INDEX ROWID) OF 'T3'
   8    7         INDEX (RANGE SCAN) OF 'T3_IDX' (NON-UNIQUE)

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select * from v where x > 2 and y <
sysdate;

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   VIEW OF 'V'
   2    1     UNION-ALL
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'T1'
   4    3         INDEX (RANGE SCAN) OF 'T1_IDX' (NON-UNIQUE)
   5    2       TABLE ACCESS (BY INDEX ROWID) OF 'T2'
   6    5         INDEX (RANGE SCAN) OF 'T2_IDX' (NON-UNIQUE)
   7    2       TABLE ACCESS (BY INDEX ROWID) OF 'T3'
   8    7         INDEX (RANGE SCAN) OF 'T3_IDX' (NON-UNIQUE)

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> set autotrace off

shows that the view will use the index if it can.  if we did not
query on X, it would not have used the index in this case since
X is on the leading edge of it.

If you create a view of a view of a view of a view (etc) -- the
thing you select from will have access to all indexes on the
base table(s) involved and will use them if it is proper and it
can (some views, like queries (since a view is just a stored
query), preclude the use of indexes).
Review &
      Followup

      Rating: 5
Improving
      views  March 27, 2002
Reviewer:  Stacy Mader
       from Parkes, NSW, Australia.
Thanks to Tom! I found my view to be very slow when using select statements, but
once I created indexes for each table and then re-creating the view, the return
speed was like querying the base tables themselves.

      Rating:
      4
  August 13, 2002
Reviewer:
      A reader
Can we create index on view?

          Followup:

you index base tables and these indexes are used by the view.

      Rating: 5
hints in
      views  November 27, 2003
Reviewer:  Mariano
       from Cordoba, Argentina
Tom, hi.
i'm working in 8.1.7 where i have a table:
create table table1
(user varchar2(10), since date, hourtype hour, q number);
-- notice "hourtype" is a type i created
create index table1_idx on table1 (user);

'cause i have to output this in the web and it does not support the hour type, i
use to view where i convert the type to varchar2:

create or replace view v_table1 select a.user, a.since,
a.hourtype.convertochar() hourtype from table1 a;

then, from the web, the user retrieves information based on his "user session"
where it matches v_table1.user
the problem seems that it's making an access full (when i check the explain
plan) and it takes a lot to retrieve the info.
how can i make it user the index in the master table?
regards

          Followup:

really BAD idea to use USER as a column name!!! rethink that immediately, you'll
be really sorry if not.

user is a builtin word.  "where user = :bind" is not thinking "database column"
but rather "currently logged in user"

Nothing to do with the UDT at all -- everything to do with what the word USER
means!

ops$tkyte@ORA920> create table t ( "USER" varchar2(10), uname varchar2(10) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( 'TOM', 'TOM' );

1 row created.

ops$tkyte@ORA920> create index t_idx1 on t("USER");

Index created.

ops$tkyte@ORA920> create index t_idx2 on t(uname);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select * from t where user = 'TOM';

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

ops$tkyte@ORA920> select * from t where "USER" = 'TOM';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T'
   2    1     INDEX (RANGE SCAN) OF 'T_IDX1' (NON-UNIQUE)

ops$tkyte@ORA920> select * from t where uname = 'TOM';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T'
   2    1     INDEX (RANGE SCAN) OF 'T_IDX2' (NON-UNIQUE)

ops$tkyte@ORA920> set autotrace off

so, you can use USER (quote it) but -- i would not suggest it at all.

      Rating: 5
my
      mistake  November 28, 2003
Reviewer:
      Mariano  from Córdoba, Argentina
tom,
i rewrite my problem:
let's say i create a materialized view as follows:
create materialized view nmv_manejo
tablespace reporting nologging
using index tablespace reporting_idx
refresh start with sysdate next sysdate+10/24
as
select e.prl_id legajo, e.prl_surname || ' ' || substr(e.prl_name,1,1)||'.'
nombre,
       concat(decode(length(b.ulo_prl_id), 3, 'CTI0', 2, 'CTI00', 1,
                      'CTI000', 'CTI'), b.ulo_prl_id) usr_legajo, c.superior,
c.nodo_hijo, c.nodo_padre,
                      c.grupo, c.id_nodo,
       concat(decode(length(c.superior), 3, 'CTI0', 2, 'CTI00', 1, 'CTI000',
                      'CTI'), c.superior) usr_superior, c.nivel, c.tipo, c.memo,
       extract(year from a.lag_date) anio,
       extract(month from a.lag_date) mes,
       to_number(decode(to_char(a.lag_date, 'w'), '5', '4',
                         to_char(a.lag_date, 'w'))) semana,
       to_char(a.lag_date, 'dd/mm') dia, a.lag_date fecha,
       a.lag_skill nro_split, initcap(d.spl_name) split_name,
       a.lag_acd_calls, a.lag_avg_acd.ToSeconds() lag_avg_acd,
       a.lag_avg_acw.ToSeconds() lag_avg_acw,
       a.lag_avg_hold.ToSeconds() lag_avg_hold,
       f.sao_objective.ToSeconds() objetivo,
       f.sao_objective
  from lucent_aht_grupo a, user_logins b, v_hierarchy_tree c, splits d,
       payroll e, split_aht_obj f
 where a.lag_logid = b.ulo_login and b.ulo_prl_id = c.legajo and
       c.puesto = 'REP' and c.es_invalido = 'N' and c.computa = 'Y' and
       a.lag_acd_calls > 0 and a.lag_skill = d.spl_id and c.tipo != 'dir' and
       b.ulo_prl_id = e.prl_id and d.spl_id=f.sao_spl_id and
       a.lag_date>=(last_day(add_months(trunc(sysdate),-4))+1) and
       a.lag_date between b.ulo_start_date and nvl(b.ulo_end_date, a.lag_date)
and
       a.lag_date between c.inicio_relacion and  nvl(c.fin_relacion, a.lag_date)
and
       a.lag_date between c.inicio_puesto and nvl(c.fin_puesto, a.lag_date) and
       a.lag_date between c.estado_inicio and nvl(c.estado_fin, a.lag_date) and
       a.lag_date between c.sup_inicio and nvl(c.sup_fin, a.lag_date) and
       a.lag_date between d.spl_start_date and nvl(d.spl_end_date, a.lag_date)
and
       a.lag_date between f.sao_start_date and nvl(f.sao_end_date, a.lag_date);
create index nmv_mnjo_usridx on  nmv_manejo (usr_legajo) tablespace
reporting_idx;

And then, in order to calculate certain results i must output in a .asp page, i
create the following view:
create or replace view rep_mnj_diario
(nombre, usr_legajo, anio, mes, dia, llamadas_dia, pond_real, pond_obj,
cumplimiento)
as
select nombre, usr_legajo, anio, mes, dia,
                sum(lag_acd_calls) over(partition by usr_legajo, anio, mes,
fecha),
                a.sao_objective.SecsToHour(round(sum(lag_acd_calls *
                                                      (lag_avg_acd +
                                                      lag_avg_acw))
                                                  over(partition by
                                                       usr_legajo, anio, mes,
                                                       fecha) /
                                                  sum(lag_acd_calls)
                                                  over(partition by
                                                       usr_legajo, anio, mes,
                                                       fecha), 0)) ,
                a.sao_objective.SecsToHour(round(sum(lag_acd_calls *
                                                      objetivo)
                                                  over(partition by
                                                       usr_legajo, anio, mes,
                                                       fecha) /
                                                  sum(lag_acd_calls)
                                                  over(partition by
                                                       usr_legajo, anio, mes,
                                                       fecha), 0)) ,
                round(((round(sum(lag_acd_calls * objetivo)
                               over(partition by usr_legajo, anio, mes, fecha) /
                               sum(lag_acd_calls)
                               over(partition by usr_legajo, anio, mes, fecha),
                               0)) /
                       (round(sum(lag_acd_calls *
                                   (lag_avg_acd + lag_avg_acw))
                               over(partition by usr_legajo, anio, mes, fecha) /
                               sum(lag_acd_calls)
                               over(partition by usr_legajo, anio, mes, fecha),
                               0)) * 100), 3)
  from nmv_manejo a;
if i run this as a simple query in order to retrieve information from just one
"user" (adding a where clause
as follows:
  "where a.usr_legajo='CTI0454'")
it ends really quickly and the explain plan says:
SELECT STATEMENT, GOAL = CHOOSE        72    336    48048            0
         SELECT STATEMENT    CHOOSE                                72
REPORTING
 WINDOW SORT            336    48048            1                    WINDOW
  SORT            0                1            REPORTING
  TABLE ACCESS BY INDEX ROWID    NMV_MANEJO    36    336    48048            2
     1            TABLE ACCESS    ANALYZED    BY INDEX ROWID            1
        1            REPORTING
   INDEX RANGE SCAN    NMV_MNJO_USRIDX    1    336                3
  NON-UNIQUE    INDEX    ANALYZED    RANGE SCAN            2                1
        REPORTING

but if i execute the view instead of the query, this happens:
SELECT STATEMENT, GOAL = CHOOSE        234    17    2465            0
        SELECT STATEMENT    CHOOSE                                234
REPORTING
 VIEW    REP_MNJ_DIARIO    234    17    2465            1        1
VIEW                    0                1            REPORTING
  WINDOW SORT            17    2431            2                    WINDOW
 SORT            1                1            REPORTING
   TABLE ACCESS FULL    NMV_MANEJO    211    17    2431            3        2
        TABLE ACCESS    ANALYZED    FULL            2                1
 REPORTING

i'm aware that full is not always bad and indexes are not always good but in
this case it, as it not accessing with the index, seems a never ending query.
Any advice will be appreciated.
Regards

          Followup:

Help me reproduce with a simple example I can run on my system -- take this and
modify it to be like yours till it "doesn't work".  try to make the display
"readable" as well please.  I didn't even attempt to try and figure out what the
query plans were -- it is unreadable.

ops$tkyte@ORA920> create table t ( x varchar2(10), y varchar2(10), z
varchar2(10), a int, b int, c int );
Table created.

ops$tkyte@ORA920> create index t_idx on t(x);
Index created.

ops$tkyte@ORA920> exec dbms_stats.set_table_stats( user, 'T', numrows => 100000,
numblks => 10000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace view v
  2  as
  3  select x, y, z, sum(a) over (partition by x, y, z) /
                     sum(b) over (partition by x, y, z ) sum_ab,
  4         demo_pkg.sectohour(
  5                  sum(a) over (partition by x, y, z) /
                     sum(b) over (partition by x, y, z )
  6                                  ) pkg
  7    from t
  8  /

View created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select x, y, z, sum(a) over (partition by x, y, z) / sum(b)
over (partition by x, y, z ) sum_ab,
  2         demo_pkg.sectohour(
  3                  sum(a) over (partition by x, y, z) /
                     sum(b) over (partition by x, y, z )
  4                                  ) pkg
  5    from t
  6   where x = 'A'
  7  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1000 Bytes=47000)
   1    0   WINDOW (SORT) (Cost=4 Card=1000 Bytes=47000)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=1000
Bytes=47000)
   3    2       INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=400)

ops$tkyte@ORA920> select *
  2    from v
  3   where x = 'A'
  4  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1000 Bytes=47000)
   1    0   VIEW OF 'V' (Cost=4 Card=1000 Bytes=47000)
   2    1     WINDOW (SORT) (Cost=4 Card=1000 Bytes=47000)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=1000
Bytes=47000)
   4    3         INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=400)

ops$tkyte@ORA920> set autotrace off

I get the index in both cases.  I think my example is close to yours.  I
tested in 8174 and 9203

November 28, 2003 Marcio -- Thanks for the question regarding "LOB
      Space", version 920

      You Asked
Tom,
When you have a table with lob like this:

create table t ( x clob ) tablespace users
 lob ( x ) store as ( tablespace tools );

1) The data goes to USERS when x < 4000 bytes and TOOLS when x > 4000 bytes, is
that right?

If so, suppose:

insert into t select rpad('*', 3999, '*') from dual;
this goes to users and...
update t set x = rpad('*', 6000, '*');
this goes to tools

2) Did whole clob data, mean 3999 *'s go to tools? Could I use users spaces
after that update?

I am pretty confused about it.

Thanks,
Marcio

      and we said...
1) correct (but that update won't work unless you use binds -- rpad can only
return 4000 bytes in SQL)

ops$tkyte@ORA920> create table t ( x clob );

Table created.

ops$tkyte@ORA920> insert into t values ( rpad('*',3999,'*') );

1 row created.

ops$tkyte@ORA920> select dbms_lob.getlength(x) from t;

DBMS_LOB.GETLENGTH(X)
---------------------
                 3999

ops$tkyte@ORA920> update t set x = rpad('*',6000,'*');

1 row updated.

ops$tkyte@ORA920> select dbms_lob.getlength(x) from t;

DBMS_LOB.GETLENGTH(X)
---------------------
                 4000

ops$tkyte@ORA920> declare
  2          l_data long := rpad('*',6000,'*');
  3  begin
  4          update t set x = l_data;
  5  end;
  6  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select dbms_lob.getlength(x) from t;

DBMS_LOB.GETLENGTH(X)
---------------------
                 6000

2) the entire clob is moved OUT OF LINE and replaced with a lob locator.

if you have 6000 characters in the clob then the table will have a LOB LOCATOR
that points to all 6000 characters in the lob tablespace.

November 28, 2003 -- Thanks for the question regarding "difference
      between soft parse and hard parse", version 8.1.7.2

      You Asked
Hi Tom

Can you explain briefly the difference between soft and hard parse?

cheers

      and we said...
Here is a long winded answer, it is extracted in part from a new book coming out
soon "beginning Oracle programming" that I collaborated on:

Parsing

This is the first step in the processing of any statement in Oracle.  Parsing is
the act of breaking the submitted statement down into its component parts ?
determining what type of statement it is (query, DML, DDL) and performing
various checks on it.

The parsing process performs two main functions:

o Syntax Check: is the statement a valid one?  Does it make sense given the SQL
grammar documented in the SQL Reference Manual.  Does it follow all of the rules
for SQL.

o Semantic Analysis:  Going beyond the syntax ? is the statement valid in light
of the objects in the database (do the tables and columns referenced exist).  Do
you have access to the objects ? are the proper privileges in place?  Are there
ambiguities in the statement ? for example if there are two tables T1 and T2 and
both have a column X, the query ?select X from T1, T2 where ?? is ambiguous, we
don?t know which table to get X from.  And so on.

So, you can think of parsing as basically a two step process, that of a syntax
check to check the validity of the statement and that of a semantic check ? to
ensure the statement can execute properly.  The difference between the two types
of checks are hard for you to see ? Oracle does not come back and say ?it failed
the syntax check?, rather it returns the statement with a error code and
message.  So for example, this statement fails with a syntax error:

SQL> select from where 2;
select from where 2
       *
ERROR at line 1:
ORA-00936: missing expression

While this statement failed with a semantic error ? if the table NOT_A_TABLE
existed and we had permission to access it, this statement would have succeeded:

SQL> select * from not_a_table;
select * from not_a_table
              *
ERROR at line 1:
ORA-00942: table or view does not exist

That is the only way to really tell the difference between a semantic and
syntactic error ? if the statement COULD have executed given the proper objects
and privileges, you had a semantic error, otherwise if the statement could not
execute under any circumstances, you have a syntax error.  Regardless ? Oracle
will not execute the statement for you!

The next step in the parse operation is to see if the statement we are currently
parsing has already in fact been processed by some other session.  If it has ?
we may be in luck here, we can skip the next two steps in the process, that of
optimization and row source generation.  If we can skip these next two steps in
the process, we have done what is known as a Soft Parse ? a shorter process
to getting our query going.  If we cannot, if we must do all of the steps, we
are performing what is known as a Hard Parse ? we must parse, optimize,
generate the plan for the query.  This distinction is very important.  When
developing our applications we want a very high percentage of our queries to be
Soft Parsed ? to be able to skip the optimize/generate phases ? as they are
very CPU intensive as well as a point of contention (serialization).  If we have
to Hard Parse a large percentage of our queries, our system will function
slowly and in some cases ? not at all.

The way this sharing of SQL in Oracle is accomplished is via the shared pool, a
piece of memory in the SGA maintained by Oracle.  We covered this topic in
chapter 5 but will revisit is again in the context of processing a query.  After
Oracle parses the query and it passes the syntax and semantic checks ? it will
look in the shared pool component of the SGA to see if that same exact query has
already been processed by another session.  Since it has performed the semantic
check it has already figured out:

o Exactly what tables are involved
o That we have access to the tables (the proper privileges are there)

And so on.  Now, it can look at all of the queries in the shared pool that have
already been parsed/optimized and generated to see if the work has already been
done.
........
Review
      & Followup

      Rating: 5
Soft Parse and
      session_cashed_cursor parameter  January 28, 2002

Reviewer:  A reader
can you explain what means the default value (0) of session_ casched_cursor
parameter? Oracle always has a cache
of sqls in sga and size of this cache is determinated through init.ora parameter
shared_pool_size.

          Followup:

The default value of ZERO means that by default, ZERO cursors will be cached for
your session.

They will be cached in the shared pool -- but your session will have to find
them there.  Session Cached Cursors can remove the need to have to "find" them.
Consider this example that shows the difference spent when soft parsing without
session cached cursors vs soft parsing WITH session cached cursors:

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

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table run_stats ( runid varchar2(15),
name varchar2(80), value int );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace view stats
  2  as select 'STAT...' || a.name name, b.value
  3        from v$statname a, v$mystat b
  4       where a.statistic# = b.statistic#
  5      union all
  6      select 'LATCH.' || name,  gets
  7        from v$latch;

View created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> column name format a40
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_start number;
  3          l_cnt   number;
  4  begin
  5      execute immediate 'alter session set session_cached_cursors=0';
  6      insert into run_stats select 'before', stats.* from stats;
  7
  8      l_start := dbms_utility.get_time;
  9      for i in 1 .. 1000
 10      loop
 11            execute immediate 'select count(*) from emp' into l_cnt;
 12      end loop;
 13      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 14
 15      execute immediate 'alter session set session_cached_cursors=100';
 16      insert into run_stats select 'after 1', stats.* from stats;
 17
 18      l_start := dbms_utility.get_time;
 19      for i in 1 .. 1000
 20      loop
 21            execute immediate 'select count(*) from emp' into l_cnt;
 22      end loop;
 23      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
 24
 25      insert into run_stats select 'after 2', stats.* from stats;
 26  end;
 27  /
45 hsecs
35 hsecs

PL/SQL procedure successfully completed.

so, session cached cursors RAN faster (i ran this a couple of times, there
were no hard parses going on.  But the real good news is:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value-a.value run1,
c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

NAME                                           RUN1       RUN2       DIFF
---------------------------------------- ---------- ---------- ----------
LATCH.checkpoint queue latch                      3          4          1
LATCH.redo allocation                            30         31          1
STAT...consistent gets                         5088       5089          1
STAT...deferred (CURRENT) block cleanout          2          3          1
 applications

STAT...calls to get snapshot scn: kcmgss       5019       5018         -1
STAT...enqueue releases                          10          9         -1
STAT...execute count                           1015       1014         -1
STAT...opened cursors cumulative               1015       1014         -1
STAT...parse count (total)                     1015       1014         -1
STAT...session cursor cache count                 0          1          1
STAT...redo entries                              28         27         -1
STAT...recursive calls                         1180       1179         -1
STAT...physical reads                             1          0         -1
LATCH.direct msg latch                            2          0         -2
LATCH.session queue latch                         2          0         -2
LATCH.done queue latch                            2          0         -2
STAT...free buffer requested                      8          6         -2
STAT...enqueue requests                          11          9         -2
LATCH.messages                                    3          0         -3
STAT...db block changes                          47         44         -3
LATCH.redo writing                                3          0         -3
LATCH.ksfv messages                               4          0         -4
STAT...session logical reads                  17128      17123         -5
LATCH.row cache objects                         184        178         -6
STAT...db block gets                          12040      12034         -6
STAT...parse time elapsed                         9          3         -6
STAT...parse time cpu                            13          4         -9
STAT...recursive cpu usage                       51         38        -13
LATCH.cache buffers chains                    34315      34335         20
STAT...redo size                              23900      24000        100
STAT...session cursor cache hits                  3       1002        999
LATCH.shared pool                              2142       1097      -1045
LATCH.library cache                           17361       2388     -14973

34 rows selected.

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

see the significantly REDUCED number of LATCH counts on the library and shared
pool.  Since a latch is a lock, a lock is a serialization device, serialization
implies WAITS -- using the session cached cursors will increase scalability and
performance as you add more and more users.  Its not only FASTER, but more
scalable as well...

      Rating:
      5
  January 28, 2002
Reviewer:
      A reader

      Rating: 5
SQL
      parsing  January 28, 2002
Reviewer:
      A reader
You mean that to parse SQL Oracle have always make two steps to check SQL code
"syntactically and semantically" and than check shared_pool. I have read, that
Oracle always save unparsed representation of SQL code and have a hashing
algorithm to quick locate this code and I thought that Oracle make this step
(check shared_pool for matching statement) as a very first, without make any
other checks before.

          Followup:

It needs to do the syntax to make sure it can understand it.

It needs to do the semantic check to see what objects are really referenced.

Only then can it safely goto the shared pool to see if the sql was already
parsed.  Think about it, what if we have:

select * from t;

That statement might be in the shared pool -- the one in the shared pool however
references the table SCOTT.T.  Now, you submit:

select * from t;

Can you reuse that plan?  I don't know, you don't know, the database sure
doesn't know.  It must parse the statement -- figure out that T is a table,
determine the owner of the table T in question.  Then, it can goto the shared
pool to see if the statement is there -- and upon finding the identical "select
* from t", it will have to further verify that the T referenced by both queries
is the SAME exact T.

Hence, we need to do the "semantic" parse before we can find a statement in the
shared pool for you to reuse (there could be 1,000 "select * from t" statements
in the shared pool -- none of which you could use for example).  We need to
syntatically parse the query before we can semantically parse it.  The hash is
only good for finding query strings that are the same -- its not any good for
figuring out if the referenced statement is ACTUALLY the same statement in your
execution context.

      Rating:
      4
  January 28, 2002
Reviewer:
      A reader
Thank you for quick answer of my question "Soft Parse and session_cashed_cursor
parameter".
To my understanding:
does Oracle save this session cursors in PGA (dedicated server mode)and are this
cursors available for other sessions

          Followup:

UGA, it'll be in the UGA.  The PGA is specific to a process and in various
configurations (eg: shared server mode, aka MTS) the PGA you use on one query
will be different from the PGA you use in another.

In dedicated server mode, the UGA is in the PGA so the difference is subtle (but
there). In shared server, the UGA is in the SGA.

A cursor is specific to a SESSION, the query plans and such are shared by all
sessions.

      Rating: 3
Thought I had
      it, but maybe not  January 28, 2002
Reviewer:
      Jon  from Hartford, CT
Just as I thought I was starting to get this soft vs hard parse, I read this and
got confused all over again...

Here you say that, "When developing our applications we want a very high
percentage of our queries to be Soft Parsed", but in your book (p 441), you say
that "Using bind variables is crucial to the performance; avoiding the soft
parse of a query is equally as important".  Sounds contradictory...can you
clarify?

I thought the pecking order was:

BAD  (not using bind variables; therefore "hard parse")

WHERE XXXXX_ID = '123456'

call     count
------- ------
Parse    1
Execute  1

Better (using bind variables, but soft parsing, perhaps due to closing cursor
and reopening, etc.)

WHERE XXXXX_ID = :b1

call     count
------- ------
Parse    1570
Execute  1570

Best (using bind variables, avoids soft parses)

WHERE XXXXX_ID = :b1

call     count
------- ------
Parse    1
Execute  1570

Please set me straight.  Thanks.

          Followup:

Ok, at the SYSTEM LEVEL (eg: what you see with statspack) we want our ratio of
SOFT parses to HARD parses to be very very large.  We want to be SOFT parsing at
the system level (that means our applications as a whole make use of shared
sql).  That means a given query that was hard parsed once was soft parsed
(reused) over and over.

Within an application -- we want our PARSE to EXECUTE ratio to be very very LOW.
 We want to parse a statement once, execute it over and over.  It is best if all
of our queries in our application are soft parsed (meaning someone else did the
hard parse) and I would be suspicious if all queries were hard parsed in an
application unless the database had just been restarted.

So, system wide -- the ratio of SOFT to HARD should be high (1000 soft parses vs
10 hard parses means each statement was reused 100 times).

In an application -- the ratio of PARSE to EXECUTE should be low (1 parse to 100
executes means our app parsed -- be it soft or hard -- ONCE and executed it 100
times)

You are correct in the above, the last one is the best one IN AN APPLICATION.
System wide -- everyone will parse it AT LEAST ONCE (and they should parse it AT
MOST once).

      Rating: 5
What is the new
      book you are co-operating on  January 28, 2002

Reviewer:  Nag
Tom

When will this book you are co-operating on , be out in the market.

          Followup:

It is due out in March/April.

It is a beginning Oracle book -- very different from my current book.  At a very
different level.  I've written 3 chapters in it.  The entire book will be 1
introduction, 19 chapters, 2 case studies and 5 appendices....

      Rating: 4
Why is
      cached_cursors session level ?  January 28, 2002

Reviewer:  Ravi  from NY,NY
Tom,

Thanks for your time. Please clarify:

1. Why is the cursor caching at session level ? Suppose one user fires the query
'SELECT * FROM CT.EMP WHERE ENAME = :p1'. Then if another user makes the exact
same query (with same value of p1), should not the cached cursor be used for her
also ?

2. Are there any negatives of using cursor caching ? If not, why is the default
value 0 ?

3. Could you show an example which avoids soft parsing. For example I would like
to see one hard parse and n executes, without any further parsing.. I tried this
in SQL Plus, but I get as many soft parses as many executes.

          Followup:

you are confusing cursors with shared sql.

A cursor points to some shared sql.  Shared sql is used by all.  A cursor is
more then just the plan and such from the shared pool, a cursor is a "state" as
well.  A cursor is your data -- YOUR data.  A cursor has your bind variable
values -- YOUR values.

Suppose one user fires that query, the plan will be placed into the shared pool
for all to use.  A CURSOR is a session thing -- you might (in your SINGLE
SESSION) have 500 CURSORS all pointing to that same piece of shared sql.

Just remember - a cursor is a SESSION thing, its your state, your values, your
result set.  the plan -- thats a SHARED thing, its the part of the shared pool
that all sessions can share.

2) cursor caching increases the amount of memory your session will consume.  It
is a tuning feature that trades increased RAM for significantly increased
performance.  I personally see no downside to it.

3) see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:2444907911913
...

      Rating: 5
Quick
      connections (web) will not benefit from cached cursors
      ?  January 28, 2002
Reviewer:  Ravi
       from NY,NY
Thanks for your quick response Tom .. I got my answers.

A followup question on cursor caching:
The components in our web servers(iis) make and break connections quickly. In
such cases, cursor caching will not be useful, right ?

          Followup:

depends.  do they parse and execute the same statement more then once in this
quick connection?  If so, they might benefit from this.

if they have code like:

   loop
      parse  "insert into t values ( ? )"
      bind
      execute
      close
   end

they would benefit (although that code SHOULD be fixed)

      Rating: 5
Shared SQLs and
      execution plan  January 29, 2002
Reviewer:
      Andrej  from Russia
When I have a very large shared pool and execution plan for my query is always
saved in shared pool what happens if I make very large insert in this table and
than analyze this table. Will Oracle change execution plan?

          Followup:

Yes, Oracle will change the plan -- it is all part of our automatic dependency
tracking, we'll flush the plans and redo them the next time they are executed.

      Rating: 4
so soft parse
      ALWAYS occurs???  January 29, 2002
Reviewer:
      A reader
Quoted from above:

-----The next step in the parse operation is to see if the statement we are
currently  parsing has already in fact been processed by some other session.  If
it has we may be in luck here, we can skip the next two steps in the process,
that of optimization and row source generation.  If we can skip these next two
steps in the process, we have done what is known as a Soft Parse-----

You mean there is ALWAYS soft parse????? I thought you can avoid that

Now I am confused again

          Followup:

if you parse -- you will either SOFT or HARD parse.  If you parse, you by
definition cannot skip the parse!!! you are doing the parse!!!

You can skip the parse by keeping cursors open.  Instead of coding:

   parse
   bind
   execute
   close

in a subroutine, do:

   if (firsttime)
      parse
   end if

   bind
   execute

You can skip the parse by using PLSQL.  PLSQL always caches cursors for us.  If
you have a procedure:

   procecedure p
   is
   begin
         insert into t values ....
         update t set ....
         delete from t .....
         select * into ... from t where ...
   end;

those four DML statements will be parsed ONCE per session -- no matter how many
times you call P in your session (all you have to do is make sure you parse the
call to P once and you are done with your job)

      Rating: 4
Session cursor
      cache to avoid soft parse....  January 30, 2002

Reviewer:  Mark J. Bobak  from Dearborn Heights, MI

If a statement is in the session cursor cache, it can
avoid a soft parse.  So, absolute best case, it's in the
shared pool (someone else hard parsed it), you execute
the SQL, you soft parse it, it goes to the session cursor
cache, subsequent executions of the same SQL by the SAME
SESSION, will not parse, hard or soft.  I believe you can
observe this in TKPROF parse counts.  (If a statement is
already in session cursor cache, and you turn trace on and
execute the same statement, TKPROF will report 0 parses.)

As always, correct me if I'm wrong, Tom.

Also, may I recommend Bjorn Engsig's excellent paper on
this stuff and how cursor_sharing relates.  It's at:

http://otn.oracle.com/deploy/performance/pdf/cursor.pdf
-Mark

          Followup:

it will not avoid the soft parse, it'll be a softer soft parse.

The soft parse count will still go up, see how the stats:

STAT...parse count (total)                     1015       1014         -1

show that -- we parsed 1000 times that statement in both.  We just parsed it
"softer" the second time.....

      Rating: 5
Two more
      question to parsing  January 30, 2002
Reviewer:
      A reader
Can using of public synonyms reduce the parsing?
If I have a very large shared pool, is it not easier to hard  reparse the small
sql statement as to locate it in memory?

          Followup:

Public synonyms will not reduce the parsing.

ONLY the application can reduce the parsing.

Public synonyms can actually make things more difficult.

As for the "is it not easier to hard reparse the small sql statement" -- the
answer is 100%, definitely NO in capital letters.  It is most definitely NOT
easier -- it is much much harder, time consuming AND it increases latching on
the shared pool and since latches are locks, locks are a serialization device,
serialization devices mean "one at a time", it (parsing) greatly reduces
concurrency, scalability and your chances for success.

      Rating: 4
stored
      outlines  January 30, 2002
Reviewer:  Olaf
       from Germany
Does Oracle make any sorts of parsing (hard or soft) if I use stored outlines?

          Followup:

Yes, 100%.

stored outlines is a hinting mechanism.  It works via rewriting the query with
hints.  The normal, same process takes place 100%.

Consider if you stored an outline that referenced an index.  You drop the index.
 The query still executes since the hint to use that index is just totally
ignored.

Stored outlines are hints...

      Rating: 5
When to close
      statement?  January 30, 2002
Reviewer:  scott
      watson  from Canada
Tom,

You mention that you should code routines in the following way to avoid soft
parses however, I was wondering where I should tell my developers to close their
statements.

So if they code their EJB as you suggest

in a subroutine, do:

   if (firsttime)
      parse
   end if

   bind
   execute

when would they close the statement.

Thanks.

          Followup:

Me -- i never close them ;)

If I have a subroutine that will be called over and over -- I would parse the
statement once, and keep it open. When we log off, it'll be closed.

Connection pools and other such stuff mess this up sometimes, it varies by
vendor.

      Rating: 5
so how can soft
      parse avoid  January 30, 2002
Reviewer:
      A reader
have test this

opened 3 sessions

first session:

CREATE OR REPLACE procedure temp
  as
  l_deptno number default 30;
 l_cnt number;
  begin
 select count(*)
 into l_cnt
  from emp
 where deptno=l_deptno;
end;
/

exec temp
exec temp
exec temp

Second session monitoring v$sesstat of first session:

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
parse time cpu                                                            0
parse time elapsed                                                        0
parse count (total)                                                     123
parse count (hard)                                                       33
execute count                                                           148

parse time cpu                                                            0
parse time elapsed                                                        0
parse count (total)                                                     124
parse count (hard)                                                       33
execute count                                                           150

parse time cpu                                                            0
parse time elapsed                                                        0
parse count (total)                                                     125
parse count (hard)                                                       33
execute count                                                           150

The parse count rises

Third session monitroing v$sql:

select sql_text, executions, parse_calls from v$sql where sql_text like
'%DEPTNO%';

SQL_TEXT    EXECUTIONS    PARSE_CALLS

SELECT COUNT(*)   FROM EMP  WHERE DEPTNO = :b1    1    1

SELECT COUNT(*)   FROM EMP  WHERE DEPTNO = :b1    2    1

SELECT COUNT(*)   FROM EMP  WHERE DEPTNO = :b1    3    1

so parse is soft parse I guess, now my question is how can I avoid that soft
parse

cheers

          Followup:

Yes, the parse count rises in order to parse:

exec temp
exec temp
exec temp

which is the same as:

begin temp; end;
begin temp; end;
begin temp; end;

the parsing you are seeing is sqlplus parsing that block of code each time.
Now, if you put 10 queries in the procedure temp -- you'll see the same parse
count increment -- one for each block -- not 11 or 10....

You are avoiding the parse on the select count(*).  In sqlplus -- you cannot
avoid that one parse, its just a "dumb" character mode environment.  It'll parse
(be it soft or hard) every string you submit.

      Rating: 5
soft and hard
      parse  January 30, 2002
Reviewer:  MUnzer
       from Reston, va
Tom:

1.  How do you make sure in application development code that I am using soft
parse im queries.

Do you use bind variables all the time?

also, if you have
select * from table where state='VA';
and
select * from table where state='CA';

would those be soft or hard parsed since paramter is different.

would you write this as

select * from table where state=:p_state;

and use owa_util.bind variables to run it ?

Thank you,

          Followup:

if you use bind variables -- you will be using shared sql.

You can use tools like SQL_TRACE+tkprof and statspack to see that you are (if
you have my book -- read chapter 10)

The queries:

select * from table where state='VA';
and
select * from table where state='CA';

would be hard parsed the first time they are parsed -- softparsed the 2cnd
through N'th time. Just like every other query in the database.  I would most
likely use a bind variable here if this query was to be run for each state and
the state was a parameter to a procedure.

I don't know if I would use owa_util to bind it -- i would only do that if i was
using listprint or cellsprint found in the owa_util package.

      Rating:
      5
  February 14, 2002
Reviewer:
      A reader
Thank you very much

      Rating: 5
softer soft
      parse and soft parse, difference  December 02, 2002

Reviewer:  A reader
Hi

Just to verify if I understand correctly!

I understand that soft parse does the following

1. syntax check
2. semantics check
3. go to shared pool and look if the SQL is there

softer soft parse does

1. syntax check
2. semantics check
3. if SQL is in my session cache then execute otherwise look in shared pool

From your examples it seems that the part of looking in shared pool uses library
cache latch and shared pool latch right?

Am I correct in any sense?

          Followup:

softer soft parse does #3, it has to latch in order to tell the database "hey,
i'm going to use this again"

      Rating: 5
what if sql is
      flushed from shared pool  December 02, 2002

Reviewer:  A reader
Hi

so in softer soft parse

3. if SQL is in my session cache then execute otherwise look in shared pool

is used

so if this what it does what happens when it tried to execute something which
have been flushed from shared pool? (something inexistent)

          Followup:

that is why you need the latch to the shared pool in order to tell the database
"we want to execute that statement again" (and make sure it is still there).  If
the sql isn't there anymore, we'll not find it and you'll be doing a hard parse.

      Rating: 5
then what step
      does session_cached_cursor save??  December 02, 2002

Reviewer:  A reader
Hi

Then I dont see what step does session_cached_cursors save...?

I mean all steps are the same when use that parameter or not :-?

          Followup:

No, in a soft parse you have

1. syntax check
2. semantics check
3. go to shared pool and look if the SQL is there

in a softer soft parse you have

3. go to shared pool and look if the SQL is there

re-read the first "review" to this question and you'll see NUMERICALLY the
benefits (in reduced latching, increased performance)

      Rating: 5
softer soft
      parse  January 09, 2003
Reviewer:  Sam
       from San Mateo, CA
Tom,

You mentioned:

"No, in a soft parse you have

1. syntax check
2. semantics check
3. go to shared pool and look if the SQL is there

in a softer soft parse you have

3. go to shared pool and look if the SQL is there"

I have a question on which latch gets are saved when doing the softer soft
parse.  Is avoiding step 2 the main reason why there are fewer latch gets?  (I
think you need a latch to get the lock to load and lock the object definition
during parsing, and doesn't look like step 1 requires any latch).  Also, is the
cost of step 3 the same in soft parse vs softer soft parse?  Or does the cached
cursor hold some pointer to the library cache so it does not need to search the
library cache? (it just needs to see if the location pointed to is still valid)

          Followup:

mostly 2.

the cached cursor holds a pointer, it is a little more efficient.

You can use runstats as I did above to see what you save.

      Rating: 5
Re-generating
      Plan After Analyze  January 19, 2003
Reviewer:
      Jerry  from Washington, DC USA
If I have a cached cursor, and then analyze the underlying table, it seems to
re-generate a new plan.  That's good news, but not what I expected.  Have I
modeled this correctly?

SQL> set echo on
SQL> set feedback on
SQL> set autotrace traceonly
SQL>
SQL> drop table t;

Table dropped.

SQL>
SQL> create table t nologging as select * from all_objects;

Table created.

SQL>
SQL> create index t_idx on t(object_id);

Index created.

SQL>
SQL> @call_sql
SQL> select t1.object_name, t2.object_name
  2    from t t1, t t2
  3   where t1.object_id = t2.object_id
  4     and t1.owner = 'SYS';

13480 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T'
   2    1     NESTED LOOPS
   3    2       TABLE ACCESS (FULL) OF 'T'
   4    2       INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      33740  consistent gets
        403  physical reads
          0  redo size
     761191  bytes sent via SQL*Net to client
      10381  bytes received via SQL*Net from client
        900  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      13480  rows processed

SQL>
SQL>
SQL>
SQL> analyze TABLE t compute statistics
  2  FOR TABLE
  3  FOR ALL INDEXES
  4  FOR ALL indexed COLUMNS;

Table analyzed.

SQL>
SQL> @call_sql
SQL> select t1.object_name, t2.object_name
  2    from t t1, t t2
  3   where t1.object_id = t2.object_id
  4     and t1.owner = 'SYS';

13480 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=86 Card=293 Bytes=17287)
   1    0   HASH JOIN (Cost=86 Card=293 Bytes=17287)
   2    1     TABLE ACCESS (FULL) OF 'T' (Cost=42 Card=293 Bytes=11134)
   3    1     TABLE ACCESS (FULL) OF 'T' (Cost=42 Card=29299 Bytes=615279)

Statistics
----------------------------------------------------------
         42  recursive calls
          0  db block gets
       1701  consistent gets
        239  physical reads
          0  redo size
     761191  bytes sent via SQL*Net to client
      10381  bytes received via SQL*Net from client
        900  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      13480  rows processed

          Followup:

that is exactly what is supposed to happen.  there is a seriously complex and
detailed dependency mechanism inside of Oracle.  It takes care of invalidations
like this automagically.

consider this analyze to be like "drop index I" -- you would expect the queries
against that table with I on it to be "fixed".  the analyze is no different.

      Rating: 5
Can you
      Substantiate  January 19, 2003
Reviewer:
      Lakshmi Narasimhan R  from Dubai
Hi Tom

"Public synonyms can actually make things more difficult."

Can you explain it.

Atleast i am seeing in maximum projects, people are using Public synonym only
for all the tables and privilages given to users according to their needs.

Is it a wrong way of designing or public synonyms are difficult only in few
scenario's.

Your explanation will be appreciated.

Reg

          Followup:

tell me, how many EMP public synonyms can you have?

it is a namespace issue (there is one public namespace).

how many times I've seen "oh, i didn't know that name was taken, our scripts
just drop and create that public synonym -- sorry about that".

you should strive to use as few public synonyms as possible.

      Rating: 4
Parse Count in
      Tkprof  April 04, 2003
Reviewer:  Sushanta
       from INDIA
This is pseudo code of a program in Developed in PRO-C

# ------------------------------------------------------Check the

         EXEC SQL DECLARE c_parse1 CURSOR FOR
         /* Outer Cursor */
         SELECT abc1.a
          FROM cc_abc_table abc1
         WHERE ccp.e IN (1, 3)
           EXEC SQL OPEN c_parse1; <<Total Number of Records in the is
        for (;;)
        {
          EXEC SQL FETCH c_parse1 INTO :cc_abc_t indicator :i_cc_abc_t;
          get_abc_data(messagehandle,i_index,runid)
          {
             get_abcdet_segment(messagehandle,i_po_index,runid)
             {
              EXEC SQL DECLARE c_parse2 CURSOR FOR
              /* Inner Cursor */
              SELECT count(*)
                FROM cc_xyz_table xyz1
               WHERE xyz1.a_fk      =  :t_po_number
                 EXEC SQL OPEN c_parse2;
                 for (;;)
                 {
                      EXEC SQL FETCH c_parse2 INTO :cc_podet_t
indicator:i_cc_podet_t;
                 }
                 EXEC SQL CLOSE c_parse2;
             }
          }
        }

# -----------------------------------------------------Tkprof of the  Query >>

SELECT abc1.a
  FROM cc_abc_table abc1
 WHERE ccp.e IN (1, 3)

call     count       cpu    elapsed       disk      query    current   rows
------- ------  -------- ---------- ---------- ---------- ----------
----------
Parse        1      0.00       0.00          0          0          0   0
Execute      1      0.00       0.00          0          0          0   0
Fetch       44      0.03       0.05        268        330          5   300
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       46      0.03       0.05        268        330          5   300
--------------------------------------------------------------------------------

SELECT count(*)
  FROM cc_xyz_table xyz1
 WHERE xyz1.a_fk      =  :b0

call     count       cpu    elapsed       disk      query    current  rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse      234      0.05       0.07          0          0          0  0
Execute    300      0.01       0.02          0          0          0  0
Fetch      523      0.02       0.01          8       2385          0  385
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1057      0.08       0.10          8       2385          0  385

The Outer Cursor
SELECT abc1.a
  FROM cc_abc_table abc1
 WHERE ccp.e IN (1, 3)

total Number of records are fetch is 300.

So the Inner Cursor
is running 300 times.
Hence the Inner Cursor
SELECT count(*)
  FROM cc_xyz_table xyz1
 WHERE xyz1.a_fk =  :t_po_number

is Openning and Closing 300 times .

But in the Tkprof File it is observed that the parse count is 234 (How ??)
It should be 1 -Hard Parsing (if the SQL is Not found in the library cache)  and
299 (Soft Parsing)
, however it is showing 234 .

Can u clarify on this

Thanks

          Followup:

what are your hold cursor and release cursor settings on the proc compile line.

and hopefully that is a "fake" example -- that you really do not do that in real
life that you know that the above should just be a single query with whatever
logic is in get_abcdet_segment(messagehandle,i_po_index,runid) (which probably
has its own sql) rolled right in.

Actually the parse count, under optimal circumstances, would be 1 in all cases!

      Rating: 3
As
      above  April 07, 2003
Reviewer:  Sushanta
       from INDIA
Hi,

The Hold Cursor and Release Cursor setting in PRO-C is default in our setting .

Option name   :  release_cursor=boolean
Current value :  no
Restrictions  :  yes, no, true, false
Description   :  Control release of cursors from cursor cache

  This is a part of the Programme just i have mention for reference
  inside these there are lot of processing are going on.
  Now i remove the Function
  get_abcdet_segment(messagehandle,i_po_index,runid) which is confusing to you

  ------------------------------------------------------------------------------
         EXEC SQL DECLARE c_parse1 CURSOR FOR
         SELECT abc1.a
          FROM cc_abc_table abc1
         WHERE ccp.e IN (1, 3)
           EXEC SQL OPEN c_parse1;
         for (;;)
         {
           EXEC SQL FETCH c_parse1 INTO :cc_abc_t indicator :i_cc_abc_t;
           EXEC SQL DECLARE c_parse2 CURSOR FOR
           SELECT count(*)
             FROM cc_xyz_table xyz1
            WHERE xyz1.a_fk      =  :t_po_number
           EXEC SQL OPEN c_parse2;
           for (;;)
           {
             EXEC SQL FETCH c_parse2 INTO :cc_podet_t indicator:i_cc_podet_t;
           }
           EXEC SQL CLOSE c_parse2;
          }
  ------------------------------------------------------------------------------

Could u tell us where is the Problem and why the Parse count is giving such a
odd number.

Thanks

          Followup:

that function is not confusing me -- I'm telling you is should not be there --
you are doing what we call "a join".  You are doing it "manually" -- eg: wrong.
My point is there should be one query here -- not queries in loops in loops in
loops.

The default values for those parameters varies by OS.  You have release cursor
"no" apparently -- meaning that to a degree - cursors will be cached if there is
space.  Apparently - you SOMETIMES have space and sometimes NOT.  60 some odd
times, we reused an already opened cursor -- the other 234 times we did not.
Nothing strange here -- other then the fact that you are doing procedurally what
rightly should be done purely in SQL.

      Rating: 3
Same as
      above  April 07, 2003
Reviewer:  Sushanta
       from INDIA
Also if u comment this statement
EXEC SQL CLOSE c_parse2;

the Parse Count is giving as 1

          Followup:

now that is almost what you want.  I would want parse count = 0 for this one as
it should just be a single query....

      Rating: 3
Same as above
        April 08, 2003
Reviewer:  Sushanta
       from INDIA
Hi Tom ,
  I am not yet get the answar of my question.
  My question is that why the Parse Count is shows such a odd number (234),
  And when i Comment the CLOSE c_parse2 the prase count is 1.
  I also mention that it is a very large programme about 12000 lines
  and it also not easy to interpret the SQL which i have mention in a single
statement,
  because in between the two cursor there are lot of processing .

  So the main question is now
  when i comment the CLOSE c_parse2 then the parse count in 1
  and
  when i decommented the CLOSE c_parse2 then the parse count is 234

  Why so , and it is not very realistic to approach the solution.

  can u clarify on this

      Rating:
      5
  May 21, 2003
Reviewer:
      A reader
" Hard parses,
they are very CPU intensive as well as a point of contention (serialization).
"

What do you mean 'contention (serialization)'? Oracle only does one a time as
processing the hard parses?
Thank you.

          Followup:

we latch the shared pool among other shared structures.

latches are serialization devices
serialization devices make things run -- well -- serially
things that run serially introduce contention, don't scale as well as things
that run concurrently.

when we parse, we update shared data structures.  These shared data structures
in the SGA are protected by latches.  It is not that we parse one at a time, but
rather that while parsing, we update lots of stuff in the SGA, read lots of
stuff in the SGA -- but can only do that serially -- to avoid two people from
updating a linked list at the same time for example.

      Rating: 5
Hard Parses and
      Parses in Statspack  May 21, 2003
Reviewer:
      Vivek Sharma  from Bombay, India
Dear Tom,

I am going thru your book Expert One-on-One and have gained a very good
knowledge of Oracle Internals. On Page 480, Tuning Stategies using Statspack, in
the Load Profile, the Parses per second is 16.44 and Hard Parses is 0.17.
Then How did you calculate that every 6 seconds you are parsing some bit of sql
for the very first time. Also on Page 481 for Non Parse CPU you have mentioned a
formula (100 * 1-(PARSE_CPU/TOT_CPU),2). From where do I take the values of
Parse CPU and TOT_CPU. Can  u please explain.

Thanks for your hard work.
Vivek Sharma

          Followup:

ops$tkyte@ORA920> select 1/.17 from dual;

     1/.17
----------
5.88235294

if I hard parse 0.17 of something every second, it takes about 6 seconds to hard
parse a whole thing

statspack supplies the parse_cpu and tot_cpu, it gets that from v$sysstat.

      Rating: 4
Query parsing
      in PL/SQL  May 22, 2003
Reviewer:  Tony
       from INDIA
What about the DML statmemts and quries in packages/proceduese? Do they get
parsed during compilation or during each and every execution? What is done in
compilation?

          Followup:

that is one of the massive benefits of stored plsql -- the statements are parsed
once per SESSION, not once per execution.

during compilation, the statements are not parsed into the shared pool and
optimized or anything, you need to actually execute the statement by running the
procedure to have that happen.

      Rating: 4
Is too much of
      soft parse count an over kill?  May 22, 2003

Reviewer:  Tony  from India
s too much of soft parse count an over kill?. If yes, How to reduce soft parse
count?

          Followup:

you should parse a statement once per session...

to reduce the soft parse count -- stop parsing it, you control it, just don't do
it

      Rating: 5
Soft Parse - No
      Soft Parse  June 03, 2003
Reviewer:  Bala
       from Kuala Lumpur
1.Your answer to the first follow up says
"They will be cached in the shared pool -- but your session will have to find
them there.  Session Cached Cursors can remove the need to have to "find" them."
Find what? the cursor or shared SQL
What SESSION_CACHED_CURSORS is caching for me? If it is caching for me my cursor
which according to you
"is
more then just the plan and such from the shared pool, a cursor is a "state" as
well.  A cursor is your data -- YOUR data.  A cursor has your bind variable
values -- YOUR values."
where is the plan now, is it still in the shared pool or in my UGA along with my
Cursor(Session state and stuff)?
2. A softer soft parse would avoid
"1. syntax check
2. semantics check"

Does SESSION_CACHED_CURSOR avoid
"3. go to shared pool and look if the SQL is there"
making the SQL available in my UGA and thus lessening the number of latches -
serialization.....

It's really confusing Tom....

          Followup:

1) it makes it a softer soft parse.  It is helping you get your cursor state
instantiated faster, with less latches.

3) no.  it lessens the latching required to do so as it set up a dependency that
would be invalidated if something happened to the cached sql - but it still
needs to see if it is OK.

the example above should help you in understanding.  it shows there is still
latching, just less of it.

      Rating: 4
Too many Parses
      resulting in lesser Database Connections  June 04, 2003

Reviewer:  Vivek Sharma  from Bombay, India

Dear Tom,

I was going thru your book Expert One on One in which u had mentioned that too
many parses takes too much resources resulting in lesser user connections. I
totally agree with your points as I have faced this problem in one of my
databases but I am eager to know the exact reason for this. How is a new user
connection related to it. Can U please explain in brief.

Thanks and Regards
Vivek Sharma

          Followup:

I said you lose the ability to SCALE.

if your machine can do X units of work.

And you make each session to X/100 units of work.
You can have 100 sessions doing work.

If you stop parsing so much and now you do X/200 units of work per user...
You can have 200 sessions doing work.

The less you do, the more you can do.

      Rating: 5
X Unit of
      Work  June 04, 2003
Reviewer:  Vivek Sharma
       from Bombay, India
Dear Tom,

Sorry, Not much Clear.

Please check whether I am correct.

Suppose My machine has the capability of doing 10000 unit of work.

If my users do not parse too much and does only 100 units then 10000/100 = 100
Sessions will be doing the work.
Whereas, If I parse too much and the sessions do about 200 unit of works then
10000/200 = 50 Sessions will be doing the work. Hence I lose the scalability.

Please correct me if I am wrong and apologize.

Thanks
Vivek Sharma

          Followup:

yes, that is my point.

the LESS you do, the MORE you can do.

      Rating: 4
Optimization
      During Parsing  June 04, 2003
Reviewer:
      Robert  from PA
Tom,
In this white paper from Oracle Corp
"Efficient use of bind variables, cursor-sharing and related cursor parameters"
(8/2001).....
bottom of page 4 in fine print it notes:

"In some cases, optimization may take place during parsing, but this is
irrelevent to the discussion in this WP"

Can you elaborate on this abit or point me to where (asktom or EOOOO) you
already done so ?
thanks

          Followup:

hard parse = optimize
soft parse = been there, done that, use the plan from the shared pool.

they are referencing the difference between hard and soft parses.

      Rating: 4
What happens
      during compilation?  July 04, 2003
Reviewer:
      Tony  from INDIA
You have mentioned that parsing is done even for stored procedures during first
execution. Then, what exactly happens during compilation? I've been thinking
that parsing of stored procedure is done during compilation itself. Please
clarify

          Followup:

the parse of the stored procedure is done during compilation.  this generates
pcode, an executable if you will.

The SQL contained therein is executed by the plsql engine at runtime and is
parsed just like any and all sql is when it first hits the shared pool.

      Rating: 4
What's the
      title of the book and if possible ISBN  August 20, 2003

Reviewer:  Mohammd Saleem  from PA,USA
Tom,
    I  read your expert reviews/suggestioons/explonations from time to time.
You always refer to  my book, hardly (The complete title).

     I am a DBA. I know the basic concepts decently well. I want to be an
proficient in performance tuing and Backup and recovery, and 8i to 9i
differences. Can you tell me if you have written any books on them.
    Please send me the titles and ISBNs.

          Followup:

see my home page -- asktom.oracle.com.

nothing in backup and recovery (the oracle docs are actually pretty good there)

      Rating: 4
What's the
      title of the book and if possible ISBN  August 20, 2003

Reviewer:  Mohammd Saleem  from PA,USA
Tom,
    I  read your expert reviews/suggestioons/explonations from time to time.
You always refer to  my book, hardly (The complete title).

     I am a DBA. I know the basic concepts decently well. I want to be an
proficient in performance tuing and Backup and recovery, and 8i to 9i
differences. Can you tell me if you have written any books on them.
    Please send me the titles and ISBNs.

      Rating: 5
How much
      memory?  October 13, 2003
Reviewer:  Shankar
       from Houston, TX
Tom, If I have open_cursors=100, how much memory oracle needs to allocate in the
pga for a session for each cursor?

(2) Also, what is the difference between open_cursors and
session_cached_cursors? Thanks.

          Followup:

the same as if you have it at 64 :)

or 5000

we allocate the cursor array (pointers) 64 at a time.

As you use them - we allocate a cursor structure to be pointed to.

so, if you set to 100 and never use more then 40, it is as if it were set to 64
(or 63 or .....)

It is a function of how many you actually open and since each one could be a
different size -- it is not possible to say how much 40 open ones would be
(short of getting your 40 together -- measuring the pga use, opening them and
fetching a bit, and then measuring again)

      Rating: 4
Syntax Check of
      a SQL  October 26, 2003
Reviewer:  Vivek
      Sharma  from Bombay, India
Dear Tom,

I was asked a question by one of my junior when I was explaining him the
difference between Soft Parse and Hard Parse. He asked me that where is the
syntax check stored i.e. when I execute a query
select * form <tablename> it gives me syntax error as
         *
ERROR at line 1:
ORA-00923: FROM keyword not found where expected

But where are these syntax stored ? are they in the dictionary cache of the
shared pool. If this query was entered properly, it would have checked it in the
library cache for the exact match. But from where does it checks that the syntax
is unrecognizable by oracle.

Thanks and Regards
Vivek Sharma

          Followup:

syntax checking is part of parsing the query into a data structure.  It is part
of the procedural code that is the query parser itself.  It has a set of rules,
a grammar, that describes valid SQL.  The string is literally parsed using that
grammar into a data structure.

It is just procedural code.

      Rating: 5
how to reduce
      soft parses  November 14, 2003
Reviewer:
      A reader
Hi

My execute to parse ratio is pretty low, around 25%, there are high number of
parses and high number of executions. Few hard parses but very high number of
soft and softer parses, how can we improve in this case? It is a Forms
application.

I have also another Forms Application which uses extensively stored packages
(the one before does not use stored PL/SQL) and execute to parse ratio is 95%...
Is this the only way to increase this ratio...? Using stored PL/SQL stuffs?

          Followup:

a forms application should have excessively HIGH execute/parse ratios unless the
forms application uses and abuses dynamic sql a ton

      Rating: 4
'Dynamic' forms
      SQL  November 16, 2003
Reviewer:  Gary
       from Sydney, Aus
The issue I seem to be seeing more often in Forms programs is people using the
facility to set the where clause dynamically for a base-table block, rather than
using a pre-query trigger (which is becoming a lost art). And the hard-coding is
more like

if :PARAMETER.COL1 IS NOT NULL THEN
  whereclause := whereclause || ' AND col1 LIKE ''%' ||
             :PARAMETER.col1 || '%''';
end if;

Maybe worth scanning the code for default_where

          Followup:

they should be:

if :parameter.col1 is not null then
   whereclause := whereclause ||
                 ' and col1 like ''%''|| :parameter.col1 || ''%'' ';
end if;

so the resulting string looks like:

 and c1 like '%' || :block.item || '%'
 and c2 = :anotherblock.item
 and c3 > :yetanotherBlock.item

you can reference binds in the default where.

      Rating: 4
What about
      this?  November 19, 2003
Reviewer:
      A reader
Tom,
If the statement is soft parsed, and bind variables are resolved afterwards,
will it not mis-lead the optimizer? especially whenever there is non uniform
distribution of data. For example, there could be the query :- select * from
customer where first_name = :first_name. Sometimes the first_names are unique
and one row (very less) is returned, but some times lots of rows are returned.
If there is index on name, and optimizer used index first time, but for second
query, with soft parse, index scan will be slower, or I am missing something.
Thanks

          Followup:

the parse/optimization is deferred to the open, after the bind.

the first hard parse will determine what the plan is for the rest of the soft
parses.

      Rating: 3
Totally lost as
      to massive execute time  November 20, 2003

Reviewer:  Mike  from England
This is one of the worst SQL's I've ever seen, may we all forgive the original
developer he probably knew not what he did...

Aside from the lack of Bind Variables, the fact that it asks to use an index on
a table and then uses upper on the column so that the index can't be used as it
intended, sticks a PL/SQL function in the where clause and outer joins like it
was the latest fashion....

In the tkprof section on the timings all the time is spent on the execute
phase... the PL/SQL function does not do any DML, just some queries and there
are no triggers that would fire on the selects (although there are triggers)

I don't understand why so much time is spent on the exec phase?  I thought the
lack of binds would hit the Parse phase is it the PL/SQL function that is
causing it?

If this isn't related enough to the above questions then please ignore it, I did
a search for "High executes" and tkprof and this was the only thread that came
back.

Thanks,

Mike.

select data_section
from
 ( select  /*+ index (NIM_DAT_ENGINEERING_REQS) index (nim_dat_listof_users
  lus_dat_i_pi1) */
ERE_REQUIREMENT_ID||'^U^V'||ERE_expire_yn||'^U^V'||ERE_status

_id||'^U^V'||set1.sst_name||'^U^V'||ERE_class_id||'^U^V'||set2.scl_name||'^U^V'||
ER

E_NAME||'^U^V'||ERE_GEN_NAME||'^U^V'||set5.lva_display_value||'^U^V'||set6.pro_na

  me||'^U^V'||set7.lva_display_value||'^U^V'||set8.lva_display_value
data_section,
  omx_oam_utl_03.check_access(ERE_profile_id,ERE_access_id,
  spe_update_access_method_id,cast(multiset(select lui_access_id,lui_user_id
  from nim_Dat_listof_users_items  where lui_list_id (+) = lus_list_id)  as
  omx_t_id_rec_tab),:fineg_flag,:user_id) check_ok  from
  NIM_DAT_ENGINEERING_REQS,nim_cfg_source_statuses set1,
  nim_cfg_source_classes set2,nim_cfg_list_values set5,nim_sec_profiles set6,
  nim_cfg_list_values set7,nim_cfg_list_values set8 ,nim_dat_listof_users,
  nim_cfg_source_peripherals   where 1=1 and set1.sst_source_id(+) = 53 and
  set1.sst_status_id(+) = ERE_STATUS_ID and set2.scl_source_id(+) = 53 and
  set2.scl_class_id(+)    = ERE_CLASS_ID and  upper(ERE_NAME) like
  upper('10053_0%') and set5.lva_list_id(+) = 97 and set5.lva_storage_value(+)
   = ERE_GEN_REGION and set6.pro_profile_id(+) = ERE_PROFILE_ID and
  set7.lva_list_id(+) = 121 and set7.lva_storage_value(+) = ERE_GEN_TECH_TYPE
  and set8.lva_list_id(+) = 1181 and set8.lva_storage_value(+) =
  ERE_GEN_PROGRAMME_STATUS and lus_owner_id (+) = ERE_REQUIREMENT_ID and
  upper(lus_name) = upper(spe_name)  and spe_source_id = scl_source_id  and
  spe_class_id in (scl_class_id,0)  and lus_security_yn (+) = 'Y'  and
  ERE_class_id IN (0,54)) where check_ok = 'Y'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1     11.46      11.52          1          6          0           0
Fetch        1      0.10       0.10        839        856         12           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3     11.57      11.63        840        862         12           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 22     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  FILTER
      2   FILTER
      1    NESTED LOOPS OUTER
      2     HASH JOIN OUTER
      1      HASH JOIN
     19       TABLE ACCESS FULL NIM_CFG_SOURCE_PERIPHERALS
      1       HASH JOIN OUTER
      1        HASH JOIN OUTER
      1         HASH JOIN OUTER
      1          HASH JOIN OUTER
      1           HASH JOIN OUTER
      1            TABLE ACCESS BY INDEX ROWID NIM_DAT_ENGINEERING_REQS
  29655             INDEX FULL SCAN (object id 19762)
      2            INDEX RANGE SCAN (object id 19583)
      6           TABLE ACCESS FULL NIM_CFG_SOURCE_STATUSES
      2          TABLE ACCESS BY INDEX ROWID NIM_CFG_LIST_VALUES
      3           INDEX RANGE SCAN (object id 19546)
      3         TABLE ACCESS BY INDEX ROWID NIM_CFG_LIST_VALUES
      4          INDEX RANGE SCAN (object id 19546)
      4        TABLE ACCESS BY INDEX ROWID NIM_CFG_LIST_VALUES
      5         INDEX RANGE SCAN (object id 19546)
     85      TABLE ACCESS FULL NIM_SEC_PROFILES
      1     TABLE ACCESS BY INDEX ROWID NIM_DAT_LISTOF_USERS
      2      INDEX RANGE SCAN (object id 19780)
      1   INDEX RANGE SCAN (object id 19784)

********************************************************************************

          Followup:

most queries spend their time in the fetch phase.

some during the execute phase.  You are seeing the time it took to build the
result set.

      Rating: 4
Soft/Hard vs.
      Bind vars  November 21, 2003
Reviewer:  Mark
       from San Francisco, CA USA
In one of your responses way back in this thread you state in regard to the
following SQL -

select * from table where state='VA';
and
select * from table where state='CA';

"would be hard parsed the first time they are parsed -- softparsed the 2cnd
through N'th time. Just like every other query in the database.  I would most
likely use a bind variable here if this query was to be run for each state and
the state was a parameter to a procedure."

I thought bind variables were irrelevent with stored procedure SQL (except when
expressly written as dynamic SQL). If so, what did you mean when you said you'd
use bind variables?

Also - I was surprised to see (via v$sqlarea) that whitespace was not ignored,
so that two statements which are really the same were stored separately in the
shared pool:
SELECT SYSDATE FROM DUAL;
vs.
SELECT   SYSDATE FROM DUAL;

You comment?
Thanks.

          Followup:

with a stored procedure, sure

with a PROCEDURE (VB, Java, C, C++, etc etc etc) -- no, not unless the coder
coded it properly.

whitespace, case, EVERYTHING - the queries must be 100% the same.

      Rating: 5
What affects
      the parse time?  November 25, 2003
Reviewer:
      Jens  from Germany
Hi Tom,

I have a cursor for loop where I loop through the resultset of "difficult"
query. This program was never changed for months, it runs every month in an
acceptable time.

But suddenly the never changed query takes a lot of parse time:

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        7    786.90     790.33          0          0          0           0
Execute      8      0.19       0.25          0          4        139         122
Fetch       15      8.25      11.57       4998      11655        667         153
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       30    795.34     802.15       4998      11659        806         275

If I try it with the RBO it's ok, but I don't want to change the code. In the
meantime the DBA changed two parameter:

log_buffer              from 524288 to 2048000
log_checkpoint_timeout    from 0 to 1800

But I could not see any correlation between these parameters and the optimizer
behaviour.

What other settings could have an impact on the parse-time? I know
OPTIMIZER_MAX_PERMUTATIONS, but this parameter was left unchanged to the default
of 80.0000 (if i set it to a very low value, the performance is also acceptable)

Bye,

Jens

          Followup:

what does the rest of the tkprof look like (how many misses in library cache)

why is this parsed more then one time????????

something else changed on this system -- a table altered, indexes mucked with --
what is different here?

what is the plan, what was the plan?

      Rating: 5
Parsing Stored
      Procedure  November 28, 2003
Reviewer:
      A reader
Hi Tom,

Is parsing of stored procedures much slower than normal SQL statements for
example INSERT?

I am using ODP.NET in my application right now and one of its current limitation
is that it cannot reuse cursors. So the parse/execution ratio is always 1 to 1.

Begin PKG_TXNS.CREATE_TXN(:v0, :v1, :v2, :v3, :v4, :v5, :v6, :v7); End;

call     count       cpu    elapsed       disk      query    current        rows

------- ------  -------- ---------- ---------- ---------- ----------  ----------

Parse     1932      0.00      14.78          0          0          0           0
Execute   1932      0.00       0.65          0          0          0        1932
Fetch        0      0.00       0.00          0          0          0           0

INSERT INTO REVERSINGTXNS                                               ( TXNID,
ORIGINALTXNID  )   VALUES( :"SYS_B_0", :"SYS_B_1"  )

call     count       cpu    elapsed       disk      query    current        rows

------- ------  -------- ---------- ---------- ---------- ----------  ----------

Parse      924      0.00       0.09          0          0          0           0
Execute    924      0.00       0.24         15         22      11237         924
Fetch        0      0.00       0.00          0          0          0           0

The stored procedure contains only a simple insert statement. What makes it
almost 100 times slower than direct SQL statements?

          Followup:

use session_cached_cursors to offset this.

if that is just "one" of its limitations -- well, gotta say using MS
technologies is a wonderful thing isn't it.

but I do not see the wide disparity you do -- no:

drop table t;
create table t ( a number, b number, c number );

create or replace package demo_pkg
as
    procedure foo( a in number, b in number, c in number );
end;
/
create or replace package body demo_pkg
as
    procedure foo( a in number, b in number, c in number )
    is
    begin
        insert into t values ( a, b, c );
    end;
end;
/

create or replace procedure p1
as
begin
    for i in 1 .. 2000
    loop
        execute immediate 'begin demo_pkg.foo( :x, :y, :z ); end;'
        using  i, i, i;
    end loop;
end;
/

create or replace procedure p2
as
begin
    for i in 1 .. 2000
    loop
        execute immediate 'insert into t values ( :x, :y, :z )'
        using  i, i, i;
    end loop;
end;
/
@trace
exec p1
exec p2

begin demo_pkg.foo( :x, :y, :z ); end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse     2000      0.49       0.60          0          0          0           0
Execute   2000      1.19       1.55          0          1         20        2000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     4000      1.68       2.16          0          1         20        2000

********************************************************************************
insert into t values ( :x, :y, :z )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse     2000      0.23       0.21          0          0          0           0
Execute   2000      1.01       0.99          0       2013       6137        2000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     4000      1.24       1.21          0       2013       6137        2000

November 28, 2003 Kevin -- Thanks for the question regarding
      "Formatting output of DESC", version 8.1.7.4.0

      You Asked
Hi Tom,  I saw in one of your books how to format the AUTOTRACE output.  Maybe I
missed it, but I didn't see how to format the output of the Describe command.
If I set SQL*Plus linesize to say 1000 because I am looking at wide tables, then
the Name and Type columns of the describe command widen correspondingly such
that the Length and Type information is way off to the right.  This is quite
annoying.  Is there a way to stop this so that describe output is always the
same width (say 80 bytes), or should I just use an @desc sql script and forget
about it.  Thanks.

      and we said...
describe is driven 100% off of the linesize

you would need to set linesize NNNN in order to control it.

Me, I use @desc normally because I like to see indexes:

set verify off
set linesize 72
set pagesize 9999
set feedback off

alter session set cursor_sharing=force;

Prompt Datatypes for Table &1

column data_type format a20
column column_name heading "Column Name"
column data_type   heading "Data|Type"
column data_length heading "Data|Length" format a10
column nullable    heading "Nullable" format a8

select column_name,
       data_type,
       substr(
       decode( data_type, 'NUMBER',
               decode( data_precision, NULL, NULL,
                '('||data_precision||','||data_scale||')' ),
                   data_length),
              1,11) data_length,
       decode( nullable, 'Y', 'null', 'not null' ) nullable
from all_tab_columns
where owner = user
  and table_name = upper('&1')
order by column_id
/

prompt
prompt
Prompt Indexes on &1
column index_name heading "Index|Name"
column Uniqueness heading "Is|Unique" format a6
column columns format a32 word_wrapped

select substr(a.index_name,1,30) index_name,
       decode(a.uniqueness,'UNIQUE','Yes','No') uniqueness,
max(decode( b.column_position,  1, substr(b.column_name,1,30),
NULL )) ||
max(decode( b.column_position,  2, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position,  3, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position,  4, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position,  5, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position,  6, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position,  7, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position,  8, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position,  9, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position, 10, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position, 11, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position, 12, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position, 13, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position, 14, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position, 15, ', '||
substr(b.column_name,1,30), NULL )) ||
max(decode( b.column_position, 16, ', '||
substr(b.column_name,1,30), NULL )) columns
from all_indexes a, all_ind_columns b
where a.owner = user
and a.table_name = upper('&1')
and b.table_name = a.table_name
and b.table_owner = a.owner
and a.index_name = b.index_name
group by substr(a.index_name,1,30), a.uniqueness
/

prompt
prompt
prompt Triggers on &1
set long 5000
select trigger_name, trigger_type,
       triggering_event, trigger_body
from user_triggers where table_name = upper('&1')
/
alter session set cursor_sharing=exact;
set verify on
set linesize 131
set feedback on

Review &
      Followup

      Rating: 5
What about
      constraints?  November 28, 2003
Reviewer:
      Marcio  from br
How do you inspect constraints ?

          Followup:

actually, i don't think I do really... when and if I need to I query
user_constraints

      Rating: 5
Exactly what I
      was looking for thanks again  December 01, 2003

Reviewer:  A reader
thanks again Tom

      Rating: 5
Thanks, so
      much, I updated it, hope you don't mind  December 01, 2003

Reviewer:  Kevin  from Connecticut USA
Thanks, this was great.  We do lots of stuff here with views and synonyms and
multiple users, so I updated the code you supplied to handle: owner.object_name
syntax, and translate private synonyms to their root view/table.  Like you I
have my own "pretty my output" setup script.  If you think this update to your
code might cause problems let me know.  I really like your use of cursor_sharing
here.

There are still at least two potential updates possible:  1) handle public
synonyms
2) handle remote objects

Maybe somebody out there could add these; then we would really have a
co-operative thing going on.

usage is: @desc emp
          @desc scott.emp

Thanks again Tom.

Kevin.

-- my "pretty my output" and setup my sqlplus environment
-- to each his own
@bequiet

set feedback off
alter session set cursor_sharing=force;

-- recognize owner.object_name syntax if used

variable owner_v varchar2(30)
variable tablename_v varchar2(30)

begin
   if instr('&&1','.') > 0 then
      :owner_v := upper(substr('&&1',1,instr('&&1','.')-1));
      :tablename_v  := upper(substr('&&1',instr('&&1','.')+1));
   else
      :owner_v := user;
      :tablename_v := upper('&&1');
   end if;
end;
/

-- translate private synonyms

declare
   procedure translate_synonyms (owner_p in out varchar2,synonym_name_p in out
varchar2) is
   begin
      owner_p := upper(owner_p);
      synonym_name_p := upper(synonym_name_p);
      for r1 in (
                 select table_owner,table_name
                 from all_synonyms
                 where owner = owner_p
                 and synonym_name = synonym_name_p
                ) loop
         owner_p := r1.table_owner;
         synonym_name_p := r1.table_name;
         translate_synonyms(owner_p,synonym_name_p);
      end loop;
   end;
begin
   translate_synonyms(:owner_v,:tablename_v);
end;
/

column data_type format a20
column column_name heading "Column Name"
column data_type heading "Data|Type"
column data_length heading "Data|Length" format a10
column nullable heading "Nullable" format a8

-- user Tom's original code but with our derived values

select column_name,
       data_type,
       substr(
       decode( data_type, 'NUMBER',
               decode( data_precision, NULL, NULL,
                '('||data_precision||','||data_scale||')' ),
                   data_length),
              1,11) data_length,
       decode( nullable, 'Y', 'null', 'not null' ) nullable
from all_tab_columns
where owner = :owner_v
  and table_name = :tablename_v
order by column_id
/

alter session set cursor_sharing=exact;

@bequiet

November 28, 2003 Mo -- Thanks for the question regarding "Update
      Statement and Exception Handling", version 8.1.7

      You Asked
Tom:

1.  I have several PL/SQL procedures that use several update statments.  After
each update, I use the
IF (sql%rowcount > 0) THEN
update 2nd table
END IF;

and so on.  I am doing a lot of IFs and I am wondering whether I cao commit all
updates at once or rollback.  If updates results in 0 rows updated would it
raise any excpetion?  Any recommendations here.

2.  If I have two tables that has many-to-many relationship and thus
are implemented using three table design with many to one to the intermediate
table.

Since the intermediate table has foreign keys to both tables, I can not seem to
updated the parent when a user wants to change the column value in the parent.
Would you suggest here using delete the child record and then updating the
parent and then re-inserting the child with the new value, or using
update_cascade? Any recommendations.

3.  THere is a database that I select/update from that uses several columns for
primary keys. SOmetime 8 columns and each is about 10 to 20 characters. ANyway,
I notice that my procedure is slow when I try to do something with the database.
Do yo uthink it might be these long primary keys and it would be better to use
one sequence instead.

Thank you,

Thank you,

      and we said...
You can commit all updates at one or rollback all updates at once, yes.

There are NO exceptions when 0 rows are updated-- that is NOT an error.  If is
the right way to catch that.

2) You don't have a primary key then -- a primary key never never never never
never changes.

Consider using a surrogate key -- ID -- populated via a sequence.  It'll never
change -- no more problem.

3) I seriously doubt it is solely due to an 8 column key -- you need to diagnose
the issue, never guess.  Use the tools provided

http://asktom.oracle.com/~tkyte/tkprof.html
being the place to start.....

I like surrogate keys personally.  An 8 column key just doesn't sit right with
me.
Review
      & Followup

      Rating:
      5
update  November 28, 2003
Reviewer:
      mo
Tom:

Is there a better way to do this. I want to update 3 tables and commit only if 3
updates ran fine otherwise rollbacK.  Her eis how I do it but is creating a
meshy code:

update table 1 set col1.....

If (sql%rowcount > 0) Then
  update table 2 set col1 .....
else
  raise update-failed;
end if;

IF (sql%rowcount > 0) Then
  update table 3 set col11.....
else
  raise update-filed;
end if;

commit;

exceptions
  WHEN update-failed then
  rollback;

end;

          Followup:

well, it is only according to you that an update of zero rows is a "failure".

updating zero rows to me is total success, just means there wasn't anything to
update!

Seems strange that you would update three rows and care that you updated three
rows having never read them out???  if you read them out (with select for
update), you would have no such worries and would not suffer from lost updates
(as you stand a chance of now -- you are basically "shooting at deer in the dark
and hoping you hit some" apparently).

begin
  update t1
  t1_updated := sql%rowcount > 0;
  update t2
  t2_updated := sql%rowcount > 0;
  update t3
  t3_updated := sql%rowcount > 0;
  if ( not t1_updated or not t2_updated or not t3_updated )
  then
      raise failure;
  end if;
end;

is all you need.  as long as the invoker of this does not catch the error and
"hide it", it'll rollback all by itself (if you have my book expert one on one
oracle -- i go over transactions and how they work in some detail)

      Rating:
      5
update  December 03, 2003
Reviewer:
      mo
Tom:

Thanks for the neat way of doing the transactional update.  But you say do
"select for update" and I understood that this will not work for web
applications and it is only for client server apps.

do you do also "select for update" before actual "update" when you are updating
via mod_plsql? Do you do that to lock the record until you are done with all of
your updates for other tables?

THanks,

          Followup:

we use optimistic concurrency controls.

search this site for those words

or if you have my book expert one on one oracle -- i go over that in some detail
in there in the chapter on transactions

November 29, 2003 Steve -- Thanks for the question regarding
      "Equivalent to SESSION_ROLES that works in stored proc", version 7.3.3

      You Asked
Hi Tom,

Firstly, yes I know it's a very old version of the DB.  Not my decision.

Our web based app uses logical users, rather than every user having their own
schema.  So, the cgi program always connects to the DB as the same schema.  When
the user logs in, we set appropriate session roles based on a USER_ROLE table.
So, we may have the following scenario

DB Roles
R1, R2, R3, R4, R5, R6

Role Grants
R2 to R5
R1 to R6
R3 to R4, R2 to R3, R1 to R2

User_Role
R4 to U1
R5 to U2
R6 to U3

After this process, if I log in as U1 and select from SESSION_ROLES, I see R1,
R2, R3, R4, which is exactly what I want.  However, I need to do this from a
stored proc, which of course see's nothing when selecting from SESSION_ROLE.

I assume from reading your replies to similar questions that I need to create a
view that accesses the underlying dictionary tables, however I'm not sure if any
related to my specific scenario/db version.

Thanks for your help,

Steve

      and we said...
Correct -- roles are not enabled during the execution of a procedure (with the
special exception of AUTHID CURRENT_USER procedures in 8i and up -- but still,
in general, no roles are enabled in procedures)

Now, since you are explicitly setting roles -- all you need to do is execute the
same logic in your procedure to see what "roles" should be in effect.  That is,
do the same logic you do to set the roles to see if a role is "enabled".
Perhaps you can encapsulate that logic in a plsql package and have a PLSQL table
type that contains the list of them in an array you can access whenever you
need.
Review
      & Followup

      Rating:
      3
  July 09, 2002
Reviewer:  Steve
      Baldwin  from Melbourne, Australia
Thanks for the feedback.  Good to know I was on the right track!!

      Rating: 3
role changing
      from the proc  November 29, 2003
Reviewer:
      alex  from uk
Tom

I have one schema user called alex and there two roles RL1 and RL2, both of
these roles are assigned to alex but RL1 is the default one.So if I connect to
database using alex user I have got all the privileges as RL1 has but if I want
to set the role RL2 dynamically while executing the program can I do it.

Following i have tried and got the compilation error
EXEC SQL CONNECT :user IDENTIFIED BY :passd

EXEC SQL SET ROLE RL2 IDENTIFIED BY :rl2passd:indrl

I get compilation time error DDL is not allowed. Give me some idea how to do
this.

thanks

          Followup:

exec sql execute immediate :stmt;

put into :stmt whatever you want to execute.  You cannot use BINDS in DDL.

November 29, 2003 wallacel -- Thanks for the question regarding
      "incomplete recovery", version 8.1.6

      You Asked
Tom:

let's say i accidently drop an important table.
one choice is i perform an incomplete recovery to the time
right before the action,
but how do i know the exactly time i do that?
maybe i know it is around 10Am yesterday, but i don't
think that information is good enough.

another choice is cancel based or change-based incomplete
recovery, again, how do i know the SCN i drop the table?
or when is proper time to cancel?

Regards,

      and we said...
Use dbms_logmnr to find out when you dropped the table.

http://technet.oracle.com/doc/oracle8i_816/server.816/a76936/dbms_log.htm#999107
This is one of the more common uses of log miner I’ve seen.
Someone “accidentally” dropped a table.  You would like to get
it back.  You have been running in archivelogmode and have the
backups.  You would like to restore your backups and do a point
in time recovery to the point in time IMMEDIATELY prior to the
drop table.  In that fashion, you can restore and recover that
table – stop recovery (hence not dropping the table again) and
then export this table from the restored database and import it
into the correct database.  This allows you to restore the table
with all of the changes intact.

In order to do that, we’ll need to know either the exact time or
the SCN (system change number) of the drop table.  Since clocks
are out of sync and people are probably panicked – they may give
bad information in this case.  What we can do is load up the
archived log files from around the time of the drop table and
find the exact SCN we want to recover to.

We’ll do another quick example here to isolate the statements
you might see in log miner when a table is dropped.  I’m using
locally managed tablespaces so if you are using dictionary
managed tablespaces you may see more SQL then I do below.  That
will be the SQL executed to return the extents back to the
system and free up the space allocated to the table.  So, here
we go to drop the table:

tkyte@TKYTE816> alter system switch logfile;
System altered.

tkyte@TKYTE816> drop table dept;
Table dropped.

tkyte@TKYTE816> commit;
Commit complete.

tkyte@TKYTE816> alter system switch logfile;
System altered.

I did the switches so I know what logfile contains the redo and
to cut down on the stuff I have to weed through in the example.

Now, we want to locate the SQL_REDO that represents the “drop
table”.  The actual SQL executed at runtime is not reported by
log miner, rather equivalent SQL is.  We will not see a “drop
table” statement – we will see data dictionary modifications.
The one we are looking for will be a DELETE against SYS.OBJ$
which is the base table for holding all objects.  Part of
dropping a table involves deleting a row from SYS.OBJ$.
Fortunately, when log miner builds the SQL_REDO to process a
delete – it includes the column values in the SQL along with the
rowid.  We can use that fact to search for the delete of DEPT
from OBJ$.  Here is how:

tkyte@TKYTE816> begin
  2     sys.dbms_logmnr.add_logfile
        ( 'C:\oracle\rdbms\ARC00023.001',
  3        dbms_logmnr.NEW );
  4  end;
  5  /
PL/SQL procedure successfully completed.

tkyte@TKYTE816> begin
  2     sys.dbms_logmnr.start_logmnr
  3     ( dictFileName => 'c:\temp\miner_dictionary.dat' );
  4  end;
  5  /
PL/SQL procedure successfully completed.

tkyte@TKYTE816> select scn, sql_redo
  2    from v$logmnr_contents
  3   where sql_redo like 'delete from SYS.OBJ$ %''DEPT''%'
  4  /

       SCN SQL_REDO
---------- ----------------------------------------
    804132 delete from SYS.OBJ$ where OBJ# = 22178
           and DATAOBJ# = 22178 and OWNER# = 50 and
           NAME = 'DEPT' and NAMESPACE = 1 and
           SUBNAME IS NULL and TYPE# = 2 and CTIME
           = TO_DATE('22-JAN-2001 14:23:38',
           'DD-MON-YYYY HH24:MI:SS') and MTIME =
           TO_DATE('22-JAN-2001 14:23:38',
           'DD-MON-YYYY HH24:MI:SS') and STIME =
           TO_DATE('22-JAN-2001 14:23:38',
           'DD-MON-YYYY HH24:MI:SS') and STATUS = 1
           and REMOTEOWNER IS NULL and LINKNAME IS
           NULL and FLAGS = 0 and OID$ IS NULL and
           SPARE1 = 6 and ROWID =
           'AAAAASAABAAAEsuAAx';

That is all there is to it – now that we found that the SCN was
804132, we can do a point in time recovery elsewhere to recover
this table and restore it to our system.  We can recover it to
the very point in time immediately prior to it being dropped.

Review &
      Followup

      Rating: 2
question
      regarding 'DBMS_LOGMNR' version O816 for NT(SP6)  April 20,
      2001
Reviewer:  zhanghuafu(zhanghuafu@163.net)  from
      china
Tom:
  I use the package "DBMS_LOGMNR" to get the scn(804132) that created by the SQL

  "delete table scott.dept". But when I recover the database using the sql
  "recover database until change 804132-1" and
  open the database resetlogs, I don't find the table 'dept' that I want to
recover.
  Why?  How can I recover the table 'DEPT' with this "SCN";

another question:

SQL> connect sys/system@web
Connected.
SQL> begin
  2    sys.dbms_logmnr_d.build('dictfile.ora','c:\temp');
  3  end;
  4  /

PL/SQL procedure successfully completed.

SQL> begin
  2
sys.dbms_logmnr.add_logfile('e:\datafile\web_arch\ARC00002.001',dbms_logmnr.new);

  3  end;
  4  /

PL/SQL procedure successfully completed.

SQL> begin
  2    sys.dbms_logmnr.start_logmnr(DictFileName=>'c:\temp\dictfile.ora');
  3  end;
  4  /
begin
*
ERROR at line 1:
ORA-01295: DB_ID mismatch between dictionary c:\temp\dictfile.ora and logfiles
ORA-06512: at "SYS.DBMS_LOGMNR", line 42
ORA-06512: at line 2

how to resolve it.

Thank You

Best Regards

      Rating: 4
Problem with
      incomplete recovery  November 29, 2003
Reviewer:
      Mike
Tom,
It's an Oracle 8174 database.

The following was what I did:

1) full backup the database;
2) bring up the database
3) alter system switch logfile;   --- 607 found in the log dest
4) create the tab_test
5) alter system switch logfile;   --- 608
6) create another tab_test2
7) alter system switch logfile;   --- 609
8) alter system switch logfile;   --- 610
9) drop table tab_test
10) shutdown the datbase

I wanted to recover the tab_test, so I found the log switch  time stamp for the
log 610 from the alert.log
"Sat Nov 29 15:03:19 2003"

A) restore the databse from the fullback as performed in 1)
B)
SQL> conn /as sysdba
Connected to an idle instance.
SQL> startup mount
ORACLE instance started.

Total System Global Area   77738144 bytes
Fixed Size                    73888 bytes
Variable Size              76754944 bytes
Database Buffers             819200 bytes
Redo Buffers                  90112 bytes
Database mounted.
SQL> RECOVER DATABASE UNTIL TIME '2003-11-29:15:03:19';
Media recovery complete.
SQL> alter database open resetlogs;

Database altered.

C) check the tables, Both tab_test and tab_test2 No Found!

Tom, what could be the problem?

Thanks

Mike

          Followup:

time based recovery is not 100% accurate.  you would (could) use log miner to
see when the tables came into being and use the SCN.

November 29, 2003 Nisheeth -- Thanks for the question regarding
      "Not able to FTP from oracle server. Get the error : The filename,
      directory name, or volume label syntax is incorrect.", version 9i

      You Asked
I am using UTL_TCP package for FTP from oracle server to a messaging server.
Here is the code
------------------------------------------------------
con := utl_tcp.open_connection(p_ftp_server, 21);
v_bytes_written  := UTL_TCP.write_line(con,'USER ' || p_userid);
v_bytes_written  := UTL_TCP.write_line(con,'PASS ' || p_pwd);
v_bytes_written  := UTL_TCP.write_line(con,'PASV');
v_bytes_written  := UTL_TCP.write_line(con,'TYPE I');
v_bytes_written  := UTL_TCP.write_line(con,'STOR F:\ftp\'  || p_file_name);
v_bytes_written  := UTL_TCP.write_line(con,'quit');
utl_tcp.close_all_connections;
----------------------------------------------------
code works fine utill it comes to STOR (put command) command and there it gives
'The filename, directory name, or volume label syntax is incorrect.' error. This
PL/sql has access to 'F:\ftp' folder. i am able to read and write to that
directory.I have tried different options also like.,
\\servername\ftp\file.txt, \\ftp\file.txt and what not but got the same error.
Tom, i would really appreciate, if you can help me.

      and we said...
You are lucky it is failing.

If only it were that easy -- you see -- you would have to implement the ftp
protocol itself -- the stor command is most likely failing because the REMOTE
machine doesn't have an F: drive.  The STOR command just says "hey, here is the
name of a file that if you connect BACK TO ME, I'll give you -- please name it
that on the server".  After doing the STOR command - the ftp server tries to
connect to you to get the data for the file.

Do this (use your ftp server of course, not mine!!!) :

[tkyte@tkyte-pc-isdn tkyte]$ telnet aria 21
Trying 138.2.5.51...
Connected to aria.
Escape character is '^]'.
220 aria FTP server (SunOS 5.8) ready.
USER tkyte
331 Password required for tkyte.
PASS xxxx
230 User tkyte logged in.
PASV
227 Entering Passive Mode (138,2,5,51,179,70)
TYPE I
200 Type set to I.
STOR /nope/sp_1_2.lst
553 /nope/sp_1_2.lst: No such file or directory.  this happens because NOPE
doesn't exist as a directory ON THE REMOTE SERVER!!!!!
STOR /tmp/sp_1_2.lst   this will "hang" assuming you have a /tmp/ directory on
the remote server (i did).  The remote server is waiting for us to send it the
file on another connection!!!

the ftp protocol is bigger then a breadbox.  You would be best off using a java
stored procedure to do this!

Review &
      Followup

      Rating: 4
What would be
      the file location on server.  December 30, 2002

Reviewer:  Nisheeth Mitharwal  from Louisville, KY
      USA
Tom your response was useful. Thanks. one more doubt TOM.
i am dropping that file on remote server say
i want file on batch/xml folder.i do this using

v_bytes_written  := UTL_TCP.write_line(con,'CWD /batch/xml');

v_bytes_written  := UTL_TCP.write_line(con,'STOR file.xml');

now can i mention the file location  from where i want to pick the file.

for ex: oracle server has F drive connected and i want to pick file from that
particular drive(F) and and send it to /batch/xml folder of remote server.

How do i mention this using store command? Do i need to put that file on some
default directory of local server and ftp process will know from where to pick
up that file.
Thaks in advance TOM.

          Followup:

but now, after doing the STOR command -- your plsql routine has to get the file
to ftp server.

the ftp server doesn't just magically "get the file", it has to be transmitted.
STOR doesn't move a file.  STOR just says "hey, I'm going to send you a file
later, this is what and where I would like you to PUT IT when you get it"...

The ftp "process" is YOU -- you are the ftp process!  you would have to open a
connection to the server again and send it the file.  The STOR command doesn't
send anything, you have to.

This is NOT something you will be doing in PLSQL very easily

If you attemp -- you will need to know exactly how to implement the ftp protocol
-- you are only halfway there!

      Rating: 4
How do we
      implement FTP process in PL/sql?  December 30, 2002

Reviewer:  Nisheeth Mitharwal  from louisville , KY
      USA
Thanks TOM for answer.

Tom,I thought STOR works as standard put command. Now how would i implement FTP
process in oracle. Can you suggest me how can we actually transfer files using
ftp in PL/SQL.

Thanks in advace.

          Followup:

PUT is a command to a ftp client (which is what you are writing).  It tells the
ftp client "hey, tell the ftp server where I want it to put this file -- that is
the STOR command and then actually SEND the file for me".  That is the part you
are missing -- you need to send the file, you need to understand the ftp
protocol and stream the file yourself

I am suggesting you do not use PLSQL, I am suggesting that java, with existing
ftp classes, would be an easier approach.

Short of that -- look up the ftp spec on the web and code the entire thing from
scratch.

      Rating: 5
how to
      implement FTP in java stored procedure  December 30, 2002

Reviewer:  Nisheeth Mitharwal
Thanks Tom,
your response was very useful.Now i am thinking of doing this using Java stored
procedure. Tom can you send me some sample code for FTP in java stored proc. I
am new to orcale and java. How to call java stored proc from oracle and what is
the archirecture.

Thanks in advance.

          Followup:

here is an example -- it does a get, i don't have a put - but it won't be much
different.  You'll want to "bfile" the data to PUT (so it is a blob) to minimize
the code changes:

set define off
create or replace and compile java source named "my_ftp" as
import java.io.*;
import sun.net.ftp.*;
import oracle.sql.*;

public class my_ftp {

  public static void get(
    String host,
    NUMBER port,
    String username,
    String password,
    String sourceFile,
    BLOB   target,
    String proxyHost,
    NUMBER proxyPort) throws Exception {

    OutputStream os = target.getBinaryOutputStream();
    FtpClient ftpClient = null;

    try {
      int lastSlash = sourceFile.lastIndexOf('/');
      String file = sourceFile.substring(lastSlash + 1);
      String directory = sourceFile.substring(0, lastSlash);

      // Turn on proxy support when requested.
      if (proxyHost != null && proxyHost.trim().length() > 0 && proxyPort !=
null) {
        System.getProperties().put("proxySet", "true");
        System.getProperties().put("proxyHost", proxyHost.trim());
        System.getProperties().put("proxyPort", proxyPort.stringValue());
      }

      ftpClient = new FtpClient(host, port.intValue());
      ftpClient.login(username, password);
      ftpClient.binary();
      ftpClient.cd(directory);
      InputStream is = ftpClient.get(file);
      int length;

      byte[] buffer = new byte[target.getChunkSize()];
      while((length = is.read(buffer)) != -1) {
        os.write(buffer, 0, length);
      }
    } finally {
      if (ftpClient != null) {
        ftpClient.closeServer();
      }
    }
  }
}
/
show errors
create or replace procedure FTP_GET(
    p_host        in varchar2,
    p_port        in number,
    p_username    in varchar2,
    p_password    in varchar2,
    p_source_file in varchar2,
    p_target_blob in BLOB,
    p_proxy_host  in varchar2,
    p_proxy_port  in number)
as language java name 'my_ftp.get(
    java.lang.String,
    oracle.sql.NUMBER,
    java.lang.String,
    java.lang.String,
    java.lang.String,
    oracle.sql.BLOB,
    java.lang.String,
    oracle.sql.NUMBER)';
/
show errors
set serveroutput on size 1000000
exec dbms_java.set_output(1000000)
declare
  file_data  blob;
  file_name  varchar2(255) := '/export/home/tkyte/tmp/tford.dbg';
begin
  insert into lob_test(id, filename, title, data)
  values(2, file_name, 'Terry Ford debug file', empty_blob)
  returning data into file_data;

  ftp_get(
    'aria.us.oracle.com',
    21,
    'mbpierma',
    'password',
    file_name,
    file_data,
    'www-proxy.us.oracle.com',
    0);
end;
/

      Rating: 5
getting Java
      exception: sun.net.ftp.FtpProtocolException: PORT  December
      31, 2002
Reviewer:  nisheeth mitharwal
Tom,
I implemented the put method using sun.net.ftp.* and getting some exceptions.
some of them were for granting permissions and resolved using

begin
dbms_java.grant_permission( 'ENROLL', 'SYS:java.net.SocketPermission',
'remote_server', 'resolve');
dbms_java.grant_permission( 'ENROLL', 'SYS:java.net.SocketPermission', 'IP
address:21', 'connect,resolve');
dbms_java.grant_permission( 'ENROLL', 'SYS:java.net.SocketPermission',
'localhost:1024-', 'listen,resolve' );
exception
    when others then
        dbms_output.put_line(sqlerrm);
end;

still i am getting this exception
Java exception: sun.net.ftp.FtpProtocolException: PORT

my code is

create or replace and compile java source named "ftp_utility" as
import java.io.*;
import sun.net.ftp.*;
import oracle.sql.*;

public static void put(
    String host,
    NUMBER port,
    String username,
    String password,
    String sourceFile,
    String remoteDirectory ) throws Exception
    {

        FtpClient ftpClient = null;

        try
        {
          int lastSlash = sourceFile.lastIndexOf('/');
          String file = sourceFile.substring(lastSlash + 1);
          String directory = sourceFile.substring(0, lastSlash);
          ftpClient = new FtpClient(host, port.intValue());
          ftpClient.login(username, password);
          ftpClient.cd(remoteDirectory);
          ftpClient.put(sourceFile);
         }
         finally
         {
          if (ftpClient != null)
          {
            ftpClient.closeServer();
          }
        }
    }

sourcefile  variable has local path in it as
'F:/ftp/sourcefile.txt'

could you please help me in this...

am i using the right class...

how can i set the mode(active or passive) using this class....

thanks in advance

          Followup:

Sorry, not a heavy duty java programmer myself.  You'll need to goto the java
groups to see if/how to set passive on the connection.

As you give no real error message, and an imcomplete example (eg: no plsql
mapping and such to your java routine) -- I cannot tell you what the PORT issue
is -- I don't know what values you are using for port for example.

      Rating: 4
Getting
      exceptions  December 31, 2002
Reviewer:
      nisheeth mitharwal
sorry Tom for incomplete information

here is complete code...

create or replace and compile java source named "ftp_utility" as
import java.io.*;
import sun.net.ftp.*;
import oracle.sql.*;

public class ftp_utility {

public static void put(
    String host,
    NUMBER port,
    String username,
    String password,
    String sourceFile,
    String remoteDirectory ) throws Exception
    {

        FtpClient ftpClient = null;

        try
        {
          int lastSlash = sourceFile.lastIndexOf('/');
          String file = sourceFile.substring(lastSlash + 1);
          String directory = sourceFile.substring(0, lastSlash);
          ftpClient = new FtpClient(host, port.intValue());
          ftpClient.login(username, password);
          ftpClient.cd(remoteDirectory);
          ftpClient.put(sourceFile);
         }
         finally
         {
          if (ftpClient != null)
          {
            ftpClient.closeServer();
          }
        }
    }
}
/

Procedure        PRC_FTP_PUT (
    p_host        in varchar2,
    p_port        in number,
    p_username    in varchar2,
    p_password    in varchar2,
    p_source_file in varchar2,
    p_remoteDirectory in varchar2)
    as language java name 'ftp_utility.put(
    java.lang.String,
    oracle.sql.NUMBER,
    java.lang.String,
    java.lang.String,
    java.lang.String,
    java.lang.String)';
/

PRC_FTP_PUT(p_ftp_server,21,p_userid,p_pwd,'F:/xml/test.xml','/batchxml/init');

actual error message i am getting is

ORA-29532: Java call terminated by uncaught Java exception:
sun.net.ftp.FtpProtocolException: PORT

Thanks

          Followup:

see

http://groups.google.com/groups?selm=3c68ad4f.9453792%40news.west.cox.net&oe=UTF-8&output=gplain

Looks like your firewall issue -- you'll have to find a ftp client that support
passive transfers.

      Rating: 5
Still getting
      the same error...  January 02, 2003
Reviewer:
      Nisheeth Mitharwal
Tom,
Happy new year.

I have checked the server. It seems there is no firewall problem.

Could you please help me further finding out what could be wrong.I wasn't able
to find out FTPCliet which supports passive connections.

Thanks in advance Tom for your support.

happy new year
Nisheeth

          Followup:

sorry -- i cannot reproduce.

how did you check the server?

goto the SERVER and try to ftp the the client side -- see if an inbound ftp
connection is permitted.

You're exceeding my limited knownledge of the FTP protocol.  Your code worked
"as is" for me.

      Rating: 4
How to set
      permission using DBMS_JAVA package?  January 03, 2003

Reviewer:  Nisheeth Mitharwal
Tom,
There is no firewall i am sure and one more thing i ran that java progaram (not
from otacle) form my machine and i ma able to FTP to the server.

Does this mean that oracle srevr is having some access permission probelm to
remote server.?

Yesterday i granted these permission to server.

Can you tell me if there is any thing else i need to grant to get rid of PORT
exception.

begin
dbms_java.grant_permission( 'ENROLL', 'SYS:java.net.SocketPermission',
'test-mdsftp.humana.com', 'resolve');
dbms_java.grant_permission( 'ENROLL', 'SYS:java.net.SocketPermission',
'test-mdsftp.humana.com:20', 'connect,resolve');
dbms_java.grant_permission( 'ENROLL', 'SYS:java.net.SocketPermission',
'test-mdsftp.humana.com:21', 'connect,resolve');
dbms_java.grant_permission( 'ENROLL', 'SYS:java.net.SocketPermission',
'localhost:1024-', 'listen,resolve' );
exception
    when others then
        dbms_output.put_line(sqlerrm);
end;

Thanks in advance.
Nisheeth

          Followup:

grant yourself javasyspriv and retry.

      Rating:
      5
  January 03, 2003
Reviewer:
      A reader
Thanks Tom it worked...

you have been very helpful Tom.

Thanks again
Nisheeth

      Rating: 4
Not able to FTP
      form oracle server.  October 10, 2003
Reviewer:
      Ted Smith  from Kansas City
I used nisheet mitharwal complete code in this document and
set the dbms_java as mentioned, but I am receiving the following error:  Java
exception: sun.net.ftp.FtpProtocolException: STOR.  Can you assist me in
resolving this error.  We are useing a 8i database.

Thanks,

Ted

          Followup:

hows about the entire stack (error stack)

      Rating: 5
java works
      outside database but not inside  November 29, 2003

Reviewer:  Tom  from UK
Hi Tom,

I have looked at the code you wrote using the sun ftp classes and re-implemented
using the jakarta commons net libraries [www.jakarta.org] for the FTPClient.

The following works fine if called outside the database using the main method,
but hangs the procedure when called from inside the database [never terminates].

Any ideas?

Code follows
JAVA CLASS
----------
import org.apache.commons.net.ftp.*;
import java.io.*;

public class FtpHelper{
    private static FTPClient ftp = new FTPClient();

    public static final void main(String[] args){
        try{
            getFile();
        }catch(Exception e){
            e.printStackTrace(System.err);
        }
    }

    public static void getFile() throws Exception{
        try{
            int reply;

            //connect to the server
            ftp.connect("ftp.yourname.co.uk");

            //get the reply code
            reply = ftp.getReplyCode();

            //if we connected successfully
            if(FTPReply.isPositiveCompletion(reply)){

                //login
                if(ftp.login("username","password")){

                    //text file download
                    ftp.setFileType(FTP.ASCII_FILE_TYPE);

                    //behind firewall so use passive connection
                    ftp.enterLocalPassiveMode();

                    //create a new file
                    File file = new File("c:\\downloaded.html");

                    //get an outputstream
                    FileOutputStream out = new FileOutputStream(file);

                    //read the file
                    if(!ftp.retrieveFile("file.txt",out)){
                        throw(new Exception("Didn't get file"));
                    }

                    //flush the stream
                    out.flush();
                    out.close();

                    //logout and disconnect
                    ftp.logout();
                    ftp.disconnect();

                }else{
                    throw(new Exception("invalid logon"));
                }
            }else{
                throw(new Exception("unable to connect"));
            }
        }catch(Exception e){
            try{
                ftp.logout();
                ftp.disconnect();
            }catch(Exception logoutexception){ throw(new Exception("Couldn't
logout");}
        }
    }
}

PLSQL Wrapper
-------------
create or replace package utlFtp IS

PROCEDURE getFile;

end utlFtp;
/
create or replace package body utlFtp IS

PROCEDURE getFile
AS LANGUAGE JAVA
NAME 'FtpHelper.getFile()';

end utlFtp;

          Followup:

nope, no idea.  don't write too much java, get a headache reading it.

guess I would put some debug in there (system.out.printlns will be written to a
trace file on the server by default) to see where it gets stuck -- or calls to
dbms_application_info.set_client_info so you can query v$session to see what
"step" it is on.

November 29, 2003 Nilanjan -- Thanks for the question regarding
      "Intermedia text search from a union of two tables", version 8.1.6

      You Asked
I need to do a search from a union of two tables. The view is like the
following:

select uoi_id,party_name as author_name
from isbn_author
UNION
select uoi_id,author_name
from metadata
where uoi_id not in (select uoi_id from isbns) and author_name is not null;

I have created context indextes on isbn_author(party_name) and as well as on
metadata(author_name). I can do a simple search like:

select author_info from author_view
where contains(author_info,'Jesica')>0

But it returns no rows if I use score:

select author_info,score(1) from author_view
where contains(author_info,'Jesica',1)>0
order by score(1).

Is there any way to do that kind of search using score?

Thanks,
Nilanjan

      and we said...
Well, there is a bug registered against interMedia -- but only to make this
raise an error (not return no rows).

Using SCORE in this way doesn't make sense.  SCORE is generated based on a
single hit list from a single index.  The query:

select author_info,score(1) from author_view
where contains(author_info,'Jesica',1)>0
order by score(1)

is similar to:

select *
from (
select author_info,score(1)
 from isbn_author
where contains(author_info,'Jesica',1)>0
union
select author_info,score(1)
  from metadata
 where ....
)
order by 2
/

The scores would be computed based on the individual INDEX.  But now the score
is totally not relevant.  The HIGHEST score from metadata might be LESS relevant
in the result set then the LOWEST score from isbn_author (or vice versa).  You
would be mixing and matching scores that were like apples and oranges.  You
cannot compare scores across indexes -- they just don't work like that.

So, even if it worked -- it would not work.  The scores would be meaningless.

Review &
      Followup

      Rating:
      3
  April 26, 2001
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating: 2
What's the work
      around  July 01, 2002
Reviewer:  Rukshan
       from London
I have a similar situation where I have to do a union and score. However, if the
importance of results in this case in both metadata and authors are the same how
can I display result set in descending score order

          Followup:

  1  select 'a', displayid, score(1)
  2  from WWC_ASK_INDEXED_QUESTIONS$
  3  where contains( text, 'kyte', 1 ) > 0
  4  union all
  5  select 'b', displayid, score(2)
  6  from WWC_ASK_INDEXED_QUESTIONS$
  7  where contains( text, 'hello', 2 ) > 0
  8* order by 3 desc
ask_tom@OSI816> /

'  DISPLAYID   SCORE(1)
- ---------- ----------
b 4.0995E+11        100
b 6.8898E+11        100
a 2.0676E+12         73
b 7.3301E+11         70
a 2.1899E+12         54
b 2.2061E+11         51
b 1.9959E+12         51
a 2.5562E+11         45
a 4.4195E+11         45
a 7.4763E+11         45
.....

but beware -- the score of 73 (3rd row) might be a less meaningful result then
the score of 70 on the next line.  That is because the score is relative to the
search term in the index -- NOT ACROSS THEM -- it is apples and oranges.

      Rating: 2
I've a similar
      problem ... and extended one  September 18, 2002

Reviewer:  Chaitanya Sravanth P  from India

I've indexed 6 columns on a single table (6 seperate indexes). My search should
search all the 6 indexes and return the highest score record as the first
record. Can you give me a solution for this. I'm in a standstill. The queries
what I've written I'm enclosing.
        OPEN RCURSOR FOR select unique(item_id), Item_title,
        Item_author,category_name, usage_type,room,slot_no,
        item_availability,Item_content from

        (select score(1), d.* from search_data d where
        contains(item_id, LFinalSearchString, 1) > 0 and item_status = 'Y'
        union all
        select score(2), d.* from search_data d where
        contains(item_title, LFinalSearchString, 2) > 0 and item_status = 'Y'
        union all
        select score(3), d.* from search_data d where
        contains(item_author, LFinalSearchString, 3) > 0 and item_status = 'Y'
        union all
        select score(4), d.* from search_data d where
        contains(item_content, LFinalSearchString, 4) > 0 and item_status = 'Y'
        union all
        select score(5), d.* from search_data d where
        contains(item_publisher, LFinalSearchString, 5) > 0 and item_status =
'Y'
        union all
        select score(6), d.* from search_data d where
        contains(item_remarks, LFinalSearchString, 6) > 0 and item_status = 'Y'
        order by 1 desc);

I need ASAP. I'll be waiting for you reply.

          Followup:

Well, what is wrong with the output you get from that.

My crystal ball is on the blink, hence my ability to "see" your problem is
diminished.

(but me, I would have created a single index that contains all 6 fields and do
ONE search like this:

select score(1), d.* from searchdata d where contains( my_big_index,
searchstring, 1 ) > 0 and item_status = 'Y' order by 1 desc;

consider how much faster that be!!

      Rating: 3
How to create a
      single domain index on multiple columns of a
      table?  September 25, 2002
Reviewer:  Chaitanya
      Sravanth P  from India
Even I want my search faster. Can you tell me how to create a single domain
index on multiple columns. I tried but it is not allowing me to create domain
index on more than one column.

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:440419921146
you would index a function that gathers together your data into a single field
to be indexed.

      Rating: 2
Can you give me
      an example based on the data I'm providing.  September 28,
      2002
Reviewer:  Chaitanya Sravanth  from India

I've created a Search_data table which has 14 columns. In that I'm indexing 6
columns (item_id, Item_title, Item_author, ITem_content, item_publisher,
Item_remarks) and allowing user to search only those columns. Can you please
give an example basing on this, since I'm finding it difficult to understand the
example given by you. Sorry for the inconvenience. Give me a detailed
description so that I can understand properly.

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:440419921146
it is the example.  specifically for you:

create or replace procedure foo_proc(
    p_id in rowid,
    p_lob IN OUT clob
)
is
begin
    for c1 in (select *  from search_data where rowid = p_id)
    loop
        dbms_lob.writeAppend(p_lob, length(c1.item_id)||' ',c1.item_id||' ' );
        .... the other fields ....
        dbms_lob.writeAppend(p_lob, length(c1.item_remarks),c1.item_remarks );
  end loop;
end;
/

would be the function to assemble the data.

      Rating: 4
Some more
      clarifications!!  September 30, 2002
Reviewer:
      Chaitanya Sravanth P  from India
Thanks for the response you have given Tom.

I've done as you said. The condition is - I'm creating domain indexes in a
seperate tablespace for which I've written a pl/sql block like this -

BEGIN
CTX_DDL.CREATE_PREFERENCE('STORE_CLOB', 'BASIC_STORAGE');
CTX_DDL.SET_ATTRIBUTE('STORE_CLOB', 'I_TABLE_CLAUSE', 'TABLESPACE SRCH_IND_TBLSP
        STORAGE (INITIAL 1M)');
CTX_DDL.SET_ATTRIBUTE('STORE_CLOB', 'K_TABLE_CLAUSE', 'TABLESPACE SRCH_IND_TBLSP
        STORAGE (INITIAL 1M)');
CTX_DDL.SET_ATTRIBUTE('STORE_CLOB', 'R_TABLE_CLAUSE', 'TABLESPACE SRCH_IND_TBLSP
        STORAGE (INITIAL 1M)');
CTX_DDL.SET_ATTRIBUTE('STORE_CLOB', 'N_TABLE_CLAUSE', 'TABLESPACE SRCH_IND_TBLSP
        STORAGE (INITIAL 1M)');
CTX_DDL.SET_ATTRIBUTE('STORE_CLOB', 'I_INDEX_CLAUSE', 'TABLESPACE SRCH_IND_TBLSP
        STORAGE (INITIAL 1M)');
END;
/
This block I'm using as a parameter while creating domain indexes. The statement
is like this -

CREATE INDEX ITEMID_IDX ON SEARCH_DATA(ITEM_ID) INDEXTYPE IS CTXSYS.CONTEXT
PARAMETERS('STORAGE STORE_CLOB');

Can I use it for the new index like shown below -

CREATE INDEX SEARCH_IDX ON SEARCH_DATA(DUMMYCOL) INDEXTYPE IS CTXSYS.CONTEXT
PARAMETERS( 'DATASTORE LIMS_USER_DATASTORE LEXER MY_LEXER', 'STORAGE
STORE_CLOB');

In the example given by you, you created index on a "dummycol", should I specify
one of the 6 columns which I'm searching for? Can you give me a clarification
for this. I'll be waiting for your earliest reply.

          Followup:

if you use correct syntax, you can do that (parameters is a single string, not a
comma delimited list of strings)

You can EITHER index a dummycol or ANY ONE of the columns in your 6.  This will
just drive what will be used in the CONTAINS clause.

      Rating: 4
A Bit more
      information  October 01, 2002
Reviewer:
      Chaitanya Sravanth  from India
Thanks for the value details you are giving.

That means, even though I use only one of the 6 Columns and use the same Column
in Contains operator like

select item_id, item_title, item_author from search_data where contains(item_id,
'searching', 1) > 0;

It will go and Search the User datastore where the data of all 6 Columns are
stored and accordingly it will display the records (item_id, item_title and
item_author) where the string "searching" occurs. Am I right Tom?

          Followup:

yes, cause the FUNCTION that is used to index item_id glued all of the other
fields together.

      Rating: 5
Some more
      information  October 03, 2002
Reviewer:
      Chaitanya Sravanth  from India
Thank you very much for the information you have given. I'm verifying the code.
One more thing - The function which I wrote for appending all the columns and
storing in a Datasore, When will it get refreshed, means when new data is added
to the Search_data table will it get refreshed or should I write some code to do
that. Let me know regd. this. Thanks once again.

          Followup:

If you have the ctxsrv process running, the index will be maintained
automagically.

If you do not, you need to "sync" the index using ctx_ddl after a load

      Rating: 4
pl/sql block
      where to execute??  August 25, 2003
Reviewer:
      Chaitanya Sravanth Pillutla  from Bangalore, India
I created a pl/sql block as follows -

BEGIN
    CTX_DDL.CREATE_PREFERENCE('STORE_CLOB', 'BASIC_STORAGE');
     CTX_DDL.SET_ATTRIBUTE('STORE_CLOB', 'I_TABLE_CLAUSE', 'TABLESPACE
SRCH_IND_TBLSP
        STORAGE (INITIAL 1M)');
    CTX_DDL.SET_ATTRIBUTE('STORE_CLOB', 'K_TABLE_CLAUSE', 'TABLESPACE
SRCH_IND_TBLSP
        STORAGE (INITIAL 1M)');
    CTX_DDL.SET_ATTRIBUTE('STORE_CLOB', 'R_TABLE_CLAUSE', 'TABLESPACE
SRCH_IND_TBLSP
        STORAGE (INITIAL 1M)');
    CTX_DDL.SET_ATTRIBUTE('STORE_CLOB', 'N_TABLE_CLAUSE', 'TABLESPACE
SRCH_IND_TBLSP
        STORAGE (INITIAL 1M)');
    CTX_DDL.SET_ATTRIBUTE('STORE_CLOB', 'I_INDEX_CLAUSE', 'TABLESPACE
SRCH_IND_TBLSP
        STORAGE (INITIAL 1M)');
END;
/

This is for creating domain indexes in a different tablespace other than default
tablespace.
I'm executing this script in a user I've created for my application.
It is givein the following error -

CTX_DDL.CREATE_PREFERENCE('STORE_CLOB', 'BASIC_STORAGE');
 *
ERROR at line 2:
ORA-06550: line 2, column 2:
PLS-00201: identifier 'CTXSYS.CTX_DDL' must be declared
ORA-06550: line 2, column 2:
PL/SQL: Statement ignored
ORA-06550: line 3, column 3:
PLS-00201: identifier 'CTXSYS.CTX_DDL' must be declared
ORA-06550: line 3, column 3:
PL/SQL: Statement ignored
ORA-06550: line 5, column 2:
PLS-00201: identifier 'CTXSYS.CTX_DDL' must be declared
ORA-06550: line 5, column 2:
PL/SQL: Statement ignored
ORA-06550: line 7, column 2:
PLS-00201: identifier 'CTXSYS.CTX_DDL' must be declared
ORA-06550: line 7, column 2:
PL/SQL: Statement ignored
ORA-06550: line 9, column 2:
PLS-00201: identifier 'CTXSYS.CTX_DDL' must be declared
ORA-06550: line 9, column 2:
PL/SQL: Statement ignored
ORA-06550: line 11, column 2:
PLS-00201: identifier 'CTXSYS.CTX_DDL' must be declared
ORA-06550: line 11, column 2:
PL/SQL: Statement ignored

what are the other things I should check to execute this script properly. Please
let me know ASAP. It is very urgent for me.

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/text.920/a96517/admin.htm#1005590

should be obvious -- you have not been granted access to this package.  that
chapter (i have to assume that since you are doing new development, you must be
using current software), tells you about the roles available for you -- ctxapp
is probably the one you need your account to have granted to it.

      Rating: 3
After granting
      CTXAPP Role to the user, errors while creating index
        August 26, 2003
Reviewer:  Chaitanya Sravanth
      P  from Bangalore, India
I've granted CTXAPP to the USer I created specifically for this project by using
command - GRANT CTXAPP TO LIMS;
The pl/sql package got created.
The following error is getting displayed while creating indexes -

SQL> CREATE INDEX ITEMID_IDX ON SEARCH_DATA(ITEM_ID) INDEXTYPE IS CTXSYS.CONTEXT
PARAMETERS('STORAGE

  2  STORE_CLOB');
CREATE INDEX ITEMID_IDX ON SEARCH_DATA(ITEM_ID) INDEXTYPE IS CTXSYS.CONTEXT
PARAMETERS('STORAGE
*
ERROR at line 1:
ORA-29855: error occurred in the execution of ODCIINDEXCREATE routine
ORA-20000: ConText error:
DRG-50704: Net8 listener is not running or cannot start external procedures
ORA-28575: unable to open RPC connection to external procedure agent
ORA-06512: at "CTXSYS.DRUE", line 122
ORA-06512: at "CTXSYS.TEXTINDEXMETHODS", line 34
ORA-06512: at line 1

Can you please tell me what to do regd. next.

      Rating: 3
Regd. loading
      of java in oracle 8i for utl_smtp access  October 13, 2003

Reviewer:  Chaitanya Sravanth P  from Bangalore,
      India
I'm using Oracle 8i as database.
To send mails I'm using utl_smtp.
I came to know that to use utl_smtp java should be loaded and the command for
that is as follows -

At DOS prompt :
Go to the drive where Oracle is installed
$ORACLE_HOME\plsql\jlib>
(Example: cd Oracle\ora81\plsql\jlib)
And type loadjava -f -v -r -u sys/**** plsql.jar and press Enter.

But to my surprise i've found jlib folder at the same level as plsql folder the
above said path.
And also plsql.jar file is not available in the jlib folder.
Since my project requires to send an auto mail to all its users, please let me
know how I can accomplish with Oracle 8i as DB.
This is very urgent for me. Will be eagerly waiting for your reply. Thanks in
advance.

          Followup:

it should already be loaded in a 817 database. you need do nothing.  did you try
to see if it works?

      Rating: 2
Regd.
      utl_smtp  October 14, 2003
Reviewer:  Chaitanya
      Sravanth P  from Bangalore, India
I tried to find plsql.jar file in oracle folder, but in vain.
I tried to load java using the command specified but it is not taking it. It is
giving IOException on the console.
Please let me know what to do next.

      Rating: 2
Regd. loading
      of java in oracle 8i for utl_smtp access  October 14, 2003

Reviewer:  Chaitanya Sravanth P  from Bangalore,
      India
I've executed the procedure written for sending mail from database which is
given below -
CREATE OR REPLACE PROCEDURE SP_ADV_KEYWORD_SEARCH(
    ErrorStatus     OUT     Number,
    VKeyword     IN     Varchar2,
    TotalRecords    OUT    Number,
    SearchResults    OUT     Types.cursortype)
AS
    RCURSOR         TYPES.CURSORTYPE;
    LItemCount         NUMBER;
    COMMA             NUMBER;
    LNewSearchString     Varchar2(4000);
    LFinalSearchString     Varchar2(4000);
    lcount             integer := 0;
    mcount             integer := 0;
    vcatgid            integer;
    vclassid        integer;
BEGIN
    --replace all the spaces with ','
    LNewSearchString := Replace(trim(VKeyword),' ',',');

    --remove all unwanted spaces before and after
    LNewSearchString := NVL(trim(VKeyword),' ');

    --replace ',' with spaces
    LNewSearchString := REPLACE(LNewSearchString,',',' ');

    --if any special characters or any functions are available replace
    --them with '\'
    If  upper(trim(LNewSearchString)) IN ('%','$','!','?','OR','||','RT','=',
    'BT','&',',','ABOUT','=',';','*','>','-','~','WITHIN','AND','|','ACCUM')
then
         LNewSearchString := replace(LNewSearchString,
         LNewSearchString, '\'||LNewSearchString);
    end if;
    --replace all the spaces with commas
    LNewSearchString := Replace(TRIM(LNewSearchString),' ',',');

    If NVL(LNewSearchString,' ') <> ' ' then

    LNewSearchString:= REPLACE(upper(LNewSearchString),',OR,',' OR ');
    LNewSearchString:= REPLACE(upper(LNewSearchString),',AND,',' AND ');
    LNewSearchString:= REPLACE(upper(LNewSearchString),',ABOUT,',' ABOUT
    ');
    LNewSearchString:= REPLACE(upper(LNewSearchString),',NEAR,',' NEAR ');

    LFinalSearchString:= ''''||LNewSearchString||'''';
    --LFinalSearchString:=LNewSearchString;
    --CheckComma(LNewSearchString,LFinalSearchString);
    dbms_output.put_line(LFinalSearchString);

    --check if matching records are available
    select count(*) into LItemCount from
        (select search_data.* from search_data where
            contains(item_id,LFinalSearchString,1)>0 and item_status = 'Y'

        union
        select search_data.* from search_data where
            contains(item_title,LFinalSearchString,1)>0 and item_status = 'Y'

        union
        select search_data.* from search_data where
            contains(item_author,LFinalSearchString,1)>0 and item_status = 'Y'

        union
        select search_data.* from search_data where
            contains(item_publisher,LFinalSearchString,1)>0 and item_status =
            'Y'

        union
        select search_data.* from search_data where
            contains(item_content,LFinalSearchString,1)>0 and item_status = 'Y'

        union
        select search_data.* from search_data where
            contains(item_remarks,LFinalSearchString,1)>0 and item_status =
'Y');

    dbms_output.put_line('Item Count: '||LItemCount);
    --if matching records are available
    If LItemCount > 0 Then
        OPEN RCURSOR FOR select item_id, Item_title,
        Item_author,category_name, usage_type,room,slot_no,
        item_availability,Item_content, max(score) score from
        (select d.*, score(1) score from search_data d where
        contains(item_id, LFinalSearchString, 1) > 0 and item_status = 'Y'
        union
        select d.*, score(1) score from search_data d where
        contains(item_title, LFinalSearchString, 1) > 0 and item_status = 'Y'
        union
        select d.*, score(1) score from search_data d where
        contains(item_author, LFinalSearchString, 1) > 0 and item_status = 'Y'
        union
        select d.*, score(1) score from search_data d where
        contains(item_content, LFinalSearchString, 1) > 0 and item_status = 'Y'
        union
        select d.*, score(1) score from search_data d where
        contains(item_publisher, LFinalSearchString, 1) > 0 and item_status =
'Y'
        union
        select d.*, score(1) score from search_data d where
        contains(item_remarks, LFinalSearchString, 1) > 0 and item_status = 'Y')
        group by item_id, item_title, item_author, category_name, usage_type,
room, slot_no, item_availability, item_content
        order by score desc;

        ErrorStatus := 0;    --Items available for the Keyword
        TotalRecords := LItemCount;
        SearchResults := RCURSOR;
    else
        ErrorStatus:=1010; -- No Items matching your search criteria
    End If;
      else
              ErrorStatus:=1010; -- No Items matching your search criteria
      end if;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
    ErrorStatus := 1010; -- No Items matching your search criteria
    WHEN OTHERS THEN
    ErrorStatus:=3001;
END;
/
But it is not getting executed and it says that utl_smtp is not supported. Can
you please tell me how to about utl_smtp.

          Followup:

gee -- everthing but the actual error!!!

remove your exception block.
remove the code that doesn't count (your search code)

make a teeny tiny small example

and let us SEE the actual error.

as i said, plsql.jar is already loaded up on your system - -that is NOT the
problem

      Rating:
      5
Questions  November 26, 2003
Reviewer:
      Dan  from India
Hi Tom

I am using CONTAINS function for search.
My query is like this.

select compname from company where contains(compname,'bio|pol')>0;

This one is working fine. But my client is asking that the compname should start
with 'bio' or 'pol'.
They don’t want all the records which contains 'bio' or 'mn'

My current query result is
------------------------

COMPNAME
--------------------------------------------------------------------------------
Bio-Metric Systems, Incorporated
FMC Bio Polymer
Bio Polymer

But the actual result should be like this
COMPNAME
--------------------------------------------------------------------------------
Bio-Metric Systems, Incorporated
Bio Polymer

They don’t want any records that not start with these keywords.

Is there any other way to solve this sort of problem?. Basically I dont want to
use
Intermedia text search(actually client don’t want).

          Followup:

that is just like then

select * from t where compname like 'Bio%';

      Rating: 4
Question
        November 27, 2003
Reviewer:  Dan
       from India
Sorry Tom, I didn't explain my question properly

Actualy I am using dynamic sql.

My procedure is like this
procedure xx(x refcursor..., Variable_name varchar2)
begin
open x for
'select compname from company where contains(compname,'||Variable_Name|| ')>0'
end;

Variable_Name contains comma separated values. So I need all the company names
which are all starting with these keywords

for e.g.
if the variable contains 'bio|FMC' then result set should be
COMPNAME
--------------------
Bio-Metric Systems, Incorporated
FMC systems
Bio Polymer

Thanks

          Followup:

oh no, no bind variables!  man oh man.   you have to use them!!!!!

but again -- Oracle text isn't the "right" technology for this exact sort of
search.

LIKE is

ops$tkyte@ORA920> create or replace type str2tblType as table of varchar2(30);
  2  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace function str2tbl( p_str in varchar2 ) return
str2tblType
  2  PIPELINED
  3  as
  4      l_str      long default p_str || ',';
  5      l_n        number;
  6  begin
  7      loop
  8          l_n := instr( l_str, ',' );
  9          exit when (nvl(l_n,0) = 0);
 10          pipe row( ltrim(rtrim(substr(l_str,1,l_n-1))) );
 11          l_str := substr( l_str, l_n+1 );
 12      end loop;
 13      return;
 14  end;
 15  /

Function created.

That'll let us query a delimited string "as if it were a table".  We'll use
this in our query....

Now for some test data:

ops$tkyte@ORA920> create table emp as select * from scott.emp;

Table created.

ops$tkyte@ORA920> create index emp_ename_idx on emp(ename);

Index created.

ops$tkyte@ORA920> exec dbms_stats.set_table_stats( user, 'EMP', numrows => 1000,
numblks => 100 );

PL/SQL procedure successfully completed.

and we'll trick the optimizer into thinkingj "there is more then 14 rows in
there..

ops$tkyte@ORA920>
ops$tkyte@ORA920> variable foo varchar2(25)
ops$tkyte@ORA920> exec :foo := 'KIN%,BLA%,CLA%';

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from table( cast( str2tbl(:foo) as str2tblType ) );

COLUMN_VALUE
------------------------------
KIN%
BLA%
CLA%

that just demos that a bind variable as a string can be used as a 'table'
now...

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace on explain
ops$tkyte@ORA920> select emp.ename,  t.column_value
  2    from emp,
  3        (select *
  4               from table( cast( str2tbl(:foo) as str2tblType ) ) t
  5                  where rownum >= 0) t
  6   where emp.ename like t.column_value
  7  /

ENAME      COLUMN_VALUE
---------- ------------------------------
KING       KIN%
BLAKE      BLA%
CLARK      CLA%

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=8222 Card=408400
Bytes=9801600)
   1    0   NESTED LOOPS (Cost=8222 Card=408400 Bytes=9801600)
   2    1     VIEW (Cost=12 Card=8168 Bytes=138856)
   3    2       COUNT
   4    3         FILTER
   5    4           COLLECTION ITERATOR (PICKLER FETCH) OF 'STR2TBL'
   6    1     INDEX (RANGE SCAN) OF 'EMP_ENAME_IDX' (NON-UNIQUE) (Cost=2 Card=50
Bytes=350)

and there we go.  notes:

where rownum >= 0 -- that added the "view" step there.  causes the str2tbl
function to be called ONCE in the query (instead of perhaps over and over which
you would tend to notice performance wise) and materialized into temp space.

card=8168 for the view step -- that is hard coded into the server, could cause
issues which we'll deal with in a second...

index range scan -- which is probably what we want in this case -- take each
value from the str2tbl output and do a range scan for it in the big table.  Sort
of like an in list iterator here.  If this must be done "case insensitive",
simply create a function based index on the UPPER(Ename) and do where
upper(ename) like...

Now, lets see what we can do with this 8168 number....

ops$tkyte@ORA920> select emp.ename,  t.column_value
  2    from emp,
  3        (select /*+ CARDINALITY( t 10 ) */ *
  4               from table( cast( str2tbl(:foo) as str2tblType ) ) t
  5                  where rownum >= 0) t
  6   where emp.ename like t.column_value
  7  /

ENAME      COLUMN_VALUE
---------- ------------------------------
KING       KIN%
BLAKE      BLA%
CLARK      CLA%

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=22 Card=500 Bytes=12000)
   1    0   NESTED LOOPS (Cost=22 Card=500 Bytes=12000)
   2    1     VIEW (Cost=12 Card=10 Bytes=170)
   3    2       COUNT
   4    3         FILTER
   5    4           COLLECTION ITERATOR (PICKLER FETCH) OF 'STR2TBL'
   6    1     INDEX (RANGE SCAN) OF 'EMP_ENAME_IDX' (NON-UNIQUE) (Cost=2 Card=50
Bytes=350)

ops$tkyte@ORA920> set autotrace off

here I've hinted the optimizer to think "there will be 10 rows here".  I
picked 10 because I just "feel in my gut that 10 is a good number here".  that
is, I anticipate perhaps 10 names to be searched for at most in general.
Doesn't mean there cannot be more, cannot be less -- just that "10" is a good
number.

See what it did for the cardinalities and cost?  You can use that in the even
the big number 8168 causes the optimizer to thing "hash join", "sort merge"
instead of the nice index range scan we are probably shooting for.

      Rating: 5
Too
      Good  November 29, 2003
Reviewer:  Dan
       from India
Hi Tom,
Excellent. Exactly what I need.

But the problem is that Our client is using oracle 8.1.7.

So Pleeeease give some idea to implement this logic in 8.1.7

Sorry for disturbing you

Thanks in advance

          Followup:

not too much different.  no cardinality hint, so that'll be a little different.

ops$tkyte@ORA817DEV> create or replace type str2tblType as table of
varchar2(30);
  2  /

Type created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace function str2tbl( p_str in varchar2 )
return str2tblType
  2  as
  3      l_str      long default p_str || ',';
  4      l_n        number;
  5      l_data     str2tblType := str2tblType();
  6  begin
  7      loop
  8          l_n := instr( l_str, ',' );
  9          exit when (nvl(l_n,0) = 0);
 10          l_data.extend;
 11          l_data(l_data.count) := ltrim(rtrim(substr(l_str,1,l_n-1)));
 12          l_str := substr( l_str, l_n+1 );
 13      end loop;
 14      return l_data;
 15  end;
 16  /

Function created.

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

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create index emp_ename_idx on emp(ename);

Index created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> exec dbms_stats.set_table_stats( user, 'EMP', numrows =>
1000, numblks => 100 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> variable foo varchar2(25)
ops$tkyte@ORA817DEV> exec :foo := 'KIN%,BLA%,CLA%';

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select * from table( cast( str2tbl(:foo) as str2tblType )
);

COLUMN_VALUE
------------------------------
KIN%
BLA%
CLA%

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   COLLECTION ITERATOR (PICKLER FETCH)

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> set autotrace on explain
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select /*+ ORDERED INDEX( emp emp_ename_idx ) */
  2    emp.ename,  t.column_value
  3    from
  4        (select *
  5               from table( cast( str2tbl(:foo) as str2tblType ) ) t
  6                  where rownum >= 0) t,
  7         emp
  8   where emp.ename like t.column_value
  9  /

ENAME      COLUMN_VALUE
---------- ------------------------------
KING       KIN%
BLAKE      BLA%
CLARK      CLA%

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=16339 Card=408400
Bytes=9801600)
   1    0   NESTED LOOPS (Cost=16339 Card=408400 Bytes=9801600)
   2    1     VIEW (Cost=3 Card=8168 Bytes=138856)
   3    2       COUNT
   4    3         FILTER
   5    4           COLLECTION ITERATOR (PICKLER FETCH)
   6    1     INDEX (RANGE SCAN) OF 'EMP_ENAME_IDX' (NON-UNIQUE) (Cost=2
Card=1000 Bytes=7000)

      Rating: 5
Tom The
      GREAT  November 29, 2003
Reviewer:  Dan
       from India
Thank you soooo.. much. It's working fine. Exactly what I need.

But I couldn't understand what that rownum >=0 is doing. Even with out rownum>=0
it is working fine. Could you please tell me the reason?.

Sorry Tom, One more question from my side.

What I have got from you is like this

select compname,techname from company c,technology t where
t.techname like     =ANY    (KIN%,BLA%,CLA%, MA%')

Please see, near to LIKE keyword.

is it possible to get the records for these crieteria

select compname,techname from company c,technology t where
t.techname like     =ALL   (KIN%,BLA%,CLA%, MA%')

Actually our project is web based. User can select
'EQUAL TO ANY' or 'EQUAL TO ALL' option button. According to your guidelines I
have done the first part(Equal to any). But I couldn't do the second part. Could
you help me please?

Thanks in advance
dan

          Followup:

like all doesn't make sense to me????? how or why would you ever use it????????

but, yes, you could do it.  just add

group by company_name
having count(*) = length(comma_string)-length(replace(comma_string,',',''))+1

count the number of times the company name was in there, and then make sure you
got it as many times as you had elements in the "in list"

the where rownum, as indicated, just helps ensure that the plsql table function
(str2tbl) is called ONCE per query, instead of once per row per query.  We
materialize the result set to temp -- it may or may not be necessary depending
on the query plan used (if you have my new book "Effective Oracle by design" i
go into this in some detail in the efficient sql chapter)

      Rating: 3
Nice. But Its
      not solving my purpose.  November 29, 2003

Reviewer:  Dan  from India
Let me explain in detail.

In my case one COMPANY may have one or more than one APPLICATION.

If user selects 'EQUAL TO ANY'
then list all companies whose application matches with any keyword given by the
user(dynamic comma list)

Say  For E.g.

COMPANY and APPLICATION are two different tables(It has one to many
relationship. One company --> many application)

Company    Application
-------    --------------
C1         A1
           B4
           M5

C2         A8
           N2

C3       Z3
       M9

If comma separated value(FROM USER, WHICH COME DYNAMICALLY) is 'A%,Z%' then my
result set should be like this

CompName
---------
C1
C2
C3

If user selects 'EQUAL TO ALL'  Then..

If comma separated value(FROM USER, WHICH COME DYNAMICALLY) is 'A%,M%' then my
result set should be like this

CompName
---------
C1

          Followup:

well, did you actually attempt to apply the technology?  what I gave you was in
fact -- THE ANSWER entirely.

ops$tkyte@ORA920> select * from t order by c, a;

C  A
-- --
c1 a1
c1 b4
c1 m5
c2 a8
c2 n2
c3 m9
c3 z3

7 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> variable foo varchar2(25)
ops$tkyte@ORA920> exec :foo := 'a%,z%'

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select t.c
  2    from t,
  3         (select *
  4                from table( cast( str2tbl(:foo) as str2tblType ) )
  5               where  rownum >= 0) x
  6    where t.a like x.column_value
  7  /

C
--
c1
c2
c3

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec :foo := 'a%,m%'

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select t.c
  2    from t,
  3         (select *
  4                from table( cast( str2tbl(:foo) as str2tblType ) )
  5               where  rownum >= 0) x
  6    where t.a like x.column_value
  7   group by t.c
  8   having count(*) = length(:foo)-length(replace(:foo,',',''))+1
  9  /

C
--
c1

November 30, 2003 pauline -- Thanks for the question regarding
      "Re:add partition", version 8.1.7

      You Asked
Tom,
How are you? I have some questions:
1. The consultant DBA created a composite partitioned table
as :
create table bid_facts_dss
partition by range (activity_dt)
  subpartition by hash (activity_type_id) subpartitions 6
  (partition values less than (to_date('01-JAN-1998', 'DD-MON-YYYY')),
   ...
   ...
   partition values less than (to_date('01-DEC-2001', 'DD-MON-YYYY'))
  )
tablespace data_1
/

Since he did not explicitely define the range partition name, Oracle
assigned partion name to each partition positions. From dba_tab_partition, I see
the partition names are named as from sys_p289 to sys_p336. Now if I don't want
to use system defined partition names, I may create another temp table with new
partition name as select * from bid_facts_dss and then rename temp table to
bid_facts_dss. Is there any other easy way to directly rename old partition name
?

2. I need to add new partition to hold the value larger than or equal to
01-DEC-2001 -- let us say, add one year beyond current partition.
If I do:

alter table bid_facts_dss
add partition dss_p49 values less than (to_date('01-JAN-2002', 'DD-MON-YYYY'))
tablespace data_1;

alter table bid_facts_dss
add partition dss_p50 values less than (to_date('01-FEB-2002', 'DD-MON-YYYY'))
tablespace data_1;

and so on...

Does the new range partitions also will automatically have their subpartitions
as 6?

3. This data warehouse, now is configured as noarchive mode. But I think it is
better to use archive mode. For DSS database,which backup strategy you recommend
? Cold backup or hot backup?

thanks in advance.

Pauline

      and we said...
1) just use alter table T rename subpartition|partition current_name to
new_name;

2) yes:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table bid_facts_dss ( activity_dt
date, activity_type_id number )
  2  partition by range (activity_dt)
  3    subpartition by hash (activity_type_id) subpartitions 6
  4    (partition values less than (to_date('01-JAN-1998', 'DD-MON-YYYY')),
  5     partition values less than (to_date('01-DEC-2001', 'DD-MON-YYYY'))
  6    )
  7  /

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table bid_facts_dss
  2  add partition dss_p49 values less than (to_date('01-JAN-2002',
'DD-MON-YYYY'))
  3  /

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select subpartition_name from
user_tab_subpartitions where partition_name = 'DSS_P49'
  2  /

SUBPARTITION_NAME
------------------------------
SYS_SUBP89
SYS_SUBP90
SYS_SUBP91
SYS_SUBP92
SYS_SUBP93
SYS_SUBP94

6 rows selected.

3) it depends.  Is it easier to reconstitute the data by reloading in the event
of a media failure or it is easier to recover from a backup and roll forward?

Ask yourself, if my data disk failed, would I be able to recover my data in a
timely fashion or at all?  If not, implement a backup strategy that makes it so
you can.
Review
      & Followup

      Rating: 5
Very useful
      help  October 02, 2001
Reviewer:  Pauline
       from NY,U.S.A.
Now the DSS database has nightly load every day(No end users can do DML against
the database.It is only be done by SQL loader and PL/SQL program). And after
load we do full export. I think if we encountered media failure,export is not
good to do recovery. So if we have cold backup and recover from last night
backup, then it seems like easier to recover. Could you give your opinion
regarging my situation?

Thanks again.

          Followup:

Well, look at it this way.  If you have a 500 gig database and you suffer media
failure on a 5gig datafile and you are running in NOARCHIVELOG MODE - you will
have to restore the ENTIRE database -- all 500 gig, not just the 5 gig failed
file.

On the other hand, you can reduce this somewhat by

0) making all tablespaces needed to be written to read/write
1) doing your load
2) making all tablespaces except system, rollback, temp read only
3) cold backup
4) startup leaving all tablespaces that are read only, read only.

Now if you fail, you have to restore the failed FILE only (since they are read
only during the backup, no recovery is needed for them).

Only if you take a hit on system or rollback would you have to restore BOTH
system and rollback (regardless of which one took a hit).  Temp of course is
temp and doesn't have to be backed up or recovered.

That can be used to minimize the time to restore this read only database (during
the day) from a cold backup.

      Rating: 5
Very good
      strategy for my database  October 02, 2001

Reviewer:  Pauline  from NY,U.S.A.

      Rating: 4
Does "ALTER
      TABLE ADD PARTITION" command create local indexes in another tablespace ?
        October 03, 2001
Reviewer:  Sunil Gururaj
       from Cyprus
Hi Tom,

Your answers / examples have helped me a lot always. Thanks very much for that.

We have a partitioned table TAB1 with columns C1 and C2 with two partitions p1
(tablespace TT1) and p2 (tablespace TT2), and also a primary key PK1 on column
C1 using local indexes i1 (tablespace TP1) and i2 (tablespace TP2) corresponding
to the partitions of the table. Please note that the indexes are built on index
tablespaces which are different from the actual table partitions themselves. We
further require to add a partition p3 (tablespace TT3) with PK index i3
(tablespace TP3). Although, we can just add a partition, without bothering about
the index as it is automatically created (in the same tablespace as that of the
table partition) using the "ALTER TABLE ADD PARTITION" command, we would like
the Index of the PK to be created on a separate tablespace (TP3). Please note
that the Oracle version is 8.0.6

One alternative solution for this is to first create the table partition
ignoring the fact that the index also is created in the same tablespace as that
of the table partition, and then to issue the command "ALTER INDEX I3 REBUILD
TABLESPACE TP3". We would like to know if this can be done in a single command ?

Thanks again,

          Followup:

There is an enhancement request for this, #751198, but currently there is no
single command.

An alternative is to create a table that looks like your partition -- including
ALL local indexes.  then, add the partition, exchange that partition with this
new table (that has everything where you want it exactly) INCLUDING INDEXES, and
then drop the table (which was the partition)....

      Rating: 3
Add table as to
      other partitioned table as a partition  November 30, 2003

Reviewer:  alex  from uk
Tom
I have one table T1 and another table T2 with partitions. T1 and T2 both the
have the same structure. T2 has 130M of data in different partitions.

Can I insert records in T1 and add this table T1 to T2 as new partiton i yes
please give the full commands for these
Thanks

          Followup:

as long as t1 is going to be a "complete partition" in t2 -- yes.

that is

t1's data will be completely contained in a SINGLE partition in t2
that single partition in t2 is currently 100% empty

you just use alter table exchange partition commands.

search for

"alter table" "exchange partition"

to see many examples

November 30, 2003 Hitesh -- Thanks for the question regarding
      "Function", version 8.1.5

      You Asked
Hi...Guru...
1.I wants to create a function which will return more than one parameter.
2. I wants to create a function in which number of parameter will be not fixed.
Function like greatest().

Thanx in advance.....

      and we said...
1) go for it....

create function f ( x OUT number, y OUT varchar2, z OUT date ) returns
YOUR_RECORD_TYPE

a function can have multiple out parameters

a function can return a composite record with many attributes.

2) you would send an array.

ops$tkyte@ORA920.US.ORACLE.COM> create or replace package my_pkg
  2  as
  3          type array is table of number;
  4
  5          function f( p_data in array ) return number;
  6  end;
  7  /

Package created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace package body my_pkg
  2  as
  3
  4  function f( p_data in array ) return number
  5  is
  6          l_greatest number;
  7  begin
  8          if ( p_data is null OR p_data.count = 0 )
  9          then
 10                  return null;
 11          end if;
 12
 13          l_greatest := p_data(1);
 14          for i in 2 .. p_data.count
 15          loop
 16                  if ( p_data(i) > l_greatest )
 17                  then
 18                          l_greatest := p_data(i);
 19                  end if;
 20          end loop;
 21          return l_greatest;
 22  end;
 23
 24  end;
 25  /

Package body created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> exec dbms_output.put_line( my_pkg.f(
my_pkg.array( 1, 2, 3, 4, 5 ) ) );
5

PL/SQL procedure successfully completed.

Review &
      Followup

      Rating: 3
In
      SQL?  October 19, 2002
Reviewer:  A reader
       from BG
Can we reference such a function (point 1) in pure SQL?

          Followup:

it would have to return an OBJECT type and cannot by definition have OUT
parameters.

ops$tkyte@ORA920.US.ORACLE.COM> create or replace type myType as object
  2  ( x int,
  3    y date,
  4    z varchar2(30)
  5  )
  6  /

Type created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace function f return myType
  2  as
  3  begin
  4          return myType( 55, sysdate, 'Hello World' );
  5  end;
  6  /

Function created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select f()
  2    from dual
  3  /

F()(X, Y, Z)
---------------------------------------------------------------------------------
--------------------------------------------------
MYTYPE(55, '19-OCT-02', 'Hello World')

      Rating: 4
function in
      update statement  October 20, 2002
Reviewer:
      Richard  from Germany
Hello Tom,

is there a possibility to use function returning record
in an SQL update statement like:

update t
set (x, y, z) = f();

Oracle 9i says: ORA-01767: UPDATE ... SET expression must be a subquery ...

Richard

          Followup:

Nope, it must be a subquery - something like:

set (x,y,z) = ( select f.x, f.y, f.z from dual )

      Rating: 4
Its
      wonderful  October 21, 2002
Reviewer:
      Hitesh  from India
Its really gr8. Thanks for the quick reply. Just keep it up...............

      Rating: 4
Its giving
      error...............  October 21, 2002
Reviewer:
      hitesh  from India
If i wants to run 2nd function in normal fashion, how can i do that ? means...
select f(1,2,3,4,5) from dual........

Thankx in advance

          Followup:

not sure that "select f(.....)" would be classified as the "normal fashion",
most of my plsql functions are used in plsql -- i rarely call plsql from sql but
anyway...

giving what error?  My crystal ball is on the blink -- so, I guess I'll just
have to guess.

You are trying the example verbaitim.  SQL doesn't understand PLSQL datatypes --
only SQL types.  So, the example would be:

ops$tkyte@ORA920.US.ORACLE.COM> create or replace type ARRAY as table of number
  2  /

Type created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace package my_pkg
  2  as
  3          function f( p_data in array ) return number;
  4  end;
  5  /

Package created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace package body my_pkg
  2  as
  3
  4  function f( p_data in array ) return number
  5  is
  6          l_greatest number;
  7  begin
  8          if ( p_data is null OR p_data.count = 0 )
  9          then
 10                  return null;
 11          end if;
 12
 13          l_greatest := p_data(1);
 14          for i in 2 .. p_data.count
 15          loop
 16                  if ( p_data(i) > l_greatest )
 17                  then
 18                          l_greatest := p_data(i);
 19                  end if;
 20          end loop;
 21          return l_greatest;
 22  end;
 23
 24  end;
 25  /

Package body created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select my_pkg.f( ARRAY( 1, 2, 3, 4, 5 ) )
from dual;

MY_PKG.F(ARRAY(1,2,3,4,5))
--------------------------
                         5

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

      Rating:
      4
But........  October 22, 2002
Reviewer:
      Hitesh  from India
When we used in-built function we dont have to give any datatype there
eg.greatest(...),least(...). As you have specified ARRAY here. I wants to write
this query like following:

select f(1,2,3,4,5) from dual

          Followup:

but.....  i showed you how to do this and it involves using an array -- a
complex type.

Short of that, you would have to get the source code to the kernel and write
your own f function -- but that won't be happening....

So, if you want VARARGS type support, you'll be using my array concept.

      Rating: 4
Extend the
      Array thought...  October 22, 2002
Reviewer:
      Ken  from Vancouver, Canada
We need to compare records of production/sales on a component basis for a
specific time slice(month) and location.  There are generally 14 components
(could extend) and thousands (growing) locations.  The client request has been
made to compare individual locations by time slice.

Your proposal for the array would seem to fit this request and if it works puts
the work back where I like it..the DB not JAVA.

The question is this...
Can I do the compare in this form and would I pass in two arrays of 14 items or
two objects of 14 properties with these two being IN/OUT parameters and generate
the return of a similar type being the difference?
Can you include a small example?

We are using 9iR2.

          Followup:

what do the tables look like and how does the array concept fit in here?  why
wouldn't I just be returning a result set?

      Rating: 5
difference
      between greatest/least and max/min  November 30, 2003

Reviewer:  A reader
Hi

What are the differences between greatest/least and max/min

from the documentation they seem the same...

          Followup:

greatest/least work IN A ROW

max/min ACROSS ROWS

ops$tkyte@ORA920> select greatest(deptno,sal), least(deptno,sal) from emp;

GREATEST(DEPTNO,SAL) LEAST(DEPTNO,SAL)
-------------------- -----------------
                 800                20
                1600                30
                1250                30
                2975                20
                1250                30
                2850                30
                2450                10
                3000                20
                5000                10
                1500                30
                1100                20
                 950                30
                3000                20
                1300                10

14 rows selected.

ops$tkyte@ORA920> select max(deptno), max(sal), min(deptno), min(sal) from emp;

MAX(DEPTNO)   MAX(SAL) MIN(DEPTNO)   MIN(SAL)
----------- ---------- ----------- ----------
         30       5000          10        800

ops$tkyte@ORA920>

      Rating:
      5
concat  November 30, 2003
Reviewer:
      Sudhir
Why oracle does not have following, seems like would be useful

select deptno, sum(sal), count(*), concat(empid,' ,') names
from emp
group by deptno order by 1;

Thanks

          Followup:

search for stragg on this site.

problem is -- it is very hard to solve generically as the result of concat would
quickly exceed 4000 in many real cases.

in 9i, with the ability to create user defined aggregates, you have the ability
to add anything you think "we forgot"

      Rating: 5
Fantastic!!
      eom  November 30, 2003
Reviewer:  Sudhir

      Rating:
      4
  November 30, 2003
Reviewer:  Gary
       from Sydney, Australia
Stragg is great.

"problem is -- it is very hard to solve generically as the result of concat
would quickly exceed 4000 in many real cases."

I was told by an Oracle person (way back -probably when Oracle7 was new and
exciting) that the issue was an ordering one.
IE 1+2+3+4 is always the same as 1+4+2+3, so SUM(col) is easy.
but 'A'||'B'||'C' isn't the same as 'C'||'B'||'A'
so that either
(a) a CONCAT(col) function would end up returning different results with the
same input data (ie non-deterministic) or
(b) there would have to be a layer of sort between the selecting and the
concatenation.

(b) isn't impossible, but probably not very pretty from a performance point of
view.

          Followup:

that to -- my stragg is non-deterministic in fact!  good point.

      Rating: 5
As regards
      function in an update  December 01, 2003
Reviewer:
      Kevin  from Connecticut USA
Hi Tom, just a followup for the person above.  AS you once showed be me before
(think it was you), if his need or desire was to use an existing function that
gave the desired results, as input to an update it is possible to wrap the
function in a select to satisfy syntax.  Thus he could use his function code in
the update.  Here is an example:

SQL> set feedback 1
SQL>
SQL> drop type c_temp1
  2  /

Type dropped.

SQL> drop type o_temp1
  2  /

Type dropped.

SQL> drop table a
  2  /

Table dropped.

SQL> drop function foo
  2  /

Function dropped.

SQL>
SQL> create type o_temp1 is object (a number,b number)
  2  /

Type created.

SQL>
SQL> create type c_temp1 is table of o_temp1
  2  /

Type created.

SQL>
SQL> create table a (a number,b number)
  2  /

Table created.

SQL>
SQL> insert into a values (1,1)
  2  /

1 row created.

SQL>
SQL> commit
  2  /

Commit complete.

SQL>
SQL> create function foo return c_temp1 is
  2      c_temp1_v c_temp1 := c_temp1();
  3  begin
  4      c_temp1_v.extend;
  5      c_temp1_v(1) := o_temp1(2,2);
  6      return c_temp1_v;
  7  end;
  8  /

Function created.

SQL> show errors
No errors.
SQL>
SQL> select * from a
  2  /

         A          B
---------- ----------
         1          1

1 row selected.

SQL>
SQL> update a set
  2      (a,b) = (select* from table(cast(foo as c_temp1)))
  3  /

1 row updated.

SQL>
SQL> select * from a
  2  /

         A          B
---------- ----------
         2          2

1 row selected.

SQL>
SQL> rollback
  2  /

Rollback complete.

SQL>
SQL> select * from a
  2  /

         A          B
---------- ----------
         1          1

1 row selected.

SQL>
SQL> spool off

December 01, 2003 -- Thanks for the question regarding "SQL
      Performance", version 8.1.7

      You Asked
Hi Tom:

The attached query takes a long to complete.  How can I speed it up?

select person_nm, deal_id, transaction_dt, api_trans_tm,
  start_dtm, stop_dtm, point_sn, total_amt
from api_liquidation a, point e, person f,
  (select b.api_rec_id
  from api_liquidation b, api_liquidation c
  where b.api_rec_id = c.api_rec_id
    and b.api_status_code = 2
    and c.api_status_code = 2
    and b.api_op_code in ('N','U')
    and c.api_op_code = 'D' ) d
where a.api_rec_id = d.api_rec_id(+)
  and d.api_rec_id is null
  and a.api_status_code = 2
  and trunc(a.start_dtm) < trunc(add_months(start_dtm,1),'MONTH')
  and trunc(a.transaction_dt) > last_day(start_dtm)
  and a.api_op_code = 'N'
  and a.line_type_id <> 25
  and a.sd_from_point_id = e.point_id
  and f.person_id = a.trader_id
order by person_nm, deal_id, transaction_dt, api_trans_tm,
  start_dtm, stop_dtm, point_sn, total_amt

Thanks,
Tom

      and we said...
No idea.  My crystal ball is in the shop.

So, I'll use this instead to point out how to not ask a question (you are
anonymous here, no one knows who you are).

There is no tkprof
There is no query plan
There is no info as to whether you use CBO or RBO
No info as to how long it takes (long time -- not too descriptive)
No info as to how long you feel it should take
No info as to number of rows, cardinality of columns
No information on table structures (so, what columns come from which tables)

In short -- nothing to work with.

the outer join to the self join looks really ugly.  I sure would look at that.
Review &
      Followup

      Rating: 5
Tuning
      SQL  November 24, 2003
Reviewer:  Rory
      Concepcion  from Philippines
Hi Tom,

Congrats on your new book.

We have an extraction in our system that runs daily at 12mn.
The problem is that it is taking tooooo long. About 12 hrs.
I saw the query that was taking too long. I'll paste down with the statistics
and explain plan and timing on. I need to at least let that query run for "1
second". It is because that query processes one sequence at a time.

The variable but I'll substritute a value for this test.

SEQ := 16524354 NUMBER

SQL> l
  1  select count(*)
  2    from cs_for_inv_bllvvd
  3   where (tmp_blhe_seq_no = 16524354 and  tmp_blcde_seq_no = 1)
  4    or (('BL#'||tmp_blhe_seq_no) in (select shipping_seal_no
  5                                        from ft_bl_container_detail
  6                                        where blhe_seq_no = 16524354
  7                                        and blcde_cont_code <>
'WHSECONTAINER'))
  8*   and proc_flag = 'N'
SQL> /

  COUNT(*)
----------
         1

Elapsed: 00:00:29.00

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2273 Card=1 Bytes=12
          )

   1    0   SORT (AGGREGATE)
   2    1     FILTER
   3    2       TABLE ACCESS (FULL) OF 'CS_FOR_INV_BLLVVD' (Cost=2273
          Card=23508 Bytes=282096)

   4    2       TABLE ACCESS (BY INDEX ROWID) OF 'FT_BL_CONTAINER_DETA
          IL' (Cost=4 Card=1 Bytes=31)

   5    4         INDEX (RANGE SCAN) OF 'BY_SHIPPING_SEAL_NO2' (NON-UN
          IQUE) (Cost=3 Card=1)

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

What can you advise me or if you have a better way of writing this code. I
really need to make it to fast. Thanks a lot Tom.

          Followup:

why are you counting the rows?

the best way to speed something up is not to do it at all.  I find spurious, not
necessary count(*)'s to be a huge contributor to runtimes in "batch" processes.
999 times out of 1,000 -- they are wholly unnecessary.

So, tell us - why are you counting rows?

      Rating:
      5
  November 29, 2003
Reviewer:  Rory
      Concepcion  from Philippines
Hi again Tom. With regards to the counting. Actually it isn't my code. The query
I showed you is just for one record. I believe that the developer is reading a
table and for each record in that table, he needs to count how many were
processed based from other tables. What is an alternative from counting.

From the sql statement above, my colleagues were able to tune it into the
following sql statement. WIth this our extraction run from 4 hours (with the sql
statement above)to 20 mins(with the sql statement below).

                select count(*)
                  into v_count_for_process
                  from cs_for_inv_bllvvd
                 where tmp_blhe_seq_no = p_blhe_seq_no
                           and  tmp_blcde_seq_no = 1
                               and proc_flag = 'N';

                  v_count_for_process2:=0;
                  for c1 in (select substr(shipping_seal_no,4) shipping_seal_no
                                from ft_bl_container_detail
                            where blhe_seq_no = 9005488472
                              and blcde_cont_code <> 'WHSECONTAINER'
                            and shipping_seal_no like 'BL#%') loop

                  select v_count_for_process2 + count(*)
                    into v_count_for_process2
                    from cs_for_inv_bllvvd
                   where not (tmp_blhe_seq_no = p_blhe_seq_no
                   and  tmp_blcde_seq_no = 1
                   and proc_flag = 'N')
                     and tmp_blhe_seq_no = to_number(c1.shipping_seal_no)
                     and proc_flag = 'N';
                  end loop;
              v_count_for_process := v_count_for_process + v_count_for_process2;

          Followup:

and think how fast it could be if you avoided counting.  again, I would ask
(seriously ask) "why", to what end are we doing that

      Rating:
      5
  December 01, 2003
Reviewer:  Rory
       from Philippines
Hi Tom,

I understant what you're trying to say about counting.
Anyway, with regards to our extraction, there is also one
which running for so loooong.
I got the sql_text from v$sql for that extraction.
It is running for many hours. What do think should we do about this. thanks.

SQL> SELECT DECODE (A.ACTY_CODE, 'TC', 'TOURIST'),R.ORIGIN,R.DESTINATION,R.START
  2  FROM NX_RESERVATION_ITEMS R,NX_ACCOMMODATIONS A
  3  WHERE R.ACCO_CODE= A.ACCO_CODE
  4  AND R.STAT='CF'
  5  AND R.PAID_DATE IS NOT NULL
  6  AND A.ACTY_CODE= 'CABIN'    -- variable here
  7  AND R.ORIGIN = 'DUM'        -- variable here
  8  AND R.DESTINATION = 'MNL'   -- variable here
  9  AND TRUNC (R.START_ETD)  = TRUNC (sysdate)  -- variable
 10  ORDER BY R.START_ETD
 11
SQL>
SQL> set timing on
SQL> set autotrace on
SQL> /

no rows selected

Elapsed: 00:00:22.09

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4812 Card=2 Bytes=88
          )

   1    0   SORT (ORDER BY) (Cost=4812 Card=2 Bytes=88)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'NX_RESERVATION_ITEMS'
          (Cost=2404 Card=1 Bytes=33)

   3    2       NESTED LOOPS (Cost=4810 Card=2 Bytes=88)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'NX_ACCOMMODATIONS'
           (Cost=2 Card=2 Bytes=22)

   5    4           INDEX (RANGE SCAN) OF 'ACCO_ACTY_FK_I' (NON-UNIQUE
          ) (Cost=1 Card=2)

   6    3         INDEX (RANGE SCAN) OF 'REIT_ACCO_VOYAGE' (NON-UNIQUE
          ) (Cost=1870 Card=1069)

Statistics
----------------------------------------------------------
        217  recursive calls
          0  db block gets
       6779  consistent gets
       4408  physical reads
          0  redo size
        420  bytes sent via SQL*Net to client
        373  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          0  rows processed

Is it possible to cut the time to at least 2secs?
THanks and God bless!

          Followup:

always need a tkprof to go with the autotrace and as well, understanding what
indexes are in place would be useful

given this predicate:

  3  WHERE R.ACCO_CODE= A.ACCO_CODE
  4  AND R.STAT='CF'
  5  AND R.PAID_DATE IS NOT NULL
  6  AND A.ACTY_CODE= 'CABIN'    -- variable here
  7  AND R.ORIGIN = 'DUM'        -- variable here
  8  AND R.DESTINATION = 'MNL'   -- variable here
  9  AND TRUNC (R.START_ETD)  = TRUNC (sysdate)  -- variable
 10  ORDER BY R.START_ETD

changed to this:

  3  WHERE R.ACCO_CODE= A.ACCO_CODE
  4  AND R.STAT='CF'
  5  AND R.PAID_DATE  > to_date('01010001','ddmmyyyy')
  6  AND A.ACTY_CODE= 'CABIN'    -- variable here
  7  AND R.ORIGIN = 'DUM'        -- variable here
  8  AND R.DESTINATION = 'MNL'   -- variable here
  9  AND R.START_ETD BETWEEN  = TRUNC (sysdate) and
trunc(sysdate+1)-1/24/60/60
 10  ORDER BY R.START_ETD

an index on

acty_code,origin,destination,stat,paid_date,start_etd
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ those 4 can be in any order

we may find that the predicate is answered from an index range scan, cutting way
down on table access by index rowids and the sort is obviated

December 01, 2003 Lindsay -- Thanks for the question regarding
      "Referencing %rowtype variables without using column names", version
      8.1.7.2.5

      You Asked
I have a 3rd party product (which can't be changed) which needs to call a PL/SQL
stored procedure (which I am writing) for which an input parameter is a %rowtype
variable. I need to create many of these stored procedures.

The problem I am having is - I want to "extract" the values of the columns from
this %rowtype variable without "hard coding" the name of each column. I can
lookup up the column names in user_tab_columns but am unable to "dereference"
the column names to get its values from the %rowtype variable.

What I would like to do is of the following form (pseudo code) -

procedure p_get_col_values (p_table_A%rowtype) is
  d_var     varchar2(255);
  cursor c_tab_cols (c_table_name) is
    select column_name, data_type, column_id
    from   user_tab_columns
    where  table_name = c_table_name
    order  by column_id;
begin
  ..
  for c_rec in c_tab_cols loop
    if    c_rec.data_type = 'DATE' then
      d_var := to_char("value_of"(c_rec.column_name), 'dd-Mon-yyyy hh24:mi:ss);
      ...
    elsif c_rec.data_type = 'NUMBER' then
      d_var := to_char("value_of"(c_rec.column_name));
      ..
    else
      ..
    end if;

I have had a look at

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:235814350980
but I don't have a cursor.

What I'm trying to achieve is similar in functionality to the NAME_IN function
in Developer Forms or in C this would be a case of double indirection. I am
unsure if this can be achieved in PL/SQL. Any help would be greatly appreciated.

Lindsay Hess

PS. Love the book 'Expert One-on-One'

      and we said...
Well, since you love the book -- i'll tell you one way to do this.  It'll be
less performant then coding the name cause we have to use dynamic sql, but we
can do it.

It can look like this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package demo_pkg
  2  as
  3      emp_rec   emp%rowtype;
  4
  5      -- other records here for other tables
  6  end;
  7  /

Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure your_procedure(
p_record in emp%rowtype )
  2  as
  3      l_data  long;
  4  begin
  5      demo_pkg.emp_rec := p_record;
  6
  7      for x in ( select column_name, data_type
  8                   from user_tab_columns
  9                  where table_name = 'EMP' )
 10      loop
 11          if ( x.data_type = 'DATE' )
 12          then
 13              execute immediate
 14                 'begin
 15                    :x := to_char( demo_pkg.emp_rec.' || x.column_name || ',
 16                                 ''dd-mon-yyyy hh24:mi:ss'' );
 17                  end;' using OUT l_data;
 18          else
 19              execute immediate
 20                 'begin
 21                    :x := demo_pkg.emp_rec.' || x.column_name || ';
 22                  end;' using OUT l_data;
 23          end if;
 24          dbms_output.put_line( x.column_name || ' = ' || l_data );
 25      end loop;
 26  end;
 27  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2      for x in ( select * from emp where rownum = 1 )
  3      loop
  4          your_procedure( x );
  5      end loop;
  6  end;
  7  /
EMPNO = 7369
ENAME = A
JOB = CLERK
MGR = 7902
HIREDATE = 17-dec-1980 00:00:00
SAL = 800
COMM =
DEPTNO = 20

PL/SQL procedure successfully completed.

Note you don't need to do numbers special, they'll be converted OK, just
dates...
Review
      & Followup

      Rating: 5
Joining data
      dictionary column names with values in a row  June 11, 2002

Reviewer:  Dave Fowler  from St.Louis MO USA

This is very similar to what I need to allow a Java application get the column
name with the row value for that column from a single row in table/view query.

Thanks.

      Rating:
      5
%rowtype  December 01, 2003
Reviewer:
      mo
Tom:

I have a table named "History" and a variable declared baed on it in a procedure
in a package.

l_hist   history%rowtype;

When I added another procedure named "History" to the package, I gor an error
for the vairiable declaration saying that

 (1): PLS-00310: with %ROWTYPE attribute,
'HISTORY' must name a table, cursor or cursor-variable

I think PL/SQL is loooking at history as a procedure name now.  How can I force
it as a table declaration?

Thank you,

          Followup:

change the name of the procedure :)

rules of scoping are kicking in here -- plsql scope is overriding the sql scope.

      Rating:
      5
Super!  December 02, 2003
Reviewer:
      Orca777  from Germany
short and effective !

December 01, 2003 Pushparaj -- Thanks for the question regarding
      "Combining Multiple REF CURSORs into one", version 8.1.7

      You Asked
Tom,

Is it possible to call a stored procedure that returns the cursor through the
parameter in a loop and concatenate all the cursors into one in the calling
program.

For example,

CREATE OR REPLACE PACKAGE p_emp_pack AS
    TYPE GenCursorType IS REF CURSOR;
    PROCEDURE emp_cursor ( par_dno IN NUMBER, emp_cur IN OUT GenCursorType);
END;
/
CREATE OR REPLACE PACKAGE BODY p_emp_pack AS
    PROCEDURE emp_cursor ( par_dno IN NUMBER, emp_cur IN OUT GenCursorType )
    IS
    BEGIN
        OPEN emp_cur FOR
        SELECT empno,sal FROM emp WHERE deptno=par_dno;
    END;
END;
/

What I have currently:
DECLARE
  dno       dept.deptno%TYPE;
  eno       emp.empno%TYPE;
  esal      emp.sal%TYPE;
  ecursor   emp_pack.GenCursorType;
BEGIN
  dno := 10;
  emp_cursor ( 10, ecursor );

  LOOP
  FETCH ecursor INTO eno,esal;
  EXIT WHEN ecursor%NOTFOUND;
  END LOOP;
END;
/

What I am looking for is:
DECLARE
   CURSOR c1 IS SELECT deptno FROM dept;
   ecursor   emp_pack.GenCursorType;
   all_cur   emp_pack.GenCursorType;
BEGIN
   FOR rec IN c1 LOOP
     emp_pack.emp_cursor ( rec.deptno, ecursor );

     /** Keep storing the ecursor into the all_cur **/

   END LOOP;

   LOOP
   FETCH all_cur INTO eno,esal;
   EXIT WHEN all_cur%NOTFOUND;
   END LOOP;
END;
/

This program like emp_pack.emp_cursor is called from the Visual Basic client
application. The VB passes the input parameter and receives the cursor back. Now
there is a need wherein certain scenarios the VB wants to call a wrapper
procedure and the wraper procedure basically derives the input parameter for the
emp_pack.emp_cursor and
calls multiple times in a loop and finally sends the REF CURSOR to the VB.

The real application programs (similar to emp_pack.emp_cursor) have very complex
coding and the cursor is opened by joining many tables. So there is no way I can
do it one call by sending all the input parameter values as an array to
emp_pack.emp_cursor and get the
single cursor for all from the emp_pack.cursor itself.
That is why I want to create a wrapper procedure which simply calls the existing
emp_pack.cursor in a loop with various input parameters.

If combining cursors into one is not possible then please advice me the other
possible best options for this scenario.

Please advise me.

Thanks
Pushparaj

      and we said...
It is not possible.

Why cannot you send all of the inputs in one call?  I've yet to see a case where
you cannot do that.  We can use Native Dynamic sql to build a query based on the
inputs dynamically.  My suggestion is a dynamically opened ref cursor -- similar
to this:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1288401763279
....
Review & Followup

      Rating: 3
Combining two
      or more cursors into one  November 28, 2003

Reviewer:  Praveen  from Bangalore
Hi Tom,

I agree with your explanation that using dynamic sql, you can programatically
built queries based on inputs. But what if two or more cursors are needed to be
built separately and then combine the result sets? Why oracle does not provides
a method to combine one or more cursors together,
either programatically or as a built-in feature of the oracle itself?

Take a scenario like this.

FUNCTION f1 (parameters.....) return a_type_ref_cursor_declared_globally
is
    a_ref_cursor a_type_ref_cursor_declared_globally;
    cursor cur_1 is select fields_1 from table_1;
    cursor cur_2 is select fields_2 from table_2;
begin

for r1 in cur_1
loop
    .........
    <some work done here>
    .........

    for r2 in cur_2
    loop
        ..........
        <some more work done here>
        ..........

        select some_fields_and_do_sql_calculations
        into a_temp_table
        from a_table
        where condition_1 = fields_of_cur_1
        and condition_2 = fields_of_cur_2;

    end loop; --cur_2
end loop; --cur_1

open a_ref_cursor for
select * from a_temp_table;

return a_ref_cursor;

end function_f1;

I am forced use a temporary table here to store the intermediate result
and query the data back from it to a ref_cursor to return as function value;
only because oracle does not provide a feature to do the same thing
using the cursors itself. ie something like combining two or more
cursors into a 'super-cursor'. Ofcourse the latter method would be
much more faster than using a temporary table.

You may suggest, for the above example, an alternate method like this

        select some_fields_and_do_sql_calculations
        into a_temp_table
        from a_table
        where condition_1 = (select fields_1 from table_1)
        and condition_2 = (select fields_2 from table_2);

But this may not be possible in all cases...right? ie you cannot always
build a single query (simple or complex) instead of trying to
'combine two or more cursors together'.

Or does a UNION will always solve the problem?

Any comments?

Thanks Tom

warm regards

Praveen

          Followup:

we do -- it is called "union all"

but -- i would never process a query the way you describe.  Look for a way to
just return "result set".  temporary tables are for sqlserver programmers.

you have inline views
you have set operations

so - give me an example, one you think "cannot be done in a single cursor"

they all can be.

      Rating: 2
Combining
      Multiple REF CURSORs into one  November 28, 2003

Reviewer:  Ilya Mayzus  from New York, NY
Hi Tom,

I find your site absolutely invaluable! I'm doing a conversion from sqlserver to
oracle, and in most cases I manage to convert temptables to inline views.
However, there is one case where I am forced to use temp tables: when the query
spans several months, it does a union of queries on monthly tables. In this
case, having the ability to combine ref cursors would be quite useful. In
theory, I could just append those unions in one large sql statement, but I'm
concerned about the final size of that query. Or would you still suggest using a
CLOB as a placeholder for the final query and avoid temp tables? Also, what do
you think of using indexes on temp tables as a way of speeding up the query?

Thanks,
ilya

          Followup:

like i said -- give me a WORKING example, something small -- concise -- yet 100%
complete (like my examples) and I'll show you how to do that in Oracle.

Why you would use "monthly tables"????  what is "monthly table"?  please don't
say you create tables for each month's worth of data???

I don't like temp tables much -- I use them ONLY when I have to.  They are
typically just "overhead" and a single query is generally much more effective
and efficient.

      Rating: 2
Combining
      Multiple REF CURSORs into one  December 01, 2003

Reviewer:  Ilya Mayzus  from New York, NY
Hi Tom,

yes, monthly tables are exactly what they sound like -- we have a set of about
10 tables like ephist1_1999DEC, ephist2_1999DEC, ephist3_1999DEC, epirn_1999DEC,
tpmsghist_1999DEC, eploghist_1999DEC etc. and we create a new set each month via
a stored procedure. Each table has a primary key (called 'IRN') that is an
integer corresponding to a julian date of the current day + six-digit sequence
number (like 335000001, 335000002, etc. -- 335 would be a julian date for one of
the days in december). Just to make matters more concrete, here are abbreviated
definitions of ephist tables:

CREATE TABLE EPHIST1_1999DEC
(
  IRN                            INTEGER        NOT NULL,
  SRN                            VARCHAR2(20 BYTE),
  CREDIT_PARTY_TYPE              CHAR(1 BYTE),
  CREDIT_PARTY_ID                VARCHAR2(34 BYTE),
  CREDIT_PARTY_NAME              VARCHAR2(35 BYTE),
  CREDIT_PARTY_ADDRESS1          VARCHAR2(35 BYTE),
  CREDIT_PARTY_ADDRESS2          VARCHAR2(35 BYTE),
  CREDIT_PARTY_ADDRESS3          VARCHAR2(35 BYTE)
)

CREATE TABLE EPHIST2_1999DEC
(
  IRN                             INTEGER       NOT NULL,
  INTERNAL_MESSAGE_TYPE           VARCHAR2(4 BYTE),
  DEBIT_PARTY_ACCOUNT             INTEGER
)

CREATE TABLE EPHIST3_1999DEC
(
  IRN                           INTEGER         NOT NULL,
  TRANSACTING_PARTY_NAME        VARCHAR2(35 BYTE),
  EXCHANGE_RATE                 FLOAT(126)
)

CREATE TABLE EPIRN_1999DEC
(
  INTERNAL_IRN  INTEGER                         NOT NULL,
  CLIENT_IRN    VARCHAR2(18 BYTE)               NOT NULL
)

EPIRN table only has two columns, the other three tables have between 130 and
200 columns each. They are usually referenced as a join and should really be one
big table, but were split for performance reasons a long time ago (please take
into account that we're talking about SQL Server - based application that we're
trying to convert to oracle, but SQL Server version will still need to be
supported).

So -- here's the problem: when I have a large query for several months, I have
to do something like:
Select * from ephist1_1999DEC E1 join ephist2_1999DEC E2 on E1.irn = E2.irn join
ephist3_1999DEC E3 on E1.irn = E3.irn
join epirn_1999DEC EI on E1.irn = EI.irn
UNION ALL
Select * from ephist1_1999NOV E1 join ephist2_1999NOV E2 on E1.irn = E2.irn join
ephist3_1999NOV E3 on E1.irn = E3.irn
join epirn_1999NOV EI on E1.irn = EI.irn
UNION ALL
etc.

I'm also simplifying a bit, because usually there are other tables involved that
are joined in the query, although they are usually not 'monthly', and also when
all requested columns are spelled out the query for one month can easily take
VARCHAR2(4000).

I know if I were designing things from scratch, it would be one static EPHIST
table with all columns from the three tables and with data from all months that
is archived as necessary. However, in this case if I did propose a big redesign
change, I'd also need to do it to SQL Server version as well, and also convince
the original developers that the change would be workable on the SQL Server
version as well. So I would prefer to find some solution that doesn't require
drastic changes. Is there anything you can suggest?

thanks,
ilya

          Followup:

how did you handle it in sqlserver?  if you want to conform to the lowest common
denominator, thats what we'll have to do.  you'll be using temporary tables and
running not nearly as good as you should.

December 02, 2003 bhashyakar -- Thanks for the question regarding
      "checkpoint", version 8.1.6

      You Asked
why is a check point necessary ?what is its acutual purpose?
why is check point necesary during instance recovery when u have a sid?

      and we said...
a checkpoint is the act of flushing modified, cached database blocks to disk.
Normally, when you make a change to a block -- the modifications of that block
are made to a memory copy of the block.  When you commit -- the block is not
written (but the REDO LOG is -- that makes it so we can "replay" your
transaction in the event of a failure).   Eventually, the system will checkpoint
your modified blocks to disk.

there is no relationship between "checkpoint" and sid and instance recovery does
not imply "checkpoint".  a checkpoint reduces the amount of time it takes to
perform instance recovery.
Review & Followup

      Rating: 4
instance
      recovery  August 13, 2002
Reviewer:
      A reader
Hi Tom

I dont understand how instance recovery is reduced by checkpoint

For example:

database no archive log

1. I have 3 redo log groups
2. I have 10 uncomitted transactions, 3 in redo 1 , 3 in redo 2 and 4 in redo 4
3. the uncomitted data are written to datafiles
4. redo 1 is overwritten by new redo
5. instance failre
6. we start database

instance recovery starts:
rolling forward to recover data that has not been recorded in the datafiles, yet
has been recorded in the online redo log, including the contents of rollback
segments.

Marking all transactions system-wide that were active at the time of failure as
DEAD. I dont understand how Oracle can know what transactions were active in the
time of failure? I mean for example my uncomitted transactions in redo 1 has
been overwritten then from where does Oracle obtain information about these 4
uncomitted transactions?

thank you!

          Followup:

Isn't it obvious that:

...
instance recovery starts:
rolling forward to recover data that has not been recorded in the datafiles, yet

has been recorded in the online redo log, including the contents of rollback
segments.
...

would take less time if there was 100 blocks not recorded in the datafiles
versus 10000?  checkpointing is the act of flushing those blocks to disk, making
it so we need not recover them upon instance failure....  the less blocks we
have to recover, the less time it'll take.

The information is in the rollback segments (which we just recovered) as to what
was going on at the time of failure.  The details really are not relevant, the
fact that it does know is.

      Rating: 5
from rollback
      segments  August 14, 2002
Reviewer:
      A reader
Hi

Does Oracle get dead transaction information from RBS segment headers? From that
small transaction table in RBS header I mean...

          Followup:

yeah

      Rating:
      4
  August 14, 2002
Reviewer:
      A reader
...
instance recovery starts:
rolling forward to recover data that has not been recorded in the datafiles, yet

has been recorded in the online redo log, including the contents of rollback
segments.
...

Since at every log switch there is an implicit checkpoint which consequently
triggers CKPT process to update the file headers and CKPT then triggers DBWR to
flush dirty blocks to disk does this mean that Oracle always read the last
current and active redo only therefore speeding up instance recovery? And that's
why it does not matter if older redo logs have been overwritten since uncomitted
transactions informations can be obtained from rollback segments transaction
table and can be rollback by reading that? (no redo is needed to rollback these
uncommitted transaction)

          Followup:

No, because the checkpoint need not have completed.  Say we have 10 log files.
When we switch from 1 to 2 then 2 to 3 then .... then 9 to 10 -- now we want to
switch from 10 to 1.  The blocks protected by 1 MIGHT NOT YET BE CHECKPOINTED.
You might get a message "checkpoint not complete cannot allocate new log" in the
alert, the system will pause whilst the checkpoint completes.

We might have to read every redo log file upon crash recovery.

We will not reuse a redo log until the blocks it "protects" are flushed to disk
and once they are we no longer need that redo (that is why it is OK for older
redo logs to be reused)

      Rating: 3
Checkpoint
      Queue  September 16, 2002
Reviewer:  A
      Reader  from INDIA
What is Checkpoint Queue? What is the underlying mechanism for Oracle
checkpointing. How internally and in how many steps it does it.?

Please give valuable advise.

          Followup:

valuable advice = read the server concepts guide, chock full of really cool
information.

      Rating:
      5
checkpoint  October 09, 2002
Reviewer:
      atul  from india
Sir,

At checkpoint dirty blocks from database buffer are written to the datafiles.Now
for example an uncommitted transaction
like the update statement in my example is present in the buffer ,
it will be subsequently written to the data file at checkpoint.
    So now datafile contains some uncommitted data.Isn't it ??

Thanks.
atul

          Followup:

yes, datafiles are "fuzzy" -- pretty much all of the time

A key piece of knowledge here is that BEFORE DWBR will flush the blocks to disk
-- it will signal LGWR to flush the redo.

So, if we write a block that contains UNCOMMITTED data (happens all of the time)
and we "crash" right then - before the commit -- we are OK.

o upon restart we recover using the REDO
o this recovery includes recovering ROLLBACK (UNDO)
o we can then rollback the uncommitted changes from the ROLLBACK after roll
forward recovery

      Rating:
      5
checkpoint  October 10, 2002
Reviewer:
      atul  from india
Sir,

# When a DML is issued , there are two blocks in the database buffer

 a) A modified data block that contains new value & a block of rollback segment
that contain old image.
 b) Also Redo ( buffer ) is generated that contains old & new image (values).

CASE 1: THERE IS NO COMMIT.
        # Users are reading data from rollback blocks ( old image )
Case2: Commit happens ?? then they should read data from buffer that contains
new values & rollback blocks should disappear.

How does it happen ?

Thanks.
atul

          Followup:

If you have my book "Expert one on one Oracle" -- read the architecture, redo,
rollback and transaction sections.  I go into this in detail.

Short of that, read:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/c23cnsis.htm#2570
just because the user committed does not imply at all that the rollback is no
longer needed.  You need to understand our multi-versioning and consistent read
mechanisms.  that undo data might be needed for a long time after the user
commits.

      Rating: 3
Few
      Clarifications  October 10, 2002
Reviewer:
      Santhosh  from IND
Hi Tom,

Just a clarification on the answers u gave...

Quote your answer -
1.  "When you commit -- the block is not
written (but the REDO LOG is -- that makes it so we can "replay" your
transaction in the event of a failure)."

Question : Ok, when a user commits the data, if the data is not immediately
written on to the block, then how is concurrency handled ? On commit from User
A, if user B wants to query the data just saved, then how is he able to get the
updated data ??

2.
"...just because the user committed does not imply at all that the rollback is
no longer needed....  that undo data might be needed for a long time after the
user commits."

Question : It was assumed that once a user commits the data, the rollback is
cleared. But, as per ur answer u r saying that it "might be needed for a long
time after the user" ? Could u explain it's purpose ?

Rgds,

Santhosh

          Followup:

1) from the buffer cache of course, we look there for the data before going to
disk.

2) Read

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96524/c21cnsis.htm#17882
you'll see that rollback (undo) data is used when you SELECT data as well -- it
would be used to provide the read consistent view of the data for a long running
query.

      Rating: 4
not clear this
      part  March 03, 2003
Reviewer:
      A reader
you said

"No, because the checkpoint need not have completed.  Say we have 10 log files.
When we switch from 1 to 2 then 2 to 3 then .... then 9 to 10 -- now we want to
switch from 10 to 1.  The blocks protected by 1 MIGHT NOT YET BE CHECKPOINTED.
You might get a message "checkpoint not complete cannot allocate new log" in the
alert, the system will pause whilst the checkpoint completes.
We might have to read every redo log file upon crash recovery.
We will not reuse a redo log until the blocks it "protects" are flushed to disk
and once they are we no longer need that redo (that is why it is OK for older
redo logs to be reused)"

I dont understand this fully, for example I have 4 redo log groups and an
uncommitted transaction recorded in the first group if the corresponding dirty
blocks are flushed to disk and the first group has been overwritten by new redo
vectors what happens if instance fails? I assume the redo logs are needed to
rebuild the RBS of my uncommitted transaction but is that information to rebuild
the RBS available anywhere? Or Oracle simply reads ths RBS from the datafiles in
disk? (No need to rebuild the RBS for this specific transaction?)

So in resume, I guess Oracle does roll forward using the redo logs but it also
reads RBS (NOT rebuilt using the redo) to rollback my uncommitted transaction.
Am I correct? Or all RBS are rebuilt using redo logs?

          Followup:

... if the corresponding dirty
blocks are flushed to disk and the first group has been overwritten by new redo
vectors what happens if instance fails? ....

then when we recover we would:

a) roll forward from all changes in the redo logs.  this INCLUDES rolling
"rollback" forward -- we generate redo for undo

b) roll back any uncommitted changes using the UNDO information.

In fact -- even of the corresponding dirty blocks WERE NOT flushed to disk --
the recovery process would have recreated those same dirty blocks AND THEN
rolled them right back again.  So no matter if flushed or not, we would have
"gotten them again"

      Rating: 4
thx for prompt
      reply  March 03, 2003
Reviewer:
      A reader
Hi again

Thank you very much for prompt reply.

you said
"a) roll forward from all changes in the redo logs.  this INCLUDES rolling
"rollback" forward -- we generate redo for undo"

however what I meant initially is what happen if the redo to build the rollback
is overwritten? I mean if we always redo to rebuld RBS then what happens if I
have a uncommitted transaction if it does not commit for long timeand the redo
of this transaction and the redo of RBS related to this transaction are
overwritten?

          Followup:

the rollback would have been flushed to disk just like any other data.

If there is UNDO that is protected by REDO -- the redo will not be reused until
those dirty UNDO blocks are likewise flushed to disk -- just like data.

So, we'll overwrite the REDO after the UNDO is safely on disk and no longer
needs it.

      Rating: 5
Commited data
      not "guaranteed" ?  March 05, 2003
Reviewer:
      David
Tom, I was thinking of a scenario that sometimes scares me...

**From a database perspective** -- theoretically -- when data is commited it
inevitably goes to the redo log files on disk.

However, there are other layers between the database and the hardware. I mean,
the commited data doesn't go "directly" to disk, because you have "intermediate"
structures like i/o buffers, filesystem buffers, etc.

1) What if you have commited and the redo data has not yet "made it" to the redo
log. In the middle of the way -- while this data is still in the OS cache -- the
OS crashes. So, I think, Oracle is believing the commited data got to the redo
logs -- but is hasn't in fact **from an OS perspective**. It just "disapeared"
while in the OS cache. So redo would be unsusable. Is it a possible scenario ?

3) I imagine the so-called journaled file systems can recover from this kind of
crash, but how about non-journaled file system and systems that use raw devices
?

4) Could you point me to some links or threads in this regard ?

Thanks

          Followup:

the data does go to disk.  We (on all os's) use forced IO to ensure this.  We
open files for example with O_SYNC -- the os does not return "completed io"
until the data is on disk.

It may not bypass the intermediate caches and such -- but -- it will get written
to disk when we ask it to.

1) that'll not happen.  from an os perspective, it did get to disk

3) not a  problem, the data is on disk.

4) not sure -- anyone else have any pointers?

      Rating:
      5
JFS  March 05, 2003
Reviewer:  Jim

I first heard about JFS at this link

http://www.linuxgazette.com/issue55/florido.html
A quick search on google.com returned a stack of
sites

Regards
Jim

      Rating: 4
datafile - data
      and header  August 07, 2003
Reviewer:  ygp
       from India
In your book you mention that LGWR (now, may be, CKPT) writes the headers of the
datafiles; whereas the data in the datafiles are written by the DBWR / DBWn.

Does not this increase to the need of locking / latching ?
(One moe point of contention ?!)

          Followup:

no, it is just another guy doing it -- freeing lgwr to go back to its job that
much sooner.

      Rating: 5
another
      question....  August 11, 2003
Reviewer:
      A reader
Tom,

ver: 8.1.7.4

Is if the log file size is 512M. Log_check_point_interval is
25000. log_check_point_timeout = 0.

db_block_buffers= x = fast_start_io_target.

I am once checkpointing around 12M (along with regular checkpointing) . That is
i am checkpointing around 50 times per log file with this one only. Don't you
think its really too high to get "check point not complete " error message....

Further i am switching the log file once every 4-5 minutes during peak hr. so
with overiding checkpoint priority might be i am not completing some of the
checkpoints with higher priority ....

with dbwr = 2 and with db_block_buffers= x = fast_start_io_target are my DBWR
not that active to flush the dirty blocks as quickly as possible.

I know i have to add more redo and might be the size also...
but....

q2) with checkpointing not complete will the database freeze for hours with out
any trace or error messages to alert?

Thanks,
Kasturi

          Followup:

sorry -- most of that didn't really make clear sense.

checkpoint not complete will basically freeze the instance whilst dbwr rushes to
catch up-- the message IS written to the alert log.

      Rating:
      3
question  August 11, 2003
Reviewer:
      A reader
Tom,

Sorry if i was not clear enough. I was just trying to understand the below:

I was refering to Doc Id 147468.1

where

"The LOG_CHECKPOINT_INTERVAL init.ora parameter controls how often a checkpoint
operation will be performed based upon the number of operating system blocks
that have been written to the redo log.  If this value is larger than the size
of the redo log, then the checkpoint will only occur when Oracle performs a
log switch from one group to another, which is preferred.

NOTE: Starting with Oracle 8.1, LOG_CHECKPOINT_INTERVAL will be interpreted
to mean that the incremental checkpoint should not lag the tail of the
log by more than log_checkpoint_interval number of redo blocks.

On most Unix systems the operating system block size is 512 bytes.  This means
that setting LOG_CHECKPOINT_INTERVAL to a value of 10,000 (the default
setting), causes a checkpoint to occur after 5,120,000 (5M) bytes are written
to the redo log.  If the size of your redo log is 20M, you are taking 4
checkpoints for each log.

............."

and "Starting from Oracle8i, Oracle Corporation recommends that Enterprise
Edition users who were using incremental checkpoints in an earlier release
to use fast-start checkpointing in Oracle8i. In fast-start checkpointing, the
FAST_START_IO_TARGET parameter replaces DB_FILE_MAX_DIRTY_TARGET.
FAST_START_IO_TARGET specifies the number of I/Os that should be needed during
crash or instance recovery.When you set this parameter, DBWR writes dirty
buffers out
more aggressively, so that the number of blocks that must be processed during
recovery
stays below the value specified in the parameter.

So in Oracle8i The incremental checkpoint position should not lag the tail of
the
log by more than LOG_CHECKPOINT_INTERVAL operating system blocks.The
LOG_CHECKPOINT_INTERVAL and FAST_START_IO_TARGET is taken into account
to determine how far behind the end of the redo stream the checkpoint position
can really be.

In Oracle9i FAST_START_MTTR_TARGET is the preferred method of specifying
how far the checkpoint position should be behind the tail of the redo stream.
However,
LOG_CHECKPOINT_INTERVAL is still supported if needed. It functions as per the
Oracle8i
behaviour above.

"
Thanks,

Kasturi

      Rating:
      3
cont../  August 11, 2003
Reviewer:
      A reader
While refering to checkpoint priority:

I am refereeing to Global check points/local check points /File check points
with Fast/Slow/Over ride options of check pointing.

Thanks,
Kasturi.

          Followup:

what I meant was -- I'm not really sure what you are asking.

      Rating:
      3
  August 11, 2003
Reviewer:
      A reader
Tom,

My question is if i am checkpointing as frequently as 50 times per log file
along with other checkpoints(as my log files are switched once for every 4-5
minutes during peak hr), that "i am getting checkpoint not complete error"? or
is it purely becuase of my redo log files which are tiny?

Thanks,

          Followup:

it just means that the checkpoints are *not completing*, you are not doing 50 --
they are not finishing that fast.

you either want more logfiles or larger ones

      Rating: 5
checkpoint
      process and datafiles  November 18, 2003
Reviewer:
      A reader
Tom,
Checkpoint process updates the file headers of the data files.
What does it update the file headers of the data files with?

Thank you

          Followup:

the fact the checkpoint completed there

      Rating: 5
checkpoint
      scn  December 01, 2003
Reviewer:  Shankar
       from Houston, TX
Tom, Do the start and end scn in redolog files correspond to checkpoint scn on
the file headers? Is this how oracle knows which log file needs to be applied in
case we need to restore a datafile from backup and perform recovery? Thanks.

          Followup:

well, the control files are involved as well, but conceptually -- sure.

December 02, 2003 -- Thanks for the question regarding "insert",
      version 8.1.7

      You Asked
Tom

We have Table A, into which we insert a million records everyday, after deleting
previous day records.( we have other tables where we do the same thing except
that the interval of time is weekly and monthly). Now our requirement has
changed and we need to keep 10 days data . But everyday we should delete the
olders day's data and add the current days data.

We have decided that partitioning the table is the way to go. Can you please
discuss the sql script which can  make the whole process automatic, that
everyday a new partition gets added, and the oldest partition is dropped.

Could you also discuss any other issues which you can foresee with the solution
we are trying to arrive at..

Thank you

      and we said...
Here is an example of s sliding window:

tkyte@TKYTE816> CREATE TABLE partitioned
  2  ( timestamp date,
  3    id        int
  4  )
  5  PARTITION BY RANGE (timestamp)
  6  (
  7  PARTITION fy_1999 VALUES LESS THAN
  8  ( to_date('01-jan-2000','dd-mon-yyyy') ) ,
  9  PARTITION fy_2000 VALUES LESS THAN
 10  ( to_date('01-jan-2001','dd-mon-yyyy') ) ,
 11  PARTITION the_rest VALUES LESS THAN
 12  ( maxvalue )
 13  )
 14  /
Table created.

tkyte@TKYTE816> create index partitioned_idx_local
  2  on partitioned(id)
  3  LOCAL
  4  /
Index created.

So, this sets up our 'warehouse' table. The data is partitioned by fiscal year
and we have the last two years worth of data online. Notice that I left an empty
partition THE_REST at the end of the table. This will facilitate sliding new
data in quickly. Now, it is the end of the year and we would like to:

1. Remove the oldest fiscal year data. We do not want to lose this data forever,
we just want to age it out and archive it.
2. Add the newest fiscal year data. It will take a while to load it, transform
it, index it, and so on. We would like to do this work without impacting the
availability of the current data if at all possible.

The steps I might take would be:

tkyte@TKYTE816> create table fy_1999 ( timestamp date, id int );
Table created.

tkyte@TKYTE816> create index fy_1999_idx on fy_1999(id)
  2  /
Index created.

tkyte@TKYTE816> create table fy_2001 ( timestamp date, id int );
Table created.

load fy-2001-here

tkyte@TKYTE816> create index fy_2001_idx on fy_2001(id) nologging
  2  /
Index created.

What I've done here is to set up an empty 'shell' table and index for the oldest
data. What we will do is turn the current full partition into an empty partition
and create a 'full' table, with the FY 1999 data in it. Also, I've completed all
of the work necessary to have the FY 2001 data ready to go. This would have
involved verifying the data, transforming it – whatever complex tasks you need
to undertake to get it ready.

Now we are ready to update the 'live' data:

tkyte@TKYTE816> alter table partitioned
  2  exchange partition fy_1999
  3  with table fy_1999
  4  including indexes
  5  without validation
  6  /
Table altered.

tkyte@TKYTE816> alter table partitioned
  2  drop partition fy_1999
  3  /
Table altered.

That's it for aging the old data out. We turned the partition into a full table
and the empty table into a partition. This was a simple data dictionary update –
no large amount of IO took place, it just happened. We can now export that table
(perhaps using a transportable tablespace) out of our database for archival
purposes. We could re-attach it quickly if we ever need to.

Next, we want to slide in the new data:

tkyte@TKYTE816> alter table partitioned
  2  split partition the_rest
  3  at ( to_date('01-jan-2002','dd-mon-yyyy') )
  4  into ( partition fy_2001, partition the_rest )
  5  /
Table altered.

tkyte@TKYTE816> alter table partitioned
  2  exchange partition fy_2001
  3  with table fy_2001
  4  including indexes
  5  without validation
  6  /
Table altered.

That's it for sliding the data in. Again, this was instantaneous – a simple data
dictionary update. Splitting the empty partition takes very little real-time
since there never was, and never will be data in it. That is why I placed an
extra empty partition at the end of the table, to facilitate the split. Then, we
exchange the newly created empty partition with the full table and the full
table with an empty partition. The new data is online.

So, that is how to slide off old data, slide in new data -- very fast, no
deletes.

You can definitely script that as well.

A big issue you might have to watch out for -- make sure you use LOCALLY
partitioned indexes only.  That means no unique constraints across partitions
that don't include the TIMESTAMP column.  Otherwise you have to rebuild th
global indexes after the partition operations.

Review &
      Followup

      Rating:
      5
Fantastic  July 06, 2001
Reviewer:
      A reader
Very good solution and easy to understand.

      Rating: 5
Very userful
      information. Thanks to Tom.  July 24, 2001

Reviewer:  Rao Munaga

      Rating: 5
very good ! but
      i want to konw   September 03, 2001
Reviewer:  a
      reader
if every partitin in different tablespace , when you split
the partition ,you need set up new tablespace at first
can you create tablespace automatic?

      Rating: 5
very good ! but
      i want to konw   September 03, 2001
Reviewer:  a
      reader
if every partitin in different tablespace , when you split
the partition ,you need set up new tablespace at first
can you create tablespace automatic?

          Followup:

There are never any tablespaces that are created implicitly -- you must
explicitly create them.

      Rating: 5
Simply
      SUPERB  September 08, 2001
Reviewer:
      Narayanan.S.S.  from chennai INDIA
your solutin is just fabulous. It looks nice too. It helped me to solve my
hassle also Thank you.

      Rating: 5
very good
      answer but i want to know more about updating...  September
      13, 2001
Reviewer:  saggi
 i want to give rights on update(row)  to user on a
 particular partition how can i do it ????
thank ...

          Followup:

You could use fine grained access control.  See

http://osi.oracle.com/~tkyte/article2/index.html
Or a trigger that inspected the :new/:old values and prevent users from updating
rows they should not.

Or a view

Or a procedure to encapsulate the update logic

Grants are done at the OBJECT level (table in this case), not on individual
partitions.  For something like that, you will use one of the techniques above

      Rating: 5
Excellent
      answer  March 02, 2003
Reviewer:  G.V.N.
       from Houston

      Rating: 4
Re-attach old
      data  September 18, 2003
Reviewer:  Michael
       from Germany
Hello Tom,

I experimented with partitioning and also tried to reattach old data. I would
like to know, if my approach is OK.

The setup:

SQL> CREATE TABLESPACE "MYDATA_2000"
  2      LOGGING
  3      DATAFILE 'E:\APPS\ORACLE\ORADATA\ORA92DB\MYDATA_2000_01.DBF'
  4      SIZE 1M EXTENT MANAGEMENT LOCAL SEGMENT SPACE MANAGEMENT
  5      AUTO ;

  1  CREATE TABLESPACE "MYDATA_2001"
  2      LOGGING
  3      DATAFILE 'E:\APPS\ORACLE\ORADATA\ORA92DB\MYDATA_2001_01.DBF'
  4      SIZE 1M EXTENT MANAGEMENT LOCAL SEGMENT SPACE MANAGEMENT
  5*     AUTO

  1  CREATE TABLESPACE "MYDATA_2002"
  2      LOGGING
  3      DATAFILE 'E:\APPS\ORACLE\ORADATA\ORA92DB\MYDATA_2002_01.DBF'
  4      SIZE 1M EXTENT MANAGEMENT LOCAL SEGMENT SPACE MANAGEMENT
  5*     AUTO

  1  CREATE TABLESPACE "MYDATA_2003"
  2      LOGGING
  3      DATAFILE 'E:\APPS\ORACLE\ORADATA\ORA92DB\MYDATA_2003_01.DBF'
  4      SIZE 1M EXTENT MANAGEMENT LOCAL SEGMENT SPACE MANAGEMENT
  5*     AUTO

SQL> CREATE TABLE MYDATA
  2  (TIMESTAMP DATE, DATA VARCHAR2(10))
  3  PARTITION BY RANGE (TIMESTAMP)
  4  (  PARTITION MYDATA_2000
  5       VALUES LESS THAN (TO_DATE('01.01.2001', 'dd.mm.yyyy'))
  6       TABLESPACE MYDATA_2000,
  7     PARTITION MYDATA_2001
  8       VALUES LESS THAN (TO_DATE('01.01.2002', 'dd.mm.yyyy'))
  9       TABLESPACE MYDATA_2001,
 10     PARTITION MYDATA_2002
 11       VALUES LESS THAN (TO_DATE('01.01.2003', 'dd.mm.yyyy'))
 12       TABLESPACE MYDATA_2002,
 13    PARTITION MYDATA_2003
 14       VALUES LESS THAN (TO_DATE('01.01.2004', 'dd.mm.yyyy'))
 15       TABLESPACE MYDATA_2003
 16  )
 17  /

SQL> INSERT INTO MYDATA(TIMESTAMP, DATA)
  2  VALUES (TO_DATE('01.06.2000', 'dd.mm.yyyy'), 'DATA2000');

  1  INSERT INTO MYDATA(TIMESTAMP, DATA)
  2* VALUES (TO_DATE('01.06.2001', 'dd.mm.yyyy'), 'DATA2001')

  1  INSERT INTO MYDATA(TIMESTAMP, DATA)
  2* VALUES (TO_DATE('01.06.2002', 'dd.mm.yyyy'), 'DATA2002')

  1  INSERT INTO MYDATA(TIMESTAMP, DATA)
  2* VALUES (TO_DATE('01.06.2003', 'dd.mm.yyyy'), 'DATA2003')

SQL> SELECT * FROM MYDATA;

TIMESTAM DATA
-------- ----------
01.06.00 DATA2000
01.06.01 DATA2001
01.06.02 DATA2002
01.06.03 DATA2003

Now i want to archive the data from year 2000:

SQL> CREATE TABLE MYDATA_2000
  2  TABLESPACE MYDATA_2000
  3  AS
  4  SELECT * FROM MYDATA
  5  WHERE 1=0;

SQL> ALTER TABLE MYDATA
  2  EXCHANGE PARTITION MYDATA_2000
  3  WITH TABLE MYDATA_2000
  4  /

SQL> SELECT COUNT(*) FROM MYDATA;

  COUNT(*)
----------
         3

SQL> ALTER TABLE MYDATA
  2  DROP PARTITION MYDATA_2000;

SQL> ALTER TABLESPACE MYDATA_2000 OFFLINE;

Now i zip and archive the dbf file(s) of this tablespace (MYDATA_2000).

To reattach i do the following:

Dearchive and unzip the dbf file(s) from this tablespace to the right location.
Then:

SQL> ALTER TABLESPACE MYDATA_2000 ONLINE;

SQL> SELECT * FROM MYDATA_2000;

TIMESTAM DATA
-------- ----------
01.06.00 DATA2000

SQL> ALTER TABLE MYDATA
  2  SPLIT PARTITION MYDATA_2001
  3  AT (TO_DATE('01.01.2001', 'dd.mm.yyyy'))
  4  INTO (PARTITION MYDATA_2000 TABLESPACE MYDATA_2000,
  5        PARTITION MYDATA_2001 TABLESPACE MYDATA_2001)
  6  /

SQL> ALTER TABLE MYDATA
  2  EXCHANGE PARTITION MYDATA_2000
  3  WITH TABLE MYDATA_2000;

SQL> SELECT * FROM MYDATA;

TIMESTAM DATA
-------- ----------
01.06.00 DATA2000
01.06.01 DATA2001
01.06.02 DATA2002
01.06.03 DATA2003

SQL> DROP TABLE MYDATA_2000;

It seems to work, but is this the right way to re-attach old data??

(To get the data from 2004 i do:

  1  ALTER TABLE MYDATA
  2  ADD PARTITION MYDATA_2004
  3      VALUES LESS THAN  (TO_DATE('01.01.2005','dd-mm-yyyy'))
  4*     TABLESPACE MYDATA_2004
SQL> /
)

Thanks.

          Followup:

that certainly works.  another option is to just let the empty partition "stay"
there -- don't drop it, just keep it.

less work during reattach time.

      Rating:
      2
parallel  December 01, 2003
Reviewer:
      alex  from uk
Tom

insert  /*+ append parallel(xyz,8) */ into xyz select /*+ parallel(xyz1,8) */ *
from xyz1;

would the above sql be faster or inserting 100,000 records in a loop through pro
C woould be faster for 3M records.

thanks

          Followup:

does not compute.

parallel 8 for a measely 100k records would probably take longer to set up PQ
then to actually insert the records.

but let me say this -- if you can:

a) insert into t select * from t2

or

b) open cursor on t2, fetch from t2 insert into t

you would choose (A) hands down, no real question about it.

December 02, 2003 Marc -- Thanks for the question regarding "Jobs
      in RMAN", version 8.1.7

      You Asked
Hi Tom,

Could you tell me how I could automate the purge of the backup set in RMAN ?
I would like to delete all the obsolete pieces with a cron.

RMAN> report obsolete -> gives the list of pieces to delete
RMAN> allocate channel for delete type disk ;
RMAN> change backuppiece ... delete ;

How to put that in a script ?

Thanks for your help

Marc

      and we said...
See $ORACLE_HOME/rdbms/demo/rman1.sh

Review &
      Followup

      Rating:
      4
  June 29, 2001
Reviewer:
      A reader
Thanks for your help, Tom

      Rating: 4
how about
      Windoze  November 25, 2003
Reviewer:  Susan
       from Watertown, MA
any way to automate RMAN maintenance in a Windows environment?

          Followup:

oem (enterprise manager) is the general purpose tool we would use to schedule
recurring jobs.

      Rating: 4
Can OEM
      automate deleting obsolete backups in 8i  December 01, 2003

Reviewer:  A reader
We're running RMAN in a 8.1.7 database on Windows Advanced 2000 Server.  I'd
like to be able to automate the deleting of backups through RMAN.  I know that
in a UNIX environment you can use the RMAN1.sh script.  Can OEM be used in a
similar way?  Alternatively, any suggestions to automate this process in a
Windows environment?  Thanks.

          Followup:

sure, you can script in OEM (i don't have one for you, no)

December 02, 2003 -- Thanks for the question regarding "How can we
      release a lock held by another user? (kill them)", version 8.1.6

      You Asked
DBMS_LOCK.RELEASE holds true only for id or lockhandle that are user assigned.
But looking at the information supplied by Lock Monitor, how can we release the
lock? (Say in the following situation for Scott holding exclusive lock? -- I
have copied the name of columns and corresponding data)

User Name  SCOTT
Session ID 17
Session Serial Number 3040
Lock Type TM
Mode Held EXCLUSIVE
Mode Requested NONE
Object Name EMP
ROWID null
Object Owner SCOTT
Object Type TABLE
Resource ID1 21955
Resource ID2 0

Do kindly let me know. Thanking you in anticipation.

Best Regards,

(Chetan)

      and we said...
alter system kill session '17,3040';

thats the only way.

followup to comment

Well, there cannot be a "more graceful way".  A session gathers resources and
accumulates locks -- if we "took away" a lock from it, we would have to commit
the work that lock might represent (an update for example).  We cannot commit
just PART of the transaction and the transaction is in PROCESS so we cannot
commit all of the work to date either (that would be committing just PART of a
transaction -- would compromise the integrity of the data in the database).

Therefore, we must rollback their transaction (drastic).  The only way to do
that is to kill their session.
Review &
      Followup

      Rating: 4
Release a lock
        June 16, 2001
Reviewer:  Chetan  from
      NJ, USA
As always, the information helped. But, I was looking for a more graceful way of
releasing the lock.

      Rating:
      4
  June 22, 2001
Reviewer:  Tianhua
      Wu  from Atlanta, GA USA
I had bad experience with 'alter system kill..'.  From my experience, 'kill -9
PID' for Unix (or orakill for NT ) is a much better solution.

      Rating: 4
session id
      serial#  December 01, 2003
Reviewer:  john

why do session id and serial# both uniquely identifies a user session? session
id alone not enough?
i believe it is because session are re used. am i right?

          Followup:

I think you mean "sid, serial#"

sids are reused, yes.

December 02, 2003 -- Thanks for the question regarding "how to use
      dbms_stats.gather_databse_stats", version 8.1.6

      You Asked
Tom:

i try to use dbms_stats to give me a report of
what statistics is missing, what i do is:

declare a dbms_stats.objecttab;
begin
dbms_stats.gather_database_stats(OPTIONS=>'LIST
EMPTY',OBJLIST=>a);
end;

after that how can i know the content of a?

      and we said...
You would simply print out the values of A(i) in a loop.  For
example:

tkyte@TKYTE816> l
  1  declare
  2    a dbms_stats.objecttab;
  3  begin
  4     dbms_stats.gather_database_stats
          (OPTIONS=>'LIST EMPTY',OBJLIST=>a);
  5
  6     for i in 1 .. a.count
  7     loop
  8             dbms_output.put_line( a(i).ownname );
  9             dbms_output.put_line( a(i).objType );
 10             dbms_output.put_line( a(i).objName );
 11             dbms_output.put_line( a(i).PartName );
 12             dbms_output.put_line( a(i).subPartName );
 13             dbms_output.put_line( a(i).Confidence );
 14             dbms_output.put_line
                      ( '-------------------------' );
 15     end loop;
 16  end;
 17*
tkyte@TKYTE816> /
CTXSYS
INDEX
DRC$DEL_KEY
100
-------------------------
CTXSYS
INDEX
DRC$IDX_COLSPEC
100
-------------------------
.....

PL/SQL procedure successfully completed.

Make sure to set serveroutput on in sqlplus first!
Review &
      Followup

      Rating: 5
What does
      CONFIDENCE mean ?  September 03, 2002
Reviewer:
      Anton  from NZ
Hi Tom.

What does the CONFIDENCE figure listed by LIST_EMPTY mean ?  I've searched in
the 8i DBMS_STATS documntation but it says that the CONFIDENCE column is not
used.

Thanks.

          Followup:

I guess it means nothing then, if it says "it is not used"

      Rating: 4
Why or why not
      get full statistics  March 19, 2003
Reviewer:
      Juan Carlos Reyes Pacheco  from Bolivia
Hi Tom

I was using
begin
DBMS_UTILITY.Analyze_Database( 'COMPUTE' );
end;
until I start to read several answers about this so I have 3 questions, please

1. How do I get full statistics for all objects in the database.
2. Is possible to get full statistis for all objects in the database only for
those that not has statistics or has statistics calculated more than one month
ago.
3. Which real advantage you get getting full statistis, is better to use a
sample, which size to be as useful as full statistics?

Thanks.

          Followup:

don't do that.  that is there from 7.3

dbms_stats is the tool to use.

1) read up on DBMS_STATS - gather_database_stats

2) read up on ALTER TABLE MONITORING and the gather stale option of dbms_stats.
It doesn't make sense to gather stats based on time -- it makes sense to gather
stats based on percentage of changes to the table -- we can do that.

3) if the tables are very large, random samples work well.  if the tables are
not very large, random samples can be very skewed. large is many gigabytes and
above.

      Rating: 1
I am sorry but
      that does not work  March 19, 2003
Reviewer:
      A reader
2) read up on ALTER TABLE MONITORING and the gather stale option of dbms_stats.

It doesn't make sense to gather stats based on time -- it makes sense to gather
stats based on percentage of changes to the table -- we can do that.

that unfortunately does not work until Oracle 9.2.0

          Followup:

Hmm, why do you say that?

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76936/dbms_st2.htm#1006517
it was a feature of Oracle8i..

So, I'm sorry -- but yes, it does 'work'

      Rating:
      5
  March 20, 2003
Reviewer:
      A reader
Thanks Tom :) nice answer

      Rating: 1
check bug
      1890016  March 20, 2003
Reviewer:
      A reader
well I am saying it´s not working because I tried to implement on Sun Solaris
and Oracle 8.1.7.4 and it didnt work, after that Meatalink confirmed my fears...
another  bug fixed in 9.2.0 and seems like they dont want to release a good
dbms_stats package to fix this in 8.1.7 (yes it´s a PL/SQL problem and not any
kernel or funky bug)

          Followup:

You know -- when you add a little detail, you get a little more.

umm, simple workaround -- specify the granularity parameter. (that is mentioned
right there in the report you read)

...
Rediscovery Information:
You will see this bug when using dbms_stats.gather_xxx_stats and :
1. An invalid granularity is supplied
2. The GATHER STALE or GATHER EMPTY options are used
3. The procedure call does not seem to work
.
Workaround:
Correct the granularity parameter
......

another simple workaround -- list stale, gather (hey, thats what is above isn't
it?)

Also, that is 9.0.2 which is 9iR1, not release 2.

but in any case, I believe your test was flawed since this is in fact
corrected in 8174

Maybe you either

o did not wait the requisite 3 hours or so for the tab modifications view to be
updated
o did not shutdown normal and restart to test the behavior.

In 8i, there is a 3 hour time delay -- the table isn't marked stale for at least
3 hours (in 9i, running gather stale forces the modifications view to be updated
first, changing this behavior).

Anyway, here is a test that shows -- yes in fact this does work:

ops$tkyte@ORA817DEV> create table emp as select * from scott.emp;
Table created.

ops$tkyte@ORA817DEV> alter table emp monitoring;
Table altered.

ops$tkyte@ORA817DEV> analyze table emp compute statistics;
Table analyzed.

ops$tkyte@ORA817DEV> select table_name, num_rows, blocks, avg_row_len
  2    from user_tables
  3   where table_name = 'EMP';

TABLE_NAME                       NUM_ROWS     BLOCKS AVG_ROW_LEN
------------------------------ ---------- ---------- -----------
EMP                                    14          1          40

Now, lets modify emp:

ops$tkyte@ORA817DEV> insert into emp select * from emp;
14 rows created.

ops$tkyte@ORA817DEV> /
28 rows created.

ops$tkyte@ORA817DEV> /
56 rows created.

ops$tkyte@ORA817DEV> /
112 rows created.

ops$tkyte@ORA817DEV> /
224 rows created.

ops$tkyte@ORA817DEV> /
448 rows created.

ops$tkyte@ORA817DEV> /
896 rows created.

ops$tkyte@ORA817DEV> commit;
Commit complete.

and since I didn't feel like waiting 3 hours, I simulated 3 hours by
bouncing:

ops$tkyte@ORA817DEV> connect / as sysdba
Connected.
ops$tkyte@ORA817DEV> shutdown immediate
Database closed.
Database dismounted.
ORACLE instance shut down.
ops$tkyte@ORA817DEV> startup
ORACLE instance started.

Total System Global Area   31965344 bytes
Fixed Size                    73888 bytes
Variable Size              26796032 bytes
Database Buffers            4915200 bytes
Redo Buffers                 180224 bytes
Database mounted.
Database opened.
ops$tkyte@ORA817DEV> connect /
Connected.

ops$tkyte@ORA817DEV> select * from sys.dba_tab_modifications;

TABLE_OWNER                    TABLE_NAME                     PARTITION_NAME
            SUBPARTITION_NAME
------------------------------ ------------------------------
------------------------------ ------------------------------
   INSERTS    UPDATES    DELETES TIMESTAMP TRU
---------- ---------- ---------- --------- ---
OPS$TKYTE                      EMP
      1778          0          0 20-MAR-03 NO

table appears to need to be analyzed right...

ops$tkyte@ORA817DEV> exec dbms_stats.gather_schema_stats( user,  options =>
'gather stale');

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> select table_name, num_rows, blocks, avg_row_len
  2    from user_tables
  3   where table_name = 'EMP';

TABLE_NAME                       NUM_ROWS     BLOCKS AVG_ROW_LEN
------------------------------ ---------- ---------- -----------
EMP                                  1792         15          37

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select * from sys.dba_tab_modifications;

no rows selected

and hey -- it was!  now, if you run this and if it was not -- just specify
granularity => 'DEFAULT' and it'll be..

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
PL/SQL Release 8.1.7.4.0 - Production
CORE    8.1.7.0.0       Production
TNS for Solaris: Version 8.1.7.4.0 - Production
NLSRTL Version 3.4.1.0.0 - Production

as 8174 is the terminal release of 8i, is it the only currently supported
release level..

      Rating: 5
Why some sys
      statics are not updated  March 20, 2003
Reviewer:
      Juan Carlos Reyes  from Bolivia
Hi Tom I ran
 DBMS_STATS.GATHER_DATABASE_STATS(); connected as sys, the only user connected.

And I had some tables in sys with last analyzed in null and others were not
analyzed

Then I run
 DBMS_STATS.GATHER_SCHEMA_STATS( 'SYS' );

Then I has some (less than before ) with last analyzed in null, and others
tables without updated statics (last statics dic-3-2002)
ATEMPTAB$,MAP_OBJECT,ODCI_SECOBJ$,
ODCI_WARNINGS$,LOGMNRT_SEED$,LOGMNRT_DICTIONARY$,
LOGMNRT_OBJ$,LOGMNRT_TAB$,LOGMNRT_COL$,
LOGMNRT_ATTRCOL$,LOGMNRT_TS$,LOGMNRT_IND$,LOGMNRT_USER$,

Questions.
1. Why I has to run  DBMS_STATS.GATHER_SCHEMA_STATS( 'SYS' ); to get some
additional tables analyzed for first time?
2. Why some tables statistics aren't update.

Thanks Tom

          Followup:

first -- it is not recommended to gather stats on sys as yet.  In 9i it is
supported, in 8i, not so.

SYS is special, SYS is magical (don't run stuff as SYS, please -- create your
OWN database account and use that).

some sys tables are skipped purposefully, analyzing them could lead to certain
issues.  sys is special.

      Rating: 5
Thanks
      Tom  March 20, 2003
Reviewer:  A reader

      Rating: 4
instace failure
      and monitoring  March 20, 2003
Reviewer:
      A reader
Hi

I wonder, if we use gather stale option what happen if instance fails? I guess
we lost all stale statistics stored in *_tab_modifications (SGA) right? We have
to run again the base analyze (for all monitored tables) again right?

          Followup:

it is flushed every 3 hours.  So, no, not really.

      Rating: 4
so at
      most  March 20, 2003
Reviewer:
      A reader
Hi

So at most we will lose 3 hours statstics, am I correct :?

          Followup:

3 hours of observed changes..

      Rating: 5
only one
      question more DBMS_STATS.GATHER_SYSTEM_STATS  March 20, 2003

Reviewer:  Juan Carlos Reyes P  from Bolivia

Hi Tom, I had been looking for more about information about
DBMS_STATS.GATHER_SYSTEM_STATS, other than simple reference
But I can't get.

I would like to askyou one question more.

As I read DBMS_STATS.GATHER_SYSTEM_STATS allows you get the CUP and IO
conditions in specifict periods of time. Then reload the proper conditions
through a job.

how important is to get and set DBMS_STATS.GATHER_SYSTEM_STATS in an OLTP and in
a DSS.

I had seen how recalculating statistics a query that took 30mn took only 5mn. Is
DBMS_STATS.GATHER_SYSTEM_STATS really as important to set? you can get that kind
of improvement, something you can't skip?

I had tried to create two scenarios but I don't see the difference, could you
please illustrate what savings you can get.

          Followup:

You need a really busy system to see the difference.  (or at least a busy system
then a not so busy system or a system that does OLTP by day and batch by night)

Those are the conditions under which this becomes useful.  On a single user test
machine, it'll be really hard to simulate.

      Rating:
      4
Thanks  March 20, 2003
Reviewer:
      A reader

      Rating: 4
Any harm with
      MONITORING  March 20, 2003
Reviewer:  Bob D.
       from NYC
Is there any reason NOT to use the MONITORING option ???
Unless you always need the very latest stats generated
all the time I don't see why all my tables shouldn't
have this option on.  Thanks.

          Followup:

neither do i.  It is very low bandwidth.

      Rating: 4
calculate
      DBMS_STATS.GATHER_SYSTEM_STATS only to help CBO  March 20,
      2003
Reviewer:  Juan Carlos reyesp  from bolivia

Hi Tom, one last question.

What does DBMS_STATS.GATHER_SYSTEM_STATS do really?

I think make sense to execute it only to get it update some statistics about IO
and CPU use, to help CBO. Even if you don't need to switch between distinct
saved systemstats.

I'm right or not?

In that case which is the most advisable way to execute it?
BEGIN
DBMS_STATS.GATHER_SYSTEM_STATS( gathering_mode => 'interval', interval => 60);
END;
/
In a periodo of average cpu and io use?

          Followup:

I just don't have enough real world experience with it as yet to give you best
practices -- sorry.

      Rating: 4
About the
      bug...  March 21, 2003
Reviewer:  Alex Daher
       from Campinas, SP Brazil
Well, I am using monitoring/gather stale stats on a 8.1.7.4 database at Sun
Solaris when it's working pretty fine... much better then gathering ALL stats
every time!

I have a daily job the calls dbms_stats.

Why do the exactly same work twice??? :-)

      Rating: 4
About the
      bug...  March 21, 2003
Reviewer:  Alex Daher
       from Campinas, SP Brazil
Well, I am using monitoring/gather stale stats on a 8.1.7.4 database at Sun
Solaris AND it's working pretty fine... much better then gathering ALL stats
every time!

I have a daily job THAT calls dbms_stats.

Why do the exactly same work twice??? :-)

      Rating:
      5
GATHER_SYSTEM_STATS  May 09, 2003

Reviewer:  PINGU
Hi

Oracle highly recommends running GATHER_SYSTEM_STATS so CBO can use a better
execution plan however I really dont see the use of this procedure! I mean if my
server is heavily loaded (only loaded not dead!) 24 hours per day, durng day
time users and night times tons of batch running in our e10000, what´s the point
of using this procedure, I mean whats the point of having one statistics in the
morning and one during night? I think it will just mess up even more to CBO no?

Anyone used this in production?

          Followup:

thousands of people use this in production every day.

your question does not compute -- not sure what you are asking.

for you -- sounds like you want to "monitor" tables (alter table T monitoring)
and gather_stale stats from time to time.

      Rating: 5
Ok, STALE is
      very usefull, but...  July 10, 2003
Reviewer:
      Bruno Paquet  from Canada
i collect table stats and play with the SIZE in method_opt for correct use of
indexes.

Do i simply execute the DBMS_STATS with the SIZE for every table and after when
i will execute DBMS_STATS for the schema with STALE, do Oracle keep my SIZE
option?

          Followup:

you would use list stale to find the stale objects and then invoke dbms_stats
with customize stats options (perhaps table driven) in order to do that.

      Rating: 3
what about
      other statistics in ANALYZE  September 25, 2003

Reviewer:  J  from US, MD
when it is the direction to use DBMS_STATS package, what about the statistics
available in regular analyze command but not in dbms_stats yet, say,
chained_row, etc. How should we use these two efficiently to avoid duplicate
efforts? in reality, how should we schedule to gather statisics in:

GATHER_DATABASE_STATS
GATHER_INDEX_STATS (shouldn't this gathered in schema stats?)
GATHER_SCHEMA_STATS
GATHER_SYSTEM_STATS
GATHER_TABLE_STATS

and for table with long values or frequent DML operation, do analyze on table?

as of now, we do weekly analyze on schema level and do daily analyze on new
daily partition tables. we plan to use DBMS_STATS but still confuse on what we
will get from gathering system statistics.

thanks!

          Followup:

chained rows is not a statistic.  the optimizer does not use this piece of data.

dbms_stats gathers that which the optimizer uses.  anything it does not use, it
does not gather.

      Rating: 4
Regarding
      GATHER_SYSTEM_STATS  December 01, 2003
Reviewer:
      Matt  from Australia
I was told by a reputable source that the use of this procedure negates the need
to alter the optimizer_index_cost_adj and optimizer_index_caching parameters.
I've only just started playing with this feature. Can you confirm or deny that
the generated AUX_STATS$ values are used in some way used instead of these
parameters?

Can you can think up a suitable test to prove/disprove the above that doesn't
involve a 10053 trace? If so, let me know and I will set it up.

Best Regards,

          Followup:

does not negate but can reduce (as a side effect)

If you look at the definitions of the optimizer_* settings, they are somewhat
orthogonal to the system stats.

optimizer_* tells oracle what to expect to find in the buffer cache.

system stats tells oracle the cost of doing single block IO, multiblock IO and
how fast the machine in general is.

it is not that they are used INSTEAD OF, they can be used IN CONJUNCTION WITH.

Here is the test case showing this.  test case shows

a) plan with default optimizer* and no system stats
b) different plan with some system stats forced onto the system
c) yet a 3rd plan with both optimizer* and system stats in place

ops$tkyte@ORA9IR2> create table t1
  2  as
  3  select mod(rownum,1000) id, rpad('x',300,'x') data
  4    from all_objects
  5   where rownum <= 5*1000;
Table created.

ops$tkyte@ORA9IR2> create table t2
  2  as
  3  select rownum id, rpad('x',300,'x') data
  4    from all_objects
  5   where rownum <= 1000;
Table created.

ops$tkyte@ORA9IR2> create index t1_idx on t1(id);
Index created.

ops$tkyte@ORA9IR2> create index t2_idx on t2(id);
Index created.

ops$tkyte@ORA9IR2> begin
  2     dbms_stats.gather_table_stats( user, 'T1',
  3        method_opt=> 'for all indexed columns', cascade=>true );
  4     dbms_stats.gather_table_stats( user, 'T2',
  5        method_opt=> 'for all indexed columns', cascade=>true );
  6  end;
  7  /
PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> alter system flush shared_pool;
System altered.

ops$tkyte@ORA9IR2> alter session set optimizer_index_caching = 0;
Session altered.

ops$tkyte@ORA9IR2> alter session set optimizer_index_cost_adj = 100;
Session altered.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> set autotrace traceonly explain
ops$tkyte@ORA9IR2> select * from t1, t2 where t1.id = t2.id and t2.id between 50
and 55;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=28 Card=5 Bytes=1000)
   1    0   HASH JOIN (Cost=28 Card=5 Bytes=1000)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'T2' (Cost=3 Card=5 Bytes=500)
   3    2       INDEX (RANGE SCAN) OF 'T2_IDX' (NON-UNIQUE) (Cost=2 Card=5)
   4    1     TABLE ACCESS (FULL) OF 'T1' (Cost=24 Card=26 Bytes=2600)

ops$tkyte@ORA9IR2> set autotrace off

ops$tkyte@ORA9IR2> exec dbms_stats.set_system_stats( 'sreadtim', 0.595 );
PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec dbms_stats.set_system_stats( 'mreadtim', 2.386 );
PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec dbms_stats.set_system_stats( 'cpuspeed',   340 );
PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec dbms_stats.set_system_stats( 'mbrc',   7 );
PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec dbms_stats.set_system_stats( 'maxthr',   17729536 );
PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> select * from sys.aux_stats$;

SNAME                PNAME                     PVAL1 PVAL2
-------------------- -------------------- ---------- --------------------
SYSSTATS_INFO        STATUS                          COMPLETED
SYSSTATS_INFO        DSTART                          12-01-2003 07:04
SYSSTATS_INFO        DSTOP                           12-01-2003 07:04
SYSSTATS_INFO        FLAGS                         1
SYSSTATS_MAIN        SREADTIM                   .595
SYSSTATS_MAIN        MREADTIM                  2.386
SYSSTATS_MAIN        CPUSPEED                    340
SYSSTATS_MAIN        MBRC                          7
SYSSTATS_MAIN        MAXTHR                 17729536
SYSSTATS_MAIN        SLAVETHR                     -1

10 rows selected.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> set autotrace traceonly explain
ops$tkyte@ORA9IR2> select * from t1, t2 where t1.id = t2.id and t2.id between 50
and 55;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=34 Card=5 Bytes=1000)
   1    0   HASH JOIN (Cost=34 Card=5 Bytes=1000)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'T2' (Cost=4 Card=5 Bytes=500)
   3    2       INDEX (RANGE SCAN) OF 'T2_IDX' (NON-UNIQUE) (Cost=3 Card=5)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=30 Card=26 Bytes=2600)
  5    4       INDEX (RANGE SCAN) OF 'T1_IDX' (NON-UNIQUE) (Cost=3 Card=1)

ops$tkyte@ORA9IR2> set autotrace off
ops$tkyte@ORA9IR2> alter session set optimizer_index_caching = 90;
Session altered.

ops$tkyte@ORA9IR2> alter session set optimizer_index_cost_adj = 10;
Session altered.

ops$tkyte@ORA9IR2> set autotrace traceonly explain
ops$tkyte@ORA9IR2> select * from t1, t2 where t1.id = t2.id and t2.id between 50
and 55;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=5 Bytes=1000)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=2 Card=1 Bytes=100)
   2    1     NESTED LOOPS (Cost=4 Card=5 Bytes=1000)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'T2' (Cost=2 Card=5 Bytes=500)
   4    3         INDEX (RANGE SCAN) OF 'T2_IDX' (NON-UNIQUE) (Cost=3 Card=5)
   5    2       INDEX (RANGE SCAN) OF 'T1_IDX' (NON-UNIQUE) (Cost=1 Card=1)

ops$tkyte@ORA9IR2> set autotrace off
ops$tkyte@ORA9IR2> spool off

      Rating: 4
Relaiable way
      to choose values of the optimizer parameters?  December 02,
      2003
Reviewer:  Matt  from Australia
This makes sense. I can see that the SYSTEM stats and optimizer parameters
affect the plans in each case.

Is there some reliable way of working out what the optimizer values should be
set to. Is it possible to somehow calculate precisely how much of an index is
cached and use this value, for example? In the past I have spent some
considerable time testing various combinations and never being comfortable that
I have found the right values.

Best Regards,

          Followup:

I'm of the mind set (until someone convinces me otherwise) that there are about
3 settings for the optimizer_* stuff

a) as they are
b) in the middle
c) opposite of what they are

sort of like "low", "med", and "high" index bias.

using system stats may obviate the need to look at them intensely as the
optimizer will take into consideration the "power" of your machine.

December 02, 2003 Srini -- Thanks for the question regarding
      "Database upgrades.", version 8.1.7

      You Asked
Tom,

Thanks as usual for everything.  My question is regarding database upgrades- a
general question.  If I have multiple instances running on a box, my practice is
to bring down all the instances to do an new version of Oracle Install, and
upgrade the ones I need to upgrade and bring them all up and running after the
upgrades are done.  Occasionally, I encounter people who are of the opinion that
I need not bring down all the databases when I am doing the Oracle install.  My
fear is that at the time of install, some processes can impede or some DLLs can
get impacted at the time of the install and there could be some issues with the
files linking etc.,.  Is my fear unfounded or is it a good practice? I feel it
is better safe than sorry..  Oracle install guide does recommend all the
instances to be brought down. Ofcourse the database that I upgrade have to go
down. Installs are done on a new Oracle Home. Your thoughts on this would be
highly appreciated.

Thanks again!

Sincerely,

Srini Ramaswamy

      and we said...
On windows -- the most "stable" os in the world (dripping with something there)
-- it is recommended by the OS vendor themselves to stop EVERYTHING when
installing software and reboot afterwards.

a) i would not consider running more then on instance on a machine
b) i would stop everything I could  -- it takes one file opened by some process
to muck up the entire thing

c) installs are performance killers anyway -- the less you are doing, the faster
they go.
Review
      & Followup

      Rating: 5
more then one
      instance on a machine  January 03, 2003
Reviewer:
      Inquiring mind  from Albany, NY USA
You said:
"a) i would not consider running more then on instance on a machine"
Do you mean just windoze? Or Unix as well?

          Followup:

Unix as well -- except for a testing box I guess I should have added (i have a
box with 7x, 8x, 9x on it)

On the real machines -- one database, one instance, all apps share and share
alike and all either get upgraded or not at the same time.

      Rating: 4
followup.. How
      about Unix platforms  January 03, 2003
Reviewer:
      Srini Ramaswamy  from Atlanta
Thanks Tom, Does this hold good for UNIX platforms as well. I have all my
databases running on IBM AIX 4.3.3. Sorry I should have mentioned this earlier.

      Rating: 2
One instance
      per machine - really?  January 04, 2003
Reviewer:
      Kurt  from Springfield, VA USA
Tom,
In the original response, you mention "On the real machines -- one database, one
instance, all apps share and share alike and all either get upgraded or not at
the same time."

One instance per machine seems unusual to me. Note that I am an application DBA
rather than a production DBA, so I'm not that involved in instance management.
But in the environments I've worked in, we maintain up to 20 separate instances
on each machine - Unix or Windows, development or production. Obviously, the
machines are loaded with memory, processors, disk drives, etc. so that they can
adequately support the load.

We maintain separate instances for each application because:
- some applications are COTS-like and are "greedy", stepping on anything in
their way
- in the case where one application is having a problem that causes a restart of
the instance, we don't want to bring down every application in the system

Do you feel we're unnecessarily creating more instances than we should?
Management understands the extra work involved in maintaining all of the
instances, and the production DBAs have never objected.

Thanks, Kurt

          Followup:

- some applications are COTS-like and are "greedy", stepping on anything in
their way

but, if they are all running on separate instances -- how the heck do you stop
them???? At least, if they were in a single instance, I could use resource
profiles or better -- the resource manager to LIMIT the amount of system
resources they can have (hey, application X users, you get 10% of the machine).
If I run an instance per app -- there is NOTHING to stop instance X from taking
over the entire machine, nothing.

- why would an app cause an instance restart?

Yes, one instance per machine is nirvana.  How is it to manage, monitor, backup,
upgrade, test, tune, fix, etc 20 instances vs 1?  (it is more then 20 times
harder, 20 times the work).

You would need less cpu, less ram, less disk with a single instance as well.
And you would not end up with 20 different dot releases of Oracle on that single
box.

Why would the production DBAs object?  They have more work to do, need bigger
staffs -- its great right?

bear in mind however, for true COTS apps, things you buy from a 3rd party --
you are probably running them in their own instance as they will have very
stringent version/patch requirements.  I would say you would be running them on
their own machine even (preference).

      Rating: 5
Unix
      upgrade  November 26, 2003
Reviewer:  atul
       from India
Hi Tom.

We are planning to upgrade AIX-UNIX from 4.2 to 4.3.

Are there any specific issues we will face about databases
residing on same node after we upgrade unix?

Thanks.
atul

          Followup:

contact support, that is what they are really good at -- this factual "research
this question" sort of thing.

I don't use AIX

      Rating: 3
one
      instance  November 27, 2003
Reviewer:  Stu
       from UK
Going back to the one instance per machine question.

If you had several apps running from the one instance how would you keep them
separate? For example would you create separate tablespaces for each app?

We have currently followed you advice and gone with 3 tblspaces small, medium,
and large for each instance (for the app data) since we have self tuning disks
(HP UX VA7100 disk arrays self adapting auto RAID) ie it doesn't matter which
file volumes the tablespaces are under.

But with all apps in this instance would you still segregate all app data into
their own s/m/l tablespaces?
So for 3 apps you'd have 3 small, 3 medium and 3 large tblspaces?

What about data dictionary updates would there be contention?

How exactly do you restrict app X to use 10% of the box as you said earlier?

          Followup:

I create separate tablespaces for each, exactly.

for small/medium apps -- i would use a single tablespace with system allocated
extents.

for "big" ones, I might go with small/med/large.

Read up on the resource manager in the admin guide.  It won't restrict it to 10%
until the machine gets loaded (since you cannot put cpu in the bank and use it
later -- the app can use 100% of the machine, as long as other apps do not need
it)

      Rating: 3
More than one
      instance on a machine  November 28, 2003
Reviewer:
      DBA  from virginia
The #1 reason for running multiple instances on a server is cost. If you get a
new server for each instance, the cost can be profound. We do data publication
here and we only run our batch publications at night and early morning. So its
just not cost effective to have the staging and production instance on a
seperate box. Its just too expensive. We are talking an $88,000 license for a 4
CPU box, plus the extra support costs and we have a VERY large discount.

People make business decisions here. We sell private sector products built on
Oracle databases, if we dont keep our costs down our customers will find other
vendors and people will lose their jobs.

In an ideal world where cost is not an option, its ideal to have one instance
per server. However, the real world doesnt always work that way.

          Followup:

you got it 100% backwards!!!

if you have a server that can run two instances, each with a single application
-- I could take that same server and run a SINGLE instance with THREE
applications -- because of the reduced RESOURCE UTILITIZATION.

The more applications you run in a single instance -- well -- the more
applications you can run on a single server.

Having a single instance is all about reducing cost.

I cannot imagine wanting to run staging and production in two instances on the
same server.

staging sounds like "a schema" to me
production sounds like "another schema" to me

it all sounds like "a single instance" to me.

the less instances -- the more work you can do on the same hardware.  You want
to keep costs down?  do as much as you can in a single instance

o less hardware needed
o more manageable
o easier to admin

there is nothing that would drive me to multi-instance on a single box.  I can
do more with less.

I agree it is all about making business decisions here -- I just think you've
taken the most costly route.

      Rating: 4
shared
      pool?  December 01, 2003
Reviewer:  Duncan
       from UK
Tom
What about the shared pool? If you have 3 or 4 production applications using one
instance then won't your sql get pushed out the shared pool more often?  Is it
wise to increase the shared pool (we use 200m at the moment) or will this mean
that it takes oracle longer to search?

          Followup:

you use it

or

you lose it

that simple.  Having one instance will actually reduce the chances of your sql
not being found!

Having many instances will INCREASE that risk....

why?  You have say X megabytes for shared pools.   So, say each gets X/4
megabytes for their shared pools.  Well, each shared pool will have tons of
copies of dictionary queries, dbms_* packages, etc.  Lets call this "overhead"
Y.  In this system you have:

4 * ( x/4 - y ) megabytes = x-4*y megabytes available for YOUR stuff

Now, goto a single instance, you have simply X-Y megabytes available for YOUR
stuff

It (single instance) is more efficient, uses less resources.  Period.

      Rating: 3
Sharing is
      caring  December 01, 2003
Reviewer:  Mark
       from SF, USA
I see your point when referring to stage vs. prod, or two apps supported by the
same folk.  Is that the context of your advice?
If you share the instance across apps outside your realm, you are now operating
at the lowest common denominator as far as:

1) Apps that don't use bind variables or stored code, using more shared pool
than they should;

2) SLA - one app needs the DB down, another needs it up.

          Followup:

there should not be "apps outside your realm" on the same machine, so all apps
running on A machine are managed by A (person|group).

Apps that don't use bind variables should never make it out of test :)

I am talking about N apps that you run as part of running your business.  They
are all "equally" important.  If something is so large and so huge as to be
"special", then it will be the only thing on that machine.  What I am saying is
-- do not have more then one instance per machine, period.

It takes less resources
It is just as available (if not more, less moving pieces)
It is the only way for you to control resources
It lowers support costs (as you do not end up running 5 versions of Oracle to
support 5 apps)

      Rating: 5
Is DW vs. OLTP
      separation still relevant ? ... (ever was?)  December 01,
      2003
Reviewer:  Gabriel  from Toronto, CA
Tom,

You said .... "On the real machines -- one database, one instance, all apps
share and share alike and all either get upgraded or not at the same time."

Assuming no spare hardware, would you consider hosting DW and OLTP apps on the
same instance? With one single block size in 8i, separate instances were
justifiable ... 9i allows multiple block sizes so at least one problem is
addressable ... still, giving the different type of load, what would be your
recommendation for 9i?

Thank you.

          Followup:

8k is fine for all here -- especially if you are in a "no hardware available"
environment (eg: read that as "do it cheap and good enough").  So, i would not
use multiple block size databases in support of concurrent reporting and oltp --
just use 8k and be done with it.

IF I had to do "dw" and "oltp" on a single machine.
THEN a single instance would be the only way to do it.

      Rating: 4
Multiple
      instances on windows  December 02, 2003
Reviewer:
      Dale Ogilvie  from New Zealand
I can definitely understand the single instance equals less resource overhead
argument. But, in our environment we run 32 bit windows where oracle is limited
to 2 or 3GB of RAM per instance. My esteemed colleagues want multiple instances
so that they can make use of extra installed ram up to 64GB on the same hardware
(Something to do with Intel extensions that windows server is aware of). Would
this "use the extra RAM on 32 bit windows" argument lead you to accepting a
multi-instance setup?

          Followup:

I'd look at a platform that permitted me to use what I bought first and
foremost.

Depends on what I had to do on this box.  2/3 gig goes pretty far (i see people
way oversize their buffer caches and other settings over and over and over)...

But hey, if your OS lets you down - not much you can do.  Good luck trying to
tune that machine (windows with 64gig, who'd have thunk it)

December 02, 2003 Mudassar -- Thanks for the question regarding
      "Calling External Procedures", version 8.1.5

      You Asked
This is what I am trying to do.

SQL> create or replace library lib1
  2  as 'g:\winnt\system32\kernel32.dll' ;
  3  /

Library created.

SQL> create or replace function func1
  2  (v1 in varchar2, v2 in number)
  3  return number
  4  as
  5  language c
  6  library lib1
  7  name "WinExec" ;
  8  /

Function created.

SQL> select func1('notepad.exe',1) from dual ;
select func1('notepad.exe',1) from dual
                                   *
ERROR at line 1:
ORA-28576: lost RPC connection to external procedure agent

Although Oracle gives me an error, but it does start the application notepad.exe
in the background. Any idea how to prevent the error and start the program in
the foreground.

Thanks and Best Regards,
Mudassar Shahzad

      and we said...
The database cannot do things GUI.  Its not running on a console.  It has no
display to put anything on.

The database is running as a service, services cannot interact with the end
user.  Besides -- even if this works -- the notepad app would tend to show up on
the server wouldn't it, what then?  The server isn't "logged into" by anyone
typically.

There is no place for this notepad to goto.

Ok, to make this "work" (eg: not crash) you need to use a binary integer.  Your
procedure as coded would need a C subroutine that has this prototype:

  OCINumber *WinExec
    (
                char  *,  /*   1 : V1  */
           OCINumber  *   /*   2 : V2  */
    );

I seriously doubt if MS uses an OCINumber structure, you need a plsql function
that would map to a C function this this prototype:

  int WinExec
    (
                char  *,  /*   1 : V1  */
                 int      /*   2 : V2  */
    );

So, that would be:

tkyte@TKYTE816> create or replace function func1
  2  (v1 in varchar2, v2 in binary_integer)
  3  return binary_integer
  4  as
  5  language c
  6  library lib1
  7  name "WinExec" ;
  8  /

Function created.

but no you'll find:

tkyte@TKYTE816> select func1( 'C:\WINNT\system32\notepad.EXE', 1 ) from dual;

FUNC1('C:\WINNT\SYSTEM32\NOTEPAD.EXE',1)
----------------------------------------
                                      33

Now, I have no idea what 33 is for their return codes but basically NOTEPAD is
actually running -- you just cannot see it.  Using the resource toolkit I can
show you this.  I'm on the server when I do this:

tkyte@TKYTE816> host tlist notepad

tkyte@TKYTE816> select func1( 'C:\WINNT\system32\notepad.EXE', 1 ) from dual;

FUNC1('C:\WINNT\SYSTEM32\NOTEPAD.EXE',1)
----------------------------------------
                                      33

tkyte@TKYTE816> host tlist notepad
1404 notepad.exe
   CWD:     C:\WINNT\system32\
   CmdLine: C:\WINNT\system32\notepad.EXE
   VirtualSize:    11764 KB   PeakVirtualSize:    11764 KB
   WorkingSetSize:   996 KB   PeakWorkingSetSize:   996 KB
   NumberOfThreads: 1
      0 Win32StartAddr:0x00000000 LastErr:0x00000000 State:Initialized
     5.0.2140.1 shp  0x01000000  notepad.EXE
     5.0.2163.1 shp  0x77f80000  ntdll.dll
     5.0.2920.0 shp  0x76b30000  comdlg32.dll
     5.0.2920.0 shp  0x77c70000  SHLWAPI.DLL
     5.0.2180.1 shp  0x77f40000  GDI32.DLL
     5.0.2191.1 shp  0x77e80000  KERNEL32.DLL
     5.0.2180.1 shp  0x77e10000  USER32.DLL
     5.0.2191.1 shp  0x77db0000  ADVAPI32.DLL
     5.0.2193.1 shp  0x77d40000  RPCRT4.DLL
    5.81.2920.0 shp  0x77b50000  COMCTL32.DLL
     5.0.2920.0 shp  0x775a0000  SHELL32.DLL
     6.1.8637.0 shp  0x78000000  MSVCRT.DLL
     5.0.2167.1 shp  0x77800000  WINSPOOL.DRV

The old "now you don't see it, now you do".  Notepad is running but I don't see
it anywhere.

You can use this technique to run things that do not demand a console safely
(after you run this notepad thing, its up to you to figure out how to KILL all
of the notepads that are now running on your server, i used the nt resource
toolkit kill command).
Review & Followup

      Rating: 3
Problem
      solved  January 13, 2002
Reviewer:  Mudassar
      Shahzad  from Kuwait
Hello Tom,

First of all thanks for your solution. It did work. But it only works for Oracle
8.1.5; which I had asked for. I tried the same solution on Oracle 9i, but it
doesnot work for Oracle 9i.

      Rating: 5
what
      dll???  March 09, 2002
Reviewer:  Senthil
       from Massachusetts
i am writing a script for hot backup. As my environment is, Oracle 8.0.5 on
Windows NT, I am thinking of writing script on database side, That is, writing a
stored procedure that sets/resets tablespace to backup mode and copies datafiles
to a backup directory. I guess i will have to use Oracle's "ocopy" utility. And
also, if i'd have to write script on database side, have to use external library
to invoke "ocopy" utility. If its a simple copy command, i would have used
kernel32.dll. But, to invoke ocopy, is there any dll available. i dont want to
write my own library/dll to call the utility. Could you please provide some
idea.
thanks,
senthil.

          Followup:

No, there is no DLL for ocopy -- and I would urge you to write your OWN C code
to do this (invoke ocopy) instead of just calling kernel32.dll.  You'll need to
verify that the file sizes and such are correct after the copy -- just using the
SYSTEM() call is too simplistic for something as important as a backup.

      Rating: 5
question
      again?  March 12, 2002
Reviewer:
      A reader
Sorry Tom, to pull back the question again.

i have created a new dll, using Borland C++ compiler. i am able to register the
dll on my database, but if i try to call the function am getting "unable to load
dll function". Here is the code for easy reference...

#include <fstream.h>

#define BUFLEN 256

#ifdef WIN32COMMON
__declspec(dllexport)
#endif
long getfilesize(const char *name)
{
   ifstream ifs(name);
   if (!ifs)
      return -1;
   ifs.seekg(0, ios::end);
      return ifs.tellg();
// ifstream destructor will close the file
}

In sqlplus...
SQL> get shell
  1  create or replace library mylib_getfilesize as 'd:\users\library\devl.dll';
  2  /
  3  create or replace function mylib_getfilesize_func(cmd in varchar2)
  4  return binary_integer as
  5  external library mylib_getfilesize
  6  name "getfilesize"
  7  language c
  8  parameters (cmd string, return long);
  9  /
 10  var r number;
 11  exec :r := mylib_getfilesize_func('d:\a.log');
 12* print r
 13  .

SQL> @shell

Library created.

Function created.

BEGIN :r := mylib_getfilesize_func('d:\a.log'); END;

*
ERROR at line 1:
ORA-06521: PL/SQL: Error mapping function
ORA-06522: Unable to load symbol from DLL
ORA-06512: at "SCOTT.MYLIB_GETFILESIZE_FUNC", line 0
ORA-06512: at line 1

         R
----------

For testing purpose, i am using my own database, which is Oracle 8.1.7 personal
edition.

your comments will definitely help me...

thanks,
senthil.

          Followup:

You are probably compiling in C++ mode -- the name "getfilesize" is being
'mangled' into something else.

Compile in C mode, not C++.  Turn off the name mangling.

      Rating: 5
RE: Senthil's
      code  March 12, 2002
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Tom:

PMFJI...

I realize you did not write the code for the external library in question, but I
wanted to pass along a quick observation about that code...

I believe the following line will cause an unintended result:

ifstream ifs(name);

This code allows the default values for the second and third parameters of the
constructor to be taken.  The second parameter will default to 'ios::in',
causing an empty file to be created if it does not already exist.  Therefore,
the "if (!ifs)..." will succeed, even though that is probably not desirable in
this case.

I believe the code would be better as something like:

ifstream ifs(name, ios::in | ios::nocreate);

It has been awhile since I was coding, but I think I am remembering this
correctly - if not sorry, and just tell me to sod off!  Plus, I don't have a
Borland compiler to verify this with...

- Mark

          Followup:

could be -- I never really used C++, finding C more then capable of doing
whatever needed be done ;)

      Rating: 5
RE:
      C  March 13, 2002
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Tom:

Agree with you in regard to C...  The code presented by the poster probably
would be better off in straight C anyway.  Now he's going to have to wrap
"extern C..." around stuff and/or make sure he's not using name mangling as
you've already pointed out.

I have found (in the past anyway) people think they are doing OO just because
they use C++...  anyway, that's another story.  I just wanted to let the poster
know he may be getting some unexpected results...

Thanks!

- Mark

      Rating:
      5
  March 13, 2002
Reviewer:  Senthil
       from Massachusetts
Mark-

You could be right. As i am just trying to create library(dll) and use it my
procedure. If that started working, then i should be concentrating on the
library side. And also, i stole the code from one of the forums, so, might be
having a lot of flaws. i should appreciate you for reviewing the code. Thank
you.

thanks,
senthil.

      Rating: 5
external
      procedure  December 02, 2003
Reviewer:
      abeda  from INDIA
Hi,

While i'm trying to execute external procedure,
Following error has been occured:

SQL> create library libgettz as '/peur/cre/ora0816/gettz.a';   2  /

Library created.

SQL> create or replace function gettz
  2  return char is external
  3  library libgettz
  4  name "gettz"
  5  language C;
  6  /

Function created.

SQL> set serveroutput on
SQL> select gettz from dual;
select gettz from dual
       *
ERROR at line 1:
ORA-06521: PL/SQL: Error mapping function
ORA-06522: dlsym(): symbol not found
ORA-06512: at "SYSTEM.GETTZ", line 0
ORA-06512: at line 1
--------------------

Please suggest us.

Thanks in advance!

          Followup:

you don't have a function gettz in your library according to the error message.

You are using a ".a" (archive), it should be a ".so" (shared object).

If you have my book "Expert one on one Oracle" -- i spent alot of time going
over everything about extprocs.

December 02, 2003 Koshal -- Thanks for the question regarding
      "Index Organised Tables Vs Ordinary Tables", version 8.1.7.0.0

      You Asked
Hi Tom

I am having two tables
1.
CREATE TABLE NEWT (
  X  NUMBER (10)   NOT NULL,
  Y  VARCHAR2 (40),
  PRIMARY KEY ( X )
    USING INDEX
     TABLESPACE SYSTEM PCTFREE 10
     STORAGE ( INITIAL 65536 NEXT 8724480 PCTINCREASE 50 ))

2.

CREATE TABLE T (
  X  NUMBER (10)   NOT NULL,
  Y  VARCHAR2 (40),
  CONSTRAINT SYS_IOT_TOP_30792
  PRIMARY KEY ( X ))
 ORGANIZATION INDEX  NOCOMPRESS  PCTTHRESHOLD 50;

SQL> alter system flush shared_pool;

System altered.

Elapsed: 00:00:00.10
SQL> select count(*) from newt where x>2000;

  COUNT(*)
----------
   1010000

Elapsed: 00:00:01.42

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'SYS_C002502' (UNIQUE)

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

SQL> alter system flush shared_pool;

System altered.

Elapsed: 00:00:00.30
SQL> select count(*) from t where x>2000;

  COUNT(*)
----------
   1008000

Elapsed: 00:00:01.41

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=13)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'SYS_IOT_TOP_30792' (UNIQUE) (Cost
          =2 Card=409 Bytes=5317)

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

SQL>

I executed the query n number of times i could not find any
significant difference between two tables even though there where
around one million records.

Under what circumstances is index organised tables going increase the
performance.

Where Index Organized tables are to be specifically used.

Thanks In Advance

Regards

      and we said...
why flush the shared pool??? the only thing you did there was make the recursive
sql calls go way up as we had to hard parse that query????

Look at that query plan and consistent gets -- in this case, the table is
actually more efficient!

The count(*) read the index of the table -- the index had the number only and
did about 1920 logical IOs.  The IOT on the other hand had over 3200 logical
IOs. for that query -- IOTs would be less efficient.

Where would an IOT be more efficient.  Think of a "stocks" table.  Every day at
4pm you get 1,000 new records that have:

stock ticker, date, volumne, closing price. (primary key stock ticker, date)

You load that into a conventional table -- they get added to the end of the
table.  Tomorrow everyone runs the query:

select avg(closing_price)
from stocks
where stock_ticker = 'ORCL' and date >= sysdate - 6;

Well, on the conventional table that'll use the primary key to find the 5 or 6
matching rows and do a separate IO to the table for each stock_ticker to get the
price.  Well, since the data is added to the end of the heap table each night --
that will be 5 or 6 DIFFERENT blocks.  Each row in the table for the stock
ticker ORCL will be on a DIFFERENT block due to the nature of your load routine
-- every night you get 1,000 more and they are added to the end.  Each and every
entry for ORCL (or any stock) is on a separate block.  Too bad -- your
application ALWAYS asks for stocks of a given ticker in a date range -- you blow
out your buffer cache.

Ahh, now store that in an IOT.  Each night the load puts all of the ORCL data
"near" all of the other ORCL data.  Tomorrow when you run that exact same query
-- we'll index range scan and find ALL of our data on a single block.  Here we
are using the IOT to force a physical clustering of data -- all things ORCL are
grouped together physically -- the buffer cache is able to cache our one block
-- not all 5 (with lots of data we don't care about).  We do tons less logical
IO's and physical IOs here.

That is but one example.

Consider the table that you ALWAYS get one row from on a given query -- lots of
"select * from t where primary_key = :x".   Here we can do an index range scan,
find all of the data.  Using a conventional table we would index range scan and
then table access by rowid.  Doesn't sound too bad -- now consider 100,000
queries being executed during the course of a day -- you just saved 100,000
table access by rowids -- you saved 100,000 blocks from hitting the buffer
cache, you saved 100,000 logical IOs.  If you have lots of lookup tables like
"zip_code,state"  -- an IOT only makes sense.

there are dozens of other uses as well.
Review &
      Followup

      Rating: 4
Need help to
      understand:  August 16, 2001
Reviewer:  FRED
       from ATLANTA
since the data is added to the end of the heap table each night --
    that will be 5 or 6 DIFFERENT blocks.  Each row in the table for the stock
    ticker ORCL will be on a DIFFERENT block due to the nature of your load
routine
    -- every night you get 1,000 more and they are added to the end.  Each and
every
    entry for ORCL (or any stock) is on a separate block.

WHY UN-ENABLE LOADING A FEW RECORDS IN A BLOCK IN REGULAR TABLE?

      Rating: 4
Heap
      tables  June 10, 2002
Reviewer:  Andre
       from Brazil
Why are non-IOT tables often called "heap" tables ?

          Followup:

Because that is what they are?  It is the default table type:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int ) organization HEAP;

Table created.

The data in a heap organized table is stored in a "heap" fashion -- data goes
where ever it fits.  In an IOT -- data goes where it belongs (by key).  For tons
of details on this (and all of the table types) -- I will recommmend my book.  I
go through this in some detail.

      Rating:
      5
  December 20, 2002
Reviewer:  Gopi

It was an excellent example. But I could not link the example with your last
statement: 'If you have lots of lookup tables like "zip_code,state"  -- an IOT
only makes sense.'

Can you kindly elaborate? Will it be a good idea to make the dimension tables in
a star schema IOT? Thanks verymuch.

          Followup:

if you have a two or three column table like

ZIP_CODE primary key, STATE char(2)

or

STATE_ABBREV char(2) primary key, STATE_NAME varchar2(80)

etc, you have to ask:

o do I want a table and a primary key index, such that I'll use the index to
read the table to go from code to code value

o or do I just want an index with the code and code value?

      Rating: 4
IOT in a
      Datawarehouse  June 21, 2003
Reviewer:
      A reader
Hi Tom,
Will an IOT be a good candidate for a large Fact table in a datawarehouse, where
large amount of data is loaded daily followed by massive reads?
What are the points to be considered, before implementing the Fact table as an
IOT?

          Followup:

fact?  probably not.

dimension?  probably so.

that is where they seem more useful.

How many keyed reads into a fact table do you do?  there the index structure
would just get in the way. full scan ahead.

dimensions -- perhaps you would want to benchmark them there.

      Rating:
      5
Thanks  June 21, 2003
Reviewer:
      A reader

      Rating: 3
What about an
      IOT Dual table?  October 31, 2003
Reviewer:
      Murali  from Raleigh,NC USA
Hi Tom,

One of my friends was mentioning that their setup has an IOT called X$DUAL and
they use it to select values like sysdate , user etc instead of a regular DUAL
table.

Do you think this is a good idea?. If either yes or no, Can you please elaborate
the reasons?.

Thanks.

          Followup:

well, x$ tables are "special", they'd have to create a view of that in order to
grant on it.

I can say in 10g -- the problem goes away.  Check this out:

ops$tkyte@ORA10G> select * from dual;

D
-
X

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=2)
   1    0   TABLE ACCESS (FULL) OF 'DUAL' (TABLE) (Cost=3 Card=1 Bytes=2)

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

ops$tkyte@ORA10G> select sysdate from dual;

SYSDATE
---------
01-NOV-03

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1)
   1    0   FAST DUAL (Cost=3 Card=1)

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

see "fast dual" in the second one.  If you do not actually select a column
from dual -- it short circuts and just executes your function.  No IO at all.

I would setup my OWN table -- not use x$dual

      Rating: 3
IOT or not IOT
      - that is the question  November 24, 2003

Reviewer:  j.
one of our applications uses one single table as storage for all language
specific text values.

those texts are stored in 3 columns (10, 30 or 80 characters long) and accessed
through a composite primary key consisting of an ID and an additional "language
marker".

views join this table's content to present text according to the language
specified as an environment variable.

apart from attacking the issue using the brute force approach (just measure the
impact) how can i evaluate
whether or not an IOT would reduce I/O (and be a performance gain after all)?

          Followup:

IOT's are awesome for these sort of keyed lookups.

Suggest you do this

a) trace your application, save the tkprofs of the queries that access this
table
b) simply rebuild the table as an IOT and do (a) again.

there is nothing short of benchmarking here...  we can hypothesize all day long,
but until you measure it, you'll never know for sure.

      Rating: 4
what about
      storage options  November 30, 2003
Reviewer:
      j.
thank you. i'll give it a try.

but please let me ask an additional question first: since the application almost
always accesses only the 30 characters long pieces of text from the lookup would
you recommend to put the 80 characters long column into the overflow segment to
get more blocks stored into the IOTs segment itself?

          Followup:

not really -- that offsets the benefits of the IOT itself.

You would be back to the equivalent of:

   index range scan -> table access by index rowid

where the table access would be the read into the overflow segment.

      Rating:
      3
???  December 01, 2003
Reviewer:  j.

but this only holds true in case one retrieves the 80 characters long text
instead of the 30 characters long (stored in the index), right?

          Followup:

ok, read too fast, missed the "almost always only accesses"

Here -- I would want to benchmark but -- I would still tend to want to avoid
overflow segments.

The concept of the IOT to me is to store KEY + functionally dependent data
together, sorted by key.  Overflows defeat that.

I would still tend to want to have everything in the one segment.

      Rating: 4
which datatype
      is good for an IOT?  December 01, 2003
Reviewer:
      Ashiq Shamsudeen A  from Trichy, TamilNadu, India
Hi Tom,

  My colleague was telling me ,if you use IOT,  create it for a number datatype
than for other datatypes.But he didn't gave the any metrics for it. Does we any
advantage using number datatype than using other type for index ?

          Followup:

ask them

a) why
b) for the proof

else ignore it.  Use the right datatype, if your primary key is in fact a
VARCHAR2, make it so.  think:

postal code -> region lookup (are you going to make it really be SURROGATE_KEY
-> postal_code,region lookup????)

state_code -> state lookup (are you going to make it be SURROGATE_KEY->
state_code,state ??)

I wouldn't.

      Rating: 5
Loading Index
      Organised Tables  December 01, 2003
Reviewer:
      CJ  from NY
Is there any good performing way to load an IOT? I've got a 55M row table to
load nightly. It is a 4 column lookup table in a warehouse, and I'm considering
an IOT. Since nologging does not apply to IOT, it takes a while to load. Is
there some tips you can give to speed it up?

          Followup:

insert /*+ APPEND */ into iot select * from external_table;

seems to reduce by about 50% the amount of redo I generate -- create table iot
as select * from external table -- a little more then 50% (noarchivelog mode)

how about you?

      Rating: 4
IOT
      freelist  December 01, 2003
Reviewer:  Alvin
       from Philippines
1. On concurrent inserts can you change the freelist settings of an IOT ?

2. How "wide or fat" should a table be before an IOT table "overflows" ?

          Followup:

1) yes, IOTs can have multiple freelists.

2) I think if a table is so wide or fat that it would overflow, you would be
hard pressed to have me say "make it an IOT".  I do not overflow IOTs myself.

      Rating: 5
Re: "Loading
      Index Organised Tables "  December 02, 2003

Reviewer:  Sudhir
sort_area_size/sort_area_retained_size of very large value should help?

Thanks

          Followup:

pga_aggregate_target in 9i is more appropriate.

it can help upto a point (diminishing returns after a while)

December 02, 2003 Parag -- Thanks for the question regarding
      "Integrity constraints", version 9.2

      You Asked
Hi Tom,

After going through Oracle 9i conecpts manual and your site I have not
understood yet following concept

<quote>
A constraint that is defined as deferable can be specified as one of following
1. initially immediate  or
2. initially defered
<quote>

I think I know what is defered constraints but not defered initally immediate
and defered initially defered constraints and secondly what is the use of these
constraints ? ( Defered constraints can be use for child parent insert )

It will be great help if you explain above two concepts with examples.

thanks for your help.

      and we said...
The initially immediate/deferred part tell us how the constraint should be
enforced by default

o initially immediate -- check constraint at the end of the statement execution
o initially deferred -- wait to check until the transaction ends (or you invoke
set constraint immediate)

Consider:

ops$tkyte@ORA920> create table t
  2  ( x int constraint check_x check ( x > 0 ) deferrable initially immediate,
  3    y int constraint check_y check ( y > 0 ) deferrable initially deferred
  4  )
  5  /

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( 1,1 );

1 row created.

ops$tkyte@ORA920> commit;

Commit complete.

so, when both constraints are OK, rows go in...

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( -1,1);
insert into t values ( -1,1)
*
ERROR at line 1:
ORA-02290: check constraint (OPS$TKYTE.CHECK_X) violated

check_x is deferrable but initially "immediate" so that row is rejected
straight away

ops$tkyte@ORA920> insert into t values ( 1,-1);

1 row created.

Now, the constraint check_y is deferrable and initially deferred so it doesn't
fail until....

ops$tkyte@ORA920> commit;
commit
*
ERROR at line 1:
ORA-02091: transaction rolled back
ORA-02290: check constraint (OPS$TKYTE.CHECK_Y) violated

we commit

ops$tkyte@ORA920>
ops$tkyte@ORA920> set constraints all deferred;

Constraint set.

ops$tkyte@ORA920> insert into t values ( -1,1);

1 row created.

Now, by setting the initially immediate constraint to deferred mode -- that
statement succeeds, but..

ops$tkyte@ORA920> commit;
commit
*
ERROR at line 1:
ORA-02091: transaction rolled back
ORA-02290: check constraint (OPS$TKYTE.CHECK_X) violated

the transaction fails since the check constraint is checked upon commit..

ops$tkyte@ORA920>
ops$tkyte@ORA920> set constraints all immediate;

Constraint set.

ops$tkyte@ORA920> insert into t values ( 1,-1);
insert into t values ( 1,-1)
*
ERROR at line 1:
ORA-02290: check constraint (OPS$TKYTE.CHECK_Y) violated

and now the statement that used to work when the constraint was initially
deferred -- fails immediately

Review &
      Followup

      Rating: 5
set constraints
      all default  July 09, 2003
Reviewer:  Logan
      Palanisamy  from Santa Clara, CA USA
Tom,

Excellent explanation.

I wanted to put back the constraints to their default state after setting them
to either "set constraints all deferred" or "set constraints all immediate".

So I tried "set constraints all default". It didn't work. I had to use the
"alter session set constraints=default" to put them back to the default state.

Looks like a commit also put the constraints back to the their default state.

Nevertheless, what could be the reason for not having a construct like "set
constraints all default"
when a construct like
"alter session set constraints=default" is available?

Why this inconsistency vis a vis
"set constraints all {immediate|deferred}" and
"alter session set constraints={immediate|deferred|default}"

Just a little curious. That's all.

Here is my demo using the same example.

SQL> set constraints all default;
set constraints all default
                    *
ERROR at line 1:
ORA-00905: missing keyword

SQL> insert into t values ( -1,1);
insert into t values ( -1,1)
*
ERROR at line 1:
ORA-02290: check constraint (SCOTT.CHECK_X) violated

SQL> insert into t values ( 1,-1);

1 row created.

SQL> commit;
commit
*
ERROR at line 1:
ORA-02091: transaction rolled back
ORA-02290: check constraint (SCOTT.CHECK_Y) violated

SQL>
SQL> alter session set constraints = default;

Session altered.

SQL> insert into t values ( -1,1);
insert into t values ( -1,1)
*
ERROR at line 1:
ORA-02290: check constraint (SCOTT.CHECK_X) violated

SQL> insert into t values ( 1,-1);

1 row created.

SQL> commit;
commit
*
ERROR at line 1:
ORA-02091: transaction rolled back
ORA-02290: check constraint (SCOTT.CHECK_Y) violated

SQL>

          Followup:

well, it would be a strange situation to be in the of such a facility.

consider that the SET CONSTRAINT command lives for the life of a transaction
only.

that it is somewhat assumed that it would be the first, or near the first,
instruction of a transaction (this transaction will run in IMMEDIATE MODE, or
DEFERRED MODE).

that default is the de-facto default mode -- there is no reason to set to
default, you are already there.

Then it makes sense.  the ALTER SESSION command is longer lived -- it lives over
multiple transactions (for the session) but can be overridden on a transaction
by transaction basis.

      Rating: 4
Integrity
      constraints  July 10, 2003
Reviewer:  Moiz
       from India
Hi Tom ,
Execellent Explanation!!
I have  a table with no data and now in one Existing column,I am adding NOT NULL
constraints with deferrable initially deferred constraint. and it gives me
error.
Pls help me out...

Thanx in Advance..

          Followup:

any error in particular, how about this -- give us a CLEAR concise, yet complete
example..... then maybe we can comment.

      Rating:
      4
  July 10, 2003
Reviewer:  Dave
       from Colorado
I believe that NOT NULL constraints are not deferrable

          Followup:

they are:

ops$tkyte@ORA920> create table t ( x number constraint x_not_null not null
deferrable )
  2  ;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( null );
insert into t values ( null )
*
ERROR at line 1:
ORA-02290: check constraint (OPS$TKYTE.X_NOT_NULL) violated

ops$tkyte@ORA920> set constraints all deferred;

Constraint set.

ops$tkyte@ORA920> insert into t values ( null );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> commit;
commit
*
ERROR at line 1:
ORA-02091: transaction rolled back
ORA-02290: check constraint (OPS$TKYTE.X_NOT_NULL) violated

      Rating:
      5
  July 10, 2003
Reviewer:  Chuck
      Jolley  from OKC, OK USA
Tom,
What would be a real world reason for using defered constraints?
Eventualy, the code has to create the parent or replace the null or foreign key,
etc.
Is it just a matter of making coding easier?
In our case, we do a lot of batch type processing and in those processes it
seems like this would just cause a lot of needless work for the server if there
was a constraint violation somewhere in the batch.
chuck

          Followup:

snapshots (materialized views) use them in order to perform their refreshes,
during the refresh - RI can be violated, constraints out of wack -- but by the
time "commit" comes around everything is OK.

you can use them to facilitate an update cascade -- set the fkeys to be
deferrable and you set:

set constraints all deferred;
update parent
update children
set constraints all immediate <<== fails if there are any orphans, you can fix
                                   that
commit; <<=== fails and rolls BACK the work if there are any orphans

You can use them in a variety of multi-statement transactions that need to
violate integrity during the course of the transaction -- but end up with
everything "as it should be"

      Rating: 5
Deffered
      constraint  July 15, 2003
Reviewer:  Moiz
       from India
Hi Tom,

SQL>
 1  create table m_signature
  2  ( Br_code varchar2(2) not null,
  3  ac_no   number(6) not null,
  4* ac_sign  blob)
SQL> /

Table created.

THis is the requirement like:
Make Ac_sign column as NOT NULL and that constraint should be DEFERRABLE and
INITIALLY DEFERRED. This is required as this table is to be used in REPLICATION
and according to Metalink, the BLOB columns should have DEFERRABLE NOT NULL
constraint else the table always needs COMPLETE REFRESH.

The syntax required like: (This is not taking place)

SQL> alter table m_signature
  2  add (constraint nn_sign not null(ac_sign) deferrable initally deferred)
  3  /
add (constraint nn_sign not null(ac_sign) deferrable initally deferred)
                        *
ERROR at line 2:
ORA-00904: invalid column name

So , I tried it with other way also ..
SQL> ed
Wrote file afiedt.buf

  1  alter table m_signature
  2* modify (ac_sign blob constraint nn_sign not null  deferrable initally
deferred)
SQL> /
modify (ac_sign blob constraint nn_sign not null  deferrable initally deferred)
                                                             *
ERROR at line 2:
ORA-02253: constraint specification not allowed here

Pls , Help me out.

Moiz.

          Followup:

ops$tkyte@ORA920LAP> create table t ( x int, y blob );

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> alter table t
  2  add constraint check_y_is_not_null
  3  check (y is not null)
  4  deferrable initially deferred;

Table altered.

you cannot specify the NOT NULL constraint out of line (not in the create table)
that way -- so the check constraint, which does the same thing, is used.

      Rating:
      4
  July 16, 2003
Reviewer:  Parag

Excellent!!! Just small query ->

Are deferrable constraints by default are of type initially deferred ?

thx

          Followup:

documentation or simple trivial easy for you to have tried test says.....

initially immediate

ops$tkyte@ORA920LAP> create table t ( x int check (x>0) deferrable );

Table created.

ops$tkyte@ORA920LAP> insert into t values ( -1 );
insert into t values ( -1 )
*
ERROR at line 1:
ORA-02290: check constraint (OPS$TKYTE.SYS_C005785) violated

ops$tkyte@ORA920LAP> set constraints all deferred;

Constraint set.

ops$tkyte@ORA920LAP> insert into t values ( -1 );

1 row created.

      Rating:
      4
  July 17, 2003
Reviewer:  parag

Thanks TOM for helping us by excellent logical answers.

Once again a query to ask you!!!!

if deferrable constraints by default
initially immediate it means constraint has to execute at the end of the
statement execution then how deferrable constraint is working in inserting of
"child parent records"
in a transaction ?

( bcoz in child parent insert we want deferrable constraints to be checked only
at commit i.e transaction ends it means in such situations we have to specially
set deferrable constraints initially deferred when we have to insert child -
parent insert correct ?)

thx

          Followup:

a deferrable constraint that is initially immediate works NO DIFFERENTLY then a
non-deferrable constraint.

If you want to check at the end of the transaction, instead of statement you
would either

a) set constraint all | constraint_name deferred
b) create them initially deferred.

      Rating: 5
Constraints on
      multiple rows  July 23, 2003
Reviewer:
      A reader
Hi Tom,

Assume I have a table

T (id number, seq number, percentage number);

PK is (id, seq).

How could I create a check constraint for the business rule of "the total of
percentages of each id always equals 100"?

For example:
(id, seq, percentage)
1, 1, 100
2, 1, 40
2, 2, 60
...

Please help. Thanks.

          Followup:

here is a short excerpt from my book coming out next month (aug 2003) - it shows
how to do this -- but it'll require another table for you -- one that contains
the total pct by id -- maintained via a trigger:

To solve the problem, we can use serialization and server-enforced integrity.
There are a couple of ways to do this. Here, we'll use the DEPT table, where
we'll maintain an aggregate column EMP_COUNT. It looks like this:

ops$tkyte@ORA920> alter table dept
  2  add emp_count number
  3  constraint must_be_between_3_8
  4  check(emp_count between 3 and 8 OR emp_count = 0)
  5  deferrable initially deferred;
Table altered.

ops$tkyte@ORA920> update dept
  2     set emp_count = (select count(*)
  3                        from emp
  4                       where emp.deptno = dept.deptno )
  5  /
4 rows updated.

ops$tkyte@ORA920> alter table dept
  2  modify emp_count NOT NULL;
Table altered.

Now, we have an aggregate column in the DEPT table that we will use to maintain
a count of employees. Additionally, this column has a declarative check
constraint on it that verifies the count of employees is either 0 or between 3
and 8, as specified in the rule. Lastly, we made this constraint deferrable
initially deferred, meaning it will not be validated until we commit by default.
The reason for doing that is to permit multistatement transactions to execute
without error. For example, we could update a row setting the DEPTNO from 20 to
30, reducing the number of employees in DEPTNO 20 to 2 but immediately follow
that with an update of another row from DEPTNO 30 to 20, increasing the number
of employees back to 3. If the constraint were validated after each and every
statement, this sort of logic would fail.

Now, we need a trigger to maintain that value:

ops$tkyte@ORA920> create trigger emp_dept_cnt_trigger
  2  after insert or update or delete on emp
  3  for each row
  4  begin
  5      if ( updating and :old.deptno = :new.deptno )
  6      then
  7          return; -- no change
  8      end if;
  9      if ( inserting or updating )
 10      then
 11          update dept set emp_count = emp_count+1
 12           where deptno = :new.deptno;
 13      end if;
 14      if ( updating or deleting )
 15      then
 16          update dept set emp_count = emp_count-1
 17           where deptno = :old.deptno;
 18      end if;
 19  end;
 20  /
Trigger created.

Note:    For ease of example, the SQL for the trigger is presented in-line. It
would be advantageous to place the SQL into a stored procedure, especially in
this case. Not only would we benefit from reducing the soft parsing our
application performs, but we would also be able to get away with a single UPDATE
statement.

If we are updating and did not change the DEPTNO column, we just return; no
changes need to be made. Otherwise, we update the DEPT table and increment or
decrement the EMP_COUNT column as needed, since we are using foreign keys. We
are assured that the UPDATE statements update at least and at most a single row
in DEPT. (You cannot possibly have a child row in EMP with a DEPTNO that does
not exist in DEPT, because the database is enforcing that for us.)

The update of the single row in DEPT also causes serialization at the DEPTNO
level; that is, only one transaction can insert into DEPTNO=20 at any point in
time. All other sessions trying to insert, delete, or transfer where DEPTNO=20
will block on that update. All of the other DEPTNO operations are available for
modification; just not this one. We can still update other EMP rows in
DEPTNO=20, as long as we do not change their DEPTNO. This serialization is what
allows our constraint to be effective.

      Rating: 5
(Following my
      last message)  July 23, 2003
Reviewer:  Fan
       from Canada
Hi Tom,

I did some DIY, following your another post on triggers and constraints.

SQL> create table trans (id number, seq number, percentage number);

Table created.

SQL>
SQL> create table trans_sum (id number, total number);

Table created.

SQL>
SQL> alter table trans_sum add (constraint pk1 primary key (id));

Table altered.

SQL>
SQL> alter table trans add
  2  (constraint sum_fk foreign key (id) references trans_sum(id));

Table altered.

SQL>
SQL> alter table trans_sum add (constraint ck_100 check (total = 100) deferrable
initially deferred)
;

Table altered.

SQL>
SQL> create or replace trigger trans_trigger
  2  after insert on trans for each row
  3  begin
  4
  5      insert into trans_sum values (:new.id, :new.percentage);
  6
  7  exception
  8      when dup_val_on_index
  9      then
 10         update trans_sum set total = total + :new.percentage where id =
:new.id;
 11  end;
 12  /

Trigger created.

SQL>
SQL> insert into trans values (1, 1, 80);

1 row created.

SQL>
SQL> insert into trans values (1, 2, 20);

1 row created.

SQL>
SQL> commit;

Commit complete.

SQL> select * from trans_sum;

        ID      TOTAL
---------- ----------
         1        100

1 row selected.

SQL> insert into trans values (2, 1, 20);

1 row created.

SQL>
SQL> commit;
commit
*
ERROR at line 1:
ORA-02091: transaction rolled back
ORA-02290: check constraint (SCOTT.CK_100) violated

SQL>

It seems to work. Again my question if this is the only way...if possible to
implement as CHECK constraint?

Thanks.

      Rating: 3
Materialized
      View refresh and Foreign Key constraints  August 14, 2003

Reviewer:  Albert de Ruiter  from Utrecht, The
      Netherlands
Dear Tom,
In your reply to Chuck (July 10) you mention the refreshing of materialized
views regarding deferred constraints. Also in another thread ("Constraints using
snapshot" April 3, 2001) you give a similar solution. Yet I am having some
problems in achieving the same result. By the way, I'm on Oracle 8.1.7.

For a reporting solution I use materialized views. Test is as follows.

ALTER TABLE BI_INKOMSTEN_EN_KOSTEN_MV ADD CONSTRAINT INK_KST_FK_VHE FOREIGN KEY
(VHE_FK) REFERENCES BI.BI_VHE_MV
;

create materialized view test_mv_1 as
select '1' PK from dual
;
alter table test_mv_1 add constraint test_mv_1_fk
primary key (PK)
;
create materialized view test_mv_2 as
select '1' FK from dual
;
alter table test_mv_2 add constraint test_mv_2_fk
foreign key (FK) references test_mv_1
deferrable initially deferred
;
set constraint test_mv_2_fk deferred;

bi@SVG2> create materialized view test_mv_1 as
  2  select '1' PK from dual
  3  ;

Materialized view created.

bi@SVG2> alter table test_mv_1 add constraint test_mv_1_fk
  2  primary key (PK)
  3  ;

Table altered.

bi@SVG2> create materialized view test_mv_2 as
  2  select '1' FK from dual
  3  ;

Materialized view created.

bi@SVG2> alter table test_mv_2 add constraint test_mv_2_fk
  2  foreign key (FK) references test_mv_1
  3  deferrable initially deferred
  4  ;

Table altered.

bi@SVG2> exec dbms_mview.refresh('bi.test_mv_1');
BEGIN dbms_mview.refresh('bi.test_mv_1'); END;

*
ERROR at line 1:
ORA-12008: error in snapshot refresh path
ORA-02266: unique/primary keys in table referenced by enabled foreign keys
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 617
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 674
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 654
ORA-06512: at line 1

So maybe I should have stated explicitly that the constraint should be deferred
(although you would say that 'initially deferred' is the default here; or does
'at the end of the transaction' mean something different here than 'after
commit'?)

bi@SVG2> set constraint test_mv_2_fk deferred;

Constraint set.

bi@SVG2> exec dbms_mview.refresh('bi.test_mv_1');
BEGIN dbms_mview.refresh('bi.test_mv_1'); END;

*
ERROR at line 1:
ORA-12008: error in snapshot refresh path
ORA-02266: unique/primary keys in table referenced by enabled foreign keys
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 617
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 674
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 654
ORA-06512: at line 1

I must be overlooking something here. Do you know what?
Your help will be greatly appreciated.

Kind regards,

Albert.

          Followup:

where are your snapshot groups?  mv1 and mv2 HAVE to be refreshed as a
consistent refresh group -- else all is for naught here.

      Rating: 4
Refresh
      groups  August 28, 2003
Reviewer:  Albert R. de
      Ruiter  from Utrecht, The Netherlands
Oracle 8.1.7

Hi Tom,

After your suggestion to apply refresh-groups in this case
I did some studying.
If the solution that I can come up with makes some sense,
then I am a little bit puzzled about why something trivial
like a refresh has to be accompanied by so much extra effort.

Can you please look at my thoughts and tell if it makes sense?

What I understand of the refresh groups is that you should
group together dimension tables with the fact table (in general
tables that reference each other).
All materialized views are refreshed in one go and there is no
ora-02266 bothering you.
But what if a dimension table is referenced by more than 1
fact table? Couldn't you, put simply, eventually end up with
one big group containing all materialized views like that?

So I read somewhere on Metalink that alternatives could be:
1. Disable the constraints prior to the refresh; then you
would have to specify them by name in your code (no such
thing as to disable all constraints within one schema?).
Since this doesn't look very generic to me, I considered
alternative 2.
2. Use a dummy materialized view to group a regular
materialized view with. Disadvantage would be that you would
require a lot of extra objects (the amount of mv-s doubles),
or you would have to create a dummy prior to the refresh and
drop it afterwards.

The idea is that the materialized views are refreshed by means
of a job, so that a commit on the master-table doesn't include
the refresh. The job is created by a trigger on the master-table.

Thus it would go as follows:

/********************************************************
 First create the dummy materialized view
********************************************************/
CREATE MATERIALIZED VIEW BI_DUAL1_MV
REFRESH COMPLETE
AS SELECT * FROM DUAL
;
/********************************************************
 Then the refresh-group is created
********************************************************/
BEGIN
   dbms_refresh.make(
    name    =>    'BI.BI_MISRUBRIEKEN_MV_GRP',
    list    =>    'BI.BI_MISRUBRIEKEN_MV, BI_DUAL1_MV',
    next_date =>    null,
    interval =>    null,
    lax    =>    TRUE);
END;
/
COMMIT;
/*******************************************************/
 Finally the trigger is created
/*******************************************************/
CREATE TRIGGER MISRUBR_REFR
AFTER INSERT OR UPDATE ON CTAB.CTB_MIS_RUBRIEKEN
DECLARE
   l_job NUMBER;
   l_count NUMBER;
BEGIN
   SELECT COUNT('x') INTO l_count
   FROM dba_jobs
   WHERE UPPER(WHAT) LIKE '%BI.BI_MISRUBRIEKEN_MV%';
   IF l_count = 0
   THEN
   DBMS_JOB.SUBMIT(
    job      =>    l_job,
    what     => 'dbms_refresh.refresh( ''BI.BI_MISRUBRIEKEN_MV_GRP'');
            dbms_stats.gather_table_stats(
''BI'',''BI_PORTEFEUILLES_MV'',CASCADE => TRUE);',
    next_date => TRUNC(SYSDATE) + 9/24 );
   END IF;
END
;

And the above steps are repeated for the remaining
materialized views.

Apart from the already mentioned problem that so much
extra objects are needed like this, another disadvantage
is that the null-value in the next_date within
dbms_refresh.make is in fact interpreted as 01-01-4000.
This means that a job is created that will refresh the
group on the mentioned date. So I would have to remove
the job afterwards, because I only want the trigger
to create a job.

Of course I could supply next_date and interval with
proper values, but I would like the materialized view
only to be refreshed when the mastertable has changed.

Something is going wrong anyway, cause I now get
ora-12008 error in snapshot refresh path
ora-02292 integrity constraint <name> violated,
child record found.

Do you know of any alternatives for disabling the fk-s
by name or using dummy mv-s on behalf of grouping?
Or is it simply just the way it goes?

Thanks in advance for your comments and kind regards,

Albert.

          Followup:

if you have multiple tables -- related via primary/fkeys -- you have to refresh
them all at once or not at all in general.

think about it -- even with your "trick" -- what happens when you delete a
parent key????  bummer, the child tables are going to prevent that.

It goes way beyond the ora-02266 error...

Fact: you have RI
Fact: all tables involved in RI must be consistent with respect to eachother
Fact: you must refresh them all at once or *remove the integrity constraints in
order to allow them to actually refresh independently -- during this period,
there is NOT ANY RI between them*

      Rating: 5
Refresh
      groups  September 01, 2003
Reviewer:  Albert
       from Utrecht, The Netherlands
Okay Tom thanks. It's all clear now.

      Rating:
      4
Efficiency?  November 11, 2003
Reviewer:
      Jon  from Hartford, CT
I notice in your examples, you tend to favor the construct
  after insert or update or delete on table...
         if updating then...
         elsif inserting then...
         elsif deleting then
         end if;
as opposed to three separate triggers for update, insert and delete.  Is it more
efficient this way or is it a wash?

          Followup:

it was more convienent then three triggers and since they were in support of the
same "thing", it made sense to keep them together.

      Rating: 5
Thanks - hope
      I'm right  November 11, 2003
Reviewer:  Jon
       from Hartford, CT
I took your answer to mean there is no efficiency gain.  I would appreciate a
response only if I misinterpretted your answer.  Thanks much either way.

          Followup:

to make it more efficient, i would really put the SQL into a plsql package --
the trigger would be one line long - a call to a package with the logic.

There would be no real efficiency gain in three triggers (other then the removal
of the need for an if/elsif construct, minimal)

      Rating: 3
inseting a row
      contain foreign key constraint  December 02, 2003

Reviewer:  apl
i got a doubt.I checked the integrity constaint in scott's schema.
 sql>insert into emp (EMPNO,ENAME) values (5,'aaa');
i got
*
ERROR at line 1:
ORA-00001: unique constraint (SCOTT.PK_EMP) violated
then i gave

SQL> insert into emp (EMPNO) values (5);

1 row created.

How is it possible?

          Followup:

show us the definition of pk_emp eh?  tell us how you created EMP table.... (our
demobld does not create an emp_pk)

Here is one way to get what you got:

ops$tkyte@ORA920PC> create table emp as select * from scott.emp where 1=0;

Table created.

ops$tkyte@ORA920PC> alter table emp add constraint emp_pk unique(empno,ename);

Table altered.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert into emp (empno,ename) values(5,'aaa');

1 row created.

ops$tkyte@ORA920PC> insert into emp (empno,ename) values(5,'aaa');
insert into emp (empno,ename) values(5,'aaa')
*
ERROR at line 1:
ORA-00001: unique constraint (OPS$TKYTE.EMP_PK) violated

ops$tkyte@ORA920PC> insert into emp (empno) values(5);

1 row created.

December 02, 2003 Anil -- Thanks for the question regarding
      "V$session_wait", version 8.1.5

      You Asked
Hi tom,
   Here is the query

SQL> select segment_name,segment_type,owner,tablespace_name from
  2  dba_extents,v$session_wait
  3  where file_id=p1
  4  and p2 between block_id and block_id + blocks -1;

SEGMENT_NAME
 SEGMENT_TYPE
---------------------------------------------------------------------------------
 ----------------
OWNER                          TABLESPACE_NAME
------------------------------ ------------------------------
A                                                                     TABLE
try                           T

B                                                                   TABLE
try                           T

C                                                                    TABLE
try                           T

C_OBJ#
 CLUSTER
SYS                            SYSTEM

Is this the correct query(look for v$session_wait..) ??If above is correct..it
means I have to increase
freelist for this table.

2)when i say set timing on..i get time somthing like

real: 3244...i would like to see this hour:min:ss..how do i do this ??

3)select state from v$session_wait;
when i say this i get lot many waiting and one or two times
"waited unknown time"..Is this normal ??

4)when i take hotbackup(scheduled at night..all tablespace) it goes on and on
till next day morning ..which i don't want.I am following
your advice on backup of mon/wed/fri and keep the logs.Now since
database has become big..it takes lot of time.Can you suggest me
something further on this.

Thanks

Regards
  Ak

      and we said...
1) no, that query is not corrct.  You are ignoring what WAIT is happening.  very
very few waits, such as:

buffer busy due to global cache
buffer busy waits

have a file#, block# in p1, p2.  Please refer to the reference guide to find the
waits you want to look at exactly.

No, it does not mean you have to increase the freelist.  For example, if the
wait was a busy buffer wait (cut and pasted right from the SERVER reference
manual here:

...
Wait until a buffer becomes available. This event happens because a buffer is
either being read into the buffer cache by another session (and the session is
waiting for that read to complete) or the buffer is the buffer cache, but in a
incompatible mode (that is, some other session is changing the buffer).
....

increasing freelists won't help that.

2)don't use timing (very unrealiable anyway) -- use
sql_trace+tkprof+timed_statistics (very accurate, better information)

Or -- use the 8i sqlplus -- they all do the hour:mi:ss display -- you must be
using 8.0 or before sqlplus on NT.

3) yes, its normal, you do not have timed statistics turned on.

4) back it up faster -- get a faster tape drive, do it in parallel, make it go
faster.  don't know exactly what to tell you here -- don't know enough about
your system.  Tape drives go very fast these days (if you pay for them).
Review &
      Followup

      Rating: 5
and maybe just
      backup less...  August 16, 2001
Reviewer:
      Connor  from UK
Take a look at what is "read-only" in your system.  If you can, then move it to
a read-only tablespace that then does not need to be backed up as frequently

      Rating:
      5
  August 16, 2001
Reviewer:  Ason
       from LA
Tom

Give the link to SERVER reference
manual

      Rating: 2
What does this
      mean?  February 22, 2002
Reviewer:  Pawan
       from USA
Hope you had a great vacation.

I am trying to load a table with data from 7 different tables. When I run the
Select statement (with the 7 table join) in SQLPLUS then I get he results within
7-10 mins. But when I use a cursor (with this Select statement) to insert into
the table then the process becomes very slow. In 7 hours it has inserted only
300,000 records ( out of 30 million that it has to). The DBA tells that we have
huge SGA (20 GB – 64 bit) and enough sort area . I just ran a query on
V$SESSION_WAIT for my SID and goth following results

ADWP: SQL> select * from v$session_wait where sid = 50;

       SID       SEQ#
---------- ----------
EVENT
----------------------------------------------------------------
P1TEXT                                                                   P1
---------------------------------------------------------------- ----------
P1RAW
----------------
P2TEXT                                                                   P2
---------------------------------------------------------------- ----------
P2RAW
----------------
P3TEXT                                                                   P3
---------------------------------------------------------------- ----------
P3RAW             WAIT_TIME SECONDS_IN_WAIT STATE
---------------- ---------- --------------- -------------------
        50      13889
direct path read
file number                                                              27
000000000000001B
first dba                                                            104766
000000000001993E
block cnt                                                                 1
0000000000000001         -1              15 WAITED SHORT TIME

My question is what does this mean and how to get over it.

Thanks

          Followup:

run sql_trace and tkprof to see if the same plan is being used.  A COMMON issue
I see is people compare a query like:

select * from emp where empno = 55;

run in sqlplus to:

p_empno := 55;
for x in ( select * from emp where empno = P_EMPNO ) loop
...

in PLSQL.  they are entirely DIFFERENT queries.

http://asktom.oracle.com/~tkyte/tkprof.html
Hopefully you are not:

   for x in ( select * from t )
   loop
      insert into another_t values ( x.c1, x.c2 ... );
   end loop;

as it would be many times better to simply

  insert into another_t select * from t;

      Rating: 2
Thanks But
      ..  February 22, 2002
Reviewer:  Pawan
       from USA
Thanks for the response. I am going to run Tkprof. What I want to know is ---"
what do the waits as they show up in the V$session_wait query Mean"

          Followup:

if you are asking what the definition of the waits are -- see

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76961/apa.htm#264187
if not, you'll need to be much more clear.

      Rating:
      2
NOlogging  February 22, 2002
Reviewer:
      Pawan  from USA
I am reading the about the locks but found the file that we have waits on was
the REDO LOG. I have heard about NOLOGGING. Can we make the table nologging and
then maybe the process will be faster

          Followup:

Not really.  nologging works on for very very specific BULK operations -- not
for regular insert/update/delete operations.

If you have waits on the log -- there are many ways to tune it.  You can tune to
fix this issue.

      Rating: 3
V$session_wait
      query  December 01, 2003
Reviewer:
      A reader  from NJ, USA
Hi Tom,

I did the following test on my development environment. I connected as the same
user id in 2 different sessions.

Session 1
=========
UPDATE a
   set col1 = 'Holding'
  where col2 = 10

Session 2
=========
SELECT SID FROM V$SESSION WHERE AUDSID = USERENV('sessionid')

SID
===
299

UPDATE a
   set col1 = 'Waiting'
  where col2 = 10

I went back again to session 1 and did the query few times :

select seq#,sid,event,seconds_in_wait,state
 from v$session_wait
where sid = 299 (which is the session id of waiting session
                 that I obtained as shown above)

To my surprise, I keep seeing records in the table with increasing seq# and a
blank sid each time for the same wait event.

Output from First run of the query
==================================
795 null ENQUEUE 3 WAITING

Second run of the query (after a few seconds)
=============================================
800 null ENQUEUE 0 WAITING

1. ENQUEUE is occuring (because of session 1 holding the table). Is that
correct?
2. Why SECONDS_IN_WAIT shows 3 in the first case and shows 0 in the second case
with different seq#. My understanding of v$session_wait is that this will show
only one entry and a STATE of WAITING with a event of ENQUEUE until the lock is
released. Is that understanding not correct? If so, please explain
3. If this is the case, how can we use this view to track down waits and how do
we figure out the latest wait information from this view for each wait event.

          Followup:

1) yes.

2) we wait for an enqueue for 3 seconds, time out, look around and wait again.
It does this forever (the look around is to look for deadlocks and other such
stuff)

use last_call_et from v$session to see the total "time in last call" which will
be roughly equivalent to your total wait.

      Rating: 3
Determining
      wait for each event  December 02, 2003
Reviewer:
      A reader  from NJ, USA
Tom,

1. The column you had mentioned in V$SESSION will give a total wait. Does this
mean it will give a total wait of the session for all events that the session is
currently waiting? This is because the definition of V$SESSION_WAIT is that the
wait event time shows the wait times for sessions that are currently waiting of
the last waited time.
2. If 1 is true, then how to find wait time for individual wait events from
V$SESSION_WAIT.
3. I have read some materials on wait interface showing waits for a given
session at a specific point in time. If this keeps changing, how are they able
to take a snapshot of waits based on V$SESSION_WAIT. Also, pls. clarify that
these documents suggest that you need to take wait event snapshots between 2
time intervals. In such a case should we use V$SESSION_EVENT view or
V$SESSION_WAIT view to get the starting and ending snapshots.
4. Pls provide me info/script on how each wait events for a specific session are
determined and listed and the correct method of using the wait interfaces. ie
when to use v$system_event, v$session_event, v$session_wait etc

          Followup:

1) no, it won't give you a total wait.  last_call_et is the time since the last
call started or finished.  It'll be a good approximation of how long you've been
waiting -- but only approx.

If you update t set x = 10 and update 10,000,000 rows (thats takes a bit of
time) and then hit an enqueue lock -- last_call_et will include the time to
update the 10,000,000 rows.

In this example, last_call_et will be a very good approximation since they get
locked right up.

2) use 10046 trace event and tkprof in 9i -- it makes it very easy (if you never
heard of 10046, search for it on this site)

3) look at v$session_event

4) i would (do) use the 10046 trace event and tkprof :)

December 02, 2003 gaurang -- Thanks for the question regarding
      "connect by ", version 8.1.6

      You Asked
hi tom
can u explain me in detail about "start with  connect by" sql statement (tree
structure).i know there is documentatin but it is very confusing.

      and we said...
It builds a hierarchical query.

There are 2 components to is:

"start with" -- this identifies all LEVEL=1 nodes in the tree

"connect by" -- describes how to walk from the parent nodes above to their
children and their childrens children.

Easiest to use an example on emp.  If we start with "where mgr is NULL", we
generate the set of employees that have no mgr (they are the top of the tree).
If we

CONNECT BY PRIOR EMPNO = /* current */ MGR

that will take all of the PRIOR records (the start with at first) and find all
records such that the MGR column equals their EMPNO (find all the records of
people managed by the people we started with).

Using EMP, the start with SET is:

scott@ORA8I.WORLD> select ename, empno, mgr from emp
  2  where mgr is null;

ENAME           EMPNO        MGR
---------- ---------- ----------
KING             7839

Now, if we do the "connect by manually" we would find:

scott@ORA8I.WORLD> select ename, empno, mgr
  2  from emp where mgr = 7839;

ENAME           EMPNO        MGR
---------- ---------- ----------
JONES            7566       7839
BLAKE            7698       7839
CLARK            7782       7839

scott@ORA8I.WORLD>

KINGS empno is the prior empno.  If we build the entire hierarch -- we have:

scott@ORA8I.WORLD> select lpad(' ',level*2,' ')||ename ename, empno, mgr
  2  from emp
  3  START WITH MGR IS NULL
  4  CONNECT BY PRIOR EMPNO = MGR
  5  /

ENAME                EMPNO        MGR
--------------- ---------- ----------
  KING                7839
    JONES             7566       7839
      SCOTT           7788       7566
        ADAMS         7876       7788
      FORD            7902       7566
        SMITH         7369       7902
    BLAKE             7698       7839
      ALLEN           7499       7698
      WARD            7521       7698
      MARTIN          7654       7698
      TURNER          7844       7698
      JAMES           7900       7698
    CLARK             7782       7839
      MILLER          7934       7782

14 rows selected.

So, KING is the start with set then JONES BLAKE and CLARK fall under him.  Each
of them becomes the PRIOR record in turn and their trees are expanded.
Review &
      Followup

      Rating:
      5
  April 12, 2001
Reviewer:  gaurang

      Rating: 4
useful summary
      of connect by  September 25, 2002
Reviewer:
      RParr  from Seattle, WA U.S.A.
in a fraction of the space you provided a much better overview of connect by and
basic heirarchical query.

      Rating: 5
Nice
      overview  January 22, 2003
Reviewer:  Anirudh
       from New delhi, India
Hi Tom
Your explaination about the connect by clause was very helpfull. However, I have
a question
we have got a function
FUNCTION Get_Parent_Entity_Id
   (
      in_test_pgm_id            number,
      in_test_admin_id            number,
      ic_child_entity_type_code        varchar2,
      in_child_entity_id        number,
      ic_parent_entity_type_code    varchar2
   )
   RETURN number IS
      ln_count            NUMBER;
   BEGIN
      ln_count := 0;
      BEGIN
         select  n_parent_entity_id
         into    ln_count
         from
         (
         select
               n_test_pgm_id,
               n_test_admin_id,
               c_parent_entity_type_code,
               n_parent_entity_id
         from
               rpt_entity_struc res
         start with
                  res.n_test_pgm_id = in_test_pgm_id and
               res.n_test_admin_id = in_test_admin_id and
               res.n_entity_struc_id = 0 and
               res.c_child_entity_type_code = ic_child_entity_type_code and
               res.n_child_entity_id = in_child_entity_id
         connect by
                  res.n_test_pgm_id = prior n_test_pgm_id and
               res.n_test_admin_id = prior n_test_admin_id and
               res.n_entity_struc_id = prior n_entity_struc_id and
               res.c_child_entity_type_code = prior c_parent_entity_type_code
and
               res.n_child_entity_id = prior n_parent_entity_id
         )
         where c_parent_entity_type_code = ic_parent_entity_type_code;
      EXCEPTION
         WHEN NO_DATA_FOUND THEN
            ln_count := 0;
         WHEN OTHERS THEN
            ln_err_num := SQLCODE;
            lc_err_msg := 'Error in Get_Parent_Entity_Id - ' || substr(sqlerrm,
1, 200);
            raise_application_error(-20000, lc_err_msg);
      END;
      RETURN ln_count;
      --
Algo:n_parent_entity_id is retrieved based on the input parameters passed
besides the ic_parent_entity_type_code which is one of the important input
columns to be considered. The Hierarchical query (STARTWITH and CONNECT BY)
clauses are used to find the root parent id.
Question
The connect by prior clause here refers to the columns and one of those
(n_entity_struc_id ) is not in the select list. My question is what is the use
of that PRIORing the column when it has not been selected.?

          Followup:

the same as when you use it in a predicate, no different.

select ename from emp where empno = :x

what is the use of empno in the predicate if it has not been selected?  well, it
is used to identify what record(s) you want.  same with columns in the start
with, connect by prior and so on...

      Rating: 5
Urgent
      Requirement  May 22, 2003
Reviewer:  Shrikant
      Gavas  from India
Please go throught the script :

CREATE TABLE MY_LEVEL1 (
  ORIG_RECP  VARCHAR2 (10),
  ORIG_AMT   NUMBER,
  REF_RECP   VARCHAR2 (10),
  REF_AMT    NUMBER,
  PARTY      VARCHAR2 (6) ) ;

INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'100', 10000, NULL, 500, 'A0001');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'110', 300, '100', 100, 'A0001');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'120', 200, '110', 50, 'A0001');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'130', 100, '120', 30, 'A0001');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'300', 10000, NULL, 500, 'A0001');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'310', 300, '300', 100, 'A0001');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'320', 200, '310', 50, 'A0001');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'330', 100, '320', 30, 'A0001');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'100', 10000, NULL, 500, 'A0003');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'110', 300, '100', 100, 'A0003');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'120', 200, '110', 50, 'A0003');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'130', 100, '120', 30, 'A0003');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'100', 10000, NULL, 500, 'A0004');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'110', 300, '100', 100, 'A0004');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'120', 200, '110', 50, 'A0004');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'130', 100, '120', 30, 'A0004');
INSERT INTO MY_LEVEL1 ( ORIG_RECP, ORIG_AMT, REF_RECP, REF_AMT,
PARTY ) VALUES (
'130', 100, '120', 30, 'A0004');

select  level x,
 party, orig_recp, ref_recp, orig_amt, ref_amt
from my_level1
where party between 'A0001' AND 'A0003'
connect by
prior orig_recp = ref_recp and
prior party = party
start with ref_recp is null

1    A0001    100        10000    500
2    A0001    110    100    300    100
3    A0001    120    110    200    50
4    A0001    130    120    100    30
1    A0001    300        10000    500
2    A0001    310    300    300    100
3    A0001    320    310    200    50
4    A0001    330    320    100    30
1    A0003    100        10000    500
2    A0003    110    100    300    100
3    A0003    120    110    200    50
4    A0003    130    120    100    30

Clients desired output :

1    A0001    100        10000    500
4    A0001    130    120    100    30
1    A0001    300        10000    500
4    A0001    330    320    100    30
1    A0003    100        10000    500
4    A0003    130    120    100    30

i.e. clients requirement is that they want first and last row per level.  We
have tried a lot but not got any appropriate solution for this.

Please provide some solution for above query asap.

      Rating:
      4
Interpret  July 13, 2003
Reviewer:
      Nitin  from Atlanta
How do I interpret this SQL and the output? Is the output correct?

SELECT
TO_CHAR(A.EFFDT,'YYYY-MM-DD'), A.TREE_NODE_NUM, A.TREE_NODE,
A.TREE_NODE_NUM_END, A.TREE_LEVEL_NUM, A.TREE_NODE_TYPE, A.PARENT_NODE_NUM,
A.OLD_TREE_NODE_NUM
From PSTREENODE A
Where A.TREE_NAME = 'OVER_EXP'
And A.SETID = 'LOCKE'
And A.EFFDT = (Select Max(Z.EFFDT)
From PSTREENODE Z
Where  Z.TREE_NAME = A.TREE_NAME
And Z.TREE_NODE = A.TREE_NODE
And Z.SETID     = A.SETID)
Start With TREE_NODE               = 'WORK'
Connect By PARENT_NODE_NUM         = Prior TREE_NODE_NUM

TO_CHAR(A. TREE_NODE_NUM TREE_NODE            TREE_NODE_NUM_END TREE_LEVEL_NUM T
PARENT_NODE_NUM O
---------- ------------- -------------------- ----------------- -------------- -
--------------- -
2002-01-01     222222223 WORK                     333333333              0 G
          1 N
2002-01-01    1222222222 CASE                   1333333332              0 G
1111111111 N
2002-01-01    1166666666 DEPT                    1222222221              0 G
 1111111111 N
2002-01-01    1222222222 CASE                    1333333332              0 G
 1111111111 N
2002-01-01    1166666666 DEPT                    1222222221              0 G
 1111111111 N

          Followup:

the output is correct -- given the query.

However, without any clue as to the "question" - no one can really tell you if
the output correctly answers your question!

      Rating:
      4
Clarification  July 13, 2003
Reviewer:
      Nitin  from Atlanta
The SQL provided in my previous feedback finally made sense. I have the
following question.

The output is not what is desired. We need the condition
Where A.TREE_NAME = 'OVER_EXP'
And A.SETID = 'LOCKE'
And A.EFFDT = (Select Max(Z.EFFDT)
From PSTREENODE Z
Where  Z.TREE_NAME = A.TREE_NAME
And Z.TREE_NODE = A.TREE_NODE
And Z.SETID     = A.SETID)

to be applied prior to executing the CONNECT BY - PRIOR part of the SQL.

Now, we observe that the SQL is first performing the CONNECT BY - PRIOR and then
the WHERE portion. Please let us know how it can be achieved.

Thanks

          Followup:

then that condition should be in the connect by itself as well.  you don't have
to only do prior's in there.

start with /connect by is done

AND THEN

the where clause is applied

If you want to stop building the tree when you hit some condition -- put that
condition in the connect by

      Rating: 5
chained connect
      by  July 26, 2003
Reviewer:  farweeda
       from qatar
ihave docs tab containing two fields (doc_id , doc_rel_id)
with e.g. following values {(5,2)(9,5)(11,2)(3,1)(4,2)(8,3)(6,4)} the doc_rel_id
has some relation with doc_id and vise versa so when i'm asking for the related
docs to specific doc_id e.g. doc_id =2 the resault should be : ( 5,11,4,9
because of 5 ,6 because of 4 ) and if i change doc_id to any of the resault's
value suppose 4  the same resault should be there

          Followup:

ops$tkyte@ORA920LAP> select *
  2    from t
  3   start with doc_rel_id = 2
  4  connect by prior doc_id = doc_rel_id;

    DOC_ID DOC_REL_ID
---------- ----------
         5          2
         9          5
        11          2
         4          2
         6          4

      Rating: 5
To Srikant for
      his urgent query  July 27, 2003
Reviewer:
      A reader
I tried out your scenario - I got a query to get the
same set of records that you wanted but not in the same
order - you can probably tweak it.
Tom may have an even better solution of course!

Menon:)

Here goes:
select x, party, orig_recp, ref_recp, orig_amt, ref_amt
from
(
  select a.*, first_value( x) over (partition by party order by party) first_x,
        last_value( x) over (partition by party order by party) last_x
  from
  (
    select  level x, party, orig_recp, ref_recp, orig_amt, ref_amt
    from my_level1
    where party between 'A0001' AND 'A0003'
    connect by
    prior orig_recp = ref_recp and
    prior party = party
    start with ref_recp is null
  ) a
) b
where x = first_x or
      x = last_x;

      Rating: 4
Skipping
      Gaps?  November 23, 2003
Reviewer:  Doug
       from CT, USA
Tom - is it possible to make a connect by skip gaps in the sequence? Like

SQL> desc a;
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------
 ID                                                 NUMBER
 PRINUMBER                                          NUMBER

SQL> select * from a;

        ID  PRINUMBER
---------- ----------
         1          2
         2          3
         3          4
         9         10

SQL> select id, prinumber from
  2   a start with id=1
  3  connect by prior prinumber=id;

        ID  PRINUMBER
---------- ----------
         1          2
         2          3
         3          4

Is there any way to move on to NINE and continue?

          Followup:

what would be the possible use of this?

how would this be any different from "select * from t"

      Rating: 4
fair
      point  November 23, 2003
Reviewer:  Doug
       from CT, USA
Fair enough - maybe I am asking the wrong question.

What I'm trying to do is take a time history, potentially with workspace manager
that might look like this:

SQL> select name, salary, starttime, stoptime, salary from doug;

NAME           SALARY STARTTIME STOPTIME      SALARY
---------- ---------- --------- --------- ----------
Bob              6000 01-JAN-95 01-JUN-95       6000
Bob              7000 01-JUN-95 01-OCT-95       7000
Bob              7000 01-OCT-95 01-FEB-96       7000
Bob              7000 01-FEB-96 01-JAN-97       7000
Bob              6000 01-JAN-97 01-MAR-98       6000
Bob              5000 01-APR-03 01-JUN-03       5000

The startimes and stoptimes connect very nicely except there is a gap between
March 1998 and April 2003.

If I want to order these dates and include a special salary "gap".   We "don't
know".. what Bob was doing between March,98 and April 2003.

This is what happens at the root of the query I am looking at -

SQL> l
  1  select name, salary, starttime, stoptime,decode( lag(salary) over (order by
 starttime), salary,
  2* to_number(null), row_number() over (order by starttime) ) rn  from doug
SQL> /

NAME           SALARY STARTTIME STOPTIME          RN
---------- ---------- --------- --------- ----------
Bob              6000 01-JAN-95 01-JUN-95          1
Bob              7000 01-JUN-95 01-OCT-95          2
Bob              7000 01-OCT-95 01-FEB-96
Bob              7000 01-FEB-96 01-JAN-97
Bob              6000 01-JAN-97 01-MAR-98          5
Bob              5000 01-APR-03 01-JUN-03          6

6 rows selected.

This is WHAT I WANT more or less - the null values when salarys are the same so
they can be coalesed.  What I REALLY want is THIS:

NAME           SALARY STARTTIME STOPTIME          RN
---------- ---------- --------- --------- ----------
Bob              6000 01-JAN-95 01-JUN-95          1
Bob              7000 01-JUN-95 01-OCT-95          2
Bob              7000 01-OCT-95 01-FEB-96
Bob              7000 01-FEB-96 01-JAN-97
Bob              6000 01-JAN-97 01-MAR-98          5
Bob    <unknown> NULL 01-MAR-98 01-APR-03          6
Bob              5000 01-APR-03 01-JUN-03          7

Now if I just order by time with a "select * from T".. how could I "fill in" the
gaps in the sequence?

Thanks,
D.

          Followup:

it was the connnect by that really confused me -- couldn't understand where that
is coming in.

Now that I know the question, an answer can be forthcoming :)

Here is one technique:

ops$tkyte@ORA920> select name,
  2         decode( r, 1, null, '<unknown>' ) msg,
  3         decode( r, 1, salary, null ) salary,
  4         decode( r, 1, starttime, last_stop ) starttime,
  5         decode( r, 1, stoptime, starttime ) stoptime
  6    from (
  7  select name, salary,
  8         starttime, stoptime,
  9         lag(stoptime) over (partition by name order by starttime) last_stop,
 10         decode( lag(stoptime) over (partition by name order by starttime),
starttime, null, null, null, 1 ) dup_me
 11    from t
 12         ),
 13         (select 1 r from dual union all select 2 r from dual)
 14   where r = 1 or dup_me = 1
 15   order by 4
 16  /

NAME                           MSG           SALARY STARTTIME STOPTIME
------------------------------ --------- ---------- --------- ---------
Bob                                            6000 01-JAN-95 01-JUN-95
Bob                                            7000 01-JUN-95 01-OCT-95
Bob                                            7000 01-OCT-95 01-FEB-96
Bob                                            7000 01-FEB-96 01-JAN-97
Bob                                            6000 01-JAN-97 01-MAR-98
Bob                            <unknown>            01-MAR-98 01-APR-03
Bob                                            5000 01-APR-03 01-JUN-03

7 rows selected.

      Rating:
      5
Stunning  November 23, 2003
Reviewer:
      Doug  from CT, USA
Tom - that is very crafty.  I learn a lot about SQL from you.  Is there a bit of
inefficiency built into that however? The join with the 2 rows in the dual
table, doesn't that increase the work for the db?  Don't get me wrong I'm not
complaining. :-)   Very nice solution.  I wasn't sure it could be done.
Analytic functions seem to have all sorts of interesting uses.

          Followup:

well -- in order to "make up" data we need to join -- to synthesize that row, it
was somewhat unavoidable (joining to a two row table).  pipelined functions and
procedural code might have worked as well, but this is easier.

      Rating: 4
connect by but
      parent in different table  December 02, 2003

Reviewer:  umesh  from blore india
Tom
I have a situation

There are 2 tables srch_criteria, srch_hierarchy

srch_criteria is the master
------------------------------------------------------
id                     name
07                     Aerospace Tier 1
08                     Functional Tier 2
09                     Finance Tier 1
10                     x Tier 1
11                     y Tier 1
12                     z Tier 3

.....

srch_hierarchy has the hierarchy maintained in it
sc_id_child             sc_id_parent
-------------------------------------
08                      07
09                      07

081                     08
082                     08

091                     09
092                     09

search hierarchy will have a hierachy
07 is linked with 08,09
08 has children in 081 , 082
09 has children in 091, 092
( I agree bad design)

I take only Tier 1 from the top and traverse thru whole of children and grand
children in the hierarchy table
I need a query for it Is it possible in SQL or should i write procedure or
function

          Followup:

I don't see "a bad design" here?  looks pretty typical.

I do not know what "tier 1" from the "top" is though.  hows about you show us
the desired output and explain how it was arrived at.

      Rating: 4
using an order
      by   December 02, 2003
Reviewer:  Doug
       from CT, USA
Tom - as you pointed out to me earlier, in some situations gaps or duplicates
with a connect by are redundant because they are the same as an order by clause.

In a situation where I want to ensure the order but not select the column I am
ordering by - will this work?
select T.x, T.y from  (select x,y,z from R order by z ) T
It the order ensured?

          Followup:

just

select x, y
  from R
 order by Z;

      Rating:
      4
  December 09, 2003
Reviewer:  umesh
       from blore
SQL> l
  1  SELECT
  2   DECODE ( ll ,1 , crit1.name ) one ,
  3   DECODE ( ll ,2 , crit1.name) two ,
  4   DECODE ( ll ,3 , crit1.name) three ,
  5   DECODE ( ll ,4 , crit1.name) four
  6   FROM
  7  (SELECT sc_id_child , sc_id_parent   , LEVEL ll  FROM CNT_SEARCH_HIERARCHY
src
  8  START WITH sc_id_parent='crite000000000000002'
  9  CONNECT BY PRIOR sc_id_child=sc_id_parent
 10   ) , CNT_SEARCH_CRITERIA crit ,  CNT_SEARCH_CRITERIA crit1
 11  WHERE crit.id = sc_id_parent
 12* AND crit1.id=sc_id_child
SQL> /

ONE                                      TWO                          THREE
             FOUR
-------------------------- ---------------------------
------------------------   -------------------
Building Automation
                                                                         ACSELON

                                     Authorized Trainer Programs

 Delivery & Installation

ADEPT
                                            Project Management

                                     Human Resources

December 02, 2003 Venkatasubramanian -- Thanks for the question
      regarding "table analysis", version 8.1.7.4

      You Asked
Dear Tom,

1. Towards analysis of tables in production environment, we advocate to put the
dml oriented tables under monitoring clause and use dbms_stats to analyse. Is
this the best method or anything you like to suggest towards quick analysis in
production scenario?

2. We understand the impact of not resetting high water mark impact performance
during full scans. Is there any script which we can periodically run and find
out the tables where hwm need to be reset? In production scenario, we cannot
truncate tables. Hence, to reset hwm, can we move  the table to another work
tablespace and move again to its original tablespace? Is this approach correct?
Any other option is available?

Thanks,
Venkat

      and we said...
1) that is it.  monitor and gather stale.

Also, give serious consideration to:

a) export stats to a named set of stats, named by "day" for example
b) then gather

that way you

a) have a history of what the stats where, sure you can purge individual sets
over time but keep for example "all of the monday observations".  now you can
chart growth and other things

b) a way to get back to "yesterday" in the event the gather whacked some query.
While you are figuring out what went wrong, you can put the stats back to
yesterday at least (always be able to undo what you do in case what you do does
something bad!)

2) it should be obvious based on the application what tables would suffer from
this, if any.  But, a simple query on the BLOCKS*blocksize and
num_rows*avg_row_len in user tables would give an indication of tables that have

lots of blocks under the HWM with little to no data in them.

Review &
      Followup

      Rating: 5
Thanx
      Tom!  August 22, 2003
Reviewer:
      A reader
I am a developer and don't control the production environment. The above
suggestions are for a production
environment, right? For development also of course
we should be gathering stats - but do you think that
it makes sense for us to have a separate box where
we install the latest "stable" dev environment. We
can then have a "history" of stats and probably find
new performance problems introduced easily? So to
identify performance problems early on int he dev env
we do the following (apart from designing for performance
   in the first place)

1 we use debug.f package of yours combined with
  dbms_utility.gettime to log timings for
  all major steps - this is controlled by a debug flag
  just like the flag in asktom. (did you need to use
  dbms_utility.gettime or are the time stamps provided
  by the debug.f good enough?
2 we use dbms_trace to find additional information such
  as line numbers causing problems (this is not strictly
  performance related)
3 we use monitoring and gather stale stats (of course)
4 We also take regular statspack snapshots on this box
  and save them for history
5 we don't need to save stats though cos unlike
  production we don't need to go back in development
  environment usually.
6 when we update this box with the latest code, we can
  then figure out newer problems introduced easily.
7. we need to keep this box "busy" through automated tests
   we may need the "hurlers" and "flingers" that
   you talked about elsewhere on this site.

Did I nmiss anything?

Thanx!

          Followup:

no reason why this approach won't work in dev as well prod -- they should not be
that different (me, i think dev and test should be just LIKE prod!!!)

1) debug.f uses just sysdate (could be updated to a timestamp time in 9i!) they
are usually "good enough" at a second level.  dbms_profiler would be used for
finer access.

2 3 4) ok

5) be nice to have the history is all

6) especially if 5 were saved...

7) they are automated test tools - there is an entire industry built around
them.

      Rating:
      4
hwm  September 03, 2003
Reviewer:
      Venkat  from India
Tom,

As suggested, we collected information on a table towards identification of
blocks under hwm. After moving the table to a different tablespace and again to
its original tablespace, the total no. of blocks used by the table has reduced.
Details are given below: Is this an illustration of resetting hwm? or How to
explain this phenomena?

SQL>select table_name,blocks*8196 asize, avg_row_len*num_rows
 usize from user_tables where table_name='C_ERROR';

TABLE_NAME                          ASIZE      USIZE
------------------------------ ---------- ----------
C_ERROR                          122940      78358

SQL>select blocks from user_tables where
  2  table_name='C_ERROR';

    BLOCKS
----------
        15

SQL>alter table c_error move tablespace idx;

Table altered.

SQL>alter table c_error move tablespace tran;

Table altered.

SQL>analyze table c_error compute statistics;

SQL>select table_name,blocks*8196 asize, avg_row_len*num_rows
  2  usize from user_tables where table_name='C_ERROR'
  3  ;

TABLE_NAME                          ASIZE      USIZE
------------------------------ ---------- ----------
C_ERROR                           98352      78358

SQL>select blocks from user_tables where table_name='C_ERROR';

    BLOCKS
----------
        12

Thanks,
Venkat

          Followup:

umm, yes, that is an example of resetting the hwm.

you could have just

alter table c_error move;

and been done with it.

      Rating: 3
TX Exclusive
      lock when move table  December 02, 2003
Reviewer:
      A reader
When alter table table_name move, TM Exclusive and TX Exclusive lock will be
issued against that table and thus prevent transactions on that table. Are there
other options to reduce hwm for production OLTP tables?

Thanks a lot.

          Followup:

in 9i -- online redefinitions via dbms_redefinition (see admin and supplied
packages guide or search for that package on this site)

in 10g -- online segment shrink (sort of "deletes" rows at the "end" of the
table and "inserts" them at the "front" until the last row at the "end" of the
table cannot be moved up anymore and then redraws the high water mark --
releasing the unused stuff at the end

December 02, 2003 Georg -- Thanks for the question regarding
      "ORA-01417: a table may be outer joined to at most one other table",
      version 8.1.x

      You Asked
Hi Tom,

I have a data mart, with two dimensions: Time and Customer and one fact table:
Sales. The data model is a star schema, the time and customer PKs are available
as FKs in the fact table. I would like to be able to retrieve all rows from the
time table and all rows from the customer table, regardless of having (or not
having) matching rows in the fact table.

When using the outer join (on both dimensions)I get: "ORA-01417: a table may be
outer joined to at most one other table".

SELECT
  TIME_DIM.MONTH,
  CUSTOMER_DIM.CUSTOMER_NAME,
  SUM(SALES_FACTS.HOURS)
FROM
  TIME_DIM,
  CUSTOMER_DIM,
  SALES_FACTS
WHERE
  ( SALES_FACTS.CUSTOMER_FK(+)=CUSTOMER_DIM.CUSTOMER_PK  )
  AND  ( TIME_DIM.TIME_PK=SALES_FACTS.TIME_FK(+)  )
GROUP BY
  TIME_DIM.DAY,
  CUSTOMER_DIM.CUSTOMER_NAME

Any help appreciated !

Best regards,
Georg Breazu

      and we said...
You are able to:

select ...
  from ( select * from time_dim, customer_dim ) A,
       sales_fact
 where a.customer_pk = sales_fact.customer_fk(+)
   and a.time_pk = sales_fact.time_fk(+)
 group by a.day, a.customer_name

which is logically equivalent.  Just create the cartesian product of
time/customer (which you want according to your first query) and outer join to
that.

Review &
      Followup

      Rating: 5
"Double"
      Outer-Join  May 06, 2002
Reviewer:  Georg
      Breazu  from Germany
Thanks !

Best regards,
Georg

      Rating: 3
Whole lot of
      recs  July 30, 2002
Reviewer:  A reader

Woudnt that get a while lot of records ?
and what it we need to do it mroe then ones say one master table and mutilple
detaisl tables..? would we need to have more then one select *  from (select *
etc

      Rating:
      5
query  December 02, 2003
Reviewer:
      Tony  from Canada
Tom,
    Thanks a lot for all your help to the DBA community all over the world, I
have the following query for which i'm getting this

"ORA-01417: a table may be outer joined to at most one other tabl" error. Can
you please help me put here, your quick response(as always) will be really
valueable and appreciated.

select * from
     (select inner.*, rownum rn from
          (select SL.LD_LEG_DETL_ID XYZ0,
                  SL.SEQ_NUM XYZ1,
                  SL.SHPM_ID XYZ2,
                  SL.CUR_OPTLSTAT_ID XYZ3,
                  SL.DISPLAY_STATUS XYZ4,
                  SL.CUR_FNCLSTAT_ID XYZ5,
                  SL.DIV_CD XYZ6,
                  SL.LGST_GRP_CD XYZ7,
                  (case when SL.HELD_YN = 'T' then 1 else 0 end) XYZ8,
                  (case when SL.SUS_YN = 'T' then 1 else 0 end) XYZ9,
                  (case when SL.URGT_YN = 'T' then 1 else 0 end) XYZ10,
                  SL.RLS_FUNC_ENU XYZ11,
                  SL.BS_WGT XYZ12,
                  SL.BS_VOL XYZ13,
                  SL.TOT_DIST XYZ14,
                  SL.CUST_CD XYZ15,
                  SL.CARR_CD XYZ16,
                  (case when SL.CARR_CMTD_YN = 'T' then 1 else 0 end) XYZ17,
                  SL.SRVC_CD XYZ18,
                  (case when SL.SRVC_CMTD_YN = 'T' then 1 else 0 end) XYZ19,
                  SL.EQMT_TYP XYZ20,
                  (case when SL.EQMT_TYP_CMTD_YN = 'T' then 1 else 0 end) XYZ21,

                  SL.LD_LEG_ID XYZ22,
                  SL.LD_LEG_CAT_ENU XYZ23,
                  SL.PKUP_ARVL_DTT XYZ24,
                  SL.PKUP_DPTR_DTT XYZ25,
                  SL.DROP_ARVL_DTT XYZ26,
                  SL.DROP_DPTR_DTT XYZ27,
                  SL.CFMD_DTT XYZ28,
                  SL.RFRC_NUM XYZ29,
                  SL.BOL_NUM XYZ30,
                  SL.MMO_ID XYZ31,
                  SL.TFF_ID XYZ32,
                  SL.BILLTO_CUST_CD XYZ33,
                  SL.PKUP_APT_ID XYZ34,
                  SL.DROP_APT_ID XYZ35,
                  SL.PICK_STOP_ID XYZ36,
                  SL.DROP_STOP_ID XYZ37,
                  SL.TNST_MD_ENU XYZ38,
                  (case when SL.TNST_MD_CMTD_YN = 'T' then 1 else 0 end) XYZ39,
                  SL.MRGE_CSLD_CLS_ID XYZ40,
                  SL.CSLD_CLS XYZ41,
                  (case when SL.SCHD_ACTV_YN = 'T' then 1 else 0 end) XYZ42,
                  (case when SL.RATG_VLID_YN = 'T' then 1 else 0 end) XYZ43,
                  (case when SL.COMP_TRKG_YN = 'T' then 1 else 0 end) XYZ44,
                  SL.RATE_CD XYZ45,
                  SL.UMSRSYS_ENU XYZ46,
                  SL.UMSRWGT_ENU XYZ47,
                  SL.UMSRLEN_ENU XYZ48,
                  SL.UMSRDIST_ENU XYZ49,
                  SL.CNCY_TYP XYZ50,
                  SL.FRHT_TRM_ENU XYZ51,
                  SL.FRM_SHPG_LOC_CD XYZ52,
                  SL.FRM_SHPG_LOC_NAME XYZ53,
                  SL.FRM_PNT_TYP_ENU XYZ54,
                  SL.FRM_CTRY_CD XYZ55,
                  SL.FRM_STA_CD XYZ56,
                  SL.FRM_CTY_NAME XYZ57,
                  SL.FRM_PSTL_CD XYZ58,
                  SL.TO_SHPG_LOC_CD XYZ59,
                  SL.TO_SHPG_LOC_NAME XYZ60,
                  SL.TO_PNT_TYP_ENU XYZ61,
                  SL.TO_CTRY_CD XYZ62,
                  SL.TO_STA_CD XYZ63,
                  SL.TO_CTY_NAME XYZ64,
                  SL.TO_PSTL_CD XYZ65,
                  S.SHPM_NUM XYZ66,
                  S.SHPM_DESC XYZ67,
                  S.CDTY_CD XYZ68,
                  S.TOT_SKID XYZ69,
                  S.TOT_PCE XYZ70,
                  S.BS_WGT XYZ71,
                  S.BS_VOL XYZ72,
                  S.BS_ODR_DLR XYZ73,
                  S.BS_DCLD_DLR XYZ74,
                  S.INPT_UMSR_WGT_ENU XYZ75,
                  S.SCLD_WGT XYZ76,
                  S.NMNL_WGT XYZ77,
                  S.INPT_UMSR_LEN_ENU XYZ78,
                  S.LDN_LEN XYZ79,
                  S.CFMG_USR_CD XYZ80,
                  RN1.RFRC_NUM XYZ81,
                  RN2.RFRC_NUM XYZ82,
                  RN3.RFRC_NUM XYZ83,
                  RN4.RFRC_NUM XYZ84,
                  RN5.RFRC_NUM XYZ85,
                  RN6.RFRC_NUM XYZ86,
                  RN7.RFRC_NUM XYZ87,
                  RN8.RFRC_NUM XYZ88,
                  RN9.RFRC_NUM XYZ89
                     from ld_leg_detl_t SL, shpm_t S, ld_bult_ver_t LBV,
                          rfrc_num_t RN1, rfrc_num_t RN2, rfrc_num_t RN3,
rfrc_num_t RN4,
                          rfrc_num_t RN5, rfrc_num_t RN6, rfrc_num_t RN7,
rfrc_num_t RN8,
                          rfrc_num_t RN9
               where SL.SHPM_ID = S.SHPM_ID AND
               SL.CUR_OPTLSTAT_ID in (410, 425)  and
               LBV.LD_BULT_VER_CD = '*DFT'
and (SL.SHPM_ID = RN1.SHPM_ID(+) and LBV.LD_TYP_ENU = RN1.RFRC_NUM_TYP(+))
and (SL.SHPM_ID = RN2.SHPM_ID(+) and LBV.LD_TYP_ENU = RN2.RFRC_NUM_TYP(+))
and (SL.SHPM_ID = RN3.SHPM_ID(+) and LBV.LD_TYP_ENU = RN3.RFRC_NUM_TYP(+))
and (SL.SHPM_ID = RN4.SHPM_ID(+) and LBV.LD_TYP_ENU = RN4.RFRC_NUM_TYP(+))
and (SL.SHPM_ID = RN5.SHPM_ID(+) and LBV.LD_TYP_ENU = RN5.RFRC_NUM_TYP(+))
and (SL.SHPM_ID = RN6.SHPM_ID(+) and LBV.LD_TYP_ENU = RN6.RFRC_NUM_TYP(+))
and (SL.SHPM_ID = RN7.SHPM_ID(+) and LBV.LD_TYP_ENU = RN7.RFRC_NUM_TYP(+))
and (SL.SHPM_ID = RN8.SHPM_ID(+) and LBV.LD_TYP_ENU = RN8.RFRC_NUM_TYP(+))
and (SL.SHPM_ID = RN9.SHPM_ID(+) and LBV.LD_TYP_ENU = RN9.RFRC_NUM_TYP(+))
order by S.SHPM_NUM desc) inner where rownum < 18) where rn >= 1

Regards
Tony

          Followup:

that doens't compute to me.

why join the same row from SL to the same row in RFRC_NUMT over and over and
over????

rn1..rn9 identify the same rows in the join???????

      Rating:
      5
query  December 02, 2003
Reviewer:
      Tony  from Canada
Thanks for the quick response Tom, well they'll be different, actually my
concern is to make one join condition work e.g

(SL.SHPM_ID = RN1.SHPM_ID(+) and (LBV.LD_TYP_ENU = RN1.RFRC_NUM_TYP(+) )

I just need all ship_id values if LBV.LD_TYP_ENU = RN1.RFRC_NUM_TYP and if
LBV.LD_TYP_ENU <> RN1.RFRC_NUM_TYP then i'm looking for those where SL.SHPM_ID =
RN1.SHPM_ID(+) is true and  LBV.LD_TYP_ENU = RN1.RFRC_NUM_TYP is not true.
let me give you and example:
let's say

SL table constains value 223
RN1 table constains values 223,223 for shpm_id and 5,6 for RFRC_NUM_TYP
Now i'm looking for both the rows in RN1 table if LBV table constains LD_TYP_ENU
 values 5 and 6
but if LBV.LD_TYP_ENU  does not contain  5 and 6 then I just need one row from
RN1 table with value 223 and a null for RN1.RFRC_NUM_TYP

Please help me out!
Thanks
Tony

          Followup:

what do you mean they are different?  you are outer joining to the same ROW in
the same TABLE 9 times????

am i missing something obvious?

      Rating:
      5
Query  December 02, 2003
Reviewer:
      Tony  from Canada
Thanks Tom,
           I know the query is performing the same join nine times that's why i
don't care about the other eight joins but just the first one. Actually this
query is written by a developer here and I know he is doing it wrong. Can you
please help me with the problem of getting the desired rows for just one join as
I mentioned in previous update.

Thanks a lot
Tony

          Followup:

use a scalar subquery, easiest fix:

Note: all of rn1,...rn9 returned the SAME VALUE, so just use one scalar subquery
please!

It is very hard to rewrite a "nonsense query".  I've really no clue what they
were thinking when they wrote that 9 time self join.  Makes me question the
entire query from the get go!

select * from
     (select inner.*, rownum rn from
          (select /*+ FIRST_ROWS */ SL.LD_LEG_DETL_ID XYZ0,
                  /* ..... */
                  (select RN1.RFRC_NUM
                     from rfrc_num_t
                    where SL.SHPM_ID = RN1.SHPM_ID
                      and LBV.LD_TYP_ENU = RN1.RFRC_NUM_TYP) XYZ81
              from ld_leg_detl_t SL,
                   shpm_t S,
                   ld_bult_ver_t LBV
             where SL.SHPM_ID = S.SHPM_ID
               AND SL.CUR_OPTLSTAT_ID in (410, 425)
               and LBV.LD_BULT_VER_CD = '*DFT'
             order by S.SHPM_NUM desc) inner
      where rownum < 18)
  where rn >= 1

December 02, 2003 Vikas -- Thanks for the question regarding "Sql
      Quey", version 8.0.5

      You Asked
Hello Tom!

I need a SQL Query to break a line into three lines with the line feed in
between them. The line's datatype is varchar(90) and has to broken down into 3
lines of length 30 each and stored in the column only as :-

Line1 || Chr(10) || Line2 || Chr(10) || Line3
1-30 characters     31-60 characters    61 till the end of line

Rule to follow :
1.The Words should not break in between. The Word should actually go to the next
line if breakage is happening.

eg. Attn: Vikas Khanna Ste 310 1800 grant Street is the actual line to be broken
down into (44 characters in total)

Attn: Vikas Khanna Ste 310 || Chr(10) || 1800 grant Street
(1-26 characters)                        (28-44 characters)

If it is not possible then what is the way to do it. Please suggest.

Thanks in anticipation.
Vikas Khanna

      and we said...
You'll need to write a word_wrap routine.  Actually, here is the one I use here
on asktom (well, its similar -- mine looks for URLS and escapes HTML entities
for display on the web:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace
  2  function word_wrap( p_string in varchar2, p_len in number default 65 )
return varchar2
  3  as
  4      l_string    long default replace( p_string || chr(10), chr(9), '    '
);
  5      l_result    long default NULL;
  6      l_piece     long;
  7      l_ws        varchar2(25) default ' ' || chr(9);
  8      l_sep       varchar2(5) default NULL;
  9      n           number;
 10  begin
 11      loop
 12          exit when l_string is NULL;
 13
 14          n := instr( l_string, chr(10) );
 15          l_piece := substr( l_string, 1, n-1 );
 16          l_string := substr( l_string, n+1 );
 17
 18          loop
 19              exit when l_piece is NULL;
 20
 21              n := length(l_piece);
 22              if ( n > p_len ) then
 23                  n := instr( substr(
 24                                translate(l_piece,l_ws,rpad('
',length(l_ws))),
 25                                     1, p_len ), ' ', -1);
 26                  if ( nvl(n,0) = 0 ) then
 27                      n := p_len;
 28                  end if;
 29              end if;
 30              l_result := l_result || l_sep || substr( l_piece, 1, n );
 31              l_sep := chr(10);
 32              l_piece := substr( l_piece, n+1 );
 33          end loop;
 34      end loop;
 35      return l_result;
 36  end;
 37  /

Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select word_wrap( 'Attn: Vikas Khanna Ste 310
1800 grant Street', 30 )
  2  from dual;

WORD_WRAP('ATTN:VIKASKHANNASTE3101800GRANTSTREET',30)
---------------------------------------------------------------------------------
--------------------------------------------------
Attn: Vikas Khanna Ste 310
1800 grant Street

Review &
      Followup

      Rating: 5
All wrapped
      up  August 14, 2002
Reviewer:  Mike
       from St. Louis, Missouri
Tom: Thank you -- your word wrap code is very useful in SQL*Plus.  Even with
multiple columns wrapped within the same SQL statement it works VERY well.
Example:

col s1 format a19;
col s2 format a19;
col s3 format a19;

select word_wrap( 'Let me tell ya a story about a man named Jed', 15 ) s1,
word_wrap('It''s not what you are that holds you back its what you think you''re
not', 15 ) s2,
word_wrap( 'Frankly my dear, I don''t give a damn', 15 ) s3
from dual;

S1                  S2                  S3
------------------- ------------------- -------------------
Let me tell ya      It's not what       Frankly my
a story about       you are that        dear, I don't
a man named Jed     holds you back      give a damn
                    its what you
                    think you're
                    not

Question: is SQL*Plus a key element of the solution here?  I mean the query
above only selected a single row (not six as displayed).  SQL*Plus is honoring
the chr(10) your code adds to visually break the text, right?  If I'm
understanding this correctly then how would you suggest I do the same thing
*without* SQL*Plus (e.g. PL/SQL and utl_file for example)?  Thank you for your
thoughts.

          Followup:

You would need to "parse" the lines like this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_str1 varchar2(255) := 'Let me tell ya a story about a man named Jed';
  3      l_str2 varchar2(255) := 'It''s not what you are that holds you back its
what you think you''re not';
  4      l_str3 varchar2(255) := 'Frankly my dear, I don''t give a damn';
  5      n      number;
  6  begin
  7      l_str1 := word_wrap( l_str1, 15 ) || chr(10);
  8      l_str2 := word_wrap( l_str2, 15 ) || chr(10);
  9      l_str3 := word_wrap( l_str3, 15 ) || chr(10);
 10      loop
 11          exit when l_str1||l_str2||l_str3 is null;
 12
 13          n := instr( l_str1, chr(10) );
 14          dbms_output.put( rpad( substr( l_str1, 1, n-1 ) , 20 ) );
 15          l_str1 := substr( l_str1, n+1 );
 16          n := instr( l_str2, chr(10) );
 17          dbms_output.put( rpad( substr( l_str2, 1, n-1 ) , 20 ) );
 18          l_str2 := substr( l_str2, n+1 );
 19          n := instr( l_str3, chr(10) );
 20          dbms_output.put_line( rpad( substr( l_str3, 1, n-1 ) , 20 ) );
 21          l_str3 := substr( l_str3, n+1 );
 22      end loop;
 23  end;
 24  /
Let me tell ya      It's not what       Frankly my
a story about       you are that        dear, I don't
a man named Jed     holds you back      give a damn
its what you
think you're
not

PL/SQL procedure successfully completed.

      Rating: 5
Wrap
      Gap  August 15, 2002
Reviewer:  Mike
       from St. Louis, MO
Tom:

Clever, very clever!  You are so resourceful!

It's interesting to note that the middle column above is "snapping" to the left
margin of the SQL*Plus output. This seems to be dbms_output.put behavior vs.
SQL*Plus.  Example:

SQL> -- OK in SQL*Plus.
SQL> select '    left-padded string' from dual;

'LEFT-PADDEDSTRING'
----------------------
    left-padded string

-- Lpadding removed in PUT?
begin
    dbms_output.put( '    left-padded string' );
    dbms_output.put_line( '' );
end;
/
left-padded string

PL/SQL procedure successfully completed.

SQL>

I will have to try this out with utl_file... Thanks again for your time, ideas
and great working code.

-Mike

          Followup:

depends on how you use serveroutput:

ops$tkyte@ORA817DEV.US.ORACLE.COM> set serveroutput on
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( '    Please do not
snap' );
Please do not snap

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set serveroutput on format wrapped
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_output.put_line( '    Please do not
snap' );
    Please do not snap

PL/SQL procedure successfully completed.

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

you can have it preserve the leading whitespace

      Rating: 5
You made wrap a
      snap  August 16, 2002
Reviewer:  Mike
       from St. Louis, MO
Perfect!  Thanks again.

      Rating: 4
Word
      Wrap  March 20, 2003
Reviewer:  Randy
      Schneider  from Warren, MI
Hi Tom,
  I have a marvel app similar to the way your site takes questions. I then take
the questions and format 3 columns into one clob column.  I use a SQL command to
output into a report area in marvel.  The text is displayed off the page when
using the <pre> tags (which I need to use to retain format information).  I
tried your word wrap function, which is pretty cool, but it truncates the text.
See SQL below...

  1* select word_wrap (issue, 50) from one_liner where file_id = 19
SQL> /

WORD_WRAP(ISSUE,50)
------------------------------------------------------------
GDLS received Honeywell's DECU100 version 5.2 soft
ware on schedule - Friday, March 14 - and have beg
un testing it.

What am I missing here to resolve this problem.  Is this how you format your
questions using the <pre> tags?

Thanks for the response,
Randy

          Followup:

hmm, that is the routine I use here.  it doesn't break words, it looks for
whitespace to break on:

ops$tkyte@ORA920> insert into t values ( 'GDLS received Honeywell''s DECU100
version 5.2 software on schedule - Friday, March 14 - and have begun testing
it.' );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> column x format a60
ops$tkyte@ORA920> select word_wrap( x, 50 ) x from t;

X
------------------------------------------------------------
GDLS received Honeywell's DECU100 version 5.2
software on schedule - Friday, March 14 - and
have begun testing it.

Are you sure you got the code "just right"???

especially this line:

  7      l_ws        varchar2(25) default ' ' || chr(9);
                                          ^^^^ needs to be a space!

      Rating:
      4
  March 26, 2003
Reviewer:  Randy

Tom,
  When I use your word wrap function I get the following error...

  ORA-20001: Report Error: ORA-06502: PL/SQL: numeric or value error: character
string buffer too small

any idea on a work around?

Thanks in advance,
Randy

          Followup:

No you don't.  Not with my function anyway.  That error message is not possible
from my function as I do not throw the 20001.

so, you changed something.

this routine works for strings that word wrap upto 32k in size AFTER applying
the word wrap.  So, assume you can send a string of max 30,000 characters as
input.

      Rating:
      4
  March 26, 2003
Reviewer:  Randy

Hi Tom,
  Disregard that last review.  I figured it out.

Randy

      Rating:
      5
Query  March 26, 2003
Reviewer:
      Tony  from Canada
Tom,
     Thanks a lot for all your help.
can you please tell me how can I achive something like:

table1
========
shipment1
shipment2
shipment3
shipment4

table2
========
shipment1, qualifier1, reference1
shipment1, qualifier2, reference2
shipment2, qualifier2, reference2
shipment3, qualifier3, reference3

result:
shipment1 ,qualifier1,reference1
shipment1 ,qualifier2,reference2
shipment1 ,qualifier3
shipment2 ,qualifier1
shipment2 ,qualifier2,reference2
shipment2 ,qualifier3
shipment3 ,qualifier1
shipment3 ,qualifier2
shipment3 ,qualifier3,reference3
shipment4 ,qualifier1
shipment4 ,qualifier2
shipment4 ,qualifier3

Regards

          Followup:

er -- no.

only 4 of the output rows make sense to me -- the long ones.

The others seem  to be made up somehow.

      Rating: 4
What about
      this?  December 02, 2003
Reviewer:  Adrian
       from Lima, Peru
Hello Tom, we suppose the table emp that it has the column mgr, I need a SQL
query that allows to show the data in the following format

Scott
   smith
       paul
       marcus
   william
       john
           steve
.
.
.
where first he is head of the following ones and like this of the sort
thanks beforehand

          Followup:

select rpad( ' ', 2*level, ' ' ) || ename
  from emp
 start with mgr is null
connect by prior empno = mgr;


Subject | Article | Last Updated (East coast time)
Importing a (Oracle8i version 8.1.6) exported .dmp file to Oracle8i version 8.1.5 | article only | Dec 03, 2003 07:03 am
load a pl/sql table with lookup table and use it in my program for look up values | article only | Dec 03, 2003 07:07 am
external table, ebcdic, position | article only | Dec 03, 2003 07:32 am
Collection | article only | Dec 03, 2003 10:36 am
miscelaneous questions | article only | Dec 03, 2003 10:37 am
Installation of Developer 6i | article only | Dec 03, 2003 10:42 am
File Formats Supported by interMedia | article only | Dec 03, 2003 11:04 am
Replacing Multiple Blanks by single blanks in text | article only | Dec 03, 2003 11:07 am
tkprof report | article only | Dec 03, 2003 04:45 pm
Raw devices and backup | article only | Dec 03, 2003 04:46 pm
Oracle standby database hardware/software requirements | article only | Dec 03, 2003 04:49 pm
Blocks read using index vs. full table scan | article only | Dec 03, 2003 04:59 pm
Improving performance | article only | Dec 03, 2003 05:12 pm
Function-based Index on Y/N Completed Flag | article only | Dec 03, 2003 06:45 pm
using sql*loader | article only | Dec 03, 2003 06:52 pm
histogram | article only | Dec 03, 2003 07:00 pm
Normalization of the database | article only | Dec 04, 2003 07:43 am
how to multiplex redo log files. | article only | Dec 04, 2003 07:44 am
Deleting duplicate records | article only | Dec 04, 2003 07:46 am
Date Validation with PL/SQL | article only | Dec 04, 2003 07:48 am
Dispatcher Error | article only | Dec 04, 2003 07:50 am
Question About PORT# in Tnsnames.ora file (Oracle8i) | article only | Dec 04, 2003 07:51 am
Poor Response while working in MTS mode. | article only | Dec 04, 2003 08:42 am
uninitialized collections | article only | Dec 04, 2003 08:51 am
stopping listener having a password | article only | Dec 04, 2003 08:51 am
CURRENT MODE AND consistent mode | article only | Dec 04, 2003 09:47 am
Conntinuing question "Remote Listener Registration" | article only | Dec 04, 2003 09:49 am
A Custom Error Handling System | article only | Dec 04, 2003 09:50 am
Adding Time part in Date Field | article only | Dec 04, 2003 11:08 am
HOW CAN I COMPARE NULL VALUES | article only | Dec 04, 2003 12:27 pm
how to select and insert a long type column | article only | Dec 04, 2003 12:27 pm
Dynamic cursor, Static Cursor, Bind variable etc. | article only | Dec 04, 2003 12:30 pm
Data Warehousing database & a regular Database | article only | Dec 04, 2003 12:35 pm
How can one insert a carriage return | article only | Dec 04, 2003 02:58 pm
How to do document management using Oracle | article only | Dec 04, 2003 06:09 pm
SQL Loader | article only | Dec 05, 2003 07:26 am
Trimming Blanks | article only | Dec 05, 2003 07:27 am
questions on 9i new features | article only | Dec 05, 2003 07:31 am
how to set values as new column headers | article only | Dec 05, 2003 10:48 am
Steps in Tuning | article only | Dec 05, 2003 11:22 am
Question on Using Ref Cursor | article only | Dec 05, 2003 12:29 pm
UTL_HTTP package usage | article only | Dec 05, 2003 04:59 pm
what is TWO_TASK | article only | Dec 06, 2003 08:54 am
Sending e-mail! -- Oracle 8i specific response | article only | Dec 06, 2003 08:56 am
reading data from .dbf file into oracle 8i -- HOW TO LOAD DBASE data | article only | Dec 06, 2003 10:52 am
pl/sql type and sql types and collections | article only | Dec 06, 2003 01:30 pm
Difference between DB_NAME and Instance_Name | article only | Dec 06, 2003 02:14 pm
Some clarifications... | article only | Dec 07, 2003 07:11 pm
Append Hint | article only | Dec 08, 2003 06:38 am
alter database -- clear logfile | article only | Dec 08, 2003 06:42 am
querying a clob | article only | Dec 08, 2003 10:11 am
in and out variables - whats the difference | article only | Dec 09, 2003 05:24 am
Script for to Backup Oracle Database | article only | Dec 09, 2003 05:30 am
Using the DBMS_STATS-package | article only | Dec 09, 2003 05:34 am
ROLLUP OR CUBE | article only | Dec 09, 2003 06:01 am
Force use of index even when index value in where clause is modified by a function | article only | Dec 09, 2003 06:05 am
DBMS_STATS cascade option | article only | Dec 09, 2003 06:07 am
View tuning | article only | Dec 09, 2003 06:18 am
Have to give table name with in "" while selecting from a table. | article only | Dec 09, 2003 06:27 am
No Exection Generated in 10046 Event Trace | article only | Dec 09, 2003 06:30 am
Can't access remote object via public link and How to hide password for sqlplus login | article only | Dec 09, 2003 06:33 am
Multiple DBWn | article only | Dec 09, 2003 08:16 am
checkpoint not complete, cannot allocate new log | article only | Dec 09, 2003 08:17 am
performance problem with partitioning table | article only | Dec 09, 2003 08:19 am
TNS_ADMIN | article only | Dec 09, 2003 08:20 am
redo log generation | article only | Dec 09, 2003 01:55 pm
Returning a refcursor and use it in IN clause. | article only | Dec 09, 2003 01:58 pm
does refrential integrity make the database slow | article only | Dec 09, 2003 02:04 pm
High Availability questions | article only | Dec 09, 2003 02:18 pm
row(s) 1 - 69 of 69
December 03, 2003 Rohit -- Thanks for the question regarding "Importing a (Oracle8i version 8.1.6) exported .dmp file to Oracle8i version 8.1.5", version version 8.1.5
You Asked
Hi, Tom

            Is there a way to import a dump file created by a newer version of
oracle using export into a lower version of oracle.Please let me know if there's
away to do this.When i try to import a the dump file created by exp of Oracle 8i
ver 8.1.6  to  Oracle ver 8.i.5. I get error No. IMP-00000 & IMP-00010 with the
message about invalid message header information.

Waiting for your reply,
Thanks in advance.

Rohit Sinha
and we said...
No, as is typical with most all software in this regards -- a file created by a
HIGHER version cannot be used by a LOWER version.  A file created by a LOWER
version can be used by a HIGHER version.

So, exp from 8.1.5 imp to 8.1.6 is OK
    exp from 8.1.6 imp to 8.1.5 is not OK.

The solution is to use the 8.1.5 export tool against the 8.1.6 database via
Net8.  This will create a file that 8.1.5 can in fact use.  This works in all
cases -- even if the target database is a 7.x database.  You can run catexp7 in
the 8i instance and then use the 7.x export tool over sqlnet to perform the
export.
Review & Followup
Rating: 4
Similar problem with 9.01 => 8.1.7 October 09, 2002
Reviewer: Brad Peek from Dallas
I tried to use the transportable tablespace feature to transport a tablespace
from a v9.0.1 DB to a v8.1.7 DB and ran into the same issue (IMP-00010).

1) On v9.0.1 export the metadata (using v9's exp)
2) On v8.1.7 import the metadata (using v8's imp)
   -- Import failed with IMP-00010
3) On v8.1.7 export via net8 (connect sys/pw@v9db as sysdba)
   -- Export failed due to export view inconsistencies
4) On v9.0.1 run catexp.sql (copied from v8.1.7)
5) On v8.1.7 repeat step 3
   -- Export died with a core dump (not IMP-00010)

Unfortunately, even when I ran v8's catexp.sql in the v9 DB I was still not able
to run the v8 export against the v9 DB.
The fact that this was a metadata export may account for it not working.  I
didn't try any other exp/imp combinations.

Followup:
files are upwards compatible -- not backwards.

the file format of 901 has things in it that didn't exist in 8i, i would not
expect to be able to go "backwards", only "forwards"

Rating: 4
Importing Higher to Lower October 10, 2002
Reviewer: Reaz form Dhaka from Dhaka Bangladesh
As regards to TTS you also have to follow the DB Version and OS version.

Rating: 3
October 10, 2002
Reviewer: Sriram from Thailand
Quote: You can run catexp7 in  the 8i instance and then use the 7.x export tool
over sqlnet to perform the
export.

We Know this script creates a bunch of views which the
version 7 export utility needs to see before it can function properly.

Is it required to run any other script after completing the export to bring back
the 8i Instance to allow the use of 8.x export tool.

Followup:
nope, catexp7 creates a bunch of exu7* views, it doesn't overwrite the 8.x
views.

Rating: 5
Lower to Higher December 03, 2003
Reviewer: Praveen from Bangalore
Hi Tom,
    Is it possible to export data created using EXP ver8.1.7 from 8.1.7 database
into 9.2 database using EXP 9.2.

I have tons of data in the server machine (oracle 8.1.7). My plan is to create
the dump file, write it onto a couple of CD's, take home, and export them into
9.2 ver database in my personal computer. (ie No NET 8 connection is available).

Thanks Tom
regars

Praveen

Followup:
you can use the 8i export tool to create a dmp file

that

the 9i import tool can read.

export/import is backwards compatible like that.  new imports can read old
exports.

it is not upwards compatible however, old imports cannot read new exports (so 9i
exports cannot be read by 8i, you would install the 8i client and use sqlnet to
create the dmp file from 9i in the 8i format)

December 03, 2003 -- Thanks for the question regarding "load a pl/sql table with lookup table and use it in my program for look up values", version 8.1.7
You Asked
Tom

Recently I was told that instead of repeatedly querying a lookup table for
lookup values(like querying a description table for description using
description_id), it is much more efficient to load up a pl/sql table and do a
lookup on the pl/sql table.

1.Can you show me how we can load up a pl/sql table

2.How to do a look up using pl/sql table

3.If I want to load a pl/sql table with values from more than columns, can we do
it. Arent pl/sql tables single dimensional?

Thanks

Tom
and we said...
Why do you believe all that you hear?  Did they give you a test case to "prove"
this? Hmmmmm.

Did you know that databases were born to be queried?  It is one of the things
they do best.

I would ask this person who gave you this advice for their test case that proves
it.  In there you would find out

1) how to load the plsql table (eg: look up BULK COLLECT in the plsql manuals)
2) how to look up using plsql table (eg: they either wrote their own binary
search -- which we happen to have in the database already, or they are using a
simple, yet non-scalable linear search, start at index 1 and look till you find
a match)
3) in 8i, yes they are -- you can bulk collect into a RECORD of ARRAYS however.

But, the scientific proof offered by this person will answer all of your
questions.
Review & Followup
Rating: 5
I think this was published in Oracle Professional September 16, 2002
Reviewer: Scott Watson from Canada
I beleive I read this in Oracle Professional written by Steve Feuerstein.  Here
is a link to the article.  Basically it is for 9iR2 and uses associative arrays.
http://www.oracleprofessionalnewsletter.com/op/OPmag.nsf/0/F2E754B65828DEF985256C1A0056DF3F
I can rationlize this article by saying that the PL/SQL engine does not have to
perform a context switch to the SQL layer, however, using this method would
require that each connection maintain a lookup table which may require more
memory if the number of users using this approach is large.  Like any good
solution it requires testing in your enviroment etc. etc.

HTH
Scott.

Followup:
In 9iR2 (not 817 and not before) this may be true with the new associative
arrays which are b-tree indexed RAM data structures (very efficient for
searching).  This is "brand new" at this point.  A quick and dirty test will
show that they are in fact faster and probably more scalable but at the cost of
RAM (tradeoffs as usual).

This will NOT work in 817 (note to the original questioner, this will not work
for you)

ops$tkyte@ORA920.US.ORACLE.COM> create table t ( code varchar2(5) constraint
t_pk primary key, data varchar2(40) )
  2  organization index
  3  /

Table created.

ops$tkyte@ORA920.US.ORACLE.COM> exec gen_data( 'T', 5000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> select count(*) from t;

  COUNT(*)
----------
      5000

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> declare
  2      type  lookupTable is table of varchar2(40) index by varchar2(5);
  3      type  indexByTable is table of varchar2(40) index by binary_integer;
  4
  5      l_lookupTable lookupTable;
  6      l_codes       indexByTable;
  7      l_values      indexByTable;
  8      l_value       varchar2(40);
  9
 10      l_start number;
 11      l_run1  number;
 12      l_run2  number;
 13  begin
 14      select code, data BULK COLLECT into l_codes, l_values from t;
 15
 16      insert into run_stats select 'before', stats.* from stats;
 17
 18      l_start := dbms_utility.get_time;
 19      for i in 1 .. l_codes.count
 20      loop
 21          select data into l_value from t where code = l_codes(i);
 22      end loop;
 23      l_run1 := (dbms_utility.get_time-l_start);
 24      dbms_output.put_line( l_run1 || ' hsecs' );
 25
 26      insert into run_stats select 'after 1', stats.* from stats;
 27      l_start := dbms_utility.get_time;
 28
 29
 30      for i in 1 .. l_codes.count
 31      loop
 32          l_lookupTable( l_codes(i) ) := l_values(i);
 33      end loop;
 34      for i in 1 .. l_codes.count
 35      loop
 36          l_value := l_lookupTable( l_codes(i) );
 37      end loop;
 38      l_run2 := (dbms_utility.get_time-l_start);
 39      dbms_output.put_line( l_run2 || ' hsecs' );
 40      dbms_output.put_line
 41      ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) || '% of the time' );
 42
 43      insert into run_stats select 'after 2', stats.* from stats;
 44  end;
 45  /
74 hsecs
3 hsecs
run 1 ran in 2466.67% of the time

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> select a.name, b.value-a.value run1,
c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

NAME                                 RUN1       RUN2       DIFF
------------------------------ ---------- ---------- ----------
STAT...recursive calls               5031         50      -4981
STAT...calls to get snapshot s       5010         16      -4994
cn: kcmgss

STAT...execute count                 5005          7      -4998
STAT...index fetch by key            5000          1      -4999
LATCH.shared pool                    5127        123      -5004
LATCH.cache buffers chains          10167        291      -9876
STAT...session logical reads        10056        103      -9953
STAT...consistent gets              10012         24      -9988
STAT...consistent gets - exami      10000          2      -9998
nation

LATCH.library cache pin             10105        101     -10004
LATCH.library cache                 10258        248     -10010
STAT...session pga memory               0     476412     476412

62 rows selected.

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

All in all, if you got the RAM and you got 9iR2, this makes sense.  Before then
-- I question it.

Rating: 3
Tried and Tested September 16, 2002
Reviewer: Adrian Billington from UK
Tom

Initialising package global index-bys with data from lookup tables, using the
numeric-PK as the index (can be varchar2 now with associative arrays of course)
is a tried and tested technique of returning lookup values much quicker than
repeated database access. I took an hour off a one hour fifteen minutes INSERT
using this method and I have a couple of demos "proving" them to be quicker
(both methods have also been through your run_stats harness as well to great
effect).

I am off work at the moment so can't get to the demos, but if no-one beats me to
it, then I'll post something next week.

The SF article in Oracle Professional this month takes it from an associative
array context, but I already have a demo of that as well and again, they are
much more efficient than repeated lookup table access.

BUT, the only thing I have not tested against is repeated table access with a
BUFFER POOL KEEP / KEEP CACHE, so maybe that's for someone else to try and
report on.  But hey, SQL can't ALWAYS win, surely ;).

Regards

Adrian

Followup:
I betcha using a single INSERT -- removing the procedural code all together --
would get rid of the rest of the time.

I rarely see codes that are 1, 2, 3, 4, 5, .... N -- making the index by tables
of some what limited use.  It is usually something like

AK Alaska
VA Virginia
...

Rating: 5
What about the other way around September 17, 2002
Reviewer: Scott Watson from Montreal, Canada
Hi Tom,

I was thinking performing lookups in PL/SQL is faster than doing the same lookup
in sql but what about the other way around.  (I don't have 9i so I cannot test
this myself)  What would be faster in this case.

Select a.col1, b.col2
  from a, b
 where a.id = b.a_id;

or

select a.col1, lookup_in_pl_sql(a.id)
  from a

Thanks,
Scott.

Followup:
Select a.col1, b.col2
  from a, b
 where a.id = b.a_id;

Try it and see.

Rating: 5
That is not what I meant September 17, 2002
Reviewer: Scott watson from Montreal, Canada
Funny!!!

I know I can do the table lookup but what about the lookup using a PLSQL
function in terms of the perfomance we have beed discussing.  We have already
asserted that these types of lookups are faster when executing within PLSQL than
SQL but what about when we access PLSQL from our sql statement.

select name_en, convert_to_french(name_en) PLSQL_FUNCTION
  from tableA

This is what I was refering to when I said I did not have 9i and could not test
it.

Followup:
well, you could have tested with an INDEXED BY BINARY_INTEGER table (eg: number
to string lookup)....

but here you go -- sql will be FASTER here:

ops$tkyte@ORA920.LOCALHOST> /*
DOC>drop table t;
DOC>
DOC>create table t ( code varchar2(5) constraint t_pk primary key, data
varchar2(40) )
DOC>organization index
DOC>/
DOC>
DOC>exec gen_data( 'T', 5000 );
DOC>
DOC>select count(*) from t;
DOC>
DOC>pause
DOC>*/
ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> create or replace package demo_pkg
  2  as
  3           function get_value( p_code in varchar2 ) return varchar2;
  4  end;
  5  /

Package created.

ops$tkyte@ORA920.LOCALHOST> create or replace package body demo_pkg
  2  as
  3      type  indexByTable is table of varchar2(40) index by binary_integer;
  4      type  lookupTable is table of varchar2(40) index by varchar2(5);
  5
  6      l_lookupTable lookupTable;
  7
  8          function get_value( p_code in varchar2 ) return varchar2
  9          as
 10          l_codes       indexByTable;
 11          l_values      indexByTable;
 12          begin
 13                  if nvl( l_lookupTable.count,0 ) = 0
 14                  then
 15                          dbms_output.put_line( 'filling table' );
 16                  select code, data BULK COLLECT into l_codes, l_values from
t;
 17                  for i in 1 .. l_codes.count
 18                  loop
 19                          l_lookupTable( l_codes(i) ) := l_values(i);
 20                  end loop;
 21                  end if;
 22                  return l_lookupTable( p_code );
 23          exception
 24                  when no_data_found then
 25                          raise program_error;
 26          end;
 27  end;
 28  /

Package body created.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> declare
  2       l_start number;
  3       l_run1  number;
  4       l_run2  number;
  5  begin
  6       insert into run_stats select 'before', stats.* from stats;
  7
  8       l_start := dbms_utility.get_time;
  9       for x in ( select a.code, b.data from t a, t b where a.code = b.code )
 10       loop
 11           null;
 12       end loop;
 13       l_run1 := (dbms_utility.get_time-l_start);
 14       dbms_output.put_line( l_run1 || ' hsecs' );
 15
 16       insert into run_stats select 'after 1', stats.* from stats;
 17       l_start := dbms_utility.get_time;
 18
 19
 20       for x in ( select a.code, demo_pkg.get_value( a.code ) from t a )
 21       loop
 22           null;
 23       end loop;
 24       l_run2 := (dbms_utility.get_time-l_start);
 25       dbms_output.put_line( l_run2 || ' hsecs' );
 26       dbms_output.put_line
 27       ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) || '% of the time');
 28
 29       insert into run_stats select 'after 2', stats.* from stats;
 30  end;
 31  /
23 hsecs
filling table
65 hsecs
run 1 ran in 35.38% of the time

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> select a.name, b.value-a.value run1, c.value-b.value
run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

NAME                                 RUN1       RUN2       DIFF
------------------------------ ---------- ---------- ----------
....
LATCH.cache buffers chains          25135      10515     -14620
LATCH.library cache pin                95      30215      30120
LATCH.shared pool                     112      35210      35098
LATCH.library cache                   236      50445      50209
STAT...session uga memory               0     589176     589176
STAT...session pga memory         -131072     589824     720896

70 rows selected.

ops$tkyte@ORA920.LOCALHOST>

Also, I'll REVISE my earlier answer.

Instead of doing it PROCEDURALLY, lets do the lookup where it belongs!  In SQL
in the first place.  EG: I would not be caught reading a table and then doing
another query to LOOKUP data in the first place.  Only if I was doing some data
load would I do that -- else, I would have JOINED in the first place...

What I mean by that -- is that the basic PREMISE that you should

  for x in ( select * from t1 )
  loop
      select value into l_var from t2 where t2.code = t1.code;
      .....

is flawed in the FIRST place, you should have JOINED (or selected a select)

Now that will outperform using a index by table (associated array or not)...

Rating: 3
Strange behaviour... September 18, 2002
Reviewer: Padders from UK (where else?)
Since when did collection.COUNT return NULL for empty collection? I think you'll
find you never even populate your collection.

This raises another issue (and I am testing on 9.2, btw) - if the collection is
never populated, RETURN l_lookuptable (p_code) should raise NO_DATA_FOUND, and
yet we get no error from test script, although if we call function from PL/SQL,
we get NO_DATA_FOUND raised as expected, e.g.

SQL*Plus: Release 9.2.0.1.0 - Production on Wed Sep 18 21:12:43 2002

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

SQL> SELECT demo_pkg.get_value ('Rubbish')
  2  FROM   dual;

DEMO_PKG.GET_VALUE('RUBBISH')
---------------------------------------------------------------------

SQL> BEGIN
  2    DBMS_OUTPUT.PUT_LINE (demo_pkg.get_value ('Rubbish'));
  3  END;
  4  /
BEGIN
*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at "SCOTT.DEMO_PKG", line 28
ORA-06512: at line 2

Bizarrely, it has nothing to do with collection, since we can create function
which just raises NO_DATA_FOUND and get the same behaviour, e.g.

SQL> CREATE OR REPLACE FUNCTION ndf
  2    RETURN VARCHAR2
  3  IS
  4  BEGIN
  5    RAISE NO_DATA_FOUND;
  6  END;
  7  /

Function created.

SQL> SELECT ndf
  2  FROM   dual;

NDF
----------------------------------------------------------------------

SQL> BEGIN
  2    DBMS_OUTPUT.PUT_LINE (ndf);
  3  END;
  4  /
BEGIN
*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at "SCOTT.NDF", line 5
ORA-06512: at line 2

Note that the same does not appear to apply to other exceptions, and no I
haven't tried them all, but VALUE_ERROR, for example, appears to work as
expected, e.g.

SQL> CREATE OR REPLACE FUNCTION ve
  2    RETURN VARCHAR2
  3  IS
  4  BEGIN
  5    RAISE VALUE_ERROR;
  6  END;
  7  /

Function created.

SQL> SELECT ve
  2  FROM   dual;
SELECT ve
       *
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SCOTT.VE", line 5

SQL> BEGIN
  2    DBMS_OUTPUT.PUT_LINE (ndf);
  3  END;
  4  /
BEGIN
*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at "SCOTT.NDF", line 5
ORA-06512: at line 2

Please confirm - have I lost the plot or is this expected behaviour (not the me
losing the plot bit, the other bit)?

Followup:
whoops -- corrected that.  surprisingly, the results were not materially
affected.  see above

Rating: 3
Still wondering... September 19, 2002
Reviewer: Padders from UK
Thanks for correcting.

But why is NO_DATA_FOUND exception ignored by SQL, and NULL returned instead?

Followup:
I guess cause the answer is "unknown"

just like if I

select ( select * from dual where 1=0 ) from dual;

that does not fail, even though the subquery (function) returns NO_DATA_FOUND.
or

select * from dual where 1 = ( select 0 from dual where 1=0 )

the subquery returns NULL - well, actually the subquery doesn't return anything,
it is "unknown" what it returns.  the function is treated in the same way.

Rating: 4
Some results... September 19, 2002
Reviewer: Adrian Billington from UK
Tom

I agree with your comment about SQL being faster when the lookup table is
available (i.e. just join to it), but I don't think that was ever disputed. We
wouldn't write a PL/SQL function to do such simple SQL work in the case of a
straight insert. But when you don't have access to that lookup table (i.e.
during a data load), the use of an index-by (or extended assoc. array in 9i)
beats the hell out of a database-accessing function.

On a simple 10,000 row insert demo, looking up the description from a lookup
table for each FK row in the staging table, the results were as follows
(consistently - ran about 4 times):-

SQL with join to lookup table........ 0 secs 16 hsecs
PL/SQL function using index-by....... 0 secs 67 hsecs
PL/SQL function using lookup table...10 secs 66 hsecs

Which I guess validates the assertions.

I would have thought that from an OLTP perspective, the use of index-bys for the
common lookup tables should be encouraged, to save all those repeated accesses
to the numerous lookup tables that OLTP systems seem to have, which is possibly
relevant the original poster...

Regards

Adrian

Followup:
Yes, that is exactly what i said tho:

...
Instead of doing it PROCEDURALLY, lets do the lookup where it belongs!  In SQL
in the first place.  EG: I would not be caught reading a table and then doing
another query to LOOKUP data in the first place.  Only if I was doing some
data
load would I do that -- else, I would have JOINED in the first place...
.....

What I see CONSTANTLY, continously and all of the time is code like

  for x in ( select * from t )
  loop
      select ... into ... from t2 where t2.code = t.code;
      ......

that should be a join, if you see yourself doing that, stop.

During a data load -- you are 100% correct.

(consider an OLTP application a loader and the premise continues ;)

Rating: 5
illustrate September 19, 2002
Reviewer: Nag
"
What I see CONSTANTLY, continously and all of the time is code like

  for x in ( select * from t )
  loop
      select ... into ... from t2 where t2.code = t.code;
      ......
"

Tom, can you give us an example and show us , how a loop like the above can be
converted into sql

Followup:
It is called a join.  join t to t2.

Rating: 5
Have a look at this , and tell us wha tyou have to say September 26, 2002
Reviewer: Famy
this is from quest-pipelines , a very well respected resource..

July's Tip of the Month

Loading a Lookup Table in Memory to Speed Up Queries

Compliments of Dan Clamage, PL/SQL Pipeline SYSOP (danielj@clamage.com)

Recently, I worked with a developer who faced the challenge of speeding up his
Pro*C application. It was taking about 2 hours to run. After examining his
application, I discovered that he had one table on which every row returned in
the driving loop required a random lookup. Furthermore, he could expect nearly
every row in this lookup table to be accessed at some point over the life of the
program. I instructed him to preload this moderately sized table (~50k rows)
into a persistent PL/SQL table. In order to be persistent, the PL/SQL table had
to be declared in the package body, outside of any routine:

CREATE OR REPLACE
PACKAGE BODY lookup
IS
TYPE typ_val_data IS TABLE OF tbl_lookup.val_data%TYPE
  INDEX BY BINARY_INTEGER;
tab_val_data typ_val_data;
I explained to him that the numeric primary key could be used as the index
offset, and the column value he needed could then be stored at that offset. For
example:
Lookup Table

OID NUMBER(10)  VAL_DATA VARCHAR2(4)
3               'GHGL'
27              'ACBF'
101             'LEFP'
157             'KPOI'
PL/SQL Table (also known as index-by table or array)
Array Offset    Array Value
3               'GHGL'
**no data in array elements 4-26**
27              'ACBF'
**no data in array elements 28-100**
101             'LEFP'
**no data in array elements 158+**
157             'KPOI'
This would result in a sparsely populated array. He populated the array up
front, as part of his program initialization, using a packaged procedure. This
procedure simply read every row in his lookup table in a cursor FOR loop, and
used the primary key as the element offset into the PL/SQL table. For example:
PROCEDURE populate_lookup
IS
  CURSOR get_all_data IS
  SELECT oid, val_data
  FROM tbl_lookup;
BEGIN
  FOR rec IN get_all_data LOOP
    tab_val_data(rec.oid) := rec.val_data;
  END LOOP;
END populate_lookup;
Then he provided a packaged function to interface with this array. He could use
this function in PL/SQL or in SQL. For example:
FUNCTION get_val_data(p_oid IN tbl_lookup.oid%TYPE)
RETURN tbl_lookup.val_data%TYPE
IS
BEGIN
  RETURN(tab_val_data(p_oid));
EXCEPTION
  WHEN NO_DATA_FOUND THEN
  RETURN(NULL);
END get_val_data;
Inside his main loop, he coded something like this:
INSERT INTO tbl_target (val_data, ...)
VALUES (lookup.get_val_data(fk_oid), ...);
He had to fully qualify the function, since it's being called from SQL. He
reported that his program now runs in 20 minutes, a 5x speed improvement.

Followup:
And he could have speed it up 10x more by simply coding:

insert into tbl_target ( val_data, .... )
select max(t.val_data), :bv1, :bv2, :bv3, :b4, ....
  from tbl_lookup t
 where oid = :bv0;

or 80x faster by using array binds and the above!!!!

Here is the output from my testing (totally consistent, ran it many many times):

ops$tkyte@ORA920.US.ORACLE.COM> !./test

Connected to ORACLE as user: /

Using SQL 2002-09-26 16:38:11.041532
+000000000 00:00:02.851382000
Using plsql 2002-09-26 16:38:13.902126
+000000000 00:00:04.002790000
Using SQL 2002-09-26 16:38:17.915989
+000000000 00:00:00.534158000

ops$tkyte@ORA920.US.ORACLE.COM> select count(*) from t2;

  COUNT(*)
----------
      9000

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

so, SQL -- no PLSQL = 2.8 seconds.
PLSQL = 4 seconds

SQL with array binds of 100 = 0.58 seconds......

Here is my code:

drop table t;

create table t  ( oid primary key, val_data )
organization index
as
select rownum oid, object_name val_data from all_objects;

drop table t2;

create table t2 ( val_data varchar2(30), data varchar2(400) );

create or replace package pkg
as
    function f( n in number ) return varchar2;
end;
/

create or replace package body pkg
as

type lookup is table of varchar2(30) index by binary_integer;

g_data lookup;

function f( n in number ) return varchar2
is
begin
    return g_data(n);
exception
    when no_data_found then return null;
end;

begin
    for x in ( select * from t )
    loop
        g_data(x.oid) := x.val_data;
    end loop;
end;
/

#include <stdio.h>
#include <string.h>

static char *     USERID = "/";

#define SQLCA_INIT
EXEC SQL INCLUDE sqlca;

static void sqlerror_hard( int lineno)
{
    EXEC SQL WHENEVER SQLERROR CONTINUE;

    printf("\nORACLE error detected (%d):", lineno);
    printf("\n% .70s \n", sqlca.sqlerrm.sqlerrmc);

    EXEC SQL ROLLBACK WORK RELEASE;
    exit(1);
}

static void process1( void )
{
EXEC SQL BEGIN DECLARE SECTION;
varchar        data[400];
varchar        time[125];
int            i;
EXEC SQL END DECLARE SECTION;

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard(__LINE__);

    memset( data.arr,'*',400 );
    strncpy( data.arr, "process1", strlen("process1") );
    data.len = 400;

    EXEC SQL SELECT to_char(systimestamp,'YYYY-mm-dd hh24:mi:ss.ff')
               INTO :time
               FROM DUAL;
    printf( "Using SQL %.*s\n", time.len, time.arr );

    for( i = 0; i < 3000; i++ )
    {
        EXEC SQL insert into t2
        select max(val_data), :data
          from t
         where oid = :i;
    }
    EXEC SQL SELECT SYSTIMESTAMP-to_timestamp(:time,'yyyy-mm-dd hh24:mi:ss.ff')
INTO :time FROM DUAL;
    printf( "%.*s\n", time.len, time.arr );
    EXEC SQL COMMIT;
}

static void process2( void )
{
EXEC SQL BEGIN DECLARE SECTION;
varchar        data[400];
varchar        time[125];
int            i;
EXEC SQL END DECLARE SECTION;

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard(__LINE__);

    memset( data.arr,'*',400 );
    strncpy( data.arr, "process2", strlen("process2") );
    data.len = 400;

    EXEC SQL SELECT to_char(systimestamp,'YYYY-mm-dd hh24:mi:ss.ff')
               INTO :time
               FROM DUAL;
    printf( "Using plsql %.*s\n", time.len, time.arr );

    for( i = 0; i < 3000; i++ )
    {
        EXEC SQL insert into t2
        values ( pkg.f(:i), :data );
    }
    EXEC SQL SELECT SYSTIMESTAMP-to_timestamp(:time,'yyyy-mm-dd hh24:mi:ss.ff')
INTO :time FROM DUAL;
    printf( "%.*s\n", time.len, time.arr );
    EXEC SQL COMMIT;
}

static void process3( void )
{
EXEC SQL BEGIN DECLARE SECTION;
varchar        data[100][400];
varchar        time[125];
int            oids[100];
int            i;
EXEC SQL END DECLARE SECTION;

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard(__LINE__);

    for( i = 0; i < 100; i++ )
    {
        memset( data[i].arr,'*',400 );
        strncpy( data[i].arr, "process3", strlen("process3") );
        data[i].len = 400;
    }

    EXEC SQL SELECT to_char(systimestamp,'YYYY-mm-dd hh24:mi:ss.ff')
               INTO :time
               FROM DUAL;
    printf( "Using SQL with Array binds %.*s\n", time.len, time.arr );

    for( i = 0; i < 3000; i++ )
    {
        oids[ i%100 ] = i;

        if ( i%100 == 99 )
        {
            EXEC SQL insert into t2
            select max(val_data), :data
              from t
             where oid = :oids;
        }
    }
    EXEC SQL SELECT SYSTIMESTAMP-to_timestamp(:time,'yyyy-mm-dd hh24:mi:ss.ff')
INTO :time FROM DUAL;
    printf( "%.*s\n", time.len, time.arr );
    EXEC SQL COMMIT;
}

main( argc, argv )
int    argc;
char *    argv[];
{
EXEC SQL BEGIN DECLARE SECTION;
VARCHAR   oracleid[50];
EXEC SQL END DECLARE SECTION;

    strcpy( oracleid.arr, USERID );
    oracleid.len = strlen(oracleid.arr);

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard(__LINE__);
    EXEC SQL CONNECT :oracleid;
    printf("\nConnected to ORACLE as user: %s\n\n", oracleid.arr);

    process1();
    process2();
    process3();

    exit(0);
}

Rating: 4
Then why use pl/sql tables? October 04, 2002
Reviewer: Kashif from Houston, TX USA
Hi Tom,

Great discussion, thanks for the invaluable input. Just a quick couple of
questions. Firstly, you mention:

"
What I see CONSTANTLY, continously and all of the time is code like

  for x in ( select * from t )
  loop
      select ... into ... from t2 where t2.code = t.code;
      ......
"

and you mention this is not the correct way of doing lookups. Does this apply
when there is the need to outer join the driving table in the main query, i.e.

select t.*
from t, t2
where t.code (+) = t2.code

Is it not more efficient to read the main table and then read the lookup table,
and return nulls (using exception when no_data_found then return null) when the
lookup table doesn't retrieve anything?

Secondly, your response to the original poster seemed to indicate that pl/sql
tables aren't always the best solution when it comes to lookup tables. Can you
give examples of when they are the best solution? Thanks in advance.

Kashif

Followup:
I would outer join or select a select (eg:

   select dept.*, (select count(*) from emp where emp.deptno = dept.deptno )
     from dept

never procedurally loop running a select based on other selects if I can.

A plsql table to do some lookups might make sense when doing a flat file data
load and you don't have external tables (eg: pre 9i)  in 9i and beyond, maybe
NEVER.

Rating: 4
Whoops... October 04, 2002
Reviewer: Kashif from Houston, TX USA
I meant the outer join should be on the lookup table (t2), i.e. the query would
be something like

select t.*
from t, t2
where t.code = t2.code (+)

Wouldn't it be more efficient to simply read the rows needed from the driving
table (t) and then do inner selects on the lookup (t2) table as needed? THanks.

Kashif

Followup:
if you can do it in a single query -- do it, it's almost always faster, easier,
more maintainable, faster to type, etc etc etc

Rating: 4
Decode on select (select)? October 11, 2002
Reviewer: Kashif from Houston, TX USA
Hi Tom,

Thanks for your tip, it works wonders in my query! Quick question, if I can
control when I execute the 'select a select' somehow, for example using a
decode, will that be more efficient versus executing the 'select a select' for
each row? Example:

select deptno, decode (deptno, null, null, (select deptname from dept where
dept.deptno = emp.deptno)
from emp;

versus

select deptno, (select deptname from dept where dept.deptno = emp.deptno)
from emp

assuming the deptno in emp is not always populated. Thanks for your feedback in
advance.

Kashif

P.S. Hope you're having fun at the VOUG conference!

Followup:
Well, lets see....

DOC>create table emp ( deptno int );
DOC>
DOC>create table dept as select * from scott.dept;
DOC>alter table dept add constraint dept_pk primary key(deptno);
DOC>
DOC>insert into emp
DOC>select decode( mod(rownum,5), 0, null, mod(rownum,5)*10 )
DOC>from all_objects;
DOC>*/
ops$tkyte@ORA920.US.ORACLE.COM>

ops$tkyte@ORA920.US.ORACLE.COM> set autotrace traceonly
ops$tkyte@ORA920.US.ORACLE.COM> select deptno, (select dname from dept where
dept.deptno = emp.deptno)
  2    from emp
  3  /

29284 rows selected.

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

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       2012  consistent gets
          0  physical reads
          0  redo size
     570620  bytes sent via SQL*Net to client
      21971  bytes received via SQL*Net from client
       1954  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      29284  rows processed

ops$tkyte@ORA920.US.ORACLE.COM> select deptno, decode( deptno, null, null,
(select dname from dept where dept.deptno = emp.deptno) )
  2    from emp
  3  /

29284 rows selected.

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

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       2012  consistent gets
          0  physical reads
          0  redo size
     570645  bytes sent via SQL*Net to client
      21971  bytes received via SQL*Net from client
       1954  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      29284  rows processed

ops$tkyte@ORA920.US.ORACLE.COM> select deptno from emp
  2  /

29284 rows selected.

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

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       2006  consistent gets
          0  physical reads
          0  redo size
     347959  bytes sent via SQL*Net to client
      21971  bytes received via SQL*Net from client
       1954  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      29284  rows processed

ops$tkyte@ORA920.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA920.US.ORACLE.COM>

I threw in that last query to show you that there is "magic" happening with
select of selects.  See the number of consistent gets on that last query?  Very
very close to the others

There are optimizations happening here that go beyond what we can see.

Rating: 5
exec gen_data( 'T', 5000 ); October 12, 2002
Reviewer: A reader
Hi Tom,
I've seen this procedure executed in the response above,
can you tell what the procedure is doing, Of course it is populating the table
with test data, but how?

thanks in advance.

Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1552006405645
code for gen_data is there.

Rating: 3
Comments on this solution? October 25, 2002
Reviewer: andrew from ca, usa
Tom
The URL below advocates pre-loading a pl/sql table for speed. The solution looks
efficient to me and the performance claim sounds convincing. What do you think
of it?
http://www.quest-pipelines.com/Pipelines/PLSQL/tips01.htm#july

          Followup:

its the same as the other discussions here.

if you CANNOT DO IT in a single sql statement and must loop procedurally (which
many people do when they SHOULD NOT), this can help.

If you can do it in a join in the first place, rather then lookup lookup lookup
all of the time -- do it that way.

      Rating: 4
Whoops -
      nevermind...  October 25, 2002
Reviewer:
      Andrew  from ca, usa
Sorry Tom - I just saw your comments on the link above.

      Rating: 4
Lookups in
      SGA?  July 31, 2003
Reviewer:  Clark
       from Leeds, England, UK.
Tom, my first time, so please be gentle! And please excuse (and explain) any
misunderstandings I may have of Oracle.

To summarise the whole thread, I think, and a common point you make in all
discussions, do it in SQL unless it HAS to be PLSQL - so do a(n sql) JOIN to a
lookup table and not an x-ref to a (plsql) memory structure pre-loaded with a
lookup. (Tho, exception from 9iR2 noted, but I'm on 817.)

However, I often have the need to perform a lookup within plsql but without a
join to another table, e.g to validate input. Right, now I think I've got this.
This is an OLTP system, so it should be considered as a data loader and
therefore keeping these lookups in plsql memory is an ok way of doing it. Yes?

So -- is there not some way of sticking them in some part of the SGA and
avoiding duplicating UGA for each session? Something along the lines of a
SERIALLY_REUSABLE package that doesn't lose state between calls would do the
trick -- it would only ever need a SINGLE copy of the package, globals wouldn't
need re-initialising on every call, etc.

Or am I worrying too much about memory/processing usage? Maybe there's so little
overhead in doing a genuine sql lookup, from plsql, and letting Oracle store the
lookups in the KEEP pool (thus managing SGA usage for me too) that I should just
do it that way -- but comments about sql-plsql context switching lead me to
believe that this SHOULD be avoided.

I could/can do straight performance tests for speed, but I am not experienced
enough to understand and find out the wider implications regarding SGA usage
etc. Speed is not always the primary concern in an OLTP system -- an extra 2
millisecs per user query, say, would not be of concern if you can shave 20% from
the memory you need.

Many thanks for your time.
Cheers,
Clark.

          Followup:

give me a concrete example whereby plsql would have to perform a validation
PRIOR to inserting the data to work with.  Me, I would let SQL do the validation
itself.

If I needed to lookup some data,

insert into t
select plsql_variable_a, plsql_variable_b, plsql_variable_c, column_x_from_table
  from lookup_table
 where lookup_table.key = plsql_variable_d;

that would do my "lookup" for me straight away.  SQL%rowcount would tell me if
the lookup "failed"

If this was a data validation -- well, then I would use declaritive RI and let
the database tell me when the data was garbage.

      Rating: 1
lookup
      x-ref  July 31, 2003
Reviewer:  Clark
       from Leeds,UK
Sorry, put it wrongly, I do use RI constraints, it wasn't for validation it was
for x-ref.

The example I have just implemented uses a table of response codes (ids) with
associated meanings and error-levels, eg.

create table response_codes as
 ( id       NUMBER
 , descr    VARCHAR2(100)
 , errlevel NUMBER
);

Crucially, MULTIPLE "detail" response codes are possible, which get stored in a
child table to an "overall" response -- the overall being dictated by the
worst-case detail, the highest response_codes.errlevel.

So, during validation, I log a detail response when a problem is found and keep
a global g_worst_errlevel var which is a reflection of the worst
response_codes.errlevel that has been found.

I could, of course, do an sql-query each time I have a detail response, but I
have implemented the worst-lookup by pre-storing the lookup errlevels in a plsql
table indexed by the id-col from response_codes. Here's the key proc:

  -- - - - - - - - - - - - - - - - - - - - - - - -
  PROCEDURE pr_detail_response
  -- - - - - - - - - - - - - - - - - - - - - - - -
    -- Acts on a 'detail' response
    ( pi_detail_response IN  s_o_TYPE )
  IS
  BEGIN
    g_detresps_TAB( pi_detail_response ) := NULL ;
    g_worst_err :=
      GREATEST
       ( g_worst_err
       , g_det_errlvls_TAB( pi_detail_response ) );
  END ;

g_detresps_TAB stores all detail responses that I've had**.

g_det_errlvls_TAB is the pre-stored lookup to the response_codes.errlevel col.

**In case you're wondering, it's not sensible to INSERT these at this point
because the overall response-ID is the parent -- but I don't know my overall
response until I've found out all my detail responses.

Thanks,
Clark

      Rating:
      5
  July 31, 2003
Reviewer:  Suri
       from PA

      Rating: 5
Loading Data
      into PL/SQL table  July 31, 2003
Reviewer:
      Ketan. Bengali  from Sunnyvale, CA, USA
Hi Tom,

My data is like this:
Level 1.1:
  Level 2.1 (Parent = Level1.1, Previous Sibling=NULL)
     Level 3.1.1(Parent = Level2.1, Prev Sibling=NULL)
     Level 3.1.2(Parent = Level2.1, Prev Sibling=Level 3.1.1)
  Level 2.2(Parent = Level1.1, Previous Sibling=Level2.1)
     Level 3.2.1(Parent = Level2.2, Prev Sibling=NULL)
     Level 3.2.2(Parent = Level2.2, Prev Sibling=Level 3.2.1)

Basically, it is hierarchial and ordered data. To display this data to the
users, have created all the records in pl/sql table and provided view on top of
it.

Package Test_Pack is
    -- rec tree_hierarchy_array;
    function get_tree return tree_hierarchy_array;
    --
end test_pack;
--
View is :
    SELECT *
    FROM   TABLE(CAST(test_pack.get_tree() AS tree_hierarchy_array))

Today, I recreate pl/sql table whenever user selects from the view because
variable, which stores pl/sql table, is local variable declared inside the
get_tree function. I could store this variable in a session by declaring it in a
package body.

If I store it to session level then the PL/SQL table has to be recreated only
when the data is changed in the database. This will improve the performance. How
do I find out whether database is changed since last run?

Creating a package and displaying it using a view, is it a right approach?

Thanks,

Ketan.

          Followup:

to discover that the data has changed is "hard" to do in a scalable fashion,
unless you add auditing to the base table.

If you added a trigger that

create table last_update( x int primary key ) organization index;
create sequence s;

create trigger t_trigger after insert or update or delete of t
begin
   insert into last_update values ( s.nextval );
end;

your routine can now keep a package variable that was the max(x) from t and
every time it is called, it simply selects max(x) from t again to see if it
changed -- if so, it must requery.

if modifications are really really "light" on this table, you could update a
single row instead of inserting a new row each time.  that will cause all
modifications of the table to serialize by session though.

is creating a package and displaying as view the right approach?  depends -- if
it CANNOT be done in pure sql, maybe.  if it could be done in pure sql, probably
not.

      Rating: 3
The
      Questions.  August 01, 2003
Reviewer:  Clark
       from Leeds, UK
Hi Tom,

wrt my comments yesterday - (a) sorry for the low review grade, that was mainly
because I asked the wrong q.. and (b) the questions I still have are:

1.
Is there anything inherently wrong/ bad practice with using a plsql index-by
table to store values from the db this way - would you personally do it with a
query each and every time you needed to x-ref the errlevel? If so, what are your
reasons?

2.
Is there any way of storing common data (available to plsql) in the SGA to avoid
duplicating 'application-static' data across all sessions? Are you aware of any
plans to introduce such a concept if not?

Cheers,
Clark.

P.S. Keep up the good work!

          Followup:

1) if I had procedural code that I could not turn into a query (a single query,
not a single query executed 500 times, a single query executed once) I would be
looking at using lookup tables.  Yes, I do that.

2) there have been talks about it -- it is a hard problem to solve actually.
introduces a new address space, concurrency issues (session 1 updates a variable
at the same time session 2 does) and so on.

right now, you would have to do it "per session"

      Rating:
      5
Perfect  August 01, 2003
Reviewer:
      Clark  from Leeds
Thanks Tom, at least I know I'm not missing something.

Point 2 tho - my concept was simply as somewhere to store absolutely-static
data, which you know absolutely will not change; or alternatively, if it does
change, every session needs the current values. (Why? To provide for different
installations of the same application with, say, different application
parameters.)

Thanks again.
Clark.

          Followup:

and we call that a database table ;)

it'll be in the cache if

a) it is small
b) it is used.

      Rating: 5
insert select
      for load (best practices)  September 12, 2003

Reviewer:  Joe  from PA
Hi tom,
best practices question related to this thread

during a load into a normalized schema, i have to determine if the dimension row
of the values I am loading already exists in the dim table.  if it does, than
use the key assigned to that set of values in my fact table.

did I understand all the posts correctly that I should

insert into t
select pk_from_dimension_table,plsql_var_a,plsql_var_b
FROM dimension_table
where dim_col_a = 'plsql_variable_d;
and dim_col_b='plsql_variable_e'

and if I get no rows inserted, assume that combo didn't exist and go ahead and
insert it into the dim table?

This single line approach potentially followed by an insert would be better than
a lookup query, followed by one or two inserts. (fact row and dim row)

In this case, after loading starts, I would assume that almost all combinations
will be exist after a fairly short time.

          Followup:

how would you populate the dim table?  if the dim table is just "number" to
"value" and nothing else -- it is not very useful?

give me a real case to work with.

      Rating: 4
good, but one
      'problem'  September 22, 2003
Reviewer:  joachim
      mayer  from austria/europe
Hi tom,

i spend some time with this article before i started to use lookup-tables myself
– which turned out to be very helpful and improved the performance of my
applications a lot. But there is still one “problem” – or better unpleasant
thing – in it.

I will try to describe it as good as I can. Sorry, I am not an native English
speaker!

Most of my oracle-applications are called by other programs which stay connected
to the database and therefore keep their sessions. When the first time call one
of my package it reads (inialize) the lookup-table(s) for the package and it
keeps going as long as it takes. Sometimes for hours or longer… very rare, but
it happens I would like to chance one value in the lookup-table and want this
change to sow effect at once. But I can only change the value in the table where
the process did get its original data but of course it wont get reread.
So, my question is – is there a way to inform this package to this session it
should now reread its table and renew the lookup-table. (function-call,
signal?)? At the moment, we must restart the process which is connected to the
DB and uses my packages – which is sometimes cumbersome.

Thx

hannibal

          Followup:

you would have to poll the database using something like dbms_alert, or AQ or
just a simple single row table that contains the date/time of last update to see
if you needed to "refresh"

      Rating: 5
Associative
      arrays  December 01, 2003
Reviewer:  Zoran
      Martic  from Dublin, Ireland
Tom,

This is very nice discussion.

I want to add one more case in this story to have complete picture.
I want to cover this situation:
You have the application (forms, c, c++, ...) and you have all lookup tables in
Oracle database.
It is commonly known that doing lookups one by one from the client side is going
to be slow (or to say not fast enough) in some cases because of SQL*Network
roundtrip (TCP, IPC or even BEQ). On the newest hardware you can do to say up to
few thousands lookups from C or C++. If you do this with somekind of
in-process-memory structure (as associative arrays, hash table, ...) you can get
10 times more lookups. In this case the biggest overhead in doing Oracle lookups
are roundtrips (including context switches) then executing lookup SELECT.
Of course this is the case only if you cannot load the data into Oracle with
bulk inserts and after that do the magic in PL/SQL. You need to do validation
and other things on the client side and in this case perform lookups one by one.
In this scenario you need somekind of client cache.

It looks to me that Oracle filled the gap with Oracle Forms if Oracle Forms can
use associative arrays on the client side PL/SQL engine.
Associative arrays are much faster because it is in-process-memory, no need for
locking, .....

For example if you use associative array on the Forms client side (if possible)
you can get much better performances then doing lookups on a table.

This is from my perspective the biggest gap that I cannot solve with Oracle yet.
What with C, C++, Java, .... Oracle 9iAS.
Are there any way that Oracle is going to address this issue on the client side
(as in PL/SQL with associative arrays)?
What you can tell about OCI object cache ? Is it to do anything with this story?

Thanks for any response.

Regards,
Zoran

          Followup:

question -- why are you doing lookups on the client.

      Rating: 5
Why to do
      lookups on the client?  December 02, 2003

Reviewer:  Zoran Martic  from Dublin, Ireland

In some special cases we need (or want) to do lookups on the client because of
the needed speed.
The main idea is that it is faster because you do not have networking overhead
(as SQL in this case), everything is in your process memory (as for associative
arrays).

If you need to respond to some "real-time" (if something like that exists) event
faster then you want to spend on the everhead on communicating between the
client and the database (OCI) you need to consider somekind of client cache.
All other cases I covered by doing bulk insert and later on join with other
tables and process that.

You saw how much faster is associative array then SQL lookup. I tested simple 2
column IOT lookup with 1M rows versus associative array lookup based on the same
table. The difference was 25 times.
Of course this is limited to just one value lookup and it is unique lookup.
Also I tested some so called in-process or in-memory databases (Berkley
Sleepycat) and they are about 5 times faster for simple 1-column lookup then
PL/SQL, but much more faster if it is OCI call from the client even on the same
server (SQL*Network time).

This is the only case when I cannot find the solution for the problem in Oracle.

From the results I have associative arrays are faster then B*Tree
implementations in those fastest in-memory databases (they are simple, small,
not reliable and robust like Oracle), even 5 times, but I cannot use PL/SQL in C
or C++.
I want to use Oracle 100% because I am Oracle DBA/Dev, but I am facing problems
to be fast enough in these special cases.

OCI object cache is looking like to simekind of caching mechanism, but hard to
apply to normal tables and it is probably not like associative arrays (B*Tree)
in PL/SQL.
Also 9iAS needs to be fast with dealing with data. They do not want for any
validation to go to the server.
I found network roundtrips plus type conversion between C or Java and Oracle
types very expencive.

I am asking you because I know that you are curious about this as I am.
I was thinking a few months ago that there is nothing faster then standard SQL
lookup from IOT (or hash table or normal table). But as you saw with associative
arrays this is not true (also by similar implementations in some small
in-process databases like Sleepycat and TimesTen).
One-task process is not supported by Oracle anymore, that means no way to be
fast as PL/SQL if you are using OCI.

What are you thinking about all of this?
Any new things in 10g with associative arrays or something similar on the OCI
client side?

Regards,
Zoran

          Followup:

I would encourage you to post your simple test -- I'll betcha we can change the
algorithm to go the OTHER WAY (iot creams associative array ;)

it is all about thinking in sets, if you go slow by slow (opps -- meant row by
row), the assoc array might win but if you do set based processing (single sql),
the DB will cream you.

I blame the "procedural mindset", not the database here (i prototype this in my
latest book effective oracle by design if you are interested).

I don't do lookups on the client side.  to me -- the database IS the client side
(mod_plsql).

For OCI, you'd be looking at a C subroutine (binary tree, avl tree, something
like that).

Java's got hash tables...

for the 10g database -- its all about "the server", wouldn't really do "the
client"

      Rating: 5
Client
      lookups  December 02, 2003
Reviewer:  Zoran
      Martic  from Dublin, Ireland
Tom,

Thanks for your answer.
I have read all your books.

I agree that one SQL join per 50,000 rows in set is better then doing 50,000
lookups.
I am using that idea for a long time (as you described it in your books):
1. simple SQL
2. PL/SQL
3. everything else

I tested stupid associative lookups here because I wanted to know the difference
between these fastest algorithms (or databases, TimesTen, Sleepycat, ...) and
Oracle pure B*Tree implementation.
I was happy to see that even doing lookups on associative arrays was 5 times
faster in my case then using these "fast" databases (algorithms).

You know that the most app clients are written in something diffeernt then
PL/SQL and because of that, in my case C, you need to perform somekind of
lookups (database, C hash table or whatever).

For you the client is PL/SQL (and me if not looking the company I am working for
:), for many of us is something else.

Just that example about these lookups and associative arrays (to confirm that
maybe my test is not wrong):

SQL> desc x2
 Name                                      Null?    Type
 ----------------------------------------- -------- -------- X
                      NOT NULL VARCHAR2(100)
 Y                                                  VARCHAR2(100)

Populated with 1M rows with this script:

declare
   x varchar2(100);
   y varchar2(100);
   i number;
begin
   for j in 1..1000000 loop
    insert into x2 values(to_char(j)||'abcdffgghhghg','abcdffgghhghg');
    if mod(j,10000) = 0 then
        commit;
    end if;
   end loop;
   commit;
end;
/

*************** Test ************************************
declare
      type  lookupTable is table of varchar2(100) index by varchar2(100);
      type  indexByTable is table of varchar2(100) index by binary_integer;

      l_lookupTable lookupTable;
      l_codes       indexByTable;
      l_values      indexByTable;
      l_value       varchar2(100);

      l_start number;
      l_run1  number;
      l_run2  number;
  begin

      dbms_output.put_line(dbms_utility.get_time);

      select x, y BULK COLLECT into l_codes, l_values from x2;

      dbms_output.put_line(dbms_utility.get_time);

      for i in 1 .. l_codes.count
      loop
          l_lookupTable( l_codes(i) ) := l_values(i);
      end loop;

      dbms_output.put_line(l_codes.count);
      dbms_output.put_line(dbms_utility.get_time);

      for i in 1 .. 1000000
      loop
          l_value := l_lookupTable( '501abcdffgghhghg' );
      end loop;

      dbms_output.put_line(dbms_utility.get_time);

      for i in 1 .. 1000000
      loop
           select y into l_value from x2
                   where x = '501abcdffgghhghg';
      end loop;

      dbms_output.put_line(dbms_utility.get_time);

  end;
/

Result:
411879663
411886578
1000000
411898013
411898682
411912955

You can see the difference between last 3 numbers.
I fixed the string I am searching for because I did not want to have possibility
using the disk (the table is about 100M I think and also PGA was 180M for this
test).
I did it for 100000 also. It was also much faster.
I did not try more columns or anything else.
I know that could be a way different.

Let me know if my test is wrong.
If not please give us some example where the table lookup is faster then
associative array (no big JOIN technique please).

I agree again in PL/SQL is probably stupid to do lookups when you have joins,
but sometimes you need to process event by event and not the bunch of events
together (Imagine processing connect request or something similar in real world,
you cannot batch that).

Thanks,
Zoran

          Followup:

i would have just joined, once again, that original bulk collect -- 0% chance I
would have looked up on the client, 1000% chance I would have looked up on
server (even if I had to create a global temporary table and shoot the data over
first!)

In PLSQL, just use sql.

In C, -- well -- call PLSQL :)

(or look to that LANGUAGE to provide you a lookup tool -- C has that nifty
bsearch routine)

      Rating:
      5
Join  December 03, 2003
Reviewer:  Zoran
      Martic  from Dublin, Ireland
Tom,

If you read carefully, there are not N records. There is not set of rows.
You are dealing with one event you need to respond as fast as possible.
Imagine the situation where you need to respond to some network event
immediately, or even to some database event, you cannot use joins, you need to
respond immediately. You cannot batch it.
That is the point, no way to have a batch.
You have "real-time" event you need to respond to.
In this case this server call (from C) from my perspective is very expensive.
In the same way you can look from PL/SQL you do not want sometimes to go and
look the table, you will use local variables or associative arrays. That is the
point.

My example was to show that looking into associative array is much faster then
looking into IOT for direct hit lookups.

You owe me an example where IOT is faster then associative array as you said
previously (just for PK matches).

I am pretty confident that even if you have the multicolumn primary key (doing
concatenation in associative array to make the index), the lookup will be
faster.

That means if you need to do something faster then your tables can do, you will
opt for associative arrays.

At the end why should Oracle put associative arrays in the product if you can do
everything with join :)
Are you saying to us that associative arrays are not needed?
I am sure that you want to use it in some special cases where you need to do
things record by record (or event by event).
Oracle put it to fullfil PL/SQL speed.
I asked you in the first place is Oracle going to make something on the client
side similar to the OCI object cache but more advanced.
I want to rely on Oracle, do not want to use other products.
I saw (tested) that Oracle B*tree search in associative arrays are faster then
coresponding "fastest" in-process in-memory databases.

Thanks for all your time.

Zoran

          Followup:

then, as I keep saying, you need to look to your LANGUAGE to help you out.  be
it c, java, whatever.  the LANGUAGE will supply this feature

then you have to figure out how to make it so that when the database changes the
client knows to refresh its cache.

you would not be using a plsql stored procedure in your case -- there would be
no need to have it do a lookup as plsql stored procedures are running in the
database and have no UI.

you would not be using forms for a real time interface either (i wouldn't
imagine you would)

we put lots of stuff in the database cause people ask for it.  its not always
the best.  i've demonstrated the best, most performant way.

I've yet to use an associative array in code, for real.

      Rating: 5
Associative
      arrays  December 03, 2003
Reviewer:  Zoran
      Martic  from Dublin, Ireland
Tom,

Thanks a lot for your time.
That is what I am doing at the moment.

I am impressed with the speed of associative arrays.
I mean 1M lookups in 6 seconds on 1M table (simple two columns but..) is very
good. The server is just 450MHz Sparc.
That is proving that Oracle has crazy good B*Tree algorithm, just all code on
top of it (optimizer, SQL, locking, ....) are having initial overhead per SQL.
At the end this is why joins are fast, the overhead is too small in comparison
to memory operations performed to read database blocks from the buffer cache
(about 5k - 10k LIO's per 100MHz CPU).

Regards,
Zoran

      Rating: 5
This was
      incredible!  December 10, 2003
Reviewer:  Roland
      G. Bender  from Ashburn, VA USA

December 03, 2003 -- Thanks for the question regarding "external
      table, ebcdic, position", version 9.2

      You Asked
Hello Tom,

Please can you provide an example for external table in which I need to use
ebcdic file and fields with position layout like in sql loader

field1  position(01:24),
field2  position(26:35)
...
...

Thanks,

Regards,

Phil

      and we said...
if you have existing control files, just use

external_table=generate_only

on the sqlldr command line and it'll convert it -- but here is the output of an
example I had (search for ebcdic sqlldr on this site for others)

CREATE TABLE "SYS_SQLLDR_X_EXT_T"
(
  X NUMBER(38),
  Y VARCHAR2(3),
  Z DATE
)
ORGANIZATION external
(
  TYPE oracle_loader
  DEFAULT DIRECTORY SYS_SQLLDR_XT_TMPDIR_00000
  ACCESS PARAMETERS
  (
    RECORDS FIXED 100 CHARACTERSET WE8EBCDIC500
    BADFILE 'SYS_SQLLDR_XT_TMPDIR_00000':'foo.bad'
    LOGFILE 't.log_xt'
    READSIZE 1048576
    FIELDS LDRTRIM
    REJECT ROWS WITH ALL NULL FIELDS
    (
      X (1:2) CHAR(2),
      Y (2:4) CHAR(3),
      Z (5:10) CHAR(6)
    )
  )
  location
  (
    'foo.dat'
  )
)REJECT LIMIT UNLIMITED

Review &
      Followup

      Rating: 5
External table
      and ebcdic and positions  December 03, 2003

Reviewer:  Phil
Thank you very much for prompt response, I was trying to do this and but making
mistakes also learned new thing to convert existing control files on sqlldr
prompt.

December 03, 2003 Igor -- Thanks for the question regarding
      "Collection", version 8.1.6

      You Asked
Hi Tom,

I am trying to create a collection from a table test. If the field is null, I
dont want it to appear in the collection. Is this possible with a single SQL? My
solution was to  use decode and bitmask but with more than 3 columns in the
table this aproach is impossible.

SQL> select * from test;

NAME                 ADDRESS
-------------------- --------------------
tom                  124 Street
bob

SQL>CREATE OR REPLACE
TYPE parametervalue
AS OBJECT (
   parameter_id VARCHAR2(30),
   value  VARCHAR2(200)
)
/

SQL>CREATE OR REPLACE
TYPE parametervalue_list
AS VARYING ARRAY(500) OF PARAMETERVALUE
/

SQL> SELECT DECODE (
            DECODE (NAME, NULL, 0, 2)
          + DECODE (address, NULL, 0, 4),
          0, NULL,
          2, parametervalue_list (parametervalue (1, NAME)),
          4, parametervalue_list (parametervalue (2, address)),
          6, parametervalue_list (
                parametervalue (1, NAME),
                parametervalue (2, address)
             )
       )
  FROM test
  2    3    4    5    6    7    8    9   10   11   12   13
SQL> /

DECODE(DECODE(NAME,NULL,0,2)+DECODE(ADDRESS,NULL,0,4),0,NULL,2,PARAMETERVALUE_LI
--------------------------------------------------------------------------------
PARAMETERVALUE_LIST(PARAMETERVALUE('1', 'tom'), PARAMETERVALUE('2', '124 Street'
))

PARAMETERVALUE_LIST(PARAMETERVALUE('1', 'bob'))

Thanks,

      and we said...
Easy enough -- start with:

ops$tkyte@ORA920> create table t ( name varchar2(5), address varchar2(10) );
Table created.

ops$tkyte@ORA920> insert into t values ( 'tom', '124 Street' );
1 row created.

ops$tkyte@ORA920> insert into t values ( 'bob', null );
1 row created.

Now, generate a relational query that pivots the columns to rows and filters
the nulls out as so:

ops$tkyte@ORA920> select *
  2    from (
  3  select param_id,
  4         decode( param_id,
  5                  1, name,
  6                                  2, address ) value
  7    from t, ( select rownum param_id from all_objects where rownum <= 2 )
  8         )
  9   where value is not null
 10  /

  PARAM_ID VALUE
---------- ----------
         1 tom
         1 bob
         2 124 Street

Now, we just need to create our types:

ops$tkyte@ORA920> create or replace type parametervalue as object
  2  ( parameter_id number, value varchar2(10) )
  3  /

Type created.

ops$tkyte@ORA920> create or replace type parametervalue_list as table of
parametervalue
  2  /

Type created.

 and cast our relational data as that table type

ops$tkyte@ORA920> select cast( multiset(
  2      select *
  3        from (
  4      select param_id,
  5             decode( param_id,
  6                      1, name,
  7                      2, address ) value
  8        from t, ( select rownum param_id from all_objects where rownum <= 2 )
  9             )
 10       where value is not null ) as parametervalue_list )
 11    from dual;

CAST(MULTISET(SELECT*FROM(SELECTPARAM_ID,DECODE(PARAM_ID,1,NAME,2,ADDRESS)VALUEFR
OMT,(SELECTROWNUMPARAM_IDFROMALL_OBJECTSWHEREROWNU
---------------------------------------------------------------------------------
--------------------------------------------------
PARAMETERVALUE_LIST(PARAMETERVALUE(1, 'tom'), PARAMETERVALUE(1, 'bob'),
PARAMETERVALUE(2, '124 Street'))

Review &
      Followup

      Rating: 3
somewhat wrong
      result  March 11, 2003
Reviewer:  Igor
       from San Francisco, CA USA
Thanks Tom, this is very usefull.

However,  the results of our queries are very different. My query returns
parametervalue_list() collection for each row in the table (2 rows), and
illiminates null parametervalue. Your query puts all rows in a single
parametervalue_list() collection, thats why you get 1 row only.

          Followup:

so, use the same technique, concepts and techniques are expandable like that:

ops$tkyte@ORA920> select cast( multiset(
                         select param_id,
                                decode( param_id, 1, name, 2, address ) value
  2                        from t t2,
  3                             (select rownum param_id from all_objects
                                  where rownum <= 2)
  4                       where t2.rowid = t.rowid
  5                         and decode( param_id, 1, name, 2, address )
                                    is not null )
  6              as parametervalue_list )
  7    from t;

CAST(MULTISET(SELECTPARAM_ID,DECODE(PARAM_ID,1,NAME,2,ADDRESS)VALUEFROMTT2,(SELEC
TROWNUMPARAM_IDFROMALL_OBJECTSWHEREROWNUM<=2)WHERE
---------------------------------------------------------------------------------
--------------------------------------------------
PARAMETERVALUE_LIST(PARAMETERVALUE(1, 'tom'), PARAMETERVALUE(2, '124 Street'))
PARAMETERVALUE_LIST(PARAMETERVALUE(1, 'bob'))

      Rating:
      5
slow  March 14, 2003
Reviewer:  Igor

Tom, thank you for taken the time to answer. I am inserting 20 mill rows from
remote system using slightly modified query to a table that has no constraints
or indexes. v$longops shows 160hours +.
Is there anything I can do to improve the speed. I am migrating from Solaris to
Linux.

Here is the trace for 1000 rows.

ALTER TABLE member_account_test NOLOGGING;
ALTER TABLE member_account_test PARALLEL 32;
ALTER SESSION ENABLE PARALLEL DML;

set autotrace on

INSERT /*+ Append parallel(member_accoount,32) */
       INTO member_account_test
            (member_account_id, member_entity_id, site_id, email, PASSWORD,
             partner_member_id, signup, optout, optout_reason, email_verified,
             password_verified, info)
SELECT ROWNUM + :v_member_seq, ROWNUM + :v_entity_seq, t.site_id, t.email,
       t.PASSWORD, t.partner_member_id, t.signup, t.optout, NULL,
       t.email_verified, t.password_verified,
       CAST (MULTISET (SELECT parameter_id,
                              DECODE (
                                 parameter_id,
                                 :v_param_fn, t2.person_first_name,
                                 :v_param_addr1, t2.address1,
                                 :v_param_addr2, t2.address2,
                                 :v_param_city, t2.city,
                                 :v_param_zip, t2.postal_code,
                                 :v_param_st, t2.state,
                                 :v_param_ctry, t2.country_code,
                                 :v_param_ln, t2.person_last_name,
                                 :v_param_gndr, t2.gender,
                                 :v_param_dob_day, t2.dob_dd,
                                 :v_param_dob_month, t2.dob_mm,
                                 :v_param_dob_year, t2.dob_yyyy
                              ) VALUE
                         FROM member_account@master t2,
                              (SELECT parameter_id
                                 FROM parameter
                                WHERE NAME IN ('fn',
                                               'addr1',
                                               'addr2',
                                               'city',
                                               'zip',
                                               'st',
                                               'ctry',
                                               'ctry',
                                               'ln',
                                               'gndr',
                                               'dob_day',
                                               'dob_month',
                                               'dob_year'
                                              )
                                  AND TYPE = 'MEM')
                        WHERE t2.ROWID = t.ROWID
                          AND DECODE (
                                 parameter_id,
                                 :v_param_fn, t2.person_first_name,
                                 :v_param_addr1, t2.address1,
                                 :v_param_addr2, t2.address2,
                                 :v_param_city, t2.city,
                                 :v_param_zip, t2.postal_code,
                                 :v_param_st, t2.state,
                                 :v_param_ctry, t2.country_code,
                                 :v_param_ln, t2.person_last_name,
                                 :v_param_gndr, t2.gender,
                                 :v_param_dob_day, t2.dob_dd,
                                 :v_param_dob_month, t2.dob_mm,
                                 :v_param_dob_year, t2.dob_yyyy
                              ) IS NOT NULL) AS parametervalue_list)
  FROM member_account@master t
    WHERE rownum <= 1000
/

1000 rows created.

Execution Plan
----------------------------------------------------------
   0      INSERT STATEMENT Optimizer=CHOOSE (Cost=49088 Card=21575342
          Bytes=2416438304)

   1    0   COUNT (STOPKEY)
   2    1     REMOTE* (Cost=49088 Card=21575342 Bytes=2416438304)      NETFLIP.
                                                                       NETFLIP.
                                                                       COM

   2 SERIAL_FROM_REMOTE            SELECT ROWID,"SITE_ID","EMAIL","PASSWORD","P
                                   ARTNER_MEMBER_ID","SIGNUP","OPTOUT",

Statistics
----------------------------------------------------------
        295  recursive calls
       5959  db block gets
       4020  consistent gets
         37  physical reads
     508980  redo size
        440  bytes sent via SQL*Net to client
       4043  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
         26  sorts (memory)
          0  sorts (disk)
       1000  rows processed

          Followup:

I would be tempted to copy the table "as is" and then perform this
transformation.

You cannot drive the query from the other site cause of the object reference so
it is doing 20million back and forths.

      Rating: 4
Getting
      objects  December 03, 2003
Reviewer:  A
      reader  from Russia
Hello Tom!
I have a SQL output that in general is similar to the result of the query in the
last review:

PARAMETERVALUE_LIST(PARAMETERVALUE(1, 'tom'), PARAMETERVALUE(1, 'bob'),
PARAMETERVALUE(2, '124 Street'), PARAMETERVALUE(2, '124 Street'))
PARAMETERVALUE_LIST(PARAMETERVALUE(1, 'john'), PARAMETERVALUE(2, 'bob'))

2 rows.

Want to get:

tom
bob
124 Street
john

4 rows.

How could it be done?
Thank you in advance!

          Followup:

ops$tkyte@ORA9IR2> select * from t;

         X
----------
Y(PARAMETER_ID, VALUE)
---------------------------------------------------------------------------------
--------------------------------------------------
         1
PARAMETERVALUE_LIST(PARAMETERVALUE(1, 'tom'), PARAMETERVALUE(1, 'bob'),
PARAMETERVALUE(2, '124 Street'), PARAMETERVALUE(2, '124 Str
eet'))

         2
PARAMETERVALUE_LIST(PARAMETERVALUE(1, 'john'), PARAMETERVALUE(2, 'bob'))

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select t.x, x.* from t, TABLE(t.y) x
  2  /

         X PARAMETER_ID VALUE
---------- ------------ ----------
         1            1 tom
         1            1 bob
         1            2 124 Street
         1            2 124 Street
         2            1 john
         2            2 bob

6 rows selected.

      Rating: 4
And even
      more..  December 03, 2003
Reviewer:  A
      reader  from Russia
Thank you very much for the quick response!

But I don't how to apply this to the following case:

create type o_date as object ( d date )
/

create type t_date as table of o_date
/

create or replace function f (d1 date, d2 date)
return t_date pipelined
as
begin
  for i in 0 .. (d2 - d1) loop
    pipe row ( o_date(d1 + i) );
  end loop;
  return;
end;
/

select cast(f(d1, d2) as t_date)
from (select sysdate d1, sysdate+2 d2 from dual
union
select sysdate + 10, sysdate + 10 + 2 from dual)
/

SQL> select cast(f(d1, d2) as t_date)
  2  from (select sysdate d1, sysdate+2 d2 from dual
  3  union
  4   select sysdate + 1, sysdate + 6 from dual);

CAST(F(D1,D2)AST_DATE)(D)
--------------------------------------------------------------------------------
T_DATE(O_DATE('03.12.03'), O_DATE('04.12.03'), O_DATE('05.12.03'))
T_DATE(O_DATE('04.12.03'), O_DATE('05.12.03'), O_DATE('06.12.03'), O_DATE('07.12
.03'), O_DATE('08.12.03'), O_DATE('09.12.03'))

But I want to get:

03.12.03
04.12.03
05.12.03
06.12.03
07.12.03
08.12.03
09.12.03

in a single query.

          Followup:

ops$tkyte@ORA9IR2> with Q
  2  as
  3  (select rownum X, cast(f(d1, d2) as t_date) Y
  4    from ( select sysdate d1, sysdate+2 d2 from dual
  5           union
  6           select sysdate + 10, sysdate + 10 + 2 from dual
  7         )
  8  )
  9  select x, d
 10    from q, table(q.y) q2
 11  /

         X D
---------- ---------
         1 03-DEC-03
         1 04-DEC-03
         1 05-DEC-03
         2 13-DEC-03
         2 14-DEC-03
         2 15-DEC-03

6 rows selected.

is one approach...

      Rating: 5
Thank you very
      much, Tom!!  December 03, 2003
Reviewer:  A
      reader  from Russia

December 03, 2003 rd -- Thanks for the question regarding
      "miscelaneous questions", version 8.1.7

      You Asked
Tom

A lot of times when you are working, quite a few misc questions croos our minds,
which we dont attend to or postpone. I have put togeteher a list of such
qeustions in my case.

1.From the sqlplus prompt , how should I declare a variable, then use a select
statement to store a value in that variable , and then display it.

2.When I use the set serveroutput on and dbms_output.put_line I frequently get ,
buffer overflow error. what is the reason for this error and how shoud I get
over this.

3.From some of the posts I have come to the conclusion that we should not use
the when others than exception( when you dont expect any errors), and allow the
errors to propogate out of pl/sql, so that the user will know there has been an
error. Your comments please.

4.When using sqlplus, I do  line<line number> to get a particular line from the
buffer, how can I get a group of lines.

5.What is the difference between call and execute  sqlplus commands.

6.Why is it that when I run a sql from sqlplus, it gets parsed each time it is
run, where as when I run the same sql as a part of a procedure it gets parsed
only once for multiple executions.

7.What do the max and min transactions mean in the create table syntax. Can we
give them at the table space level.

8.How can I save all my current settings of my sqlplus session in a file.

9.what is the maxvalue we can give to set line size.

10.when I do a  insert into t select * from t1, can I capture the rowcount using
sql%ROWCOUNT.

11.when I do  two consequetive loop like

for rec in c1 loop
for records in c2 loop

..some processing

can I refer to the records in the first loop in the second loop

if I want such type of functionality what is the way of doing it.

12.Which is faster  a select into into <variable names> or a cursor fetch  or a
cursor loop like for rec in c1 loop,and why.

In case if select into      is faster , then why do everyone use cursors.

13.I want to know how records are being inserted by a large select statement.
One method to do it is using the dbms_application_info package,  but I want to
know how to do the same using dbms_pipe and dmbs_alert.

14.What is the purpose of using dmbs_session.set_context

15.From your answers , I have come to the conclusion  that the order of
preference for maximum performance is  to do a particular task

i.do it in pure sql if possible
ii.do it in pl/sql
iii.do it in pro*c or c
iii. do it in java
your comments please

      and we said...
You know, based on your questions -- i have a book suggestion for you:

http://www.amazon.com/exec/obidos/ASIN/1861004826/002-4789950-3064808
I go into most all of this in great depth in there.

this is a truly loooonnnnggg list.

1) I usually use a substitition variable for that:

ops$tkyte@ORA817.US.ORACLE.COM> column X new_val Y
ops$tkyte@ORA817.US.ORACLE.COM> select dummy X from dual;

X
-
X

ops$tkyte@ORA817.US.ORACLE.COM> prompt &Y
X

You can also use bind variables:

ops$tkyte@ORA817.US.ORACLE.COM> variable xx varchar2(20);
ops$tkyte@ORA817.US.ORACLE.COM> begin
  2  select dummy into :xx from dual;
  3  end;
  4  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817.US.ORACLE.COM> print xx

XX
--------------------------------
X

2) if you want the WHOLE story (and its longer then you think) about dbms_output
-- get my book.  I explain in somewhat gory detail how dbms_output works
exactly.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1011431134399
for the short story.

You can set

SQL> set serveroutput on size 1000000

to get the maximum output

3) You hit the nail on the head -- thats exactly what I'm trying to say, glad it
got across.

A when others will "hide" an error from the caller.  If you do not HANDLE the
error, if you are not EXPECTING the error -- then DONT CATCH and subsequently
IGNORE the error.  Its a bug waiting to happen.

4)

SQL> list 100 105

will list lines 100 - 105 of the buffer.

5) The CALL statment is SQL (and only understands SQL types).
   EXEC is really shorthand for begin/end;

An example helps best.  CALL can only call those functions/procedures that have
SQL types as inputs/outputs so if we:

ops$tkyte@ORA817.US.ORACLE.COM> create or replace procedure p( x in boolean
default false )
  2  as
  3  begin
  4          null;
  5  end;
  6  /

Procedure created.

boolean is NOT a sql type but we can call P() using call

ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> call p();

Call completed.

but not when we want to supply that boolean input:

ops$tkyte@ORA817.US.ORACLE.COM> call p(true);
call p(true)
       *
ERROR at line 1:
ORA-06576: not a valid function or procedure name

Now exec works cause that runs plsql to run the proceudre:

ops$tkyte@ORA817.US.ORACLE.COM> exec p(true);

PL/SQL procedure successfully completed.

and this just shows that exec is really just shorthand for begin/end;

ops$tkyte@ORA817.US.ORACLE.COM> exec blah
BEGIN blah; END;

      *
ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00201: identifier 'BLAH' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

6) because SQLPlus uses only one cursor for your statements.  PLSQL caches and
keeps many cursors open for you.  PLSQL is optimized for running SQL and PLSQL
in the database.  SQLPlus is just a simple little interface.

7) initrans and maxtrans control the initial size of the transaction table on a
block and maxtrans limits the overall size.  A transaction table can dynamically
grow in size given sufficient free space on the block so the defaults are
generally sufficient.

These settings are specific to segments are are not settable at the tablespace
level.

8) see

http://technet.oracle.com/doc/oracle8i_816/sqlplus.816/a75664/ch845.htm#1002991
9) see

http://technet.oracle.com/doc/oracle8i_816/sqlplus.816/a75664/ch839.htm#1002118
it is OS dependent.  If you do something like:

ops$tkyte@ORA817.US.ORACLE.COM> set linesize 999999999999999999999999999999999
SP2-0267: linesize option -1 out of range (1 through 32767)

it'll tell you.

10) yes.

11) yes -- you can refer to the current value of rec inside of that loop.

12) see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1544606261686
implicit cursors rule, they are faster, better, cheaper, easier to code -- they
ain't broke.

explicit cursors are harder to code, slower -- using them only when you have no
choice.

I don't know why people persist in believing mythology such as "explicit cursors
are faster".  Once upon a very very long time ago -- it was true in FORMS (like
version 2 of forms) that an explicit cursor was marginally faster then a select
into HOWEVER that changed many many years ago (like 8 or 9 years ago)

13) I don't know how to do that using dbms_application_info, or dbms_pipe/alert.
 If you "insert into t select* from t2" - you can query v$session_longops to see
how long it is estimated to take from another session but the session doing the
insert into cannot make use of any of those packages during that statement

14) see

http://osi.oracle.com/~tkyte/article2/index.html
for the use of that package

15) almost -- i actually list this in my book in order:

i) sql
ii) plsql
iii) java stored procedure
iv) c

sql = fastest, easiest, least amount of code to maintain
plsql = best integration with sql, fast, easy to code, best in the database
java = do all of the things plsql cannot do (eg: send an email, write a file)
c = that which java cannot do or java does too slowly.

followup to comment one

Yes - that is the hypothesis people base their fundematal misunderstanding on.

A select into is known by Oracle -- we prefetch in one call 2 rows.  We make no
extra call (you can verify this via a SQL_TRACE+TKPROF -- you'll see 1 fetch
being made for a select into -- never two).

The other fundemental issue is that:

open c;
fetch c into x;
close c;

is not in any way shape or form equivalent to

select x into x from t;

You must code:

open c;
fetch c into x;
if (c%notfound) then raise no_data_found; end if;
fetch c into x;
if (c%found) then raise too_many_rows; end if;
close c;

by the time you've done that -- you are way worse off then a select into (which
will do the above in C very quickly with minimal round trips)

The mantra should be: if you can do it in a single sql statement -- do it, it'll
be faster, cheaper and easier to code.

followup to comment 2, Very good, but we want this clarified

One case is when you use a dynamically opened ref cursor for example.  Eg:

declare
   type rc is ref cursor;
   l_cursor rc;
begin
    open l_cursor for 'some string....';
    loop
       fetch l_cursor into l_rec;
       exit when l_cursor%notfound;
    end loop;
    close l_cursor;
end;

that you cannot do with an implicit cursor.

followup to comment 3, "dont leave us in confusion"

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1544606261686
Also, we just had a big discussion on this in the newsgroups not too long ago:

http://groups.google.com/groups?hl=en&safe=off&th=c41fe18e5bd97a33,17&start=0
followup to comment 4

Well, tell them its FASTER to code select into -- the code runs FASTER.

If the data in the select into comes from a single table, the code is:

declare
    l_rec T%rowtype;
begin
    select * into l_rec from t where ....;

using an explicit cursor you have to code:

declare
    cursor c is select ....;
    l_rec c%rowtype;
begin
    open c;
    fetch c into l_rec;
    if ( c%notfound ) then
        raise no_data_found;
    end if;
    close c;

which one is harder to code???? And what if the select into isn't based on a
table but many?  Well, I might use a template cursor:

declare
    cursor c is select * from t1, t2, t3;
    l_rec c%rowtype;
begin
    select * into l_rec
      from t1, t2, t3
     where .......;

still easier then an explicit cursor (note that template cursor doesn't need
anything other then the select list and the from clause -- no joins, we'll never
use it)

followup to comment 5

Get off the fence, use implicit cursors until you cannot -- then use an explicit
one.  they are better.

followup to comment 7

The whole store is that

o implicit cursors are easier to code by their very nature. (i'm including
cursor for loops in here as well -- not just select intos)

o implicit cursors run faster

o implicit cursors are easiest to understand in the code, less code to read
through, less code to manage.
Review &
      Followup

      Rating: 4
A book in
      itself  August 16, 2001
Reviewer:  Trevor
       from UK
This posting was a book in itself.

I think the idea explicit cursors are faster stems
from a book which explicit cursors were better because
it said something like, implicit cursors always did an
extra fetch. When it reached the end of a data set
and implicit cursor (I heard) will do another read
to check if it really was the end. whereas an explicit
cursor you can control in you program so when the
no more rows is reached you end things.

      Rating: 5
Very good, but
      we want this clarified  August 16, 2001
Reviewer:
      abbajaan  from dubai
Please give us an example for the following 'explicit cursors are harder to
code, slower -- using them only when you have no choice.', what are those
situations or situation where we can consider tha we have no choice but to use
explicit cursors.

      Rating: 4
dont leave us
      in confusion  August 16, 2001
Reviewer:  John
      Quirk  from CA
Illustrate  you statement  "by the time you've done that -- you are way worse
off then a select into (which
will do the above in C very quickly with minimal round trips)"   with examples,
that is the only way out of this

      Rating:
      4
  August 16, 2001
Reviewer:  Daaler
       from Tx
Developers have got used to explicit cursor so much , that select into   is kind
of out of fashion.

Tom, can you think of a way to prove conclusively ( I know you have already done
that) to the developers, that select into should be used.

But the arguement against select into is that, so many variables need to be
declared, in case the select has multiple columns, and that explicit cursors are
convenient to code .

You need to do something Tom.

      Rating:
      4
  August 16, 2001
Reviewer:  Daaler
       from Tx
all these days I thought I was doing the right thing using explicit cursors. I
feel as if Iam on crossroads, Iam being very indecisive about this , WHICH WAY
TO GO?

      Rating: 5
more discussion
      on this  August 16, 2001
Reviewer:  Ason
       from LA
Tom

I think this is a very good question, every developer gets this kind of trivial
doubts while working, he saves it for later, and forgets about it.

I think you shoud beat the select into and explicit cursors discussion  to
death, as this is very important, many so called oracle experts out there, will
be running out for cover.

      Rating:
      5
  August 16, 2001
Reviewer:
      A reader
Damit, after working for 7 years in oracle, and doing so many explicit cursors,
I get to hear that select into is better

"O.K., TELL ME THE WHOLE STORY"

      Rating: 5
MIN/MAX
      subqueries in implicit cursors  August 16, 2001

Reviewer:  A reader
I agree on most points, but how about queries designed to give values on a
certain point in time.

If you compare the two statements underneath I would prefer the second one using
the explicit cursor. Espacially if I could reuse the cursor again and again.

SELECT .. INTO .. FROM t main WHERE main.version_date = (SELECT
MIN(sub.version_date) FROM t sub WHERE .. AND sub.version_date >= l_date)

as compared to

CURSOR c_t .. IS SELECT .. FROM t WHERE .. AND sub.version_date >= b_date ORDER
BY t.version_date DESC

and then fetching just one row.

      Rating: 5
response to
      previous post  August 17, 2001
Reviewer:
      Connor  from UK
Implicit is still the way to go here but your code would be:
select blah
from (
  select blah
  from   table
  order by date_col desc )
where rownum = 1

because you are giving Oracle EXTRA information that you only want the highest
(or lowest) piece of information.  In the explicit cursor case, Oracle doesn't
know that you'll only be fetching 1 row.

      Rating: 5
Is there a
      scrollable cursor in Oracle?  April 02, 2002

Reviewer:  Shane Hu  from NYC
DB2 CLI has scrollable cursors. Just wondering how Oracle do backward fetch with
pro*c or OCI?

          Followup:

see

http://download-west.oracle.com/otndoc/oracle9i/901_doc/appdev.901/a89857/oci04sql.htm#440735
...

      Rating: 5
A wee-bit more
      of clarification wanted!!!!!!!!  April 03, 2002

Reviewer:  AK  from India
Hi tom,
Admittedly the newsgroup discussion has made it amply clear that select into is
to be a preferred choice against an explicit cursor. I desire just one more
clarification:

Considering that for each row fetched by a cursor there is explicit processing
to be performed, which one of the below is preferable??

1. for crec in (select a,b,c
                from <table list>
                where <where clauses>
   loop
            <my processsing>
   end loop;

2. Explicit cursors
   cursor crec is <select SQl>

   open cursor
   loop
   fetch
   exit when crec%notfound
           < my processing>
   end loop

What i want to know is which has a better performance???

Thanks in advance.

          Followup:

Just one thing you need to remember:

"implicit cursors rule, they are FASTER, they are easier to code, they are more
readable, they cause you to type less characters"

#1 is better.  Benchmark it and see!

      Rating: 5
most of the
      doubts regarding cursors are clear  May 27, 2003

Reviewer:  srinivasa rao bachina  from bangalore

Hello Tom
  thanks for u r nice explanation
one more doubt regardign cursor declaration
--in the cursor declaration we can use sql statement in the decode satement?
Ex:
  select decode(sysdate,sysdate,(Select sysdate+1 from dual),sysdate-1) from
dual  is working in sqlplus
if i use the same staement in cursor declaration like
declare
cursor c1 is
 select decode(sysdate,sysdate,(Select sysdate+1 from dual),sysdate-1) dt from
dual
 begin
 for i in c1
 loop
  dbms_output.put_line(to_char(i.dt,'DDMMYYYY'));
  end loop;
end;
i am getting the error
--
ORA-06550: line 3, column 33:
PLS-00103: Encountered the symbol "SELECT" when expecting one of the following:

   ( - + mod not null others <an identifier>
   <a double-quoted delimited-identifier> <a bind variable> avg
   count current exists max min prior sql stddev sum variance
   execute forall time timestamp interval date
   <a string literal with character set specification>
   <a number> <a single-quoted SQL string>
ORA-06550: line 3, column 60:
PLS-00103: Encountered the symbol "," when expecting one of the following:

   ; return returning and or
ORA-06550: line 4, column 2:
PLS-00103: Encountered the symbol "BEGIN" when expecting one of the following:

   . , @ ; for <an identifier>
   <a double-quoted delimited-identifier> group having intersect
   minus order partition start subpartition union where connect
--
 i am using oracle 8i release 8.1.7.4.0
Can u Please Help me
Thank You in Advance

          Followup:

in 9i, yes, in 8i no.

in 8i you must hide that construct (and CASE, order by in subquery, group by
rollup/cube, analytics and some others) in a DYNAMIC query or in a view.

      Rating: 4
use
      ful  May 29, 2003
Reviewer:  srinivasa rao
      bachina  from bangalore
Hi tom
Can u explain me this case with some examples,if time permits to you?

thanks in advance

          Followup:

you mean like

"create view v as select ......"

or

   execute immediate open l_cursor for l_query;

it is very much straightforward stuff -- views and native dynamic sql.

      Rating: 4
One
      question  June 27, 2003
Reviewer:  VMK
       from India
Hi tom,

My query

SELECT E.ENAME,E.SAL,E.DEPTNO,
(SELECT DNAME FROM DEPT D WHERE E.DEPTNO = D.DEPTNO)
FROM EMP E;

works fine in sql*plus. But if I want to declare it in cursor, it gives an error

DECLARE
 CURSOR CUR_TEMP IS
  SELECT E.ENAME,E.SAL,E.DEPTNO,
  (SELECT DNAME FROM DEPT D WHERE E.DEPTNO = D.DEPTNO)
  FROM EMP E;
BEGIN
NULL;
END;
/

Why it is so?

Thanks in advance.

VMK.

          Followup:

because you are using 8i and there are some constructs that 8i does not
recognize (select of a select, order by in a subquery, cursor() functions,
analytics, group by cube/rollup)

solutions:

o use 9i
o use dynamic sql
o use a view.

      Rating: 3
Command line
      arguments  June 29, 2003
Reviewer:  a reader
       from pakistan
How can I pass command line argument to a .sql file
in both of the following situations.

1- Running SQLPLUS from a host prompt.
2- Running .sql file with @ in SQL prompt.

Thanks

          Followup:

$ sqlplus user/password @script first_parm second_parm

or

SQL> @script first_parm second_parm

      Rating: 5
miscelaneous
      questions  June 30, 2003
Reviewer:
      A reader
As usual excellent and resourceful answers.
Thanks Tom!

      Rating: 3
Will you please
      provide a concrete example to handle subselect statement .
        July 06, 2003
Reviewer:  onsh  from
      Canada
Hi Tom,
I want to follow up with you as it would be great to get your assistance to
resolve the PLS-00103 error with the following code:
CREATE OR REPLACE
    PROCEDURE SPR_REP_VISIT_DETAIL (
                                          v_msched_skey IN NUMBER,
                                          v_start_date  IN CHAR,
                                          v_end_date    IN CHAR,
                                          v_all_hangarbays  IN CHAR,
                                          v_hangarbays  IN varchar2,
                                          result_set  OUT
VVV_REPORTS.vis_det_hangarbay_refcur

                                         )IS

   temp_container VVV_REPORTS.vis_det_hangarbay_refcur;

   BEGIN

     IF ( NVL(v_all_hangarbays, 'FALSE') = 'TRUE') THEN

      OPEN temp_container FOR

            SELECT  (v.base_code||'-'||v.hangar_code||'-'||v.bay_code) hbay ,
                    (SELECT DISTINCT a.aircraft_serial_num FROM aircraft a WHERE
a.item_id = v.item_id) thetail,
                    (SELECT DISTINCT mp.mntce_project_code|| ' '
||mp.MNTCE_PROJECT_TITLE FROM MAINTENANCE_PROJECT mp WHERE mp.MNTCE_PROJECT_ID =
vmi.MNTCE_PROJECT_ID) projtitle,
                    (SELECT DISTINCT mp.MNTCE_PROJECT_TYPE_CODE FROM
maintenance_project mp WHERE mp.MNTCE_PROJECT_ID = vmi.MNTCE_PROJECT_ID )
checktype,
                    v.visit_start_date_sched visit_start,
                    v.visit_end_date_sched visit_end,
                    (v.visit_end_date_sched - v.visit_start_date_sched   )
duration,
                    (vmi.FORECASTED_START_DATE - v.VISIT_START_DATE_SCHED)
slack,
                    vmi.percent_yield yield,
                    v.visit_desc comments

            FROM visit v,
                 visit_maint_item vmi
            WHERE v.visit_skey = vmi.visit_skey
              AND v.msched_skey = v_msched_skey
              AND (
                    (     V.VISIT_START_DATE_SCHED >= TO_DATE(v_start_date,
'DD-MON-YYYY')
                      AND V.VISIT_START_DATE_SCHED <= TO_DATE(v_end_date,
'DD-MON-YYYY')
                    )

                  )
            ORDER BY 1, 2, 3;
      END IF;

      END IF;

        result_set:= temp_container;

   END SPR_REP_VISIT_DETAIL;

I am using Oracle 8.1.7.4 and I realized that PL/SQL engine in 9i is much more
smart than in 8i.The code above is running w/o problem in 9i and there are bunch
of errors in 8i reffereing to subselect statements. Will you please help me.

Thanks a lot in advance,

onsh

          Followup:

in 8i, the construct:

  select ( select count(*) from dual )
    from dual;

is not accepted by PLSQL (in addtition to analytics, case statements, group by
rollup/cube, order by in a subquery, and some others I may have forgotten)

The work around is:

  open p_cursor for
  'SELECT .....' USING <binds>;

(native dynamic sql) OR use a VIEW to hide these constructs from PLSQL.

      Rating: 4
Thanks a lot
      for your suggestion  July 08, 2003
Reviewer:
      A reader
Hi Tom,
I've used a view to hide the subselect with analytic functions.
BTW, is there any reference in Oracle 8i (8.1.7.3) to highlight PL/SQL engine
limitations?

Thanks,
onsh

          Followup:

No, there is not.

      Rating:
      3
OK  September 10, 2003
Reviewer:
      R.Chacravarthi  from Chennai,India
Dear Sir,
I have a question for you.It is
1)Normally in queries we use column name to restrict rows
for ex.
  select * from emp where deptno = 10;
 My Question is
   Can column values be used for row restriction? Like
   select * from emp where 10 ...code goes here.
I expect your reply.
Please provide some examples.
Thanks in advance.

          Followup:

sorry, not getting your point.  don't understand what you mean

      Rating:
      2
OK  September 11, 2003
Reviewer:
      R.Chacravarthi  from Chennai,India
Dear Sir,
What I asked you was
  'select ename from emp where <condition>;'
  condition can be of the column names like ename/deptno/empno.My question is
whether column values can
be specified in the condition.?
  for ex.
  'select ename from emp where 10 (<= -- denotes deptno )
                                in (...code which tests for
                                        some condition)'
Is this possible?

          Followup:

if you are asking "can I reference the first column of a table using the number
"1" instead of its name"

the answer is NO.

select * from emp where 1 = 'KING';

instead of

select * from emp where enamme = 'KING';

won't work (correctly).  where 1='KING' is valid SQL -- but it is using the
number 1 in there.

there would be no way to ascertain whether you meant "the number one" or "the
first column" in any query -- ever.  you would never be able to use a number in
a SQL query.

      Rating: 5
Just
      great!!!  September 11, 2003
Reviewer:
      A reader
This thread is really going good.

      Rating: 2
OK
        September 11, 2003
Reviewer:
      R.Chacravarthi  from Chennai,India
Dear Sir,
Received your response and thanks for that.You are saying that numbers cannot be
used in sql queries.But certain things behave in  a strange manner.Consider the
following
queries.
QL> select ename from emp where sal = 3000;

ENAME

----------

SCOTT

FORD

SQL> select ename from emp e where 3000 in(select sal from emp where e.ename =
ename);

ENAME

----------

SCOTT

FORD

SQL> select ename from emp where deptno = 20;

ENAME

----------

SMITH

JONES

SCOTT

ADAMS

FORD

SQL> select ename from emp e where 20 in(select deptno from emp where e.ename =
ename);

ENAME

----------

SMITH

JONES

SCOTT

ADAMS

FORD

SQL> spool off
Why is this happening?Is this correct?.I should mention that
"I am using column values in queries"

          Followup:

of course you can use numbers in there -- your question is/was very very
unclear.

of COURSE you can use a number.

i had no idea what you meant above.

yes, where <NUMBER> <operator> <something> works just dandy.

      Rating:
      2
OK  September 12, 2003
Reviewer:
      R.Chacravarthi  from Chennai,India
Thanks for your response.Could you please provide some
"extraordinary" examples for such types of queries.Please do
reply.

          Followup:

huh?  "extraordinary"??  don't know what you are looking for.

      Rating: 5
Basic
      questions  December 03, 2003
Reviewer:
      Suresh  from India
Hi

I have SOME basic questions

Basically I am SQL SERVER professional. My current project is using ORACLE
So Please clarify these basic questions.

We are using packages. But inside the procedure (which resides
inside the PACKAGES) we are not doing any
commit or rollback work(transaction related).

Q1) so how these data’s will get commit. do we have to COMMIT it explicitly.
Actually we are doing up gradation work. But the database is same(ORACLE 8.1.7).
I have to
write some more procedures. In the old package I didn’t see any
transaction related (commit, rollback) works?

Q2) My Current project is web based project. For e.g. one person login (using
website)
and added/edited some data’s. Can other session see that data’s. Why I am asking

this question is that, these programs are using packages to edit/add. But we
don’t have any transaction
controls here.
So other session can see these added/edited data

Q3) I have procedure like following in my package.

There are four insert statement are there.
Suppose if the fourth insert statement fails I don’t want first there
statements.
Can I use AUTONOMUS TRANSACTION? or what to do?

Please explain what I have to do exactly.

          Followup:

q1) that is awesome -- the sqlserver model of "transactions" is totally wrong --
they "autocommit" (because having locks in their system is like the kiss of
death, everything halts).  We do not.

the CLIENT that calls the procedure should commit or rollback -- it is the
CLIENTS decision.  In that fashion, you can take procedure1 and procedure2 and
call them as a single transaction anytime they want!!!

the client should either:

   begin procedure; end;
   commit or rollback based on the error code (in the event of an error, the
rollback of changes is implicit)

or, to call a procedure it can submit this block instead:

   begin
       procedure;
       commit;
   exception when others then
       rollback;
       raise;
   end;

in that fashion, the client can later code:

   begin
       procedure1;
       procedure2;
       procedure3;
       commit;
   exception when others then
       rollback;
       raise;
   end;

as a single atomic transaction.

q2) other sessions can see the data as soon as the session modifying the data
commits.  not before.

q3) if you have my book "expert one on one Oracle" - i cover this topic in huge
detail -- huge detail.  this is important stuff (not that I'm trying to see a
book here but i think you would benefit from the discussions contained therein).

in Oracle -- ACID is important, we pay attention to it.  A procedure is
considered a STATMENT -- if you call a procedure it either

a) entirely succeeds
b) entirely fails and it is as if it never occured.

a simple test:

ops$tkyte@ORA9IR2> create table t ( x int check (x>0) );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace procedure good
  2  as
  3  begin
  4    insert into t values ( 1 );
  5    insert into t values ( 2 );
  6    insert into t values ( 3 );
  7    insert into t values ( 0 );
  8  end;
  9  /

Procedure created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace procedure BAD
  2  as
  3  begin
  4    insert into t values ( 1 );
  5    insert into t values ( 2 );
  6    insert into t values ( 3 );
  7    insert into t values ( 0 );
  8  exception
  9    when others then dbms_output.put_line( 'bummer ' || sqlerrm );
 10  end;
 11  /

Procedure created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select * from t;

no rows selected

ops$tkyte@ORA9IR2> exec good;
BEGIN good; END;

*
ERROR at line 1:
ORA-02290: check constraint (OPS$TKYTE.SYS_C002910) violated
ORA-06512: at "OPS$TKYTE.GOOD", line 7
ORA-06512: at line 1

ops$tkyte@ORA9IR2> select * from t;

no rows selected

ops$tkyte@ORA9IR2> exec bad;
bummer ORA-02290: check constraint (OPS$TKYTE.SYS_C002910) violated

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> select * from t;

         X
----------
         1
         2
         3

as you can see -- if the procedure "fails" (good does that) and returns the
error the client -- lets the exception propagate out -- the work it does is
"undone" totally.

as you can also see, if you use the "when others" or any other exception to
catch the error and "make it go away" as I did here (eg: what I'm getting at is
LET THE EXCEPTIONS PROPAGATE TO THE CLIENT, do not catch them and return return
CODES!!!!) then the client is responsible for undoing the work.

      Rating: 5
Use of commit
      and rollback in java  December 03, 2003
Reviewer:
      suresh  from India
The application(java) programmers are calling a package. How transactions can be
comitted/rolled back using the java programs. If the commit/rollback is
implemented in package, does that have a down side.

If commit/rollback is not done in the java & packages both, what will be the
consequnces ?

          Followup:

well, i already stated two ways

a) have the application do it:

conn.commit()

b) submit a block that calls procedure and either commits or rollsback.

I FIRMLY believe transactional control MUST be in the client.

December 03, 2003 Christina -- Thanks for the question regarding
      "Installation of Developer 6i", version 8.1.7

      You Asked
Hi, this is my first time installing developer, what are the steps in installing
developer 6i? Do I just upgrade our developer 6 installation o do i need to do a
new install? Thanks in advance for your help.

      and we said...
See chapter 3 of Oracle Forms Developer Release 6i: Getting Started for Windows
found on

http://technet.oracle.com/docs/products/forms/doc_index.htm
it describes how you can install as an upgrade (not a full install) over 6.0
Review &
      Followup

      Rating: 3
Auto
      Installer  September 11, 2003
Reviewer:
      juancarlosreyesp@yahoo.com  from Bolivia
Hi Tom, is there a way to create a auto-installer for developer 6i runtime.
This is a way (using a script, or other options) so you don't have to be
choosing every time the options to install and after that the same with the
patch.

Thank you

          Followup:

the OUI can be run in silent mode.

so yes, you can.

but really -- it should be a zero client install - just do this web thing.

      Rating: 3
Thank Tom, for
      client/Server?  September 12, 2003
Reviewer:
      juancarlosreyesp@yahoo.com
Hi Tom
to install developer 6i for client-server, the right
solution is:

Oracle Software Packager and Installer, Release 2.2.0.16.0

http://otn.oracle.com/software/products/osp/htdocs/winsoft.html
Thank you

      Rating:
      3
  September 12, 2003
Reviewer:
      A reader
The software packager works for database and I suppose for developer 9i and up.
But to install develover 6i software I think the only solution is to change the
nt.prd and win95.prd, and leave only the options you use. (to simplify massive
installations)
The idea to use java client, doesn't works, becaues you need to pay for the
runtime, ias enterprise.
:)

          Followup:

or pay continously forever to support wickedly hard client server installations
-- sure.  you choose.

      Rating: 3
Is there a way
      to use Oracle Software Packager with dev 6i?  September 12,
      2003
Reviewer:  juancarlosreyesp@yahoo.com  from
      Bolivia
Finally I download and installed ( it didn't work with jdk1.4 )
Oracle Software Packager  it searches for products.jar
.prd file doesn't works.

The other chance was add files from filemap, but nt.prd doesn't have the
required format.
I searched a file with the clause [FILEMAP], but I didn't found.

Tom Do you have trick to use Oracle Software Packager with developer 6i?

Thank you.

          Followup:

never used either.

      Rating: 5
:) Thanks
      Tom  September 12, 2003
Reviewer:
      A reader

      Rating: 4
Oracle Net8
      Easy Config - 6i  November 05, 2003
Reviewer:
      Nazmul Hoque  from Bangladesh
After Success Instalation Of Deleloper 6i on Pentium-IV, While runing the Wizard
Oracle Net8 Easy Config to connect the Oracle Server (Database) it is showing
error :

Jrew
This Program has Perfrom illegal Oparation...
Contact vendor......

But while installed on Pentium-III than it is okay. Even copy the TNSNAMES.ORA
from P-III to P-IV is working fine. Only Problem is the Wizard is not working On
P-IV, So What is solition run the the wizard "Oracle Net8 Easy Config" on P-IV
of Developer 6i

Thanks in Advance
Nazmul

          Followup:

contact support, there is a well known issue with that very very old software on
p4's (that software predates the existence of the p4)

      Rating: 4
connecting
      other database's  November 05, 2003
Reviewer:
      Ashiq Shamsudeen A  from Trichy, Tamil Nadu, India
Hi Tom,

  Is it possible to have sqlserver2000 or other database's provided by other
database vendors , as a backend for D2K.

  If yes, can you tell how to have above said db's as a backend for D2K.

          Followup:

d2k

is that designer?  (no)
is that developer? (yes -- using ODBC, using a gateway, using generic
connectivity)

      Rating: 4
connecting
      other database's  November 05, 2003
Reviewer:
      Ashiq Shamsudeen A  from Trichy, Tamil Nadu, India
  Is it possible to have sqlserver2000 or other database's provided by other
database vendors , as a backend for D2K.

  If yes, can you tell how to have above said db's as a backend for D2K.

Hi Tom
 front end is forms(4 or 5 or 6i)

 What all are steps I've to do ,if my backend is SQL server or other vendors
database . Is any link for this ?

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4406709207206
shows how to set it up so you can do dblinks

but d2k can use odbc directly as well.

      Rating: 5
Can you give us
      a new direct link  November 05, 2003
Reviewer:
      A reader

          Followup:

otn.oracle.com -> documentation -> ids

      Rating: 4
Oracle Net8
      Easy Config   November 05, 2003
Reviewer:
      Nazmul Hoque  from Bangladesh
After Success Instalation Of Deleloper 6i on Pentium-IV, While runing the Wizard

Oracle Net8 Easy Config to connect the Oracle Server (Database) it is showing
error :

Jrew
This Program has Perfrom illegal Oparation...
Contact vendor......

But while installed on Pentium-III than it is okay. Even copy the TNSNAMES.ORA
from P-III to P-IV is working fine. Only Problem is the Wizard is not working On

P-IV, So What is solition run the the wizard "Oracle Net8 Easy Config" on P-IV
of Developer 6i

Thanks in Advance
Nazmul
--------------

Followup:
contact support, there is a well known issue with that very very old software on

p4's (that software predates the existence of the p4)

***************
Dear Tom,

Actually this is a new and critical problem for us. are there any file, that can
help me update my "Oracle Net8 Easy Config of 6i"  and i can use with out any
problem.

Thanks
Nazmul Hoque

          Followup:

contact *support*

you can edit the config files manually, sure, but there is a patch for this.

      Rating: 5
Oracle Net8
      Easy Config of 6i  November 06, 2003
Reviewer:
      Nazmul Hoque  from Bangladesh
Please advise me where and how I get this patch files and install.....

Please ... Please..., That will be a great help for me.

Best Regards
Hoque

          Followup:

http://metalink.oracle.com/

      Rating: 3
Deleloper 6i
      Patch Files  November 08, 2003
Reviewer:  Nazmul
      Hoque  from Bangladesh
Dear Tom,

Sorry, I have go above site, but did get any thing, cause of I am user not the
supplier, I did not have any Identification number to login there(or create new
user)....

Means i can't get the scope to download the patch files which very very usefull
for me.....

If you have any other solution, Please help me...

Best Reagards
Nazmul Hoque

          Followup:

you need to have support.  in order to access the support site.  if you are
contracting for someone, they should have support -- tell them what you need.

      Rating: 3
Developer 6I
      Patch files  November 09, 2003
Reviewer:  Nazmul
      Hoque  from Bangladesh
I am helpless from that site, That's why am trying get from only. You are the
only person who cna help me....

Hope you understand my problem...

Regards
Hoque

          Followup:

i cannot supply patches.  only metalink can.

      Rating: 5
Client Server
      support  December 03, 2003
Reviewer:
      juancarlosreyesp@yahoo.com  from I'm here :)
Hi Tom I would like to have your opinion about this
I think Oracle killed client-sever, I'm not sure if you did it to get your
customer go to jdeveloper 9i, or to get more money selling IAS.

About jdeveloper 9i, it's an excellent product, but it is still heavy (more
memory and resources) and it requires people with a knowledge it takes time to
learn ( I think at least! 3 months),

But the real effect I see, is that your are losing customers
Even when is true that you can swear them you are going to give support all the
eternity to developer 6i. using an old release is like using not enough good or
something that in some moment is going to be unsupported suddenly.

I agree that in some moment it was unevitable you would had to drop the support
to client-server, but it was too soon.
People says database+ias+develoer is too much, better we try using other
database, or why not to try delphi.
I think you should had keep the client-server option at least to developer 11i.
Something I suspected once I knew your your new developer 9i/ias licensing
policy, is happening, very subtle.

There several, specially thos of the standard edition, that want the
client-server, or uses because they can'd acquire knowledge or money to use IAS.

I made some post  about it,

http://forums.oracle.com/forums/thread.jsp?forum=82&thread=161385&message=579820&q=696173203530303020667265652072756e74696d65203969#579820
What do you think.?

          Followup:

I *wish* oracle killed client server.

client server KILLED itself.  the WEB killed client server (thank goodness).

No ulterior motives there, it is just 1000% superior (web based) to client
server.  client server was a brain dead idea - it is DOA (dead on arrival).  go
ahead, patch 10,000 clients someday, keep them in sync.  good luck with all of
that.

I don't see what you see.

don't be parnoid here -- it is cheaper to field 3 tier forms then to field
client server.

      Rating:
      5
  December 03, 2003
Reviewer:
      A reader
Thanks Tom,
I agree with you, this is better.
From the beginning I supposed I was the only one in this world who was going to
complaint about this, but that thread show I was'nt and confirmed other
suspicios I had about some reaction could happend, about the misunderstanding
about the license policies ias will have and the new developer 9i.

Your position seems to say "We offer a better product(no client-server feature),
this will save you time and money; and save us the work to mantain another
release and made this new release better."
Additionally was a rumor about "Killing developer/2000", but that ended, and it
was not my impression that some people try to say that( you can read the thread
some oracle people made understand that).

You too says "If you used it as client server, and you want to continue using
it, with really new feature , you must pay 5,000U$; otherwise use developer 6i
only maintenance (like keep with "the old things" a punishment for those who
like to be uptodate with latest release)"

There is people who still want to be using client-server with new features,there
is other that want new feature, but can't pay it. Other that want and can pay
it.
All that is the usual,and is unevitable, You're right about taking that
decision,

but backward Oracle (intentionally or intentionally)
had created the image that developer is not as good product (tried to kill it)
There is no freeruntime (when this still exists for delphi, java, etc. etc.)
Oracle can change the rules suddenly, from one moment to other.
etc.

honestly there several people that would like to be using client server, for
some reasons like
- Java is excellent, but is still heavy.
- JDeveloper requires still too resources.
- There is no time to learn new technologies.
- There is no $$ to buy IAS

This is not "We don't like ias."
This is like "
Thank Oracle ofr you product. (They are happy with Oracle)
We work hard and don't have time, why you try to force something we are not
prepared to (even when this is unevitable).
Why is as difficult to get a definitive position about licensing, because (if
you read the thread) it´s not clear about new policies.
You can do what you want but we would had wished you would had keep the
client-server for some release more, or give a free runtime :)
"

This sounds like a wish more than a complaint.

In few word we want free runtime!!!.
:) I was only expressing a opinion shared with other people about a free
runtime, a wrong image against developer (like a product to be deprecated) and a
need for clear license policies.

Thanks Tom.

December 03, 2003 -- Thanks for the question regarding "File
      Formats Supported by interMedia", version 8.1.7

      You Asked
Tom,

I've searched the Oracle and OTN sites high and low, but cannot find a list of
the file formats supported by interMedia.  There are several references that
claim it support "over 150" file formats, but I can't find a list.  I'm
interested in both text, audio, image and video formats.

Can you please provide the list or point me to a reference?

Thanks!!

    Doug T

      and we said...
They are documented in the interMedia guides.

For audio, video, and image:

http://otn.oracle.com/doc/oracle8i_816/inter.816/a67299/toc.htm
Appendixes a,b,c

for text:

http://technet.oracle.com/doc/oracle8i_816/inter.816/a77063/toc.htm
Appendix c.

Review &
      Followup

      Rating: 1
Dead
      Links  December 03, 2003
Reviewer:
      A reader
The links in the response are dead.

          Followup:

teach a man to fish....

otn.oracle.com -> documentation -> 9iR2 database -> list all books -> a fountain
of knowledge.

December 03, 2003 Thiru -- Thanks for the question regarding
      "Replacing Multiple Blanks by single blanks in text ", version 8.1.7.4

      You Asked
Hi Tom,

I was waiting to ask this question for last two days, now only i got this far.
So here is my question.
We have column called prmd_title varchar2(512).
It is possible we will get a title with multiple space in between the words.
For example,

PRMD_TITLE
==========
Archivos de   biologia      y medicina experimentales
Archivos de     Psiquiatria
Archivos de pediatria    del Uruguay
Archivos dominicanos de pediatria
Archivos espanoles   de urologia
Archivos latinoame   ricanos de nutricion

But in some queries we need to remove those multiple blanks space to single
blank space.

We tried following.
1) create a function that goes through the string removes it. here is the
function
CREATE OR REPLACE FUNCTION PQDR2SPC
 (PI_INSTR IN VARCHAR2
 )
 RETURN VARCHAR2
 IS
IN_STR varchar2(2000);
OUT_STR varchar2(2000);
BEGIN
   IN_STR := pi_inStr;
  WHILE instr(IN_STR,'  ',1) > 0 LOOP
    OUT_STR := replace(IN_STR,'  ',' ');
    IN_STR  := OUT_STR;
  END LOOP;
  RETURN(IN_STR);
END PQDR2SPC

This takes longtime and affects our performance.

2. We used following to replace, but limitation as you can see it will remove
only maximum of five blank space.

select
replace(replace(replace(replace(replace(
upper(decode(prmd_overwrite_pm_name,null,prmd_original_pm_name,
prmd_overwrite_pm_name)),
'      ',' '),'     ',' '),'    ',' '),'   ',' '),'  ',' ')
from ttl_print_medias

I created the function based index now its performance is ok.But this will have
problem if we have six or more space continously.
Is there any generic way to do this??

Thanks for your response.

-Thiru

      and we said...
well, it'll actually do way way more then 5!

ops$tkyte@ORA920> select
  2  replace(replace(replace(replace(replace(data,
  3  '      ',' '),'     ',' '),'    ',' '),'   ',' '),'  ',' '),
  4  length(data)
  5  from (select 'x             y' data from dual )
  6  /

REP LENGTH(DATA)
--- ------------
x y           15

the way you coded it -- turns all 5 blanks into 1.  Then, that replaced string
is used to turn 4 into 1 and so on.

ops$tkyte@ORA920> select
  2  replace(replace(replace(replace(replace(data,
  3  '      ',' '),'     ',' '),'    ',' '),'   ',' '),'  ',' '),
  4  length(data)
  5  from (select 'x                                                   y' data
from dual )
  6  /

REP LENGTH(DATA)
--- ------------
x y           53

it'll actually get upto 210 BLANKS!!!  add one more replace of 6 to 1 blank and
you'll get 1230 blanks!

I brute forced the "proof":

drop table t;

create table t ( data varchar2(4000) );

begin
    for i in 1 .. 3998
    loop
        insert into t values ( 'x' || rpad(' ',i,' ') || 'y' );
    end loop;
end;
/
column data2 format a5

select data2, l_data,
       decode( lag(l_data) over (order by l_data), l_data-1, null, '<<<====' )
lag_l_data
  from (
select
replace( replace(replace(replace(replace(replace(data,
'      ',' '),'      ',' '),'     ',' '),'    ',' '),'   ',' '),'  ',' ') data2,
length(data) l_data
from t
)
where length(data2) = 3
/

just look for the first <<<====== to see where it stops processing...
Review &
      Followup

      Rating: 3
another
      way  November 24, 2003
Reviewer:  Paul Moen
       from Sydney Australia
what about OWA_PATTERN.CHANGE
This package allows for regex (regular expressions) in Oracle 9i, don't know
about 8i
not as powerful as perl but probably enough to do this job.

CREATE OR REPLACE FUNCTION PQDR2SPC
 (PI_INSTR IN VARCHAR2
 )
 RETURN VARCHAR2
 IS
IN_STR varchar2(2000);
OUT_STR varchar2(2000);
BEGIN
   IN_STR := pi_inStr;
   OWA_PATTERN.CHANGE(IN_STR,'\s{2,}',' ','g');
   RETURN(IN_STR);
END PQDR2SPC

\s{2,} will replace any 2 or more whitespace chars with one whitespace.
This will work for LOBS,CLOBS as well.

There is an article about regex in 10G on oramag...

Have Fun

          Followup:

it suffers from the same issue the person asking the question wanted to avoid in
the first place!  they had a fairly optimized plsql function already -- and
deemed it "too slow"

      Rating: 3
to Paul:
      owa_pattern is slow  November 25, 2003
Reviewer:
      msc
Hi Paul,

owa_pattern would have seriuos impact on performance - it is pure PLSQL and very
complex one also. You better use your own replacement function if you do not
like replace()-s.

But using several replace()-s in SQL will beat any PLSQL function anyway (create
view if you need to hide complexity).

      Rating: 4
Dividing a
      string into multiple parts  November 25, 2003

Reviewer:  Arvind  from India
Dear Tom,

I have problem with spaces. I have column of length 200 and I am dividing into
four columns. All column types are of varchar2. Some time later on it is also
required to concatenate the string back. I am facing a problem is that after
dividing the string i last character happens to be a blank then that blank goes
of and while concatinating the two words which were separted by a blank, just
conctenates.

          Followup:

give me an "example", not understanding the issue here.

      Rating:
      4
Thanks.  November 25, 2003
Reviewer:
      Thiru Pandian  from USA
THanks Tom. You are always right. I don't think i need to revisit that problem
as it can remove 210 blank spaces. I have one more for you,hopefully i am not
asking too much.. just follow your advice "if you can do it sql, try to do it in
sql" -:).

We form URL based on Author and title and it is possible these attributes will
have few special characters.So before we give this URL to web we convert all
special character to hexa character. Right now we do this by calling PL/SQL
function. Do think is there any better way than this?

Here is our PL/SQL Function.
hexcharacterstring VARCHAR2(100) := '% ;/?:@&=+$,<>#{}|\^[]`"';
 FUNCTION convert_type (pi_in_string    IN VARCHAR2 ,
                          pi_convert_type IN VARCHAR2 ,
                          pi_debug_flag   IN NUMBER)
   RETURN VARCHAR2 IS

   lv_temp_string     VARCHAR2(3000);
   po_out_string      VARCHAR2(3000);
   lv_message_grp_id  VARCHAR2(15);
   lv_function_name   VARCHAR2(30);
BEGIN
     lv_temp_string := pi_in_string;
     FOR LoopPos IN 1..LENGTH(hexcharacterstring)
       LOOP
           hex_table(LoopPos) := substr(hexcharacterstring,LoopPos, 1);
           SELECT REPLACE(lv_temp_string, hex_table(LoopPos) ,lv_reserve_char
                  ||  rawtohex(hex_table(LoopPos)))
           INTO po_out_string FROM dual;
           lv_temp_string := po_out_string;
     END LOOP;
END;

Thanks.
-Thiru.

          Followup:

create or replace function urlencode( p_str in varchar2 ) return varchar2
as
    l_tmp   varchar2(12000);
    l_len   number default length(p_str);
    l_bad   varchar2(100) default ' >%}\~];?@&<#{|^[`/:=$+''"' || chr(10);
    l_char  char(1);
begin
    if ( p_str is NULL ) then
        return NULL;
    end if;

    for i in 1 .. l_len loop
        l_char :=  substr(p_str,i,1);
        if ( instr( l_bad, l_char ) > 0 )
        then
            l_tmp := l_tmp || '%' || to_char(ascii(l_char), 'fm0X');
        else
            l_tmp := l_tmp || l_char;
        end if;
    end loop;

    return l_tmp;
end;
/

is my version (don't select replace -- just assign replace -- in the future!)

      Rating:
      5
Thanks.  November 25, 2003
Reviewer:
      Thiru Pandian  from USA
Thanks Tom. It was helpful.
-Thiru.

      Rating: 5
Another way of
      doing the same?  November 25, 2003
Reviewer:
      A reader  from Detroit, USA
declare
  function urlencode(p_str in varchar2) return varchar2 as
    l_char   varchar2(1);
    type     t_repl is table of varchar2(10) index by l_char%type;
    a_repl   t_repl;
    l_outstr varchar2(4000) := '';
    l_len    number := length(p_str);
    l_xlate    varchar2(100) := ' >%}\~];?@&<#{|^[`/:=$+''"' || chr(10);
begin
    if p_str is null then
      return null;
    end if;
    for i in 1 .. length(l_xlate) loop
      a_repl(substr(l_xlate,i,1)) := '%'||to_char(ascii(substr(l_xlate,i,1)),
'fm0X');
    end loop;
    for i in 1 .. length(p_str) loop
      l_char := substr(p_str,i,1);
      if a_repl.exists(l_char) then
         l_outstr := l_outstr || a_repl(l_char);
      else
         l_outstr := l_outstr || l_char;
      end if;
    end loop;
    return l_outstr;
end;
begin
  dbms_output.put_line(urlencode('my simon.com/~uuu'));
end;

          Followup:

there are an infinite number of ways to code this, yes.

      Rating: 4
Dividing a
      string into multiple parts   November 26, 2003

Reviewer:  Arvind  from India
The problem which I have narrated is given below. The example is

I have a columj description of varchar2(100). The data in that column is given
below

VLV.BALL, SHEET 54313 2.0 INCH

now I have to divide this decription in four columns as given below
DESC_1                                VARCHAR2(22)
DESC_2                                VARCHAR2(31)
DESC_3                                VARCHAR2(10)
DESC_4                                VARCHAR2(37)

If I didvide string in four parts then it takes string
desc_1 = "VLV.BALL, SHEET 54313 "
desc_2 = "2.0 INCH"
desc_3 = null
desc_4 = null

when I concatenate above four string then the result appears to be

VLV.BALL, SHEET 543132.0 INCH

Here the blank space beween 54313 and 2.0 goes off.

this is the wrong result.

I use the statement desc_1||desc_2||desc_3||desc_4 for concatenation.

This problem happens only when after doing substring if the substring has last
or first character as blank.

Please tell me the solution for the problem.

Thanks
Regards

November 25, 2003
Reviewer:  Arvind  from India

Dear Tom,

I have problem with spaces. I have column of length 200 and I am dividing into
four columns. All column types are of varchar2. Some time later on it is also
required to concatenate the string back. I am facing a problem is that after
dividing the string i last character happens to be a blank then that blank goes
of and while concatinating the two words which were separted by a blank, just
conctenates.

Followup:
give me an "example", not understanding the issue here.

          Followup:

please give me the full example -- you mention substr, but I don't see substr
anywhere in there!

something like this:

ops$tkyte@ORA920> select desc_1, desc_2, desc_1||desc_2
  2    from (
  3  select substr(data,1,22) desc_1,
  4         substr(data,23,31) desc_2,
  5             substr(data,54,10) desc_3,
  6             substr(data,64,37) desc_4
  7    from (
  8  select 'VLV.BALL, SHEET 54313 2.0 INCH' data from dual
  9         )
 10         )
 11  /

DESC_1                 DESC_2   DESC_1||DESC_2
---------------------- -------- ------------------------------
VLV.BALL, SHEET 54313  2.0 INCH VLV.BALL, SHEET 54313 2.0 INCH

(which shows I don't see what you say I should be seeing!)

      Rating: 2
replace mulit
      blanks in single using 3 "replace"  November 27, 2003

Reviewer:  Reeds Lau  from Hong Kong
Hi,

Pls try this :
Note : using '@' is just an example, you may choose any one
from the code page -- chr(n) instead.

select
 replace(
  replace (
    replace('&1',' ',' @'),'@ ',''),' @',' ')
from dual;

09:47:03 DB:ORCL>select
09:47:05   2   replace(
09:47:05   3    replace (
09:47:05   4      replace('&1',' ',' @'),'@ ',''),' @',' ')
09:47:05   5  from dual;
Enter value for 1:    REPLACE      MULTIPLE    BLANKS IN   SINGLE

REPLACE(REPLACE(REPLACE('REPLACEMUL
-----------------------------------
 REPLACE MULTIPLE BLANKS IN SINGLE

Rgs,
Reeds

          Followup:

neat approach, as long as @ is not in your string (you must pick a "safe"
character)

ops$tkyte@ORA920> select
  2      replace('how         now          brown       cow',
  3       ' ',' @') from dual;

REPLACE('HOWNOWBROWNCOW','','@')
------------------------------------------------------------------
how @ @ @ @ @ @ @ @ @now @ @ @ @ @ @ @ @ @ @brown @ @ @ @ @ @ @cow

ops$tkyte@ORA920>
ops$tkyte@ORA920> select
  2    replace (
  3      replace('how         now          brown       cow',
  4       ' ',' @'),'@ ','') from dual;

REPLACE(REPLACE('HOW
--------------------
how @now @brown @cow

ops$tkyte@ORA920>
ops$tkyte@ORA920> select
  2   replace(
  3    replace (
  4      replace('how         now          brown       cow',
  5       ' ',' @'),'@ ',''),' @',' ')
  6  from dual;

REPLACE(REPLACE(R
-----------------
how now brown cow

ops$tkyte@ORA920>

      Rating: 4
Dividing a
      String into Multiple parts  November 27, 2003

Reviewer:  Arvind  from India
The example is shown below

select item_desc from item_detail

ITEM_DESC
----------------------------------------------------------------------------
VLV.BALL, SHEET 54313 2.0 INCH
VLV.BALL, SHEET 54413 1.0 INCH
VLV.BALL, SHEET 54413 3.0 INCH
VLV.BALL, SHEET 54413 6.0 INCH

desc items
IT_DESC_1                                VARCHAR2(22)
IT_DESC_2                                VARCHAR2(31)
IT_DESC_3                                VARCHAR2(10)
IT_DESC_4                                VARCHAR2(37)

insert into items(it_desc_1, it_desc_2, it_desc_3, it_desc_4)
(select SUBSTR(ITEM_DESC,1,22), SUBSTR(ITEM_DESC,23,31),
SUBSTR(ITEM_DESC,54,10),
        SUBSTR(ITEM_DESC,64,37) from item_detail)

select it_desc_1, it_desc_2, it_desc_1||it_desc_2 from items

IT_DESC_1              IT_DESC_2                       IT_DESC_1||IT_DESC_2
---------------------- -------------------------------
-------------------------------------
VLV.BALL, SHEET 54313  2.0 INCH                        VLV.BALL, SHEET 543132.0
INCH

VLV.BALL, SHEET 54413  1.0 INCH                        VLV.BALL, SHEET 544131.0
INCH

VLV.BALL, SHEET 54413  3.0 INCH                        VLV.BALL, SHEET 544133.0
INCH

VLV.BALL, SHEET 54413  6.0 INCH                        VLV.BALL, SHEET 544136.0
INCH

The problem which I have narrated is given below. The example is

I have a column description of varchar2(100). The data in that column is given
below

VLV.BALL, SHEET 54313 2.0 INCH

now I have to divide this decription in four columns as given below
DESC_1                                VARCHAR2(22)
DESC_2                                VARCHAR2(31)
DESC_3                                VARCHAR2(10)
DESC_4                                VARCHAR2(37)

If I didvide string in four parts then it takes string
desc_1 = "VLV.BALL, SHEET 54313 "
desc_2 = "2.0 INCH"
desc_3 = null
desc_4 = null

when I concatenate above four string then the result appears to be

VLV.BALL, SHEET 543132.0 INCH

Here the blank space beween 54313 and 2.0 goes off.

this is the wrong result.

I use the statement desc_1||desc_2||desc_3||desc_4 for concatenation.

This problem happens only when after doing substring if the substring has last
or first character as blank.

Please tell me the solution for the problem.

Thanks
Regards

ops$tkyte@ORA920> select desc_1, desc_2, desc_1||desc_2
  2    from (
  3  select substr(data,1,22) desc_1,
  4         substr(data,23,31) desc_2,
  5             substr(data,54,10) desc_3,
  6             substr(data,64,37) desc_4
  7    from (
  8  select 'VLV.BALL, SHEET 54313 2.0 INCH' data from dual
  9         )
 10         )
 11  /

DESC_1                 DESC_2   DESC_1||DESC_2
---------------------- -------- ------------------------------
VLV.BALL, SHEET 54313  2.0 INCH VLV.BALL, SHEET 54313 2.0 INCH

(which shows I don't see what you say I should be seeing!)

          Followup:

so, like I've said -- please give me an entire test case to reproduce with.  You
must be doing SOMETHING DIFFERENT from what you describe.  A simple, small,
concise YET 100% COMPLETE sqlplus script!!

Like this, that shows what you describe does not result in the output you are
observing (so there is something else afoot here!!)

ops$tkyte@ORA920> create table item_detail ( item_desc varchar2(255) );

Table created.

ops$tkyte@ORA920> create table items ( it_desc_1 varchar2(22), it_desc_2
varchar2(31), it_desc_3 varchar2(10), it_desc_4
  2  varchar2(37) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into item_detail values ( 'VLV.BALL, SHEET 54313 2.0
INCH' );

1 row created.

ops$tkyte@ORA920> insert into item_detail values ( 'VLV.BALL, SHEET 54413 1.0
INCH' );

1 row created.

ops$tkyte@ORA920> insert into item_detail values ( 'VLV.BALL, SHEET 54413 3.0
INCH' );

1 row created.

ops$tkyte@ORA920> insert into item_detail values ( 'VLV.BALL, SHEET 54413 6.0
INCH' );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into items(it_desc_1, it_desc_2, it_desc_3, it_desc_4)
  2  (select SUBSTR(ITEM_DESC,1,22), SUBSTR(ITEM_DESC,23,31),
  3  SUBSTR(ITEM_DESC,54,10),
  4          SUBSTR(ITEM_DESC,64,37) from item_detail)
  5  /

4 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select it_desc_1, it_desc_2, it_desc_1||it_desc_2 from items ;

IT_DESC_1              IT_DESC_2
---------------------- -------------------------------
IT_DESC_1||IT_DESC_2
-----------------------------------------------------
VLV.BALL, SHEET 54313  2.0 INCH
VLV.BALL, SHEET 54313 2.0 INCH

VLV.BALL, SHEET 54413  1.0 INCH
VLV.BALL, SHEET 54413 1.0 INCH

VLV.BALL, SHEET 54413  3.0 INCH
VLV.BALL, SHEET 54413 3.0 INCH

VLV.BALL, SHEET 54413  6.0 INCH
VLV.BALL, SHEET 54413 6.0 INCH

I've done this in 716, 8174, 9203 -- each with identical results.

      Rating: 4
Dividing String
      The example is shown below  November 27, 2003

Reviewer:  Arvind  from India

Please ignore previous one. This gives the complete detail of the problem.

This problem is happening in this style.

I have a form in D2K 5.0

I am selecting the string of length Varchar2(100) from item_detail thru cursor
and putting it in database block. Afer saving the data the blanl gets removed.

Thanks

The example is shown below

select item_desc from item_detail

ITEM_DESC
----------------------------------------------------------------------------
VLV.BALL, SHEET 54313 2.0 INCH
VLV.BALL, SHEET 54413 1.0 INCH
VLV.BALL, SHEET 54413 3.0 INCH
VLV.BALL, SHEET 54413 6.0 INCH

desc items
IT_DESC_1                                VARCHAR2(22)
IT_DESC_2                                VARCHAR2(31)
IT_DESC_3                                VARCHAR2(10)
IT_DESC_4                                VARCHAR2(37)

insert into items(it_desc_1, it_desc_2, it_desc_3, it_desc_4)
(select SUBSTR(ITEM_DESC,1,22), SUBSTR(ITEM_DESC,23,31),
SUBSTR(ITEM_DESC,54,10),
        SUBSTR(ITEM_DESC,64,37) from item_detail)

select it_desc_1, it_desc_2, it_desc_1||it_desc_2 from items

IT_DESC_1              IT_DESC_2                       IT_DESC_1||IT_DESC_2
---------------------- -------------------------------
-------------------------------------
VLV.BALL, SHEET 54313  2.0 INCH                        VLV.BALL, SHEET 543132.0
INCH

VLV.BALL, SHEET 54413  1.0 INCH                        VLV.BALL, SHEET 544131.0
INCH

VLV.BALL, SHEET 54413  3.0 INCH                        VLV.BALL, SHEET 544133.0
INCH

VLV.BALL, SHEET 54413  6.0 INCH                        VLV.BALL, SHEET 544136.0
INCH

The problem which I have narrated is given below. The example is

I have a column description of varchar2(100). The data in that column is given
below

VLV.BALL, SHEET 54313 2.0 INCH

now I have to divide this decription in four columns as given below
DESC_1                                VARCHAR2(22)
DESC_2                                VARCHAR2(31)
DESC_3                                VARCHAR2(10)
DESC_4                                VARCHAR2(37)

If I didvide string in four parts then it takes string
desc_1 = "VLV.BALL, SHEET 54313 "
desc_2 = "2.0 INCH"
desc_3 = null
desc_4 = null

when I concatenate above four string then the result appears to be

VLV.BALL, SHEET 543132.0 INCH

Here the blank space beween 54313 and 2.0 goes off.

this is the wrong result.

I use the statement desc_1||desc_2||desc_3||desc_4 for concatenation.

This problem happens only when after doing substring if the substring has last
or first character as blank.

Please tell me the solution for the problem.

Thanks
Regards

ops$tkyte@ORA920> select desc_1, desc_2, desc_1||desc_2
  2    from (
  3  select substr(data,1,22) desc_1,
  4         substr(data,23,31) desc_2,
  5             substr(data,54,10) desc_3,
  6             substr(data,64,37) desc_4
  7    from (
  8  select 'VLV.BALL, SHEET 54313 2.0 INCH' data from dual
  9         )
 10         )
 11  /

DESC_1                 DESC_2   DESC_1||DESC_2
---------------------- -------- ------------------------------
VLV.BALL, SHEET 54313  2.0 INCH VLV.BALL, SHEET 54313 2.0 INCH

(which shows I don't see what you say I should be seeing!)

          Followup:

if you want a fixed width field -- you can use CHAR in the database (preserves
trailing blanks, forms is doing blank trimming)

or, you can use RPAD in your concatenate

rpad( desc_1, 22 ) || rpad( desc_2, 31 ) .....

      Rating: 3
begs
      recursion  December 02, 2003
Reviewer:
      A reader  from Philadelphia, PA USA
The replacement of multiple blanks or other character begs for a recursive
solution (see below). Be very careful when writing these types of functions ;-)

DECLARE
    n integer;
    myCHAR char(1) := 'aa'; --run with various values here
--I'll name this foo and let you come up with your own name
function foo(str in varchar2, search_str in varchar2)
return varchar2
as
    myConstant  constant integer  := 255;  --made this a constant to make
testing easier/safer; adjust to need
begin
    if str is null then  --just get this out of the way
        return null;
    elsif instr(str, search_str || search_str) = 0 then  --we have no repeating
values of search_str
        return str;
    --the below is here for efficiency only; remove if not doing very long
strings of repeating characters
    elsif instr(str, rpad(search_str, myConstant, search_str)) > 0 then
        return foo(replace(str, rpad(search_str, myConstant, search_str) ,
search_str), search_str);
    else --replace search_str || search_str with search_str and try again
recursively
        return foo(replace(str, search_str || search_str, search_str),
search_str);
    end if;
end;

begin
 dbms_output.put_line('---begin---');
 for n in 1..2000 loop  --loop to your heart's delight
    if foo(rpad(myChar, n, myChar) , myChar) = myChar or myChar is null then
        null;
    else
        dbms_output.put_line('#' || foo(rpad(myChar, n, myChar) , myChar) ||
'#');
    end if;
 end loop;
 --test null
 dbms_output.put_line(nvl(foo(null , myChar), 'NULL'));
 dbms_output.put_line('---end---');
end;

Of course,we could make the above more robust, but I'll leave that to individual
needs.

          Followup:

I don't know, I found this solution:

select
 replace(
  replace (
    replace('&1',' ',' @'),'@ ',''),' @',' ')
from dual;

to be really "elegant" (not to mention terse, fast, and 99.9999% complete --
that '@' part isn't too much of a problem, normally, chr(0) will fill the bill
as a "safe character".

but a shorter "iterative" solution is

ops$tkyte@ORA920> create or replace function multi_replace( p_str in varchar2 )
return varchar2
  2  is
  3          l_str long := p_str;
  4          l_ret long;
  5  begin
  6          if ( l_str is null ) then return null; end if;
  7
  8          loop
  9                  l_ret := replace( l_str, '  ', ' ' );
 10                  exit when (l_ret = l_str);
 11                  l_str := l_ret;
 12          end loop;
 13          return l_ret;
 14  end;
 15  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select multi_replace
  2  ( 'how        now       brown        cow' ),
  3         replace( replace( replace( 'how        now       brown        cow',
' ', ' @'), '@ ', ''),' @', ' ')
  4    from dual;

MULTI_REPLACE('HOWNOWBROWNCOW')
---------------------------------------------------------------------------------
--------------------------------------------------
REPLACE(REPLACE(R
-----------------
how now brown cow
how now brown cow

but I would really want to use the replace*3:

select multi_replace
( 'how        now       brown        cow' )
from t

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.03          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     2110      1.70       2.15         49       2161          0       31630
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2112      1.70       2.19         49       2161          0       31630

select
       replace( replace( replace( 'how        now       brown        cow', ' ',
' @'), '@ ', ''),' @', ' ')
  from t

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.02          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     2110      0.28       0.27          0       2161          0       31630
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2112      0.28       0.30          0       2161          0       31630

      Rating: 5
The
      three-replace solution is really nifty !  December 02, 2003

Reviewer:  Alan  from Washington DC
The three-replace solution is really nifty !

      Rating: 5
You can
      actually use any string instead of a single char  December
      03, 2003
Reviewer:  Dirk  from Belgium
> neat approach, as long as @ is not in your string (you must pick a "safe"
character)
SQL> select
  2   replace(
  3    replace (
  4      replace('&1',' ',' @#§'),'@#§ ',''),' @#§',' ')
  5  from dual;
Voer waarde voor 1 in: @how         now#          brown§       cow§#@
oud   4:     replace('&1',' ',' @#§'),'@#§ ',''),' @#§',' ')
nieuw   4:     replace('@how         now#          brown§       cow§#@',' ','
@#§'),'@#§ ',''),' @#§',' ')
>>>

REPLACE(REPLACE(REPLACE
-----------------------
@how now# brown§ cow§#@

SQL>

          Followup:

still, gotta pick a string then :)

I don't think this is a showstopper problem at all -- i really liked the
solution totally.  very neat.

      Rating: 5
Another
      variation  December 05, 2003
Reviewer:
      Kannan  from NJ, USA
I came up with this function to replace repeated contiguous characters. I am
using dynamic sql because of the use of analytic function and the oracle version
is 8i.
FUNCTION Replace_Repeated_Char (
    pi2_InputString        IN    VARCHAR2
) RETURN VARCHAR2 IS
    TYPE refcur IS REF CURSOR;
    vl_rc            refcur;
    vl2_sql            VARCHAR2(2000);
    vl2_final_string    VARCHAR2(4000);
    vln_rnum        PLS_INTEGER;
    vl2_pattern        VARCHAR2(4000);
    vl2_single_char        VARCHAR2(4000);

BEGIN

    vl2_final_string := pi2_InputString;
    vl2_sql := '
        SELECT rnum, pattern, single_char
        FROM
        (
            SELECT rnum, single_char,
            DECODE( LEAD(single_char) OVER( ORDER BY rnum ), single_char,
single_char || single_char, NULL ) pattern
            FROM (
                 SELECT rnum, SUBSTR(:vl2_final_string, rnum, 1) single_char
                 FROM (
                       SELECT rownum rnum
                      FROM ALL_OBJECTS
                      WHERE rownum < LENGTH(:vl2_final_string ) + 1
                  )
                  ORDER BY rnum
            )
            ORDER BY rnum
        )
        WHERE pattern IS NOT NULL
        ORDER BY rnum ';

    OPEN vl_rc FOR vl2_sql USING vl2_final_string, vl2_final_string;
    LOOP
        FETCH vl_rc INTO vln_rnum, vl2_pattern, vl2_single_char;
        EXIT WHEN vl_rc%NOTFOUND;
        vl2_final_string := REPLACE(vl2_final_string, vl2_pattern,
vl2_single_char );
    END LOOP;
    CLOSE vl_rc;
    RETURN vl2_final_string;
END Replace_Repeated_Char;
/

SELECT Replace_Repeated_Char ( 'NNNNEW    YO   RRRKKKK' ) from dual;

Result:
NEW YO RK

December 03, 2003 wallacel -- Thanks for the question regarding
      "tkprof report", version 8.1.6

      You Asked
Tom:

what cause parse >1 in tkprof report?
i think it should always be 1 at most of circumstance.
if the statement run more than once, and the parse tree is aged
out, i think
this is the only situation when parse >1 , am i right?

BTW, can you tell me some document to help me understand
the meaning of report.txt?

      and we said...
Parse will be greater then 1 when the application parses the
query more then once.  The parses might be HARD or SOFT.  For
example, say you have a script:

alter session set sql_trace=true;

begin
    for i in 1 .. 2 loop
        for x in ( select /*+ QUERY 1 */ * from dual )
        loop
            null;
        end loop;
    end loop;
end;
/

declare
    type rc is ref cursor;
    C rc;
begin
    for i in 1 .. 2 loop
        open C for select /*+ QUERY 2 */ * from dual;
        close C;
    end loop;
end;
/

The tkprof will tell us:

SELECT /*+ QUERY 1  */*
FROM
 DUAL

call     count       cpu    elapsed       disk      query
------- ------  -------- ---------- ---------- ----------
Parse        1      0.00       0.00          0          0
Execute      2      0.00       0.00          0          0
Fetch        4      0.00       0.00          0          2
------- ------  -------- ---------- ---------- ----------
total        7      0.00       0.00          0          2

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 334     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      2  TABLE ACCESS FULL DUAL

********************************************************

That shows us the query was parsed ONCE and executed 2 times.
PLSQL cached the cursor.  the 'misses in library cache during
parse' also shows us this was a HARD parse -- it wasn't found in
the shared pool...

SELECT /*+ QUERY 2  */*
FROM
 DUAL

call     count       cpu    elapsed       disk      query
------- ------  -------- ---------- ---------- ----------
Parse        2      0.00       0.00          0          0
Execute      2      0.00       0.00          0          0
Fetch        0      0.00       0.00          0          0
------- ------  -------- ---------- ---------- ----------
total        4      0.00       0.00          0          0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 334     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS FULL DUAL

This shows us the query was parsed two times -- it was, we
opened it with a ref cursor and plsql cannot cache that cursor
for us.  It also shows we had 1 HARD parse and (therefore) 1
soft parse -- the second parse was done by finding the already
existing plan in the library cache -- the shared pool.

OPTIMALLY -- you will see parse =1 but it is not unusual to see
parse = execute = N.  These programs need to be looked at to see
if they are using cursors in the most efficient method.

see

http://technet.oracle.com/doc/oracle8i_816/server.816/a76992/ch14_str.htm#1018
for interpreting tkprof reports..
Review &
      Followup

      Rating: 4
Misses in
      library cache  March 14, 2002
Reviewer:
      Edgars  from Riga, Latvia
Hello Tom!

Does miss in library cache means that SQL was not found in shared pool, when I
have misses during parse = 2 or even = parse count does this means that my
shared pool size is sized incorrectly? What is the difference between misses in
library cache during parse and during execute? Thank you!

          Followup:

Miss in the library cache means we did not find it there.  It does not have to
be due to a too small shared pool  -- consider:

drop table t;
create table t ( x int );
insert into t select 1 from dual;
alter system flush shared_pool;
alter session set sql_trace=true;
select * from t;
alter session set optimizer_goal=first_rows;
select * from t;

will result in this:

select *
from
 t

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.02       0.00          0          0          1           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        4      0.00       0.00          0          2          8           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        8      0.02       0.00          0          2          9           2

Misses in library cache during parse: 2
....

There needs to be TWO query plans for that one -- one with CHOOSE and one with
FIRST_ROWS optimization.  Environmental changes (change your sort_area_size,
current_schema, etc etc etc) will affect this as well.

Give me an example of the second, what report are you refering to.

      Rating: 4
Misses in
      library cache   March 14, 2002
Reviewer:
      Edgars  from Riga, Latvia
Hello Tom!

But in my case there is no changes in environment. I'm tracing one session of
application that we are developing and in tkprof report I see something like
this:

select              id as grouping_id,
       longdescription as caption1,
      shortdescription as caption2
from groupingtype

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          3          0           3
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          3          0           3

Misses in library cache during parse: 2
Optimizer goal: CHOOSE
Parsing user id: 24  (MAP)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'GROUPINGTYPE'

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                       2        0.00          0.00
  SQL*Net message from client                     2        0.00          0.00
********************************************************************************

why does this statement is not cached in shared pool, this table is with CACHE
option, could this be the case?

About the second case I think I get it, it was because we were using execute
immediate with statement that was not executed before.

          Followup:

No, it would not have anything to do with the CACHE attribute of the table.

If the environment did not change (you can edit the .trc file and look for any
ALTER statements you might not be aware of) then it would appear that this
statement was flushed.  What is the size of your shared pool and how many
statements were executed between the two executions?

      Rating: 4
Misses in
      library cache  March 14, 2002
Reviewer:
      Edgars  from Riga, Latvia
Hello Tom!

I have specialy restarted a database, disconnected all users to test this again,
and I get the same result. There is no statements between two executions, here
is cut from trace file:

=====================
PARSING IN CURSOR #1 len=130 dep=0 uid=24 oct=3 lid=24 tim=1016127667479630
hv=276055554 ad='60c421a4'
select              id as grouping_id,
       longdescription as caption1,
      shortdescription as caption2
from groupingtype
END OF STMT
PARSE
#1:c=50000,e=46280,p=4,cr=62,cu=0,mis=1,r=0,dep=0,og=4,tim=1016127667479605
WAIT #1: nam='SQL*Net message to client' ela= 5 p1=1952673792 p2=1 p3=0
WAIT #1: nam='SQL*Net message from client' ela= 2742 p1=1952673792 p2=1 p3=0
WAIT #1: nam='SQL*Net message to client' ela= 2 p1=1952673792 p2=1 p3=0
WAIT #1: nam='SQL*Net message from client' ela= 2837 p1=1952673792 p2=1 p3=0
=====================
PARSING IN CURSOR #1 len=130 dep=0 uid=24 oct=3 lid=24 tim=1016127667487207
hv=276055554 ad='60c421a4'
select              id as grouping_id,
       longdescription as caption1,
      shortdescription as caption2
from groupingtype
END OF STMT
PARSE #1:c=0,e=1400,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=4,tim=1016127667487171
WAIT #1: nam='SQL*Net message to client' ela= 7 p1=1952673792 p2=1 p3=0
WAIT #1: nam='SQL*Net message from client' ela= 11746 p1=1952673792 p2=1 p3=0
BINDS #1:
EXEC #1:c=0,e=94,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=1016127667499629
WAIT #1: nam='SQL*Net message to client' ela= 3 p1=1952673792 p2=1 p3=0
WAIT #1: nam='SQL*Net message from client' ela= 2821 p1=1952673792 p2=1 p3=0
WAIT #1: nam='db file sequential read' ela= 6245 p1=4 p2=1473 p3=1
WAIT #1: nam='db file sequential read' ela= 46 p1=4 p2=1474 p3=1
WAIT #1: nam='SQL*Net message to client' ela= 5 p1=1952673792 p2=1 p3=0
FETCH #1:c=0,e=6737,p=2,cr=3,cu=0,mis=0,r=3,dep=0,og=4,tim=1016127667509430
WAIT #1: nam='SQL*Net message from client' ela= 18499 p1=1952673792 p2=1 p3=0
=====================

My shared pool size is shared_pool_size=268435456! Thanks Tom for greate site
and book, it's very useful!

          Followup:

When I put your trace into a trace file and tkprof it, I get:

select              id as grouping_id,
       longdescription as caption1,
      shortdescription as caption2
from groupingtype

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          2          3          0           3
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          2          3          0           3

Misses in library cache during parse: 1
Parsing user id: 24

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                       2        0.00          0.00
  SQL*Net message from client                     2        0.00          0.00

1 miss.  Can you EMAIL me the trace file (tkyte@oracle.com).  Compress it first.

      Rating: 4
High parse
      elapsed time  March 15, 2002
Reviewer:  Igor
       from France
Hi

Do you have idea how to improve soft parse elapsed time:

call     count       cpu    elapsed       disk
query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        3      0.80       1.72          0          0          0           0
Execute      3      0.00       0.00          0          0          0           0
Fetch        3      0.02       0.05          2        665          0          14
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        9      0.82       1.77          2        665          0          14

Misses in library cache during parse: 0

We can't do much to avoid soft parse changing client application (Siebel)

  Any hint?

                                         Igor

          Followup:

set the session_cached_cursors to some postive number.  We'll do even a "softer"
soft parse.

      Rating: 4
More On Session
      Cached Cursor Parameter.  March 16, 2002
Reviewer:
      A reader
Hi Tom,

Can u tell us more on the session cached cursor parameter.

Thanks.

Best Regards,
Ganesh R

          Followup:

search for session_cached_cursor on this site.

      Rating:
      4
  November 07, 2002
Reviewer:
      A reader
Hi Tom,

What does it exactly mean "Misses in library cache during execute: 1"?
This question is at the begining of this post but there is no answer for it.

Thanks

          Followup:

hard parse happened.  It was the first time the query was executed since the
database was up or the query had been aged out of the shared pool.

(and actually if you read the question, that was not the question -- but if you
read my original ANSWER -- i actually answered it anyhow!!!)

<quote>
...
That shows us the query was parsed ONCE and executed 2 times.  PLSQL cached
the cursor.  the 'misses in library cache during parse' also shows us this was a

HARD parse -- it wasn't found in the shared pool...
</quote>

In fact -- i answered this question a couple of times in this post!

      Rating: 4
Where is the
      plan and logical read ?  December 30, 2002

Reviewer:  Robert  from PA
Tom, is there something I have to set to get sql_trace to show the plan in
tkprof ?
I got no plan as you see below.
Also, *** which number shows logical read ? ****

thanks

select count(*) FROM LMSD46
WHERE Client = 'CCTI' AND <--snip-->

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.04       0.13          0          0          2           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        7      0.00     506.49          0          0          0          77
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        9      0.04     506.62          0          0          2          77

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 44
********************************************************************************

alter session set sql_trace=false

          Followup:

You didn't exit sqlplus or whatever application you used to run the query.

the STAT records are written when the cursor is closed -- plus and many other
things do not close the cursor until you exit.

also -- make sure the trace file doesn't have "truncated" at the end -- looks
like lots of stuff is missing there -- maybe you max dump file size is too
small.

query and current are logical IO's

      Rating: 4
Query
      Statistics in TKPROF report ?  December 30, 2002

Reviewer:  Robert  from pa
>> the STAT records are written when the cursor is closed --

You don't mean this STAT that you get with AUTOTRACE, do you ?
Is there a way to make SQL_TRACE output this ?

Statistics
----------------------------------------------------------
        191  recursive calls
         15  db block gets
         38  consistent gets
          3  physical reads
          0  redo size
       1809  bytes sent via SQL*Net to client
        358  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
         14  rows processed

          Followup:

No, I mean the STAT records that are in trace files, like this:

FETCH #1:c=0,e=3,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=0,tim=1016874557409886
WAIT #1: nam='SQL*Net message to client' ela= 5 p1=1650815232 p2=1 p3=0
WAIT #1: nam='SQL*Net message from client' ela= 490164 p1=1650815232 p2=1 p3=0
XCTEND rlbk=0, rd_only=1
STAT #1 id=1 cnt=1 pid=0 pos=1 obj=222 op='TABLE ACCESS FULL OBJ#(222) (cr=3 r=0
w=0 time=128 us)'

they are the records that are the actual query plan.

As for the stats -- recursive SQL shows up in the tkprof already, so do db block
gets, consistent gets and rows processed.

The others -- no.

      Rating: 4
How detect
      problem with soft parses  March 27, 2003
Reviewer:
      Juan Carlos Reyes P  from Bolivia
Hi, based in a previous question, why do you know you have problems with
softparses only looking at this statistics.

Thanks.

Hi

Do you have idea how to improve soft parse elapsed time:

call     count       cpu    elapsed       disk
query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        3      0.80       1.72          0          0          0           0
Execute      3      0.00       0.00          0          0          0           0
Fetch        3      0.02       0.05          2        665          0          14
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        9      0.82       1.77          2        665          0          14

Misses in library cache during parse: 0

          Followup:

parse = execute.

In a well tuned application

parse = 1
execute = some number greater than or equal to 1

The best way to speed something up is to NOT do it.  Hence, don't parse 3 times,
just parse 1 time.

      Rating: 5
:)
      thanks  March 27, 2003
Reviewer:
      A reader

      Rating: 5
tkprof
      ?  March 27, 2003
Reviewer:  A reader

Is TKPROF an acronym or abreviation of what ?

Maybe Tom Kyte PROFessional ?

          Followup:

Tool
Kit
PROFiler

      Rating:
      5
  March 27, 2003
Reviewer:
      A reader
I thought it was Tom Kyte's PROFiler ;)

      Rating: 5
I wanted to say
      the same  July 04, 2003
Reviewer:  Piotr
      Jarmuz  from Braunschweig Germany, (BORN IN POZNAN POLAND)

Tool Kit Profile... Hehehe... Tell us more about it :-)

how about...
"Tom Kyte PROFessional"

P.S.
Bur you guys were faster ;)
I betcha you TK wrote (parts, everything) of this didn't you?

      Rating: 4
tkprof
      observations   August 06, 2003
Reviewer:
      umesh  from blore india
Tom
Please let know your observations
header_id column is a primary key column in both tables

I think i need to create+insert+drop the tables  becoz of HWM
also I need to set my sort_area_size to a bigger value

select date_ordered     , order_number
from so_headers soh ,so_lines         sol
WHERE
soh.header_id = sol.header_id
AND
soh.order_type_id = 1004
AND soh.date_ordered BETWEEN TO_DATE('23-JUL-03','DD-MON-YY') AND
TO_DATE('23-JUL-03','DD-MON-YY')

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1    234.55     382.04     340827   17255634          2           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3    234.55     382.04     340827   17255634          2           2

          Followup:

i think there is wholly insufficient data to say anything about anything.

No, you cannot say "hwm is a problem here".  could be that both tables are
packed to the gills.

without the rest of the tkprof (the execution plan with associated row counts),
one cannot say anything.

I fail to see how sort_area_size would come into play at all.

I could easily reproduce this simply by filling the SOH table with:

order_type_id      date_ordered             header_id
---------------    ---------------------    --------------
1004               23-JUL-03                1

say a couple million times and in SOL putting nothing.  Now, I might range/full
scan SOH and do a couple million index probes into SOL

No sorting
No hwm issues
Lots of IO

see:

ops$tkyte@ORA920> create table soh
  2  as
  3  select 1004 order_type_id, to_date('23-jul-2003') date_ordered, rownum
header_id
  4    from big_table.big_table;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index soh_idx on soh(order_type_id,date_ordered);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table sol
  2  as
  3  select 0 header_id, 0 order_number
  4    from dual;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index sol_header_id_idx on sol(header_id);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace on
ops$tkyte@ORA920> select date_ordered, order_number
  2    from soh , sol
  3   WHERE soh.header_id = sol.header_id
  4     AND soh.order_type_id = 1004
  5     AND soh.date_ordered BETWEEN TO_DATE('23-JUL-03','DD-MON-YY') AND
TO_DATE('23-JUL-03','DD-MON-YY')
  6  /

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'SOL'
   2    1     NESTED LOOPS
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'SOH'
   4    3         INDEX (RANGE SCAN) OF 'SOH_IDX' (NON-UNIQUE)
   5    2       INDEX (RANGE SCAN) OF 'SOL_HEADER_ID_IDX' (NON-UNIQUE)

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

ops$tkyte@ORA920> set autotrace off

table is freshly packed.  zero sorts.  no rows found. lots of IO though.

CBO does much better:

ops$tkyte@ORA920> set autotrace on
ops$tkyte@ORA920> select date_ordered, order_number
  2    from soh , sol
  3   WHERE soh.header_id = sol.header_id
  4     AND soh.order_type_id = 1004
  5     AND soh.date_ordered BETWEEN TO_DATE('23-JUL-03','DD-MON-YY') AND
TO_DATE('23-JUL-03','DD-MON-YY')
  6  /

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1568 Card=1 Bytes=17)
   1    0   FILTER
   2    1     HASH JOIN (Cost=1568 Card=1 Bytes=17)
   3    2       TABLE ACCESS (FULL) OF 'SOL' (Cost=3 Card=1 Bytes=2)
   4    2       TABLE ACCESS (FULL) OF 'SOH' (Cost=1491 Card=1833792
Bytes=27506880)

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

ops$tkyte@ORA920> set autotrace off

      Rating: 5
Critical
      Performance and Clue Less...  August 06, 2003

Reviewer:  JFC  from Dominican Republic
I have this VIEW that actualy is used almost every where to fiend the actual
inventory per branch office.
But I also notice a lot hard word here.  Any advice will be appreciate.  This
view is a sum of every move made in inventory by the POS or other app.

CREATE OR REPLACE VIEW IPVI00018 ( CODIGO_COMPANIA,
       NOMBRE_COMPANIA,
       NOMBRE_SUCURSAL,
       NOMBRE_LOCALIDAD,
       CODIGO_SUCURSAL,
       CODIGO_LOCALIDAD,
       MARCA,
       MODELO,
       DESCRIPCION_ITEM_ESPANOL1,
       DESCRIPCION_COLOR,
       EXISTENCIA ) AS
        select a.codigo_compania,
               b.nombre_compania,
               e.nombre_sucursal,
               f.nombre_localidad,
               a.codigo_sucursal,
               a.codigo_localidad,
               a.marca,
               a.modelo,
               c.descripcion_item_espanol1,
               d.descripcion_color,
               sum(a.cantidad) existencia
        from   inv.iptb00015 a,
               inv.iptb00008 b,
               inv.iptb00009 e,
               inv.iptb00010 f,
               inv.iptb00001 c,
               inv.iptb00006 d
          where     a.estatus_t is null
                  and a.codigo_compania = b.codigo_compania
                and a.codigo_compania = e.codigo_compania
                and a.codigo_sucursal = e.codigo_sucursal
                and a.codigo_localidad= f.codigo_localidad
                and a.marca = c.marca
                and a.modelo = c.modelo
                and c.codigo_color= d.codigo_color
            group by a.codigo_compania,
                       b.nombre_compania,
                     e.nombre_sucursal,
                     f.nombre_localidad,
                     a.codigo_sucursal,
                     a.codigo_localidad,
                     a.marca,
                     a.modelo,
                     c.descripcion_item_espanol1,
                     d.descripcion_color

select *
from ipvi00018
/

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=358750 Card=302998
Bytes=69992538)

   1    0   VIEW OF 'IPVI00018' (Cost=358750 Card=302998 Bytes=69992538)

   2    1     SORT (GROUP BY)
   3    2       HASH JOIN (Cost=358750 Card=302998 Bytes=148772018)
   4    3         TABLE ACCESS (FULL) OF 'IPTB00008' (Cost=1 Card=1 Bytes=77)

   5    3         HASH JOIN (Cost=52411 Card=302998 Bytes=125441172)
   6    5           TABLE ACCESS (FULL) OF 'IPTB00010' (Cost=1 Card=9 Bytes=504)

   7    5           HASH JOIN (Cost=50878 Card=302998 Bytes=108473284)
   8    7             TABLE ACCESS (FULL) OF 'IPTB00009' (Cost=1 Card=33
Bytes=2970)

   9    7             HASH JOIN (Cost=48559 Card=302998 Bytes=81203464)

  10    9               TABLE ACCESS (FULL) OF 'IPTB00006' (Cost=1 Card=11
Bytes=407)

  11    9               MERGE JOIN (Cost=47552 Card=302998 Bytes=69992538)

  12   11                 SORT (JOIN)
  13   12                   TABLE ACCESS (FULL) OF 'IPTB00015' (Cost=1904
Card=302998 Bytes=19391872)

  14   11                 SORT (JOIN)
  15   14                   TABLE ACCESS (FULL) OF 'IPTB00001' (Cost=79
Card=3696 Bytes=617232)

Statistics
----------------------------------------------------------
        127  recursive calls
        724  db block gets
       1027  consistent gets
          2  physical reads
       2328  redo size
        409  bytes sent via SQL*Net to client
        250  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

BTW:  How do I format the plan colmn in SQL*Plus to fit and not word wrap

          Followup:

last first:

column plan_plus_exp format a100

put that in your login.sql.

tkprof is best -- we can see what rows are process by which steps.  (bear in
mind, i'm getting ready to hit the road, won't be back for a couple of days)

      Rating: 5
TKPROF for the
      masses  August 06, 2003
Reviewer:  JFC
       from Dominican Republic
Is there a way to make Your package work in 7.3.4

http://asktom.oracle.com/pls/ask/f?p=4950:8:12065486831475240218::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:7115831027951,

          Followup:

you would use utl_file instead of a bfile.

You would have to use a package similar to the "my_dbms_output" package (search
for that here) instead of a collection or pipelined function.

      Rating: 5
No trace to
      look at...  August 06, 2003
Reviewer:
      A reader  from Dominican Republic
My oracle parameters tell that /home12/oracle/admin/udump is my user_dump_dest
but nothing is there.

          Followup:

are  you using MTS (shared server) connections?  if so, it goes to bdump and is
virtually useless as your trace info is spread across many files and
intermingled with other sessions trace data.

      Rating: 4
tkprof report
        August 09, 2003
Reviewer:  umesh
       from Bangalore India
Tom
( Sorry that I did not mention it is on 7.3 RBO)
The query above for " sol " and "soh" did complete and is running fine in 40
seconds
What I am unable to comprehend is using a correlated subquery I am able to run
it fast

Can you please explain me why the below query is working fast ?

Thanks for your patience

SELECT
rac.tax_reference "Customer Tax Reference",
rac.customer_name_phonetic "Customer Code",
rac.customer_number "Customer Number",
rac.customer_name "Customer Name",
--rad.address1 "Customer Address",
rad.attribute5 "Address Attribute5",
--SUBSTR(ras.salesrep_number,1,3) "Salesrep Number",
ras.name "Salesperson",
rad.address1||','||rad.postal_code||','||rad.city "Customer Address",
SUBSTR(rad.attribute12,1,10) "Address Attribute12",
sol.item_type_code "Item Code",
msi.description "Item Description",
sol.ordered_quantity "Quantity Ordered",
racon.last_name "Contact Name",
rap.phone_number "Telephone Number",
rac.attribute2 "Ultimate DUNS"
FROM
mtl_system_items msi,
ra_contacts      racon,
ra_salesreps     ras,
ra_addresses     rad,
ra_customers     rac,
ra_phones        rap,
so_lines         sol,
so_headers soh
WHERE
(soh.order_type_id = 1004
AND soh.date_ordered BETWEEN
TO_DATE('&from_date' ,'DD-MON-YY') AND TO_DATE('&to_date','DD-MON-YY') )
AND soh.header_id = sol.header_id
AND msi.inventory_item_id = (SELECT msi1.inventory_item_id
                             FROM mtl_system_items msi1
                             WHERE msi1.inventory_item_status_code = 'Active'
                        AND msi1.inventory_item_id = sol.inventory_item_id )
AND soh.customer_id = rac.customer_id
AND soh.salesrep_id = ras.salesrep_id
AND rac.customer_id = racon.customer_id
AND rac.orig_system_reference = racon.orig_system_reference
AND rac.customer_id = rad.customer_id
AND rac.orig_system_reference  = rad.orig_system_reference
AND rap.phone_id = (SELECT MAX(r.phone_id)
                    FROM ra_phones r
                    WHERE r.customer_id = rap.customer_id)
AND rac.customer_id = rap.customer_id(+)

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=RULE
   1    0   FILTER
   2    1     NESTED LOOPS
   3    2       NESTED LOOPS
   4    3         NESTED LOOPS
   5    4           NESTED LOOPS (OUTER)
   6    5             NESTED LOOPS
   7    6               NESTED LOOPS
   8    7                 NESTED LOOPS
   9    8                   TABLE ACCESS (FULL) OF 'SO_HEADERS_ALL'
  10    8                   TABLE ACCESS (BY ROWID) OF 'RA_CUSTOMERS'
  11   10                     INDEX (UNIQUE SCAN) OF 'RA_CUSTOMERS_U1'
(UNIQUE)

  12    7                 TABLE ACCESS (BY ROWID) OF 'RA_CONTACTS'
  13   12                   INDEX (UNIQUE SCAN) OF 'RA_CONTACTS_U2' (UNIQUE)

  14    6               TABLE ACCESS (BY ROWID) OF 'SO_LINES_ALL'
  15   14                 INDEX (RANGE SCAN) OF 'SO_LINES_N1' (NON-UNIQUE)

  16    5             TABLE ACCESS (BY ROWID) OF 'RA_PHONES'
  17   16               INDEX (RANGE SCAN) OF 'RA_PHONES_N1' (NON-UNIQUE)

  18    4           TABLE ACCESS (BY ROWID) OF 'RA_ADDRESSES_ALL'
  19   18             INDEX (RANGE SCAN) OF 'RA_ADDRESSES_N3' (NON-UNIQUE)

  20    3         TABLE ACCESS (BY ROWID) OF 'RA_SALESREPS_ALL'
  21   20           INDEX (RANGE SCAN) OF 'RA_SALESREPS_U1' (UNIQUE)
  22    2       TABLE ACCESS (BY ROWID) OF 'MTL_SYSTEM_ITEMS'
  23   22         INDEX (RANGE SCAN) OF 'MTL_SYSTEM_ITEMS_U1' (UNIQUE)
  24   23           TABLE ACCESS (BY ROWID) OF 'MTL_SYSTEM_ITEMS'
  25   24             INDEX (RANGE SCAN) OF 'MTL_SYSTEM_ITEMS_N3' (NON-UNIQUE)

  26    1     SORT (AGGREGATE)
  27   26       TABLE ACCESS (BY ROWID) OF 'RA_PHONES'
  28   27         INDEX (RANGE SCAN) OF 'RA_PHONES_N1' (NON-UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          3  db block gets
      97388  consistent gets
      85764  physical reads
          0  redo size
       1930  bytes sent via SQL*Net to client
       2275  bytes received via SQL*Net from client
         10  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          7  rows processed

          Followup:

umm, no....

heck, I don't even know what I'm looking at here.

      Rating: 5
TKPROF
      analysis  October 05, 2003
Reviewer:
      A reader
Tom,
In the book 'Expert one-to-one Oracle', Chapt 10,
Section: Using and Interpreting TKPROF output, PG 459 (wrox press)
<quote>
...If we see a high parse count, this implies we are performing many soft parses
of this query...
</quote>

1) This could imply not just soft parses, but also hard parses.  How can you can
that these are not hard parses?
2) How do you know from the TKPROF report if the parse is either a soft parse or
a hard parse

Thank you

          Followup:

1) because a hard parse would imply the statment was being aged or flushed out
of the shared pool whilst we were running -- which would be an "unlikely
situation".  But in any case, you can always look at "misses in library cache"
to determine how many of the parses in each section of the report were hard.

2) see number 1 (or turn the page to 460 where I say "Let's go, onto the next
part of the report :)

      Rating: 5
TKPROF
      analysis  October 06, 2003
Reviewer:
      A reader
Thank you

      Rating: 5
consistent mode
      vs current mode   October 06, 2003
Reviewer:
      A reader
On reading your answers for TKPROF reports on this site, I understand that query
and current are numbers for logical IO's and
Query: Bufferes retrieved in consistent mode for queries
Current: Buffers retrieved in current mode for inserts, updates and deletes

Can you please explain what consistent mode and current mode mean?

Thank you

          Followup:

consistent read means "get the block as it appeared at the beginning of our
statement, using rollback data if needed to undo changes"

current means "get it as it exists RIGHT NOW"

      Rating: 5
Consistent mode
      vs Current mode  October 07, 2003
Reviewer:
      A reader
Tom,
Consistent read gets committed date.  Right?

Does current mean that you are going to get records that are not committed ie.
records that have been changed by your session?

If I am wrong on what I have said here, can you please pont me to the
documentation/URL that explains the same.

Thank you

          Followup:

do you have either of my books?  i go into this.

A consistent read means you are reading the committed version of the block AS OF
THE TIME your statement began (or transaction if you are using serializable).
If you start a query at 12:00 and it finished at 12:05 -- all of the blocks it
"sees" will be "as of 12:00".  Even if the last block you read at 12:05 was
modified and comitted at 12:04 by some other session -- you will not see those
changes.  All of the data in your result set will be "as of" 12:00 (that is the
very definition of a consistent read -- all of the data is read as of the same
point in time, all of the data is consistent with respect to itself)

A current read reads the block "as of right now".  As it exists.  This is done
to actually effect a change on the block -- say you were running a 5 minute
update -- at 12:05 we need to see the block AS OF 12:05 in order to actually
write to it -- we don't want to update the 12:00 version of the block as it may
have changed thousands of times in the 5 minutes.

      Rating: 5
Consistent mode
      vs Current mode   October 07, 2003
Reviewer:
      A reader
Thank you.

      Rating: 5
Negative impact
      with session_cached_cursors  November 28, 2003

Reviewer:  Saminathan Seerangan  from NJ,USA

Dear Tom,
Always thankful to you for providing excellent guidance and support.

In our DB we see huge number of softparses during the load test. So I thought of
setting SESSION_CACHED_CURSORS parameter to positive number(100) to take
advantage of SOFTER SOFT PARSE. But I am getting negative impact.
Kindly advice me what is going on here.

Version :8.1.7.3
OS: Sun Solaris

tkprof output
=============
 SELECT /*+ cached cursors 0  */FIRST_NAME,LAST_NAME,CUSTOMERID,COUNTRYABBREV
 FROM  T1 P,T2 E,T3 C  WHERE P.T1ID =   E.T1ID  AND P.BUSINESS_COUNTRY_ID =
C.COUNTRYABBREV

 call     count       cpu    elapsed       disk      query    current
rows
 ------- ------  -------- ---------- ---------- ---------- ----------
----------
 Parse     2000      1.76       1.77          0          0          0
0
 Execute   2000      0.84       0.74          0          0          0
0
 Fetch        0      0.00       0.00          0          0          0
0
 ------- ------  -------- ---------- ---------- ---------- ----------
----------
 total     4000      2.60       2.51          0          0          0
0

 Misses in library cache during parse: 0
 Optimizer goal: CHOOSE
 Parsing user id: 165     (recursive depth: 1)

 Rows     Row Source Operation
 -------  ---------------------------------------------------
       0  HASH JOIN
       0   INDEX FAST FULL SCAN (object id 76648)
       0   HASH JOIN
       0    TABLE ACCESS FULL T2
       0    TABLE ACCESS FULL T1

 SELECT /*+ cached cursors 100  */FIRST_NAME,LAST_NAME,CUSTOMERID,
COUNTRYABBREV
 FROM  T1 P,T2 E,T3 C  WHERE P.T1ID =   E.T1ID  AND P.BUSINESS_COUNTRY_ID =
C.COUNTRYABBREV

 call     count       cpu    elapsed       disk      query    current
rows
 ------- ------  -------- ---------- ---------- ---------- ----------
----------
 Parse     2000      2.05       1.99          0          0          0
0
 Execute   2000      0.82       0.74          0          0          0
0
 Fetch        0      0.00       0.00          0          0          0
0
 ------- ------  -------- ---------- ---------- ---------- ----------
----------
 total     4000      2.87       2.73          0          0          0
0

 Misses in library cache during parse: 0
 Optimizer goal: CHOOSE
 Parsing user id: 165     (recursive depth: 1)

 Rows     Row Source Operation
 -------  ---------------------------------------------------
       0  HASH JOIN
       0   INDEX FAST FULL SCAN (object id 76648)
       0   HASH JOIN
       0    TABLE ACCESS FULL T2
       0    TABLE ACCESS FULL T1

********************************************************************************

Program used to generate the above trace file.
==============================================

alter session set SQL_TRACE=true;
alter session set session_cached_cursors=0;
declare
    type rc is ref cursor;
    C rc;
    n number :=0;
begin
n := dbms_utility.get_time;
    for i in 1 .. 2000 loop
        open C for select /*+ cached cursors 0 */
first_name,last_name,customerid,countryabbrev from t1 p,t2 e,t3 c where
p.t1id=e.t1id and p.business_country_id=c.countryabbrev;
        close C;
    end loop;
dbms_output.put_line( dbms_utility.get_time - n );
end;
/
alter session set session_cached_cursors=100;
declare
    type rc is ref cursor;
    C rc;
    n number :=0;
begin
n := dbms_utility.get_time;
    for i in 1 .. 2000 loop
        --open C for select /*+ cached_cursors 100 */ * from dual;
        open C for select /*+ cached cursors 100 */
first_name,last_name,customerid,countryabbrev from t1 p,t2 e,t3 c where
p.t1id=e.t1id and p.business_country_id=c.countryabbrev;
        close C;
    end loop;
dbms_output.put_line( dbms_utility.get_time - n );
end;
/

  SQL> @x

 Session altered.

 Session altered.

 394

 PL/SQL procedure successfully completed.

 Session altered.

 413

 PL/SQL procedure successfully completed.

  SQL>

          Followup:

i always said the softer soft parse isn't any "faster" (run that test a couple
dozen times, betcha the results go back and forth)

ops$tkyte@ORA817DEV> create or replace procedure p1
  2  as
  3      type rc is ref cursor;
  4      C rc;
  5      n number :=0;
  6  begin
  7      execute immediate 'alter session set session_cached_cursors=0';
  8      for i in 1 .. 2000
  9      loop
 10          open C for select * from all_objects t1;
 11          close C;
 12      end loop;
 13  end;
 14  /

Procedure created.

ops$tkyte@ORA817DEV> create or replace procedure p2
  2  as
  3      type rc is ref cursor;
  4      C rc;
  5      n number :=0;
  6  begin
  7      execute immediate 'alter session set session_cached_cursors=100';
  8      for i in 1 .. 2000
  9      loop
 10          open C for select * from all_objects t2;
 11          close C;
 12      end loop;
 13  end;
 14  /

Procedure created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> exec runstats_pkg.rs_start

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> exec p1

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> exec runstats_pkg.rs_middle

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> exec p2

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> exec runstats_pkg.rs_Stop
Run1 ran in 105 hsecs
Run2 ran in 61 hsecs
run 1 ran in 172.13% of the time

Name                                  Run1        Run2        Diff
LATCH.redo allocation                    9          10           1
LATCH.redo writing                       2           1          -1
STAT...calls to kcmgcs                   2           3           1
STAT...messages sent                     0           1           1
LATCH.undo global data                   2           3           1
LATCH.direct msg latch                   2           0          -2
STAT...redo entries                      7           9           2
STAT...bytes received via SQL*         786         784          -2
STAT...consistent gets                   5           7           2
LATCH.session queue latch                2           0          -2
STAT...db block gets                     9          12           3
STAT...session cursor cache co           0           3           3
STAT...db block changes                 14          18           4
STAT...session logical reads            14          19           5
LATCH.ksfv messages                      7           1          -6
LATCH.messages                           7           1          -6
LATCH.cache buffers chains              49          65          16
STAT...recursive cpu usage              85          48         -37
STAT...parse time cpu                   48           8         -40
STAT...Elapsed Time                    108          64         -44
STAT...parse time elapsed               51           5         -46
STAT...CPU used by this sessio         114          64         -50
STAT...CPU used when call star         114          64         -50
STAT...redo size                    20,568      21,288         720
STAT...session cursor cache hi           0       1,999       1,999
LATCH.shared pool                   10,082       2,076      -8,006
STAT...session uga memory           20,080       4,236     -15,844
STAT...session uga memory max       20,080       4,236     -15,844
LATCH.library cache                106,236       4,269    -101,967

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
116,411       6,439    -109,972  1,807.90%

this is the key part here -- latching = locking = scalability inhibitor =
performance killer as you scale up.  less latching = better scalability.   Even
if the time taken for each parse was larger, I would prefer p2 over p1 for this
reason

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> @trace
ops$tkyte@ORA817DEV> alter session set events '10046 trace name context forever,
level 12';

Session altered.

ops$tkyte@ORA817DEV> exec p1

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> exec p2

PL/SQL procedure successfully completed.

SELECT *   FROM ALL_OBJECTS T1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse     2000      0.93       1.14          0          0          0           0
Execute   2000      0.35       0.33          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     4000      1.28       1.47          0          0          0           0

********************************************************************************

SELECT *   FROM ALL_OBJECTS T2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse     2000      0.38       0.45          0          0          0           0
Execute   2000      0.35       0.41          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     4000      0.73       0.86          0          0          0           0

but as you can see -- i cannot reproduce your findings (8174 on Solaris) --
but if you run your test over and over -- you'll find the cpu time varies from
run to run to run.  each discrete event is measured using 1/100ths of a second.
you did 2,000 parses.  they took around 2 cpu seconds -- meaning each parse was
ABOUT 1/1000 of a second.  It is very very easy for 2000 discrete observations
to either

a) not contribute any time (happened to fast, clock did not advance)
b) over contribute time (always happened on a clock tick even though each event
did not TAKE an entire clock tick)

Use runstats (as i did) and you'll get a nice aggregate number:

...
STAT...CPU used by this sessio         114          64         -50
......

that does not suffer from 2,000 discrete observations and over/under counting...

Also, remember, it is mostly about latching here.  you are not going to get a
ton of runtime back in single user mode with this approach (best to have WRITTEN
THE CODE PROPERLY IN THE FIRST PLACE!!!!  that you need to use session cached
cursors might well imply you have bugs in your code).  What you will get is
increased scalability.

      Rating: 5
session cursor
      cache hits= 0  November 29, 2003
Reviewer:
      Saminathan Seerangan  from Jersey City,NJ,USA
Dear Tom,
Many thanks for your response. Could you kindly explain why am  I getting
"session cursor cache hits= 0" even though the value of "session cursor cache
count is set to 100".

------------------------------------------------
create or replace procedure y as
begin
declare
    type rc is ref cursor;
    C rc;
    n number :=0;
        name varchar2(80);
        value varchar2(80);
begin
n := dbms_utility.get_time;
    for i in 1 .. 20000 loop

        open C for select /*+ cached cursors 100 */
first_name,last_name,customerid,countryabbrev
        from p p,e e,c c
        where p.pid=e.pid and p.business_country_id=c.countryabbrev;
        close C;
    end loop;
dbms_output.put_line( dbms_utility.get_time - n );

select a.name, b.value into name,value
from v$statname a, v$mystat b
where a.statistic# = b.statistic#
and trim(a.name)='session cursor cache hits'
and lower(a.name) like '%cursor ca%';

dbms_output.put_line(name ||'= '||value);

select a.name, b.value into name,value
from v$statname a, v$mystat b
where a.statistic# = b.statistic#
and trim(a.name)='session cursor cache count'
and lower(a.name) like '%cursor ca%';

dbms_output.put_line(name ||'= '||value);

end;
end;
/
----------------------------------------------------
SQL> get runy
  1  declare
  2  i number :=0;
  3  begin
  4  for i in 1..10
  5  loop
  6  y;
  7  end loop;
  8* end;
-------------------------------------------------

SQL> @runy

2531
session cursor cache hits= 0
session cursor cache count= 100
2489
session cursor cache hits= 0
session cursor cache count= 100
2503
session cursor cache hits= 0
session cursor cache count= 100
2482
session cursor cache hits= 0
session cursor cache count= 100
2417
session cursor cache hits= 0
session cursor cache count= 100
2383
session cursor cache hits= 0
session cursor cache count= 100
2387
session cursor cache hits= 0
session cursor cache count= 100
2347
session cursor cache hits= 0
session cursor cache count= 100
2232
session cursor cache hits= 0
session cursor cache count= 100
2232
session cursor cache hits= 0
session cursor cache count= 100

PL/SQL procedure successfully completed.

SQL> SQL> show parameter cursor

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
cursor_sharing                       string  EXACT
cursor_space_for_time                boolean FALSE
open_cursors                         integer 800
session_cached_cursors               integer 100

          Followup:

seems that your cursor cache is already filled, that is why.

the 100 shows "how many are used", not "how many could be used"

bear in mind the algorithms for managing this cursor cache are very version
specific -- what you see in each release will be different with regards as to
how the cursor cache is managed.

      Rating: 5
tkprof - Isnt
      it "Transient kernel profile" ? EOM  November 29, 2003

Reviewer:  Sudhir

          Followup:

I've heard it called

ToolKit profiler
Trace Kernel Profiler

but never transient

      Rating: 5
Very Quick
      Response!!!  November 29, 2003
Reviewer:
      Saminathan Seerangan  from NJ,USA
Dear Tom, Many Thanks. Everyday I am learning something new from you.

      Rating: 5
Help on tkprof
      output  December 02, 2003
Reviewer:  Saminathan
      Seerangan  from Jersey City,NJ,USA
Dear Tom,

Oracle Version 8.1.7.3(OPS) on Sun Solaris.
We have very poor perfromance issue in prod environment.

Could you kindly shed some light on the following tkprof output?

To get 0 record it is aking more than 11 seconds. Also I see huge difference
between CPU time and elapsed time even though the system is not so busy(It is a
test machine. very low load on it). If you say it is waiting on something, could
you tell me how to identify the wait event associated with this and how to
rectify the same?

Also please let me know why the query count is very high?

   select countryname, e.lastupdatedate
    from e e, p p, c c
    where p.pid = e.pid
    and p.hsbc_user_category='GIB'
    and p.business_country_id=c.countryabbrev
    and e.userstatusid in ( select userstatusid from userstatus )
    and p.business_country_id in ( select countryabbrev from c )
    order by countryname, e.lastupdatedate desc

Elapsed: 00:00:11.37

  call     count       cpu    elapsed       disk      query    current
rows
  ------- ------  -------- ---------- ---------- ---------- ----------
----------
  Parse        2      0.07       0.08          0          0          0
0
  Execute      2      0.00       0.02          0          0          0
0
  Fetch        2     42.95     133.21      58730     118694         24
0
  ------- ------  -------- ---------- ---------- ---------- ----------
----------
  total        6     43.02     133.31      58730     118694         24
0

  Misses in library cache during parse: 1
  Optimizer goal: CHOOSE
  Parsing user id: 165

  Rows     Row Source Operation
  -------  ---------------------------------------------------
        0  SORT ORDER BY
        0   NESTED LOOPS
        1    NESTED LOOPS
     1590     HASH JOIN
      239      TABLE ACCESS FULL c
     1589      HASH JOIN
      239       VIEW VW_NSO_1
      239        SORT UNIQUE
      239         INDEX FAST FULL SCAN (object id 76648)
     1589       TABLE ACCESS FULL p
     1589     TABLE ACCESS BY INDEX ROWID e
     1589      INDEX UNIQUE SCAN (object id 76709)
        0    INDEX UNIQUE SCAN (object id 76899)

********************************************************************************

  OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

  call     count       cpu    elapsed       disk      query    current
rows
  ------- ------  -------- ---------- ---------- ---------- ----------
----------
  Parse        4      0.07       0.08          0          0          0
0
  Execute      5      0.00       0.05          0          0          0
2
  Fetch        2     42.95     133.21      58730     118694         24
0
  ------- ------  -------- ---------- ---------- ---------- ----------
----------
  total       11     43.02     133.34      58730     118694         24
2

  Misses in library cache during parse: 1

  OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

  call     count       cpu    elapsed       disk      query    current
rows
  ------- ------  -------- ---------- ---------- ---------- ----------
----------
  Parse        0      0.00       0.00          0          0          0
0
  Execute      0      0.00       0.00          0          0          0
0
  Fetch        0      0.00       0.00          0          0          0
0
  ------- ------  -------- ---------- ---------- ---------- ----------
----------
  total        0      0.00       0.00          0          0          0
0

  Misses in library cache during parse: 0

      5  user  SQL statements in session.
      0  internal SQL statements in session.
      5  SQL statements in session.

********************************************************************************
  Trace file: hsbc1_ora_3102.trc
  Trace file compatibility: 8.00.04
  Sort options: default

         1  session in tracefile.
         5  user  SQL statements in trace file.
         0  internal SQL statements in trace file.
         5  SQL statements in trace file.
         3  unique SQL statements in trace file.
        93  lines in trace file.

Thanks in Advance,
Sami

          Followup:

look at the disk reads

look at the consistent gets

look at the rows flowing through each step of the plan

you are processing thousands of rows -- to find one.

without understanding what indexes exist and cardinalities/etc -- one cannot
"tune"

(eg: without understanding the question and the data and the indexing scheme,
one cannot do much)

      Rating: 5
More
      info....  December 02, 2003
Reviewer:
      Saminathan Seerangan  from NJ,USA
Dear Tom,

Provided more info...

select count(*) from P
  COUNT(*)
----------
    299178

select count(*) from E
  COUNT(*)
----------
      7258

select count(*) from C
  COUNT(*)
----------
       239

TABLE P
========
INDEX_NAME                     COLUMN_NAME
COLUMN_POSITION
------------------------------ ----------------------------------------
---------------
PROFILEDUSER_FK10              CAMLEVELID
     1
PROFILEDUSER_I01               HSBC_USER_CATEGORY
     1
PROFILEDUSER_I02               BUSINESS_COUNTRY_ID
     1
PROFILEDUSER_IX01              SEARCH_FIRST_NAME
     1
PROFILEDUSER_IX02              SEARCH_LAST_NAME
     1
PROFILEDUSER_IX03              SEARCH_COMPANY_NAME
     1
PROFILEDUSER_IX04              USERID
     1
PROFILEDUSER_IX04              PROFILEDUSERID
     2
PROFILEDUSER_PK                PROFILEDUSERID
     1
PROFILEDUSER_AK1               USERID
     1
10 rows selected

TABLE E
=========
INDEX_NAME                     COLUMN_NAME
COLUMN_POSITION
------------------------------ ----------------------------------------
---------------
EXTENDEDATTRIBUTES_FK09        USERTIERID
     1
EXTENDEDATTRIBUTES_FK10        USERTYPEID
     1
EXTENDEDATTRIBUTES_FK03        USERSTATUSID
     1
EXTENDEDATTRIBUTES_FK04        STAFFUSERDEPTID
     1
ATTRIBUTES_PK                  PROFILEDUSERID
     1
EXTENDEDATTRIBUTES_FK01        COMPANYINCCOUNTRY
     1
EXTENDEDATTRIBUTES_FK02        COMPANYSECTORID
     1
EXTENDEDATTRIBUTES_FK06        INVESTORCLASSID
     1
EXTENDEDATTRIBUTES_FK07        LANGUAGEID
     1
EXTENDEDATTRIBUTES_FK08        TIMEZONEID
     1
10 rows selected

TABLE C
========
INDEX_NAME                     COLUMN_NAME
COLUMN_POSITION
------------------------------ ----------------------------------------
---------------
COUNTRIES_PK                   COUNTRYID
     1
COUNTRIES_AK1                  HSBCSERVICEID
     1
COUNTRIES_AK1                  COUNTRYABBREV
     2
COUNTRIES_FK01                 HSBCSERVICEID
     1
COUNTRIES_FK02                 LANGUAGEID
     1
5 rows selected

          Followup:

hows about an autotrace traceonly explain so one can see the index names.

      Rating: 5
autotrace
      output  December 03, 2003
Reviewer:  Saminathan
      Seerangan  from NJ,USA
 1   select countryname, e.lastupdatedate
  2    from extendedattributes e, profileduser p, countries c
  3    where p.profileduserid = e.profileduserid
  4    and p.hsbc_user_category='GIB'
  5    and p.business_country_id=c.countryabbrev
  6    and e.userstatusid in ( select userstatusid from userstatus )
  7    and p.business_country_id in ( select countryabbrev from countries )
  8*   order by countryname, e.lastupdatedate desc

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=7398 Card=9 Bytes=10
          62)

   1    0   SORT (ORDER BY) (Cost=7398 Card=9 Bytes=1062)
   2    1     HASH JOIN (Cost=7389 Card=9 Bytes=1062)
   3    2       TABLE ACCESS (FULL) OF 'COUNTRIES' (Cost=2 Card=239 By
          tes=3107)

   4    2       HASH JOIN (Cost=7386 Card=254 Bytes=26670)
   5    4         VIEW OF 'VW_NSO_1' (Cost=12 Card=239 Bytes=2868)
   6    5           SORT (UNIQUE) (Cost=12 Card=239 Bytes=478)
   7    6             INDEX (FAST FULL SCAN) OF 'COUNTRIES_AK1' (UNIQU
          E) (Cost=1 Card=239 Bytes=478)

   8    4         NESTED LOOPS (Cost=7373 Card=7258 Bytes=674994)
   9    8           NESTED LOOPS (Cost=115 Card=7258 Bytes=355642)
  10    9             TABLE ACCESS (FULL) OF 'EXTENDEDATTRIBUTES' (Cos
          t=115 Card=7258 Bytes=333868)

  11    9             INDEX (UNIQUE SCAN) OF 'USERSTATUS_PK' (UNIQUE)
  12    8           TABLE ACCESS (BY INDEX ROWID) OF 'PROFILEDUSER' (C
          ost=1 Card=32929 Bytes=1448876)

  13   12             INDEX (UNIQUE SCAN) OF 'PROFILEDUSER_PK' (UNIQUE
          )

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

          Followup:

ok, thats a different plan. how about you just translate the object_ids in the
tkprof for me instead

      Rating:
      5
  December 03, 2003
Reviewer:
      Saminathan Seerangan  from NJ,USA
Dear Tom, Really appreciated.

select countryname, e.lastupdatedate
    from e e, p p, c c
    where p.pid = e.pid
    and p.hsbc_user_category='GIB'
    and p.business_country_id=c.countryabbrev
    and e.userstatusid in ( select userstatusid from userstatus )
    and p.business_country_id in ( select countryabbrev from c )
    order by countryname, e.lastupdatedate desc

select object_id,object_name
from dba_objects where object_id in (76648,76709,76
 OBJECT_ID OBJECT_NAME
---------- ----------------
     76648 COUNTRIES_AK1
     76709 ATTRIBUTES_PK
     76899 USERSTATUS_PK

Note :-

"and p.business_country_id in ( select countryabbrev from c )"
In the above subquery,sometimes few predicates will be added on the fly.
In other words the above query will be generated dynamically from java code.

In this particular case all countries are selected, so no predcicate has been
added.

          Followup:

how many rows would where hsbc_user_category = 'GIB' return from p?

are the "in's" really needed there?

      Rating: 5
Small
      correction:  December 03, 2003
Reviewer:
      Sami
In the above query "where p.pid = e.pid" should be read as "where
p.profileduserid = e.profileduserid"

December 03, 2003 Murali -- Thanks for the question regarding "Raw
      devices and backup", version 8.1.5

      You Asked
Hi

Currently we are running an Oracle database in Windows NT
platform(30 GB- 24 * 7 uptime ). We are planning to port DB
 to a more stable platform like (SUN / AIX - yet to be decided).
I come to know that all Unix boxes support the RAW devices and
DB writting will be faster. How will I configure a RAW device..?
Can I put more than one tablespace in a RAW device..? If that is
the case how will i take a online backup

Please help

Thanks & Regards

Murali

      and we said...
A raw device to Oracle is nothing more then a file is.  To us,
there is no difference.  So, when thinking about raw, think
about a raw device as a single big file.

So, for example, "can I put more then one tablespace in a raw
device" -- the answer is No, since you cannot have more then one
tablespace in a file (a tablespace contains files, not the other
way around).

People typically use some sort of volume/disk manager to slice
and dice and configure their raw partitions.  I can take a
single 9 gig drive and configure it to have many "slices"
(partitions) each of varying sizes.  So, on one physical device
-- i can configure many "virtual" devices.  Each of these will
be considered a file in itself by Oracle.  So, if I slice a disk
into 5 pieces, Oracle will see 5 "files" -- you could have 5
tablespaces on that one physical disk.

A more common operation would be to use a volume manager to
implement raid at some level and then logically partition the
disk out into N raw partitions and use them.

On a UNIX OS, the benefit of raw over cooked are very minimal
and mostly in special cases (you might want to read some of the
material at

http://www.jlcomp.demon.co.uk/raworfs_i.html
he's done a good writeup of this).  If you are not comfortable
with raw, you can consider staying with a cooked file system.
Many OS's/volume managers have options to mount their cooked
file systems without buffering (eg: on SunOS I mount my database
disks with:

/dev/...        /dev/....  /log2 ufs 3  yes  forcedirectio

the forcedirectio turns off buffering giving nearly the
equivalent performance of raw with the benefits of a file
system)

Review &
      Followup

      Rating:
      5
  December 03, 2003
Reviewer:
      A reader
Tom,

I know this question is related to unix, may be you might have something for me
from your bag of tricks.

What unix command can i use to find the LUN No. of raw device.

ex. my raw device is
/dev/oracle/ora9i/tools_ora9i_01.dbf

Thanks.

          Followup:

don't know, maybe an SA would know -- but its probably vendor specific.

(but that looks like a filename, not a raw device to me)

December 03, 2003 Rich -- Thanks for the question regarding "Oracle
      standby database hardware/software requirements", version 8.1.7

      You Asked
I know primary and the standby database need to be the same base release level.
But, do they have to be the same edition of the database - (primary enterprise
edition and standby standard edition), and the same O/S - (primary on SUN
Solaris and standby on Linux) ??

      and we said...
For a physical standby (9iR1 and before) they must be 100% homogenous --
Software, hardware (Sun to Sun, could be Sun 4500 to Sun E450 but must be sun
sparc to sun sparc for example), and OS.  EE to EE and SE to SE.

updated this answer..

For a LOGICAL standby (9iR2 and above), they are still homogenous although by
using Oracle streams directly, you can achieve the same goal of a standby but
use  heterogenous hardware.

Sorry for the confusion.  See

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96571/toc.htm
Review
      & Followup

      Rating: 4
Exactly what I
      needed  July 03, 2002
Reviewer:  Rich Stucke
       from St. Louis, MO
This answer is exactly what I needed to know.  If I understand correctly:  If my
Primary database is Oracle 9.2 EE on a SUN Sparc Solaris 4500, I can have my
standby database as Oracle 9.2 EE on a Linux box ??

          Followup:

with a logical standby -- yes.

that is a new 9iR2 feature -- we turn the binary redo stream into logical change
records which are turned into SQL and applied to the logical standby (you can
still do a physical standby of course)

      Rating:
      4
Correct  July 04, 2002
Reviewer:  Akthar
      A.J.  from Sun Micro - Singapore
If you guys have solaris platform ,'Data Guard' can be used to configure standby
DB. Available right from 8i. Have any folks tried it..It is really fantastic and
Real point in time.

Tom , any comment on Data Guard..

          Followup:

Data guard is cool, better then standby.  More features, more options.

Doesn't require solaris -- just any two homogenous platforms that Oracle is on.
and in 9iR2 -- can be heterogenous as well (Solaris to Nt, Nt to HP/UX etc)

      Rating:
      3
  July 04, 2002
Reviewer:  Sagi
       from India
What is Logical and Physical Standby.

Would appreciate if you could explain in short.

Thanx in advance.

Regards,

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96653/standbycon.htm#1005573
<quote from there>

    * Physical standby databases

      A physical standby database is physically identical to the primary
database, with on-disk database structures that are identical to the primary
database on a block-for-block basis. The physical standby database is updated by
performing recovery. It can either be recovering data or open for read-only
reporting.

    * Logical standby databases

      A logical standby database is logically identical to the primary database.
The logical standby database is updated using SQL statements. The tables in a
logical standby database can be used simultaneously for recovery and for other
tasks such as reporting, summations, and queries.

</quote>

      Rating:
      5
  August 21, 2002
Reviewer:
      A reader
Hi Tom,

I am little confuse ,

1- you said:
<quote>
Data guard is cool, better then standby.  More features, more options.

Doesn't require solaris -- just any two homogenous platforms that Oracle is on.
and in 9iR2 -- can be heterogenous as well (Solaris to Nt, Nt to HP/UX etc)
<quote>

But when I read the operation requirements in the Oracle documentation about
Data Guard for the release 9i2, they said
<quote>
The hardware and operating system architecture on the primary and standby
locations must be the same. For example, this means a Data Guard configuration
with a primary database on a 32-bit Sun system must have a standby database that
is configured on a 32-bit Sun system. Similarly, a primary database on a 64-bit
HP-UX system must be configured with a standby database on a 64-bit HP-UX
system, a primary database on a 32-bit Linux on Intel system must be configured
with a standby database on a 32-bit Linux on Intel system, and so forth.
<qoute>

Taking into account this, Can I use two differents severs with differents OS but
the same ORacle release?

2-You said <qoute> Data guard is cool, better then standby.  More features, more
options. <quote>
Why did you say better then standby? Isn't data guard an Oracle tool (like
entreprise manager for example) in order to manage better the standby databases?
Don't you need to have a standby database in order to use data guard?

Thanks for your help

          Followup:

1) I "mispoke" a little there.  The underlying technology here is "streams" and
with streams it can be heterogenous.  A little more involved then out of the box
logical standby (which wants you to start with a hot/cold backup of the original
which won't work in a heterogenous situation).  So yes, you are correct -- it
would not be a true "logical standby", it would be a one way stream (see the
streams concepts guide)

2) standby is the version 8i and before name (in my mind) -- data guard is a
more comprehensive solution giving you switch over, switch back; 4 levels from
which to choose (as far as apply goes) and so on.  The standby database is just
a tiny component of that now.

Data Guard gives you a standby database.
A standby database doesn't necessarily have all of the features of data guard.

      Rating:
      5
  August 22, 2002
Reviewer:
      A reader
Thanks for the answer.
1-Logical standby database is available in 9i standard edition?
2-Do you know if data guard is available in 9i standard edition?

regards

          Followup:

they are enterprise features

see

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96531/ch5_edit.htm#73594

      Rating: 5
Why primary and
      standby on same box?  August 23, 2002
Reviewer:
      A reader  from USA
Tom,
Can you please explain in what situation and why we put primary and standby
databases on the same box?  I thought the standby is for disaster recovery. If
both on the same box, how?
Thank you very much.

          Followup:

they are on different computers.

      Rating: 3
What is the
      difference between Data Guard's logical Standby and
      Replication?  February 05, 2003
Reviewer:  Alin
      Cretu  from Romania
I want to have a better comprehention of these options. A highlighted list of
differences and resemblances between Data Guard's logical Standby and Advance
Replication would help.

          Followup:

Well that is easy.

One is an apple.

The other is a toaster oven.

In as much as you can compare them, go for it.

You see -- data guard is for DR, disaster recovery.  plain and simple.

Replication -- that is a technology that does lots of things, you build
applications with it in mind.

Using data guard for replication -- no, not really.

Using replication for DR -- wouldn't suggest it, it is not a DR tool, never has
been.

So, if you are building an application that needs replication -- you use
replication.

If you are building a system that needs DR -- data guard is what you want and if
you want to use the DR site as a reporting instance just as a bonus and can live
within the bounds of the logical standby -- it is an OK thing to do.

But -- one is not to be used for the other (although everyone keeps trying to
use replication as DR, it just is the most utterly painful way to try and do it.
 if you *need* replication, thats fine.  If you want DR, replication isn't the
way to go)

      Rating: 3
Is 9.2.0.2
      compatable with 9.2.0.1 for physical standby  March 03, 2003

Reviewer:  Baqir Hussain  from Emeryville, CA USA

Tom,
In the test environment for the physical standby,

Primary database is 9.2.0.2 on sun solaris 2.8, ULTRA 5.
Standby databse is 9.2.0.1 on e250 solris 2.8.

The standby database was set successfully into the managed recover mode as well
as the transfer of the archivelog from the PRIMARY to the STANDBY.

Now I tried to open the standby database in read-only mode as follows:

ALTER DATABASE RECOVER MANAGED STANDBY DATABASE CANCEL;
and then
SQL> ALTER DATABASE OPEN READ ONLY;

the database shutdown with the following message:

ORA-01092: ORACLE instance terminated. Disconnection forced

and there is an error message in the alert log:

Mon Mar  3 13:13:43 2003
ALTER DATABASE OPEN READ ONLY
Mon Mar  3 13:13:43 2003
SMON: enabling cache recovery
Mon Mar  3 13:13:44 2003
Database Characterset is WE8ISO8859P1
Updating 9.2.0.2.0 NLS parameters in sys.props$
-- adding 9.2.0.1.0 NLS parameters.
An error occurred while updating sys.props$ !
Failure when inserting the new parameters.
It is inadvisable to use the new 9.2.0.1.0 NLS parameters
until this problem is resolved.

1.  Is there is any kind of bug that conversion of 9.2.0.2 of NLS parameters
could not update into 9.2.0.1?  As per oracle only base of the database should
be the same (EE 9.2)??
2. OR it is due to the hradware or both?

Please advise. thanks
2.

          Followup:

physical standbys -- by definition -- must be at the same release.

they are not in your case.

contact support.

      Rating: 4
upgrading
      database and standby  March 04, 2003
Reviewer:
      Rich Stucke  from St. Louis, MO USA
How can I install patches to a standby database.  I can upgrade the engine, but
I can't run the catalog and catproc scripts without opening the database can I
??  And if that is true, then I suspect that you only upgrade the production
database and then create a new standby.  Or because I've installed the patches
in the standby engine, the application of the archive logs during
synchronization will effectively do the catalog and catproc, etc. scripts for me
??

          Followup:

think about what catalog and catproc do.....

they update tables -- which are logged -- which propagate...  just like if you
create or replace a view in your own schema -- they move over via redo.

      Rating: 5
Data Guard for
      Windows 2000  March 05, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Islamabad, Pakistan
Hi Tom !

Is data guard available for Oracle 8.1.7 running on Windows 2000 Advanced Server
? I vistied technet.oracle.com > Download but couldn't find one for Windows
2000.

Thankx

          Followup:

data guard is a 9i new feature

SOME of the bits were available for 8i like the data guard manager command line
interface but the architectural changes are 9i and up.

      Rating: 5
standby
      upgrades  March 05, 2003
Reviewer:  Rich
      Stucke  from St. Louis, MO USA
OK if I understand correctly, I can do DDL processes on my production database
and they also will propagate to my standby ??  Is there anything that might be
added to my production database that would warrant having to create a new
standby ??

          Followup:

see

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76995/standbym.htm#27672

      Rating:
      3
stream  May 16, 2003
Reviewer:  test
       from usa
I have release 9ir2(9.2.0.2.1) on sun solaris. I am trying to configure oracle
stream on a single table object between two database through OEM. OEM generates
three scripts 1. Setup script 2. EXp/im script 3. Run script.

When the first scripts(setup script) run it fails at giving the permission of
ENQUE ANY using DBMS_AQ package. Giveing an error End of file on communication
channel. When I try to look into alert log file it generates ORA-600 internal
error. I tried to check on metalink for any patch related to this. But none was
there.

Has anyone faced this problem?

Thanks for the help.

          Followup:

open a tar with support -- you have metalink access so apparently you can.  all
ora-600's should lead to a tar.

      Rating: 4
Setting up of
      DR on different Palteform  August 08, 2003

Reviewer:  Manish Garg
Hi Actualy i have Oracle 8.1.7 running on AIX Plateform ,i wanted to setup a HOT
DR Server on Solaris Box .i am looking for online replication sort of thing .so
this article help me in finding answer of my query

Regards
Manish Garg

      Rating: 4
Standby Cold
      backup   September 18, 2003
Reviewer:
      Haratsaris,Alexandre  from Rio de Janeiro,RJ BRAZIL

Hi Tom,
My question is quite strange, but here it goes anyway...
I've created a standby database on a new Customer ( I work as Oracle Consultant)
that I supose it would be for Disaster Recovery issues, but then they sad that
they are planning to use only Standby database files for cold backups rather
than have to stop Production Env (24X7 new APPS) , as it "would" be at same
point-in-time as Prod . You know : No RMAN, No HOTBACKUPS, ONLY WEEKLY SO COLD
backups for years...terrible.
Well, as it is not easy to change their minds, what more arguments can I use
than explain problems with gap of sequence logs , nologging indexes replication,
create datafiles replication and so on ?
Thanks!

          Followup:

i would tell them "i can cut your hardware costs in half and never shutdown your
production database"

in order to do standby, they MUST be in archivelog mode.

if they are in archivelog mode, it is *trivial* to hot backup.

restoring a cold or hot backup is no more or less difficult.

taking a cold or hot backup is just as easy as well.

they *could* do what they are doing but they need 2x the disk, 2x the computers,
2x the software.  Maybe you can be their hero and cut their costs in half by
giving them a simple hot backup script.

      Rating:
      5
  November 10, 2003
Reviewer:
      A reader
Tom,

We are using dgmgrl which is pretty cool. Consider this

P1 is production and S1 is standby.

I gracefully switched over to S1, so now S1 is my production. Now i wanna
switchback to P1 so it becomes my production (back again).

Do i need to create a new controlfile from S1 and copy it to P1 or just issuing
the command "switchover ..." should work like before.

Thanks.

          Followup:

the switchover reverses the roles -- standby becomes prod, prod becomes standby.
 to re-reverse them, putting them back, is just another switchover.

      Rating:
      5
  November 10, 2003
Reviewer:
      A reader
Thanks..

      Rating:
      5
  November 12, 2003
Reviewer:
      A reader
Tom,

We have similar kind of situation, instead of switchover we are testing failover
using dataguard(dgmgrl).

As per my understanding after performing failover we cannot use former primary
as standby. We have to configure it again.

Example:- From Prod1 i failover  to Standby1.

So now Standby1 is Production, now I have to configure Prod1 to be my new
Standby.

Do i have to copy all the datafiles and standby controlfiles from  primary
(former standby) to be standby (former primary), or i can just create a standby
controlfile and copy & follow the process as suggested in Oracle docs.

Thanks.

          Followup:

if you did a non-graceful "failover" (as opposed to switchover), you
reinstantiate the standby from this new production (just like you were setting
up from scratch) and then do a graceful switchover.

if no data was lost during the non-graceful "failover", there is an opportunity
to "fail back" -- but in reality, it would not really happen.  You failed over
because the production machines blew up, they don't exist anymore.

      Rating: 5
Standby for
      SE  December 03, 2003
Reviewer:  Olaf
       from Germany
Tom,

what the possibilities I have to make a standby database for our prod DB SE.

In "Oracle 9i Feature and Option Availability"  I saw that DATA GUARD as well as
Standby database GUI are not available for SE.

          Followup:

managed standby (data guard) is a feature of Enterprise Edition.

so the redo shipping, etc etc etc -- it not available with SE.

with SE, you have to "roll your own" - shipping logs, applying, etc.  no FAL
server, no managed standby

      Rating: 5
Standby
      DB  December 03, 2003
Reviewer:
      A reader
Is it enough to buy a Standard Edition One license for the prod database with SE
license? HW for standby always has one CPU.

      Rating:
      5
  December 03, 2003
Reviewer:
      A reader
Tom,

In 9i dataguard is there some command for reinstantiating.

Thanks.

          Followup:

reinstantiating?

December 03, 2003 Robert -- Thanks for the question regarding
      "Blocks read using index vs. full table scan", version 8.1.7

      You Asked
Hi Tom,

I have a 3 part question about performance...

1) The first question is sort of a introductory summary question.
2) Then I have an example ending with a question.
3) Finaly I have a very specific 'important' question.

Why is it sometimes faster to do a full table scan on a large table vs. using an
index ?

For example, say you have a large table which has an index that has 4 distinct
values, each making up 25% of the index. Now let's say you run a query on this
table which is going after one of the distinct values in the index...

select *
  from big_table
 where indexed_col1 = 'ONE_FOURTH_OF_TABLE';

My understanding is that Oracle might typically choose to do a full table scan
on the table instead of using the index.

Why is this ? It would appear to me that Oracle would have to at least twice as
many reads (in this example) to do the FTS vs. using the index.

IMPORTANT QUESTION: Does Oracle do a separate read for each BLEVEL in the index
it is accessing ?

Thanks!

Robert.

      and we said...
Well, I'm going to recommend a book -- "expert one on one Oracle" written by me.
 I have quite a few examples that show why this is true (and it takes a couple
of pages).  If you read the reviews on amazon, I think you'll find that people
generally like the book overall.

Here is a short answer:

A full tablescan reads the table using large sequential reads -- many blocks at
a time.  Sequential reads are the fastest type of IO you can do on a disk in
general.

An index read will do single block, random IO's.  These are generally the
slowest you can do.

If you are going to read 25% of the table via an index you will probably hit
MOST if not all of the blocks in the table.  You will hit some blocks many times
probably as well.

Ok, if you take the above as "fact" we can move on... Lets say the table is 100
blocks, with 1000 values and your buffer cache can hold 50 blocks.  Let's say
you will read this via an index. We will read 250 rows.

The index might tell us "goto block 1, get row 1", "goto block 2, get row 1" ...
"goto block N get row 1".... "goto block 100 get row 1"

So, so far, the index has had us read each block once and get the first row on
each block.  The index processes the data in sorted order, not in the order it
physically resided on the disk in the table.  Hence, we did 100 random IO's to
read 100 blocks in.  Not only that -- but blocks 1-50 are no longer in the
buffer cache, blocks 51-100 are.  Now, resuming our query (we have 150 more rows
to get!) the index tells us: "goto block 1, get row 2", "goto block 2, get row
2" ... "goto block N get row 2".... "goto block 100 get row 2"

Oh no -- we just got told to RE-READ the blocks we already read!!!  Again!!!
from Disk!!   So, assuming the table had no blocks in the cache, we've now just
done 200 random physical IO's against that table -- AND we are not done yet!!!

So, picking up where we left off -- the index now says "goto block 1, get row
3", "goto block 3, get row 1" ... "goto block N get row 3".... "goto block 50
get row 3".   Bummer, 50 more random physical IO's.

Now, the full table scan comes in.  Suppose our db_file_multiblock_read_count is
set to 20.  We do 5 sequential IO's of 20 blocks each and answer the query.
Done.  Period.  Much faster.

Here, a full table scan does a whole lot less IO more efficiently.

For the important question: We do single block IO on indexes generally, so yes,
it would be a separate read for each block to traverse the tree.

Review &
      Followup

      Rating: 4
Good
      explanation  June 27, 2002
Reviewer:  Robert
       from Memphis
So far so good; now, if we can reduce the BLEVEL on the index by one then we
will, by definition, reduce the random I/O read for that index by one as well
(unless two blevels existed on the same block, etc.) ?

Thanks,

P.S.
I have your book. It was your chapter on TKPROF and your digging into the raw
trace files that made me say "This is just the Oracle information I've been
looking for" And by a guy with a sense of humor too! (now I just need to read
the book, huh?!).

          Followup:

yes, but most indexes are going to hang out at 3 levels in the real world and
levels 1 and 2 are typically cached so it isn't really worth the effort.

If you have the book, read pages 277-285.  It has the examples I was refering to
(colocated and disorganized tables)

      Rating: 5
Concerning
      db_file_multiblock_read_count   June 27, 2002

Reviewer:  Richard  from Florida
I was under the impression that the amount of performance
improvement you can get by setting the
db_file_multiblock_read_count parameter is limited by the
OS's IO buffer size. In short, the Oracle block size (in K)
multiplied by the value of db_file_multiblock_read_count
should be equal to the OS's IO buffer (in K).

So, if you have a block size of 8K and your OS's IO buffer
is 64K, setting the db_file_multiblock_read_count to
anything greater than 8 is useless.

Correct? Or no?

          Followup:

That would be pretty much correct -- however, on many OS's today the OS IO
buffer is in the multiple- hundreds of K even into the megabyte range.  Depends
on the OS (and we limit it to that, you can set it artificially high, we'll
bring it back down).

I was using numbers that made the math really really easy for illustrative
purposes.

      Rating: 4
Oracle internal
      IO limitation  June 28, 2002
Reviewer:  A P
      Clarke  from London, England
Tom

I read somewhere that Oracle itself limits I/O through the SSTIOMAX kernel
constant. My info is now a couple of years old, but at the time the value of
SSTIOMAX was said to be 512K.  What is its current (9i) value?  How could I find
the value for myself? The only place it's mentioned is in the error messages
(ORA-27067).

Also, would setting db_file_multiblock_read_count to an unfeasibly high number
have an impact on optimization?

Thanks, APC

          Followup:

One method  is to do a big full scan and see what the wait events tell us.  For
example:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t storage ( initial 100m next
100m ) tablespace xxx  as select * from all_objects;
Table created.

create a table with a pretty large initial extent. We never will read more
then an extent at a time (eg: I initially did this test in a locally managed
tablespace with 512k extents -- I read 64 8k blocks at a time, then realized "oh
yeah, thats my extent size"...)

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from t;
22858 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from t;
45716 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from t;
91432 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from t;
182864 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set
db_file_multiblock_read_count = 1024;
Session altered.

now, set this paramter to something really really high...

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set events '10046 trace name
context forever, level 12';

Session altered.

enable tracing with wait events capture on..

ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ FULL(t) */ count(*) from t;

  COUNT(*)
----------
    365728

and full scan.  Now, look at the trace file:

PARSING IN CURSOR #1 len=38 dep=0 uid=439 oct=3 lid=439 tim=3742056778
hv=194957013 ad='86989890'
select /*+ FULL(t) */ count(*) from t
END OF STMT
PARSE #1:c=0,e=1,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=4,tim=3742056778
BINDS #1:
EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=3742056778
WAIT #1: nam='SQL*Net message to client' ela= 0 p1=1650815232 p2=1 p3=0
WAIT #1: nam='file open' ela= 0 p1=0 p2=0 p3=0
WAIT #1: nam='db file scattered read' ela= 14 p1=10 p2=2514 p3=128
WAIT #1: nam='db file scattered read' ela= 12 p1=10 p2=2642 p3=128
WAIT #1: nam='db file scattered read' ela= 10 p1=10 p2=2770 p3=128
WAIT #1: nam='db file scattered read' ela= 10 p1=10 p2=2898 p3=128
WAIT #1: nam='db file scattered read' ela= 12 p1=10 p2=3026 p3=128
WAIT #1: nam='db file scattered read' ela= 12 p1=10 p2=3154 p3=128
WAIT #1: nam='db file scattered read' ela= 10 p1=10 p2=3282 p3=128
WAIT #1: nam='db file scattered read' ela= 9 p1=10 p2=3410 p3=128

db file scattered read is our wait event -- p3 is the number of blocks we
tried to read.  Apparently on my OS+filesystem+db version, I can read 1m at a
time (128 * 8K block)...

Yes, setting db_file_multiblock_read_count to an unfeasibly high number can
unduly affect the optimizer.  If you can never obtain that read rate, and it
thinks you will, it could lead it down the incorrect path.

      Rating:
      4
Excellent.....  June 28, 2002
Reviewer:
      Ashok  from India

      Rating:
      5
Excellent.  June 28, 2002
Reviewer:
      Leo  from Boston, MA

      Rating: 4
OS IO
      buffer  March 05, 2003
Reviewer:  Nitin
       from India
I am unable to understand what is meant by OS IO buffer.
How do we find it .

Is it the same as read write cache on sun solaris .
Well if it is , then if i have understood it correctly we can have the value of
db_file_multiblock_io up to
1073741824/8172=130000(approx) as we have 1gb of read write cache and using
db_block_size=8k.

          Followup:

it is the max amount the OS will read in a read request.

it was 128 blocks on my system as per the example.  It is the MAX io size the os
will do, regardless of what we ask for

      Rating:
      4
Question  March 05, 2003
Reviewer:
      sankar  from KY,USA
I created a table that spans across 5 extents and each extent is 260 blocks . I
did the alters and part of the trace file is below . It is on 8.1.7.2 / hp-ux
11.0

Select extent_id ,blocks from dba_extents where segment_name='T';

 EXTENT_ID     BLOCKS
---------- ----------
         0        260
         1        260
         2        260
         3        260
         4        260

PARSING IN CURSOR #1 len=70 dep=0 uid=163 oct=42 lid=163 tim=1608319387
hv=347037164 ad='85c9e8a0'
alter session set events '10046 trace name context forever, level 12'
END OF STMT
EXEC #1:c=1,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=1608319387
WAIT #1: nam='SQL*Net message to client' ela= 0 p1=1650815232 p2=1 p3=0
WAIT #1: nam='SQL*Net message from client' ela= 807 p1=1650815232 p2=1 p3=0
=====================
PARSING IN CURSOR #1 len=38 dep=0 uid=163 oct=3 lid=163 tim=1608320195
hv=194957013 ad='85caffd0'
select /*+ FULL(t) */ count(*) from t
END OF STMT
PARSE #1:c=1,e=1,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=4,tim=1608320195
BINDS #1:
EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=1608320195
WAIT #1: nam='SQL*Net message to client' ela= 0 p1=1650815232 p2=1 p3=0
WAIT #1: nam='db file scattered read' ela= 5 p1=3 p2=867 p3=128
WAIT #1: nam='db file scattered read' ela= 1 p1=3 p2=995 p3=47
WAIT #1: nam='db file scattered read' ela= 4 p1=3 p2=1302 p3=128
WAIT #1: nam='db file scattered read' ela= 3 p1=3 p2=1430 p3=128
WAIT #1: nam='db file scattered read' ela= 0 p1=3 p2=1558 p3=4
WAIT #1: nam='db file scattered read' ela= 4 p1=3 p2=2082 p3=128
WAIT #1: nam='db file scattered read' ela= 1 p1=3 p2=2210 p3=39
FETCH #1:c=34,e=43,p=602,cr=1210,cu=12,mis=0,r=1,dep=0,og=4,tim=1608320238
WAIT #1: nam='SQL*Net message from client' ela= 0 p1=1650815232 p2=1 p3=0
FETCH #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=0,tim=1608320238
WAIT #1: nam='SQL*Net message to client' ela= 0 p1=1650815232 p2=1 p3=0
WAIT #1: nam='SQL*Net message from client' ela= 462 p1=1650815232 p2=1 p3=0
STAT #1 id=1 cnt=1 pid=0 pos=0 obj=0 op='SORT AGGREGATE '
STAT #1 id=2 cnt=99200 pid=1 pos=1 obj=5187 op='TABLE ACCESS FULL T '
XCTEND rlbk=0, rd_only=1

I expected to see in the trace file that oracle tried to
read 128 blocks at a time twice and 4 blocks next time for each extent . My
question is why do i see as below .

WAIT #1: nam='db file scattered read' ela= 5 p1=3 p2=867 p3=128
WAIT #1: nam='db file scattered read' ela= 1 p1=3 p2=995 p3=47
Also i see a p3=39 once and i dont  see this for all extents .

          Followup:

the table isn't full.  we only read what we need -- and some of it can be in the
buffer cache (hence we won't read that necessarily).

It "looks" like you are using 3 of the 5 extents to hold data (this is probably,
not 100%, caching can affect this).

extent 1: blocks 867 .. 1042 (128+47)
extent 2: blocks 1302 .. 1562 (128+128+4)
extent 3: blocks 2082 .. 2249 (128+39)

the other two extents have nothing, two of the three that do have data are not
full.

      Rating:
      5
db_file_multiblock_read_count IN 9i VS 8i  April 17,
      2003
Reviewer:  Saradha Bavanandam  from NJ, USA

Tom,

We are currently upgrading our databases to Oracle 9i one by one. Currently we
have two development databases one running 8i version and other running 9i
version. These two databases exactly same in size, table structures, data and
parameters. We have same application running under 8i and 9i database. The
performance in 8i database is good and the performance on 9i database is very
bad.

After so much of investigation, we have modified db_file_multiblock_read_count
parameter to 8 in the Oracle9i database. Initially it was 32. After reducing
this parameter to 8 the performance in 9i database is better.
But this parameter is in 8i database is set to 32 and the performance is fine.

Do the behaviour or influence of this parameter in Oracle9i
different from 8i database.

What we should do when we upgrade our production databases.
Currently the production (8i) database is set to 32.

Production databases and development database are in two different unix boxes.

Please advice.

Thanks
Saradha

          Followup:

have you considered comparing query plans -- statspack -- anything "database
wise" to see what might be different in the two.

the parameter you changed affects the costing of full scans.  The bigger the
number, the lower the cost.  that, combined with a totally different cluster
factor and a host of other things -- could affect the plans.  Rather then tweak
random knobs and dials -- you should figure out "whats different" between the
two and diagnose it from that perspective.

(hint, look at optimizer_* init parameters even more then the one you tweaked
already -- see what it does to the costing of index plans)

      Rating: 4
explain
      plan  April 21, 2003
Reviewer:  Saradha
      Bavanandam  from NJ, USA
Tom,

I have printed the execution plan of the same query in 8i
and 9i. The plan is different for the same query where the
database setup, size, structure are exactly the same in 8i and 9i.

SELECT
    A.ID,
    CV.CURRENCY_RATE,
    I.BID_DRIVER_ID,
    CRA.ACTIVITY_ID,
    NVL(I.RANGE_DRIVER_ID, -1),
    NVL(MAX_DRIVER_VALUE, 0),
    NVL(MIN_DRIVER_VALUE, 0) ,
    I.COMP_VALUE,
    I.BILL_TYPE_ID
  FROM COMP_AGREEMENT_LANES L,
    COMP_AGREEMENT A,
    CURRENCY_VALUES CV,
    COMP_AGREEMENT_SET CAS,
    MSP_GROUP_ITEMS MGI,
    COMP_RATE_ACTIVITY CRA,
    COMP_AGREEMENT_ITEMS I,
    USER_DATA_SET UDS
  WHERE ( L.ORIG_LOCATION_ID = :oraAgentLocationId
    OR L.DEST_LOCATION_ID = :oraAgentLocationId)
    AND A.IMP_EXP = :oraAgentImpExp
    AND MGI.MSP_ID = :oraAgentMSPId
    AND L.MSP_COLLECTION_ID = MGI.MSP_GROUP_ID
    AND L.ORIG_PC_LOW IS NULL
    AND L.ORIG_PC_HIGH IS NULL
    AND L.DEST_PC_LOW IS NULL
    AND L.DEST_PC_HIGH IS NULL
    AND L.COMP_AGREEMENT_ID = A.ID
    AND L.COMP_AGREEMENT_SET_ID = CAS.ID
    AND UDS.CURRENCY_SET_ID = CV.CURRENCY_SET_ID
    AND UDS.id = :oraUserDataSetId
    AND CV.CURRENCY_ID = A.CURRENCY_ID
    AND EXISTS (
      SELECT * FROM COMP_AGREEMENT_SET_DEFN D
      WHERE A.ID = D.COMP_AGREEMENT_ID
      AND D.COMP_AGREEMENT_SET_ID = :oraCompAgreementSetId )
    AND I.COMP_AGREEMENT_ID = A.ID
    AND CRA.BID_DRIVER_ID = I.BID_DRIVER_ID
  ORDER BY I.BILL_TYPE_ID DESC;

SQL> show parameter optim

NAME                                 TYPE    VALUE
------------------------------------ ------- ------
optimizer_features_enable            string  8.1.7
optimizer_index_caching              integer 0
optimizer_index_cost_adj             integer 100
optimizer_max_permutations           integer 80000
optimizer_mode                       string  CHOOSE
optimizer_percent_parallel           integer 0
db_file_multiblock_read_count        integer 8

8i:SQL> set autotrace on exp
8i:SQL> @iq
no rows selected.

   1    0   SORT (ORDER BY) (Cost=133 Card=16 Bytes=1504)
   2    1     CONCATENATION
   3    2       HASH JOIN (Cost=37 Card=3 Bytes=264)
   4    3         NESTED LOOPS (Cost=34 Card=3 Bytes=246)
   5    4           HASH JOIN (Cost=22 Card=3 Bytes=189)
   6    5             NESTED LOOPS (Cost=19 Card=3 Bytes=153)
   7    6               NESTED LOOPS (Cost=37 Card=4 Bytes=376)
   8    7                 NESTED LOOPS (Cost=13 Card=3 Bytes=126)
   9    8                   NESTED LOOPS (Cost=13 Card=3 Bytes=117)
  10    9                     TABLE ACCESS (BY INDEX ROWID) OF 'USER_DATA_SET'
(Cost=1 Card=1 Bytes=6)
  11   10                       INDEX (UNIQUE SCAN) OF 'PK_USER_DATA_SET'
(UNIQUE)
  12    9                     TABLE ACCESS (BY INDEX ROWID) OF
'COMP_AGREEMENT_LANES' (Cost=12 Card=3 Bytes=99)
  13   12                       INDEX (RANGE SCAN) OF
'IX_COMP_AGREEMENT_LANES_IMP' (NON-UNIQUE) (Cost=11 Card=3)
  14    8                   INDEX (UNIQUE SCAN) OF 'PK_COMP_AGREEMENT_SET'
(UNIQUE)
  15    7                 INDEX (UNIQUE SCAN) OF 'PK_MSP_GROUP_ITEMS' (UNIQUE)
  16    6               TABLE ACCESS (BY INDEX ROWID) OF 'COMP_AGREEMENT'
(Cost=2 Card=20488 Bytes=184392)
  17   16                 INDEX (UNIQUE SCAN) OF 'IDX_CA_IMP_EXP' (UNIQUE)
(Cost=1 Card=20488)
  18   17                   INDEX (UNIQUE SCAN) OF 'PK_COMP_AGREEMENT_SET_DEFN'
(UNIQUE) (Cost=2 Card=1 Bytes=8)
  19    5             TABLE ACCESS (FULL) OF 'CURRENCY_VALUES' (Cost=1  Card=215
Bytes=2580)
  20    4           TABLE ACCESS (BY INDEX ROWID) OF 'COMP_AGREEMENT_ITEMS'
(Cost=4 Card=819508 Bytes=15570652)
  21   20             INDEX (RANGE SCAN) OF 'CAI_NONUNIQUE' (NON-UNIQUE) (Cost=3
Card=819508)
  22    3         INDEX (FULL SCAN) OF 'PK_COMP_RATE_ACTIVITY' (UNIQUE) (Cost=1
Card=7 Bytes=42)
  23    2       NESTED LOOPS (Cost=37 Card=4 Bytes=376)
  24   23         NESTED LOOPS (Cost=37 Card=3 Bytes=264)
  25   24           NESTED LOOPS (Cost=34 Card=3 Bytes=246)
  26   25             NESTED LOOPS (Cost=22 Card=3 Bytes=189)
  27   26               NESTED LOOPS (Cost=19 Card=3 Bytes=153)
  28   27                 NESTED LOOPS (Cost=13 Card=3 Bytes=126)
  29   28                   NESTED LOOPS (Cost=13 Card=3 Bytes=117)
  30   29                     TABLE ACCESS (BY INDEX ROWID) OF 'USER_DATA_SET'
(Cost=1 Card=1 Bytes=6)
  31   30                       INDEX (UNIQUE SCAN) OF 'PK_USER_DATA_SET'
(UNIQUE)
  32   29                     TABLE ACCESS (BY INDEX ROWID) OF
'COMP_AGREEMENT_LANES' (Cost=12 Card=3 Bytes=99)
  33   32                       INDEX (RANGE SCAN) OF 'IX_COMP_AGREEMENT_LANES'
(NON-UNIQUE) (Cost=11 Card=3)
  34   28                   INDEX (UNIQUE SCAN) OF 'PK_COMP_AGREEMENT_SET'
(UNIQUE)
  35   27                 TABLE ACCESS (BY INDEX ROWID) OF 'COMP_AGREEMENT'
(Cost=2 Card=20488 Bytes=184392)
  36   35                   INDEX (UNIQUE SCAN) OF 'IDX_CA_IMP_EXP' (UNIQUE)
(Cost=1 Card=20488)
  37   26               TABLE ACCESS (BY INDEX ROWID) OF 'CURRENCY_VALUES'
(Cost=1 Card=215 Bytes=2580)
  38   37                 INDEX (UNIQUE SCAN) OF 'PK_CURRENCY_VALUES'
(UNIQUE)
  39   25             TABLE ACCESS (BY INDEX ROWID) OF 'COMP_AGREEMENT_ITEMS'
(Cost=4 Card=819508 Bytes=15570652)
  40   39               INDEX (RANGE SCAN) OF 'CAI_NONUNIQUE' (NON-UNIQUE)
(Cost=3 Card=819508)
  41   24           INDEX (RANGE SCAN) OF 'PK_COMP_RATE_ACTIVITY' (UNIQUE)
(Cost=1 Card=7 Bytes=42)
  42   23         INDEX (UNIQUE SCAN) OF 'PK_MSP_GROUP_ITEMS' (UNIQUE)

In Oracle9i
9i:SQL> show parameter optimizer

NAME                                 TYPE        VALUE
------------------------------------ ----------- -------timizer_dynamic_sampling
          integer     1
optimizer_features_enable            string      9.2.0
optimizer_index_caching              integer     0
optimizer_index_cost_adj             integer     100
optimizer_max_permutations           integer     2000
optimizer_mode                       string      CHOOSE
db_file_multiblock_read_count        integer     32

9i:SQL> alter session set db_file_multiblock_read_count=32;
Session altered.
9i:SQL> set autotrace on exp
9i:SQL> @iq.sql
no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=491 Card=34 Bytes=3468)
   1    0   SORT (ORDER BY) (Cost=491 Card=34 Bytes=3468)
   2    1     HASH JOIN (Cost=489 Card=34 Bytes=3468)
   3    2       HASH JOIN (Cost=487 Card=34 Bytes=3264)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'COMP_AGREEMENT_ITEMS'
(Cost=3 Card=1 Bytes=19)
   5    4           NESTED LOOPS (Cost=484 Card=34 Bytes=2856)
   6    5             NESTED LOOPS (SEMI) (Cost=382 Card=34 Bytes=2210)
   7    6               NESTED LOOPS (Cost=348 Card=34 Bytes=1938)
   8    7                 NESTED LOOPS (Cost=280 Card=34 Bytes=1632)
   9    8                   NESTED LOOPS (Cost=280 Card=34 Bytes=1530)
  10    9                     NESTED LOOPS (Cost=280 Card=222 Bytes=8658)
  11   10                       TABLE ACCESS (BY INDEX ROWID) OF 'USER_DATA_SET'
(Cost=1 Card=1 Bytes=6)
  12   11                         INDEX (UNIQUE SCAN) OF 'PK_USER_DATA_SET'
(UNIQUE)
  13   10                       TABLE ACCESS (FULL) OF 'COMP_AGREEMENT_LANES'
(Cost=279 Card=222 Bytes=7326)
  14    9                     INDEX (UNIQUE SCAN) OF 'PK_MSP_GROUP_ITEMS'
(UNIQUE)
  15    8                   INDEX (UNIQUE SCAN) OF 'PK_COMP_AGREEMENT_SET'
(UNIQUE)
  16    7                 TABLE ACCESS (BY INDEX ROWID) OF 'COMP_AGREEMENT'
(Cost=2 Card=409754 Bytes=3687786)
  17   16                   INDEX (UNIQUE SCAN) OF 'IDX_CA_IMP_EXP' (UNIQUE)
(Cost=1 Card=2)
  18    6               INDEX (UNIQUE SCAN) OF 'PK_COMP_AGREEMENT_SET_DEFN'
(UNIQUE) (Cost=1 Card=819508 Bytes=6556064)
  19    5             INDEX (RANGE SCAN) OF 'CAI_NONUNIQUE' (NON-UNIQUE) (Cost=2
Card=1)
  20    3         TABLE ACCESS (FULL) OF 'CURRENCY_VALUES' (Cost=2 Card=215
Bytes=2580)
  21    2       INDEX (FULL SCAN) OF 'PK_COMP_RATE_ACTIVITY' (UNIQUE) (Cost=1
Card=7 Bytes=42)

Now I changed the db_file_multiblock_read_count=8 and ran
the same query.

9i:SQL> alter session set db_file_multiblock_read_count=8;
Session altered.
9i:SQL> @iq.sql
no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=541 Card=34 Bytes=34
          68)
   1    0   SORT (ORDER BY) (Cost=541 Card=34 Bytes=3468)
   2    1     HASH JOIN (Cost=538 Card=34 Bytes=3468)
   3    2       HASH JOIN (Cost=536 Card=34 Bytes=3264)
   4    3         NESTED LOOPS (SEMI) (Cost=533 Card=34 Bytes=2856)
   5    4           NESTED LOOPS (Cost=499 Card=34 Bytes=2584)
   6    5             NESTED LOOPS (Cost=397 Card=34 Bytes=1938)
   7    6               NESTED LOOPS (Cost=329 Card=34 Bytes=1632)
   8    7                 HASH JOIN (Cost=329 Card=222 Bytes=9324)
   9    8                   NESTED LOOPS (Cost=2 Card=1 Bytes=9)
  10    9                     TABLE ACCESS (BY INDEX ROWID) OF 'USER_DATA_SET'
(Cost=1 Card=1 Bytes=6)
  11   10                       INDEX (UNIQUE SCAN) OF 'PK_USER_DATA_SET'
(UNIQUE)
  12    9                     INDEX (FULL SCAN) OF 'PK_COMP_AGREEMENT_SET'
(UNIQUE) (Cost=1 Card=1 Bytes=3)
  13    8                   TABLE ACCESS (BY INDEX ROWID) OF
'COMP_AGREEMENT_LANES' (Cost=325 Card=222 Bytes=7326)
  14   13                     BITMAP CONVERSION (TO ROWIDS)
  15   14                       BITMAP OR
  16   15                         BITMAP CONVERSION (FROM ROWIDS)
  17   16                           SORT (ORDER BY)
  18   17                             INDEX (RANGE SCAN) OF
'IX_COMP_AGREEMENT_LANES' (NON-UNIQUE) (Cost=7)
  19   15                         BITMAP CONVERSION (FROM ROWIDS)
  20   19                           SORT (ORDER BY)
  21   20                             INDEX (RANGE SCAN) OF
'IX_COMP_AGREEMENT_LANES_IMP' (NON-UNIQUE) (Cost=6)
  22    7                 INDEX (UNIQUE SCAN) OF 'PK_MSP_GROUP_ITEMS'   (UNIQUE)
  23    6               TABLE ACCESS (BY INDEX ROWID) OF 'COMP_AGREEMENT'
(Cost=2 Card=1 Bytes=9)
  24   23                 INDEX (UNIQUE SCAN) OF 'IDX_CA_IMP_EXP' (UNIQUE)
(Cost=1 Card=2)
  25    5             TABLE ACCESS (BY INDEX ROWID) OF 'COMP_AGREEMENT_ITEMS'
(Cost=3 Card=1 Bytes=19)
  26   25               INDEX (RANGE SCAN) OF 'CAI_NONUNIQUE' (NON-UNIQUE)
(Cost=2 Card=1)
  27    4           INDEX (UNIQUE SCAN) OF 'PK_COMP_AGREEMENT_SET_DEFN' (UNIQUE)
(Cost=1 Card=819508 Bytes=6556064)
  28    3         TABLE ACCESS (FULL) OF 'CURRENCY_VALUES' (Cost=2 Card=215
Bytes=2580)
  29    2       INDEX (FULL SCAN) OF 'PK_COMP_RATE_ACTIVITY' (UNIQUE) (Cost=1
Card=7 Bytes=42)

Specifically, When the db_file_multiblock_read_count=32, the
COMP_AGREEMENT_LANES goes for a FULL TABLE SCAN,
and when the db_file_multiblock_read_count=8, it uses the INDEX SCAN. But in
Oracle8i, when db_file_multiblock_read_count=32, it does the INDEX SCAN.
Basically the INDEX SCAN is faster in this case.

This is the query in particular causing the performance problem. The query is
doing a index scan and the performance is good in 8i even though the
db_file_multiblock_read_count=32.

To determine the optimal value, I did the following test.

SQL> alter session set events '10046 trace name context forever, level 8';

Session altered.

SQL> select /*+ FULL(t) */ count(*) from dba_source t;

  COUNT(*)
----------
    787035

WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=1787 p3=25
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=1851 p3=25
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=1931 p3=25
WAIT #1: nam='db file scattered read' ela= 0 p1=1 p2=2027 p3=25

I have again ran the same query by setting the db_file_multiblock_read_count=25
and the performance still bad and the plan is same as
db_file_multiblock_read_count=32.

Please advice. Should we try to change the query or should
we lower the db_file_multiblock_read_count to 8.

Thanks
Saradha

      Rating: 4
What is BITMAP
      INDEX CONVERSION  April 21, 2003
Reviewer:
      Saradha Bavanandam  from NJ, USA
Tom,

If you notice the EXECUTION PLAN, it say BITMAP CONVERSION
and can you please explain me in what situation this happens. We do not have any
BITMAP INDEX at all.

 13    8                   TABLE ACCESS (BY INDEX ROWID) OF
'COMP_AGREEMENT_LANES' (Cost=325 Card=222 Bytes=7326)
  14   13                     BITMAP CONVERSION (TO ROWIDS)
  15   14                       BITMAP OR
  16   15                         BITMAP CONVERSION (FROM ROWIDS)
  17   16                           SORT (ORDER BY)
  18   17                             INDEX (RANGE SCAN) OF
'IX_COMP_AGREEMENT_LANES' (NON-UNIQUE) (Cost=7)
  19   15                         BITMAP CONVERSION (FROM ROWIDS)
  20   19                           SORT (ORDER BY)
  21   20                             INDEX (RANGE SCAN) OF
'IX_COMP_AGREEMENT_LANES_IMP' (NON-UNIQUE) (Cost=6)

Thanks
Saradha

          Followup:

you do not need a bitmap, it is a valid plan -- it is creating a bitmap index on
the fly...

what are the time differences here do

alter session set the multi-block read count to 8
alter session set sql_trace=true;
run query
exit

do again but with 32 and post just the relevant portions of the tkprof

      Rating: 5
touch count in
      9i R2  April 22, 2003
Reviewer:  A reader
       from Houston, TX
Tom, Your wonderful book explained the touch count concept. If I tried it in
9iR2, the x$bh has different columns. Is there a way that you can give the touch
count demo in 9iR2? Thanks as always for your time.

          Followup:

works for me?  what problem are you hitting exactly

      Rating:
      5
Works!  April 23, 2003
Reviewer:  A
      reader  from Houston, TX
Tom, I tried again. It works. Sorry for the confusion.

Just one clarification. Does it mean that if the block is not touched for some
time, and if the space is needed for other blocks, this "untouched" block will
be removed from the list, can i say, the same LRU list. Does Oracle still use
the LRU list alogrithm in 9i and touch count is the way it is managed? Thanks.
Your book is the best in the market. I have read it two times already.

          Followup:

correct, the less often the block is "touched" the more probable it will be
removed over time from the cache itself.

Yes, the touch count algorithm is still in effect.

      Rating: 4
why the
      execution plan is different in 8i and 9i  April 24, 2003

Reviewer:  Saradha Bavanandam  from NJ, USA

Tom,

I have taken the trace in 9i and in 8i where the two instanace are the same in
everything except the oracle version. I ran this query but currently no matching
data
available in both the instances and I ran the query with
"no rows selected". In both the instances the
db_file_multiblock_read_count=32.

My primary question is, the execution plan is totally look
different in 9i as compared to 8i. The performance in 8i was good for this query
and it is bad in 9i instance.
Because in Oracle9i, for a table (COMP_AGREEMENT_LANES), the optimizer chooses
the FULL TABLE SCAN but in 8i the same
query the optimizer choosess the INDEX SCAN.

I would like to verify first why the plan is so different
in 9i and 8i when the matching parameters in both the database are the same.

SELECT
    A.ID,
    CV.CURRENCY_RATE,
    I.BID_DRIVER_ID,
    CRA.ACTIVITY_ID,
    NVL(I.RANGE_DRIVER_ID, -1),
    NVL(MAX_DRIVER_VALUE, 0),
    NVL(MIN_DRIVER_VALUE, 0) ,
    I.COMP_VALUE,
    I.BILL_TYPE_ID
FROM COMP_AGREEMENT_LANES L,
    COMP_AGREEMENT A,
    CURRENCY_VALUES CV,
    COMP_AGREEMENT_SET CAS,
    MSP_GROUP_ITEMS MGI,
    COMP_RATE_ACTIVITY CRA,
    COMP_AGREEMENT_ITEMS I,
    USER_DATA_SET UDS
WHERE ( L.ORIG_LOCATION_ID = 3596
    OR L.DEST_LOCATION_ID = 3596)
    AND A.IMP_EXP = 'E'
    AND MGI.MSP_ID = 123
    AND L.MSP_COLLECTION_ID = MGI.MSP_GROUP_ID
    AND L.ORIG_PC_LOW IS NULL
    AND L.ORIG_PC_HIGH IS NULL
    AND L.DEST_PC_LOW IS NULL
    AND L.DEST_PC_HIGH IS NULL
    AND L.COMP_AGREEMENT_ID = A.ID
    AND L.COMP_AGREEMENT_SET_ID = CAS.ID
    AND UDS.CURRENCY_SET_ID = CV.CURRENCY_SET_ID
    AND UDS.id = 564
    AND CV.CURRENCY_ID = A.CURRENCY_ID
    AND EXISTS (
      SELECT * FROM COMP_AGREEMENT_SET_DEFN D
      WHERE A.ID = D.COMP_AGREEMENT_ID
      AND D.COMP_AGREEMENT_SET_ID = 377 )
    AND I.COMP_AGREEMENT_ID = A.ID
    AND CRA.BID_DRIVER_ID = I.BID_DRIVER_ID
ORDER BY I.BILL_TYPE_ID DESC

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      2.58       2.53          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      1.49       1.65       4467       4561          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      4.07       4.19       4467       4561          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 59

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  SORT ORDER BY (cr=4561 r=4467 w=0 time=1659641 us)
      0   HASH JOIN  (cr=4561 r=4467 w=0 time=1659556 us)
      0    HASH JOIN  (cr=4561 r=4467 w=0 time=1658996 us)
      0     TABLE ACCESS BY INDEX ROWID COMP_AGREEMENT_ITEMS (cr=4561 r=4467 w=0
time=1658502 us)
      1      NESTED LOOPS  (cr=4561 r=4467 w=0 time=1658472 us)
      0       NESTED LOOPS SEMI (cr=4561 r=4467 w=0 time=1658450 us)
      0        NESTED LOOPS  (cr=4561 r=4467 w=0 time=1658442 us)
      0         NESTED LOOPS  (cr=4561 r=4467 w=0 time=1658436 us)
      0          NESTED LOOPS  (cr=4561 r=4467 w=0 time=1658428 us)
      0           NESTED LOOPS  (cr=4561 r=4467 w=0 time=1658421 us)
      1            TABLE ACCESS BY INDEX ROWID USER_DATA_SET (cr=2 r=0 w=0
time=223 us)
      1             INDEX UNIQUE SCAN PK_USER_DATA_SET (cr=1 r=0 w=0 time=123
us)(object id 139307)
      0            TABLE ACCESS FULL COMP_AGREEMENT_LANES (cr=4559 r=4467 w=0
time=1658175 us)
      0           INDEX UNIQUE SCAN PK_MSP_GROUP_ITEMS (cr=0 r=0 w=0 time=0
us)(object id 139300)
      0          INDEX UNIQUE SCAN PK_COMP_AGREEMENT_SET (cr=0 r=0 w=0 time=0
us)(object id 139243)
      0         TABLE ACCESS BY INDEX ROWID COMP_AGREEMENT (cr=0 r=0 w=0 time=0
us)
      0          INDEX UNIQUE SCAN IDX_CA_IMP_EXP (cr=0 r=0 w=0 time=0
us)(object id 143072)
      0        INDEX UNIQUE SCAN PK_COMP_AGREEMENT_SET_DEFN (cr=0 r=0 w=0 time=0
us)(object id 139247)
      0       INDEX RANGE SCAN CAI_NONUNIQUE (cr=0 r=0 w=0 time=0 us)(object id
143071)
      0     TABLE ACCESS FULL CURRENCY_VALUES (cr=0 r=0 w=0 time=0 us)
      0    INDEX FULL SCAN PK_COMP_RATE_ACTIVITY (cr=0 r=0 w=0 time=0 us)(object
id 139230)

********************************************************************************

In Oracle8i:
----------------------------------------
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.57       0.57          0          0          0           0
Execute      1      0.04       0.04          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.61       0.61          0          0          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 78

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  SORT ORDER BY
      0   CONCATENATION
      0    HASH JOIN
      0     NESTED LOOPS
      0      HASH JOIN
      0       NESTED LOOPS
      0        NESTED LOOPS
      0         NESTED LOOPS
      0          NESTED LOOPS
      0           TABLE ACCESS BY INDEX ROWID USER_DATA_SET
      0            INDEX UNIQUE SCAN (object id 659595)
      0           TABLE ACCESS BY INDEX ROWID COMP_AGREEMENT_LANES
      0            INDEX RANGE SCAN (object id 661403)
      0          INDEX UNIQUE SCAN (object id 659554)
      0         INDEX UNIQUE SCAN (object id 659585)
      0        TABLE ACCESS BY INDEX ROWID COMP_AGREEMENT
      0         INDEX UNIQUE SCAN (object id 661411)
      0       TABLE ACCESS FULL CURRENCY_VALUES
      0      TABLE ACCESS BY INDEX ROWID COMP_AGREEMENT_ITEMS
      0       INDEX RANGE SCAN (object id 661410)
      0     INDEX FULL SCAN (object id 659517)
      0    NESTED LOOPS
      0     NESTED LOOPS
      0      NESTED LOOPS
      0       NESTED LOOPS
      0        NESTED LOOPS
      0         NESTED LOOPS
      0          NESTED LOOPS
      0           TABLE ACCESS BY INDEX ROWID USER_DATA_SET
      0            INDEX UNIQUE SCAN (object id 659595)
      0           TABLE ACCESS BY INDEX ROWID COMP_AGREEMENT_LANES
      0            INDEX RANGE SCAN (object id 661405)
      0          INDEX UNIQUE SCAN (object id 659554)
      0         TABLE ACCESS BY INDEX ROWID COMP_AGREEMENT
      0          INDEX UNIQUE SCAN (object id 661411)
      0        TABLE ACCESS BY INDEX ROWID CURRENCY_VALUES
      0         INDEX UNIQUE SCAN (object id 659538)
      0       TABLE ACCESS BY INDEX ROWID COMP_AGREEMENT_ITEMS
      0        INDEX RANGE SCAN (object id 661410)
      0      INDEX RANGE SCAN (object id 659517)
      0     INDEX UNIQUE SCAN (object id 659585)

This table (COMP_AGREEMENT_LANE) is joined with many other tables in the query.
But if I take the portion of the query for this table alone and run in both 9i
and 8i, the execution plan is the same.

For example,
SQL> select MSP_COLLECTION_ID, COMP_AGREEMENT_SET_ID, COMP_AGREEMENT_ID
  2  from COMP_AGREEMENT_LANES xx, COMP_AGREEMENT_SET yy
  3  where (xx.DEST_LOCATION_ID = 2430 OR xx.ORIG_LOCATION_ID = 2430)
  4  and xx.ORIG_PC_LOW IS NULL
  5  and xx.ORIG_PC_HIGH IS NULL
  6  and xx.DEST_PC_LOW IS NULL
  7  and xx.DEST_PC_HIGH IS NULL
  8* and xx.COMP_AGREEMENT_SET_ID = yy.ID

----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=31 Card=60 Bytes=216
          0)

   1    0   CONCATENATION
   2    1     NESTED LOOPS (Cost=17 Card=3 Bytes=108)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'COMP_AGREEMENT_LANES
          ' (Cost=17 Card=3 Bytes=99)

   4    3         INDEX (RANGE SCAN) OF 'IX_COMP_AGREEMENT_LANES' (NON
          -UNIQUE) (Cost=8 Card=3)

   5    2       INDEX (UNIQUE SCAN) OF 'PK_COMP_AGREEMENT_SET' (UNIQUE
          )

   6    1     NESTED LOOPS (Cost=17 Card=3 Bytes=108)
   7    6       TABLE ACCESS (BY INDEX ROWID) OF 'COMP_AGREEMENT_LANES
          ' (Cost=17 Card=3 Bytes=99)

   8    7         INDEX (RANGE SCAN) OF 'IX_COMP_AGREEMENT_LANES_IMP'
          (NON-UNIQUE) (Cost=8 Card=3)

   9    6       INDEX (UNIQUE SCAN) OF 'PK_COMP_AGREEMENT_SET' (UNIQUE
          )

I get the same execution plan in 8i as well as in 9i.
But in the actual query, this table's access path is totally different.

Thanks
Saradha

          Followup:

I do not see any fetch calls in 8i -- why not?

      Rating:
      4
  April 24, 2003
Reviewer:  Saradha
      Bavanadam  from NJ, USA
Tom,
I ran once again but by replacing one of the bind value to
A different one and you can see fetch calls in 8i as well as 9i but the plan is
different.

In Oracle9i:
------------------------------
SELECT
    A.ID,
    CV.CURRENCY_RATE,
    I.BID_DRIVER_ID,
    CRA.ACTIVITY_ID,
    NVL(I.RANGE_DRIVER_ID, -1),
    NVL(MAX_DRIVER_VALUE, 0),
    NVL(MIN_DRIVER_VALUE, 0) ,
    I.COMP_VALUE,
    I.BILL_TYPE_ID
FROM COMP_AGREEMENT_LANES L,
    COMP_AGREEMENT A,
    CURRENCY_VALUES CV,
    COMP_AGREEMENT_SET CAS,
    MSP_GROUP_ITEMS MGI,
    COMP_RATE_ACTIVITY CRA,
    COMP_AGREEMENT_ITEMS I,
    USER_DATA_SET UDS
WHERE ( L.ORIG_LOCATION_ID = 2430
    OR L.DEST_LOCATION_ID = 2430)
    AND A.IMP_EXP = 'E'
    AND MGI.MSP_ID = 123
    AND L.MSP_COLLECTION_ID = MGI.MSP_GROUP_ID
    AND L.ORIG_PC_LOW IS NULL
    AND L.ORIG_PC_HIGH IS NULL
    AND L.DEST_PC_LOW IS NULL
    AND L.DEST_PC_HIGH IS NULL
    AND L.COMP_AGREEMENT_ID = A.ID
    AND L.COMP_AGREEMENT_SET_ID = CAS.ID
    AND UDS.CURRENCY_SET_ID = CV.CURRENCY_SET_ID
    AND UDS.id = 564
    AND CV.CURRENCY_ID = A.CURRENCY_ID
    AND EXISTS (
      SELECT * FROM COMP_AGREEMENT_SET_DEFN D
      WHERE A.ID = D.COMP_AGREEMENT_ID
      AND D.COMP_AGREEMENT_SET_ID = 377 )
    AND I.COMP_AGREEMENT_ID = A.ID
    AND CRA.BID_DRIVER_ID = I.BID_DRIVER_ID
ORDER BY I.BILL_TYPE_ID DESC

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      2.71       2.68          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.01       0.00          0         12          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      2.72       2.68          0         12          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 59

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  SORT ORDER BY (cr=12 r=0 w=0 time=4044 us)
      0   HASH JOIN  (cr=12 r=0 w=0 time=3995 us)
      0    HASH JOIN  (cr=12 r=0 w=0 time=3504 us)
      0     NESTED LOOPS SEMI (cr=12 r=0 w=0 time=3079 us)
      0      NESTED LOOPS  (cr=12 r=0 w=0 time=3071 us)
      0       NESTED LOOPS  (cr=12 r=0 w=0 time=3068 us)
      0        NESTED LOOPS  (cr=12 r=0 w=0 time=3065 us)
     18         HASH JOIN  (cr=10 r=0 w=0 time=2900 us)
      1          NESTED LOOPS  (cr=3 r=0 w=0 time=393 us)
      1           TABLE ACCESS BY INDEX ROWID USER_DATA_SET (cr=2 r=0 w=0
time=234 us)
      1            INDEX UNIQUE SCAN PK_USER_DATA_SET (cr=1 r=0 w=0 time=123
us)(object id 139307)
      1           INDEX FULL SCAN PK_COMP_AGREEMENT_SET (cr=1 r=0 w=0 time=119
us)(object id 139243)
     18          TABLE ACCESS BY INDEX ROWID COMP_AGREEMENT_LANES (cr=7 r=0 w=0
time=890 us)
     18           BITMAP CONVERSION TO ROWIDS (cr=6 r=0 w=0 time=696 us)
      1            BITMAP OR  (cr=6 r=0 w=0 time=649 us)
      1             BITMAP CONVERSION FROM ROWIDS (cr=3 r=0 w=0 time=423 us)
     12              SORT ORDER BY (cr=3 r=0 w=0 time=340 us)
     12               INDEX RANGE SCAN IX_COMP_AGREEMENT_LANES (cr=3 r=0 w=0
time=141 us)(object id 143075)
      1             BITMAP CONVERSION FROM ROWIDS (cr=3 r=0 w=0 time=136 us)
      6              SORT ORDER BY (cr=3 r=0 w=0 time=122 us)
      6               INDEX RANGE SCAN IX_COMP_AGREEMENT_LANES_IMP (cr=3 r=0 w=0
time=73 us)(object id 143073)
      0         INDEX UNIQUE SCAN PK_MSP_GROUP_ITEMS (cr=2 r=0 w=0 time=82
us)(object id 139300)
      0        TABLE ACCESS BY INDEX ROWID COMP_AGREEMENT (cr=0 r=0 w=0 time=0
us)
      0         INDEX UNIQUE SCAN IDX_CA_IMP_EXP (cr=0 r=0 w=0 time=0 us)(object
id 143072)
      0       TABLE ACCESS BY INDEX ROWID COMP_AGREEMENT_ITEMS (cr=0 r=0 w=0
time=0 us)
      0        INDEX RANGE SCAN CAI_NONUNIQUE (cr=0 r=0 w=0 time=0 us)(object id
143071)
      0      INDEX UNIQUE SCAN PK_COMP_AGREEMENT_SET_DEFN (cr=0 r=0 w=0 time=0
us)(object id 139247)
      0     TABLE ACCESS FULL CURRENCY_VALUES (cr=0 r=0 w=0 time=0 us)
      0    INDEX FULL SCAN PK_COMP_RATE_ACTIVITY (cr=0 r=0 w=0 time=0 us)(object
id 139230)

********************************************************************************

In Oracle8i:
---------------------
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.61       0.64          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.01       0.00          0        148          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.62       0.64          0        148          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 78

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  SORT ORDER BY
      0   CONCATENATION
      0    HASH JOIN
      0     NESTED LOOPS
      1      HASH JOIN
      0       NESTED LOOPS
      1        NESTED LOOPS
      7         NESTED LOOPS
      7          NESTED LOOPS
      2           TABLE ACCESS BY INDEX ROWID USER_DATA_SET
      2            INDEX UNIQUE SCAN (object id 659595)
      7           TABLE ACCESS BY INDEX ROWID COMP_AGREEMENT_LANES
      7            INDEX RANGE SCAN (object id 661403)
     12          INDEX UNIQUE SCAN (object id 659554)
      6         INDEX UNIQUE SCAN (object id 659585)
      0        TABLE ACCESS BY INDEX ROWID COMP_AGREEMENT
      0         INDEX UNIQUE SCAN (object id 661411)
      0       TABLE ACCESS FULL CURRENCY_VALUES
      0      TABLE ACCESS BY INDEX ROWID COMP_AGREEMENT_ITEMS
      0       INDEX RANGE SCAN (object id 661410)
      0     INDEX FULL SCAN (object id 659517)
      0    NESTED LOOPS
     13     NESTED LOOPS
     13      NESTED LOOPS
     13       NESTED LOOPS
     13        NESTED LOOPS
     13         NESTED LOOPS
     13          NESTED LOOPS
      2           TABLE ACCESS BY INDEX ROWID USER_DATA_SET
      2            INDEX UNIQUE SCAN (object id 659595)
     13           TABLE ACCESS BY INDEX ROWID COMP_AGREEMENT_LANES
     13            INDEX RANGE SCAN (object id 661405)
     24          INDEX UNIQUE SCAN (object id 659554)
     24         TABLE ACCESS BY INDEX ROWID COMP_AGREEMENT
     24          INDEX UNIQUE SCAN (object id 661411)
     24        TABLE ACCESS BY INDEX ROWID CURRENCY_VALUES
     24         INDEX UNIQUE SCAN (object id 659538)
     24       TABLE ACCESS BY INDEX ROWID COMP_AGREEMENT_ITEMS
     24        INDEX RANGE SCAN (object id 661410)
     24      INDEX RANGE SCAN (object id 659517)
      0     INDEX UNIQUE SCAN (object id 659585)
     12          INDEX UNIQUE SCAN (object id 659526)

********************************************************************************
The count of rows in both the databases are the exactly same.

Thanks
Saradha

          Followup:

and the 8i database used the wrong plan - now you are confusing me.

The 9i plan above is "correct" and much better then the 8i plan was.  The parse
took longer -- but it was a hard parse.  run it again so it is soft and see.

12 logical IO's 9i.
148 logical IO's 8i.

9i wins.

      Rating: 4
What
      else  April 24, 2003
Reviewer:  Saradha
      Bavanandam  from NJ, USA
Tom,

We have tested this query in another 9i instance
where we have these same set of tables with the same
number of rows and interestingly the execution plan is
much much similar to the one in 8i.

So, it comes down to only this 9i instance that is behaving differently. The
tables, indexes are created, populated and analyzed exactly the same way in both
the instance.
The parameters are all the same.

What else I should check in this 9i instance which is causing this performance
problem.

Thanks
Saradha

          Followup:

umm, it seems to be running faster?

      Rating: 4
consistent
      gets  April 24, 2003
Reviewer:  saradha
      bavanandam  from NJ, EDISON
Tom,

The consistent gets is different in 9i and 8i instances for
a table that has exactly the same number of rows (actuall it is exactly the same
data) I repeated
the following for a number of time it comes out the same. This the table I am
actually having problem with where the access path is FULL TABLE in 9i and INDEX
SCAN in 8i. Can
you please read the output and lead me anything from there.

In Oracle8i:
SQL> SELECT COUNT(*) FROM comp_agreement_lanes;
Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
       4520  consistent gets
          0  physical reads
          0  redo size
        478  bytes sent via SQL*Net to client
        438  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

In Oracle9i instance:
SQL> SELECT COUNT(*) FROM comp_agreement_lanes;
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       2744  consistent gets
          0  physical reads
          0  redo size
        493  bytes sent via SQL*Net to client
        655  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

May be is it something causing the execution plan to be different in 9i versus
8i.. Should I drop the table in both
the instance and recreate it and test the query to see if the plan is the same.

          Followup:

I am curious why you want 9i to slow down?  to do more work?

      Rating: 5
Comment on
      first explain plan  April 25, 2003
Reviewer:
      Johan Snyman  from JHB South Africa
The first explain plan comparison between 8i and 9i included the following
parameters:

For 8i:
optimizer_features_enable            string  8.1.7
optimizer_index_caching              integer 0
optimizer_index_cost_adj             integer 100
optimizer_max_permutations           integer 80000  <- **
optimizer_mode                       string  CHOOSE
optimizer_percent_parallel           integer 0
db_file_multiblock_read_count        integer 8

For 9i:
optimizer_dynamic_sampling           integer     1
optimizer_features_enable            string      9.2.0
optimizer_index_caching              integer     0
optimizer_index_cost_adj             integer     100
optimizer_max_permutations           integer     2000 <- **
optimizer_mode                       string      CHOOSE
db_file_multiblock_read_count        integer     32

Shurely the large difference in optimizer_max_permutations could affect the
capability of 9i to consider enough options to find an optimal plan ? Maybe this
is what makes them different ?

          Followup:

as well the multiblock read count which at 32 makes 9i cost a full scan cheaper
then 8i with a session of 8.

      Rating:
      4
optimizer_max_permutations=80000  April 25, 2003

Reviewer:  Saradha Bavanandam  from NJ, USA

Tom,

Actually the db_file_multiblock_read_count parameter was set to 32 only in 8i
instance. I have mistyped in my first posting as 8 in 8i. But it is actually 32
and that's why the whole concern is. Both the instance is set to 32 and why
the plan is different. With the FULL TABLE SCAN the perfromance is bad in 9i
instance.

Also I have tried to set the optimizer_max_permutations=80000
and tried again but the plan did not change.

Any other direction or clue would be greatly appreciated.
Thanks
Saradha

          Followup:

sigh, i must be totally missing something somewhere - unless you are changing
all of the cuts and pastes -- the ONLY thing I've seen so far shows "9i's plan
is better then 8i's"

Tell you what - if you can get it all together, provide the correct, accurate,
concise information in the form of the init.oras and the tkprofs that
demonstrate the issue AND i'm taking questions -- feel free to post it as a
question.  this is getting too far off topic here and at this point -- I don't
trust the information above.

      Rating: 5
"Touch
      Count"  April 25, 2003
Reviewer:  Robert
       from Memphis, USA
[If I may...] Discussed in Tom's book on pages 78 and 79.

      Rating: 4
OS IO Buffer
      Size  April 25, 2003
Reviewer:  Jack
      Mulhollan  from Little Rock, AR, USA
I tried you test to try to determine the OS IO buffer size.  I couldn't set
db_file_multiblock_read_count higher than 32.

SQL> show parameter db_file_multiblock_read_count;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
db_file_multiblock_read_count        integer     16

SQL> alter session set db_file_multiblock_read_count = 1024;

Session altered.

SQL> show parameter db_file_multiblock_read_count;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
db_file_multiblock_read_count        integer     32

My db_block_size is 32k.

SQL> show parameter db_block_size;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
db_block_size                        integer     32768

I did the 10046 trace and saw that Oracle was waiting to read 32 blocks (which
makes sense).

=====================
PARSING IN CURSOR #3 len=49 dep=0 uid=21 oct=3 lid=21 tim=1051298646121978 hv=73
3395062 ad='8a617f00'
select /*+ full(a) */ count(*)
from temp_01_tb a
END OF STMT
PARSE #3:c=0,e=1004,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=4,tim=1051298646121969
BINDS #3:
EXEC #3:c=0,e=96,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=1051298646122262
WAIT #3: nam='SQL*Net message to client' ela= 4 p1=1650815232 p2=1 p3=0
WAIT #3: nam='db file scattered read' ela= 26274 p1=113 p2=278406 p3=32
WAIT #3: nam='db file scattered read' ela= 19549 p1=113 p2=278438 p3=32
WAIT #3: nam='db file scattered read' ela= 18979 p1=113 p2=278470 p3=32

...

My questions:

1) Should I conclude that my OS IO Buffer Size is 1m (32 x 32K) or is there some
other reason that I'm not able to set db_file_multiblock_read_count higher?

2) I do mostly full scans of multi-gig tables (large batch environment).  Should
I consider increasing my db_file_multiblock_read_count from 16 to 32?

Thanks.

          Followup:

1) conclude away....
2) sure

      Rating: 5
execution plan
      8i vs 9i  April 28, 2003
Reviewer:  Saradha
      Bavanandam  from NJ, USA
Tom,

Sorry If I misled you on this question. But my question remains the same.

Why do the execution plan for a query drastically change in a 9i instance as
compared to the 8i instance. The performance for this particular query is bad in
9i.

I have simplified the query from my original posting, in order to explain you
better.

SQL executed in 9i and in 8i instance:
---------------------------------------
SQL> SELECT L.AMT_CALC_ID
  2    FROM AMT_CALC_OPR L
  3    WHERE ( L.GO_CAT_ID = 2430 OR L.DT_CAT_ID = 2430)
  4    AND L.GO_PACT_MIN IS NULL
  5    AND L.GO_PACT_MAX IS NULL
  6    AND L.DT_PACT_MIN IS NULL
  7    AND L.DT_PACT_MAX IS NULL
  8   AND EXISTS (  SELECT 1 FROM AMT_CALC_DESC D
  9                 WHERE L.AMT_CALC_ID = D.AMT_CALC_ID
 10*                AND D.AMT_CALC_SET_ID = 377 );

Execution plan in 8i instance:
-----------------------------
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=19 Card=4 Bytes=108)
   1    0   CONCATENATION
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'AMT_CALC_OPR' (Cost=10 Card=1
Bytes=27)
   3    2       INDEX (RANGE SCAN) OF 'IX_AMT_CALC_OPR_IMP' (NON-UNIQUE) (Cost=9
Card=1)
   4    3         INDEX (UNIQUE SCAN) OF 'PK_AMT_CALC_DESC' (UNIQUE) (Cost=2
Card=1 Bytes=8)
   5    1     TABLE ACCESS (BY INDEX ROWID) OF 'AMT_CALC_OPR' (Cost=10 Card=1
Bytes=27)
   6    5       INDEX (RANGE SCAN) OF 'IX_AMT_CALC_OPR_EXP' (NON-UNIQUE) (Cost=9
Card=1)

Execution plan in 9i instance:
------------------------------
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=501 Card=222 Bytes=7770)
   1    0   NESTED LOOPS (SEMI) (Cost=501 Card=222 Bytes=7770)
   2    1     TABLE ACCESS (FULL) OF 'AMT_CALC_OPR' (Cost=279  Card=222
Bytes=5994)
   3    1     INDEX (UNIQUE SCAN) OF 'PK_AMT_CALC_DESC' (UNIQUE) (Cost=1
Card=819508 Bytes=6556064)

So the access path in 8i is better and it is faster but in 9i it chooses the
FULL TABLE SCAN on AMT_CALC_OPR table and the performance is very bad.

Now, I have modify the same query and take out the EXISTS correlated sub query
from the main query and run this in 8i and in 9i instance. This time the ACCESS
PATH is exactly the same.

SQL Executed in 8i and in 9i instance:
--------------------------------------
SQL> SELECT L.AMT_CALC_ID
  2    FROM AMT_CALC_OPR L
  3    WHERE ( L.GO_CAT_ID = 2430 OR L.DT_CAT_ID = 2430)
  4    AND L.GO_PACT_MIN IS NULL
  5    AND L.GO_PACT_MAX IS NULL
  6    AND L.DT_PACT_MIN IS NULL
  7    AND L.DT_PACT_MAX IS NULL;

Execution plan in 8i:
---------------------
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=28 Card=58 Bytes=1566)
   1    0   CONCATENATION
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'AMT_CALC_OPR' (Cost=11 Card=3
Bytes=81)
   3    2       INDEX (RANGE SCAN) OF 'IX_AMT_CALC_OPR_IMP' (NON-UNIQUE)
(Cost=11 Card=3)
   4    1     INDEX (RANGE SCAN) OF 'IX_AMT_CALC_OPR' (NON-UNIQUE) (Cost=11
Card=3 Bytes=81)

Execution plan in 9i:
----------------------
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=22 Card=57 Bytes=1539)
   1    0   CONCATENATION
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'AMT_CALC_OPR' (Cost=7 Card=3
Bytes=81)
   3    2       INDEX (RANGE SCAN) OF 'IX_AMT_CALC_OPR_IMP' (NON-UNIQUE) (Cost=7
Card=3)
   4    1     INDEX (RANGE SCAN) OF 'IX_AMT_CALC_OPR' (NON-UNIQUE) (Cost=7
Card=3 Bytes=81)

Again, I modify the main query and replace the EXISTS correlated sub query with
a simple join and execute in 9i instance. This time it chooses the INDEX SCAN
and the performance is good in 9i.

9:SQL>  SELECT L.AMT_CALC_ID
  2      FROM AMT_CALC_OPR L, AMT_CALC_DESC D
  3      WHERE ( L.GO_CAT_ID = 2430  OR  L.DT_CAT_ID = 2430)
  4      AND L.GO_PACT_MIN IS NULL
  5      AND L.GO_PACT_MAX IS NULL
  6      AND L.DT_PACT_MIN IS NULL
  7      AND L.DT_PACT_MAX IS NULL
  8      AND L.AMT_CALC_ID = D.AMT_CALC_ID
  9     AND D.AMT_CALC_SET_ID = 377;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=79 Card=57 Bytes=1995)
   1    0   CONCATENATION
   2    1     NESTED LOOPS (Cost=10 Card=3 Bytes=105)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'AMT_CALC_OPR' (Cost=7 Card=3
Bytes=81)
   4    3         INDEX (RANGE SCAN) OF 'IX_AMT_CALC_OPR_IMP'   (NON-UNIQUE)
(Cost=7 Card=3)
   5    2       INDEX (UNIQUE SCAN) OF 'PK_AMT_CALC_DESC' (UNIQUE) (Cost=1
Card=1 Bytes=8)
   6    1     NESTED LOOPS (Cost=10 Card=3 Bytes=105)
   7    6       INDEX (RANGE SCAN) OF 'IX_AMT_CALC_OPR' (NON-UNIQUE) (Cost=7
Card=3 Bytes=81)
   8    6       INDEX (UNIQUE SCAN) OF 'PK_AMT_CALC_DESC' (UNIQUE) (Cost=1
Card=1 Bytes=8)

SQL> desc AMT_CALC_OPR
 Name                                      Null?    Type
 ----------------------------------------- -------- --------
 AMT_CALC_SET_ID                     NOT NULL NUMBER(9)
 GO_CAT_ID                                    NUMBER(9)
 GO_PACT_MIN                                  VARCHAR2(10)
 GO_PACT_MAX                                  VARCHAR2(10)
 DT_CAT_ID                                    NUMBER(9)
 DT_PACT_MIN                                  VARCHAR2(10)
 DT_PACT_MAX                                  VARCHAR2(10)
 PCOL_ID                             NOT NULL NUMBER(9)
 AMT_CALC_ID                         NOT NULL NUMBER(9)

SQL> desc AMT_CALC_DESC
 Name                                      Null?    Type
 ----------------------------------------- -------- ------
 AMT_CALC_SET_ID                     NOT NULL NUMBER(9)
 AMT_CALC_ID                         NOT NULL NUMBER(9)

SQL> SELECT index_name, uniqueness from dba_indexes where
table_name='AMT_CALC_OPR';

INDEX_NAME                     UNIQUENES
------------------------------ ---------
IX_AMT_CALC_OPR_IMP            NONUNIQUE
IX_AMT_CALC_OPR_EXP            NONUNIQUE
IX_AMT_CALC_OPR                NONUNIQUE

SQL> select column_name from dba_ind_columns where index_name='IX_AMT_CALC_OPR'
order by column_position
COLUMN_NAME
-----------
GO_CAT_ID
DT_CAT_ID
PCOL_ID
GO_PACT_MIN
GO_PACT_MAX
AMT_CALC_ID
DT_PACT_MIN
DT_PACT_MAX

SQL> select column_name from dba_ind_columns where
index_name='IX_AMT_CALC_OPR_EXP' order by column_position
COLUMN_NAME
-----------
GO_CAT_ID
PCOL_ID
GO_PACT_MIN
GO_PACT_MAX
AMT_CALC_ID

SQL> select column_name from dba_ind_columns where
index_name='IX_AMT_CALC_OPR_IMP' order by column_position
  2  /
COLUMN_NAME
------------
DT_CAT_ID
PCOL_ID
DT_PACT_MIN
DT_PACT_MAX
AMT_CALC_ID

SQL> select index_name, uniqueness from dba_indexes where
table_name='AMT_CALC_DESC'
  2  /

INDEX_NAME                     UNIQUENES
------------------------------ ---------
XIF973AMT_CALC_DESC            NONUNIQUE
PK_AMT_CALC_DESC               UNIQUE

SQL> select column_name from dba_ind_columns where index_name='PK_AMT_CALC_DESC'
order by column_position
  2  /

COLUMN_NAME
-----------------
AMT_CALC_SET_ID
AMT_CALC_ID

SQL> SELECT COUNT(*) FROM AMT_CALC_OPR
/
  COUNT(*)
----------
    819508

SQL> SELECT COUNT(*) FROM AMT_CALC_DESC
/
  COUNT(*)
----------
    819508

SQL> SELECT COUNT(*) FROM AMT_CALC_OPR
2 WHERE DT_CAT_ID IS NULL
/
  COUNT(*)
----------
    426258

SQL> SELECT COUNT(*) FROM AMT_CALC_OPR
2 WHERE GO_CAT_ID IS NULL
/

  COUNT(*)
----------
    393250

One more time, I made a simple adjustment to the query. Here I take out the OR
condition in the query and run the same query in 9i and in 8i. Each chooses a
different INDEX to solve this query.

SQL executed in 9i and in 8i database:
---------------------------------------
SQL> SELECT L.AMT_CALC_ID
  2    FROM AMT_CALC_OPR L
  3    WHERE ( L.GO_CAT_ID = 2430 )
  4    AND L.GO_PACT_MIN IS NULL
  5    AND L.GO_PACT_MAX IS NULL
  6    AND L.DT_PACT_MIN IS NULL
  7    AND L.DT_PACT_MAX IS NULL
  8   AND EXISTS (  SELECT 1 FROM AMT_CALC_DESC D
  9                 WHERE L.AMT_CALC_ID = D.AMT_CALC_ID
 10*                AND D.AMT_CALC_SET_ID = 377 );

Execution plan in 8i:
---------------------
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=10 Card=3 Bytes=72)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'AMT_CALC_OPR_EXP' (Cost=10 Card=3
Bytes=72)
   2    1     INDEX (RANGE SCAN) OF 'IX_AMT_CALC_OPR_EXP' (NON-UNIQUE) (Cost=9
Card=3)
   3    2       INDEX (UNIQUE SCAN) OF 'PK_AMT_CALC_DESC' (UNIQUE) (Cost=2
Card=1 Bytes=8)

Execution plan in 9i:
---------------------
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=64 Card=57 Bytes=1824)
   1    0   NESTED LOOPS (SEMI) (Cost=64 Card=57 Bytes=1824)
   2    1     INDEX (RANGE SCAN) OF 'IX_AMT_CALC_OPR' (NON-UNIQUE) (Cost=7
Card=57 Bytes=1368)
   3    1     INDEX (UNIQUE SCAN) OF 'PK_AMT_CALC_DESC' (UNIQUE) (Cost=1
Card=819508 Bytes=6556064)

So, the 8i optimizer had chosen IX_AMT_CALC_OPR_EXP index
and the 9i optimizer had chosen IX_AMT_CALC_OPR index scan.

The data on both the instance is exactly the same. The common initilization
parameters in both the instances are  same.

Finally, why the execution plan and the performace are different and bad in 9i
instance.

SQL> SELECT L.AMT_CALC_ID
  2    FROM AMT_CALC_OPR L
  3    WHERE ( L.GO_CAT_ID = 2430 OR L.DT_CAT_ID = 2430)
  4    AND L.GO_PACT_MIN IS NULL
  5    AND L.GO_PACT_MAX IS NULL
  6    AND L.DT_PACT_MIN IS NULL
  7    AND L.DT_PACT_MAX IS NULL
  8   AND EXISTS (  SELECT 1 FROM AMT_CALC_DESC D
  9                 WHERE L.AMT_CALC_ID = D.AMT_CALC_ID
 10*                AND D.AMT_CALC_SET_ID = 377 );

Thanks
Saradha

          Followup:

tell you what, when I'm taking questions -- feel free.  this is too hard here to
follow.

      Rating: 4
execution plan
      in 8i vs 9i  April 29, 2003
Reviewer:  Saradha
      Bavanandam  from NJ, USA
Tom,

The performance of the following particular query is very bad in Oracle9i
instance. The same query works fine in Oracle8i instnace. The oracle9i and the
oracle8i instances are very very similar.

SQL> SELECT L.AMT_CALC_ID
  2    FROM AMT_CALC_OPR L
  3    WHERE ( L.GO_CAT_ID = 2430 OR L.DT_CAT_ID = 2430)
  4    AND L.GO_PACT_MIN IS NULL
  5    AND L.GO_PACT_MAX IS NULL
  6    AND L.DT_PACT_MIN IS NULL
  7    AND L.DT_PACT_MAX IS NULL
  8   AND EXISTS (  SELECT 1 FROM AMT_CALC_DESC D
  9                 WHERE L.AMT_CALC_ID = D.AMT_CALC_ID
 10*                AND D.AMT_CALC_SET_ID = 377 );

8i-SQL > SET AUTOTRACE ON EXP
8i-SQL > @qry1
...
18 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=19 Card=4 Bytes=108)
   1    0   CONCATENATION
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'AMT_CALC_OPR' (Cost=10 Card=1
Bytes=27)
   3    2       INDEX (RANGE SCAN) OF 'IX_AMT_CALC_OPR_IMP' (NON-UNIQUE) (Cost=9
Card=1)
   4    3         INDEX (UNIQUE SCAN) OF 'PK_AMT_CALC_DESC' (UNIQUE) (Cost=2
Card=1 Bytes=8)
   5    1     TABLE ACCESS (BY INDEX ROWID) OF 'AMT_CALC_OPR' (Cost=10 Card=1
Bytes=27)
   6    5       INDEX (RANGE SCAN) OF 'IX_AMT_CALC_OPR_EXP' (NON-UNIQUE) (Cost=9
Card=1)

9i-SQL > SET AUTOTRACE ON EXP
9i-SQL > @qry1
...
18 rows selected.

9i:SQL>

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=501 Card=222 Bytes=7770)
   1    0   NESTED LOOPS (SEMI) (Cost=501 Card=222 Bytes=7770)
   2    1     TABLE ACCESS (FULL) OF 'AMT_CALC_OPR' (Cost=279  Card=222
Bytes=5994)
   3    1     INDEX (UNIQUE SCAN) OF 'PK_AMT_CALC_DESC' (UNIQUE) (Cost=1
Card=819508 Bytes=6556064)

Somehow, the combination of OR and EXISTS caluse in the query is what causing
this performance problem in Oracle9i instance. To prove my argument if I take
out the EXISTS caluse and make it as a regular join, then I get a good
performance in 9i. Or if I split the query with UNION ALL instead of OR
condition then also I get a good performance.

9i-SQL> get qry1_modified
  1  SELECT
  2     L.AMT_CALC_ID
  3  FROM AMT_CALC_OPR L, AMT_CALC_DESC D
  4  WHERE ( L.GO_CAT_ID = 2430
  5      OR L.DT_CAT_ID = 2430)
  6      AND L.GO_PACT_MIN IS NULL
  7      AND L.GO_PACT_MAX IS NULL
  8      AND L.DT_PACT_MIN IS NULL
  9      AND L.DT_PACT_MAX IS NULL
 10      AND L.AMT_CALC_ID = D.AMT_CALC_ID
 11*     AND D.AMT_CALC_SET_ID = 377
SQL> /

AMT_CALC_ID
-----------------

18 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=79 Card=57 Bytes=1995)
   1    0   CONCATENATION
   2    1     NESTED LOOPS (Cost=10 Card=3 Bytes=105)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'AMT_CALC_OPR' (Cost=7 Card=3
Bytes=81)
   4    3         INDEX (RANGE SCAN) OF 'IX_AMT_CALC_OPR_IMP'   (NON-UNIQUE)
(Cost=7 Card=3)
   5    2       INDEX (UNIQUE SCAN) OF 'PK_AMT_CALC_DESC' (UNIQUE) (Cost=1
Card=1 Bytes=8)
   6    1     NESTED LOOPS (Cost=10 Card=3 Bytes=105)
   7    6       INDEX (RANGE SCAN) OF 'IX_AMT_CALC_OPR' (NON-UNIQUE) (Cost=7
Card=3 Bytes=81)
   8    6       INDEX (UNIQUE SCAN) OF 'PK_AMT_CALC_DESC' (UNIQUE) (Cost=1
Card=1 Bytes=8)

Is there any change in Oracle9i optimizer in respect to
the combination of "OR" and "EXISTS" clause in the query.

Thanks
Saradha

          Followup:

I'll try again:

tell you what, when I'm taking questions -- feel free.  this is too hard here to

follow.

      Rating:
      4
optimizer_max_permutations.  June 10, 2003

Reviewer:  Kashif  from Houston, TX
Hi Tom,

Curious, why would there be such a drastic change from 8.1.7. to 9.2.0 of this
parameter, i.e. from 80000 to 2000? Surely this, along with the
OPTIMIZER_INDEX_CACHING and OPTIMIZER_INDEX_COST_ADJ parameters is a parameter
whose default value makes mostly no sense on OLTP type applications? What would
be an optimal setting for this parameter? Thanks.

Kashif

          Followup:

why would this "surely not" be suitable for OLTP?

      Rating:
      3
Well...  June 10, 2003
Reviewer:
      Kashif  from Houston, TX
There's the chance that the optimal plan might not be created in the 2000
permutations...

Kashif

P.S. I knew the "surely" word sounded a little too corny...

          Followup:

or 80,0000

or 1 billion.

or 5.

but, especially in a OLTP system - characterized by queries not nearly as
complex as say a data warehouse, 2,000 has been tested and deemed more then
sufficient. It can help to cut down on parse time as well -- which in a poorly
coded OLTP system may well account for more time then the actual execute phase
of the query itself.

      Rating:
      5
  June 11, 2003
Reviewer:  Dave
       from Colorado
Would you raise it back up from 2000 for a DW? Up to 80,000, for example? Is
there a way ofchecking how many permutations were actually tried in order to
determine the optimum plan?

          Followup:

I would not raise it unless told to by support in response to a bug actually.

you can do a 10053 trace if you like but for something doing 2000 plans, the
trace file would be, well, "large"

      Rating: 4
Experiencing
      the same problem  July 24, 2003
Reviewer:
      Nunzio Consiglio  from Canada
I have review this thread and we are experiencing something similar. With a bit
of a twist. We have moved from Enterprise 9.0.1 to Standard 9.2.0.3. The explain
plan indicates that the script should in fact run much more effecient, but is
brutialy slow and does not return. On the Enterprise edition the script begins
to return rows within seconds.
I have investigate most if not all the areas indicated in this thread and have
not resolved this issue.
I have set up a new server ( we are running window 2000) and installed the
Standard version 9.2.0.1 from scratch with the same results.
I will say at this point I am a little frustrated.

          Followup:

have you tried /*+ FIRST_ROWS */ if what you want is "first rows" fast?

      Rating: 3
more
      information about full scan  August 26, 2003

Reviewer:  Juancarlosreyesp@yahoo.com  from Bolivia

Hi Tom,
In theory if you have all the table in memory, I think is better to use an
index, because even in that situation it will read less blocks in memory.

But when you have a string that can be searched in any place
for example searchs like '%XX%'.
In that situation is better to kill al indexes to avoid a not optimal execution
plan?

How does it really works, I ask how Oracle searches, it starts from the left
corner an search the first XX,when Oracle finds it, then goes to the next
record.
Which is the logic behind the '%' searches, to know how that searches could be
optimized.
There is a way for example if I know the string is at the right to say oracle
start from the right corner, or something like that.
:) Thank you

          Followup:

theory is wrong.

if Oracle were a single user database...
if there were no need for latches....
if blocks could be accessed instantly (eg: instead of rowids, indexes had
pointers in ram)

maybe what you say is true.

it is expensive to access a block in memory.  accessing many many blocks via an
index typically involves accessing that block many many times -- instead of just
ONCE.

goto hotsos.com and read the paper "are you still using cache hit ratios"

      Rating: 5
;)
      Thanks  August 26, 2003
Reviewer:
      A reader

      Rating: 4
are the index's
      block's retrieved to the buffer cache as well?  October 04,
      2003
Reviewer:  Ravi  from Edinburgh,UK
Is the data in the indexes stored as Blocks of memory as well?

2) In that case are they retrieved and placed in the MRU (most recently used)
are of the Buffer Cache as well?

3) If so are they available for other processes for re-use as well?

4) Like packages, can we not have a process with which we can Load the indexes
to the Buffer cache periodically so that the most frequently used are available
in the Buffer Cache?

          Followup:

yes, all data is treated "in blocks"

all data is created "equal", index blocks are treated no differently then table,
rollback, etc.

the beauty of a buffer cache is that STUFF YOU USE stays there, STUFF YOU DON"T
goes away (or never gets there in the first place!)

Hence, you do not *need* nor do you *want* to "load" things in there.

      Rating: 5
Keep
      pool  October 04, 2003
Reviewer:  reader

<quote> the beauty of a buffer cache is that STUFF YOU USE stays there, STUFF
YOU DON"T
goes away (or never gets there in the first place!)

Hence, you do not *need* nor do you *want* to "load" things in there.  <quote>

Tom, do you recommend a keep pool to be configured? Looking at your response
above, you don't recommend it. Thanks.

          Followup:

it is a knob that can be used for super fine tuning

after everything else is done.

if you are doing this -- this means you've verified your architecture is correct
and "best", that your code is written properly, that you don't have SQL to 100's
or 1000's of LIOs that should be doing 1's or 10's of LIO's.

I don't really see where i said anything negative about a keep pool -- i was
basically saying "don't try to second guess the system, let it work the way it
works, you might be surprised"

      Rating: 3
Possible
      Index-ing causing Performance Issues  December 03, 2003

Reviewer:  Denise  from Virginia, USA
Tom

We recently upgraded our application software to a newer version.

We are experiencing severe performance issues with one of the
functionalities..specifically the Mailing Module.

We do large Mailing campaigns twice monthly and each campaign solicits using
different criteria.

When we do not specify criteria from the Interest Table
the Mailing Process performs with no problems.

When we assign exclusion codes based on Interest Code
criteria the Mailing process just "hangs out there" and
never completes..I have to terminate the job in the job_queue then kill the
session.

I have attached the code that gets generated through the
software module..I then wrote a SQL statement querying a
sample data of what the module code is supposed to do.
I set the autotrace traceonly explain on and produced the
following results.

Can you decipher anything from the results?

thanks
************************************************************
 insert into NATIONAL.mailing (timestamp,idnumber,first,
     middle,last,cfirst,cmiddle,clast,title,salutation,suffix,
     recordtype,ctitle,csalutation,csuffix,familiar,cfamiliar,
     line1,line2,line3,city,state,zip,carrte,delivery_point,
     check_digit, company_name, usertext1, country_name,
     row_number, userid_add, fullname, email, random_no)
  select
sysdate,prospect.idnumber,prospect.first,prospect.middle,prospect.last,prospect.c
first,
     prospect.cmiddle,prospect.clast,t.title,s.salutation,prospect.suffix,

prospect.recordtype,t1.title,s1.salutation,prospect.csuffix,prospect.familiar,
     prospect.cfamiliar, address.line1, address.line2, address.line3,
address.city, address.state, address.zip,
     address.carrte, address.delivery_point, address.check_digit,
     address.company_name, substr(address.usertext1,1,60), c.name, rownum +
0,'NATIONAL',
     DECODE(prospect.recordtype, 'O',
     SUBSTR(DECODE(s1.salutation, NULL, NULL, s1.salutation || ' ')||
     DECODE(prospect.cfirst, NULL, NULL, prospect.cfirst || ' ')||
     DECODE(prospect.cmiddle, NULL, NULL, prospect.cmiddle || ' ')||
     prospect.clast || prospect.csuffix, 1, 180),
     SUBSTR(DECODE(s.salutation, NULL, NULL, s.salutation || ' ')||
     DECODE(prospect.first, NULL, NULL, prospect.first || ' ')||
     DECODE(prospect.middle, NULL, NULL, prospect.middle || ' ')||
     prospect.last || prospect.suffix, 1, 180)) FULLNAME, email.email,
     dbms_utility.get_hash_value(to_char(dbms_utility.get_time)||prospect.last,
2, 1048576)
  from prospect,address,email,salutations s,titles t,salutations s1,titles
t1,countries c
  where prospect.mailflag in ('Y')
     and prospect.salutcode = s.salutcode(+)
     and prospect.titlecode = t.titlecode(+)
     and prospect.csalutcode = s1.salutcode(+)
     and prospect.ctitlecode = t1.titlecode(+)
     and prospect.idnumber = email.idnumber(+)
     and (email.preference = 'Y' or email.preference IS NULL)
     and prospect.idnumber = address.idnumber(+)
     and (address.preference = 'Y' or address.preference IS NULL)
     and address.country = c.code(+)
     and prospect.mailflag = 'Y'
     and prospect.solicit = 'Y'
     and prospect.usercode1 in ( 'ACLJ')
     and prospect.usercode7 in ( 'A')
     and not exists(select i.idnumber
from interest i where prospect.idnumber = i.idnumber and i.intcode in ('201' ,
'400' , '995' , '996' , '997' , '998' , 'DECD' , 'ATL' , 'EML' , 'NOLAST' ,
'OFC', 'UDAD' , 'SF'))
     and not exists (select m2.idnumber from NATIONAL.mailing m2 where
m2.idnumber = prospect.idnumber)
************************************************************

SQL> set autotrace traceonly explain

  1  select idnumber from prospect where
  2  NOT EXISTS(select i.idnumber from interest i where i.intcode
in('995','996','DECD') and
  3* idnumber=i.idnumber)
SQL> /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=RULE
   1    0   FILTER
   2    1     TABLE ACCESS (FULL) OF 'PROSPECT'
   3    1     CONCATENATION
   4    3       TABLE ACCESS (BY INDEX ROWID) OF 'INTEREST'
   5    4         INDEX (RANGE SCAN) OF 'INTEREST_INTCODE_INDEX' (NON-
          UNIQUE)

   6    3       TABLE ACCESS (BY INDEX ROWID) OF 'INTEREST'
   7    6         INDEX (RANGE SCAN) OF 'INTEREST_INTCODE_INDEX' (NON-
          UNIQUE)

   8    3       TABLE ACCESS (BY INDEX ROWID) OF 'INTEREST'
   9    8         INDEX (RANGE SCAN) OF 'INTEREST_INTCODE_INDEX' (NON-
          UNIQUE)

          Followup:

nope.

not even sure why we are looking at the query we are looking at ;)

      Rating: 3
I Think I just
      answered my own question......  December 03, 2003

Reviewer:  Denise  from Virginia, USA
Tom

I ran the sql trace on the server that contains the previous software version
before the upgrade.
see the results:

  1  select idnumber from prospect where
  2  NOT EXISTS(select i.idnumber from interest i where i.intcode
in('995','996','DECD') and
  3* idnumber=i.idnumber)
SQL> /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=RULE
   1    0   FILTER
   2    1     TABLE ACCESS (FULL) OF 'PROSPECT'
   3    1     TABLE ACCESS (FULL) OF 'INTEREST'

************************************************************

I can't recall if it was here or in Chapter 7 of your book
where you explain the Block IO on Full Scans versus
Index Scans by individual blocks...etc where Full Scans
can be faster and better depending on the query criteria.

Please add your own feedback if I am on the correct path
here. I will need to pass this on to the software developers(with much
trepidation!)

tata

          Followup:

well, look see if that index exists in the older release.

i went all of the way back to 7.1.6 and the RBO always used the index

did you identify that index use as being the issue?

      Rating: 5
Thanks
      Tom!  December 03, 2003
Reviewer:  Denise
       from Virginia, USA
for letting me use your forum to figure out the indexing issue.

I read pages 277-278 in your book and the "light bulb"
upstairs switched on.

I got rid of the index and 'lo and behold' the Mailing
Module with exclude Interest Codes was completed in 1.25
minutes.

My boss and I plan to keep the 'dirty little secret'
to ourselves and not tell the software vendor that we
exterminated one of their stupid indexes.

;~)

      Rating:
      4
Clarification...  December 04, 2003

Reviewer:  Denise  from Virginia, USA
sorry Tom....didn't mean an earlier version of Oracle.
We have Fundraising Application Software that runs on
Oracle. I meant the Application Software upgrade(where
the developers saw fit to fix things that weren't 'broken'
and we now have a host of problems with the newer version..
i.e. creating new indexes where not needed.)

I looked at the older version(that worked!) and then looked
at the newer version(that doesn't work!) and deleted an
index that was not necessary...and now everything WORKS!

I found the solution reading your book and reading the
previous posts on this thread...

the learning continues.....thanks

December 03, 2003 Suhail -- Thanks for the question regarding
      "Improving performance ", version 9.0.1.3.0

      You Asked
Tom,

I have a small function which generate 9 digit random number between 100100100
and 998998999. I have to make sure that number should not start with 0 and
occurance of a digit cannot exceed with more than 2 ie 3 can only repeat in a
number upto two times.If I generate 20000 numbers then this function works OK
and just take only 5 minutes. However when I generate this for 200,000 rows it
takes 40 minutes and for 1 million rows , it takes almost 3-4 hours.

Could you please recommend me to improve the performance. I need to generate at
least 5 millions of these numbers first year and then 1/2 millions after every
year.

My code starts here.....

FUNCTION FUNC_GENERATE_ID
 RETURN NUMBER
 IS

 l_candidate_number NUMBER;
 l_dummy VARCHAR2(1);
 l_is_too_repetitive BOOLEAN;
 l_chk_digit   NUmber;

BEGIN
 <<gen_id>>
 LOOP
 l_is_too_repetitive := FALSE;
 -- VALUE function returns x such that low <= x < high,
 -- hence high = ...9990 instead of ....9989.
 SELECT TRUNC(DBMS_RANDOM.VALUE(100100100,998998999))
 INTO l_candidate_number
 FROM DUAL;
 BEGIN
 -- You'll do your uniqueness check here
 SELECT NULL
 INTO l_dummy
 FROM a_tbl yt
 WHERE yt.id = l_candidate_number;
 EXCEPTION
 WHEN NO_DATA_FOUND THEN
 <<check_2>>
 FOR i IN 0..9 LOOP
 IF (LENGTH(l_candidate_number)
 -
 NVL(LENGTH(REPLACE(l_candidate_number
 , TO_CHAR(i))),0) > 2) THEN
 l_is_too_repetitive := TRUE;
 EXIT check_2;
 END IF;
 END LOOP check_2;
 IF (NOT l_is_too_repetitive) THEN
 EXIT gen_id;
 END IF;
 END;
 END LOOP gen_id;
--check digit calculation
 --l_chk_di
l_chk_digit := to_number(substr(l_candidate_number,1,1)*2+
               substr(l_candidate_number,2,1)*3+
               substr(l_candidate_number,3,1)*4+
               substr(l_candidate_number,4,1)*5+
               substr(l_candidate_number,5,1)*6+
               substr(l_candidate_number,6,1)*7+
               substr(l_candidate_number,7,1)*8+
               substr(l_candidate_number,8,1)*9+
               substr(l_candidate_number,9,1)*10);
--dbms_output.put_line('check digit total = '||l_chk_digit);
--get remainder
select MOD(l_chk_digit,7) into l_chk_digit from dual;
--dbms_output.put_line( 'Check didgit = '||l_chk_digit);
l_candidate_number := to_number(l_candidate_number||l_chk_digit);
 RETURN (l_candidate_number);
 END func_generate_id;

I call this function as follows :

for i in 1..20000 loop
   insert into a_tbl values(func_generate_id);

end loop;
commit;

Thank you for all your help.

Suhail

      and we said...
we've all got to think in sets -- set based thinking is cool, it is efficient.

Now, I'm sure there are an infinite number of ways to do this -- the one I came
up with is as follows.

basically -- we'll set up tables with data (a table with lots of 1..9's in it
and another with 01234..9999 with the "bad ones" deleted (bad in this case was
any number with the same digit two times)

Now, we just need to join!  we can get upto

ops$tkyte@ORA920> select 9 * (select count(*) from t1) * (select count(*) from
t1 where x <= '8999') from dual;

9*(SELECTCOUNT(*)FROMT1)*(SELECTCOUNT(*)FROMT1WHEREX<='8999')
-------------------------------------------------------------
                                                   206,443,008

unique values from this one.  Here is my setup:

 ops$tkyte@ORA920> create table t ( x varchar2(9) primary key ) organization
index;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> drop table t1;

Table dropped.

ops$tkyte@ORA920> drop table t2;

Table dropped.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t1 ( x varchar2(4) );

Table created.

ops$tkyte@ORA920> create table t2 ( x varchar2(1) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t1
  2  select to_char( rownum+122, 'fm0000' )
  3    from all_objects
  4   where rownum <= 9899-122;

9777 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> delete from t1
  2   where length(replace(x,'0','')) < 3
  3      or length(replace(x,'1','')) < 3
  4      or length(replace(x,'2','')) < 3
  5      or length(replace(x,'3','')) < 3
  6      or length(replace(x,'4','')) < 3
  7      or length(replace(x,'5','')) < 3
  8      or length(replace(x,'6','')) < 3
  9      or length(replace(x,'7','')) < 3
 10      or length(replace(x,'8','')) < 3
 11      or length(replace(x,'9','')) < 3
 12  /

4729 rows deleted.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t2
  2  select mod(rownum,9)+1
  3    from all_objects
  4   where rownum <= (select count(*) from t1);

5048 rows created.

so, t2 has lots of 1..9's and t2 has all of the 4digit numbers we could want.
This procedure:

ops$tkyte@ORA920> create or replace procedure gen_data( p_num_rows in number )
  2  as
  3      l_cnt number := 0;
  4  begin
  5      while (l_cnt < p_num_rows )
  6      loop
  7          insert into t
  8          select t2.x || a.x || b.x
  9            from (select rownum r, x from (select * from t2
                      order by dbms_random.random)) t2,
 10                 (select rownum r, x from (select * from t1
                      order by dbms_random.random)) a,
 11                 (select rownum r, x from (select * from t1
                     where x <= '8999' order by dbms_random.random)) b
 12           where t2.r = a.r and a.r = b.r
 13             and rownum <= (p_num_rows-l_cnt)
 14             and not exists ( select null
 15                                from t
 16                               where x = t2.x || a.x || b.x );
 17          l_cnt := l_cnt + sql%rowcount;
 18      end loop;
 19  end;
 20  /

Procedure created.

generates our data for us.  Now on my itty bitty desktop pc, I observed:

rows       your time       my time
   20,000   5minutes        2.73 seconds
  200,000  40minutes       30.98 seconds
1,000,000  3-4 hours       3 minutes 12 seconds

(that last one got my fan going pretty good :)

ops$tkyte@ORA920> set timing on
ops$tkyte@ORA920> exec gen_data(20000)
PL/SQL procedure successfully completed.
Elapsed: 00:00:02.73

ops$tkyte@ORA920> exec gen_data(200000)
PL/SQL procedure successfully completed.
Elapsed: 00:00:30.98

ops$tkyte@ORA920> exec gen_data(1000000)
PL/SQL procedure successfully completed.
Elapsed: 00:03:12.08

ops$tkyte@ORA920> set timing off

ops$tkyte@ORA920> select count(*) from t;

  COUNT(*)
----------
   1220000

so, think "sets", think "bulk operations".  row by row is synonymous with slow
by slow!

other observations:

o use assignments, not selects from dual!
o don't peek at a table to see if you should insert the value (eg: don't
  do your own 'primary key checking'.  let the database do the work in a
  single sql statement

Review &
      Followup

      Rating: 5
very
      helpfu  November 26, 2003
Reviewer:  Suhail

Tom, thanks a lot, it reallyhelped me. You the MAN.

      Rating: 4
Allows more
      than 2 occurences of the first digit!  November 27, 2003

Reviewer:  Roger Andersson  from Sweden
It's a really nice example of performance improvement using set based instead of
row based techniques. I had to go through it piece by piece to understand how it
works and when doing that I noticed that it doesn't fulfill the original
requirements!

The requirement was that a digit can not exists more than twice. The supplied
code actually allows the first digit to exists three times! This happens when
the number generated by t2 exists in both the number generated by a and b.

To handle this add

AND INSTR(a.x || b.x, T2.x, 1, 2) = 0

to the where clause in the insert statement.

          Followup:

there you go !! thanks :)   missed that one.....

but thanks much for the "fix", nice

      Rating: 2
to make it
      simpler  November 28, 2003
Reviewer:  reeds
      lau  from Hong Kong
Hi,

I guest the validation  of occurrence of digits in the original script can be
make simpler :

select ...
where
substr(l_candidate_number,1,1) <> '0' and (
nvl(length(replace(translate(l_candidate_number,'012345678','aaaaaaaaa'),'a',''))
,1) <=2 and
nvl(length(replace(translate(l_candidate_number,'012345679','aaaaaaaaa'),'a',''))
,1) <=2 and
nvl(length(replace(translate(l_candidate_number,'012345689','aaaaaaaaa'),'a',''))
,1) <=2 and
nvl(length(replace(translate(l_candidate_number,'012345789','aaaaaaaaa'),'a',''))
,1) <=2 and
nvl(length(replace(translate(l_candidate_number,'012346789','aaaaaaaaa'),'a',''))
,1) <=2 and
nvl(length(replace(translate(l_candidate_number,'012356789','aaaaaaaaa'),'a',''))
,1) <=2 and
nvl(length(replace(translate(l_candidate_number,'012456789','aaaaaaaaa'),'a',''))
,1) <=2 and
nvl(length(replace(translate(l_candidate_number,'013456789','aaaaaaaaa'),'a',''))
,1) <=2 and
nvl(length(replace(translate(l_candidate_number,'023456789','aaaaaaaaa'),'a',''))
,1) <=2 and
nvl(length(replace(translate(l_candidate_number,'123456789','aaaaaaaaa'),'a',''))
,1) <=2)

Rgs,
Reeds

      Rating: 5
check for null
      value when deleting from t1  November 28, 2003

Reviewer:  suresh govindarajan  from chennai,India

check for null value when deleting  from t1
because it if all the digits are same like '5555' the entire value may become
null and the delete fails.
should be nvl(length(replace(x,'0','')),0) < 3

          Followup:

good point -- thanks

      Rating: 4
changed the
      code little bit but no improvement in performance  December
      01, 2003
Reviewer:  Suhail Ahmad  from NY, USA

Tom,

As per your instruction and other reviewers , I tried on my end. I changed the
get_data procedure a little bit, it calls a function chk_digit which calculate a
check digit and add the check digit to the first position of the generated
number so I am inserting a 10 digit number instead of 9 digit. I am also
checking for UNIQUE_KEY exception since I donot want to stop the loop . However,
I still donot see any performance improvements with 20000 number. Here are my
changed code:

My check digit function .. as follows:

CREATE OR REPLACE function chk_digit(p_digit in number)
return number
IS
return number;
l_candidate_number NUMBER;
l_chk_digit  number;
BEGIN
l_chk_digit := to_number(substr(p_digit,1,1)*2+
               substr(p_digit,2,1)*3+
               substr(p_digit,3,1)*4+
               substr(p_digit,4,1)*5+
               substr(p_digit,5,1)*6+
               substr(p_digit,6,1)*7+
               substr(p_digit,7,1)*8+
               substr(p_digit,8,1)*9+
               substr(p_digit,9,1)*10);
l_chk_digit := MOD(l_chk_digit,7);

l_candidate_number := to_number(p_digit||l_chk_digit);

return l_candidate_number;
end;

My gen_data procedure is as follows :

CREATE OR REPLACE procedure gen_data( p_num_rows in number )
    as
        l_cnt number := 0;
    begin
        while (l_cnt < p_num_rows )
        loop
            insert into t
            select chk_digit(t2.x || a.x || b.x)
              from (select rownum r, x from (select * from t2
                      order by dbms_random.random)) t2,
                  (select rownum r, x from (select * from t1
                      order by dbms_random.random)) a,
                  (select rownum r, x from (select * from t1
                     where x <= '8999' order by dbms_random.random)) b
            where t2.r = a.r and a.r = b.r
              and rownum <= (p_num_rows-l_cnt)
              and not exists ( select null
                                 from t
                                where x = chk_digit(t2.x || a.x || b.x ));
           l_cnt := l_cnt + sql%rowcount;
       end loop;
exception when others then
         errors := SQL%BULK_EXCEPTIONS.COUNT;
    dbms_output.put_line(' Unique Errors ='||errors);
    COMMIT;
   end;

I have the same table names set as you described. What I did is that I used
chk_digit function in the SELECT clause and also in the WHERE clause. Do you see
any problem in it?

Thanks for your help.

Suhail

          Followup:

use the technique i used, no procedural code.  INLINE "chk_digit"

      Rating: 4
Still
      performance issue  December 03, 2003
Reviewer:
      Suhail  from NY USA
Tom,

I did as you said, I am using the procedure which you have with the same table,
except I had written a db trigger which  checks for check digit and add the
check digit at the last position. Trigger is as follows:

CREATE OR REPLACE TRIGGER cg$BIR_T
BEFORE INSERT ON T FOR EACH ROW
DECLARE

BEGIN

declare
     op number;
 oRes   varchar2(20);
BEGIN
    --call the check digit procedure
   proc_chk_digit(:new.x,op,oRes);
   IF oRes='SUCCESS' then
    :new.x := op;
   END IF;
END;

My proc_chk_digit procedure is as follows :
CREATE OR REPLACE procedure proc_chk_digit(p_digit in  number,o_digit out
number,o_Result out varchar2)
IS
l_candidate_number NUMBER;
l_chk_digit  number;
 l_is_too_repetitive BOOLEAN;
BEGIN
l_chk_digit := to_number(substr(p_digit,1,1)*2+
               substr(p_digit,2,1)*3+
               substr(p_digit,3,1)*4+
               substr(p_digit,4,1)*5+
               substr(p_digit,5,1)*6+
               substr(p_digit,6,1)*7+
               substr(p_digit,7,1)*8+
               substr(p_digit,8,1)*9+
               substr(p_digit,9,1)*10);
select MOD(l_chk_digit,7) into l_chk_digit from dual;

l_candidate_number := to_number(p_digit||l_chk_digit);
-- check again for digit occurance
 l_is_too_repetitive := FALSE;
 FOR i IN 0..9 LOOP
 IF (LENGTH(l_candidate_number)
 -
 NVL(LENGTH(REPLACE(l_candidate_number
 , TO_CHAR(i))),0) > 2) THEN
  l_is_too_repetitive := TRUE;
  EXIT;
 END IF;
 END LOOP ;
o_digit := l_candidate_number;
if  l_is_too_repetitive = FALSE
   then
      o_Result := 'SUCCESS';
else
      o_Result := 'FAILURE';
end if;
dbms_output.put_line('Digit is '||o_digit||' and Result is '||o_Result);
end;
/

when I run gen_data procedure which is same except I added exception to count
how many numbers are rejected due to unique key, Its still taking lot of time.
On my machine, I observed :

SQL> set time on
07:48:50 SQL> exec gen_data(20000)

PL/SQL procedure successfully completed.

07:50:04 SQL> exec gen_data(200000)

PL/SQL procedure successfully completed.

08:27:50 SQL> select count(*) from t;

  COUNT(*)
----------
    220000

My gen_data procedure is as follows:
CREATE OR REPLACE procedure gen_data( p_num_rows in number )
    as
        l_cnt number := 0;
          errors number;
    begin
        while (l_cnt < p_num_rows )
        loop
        BEGIN
            insert into t(x)
            select t2.x || a.x || b.x
              from (select rownum r, x from (select * from t2
                      order by dbms_random.random)) t2,
                  (select rownum r, x from (select * from t1
                      order by dbms_random.random)) a,
                  (select rownum r, x from (select * from t1
                     where x <= '8999' order by dbms_random.random)) b
            where t2.r = a.r and a.r = b.r
              and rownum <= (p_num_rows-l_cnt)
              and not exists ( select null
                                 from t
                                where x = t2.x || a.x || b.x );
           l_cnt := l_cnt + sql%rowcount;
        EXCEPTION
         when dup_val_on_index then
         insert into tab_unque
         values(1);
         --dup_val_on_index then dbms_output.put_line('Whats the error dup value
='||sqlerrm);null;
         end;
       end loop;

    COMMIT;
   end;
/

Do you see any reason, why the performance is not improving?

Thanks

Suhail

          Followup:

remove the procedural code.

get RID of the trigger, do it ALL in sql (as demonstrated!!!!!!!)

      Rating: 3
can't write in
      sql  December 03, 2003
Reviewer:  Suhail
       from NY-USA
Tom,
I could not get you. I am sorry for that, how can I write the pl/sql logic which
i had written in procedure proc_chk_digit in a simple SELECT , I am using LOOP
and some IF condition?
Hope I am not asking too much or may be my SQL is not good enough.

          Followup:

think about it a bit -- i can see it.  it isn't "sql" here, it is taking a
procedure and turning it into a single line of code.

for example:

l_chk_digit := to_number(substr(p_digit,1,1)*2+
               substr(p_digit,2,1)*3+
               substr(p_digit,3,1)*4+
               substr(p_digit,4,1)*5+
               substr(p_digit,5,1)*6+
               substr(p_digit,6,1)*7+
               substr(p_digit,7,1)*8+
               substr(p_digit,8,1)*9+
               substr(p_digit,9,1)*10);
select MOD(l_chk_digit,7) into l_chk_digit from dual;

why why WHY is that two lines of code?????

why why WHY is that using DUAL????? (i told you not to do that already!!!)

                                   l_candidate_number :=
to_number(p_digit||l_chk_digit);

why is that yet another line of code -- so far I see one line of code!

that one line of code generates the number you apparently want (so thats what
goes into the SELECT list there).

Now all you need to do is add the "where" clause check of

  ....
  and length(candidate_number)-length(replace( candidate_number, '9', ''))<= 2
  and length(candidate_number)-length(replace( candidate_number, '8', ''))<= 2
  .....

to avoid inserting "bad" numbers.

variation on a theme here, take the concept and run with it.

      Rating:
      5
  December 03, 2003
Reviewer:
      A reader

      Rating:
      5
super  December 04, 2003
Reviewer:
      Hannibal  from austria/europe

December 03, 2003 Peter -- Thanks for the question regarding
      "Function-based Index on Y/N Completed Flag", version 9.2.0

      You Asked
Tom,

Your latest book has an innovative way of using a Function-based Index on a
"Y/N" Processed Column.

I thought I would give it a try, but ran into a problem in that the index is not
being used.

My table has half a million rows : 1 row has "Y" in the flag column, all the
others have "N", but when I run some SQL to look for the record which is a "Y",
a Full Table Scan is used.

Please could you explain why ?

Thanks,

Peter

The database block size is 8k, the index tablespace I used has uniform extents
of 1Mb

Here are the steps I took, and the results I obtained :-

SQL> select * from sys.v_$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.3.0 - 64bit Production
PL/SQL Release 9.2.0.3.0 - Production
CORE    9.2.0.3.0    Production
TNS for HPUX: Version 9.2.0.3.0 - Production
NLSRTL Version 9.2.0.3.0 - Production

5 rows selected.

SQL> alter session set QUERY_REWRITE_ENABLED=TRUE;

Session altered.

SQL> alter session set QUERY_REWRITE_INTEGRITY=TRUSTED;

Session altered.

SQL> drop table t1;

Table dropped.

SQL> create table t1
  2  (
  3   c1 number(10) not null,
  4   c2 varchar2(1) not null
  5  )
  6  tablespace ref_small_data
  7  nologging
  8  ;

Table created.

SQL> insert /*+ append */ into t1
  2  select rownum,
  3         'N'
  4  from   all_objects,
  5         all_objects
  6  where  rownum <= 500000
  7  ;

500000 rows created.

SQL> commit;

Commit complete.

SQL> update t1
  2  set    c2 = 'Y'
  3  where  c1 = 123456
  4  ;

1 row updated.

SQL> commit;

Commit complete.

SQL> create index t1_fbi on t1
  2  (
  3   case when c2 = 'Y' then c2
  4   else null
  5   end
  6  )
  7  tablespace ref_small_idx
  8  ;

Index created.

SQL> analyze table t1
  2  estimate statistics
  3  sample 10 percent
  4  for table
  5  for all indexes
  6  for all indexed columns
  7  ;

Table analyzed.

SQL> analyze index T1_FBI validate structure;

Index analyzed.

SQL> column name format a18
SQL> set numwidth 8 lines 132
SQL> select NAME,
  2         BLOCKS,
  3         LF_ROWS,
  4         LF_BLKS,
  5         DEL_LF_ROWS,
  6         DISTINCT_KEYS,
  7         USED_SPACE,
  8         ROWS_PER_KEY
  9  from   index_stats
 10  where  name = 'T1_FBI'
 11  ;

NAME                 BLOCKS  LF_ROWS  LF_BLKS DEL_LF_ROWS DISTINCT_KEYS
USED_SPACE ROWS_PER_KEY
------------------ -------- -------- -------- ----------- -------------
---------- ------------
T1_FBI                  130        1        1           0             1
13            1

SQL> set autotrace on
SQL> select * from t1 where c2 = 'Y'
  2  ;

      C1 C
-------- -
  123456 Y

Execution Plan
----------------------------------------------------------
          0
SELECT STATEMENT Optimizer=CHOOSE (Cost=127 Card=5000 Bytes=50000)

          1                  0
  TABLE ACCESS (FULL) OF 'T1' (Cost=127 Card=5000 Bytes=50000)

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

-- end of output

      and we said...
you indexed:

SQL> create index t1_fbi on t1
  2  (
  3   case when c2 = 'Y' then c2
  4   else null
  5   end
  6  )
  7  tablespace ref_small_idx
  8  ;

but you query:
SQL> select * from t1 where c2 = 'Y'
  2  ;

you need to query:

SQL> select * from t1 where (case when c2 = 'Y' then c2 else null end) = 'Y'
  2  ;

that is, you need to query that which you indexed!

Review &
      Followup

      Rating: 3
what is the
      advantage of using functional index here?  November 24, 2003

Reviewer:  A reader
Why normal index wouldn't do

      Rating: 3
To reader: this
      index is a lot smaller  November 25, 2003

Reviewer:  msc
This (function based) index does not contain all 'N' values - it contains only
single 'Y' record. So index will be quite small and fast for 'Y' values (of
course you would use this only if you have few 'Y'-s and a lot of 'N'-s).

You could also use NULL to mean 'N' and skip functional index. I personally
would rather use NULL approach.

      Rating:
      3
smaller?  November 25, 2003
Reviewer:
      mikito harakiri
Yes, but who cares about memory these days? You'll spend much more onto
developers time who would puzzle what fancy predicate in

select * from t1 where (case when c2 = 'Y' then c2 else null end) = 'Y'

is all about. I thought that Tom's book is against writing unreadable,
unportable (cause you won't find the index NULL "feature" in any competitor's
RDBMS), unreliable (cause oracle might decide to remove this bug in the future
version) code.

          Followup:

it is not memory -- it is maintanence and the usability of the index itself
(whether the optimizer will choose to use it).

maintaining the index with millions of "N's" and few Y's is bad (impacts DML)

using the index becomes iffy.

If your developers

a) do not comment
b) do not document
c) do not read manuals
d) eg: don't do there job

a simple "select * from dual" becomes pretty obscure too.  I would never in a
billion years not do something smart because someone might be dumb and not
understand it.

My book was about EXPLOITING THE PRODUCT YOU BOUGHT to its 100% fullest.

It is decidely against doing things in a database independent fashion, decidedly
against that (i went as far as to say "DON'T do that -- if you bought sqlserver,
exploit the heck out of it.  if you bought Oracle, use it to it's extreme).

Entirely Null entries are not indexed
It is documented to that effect
It is not a bug

      Rating: 4
Oracle
      DBA  November 25, 2003
Reviewer:  David
       from Dallas, Texas
Hi Tom,
I am facing a performance issue regarding to creating a function index. If the
function "key value" is more then boolean, still a workaround for using the
function index.
for example:
select C1, C2 from T
where MOD(C1, :V01)='XYZ'
Value ":V01" could be a number from 0 to 10.

Thanks in advance,
David

          Followup:

not going to work with binds.  function based indexes must be deterministic.

you would need 10 indexes here.

      Rating: 3
Oracle
      DBA  November 25, 2003
Reviewer:  David
       from Dallas, Texas
Now, I got.  Thanks for quick response.

--David

      Rating: 5
Function -based
      Index on Y/N Completed Flag  November 26, 2003

Reviewer:  Peter  from London
Tom,

Excellent advice as ever, thanks very much.

Just to clarify for the other posters, this was a very simplified case to try
and figure out why my FBI was not working.

In the real world, the flag C2 has several values ( Y, N, null, A, B, C, etc ).

For the most part, our system queries all the M's, C's, S's etc, so a Full Table
Scan is used.

A B-Tree index exists on C2, and is used when our query just wants the Y's,
which is great, but the index is huge.

I was looking for a way to cut down on the size of the index by using a FBI, and
now I have it.

      Rating: 3
FBI Question
      ..  December 03, 2003
Reviewer:
      A reader
Hi Tom,

Does fbi work with rbo ?

          Followup:

nope, not even a little

December 03, 2003 ade -- Thanks for the question regarding "using
      sql*loader", version 8.1.6.0.0

      You Asked
Tom,

I have a situation, and I'm not sure as to how to proceed. I'll
try my best to be clear with my question.
I have a table, say Temp, and I need to load data
(row1.f1, row1.f2, row1.f3, row1.f4, row1.f5) from a datafile
using a control file into temp. before I can insert into a row,
I need to determine the following for row1.f3 from  another
table , say 'mytable' in my database:

if not null then find the corresponding value in 'mytable',
where countryid = 200, and put the value into the column
row1.f3 otherwise put something else. if this makes sense to
you. how do I solve this using
decode in sql*loader or do i need to write a pl/sql query to do
this.

      and we said...
Ok, lets say you have a set of tables:

create table temp
( f1 varchar2(25),
  f2 varchar2(25),
  f3 varchar2(25),
  f4 varchar2(25),
  f5 varchar2(25)
)
/

create table mytable
( countryid number,
  name      varchar2(25),
  value     varchar2(25)
)
/

insert into mytable values ( 200, 'f3', 'newvalue for f3' );

You can use a control file such as:

OPTIONS(rows=1)
LOAD DATA
INFILE *
INTO TABLE TEMP
REPLACE
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
(f1,f2,
 f3 "decode(:f3,NULL,NULL,(select value from mytable where
countryid=200 and name=:f3))",
 f4, f5 )
BEGINDATA
f1x,f2x,f3x,f4x,f5x
f1,f2,f3,f4,f5

to perform your operation.  It uses a new Oracle8i release 8.1
feature - the ability to use a subquery virtually anywhere a
column/constant could be used.  Here we use decode to either
load NULL if :f3 is NULL or the result of that query.

IMPORTANT NOTE
when doing this, make sure to use rows=1.  There is an issue
with this if you do not -- it'll not get the expected results.
Review &
      Followup

      Rating: 3
decode on
      external table  December 03, 2003
Reviewer:
      Marcio  from br
Tom, consider this:
LOAD
replace
INTO TABLE t
FIELDS TERMINATED BY ';'
(
A     CHAR,
B     CHAR "decode(:b,'','NULL VALUE',:b )"
)

I used "decode(:b...)", can I use this kind of "decode..." on external table
creation? I have issue with numbers.

t.txt
1, 1.02
2, .98
3, 1,22
4, ,43

so, when I created table got 2 bad records, then... I tried mask "99d99",
to_number(), translate(:x, ',','.')... and nothing.

CREATE TABLE t
  (id       number(4),
   x        number4,2)
  )
  ORGANIZATION EXTERNAL
  (
    TYPE ORACLE_LOADER
    DEFAULT DIRECTORY dir_sre
    ACCESS PARAMETERS
    (
      records delimited by newline
      fields terminated by ';'
      missing field values are null
      ( id, x "translate(:x, ',','.')" ) <---*
    )
    LOCATION ('test.txt')
  )
  PARALLEL
  REJECT LIMIT UNLIMITED

Any idea?

thank you
Marcio

          Followup:

use varchar2 for any of the columns in the CREATE TABLE (eg: don't have x be a
number(4,2), have it be a varchar2)

then, use DECODE in the SQL that accesses the external table

select ..., decode(x,....)

create a view if you like.

December 03, 2003 -- Thanks for the question regarding "histogram",
      version 8.1.5

      You Asked
Tom

Can you give a simple example of how to create a histogram, to see the
performance of a query?

      and we said...
Sure, run a script like this:

create table t
as
select 1 X, rpad('*',4000,'*') data from all_objects a
/
insert into t
select 2, rpad('*',4000,'*') from all_objects a where rownum = 1
/
create index t_idx on t(x)
/

alter session set sql_trace=true;

select count(data) from t t1 where x = 1;
select count(data) from t t1 where x = 2;

analyze table t compute statistics
for table
for all indexes
for all indexed columns;

select count(data) from t t2 where x = 1;
select count(data) from t t2 where x = 2;

(the for all indexed columns got the histograms for us on the indexed column
X)...

Now the tkprof shows (your mileage WILL vary on this)

select count(data)
from
 t t1 where x = 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      4.46      29.99      30847      30864          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      4.46      29.99      30847      30864          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 43984

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
  30802   TABLE ACCESS BY INDEX ROWID T
  30803    INDEX RANGE SCAN (object id 123779)

select count(data)
from
 t t1 where x = 2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          1          3          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          1          3          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 43984

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
      1   TABLE ACCESS BY INDEX ROWID T
      2    INDEX RANGE SCAN (object id 123779)

that shows the optimizer (without any stats at this point) used the index.
For "x=1" this was a very bad idea  - we were reading almost every row already
(all but 1) much faster to full scan.  the optimizer did not realize that the
indexed column was heavily skewed.  so we ran stats and then:

select count(data)
from
 t t2 where x = 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      2.03      11.06      22744      30807         52           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      2.03      11.07      22744      30807         52           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 43984

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
  30802   TABLE ACCESS FULL T

********************************************************************************

Ahh, thats much better.  1/2 the cpu time and much better on the elapsed time.
 as well....  Note that an index will still be used when appropriate:

select count(data)
from
 t t2 where x = 2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          3          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.00          0          3          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 43984

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
      1   TABLE ACCESS BY INDEX ROWID T
      2    INDEX RANGE SCAN (object id 123779)

follow up to comment one

the optimizer would use heuristics (rules) to see if it wanted to use the index
in general or not and would come up with ONE plan that both queries would use.
You would obviate the usefulness of histograms in this case.

this is not a reason to NOT USE bind variabes -- it simply points out one time
where you might consider NOT using them

Review &
      Followup

      Rating:
      3
  May 27, 2001
Reviewer:
      A reader
What the CBO will do if the bind variable used for the x?

      Rating: 3
Data
      Skewness  December 03, 2003
Reviewer:
      A reader  from NJ, USA
Tom,

What is the best way to get the data skewness. For example, assume that we have
an index on a table on column COL1. If it uses indexes for certain values and
does a full-table scan for other values, then the approach would be :
1. Check the data skewness. Is that correct?
2. If the column in question is a integer column, how do we do we do that
3. If the column in question is a non-integer column, how do we do that

The reason I am asking this question is that when I generate a histogram for an
integer column, I am able to use the ENDPOINT_NUMBER and ENDPOINT_VALUE to
determine the data distribution by finding the difference in ENDPOINT_NUMBER
between the previous and current row (after we sort on ENDPOINT_NUMBER).
However, for non-numeric columns, only ENDPOINT_NUMBER column shows the numbers.
The ENDPOINT_VALUE shows some cryptic number and the ENDPOINT_ACTUAL_VALUE is
NULL.

I request you to provide me a methodology to determine data skewness in general
(both for numeric and non-numeric columns). The text books merely state that we
need to check the data skewness, but does not provide an example or way to
determine that. An example from you in this regard will be of great help
Also, please provide me an example and a clear explanation of the SIZE clause in
ANALYZE command because I am not able to find clear explanation in Oracle
documentation or in any performance tuning books I read so far.

          Followup:

select column, count(*) from t group by column;

look at the results, or maybe

select min(cnt), max(cnt), avg(cnt), stddev(cnt)
  from ( select column, count(*) cnt from t group by column )

to see if there is skewness -- min far from max indicates skewed.

December 04, 2003 Rupali -- Thanks for the question regarding
      "Normalization of the database", version 8.1.5

      You Asked
Dear Mr. Tom,

I am in big dilemma now a days.

We say that we should normalize data to the most possible level.
But when web enabled applications come into picture, i have seen
a general tendency to denormalize the data just to increase the
speed on net, and as a result increase in data redundancy.

If only speed will be the case then will make only one table
only of all fields........

I have got following probs..

1.  If fully normalize database is there then what will be the

    speed of web enabled applications
2.  Any adverse effects of denormalized database on the web
    enabled applications.
3.  Which is the best way for web enabled application....
    completely normalized database
    denormalized database with a lot of redundancy

Regards,

Rupali

      and we said...
I normalize all data for OLTP -- I never store redundant data.
In an OLTP type system -- denormalizing is slower then
normalizing.  The cost associated with an UPDATE (of which you
do as many as you do reads typically) more then offsets any
percieved gain by denormalization.

In a DSS type situation.  I would consider denormalization as
the data never or very infrequently changes.  I would use it
only when needed.

I'm not aware of any big changes in thoughts or perceptions in
this area due to the web -- the web changed nothing in regards
to this.
Review & Followup

      Rating: 4
I have a
      slightly different twist to the original question  October
      25, 2002
Reviewer:  Mohsin  from Unguja
Given a database/schema how can we determine the level of normalization the
schema underwent.

          Followup:

you would reverse engineer it into some tool and study the results I suppose.
I've (very frankly) never even considered doing anything like that, ever.

      Rating:
      4
  October 26, 2002
Reviewer:  Mike
       from TX
Not vary understood your statement:
"
The cost associated with an
    UPDATE (of which you do as many as you do reads typically) more then offsets
any
    percieved gain by denormalization.
"

Could you elaborate that by a test example.

Thank you vary much

          Followup:

A normalized structure might have:

table customer ( cust_id, cust_name, cust_address );
table orders ( order_id, cust_id );

someone might denormalize that to:

table customer ( cust_id, cust_name, cust_address );
table orders ( order_id, cust_id, cust_name, cust_address );

in order to avoid the "join" from customer to orders during a select.  However,
they have massively impacted

o inserts into orders (how many times do you call up an order really? does what
  you did for SELECTS pay off the penalty you imposed on inserts?)

o updates to customer which much cascade all over the place

database were born to join, my comment was people try to avoid the join -- not
realizing they just moved the "cost" elsewhere.  did they do the right thing?
many times *not*

      Rating: 5
How about small
      lookup tables?  October 27, 2002
Reviewer:  Doug
      C  from CT, USA
Tom - if databases are born to join, how do you feel about small lookup tables
that show up in a bazillion queries and add an addtional join level? What I mean
is the kind of table where there is nothing more than a description for a type
of order or something like that.  I'm guessing you are going to say that if it
is read quite frequently then the blocks will be in the buffer cache and the
overhead is minimal.  Even so, it seems like the kind of think that can be
easily re-inforced in the app as well (i.e, - forcing values 1-5 or something
rather than joining with a lookup talbe containing 1-5)..  What do you prefer to
do?

          Followup:

are 1-5 constants for ever and ever and ever (they never are -- you'll add 6
later, remove 1)

If 1-5 are well know, take the approach we did in the data dictionary for many
things -- views with DECODES.  Using FBI's (function based indexes) you could
even "where on them" if you like.

Works well for very small lookup tables.

      Rating: 5
What difference
      does it make if ...  October 28, 2002
Reviewer:
      Pasko  from HH;Germany
Hi Tom,

In your example above ...

table customer ( cust_id, cust_name, cust_address );
table orders ( order_id, cust_id );

What difference does it make when i specify  or do not specify the Foreign Key
from Orders to Customer.

Some people would still call that Denormalized if i omit the Foreign Key .

What's your take on that Tom.

Best Regards.

          Followup:

A rose by any other name is still a rose.

If it walks like a duck, talks like a duck -- hey, it must be a duck.

I would not call that denormalized, no.  There is no redundant data -- I left
out primary and fkeys for the example.

      Rating: 5
What is your
      opinion?  May 06, 2003
Reviewer:  Thaha
      Hussain  from Manama, BAH
Dear TOM,

Please see a few tables from my comuterised Library:

LIB_MASTER
=========
MASTER_CODE
TITLE
AUTHOR_ID
......
......
......

LIB_CHILD
==========
ITEM_CODE
MASTER_CODE
AVAILABLE DEFAULT 'Y' <<---- Please Note this field

BOOK_ISSUE
==========
ITEM_CODE
MEMBER_ID
ISSUE_DATE
RETURN_DATE
RETURNED_DATE
.........
.........

When one book is issued, the transaction is recorded in the BOOK_ISSUE table and
the field AVAILABLE is made 'N' for the curresponding ITEM_CODE in LIB_CHILD
table.

RETURNED_DATE is filled while the book is returned. (Suppose no renewal!)

My doubt is:-

We will know whether a particular book is available or not by querying the

BOOK_ISSUE table. So there is no need of the filed AVAILABLE in the LIB_CHILD

table. If it is there, is it against Normalization? What is your opinion?

          Followup:

Normalization is overrated.
Denormalization can be taken to frightening extremes.

The answer usually lies some where in between the two extremes.

Depends on the volume of queries against this system and their nature.  If you
query up "a" row from lib_child to see if it is available and do not ask
questions like "how many books are available" "how many are not available", then
a view like:

create view lib_child
as
select ......, nvl( (select 'N'
                       from book_issue
                      where item_code = lib_child_table.item_code
                        and returned_date is null ), 'Y' ) available
  from lib_child_table;

might be easier to maintain.

If I asked those questions frequently however -- I might want Y/N to physically
appear in that table and would pay the price on insert/update/delete on
book_issue in order to have it be so.

Since there could be mutating table issues if you try to maintain this field via
a trigger (assuming referential integrity is in place here), you might lean
toward the view -- an index on book_issue(item_code,returned_date) would make it
fairly efficient.

      Rating: 5
Thank
      You!!!  May 06, 2003
Reviewer:  Thaha
      Hussain  from Manama, BAH
Dear Tom,

Thank You!!

-Thaha Hussain

      Rating: 5
Whick is the
      best practice?  July 14, 2003
Reviewer:  TH
       from Manama, BAH
Dear Tom,

We are extending one of our software to a new edition.

Right now we have a table like this
=======================================
TABLE CLASSES:-
class#   |   column2   |   column3
-----------------------------------
Level1   |     --      |      --
Level2   |     --      |      --
Level3   |     --      |      --
Level4   |     --      |      --
Level5   |     --      |      --
Level6   |     --      |      --
Level7   |     --      |      --

Column2 & Column3 are a few columns which are out of our discussion.

We introduced a new table to group these levels for taking reports based on
that.

PROGRAM_MASTER:-

Program_id #  | Description
-----------------------------
     ID1      | First program
     ID2      | Second program
     ID3      | Third program
     ID4      | Forth program

===================================================================

Question:-
Whick is the best practice among the following?

CASE I
Adding a new column (Program_id) to the table CLASSES

TABLE CLASSES:-
class#   |   column2   |   column3   | Program_id
---------------------------------------------------------
Level1   |     --      |      --     |  ID1
Level2   |     --      |      --     |  ID1
Level3   |     --      |      --     |  ID1
Level4   |     --      |      --     |  ID2
Level5   |     --      |      --     |  ID2
Level6   |     --      |      --     |  ID3
Level7   |     --      |      --     |  ID3

CASE - II
Creating a new table as follows?

PROGRAMS_AND_LEVELS:-

Program_id   |    Level
------------------------------
     ID1     |   Level1
     ID1     |   Level2
     ID1     |   Level3
     ID2     |   Level4
     ID2     |   Level5
     ID3     |   Level6
     ID3     |   Level7

Thanks a lot!

          Followup:

since it appears to be a classic "1 to M" relationship -- not a "many to many",
the first approach, using a foreign key, would be the one I would lean towards

      Rating: 4
In your new
      book...  July 14, 2003
Reviewer:  Kashif
       from Reston, VA
Hi Tom,

Do you cover normalization/denormalization in your new book? I did read
somewhere that you discuss how to design efficient schemas, and I assumed one of
the sections under that would be normalization. Thanks.

Kashif

          Followup:

efficient schemas talks about the physics of data.

Me, i've never been too overly concerned about getting my data into the 30th
normal form close to nirvana.

I'm more worried about it getting it *fast*

In a way i guess I do talk about normal/denormal -- but I never call it that.  I
call it "implementing to answer your most frequently asked questions".  I'll
organize the data in the way that

a) is easy enough to access
b) gives the best performance for the most frequent types of access

eg: I'll store the data in a format perfect for querying but terrible for
writing if 90% of my access is reading the data (i'll ping the writes for the
benefit of the read).

I'll do the opposite if the opposite is true.

      Rating:
      3
Clarification?  December 03, 2003

Reviewer:  Mark Wooldridge  from Vienna VA.

Can you calrify your remark...

If 1-5 are well know, take the approach we did in the data dictionary for many
things -- views with DECODES.  Using FBI's (function based indexes) you could
even "where on them" if you like.

Works well for very small lookup tables.

I kinda udertand the decode concept but the FBI concept is still confusing.

          Followup:

ops$tkyte@ORA920PC> create table t ( cd int, data varchar2(80) );

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create or replace view v
  2  as
  3  select cd, decode( cd, 1, 'code 1',
  4                         2, 'code 2',
  5                         3, 'code 3',
  6                            'unknown' ) cd_str, data
  7    from t
  8  /

View created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create index t_idx on t( decode( cd, 1, 'code 1',
  2                                          2, 'code 2',
  3                                          3, 'code 3',
  4                                             'unknown' ) )
  5  /

Index created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> exec dbms_stats.set_table_stats( user, 'T', numrows =>
1000000, numblks => 100000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> set autotrace traceonly explain
ops$tkyte@ORA920PC> select * from v
  2  where cd_str = 'unknown'
  3  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=10000 Bytes=550000)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=10000
Bytes=550000)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=4000)

ops$tkyte@ORA920PC> set autotrace off

what I meant was -- you can index the "decode" and then "where" on the decod and
still use the index efficiently.

December 04, 2003 PRASAD -- Thanks for the question regarding "how
      to multiplex redo log files.", version

      You Asked
I want to know the procedure for
multiplexing of online redolog files

      and we said...
You simply create groups with more then 1 member in them.

For example it might look like:

system@ORA734.WORLD> alter database add logfile
                     group 4 ( '/d02/oradata/ora734/redo04.log',
                               '/d04/oradata/ora734/redo04.log' )
                     size 10m;

Database altered.

That added a group with 2 members.  they are now multiplexed.  You can just add
members to existing groups as well (see the lang ref manual for syntax).

The IMPORTANT thing is to make sure the members of the groups are on DIFFERENT
physical devices -- else you are defeating the purpose of multiplexing them in
the first place (recovery from media failure)

Review &
      Followup

      Rating: 3
Which Member of
      the Group is Copied to the Archive Destination?  December
      03, 2003
Reviewer:  Joe Fabiano  from PA, USA

I want to multiplex the online redo logs but I only
have 3 mount mounts (single disks) dedicated to
the Online Redo logs.  If I create members A on disk1
and B on disk 2 for group 1 and C on disk 2 and D on disk 3 for group 2 wont
there be a conflict with the log writer and the archiver when the switch is made
between group 1 and 2?  Also, since there's 2 members in a group which one
will arch copy the the archived redo log destination directory?

          Followup:

with 3 disks, you don't have any real choice here.  takes 5 (4 online 1 arch)
minimally but really 6 (4 online 2 arch since ARCHIVES should be multiplexed as
well)

December 04, 2003 Leah -- Thanks for the question regarding
      "Deleting duplicate records", version 8.1.5

      You Asked
Hi Tom,

I have a table T1 with columns C1, C2 and C3. Currently, there
are duplicate records in T1 with the C1, C2 combination.
T1:
         C1    C2    C3
         --    --    --
          1    50     a
          1    50     b
          2    89     x
          2    89     y
          2    89     z

I would like to delete the duplicate record on this combination
and retain either value of C3. What is the best way of writing
the PL/SQL block or query to do this?

Thanks in advance.
Leah

      and we said...
scott@ORA8I.WORLD> create table t1 ( c1 int, c2 int, c3 char(1)
);

Table created.

scott@ORA8I.WORLD>
scott@ORA8I.WORLD> insert into t1 values ( 1, 50, 'a' );

1 row created.

scott@ORA8I.WORLD> insert into t1 values ( 1, 50, 'b' );

1 row created.

scott@ORA8I.WORLD> insert into t1 values ( 2, 89, 'x' );

1 row created.

scott@ORA8I.WORLD> insert into t1 values ( 2, 89, 'y' );

1 row created.

scott@ORA8I.WORLD> insert into t1 values ( 2, 89, 'z' );

1 row created.

scott@ORA8I.WORLD>
scott@ORA8I.WORLD> delete from T1
  2   where rowid <> ( select max(rowid)
  3                      from t1 b
  4                     where b.c1 = t1.c1
  5                       and b.c2 = t1.c2 )
  6  /

3 rows deleted.

scott@ORA8I.WORLD> select * from t1;

        C1         C2 C
---------- ---------- -
         1         50 b
         2         89 z

will do it.
Review & Followup

      Rating: 4
Duplicate
      values  February 18, 2002
Reviewer:  mohammad
      taha  from Dubai,UAE.
The question was like as follow:

I have a table T1 with columns C1, C2 and C3. Currently, there
are duplicate records in T1 with the C1, C2 combination.
T1:
         C1    C2    C3
         --    --    --
          1    50     a
          1    50     b
          2    89     x
          2    89     y
          2    89     z

I would like to delete the duplicate record on this combination and retain
either value of C3. What is the best way of writing the PL/SQL block or query to
do this?

Here, if I want  to keep both the values of C3 ( meaning 'a' and 'b' both), what
should be done?

More precisely, I want to see the result like:

scott@ORA8I.WORLD> select * from t1;

        C1         C2 C
---------- ---------- -

                      a
         1         50 b
         2         89 x
                      y
                      z

          Followup:

Well, besides the fact that it sounds utterly "illogical", I would use SQLPlus
to do this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t1 ( c1 int, c2 int, c3 char(1)
);

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t1 values ( 1, 50, 'a' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t1 values ( 1, 50, 'b' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t1 values ( 2, 89, 'x' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t1 values ( 2, 89, 'y' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t1 values ( 2, 89, 'z' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> break on c1 on c2
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select c1, c2, c3 from t1 order by c1, c2;

        C1         C2 C
---------- ---------- -
         1         50 a
                      b
         2         89 x
                      y
                      z

      Rating: 5
Sorry for
      asking this here-finding rows with duplicate value in one
      column  May 08, 2003
Reviewer:  lotus
       from MD, US
In my employee table, I have these four column (among others)
col Name              Row 1                Row2
Seq Id                  1                   2
F_name                'Tom'               'Tom'
L _Name                'Kyte'             'Kyte'
Full_name             'Kyte, Tom'        'Kyte, Tom'
i have a need to find out the seq ids of all such employees who have similar
full names.
I will highly appreciate your feedback.
Thanks & best regards,

          Followup:

part of our warehouse building tool is a thing called pure integrate that does
this name merging (which is a complex algorith at best)....

You say "similar" but the above are identical. identical is easy (simple sql).
similar takes a larger tool -- part of our data warehousing offering.

      Rating: 5
Sorry I should
      have mentioned it Identical  May 09, 2003

Reviewer:  A reader
Yes, I am looking for records with identical names.
Thanks

          Followup:

select *
  from t
 where (columns-to-be-compared) in (select columns-to-be-compared
                                      from t
                                     group by columns-to-be-compared
                                    having count(*) > 1 )

      Rating: 5
Many
      Thanks!!  May 09, 2003
Reviewer:
      A reader

      Rating: 3
so
      simple...  May 12, 2003
Reviewer:  Aries
       from PH
this is cool... but i have some solutions you would want to assess. i run this
with less LIO and much much faster... what do you think sir?

delete from t1 where
 rowid in ( select rowid from t1 group by rowid, col1, col2, coln
minus
select min(rowid) from t1 group by col1, col2,coln);

...
assuming we got duplicates in col1, col2,coln.

          Followup:

sure, works great.

      Rating: 4
so
      simple...  May 12, 2003
Reviewer:  Aries
       from PH
whew! cool... *salutes*

i simple 'nod' from the guru would be an honor to me.
sincerely, i am really learning a lot from this site...

may this site go a long long way... and i patiently waiting for the book Tom.

      Rating:
      5
  May 13, 2003
Reviewer:  Rory
       from PH
Great solutions.... c",)

      Rating:
      5
  May 30, 2003
Reviewer:  Mark

How about like this.

delete from t1 where
 rowid in ( select MAX(rowid) from t1 group by col1, col2, coln having count(1)
> 1);

Where your duplicates are accoss col1, col2..coln

          Followup:

Nope, think about it.

if you have:

table t:

X
-------
1
1
1
1

how many times would you have to run your delete to de-dup that?

      Rating:
      5
  May 30, 2003
Reviewer:
      A reader

      Rating: 4
Deleting
      Duplicate Records   September 22, 2003
Reviewer:
      JAMIL  from SAUDI
Dear sir,

   I have this table for creating the items definitions
and the structure of the table as follow:

SQL> descr inventory
 Name                            Null?    Type
 ------------------------------- -------- ----
ITEM_CODE                                VARCHAR2(30)   PRIMARY KEY NOT NULL
ITEM_NAME_E                              VARCHAR2(100)
STORE_CODE                               VARCHAR2(10)
ITEM_CLASS                               VARCHAR2(10)

The ITEM_CODE is generated automatically by the system, but I have duplicate
records in the ITEM_NAME_E , because the item name is enter manually by the user
,so I would like to delete all the duplicate records and keep  only the oldest
ITEM_CODE  , for example

SELECT ITEM_CODE,SUBSTR(ITEM_NAME_E,1,20),ITEM_CLASS
    FROM JAM_INVENTORY A
    WHERE ITEM_NAME_E IN (SELECT ITEM_NAME_E FROM JAM_INVENTORY B
      WHERE ITEM_CLASS = 125
    GROUP BY ITEM_NAME_E
    HAVING COUNT(*) > 1)
    ORDER BY ITEM_NAME_E,ITEM_CODE
/

ITEM_CODE  SUBSTR(ITEM_NAME_E ITEM_CLASS
---------- ---------------------------------------
1251050075    BOX 120*60      125-> not to be delete it
1251050104    BOX 120*60      125-> to be delete it
1251050135    BOX 120*60      125-> to be delete it
1251050165    BOX 120*60      125-> to be delete it
1251050196    BOX 120*60      125-> to be delete it
1251050197    BOX 120*60      125-> to be delete it
1251050198    BOX 120*60      125-> to be delete it
1251050076    BOX 120*70      125-> not to be delete it
1251050105    BOX 120*70      125-> to be delete it
1251050166    BOX 120*70      125-> to be delete it
1251050199    BOX 120*70      125 -> to be delete it

I have 54536 records in this table, would you please give  me an example how to
delete the duplicate records with fast speed.
Thank you very much in advance

Jamil

          Followup:

delete from t
 where item_code <> ( select max(item_code)
                        from t t2
                       where t2.item_name_e = t.item_name_e );

an index on (item_name_e,item_code) would be useful for making this "faster"

      Rating: 3
How to get
      this  September 29, 2003
Reviewer:  pawan
       from USA
Thanks for all the help.
Have a table with the following records

col1     col2    Desc
----    ----    -------------------------
C08     04    REFUSED - INCORRECT ORDER
C09     04    REFUSED - SHIPPING ERROR
C10     04    REFUSED - DUPLICATE ORDER
C11     04    REFUSED - NO REASON
C12     04    RECEIVED LATE
C13     04    ORDER CANCELLED
C14     04    REFUSED - DAMAGED IN TRANSIT
C15     06    WRONG MERCHANDISE
C16     07    DISCONTINUED BY SUPPLIER
C17     07    DISCONTINUED BY CUSTOMER
C18     08    RECALLS

When I run the following query (t1.col2 in ('04','06','07','08')) is just to
simplify

select * from cso.reason_codes_xref t1
where col1 in ( select min(col1)
from cso.reason_codes_xref t2
where t2. col2  = t1. col2)
and t1.col2 in ('04','06','07','08')

I get:
col1     col2    Desc
----    ----    -------------------------
C08     04    REFUSED - INCORRECT ORDER
C15     06    WRONG MERCHANDISE
C16     07    DISCONTINUED BY BMS
C18     08    RECALLS

I want to have distinct col1,col2 and concatenation of all the "desc" for a
particular col2.

i.e.
C08     04    REFUSED - INCORRECT ORDER||REFUSED - SHIPPING ERROR||REFUSED -
DUPLICATE ORDER...
C15     06    WRONG MERCHANDISE
C16     07    DISCONTINUED BY SUPPLIER||DISCONTINUED BY CUSTOMER
C18     08    RECALLS

          Followup:

search this site for

STRAGG

      Rating: 4
SELECTING
      duplicate records  December 02, 2003
Reviewer:
      Jamil  from Saudi
Dear Sir
  I would like to get the duplicate item by using this Query , but it is not
giving the wright result .

SELECT ITEM_CODE,SUBSTR(ITEM_NAME_E,1,40)
    FROM PROJ.IM_INVENTORY
    WHERE (ITEM_LENGTH,ITEM_WIDTH,ITEM_HIGHT1,ITEM_HIGHT2)
IN (SELECT       ITEM_LENGTH,ITEM_WIDTH,ITEM_HIGHT1,ITEM_HIGHT2
            FROM PROJ.IM_INVENTORY
           WHERE ITEM_CLASS =125
            GROUP BY
ITEM_NAME_E,ITEM_CLASS,ITEM_LENGTH,ITEM_WIDTH,ITEM_HIGHT1,ITEM_HIGHT2
            HAVING COUNT(*) > 1)

how can I get the duplicate records by checking Multiple fields ,can give an
example.
thank you  for your valuable answer .

Best regards
Jamil

          Followup:

your query works to find dups as far as I can tell.

ops$tkyte@ORA920PC> select * from im_inventory;

ITEM_CODE ITEM_NAME_E ITEM_LENGTH ITEM_WIDTH ITEM_HIGHT1 ITEM_HIGHT2 ITEM_CLASS
--------- ----------- ----------- ---------- ----------- ----------- ----------
        1           1           1          1           1           1        125
        1           1           1          1           1           1        125
        1           1           1          2           1           1        125
        1           1           1          3           1           1        125
        1           1           1          3           1           1        126
        1           1           1          3           1           1        126
        1           1           1          3           1           1        126

7 rows selected.

ops$tkyte@ORA920PC> SELECT ITEM_CODE,SUBSTR(ITEM_NAME_E,1,40)
  2      FROM IM_INVENTORY
  3      WHERE (ITEM_LENGTH,ITEM_WIDTH,ITEM_HIGHT1,ITEM_HIGHT2)
  4  IN (SELECT  ITEM_LENGTH,ITEM_WIDTH,ITEM_HIGHT1,ITEM_HIGHT2
  5              FROM IM_INVENTORY
  6             WHERE ITEM_CLASS =125
  7              GROUP BY
  8  ITEM_NAME_E,ITEM_CLASS,ITEM_LENGTH,ITEM_WIDTH,ITEM_HIGHT1,ITEM_HIGHT2
  9              HAVING COUNT(*) > 1)
 10  /

 ITEM_CODE SUBSTR(ITEM_NAME_E,1,40)
---------- ----------------------------------------
         1 1
         1 1

you must just not "have any to find"

      Rating: 4
selecting the
      duplicate records   December 03, 2003
Reviewer:
      Jamil Shaibani  from Saudi
Dear Sir
   Thank you very much for your help, As you said the query it works fine
But I have done some modification to the query as follow:

SELECT A.ITEM_CODE,SUBSTR(A.ITEM_NAME_E,1,40)
        FROM IM_INVENTORY A
        WHERE (A.ITEM_LENGTH,A.ITEM_WIDTH,A.ITEM_HIGHT1,A.ITEM_HIGHT2)
    IN (SELECT  B.ITEM_LENGTH,B.ITEM_WIDTH,B.ITEM_HIGHT1,B.ITEM_HIGHT2
                FROM IM_INVENTORY B
               WHERE A.ITEM_CLASS = B.ITEM_CLASS
                GROUP BY

A.ITEM_NAME_E,A.ITEM_CLASS,B.ITEM_LENGTH,B.ITEM_WIDTH,B.ITEM_HIGHT1,B.ITEM_HIGHT2

                HAVING COUNT(*) > 1)
    ORDER BY

   A.ITEM_LENGTH,A.ITEM_WIDTH,A.ITEM_HIGHT1,A.ITEM_HIGHT2

So this query works fine if the table contains is few records , but my current
table contains 56077 records , so the query takes a lot of time , so I am
waiting for the query  to return the result since four hours but still not yet
finished , also I have created the index for the table as follow :

CREATE INDEX  INV_IND1 ON IM_INVENTORY
(ITEM_CLASS,ITEM_LENGTH,ITEM_WIDTH,ITEM_HIGHT1,ITEM_HIGHT2)

How can I make this query faster?

Best regards
Jamil

          Followup:

are you using the rbo or cbo, what query plan do you have.  (hint use CBO if you
want to go fast)

      Rating: 1
SELECTING THE
      DUPLICATE RECORDS  December 04, 2003
Reviewer:
      Jamil  from Saudi
Dear Sir
   I do not have any idea about RBO AND CBO can you explain  in more details
what is RBO AND CBO , I am running my  query in
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, Oracle Label Security, OLAP and Oracle Data Mining
options
JServer Release 9.2.0.1.0 &#8211; Production

Best regards
Jamil

          Followup:

Ahh, ok, your reading assignment for the weekend:

a) read the concepts guide, the most important document about oracle you'll ever
read.

b) read the performance and tuning guide, it'll do the deep dive on the
optimizer

http://otn.oracle.com/pls/db92/db92.docindex?remark=homepage
do NOT skip step a)

it is the most important thing you can do for your Oracle career.

December 04, 2003 -- Thanks for the question regarding "Date
      Validation with PL/SQL", version 8i (8.1.6)

      You Asked
I asked a question about date validation a few days ago...but
the response did not work for some reason.  Here goes again (I
know it's simple, but it's killing me right now)
I am accepting 3 values from a form on a web page - day(1),
month('JAN') and year(2000) - and I need to convert them to a
date, and check them against the server's date, to ensure that
the date supplied is the current date (minus 2 hours, as I need
to use pacific coast time)
If the date is current, the information will be processed, if
not, the form will be re-posted with the information asking the
user to choose the current date.
I have simplified my procedures as much as possible for
debugging purposes, but they still give me the following error
message.  It seems REALLY basic, but it won't go for reasons
that I do not understand.  (I've included the procedures)

ERROR at line 1:
ORA-01898: too many precision specifiers
ORA-06512: at "DOGFISH.CHECK_DATE_FUNC", line 8
ORA-06512: at "DOGFISH.TEST_PROC", line 5
ORA-06512: at line 1

create or replace FUNCTION check_date_func
        (in_date        in date)
RETURN BOOLEAN
IS
        l_date          date;
        l_today         boolean;
BEGIN
        l_date := trunc(in_date,'DD-MON-YY');
        IF trunc(new_time(SYSDATE,'CDT','PDT'),'DD-MON-YY') =
l_date THEN
                l_today := true;
        ELSE
                l_today := false;
        END IF;
        return l_today;
END check_date_func;
/

create or replace procedure test_proc
IS
BEGIN
if check_date_func(sysdate) then
        htp.prn('check_date_func = true');
else
        htp.prn('false');
end if;
END test_proc;
/

Please, I am a very frustrated beginnner who cannot find help on
any of the newsgroups.  Any detailed help (code always welcome)
would be greatly apppreciated!
THANKS!

      and we said...
It is in your use of TRUNC.  You are giving TRUNC a format mask
-- one that you might use on a date with the to_char() function
(eg: given a date and the format mask dd-mon-yy, it will format
the 7 byte binary date into that character string).  Or you
might use it on a STRING with the to_date() function, eg:

    to_date( '01-JAN-00', 'DD-MON-YY' );

That will convert the first string into a 7 byte binary date
using the date format mask dd-mon-yy.

Your check date function (you are just trying to truncate off
the TIME component of the date) can be written simply as:

ops$tkyte@8i> create or replace
  2  FUNCTION check_date_func(in_date in date) RETURN BOOLEAN
  3  IS
  4  BEGIN
  5      return trunc(in_date,'dy') =
  6                trunc(new_time(sysdate,'cdt','pdt'),'dy');
  7  end;
  8  /

Function created.

You just want to TRUNC the dates using 'dy' (which is the
default actually -- to trunc to the DAY).  You can trunc dates
to the hour, minute, day, week, month, year and so on.  We
return the boolean comparision of the input date with the
converted sysdate...

ops$tkyte@8i> BEGIN
  2     if check_date_func(sysdate) then
  3          htp.p('check_date_func = true');
  4     else
  5          htp.p('false');
  6     end if;
  7  END test_proc;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@8i>
ops$tkyte@8i> set serveroutput on
ops$tkyte@8i> exec owa_util.showpage
check_date_func = true

PL/SQL procedure successfully completed.

That shows that it works when we send in SYSDATE (careful --
if its close to midnight this will not work, it will be
FALSE!)

ops$tkyte@8i> BEGIN
  2     if check_date_func(sysdate+1) then
  3          htp.p('check_date_func = true');
  4     else
  5          htp.p('false');
  6     end if;
  7  END test_proc;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@8i> set serveroutput on
ops$tkyte@8i> exec owa_util.showpage
false

PL/SQL procedure successfully completed.

and that shows if we send in tomorrow -- it returns false
(same caveat about midnight and timezones!)
Review &
      Followup

      Rating: 4
Date and time
      difference   December 03, 2003
Reviewer:
      Jamil  from Saudi
Dear Sir
 In this example I am calculating the consume time for the cars journey, my
table structure as follow:

SQL> descr travel_table
 Name                            Null?    Type
 ------------------------------- -------- ----
 J_START_DATE                            DATE
 J_START_TIME                            CHAR(5)
 J_START_TIME_STATUS                     NUMBER(1)
 J_END_DATE                              DATE
 J_END_TIME                              CHAR(5)
 J_END_TIME_STATUS                       NUMBER(1)
 J_CONS_DAY                              CHAR(5)
 J_CONS_HOUR                             CHAR(5)
 J_CONS_MIN                              CHAR(5)

In my form  I am using this code for calculating
the consume days and consume hours and the consume minutes

the code for the bush button as follow
--------------------------------
when-button-pressed
DECLARE
  ST_DATE VARCHAR2(10);
  ST_TIME VARCHAR2(5);
  ST_TIME_STATUS VARCHAR2(2);
  EN_DATE VARCHAR2(10);
  EN_TIME VARCHAR2(5);
  EN_TIME_STATUS VARCHAR2(2);
  DATA_RET VARCHAR2(50);
  CON_L    NUMBER(4) := 0;

BEGIN

    CHECK_DATE_RANGE(:J_START_DATE,:J_END_DATE);
  ST_DATE := TO_CHAR(:J_START_DATE,'MM-DD-YYYY');
  ST_TIME := RTRIM(LTRIM(:J_START_TIME));

  IF    :J_START_TIME_STATUS  = 1 THEN
              ST_TIME_STATUS :='AM';
  ELSIF :J_START_TIME_STATUS  = 2 THEN
              ST_TIME_STATUS :='PM';
  END IF;

  EN_DATE :=to_char(:J_END_DATE,'MM-DD-YYYY');
  EN_TIME := RTRIM(LTRIM(:J_END_TIME));

  IF    :J_END_TIME_STATUS  = 1 THEN
            EN_TIME_STATUS :='AM';
  ELSIF :J_END_TIME_STATUS  = 2 THEN
             EN_TIME_STATUS :='PM';
  END IF;

  BEGIN
    DATA_RET :=travel_time(ST_DATE,ST_TIME,ST_TIME_STATUS,
                           EN_DATE,EN_TIME,EN_TIME_STATUS);
    MESSAGE('Time Calculated='||DATA_RET);

    :J_CONS_DAY  := SUBSTR(DATA_RET,2,INSTR(DATA_RET,' ')-1);
         CON_L :=INSTR(DATA_RET,'H');
         CON_L := CON_L + 1;
              MESSAGE('CON_L ='||'  ' ||CON_L);

    :J_CONS_HOUR := SUBSTR(DATA_RET,CON_L,INSTR(DATA_RET,' ')-1);
         CON_L :=INSTR(DATA_RET,'M');
         CON_L := CON_L + 1;

    :J_CONS_MIN  := SUBSTR(DATA_RET,CON_L,INSTR(DATA_RET,' ')-1);
    COMMIT;
  END;
END;
---------------------------------------
And the code for the function travel_time as follow:
---------------------------------------
FUNCTION travel_time (sd varchar2,st varchar2,s_ampm varchar2,
                      ed varchar2,et varchar2,e_ampm varchar2)
  RETURN varchar2
IS
  s_dt date;
  e_dt date;
  rtrn varchar2(50);
  ET1 NUMBER; -- Elapsed time
Begin
  begin
    s_dt := to_date(sd || st || s_ampm,'MM-DD-YYYYHH:MIAM');
    exception when others then
    MESSAGE('Invalid start date/time: '||sd||st||s_ampm);
    rtrn := 'Invalid start date/time: '||sd||st||s_ampm;
  end ;

  if rtrn is null then
    begin
      e_dt := to_date(ed || et || e_ampm,'MM-DD-YYYYHH:MIAM');
    exception when others then
      MESSAGE('Invalid end date/time: '||ed||et||e_ampm);
      rtrn := 'Invalid end date/time: '||ed||et||e_ampm;
    end;
  end if;
  if rtrn is null then
    ET1 := e_dt - s_dt;
    rtrn := 'D'||Trunc(ET1)||' '
          ||'H'||Mod(Trunc(ET1*24),24)||' '
          ||'M'||Mod(Round(ET1*1440),60);
  end if;
  return rtrn;
End travel_time;
-----------------------------------------
So my problem is when I run this form I am getting this message when I press the
bush button

1) Invalid start date/time : 12-03-200310:30AM

2) FRM-40735:WHEN-BUTTON-PRESSED trigger raised unhandled exception  ORA-06502

Can you please tell me what is wrong with the above code.

Notice :

The above code works fine if I change the
NLS_LANG  from ARABIC_UNITED ARAB EMIRATES.AR8MSWIN1256
To
AMERICAN_AMERICA.AR8MSWIN1256
I have make this changed for NLS_LANG in the registry editor,But I required to
use the NLS_LANG as
ARABIC_UNITED ARAB EMIRATES.AR8MSWIN1256
Because my form layout is Arabic not English

Waiting for your valuable answer.

Best Regards

Jamil

          Followup:

http://asktom.oracle.com/~tkyte/Misc/DateDiff.html
won't be NLS specific at all

      Rating: 1
Date difference
        December 04, 2003
Reviewer:  Jamil
      Shaibani  from Saudi
Dear sir
   Would you please tell me what is wrong with my code,because the user must
enter the data in the form using these fields

J_START_DATE DATE
J_START_TIME CHAR(5)
J_START_TIME_STATUS NUMBER(1)
J_END_DATE DATE
J_END_TIME CHAR(5)
J_END_TIME_STATUS NUMBER(1)

And also the time format it should be in this format HH12:MI  not as HH24:MI
hours

If you have time to correct my code , it  will be  very help full to me, because
this code is working fine except that as I mention before it is working when I
change the NLS_LANG to English.

Thank you very much for your replay
waiting for your valuable replay.

Best regards

Jamil

          Followup:

i see two dates there

start date
end date

use my code, no time to debug others ;)  you have everything you need.

and remember -- formats are formats -- not data.  DATES are stored in 7 bytes,
in binary.  formats are used to translate strings on the way in and format them
on the way out of the database.  they have no bearing on HOW the data is
actually stored.  they are purely for your viewing pleasure.  so you can in fact
format the dates however you like.

don't work with dates as strings (as you are). work with DATES.

      Rating:
      5
excellent  December 04, 2003
Reviewer:
      عا شد  from inida
&#1705;&#1740;&#1575;

December 04, 2003 Yogesh -- Thanks for the question regarding
      "Dispatcher Error", version 8.1.7

      You Asked
Why do we get, ORA-00106 cannot startup/shutdown database when connected to a
dispatcher. I faced this error while shutting down the database. But this was
not the case with all, I could shutdown another DB on the same server ... why ?

      and we said...
You cannot shutdown/startup the database via MTS, you must be using a dedicated
server connection.

The times it worked -- you were NOT using MTS, you were connected over a
dedicated server connection.

You've never been able to do startup/shutdown via MTS.
Review &
      Followup

      Rating: 2
More
      Explaination Required  March 19, 2002
Reviewer:
      Yogesh  from PUNE, India
I was trying to do this from server manager prompt,

SVRMGRL
connect Internal
shutdown

I was not using Microsoft Transactions Server...

          Followup:

MTS = Multi-Threaded Server (long before there was an MS Transaction server)...

If you were using a dispatcher, you were connected via MTS (aka shared server).
You cannot shutdown using a shared server (aka MTS) connection.

You must use a dedicated server only.

The full error explains it all:

$ oerr ora 106
00106, 00000, "cannot startup/shutdown database when connected to a dispatcher"
//  *Cause: An attempt was made to startup/shutdown database when connected to
//          a shared server via a dispatcher.
// *Action: Re-connect as user INTERNAL without going through the
dispatcher.
//          For most cases, this can be done by connect to INTERNAL without
//          specifying a network connect string.

      Rating: 3
Got
      Solution  April 05, 2002
Reviewer:  Yogesh
       from Pune, India
I was having 4 Db instances running on this machine.

What I done is

set oracle_sid=orcl
svrmgrl
svrmgrl> connect internal/oracle
svrmgrl> shutdown

It worked. It did not gave me dispatcher error.

          Followup:

yes, you didn't use MTS that time.  Ta-Dah...

You were using MTS (multi-threaded server, shared server) before and as the
error message says -- no go.

      Rating: 5
About the
      command  October 18, 2002
Reviewer:  Sanjaya
      Balasuriya  from Sri Lanka
Tom,

You have used ; "oerr"

What is this comamnd ? Does it come with Oracle ?
Or you created it ?

Tkanks.

-San

          Followup:

It comes with Oracle on platforms that have rich command line tools like Unix.

Since it uses common tools like grep and such, it does not exist for windows.

you can:

ops$tkyte@ORA920.US.ORACLE.COM> exec dbms_output.put_line( sqlerrm(-1) );
ORA-00001: unique constraint (.) violated

on any platform to get some of the information....

      Rating: 3
Q on
      Dispatcher  December 04, 2003
Reviewer:
      Bobby  from India
You said "You cannot shutdown/startup the database via MTS"
If I've configured a DB for shared server then ? Can u tell me how do I start?

          Followup:

you are connected via dedicated server.

shared server (MTS) and dedicated server are NOT mutually exclusive.

to startup/shutdown you probably are:

connect / as sysdba

that'll be dedicated server.  go ahead and connect over an MTS network
configuration and give it a go, see what happens.

December 04, 2003 VIMAL -- Thanks for the question regarding
      "Question About PORT# in Tnsnames.ora file (Oracle8i)", version Oracle
      8.1.6

      You Asked
Hi,
 I have noticed in Oracle 8i I can have the same port# for
different alias entries in Tnsnames.ora file
I have two databases E8i9 & Q8i9 and Tnsnames.ora file enties
are as follows. Both working fine.
E8i9.world=(DESCRIPTION=(ADDRESS=(COMMUNITY=TCP.world)(PROTOCOL=T
CP)(Host=Whatcom)(Port=1521))(CONNECT_DATA=(SID=E8i9)(GLOBAL_NAME
=E8i9.world)))
Q8i9.world=(DESCRIPTION=(ADDRESS=(COMMUNITY=TCP.world)(PROTOCOL=T
CP)(Host=Whatcom)(Port=1521))(CONNECT_DATA=(SID=Q8i9)(GLOBAL_NAME
=Q8i9.world)))
Could you please explain me How I can have the same port# ?
If I have about 500 users on each database, does the same port#
degrade the performance of the system?
Is there any documentation available to read more about this
topic?

Thaanks
Vimal K Nair
Captura Software

      and we said...
The port number in the tnsnames entry tells us how to find the
sqlnet listener.  The sqlnet listener, upon recieving a
connection will create a new process to handle that connection.
This port is used to find a "master" process that will create a
"child process" that we really connect to.

The listener is used upon connect and after that is out of the
loop.  As long as all 500 of your users are able to connect in
an OK fashion -- using the one listener on the one port is just
fine.  You can have >1 listener running but it would only affect
connecting to the datbase, not performance after that.

As well, if you had a tnsnames where each entry had a different
port -- that does not imply there is in fact >1 listener! A
single listener can listen on many ports.

As it is -- you are fine the way you are unless you are
experiencing connection problems currently (and you do not
mention any)

See

http://technet.oracle.com/doc/oracle8i_816/network.816/index.htm
for more info on the networking architecture.  In particular:

http://technet.oracle.com/doc/oracle8i_816/network.816/a76933/toc.htm
would be of interest.
Review & Followup

      Rating: 3
How many
      listeners are used?  December 04, 2003
Reviewer:
      Bobby  from India
how to find the number of listeners currently used ?

          Followup:

$ ps | grep tns

typically "one", but -- you as the admin, should know already.

you start them manually, yourself?

December 04, 2003 Anil Kumar -- Thanks for the question regarding
      "Poor Response while working in MTS mode.", version 8.1.6

      You Asked
HI Tom,

Sub : Poor Response while working in MTS mode.

We have 8i 8.1.6 installation on NT plateform and Oracle database is configured
to run in MTS mode. We are 25 concurrent users at present but expected to grow
up to 250. Local naming method of NET8 is followed to connect to Oracle server
in a switched ethernet LAN.

My question is after connecting to a database in a LAN, users face delay in
getting response most of the times even for smaller data volume. In general,
delay is around about one or one and one half minutes. Only few times, we get
prompt response but not always. Our SGA is properly sized and there is no
contention in harddisks. Also, all objects in the database are properly tuned. I
personnely feel that MTS performance at our end does not seem to be proper or we
have not configured MTS for best performance.

Can you suggest any remedial solution to this ?

Followings are our initialization parameters used for MTS.

MTS_DISPATCHERS = "(PROTOCOL=TCP)(PRE=Oracle.aurora.server.SGiopServer)(MUL=ON)"
SHARED_POOL_SIZE = 152428800
LARGE_POOL_SIZE = 31457280
SORT_AREA_SIZE = 65536
SORT_AREA_RETAINED_SIZE = 65536

There is no mts_max_dispatchers, mts_servers & mts_max_servers defined. Meaning
that we have retained default one dispatcher and one shared server. Session Data
Unit (SDU) buffer size has not been efined in a client as well as server
tnsnames.ora resulting efault SDU buffer size of 2048. I have also verified the
available MTS performance views but could not conclude anything.

Following are tnsnames.ora files for SERVER & CLIENT respectively.

1.    SERVER tnsnames.ora

# TNSNAMES.ORA Network Configuration File:
D:\Oracle\Ora81\network\admin\tnsnames.ora
# Generated by Oracle configuration tools.

GEB.WORLD =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = NMP)(Server = NF5000)(Pipe = ORAPIPE))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = orcl)
    )
  )

GEBTCP.WORLD =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = nf5000)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = orcl)
    )
  )

ORCL =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = nf5000)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = ORCL)
    )
  )

EXTPROC_CONNECTION_DATA =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC0))
    )
    (CONNECT_DATA =
      (SID = PLSExtProc)
      (PRESENTATION = RO)
    )
  )

2.    CLIENT tnsnames.ora

# C:\FORMS6I\NET80\ADMIN\TNSNAMES.ORA Configuration
File:C:\forms6i\net80\admin\tnsnames.ora
# Generated by Oracle Net8 Assistant

GEB =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = nf5000)(PORT = 1521))
    (CONNECT_DATA = (SID = ORCL))
  )

EXAMPLE3.WORLD =
  (DESCRIPTION =
    (ADDRESS = (COMMUNITY = nmp.world)(PROTOCOL = NMP)(Server =
FinanceServer1)(Pipe = ORAPIPE))
    (CONNECT_DATA = (SID = ORCL))
  )

EXAMPLE1.WORLD =
  (DESCRIPTION =
    (ADDRESS = (COMMUNITY = tcp.world)(PROTOCOL = TCP)(Host = Production1)(Port
= 1521))
    (CONNECT_DATA = (SID = SID1))
  )

BEQ-LOCAL.WORLD =
  (DESCRIPTION =
    (ADDRESS = (COMMUNITY = beq.world)(PROTOCOL = BEQ)(PROGRAM = oracle73)(ARGV0
= oracle73ORCL)(ARGS = '(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))'))
    (CONNECT_DATA = (SID = ORCL))
  )

EXAMPLE2.WORLD =
  (DESCRIPTION =
    (ADDRESS = (COMMUNITY = spx.world)(PROTOCOL = SPX)(Service = Server_lsnr))
    (CONNECT_DATA = (SID = ORCL))
  )

TCP-LOOPBACK.WORLD =
  (DESCRIPTION =
    (ADDRESS = (COMMUNITY = tcp.world)(PROTOCOL = TCP)(Host = 127.0.0.1)(Port =
1521))
    (CONNECT_DATA = (SID = ORCL))
  )

3.    SERVER sqlnet.ora

# SQLNET.ORA Network Configuration File:
D:\Oracle\Ora81\network\admin\sqlnet.ora
# Generated by Oracle configuration tools.

SQLNET.AUTHENTICATION_SERVICES= (NTS)

NAMES.DIRECTORY_PATH= (TNSNAMES)

4.    CLIENT sqlnet.ora

AUTOMATIC_IPC = OFF
TRACE_LEVEL_CLIENT = OFF
names.directory_path = (TNSNAMES)
names.default_domain = world
name.default_zone = world

      and we said...
one shared server -- think about it.

25 people.  All ask to do something that takes 5 seconds.

Hmmm... what happens?  the first guy gets his answer in 5 seconds, second in 10,
third in 15, ..... the poor 10th guy is 50 seconds, 20th guy 100 seconds and so
on.

Sound like you have INSUFFICIENTLY configured your shared servers for your
concurrent usage doesn't it???  You have one server serving everyone.

Unless you have a real reason to use MTS -- don't.  250 users is *tiny*.  No
need for MTS there.  Just use dedicated server.
Review &
      Followup

      Rating: 3
Is this correct
      for MTS  March 18, 2003
Reviewer:  subbu
       from MN, USA
i got that 250 is tiny. I'm having a server with 7 database instances(databases
and not in OPS) having the following load 500+600+50+50+20+10+2 = 1232 users
using a single listener. Can i go for MTS configuration for the databases where
the no. of users are 500 and 600? Considering my server is having 8 CPU and 8GB
ram on E10k?

Is going for MTS is only related to resource availability then can you tell how
much resource is normally required for a dedicated connection?
thanks

          Followup:

oh yuck.  hate that "more then one instance on a server" concept.  why don't you
consolidate them all -- it is more manageable, more secure, more everything.

Yes, using MTS would be something to consider here (moving to a single instance
would be my first consideration).  8 cpu is on the low side here for that many
concurrently connected users.

A dedicated server connection consumes an OS process.  Each process has its own
PGA.  Using MTS serves three primary purposes:

o process reduction, less context switching between OS level processes in the OS
itself

o slight memory reduction, by removing the now redundant PGA allocations

o maximum active session control -- you'll never have more then N active
sessions where N = max shared servers.  If all 1,232 users hit the "go" button
using dedicated server -- the machine would fall over, get swamped.  Using MTS,
some would just wait a tad longer then others but everything would proceed in an
organized manner.

      Rating:
      4
  March 20, 2003
Reviewer:
      A reader

      Rating: 5
Very
      useful  May 19, 2003
Reviewer:  Bruno Di Rei
      Araujo  from Uberlândia, MG, Brazil
Thanks for this explanation Tom !

Although I have a small server and small # of clients, I figured out that MTS
would be better to my case based in your answer!

Thank you a lot !

      Rating: 3
Can u bit more
      explainatory ?  December 04, 2003
Reviewer:
      Anu  from Mumbai
you said "one shared server -- think about it.". u also said go for dedicated
configuration. but 250+ users going for dedicated ? more user better to go for
MTS. what u say?

          Followup:

one shared server (one thing at a time in the database, no concurrency)

PLUS

25 users

EQUALS
----------------

nothing getting done.  it is pure math here.

250 users is tiny, small.  the smallest of database servers today should handle
that fine

shared server is for why you absolutely cannot get another process/thread
created on the system (totally maxed out) and you need to get one more user
logged in.

      Rating: 4
Poor Response
        December 07, 2003
Reviewer:  AnilKumar
      Deshmukh  from VADODARA, GUJARAT, INDIA,
HI Tom,

Thanks for a nice reply & explanations.

December 04, 2003 -- Thanks for the question regarding
      "uninitialized collections", version 8.1.7

      You Asked
Hi Tom,

I wonder if you could help me with the following? I am trying to test a small
package that
will be called by java to insert into a database. The connectivity will be using
JDBC thin client.

I need to test the package to be sure that it's working properly. As you can see
I am having no luck.
The JAVA program will be passing an ARRAY of stf_no to the procedure. Hence this
is the reason why I
created the type (MY_TYPE) at database level.

Any help will be appreciated.

1) create table tst_table (stf_no varchar2(7))

2) create or replace type my_type as table of VARCHAR2(7);

3)

Create or replace package tst_pkg
AS
    procedure insert_data ( i_stf_no IN MY_TYPE );

END tst_pkg;
/

4)

Create or replace package body tst_pkg
As
    procedure insert_data ( i_stf_no IN MY_TYPE )
    As

    BEGIN
        for i IN 1 .. i_stf_no.count
        LOOP
            insert into tst_table (stf_no) values (i_stf_no(i));
        END loop;
    COMMIT;

    END;

END tst_pkg;
/

5)
Declare
    v_stfno_array my_type;
begin
    v_stfno_array(1) := 'U123456';
    v_stfno_array(2) := 'U234567';
    v_stfno_array(3) := 'U345678';

    tst_pkg.insert_data(v_stfno_array);
end;
/

I got the following error:

Declare
*
ERROR at line 1:
ORA-06531: Reference to uninitialized collection
ORA-06512: at line 4

      and we said...
5)
Declare
    v_stfno_array my_type := my_type();
begin
    v_stfno_array(1) := 'U123456';
    v_stfno_array(2) := 'U234567';
    v_stfno_array(3) := 'U345678';

    tst_pkg.insert_data(v_stfno_array);
end;
/

or more simply:

Declare
    v_stfno_array my_type := my_type('U123456', 'U234567', 'U345678');
begin
    tst_pkg.insert_data(v_stfno_array);
end;
/

or, as terse as it gets:

exec tst_pkg.insert_data( my_type('U123456', 'U234567', 'U345678') );

Review &
      Followup

      Rating:
      4
  September 29, 2002
Reviewer:  Femi
       from UK
Thanks Tom. I must say that if I use:

Declare
    v_stfno_array my_type := my_type();
begin
    v_stfno_array(1) := 'U840971';
    v_stfno_array(2) := 'U840972';
    v_stfno_array(3) := 'U840973';
    v_stfno_array(3) := 'U840974';
    tst_pkg.insert_data(v_stfno_array);
end;

I get the following error:-

Declare
*
ERROR at line 1:
ORA-06533: Subscript beyond count
ORA-06512: at line 4

The

exec tst_pkg.insert_data( my_type('U123456', 'U234567', 'U345678') );

and

Declare
    v_stfno_array my_type := my_type('U123456', 'U234567', 'U345678');
begin
    tst_pkg.insert_data(v_stfno_array);
end;

works fine for me.

          Followup:

whoops -- forgot the

Declare
    v_stfno_array my_type := my_type();
begin
    v_stfno_array.extend;
    v_stfno_array(1) := 'U840971';
    v_stfno_array.extend;
    v_stfno_array(2) := 'U840972';
    v_stfno_array.extend;
    v_stfno_array(3) := 'U840973';
    v_stfno_array.extend;
    v_stfno_array(3) := 'U840974';

or:

Declare
    v_stfno_array my_type := my_type();
begin
    v_stfno_array.extend(4);
    v_stfno_array(1) := 'U840971';
    v_stfno_array(2) := 'U840972';
    v_stfno_array(3) := 'U840973';
    v_stfno_array(3) := 'U840974';

      Rating:
      4
  September 29, 2002
Reviewer:  Femi
       from UK
Excellent! Thanks.

      Rating: 5
Reference to
      uninitialized collection - Record of Table  October 17, 2003

Reviewer:  Su Baba  from Ca, USA
Hi Tom,

It seems that Oracle 10g has a different way of initializing a record of table.
Is the last example that I provide below a correct way of initializing a record
of table?  Is there any other way of doing it?

thanks

SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
PL/SQL Release 8.1.7.4.0 - Production
CORE    8.1.7.0.0    Production

TNS for Linux: Version 8.1.7.4.0 - Production
NLSRTL Version 3.4.1.0.0 - Production

SQL>
SQL> declare
  2    type number_table is table of number;
  3
  4    type tbl_rec is record (
  5       tbl1 number_table,
  6       tbl2 number_table
  7    );
  8
  9    x tbl_rec;
 10
 11  begin
 12    x.tbl1.EXTEND(1);
 13    x.tbl2.EXTEND(1);
 14  end;
 15  /

PL/SQL procedure successfully completed.

-- connect to a 10g database

SQL>
SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle10i Enterprise Edition Release 10.1.0.1.0 - Beta
PL/SQL Release 10.1.0.1.0 - Beta
CORE    10.1.0.1.0    Beta

TNS for Linux: Version 10.1.0.1.0 - Beta
NLSRTL Version 10.1.0.1.0 - Beta

SQL>
SQL> declare
  2    type number_table is table of number;
  3
  4    type tbl_rec is record (
  5       tbl1 number_table,
  6       tbl2 number_table
  7    );
  8
  9    x tbl_rec;
 10
 11  begin
 12    x.tbl1.EXTEND(1);
 13    x.tbl2.EXTEND(1);
 14  end;
 15  /
declare
*
ERROR at line 1:
ORA-06531: Reference to uninitialized collection
ORA-06512: at line 12

SQL>
SQL> declare
  2    type number_table is table of number;
  3
  4    type tbl_rec is record (
  5       tbl1 number_table,
  6       tbl2 number_table
  7    );
  8
  9    x tbl_rec;
 10
 11  begin
 12    x.tbl1 := NUMBER_TABLE();
 13    x.tbl2 := NUMBER_TABLE();
 14    x.tbl1.EXTEND(1);
 15    x.tbl2.EXTEND(1);
 16  end;
 17  /

PL/SQL procedure successfully completed.

          Followup:

10g is more correct then 9i and before was.  9i and before "did it wrong"

ops$tkyte@ORA920> declare
  2    type number_table is table of number;
  3
  4    type tbl_rec is record (
  5       tbl1 number_table,
  6       tbl2 number_table
  7    );
  8
  9    x tbl_rec;
 10
 11  begin
 12    x.tbl1.EXTEND(1);
 13    x.tbl2.EXTEND(1);
 14  end;
 15  /

PL/SQL procedure successfully completed.

since that works -- this too should:

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2    type number_table is table of number;
  3
  4    x number_table;
  5
  6  begin
  7    x.EXTEND(1);
  8  end;
  9  /
declare
*
ERROR at line 1:
ORA-06531: Reference to uninitialized collection
ORA-06512: at line 7

but it did not.  the number_tables in the record were atomically NULL, they
should not have been permitted to extend.  10g fixed the 9i and before "buggy"
behavior

      Rating: 4
What is
      wrong?  December 04, 2003
Reviewer:  Duncan
       from UK
Tom

Please can you help with this problem.  I am trying to
pass a varray between two procedures:

create or replace type myScalartype as object ( x int, y date, z varchar2(20))
/
create or replace type myCollectionType as varray(5) of myScalarType
/

create or replace procedure p_ArrayTest (p_id IN number, p_array out
myCollectionType)

is
p_1  myScalarType;
p_2  myCollectionType := myCollectionType();
i number(10):=1;

begin

For rec_Cur in (select     c1,
            c2,
            c3
        From    arrayTest
        Where    c1 = p_id ) LOOP

    dbms_output.put_line('a c1 = '||rec_Cur.c1);
    dbms_output.put_line('a c2 = '||rec_Cur.c2);
    dbms_output.put_line('a c3 = '||rec_Cur.c3);

    p_1 := myScalarType(rec_Cur.c1, rec_Cur.c2, rec_Cur.c3);

    P_2.extend;

    p_2(i) := p_1;

    i:=i+1;
END LOOP;

end p_ArrayTest;
/

create or replace procedure p_readArrayTest (pid in number)

is
p_1  myScalarType;
readArray myCollectionType := myCollectionType();
i number(12);

begin

    P_arrayTest(pid, readArray);

    For i in 1 .. 2 loop

    dbms_output.put_line('c1 = '||readArray(i).x);
    dbms_output.put_line('c2 = '||readArray(i).y);
    dbms_output.put_line('c3 = '||readArray(i).z);

    end loop;

end p_readArrayTest;
/

exec p_readArrayTest(1);

10:06:50 HRIA@HRIA>
10:06:50 HRIA@HRIA>exec p_readArrayTest(1);
a c1 = 1
a c2 = 03/12/2003 12:39:50
a c3 = Today
a c1 = 1
a c2 = 03/12/2003 12:39:50
a c3 = AlsoToday
BEGIN p_readArrayTest(1); END;

*
ERROR at line 1:
ORA-06531: Reference to uninitialized collection
ORA-06512: at "HRIA.P_READARRAYTEST", line 14
ORA-06512: at line 1

I thought i had initialized it? What is wrong with this
code?  Please can you help.

Many thanks

          Followup:

you never made any assignments to p_array in p_arrayTest

p_array is the OUT parameter, it is set to null on the way in (it was OUT, not
IN OUT), and then assigned to your local variable.

this is what the code could look like:

ops$tkyte@ORA920PC> create table arraytest( c1 int, c2 date, c3 varchar2(20) );

Table created.

ops$tkyte@ORA920PC> insert into arraytest values ( 1, sysdate, 'x' );

1 row created.

ops$tkyte@ORA920PC> insert into arraytest values ( 1, sysdate, 'x' );

1 row created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create or replace procedure p_ArrayTest (p_id IN number,
p_array out myCollectionType)
  2  as
  3  begin
  4      select cast( multiset( select myScalarType(c1,c2,c3)
  5                               from arrayTest
  6                              where c1 = p_id ) as myCollectionType )
  7        into p_array
  8        from dual;
  9
 10  end p_ArrayTest;
 11  /

Procedure created.

ops$tkyte@ORA920PC> show errors
No errors.
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create or replace procedure p_readArrayTest (pid in number)
  2
  3  is
  4      readArray myCollectionType := myCollectionType();
  5  begin
  6      P_arrayTest(pid, readArray);
  7
  8      For i in 1 .. readArray.count
  9      loop
 10          dbms_output.put_line('c1 = '||readArray(i).x);
 11          dbms_output.put_line('c2 = '||readArray(i).y);
 12          dbms_output.put_line('c3 = '||readArray(i).z);
 13      end loop;
 14  end p_readArrayTest;
 15  /

Procedure created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> exec p_readArrayTest(1);
c1 = 1
c2 = 04-DEC-03
c3 = x
c1 = 1
c2 = 04-DEC-03
c3 = x

PL/SQL procedure successfully completed.

      Rating: 3
IGNORE previous
      posting !!  December 04, 2003
Reviewer:
      A reader
Sorry Tom please ignore my previous posting.

I realise my mistake, i had forgotten to assign anything to the OUT array!!
It was just the error message that confused me.  It was one of those things you
stare at for ages and then realise it was a hugely basic mistake!

Thanks anyway

      Rating:
      5
Message  December 18, 2003
Reviewer:
      Mariano  from Córdoba, Argentina
Tom,
It happens a lot that someone post a question, you answer it but immediately
after the post the sender realizes he/she made a mistake or something like that.

We know you're answering in arrival order (?) and of course you cannot know that
the sender has changed his/her post before reading it when its turn has arrive.
There must be a way to relate posts in order to save that work when the question
has been changed.
Regards.-

December 04, 2003 mahbuba -- Thanks for the question regarding
      "stopping listener having a password", version 8.1.6.0

      You Asked
To stop a listener,I tried as follow but in vain.

LSNRCTL> stop

connecting to <description=<address=<protocol=IPC><KEY=EXTPROC0>>>
TNS-01169: The Listener has not recognised the password.

My password is like this :

passwords_listener = (oracle)

LSNRCTL> stop/oracle

NL-00853:Undefined command "stop/oracle". Try "help"

I don't see any solution in help.

So,plz help me out.

      and we said...
LSNRCTL> help set
The following operations are available after set
An asterisk (*) denotes a modifier or extended command:

password                rawmode                 displaymode
trc_file                trc_directory           trc_level
log_file                log_directory           log_status
current_listener        connect_timeout         startup_waittime
use_plugandplay         save_config_on_stop

LSNRCTL> help set password
set password : set the password for subsequent calls

LSNRCTL>

use set password <password>

read

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/network.817/a76933/controlu.htm#421503
it explains how to use this command line tool in depth.
Review &
      Followup

      Rating: 1
stopping
      listener having a password  December 04, 2003

Reviewer:  sami  from Islamabad , Pakistan

it didn't work.

          Followup:

sure it does. you must not know the password.

show us a cut and paste of "it not working"

December 04, 2003 gaurang -- Thanks for the question regarding
      "CURRENT MODE AND consistent mode", version 8.1.6

      You Asked
hi tom

i your discussion regarding consistant gets and db dblock gets
you mention about block read in CURRENT MODE AND consistent mode

Could you please explain exact meaning of this words by giving
some examples?

Thanks

      and we said...
Ok, when you turn on autotrace in sqlplus you can see these stats.

Lets run a query:

ops$tkyte@ORA817.US.ORACLE.COM> set autotrace traceonly statistics
ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> select * from emp;

14 rows selected.

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

Here we had 4 db block gets.  Those were blocks read in CURRENT MODE.  The
blocks that were read are actually the blocks that tell us how to FULL SCAN the
dept table (data dictionary type of information).  We need to get that in
CURRENT MODE (as of RIGHT NOW) to get an accurate picture of what the table
looks like.

We also had 2 consistent gets -- these are blocks we read in "consistent read"
mode -- also known as query mode.  This means we were reading them as of the
POINT IN TIME the query began.  See

http://technet.oracle.com/doc/oracle8i_816/server.816/a76965/c23cnsis.htm#17882
for a great discussion of this.

Now, if we do a delete:

ops$tkyte@ORA817.US.ORACLE.COM> delete from emp;

14 rows deleted.

Statistics
----------------------------------------------------------
          0  recursive calls
         20  db block gets
          1  consistent gets
          0  physical reads
       4220  redo size
       1009  bytes sent via SQL*Net to client
        796  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         14  rows processed

we do lots more db block gets -- why?  because in order to delete the data we
need to get the block in CURRENT MODE, as it exists right then.

why did we do a consistent get?  because the "read" part of the delete uses the
consistent read mechanism -- we only delete data that existed in the table as of
the point in time the delete began.  Consider if DEPT was a 1,000,000 row table
instead.  It'll take a while to delete all of those rows.  As you are deleting
however, other sessions are inserting and committing data.  This consistent read
mechanism makes it so that we only delete the rows that existed WHEN WE BEGAN
the delete.  We will not delete this new data being inserted.

followup to comment one below

Think of the delete being processed like this:

  for x in ( select rowid from emp )   --- CONSISTENT GETS
  loop
     delete from emp where rowid = x.rowid;  --- CURRENT MODE GETS
  end loop;

that is in effect what is happening.  the READ portion of the delete, the
portion of the delete that finds rows to actually remove reads the table in
consistent read mode.  For each row that it finds that it wants to delete, it
does a CURRENT MODE get on that block to get the row as it exists now (so as to
modify that row).

Consider what happens when we delete 1, 5, 10, and all rows from emp:

scott@ORA817.US.ORACLE.COM> set autotrace traceonly statistics;
scott@ORA817.US.ORACLE.COM> select * from emp;

14 rows selected.

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

scott@ORA817.US.ORACLE.COM>
scott@ORA817.US.ORACLE.COM> delete from emp where rownum <=1;

1 row deleted.

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

scott@ORA817.US.ORACLE.COM> rollback;

Rollback complete.

scott@ORA817.US.ORACLE.COM> delete from emp where rownum <=5;

5 rows deleted.

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

scott@ORA817.US.ORACLE.COM> rollback;

Rollback complete.

scott@ORA817.US.ORACLE.COM> delete from emp where rownum <=10;

10 rows deleted.

Statistics
----------------------------------------------------------
          0  recursive calls
         16  db block gets
          1  consistent gets
          0  physical reads
       3080  redo size
        850  bytes sent via SQL*Net to client
        565  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         10  rows processed

scott@ORA817.US.ORACLE.COM> rollback;

Rollback complete.

scott@ORA817.US.ORACLE.COM> delete from emp where rownum <=15;

14 rows deleted.

Statistics
----------------------------------------------------------
          0  recursive calls
         22  db block gets
          1  consistent gets
          0  physical reads
       4272  redo size
        850  bytes sent via SQL*Net to client
        565  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         14  rows processed

scott@ORA817.US.ORACLE.COM> rollback;

Rollback complete.

Each one reads blocks in current mode with a cardinality that relates to the
number of rows deleted.
Review & Followup

      Rating:
      1
  June 28, 2001
Reviewer:
      A reader
I still don't understand why delete takes so many db block gets,
In this case, there are only 14 rows in emp table, if we have 8k db block, all
those rows should stay at ONLY one block, what you mean get the block in CURRENT
MODE?
if no body else touch the database, it is in CURRENT MODE,
you simply get that block and delete it, ONE db block gets is enough.
if there are some concurrent user modifying the database, i can understand that
db block gets goes high, but not in this case. can you explain to me more about
it?

      Rating:
      3
  June 29, 2001
Reviewer:
      A reader
Tom:
I create table empback as select * from emp;
set autotrace on;
then select * from empback.
why consistent get and db block gets is different from
select * from emp?
for emp it is 4 and 2;
for empback it is 12 and 6;

      Rating: 5
current mode
      AND consistent mode  December 04, 2003
Reviewer:
      A reader
Tom,
I am trying to understand the difference between current mode AND consistent
mode.
I was looking at the examples you have given above for 'CURRENT MODE AND
consistent mode'.  The URL you have pointed to
>>
http://technet.oracle.com/doc/oracle8i_816/server.816/a76965/c23cnsis.htm#17882
>>for a great discussion of this.

Is this related to statement-level read consistency and transaction-level read
consistency?
Can you please give the book and chapter for the above URL(since OTN moved all
docs....)
Book: Concepts guide ....Chapter ....

Thank you

          Followup:

http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76965/c23cnsis.htm#2570

December 04, 2003 Andre -- Thanks for the question regarding
      "Conntinuing question "Remote Listener Registration"", version 8.1.6

      You Asked
Continuing this question from yesterday:

------------------------------------------------------------

You Asked

In order to achieve connection load balancing, how does remote
connection registration take place ? Could you describe the
steps in the process in which PMON and a local listener become
aware of the remote nodes and intances ?

------------------------------------------------------------
and I said...

PMON is associated with an instance.  PMON contacts a listener
and registers itself with that listener, making the listener
aware of the existence and status of that instance.

You configure this by setting up the mts_dispatchers init.ora
parameter.  Information contained in this parameter tells PMON
how to contact a given listener.

------------------------------------------------------------

In fact, I read that

MTS_DISPATCHER="(protocol=tcp)(listener=<listener_alias>)"

makes PMON register with a given remote listener.

My question is: Since it seems I can only register remotely
using MTS, can´t I register remotely using only dedicated
servers. Is MTS necessary ?

Thanks

      and we said...
It is a function for MTS.  When using dedicated severs -- we
need something on that machine with the SERVER to spawn the
dedicated server.  The tnslistener does this for us.  If the
listener were remote, it would not be able to start the
dedicated server, hence the listener must be co-located with the
server when using a dedicated server configuration.
Review &
      Followup

      Rating: 3
Pls
      Clarify  December 04, 2003
Reviewer:  Anil
      Pant  from Hyderabad
It means even while using dedicated server and if the listener is remote u've to
register through and mts_dispatcher.
If listener is not remote then no need of using this parameter.
Am I correct ?

          Followup:

no, it means if

a) the listener is remote, you'll be using shared server (MTS), not dedicated

if you are using shared server, these parameters apply.

if you are using dedicated server only, they do not.

December 04, 2003 Robert -- Thanks for the question regarding "A
      Custom Error Handling System", version 8.1.7

      You Asked
Hi Tom,

I am in the process of designing an error handling and failure notification
system for use by our all our custom PL/SQL programs.
My plan is that all our custom PL/SQL programs, in the case of a fatal error,
will call a single error handling procedure from within it's "WHEN OTHERS"
exception clause.

The intent is that we will have a centralized, standard, efficient error
handling system, particularly an error notification system whereby the
appropriate people will automatically be notified in case their program fails.

This error handling procedure will receive parameters such as: calling program,
sqlerrm, key variable values at time of error, etc.

The error handling procedure will then send an e-mail notification to the
appropriate person (based on the program that failed), write a formatted error
messege to the appropriate location based on the failed program, and bail out.

I have some questions, please regarding this.
1. Do you like this idea philosophically and practically (i.e. having a
centralized error handling and notification procedure/system) ? if not why?
2. I am concerned that by calling a procedure inside the exception clause, even
though I am passing the current error info
(sqlerrm, sqlcode)... I am afraid I may be losing some error info or masking
some error info. I am wondering if I am losing anything by not dumping the
original error immediatly, while it is 'fresh', instead of passing the 'sqlerrm'
to another procedure... is this a valid concern ? why or why not ?
3. Can I count on 'format_error_stack' to give me the failing program's error or
should I insist they pass this (e.g. sqlerrm) in to the error handling
procedure?
4. Do you have a better way to implement a centralized error handling and
notification system; or do you have any other comments, suggestions, critisisms
regarding my idea ?

Thanks for your valuable expertise. Please answer the questions as detailed as
possible.

Thanks,

Robert.

      and we said...
1) no, not really. Here is why.

I believe this common routine would HAVE to call "raise;" to re-raise the error
(else the caller of the procedure has NO CLUE that an error happened.  Dont say
"we'll use return codes -- that defeats the entire purpose of exceptions")

So, if this common routine has to re-raise the exception (and it DOES, 100% on
that) - then what happens when you have:

  p1 calls p2 calls p3 calls p4

and p4 "fails".  Now the when others in p4 logs the error, sends the email and
re-raises the error.  P3 gets the error and does it all over again.  Then p2
then p1.  You get 4 notifications -- 4 logs -- for the single error.

I believe this is the responsibility of the CALLER of P1 -- it'll get the error
and that is where this error handler belongs.

2) You'll lose the line number of the source codee that actually flung the
error....  Only the CALLER of p1 can get that.

3) you can call sqlerr to get the error

4) do it at the TOP level, at the client level (eg: at the app server) not
inside the stored procedure.

Review &
      Followup

      Rating: 3
We can't put
      the notification our client apps.  July 17, 2002

Reviewer:  Robert  from USA
Tom,

We use a 3rd party client app. Our PL/SQL jobs are scheduled from within this
client app's scheduler.

So do you think it might work IF an application only called this common error
handling procedure once? (e.g.: in the exception clause of the main block) ...
all inner calls would have 'raise' within their "when others"...(?).

I would appriciate any other ideas or feedback.

Thanks,

Robert

          Followup:

That falls in line with my "the caller should handle it" however -- if the
caller is a PLSQL block -- you will lose the line number information whence the
error occurred.  This information is what is truly useful -- it would be a shame
to lose it.

search this site for dbms_trace for a possible solution in that area.

      Rating: 2
this approach
      is already in use!  July 18, 2002
Reviewer:
      Alex Rakhalski  from Moscow, Russia
  Hi, Tom, hi, Robert!
  Robert, you described almost same thing, what we are using in real-life big
project. It works quite fine! We have a lot of server-side PL\SQL code - mainly
packages and triggers. They calls each other in such complex ways, that we need
some way to see exactly where and what is happened (For example, procedure proc1
executes some DML and implicitly fires table trigger T1, it in turn fires table
trigger T2, it calls proc P2, it calls proc P3, proc P3 recursively calls
himself 5 times, and raise exception). We developed error-handling package
(about 1996 year), which successfully works several years in large financial
information system. Lets call it PK_TRACE, for example. A few months ago I
rewrite this package for 8I (with autonomous transaction package become much
more elegant!) for our new project.
  Describe briefly main principles.
  1. Each procedure, function, trigger and so on begins with service call to
pk_trace.pr_begin_routine.
  2. Each procedure and ... calls before exit pk_trace.pr_end_routine. If there
are several exit points (RETURNs from function, for example),
pk_trace.pr_end_routine must be before each of them.
  3. Each proc ... has standart WHEN OTHERS handler, with calls
pk_trace.pr_except_handler.
  4. If developer wants to issue custom error message to user, he also can call
pk_trace.pr_except_handler(Error message) from any place in his proc.
  5. pk_trace.pr_except_handler saves in background error messages and their
call stacks in tables. We can build call stack on previous calls to
pr_begin_routine/pr_end_routine, call Oracle for help
(dbms_utility.format_call_stack, but you will not see triggers in stack in this
case).
  6. Standart client-side routines detects call to pk_trace.pr_except_handler
and display corresponding error information on special screen form to end user.
Error information remains in tables for later uses also.
  7. Because we register facts of routines starting/finishing, we can also
collect PL/SQL performance metrics in background and save them in tables too. We
have some parameters, influencing collection performance statistics.

  What about reraising exception? pk_trace.pr_except_handler does some
intelligent processing (we saves information to tables, you send e-mail) only
once, in first place. All consequent calls to pk_trace.pr_except_handler (while
"thrown out" to uppest caller) does nothing, only re-raises exception.

      Rating:
      5
Errors  February 25, 2003
Reviewer:
      mo
Tom:

I have a web page where user enter several rows  in one page and then he saves
it. If one colum "storage_code" is not a valid entry, it will raise a foreign
key violation nd then I create a page that tells user to go back and edit that
storage code.

Now, I want to take all the user-entered records and check all sotrage codes and
report them at once instead of one at a time.  Is this possible and how would
you do it?

Do you go to an exception handler as soon the first foreign key violation
happens and then check for all other records in a select statement in teh
exceptin handler and keep raising the excpetion again.  Can this be done?

Thank you

          Followup:

dude, you have an array of them -- just check them?

you would:

  for i in 1 .. array.count
      begin
          insert ...
      exception
          when ...
               bad_codes := bad_codes || ',' || array(i);
      end;
   end loop;
   if ( bad_codes is not null )
   then
        print out error message "these codes are bad, try again " || bad_codes;
        ROLLBACK;
   else
        normal processing...
   end if

      Rating:
      5
error  February 25, 2003
Reviewer:
      mo
Tom:

1. I thought when an exception is raised in pl/sql, processing stops.  Is it
because the loop it goes back to process the next array value.

Thank you,

          Followup:

you need to read more about exceptions and error handling in the plsql guide (or
in my book -- i cover it there as well)

processing continues at the nearest exception block.  processing never "stops",
processing just goes somewhere else.  You put the exception block in the loop
and the loop will "continue"

      Rating:
      5
errors  February 26, 2003
Reviewer:
      mo
Tom:

Thanks, it works nicely. However I declared
bad_Codes  varchar2(4000)

What about if there are more than 4000 bytes.  How would you handle it then?

          Followup:

try LONG which is a varchar2 32k in plsql

      Rating: 4
CLOBber
      it  February 26, 2003
Reviewer:  KP
       from IL USA
No doubt MO tried to bump it over 4000.
Anyone for LONG RAW?

Me? I would use a CLOB.

      Rating: 1
No perfect
      solution for error handling  April 17, 2003

Reviewer:  Milan Adamovic  from Canada
I have been investigating perfect error handling approach in PL/SQL and come up
with the conclusion that it cannot be really done.

We have been using approach with standard "when others" exception handling
approach. The handler logs the error in the error log table and re-raises the
exception. The problem with this approach is that you loose the line number
where the error occurred, which could otherwise be obtained by parsing calling
stack (dbms_utility.format_call_stack).

Here are the issues, if you do not handle the error until the main calling
procedure:
- Main procedure (that is invoked by the client) can very often be at several
levels of the calling chain, especially during testing and debugging, as well in
the final application.
- As soon as you put exception handler (in the main procedure, for instance),
you loose the calling stack and you cannot trace back to the procedure where the
error originated. I am not sure why Oracle does it this way. Something like
print stack trace from Java would resolve all problems. Note that I haven't
tested this behavior in 9i.

To me the first solution is still better, because you at least get the procedure
name where the error occurred.

          Followup:

In developement -- use dbms_trace, problem solved.  search this site for
dbms_trace.

using when others -- hate it.  client logs errors.  In 3 tier -- this is
trivial.  In client server, harder, but tell me -- are you building a client
server application?

      Rating: 2
Useless Error
      Stack?  August 20, 2003
Reviewer:  Vicky Fan
       from Canada
The error stack seems only record error number and message without
package/procedure names and line number at which the error occurred, with the
exception of trigger. Why wouldn't Oracle provide it?

Your whence function has to be executed before each major step in user
procedures in order to capture the line number.

          Followup:

the package and procedure is there?

what useless error stack do you refer to?

      Rating:
      2
  August 22, 2003
Reviewer:  Vicky
      Fan  from Canada
dbms_utility.format_error_stack

With dbms_utility.format_call_stack, the original line number at which the error
occurred is lost in exception block. The format_error_stack records error module
name without error line number, at least with 8.1.7.

If exception is required to be logged in the database table, the only option to
record the exact error line number is using variable locator or the whence
function to mark up each major steps in the source code. Not at all a graceful
solution than if the format error stack can track and provide all the error
details in the exception block.

          Followup:

or dbms_trace

it'll do it as well.  search this site for that.

      Rating:
      3
  August 25, 2003
Reviewer:
      A reader
My only concern is: Is there any performance impact to use dbms_trace in
production environment as error logging or is it only meant for debugging in
development environment?

Thanks.

          Followup:

sounds like a prime time to "benchmark"

of course, every single extra line of code adds a Performance impact -- by you
simply writing the first line of code, you've introduced a performance impact.

what you need to determine, based on your requirements is:

does the value of having this additional information outweigh the cost of having
this additional information.

perhaps you make this "feature" something that is EASILY enabled via a parameter
somewhere -- so you can turn it on and off at will.  That is what I do.  I leave
all code fully instrumented and turn it on/off when I want.  It is in production
you REALLY need this capability as they generally don't allow you to just drop
in some debug code then!  being able to turn this on is of great benefit.

      Rating: 5
package
      excpetion  December 02, 2003
Reviewer:  mo

Tom:

I have 6 procedures in a package that use the same exception.  I declared the
exception in the package body variable section and put the EXCEPTION
no-records-Found at the end of the package.  However I got an error in
compilation.

Is it true that I have to put the expcetion handler in each procedure body?  If
it is true, then I thought packages are supposed to help you build modular code
rather than repeating the same routine in each procedure.

Thank you,

          Followup:

example, example, example

examples are worth a trillion words.

but if I follow you -- you are asking "does the exception handler actually have
to be in executable code -- places where code goes?"  the answer is YES, the
exception block must be in a scoped section of executable plsql code.

create package body demo_pkg
as
   -- no code here!

procedure p
is
begin
    -- code here, here we can have an exception handler
end;

   -- no code here, no exceptions can be caught here, we don't have any CODE

procedure p2
is
begin
   -- a new scope of code, exception away
end;

-- no code here, no exceptions where

begin
    -- code here, could have an exception block, but remember SCOPE
    -- this block ONLY runs once per session, when the package is first
    -- touched
end demo_pkg;
/

You need to think about what exceptions are for

they are for handling EXPECTED errors.

They should surround the TINEST, MOST FOCUSED piece of code possible.  EG:

begin
    .... code code code.....
    begin
        select ... into .... from .... where ....;
    exception
        when no_data_found then ....
    end;
    .... code code code .....
end;

sounds like you might be trying to be "lazy" and type just a little code with a
huge exception block -- but that would be wrong, very bad. exception blocks =
small, tight, little.

      Rating:
      5
exceptions  December 03, 2003
Reviewer:
      mo
Tom:

I am not trying to be lazy but I am trying to write good modular code like you
said.  I have one exception handler (to handle no records found) in about 6
procedures.

I was thinking there should be a better way to repeat the same code 6 times in
each procedure.

Obviously I can't write it in the package body.  I guess the only way is to
write another procedure for that block of code and call it in the exception
handler?

For now I will stick with the handler written 6 times.

          Followup:

that is not good modular code.

Good modular code would have exception blocks at the very lowest level possible
(as close to the source of the suspected error spot as humanly possible) that
all call a subroutine <<<==== thats the modular part.

don't repeat the code, put the code into a subroutine, call the subroutine.

you are not "stuck" with anything, you are coding "properly" when you have the 6
well formed, meaningful, clearly defined, close in scope exception blocks.

Don't be mad
Be proud of the good code.

      Rating: 4
Exception
      Stack  December 04, 2003
Reviewer:  malcolm

Tom, I want to get the whole error stack in PL/SQL.
dbms_utility.format_error_stack does not give me the entire error stack, but
somehow SQL*Plus reports it, so the information must be available. How does
SQL*Plus get the line numbers and the error stack?

  1  declare
  2  procedure p(i pls_integer) is
  3  begin
  4     if i > 0 then p(i-1); else p(1/0); end if;
  5  end;
  6  begin
  7     p(5);
  8  exception
  9     when others then
 10        dbms_output.put_line(dbms_utility.format_error_stack);
 11* end;
SQL>/
ORA-01476: divisor is equal to zero

  1  declare
  2  procedure p(i pls_integer) is
  3  begin
  4     if i > 0 then p(i-1); else p(1/0); end if;
  5  end;
  6  begin
  7     p(5);
  8* end;
SQL>/
declare
*
ERROR at line 1:
ORA-01476: divisor is equal to zero
ORA-06512: at line 4
ORA-06512: at line 4
ORA-06512: at line 4
ORA-06512: at line 4
ORA-06512: at line 4
ORA-06512: at line 4
ORA-06512: at line 7

          Followup:

only the CLIENT can get it.  one you "catch" the exception, the error stack is
reset, cleared.

search this site for dbms_trace if your goal is to log this information.

December 04, 2003 Valsaraj -- Thanks for the question regarding
      "Adding Time part in Date Field", version 4.5

      You Asked
Tom
We are developing an application which needs to add no.of hours
worked by a person on a particular day.Say, I am working over a
problem for 5 times in a day. So I keep a record for that call.
 Table Action_record contains-
Callno number, Action_date Date, Description varchar2(100),
Time_spent Date.

Over the forms I specify the format mask HH24:MI for the field
Time_spent, which push the data with date and time . I wanted to
calculate total time worked over that problem/problems on a
particular date. Also this calculation is required for Reporting
purpose. Is there any routine to extract the time portion and
add together from that date field ? If not, please suggest me a
way.

thanks

      and we said...
You should have stored time_spent as a number and put into the
number of seconds or minutes or whatever spent on the task.
that would be easy to convert into hours/minutes/seconds and to
add up.

You can

  select sum( time_spent-trunc(time_spent) )
    from t
   where .....

that'll give you an decimal number that represents the time as a
fraction of a day.  Then we can get days, hours minutes:

ops$tkyte@ORA8I.WORLD> create table t ( time_spent date );

Table created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> insert into t values ( to_date(
'13-feb-2000 05:42', 'dd-mon-yyyy hh24:mi' ) );

1 row created.

ops$tkyte@ORA8I.WORLD> insert into t values ( to_date(
'13-feb-2000 01:30', 'dd-mon-yyyy hh24:mi' ) );

1 row created.

ops$tkyte@ORA8I.WORLD> insert into t values ( to_date(
'13-feb-2000 00:15', 'dd-mon-yyyy hh24:mi' ) );

1 row created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select
  2     trunc( sum( time_spent - trunc(time_spent) )  ) Days,
  3     trunc( mod( sum( time_spent-trunc(time_spent) )*24,
                                                 24  ) ) Hours,
  4     trunc( mod( sum( time_spent-trunc(time_spent) )*24*60,
                                               60  ) ) Minutes
  5    from t;

      DAYS      HOURS    MINUTES
---------- ---------- ----------
         0          7         27

Review &
      Followup

      Rating: 3
Converting
      number to hours/miniutes/seconds/subseconds  July 18, 2002

Reviewer:  Tony
Tom,
Could you please show with the example to get sum of activity duration stored in
number format in the oracle DB (8.1.6). But the input is from an ASCII file in
HHMISSSS format.
Input (through ASCII file)--> 00054000 (three records)
stored in DB in number(8) format -->54000
O/P required in the format HHMISSSS = 00170000
Thanks

          Followup:

I don't understand what

HH MI SSSS

could be as a format?  Further, I don't see how 54000, using that mask, could
result in 00170000 either?

      Rating: 4
A
      Reader  July 22, 2002
Reviewer:  Rahul
       from India
Hi Tom

Might be this is a very silly question but I want to know that how can we add
minutes or seconds in time part of date.
suppose current date is showing 23-jul-02 05:45:33 , but at
the time of insertion in the table I want to send 10 seconds ahead like
23-jul-02 05:45:43.

How can we do this??

Thanks.

          Followup:

date + 1 = date + one day
date + 1/24 = date + one hour
date + 1/24/60 = date + one minute
date + 1/24/60/60 = date + one second

so, date + 10 * (1/24/60/60) = 10 seconds from now.

      Rating: 5
TIME
      only  February 28, 2003
Reviewer:  Jeff
       from Miami, USA
Great!

Question - how can I store just a time with no date?

In other words I'm tracking shift start and stop times like 8:00 AM and 5:00 PM.
 This is a daily schedule so I don't want to save the day part just the time
part.

Does it make sense to save in Date/Time data type with a low values for date?

Thanks,
Jeff

          Followup:

Just pick a date and use it

01010001 (january 1, year 1 is one I've used)

In 9i you could use an interval -- start_time_interval = 8hours.  duration =
9hours.

That way -- any "day" plus start_time_interval would give you 8am on that day.

any "day" plus start_time_interval + duration would give you 5pm.

      Rating: 4
Time Enter as
      HH12:Mi  October 12, 2003
Reviewer:  Jamil
       from Saudi
Hi Tom

I have a table that is used for the driver travelling information date and time
so, the structure of the table as follow
Travel_table
-------------
Car_no varchar2(15)
Driver_code number
Tr_date  date
Tr_time number

So I want the user to enter the travel time  as HH12:MI  and calculated and get
the out put as follow

DAYS    HOURS     MINUTES
---------- ---------- -----------------
 0                 7               27
can you give an example in detail.
Thank you very much for you help

Best Regards

Jamil

          Followup:

not following you.

if they give you hh12:mi (which would be a bug -- what exactly is 01:01 -- 1am
or 1pm) - you

a) have 0 days always
b) have the hours (they gave you hh12)
c) have the minutes (they gave you mi)

I'm not seeing what the issue is?  you have everything you need?

      Rating: 1
Adding Time
      part in Date Field  October 13, 2003
Reviewer:
      Jamil  from Saudi
Dear Tom
What I am trying to say, that the user want to enter the date
as 1 PM not as 13 PM and 2 PM Not as 14 PM
and 3:30 PM not as 15:30 PM and so on. I am very sorry if my question was not
clear.
As you can see the field that I want the user to enter the time in it is defined
as follow
Tr_time number .

Would you give an example how to do it.
And thank  you very much for your valuable answer.
Best regards
Jamil

          Followup:

sorry -- not getting it.  so the user gives you 1 pm.

now what?  what do you want to do with that?  what would that look like in a
"number".

i would be storing in a date (or interval type in 9i).

but it is pretty easy to turn 1:30 pm into 13 hours, 30 minutes:

  1   select to_char( to_date('1:30 pm', 'hh:mi am'), 'hh24' ) hours,
  2          to_char( to_date('1:30 pm', 'hh:mi am'), 'mi' ) minutes
  3*        from dual
ops$tkyte@ORA920> /

HO MI
-- --
13 30

      Rating: 1
Getting the
      difference between two times as the format HH12:MI   October
      14, 2003
Reviewer:  Jamil  from Saudi
Dear Tom

I am very sorry for not understanding my problem I have change my table
structure as follow

Travel_table

CAR_NO  VARCHAR2(15)
DRIVER_NO NUMBER
START_DATE        DATE  --> for starting travailing date
END_DATE          DATE  -->  for ending the journey date
START_TIME        DATE   --> for starting travailing TIME
END_TIME          DATE    -->  for ending the journey TIME
So I want the system to display the  "Calculating time between two
date/timestamp "
my date format is 'HH12:MI'.
Please do not change the date format
So the user will enter the data as follow
Enter value START_TIME: 12:45
Enter value for END_TIME: 01:05

The required out put as follow:

The output should be as follow :

  DAYS           HOURS           MINUTES
----------   ----------       ----------
   0               0              20

Enter value START_TIME: 11:45
Enter value for END_TIME: 01:05

The output should be as follow :

  DAYS         HOURS          MINUTES
----------   ----------       ----------
   0            1                20

Enter value START_TIME: 12:00
Enter value for END_TIME: 01:00

The output should be as follow :

   DAYS         HOURS           MINUTES
----------   ----------       ----------
    0            1                  0

Thanks a lot for your cooperation .
Best regards
Jamil

          Followup:

to_date() both

then subtract

and see

http://asktom.oracle.com/~tkyte/Misc/DateDiff.html
for getting h/m/s from the difference...

      Rating: 3
Adding Time
      part in Date Field  October 15, 2003
Reviewer:
      Jamil  from Saudi
Dear Tom
Would you please give Detail example of using HH12:MI instead of HH24:MI
,because I am installing the START_TIME , END_TIME and START_DATE and END_DATE
in the database as HH12:MI format  and I am not understand
how I can use this HH24:MI format in your example

create or replace function datediff( p_what in varchar2,
                                     p_d1   in date,
                                     p_d2   in date ) return number
   as
      l_result    number;
   begin
        select (p_d2-p_d1) *
               decode( upper(p_what),
                       'SS', 24*60*60, 'MI', 24*60, 'HH', 24, NULL )
        into l_result from dual;

       return l_result;
   end;

and also consider that I am entering the other two filed
START_DATE  DATE  --> for starting travailing date
END_DATE    DATE  -->  for ending the journey date
the user enter the data as the following :

Enter value for START_DATE : 01-03-2003
Enter value for END_DATE   : 01-03-2003
Enter value for START_TIME : 12:00
Enter value for END_TIME   : 01:00
so As I said before I want to  get the  output  as the following  :

  DAYS          HOURS           MINUTES
----------   ----------       ----------
   0             1                  0

Thank you very much for your help

Best regards

Jamil

          Followup:

you are storing it in 24 hour format -- dates are a 7 byte internal format that
always have

YYYY
MM
DD
HH24
MI
SS

don't get confused by format masks - they are used simply to parse data on the
way in and format it on the way out.

they have NOTHING to do with HOW the data is stored.

just do it the way I described above and you'll get what you asked for.

      Rating: 4
How
      ?  October 15, 2003
Reviewer:  Ashiq Shamsudeen
      A  from Trichy, Tamil Nadu, India
What's the reason behind this..

scott@ASHIQ>create table a (dt date)
/
Table created.

scott@ASHIQ> insert into a values('01-jan-02');

1 row created.

scott@ASHIQ> ed
Wrote file afiedt.buf

  1* insert into a values('01-jan-99')
scott@ASHIQ> /

1 row created.

   Here, I've inserted the date(for the testing purpose I've not give the
format)

scott@ASHIQ> select to_char(dt, 'dd/mm/yyyy hh:mi:ss am')dt
from a

DT
----------------------
01/01/2002 12:00:00 am
01/01/1999 12:00:00 am

2 rows selected.

 Why its treating "99" as "1999"?. But if I give the format..

scott@ASHIQ> rollback;
Rollback complete.

scott@ASHIQ>insert into a values(to_date('01-jan-99', 'dd-mon-yy'))

scott@ASHIQ> select to_char(dt, 'dd/mm/yyyy hh:mi:ss am')dt from a;

DT
----------------------
01/01/2099 12:00:00 am

 1 row selected.

   Here its taking "99" as "2099"

          Followup:

this is a CLASSIC example of why it is a really bad, sadly bad idea to use 2
characters to try and enter something that takes 4 at least!!!!!!!!!!!!

we should all have learned this in 1999.

anyway, your default date format must be dd-mon-rr

rr says "use a sliding 50 year window".
yy says "use this century"

ops$tkyte@ORA920LAP> select year, to_char( to_date(year,'rr'), 'yyyy' ),
to_char( to_date(year,'yy'), 'yyyy' )
  2    from t;

YE TO_C TO_C
-- ---- ----
50 1950 2050
60 1960 2060
70 1970 2070
80 1980 2080
90 1990 2090
99 1999 2099
01 2001 2001
10 2010 2010
20 2020 2020
30 2030 2030
40 2040 2040

11 rows selected.

see -- the year 50 -- today in 2003 -- resolves to 1950 using RR, 2050 using YY

All of the dates will be 20xx using YY
The dates will be a mix of 19xx and 20xx using RR

If you take this piece of advice you will NEVER EVER be sorry you did (promise)

a) always insist on 4 characters for a year
b) always use EXPLICIT conversions from strings to dates (to_date always)

      Rating: 4
Selecting rows
      of the prior hour and grouping by every minute  December 04,
      2003
Reviewer:  A reader
Hi Tom,

I have a table:
Create table ntwk_stats (delay_secs number, insert_time date);
with the following data:

insert into ntwk_stats values(20, ’12-02-2003 9:59:20’);
insert into ntwk_stats values(18, ’12-02-2003 9:59:50’);
insert into ntwk_stats values(20, ’12-02-2003 10:53:21’);
insert into ntwk_stats values(10, ’12-02-2003 10:53:49’);
insert into ntwk_stats values(22, ’12-02-2003 10:54:10’);
insert into ntwk_stats values(20, ’12-02-2003 10:54:40’);
insert into ntwk_stats values(20, ’12-02-2003 11:01:01’);
insert into ntwk_stats values(22, ’12-02-2003 11:01:03’);

and another table:
Create table avg_ntwk_stats (avg_delay_secs number, stats_time date);

At any given time, I have to find avg(delay_secs) in the prior hour, group them
by every minute and add insert them into avg_ntwk_stats table, i.e., if the
sysdate is say
’12-02-2003 11:03:40 AM’, I should only be selecting records between 10:00:00
and 10:59:59 AM and group them by minute, such that the following rows get
inserted in avg_ntwk_stats table :

15, ’12-02-2003 10:53:00’
21, ’12-02-2003 10:54:00’

I am having trouble selecting data that falls in the prior hour and then
grouping by every minute. Can you help?

Thanks in advance for your input.

          Followup:

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select avg(delay_secs), trunc(insert_time,'mi')
  2    from ntwk_stats
  3   where insert_time between trunc( to_date('12-02-2003 11:03:40'),'hh')-1/24
  4              and trunc( to_date('12-02-2003 11:03:40'),'hh')-1/24/60/60
  5   group by trunc(insert_time,'mi')
  6  /

AVG(DELAY_SECS) TRUNC(INSERT_TIME,'
--------------- -------------------
             15 12-02-2003 10:53:00
             21 12-02-2003 10:54:00

replace to_date('12-02-2003 11:03:40') with sysdate

      Rating: 5
Just wanted to
      Thank You!   December 05, 2003
Reviewer:
      A reader
This is great! I really appreciate this.

December 04, 2003 ALVARO -- Thanks for the question regarding "HOW
      CAN I COMPARE NULL VALUES", version 7.3.4.0.0

      You Asked
I have a trigger for recording the old and new values of the fields changed on a
table.
I compare ...
If (updating and :old.field<>:new.field) Then
 (  Insert into auditing_tables )
End If;

if :old.field is 'JHON' and :new.field is null the insert doesn´t apply and I
need to record both values.

when I compare null values the instruccion fails.
I know that I can´t compare null values ( because the result is unknow)
, so I want to know if is there any function to do this.

thanks

acueto

      and we said...
See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:59412348055
for a method to generate triggers that do this for you but in short, you
need to do a compare such as:

    if ( l_new <> l_old or
         (l_new is null and l_old is not NULL) or
         (l_new is not null and l_old is NULL) )

You could also write a package with overloaded functions for numbers, dates and
strings to do the compare as well.

Review &
      Followup

      Rating: 3
comparing null
      values  June 25, 2003
Reviewer:  A reader
       from pk
Hi,

I have a table with one column
DESC t1

C1 VARCHAR2(200)

Now this column contains alphanumeric values as well
as some nulls. In my application we consider NULL is equal
to NULL.

A user gives an input and then i have to display
all records that match this value in C1, if user inputs
null then all nullls should be displayed

How can I do this in sql.
if I do SELECT * FROM T1 WHERE C1  = &USER_INPUT

if user inputs NULL then this query will return no
records

is there any way to consider NULL = NULL for
this query?

          Followup:

Your application is not written for relational databases, there NULL is never
equal NULL, nor is it NOT equal NULL....

but, read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7555175291763
and use the CBO

      Rating:
      5
NULLIF  June 26, 2003
Reviewer:
      A reader
acueto:

You can NULLIF if you are in 9i.

NULLIF compares expr1 and expr2. If they are equal, then the function returns
null. If they are not equal, then the function returns expr1. You cannot specify
the literal NULL for expr1.

          Followup:

ops$tkyte@ORA920> set null *null*
ops$tkyte@ORA920> select x, y, nullif( x, y )
  2    from t;

         X          Y NULLIF(X,Y)
---------- ---------- -----------
         1          1 *null*             <<<=== the same
         1          2           1
         1 *null*               1
*null*              2 *null*             <<<=== different
*null*     *null*     *null*             <<<=== the same

I don't think it works well when expr1 is NULL

      Rating: 3
Sort of a work
      around....  June 26, 2003
Reviewer:  Mark J
      Bobak  from Ann Arbor, MI
Regarding this query:
SELECT * FROM T1 WHERE C1  = &USER_INPUT

Where the user may supply a NULL.

You should be able to do something like:
select * from t1
where decode('&user_input',null,1,0) = 1
and c1 is null
union all
select * from t1
where decode('&user_input',null,0,1) = 1
and c1 = '&user_input'

It's not pretty, and Tom's right, your data model should
represent that which can be represented by the database,
but the above might work if you're stuck and have no
alternatives.

      Rating: 5
NULLIF =
      CONFUSIONTHEN  June 26, 2003
Reviewer:
      A reader
Hi,
   Now how does this NULLIF work.
As per Tom's example last line
*null*     *null*     *null*

Null is never equal to or not equal to NULL.Its result is unknown

So why does NULLIF returns null ?
I thought we can only compare nulls to nulls in decodes or
NVL or NVL2.

          Followup:

nullif returns null if the first parameter is null.

*null*              2 *null*             <<<=== different
*null*     *null*     *null*             <<<=== the same

      Rating: 4
HOW IS
      THIS  June 27, 2003
Reviewer:  a reader
       from pakistan
CREATE TABLE T1 (C1 VARCHAR2(4));

SELECT * FROM T1 WHERE
NVL(C1,'XXXXX') = NVL(&USER_INPUT, 'XXXXX')

I replace NULL with five 'X' characters which is greater
than the maximum length of C1 i.e VARCHAR2(4). This will
return all rows for which C1 is NULL if the user inputs
NULL. Because c1 is varchar2(4), therefore there is no
chance that XXXXX will be found in c1.

Please comment on this workaround.

Thanks

          Followup:

you want to full scan all of the time and never use an index?

maybe if you used a function based index on nvl(c1...)

      Rating:
      5
PL/SQL  June 27, 2003
Reviewer:
      A reader
acueto, if I were you, I would use:

if :new.field = :old.field then
  null;
else
  << your logic here >>
end if;

      Rating:
      5
Nulls  August 12, 2003
Reviewer:
      Ishaque Hussain  from NJ USA
In our table, we can't figure out what one of the column values is.  The column
is a varchar2(1) and it looks like it's a blank or a null.  It's neither a null
or one blank space.  When we converted it to ascii, it converts to a zero and
ascii 0 is considered a null in ascii format.  Could you let me know what could
have caused that and what an ascii 0 translates to?  We are using Oracle version
8.1.7.4.5

Thanks

          Followup:

ascii 0 is just that -- ascii 0

it is not a blank (that would be ascii 32).
it is not null (that would be NULL)

ops$tkyte@ORA920> create table t ( msg varchar2(10), x varchar2(1) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( 'chr(0)', chr(0) );

1 row created.

ops$tkyte@ORA920> insert into t values ( 'null', NULL );

1 row created.

ops$tkyte@ORA920> insert into t values ( 'chr(32)', chr(32) );

1 row created.

ops$tkyte@ORA920> insert into t values ( ''' ''', ' ' );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> column dumpx format a15
ops$tkyte@ORA920> select msg, dump(x) dumpx, x from t;

MSG        DUMPX           X
---------- --------------- -
chr(0)     Typ=1 Len=1: 0
null       NULL
chr(32)    Typ=1 Len=1: 32
' '        Typ=1 Len=1: 32

someone/program inserted ascii 0 into your table, that is all.  it is not blank,
it is not null, it is chr(0)

      Rating: 3
When is CHR(0)
      used in v$session.terminal?  December 02, 2003

Reviewer:  Matt  from Australia
I picked up the following query from Cary Millsaps new book.

select s.sid db_sid
      ,s.serial# db_serial
      ,p.spid
      ,to_char(s.logon_time,'DD/MM/YYYY HH24:MI:SS') db_logon_time
      ,nvl(s.username, 'SYS') db_user
      ,s.osuser os_user
      ,s.machine os_machine

,nvl(decode(instr(s.terminal,chr(0)),0,s.terminal,substr(s.terminal,1,instr(s.ter
minal,chr(0))-1)),'none') os_terminal
      ,s.program os_program
  from v$session s
      ,v$process p
 where 1=1
   and s.paddr = p.addr
   and s.username like upper('SYSTEM')

The line:

nvl(decode(instr(s.terminal,chr(0)),0,s.terminal,substr(s.terminal,1,instr(s.term
inal,chr(0))-1)),'none') os_terminal

Looks for CHR(0). I wondered what the purpose of CHR(0) was and when it might
appear in the terminal column.

Any ideas?

          Followup:

chr(0) is the "c" null terminator.

it can appear in the terminal name.

if it does, it could tell a c print program to "stop printing" anymore

he is just removing it.

      Rating: 4
Do you
      recommend using sys_op_map_nonnull()?  December 03, 2003

Reviewer:  Kurt  from Springfield, VA USA
Tom,
Comparing 2 values, each of which is possibly null, seems to be a common
problem. In a newsgroup, Jonathan Lewis suggests that the "brave at heart" can
use an undocumented function named sys_op_map_nonnull(). In another posting on
this site, you say that that function is:

<quote>
just an internal "hint".

"ignore that man behind the curtain" sort of stuff.
</quote>

I find that function to be great as an elegant method for comparing 2 values,
but it has limitations:
- it's undocumented, and therefore poses a risk of going away or changing
functionality
- it's a SQL-only function that is unrecognized by PL/SQL

For the benefit of other readers, here's how it *could* be used, if you dare.

v9.2.0.1.0> select sys_op_map_nonnull(null) from dual;

SY
--
FF

v9.2.0.1.0> select sys_op_map_nonnull('abc') from dual;

SYS_OP_M
--------
61626300

v9.2.0.1.0> select sys_op_map_nonnull(1234) from dual;

SYS_OP_M
--------
C20D2300

v9.2.0.1.0> select sys_op_map_nonnull(sysdate) from dual;

SYS_OP_MAP_NONNU
----------------
78670C0301251B00

v9.2.0.1.0> select * from dual where sys_op_map_nonnull(null) =
sys_op_map_nonnull('a');

no rows selected

v9.2.0.1.0> select * from dual where sys_op_map_nonnull(null) =
sys_op_map_nonnull(null);

D
-
X

v9.2.0.1.0> select * from dual where sys_op_map_nonnull('a') =
sys_op_map_nonnull('b');

no rows selected

v9.2.0.1.0> select * from dual where sys_op_map_nonnull('a') =
sys_op_map_nonnull('a');

D
-
X

v9.2.0.1.0> var x varchar2(10)

-- This works because of the context switch to SQL
v9.2.0.1.0> exec select case when
sys_op_map_nonnull(null)=sys_op_map_nonnull(null) then 'Y' else 'N' end into :x
from dual;

PL/SQL procedure successfully completed.

X
--------------------------------
Y

-- This fails because PL/SQL doesn't support it - yet?!
v9.2.0.1.0> exec :x := sys_op_map_nonnull('a');
BEGIN :x := sys_op_map_nonnull('a'); END;

            *
ERROR at line 1:
ORA-06550: line 1, column 13:
PLS-00201: identifier 'SYS_OP_MAP_NONNULL' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

The original post was asking for the best way to perform comparisons in PL/SQL
(a trigger), so this function cannot be used there. But it can be used in SQL
when needed.

Do you have any further thoughts on it?

Thanks, Kurt

          Followup:

- it's undocumented, and therefore poses a risk of going away or changing
functionality

that is enough said that should make people just "stop reading"

go there and you might be really mad in the next release.

the only CORRECT way is:

   where (a = b or (a is null and b is null))

period.  that is my thoughts on it.  I would not consider using
sys_op_map_nonnull, ignore that man behind the curtain.

the day it appears in the sql reference guide -- go for it (in all releases).

For exmaple, i never recommended alter session set current_schema UNTIL it was
documented in 8i.  then I said "use it all over the place"

      Rating: 3
YUK to
      'SYS_OP_MAP_NONNULL'   December 03, 2003
Reviewer:
      Gary  from Sydney, Aus
You can use DUMP to the same effect, and at least that returns a VARCHAR2
instead of a RAW and it is documented as well.
Another 'benefit' of using DUMP to compare values is that if the datatype is
different, the match fails, so you are compelled to do explicit datatype
conversion.

Still doesn't work in PL/SQL

      Rating: 3
DUMP -my
      mistake  December 03, 2003
Reviewer:  Gary
       from Sydney, Aus
Realised that DUMP does return a NULL. I've just got my own function replaceing
itthat does an NVL(DUMP(...),'NULL').

          Followup:

that aside, you don't need dump to do that compare:

ops$tkyte@ORA9IR2> select a, b, decode( a, b, 'Equal', 'Not Equal' )
  2    from t;

         A          B DECODE(A,
---------- ---------- ---------
         1          1 Equal
         1          2 Not Equal
         1            Not Equal
                    2 Not Equal
                      Equal

decode itself will do that.  not in PLSQL tho...

In plsql, just write an "is_equal" function that does the right thing

      Rating: 4
is there a big
      difference between this and nvl?  December 04, 2003

Reviewer:  pete  from uk
what is the difference between using:

where (a = b or (a is null and b is null))

and:

where nvl(a, '!') = nvl(b, '!')

 - providing that '!' is something which will never ever appear in your columns?

thanks,
pete

          Followup:

providing you are 10000% sure that '!' will *never* appear (which almost always
turns out to be a false assumption at some point in time)

it is OK.  I have no problems with

where (a = b or (a is null and b is null))

though (it really isn't that much more typing)

one advantage that nvl() may have is you can index it and search using the
index.

      Rating: 5
Alvaro -
      Another way  December 04, 2003
Reviewer:
      Alton  from Florida
I do this in my audit triggers:
IF new.colA || '!' <> old.colA || '!' THEN
  do_audit...
END IF;

Here's an actual trigger...
BEGIN
  <<audit>>
  DECLARE
    v_event_id           NUMBER;
    v_table              VARCHAR2(30) := 'AUTHORIZATION_CODES';
    v_operation          VARCHAR2(15);
    v_recid              VARCHAR2(60);
    v_module             VARCHAR2(64);
    --
    v_first              BOOLEAN := TRUE;
    --
  PROCEDURE audit_detail(
    pi_column            VARCHAR2,
    pi_before            VARCHAR2,
    pi_after             VARCHAR2
  ) IS
  BEGIN
    IF pi_before || '!' <> pi_after || '!' THEN
      IF v_first THEN
        SELECT program
          INTO v_module
          FROM v$session
         WHERE sid = (SELECT sid FROM v$mystat WHERE rownum = 1 );
        --
        -- Insert the log record
        INSERT INTO auditlog (event_id, table_name, operation, record_id,
module_name)
         VALUES (spsseq.NEXTVAL, v_table, v_operation, v_recid, v_module)
         RETURNING event_id INTO v_event_id;
        --
        v_first := FALSE;
      END IF;
      INSERT INTO audit_detail(event_id, column_name, before_data, after_data)
       VALUES (v_event_id, pi_column, pi_before, pi_after);
      --
    END IF;
  END audit_detail;
  --
  BEGIN
    IF INSERTING THEN
      v_operation := 'INSERT';
      v_recid     := :new.CODE;
    ELSIF UPDATING THEN
      v_operation := 'UPDATE';
      v_recid     := :new.CODE;
    ELSE
      v_operation := 'DELETE';
      v_recid     := :old.CODE;
    END IF;
    --
    -- Insert audit details
    --
    audit_detail('CODE', :old.CODE, :new.CODE);
    --
    audit_detail('DESCRIPTION', :old.DESCRIPTION, :new.DESCRIPTION);
    --
    audit_detail('ABBREVIATION', :old.ABBREVIATION, :new.ABBREVIATION);
    --
    audit_detail('DENIED_GROUP', :old.DENIED_GROUP, :new.DENIED_GROUP);
  END audit;
EXCEPTION
  WHEN OTHERS THEN
  DECLARE
    err_number       NUMBER          := SQLCODE;
    err_message      VARCHAR2(2000)  := SUBSTR(SQLERRM, 1, 2000);
  BEGIN
    INSERT INTO activitylog (activity_id, message_type, program, error_number,
description)
    VALUES(-20000, 'E', 'authorization_codes_audit', err_number, err_message);
  END;
END;

          Followup:

I think its worth the tiny bit of typing it takes.

declare
    nullstr1 varchar2(20);
    nullstr2 varchar2(20);
    notnullstr1 varchar2(20) := 'xxxxxxxxxxx';
    notnullstr2 varchar2(20) := 'xxxxxxxxxxx';
    notnullstr3 varchar2(20) := 'xxxxxxxxxxxy';
    notnullnum1 number := 1;
    notnullnum2 number := 1;
    notnullnum3 number := 2;
begin
    for i in 1 .. 500000
    loop
        if ( nullstr1 = nullstr2 or (nullstr1 is null and nullstr2 is null) )
then null; end if;
        if ( notnullstr1 = notnullstr2 or (notnullstr1 is null and notnullstr2
is null) ) then null; end if;
        if ( notnullstr1 = notnullstr3 or (notnullstr1 is null and notnullstr3
is null) ) then null; end if;
        if ( notnullnum1 = notnullnum2 or (notnullnum1 is null and notnullnum2
is null) ) then null; end if;
        if ( notnullnum1 = notnullnum3 or (notnullnum1 is null and notnullnum3
is null) ) then null; end if;
    end loop;
end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.66       0.66          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.67       0.67          0          0          0           1
********************************************************************************
declare
    nullstr1 varchar2(20);
    nullstr2 varchar2(20);
    notnullstr1 varchar2(20) := 'xxxxxxxxxxx';
    notnullstr2 varchar2(20) := 'xxxxxxxxxxx';
    notnullstr3 varchar2(20) := 'xxxxxxxxxxxy';
    notnullnum1 number := 1;
    notnullnum2 number := 1;
    notnullnum3 number := 2;
begin
    for i in 1 .. 500000
    loop
        if ( nullstr1||'!' = nullstr2||'!' ) then null; end if;
        if ( notnullstr1||'!' = notnullstr2||'!' ) then null; end if;
        if ( notnullstr1||'!' = notnullstr3||'!' ) then null; end if;
        if ( notnullnum1||'!' = notnullnum2||'!' ) then null; end if;
        if ( notnullnum1||'!' = notnullnum3||'!' ) then null; end if;
    end loop;
end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.02          0          0          0           0
Execute      1      2.81       2.75          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      2.82       2.77          0          0          0           1
********************************************************************************
declare
    nullstr1 varchar2(20);
    nullstr2 varchar2(20);
    notnullstr1 varchar2(20) := 'xxxxxxxxxxx';
    notnullstr2 varchar2(20) := 'xxxxxxxxxxx';
    notnullstr3 varchar2(20) := 'xxxxxxxxxxxy';
    notnullnum1 number := 1;
    notnullnum2 number := 1;
    notnullnum3 number := 2;
begin
    for i in 1 .. 500000
    loop
        if nvl(nullstr1,'!')    =  nvl( nullstr2,'!' )    then null; end if;
        if nvl(notnullstr1,'!') =  nvl( notnullstr2,'!' ) then null; end if;
        if nvl(notnullstr1,'!') =  nvl( notnullstr3,'!' ) then null; end if;
        if nvl(notnullnum1,0) =  nvl( notnullnum2,0 ) then null; end if;
        if nvl(notnullnum1,0) =  nvl( notnullnum3,0 ) then null; end if;
    end loop;
end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.09          0          0          0           0
Execute      1      7.06       6.93          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      7.07       7.03          0          0          0           1

      Rating: 3
Re: Alvaro -
      Another way   December 04, 2003
Reviewer:
      Alton  from Florida
Tom,
I don't understand your response... I'm not using NVL anywhere.

          Followup:

i know

i tested

a) my way
b) your way
c) their way (see above, the NVL() ideas)

just to give an overview of "the ways" and their respective performance.

      Rating: 5
Sorry... Didn't
      see my way.   December 04, 2003
Reviewer:
      A reader
I think I'll be changing some code soon.  Your way is fastest.  Thanks.

December 04, 2003 Chitao -- Thanks for the question regarding "how
      to select and insert a long type column", version 8.1.6

      You Asked
Tom

You answered how to select or insert a long type column in your
faq.

but what I need is select and insert a long type column in one
statement.

create table tb2(.., xlong long);
PROCEDURE copyLong IS
p_query varchar2(256);
l_cursor integer default dbms_sql.open_cursor;
l_n number;
l_long_val varchar2(250);
l_long_len number;
l_buflen number := 250;
l_curpos number := 0;
myvar long default '';
BEGIN
p_query := 'insert into tb2(xlong ,cl1,cl2,cl3) select xlong
,cl1,cl2,cl3 from tb2 where ruleid = 2';
dbms_sql.parse(l_cursor,p_query,dbms_sql.native);
dbms_sql.define_column_long(l_cursor,1);
dbms_sql.define_column_long(l_cursor,5);
l_n := dbms_sql.execute(l_cursor);
dbms_sql.close_cursor(l_cursor);
exception
when others then
if dbms_sql.is_open(l_cursor) then
dbms_sql.close_cursor(l_cursor);
end if;

raise;

end copyLong;

what's wrong with this procedure? any suggestiON?

      and we said...
A long / long raw cannot be used in an "insert into" or "create
table as select" statement (among a host of other limitations).

A Long / long raw that is greater then 32k cannot be inserted
into another table via plsql.  You must use C or VB or Java or
someother language for that. The SQLPLUS copy command works as
well.

In Oracle8i, release 8.1.6 which you have, the correct datatype
to use is a CLOB.  Longs and Long Raws are depricated types.  A
clob / blob has none of these limitations.  All of your
longs/long raws can be converted into clobs/blobs using the
TO_LOB() builtin statement.
Review & Followup

      Rating: 3
Need to move
      Long data into BLOB column  April 03, 2002

Reviewer:  Shefali  from Los Angeles, CA
Hi Tom,

The TO_LOB() function works fine , for moving LONG data to table with CLOB
datatype , but fails when moving LONG data into table with BLOB datatype.

E.g.
table t1 :
Name      Type
-----     -----
C1        CLOB

table t2 :
Name      Type
-----     -----
C1        LONG

table t3 :
Name      Type
-----     -----
C1        BLOB

 insert into t1 select TO_LOB(c1) from t2;  -- Works fine
 insert into t3 select TO_LOB(c1) from t2;  -- Gives error
   ERROR at line 1:
   ORA-00932: inconsistent datatypes

Why is it so, How do I insert LONG Data into table with BLOB datatype?

Thanks,
Shefali

          Followup:

LONGS goto CLOBS
LONG RAWS goto BLOBS

Makes no sense to move a long to a blob, we don't do that.

      Rating: 5
can you give us
      an example...  June 27, 2002
Reviewer:
      A reader
as to how we can use the copy command and insert a long or clob column into
another table...

          Followup:

http://asktom.oracle.com/~tkyte/Misc/MoveLongs.html

      Rating: 3
and how to MOVE
      table with Long datatype  December 05, 2002

Reviewer:  Lut Anna  from Ukraine
how I can
ALTER TABLE MOVE stmt. Table with Long datatype??

          Followup:

by definition - you cannot.

      Rating: 4
using COPY
      command  December 05, 2002
Reviewer:  Lut
      Anny  from Ukraine
using SQL COPY comman I get error
set arraysize 50
set long 100000
set  set copycommit 20000

 COPY FROM  usr/pwd@MYSID CREATE tab2 USING SELECT  * FROM  tab1

I use simpl TO_LOB function

t2 (a CLOB,  b number)
t1 (a LONG,  b number)
insert into t2(a,b) select (to_log(a), b ) - Works GOOD!

      Rating: 5
Move Long to
      Long  July 16, 2003
Reviewer:  Sean
       from NJ, USA
It works.  Thanks so much for the info.    Sean

SQL> create table t1(id number, text long);

Table created.

SQL> insert into t1(id , text)
  2  values(1, 'Hello');

1 row created.

SQL> commit;

Commit complete.

SQL> create table t2(id number, text long);

Table created.

SQL> set arraysize 100
SQL> set long 100000
SQL> set copycommit 1
SQL> copy from scott/tiger@winter insert t2(id, text) using select id, text from
t1;

Array fetch/bind size is 100. (arraysize is 100)
Will commit after every array bind. (copycommit is 1)
Maximum long size is 100000. (long is 100000)
   1 rows selected from scott@winter.
   1 rows inserted into T2.
   1 rows committed into T2 at DEFAULT HOST connection.

SQL>

      Rating: 4
LONG to
      BLOB  September 06, 2003
Reviewer:  Steve
       from New York
Tom, you wrote:

"LONGS goto CLOBS
LONG RAWS goto BLOBS

Makes no sense to move a long to a blob, we don't do that. "

Unfortunately, in our case, our Java develpers are insisting that we convert
LONG to BLOB because when they tested CLOBs, there were issues with foreign
characters.

Most records are less than 32K, so if that makes a big difference in your
answer, we'll deal with the handful of records that are >32K.

We are on 8.1.7.4
Thanks.

Looking forward to seeing you at the NYOUG Day on Sep 23.
Are you going to talk about the 10g MODEL clause?

          Followup:

what issues do they perceive that they believe will be done differently with
LONGS????????

give me something to work with here.

(i'll be talking about 10g in general, not the model clause specifically)

      Rating: 3
LONG to
      BLOB  September 08, 2003
Reviewer:  Steve
       from New York
Tom,
I sent an email to the Java developers about this, and here is their response
(our DB characterset is WE8ISO8859P1):

"In an ideal world (a true UTF8 database), we would be able to use CLOB instead
of BLOB to store large text data.  But, because our database is configured with
a Western European character set (meaning it doesn't know anything about
Japanese, Chinese, etc), whenever we write text to either a VARCHAR2, LONG,
CLOB, or BLOB field from a Java/JDBC program, we need to perform the following
steps when writing to the database:

Convert Java String to a Java UTF8 byte array
Store the byte array in the database

When reading from the database, we do the reverse:
Read byte array from the database
Convert byte array to Java String using UTF8 character encoding

With VARCHAR2 and LONG fields, for writing we use the
PreparedStatement.setAsciiStream(..) method.  For reading we use
ResultSet.getBytes(...).  This technique works in that the bytes we store in the
database are the same as the bytes we read.

For CLOB fields, we can't read and write the same was as we do with VARCHAR2 or
LONG fields.  To write to a CLOB in JDBC, we use:

        CLOB clob = (CLOB)pstmt.getClob(3);
        OutputStream os = clob.getAsciiOutputStream();
        os.write(byteArray);

To read from a CLOB, we use:

        ResultSet rs = pstmt.executeQuery();
        rs.next();
        Clob clob = rs.getClob(1);
        InputStream is = clob.getAsciiStream();

The problem is that the CLOB reading/writing routines attempt to perform
character conversion when reading or writing using the OCI JDBC driver, which
causes corruption of foreign characters.  This corruption does not happen when
reading and writing strictly using the Thin JDBC driver, but I don't think we
should restrict ourselves to using the Thin driver only.

The long term solution is to go to a true UTF8 database, where the database
character set is UTF8, and no funny conversion occurs in the database.  This
would also involve converting all existing data to UTF8, which is not a trivial
task.  Then, we would be able to use CLOBs to store large text.
The short term solution is to use BLOBs instead of CLOBs, since BLOBs do not
perform character conversion."

What do you make of this?

          Followup:

use a blob then -- use a blob.

it'll be trivial.  they just stream into a blob.

that is what they really have here -- a blob. (RAW BINARY DATA).

      Rating: 5
I tried your
      example and get this error  October 02, 2003

Reviewer:  Sanjay Jha  from Toronto
What is the problem here Tom?

SQL> create table foo
  2  ( The_Whole_View   varchar2(65),
  3    TextLength       number,
  4    TheText          Long )
  5  /

Table created.

SQL> copy from ci_user/ci_user@cica_dev insert foo (the_whole_view, textlength,
thetext ) using sele
ct owner||'.'||view_name, text_length, text from all_views;

Array fetch/bind size is 15. (arraysize is 15)
Will commit when done. (copycommit is 0)
Maximum long size is 80. (long is 80)

ERROR:
ORA--3924922: Message -3924922 not found;  product=RDBMS; facility=ORA

          Followup:

you can try PUSHING instead of PULLING to see if that works around.

copy to ...

      Rating: 4
It
      worked...  October 06, 2003
Reviewer:  Sanjay
      Jha  from Toronto
I am not sure whether one has to provide user/password@connect-string
combination twice, even though it is for the same database..., but when I did
that way, it worked..

SQL> COPY FROM ci_user/ci_user@cica_dev TO ci_user/ci_user@cica_dev insert foo
(the_whole_view, text
length,thetext ) using select owner||'.'||view_name, text_length, text from
all_views;

Array fetch/bind size is 15. (arraysize is 15)
Will commit when done. (copycommit is 0)
Maximum long size is 80. (long is 80)
   1198 rows selected from ci_user@cica_dev.
   1198 rows inserted into FOO.
   1198 rows committed into FOO at ci_user@cica_dev.

Question:
How COPY command works..is it different from normal DML?

          Followup:

copy is just a sqlplus "command" that is translated into a "select" and an
"insert"

sqlplus is just doing:

   for x in (select * from t1)
   loop
      insert into t2 values ....
   end loop

logically.

      Rating: 2
Am I
      delirious?  December 04, 2003
Reviewer:
      Oleksandr Alesinskyy  from Bad Soden a.Ts., Germany

I have some vague reminiscence that older versions of Oracle (7.2? 7.3?) have
DBMS_LONG package that allows piecewise processing of LONG data from PL/SQl. Am
I delirious or this package has silently gone?

          Followup:

it is dbms_sql that allows (current tense) for piece wise READ access to longs.

never write.

December 04, 2003 Debasish -- Thanks for the question regarding
      "Dynamic cursor, Static Cursor, Bind variable etc.", version 8.1.7

      You Asked
Hi
Tom,
If you clear me about Cursor and which cursor is suitable to use with some
example and clear me about bind variable in details then I will be oblige.

Debasish Ghosh

      and we said...
If you have my new book "Effective Oracle By Design", i cover these topics in
large detail.

In short, all SQL in Oracle is dynamic sql under the covers.  Therefore, it is a
"language thing" in reality (static vs dynamic).

In PLSQL the best, by far (by leaps and bounds) the best is static sql.  use
dynamic sql only when there is no other way to do it.  static sql is more
performat, easier to code, easier to maintain, sets up the dependency between
the code and the database objects, can "fix" your code as you alter definition
of the table in the database.  In short, static sql = good, dynamic sql = bad,
unless you have to.

In PLSQL if you use static sql -- you don't even need to think about binding --
it is done automagically for you.  It is IMPOSSIBLE to not bind correctly and
fully in PLSQL with static sql (one of the major benefits i believe!)

In other languages, say java or "VB" -- you have to bind yourself.  For that,
you refer to your programmers guide for that language (in java and vb, you'd be
looking for prepared and/or callable statements)

In PLSQL, binding with dynamic sql is easy, in short:

   execute immediate l_statement using variable1, variable2, variable3, ....

refer to the plsql guide for details.
Review &
      Followup

      Rating: 5
SQL and PL/SQL
      boundary  December 02, 2003
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
Tom,
Is it right to say that the moment I code something like
declare...begin...exception...end, I am using PL/SQL and all the code in
packages/stored procedures/functions is PL/SQL, though it might contain SQL
statements?

If this is correct, and all the code is in packages in the database, does this
imply that I should try to minimize the use of dynamic SQL in packages?

Thanks...

          Followup:

if it starts with declare or begin -- it is plsql.

yes, minimize the use of

o execute immediate
o dbms_sql

maximize the use of STATIC SQL

      Rating:
      5
  December 03, 2003
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
Tom,
I want to know more about why is dynamic sql less efficient when called from
within PL/SQL. The context switching between SQL and PL/SQL will still take
place even when I use static SQL.
Thanks...

          Followup:

do you have my book "Effective Oracle by Design"?

I go into this in detail but to summarize:

procedure p1
is
   l_x number;
begin
   execute immediate 'select count(*) from t' into l_x;
end;

procedure p2
is
   l_x number;
   select count(*) into l_x from t;
end;

Ok -- say we execute p1 and p2 100 times.  Answer the following:

a) how many times will select count(*) from t be parsed by p1?  100.  by p2?  1

parsing per execute instead of one parse per session.  big reason number 1.

b) what procedures access T?  I want to modify it.  What code will be affected.
Well P1 will -- but you HAVE NO CLUE that it will.

c) will p1 run successfully each time?  no idea, what of the dynamic sql is bad.
 compile time static sql will execute.  runtime dynamic sql -- who knows?  (hard
to code, harder to debug, really truly hard to maintain)

Go ahead and benchmark it.  run p1 and p2 lots -- see which is "faster"

      Rating:
      5
  December 04, 2003
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
I did run the tests for i in 1..1000 loop. Posting the tkprof output would just
be a waste of space. The dynamic sql was parsed 1000 times, the static, just
once.
a) Why does Oracle parse a dynamic SQL statement every time if nothing has
changed? From tkprof, both the statements look the same to Oracle: select
count(*) from t
b) Is it a hard parse or a soft parse?

I am also trying to understand how your statement fits in:

In short, all SQL in Oracle is dynamic sql under the covers.  Therefore, it is a

"language thing" in reality (static vs dynamic).

Thanks for your patience...

          Followup:

a) because each time it COULD be different, hence it is not cached.

b) soft

my point was -- in Oracle -- ALL SQL is dynamic (we hard and soft parse all sql,
we do not "precompile" access plans in the manner DB2 on the mainframe does --
which is a GOOD thing (that we don't))

It is the language environment that will mandate whether static or dynamic sql
is to be used

a) VB -- all about dynamic sql, no such thing as static
b) java -- pick between SQLJ (static) and JDBC (all about dynamic)
c) OCI -- all about dynamic
d) pro*c -- use static until you are forced to use dynamic
e) plsql -- DEFINITELY use static until you are forced at gunpoint to do dynamic

and so on.

      Rating:
      5
  December 04, 2003
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
Thanks...it clears the doubts. In addition to everything that you said about
dependence and maintainability, the developers coded such convoluted logic using
dynamic SQL that there were several (more than 20) possible SQLs generated from
one stored procedure. Looking at the SQL from statspack report, it was
impossible to tell where they came from. I regretted the day I taught the
developers to use dynamic SQL.

December 04, 2003 Nag -- Thanks for the question regarding "Data
      Warehousing database & a regular Database", version 8.1.7

      You Asked
Tom

How is a data warehousing database different from a regular database.

I usually find that data warehousing is seen as a separate skill. Why ?

What are the areas (in a nutshell ) wherein a data warehouse database differes
from a regular database.

Thanks

      and we said...
Most datawarehouses are characterized by:

o hundreds of gigabytes to terabyte in size.  dealing with that scale takes
knowledge.

o periods of read mostly activity, with periods of short bursts of FURIOUS load,
reorg, rebuild activity

o use of specialized features that many people have read about but fewer have
used such as bitmap indexes, materialized views, star query schemas,
partitioning, etc.

o use of parallel query -- similar to the point right above.

o constantly shifting set of questions - requiring shifting sets of data, MV's,
indexes, etc (most systems, once production, are relatively static. DW's --
every day is a brand new day)

To name a few.  Dealing with terabytes of data is hard, takes experience.  Lots
of know how required.
Review & Followup

      Rating:
      4
elaborate  September 25, 2002
Reviewer:
      Nag
"Lots of know how required. " ... can you substantiate this statement required.
A person might be very experienced in a regular database , do you think he will
not be able to handle datawarehouse database if he is a novicein
datawarehousing?

"constantly shifting set of questions""DW's --
every day is a brand new day", do you mean data is moved to datawarehouse every
day. It is not entirely true, some datawarehouses are loaded once in a month and
some once in a quarter?

          Followup:

Nag,

have you worked with TERABYTES of data.

It is very different from GIGABYTES of data, by an order of magnitude.  It can
be a world of difference (or a world of hurt, depending on who you ask)

What I meant by "constantly shifting set of questions" is that

o you set up a data warehouse on day 1
o the question is "how many red shoes did we sell in NC that day.
o tomorrow it is how may gidgets did we try to sell vs actually sell in the
North west for the last 5 years"
o after that, they want to know, so, what should we forecast for widgets in the
south based on the last 17 years - taking into consideration that el-nina
(weather pattern) was in place in '94.

and so on.  Once you open the floodgates on a DW -- all of the "analysis" you
did for what questions they would ask is moot.  They will ask whatever they want
to ask, when they want to ask it.  What are you goind to do about that? Ready to
respond, to analyze, to create new MV's, new indexes, do you know where to begin
to look to find out what you need to do to respond?

      Rating: 4
Could you also
      talk about DW backup strategy?  September 26, 2002

Reviewer:  Ramakrishna  from Bangalore, India

Hi Tom,
  Sometimes, it is not feasible to run a data warehouse in ARCHIVELOG mode due
to the enormous amount of redo generated for various reasons (unable to use
direct path loading, stored procs computing lots of derived values, etc).  Since
the data is availabe in the staging area anyway, is it OK to run in NOARCHIVELOG
mode with nightly cold backups (50GB currently)?
  What is the recommended way of backing up a data warehouse?

thanks
Ramakrishna

          Followup:

As long as you can afford to lose the entire database, rebuild the whole thing
from scratch -- go for it.

If you cannot, then think 50 times about this.

If you are unable to use direct path loading, I would look into why that is so
and perhaps change my methods to permit it.  In 9i with EXTERNAL tables, PLSQL
pipelined functions, Multi-table direct path inserts -- there will be NO logical
reason you cannot.

In order to answer "what is the recommended way...", you would have to look at
your requirements for

o MTTR
o ability to rebuild the entire thing from scratch
o cost of not have the data available

and then we could answer

      Rating:
      4
  September 26, 2002
Reviewer:
      Nathan  from United Kingdom
Thanks Tom,
 Can U please explain some issues with the terabyte database , compared to
Gigabytes. ?

  I believe with regards to backup , rebuilding the database would be better
than recovering from a archive. periodic Exports I guess could be a good backup
strategy.

Thanks Tom,

          Followup:

See -- you got it backwards 100%

That is why people want experience.

Suppose you have a terabyte of data, 1 block goes bad (disk failure).  You would
really want to rebuild the database???  Not me, I would prefer to be using 9i
with RMAN and block level recovery that lets me patch a block in place without
taking the datafile offline, without taking the tablespace offline.

Export?  On a terabyte database?  Your great grandchildren would be running the
system by the time your export you kick off today finished.  And trying to
Import that export?  Their great great great great grandchildren might be able
to monitor the end of the import.  Maybe you mean to "transport the
tablespaces", that would work -- but it would be easier to just have backups.

Also, you would have large portions of the DW in read only mode -- meaning you
need not backup the entire database -- much of a DW can (should) be read only
and you add to it over time (partitioning helps here).  Design (physical design)
plays a huge role in a DW.

      Rating:
      5
  September 26, 2002
Reviewer:
      A reader
Datawarehousing is a different world. There is no way someone with OLTP
experience will feel at home with a DW system from day one.

In addition to what Tom already listed, an area where you need experience is
ETL(extraction, transformation & loading). The data could be coming from
literally hundreds of sources running on a variety of platforms. There could be
issues with different conventions, formating, filling up absent data etc. (That
is why datawarehouses generally use surrogate keys). You might be having one or
more ODS (operational data stores) that holds the data to be loaded to the
warehouse. You might be doing lot of "data massaging", summation/pre-processing,
you might have to validate the data/data samples. Data loads are done using
Direct/Parallel options with data pre-sorted wherever possible. After the data
is loaded to the warehouse, comes the performance part. Tuning and re-writing of
multipage SQL queries, star transformation, bitmap indexing, database resource
management - it is a whole lot which is not generally used in any other systems.

Tim Gorman's book, Oracle 8i Essential Datawarehousing is a very good starting
point for beginers.

Cheers,

AR

      Rating: 5
Great summary
      of issues  September 26, 2002
Reviewer:
      Robert  from Memphis, USA

      Rating: 3
What is DW
      anyway?  September 26, 2002
Reviewer:  A
      Reader  from USA
Do data warehouses have to be in TERABYTES?  What is the definition of DW in
sizes? If I have some OLTP experience and want to learn some DW work (Jr.
level), where should I start with, or how can we get into the DW business? We
will never have a database of this size.  I am also scared by the comment:
"There is no way someone with OLTP experience will feel at home with a DW system
from day one."
What about the other way around?  Those who have DW experiences, can they feel
at home when working on OLTP?  I mean is DW more difficult than OLTP?
Thanks

          Followup:

No, i started with:

...
Most datawarehouses are characterized by:

o hundreds of gigabytes to terabyte in size.  dealing with that scale takes
knowledge.
....

good book referenced above....

the converse is true, DW != OTLP.  they are each equally difficult with their
own challenges.  I know of OLTP envs that are bigger then some DW's

      Rating: 4
It's all about
      experience....  September 26, 2002
Reviewer:
      Robert C  from PA
>>? If I have some OLTP experience and want to learn some >>DW work (Jr.level),
where should I start with, or how can >>we get into the DW business?

Just my 2 cents re the above...
Going into DW with "some OLTP experience " ?
I'd say that's almost impossible...you'd really have to impress the other guy
with other qualities/soft skills like business knowledge.
(and re hard-skills, there're monster truck loads of technologies, products,
tools etc)
With LOTS OLTP experience ? maybe...but that's still a hard sell.
As Tom has mentioned about experience...it (perceived or true) is a BIG thing in
the DW world, especially when they are looking to bring in someone from OUTSIDE.
Best place to start: Your current place...try to make a lateral move and get the
foot in the door.

      Rating: 4
star
      transformation slow  October 21, 2002
Reviewer:
      Sreejith  from USA
  The information was very useful. I have a performance issue with star
transformation. We are using star schema for a data mart. There is a single fact
table and 8 dimensions. There are 8 million records in the fact and 2 of the
biggest dimensions have 10,000 records each. All others are small (100ds). When
I checked the execution plan it shows the usage of bimap indexes to get rowids
from the fact table. To retrieve 10 records , it takes around 60 sec. The same
query by creating a btree index retrives the uses less than 1 sec. I tried all
the ways -  analyzed tables, indexes, histograms  etc. Appreciate  your
suggesions to make the star transformation query faster

Thanks in advance,
Sreejith

          Followup:

Umm, I'm always confused when I get things like:

I tried "A"
It went to slow
When I use "B" it works greeeaattt, really fast

Me, I would stop at B -- works great, less filling.  My job is done, move onto
next problem please.

Maybe -- just maybe -- in this case, a star query is not what you want, an index
plan that uses b*trees is?

If star queries were the correct answer for every DW query -- we would only
supply them and nothing else.

      Rating: 2
Star
      transformation  October 21, 2002
Reviewer:
      sreejith
Issue is that if  i alter the query, need to create another set of indexes.
Queries are not fixed as it is generated from Cognos. if star transformation
gives a better performance  that would solve everything

          Followup:

Given the level of information -- it is impossible for me to deliver the "fast =
true" setting you are looking for.

      Rating: 5
Designing a
      Dataware house  October 23, 2002
Reviewer:
      Nag
Tom
1.
Are the basics of desinging a datawarehouse different from the ones followed in
designing a regular database.

I know for sure that global indexes are not favored for DSS systems, and local
indexes are favored for the purpose of ease of maintenance.

Are there any similar do and dont do's when it comes to datawarehouse and a
regular database.

2.When we dump data into a datawarehouse, we usually disable the constraints and
put the data it( off course we clean the data as much as posible before doing
this).

But what if the data loaded has some bad data which violates integrity
constraints. Once we enable the constraints again woudnt the table be in a
unusable state as the constraints will find that some data is a misfit into the
table.  How is this situation handled.

          Followup:

Yes, the basics are different.

Global indexes may well come into vogue with 9i and "maintain global indexes".
And in a DW/DSS/whatever -- global indexes may well come into play in 8i and
before -- all depends on your needs.

As for the constraints -- it depends.  If you enable the constraint without
validating the data - you could have primary keys that have dups, foreign keys
that have no parents, check constraints that aren't satisfied.  We "allow" you
to do that.  If you validate the data, you can use exceptions into tablename to
capture the "bad" rows.

      Rating:
      5
Syntax  October 24, 2002
Reviewer:
      A reader
"exceptions into tablename to
capture the "bad" rows"

Can you give us the syntax for doing the above, is this a constraint related
setting , or do you do that while creating the table itself.

          Followup:

see

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a85397/statem2a.htm#2054899
and ctl-f (search) for exception.

      Rating: 4
star query
      schemas  October 24, 2002
Reviewer:  Reddy
       from India, Bangalore
Hi tom

can you please explain about star query schemas more elaborately.

Thanks

Reddy

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96520/logical.htm#97642
does a pretty good job of it.

      Rating: 5
Could you
      explain what's a fully normalized enterprise data
      warehouse  January 03, 2003
Reviewer:  Sean
       from GA
I have just joined a data warehouse design team. To my surprise, they claim to
design a fully normalized enterprise data warehouse. The data warehouse will etl
data from  OLTP databases. And there will be some datamarts, which are
denormalized, etl data from the enterprise dataware house. This is first time I
have ever heard "FULLY NORMALIZED DATA WAREHOUSE". It seams to No different with
an OLTP database, because most of the OLTP are normalized databases?

          Followup:

Surprisingly -- most OLTP databases are denormalized to a degree as well.

You know what -- I've never paid much attention to whether I'm in 3rd normal
form, 2cnd normal for or 99th (ask me to define 3rd normal form, I'd have to go
look it up)

The goal, the real goal is to have the data organized in such a way so as

o people can as easily as possible get their information
o the response times are as good as you can get
o the integrity of the data is not compromised

Everything else -- well so what?  Who cares if a data warehouse is normalized, I
mean -- it is not transactional after all.  Sounds like this data warehouse is
really the aggregated OLTP schema in disguise -- and performance would not be
sufficient to satisfy end users so they end up making "data marts" (always found
that a quaint term) which are really the data warehouses.  They built a staging
area, the data marts could probably just be in the data warehouse as
materialized views -- providing for better security, easier managing, greater
access, less redundancy, less work.

      Rating: 5
real
      warehouses  January 04, 2003
Reviewer:  Stu
      Charlton  from Toronto ON Canada
Bill Inmon style data warehouses are usually highly granular and have maximum
flexbility to meet different department's business needs...  that usually means
something more complex than star schema -- in effect a "normalized" database.

Most Teradata warehouses are designed in this way (their own industry data
models aren't star schemas), and I believe some Oracle DW's are desgined this
way (for example, the Richard Winter paper:)

http://www.oracle.com/ip/collateral/vldw_winter.pdf
If your team is going normalized for the "purity", it's probably wrong-headed.
If it's for the flexibility to handle different informational needs (finance,
marketing, sales, engineering, etc.) then it might be appropriate.... assuming
that's your business need.  But it IS a tradeoff -- flexibility for a more
resource-intensive approach.  You'll have your work cut out for you.

Dart marts & star schemas are usually highly specific to departmental needs...
they are great if that's what you need (and usually it is).

          Followup:

...
If your team is going normalized for the "purity", it's probably wrong-headed.
.....

you could have left probably out of there and still be 100% dead on.

      Rating: 5
criteria for DW
      implementation  January 04, 2003
Reviewer:
      Will  from TX
Where I work, we have ten production Oracle databases ranging in size from 300MB
to 17GB totaling about 50GB.  Puny by most standards, I would think.  We do not
have any other significant stores of data in non-Oracle format.

Yet we have on the books for this FY, a data warehouse project.  I'm not sure
what the budgeted amount for this project is but I believe it's in the high
$10K's to low $100K's.

The one-liner description is:

"Provide a centralized up-to-date data store to allow analysis and reporting
from multiple databases"

and the justification statement is:

"The lack of an ad-hoc reporting tool and easy-to-use data repository is the
number one issue end users complain about"

Basically, the idea is to bring in some consultants to build a data warehouse
for us.

The DW concept has been thrown around in our org for a few years now.  I have
chimed in on a few occasions (apparently to no avail) with the following
arguments:

o  Many of our users don't seem that hungry for information and as such do not
really know what they want.  Sometimes we "decide" what they want, give it to
them and let them elaborate.

o  Some consultant that comes in has no clue what we want and need and must ask
the users who also do not know.  We've educated too many consultants already
anyway!

o  Most all of our data is in Oracle which means it is virtually centralized
already.

o  We already have a DW-like database that replicates certain data away from the
OLTP's, we can continue to enhance this.

o  Oracle, especially later versions, has tons on DW functionality that we are
already familiar with, in-house.

o  We're too small of a shop to be considering a full-fledged DW.

...

I agree with your statement that a DW is not something you buy but rather
something you do.

I am curious if I am making valid points here and am interested in your input
about the decision process regarding the development of a data warehouse.

Thanks, as always.

          Followup:

with a budge of 10k to low 100k's you probably won't get very far.  If you go
the consulting route, you'll burn that doing requirements collection!

If the end users don't have requirements -- tell them to stop complaining or
develop some requirements.  I agree with the consultant route you talk of.
Better to bring in warm bodies (temps) to admin the existing systems whilst you
(who will be there AFTERWARDS) do the knowledge gathering.  You will after all
be responsible for it after the fact.  If you don't have DW experience, maybe
you bring in one "expert" you trust to help you with coming up with the plan --
but do the work yourself.

If you already have a DW like database -- it sounds like the place to start and
grow from....

      Rating: 4
Business
      reports  January 05, 2003
Reviewer:  Raj
       from India
Hi Tom,

As I understand, the main objective of a DWH is for reporting. You can get
sales, invoice, inventory etc etc information from a DWH.

Can you suggests some books which can give us detailed idea about which kind of
reports/data we can extract from a DWH and how that data will be useful for the
business.

Thanks.

      Rating:
      3
  January 07, 2003
Reviewer:  Sean
       from GA
I might certainly have missed something here. How could a reporting  application
perform well on a normalized database, which someone cliams as the data
warehouse? Most reporting apps, are basically ad hoc queries, have a lot amd lot
aggragated summing, sorting grouping and joins are complicate and involving many
tables. Cerntainly this kind of application would not perform on the "data
warehouse".

      Rating: 5
what and how to
      define dimension tables, fact tables are
      normalized?  November 20, 2003
Reviewer:
      A reader
Hi

How do we define dimensions in a datawarehouse?
For example we have data for product category which would have items such as
drinks, food, cloth and sub product category would have itmes such like cold
drinks, hot drinks, sea food, fast food, t-shirts, shirts, pants etc

We would like to do drill-down and rollup analysis on a sales fact table, how
would we define the dimensions?

I also read that the fact table is normalized but I always understood that fact
tables are denormalized, may you give a brief explanation?

Cheers

          Followup:

have you read the data warehousing guide ?  it is on OTN.  it describes such
topics.  for the nitty gritty details, a book on data warehousing concepts would
be called for.

      Rating: 5
difference
      between Oracle Dimensions and Plain table
      dimensions  November 22, 2003
Reviewer:
      A reader
Hi

What's the differnce of dimensions created by

CREATE DIMENSION

and dimensions created by us, such as simple

CREATE TABLE?

          Followup:

A dimension is a schema object that defines hierarchical relationships between
columns or column sets. A hierarchical relationship is a functional dependency
from one level of a hierarchy to the next one. A dimension is a container of
logical relationships and does not require any space in the database. A typical
dimension is city, state (or province), region, and country.

In short, the create dimension adds META DATA to the database that can be used
for query optimization.

Not to be confused with dimension tables you create for star schemas.

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96520/dimensio.htm#11221

      Rating: 4
Empty
      DW  November 24, 2003
Reviewer:  Dan Clamage
       from Pittsburgh, PA (home of the world-renowned Pittsburgh Steelers)

The dumbest DW-related remark I ever heard was from a project manager who wanted
to build an "empty" Data Warehouse as a "proof of concept". I said you mean
don't actually load any data into it? What kind of proof of concept is that?
That's not what he meant. I asked him what data did he want to model? He said no
data modeling, just a DW engine. I said like OWB? He said no just a DW that has
no specific data stores defined. That's when I realized he had no clue what he
was talking about, and I pretty much said so. Finally, he said "Let's agree to
disagree". I said NO you're wrong! The idea of building a non-descript "empty"
Data Warehouse where you have no clue as to what you're putting in it, is
nonsensical; it can't be built because there's nothing to build. This from the
same idiot who wrote "Assumptions identify things that we are considering and
not taking into consideration." A AND NOT A.
If you don't know what you're doing, it'll manifest itself eventually.

      Rating: 4
Dimension is
      exactly a star schema dimension  December 03, 2003

Reviewer:  Stewart Bryson  from Atlanta, GA

The use of the "create dimension..." SQL statment is to specify a dimension
table as such for Oracle. The reason this is done is so that the Summary Advisor
is knowledgeable of the relationship, as is the query rewrite functionality.

To say that a "dimension" should not be confused with a dimension table in a
star schema is false. In order for many of Oracle's advanced warehousing
features to be used, it has to be aware of which tables are dimension tables.
This is the functionality that does this.

If you carefully read the very page that you included a link for, this is made
quite clear.

          Followup:

how is it false exactly?

I use create dimension to specify rollup hierarchies for MV rewrite.

A dimension table in a star schema is a dimension table.

A 'dimension' created by "create dimension" is NOT a table, it is meta data that
describes relationships.

If you believe they are the same, you are mistaken.  They are not even remotely
"the same"

that would be like saying a primary key is a table. a table is a primary key.
(loosely speaking)

      Rating: 5
dimension
      description  December 04, 2003
Reviewer:
      Stewart Bryson  from Atlanta, GA
I never said they were "the same". But they are related.

I don't think your primary key/table example holds water, nor is it exactly
representative of the point you and I are debating.

You could say that a many-to-one relationship existed between two tables. To
ensure that relationship is maintained, you could put a foreign key on the
"many" table pointing to the primary key on the "one" table. It is true to say
that the foreign key is not the same as the relationship, I agree with you
there. However, I think it is misleading to say that the foreign key should not
be confused with the relationship... it should be considered representative of
the relationship.

I thought the statement "Not to be confused with dimension tables you create for
star schemas" was confusing because, just as a foreign key represents a certain
relationship to the RDBMS, so does the dimension facility. When you have a
dimension table that you are planning on rolling-up, you should designate it as
such with the logical functionality of "create dimension..." so query rewrite
will know about it.

I do conceed your point... and I shouldn't have said your statement was false.
But I do believe it was misleading.

Thanks for the dialogue.

          Followup:

we'll have to agree to disagree i suppose.

the question was:

What's the differnce of dimensions created by CREATE DIMENSION
and dimensions created by us, such as simple CREATE TABLE?

I would answer the same way.  A dimension created via "create dimension" is not
a table.  A table created via "create table" is not a "dimension that describes
a hierarchy".

I would say it the same all over again.

from the doc:

"In Oracle9i, the dimensional information itself is stored in a dimension table.
In addition, the database object dimension helps to organize and group
dimensional information into hierarchies."

the dimensional information is stored in a dimension table.

the database object "dimension" helps to organized and group dimensional
information in hierarchies.

the "create dimension" object should NOT be confused with a dimension database
table.  they are two distinctly different beasts that may well be used at about
the same time -- but they are two totally different things entirely.

"create table" --> something to store data in
"create dimension" --> method for you to put more metadata into the dictionary.

      Rating:
      2
Riddle  December 31, 2003
Reviewer:
      noname  from Nowhere, usa
When is a door not a door?
When it's ajar.

>primary key is a table. a table is a primary key
In the narrowest technical sense, this could be true when all columns in the
table are also in the primary key. But that's not what you meant of course.

Metadata is data about the data, not the data itself. And a table is not the
data, but the organization of the data. And the representation of an object is
not the same thing as the real object being modeled.

This was not intended to be a philosophical discussion.

December 04, 2003 Aron -- Thanks for the question regarding "How
      can one insert a carriage return ", version

      You Asked
How can one insert a carriage return or
line-feed into a text field so that one can "force" a blank line

between paragraphs in a form in a way that it
will also print as multiple paragraphs?

      and we said...
Carriage returns = 13
Line Feeds = 10
Tabs = 9

(and other ascii codes..)  Once you know the code, getting them
from the database is easy.  There is a function chr() that will
take the ascii code and return the character.  So, if you:

  myString := 'Some Text' || chr(10) || 'Some more Text....';

that'll build a string that has a newline (line feed) in it.

Review &
      Followup

      Rating:
      5
  November 02, 2002
Reviewer:
      A reader

      Rating: 5
chr() not
      working  December 31, 2002
Reviewer:  Sikandar
      Hayat Awan  from Pindi Bhattian - Pakistan
Hi TOM,
I want to use carriage return or line feed but facing in the following way,

SCOTT > select 'A' || chr(10) || 'B' from dual;

'A'
---
A

SCOTT > ed
Wrote file afiedt.buf

  1* select 'A' || chr(13) || 'B' from dual
SCOTT > /

'A'
---
A
B
When I copied it on your side it is ok but in my sql plus window it is A{balck
box}B.

SQL Plus 8.0.6 and Oracle 8.1.7.

          Followup:

and you forgot one thing "windows"....

On Unix, LINEFEED (chr(10)) is the "end of line" marker.  Bill long ago decided
that on DOS, CARRIAGE RETURN/LINEFEED (chr(13)||chr(10)) should be the end of
line marker.

      Rating: 5
hard
      returns  December 31, 2002
Reviewer:  mo

Tom:

Are "Carriage returns" = "Hard Returns" while
 "Line Feeds" = "Wrap End of Line every number of characters".

          Followup:

don't know -- doesn't matter.  "wrap end of line every number of characters"
sounds more like LRECL from the mainframe -- the logical record length.  They
don't have end of lines typically...

\r\n = dos end of line.
\n   = unix end of line

\r = chr(13)
\n = chr(10)

use what you need based on the platform you want.

      Rating: 5
chr(13) ||
      chr(10) has not solved it  December 31, 2002

Reviewer:  Sikandar Hayat Awan  from Pindi Bhattian -
      Pakistan
Ya you are right that I am using MS Windows. I wish to swith this plate form but
I think Unix is very difficult?

Now what I am doing wrong,
SCOTT > select 'A' || chr(13) || chr(10) || 'B' from dual;
'A'|
----
A{black box)

SCOTT >

          Followup:

stop using the 'gui'

Start -> run -> sqlplus.exe

do it in character mode.

ops$tkyte@ORA920> select 'A' || chr(13) || chr(10) || 'B' from dual;

'A'|
----
A
B

ops$tkyte@ORA920>

Unix difficult?  Hah, windows - now I find that hard cause nothing but nothing
can be done without a mouse, without a gui display.  Ease of use?  I doubt it.
I never get questions from unix people like "i set the utl_file_dir to an NFS
mounted disk, now when I try to access it, it doesn't work".  I get tons of
questions from windows people like "i set the utl_file_dir to a windows share,
now when I try to access it from the explorer -- it works but the database
doesn't see it, why not?"

The list goes on.  Unix is not "hard".  Windows to me is painfully hard.  Give
me grep, sed, awk and a good shell and I'm set to do whatever I need.  Give me
windows and I need to buy the MKS toolkit just to make it bearable....

      Rating: 5
Even Character
      mode has not solved it  January 01, 2003
Reviewer:
      Sikandar Hayat Awan  from Pindi Bhattian - Pakistan

Result is same even in character mode,

Output from character mode,
c:\sqlplus.exe

SCOTT > select 'A' || chr(10) || 'B' from dual;

'A'
---
A

SCOTT >

          Followup:

sorry -- i don't have windows anywhere -- I've never seen such behavior
personally (when I did have windows, this worked fine).

      Rating: 5
Third party SQL
      Editor is working fine  January 01, 2003
Reviewer:
      Sikandar Hayat Awan  from Pindi Bhattian - Pakistan

Just for your info............

Dear TOM,
With a third parth SQL Editor it is working fine on the same machine and OS.

select 'alter tablespace '||tablespace_name||' begin backup;' || chr(10) ||
'host copy '||file_name||' &destination\&dbsid\ >> &temp\hotbackup.log' ||
chr(10) ||
'alter tablespace '||tablespace_name||' end backup;' || chr(10)
from dba_data_files;

OUTPUT

alter tablespace XNID begin backup;
host copy D:\ORACLE\PRODDATA\XNID01.DBF X:\Sikandar\Backup_hot\PROD\ >>
D:\Sikandar\Backup_hot\Temp\hotbackup.log
alter tablespace XNID end backup;

But my requirement is to do with sqlplus. I am trying to follow Metalink Note :
152111.1.

          Followup:

well, first of all -- suggest you cease and desist your approach to backups
right now.  That is a certain recipe for DISASTER. (copy won't work really
either on open files, ocopy would but it still is a disaster waiting to happen)

You do not have the necessary control to verify that the copy actually
succeeded.  You'll blindly believe "my backups are OK" -- when in fact they are
not.

What if the alter fails?  How do you detect that?

Strongly urge you to use RMAN instead, for your own protection.  windows is not
powerful enough to script in without tons of help -- RMAN is that help.

But if you persist in this bad idea... do this:

set feedback off
set serveroutput on size 1000000
set linesize 512
set trimspool on
spool badidea.sql
begin
   for x IN ( select tablespace_name, file_name from dba_data_files )
   loop
       dbms_output.put_line( 'alter tablespace ' || x.tablespace ||
                             ' begin backup;' );
       dbms_output.put_line( 'host ocopy ' || x.file_name ||
                             ' &dest\&dbsid ' );
       dbms_output.put_line( 'alter tablespace ' || x.tablespace ||
                             ' end backup;' );
   end loop;
end;
/
spool off
@badidea

      Rating: 5
Of course I
      want to use RMAN  January 01, 2003
Reviewer:
      Sikandar Hayat Awan  from Pindi Bhattian - Pakistan

Dear TOM,
You are right that RMAN is more powerful and reliable but I am unable to use
RMAN due to following reasons,

1- Unable to find step by step implementation Procedure
2- I have to maintain another database (RMAN Repository)
3- As I can't keep on the same machine so need another machine.
4- Recovery steps
5- I need some practice of it

All the steps will be ok if I will get a proper document of it. I have read from
Oracle Manuals but unable to do all from there. May be lack of time or....

I am anxiously waiting for your response as this will be a great achievement to
implement RMAN.

Thanks for saying it BadIdea as now I will try my best to implement RMAN to
remove this bad idea.

Really I appreciate your ideas.

          Followup:

1) did you try the documentation?
2) the documentation tells you that a repository offers
   more features -- but is not a requirement
3) see #1 again please.  By the way -- what is the POINT of backing up onto the
   same machine?  the whole point of backing up is to get it ELSEWHERE.
4) huh?
5) well your current approach isn't going to be very workable in the real world.
   At best it gives you a false sense of security.

Let's put it this way.  In my opinion there is one thing the DBA cannot do
wrong.  They can do almost everything else wrong but if they cannot master
backup and recovery in a bullet proof way -- well, they just are not a DBA at
all.  It is all about the data -- lose the data and who cares how fast the
system USED TO BE.

Backup and recovery is the single most important topic for a DBA.  It is what
they need to know better then anything else in the world -- without exception.
Take the time, live it, learn it -- do it right.

      Rating: 4
You want step
      by step backup and recovery then  January 01, 2003

Reviewer:  Jim Kennedy  from Beaverton, OR USA

Shell out some money and by Oracle Backup and Recovery by Oracle Press.  It has
a lot of information and all the recovery scenerios and how to practice them.
(Best to do that)  Sigh, you will have to read the book.

      Rating: 5
Reading this
      DOC  January 01, 2003
Reviewer:  Sikandar Hayat
      Awan  from Pindi Bhattian - Pakistan
I am reading Metalink Note 120084.1 and 104798.1 seems very informative.

Reading a book is not problem for me but to Purchase a book from USA is really a
hard time for me. The books are very expensive for us so we have to get info
from online docs. This is due to currency coversion. Company is also not
supporting.

TOM you are right that backup and recovery is the main activity of a DBA.
Already I am using badidea and it was working fine for me. I have disk mirroring
and RAID-5. My another requirement to implement RMAN is incremental backups as
the data is growing day by day.

          Followup:

There are reprints in your country of most of these books -- my book "Expert one
on one Oracle" for example -- most of the Oracle press books as well.  The
reprints are priced in your local currency (believe me, I know, I get royalties
based on what the book actually sells for -- it sells for much less in some
countries than others)

You know -- I had both sides of a mirror fail within 5 minutes of eachother.

I've lost 2 out of 5 disks in raid-5 configuration (imagine what that does)....

that is is quote "working fine" means you have been pulled into that totally
FALSE SENSE OF SECURITY.

backing up to the same machine is a recipe for disaster.  When the machine
totally fails then what?

backing up using "copy" invoked via SQLPlus's host command -- what happens when
the copy fails and you have no way to recover from it?

I bet you even keep just ONE COPY of the backups -- meaning that you overwrite
the last backup with the current backup.  What happens when the disk fails in
the middle of a backup? (don't laugh, I've seen it happen).  You don't have your
last good backup, you don't have a current backup, you have just lost your
database.

      Rating: 4
"Expert" in
      other tongues...  January 02, 2003
Reviewer:
      Robert  from PA
>>The reprints are priced in your local currency (...it sells for much less in
some countries than others)

Just curious....are there any translated versions of "Expert" , into other
languages, that is ?

          Followup:

I have a Chinese copy, pretty different looking.  Lots of symbols and then the
word Oracle, more symbols then my name... There are russian ones and portugese
as well.  Probably others I'm not aware of.

      Rating: 4
Local
      Currencies  January 02, 2003
Reviewer:
      Adrian  from Exeter England
"The reprints are priced in your local currency (believe me, I know, I get
royalties
based on what the book actually sells for -- it sells for much less in some
countries than others)"

You must love it when us suckers in the UK buy your book then. US: $59.99 UK:
£47.99 (~$76 at todays exchange rate)

Then again we seem to get fleeced on the exchange rates on most books.

          Followup:

No -- in the UK the list should be (according to Amazon)

US List Price: $59.99
UK Equivalent: £37.40
Our Price: £33.66
You Save: £3.74 (10%)

you should only pay £33.66 at most (about $42) which is the same price as over
this side of the lake.

http://www.amazon.co.uk/exec/obidos/ASIN/1861004826/

      Rating: 4
translated
      editions..  January 02, 2003
Reviewer:
      robert  from PA
>> I have a Chinese copy, pretty different looking.  Lots of symbols and then
the word Oracle, more symbols then my name...

Chinese "symbols" ? You mean characters ?...hmmm I will probably get a copy of
that to keep around just for fun. Will let you know if the translation gets the
ideas way too wrong (seen that in other translated tech books)

      Rating: 4
Eastern Economy
      Edition  January 02, 2003
Reviewer:  Pawan
       from USA
I was recently talking to a friend that bought your book from India - Less than
$15. The paper is not that good but has the same contents and in English.This is
a cut and paste from one of the online book sites
Expert One-On-One Oracle
   Kyte, Thomas
ISBN : 8173663874
Wrox Press ;   Published:  6/ 2001;
Generally dispatched in 1 day.
Special Indian Price.
List Price  : Rs. 675
Our Price  : Rs. 675

If Larry Ellison really wants to make Oracle more popular then im my opinion he
should make multiple copies of this book available in Libraries accross the
world.

      Rating:
      5
Amazon  January 02, 2003
Reviewer:
      Andrew Gilfrin  from Dover, Kent, UK
I find Amazon.co.uk to be very very competetive on Computer books (but not so
for other sutff).

I picked up Tom's book for around £33. It also makes me smile when they have New
Price £33.60 or buy used for £38.00. I did actually by a second hand version of
an Oracle book for £14, it was in perfect condition and the guy even left an
offical copy of JDeveloper in the cover which I dont think should have been
there.

Well worth a try and the transactions for second hand stuff are all delt with by
amazon (you pay amazon and they pay the seller) so it feels much safer than
something like e-bay.

      Rating: 4
For UK readers
      ...  January 02, 2003
Reviewer:  Paul
       from UK
Click on

http://www.bookbrain.co.uk/pricesearch.asp?isbn=1861004826
and take your pick!

      Rating: 4
Stop the
      whining and buy the books  January 02, 2003

Reviewer:  Jim Kennedy  from Portland, OR, USA

I usually pay out of my own pocket for my technical books.  That way I don't
have to track them down at work and if I go to another company I can take them
with me.  I find it difficult to believe that you can't get Tom's book and the
Oracle Backup and Recovery Book locally.  I have friends in India and they can
purchase those books (not as nice paper, but very cheap) locally.  I feel for
you if your company is so short sighted that they won't shell out a little money
to get some manuals to do your job correctly.  They must not think the data is
worth much.

      Rating: 5
I am in
      Pakistan not in India so no reprints  January 02, 2003

Reviewer:  Sikandar Hayat Awan  from Pindi Bhattian -
      Pakistan
Ya you all are right that there are reprints are available but not in Pakistan.
We purchase reprints from India which has been stopped due to some political
issues. Expecting soon to resume.

We have to purchase from amazon.com and the price comparison is as under,

USA $59.99 Pak = Rs 3599.4 + Shippment = Rs > 6000

In Pakistan the average salary is 6000 / month.
OCP 1 Paper Fee is 6500.

I will really suggest to Oracle not Microsoft that please provide your softwares
and docs according to the local currency to promote Oracle in the backward
countries. I can't explain how it is difficult to work with latest technologies.
You can check how many people are from Pakistan or any other backward country on
this site.
------------------------------------------------------------
Anyway coming back to our real issue,
TOM we daily move our backup to a DAT and keep last 6 backups in out archive.
But still as you metioned any thing may happen. You are 100% right. So I am
going to switch to RMAN as early as possible and in this regard I will really
seek for your assistance which is vital.

I have raised a TAR on Metalink for chr(10) and they are working on it.

      Rating: 5
Confessions
      from a roll-your-own backup scripter  January 08, 2003

Reviewer:  Mike  from St. Louis
Tom:

Your advice on so many topics has been rock solid over the years but I guess
I've made excuses for not converting totally to RMAN.  No more excuses, I'm
starting the conversion this week.

One question for you: we have many "older" (8.1.6 and 8.1.7) instances.  Do you
have any cautionary words or advice for implimenting RMAN on these "older"
versions?

Thank you,

-Mike

          Followup:

just to test, test, test -- backup and recovery is the most important thing.
Make sure to actually test that which you've backed up to make sure you can
actually use it under pressure (when you need to)

      Rating: 5
chr(10) issue
      resolved  January 14, 2003
Reviewer:  Sikandar
      Hayat Awan  from Pindi Bhattian - Pakistan
The issues has resolved in the following way,
TAR# 2727029.995

SQL> set wrap on
SQL> select 'A' || chr(10) || 'B' from dual;
'A'
---
A
B
SQL> set wrap off
SQL> /
'A'
---
A
SQL> set wrap on
SQL> /
'A'
---
A
B
SQL>

      Rating:
      5
  February 03, 2003
Reviewer:  Sridhar
      Kolluru  from VS USA
Tom I used your site all the time it is very very very to the power of infintiy
times useful

      Rating: 5
displaying
      carriage return on web page  May 14, 2003

Reviewer:  mary  from dover de
Tom, i'm having trouble capturing carriage returns when they are submitted in an
html form.  The user enters alot of text in a form box, hitting the return key
where they want it and then submit it (goes to database).  However, when I
display their comments from the database, none of the carriage returns show up
(the text entered is just one continuous glob of words on the same line).

If I store html breaks (<br>) in the data where they hit the carriage return, it
works, but I don't want them to have to type <br> every time they want a new
line.  When I save the form, I am using the trim() function.  Could this be
causing the problem?

Sorry if this is off the topic. (The database is Oracle though).

          Followup:

<pre>
their text
with line breaks
will appear as
they entered it
</pre>

without the pre tag -- it'll wrap it as it (the browser) wants.

you could alternatively

htp.p( replace( l_text, chr(10), '<br>' ) );

as well

      Rating: 5
Disregard Last
      Posting  May 14, 2003
Reviewer:  mary
       from dover de
I think I figured it out. I'm creating a function in the database that I will
use when selecting the data for the field in question.  The function will
replace chr(13) with <br /> and chr(10) with <p />.

      Rating: 5
i'll try pre
      tag  May 14, 2003
Reviewer:  mary  from
      dover de
thanks Tom.  I'll try the <pre> tag.

      Rating: 4
No wonder the
      'ask a question' is rarely available  July 08, 2003

Reviewer:  A reader  from USA
RMAN?
Books?
Chinese characters?
Backups?
Windows v. Unix?

      Rating:
      4
reader  August 13, 2003
Reviewer:
      gre
Hi Tom,

I am currently working on extract table from oracle(data warehouse).and load the
flat file to another data warehosue.

What is the general strategy to handle the Chr(10), chr(13)
this kind of special characters. I saw some scripts some times handle them some
times do not care of them.

Thank you!

          Followup:

why would you dump and load?

is this oracle to oracle?

do you have my book "expert one on one oracle"?

      Rating:
      4
reader  August 13, 2003
Reviewer:
      gre
Hi Tom,

1.We will extract some tables from a local Oracle
warehouse and send the gziped flat files by FTP to another
ORACLE date warehouse(not local) to load weekly.

2. Yes, I have your book.
Which Chapert has the related topic ?

Thank you!

      Rating:
      4
  August 13, 2003
Reviewer:  GRE

Hi TOM,

Sorry I am wrong. The remote one  is Teradata .

That means FROM Oracle to Teradata.

Thank you!

          Followup:

you need to ask teradata "what format shall i put the files in so you can ingest
them" then...

I cannot tell you how to format a file for teradata's loader that includes
newlines.

the chapter on sqlldr in my book tells you how the file would be formatted with
Oracle.

      Rating: 5
Want RMAN
      Examples Step by Step? OBE is the answer :)  August 14, 2003

Reviewer:  pasko  from Hamburg,Germany
Hi Tom,

i just wanted to point out that , i find the Oracle By Examples Online Series to
be very useful and easy to follow:

If somebody wants a step by step RMAN back-up Examples, they can go to :

http://otn.oracle.com/obe/paa/index.html
click: Oracle9i Release 9.2.0.2
   and then click : 7. Develop a Highly-Available Database Environment
       and then click : 7. Performing Backups and Recovering your Database using
Oracle9i Recovery Manager

      Rating: 4
Counting
      carriage returns in a column ?  November 19, 2003

Reviewer:  Vishal Sharma  from CT, USA
I have a table like

user_comments(comment_id   NUMBER(9) PRIMARY KEY,
user_name   VARCHAR2(50),
comments    VARCHAR2(2000) ) ;

comments field has carriage returns as it is a multiline field on the form where
users record their comments.
In the same application, different forms, i pull their comments. I want to
resize my comment (POP UP)window
depending on how many lines (Carriage returns) are present in the comment for a
given comment_id.

I know that :

select instr(comments, instr(chr(10) ) from user_comments where comment_id = 1 ;

will give me first ocuurance of carraige return.

What will be good way of getting total no. of ocuurences in this context ???

          Followup:

  1* select ename, length(ename)-length( replace(ename,'A','') ) "A's" from emp
ops$tkyte@ORA920LAP> /

ENAME             A's
---------- ----------
SMITH               0
ALLEN               1
WARD                1
JONES               0
MARTIN              1
BLAKE               1
CLARK               1
SCOTT               0
KING                0
TURNER              0
ADAMS               2
JAMES               1
FORD                0
MILLER              0

14 rows selected.

replace 'A' with chr(10) for newlines.

      Rating: 5
In
      Contunuation  December 04, 2003
Reviewer:
      Vishal Sharma  from CT, USA
Tom,
The query is great. Helps me find the total lines in Comments. It would be great
if I can get the characters in longets line in comments. I mean a result set
like

Assuming  'A' is chr(10) in above shown result set

ENAME             A's     Charactrs_IN_Longets_Line
---------- ----------     --------------------------
SMITH               0           5
ALLEN               1           4
WARD                1           2
JONES               0           5
MARTIN              1           4
BLAKE               1           2
CLARK               1           2
SCOTT               0           5
KING                0           4
TURNER              0           6
ADAMS               2           2
JAMES               1           3
FORD                0           4
MILLER              0           6

Thanks a lot for last answer. Everytime you get some question, you answer it in
most efficient and straight way.

          Followup:

is "longets" really "longest" the "longest" (or largest, or line with the most
characters)?

we'd probably be writing a plsql function for that.

ops$tkyte@ORA920PC> create or replace type str2tblType as table of
varchar2(4000);
  2  /

Type created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create or replace function str2tbl( p_str in varchar2,
p_delim in varchar2 default ',' ) return str2tblType
  2  PIPELINED
  3  as
  4      l_str      long default p_str || p_delim;
  5      l_n        number;
  6  begin
  7      loop
  8          l_n := instr( l_str, p_delim );
  9          exit when (nvl(l_n,0) = 0);
 10          pipe row( ltrim(rtrim(substr(l_str,1,l_n-1))) );
 11          l_str := substr( l_str, l_n+1 );
 12      end loop;
 13      return;
 14  end;
 15  /

Function created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> drop table t;

Table dropped.

ops$tkyte@ORA920PC> create table t
  2  as
  3  select ename, 'Hows this going to ' || chr(10) || ' work?'  || chr(10) || '
i have no idea' cmt
  4   from scott.emp;

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select ename, length(cmt)-length(replace(cmt,'a','')) "A's",
  2         (select max(length(column_value)) from TABLE(str2tbl(cmt)))
max_length
  3    from t;

ENAME             A's MAX_LENGTH
---------- ---------- ----------
SMITH               2         42
ALLEN               2         42
WARD                2         42
JONES               2         42
MARTIN              2         42
BLAKE               2         42
CLARK               2         42
SCOTT               2         42
KING                2         42
TURNER              2         42
ADAMS               2         42
JAMES               2         42
FORD                2         42
MILLER              2         42

14 rows selected.

is one approach

      Rating:
      4
Great  December 04, 2003
Reviewer:
      Vishal Sharma  from CT, USA
I am already using PL/Sql to get no. of lines and the  number of characters in
largest the lines. I was wondering if there could be some way to do both of them
in a SQL query.
Though this answer heled me a lot.

Thanks

      Rating: 5
Hidden
      characters  December 04, 2003
Reviewer:
      Tony  from Canada
Hi Tom,
       Thanks a lot for all your help.
I'm trying to find some hidden characters in one of the fields in my table.
if I run the following sql I get a space at the end of field value(for all rows
in the table)

select 'x' || comment || 'x' from cmt_t ;

xCARTHAGE , MO    64836.  This is a test. x
xabc , xy    64836.  This is a test. x
........
.....
...
Now if I run the follwing query(with one blank in the where clause) I don't get
back any row:

select comment from cmt_t where comment like '% ';

That means the comment field doesn't contain blanks but some hidden characters
that are not visible to me.

Can you please tell me if there is a way to find these hidden characters.

Thanks a lot

          Followup:

select dump( comment ) from cmt_t;

that'll give you a byte by byte dump

or

select ascii( substr(comment,length(comment)-1) ) from cmt_t;

that'll get you the ascii code of the "bad" character.

December 04, 2003 Randy -- Thanks for the question regarding "How
      to do document management using Oracle", version 8.1.7

      You Asked
Hi Tom,

I am looking for document management solution from Oracle. We have an
application which currently have about 2TB of documents all in tiff format. We
are using a document mananegment software for storing and retrieval. It stores
the files in file system using some propritory hashing algorithm and distributes
the files in file system evenly into multiple directory/sub directory. But the
company may belly up soon and we are looking for alternative. As a proponenet of
Oracle I am thinking if its possible to store all documents inside the database
as clob/blob/bfile. I came to know from your forum that Oracle has implemented
file server of size 3.3TB.
Can you explain me the technology behind it ?
What's your recommendation about design if I am using blob ?
Can the performance be any issue ? If so then where can I tune it ?
What's specific products/technology I need other than the RDBMS engine ? I cannt
seem to use Oracle iFS as our application is written in VB.

Also do you know if any other company has implemented a large document
management system using Oracle ?

Can you forward me some white paper or success story so that I can convince the
management ?

Thanks for your help.

      and we said...
we use Oracle Files (ifs) -- part of our collaboration suite.  See

http://www.oracle.com/ip/deploy/cs/
for details.  customer profiles are there as well.
Review &
      Followup

      Rating: 3
Can I use blob
      ?  June 28, 2003
Reviewer:  Randy  from
      Chicago, usa
I thought of using iFS but as you know it only provides interface through JAVA
API. Our application is
COM based. I am thinking to use BLOB to stored images within the table. I guess
iFS also uses BLOb internally to store documents. So is there any performance
benefit to use iFS rather than storing documents in blob. I know iFS provided
cool feature like serching, version contrl check in/check ou etc. I am not
looking for those features now.  What's your recommendation in terms of
performance to store the blob as I am thinking the table may grow upto 3TB ?

          Followup:

ifs works like a file system -- its api is ANYTHING that can talk "file system"
-- eg "windows explorer", "ftp", "webdav", http, https -- those are its
protocols.

You asked for document management -- that is alot more then "store in blob".

What you describe fits "collaboration suite"

      Rating: 3
iFS
      architecture  December 04, 2003
Reviewer:
      Dave  from Lexington, MA
Tom -

Can you confirm my simplified understanding of the architecture? Is it...

Client (e.g., Windows explorer) <--> 9iAS <--> 9i database

I'd like to avoid 9iAS for now if possible, but I am guessing iFS isn't
out-of-box functionality with the database alone, true?

Thanks,
Dave

          Followup:

it is more like

client <---> database

but ias is many times the client and required to administer/use it.

you'll need ias.

December 05, 2003 Gururaj -- Thanks for the question regarding "SQL
      Loader", version 7.X or 8.X

      You Asked
Is it possible to use Direct path option while loading data using
sql loader from NT work station to database on Unix ?

What is the following error about ?
ORA-02352: Direct path connection must be homogeneous

Thanks
Gururaj Kulkarni

      and we said...
No it is not

The byte order on those platforms are different, the nt code would mess up a
unix file totally.

it is saying that you must use the same exact OS to direct path with.

02352, 00000, "Direct path connection must be homogeneous"
// *Cause: The user-side to kernel-side connection is heterogenous.
// *Action: Make the connection be between identical hardware and O/S's.
Review &
      Followup

      Rating:
      4
  June 28, 2001
Reviewer:  Gururaj
      Kulkarni  from GA USA
Thanks Tom. Is there a way to get the benefit ?

      Rating: 2
there is no way
      to do direct path?  December 05, 2003
Reviewer:
      john_jakartia@hotmail.com
our DB is in sun solaris, we are trying load the data from Windows NT. is there
a workaround to use DIRECT path, because without direct path, too much redo is
generated. so, our application is quite slow. Our Oracle version is 8.1.6.

would appreciate your help.

          Followup:

so, umm, why don't you put the data on the server to load if loading is your
primary concern.

I cannot imagine the redo would be making you slower then the windows client and
network.

Have you postively identified "redo generation is the thing that is making us
dog slow"

or -- is it a "hunch" based on "generating lots of it, must be the cause"

that aside, you can direct path over the network.

$ sqlldr 'ops$tkyte/tkyte@ora920' test direct=y

SQL*Loader: Release 9.2.0.4.0 - Production on Fri Dec 5 07:24:31 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Load completed - logical record count 14.

that was from my linux laptop (same sort of hardware as your nt machine) to a
solaris box over the network.  works as long as the sqlldr version matches the
database version.

bear in mind, direct path does NOT mean "no logging".  in noarchivelog mode it
will not generate log, but in archive log mode it will -- you have to disable
logging for the object you are loading into (and remember -- indexes = always
logged)

but again, before going down this path, spend a moment to diagnose the
bottleneck.  be sure you are fixing something that is broken.

December 05, 2003 Munzer -- Thanks for the question regarding
      "Trimming Blanks", version 8.1.7

      You Asked
Tom:

If I want to let user insert a vendor name and then check database if that
vendor exists (no unique constraints) Can I do:

SELECT count(*) into l_count from table where upper(replace(vendor,'
'))=upper(replace(p_vendor,' '));

Is this correct?

Can REPLACE be used here to trim blanks from right, left and in between words.
Would this assure that if user eneted extra space with vendor it would not let
him enter it again without a space?

2.  I am confused of function of TRANSLATE and REPLACE? Is one for strings and
other for  characters?

Thank you,

      and we said...
Well, that won't work in a multi-user environment, if you are going to use that
for business rule processing.

a more efficient way is:

select count(*) from dual where exists ( select null rom table where
upper(replace(vendor,' '))=upper(replace(p_vendor,' ')) );

that'll stop searching when it finds the first hit (and a function based index
would be useful here as well)

replace will remove blanks yes (a simple test would bear this out).  It would
not prevent the end user from doing anything - that is something YOU must do.

2) translate does a translation from one table to another table of characters.
If you translate:

  translate ( x, 'abc', 'xyz' )

that will change a -> x, b -> y, c -> z.  That translates using the input table
(abc) into the output table (xyz).

replace replaces, so

  replace ( x, 'abc', 'xyz' )

would find the string abc and replace it with xyz in the variable X.  It works
one a string, translate works character by character.

Review &
      Followup

      Rating: 3
Followup
      Trimming Blanks  April 25, 2002
Reviewer:  Dave
      Brys  from Belgium
Would

SELECT 'x' into l_exists from table where
upper(replace(vendor,''))=upper(replace(p_vendor,' '));

and then check the value of l_exists

be equally efficient as your response ? Or is this something one must avoid ?

Best regards

          Followup:

That would through:

NO_DATA_FOUND when no row exists that matches the criteria and
TOO_MANY_ROWS when more then one row exists.

select count(*) from dual where EXISTS ( condition to check )

is what you want to do.  returns AT LEAST one row, returns AT MOST one row.

      Rating: 3
Multiuser
      environment  April 25, 2002
Reviewer:
      Jagdish  from FL, USA
Hi Tom,
Can you please explain why the following would not work in multiuser environment
SELECT count(*) into l_count from table where upper(replace(vendor,'
'))=upper(replace(p_vendor,' '));

I thought above one and
select count(*) from dual where exists ( select null rom table where
upper(replace(vendor,' '))=upper(replace(p_vendor,' ')) );

this one are both same.

Thanks,

          Followup:

To be clear, i said:

...
Well, that won't work in a multi-user environment, if you are going to use that
for business rule processing.
.....

The reason is simple.  If two people insert "hello world" as the vendor at the
same time, and both run the above query -- neither will "see" eachothers
insertion.  We do non-blocking reads, we will not serialize.  Hence you CAN and
WILL get duplicates.

You must use the declaritive integrity constraints to do things like
"uniqueness", "foreign keys".  Trying to do it yourself will lead to almost
certain failure OR if done properly -- heavy duty serialization.

      Rating: 4
Triming two or
      more spaces  January 18, 2003
Reviewer:  Edgar
      Chupit  from Latvia
Hello Tom,

But how can we trim more than one space between characters, for example if I
have string like 'aaa  bbb   ddd     eee', I would like to leave only 'aaa bbb
ddd eee'. Now I'm using something like:

create or replace function replacemany(data varchar, src varchar, dest varchar)
return varchar is
  Result varchar(100);
  Result2 varchar(100);
begin
  Result := data;
  loop
     Result := replace( Result, src, dest );
     Result2 := replace( Result, src, dest );
     exit when Result = Result2;
     exit when Result is null;
     exit when Result2 is null;
     Result := Result2;
  end loop;
  return(Result);
end replacemany;

but there have to be a fastest way. Thank you.

          Followup:

replacing over and over is the easiest approach I can think of myself.

You could improve that slightly via:

create or replace function replacemany(data varchar, src varchar, dest varchar)
return varchar
is
  Result long;
  l_curr_len  number := 0;
  l_last_len  number := 0;
begin
    if ( data is null or src is null or dest is null )
    then
        return null;
    end if;
    result := data;
    loop
        result := replace( result, src, dest );
        l_curr_len := length( result );
        exit when (l_curr_len = l_last_len or result is null);
        l_last_len := l_curr_len;
    end loop;
    return result;
end;
/

but they are about the same

      Rating: 5
index usage and
      blanks  December 04, 2003
Reviewer:
      A reader
Hi

We have a table with many not null columns, when users dont have anything to
insert they simply insert a whitespace (Peoplesoft App). The problem is even the
column is indexed when we use this predicate

select * from x where col_a =' '  ===> a whitespace

it takes ages, if we change space to literal it runs fast, both share same
execution plan

My question is how Oracle treat blanks in an index???? Does it scan the whole
index segment ??? Why a query is slow when asking for blanks?

          Followup:

turn what into a literal???

the only thing I see here is a literal.

show us a tkprof of the "good query" and the "bad query"

      Rating: 5
tkprof, Oracle
      9.2 on HP-UX 64bits  December 04, 2003
Reviewer:
      A reader
Hi

here are the queries with their tkprofs

Nice and fast with no whitespaces in :b and :d
======================================
var a varchar2(15)
var b varchar2(20)
var c varchar2(18)
var d varchar2(144)
exec :a:='5802496'
exec :b:='8000197109'
exec :c:='CAT000000000000275'
exec :d:='976353623'

SELECT B.SETID, B.INST_PROD_ID
FROM sysadm.PS_RF_INST_PROD A,
     sysadm.PS_PA_ARBOR_ELEMEN B,
     sysadm.PS_RFTINST_PROD C
WHERE C.RBTACCTID=:a
  AND A.PARENT_INST_PRODID=:b
  AND A.PRODUCT_ID =:c
  AND B.PA_EXTERNAL_ID =:d
  AND B.PA_SUBSCR_NO=0
  AND B.PA_COMP_INST_ID=0
  AND A.INST_PROD_ID=B.INST_PROD_ID
  AND A.INST_PROD_ID=C.INST_PROD_ID
  AND A.SETID=B.SETID
  AND A.SETID=C.SETID

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.01       0.03          3          3          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.01       0.03          3          3          0           0

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  NESTED LOOPS  (cr=3 r=3 w=0 time=31851 us)
      0   NESTED LOOPS  (cr=3 r=3 w=0 time=31840 us)
      0    TABLE ACCESS BY GLOBAL INDEX ROWID OBJ#(48103) PARTITION: ROW
LOCATION ROW LOCATION (cr=3 r=3 w=0 time=31833 us)
      0     INDEX RANGE SCAN OBJ#(52039) (cr=3 r=3 w=0 time=31816 us)(object id
52039)
      0    TABLE ACCESS BY GLOBAL INDEX ROWID OBJ#(47711) PARTITION: ROW
LOCATION ROW LOCATION (cr=0 r=0 w=0 time=0 us)
      0     INDEX UNIQUE SCAN OBJ#(52060) (cr=0 r=0 w=0 time=0 us)(object id
52060)
      0   TABLE ACCESS BY GLOBAL INDEX ROWID OBJ#(48491) PARTITION: ROW LOCATION
ROW LOCATION (cr=0 r=0 w=0 time=0 us)
      0    INDEX UNIQUE SCAN OBJ#(52047) (cr=0 r=0 w=0 time=0 us)(object id
52047)

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                       1        0.00          0.00
  db file sequential read                         3        0.01          0.02
  SQL*Net message from client                     1        1.87          1.87

Damn slow with blanks in :b and :d
======================================
var a varchar2(15)
var b varchar2(20)
var c varchar2(18)
var d varchar2(144)
exec :a:='0370'
exec :b:=' '
exec :c:='CAT000000000004822'
exec :d:=' '

SELECT B.SETID, B.INST_PROD_ID
FROM sysadm.PS_RF_INST_PROD A,
     sysadm.PS_PA_ARBOR_ELEMEN B,
     sysadm.PS_RFTINST_PROD C
WHERE C.RBTACCTID=:a
  AND A.PARENT_INST_PRODID=:b
  AND A.PRODUCT_ID =:c
  AND B.PA_EXTERNAL_ID =:d
  AND B.PA_SUBSCR_NO=0
  AND B.PA_COMP_INST_ID=0
  AND A.INST_PROD_ID=B.INST_PROD_ID
  AND A.INST_PROD_ID=C.INST_PROD_ID
  AND A.SETID=B.SETID
  AND A.SETID=C.SETID

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2     94.21     195.88     154576     157393          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4     94.21     195.88     154576     157393          0           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 123

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  NESTED LOOPS  (cr=157393 r=154576 w=0 time=195882817 us)
    140   NESTED LOOPS  (cr=156970 r=154494 w=0 time=194841985 us)
    857    TABLE ACCESS BY GLOBAL INDEX ROWID OBJ#(48103) PARTITION: ROW
LOCATION ROW LOCATION (cr=154396 r=154393 w=0 time=19356558
6 us)
10333634     INDEX RANGE SCAN OBJ#(52039) (cr=27413 r=27411 w=0 time=35091554
us)(object id 52039)
    140    TABLE ACCESS BY GLOBAL INDEX ROWID OBJ#(47711) PARTITION: ROW
LOCATION ROW LOCATION (cr=2574 r=101 w=0 time=1257424 us)
    857     INDEX UNIQUE SCAN OBJ#(52060) (cr=1717 r=9 w=0 time=168531
us)(object id 52060)
      1   TABLE ACCESS BY GLOBAL INDEX ROWID OBJ#(48491) PARTITION: ROW LOCATION
ROW LOCATION (cr=423 r=82 w=0 time=1034715 us)
    140    INDEX UNIQUE SCAN OBJ#(52047) (cr=283 r=23 w=0 time=175735 us)(object
id 52047)

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                       2        0.00          0.00
  db file sequential read                    154576        0.57        114.59
  latch free                                     25        0.07          0.16
  SQL*Net message from client                     2      196.55        196.55

I am totally amazed by the optimizer and it´s intelligenece :-(

Ah, and the tables were analyzed today with dbms_stats

Salut

          Followup:

well -- think about it. (and by the way, this has ZERO to do with "blanks" -
whitespace isn't part of the equation here at all)

do you know what it means to use bind variables and what a soft parse is??
basically you have massively skewed data..  you are using binds.  the optimizer
will parse and optimize the query ONCE.  It will come up with "generic query
plan" for that (it has to, thats what it does, thats how it works).

In 9i, it'll use bind variable peeking to do so.  So, the first time it parsed
this query, it saw values for b and d and came up with "generic plan for this
query".  that plan is the plan that will be used for that query from then on in.

so, in your case, you will need to supply more info.  eg: is this query run once
a day, once a minute, 50 times a second?  what is the use pattern.  your
solution, the technique we use will vary based on that.

perhaps we could discuss the "smartness/intelligence" of storing ' ' instead of
null in the table as well?

      Rating: 5
not my fault
      storing ' '   December 04, 2003
Reviewer:
      A reader
Hi

This is Peoplesoft standard application. We are not storing ' ' !!!! It is
Peoplesoft!!!

This runs every 30 minutes, and it runs around 100 times every time (processing
orders)

My solution is (which I dont like)

SELECT B.SETID, B.INST_PROD_ID
FROM sysadm.PS_RF_INST_PROD A,
     sysadm.PS_PA_ARBOR_ELEMEN B,
     sysadm.PS_RFTINST_PROD C
WHERE C.RBTACCTID=:a
  AND TRIM(A.PARENT_INST_PRODID)=:b
  AND A.PRODUCT_ID =:c
  AND TRIM(B.PA_EXTERNAL_ID) =:d
  AND B.PA_SUBSCR_NO=0
  AND B.PA_COMP_INST_ID=0
  AND A.INST_PROD_ID=B.INST_PROD_ID
  AND A.INST_PROD_ID=C.INST_PROD_ID
  AND A.SETID=B.SETID
  AND A.SETID=C.SETID

Why you say this has nothing to do with whitespace? When the reality is when we
pass whitespace to the query everything slows down....

          Followup:

suppose they put "@" in there instead of whitespace

then "@" would be the "bad thing", it is not whitespace per se, it is the skewed
nature of the data.

You might consider putting conditional logic in there

  if binds are blank
    then query with literal blanks in there
  else
    fully bound query
  end if

      Rating: 5
Yep - Skewed
      data will cause it...  December 04, 2003
Reviewer:
      J  from Vancouver
Ran into a similar situation as "A Reader", eneded up implementing something
similar to your proposal.  Works fine (just made sure to comment the code to
explain the rationale).  =o)

Ciao!
- J

      Rating: 5
how do you run
      QUERYS with literals inside PL/SQL?  December 05, 2003

Reviewer:  A reader
Hi

if I have this procedure

create or replace procedure prueba(p_name varchar)
  as
  l_into varchar2(30);
  begin
  select ename
    into l_into
    from emp
   where ename = p_name;
end;
/

if I want to use literals for p_name how can we do? I can only think of dynamic
SQL huh?

          Followup:

select ename into l_into from emp where ename = 'KING';

works.

so the concept would be:

   if ( p_input = ' ' ) then
        query 1 with literals
   else
        query 2 with binds
   endif

      Rating:
      3
  December 05, 2003
Reviewer:
      A reader
create or replace procedure prueba(p_name varchar)
  as
  l_into varchar2(30);
  begin
  if p_name = ' '
  then
  select ename
    into l_into
    from emp
   where ename = ' ';
  else
  select ename
    into l_into
    from emp
   where ename = p_name;
  end if;
end;
/

December 05, 2003 Bill -- Thanks for the question regarding
      "questions on 9i new features", version 9.0.1

      You Asked
Hi Tom
I have a couple of questions -i was recently stumped when asked the following
questions regarding 9i specific features - and cannot find them in the docs:
1) What does 'safe literal' mean in context of cursor sharing.  I understand the
concept of using SIMILAR,FORCE,EXACT - and that you do not want CBO to take the
same path for a 10 row table vs a 100,000 row table - but i do not know what
consitutes a "safe literal".

2)In regards to Index Monitoring with 9i and the V$OBJECT_USAGE view, when
exactly does the row get written for the view -is it during a parse of execution
of the underlying query that would use the index ?

thanks!
Bill

      and we said...
1) if

select * from emp where job = 'any constant'

always resulted in a the same plan as:

select * from emp where job = :x;

then 'any constant' is a "safe literal".  It can safely be replaced with any
other literal without affecting the plan and hence, it can safely be bound
without affecting the query plan.

2) lets see:

ops$tkyte@ORA920> create table t ( x int );

Table created.

ops$tkyte@ORA920> create index t_idx on t(x);

Index created.

ops$tkyte@ORA920> alter index t_idx monitoring usage;

Index altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      l_theCursor     integer default dbms_sql.open_cursor;
  3      l_columnValue   varchar2(4000);
  4      l_status        integer;
  5      l_query         varchar2(1000) default 'select * from t where x > 0';
  6
  7      procedure p( p_str in varchar2 )
  8      is
  9      begin
 10          dbms_output.put_line( p_str );
 11          for x in ( select index_name, used from v$object_usage )
 12          loop
 13              dbms_output.put_line( x.index_name || ' ' || x.used );
 14          end loop;
 15      end;
 16  begin
 17      p( 'before parse' );
 18      dbms_sql.parse(  l_theCursor,  l_query, dbms_sql.native );
 19      p( 'after parse, before define' );
 20      dbms_sql.define_column( l_theCursor, 1, l_columnValue, 4000 );
 21      p( 'after define, before execute' );
 22      l_status := dbms_sql.execute(l_theCursor);
 23      p( 'after execute, before fetch' );
 24      l_status := dbms_sql.fetch_rows(l_theCursor);
 25      p( 'after fetch, before close' );
 26      dbms_sql.close_cursor(l_theCursor);
 27      p( 'after close' );
 28  end;
 29  /
before parse
T_IDX NO
after parse, before define
T_IDX YES
after define, before execute
T_IDX YES
after execute, before fetch
T_IDX YES
after fetch, before close
T_IDX YES
after close
T_IDX YES

PL/SQL procedure successfully completed.

it is during the parse -- that is why this is so "lightweight" and fast.
Review &
      Followup

      Rating:
      5
  January 18, 2003
Reviewer:
      A reader
Hi Tom,

 Is there any way to tell that this index is being used this many times(query
count)?

          Followup:

nope, just that it either

a) is
b) is not

you can use v$segstat (v$segment_statistics) to see how much LIO has been done
to it, that should give an indication as to its "popularity"

      Rating:
      5
  January 18, 2003
Reviewer:
      A reader
Hi Tom,

 I checked v$segstat view.
 What is the difference between OBJ# and DATAOBJ# ?

 Both value is same in the view.

          Followup:

truncate a table and then check it again ;)

obj# = fixed
dataobj# = changes if you truncate/move/reorg the object in general.

      Rating: 4
no entry appear
      in v$object_usage  September 12, 2003
Reviewer:
      Pushparaj Arulappan  from NJ, USA
Tom,

I do not see any entry from v$object_usage after
I altered the index to monitor the usage.

SQL > create unique index emp_ind1 on emp (empno);
Index created.

SQL> alter index emp_ind1 monitoring usage;
Index altered.

SQL> select * from emp where empno=7902;

     EMPNO ENAME                JOB                         MGR
---------- -------------------- -------------------- ----------
HIREDATE                   SAL       COMM     DEPTNO
------------------- ---------- ---------- ----------
      7902 ford                 ANALYST                    7566
1981-12-03.00.00.00        100                    20

SQL> SELECT * FROM V$OBJECT_USAGE;
no rows selected.

Can you please help me on this..

Thanks
Pushparaj

          Followup:

Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production

ops$tkyte@ORA920> create unique index emp_ind1 on emp(empno);

Index created.

ops$tkyte@ORA920> alter index emp_ind1 monitoring usage;

Index altered.

ops$tkyte@ORA920> select * from v$object_usage;

INDEX_NAME                     TABLE_NAME                     MON USE
START_MONITORING    END_MONITORING
------------------------------ ------------------------------ --- ---
------------------- -------------------
EMP_IND1                       EMP                            YES NO  09/12/2003
14:29:19

never saw your behaviour before but then again, I don't know what
version/platform you might be on.

suggest you try support for this one -- i cannot reproduce at all.

      Rating:
      3
version/platform  September 12, 2003

Reviewer:  Pushparaj Arulappan  from NJ, USA

Our platform is HP-UX and the oracle version is:
Connected to:
Oracle9i Enterprise Edition Release 9.2.0.3.0 - 64bit Production
With the Partitioning option
JServer Release 9.2.0.3.0 - Production

Thanks
Pushparaj

          Followup:

contact support.  i've never had an issue with it.  I cannot reproduce and see
no obvious bugs filed against it.

      Rating:
      4
Lightweight?  December 04, 2003
Reviewer:
      Vladimir Andreev  from Herrenberg, Germany
It looks like it should be lightweight alright. And I couldn't measure any
significant overhead when soft-parsing queries that use the monitored index. But
I got a substantial increase (double or more) of latching, calls, open cursors,
and some other statistics when doing many hard parses (by not using bind
variables, of course). I tested this on HP-UX 11 and Windows with similar
results (these are from a test on Windows, 9.2.0.4.0). I swapped the order of
run1 and run2 to exclude any 'warm-up' effects -- same thing. I ran the same
queries as run1 and run2 to 'callibrate' the test environment -- same thing. I'm
pretty sure the only difference is in the 'monitoring usage' state of the index
being used.

Regards,
Flado
SQL> create table t1(mon number,nomon number);

Table created.

SQL> insert into t1 select rownum,rownum from all_objects where rownum<=10000;

10000 rows created.

SQL> create unique index t1mon on t1(mon);

Index created.

SQL> create unique index t1nomon on t1(nomon);

Index created.

SQL> analyze table t1 compute statistics for table for all indexes;

Table analyzed.

SQL> alter index t1nomon nomonitoring usage;

Index altered.

SQL> alter index t1mon monitoring usage;

Index altered.

SQL> declare
  2       dummy number;
  3  begin
  4       runstats_pkg.rs_start;
  5       for i in 1..10000 loop
  6           execute immediate 'select count(nomon) from t1 where nomon='||i
into dummy;
  7       end loop;
  8       runstats_pkg.rs_middle;
  9       for i in 1..10000 loop
 10           execute immediate 'select count(mon) from t1 where mon='||i into
dummy;
 11       end loop;
 12       runstats_pkg.rs_stop(800);
 13  end;
 14  /
Run1 ran in 2102 hsecs
Run2 ran in 2767 hsecs
run 1 ran in 75,97% of the time
Name                                Run1      Run2      Diff
LATCH.child cursor hash table     70,068    70,915       847
STAT...parse time elapsed          1,665     2,533       868
LATCH.row cache enqueue latch    140,126   141,854     1,728
LATCH.row cache objects          140,131   142,001     1,870
LATCH.session idle bit                 2    10,001     9,999
STAT...enqueue releases                5    10,015    10,010
STAT...enqueue requests                5    10,015    10,010
STAT...opened cursors cumulati    10,009    20,029    10,020
STAT...parse count (total)        10,009    20,038    10,029
STAT...index scans kdiixs1            12    10,059    10,047
STAT...shared hash latch upgra        12    10,059    10,047
STAT...execute count              10,023    20,091    10,068
STAT...table fetch by rowid           11    10,102    10,091
LATCH.enqueues                        32    10,165    10,133
STAT...no work - consistent re        29    10,228    10,199
STAT...recursive calls            10,427    21,838    11,411
LATCH.dml lock allocation            190    20,191    20,001
STAT...calls to get snapshot s    10,023    30,096    20,073
LATCH.enqueue hash chains            310    20,442    20,132
STAT...buffer is not pinned co        40    20,329    20,289
STAT...consistent gets            20,078    40,431    20,353
STAT...session logical reads      20,581    40,938    20,357
LATCH.library cache pin alloca    60,268   102,647    42,379
LATCH.cache buffers chains        23,809    69,817    46,008
LATCH.session allocation             448    51,749    51,301
LATCH.shared pool                228,218   281,221    53,003
LATCH.library cache pin           90,371   155,432    65,061
LATCH.library cache              200,759   308,581   107,822

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
956,987 1,388,135   431,148  68.94%

PL/SQL procedure successfully completed.

          Followup:

well, if you are hard parsing like mad, you have a bug to be fixed no?  (yes)

hard parsing is the culprit here, index monitoring just a symptom.

you already have to fix that hard parse issue, no if and or buts around it.

but interesting caveat -- index monitoring will up the latching in a system that
is already broken (making it more broken).

Moral to the story -- don't try tuning something until you fix the OBVIOUS and
egregious issues!

thanks.

      Rating: 2
It's not about
      tuning  December 04, 2003
Reviewer:  Vladimir
      Andreev  from Germany
Well, Tom, I wasn't tuning a particular system. I was trying to set up an
experiment to measure the impact of index monitoring on parse performance. It
was clear to me from the beginning that only a hard parse has any chance of
suffering an overhead from monitoring. What I think my experiment proves is that
*every* hard parse becomes significantly more expensive in terms of latching and
CPU (and LIO, and...). That might not matter in a well-designed and healthy
application, but is something to have in mind.
And the fact remains, that index monitoring is not, in fact, lightweight in all
circumstances -- as you put it, it makes a sick system sicker.
I was actually hoping for some explanation on the inner workings of index
monitoring. For example, am I right that to maintain index monitoring
information, every hard parse requires an additional recursive call (recursive
calls goes up), which needs to be parsed (parse calls, opened cursors go up) and
executed (execute count goes up), and it is satisfied via an index lookup and a
table fetch by rowid, either of which is a consistent get?

In other words, INDEX MONITORING USAGE is not the kind of lightweight mechanism
that TABLE MONITORING is (no recursive SQL there, no latches, no accuracy).

Thanks,
Flado

          Followup:

but hard parses should be 1/100000 of the parses on your system.  the 'overhead'
to me would be a non-issue really.

the extra work involved in the recursive SQL seems to center on this:

update object_usage set flags=flags+1 where obj#=:1 and bitand(flags, 1)=0

they are basically "setting it on" during the hard parse -- each hard parse.

      Rating: 4
In an ideal
      universe  December 05, 2003
Reviewer:  Vladimir
      Andreev  from Germany
Yes, Tom, hard parses should not be a problem. But out in the field there are
still far too many systems where hard parses ARE a problem. And not only because
of literals -- there are many middle layers that generate dynamic in-lists of
varying number of bind variables and other non-shareable constructs. I come
accross such systems far too often for my liking :-(

My point is, I can't afford to make an already sick system sicker. So, I need to
be careful NOT to recommend index monitoring before they fix their hard parse
problem -- which never happens, usually.

Thanks for your time. I remain your devoted fan.
Flado

P.S.: There are a couple of questions on the subject of index monitoring I
intend to test: Does "alter index ... monitoring usage" invalidate dependent
cursors and if not, does a soft parse after the "alter index" get registered in
object_usage?
If you want, I can post the results here, but I really don't want to intrude on
your web space.

          Followup:

well, my point was

a) index monitoring is used to "fine tune"
b) if you are hard parsing like mad, you are not at the fine tuning step
c) so, fix your bug (hard parse) and then do this :)

so we are in agreement i think!

go for it, post your findings - the more information the better.

December 05, 2003 Petr -- Thanks for the question regarding "how to
      set values as new column headers", version 8.1.5

      You Asked
Hello Tom,

I wonder if it is possible to do something that is called a
'crosstab query' in MS Access or 'pivot table' in MS Excel.

Let's say I have a table with three columns:
-Item
-Year
-Quantity

Now I want to output the data, having the items listed
vertically
and the years horizontally as new column headers. The quantities
would be the values in this matrix.

Many thanks for your help!
Petr

      and we said...
This is something a query tool does not the database.  Access
and Excel and "display" tools.

Oracle Discoverer does this feature and does it well.  See

http://www.oracle.com/datawarehouse/products/bitools/discoverer/
for information on that.

Inside of SQL, we can do somethings to make this happen as well.
 If you know the absolute range of YEARS, I can do the above.
Lets say the data ranged from 1996 - 2000.  I can do the
following then:

ops$tkyte@8i> create table t ( item varchar2(6), qty number,
year number );

Table created.

ops$tkyte@8i>
ops$tkyte@8i>
ops$tkyte@8i> begin
  2      for i in 1 .. 25 loop
  3          insert into t values
  4          ( 'item-' || mod(i,3),
  5            random.rand,
  6            to_char(sysdate-(365)*mod(i,5),'yyyy')
  7          );
  8      end loop;
  9  end;
 10  /

PL/SQL procedure successfully completed.

ops$tkyte@8i>
ops$tkyte@8i> select * from t;

ITEM          QTY       YEAR
------ ---------- ----------
item-1      27104       1999
item-2       4073       1998
item-0       3756       1997
item-1      20834       1996
item-2       3484       2000
item-0      26648       1999
item-1      29438       1998
item-2       2555       1997
item-0      27303       1996
item-1      16503       2000
item-2      31100       1999
item-0       6115       1998
item-1       4212       1997
item-2      23934       1996
item-0      24577       2000
item-1       1707       1999
item-2       1004       1998
item-0       3689       1997
item-1       1781       1996
item-2      30265       2000
item-0      10985       1999
item-1      13802       1998
item-2       9443       1997
item-0      21010       1996
item-1      26354       2000

25 rows selected.

ops$tkyte@8i>
ops$tkyte@8i> select item,
  2         sum( decode(year,1996,qty,0) ) y1996,
  3         sum( decode(year,1997,qty,0) ) y1997,
  4         sum( decode(year,1998,qty,0) ) y1998,
  5         sum( decode(year,1999,qty,0) ) y1999,
  6         sum( decode(year,2000,qty,0) ) y2000
  7    from t
  8   group by item
  9  /

ITEM        Y1996      Y1997      Y1998      Y1999      Y2000
------ ---------- ---------- ---------- ---------- ----------
item-0      48313       7445       6115      37633      24577
item-1      22615       4212      43240      28811      42857
item-2      23934      11998       5077      31100      33749

Another way to do this in 8.1 is as follows:

ops$tkyte@8i> variable y1 number
ops$tkyte@8i> variable y2 number
ops$tkyte@8i> variable y3 number
ops$tkyte@8i> variable y4 number
ops$tkyte@8i>
ops$tkyte@8i> exec :y1 := 1996; :y2 := 1997; :y3 := 1998; :y4 :=
1999;

PL/SQL procedure successfully completed.

ops$tkyte@8i>
ops$tkyte@8i> select item,
  2            (select sum(qty) from t t2 where t2.item = t.item

                 and year = :y1 ) y1,
  3            (select sum(qty) from t t2 where t2.item = t.item

                 and year = :y2 ) y2,
  4            (select sum(qty) from t t2 where t2.item = t.item

                 and year = :y3 ) y3,
  5            (select sum(qty) from t t2 where t2.item = t.item

                  and year = :y4 ) y4
  6   from (select distinct item from t ) t
  7  /

ITEM           Y1         Y2         Y3         Y4
------ ---------- ---------- ---------- ----------
item-0      40021       6585      15153      35386
item-1      36690       5619      22001      32194
item-2      14853      37198       8441       9894

Review &
      Followup

      Rating: 2
How do I do the
      same thing for strings  December 05, 2003

Reviewer:  Manu Goel  from New Delhi, India

Hi,
How do I do the same thing if my data consists of string values instead of
numeric ones ? Like, in the above example, I had the values 'A' to 'Z' instead
of qty for every year.

How do I perform the same thing keepings the years as columns ?

Thanks !!

          Followup:

use max instead of sum

December 05, 2003 Kailash -- Thanks for the question regarding
      "Steps in Tuning", version 8.1.6

      You Asked
Hi Tom,

In one of your followup, you had mentioned that you need to look for queries
that perform a large number of logical I/Os. But my understanding is that an I/O
is considered as logical I/O if I/O involves a memory access and not a disk
access (which constitutes a physical I/O) which will be performed much much
faster compared to a disk access. Could you explain me why this is bad? (Because
the performance tuning books say that the hit ratio should be high and hit ratio
will be high if the query is able to find it's data in memory rather than
accessing the disk)

Please explain

      and we said...
goto www.hotsos.com and read the paper "so are you still using cache hit ratios"
Review &
      Followup

      Rating: 3
Goal of
      tuning  December 04, 2003
Reviewer:
      A reader  from NJ, USA
Tom,

I have read several of your articles. In all of them you insisted the following
:

1. Use CBO and analyze the tables,indexes etc
2. Make sure that the query accesses/performs fewest logical I/Os. ie it
accesses the smallest number of blocks to satisfy the query
3. Use hints only as the last resort
4. Use bind variables and bulk operations such as array processing instead of
row by row process
5. Always look for Consistent Gets in the trace output and try to minimize it.

1. Are those the things you always look for when you are asked to tune the
query. I mean, if you were to tune the query, are those the above ones that you
focus on. If not, what other things you generally concentrate?
2. What things do you look for in EXPLAIN PLAN such as Bytes, Card, Cost etc.
Which one would be of real significance when you tune the query.
3. Also, you mention that higher cost does not mean a bad performing query and
lower cost mean a good performing query. ie. cost cannot be directly related to
performance. Is that true?
4. As soon as you see a full table scan or an index scan, what other things do
you immediately see and what steps do you follow to arrive at alternate queries
to make the query efficient.
5. What collection of scripts do you personally generally use when you do a
query tuning.

          Followup:

i like to tune QUESTIONS not queries first of all :)

but #1 is there for sure.  thats a given.

The rest are really not "process".  In order to tune, I want to know what the
"goal" is (you've probably seen me ask "but what is it you are trying to
accomplish" here more then once).

Once I know the goal, i'll look at the existing process and try to optimize it.

Take a process like

   for x in ( select * from t )
   loop
      some trival processing of x
      insert into another_t x

I'll turn that procedural code (sometimes pages of it) into a single query.  Now
thats tuning via re-design.  that kind of tuning gets you massive payback.

Suppose I could take a query you execute 1,000,000 times in some process and cut
its run time in half via "query tuning".  You still have 1,000,000 * (some time)
of runtime for that query.  Now, I take that process that calls that query
1,000,000 times and develop a single query that runs the entire process.  That
-- that is tuning.

I cannot verbalize "how I tune a query" when I do tune a query.  I did write in
my new book "Effective Oracle By Design" what I think it takes:

<quote>
This was probably the hardest chapter of the book to write. That is not because
the material is all that complex. It's because I know what people want, and I
know what can be delivered. What people want is the ten-step process for tuning
any query. What can be delivered is knowledge about how queries are processed,
which you can use and apply as you develop queries.

If there were a ten-step, or even a million-step, process by which any query (or
a large percentage of queries) could be tuned, we would write a program to do
it. Sure, there are actually many programs that try to do this, such as Oracle
Enterprise Manager with its tuning pack, SQL Navigator, and others. What they do
is primarily recommend indexing schemes to tune a query, suggest materialized
views, and offer to add hints to the query to try other access plans. They show
you different query plans for the same statement and allow you to pick one.
These tuning tools use a very limited set of rules that sometimes can suggest
that index or set of indexes you really should have thought of during your
design. They offer "rule of thumb" (what I generally call ROT, since the acronym
and the word it maps to are so appropriate for each other) SQL optimizations. If
these were universally applicable, the optimizer would do them routinely.

In fact, the CBO does tuning already. It rewrites our queries all of the time.
If there were an N-step process to tuning a query-to writing efficient SQL- the
optimizer would incorporate it all, and we would not be having a discussion on
this topic. It is like the search for the Holy Grail. Maybe someday the software
will be sophisticated enough to take our SQL, understand the question being
asked, and process the question rather than syntax.

This chapter will provide the foundation knowledge you need in order to begin
thinking about how queries could be processed. Once you understand query
processing, you can make the query do what you think might be best. Writing
efficient SQL is no different from writing efficient C, Java, or even English.
It takes an understanding of how things work and what is available. If you don't
know some technique is available, you'll never be able to optimize to use it.

What You Need to Write Efficient SQL

For every rule of thumb out there I've seen regarding writing efficient SQL,
I've been able to come up with a slew of common (not esoteric) countercases to
prove that rule of thumb is wrong as many times as it is right. I've talked to
people who swear NOT IN is fatal, and you should never use it; instead, always
use NOT EXISTS. Then I show them NOT IN running a query ten times faster than
NOT EXISTS. I talk with people who feel NOT EXISTS is the worst construct on the
planet; you must use NOT IN. Then I show them how NOT EXISTS can run many times
faster than NOT IN.

Rather than following rules of thumb, writing efficient SQL requires the
following:

o Knowledge of the physical organization of what you're querying against That
is, you should know the schema. (Refer to Chapter 7 of this book for information
about designing an efficient schema.)

o Knowledge of what the database is capable of doing For example, if you did not
know about skip-scan indexes and what they do, you might look at a schema and
say, "Aha, we are missing an index!" when, in fact, this is not true.

o Knowledge of all of the intricacies of SQL You should be familiar with SQL,
from the lowly WHERE clause up to analytics and pseudo columns. You should know
what using a particular construct would do to your runtime processing.

o A solid understanding of the goal-what the question is Tuning a query or
process is really hard (or impossible), unless you understand the question in
the first place. For example, many people use outer joins in all queries,
because they are afraid of losing a row. If the objects are related in a
one-to-one mandatory fashion, they don't need an outer join at all. In many
cases, the question derived from the query is much more confining than the real
goal of the query.

In this chapter, we'll cover the topics of what the database is capable of doing
in general, looking at many of the access paths and join operations available.
We'll look at what SQL is capable of doing, but not by discussing the entire
language, since that is a book in itself. Rather, we'll look at a couple of
things that will whet you appetite, showing you how powerful this language can
be-how much more than just SELECT, FROM, WHERE, and ORDER BY there is. Then
we'll close up with a look at that most important topic: why understanding the
question is more important than having a query at hand to tune. I hope that when
you finish this chapter, you'll want to learn more about SQL and what it can do.
</quote>

The only tools I use -- autotrace, sql_trace (10046 trace really) + tkprof,
statspack sometimes but mostly -- mostly -- almost 100% -- my knowledge of what
is possible with SQL and the database.

If you don't know what it can do -- you'll lose everytime.

For example -- was just talking with someone.  They had a problem -- a table A
with a one to many relationshop to B and a one to many relationship to C.

They wanted to get data from A, aggregated data from B and aggregated data from
C.

Sounds "simple" but the A 1:M B and A 1:M C was "getting in the way".  they were
getting a cartesian product between B and C when just joining A to B and A to C

(eg: if the parent record in A has 2 children in B and 3 children in C, they'd
get 6 records!)

Their thoughts -- we'll run two queries and join them in our application.

My thoughts -- let me show you this inline view thing where we can:

select ..
  from ( select ... from a, b where ... group by ... ) T1,
       ( select ... from a, c where ... group by ... ) T2
 where t1.... = t2.....

hmmm, very nice, very fast - but not possible unless you knew you could do it.
Then we went into analytics, cubes, rollups and so on.

You cannot tune until you know what you have available to tune with!

      Rating: 4
Good
      show  December 04, 2003
Reviewer:  Brett
       from Richmond, VA
I have been trying to convince fellow workers to use in-line views for complex
joins.  Not only can they run faster, but it allows you to break up your logic
into little blocks.  I'll keep screaming about them and hopefully, someday, they
will listen.

      Rating: 5
Well
      said  December 04, 2003
Reviewer:
      A reader
Tom,
Many times I have been asked the same question. "Is there any tool which will
automatically scan through all our packages and generate explain plans for each
query?" My first response is NO. Next I ask: If such a tool was there. what do
you do with the 700 explain plans? "Oh we want you to look at all explain plans
and tune each query to make sure that indexes are being used". When I tell that
this is not the correct approach to tuning, I get reactions from looks of
disbelief "perhaps he doesn't know his job" or "he is trying to get away from
it" to stern glares of "do what you are told, we don't have time for your proper
approach nonsense".

You are doing a great service to the DBA and Developer communities through this
forum by providing advise on technical matters and dispelling many myths about
how databases work. Please advise how to handle these kind of situations.

For obvious reasons, keeping my name and location null.

Thanks...

      Rating: 3
Tools you
      use  December 04, 2003
Reviewer:
      A reader  from NJ, USA
Tom,

Thanks much for the response. If you use the Autotrace or TKPROF (trace 10046) :

1. What items would you first focus on such as Consistent Gets, Physical reads
etc
2. What do you focus on 10046 events

          Followup:

10046 is just "sql_trace with options"

i look for low hanging fruit (queries with big numbers on the totals line)

then I decide if they are worth looking at.

Then I try to figure out "why, why are we doing that query" and tune the
question (is there a better approach)

or I apply my knowledge of SQL and say -- you know, instead of that self join to
get the current record joined with the 'next' record -- lets just use lead()
instead of the join.

(logical ios are what I'm looking mostly to reduce.  ignore physical, pay
attention to logical and the physical will take care of themselves)

      Rating: 5
Physical
      IO's  December 05, 2003
Reviewer:
      A reader
Tom,
am an avid reader of your site. Great work.
Can you please explain why the Physical IO's would take care of themselves when
the Logical IO's are reduced ?

          Followup:

reduce the LIOs and...

you have less blocks to buffer (probably)

you appear to have a bigger buffer cache.

you can more efficiently cache that frequently used information -- rather then
bumping it out to make room for other stuff.

A block has to be read into the buffer cache eventually right.  If you reduce
the number of blocks you need to process to execute your query -- you'll have
less blocks to potentially read and more buffer space to cache the ones you use
over and over.

December 05, 2003 -- Thanks for the question regarding "Question on
      Using Ref Cursor", version 8.1.6

      You Asked
Tom,

I 've written a stored procedure which uses ref cursor. Following is how I 've
written it :-

create or replace package pkg_dept
AS
  type rc_dept is ref cursor;
end;
/

create or replace
    procedure sp_dept( t_deptno IN NUMBER,
                t_designation  IN NUMBER,
                    dept_cur in out pkg_dept.rc_dept )
   is
   begin

       OPEN dept_cur for
        select ename,salary,join_date
        from emp
        where deptno=t_deptno
        and designation=t_designation;

 end;
   /

From Sql*plus I can call the stored procedure and get results using :-var c
refcursor
execute sp_dept(100,'CLERK',:c);
print c

1. But how do I do it using a small pl/sql block ie. how do I put
var c refcursor (Step 1)
execute sp_dept(100,'CLERK',:c); (Step 2)
print c  (Step 3)
the above 3 steps into a pl/sql block and get the output, I tried a couple of
ways but it didn't work.

2. Is the above method of opening a ref cursor and executing the results a good
method ? In my application I will be calling the stored procedure from a jsp
page, I ran the jsp page and it works fine in retrieving the results, But I am
wondering if the method I 've adopted in opening the cursor a good one,because I
dont want to end up into any problems in the future. Also could you mention some
EXCEPTIONS that I could use in the stored procedure.

3. I would like to know if I am using bind variables in the stored procedure. I
am rewriting a sql query in to stored procedure for it to use bind variables to
improve performance. So I wanted to confirm that this procedure uses bind
variables.

Thanks in Advance,
TS

      and we said...
1) you would have to explicitly fetch and print the results:

declare
   c pkg_dept.rc_dept;
   l_ename emp.ename%type;
   l_sal   emp.salary%type;
   l_join_date emp.join_date%type;
begin
   sp_dept( 100, 'CLERK', c );
   loop
       fetch c into l_ename, l_sal, l_join_date;
       exit when c%notfound;
        ....
   end loop;
   close c;

2) I firmly believe the best java programs are those that have ZERO
"selects/inserts/updates/deletes" in them.  Hence, using ref cursors is the way
to go.  Lets you tune without bothering those java programmers.

You use the exception you need to -- there is no "list" of ones you should use.
You use what you need?

3) yes you are.  T_DEPTNO and T_DESIGNATION are bind variables.  Now, just make
sure the CALLER (java programmer) uses bind variables when calling your
procedure!!!!

Review &
      Followup

      Rating: 4
One More
      Clarification on Ref Cursor  July 15, 2002

Reviewer:  Saikumar  from USA
Tom,

Thanks for your help on giving examples to clarify the usage of Ref Cursor. I
've one more doubt on if I could use EXCEPTION in the pl/sql block I had
mentioned. Say for eg. I want to use "EXCEPTION NO DATA FOUND" how do i use it
in the procedure that I had mentioned in the question.

Thanks,
TS

          Followup:

You cannot with a ref cursor as NO_DATA_FOUND *only* applies to

select ... INTO ....

queries and not

open
fetch
close

type of queries.

      Rating: 4
Any other way
      of writing the stored procedure with ref cursor  August 07,
      2002
Reviewer:  A reader  from US
Tom,

Is there any other way of writing the stored procedure below :-

create or replace package pkg_dept
AS
  type rc_dept is ref cursor;
end;
/

create or replace
    procedure sp_dept( t_deptno IN NUMBER,
                t_designation  IN NUMBER,
                    dept_cur in out pkg_dept.rc_dept )
   is
   begin

       OPEN dept_cur for
        select ename,salary,join_date
        from emp
        where deptno=t_deptno
        and designation=t_designation;

 end;

I would like to use some relevant exceptions and also like to open and close the
ref cursor. This procedure will be called from a java program. I am more
confused with using the exceptions as I feel nothing could fit in to the above
procedure. Please help.

Thanks,
TS

          Followup:

Well first -- use packages for everything in real code -- it breaks the
dependency chain and reduces the amount of recompilation that'll have to take
place when you update code.

With the ref cursor -- that is it.  That is the way it is done.  the CALLER
(java in this case) is responsible for the "close" call -- you don't do that in
plsql when the client fetches from the cursor.

I don't see any need for any exception handling here -- there is nothing you
could "catch" and handle -- the caller would want to handle any and all errors
that occur (by reporting them to the end user most likely)

      Rating: 5
Adding a Status
      to the procedure that has ref cursor  August 08, 2002

Reviewer:  TS  from US
Tom,

Sorry for going over this again and again. I 've included a status variable ,
where if there is no rows retrieved by the ref cursor it should return the
status 0 and if there are rows retrieved the procedure should return a status 1.
I 've modified the procedure as follows :-

create or replace
    procedure sp_dept( t_deptno IN NUMBER,
                t_designation  IN NUMBER,
                status out NUMBER,
                dept_cur in out pkg_dept.rc_dept )
   is
   begin

      status:=1;

       OPEN dept_cur for
        select ename,salary,join_date
        from emp
        where deptno=t_deptno
        and designation=t_designation;

      IF dept_cur%NOTFOUND
      THEN
        status:=0;
     ELSE
        status:=1;
      END IF;

 end;

when I execute the above procedure, it doesn't seem to pickup the  correct
status, irrespective of whether rows exist or not , it shows status=1. Could you
pls help.

Thanks,
TS

          Followup:

An OPEN fetches NO DATA WHATSOEVER.

A FETCH fetches data.

There is absolutely no way upon OPEN to decide that "hey there is no data".  You
must actually FETCH a row.  Only the client that recieves this ref cursor and
FETCHES from it can discover "hey, there is no data.

dept_cur%notfound will not be set until AFTER YOU FETCH.

You cannot do what you are trying to do. the guy who gets the cursor will have
to fetch and then say "there is data" or "there is no data".

      Rating: 4
Point made very
      clearly.  August 09, 2002
Reviewer:  Robert
       from TN USA

      Rating: 4
process ref
      cursor  December 16, 2002
Reviewer:
      A reader
Hi

I have a function which retrieves a small table and store the results into a ref
cursor and that ref cursor is returned

something like

CREATE OR REPLACE PACKAGE TYPES
AS
    TYPE RC IS REF CURSOR;
END;
/

create or replace function x(y varchar2)
return types.rc
...
...
open c1 for select * fdrom emp
return c1;
.....

If I have a pl/sql anonymous block similar to

declare
  c1  types.rc;
begin
  c1:=x('EMP');
....
....
end;
/

How can I process c1 row by row in the procedure?

          Followup:

Look at the answer above.

FETCH

      Rating: 4
sorry just
      noticed after I post  December 16, 2002
Reviewer:
      A reader
Hi

I saw the answer after I posted really sorry... but I have a serious problem, I
have to declare the record type. The problem is I pass the table_name dynamiclly
so in the run time I dont know what's the record type. For example if I passed
EMP as table_name record type should be

emp%rowtype

if I pass DEPT then it should be

dept%rowtype

I think it's impossible to assign record type in the run time, is there a
workaround?

          Followup:

You will not be using REF CURSORS then.

With a ref cursor you by definition have a fixed number of inputs and outputs -
everything is setup at COMPILE time.

You can use DBMS_SQL.  search this site for

dump_csv

to see a very generic example.

      Rating: 5
forogot to say
      recorde type is going to be declared in anonymous pl/sql
      block  December 16, 2002
Reviewer:
      A reader
Hi

I forgot to mention that record type has to be in PL/SQL block so I can do

c1:=x('EMP');
loop
fetch c1 into record_type

etc.....

      Rating: 5
Can you put a
      ref cursor in another cursor or in "if ... end if"?  January
      17, 2003
Reviewer:  John  from New Jersey
I have learnt how to return a result set using ref cursor from this forum, which
worked great for me!  But can you put a ref cursor in another cursor or in "if
... end if"?

Something like this:

Declare
    v_user_setting    cursor type
begin
   if ....... then
    open v_user_setting for select ...
   end if;
    return v_user_setting;
end if;

If I put OPEN cursor statement in a "if ... end if" statement, it worked fine
when it returns some rows.  But I got error if it return 0 row.  Here is the
error message when I tested from SQL*Plus:

ERROR:
ORA-24338: statement handle not executed

Is this expected or did I do anything wrong?  Any work around?

          Followup:

umm, the cursor wasn't opened so yes there will be an error.

what would you "like" it to do??  It is like accessing "NULL" -- there is
nothing there.

You could always

  if ... then
     open v_user_setting for select ...
  else
     open v_user_setting for select * from dual where 1=0;
  end if;

      Rating: 5
Now, just make
      sure the java programmers use bind variables  January 24,
      2003
Reviewer:  Rob  from Overland Park, KS USA

Tom,

You mentioned that the Java programmers need to use bind variables.  Would you
please elaborate and give an example.

          Followup:

  static void doPreparedStatement (Connection con, int count) throws Exception
  {
    long start = new Date().getTime();
    // p ("doing prepared statement start time : " + start);
    PreparedStatement ps = con.prepareStatement("insert into testxxxperf " +
                       "(id, code, descr, insert_user, insert_date)"
                       + " values (?,?,?, user, sysdate)");

    for (int i = 0; i < count; i++)
    {
      ps.setInt(1,i);
      ps.setString(2,"PS - code" + i);
      ps.setString(3,"PS - desc" + i);
      ps.executeUpdate();
    }

is an example.

      Rating: 5
ref curser in
      "if" statement  February 04, 2003
Reviewer:
      John  from New Jersey
Regarding the ref cursor in "if" statement, what I was trying to do is to open
ref cursor only under certain conditions.  And even under that condition, there
could be no record found.  In most cases I can put the condition in where clause
of the "open curser for select ..." statement, but there are times that it's
just easier to use "if" statement.

What you are saying is even I do "open cursor for select ..." and if there is no
data, the cursor is not open?  But if the "open cursor for select ..." is not in
a "if" statement, there was no error.  This is what puzzled me.

Your suggestion is a good work around!  Thanks.

          Followup:

it matters no if the ref cursor finds data or not.  Look at my example quick:

You could always

  if ... then
     open v_user_setting for select ...
  else
     open v_user_setting for select * from dual where 1=0;
  end if;

there will never be any data for the second open -- it is just that there is an
OPEN cursor for the client to try and fetch from (and will immediately get the
"no data found")

      Rating: 5
Thanks, Tom.
        February 04, 2003
Reviewer:  John
       from New Jersey
But what's the difference between
    open v_user_setting for select ...
and
    open v_user_setting for select * from dual where 1=0;

There's no data found in either case.  But the first one gives error whereas the
second one doesn't.

          Followup:

If you code this:

Declare
    v_user_setting    cursor type
begin
   if ....... then
    open v_user_setting for select ...
   end if;
    return v_user_setting;
end if;

you will get an error in the client if they attempt to access the cursor (it was
never open)

If you code this:

Declare
    v_user_setting    cursor type
begin
    open v_user_setting for select ...
    return v_user_setting;
end if;

You should not -- i would be interested in an example of such an error if you
have one.

      Rating: 5
what is the use
      of ref cursor ?   October 28, 2003
Reviewer:
      A reader

HI, By keeping What need in mind oracle introduced the REF cursor except passing
the result set.

 I just want to know what are best places to use refcursor.
1.) to pass the result set
2.) support JDBC/ result set
3.) ?
4.) ?

 places in pl/sql code, where one should use it to improve  code..

Thanks,

          Followup:

only #1 since #2 is a repeat of #1

use it ONLY when forced to -- to return a result set to a client.  be that
client a plsql subroutine or a java program or vb or whatever.

it returns a result set -- period.

      Rating: 4
REF
      cursor  October 29, 2003
Reviewer:
      A reader
Thanks, Tom.

  In Java, I heard that String manipulation is easy.
we still have stored package that returns the refcursor
after building the dynamic query. There are 20 to 25 ref cursors returned in
various procedures in pkg.

 do you think they should just build the query in java and query it directly
from htere ?

please explain

Thanks.

          Followup:

pssst -- string manipulation in plsql is easy.

there, we are even.  just use plsql.

      Rating: 3
Would you use
      java with a sql or a store procedure to return a ref
      cursor  October 29, 2003
Reviewer:  marc

Which would be your prefrence for standard practice,java with a sql statmment or
a store procedure to return a ref cursor to java.

          Followup:

i believe the best java programs do not have the words "select", "insert",
"update" or "delete" them -- only "begin .... end;"

so, ref cursor from a stored procedure.

      Rating: 5
Function
      returns Ref Cursor question  December 05, 2003

Reviewer:  Justin  from PA
owner@DEV> CREATE TABLE loser AS SELECT rownum id FROM user_tables WHERE rownum
< 11;

Table created.

owner@DEV>
owner@DEV>
owner@DEV> CREATE OR REPLACE PACKAGE TYPES
  2  AS
  3  TYPE RC IS REF CURSOR;
  4  END;
  5  /

Package created.

owner@DEV>
owner@DEV> create or replace function demofunc
  2  RETURN types.rc
  3  AS
  4  l_rc       types.rc;
  5  BEGIN
  6  open l_rc FOR SELECT id FROM loser;
  7  return l_rc;
  8  end;
  9  /

Function created.

owner@DEV> select demofunc from dual;

DEMOFUNC
====================
CURSOR STATEMENT : 1

CURSOR STATEMENT : 1

               ID
=================
                1
                2
                3
                4
                5
                6
                7
                8
                9
               10

10 rows selected.

What is the story with:

DEMOFUNC
====================
CURSOR STATEMENT : 1

CURSOR STATEMENT : 1

What generates that?
How do you get rid of it?
Do you need to get rid of it if you are returning these values to a calling
program?

Thanks much

          Followup:

variable x refcursor
exec :x := demofunc
print x

the calling program (sqlplus in this case) is the thing printing it out -- if
your client program doesn't print it out, it won't be printed out.

if you don't call the function from sql, just call the function and ask plus to
print the cursor -- it'll look "normal"

December 05, 2003 V Siva -- Thanks for the question regarding
      "UTL_HTTP package usage", version 7.3.2.3.0

      You Asked
Hi Tom,

I want to know the usage of UTL_HTTP package. Can u explain it with an example?

Thanks
Siva

      and we said...
Well, UTL_HTTP was introduced in 7.3.3 and I see you have 7.3.2 so you might not
have access to it.  Here is how to use utl_http.  It has 2 interfaces

o one simply returns a URL requested page as a single string.  Works for small
pages upto 2000 bytes.

o the other returns a PLSQL table (like an array) of 2000 byte strings that
represent the entire page.

In Oracle8i, release 8.1, support for using a proxy server was introduced as
well.  Here are 2 examples showing the simple and then the table based
interfaces:

ops$tkyte@8i> create or replace
  2  procedure p( p_string in varchar2 )
  3  is
  4     l_string long default p_string;
  5  begin
  6     loop
  7       exit when l_string is null;
  8       dbms_output.put_line( substr( l_string, 1, 250 ) );
  9       l_string := substr( l_string, 251 );
 10     end loop;
 11  end;
 12  /

Procedure created.

P is a little procedure to help us print out the text we get for this demo..

ops$tkyte@8i>
ops$tkyte@8i> declare
  2      l_page    long;
  3     l_url    varchar2(35) default
  4              '
http://aria.us.oracle.com/';
  5  begin
  6      l_page := utl_http.request( l_url );
  7
  8      p( l_page );
  9  end;
 10  /
<HTML>
<HEAD>
<TITLE>Oracle Service Industries</TITLE>
</HEAD>
<FRAMESET COLS="130,*"
border=0>
<FRAME SRC="nav.html" NAME="sidebar" frameborder=0>
<FRAME
SRC="
http://aria.us.oracle.com/sb/web$sb.folder.home
NAME="body" frameborder="0" marginheight=
"0"
marginwidth="0">
</FRAMESET>
</BODY>
</HTML>
PL/SQL procedure successfully completed.

That was the simplest way to call UTL_HTTP - just send it a URL and get back
the page.  As long as the page was 2,000 bytes or less, this would work well.

For larger pages, we need to use REQUEST_PIECES as follows:

ops$tkyte@8i>
ops$tkyte@8i> declare
  2      l_page    utl_http.html_pieces;
  3          l_url     varchar2(25) default
  4                            '
http://www.yahoo.com';/
  5  begin
  6      l_page := utl_http.request_pieces( l_url,
  7                                         99999,
  8                                       'www-proxy' );
  9
 10      for i in 1 .. l_page.count
 11      loop
 12          p( l_page(i) );
 13                  exit when ( i = 3 );
 14      end loop;
 15  end;
 16  /
<html><head><title>Yahoo!</title><base href=
http://www.yahoo.com/>
http-equiv="PICS-Label"
content='(PICS-1.1 "
 http://www.rsac.org/ratingsv01.html
l gen true for "
http://www.yahoo.com/
r (n
0 s 0 v 0 l 0))'></head><body><center><form action=htt

[snip]....

href=r/sp>Sports</a>,
<a href=

PL/SQL procedure successfully completed.

Here we uses a proxy server (new in 8.1) to get the page since I am behind a
firewall and we got the page back in an "array" -- not as a single variable...

Review &
      Followup

      Rating:
      4
Thanks  March 04, 2002
Reviewer:  Pierre
      bilodeau  from Canada
This example help me to use this function, thank you for your help

      Rating: 3
I have some
      problem.........  August 12, 2002
Reviewer:
      T.V. Sarada Priya  from Kuala Lumpur, Malaysia
I am trying to use this UTL_HTTP in 8.1.7.2.0 Oracle Database on Compaq Tru64
Unix. But I always get an exception ORA6510: PL/SQL user defined unhandled
error. After handling the error with an exception too, it doe not solve. Have I
missed out any setting in Init.ORA or elsewhere? Otherwise, wanted to know
whether this will work on Enterprise Edition or does it require any other extra
Oracle products to be installed. I searched high and low in your web site as
well as others, but could not find the right information. Please help me!!!
I used the same code u had given:

ops$tkyte@8i> declare
  2      l_page    long;
  3     l_url    varchar2(35) default
  4              '

http://aria.us.oracle.com/';
  5  begin
  6      l_page := utl_http.request( l_url );
  7
  8      p( l_page );
  9  end;
 10  /

Thanks

Sarada Priya

          Followup:

Ummm, you do understand that aria is my server inside the Oracle firewall and
that you won't be able to get to it?

That is the exception -- you are not able to get to the web site.

Try a URL that you can access and see what happens.

      Rating:
      3
UTL_HTTP  November 20, 2002
Reviewer:
      debasis  from USA
good examples.

      Rating: 3
HTTP 407 Proxy
      Authentication Required  July 05, 2003
Reviewer:
      Rasin
How do I avoid the above  error

here is the code i am executing

declare
      l_page    utl_http.html_pieces;
      l_url     varchar2(25) default '
http://www.yahoo.com';/
  begin
      l_page := utl_http.request_pieces( l_url,
                                         99999,
                                       'myproxy' );
      for i in 1 .. l_page.count
      loop
          p( l_page(i) );
                  exit when ( i = 3 );
      end loop;
  end;
/

this proxy is in our firewall which requires windows NT username and password
can i pass my windows username and password for proxy authentication if not how
can I pass username and password to my proxy server for authentication.

Thanks
Rasin

          Followup:

given that you give me no idea what release -- all I can say is

in 8i, no go, you won't be doing this with utl_http.  you'll have to load a java
class or do it yourself using utl_tcp

in 9i, read the supplied packages guide -- there are lots of routines in
utl_http that are new and allow you to set headers.  including a "set
authentication" routine that permits this.

      Rating: 3
I modified
      it  July 05, 2003
Reviewer:  Rasin

here is my latest code after modification
but still there is error
since out proxy uses windows username and password
for authentication i supplied my windows username
and password.

  1  declare
  2      req utl_http.req;
  3        l_page    utl_http.html_pieces;
  4        l_url     varchar2(25) default '
http://www.yahoo.com';/
  5    begin
  6  UTL_HTTP.set_authentication(
  7     req,
  8     'my-windows-username',
  9     'my-windows-password-mypass',
 10     'Basic',
 11     TRUE); -- true for proxy
 12       --utl_http.set_authentication(req, username, password);
 13        l_page := utl_http.request_pieces( l_url,
 14                                           99999,
 15                                         'proxawq' );
 16        for i in 1 .. l_page.count
 17        loop
 18            p( l_page(i) );
 19                    exit when ( i = 3 );
 20        end loop;
 21*   end;
archgn@AWQ8> /
declare
*
ERROR at line 1:
ORA-29273: HTTP request failed
ORA-06512: at "SYS.UTL_HTTP", line 1060
ORA-29261: bad argument
ORA-06512: at line 6

          Followup:

I don't have a proxy server that requires authentication, so I cannot debug this
for you.  but you are not doing it properly.  request and request_pieces are the
legacy simplistic apis.

you need to read the examples in the supplied packages guide that show you how
to use the more sophisticated apis.  you need a begin_request call, set_* calls,
get_response, read_line calls, end_response.

In fact:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96612/u_http.htm#1012345
shows you verbaitim.

      Rating: 5
dumping url
      pages  December 05, 2003
Reviewer:
      A reader
Hi I have a requirement as follows:
1. Access a URL which requires me to enter user name
   and password
2. Go to a particular set of pages that basically generates
a report dynamically
3. Dump the report into a directory for later viewing.

In your expert one on one, in utl_file, you have a
simple utility that does it.
I already have this application in a shell script
where I use wget but I am thinking that writing it
in pl/sql may simplify it - there is whole
lot of logic related to grabbing the session id
in shell script for reusing the same session as I go
to different urls).
As part of the requirement, I need
to also download some static images from the same
web application.

Can i Use utl_http for this? How would you do it?

thanx!!

          Followup:

in 9i you could, possibly.  the images will be problematic (binary files).

i don't think it'd be any easier actually.  not in this case.

Not unless you can "skip the web" altogether and just run the htp procedure and
use my "dump_page" utility to save it out.

      Rating: 5
thanx
      tom!!  December 05, 2003
Reviewer:
      A reader

December 06, 2003 Vishal -- Thanks for the question regarding "what
      is TWO_TASK", version

      You Asked
what is the use of TWO_TASK environment variable ?
where can we use it ?

      and we said...
If you are in a Unix environment, you can normally set 2
environment variables:

ORACLE_HOME
ORACLE_SID

that will connect you to a local database identified by that
home and sid.  If you wanted to be able to connect to a remote
database or a database on that machine via SQLNet, you can also
set:

TWO_TASK = <tnsconnect string>

The setting of TWO_TASK overrides the ORACLE_SID when set.  You
will not connecting to a local database with two_task but rather
using sqlnet to connect to a remote database.

So, instead of:

$ sqlplus scott/tiger@some_db

I can:

$ setenv TWO_TASK some_db
$ sqlplus scott/tiger

and the @some_db is implied.

Review &
      Followup

      Rating: 5
connect
      string  December 05, 2003
Reviewer:  Reader

Tom, In the above example, some_db should have been resolved using tnsnames.ora
on the box from which we connect to the remote database. Is it correct? Thanks.

          Followup:

yes, the "local machine"

it could come from a variety of sources on that machine

~/.tnsnames.ora
$ORACLE_HOME/network/admin/tnsnames.ora
/var/opt/oracle/tnsnames.ora
$TNS_ADMIN/tnsnames.ora

for example

      Rating:
      5
tnsnames_production.serv_name  December 05, 2003

Reviewer:  mike
I have inherited:
Sun Solaris, Oracle8174.
Under $ORACLE_HOME/network/admin/
there are tnsnames.ora and the tnsnames_production.server_name. the server_name
is another server machine. I have found Oracle also uses
"tnsnames_production.server_name"
How this could happen?

Thanks

Mike

          Followup:

does your tnsnames.ora have an ifile command in it?

      Rating: 5
That's it!
      thanks!  December 06, 2003
Reviewer:
      A reader

December 06, 2003 Leonardo -- Thanks for the question regarding
      "Sending e-mail! -- Oracle 8i specific response", version 8.1.5

      You Asked
How to send personalized email to clients registered in my portal
www.intrainternet.com using the information stored in our Database Oracle 8i
automatically?

      and we said...
This is a sample chapter from my book Expert One on One Oracle -- it describes
how to send email from Oracle versions 816 and higher (the java only approach
described about halfway through works in 815 and higher)

UTL_SMTP and sending mail

UTL_SMTP, introduced for the first time in Oracle 8.1.6, is an interface to the
Simple Mail Transfer Protocol. It requires that you have an SMTP server in your
network somewhere ? most sites I have been to have at least one SMTP server
running as it is the most popular method for sending mail.

The UTL_SMTP package is best suited for sending small, text only emails from the
database. While its API supports the sending of attachments and everything else
? it is left to you to actually encode the multi-part document ? for example
turning binary attachments into mime-encoded documents.

We've already seen how to use UTL_SMTP in the DBMS_JOB section where we made it
'apparently' execute faster by doing it asynchronously. In this section we?ll
revisit that example, build upon it ? adding additional functionality. We will
also look at an alternative to UTL_SMTP that provides somewhat much more
functionality ? including the ability to easily send attachments with the email.
Since SMTP is a very low level protocol, we'll reuse existing public domain code
to get an SMTP interface at much higher level ? and we'll get it with very
little code.

UTL_SMTP ? a larger example

In the DBMS_JOB section, we explored how to make sending email using UTL_SMTP
appear to execute faster.  We also made email transactional in nature in that
section ? if you rollback the email does not get sent, if you commit ? out it
goes.  I highly recommend the use of DBMS_JOB as a layer on your emails routines
for these reasons.  In that section, the example UTL_SMTP routine we used was:

tkyte@TKYTE816> create or replace
  2  PROCEDURE send_mail (p_sender       IN VARCHAR2,
  3                       p_recipient IN VARCHAR2,
  4                       p_message   IN VARCHAR2)
  5  as
  6     l_mailhost VARCHAR2(255) := 'aria.us.oracle.com';
  7     l_mail_conn utl_smtp.connection;
  8  BEGIN
  9     l_mail_conn := utl_smtp.open_connection(l_mailhost, 25);
 10     utl_smtp.helo(l_mail_conn, l_mailhost);
 11     utl_smtp.mail(l_mail_conn, p_sender);
 12     utl_smtp.rcpt(l_mail_conn, p_recipient);
 13     utl_smtp.open_data(l_mail_conn );
 14     utl_smtp.write_data(l_mail_conn, p_message);
 15     utl_smtp.close_data(l_mail_conn );
 16     utl_smtp.quit(l_mail_conn);
 17  end;
 18  /
Procedure created.

tkyte@TKYTE816> begin
  2          send_mail( 'thomas.kyte@oracle.com',
  3                     'tkyte@oracle.com',
  4                     'Hello Tom' );
  5  end;
  6  /

PL/SQL procedure successfully completed.

That works OK but is very limited in nature.  It sends email to exactly one
recipient, you cannot CC or BCC anyone, you cannot setup a subject -- the email
always arrives with a ?blank? subject line.  We would like to support more
options with this package.

A full discussion of all of the possibilities with UTL_SMTP would require in
depth knowledge of the SMTP protocol itself ? something that is outside the
scope of this book.  Readers interested in all of the opportunities available
with SMTP should review RFC812 ? which is the description of SMTP.  This is
available online at
http://www.faqs.org/rfcs/rfc821.html
 Below, I will simply present how to send an email
using UTL_SMTP that supports:

o Multiple ?to? recipients
o Multiple ?cc? recipients
o Multiple ?bcc? recipients
o A single body of up to 32k in size
o A subject line
o A descriptive ?from? line (instead of showing just the email address as the
?from? in the email client)

A specification for a PLSQL package that supports this might look like the
following.  In here, we define an array type to allow for a caller to easily
send a list of recipients as well as provide the external specification of the
PLSQL routine we will be implementing:

tkyte@TKYTE816> create or replace package mail_pkg
  2  as
  3          type array is table of varchar2(255);
  4
  5          procedure send( p_sender_email in varchar2,
  6                          p_from         in varchar2,
  7                          p_to           in array default array(),
  8                          p_cc           in array default array(),
  9                          p_bcc          in array default array(),
 10                          p_subject      in varchar2,
 11                          p_body         in long );
 12  end;
 13  /
Package created.

The package body for this implementation is relatively straightforward ? if
understand just enough of the SMTP protocol and what an email looks like (how
email clients get the From, To, CC and so on).  Before we look at the code,
we?ll look at what an email might actually look like. Consider the following
ASCII text:

Date: 13 May 01 12:33:22
From: Oracle Database Account <tkyte@us.oracle.com>
Subject: This is a subject
To: tkyte@us.oracle.com, thomas.kyte@oracle.com
Cc: tkyte@oracle.com

Hello Tom, this is the mail you need

That is what you would transmit as the body of the email using UTL_SMTP to have
the email client set the From, Subject, and so on.  There are no SMTP commands
to do that magic, rather, this header information is placed right in the body of
the email itself ? separated from the text of the email by a blank line.  Once
we understand that ? sending an email with all of the options we need is pretty
easy.  The only thing we need to understand beyond that is that in order to send
the email to more then one recipient, we simply call UTL_SMTP.RCPT more then
once ? with different names.  That?s all of the information we need to know then
to send an email.

So, here is the package body.  We start with a couple of constants and globals.
You will of course need to change the g_mailhost to be the name of a server you
have access to, Aria is my machine inside of Oracle ? you will not be able to
access that:

tkyte@TKYTE816> create or replace package body mail_pkg
  2  as
  3
  4  g_crlf        char(2) default chr(13)||chr(10);
  5  g_mail_conn   utl_smtp.connection;
  6  g_mailhost    varchar2(255) := 'aria.us.oracle.com';
  7

Next we have an internal (unpublished) function to send an email to many
recipients ? it in effect addresses the email.  At the same time, it builds the
?To:? or ?Cc:? lines that we?ll eventually send as part of the email itself and
returns that formatted string.  It was implemented as a separate function since
we need to do this separately for the To, CC, and BCC lists:

  8  function address_email( p_string in varchar2,
  9                          p_recipients in array ) return varchar2
 10  is
 11      l_recipients long;
 12  begin
 13     for i in 1 .. p_recipients.count
 14     loop
 15        utl_smtp.rcpt(g_mail_conn, p_recipients(i) );
 16        if ( l_recipients is null )
 17        then
 18            l_recipients := p_string || p_recipients(i) ;
 19        else
 20            l_recipients := l_recipients || ', ' || p_recipients(i);
 21        end if;
 22     end loop;
 23     return l_recipients;
 24  end;
 25
 26

Now we have the implementation of our published function ? the one people will
actually call to send mail.  It starts with an internal procedure writeData that
is used to simplify the sending of the email headers (the To:, From:, Subject:
records).  If the header record is NOT NULL, this routine will use the
appropriate UTL_SMTP call to send it ? along with the necessary end of line
marker (the carriage return/line feed):

 27  procedure send( p_sender_email in varchar2,
 28                  p_from         in varchar2 default NULL,
 29                  p_to           in array default array(),
 30                  p_cc           in array default array(),
 31                  p_bcc          in array default array(),
 32                  p_subject      in varchar2 default NULL,
 33                  p_body         in long  default NULL )
 34  is
 35      l_to_list   long;
 36      l_cc_list   long;
 37      l_bcc_list  long;
 38      l_date      varchar2(255) default
 39                  to_char( SYSDATE, 'dd Mon yy hh24:mi:ss' );
 40
 41      procedure writeData( p_text in varchar2 )
 42      as
 43      begin
 44          if ( p_text is not null )
 45          then
 46              utl_smtp.write_data( g_mail_conn, p_text || g_crlf );
 47          end if;
 48      end;

Now we are ready to actually send the mai.  This part is not very different from
the very simple routine we started with.  It begins in exactly the same fashion
? by connecting to the SMTP server and starting a session:

 49  begin
 50      g_mail_conn := utl_smtp.open_connection(g_mailhost, 25);
 51
 52      utl_smtp.helo(g_mail_conn, g_mailhost);
 53      utl_smtp.mail(g_mail_conn, p_sender_email);
 54

Here is where it differs, instead of calling UTL_SMTP.RCPT once ? it uses are
address_email function to call it potentially many times, building the ?To:? and
?Cc:? list for us as well.  It builds the BCC list but we won?t actually send
that (we don?t want the recipients to see that list!)

 55      l_to_list  := address_email( 'To: ', p_to );
 56      l_cc_list  := address_email( 'Cc: ', p_cc );
 57      l_bcc_list := address_email( 'Bcc: ', p_bcc );
 58

Now, we use the OPEN_DATA call to start sending the body of the email.  The code
on lines 61 through 68 generates the header section of data.  Line 69 sends the
body of the email (the contents of the email) and line 70 terminates the email
for us.

 59      utl_smtp.open_data(g_mail_conn );
 60
 61      writeData( 'Date: ' || l_date );
 62      writeData( 'From: ' || nvl( p_from, p_sender_email ) );
 63      writeData( 'Subject: ' || nvl( p_subject, '(no subject)' ) );
 64
 65      writeData( l_to_list );
 66      writeData( l_cc_list );
 67
 68      utl_smtp.write_data( g_mail_conn, '' || g_crlf );
 69      utl_smtp.write_data(g_mail_conn, p_body );
 70      utl_smtp.close_data(g_mail_conn );
 71      utl_smtp.quit(g_mail_conn);
 72  end;
 73
 74
 75  end;
 76  /
Package body created.

And that?s it, now since I have many email addresses (tkyte@us.oracle.com,
tkyte@oracle.com, thomas.kyte@oracle.com, utility@aria.us.oracle.com) I can test
this API like this:

tkyte@TKYTE816> begin
  2      mail_pkg.send
  3      ( p_sender_email => 'tkyte@us.oracle.com',
  4        p_from => 'Oracle Database Account <tkyte@us.oracle.com>',
  5        p_to => mail_pkg.array( 'tkyte@us.oracle.com',
                                           'thomas.kyte@oracle.com' ),
  6        p_cc => mail_pkg.array( 'tkyte@oracle.com' ),
  7        p_bcc => mail_pkg.array( 'utility@aria.us.oracle.com' ),
  8        p_subject => 'This is a subject',
  9        p_body => 'Hello Tom, this is the mail you need' );
 10  end;
 11  /

PL/SQL procedure successfully completed.

And that call is exactly what generated the ASCII text:

Date: 13 May 01 12:33:22
From: Oracle Database Account <tkyte@us.oracle.com>
Subject: This is a subject
To: tkyte@us.oracle.com, thomas.kyte@oracle.com
Cc: tkyte@oracle.com

Hello Tom, this is the mail you need

We saw above ? that is what got sent to all of those recipients ? including
utility@aria.us.oracle.com, although we cannot see that recipient since it was
bcc?ed.

That covers most of the typical uses of the UTL_SMTP supplied package.  Above I
did say it is capable of sending email with attachments and such but that would
require an inordinate amount of effort on our part.  We would have to:

o Learn how to format a multi-part mime encoded document, no small feat
o Base-64 encode binary data (or use some equivalent encoding technique such as
uuencoding, binhex, and so on)

That would be (conservatively) a couple of hundred, if not thousands of lines of
PL/SQL code.  Rather then do that, I will suggest that you use the already
written and very robust JavaMail API as described below.

Loading and using the JavaMail API

In order to use the UTL_SMTP package, you must already have a Java enabled
database in Oracle8i.  This is because UTL_SMTP relies on UTL_TCP and UTL_TCP in
turn is built on Java functions. (Remember, if you don't have a Java enabled
database you can use UTL_HTTP as described above to send simple emails). So, if
you are able to use UTL_SMTP, you must have a Java enabled database, we can go
to the Sun website and download their JavaMail API. This will give us the
ability to send much more complicated emails from the database; including
attachments. The following is based on work performed by a coworker of mine,
Mark Piermarini who helps me out with lots of my Java issues.

If you go to
http://java.sun.com/products/javamail/index.html,
you'll be able to download their JavaMail API. The download
you get will consist of a couple of hundred files; only one of which we are
interested in. After you download the JavaMail API ? make sure also to get their
the JavaBeansTM Activation Framework extension or JAF (javax.activation). This
is needed to run the JavaMail API package.

After you have downloaded these two sets of files ? you will need to extract
mail.jar from the JavaMail APIdownload and activation.jar from the JAF download.
This is all you will need from this ? feel free to read through the
documentation, there is a lot of functionality in there we are not using, we are
just using the 'send an email' part of the API. The API includes functions for
receiving mail as well from IMAP, POP, and other sources.

We will need to load the mail.jar and activation.jar into the database using
loadjava but before we can do that we must repackage them. These jar files are
compressed in a format that is not understood by the database byte code
interpreter. You need to 'unjar' and 'rejar' them without compression or use a
tool such as WinZip to 'rejar' them into a zip file. What I did on Windows 2000
was:

1. Used WinZip to extract the contents of mail.jar into my c:\temp\mail
directory
2. Used WinZip to create a new archive c:\temp\mail8i.zip
3. Put the contents of c:\temp\mail\*.* including subdirectories into this new
archive

I did the same thing for activation.jar ? only replacing mail with activation in
the above steps. Now we are ready to load these zip (or jar files, whatever you
named them) into the database. These need to be loaded using the SYS user since
they have 'protected' Java packages that regular users cannot upload. We will
use the commands:

loadjava -u sys/manager -o -r -v -f -noverify -synonym -g public mail8i.zip
loadjava -u sys/manager -o -r -v -f -noverify -synonym -g public
activation8i.zip

Where:

o -u sys/manager: is the userid and password for your SYS account.  As stated
previously, some of the packages are protected and must be loaded as SYS
o -o: is shorthand for ?oci8, I am using the oci8 driver. You could use the thin
driver as well but you'll need to modify the command to do so
o -r: is short for ?resolve. This will resolve all external references in the
loaded classes helping to verify that the loaded java classes will be able to
function after we load them
o -v: is short for ?verbose.  This gives us something to do while loadjava is
running. We can see it work through each step of its process.
o -f: is short for ?force. This isn't necessary on the first load but is OK to
use. If you try a loadjava and hit an error, you can correct it, and reload ?
then you would either need to use the dropjava command to drop the jar file from
the database or use ?force. Using ?force just makes it easier for us.
o -noverify: does not attempt to verify the bytecode. You must be granted
oracle.aurora.security.JServerPermission(Verifier) to execute this option. In
addition, this option must be used in conjunction with -r. SYS has this
privilege. This is needed because the bytecode verifier will flag some issues
with the mail.jar file and this works around that issue.
o -synonym: creates public synonyms for these classes. Since we will not install
the mail java code we write as SYS, this allows us to 'see' the SYS loaded java
classes.
o -g public: grants execute on these loaded classes to PUBLIC. If this is not
desirable, change the ?g to be just the user you want to create the 'send mail'
routines in, for example -g UTILITY_ACCT.
You can find out more about loadjava and the above options in the Oracle8I Java
Developers Guide.

After these packages are loaded, we are ready to create a Java stored procedure
to actually send the mail. This procedure will act as a thin layer on top of the
JavaMail API and will let us ultimately write a PL/SQL binding layer with the
following spec:

tkyte@TKYTE816> desc send
FUNCTION send RETURNS NUMBER
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 P_FROM                         VARCHAR2                IN
 P_TO                           VARCHAR2                IN
 P_CC                           VARCHAR2                IN
 P_BCC                          VARCHAR2                IN
 P_SUBJECT                      VARCHAR2                IN
 P_BODY                         VARCHAR2                IN
 P_SMTP_HOST                    VARCHAR2                IN
 P_ATTACHMENT_DATA              BLOB                    IN
 P_ATTACHMENT_TYPE              VARCHAR2                IN
 P_ATTACHMENT_FILE_NAME         VARCHAR2                IN

This function will give us the ability to use CC's and BCC's and send an
attachment. It is left as an exercise for the reader to implement passing arrays
of BLOBs or overloading this to support CLOB or BFILE types for attachments as
well.

The Java stored procedure we will create follows. It uses the basic
functionality of the JavaMail API class and is relatively straightforward.
Again, we are not going into all of the uses of the JavaMail API (that could be
a book in itself), just the basics here.  The ?mail? class below has a single
method ?send?.  This is the method we will use to send a message.  As it is
implemented, it returns the number 1 if it is successful in sending the meial
and a 0 otherwise.  This implementation is very basic ? it could be much more
sophisticated, providing support for many attachment types (CLOBS, BFILES, LONGS
and so on).  It could also be modified to report back to the caller the exact
error received from SMTP such as ?invalid recipient, no transport, and so on.

tkyte@TKYTE816> create or replace and compile
  2  java source named "mail"
  3  as
  4  import java.io.*;
  5  import java.sql.*;
  6  import java.util.Properties;
  7  import java.util.Date;
  8  import javax.activation.*;
  9  import javax.mail.*;
 10  import javax.mail.internet.*;
 11  import oracle.jdbc.driver.*;
 12  import oracle.sql.*;
 13
 14  public class mail
 15  {
 16    static String dftMime = "application/octet-stream";
 17    static String dftName = "filename.dat";
 18
 19    public static oracle.sql.NUMBER
 20                         send(String from,
 21                              String to,
 22                              String cc,
 23                              String bcc,
 24                              String subject,
 25                              String body,
 26                              String SMTPHost,
 27                              oracle.sql.BLOB attachmentData,
 28                              String attachmentType,
 29                              String attachmentFileName)

The above argument list matches up with the SQL call specification we outlined
above ? the arguments are mostly self explanatory.  The two that might need some
clarification are the attachmentType and the attachmentFileName.  The
attachmentType should be a MIME (Multi-purpose Internet Mail Extensions) type ?
as you might be familiar with from HTML documents.  The MIME type of a GIF image
for example is ?image/gif?, the mime type of a plain text document would be
?text/plain?, an HTML attachment would be ?text/html? and so on.  The
attachmentFileName in this example is NOT the name of an existing OS file that
would be attached but rather the filename of the attachment in the email itself
? what the recipient of this email will see the name of the attachment as.  The
actual attachment is the oracle.sql.BLOB that is sent to this routine.  Now,
onto the body of the code.  We begin by setting the session property
?mail.smtp.host? to the name of the SMTP host the caller sent to us ? the
JavaMail API reads this value when deciding what SMTP server to connect to:

 30    {
 31      int rc = 0;
 32
 33      try
 34      {
 35        Properties props = System.getProperties();
 36        props.put("mail.smtp.host", SMTPHost);
 37        Message msg =
 38          new MimeMessage(Session.getDefaultInstance(props, null));
 39

Next, we set up the email headers.  This part tells the JavaMail API who the
message if from, who to send it to, who to send a ?carbon copy? (cc) or ?blind
carbon copy? (bcc), what the subject of the email is and what date should be
associated with the email:

 40       msg.setFrom(new InternetAddress(from));
 41
 42       if (to != null && to.length() > 0)
 43          msg.setRecipients(Message.RecipientType.TO,
 44                            InternetAddress.parse(to, false));
 45
 46       if (cc != null && cc.length() > 0)
 47          msg.setRecipients(Message.RecipientType.CC,
 48                            InternetAddress.parse(cc, false));
 49
 50       if (bcc != null && bcc.length() > 0)
 51          msg.setRecipients(Message.RecipientType.BCC,
 52                            InternetAddress.parse(bcc, false));
 53
 54       if ( subject != null && subject.length() > 0 )
 55            msg.setSubject(subject);
 56       else msg.setSubject("(no subject)");
 57
 58        msg.setSentDate(new Date());
 59

Next, we use one of two methods to send an email.  If the attachmentData
argument is not null, then we will MIME encode the email ? a standard that
supports the sending of attachments and other multi-part documents.  We do this
by setting up multiple MIME body parts ? in this case two of them, one for the
body of the email (the text) and the other for the attachment itself.  Lines 76
through 78 need a little additional explanation.  They are how we can send an
email via a BLOB.  The JavaMail API doesn?t understand the oracle.sql.BLOB type
natively (it is after all a generic API).  In order to send the BLOB attachment,
we must provide a method for the JavaMail API to get at the BLOB data.  We
accomplish that by creating our own DataHandler ? a class with an interface that
the JavaMail API understands how to call in order to get data to populate the
attachment.  This class (BLOBDataHandler) is implemented by us as a nested class
below.

 60        if (attachmentData != null)
 61        {
 62          MimeBodyPart mbp1 = new MimeBodyPart();
 63          mbp1.setText((body != null ? body : ""));
 64          mbp1.setDisposition(Part.INLINE);
 65
 66          MimeBodyPart mbp2 = new MimeBodyPart();
 67          String type =
 68              (attachmentType != null ? attachmentType : dftMime);
 69
 70          String fileName = (attachmentFileName != null ?
 71                             attachmentFileName : dftName);
 72
 73          mbp2.setDisposition(Part.ATTACHMENT);
 74          mbp2.setFileName(fileName);
 75
 76          mbp2.setDataHandler(new
 77             DataHandler(new BLOBDataSource(attachmentData, type))
 78          );
 79
 80          MimeMultipart mp = new MimeMultipart();
 81          mp.addBodyPart(mbp1);
 82          mp.addBodyPart(mbp2);
 83          msg.setContent(mp);
 84        }

If the email does not have an attachment ? setting the body of the email is
accomplished very simply via the single call to setText:

 85        else
 86        {
 87          msg.setText((body != null ? body : ""));
 88        }
 89        Transport.send(msg);
 90        rc = 1;
 91      } catch (Exception e)
 92      {
 93        e.printStackTrace();
 94        rc = 0;
 95      } finally
 96      {
 97        return new oracle.sql.NUMBER(rc);
 98      }
 99    }
100

Now for our nested class BLOBDataSource.  It simply provides a generic interface
for the JavaMail API to access our oracle.sql.BLOB type.  It is very
straightforward in its implementation:

101    // Nested class that implements a DataSource.
102    static class BLOBDataSource implements DataSource
103    {
104      private BLOB   data;
105      private String type;
106
107      BLOBDataSource(BLOB data, String type)
108      {
109          this.type = type;
110          this.data = data;
111      }
112
113      public InputStream getInputStream() throws IOException
114      {
115        try
116        {
117          if(data == null)
118            throw new IOException("No data.");
119
120          return data.getBinaryStream();
121        } catch(SQLException e)
122        {
123          throw new
124          IOException("Cannot get binary input stream from BLOB.");
125        }
126      }
127
128      public OutputStream getOutputStream() throws IOException
129      {
130        throw new IOException("Cannot do this.");
131      }
132
133      public String getContentType()
134      {
135        return type;
136      }
137
138      public String getName()
139      {
140        return "BLOBDataSource";
141      }
142    }
143  }
144  /

Java created.

Now that we have the Java class created for PL/SQL to bind to, we need to create
that binding routine to map the PL/SQL types to their Java Types and to bind the
PL/SQL routine to this Java class. That is simply:

tkyte@TKYTE816> create or replace function send(
  2      p_from                  in varchar2,
  3      p_to                    in varchar2,
  4      p_cc                    in varchar2,
  5      p_bcc                   in varchar2,
  6      p_subject               in varchar2,
  7      p_body                  in varchar2,
  8      p_smtp_host             in varchar2,
  9      p_attachment_data       in blob,
 10      p_attachment_type       in varchar2,
 11      p_attachment_file_name  in varchar2) return number
 12  as
 13  language java name 'mail.send( java.lang.String,
 14                                 java.lang.String,
 15                                 java.lang.String,
 16                                 java.lang.String,
 17                                 java.lang.String,
 18                                 java.lang.String,
 19                                 java.lang.String,
 20                                 oracle.sql.BLOB,
 21                                 java.lang.String,
 22                                 java.lang.String
 23                               ) return oracle.sql.NUMBER';
 24  /

Function created.

Now, the very last thing we must do before using this is to ensure our user (the
owner of the above "mail" class and "send" stored procedure) has sufficient
privileges to execute the routine. Those would be the following:

sys@TKYTE816> begin
  2    dbms_java.grant_permission(
  3       grantee => 'TKYTE',
  4       permission_type => 'java.util.PropertyPermission',
  5       permission_name => '*',
  6       permission_action => 'read,write'
  7    );
  8    dbms_java.grant_permission(
  9       grantee => 'TKYTE',
 10       permission_type => 'java.net.SocketPermission',
 11       permission_name => '*',
 12       permission_action => 'connect,resolve'
 13    );
 14  end;
 15  /

PL/SQL procedure successfully completed.

Note that in the grant on java.net.SocketPermission, I used a wildcard in the
permission_name. This allows TKYTE to connect to and resolve ANY host.
Technically, we could put in there just the name of the SMTP sever we will be
using.  That would be the minimal grant we needed. This is needed in order to
resolve the hostname of our SMTP host and then connect to it.  The other
permission, java.util.PropertyPermission, is needed in order to set the
mail.smtp.host in our sessions properties.

Now we are ready to test. I reused some code from the DBMS_LOB section where we
had a routine "load_a_file". I modified that and the DEMO table to have a BLOB
column instead of a CLOB and loaded the mail8i.zip file we loaded in as a class
into this demo table. Now I can use the following PL/SQL block to send it to
myself as an attachment in an email from the database:

tkyte@TKYTE816> set serveroutput on size 1000000
tkyte@TKYTE816> exec dbms_java.set_output( 1000000 )

tkyte@TKYTE816> declare
  2    ret_code number;
  3  begin
  4    for i in (select theBlob from demo )
  5    loop
  6      ret_code := send(
  7                    p_from => 'tkyte@oracle.com',
  8                    p_to => 'tkyte@oracle.com',
  9                    p_cc => NULL,
 10                    p_bcc => NULL,
 11                    p_subject => 'Use the attached Zip file',
 12                    p_body => 'to send email with attachments....',
 13                    p_smtp_host => 'aria.us.oracle.com',
 14                    p_attachment_data => i.theBlob,
 15                    p_attachment_type => 'application/winzip',
 16                    p_attachment_file_name => 'mail8i.zip');
 17      if ret_code = 1 then
 18        dbms_output.put_line ('Successfully sent message...');
 19      else
 20        dbms_output.put_line ('Failed to send message...');
 21      end if;
 22    end loop;
 23  end;
 24  /
Successfully sent message...

PL/SQL procedure successfully completed.

You definitely want to set serverouput on and call the DBMS_JAVA.SET_OUTPUT
routine on when testing this. This is because the exception is being printed by
the Java stored procedure to System.out and by default that will go into a trace
file on the server. If you want to see any errors in your SQLPlus session, you
need to make these two settings. It will be very useful for debugging purposes!

Summary

In this section, we briefly reviewed the existing UTL_SMTP package.  Here we saw
how to send emails to multiple recipients with a custom From: and Subject:
header.  That should satisfy most peoples needs for sending email from the
database. UTL_SMTP is good for sending simple text only emails but sending
attachments or complex emails is beyond its capabilities (unless you want to
encode the entire email yourself).  In the cases where you need that additional
sophistication, we looked at how to use the JavaMail API. Since Sun has
graciously supplied us with all of the logic we would need to do that ? we'll
just reuse their code. This section has demonstrated not only how to send mail
but a powerful side effect of having Java as an alternative stored procedure
language. The entire set of public domain code and class libraries out there is
now yours for the using. We can enable the database to do many things that were
not previously possible. In fact, the PL/SQL developers at Oracle used the same
technique themselves. UTL_TCP is built on Java itself in Oracle8i.

Review &
      Followup

      Rating: 5
Very Good but a
      small doubt  March 29, 2001
Reviewer:
      Srinivas  from India(Secunderabad)
How do i send the mails thru oracle 7.1 database as ourt dataBase is not Oracle
8. can u plz send me ur suggestion...will the same solution work for Oracle
7.1???

      Rating: 4
Great Solution
      for Email sending from within Database  April 09, 2001

Reviewer:  Bijay R. Tuladhar  from Hayward, CA

This is one of the most useful solutions.
Thank you Tom for helping us all!

      Rating:
      5
E-Mail  April 12, 2001
Reviewer:  Gopal
      KVS  from Secunderabad India
Excelent, but sending mails has been discussed, how about receiving mails.

      Rating:
      4
  June 11, 2001
Reviewer:  Bhagat
      Singh  from India

      Rating: 4
jar
      files  July 09, 2001
Reviewer:  Shawn
       from Toronto, ON,Canada
I tried to un-jar them and re-jar the files.  while using loadjava I am getting
a java.util exception.
Any suggestions?  Or can you send me the re-jar'ed files and tell me where to
get them.

Thanks,

      Rating: 4
I run until
      step 3, error occurs, can not continue...  July 12, 2001

Reviewer:  yong@airmedia.com  from USA
The error message is :
$ loadjava -u user/paswd@db -o -r -f -v mail.java
initialization complete
loading  : mail
creating : mail
resolver :
resolving: mail
errors   :  mail
    ORA-29535: source requires recompilation
    mail:31: Class Message not found.
    mail:31: Class MimeMessage not found.
    mail:37: Variable msg may not have been initialized.
    mail:39: Variable msg may not have been initialized.
    mail:41: Variable msg may not have been initialized.
    mail:44: Undefined variable or class name: Transport
     Info: 6 errors
loadjava: 8 errors

Can anyone help me out? Thanks in advance.

      Rating:
      4
  July 24, 2001
Reviewer:  Rajesh
      Jaswal  from Hoshiarpur, Punjab
Very good solution.

      Rating: 5
Excellently
      simplified solution  July 26, 2001
Reviewer:
      Akthar & Kamalanathan - akthar.aj@ebridgex.com  from
      Singapore
Neatly done with correct step, We would like to comment(checklist) something on
positive sense ,
1. First to start with Check whether JVM for Oracle is installed, else run
$oracle_home/javavm/install/initjvm.sql
2.Set(add) classpath $oracle_home/sqlj/lib/translator.zip
3.Follow the steps 1,2,3,4 given in the TOM's response with only change on step
4 you have to "desc send" not "desc mail". Step 5 make changes to suit your SMTP
HOST.
4. You are done ...

Wish u Happy Oracle Mailing on 8.1.5.

      Rating:
      2
Mail-trouble  October 25, 2001
Reviewer:
      Vera  from Russia
Don't you think that in the sentence loadjava parameter -s (synonym) for
activation8i.jar and mail8i.jar elso must be present ?

      Rating:
      3
  December 28, 2001
Reviewer:  Kiro

It is good but how to attach file to my e-mail?

      Rating: 4
Having problem
      in stage 3  January 26, 2002
Reviewer:  Kazi
      Nazmul Hassan  from Bangladesh
Dears,
When running on step 3 i got the following errors.
Can anyone help me out of it?

D:\mail>loadjava -u marchant/marchant@itcmis -o -r -f -v mail.java
arguments: '-u' 'marchant/marchant@itcmis' '-o' '-r' '-f' '-v' 'mail.java'
creating : source mail
loading  : source mail
creating : mail
resolving: source mail
errors   : source mail
    ORA-29535: source requires recompilation
    mail:31: Class Message not found.
    mail:31: Class MimeMessage not found.
    mail:37: Variable msg may not have been initialized.
    mail:39: Variable msg may not have been initialized.
    mail:41: Variable msg may not have been initialized.
    mail:44: Undefined variable or class name: Transport
     Info: 6 errors
The following operations failed
    source mail: resolution
exiting  : Failures occurred during processing

Thanks

          Followup:

use NOVERIFY as well.

On Januarary 27th, 2002 -- I totally rewrote the answer to this.  The answer
above is now an extract from my book which has better step by steps.....  Follow
those.

      Rating: 4
getting
      error  January 27, 2002
Reviewer:  Arindam
      Kar  from Midlle East
HI all

While loading Jar files I am getting the following error, can any body tell me
why??
 loadjava -u sys/change_on_install -o -r -
v -g public activation8i.jar java.lang.NoClassDefFoundError:
oracle/jdbc/driver/OracleDriver
        at oracle.aurora.server.tools.LoadJava.getConnection(LoadJava.java:526)
        at oracle.aurora.server.tools.LoadJava.fileIsDifferent(LoadJava.java:442
)
        at oracle.aurora.server.tools.LoadJava.loadAndCreateJar(LoadJava.java:93
1)
        at oracle.aurora.server.tools.LoadJava.processLoadAndCreate(LoadJava.jav
a:1149)
        at oracle.aurora.server.tools.LoadJava.process(LoadJava.java:1021)
        at oracle.aurora.server.tools.LoadJavaMain.run(LoadJavaMain.java:193)
        at oracle.aurora.server.tools.LoadJavaMain.main(LoadJavaMain.java:49)

      Rating: 3
Can I send
      email from D2K?  January 28, 2002
Reviewer:
      Sazzadur Rahman Tusar  from Bangladesh
I tried to send email from D2K but it failed.
I used OLE2.OBJ_TYPE as per your writings earlier, but it shows error, because
OBJ_TYPE is not a procedure.
How it will be solved?
Would you please give me a solution?

      Rating: 4
I have
      something missing...??  January 31, 2002
Reviewer:
      Yogeeraj  from Mauritius
hi,
I tried the first example, it does not seem to work. Any Idea?
thanks in advance
Yogeeraj
========================================
SQL> begin
send_mail( 'ydegambur@cmt.mu',
'ydegambur@cmt.mu',
'Hello Deg' );
end;
/  2    3    4    5    6
begin
*
ERROR at line 1:
ORA-20001: 421 Service not available
ORA-06512: at "SYS.UTL_SMTP", line 83
ORA-06512: at "SYS.UTL_SMTP", line 121
ORA-06512: at "SW.SEND_MAIL", line 8
ORA-06512: at line 2

SQL>
==============================================

          Followup:

you did not use a valid mail host in your send_mail routine.  You are getting an
error back from the "smtp" server (which isn't an smtp server) saying "i'm not
available"

you did change:

  5  as
  6     l_mailhost VARCHAR2(255) := 'aria.us.oracle.com';
  7     l_mail_conn utl_smtp.connection;
  8  BEGIN

to have a hostname that would be valid for you right?

      Rating: 5
Great it
      works!!  February 01, 2002
Reviewer:
      Yogeeraj  from Mauritius
Hi,

thanks for locating the error for me. Indeed, there was an error in the address.

sorry for any inconveniences.
;)

For sure, this will help me proceed in my implementations and research.

Best Regards
Yogeeraj

      Rating: 5
TOM you are
      simply GREAT..!!!!!  February 18, 2002
Reviewer:
      Manoj Bajaj  from Singapore
Tom this mail routine we are using it since last few months and is running and
mailing well. thank u so much.

I have made some changes to this routine to send multiple files / attachments
..ofcourse by not passing blob array ..(as did not know I to do that ..!!!) but
by reading files in java program..! I would like to send u the source.

 Mean I would also like to know one thing what decides total attachment size to
be sent in one mail. In one database sch I can send attachments of 1MB whereas
in other I'm restricted to 6 MB..I'm cannot find what is restricting me from
sending bigger attachments...Any clue...unix or oracle parameter as these two
are on diff  unix servers but SMTP is same.

 Once again Many thanks for ur continuous support.

      Rating: 5
Error
      handling  February 21, 2002
Reviewer:  Sudip
      Sarkar  from India
Hi Thomas,
Your solutions are simply great. I have used your code to send email. But I
facing a peculiar problem. Once there was a problem in the SMTP server and it
stopped responding, now at that moment I fired a mail from the database. The
result was that my sesion got hung.
Is there any way of trapping the error.

      Rating:
      5
  February 25, 2002
Reviewer:  manoj
       from singapore
Hi Check the trace file generated in user_dump_dest ..directory on your server u
may get the exacty nature of problem there.

      Rating: 4
Looks
      promising, but am having problems installing - loadjava
      fails  February 25, 2002
Reviewer:  Bob
      Yexley  from Dayton, OH USA
Thanks so much for this solution.  If I can get it working, it will be a HUGE
help and solution for our needs.

I am trying to follow the instructions laid out here, and did everything they
said to do, but am having problems with the loadjava command for mail8i.zip.
When I ran it, it loaded everything fine, but ran into problems when trying to
resolve referenced objects.  I'm getting ORA-29534, and the result is 45
unresolved object references after the load.  The loading of the
activation8i.zip worked great, no problems at all, but had problems with SOME of
the objects in mail8i.zip.  Here is one example of the errors that I am getting:

==========================================================
skipping : com/sun/mail/imap/Rights$Right is already resolved
skipping : javax/mail/IllegalWriteException is already resolved
skipping : com/sun/mail/iap/Argument is already resolved
resolving: com/sun/mail/smtp/SMTPMessage
errors   :  com/sun/mail/smtp/SMTPMessage
    ORA-29534: referenced object SYS.javax/mail/internet/MimeMessage could not
be resolved
resolving: javax/mail/internet/MimeMessage
errors   :  javax/mail/internet/MimeMessage
    ORA-29521: referenced name javax/activation/DataHandler could not be found
    ORA-29521: referenced name javax/activation/DataSource could not be found
resolving: javax/mail/FolderClosedException
errors   :  javax/mail/FolderClosedException
    ORA-29534: referenced object SYS.javax/mail/Folder could not be resolved
==========================================================

Any idea what's wrong, and/or how I can fix it??

-::YEX::-
<)))><

          Followup:

show me a cut and paste of your loadjava command.

      Rating: 4
Response to
      followup  February 26, 2002
Reviewer:  Bob
      Yexley  from Dayton, OH USA
My loadjava command was as follows:

C:\MyDir>loadjava -u sys/password@orcl -o -r -v -f -noverify -synonym -g public
mail8i.zip

          Followup:

Can you reload both the activation and mail ZIP files using another user? This
will facilitate debugging and we can use it to check the resolver for any
problems. Also, add '-debug' to your loadjava commands and send that output to
mbpierma@us.oracle.com.

      Rating: 4
Some problem!
      can anyone help  February 26, 2002
Reviewer:
      Raza  from Toronto, Canada
I was trying tom's example 1 and have this error! can any one help me out
begin
    mail_pkg.send
    ( p_sender_email => 'rkazmi@rci.rogers.com',
      p_from => 'Oracle Database Account <rkazmi@rci.rogers.com>',
      p_to => mail_pkg.array( 'kazmi81@hotmail.com',
                                      'kazmi@rogers.com' ),
      p_cc => mail_pkg.array( 'kazmi5221@rogers.com' ),
      p_bcc => mail_pkg.array( 'utility@aria.us.oracle.com' ),
      p_subject => 'This is a subject',
      p_body => 'Hello Raza, this is the mail you need' );
end;

ORA-29540: class oracle/plsql/net/TCPConnection does not exist
ORA-06512: at "SYS.UTL_TCP", line 678
ORA-06512: at "SYS.UTL_TCP", line 247
ORA-06512: at "SYS.UTL_SMTP", line 99
ORA-06512: at "SYS.UTL_SMTP", line 121
ORA-06512: at "SYS.MAIL_PKG", line 49
ORA-06512: at line 2

and what needs to be done!!!???

          Followup:

search for

ORA-29540

on this site.

      Rating: 4
Another
      problem!!  February 26, 2002
Reviewer:  Raza
       from Toronto, Canada
After issuing the following command
(RCIS_DEV)$ loadjava -user sys/change_on_install@rcis_dvl plsql.jar

I got this error!!

SQL Error while connecting with oci8 driver to rcis_dvl: ORA-01031: insufficient
 privileges

could not open connection
loadjava: 2 errors

          Followup:

this should not be done over the network -- the file you are loading must be the
one on the server anyway so... don't use the network.

      Rating: 4
Sending e-mail!
      -- Oracle 8i specific response  February 26, 2002

Reviewer:  Harvey  from MO USA
Do we need to do the same things for Oracle9i as you described in this detailed
instruction?  If it is different, could you explain it also in detail?

Thanks a lot!

Harvey

          Followup:

should work in 9i, haven't loaded it up myself as yet but the steps would
basically be the same (you don't have to use sys and probably won't be able to
use sys in most cases in 9i, that would be different)

yes, you would need to do this in 9i if you wanted to send email with
attachments.

      Rating: 5
Implemented the
      SMTP mail sending attachments pure Oracle code  February 26,
      2002
Reviewer:  Ashokkumar Sankaran  from USA

Thx for your Valuable ideas

We have succefully implmented the SMTP mail with any attachments Including
Binary in Pure oracle code

(i .e It Converts Binary to Base 64 format)

Thx
Ashok

      Rating: 4
Problem using
      loadjava  February 27, 2002
Reviewer:
      A reader
I cant get it work!

I have two instances running on the same machine,
I have opened the telnet session on box where my database is,  and trying to
load plsql.jar by issuing the following command.
(RCIS_DEV)$ loadjava -user sys/change_on_install@rcis_dvl plsql.jar
And getting this error..

SQL Error while connecting with oci8 driver to rcis_dvl: ORA-01031: insufficient
 privileges

could not open connection
loadjava: 2 errors

I checked my $ORACLE_SID which is set to rcis_dvl
and if I issue loadjava without @rcis_dvl
(RCIS_DEV)$ loadjava -user sys/change_on_install plsql.jar

Then I get this error!!

SQL Error while connecting with oci8 driver to default database: ORA-01034: ORAC
LE not available
ORA-27101: shared memory realm does not exist
IBM AIX RISC System/6000 Error: 2: No such file or directory

could not open connection
loadjava: 2 errors
What should be done!!! you advice
Thanks in advance
Raza

          Followup:

You should log in as the Oracle software owner.

You should verify your oracle_home and oracle_sid

You should verify that you can then:

sqlplus sys/change_on_install

then run loadjava.

      Rating:
      5
  March 01, 2002
Reviewer:  xue li

Very nice!

But where can i get more information about UTL_SMTP.

          Followup:

all supplied packages are documented in the supplied packages guide

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76936/utl_smtp.htm#1000695
....

      Rating: 5
BCC
      List  April 03, 2002
Reviewer:  Chris Soza
       from London
Tom,

In your pl/sql solution if bcc list is not written out with writeData, can you
explain how it will work ?

Thanks,

Chris

          Followup:

because this:

 15        utl_smtp.rcpt(g_mail_conn, p_recipients(i) );

does the actual sending, the writing of the CC and TO list is only for display
-- the call to writedata with that doesn't affect WHO the email is actually sent
to -- just what the email client displays.

      Rating:
      4
Partly  April 04, 2002
Reviewer:
      Sandeep Pathak  from India
I have followed all the instructions. And they were successfull also. But when I
type the following command

SQL> desc send
ERROR:
ORA-04043: object send does not exist

I get the above said error.
Can anybody help me.
The mail8i and activation8i, are proper. But if anybody has a working version
please send on sandeep@inbox.net

          Followup:

If you run this:

tkyte@TKYTE816> create or replace function send(
  2      p_from                  in varchar2,
  3      p_to                    in varchar2,
  4      p_cc                    in varchar2,
  5      p_bcc                   in varchar2,
  6      p_subject               in varchar2,
  7      p_body                  in varchar2,
  8      p_smtp_host             in varchar2,
  9      p_attachment_data       in blob,
 10      p_attachment_type       in varchar2,
 11      p_attachment_file_name  in varchar2) return number
 12  as
 13  language java name 'mail.send( java.lang.String,
 14                                 java.lang.String,
 15                                 java.lang.String,
 16                                 java.lang.String,
 17                                 java.lang.String,
 18                                 java.lang.String,
 19                                 java.lang.String,
 20                                 oracle.sql.BLOB,
 21                                 java.lang.String,
 22                                 java.lang.String
 23                               ) return oracle.sql.NUMBER';
 24  /

Function created.

(which is above, in the answer), you will not get that response.  Me thinks you
skipped a step or two.

      Rating: 4
mail.jar and
      activation.jar  April 04, 2002
Reviewer:  Chris
      Soza  from England
Tom

I downloaded the javamail package as per your note. However, in the archive
there is no activation.jar file. Any idea how I can get this ?. I downloaded the
1.2 version.

Also, thanks for explaining how bcc list works.

Rgds, Chris

          Followup:

It is separate, goto java.sun.com and search for activation.

      Rating:
      4
error  April 04, 2002
Reviewer:
      nagarju
tom,

i run the following snippet of code, i get the error

declare
  l_mailhost VARCHAR2(255) := 'our mailserver ip';
  l_mail_conn utl_smtp.connection;
begin
  l_mail_conn := utl_smtp.open_connection(l_mailhost, 25);
utl_smtp.helo(l_mail_conn, l_mailhost);
utl_smtp.mail(l_mail_conn, 'nagarjuk@yahoo.com');
utl_smtp.rcpt(l_mail_conn, 'nagarajuk@hotmail.com');
utl_smtp.open_data(l_mail_conn );
utl_smtp.write_data(l_mail_conn, 'pakka ma');
utl_smtp.close_data(l_mail_conn );
utl_smtp.quit(l_mail_conn);
end;

ORA-20002: 550 5.7.1 Unable to relay for nagarajuk@hotmail.com
ORA-06512: at "SYS.UTL_SMTP", line 86
ORA-06512: at "SYS.UTL_SMTP", line 223
ORA-06512: at line 8

can you tell me why is this?
i get this error what ever the email address i give in the recpt

please help

          Followup:

contact your email guys/network guys.

the server you are attempting to use is not set up to relay, this is NOT an
oracle issue, not a UTL_SMTP issue, it is a SMTP configuration issue.  You are
probably using the WRONG mailhost as it's not setup to relay (anti-spamming
defense)

      Rating: 4
email body
      size  April 04, 2002
Reviewer:  manohar

tom,

  is there a limit on the email body size like you mentioned in your answer as
32k only.

please reply.

thanks

          Followup:

Only as I have it coded.  I am passing the body as a single plsql varchar2
variable.

If you passed a temporary clob and wrote it out 32k at a time, you could make it
as large as you wanted.  There is no limit (well, 4gig)

      Rating: 4
sending
      multiple emails in a single SP   April 04, 2002

Reviewer:  nagaraju
If i have to send multiple emails,like typicallly.

i have a cursor that returns email addresses and other detials. i need to loop
and email the other details to the email address

is it only the following way or anyother simple way

declare
l_mailhost VARCHAR2(255) := 'our mail server';
l_mail_conn utl_smtp.connection;
g_crlf        char(2) default chr(13)||chr(10);
 begin
  l_mail_conn := utl_smtp.open_connection(l_mailhost, 25);
  utl_smtp.helo(l_mail_conn, l_mailhost);
  for rec in c_email_cur
  loop
  utl_smtp.mail(l_mail_conn, 'nagarajuk@hotmail.com');
  utl_smtp.rcpt(l_mail_conn, rec.email_address);
  utl_smtp.open_data(l_mail_conn );
  utl_smtp.write_data( l_mail_conn, 'Date: ' || sysdate || g_crlf );
      utl_smtp.write_data( l_mail_conn, 'Subject: ' || 'Testing utl_smtp' ||
g_crlf );
  utl_smtp.write_data(l_mail_conn,'To: ' || rec.email_address || g_crlf);
 utl_smtp.write_data(l_mail_conn, 'This is a Reminder for a Notification
Received originally on: March 1, 2002.'|| g_crlf);
utl_smtp.write_data(l_mail_conn,  g_crlf);
 utl_smtp.write_data(l_mail_conn, 'The Access Removal form attached requires
your immediate attention.  '||rec.somevalue || g_crlf);
 utl_smtp.write_data(l_mail_conn, g_crlf);
 utl_smtp.write_data(l_mail_conn, 'Access Removal Request For:'|| g_crlf);
 utl_smtp.write_data(l_mail_conn,  g_crlf);
 utl_smtp.close_data(l_mail_conn );
end loop;
 utl_smtp.quit(l_mail_conn);
end;

any suggestions?

thanks

          Followup:

Yes, use modular code and factor out repeated processing

Write a send_mail routine (or just use the ones above).

then it becomes:

begin
   for rec in ( select ... )
   loop
         send( .... );
   end loop;
end;
/

Thats what procedures are all about.

      Rating: 5
Attaching a
      bfile  April 09, 2002
Reviewer:  Johan
      Snyman  from JHB SA
Can you please indicate how the code need to be changed to e-mail a bfile as an
attachment (I have to e-mail compressed files in an OS filesystem).

I am just starting to learn Java and don't even know where to start with above
mentioned problem (although I expect at least the BLOBDataSource nested class to
change or a similar class for bfile to be added).

By the way - which resources would you suggest for learning Java, coming from an
Oracle background ?

          Followup:

Should be as easy as changing p_attachment_type to BFILE and opening the BFILE
in plsql before calling java -- the api for reading a bfile in java is the same
as a blob.

You might consider:

http://www.amazon.com/exec/obidos/ASIN/1861006020/
it has some nifty utilities in it.  It is written for the PLSQL developer that
wants to get their feet wet with some java.

      Rating: 5
Email from
      database  April 10, 2002
Reviewer:  Randy
      Richardson  from St. Louis, MO
Tom,

Is there a way to validate the TO list before sending?  Or do you have to just
send and let the bad email addresses fail?  We are using MS exchange 2000 mail
server and would like to do validation against the address book.

Thanks.

          Followup:

Well, the email is coming from the database (on the server)

The address book is in exchange or on the client...

If you can ask MS how to access the address book via java, we can certainly do
that.  (hmm, isn't this how email viruses get started?)

I'm doubtful, all kinds of security issues with accessing the address book (you
have to "trust" the oracle account) and I don't know if Java can access that or
not.  We could use C as well if they support that -- but the security issues
abound.

      Rating: 4
java option in
      oracle  April 15, 2002
Reviewer:  srividhya
       from bangalore, India
Hi Tom,

  Thanks a lot for the mail.It is really great and working fine.We had installed
on the development database(oracle 8.1.6) and had no problems with it. However,
when we tried to deploy on our live database(oracle 8.1.6), we are facing a lot
of installation problems. We realized that the java option has not been set when
the database was setup(1.5 years ago).The database size is 25GB.How do we set
the java option?We are not able to set the java option as an incremental
update.How do we do that.
Thanks in advance.
Please reply...!!. We are just held up in deploying it.

          Followup:

Have you run initjvm.sql found in $ORACLE_HOME/javavm/install

      Rating: 5
Failure
      tracking from Oracle  April 18, 2002
Reviewer:
      rajiv
tom,

is it possible to track the failure of an email.

assume that i am sending email using utl_smtp with TO list has one person and CC
list has 10 persons.
now email was sent to all the CC people but not to the TO list person.

in this case i need to send an email to an ADMIN saying email to that TO person
is failed

is it possible?

          Followup:

the email will bounce tot he REPLY-TO address.  set the reply-to to admin.  I
believe there is an "error-to" or something like that as well.  you would have
to look it up somewhere....

      Rating: 3
Failure
      tracking from Oracle  April 22, 2002
Reviewer:
      rajiv
tom,

can you give me how do set the reply-to address to admin.
because when i tried in the following way mail is not sent to the admin. but the
bounced mail come to the from addressee only.

declare
l_mailhost VARCHAR2(255) := 'our mail server';
l_mail_conn utl_smtp.connection;
l_toaddr   varchar2(50) := 'xyz@xyz.com'; -- invalid email id
l_fromaddr varchar2(50); -- assign the from address here
l_replyto varchar2(50);
g_crlf        char(2) default chr(13)||chr(10);
 begin
  l_mail_conn := utl_smtp.open_connection(l_mailhost, 25);
  utl_smtp.helo(l_mail_conn, l_mailhost);
  utl_smtp.mail(l_mail_conn, l_fromaddr);
  utl_smtp.rcpt(l_mail_conn, l_toaddr);
  utl_smtp.open_data(l_mail_conn );
  utl_smtp.write_data( l_mail_conn, 'Date: ' || sysdate || g_crlf );
      utl_smtp.write_data( l_mail_conn, 'Subject: ' || 'Testing utl_smtp' ||
g_crlf );
  utl_smtp.write_data(l_mail_conn,'To: ' || l_toaddr || g_crlf);
  utl_smtp.write_data(l_mail_conn,'REPLY-TO: ' || l_replyto || g_crlf);
 utl_smtp.write_data(l_mail_conn, 'This is a Reminder for a Notification
Received originally on: March 1, 2002.'|| g_crlf);
utl_smtp.write_data(l_mail_conn,  g_crlf);
 utl_smtp.write_data(l_mail_conn, 'The Access Removal form attached requires
your immediate attention.  ' || g_crlf);
 utl_smtp.write_data(l_mail_conn, g_crlf);
 utl_smtp.write_data(l_mail_conn, 'Access Removal Request For:'|| g_crlf);
 utl_smtp.write_data(l_mail_conn,  g_crlf);
 utl_smtp.close_data(l_mail_conn );
 utl_smtp.quit(l_mail_conn);
end;

      Rating: 4
good but have
      error in downloading mail.zip   April 24, 2002

Reviewer:  shamim  from New Delhi, India
Great Service  done my you TOM
Hi Tom Your answer and subsequent followup are very informative. I tried it ,
activation.zip doenloaded perfectly but I am facing problem in downloading of
mail.zip ,It is showing error while recolving objects,
in the same way as Mr.Bob Yexley   have. and send the debugged code at
mbpierma@us.oracle.com., BUt message comes that this id doed not exist.
So please if he have given some solution to Mr.Bob Yexley  problem Forward it to
me.

          Followup:

drop the us. from the email address (as should have been indicated in the
bounced email, we've stopped using the country specific domain name)

      Rating: 4
PL/SQL to do
      email and appointment  May 03, 2002
Reviewer:
      Binh Ta  from Maryland, USA
Hi Tom,
Very helpful indeed.  But I can you answer my follow-up question too please:
How can one use PL/SQL to create Appointments in Exchange?  I've searched
practically the whole internet but found nothing.  Would really appreciate if
you can shedsome light.  Many thanks.   Binh Ta.

          Followup:

No idea, I know nothing about exchange.  We can do com automatation calls from
plsql on NT using the "com cartridge".

      Rating:
      5
E-mail  May 24, 2002
Reviewer:  Munz
       from Reston, USA
Tom:

Do I understand that if I want to send an  email but with a subject line and a
message pulled out of the database I can do it with PL/SQL.

I only need to use jAVAMAIL API when I need t osend attachments.

THank you,

          Followup:

You never *need* javamail, you could use utl_smtp 100%.

It is just much much easier with javamail to send attachments, it does the work
for you.

UTL_SMTP is sufficient for sending text based email

      Rating:
      5
email  May 31, 2002
Reviewer:  Munz
       from Reston, USA
Tom:

If you do not want to use named parameters and not cc or bcc anyone in email how
do you pass parameters.

I am getting an error when I do:

begin
 mail_pkg.send(
           'myname@att.com',
       null,
       mail_pkg.array('myname@att.com'),
      mail_pkg.array('   '),
      mail_pkg.array('   '),
       'This is a subject',
       'Hello Tom, this is the mail you need' );

dbms_output.put_line('Message sent to the user successfully');
end;
/

Can't you pass null values to an array.

Thanks,

          Followup:

why don't you want to use named parameters?  they are better then positional,
you are not calling this from SQL so that is not an issue.

Otherwise, arrange the parameters to suit your needs (but wait, that'll break
your other code unless it uses named parameters)

I would recommend you use a named parameter, it is more readable, less ambigous
and if someone changes the order on you later -- you are PROTECTED.

You can do this:

begin
   mail_pkg.send( 'sender email', 'from', mail_pkg.array('to'),
                   mail_pkg.array(), mail_pkg.array(),
                  'subj', 'body' );
end;
/

as well.  You are not passing an empty array - you passed an array with some
blank strings -- very different.  Just pass empty arrays (like I have them
defaulted to)

      Rating: 5
database
      link  June 10, 2002
Reviewer:  frustrated
      mailer  from Ohio
TCPConnection class is not loaded in database I want to send mail out from, but
the classes are loaded in another db behind the firewall that I can access
through a db link.

I have tried creating a synonym for UTL_SMTP using the db link.  Should this
work?

I also tried writing a package that accepts the same inputs send mail does, but
just calls send mail across the db link with the same arguments it was called
with.

This doesn't work either.  I get wrong number or type arguments.

Can you show how you would call UTL_SMTP across a db link from a db that doesn't
have the java classes installed?

          Followup:

utl_smtp uses a record.  you are probably not defining your connection record
using this remote utl_smtp package.

tkyte@TKYTE816> create or replace
  2  PROCEDURE send_mail (p_sender       IN VARCHAR2,
  3                       p_recipient IN VARCHAR2,
  4                       p_message   IN VARCHAR2)
  5  as
  6     l_mailhost VARCHAR2(255) := 'aria.us.oracle.com';
  7     l_mail_conn ALIAS_FOR_UTL_SMTP.connection;

but, sigh, without an example and the error message cut and pasted from sqlplus,
its really hard to tell

      Rating: 4
Some details
      when using UTL_SMTP...  June 19, 2002
Reviewer:
      Sven Bleckwedel  from Santos, Brazil
Hi TOM,

Your explanation was very useful to help me to implement this resource in a
database that i administer.  When using another character set (in my case,
WE8ISO8859P1) some problems appeared, but i´ve found some tips that helped me to
solve this problem, too:

<quote>
The original SMTP protocol communicates using 7-bit ASCII. But if the target
SMTP server supports 8BITMIME extension, the user can convert the non-US7ASCII
data to RAW and use the write_raw_data procedure to send data using 8-bit MIME
encoding
...
Using the utl_smtp and utl_tcp packages requires that the JServer option is
installed in the database.
Reference: Oracle8i Supplied PL/SQL Packages Reference, 65 UTL_SMTP
<quote>

- The code, before changes:

 61 writeData('Date: ' || l_date);
 62 writeData('From: ' || nvl(p_from, p_sender_email));
 63 writeData('Subject: ' || nvl(p_subject,'(nosubject)'));
 64
 65 writeData( l_to_list );
 66 writeData( l_cc_list );
 67
 68 utl_smtp.write_data(g_mail_conn, '' || g_crlf);
 69 utl_smtp.write_data(g_mail_conn, p_body );
 70 utl_smtp.close_data(g_mail_conn );
 71 utl_smtp.quit(g_mail_conn);

- After changes are made, at lines 65-67 and 72:

 61 writeData('Date: ' || l_date);
 62 writeData('From: ' || nvl(p_from, p_sender_email));
 63 writeData('Subject: ' || nvl(p_subject,'(no subject)'));
 64
 65 utl_smtp.write_data(g_mail_conn,'MIME-Version: 1.0' || g_crlf);
 66 utl_smtp.write_data(g_mail_conn,'Content-Type: text/plain;
charset=iso-8859-1' || g_crlf)
 67 utl_smtp.write_data(g_mail_conn,'Content-Transfer-Encoding: 8bit' ||
g_crlf);
 68
 69 writeData( l_to_list );
 70 writeData( l_cc_list );
 71
 72 utl_smtp.write_raw_data(g_mail_conn,utl_raw.cast_to_raw(g_crlf || p_body));
 73 utl_smtp.close_data(g_mail_conn );
 74 utl_smtp.quit(g_mail_conn);

- But, other problems appeared:

SQL> @send_mail3

Package created.

Warning: Package Body created with compilation errors.

SQL> show errors
Errors for PACKAGE BODY MAIL_PKG:

LINE/COL ERROR
-------- ---------------------------------------------
72/5     PL/SQL: Statement ignored
72/14    PLS-00302: component 'WRITE_RAW_DATA' must be
                    declared

Searching for the reason, in Metalink:

fact: Oracle Server - Enterprise Edition 8.1.6.1
fact: PL/SQL 8.1.6
symptom: Calling utl_smtp.write_raw_data within application
symptom: Procedure UTL_SMTP.WRITE_RAW_DATA Does Not Exist
cause: <Bug:1239254> Procedure UTL_SMTP.WRITE_RAW_DATA does not exist
fix: Apply patch 8.1.6.2 or upgrade to 8.1.7.

After applying the latest patch (8.1.6.3.1, for HP-UX 11.0), the problem was
solved !

Sven Bleckwedel
Unix/Oracle Admin

      Rating:
      5
  June 25, 2002
Reviewer:  Sheryl
      Smith  from UK
Tom,
When I loadjava
i.e.
loadjava -u sys/passwr -o -r -v -f -noverify -synonyn  -g public mail8i.zip

I get the following errors:
resolving: mail/javax/mail/search/ComparisonTerm
Error while resolving class mail/javax/mail/search/ComparisonTerm
    ORA-04043: object /fdec8a69_ComparisonTerm does not exist

resolving: mail/com/sun/mail/handlers/message_rfc822
Error while resolving class mail/com/sun/mail/handlers/message_rfc822
    ORA-04043: object /83f5d240_message_rfc822 does not exist

resolving: mail/javax/mail/FolderNotFoundException
Error while resolving class mail/javax/mail/FolderNotFoundException
    ORA-04043: object /45ac233_FolderNotFoundExcepti does not exist

resolving: mail/com/sun/mail/imap/protocol/BODY
Error while resolving class mail/com/sun/mail/imap/protocol/BODY
    ORA-04043: object /a31a955b_BODY does not exist

resolving: mail/javax/mail/search/SentDateTerm
Error while resolving class mail/javax/mail/search/SentDateTerm
    ORA-04043: object /70998eaf_SentDateTerm does not exist
.....

What do I need to do?
Thanks.

      Rating: 4
File system
      file as attachment  June 27, 2002
Reviewer:
      Vikas
Hi Tom,
This uses the file stored in the database for attachment. can you pl tell me how
to do it for file system. Actually I want to use a browse button for attachments
in which user will select file from client os(own system). I am using mod_plsql.
Do i need to upload that file in the database and than execute your procedure.
or there is any direct apporach. I am wondering how it can be done? Because
mod_plsql does not support file system uploads( from client file system to
server file system).

          Followup:

If you want to

a) send email from the server
b) with a file from the client

you'll have to upload the file to the server (mod_plsql will be great for that).
 Then, it'll be in a blob and the code will work "as is".  After you email the
document, delete the blob.

      Rating: 4
I cannot
      recompile java source mail  July 10, 2002

Reviewer:  Tom Jaros  from Czech Republic
I used following way of loadjava:

C:\SourceSafe\frm_rep\mail\JavaMail>loadjava -u sys/gambac12@x -o -r -v -f
-synonym -g public mail8i.zip
initialization complete
loading  : mail.jar
creating : mail.jar
resolver :

C:\SourceSafe\frm_rep\mail\JavaMail>loadjava -u sys/gambac12@x -o -r -v -f
-synonym -g public activation8i.zip
initialization complete
loading  : activation.jar
creating : activation.jar
resolver :

When I tried to create java source I got the warning message about error in
source:
SQL> show error
Errors for JAVA SOURCE mail:

LINE/COL ERROR
-------- -----------------------------------------------------
0/0      mail:98: Interface DataSource of nested class mail.
         BLOBDataSource not found.

0/0      mail:132: Interface DataSource of nested class mail.
         CLOBDataSource not found.

0/0      mail:54: Class Message not found.
0/0      mail:54: Class MimeMessage not found.
0/0      mail:59: Variable msg may not have been initialized.
0/0      mail:61: Variable msg may not have been initialized.
0/0      mail:62: Variable msg may not have been initialized.

LINE/COL ERROR
-------- -----------------------------------------------------
0/0      mail:65: Class MimeBodyPart not found.
0/0      mail:65: Class MimeBodyPart not found.
0/0      mail:68: Class MimeBodyPart not found.
0/0      mail:68: Class MimeBodyPart not found.
0/0      mail:77: Class MimeMultipart not found.
0/0      mail:77: Class MimeMultipart not found.
0/0      mail:86: Undefined variable or class name: Transport
0/0      Info: 14 errors

In database there are only mail.jar and activation.jar, I can see them in SQL
Navigator in java resources. What was wrong in loadjava?

          Followup:

My name is Mark Piermarini and I work for Tom. He asked me to help you. I have a
few questions:

1. Database version?
2. Operating system of the database host?
3. Did you load activation8i.zip before mail8i.zip?
4. Did you get any errors when loading either of the ZIP files? (if so then send
those as well).
5. Are any of the classes marked invalid when you look at them with SQL
Navigator?
6. What user is running the SQL to create the PL/SQL wrapper and java stored
procedure? SYS or another user?
7. Have you tried loading the ZIP and SQL file as another user? (might not work
depending upon you database version).

If I recall correctly, there was an issue with the "-g" option for loadjava but
i'll have to get your DB version before verifying that as a potential problem.

      Rating: 5
attached
      BFILE  July 29, 2002
Reviewer:  kiro
       from Bulgaria
If someone is interested for attachement of bfile see below:
In Java source I have changed BLOB to BFILE, but as Tom said it can work as it
is with BLOB.

 create or replace function send_bfile(
        p_from                  in varchar2,
        p_to                    in varchar2,
        p_cc                    in varchar2,
        p_bcc                   in varchar2,
        p_subject               in varchar2,
        p_body                  in varchar2,
        p_smtp_host             in varchar2,
        p_attachment_data       in BFILE,
       p_attachment_type       in varchar2,
       p_attachment_file_name  in varchar2) return number
   as
   language java name 'mail_bfile.send_bfile( java.lang.String,
                                  java.lang.String,
                                  java.lang.String,
                                  java.lang.String,
                                  java.lang.String,
                                  java.lang.String,
                                  java.lang.String,
                                  oracle.sql.BFILE,
                                  java.lang.String,
                                  java.lang.String
                               ) return oracle.sql.NUMBER';

set serveroutput on size 1000000
exec dbms_java.set_output( 1000000 );

 declare
    ret_code number;
    l_bfile bfile := bfilename( 'MY_FILES', 'mail8i.zip' ) ;
  begin
       dbms_lob.fileopen(l_bfile) ;
       ret_code:=send_bfile(
                    p_from => 'Welcome_From_my_Database',
                    p_to => 'my@myserv.com',
                    p_cc => null,
                    p_bcc => NULL,
                    p_subject => 'Use the attached Zip file',
                    p_body => 'to send email with attachments....',
                    p_smtp_host => 'smtpHOST',
                    p_attachment_data => l_bfile,
                    p_attachment_type => 'application/winzip',
                    p_attachment_file_name => 'mail8i.zip');

      if ret_code = 1 then
        dbms_output.put_line ('Successfully sent message...');
      else
        dbms_output.put_line ('Failed to send message...');
      end if;
dbms_lob.fileclose( l_bfile ) ;
  end;

I have tried on 9i and it is working fine!

Tom
do you have idea how big could be an attached file?
Could you give as a link or info where we can find info about MIME types?
Thanks
kiro

      Rating: 5
Error trying to
      run javamail.send function  August 01, 2002

Reviewer:  Richard Smith  from Bensenville, IL USA

I successfully executed the pl/sql based mail functions in the article, loaded
the JAXF and javamail apis and created the java stored procedure, granted
permissions, and created the pl/sql wrapper function.  Then when I execute an
anonymous block using the same values for from, to, and smtp host as in the
utl_smtp examples I get the following error:

javax.mail.NoSuchProviderException: No provider for Address type: rfc822
at javax.mail.Session.getTransport(Session.java:516)
at javax.mail.Transport.send0(Transport.java:155)
at javax.mail.Transport.send(Transport.java:81)
at mail.send(mail:71)
FAILED TO SEND

PL/SQL procedure successfully completed.

          Followup:

do a search for

javax.mail.NoSuchProviderException: No provider for Address type: rfc822

on www.google.com.

......
According to Sun's JavaMail FAQ:

Usually this is because JavaMail can't access the configuration files in
mail.jar, possibly because of a security permission problem; see this item for
more details. Also, make sure that you haven't extracted the mail.jar contents;
you should include the unmodified mail.jar file in the server's CLASSPATH.
.................

It is a java thing, not a database thing here.  You might be missing a
permission

      Rating: 5
LoadJava
      Errors...8.1.7  August 05, 2002
Reviewer:
      Sikandar Hayat Awan  from Pindi Bhattian - Pakistan

Oracle OEM 8.1.7 on WINNT 4 Sp-5

1/7 >Banner
Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production
With the Partitioning option
JServer Release 8.1.7.0.0 - Production

2/7 >Error When I am trying to send
ORA-29540: class oracle/plsql/net/TCPConnection does not exist
ORA-06512: at "SYS.UTL_TCP", line 678
ORA-06512: at "SYS.UTL_TCP", line 247
ORA-06512: at "SYS.UTL_SMTP", line 99
ORA-06512: at "SYS.UTL_SMTP", line 121
ORA-06512: at "SYS.MAIL_PKG", line 49
ORA-06512: at line 2
Note: I have configured SMTP email on the same server in OEM and it is working
fine.

3/7 >The following statement is successful giving no error
Plsql/jlib
loadjava -user sys/mypass@cstring plsql.jar

4/7 >I have downloaded both files from java-sun-com and performed the steps u
mentioned
1. Used WinZip to extract the contents of mail.jar into my c:\temp\mail
directory
2. Used WinZip to create a new archive c:\temp\mail8i.zip
3. Put the contents of c:\temp\mail\*.* including subdirectories into this new
archive
5/7 >Then issued the following command and received 422 errors if I ommit -g
public then 210 errors. I have also changed the user but same result.

D:\Sikandar\Java>loadjava -u sys/mypass -o -r -v -f -noverify -synonym -g public
mail8i.zip
The name specified is not recognized as an
internal or external command, operable program or batch file.
initialization complete
loading  : mail8i/javax/mail/internet/NewsAddress
creating : mail8i/javax/mail/internet/NewsAddress
loading  : mail8i/com/sun/mail/util/QDecoderStream
creating : mail8i/com/sun/mail/util/QDecoderStream
loading  : mail8i/com/sun/mail/util/QEncoderStream
creating : mail8i/com/sun/mail/util/QEncoderStream
loading  : mail8i/javax/mail/internet/InternetHeaders
creating : mail8i/javax/mail/internet/InternetHeaders
loading  : mail8i/com/sun/mail/imap/IMAPFolder$ProtocolCommand
creating : mail8i/com/sun/mail/imap/IMAPFolder$ProtocolCommand
loading  : mail8i/com/sun/mail/imap/protocol/Namespaces
creating : mail8i/com/sun/mail/imap/protocol/Namespaces
....
loading  : mail8i/com/sun/mail/imap/protocol/BASE64MailboxDecoder
creating : mail8i/com/sun/mail/imap/protocol/BASE64MailboxDecoder
....
loading  :
Error while granting to mail8i/com/sun/mail/util/QEncoderStream
    ORA-04042: procedure, function, package, or package body does not exist
....
synonym  : mail8i/javax/mail/AuthenticationFailedException
loadjava: 422 errors

6/7 >
D:\Sikandar\Java>loadjava -user sys/mypass -o -r -v -f mail.java
The name specified is not recognized as an
internal or external command, operable program or batch file.
internal error: unanticipated exception: java.io.FileNotFoundException:
mail.java
java.io.FileNotFoundException: mail.java
        at java.io.FileInputStream.<init>(FileInputStream.java:56)
        at java.io.FileInputStream.<init>(FileInputStream.java:75)
        at
oracle.aurora.server.tools.loadjava.LoadJava.processLoadAndCreate(LoadJava.java:1
512)
        at
oracle.aurora.server.tools.loadjava.LoadJava.process(LoadJava.java:1422)
        at
oracle.aurora.server.tools.loadjava.LoadJavaMain.run(LoadJavaMain.java:299)
        at
oracle.aurora.server.tools.loadjava.LoadJavaMain.main(LoadJavaMain.java:58)
resolver :
loadjava: 1 errors

**********************************************************
7/7 >In the loadjava help there is no -debug parameter so how to specify it?

"Also, add '-debug' to your loadjava commands and send that output to
mbpierma@us.oracle.com."

The following is response from your server,
   ----- Transcript of session follows -----
... while talking to inet-mail1.oracle.com.:
>>> RCPT To:<mbpierma@us.oracle.com>
<<< 553 5.7.1 <mbpierma@us.oracle.com>... Oracle email addresses in the
country-specific format have been retired. Please contact the intended recipient
by other means to obtain their correct address. For additional assistance,
please contact your local Oracle office representative. 550 5.1.1
<mbpierma@us.oracle.com>... User unknown

Please let me know for any further query.

      Rating: 4
All Good ..But
      need to encrypt the attachments...!  August 08, 2002

Reviewer:  Manoj Bajaj  from Singapore
Tom, Life for programmer has never been easy. We started using the email
utility..Now it sends attachments...many attachments tooo...but now one more
secutiry reuirement..To encrypt the attachments and send ...imp. the user /
receiver shoud be able to decrypt it ..how ?? hope u can show some path...

          Followup:

beyond the scope of asktom here.  It'll be very specific to the email client.

sorry.

      Rating: 5
Sending
      multiple emails  August 27, 2002
Reviewer:
      A reader
Excellent work Tom,
Here is my problem..
I am trying to run the procedure given by you in a cursor...
(we need to automate the email sending procedure...a job will run in the
morning)...
Recipient, to, cc, bcc...all the info will come from a table:
SQLWKS> desc tab_email
Column Name                    Null?    Type
------------------------------ -------- ----
RECIPIENT                               VARCHAR2(500)
CC                                      VARCHAR2(500)
BCC                                     VARCHAR2(500)
SUBJECT                                 VARCHAR2(500)
MESSAGE                                 VARCHAR2(900)

recipient, cc, bcc will be comm-delimited list of email addresses for example,
a record from the tab_email (Recipient) will be  stored exactly like this in the
database:
'this@hotmail.com','that@yahoo.com'

CREATE OR REPLACE procedure send_emails as

cursor c_cur is
select * from tab_email;

begin

for c_rec in c_cur loop

mail_pkg.send
    ( p_sender_email => '"automated mail"',
      p_from => '"automated mail"',
      p_to => mail_pkg.array(c_rec.RECIPIENT),
      p_cc => mail_pkg.array(c_rec.cc),
      p_bcc => mail_pkg.array(c_rec.bcc),
      p_subject => c_rec.subject,
      p_body => c_rec.message);

end loop;

end;

this compiles fine.....but when I run it....
I get

ERROR at line 1:
ORA-20002: 501 5.5.4 Invalid arguments
ORA-06512: at "SYS.UTL_SMTP", line 86
ORA-06512: at "SYS.UTL_SMTP", line 204
ORA-06512: at "MANAGER.MAIL_PKG", line 54
ORA-06512: at line 1

If I don't pass values through a cursor....the procedure works O.K.
Is there a Tom Classic way of getting this going....

I am using PL/SQL solution because we don't have attachment issues.
We are using Oracle 8i (8.1.7.1.1)

Thanks..Always

          Followup:

Well, my line 54:

 49  begin
 50      g_mail_conn := utl_smtp.open_connection(g_mailhost, 25);
 51
 52      utl_smtp.helo(g_mail_conn, g_mailhost);
 53      utl_smtp.mail(g_mail_conn, p_sender_email);
 54

Here is where it differs, instead of calling UTL_SMTP.RCPT once ? it uses are
address_email function to call it potentially many times, building the ?To:? and

?Cc:? list for us as well.  It builds the BCC list but we won?t actually send
that (we don?t want the recipients to see that list!)

 55      l_to_list  := address_email( 'To: ', p_to );
 56      l_cc_list  := address_email( 'Cc: ', p_cc );
 57      l_bcc_list := address_email( 'Bcc: ', p_bcc );

contains no code, so I don't know if it is a problem with your:

sender_email
p_to
p_cc
p_bcc

Suggest you code this:

CREATE OR REPLACE procedure send_emails as

cursor c_cur is
select * from tab_email;

begin

for c_rec in c_cur loop

begin
mail_pkg.send
    ( p_sender_email => '"automated mail"',
      p_from => '"automated mail"',
      p_to => mail_pkg.array(c_rec.RECIPIENT),
      p_cc => mail_pkg.array(c_rec.cc),
      p_bcc => mail_pkg.array(c_rec.bcc),
      p_subject => c_rec.subject,
      p_body => c_rec.message);
exception
   when others then
      raise_application_error( -20002,
      '"' || c_rec.recipient || '" "' || c_rec.cc || '" "' || c_rec.bcc || '"'
);
end;

end loop;

end;

run that and your error message will including the "bad" stuff that you can test
with from the command line to see what you need to fix up.

      Rating: 5
Another
      issue...thanks for the first reply  August 27, 2002

Reviewer:  A reader
Tom,

I actually came up with another solution...again from your site...
I created a function:
CREATE OR REPLACE FUNCTION MAIL_ARRAY (PSTRING VARCHAR2) RETURN MAIL_PKG.ARRAY
AS

   L_ARRAY MAIL_PKG.ARRAY := MAIL_PKG.ARRAY();
BEGIN
   FOR X IN (SELECT A.COLUMN_VALUE
              FROM   THE (SELECT CAST(in_list(PSTRING) AS MYTABLETYPE_VARCHAR2)
                       FROM   DUAL) A
            )
   LOOP
     L_ARRAY .EXTEND;
     L_ARRAY (L_ARRAY.COUNT) := X.COLUMN_VALUE;
END LOOP;

RETURN L_ARRAY;

END MAIL_ARRAY;

/

Then I ran the procedure like this:
CREATE OR REPLACE PROCEDURE SEND_EMAILS AS

CURSOR C_CUR IS
select * from tab_email;

BEGIN

FOR C_REC IN C_CUR LOOP

MAIL_PKG.SEND
    ( P_SENDER_EMAIL => '"Automated Mail Message"',
      P_FROM => '"Automated Mail Message"',
      P_TO => MAIL_ARRAY(C_REC.RECIPIENT),
      P_CC => MAIL_ARRAY(C_REC.CC),
      P_BCC => MAIL_ARRAY(C_REC.BCC),
      P_SUBJECT => C_REC.SUBJECT,
      P_BODY => C_REC.MESSAGE);

END LOOP;

END;

This work's perfect...and there is always a "BUT"  :)
My table structure that will feed the records to the cursor
will be like this:

SQLWKS> desc email
Column Name                    Null?    Type
------------------------------ -------- ----
EMAILID                        NOT NULL NUMBER
RECIPIENT                      NOT NULL VARCHAR2(500)
CC                                      VARCHAR2(500)
BCC                                     VARCHAR2(500)
SUBJECT                        NOT NULL VARCHAR2(255)
SUBMITDT                       NOT NULL DATE
SENTYN                         NOT NULL VARCHAR2(1)
SENTDT                                  DATE

SQLWKS> desc email_message
Column Name                    Null?    Type
------------------------------ -------- ----
MESSAGEID                      NOT NULL NUMBER
EMAILID                        NOT NULL NUMBER
MESSAGE                        NOT NULL VARCHAR2(900)

so,
"CURSOR C_CUR IS
select * from tab_email;"

will change to
"
CURSOR C_CUR IS
SELECT E.EMAILID, M.MESSAGEID, E.RECIPIENT, CC,BCC, SUBJECT, SENTYN, SENTDT
FROM EMAIL E, EMAIL_MESSAGE M
WHERE E.EMAILID = M.EMAILID (+)
ORDER BY EMAILID, MESSAGEID;
"

email_message.emailid is an FK to EMAIL.emailid.
so that, an emailid can have multiple messages..so there won't be any constraint
on the length of the message...keep concatenating...
fine...
"BUT"
Now we have declared p_body in mail_pkg.send as LONG...so that
is 32000 approx.
Maximum length of a message (all the messageid's combined for an emailid) that I
have seen in our data is 16452.
(Concatenated message for an emailid will be passing as mail_pkg.send(p_body)
parameter)

So, at some point there is a chance this code might break..
Is there a better way to do this...

Thanks Always...

          Followup:

rewrite my stuff to use CLOBS instead of LONGS and store the message in a clob
instead.

If a plain text email exceeds 32k -- man, that must be one heck of an email.

      Rating: 5
CLOBS
      hmmmm  August 27, 2002
Reviewer:
      A reader
Tom,

:)))))))))))))))))
Well, it did go to 16452.....
We are sending contractors emails about their projects..something something...
it's not somebody writing their lifestory :))))))
Anyway,
I will change LONG to CLOB....

Thanks a zillion to the power infinity for the Superfly Quick replies...

You are the Best...

      Rating: 5
CLOB fails on
      utl_smtp.write_data :(  August 27, 2002
Reviewer:
      A reader
I changed procedure send to:

procedure send( p_sender_email in varchar2,
                p_from         in varchar2 default NULL,
                p_to           in array default array(),
                p_cc           in array default array(),
                p_bcc          in array default array(),
                p_subject      in varchar2 default NULL,
                p_body         in clob  default NULL )

so, p_body is a CLOB now..
"BUT" it fails to compile..because data is varchar2

utl_smtp:
PROCEDURE write_data(c     IN OUT NOCOPY connection,
                       data  IN            VARCHAR2);

Now, How was it working with LONG anyway?

Here is the line and the error:

70>     utl_smtp.write_data(g_mail_conn, p_body );

"70/5     PLS-00306: wrong number or types of arguments in call to 'WRITE_DATA'
"

Any ideas....

Thanks...

          Followup:

Well you have to rewrite the code ALL OF THE WAY down to the utl_smtp.write_data
call and write the CLOB out 32k at a time!

      Rating: 5
getting
      error  August 28, 2002
Reviewer:
      A reader
getting

ORA-29532: Java call terminated by uncaught Java exception:
java.lang.NullPointerException: Null buffer

on 8.1.7.1.1

worked ok on 8.1.7.3.0

any ideas...

          Followup:

Nope, you must have missed a grant or something like that.

Put some debug in there and snoop around - i've used this on pretty much every
release of Oracle since 815

      Rating: 5
followup to my
      response above  August 28, 2002
Reviewer:
      A reader
Just wanted to clarify...
I am using utl_smtp...just the way you said to send email..
no java..then
Why this Java error..

          Followup:

UTL_SMTP is written in java.

Time for some debug.  Find out where it is happening.

      Rating: 5
found the
      error  August 30, 2002
Reviewer:
      A reader
if you pass null to the p_body..that's when ora-29532 java error occurs..
NVL took care of that issue..

Thanks Tom.....

      Rating: 5
excellent piece
      of art!  September 10, 2002
Reviewer:  Arielito
      Lopez  from Atlanta, GA
thanks for this information...it helps a lot!

      Rating: 4
I get an error
      when I using Javamail  September 24, 2002

Reviewer:  Mukesh Thakkar  from Kansas City, KS

Hi
I am running Oracle 8.1.7 RDBMS on windows NT/2000. I can get utl_smtp to work
fine sending emails. However when I use the Javamail API as shown I get the
following errors
javax.mail.SendFailedException: No recipient addresses
at javax.mail.Transport.send0(Transport.java:110)
at javax.mail.Transport.send(Transport.java:80)
at mail.send(mail:132)
Failed to send message...

Any ideas?
Thanks
Mukesh Thakkar

          Followup:

Well, thats a java exception -- you must have not sent the right inputs (like a
recipient - a to list) down to the routine.

      Rating: 5
It worked
      fantastic on Oracle 9i on Sun Solaris !!! Thanks a
      lot  October 10, 2002
Reviewer:  Arun
      Srivastav  from INDIA
I followed all the steps, except deviated while running loadjava with user as
system and I got errors, then went back to SYS and it ran through.

It is very very useful, specially in the project I am working with i.e. Digital
Asset Management. A lot of files and reports are required to be mailed as
attachment for reviews, reports, etc.

Thanks a lot !!!

      Rating: 4
load java
      Problem  October 28, 2002
Reviewer:  Vikas
      Sharma  from Delhi India
hi Tom/Mark Piermarini,

I  got the error while doing loadjava and creating javasource. But when i create
the javasource in sys user i dont receive any errors and it gets created
sucessfully.   Following are the errors

loadjava -u sys/change_on_install@demo_mtl -o -r -v -f -noverify -synonym -g
public mail8i.zip

resolving: mail8i/com/sun/mail/imap/protocol/FLAGS
Error while resolving class mail8i/com/sun/mail/imap/protocol/FLAGS
    ORA-04043: object mail8i/com/sun/mail/imap/protocol/FLAGS does not exist

resolving: mail8i/com/sun/mail/imap/MessageLiteral
Error while resolving class mail8i/com/sun/mail/imap/MessageLiteral
    ORA-04043: object mail8i/com/sun/mail/imap/MessageLiteral does not exist

resolving: mail8i/javax/mail/internet/SharedInputStream
Error while resolving class mail8i/javax/mail/internet/SharedInputStream
    ORA-04043: object mail8i/javax/mail/internet/SharedInputStream does not
exist
......
like That 392 Errors
*******************************************************

Following are the error when loading activation.zip
loadjava -u sys/change_on_install@demo_mtl  -o -r -v -f -noverify -synonym -g
public activation8i.zip

Error while granting to activation8i/javax/activation/SecuritySupport12$2
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to activation8i/javax/activation/CommandMap
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to
activation8i/com/sun/activation/registries/MailcapTokenizer
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to activation8i/javax/activation/MimeType
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to activation8i/javax/activation/DataHandler
    ORA-04042: procedure, function, package, or package body does not exist

Error while granting to activation8i/javax/activation/SecuritySupport12$4
    ORA-04042: procedure, function, package, or package body does not exist
......
loadjava: 76 errors
*******************************

When i tried to create java source in  it gives Errors,
But when i create the javasource in sys user i dont receive any errors and it
gets created sucessfully.

ERROR for JAVA SOURCE mail when some other user:

LINE/COL ERROR
-------- -----------------------------------------------------
0/0      mail:98: Interface DataSource of nested class mail.
         BLOBDataSource not found.

0/0      mail:132: Interface DataSource of nested class mail.
         CLOBDataSource not found.

0/0      mail:54: Class Message not found.
0/0      mail:54: Class MimeMessage not found.
0/0      mail:59: Variable msg may not have been initialized.
0/0      mail:61: Variable msg may not have been initialized.
0/0      mail:62: Variable msg may not have been initialized.

LINE/COL ERROR
-------- -----------------------------------------------------
0/0      mail:65: Class MimeBodyPart not found.
0/0      mail:65: Class MimeBodyPart not found.
0/0      mail:68: Class MimeBodyPart not found.
0/0      mail:68: Class MimeBodyPart not found.
0/0      mail:77: Class MimeMultipart not found.
0/0      mail:77: Class MimeMultipart not found.
0/0      mail:86: Undefined variable or class name: Transport
0/0      Info: 14 errors

I created function send in sys and tried to send mail but that there alos i am
to send mail and get
sys@DEMO.MIS1>/
Failed to send message...

The non java version is working fine. i could send mail with that.

My database version 8.1.7.0
I loaded activiation.zip first
errors i showed you while loading zip files.

Kindly help me.

Regards,

Vikas sharma

      Rating:
      5
email  October 30, 2002
Reviewer:
      mo
Tom:

If I want to CC several people do I create a variable that holds all emails
included in ' ' and separated by a comma and use it in

v_email1:= 'mike@yahoo.com';
v_email2:= 'John@aol.com';
v_email3:= 'mary@msn.com';
v_cc_list := v_email1||','||v_email2||','||v_email3;

p_cc => mail_pkg.array( v_cc_list ),

2.  Would the email package work if the p_to has a null variable while the p_cc
has a list of emails or you have to have to.  The reason I ask this is that
sometimes you send emails based on user profile flag. The to person may have it
off while the one on CC is on.

Thank you

          Followup:

not as I have the code written.  you would do it just like the example does for
the p_to parameter below:

tkyte@TKYTE816> begin
  2      mail_pkg.send
  3      ( p_sender_email => 'tkyte@us.oracle.com',
  4        p_from => 'Oracle Database Account <tkyte@us.oracle.com>',
  5        p_to => mail_pkg.array( 'tkyte@us.oracle.com',
                                           'thomas.kyte@oracle.com' ),
  6        p_cc => mail_pkg.array( 'tkyte@oracle.com' ),
  7        p_bcc => mail_pkg.array( 'utility@aria.us.oracle.com' ),
  8        p_subject => 'This is a subject',
  9        p_body => 'Hello Tom, this is the mail you need' );
 10  end;
 11  /

an array, not a comma delimited string.  YOu would have to parse the string into
an array somewhere.

      Rating:
      5
email  October 31, 2002
Reviewer:
      mo
Tom:

1. What do you use to parse a string into an array.  Do you have any reference
for examples?

2.  How about if I declared a variables of an array type

 type emailArray is table of varchar2(8);
  email                   emailArray:=emailArray(null,null,null,null,null;

Then I select first email into email(1) and second into
email(2) and so on.

Would just use this then:

p_to => mail_pkg.array(email)

would this work.

          Followup:

1) search for

str2table

for an example (on this site)

2) or just

...
  l_emails mail_pkg.array := mail_pkg.array();  /* gotta use the SAME type,
don't
                                                invent your own!!!! */
....
begin
   ....
   l_emails.extend(4);
   l_emails(1) := ...
   l_emaisl(2) := ....
......

and then just

   p_to => L_emails,

      Rating: 5
..and under
      Oracle9i ?  December 02, 2002
Reviewer:
      Mario  from Italy
From your reply:

" loadjava -u sys/manager -o -r -v -f -noverify -synonym -g public mail8i.zip
loadjava -u sys/manager -o -r -v -f -noverify -synonym -g public "

1) But under Oracle9i you can log in as "sys" only with the special "as sysdba".
How can I workaround it?

Apparently the "Java Developer’s Guide", for Oracle9iR2 obviously, doesn't say
anything about this issue.

2) Is there a better way under Oracle9i to send an email with an attachment?

Thank you very much Tom

Mario from Rome, Italy

      Rating:
      5
email  December 04, 2002
Reviewer:
      mo
Tom:

Can you still use utl_smtp to send emails out out of the database to users
outside of a firewall, if you have a database server that is not on the
internet.  Server has classified info and is only accessed through app server.

Thanks,

          Followup:

only if you can send an email out from the command line using "mail" from the
server.

Do you have an SMTP relay to the outside?  if not, no go.  Nothing to do with
utl_smtp here, more with the firewall policies and how it is setup.  If the
database server HOST can send emails to the outside, then utl_smtp certainly
can.  If not, then no.

      Rating:
      4
excellent  December 31, 2002
Reviewer:
      Mohammed Saleemuddin  from Riyadh(KSA)
Hi Tom,

Your procedure of sending mails from D2K was execellent.i got it within notime
and is working fine till now.
here my reqiurements,realted to ur application.sending is ok.my question is
,when i recieve the mail,it will go to my exchange server.
what i want is,recieve the mails in D2k form itself.is there any possibility
that i can store the incoming mails in D2K form instead of going in exchange
server.plz,help me out in this matter.
waitng for ur answer.
thanks
Mohd.salim.
Riyadh.

          Followup:

forms is just an application development tool.  It doesn't "store anything"

You must mean the database, the java apis above are capable of "reading" mail as
well as sending it. You just need read up on the java mail api and then you'll
have to build a set of tables and such to store the retrieved mail in.

      Rating: 3
loadjava error
      - please help  January 08, 2003
Reviewer:
      Michael  from Australia
Hi Tom

I have downloaded JavaMail 1.3 and did everything up to the point of loading
mail8i.zip into the database.

When our DBA execute loadjava for mail8i.zip using the SYS account, she
encountered 422 errors, a sample of these is:

resolving: temp/javamail13/extracted_mail/com/sun/mail/iap/Argument
Error while resolving class
temp/javamail13/extracted_mail/com/sun/mail/iap/Argument
    ORA-04043: object /8db2db4c_Argument does not exist

resolving: temp/javamail13/extracted_mail/com/sun/mail/iap/Protocol
Error while resolving class
temp/javamail13/extracted_mail/com/sun/mail/iap/Protocol
    ORA-04043: object /26640416_Protocol does not exist

resolving:
temp/javamail13/extracted_mail/com/sun/mail/imap/protocol/FetchResponse
Error while resolving class
temp/javamail13/extracted_mail/com/sun/mail/imap/protocol/FetchResponse
    ORA-04043: object /3e15cf74_FetchResponse does not exist

We are using oracle 8i:

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production
With the Partitioning option
JServer Release 8.1.7.0.0 - Production

Could you please tell me where we went wrong?

Thanks in advance

          Followup:

try loading it not into sys, but your own schema and see what happens.  that way
you can work with it.

(not sure, but maybe i have the loadjava's backwards?  try activation first)

      Rating:
      5
Benchmarking  January 23, 2003
Reviewer:
      Scott  from Perth, Western Australia - world's best city!

G'day Tom

For those who are interested, I did some benchmarking (on our development box)
to compare the two emailing methods.

1 - SMTP
2 - Javamail

The SMTP benchmark used on session, and mailed within that session. This was
much better performance than using one session per email.

Each count represents a batch of 5 emails, to split the load, in case the server
activity varied.
Sum and avg is in hsecs.

No attachments...(ie - 200 emails - 100 each)

    SUM_1      SUM_2      AVG_1      AVG_2    COUNT_1    COUNT_2
--------- ---------- ---------- ---------- ---------- ----------
     3423       2040      85.58         51         20         20

44kb attachment included.

    SUM_1      SUM_2      AVG_1      AVG_2    COUNT_1    COUNT_2
--------- ---------- ---------- ---------- ---------- ----------
   202041      74569     3741.5    1380.91         27         27

So when using attachments, Javamail had even better performance compared to
UTL_SMTP.

Here are some other runs with various conditions varied. Each number represents
a run of 100/50 emails, so server usage may have affected some of these results.
Javamail outperformed UTL_SMTP in all but runs 7 and 8.
The first two runs using UTL_SMTP (1 and 3) are opening one session per email -
hence the long run-time.
-- (100 emails each)
 1 #1: 10752 hsecs  (10.75 seconds)
 2 #2:  6100 hsecs
 3 #1: 10329 hsecs
 4 #2:  5247 hsecs
 5 #1:  6537 hsecs
 6 #2:  5287 hsecs
 7 #1:  4789 hsecs
 8 #2:  5047 hsecs
 9 #1:  6060 hsecs
10 #2:  2910 hsecs
11 #1:  7344 hsecs
12 #2:  4214 hsecs
-- attachments (50 emails each)
13 #1: 46616 hsecs
14 #2: 19054 hsecs
15 #1: 40845 hsecs
16 #2: 17496 hsecs

Thanks for your contributions, Tom.

      Rating: 5
Great - Spot
      on  January 24, 2003
Reviewer:  Michael
       from Australia
Following your suggestion, we have successfully loadjava for the zip files and
implemented it.

You were right in saying activation8i.zip needs to be loaded first.

Thanks again.

      Rating: 4
util_smtp
      package  February 20, 2003
Reviewer:
      naivedya  from USA
It is very good, for sending mails. But at the same time I wish to attach my
attachments (txt, doc, etc. or database fields). How do I do this with PL/SQL
(no Java) ??

Rgds

          Followup:

with tons and tons of code.

Lets see, you would have to write a mime encoder.
You would have to write a base64 encoder.
You would have to write tons and tons and tons of code.

Now, in 8i, utl_smtp is in java so you are already using java -- in for a penny
-- in for a pound as they say.  you are using java already so just add a "little
bit more" and you won't have to write scads of code.

      Rating: 5
Characters not
      in us7ascii  March 26, 2003
Reviewer:  Salaam
      Yitbarek  from Ottawa, ON Canada
So we are now successfully sending email using UTL_SMTP all except for one thing
-- accented characters in the subject field.

From the Solaris command line, I can successfully send mail with accented
characters. However, using UTL_SMTP, this does not work.

I don't mind it not working in the body, since we're sending html (&agrave
instead of à), but the subject line is a problem.

Our database character set is we8iso8859pi.

          Followup:

see

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76936/utl_smtp.htm#1000695
...
Note that the original SMTP protocol communicates using 7-bit ASCII. Using
UTL_SMTP, all text data (in other words, those in VARCHAR2) will be converted to
US7ASCII before it is sent over the wire to the server.
......

You can use javamail instead if you like.

      Rating: 5
UTL_SMTP and
      sending mail  March 28, 2003
Reviewer:  A K
      Prasanna  from India
I was pretty frustrated that "Expert One on One Oracle"
is not available in the library to refer to this topic but I don't think I need
any book now.Thank you very much.

      Rating: 5
email
      table  April 24, 2003
Reviewer:  mo

Tom:

1.  How do you send results of the emp table (or any sql query) using the above
email package.

Basically the result of this SQL command
 select * from emp1;

Thank you,

          Followup:

same way you get it onto an html page.

run it, fetch it, send it.  put it in a big string, email it.

      Rating: 4
email
      table  April 24, 2003
Reviewer:  mo

Tom:

I did this:

v_data   long default null;

FOR x in (select * from emp)
LOOP
v_data:=v_data||x.empno||' '||x.ename||' '||x.job||' '
||x.mgr||chr(10);
END LOOP;

then in the email package
 p_body => v_data

However the data is not lined up as it does in SQL*PLUS.

Is this how you do it and can i get it lined up?

Thank you,

          Followup:

well, I don't email myself "select * from table" output.

I email things like "hey, click on this link and you'll get a really pretty
report on the web".

But anyway, think fixed width

to_char( x.emp, '99999' || ' ' || rpad( x.ename, 20 ) || ' ' || rpad( ....

      Rating:
      5
email  April 24, 2003
Reviewer:  mo

Tom:

1.  When you schedule the job as
 dbms_job.submit(l_job,'SEND_EMAIL(JOB);');
DOes that send the email as soon as you hit commit?

I did it but it did not send an email. Is it because
job_queue_processes = 0.

2.  Do you delete records in tables email_parameters and job table after you
commit or every period.

Thank you

          Followup:

1) if you do not have job queues set up to run jobs, it'll not run.  set up the
job_queue_processes and it'll send shortly after you commit.

2) you can do whatever suits your needs, what I do isn't relevant here.  I keep
them as an audit and purge every now and again when I feel like it with
truncate.

      Rating: 5
Large to
      Lists  April 25, 2003
Reviewer:  Randall
       from St. Louis
Tom,  Thanks for all the help.  We have utl_smtp in 8.1.7 working great now for
some time.  We use it to notify employees when changes are scheduled on systems
that would affect them.

We did get an error today,
      "ORA-20001: 452 Too many recipients"

The email that it was tring to send had 512 names on it with the TO list having
a length of 14215.  The email body is only about 6k and will never have a body
bigger that 10k.  Even thought this large TO list is not normal, it can happen.
 So I guess I will write some logic to break up the TO list and send multi SMTP
emails.

Can you tell me what the limits are for UTL_SMTP for:
    1.  number of recipients
    2.  total size of TO: list (can you have multi TO: headers?)
    3.  max size of subject line
    4.  Does the 32k limit include body, TO list, and subject or just the size
of the body.

Thanks for all the help and have a good weekend.

          Followup:

this is NOT a utl_smtp limit.

this IS a smtp limit imposed by your SMTP server

snipped from RFC 2821 which talks about this

4.5.3.1 Size limits and minimums
[snip]
  recipients buffer
     The minimum total number of recipients that must be buffered is
     100 recipients. Rejection of messages (for excessive recipients)
     with fewer than 100 RCPT commands is a violation of this
     specification. The general principle that relaying SMTP servers
     MUST NOT, and delivery SMTP servers SHOULD NOT, perform validation
     tests on message headers suggests that rejecting a message based
     on the total number of recipients shown in header fields is to be
     discouraged. A server which imposes a limit on the number of
     recipients MUST behave in an orderly fashion, such as to reject
     additional addresses over its limit rather than silently
     discarding addresses previously accepted. A client that needs to
     deliver a message containing over 100 RCPT commands SHOULD be
     prepared to transmit in 100-recipient "chunks" if the server
     declines to accept more than 100 recipients in a single message.

  Errors due to exceeding these limits may be reported by using the
  reply codes. Some examples of reply codes are:

     500 Line too long.
  or
     501 Path too long
  or
     452 Too many recipients (see below)
  or
     552 Too much mail data.

  RFC 821 [30] incorrectly listed the error where an SMTP server
  exhausts its implementation limit on the number of RCPT commands
  ("too many recipients") as having reply code 552. The correct reply
  code for this condition is 452. Clients SHOULD treat a 552 code in
  this case as a temporary, rather than permanent, failure so the logic
  below works.

  When a conforming SMTP server encounters this condition, it has at
  least 100 successful RCPT commands in its recipients buffer. If the
  server is able to accept the message, then at least these 100
  addresses will be removed from the SMTP client's queue. When the
  client attempts retransmission of those addresses which received 452
  responses, at least 100 of these will be able to fit in the SMTP
  server's recipients buffer. Each retransmission attempt which is
  able to deliver anything will be able to dispose of at least 100 of
  these recipients.

  If an SMTP server has an implementation limit on the number of RCPT
  commands and this limit is exhausted, it MUST use a response code of
  452 (but the client SHOULD also be prepared for a 552, as noted
  above). If the server has a configured site-policy limitation on the
  number of RCPT commands, it MAY instead use a 5XX response code.
  This would be most appropriate if the policy limitation was intended
  to apply if the total recipient count for a particular message body
  were enforced even if that message body was sent in multiple mail
  transactions.

      Rating:
      5
email  April 25, 2003
Reviewer:  mo

Tom:

1.  The email package seems to mandate a p_from email address.  What about if
the email message is an a system alert and it is not really from anyone. What do
you do in this case? Do you put a dummy email address?

2.  IF you have multiple send email triggers do you always use the same
email_parameters table.  Would there ever be a conflict in job id if one for
example schedules the job two days later?

Thank you,

          Followup:

1) put in postmaster@host or something meaningful.  mail cannot be sent without
a from

2) one table, dbms_job generates a unique id using a sequence.

      Rating: 5
email with two
      emails addresses  May 07, 2003
Reviewer:  mo

Tom:

1. Do you know why the email package fails when I add two variables in the p_to
list?

2. It seems also if I put a  variable for the cc_list it does not work if that
vairable is null. I had to create an if statement to check if v_cc_email is null
and include if it is not and not include if it is. Does this sound correct?

 mail_pkg.send(
  p_sender_email=>l_rec.sender_email,
  p_from=>null,
  p_to=>mail_pkg.array(l_rec.pvt_email,l_req.request_email),
  p_cc=>mail_pkg.array(),
  p_bcc=>mail_pkg.array('xxx@yahoo.com'),
 p_subject => 'Request# '||l_rec.request_id||' - Shipment has been Completed',
 p_body => l_rec.email_data);

ERROR at line 1:
ORA-20002: 501 5.5.2 Syntax error in parameters scanning "TO"
ORA-06512: at "SYS.UTL_SMTP", line 86
ORA-06512: at "SYS.UTL_SMTP", line 223
ORA-06512: at "PSSADMIN.MAIL_PKG", line 13
ORA-06512: at "PSSADMIN.MAIL_PKG", line 55
ORA-06512: at line 7

          Followup:

probably has nothing to do with "two"

probably has everything to do with your email address not being well formed in
one of those two variables...

without an isolated, standalone, complete, runnable by my test case -- well, i
won't be able to say.

      Rating:
      5
email  May 08, 2003
Reviewer:  mo

Tom:

1.  The variables are defined for two valid email addresses. You can set any two
variables and try to pass them into the p_to list and it will fail.  I created
an array variable instead.  The problem with array vairable "l_emails" is that
you have to extend it exactly to the number of emails you are sending.  When I
did l_emails.extend(2) and my second email in the email_parameters table was
null the email package failed.  I had to pass i and figure out what i is.

          Followup:

show us the test case -- eh?

thats all I ask, self contained, reproducible, SMALL <<<=== really important.

your solution described fits NOT AT ALL with the problem presented:

 mail_pkg.send(
  p_sender_email=>l_rec.sender_email,
  p_from=>null,
  p_to=>mail_pkg.array(l_rec.pvt_email,l_req.request_email),
  p_cc=>mail_pkg.array(),
  p_bcc=>mail_pkg.array('xxx@yahoo.com'),
 p_subject => 'Request# '||l_rec.request_id||' - Shipment has been Completed',
 p_body => l_rec.email_data);

there is nothing to .extend there -- you are creating a temporary array.  what
you describe as your fix could have nothing to do with the example provided.

      Rating: 5
Send email does
      not work constantly   May 09, 2003
Reviewer:  A
      Reader  from USA
Hi Tom,
I have system trigger that uses the send mail procedure to send email to me
whenever there is a defined server error.  The trigger sometimes works fine, I
mean I receive emails when the errors occur.  Sometimes it does not work (I
don’t receive any emails where the errors occur).  I know that by checking the
trace files because there will be a trace file generated when the defined error
appears.  I want receive all of the emails when errors occur.  My question:
Where should I start with the debugging, the procedure or the trigger?  Or the
ora_is_servererror sometime does not response to the errors?  My tests worked
fine.  The sendmail procedure was copied and modified from the beginning of this
thread.
Thank you for your help.

CREATE OR REPLACE TRIGGER systrig_servererror
after servererror on database
begin
  IF (ora_is_servererror(60) or ora_is_servererror(4020)) THEN
    sendmail('dba@ourcompany.com','dba@ourcompany.com','A deadlock is detected
in prod_db');
  ELSIF (ora_is_servererror(1555)) THEN
    sendmail('dba@ourcompany.com','dba@ourcompany.com','Check rollback segments
in prod_db');
  END IF;
end;
/

          Followup:

If the sending of emails did not work -- asktom would be broken, my internal
website would be broken.

suggest you stick an "insert into msg_table (some string)" to see that the error
is getting fired.

1555's are not written to traces.

      Rating:
      5
email  May 09, 2003
Reviewer:  mo

TOm:

Here is the code. The email fails if the second email is null.

PSD> edit
Wrote file afiedt.buf

  1  declare
  2  l_rec           email_parameters%rowtype;
  3  l_emails        mail_pkg.array:=mail_pkg.array();
  4  begin
  5  select * into l_rec from email_parameters where id=108;
  6  dbms_output.put_line(l_rec.sender_email);
  7  dbms_output.put_line(l_rec.pvt_email);
  8  dbms_output.put_line(l_rec.request_email);
  9   l_emails.extend(2);
 10   l_emails(1) := l_rec.pvt_email;
 11   l_emails(2) := l_rec.request_email;
 12   mail_pkg.send(
 13          p_sender_email=>l_rec.sender_email,
 14     p_from=>null,
 15             p_to=>l_emails,
 16             p_cc=>mail_pkg.array(),
 17     p_bcc=>mail_pkg.array(),
 18     p_subject => 'Request# '||l_rec.request_id||' - Shipment has been
Completed',
 19          p_body => l_rec.email_data);
 20* end;
 21  /

declare
*
ERROR at line 1:
ORA-20002: 501 5.5.2 Syntax error in parameters scanning "TO"
ORA-06512: at "SYS.UTL_SMTP", line 86
ORA-06512: at "SYS.UTL_SMTP", line 223
ORA-06512: at "IMSADMIN.MAIL_PKG", line 13
ORA-06512: at "IMSADMIN.MAIL_PKG", line 55
ORA-06512: at line 12

          Followup:

It is exactly what I said above then

probably has everything to do with your email address not being well formed in
one of those two variables...

tell me, is 'null' a valid email?  no, it is not.  Hence, you get the SMTP error
(not an oracle error) of 501 saying "hey, that is not a valid well formed email
address"

      Rating:
      5
email  May 09, 2003
Reviewer:  mo

Tom:

1. How would you get around it if one of the cc emails is null.  Do you always
check how many emails are not null and then do extend(i) rather than a fixed
count?

2.  The email package works if one of the emails is not valid at all? e.g, if i
type 123@yahoo.com it will still work, even though it is not valid email?

          Followup:

i would make darn sure there were no null emails in the array

obviously, since you own the code, you can modify the lowest level routine to
just ignore nulls.

123@yahoo.com is perfectly "valid".  It might not exists, but it conforms to the
rules for email address format.

      Rating: 4
mail_pkg not
      working in the trigger  May 12, 2003
Reviewer:
      asusarla  from Nz
This goes into Exception and uses send_mail
li_rec changerequest%ROWTYPE;
CURSOR li (arg IN NUMBER) IS
 SELECT * FROM changerequest WHERE changerequestid= arg;
 BEGIN
 SELECT emailaddress, preferredname, surname
 INTO recipient, givenname, surname
 FROM mituser
 WHERE mituserid = :new.raisedby;
 IF :new.statuslookup = 8 THEN
 subjectline := 'Your New Follow-up request #'||:new.changerequestid;
 message_body := 'Dear '||givenname||' '||surname||','||crlf||crlf;
 message_body := message_body||'Thank you for submitting your ';
 message_body := message_body||'Follow-up ';
 message_body := message_body||'request #
'||:new.changerequestref||'.'||crlf||crlf;
 message_body := message_body||'Your request for the following ';
 message_body := message_body||'has been received:'||crlf||crlf;
 message_body := message_body||'changerequestid';
 OPEN li (:new.changerequestid);
  LOOP
 FETCH li INTO li_rec;
 EXIT WHEN li%NOTFOUND;
 message_body
:=message_body||SUBSTR(RPAD(li_rec.changerequestref,14),1,14)||tab;
 message_body
:=message_body||SUBSTR(RPAD(li_rec.shortdescription,16),1,16)||crlf;
END LOOP;
message_body := message_body||crlf||' Please do not reply to it.';
End If;
MAIL_PKG.SEND(
 p_sender_email => 'aparna.susarla@barnardos.org.nz',
 p_from => 'The Request Processing System
 <aparna.susarla@barnardos.org.nz>',
 p_to => mail_pkg.array('aparna.susarla@barnardos.org.nz',recipient),
 p_cc => mail_pkg.array('aparna.susarla@barnardos.org.nz '),
 p_bcc => mail_pkg.array('aparna.susarla@barnardos.org.nz'),
 p_subject => subjectline,
 p_body => message_body);
 EXCEPTION
 WHEN OTHERS THEN
 BEGIN
 SEND_MAIL(

          Followup:

when others means you have a bug in your code.

get rid of it.  then you'll actually be able to tell what bug you have as you'll
get a meaningful error message.

      Rating: 4
mail_pkg not
      working in the trigger   May 12, 2003
Reviewer:
      asusarla  from Nz
The other part of this trigger is as follows. I get an email with send_mail
procedure with the message.If I try to run it with just mail_pkg does not allow
the table to be updated.

EXCEPTION
 WHEN OTHERS THEN
 BEGIN
 SEND_MAIL(
P_SENDER => 'aparna.susarla@barnardos.org.nz',
 P_RECIPIENT => recipient,
 P_MESSAGE => 'A problem occurred during the firing of '||
 'trigger trg_changerequeststatus '||
'for request #'||:new.changerequestid);
 EXCEPTION
 WHEN OTHERS THEN
 null;
 END;
 END;
/

          Followup:

you have a bug.

the "when others" exception handler is hiding this bug from you.

"when others" in code that is not followed by "RAISE" is a bug.  You have two
that I see.  What happens when send mail fails?  Oh - no one EVER knows.

lose the when others, fix the third bug that is hiding in there once you know
where and what it is!

      Rating: 4
Bulk email and
      sessions  May 13, 2003
Reviewer:  Bastiaan
       from The Netherlands
Hi Tom,

We are using a similar procedure to send mail from our (PL/SQL web-)
applications. From one of these applications we send personalised mail to people
on a mailing list. We have a job scheduled to handle this. Now we have reached a
point where our mailing list contains about 1500 mail-adresses, and the
procedure that is scheduled in the job starts to fail. After investigating it
turned out that the procedure failed every time it reached the 493rd person on
the list. At that time there were 7 other sessions active (checked in
v$sessions). Turns out that 493 + 7 is the maximum number of sessions we
specified in the init.ora. Does every utl_smtp connection to a mailserver cause
a (hidden) session to be started? Or is this just a coincidence?

          Followup:

believe it is a coincidence. I send hundreds/thousands of emails daily using
this technique.

do you have an error message.

      Rating:
      4
  May 13, 2003
Reviewer:
      A reader
hello tom,

I took this from your 1st sample above:

declare
  l_mailhost  VARCHAR2(255) := 'DEESSMAIL';
  l_mail_conn utl_smtp.connection;

  p_sender    VARCHAR2(50) := 'me@somewhere.net';
  p_recipient VARCHAR2(50) := 'me@somewhere.net';
  p_message   VARCHAR2(20) := 'message content';

BEGIN
  l_mail_conn := utl_smtp.open_connection(l_mailhost, 25);
  utl_smtp.helo(l_mail_conn, l_mailhost);
  utl_smtp.mail(l_mail_conn, p_sender);
  utl_smtp.rcpt(l_mail_conn, p_recipient);
  utl_smtp.open_data(l_mail_conn );
  utl_smtp.write_data(l_mail_conn, p_message);
  utl_smtp.close_data(l_mail_conn );
  utl_smtp.quit(l_mail_conn);
end;

but got only EMPTY messages. can you give some advice?

          Followup:

I just used that block (changed the email/l_mailhost) and recieved:

From - Tue May 13 16:53:30 2003
X-Mozilla-Status: 0001
X-Mozilla-Status2: 00000000
Return-Path: <tkyte@oracle.com>
Received: from rgmum12.us.oracle.com by rgmum13.us.oracle.com
    with ESMTP id 188485611052859142; Tue, 13 May 2003 14:52:22 -0600
Return-Path: <tkyte@oracle.com>
Received: from rgmgw4.us.oracle.com by rgmum6.us.oracle.com
    with ESMTP id 157896281052858993; Tue, 13 May 2003 16:49:53 -0400
Received: from rgmgw4.us.oracle.com (localhost [127.0.0.1])
    by rgmgw4.us.oracle.com (Switch-2.1.5/Switch-2.1.0) with ESMTP id
h4DKnoa16288
    for <thomas.kyte@rgmumsmtp.oraclecorp.com>; Tue, 13 May 2003 14:49:51 -0600
(MDT)
Received: (from mailora@localhost)
    by rgmgw4.us.oracle.com (Switch-2.1.5/Switch-2.1.0) id h4DKnmb16120
    for thomas.kyte@rgmumsmtp.oraclecorp.com; Tue, 13 May 2003 14:49:48 -0600
(MDT)
X-Authentication-Warning: rgmgw4.us.oracle.com: mailora set sender to
tkyte@oracle.com using -f
Received: from rgmgw4.us.oracle.com (localhost [127.0.0.1])
    by rgmgw4.us.oracle.com (Switch-2.1.5/Switch-2.1.0) with ESMTP id
h4DKnLa15341
    for <tkyte@oracle.com>; Tue, 13 May 2003 14:49:21 -0600 (MDT)
Received: from gmamersmtp.oraclecorp.com
(dhcp-amer-vpn-gw2-west-141-144-91-44.vpn.oracle.com [141.144.91.44])
    by rgmgw4.us.oracle.com (Switch-2.1.5/Switch-2.1.0) with SMTP id
h4DKnLJ15321
    for tkyte@oracle.com; Tue, 13 May 2003 14:49:21 -0600 (MDT)
Date: Tue, 13 May 2003 14:49:21 -0600 (MDT)
From: tkyte@oracle.com
Message-Id: <200305132049.h4DKnLJ15321@rgmgw4.us.oracle.com>

message content

As my email -- sounds like it might be a problem with your email client, ask it
to show you the RAW email message (like I did above -- ctl-u if you are using
mozilla, although mozilla shows it right the first time)

we are just talking "straight to smtp" here.  You can do this with telnet - so
the issue is NOT in the database.

      Rating: 4
Bulk email and
      sessions part II  May 14, 2003
Reviewer:
      Bastiaan  from The Netherlands
Hi Tom,

Thank you for looking into my problem. The error messages I get back from the
procedure are:

ORA-29278: SMTP transient error: 421 Service not available
ORA-00018: maximum number of sessions exceeded

I don't understand this because there is only 1 job, that runs the procedure.
The procedure just takes a cursor loop through a table, and sends an email for
every record...

My idea is that it's maybe the mail-server that doesn't accept any more
connections (because of the 421 SMTP error), but within the procedure the smtp
connection is closed, and at any given moment there should be only one
connection from the database to the mailserver...

          Followup:

I haven't seen this myself -- but then, I use one connection call period in my
procedures.

      Rating: 4
Seding Email
      !  July 07, 2003
Reviewer:  vinodhps
       from India, Chennai.
Hi.. Tom ,
Hope you are fine,

as you said i created the procedure.. but iam getting the same error , how to
solve this problem can you please help me out..

oiisdba@TEST902> create or replace
  2  PROCEDURE send_mail (p_sender       IN VARCHAR2,
  3                       p_recipient IN VARCHAR2,
  4                       p_message   IN VARCHAR2)
  5  as
  6     -- Note that you have to use a host
  7     -- that supports SMTP and that you have access to.
  8     -- You do not have access to this host and must change it
  9     l_mailhost VARCHAR2(255) := 'tnpl.co.in';
 10     l_mail_conn utl_smtp.connection;
 11  BEGIN
 12     l_mail_conn := utl_smtp.open_connection(l_mailhost, 25);
 13     utl_smtp.helo(l_mail_conn, l_mailhost);
 14     utl_smtp.mail(l_mail_conn, p_sender);
 15     utl_smtp.rcpt(l_mail_conn, p_recipient);
 16     utl_smtp.open_data(l_mail_conn );
 17     utl_smtp.write_data(l_mail_conn, p_message);
 18     utl_smtp.close_data(l_mail_conn );
 19     utl_smtp.quit(l_mail_conn);
 20  end;
 21  /

Procedure created.

oiisdba@TEST902> set serveroutput on
oiisdba@TEST902> declare
  2          l_start number := dbms_utility.get_time;
  3  begin
  4          send_mail( 'someone@there.com',
  5                     'shabobby@yahoo.com', 'hey there' );
  6          dbms_output.put_line
  7          ( round( (dbms_utility.get_time-l_start)/100, 2 ) ||
  8            ' seconds' );
  9  end;
 10  /
declare
*
ERROR at line 1:
ORA-29278: SMTP transient error: 421 Service not available
ORA-06512: at "SYS.UTL_SMTP", line 17
ORA-06512: at "SYS.UTL_SMTP", line 96
ORA-06512: at "SYS.UTL_SMTP", line 138
ORA-06512: at "OIISDBA.SEND_MAIL", line 11
ORA-06512: at line 4

          Followup:

that means, just as it did before, that your host you are using is not running
smtp services.  tnpl.co.in is not running smtp on port 25.

ask your SA's where there is an smtp server in your network that does relaying.

      Rating: 5
Sending Email
      !  July 07, 2003
Reviewer:  vinodhps
       from India,Chennai
Hi.. Tom,
we got only one mail server and can we get the host name if i know the ip
address of mail server, if yes can you tell me .
if i try with that ip address iam getting error

oiisdba@TEST902> /
declare
*
ERROR at line 1:
ORA-29279: SMTP permanent error: 553 sorry, that domain isn't in my list of
allowed rcpthosts
(#5.7.1)
ORA-06512: at "SYS.UTL_SMTP", line 17
ORA-06512: at "SYS.UTL_SMTP", line 98
ORA-06512: at "SYS.UTL_SMTP", line 240
ORA-06512: at "OIISDBA.SEND_MAIL", line 14
ORA-06512: at line 4

It is SMTP mail server only ..
can you help me Tom

          Followup:

you are going to need to talk to your system admins here, this is not an Oracle
error, it is purely an SMTP configuration issue at this point.

it is probably that you are not allowed to relay mail to @there.com

      Rating:
      4
  July 16, 2003
Reviewer:  Flemming
       from Denmark
Hi Tom
I have a problem with the time-stamp of the email. In my code I have this
statement:
utl_smtp.write_data(v_smtp_connection, 'DATE: '|| to_char(sysdate,'DD-MON-YYYY
HH24:MI') || utl_tcp.CRLF);
I have verified that the time is correct in the code, but on the email the label
for Sent shows a time that is 2 hours later. Any ideas ?
/Flemming

          Followup:

timezones perhaps -- if it is correct to the 'minute', i would guess a time zone
change is taking place either along the way or by the email client itself.

      Rating: 4
Ackownledgement
      Email...  July 20, 2003
Reviewer:  vinodhps
       from India,chennai
Hi Toms,
Now its working fine, and your Book and your website are so useful and your new
book was good.. i read all the 3 chapters.. waiting to get one copy for me .. so
when it is going to come in market.. (India)..

well i got recuirement, that after we send mail, is it possible to know that
whether they have opened the mail , something like Ackownledgement - mail.when
the opened.....

Is it possible Tom , If so how can we do that tom.

Thankyou

          Followup:

that is a protocol private to email -- the CLIENT recieving it must be willing
to send it (i for example, have that disabled -- i do not send ack's back)

then, you would need a client to read it.

In short - yes, you could -- but it would be fairly involved and outside of "an
example" (meaning I'm not going to build it)

You would need to

a) find out what the protocol for acknowledgement is (something in the header
undoubtably)

b) send that

c) figure out how you want to read the email (could be a java stored procedure
in the database if you wanted)

      Rating: 4
acknowledgemen
      Mail  July 21, 2003
Reviewer:  vinodhps
       from India, chennai
Hi Toms ....
for you responce ..
i searched in google.. i could not able to find any document which will help
me..out ..

i will try myself..can you give some useful links or site where i can find
anything about acknowledgemen mail ...tom

all i want is just to know wether they read the mail or not.

a) "(something in the header undoubtably)"

Can you explain this tom.

Thank you for your time

          Followup:

to tell whether they read a mail, the CLIENT must participate (meaning there is
NO WAY you can rely on this even a little -- I for example disallow 100% the use
of this feature on my client, it is NONE of your business when or if I've read
your mail).

there must be some header that does this, me, being a database guy, not an email
guy -- doesn't know the header in general.

try searching for

"return receipt" "header"

on google

      Rating: 4
Returning
      mail  July 22, 2003
Reviewer:  vinodhps
       from India, Chennai
Hi Tom,
Thanks I will try to get that
Thanks for you time tom

      Rating: 4
sending email
      attachments  July 23, 2003
Reviewer:  Able
       from KSA
Hi Tom,

Please help me, as I got the following errors which is misguiding when i run the
script to create procedure for sending attachment through mail. I have
successfully loaded mail.jar and activation.jar files (Javamail API)

I appreciate your earlier reply.

Errors for JAVA SOURCE MAIL:

LINE/COL ERROR
-------- -----------------------------------------------------------------
0/0      MAIL:83: Interface DataSource of nested class mail.
         BLOBDataSource not found.

0/0      MAIL:31: Class Message not found.
0/0      MAIL:32: Class MimeMessage not found.
0/0      MAIL:38: Variable msg may not have been initialized.
0/0      MAIL:41: Variable msg may not have been initialized.
0/0      MAIL:44: Variable msg may not have been initialized.
0/0      MAIL:45: Variable msg may not have been initialized.
0/0      MAIL:46: Variable msg may not have been initialized.
0/0      MAIL:49: Class MimeBodyPart not found.
0/0      MAIL:49: Class MimeBodyPart not found.
0/0      MAIL:52: Class MimeBodyPart not found.
0/0      MAIL:52: Class MimeBodyPart not found.
0/0      MAIL:62: Class MimeMultipart not found.
0/0      MAIL:62: Class MimeMultipart not found.
0/0      MAIL:71: Undefined variable or class name: Transport

LINE/COL ERROR
-------- -----------------------------------------------------------------
0/0      Info: 15 errors

      Rating: 5
Sending e-mail
      to groups of users at once  July 30, 2003

Reviewer:  Daryl Green  from Arlington, VA

Tom,

How can you use these methods to send e-mails to a group of users (at once)
instead of addressing them each individually. I have security set up on my NT
8.1.6.3 database with profiles and would like to automatically send an email to
all users whose password will expire in 10 or less days on a daily basis until
the password has been successfully changed. Also, i have a web form (using
Webdb) to allow user to change their passwords. I want to send an individual
e-mail to them. How can this be done!

Thanks for all of you help!!!

          Followup:

you have to make an RCPT call once for each user is all.

      Rating: 5
Very
      nice  July 31, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
Hi TOM,
I tried this 8i but it was not working there may be due to my poor knowledge of
Java. Now in Oracle9i Rel2 I have tried and it is working.

Thanks for such a cool stuff.

      Rating: 5
How to send
      email to local users?  August 08, 2003
Reviewer:
      Sikandar Hayat  from Pindi Bhattian - Pakistan
I am able to send email to those who have SMTP account. Now our many local users
don't have internet account so let me know if is there any way to send emails to
them. We are using window with exchange server 5.5.

          Followup:

exchange supports SMTP.

      Rating: 5
I am not
      getting the Message body  September 03, 2003

Reviewer:  Sara  from Atlanta, GA
I have used your query...but i am not getting any message body..? what is the
problem in the query.

create or replace PROCEDURE send_mail (p_sender       IN VARCHAR2,
                         p_recipient IN VARCHAR2,
                         p_message   IN VARCHAR2)
    as
       l_mailhost VARCHAR2(255) := 'smtpgw.cdc.gov';
       l_mail_conn utl_smtp.connection;
    BEGIN
       l_mail_conn := utl_smtp.open_connection(l_mailhost, 25);
      utl_smtp.helo(l_mail_conn, l_mailhost);
      utl_smtp.mail(l_mail_conn, p_sender);
      utl_smtp.rcpt(l_mail_conn, p_recipient);
      utl_smtp.open_data(l_mail_conn );
      utl_smtp.write_data(l_mail_conn, p_message);
      utl_smtp.close_data(l_mail_conn );
      utl_smtp.quit(l_mail_conn);
   end;

 begin
     send_mail( 'bcv5@cdc.gov',
                'bcv5@cdc.gov',
                       'Hello Tom' );
    end;
If i run the procedure all i am getting is the from email id.

Awaiting your response

          Followup:

in your email client, use the option to view the original unformatted email.
Perhaps your client is hiding it from you. (this works -- this is just standard
smtp stuff -- nothing simplier -- but I can imagine the email client you are
using it mucking it up somehow.  Lets see the "real" email)

      Rating: 5
reply
      immd..  September 16, 2003
Reviewer:  suresh
       from India
  How to pass PL/SQL TABLE FROM D2K TO PL/SQL PROCEDURE. ?

   I declared one Package of two pl/sql table types and one Procedure , i am
access these types(declare variable of that type) in D2K and papulate some
values on that pl/sql tables(fornt end), after that i called Packed Procedure by
passing these PL/SQL Tables from frontend D2K TO THAT Package Procedure backend
, how to do this ?

  is their any Prob's appares ? by passing frontend tables(pl/sql) to back end
of (same type).
   reply tom, immd...

          Followup:

sorry, don't do forms, don't have forms, haven't touched forms since 95'

you can try otn.oracle.com -> discussion forums.

      Rating: 5
Sending fax
        September 23, 2003
Reviewer:
      A reader
Tom

Is there anything 8i , 9i or 10G which would enable faxing from the database,
just like emailing from the database, illustrated in this example ?

Thanks

          Followup:

drop a java stored procedure in there -- there must be one out there that can
interface with some fax software.

(oracle collaboration suite does this as well)

      Rating: 4
For time
      zones  September 25, 2003
Reviewer:  Jon
       from Richmond, KY USA
Thanks Tom, this works great for us.

We were concerned at first because our time was coming out wrong.  Then we
realized that we needed to append ' -0400' to the date so that it would show the
time for the correct time zone (in our case, EST).  Just thought other people
may have ran into this problem...

      Rating: 5
Getting error
      when using JavaMail  October 02, 2003
Reviewer:
      Andrew T. Nguyen  from Kansas City, MO USA
Hi Tom,

I am running Oracle 8.1.7 on Windows 2000. I can get UTL_SMTP to send emails
fine. However when I use the Javamail API as shown, I get the following errors:

javax.mail.SendFailedException: Sending failed;
  nested exception is:
        class
javax.mail.MessagingException: Could not connect to SMTP host: localhost, port:
25;
  nested exception is:
        java.net.ConnectException: Connection refused
at javax.mail.Transport.send0(Transport.java:218)
at javax.mail.Transport.send(Transport.java:80)
at mail.send(mail:86)
Failed to send message...

I used the same smtp host in the Utl_smtp and it worked fine.  Any idea?
Thanks

          Followup:

your sendmail is running on the host "localhost"?  127.0.0.1??

      Rating: 5
Reply: Getting
      error when using JavaMail   October 03, 2003

Reviewer:  Andrew  from Kansas City, MO USA

Yes, I tested on my PC with local host 127.0.0.1.
Is it OK for this JavaMail setting? Do I need to set up some thing else for the
local host?

Thanks.

          Followup:

should work OK i suppose.  i'm not an smtp/java expert by anymeans.  this is
simply saying "the connection didn't work -- we tried"

you did do the grants right?  using dbms_java??

      Rating: 5
Reply: Getting
      error when using JavaMail   October 04, 2003

Reviewer:  Andrew  from Kansas City, MO USA

Hello Tom,

It worked....  I found the error in the Java stored procedure, fixed, and
recompiled in database and the javamail api works fine.

Thank you very much.

      Rating: 4
ORA-03113 while
      calling utl_smtp.open_connection  October 10, 2003

Reviewer:  Mohan  from India
When I call the function UTL_SMTP.OPEN_CONNECTION from a PL/SQL block gives the
error ORA-03113: end-of-file on communication channel. How to catch the
exception here.

          Followup:

ora 3113 sort of means "bamm, your dead, game over player one, do not pass go,
do not collect $200"

Your session is over.

Please contact support for this one.

      Rating: 5
most
      useful  October 13, 2003
Reviewer:  appreciative
      fan  from MA
Tom,

Been using this for awhile, and keep coming back to this link to find this
procedure when it is needed in different places (since I don't keep it in my
shirt pocket).

The package spec and package body for the send procedure are out of sync now
though.  default NULLS that are present in the body are missing from the spec on
two or three of the attributes.

          Followup:

they've always been like that :)

it just wasn't a problem until you upgraded to 9i!  I had a bug that was OK in
8i, not in 9i.  Just remove the defaults.

      Rating: 5
Error
      Encountered  October 21, 2003
Reviewer:  Anirudh
      Sharma  from N.Delhi, India
Hi Tom,
I encountered the following error while trying to send the mail through java.
anirudh@DW9I01T.US.ORACLE.COM 9.2> ed
Wrote file afiedt.buf

  1  declare
  2    ret_code number;
  3  begin
  4    for i in (select attachment from mailattachment )
  5    loop
  6       ret_code := send(
  7                     p_from => 'anirudhs@noida.hcltech.com',
  8                     p_to => 'anirudhs@noida.hcltech.com',
  9                     p_cc => null,
 10                     p_bcc => NULL,
 11                     p_subject => 'testing',
 12                     p_body => 'JAI HIND',
 13                     p_smtp_host => 'exch-01.noida.hcltech.com',
 14                     p_attachment_data => i.attachment,
 15                     p_attachment_type => 'text/plain',
 16                     p_attachment_file_name => 'hello.txt');
 17       if ret_code = 1 then
 18         dbms_output.put_line ('Successfully sent message...');
 19       else
 20         dbms_output.put_line ('Failed to send message...');
 21       end if;
 22     end loop;
 23*    end;
anirudh@DW9I01T.US.ORACLE.COM 9.2> /
javax.mail.NoSuchProviderException: smtp
at javax.mail.Session.getService(Session.java:750)
at javax.mail.Session.getTransport(Session.java:689)
at javax.mail.Session.getTransport(Session.java:632)
at javax.mail.Session.getTransport(Session.java:612)
at javax.mail.Session.getTransport(Session.java:667)
at javax.mail.Transport.send0(Transport.java:154)
at javax.mail.Transport.send(Transport.java:80)
at mail.send(mail.java:86)
Failed to send message...

PL/SQL procedure successfully completed.
---------------------------------------------------------------------------------
---------------------------------------
I tried to exclude Oracle from this and do the process using java itself. Wrote
follo code in java but it gave the same error:
  import java.util.Properties;
    import javax.mail.*;
   import javax.mail.internet.*;

   public class MailJava
   {

    public static void send(String mailserver,String subject ,String to[],
String from, String messageText) throws AddressException, MessagingException
    {
     try
    {
        Properties mailProps = new Properties();
        mailProps.put("mail.smtp.host", mailserver);
        //mailProps.put("mail.debug", true);
        Session mailSession = Session.getDefaultInstance(mailProps, null);
        //mailSession.setDebug(true);
        // Construct addresses

        int toCount = to.length;
        InternetAddress[] toAddrs = new InternetAddress[toCount];
        for (int i = 0; i < toCount; ++i)
        {
            toAddrs[i] = new InternetAddress(to[i]);
        }
        InternetAddress fromAddr = new InternetAddress(from);
         // Create and initialize message
        Message message = new MimeMessage(mailSession);
        message.setFrom(fromAddr);
        System.out.println("30::"+Message.RecipientType.TO);
        message.setRecipients(Message.RecipientType.TO, toAddrs);
        System.out.println("35::"+subject);
        message.setSubject(subject);
        System.out.println("40::"+messageText.toString());
        message.setContent(messageText.toString(), "text/plain");

        // Send message
        Transport.send(message);
    }
    catch(Exception e)
    {
        System.out.println(e);
    }
}

public static void main(String[] args)
{

//MailJava.send(exch-01.noida.hcltech.com,"testSubject","anirudhs@noida.hcltech.c
om","From","","","Go_to_Hell","I****Love****You","exch-01.noida.hcltech.com","tex
t/plain","anything.zip");
    String too[] = {"anirudhs@noida.hcltech.com"};
    try{
    MailJava.send("EXCH-01","testSubject",too,"From","Error report");
}
catch(Exception e){System.out.println(e);}
}
}
--------------------------
-------------------------

D:\temp\activation8i>javac MailJava.java

D:\temp\activation8i>javac MailJava.java

D:\temp\activation8i>java MailJava
30::To
35::testSubject
40::Error report
javax.mail.NoSuchProviderException: smtp

D:\temp\activation8i>javac MailJava.java

D:\temp\activation8i>java MailJava
30::To
35::testSubject
40::Error report
javax.mail.NoSuchProviderException: smtp
------------------------
--------------------------
Please throw some light on what the problem could actually be.
Thanks,
Anirudh

          Followup:

well, that would be a java/smtp question.  apparently, you don't have an smtp
server at that hostname.

      Rating: 3
Sending Email
      Raw data  October 22, 2003
Reviewer:  Vikas
      Sharma  from Delhi India
Hi Tom,

I am sorry if i am posting this review at a wrong place.

I am trying to send mail using utl_smtp. I am not using the javamail api. I have
writtena code using utl_smtp and setting up the MIME headers to send the text,
html and any text files succesfully.

But i am facing problem in sending the birary file. I have set the mimie
headers, i getting my file data send to mail server also but when i try to
download the file it download with junk raw data.

To send the binary attachments i am doing the following things.
1. setting the mime headers to

--abs2sc3d74F7A1CD

Content-Type: application/octet
Content-Disposition: attachment; filename="mail.doc"
Content-Description: test-part-2
Content-Transfer-Encoding:base64

2. After setting the header i read the content of my file "mail.doc" and load
the content to a blob using dbms_lob.loadfromfile. so now i have the content of
the file into a blob. now i am using the following peice of code to write the
blob data on SMTP

   ======================
   l_offset  := 1;
   l_ammount := 57;
   while l_offset < dbms_lob.getlength(l_blob) loop
     utl_smtp.write_raw_data(l_connection,

(utl_encode.base64_encode(utl_raw.cast_to_raw(dbms_lob.substr(l_blob,l_ammount,l_
offset)))));
           l_offset  := l_offset + l_ammount ;
           l_ammount := least(57,dbms_lob.getlength(l_blob) - l_ammount);
   end loop;
   utl_smtp.write_data(l_connection,end_boundary); -- writing end boundary

  =================================================

  I am not sure is this the correct way of doing it. I have the data into a BLOB
Do i need to cast in raw before i use utl_smtp.write_raw_data.?

If i cast to raw it using
(utl_smtp.write_raw_data(l_connection,

(utl_encode.base64_encode(utl_raw.cast_to_raw(dbms_lob.substr(l_blob,l_ammount,l_
offset)))));

then the result of the content of files are like
 ===============================
  Content-Transfer-Encoding:base64

  RDBDRjExRTBBMUIxMUFFMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw
  M0UwMDAzMDBGRUZGMDkwMDA2MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxMDAwMDAw

MjEwMDAwMDAwMDAwMDAwMDAwMTAwMDAwMjMwMDAwMDAwMTAwMDAwMEZFRkZGRkZGMDAwMDAwMDAyMDAwM
DAwMEZG

 =================

If i dont use the use utl_raw.cast_to_raw ie using
utl_smtp.write_raw_data(l_connection,
 (utl_encode.base64_encode(dbms_lob.substr(l_blob,l_ammount,l_offset))));

then the result are as follows

===============
Content-Transfer-Encoding:base64

0M8R4KGxGuEAAAAAAAAAAAAAAAAAAAAAPgADAP7/CQAGAAAAAAAAAAAAAAABAAAA
IQAAAAAAAAAAEAAAIwAAAAEAAAD+////AAAAACAAAAD/////////////////////////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
=================

Pl suggest what i am doing wrong. i think somting is wrong when i am doing
conversion of blob to raw.

Regards,

Vikas Sharma

          Followup:

sorry -- i don't know myself how to mime encode an entire email.  I took the
easy way out and borrowed the code from sun.

I would suggest the same to you!  Its a nightmare trying to encode that junk.

      Rating: 3
UTL_SMTP with
      raw data  October 22, 2003
Reviewer:  Vikas
      Sharma  from Delhi India
Hi Tom,

Thanks For the reply.

I tried the Java API approach earlier also but it did not worked out. Recently I
tried again but getting error

declare
  ret_code number;
begin
  for i in (select filename from mailattachments)
  loop
    ret_code := send(
                  p_from => 'vikas03@acme.com',
                  p_to => 'vsharma012@hotmail.com',
                  p_cc => NULL,
                  p_bcc => NULL,
                  p_subject => 'Use the attached Zip file',
                  p_body => 'to send email with attachments....',
                  p_smtp_host => 'mtlweb',
                  p_attachment_data => i.filename,
                  p_attachment_type => 'application/winzip',
                  p_attachment_file_name => 'ld_mem_banks.zip');
    if ret_code = 1 then
      dbms_output.put_line ('Successfully sent message...');
    else
      dbms_output.put_line ('Failed to send message...');
    end if;
  end loop;
end;
/
javax.mail.SendFailedException: Sending failed;
  nested exception is:
        class javax.mail.MessagingException: IOException while sending message;
  nested exception is:
        javax.activation.UnsupportedDataTypeException: no object DCH for MIME
type multipart/mixed;
        boundary="----=_Part_1_-909169289.1066049169595"
at javax.mail.Transport.send0(Transport.java:218)
at javax.mail.Transport.send(Transport.java:80)
at mail.send(mail:86)
Failed to send message...

PL/SQL procedure successfully completed.

i posted the error earlier also see above.
So i thought i will go for mime approach. And  try with that.

Can you pl suggest me why i am encounter the above error my database is
8i(8.1.7). I loaded the mailapi version javamail 1.3 and frame work jaf-1.0.2 on
sys account using your guide lines.

Btw pl tell me how can i use the blob data with  "utl_smtp.write_raw_data" i am
doing as following :

utl_smtp.write_raw_data(l_connection,

(utl_encode.base64_encode(utl_raw.cast_to_raw(dbms_lob.substr(l_blob,l_ammount,l_
offset)))));

Is this correct?

Thanks & Regards

Vikas Sharma

          Followup:

i'll have a java dude take a look see and if he has any advice, will follow up.

base64 encoding -- as I remember it takes 3 bytes to store 2 or something like
that.  probably a boundary value condition, not sure that you can just "substr"
like that and encode chunks.  (this is why this becomes "hard".  you have learn
all about b64 encoding, something I haven't looked at in years myself)

and he says (the java guy)

Can you ask them to send a description of their table named  "mailattachments".
I wonder why they are setting the "filename" column  to the actual data.

Also, it's looking for a data handler for "multipart/mixed" but I  wonder why
they are trying to attach that type to a mail message.

Even though he said he followed your instructions, I'd still like to  see
exactly how they loaded the the mail.jar and activation.jar

      Rating: 3
API
      Error  October 22, 2003
Reviewer:  Vikas
      Sharma  from Delhi India
Hi Tom,

Thanks,

If you want i can send you the complete description how i loaded classes and
what error (output of loadjava) i got earlier by Email. It is not big abt 7k zip
file.

Actually when i tried to load mail.zar on sys using loadjava i received few
errors but then again i tried for second time, i did NOT get any error.

But my loadjava of activation.zar was finished with out any error on sys.

Thanks And Regards

Vikas Sharma

      Rating: 5
JavaMail on
      9.2.0.4?  October 23, 2003
Reviewer:  Doyle
      Freeman  from West Des Moines, IA
Is javamail still the best way to send an email with large attachments from
pl/sql?
--
To be more clear about my specific issue.
--
I am creating an app with html db (love it by the way).
--
The user is able to select a file from their client machine and upload it. I
then want to email that file to another party after I have done some validation
on it thru pl/sql. I have a pl/sql package that gets fired after the file is
uploaded that is reading the blob and verifying some business rules. If all of
the business rules pass, then I want to email the blob as an attachment to
another party.
--
Would you use the javamail through pl/sql to accomplish this, or is there a
better way now?
--
Thanks

          Followup:

yes.  it is the best way (until 10g)

java mail -- it is the best way.

      Rating: 3
Sending
      CLOB/BLOB with javamail  October 23, 2003

Reviewer:  paul  from Ottawa, ON, Canada
I dont do this with UTL_SMTP but we do it with javamail if you want to go back
to that approach.

Here is some background.
a GTT gets populated with a list of external files and/or blobs, and/or clobs to
attach to mail,
-we figure out mimetype based on extension.
-we use send_as_name to allow user to change filename of source document while
sending.

the Java reads the GTT and  for each record in resultset determines if
File/Blob/Clob
[..]
if (rset.getString("file_type").equals("BLOB"))
  {
   MimeBodyPart mbp = new MimeBodyPart();
   mbp.setDisposition(Part.ATTACHMENT);
   mbp.setFileName(rset.getString("send_as_name"));
   mbp.setDataHandler
(new DataHandler(new BLOBDataSource(rset.getBlob ("blob_data") , set.getString
"mime_type"))));
   mp.addBodyPart(mbp);
}
[..]
BLOBDatasource simply returns a binaryDatastream like so...
static class BLOBDataSource implements DataSource
{
private java.sql.Blob   data;
 private String type;
BLOBDataSource(java.sql.Blob data, String type)
{
this.type = type;
this.data = data;
}
 public InputStream getInputStream() throws IOException
{
 try
{
if(data == null)
throw new IOException("No data.");
return data.getBinaryStream();
} catch(SQLException e)
{
throw new
IOException("Cannot get binary input stream from BLOB.");
}
}
public OutputStream getOutputStream() throws IOException
{
throw new IOException("Cannot do this.");
}
public String getContentType()
{
return type;
}
public String getName()
{
return "BLOBDataSource";
}
}

Then we add the mp to the msg
 msg.setContent(mp);

This was cobbled together from code here at asktom and also on OTN.
It will do fileattachments from o/s BLOBS (binarystream) and CLOBS (asciistream)

It might be easier to do the binary stuff this way than with CAST

      Rating: 5
Thanks
      Paul  October 23, 2003
Reviewer:
      A reader
Does anybody have any code for this, strictly using PL/SQL with javamail?
Sorry, reading java code is not my strength.

          Followup:

umm, yes, the original answer above is strictly using plsql with javamail.  all
of the code is there.

      Rating: 5
Best way until
      10g?  October 23, 2003
Reviewer:  Doyle
      Freeman  from West Des Moines, IA
Just curious. What will be the best way with 10g?

          Followup:

UTL_MAIL :)

plsql api that supports attachments.

  PROCEDURE send_attach_varchar2(
                 sender         IN VARCHAR2 CHARACTER SET ANY_CS,
                 recipients     IN VARCHAR2 CHARACTER SET ANY_CS,
                 cc             IN VARCHAR2 CHARACTER SET ANY_CS DEFAULT NULL,
                 bcc            IN VARCHAR2 CHARACTER SET ANY_CS DEFAULT NULL,
                 subject        IN VARCHAR2 CHARACTER SET ANY_CS DEFAULT NULL,
                 message        IN VARCHAR2 CHARACTER SET ANY_CS DEFAULT NULL,
                 mime_type      IN VARCHAR2 CHARACTER SET ANY_CS
                                   DEFAULT 'text/plain; charset=us-ascii',
                 priority       IN PLS_INTEGER DEFAULT 3,
                 attachment     IN VARCHAR2 CHARACTER SET ANY_CS,
                 att_inline     IN BOOLEAN  DEFAULT TRUE,
                 att_mime_type  IN VARCHAR2 CHARACTER SET ANY_CS
                                   DEFAULT 'text/plain; charset=us-ascii',
                 att_filename   IN VARCHAR2 CHARACTER SET ANY_CS DEFAULT NULL);

  PROCEDURE send_attach_raw(
                 sender         IN VARCHAR2 CHARACTER SET ANY_CS,
                 recipients     IN VARCHAR2 CHARACTER SET ANY_CS,
                 cc             IN VARCHAR2 CHARACTER SET ANY_CS DEFAULT NULL,
                 bcc            IN VARCHAR2 CHARACTER SET ANY_CS DEFAULT NULL,
                 subject        IN VARCHAR2 CHARACTER SET ANY_CS DEFAULT NULL,
                 message        IN VARCHAR2 CHARACTER SET ANY_CS DEFAULT NULL,
                 mime_type      IN VARCHAR2 CHARACTER SET ANY_CS
                                   DEFAULT 'text/plain; charset=us-ascii',
                 priority       IN PLS_INTEGER DEFAULT 3,
                 attachment     IN RAW,
                 att_inline     IN BOOLEAN  DEFAULT TRUE,
                 att_mime_type  IN VARCHAR2 CHARACTER SET ANY_CS
                                   DEFAULT 'application/octet',
                 att_filename   IN VARCHAR2 CHARACTER SET ANY_CS DEFAULT NULL);

      Rating:
      4
POP3  November 16, 2003
Reviewer:
      Praveen Sehgal  from Virginia
Is there ANY way to retreive  email directly into the database?

          Followup:

load the java mail apis in there, and yes, you can read email.

or, if you use the collaboration suite, the database is email.

      Rating: 3
Retriving Mail
      IN 10g  November 19, 2003
Reviewer:  vikas
      Sharma  from Delhi India
Hi Tom,

Just curios to know that can we retrive the eamil to database using 10g.

Thanks,

Regards

Vikas Sharma

          Followup:

sure, just like you can in 9i, 8i -- you load the java mail API and retrieve it,
store it, do whatever you like with it.

      Rating: 3
Error occurs
      while using UTL_SMTP  November 26, 2003
Reviewer:
      Ken  from Canada
Hi Tom,

The solution is very helpful, but when I tried it, got some error messages:

SQL> begin
  2      pk_mail.send
  3      ( 'kchiang@toronto.bata.com',
  4        'kchiang@TORONTO.BATA.COM',
  5        'This is a testing mail',
  6        'Hello Ken, this is a testing mail'  );
  7  end;
  8  /

begin
*
ERROR at line 1:
ORA-29279: SMTP permanent error: 550 Unable to relay for
kchiang@toronto.bata.com
ORA-06512: at "SYS.UTL_SMTP", line 17
ORA-06512: at "SYS.UTL_SMTP", line 98
ORA-06512: at "SYS.UTL_SMTP", line 240
ORA-06512: at "ADP6_ADMIN.PK_MAIL", line 51
ORA-06512: at line 2

What could be the possible reason and solution to it?  Please help!

          Followup:

your smtp server is not configured to be a relay, it is not allowed to forward
mail.

either

a) find someone who knows how to configure smtp and reconfigure your server to
permit relays

b) use another smtp server.

      Rating:
      4
  November 27, 2003
Reviewer:  Ken

Hi Tom,

Don't worry, my problem has been resolved, the problem was due to the wrong SMTP
mail server name.

Thank you.

      Rating: 4
Sending Files
      as attachment  December 05, 2003
Reviewer:
      Jatinder  from CA USA
Hi Tom,

Just wanted to let you know that there's some information on TechNet that talks
about using UTL_SMTP for sending emails with attachments.

http://otn.oracle.com/sample_code/tech/pl_sql/htdocs/Utl_Smtp_Sample.html
I am not sure if I follow how can I attach a document with the email. Basically,
what I want to do is that we have a repositary of thousands of documents and the
user can pick the document they want to have. Upon selection, the program should
be able to email the selected documents to the user. Can you please let me know
how I can achieve that using UTL_SMTP package.

Thanks in advance.

          Followup:

i don't use utl_smtp for attachements, really hard.

i use the java mail api, demonstrated above.  you send it a blob and away she
goes.

December 06, 2003 Mamta -- Thanks for the question regarding
      "reading data from .dbf file into oracle 8i -- HOW TO LOAD DBASE data",
      version oracle 8i

      You Asked
I am unable to read the file which in .dbf format . if I open
the file in excel and save it as .csv file , I can read the file
using utl_file package of oracle 8i.

I cannot do this activity manually as I would be ftp ..ing the
files after every 10 minutes , hence the process needs to be
automated .

how can do this . I have searched through many sites , asked
many oracle professional but I have not got any answer for this
.

I need the help on this immediately .

Regards
mamta

      and we said...
Well, I looked up the dbase file format on the web and found
some sample DBASE files (specifically at

http://www.e-bachmann.dk/docs/xbase.htm
).  I put together a "dbase_pkg" plsql package that in Oracle8.0
and up should be able to load many DBASE files.  It requires
Oracle8.0 because I use a bfile to do binary file io which is
required with dbase files.

The package I developed will load into an existing table a DBASE
file.  Optionally -- it will show you the insert it will use an
print out a create table you might use to load into.  It is not
heavily "field tested" as yet -- should work in most cases,
however I'm pretty much sure MEMO fields won't work since they
are stored out of line in another file.

Here is the specification of this package:

create or replace package dbase_pkg
as

    -- procedure to a load a table with records
    -- from a DBASE file.
    --
    -- Uses a BFILE to read binary data and dbms_sql
    -- to dynamically insert into any table you
    -- have insert on.
    --
    -- p_dir is the name of an ORACLE Directory Object
    --       that was created via the CREATE DIRECTORY
    --       command
    --
    -- p_file is the name of a file in that directory
    --        will be the name of the DBASE file
    --
    -- p_tname is the name of the table to load from
    --
    -- p_cnames is an optional list of comma separated
    --          column names.  If not supplied, this pkg
    --          assumes the column names in the DBASE file
    --          are the same as the column names in the
    --          table
    --
    -- p_show boolean that if TRUE will cause us to just
    --        PRINT (and not insert) what we find in the
    --        DBASE files (not the data, just the info
    --        from the dbase headers....)

    procedure load_Table( p_dir    in varchar2,
                          p_file   in varchar2,
                          p_tname  in varchar2,
                          p_cnames in varchar2 default NULL,
                          p_show   in BOOLEAN default FALSE);
end;
/

and the body is:

create or replace package body dbase_pkg
as

-- Might have to change on your platform!!!
-- Controls the byte order of binary integers read in
-- from the dbase file
BIG_ENDIAN      constant boolean default TRUE;

type dbf_header is RECORD
(
    version    varchar2(25), -- dBASE version number
    year       int,          -- 1 byte int year, add to 1900
    month      int,          -- 1 byte month
    day        int,             -- 1 byte day
    no_records int,             -- number of records in file,
                             -- 4 byte int
    hdr_len    int,             -- length of header, 2 byte int
    rec_len    int,             -- number of bytes in record,
                             -- 2 byte int
    no_fields  int           -- number of fields
);

type field_descriptor is RECORD
(
    name     varchar2(11),
    type     char(1),
    length   int,   -- 1 byte length
    decimals int    -- 1 byte scale
);

type field_descriptor_array
is table of
field_descriptor index by binary_integer;

type rowArray
is table of
varchar2(4000) index by binary_integer;

g_cursor binary_integer default dbms_sql.open_cursor;

-- Function to convert a binary unsigned integer
-- into a PLSQL number

function to_int( p_data in varchar2 ) return number
is
    l_number number default 0;
    l_bytes  number default length(p_data);
begin
    if (big_endian)
    then
        for i in 1 .. l_bytes loop
            l_number := l_number +
                              ascii(substr(p_data,i,1)) *
                                           power(2,8*(i-1));
        end loop;
    else
        for i in 1 .. l_bytes loop
            l_number := l_number +
                         ascii(substr(p_data,l_bytes-i+1,1)) *
                         power(2,8*(i-1));
        end loop;
    end if;

    return l_number;
end;

-- Routine to parse the DBASE header record, can get
-- all of the details of the contents of a dbase file from
-- this header

procedure get_header
(p_bfile        in bfile,
 p_bfile_offset in out NUMBER,
 p_hdr          in out dbf_header,
 p_flds         in out field_descriptor_array )
is
    l_data            varchar2(100);
    l_hdr_size        number default 32;
    l_field_desc_size number default 32;
    l_flds            field_descriptor_array;
begin
    p_flds := l_flds;

    l_data := utl_raw.cast_to_varchar2(
                       dbms_lob.substr( p_bfile,
                                        l_hdr_size,
                                        p_bfile_offset ) );
    p_bfile_offset := p_bfile_offset + l_hdr_size;

    p_hdr.version    := ascii( substr( l_data, 1, 1 ) );
    p_hdr.year       := 1900 + ascii( substr( l_data, 2, 1 ) );
    p_hdr.month      := ascii( substr( l_data, 3, 1 ) );
    p_hdr.day        := ascii( substr( l_data, 4, 1 ) );
    p_hdr.no_records := to_int( substr( l_data,  5, 4 ) );
    p_hdr.hdr_len    := to_int( substr( l_data,  9, 2 ) );
    p_hdr.rec_len    := to_int( substr( l_data, 11, 2 ) );
    p_hdr.no_fields  := trunc( (p_hdr.hdr_len - l_hdr_size)/
                                           l_field_desc_size );

    for i in 1 .. p_hdr.no_fields
    loop
        l_data := utl_raw.cast_to_varchar2(
                         dbms_lob.substr( p_bfile,
                                          l_field_desc_size,
                                          p_bfile_offset ));
        p_bfile_offset := p_bfile_offset + l_field_desc_size;

        p_flds(i).name := rtrim(substr(l_data,1,11),chr(0));
        p_flds(i).type := substr( l_data, 12, 1 );
        p_flds(i).length  := ascii( substr( l_data, 17, 1 ) );
        p_flds(i).decimals := ascii(substr(l_data,18,1) );
    end loop;

    p_bfile_offset := p_bfile_offset +
                          mod( p_hdr.hdr_len - l_hdr_size,
                               l_field_desc_size );
end;

function build_insert
( p_tname in varchar2,
  p_cnames in varchar2,
  p_flds in field_descriptor_array ) return varchar2
is
    l_insert_statement long;
begin
    l_insert_statement := 'insert into ' || p_tname || '(';
    if ( p_cnames is NOT NULL )
    then
        l_insert_statement := l_insert_statement ||
                              p_cnames || ') values (';
    else
        for i in 1 .. p_flds.count
        loop
            if ( i <> 1 )
            then
               l_insert_statement := l_insert_statement||',';
            end if;
            l_insert_statement := l_insert_statement ||
                            '"'||  p_flds(i).name || '"';
        end loop;
        l_insert_statement := l_insert_statement ||
                                           ') values (';
    end if;
    for i in 1 .. p_flds.count
    loop
        if ( i <> 1 )
        then
           l_insert_statement := l_insert_statement || ',';
        end if;
        if ( p_flds(i).type = 'D' )
        then

            l_insert_statement := l_insert_statement ||
                     'to_date(:bv' || i || ',''yyyymmdd'' )';
        else
            l_insert_statement := l_insert_statement ||
                                                ':bv' || i;
        end if;
    end loop;
    l_insert_statement := l_insert_statement || ')';

    return l_insert_statement;
end;

function get_row
( p_bfile in bfile,
  p_bfile_offset in out number,
  p_hdr in dbf_header,
  p_flds in field_descriptor_array ) return rowArray
is
    l_data     varchar2(4000);
    l_row   rowArray;
    l_n     number default 2;
begin
    l_data := utl_raw.cast_to_varchar2(
                   dbms_lob.substr( p_bfile,
                                    p_hdr.rec_len,
                                    p_bfile_offset ) );
    p_bfile_offset := p_bfile_offset + p_hdr.rec_len;

    l_row(0) := substr( l_data, 1, 1 );

    for i in 1 .. p_hdr.no_fields loop
        l_row(i) := rtrim(ltrim(substr( l_data,
                                        l_n,
                                        p_flds(i).length ) ));
        if ( p_flds(i).type = 'F' and l_row(i) = '.' )
        then
            l_row(i) := NULL;
        end if;
        l_n := l_n + p_flds(i).length;
    end loop;
    return l_row;
end get_row;

procedure show( p_hdr    in dbf_header,
                p_flds   in field_descriptor_array,
                p_tname  in varchar2,
                p_cnames in varchar2,
                p_bfile  in bfile )
is
    l_sep varchar2(1) default ',';

    procedure p(p_str in varchar2)
    is
        l_str long default p_str;
    begin
        while( l_str is not null )
        loop
            dbms_output.put_line( substr(l_str,1,250) );
            l_str := substr( l_str, 251 );
        end loop;
    end;
begin
    p( 'Sizeof DBASE File: ' || dbms_lob.getlength(p_bfile) );

    p( 'DBASE Header Information: ' );
    p( chr(9)||'Version = ' || p_hdr.version );
    p( chr(9)||'Year    = ' || p_hdr.year   );
    p( chr(9)||'Month   = ' || p_hdr.month   );
    p( chr(9)||'Day     = ' || p_hdr.day   );
    p( chr(9)||'#Recs   = ' || p_hdr.no_records);
    p( chr(9)||'Hdr Len = ' || p_hdr.hdr_len  );
    p( chr(9)||'Rec Len = ' || p_hdr.rec_len  );
    p( chr(9)||'#Fields = ' || p_hdr.no_fields );

    p( chr(10)||'Data Fields:' );
    for i in 1 .. p_hdr.no_fields
    loop
        p( 'Field(' || i || ') '
             || 'Name = "' || p_flds(i).name || '", '
             || 'Type = ' || p_flds(i).Type || ', '
             || 'Len  = ' || p_flds(i).length || ', '
             || 'Scale= ' || p_flds(i).decimals );
    end loop;

    p( chr(10) || 'Insert We would use:' );
    p( build_insert( p_tname, p_cnames, p_flds ) );

    p( chr(10) || 'Table that could be created to hold data:');
    p( 'create table ' || p_tname );
    p( '(' );

    for i in 1 .. p_hdr.no_fields
    loop
        if ( i = p_hdr.no_fields ) then l_sep := ')'; end if;
        dbms_output.put
        ( chr(9) || '"' || p_flds(i).name || '"   ');

        if ( p_flds(i).type = 'D' ) then
            p( 'date' || l_sep );
        elsif ( p_flds(i).type = 'F' ) then
            p( 'float' || l_sep );
        elsif ( p_flds(i).type = 'N' ) then
            if ( p_flds(i).decimals > 0 )
            then
                p( 'number('||p_flds(i).length||','||
                              p_flds(i).decimals || ')' ||
                              l_sep );
            else
                p( 'number('||p_flds(i).length||')'||l_sep );
            end if;
        else
            p( 'varchar2(' || p_flds(i).length || ')'||l_sep);
        end if;
    end loop;
    p( '/' );
end;

procedure load_Table( p_dir in varchar2,
                      p_file in varchar2,
                      p_tname in varchar2,
                      p_cnames in varchar2 default NULL,
                      p_show in boolean default FALSE )
is
    l_bfile      bfile;
    l_offset  number default 1;
    l_hdr     dbf_header;
    l_flds    field_descriptor_array;
    l_row      rowArray;
begin
    l_bfile := bfilename( p_dir, p_file );
    dbms_lob.fileopen( l_bfile );

    get_header( l_bfile, l_offset, l_hdr, l_flds );

    if ( p_show )
    then
        show( l_hdr, l_flds, p_tname, p_cnames, l_bfile );
    else
        dbms_sql.parse( g_cursor,
                        build_insert(p_tname,p_cnames,l_flds),
                        dbms_sql.native );

        for i in 1 .. l_hdr.no_records loop
            l_row := get_row( l_bfile,
                              l_offset,
                              l_hdr,
                              l_flds );

            if ( l_row(0) <> '*' ) -- deleted record
            then
                for i in 1..l_hdr.no_fields loop
                    dbms_sql.bind_variable( g_cursor,
                                            ':bv'||i,
                                            l_row(i),
                                            4000 );
                end loop;
                if ( dbms_sql.execute( g_cursor ) <> 1 )
                then
                    raise_application_error( -20001,
                                 'Insert failed ' || sqlerrm );
                end if;
            end if;
        end loop;
    end if;

    dbms_lob.fileclose( l_bfile );
exception
    when others then
        if ( dbms_lob.isopen( l_bfile ) > 0 ) then
            dbms_lob.fileclose( l_bfile );
        end if;
        RAISE;
end;

end;
/

Now, if you get the "sample" dbase files from the web site I
referred to above, you can now run the following test to see
this work.  You would need to change the PATH of the create
directory below and make sure to put t1.dbf and so on in that
directory (from their template/t1/with_data/t1.dbf directory and
so on)

ops$tkyte@DEV816> set serveroutput on size 1000000
ops$tkyte@DEV816>
ops$tkyte@DEV816> create or replace directory
  2  my_files as '/export/home/tkyte/dbf';

Directory created.

ops$tkyte@DEV816>
ops$tkyte@DEV816> column cfield format a15 word_wrapped
ops$tkyte@DEV816> column memo format a5
ops$tkyte@DEV816> set numformat 99999.999999999999999
ops$tkyte@DEV816> alter session set nls_date_format =
'dd-mon-yyyy';

Session altered.

ops$tkyte@DEV816>
ops$tkyte@DEV816>
ops$tkyte@DEV816> begin
  2  dbase_pkg.load_table( 'MY_FILES',
  3                        't1.dbf',
  4                        't1',
  5                        p_show => TRUE );
  6  end;
  7  /
Sizeof DBASE File: 1581
DBASE Header Information:
        Version = 3
        Year    = 1900
        Month   = 6
        Day     = 12
        #Recs   = 11
        Hdr Len = 161
        Rec Len = 129
        #Fields = 4

Data Fields:
Field(1) Name = "CHARACTER", Type = C, Len  = 100, Scale= 0
Field(2) Name = "NUMERIC", Type = N, Len  = 19, Scale= 15
Field(3) Name = "DATE", Type = D, Len  = 8, Scale= 0
Field(4) Name = "LOGICAL", Type = L, Len  = 1, Scale= 0

Insert We would use:
insert into t1("CHARACTER","NUMERIC","DATE","LOGICAL") values
(:bv1,:bv2,to_date(:bv3,'yyyymmdd' ),:bv4)

Table that could be created to hold data:
create table t1
(
        "CHARACTER"   varchar2(100),
        "NUMERIC"   number(19,15),
        "DATE"   date,
        "LOGICAL"   varchar2(1))
/

PL/SQL procedure successfully completed.

ops$tkyte@DEV816> drop table t1;

Table dropped.

ops$tkyte@DEV816> create table t1
  2  ( cfield varchar2(255),
  3    nfield number,
  4    dfield date,
  5    logical char(1)
  6  );

Table created.

ops$tkyte@DEV816> begin
  2  dbase_pkg.load_table( 'MY_FILES',
  3                        't1.dbf',
  4                        't1',
  5  'cfield, nfield, dfield, logical' );
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@DEV816> commit;

Commit complete.

ops$tkyte@DEV816> select * from t1;

CFIELD                          NFIELD DFIELD      L
--------------- ---------------------- ----------- -
One                  1.000000000000000 01-jan-1970 Y
Two records          2.000000000000002 31-dec-1970 Y
Three records        3.000000000000000 01-jan-1980 N
stored

Four records          .000000000000004 31-dec-1980 N
stored now

Five records       555.555555555555543 01-jan-1900
stored now in

Six records          6.000000000000000
stored now in
the

Seven records        7.000000000000000
stored now in
the database

Eight records        8.000000000000000
stored now in
the database
for

Nine records          .099999999999999
stored now in
the database
for test

Ten  records        10.000000000000000
stored now in
the database
for test
purposes

11 rows selected.

ops$tkyte@DEV816>
ops$tkyte@DEV816> begin
  2  dbase_pkg.load_Table( 'MY_FILES',
  3                  't2.dbf',
  4                  't2',
  5                  p_show=>TRUE );
  6  end;
  7  /
Sizeof DBASE File: 1723
DBASE Header Information:
        Version = 131
        Year    = 1900
        Month   = 6
        Day     = 12
        #Recs   = 11
        Hdr Len = 193
        Rec Len = 139
        #Fields = 5

Data Fields:
Field(1) Name = "CHARACTER", Type = C, Len  = 100, Scale= 0
Field(2) Name = "NUMERIC", Type = N, Len  = 19, Scale= 15
Field(3) Name = "DATE", Type = D, Len  = 8, Scale= 0
Field(4) Name = "LOGICAL", Type = L, Len  = 1, Scale= 0
Field(5) Name = "MEMO", Type = M, Len  = 10, Scale= 0

Insert We would use:
insert into t2("CHARACTER","NUMERIC","DATE","LOGICAL","MEMO")
values (:bv1,:bv2,to_date(:bv3,'yyyymmdd' ),:bv4,:bv5)

Table that could be created to hold data:
create table t2
(
        "CHARACTER"   varchar2(100),
        "NUMERIC"   number(19,15),
        "DATE"   date,
        "LOGICAL"   varchar2(1),
        "MEMO"   varchar2(10))
/

PL/SQL procedure successfully completed.

ops$tkyte@DEV816> drop table t2;

Table dropped.

ops$tkyte@DEV816> create table t2
  2  ( cfield varchar2(255),
  3    nfield number,
  4    dfield date,
  5    logical char(1),
  6    memo varchar2(255)
  7  );

Table created.

ops$tkyte@DEV816> begin
  2  dbase_pkg.load_Table( 'MY_FILES',
  3                  't2.dbf',
  4                  't2',
  5  'cfield, nfield, dfield, logical, memo' );
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@DEV816> select * from t2;

CFIELD                          NFIELD DFIELD      L MEMO
--------------- ---------------------- ----------- - -----
One                  1.000000000000000 01-jan-1970 T 1
Two records          2.000000000000002 31-dec-1970 T 2
Three records        3.000000000000000 01-jan-1980 F 3
stored

Four records          .000000000000004 31-dec-1980 F 4
stored now

Five records       555.555555555555543 01-jan-1900 F 5
stored now in

Six records          6.000000000000000             F 6
stored now in
the

Seven records        7.000000000000000             F 7
stored now in
the database

Eight records        8.000000000000000             F 8
stored now in
the database
for

Nine records          .099999999999999             F 9
stored now in
the database
for test

Ten  records        10.000000000000000             F 10
stored now in
the database
for test
purposes

                      .000000000000000             F

11 rows selected.

ops$tkyte@DEV816>
ops$tkyte@DEV816> begin
  2  dbase_pkg.load_Table( 'MY_FILES',
  3                  't4.dbf',
  4                  't4',
  5                  p_show => true );
  6  end;
  7  /
Sizeof DBASE File: 1694
DBASE Header Information:
        Version = 3
        Year    = 2000
        Month   = 6
        Day     = 12
        #Recs   = 10
        Hdr Len = 193
        Rec Len = 150
        #Fields = 5

Data Fields:
Field(1) Name = "CHARACTER", Type = C, Len  = 100, Scale= 0
Field(2) Name = "NUMERICAL", Type = N, Len  = 20, Scale= 2
Field(3) Name = "DATE", Type = D, Len  = 8, Scale= 0
Field(4) Name = "LOGICAL", Type = L, Len  = 1, Scale= 0
Field(5) Name = "FLOAT", Type = F, Len  = 20, Scale= 18

Insert We would use:
insert into t4("CHARACTER","NUMERICAL","DATE","LOGICAL","FLOAT")
values (:bv1,:bv2,to_date(:bv3,'yyyymmdd' ),:bv4,:bv5)

Table that could be created to hold data:
create table t4
(
        "CHARACTER"   varchar2(100),
        "NUMERICAL"   number(20,2),
        "DATE"   date,
        "LOGICAL"   varchar2(1),
        "FLOAT"   float)
/

PL/SQL procedure successfully completed.

ops$tkyte@DEV816> drop table t4;

Table dropped.

ops$tkyte@DEV816> create table t4
  2  ( cfield varchar2(255),
  3    nfield number,
  4    dfield date,
  5    logical char(1),
  6    float_field number
  7  );

Table created.

ops$tkyte@DEV816> begin
  2  dbase_pkg.load_Table( 'MY_FILES',
  3                  't4.dbf',
  4                  't4',
  5      'cfield, nfield, dfield, logical, float_field' );
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@DEV816> set numformat 999999
ops$tkyte@DEV816> column cfield format a6
ops$tkyte@DEV816> select * from t4;

CFIELD  NFIELD DFIELD      L FLOAT_FIELD
------ ------- ----------- - -----------
One          1 01-jan-1970 Y           1
Two          2 31-dec-1970 T           2
Three        3 01-jan-1980             3
Four         4 01-jan-1900             4
Five         5 31-dec-1900             5
Six          6 01-jan-1901             6
Seven        7 31-dec-1999             7
Eight        8 31-dec-1919             8
Nine         9
Ten         10                         0

10 rows selected.

ops$tkyte@DEV816>

If anyone uses this and encounters a "bug", drop me a line and
let me know -- I'll fix it accordingly so others don't hit it.

UTL_RAW might not be installed on your system -- it comes with
Oracle7.1.6 and up.  To install if you do not have it:

o cd $ORACLE_HOME/rdbms/admin
o connect as SYS or INTERNAL only using SVRMGRL
o @utlraw
o @prvtrawb.plb

Review &
      Followup

      Rating: 4
reading data
      from dbf  June 18, 2001
Reviewer:  balu
       from india
hai tom
  thanks for answer but i am facing problem
  i am using o8i client and win 98 environment.i have created one table to hold
bfile details with a directory in oracle pointing to it.well i have created a
table in oracle to insert data from dbf to that.but it is reading data but not
inserting into t1 table in oracle need u r help
 urgent
thanks in advance
balu

      Rating: 5
It doesn't work
      with foxpro files ...  November 06, 2001
Reviewer:
      Mattia Rossi  from Italy
because the rtrim that returns the name of the column in the dbf file fails (
get_header function) ...
here is the new get_header function:

-- Routine to parse the DBASE header record, can get
-- all of the details of the contents of a dbase file from
-- this header

procedure get_header
(p_bfile        in bfile,
 p_bfile_offset in out NUMBER,
 p_hdr          in out dbf_header,
 p_flds         in out field_descriptor_array )
is
    l_data            varchar2(100);
    l_hdr_size        number default 32;
    l_field_desc_size number default 32;
    l_flds            field_descriptor_array;
begin
    p_flds := l_flds;

    l_data := utl_raw.cast_to_varchar2(
                       dbms_lob.substr( p_bfile,
                                        l_hdr_size,
                                        p_bfile_offset ) );
    p_bfile_offset := p_bfile_offset + l_hdr_size;

    p_hdr.version    := ascii( substr( l_data, 1, 1 ) );
    p_hdr.year       := 1900 + ascii( substr( l_data, 2, 1 ) );
    p_hdr.month      := ascii( substr( l_data, 3, 1 ) );
    p_hdr.day        := ascii( substr( l_data, 4, 1 ) );
    p_hdr.no_records := to_int( substr( l_data,  5, 4 ) );
    p_hdr.hdr_len    := to_int( substr( l_data,  9, 2 ) );
    p_hdr.rec_len    := to_int( substr( l_data, 11, 2 ) );
    p_hdr.no_fields  := trunc( (p_hdr.hdr_len - l_hdr_size)/
                                           l_field_desc_size );

    for i in 1 .. p_hdr.no_fields
    loop
        l_data := utl_raw.cast_to_varchar2(
                         dbms_lob.substr( p_bfile,
                                          l_field_desc_size,
                                          p_bfile_offset ));
        p_bfile_offset := p_bfile_offset + l_field_desc_size;

/*
        p_flds(i).name := rtrim(substr(l_data,1,11),chr(0));
        p_flds(i).type := substr( l_data, 12, 1 );
        p_flds(i).length  := ascii( substr( l_data, 17, 1 ) );
        p_flds(i).decimals := ascii(substr(l_data,18,1) );
*/
        p_flds(i).name := mytrim(substr(l_data,1,11));
        p_flds(i).type := substr( l_data, 12, 1 );
        p_flds(i).length  := ascii( substr( l_data, 17, 1 ) );
        p_flds(i).decimals := ascii(substr(l_data,18,1) );
    end loop;

    p_bfile_offset := p_bfile_offset +
                          mod( p_hdr.hdr_len - l_hdr_size,
                               l_field_desc_size );
end;

and this is the mytrim function:

function mytrim(p_str in varchar2) return varchar2 is
i number;
j number;
v_res varchar2(100);
begin
  for i in 1 .. 11 loop
    if ascii(substr(p_str,i,1)) = 0 then
     j:= i;
     exit;
    end if;
  end loop;
  v_res := substr(p_str,1,j-1);
  return v_res;
end mytrim;

The package modified in this way works for me ...

Mattia

      Rating: 4
reading data
      from .dbf file into oracle 8i   November 17, 2001

Reviewer:  Deepali Aggarwal
Worked great. There are many ways to convert a .dbf file into oracle table
manually but through this package, was able to automate the whole process. Being
a novice PL*SQL programmer had to search few more links for 'unable to open file
or directory to read'. But finally it worked.

Thanks

      Rating: 5
dbase_pkg in
      oracle9i  August 19, 2002
Reviewer:  mick
      barlow  from Lancshire, England
Hi Tom,

I know this is a about a year late but, i am try to use this package in
Oracle9i(win2k) but whem i create it using plus80w it says "Warning: Package
created with compilation errors."

Any ideas's?

Thanx...

P.S. I am new to this Oracle stuff, so please be gentle.

          Followup:

must be a cut and paste error on your part.  I just ran it without issue.

why are you using plus80w, just use the sqlplus.exe that comes with 9i.

after you run the package body, do a

SQL> show errors package body dbase_pkg

and see what is wrong (and then fix it).  The code "as is" compiled just fine.

      Rating: 3
Need a bit more
      help  August 30, 2002
Reviewer:
      Shyampaliyath  from Tamil Nadu,India.
I was looking for a solution to read from dbf files into oracle when i came
across this question.Hence i tried out the solution provided by u. But i came
across a problem which i kindly request u to take into consideration.

create or replace directory my_files as '/home/applications';

directory created.

create or replace procedure dbf_file_open
as
file_name bfile;
begin
   file_name:=bfilename('my_files','Bankfile.dbf');
   dbms_lob.open(file_name);
   dbms_output.put_line('Successful');
end;

ERROR at line 1:
ORA-22288: file or LOB operation FILEOPEN failed
No such file or directory
ORA-06512: at "DEPOSITS.DBF_FILE_OPEN", line 280
ORA-06512: at line 2

how to proceed further.

(ii) This is another requirement
i get the path from the get_file_name function and

i execute the procedure as

create or replace procedure dbf_file_open
as
file_name bfile;
begin
   file_name:=bfilename('c:\dbf','Bankfile.dbf');
   dbms_lob.open(file_name);
   dbms_output.put_line('Successful');
end;

i get the error as
==================
ERROR at line 1:
ORA-22288: file or LOB operation FILEOPEN failed
No such file or directory
ORA-06512: at "DEPOSITS.DBASE_PKG", line 280
ORA-06512: at line 2

what shall i do??

I f u could plz guide me i'd be much happy.

Shyam

          Followup:

i)    file_name:=bfilename('MY_FILES','Bankfile.dbf');

it is case sensitive.

ii) You cannot use OS dependent paths like that to bfilename.  You can only use
directory objects.

      Rating: 2
dbase data into
      oracle  August 30, 2002
Reviewer:  k ullur

It might be easier using odbc in a language like perl for dbase to oracle
xfr...?

I used this method for data xfr between Access and Oracle.

odbc drivers for dbase and oracle are available and hence must be practical...

          Followup:

Lets see --

I could write a custom perl program, dig up ODBC drivers (perl is generally
associated with Unix, Unix is not generally associated with ODBC) -- blah blah
blah

Or, I could use this utility and write nothing, be dependent on no other piece
of software, have it work on any platform --- hmm.

I don't know -- which is easier?

      Rating:
      2
Feedback  September 02, 2002
Reviewer:
      Shyampaliyath  from Tamil Nadu,India.
hello sir,

Thanxs for ur response to my question. I tried out the solution provided by u
(i.e.) abt the case sensitivity
and i rewritten my procedure as
SQL> CREATE OR REPLACE procedure dbf_file_open
  2  as
  3  file_name bfile;
  4  begin
  5     file_name:=bfilename('DBF_PTR','Bankfile.dbf');
  6     DBMS_LOb.FILEOPEN(FILE_name);
  7  end;
  8  /

Procedure created.

SQL> EXEC DBF_FILE_OPEN;
BEGIN DBF_FILE_OPEN; END;

*
ERROR at line 1:
ORA-22285: non-existent directory or file for FILEOPEN operation
ORA-06512: at "SYS.DBMS_LOB", line 475
ORA-06512: at "KCP.DBF_FILE_OPEN", line 6
ORA-06512: at line 1

since my database is in linux i went to init.ora file and added another line as

utl_file_dir=kcp_ro_linux/home/applications

and restarted my oracle database and executed my procedure
but the same error occurs.

waiting for ur response.

          Followup:

UTL_FILE_DIR has nothing to do with bfiles -- nothing.

You do understand that the FILE itself must be on the SERVER (not your pc).  I
see in your example (original example)

...
create or replace directory my_files as '/home/applications';

directory created.
....
create or replace procedure dbf_file_open
as
file_name bfile;
begin
   file_name:=bfilename('c:\dbf','Bankfile.dbf');
   dbms_lob.open(file_name);
   dbms_output.put_line('Successful');
end;
.....

So, I am thinking that since you are mixing Unix and DOS naming conventions --
you have the file on your PC and the SERVER is where it needs to be.

      Rating: 5
dbf to
      oracle  September 05, 2002
Reviewer:
      Shyampaliyath  from Tamil Nadu,India
atlast it worked. Thank u  for u r  help

      Rating: 5
Vice
      Versa  September 11, 2002
Reviewer:  Mick
      Barlow  from Lancashire, England
Superb, works brilliant. BUT just one more thing, is possible to make this work
the other way and export from Oracle to DBASE now that the data has been queried
and amended.

Cheers

          Followup:

Well, I suppose you could write the reverse piece of code I have here
however.... until 9ir2 you'll have to do it in a Java stored procedure (at least
the IO part) or C since UTL_FILE does not support BINARY file io until then.

      Rating: 4
An alternative
      approach  December 05, 2002
Reviewer:
      andrew  from ca, usa
Converting dbf to txt and txt to dbf outside the database is attractive is if
saves coding. See this URL for txt2dbf and dbf2txt:

http://www.usf.uos.de/~fkoorman/software/dbftools.en.html

      Rating: 5
Thanks, but
      what is the best way to import DBF  December 23, 2002

Reviewer:  Edgar Chupit  from Riga, Latvia

Hello Tom,

Thanks for a great piece of code, we learn a lot from this site. Can you please
give me suggestion about my problem. My problem is that I need to migrate data
from DBF files to Oracle, but not only once, for now, I need to do synchronize
data from DBF to Oracle each month. I saw a lot of methods of loading DBF files
to Oracle such as:
a) Use your code to load DBF directly from PL/SQL;
b) As I saw on internet there was an application to create SQLLDR script to load
DBF files;
c) There are some articles and FoxPro scripts to migrate from FoxPro to Oracle7
at otn.oracle.com;
d) Write my own application that will put data from DBF files to Oracle
database;
e) Write my own application that will generate a SQLLDR param file (DBF is just
fixed record file);
f) Use third party app that will do what I need.

Can you please give me advice on what is better way to transfer data from DBF to
Oracle. Thank you.

          Followup:

a) works
b) doesn't work
c) and on this site, you could use odbc easily and sqlplus copy commands
d) sure
e) no it is not.  it contains binary data and a very binary header
f) sure

I would use either A or C as they work and involve no code.  See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4406709207206
for help on C)

      Rating:
      5
Output  March 06, 2003
Reviewer:
      Steve  from UK
Using 9iR2 I don't suppose you could give a very quick example of reversing your
code and outputting in dbf format could you?

Thanks

          Followup:

select reverse( text )
  from user_source
 where name = 'DBASE_PKG'
 order by type, line desc;

should reverse the code from top to bottom ;)

seriously -- you should be able to reverse it based on this sort of code -- and
most things that read "dbf" read CSV and for that you search for dump_csv

      Rating:
      5
Thanks  March 06, 2003
Reviewer:
      Steve  from UK
I know - I was just being lazy!!  Sorry!
Almost got it cracked now anyway - just a question of reading the spec., using a
decent text editor and some thought and perserverence!!

      Rating: 5
An Excellent
      and Flexible Package..  August 01, 2003
Reviewer:
      Vikas Gupta  from India
Thanks Tom, for the excellent package. This will solve many of my problems.

However I 'am confused at one place.

When ever I keep the file in the local drive of the server I am being able to
run the package without problems.

But when the file is on a mapped drive, I get error of file not found.

What am I doing wrong here ?

Vikas.

          Followup:

windoze makes this really hard

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:241814624807

      Rating: 5
reading data
      from .dbf file into oracle 8i -- HOW TO LOAD DBASE data", version oracle
      8i  September 21, 2003
Reviewer:  Dulal
      Sarkar  from Bangladesh
Hi Tom,

I have go through this topics and its an excellent help for me. Thanks a lot.

I have to read Paradox5.0 file into Oracle8i frequently as automated system and
so if you kindly give me a solution like dbase_pkg of you. I will be highly
glad. I am using Oracle8i, Developer 6i and Windows2000.
I need the help on this immediately.

Thanks in advance.

          Followup:

well, i know as much about the file format of a paradox file as you do --
probably even less (i don't run any OS's that could even run paradox)

So, you see the method I used -- you could code the same...

or, you can use odbc:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4406709207206
and just insert into select * from

      Rating:
      3
  November 19, 2003
Reviewer:
      padmaja
the info is quite useful . but how to load memo fields into Oracle Database (8i)
.

      Rating: 4
HOW TO LOAD
      DBASE data  December 06, 2003
Reviewer:
      Vijay  from Bahrain
Given info is very useful. but what about memo field in the dbf file.  Please
advise.

Thanks in advance

          Followup:

well, you see *how* i did what I did.

all you need to do now is dig up "how are memo fields stored in dbase files"
(should be easy to find on google somewhere) and implement it.

and then you can post it for others

      Rating: 5
Not
      difficult  December 09, 2003
Reviewer:
      Steve  from UK
Memo fields are held in a separate file to the .dbf file - usually .dbt file.
Memo's are held in 512k blocks and a pointer is held in a field in the .dbf file
- so a value of 6 would indicate that the memo is in the 6th 512k block of the
memo (dbt) file.

What I do is dbms_lob.fileopen the .dbt file the dbms_lob.substr it for the
block I want.

      Rating: 5
How To Load
      DBASE Data  January 06, 2004
Reviewer:
      A reader  from British Columbia Canada
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:711825134415
Thank you very much for this information, it was immensely helpful.  Great site.

      Rating: 4
Corrected
      function because of different NLS_LANG parameter!  January
      07, 2004
Reviewer:  Roman Frelih  from Slovenia

Because of different NLS_LANG parameters, an error occured with message: INVALID
NUMBER.
Because the type rowArray is consisted of strings, we have to change to real
(system) decimal character.
Some could use , and someone could use .

This is my replaced function, which I use know!
It uses nested exception, which may be not so good.

FUNCTION get_row ( p_bfile IN BFILE, p_bfile_offset IN OUT NUMBER, p_hdr IN
dbf_header,  p_flds IN field_descriptor_array ) RETURN rowArray IS
    l_data     VARCHAR2(4000);
    l_row   rowArray;
    l_n     NUMBER DEFAULT 2;
    l_NumberRow NUMBER; -- by Roman
    l_StringRow VARCHAR2(255); -- by Roman
BEGIN
    l_data := utl_raw.cast_to_varchar2( dbms_lob.SUBSTR( p_bfile, p_hdr.rec_len,
p_bfile_offset ) );
    p_bfile_offset := p_bfile_offset + p_hdr.rec_len;

    l_row(0) := SUBSTR( l_data, 1, 1 );

    FOR i IN 1 .. p_hdr.no_fields LOOP
        l_row(i) := RTRIM(LTRIM(SUBSTR( l_data, l_n, p_flds(i).LENGTH ) ));
        -- by Roman, jan.2004, Slovenia
        -- Because of different NLS_LANG parameters, an error occured with
message: INVALID NUMBER.
        -- Because the type rowArray is consisted of strings, we have to change
to real (system) decimal character.
        -- Some could use , and someone could use .
        -- So, what I do here:
        -- I convert the string value of number to number value and that back to
string value.
        IF ( p_flds(i).TYPE = 'F'  OR p_flds(i).TYPE = 'N'  ) THEN -- Only for
Float and Number type
           BEGIN
                   l_StringRow := l_row(i); --Save to temporary variable
                l_StringRow := REPLACE ( l_StringRow , ',' , '.' ); --We change
characters
                   l_NumberRow :=  TO_NUMBER ( l_StringRow ); -- try to convert
to number
                   l_row(i) := TO_CHAR( l_NumberRow );  --If succeded that we
have right number value!
           EXCEPTION
                   WHEN OTHERS THEN
                       l_StringRow := l_row(i); --Save to temporary variable
                    l_StringRow := REPLACE ( l_StringRow , '.' , ',' ); --We
change characters
                       l_NumberRow :=  TO_NUMBER ( l_StringRow ); -- try to
convert to number
                       l_row(i) := TO_CHAR( l_NumberRow );  --If succeded that
we have right number value!
           END;
        END IF;
        -- End Roman

        IF ( p_flds(i).TYPE = 'F' AND l_row(i) = '.' )
        THEN
            l_row(i) := NULL;
        END IF;
        l_n := l_n + p_flds(i).LENGTH;
    END LOOP;
    RETURN l_row;
END get_row;

December 06, 2003 sj -- Thanks for the question regarding "pl/sql
      type and sql types and collections", version 8.1.7

      You Asked
Tom

There is a lot of confusion between what are pl/sql types and sql types.

Can you please discuss with examples and clarify the confusion for us.

2.What are collections, how do they differe from pl/sql tables.

Bye

      and we said...
Its very simple really.

If you:

SQL> create type mytable .....;

thats a SQL type.  I can use that in SQL (create table of that table, as a
column, in a query, etc).  Since all things SQL are in PLSQL -- I can use it in
PLSQL as well (as a type for a variable, in queries and so on).

If you:

SQL> create package my_pkg as type foo is .....

thats a PLSQL type.  I can use it in PLSQL only -- SQL has no clue about it.
Its not in the data dictionary -- it is only available to the PLSQL engine.

2) read:

http://technet.oracle.com/doc/oracle8i_816/appdev.816/a76976/adobjint.htm#452711
They do not differ much from plsql tables except that you must explicitly
"extend" them to make space in them.  In a plsql table type, I can just:

declare
    x   plsql_table_type;
begin
    x(1) := 5;
end;

using a collection I must:

declare
    x collection_type := collection_type();
begin
    x.extend;
    x(1) := 5;
end;

the nice thing about collection types is that you can initialize them:

declare
   x collection_type := collection_type( 1, 100, 1000, 10000 );
begin
   ...

using a table type -- i would need to make 4 assignments to get those values in.

Review &
      Followup

      Rating: 4
Are collections
      and varrays one and the same  September 27, 2001

Reviewer:  A reader
You gave the example

SQL> create package my_pkg as type foo is .....

I cant think of an example where you have to create a package as a type of
something else,

elaborate the declaration of a package as a type of something else please.

          Followup:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create package my_pkg
  2  as
  3          type foo is table of number index by binary_integer;
  4  end;
  5  /

Package created.

I was just using shorthand -- maybe it makes more sense with the line breaks.

      Rating: 2
varchar2 limit
      differences  September 27, 2001
Reviewer:
      Mikito Harakiri
The fact that varchar2 have different limits in SQL and PL/SQL is a shame. Why
not to equalize CLOB and PL/SQL varchar2? PL/SQL is not constrained by storage
access efficiency concerns, after all. I hope this character string mess would
be cleaned one day.

On optimistic note, Oracle has a single Date datatype, while other databases
have many (and it's the same in SQL and PL/SQL!-).

      Rating: 5
referenging
      type of a "col" of an object type  December 05, 2003

Reviewer:  A reader
when we declare a local variable that will
get value from a table column, we do
l_variable table_name.column_name%type;

Now let us say we have an object
create or replace type obj_type as object
(a varchar2(30),
  b varchar2(10)
);

If we declare a local variable where we want to
store the value of "a" in the object type, do
we have a similar elegant way?

thanx!

          Followup:

the datatype is obj_type, people would be using "obj_type"

there isn't a way to reference the type of "a" that i'm aware of.

      Rating: 4
Record vs.
      Object  December 06, 2003
Reviewer:  Alex
       from Montreal, Canada
Tom, can you, please, comment on Record vs. Object_Type.

My goal is to have a record type
(data object without methods)
available globally for data transfer between modules.

1.  SQL: create type my_rec as record.....
Failed, apparently, it is not possible

2.   PLSQL record in package specification:
CoR PACKAGE "RP" AS
TYPE foo IS RECORD (todayy date,numm number);END;

Works fine as RP.FOO, but I was told that pl/sql
memory cache can expell this definition under
heavy load and cause exception (???)

3. SQL: create type my_rec as object..... (with no methods)
Is it good aproarch here? will it be some overheat
versus pl/sql record : get/set operations, IN and OUT
passing, return data from function ?

4. Is there something else to consider?

Thanks a lot in advance

          Followup:

1) is it sql

create or replace TYPE myType as OBJECT ( attributes .... );

2) well, that is true of everything in the shared pool -- Everything.

3) that lets others outside of PLSQL (java, c, vb, etc) have access to it.  a
little more "open" if you will.

4) if you are doing this for plsql, the record type is nice, easy, self
contained.  if you believe others might want at this type in the future, the sql
type is more 'Open' that way.

      Rating:
      4
Thanks  December 06, 2003
Reviewer:
      Alex  from Montreal, Canada
OK, I will consider "record" for our pure pl/sql
and forms project. Thanks again.

December 06, 2003 Saibabu -- Thanks for the question regarding
      "Difference between DB_NAME and Instance_Name", version 8.1.6

      You Asked
Hi,If I use different names for db_name and instance_name in
init
file.I suppose ORACLE_SID=instance_name (OS variable).For all
Alter database and Startup options use db_name.Where else these
differences will matter.We are planning to Standby Database on
same host.Manual says some OS will not allow to have same
database names.My doubt is how does OS knows db_name if we are
using ORACLE_SID=instance_name.Which among db_name and
Instance_name should not be same if we are using Standby on same
host.
 Thanks
 Saibabu

      and we said...
A database is a set of files (data, redo, ctl and so on)

An instance is a set of processes (SMON, PMON, DBWR, etc) and a
shared memory segment (SGA).

A database may be mounted and opened by many INSTANCES (Parallel
Server) concurrently.

An instance may mount and open ANY database -- however it may
only open a single database at any time.

Therefore -- you need a unique database name (for the sets of
files).  The db_name is burned into these files.  thats how it
knows.

doing a standby database on the same machine with the primary
database is sort of self defeating isn't it?  It would be so
much cheaper resource/hardware wise and easier to simply use
raid mirrors (as the only thing you could protect against with
this setup would be a disk failure and mirrors will give you the
same level of protection).  It does not help you at all if the
machine itself goes south....

Review &
      Followup

      Rating:
      4
  April 03, 2001
Reviewer:  Kalpesh
      Patel  from Fort Lee, NJ, USA

      Rating:
      5
  May 08, 2001
Reviewer:  Hari
       from FL,USA

      Rating: 5
Clear and
      "clean" answer!  August 28, 2001
Reviewer:
      Elka  from McLean VA
At some point I was confused too. I am glad for verification on that matter. Is
oracle_sid is = to instance name? I think it simply allies for network use.

          Followup:

A sid is "sort of" the same as the instance name -- if you:

$ echo $ORACLE_SID
ora8i

ops$tkyte@ORA8I.WORLD> select instance_name from v$instance;

INSTANCE_NAME
----------------
ora8i

you'll see them as the same. I would go a step further -- the combination of:

o ORACLE_HOME plus
o ORACLE_SID plus
o HOSTNAME

identifies the instance (i can have two instances in different homes on a single
machine with the same sid).

      Rating: 4
Some more
      clarification  March 27, 2002
Reviewer:
      Niraj  from UK
Hi Tom,

The explanation is clear, But I just need your help in understanding how exactly
we can implement it. A brief example will help a lot.

Thanks in advance.

          Followup:

No, I'd need clarification from you.  What is "it".  There are lots of "its" on
this page.

Implement "what"?

      Rating:
      4
  November 17, 2002
Reviewer:  Don
      Stamps  from San Francisco
The reason so many of us ask about putting a standby database on the same server
as the primary database, is that it is homework for Oracle DB Admin courses,
which require the creation of a standby DB, and most of us only have access to a
single system.

In my case, I am trying to create a standby DB on the same PC (Windows 2000).
Not easy.  Not there yet.

There is a document on your site about doing so on a Unix system, but doesn't
quite get me there on my Windows System.

Any points with respect to primary / standby on a PC running Windows 2000 would
be greatly appreciated.

Thank you very much!

          Followup:

but -- it is not any different?  windows, unix -- matters not.

besides the steps of using oradim to register a new instance -- and global
change "/" to "\" - the steps are the same.

And -- unless you've actually set it up on two different machines -- in my mind
-- you haven't done your homework as yet.

this covers the steps:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76995/standbys.htm#29841

you need to read:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/win.817/a73008/ch5.htm#1027981
to see how to use oradim to register a new instance on that host as well.

      Rating:
      5
database  November 17, 2002
Reviewer:
      mo
Tom:

1.  Would "select * from v$database" give you the database name.  It seems that
usually it is the same as the instance name.

2.  When you connect in sql*plus is host the database or intance name? I think
it is database.

3. for the  scott/tiger is not there a host?

THank you,

          Followup:

1) "usually" -- good choice of words.  not "always", "usually"

2) you think wrong.  it is a tns connect string which might be connecting you to
a service (a colleciton of instances all serving the same database -- in this
case you would be right).  or it might be connecting you to a specific instance,
in which case you are not being connected to a database but to an instance and
whatever database it just happens to have mounted (in that case you would be
wrong).

You would need to read the networking guides in order to understand the subtle
difference.

3) huh?  makes no sense to me.  scott/tiger is a username/password.

HOST really has nothing to do with any of these questions.

      Rating:
      5
instance  November 17, 2002
Reviewer:
      mo
TOm:

I know that scott/tiger is a userid/password but I noticed in your book that
when you do

@connect scott/tiger  --->missing @host

you do not specify a host or tns connect string? So which instance/database will
it use?

          Followup:

the local one as defined by your oracle_sid and oracle_home or the two_task
environment variable (local on windoze).

      Rating:
      5
OPS-RAC  January 14, 2003
Reviewer:
      ram  from india
Great explanation!
<quote>
A database may be mounted and opened by many INSTANCES (Parallel Server)
concurrently <unquote>

does RAC work similar to OPS with regard to mounting/opening the db by multiple
instances (besides the new cache fusion) ?

      Rating: 4
on
      Windows  January 20, 2003
Reviewer:  r
       from london
Hi Tom,

I use DBCA on Windows to create additional databases. Now, when I create a
database I think it also creates an instance(correct me if I am wrong) and hence
I am able to get both the databases up & running at the same time. I understand
that an instance can only mount/open 1 database at a given point. On windows do
we have an option(using DBCA) to create only the database and not a new instance
so that I could use the existing instance to mount either of the databases.

Thanks very much indeed.

          Followup:

DBCA doesn't create an instance.

An instance is a set of processes + memory.  when you stop the service, the
INSTANCE is gone.

So, DBCA isn't creating an instance, it creates a database.  The instance is
fleeting, do a shutdown and goodbye instance.

So, just don't start the service (which is just a definition -- the service
itself consumes no resources) and you'll have no instance.

      Rating: 4
Follow up
      question.  January 20, 2003
Reviewer:  Saibabu
      Devabhaktuni  from Palo Alto, CA
Hi Tom,

You said sid/instance is a combination of ORACLE_HOME plus, ORACLE_SID plus,
HOSTNAME. You also said "i can have two instances in different homes on a single
machine with the same sid".

Each Oracle instance have background processes something like this
"ora_pmon_TEST".

Single Oracle unix account can have two different ORACLE_HOME's...but they
cannot have two sid's with same name. If it is possible what will the output of
"ps -ef|grep pmon"....

Thanks
 Sai

          Followup:

the output -- a list of processes of course?

      Rating: 4
Something more
      ..  January 20, 2003
Reviewer:  r  from
      london
Hi Tom,

No, I don't understand. Please bear with me.

Okay ..when I create a database using DBCA I get an additional service called
"OracleService<SID>" for the new database. So, I have got 2 services running
each for the database. Now when I shutdown one of the services that doesn't mean
that the other database will also be shutdown. Am I right ? If yes, then how
come you say "just don't start the service and you'll have no instance" , as
stopping one of the services does not mean that the instance is gone since the
other database is still operational.

Thanks for your patience.

          Followup:

an instance = set of running processes + memory. an instance is transient.

a dataqbase = set of files.  a database is forever.

A service = something windows artificially introduces to confuse us.

Now, in your case, you have TWO instances and TWO databases.  If you stop one of
the services -- you'll have ONE instance and TWO databases.  If you stop the
other service, you'll have ZERO instances and TWO databases.

You'll always have TWO databases (unless you erase them).

You'll have as many instances as you want at any point in time by starting and
stopping these services.

So, to answer "when I shutodnw one of the services that doesn't mean that the
other INSTANCE <not database> will also be shutdown" -- that is correct, they
are independent of eachother.

"If yes, then how come you say 'just don't .....'"  -- I said that cause if you
don't start the service, the instance won't be running.

I'm not understanding your confusion.  Each instance is separate and distinct,
you start them independent of eachother.  They have nothing whatsoever to do
with eachother.

The instance IS in fact gone -- since an INSTANCE is just processes+memory.
When you stop the service -- goodbye instance.  Database -- oh, that is still
there, instance -- gone.

So -- what exactly is the question?

      Rating: 4
more
      ...  January 20, 2003
Reviewer:  r
       from london
Tom,

This is the confusion. In your earlier reply you said that the DBCA does not
create an instance but only database. That's fine. But when I said that I see
two services you said "Now, in your case, you have TWO instances and TWO
databases".

Secondly, if I shutdown one of the services it means that there is 1 instance
and 2 database. Now, this means that I can shutdown the database for which I
service is currently running and startup the other database for which I just
shutdown the service. Is this correct ?

Thanks again for your patience. I appreciate it.

          Followup:

I was responding to your comment:

 So, I have got 2 services running
each for the database.

The services are running you said. Hence you have two instances going.

You do not shutdown a database -- you shutdown an instance, but anyway... For
practicality sake think of it this way:

o service running = processes+memory up = some database available.

I don't know how to say it.  An instance is just a process running. stop the
process and that instance "disappears" -- never ever to be seen again - NEVER.

For each database you want accessible on your machine, you will have an instance
running.

If you do not want to have access to that datbase -- just stop that instance,
easiest way on windows, stop the service.

An instance doesn't "exist" anywhere really -- it is just processes+memory.

Don't let the service confuse you.  It is nothing, a windows artifact, not
meaningful.

      Rating: 5
dear me
      ..  January 20, 2003
Reviewer:  r  from
      london
Tom,

I totally understand what you are saying about an instance and the database but
this Windows services has confused me a lot.

Okay ... one last try. Hopefully this will clear the confusion.

I said "So, I have got 2 services running each for the database" because when I
create a database using a DBCA it by default creates a service for the new
database. And hence I can assume that DBCA does create an instance. I infer this
from you following statement.
"The services are running you said. Hence you have two instances going."

So, finally back to my original question. "Does DBCA create an instance ?" and
you said "DBCA doesn't create an instance".

If your answer to the question is that the DBCA always creates a new instance
then I would ask "Is there any way that I can create a database without an
instance using DBCA".

Do you see where I am coming from ?

I would be very thankful to you for clarifying this confusion.

          Followup:

services are a "windoze" neccessary evil.  As they say in the wizard of oz
"ignore that man behind the curtain".

They are meaningless -- they are just things windows needs to start these things
up in the background.

A service is a windoze thing - it is not anything to do with the database other
than on that platform we need them in order to get started up in the
background....

DBCA doesn't create an instance.  It creates a database (set of files) and on
windows it registers the necessary services that can be used to start an
instance when you want.

Is there anyway to create a database without dbca?  Yes, definitely -- you use
oradim directly.

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/win.817/a73008/toc.htm
see chapter 6

      Rating: 3
Documentation
      conflicts with what you say  June 14, 2003

Reviewer:  A reader
Hi tom,
   You have quoted in your response above as
<quote>
 I would go a step further -- the combination of:

o ORACLE_HOME plus
o ORACLE_SID plus
o HOSTNAME

identifies the instance (i can have two instances in different homes on a single

machine with the same sid).

</quote>

However when I went thru the docs i found this...

<quote>
If you attempt to mount two Oracle8i databases with the same database name, you
receive the following error during the second mount:

ORA-01102: cannot mount database in EXCLUSIVE mode

If there are two or more Oracle8i databases on the same computer, but located in
different Oracle homes, the following rules apply:

*Each database name must be unique

*Each SID must be unique
</quote>

which essentially states that on the same machine the SID "must" be unique. Can
you please clarify??

          Followup:

that is probably true on windoze with the registry.  good point.

      Rating: 3
Having same SID
      for Primary & Standby  September 09, 2003

Reviewer:  Mohammed Osman  from Riyadh, KSA

Dear Tom,

Greetings

Thanks for answering my previous questions. I have quite a similar question
concerning SID & Services.  Would appreciate your advise.

I am in process of implementing Standby Database (Oracle Release 8.1.7.0.0) -
Mangement is considering for Oracle 9i but it will take quite some time.

Primary Database and Standby Database will be on a Different Node with same
Directory Structure.

The Database Name, Instance Name of Primary & Standby will be same but the
Service Name in Standby will be different from Primary.

Init.ora (Primary)

db_name = PROD
instance_name = PROD
service_names = PROD

Init.ora (Standby)

db_name = PROD
instance_name = PROD
service_names = STBY

Is it possible to have these settings for Standby DB ?

Oradim -NEW -SID PROD -SRVC OracleServiceSTBY -INTPWD <Password> -MAXUSERS 5

1) Can I have the same SID for Primary and Standby ?   When Primary fails I can
activate the Standby and change the IP Address of the Standby DB Server, thereby
users will use the same Tnsnames alias to connect the Database.

2) How do I get the data from Primary DB Redo Logs when it is not archived yet ?
 In 9i LGWR can write directly to the Standby Database, hence in 8i I have to
copy the Redo Logs from Primary Database and apply the Logs after activating the
Standby Database IF Primary Server can be accessable.

With best regards

Mohammed Osman

          Followup:

1) same sid is fine.  but you don't need a sid to connect remotely -- it is all
done by service these days (preferably anyway)

2) that is something you sort of have to do yourself.  if the primary fails, you
have to either

a) make the redo available to standby using any technique you want
b) lose the data in the redo logs

      Rating: 3
different SID
      on the same machine  October 13, 2003
Reviewer:
      dash  from co
OS : windows2000 Oracle : 8.1.7

Hi,
I have two database(sid) on same computer
(ex: DB1, DB2)
I tried to change initDB1.ora and initDB2.ora
so I tried shutdown DBx. It's good.
but startup option is fail.

ORACLE_HOME is same PATH(D:\oracle\ora81)
ORACLE_SID is different(DB1, DB2)
DB_NAME is different too.(DB1, DB2)

I tried to set ENV ORACLE_SID=DB1(or ORACLE_SID=DB2)
on windows prompt and
$> sqlplus internal@db1(or @db2)
sqlplus> startup mount

ora-01102: cannot mount database in EXCLUSIVE mode

My system is not OPS.
Windows registry :
ORACLE_DB1_PFILE = D:\oracle\admin\DB1\pfile\init.ora
ORACLE_DB2_PFILE = D:\oracle\admin\DB2\pfile\init.ora
ORACLE_HOME = D:\oracle\ora81
ORACLE_SID = DB1(only)

How can i startup DB1, DB2.

thanks

          Followup:

[tkyte@localhost tkyte]$ oerr ora 1102
01102, 00000, "cannot mount database in EXCLUSIVE mode"
// *Cause:  Some other instance has the database mounted exclusive or shared.
// *Action: Shutdown other instance or mount in a compatible mode.
[tkyte@localhost tkyte]$

your database is already running.

      Rating: 4
ok for standby
      & primary to have same sid and service_name ?   October
      27, 2003
Reviewer:  Susan  from Watertown, MA

We have a standby on a different server (host) than the primary.  TNSNAMES for
standby has the same sid and service_name for standby and primary. We also use
the same internal passwords.  This has been working fine, but, now we're setting
up a recovery catalog database on the same machine as the standby.  I'm
wondering how RMAN (and Net8 for that matter) knows to resolve @larg to the
primary and not the standby.  Thanks.

LARG_PRIM.NERI.ORG =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(PORT = 1521)(HOST = ORACLE))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = LARG)
    )
  )

LARG.NERI.ORG =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(PORT = 1521)(HOST = ORACLE))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = LARG)
    )
  )
LARG_STBY.NERI.ORG =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(PORT = 1521)(HOST = TROY))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = LARG)
    )
  )

          Followup:

the @larg is going to be larg.neri.org and it'll use that connect string after
that says "connect to the listener on the host ORACLE, port 1521"..

      Rating: 5
ORACLE_HOME and
      HOSTNAME  October 31, 2003
Reviewer:
      A reader
Tom,
On the Title 'Clear and "clean" answer!', you mentioned that

<quote>
you'll see them as the same. I would go a step further -- the combination of:

o ORACLE_HOME plus
o ORACLE_SID plus
o HOSTNAME

identifies the instance (i can have two instances in different homes on a single

machine with the same sid).
</quote>

Question
What is the purpose of Oracle home and how do I find out what oracle_home is?
What is the purpose of hostname and how do I find out what hostname is?

Thank you for your time.

          Followup:

oracle home is where Oracle lives -- its the root directory for the install.
It'll have a bin directory (software) a lib directory, and so on.  Its "oracle's
home" -- literaly

hostname is what your SA named the machine.  on unix, you simply type
"hostname".

      Rating: 5
oracle home and
      host name  November 02, 2003
Reviewer:
      A reader
Is there a SQL etc to find out oracle_home and hostname?

Thank you

          Followup:

select host_name from v$instance;

the oracle_home really isn't "in the database" anywhere.  It is where you
installed the software.  It might be inferred from some init.ora parameters, but
not necessarily.

      Rating: 5
same sid on the
        December 06, 2003
Reviewer:  Reader

Following is an extract from above discussions in this thread.

*Each database name must be unique

*Each SID must be unique

which essentially states that on the same machine the SID "must" be unique. Can
you please clarify??

Followup:
that is probably true on windoze with the registry.  good point.

Tom, can we have two databases with the same name on Unix platform if the two
databases are in different ORACLE_HOME? Thanks.

          Followup:

each sid in each oracle_home must be unique actually.  but it would be a really
bad practice to have the same sids in different oracle homes.

On windows, it is probably 100% true that the sid must be unique on a machine.

On unix -- everything is possible.  The two oracle homes are totally unaware of
eachothers existence, totally separate.

Windows, with its copy protection device -- opps meant "registry", makes
everything "shared"

don't have any windows installed, cannot verify, but would not doubt it.

      Rating: 5
Duplicate SIDs
      on Windows  December 06, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Oracle is implemented as a service on Windows... meaning that a sid is
"connected" to a service.  If you have a SID called 'TEST' then you will have a
service called OracleServiceTEST.  For that reason you can not have identical
SIDs on a Windows server - it is not possible to have 2 services with the same
name.  If you try to create a SID that is a duplicate (even if you use oradim
from a different Oracle Home) you will receive:

DIM-00020: A service for this SID is already created. Please enter a different
SID name. No action has been taken
O/S-Error: (OS 1073) The specified service already exists.

- Mark

December 07, 2003 -- Thanks for the question regarding "Some
      clarifications... ", version 8

      You Asked
Hi,

1. What exactly is 'Dynamic SQL' ?  How does that differ from normal queries /
subqueries ?

2. Why is there a concept of 'Dual' table here in Ora ? No other RDBMS makes use
of this sort of table. Just wondering what is the need for this table ?
By the way, incase someone inserts a row / drops the table, I can realse that
none of Oracle related applications will work, since for every system related
query, I need to say "Select .... from Dual"

3. Just would like to clarify a "Date" related issue.
Should a date be stored in db by using To_char(...) or To_date(..) ?
Assume that my application takes Date in DD-MM-YYYY format. Can't I just insert
the date in same format & retrieve also in same format ? i.e Other than using
"Alter session set nls_date_pa.... " or "select to_char(...) from ....", can't I
just override the deafult Date format of Ora ?

Thank u,

 - Santhosh

      and we said...
1) dynamic sql is sql that is not known at compile time.

   begin
      for x in ( select * from emp )
      loop

that is static sql.

   begin
       open ref_cursor for 'select id from ' || table_name;
       loop
          fetch ref_cursor into l_id;
          ....

that is dynamic sql -- everytime we run the second one, it could be a brand new
query that never existed.

2) really -- what do you use in informix?  db2?

we do not MAKE you use dual.  It is there in the event you want to use it.  Feel
free NOT to use it.

Show me where you NEED to use dual please???

3) (i have opinions on things, the following is my strong opinion)

you should use explicit formats with TO_DATE on the way into a date, eg:

    insert into t values ( to_date( :bv, 'dd-mon-yyyy' ) );

why?  because YOU do NOT control the default date format, you CANNOT control the
default date format - and (especially) if you use a format like 'dd/mm/yyyy' but
someone switches to 'mm/dd/yyyy' -- you have a bug.  Or if they switch to
'dd-mon-yyyy' -- your program stops working.

On the WAY OUT you should always use TO_CHAR with an explicit format:

    select to_char( date_column, 'dd-mon-yyyy' ) .....

unless you are in PLSQL which has a native DATE datatype. If you are in VB where
the date -> string or jdbc where date -> string -- use the to_char for the SAME
reason as to_date above.

see

http://asktom.oracle.com/~tkyte/Misc/NLSDateFormat.html
but don't rely on it -- it is far too easy for the user to change it yet again.

Review &
      Followup

      Rating: 3
Reply to ur
      query...  December 09, 2002
Reviewer:
      A reader
Hi,

Great was ur explanation about the Dynamic SQL & Date insertion issue. Now, to
come back to the topic u wanted me to answer u -

SQL Server does not make use of 'Dual' table.
By the way, answer this - Incase a trouble maker, just inserts a row in Dual OR
drops the table, wouldn't my DB go for a toss ??

Also, u had asked me where we need to typically use Dual -- "Select sysdate from
Dual", "Select user from dual" are some of the common statements, I guess, every
appl will use many times...

Can u show me a way to get username / date from Ora other than using Dual ? (Of
course, I get it by saying "select sysdate from EMP", but that would, serve no
purpose )

          Followup:

Right -- they make you code something like the totally non-intuitive:

procedure p
is
   ..
begin
   select user into @username

whereas we make you code sensibly:

procedure p
is
   ..
begin
   username := user;

You do NOT need to select user from dual, sysdate from dual.

is
   l_date   date := sysdate;
   l_username varchar2(30) := user;
begin
   ....
   l_date := sysdate+55;
   ......

no dual there.....

      Rating: 3
U haven't
      answered fully....  December 09, 2002
Reviewer:
      A reader
Hey Tom,

U haven't answered my question fully -
Incase a trouble maker, just inserts a row in Dual OR
drops the table, wouldn't my DB go for a toss ??

          Followup:

and if some joker deletes from sys.obj$ wouldn't your db go for a toss.

Yes, if you directly modify any table key to the operation of the database,
you know -- the data dictionary -- your DB could "go for a toss"

I would hazzard a guess however, that if you modified the SQLServer data
dictionary directly in a "bad way", the same thing would happen to them would it
not?

I fail to see how this is a shortcoming or anything -- it is common sense.  If
you muck with the data dictionary -- all bets are off.

If you setup your system so insecurely as to let "some joker" do this - well,
Caveat Emptor.

      Rating: 3
DUAL
      Table  December 09, 2002
Reviewer:  Martin
       from The Netherlands
Hi Tom and Santhosh,

I can recall a situation where some Dopey inserted multiple rows in DUAL; With
in a few seconds every session got flooded with errors. Oracle seems to rely
heavily on the DUAL table. But idiots should not be given the SYS password in
the first place so in a normal situation this would never happen.

          Followup:

The same bad things would happen if you inserted into any SYS.table

It is not that dual is special.

It is that is is stupid to manipulate the DATA DICTIONARY OWNED BY SYS BY HAND.
change DUAL to any other sys owned table and the same is true.  We use LOTS of
tables internally, dual isn't special in that regards.

      Rating: 5
DUAL
      table  December 09, 2002
Reviewer:  David
       from Sydney, Australia
You asked what you use in other database systems instead of  the DUAL table (or
a similar table). In PostgreSql one is allowed to SELECT without a FROM clause.
For example, instead of

SELECT USER FROM DUAL;

one would write:

SELECT USER;

This is not a better way of doing this, just different. And most likely
PostgreSql way is non-standard.

          Followup:

and you don't need to use dual!

   ...
   x := user;
   ...

period.

      Rating: 5
Inserting in
      DUAL?  December 10, 2002
Reviewer:  Martin
      Guillen  from Argentina.
AFAIK nobody can Insert into dual with out explicit added rights:

SQL> desc dba_tab_privs
 Name                                Null?    Type
 ----------------------------------- --------
--------------------------------------------
 GRANTEE                             NOT NULL VARCHAR2(30)
 OWNER                               NOT NULL VARCHAR2(30)
 TABLE_NAME                          NOT NULL VARCHAR2(30)
 GRANTOR                             NOT NULL VARCHAR2(30)
 PRIVILEGE                           NOT NULL VARCHAR2(40)
 GRANTABLE                           VARCHAR2(3)
 HIERARCHY                           VARCHAR2(3)

SQL> select * from dba_tab_privs where table_name='DUAL'
  2  /

GRANTEE    OWNER         TABLE GRAN PRIVILEGE                                GRA
HIE
---------- ------------- ----- ---- ---------------------------------------- ---
---
PUBLIC     SYS           DUAL  SYS  SELECT                                   YES
NO

SQL>

          Followup:

correct, it would be 100% a SELF INFLICTED WOUND, no different then someone
issuing a delete on obj$ directly.

      Rating: 5
Well, but there
      are sequences  December 10, 2002
Reviewer:  Arup
      Nanda  from Norwalk, CT, USA
As to the original question, why does Oracle depend on DUAL; Oracle does not
*depend* on it, you may do so, if you choose. However in some reviews it was
blatant to claim that inserting into dual would make the apps fail. Of course,
they will; so will it be if you pull the power plug! But would you consider that
possibility while deciding between Oracle and an "non-dual-table-database" like
SQL Server?

However, it all fairness, the dependence on dual table still exists for
selecting from sequences and to some extents using DECODEs. the latter can be
resolved by using CASE statements but, still... Hopefully in the later releases
the dependence for sequences will be removed.

          Followup:

but -- why do you need them for sequences?

  insert into t ( id, .... ) values ( my_seq.nextval, .... )
  returning ID into l_id;

      Rating: 4
select
      sequence_name.nextval from dual  December 11, 2002

Reviewer:  Paul  from UK
I work on a system where we load files into the database using SQL*Loader.  Each
file has a header record which goes in one table, while the data records go into
another. To preserve the master-detail relationship we have a trigger on the
master that populates a primary key with file_id.nextval, while the child
records have their foreign key populated by a trigger that uses file_id.currval.
 Is this not a legitimate use of "select sequence_name.nextval from dual", or is
there a better way to achieve what we want?

Also, I don't think you can use the RETURNING clause if you are doing a "INSERT
INTO .. SELECT .. FROM" type of insert, for obvious reasons, I guess.

Paul

          Followup:

You could just put the "sequence.nextval" and "sequence.currval" in the sqlldr
control file itself.

True -- but why would you need a select from dual in such a case?

I mean, you might

insert into t select seq.nextval, .....

in which case, you wouldn't need to select from dual.  Or, you might be using
currval (it is a "detail table") but that means you already inserted a parent
row (singleton) with nextval and got it using the returning clause....

Not that select'ing from dual is inheritly bad -- it just isn't generally
needed.  there are times, like your trigger example, when you have to if that is
the way you want to do it -- but that is only because the sequence "lives in the
database" - like a table - and you do need to use SQL to access it.

      Rating: 5
thanks for your
      views  December 11, 2002
Reviewer:  Paul
       from UK
We use triggers to populate all our surrogate primary keys 'as standard'.  I
guess this is so any application inserting data "doesn't have to worry" about
this.  I'll certainly have the sequences specified in the control file next
time, though.

My "INSERT INTO .. SELECT .. FROM" example was from a web application where I
was copying 90% of an existing record to a new one and needed to pass back the
new primary key in order for the web app to display the new data.  But in this
case (i.e. inserting a single record), I'd guess that throwing a "SELECT FROM
DUAL" in there would not be noticed by the user!

Thanks

Paul

      Rating: 4
Returning
      Clause in Insert  December 11, 2002
Reviewer:
      A reader
But, we need to use select seq.nextval from dual, when we insert into views.

          Followup:

really?

ops$tkyte@ORA920.LOCALHOST> create table t ( x int );

Table created.

ops$tkyte@ORA920.LOCALHOST> create or replace view v as select * from t;

View created.

ops$tkyte@ORA920.LOCALHOST> create sequence s;

Sequence created.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> declare
  2          l_x number;
  3  begin
  4          insert into v (x) values (s.nextval) returning x into l_x;
  5          dbms_output.put_line(l_x);
  6  end;
  7  /
1

PL/SQL procedure successfully completed.

      Rating: 4
Returning old
      values during an update  December 07, 2003

Reviewer:  Srikanth Adiga  from Pune
Can I use returning (or something else) to return the old value during an
update?

E.g. Before update table contains,
name_id = 1
name = sri

now during my update: "update name_table set name = tom where name_id = 1;"
i also want to know what was the old value of name.

Is it possible?

~Sri

          Followup:

well, you should select that data FOR UPDATE before updating......

then you can know.

but it'll take two SQL statements at the very least (even with a trigger -- one
to update, one to retrieve)

December 08, 2003 -- Thanks for the question regarding "Append
      Hint", version 817

      You Asked
Hi Tom
I have read about the append hint that when its used with the Insert stmt it
takes more space than a usual Insert would.
In our case we have a batch process that first truncates tables and then does
Inserts with the Append hint.
In such a case would it also take more space than ususal?
From my understanding a truncate resets the HWM , so does the insert reuse all
the space from the baseline or still waste space sue to its very nature?

THanks

      and we said...
It only takes more space in that is loads above the high water mark, it will NOT
use space that is on the freelist for that table.

If you truncate -- nothing will be on the freelist, everything is above the high
water mark and the storage will be the same.  Using append will not "waste"
space in this case.

Review &
      Followup

      Rating:
      3
Suresh  November 12, 2001
Reviewer:
      A reader
You may use "Truncate table table_name REUSE STORAGE" to make sure that you
don't waste any space.

          Followup:

that won't waste or NOT waste space -- it just doesn't release the currently
allocated space (so in fact, it could "waste space" if the table had 1gig
allocated to it and you load it up with 10meg of data -- it'll have almost a gig
of alloocated but not used space -- which many people would consider "wasted")

It really doesn't have any affect on the space usage of the direct path insert.

      Rating:
      3
  February 09, 2002
Reviewer:  Make
       from Alpharetta, GA
if there were spaces under the high water mark, and the the direct load only
using the space above the high water mark, why it is not considered as wastful
of the space? since the HWM can not be released till truncating it, then the HWM
will increase every time as the direct loading.

          Followup:

my point is -- it may or may not be "wasted" space.

Suppose you have 10meg on the freelists.  You do a direct path load, it'll not
touch that data on the freelist instead it will go above the HWM.  Now, is that
10meg "wasted"?  Not if you also insert into the table as part of your day to
day operations.  Not if you update the data causing it to grow (and hence
migrate or chain rows).  It is not "wasted space" in general.

If all you do is direct path load -- all of the time -- you will not have any
space on the freelists anyway (nothing to "waste").  In order to get stuff on
the freelist you have to be doing conventional inserts/updates and deletes.  If
you are doing that -- then I would say the space is definitely not wasted,
you'll use it later with conventional inserts/updates.

      Rating: 5
thanks for
      clearfying  February 10, 2002
Reviewer:
      Make

      Rating:
      5
  November 18, 2002
Reviewer:  DJ
       from Newton, MA
What if all I do is:
1. Direct path insert
2. Convention path delete

Will this waste space?

          Followup:

waste is such a RELATIVE term.

The space freed by a DELETE (there is but one kind of DELETE) will never be
reused by a direct path insert.  So, if you feel the deleted spaced is wasted --
it will be wasted.

      Rating: 4
Will APPEND cmd
      extend the datafiles  June 06, 2003
Reviewer:
      Neeraj Nagpal  from Los Angeles, CA
Will the APPEND hint extend the datafile even though there is available space in
that datafile ?

          Followup:

if the available space is insufficient to satisfy the next extent, sure.

remember a datafile in a DMT (dictionary managed tablespace) might have 500meg
free but be unable to satisfy a request for a 1meg extent (reason 12432 to use
LMT's) due to fragmentation.

if there was sufficient, logically contigous space available, the append would
use it.  else, it'll extend the datafile

      Rating: 4
HWM after
      conventional inserts and direct path inserts  July 08, 2003

Reviewer:  Chuan Sat  from VA USA
Tom

     As usual, your valuable insights are very much appreciated.

     Hypothetically.  If I used 100M for conventional path inserts. HWM now is
100M.  Then do another 100M of direct path inserts.  And do another 100M of
conventional inserts. What is the HWM now?

Thanks so much

          Followup:

if you loaded 300m of data total, it'll be 300m

      Rating: 3
Does MERGE with
      APPEND use the HWM or something else  September 16, 2003

Reviewer:  Arnoud Otte  from Portland, OR, USA

Hi Tom,

I understand from your answer that the white space ("wasted") should be minimal.
We experienced something a bit more than minimal though.

From the documentation, after we saw a table growing bigger than the data
warranted.

"Parallel DML Tip 2: Direct-Path INSERT
The append mode is the default during a parallel insert: data is always inserted
into a new block which is allocated to the table. Therefore the APPEND hint is
optional. You should use append mode to increase the speed of INSERT operations,
but not when space utilization needs to be optimized. You can use NOAPPEND to
override append mode."

"High Water Mark
The high water mark is the boundary between used and unused space in a segment.
As requests for new free blocks that cannot be satisfied by existing free lists
are received, the block to which the high water mark points becomes a used
block, and the high water mark is advanced to the next block. In other words,
the segment space to the left of the high water mark is used, and the space to
the right of it is unused."

Armed with this knowledge, and knowing we were doing parallel MERGEs, and our
tablespace is defined as

  Property                  Value
--------------------------------------
- BLOCK_SIZE                8192
- INITIAL_EXTENT            1048576
- NEXT_EXTENT               1048576
- MIN_EXTENTS               1
- MAX_EXTENTS               2147483645
- PCT_INCREASE              0
- MIN_EXTLEN                1048576
- STATUS                    ONLINE
- CONTENTS                  PERMANENT
- LOGGING                   NOLOGGING
- FORCE_LOGGING             NO
- EXTENT_MANAGEMENT         LOCAL
- ALLOCATION_TYPE           UNIFORM
- PLUGGED_IN                NO
- SEGMENT_SPACE_MANAGEMENT  MANUAL
- DEF_TAB_COMPRESSION       DISABLED

We did the following tests:

   --#########################################################################
   Connected to Oracle9i Enterprise Edition Release 9.2.0.4.0
   SET ECHO ON;
   COLUMN segment_name FORMAT a12;

   DROP TABLE src;
   CREATE TABLE src (
     PK  INTEGER NOT NULL,
     COL INTEGER);
   ALTER TABLE src ADD CONSTRAINT PK_src PRIMARY KEY (PK) USING INDEX;

   DROP TABLE trgt;
   CREATE TABLE trgt (
     PK  INTEGER NOT NULL,
     COL INTEGER);
   ALTER TABLE trgt ADD CONSTRAINT PK_trgt PRIMARY KEY (PK) USING INDEX;

   DECLARE
      i INTEGER;
   BEGIN
      FOR i IN 1 .. 8 LOOP
         INSERT INTO src VALUES (i,i);
      END LOOP;
      COMMIT;
   END;
   /

   SELECT segment_name, SUM(bytes) bytes, SUM(blocks) blocks, COUNT(*) ext_cnt
   FROM user_extents ue
   WHERE segment_name = 'TRGT'
   GROUP BY segment_name;

   SEGMENT_NAME      BYTES     BLOCKS    EXT_CNT
   ------------ ---------- ---------- ----------
   TRGT            1048576        128          1

   ALTER SESSION ENABLE PARALLEL DML;

   DECLARE
      i INTEGER;
   BEGIN
      FOR i IN 1 .. 2 LOOP
         MERGE /*+ PARALLEL (mt,2) */ INTO trgt mt
         USING
            (SELECT *
            FROM src
            WHERE    src.pk = i) ms
         ON
            (mt.pk = ms.pk)
         WHEN MATCHED THEN UPDATE SET
            mt.COL = ms.COL
         WHEN NOT MATCHED THEN INSERT (mt.pk,mt.COL)
               VALUES (ms.pk,ms.COL);
         COMMIT;
       END LOOP;
   END;
   /

   -- See above for actual SQL
   SEGMENT_NAME      BYTES     BLOCKS    EXT_CNT
   ------------ ---------- ---------- ----------
   TRGT            3145728        384          3
   --#########################################################################

OK, so it does seem to be the implicit APPEND hint.
1) However, why is it adding an extent per insert/commit, when there are plenty
of blocks empty?
2) Is the HWM kept at extent level instead of block level?

So, following up on this, is the MERGE different than the INSERT?

   --#########################################################################
   DECLARE
      i INTEGER;
   BEGIN
      FOR i IN 3 .. 4 LOOP
         INSERT /*+ PARALLEL(mt,2) */ INTO trgt mt
            SELECT *
             FROM src

             WHERE    src.pk = i;
         COMMIT;
       END LOOP;
   END;
   /

   -- See above for actual SQL
   SEGMENT_NAME      BYTES     BLOCKS    EXT_CNT
   ------------ ---------- ---------- ----------
   TRGT            5242880        640          5
   --#########################################################################

Well, that's even more confusing.
3) Why does INSERT PARALLEL act different from MERGE PARALLEL for inserts?

Well, OK, does the NOAPPEND hint avoid extents from being added, as the
documentation suggests?

   --#########################################################################
   DECLARE
      i INTEGER;
   BEGIN
      FOR i IN 5 .. 6 LOOP
         MERGE /*+ NOAPPEND PARALLEL (mt,2) */ INTO trgt mt
         USING
            (SELECT *
             FROM src
             WHERE    src.pk = i) ms
         ON
            (mt.pk = ms.pk)
         WHEN MATCHED THEN UPDATE SET
            mt.COL = ms.COL
         WHEN NOT MATCHED THEN INSERT (mt.pk,mt.COL)
               VALUES (ms.pk,ms.COL);
         COMMIT;
       END LOOP;
   END;
   /

   -- See above for actual SQL
   SEGMENT_NAME      BYTES     BLOCKS    EXT_CNT
   ------------ ---------- ---------- ----------
   TRGT            5242880        640          5
   --#########################################################################

Yes, NOAPPEND hint does avoid extents from being added.
So what does an INSERT APPEND do?

   --#########################################################################
   DECLARE
      i INTEGER;
   BEGIN
      FOR i IN 7 .. 8 LOOP
         INSERT /*+ APPEND */ INTO trgt mt
            SELECT *
             FROM src
             WHERE    src.pk = i;
         COMMIT;
       END LOOP;
   END;
   /

   -- See above for actual SQL
   SEGMENT_NAME      BYTES     BLOCKS    EXT_CNT
   ------------ ---------- ---------- ----------
   TRGT            6291456        768          6
   --#########################################################################

So, INSERT PARALLEL and INSERT APPEND are acting the same, but INSERT PARALLEL
and MERGE PARALLEL, although we're only doing inserts, are not acting the same.

Any clarification of the behaviour observed is appreciated.

          Followup:

looks like an interesting new question...

the problem with long involved things that I have to look at in a review section
is -- well, i don't have any method to "back burner it".

meaning, I see reviews chronologically.  I either answer them right there or
not.

this will take more then 30/60 seconds to research and I'm hungry right now :)

hence, i'll just look at the rest of the reviews and go eat......

when I'm taking new questions -- feel free to put this that -- too big for right
here.

      Rating: 4
Unable to
      Extend tablespace SORT  September 28, 2003

Reviewer:  Sridhar Reddy  from USA
Hi Tom,
   i have a small question regarding the append hint.
i usally do it like this,which i learned from you.
mark indexes unuasble,
disable primary key,
alter table nologging,
do an insert /*+ append */ into table (select ..)
enable primary key,
rebuild indexes nologging.

the issue here is one of my scripts throw an error
'Unable to Extend tablespace SORT'
and my DBA asks me not to mark indexes unusable and do the insert append.do you
think that this was the cause for the problem.i told him that the error is
because of something else.
Thanks,
M S Reddy.

          Followup:

and what step exactly is throwing this error.

if it is:

insert /*+ append */ into table t select .....

then the SELECT is the culprit and it may well have nothing to do with your
indexes at all (unless of course the select itself is selecting from t and due
to the lack of indexes.....)

but this is sort of like me asking "hey, my car won't start -- whats wrong".
Insufficient data to say anything.

What is the thought process behind not marking the indexes unusable?  Why do
they feel that is the issue?

      Rating: 4
Unable To
      Extend tablespace SORT  September 29, 2003

Reviewer:  Sridhar Reddy  from USA
Tom,
   Thanks a lot for the quick response.
Actually i think it is the problem with the select ,it is doing a full scan for
quite a few tables.i will try to add indexes on those columns which the query is
using and then see if it does the full table scan.do you think indexing those
columns will eliminate the full scan and do an index scan.we are doing a join on
some varchar2 columns.if i add indexes on them will it help me to eliminate this
error
Thanks,
M S Reddy

          Followup:

FULL SCANS = GOOD
INDEXES    = BAD

that is true as often as the converse.

sizing system to do workload required = good

is a correct statement as well.

      Rating: 4
Unable to
      Extend Tablespace SORT  September 29, 2003

Reviewer:  Sridhar  from USA
Tom,
   I did not understand your previous reply.
you mean to say
TABLE ACCESS FULL -- GOOD
INDEX FULL SCAN -- BAD
or is it the other way around.
when i add indexes to those columns that iam using in the join
TABLE ACCESS FULL is changed to
INDEX FULL SCAN in the explain plan.
Is that how it should behave?
Thanks for the help!
Thanks,
M S Reddy

          Followup:

nope. i meant to say

FULL SCANS = GOOD
INDEXES    = BAD

using an index is not always good.
using an index is not always bad
full scanning is not always good.
full scanning is not always bad.

an index full scan just means it is reading the index in order -- every row (so
it probably took out a sort).  that could be good -- but I cannot really comment
-- no data to look at here....

is the query running faster and using less temp?  then you have success :)

      Rating:
      5
SYS_OP_MAP_NONNULL  November 21, 2003

Reviewer:  M S Reddy  from USA
Tom,
  what does this do ? SYS_OP_MAP_NONNULL
we are using it in an inline view update
and has SYS_OP_MAP_NONNULL(and column name)
what is the advantage?

Thanks,
M S Reddy

          Followup:

just an internal "hint".

"ignore that man behind the curtain" sort of stuff.

      Rating: 4
partitioned
      table AND TRY TO INSERT 3Million records  November 25, 2003

Reviewer:  Alex smith  from UK
Tom

I have one table TAB1(x NUMBER(4), y NUMBER(10), Z VARCHAR2(10));
column x and Y are the part of primary key and table is partitioned on X. when I
am using the direct insert it takes 3 hoursto load 3M records.
INSERT /*+ APPEND PARALLEL(TAB1,8) */ INTO TAB1
SELECT /*+ PARALLEL(a,8) */FROm TAB2;
TAB2 has the same structure as TAB1.

while if I use PL/SQL table then it finishes in 1 hour 30 mins

why it is so.

          Followup:

what do you mean "if i use a plsql table"

give us a sample of what you are doing (code sample) and if you have the tkprofs
-- they would be very telling.

      Rating: 4
Bulk insert
        November 26, 2003
Reviewer:  Alex
       from UK
I mean if I insert 100,000 records using the ProC code then it is faster the the
direct insert.
Why this is happening.

Can you please tell me to make the insert faster what all the things we should
consider like partition size etc.

Thanks

          Followup:

i would need a full blown example here -- or at least a much better description
of what we are comparing to what.

we've gone from plsql table to proc

not sure the setup -- tab1 and tab2, same server, different server.

100,000 records in proc versus millions in the database?

i don't understand WHAT we are comparing.

      Rating: 4
Bulk
      Insert  November 26, 2003
Reviewer:  Alex
       from UK
I mean if I insert 100,000 records in loop using the ProC code then it is faster
the then
direct insert.
Why this is happening.

Even through proc it is take approx 3 min for 100,000 records.

Can you please tell me to make the insert faster what all the things we should
consider like partition size etc.

Thanks

      Rating: 5
a question from
      my friend:  December 08, 2003
Reviewer:
      Fenng
Oracle9i Database Administrator's Guide Release 2 (9.2)
chapter 5
Managing Oracle Processes 5-19
said that :

-------------------------------------------------
Altering Parallel Execution for a Session
Disabling Parallel Execution
All subsequent DML (INSERT, UPDATE, DELETE), DDL (CREATE, ALTER), or query
(SELECT) statements will not be parallelized after an ALTER SESSION DISABLE
PARALLEL DML|DDL|QUERY statement is issued. They will be executed serially,
regardless of any PARALLEL clause or parallel hints associated with the
statement. The following statement disables parallel DDL:
ALTER SESSION DISABLE PARALLEL DDL;
-------------------------------------------------

then do the following operation:

SQL> create table test as select * from dba_objects;

&#34920;&#24050;&#21019;&#24314;&#12290;

SQL> set autot on exp

SQL> select count(*) from test;

COUNT(*)
----------
37279

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE
1 0 SORT (AGGREGATE)
2 1 TABLE ACCESS (FULL) OF 'TEST'

SQL> select /*+ parallel(test, 2) */ count(*) from test;

COUNT(*)
----------
37279

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=25 Card=1)
1 0 SORT (AGGREGATE)
2 1 SORT* (AGGREGATE) :Q37000
3 2 TABLE ACCESS* (FULL) OF 'TEST' (Cost=25 Card=41331) :Q37000

2 PARALLEL_TO_SERIAL SELECT /*+ PIV_SSF */ SYS_OP_MSR(COUNT(*)) F
ROM (SELECT /*+ NO_EXPAND ROWID(A2)

3 PARALLEL_COMBINED_WITH_PARENT

SQL> alter session disable parallel query;

&#20250;&#35805;&#24050;&#26356;&#25913;&#12290;

SQL> select /*+ parallel(test, 2) */ count(*) from test;

COUNT(*)
----------
37279

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=50 Card=1)
1 0 SORT (AGGREGATE)
2 1 SORT* (AGGREGATE) :Q39000
3 2 TABLE ACCESS* (FULL) OF 'TEST' (Cost=50 Card=41331) :Q39000

2 PARALLEL_TO_SERIAL SELECT /*+ PIV_SSF */ SYS_OP_MSR(COUNT(*)) F
ROM (SELECT /*+ NO_EXPAND ROWID(A2)

3 PARALLEL_COMBINED_WITH_PARENT

SQL> alter system flush shared_pool;

&#31995;&#32479;&#24050;&#26356;&#25913;&#12290;

SQL> select /*+ parallel(test, 2) */ count(*) from test;

COUNT(*)
----------
37279

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=50 Card=1)
1 0 SORT (AGGREGATE)
2 1 SORT* (AGGREGATE) :Q41000
3 2 TABLE ACCESS* (FULL) OF 'TEST' (Cost=50 Card=41331) :Q41000

2 PARALLEL_TO_SERIAL SELECT /*+ PIV_SSF */ SYS_OP_MSR(COUNT(*)) F
ROM (SELECT /*+ NO_EXPAND ROWID(A2)

3 PARALLEL_COMBINED_WITH_PARENT

&#30456;&#20851;&#21442;&#25968;
SQL> show parameter parallel

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
fast_start_parallel_rollback string LOW
log_parallelism integer 1
parallel_adaptive_multi_user boolean TRUE
parallel_automatic_tuning boolean TRUE
parallel_execution_message_size integer 4096
parallel_instance_group string
parallel_max_servers integer 10
parallel_min_percent integer 0
parallel_min_servers integer 2
parallel_server boolean FALSE
parallel_server_instances integer 1
parallel_threads_per_cpu integer 2
recovery_parallelism integer 0
SQL>

what's wrong ? or the document's error?

Thank you!

          Followup:

use tkprof.  autotrace (using explain plan) doesn't have to tell you "the truth"

sql_trace plus tkprof will tell you the true.

December 08, 2003 razvan -- Thanks for the question regarding
      "alter database -- clear logfile", version 7.3.2

      You Asked
hello tom,

can you explain me what does the following statement and in what
case should i utilise this:

alter database clear unachived
logfile group 1;

alter database clear unarchived
logfile group 1
unrecoverable datafile;

i seem it does the same things as does the alter database with
resetlogs option.

thanks a lot
razvan

      and we said...
Straight from the docs on this one:

<quote ref=sql ref manual>
CLEAR LOGFILE

reinitializes an online redo log, optionally without archiving
the redo log. CLEAR LOGFILE is similar to adding and dropping a
redo log, except that the statement may be issued even if there
are only two logs for the thread and also may be issued for the
current redo log of a closed thread.

UNARCHIVED You must specify UNARCHIVED if you want to reuse a
redo log that was not archived.

WARNING: Specifying UNARCHIVED makes backups unusable if
the redo log is needed for recovery.
Do not use CLEAR LOGFILE to clear a log needed for media
recovery. If it is necessary to clear a log containing redo
after the database checkpoint, you must first perform
incomplete media recovery. The current redo log of an open
thread can be cleared. The current log of a closed thread can be
cleared by switching logs in the closed thread.

If the CLEAR LOGFILE statement is interrupted by a system or
instance failure, then the database may hang. If this occurs,
reissue the statement after the database is restarted. If
the failure occurred because of I/O errors accessing one member
of a log group, then that member can be dropped and other
members added.

UNRECOVERABLE DATAFILE
You must specify UNRECOVERABLE DATAFILE if you have taken
the datafile offline with the database in ARCHIVELOG mode (that
is, you specified ALTER DATABSE ... DATAFILE OFFLINE without the
DROP keyword), and if the unarchived log to be cleared is needed
to recover the datafile before bringing it back online. In this
case, you must drop the datafile and the entire tablespace once
the CLEAR LOGFILE statement completes.

</quote>

So, that explains what it does.  As to when you would use it
(with extreme caution)--

o you might use it in some cases to downgrade a database if the
version you are downgrading from has changed the redo log format
(hence making that incompatible with the prior release)

o If an online redo log file has been corrupted while the
database is open, the  'alter database clear logfile' command
can be used to clear the files without the database having to be
shut down.

o Your redo log was "inadvertantly" dropped at the OS level.

Review &
      Followup

      Rating: 3
Status of log
      files  December 08, 2003
Reviewer:  Anil
      Pant  from Hyderabad, India
The v$logfile shows the following status :
Invalid - file is inaccessible.
Stale - contents are incomplete
Deleted - file is no longer used
Blank - file is in use.
Pls correct me if Im wrong.

Deleted means when we drop the logfile though the file is deleted physically for
Oracle its deleted.
Blank is when LGWR is currently writing that logfile.
Also pls explain. Invalid and Stale. When is file inaccessible.

The v$log has following status :
Active / Inactive / Current / Clearing / Clearing_Current / Invalidated / Unused

Pls tell me what is the difference among Clearing,  Clearing_Current and Unused
?

Am I correct if I say when COMMIT is done LGWR is invoked and it writes Redolog
entries. Is there anything other than this happens ?Im speaking from Oracle 9
point of view.

          Followup:

see

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96536/ch399.htm#1117308
the v$ tables are documented in that guide, that is specifically for v$log

when you commit, lgwr flushes the redo log buffer and returns a status back to
you (success or failure)

December 08, 2003 sangita -- Thanks for the question regarding
      "querying a clob", version 9.0.1

      You Asked
Hi Tom

I have a table t_request
(request_number number,
request_detail  clob)

For example the clob could have a value of
<REQUEST_DETAIL><GROUP_TYPE>PR</GROUP_TYPE><GROUP_NAME>DATA ENGINEERING ASSET
MANAGEMENT</GROUP_NAME><BUS_UNIT_ACRN>SCS-FCAT</BUS_UNIT_ACRN><PROJ_MGR_ID></PROJ
_MGR_ID><PROJ_MGR_NAME>Roland Roy</PROJ_MGR_NAME></REQUEST_DETAIL>

I need a query to extract the value in the Group_type tag .
I tried the following but am getting an error

sql >     select
e.request_detail.extract('/REQUEST_DETAIL/GROUP_TYPE/text()').getStringVal()
    from t_request e
       where request_number = 9348605;
select
e.request_detail.extract('/REQUEST_DETAIL/GROUP_TYPE/text()').getStringVal()
                        *
ERROR at line 1:
ORA-22806: not an object or REF

However the same code works in a PL/SQL block where i convert the request_detail
(clob) to sys.xmltype before using the extract
function on it.

Please show me how to extract the value out of an xml tag on a clob using a sql
query . I have seen various examples of doing the
same on xmltype column but they do not work on a clob .

Thanks
Sangita

      and we said...
Well, you know -- this is alot like storing a DATE in a number, a number in a
string.

You know -- a bad idea.  You'll spend the rest of your life converting -- and if
the data in the clob isn't actually XML (well formed enough), you won't find out
until you need it (just like when that string that should be a number contains
'hello world')

Here is a temporary solution -- long term, use the right type for the data:

ops$tkyte@ORA920LAP> create table t ( x clob );

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> insert into t values (
  2  '<REQUEST_DETAIL>
  3    <GROUP_TYPE>PR</GROUP_TYPE>
  4    <GROUP_NAME>DATA ENGINEERING ASSET MANAGEMENT</GROUP_NAME>
  5    <BUS_UNIT_ACRN>SCS-FCAT</BUS_UNIT_ACRN>
  6    <PROJ_MGR_ID></PROJ_MGR_ID>
  7    <PROJ_MGR_NAME>Roland Roy</PROJ_MGR_NAME>
  8   </REQUEST_DETAIL>' );

1 row created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select
e.x.extract('/REQUEST_DETAIL/GROUP_TYPE/text()').getStringVal()
  2    from t e
  3  /
select e.x.extract('/REQUEST_DETAIL/GROUP_TYPE/text()').getStringVal()
           *
ERROR at line 1:
ORA-22806: not an object or REF

ops$tkyte@ORA920LAP> select
xmltype(e.x).extract('/REQUEST_DETAIL/GROUP_TYPE/text()').getStringVal()
  2    from t e
  3  /

XMLTYPE(E.X).EXTRACT('/REQUEST_DETAIL/GROUP_TYPE/TEXT()').GETSTRINGVAL()
------------------------------------------------------------------------
PR

Review &
      Followup

      Rating: 5
Thanks very
      much   April 16, 2003
Reviewer:  Sangita
      Saxena  from Boston, MA USA
Thanks Tom - once again for a great solution .

      Rating:
      5
Thanks  April 16, 2003
Reviewer:
      sangita saxena  from Boston, MA USA
Hi tom
Thanks for the solution which i have already implemented .
But your statement "long term, use the right type for the data" . Isn't clob
used for xml storage  ? I thought clob and xmltype either could be used ?

          Followup:

You can store a number in a varchar2.

but it would be wrong.  You spend lots of time converting strings into numbers
over and over -- and the data isn't validated.

You can store XML in a clob.

but it would be wrong.  You spend lots of time converting strings into XML
documents over and over -- and the data isn't validated

Just substitute NUMBER for XML and VARCHAR for CLOB
Or
DATE for XML and NUMBER for CLOB
Or
<any specific type> for XML and <any more generic type> for CLOB

same argument.  use the type that most closely models your data.  XML ->
xmltype.  Unknown text -> clob.

      Rating: 2
getting an
      error  April 16, 2003
Reviewer:  Anuja Batra
       from Chicago, IL USA
Tom
I tried the example and am getting an error
SQL > select
  xmltype(e.x).extract('/REQUEST_DETAIL/GROUP_TYPE/text()').getStringVal()
 from t e;
xmltype(e.x).extract('/REQUEST_DETAIL/GROUP_TYPE/text()').getStringVal()
*
ERROR at line 2:
ORA-00904: invalid column name

I also tried creating a table using xmltype and got an error
sql> create table yy(x xmltype);
create table yy(x xmltype)
                  *
ERROR at line 1:
ORA-00902: invalid datatype

However i could create the table using sys.xmltype
12:34:02 a246156@rmu2.world> create table yy(x sys.xmltype);

Table created.

So i replaced xmltype in your query with sys.xmltype and it still doesnt work .

sql > select
sys.xmltype(e.x).extract('/REQUEST_DETAIL/GROUP_TYPE/text()').getStringVal()
  from t e ;
  sys.xmltype(e.x).extract('/REQUEST_DETAIL/GROUP_TYPE/text()').getStringVal()
  *
ERROR at line 2:
ORA-22876: this user-defined type is not allowed or it cannot be used in this
context

Please advise
Thank You
Anuja

          Followup:

You're getting the error when you don't specify SYS.xmltype because you're on
Oracle9i Release 1.  In Release 2, you no longer have to specify SYS.  For your
query, try removing the (e.x) from the query... Not sure how/why that's there...

select sys.xmltype.extract('/REQUEST_DETAIL/GROUP_TYPE/text()').getStringVal()
  from t e;

Hope that helps!

      Rating: 4
How to insert
      into XMLType then  April 16, 2003
Reviewer:
      Nilendu  from Mountain View, CA
Tom,

I guess XMLType is a new 9i Rel. 2 feature.

We have been using CLOB for holding XML data too. We use DBMS_XMLSave.INSERTXML
which inserts XML in the form of CLOB into the table.

But if we have XMLType column, how we insert the XML there?

I went to see spec for DBMS_XMLSave - but could not find INSERTXML has a XMLType
argument.

Thanks,
Nilendu

      Rating: 5
9.0.1
      feature  April 16, 2003
Reviewer:  joe
       from PA
from the 9.2 docs

Oracle9i Release 1 (9.0.1) introduced a new datatype, XMLType, to facilitate
native handling of XML data in the database.

http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96620/xdb04cre.htm#1656
Note:
In Oracle9i Release 1 (9.0.1), XMLType was only supported in the server in SQL,
PL/SQL, and Java. In Oracle9i Release 2 (9.2), XMLType is also supported on the
client side through SQL, Java, and protocols such as FTP and HTTP/WebDav.

      Rating: 3
Is XML a good
      idea to begin with?  April 16, 2003
Reviewer:
      Mikito Harakiri
How about

table REQUEST_DETAIL (
   GROUP_TYPE  integer,
   GROUP_NAME  varchar2,
   ...
)

?

XML has been promoted by document content folks who have no clue about data
management. It's reincarnated EDI.

          Followup:

XML is an excellent idea -- but like anything when taken to illogical extremes,
can be made to look silly.

I've seen systems where people don't use subroutines (ok, in 21st century speak
"methods"), they generate XML -- and soap it to eachother.

Why do something fast and efficient when you can do it "cool"...

Here I have to assume there is some logical reason they are storing XML in the
database instead of using XMLSave to parse and load the XML as structured data.

      Rating:
      2
  July 28, 2003
Reviewer:  Sonika
       from India
Hi Tom,
understood that EXTRACT is used for searching in XML but
as in the above example, EXTRACT requires the full XPATH. I was trying to use
wildcards and attributes in the XPATH.
I changed the XML to have a flag attribute.
<REQUEST_DETAIL>
   <GROUP_TYPE flag="1">PR</GROUP_TYPE>
   <GROUP_NAME flag="0">DATA ENGINEERING ASSET MANAG</GROUP_NAME>
   <BUS_UNIT_ACRN flag="1">SCS-FCAT</BUS_UNIT_ACRN>
</REQUEST_DETAIL>
when I write the SQL : select
e.request_detail('/REQUEST_DETAIL/*[@flag="1"]/text()').getStringVal()
from t_request e
This is the output which I got:
PRSCS-FCAT
The output gets clubbed as a single column. Is there any way that the output
comes out as two different columns?
Thanks
Sonika

          Followup:

getstringval by its very definition returns "a thing", not "some number of
things"

think about it -- each and every row could/would return a different number of
columns -- that won't work.

You would have to call getstringval for each attribute you wanted as a column

      Rating:
      5
Thanks  July 29, 2003
Reviewer:
      Sonika  from India
Thanks Tom

      Rating: 3
..and
      8.1.7  November 18, 2003
Reviewer:  Steve
      Kiteley  from UK
What is the best way to emulate this functionality (extracting a particular XML
tag value from a clob) using Oracle 8.1.7?

          Followup:

I asked Sean Dillon, our local XML technologist, to take a look at this here's
what he had to say:
--

Hi Steve,

In Oracle8.1.7, your only option is the XML Developer's Kits on OTN (
http://otn.oracle.com/tech/xml/
.  In
the XDKs, we have PL/SQL, C, Java and JavaBeans support for DOM & SAX parsers.
That would be the best way to do what you're trying to do.  The XDKs are
backward compatible to 8.1.6, so you should have no problems downloading the
latest kits into your 8.1.7 database and using the parsers.

Hope that helps.

December 09, 2003 razvan -- Thanks for the question regarding "in
      and out variables - whats the difference", version 7.3.2

      You Asked
hello tom,

can you explain me what is the diference between variables in
and out in pl/sql?

thanks a lot
razvan

      and we said...
An IN parameter can be read but not written to in plsql.  If I
attempt to modify an IN parameter -- it will fail at compile
time.  For example:

ops$tkyte@8i> create or replace procedure p( x in number )
  2  as
  3  begin
  4          dbms_output.put_line( 'x = ' || x );
  5          x := 55;
  6  end;
  7  /

Warning: Procedure created with compilation errors.
ops$tkyte@8i> show err
Errors for PROCEDURE P:

LINE/COL ERROR
-------- -----------------------------------------------------
5/2      PLS-00363: expression 'X' cannot be used as an
         assignment target
5/2      PL/SQL: Statement ignored

An IN OUT parameter can not only be READ but it can be WRITTEN
to as well.  It retains whatever value it had before coming into
the subroutine.  Consider:

ops$tkyte@8i> create or replace procedure p( x in OUT number )
  2  as
  3  begin
  4          dbms_output.put_line( 'x = ' || x );
  5          x := 55;
  6  end;
  7  /

Procedure created.

ops$tkyte@8i> declare
  2          y       number default 10;
  3  begin
  4          p(y);
  5          dbms_output.put_line( 'y = ' || y );
  6  end;
  7  /
x = 10
y = 55

PL/SQL procedure successfully completed.

So, the value 10 was passed in and the value 55 was written to
it on the way out.

An OUT parameter can be read and written however an OUT only
parameter is always assigned NULL on the way into the routine.
Consider:

ops$tkyte@8i> create or replace procedure p( x OUT number )
  2  as
  3  begin
  4          dbms_output.put_line( 'x = ' || x );
  5          x := 55;
  6  end;
  7  /

Procedure created.

ops$tkyte@8i> declare
  2          y       number default 10;
  3  begin
  4          p(y);
  5          dbms_output.put_line( 'y = ' || y );
  6  end;
  7  /
x =
y = 55

PL/SQL procedure successfully completed.
on the way in, NULL -- not 10 -- was passed.  OUT parameters
always default to NULL.

Here is another example showing that an OUT parameter is
*always* modified -- even if we don't directly modify it in the
routine:

ops$tkyte@8i> create or replace procedure p2( x OUT number )
  2  as
  3  begin
  4          dbms_output.put_line( 'x = ' || x );
  5          -- x := 55;  we do not assign 55
  6  end;
  7  /

Procedure created.

ops$tkyte@8i> declare
  2          y       number default 10;
  3  begin
  4          p2(y);
  5          dbms_output.put_line( 'y = ' || y );
  6  end;
  7  /
x =
y =

PL/SQL procedure successfully completed.

Notice how y is set to NULL, even though we made no
assignments to it

It is interesting to note that the ability to READ an OUT
parameter is new with 7.3.  In prior releases you would have
gotten the error:

LINE/COL ERROR
-------- ----------------------------------------------------
4/2      PL/SQL: Statement ignored
4/34     PLS-00365: 'X' is an OUT parameter and cannot be read

Review &
      Followup

      Rating: 5
examples using
      copy and nocopy  August 19, 2001
Reviewer:
      Debby  from USA
Please give more examples using copy and nocopy.
and also the example for inout.

Thx

          Followup:

Good point, COPY and NOCOPY (new with 8i) modify this behaviour somewhat.

Normally paramters are copied to the OUT values AFTER the succesful execution of
a procedure, so for example when we compare the behaviour of a COPY and NOCOPY
routine:

ops$tkyte@ORA8I.WORLD> create or replace procedure p1( x OUT number, y IN OUT
number )
  2  as
  3  begin
  4          x := 55;
  5          y := 55;
  6          raise program_error;
  7  end;
  8  /

Procedure created.

ops$tkyte@ORA8I.WORLD> create or replace procedure p2( x OUT nocopy number, y IN
OUT nocopy number )
  2  as
  3  begin
  4          x := 55;
  5          y := 55;
  6          raise program_error;
  7  end;
  8  /

Procedure created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> declare
  2          l_x number default 0;
  3          l_y number default 0;
  4  begin
  5          p1( l_x, l_y );
  6  exception
  7          when others then
  8                  dbms_output.put_line( 'x = ' || l_x );
  9                  dbms_output.put_line( 'y = ' || l_y );
 10  end;
 11  /
x = 0
y = 0

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> declare
  2          l_x number default 0;
  3          l_y number default 0;
  4  begin
  5          p2( l_x, l_y );
  6  exception
  7          when others then
  8                  dbms_output.put_line( 'x = ' || l_x );
  9                  dbms_output.put_line( 'y = ' || l_y );
 10  end;
 11  /
x = 55
y = 55

PL/SQL procedure successfully completed.

we see that x and y's values are different.  In the COPY routine -- p1 -- the
values are COPIED to the out parameters upon successful completion.  In the
nocopy routine, PLSQL is in effect sending a pointer to X and Y -- as soon as we
modify them in the subroutine, their values are changed in the calling routine.

So, that begs the question, why the heck would you want to do this?  The side
effect seems to be not nice, whats the benefit?  Performance:

ops$tkyte@ORA8I.WORLD> create or replace procedure p3( x OUT dbms_sql.varchar2s
)
  2  as
  3  begin
  4          for i in 1 .. 20000 loop
  5                  x(i) := rpad( '*', 255, '*' );
  6          end loop;
  7  end;
  8  /

Procedure created.

ops$tkyte@ORA8I.WORLD> create or replace procedure p4( x OUT NOCOPY
dbms_sql.varchar2s )
  2  as
  3  begin
  4          for i in 1 .. 20000 loop
  5                  x(i) := rpad( '*', 255, '*' );
  6          end loop;
  7  end;
  8  /

Procedure created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> set timing on
ops$tkyte@ORA8I.WORLD> declare
  2          l_x dbms_sql.varchar2s;
  3  begin
  4          p3(l_x);
  5  end;
  6  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.47
ops$tkyte@ORA8I.WORLD> declare
  2          l_x dbms_sql.varchar2s;
  3  begin
  4          p4(l_x);
  5  end;
  6  /

PL/SQL procedure successfully completed.
Elapsed: 00:00:00.35

as you can see, avoiding the copy of that much data can shave some runtime off
our execution. You should consider NOCOPY for all large variables (tables of
anything) if it doesn't hurt your logic to do so.

      Rating: 5
in what
      scenarios we need to use out and inout  April 23, 2002

Reviewer:  Schesser
Tom, can you generalize as to when specifically we have to use out and inout
parameter modes.

1.Actually instead of using an out parameter I can use a function.Isnt it? So
what is the utility of out mode.

2.When do we have to or make use of inout mode. Are there any specific
situations where we need to use this mode.

And lastly, and most interestingly, we use functions from within our sql as it
returns a value. Now using out and inout parameter modes we can also return
values from procedures, but why cant we use them in sql statements.

( thinking this way there is no difference between functions and procedures
except that that the former can be used in sql , where as latter cannot be used)

Can you explain.

Thank you

          Followup:

1) some people just like to use procedures.  a function can return a single
value, a procedure -- many.  You can have more then one OUT in a procedure.

2) there are never any cases where you HAVE to.  You would use it when you
   a) want to supply a value that
   b) the procedure can modify and
   c) you will recieve the modifications

3) because there is no way for you to declare a variable in a SQL query and in
order to call an IN OUT -- you need a local variable (to supply a value to be
read from and to supply a place to put the out value into)

      Rating: 4
NOCOPY and
      collection  April 24, 2002
Reviewer:  kiro
       from Bulgaria
Hi Tom
NOCOPY is very useful for performance when we are working with collection. What
do you mean?

          Followup:

re-read the comments/followups.  Look for "performance".  NOCOPY causes the
collection (big data) to be passed by REFERENCE (by a pointer) not by COPYING
the data to a temp variable, working on the temp variable, and copying back out
again.

      Rating: 5
Legal to select
      into an OUT parameter??  April 24, 2002
Reviewer:
      Pascal  from HH,Germany
Hi Tom

Thanks very much for a Great Discussion  on this...but all i know is that NOCOPY
is great when there's an IN OUT/OUT  pl/sql Table type  parameter...

I have one doubt though, although i know it's working , but can we safely select
into / (Bulk select) INTO  an OUT/ IN OUT parameters

I was thinking may be only the Assignment is allowed when working with OUT/ IN
OUT parameters , but couldn't find Documents to prove this..

Thanks

          Followup:

NOCOPY is only useful with IN/OUT and OUT parameters actually.  (not available
with IN)

IN parameters -- already passed by reference.

You can only assign to OUT or IN/OUT parameters.  Yes you can "safely" select
into them.

      Rating: 5
You didnt get
      my poing  April 24, 2002
Reviewer:  Schesser

The below as you said

"2) there are never any cases where you HAVE to.  You would use it when you
   a) want to supply a value that
   b) the procedure can modify and
   c) you will recieve the modifications
"

Can be done even using a function( except that only one value will be returned
using a function, and many using a procedure). Why would I use a procedure , I
will use a function.

Further, even when I want to return multiple values out from a function, I CAN
USE A REF CURSOR variable, and return multiple values out  isnt it.

Kindly clarify.

          Followup:

As i always say -- there are an INIFINITE number of ways to accomplish most
anything.

Yes, you can use a function (however, the function would have to allocate a
temporary variable, copy the input parameter so it can change it and then return
the temporary.  Might be more efficient to use a procedure with nocopy)

Why would you use a procedure?  Because you want to -- it is your choice.

A ref cursor is to return a result set.  It would not be an appropriate
technique for returning formal parameters.

      Rating:
      5
  April 24, 2002
Reviewer:  Tianhua
      Wu  from Atlanta, GA
Everyday I can learn something new here.  I use in and out for quite q while,
but I never understand them btter than now, Thankk you!

      Rating: 5
Constant/Static
      variables  April 24, 2002
Reviewer:
      A reader
Hi, Tom,

Could you clarify and give us an example on
constant / static variables like in C/C++ please?

Many thanks,

          Followup:

easy

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package demo_pkg
  2  as
  3          x CONSTANT number := 5;
  4          y number;
  5  end;
  6  /

Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package body demo_pkg
  2  as
  3          a CONSTANT number := 5;
  4          b number;
  5  end;
  6  /

Package body created.

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

X is like "#define X 5" in a .h file
Y is like

int y;

defined outside of a function (global variable, avaiable in all code).

A is like #define A 5 in the .c file
B is like

static int b;

      Rating: 5
OUT
      parameters  August 31, 2002
Reviewer:  Mo
       from Reston, USA
Tom:

1.  I do understand that IN parameters are a must for a procedure that you want
to pass values into.  However, Why do I need to define OUT parameters to write
to.  Can I just declare variables in my PL/SQL program, and write values to
those variables and then output the values using dbms_ouput.put_line.

Is there a benefit of using an OUT paraemter.

2.  Also, in a web application you pass the values that you want to submit by
incorporating it in the <form></form> tags. How do you do this in a
client/server program.
My thinking is that when you want output of procedure A read into procedure B,
you make a call to A within B procedure code.  I think in this case you MUST use
OUT parameters in A otherwise if you use variables they can not be accessed in B
code. AM I correct?

Thank you,

          Followup:

1) umm, say your client is a VB program and needs to get the results.
dbms_output doesn't do anything in VB (and in fact, dbms_output just contains a
procedure get_line(s) that has an OUT parameter that allows the client to get
the output and display it - that is what sqlplus does)

OUT parameters are necessary for the language PLSQL to be a real programming
language.  Just like every other language in the world (Java, C, C++, etc)

2) Yes, you are accurate in your depiction -- although you could use globals
instead of formal parameters hence the word "must" is a little strong.

      Rating: 4
nocopy - a hint
      ?  September 09, 2002
Reviewer:  ram
       from India
but i read in a book that nocopy is a 'hint' rather than a 'directive' and so
the compiler is not obliged to make use of it everytime. the book also mentioned
that if the compiler chooses to ignore the hint we will NOT get an error. in
your example the values of x and y were different for a procedure with/without
nocopy. however if we are not really sure if nocopy is being used then we cant
really say that the value of x will be some_value, can we ? or, is it that if
the compiler uses it once we can be sure it will be used in all the future calls
?
pls clarify.

          Followup:

The only thing you can say is:

if you use NOCOPY, beware that the behaviour (eg: value) of your in out or out
parameters may be different then when NOT using NOCOPY.

      Rating: 3
A Way to get
      Procedure Parameters ?  October 23, 2002
Reviewer:
      Christophe Parmentier  from Sophia-Antipolis, France

Is there a Way to get procedure parameters from another procedure ?

E.G.
Calling a Trace Procedure that gets the :
1) The Calling Procedure Name?
2) Its parameters Names?
to UTL_FILE them...

Thanks !

          Followup:

no, there is not.

search for DBMS_TRACE  on this site or

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76936/dbms_tra.htm#1000263
to see the sort of debug stuff you can capture.

      Rating: 4
OUT and
      NOCOPY  April 24, 2003
Reviewer:  J. Smits
       from Springfield, MA
If an OUT parameter is not assigned to any value during a procedure, its value
goes out as NULL (or an empty table) when the procedure completes.

However, what is the behavior for an OUT NOCOPY parameter that is not assigned
during the procedure?  Will it retain the value it had before the procedure
call, or will it be nulled out?

In addition, although I don't expect you to explain how to predict whether the
compiler will actually choose to implement NOCOPY, is there any way we can know
after compilation that the compiler chose it? Knowing this would help to
quantify the performance improvement that NOCOPY has on a particular process.

          Followup:

the semantics of an OUT parameter is

a) set it to null
b) call procedure
c) upon success, copy the value back out

NOCOPY only changes the behaviour of C

ops$tkyte@ORA920> create or replace procedure nocopy_demo( p1 out NOCOPY number,
p2 out NOCOPY number )
  2  as
  3  begin
  4          p1 := 55;
  5          raise program_error;
  6  end;
  7  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          l1 number := 1;
  3          l2 number := 2;
  4  begin
  5          nocopy_demo( l1, l2 );
  6  exception
  7          when others then
  8                  dbms_output.put_line( 'l1 = ' || l1 );
  9                  dbms_output.put_line( 'l2 = ' || l2 );
 10  end;
 11  /
l1 = 55
l2 =

PL/SQL procedure successfully completed.

The variable is nulled out.....

The only way to quantify the performance increase/decrease in any case (even if
you knew that it did the nocopy) would be to benchmark the procedure before and
after.

      Rating: 5
Thanks for the
      clarification  April 24, 2003
Reviewer:  J.
      Smits
Yes, when doing performance testing we always do a before and after to see if a
particular change made a difference.

However, when a test focusing on NOCOPY does not show a significant difference
in performance, we don't know if it was because NOCOPY itself did not have much
of an effect, or because the compiler chose not to implement NOCOPY for the
particular code base.

      Rating: 5
out
      parameter  April 24, 2003
Reviewer:
      A reader
Hi otm,

  I have procedure

create or replace p1(x out char,
                     y out varchar)
as
begin

  select 'xyz'||myseq.nextval
   into  x
   from dual;
   y := success;
end;

create or replace p2(x in char,
                     y out varchar)
as
begin

  insert into t values(x);
     y := success;

end;

create or replace proceudre p(
             p_1 out char,
             p_2 in varchar,
             p_3 out varchar,
             p_4 out char)
is

begin

/**** calling another procedure p1 that will
    generate a code(using seq.nexval) and return in
     p_1 parameter */

  p1(p_1,p_3);

/******* also use this generated code in the
   another proceudres...

/**---->***/  dbms_output.put_lin(p_1);

  p2(p_1,p_4);

end;

/*******************************************************/

 this procedure works fine if I use sqlplus
but, if i use java/jsp --(app - tomcat )

 it complains about can not bind LONG value to parameter and
p_1 parameter give problems it is some how get LONG ?? values, if called from
java
 but if I do that from sqlplus it is fine
they (java developer) uses registerparameter ??

can you please comment on that ?

          Followup:

without actually seeing the smallest possible test case that reproduces the
error, I cannot really say what the java programmer is doing wrong in their
code.

      Rating: 5
Call to
      procedure having more than 1 OUT variables...  April 25,
      2003
Reviewer:  Riaz Shahid  from PRAL, Islamabad,
      Pakistan
Hello Tom!

How can we call a procedure having more than 1 OUT variables ?

create or replace procedure myproc (a OUt number, b OUT number)
as
begin
a:=10;
b:=15;
end;

Now how we will make call to this procedure ? Like

declare
a number;
b number;
begin
a,b):=myproc;
end;

But this is not valid. So how we'll do that?

Riaz

          Followup:

you need to read the PLSQL programmers guide -- this is like "basic programming
101" stuff.

Just like you would in ANY 3gl language

begin
   myproc( a, b );
end;
/

      Rating:
      5
C  April 25, 2003
Reviewer:
      A reader
In sum, nocopy works like passing parameters by reference, like in C. Shouldn't
there be an "IN OUT NOCOPY" -- which is how it works in C ?

      Rating:
      5
log  April 25, 2003
Reviewer:
      A reader
hi tom,

  here is the log from tomcat40 and debug stmts.

Apr 22 15:47:47 [DEBUG]: Executing :{call p_mypkg.p
(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) } -
com.com.oracle.dataaccess.p.processInsertUpdate(Unknown Source)

Apr 22 15:47:48 [INFO]: p1 Message Code: DB-SUCCESS -
com.oracle.dataaccess.p.processInsertUpdate(Unknown Source)
Apr 22 15:47:48 [ERROR]: p1 SQL Error Code: null -
com.oracle.dataaccess.p.processInsertUpdate(Unknown Source)

/************************/

step 1: p calls p1 and get success and
           generates a code in out parameter p_1

setp 2 : p calls p2 using new value in p_1 and passes in to
           p2 as **IN** parametner
/*************************/

Apr 22 15:47:48 [INFO]: p2 Message Code: DB-GEN_FAIL_SYSTEM -
com.oracle.dataaccess.p(Unknown Source)
Apr 22 15:47:48 [ERROR]: p2 SQL Error Code: ORA-01461: can bind a LONG value
only for insert into a LONG column -
com.oracle.dataaccess.p.processInsertUpdate(Unknown Source)

/*************************************/

setp 3: p2 gets fali system
setp 4: action as a whole fails...
/*************************************/

          Followup:

that is not java code...
test case -- small, concise, yet complete...

      Rating: 5
why nt default
      nocopy  June 02, 2003
Reviewer:  Tanmoy Datta
      Choudhury  from Bangalore,India
tom ,
as u said that nocopy and copy is for performance that why oracle is not using
that as a default..

          Followup:

because

a) it was not that way in the beginning (so making it the default would affect
tons of existing code)

b) it has undesirable side effects if applied arbitrarily (it would not be good
to be the default)

so, to be backwards compatible AND to promote proper behavior of variables -- it
is something you have to explicitly (and hopefull knowledgably) choose to use.

      Rating: 5
thanks
      tom  June 04, 2003
Reviewer:  Tanmoy Datta
      Choudhury  from Bangalore,India
thanks for your reply ...i understood ..

      Rating: 5
IN OUT in
      Functions?  September 09, 2003
Reviewer:  A
      reader  from Canada
Hi Tom!

I was wondering, can we also use IN OUT parameters in a function?  I know we can
use several IN OUT parameters in a procedure, but since a function already
returns one value by default, is it ok to add another value it can return by
using the IN OUT parameters?

Thanks and more power!

          Followup:

yes, you can.

      Rating: 5
C External
      Routines  September 09, 2003
Reviewer:
      A reader
Hi Tom,
Can the NOCOPY be used for returing values from C External Routines? Do we have
the same performance benifit?
Thanks

      Rating: 5
Passing Ref
      Cursor Result Set from one stored program to
      another  December 08, 2003
Reviewer:  Justin
       from PA
Tom,

We are converting from SQL Server to Oracle.

In SQL Server, we have a procedure that calls a function.

Here is the SQL Server function:

ALTER  FUNCTION dbo.fn_CEPrescriptionGetFamilyT071Ids (@intFamilyId int)
RETURNS @tblChild TABLE (t071_id int)
AS
BEGIN

INSERT @tblChild
SELECT DISTINCT t071_id
FROM t071_prescription t071 WITH (NOLOCK)
  INNER JOIN t067_participant t067 WITH (NOLOCK)
  INNER JOIN t033_fam_members t033 WITH (NOLOCK)
  INNER JOIN t014_benefit_fam t014 WITH (NOLOCK)
    ON t033.t014_id = t014.t014_id
    ON t067.t069_id = t033.t069_id
    ON t071.t067_id = t067.t067_id
WHERE family_id = @intFamilyId
  AND fam_mbrship_typ_cd = 'P'

RETURN

END

Here is the SQL Server procedure that is calling the function:

ALTER  PROCEDURE dbo.sp_CEPrescriptionGetPrescriptionAndContents_Child
@intFamilyId    int,
@intT071Id    int
AS

SET NOCOUNT ON

DECLARE @T        table ( t071_id    int )

IF @intFamilyId > 0
BEGIN
    INSERT @T
    SELECT t071_id FROM dbo.fn_CEPrescriptionGetFamilyT071Ids(@intFamilyId)
END
ELSE
BEGIN
    INSERT @T ( t071_id )
    VALUES ( @intT071Id)
END

SELECT  T040_ID
       ,ABS(T040_ID) AS ID40
       ,t040.T071_ID
       ,FI_SEQ_NR
       ,T040_STATUS_CD
       ,t040.T093_ID
       ,NEWID() AS Id
       , '' AS WEB_STATUS
FROM t040_FI_Spec t040 WITH (NOLOCK)
WHERE t040.t071_id IN (SELECT t071_id FROM @T)
ORDER BY t040.FI_SEQ_NR

GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

I didn't write the SQL Server stuff, but I am trying to duplicate it in Oracle
utilizing Ref Cursors.

The application is a VB app and we have gotten this to work utilizing "sql
types" (myscalartype & myscalartabletype), but can't seem to get Ref Cursors to
work. We keep getting inconsistent datatypes errors. (Which I can understand
why, but don't know how to remedy.)

Here is our Oracle translation attempt at ref cursors which fails from VB with
the ORA-00932.

*Oracle Function:*

CREATE OR REPLACE FUNCTION FN_CEPRESCRIPTIONGETFAMILYT071(intFamilyId     IN
NUMBER  DEFAULT NULL)

RETURN types.RC_NUM

AS
l_rc          types.RC_NUM;
intFamilyId_  NUMBER(10,0) := intFamilyId;

BEGIN

OPEN l_rc FOR
SELECT DISTINCT  t071_id
FROM wic_owner8.t071_prescription t071
     ,wic_owner8.t067_participant t067
     ,wic_owner8.t033_fam_members t033
     ,wic_owner8.t014_benefit_fam t014
WHERE (t033.t014_id = t014.t014_id)
  AND (t067.t069_id = t033.t069_id)
  AND (t071.t067_id = t067.t067_id)
  AND (family_id = FN_CEPRESCRIPTIONGETFAMILYT071.intFamilyId_ AND
fam_mbrship_typ_cd = 'P');

RETURN l_rc;

END FN_CEPRESCRIPTIONGETFAMILYT071;
/

CREATE OR REPLACE PROCEDURE SP_CEPRESCRIPTIONGETPRESCRIPTI ( intFamilyId int,
intT071Id int DEFAULT NULL, l_rc OUT sys_refcursor )
AS

BEGIN

IF intFamilyId > 0 THEN
OPEN l_rc FOR
SELECT T040_ID
       ,ABS(T040_ID) AS ID40
       ,t040.T071_ID
       ,FI_SEQ_NR
       ,T040_STATUS_CD
       ,t040.T093_ID
       ,NEWID() AS Id
       ,'' AS WEB_STATUS
FROM t040_FI_Spec t040
WHERE t040.t071_id IN (select FN_CEPRESCRIPTIONGETFAMILYT071(intFamilyId) from
dual )
ORDER BY t040.FI_SEQ_NR;

ELSE

OPEN l_rc FOR
SELECT T040_ID
      ,ABS(T040_ID) AS ID40
      ,t040.T071_ID
      ,FI_SEQ_NR
      ,T040_STATUS_CD
      ,t040.T093_ID
      ,NEWID() AS Id
      ,'' AS WEB_STATUS
FROM t040_FI_Spec t040
WHERE t040.t071_id IN (intT071Id)
ORDER BY t040.FI_SEQ_NR;
END IF;
END;
/

I have copied both the function code and the procedure code directly from this
window into sqlplus, and both compile fine, but I know the procedure is *wrong*.

This:
select FN_CEPRESCRIPTIONGETFAMILYT071(1001) from dual;

Results in this:

FN_CEPRESCRIPTIONGET
====================
CURSOR STATEMENT : 1

CURSOR STATEMENT : 1

          T071_ID
=================
             1001
             1002
           486562
         11189949

4 rows selected.

1 row selected.

This:

exec SP_CEPRESCRIPTIONGETPRESCRIPTI (1001,NULL);

Results in this:

PLS-00306: wrong number or types of arguments in call to
'SP_CEPRESCRIPTIONGETPRESCRIPTI'

So if you could go through - step by step - passing the result set of a stored
program in the form of a Ref Cursor, from one stored subprogram to another.

I am working on a more simplistic test case right now...

Thanks much for looking at this!

          Followup:

looks like it worked to me - given the select f(x) from dual worked.

You just want to (in sqlplus) declare a cursor variable to return the value into
and then print it:

variable x refcursor
exec sp_.....( 1001, null, :x )
print x

thats all -- you, you just needed to "get" the last parameter and print it.

      Rating: 5
follow up
      goof  December 08, 2003
Reviewer:  Justin
       from PA
By the way, in case you want to reference page numbers,
I have all 3 of your books:

Beginning Oracle Programming
Effective Oracle by Design
Expert One on One...

I am looking at pg 572-575 in EOBD.

Goof:

The Oracle procedure listing in my previous post should have been:

CREATE OR REPLACE PROCEDURE SP_CEPRESCRIPTIONGETPRESCRIPTI ( intFamilyId int,
intT071Id int DEFAULT NULL ) --, l_rc OUT sys_refcursor )

Without the refcursor parameter declaration.

I know we need to have a ref cursor in the parameter list to pump the final
result set out to VB, but when I have the ref cursor commented out, then I get
the:

exec SP_CEPRESCRIPTIONGETPRESCRIPTI(1001);

ORA-00932: inconsistent datatypes: expected - got CURSER

I was talking about.

PS. Why the "got "CURSER""?

Is CURSOR simply misspelled CURSER??

      Rating: 5
I have found a
      link on your site which may help me  December 08, 2003

Reviewer:  Justin  from PA
Just wanted to let you know, I found (interestingly enough) by typing in simply
"ref cursor" the very first note that comes up is
"Returning a refcursor and use it in IN clause"
which made me sick, as I've tried so many ways to find it, and it was staring me
in the face...

I'll try it, and see if I can figure it out with a YAF.

Sorry for the thread continuations...

December 09, 2003 Donald -- Thanks for the question regarding
      "Script for to Backup Oracle Database", version 7.3.3

      You Asked
Do you have any script to backup an Oracle Database ONLINE where
you dont have to shut down the services and that it doesn't need
to export the whole database but only those records that are
modified at that time so as to spend little time in exporting?

Thank you very much and more power !!!

      and we said...
With Oracle7.3 -- no, there are no incemental backups.  With
Oracle8.0 and up there is a tool RMAN (recovery manager) that
can do incremental backups of an Oracle database (only backup
blocks modified since the last incremental or full backup for
example).

With 7.3, you can certainly backup the database ONLINE using hot
backups if the database is in archive log mode.  You would place
a tablespace into backup mode (alter tablespace T begin backup),
copy all of the datafiles associciated with that tablespace
elsewhere and then bring the tablespace out of backup mode via
an alter statement again.  Hot backups are easy to do -- I would
recommend the Oracle8 Backup & Recovery Handbook -- Rama
Velpuri, Anand Adkoli; for help in this area.

Hopefully, you are using the term "export" loosely.  EXP is not
a backup tool -- it is a point in time copy of the data.  It is
not good for recovering from media failures and other occurences
where you need a point in time recovery.
Review &
      Followup

      Rating: 5
Hot Backup
      Script  July 17, 2002
Reviewer:  atul
       from India
Hello sir,

I have written a script for daily hot backup..

Could you kindly help me in that

Script goes like this..

---------------------------------------------------------------------------------
--------------

for i in `cat /tmp/sc1.lis`
do
echo "set heading off;\nset pages 0;\nALTER TABLESPACE '$i' BEGIN BACKUP
;\nexit"|sqlplus -s /
echo "set heading off;\nset pages 0;\nselect file_name from dba_data_files where
tablespace_name ='$i' ;\nexit"|sqlplus -s / > /tmp/
w32.lis
for l in `cat /tmp/w32.lis`
do
cp $l /backup/.
done
echo "set heading off;\nset pages 0;\nALTER TABLESPACE '$i' END BACKUP
;\nexit"|sqlplus -s /
done
---------------------------------------------------------------------------------
------------------------------

Where
vi /tmp/sc1.lis (Name of the tablespaces to be backed up)

TABLESPACE1
TABLESPACE2
TABLESPACE3

Actually we don't have development server so i can't test it..
so plz tell me will this script work??
or what changes should i do in it..

Thanks.
atul

          Followup:

No, not really (not in the job of writing shell scripts)

Well, maybe I can help you out.  You would be better served using RMAN.  No OS
dependent scripts -- it can be as easy as "backup database" basically.  Suggest
you look at RMAN.

      Rating: 3
incremental
      backups using rman  December 08, 2003
Reviewer:
      Duncan  from UK
I would like to take incremental backups either cumulative or non.  We have HP
ultrium tapes but no license to use legato (or any other type of mms) so i am
thinking about using rman to backup to disk and then archive using o/s commands.

My questions are:

1)  How does rman decide on how to restore the database (possibly)using the
incremental strategy?  I mean in some cases it may be quicker (or more
efficient) to do a restore from eg a level 2 and then roll forward using archive
logs. In other cases it may be that it needs a level 0 and 3 level 2 backupsets
before using the archive logs. So does RMAN actually make all of these decisions
for you?

IF so then do i just need to decide on the best backup strategy for us eg

sun          level 0
mon-fri      level 2
sun          level 1
mon-fri      level 2
...
... etc

Then will rman decide on how to use these backupsets to achieve the desired
restore?

2)  If rman IS that clever and if i am manually archiving the backupsets to tape
then how do i know which bacupsets RMAN is going to need to do a restore? Do i
have to get all the available backupsets off of the tape and restore to disk
before RMAN can decide and use the most efficient strategy??

          Followup:

1) rman is not really going to analyze "it would be less work to restore the
level 0 and then roll forward then to restore the level 0 and apply the N level
and then roll forward".  it'll restore the full, apply the needed incremental
and roll forward pretty much.

2) you would need to restore anything it would want to use -- it assumes "all is
where I put it last".  You might consider getting some inexpensive ATA drives
(you can get something like a terabyte for pretty little money) and keeing
everything on magnetic storage and backing it up to tape using conventional
means (file system dumps).

      Rating:
      4
Thanks  December 09, 2003
Reviewer:
      A reader

      Rating: 3
RMAN catalog Vs
      No Catalog  December 09, 2003
Reviewer:  M.
      Senthil Kumar  from OH USA
Hi Tom
Export  can be used in addition to HOT backups or  backups using RMAN. The
export <<dmp>> can be used for retreival of  data for a particular record. (If
the user has accidently deleted the data , the dmp file will be useful in
retrivel of the records). But this is not a solution for a large database where
export takes a lot of time and disk space. In this case the logminer can be used
for the retrival of the the record. Still it depends on how many records were
deleted.
I do prefer RMAN  and find it is much faster for back-up and retrivel. It
depneds on the individual environment to choose catalog or no catalog option.

December 09, 2003 Harald -- Thanks for the question regarding
      "Using the DBMS_STATS-package", version 8.1.6

      You Asked
We are doing performance-testing of SQL-statements in our
project. When running explain-plans on the statements, the
results are often not realistic due to the fact that we have not
loaded the tables with data, and therefore the indexes, in most
cases, will not be used as they will in production.

I found the DBMS_STATS-package and am wondering if I can use
this package to virtually load my test-environment when using
the explain-plan. Our Oracle-support team doubt that it will be
possible to extract the data using
GET/EXPORT_TABLE/INDEX/COLUMN_STATS, modifying the data and then
using SET/IMPORT_TABLE/INDEX/COLUMN_STATS to put new statistics
back into the data-dictionary-tables.

Are there any descriptions on the tables the CREATE_STAT_TABLE
makes?

Regards,
Harald

      and we said...
That table structure is not documented and is not designed to be
modified by anything other then the DBMS_STATS package.  Its
sole purpose is to take stats from one database to another via
export/import in the dbms_stats package.  Attempting to plug
your own values in there would be difficult at best and
potentially harmful at worst.

Instead of trying to insert/update rows in that table -- you
should just use the other entries in the dbms_stats package.
For example, to set the statistics for a table -- you would use
the dbms_stats.set_table_stats procedure.  It can either put the
stats you want into the data dictionary directly OR you can have
this procedure stuff those stats in the table created by
CREATE_STAT_TABLE.  You use the formal named arguments in the
set_table_stats procedure and they'll take care of stuffing them
into the table itself.   You lose NO functionality by doing this
-- there would be NOTHING to be gained by accessing the table
created directly.

You would use the SET_COLUMN|INDEX|TABLE_STATS to set whatever
stats you wanted on the data.  There would be no reason to
modify existing data.  You can see all of the "existing" data by
querying the data dictionary.
Review &
      Followup

      Rating: 5
what is the
      utility of dbms_stats.set_table_stats   September 29, 2001

Reviewer:  Wells  from USA
Is it used to set statistics and test the performance i.e. the execution path.

Actually, We are interested in knowing how we can use and where we can use
dbms_stats.set_table_stats to the maximum benefit.

          Followup:

You can use it to transfer the stats from your production system (with all of
the data) to your test system.  You might do that to review the query plans
generated by new/altered queries in your application before putting them into
production -- so you can see what might be different.

I use it to show the effects of having differeing numbers of rows/blocks in
tables.  For example, in order to demonstrate a point -- I might analyze an
empty table -- run a query and show the plan.  Then, I will use dbms_stats to
trick the optimizer into thinking there are LOTS and LOTS of rows and blocks in
there and how the different plans.  You can use this to see how the optimizer
will change its mind about the proper plan over time.

Another use I've found for it is with temporary tables.  You cannot really
analyze them but what you can do is create a "real" table -- fill it up with
representative data -- and then export these stats using dbms_stats.  Later, you
create the temporary table and import these other stats you saved.  Now the
optimizer will use these stats for the temporary table when developing query
plans that involve it.  This could be important if you put lots of rows into a
temp table and then use it in subsquent SQL statments -- the optimizer would
otherwise think the temporary table is "small"

      Rating: 4
why is
      dbms_stats slower than analyze  November 01, 2001

Reviewer:  A reader
Hi

I have tested a few times and always find that dbms_stats is slower than
ordinary analyze even specifying parallel degree, why is that? we are using
8.1.6.3

Also is it possible analyze a table using analyze table xx compute statistics in
parallel?

          Followup:

Geez, no test case, no parameters, not even "we find it X% slower" -- nothing.

Perhaps the tables you used were trivial in size (parallel can take much
longer).  Perhaps you were comparing apples with oranges (eg: dbms_stats by
default does "for all columns size 1" -- did your analyze do that too?)

No, you cannot analyze in parallel, only dbms_stats can.

Also, I do not see it being significantly slower (given that dbms_stats will
just issue an analyze in this particular case you would expect that to be so!):

ops$tkyte@ORA717DEV.US.ORACLE.COM> variable n number
ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> analyze table t delete statistics;

Table analyzed.

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> exec :n := dbms_utility.get_time

PL/SQL procedure successfully completed.

ops$tkyte@ORA717DEV.US.ORACLE.COM> exec dbms_stats.gather_table_stats( user,
'T', method_opt => 'for all indexed columns', cascade => true );

PL/SQL procedure successfully completed.

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> exec dbms_output.put_line(
(dbms_utility.get_time-:n) || ' hsecs ' );
22762 hsecs

PL/SQL procedure successfully completed.

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> analyze table t delete statistics;

Table analyzed.

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> exec :n := dbms_utility.get_time

PL/SQL procedure successfully completed.

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> analyze table t
  2  compute statistics
  3  for table
  4  for all indexed columns
  5  for all indexes
  6  /

Table analyzed.

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> exec dbms_output.put_line(
(dbms_utility.get_time-:n) || ' hsecs ' );
22732 hsecs

PL/SQL procedure successfully completed.

ops$tkyte@ORA717DEV.US.ORACLE.COM> select num_Rows from user_tables where
table_name = 'T';

  NUM_ROWS
----------
   1093312

and when I do it in parallel -- I see a HUGE difference:

ops$tkyte@ORA717DEV.US.ORACLE.COM> variable n number
ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> analyze table t delete statistics;

Table analyzed.

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> exec :n := dbms_utility.get_time

PL/SQL procedure successfully completed.

ops$tkyte@ORA717DEV.US.ORACLE.COM> exec dbms_stats.gather_table_stats( user,
'T', method_opt => 'for all indexed columns size 1', cascade => true, degree=>8
);

PL/SQL procedure successfully completed.

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> exec dbms_output.put_line(
(dbms_utility.get_time-:n) || ' hsecs ' );
2525 hsecs

PL/SQL procedure successfully completed.

I think you were using tables that were so small as to be insignificant in even
timing them.

Btw, table t is:

create table t as select * from all_objects;
insert into t select * from t;
insert into t select * from t;
insert into t select * from t;
insert into t select * from t;
insert into t select * from t;
insert into t select * from t;

create index t_idx1 on t(object_id);
create index t_idx2 on t(object_name);

      Rating: 5
Partitions are
      a slightly different story...  November 02, 2001

Reviewer:  Connor  from Deepest Darkest Scotland

analyze table p1 partition (x1)
compute statistics

********************************************************************************

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.05          0          0          0           0
Execute      3     14.89      44.28      11089       1818        300           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4     14.92      44.33      11089       1818        300           0

BEGIN dbms_stats.GATHER_TABLE_STATS('MCDONAC','P1','X1'); END;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       11      0.04       0.08          0          0          0           0
Execute     18      0.01       0.03          2         23         13           9
Fetch       15     50.47     198.18      48473       7511       1723          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       44     50.52     198.29      48475       7534       1736          19

so you do need to be a little wary before converting all of your analyze scripts
to use dbms_stats.

          Followup:

Connor -- you've compared apples to toaster ovens.  That dbms_stats gathers lots
more stats then the analyze command does!

The analyze doesn't work 100% on partitions, for example -- analyze that table
and look at the LAST_ANALYZED in the USER_TAB_COLUMNS table -- it'll not have
changed (nor will the values there).  Then, do it with dbms_stats and you'll
find that it does.  they are two different commands above.

      Rating: 3
DBMS_STATS
      slower  November 02, 2001
Reviewer:
      A reader
Hi Tom

I have done a test after reading this thread on a Tru64 Machine with 1 CPU ONLY,
Oracle 8.1.7 Enterprise Edition and a table of 4500000 rows

SQL> analyze table opsc.CALLS_OPSC estimate statistics sample 20 percent for all
columns size 1;

Table analyzed.

Elapsed: 00:17:14.11

SQL> exec dbms_stats.GATHER_TABLE_STATS('OPSC', 'CALLS_OPSC', estimate_percent
=> 20, method_opt => 'for all columns size 1', cascade => true, degree => 2);

PL/SQL procedure successfully completed.

Elapsed: 00:20:05.20

Is DBMS_STATS slower in this case because we only have one CPU? Also why with

analyze table opsc.CALLS_OPSC estimate statistics sample 20 percent for all
columns size 1;

in num_rows I see NULL? Only column statistics appears in dba_tab_columns

By the way there is no index in this table

cheers

          Followup:

Your commands are totally different.

You have to compare:

analyze table opsc.CALLS_OPSC estimate statistics sample 20 percent
for table
for all columns size 1
for all indexes

(you didn't analyze the table!!  dbms_stats did, analyze did not)

and yes, there is a chance that the overhead of PQ on a single cpu system
outweighs any benefit in this particular case.  But here, the difference is most
likely due to the fact that dbms_stats did more work.

17 minutes to sample 20% of a 4.5 million row table seems long to me.  You might
want to tkprof that to see if you have tons of waits and if so, play around with
your sort area size and such -- both of those should go faster.

      Rating: 5
Your followup
      on partitions - dbms_stats vs analyze  November 03, 2001

Reviewer:  Connor McDonald  from UK
I wasn't trying to dispute that dbms_stats and analyze do something very
different.  I'm just trying to warn people about possible consequences of taking
a set of existing ANALYZE scripts and merely converting them (in what would seem
a fairly natural sense) to DBMS_STATS - sure you get better stats - but you may
get a large resource hit.

Cheers
Connor

          Followup:

ahh, OK, I would think (hope, pray, whatever) that people would test things --
read the docs and realize that these are different implementations -- collected
radically different values - did different things.

You are right though -- pointing it out doesn't hurt (but I still feel compelled
to point out "apples and oranges" just in case someone reading THOUGHT they were
the same!)

Thanks Connor -- always appreciate your input.

      Rating:
      3
missread  December 11, 2001
Reviewer:
      A reader
Hi Tom

I read the replay "dbms_stats is slower", it states

SQL> analyze table opsc.CALLS_OPSC estimate statistics sample 20 percent for all

columns size 1;

Table analyzed.

Elapsed: 00:17:14.11

SQL> exec dbms_stats.GATHER_TABLE_STATS('OPSC', 'CALLS_OPSC', estimate_percent
=> 20, method_opt => 'for all columns size 1', cascade => true, degree => 2);

PL/SQL procedure successfully completed.

Elapsed: 00:20:05.20

I am not sure, you said they are different but I dont see the difference because
method_opt => 'for all columns size 1' is specified too. Unless even you specify
for all columns Oracle does for all columns as well?

Also is there anyway to know the default values of parameters of this package?
It seems impossible and how can we know what default it is doing?

          Followup:

They are different because the analyze command does ONE thing -- gets the column
stats.  It does NOT get the table stats, it does NOT get the index stats.  The
dbms_stats call does get the table stats (gather_table_stats always does).  In
addition it gets stats FOR ALL COLUMNS (what the analyze did).  In addition, it
was specified with CASCADE=>TRUE meaning the indexes associated with the table
will be analyzed as well.

You can see the difference to confirm it via:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t as select * from all_objects;
ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx on t(object_id);

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t delete statistics;
Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t
  2  estimate statistics sample 20 percent
  3  for all columns size 1;
Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select last_analyzed from user_tables where
table_name = 'T';

LAST_ANAL
---------

ops$tkyte@ORA817DEV.US.ORACLE.COM> select last_analyzed from user_indexes where
index_name = 'T_IDX';

LAST_ANAL
---------

ops$tkyte@ORA817DEV.US.ORACLE.COM> select column_name, last_analyzed from
user_tab_columns where table_name = 'T';

COLUMN_NAME                    LAST_ANAL
------------------------------ ---------
OWNER                          11-DEC-01
OBJECT_NAME                    11-DEC-01
SUBOBJECT_NAME                 11-DEC-01
OBJECT_ID                      11-DEC-01
DATA_OBJECT_ID                 11-DEC-01
OBJECT_TYPE                    11-DEC-01
CREATED                        11-DEC-01
LAST_DDL_TIME                  11-DEC-01
TIMESTAMP                      11-DEC-01
STATUS                         11-DEC-01
TEMPORARY                      11-DEC-01
GENERATED                      11-DEC-01
SECONDARY                      11-DEC-01

13 rows selected.

Note that the columns have a last_analyzed -- the table and index DO NOT

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t delete statistics;
Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2     dbms_stats.GATHER_TABLE_STATS
  3     (user, 'T', estimate_percent => 20,
  4          method_opt => 'for all columns size 1',
  5          cascade => true, degree => 2);
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select last_analyzed from user_tables where
table_name = 'T';

LAST_ANAL
---------
11-DEC-01

ops$tkyte@ORA817DEV.US.ORACLE.COM> select last_analyzed from user_indexes where
index_name = 'T_IDX';

LAST_ANAL
---------
11-DEC-01

ops$tkyte@ORA817DEV.US.ORACLE.COM> select column_name, last_analyzed from
user_tab_columns where table_name = 'T';

COLUMN_NAME                    LAST_ANAL
------------------------------ ---------
OWNER                          11-DEC-01
OBJECT_NAME                    11-DEC-01
SUBOBJECT_NAME                 11-DEC-01
OBJECT_ID                      11-DEC-01
DATA_OBJECT_ID                 11-DEC-01
OBJECT_TYPE                    11-DEC-01
CREATED                        11-DEC-01
LAST_DDL_TIME                  11-DEC-01
TIMESTAMP                      11-DEC-01
STATUS                         11-DEC-01
TEMPORARY                      11-DEC-01
GENERATED                      11-DEC-01
SECONDARY                      11-DEC-01

13 rows selected.

and now the table, index and columns have been indexed

As for how to know the default parameters (and how this package works) one would
read the documentation on it:

http://technet.oracle.com/doc/oracle8i_816/server.816/a76936/dbms_sta.htm#999107
....

      Rating: 5
Follow on
      questions  April 18, 2002
Reviewer:  Robert
      Ware  from St. Louis Mo.
Tom

First, THANK YOU FOR ALL THE INFORMATION SHARING YOU PROVIDE.

1)  Does dbms_stats provide information on chained_rows compatible to the
analyze command?  If so what do I need to modify in my script below to implement
it?

     ---We recently replaced---

RDWARE@ORCL..SQL>BEGIN
  2    FOR s in (select distinct(owner) schema
  3              from dba_tables
  4              where owner not like 'SYS%')
  5    LOOP
  6        dbms_output.put_line('Analyzing the entire '||s.schema||' schema');
  7        dbms_utility.analyze_schema(s.schema,'ESTIMATE',null,20,'for table
for all indexed columns size 254');
  8    End Loop;
  9  END;
 10  /
Analyzing the entire LJCAVANO schema
Analyzing the entire OUTLN schema
Analyzing the entire PD6_REPO schema
Analyzing the entire PD8_REPO schema
Analyzing the entire RDWARE schema
Analyzing the entire RPCOCA schema
Analyzing the entire RSPAYNE schema
Analyzing the entire SCOTT schema
Analyzing the entire SDCORP_REPO schema

PL/SQL procedure successfully completed.

Elapsed: 00:01:05.72

           ---with---

RDWARE@ORCL..SQL>BEGIN
  2    FOR s in (select distinct(owner) schema
  3              from dba_tables
  4              where owner not like 'SYS%')
  5    LOOP
  6        dbms_output.put_line('Analyzing the entire '||s.schema||' schema');
  7        dbms_stats.gather_schema_stats(ownname           => s.schema,
  8                                       estimate_percent  => 20,
  9                                       method_opt        => 'FOR ALL COLUMNS
SIZE 1',
 10                                       degree            => 8,
 11                                       cascade           => true);
 12    End Loop;
 13  END;
 14  /
Analyzing the entire LJCAVANO schema
Analyzing the entire OUTLN schema
Analyzing the entire PD6_REPO schema
Analyzing the entire PD8_REPO schema
Analyzing the entire RDWARE schema
Analyzing the entire RPCOCA schema
Analyzing the entire RSPAYNE schema
Analyzing the entire SCOTT schema
Analyzing the entire SDCORP_REPO schema

PL/SQL procedure successfully completed.

Elapsed: 00:09:21.68

          Followup:

if you do not use parallel (and use size > 1), it'll get the chain count
Estimated.  the parallel stuff seems to disable that.

      Rating: 5
DBMS_STATS: i
      tried and it worked! Made my life easy.. ;)  June 29, 2002

Reviewer:  Yogeeraj  from Mauritius
Hello,

I was trying to analyze a schema in our database, and seems like it is was not
working!!
------------------------------------------------------------
SQL> exec print_table('select * from dba_tables where owner=''CMTSTORE'' and
table_name=''STT110''');
OWNER                         : CMTSTORE
TABLE_NAME                    : STT110
TABLESPACE_NAME               : CMTSTORE_LMT_DATA_MEDIUM
CLUSTER_NAME                  :
IOT_NAME                      :
PCT_FREE                      : 10
PCT_USED                      : 40
INI_TRANS                     : 1
MAX_TRANS                     : 255
INITIAL_EXTENT                : 10485760
NEXT_EXTENT                   : 1048576
MIN_EXTENTS                   : 1
MAX_EXTENTS                   : 2147483645
PCT_INCREASE                  : 0
FREELISTS                     : 1
FREELIST_GROUPS               : 1
LOGGING                       : YES
BACKED_UP                     : N
NUM_ROWS                      : 62255
BLOCKS                        : 679
EMPTY_BLOCKS                  : 668
AVG_SPACE                     : 898
CHAIN_CNT                     : 0
AVG_ROW_LEN                   : 73
AVG_SPACE_FREELIST_BLOCKS     : 6942
NUM_FREELIST_BLOCKS           : 5
DEGREE                        :          1
INSTANCES                     :          1
CACHE                         :     N
TABLE_LOCK                    : ENABLED
SAMPLE_SIZE                   : 12451
LAST_ANALYZED                 : 19-apr-2002 10:26:00
PARTITIONED                   : NO
IOT_TYPE                      :
TEMPORARY                     : N
SECONDARY                     : N
NESTED                        : NO
BUFFER_POOL                   : DEFAULT
ROW_MOVEMENT                  : DISABLED
GLOBAL_STATS                  : YES
USER_STATS                    : NO
DURATION                      :
SKIP_CORRUPT                  : DISABLED
MONITORING                    : NO
CLUSTER_OWNER                 :
-----------------

PL/SQL procedure successfully completed.

SQL> exec dbms_stats.gather_schema_stats('cmtstore',method_opt=>'for all indexed
columns',cascade=>true,stattab=>'CMTSTORESTATS');

PL/SQL procedure successfully completed.

SQL> exec dbms_stats.create_stat_table('yd','cmtstorestats');

PL/SQL procedure successfully completed.

SQL> exec print_table('select * from dba_tables where owner=''CMTSTORE'' and tab
le_name=''STT110''');
OWNER                         : CMTSTORE
TABLE_NAME                    : STT110
TABLESPACE_NAME               : CMTSTORE_LMT_DATA_MEDIUM
CLUSTER_NAME                  :
IOT_NAME                      :
PCT_FREE                      : 10
PCT_USED                      : 40
INI_TRANS                     : 1
MAX_TRANS                     : 255
INITIAL_EXTENT                : 10485760
NEXT_EXTENT                   : 1048576
MIN_EXTENTS                   : 1
MAX_EXTENTS                   : 2147483645
PCT_INCREASE                  : 0
FREELISTS                     : 1
FREELIST_GROUPS               : 1
LOGGING                       : YES
BACKED_UP                     : N
NUM_ROWS                      : 62255
BLOCKS                        : 679
EMPTY_BLOCKS                  : 668
AVG_SPACE                     : 898
CHAIN_CNT                     : 0
AVG_ROW_LEN                   : 73
AVG_SPACE_FREELIST_BLOCKS     : 6942
NUM_FREELIST_BLOCKS           : 5
DEGREE                        :          1
INSTANCES                     :          1
CACHE                         :     N
TABLE_LOCK                    : ENABLED
SAMPLE_SIZE                   : 12451
LAST_ANALYZED                 : 19-apr-2002 10:26:00
PARTITIONED                   : NO
IOT_TYPE                      :
TEMPORARY                     : N
SECONDARY                     : N
NESTED                        : NO
BUFFER_POOL                   : DEFAULT
ROW_MOVEMENT                  : DISABLED
GLOBAL_STATS                  : YES
USER_STATS                    : NO
DURATION                      :
SKIP_CORRUPT                  : DISABLED
MONITORING                    : NO
CLUSTER_OWNER                 :
-----------------

PL/SQL procedure successfully completed.

SQL>
============================================================

You will note that the last_analyzed column did not change.
  LAST_ANALYZED                 : 19-apr-2002 10:26:00

also, no records found in the table which i specified to save the previous
statistics:
  SQL> select count(*)
    2  from cmtstorestats;

    COUNT(*)
  ----------
           0
  SQL>
============================================================
Then as you always recommend ==> when back to Oracle Documentations: "Supplied
PL/SQL Packages" and found that something was missing -
  "statown  Schema containing stattab (if different than ownname)".

and tried again: (IT WORKED!!)
============================================================
yd@CMTDB.CMT.MU> exec dbms_stats.gather_schema_stats('cmtstore',method_opt=>'for
all indexed columns',cascade=>true,stattab=>'CMTSTORESTATS',statown=>'yd');

PL/SQL procedure successfully completed.

Elapsed: 00:00:52.57

yd@CMTDB.CMT.MU> exec print_table('select * from dba_tables where owner=''CMTSTO
RE'' and table_name=''STT110''');
OWNER                         : CMTSTORE
TABLE_NAME                    : STT110
TABLESPACE_NAME               : CMTSTORE_LMT_DATA_MEDIUM
CLUSTER_NAME                  :
IOT_NAME                      :
PCT_FREE                      : 10
PCT_USED                      : 40
INI_TRANS                     : 1
MAX_TRANS                     : 255
INITIAL_EXTENT                : 10485760
NEXT_EXTENT                   : 1048576
MIN_EXTENTS                   : 1
MAX_EXTENTS                   : 2147483645
PCT_INCREASE                  : 0
FREELISTS                     : 1
FREELIST_GROUPS               : 1
LOGGING                       : YES
BACKED_UP                     : N
NUM_ROWS                      : 74026
BLOCKS                        : 807
EMPTY_BLOCKS                  : 472
AVG_SPACE                     : 950
CHAIN_CNT                     : 0
AVG_ROW_LEN                   : 76
AVG_SPACE_FREELIST_BLOCKS     : 7737
NUM_FREELIST_BLOCKS           : 4
DEGREE                        :          1
INSTANCES                     :          1
CACHE                         :     N
TABLE_LOCK                    : ENABLED
SAMPLE_SIZE                   : 74026
LAST_ANALYZED                 : 29-jun-2002 15:21:13
PARTITIONED                   : NO
IOT_TYPE                      :
TEMPORARY                     : N
SECONDARY                     : N
NESTED                        : NO
BUFFER_POOL                   : DEFAULT
ROW_MOVEMENT                  : DISABLED
GLOBAL_STATS                  : NO
USER_STATS                    : NO
DURATION                      :
SKIP_CORRUPT                  : DISABLED
MONITORING                    : NO
CLUSTER_OWNER                 :
-----------------

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.02
yd@CMTDB.CMT.MU>
============================================================

I have two question:
1. If i set MONITORING on all tables and indexes (??) do i  still need to run
these steps regularly?
2. If i decide to use only CBO on my production database, what type of problems
(if any) can i anticipate?

Thanks a lot

Best Regards
Yogeeraj.

PS. Since i got your book, i have lesser questions to ask! Thanks. (however the
DBMS_STATS is not there in the Appendices ;) - maybe you don't use it that
frequently)

          Followup:

1) Yes.

if you use monitoring on a table, you still use dbms_stats to gather stats --
but only on STALE objects (not everything)

Monitoring on an index is for an entirely differently purpose, it'll tell you if
the index was used or not.  It is not for statistics purposes.

2) None.

I viewed dbms_stats (like backup and recovery) as being 100% a DBA thing.  The
book is definitely 100% useful to the DBA however, it was geared towards the
developer.  At 1,200+ pages -- something had to give (believe me, there is a lot
more I left out then I put in)

      Rating: 5
Thanks a lot
      for the reply.  July 01, 2002
Reviewer:
      Yogeeraj  from Mauritius
thanks for the reply.

BTW, very nice discussion in this link:

http://asktom.oracle.com/pls/ask/f?p=4950:8:987522::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1260600445561,%7Bmonitoring%7D
One last Question:
I have a "huge table" with 1.5M records. What will be the best way to update its
statistics (table-with lots of inserts/updates 24x7)? (how do i know if i really
need to do it? ;) the current statistics are since the date we moved from Oracle
7.2.3 to Oracle 8.1.7.

============================================================
yd@CMTDB.CMT.MU> exec print_table('select * from dba_tables where
table_name=''KNT200'' and owner=''SWPROD''');
OWNER                         : SWPROD
TABLE_NAME                    : KNT200
TABLESPACE_NAME               : PFS_TBS_KN
CLUSTER_NAME                  :
IOT_NAME                      :
PCT_FREE                      : 15
PCT_USED                      : 50
INI_TRANS                     : 10
MAX_TRANS                     : 200
INITIAL_EXTENT                : 272629760
NEXT_EXTENT                   : 10485760
MIN_EXTENTS                   : 1
MAX_EXTENTS                   : 240
PCT_INCREASE                  : 0
FREELISTS                     : 1
FREELIST_GROUPS               : 1
LOGGING                       : YES
BACKED_UP                     : N
NUM_ROWS                      : 1319110
BLOCKS                        : 17160
EMPTY_BLOCKS                  : 16119
AVG_SPACE                     : 1226
CHAIN_CNT                     : 0
AVG_ROW_LEN                   : 85
AVG_SPACE_FREELIST_BLOCKS     : 0
NUM_FREELIST_BLOCKS           : 0
DEGREE                        :          1
INSTANCES                     :          1
CACHE                         :     N
TABLE_LOCK                    : ENABLED
SAMPLE_SIZE                   : 1054
LAST_ANALYZED                 : 16-dec-2001 10:36:59
PARTITIONED                   : NO
IOT_TYPE                      :
TEMPORARY                     : N
SECONDARY                     : N
NESTED                        : NO
BUFFER_POOL                   : DEFAULT
ROW_MOVEMENT                  : DISABLED
GLOBAL_STATS                  : NO
USER_STATS                    : NO
DURATION                      :
SKIP_CORRUPT                  : DISABLED
MONITORING                    : NO
CLUSTER_OWNER                 :
-----------------

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.03

yd@CMTDB.CMT.MU> select count(*) from knt200;

  COUNT(*)
----------
   1540611

Elapsed: 00:00:09.33

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FAST FULL SCAN) OF 'IND_KNT200_1' (NON-UNIQUE) (Cost=2
Card=1319110)

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
       5434  consistent gets
       5432  physical reads
          0  redo size
        298  bytes sent via SQL*Net to client
        359  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed
============================================================

Thank you again for you time.
Best Regards
Yogeeraj

          Followup:

well, 1.5 million records with an average row length of 85 bytes is not very big
at all.

But, you would alter table T monitoring and let dbms_stats tell you when to do
it.  I would analyze this table once (fully) and then let monitoring take it
from there.

You are only talking about 150 meg or so of data here, pretty small.

      Rating:
      5
bright  July 01, 2002
Reviewer:
      Yogeeraj  from Mauritius
hello,
thank you for the precious guidelines.

(We always thought that our 16 GB Database and 2M record/200MB tables could be
classified as large databse/tables ;))

One little confusion about "monitoring"
you said:

http://asktom.oracle.com/pls/ask/f?p=4950:8:987536::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1260600445561,%7Bmonitoring%7D
<quote>
...since you know the table is undergoing lots of inserts during the day,
monitoring is not really needed - you know for a fact the stats are going stale
frequently and need to be re-gathered.
</quote>

so should i really, do a "alter table knt200 monitoring;"?

Also, is it true that a "table" is locked during the whole analysis operation
when using DBMS_STATS? - a problem for "online tables".

How can i know how often i need to run the DBMS_STATS.GATHER_SCHEMA_STATS with
the GATHER STALE option?

thank you again for your precious time and all the coaching you give us here!

Best Regards
Yogeeraj

          Followup:

You can either gather stats every night (if about 10% of the table will change
-- thats the threshold monitoring will use) or monitor it and gather stale.
it is ultimately upto you.  I might monitor and then see how often we do
gather stats -- to see if my thoughts about how fast this table changes were in
fact correct.

No, a table is not locked during an analyze or dbms_stats.

What the user_tab_modifications view.  It'll show you how rapidly things are
changing.  How often you run this will be a function of

a) how often you WANT to run it
b) how often you CAN run it
c) how often you should run it (based on how fast the data changes)

      Rating:
      5
DBMS_STATS  July 01, 2002
Reviewer:
      Suresh
Tom,
  This is with reference to the review posted by
Robert Ware, St.Louis. In that you have mentioned,  DBMS_STATS will be able to
gather statistics of chained rows if we make 'size > 1'.

  In the URL I have mentioned below, you said the following.

http://asktom.oracle.com/pls/ask/f?p=4950:8:989417::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:4347359891525,%7Bsuresh%7D
  "DBMS_STATS include the number of rows, number of blocks currently containing
data, and average row length but not the number of chained rows, average free
space, or number of unused data blocks."

  Could you please let me know which of the statement is correct or have I
misunderstood.

Thanks,
Suresh

          Followup:

when you do size > 1, dbms_stats seems to use analyze instead of doing it
itself.

i would not count on that always being the case, but thats the way it is right
now.

      Rating:
      5
Reader  March 25, 2003
Reviewer:
      A reader
How does oracle determine the optimal bucket size based on the data distribution
and application work load on the
columns automatically ( how Oracle calculates the work load) when

method_opt =>'for all columns size AUTO' is used

Thanks

          Followup:

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5792247321358#7417227783861

      Rating: 5
Using
      dbms_stats to transport statistics for performance
      tuning  March 25, 2003
Reviewer:  Wolfgang
      Breitling  from Calgary, Canada
I use that all the time. I take schema exports of a system (export rows=n) and
build an empty tuning sandbox. Then I export the statistics from the production
database (dbms_stats.export_xxx_stats followed by an export of the stattab
table) and then import the statistics into the sandbox. Then I can do analysis
of sql in the sandbox. Given the same input parameters the optimizer will come
up with the same access plan - even across OS platforms. Knowing what is wrong
with the original plan I can try various things, sometimes with the aid of a
10053 trace, to get the optimizer to choose a different plan. At that point I
have to go back to the production system to try out the modification to see if
that new plan is actually performing better.
That seems like a lot of work for potentially little gain, but I am a
consultant, so this setup allows me to do some of the sql analysis work
off-site.

As a note to the claims that dbms_stats is slower than analyze. I have not done
any stringent comparisons, but on my first experiences with dbms_stats I had the
distinct impression that it was faster than the analyze and I am only using
dbms_stats to gather statistics, but then I do not constantly analyze tables
either.

      Rating: 3
how can i
      gather the statstics only in the "create stat table" ?  May
      21, 2003
Reviewer:  biti_rainy  from zhuhai china

SQL> begin
  2  DBMS_STATS.GATHER_SCHEMA_STATS (
  3     ownname => user,
  4     method_opt  => 'for all indexed columns',
  5     cascade     => true,
  6     stattab     => 'BILLY_STAT',
  7     statid      => '2003-05-22-01',
  8     statown     => user);
  9  end;
 10  /

PL/SQL procedure successfully completed.

SQL> select count(*) from billy_stat;

  COUNT(*)
----------
         0

there is  no  data in  billy_stat , but  the  statistics is  stored in the DICT
TABLE .

if  i  want the statistics only store in the billy_stat, how to  do ?

          Followup:

you don't.

you would

o export the stats
o gather them, saving in billy_stat
o import the original ones

(but one would question WHY you wouldn't want them put into the dictionary since
they would be more current/accurate)

      Rating: 5
frequence of
      gathering statistics  August 06, 2003
Reviewer:
      A reader
Tom,
The impression I get from you and other articles I have read, is that current
statistics are a good thing.

"And if you don't keep your statistics current, the cost-based optimizer may not
function effectively for a particular query."

http://www.oracle.com/oramag/oracle/00-jul/index.html?o40tun.html
And your statement: "but one would question WHY you wouldn't want them put into
the dictionary since they would be more current/accurate".

But then I recently read an article by Donald K. Burleson where he states:
"One of the most common mistakes made by Oracle DBAs is to frequently re-analyze
the schema. Remember, the sole purpose of doing that is to change the execution
plans for your SQL, and if it ain't broke, don't fix it. If you are satisfied
with your current SQL performance, re-analyzing
a schema could cause significant performance problems and undo the tuning
efforts of the development staff. In practice, very few shops are sufficiently
dynamic to require periodic schema re-analysis."

http://technet.oracle.com/oramag/webcolumns/2003/techarticles/burleson_cbo_pt1_pt2.html
So, I am wondering your take on what Mr. Burleson says.

Thanks.

          Followup:

I disagree... I would say you might consider EXPORTING your statistics
frequently (nice to keep the history as a BONUS) so that in the event something
does "go bad" -- you can get back to yesterday quickly.

But having current statistics is extremely relevant for the most trivial of
applications -- consider a range scan on time sensitive data (every day you add
more records, the CBO -- which hasn't seen a new stat for 6 months assumes there
is NO DATA for the last 6 months).

Alter table monitoring
Export old
Gather stale

that way you get the benefits of any advances in performance, coupled with a
good and immediate fallback plan.  You get a very good history to boot (hey, how
did my data change in size/skew over the last 6 months?  12 months? 2 years?)

      Rating: 5
thanks Tom -
      makes sense  August 11, 2003
Reviewer:
      A reader

      Rating: 5
method_opt
      definition from 8.1.7 documentation  September 11, 2003

Reviewer:  A reader
Hi

I came across these setence in 8.1.7 documentation talking about DBMS_STATS

method_opt:
Method options of the following format (the phrase 'SIZE 1' is required to
ensure gathering statistics in parallel and for use with the phrase hidden)

I dont understand what's this talking about at all, is it saying if we leave
method_opt NULL we cannot gather statistics in parallel...??!!

          Followup:

big_table@ORA920> alter session set events '10046 trace name context forever,
level 12';

Session altered.

big_table@ORA920> begin
  2          dbms_stats.gather_table_stats
  3          ( ownname => user,
  4            tabname => 'BIG_TABLE',
  5            method_opt => NULL,
  6            degree => 4 );
  7  end;
  8  /

and tkprof shows....

select  /*+ cursor_sharing_exact dynamic_sampling(0) no_monitoring parallel(t,
  4)  parallel_index(t,4) */  count(*),sum(vsize("ID")),substrb(dump(min("ID")
  ,16,0,32),1,120),substrb(dump(max("ID"),16,0,32),1,120),count(distinct
  "OWNER"),sum(vsize("OWNER")),substrb(dump(min("OWNER"),16,0,32),1,120),
  substrb(dump(max("OWNER"),16,0,32),1,120),count(distinct "OBJECT_NAME"),
  sum(vsize("OBJECT_NAME")),substrb(dump(min("OBJECT_NAME"),16,0,32),1,120),
  substrb(dump(max("OBJECT_NAME"),16,0,32),1,120),count("SUBOBJECT_NAME"),
  count(distinct "SUBOBJECT_NAME"),sum(vsize("SUBOBJECT_NAME")),
  substrb(dump(min("SUBOBJECT_NAME"),16,0,32),1,120),
  substrb(dump(max("SUBOBJECT_NAME"),16,0,32),1,120),count(distinct
  "OBJECT_ID"),sum(vsize("OBJECT_ID")),substrb(dump(min("OBJECT_ID"),16,0,32),
  1,120),substrb(dump(max("OBJECT_ID"),16,0,32),1,120),count("DATA_OBJECT_ID")
  ,count(distinct "DATA_OBJECT_ID"),sum(vsize("DATA_OBJECT_ID")),
  substrb(dump(min("DATA_OBJECT_ID"),16,0,32),1,120),
  substrb(dump(max("DATA_OBJECT_ID"),16,0,32),1,120),count("OBJECT_TYPE"),
  count(distinct "OBJECT_TYPE"),sum(vsize("OBJECT_TYPE")),
  substrb(dump(min("OBJECT_TYPE"),16,0,32),1,120),
  substrb(dump(max("OBJECT_TYPE"),16,0,32),1,120),count(distinct "CREATED"),
  substrb(dump(min("CREATED"),16,0,32),1,120),substrb(dump(max("CREATED"),16,
  0,32),1,120),count(distinct "LAST_DDL_TIME"),
  substrb(dump(min("LAST_DDL_TIME"),16,0,32),1,120),
  substrb(dump(max("LAST_DDL_TIME"),16,0,32),1,120),count("TIMESTAMP"),
  count(distinct "TIMESTAMP"),sum(vsize("TIMESTAMP")),
  substrb(dump(min("TIMESTAMP"),16,0,32),1,120),substrb(dump(max("TIMESTAMP"),
  16,0,32),1,120),count("STATUS"),count(distinct "STATUS"),sum(vsize("STATUS")
  ),substrb(dump(min("STATUS"),16,0,32),1,120),substrb(dump(max("STATUS"),16,
  0,32),1,120),count("TEMPORARY"),count(distinct "TEMPORARY"),
  sum(vsize("TEMPORARY")),substrb(dump(min("TEMPORARY"),16,0,32),1,120),
  substrb(dump(max("TEMPORARY"),16,0,32),1,120),count("GENERATED"),
  count(distinct "GENERATED"),sum(vsize("GENERATED")),
  substrb(dump(min("GENERATED"),16,0,32),1,120),substrb(dump(max("GENERATED"),
  16,0,32),1,120),count("SECONDARY"),count(distinct "SECONDARY"),
  sum(vsize("SECONDARY")),substrb(dump(min("SECONDARY"),16,0,32),1,120),
  substrb(dump(max("SECONDARY"),16,0,32),1,120)
from
 "BIG_TABLE"."BIG_TABLE" t

it is that if you gather histograms in 8i and want parallel, you would use size
1.  setting method-opt to null bypasses histograms entirely.

      Rating: 4
am slightly
      confused  September 18, 2003
Reviewer:
      Sravan  from Northampton, MA
Tom,
I deleted the statistics on a schema.
Ran a job which gathered statistics on that schema and took 13 hours.
I tried to run it in parallel, by specifying the degree. It took 16 hours.
(Please note, that am not specifying the "method_opt" option.)

Why is the parallel job taking longer? Am I missing something obvious here or
forgot to include or do other prep work ?

First Job definition
=====================
DECLARE
v_jobno number ;
BEGIN
DBMS_JOB.SUBMIT(v_jobno,
'DBMS_STATS.GATHER_SCHEMA_STATS(''SUPPORT'',
ESTIMATE_PERCENT => 35,
CASCADE => TRUE);');
COMMIT;
END;
/
======================================

2nd Job definition with Degree specified(there are 2 CPU's on the unix box)
========================================
DECLARE
v_jobno number ;
BEGIN
DBMS_JOB.SUBMIT(v_jobno, 'DBMS_STATS.GATHER_SCHEMA_STATS(''SUPPORT'',
ESTIMATE_PERCENT => 35,
CASCADE => TRUE,
DEGREE => 3);');
COMMIT;
END;
/
================================

          Followup:

other things on the box contending with the parallel jobs...

all of the data being on too few disks (so the parallel jobs contend with
eachother)

parallel has overheads - if the schema has lots of little things, then the
overhead of parallel is greater then any benefit you might get.

use statspack to see what things are "waiting for" and whats going on .

      Rating:
      5
thanks  September 23, 2003
Reviewer:
      sravan  from Northampton, MA
Tom,
will look into it.

      Rating:
      4
dbms_stats  September 25, 2003
Reviewer:
      K.venkat  from India
Tom,

Using dbms_stats, to get the index analyzed, should we use the cascade option?
When one should go for index analysis?

Thanks,
Venkat

          Followup:

cascade => true is one method.

you might do individual indexes if you wanted

o to do the simultaneously
o gather different leves of stats for different indexes

      Rating: 5
Table lock
      ....  September 25, 2003
Reviewer:  reader

When i use dbms_stats package to collect statistics on a table, does it lock the
table like analyze table command? Thanks.

          Followup:

no but then neither does analyze (unless you are doing validate structure
and then you can use validate online in 9i)

      Rating: 5
Validate
      structure  September 27, 2003
Reviewer:
      Reader
Is there an equivalent procedure in dbms_stats to validate structure online?
Thanks. I am trying my best to digest enormous information that you have
provided in your new book Effective Oracle by Design, that is simply superb.

          Followup:

analyze is slated to do this and this alone at some time in the future.  analyze
is destined to be the "validate" thing -- analyze validate structure, analyze
list chained rows, etc.

dbms_stats will stick with stats and stats only.

      Rating: 4
Table lock ?
      Commit ?  September 28, 2003
Reviewer:
      A reader
If not table lock how come we cannot compile a proc using that table when
analyzing is in process using dbms_stats
Also I see dbms_stats issues commits automatically Correct ?

          Followup:

it doesn't work that way for me.

I just kicked off a dbms_stats.gather_table_stats on a big_table

I then proceeded to create and compile procedures that access that table

no problem.

as dbms_stats updates the data dictionary, it'll commit in order to make those
changes visible and so as to not lock up the dictionary entries.

      Rating: 4
Table Lock
      ,commit ?  September 29, 2003
Reviewer:
      A reader
You said
Followup:
no but then neither does analyze (unless you are doing validate structure
and then you can use validate online in 9i).

Are you sure because it does lock the table otherwise why would proc wait to
compile while anlyze is going.I know this may not be the correct way just tyring
to understand the behaviour of analyzing and dbms_stats. Yours is the the
correct way.

          Followup:

it *doesn't* block the procedure.

Ok, so I write a shell script:

#!/bin/csh -vx

echo analyze table big_table compute statistics > analyze.sql
echo / >> analyze.sql
echo exit >> analyze.sql

sqlplus big_table/big_table @analyze &

sleep 5

sqlplus big_table/big_table <<EOF
@showsql
create or replace procedure p
as
begin
   for x in ( select * from big_table )
   loop
        null;
   end loop;
end;
/

exit
EOF

that'll kick off an analyze, give it a couple of seconds to start... then it
creates a procedure in another session that references the table being analyzed.
 The output of that was:

$ ./test.sh

echo analyze table big_table compute statistics > analyze.sql
echo analyze table big_table compute statistics
echo / >> analyze.sql
echo /
echo exit >> analyze.sql
echo exit

sqlplus big_table/big_table @analyze &
[1] 28005

sqlplus big_table/big_table @analyze
sleep 5
sleep 5

SQL*Plus: Release 8.1.7.0.0 - Production on Tue Sep 30 06:57:40 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production

sqlplus big_table/big_table << EOF
sqlplus big_table/big_table

SQL*Plus: Release 8.1.7.0.0 - Production on Tue Sep 30 06:57:45 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production

big_table@ORA817DEV>
USERNAME        SID_SERIAL      STATUS     MODULE          LAST_CALL_ET
--------------- --------------- ---------- --------------- ------
BIG_TABLE       '8,30332'       ACTIVE     01@ analyze.sql           4
BIG_TABLE       '9,231'         ACTIVE     01@ showsql.sql           0

2 rows selected.

that shows the analyze is running and has been for about 4 seconds...

--------------------
BIG_TABLE(8,30332) ospid = 28005 command = 62 program = sqlplus@aria-dev (TNS
V1-V3) dedicated server=28007
Tuesday   06:57  Tuesday   06:57 last et = 4
analyze table big_table compute statistics

big_table@ORA817DEV>   2    3    4    5    6    7    8    9
Procedure created.

and at the same time -- we created a procedure.

big_table@ORA817DEV> big_table@ORA817DEV> Disconnected from Oracle8i Enterprise
Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production

      Rating: 3
Monitoring
      Threshold  November 07, 2003
Reviewer:  Matt
       from UK
"You can either gather stats every night (if about 10% of the table will change
-- thats the threshold monitoring will use) or monitor it and gather stale."

Our tests look to show that monitor will show *all* changes to the table -
regardless of the 10% threshold, and the dbms_stats gather stale filters out the
10%.

This is on 8.1.7.4 does behaviour change with 9i ?

          Followup:

yes, monitor monitors all changes and we only gather stats when it hits a
threshold.  I'm not sure what you mean by this tho?  the threshold that triggers
that gather and resets the monitoring is about 10% change.

      Rating: 5
oops... the
      proof  November 07, 2003
Reviewer:  Matt
       from UK
SQL> select count(*) from stat_test_table
  2  /

  COUNT(*)
----------
      1000

1 row selected.

SQL>
SQL> select num_rows,to_char(last_analyzed,'DD-MON-RRRR HH24:MI:SS') from
all_tables
  2  where table_name = 'STAT_TEST_TABLE'
  3  /

  NUM_ROWS TO_CHAR(LAST_ANALYZE
---------- --------------------
      1000 07-NOV-2003 08:59:45

1 row selected.

SQL>
SQL> select table_name, inserts, updates,deletes from all_tab_modifications
  2  /

no rows selected

SQL>
SQL> insert
  2  into stat_test_table
  3  select rownum+1000,
  4             owner,
  5             object_name,
  6             object_type,
  7             1,
  8             1
  9  from all_objects
 10  where rownum <=5
 11  /

5 rows created.

SQL>
SQL> commit;

Commit complete.

--
-- Force the refresh of all_tab_modifications by restarting instance
--

SVRMGR> shutdown immediate;
Database closed.
Database dismounted.
ORACLE instance shut down.
SVRMGR> startup
ORACLE instance started.
Total System Global Area                        127309948 bytes
Fixed Size                                         116860 bytes
Variable Size                                    94253056 bytes
Database Buffers                                 32768000 bytes
Redo Buffers                                       172032 bytes
Database mounted.
Database opened.

--
-- Reconnect
--

SQL> conn matt/matt
Connected.

SQL> select table_name, inserts, updates,deletes from all_tab_modifications
/  2

TABLE_NAME                        INSERTS    UPDATES    DELETES
------------------------------ ---------- ---------- ----------
STAT_TEST_TABLE                         5          0          0

begin
dbms_stats.gather_schema_stats ('MATT',  options => 'GATHER STALE');
end;

SQL> select table_name, inserts, updates,deletes from all_tab_modifications
/  2

TABLE_NAME                        INSERTS    UPDATES    DELETES
------------------------------ ---------- ---------- ----------
STAT_TEST_TABLE                         5          0          0

--
-- Now Insert Higher Than 10%
--

 1  insert
  2  into stat_test_table
  3  select rownum+1200,
  4             owner,
  5              object_name,
  6              object_type,
  7                 1,
  8                 1
  9      from all_objects
 10*    where rownum <=110
SQL> /

110 rows created.

SQL> commit;

Commit complete.

SQL> select count(*) from stat_test_table;

  COUNT(*)
----------
      1115

1 row selected.

--
-- BOUNCE THE INSTANCE
--

  1*  select table_name, inserts, updates,deletes from all_tab_modifications
SQL> /

TABLE_NAME                        INSERTS    UPDATES    DELETES
------------------------------ ---------- ---------- ----------
STAT_TEST_TABLE                       115          0          0

1 row selected.

SQL> l
  1  begin
  2  dbms_stats.gather_schema_stats ('MATT',  options => 'GATHER STALE');
  3  end;
  4*
SQL> /

PL/SQL procedure successfully completed.

SQL> select table_name, inserts, updates,deletes from all_tab_modifications
  2  /

no rows selected

1* select num_rows,to_char(last_analyzed,'DD-MON-RRRR HH24:MI:SS') from
all_tables
SQL> 2 where table_name = 'STAT_TEST_TABLE'

SQL> /

  NUM_ROWS TO_CHAR(LAST_ANALYZE
---------- --------------------
      1115 07-NOV-2003 09:46:01

1 row selected.

          Followup:

yes, that is the way it works -- dbms_stats uses the 10% threshold and resets
the monitoring statistics.

      Rating: 5
'GATHER STALE'
      is not working!?  November 24, 2003
Reviewer:
      harri
Hi!

I have this procedure:

SQL> select text from dba_source where name='HK_ANALYZE';

TEXT
--------------------------------------------------------------------------------
procedure hk_analyze
(v_schema in varchar2,
 v_estimate_pct in number default 0,
 v_degree in number default 1)
as
 a dbms_stats.objecttab;
 start_time varchar(30);
 stop_time varchar(30);

begin
  select TO_CHAR(sysdate, 'DD-MON-YYYY HH24:MI:SS') into start_time from dual;
     dbms_stats.gather_schema_stats
          (OWNNAME => v_schema,
           ESTIMATE_PERCENT => v_estimate_pct,
           CASCADE => TRUE,
           GRANULARITY => 'ALL',
           OPTIONS=>'GATHER EMPTY',
           DEGREE => v_degree,
           OBJLIST=>a);
dbms_output.put_line('1. kerran analysoidut');
    for i in 1 .. a.count
    loop
      dbms_output.put_line( a(i).ownname || '.' ||a(i).objName ||' ('||a(i).objT
ype || ')'  );

      end loop;
dbms_output.put_line('-----');
     dbms_stats.gather_schema_stats
          (OWNNAME => v_schema,
           ESTIMATE_PERCENT => v_estimate_pct,
           CASCADE => TRUE,
           GRANULARITY => 'ALL',
           OPTIONS=>'GATHER STALE',
           DEGREE => v_degree,
           OBJLIST=>a);
dbms_output.put_line('Analysoitu, koska vM-dhintM-dM-dn 10% muuttunut:');
    for i in 1 .. a.count
 loop
      dbms_output.put_line( a(i).ownname || '.' ||a(i).objName ||' ('||a(i).objT
ype || ')'  );

      end loop;
select TO_CHAR(sysdate, 'DD-MON-YYYY HH24:MI:SS') into stop_time from dual;
dbms_output.put_line('-----');
DBMS_OUTPUT.PUT_LINE('Analysointi alkoi: ' || start_time);
DBMS_OUTPUT.PUT_LINE('Analysointi loppui: ' || stop_time);
end hk_analyze;

45 rows selected.

******

SQL> create table MYYNTI.HK_TESTI as select * from dba_objects where
rownum<1000;

Table created.

SQL> exec hk_analyze('MYYNTI', 99,1)
1. kerran analysoidut:
"MYYNTI"."HK_TESTI" (TABLE)
-----
Analysoitu, koska vM-dhintM-dM-dn 10% muuttunut:
-----
Analysointi alkoi: 24-NOV-2003 13:13:14
Analysointi loppui: 24-NOV-2003 13:13:15

PL/SQL procedure successfully completed.

...this is ok. Table is analyzed first time.

SQL> exec hk_analyze('MYYNTI', 99,1)
1. kerran analysoidut:
-----
Analysoitu, koska vM-dhintM-dM-dn 10% muuttunut:
-----
Analysointi alkoi: 24-NOV-2003 13:13:21
Analysointi loppui: 24-NOV-2003 13:13:21

PL/SQL procedure successfully completed.

...and this is ok. No inserts or updates to that table. But then gather
stale-part of the procedure is not working:

SQL> insert into MYYNTI.HK_TESTI select * from dba_objects where rownum<110;

109 rows created.

SQL> commit;

Commit complete.

SQL> exec hk_analyze('MYYNTI', 99,1)
1. kerran analysoidut:
-----
Analysoitu, koska vM-dhintM-dM-dn 10% muuttunut:
-----
Analysointi alkoi: 24-NOV-2003 13:13:54
Analysointi loppui: 24-NOV-2003 13:13:55

PL/SQL procedure successfully completed.

SQL> insert into MYYNTI.HK_TESTI select * from dba_objects where rownum<110;

109 rows created.

SQL> commit;

Commit complete.

SQL> exec hk_analyze('MYYNTI', 99,1)
1. kerran analysoidut:
-----
Analysoitu, koska vM-dhintM-dM-dn 10% muuttunut:
-----
Analysointi alkoi: 24-NOV-2003 13:14:11
Analysointi loppui: 24-NOV-2003 13:14:11

PL/SQL procedure successfully completed.

SQL> delete from MYYNTI.HK_TESTI where rownum<400;

399 rows deleted.

SQL> exec hk_analyze('MYYNTI', 99,1)
1. kerran analysoidut:
-----
Analysoitu, koska vM-dhintM-dM-dn 10% muuttunut:
-----
Analysointi alkoi: 24-NOV-2003 13:14:41
Analysointi loppui: 24-NOV-2003 13:14:41

PL/SQL procedure successfully completed.

SQL> commit;

Commit complete.

SQL> exec hk_analyze('MYYNTI', 99,1)
1. kerran analysoidut:
-----
Analysoitu, koska vM-dhintM-dM-dn 10% muuttunut:
-----
Analysointi alkoi: 24-NOV-2003 13:14:48
Analysointi loppui: 24-NOV-2003 13:14:48

PL/SQL procedure successfully completed.

SQL> update MYYNTI.HK_TESTI set owner='TESTI' where owner='SYS';

818 rows updated.

SQL> commit;

Commit complete.

SQL> exec hk_analyze('MYYNTI', 99,1)
1. kerran analysoidut:
-----
Analysoitu, koska vM-dhintM-dM-dn 10% muuttunut:
-----
Analysointi alkoi: 24-NOV-2003 13:15:36
Analysointi loppui: 24-NOV-2003 13:15:36

PL/SQL procedure successfully completed.

SQL>

******

translations of strings
"Analysoitu, koska vM-dhintM-dM-dn 10% muuttunut:" =
analyzed becouse of 10% changes

"1. kerran analysoidut" = first time analyzed

****

So, what is wrong here?

-Harri

          Followup:

depending on your version (and you do not seem to mention one) the stats info is
flushed every 3 hours or during a normal shutdown.  If you have current
releases, this is not the case.

also -- you never made the table monitoring??  alter table T monitoring;  else
it will never be stale.

I tested in 9i with monitoring and it worked fine.

      Rating:
      5
GATHER-problem  November 24, 2003

Reviewer:  HK  from Finland
The version of that db is 8.1.7.4.0.

I did't know that "flush"-thing. Now, when flush is done it's analyzing that
table:

SQL> exec hk_analyze('MYYNTI', 30,2)
1. kerran analysoidut:
-----
Analysoitu, koska yli 10% muuttunut:
"MYYNTI"."HK_TESTI" (TABLE)
-----
Analysointi alkoi: 24-NOV-2003 21:21:09
Analysointi loppui: 24-NOV-2003 21:21:13

*****

Thanks for Your information!!

Originally I developed procedure in 9i db. In procedure there I have line:
dbms_stats.ALTER_SCHEMA_TAB_MONITORING(v_schema, TRUE);

That's not working in 8i. So, now I added lines:

begin
  for asd in no_monitor loop
   begin
     execute immediate ('alter table '|| v_schema || '.' || asd.table_name || '
monitoring');
   exception
     when e_busy then
       DBMS_OUTPUT.PUT_LINE('Setting monitoring on table' || v_schema || '.' ||
asd.table_name || '  was unsuccesfull!');
   end;
  end loop;

...and it seems to work fine. What You think, is that a good solution for that?

          Followup:

yup, thats perfect (the execute immediate)

      Rating: 5
move statistics
      between users  December 08, 2003
Reviewer:
      A reader
Hi

we have 2 schemas in our database, one is development and the other query
testing.

we analyze development schema once every 2 days, it takes around 8 hours. how
can we move these statistics from deveopment schema to to query testing schema?

with dbms_stats.get/set or dbms_stats.export/import?

What is the difference?

          Followup:

dbms_stats export and import.

export and import copy stats from place to place

set lets you manaully "set" them to whatever values you want.

December 09, 2003 Reddy -- Thanks for the question regarding
      "ROLLUP OR CUBE", version 8.1.5

      You Asked
Hi Tom

Thanks for your continuous help to Oracle WORLD.

I have table dept like

SQL> Select * from test_dept;

DEPTCODE    DEPTNAME    AMOUNT
---------   ---------   --------
100          SALES        100
100          SALES        120

120          MKTG         150
120          MKTG         200
120          MKTG         100

150          FINANCE      150
150          FINANCE      120

SO, My data is look like this.

but I want a query like,

DEPTCODE    DEPTNAME    AMOUNT
---------   ---------   --------
100          SALES        100
100          SALES        120
------------------------------
Dept SALES total amount is  220

120          MKTG         150
120          MKTG         200
120          MKTG         100
------------------------------
Dept MKTG total amount is 450

150          FINANCE      150
150          FINANCE      120
-----------------------------
Dept Finance total amount is 270

I think this can be done through CUBE OR ROLLUP , But I am not able to select
like that.

2.     Is it possible to add one more column dynamically add to the existing
table (test_dept) or one more table or view with the same data.

If I issue select * from sometable. My data should be look like this.

DEPTCODE    DEPTNAME    AMOUNT     TOTAL
---------   ---------   --------
100          SALES        100
100          SALES        120
------------------------------
Dept SALES total amount is  220     220

120          MKTG         150
120          MKTG         200
120          MKTG         100
------------------------------
Dept MKTG total amount is 450       450

150          FINANCE      150
150          FINANCE      120
-----------------------------
Dept Finance total amount is 270   270

similarly I want like that

Thanks in advance

Reddy

      and we said...
Yes, you can do this -- its a bit tricky since the group by tends to want to
"squash out" the repeated DEPTCODE/DEPTNAME values.  So, we have to add some
unique there to prevent that aggregation.  In the following I used rownum to do
that.  You are interested in the last query -- the second just shows the
grouping values so it is clear why I wrote the HAVING clause as I did:

scott@ORA817DEV.US.ORACLE.COM> create or replace view v
  2  as
  3  select dept.deptno deptcode, dept.dname deptname, sal amount
  4    from emp, dept
  5   where emp.deptno = dept.deptno
  6  /

View created.

scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM> select decode( grouping(rownum), 1,
to_number(NULL), deptcode ) deptcode,
  2             deptname || decode( grouping(rownum), 1, ' total amount' )
deptname,
  3             sum(amount),
  4             grouping(deptcode) g1, grouping(deptname) g2, grouping(rownum)
g3
  5    from v
  6   group by rollup(deptcode,deptname,rownum)
  7  having (grouping(deptcode) = 0 and grouping(deptname) = 0 and
grouping(rownum)=0) OR
  8         (grouping(deptcode) = 0 and grouping(deptname) = 0 and
grouping(rownum)=1)
  9  /

  DEPTCODE DEPTNAME                    SUM(AMOUNT)  G1  G2  G3
---------- --------------------------- ----------- --- --- ---
        10 ACCOUNTING                         2450   0   0   0
        10 ACCOUNTING                         5000   0   0   0
        10 ACCOUNTING                         1300   0   0   0
           ACCOUNTING total amount            8750   0   0   1
        20 RESEARCH                            800   0   0   0
        20 RESEARCH                           1100   0   0   0
        20 RESEARCH                           3000   0   0   0
        20 RESEARCH                           3000   0   0   0
        20 RESEARCH                           2975   0   0   0
           RESEARCH total amount             10875   0   0   1
        30 SALES                              1600   0   0   0
        30 SALES                              2850   0   0   0
        30 SALES                              1250   0   0   0
        30 SALES                               950   0   0   0
        30 SALES                              1500   0   0   0
        30 SALES                              1250   0   0   0
           SALES total amount                 9400   0   0   1

17 rows selected.

scott@ORA817DEV.US.ORACLE.COM>
scott@ORA817DEV.US.ORACLE.COM> select decode( grouping(rownum), 1,
to_number(NULL), deptcode ) deptcode,
  2             deptname || decode( grouping(rownum), 1, ' total amount' )
deptname,
  3             sum(amount),
  4             decode( grouping(rownum), 1, sum(amount), to_number(null) )
total
  5    from v
  6   group by rollup(deptcode,deptname,rownum)
  7  having (grouping(deptcode) = 0 and grouping(deptname) = 0 and
grouping(rownum)=0) OR
  8         (grouping(deptcode) = 0 and grouping(deptname) = 0 and
grouping(rownum)=1)
  9  /

  DEPTCODE DEPTNAME                    SUM(AMOUNT)      TOTAL
---------- --------------------------- ----------- ----------
        10 ACCOUNTING                         2450
        10 ACCOUNTING                         5000
        10 ACCOUNTING                         1300
           ACCOUNTING total amount            8750       8750
        20 RESEARCH                            800
        20 RESEARCH                           1100
        20 RESEARCH                           3000
        20 RESEARCH                           3000
        20 RESEARCH                           2975
           RESEARCH total amount             10875      10875
        30 SALES                              1600
        30 SALES                              2850
        30 SALES                              1250
        30 SALES                               950
        30 SALES                              1500
        30 SALES                              1250
           SALES total amount                 9400       9400

17 rows selected.
Review & Followup

      Rating: 4
Rollup and
      Cube  March 30, 2002
Reviewer:  Reddy
       from Bangalore, India
This has been almost solved my problem.

Thank u very much

Reddy

      Rating: 4
Just the Grand
      Total please...  December 31, 2002
Reviewer:
      Robert  from PA
Tom, ROLLUP is give me more info than I need...
I just want the grand total number

SQL> select count(*) from emp;

  COUNT(*)
----------
        14

SQL> select job, deptno, count(*) from emp group by job, deptno ;

JOB       DEPTNO   COUNT(*)
--------- ------ ----------
ANALYST       20          2
CLERK         10          1
CLERK         20          2
CLERK         30          1
MANAGER       10          1
MANAGER       20          1
MANAGER       30          1
PRESIDENT     10          1
SALESMAN      30          4

Is there a way to get ROLLUP (or analytics) to output the JUST grand total
rollup count like below ? without going to the inline view but just manipulate
the GROUP BY ... ROLLUP (...) ?

JOB       DEPTNO   COUNT(*)
--------- ------ ----------
ANALYST       20          2
CLERK         10          1
CLERK         20          2
CLERK         30          1
MANAGER       10          1
MANAGER       20          1
MANAGER       30          1
PRESIDENT     10          1
SALESMAN      30          4
                         14

Thanks and have a fabulous New Year !

          Followup:

This'll work in 8i:

scott@ORA920> select job, deptno, count(*)
  2    from emp
  3   group by rollup( job, deptno )
  4  having (grouping(job) = grouping(deptno))
  5  /

JOB           DEPTNO   COUNT(*)
--------- ---------- ----------
CLERK             10          1
CLERK             20          2
CLERK             30          1
ANALYST           20          2
MANAGER           10          1
MANAGER           20          1
MANAGER           30          1
SALESMAN          30          4
PRESIDENT         10          1
                             14

10 rows selected.

and in 9i, recognizing the need for this, they added grouping sets:

scott@ORA920> select job, deptno, count(*)
  2    from emp
  3   group by grouping sets ( (job,deptno), () )
  4  /

JOB           DEPTNO   COUNT(*)
--------- ---------- ----------
CLERK             10          1
CLERK             20          2
CLERK             30          1
ANALYST           20          2
MANAGER           10          1
MANAGER           20          1
MANAGER           30          1
SALESMAN          30          4
PRESIDENT         10          1
                             14

10 rows selected.

      Rating: 5
conditional
      rollup  May 06, 2003
Reviewer:
      A reader
Tom,

Can we have conditional rollup  say if its deptno 20 then
do not rollup by job else for the rest of the departments
rollup by job....

Thanks,

          Followup:

not really.

obviously -- it would be easy to add a where or having clause to eliminate the
unwanted rows.

      Rating: 4
Having clause
      neglected  July 21, 2003
Reviewer:
      A reader
the  comment point above
select job, deptno, count(*)
  2    from emp
  3   group by grouping sets ( (job,deptno), () )
      having count(*) > 1

Gives wrong  result it never considers the having clause.
Why
Same for 8i soln also
 If we include having count(*) > 1 it is not considered

          Followup:

what do you mean?

scott@ORA920LAP> select job, deptno, count(*)
  2    from emp
  3   group by grouping sets ( (job,deptno), () )
  4  having count(*) > 1
  5  /

JOB           DEPTNO   COUNT(*)
--------- ---------- ----------
CLERK             20          2
ANALYST           20          2
SALESMAN          30          4
                             14

scott@ORA920LAP> select job, deptno, count(*)
  2    from emp
  3   group by grouping sets ( (job,deptno), () )
  4  /

JOB           DEPTNO   COUNT(*)
--------- ---------- ----------
CLERK             10          1
CLERK             20          2
CLERK             30          1
ANALYST           20          2
MANAGER           10          1
MANAGER           20          1
MANAGER           30          1
SALESMAN          30          4
PRESIDENT         10          1
                             14

10 rows selected.

      Rating: 4
Total must be 8
      and not 14 hence i said having is bypassed  July 21, 2003

Reviewer:  A reader
As you see the total count(*) is wrong.
  Shouldnt the Total count(*) be 8 instead of 14
scott@ORA920LAP> select job, deptno, count(*)
  2    from emp
  3   group by grouping sets ( (job,deptno), () )
  4  having count(*) > 1
  5  /

JOB           DEPTNO   COUNT(*)
--------- ---------- ----------
CLERK             20          2
ANALYST           20          2
SALESMAN          30          4
                             14

          Followup:

umm, there are 14 rows?

scott@ORA920LAP> select job, deptno, count(*),
  2         grouping(job), grouping(deptno)
  3    from emp
  4    group by grouping sets ( (job,deptno), () )
  5  having count(*) > 1
  6  /

JOB           DEPTNO   COUNT(*) GROUPING(JOB) GROUPING(DEPTNO)
--------- ---------- ---------- ------------- ----------------
CLERK             20          2             0                0
ANALYST           20          2             0                0
SALESMAN          30          4             0                0
                             14             1                1

scott@ORA920LAP> select count(*)
  2    from emp
  3    group by grouping sets ( () )
  4  having count(*) > 1
  5  /

  COUNT(*)
----------
        14

      Rating: 1
This definitely
      deserves 0 stars  July 21, 2003
Reviewer:
      A reader
I think ur not TOM may be he is on vacation

I want to have the grand total of the existing rows which have count(*) > 1 and
that is 8 and not 14.
Can u pls if possible give some soln.

So I need
JOB           DEPTNO   COUNT(*)
--------- ---------- ----------
CLERK             20          2
ANALYST           20          2
SALESMAN          30          4
                              8 ( and not 14).
Tom where are you ?

          Followup:

No, this is me.

you know what -- when you say what you want, one can give that to you.

when you say things like "hey, this isn't returning the right stuff" (how very
clear of you -- thanks) and I answer "hey, it is returning exactly what you
asked for" -- what do you expect.

You did the query entirely wrong.

scott@ORA920LAP> select job, deptno, sum(cnt) cnt
  2    from (
  3  select job, deptno, count(*) cnt
  4    from emp
  5   group by job, deptno
  6  having count(*) > 1
  7         )
  8   group by grouping sets( (job,deptno),() )
  9  /

JOB           DEPTNO        CNT
--------- ---------- ----------
CLERK             20          2
ANALYST           20          2
SALESMAN          30          4
                              8

think about it -- you wanted the sum of the counts after the having clause was
processed.

      Rating: 2
Having
      clause  July 22, 2003
Reviewer:  c.a.
       from Vancouver
Actually, HAVING clause is only for display. Basically, you are saying 'do not
display where count(*) < 2' with the statement below. Happens after the result
set.

scott@ORA920LAP> select job, deptno, count(*)
  2    from emp
  3   group by grouping sets ( (job,deptno), () )
  4  having count(*) > 1
  5  /

you should put the conditions into the where clause! You can do this a few
different ways. One as below.

SELECT job,deptno,sum(cnt)
  FROM (Select job, deptno, count(*) cnt
          From emp
      Group By job, deptno)
 WHERE cnt > 1
GROUP BY grouping sets ((job,deptno),(' '))
/

JOB      DEPTNO  SUM(CNT)
ANALYST  20      2
CLERK    20      2
SALESMAN 30      4
                 8

      Rating: 5
Thanx much Tom
      and the person from Vancouver  July 22, 2003

Reviewer:  A reader
The combined comments explains it all I didnt know having is just for display.
 I think it is done in the very end after grouping and all is done.

      Rating: 3
Q on
      Cube  October 16, 2003
Reviewer:  Anil Pant
       from Hyderabad, India
I've done the following :

create table akp1 as select object_type otype, object_id oid,
to_char(CREATED,'month') mth
from user_objects
where object_type in ('PACKAGE', 'TYPE', 'VIEW');

SQL> select otype, mth, count(*)
  2  from akp1
  3  group by cube(otype, mth);

Query 1 :

OTYPE              MTH         COUNT(*)
------------------ --------- ----------
                                    118
                   august             3
                   july             109
                   october            3
                   september          3
TYPE                                  2
TYPE               october            2
VIEW                                 15
VIEW               august             1
VIEW               july              13
VIEW               october            1

OTYPE              MTH         COUNT(*)
------------------ --------- ----------
PACKAGE                             101
PACKAGE            august             2
PACKAGE            july              96
PACKAGE            september          3

15 rows selected.

Query 2 :

SQL> select mth, otype, count(*)
  2  from akp1
  3  group by cube(mth, otype);

MTH       OTYPE                COUNT(*)
--------- ------------------ ----------
                                    118
          TYPE                        2
          VIEW                       15
          PACKAGE                   101
august                                3
august    VIEW                        1
august    PACKAGE                     2
july                                109
july      VIEW                       13
july      PACKAGE                    96
october                               3

MTH       OTYPE                COUNT(*)
--------- ------------------ ----------
october   TYPE                        2
october   VIEW                        1
september                             3
september PACKAGE                     3

15 rows selected.

My Q is why is the blank rows on the top.
MTH       OTYPE                COUNT(*)
--------- ------------------ ----------
                                    118

Query 3 : If I give the same query with Order By clause then it's ok.

SQL> select mth, otype, count(*)
  2  from akp1
  3  group by cube(mth, otype)
  4  order by mth, otype;

MTH       OTYPE                COUNT(*)
--------- ------------------ ----------
august    PACKAGE                     2
august    VIEW                        1
august                                3
july      PACKAGE                    96
july      VIEW                       13
july                                109
october   TYPE                        2
october   VIEW                        1
october                               3
september PACKAGE                     3
september                             3

MTH       OTYPE                COUNT(*)
--------- ------------------ ----------
          PACKAGE                   101
          TYPE                        2
          VIEW                       15
                                    118

But in none of the books this is given or discussed. Am I missed something ?

          Followup:

group by cube just gives you every aggregation -- you use the GROUPING function
to see what level of aggregation.  it is free to give it to you as it "likes"

add columns grouping(mth), grouping(otype) to the select list and see what you
can make of that.

      Rating: 4
How to subtotal
      this query  October 22, 2003
Reviewer:  aish
       from tx
Select Distinct MGMT_COMPANY,
                    CUSTOMER_NAME,
                    TO_CHAR(SO_ORIGINAL_ORDER_DATE, 'MONTH') "MON",
                    NVL(Sum((SO_QUANTITY - NVL(SO_CANCEL_QTY, 0)) *
                            (SO_SALES_PRICE)),
                        0) "PRICE1",
                    NVL(Sum((PO_QTY - NVL(PO_CANCEL_QTY, 0)) *
                            PO_COST_TO_SITESTUFF),
                        0) "PRICE2"
      From SALES AA
     Where VENDOR_ID = 24 And TO_CHAR(SO_ORIGINAL_ORDER_DATE, 'YYYY') = '2003'
     Group By Rollup(MGMT_COMPANY,
                     CUSTOMER_NAME,
                     TO_CHAR(SO_ORIGINAL_ORDER_DATE, 'MONTH'));

my out put is like this

MGMT_COMPANY      CUSTOMER_NAME    MON        PRICE1   PRICE2
aa                        a        jan           100   200
aa                        b        feb           100   200
aa                        b        mar           100   200
aa                        c        jan           100   200
     ----- i need one subtotal here
bb                        a        oct           100   200

     ----- i need one subtotal here
cc                        a        jan           100   200
cc                        b        feb           100   200
          ----- i need one subtotal here
                                           ----grand total

Iam using oracle 8i trying to get subtotal using your technique of using rownum
inbetween .
but i couldn't.

Thanks in advance
aish

          Followup:

one subtotal of what?  the rollup gives you the subtotals automatically.

ops$tkyte@ORA920> select decode( grouping( ename ), 1, 'Sub' ),
  2         grouping( deptno ) gdeptno,
  3             grouping( ename ) gename,
  4         grouping( to_char(hiredate,'Mon') ) gdate,
  5         deptno, ename, to_char(hiredate,'Mon') Mon, sum(sal)
  6    from emp
  7   group by rollup( deptno, ename, to_char(hiredate,'Mon') )
  8  /

DEC    GDEPTNO     GENAME      GDATE     DEPTNO ENAME      MON   SUM(SAL)
--- ---------- ---------- ---------- ---------- ---------- --- ----------
             0          0          0         10 KING       Nov       5000
             0          0          1         10 KING                 5000
             0          0          0         10 CLARK      Jun       2450
             0          0          1         10 CLARK                2450
             0          0          0         10 MILLER     Jan       1300
             0          0          1         10 MILLER               1300
Sub          0          1          1         10                      8750
             0          0          0         20 FORD       Dec       3000
             0          0          1         20 FORD                 3000
             0          0          0         20 ADAMS      Jan       1100
             0          0          1         20 ADAMS                1100
             0          0          0         20 JONES      Apr       2975
             0          0          1         20 JONES                2975
             0          0          0         20 SCOTT      Dec       3000
             0          0          1         20 SCOTT                3000
             0          0          0         20 SMITH      Dec        800
             0          0          1         20 SMITH                 800
Sub          0          1          1         20                     10875
             0          0          0         30 WARD       Feb       1250
             0          0          1         30 WARD                 1250
             0          0          0         30 ALLEN      Feb       1600
             0          0          1         30 ALLEN                1600
             0          0          0         30 BLAKE      May       2850
             0          0          1         30 BLAKE                2850
             0          0          0         30 JAMES      Dec        950
             0          0          1         30 JAMES                 950
             0          0          0         30 MARTIN     Sep       1250
             0          0          1         30 MARTIN               1250
             0          0          0         30 TURNER     Sep       1500
             0          0          1         30 TURNER               1500
Sub          0          1          1         30                      9400
Sub          1          1          1                                29025

32 rows selected.

and if you feel some records are "duplicated", you can use a having clause to
remove them:
ops$tkyte@ORA920> select decode( grouping( ename ), 1, 'Sub' ),
  2         grouping( deptno ) gdeptno,
  3             grouping( ename ) gename,
  4         grouping( to_char(hiredate,'Mon') ) gdate,
  5         deptno, ename, to_char(hiredate,'Mon') Mon, sum(sal)
  6    from emp
  7   group by rollup( deptno, ename, to_char(hiredate,'Mon') )
  8  having NOT( grouping(deptno) = 0 and grouping(ename) = 0 and
grouping(to_char(hiredate,'Mon')) = 1 )
  9  /

DEC    GDEPTNO     GENAME      GDATE     DEPTNO ENAME      MON   SUM(SAL)
--- ---------- ---------- ---------- ---------- ---------- --- ----------
             0          0          0         10 KING       Nov       5000
             0          0          0         10 CLARK      Jun       2450
             0          0          0         10 MILLER     Jan       1300
Sub          0          1          1         10                      8750
             0          0          0         20 FORD       Dec       3000
             0          0          0         20 ADAMS      Jan       1100
             0          0          0         20 JONES      Apr       2975
             0          0          0         20 SCOTT      Dec       3000
             0          0          0         20 SMITH      Dec        800
Sub          0          1          1         20                     10875
             0          0          0         30 WARD       Feb       1250
             0          0          0         30 ALLEN      Feb       1600
             0          0          0         30 BLAKE      May       2850
             0          0          0         30 JAMES      Dec        950
             0          0          0         30 MARTIN     Sep       1250
             0          0          0         30 TURNER     Sep       1500
Sub          0          1          1         30                      9400
Sub          1          1          1                                29025

18 rows selected.

      Rating: 5
Adding another
      column to the rollup.  October 25, 2003
Reviewer:
      ht  from California
Tom,
Your examples are great.  Is it possible to add a first_name column to this
query?  I tweaked the query but wasn't able to get it to work without duplicate
lines unless I appended first_name||ename throughout the query.
This works fine for my purposes but it would be nice if I could have distinct
columns instead of a concatenated one.  This would let me sort on each column in
my PSP.
Thanks,
ht

      Rating: 5
Got the answer
      in your forum.  October 26, 2003
Reviewer:
      ht  from California
Hi Tom,
Please disregard my ignorant question.  I found the answer in your forum and it
involves my misunderstanding of the "having" clause.
ht

      Rating: 4
break up
        November 11, 2003
Reviewer:  umesh kasturi
       from bangalore india
Tom
I have some requirement like this

deptno  name                       tot_cnt
10      'total_in_deptno_10'       4
        'whose sal>4000'           2
        'whose sal <4000'          1
        'whose sal=4000'           1
.... The same repeats for deptno=20 ,30 and so on

I do not know how do i go about with analytic functions here

          Followup:

we can do it in COLUMNS

scott@ORA920> select deptno, count(*) tot_dept, count(case when sal>4000 then 1
else null end) "sal>4000",
  2   count(case when sal <4000 then 1 else null end) "sal<4000",
  3   count(case when sal=4000 then 1 else null end) "sal=4000"
  4  from emp
  5  group by deptno;

    DEPTNO   TOT_DEPT   sal>4000   sal<4000   sal=4000
---------- ---------- ---------- ---------- ----------
        10          3          1          2          0
        20          5          0          5          0
        30          6          0          6          0

scott@ORA920>

don't even need analytics.  We can unpivot that if you really need:

  1  select deptno, decode( x, 1, 'total in deptno ' || deptno,
  2                            2, 'whose sal >4000',
  3                            3, 'whose sal <4000',
  4                            4, 'whose sal=4000' ) name,
  5                 decode( x, 1, tot_dept,
  6                            2, "sal>4000",
  7                            3, "sal<4000",
  8                            4, "sal=4000" ) tot_cnt
  9     from ( select rownum x from all_objects where rownum <= 4),
 10          ( select deptno,
 11                   count(*) tot_dept,
 12                   count(case when sal>4000 then 1 else null end) "sal>4000",
 13                   count(case when sal <4000 then 1 else null end)
"sal<4000",
 14                   count(case when sal=4000 then 1 else null end) "sal=4000"
 15              from emp
 16             group by deptno
 17          )
 18*   order by deptno, x
scott@ORA920> /

    DEPTNO NAME                              TOT_CNT
---------- ------------------------------ ----------
        10 total in deptno 10                      3
        10 whose sal >4000                         1
        10 whose sal <4000                         2
        10 whose sal=4000                          0
        20 total in deptno 20                      5
        20 whose sal >4000                         0
        20 whose sal <4000                         5
        20 whose sal=4000                          0
        30 total in deptno 30                      6
        30 whose sal >4000                         0
        30 whose sal <4000                         6
        30 whose sal=4000                          0

12 rows selected.

      Rating: 5
grouping sets
      or rollup  November 11, 2003
Reviewer:  igor
       from ME USA
Tom, you wrote absolutely correctly:

and in 9i, recognizing the need for this, they added grouping sets:

scott@ORA920> select job, deptno, count(*)
  2    from emp
  3   group by grouping sets ( (job,deptno), () )
  4  /

JOB           DEPTNO   COUNT(*)
--------- ---------- ----------
CLERK             10          1
CLERK             20          2
CLERK             30          1
ANALYST           20          2
MANAGER           10          1
MANAGER           20          1
MANAGER           30          1
SALESMAN          30          4
PRESIDENT         10          1
                             14

10 rows selected.

I just want to add that grouping sets are not needed for this particular case -
in 9i rollup still does this same job:

SQL> select job, deptno, count(*)
  2  from emp
  3  group by rollup( (job, deptno) );

JOB           DEPTNO   COUNT(*)
--------- ---------- ----------
CLERK             10          1
CLERK             20          2
CLERK             30          1
ANALYST           20          2
MANAGER           10          1
MANAGER           20          1
MANAGER           30          1
SALESMAN          30          4
PRESIDENT         10          1
                             14

10 rows selected.

      Rating: 5
how do this
      8.1.7.4  November 24, 2003
Reviewer:  umesh
       from bangalore India
SQL> select * from
 2  ( select deptno, sum(sal) from emp where deptno=30 group by deptno),
 3  ( select deptno, sum(sal) from emp where deptno=20 group by deptno);

    DEPTNO   SUM(SAL)     DEPTNO   SUM(SAL)
---------- ---------- ---------- ----------
        30       9400         20      10875

gives me values as expected in unpivoting

  1  select * from
  2  ( select deptno, sum(sal) from emp where deptno=40 group by deptno),
  3* ( select deptno, sum(sal) from emp where deptno=20 group by deptno)
SQL> /

no rows selected

But this gives no rows as one of the queries return no rows
Tom Is there a way to overcome the limitation

          Followup:

well -- there are no "deptno=40" rows!

but anyway,  you are doing it "wrong" IMO

ops$tkyte@ORA920PC> select max( decode( deptno, 20, deptno ) ) deptno,
  2         max( decode( deptno, 20, sum_sal ) ) sal,
  3         max( decode( deptno, 30, deptno ) ) deptno,
  4         max( decode( deptno, 30, sum_sal ) ) sal
  5    from (
  6  select deptno, sum(sal) sum_sal
  7    from emp
  8   where deptno in ( 30, 20 )
  9   group by deptno
 10         )
 11  /

    DEPTNO        SAL     DEPTNO        SAL
---------- ---------- ---------- ----------
        20      10875         30       9400

ops$tkyte@ORA920PC> select max( decode( deptno, 20, deptno ) ) deptno,
  2         max( decode( deptno, 20, sum_sal ) ) sal,
  3         max( decode( deptno, 30, deptno ) ) deptno,
  4         max( decode( deptno, 30, sum_sal ) ) sal
  5    from (
  6  select deptno, sum(sal) sum_sal
  7    from emp
  8   where deptno in ( 40, 20 )
  9   group by deptno
 10         )
 11  /

    DEPTNO        SAL     DEPTNO        SAL
---------- ---------- ---------- ----------
        20      10875

ops$tkyte@ORA920PC>

      Rating: 5
Followup Decode
      Question  December 08, 2003
Reviewer:  John
       from Victoria, BC, Canada
Tom,

Perhaps you can follow up this post with a specific decode question.  I have a
table with two specific columns that I need to decode on.  If both columns are
0's, then I know it is one type of object, if the first is a 0 and the second
another number I know it is another type of object, if the first is a 1 and the
second another number I know it is a third type of object.  For example:

TableA
col1 = itemtype
col2 = parentobjid

The allowable values for itemtype are 0 and 1 only.

The allowable values for parentobjid are 0 or greater.

So, if the combination is:

0, 0 = Object Type 1
0, x = Object Type 2
1, x = Object Type 3

Where x is any number greater than 0.

How would I write a decode statement for this?  There are quite a number of
other columns in this table, but I'm needing to differentiate a resultset based
on the combination of values in these two columns.

Thanks!

          Followup:

well, case would probably be easier:

case when c1 = 0
     then case when c2 = 0
               then 1
               else 2
           end
     else 3
 end

decode can work

decode( c1, 0, decode( c2, 0, 1, 2 ), 3 )

as well

December 09, 2003 Meg -- Thanks for the question regarding "Force
      use of index even when index value in where clause is modified by a
      function", version 8.1.7

      You Asked
Tom,

I just read the article entititled 'Insisting on Indexes' on oramag's home page.
Here is a copy of some of the text from the article

"When your WHERE clause uses a function on a column, the Oracle optimizer won't
use an index to evaluate the query — unless you force the issue by using this
simple technique.

Sometimes, you can't set up a particular condition for your WHERE
                          clause without using a function — such as UPPER,
REPLACE, or                                            SUBSTR — on a column.
There's just one problem with doing so: the
function prevents the optimizer from using indexes on these columns, so your
queries take longer to run than they might with the index.

Fortunately, if the column on which you're using the function holds
character data, you can often modify your query in such a way as to force index
usage and make your query run more efficiently. This article describes the basic
technique involved and                      demonstrates how it works in a
couple of typical situations.

    A Mixed-Case Case

Before we discuss how to force index usage when functions modify columns, let's
look at why the optimizer otherwise doesn't use an index in such cases. Suppose
we wanted to search a column containing mixed-case data, such as the NAME column
in the simple ADDRESS table shown in Table 1. Because the NAME column is based
on user input, we can't rely on the capitalization to be uniformly correct. To
find addresses for everyone named John, we might well use a query containing the
UPPER function, such as the following:

SQL> select address from address where
     upper(name) like 'JOHN';

If we have issued the command "set autotrace on" before running this query, we
get the following results, which include an execution plan:

ADDRESS
--------------------
cleveland

1 row selected.
Execution Plan

----------------------------------------------
SELECT STATEMENT
TABLE ACCESS FULL ADDRESS

As you can see, the optimizer does a full scan of the ADDRESS table in this
case, rather than using the index on the NAME column. The index is unusable for
this query because it was created based on the actual values in the column, not
on the values as modified by the                         UPPER function, which
converts the characters to uppercase. The optimizer cannot compare "JOHN"
against the index entries, because there is no index entry for "JOHN" — only for
"john."

Fortunately, there is an easy workaround if you want to force the index to be
used in such a situation: you simply augment the WHERE clause with one or more
extra conditions that can be tested against the indexed values — and that narrow
down the number of rows that need to be scanned — but that don't alter the
requirements of the original SQL code, as in the following query:

SQL> select address from address where upper(name) like 'JO%'  AND (name like
'J%' or name like 'j%');

With this version of the query (and autotrace set on), we get the following
results:

ADDRESS
--------------------
cleveland

1 row selected.
Execution Plan
                                  ----------------------------------------------
SELECT STATEMENT
   CONCATENATION
      TABLE ACCESS BY INDEX ROWID ADDRESS
      INDEX RANGE SCAN ADDRESS_I
      TABLE ACCESS BY INDEX ROWID ADDRESS
      INDEX RANGE SCAN ADDRESS_I

Now, the optimizer does one range scan for each of the two statements joined by
the AND predicate in the WHERE clause — using the index, since the second
statement doesn't involve a function. After the two range scans, it concatenates
the result."

My question is, what is it in the revised statement that forces the index range
scan rather than the full table scan? The modified index is still being used in
the first part of the where clause. Does the fact that the second part of the
where clause uses an unmodified index override the index modification in the
first part? Or what?

      and we said...
In the future, you can just put  link there.

Before I address your question -- I will say that I am surprised the author
didn't even mention function based indexes.  See

http://osi.oracle.com/~tkyte/article1/index.html
where we see that you can use an index on upper(column) to solve the above
problem simply (no complex predicates)

As for your question -- there is no way to FORCE the optimizer to use an index,
we can make it so that the use of an index is possible.  That is what the author
has done here.

When they run the query:

select address
  from address
  where upper(name) like 'JO%'  -- that cannot use the index on name
    AND (name like 'J%' or name like 'j%' )  -- these can, it'll find all
                                             -- of the  J's and j's
                                             -- and then see if the upper is
                                             -- like JO

I would say that my 1st choice would definitely be:

index on t(upper(name))

that will process only things that start with jo.

my second choice would be the above -- in the above, it's going to process
JASON, to JYLL and everything in between....

Review &
      Followup

      Rating: 5
FB
      Indx  April 03, 2002
Reviewer:  Raj
       from newyork
Tom,
I am using functional indexes a lot and found them very useful. Various reports
run in against our datawarehouse format the data in different ways (like using
to_char, to_number, substr, replace etc..) We store the base data in the tables
and have functional indexes on this. Rebuilding these indexes after every load
is time consuming. also I dont want to create indexes for some twice-a-mont-run
query.

What would be your general suggestions for a warehouse where the data is
formated in different ways. Our developement team keep asking for function based
indexes but I would like to see if there are other things I can use. Is there
any other alternatives? I explored the use of MVs but since we re-load the
entire data on certain fact tables, we cant use it currently. Could you pls give
your valuable suggestion? Thanks a lot.

          Followup:

I don't understand why you cannot use MV's.  You can do full refreshes, no
problem -- you run the MV query once and query the results 1000 times.

Have you read through the data warehousing guide shipped free with the database
-- it has lots of ideas for you.

      Rating:
      3
SE  April 03, 2002
Reviewer:  Reader
       from Atlanta
However with Standard Edition the function based indexes option is not enabled.
In such cases how can we optimize the query having

UPPER(NAME) LIKE UPPER('Jo')

Thanks

          Followup:

Well, you can do a couple of things:

1) maintain a shadow column, 'UPPER_NAME'.  Have this column be maintained by a
trigger.  Create an index on this column and use "where upper_name like
upper('jo%')"

2) use the technique outlined above with the (name like 'J%' or name like 'j%' )
(using an index hint as well with bind variables perhaps)

3) use an index fast full scan in a subquery.  We can full scan a "skinny index"
much faster then a full scan of the table perhaps:

DOC>drop table t;
DOC>
DOC>
DOC>create table t as select * from all_objects;
DOC>
DOC>
DOC>create index t_idx on t(object_name) unrecoverable;
DOC>*/
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on
ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2   from t
  3   where rowid in ( select /*+ index_ffs( t t_idx ) */ rowid from t where
upper(object_name) = 'FOOBAR' )
  4  /

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=193 Card=192 Bytes=30528)
   1    0   NESTED LOOPS (Cost=193 Card=192 Bytes=30528)
   2    1     INDEX (FAST FULL SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=192
Bytes=4608)
   3    1     TABLE ACCESS (BY USER ROWID) OF 'T' (Cost=1 Card=19114
Bytes=2580390)

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

ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2   from t
  3   where upper(object_name) = 'FOOBAR'
  4  /

no rows selected

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

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

      Rating:
      5
  April 04, 2002
Reviewer:  Dave
       from Ohio
Is it worth mentioning fast full index scans? If the function to be applied to
the column was not known, or highly variable, then can we rely on Oracle to
consider this or would it need an optimizer hint?

      Rating: 4
Reduntant
      index  April 04, 2002
Reviewer:  Sivababu
       from GERMANY
Hello TOM,
       I have read about redundant indexes from this url

http://www.oracle.com/oramag/code/tips2002/index.html?033102.html
        I couldn't get What is redundant indexes?. Can you please explain me?

regards,
siva

          Followup:

if you have indexes:

create index t_idx1 on t(a,b,c)
create index t_idx2 on t(a,b)
create index t_idx3 on t(a)

it is very likely that t_idx2 and t_idx3 are not needed -- they are redudant.

      Rating:
      4
Reader  April 04, 2002
Reviewer:
      A reader
Tom,

SQL> select *
  2    from t
  3    where object_name in ( select /*+ index_ffs( t t_idx ) */ object_name
from t where
  4    upper(object_name) = 'FOOBAR' )
  5  ;

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=19 Card=993 Bytes=14
          3985)

   1    0   HASH JOIN (Cost=19 Card=993 Bytes=143985)
   2    1     VIEW OF 'VW_NSO_1' (Cost=6 Card=32 Bytes=544)
   3    2       SORT (UNIQUE) (Cost=6 Card=32 Bytes=544)
   4    3         INDEX (FAST FULL SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost
          =4 Card=32 Bytes=544)

   5    1     TABLE ACCESS (FULL) OF 'T' (Cost=6 Card=3104 Bytes=39731
          2)

Statistics
----------------------------------------------------------
          8  recursive calls
          4  db block gets
         19  consistent gets
          0  physical reads
          0  redo size
        862  bytes sent via SQL*Net to client
        314  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          4  sorts (memory)
          0  sorts (disk)
          0  rows processed

Is there a reason why you equi-joined, the rowid instead of
the object_name that I used in this query.
With rowid = got 4  db block gets+ 33  consistent gets+
                 12  physical reads
With object_name = got 4  db block gets+19 consistent gets+
                    0 physical read

Thanks

          Followup:

I emulated an index using the rowid.

You are in effect doing a self join on object_name.

Put a couple of thousand records in there.  I believe you have far too few
records in your test table.  And run the queries at least twice -- you have a
hit on the first one for the recursive SQL done to fill up the dictionary cache.

Oh -- and make sure you query a row that returns data as well -- that should
change the numbers as well.

      Rating:
      3
  April 04, 2002
Reviewer:  Tianhua
      Wu  from Atlanta, GA
if you need to set query_rewrite_integrity to values other than trusted (whcih
happened  when people use MV), you can not use FBI

          Followup:

That is not true.  To use MV's you can set the level to one of three settings
(this is a cut and paste from my book, i have a complete chapter on MV's and
FBI's as well)

o QUERY_REWRITE_INTEGRITY: This parameter controls how Oracle re-writes queries
and may be set to one of three values:

ENFORCED: Queries will be rewritten using only constraints and rules that are
enforced and guaranteed by Oracle. There are mechanisms by which we can tell
Oracle about other inferred relationships - and this would allow for more
queries to be rewritten - but since Oracle does not enforce those relationships,
it would not make use of those facts at this level.

TRUSTED: Queries will be rewritten using the constraints that are enforced by
Oracle as well as the relationships we have told Oracle about that exist in the
data but are not enforced by Oracle. For example, in our initial example we
could have created the physical table MY_ALL_OBJECTS_AGGS manually using a
parallel, nologging CREATE TABLE AS SELECT (to speed up the building of the
summary table).  We could have then created the  materialized view instructing
it to use this pre-built table instead of creating the summary table itself   If
we wish Oracle to use this pre-built table during a subsequent query rewrite, we
must specify a value of TRUSTED. This is because we want Oracle to 'trust' that
we have supplied the correct data in the pre-built table - Oracle does not
enforce that the data in that table is correct.

STALE_TOLERATED: Queries will be rewritten to use materialized views even if
Oracle knows the data contained in the materialized view is 'stale' (out of sync
with the details). This might be useful in an environment where the summary
tables are refreshed on a recurring basis, not on commit, and a slightly 'out of
sync' answer is acceptable.

You can use any of them with FBI's and ONLY need trusted or stale_tolerated if
you use a USER DEFINED (plsql code) funciton in the FBI.  Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function my_upper( p_str in
varchar2 ) return varchar2
  2  DETERMINISTIC
  3  as
  4  begin
  5          return upper(p_str);
  6  end;
  7  /
Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x varchar2(25) );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx on t(upper(x));
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx2 on
t(substr(my_upper(x),1,25));

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set query_rewrite_enabled =
true;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain;

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set
query_rewrite_integrity=enforced;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ index( t t_idx ) */ * from t where
upper(x) = 'FOO';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=14)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=1 Card=1 Bytes=14)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=1)

ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ index( t t_idx2 ) */ * from t
where substr(my_upper(x),1,25) = 'FOO';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=14)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=1 Card=1 Bytes=14)

At enforced, upper (and in fact -- any builtin) is used.  my_upper -- not
used..

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set
query_rewrite_integrity=trusted;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ index( t t_idx ) */ * from t where
upper(x) = 'FOO';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=14)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=1 Card=1 Bytes=14)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=1)

ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ index( t t_idx2 ) */ * from t
where substr(my_upper(x),1,25) = 'FOO';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=14)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=1 Card=1 Bytes=14)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX2' (NON-UNIQUE) (Cost=1 Card=1)

Now, and for the next case, my_upper index is used as well...

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set
query_rewrite_integrity=stale_tolerated;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ index( t t_idx ) */ * from t where
upper(x) = 'FOO';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=14)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=1 Card=1 Bytes=14)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=1)

ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ index( t t_idx2 ) */ * from t
where substr(my_upper(x),1,25) = 'FOO';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=14)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=1 Card=1 Bytes=14)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX2' (NON-UNIQUE) (Cost=1 Card=1)

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off

so, MV's and FBI's co-exist very nicely

      Rating: 5
Different
      behavior   April 05, 2002
Reviewer:  Sa?a
       from Belgrade, Yugoslavia
Hi Tom,

I tried your example on our DB (Oracle 8.1.7 Unix)
drop table t;

create table t as
select * from all_objects;

create index t_idx on t(object_name) unrecoverable;

select *
     from t
     where rowid in ( select /*+ index_ffs( t t_idx ) */ rowid from t where
upper(object_name) = 'FOEVENT')

for above got explain plan like:

SELECT STATEMENT Hint=CHOOSE        286           60

 HASH JOIN        286      44 K    60
  INDEX FAST FULL SCAN    T_IDX    286      6 K    4

  TABLE ACCESS FULL    T    28 K    3 M    54

and for query :
select *
     from t
     where upper(object_name) = 'FOEVENT'

got

SELECT STATEMENT Hint=CHOOSE
  TABLE ACCESS FULL    T
In first case I got again full table scan not
TABLE ACCESS (BY USER ROWID) OF 'T' like in your's case.

What's the reason for difference ?

Regards,

Sasa

          Followup:

do you have the tables all analyzed and everyting.  A hint uses the CBO, the CBO
without stats is like driving with your eyes closed.

In some cases, FULL SCANS are proper and best.

Toss a first_rows hint in there as well and see what happens.

      Rating:
      5
  April 08, 2002
Reviewer:  Tianhua
      Wu  from Atlanta, GA
Thank you for clearing that up for me!

      Rating: 4
Function Based
      Index behaves differently for queries  September 24, 2002

Reviewer:  TS  from USA
Tom,

I ran 2 different queries , both using a function based index in them, one of
them seem to use the function based index without using an hint , and the other
uses function based index only if an hint is used. Here is the steps I used :-

SQL> alter session set query_rewrite_enabled =true;

Session altered.

Query 1:-
==========
SQL> set autotrace on explain
SQL> select * from t where UPPER(email) = 'ABC@DEF.COM';

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=47)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=1 Card=1 Bytes=47

SQL> select /*+ index(T T_UPP_EMAIL_IDX) */ * from T where UPPER(email) =
'ABC@DEF.COM';

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=137)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=
      1 Bytes=137)

   2    1     INDEX (RANGE SCAN) OF 'T_UPP_EMAIL_IDX' (NON-UNI
      QUE) (Cost=1 Card=1)

Query 1, uses an hint to use the function based index.

Query 2:-
=======

SQL>select name from T2 where lower(email)='abc@def.com';

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=2 Bytes=34)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T2' (Cost=2
       Card=2 Bytes=34)

   2    1     INDEX (RANGE SCAN) OF 'T2_LWR_EML_IDX' (NON-UNIQ
      UE) (Cost=1 Card=2)

As you can see the difference between query 1 and query 2,
where a hint is required in query 1  but not in query 2  to
use the function based index. Can you pls. explain why ?

Thanks,
TS

          Followup:

T looks tiny (cost to full scan = 1)

T is probably too small to draw any reasonable conclusions from.

put 100,000 rows in t with lots of different emails and then let us know.

      Rating:
      5
  September 24, 2002
Reviewer:
      A reader
Only to remember that in Oracle standar edition 9i
Function indexes are enabled.
If you really need and can migrate you can be planning to.

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96531/ch5_edit.htm#88988
I don't have SE installed anywhere but... according to that, they are not part
of SE, only EE (although I see analytics are in 9iSE now)

      Rating: 5
Function Based
      Index behaves differently for queries  September 25, 2002

Reviewer:  TS  from USA
Tom,

Followup on my Question..

I tried running both the queries, Query1 and Query2
on production where there is a lot of rows, both of them use the function based
index with out having to use the hint.

Conclusion : As Always You Are Right.

Thanks a ton,
TS

      Rating:
      5
  November 15, 2002
Reviewer:  Ramu
       from India
Hi tom

Here Given below the query and execution plan ...I build a query with 3
tables.,I have created the index on all the tables.The table tbl_letter_dtl
having index on two columns(ORD_CD,CLI_INV_YN).
But this table is very big and not using index.
Can you  suggest me how to Insist optimizer  to use index.
How to optimise it.
====================================================
 1  select  a.cli_Cd,b.ORD_DEF_SEA_CD,c.LETTER_UNI_CD
 2  from  tbl_letter_dtl c,
 3        tbl_cust_mast a,
 4        tbl_ord_mast b
 5  where
 6        a.cust_cd=b.cust_cd  and
 7        b.ord_cd=c.ord_cd and
 8        c.cli_inv_yn =0 and
 9        b.ORD_DEF_SEA_CD=100 and
10*       a.cli_cd=1000
11  /
Execution Plan
----------------------------------------------------
  0      SELECT STATEMENT Optimizer=CHOOSE (Cost=989 Card=971 Bytes=36898)
  1    0   HASH JOIN (Cost=989 Card=971 Bytes=36898)
  2    1     HASH JOIN (Cost=601 Card=831 Bytes=20775)
  3    2       INDEX (FAST FULL SCAN) OF 'IDX_ORD_MAST1'  (NON-UNIQUE)(Cost=33
Card=831 Bytes=12465)
 4    2       INDEX (FAST FULL SCAN) OF 'IDX_CLI_cUST_CD' (NON-UNIQUE) (Cost=280
Card=157478 Bytes=1574780)
  5    1     TABLE ACCESS (FULL) OF 'TBL_LETTER_DTL' (Cost=362 Card=7558
Bytes=98254)
=================================================

Thanks

Ramu

          Followup:

How many rows in TBL_CUST_MAST and how many values of cli_cd, eg:

what does

select count(*), count(distinct cli_cd) from tbl_cust_mast;

return.

Do this too -- use an index hint to get the plan you want and then COMPARE the
performance.  You may well be very very very surprised to see that using indexes
many times is like setting "slow=true" -- an index does not mean "i will run
faster", it can just as easily mean "i will run lots slower"

      Rating: 4
9.2 Standard
      Edition does has Function-Based indexes!  November 15, 2002

Reviewer:  Mike  from Portland, OR
Even though the documentation says it's not available, I have Standard edition
for Windows NT installed and just created a function-based index.  That option
is not even in the v$option table anymore (as it was in 8i).

In 8i:
Select * from v$option where upper(parameter) like 'FUNC%';
PARAMETER               VALUE
Function-based indexes    FALSE

In 9i:
No rows returned...

      Rating: 5
Function Based
      index on dates  March 12, 2003
Reviewer:  Rory
      Concepcion  from Philippines
Hi TOm,

How do I make it work for this.

alter session set query_rewrite_enabled =true;
alter session set query_rewrite_integrity = trusted;

create table t
(name varchar2(30),
 created date);

insert into t values ('SPAWN',SYSDATE);
insert into t values ('ANGELA' SYSDATE);
commit;

create index t_idx2 on t(to_char(created,'YYYYMMDDHH24MISS'));

select * from t where
to_char(sysdate,'YYYYMMDDHH24MISS') > TO_CHAR(CREATED,'YYYYMMDDHH24MISS');

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

select * from v$version;

Oracle8i Enterprise Edition Release 8.1.7.0.0 - 64bit Production
PL/SQL Release 8.1.7.0.0 - Production
CORE    8.1.7.0.0       Production
TNS for IBM/AIX RISC System/6000: Version 8.1.7.0.0 - Developmen
NLSRTL Version 3.4.1.0.0 - Production

Is it possible to make function based indexes work with dates?

thanks a lot.

          Followup:

you need to use the CBO, you are not.

read the link above.

      Rating:
      5
HEHEHE  March 12, 2003
Reviewer:  Rory
      Concepcion  from Philippines
Hi Tom,

SOrry Tom for even disturbing you. I tried it again and it worked. And you know
why it didn't work the first time. COz I didnt analyze or gather statistics from
the table t. hehehe. I forgot. SO no stats, RBO will be used, right?
The table table was just created for this question so I'm sure there are no
stats as seen from last_analyzed column.

Thanks though. If I'm wrong please respond. thanks again.

      Rating: 5
But why
      function based index is needed here?  March 14, 2003

Reviewer:  Logan Palanisamy  from Santa Clara, CA USA

Rory,

Why use FBI and a complicated statement like
   select * from t where
   to_char(sysdate,'YYYYMMDDHH24MISS') > TO_CHAR
(CREATED,'YYYYMMDDHH24MISS');

instead of just
   select * from t where created < sysdate;

or

   select * from t where created < to_date('14-MAR-2003 08:10:10', 'DD-MON-YYYY
HH24:MI:SS');

You don't need FBI for this unless you are using it to show/test how FBI works.

Am I not understanding something?

      Rating: 1
Problem
      !!!  April 03, 2003
Reviewer:  Riaz Shahid
       from PRAL, Islamabad, Pakistan
Hello Tom !

Consider this:

cr@STARR.LHR> desc custom_be_master
 Name                    Null?    Type
 ----------------------- -------- ----------------
 COLLECTORATE_CODE       NOT NULL VARCHAR2(4)
 BE_TYPE                 NOT NULL VARCHAR2(2)
 BILLOFENTRY_NO          NOT NULL NUMBER(7)
 BILLOFENTRY_DATE        NOT NULL DATE
 ST_REG_NO                        VARCHAR2(13)
 IMPORTER_NAME                    VARCHAR2(50)
 IMPORTER_NTN                     VARCHAR2(7)
 IGM_NO                           NUMBER(7)
 TOTAL_SALESTAX                   NUMBER(13)
 CLAIM_NO                         VARCHAR2(13)
 BE_STATUS                        VARCHAR2(1)
 OFFICER_CODE                     VARCHAR2(6)
 CR_VERIFICATION                  VARCHAR2(1)
 COLL_VERIFICATION                VARCHAR2(1)
 CR_VERIFICATION_UID              VARCHAR2(10)
 COLL_VERIFICATION_UID            VARCHAR2(10)
 CR_VERIFICATION_DATE             DATE
 UPDATION_UID                     VARCHAR2(10)
 UPDATION_DATE                    DATE
 GROSS_WEIGHT                     NUMBER(15,4)
 UOM_WEIGHT                       VARCHAR2(4)
 COLL_VERIFICATION_DATE           DATE
 CASH_DATE                        DATE
 DATA_DATE                        DATE

cr@STARR.LHR> set linesize 500
cr@STARR.LHR> select count(1) from custom_be_master;

  COUNT(1)
==========
    891583

Elapsed: 00:00:02.10

Execution Plan
==========================================================
                                  0      SELECT STATEMENT Optimizer=CHOOSE
(Cost=457 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     SORT* (AGGREGATE)                                    :Q460000
   3    2       INDEX* (FAST FULL SCAN) OF 'CUS_BE_MST_UK' (UNIQUE) (C :Q460000
          ost=457 Card=891583)
   2 PARALLEL_TO_SERIAL            SELECT /*+ PIV_SSF */ SYS_OP_MSR(COUNT(*)) F
                                   ROM (SELECT 0 FROM :I."CUSTOM_BE_MAS
   3 PARALLEL_COMBINED_WITH_PARENT

Statistics
==========================================================
                                        19  recursive calls

       1697  db block gets

       3596  consistent gets

       3031  physical reads
        684  redo size
        369  bytes sent via SQL*Net to client
        425  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
          1  rows processed

cr@STARR.LHR> CREATE UNIQUE INDEX "CR"."CUS_BE_MST_UK"
  2      ON "CR"."CUSTOM_BE_MASTER"("COLLECTORATE_CODE", "BE_TYPE",
  3      "BILLOFENTRY_NO", TRUNC("BILLOFENTRY_DATE"))
  4      TABLESPACE "STARINDEXES" PCTFREE 10 INITRANS 2 MAXTRANS 255
  5      STORAGE ( INITIAL 64K NEXT 0K MINEXTENTS 1 MAXEXTENTS 2147483645
  6      PCTINCREASE 0 FREELISTS 1)
  7      PARALLEL ( DEGREE DEFAULT )
  8      LOGGING
  9      COMPRESS 3
 10
cr@STARR.LHR>
cr@STARR.LHR> ed
Wrote file afiedt.buf

  1* SELECT MIN(BILLOFENTRY_DATE)   FROM CUSTOM_BE_MASTER  WHERE
COLLECTORATE_CODE ='LAFU' and be_Type='HC'
cr@STARR.LHR> /

MIN(BILLO
=========
02-MAY-02

Elapsed: 00:00:06.91

Execution Plan
==========================================================
                                  0      SELECT STATEMENT Optimizer=CHOOSE
(Cost=1830 Card=1 Bytes=16
          )
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'CUSTOM_BE_MASTER' (Cost=1830 Card=22290
Bytes=356640)

Statistics
==========================================================
                                         0  recursive calls

         10  db block gets
      12053  consistent gets
       8869  physical reads
          0  redo size
        385  bytes sent via SQL*Net to client
        425  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

cr@STARR.LHR> ed
Wrote file afiedt.buf

  1* SELECT /*+Rule */ MIN(BILLOFENTRY_DATE)   FROM CUSTOM_BE_MASTER  WHERE
COLLECTORATE_CODE ='LAFU' and be_Type='HC'
cr@STARR.LHR> /

MIN(BILLO
=========                                                   02-MAY-02

Elapsed: 00:00:00.97

Execution Plan
==========================================================
                                  0      SELECT STATEMENT Optimizer=HINT: RULE

   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOM_BE_MASTER'
   3    2       INDEX (RANGE SCAN) OF 'CUS_BE_MST_UK' (UNIQUE)

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

cr@STARR.LHR> spo off

My Question:

Why my index is not getting used when i use CBO and is used when i use RBO ???
I am having great performace degrades due to this problem. Please advise

Riaz

          Followup:

according to the autotrace -- the cbo did the right thing here -- less LIO.  but
the PIO is what caused the runtime difference.

what is your optimizer index cost adj and optimizer index caching set to.  read

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6601251003901

      Rating: 5
Optimizer
      Settings  April 03, 2003
Reviewer:  Riaz
      Shahid  from PRAL, ISlamabad, Pakistan
Hello Tom !

Here's information u asked for:

cr@STARR.LHR> show parameter optimizer

NAME                                 TYPE    VALUE
==================================== ======= ======
optimizer_features_enable            string  8.1.7
optimizer_index_caching              integer 40
optimizer_index_cost_adj             integer 50
optimizer_max_permutations           integer 80000
optimizer_mode                       string  CHOOSE
optimizer_percent_parallel           integer 0

Regards

          Followup:

try cost adj lower, or caching higher.

      Rating:
      5
  April 10, 2003
Reviewer:
      A reader
From your article on Function based indexes:
----------
·    For the optimizer to use function based indexes, the following session or
system variables must be set:
·    QUERY_REWRITE_ENABLED=TRUE
·    QUERY_REWRITE_INTEGRITY=TRUSTED
---

On my Oracle 9.2.0.1 server I have these option

·    QUERY_REWRITE_ENABLED=FALSE
·    QUERY_REWRITE_INTEGRITY=enforced

I have a Test table with id number, user_name varchar(30); I have an function
based index (upper(user_name)) on user name column.

When I executed the following query it picked the function based index.

SQL> set autotrace traceonly

SQL> select * from test where UPPER(user_name) = 'TEST';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=100)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'TEST' (Cost=2 Ca
          rd=1 Bytes=100)

   2    1     INDEX (UNIQUE SCAN) OF 'TEST_USERNAME_IDX' (UNIQU
          E) (Cost=1 Card=912)

I never changed the query rewrite option for my session. How the optimizer
picked my function based index even though I didn’t enable query rewrite?

Thanks

          Followup:

things change....

      Rating:
      4
  April 11, 2003
Reviewer:
      A reader
oh! Then I don't need to set these parameters? In which version onwards it has
been changed?

          Followup:

Oracle9iR2 relaxed this so that the FBI on the *builtin* function may be used.
Eg, you would expect in 9iR2 to see this behaviour:

ops$tkyte@ORA920> create table t ( c1 varchar2(20), c2 varchar2(20) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( 'hello', 'world' );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace function f( p_str in varchar2 ) return
varchar2
  2  DETERMINISTIC
  3  as
  4  begin
  5          return upper(p_str);
  6  end;
  7  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index t_c1_idx on t( upper(c1) );

Index created.

ops$tkyte@ORA920> create index t_c2_idx on t( substr(f(c2),1,20) );

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_stats.set_table_stats( user, 'T', numrows =>
1000000, numblks => 100000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> alter session set query_rewrite_enabled=FALSE;

Session altered.

ops$tkyte@ORA920> alter session set query_rewrite_integrity=enforced;

Session altered.

ops$tkyte@ORA920> select * from t where upper(c1) = 'HELLO';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=10000 Bytes=240000)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=10000
Bytes=240000)
   2    1     INDEX (RANGE SCAN) OF 'T_C1_IDX' (NON-UNIQUE) (Cost=1 Card=4000)

ops$tkyte@ORA920> select * from t where substr(f(c2),1,20) = 'WORLD';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=9619 Card=10000 Bytes=240000)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=9619 Card=10000 Bytes=240000)

ops$tkyte@ORA920> alter session set query_rewrite_enabled=TRUE;

Session altered.

ops$tkyte@ORA920> alter session set query_rewrite_integrity=TRUSTED;

Session altered.

ops$tkyte@ORA920> select * from t where upper(c1) = 'HELLO';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=10000 Bytes=240000)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=10000
Bytes=240000)
   2    1     INDEX (RANGE SCAN) OF 'T_C1_IDX' (NON-UNIQUE) (Cost=1 Card=4000)

ops$tkyte@ORA920> select * from t where substr(f(c2),1,20) = 'WORLD';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=10000 Bytes=240000)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=10000
Bytes=240000)
   2    1     INDEX (RANGE SCAN) OF 'T_C2_IDX' (NON-UNIQUE) (Cost=1 Card=4000)

ops$tkyte@ORA920> set autotrace off

the fbi on upper -- used, on f(x) -- not until you have the correct settings

In 9iR1 and before, neither would use the fbi in the first case.

      Rating: 5
Look at
      this  April 15, 2003
Reviewer:  Riaz Shahid
       from PRAL, Islamabad, Pakistan
Hello Tom !

Consider this:

cr@STARR.LHR> show parameter query

query_rewrite_enabled                boolean TRUE
query_rewrite_integrity              string  enforced

create table test1 (a number);
cr@STARR.LHR> insert into test1 select 1 from dba_objects;

5061 rows created.

(and so on until a significant no. of rows generated)

  1* create index test1 on test1(to_char(a)) tablespace starindexes
cr@STARR.LHR> /

Index created.

cr@STARR.LHR> set autotrace on
cr@STARR.LHR> select count(1) from test1
  2  where to_char(a)='1';

  COUNT(1)
==========
  18138624

Elapsed: 00:00:22.13

Execution Plan
==========================================================
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=3)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'TEST1' (NON-UNIQUE) (Cost=1 Card=
          181387 Bytes=544161)

Statistics
==========================================================
         27  recursive calls
          0  db block gets
      32868  consistent gets
      32861  physical reads
          0  redo size
        370  bytes sent via SQL*Net to client
        425  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
          1  rows processed

cr@STARR.LHR> ed
Wrote file afiedt.buf

  1  select count(1) from test1
  2* where to_char(a)='1'
cr@STARR.LHR> show parameter query

NAME                                 TYPE    VALUE
==================================== ======= =========================
query_rewrite_enabled                boolean TRUE
query_rewrite_integrity              string  enforced

cr@STARR.LHR> alter session set query_rewrite_enabled=false

Session altered.

Elapsed: 00:00:00.46
cr@STARR.LHR> select count(1) from test1
  2  where to_char(a)='1'
  3  /

  COUNT(1)
==========
  18138624

Elapsed: 00:00:19.31

Execution Plan
==========================================================
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4160 Card=1 Bytes=3)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'TEST1' (Cost=4160 Card=18138624
          Bytes=54415872)

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

cr@STARR.LHR> alter session set query_rewrite_enabled=true;

Session altered.

Elapsed: 00:00:00.31
cr@STARR.LHR> select count(1) from test1
  2  where to_char(a)='1'
  3  /

  COUNT(1)
==========
  18138624

Elapsed: 00:00:22.22

Execution Plan
==========================================================
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=3)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'TEST1' (NON-UNIQUE) (Cost=1 Card=
          181387 Bytes=544161)

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

In the above example, the parameter query_rewrite_Enabled affects the optimizer
behaviour. But i can't see any change due to query_rewrite_integrity parameter
(which is set to enforced in this case).

So how can you say
"For the optimizer to use function based indexes, the following session or
system variables must be set:
·    QUERY_REWRITE_ENABLED=TRUE
·    QUERY_REWRITE_INTEGRITY=TRUSTED"

With Regarids

          Followup:

really really wish people would include versions -- why is it they never do?

so, try it with a user defined function -- like my example -- then tell us what
happens.

....
ops$tkyte@ORA817DEV> alter session set query_rewrite_enabled=TRUE;

Session altered.

ops$tkyte@ORA817DEV> alter session set query_rewrite_integrity=enforced;

Session altered.

ops$tkyte@ORA817DEV> select * from t where upper(c1) = 'HELLO';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=10000 Bytes=240000)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=10000
Bytes=240000)
   2    1     INDEX (RANGE SCAN) OF 'T_C1_IDX' (NON-UNIQUE) (Cost=1 Card=10000)

ops$tkyte@ORA817DEV> select * from t where substr(f(c2),1,20) = 'WORLD';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2879 Card=10000 Bytes=240000)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=2879 Card=10000 Bytes=240000)

ops$tkyte@ORA817DEV> set autotrace off

      Rating: 5
Function index
      for a complete calculation  September 23, 2003

Reviewer:  Yong Wu  from CA, USA
I have a question regarding to calculate distance for a give post code
to get all other post code within certial distance.

The Table structure likes

postal_data(
POSTALCODE     Not NULL VARCHAR2(10)
STATE          NOT NULL CHAR(2)
LONGITUDE      FLOAT(126)
LATITUDE       FLOAT(126))

This table has 820000 records.

A query need to get all postal code within 30 miles from this point

Longtitude: -87.755811
Latitude:   41.715508

I was given the following query. The performance is realy slow since the query
was contructed in a way that index cannot be used.
Is it possible to create a function index for this query?

Select state,POSTALCODE,
round(69.1 * SQRT(POWER(LATITUDE  - (41.715508), 2)
+ 0.6* POWER(LONGITUDE - (-87.755811), 2)),2) Miles
from postal_data
where (round(69.1 * SQRT(POWER(LATITUDE -(41.715508), 2)
+ 0.6 * POWER(LONGITUDE - (-87.755811), 2)),2) ) < 30
Order by round(69.1 * SQRT(POWER(LATITUDE -(41.715508), 2)
+ 0.6 * POWER(LONGITUDE - (-87.755811), 2)),2) ASC

This site(
http://tutorial153.easycfm.com/
 shows another way to calculate it:

SELECT zipcode, latitude, longitude, statename, city,
    ROUND((ACOS((SIN(#passedzip.latitude#/57.2958) * SIN(latitude/57.2958)) +
    (COS(#passedzip.latitude#/57.2958) * COS(latitude/57.2958) *
    COS(longitude/57.2958 - #passedzip.longitude#/57.2958))))
    * 3963) AS distance
    FROM zipcodes
    WHERE (latitude >= #passedzip.latitude# - (#passedradius#/111))
    And (latitude <= #passedzip.latitude# + (#passedradius#/111))
    AND (longitude >= #passedzip.longitude# - (#passedradius#/111))
    AND (longitude <= #passedzip.longitude# + (#passedradius#/111))
    ORDER BY distance

This query seems much better than the previous one.
But this query give different results. I don't know which query is correct.

I know Oracle has spacial data function to do this.
But it is not as easy as a simple query.

I would like to know what is the best way to get this done.

Thanks,

          Followup:

the best way ?

index the data.
use spatial.

http://otn.oracle.com/oramag/oracle/03-may/o33asktom.html

      Rating: 4
Follow up to
      redundant indexes  September 23, 2003
Reviewer:
      Luis C L Nobre  from Brasil
Hi, Mr.Kyte.

Do we also have redundant indexes in the examples of the following link? Or, as
you usually say, am I comparing oranges to apples?

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5671539468597
Thanks again.

          Followup:

there were lots of examples on that link

      Rating: 5
index not used
      when add ' '  September 23, 2003
Reviewer:
      A reader
Hi

We have some queries in oracle 7.3.4 with very strange behaviours.

For example a query such as

variable b1 varchar2(10)
exec :b1 := '0765'

select *
  from a
 where a_col1 = :b1

It takes ages to run, whereas if I use

variable b1 varchar2(10)
exec :b1 := 0765

select *
  from a
 where a_col1 = :b1

It runs instanenously.

If I do

set autotrace traceonly exp

both '0765' and 0765 have same explain plan....

Why these behaviours...? Optimizer bug? We cannot migrate our Oracle version
because our Vantive version is only certified on 7.3.4

          Followup:

let me see the tkprof output for these two queries.  run:

alter session set sql_trace=true;
select * from FIRST a where a_col1 = :b1;
select * from SECOND a where a_col1 = :b1;
exit

run tkprof on the trace file and lets see what it says.

      Rating: 4
here,
      tkprof  September 24, 2003
Reviewer:
      A reader
Hi

tkprof for the slow query (I have NOT let it finish since it takes over 2 hours
already!)

using '0254' for variable :b1

SELECT run.mru_msefa_ins_valor_generico codigo
  FROM tgcprt10.msrun_rel_unicidades run
 WHERE run.mspar_id_parametro = 'IDVPN'
   AND run.msefa_ins_valor_generico = :b1
   AND run.mru_msfam_id_familia = 'EQ'
   AND run.mru_mscom_id_componente = 'RO'
   AND run.mru_mspar_id_parametro = 'CODIGO'
   AND NOT EXISTS (
          SELECT efa.msefa_ins_valor_generico
            FROM tgcprt10.msefa_elem_fase efa
           WHERE efa.msfam_id_familia = 'EQ'
             AND efa.mscom_id_componente = 'RO'
             AND efa.mspar_id_parametro = 'CODIGO'
             AND efa.msval_id_valor = 'CODIGO'
             AND run.mru_msefa_ins_valor_generico LIKE
efa.msefa_ins_valor_generico || '%')

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          0          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 86  (TTD$ORACLE7)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'PK_MSRUN_REL_UNICIDADES'
              (UNIQUE)
      0    TABLE ACCESS   GOAL: ANALYZED (BY ROWID) OF 'MSEFA_ELEM_FASE'
      0     INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'MSEFA_ELEM_FASE_FK1'
                (NON-UNIQUE)

tkprof for the fast query using 0254 for variable :b1

SELECT run.mru_msefa_ins_valor_generico codigo
  FROM tgcprt10.msrun_rel_unicidades run
 WHERE run.mspar_id_parametro = 'IDVPN'
   AND run.msefa_ins_valor_generico = :b1
   AND run.mru_msfam_id_familia = 'EQ'
   AND run.mru_mscom_id_componente = 'RO'
   AND run.mru_mspar_id_parametro = 'CODIGO'
   AND NOT EXISTS (
          SELECT efa.msefa_ins_valor_generico
            FROM tgcprt10.msefa_elem_fase efa
           WHERE efa.msfam_id_familia = 'EQ'
             AND efa.mscom_id_componente = 'RO'
             AND efa.mspar_id_parametro = 'CODIGO'
             AND efa.msval_id_valor = 'CODIGO'
             AND run.mru_msefa_ins_valor_generico LIKE
efa.msefa_ins_valor_generico || '%')

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          3          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          3          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 86  (TTD$ORACLE7)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'PK_MSRUN_REL_UNICIDADES'
              (UNIQUE)
      0    TABLE ACCESS   GOAL: ANALYZED (BY ROWID) OF 'MSEFA_ELEM_FASE'
      0     INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'MSEFA_ELEM_FASE_FK1'
                (NON-UNIQUE)

same explain plan!

the first query suffers TONs of buffer busy waits, I think the problem is

AND run.mru_msefa_ins_valor_generico LIKE efa.msefa_ins_valor_generico || '%'

if I get rid of that condition query runs very fast with any variable

run.mru_msefa_ins_valor_generico is varchar2(100)
efa.msefa_ins_valor_generico is varchar2(2000)

cheers

          Followup:

can you do a 10046 level 12 trace (search for 10046 on this site) and post the
bind variable sections of the two runs from the RAW trace file.

      Rating: 5
different
      values are binded  September 24, 2003
Reviewer:
      A reader
Hi

SLOW QUERY:
==============
BINDS #1:
 bind 0: dty=1 mxl=128(100) mal=00 scl=00 pre=00 oacflg=01
   bfp=0086f5a4 bln=100 avl=04 flg=05
   value="0254"

FAST QUERY
==============
BINDS #1:
 bind 0: dty=1 mxl=128(100) mal=00 scl=00 pre=00 oacflg=01
   bfp=0086d5c4 bln=100 avl=03 flg=05
   value="254"

what can this be?

          Followup:

ahh, you are comparing apples and oranges -- I should have seen that.

when you assigned '0254' -- the zero was there.

when you assigned 0254 as a number that is '245'

so, you are comparing two different queries

where x = '0254'
where x = '254'

the answers would be different, they cannot be compared.

      Rating: 4
Function index
      not being used  October 23, 2003
Reviewer:
      TS  from USA
Tom,

I 've a query below:-

select  a.ID,a.EMAIL,a.NAME
from table1 a, table2 b
where a.ID = b.ID
AND   (LOWER(a.NAME) like '%sometext'
OR   LOWER(b.NAME) like '%sometext' )
order by ID

both of the columns using the LOWER function has
function indexes. If I take the '%sometext' and reverse
it to 'sometext%' then function indexes are used. whereas
if i do it as '%sometext', function indexes are ignored.
Is there anyway i could force the function indexes to be
used in this case?

Thanks,
TS

          Followup:

it has to inspect EVERY ROW with a leading percent.  EVERY ROW.

that is why the index is ignored.

      Rating: 4
function index
      not being used when used with AND /OR operator  October 24,
      2003
Reviewer:  TS  from USA
Tom,

I 've a query like this:-

select a.ID,a.EMAIL,a.NAME from table1 a, table2 b
where (a.ID = b.ID)
AND ( (  LOWER(a.EMAIL) = 'abc@def.com' )
OR (  LOWER(b.EMAIL) = 'abc@def.com'))
order by ID

The query was doing a full table scan, so I created function indexes on
LOWER(EMAIL) for both tables 1 and 2 and re-analyzed the tables along with
indexes. When I run the query again, it doesn't seem to use the function
indexes. I replaced  "OR"  with an "AND" and the query uses  function index for
both the columns. Do I need to do something to make the query use  function
index  with the OR operator in it?

Thanks,
TS

          Followup:

tell me the logic you would envision it using with the OR.

Say it used the index on t1 to find all rows with abc@def.com.
Then it used the index on t2.

Then it "joined" them.  Would that be the right answer?  (rhetorical q, no, it
would not)

It has to JOIN and then COMPARE.  It cannot select and then JOIN.  It is not
semantically the "same thing".  See:

ops$tkyte@ORA920> set autotrace on explain
ops$tkyte@ORA920> select a.ID,a.EMAIL,a.NAME from t1 a, t2 b
  2  where (a.ID = b.ID)
  3  AND ( (  LOWER(a.EMAIL) = 'abc@def.com' )
  4        OR
  5            (  LOWER(b.EMAIL) = 'abc@def.com')
  6          )
  7  order by ID
  8  /

        ID EMAIL                          NAME
---------- ------------------------------ ------------------------------
         1 abc@def.com                    x
         2 Xabc@def.com                   x
         3 abc@def.com                    x

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2123 Card=1990 Bytes=173130)
   1    0   SORT (ORDER BY) (Cost=2123 Card=1990 Bytes=173130)
   2    1     HASH JOIN (Cost=2095 Card=1990 Bytes=173130)
   3    2       TABLE ACCESS (FULL) OF 'T2' (Cost=963 Card=100000 Bytes=3000000)
  4    2       TABLE ACCESS (FULL) OF 'T1' (Cost=963 Card=100000 Bytes=5700000)

ops$tkyte@ORA920>
ops$tkyte@ORA920> select a.id, a.email, a.name
  2    from ( select * from t1 where lower(email) = 'abc@def.com' ) A,
  3         ( select * from t2 where lower(email) = 'abc@def.com' ) B
  4   where (a.ID = b.ID)
  5   order by id
  6  /

        ID EMAIL                          NAME
---------- ------------------------------ ------------------------------
         3 abc@def.com                    x

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=20 Card=1000 Bytes=87000)
   1    0   SORT (ORDER BY) (Cost=20 Card=1000 Bytes=87000)
   2    1     HASH JOIN (Cost=5 Card=1000 Bytes=87000)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'T2' (Cost=2 Card=1000
Bytes=30000)
   4    3         INDEX (RANGE SCAN) OF 'T2_IDX' (NON-UNIQUE) (Cost=1 Card=400)
   5    2       TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=2 Card=1000
Bytes=57000)
   6    5         INDEX (RANGE SCAN) OF 'T1_IDX' (NON-UNIQUE) (Cost=1 Card=400)

ops$tkyte@ORA920> set autotrace off

The join with an OR across the tables precludes the indexes.  Perhaps what you
might want is:

ops$tkyte@ORA920> select a.id, a.email, a.name
  2    from ( select * from t1 where lower(email) = 'abc@def.com' ) A,
  3         t2 b
  4   where (a.ID = b.ID)
  5   union
  6  select a.id, a.email, a.name
  7    from t1 a,
  8         ( select * from t2 where lower(email) = 'abc@def.com' ) B
  9   where (a.ID = b.ID)
 10   order by id
 11  /

        ID EMAIL                          NAME
---------- ------------------------------ ------------------------------
         1 abc@def.com                    x
         2 Xabc@def.com                   x
         3 abc@def.com                    x

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=894 Card=2000 Bytes=157000)
   1    0   SORT (UNIQUE) (Cost=866 Card=2000 Bytes=157000)
   2    1     UNION-ALL
   3    2       NESTED LOOPS (Cost=2 Card=1000 Bytes=70000)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=2 Card=1000
Bytes=57000)
   5    4           INDEX (RANGE SCAN) OF 'T1_IDX' (NON-UNIQUE) (Cost=1
Card=400)
   6    3         INDEX (UNIQUE SCAN) OF 'SYS_C007109' (UNIQUE)
   7    2       MERGE JOIN (Cost=836 Card=1000 Bytes=87000)
   8    7         TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=826 Card=100000
Bytes=5700000)
   9    8           INDEX (FULL SCAN) OF 'SYS_C007108' (UNIQUE) (Cost=26
Card=100000)
  10    7         SORT (JOIN) (Cost=10 Card=1000 Bytes=30000)
  11   10           TABLE ACCESS (BY INDEX ROWID) OF 'T2' (Cost=2 Card=1000
Bytes=30000)
  12   11             INDEX (RANGE SCAN) OF 'T2_IDX' (NON-UNIQUE) (Cost=1
Card=100)

as long as you are aware the UNION "sort/distincts" the data -- that is, de-dups
it (so it is NOT the same as your original query, but might be what you want)

      Rating: 5
Query picks up
      function index  October 27, 2003
Reviewer:
      TS  from USA
Tom,

I rewrote my query as per your example
and it picks up the function index and also
executes much faster.

Thanks a ton,
TS

      Rating: 3
Impact of Like
      and Not Like in the where clause  December 08, 2003

Reviewer:  BK  from NJ USA
Hi Tom,

I've a varchar2 column in the table that stores numeric as well as varchar2 data
(not my design). I'm running a query against this table and I need rows where
this field has numeric value. I tried using like, not like but none of the
option is using index and does full table scan. Question is is there a way to
make this query run faster, how does like and not like clause, <,>,between
evaluated by the optimizer.

select count(ps.party_id)
from hz_party_sites ps, hz_parties p
where p.party_id = ps.PARTY_ID
and p.ORIG_SYSTEM_REFERENCE < 'A%'

both these tables have more than 1.6 million records.

explain plan shows hash join and full table scan of both the tables. Please help
me understand how optimizer will evaluate these operators. Many Thanks.

          Followup:

tell us, what is

select count(*),
       count( case when orig_system_reference < 'A%' then 1 end )
  from hz_parties;

      Rating: 3
This gives the
      count but scans the entire table  December 09, 2003

Reviewer:  BK  from NJ USA
Hi, Thanks for showing one more way of counting. Question is how to return
numeric value stored in varchar2 field containing numeric as well as character
data without doing a full table scan.

More importantly if you throw some light or give some reference on how optimizer
will evaluate like and NOT like clauses. Does 'Not like' clause has to perform a
full table scan. Is it beeter to use <,> or between. Please pardon my ignorance
if this sounds too trivial. Thanks.

December 09, 2003 Todd -- Thanks for the question regarding
      "DBMS_STATS cascade option", version 8.1.7

      You Asked
Hi Tom,

Great site and a great book.  I look forward to the next book.

I would like to use monitoring and DBMS_STATS.GATHER_DATABASE_STATS with the
GATHER STALE option, which I have read here and seems to be a good idea.
My question is: if I use cascade => 'TRUE', do the indexes get analyzed with the
same estimate_percent as used for the table?
I would assume so, but I have read somewhere that it is generally better to use
'compute' on the indexes so the stats are more accurate.
Can you comment on this and whether if an OLTP system vs DSS enters into the
decision?

Thank you.

      and we said...
Well, I actually prefer compute myself unless the objects are truly not
analyzable in your time window...

Also, in 9i -- there is a new "AUTO_SAMPLE_SIZE" by which dbms_stats will
determine based on the object what the sample size should be (and the sample
sizes will vary by segment and even by columns within segments)

I find statistics are infinitely more important in true DSS then in true OLTP (i
use the word 'true').  OLTP queries (true ones) are defined by being pretty darn
simple queries (lots of keyed reads -- small result sets).  DSS on the other
hand -- infinitely more complex.

The problem is -- I've never seen a true OLTP system ;)  they all have
characteristics of DSS in them.
Review &
      Followup

      Rating: 5
test
      case  December 05, 2003
Reviewer:  David
       from Nashville, TN USA
Tom,

When we collect stats with DBMS_STATS.GATHER_TABLE_STATS with CASCADE = TRUE,
both the table and index are analyzed, but the sample_size (in dba_indexes) is
like 1%; which is not enough to get my query to use the index.  Thus,
performance is a dog.

However, I when I use "ANALYZE INDEX index_name COMPUTE STATISTICS" the
sample_size equals the number of rows, my index is used, and performance is
awesome.

In short, it does not seem possible to get DBMS_STATS to use a large sample
size. I tried both DBMS_STATS.GATHER_TABLE_STATS with CACADE of TRUE, and
DBMS_STATS.GATHER_INDEX_STATS with estimate_percent = 100 to no avail.  ANALYZE
works fine.  What gives?

Thanks!

          Followup:

this is a test case -- it shows I do not see what you see.  so, maybe you can
develop a test case like this to show us what you see??

big_table@ORA920> analyze table big_table delete statistics;
 Table analyzed.

big_table@ORA920> select table_name, index_name, sample_size from user_indexes
where table_name = 'BIG_TABLE';

TABLE_NAME                     INDEX_NAME                     SAMPLE_SIZE
------------------------------ ------------------------------ -----------
BIG_TABLE                      BIG_TABLE_ID
BIG_TABLE                      BIG_TABLE_OBJECT_ID_IDX
BIG_TABLE                      OBJECT_TYPE_IDX

big_table@ORA920> exec dbms_stats.gather_table_stats( user, 'BIG_TABLE',
method_opt=>null, cascade=>true );

PL/SQL procedure successfully completed.

big_table@ORA920> select table_name, index_name, sample_size from user_indexes
where table_name = 'BIG_TABLE';

TABLE_NAME                     INDEX_NAME                     SAMPLE_SIZE
------------------------------ ------------------------------ -----------
BIG_TABLE                      BIG_TABLE_ID                       1833792
BIG_TABLE                      BIG_TABLE_OBJECT_ID_IDX            1833792
BIG_TABLE                      OBJECT_TYPE_IDX                    1833791

big_table@ORA920> analyze table big_table delete statistics;

Table analyzed.

big_table@ORA920> select table_name, index_name, sample_size from user_indexes
where table_name = 'BIG_TABLE';

TABLE_NAME                     INDEX_NAME                     SAMPLE_SIZE
------------------------------ ------------------------------ -----------
BIG_TABLE                      BIG_TABLE_ID
BIG_TABLE                      BIG_TABLE_OBJECT_ID_IDX
BIG_TABLE                      OBJECT_TYPE_IDX

big_table@ORA920> analyze table big_table compute statistics for table for all
indexes;

Table analyzed.

big_table@ORA920> select table_name, index_name, sample_size from user_indexes
where table_name = 'BIG_TABLE';

TABLE_NAME                     INDEX_NAME                     SAMPLE_SIZE
------------------------------ ------------------------------ -----------
BIG_TABLE                      BIG_TABLE_ID                       1833792
BIG_TABLE                      BIG_TABLE_OBJECT_ID_IDX            1833792
BIG_TABLE                      OBJECT_TYPE_IDX                    1833791

i observed no difference?

9203 on linux (versions and all always relevant)

      Rating: 5
my test
      case  December 08, 2003
Reviewer:  David
       from Nashville, TN USA
I followed your example, but I still see a difference (results follow).  We are
on 9.2.0.4.0, on Solaris O/S.

+++++++++++++++++++++++++++++++++++++++++++++++++++++

SQL> analyze table student_ada delete statistics;

Table analyzed.

SQL> select table_name, index_name, sample_size from user_indexes
  2  where table_name = 'STUDENT_ADA';

TABLE_NAME                     INDEX_NAME                     SAMPLE_SIZE
------------------------------ ------------------------------ -----------
STUDENT_ADA                    STUDENT_ADA$ISP_ID

SQL> exec dbms_stats.gather_table_stats('XYZ','STUDENT_ADA',method_opt=>null,
cascade=>true);

PL/SQL procedure successfully completed.

SQL> select table_name, index_name, sample_size from user_indexes
  2  where table_name = 'STUDENT_ADA';

TABLE_NAME                     INDEX_NAME                     SAMPLE_SIZE
------------------------------ ------------------------------ -----------
STUDENT_ADA                    STUDENT_ADA$ISP_ID                  324262

SQL> analyze table student_ada compute statistics for table for all indexes;

Table analyzed.

SQL> select table_name, index_name, sample_size from user_indexes
  2  where table_name = 'STUDENT_ADA';

TABLE_NAME                     INDEX_NAME                     SAMPLE_SIZE
------------------------------ ------------------------------ -----------
STUDENT_ADA                    STUDENT_ADA$ISP_ID                15270621

SQL> spool off

          Followup:

I'll have to refer you to support -- I cannot reproduce

ops$tkyte@ORA9IR2> begin
  2     dbms_stats.gather_table_stats
  3     ( ownname    => user,
  4       tabname    => 'BIG_TABLE',
  5       method_opt => 'for all indexed columns',
  6       cascade    => TRUE );
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> select count(*) from big_table;

  COUNT(*)
----------
  15000000

ops$tkyte@ORA9IR2> select table_name, index_name, sample_size from user_indexes
where table_name = 'BIG_TABLE';  2

TABLE_NAME                     INDEX_NAME                     SAMPLE_SIZE
------------------------------ ------------------------------ -----------
BIG_TABLE                      BIG_TABLE_PK                      15000000

ops$tkyte@ORA9IR2> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
PL/SQL Release 9.2.0.4.0 - Production
CORE    9.2.0.3.0       Production
TNS for Linux: Version 9.2.0.4.0 - Production
NLSRTL Version 9.2.0.4.0 - Production

ops$tkyte@ORA9IR2> analyze table big_table delete statistics;

Table analyzed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select table_name, index_name, sample_size from user_indexes
where table_name = 'BIG_TABLE';

TABLE_NAME                     INDEX_NAME                     SAMPLE_SIZE
------------------------------ ------------------------------ -----------
BIG_TABLE                      BIG_TABLE_PK

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec dbms_stats.gather_table_stats( user, 'BIG_TABLE',
method_opt=>null, cascade=>true );

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select table_name, index_name, sample_size from user_indexes
where table_name = 'BIG_TABLE';

TABLE_NAME                     INDEX_NAME                     SAMPLE_SIZE
------------------------------ ------------------------------ -----------
BIG_TABLE                      BIG_TABLE_PK                      15000000

      Rating: 5
add-on to my
      last post  December 08, 2003
Reviewer:
      David  from Nashville, TN USA
I realized that I hadn't deleted statistics between the two steps so I did it
again and deleted this time, but the result was the same.

December 09, 2003 -- Thanks for the question regarding "View
      tuning", version Oracle 8.1.6.3.4

      You Asked
I am working on the implementation of a COTS application that uses views(>
1,500) very heavily.  There are multiple layers of views on views on very large
tables.  These tables will have millions of rows each.  The views are joined to
other views.  In some cases three or four are joined with some very complex
logic on top of the view joins. Do you have tuning information/recommendations
for this.

      and we said...
See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1353453813863
...

a tool you will use is

http://asktom.oracle.com/~tkyte/tkprof.html
Just make sure the views you are using are designed to answer the questions you
are asking.  What I find is people use a generic set of views that are
inadequate to answer the questions being asked.  You should not be limited to
using views of views of views if using a single view against the base table
makes the most sense and performs better...  On the other hand, there is no
reason why views of views of views of views cannot perform excellently.
Review &
      Followup

      Rating: 5
What about
      "structural Views" (SV) [my term]  October 22, 2003

Reviewer:  Kirk Wolak  from Boca Raton, FL USA

Tom,
  We ended up with views having 300 columns after
doing all of the GENERIC USEFUL JOINS we need.

  While this reduces are views, and denormalizes
the "view" of the data for us, ther performance STINKS.

  So, based on your continuous comment
(Views are just stored queries), I was thinking we should
create SVs, that do the JOINS, and return the 80% hard
columns the joins produce (Company_ID -> Company_Name),
and then join that view to the table we needed the
boatload of data from, but usually less than the total
200 fields it contains.

Currently we use:
  Select * from VW_Inventory_Vendor_ControlInfo
  -- where date ranges, etc.

--  The VW tells me it returns 200 columns from Inventory
--  and the required columns from Vendor and ControlInfo

We are wondering if we use:
  Select x,y,z...
  from SV_Inventory_Vendor_ControlInfo SV, TBL_Inventory T
  where t.ID = SV.ID
  -- and in the date ranges, etc.

We are trying to get CLOSER to the best of both worlds.

1) Views that contain the complexities of the joins to get
to the real data the user wants to see without having it
spread through our software.

2) Performance MUCH closer to a custom query.

  In summary does a well-optimized Structural View
make sense if you are expecting to consistently join
it against the primary table to get the fields you need
on a query  by query basis (programmers choose), versus
just having 10 or so similar views for each view, and
having to maintain all of them?

          Followup:

confused -- what is the point of the view here if you have to rejoin yet again.

also, why does performance "stink"  -- if the end user does not select out the N
extra columns, we will not get them.

i don't understand the "sv" if I have to join to the tables yet again (that
would be slower) -- meaning the "sv" hasn't hiddne any of the complexity from me

      Rating: 4
Reasoning for
      rejoining the table, why were views used...  October 24,
      2003
Reviewer:  Kirk Wolak  from Boca Raton, FL USA

Tom,
  Things seem to make sense to 1/2 the audience :-)

  Anyways, we CURRENTLY use the views to PROTECT the programmers from having to
do the complex joins everywhere,
and so that the data they TEND to need is readily at hand.

  This CAUSES a problem...  Too many columns in the views,
resulting in POOR Perforamnce...  Made worse by programmers
who use Select * vs. Select x1..x192 when they don't need
all 300 fields.

  So, the thought of Structural Views was to have the
RELATIONSHIPS between tables in the view, but the field
set to be THRUST back onto the developer to specify the
actualy fields they need.

  The finaly question becomes what is worse for perforamnce,
1) Having views with so many columns than ANY select from
   them pays a performance (DISK writing) penalty
2) Having the JOIN ONLY portion of the view, such that
   you join back to the MAIN Table for the NUMEROUS Fields,
   making the View fast, but COUNTING on the fact that
   the view JOINED with one of the original tables again,
   would be handled MORE EFFICIENTLY (Maybe?) than it
   handling all of that data.

Problem: We updated our standard views based on MONTHS
of user requests for additional information to be there.
Now, we CREATED a performance penalty.

Solutions:
1) Create a veiw for EACH "UI View" of the data
   (5-20 times more views)
2) Have the progrmmers code the VIEW Logic (Joins) and
   not use the views except for reference on what tables
   to join, or when that smaller view makes sense.
   (More maintenence at the programmers level)
3) Combine the two (Structural Views) Where the join
   logic is in the view, but then in 50% of the cases
   when the information is not there, JOIN that table
   back in and get the columns.
   (Is this the Best or worse of 2 worlds???)

Question:
How much of a performance penalty would one pay to:

Select x1..x99
from View_Customer_Invoice v, Customer c
where c.rowid = v.cust_rowid
and   c.ControllerID = :ControllerID -- From app

when View_Customer_InvoiceTotals =
Select c1..c3,c.rowid as cust_rowid, s.Person, I1..I5
From Customer c, SalesPerson s, Invoice I
where I.CustID = c.ID
and   c.SalesPersonID = s.ID
and   c.IsActive = 1
and   c.IsSomething = 1
and   I.IsValid = 1

---
Would it make more sense to join back by ROWID or
by ID (Primary Key), I would assume the former.

But my expectation would be that when Oracle sees
this selection from the view, it will "REASSEMBLE IT",
and do the right thing? (ROOT OF MY QUESTION).
(Right thing is FAST=TRUE<g>)

And EVEN IF IT DID NOT reassemble it, the mere fact
that we do not have so many columns being handled,
would cut down on the (SOMETIMES) 3 second parsing...

I admit, WE WERE BAD, we went to the extreme to get
ONE VIEW for Customer that had all of the "HANDY"
information.  We have sinned.  I now know that we
should have choosen our views more wisely, and NOT
abused ONE view so badly.

But the questions are:
1) Would we pay a price for REJOINING and forcing the
   columns to be specified then?
2) Are we being wrong-headed AGAIN, and should we just
   have 5-10 views according to the VIEW that is needed
   by the UI/report, etc.?
3) Are we being LAZY and we just need to Join our tables
   as the view does not have what we need?

I have asked the same 3 questions in different ways, in
hopes that you will be able to INTERPRET them to clarify
what is happening, and where we are tring to GROW.

I feel our views have become DUMPING grounds for columns
from every possible table so as to make it trivial for
the programmers to get what they need.  And views that
query these views (and carry forth their HUNDREDS of columns
become slower and slower).

We were wrong, and want to learn the RIGHT way.

Please Help.

Kirk out!

          Followup:

my answer to:

The finaly question becomes what is worse for perforamnce,

is simple -- "doing things to protect programmers, who are paid to write code
for money and do it right, is the worst thing you can do for performance"

In my world -- views are implemented for single use things.  When I build a
view, it is for an application.  I tend to not use a generic view for
everything.  I create a view much like people create subroutines -- for a
PURPOSE.

So, i will pick neither of 1 or 2 from your first list -- i think they are both
wrong.

there are no quanitative numbers one can throw on your questions.  There are too
many variables.  For example, would the exclusion of some columns allow us to
skip some table access by rowid steps?  if so, big difference, if not, small
difference.  thats just one case.  does the inclusion of some columns cause us
to exceed sort areas and swap to disk?  and so on.

To me, the right way is "you create a view for a purpose".  You NEVER "protect"
programmers, they are supposed to be professionals, not children in need of
protection.

      Rating: 5
Optimizer Hints
      in Views  October 24, 2003
Reviewer:  Doug
      Wingate  from New Orleans, LA, USA
I had an experience in performance analysis a few months ago that I think is
relevant here.  I was asked to look for ways to bring down LIO and PIO in a
third-party application.  I was surprised to discover that a number of the
application's views had hints, including the ORDERED hint, built right into
them.  One view, used for somewhat adhoc reporting, joined approximately 7
tables and the optimizer was forced to join the tables in the same order every
time, no matter what filtering conditions were applied by the query on the view.
 We ended up reducing LIO and PIO by other means, but we came close to
intervening in the third-party application by disabling the hints in this view.
If anyone has any doubt about the matter, please know that after that experience
I come down on the side of keeping hints out of views and confining them to
queries.

      Rating: 4
Protecting
      Programmers  October 25, 2003
Reviewer:  Guy
       from Manchester, England
I agree with Tom, programmers don't need protection and should be able to read a
data / schema design entity diagram to do their job, esp. if there Oracle
programmers!

However, if you do have inexperienced (in oracle) client side programmers on
your project, then I found that rather that developing a generic "meta layer" of
views to try to cover all potential queries, I would design (or oversee the
design and development) of PLSQL helper packages for the main functions / table
/ entities, including functions that return cursor refs, add, update and delete
functions.  This also prevents the programmers using CURSOR FOR loops and SELECT
SYSTIME FROM DUAL with abandon!

This gives client side programmers the tools do their jobs (create click buttons
and forms and stuff) with you (DBA and/or Oracle PLSQL developer) able to
control the inner workings.

Just my 20/20 vision...

          Followup:

I concurr -- i think we need a triumvirate really

o interface programmers.  they love the gui and doing cool things on screen.

o database programmers.  they understand how the database works, what
transactions are all about, how to work with data.

o dba's

and in many cases, the database programmers and dba's can actually be one in the
same.

interface programmers that try to do database stuff are many times in so far
over their head.  i see it all of the time.  they think "its all about the gui,
the application".  They have it backwards (IMHO)

      Rating: 5
Protecting
      Programmers (Views)  October 28, 2003
Reviewer:
      Kirk Wolak  from Boca Raton, FL USA
Tom and Everyone Else...

Great answers...  Even beyond "most useful"!
This is something I can explain to the developers,
and everyone else...  Furthermore, it makes good
sense.

Of course, now we will have to revamp our view
naming process...

But this puts us on the right track!  And I SORT feel
STUPID for asking the question.  But WHAT a revelation
to think of Views as "like" Sub-routines...  It even
EXPAINS why you would not create ONE BIG ONE that does
everything!

That comment is WORTH the subscription fee for this
site a thousand times over :-))))))

Thanks so much!

Kirk out!

      Rating: 4
What's your
      opinion?  November 17, 2003
Reviewer:
      Michael  from Germany
Hi Tom,

on the OTN i found the following Oracle Tuning Book:
"Oracle High Performance Tuning for 9i and 10g"
(
http://otn.oracle.com/books/pdfs/powell_ch.pdf
This should be only a sample chapter, but it seems the pdf contains the
whole book!)

I read only chapter "6.8 Using views". Here the author states: "Views are not
performance friendly! For the sake of performance do not use views."

The "proofs" he provides look a little bit suspicious to me.

So, is Mr. Powell (the author) right (i don't think so)?
What's your opinion (= truth ;-)?

          Followup:

using count(*) and sqlplus timing is flawed -- totally.

set autotrace traceonly
alter session set timed_statisics=true;
alter session set sql_trace=true;
select ...... <the real queries>
exit;

and then look at the tkprof and SEE what the real difference is.  many times a
view precludes pushing predicates down (it would change the answer for
example!!!) so it is not even apples and oranges comparision.

A view should be used not so much for ad-hoc sql but to hide complexity in code.
 views are not evil, views are good, views do not "kill performance" anymore
then SQL kills performance for all a view is is a stored SQL query!

      Rating: 5
To Michael: the
      author (Powell) also seems to be making many other
      mistakes  November 19, 2003
Reviewer:  Menon

At a quick glance - unless I missed something:
1. he is using elapsed time and comparing small
  differences - they could go either way in a different
  run (basically that is what Tom is saying - tkprof based
  comparison of LIOs for example, makes much more sense.)
2. he is also comparing costs across different query
   plans - I dont think you can compare costs like this.

But at least he is trying to be scientific in trying
to prove his statements (albeit his methods seem
to be flawed) - many times people just claim!

      Rating: 5
joins in a view
      are always done  November 19, 2003
Reviewer:
      Kevin  from Connecticut USA
Hi Tom, interesting topic.  May I also add this (knowing that where I err you
will correct).  A simple example: a view that joins say 10 tables and gets data
from all of them does 9 joins (let say a starting table joined to 10 others).
There will be data returned from all 10 tables.  A query written against this
view that returns say only 1 column (and hence only data from one table) will
still translate into a query that does all the joins in the view.  This is
because the view defines a rowset by the sematics of its joins and without doing
all joins in the view, the meaning of the rowset is different.  Thus although it
is true that selecting only the columns you want is more efficient than taking
select *... it is also true that a generic view may exhibit performance issues
because the large numbers of joins it does regardless of your needs.  Of course
there is the flip side too: if the joins in a view define part of the meaning of
a rowset, then a query written by a programmer that is way faster because is
only selects the columns it needs and only does the joins it needs is
technically not a sematically equivelant query because its rowset is potentially
different.  Hmm.. now we have an interesting question beyond just performance:
which query is correct, the short one of the long one.  Its something to think
about I guess.

          Followup:

you got it, dead on.

      Rating: 4
Sort area
      Size  November 19, 2003
Reviewer:  Arvind
       from India
Dear Tom,

Can you tell me that how the sort area size to be calculated. How I can
determine my requirement for sort area size.

Thanks

          Followup:

use pga_aggregate_target and don't worry about it.  if you undersize, the PGA
advisor will tell you.

      Rating: 4
More
      errors...  November 20, 2003
Reviewer:
      Michael  from Germany
Yes, Menon, there are many more errors in this paper.

For example, he writes about sequences:

"Sequences can be cached. Twenty sequences are cached by default. If a system
failure occurs then all cached sequences are lost. Only sequence numbers are
lost if a database server crashes, not log file recoverable rows in tables, or
any other operations using sequence values.
If required by local law that all sequence numbers added to something like
invoices must exist then always create sequences with the NOCACHE option." (p.
463)

It doesn't seem that he considered that a simple rollback of a transaction (with
seq.nextval) can cause "lost" sequences numbers!!

Also "interesting" is his optinion about PL/SQL:

"In short PL/SQL is not a fully capable programming language. PL/SQL has
programming language capabilities and a few bells and whistles, even some object
and abstraction capability.
From my experience my recommendation is this. PL/SQL can be
used to store database SQL code in one place. This has the effect of only
allowing people with PL/SQL and Oracle database administration skills to build
and change that code. However, since PL/SQL is essentially primitive as a
programming language and developers make better programmers than database
administrators, there is a strong case for minimizing use of PL/SQL.
Do not write your entire system using PL/SQL." (p. 58)

Quite a difference to Toms approach ...

          Followup:

not only that -- i can show you a commit that loses sequences !!! (search for

"write consistency"

on this site).....

Yes, quite different - that approach.  I'll have to agree to violently disagree
with the findings :)

      Rating: 5
To
      Michael  November 20, 2003
Reviewer:  Menon

All i can do is :) The comments about pl/sql
are really interesting:)

Anyways, only the other day, I was discussing this
with a friend of mine (I work for Oracle, btw)
Why is it that there are so many experts on Oracle
who are *so* wrong? I think we Oracle folks must
be doing something terribly wrong!
For example, there was a comment in asktom
elsewhere on wrong information on OCP exams etc.

I am curious, if the same is true for say Microsoft or
IBM tecnhologies...I think Oracle loses big time due
to these myths as they result in people using our database
suboptimally big time!

      Rating: 4
To
      Menon  November 21, 2003
Reviewer:  Michael
       from Germany
Well, there are many "experts" out there, which should read the concepts guide
before they publish an Oracle Tuning book...

And: Yes, the same is true for others. As an example: Informix (other database i
have to deal with).
Many applications ignore the rules and features of this database.
For example most applications i've seen use the "max(id) + 1" approach to
generate primary keys, and not the serial data type, which was designed exactly
for this. Or nobody thought about the concurrency and locking behaviour (shared
read locks!) and so on... :-(

So, Oracle is not alone. (But in the case of Oracle it's even more a pity,
because the potential of the Oracle database is great!)

But, what i don't understand is: Why is the *OTN* promoting this Tuning book
from Powell??? (see
http://otn.oracle.com/books/10g_books.html
.

      Rating: 5
To
      Michael  November 21, 2003
Reviewer:  Menon

"But, what i don't understand is: Why is the *OTN* promoting this Tuning book
from Powell??? (see
http://otn.oracle.com/books/10g_books.html
"

Exactly my point! Not only do we Oracle folks
promote such books (and many others)
written by "experts" but also many of our OCP
exam questions also promote incorrect thinking!
I guess it is not possible for otn folks to
review every book for accuracy - I am not sure
what should be done to stem this? May be a
committee of well known experts (such as Tom,
Cary etc) just glances through all books before we
promote them?

Glad that we are not alone though!;) and I agree
we lose even more than others because of our
potential..

      Rating: 5
Performance
      difference of fat and lean views  December 08, 2003

Reviewer:  A reader
Hi Tom,

Say if I have two similar views, one "fat" (contains more columns in the SELECT
list) and one "lean". Now I want to fetch the values of some columns, which I
can get from either view. Will there be any difference regarding performance?

Thanks.

          Followup:

depends.

does the fat view join to more tables and would those tables to get all of those
columns?  if so, fat view not as good as skinny view -- but skinny view could
return a different result set (more rows) so maybe not a relevant question
(since you can only compare views that return the same answers of course)

otherwise, the optimizer is smart enough to select out just what you need -- the
fat and skinner would be "the same" more or less.

create or replace view fat as select * from big_table;

                  create or replace view skinny as select last_ddl_time from
big_table;

set autotrace traceonly
@trace
select last_ddl_time from fat;
select last_ddl_time from skinny;

select last_ddl_time from fat

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          2          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch    66668      2.52       2.76      10912      80055          0     1000000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    66670      2.53       2.78      10912      80057          0     1000000
********************************************************************************
select last_ddl_time from skinny

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch    66668      2.44       3.03      10913      80055          0     1000000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    66670      2.44       3.04      10913      80055          0     1000000

December 09, 2003 Bala -- Thanks for the question regarding "Have
      to give table name with in "" while selecting from a table.", version
      8.0.4/ Novell Netware

      You Asked
Hi Tom,
Thanks for your help regrading my ODBC question.

One of our Departments have Oracle 8.0.4 on Novell 5.0
If we do select table_name from all_tables where owner=schema1
then we get 147 tables present in that schema1.

But if
log on as schema1/passwd@ordb
and do select count(*) from t1;
table or view does not exists.
We have to give the table name within ""
select count(*) from "t1";
count(*)
11250

This instance is runing for a year and This problem was not
there till last week.

Is it a data dictionary corruption or something?.
Restarting the instance didn't help.

One more help, how to auto start the Instance in Novell netware?

Thanks
Bala

      and we said...
It is not corrupt -- someone created a table in mixed case is
all.  Probably using a Microsoft tool like access or something
-- they are infamous for doing that.

When you create a table, you can use quoted identifiers.  These
preserve case and allow you to use spaces and other invalid
characters.  It is not recommened.

You can rename the table to correct this (but bear in mind that
anything that queries it via "t1" won't work anymore -- you
could create a view "t1" as select * from t1 after the rename if
that is an issue:

ops$tkyte@ORA8I.WORLD> create table "t1" ( x int );
Table created.

ops$tkyte@ORA8I.WORLD> select * from t1;
select * from t1
              *
ERROR at line 1:
ORA-00942: table or view does not exist

ops$tkyte@ORA8I.WORLD> select * from "t1";
no rows selected

ops$tkyte@ORA8I.WORLD> rename "t1" to t1;
Table renamed.

ops$tkyte@ORA8I.WORLD> select * from t1;
no rows selected

Article-ID:         <Note:44883.1>
Circulation:        PUBLISHED (EXTERNAL)
Folder:             platform.Desktop.Netware
Topic:              4.5 -- Technical Notes
Title:              NETWARE: AUTO START THE DATABASE AT SERVER
BOOT TIME
Document-Type:      BULLETIN
Impact:             MEDIUM
Skill-Level:        NOVICE
Updated-Date:       23-DEC-1998 15:01:35
References:
Shared-Refs:
Authors:            ABARDEEN.US
Attachments:        NONE
Content-Type:       TEXT/PLAIN
Products:           5;
Platforms:          21;

Article-ID:     <Note:61666.1>
Alias:          OLS:11967992.61
Subject:        NETWARE: AUTO START THE DATABASE AT SERVER BOOT
                TIME
Keywords:       AUTOSTART;DATABASE;BOOT;SERVER; SHUTDOWN;
                PASSWORD

PURPOSE
=======

This document gives instructions for having any database on the
server automatically start up after the server boots.

SCOPE & APPLICATION
====================

The intended audience for this article is Oracle DBAs who have a
moderate knowledge of Oracle7 or Oracle8 Server and an in-depth
knowledge of Novell NetWare.

This bulletin makes the following assumptions:
   Oracle 7.0, 7.1, 7.2, 7.3, or 8.0. has been successfully
   installed.

CONVENTIONS AND DEFAULTS
========================

For the purposes of this discussion, the following convention is
used:

   < > indicates a value that you must provide.

RELATED DOCUMENTS
=================
NOTE 61850.1  "NetWare Working with Multiple SIDs"
Bulletin 11323786.61 "How to Configure IPC Protocol Adapter"

EDITING TEXT FILES
==================
Do not edit any text file, such as CONFIG.ORA, INIT<sid>.ORA or
TNSNAMES.ORA using a Windows-based editor, such as Notepad or
Write.  These editors can embed hidden characters in the files,
even when they are saved as ASCII text files.  Instead, use
NetWare's EDIT.NLM or a DOS-based editor.

OVERVIEW
=========

In order to have the database automatically start up when the
server boots the Oracle NLMs must be loaded and then a script
must be called which starts up the database.  Because NetWare
will attempt to load another NLM before the first NLM has
finished loading, the script to autostart the database may hang.
With version 7.3.2.3.0 the ORALOAD.NCF file now uses the
ORLOAD.NLM to ensure that the NLM has finished loading before
the
next one attempts to load.  If a version prior to 7.3.2.3.0 is
being used, it may be necessary to manually force "pauses" in
the
script executions by using Novell's DELAY.NLM, which is
downloadable from their FTP site.  The DELAY.NLM causes a delay,
by the number of seconds specified, before the next NLM is
loaded.

INSTRUCTIONS
============

1. Edit the AUTOEXEC.NCF file to call the ORALOAD.NCF file to
   load the Oracle NLMs.

2. Create a script to start up the database.  A separate script
   must be created for each database which will be started when
the
   server boots.  It is recommended that the script name end in
   ".SQL".

   A sample script might be as follows.  Note:  The line numbers
   are provided for the additional comments below, but are not
   part of the actual script.

     1.   SET TERMOUT ON
     2.   SET ECHO ON
     3.   SPOOL <volume>:<path>\STRT<sid>.LOG
     4.   CONNECT INTERNAL/<password>@<connect_string>
     5.   STARTUP PFILE=<volume>:<path>INIT<sid>.ORA
     6.   SPOOL OFF
     7.   EXIT

   Comments:

   Lines 1 - 3:  These lines will log the output, including any
       error messages, to the file specified.  This file will be
       overwritten every time the script is executed.  This is
       useful to review if the database does not start properly.

   Line 4:

       On 7.0:
            A connect string is not needed.

       On 7.1:
            If the parameter DBA_AUTHORIZATION=BYPASS has been
            added to the CONFIG.ORA file, then a password does
            not need to be specified when connecting as
            INTERNAL.
            If only one database is on this server, then a
            connect string is not needed.  If running multiple
            databases, a connect string of  S:<sid>  can be used
            instead of a connect string from the server's
            TNSNAMES.ORA file.

       Versions 7.2 and later:
            The database must be started up with an IPC loopback
            (IPC connect string).  See "NetWare Working with
            Multiple SIDs" and "How to Configure IPC Protocol
            Adapter" for details.

   Line 5:
       Versions 7.0 and 7.1:
            If there is only one database on the server AND the
            parameter file is in the default location, then the
            PFILE parameter is not required.  Oracle Support,
            however, recommends that the PFILE option always be
            used when starting up the database.

       Versions 7.2 and later:
            The PFILE parameter is required.

   Line 6:  This command closes the spool file

   Line 7:  This command exits the tool (SQLDBA, SVRMGR, or
   SVRMGR30).

   WARNING:  Do NOT use a SET INSTANCE command in the script.
   If the SET INSTANCE command fails, it is possible for the
   rest of the script to continue and the database could be
   started with the wrong parameter file, thereby causing
   database corruption.  Instead of the SET INSTANCE command,
   the connect string in the CONNECT INTERNAL statement tells
   Oracle which instance to use.

3. Edit the AUTOEXEC.NCF file to call the database script
   created in step 2.

   Add the following line AFTER the ORALOAD line:

       On 7.0, 7.1 and 7.2:
            LOAD SQLDBA @<volume>:<path>\<script_name>

       On 7.3:
            LOAD SVRMGR @<volume>:<path>\<script_name>

       On 8.0:
            LOAD SVRMGR30 @<volume>:<path>\<script_name>

   NOTE:  If the command above hangs, then the NLMs being loaded
   by ORALOAD probably have not finished loading.  In this case,
   use the DELAY.NLM (see the OVERVIEW section above).  After
   downloading DELAY.NLM, add the following line BEFORE the line
   to load SQLDBA, SVRMGR, or SVRMGR30:

       LOAD DELAY 30
            Start with 30 seconds and increase the number until
            the load SQLDBA, SVRMGR, or SVRMGR30 command runs
            successfully.

FINAL NOTES
===========

1. IPC.NLM version 2.3.2.1.0 may also cause the database to
   hang. This has been fixed with the SQL*Net 2.3.2.1.5, or
later,
   patch.  Note:  Before applying this patch the IPC.NLM must be
   manually unloaded.

2. If the database does not start when the server boots it is a
   good idea to review the spool file, created by the startup
   script, for any errors.

-----------------------------------------------------------------

                                Oracle Worldwide Customer
Support

Review &
      Followup

      Rating: 4
How to pass
      table name as a parameter in procedure/function  December
      09, 2003
Reviewer:  Anurag  from INDIA
Tom,

How to pass table name as a parameter in a procedure/function. Kindly give an
example say ...writing a count function for any table.

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4541695480376

December 09, 2003 Dan -- Thanks for the question regarding "No
      Exection Generated in 10046 Event Trace", version 8.1.7

      You Asked
Hi Tom,

I'm using the event 10046 to trace other sessions. But sometimes I find that
there is not execution plan information included in the result trace file. Here
is an example:

1. From my session, I executed

dchen@SAPU> exec sys.dbms_system.set_ev(130, 54378, 10046, 8, '')

PL/SQL procedure successfully completed.

2. From another session using SQL*Navigator (sid=130 and serial#=54378),
executed

select count(*)
  from carrier.sl_conversion_phase p,
       carrier.sl_claim_number s,
       sap.claims c
 where p.reg_office = s.reg_office
   and c.claim_number = s.opus#
   and c.claim_id in (select claim_id
                 from sap.sl_spc_claims
                where created_by_user_id = 'SLIC'
                  and policy_coverage_id is null
              );

3. then, back to my session, issued
dchen@SAPU> exec sys.dbms_system.set_ev(130, 54378, 10046, 0, '')

PL/SQL procedure successfully completed.

4. ran TKPROF on the trace file ORA03688.TRC, I got

TKPROF: Release 8.1.7.0.0 - Production on Sun Dec 8 16:25:04 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Trace file: ora03688.trc
Sort options: default

********************************************************************************
count    = number of times OCI procedure was executed
cpu      = cpu time in seconds executing
elapsed  = elapsed time in seconds executing
disk     = number of physical reads of buffers from disk
query    = number of buffers gotten for consistent read
current  = number of buffers gotten in current mode (usually for update)
rows     = number of rows processed by the fetch or execute call
********************************************************************************

select count(*)
  from carrier.sl_conversion_phase p,
       carrier.sl_claim_number s,
       sap.claims c
 where p.reg_office = s.reg_office
   and c.claim_number = s.opus#
   and c.claim_id in (select claim_id
                 from sap.sl_spc_claims
                where created_by_user_id = 'SLIC'
                  and policy_coverage_id is null
              )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.88          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2     19.74      19.82          0    1866464         12           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5     19.74      20.70          0    1866464         12           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 44

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.88          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2     19.74      19.82          0    1866464         12           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5     19.74      20.70          0    1866464         12           1

Misses in library cache during parse: 0

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      0      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        0      0.00       0.00          0          0          0           0

Misses in library cache during parse: 0

    2  user  SQL statements in session.
    0  internal SQL statements in session.
    2  SQL statements in session.
********************************************************************************
Trace file: ora03688.trc
Trace file compatibility: 8.00.04
Sort options: default

       0  session in tracefile.
       2  user  SQL statements in trace file.
       0  internal SQL statements in trace file.
       2  SQL statements in trace file.
       1  unique SQL statements in trace file.
      60  lines in trace file.

But, if we check the trace shown as follow you can see the execution plan
information is there:

*** 2002-12-08 16:19:16.642
WAIT #5: nam='SQL*Net message from client' ela= 50139 p1=1413697536 p2=1 p3=0
STAT #5 id=1 cnt=1 pid=0 pos=0 obj=0 op='SORT AGGREGATE '
STAT #5 id=2 cnt=57 pid=1 pos=1 obj=0 op='NESTED LOOPS '
STAT #5 id=3 cnt=933091 pid=2 pos=1 obj=0 op='HASH JOIN '
STAT #5 id=4 cnt=1026 pid=3 pos=1 obj=0 op='MERGE JOIN CARTESIAN '
STAT #5 id=5 cnt=58 pid=4 pos=1 obj=0 op='VIEW VW_NSO_1 '
STAT #5 id=6 cnt=58 pid=5 pos=1 obj=0 op='SORT UNIQUE '
STAT #5 id=7 cnt=57 pid=6 pos=1 obj=96712 op='TABLE ACCESS FULL SL_SPC_CLAIMS '
STAT #5 id=8 cnt=1026 pid=4 pos=2 obj=0 op='SORT JOIN '
STAT #5 id=9 cnt=18 pid=8 pos=1 obj=54410 op='TABLE ACCESS FULL
SL_CONVERSION_PHASE '
STAT #5 id=10 cnt=16394 pid=3 pos=2 obj=52985 op='TABLE ACCESS FULL
SL_CLAIM_NUMBER '
STAT #5 id=11 cnt=57 pid=2 pos=2 obj=99323 op='INDEX RANGE SCAN '
WAIT #1: nam='SQL*Net message to client' ela= 0 p1=1413697536 p2=1 p3=0
WAIT #1: nam='SQL*Net message from client' ela= 0 p1=1413697536 p2=1 p3=0
WAIT #1: nam='SQL*Net message to client' ela= 0 p1=1413697536 p2=1 p3=0
WAIT #1: nam='SQL*Net message from client' ela= 0 p1=1413697536 p2=1 p3=0
=====================
PARSING IN CURSOR #1 len=366 dep=0 uid=44 oct=3 lid=44 tim=14735176 hv=876398650
ad='1297c6e4'
select count(*)
  from carrier.sl_conversion_phase p,
       carrier.sl_claim_number s,
       sap.claims c
 where p.reg_office = s.reg_office
   and c.claim_number = s.opus#
   and c.claim_id in (select claim_id
                 from sap.sl_spc_claims
                where created_by_user_id = 'SLIC'
                  and policy_coverage_id is null
              )
END OF STMT
PARSE #1:c=0,e=44,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=14735217
WAIT #1: nam='SQL*Net message to client' ela= 0 p1=1413697536 p2=1 p3=0
WAIT #1: nam='SQL*Net message from client' ela= 2 p1=1413697536 p2=1 p3=0
WAIT #5: nam='SQL*Net message to client' ela= 0 p1=1413697536 p2=1 p3=0
WAIT #5: nam='SQL*Net message from client' ela= 0 p1=1413697536 p2=1 p3=0
=====================
PARSING IN CURSOR #5 len=366 dep=0 uid=44 oct=3 lid=44 tim=14735237 hv=876398650
ad='1297c6e4'
select count(*)
  from carrier.sl_conversion_phase p,
       carrier.sl_claim_number s,
       sap.claims c
 where p.reg_office = s.reg_office
   and c.claim_number = s.opus#
   and c.claim_id in (select claim_id
                 from sap.sl_spc_claims
                where created_by_user_id = 'SLIC'
                  and policy_coverage_id is null
              )
END OF STMT
PARSE #5:c=0,e=44,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=14735278
WAIT #5: nam='SQL*Net message to client' ela= 0 p1=1413697536 p2=1 p3=0
WAIT #5: nam='SQL*Net message from client' ela= 0 p1=1413697536 p2=1 p3=0
EXEC #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=14735287
*** 2002-12-08 16:19:38.095
WAIT #5: nam='SQL*Net message to client' ela= 0 p1=1413697536 p2=1 p3=0
FETCH #5:c=1974,e=1982,p=0,cr=1866464,cu=12,mis=0,r=1,dep=0,og=4,tim=14737272
WAIT #5: nam='SQL*Net message from client' ela= 0 p1=1413697536 p2=1 p3=0
FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=0,tim=14737278
WAIT #5: nam='SQL*Net message to client' ela= 0 p1=1413697536 p2=1 p3=0

If I use SQL_TRACE to trace the same query from my owner session, I can get the
execution plan information in the TKPROF output report.
Here is the SQL_TRACE output from TKPROF:

TKPROF: Release 8.1.7.0.0 - Production on Sun Dec 8 15:48:31 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Trace file: ora03952.trc
Sort options: default

********************************************************************************
count    = number of times OCI procedure was executed
cpu      = cpu time in seconds executing
elapsed  = elapsed time in seconds executing
disk     = number of physical reads of buffers from disk
query    = number of buffers gotten for consistent read
current  = number of buffers gotten in current mode (usually for update)
rows     = number of rows processed by the fetch or execute call
********************************************************************************

alter session set sql_trace=true

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      1      0.00       0.59          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        1      0.00       0.59          0          0          0           0

Misses in library cache during parse: 0
Misses in library cache during execute: 1
Optimizer goal: CHOOSE
Parsing user id: 44
********************************************************************************

select count(*)
  from carrier.sl_conversion_phase p,
       carrier.sl_claim_number s,
    sap.claims c
 where p.reg_office = s.reg_office
   and c.claim_number = s.opus#
   and c.claim_id in (select claim_id
                 from sap.sl_spc_claims
                where created_by_user_id = 'SLIC'
                  and policy_coverage_id is null
              )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.48          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2     19.99      19.98          0    1866464         12           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4     19.99      20.46          0    1866464         12           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 44

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
     57   NESTED LOOPS
 933091    HASH JOIN
   1026     MERGE JOIN CARTESIAN
     58      VIEW VW_NSO_1
     58       SORT UNIQUE
     57        TABLE ACCESS FULL SL_SPC_CLAIMS
   1026      SORT JOIN
     18       TABLE ACCESS FULL SL_CONVERSION_PHASE
  16394     TABLE ACCESS FULL SL_CLAIM_NUMBER
     57    INDEX RANGE SCAN (object id 99323)

********************************************************************************

alter session set sql_trace=false

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.13          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.13          0          0          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 44

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.61          0          0          0           0
Execute      3      0.00       0.59          0          0          0           0
Fetch        2     19.99      19.98          0    1866464         12           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        7     19.99      21.18          0    1866464         12           1

Misses in library cache during parse: 2
Misses in library cache during execute: 1

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      0      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        0      0.00       0.00          0          0          0           0

Misses in library cache during parse: 0

    3  user  SQL statements in session.
    0  internal SQL statements in session.
    3  SQL statements in session.
********************************************************************************
Trace file: ora03952.trc
Trace file compatibility: 8.00.04
Sort options: default

       1  session in tracefile.
       3  user  SQL statements in trace file.
       0  internal SQL statements in trace file.
       3  SQL statements in trace file.
       3  unique SQL statements in trace file.
      63  lines in trace file.

Here are my questions:

1. Why dose tkprof ignore the execution plan in the trace file generated from
event 10046 in some cases? I know it won't recognize those waits related
information but not STATS related information.

2. In the case shown in my case, the event 10046 generated two sections in the
trace file for the same query statement. I only issued it once. Therefore, we
got 2 counts of parses. How could this happan?

Thanks.

      and we said...
1) trace files are linear -- the stat records you show are for some "other
cursor #5" -- no the cursor #5 you show.  The stat records are only printed out
when the cursor is closed -- I believe the other session you are monitoring is
keeping the cursor open (hence the stat records won't make it out)

2) sql navigator must "parse the query" in order to describe it (cause their
programmers didn't know how to program Oracle) and then they parse it
(wastefully) again in order to execute and display it.

SQL_TRACE shows you reality - navigator is parsing the query two times.

so, here is what i think - you ran the query in navigator.  THEN you enabled
trace.  THEN you ran again in navigator.  navigator closed the other query --
that dumped the stats records (for the PREVIOUS execution of that query).
navigator then parsed the query -- to describe it.  navigator then parsed and
executed the query in order to print it.  then you turned off trace -- hence NO
stats records for that cursor.
Review &
      Followup

      Rating: 4
Does it mean
      SQL Navigator tool has problems  December 09, 2002

Reviewer:  Ajeet  from OH,USA
Tom - you said - sql navigator must "parse the query" in order to describe it
(cause their
programmers didn't know how to program Oracle) and then they parse it
(wastefully) again in order to execute and display it.
Does it mean that SQL Navigator is doing wrong because of the way it has been
designed and develop?
In my company few programmers use this tool extensively-should we check the
trace files to ensure that we are using resources in optimum way.
Thanks for your advice on this.

          Followup:

If you see a double parse on every execute, they are wasting resources -- yes.
There should be AT WORST a 1-1 ratio between parse and execute.  At best (but
very hard to do with a generic tool like navigator), a 1 parse to N executes
where N > 1.

      Rating:
      4
  December 09, 2002
Reviewer:  Dan
      Chen  from Pittsburgh, PA, USA
Thank you very much, Tom. Your answers help me to understand what's behind the
scene. I think that's why I always get some thing more than what it dose from
sql*plus when I use the way learned from your book and this site about how to
create a test harnes.
Thanks.

-Dan

      Rating: 5
Additional
      caveat  December 09, 2002
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Don't know if this caveat applies here, but if you are using the Oracle
ADO/OLEDB provider to connect to the server, search MetaLink for bug #1727475
(which is fixed in the Oracle 9.2 provider)...

Here is a short excerpt from Oracle Support in one of the posts:

"Apparently what is happening is the provider (ADO/OLEDB)calls OCIStmtExecute
twice. Once with DESCRIBE_ONLY to do an explicit describe and again to execute.
The parse stat count is being incremented twice, once for each execute call. The
parse happens only during the first call which is a hard parse. On the second
call, the count is being incremented even though a hard parse is not made. As a
result, the parse count shows 2 even though it is actually being parsed only
once, the first time."

Just thought I'd throw that out there just in case...

- Mark

      Rating: 3
What are the
      parameters passed to DBMS_SYSTEM.SET_EV?  February 10, 2003

Reviewer:  Matt  from Australia
DESC DBMS_SYSTEM

PROCEDURE SET_EV
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 SI                             BINARY_INTEGER          IN
 SE                             BINARY_INTEGER          IN
 EV                             BINARY_INTEGER          IN
 LE                             BINARY_INTEGER          IN
 NM                             VARCHAR2                IN

Are these:

 SI - V$SESSION.SID
 SE - V$SESSION.SERIAL#
 EV - Event Number eg: 10046
 LE - Level eg:    1 (SQL_TRACE),
                   4 (SQL_TRACE+BINDS),
                   8 (SQL_TRACE+WAITS),
                or 12(SQL_TRACE+BINDS+WAITS)
 NM - ??????

What is NM?

I am looking to set tracing (SQL_TRACE + WAITS) from another session.

Regards,

          Followup:

it is the "name" which is not used with the 10046 trace.  Just pass ''

you guessed right on the rest.

      Rating: 2
How do you
      switch this off for a session?  February 10, 2003

Reviewer:  Matt  from Australia
The other question is of course that after switching on tracing using, for
example:

execute sys.dbms_system.set_ev(18,13,10046,8,'')

If the session is long running, how do I switch the tracing off after some
period?

Would execute sys.dbms_system.set_ev(18,13,10046,0,'') do this?

Regards

          Followup:

when you tried it, what did it do?

yes, setting to zero turns it off.

      Rating: 4
No Explain Plan
      In the TKProf Output.  December 09, 2003
Reviewer:
      Ganesh Raja  from Singapore
Hi Tom,

I dont get an explain plan when i run my Trace file Thru Tkprof.

What may be the Reasons for that ..

Regards,
Ganesh R

          Followup:

no version?

did you exit the application that generated the trace, the STAT records only get
written when the cursor is closed, if the application keeps cursors open (eg: it
was well written), you must exit the application.

you can use explain=u/p on the tkprof command line, but I don't like that -- the
explain plan might well not be the one that was actually used at run time.

December 09, 2003 william -- Thanks for the question regarding
      "Can't access remote object via public link and How to hide password for
      sqlplus login", version 8i

      You Asked
Hi Tom,

I would like to ask you two questions :

1) Can't access or select remote object via public dblink.

I have encountered this problems, not sure any solution for this :

i) created a ops$acct1 in 7.3.4 defined as external.
ii) created a ops$acct1 in 8i defined as external.
iii) created a public dblink in 8i pointing to 7.3.4
iv) logon to 8i via unix account acct1 and to db using 'sqlplus /'
iv) issued sql command : select count(*) from table@dblink_name
v) encountered this error :
ERROR at line 1:
ORA-01005: null password given; logon denied
ORA-02063: preceding line from dblink_name

For some reason, private dblink is not an option.

2) How to hide password for sqlplus login

I would like to automate some of my operation work, but do not want to use the
ops$ account. So how could I hide or encrypt my password so that no one could
see it, not even the root user.

I know of some method to put the password into the file and login to db by
invoking it, e.g sqlplus /nolog @x.sql where x.sql contain the connect string
with userid and password. But the x.sql is in the clear, how can I "hide" or
encrypt it such that the above command still run but no one could see the
content of x.sql except dba.

Many Thanks.
William

      and we said...
1) alter user ops$acct1 identified by some_password

ops$acct1 will STILL be os authenticated, but it has the ability to have a
password as well!

Consider:

$ sqlplus /

SQL*Plus: Release 3.3.4.0.1 - Production on Thu Aug 30 07:55:27 2001
Copyright (c) Oracle Corporation 1979, 1996.  All rights reserved.
Connected to:
Oracle7 Server Release 7.3.4.0.1 - Production
With the distributed, parallel query and Spatial Data options
PL/SQL Release 2.3.4.0.0 - Production

ops$ora734@ORA734.WORLD> alter user ops$ora734 identified by Some_password;
User altered.

ops$ora734@ORA734.WORLD> exit

$ sqlplus /

SQL*Plus: Release 3.3.4.0.1 - Production on Thu Aug 30 07:55:42 2001
Copyright (c) Oracle Corporation 1979, 1996.  All rights reserved.
Connected to:
Oracle7 Server Release 7.3.4.0.1 - Production
With the distributed, parallel query and Spatial Data options
PL/SQL Release 2.3.4.0.0 - Production

ops$ora734@ORA734.WORLD> exit
that shows we are still able to do os-auth

$ sqlplus 'ops$ora734/some_password'

SQL*Plus: Release 3.3.4.0.1 - Production on Thu Aug 30 07:55:52 2001
Copyright (c) Oracle Corporation 1979, 1996.  All rights reserved.
Connected to:
Oracle7 Server Release 7.3.4.0.1 - Production
With the distributed, parallel query and Spatial Data options
PL/SQL Release 2.3.4.0.0 - Production

ops$ora734@ORA734.WORLD> exit

but still have the ability to use a password when we need to.

Now, use the password in the create database link and it will be able to
authenticate you.

2) Well, the OPS$ account is quite simply THE BEST and most appropriate way to
do this.

See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:142212348066
for a link to "hide"

Review &
      Followup

      Rating: 4
Still having a
      problem  May 03, 2002
Reviewer:  Jeff Wilson
       from Omaha, NE USA
On an 8.1.7.3 database, I have created a user jeffwilson identified externally,
and am able to login with sqlplus /.
When I use 'alter user jeffwilson identified by some_password;', I am no longer
able to logon by using 'sqlplus /', but only by 'sqlplus
jeffwilson/some_password'.  If I try to use the 'sqlplus /', I get 'ERROR:
ORA-01017: invalid username/password; logon denied'

The OS_AUTHEN_PREFIX is set to ''.

          Followup:

That is because you set the os_authen_prefix to ''

you have to USE an os_authent_prefix in order to have your cake and eat it too.

      Rating: 1
Public Database
      Link Not Working  October 20, 2003
Reviewer:
      Prince Faran  from Pakistan
Hi Tom! I have configured Oracle9i databases over MS Windows2000 Advanced Server
for advanced replication. Before using the Replication Setup Wizard, I have
created public database links at both sides as following

=========
@ TEST1
=========
create public database link test2.world
using 'test2';

=========
@ TEST2
=========
create public database link test1.world
using 'test1';

but it doesn't help to execute the jobs like  sys.dbms_defer_sys.push  to push
the transactions from one db to other. An extract from the trace file is as
under:-

Dump file d:\oracle\admin\test1\udump\test1_j001_2084.trc
Sun Oct 19 19:45:39 2003
ORACLE V9.2.0.1.0 - Production vsnsta=0
vsnsql=12 vsnxtr=3
Windows 2000 Version 5.0 Service Pack 2, CPU type 586
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
Windows 2000 Version 5.0 Service Pack 2, CPU type 586
Instance name: test1

Redo thread mounted by this instance: 1

Oracle process number: 15

Windows thread id: 2084, image: ORACLE.EXE (J001)

*** SESSION ID:(11.7) 2003-10-19 19:45:39.000
*** 2003-10-19 19:45:39.000
ORA-12012: error on auto execute of job 5
ORA-01005: null password given; logon denied
ORA-06512: at "SYS.DBMS_DEFER_SYS", line 1716
ORA-06512: at "SYS.DBMS_DEFER_SYS", line 1804
ORA-06512: at line 1
*** SESSION ID:(11.9) 2003-10-19 19:46:40.000
*** 2003-10-19 19:46:40.000
ORA-12012: error on auto execute of job 5
ORA-01005: null password given; logon denied
ORA-06512: at "SYS.DBMS_DEFER_SYS", line 1716
ORA-06512: at "SYS.DBMS_DEFER_SYS", line 1804
ORA-06512: at line 1

Then I have tested through the private db link for repadmin, now it is working
fine. Please guide me, how can  public database link can be used for "connected
user" rather than just to hardcode the password in the create public database
link command for a specific user during replication?

Note: I had checked the public database link through sqlplus, it was working
fine. but it generates error when used by Oracle replication mechanism.

Best Regards,
Faran

          Followup:

when running as a job -- there is no identity -- no "password" to be passed
along.  You need to put that in there, you can use a private database link --
but it needs the connect to/identified by.

when you do it in sqlplus -- it HAS your password - it is in your environment.
when run as a background job, it does not have the password.

      Rating: 1
Still looking
      for any other option  October 21, 2003
Reviewer:
      Prince Faran  from Pakistan
Hi Tom,

How can I believe that I can't do it without a private database link (using
connect to/identified by). While I have tested it twice before in the past with
the public database link (without connect to/identified by). I remember that I
do used only commands like the following to create the database links while
connected with user system:-

create public database link test2.world
using 'test2';

but aaaaaah :), I forgot what else I did alongwith either inialization parameter
setting -or- what else. anyways tom, really thanks for taking your time.

With Regards,
Faran

          Followup:

well, you could use remote_os_authent=true but -- that would open a security
hole large enough to fly a space shuttle thru.

DEFINITELY NOT recommended.

      Rating: 2
Security
      Measures  October 21, 2003
Reviewer:  Faran
       from Pakistan
Nice Tom,

your logic of security seems pretty good. but can you please throw light about
security if we use the commands like the following to create the database links

create database link test2.world
connect to repadmin identified by repadmin
using 'test2';

I have read anywhere about oracle8 that if we use "connect to/identified by"
clause, RDBMS stores this credentials information. this information can be
viewed through data dictionary. which is also a big security hole that is why i
wanna rescure from hard coding the credentials. can you please guide if it has
been improved in Oracle9i.

With Best Regards,
Faran

          Followup:

the people that can see this information already have 100% access and the
ability to reset passwords anyhow.

the barn door is already always wide open for these people.

If they wanted, they would query dba_users, get the hashed password for your
"user" (or if identified externally -- just give them one).  then they can alter
that user (they are DBA's after all), log in using that password and then reset
the password back using the original hashed password.  Bamm -- they are in.
they don't even need to KNOW the password.

Sorry -- but the barn door is way open for the people that can get to sys.link$
already.

      Rating:
      4
Thanks  October 22, 2003
Reviewer:
      Prince Faran  from Pakistan
Dear Thomas Kyte!

You have explained very well. But now I am thinking why Oracle gave the
knowledge of hashed password to all users having dictionary access? There should
be specific levels of users having the rights to change the password of the
users having levels below them. I am a bit disappointed to know about the
security feature of oracle rdbms you've described. mmm I think, you wanna say
that we are on the way of improvement.

          Followup:

DBA_USERS, not all_users.

and you must have the ALTER USER priv (a DBA has that)

I think you need to understand whats at work here.  a DBA (the person who would
reasonably have access to sys.link$) already has very easy ways to "become
another user" anytime they wanted.  They have to -- they reset passwords, they
create users.  it is part of their job.

All I'm saying is that this does not materially affect your "degree of security"
here -- the people who can look at this stuff ALREADY HAVE the ability to do
what you are afraid they might do.

      Rating: 3
Yes, this is
      the right point  October 22, 2003
Reviewer:
      Prince Faran  from Pakistan
Yes Tom!

This is the right point, you've raised. Actually when we grant ALTER USER to any
person (say junior dba) then he/she gets the privilege to change the password of
any user in the database while he/she should have the rights (somewhat like)
ALTER BELOW USER. I think you got my point i.e. he/she may view/change the
passwords of those users, on which he/she has been granted superiority by the
senior level dba. Conclusion is that there should be any categorization of the
dictionary access i.e. security levels rather than grant just ALTER USER to
alter any user's account (access like say ALTER ANY USER) -or- SELECT ANY TABLE
to select all the tables of data dictionary. do you not feel it as a
requirement? will it not be a good additional feature of RDBMS?

          Followup:

that is what stored procedures allow you to do -- 100%.

the "sr" dba would write a stored procedure -- it runs with their granted privs.
 hence it can alter ANY user.  BUT, that person can put logic in there that says
"oh wait, Bill is running the procedure and Bill is only allowed to alter these
10 people" (perhaps table driven).

sr dba grants execute on this procedure to bill.

stored procedures allow you to implement ANY arbitrary security policy you can
possible dream up

      Rating: 4
But at user
      level export  December 09, 2003
Reviewer:
      A reader  from india
Thanks tom, your responce was really helpful but during user level
export,private db link passwd is also gets exported.The situation is we are
sending user level dmp files.These users have some private db links created
'connect to identified by passwd'.we don't want to reveal this passwd albeit it
is of another instance .How to achive this?Should we create public db links so
the link will not be at all exported during user level dmp or just drop and
recreate the links before and after export? As usual your responce will be
highly appreciated

          Followup:

if you don't want to share the data, you'll want to drop the dblink.

i prefer to use non-proxy links personally (don't use connect to identified by).

December 09, 2003 Chris -- Thanks for the question regarding
      "Multiple DBWn", version 8.1.6

      You Asked
I am trying to determine wheather to increase DBWn processes.
We have just implemented parameter changes for parallelism.
DB_BLOCK LRU_LATCHES=6
CPU's=6.

I have been reading oracle metalinks, oracle online doc's and your Archives to
find a way to monitor if users are waiting for the DBWR processes.

How can I monitor the DBWR processes to see if I need more processes and what is
a recommend amount of DBWR processes.

      and we said...
If you are running async_io, you almost never need to touch this.

But, goto

http://www.ixora.com.au/
and search for DBWR, you want to read "The Mysteries of DBWR Tuning"... Great
paper, starting to get a little dated but still relevant.
Review &
      Followup

      Rating: 4
Multiple CPUs
      and Multiple Instances  May 14, 2002
Reviewer:
      A reader
Hi,

We have four CPUs on this particular box. Also, we have four instances running.
In this case, is it appropriate to have DB_BLOCK LRU_LATCHES = 8 for all
instances.

Thks

          Followup:

yes -- we do not assign instances to CPU's, we just use them all.

      Rating:
      5
  August 07, 2002
Reviewer:
      A reader
Hi TOM,

 I heard DB_BLOCK_LRU_LATCH init.ora parameter In oracle 9i R2 is obsolete. Is
it true?

 Thanks,

          Followup:

actually, it was 9iR1 that obsoleted it.

ops$tkyte@ORA920.US.ORACLE.COM> show parameter DB_BLOCK_LRU_LATCH
ops$tkyte@ORA920.US.ORACLE.COM>

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96536/toc.htm
lists them all.

      Rating:
      3
  December 11, 2002
Reviewer:
      A reader
Hi Tom,

I have digged into the Oracle documentation and I didn't find anything connected
with how to set the parameter DB_WRITER_PROCESSES. They describe the parameter
and the advantages but I wasn't able to find anything that lead me to know how
to set it taking into account the number of cpu that I have.
Can you give any advice on how to set this parameter and which other parameters
should I take care of too?
I have a box with 4 cpus 8G RAM and asynchIO, 900M buffer cache (400 default,200
keep, 300 recycle).

Thanks in advance for any help.

          Followup:

are you using async io?

have you identified dbwr as a bottleneck that is causing you to wait?

if the answers are yes, no -- stop.  you are done.

if the answer is NO, yes|no -- why no?

if the answer is yes, yes -- explain how you identified this and we'll work from
there.

      Rating:
      3
  December 11, 2002
Reviewer:
      A reader
Sorry Tom, I forgot to mention in my previous question that I am using 9iR2.

      Rating:
      3
  December 12, 2002
Reviewer:
      A reader
Hi Tom,

Actually my answer is yes, yes/no.
Why yes/no, because right now most of the waits that we are facing are connected
with the fact that we don't have enought disk to spread the online redo and this
is causing us contention. But on the other hand we also have sometimes during
the peak hours waits for free buffer waits.
In order to try to resolve this in the new configuration (with 9iR2) I am tuning
the Lgwr in order to let it work more efficient and coordinated with the dbwr.
But as we have a new box with enought disks to do the things in the right way, I
was thinking that it might be a good idea to analyze the possibility to
implement dbwr processes in the new configuration instead of dbwr_io_slaves as
we are having now. Taking into account the information that I found about this
parameter, it seems like we fulfill the conditions in order to implement it:
- we have a considerable write activity (average of 1.5G redo every 4 hours)
- we have a big buffer cache (900 M)
- we have asynchronic io available in our server
- we have more than one cpu (4 total).
That's why I would like to find more information about this parameter and how to
set it, and see if this is something that will be beneficial for us or not.

thanks

          Followup:

If moving to a new server -- I would wait until moved to see if the problem
persists.  If using async io and you have sufficient disks spread out, you most
likely won't need it.

      Rating:
      4
  December 12, 2002
Reviewer:
      A reader
Thanks for answering me Tom, I will follow your advice.

But may I ask you something more? Why not try the db_writer_processes from the
very begining, isn't that a better alternative for speeding the dbwr up instead
of the dbwr_io_slaves?
Thanks

          Followup:

don't use either from the beginning.

You tune in response to bad performance.

You don't flip a thousand switches "just in case".

When you get to a machine that can support your load AND you have an identified
issue THEN we can talk.

      Rating:
      3
dbwr  December 09, 2003
Reviewer:
      Duncan  from UK
We often have period of high disk activity where the IO is at 100% capacity.
Presently we have db_writer_processes set to 1. We are on
HP-UX 11i with 4 cpus and 16Gb RAM .
On metalink there is a note <Note:97291.1> which states

DB_WRITER_PROCESSES
===================

Multiple database writers is implemented via the init.ora parameter
db_writer_processes.  This feature was enabled in Oracle8.0.4, and allows true
database writers; i.e., no master-slave relationship.  With Oracle8
db_writer_processes, each writer process is assigned to a LRU latch set.  Thus,
it is recommended to set db_writer_processes equal to the number of LRU latches
(db_block_lru_latches) and not exceed the number of CPUs on the system.  For
example, if db_writer_processes was set to four  and db_lru_latches=4, then
each writer process will manage its corresponding set.

which tells me that you can raise the db_writer_processes but not greater than
the
number of cpus.

However my question is: we have multiple instances on the same box (we are
considering changing to one
instance) for the moment so does this limit of, matching dbwr to the number of
cpus apply for the whole box or per instance??

For example:

say we have 4 instances each with 1 dbwr at the moment.

Can i take instance number 1 and increase     db_writer_processe=4
                        db_block_lru_latches=4

So i then have  instance no.1 with 4 DBWR
and the other 3 intances with 1 DBWR each giving 7 DBWR in total for the whole
box.

Or does the metalink note <Note:97291.1> apply to the whole box so that i am
already at the maximum no of recomended DBWR process
ie 4 instances with 1 DBWR  giving 4 DBWR in total??

Please let me know what you think.
Many thanks

          Followup:

if IO is at 100% capacity of the disks -- to what end will adding more dbwr's
help here?

and you've determined it is DBWR "writes" that are the cause of an issue (and
not reads and not sort writes and not lgwr writes)

December 09, 2003 William -- Thanks for the question regarding
      "checkpoint not complete, cannot allocate new log", version

      You Asked
Tom,
I am receiving the following message in my alert log and can
find
no documentation explaining what this error means or how to
correct it.
Sun Feb 13 03:30:26 2000
>>>>>Thread 1 cannot allocate new log, sequence 41801
>>>>>Checkpoint not complete
Current log# 1 seq# 41800 mem# 0:
/kla/klaquest/dym/keep/redo1.log
Current log# 1 seq# 41800 mem# 1: /kla/qdb1/dym/keep/redo1a.log
Sun Feb 13 03:30:38 2000
Thread 1 advanced to log sequence 41801
Current log# 2 seq# 41801 mem# 0:
/kla/klaquest/dym/keep/redo2.log
Current log# 2 seq# 41801 mem# 1: /kla/qdb1/dym/keep/redo2a.log
Any advice would be much appreciated.

      and we said...

the infamous "checkpoint not complete, cannot allocate new log"
message.

this occurrs when Oracle attempts to reuse a log file but the
checkpoint that would flush the blocks that may have redo in
this log file has not yet completed -- we must wait until that
checkpoint completes before we can reuse that file -- thats when
this message is printed.  during this time (when we cannot
allocate a new log) processing is suspended in the database
while the checkpoint is made to complete ASAP.

The major way to relieve this is to have sufficient log to carry
you through peak times.  that way, we can complete the
checkpoint while you are not busy.

also make sure your checkpoints happen as fast as they can (eg:
enable ASYNC IO or configure >1 DBWR if ansyc IO cannot be used,
make sure disks are not contending with other apps and so on)

Another way is to make the log files smaller, hence increasing
the frequency with which we checkpoint (log checkpoint interval
and other init.ora parameters achieve the same effect btw).

I myself prefer to use a modest sized log file (around 25meg)
and will add logfiles until these messages cease...

Review &
      Followup

      Rating: 3
dont fully
      agree on the advise re redo log sizeing  January 10, 2002

Reviewer:  john felstead  from UK
Not so sure your last statement about sizing at 25M is a good one. Isnt that a
bit of a broad statement as you arent taking into account the size/throughput of
the database. Everything else makes sense but you could be giving people a bumb
steer with that size?

I try and aim at log switches every 20 minutes and never below 5 minutes during
highest batch updates. This needs a size of 350Mb per redo log on the DB's i
work on. I typically use 6 redo log groups. Am i doing something wrong in this?

          Followup:

I'll just add "your mileage may vary".  There are times when gigabyte sized redo
logs are good, times when 25meg are good, there may even be times when smaller
then 25m are good.

rules of thumb (ROT) are always made to be broken -- i don't have many "ROT"
myself.  25m is a "safe" starting spot (safer then the old starter size of 512k
anyway)..

      Rating: 3
Can not
      allocate Log file  March 18, 2002
Reviewer:
      sivababu  from GERMANY
Hello TOM
I got more database problems at different customer places. Today is not good
day. I'm getting the same error like above. I think i have to increase the
log_buffer, Log_checkpoint_interval and log_checkpoint_timeout. But you told in
the other question that we have to drop the log file and create it. Will it
effect existing database and how we can do it?. Can you pl give me the steps?.

Entry from alert.log:

Mon Mar 18 08:20:42 2002
Thread 1 advanced to log sequence 20021
  Current log# 1 seq# 20021 mem# 0: /opt/oracle/dbs/oradata/mangdbs/redo01.log
Mon Mar 18 08:21:15 2002
Thread 1 cannot allocate new log, sequence 20022
Checkpoint not complete
  Current log# 1 seq# 20021 mem# 0: /opt/oracle/dbs/oradata/mangdbs/redo01.log
Thread 1 advanced to log sequence 20022
  Current log# 2 seq# 20022 mem# 0: /opt/oracle/dbs/oradata/mangdbs/redo02.log
Mon Mar 18 08:21:50 2002
Thread 1 cannot allocate new log, sequence 20023
Checkpoint not complete
  Current log# 2 seq# 20022 mem# 0: /opt/oracle/dbs/oradata/mangdbs/redo02.log
Thread 1 advanced to log sequence 20023
  Current log# 1 seq# 20023 mem# 0: /opt/oracle/dbs/oradata/mangdbs/redo01.log
Mon Mar 18 08:24:14 2002
Thread 1 cannot allocate new log, sequence 20024
Checkpoint not complete

Entry from init.ora:

db_block_buffers = 8192
db_block_size = 2048
shared_pool_size = 125000000
large_pool_size=62914560
log_archive_start = FALSE
log_buffer = 163840
log_checkpoint_interval=10000
lock_checkpoint_timeout=1800

Is it possible to increase this way..

log_buffer = 2163840
log_checkpoint_interval=10000
lock_checkpoint_timeout=1800

Thanks in advance.

regards,
sivababu

          Followup:

nothing in your init.ora is going to affect this.  You need more log, you have
insufficient log to carry you through your load of work.  Add more logfiles.

Just add more logfiles -- see the alter database command for the syntax.

(you know, a 16meg buffer cache and a 125m shared pool, 62m large pool -- you
might have that BACKWARDS.... thats a HUGE shared pool, simply HUGE.  I normally
start at 50/60m or thereabouts).  A 160k log buffer is a tad small as well.

If this is a production instance and you are not in archive log mode (I'm
ASSUMING by having log_archive_start=false you are not) your DAY WILL ONLY GET
WORSE as the sudden loss of data tends to ruin peoples day and running in
noarchivelog is a 100% sure way to have a sudden loss of lots of data!

      Rating:
      5
clarification  February 25, 2003
Reviewer:
      David  from Nashville, TN
Tom,

I wonder if you could clarify something for me regarding the "cannot allocate
new log" message.  From our alert log (see below), it seems that log#2 would
have been available for Oracle to use.  Instead, our system grinds to a halt
until a checkpoint completes.

Alert Log Snippet (blank lines added for clarity):
----------------------------------------------------

Tue Feb 25 03:42:29 2003
Beginning log switch checkpoint up to RBA [0x19ff.2.10], SCN: 0x0000.644a08a2
Thread 1 advanced to log sequence 6655
  Current log# 1 seq# 6655 mem# 0: /redo01/oradata/eisp/redo1a.log
  Current log# 1 seq# 6655 mem# 1: /redo02/oradata/eisp/redo1b.log

Tue Feb 25 03:42:29 2003
ARC0: Beginning to archive log# 3 seq# 6654

Tue Feb 25 03:43:08 2003
ARC0: Completed archiving log# 3 seq# 6654

Tue Feb 25 04:11:27 2003
Beginning log switch checkpoint up to RBA [0x1a00.2.10], SCN: 0x0000.644a1165
Thread 1 advanced to log sequence 6656
  Current log# 2 seq# 6656 mem# 0: /redo01/oradata/eisp/redo2a_NEW.log
  Current log# 2 seq# 6656 mem# 1: /extra01/oradata/eisp/redo2b.log

Tue Feb 25 04:11:27 2003
ARC0: Beginning to archive log# 1 seq# 6655

Tue Feb 25 04:11:55 2003
ARC0: Completed archiving log# 1 seq# 6655

Tue Feb 25 04:19:39 2003
Completed checkpoint up to RBA [0x19ff.2.10], SCN: 0x0000.644a08a2

Tue Feb 25 05:00:05 2003
Completed checkpoint up to RBA [0x1a00.2.10], SCN: 0x0000.644a1165

Tue Feb 25 05:06:35 2003
Beginning log switch checkpoint up to RBA [0x1a01.2.10], SCN: 0x0000.644a1dbb
Thread 1 advanced to log sequence 6657
  Current log# 3 seq# 6657 mem# 0: /redo02/oradata/eisp/redo3a.log
  Current log# 3 seq# 6657 mem# 1: /extra01/oradata/eisp/redo3b.log

Tue Feb 25 05:06:35 2003
ARC0: Beginning to archive log# 2 seq# 6656

Tue Feb 25 05:07:00 2003
ARC0: Completed archiving log# 2 seq# 6656

Tue Feb 25 05:51:12 2003
Beginning log switch checkpoint up to RBA [0x1a02.2.10], SCN: 0x0000.644a251e
Thread 1 advanced to log sequence 6658
  Current log# 1 seq# 6658 mem# 0: /redo01/oradata/eisp/redo1a.log
  Current log# 1 seq# 6658 mem# 1: /redo02/oradata/eisp/redo1b.log

Tue Feb 25 05:51:12 2003
ARC0: Beginning to archive log# 3 seq# 6657

Tue Feb 25 05:51:35 2003
ARC0: Completed archiving log# 3 seq# 6657

Tue Feb 25 06:39:05 2003
Thread 1 cannot allocate new log, sequence 6659
Checkpoint not complete
  Current log# 1 seq# 6658 mem# 0: /redo01/oradata/eisp/redo1a.log
  Current log# 1 seq# 6658 mem# 1: /redo02/oradata/eisp/redo1b.log

Tue Feb 25 06:56:30 2003
Completed checkpoint up to RBA [0x1a01.2.10], SCN: 0x0000.644a1dbb

Tue Feb 25 06:56:30 2003
Beginning log switch checkpoint up to RBA [0x1a03.2.10], SCN: 0x0000.644a2b29
Thread 1 advanced to log sequence 6659
  Current log# 2 seq# 6659 mem# 0: /redo01/oradata/eisp/redo2a_NEW.log
  Current log# 2 seq# 6659 mem# 1: /extra01/oradata/eisp/redo2b.log

****************************************
As soon as a checkpoint was completed for [0x1a01.2.10], then log# 2 was able to
be allocated.  If I am reading the log correctly, it was not doing anything
anyway so why could it not be used prior to the checkpoint completing?  I'm
matching up the "beginning log switch checkpoint up to RBA [xxxx]" with the
"completed checkpoint up to RBA [xxxx]" to see that #1 and #3 were busy, but
what was #2 doing that it could not be allocated?  I think I'm repeating
myself...

Thanks for any insight,
David

          Followup:

here is the begin of the checkpoint on the data protected by log 2

Beginning log switch checkpoint up to RBA [0x1a01.2.10], SCN: 0x0000.644a1dbb
Thread 1 advanced to log sequence 6657
  Current log# 3 seq# 6657 mem# 0: /redo02/oradata/eisp/redo3a.log
  Current log# 3 seq# 6657 mem# 1: /extra01/oradata/eisp/redo3b.log

Arch finished copying log 2 for us here
Tue Feb 25 05:06:35 2003
ARC0: Beginning to archive log# 2 seq# 6656
Tue Feb 25 05:07:00 2003
ARC0: Completed archiving log# 2 seq# 6656

Tue Feb 25 05:51:12 2003
Beginning log switch checkpoint up to RBA [0x1a02.2.10], SCN: 0x0000.644a251e
Thread 1 advanced to log sequence 6658
  Current log# 1 seq# 6658 mem# 0: /redo01/oradata/eisp/redo1a.log
  Current log# 1 seq# 6658 mem# 1: /redo02/oradata/eisp/redo1b.log

Tue Feb 25 05:51:12 2003
ARC0: Beginning to archive log# 3 seq# 6657

Tue Feb 25 05:51:35 2003
ARC0: Completed archiving log# 3 seq# 6657

Tue Feb 25 06:39:05 2003
Thread 1 cannot allocate new log, sequence 6659
Checkpoint not complete
  Current log# 1 seq# 6658 mem# 0: /redo01/oradata/eisp/redo1a.log
  Current log# 1 seq# 6658 mem# 1: /redo02/oradata/eisp/redo1b.log

Here is where the checkpoint finished up -- the one started for number 2

Tue Feb 25 06:56:30 2003
Completed checkpoint up to RBA [0x1a01.2.10], SCN: 0x0000.644a1dbb

Tue Feb 25 06:56:30 2003
Beginning log switch checkpoint up to RBA [0x1a03.2.10], SCN: 0x0000.644a2b29
Thread 1 advanced to log sequence 6659
  Current log# 2 seq# 6659 mem# 0: /redo01/oradata/eisp/redo2a_NEW.log
  Current log# 2 seq# 6659 mem# 1: /extra01/oradata/eisp/redo2b.log

that is why is could not be reused -- the blocks it protects in the buffer
cache were NOT yet on disk...

Now, given how fast the archive went -- and how long it took to catch up -- I'd
be looking at "why"  - perhaps you are not checkpointing frequently enough (too
many dirty blocks) or dbwr is not "tuned" -- the disks are really slow?

      Rating: 2
archive log
      file on OPS  February 25, 2003
Reviewer:
      DoQuangHuy  from Vietnam
My database runs on OPS, redo log size is 8M, could you please tell me why
archive log file even increase (about 4-6 minutes) when my database has no user
connect to whole day. How can I check my database is running on load balancing
mode.

          Followup:

8m is pretty small for a high end system...

but anyway, use log miner (see the admin guide) to analyze what is in there.
tons of stuff -- queues, jobs running in the background with dbms_job, smon -
they all can generate redo...

You check your listener.ora and tnsnames.ora to see that it is setup that way.

      Rating: 3
archive
      log  February 26, 2003
Reviewer:  Reader

Tom,
I have 2 database, one runs on 8i OPS and the other runs on 8i (no OPS). When no
user connect to, database runs on OPS genarates 1 archivelog file per 4-6
minutes. I analyse archivelog files and select from v$logmnr_contents:

SQLWKS> SELECT operation,count(*) FROM v$logmnr_contents group by operation
     2>
OPERATION                        COUNT(*)
-------------------------------- ----------
COMMIT                                 8696
DELETE                                    5
INSERT                                    5
INTERNAL                              17903
START                                  8697
UPDATE                                   30
6 rows selected.

When no user connect to, database runs on 8i (no OPS) genarates only 1
archivelog file when database startup.
Could you explain the difference.

          Followup:

look at what "internal" operations are doing.

as i said, look for jobs, look for active sql, look for aq, log in and look

      Rating:
      5
Awesome  February 26, 2003
Reviewer:
      David  from Nashville, TN
Thanks so much!  I was thinking the "Beginning checkpoint..." applied to the log
number on the next line in the alert log.  I now see that all three logs were
"busy".

Our log_checkpoint_interval is 1000000 blocks and, yes, our disks are raid 5.
Changing the raid is possible long term.  In the near term we are going to try
reducing the size of the logs (256MB) and maybe shorten the checkpoint
interval...

      Rating:
      5
Excellent  February 26, 2003
Reviewer:
      A reader
Tom,
Great explaination of the alert log

      Rating:
      5
  March 02, 2003
Reviewer:  Jim

Nicely done

      Rating: 3
Submitted
      Enhancement Request  June 17, 2003
Reviewer:
      Ted Persky  from San Diego, CA USA
Dear Tom:

FYI, with the advent of Oracle-managed files, I have submitted Enhancement
Request No. 3004256 to create a new redo logfile group in the case of checkpoint
or archive not completed.  We'll see if it is accepted for a future release.

Best regards,

      Rating: 3
LGWR and DBWR
      for performance  August 07, 2003
Reviewer:
      ygp  from India
In your book, you mention that Log Writer is useful to improve the performance
since it does sequnetial writing. Using only DBWR (non-sequential writing) will
degrade the performance.

But, as I understand, the "checkpoint not complete, cannot allocate new log",
error *indicates* "DBWR cannot compete with LGWR".

Where do I fail to understand ?

          Followup:

Umm, it is not an "either or" thing - I don't know how you got that idea.

lgwr is a point of serialization in the database -- we all wait on him.

dbwr is a guy that goes on in the background.

the checkpoint not complete indicates that dbwr was not keeping up with lgwr and
is generally caused by undersizing your log files (or number thereof)

      Rating: 1
DBWR &
      LGWR  August 31, 2003
Reviewer:  ygp
       from INDIA
You said

"the checkpoint not complete indicates that dbwr was not keeping up with lgwr
and
is generally caused by undersizing your log files (or number thereof) "

I understand that they do totally diffeent works, but, LGWR has to wait for DBWR
to complete. What I was refering the *only* benifit of sequential writing by the
LGWR.

Suppose there are 10 Log files. By making it 100 or even 1000 does not guarantee
that the error would not occur.
At some point, sooner or later, DBWR has to go ahead of LGWR.

          Followup:

not ahead, just keep up with.

Yes, if you constantly run at full steam -- 100% -- you must architect a system
such that DBWR is able to keep up with lgwr.  this is done by having sufficient
devices and cpus to spread the scattered write work out.

what I said stands 100% though "IS GENERALLY caused by ....."  Most systems run
with peaks and valleys -- on these systems (the vast majority out there) the
solution is in fact to size logs such that they carry you over the peak and lets
the valleys catch up.

      Rating:
      4
rollback?  December 09, 2003
Reviewer:
      Duncan  from UK
Tom

I ran an update during the day which took 5hrs 45 mins to complete. It finished
at 6 pm and our cold backup didn't kick in until 3am the
next morning.  When i got in the next day i noticed that there were lots of
messages like:

"...Thread 1 cannot allocate new log, sequence 177759...."

in the alert log, which i gather is from redo logs not being large enough (we
don't normally get this).
I assume this was due to the disk intensive update i was running that day?

But also the database shutdown immediate began at 3am and didn't finish til
4.45am.  Can you shed some light on why this
would happen if the update finished at 6pm the previous evening?

The relevant parts of the ALERT log are listed below:

Mon Dec  1 18:03:04 2003
Thread 1 cannot allocate new log, sequence 177753
Checkpoint not complete
  Current log# 1 seq# 177752 mem# 0: /general/oradata/OP3/redo_1.log
  Current log# 1 seq# 177752 mem# 1: /tmptblsp/OP3/logfile/redo_2.log Mon Dec  1
18:03:26 2003 Thread 1 advanced to log sequence 177753
  Current log# 4 seq# 177753 mem# 0: /general/oradata/OP3/redo_7.log
  Current log# 4 seq# 177753 mem# 1: /tmptblsp/OP3/logfile/redo_8.log Mon Dec  1
18:03:26 2003
ARC0: Beginning to archive log# 1 seq# 177752
Mon Dec  1 18:03:39 2003
ARC0: Completed archiving log# 1 seq# 177752
Mon Dec  1 18:03:43 2003
Thread 1 cannot allocate new log, sequence 177754
Checkpoint not complete
  Current log# 4 seq# 177753 mem# 0: /general/oradata/OP3/redo_7.log
  Current log# 4 seq# 177753 mem# 1: /tmptblsp/OP3/logfile/redo_8.log Mon Dec  1
18:04:09 2003 Thread 1 advanced to log sequence 177754
  Current log# 5 seq# 177754 mem# 0: /general/oradata/OP3/redo_9.log
  Current log# 5 seq# 177754 mem# 1: /tmptblsp/OP3/logfile/redo_10.log Mon Dec
1 18:04:09 2003
ARC0: Beginning to archive log# 4 seq# 177753
Mon Dec  1 18:04:21 2003
ARC0: Completed archiving log# 4 seq# 177753
Tue Dec  2 03:00:02 2003
Restarting dead background process EMN0
EMN0 started with pid=16
Tue Dec  2 03:00:07 2003
Shutting down instance (immediate)
License high water mark = 45
Tue Dec  2 03:00:10 2003
ALTER DATABASE CLOSE NORMAL
Tue Dec  2 03:00:14 2003
ARC0: Beginning to archive log# 5 seq# 177754
Tue Dec  2 03:00:14 2003
Thread 1 advanced to log sequence 177755
  Current log# 2 seq# 177755 mem# 0: /general/oradata/OP3/redo_3.log
  Current log# 2 seq# 177755 mem# 1: /tmptblsp/OP3/logfile/redo_4.log Tue Dec  2
03:00:21 2003
ARC0: Completed archiving log# 5 seq# 177754
Tue Dec  2 03:00:22 2003
Thread 1 advanced to log sequence 177756
  Current log# 3 seq# 177756 mem# 0: /general/oradata/OP3/redo_5.log
  Current log# 3 seq# 177756 mem# 1: /tmptblsp/OP3/logfile/redo_6.log Tue Dec  2
03:00:22 2003
ARC0: Beginning to archive log# 2 seq# 177755
ARC0: Completed archiving log# 2 seq# 177755
Tue Dec  2 03:00:34 2003
Thread 1 advanced to log sequence 177757
  Current log# 1 seq# 177757 mem# 0: /general/oradata/OP3/redo_1.log
  Current log# 1 seq# 177757 mem# 1: /tmptblsp/OP3/logfile/redo_2.log Tue Dec  2
03:00:34 2003
ARC0: Beginning to archive log# 3 seq# 177756
ARC0: Completed archiving log# 3 seq# 177756
Tue Dec  2 03:00:46 2003
Thread 1 advanced to log sequence 177758
  Current log# 4 seq# 177758 mem# 0: /general/oradata/OP3/redo_7.log
  Current log# 4 seq# 177758 mem# 1: /tmptblsp/OP3/logfile/redo_8.log Tue Dec  2
03:00:46 2003
ARC0: Beginning to archive log# 1 seq# 177757

ARC0: Completed archiving log# 1 seq# 177757
Tue Dec  2 03:00:59 2003
Thread 1 cannot allocate new log, sequence 177759
Checkpoint not complete
  Current log# 4 seq# 177758 mem# 0: /general/oradata/OP3/redo_7.log
  Current log# 4 seq# 177758 mem# 1: /tmptblsp/OP3/logfile/redo_8.log Tue Dec  2
03:01:09 2003 Thread 1 advanced to log sequence 177759
  Current log# 5 seq# 177759 mem# 0: /general/oradata/OP3/redo_9.log
  Current log# 5 seq# 177759 mem# 1: /tmptblsp/OP3/logfile/redo_10.log Tue Dec
2 03:01:09 2003
ARC0: Beginning to archive log# 4 seq# 177758
ARC0: Completed archiving log# 4 seq# 177758
Tue Dec  2 03:01:20 2003
Thread 1 cannot allocate new log, sequence 177760
Checkpoint not complete
  Current log# 5 seq# 177759 mem# 0: /general/oradata/OP3/redo_9.log
  Current log# 5 seq# 177759 mem# 1: /tmptblsp/OP3/logfile/redo_10.log Tue Dec
2 03:01:41 2003 Thread 1 advanced to log sequence 177760
  Current log# 2 seq# 177760 mem# 0: /general/oradata/OP3/redo_3.log
  Current log# 2 seq# 177760 mem# 1: /tmptblsp/OP3/logfile/redo_4.log Tue Dec  2
03:01:41 2003
ARC0: Beginning to archive log# 5 seq# 177759
ARC0: Completed archiving log# 5 seq# 177759
Tue Dec  2 03:01:51 2003

...
...
...continues with these type of messages until:
...
...
...

Tue Dec  2 04:37:19 2003
Thread 1 cannot allocate new log, sequence 177836
Checkpoint not complete
  Current log# 2 seq# 177835 mem# 0: /general/oradata/OP3/redo_3.log
  Current log# 2 seq# 177835 mem# 1: /tmptblsp/OP3/logfile/redo_4.log Tue Dec  2
04:37:30 2003 Thread 1 advanced to log sequence 177836
  Current log# 3 seq# 177836 mem# 0: /general/oradata/OP3/redo_5.log
  Current log# 3 seq# 177836 mem# 1: /tmptblsp/OP3/logfile/redo_6.log Tue Dec  2
04:37:30 2003
ARC0: Beginning to archive log# 2 seq# 177835
ARC0: Completed archiving log# 2 seq# 177835
Tue Dec  2 04:38:12 2003
Thread 1 cannot allocate new log, sequence 177837
Checkpoint not complete
  Current log# 3 seq# 177836 mem# 0: /general/oradata/OP3/redo_5.log
  Current log# 3 seq# 177836 mem# 1: /tmptblsp/OP3/logfile/redo_6.log Thread 1
advanced to log sequence 177837
  Current log# 1 seq# 177837 mem# 0: /general/oradata/OP3/redo_1.log
  Current log# 1 seq# 177837 mem# 1: /tmptblsp/OP3/logfile/redo_2.log Tue Dec  2
04:38:19 2003
ARC0: Beginning to archive log# 3 seq# 177836
ARC0: Completed archiving log# 3 seq# 177836
Tue Dec  2 04:38:47 2003
SMON: disabling tx recovery
SMON: disabling cache recovery
Tue Dec  2 04:41:08 2003
Thread 1 closed at log sequence 177837
Tue Dec  2 04:41:11 2003
Completed: ALTER DATABASE CLOSE NORMAL
Tue Dec  2 04:41:12 2003
ALTER DATABASE DISMOUNT
Completed: ALTER DATABASE DISMOUNT
archiving is disabled
Tue Dec  2 04:41:13 2003
ARCH shutting down
ARC0: Archival stopped

Could this have been due to the transaction being uncommitted and thus all the
activity on shutdown was actually the
db trying to rollback?

          Followup:

if the transaction was uncommitted -- yes, this activity is the database rolling
back.

December 09, 2003 goran -- Thanks for the question regarding
      "performance problem with partitioning table", version 817

      You Asked
Hi Tom,

Here is a problem:
I have snapshot of one of our big tables and wanted to test how will be the
performance of that table if it is partitioned.
OK, here is a model:
New partitioned table with name orig_pos_histories_01_new is partitioned per
column opoh_valid_from on a monthly basis.
Old table called orig_pos_histories_orig. Tables have around 3Mil. records.
So, when I execute the following queries, the query with old table runs a little
bit faster than with the new table.
Here is a queries and explain plans:

For new one:
select *
from orig_position_histories_new
where opoh_valid_from = to_date('31.05.2002', 'dd.mm.yyyy');

EP:
Operation    Object Name    Rows    Bytes    Cost    Object Node    In/Out
PStart    PStop

SELECT STATEMENT Optimizer Mode=CHOOSE        416           7

  PARTITION RANGE SINGLE                                                   KEY
 KEY
    TABLE ACCESS BY LOCAL INDEX ROWID    ORIG_POSITION_HISTORIES_NEW    416
1 M    7                                 KEY    KEY
      INDEX RANGE SCAN    OPOH_NEW_LOC_VF    416           1
            KEY    KEY

Old one:
select *
from orig_position_histories_orig a, ids.orig_validity_dates b
where a.opoh_ovld_id = b.ovld_id
and b.ovld_validity_date = to_date('31.05.2002', 'dd.mm.yyyy');
Note:
this query have to be like this (with joij), old database desing

EP:
Operation    Object Name    Rows    Bytes    Cost    Object Node    In/Out
PStart    PStop

SELECT STATEMENT Optimizer Mode=CHOOSE        182 K         17825

  NESTED LOOPS        182 K    33 M    17825

    TABLE ACCESS FULL    ORIG_VALIDITY_DATES    22      1 K    5

    TABLE ACCESS BY INDEX ROWID    ORIG_POSITION_HISTORIES_ORIG    3 M    346 M
  810
      INDEX RANGE SCAN    OPOH_OVLD_FK_ORIG_I    3 M         48

So, what I can see is that new query have a cost of 7 and old one cost of 18000
and also all other parameters of new query are better that old one, but old one
somehow still run almost with the same speed like new one. So, it seems to me
that partition will not give me better performance.
I can not understand the reason of this behavior.

Thanks in advance.

Goran

      and we said...
Well, the myth you have to expel is "partitioning is like setting fast=true"

(i wrote about this in both my books -- expert one on one oracle and more
recently in Effective Oracle by Design -- you might be very interested in the
full discourse on this)

Partitioning is a tool.

It is a tool to make administration easier (divide and conquer)

It is a tool to make availability higher (one of your partitions could take a
hit and the others stay up and running while you repair one.  many people might
well never know there was a failure)

It is a tool that on a transactional system with lots of indexed reads you need
to employ carefully to ensure you do not NEGATIVELY impact performance.  It is a
tool on a warehouse with lots of "big operations" (full scans for example) you
can use to significantly reduce run times (assuming you've DESIGNED to exploit
that of course).

Look at the physics behind the data here.  You take a table and partition it.
To access the rows by that (assuming local) local index on "opoh_valid_from"
Oracle must:

a) determine what partition to access
b) index range scan that partition(s)
c) table access by index rowid the real data

Unpartitioned Oracle must:

a) index range scan that index
b) table access by index rowid the real data

There is no net reduction in work here! (maybe, just maybe if the index height
in the partitioned index is reduced -- you may remove a logical in the partition
case but that would only show up as a benefit "in the long haul" over many
queries executed simultaneously -- or over hundreds/thousands of queries in
sequence)

Your transactional system with this keyed read may well now benefit from
concurrent inserts/updates and deletes -- given that you have N index segments
(n root blocks to contend over, n branch blocks to contend over -- not just ONE)
and N table segments (N freelists at least, increased concurrency).

You have to be careful in many cases that partitioning doesn't KILL YOU
performance wise.  That you saw no net change is a good thing here -- it is not
"fast=true" for these types of queries (although if you do it wrong, it can
easily, easily mean slow=sure)

Review &
      Followup

      Rating:
      4
Review  December 07, 2003
Reviewer:
      Goran  from Germany
Hi Tom,
Thanks for answer.
Yes, what you said I have already experienced. But, what badger me is the
following:
Do you have from your experience case whare for example up to the certain number
of records queries on a non-partitioned table have better performance than on a
partitioned but, when number of records in table are bigger then some number, in
that case performance on partitioned table are better. If so, could you give me
example of such table?

Thanks a lot!

          Followup:

No not really, do you?

Look -- if you partition intelligently, you can make it so that performance is
either

o not really affected
o tiny bit better
o dramatically better (warehouse, bulky big queries, not single row accesses)

If you do it "wrong", just turn it on you can make performance go down the
tubes.

None of it has to do with "n" records.

All of it has to do with how the data is organized, the physics of the data.

      Rating:
      5
Review  December 07, 2003
Reviewer:
      goran  from Germany
Thanks Tom for answer.
Yes, you are right, the physics of the data is the key.
Partitoning is a hot thing. If one does not use it correctly, will have a lot of
pain.

Thanks a lot once more Tom!

Cheers!!!

      Rating: 5
just cross my
      mind  December 07, 2003
Reviewer:  Goran
       from Germany
Hi Tom,
Once more I with my problem...)))
This thinking just cross my mind and I would like to hear your opinion.
Let us say that - in above mentioned case - that I do not have local index on
partition. It will mean that when I run the query, DB will do a full table scan
on particular partition depending on a date. On other hand on a non-partitioned
table DB will still do range index scan. In both cases result set is about
200000 records.
If tables grows up from 3Mil to 30Mil, partition full table scan will not be
affected by table growth - DB will find partition for particular date and do
full table scan for 200000 i.e. partition full scan is not affected by table
growth.
For non-partitioned table, when table grow up from 3Mil to 30Mil, DB have to
find using index three 200000 records out of 30Mil. records.
If my logic is correct, that will means partition query should run faster or I
am wrong.

          Followup:

why wouldn't the partitioned table "still do index range scan" as well?

I'm not "following the logic" here.

the LIO's to get 20,000 records out of 30million or 3million is "about the same"

the LIO's to full scan a partition that did not grow in size will be "exactly
the same"

      Rating: 5
data
      distribution  December 08, 2003
Reviewer:
      Goran  from Germany
Thing is in data distribution. Partitions are on monthly basis, but data
distribution per months is something like this:
Example:
from 01th to 30th in month total number of records per month is 30%. The rest of
70% comes on 31th.
If the table is partitioned and I say give me all records for date 31 (and it is
let us say 200000 records) the execution time will be the same regardless of
table size - this is clear.
On other hand, for plain table where index exist for date column, I was
wondering whether index selectivity will remain the same for table that have
3Mil or 30Mil. rec. if index have to return me 200000 records with the same data
distribution.
If I have understand you correctly, you said that:
"the LIO's to get 20,000 records out of 30million or 3million is 'about the
same'"
lead me to conclusion that index scan no matter if he returning single column or
some big data set will always be faster than partitioning. It is clear to me
that number of index iterations between 3Mil and 30Mil table is not big,
but I just had in mind that maybe, just maybe when index have to return bulky
number of records, index performance will decrease a lot.

          Followup:

You are getting yourself confused.

partitioning is a tool

partitioning is NOT fast = true

I can partition your tables and make them behave the same as your single
non-partitioned table performance wise.

Partitioning will not necessarily speed up, slow down or whatever your
performance.

It is a tool to make things easier to admin, more available, more concurrent,
faster in warehouses with lots of smaller full scans due to partition
elimination and partition wise joins.

It is false to say:

lead me to conclusion that index scan no matter if he returning single column or

some big data set will always be faster than partitioning.

      Rating: 2
A little
      suggestion to Goran  December 08, 2003
Reviewer:
      Christo Kutrovsky  from Ottawa, ON Canada
Goram,

You said that from about ~200'000 rows for a month, 30% are from 1 to 30th, and
70% at 31st.

Why don't you partition a little bit more, and have 2 partitions per month. One
for 1-30 (or whatever the end date of the month) and another for the last day of
the month.

That way, if you query for 31st, you will have a FTS of a single partition,
which is much better then a index range scan.

P.S.
Don't forget to gather statistics on the partition level. You could eventually
benefit from histograms also, as your data is probably skewed. Use:
EXECUTE DBMS_STATS.GATHER_TABLE_STATS(ownname => 'lala', tabname => 'lala',
method_opt => 'FOR ALL COLUMNS SIZE AUTO', cascade => true);

      Rating: 4
When to
      partition  December 08, 2003
Reviewer:
      Richard  from Ashburn VA
Goran -

According to the instructor of my Oracle 9i Implement Partitioning course, 5GB
is about the size you should look for before implementing table partitioning,
however size is just one of the variables to review.  Tom brings up several
valid points above.  If performance is your issue you need to review the concept
of "partitioning pruning" where the CBO will actually eliminate partitions from
the partition access list.

          Followup:

did anyone ask them "why, why 5gig, what was the science behind that number.
why not 4gig?

what if I have a DW with a 4 gig table -- with 4 regions and many full scans --
but by region.  would not 4 1gig segments make perfect sense for that??

what if I have an audit trail. must keep 12 months online.  must roll off oldest
month every month. I generate about 1.5 million audit records a month, 100 bytes
per record.  about 2gig for a year.  would not partitioning make perfect sense
for me??

size is not a relevant variable if you ask me, at least not on the low end.
Maybe a more accurate statement would have been "If your segments are getting
into the 5 gig and above range, partitioning is something you want to start
looking at for administrative and availability reasons -- if you haven't started
looking at it yet"

always question authority, push people to back up their statements, regardless
of who is saying it.

      Rating:
      5
Review  December 09, 2003
Reviewer:
      Goran  from Germany
I think that Tom is right.
Number of records in table does not play role whether the table should be
partitioned or not. For a plain table if we are retrieving via index scan 200000
records out of 3Mil. or 30Mil. table (as Tom already said) is similar. So, no
big improvement on that level. As Tom already said, the physics of the data is
the key.
Basically, what I have tested and sow from the test, performance improvement I
will have (in my case) only for bulky big queries where instead of index scan we
have full table scan.
Local index scan gave me no big performance improvement - for some cases a
little bit better for some the same exec. time.
Here I would like to ask Tom a question.
I have found in documentation that local indexes can improve performance since
many index partitions can be scanned in parallel by range queries on the index
key. On the other hand, on some other topic here I have found that you said
something like local index scan can be very expensive if oracle have to scan
more than one partition.
I will try to give you an example:
If table is partitioned per date column and on each partition I have local index
on columns (opoh_valid_from, opoh_sdsy_id) and if I execute query like this:

select *
from orig_pos_hist_new
where opoh_valid_from < to_date('31052002', 'ddmmyyyy')
and opoh_sdsy_id = 123

execution plan is:

Operation    Object Name    Rows    Bytes    Cost    Object Node    In/Out
PStart    PStop

SELECT STATEMENT Optimizer Mode=CHOOSE        15 K         116

  PARTITION RANGE ITERATOR                                                   1
 KEY
    TABLE ACCESS BY LOCAL INDEX ROWID    ORIG_POSITION_HISTORIES_NEW    15 K
37 M    116                                 1    KEY
      INDEX RANGE SCAN    OPOH_NEW_SDSY_VF_LI    15 K         85
                1    KEY

So, what I can see is that database is doing local index scan for more then one
partition.
Question: Is it done in parallel (automatically) or serial i.e. index scan on
each partition (which is qiute expensive)?

          Followup:

it can be done in parallel using parallel query but it will default to serial
which for most index cases you'll find faster then setting up parallel!!  (eg:
index implies few row *in general* and for few rows, PQ is too expensive)

In your case, there are lots of rows, PQ might be called for IF and ONLY IF you
have excess capacity on your machine (can afford the cost of PQ)

      Rating: 4
for
      Christo  December 09, 2003
Reviewer:  Goran
       from Germany
Thanks Christo,
I did already something simillar.
Anyway, you have good way of thinking.

Thanks,

Goran

December 09, 2003 Walter -- Thanks for the question regarding
      "TNS_ADMIN", version Client 8.1.6 for NT

      You Asked
I find that SQL-Plus ignores TNS_ADMIN environment variable,
while Net8 Assistant uses it.  Also SQL-Plus understands an
"ifile=" statement in tnsnames.ora, while Net8 Assistant
doesn't.  The question is:

Since I want one centralized tnsnames.ora for my developer
population (who are connecting to 8.1.6 on HP-UX 11.0 via 8.1.6
Client on NT), is it sufficient for ALL Oracle products to have
each developer set up a TNS_ADMIN environmental variable
pointing to the centralized tnsnames.ora AND have each developer
set up a local-to-his-pc tnsnames.ora with an "ifile=" statement
pointing to the centralized tnsnames.ora?  Whew ;).

      and we said...
SQLPlus on NT does use the TNS_ADMIN variable.  Consider:

C:\Documents and Settings\Thomas Kyte\Desktop>sqlplus
scott/tiger@ora8i.us.oracle.com

SQL*Plus: Release 8.1.6.0.0 - Production on Thu Aug 3 11:24:56
2000
(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Enterprise Edition Release 8.1.5.0.0 - Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.0.0 - Production

scott@8i> exit
Disconnected from Oracle8i Enterprise Edition Release 8.1.5.0.0
- Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.0.0 - Production

That shows I can get connected with the tns entry.  Now, I'll
set my TNS_ADMIN

C:\Documents and Settings\Thomas Kyte\Desktop>set
TNS_ADMIN=c:\temp

And make the tnsnames.ora file in that directory "empty"

C:\Documents and Settings\Thomas Kyte\Desktop>echo # comment >
c:\temp\tnsnames.ora

C:\Documents and Settings\Thomas Kyte\Desktop>sqlplus
scott/tiger@ora8i.us.oracle.com

SQL*Plus: Release 8.1.6.0.0 - Production on Thu Aug 3 11:25:20
2000

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

ERROR:
ORA-12154: TNS:could not resolve service name

Enter user-name: ^Z

C:\Documents and Settings\Thomas Kyte\Desktop>

So, that shows that sqlplus does use the tns_admin setting --
it has to, they all use the same support libraries to get
connected.

Now, you might not have the environment variable set at the
right level, you need to make sure the the SQLPlus environment
has it.

It should be sufficient to simply have each set a SYSTEM
environment variable of TNS_ADMIN pointing to the remote
tnsnames.ora file.  IFILE is not supported for
tnsnames/listener/sqlnet/protocol.ora files.

Oracle Names (a database nameserver) can satisfy this as well.

Review &
      Followup

      Rating: 5
It works
      but....  December 19, 2002
Reviewer:  Sujata
       from Germany
Tom,
I set the TNS_ADMIN system variable on an XP box to point to one of our Windows
2000 box TNSNAMES.
Our front end is ASP.NET.
SQLPLUS works O.K...but when we try to connect through a web page (using
IIS)..we get ORA-12154 error...
Any ideas...

Thanks

          Followup:

IIS's environment doesn't have the TNS_ADMIN set then....  or you are using the
wrong tns alias in your setup.

create a small IIS routine that just prints out what the tns admin is set to.

      Rating:
      5
IIS  December 19, 2002
Reviewer:
      Sujata  from Germany
"IIS's environment doesn't have the TNS_ADMIN set then....  or you are using the

wrong tns alias in your setup.

create a small IIS routine that just prints out what the tns admin is set to."

When I set up the TNS_ADMIN variable...I went to the system environment
variables (on client XP) and added another system variable TNS_ADMIN..with the
path for the remote TNSNAMES file on a Windows 2000 box (Database is on HP UNIX
box)
So, are you saying the client box needs another entry for the IIS...where would
that entry go?

Thanks.

          Followup:

did you restart the server?  the environment would not be propagated to already
running stuff.

also -- most web servers "hide" the environment from the clients unless you tell
them what to pass down (most web servers being apache ;)

maybe iis has the same thing.

best bet - just write a little iis program to dump the environment, see what is
there.

      Rating: 5
TNS_ADMIN /
      IIS  December 19, 2002
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Unsolicted $0.02...

Since IIS runs as a service, it can't see things like mapped drives.  IIS does
not run in a context like a 'normal user environment'.  Typically it runs as
'Local System'.   If TNS_ADMIN on the box running IIS is pointing to a location
on a mapped drive, IIS would not be able to see it...

-Mark

      Rating:
      5
IIS  December 19, 2002
Reviewer:
      Sujata  from U.S.A.
Rebooted the client machine...still the same error...

          Followup:

so, write that little program -- see what the tns admin is SET TO in IIS

not being a guy who has ever touched IIS (hard to get going on my pc and all
without windows), I'll not be the best resource for this question...

      Rating:
      5
Protocol.Ora  December 09, 2003
Reviewer:
      Bipin Ganar  from INDIA
Hi Tom,
I come to know that you can use restricted IP's to get into the database thro'
SQLPLUS by the use of protocol.ora Where this file should be in the
server/Client ,
what parameters i can set in this and uses of each parameter?

          Followup:

it's on the server

it's documented in the documenation -- see otn.oracle.com -> documentation and
get the network admin guides

December 09, 2003 Moin -- Thanks for the question regarding "redo
      log generation", version 8.1.7

      You Asked
Hi Tom,

I'm, in the dev stage of a Data warehouse project.

I've insisted that the developers provide adequate data for testing, to help
avoid any nasty surprises when we go live, also so that I can size redo logs,
rollback segs etc correctly.

The question I have is, when I insert 1.5 million rows of data avg_row_size =
170 bytes  so therefore about 255 megs of data in the table itself is generated.
There is one index that will require another 130 megs of datrabase space --
Total about 400megs DB space.

But why do I get in my archived log destination about 2gb of redo logs (no other
users on the box).  I realise redo logs contain undo as well as redo but why
such a big difference in redo log size to actual DB space used to store the
data?

      and we said...
They are undo + redo + all of the block changes to that index structure which
are massive.

In a true data warehouse, you should be striving to achieve NON-logged
operations when possible, doing a hot backup after the operation to provide for
recovery.

I would be direct path loading the data into a no-logging table.

I would be using skip_index_maintenance.

I would be rebuilding the indexes afterwards in parallel with nologging.

Consider this example.  insert = regular load.  insert /*+ append */ = direct
path load.  insert /*+ append */ into nologging table = direct path load with no
log.

ops$tkyte@ORA9I> create table t as select * from all_objects where 1=0;
Table created.

ops$tkyte@ORA9I> set autotrace traceonly statistics;

ops$tkyte@ORA9I> insert into t select * from all_objects;
41855 rows created.

Statistics
----------------------------------------------------------
    4629616  redo size

4.6meg of redo...

ops$tkyte@ORA9I> rollback;
Rollback complete.

ops$tkyte@ORA9I> insert /*+ append */ into t select * from all_objects;
41855 rows created.

Statistics
----------------------------------------------------------
    4724292  redo size

the same for direct path..

ops$tkyte@ORA9I> rollback;
Rollback complete.

ops$tkyte@ORA9I> alter table t nologging;
Table altered.

ops$tkyte@ORA9I> insert /*+ append */ into t select * from all_objects;
41855 rows created.

Statistics
----------------------------------------------------------
        984  redo size

but make the table nologging and it is hugely different

ops$tkyte@ORA9I> rollback;
Rollback complete.

ops$tkyte@ORA9I> create index t_idx on t(object_name);
Index created.

ops$tkyte@ORA9I> insert /*+ append */ into t select * from all_objects;
41856 rows created.

Statistics
----------------------------------------------------------
    6024152  redo size

but the index blows it again.  note that more redo for index maintenance is
generated then for the ENTIRE TABLE -- 6m

ops$tkyte@ORA9I> rollback;
Rollback complete.

ops$tkyte@ORA9I> alter index t_idx unusable;
Index altered.

ops$tkyte@ORA9I> alter session set skip_unusable_indexes=true;
Session altered.

ops$tkyte@ORA9I> insert /*+ append */ into t select * from all_objects;
41856 rows created.

Statistics
----------------------------------------------------------
      12164  redo size

so, that is like a direct path load into a non-logged table with skip index
maintenance -- no redo to speak of

ops$tkyte@ORA9I> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  --and b.value > 0
  6  /

NAME                                VALUE
------------------------------ ----------
redo size                        24128888

ops$tkyte@ORA9I> alter index t_idx rebuild nologging;

Index altered.

ops$tkyte@ORA9I> @mystat redo
ops$tkyte@ORA9I> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  --and b.value > 0
  6  /

NAME                                VALUE
------------------------------ ----------
redo size                        24135956

and as you can see the rebuild didn't generate any either really...

So, suggest you follow that approach -- just remember -- hot backup the affected
datafiles as soon as possible after the load! so you have something you can
recover from...

Review &
      Followup

      Rating: 5
redo log
      generation  December 24, 2002
Reviewer:
      A reader
Great reply (as ever).

One thing though, I've set up autotrace as per your instructions in your book
however I dont get any info like redo logs stats, why's that.  Is it a version
thing I'm on 8.1.7.4?

Thanks Again

          Followup:

No, it is not a version thing...

Make sure

o you have the plustrace role
o you see no errors when you issue "set autotrace on"
o you are not running this as SYS, don't do anything as SYS -- SYS is special
  and autotrace doesn't work for SYS

      Rating:
      5
  December 24, 2002
Reviewer:
      A reader
AS always best explanation!!!!

      Rating:
      5
  December 24, 2002
Reviewer:
      A reader
Hi Tom,
I am getting  clear about NOLOGGING option.
Great answer.

 One question.

 Does the LOG_MODE( e.g archine or noarchive) affect redo generation?

 I am testing this in both env.

 I get more redo in archivelong then noarchivelog mode.

 If you test with APPEND hint in archivelog -- lots of redo .
 In Noarchivelog redo is minimal.

 Please advice

          Followup:

Yes, nologging of the table is only needed in archive log mode.  If you have a
noarchive log mode database and do a direct path operation on it - no log needs
to be generated.

Eg: create table as select - in archivelog mode = lots of redo.  in noarchivelog
mode = tiny redo.

You need to set the table attributes to NOLOGGING exactly like my example above
does in archivelog mode!

      Rating: 5
Amount of redo
      generated for DDL statements  July 25, 2003

Reviewer:  A reader
Tom,
Would appreciate it if you can guide me on the following

1) Do DDL statements generate a huge amount of redo?
I would like to know if there is any big difference in redo generation for the 2
statements below.

Statement 1
ALTER TABLE TABLE1 ADD (XYZ_IND NUMBER(1));

Statement 2
ALTER TABLE TABLE1 ADD (XYZ_IND NUMBER(1) DEFAULT 1 NOT NULL );

The no. of records in the table 'TABLE1' is about 400K.  We have a standby db in
the production environment and we would like to know if the DDL Statemnet 1
affects the redo logs going into the standby DB.

Thank you

          Followup:

depends (as everything does)

take your example, the first will generate almost no redo, the second -- tons,
it has to modify every row in every block -- AND we have to be able to redo that
change in the event of media failure (as well as UNDO the change in the event of
an instance failure whilst it is running)

both will affect the redo logs, just to differing amounts.

      Rating: 5
Amount of redo
      generated for DDL statements   July 25, 2003

Reviewer:  A reader
Thank you

      Rating: 5
redo generated
      by a particular session  August 23, 2003
Reviewer:
      A reader
Tom,
What is the best approach to finding out the amount of redo generated by a
particular session?  Our DBA is restricting the type of transcations we carry
out, not allowing queries which update/insert huge # of records in one
transcation.  The concern is about the effect the generated redo will have on
the network pipe carrying it to the standby database.

Thank you

          Followup:

why?  do they want the system to run less efficiently?  do they want the system
to do more work?  do they want the system to have artificial constraints on it
that break the real transaction into tiny bits that then must be carefully
coded?

by breaking a transaction up into itty bitty pieces -- they will only accomplish
the above.

you will generate MORE redo in total.
you will consume MORE resources in total.
you will have hard to maintain code.
you will have questionable data integrity.

is that their goal?  can you clarify that with them?

      Rating: 5
redo generated
      by a particular session   August 23, 2003

Reviewer:  A reader
All the points you have mentioned are true
>>you will generate MORE redo in total.
>>you will consume MORE resources in total.
>>you will have hard to maintain code.
But the network bandwidth to the standby database is restricting the amount of
redo generated.
The situation we are running into is where we may have to update or insert 600K
records say in about 10 tables.  Instead of accomplishing this in a single
transcation or a very few transcations (say less than 10), the DBA is suggesting
that we run the SQLs as a job executing a 1000+ or so records every 15 minutes,
doing so we will be restricting the amount of redo generated that needs to go
through the NETWORK PIPE be applied on the standby database.
To determine the # of records that can be run every 15 min, we would like to
know the avergae redo generated by the transcation(or a particular session).

          Followup:

sounds like you need to fix the pipe to satisfy your requirements.

are you "maximum protection" or "maximum performance" or just using the archived
logs?

      Rating: 5
Does redo info
      show up in sql trace file  October 10, 2003

Reviewer:  Sam  from San Mateo
Tom,

Does a sql trace file contain information on how much redo is generated by an
insert/update statement? Or must we query it from v$mystat?

          Followup:

v$mystat

waits and binds can show up in the trace -- not statistics like that.

      Rating: 5
Determining
      redo from a PL/SQL anonymous block  November 05, 2003

Reviewer:  Steve Baldwin  from Melbourne, Australia

Great info !!

I have a similar problem and I'm trying to use your described mechanism.  In
SQL*Plus, I issue the 'set autotrace ...' command, and it works just fine for
SQL inserts.  However, my process is in a PL/SQL package, and I'm attempting to
'simulate' it using an anonymous PL/SQL block.  When I execute the block, the
insert happens, but I don't see any stats.  Is there another mechanism to get
this info ?

Thanks

          Followup:

you can use v$mystat for this:

ops$tkyte@ORA920LAP> create table t as select * from all_objects where 1=0;

Table created.

ops$tkyte@ORA920LAP> column value new_val V
ops$tkyte@ORA920LAP> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and a.name = 'redo size'
  5  /

NAME                                VALUE
------------------------------ ----------
redo size                         1157852

ops$tkyte@ORA920LAP> begin
  2          insert into t select * from all_objects;
  3  end;
  4  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> select a.name, b.value, b.value-&V DIFF
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and a.name = 'redo size'
  5  /
old   1: select a.name, b.value, b.value-&V DIFF
new   1: select a.name, b.value, b.value-   1157852 DIFF

NAME                                VALUE       DIFF
------------------------------ ---------- ----------
redo size                         4599572    3441720

      Rating: 5
What if I can't
      do an INSERT INTO ... SELECT ...  November 05, 2003

Reviewer:  Steve Baldwin  from Melbourne, Australia

Does the /*+ append */ hint still work with an INSERT ... VALUES (...) statement
?  When I execute a PL/SQL block like this ...

begin
  for i in 1 .. 100000 loop
    insert /*+ append */
    into   my_table (col_1, col_2, col_3)
    values (i, 2506458, 119) ;
  end loop ;
end ;
/

I still get lots of redo, even if I set nologging for my_table.

I have a scenario where if I try a single INSERT ... SELECT, I exceed the
capacity of my undo tablespace.  I suppose I could try segmenting the SELECT,
and then issuing multiple INSERT .. SELECT's, but at this point, I'm using
dbms_sql to fetch and then insert 1000 rows at a time, with commits after every
10000.  I ultimately need to insert around 5,000,000 rows.

Also, I found that marking indexes as unusable didn't quite do it for me.  I
needed to drop the primary key (because skip_unusable doesn't work for unique
indexes).  I also needed to disable all constraints on the table.  I'm not sure
if it was the FK or Check constraints that were the culprits, but until I did
this, I was still getting lots of redo.

As you may have ascertained, this is not a Data Warehouse app.  I'm actually
doing a data conversion from an old system to a new one (hence the constraints).
 I'm now planning to do the disable/insert/enable of constraints and
drop/insert/create of indexes during the conversion process.

          Followup:

no, append works for BULK OPERATIONS ONLY.

it would make no sense for row at a time.  suppose it did work, guess what you
would have ended up with?  100,000 rows each on their OWN BLOCK with 4 empty
blocks accompanying them.  wouldn't that be great?  (append writes above the
HWM, does not, cannot use existing free space)

your DBA has failed in their job duty.  they have not sized the system for the
amount of work it has been required to do.

committing after each 1000 -- yick, ugh, i wouldn't stand for it.

i would be doing a single, fast, efficient, least resource intensive insert into
select from.

a one time conversion -- tell the dba staff to do their job, beef up the storage
in order to facilitate the work required of the system.  You'll save tons of
money -- even if you have to buy more disk.  look at the time you are wasting
here trying to "come up with creative, yet hard to code and hard to debug
solutions"

      Rating: 5
SELECT'ing
      SEQUENCE.NEXTVAL another culprit  November 05, 2003

Reviewer:  Steve Baldwin  from Melbourne, Australia

After dropping all indexes and constraints, and segmenting my insert so that it
didn't blow up my UNDO tablespace, I was still getting bucket loads of redo
logs.

I tracked it down to the way I was populating the primary key - by selecting
any_sequence.NEXTVAL.  When I changed that to ROWNUM, the amount of redo for
inserting around 280,000 rows went from 10MB to 3144 BYTES !!!

          Followup:

alter sequence any_sequence cache 1000000;

before the load, it'll cause an update to seq$ every 1000000 rows, not every 20.
 will hugely speed up the sequence.

i would still be going for "single sql"

      Rating: 5
redo generated
      by clob update  December 09, 2003
Reviewer:
      A reader  from Houston
Tom,
We are looking at replacing certain characters in the clob field of a table.

SELECT column1_clob into v_var1 FROM table1 WHERE id = v_id for Update;

...
...
dbms_lob.createtemporary(v_var2, TRUE);
v_var2    := v_var1;
v_cnt    := dbms_lob.instr(v_var2,'\&',1,1);
if v_cnt > 0 then
    v_var2    :=replace(v_var2,'\&','\&amp;');
end if;

v_cnt    := dbms_lob.instr(v_var2,'<',1,1);
if v_cnt > 0 then
    v_var2    :=replace(v_var2,'<','\&lt;');
end if;

.....
v_len := dbms_lob.getlength(v_var2);

DBMS_LOB.COPY(v_var1,v_var2,v_len,1,1);
COMMIT;
dbms_lob.freetemporary(v_var2);

My questions

I read in your book expert one-on-one Oracle that update genarates the max. redo
(compared with inserts and delets..)
a) Updates to clob generate significant amount of redo - right?
b) Does the code above (particularly DBMS_LOB.COPY)generate significant amount
of redo?
c) Every transcation generates redo - right?

Thank you

          Followup:

a) depends on whether clob is logging or nologging, but it'll be a factor of
"how much work do i do" -- here you are rewriting the entire image.

b) it could

c) absolutely -- even non-logged ones generate some amount of redo.

      Rating: 5
redo generated
      by clob update  December 09, 2003
Reviewer:
      A reader  from Houston
Tom,
>>a) ....here you are rewriting the entire image.
since I am writing the entire image and if the clob size is about 10K charc's -
is it fair to say that a good amount of redo will be generated - assuming that
the table is in logging mode and no direct path inserts are made.

Thank you

          Followup:

it is fair to say that enough redo to redo the operation will be generated!
thats about all i can say here.

it is something you can easily measure.  query v$my_stat, see how much redo you
generated so far, update a couple of rows, requery and subtract.  that'll show
you.

December 09, 2003 Francisco -- Thanks for the question regarding
      "Returning a refcursor and use it in IN clause.", version 8.1.7.4

      You Asked
Hi Mr. Tom.

I have a doubt about how to use a result set in a "IN" clause of query.

I am using the next example:

SELECT HOU.ORGANIZATION_ID,
       MP.ORGANIZATION_CODE,
       HOU.NAME
  FROM HR_ALL_ORGANIZATION_UNITS HOU,
       MTL_PARAMETERS MP
 WHERE HOU.ORGANIZATION_ID = MP.ORGANIZATION_ID
   AND HOU.ORGANIZATION_ID IN (select ICC_IR_GET_USER_ORGANIZACIONES('USR1')
from dual)
ORDER BY MP.ORGANIZATION_CODE;

This select ICC_IR_GET_USER_ORGANIZACIONES('USR1') from dual) statement returns
a ref cursor.

My question is how Can I use this result set to compare in a "IN" clause?

I need to pass the USER_ID ("USR1") like parameter, then I get the organizations
which this user has access.

Any info will be really appreciated.

TIA.

      and we said...
You cannot use a ref cursor like that.

You'll have to write YAF (yet another function) like this:

ops$tkyte@ORA920> create or replace type myTableType as table of varchar2(30)
  2  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package my_pkg
  2  as
  3
  4      type rc is ref cursor;
  5      function get_ref_cursor( p_inputs in varchar2 ) return rc;
  6      function get_ref_cursor_as_table( p_inputs in varchar2 ) return
myTableType;
  7
  8  end;
  9  /

Package created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package body my_pkg
  2  as
  3      function get_ref_cursor( p_inputs in varchar2 ) return rc
  4      is
  5          l_cursor rc;
  6      begin
  7          open l_cursor for select username from all_users where username
like p_inputs;
  8          return l_cursor;
  9      end;
 10
 11      function get_ref_cursor_as_table( p_inputs in varchar2 ) return
myTableType
 12      is
 13          l_cursor rc;
 14          l_temp   varchar2(30);
 15          l_data   myTableType := myTableType();
 16      begin
 17          l_cursor := get_ref_cursor( p_inputs );
 18          loop
 19              fetch l_cursor into l_temp;
 20              exit when l_cursor%notfound;
 21              l_data.extend;
 22              l_data(l_data.count) := l_temp;
 23          end loop;
 24          close l_cursor;
 25          return l_data;
 26      end;
 27  end;
 28  /

Package body created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select username, created
  2    from dba_users
  3   where username in ( select *
  4                         from TABLE( cast( my_pkg.get_ref_cursor_as_table(
'SYS%' ) as myTableType ) )
  5                        where rownum >= 1 )
  6  /

USERNAME                       CREATED
------------------------------ ---------
SYS                            12-MAY-02
SYSTEM                         12-MAY-02

Review &
      Followup

      Rating: 5
Another
      solution  May 07, 2003
Reviewer:  Francisco
      Martínez Oviedo  from San Nicolás de los Garza, N.L.
Thanks a lot Tom.

Always you provide me with all the solutions, and I really appreciate it.

I am still waiting for your coming book, all the best for this one too.

I have tested another solution from your site, I just saw it yesterday.

This is I have tested.

create or replace type user_values as object (
value_id            varchar2(30))
/

create or replace type t_user_values as table of user_values
/

CREATE OR REPLACE FUNCTION ICC_IR_GET_USER_VALUES
(p_user_id       IN VARCHAR2,
 p_tipo_info     IN VARCHAR2)
RETURN t_user_values
AS
   some_data t_user_values := t_user_values();
BEGIN
   FOR x IN (SELECT ELE.VALOR
               FROM ICC_IR_USUARIO_APLICACION      APL,
            ICC_IR_PERFIL_GRUPO               GRP,
            ICC_IR_CODIGOS_REFERENCIA      REF,
            ICC_IR_GRUPO_INFO_ELEMENTOS    ELE
           WHERE APL.USUARIO       = p_user_id
               AND REF.APLICACION_ID = 1
           AND REF.NUMERO_TABLA  = 100
           AND REF.VALOR           = p_tipo_info
           AND GRP.PERFIL_ID     = APL.PERFIL_ID
           AND GRP.TIPO_GRUPO    = REF.DESCRIPCION
           AND GRP.GRUPO_INFO_ID = ELE.GRUPO_INFO_ID
          ORDER BY ELE.VALOR)
      LOOP
         some_data.extend;
             some_data(some_data.count) := user_values(x.VALOR);
      END LOOP;

      RETURN some_data;

   EXCEPTION
      WHEN NO_DATA_FOUND
      THEN
         RETURN null;
END;
/

--Testing the function
select *
from table(cast(ICC_IR_GET_USER_VALUES('USR1','ORG') as t_user_values))
/

VALUE_ID
------------------------------
319
326

--Testing my complete statement.

SELECT HOU.ORGANIZATION_ID,
       MP.ORGANIZATION_CODE,
       HOU.NAME
  FROM HR_ALL_ORGANIZATION_UNITS HOU,
       MTL_PARAMETERS MP
 WHERE HOU.ORGANIZATION_ID = MP.ORGANIZATION_ID(+)
   AND HOU.ORGANIZATION_ID IN (select *
                 from table(cast(ICC_IR_GET_USER_VALUES('USR1','ORG') as
t_user_values)))
ORDER BY MP.ORGANIZATION_CODE
/

ORGANIZATION_ID ORG NAME
--------------- --- ---------------------------------------------
            326 CF1 OI_ORGANIZATION1
            319     UO_ORGANIZATION2

Thanks a lot.

Best regards!!!!

      Rating:
      4
  May 07, 2003
Reviewer:  Mike
       from San Jose, CA
Re Function "get_ref_cursor_as_table"
  -  why didn't you use bulk collect to fetch the ref cursor into a table? From
performance perspective which is better?

          Followup:

excellent point -- there was more less unsaid then said here.  there was a
definite reason...

question was in 817 timeframe.

as written, I could have:

    function get_ref_cursor_as_table( p_inputs in varchar2 ) return myTableType
    is
        l_cursor rc;
        l_data   myTableType := myTableType();
    begin
        l_cursor := get_ref_cursor( p_inputs );
        fetch l_cursor BULK COLLECT into l_data;
        close l_cursor;
        return l_data;
    end;

however, I assumed they probably really used dynamic sql and didn't want them to
get frustrated with:

ops$tkyte@ORA817DEV> select username, created
  2     from dba_users
  3    where username in ( select *
  4                          from TABLE( cast( my_pkg.get_ref_cursor_as_table(
'SYS%' ) as myTableType ) )
  5                          where rownum >= 1 )
  6  /
                        from TABLE( cast( my_pkg.get_ref_cursor_as_table( 'SYS%'
) as myTableType ) )
                                          *
ERROR at line 4:
ORA-01001: invalid cursor
ORA-06512: at "OPS$TKYTE.MY_PKG", line 18
ORA-06512: at line 1

which would happen in 817 if you coded:

    function get_ref_cursor( p_inputs in varchar2 ) return rc
    is
        l_cursor rc;
    begin
        open l_cursor for 'select username from all_users where username like
:p_inputs' using
        p_inputs;
        return l_cursor;
    end;

using dynamic sql.

      Rating: 5
Ref-cursor,
      update-statements, analytical functions  May 09, 2003

Reviewer:  Kamran  from Pakistan
I 'm consulting this site for last one and half years.
I found it most useful site on Oracle regarding consultation
A quick response and very useful material in a very precise
way is present all time.

      Rating: 3
why the extra
      code?  December 09, 2003
Reviewer:
      A reader
create or replace type myTableType as table of varchar2(30)
/

create or replace package my_pkg
as
        type rc is ref cursor;
--        function get_ref_cursor( p_inputs in varchar2 ) return rc;
        function get_ref_cursor_as_table( p_inputs in varchar2 ) return
myTableType;
end;
/

create or replace package body my_pkg
  as
--      function get_ref_cursor( p_inputs in varchar2 ) return rc
--      is
--          l_cursor rc;
--      begin
--          open l_cursor for select username from all_users where username like
p_inputs;
--          return l_cursor;
--      end;

      function get_ref_cursor_as_table( p_inputs in varchar2 ) return
myTableType
      is
          l_cursor rc;
          l_temp   varchar2(30);
          l_data   myTableType := myTableType();
      begin
          open l_cursor for select username from all_users where username like
p_inputs;
--          l_cursor := get_ref_cursor( p_inputs );
          loop
              fetch l_cursor into l_temp;
              exit when l_cursor%notfound;
              l_data.extend;
              l_data(l_data.count) := l_temp;
          end loop;
          close l_cursor;
          return l_data;
      end;
  end;
/

select username, created
from dba_users
where username in ( select *
                    from TABLE(cast(my_pkg.get_ref_cursor_as_table('SYS%') as
myTableType)));

USERNAME                       CREATED
------------------------------ ---------
SYS                            12-MAY-02
SYSTEM                         12-MAY-02

Understandability?

          Followup:

it was just another subroutine, not really needed as part of this EXAMPLE (they
are all just examples, techniques)

ignore it.

      Rating: 5
ok.. thank
      you  December 09, 2003
Reviewer:
      A reader
I was just wondering since I enjoy your theory on taking the "lazy" way.

I didn't mean to be abrasive. :)

      Rating: 5
as I look
      back  December 09, 2003
Reviewer:
      A reader
I take back what I said about the "extra" code.

In the first sub-routine, you return a ref cursor, and in the second you return
a table of scalar types.

The first sub-routine is meaningful to the example, and actually helps me
understand something I didn't.

Thanks much.

          Followup:

:)

doh

i should have reread everything closer, eh?

December 09, 2003 sushant -- Thanks for the question regarding
      "does refrential integrity make the database slow", version 8.1.6

      You Asked
Hi Tom

We have a table City  consisting of different cities
where our clients offices are located.City table consists of meta
data i.e column values which are not likely to change at all.
We have some employee  allowances calculates on the basis of
the city  where the employee works in.

We have a VB form for inserting employee related details.we have foreign key on
the city column in the employee table whose parent
key is the city table.

One of our consultants recommended to discard with this sort of
 check implemented for checking the validity of city entered &
suggested to maintain all city validation checking through front end
coding. Reasons cited were refrential integrity checking at the backend
would be too time consuming & will  slowdown the data entry processing
jobs.

Initally we would be having a lot of data entry & number of cities in the
city table is 25.

i wasnt truly convinced with his recomendations .Is the argument valid.

Thanks in advance

Regards
Sushant

      and we said...
fire that consultant -- quickly, hurry up, before you even read the rest of
this.

That was close, wasn't it.  Now that they are gone, lets look at this for a
minute.

a) HOW, HOW, HOW could doing this on the client end be faster?  The front end
has to round trip to the database in order to do this.  Also, if the front end
tries to cache this information -- the front end will only succeed in logically
corrupting the data! I suppose that is what they are thinking here -- we'll
"cache it" and "cache is faster"

b) Is this consultant paid by the lines of code they write?  If so, I know their
goal then -- to increase the code they write and you have to maintain over time.
 Tell me -- what is going to be easier to code?

c) is this the only application that will access this data forever?  (rhetorical
question - history proves the answer is "NO").  So, if you hide all of the rules
of how the data relates in the application -- what then?  In a year, you have to
dig it all out again and reimplement it over and over and over and over and over
and over.

did they benchmark this?  Ok, so I will, I used:

drop table cities;
create table cities as select username city from all_users;
select count(*) from cities;
alter table cities add constraint cities_pk primary key(city);

drop table t1;
create table t1 ( x char(80), city references cities );
drop table t2;
create table t2 ( x char(80), city varchar2(30) );

alter session set sql_trace=true;

declare
    type array is table of varchar2(30) index by binary_integer;
    l_data array;
begin
    select * BULK COLLECT into l_data from cities;
    for i in 1 .. 1000
    loop
        for j in 1 .. l_data.count
        loop
            insert into t1 values ('x', l_data(j) );
        end loop;
    end loop;
end;
/

declare
    type array is table of varchar2(30) index by binary_integer;
    l_data array;
begin
    select * BULK COLLECT into l_data from cities;
    for i in 1 .. 1000
    loop
        for j in 1 .. l_data.count
        loop
            insert into t2 values ('x', l_data(j) );
        end loop;
    end loop;
end;
/

--------------------------------------------------------------------

and we find:

INSERT into t1
values
 ('x', :b1 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          2          0           0
Execute  37000      9.25      10.06          0        479      78969       37000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    37001      9.26      10.06          0        481      78969       37000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 69     (recursive depth: 1)
********************************************************************************

INSERT into t2
values
 ('x', :b1 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute  37000      7.87       7.75          0        484      41900       37000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    37001      7.88       7.75          0        484      41900       37000

So, with RI we get 0.000250000 CPU seconds per single row insert and
                   0.000212703 CPU seconds per single row insert.....

will your end users notice?  (tell you what, if this is your common VB
application running on windows -- I DON'T THINK SO.  The vast majority of their
time is spent waiting for screen paints and such).  That - and do you think this
consultants lookup routine will execute in .000037297 seconds?  If not -- (and
that is a small number) you are better off in the DATABASE!

Not only that, but you can sleep better at night knowing full well the integrity
of the data is protected.

And when you go to ad-hoc query this later, the tools will have the RI to guide
them

And when you go to extend this later, no new app can blow off the integrity

And when you change the list of cities -- you won't have to first SHUT DOWN all
clients (to clear their cache) and then restart them

And so on..

The benefits you get far far far outweight any percieved time issues here.
Review &
      Followup

      Rating: 5
That was a
      perfect response  October 22, 2002
Reviewer:
      Scott Watson
Tom,

The only reason I read this thread was to see what you were going to reply.  I
could already guess what you were going to say, but I thought it would be fun to
see how you would say it.  Boy, am I ever glad I decided to take the time to
read it as, I was laughing so hard I had a tear in my eye.  (thanks!)

Keep up the good work and I look forward to the next episode where a consultant
wants to keep all columns in one table to better centralize their data and
increase performance. ;-)

      Rating: 5
Risk vs.
      Gain  October 22, 2002
Reviewer:  Jim
      Leckband  from Seattle
As my co-worker put it: This is like not putting on your seatbelts because that
makes the rescue workers job faster when they are cutting you out of the car...

Great response and I hope that consultant has been fired and the reasons
forwarded to their boss.

      Rating: 5
NOT NULL
      Constraints   October 22, 2002
Reviewer:
      Jan  from SG
Dear Tom,

in our application (Forms 6i), we don`t put NOT NULL constraints on tables, we
used to have an validation procedure in Forms which is called when data entry is
finished, e.g.:

IF :emp.name IS NULL THEN
   show_msg( 'Name Cannot be Blank')
ELSIF...
..

The reason is - users ofen want to save data before completing all necessary
data. Is this approach OK, if not, is there better solution?

Thanks, Jan

          Followup:

Well, I would propose then that you DO NOT have a NOT NULL constraint!!!
Obviously, you have a condition where it is proper and valid to have the data in
a NULL state.

You NEED to have the constraint on the table in the database IN ADDITION.
Perhaps it needs to be a table level check constraint.  You know you have a
field "record_done_fields_are_valid" column and a check constraint like:

....,
constraint check_record check ( record_done_fields_are_valid is NULL OR
( c1 is NOT NULL and c2 is NOT NULL and c3 is NOT NULL ) ),
.......

meaning that if the record done flag is set -- the three fields must be NOT
NULL, else they can be.

It is all well and fine to have the check in the application (a bit of a pain if
the requirements change but all well and fine).

It is NOT a replacement for the constraint in the database.

What happens in 2 years when someone says "hey, forget forms, lets use JSP's"
and misses a constraint or two or a hundred.  The data is not protected, it
should be and it easily can be.

Having that NOT NULL constraint in the database will let you sleep better at
night knowing that invalid data cannot creep into the database. (also, if you
made it NOT NULL, forms would make the field NOT NULL and would remove the need
for you to write code at all!)

      Rating: 5
Check
      constraints  October 23, 2002
Reviewer:  Tony
      Andrews  from London, UK
On my last project we had an in-house Oracle "expert" who proposed removing all
the check constraints like CHECK(col IN ('A','B','C').  The system being
developed was performing very poorly, particularly the process of data entry of
new customers.  His justification for removing the constraints was:

1) They are slowing down the inserts
2) You don't need them, because the client screens use drop-down lists for
codes, so the check constraints would never be violated anyway.
3) The system would be taking on customers at the rate of 10,000,000 per year.

Naturally, he hadn't benchmarked this - it was just "common sense" to him.

I benchmarked it myself, and was able to respond:
1) Yes, by some ridiculously small amount (see 3)
2) Not true: many records are created by PL/SQL code, any line of which could
use an invalid value.
3) Big deal: I forget the figures, but the total elapsed time for inserting
10,000,000 records was something like 100 minutes with the constraints, 99.5
minutes without (these figures may be way off, but the proportions are not).
Clearly the performance problems were coming from somewhere else, i.e. a badly
tuned SELECT somewhere in the process.

Fortunately, the project got cancelled anyway, as I wasn't considered to be as
knowledgeable as the "expert" so was ignored.  I was not aware of your site
then, so could not point to that for backup.

          Followup:

Funny -- you present cold hard facts, backed up by numbers and are ignored.  I
hate it when that happens...

      Rating:
      3
  October 23, 2002
Reviewer:  Sagi
       from India
Hi Tom,

Excellant way of demonstration.

I did not get this

"So, with RI we get 0.000250000 CPU seconds per single row insert and
                   0.000212703 CPU seconds per single row insert....."

How did you get this Calculation?

Anticipating your reply.

Regards,
Sagi

          Followup:

I divided.

ops$tkyte@ORA920.US.ORACLE.COM> select 9.25/37000 from dual;

9.25/37000
----------
    .00025

      Rating:
      5
  October 23, 2002
Reviewer:
      A reader
Execllent

      Rating:
      5
  October 23, 2002
Reviewer:
      A reader
Excellent

      Rating:
      5
  October 24, 2002
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
Tom
Please refer to question 393694769981. It was also about RI and performance. You
said:
============================================
Everything has a price.

Yes, using declaritive RI will go slower then not using it.  We must make a
check to ensure a parent record exists.  More code is executed.

However, consider the flip side.  If we do not make this check, the integrity of
your data is questionable.  You'll have child records with no parent records.
Your data is unreliable.

I've always found that the cost of having bad data is infinitely high when
compared to a little additional processing.
==================================================

Are you saying that though the cost of having RI is more than not having it, it
is still lesser that implementing RI through application ?

Thanks
Arun

          Followup:

I was saying:

the COST of bad data is very high (you pay an extreme penalty if you had corrupt
data).

It more then offsets the "performance hit" of RI.

Doing RI in the application will be:
o slower
o hard (impossible really -- to do it right)
o not good enough

      Rating:
      5
  October 25, 2002
Reviewer:  Dave
       from Ohio
Even if you could be absolutely sure that the data is consistent with respect to
FK's, PK's, nullability etc., as is sometimes the case when loading data to a
data warehouse, you create the constraints anyway. They may not be validated or
enforced, but you can tell the optimizer to assume treat them as so using the
RELY option. An OLTP database is a different thing though. I would always
enforce and validate constraints -- and give them a real name, not let the
system choose it for you!

      Rating:
      5
  October 25, 2002
Reviewer:
      A reader
As usually, great answer Tom. You are the best of the best!

      Rating: 3
What about
      indexes?  October 25, 2002
Reviewer:
      Vladimir  from Albany, NY
I'd like to tell immediately that I am not that consultant.:-)
However the discussion made me ask this provoking question.
Usually the indexes are created on columns that are part of foreign key to speed
up access and to avoid locks on child tables when parent table is modified or
updated.
Well.. I ran this code:

create table aa ( a number, b number)

create index aa_b_i on aa(b)

declare
l_start number;
begin
l_start := dbms_utility.get_time;
for i in 1..10000
loop
insert into aa values(i,i);
end loop;
dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
end;
/
1710 hsec

truncate table aa;

drop index aa_b_i;

declare
l_start number;
begin
l_start := dbms_utility.get_time;
for i in 1..10000
loop
insert into aa values(i,i);
end loop;
dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs' );
end;

867 hsec

I think that that makes difference.

Was that consultant SO WRONG?

          Followup:

YES, they are *SO WRONG*.

Tell me -- did you need to index that foreign key? (no)
Does the index really even have ANYTHING to do with the conversation? (no)

You said:

<quote>
Usually the indexes are created on columns that are part of foreign key to speed

up access and to avoid locks on child tables when parent table is modified or
updated.
</quote>

so, lets break that into two:

a) speed up access.  Well, if you need the index to speed up access it would
be there regardless of the existence of a fkey.

For example, consider the very common access paths.  Between EMP and DEPT there
is a fkey on deptno.  frequent question in my system is:

select * from emp,dept where dept.deptno=:n and dept.deptno = emp.deptno

That is, get the EMP and DEPT info for DEPTNO= some value.  If DEPTNO in EMP was
NOT indexed, we have a full scan on EMP.  We don't want that so the index is
there for speeding QUERIES and needs to be there REGARDLESS of the fkey
constraint).

So, to speed access has nothing to do with fkeys *at all*

b) to avoid locks on child tables when parent table is modified or updated.

This is perfect -- if the parent table IS IN FACT modified (update to the
primary key or delete of a parent row) -- what does that application have to
do if there is no RI in the database?  It must

   1) LOCK the child table to prevent NEW rows that might reference this
      record being modified from being inserted or existing rows from being
      updated

   2) FULL SCAN the child table to see if deleting this parent or updating
      the primary key would cause an "orphan"

Hmm-- sounds like they would have to do (by definition) the same thing Oracle
would have to do in the event of a parent record delete or parent primary key
update!!!  (yes, in fact they would).  So, we are in the same boat -- aren't we.

The index (lack thereof or existence of) does not come into play here at all --
not even a little bit.

that consultant is sooooo wrong, so very very wrong

      Rating: 5
So
      Wrong?  October 25, 2002
Reviewer:  Tony
      Andrews  from London, UK
Vladimir,

Yes he was!  The ongoing cost of selecting without the index will almost
cetainly wipe out that small saving on the insert by a factor of 1000's to 1 -
assuming the index was worth having at all.

      Rating: 5
i love this
      topic  October 25, 2002
Reviewer:
      A reader  from USA
we have an application that "provides" integrity via app code. the reason being
"we don't want to be locked into any platform".  However:
1. they are locked into a development environment with unportable language
2. they are locked into a platform ( M$ Windoze )
3. they have to locate, understand, translate, recode business logic constantly
- and that is IF they FIND the "integrity" code

i deal with this nonsense everyday
why don't we use what we pay for

      Rating: 5
I could not
      help asking that question.  October 26, 2002

Reviewer:  Vladimir  from Albany, NY
Sure, the consultant was wrong.
"a.speed up access.."
It is true that I tried to mix apples and pineapples.

"b.This is perfect -- if the parent table IS IN FACT modified .."
This answer will not just fire the consultant but burn him/her to ashes:-)

      Rating: 5
How about a
      very large parent table?  November 21, 2003

Reviewer:  Tri  from Berkeley, CA
A colleage of mine is wondering how performance (when using foreign key
constraint) is affected if the parent table is very large. Say, we have dept
(parent) table with 2 million rows. His pre-conditions:

1) The parent table potentially has a not very well balanced B-tree index on the
dept_id column.

2) Inserts into the emp (child) table occurs in all sort of randomness (no
predictable pattern of dept_id).

3) It's an OLTP system with very high traffic and very high degree of
concurrency.

His questions:

1) Will inserts to the emp table be slower than the example you gave (where the
parent table may have just a few dozen rows)? If so, how much slower? (If you
have a benchmark, that would be great!)

2) Assuming there is more overhead than your example, how will it affect how
much traffic we can handle?

Thank you very much for your time.

          Followup:

1 row
1,000,000,000 rows

big deal -- you are talking about "an index"

without integrity -- you have garbage
with integrity -- you have a good database

go with good.  it is NOT a performance thing.  it is a correctness thing.
without it, you have a bug.

1) impossible -- they are all well balanced, it is their nature.

      Rating: 5
Performance
      impct of constraints  December 09, 2003
Reviewer:
      Gabriel  from Montreal, Canada
Hello,

It is very obvious to me that foreign key constraints are more than necessary to
have a bug and headache free database.
But the real life is not always as simple as that. We recently received a
mandate to optimize a data warehousing system that was developped by somebody
else. It has ~ 35 tables with max 35000000 and min 40 rows in each table. The
user is complaining of slow performance of selects.

The database has absolutely no foreign keys but most(all) of the columns that
are used in the joins are indexed. Can the missing keys create a performance
degradation? Will adding them, without any other modification to the database,
produce an improvement? This is very important because the client might refuse
to do anything without a performance improvement.

We didn't added by now because we were only given select grants on objects and
any modification proposed has to be approved by a couple of people.

          Followup:

they are needed for maximum query rewrite with MV's and other advanced
techniques (star transformation and such).

they are meta data in warehouses many times (you can enable rely without
validating -- very fast)

      Rating:
      5
Great!!!  December 09, 2003
Reviewer:
      A reader

December 09, 2003 VENKATARAMAN -- Thanks for the question regarding
      "High Availability questions", version 8.1.5

      You Asked
For 24 x 7 solution, we have designed  MultiMaster replication
with Transperent Application Failover (TAF ).  We have achieved
TAF thru adding entries in TNSNAMES.ORA file.  Is this
sufficient ?
Have I to do anything with OCI8 ?  In my company, Apache server,
Web logic , JAVA are used.  I also want to know whether load
balancing can be done across replicated sites ( NON OPS )

      and we said...
It depends on the type of TAF you are doing.

See

http://technet.oracle.com/doc/server.815/a67775/chx_inst.htm#436314
for detailed information on this feature and its setup.
Included in that section is what a programmer must be aware of
with respect to this feature.

See

http://technet.oracle.com/doc/network.815/a67440/ch2.htm#1000232
for informationon Client Load Balancing and Connection Load
Balancing -- either of which could be utilized in your
environment.
Review & Followup

      Rating: 3
Query returns
      ORA-01403: no data found after sussessful failover  December
      08, 2003
Reviewer:  Jianhui  from DC
Tom,
It says that the query will continue return the remaining rows after failover,
but i always get above error, could you give me some advices of whether i did it
right?

versions:
Database: primary & failover 9.2.0.3 indenpendant instances and databases, NO
RAC/OPS
SQL*PLUS: 9.2.0.1
OracleNet: 9.2.0.1
client OS: W2k
client tnsnames.ora setting,
      (FAILOVER_MODE =
           (TYPE = SELECT)
           (METHOD=BASIC)
           (BACKUP=backup)
       )
both primary and backup databases have same user "taf", same table "big (c
char2000)" with 10000 rows of generated values 1-10000
SQL*Plus command:
SQL> select * from big
  2  where to_number(c) <=&max
  3  order by to_number(c)
  4  /
Enter value for max: 200
old   2: where to_number(c) <=&max
new   2: where to_number(c) <=200
......
C
---------------------------------

45

ERROR:
ORA-01403: no data found

45 rows selected.

after the command runs a while, i kill the server process using kill -9 pid
command and verify that the session does fail over to the backup instance and
database, but the query stops with ora-01403 error instead of returning
remaining rows(46-200 in this case).

Could you give me some input?

          Followup:

taf is a RAC thing.  taf is not a cross database thing.

the database has NO WAY to know that your tables are even remotely "the same" in
your setup.  there is no connection between those two databases.

      Rating: 1
what oracle
      document says is different than what you said here  December
      09, 2003
Reviewer:  jianhui  from DC
url:

http://download-west.oracle.com/docs/cd/B10501_01/network.920/a96580/advcfg.htm#473298
Quote:
"
TAF Database Configurations
TAF works with the following database configurations to effectively mask a
database failure:

Oracle9i Real Application Clusters
Oracle9i Real Application Clusters Guard
Replicated systems
Standby databases
Single instance Oracle database
"

it seems to me that it's not limited to the RAC only, it could be replicated
database, standby database, or even different database.

          Followup:

sorry -- i've never seen it or heard of it used with single instance oracle.

I'll have to refer you to support.


Subject | Article | Last Updated (East coast time)
Question about hints | article only | Dec 10, 2003 02:39 am
Raise_application_error procedure in AFTER LOGON trigger | article only | Dec 10, 2003 02:46 am
Aggregates - select question | article only | Dec 10, 2003 02:51 am
row level transaction lock, table level shared lock, table level exclusive lock | article only | Dec 10, 2003 02:57 am
Data Base Trigger | article only | Dec 10, 2003 03:02 am
Advice on Backup Strategy | article only | Dec 10, 2003 03:03 am
Calling Java from PL/SQL | article only | Dec 10, 2003 03:25 am
How to change the default spfile when reboot the machine. | article only | Dec 10, 2003 03:49 am
What Oracle files do you recommend putting on SAN? | article only | Dec 10, 2003 03:22 pm
Is a composite primary key a good idea? | article only | Dec 10, 2003 03:28 pm
Java Stored Procedures | article only | Dec 10, 2003 03:41 pm
UTL_FILE.PUT_LINE raises NO_DATA_FOUND | article only | Dec 10, 2003 03:43 pm
DDL related queries? | article only | Dec 10, 2003 03:45 pm
Blob insert together with sequence numbers | article only | Dec 10, 2003 03:47 pm
Accessing views/tables from Discoverer only | article only | Dec 10, 2003 03:51 pm
moving tables across tablespaces | article only | Dec 10, 2003 03:55 pm
Encrytion and Decrytion | article only | Dec 10, 2003 03:57 pm
Group By Clause | article only | Dec 10, 2003 04:05 pm
transportable tablespaces | article only | Dec 10, 2003 04:08 pm
Capacity Planning | article only | Dec 10, 2003 04:39 pm
DBMS_UTILITY.GET_TIME | article only | Dec 10, 2003 04:43 pm
where is catrman? | article only | Dec 10, 2003 06:03 pm
How to track logon & logout ... | article only | Dec 10, 2003 06:44 pm
Alter Database Recover Vs Recover Database - Reg. | article only | Dec 11, 2003 05:43 am
ORA-12571: TNS:packet writer failure | article only | Dec 11, 2003 06:07 am
What is the meaning of schema? | article only | Dec 11, 2003 07:21 am
how to do outer joins in multiples tables. | article only | Dec 11, 2003 07:23 am
DBMS_PROFILER | article only | Dec 11, 2003 08:12 am
Summing the values from multiple tables | article only | Dec 11, 2003 08:23 am
Date Format | article only | Dec 11, 2003 10:17 am
few questions... | article only | Dec 11, 2003 02:04 pm
ASP Error In Oracle 8i Rel 8.1.5 | article only | Dec 11, 2003 02:11 pm
How can I do a variable "in list" | article only | Dec 11, 2003 02:39 pm
J2EE Sister Site | article only | Dec 11, 2003 02:45 pm
best method for statistics | article only | Dec 11, 2003 04:38 pm
Chained rows | article only | Dec 11, 2003 04:43 pm
UPDATE using COLLECTION | article only | Dec 13, 2003 10:23 am
Explicit & Implicit Cursor | article only | Dec 13, 2003 10:28 am
do i need to hardcode password inside the program? | article only | Dec 13, 2003 10:35 am
Creating a table using Dynamic SQL in stored procedure get error ORA-00942 | article only | Dec 13, 2003 10:36 am
Database Links | article only | Dec 13, 2003 10:47 am
Copying Data ? | article only | Dec 13, 2003 10:48 am
archivelog mode - some questions | article only | Dec 13, 2003 11:00 am
dbms_job.submit | article only | Dec 13, 2003 11:00 am
dbms_job | article only | Dec 13, 2003 11:01 am
Calculating Size of SGA | article only | Dec 13, 2003 11:28 am
Storing Word Documents | article only | Dec 13, 2003 11:37 am
Bind Variables | article only | Dec 13, 2003 11:39 am
How to Update millions or records in a table | article only | Dec 13, 2003 11:49 am
Good AQ example? | article only | Dec 13, 2003 11:50 am
HTP, HTF, OWA_UTIL, HTML-Packages : Documentation ?, Future ? | article only | Dec 13, 2003 11:57 am
Oracle for Windows or Linux | article only | Dec 13, 2003 12:00 pm
XML Questions | article only | Dec 13, 2003 12:51 pm
Combination of Locally Managed and Dictionary Managed TS | article only | Dec 13, 2003 03:27 pm
SQL LOADER VS. Normal Insert Statements | article only | Dec 13, 2003 05:04 pm
FORALL does not work | article only | Dec 14, 2003 09:34 am
how to do this in sqlplus | article only | Dec 14, 2003 09:45 am
SQ*Plus /nolog | article only | Dec 14, 2003 09:46 am
rowid | article only | Dec 14, 2003 11:11 am
Archive Logging | article only | Dec 14, 2003 12:31 pm
Export and ora-01013 | article only | Dec 14, 2003 12:36 pm
Redo Log Buffers - Your Book - Expert one-on-one | article only | Dec 14, 2003 03:09 pm
Condition from multiple columns | article only | Dec 14, 2003 04:03 pm
delete documents from operating system files | article only | Dec 14, 2003 05:52 pm
CURSOR_SHARING=FORCE | article only | Dec 14, 2003 05:53 pm
windows 2000 patch | article only | Dec 15, 2003 08:01 am
Application Testing | article only | Dec 15, 2003 08:15 am
Analyzing LOB segments in an Auto Segment Space Managed Tablespace | article only | Dec 15, 2003 08:29 am
Stripping special characters | article only | Dec 15, 2003 10:03 am
Freelists and Initrans | article only | Dec 15, 2003 10:04 am
DBMS_UTILITY.COMMA_TO_TABLE limitations ? | article only | Dec 16, 2003 06:48 am
nested subquery | article only | Dec 16, 2003 07:00 am
compare range of dates with range of dates | article only | Dec 16, 2003 07:23 am
logical OR optimization | article only | Dec 16, 2003 07:26 am
Select in Select | article only | Dec 16, 2003 11:28 am
How does the profile parameter 'idle_time' work? | article only | Dec 16, 2003 11:33 am
How to uset get_file_name function in WEB. | article only | Dec 16, 2003 01:55 pm
Adding a column to all the tables in the Database | article only | Dec 16, 2003 01:56 pm
Number Datatype | article only | Dec 16, 2003 01:57 pm
How to avoid the full table scan | article only | Dec 16, 2003 02:11 pm
Select for Update with MIN() | article only | Dec 16, 2003 02:40 pm
Compile procedure automatically -- how to avoid cascading invalidations | article only | Dec 16, 2003 05:41 pm
Pblm in creating MATERIALIZED VIEW | article only | Dec 16, 2003 05:42 pm
Analytics question | article only | Dec 16, 2003 05:44 pm
generic trigger for auditing column level changes | article only | Dec 16, 2003 06:45 pm
practice backup recovery and have a question | article only | Dec 16, 2003 06:58 pm
row(s) 1 - 86 of 86
December 10, 2003 John -- Thanks for the question regarding "Question about hints", version 9.0.2
You Asked
Hi Tom,

I have a few questions about hints. Hopefully this is not considered a
"multiple-part question".

1. There is still debate as to whether hints are just "hints" or directives.
Notably at comp.databases.oracle.* . I sometime find that CBO would not follow
my hints. I ask for USE_NL, it gives me HASH JOIN - even though HASH JOIN is
indeed more efficient in my case. I was under the impression that if a hint is
valid - meaning a path exists for the hint to be utilized, the CBO should obey
the hint, regardless of how inefficient it may be. What's ORACLE's position on
this?

2. Is
select /*+ ordered use_nl(a b) */ from a, b where a.id=b.id;
same as
select /*+ use_nl(b) */ from a, b where a.id=b.id;
?

My understanding is that /*+ use_nl(b) */ means you want ORACLE to use b as the
inner table. If so, there should be no point in using 2 hints?

3. Is /*+ USE_HASH(a b) */ same as /*+ USE_HASH(a,b) */ same as /*+ USE_HASH(b
a) */ ? Or is there any hash join order implied?

4.   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=48 Card=1367 Bytes=2
          3239)

   1    0   NESTED LOOPS (Cost=48 Card=1367 Bytes=23239)
   2    1     TABLE ACCESS (FULL) OF 'T1' (Cost=2 Card=23 Bytes=23)
   3    1     TABLE ACCESS (FULL) OF 'T2' (Cost=2 Card=59 Bytes=944
          )

For this plan, does it mean ORACLE build a hash table for t1 in the memory? Or
T2?

Thanks much!
and we said...
1) in theory -- if the path was valid or meaningful, it would be taken.  You
would have to provide an EXAMPLE and then we can explain.

Hints are hints.  A "valid", "well formed" hint should be taken.

But -- I also have a rule -- don't use HINTS.  The hints I think are OK in code
are

o first_rows
o all_rows
o optimizer_dynamic_sampling (new in 9ir2)
o driving_site

these are hints that allow you to give the optimizer more information which
which to do its job.  If you find you are hinting every other query in your
system -- something is obviously wrong and we need to fix it.  Abusing hints is
not recommended, you are just building another RBO if you do that -- precluding
the software from doing its job.  Might as well not have an optimizer at that
point.

2) they are not the same -- as easily evidenced via:

ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select /*+ ordered use_nl(a b) */ * from a, b where a.id =
b.id;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=19 Card=82 Bytes=4264)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'B' (Cost=2 Card=1 Bytes=26)
   2    1     NESTED LOOPS (Cost=19 Card=82 Bytes=4264)
   3    2       TABLE ACCESS (FULL) OF 'A' (Cost=2 Card=82 Bytes=2132)
   4    2       INDEX (RANGE SCAN) OF 'B_IDX' (NON-UNIQUE) (Cost=1 Card=1)

ops$tkyte@ORA920> select /*+ use_nl(b) */ * from a, b where a.id = b.id;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=82 Bytes=4264)
   1    0   HASH JOIN (Cost=5 Card=82 Bytes=4264)
   2    1     TABLE ACCESS (FULL) OF 'B' (Cost=2 Card=82 Bytes=2132)
   3    1     TABLE ACCESS (FULL) OF 'A' (Cost=2 Card=82 Bytes=2132)

ops$tkyte@ORA920> set autotrace off

but /*+ ordered use_nl(a b) */ would be the same as /*+ use_nl(a b) */

ops$tkyte@ORA920> select /*+ use_nl(a b) */ * from a, b where a.id = b.id;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=19 Card=82 Bytes=4264)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'B' (Cost=2 Card=1 Bytes=26)
   2    1     NESTED LOOPS (Cost=19 Card=82 Bytes=4264)
   3    2       TABLE ACCESS (FULL) OF 'A' (Cost=2 Card=82 Bytes=2132)
   4    2       INDEX (RANGE SCAN) OF 'B_IDX' (NON-UNIQUE) (Cost=1 Card=1)

3) technically, a,b would be not a valid well formed hint.

Oracle will pick the order of the hash join -- and may even dynamically flip
flop it at run time.  That is, A might drive part of the query -- B drive part
of the query.  Depends if the hash join is done in memory entirely or if we have
to swap to disk.

Oracle takes the smaller of the two tables and hashes that.

In your example, T1 would be the hashed table - it is smaller (it is on the top)

Consider:

ops$tkyte@ORA920> exec dbms_stats.set_table_stats( user, 'A', numrows => 23 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec dbms_stats.set_table_stats( user, 'B', numrows => 59 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select /*+ use_hash(a b) */ * from a, b where a.id = b.id;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=23 Card=59 Bytes=3068)
   1    0   HASH JOIN (Cost=23 Card=59 Bytes=3068)
   2    1     TABLE ACCESS (FULL) OF 'A' (Cost=11 Card=23 Bytes=598)
   3    1     TABLE ACCESS (FULL) OF 'B' (Cost=11 Card=59 Bytes=1534)

ops$tkyte@ORA920> exec dbms_stats.set_table_stats( user, 'B', numrows => 23 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec dbms_stats.set_table_stats( user, 'A', numrows => 59 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select /*+ use_hash(a b) */ * from a, b where a.id = b.id;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=23 Card=59 Bytes=3068)
   1    0   HASH JOIN (Cost=23 Card=59 Bytes=3068)
   2    1     TABLE ACCESS (FULL) OF 'B' (Cost=11 Card=23 Bytes=598)
   3    1     TABLE ACCESS (FULL) OF 'A' (Cost=11 Card=59 Bytes=1534)

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace off

see how the order of the tables flip flop in the plan...

But again, use hints only to see that some other plan does in fact run tons
faster -- then figure out why and correct the underlying cause.
Review & Followup
Rating: 5
WORTH GOLD March 30, 2003
Reviewer: JReinhart

Rating: 4
Still some doubt March 31, 2003
Reviewer: John from San Jose, CA, USA
Thanks, Tom. Looking at this example,
1. Do you think ORACLE had ignored the hint for a HASH JOIN?
2. What is the meaning of "Bytes=2132"? The amount of data returned?

ops$tkyte@ORA920> select /*+ use_nl(b) */ * from a, b where a.id = b.id;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=82 Bytes=4264)
   1    0   HASH JOIN (Cost=5 Card=82 Bytes=4264)
   2    1     TABLE ACCESS (FULL) OF 'B' (Cost=2 Card=82 Bytes=2132)
   3    1     TABLE ACCESS (FULL) OF 'A' (Cost=2 Card=82 Bytes=2132)

Followup:
<quote>
The USE_NL hint causes Oracle to join each specified table to another row source
with a nested loops join, using the specified table as the inner table.
</quote>

No, I don't think so, you just said to process B with NL, not A to B.  In short
-- insufficient data passed to the optimizer to make it do anything.  I think
the example in the book is "misleading" because they used ordered + use_nl to
get the NL behavior

bytes = 2132 is the amount of data in bytes the optimizer expects to spit out of
that phase of the query plan.

Rating: 5
useful event March 31, 2003
Reviewer: Connor McDonald from Perth, Australia
event 10053 is a nice way of seeing 'where and when' the hints are applied as
the optimizer canvases the various alternatives.

Cheers
Connor

Rating: 4
Will a hint always be taken? March 31, 2003
Reviewer: John Spencer from Toronto Ontario, Canada
Tom:

In your reponse, you said "A "valid", "well formed" hint should be taken.".  My
understanding, from your site and others, was that a hint merely changed the
cost of particular operations used by the optimizer from their default values.
In essence, hints make the hinted operation appear cheaper than it otherwise
would.

Given this, should the optimizer always use the hinted plan even if a cheaper
plan could be found, or, is the cost adjustment implied by the hint so large
that it guarantees the cheapest (not neccessarily the best) plan?

Followup:
it removes certain plans from consideration as well.

A hint "should be taken", if it is possible and sensible to take it

Rating: 5
driving_site September 04, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom
I have a need to join to four remote tables and one local table in the same
query. Oracle recommends that driving_site hint can offer benefits if majority
of data is on the remote site. Since driving_site takes only one table name as
argument, how do I use this hint with four remote tables? I have tried various
combinations, but Oracle seems to be ignoring the hint. Can I use table name
alias in the argument to the hint?
Thanks

Followup:
if all four remote tables are on the same site, you just pick any of them.

how do you know "we are ignoring it".  do you have an example with autotrace for
us to look at that shows you this?

Rating: 5
September 06, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
The explain plan showed that the hint was being ignored. The queries in remote
serial changed from remote database tables to local database tables when hint
was used. The reason was a function call in the select list like:

select col1, col2, pkg_util.fn_get_suffix(col3)...
from remote1, remote2, remote3, local1
where ....

Since the function is on the local database only, the driving_site hint was
being ignored. When I removed the function call, the driving_site hint worked
well. With a bit of code rewrite, the function call was eliminated. The
improvement was all rows in 2 seconds as opposed to all rows in 11 seconds!!

There is another observation. I had queries like:
Q1(with driving site hint, remote and local tables joined)
UNION
Q2 (all tables joined are local, no hint)

The query Q2 is also treated as remote query by the optimizer. Is there a way to
overcome this behaviour?

The second question I have is about hints in general. We use bind variables
everywhere possible and the data is highly unevenly distributed. The optimizer
is making row estimates which are way off the mark and too low (NUM_ROWS/NDV).
The result is a choice of NLJ where a HJ would provide greater benefits. How can
I avoid using hints in such situation?

Sorry, I do not have the explain plans right now. The e-mail system at work is
partially working because of viruses.

Thanks...

Followup:
the driving site hint was not ignore in as much as "not really practical"

driving site says "do optimization over there" -- it is all or nothing.

you can create a remote view that operated on the three tables, and join to
that.

As for the last thing, bind variable peeking in 9i and up should resolve that --
i have to assume you are on 8i still.  all_rows optimization will probably get
you what you want right now.

Rating: 5
September 06, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
I agree. The hint being "ignored" was just a manner of expression, which came
from reading somewhere that if Oracle doesn't find a practical method of doing
what the hint requires, it just ignores the hint. Sorry for the misexpression.

I did try creating an inline view on remote tables and join to the local table
and then apply the function, but no success. Could it be because the argument to
function was a column from the remote table? I did as:

select col1, col2, pkg_util.fn_get_suffix(col3)...from
(select col1, col2, col3... from remote1, remote2, remote3
  where...) rem_view,
local1
where local1.col_x= rem_view.col_y

We are on Oracle 9i. Is bind variable peeking achieved with CURSOR_SHARING=FORCE
?

Thanks...

Followup:
try adding "and rownum > 0" to the inline view or using the NO_MERGE hint.

peeking is achieved simply by using oracle9i

Rating: 5
Sorry wrong code September 07, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
Please read the code as:

select col1, col2, pkg_util.fn_get_suffix(col3)...from
(select /*+ driving_site(remote1) */
col1, col2, col3... from remote1, remote2, remote3
  where...) rem_view,
local1
where local1.col_x= rem_view.col_y

Rating: 4
DRIVING_SITE hint disabled when insert is used? November 13, 2003
Reviewer: Peter Staunton from Ireland
I've been looking at some query plans and notice that the DRIVING_SITE optimiser
hint only appears to work when performing a simple SELECT, but is ignored if
INSERT SELECT or CREATE TABLE AS SELECT is used.

For example:

INSERT INTO local_table(a,b)
SELECT /*+DRIVING_SITE(big)*/
 big.a
,big.b
FROM big_table@remotedb big
, small_table sml
WHERE big.c=sml.c

From my knowledge of data volumes, it is much better if the join happens
remotely rather than locally, but DRIVING_SITE doesn't appear to work here.

Is this a bug or a feature??

Followup:
what happens with insert /*+ driving_site(big) */ into ....

Rating: 2
DRIVING_SITE hint on INSERT November 17, 2003
Reviewer: Peter Staunton from Ireland
Thanks for the suggestion.

I tried the hint on the Insert statement instead, even tried it on both Insert
and Select, but in both cases the DRIVING_SITE hint is ignored and the join
still takes place locally rather than remotely.

This is on 8.1.7.4 by the way, if that's relevant. Don't know if this behaviouor
has changed in 9i.

Rating: 4
add hint without modifying query December 09, 2003
Reviewer: saha from OH,USA
You have discussed somewhere(9i) how to add hint without modifying a query.
Please let me know the link

Followup:
you might be talking about stored outlines.

In 9i, you can use OEM to "edit" these stored outlines and change the plan.

If you have my book "Expert one on one Oracle" -- you can read the chapter that
describes stored outlines or search this site for stored outlines (as well as
otn.oracle.com -> documentation of course)

December 10, 2003 Abu Tarek -- Thanks for the question regarding "Raise_application_error procedure in AFTER LOGON trigger", version 9.0.1
You Asked
Hi Tom,

I am trying to create an AFTER LOGON trigger to restrict logon form some
terminals. Thats why I have created the following trigger.
CREATE OR REPLACE TRIGGER afterlogon
AFTER LOGON on DATABASE
BEGIN
IF sys_context('userenv','Terminal') = 'HALOGENTEACH' THEN
RAISE_APPLICATION_ERROR (-20001, 'You are not allowed to logon from this
terminal');
END IF;
END;
/

But it does not work. Actually Raise_application_trigger is not working in After
logon trigger.

Is there any wrong in this code? What is the problem.

Have a nice time,
Tarek
and we said...
Oh it works -- just not with people that have DBA.

ops$tkyte@ORA9I.WORLD> CREATE OR REPLACE TRIGGER afterlogon
  2  AFTER LOGON on DATABASE
  3  BEGIN
  4     RAISE_APPLICATION_ERROR (-20001, 'You are not allowed to logon from this
terminal');
  5  END;
  6  /

Trigger created.

ops$tkyte@ORA9I.WORLD> drop user a cascade;
User dropped.

ops$tkyte@ORA9I.WORLD> grant connect to a identified by a;
Grant succeeded.

ops$tkyte@ORA9I.WORLD> connect a/a
ERROR:
ORA-00604: error occurred at recursive SQL level 1
ORA-20001: You are not allowed to logon from this terminal
ORA-06512: at line 2

Warning: You are no longer connected to ORACLE.
ops$tkyte@ORA9I.WORLD> connect /
Connected.
ops$tkyte@ORA9I.WORLD>

That is so that when you make a horrible mistake and have a logon trigger that
always fails, you have someone who can drop it!

It works for all be DBA accounts (and sysdba/sysoper).  And, since you don't
need to use DBA in the real world -- you should just setup your own "MY_DBA"
role and use that -- the solution is easy.  Only grant DBA to those users that
really should have this super power.
Review & Followup
Rating: 4
and what is the sys privs that bypass trigger? March 30, 2002
Reviewer: lsc from madrid
hi tom

since dba is a role and role is a set of system privileges, may I ask which is
the privilege that makes DBA Role bypass logon trigger errors?

Followup:
it is not a privilege, it is "burned into" the server.  It is the fact that the
name of a role the current user has is "DBA" that bypasses the ability of the
trigger to prevent the login.

Just like when you grant DBA or RESOURCE to a user, they automagically get
granted UNLIMITED TABLESPACE -- it's just because of the name of the role.

Rating: 3
User rerstricted to logon from a particular terminal March 30, 2002
Reviewer: mohammad toaha from Dubai, UAE
I wanted to restrict a user say, 'SCOTT' to logon from a particular terminal
say,'TDE23' and the trigger as follow but it did not work.
Tom,please tell me the mistake I have made here to solve my problem.

CREATE OR REPLACE TRIGGER afterlogon
   AFTER LOGON on DATABASE
   BEGIN
 IF USERNAME='SCOTT'  AND TERMINAL = 'TDE23' THEN
   RAISE_APPLICATION_ERROR (-20001, 'You are not allowed to logon from this
   terminal');
   END IF;
 END;
 /

Warning: Trigger created with compilation errors.

Followup:
username and terminal are neither declared nor set to a value.

see the example above for how to get "terminal".

then read up on sys_context to see what else you can get there (session user,
current schema, ip address, etc).

USER is something else you might be interested in.

Rating: 4
Parag March 31, 2002
Reviewer: Parag from India
Hi Tom :

Even i am also facing same kind of Problem. I wanted that , If specific user
login to Oracle through " SQL prompt" & If User name = 'Parag' ( Database User)
& if IP Address is '10.16.1.90' and if Terminal Name is 'Acc_Ter_16' then I
wanted SQL to throw out tht User.
I can findout Terminal & IP Address through SYS_CONTEXT.USERENV Function.

Please helpme out .

Regards
- Parag

Followup:
Well, you cannot do it cause you cannot tell that the user is using sqlplus (and
what the heck is so special about sqlplus?  if you lock me out of that, I'll
just compile cdemo6.c and be right back in).

you might think you can tell its sqlplus, but I'll just

c:> copy sqlplus.exe notsqlplus.exe

and you'll see that as notsqlplus.exe.

Anyway, if parag is a DBA, you cannot do this at all (as demonstrated above).
If parag is not a DBA, you can do this to a degree but ONLY based on the
username and ip_address-- both of which are available via sys_context.

Rating: 3
restricted time March 31, 2002
Reviewer: A reader
is there any way to make log on to time for example from 7 am to 6 pm

Followup:
umm, sure.

if ( to_char( sysdate, 'hh24' ) not between 7 and 18 )
then
   raise........

as I said tho, will not work for DBA, works for all others.

Rating: 5
error March 31, 2002
Reviewer: abeginner from asia
dear tom,
u remove the errors not by adding up more, rather subtracting from queries
mostly.

Followup:
does not compute.  doesn't even seem to make sense.

Your keyboard is broken too I see.

Rating: 1
Parag March 31, 2002
Reviewer: Parag from India
Tom :

I think that u took me in wrong way. I was not mean to say that " Check that
User has login through Oracle SQl* OR What" & u have put stress / focus on that
aspect. My Dearest Tom ... I just wanted to check that , If anybody logon to
Oracle with defined IP_Address & with particular TERMINAL_NAME , just thorwn
them out. Don't allowed them to connect to Oracle.

Hope this time u will Interpret me in a right way.

But anyway Thanks a lot for a time.

" Tom is a Last Hope for any Oracle Porbs "

Followup:
Well, if you cannot derive the answer to your question from the material above
-- I don't know what else I can do for you.

I don't see how I could have interpreted:

f specific user login to Oracle

a) through " SQL prompt" &
b) If User name = 'Parag' ( Database User)
c) & if IP Address is '10.16.1.90' and
d) if Terminal Name is 'Acc_Ter_16'

then I wanted SQL to throw out tht User.

any differently then I did.  However, if you only care about the ip_address and
terminal_name (a big difference from those two PLUS sql*plus PLUS username) --
the answer is right above you.  Use sys_context, use raise_application_error,
expect it to work ONLY for non-dba people

Rating: 1
don't do that please March 31, 2002
Reviewer: abeginner from asia
dear Tom ,
what I meant was you provide even shorter solutions instead of adding up more
and more to the problem queries you have been asked to.

Followup:
Again -- I don't know what you are trying to say here.

You seem to be asking for something -- one star.

The entire solution to this question is here.  The original question contains
the entire answer.  Their trigger is syntactically correct, it works.  It cannot
be used to prevent a DBA logon.

What more do you want?

Rating: 3
DETAILED EXAMPLE March 31, 2002
Reviewer: mohammad toaha from Dubai,UAE
IF sys_context('userenv','Terminal') = 'HALOGENTEACH' THEN

Could you please explain the above line.What is 'HALOGENTEACH' here.

CREATE OR REPLACE TRIGGER afterlogon
AFTER LOGON on DATABASE
BEGIN
IF sys_context('SCOTT','DPDDIS3') = 'HELOGENTEACH' THEN
RAISE_APPLICATION_ERROR (-20001, 'You are not allowed to logon from this
terminal');
END IF;
END;
/

Trigger created.

SQL> CONNECT SCOTT/TIGER
Connected.

So,it did not work as you see above.

My user is 'SCOTT' and Terminal is 'DPDDIS3'.Wouold you please write the trigger
correctly for me.Please include ip_address also.

Thanks in advance.

Followup:
'HALOGENTEACH' is the terminal name the person who asked the question in the
first place was looking for.

I do not know where you made sys_context('SCOTT','DPDDIS3') up from.

Ok, here we go.  Say you have a user whose name will be in the variable
l_username.  You have a terminal whose designation will be in l_termainal.  If
the user islogging in from the terminal you do NOT want them to be using -- this
is what you are looking for:

ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE OR REPLACE TRIGGER afterlogon
  2  AFTER LOGON on DATABASE
  3  DECLARE
  4      l_username varchar2(30) := 'SCOTT';
  5      l_terminal varchar2(30) := 'pts/2';
  6  BEGIN
  7      IF (USER = l_username AND sys_context('userenv','Terminal') =
l_terminal )
  8      then
  9          RAISE_APPLICATION_ERROR
 10          (-20001, 'You are not allowed to logon from this terminal!!!');
 11      END IF;
 12  END;
 13  /

Trigger created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> connect scott/tiger
ERROR:
ORA-00604: error occurred at recursive SQL level 1
ORA-20001: You are not allowed to logon from this terminal!!!
ORA-06512: at line 7

Warning: You are no longer connected to ORACLE.
ops$tkyte@ORA817DEV.US.ORACLE.COM> connect /
Connected.
ops$tkyte@ORA817DEV.US.ORACLE.COM> drop trigger afterlogon;

Trigger dropped.

Now, as to why this is either hard or confusing -- I'm 100% at a loss since this
is sooooo similar to the very very first example here.

Things for everyone to consider:

o look up the sys_context function, understand how to use it, when to use it and
then use it

o understand that in 816 and up -- a logon trigger is NOT going to prevent a DBA
from logging in and there is no way to do so.  You need to NOT use DBA which is
*just a role after all -- one that you can create*

o If you do not have a good mastery of PL/SQL -- playing with an on-logon
trigger is a really horrible idea.

Rating: 5
For Toaha April 01, 2002
Reviewer: Ali from Dubai
Try this:
CREATE OR REPLACE TRIGGER afterlogon
AFTER LOGON on DATABASE
BEGIN
  IF SYS_CONTEXT('USERENV','SESSION_USER') = 'SCOTT' AND
     SYS_CONTEXT('USERENV','TERMINAL') = 'DPDDIS3' AND
     SYS_CONTEXT('USERENV','IP_ADDRESS') = '123.123.123.123' THEN
   RAISE_APPLICATION_ERROR (-20001, 'You are not allowed to logon from this
terminal');
  END IF;
END;
/

Rating: 5
Raise_application_error procedure in AFTER LOGON trigger June 26, 2002
Reviewer: Paul Brewer from London, England
Excellent advice, as ever. I notice though (8.1.7.0.0) that logon triggers also
don't appear to fail if the IMP_FULL_DATABASE role is enabled.
Are there any other 'exemptions', besides the ones already mentioned?

Thanks,
Paul

Followup:
Interesting, just going to prove we all learn something new everyday ;)

So, I setup a test:

------------------- test.sql --------------------------------
set echo on

drop user a cascade;
create user a identified by a;
grant create session, &1 to a;

create trigger logon_trigger
after logon on database
begin
    raise_application_error( -20001, 'No, you cannot log on' );
end;
/

connect a/a
insert into ops$tkyte.test values ( userenv('sessionid'), '&1', 'GRANTED'  );
insert into ops$tkyte.test select userenv('sessionid'), role, 'IN EFFECT' from
session_roles;
connect /
drop trigger logon_trigger;
exit
------------------------------------------------------------------------

and then issued:

select 'sqlplus / @test ' || role from dba_roles;

and made that be test.sh.  I ran test.sh and found results like this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select sessionid, roles, msg
  2  from test
  3  order by sessionid, msg;

 SESSIONID ROLES                          MSG
---------- ------------------------------ --------------------
     15718 DBA                            GRANTED
     15718 PLUSTRACE                      IN EFFECT
     15718 DBA                            IN EFFECT
     15718 SELECT_CATALOG_ROLE            IN EFFECT
     15718 EXECUTE_CATALOG_ROLE           IN EFFECT
     15718 DELETE_CATALOG_ROLE            IN EFFECT
     15718 EXP_FULL_DATABASE              IN EFFECT
     15718 IMP_FULL_DATABASE              IN EFFECT
     15718 JAVA_ADMIN                     IN EFFECT
     15718 JAVA_DEPLOY                    IN EFFECT
     15733 IMP_FULL_DATABASE              GRANTED
     15733 PLUSTRACE                      IN EFFECT
     15733 IMP_FULL_DATABASE              IN EFFECT
     15733 SELECT_CATALOG_ROLE            IN EFFECT
     15733 EXECUTE_CATALOG_ROLE           IN EFFECT

15 rows selected.

that shows that the imp_full_database and DBA roles seem to be the only "magic"
ones.

Rating: 4
Further suggestion June 26, 2002
Reviewer: Jim Kennedy from Wilsonvlle, OR
If you are going to do this in ohter than an example I would put the information
in a table.  That is NOT hard code the username etc. in the logon trigger but
create a package witha  function that accesses a table with the username and IP
address for valid combinations.  The function would return an idnication of okay
to log on or not and then raise the error.  That would make maintenence much
simpler.  That is if a user moved machines or you added more users then it would
be a simple table insert or update as the case may be and not a rewrite of the
trigger.

(Just have the trigger call the function in the package)

Rating: 4
Security June 28, 2002
Reviewer: Paul from England
Tom,

What if you wanted (for security reasons) to limit access to DBAs to certain
terminals (in case somebody get hold of a DBA password for example).  If AFTER
LOGON triggers don't work on DBAs, how could you achieve this?

Paul

Followup:
Don't use the DBA role.

Create your own role "MY_DBA".

Grant whatever privs (i've ALWAYS thought that DBA and especially resource and
connect have TOO MANY privs by default) you feel necessary to MY_DBA.

Grant MY_DBA to these DBA's.

Done....

Rating: 5
logon trigger has to be created with SYS August 15, 2002
Reviewer: A reader
Hi Tom

I have been doing some small tests and if I create a logon trigger as any user
which is not SYS I get

create or replace trigger trace
after logon on database
begin
if user = 'SCOTT'
then
execute immediate 'alter session set  sql_trace=true';
end if;
end;
/
*** SESSION ID:(11.173) 2002-08-15 21:27:30.752
Skipped error 604 during the execution of SYSTEM.TRACE
*** 2002-08-15 21:27:30.752
ksedmp: internal or fatal error
ORA-00604: error occurred at recursive SQL level 1
ORA-01031: insufficient privileges
ORA-06512: at line 4

SCOTT has DBA role
If I create the trigger as SYS then everything is fine.
Also logon triggers when they are triggered under what usrname they are
executed? The owner of trigger or the user that is login in?

Followup:
read
http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html
you have alter ssession via a role, roles are not enabled in a trigger (or
proc/func generally and never in a view) hence the alter session fails.

sys has alter session directly, not via a role

NEVER NEVER NEVER do anything as sys -- sys is special.   Don't use SYS.

Rating: 5
ahh same mistake again! August 16, 2002
Reviewer: A reader
Howly this is the role stuff again, I always get into same trap!

I did a small test to see under what user is the trigger executed when they are
fired

I created a table called logon under scott´s schema, created a trigger to insert
username and sysdate in logon everytime someone logins, I used

create or replace trigger logon
after logon on database
begin
insert into logon (user, sysdate);
end;
/

I have not created any synonyms for logon but logon table still gets inserted.
So I guess the trigger is fired by SCOTT? Otherwise how can users who has not
got insert privilege insert in scott´s table....

Ambiguously if that was the case under what user is alter session set sql_trace
executed?

Or triggers works like procedures, packages and functions, the executer runs the
trigger as trigger´s owner?

Followup:
The trigger runs with DEFINERS RIGHTS (just like a procedure).  It runs with a
schemaid = SCOTT's.  Just like a procedure.  (if you have my book "expert one on
one", read the invokers/definers rights chapter)

The trigger is not FIRED by scott, the trigger is fired by the act of logging
onto the database.  The trigger runs in scotts privilege domain since scott
wrote it -- just like a procedure would.

Rating: 5
The system privilege that bypasses the trigger October 30, 2002
Reviewer: Dave from Massachusetts
I tried creating a new role called my_dba by granting it all the same system
priviliges and roles as dba, and I granted my_dba to a test user.  The test user
was still able to bypass the trigger.  However, revoking the 'administer
database trigger' system privilege worked.

Followup:
Excellent -- thanks for the followup, I never dug down that deep on it to see
exactly what priv it was.

Rating: 5
what can go wrong if trigger is created under SYS? November 29, 2002
Reviewer: A reader
Hi

What can go wrong if stuffs are created under SYS?

We have 3 Oracle Premium Support Service On-Site DBA in our company and they
create all logon triggers and procedures called under SYS!

Followup:
Things work differently for sys then for other users.

I do not personally approve of their technique.

There is never any reason to have to use sys.  For the logon triggers - they
should have created an account, granted just the bare privs needed to that
account and created the objects there.

Same for the procedures.

It is less a matter of things going wrong -- and they can -- then it is "just
the wrong way to do it".

SYS is special.
SYS is magic.
SYS should not be touched by us -- only by the database developers in Redwood
Shores.

Rating: 4
works on sysdba and sysoper accounts too? November 29, 2002
Reviewer: A reader
Hi

in
http://asktom.oracle.com/pls/ask/f?p=4950:8:1658724::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1844531724208,%7Blogon%7D%20and%20%7Btrigger%7D
you mentioned that logon trigger does not work on internal and users with sysdba
and sysoper privilege but here you say it works with sysdba users.... is it
because of version difference?

Followup:
two different "works" semantics here.

"works" in this context (this question) is that even if the trigger fails --
sysdba "works" (so it can log in, remove trigger, fix problem)

"works" in the context of the other question is that the trigger WON'T prevent
sysdba from logging in, won't "work" to stop them. (if it did prevent them, then
you could never fix a database with bad triggers - -triggers that are failing)

Rating: 4
Same problem January 09, 2003
Reviewer: raju from london
Hi Tom,

I have a user for which I have created a logon trigger and it doesn't seem to
fire. I understand that the if the users are granted the DBA role then the
trigger doesn't fire, but in my case when I queried the dba_role_privs tables I
find that the user has only CONNECT,RESOURCE granted. But still the trigger
doesn't fire.

Any ideas where I should be looking at ? Also, which data dictionary view tells
what are the privileges(not roles) granted to the user for eg : I might grant
CREATE TABLE to the user, so there should be some view where I can query this
info.

Thanks in advance.

Followup:
the trigger fires, it'll just NOT FAIL.

What is the trigger?
How do you know it is not firing?
Like I give examples to show howto -- you need to do the same....

Rating: 4
Tigger code January 09, 2003
Reviewer: raju from london
OK .. here is the trigger which doesn't seem to work ...

SYSTEM@ORA92:MYCOMP> conn sys/sys as sysdba
Connected.
SYSTEM@ORA92:MYCOMP> drop trigger scott_on_logon;

Trigger dropped.

SYSTEM@ORA92:MYCOMP> create or replace trigger scott.scott_on_logon
  2  after logon on scott.schema
  3  begin
  4    if sys_context('userenv','session_user') = 'SCOTT' then
  5      RAISE_APPLICATION_ERROR (-20001, 'You are not allowed to logon');
  6    else
  7      RAISE_APPLICATION_ERROR (-20001, 'You are not allowed to logon from
this terminal');
  8  end if;
  9  end;
 10  /

Trigger created.

SYSTEM@ORA92:MYCOMP> conn scott/tiger@ora92
Connected.

Interestingly, the following works, the only difference being that I have not
specified the schema name infront of the trigger name i,e the trigger is owned
by SYS.

SYSTEM@ORA92:MYCOMP> conn sys/sys as sysdba
Connected.
SYSTEM@ORA92:MYCOMP> create or replace trigger scott_on_logon
  2  after logon on scott.schema
  3  begin
  4    if sys_context('userenv','session_user') = 'SCOTT' then
  5      RAISE_APPLICATION_ERROR (-20001, 'You are not allowed to logon');
  6    else
  7      RAISE_APPLICATION_ERROR (-20001, 'You are not allowed to logon from
this terminal');
  8  end if;
  9  end;
 10  /

Trigger created.

SYSTEM@ORA92:MYCOMP> conn scott/tiger@ora92
ERROR:
ORA-00604: error occurred at recursive SQL level 1
ORA-20001: You are not allowed to logon
ORA-06512: at line 3

Warning: You are no longer connected to ORACLE.

Please could you explain this behaviour.

Thanks

Followup:
Looks like a safety precaution.

You cannot write a trigger that prevents YOU from logging on.  The first trigger
is owned by scott and scott cannot have a trigger that fails scott from logging
in.

the second trigger is owned by SYS (bad idea, don't use sys for anthing -
especially "testing").  have some other dba own it.

Rating: 4
More on the topic January 09, 2003
Reviewer: raju from patilbn@hotmail.com
Hi Tom,

Many thanks for the prompt response.

The problem is that in my actual database the trigger is trying to execute a
PL/SQL block which is required for some context setting. The trigger is owned by
the SCHEMA user for which we need the trigger to fire on LOGON. The trigger is
not intended to prevent anyone from logging on.

OK.. the point I am trying to get here is ...are we saying that we should never
create a LOGON trigger for a user which is owned by the same user ?

Please explain.

Thanks

Followup:
The trigger is in fact firing though.  It is just that the raise application
error is not preventing you from logging in - the server catches the error and
deals with it.

If you look at your alert log, you'll see lots of error stacks in there -- for
each time you tested this.  The trigger fires, it just doesn't stop them from
logging in.

You can most certainly create the trigger as the same person, it'll just never
prevent you from logging in.

Rating: 5
That works but ... January 10, 2003
Reviewer: raju from london
Hi Tom,

Thanks ! I checked the alert log file and I could find that the trigger did
fire.

But, I am still struggling with the problem I have on the test database. The
LOGON trigger simply calls a block of PL/SQL code. It is something like this :

create or replace trigger test.on_logon
after logon on test.schema
declare
  l   int;
begin
  l = <schema_name>.<package_name>.proc_name
end;
/

Now, this ON LOGON trigger doesn't seem to fire. When I execute the above PL/SQL
block, the PL/SQL block works OK as expected(i,e it sets the session context as
expected), but this block doesn't seem to be executed when I logon.

The strange thing is that the same trigger is working on some database and not
on the one where I am investigating. Is there any init.ora parameter etc which
might cause this behaviour.

There is no ALERT or TRC file generated and hence don't understand how to
investigate this further.

Can you throw some light on how I should investigate this further ?

Thanks again.

Followup:
is _system_trig_enabled set in the init.ora?

Rating: 5
Spot on January 10, 2003
Reviewer: raju from london
Tom,

Thanks very very much. You are great.

Yes, you are right. The parameter is set to FALSE in the database where I was
investigating.

How can I set this parameter to TRUE ?

Thanks again.

Followup:
just remove it from the init.ora and restart.

Rating: 5
Oracle Version 8174 July 08, 2003
Reviewer: A reader
Hi Tom,

CREATE OR REPLACE TRIGGER TRIG_TEST
AFTER LOGON ON database
DECLARE
      app_name   VARCHAR2(48);

BEGIN
       SELECT module INTO app_name FROM v$session
       WHERE audsid = USERENV('sessionid');

      insert into t1 (c1) values (app_name);
      commit;
END;

select * from t1;
no rows selected

How can I get module info using logon trigger only for sqlplus.

Followup:
very doubtful the MODULE info will be set right after a logon, given that this
field is populated BY the application *after* logging in.

you should at least see a row in there however:

ops$tkyte@ORA817DEV> drop table t1;

Table dropped.

ops$tkyte@ORA817DEV> create table t1( c1 varchar2(80) );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace trigger trig_test
  2  after logon on database
  3  declare
  4          app_name varchar2(255);
  5  begin
  6          begin
  7                  select nvl( module, 'nothing up my sleeve' )
  8                    into app_name
  9                    from v$session where audsid = userenv('sessionid');
 10          exception
 11                  when no_data_found then
 12                          app_name := 'no data found, sessionid = ' ||
userenv('sessionid');
 13          end;
 14          insert into t1 (c1) values (app_name);
 15  end;
 16  /

Trigger created.

ops$tkyte@ORA817DEV> show errors
No errors.
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> @connect /
ops$tkyte@ORA817DEV> set termout off
ops$tkyte@ORA817DEV> REM GET afiedt.buf NOLIST
ops$tkyte@ORA817DEV> set termout on
ops$tkyte@ORA817DEV> select * from t1;

C1
--------------------------------------------------------------------------------
nothing up my sleeve

ops$tkyte@ORA817DEV>

check your alert log on the server to see if the trigger is failing for some
reason

Rating: 3
Commit in trigger July 09, 2003
Reviewer: A reader from MN, USA
Hi Tom,
  The reader above is using a commit in the trigger. Is that allowed in an AFTER
LOGON TRIGGER ???

Thanks

Followup:
yes.

ops$tkyte@ORA817DEV> create or replace trigger trig_test
  2  after logon on database
  3  declare
  4          app_name varchar2(255);
  5  begin
  6          begin
  7                  select nvl( module, 'nothing up my sleeve' )
  8                    into app_name
  9                    from v$session where audsid = userenv('sessionid');
 10          exception
 11                  when no_data_found then
 12                          app_name := 'no data found, sessionid = ' ||
userenv('sessionid');
 13          end;
 14          insert into t1 (c1) values (app_name);
 15          commit;
 16  end;
 17  /

Trigger created.

ops$tkyte@ORA817DEV> show errors
No errors.
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> @connect /
ops$tkyte@ORA817DEV> set termout off
ops$tkyte@ORA817DEV> REM GET afiedt.buf NOLIST
ops$tkyte@ORA817DEV> set termout on
ops$tkyte@ORA817DEV> select * from t1;

C1
--------------------------------------------------------------------------------
nothing up my sleeve

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> drop trigger trig_test;

Trigger dropped.

Rating: 5
July 09, 2003
Reviewer: A reader
Tom,

Is there a way to find out if the users are logging in thru sqlplus, as i
noticed in the program column of v$session it stores the link wich doesnot
display the full link as it is only 48.

Thanks.

Followup:
not reliably -- for the very reason you have said.

You can definitely DISABLE or reduce what they can do.  see the sqlplus guide --
product user profile table.

Rating: 5
July 09, 2003
Reviewer: A reader
Thanks!!

Rating: 3
Thanks August 25, 2003
Reviewer: A reader

Rating: 4
Wealth of information.. I have a question December 05, 2003
Reviewer: Raj from San Diego, USA
Hi Tom,

Thanks for answering all the questions.

Database version 9iR2

Our application users are our oracle users and we had to give some of the rights
to user to do some admin work like dropping user, creating user, changing
password etc. which we are controlling through roles and all this is done using
a procedure which checks that the user can delete only certain users having
certain roles.

Now if that user connects through Sqlplus or some thirdparty tool then we have
no control of stopping that user from deleting some of the important user.

Is there any way we can stop them from doing it.

Followup:
if you have all of the logic to ensure that the user cannot add/drop users in
stored procedures (as you seem to be saying that is what you did) what does it
matter of they run this procedure via your application at the push of a button
or via sqlplus at the hit of an enter key?

they would be able to drop these "important users" in your application just as
easily no?

or am i misunderstanding you here?

Rating: 4
Sorry for not being clear December 08, 2003
Reviewer: Raj from San Diego, CA USA
Hi Tom,

Thank you very much for your response.

Sorry for not being clear about the logic we use to decide whether to drop user
or not :

In the proc parameter( oldUser, newUser, password)

1. First we check if the user who is trying to use this admin command has
admin_role ( a role which has certain rights like drop, create user ).

2. Then we check if the user being dropped (oldUser) has a client_role ( a role
given to application user. )

3. Then we check if the user being created (newUser) exists or not (should not
exist)

if this is true then only we drop the user from procedure. so by using procedure
one cannot delete any user.

But if the admin_role user logs on directly to SQLPLUS and try to drop any
important user then he will be so I was wondering how to control that.

Thanks

Raj

Followup:
well, if a user with admin role logs into the application they can drop the user
right?

so what if they do it

a) via the app
b) via sqlplus

???????

Rating: 3
privileges should be to package owner not admin_role December 09, 2003
Reviewer: Gary from Sydney, Aus
"1. First we check if the user who is trying to use this admin command has
admin_role ( a role which has certain rights like drop, create user )."

The answer seems to be don't actually give admin_role CREATE/DROP USER
privileges. You only need that privilege for the owner of the package (and it
should be granted directly, not through a role, and the package should not be
INVOKER rights).

Followup:
they are doing "do it yourself security".  the owner of the package does have
the right to drop anyone, they are doing a check that says "hey, if the person
running this package has been granted this special role, we'll let them drop
users too -- else not"

the role itself is not what lets them drop users or not.

December 10, 2003 Bill -- Thanks for the question regarding "Aggregates - select question", version 9.2.0
You Asked
Hi Tom
I found some help on your site with aggregates, but i just can't get this to
work right.
What i want to do is display payment history by month. I have a payee_account
table and a payments table. I have a view like this:
ctxdev@DEV1> desc payment_vw;
 Name      Null?    Type
 -------------------------------
 NAME               VARCHAR2(24)
 AMOUNT             NUMBER
 PAY_DATE           DATE
 ACCOUNT            VARCHAR2(64)
 DESCR              VARCHAR2(24)

what i want is this:
Name        JAN      FEB       MAR
----------------------------------
FOOD        500.00  525.00  530.34
GAS         150.00  140.76  160.55
...
and so on

What i have is this so far (but not working)
select name,
    sum(decode(to_char(pay_date,'MON'),
        to_char(add_months(to_date(SYSDATE,'dd-mon-yyyy'),0),'MON'),
            amount ,0 )) m1
from payment_vw
group by 1
/

Then i would add on the extra months etc etc in decode - m2, m3 etc

the error i'm getting is this:
ctxdev@DEV1> select name,
  2   sum(decode(to_char(pay_date,'MON'),
  3    to_char(add_months(to_date(SYSDATE,'dd-mon-yyyy'),0),'MON'),
  4     amount ,0 )) m1
  5  from payment_vw
  6  group by 1
  7  /
select name,
       *
ERROR at line 1:
ORA-00979: not a GROUP BY expression

***
i think i'm on the right track ,but stuck for now - i'm using 9.2.0
thanks -
Bill
and we said...
I don't think that first one is "working" as you have it keyed in:

ops$tkyte@ORA920> select job, sum(sal) from emp group by 1;
select job, sum(sal) from emp group by 1
       *
ERROR at line 1:
ORA-00979: not a GROUP BY expression

Now:

ops$tkyte@ORA920> select sum(sal) from emp group by 1;

  SUM(SAL)
----------
     29025

does -- but you see, group by works with constants -- so that is grouping by the
constant number 1.  It is not an order by which can order based on column
position "order by 1" or by name "order by name"

So, it is the group by:

select name,
       sum( decode( trunc(pay_date,'mm'), trunc(add_months(sysdate, 0),'mm'),
                    amount, 0 ) ),
       sum( decode( trunc(pay_date,'mm'), trunc(add_months(sysdate,-1),'mm'),
                    amount, 0 ) ),
       sum( decode( trunc(pay_date,'mm'), trunc(add_months(sysdate,-2),'mm'),
                    amount, 0 ) )
  from payment_vw
 group by name;

(i did not run that sql but you get the gist -- use group by name and use
trunc() on the dates -- easier and does the right thing by the year)
Review & Followup
Rating: 5
thanks Tom i forgot to ask how to 'rollup' or get totals by month March 15, 2003
Reviewer: Bill
in the query -

Thanks again!

Followup:
you are getting the totals by month already?  not sure what you mean

Rating: 5
what i mean is a grand total for the month March 15, 2003
Reviewer: Bill
ex)

      JAN   FEB   MARCH
--------------------------
food  500   500   500
gas   150   250   300

TOTAL 650   750   800

Followup:
just group by rollup(name)

  1  select decode(grouping(job), 1, 'Total', job ), sum(sal)
  2  from emp
  3* group by rollup(job)
ops$tkyte@ORA920> /

DECODE(GR   SUM(SAL)
--------- ----------
ANALYST         6000
CLERK           4150
MANAGER         8275
PRESIDENT       5000
SALESMAN        5600
Total          29025

6 rows selected.

Rating: 5
March 16, 2003
Reviewer: Asif Momen from Riyadh
Hi Tom,

  Please look at the query below,

select decode(grouping(name), 1, 'Total', name),
sum(decode(to_char(pay_date, 'mm'), 1, amount, 0)) jan,
sum(decode(to_char(pay_date, 'mm'), 2, amount, 0)) feb,
sum(decode(to_char(pay_date, 'mm'), 3, amount, 0)) mar,
sum(decode(to_char(pay_date, 'mm'), 4, amount, 0)) apr,
sum(decode(to_char(pay_date, 'mm'), 5, amount, 0)) may,
sum(decode(to_char(pay_date, 'mm'), 6, amount, 0)) jun,
sum(decode(to_char(pay_date, 'mm'), 7, amount, 0)) jul,
sum(decode(to_char(pay_date, 'mm'), 8, amount, 0)) aug,
sum(decode(to_char(pay_date, 'mm'), 9, amount, 0)) sep,
sum(decode(to_char(pay_date, 'mm'), 10, amount, 0)) oct,
sum(decode(to_char(pay_date, 'mm'), 11, amount, 0)) nov,
sum(decode(to_char(pay_date, 'mm'), 12, amount, 0)) dec
from payment_vw
group by rollup(name);

Regards,

Asif Momen

Followup:
it is very pretty?

what am I looking at it for?

Rating: 5
how bout this March 16, 2003
Reviewer: j from ma
select decode(grouping(name), 1, 'Total', name),
sum(decode(to_char(pay_date, 'mm'), '01', amount, 0)) jan,
sum(decode(to_char(pay_date, 'mm'), '02', amount, 0)) feb,
sum(decode(to_char(pay_date, 'mm'), '03', amount, 0)) mar,
sum(decode(to_char(pay_date, 'mm'), '04', amount, 0)) apr,
sum(decode(to_char(pay_date, 'mm'), '05', amount, 0)) may,
sum(decode(to_char(pay_date, 'mm'), '06', amount, 0)) jun,
sum(decode(to_char(pay_date, 'mm'), '07', amount, 0)) jul,
sum(decode(to_char(pay_date, 'mm'), '08', amount, 0)) aug,
sum(decode(to_char(pay_date, 'mm'), '09', amount, 0)) sep,
sum(decode(to_char(pay_date, 'mm'), '10', amount, 0)) oct,
sum(decode(to_char(pay_date, 'mm'), '11', amount, 0)) nov,
sum(decode(to_char(pay_date, 'mm'), '12', amount, 0)) dec
from payment_vw
group by rollup(name);

Rating: 5
not clear March 16, 2003
Reviewer: j from ma
sorry i was not clearer
this

SQL> SELECT To_char(SYSDATE,'mm') FROM dual;

TO
--
03

SQL>
return '03' and not 3

Rating: 5
GROUP BY in Subquery December 09, 2003
Reviewer: A reader
Hi Tom,

Which of the following is better?

SELECT * FROM A WHERE A_ID IN (SELECT A_ID FROM AB)

SELECT * FROM A WHERE A_ID IN (SELECT DISTINCT A_ID FROM AB)

SELECT * FROM A WHERE A_ID IN (SELECT A_ID FROM AB GROUP BY A_ID)

Or they are all the same to the Optimizer? (Assume SELECT A_ID FROM AB returns
many duplicate A_IDs.

Followup:
the first one is the "best"

it allows the optimzer the most flexibility in processing the query.  the
optimizer already knows "must make a_id unique" in order to do a full join but
could opt for a semi join without "distincting" the entire result set first.

December 10, 2003 Sam -- Thanks for the question regarding "row level transaction lock, table level shared lock, table level exclusive lock", version 8.1.7
You Asked
Tom

I was reading your book.

From what I understood on locks

When I give a select for Update, to update certain rows.

1.A row level exclusive lock is put on the rows I want to update.
2.A table level shared lock is put on the table.
3.when I actually issue the update statement the  2 will become an table level
exclusive lock.

From the above I acan understand that

in 1.(TX) other rows apart from the ones we have locked are available for
updates from other sessions.

in 2 .(TM) no changes can be either to the table structure or the objects which
the table refers to  i.e. referenced objects( does referenced objects mean the
the tables  on which we have the foreign keys etc).

and what is the 3rd kind of lock i.e. table level exclusive lock.

Thank you
and we said...
The 3rd kind of lock is a slightly more restrictive lock then the one from #1.

In #1, we got a row share lock on the table.
In #3, we got a row exclusive lock on the table.

In #1, someone else could get a table level share or share row exclusive lock on
the table...  That is, after we select for update, someone else can come along
and lock table in share mode if they like.

In #3, we can no longer get the table level share or share row exclusive lock.

You may want to refer to
http://technet.oracle.com/doc/oracle8i_816/server.816/a76965/c23cnsis.htm#2937
for a handy table ( i tried not to replicate that which was easily obtainable in
the core oracle documention in my book) that details this stuff...
Review & Followup
Rating: 3
? December 15, 2001
Reviewer: Kulguru
"That is, after we select for update, someone else can come along and lock table
in share mode if they like."

Lets say I lock 10 rows for update, by issuing a select for update on those
rows.

in my context what do you mean by saying that someone else can come along and
lock table in share mode if they like.

Do you mean that they can lock the rows(for update or delete) which I have
selected for update.

and if I actually issued an update statement , then they cannot do the above.

Thank you

Followup:
do this, fire up two sqlplus sessions as scott/tiger and execute the following:

session1                 session2                  commit
----------               ------------              --------------
select * from
emp for update;                                    reserves the rows in emp for
                                                   us, no one can update these
                                                   rows

                         lock table emp in
                         share mode;               succeeds.  the table is
locked
                                                   in share mode.  No other
                                                   session is allowed to MODIFY
                                                   the contents of the EMP
table.

update emp
set sal = sal;                                     BLOCKS.  the share mode table
                                                   lock prevents the promotion
                                                   of our lock to the type
needed
                                                   when doing an update.

                         commit;                   releases the update.

commit;                                            locks all gone.

Now, try it out with different scenarios (do an update instead of select for
update for example) and observe the behavior.

Rating: 4
how to lock table emp in share mode in session 2 December 16, 2001
Reviewer: Kulguru
Tom, how can we lock the table in share mode  in session 2, after selecting all
the rows in emp for update from session 1.

What kind dml statement would do that.

Btw , I tried the following, and Iam surprised that from the same session Iam
able to update the structure of the table after giving the update statement
before committing the update.

scott@ORAI817.US.ORACLE.COM>select * from emp for update;

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
----------
      7369 SMITH      CLERK           7902 17-DEC-80       1000
  20
      7499 ALLEN      SALESMAN        7698 20-FEB-81       1000        300
  30
      7521 WARD       SALESMAN        7698 22-FEB-81       1000        500
  30
      7566 JONES      MANAGER         7839 02-APR-81       1000
  20
      7654 MARTIN     SALESMAN        7698 28-SEP-81       1000       1400
  30
      7698 BLAKE      MANAGER         7839 01-MAY-81       1000
  30
      7782 CLARK      MANAGER         7839 09-JUN-81       1000
  10
      7788 SCOTT      ANALYST         7566 19-APR-87       1000
  20
      7839 KING       PRESIDENT            17-NOV-81       1000
  10
      7844 TURNER     SALESMAN        7698 08-SEP-81       1000          0
  30
      7876 ADAMS      CLERK           7788 23-MAY-87       1000
  20
      7900 JAMES      CLERK           7698 03-DEC-81       1000
  30
      7902 FORD       ANALYST         7566 03-DEC-81       1000
  20
      7934 MILLER     CLERK           7782 23-JAN-82       1000
  10

14 rows selected.

scott@ORAI817.US.ORACLE.COM>alter table emp add (demo varchar2(20));

Table altered.

scott@ORAI817.US.ORACLE.COM>update emp set sal =1000;

14 rows updated.

scott@ORAI817.US.ORACLE.COM>alter table emp add (demo1 varchar2(20));

Table altered.

And surprise-o-rama

I can even alter the table structure from my second session(connected again as
scott)

scott@ORAI817.US.ORACLE.COM>alter table emp add( demo2 varchar2(20));

Table altered.

Why is this happening.

In my opinion once a session locks rows update or gives the actual update
statement, we should not be able to change the structure of the table, as there
will be a table level share lock.

Followup:
See the example right above you for HOW to lock it.

See the link in the comment above for the table that shows what kind of locks
exist with other kinds of locks.

Opinions sort of don't count here (not being nasty, just stating fact).

You did something funky in your example.  I started two sessions, did an update
in session1 and in session2, I get:

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table emp add ( y int );
alter table emp add ( y int )
*
ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified

I believe you forgot that the ALTER you did right after the update committed the
update.  The update was done, committed, finished -- before you did the alter in
the other session.

Rating: 4
Follow up December 16, 2001
Reviewer: Kulguru
Tom

I went through the document and

1.select ... for update
2.row share lock on rows selected by 1
3.Table level row share lock

4.Actual update given
5. 2 will change to row exclusive lock
6.3 will change into table level row exclusive lock

Is the above right?

and

from your example you gave earlier

Tom, how can we lock the table in share mode  in session 2, after selecting all
the rows in emp for update from session 1.

What kind dml statement would do that.

Followup:
You can test it out for yourself -- just TRY it.  Use v$lock to see what
happens.  Use the table in the URL to the concepts manual -- you can see what
happens with each statement.

Please, read the example above -- I gave you the EXACT sql in the form of "lock
table in share mode".  Everything you need to do the example is typed in exactly
as you should type it.

Rating: 4
Select for update locking February 07, 2003
Reviewer: reader from USA
Tom,
In your book on page 112, you say :
'So, as soon as the command to actually update that row is issued, Oracle will
convert the ROW SHARE TABLE lock into the more restrictive ROW EXCLUSIVE TABLE
and the modification will proceed.'

My question is when does the later lock gets released?

Thank you so much.

Followup:
when you commit or rollback.

Rating: 4
Blocking situation February 10, 2003
Reviewer: reader from USA
I am trying to resolve a blocking or locking situation in our batch cycle. And I
am wondering if ROW EXCLUSIVE TALBE lock is the cause that application is
locking out other transactions.

Could this be the case?

Thank you.

Followup:
sure it could be, is it?  i don't know.  look for the blockers and waiters and
look at what the waiters are waiting for what look at what the blockers have
been doing.

Rating: 4
Blockers/waiters after same table February 11, 2003
Reviewer: reader from USA
Tom,
From what I am told, I understand that no to transaction/process will go after
the same row. I have looked at the tkprof/statspack reports and the select for
update query is using bind variables, implying that it is going after diferent
rows in the same table-hopefully.

I just want to confirm my understanding, that ROW EXCLUSIVE TABLE lock will not
create a lock situation as long as processes are trying to access different
row/s in the same table?

thanks for your time.

Followup:
a "blocking" situation -- by definition it causes a lock.

Try to have the application put something useful in v$session with
dbms_application_info -- the client_info, application and module columns --
something like, the bind variable values is useful.

Also -- look for unindexed foreign keys!  And then index them IF you

update the parent primary key
delete from the parent

Rating: 2
why does it lock table in exclusive mode August 04, 2003
Reviewer: A reader
Hi Tom,
You said
In #3, we can no longer get the table level share or share row exclusive lock.
Why can't we get the table level share lock when doing the update

Followup:
that is the rule -- that is why.

if you have that type of lock (row exclusive), no one else can get those other
lock types.  the concepts guide has a table of lock type and what other lock
types are permitted with it

Rating: 4
Hi Tom October 08, 2003
Reviewer: A reader from US
I have a situation where I want to lock a record in a table such that a
different user or a different session should not be able to acess the record
through a SELECT statement.

I am trying to find out a method such that the SQL should read past the locked
record and proceed to rest of the rows.

thanks

Followup:

Not really the way this particular database works.

How about you tell us your "goal", what your application is trying to
accomplish.  Then we might be able to tell you "how to do that"

Sounds like you might be trying to build a "queue" where by processes each get
their own records to "process"?

Rating: 4
Hi tom October 09, 2003
Reviewer: A reader from US
Our goal is that a user selects data from a single table based on the value of
the primary key.  This SQL joins to 3 other tables.  After successful execution
of the query a record is inserted into a temp table.
During the phase of selection and inserting if another user issues a same query
(this query is within a Package)then he should not be able to access the value
what the other user has accessed for.
Right now, we have used the DBMS_Lock package, this works but there is a
drawback.
If the SQL statement takes 45 seconds to execute and insert a record into the
temp table, and if the second user executes almost at the same time, he has to
wait for almost 45 seconds and then another 45 seconds for his query to execute
(total 90 seconds), third user 135 seconds.  This is causing us a big problem as
the number of users in our system is around 20.

I heard in SQL Server there is a feature of reading past the locked record.  I
am trying something similar in Oracle 9.2.0.1.

Thanks

Followup:
where do these primary keys come from?  not understanding the processing here.

If two sessions have the same primary key -- what then?

give more details.

Rating: 3
Hi October 21, 2003
Reviewer: A reader
I am sorry if I had not conveyed things properly.  However, I have found in
Oracle code tips about "SKIP LOCK" feature that the other user goes past the
locked record and selects the remaining.  SKIP LOCK feature was very helpful in
my case.

thanks

Followup:
skip lock - undocumented
skip lock - doesn't work the way everything thinks "it should"

skip lock - something I WOULD NEVER USE in any program i ever developed
(seriously, not just "saying" that - i mean it)

skip lock -- something used by AQ internally and only works in the way they
designed it to work.

skip lock - defines a system that is doing something it really shouldn't and
will have MAJOR upgrade/support issues in the future

dont go there -- really.

Rating: 5
good explanation October 22, 2003
Reviewer: Raja from USA

Rating: 4
Holding a Lock Through DDL December 09, 2003
Reviewer: John Gilmore from United Kingdom
Hi Tom,

I want to load some data into a table which has triggers on it.

Due to the nature of what the triggers do, it is necessary to disable them
during the data load.  However, I also need to ensure that users do not insert
or update data in this table while the triggers are disabled.

I can take an exclusive lock on the table; however, when I issue the "alter
table" statement to disable the triggers the lock is released.  If I disable the
triggers before taking the lock then there is always a chance that a user
transaction can sneak in between these two operations.

Can you see any way around this problem?

Followup:
why not code the trigger as:

begin
   if ( NOT pkg_owned_by_loader.I_am_the_loader )
   then
       trigger code
   end if;
end;
/

and then in the loader session:

exec pkg_ownerd_by_loader.i_am_the_loader := true;
do the load

secure the package so that only the loader schema can set the variable.  that is
one approach.  that way -- the triggers stay enabled forever (no chance of the
load failing and leaving the triggers "disabled" which you have big time now)

alternatively, an autonomous transaction can be used to disable the trigger
without committing the lock:

ops$tkyte@ORA10G> create table t ( x int );

Table created.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> create or replace trigger t_trigger
  2  before insert on t for each row
  3  begin
  4     :new.x := 1;
  5  end;
  6  /

Trigger created.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G>
ops$tkyte@ORA10G> lock table t in exclusive mode;

Table(s) Locked.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> declare
  2    pragma autonomous_transaction;
  3  begin
  4    execute immediate 'alter trigger t_trigger disable';
  5  end;
  6  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA10G>
ops$tkyte@ORA10G>
ops$tkyte@ORA10G> declare
  2    pragma autonomous_transaction;
  3  begin
  4    insert into t values ( 1 );
  5  end;
  6  /
declare
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
ORA-06512: at line 4

that shows the table is still locked and this:

ops$tkyte@ORA10G>
ops$tkyte@ORA10G> insert into t values ( 1 );

1 row created.

shows you can load it... and this:

ops$tkyte@ORA10G> alter trigger t_trigger enable;

Trigger altered.

is your commit -- but remember -- if the insert "fails" and the session goes
away -- the trigger is left disabled, so I like the other method

Rating: 5
Holding a Lock Through DDL December 09, 2003
Reviewer: John Gilmore from United Kingdom
The first method is probably not practical in my case since I will be loading
about 60 tables and each has several triggers.

I have tried to be pedantic with my use of exception handlers wherein I always
call a subroutine which enables the triggers.  In what cases would you see this
method failing to work?

Followup:
power failure (when database comes up, bummer).
system failure (for whatever reason).
software failure (database "crash").

you'd only need ONE exception block -- at the top level -- to enable the
triggers.

Rating: 5
Reader December 09, 2003
Reviewer: A reader
Is "I_am_the_loader" a global variable in the
package section of Sp

Followup:
yes, it would have to be if you wanted to access it like that in the trigger.

the other way would be to have a "getter", "setter" function/procedure to get
the value of a hidden global in the body or set it.

December 10, 2003 venkat -- Thanks for the question regarding "Data Base Trigger", version 7.3.4
You Asked
Hi Tom,

Problem i am facing in below trigger is
:old.message_text (message_text column) is a long datatype
so when i refer to the value i am getting null value , even
though table has data in that column. i also tried to assign
message_text to a variable, but there also i am getting null
value. i want to check in message_text long column weather it
contains a text of msg_type=error.
 can we use long data type in database triggers in
oracle 7.3.4.
if there is any solution please let me know.

thanks
venkat

CREATE OR REPLACE TRIGGER NSMESSAGE_UPDATE
  BEFORE UPDATE
 ON NSMESSAGE
 REFERENCING OLD AS OLD NEW AS NEW
 FOR EACH ROW

BEGIN
   IF :OLD.SOURCE_SYSTEM_NAME = 'ELLIPS1'
      AND (INSTR(:OLD.MESSAGE_TEXT, 'MSG_TYPE=ERROR') > 0)
      AND :NEW.STATUS_CODE = 'S' THEN
      INSERT INTO TRANS_TIME@SGQA1_LLIMS
      (PON,VER,EVENT_TIME,TTYPE,INSERTED)
      VALUES
      (:OLD.PON,:OLD.VER,NEW.MESSAGE_SENT_DATE,
'-10',SYSDATE);
   END IF;
END;
/
and we said...
You cannot access a long field in a trigger.  PLSQL variables
are limited to 32k in size and a long could be much longer.  the
LONG type is not accessible in a trigger.

What you can do, would be to solution similar to mutating tables
(see
http://osi.oracle.com/~tkyte/Mutate/index.html
)...

ops$tkyte@ORA8I.WORLD> create table t
  2  ( id int primary key, y long );

Table created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> create or replace package state_pkg
  2  as
  3      type array is table of t.id%type
  4         index by binary_integer;
  5
  6      newones  array;
  7      empty    array;
  8  end;
  9  /

Package created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> create or replace trigger t_bi
  2  before insert on t
  3  begin
  4      state_pkg.newones := state_pkg.empty;
  5  end;
  6  /

Trigger created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> create or replace trigger t_aifer
  2  after insert on t for each row
  3  begin
  4      state_pkg.newones
  5        (state_pkg.newones.count+1) := :new.id;
  6  end;
  7  /

Trigger created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> create or replace trigger t_ai
  2  after insert on t
  3  begin
  4      for i in 1 .. state_pkg.newones.count
  5      loop
  6          for x in ( select y
  7                       from t
  8                      where id = state_pkg.newones(i) )
  9          loop
 10              if (instr(x.y,'msg_type=error') > 1 )
 11              then
 12                  dbms_output.put_line( 'Yes' );
 13              else
 14                  dbms_output.put_line( 'No' );
 15              end if;
 16          end loop;
 17      end loop;
 18  end;
 19  /

Trigger created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> insert into t values ( 1, 'hello world'
);
No

1 row created.

ops$tkyte@ORA8I.WORLD> insert into t values ( 2, '....
msg_type=error ...' );
Yes

1 row created.

that'll work IF the longs are always 32k or less. if not, you'll
need to use DBMS_SQL in the AFTER trigger to piecewise access
the long type, eg:

create or replace procedure showlong( p_query in varchar2,
                                      p_name  in varchar2,
                                      p_value in varchar2 )
as
    l_cursor    integer default dbms_sql.open_cursor;
    l_n         number;
    l_long_val  varchar2(250);
    l_long_len  number;
    l_buflen    number := 250;
    l_curpos    number := 0;
begin
    dbms_sql.parse( l_cursor, p_query, dbms_sql.native );
    dbms_sql.bind_variable( l_cursor, p_name, p_value );

    dbms_sql.define_column_long(l_cursor, 1);
    l_n := dbms_sql.execute(l_cursor);

    if (dbms_sql.fetch_rows(l_cursor)>0)
    then
        loop
            dbms_sql.column_value_long(l_cursor, 1, l_buflen,
                                        l_curpos ,
                                        l_long_val, l_long_len
);
            l_curpos := l_curpos + l_long_len;
            dbms_output.put_line( l_long_val );
            exit when l_long_len = 0;
      end loop;
   end if;
   dbms_output.put_line( '====================' );
   dbms_output.put_line( 'Long was ' || l_curpos ||
                         ' bytes in length' );
   dbms_sql.close_cursor(l_cursor);
exception
   when others then
      if dbms_sql.is_open(l_cursor) then
         dbms_sql.close_cursor(l_cursor);
      end if;
      raise;
end showlong;
/
Review & Followup
Rating: 5
Null Triggers December 08, 2003
Reviewer: Atul from India
Hi,

I want to find out null triggers in database,
ie trigger containing null in its body..

ie

BEGIN
--  Application_logic Pre-After.Update.Row <<Start>>
--  Application_logic Pre-After.Update.Row << End >>

    NULL;

--  Application_logic Post-After.Update.Row <<Start>>
--  Application_logic Post-After.Update.Row << End >>
END;

How can i find out?

Thanks,
Atul

Followup:
begin
   for x in ( select * from dba_triggers )
   loop
      if x.trigger_text like '%NULL;%' then
          dbms_output.put_line( whatever you want );
      end if;
   end loop;
end;
/

works if trigger bodies are 32k or less. (which if they are not, you've done it
wrong IMO)

Rating: 5
Inconsistent datatype December 09, 2003
Reviewer: atul from India
Hi,

I tried what u said and got following error

select trigger_name from dba_triggers where
trigger_body like '%NULL;%' ;

trigger_body like '%NULL;%'
*
ERROR at line 2:
ORA-00932: inconsistent datatypes

Could you tell me solution for this,

Thank,
Atul

Followup:
look up one review???

you asked me how to do this and I showed you.

you cannot use sql on longs, trigger_text is a long.

December 10, 2003 Christina -- Thanks for the question regarding "Advice on Backup Strategy", version 8.1.7
You Asked
I would like to ask your advice with regards to our future backup strategy.
Currently we are using RMAN with Legato to do cold backup every night for our
250G production database.Our Sysadmins have implemented database mirroring in
our system and they are recommending to replace RMAN with what they call "hot
backup" strategy. In this strategy, we would suspend the system and put all the
tablespace in hotbackup mode and then they would break the database mirror.
After the mirror breakage,the database is made available and then a Legato tape
backup of the mirror is done. This would be a nightly thing and it would mean
more availability for the system.

We are running in archivelog mode. We see the advantage of doing this but we're
unsure if we want to do this w/o RMAN. Would this strategy cover all the
possible recovery scenarios? What do we lose if we won't use RMAN? Is there a
way this strategy would work with RMAN?

Thanks in advance for your advice.
and we said...
Why would you be doing cold backups at all???

You do not want to do the split mirrors.  You will obviate many of the benefits
and features of RMAN.  You should just be doing HOT backups with RMAN (no need
to "alter tablespace begin/end backup" with RMAN).

You can do the split mirrors with RMAN but its just like doing the backup
without RMAN.  You will lose the features available in RMAN that aren't
available without RMAN.

Just do hot backups right now.  They are not any harder then cold backups.  Your
system should be 100% available right now, today, nothing special need be done.
Review & Followup
Rating: 3
February 11, 2002
Reviewer: A reader
can you specify some examples of RMAN features that we would lose if we use that
strategy.

from the Backup class, it was mentioned that you could tell the catalog about
any backups done outside RMAN. is this reliable and doable?

Followup:
For example -- incremental backups are not possible.

But -- the bottom line is -- you should have ALWAYS been doing hot backups.
Split mirrors, unless you have triple mirroring, will open you up to failure
during your backup and re-silvering window.  Just do a plain old hot backup, its
all you need or want.  Don't over complicate the issue with split mirrors,
re-silvering, datafile copies instead of RMAN backups and such.

Rating: 5
Re-silvering February 11, 2002
Reviewer: bob rich from IN, USA
I am unfamiliar with the term re-silvering, could you explain?

Followup:
It is the process of putting the mirrors back together -- "re-silvering" being a
term borrowed from real mirrors we hang on walls and look at.  You "re-silver"
the mirror to sync it up with the copy.

Rating: 3
Are Hot backups useful where nologging operations occur February 13, 2002
Reviewer: Jim Dickson from London, UK
I have never fully understood the implication of taking hot backups only where
the application performs most operations with nologging enabled.

Surely the lack of redo means there are some scenarios that cannot be fully
recovered from OR that post-recovery there will be "soft corrupted" blocks.

My view is that the backup strategy is dependent on the application.

Oracle seems v sure in their recommendation that hot backups
are robust enough for all environments, so maybe i am missing something.

Followup:
Hot backups and cold backups are not significantly different from each other in
many respects.  there are much FEWER options with cold then hot but they are
very similar.

by the way -- it is really not HOT vs COLD, rather it is ARCHIVE LOG vs
NO-ARCHIVE log.  Whether you do hot or cold is up to you.

Lets take an example.  We are in ARCHIVE LOG mode.  I did a backup last night at
12am.  At 8am this morning I did a non-recoverable action -- I did a create
table as select without logging it and dropped the table I just copied (i did a
"reorg" if you will).  At 8:30am -- the disks that held the datafiles all
failed.  So, what can I do?  I can restore my backup from lastnight and either:

o recover it to 7:59:59 AM (or any point in time between 12am and 7:59:59).
I'll have my entire database back to what it looked like at that point in time.
I'll have my table before I dropped it.  We won't have the newly created table
of course.

o recover it completely.  Now I'll have all of my data EXCEPT that table since
it was not logged.

in the real world, we would have backed up our datafile we did the create
table as select without logging IMMEDIATELY after doing that operation to make
it so we would NOT lose this data.  We would tend to want to HOT BACKUP this
file

Now, lets say we did the same thing in noarchive log mode.  I did a backup last
night at 12am.  At 8am this morning I did a non-recoverable action -- I did a
create table as select without logging it and dropped the table I just copied (i
did a "reorg" if you will).  At 8:30am -- the disks that held the datafiles all
failed.  So, what can I do?

o I can restore the database to 12am.  Period, zippo, thats it, nothing else.

So, just to re-iterate -- it is not HOT vs COLD, its archive log vs no-archive
log and you need to backup (probably in HOT mode) datafiles that are affected by
non-logged operations ASAP after doing them to protect yourself.  (eg: in my
example above, I would have created the table as select, BACKED UP the affected
file(s), dropped the old table.  At no point in time would I have lost any data)

Rating: 3
Data could be recoverd February 16, 2002
Reviewer: Michael from HK
Tom,

If I perform a online backup at 10am and I flush the redo log to archive log
files at 10am and copy those file to tape at 10:30am, what time can I restore
with those files on tape? 10am or 10:30am?

Followup:
10:30am.

you would restore the datafiles copied at 10am, apply the redo you copied at
10:30am.

Rating: 3
Q on Backup December 10, 2003
Reviewer: Anil Pant from Hyderabad
Hello,
I've couple of questions :
I've done a normal shutdown and all the datafiles are written, control files are
updated and the DB is in consistent state. Now when I take a cold backup is it
neccessary to backup the Redo Log files ? If yes then why do I require as
everything is in consistent state and when I restart the DB it will start
without reading the Redo logs as Im not doing any rollforward. Pls correct me if
I've missed something.

Followup:
no, you do not need to backup redo.

if you do a hot backup (best, you should be doing those) you would not even
CONSIDER backing up online redo.

if you do cold backups, you should shutdown cleanly (not abort) and you would
not need to backup redo

if you shutdown abort, you would have to backup online redo logs as instance
recovery would be mandatory.  bad practice though.

December 10, 2003 behrooz -- Thanks for the question regarding "Calling Java from PL/SQL", version Oracle 8.1.6i
You Asked
Hi
I'm trying to call a java class from PL/SQL. Unfortunately when
I'm trying to load the class file into the oracle Database I do
receive an error message:

C:\>loadjava -user scott/tiger@mydb -oci8 -resolve Print.class

Error while resolving class Print
ORA-00904: invalid column name
loadjava: 1 errors

Do have the answer to this problem?
and we said...
You do not have the jserver option installed.  If you have
licensed this option, have the DBA run the initjvm.sql script
when connected as SYS or INTERNAL using svrmgrl.  This script is
found in $ORACLE_HOME/javavm/install
Review & Followup
Rating: 3
Reading Excel File Using Java & PL/SQL December 10, 2003
Reviewer: Praveen from Bangalore
   Hi Tom,

   My attempt is to access the content of an MS-Excel file using PL/SQL. I wrote
a java procedure to do this. The following
   method (in a class called "pkgXL.clsExcel") works fine (ie returns "1" when
executed from Operating system prompt). But when
   I loaded this class into Oracel database and the method is called from
PL/SQL, I am always getting a "-1".

   public static int OpenXL(String dsn, String sheet) throws SQLException
    {
        Connection connection = null;
        String sql = String.valueOf("");

        try
        {
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            conn = DriverManager.getConnection("jdbc:odbc:" + dsn, "", "");
            stmt = conn.createStatement();
            sql = sql.concat("SELECT * FROM [" + sheet + "$]");
            rs = stmt.executeQuery(sql);
            return (1);
        }
        catch (SQLException sqlex)
        {
            return ( -1);
        }
        catch (Exception ex)
    {
        return ( -1);
        }

Here the
   parameters "dsn" = ODBC data source name, and "sheet" = the sheet name of
excel file.

   Is it that the method "OpenXL" cannot find the "dsn" from inside the database
?

   The Oracle version is 8.1.7 and UTL_FILE_DIR ini-parameter is set to "*".

The call spec from PL/SQL is given as:

   FUNCTION open_xl (dsn IN VARCHAR2, sheet IN VARCHAR2)
      RETURN NUMBER
   AS
   LANGUAGE JAVA
      NAME 'pkgXL.clsExcel.OpenXL(java.lang.String, java.lang.String) return
int';

Or my attempt will never suceed ?

Thanks and regards

Praveen

Followup:
I've no idea.

you've caught ALL exceptions
You've hidden what they say
you return -1 for any and all errors.

suggestion -- remove the exception blocks, let it fling the exception and check
out what error you are getting.

We should only catch exceptions we expect to recieve and can deal with!

December 10, 2003 Yuemin -- Thanks for the question regarding "How to change the default spfile when reboot the machine.", version 9.2.0
You Asked
We are using Windows 2000 as oracle database server.
From time to time we want to manually edit pfile and then create a spfile
according to the pfile. So how to make a newly created spfile as default spfile
when reboot the machine? (from OEM, the default spfile is
%ORACLE_HOME%\DATABASE\SPFILE%ORACLE_SID%.ORA)
Thanks.
and we said...
you would startup pfile=....

and then simply

create spfile from pfile = /path/to/filename;

see
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/statements_616a.htm#2072538
Review & Followup
Rating: 3
creating default spfile December 09, 2002
Reviewer: Yuemin Yang from USA
I got an error message when using "create spfile from pfile = '....'" to create
default spfile:

ERROR at line 1:
ORA-32002: cannot create SPFILE already being used by the instance

Is that mean I have to shut down the instance to create a default spfile?

Followup:
you start up the database with that PFILE (so you know it works), then you can
create an SPFILE from it.

If you have an instance with an SPFILE already in use, you would simply use

alter system set parameter = value SCOPE=SPFILE;

in order to edit the spfile -- you would NOT edit a pfile in that case.

You can create another SPFILE -- using create spfile = ..... from pfile = .....
as well.

Rating: 4
create/edit default spfile December 09, 2002
Reviewer: Yuemin Yang from USA
We are using spfile for the database, and we want to modify the default spfile
to change audit_trail parameter. Now I know how to do it.

Rating: 5
Install DB via client OEM December 10, 2003
Reviewer: Andy from Germany
Just curious about something: yesterday I spent ages trying to work out why I
couldn't re-start a test database, until I realised that I had set it up on our
linux server via an Enterprise Manager Console running on my w2k PC. The
"original" pfile was on the windows machine, and I had to hunt around a bit on
the linux box to find the right path, so that I could re-start the DB from there
as well. Is it bad practice to install a DB via an OEM on another machine (like
I did), and if not, what is the easiest way to "synchronize" the start-up files?

Followup:
well, if you use spfiles (stored parameter files) in 9i and up -- this is a
non-problem (do not use pfiles).  the spfile is a binary file stored on the
server -- not on the client.

It is not a bad practice what you did but -- if you start it from there, you'll
have to always start it from there to get the pfile.  but, if you use an spfile
instead, you won't have this issue.

December 10, 2003 Reid -- Thanks for the question regarding "What Oracle files do you recommend putting on SAN?", version 9.2.0
You Asked
Tom,
We are implementing a SAN for 40-50 databases using primarily versions 8.1.7.4
and 9.2.0.1.  I was wondering if you have any recommendations about whether or
not to put the following Oracle files on a SAN or leave them on local server
storage:
1) redologs
2) Oracle home
3) tempfiles

In a related question, would you address some of the risks involved with
co-locating dev, test, and prod database datafiles on the same lun?

Thanks!
-Reid
and we said...
Well, you know what -- rather then fake it, I'll just say "i don't know".

I generally do not do that aspect of the setup -- it is dictated to a large
degree.

What I will do is publish this and let others followup on it.  I would encourage
you to also post this question to

o metalink.oracle.com in the discussion forums
o groups.google.com -> comp.databases.oracle.server, the usenet discussion
forums
o otn.oracle.com -> discussion forum

and get their opinions as well.  I just don't have the necessary data points to
forumulate a good response.
Review & Followup
Rating: 3
Are you sure you cannot just "fake it"? :) August 11, 2003
Reviewer: Reid McKinley from Durham, NC
When it comes to Oracle and a particular hardware implementation, it's purt-near
impossible to find any "best practices".  I'm glad I'm not alone in this one.
The little input I have received on this topic leaves me with these questions.
- if you put your Oracle home on the SAN, how do you know when you lose the SAN?
 What will generate the error?
- If you put your redologs exclusively on the SAN, how will you avoid data loss
if you lose two drives at once?

Rating: 3
SAN August 11, 2003
Reviewer: omar
If you are going to be doing clusters or fail-over, then all of the files need
to be on the SAN.

Most operating systems have the ability to have a fail-over LUN, so it shouldn't
be a problem.

Why is this even an issue?

Rating: 5
I'll fake it August 11, 2003
Reviewer: Glen from DC
As a DBA, I've worked with SANs a couple of times.  Sans (pun intended) any
special restictions/requirements/suggestions by the SAN or my own systems team,
I view the SAN as just another bunch of disks in an RAID array and plan
accordingly.

Rating: 3
just put everything on SAN! August 11, 2003
Reviewer: A reader
Ever heard of "SAME"? Stripe And Mirror Everything. Just take your SAN volume as
a huge single SAME volume and put everything there! No planning necessary (only
need to group files under logical directories, such as oracle home, oracle data,
archive log, etc, etc). SAN is safer and more robust than your locally attached
drives!

Rating: 3
I disagree August 12, 2003
Reviewer: omar
"A readers" advice might be OK for a small database, but for a high usage OLTP
or data warehouse, it's not going to perform well.

When setting up disk, it is always better to distibute i/o's across controllers
and disks.

Look at the TPC results.  You never see "SAME".  You see lots of disks and lots
of controllers.

Followup:
um, you can do that with a SAN just dandy -- nothing different there.

Rating: 3
SAN August 12, 2003
Reviewer: Tim... from UK
We use a systemwide cluster on Tru64 where everything is on the SAN. We're not
just talking Oracle software and datafiles. Even the OS is clustered. If you're
using RAC it's perfect. Only one place to so all system setup/config and the
whole RAC knows about it. Any node specific files are setup using Context
Dependant Symbolic Links (CDSL) and you're laughing.

We use raid 0+1 for files requiring sequential access (controlfiles, redologs
etc.) and raid 5 for all the datafiles. Fibre-channel links to the SAN and
between the nodes mean network speed is not an issue.

If you've got the money go for the SAN, if not, just dream about it!

Cheers

Tim...

Rating: 3
Just Dandy August 13, 2003
Reviewer: omar
Tom wrote: um, you can do that with a SAN just dandy -- nothing different there.

And that is what most organization do.  I was arguing against "Just take your
SAN volume as
a huge single SAME volume and put everything there! "

The suggestion in quotes is a recipe for bad performance.

Followup:
ahh, got it -- sorry for the confusion

Rating: 3
SAME August 14, 2003
Reviewer: Johan from JHB SA
Well, apparently Oracle recommend SAME - see:
http://technet.oracle.com/oramag/webcolumns/2003/opinion/holdsworth_opcol.html
There is also a document on OTN that compares SAME with the traditional way of
configuring Oracle storage:
http://otn.oracle.com/deploy/availability/pdf/ora_cbook1.pdf
Where I do see a problem with SAME on SAN, is when you use a single volume for
files from different databases and related apps, since this results in the same
problem you have when putting multiple database instances on the same server :
how do you control resource usage among these and how do you manage performance
(e.g. the "what is hogging the disk ?" question becomes much more complicated
and much less predictable).

The problem is that SAN was invented to consolidate storage which makes it
almost impossible to argue for seperate volumes per DB instance/application. It
also goes against the SAME principle to some extent by allowing fewer spindles
per DB/app.

Another issue I have come across is the claim from SAN vendors that their RAID5
implementation will not slow down writes (even small ones), since they use large
battery backed write-back caches (typically several Gigabytes of cache) and
associated logic that only writes full stripes back to the RAID5 volume.

The question is if these claims can be believed , or should one stick verbatim
to SAME (i.e. Mirroring). I suppose the only way to really know, would be to
benchmark it, but this is quite difficult, since performance problems are only
likely to show under heavy, real-world loads. Does anyone have some real-world
experience, i.t.o. SAN based RAID5 (with all the claimed advanced features) vs
mirroring ?

Rating: 4
Most SAN are already doing SAME September 07, 2003
Reviewer: A reader
SAN storage are almost always doing some form of mirroring and striping, be it
raid5 or raid10, or else. It would be very hard to find a vender that doesn't do
it. A SAN naturally means lots of controllers and lots of disks, and striping
and mirroring are done (usaully across all disks, or at least many disks). I
think it's perfectly OK to consider it a logical S.A.M.E. volume and put the
entire database there.

Rating: 5
Large OLTP, SAN and SAME September 22, 2003
Reviewer: Zoran Martic from Dublin, Ireland
I found through the real huge OLTP deployments (>100G of inserts every day) on
SAN that somehow is always better to put log files on the separate disks (LUNs
or whatever) and not put them together with normal database files as SAME
proposing. This eliminates a lot of waits on log file sync.
This does not telling me that SAME is totally wrong, just that we could not
implement it through the SAME way.
All files were first deployed stripped on as much as possible disks, but at the
end high OLTP and commiting volume did the damage to even huge 24G SAN cache in
front.

SAME is probably great in 90% (maybe 95% or whatever) cases, I cannot agree yet
for the last 10% until somebody said to us that this is independant in regards
to the server hardware (HP, Sun, IBM, ..), disk subsystem (SAN, NAS, normal RAID
disks, ...) or even OS dependant.
Also do not forget the application that is working on top of that. Because of
all this different factors you are dealing with there are never total
recommendations.

All recommendations are good if applied to the correct case.

Followup:
Just to add to this

"never say never"

"never say always"

"rules of thumb are never universal"

"nothing is entirely true, or false"

All recommendations are good if applied to the correct case.

I like that.

Rating: 5
December 10, 2003
Reviewer: Olaf
Tom,

if you have only four hard disk on one controller to install Oracle with very
important data and very low transaction rate, but a lot of reporting, how would
you configure this.
I think to configure 2 RAID1 (mirror) and install OS, Oracle Software and all
oracle data on the first mirror and put online redo logs and archive logs on the
second one.
Sorry, but it not possible to purchase external disk array and HW cannot more
than four internal hard disks.

Followup:
just SAME it (strip and mirror everything)

stripe 2
mirror them

have a single "logical" disk and put everything there.  you have a very low end
system without lots of choices.

Rating: 4
December 16, 2003
Reviewer: A reader
Tom,

did you mean 0+1 (stripe then mirror) or 1 +0 (mirror then stripe). Performance
difference?

If I have only one disk controller (cache with batary)for logs and data, are
there any possibility to lost my data/commited transactions?

Olaf

December 10, 2003 Greg -- Thanks for the question regarding "Is a composite primary key a good idea?", version 8.1.6 SE
You Asked
Tom,

I have a table that has 3 fields that, when considered in combination, could
uniquely identify each record.  These fields are...

Object_ID      CHAR(4)
Ticket_Number  NUMBER
Start_DateTime DATE

It will never be the case that any of these fields is null, nor will it ever
possible for the same object to have the same ticket number at exactly the same
time.

So it occurred to me that, instead of introducing a new field whose sole
significance is to uniquely identify each record in the table, the combination
of these 3 fields would do the trick.  However, when I looked up composite
primary keys in the PL/SQL Developer's Guide, it recommends not using such
beasts.

What do you think?

Some things to consider:

I'd have to insert these same values as a composite foreign key in a number of
other tables.  Would there be any performance issues raised when linking via
composite keys rather than a simple sequentially numbered key?  Perhaps because
there's character and date data to compare as well?

On the other hand, if it winds up that I do have to insert a new numeric field
to identify each record, what's the easiest way to increment that field on each
insert.  Does Oracle have something akin to MS Access's "autonumber" datatype?
and we said...
If the business rule is "these three fields uniquely identify the record and
uniqueness must be enforced" -- then you would need a UNIQUE CONSTRAINT on them
anyway.  If it would be an error for object_id,ticket_number,start_datetime to
be duplicated -- you NEED the unique constraint.

You could add yet another field to the table to be the "primary key" but that
does not remove the need to have a unique constraint on this field.

If the above is used as a foreign key in many other tables -- there is something
to be said for using a surrogate key. If it is not used as a foreign key in alot
of tables - i would just use it as the primary key.

In order to have an "auto increment" type in Oracle you will:

create sequence my_seq;

create trigger my_trigger before insert on T for each row
begin
   select my_seq.nextval into :new.ID from dual;
end;
/

that'll do it.

follow up to comment by Usama Munir Dar below

Sequence numbers skip -- YES -- anything that scales above one user will exhibit
that behavior.  The only way to get a sequential number with no gaps is to
process 1 transaction at a time at most.  Sequences are not designed for that
and any system that has that as a requirement will not scale anyhow (it is a
really silly requirement in a computerized system -- maybe it made sense when
there was a human assigning document ids or something but in a computerized
system -- it just doesn't make sense).

Sequences are a highly scalable way to assign surrogate keys.

I agree that composite keys work very well, they can be used as foreign keys,
everything works.  HOWEVER

o a sequence is smaller then 3 columns will be.  If the key of this table is a
foreign key in lots of other tables -- the space savings can be enourmous.

o people frequently try to update a primary key to correct data -- using a
surrogate key which is imutable solves forever this update cascade problem as
you never update the primary key (the sequence) you update the 3 columns which
are not stored anywhere.

o it is easier to code :

select * from p, c where p.primary_key = c.foreign_key

then

select * from p, c where p.id1 = c.fk1 and p.id2 = c.fk2 and p.id3 = c.fk3

and the first query runs faster.

Those are the things I said to take into consideration.  As I said -- if the
composite primary key is not a foreign key in lots of tables -- go for the
composite primary key.  If it is -- give serious consideration to the surrogate
key populated via a sequence (and don't even consider that "gaps" might be there
-- that is not even a little bit relevant, it is just a unique id).
Review & Followup
Rating: 3
Composite Primary Key or Sequence Numbers? - Tom's view. May 03, 2001
Reviewer: Greg Kliewer from Toronto, Ontario, Canada
This really helped me to make my decision, Tom.  Thanks.

Since there will be quite a few tables linked via foreign key, I'll go with the
new, sequentially-numbered field as primay key.

Rating: 3
Nah! Sequence Numbers give u Skips! May 04, 2001
Reviewer: Usama Munir Dar from Islamabad, Pakistan
Well on Papers good solution..but sequence numbers does give u skips..incase
trigger fails for some reasons...BUT IF this not a point i mean a skip or two is
tolerable then yes..it's ok. Why are u hasitant to make a Composite Primary key!
it works just fine.

Rating: 5
composite key columns November 22, 2002
Reviewer: A reader from Austin, TX
I am interested in your opinion with regard to the insistence by some clients to
specify the use of what I have heard referred to as "smart numbers" which are
just a composite key all stuck together in one column.

A simple example would be this Incident ID:

03-40123    Format:  FY-XNNNN

where:

FY    = 2 digit fiscal year
X     = 1 digit incident type
NNNN  = 4 digit sequence of inc. type within FY

Now, I personally try to avoid this kind of identifier (system generated or
otherwise) like the plague for several of what I consider to be obvious reasons:

*  data entered initially was wrong and made the key wrong
   (i.e.: incident type is actually 3, not 4)
   too bad, the world already "knows" this incident as 03-40123

*  it makes assumptions about upper limits:
   1) there will never be >10,000 incident type 1’s in one FY
   2) forget about adding an eleventh incident type
   3) two digit year –- been there!

*  they're cumbersome to implement

*  what good does knowing FY and incident type at a glance do anyway?

I’ve had this problem come up at times, usually when upgrading from a "legacy"
and/or paper-based system where users are resistant to changing their numbering
scheme.  Often, I have been able to convince (coerce?) the client to use a
simple sequence, but not always.

I know you’re not big on rules of thumb but can things like the nature of the
data (static reference, transactional, etc.) or a need for the key to be
"memorable" give merit to this type of key.

Under what conditions do you stand for a design that makes use of a composite
key column, like above, to identify data?

Would you be inclined to use a surrogate key and let the customer have their
smart number whatever way they want it or would you (perhaps strongly) suggest a
simple sequence approach?

Followup:
A field like this could (should) be DERIVED from the other data.

There is no reason the client has to know what the real and true primary key in
a physical schema is -- that is an implementation detail.

So, I would

create table t ( id int number primary key, /* filled by sequence */
                 fy date,
                 incident_type varchar2(1),
                 goofy_number  number,
                 ....

create view v as
select
t.*,
to_char(fy,'yy')||'-'||incident_type||to_char(goofy_number,'fm0000')their_field
from t;

You could even create a function based index on their_field if they felt they
had to search by it.

proof that intelligence is in the eye of the beholder - smart number indeed.
(silly number for all of the reasons you put forth - ESPECIALLY that 9,999
limit)

Rating: 5
really foreign key November 23, 2002
Reviewer: A reader from Austin, TX
As usual, your solution is as clear as can be.  I especially like the
function-based index part!

However, that doesn’t quite protect me from a bit of an "address change"
problem.

As I (rather clumsily) tried to state in first bullet, once fy, incident_type,
and goofy_number are known and a row is inserted, then their_number is
implicitly defined.  At this point, perhaps their_number is printed on reports,
given out to involved parties, etc.

When it is found that, say incident_type, was entered wrong initially and is
updated then the database’s view of their_number and that shown on reports, held
by involved parties, etc., are a no match.

I can see basing their_number on columns that are not updatable (thru whatever
means) but that may not meet "their" need.

I could build their_number at insert time, put it in its own column (UK, not PK)
and never update it again even if its subcomponents change.

I guess my bottom-line question is this:

Even as the "expert" hired to provide solutions with solid data definitions, am
I overstepping my bounds (and/or wasting time) when I use (sometimes) hours
trying to dissuade clients from their_number in favor of MY number, a simple
sequence?

Followup:
If you present to them the facts -- as you have here, that this will lead to
data errors -- and they reject that, then you have done all that you can do.
You might include YOUR primary key on the reports somewhere so that if and when
an issue arises, at least you can ask them for the other number as well.

You are not overstepping your bounds.  In fact I have more then once said "it is
our jobs to point out these things to those who don't write code for money".

The most recent example of this on this site was a request for how to get N
random rows from a table.  I described how to do it -- problem kept getting more
and more complex till it turned out they wanted a random sample from a complex
query with lots of joins and such.  they wanted 4 rows from this.  They wanted
to do this hundreds/thousands of times a day.  The cost of doing this was quite
high (the query took tons of resources).

Why did they want to do this?  So their portal could have a picture of 4
randomly selected employees on it when people came to it.  Hmmm, my answer back
was "tell the business users that 90% of the machine resources will now be
dedicated to serving these 4 pictures up, do you want to pay for that".  The
opinions were about 50/50 as to whether that is "our place" - whether we blindly
do whatever we are told like sheep (bah bah) or whether we actually point out
that a less then useful feature/function is really expensive and bad.

I would continue to point this out to these people -- your arguments are 100%
valid.  If they choose not to listen, do your best to try and protect them (via
that true surrogate key) but at least you tried.

Hate to be in the place that only does 9,999 things a year.  Not much room for
growth there is there?  And then on Jan 1st, you have to reset that silly
sequence back to 0 again.

Rating: 5
update composite primary key January 14, 2003
Reviewer: Sean from NJ, USA
We have two tables like this:

create table t1(c1 number, c2 varchar2(50),
constraint t1p primary key(c1, c2));

create table t2(c1 number, c2 varchar2(50),  c3 number,
constraint t2p primary key(c1, c2, c3),
constraint t2f foreign key (c1, c2)
references t1(c1, c2));

insert into t1(c1, c2)
values(1, 'c2');

insert into t2(c1, c2, c3)
values(1, 'c2', 3);

Now we need to update c2 of t1.  Are there any ways to update this primary key
besides your suggestion of surrogate key?

I am not aware of update primary key cascade SQL command.

Best,

Sean

Followup:
c1,c2 is not your primary key then -- a primary key is imutable.

You can use deferrable constraints if you like.

You can use this package:
http://asktom.oracle.com/~tkyte/update_cascade/index.html
but if you plan on this being "normal", that you'll be doing this update time
after time - you need to pick a different primary key.

Rating: 5
update primary key January 15, 2003
Reviewer: sean from NJ, USA
Hi Tom,

The package works amazingly well.

The questions:

(1)    After I update the primary key, how do I dissociate that table from
package (I still need the package for other tables, so I can not drop the
package).

      (2)  Using this package seems be the best solution for us since we don’t
need to change any code.  Besides the performance, any other disadvantages?

(3)     The solution of ‘deferrable constraints’ you mentioned is that I add
deferrable to foreign key tables, so I can update primary key first, then
foreign key.  Is this what you mean?   Here is the sample:

create table t1(c1 number, c2 varchar2(50),
constraint t1p primary key(c1, c2));

create table t2(c1 number, c2 varchar2(50),  c3 number,
constraint t2p primary key(c1, c2, c3),
constraint t2f foreign key (c1, c2)
references t1(c1, c2) deferrable initially deferred);

Best,

Sean

Followup:
Not sure I know how to take that "works amazingly well" ;)  a backhanded
compliment i guess ;)

1) the package is not associated with a table.  the package creates some
triggers and procedures SPECIFIC to a table - it is a code generator.  It is not
itself bound to any object.

2) you mean besides the fact that updating a primary key is just really a
seriously bad idea that points to a long running design flaw?

3) yes, the fkeys are deferrable so you can :

set constraints all deferred;
update parent primary key;
update child1 fkey;
update child2 fkey;
set constraints all immediate <<=== if that fails, you missed a child
commit;

Rating: 5
update primary key January 16, 2003
Reviewer: Sean from NJ, USA
(1)  First time after package installed and t1, t2, t3 table created, I need to
run this command first in order to update primary key:

exec update_cascade.on_table('t2')

But if I open a new session after this session, I was able to update primary
without exec this command
'exec update_cascade.on_table('t2')'.  But I don't want people to update this PK
all the times.

Here is the sample:

SQL> connect  UCDEMO/UCDEMO@e2rs
Connected.
SQL> update t2
  2  set b=900
  3  where b = 6;     -- update primary key without 'exec
update_cascade.on_table('t2')'

1 row updated.
SQL>

(2) Thanks,  We will try to correct the design flaw.

Followup:
exec update_cascade.on_table('t2')

created (as fully documented in the page) triggers and packages to support the
update cascade.  If you don't want it anymore, you just need to DROP them.

Rating: 5
Regenerate primary key January 17, 2003
Reviewer: sean from NJ, USA
Thanks so much for your help.

Sean

Rating: 5
how about sys_guid() instead of sequence for primary keys July 10, 2003
Reviewer: A reader
what are the pros and cons of using sys_guid()(suspect
space may be an issue with this approach?) Also with
sys_guid() you can just put it in the default clause
( no need for the auto-increment trigger".

Obviously we dont care about "gaps" here:)

Followup:
well, RAW(16) is bigger then most number fields.

it is not something someone can write down (like a number).

some things won't like raw -- they'll turn it into a 32byte varchar2.

Not sure I would do it unless I had a compelling reason to do it.

Rating: 5
sys_guid versus sequence as primary key August 02, 2003
Reviewer: A reader
Hi Tom
In our case, we are using sys_guid instead of sequence
(we have some requirement to make the primary keys
globally unique) - anyways, I thought I would run
benchmarks and share results with all:

The results show that:

1. elapsed time wise sequences are faster (I used cache
option with a cache of 5000) - I did get some wild result
variation - sequences were like 52% to 90% faster than
sys_guid in different runs.
I suppose the reason that are faster is due to the sga caching of sequences ,
right? However,I was wondering what makes sys_guid slower - what is the internal
mechanics for
generating sys_guid?)

2. Of course sequences took less space (around 1/2 of what
sys_guids occupied)

3. sys_guid consumed less latches however - mainly
following latches:

STAT...session logical reads 319,026    317,195    -1,831
STAT...db block gets         318,025    315,680    -2,345
LATCH.cache buffers chains   1,398,729  1,366,857  -31,872
LATCH.library cache          407,043    207,830    -199,213
LATCH.sequence cache         300,040          0    -300,040

Could you please explain each of them - I think the
latter two are probably the sequences latching to
get and set the next val for my session.
In particular,
what results in higher db block gets and cache buffer chains for sequences? From
"Oracle reference" cache buffer
chains - refers to a hot block contention meaning
sequences result in accessing of the same block
many times (potentially of the internal table used
to store sequences (seq$ table)?

test@apps> @schema
test@apps> drop sequence my_seq;

Sequence dropped.

Elapsed: 00:00:00.09
test@apps> create sequence my_seq cache 5000;

Sequence created.

Elapsed: 00:00:00.02
test@apps> drop table t_guid ;

Table dropped.

Elapsed: 00:00:04.51
test@apps> create table t_guid
  2  (
  3    id RAW(16) default sys_guid() not null primary key,
  4    value number
  5  );

Table created.

Elapsed: 00:00:00.09
test@apps>
test@apps>
test@apps> drop table t_seq ;

Table dropped.

Elapsed: 00:00:01.44
test@apps> create table t_seq
  2  (
  3    id int not null primary key,
  4    value number
  5  );

Table created.

Elapsed: 00:00:00.08
test@apps>
test@apps>
test@apps> @data
test@apps> create or replace package run_benchmark_pkg as
  2    g_number_of_records constant integer := 100000 ;
  3
  4    procedure insert_with_seq;
  5    procedure insert_with_guid;
  6  end;
  7  /

Package created.

Elapsed: 00:00:00.09
test@apps> create or replace package body run_benchmark_pkg as
  2    procedure insert_with_seq
  3    is
  4    begin
  5       for i in 1 .. g_number_of_records
  6       loop
  7         insert into t_seq values( my_seq.nextval, i);
  8       end loop;
  9    end;
 10
 11    procedure insert_with_guid
 12    is
 13    begin
 14       for i in 1 .. g_number_of_records
 15       loop
 16         insert into t_guid (value) values( i);
 17       end loop;
 18    end;
 19
 20  end;
 21  /

Package body created.

Elapsed: 00:00:00.16
test@apps>
test@apps> begin
  2    runstats_pkg.rs_start;
  3    run_benchmark_pkg.insert_with_seq;
  4    runstats_pkg.rs_middle;
  5    run_benchmark_pkg.insert_with_guid;
  6    runstats_pkg.rs_stop(200);
  7  end;
  8  /
Run1 ran in 5728 hsecs
Run2 ran in 7337 hsecs
run 1 ran in 78.07% of the time

Name                                Run1      Run2      Diff
STAT...calls to get snapshot s           100,582           100,847
265
STAT...commit cleanouts                      423               708
285
STAT...commit cleanouts succes               423               708
285
LATCH.list of block allocation               567               858
291
STAT...immediate (CURRENT) blo               372               674
302
STAT...calls to kcmgcs                       396               718
322
LATCH.transaction allocation                 864             1,266
402
STAT...consistent gets - exami               670             1,130
460
STAT...consistent gets                     1,001             1,515
514
STAT...free buffer requested               2,916             3,775
859
STAT...change write time                   2,455             3,775
1,320
STAT...session logical reads             319,026           317,195
-1,831
STAT...dirty buffers inspected             1,996             3,991
1,995
STAT...free buffer inspected               1,996             3,991
1,995
LATCH.redo allocation                    205,074           207,309
2,235
STAT...redo entries                      203,898           206,150
2,252
STAT...db block gets                     318,025           315,680
-2,345
LATCH.cache buffers lru chain              4,826             7,585
2,759
STAT...db block changes                  408,338           411,971
3,633
LATCH.checkpoint queue latch               8,652            12,829
4,177
LATCH.cache buffers chains             1,398,729         1,366,857
-31,872
LATCH.library cache                      407,043           207,830
-199,213
LATCH.sequence cache                     300,040                 0
-300,040
STAT...redo size                      49,988,292        56,368,192
6,379,900

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
3,946,936         3,426,263          -520,673 115.20%

PL/SQL procedure successfully completed.

Elapsed: 00:02:11.30
test@apps> exec show_space ( 'T_GUID')
Unformatted Blocks .....................              30
FS1 Blocks (0-25)  .....................               0
FS2 Blocks (25-50) .....................               0
FS3 Blocks (50-75) .....................               1
FS4 Blocks (75-100).....................              31
Full Blocks        .....................             370
Total Blocks............................             512
Total Bytes.............................       4,194,304
Total MBytes............................               4
Unused Blocks...........................              64
Unused Bytes............................         524,288
Last Used Ext FileId....................               9
Last Used Ext BlockId...................         177,800
Last Used Block.........................              64

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.27
test@apps> exec show_space ( 'T_SEQ')
Unformatted Blocks .....................               0
FS1 Blocks (0-25)  .....................               0
FS2 Blocks (25-50) .....................               1
FS3 Blocks (50-75) .....................               0
FS4 Blocks (75-100).....................              40
Full Blocks        .....................             203
Total Blocks............................             256
Total Bytes.............................       2,097,152
Total MBytes............................               2
Unused Blocks...........................               0
Unused Bytes............................               0
Last Used Ext FileId....................               9
Last Used Ext BlockId...................         176,776
Last Used Block.........................             128

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.22
test@apps> spool off

Followup:
1) undocumented however, sys guid involve OS interaction and is undoubtably more
"cpu intensive"

2)
3)
LATCH.cache buffers chains   1,398,729  1,366,857  -31,872

latches to get blocks in the buffer cache... sequences live on blocks...  that
was the latches to get the buffer to update the sequence and/or just get the
current value thereof.

LATCH.library cache          407,043    207,830    -199,213

latches to latch things in the library cache.  a sequence definition lives
there.  accessing the sequence will cause some of those.

LATCH.sequence cache         300,040          0    -300,040

should be obvious ;) but it is just a latch surrounding the sequence used
internally.

Rating: 5
thanx! August 03, 2003
Reviewer: A reader
Please keep up the awesome work!!

Rating: 3
good August 04, 2003
Reviewer: Dani from India
Hi Tom,
In the oracle  9.0.1.1.1 documentation says that "A composite primary key cannot
have more than 32 columns". But I have tried with 33 columns its working fine.
What is the reason exactly?
This is table I have tried.
create table Test(x1 number(2) ,x2 number(2) ,x3 number(2) ,x4 number(2) ,x5
number(2) ,x6 number(2) ,x7 number(2) ,x8 number(2) ,x9 number(2) ,x10
number(2),x11 number(2),x12 number(2),x13 number(2),x14 number(2),x15
number(2),x16 number(2),x17 number(2),x18 number(2),x19 number(2),x20
number(2),x21 number(2),x22 number(2),x23 number(2),x24 number(2),x25
number(2),x26 number(2),x27 number(2),x28 number(2),x29 number(2),x30
number(2),x31 number(2),x32 number(2),x33 number(2),x34 number(2),x35
number(2),x36 number(2),x37 number(2),x38 number(2),x39 number(2),x40 number(2),
primary key(
x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x2
3,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33))
/

Followup:
looks like the limit is actually 33 then.

Not that I'm overly worried as "1" is the most common size and 5 or 6 a
reasonable maximum....  32 would be "slightly overboard" and 33 is even worse.

Rating: 4
SURROGATE KEY August 04, 2003
Reviewer: umesh from blore india
Tom
Effective Oracle By Design -- When is your Book available in India?

Surrogate Key
- What is this key used for and how is it helpful .. I do not get the cocept of
this surro... key
Thanks for your patience

Followup:
sorry -- i don't do the details like "when it'll be reprinted in various
countries".  I'm not part of that particular part of the book publishing
thing...

search this site for

surrogate

lots of articles discussing them

Rating: 5
sequence versus sys_guid as surrogate key mechanism August 18, 2003
Reviewer: A reader
In our case we are using sys_guid because we want
to avoid clashes when moving data from one db to another.
I am still not sure about the entire rationale - but
it seems to be that when you import and export data
from one db - db1 to another db db2, if there are sequences
then we will run into conflicts (since the same sequence
is created in both schemas.

Have you faced this situation? Is there an elegant solution
for this problem apart from using sys_guid
instead of sequences, This is different from the case
where we have a known number of sites and each site can
start the sequence at a different number.

Followup:
say you have N sites you anticipate having to service.

multiply by 100 incase you are off.

Then, at site one:

create sequence S start with 1 increment by n*100;

at site two:

create sequence S start with 2 increase by n*100;

say n = 10, site 1 will generate numbers

1, 1001, 2001, 3001, ......

site 2:

2, 1002, 2002, 3002, ......

non-overlapping sequences generated at upto 1000 sites.

Rating: 5
thanx Tom! August 18, 2003
Reviewer: A reader
"non-overlapping sequences generated at upto 1000 sites. "

I went through that solution in your site somewhere.
It is very elegant - but you have to know in advance the
number of sites. In our case the requirement is that we
can have any local database where the schema can be populated and then
subsequently "merged" into a central
db through export/import.

Ideally, the same schema install script should always work
without creating clashes in any database.

*sigh* - I guess short of always having a central table
storing starting points of sequences and having serialized access - there does
not seem to be a good alternative...

Thank you so much!!

Rating: 3
An alternative... August 19, 2003
Reviewer: Matt from Australia
There are a couple of assumptions here (such as there being a manageable amount
of data to transfer in this way), and this involves writing some code, but:

You could write some extraction routines to pull out the data from individual
tables in a format that can be used to define an external table in your target
DB. This would give you a file per table (per source DB).

The filenames will need to encode the tablename and the source database. This
information can be stored in a table in the target DB to allow you to manage the
creation of a new source DB with a data change.

These files are then used to populate the target DB:

You will need to know the mapping between your target tables, columns and local
sequences that are used to populate these tables. As well as how the filename
maps to the table / source DB.

A procedure could then directly query your data from the external tables and
dynamically generate insert statements using the appropriate sequence in the
correct place in the insert ie;

insert into * select seq.next_val, col1, col2 from external table.

I am assuming that the target DB is live, and that you do not need to transfer
index / constrainst etc.

Rating: 5
thanx Matt!! August 19, 2003
Reviewer: A reader
I am not extremely familiar with external tables - may
be that is why the solution looks more complicated:)
I also want to avoid dynamically generated inserts.

What do you think of the following idea?

When installing the product schema sequences are created.
Our attempt is to have a unique increment number a given installation. This
number can be based on the current date and time.
Since we can't generate the sequences as above using
NDS, this would have to be done using sql - spooled
into a file and then executed.

following shows the logic of generating create statements
with unique increments.

test@apps> @t
test@apps> select 'create sequence my_sequence start with 1 increment by ' ||
inc || ';'
  2  from
  3  (
  4    select julian_date + hour_minute_second inc
  5    from
  6    (
  7       select to_number(to_char(sysdate+1, 'J')) julian_date ,
  8         to_number( to_char(sysdate, 'hh24MISS')) hour_minute_second
  9       from dual
 10    )
 11  );

create sequence my_sequence start with 1 increment by 2595011;

Elapsed: 00:00:00.01
test@apps> @t
test@apps> select 'create sequence my_sequence start with 1 increment by ' ||
inc || ';'
  2  from
  3  (
  4    select julian_date + hour_minute_second inc
  5    from
  6    (
  7       select to_number(to_char(sysdate+1, 'J')) julian_date ,
  8         to_number( to_char(sysdate, 'hh24MISS')) hour_minute_second
  9       from dual
 10    )
 11  );

create sequence my_sequence start with 1 increment by 2595014;

Elapsed: 00:00:00.00
test@apps>

This should work - what do you think Tom, Matt?

Menon:)

Rating: 5
re: sequences August 19, 2003
Reviewer: A reader
And if you are concerned with the increment being
rather high - you can of course subtract a known
constant (e.g. 2500000 from the value you got to have
lower increments but always different ones for each
install.

But I don't think this should be a concern, really!

Rating: 5
sorry the above won't work! August 20, 2003
Reviewer: A reader
Obviously - since my start point is the same! I guess the above idea won't work
even if you have the "random" increasing number based on date and time used in
start_value or increment of the sequence - The clash can not be avoided for
unless you do something like what Tom
suggested (partitioning values with given start points
changing (not randomly) and fixed increment. But this
ain't possible in my case.

Thanx!
Menon:)

Rating: 5
sequences and data migration September 23, 2003
Reviewer: A reader
One problem we have is:
If we use sequences, when we migrate data from
one db to the other instance of db (during product
upgrades) say, sequences result in clashes.
Since here the number of sites is not known -
we can not use the "n site" solution you
prescribe above. Have you ever run into this
situation and if so, how did you resolve it?

Thanx!

Followup:
well, the problem isn't sequences.

the problem is surrogate keys -- period.  sounds like you need a globally unique
one.

read about sys_guid(), a sql function.
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/functions125a.htm#79196
it might be of interest to you.

Rating: 5
thanx Tom! September 23, 2003
Reviewer: A reader
I was aware of the guid solution - wanted to see if
there is a sequence based alternative.

Thanx!!!

Rating: 5
Fat Primary Key September 23, 2003
Reviewer: A reader
Tom

1.Is there any downside to having a varchar2 column as the primary key of a
table.

2.Is there a downside to having a fat primary key, i.e. if a primary key is
composed of 3 columns

1.col1 varchar2(20)
2.col2 number(10)
3.col3 number(10)

We are having to deal with dba's who believe that having a fat primary key will
lead to performance problems, and in the kind of scenarios , they would suggest
to have unique index on the combination of the 3 columns above, and then have a
number column say col_key number as the primary key, which will be populated by
a sequence.

Please help with illustrations.

Followup:
1) no

2) more typing in predicates, that's about it.  realizing that the wider the
key, the longer to compare two values -- but i would in most all cases ignore
that.

there are performance "problems"
there are observed performance responses

this would not be a performance "problem".

consider (benchmarking, far far too easy -- easier then "hypothesizing" for
sure)

ops$tkyte@ORA920LAP> drop table t;
Table dropped.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create table t
  2  as
  3  select object_name, object_type, owner, max(object_id) object_id
  4    from all_objects
  5   group by object_name, object_type, owner;

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create unique index t1_idx on t(object_id);

Index created.

ops$tkyte@ORA920LAP> create unique index t2_idx on t(object_name, object_type,
owner);

Index created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create or replace procedure p1
  2  as
  3      l_rec t%rowtype;
  4  begin
  5      for x in ( select * from t )
  6      loop
  7          select * into l_rec from t where object_id = x.object_id;
  8      end loop;
  9  end;
 10  /

Procedure created.

ops$tkyte@ORA920LAP> create or replace procedure p2
  2  as
  3      l_rec t%rowtype;
  4  begin
  5      for x in ( select * from t )
  6      loop
  7          select * into l_rec from t where object_name = x.object_name and
owner = x.owner and
  8          object_type = x.object_type;
  9      end loop;
 10  end;
 11  /

Procedure created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> exec runStats_pkg.rs_start

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec p1

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec runStats_pkg.rs_middle

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec p2

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec runStats_pkg.rs_stop(1000)
Run1 ran in 328 hsecs
Run2 ran in 353 hsecs
run 1 ran in 92.92% of the time

Name                                  Run1        Run2        Diff
STAT...session pga memory         -131,072           0     131,072

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
315,673     315,410        -263    100.08%

PL/SQL procedure successfully completed.

so, we are talking less then 3/100's of a second here..

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> exec runStats_pkg.rs_start

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec p2

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec runStats_pkg.rs_middle

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec p1

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec runStats_pkg.rs_stop(1000)
Run1 ran in 363 hsecs
Run2 ran in 333 hsecs
run 1 ran in 109.01% of the time

3/100ths of a second.. for over 30,000 rows.  do the math, it is neglible

Name                                  Run1        Run2        Diff

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
314,060     314,149          89     99.97%

PL/SQL procedure successfully completed.

but, did they think about the inserts taking longer, generating more redo, the
extra space of the index, yadda, yadda, yadda

you can benchmark that for them if you want but something like this should be
enough.

Rating: 4
compiste PK, locking, FK and redundant indexes September 24, 2003
Reviewer: A reader
Hi

I have seen applications with many composite PK therefore  many child tables are
over-indexed. For example

table_1 has col_a, col_b and col_c as PK
table_2 has col_b, col_c, and col_e as PK
table_3 has col_b, col_c, and col_d

table_child references table_1, table_2 and table_3 as follows

col_a
col_b
col_c
col_d
col_e

to avoid locking we would index

col_a, col_b, col_c
col_b, col_c, col_e
col_b, col_c, col_d

Leads to many redundant indexes...

Is this bad database design? Or it is something which cannot be avoided?

Followup:
if you delete from the parent -- you need the index (to make the check on child
fast, avoid a full scan)

if you update the parent primary key -- well, besides being a bad idea -- you
need the index to avoid the full scan.

so, they are not redundant in this case if either of the above is true.

Rating: 5
surrogate keys , shadow keys September 24, 2003
Reviewer: A reader
1.
Are surrogate keys used to handle a situation where we have fat primary keys.
i.e.

in a situation where we have to  have a primary key like ( col1, col2,col3),
then we create another key column ( mostly populated by a sequence) which will
be the unique identifier for the ( col1, col2,col3).

And secondly we would create a unique index on ( col1, col2,col3).

So we would have  key   surrogate key
                  ( col1, col2,col3)  - unique index.

If someone has to join to this table they will do so by joining to just key
column.

2.Are surrogate keys and shadow keys one and the same?

Followup:
1) no, they are not just for "fat primary keys"

they are used when there is no true primary key.  a primary key is immutable --
never changing.  Many real world situations do not have such a thing.  Consider
a "user" table.  Say you keyed it off of the employee email id.  Well, people
get married, change names - emails CHANGE.  using email as a key (i've seen so
many people burned big time by that one) is a really utterly bad idea.  so we
use a surrogate.

2) not familar with "shadow keys" personally.

Rating: 3
what do u think ? September 29, 2003
Reviewer: huss from egypt
Tom
 i already have the problem of composite primary key that foreign in many others
so when need to update it i fall into cascading update problems,
i need ur opinion in my soluation
i think in add a not null unique key in the master table and then add a column
in each child of that master and make then reference to that made and after that
change the primary of master table to that new column and drop the fat foreign
keys from all details tables

Followup:
(your keyboard is failing -- it is dropping really important letters used in day
to day writing -- vowels!)

if you have an update cascade problem, matters not that you have 1, 2, 3, 30
attributes in your key -- you have a problem in the first place.

You chose a primary key that is not a primary key.

So, it sounds like you are trying to add a surrogate.

Yes, you can add a unique, not null column that is populated by a sequence.

You can then add that column as a fkey to the child tables and update the join
between parent and child to key the surrogates down there.  You would update all
code to make it so it populates this new column as well.

you could then drop the "fat" key which isn't a key really -- since you update
it!

Rating: 5
composite index structure December 09, 2003
Reviewer: A reader
Hi

I am reading about indexes in the concepts guide, the examples there are quite
simple since they are using simple single column indexes.

However, if my PK is formed by 2 or more columns such as

CASE_ID
BUSINESS_UNIT

If I have these rows

CASE_ID  BUSINESS_UNIT
1        XA
2        YZ
2        YA
3        XC
4        YW
5        ZQ

in the leaf blocks how are these rows stored? 2 YZ and 2 YA?
Are the 2 columns concatenated? As 2YZ 2YA?

Followup:
they are stored as two separate columns in the index blocks.  so, you can use
the pk index with queries such as:

select * from t where case_id = :x and business_unit = :y;
select * from t where case_id = :x;

and if case_id has few distinct values, maybe even:

select * from t where business_unit = :y;

using index skip scans (search for "index skip scan" to read more about that if
interested)

Rating: 5
how about range partitioned indexes December 10, 2003
Reviewer: A reader
Hello

multi column range partitioned tables concatenate the columns to form the
partition key, does it happen for range partitioned indexes too?

Followup:
no they do not.  they work as vectors.

December 10, 2003 Akthar -- Thanks for the question regarding "Java Stored Procedures", version 8.1.5
You Asked
Please enlight me regarding the concept of calling oracle stored
procedure from java application /servlets and their pros and
cons.Also regarding the advantage it has over the Java
application/servlet  and Oracle stored procedures as alone.

Matter most urgent

Thanks in advance.
and we said...
Not sure what you are asking here.  the concept of calling a
stored procedure from a java application (be it a servlet, bean,
whatever) is pretty the same as invoking a java function over
RMI.  You are running a remote piece of code.

Since a stored procedure is in the database, close to the data,
it can cut down on the amount of stuff being flung about on the
network.  It also centralizes code in a place where ANY client
can use it -- not just another java routine.  It also has unique
security aspects not available outside the database (eg: stored
procedures by default execute with the privs of the OWNER or
definer.  Hence I can create some tables, write a procedure to
manipulate them -- read them, write them and so on and grant
execute on the procedure to you.  Now, you can run the procedure
and the procedure can touch the tables but YOU yourself cannot
touch the tables).

Neither java applications or stored procedures have any
advantage over eachother -- both are TOOLS to be used when
appropriate.  I prefer to put data oriented things in the
database and have as little as possible outside of the database
(just more stuff to coordinate).  Since stored procedures can be
written in java -- the line between the two blurs even more.
Review & Followup
Rating: 2
I have a java object March 17, 2003
Reviewer: Abhijit from Mumbai, India
Hi Tom.
I have an object inside my Oracle DB for logging errors to a text file. Since I
had a ready java class handy, I have decided to put it in. If I call that stored
procedure through the stored procs inside the DB, it logs correctly.

Now I made one change in the java class and asked it to log the errors in a
table instead of a log file.

I have one question. Can I call this java class (jErrLogger) from my java
application, directly?
By directly I mean, without interacting with any other DB object.
How do I do that?

Thanks & Regards,

Followup:
you could using rmi and other stuff but -- it would be easiest just to call it
as a stored procedure.  I wouldn't use the other methods.

It would be perhaps even more performant to, oh i don't know, use PLSQL to log
things into a table?

Rating: 4
Java Stored procedures December 10, 2003
Reviewer: Dan White from Philadelphia, PA
Tom,

   Is it possible to call a java stored procedure directly from a serverlet? The
reason I'm asking is that our developers would like to have the return value of
the java stored procedure a java object(i.e. a hash table)

Followup:
not in 9ir2 and before, not really.

December 10, 2003 Gerry -- Thanks for the question regarding "UTL_FILE.PUT_LINE raises NO_DATA_FOUND", version 8.1.5
You Asked
Tom,

As the title suggests, I have successfully opened a file for writing.  I setup a
buffer and attempt to write to that opened file.  I trap the exceptions being
raised.  The exception being raised as raised by the database:

ERROR at line 1:
ORA-01403: no data found
ORA-06512: at "SYS.UTL_FILE", line 302
ORA-06512: at "SYS.UTILS", line 139
ORA-06512: at "SYS.EXTRACT_MAMSI", line 149
ORA-06512: at "SYS.DUMP_HEALTH_DRIVER", line 49
ORA-06512: at line 1

The file was opened with a number of different rec-size (1023-300).
I have much success with UTL_FILE in the past.  Must admit this one appears to
baffle me.

Regards,

Gerry Bush
and we said...
It is a problem with UTL_FILE in 815 -- fixed in 816.  You must be going over
1023 by at least one byte.  When you do:

ops$tkyte@ORA815.US.ORACLE.COM> declare
  2          f  utl_file.file_type;
  3  begin
  4          f := utl_file.fopen( '/tmp', 'testing.dat', 'w', 30000 );
  5          utl_file.put_line( f, rpad( '*', 1024 ) );
  6          utl_file.fclose( f );
  7  end;
  8  /
declare
*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at "SYS.UTL_FILE", line 302
ORA-06512: at line 5

It fails (only in 815). The workaround is as follows:

ops$tkyte@ORA815.US.ORACLE.COM> exec utl_file.fclose_all
PL/SQL procedure successfully completed.

ops$tkyte@ORA815.US.ORACLE.COM> declare
  2          f  utl_file.file_type;
  3  begin
  4          f := utl_file.fopen( '/tmp', 'testing.dat', 'w' );
  5          utl_file.fclose( f );
  6          f := utl_file.fopen( '/tmp', 'testing.dat', 'w', 30000 );
  7          utl_file.put_line( f, rpad( '*', 1024 ) );
  8          utl_file.fclose( f );
  9  end;
 10  /

PL/SQL procedure successfully completed.

Open the file WITHOUT a length, close it and then open it again with a length.
Review & Followup
Rating: 5
UTL_FILE.PUT_LINE raises NO_DATA_FOUND", version 8.1.5 December 28, 2001
Reviewer: Gerry Bush from Virginia
Not sure why one has too open first without and then with a file size but it
does work.  Not a scenario I attempted.  Tom, Thanks

Rating: 4
I'm in version 8.1.6, received same errors September 30, 2002
Reviewer: A reader from DE, usa
Hi Tom:

following is a procedure, that I tried to use your free.sql script to monitor
our pro. db tablespace usage, when I ran the procedure, I got errors:

ERROR at line 1:
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at "SYS.UTL_FILE", line 101
ORA-06512: at "SYS.UTL_FILE", line 157
ORA-06512: at "ANL.TABLESPACE_USAGE", line 7
ORA-06512: at line 1

CREATE OR REPLACE PROCEDURE tablespace_usage
IS

file_handle    utl_file.file_type;

BEGIN
    file_handle :=
        utl_file.fopen('/tmp','tabsusage.dat', 'W');
        utl_file.fclose( file_handle);
        file_handle := utl_file.fopen ('/tmp', 'tabsusage.dat', 'W', 30000);
        utl_file.put_line ( file_handle, rpad( '*', 1024));

    INSERT INTO space_usage
    SELECT *
    FROM ( SELECT TRUNC(sysdate),
              NVL(b.tablespace_name,
                       NVL(a.tablespace_name,'UNKOWN')) name,
                     kbytes_alloc kbytes,
                     kbytes_alloc-nvl(kbytes_free,0) used,
                     NVL(kbytes_free,0) free,
                     ((kbytes_alloc-nvl(kbytes_free,0))/
              kbytes_alloc)*100 pct_used,
                     NVL(largest,0) largest,
                     NVL(kbytes_max,kbytes_alloc) Max_Size,
                     DECODE( kbytes_max, 0, 0, (kbytes_alloc/kbytes_max)*100)
pct_max_used
        FROM ( SELECT SUM(bytes)/1024 Kbytes_free,
                            MAX(bytes)/1024 largest,
                            tablespace_name
                   FROM sys.dba_free_space
                   GROUP BY tablespace_name ) a,
                 ( SELECT SUM(bytes)/1024 Kbytes_alloc,
                           SUM(maxbytes)/1024 Kbytes_max,
                           tablespace_name
                   FROM sys.dba_data_files
                   GROUP BY tablespace_name
                   UNION all
                  SELECT SUM(bytes)/1024 Kbytes_alloc,
                             SUM(maxbytes)/1024 Kbytes_max,
                             tablespace_name
                   FROM sys.dba_temp_files
                   GROUP BY tablespace_name )b
        WHERE a.tablespace_name (+) = b.tablespace_name);

    FOR x IN (SELECT *
          FROM space_usage
          WHERE used > 80 )
    LOOP
        UTL_FILE.PUTF(file_handle, 'statement: was successful');
    END LOOP;
    UTL_FILE.FCLOSE(file_handle);
END;
/

my space_usage table is :
desc space_usage
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 CRTDATE                                            DATE
 TABLESPACE_NAME                                    VARCHAR2(18)
 KBYTES                                             NUMBER
 USED                                               NUMBER
 FREETABS                                           NUMBER
 PERCENTUSED                                        NUMBER
 LARGEST                                            NUMBER
 MAX_SIZE                                           NUMBER
 PERCENTMAXUSED                                     NUMBER

thank you as always,
an

Followup:
  6  BEGIN
  7      file_handle :=
  8          utl_file.fopen('/tmp','tabsusage.dat', 'W');

so, is /tmp in your UTL_FILE_DIR init.ora setting???

Rating: 4
version 8.1.6 with error ora-6512 October 01, 2002
Reviewer: A reader from de, usa
Thank you, Tom.
here is parameter utl_file_dir set in our environment, but what means '*' here?

show parameter utl_file

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
utl_file_dir                         string  *

an

Followup:
* means all directories -- pretty darn UNSAFE (think about what we could do with
that)

cut down your example, just try to open a file, close it.

Rating: 4
why not just "fclose_all" ? October 01, 2002
Reviewer: Robert C from PA
>> exec utl_file.fclose_all

Tom, Can one just use "utl_file.fclose_all" right out and
no bother with closing the individual handles ?
What are the considerations ?

Thanks

Followup:
I would only do that if I were the only programmer in your company.

Imagine how irritated the other developers might get if you closed their files!

You can use that in a "when others -- man, we are dying here -- exception block,
right before re-raiseing the exception"  but I see no other logical use of it.

Rating: 4
Exception...just remembered what I read in "Expert 1-1" October 01, 2002
Reviewer: Robert C from PA
I just remembered something on UTIL_FILE exception in your book which is not
around me right now...
Re exception handling you mentioned that those exceptions are "user-defined" in
the pkg and that you convert that to "your" exception in your code...and that
you listed out those UTIL_FILE exceptions (in a file or something) and just
"....read it into a file...." ( hope I quoted you right)
--- when I read it, I was really curious...what exactly do you mean and how do
you do that... I still am.
Thanks

Followup:
I used an exception block to catch each of the user defined exceptions and
turned them into meaningful error messages using raise application error:

create or replace package body xxxxx
as

g_invalid_path_msg constant varchar2(255) default
'INVALID_PATH: File location or filename was invalid.';
g_invalid_mode_msg constant varchar2(255) default
'INVALID_MODE: The open_mode parameter in FOPEN was invalid.';
g_invalid_filehandle_msg constant varchar2(255) default
'INVALID_FILEHANDLE: The file handle was invalid.';
g_invalid_operation_msg constant varchar2(255) default
'INVALID_OPERATION: The file could not be opened or operated on as requested.';
g_read_error_msg constant varchar2(255) default
'READ_ERROR: An operating system error occurred during the read operation.';
g_write_error_msg constant varchar2(255) default
'WRITE_ERROR: An operating system error occurred during the write operation.';
g_internal_error_msg constant varchar2(255) default
'INTERNAL_ERROR: An unspecified error in PL/SQL.';

.....

exception
    when utl_file.invalid_path then
        raise_application_error(-20001,g_invalid_path_msg);
    when utl_file.invalid_mode then
        raise_application_error(-20002,g_invalid_mode_msg);
    when utl_file.invalid_filehandle then
        raise_application_error(-20002,g_invalid_filehandle_msg);
    when utl_file.invalid_operation then
        raise_application_error(-20003,g_invalid_operation_msg);
    when utl_file.read_error then
        raise_application_error(-20004,g_read_error_msg);
    when utl_file.write_error then
        raise_application_error(-20005,g_write_error_msg);
    when utl_file.internal_error then
        raise_application_error(-20006,g_internal_error_msg);
end send;

Rating: 3
fclose_all reaches across sessions and causes irritation ? October 02, 2002
Reviewer: Robert C from PA
>> exec utl_file.fclose_all
Followup:
>>>Imagine how irritated the other developers might get if >>>you closed their
files!

Tom re your response above, you mean fclose_all will close
all file handles in OTHER sessions ? wow..that's bad.
The Package Reference certainly did not say this...
"This procedure closes all open file handles for the session. This should be
used as an emergency cleanup procedure...".

It says nothing about WHY this is an emergency bug-out.

Followup:
no, not in other sessions -- other files, other developers code might have open
in YOUR session.

I'm talking about when your code calls others peoples code or is called by other
peoples code.  I would be really bummed if your code that I was forced to call
closed my files -- wouldn't you be?

It is an emergency "bug out" cause it closes all files in your session
indiscriminantly -- that would be bad.

Rating: 5
check existance of a file using UTL_FILE August 01, 2003
Reviewer: Yogeeraj from Mauritius
hello,

I would like to know if it is possible to check the existence of a file on the
file system using UTL_FILE.

I have tried the following:
============================================================
$ sqlplus /@dev

SQL*Plus: Release 8.1.7.0.0 - Production on Fri Aug 1 16:17:28 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.4.0 - 64bit Production
With the Partitioning option
JServer Release 8.1.7.4.0 - 64bit Production

SQL> create or replace function exists_file( p_dir in varchar2, p_filename  in
varchar2 ) return boolean
is
        l_input         utl_file.file_type;
begin
     l_input := utl_file.fopen( p_dir, p_filename, 'r', 4000 );
     exception
        when no_data_found then return FALSE;
     utl_file.fclose( l_input );
     return TRUE;
end;
/

Function created.

SQL> begin
  if exists_file('/home/yogeeraj/','.profile') then
   dbms_output.put_line('hello');
  end if;
end;
/
  2    3    4    5    6  begin
*
ERROR at line 1:
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at "SYS.UTL_FILE", line 101
ORA-06512: at "SYS.UTL_FILE", line 192
ORA-06512: at "YD.EXISTS_FILE", line 5
ORA-06512: at line 2

SQL> show parameter utl

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
utl_file_dir                         string  *
SQL> !ll /home/yogeeraj/.profile
-r--r--r--   1 yogeeraj   it            1719 Jan 16  2003
/home/yogeeraj/.profile

SQL>
============================================================

Please help.
thank you in advance for you reply.

best regards

Followup:
your exception handling is all wrong.

the only time you close a file is when utl_file throws "no data found" on the
fopen call --- which it never ever would.

if the file existed -- you would have returned -- NOTHING

if no data found was thrown -- you would have then returned true (but probably
not, since the close would have failed)

You need to read up on coding with exceptions and how they work a bit.  That and
check out the docs to see what exceptions utl_file ACTUALLY throws.

these are the exceptions you could expect:

  /*
  ** Exceptions
  */
  file_open            EXCEPTION;
  charsetmismatch      EXCEPTION;
  invalid_path         EXCEPTION;
  invalid_mode         EXCEPTION;
  invalid_filehandle   EXCEPTION;
  invalid_operation    EXCEPTION;
  read_error           EXCEPTION;
  write_error          EXCEPTION;
  internal_error       EXCEPTION;
  invalid_maxlinesize  EXCEPTION;
  invalid_filename     EXCEPTION;
  access_denied        EXCEPTION;
  invalid_offset       EXCEPTION;
  delete_failed        EXCEPTION;
  rename_failed        EXCEPTION;

something like:

ops$tkyte@ORA920> create or replace function file_exists( p_path in varchar2,
p_fname in varchar2 ) return varchar2
  2  as
  3          l_temp utl_file.file_type;
  4  begin
  5          l_temp := utl_file.fopen( p_path, p_fname, 'r' );
  6          utl_file.fclose( l_temp );
  7          return 'Y';
  8  exception
  9          when utl_file.invalid_path then return 'N';
 10          when utl_file.invalid_filename then return 'N';
 11          when utl_file.invalid_operation then return 'N';
 12  end;
 13  /

Function created.

ops$tkyte@ORA920> column e format a10
ops$tkyte@ORA920> select file_exists( '/tmp/', 'x.txt' ) e from dual;

E
----------
Y

ops$tkyte@ORA920> select file_exists( '/tmp/', 'y.txt' ) e from dual;

E
----------
N

ops$tkyte@ORA920>

would be more like it.

Rating: 5
It works! thank you August 02, 2003
Reviewer: Yogeeraj from Mauritius
hello,

Thanks a million.

I went through the Docs for Oracle 8i - Supplied PL/SQL Packages Reference and
found the following exceptions:

Exception Name         Description
===================
==========================================================
INVALID_PATH         File location or filename was invalid.
INVALID_MODE         The open_mode parameter in FOPEN was invalid.
INVALID_FILEHANDLE     File handle was invalid.
INVALID_OPERATION     File could not be opened or operated on as requested.
READ_ERROR         Operating system error occurred during the read operation.
WRITE_ERROR         Operating system error occurred during the write operation.
INTERNAL_ERROR         Unspecified PL/SQL error.

unlike in 9i which has more features:
Exception         Name Description
===================
==========================================================
INVALID_PATH        File location is invalid.
INVALID_MODE        The open_mode parameter in FOPEN is invalid.
INVALID_FILEHANDLE    File handle is invalid.
INVALID_OPERATION    File could not be opened or operated on as requested.
READ_ERROR        Operating system error occurred during the read operation.
WRITE_ERROR        Operating system error occurred during the write operation.
INTERNAL_ERROR        Unspecified PL/SQL error
CHARSETMISMATCH        A file is opened using FOPEN_NCHAR, but later I/O
operations use nonchar functions such as PUTF or GET_LINE.
FILE_OPEN        The requested operation failed because the file is open.
INVALID_MAXLINESIZE    The MAX_LINESIZE value for FOPEN() is invalid; it should
be within the range 1 to 32767.
INVALID_FILENAME    The filename parameter is invalid.
ACCESS_DENIED        Permission to access to the file location is denied.
INVALID_OFFSET        The ABSOLUTE_OFFSET parameter for FSEEK() is invalid; it
should be greater than 0 and less than the total number of bytes in the file.
DELETE_FAILED        The requested file delete operation failed.
RENAME_FAILED        The requested file rename operation failed.

and modified "your" procedure accordingly.

It works fine now.

yd@MYDB.MU> column e format a10
yd@MYDB.MU> select file_exists( '/tmp/', 'x.txt' ) e from dual;

E
__________
N

Elapsed: 00:00:00.02
yd@MYDB.MU> yd@DEV.CMT.MU> !touch /tmp/x.txt

yd@MYDB.MU> /

E
__________
Y

Elapsed: 00:00:00.01
yd@MYDB.MU>

Thank you again for your helping hand..

best regards
Yogeeraj

Rating: 3
INVALID_FILEHANDLE December 10, 2003
Reviewer: jp from manila
Hi Tom,

How do you solve and exception of 'UTL_FILE.INVALID_FILEHANDLE'?

thanks

Followup:
you don't?  it is a progammer error (you passed us a handle that wasn't a valid
handle!)

You let that one propagate to the client -- just like you would with "out of
space", "out of memory", etc

you do not WANT to catch ALL exceptions

you only want to catch those exceptions that

a) you expect
b) you can deal logically with

December 10, 2003 -- Thanks for the question regarding "DDL related queries?", version 8.1.7.4
You Asked
How do I get the following information from system tables -how do I
a)identify the DDL actions performed in last 24 hrs. If a developer overwrites
some function, is there any way to get the old code from system tables?
b)identify the dependent objects and get the list of other objects dependent on
the object in all schemas?
Thanks
Dilip
and we said...
a) auditing, you want to enable auditing.

No, once they overwrite the code -- the old code is gone, wiped out, not there
anymore.

Interesting idea I just had tho -- you could use a DDL trigger (before create)
to maybe capture the code and do the audit as well?

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table old_code
  2  as
  3  select user username, 0 version, sysdate date_changed, user_source.*
  4    from user_source
  5   where 1=0
  6  /

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create sequence version_seq;

Sequence created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace trigger create_trigger
  2  before create on schema
  3  declare
  4          l_date  date := sysdate;
  5          l_ver   number;
  6  begin
  7          if (ora_dict_obj_type in ( 'PACKAGE', 'PACKAGE BODY', 'PROCEDURE',
'FUNCTION' ) )
  8          then
  9                  select version_seq.nextval into l_ver from dual;
 10
 11                  insert into old_code
 12                  select user, l_ver, l_date, user_source.*
 13                    from user_source
 14                   where name = ora_dict_obj_name
 15                     and type = ora_dict_obj_type;
 16          end if;
 17  end;
 18  /

Trigger created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function f return number
  2  as
  3  begin
  4          return 0;
  5  end;
  6  /

Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from old_code;

no rows selected

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function f return date
  2  as
  3  begin
  4          return sysdate;
  5  end;
  6  /

Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from old_code;

USERNAME   VERSION DATE_CHAN NAME TYPE     LINE TEXT
---------- ------- --------- ---- -------- ---- -------------------------
OPS$TKYTE        2 17-OCT-02 F    FUNCTION    1 function f return number
OPS$TKYTE        2 17-OCT-02 F    FUNCTION    2 as
OPS$TKYTE        2 17-OCT-02 F    FUNCTION    3 begin
OPS$TKYTE        2 17-OCT-02 F    FUNCTION    4         return 0;
OPS$TKYTE        2 17-OCT-02 F    FUNCTION    5 end;

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

b) DBA_DEPENDENCIES has that information.
Review & Followup
Rating: 4
October 17, 2002
Reviewer: Dilip Majithia
That's what I was looking for!
Thanks

Rating: 5
October 17, 2002
Reviewer: Dave from Ohio
Now THAT is going straight onto all of my databases! What a great bit of work,
thanks a lot.

Rating: 5
October 17, 2002
Reviewer: Bhagat Singh from Bhagat Singh Delhi,India
May be it may look bit of work but actually till the problem is not solved it is
big it is really very good/usefull sample of code given by Tom. I really
appreciate his efforts they are commendable.

Rating: 4
October 18, 2002
Reviewer: Sagi from India
Hi Tom,

Simply superb.

Regards.

Rating: 4
Great work October 18, 2002
Reviewer: Vijay'S from India
Simply great all of things you tell Tom are worth reading and implementing

Rating: 5
source text in one row? December 10, 2003
Reviewer: Susan from Watertown, MA
Tom,

Could you recommend an approach for storing the old_code text in one row for a
given object instead of a row per line of code.  Thanks for any suggestions.

Followup:
guess I'd ask "why" since if you restore it, you'd want it in seperate lines so
you can use the array interface with dbms_sql to parse it.

but, sure, you can use a clob, insert an empty_clob() and "return it", and then
use dbms_lob.writeAppend to the clob each of the lines

December 10, 2003 Gediminas -- Thanks for the question regarding "Blob insert together with sequence numbers", version 8.1.6
You Asked
I slightly modified your example. It's important for me to use
sequence numbers.

      import java.sql.*;
      import java.io.*;
      import oracle.jdbc.driver.*;
      import oracle.sql.*;
      class test {

  public static void main (String args []) throws SQLException ,
FileNotFoundException, IOException
      {
  DriverManager.registerDriver (new
oracle.jdbc.driver.OracleDriver());
          Connection conn =
DriverManager.getConnection("jdbc:oracle:thin:@lupa.vu.lt:1521:or
as8i","scott",
"tiger");
          conn.setAutoCommit(false);
          Statement stmt = conn.createStatement();
          stmt.execute( "delete from demo" );
          System.out.println( "deleted from demo" );

          stmt.execute( "insert into demo (id,theBlob) values
(s_enr.nextval,empty_blob())" );
         // System.out.println( "inserted into demo" );

          conn.commit();
          System.out.println( "committed" );

  ResultSet rset = stmt.executeQuery ("SELECT theBlob FROM  demo
where id =s_enr.currval  FOR UPDATE");
          System.out.println( "Executed Query" );

          if(rset.next())
          {
              System.out.println( "Fetched row " );

              BLOB l_mapBLOB =
((OracleResultSet)rset).getBLOB(1);

              File binaryFile = new File("e:\\free\\jo.jpg");
              FileInputStream instream=new
FileInputStream(binaryFile);

              int chunk = 32000;

              System.out.println( "Chunk = "+ chunk );

              byte[] l_buffer = new byte[chunk];

              int l_nread = 0;

              OracleCallableStatement cstmt =
 (OracleCallableStatement)conn.prepareCall( "begin
dbms_lob.writeappend( :1, :2, :3 ); end;" );

              cstmt.registerOutParameter( 1, OracleTypes.BLOB );
              while ((l_nread= instream.read(l_buffer)) != -1)
              {
                  cstmt.setBLOB(  1, l_mapBLOB );
                  cstmt.setInt(   2, l_nread );
                  cstmt.setBytes( 3, l_buffer );

                  cstmt.executeUpdate();

                  l_mapBLOB = cstmt.getBLOB(1);
              }
              instream.close();

              conn.commit();
              rset.close();
              stmt.close();

            }
}
      }

When I run  program I get

ava.sql.SQLException: ORA-02287: sequence number not allowed
here
    void
oracle.jdbc.dbaccess.DBError.throwSqlException(java.lang.String,
java.lang.String, int)
    void oracle.jdbc.ttc7.TTIoer.processError()
    void oracle.jdbc.ttc7.Oall7.receive()
    void oracle.jdbc.ttc7.TTC7Protocol.doOall7(byte, byte, int,
byte[], oracle.jdbc.dbaccess.DBType[],
oracle.jdbc.dbaccess.DBData[], int,
oracle.jdbc.dbaccess.DBType[], oracle.jdbc.dbaccess.DBData[],
int)
    oracle.jdbc.dbaccess.DBColumn[]
oracle.jdbc.ttc7.TTC7Protocol.parseExecuteDescribe(oracle.jdbc.db
access.DBStatement, byte[], oracle.jdbc.dbaccess.DBType[],
oracle.jdbc.dbaccess.DBData[])
    void oracle.jdbc.driver.OracleStatement.doExecuteQuery()
    void oracle.jdbc.driver.OracleStatement.doExecute()
    void
oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout()
    java.sql.ResultSet
oracle.jdbc.driver.OracleStatement.executeQuery(java.lang.String)

    void test.main(java.lang.String[])
Exception in thread main

How could I use sequence numbers with blob ?
and we said...
It really has nothing to do with the blob -- it is the use of a
sequence in the WHERE clause of a query:

tkyte@TKYTE816> select s.nextval from dual;

   NEXTVAL
----------
         1

tkyte@TKYTE816> select s.currval from dual;

   CURRVAL
----------
         1

tkyte@TKYTE816> select * from dual where s.currval = 1;
select * from dual where s.currval = 1
                           *
ERROR at line 1:
ORA-02287: sequence number not allowed here

What you'll want to do is:

  select s.nextval from dual;
  (fetch that into a host variable in your java program)

  insert into demo(id,theBlob) values (?,empty_blob() );
  (bind in that host variable from the select from dual above)

  select theBlob from demo where id = ? for update;
  (bind in that host variable from the select from dual above)
Review & Followup
Rating: 4
July 11, 2002
Reviewer: Robert Boyle from Cork, Ireland
Tom, sorry for posting here, but this should be a simple follow on from this
question.

Can you not use the RETURNING clause in the insert of this example to retrieve
the sequence number?  In other words, are you restricted to only getting the
sequence number by select the sequence.nextval before you do the insert?

Thanks
Robert

Followup:
sure, in fact, they do not even need to get the sequence technically, just the
blob.

They could have used returning on the blob column as well.

Rating: 4
July 11, 2002
Reviewer: Robert
Tom,
Can you give an example of this?

We tried this in our code before without much luck.(SQLJ)  We even tried running
it as an anonymous plsql block but still no luck.  We never received an error,
and the row inserted ok, but we were never successful in returning the sequence
id (which we need to update the blob later on in the code - although according
to your statement we wouldn't need that..would the blob indicator do the job?).

Sorry for the follow up, but being having a limited knowledge of Java I am
trying to put what I know from PLSQL into practise in SQLJ and I know it doesn't
always work.

Cheers
R.

Followup:
Sure, in SQLJ, it would look like this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create sequence t_seq;

Sequence created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace
  2     and compile java source named "returningTest"
  3  as
  4  import java.io.*;
  5  import java.sql.*;
  6
  7  public class returningTest
  8  {
  9  public static void run() throws SQLException
 10  {
 11  int   id = 0;
 12
 13      #sql { begin
 14             insert into t values ( t_seq.nextval ) returning x into :out id;
 15             end;
 16           };
 17
 18      System.out.println( "java says " + id );
 19  }
 20
 21  }
 22  /

Java created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> show errors
No errors.
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure returningTest
  2  as language java
  3  name 'returningTest.run()';
  4  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set serveroutput on
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_java.set_output(100000)

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec returningTest;
java says 1

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec returningTest;
java says 2

PL/SQL procedure successfully completed.

Rating: 5
Do you have example for jdbc? April 17, 2003
Reviewer: Winston
Hi Tom,

Is there any way to directly retrieve the value from returning clause in java
withou putting the "insert into ... returning" into a stored function? I tried
something like this:

PreparedStatement ps =                      conn.prepareStatement( " insert into
ttt values (tmp_seq.nextval) returning c1 into ? ");
ps.executeQuery();
rs = ps.getResultSet();
while (rs.next()) {
       rs.getInt(1);
 }

but I got "java.sql.SQLException: ORA-01008: not all variables bound
"

Followup:
conn.prepareStatement( "begin insert into
ttt values (tmp_seq.nextval) returning c1 into ?; end;");

will do that.

Rating: 4
have to use callableStatement April 21, 2003
Reviewer: winston
        CallableStatement cs =
            conn.prepareCall(
                "begin insert into ttt values (tmp_seq.nextval) returning c1
into ?; end;");
        int obj = 0;

            cs.registerOutParameter(1, OracleTypes.INTEGER);
            cs.execute();
            obj = cs.getInt(1);

And I have benchmarked "returning clause" vs. 'insert + select seq from dual'.
Using "returning clause" is Much faster and more scalable

Rating: 3
missing JDBC method implementation ... September 08, 2003
Reviewer: Klaus from Vienna/Austria - no kangaroos ;)
Hi all,

some questions about this topic, because the provided implementation is a bit
tricky and not really obvious!

Why for example is the JDBC Standards defined method getGeneratedKeys not
implemented for such purposes?(returning drawn Sequences-Id's while executing an
Insert-Statement)

public ResultSet getGeneratedKeys() returns with a SQLException mentioning
something like "feature not implemented!"

Why do we have to use an anonymous PL/SQL-Block, when
the SQL i'm executing is not PL/SQL-Block?

any ideas for future developments/corrections of the issue?

any comments/hints/etc are truly welcome!

kind regards
K:) UNGER

Followup:
sorry, not really a java programmer -- I'm a database developer.

but -- we (oracle) don't have "generated keys" -- YOU (the developer) populated
these keys explicitly.  we cannot implement that which does not exist.

Rating: 5
Another layer December 10, 2003
Reviewer: George from Chicago
Suppose that you had a java program that called a stored procedure that
contained an insert that you wanted to return the sequence num. to the java
program (using the returning clause)

eg:

java calls the procedure
the procedure contains an insert that uses a sequence in the values clause, the
java program needs that sequence number for future updates. What would the code
look like for that?

Thanks for everything Tom, you're the man

Followup:
java would prepare a callable statement:

   begin p( ...., ? ); end;

where that last ? is the sequence as an out parameter and the ... are the rest
of the inputs to the procedure to insert.

procedure would be:

procedure p( ....., p_seq OUT number )
is
begin
    insert into t ( .... ) values ( .... ) returning ID into p_seq;
end;

Rating: 5
Thanks! December 10, 2003
Reviewer: George from Chicago

December 10, 2003 -- Thanks for the question regarding "Accessing views/tables from Discoverer only", version 8.1.7
You Asked
Tom,
We would like to put in a few restrictions on certain  de-normalized views we
have in the oracle database, so that end users can get to these views only from
Discoverer and not any other tool like Microsoft Access(using ODBC...).

Can this be done.  If so can you please explain how?

Thanks
and we said...
I've asked Derrick Cameron -- our resident Business Intelligence expert -- to
look at this and he says:

...
Disco 9i still uses database userids, unless you are using an 'APPS'
EUL, which then uses Oracle Applications user, proxying in with the
database user APPS.  You can fetch v$session.module and see that
Discoverer4, and know that way the calling program.  You could have this
in a security policy if you want to restrict access to tables to only
Discoverer.  A couple of years ago I wrote the attached script to
protect public EULs from external tampering, if that is what you're
getting at.  9iAS v3 will have enterprise security, but not till then.
....

<note from tom>Since any application could reasonably call
DBMS_APPLICATION_INFO.SET_MODULE and put "Discoverer4" in there -- this is not a
100% solution.  You could also use the username of the connecting user (make
sure they are one of the people that should be running DISCO, the IP address of
the client (if you are using 3 tier, the ip address would be of the disco
server, not the actual end users) and whatever else you wanted to verify that
they should have access</note>

here is the code for reference:

-- This function applies a dynamic predicate to specified tables to check
whether  the user is accessing
-- the table/view through discoverer
-- if you are using the EUL gateway the policy must be disabled
-- after compiling this function you need to apply it using the dbms_rls package

FUNCTION secure_eul (obj_schema VARCHAR2, obj_name VARCHAR2)
   RETURN VARCHAR2 IS
   d_predicate VARCHAR2(2000);
   --
   access_program         number            := 0;
   --
BEGIN
   --
   -- first see if user is updating the EUL tables with discoverer
   --
   SELECT    count(*)
   INTO        access_program
   FROM        sys.v_$session
   WHERE    module not like '%Discoverer%'
   AND        module is not null
   AND        username = user
   AND        type     = 'USER';
   --
   IF access_program >0 THEN
   --
   d_predicate := '1=2';     -- deny access
   --
   ELSE
   --
   d_predicate := '1=1';  -- allow access
   --
   END IF;
   --
RETURN d_predicate;

END;

REATE OR REPLACE PACKAGE APPLY_EUL4_SECURITY IS
--*****************************************************************************
--
   PROCEDURE  prc_add_policies;
   --
   PROCEDURE  prc_drop_policies;
   --
   --
      END APPLY_EUL4_SECURITY;
--
/
---------------------------------------
-- PACKAGE BODY DEFINITIONS
---------------------------------------
--
CREATE OR REPLACE PACKAGE BODY APPLY_EUL4_SECURITY IS
--
--*****************************************************************************
--
PROCEDURE prc_add_policies IS
   --
   BEGIN
   --
   -- Apply disc_policy to EUL tables
   -- Parameters:    discoverer         - owner of table
   --        eul_versions     - table or view name
   --        disc_policy     - name of policy
   --        discoverer        - owner of policy function
   --        secure_eul    - name of function
   --
    DBMS_RLS.ADD_POLICY ('discoverer', 'EUL4_VERSIONS', 'disc_policy',
'discoverer','secure_eul');
    DBMS_RLS.ADD_POLICY ('discoverer', 'EUL4_ACCESS_PRIVS', 'disc_policy',
'discoverer','secure_eul');
.......  (add to whatever tables/views you wanted)......

<note from tom again>the above is using VPD also known as fine grained access
control.  For more information on that, see:
http://asktom.oracle.com/~tkyte/article2/index.html
Review & Followup
Rating: 3
runtime business area November 11, 2002
Reviewer: Oct from Us
Hi Tom
Thanks for great support to all.
Please let us know is it possible to hide Discoverer 4.1 business area and work
book from clients/user ,somthing like runtime forms and report in DEv2k.
Other wise after installing module to client side its open to all customers.
Thanks
Oct

Followup:
I asked Derrick Cameron, my discoverer guru, and he says:

Disco admin grants users and/or roles to business areas.  If not granted they
don't see it when creating/running workbooks.  Same goes for workbooks, although
granting in that case is not done in admin, but is done by workbook owner in
desktop or sqlplus/java

Rating: 4
Implementing a secuirty policy November 19, 2002
Reviewer: Navaz from SINGAPORE
The Below code from the site
(
http://asktom.oracle.com/~tkyte/article2/index.html
SQL> create function my_security_function( p_schema in varchar2,
  2                                        p_object in varchar2 ) return
varchar2
  3  as
  4  begin
  5      if ( user = 'RLS_ADMIN' ) then
  6          return '';
  7      else
  8          return 'owner = USER';
  9      end if;
 10  end;
 11  /

Function created.
In the above Function  i need to include one more condition
how i need to include?
Code shown below filters by business entity(company)
but i still need filter by customers for the same object please explain

create or replace  function my_security_function(p_schema in varchar2,
                                    p_object in varchar2)
                                    return varchar2
as
V_BE VARCHAR2(50);
Begin
   v_be :=  ('HTE-004','HTE');
   v_be := 'BE_ID'||' '||'IN'||' '||V_BE;
    if (user = 'V5100CUBE') then
       return (V_BE);
   elsif (user = 'JOHN') then
        return 'BE_ID = KCT';
    else
         return '';

   end if;
 end;
--
begin
dbms_rls.add_policy
 ( object_schema => 'V5100',
 object_name => 'INVENTORY_SERIAL_NUMBERS',
 policy_name => 'MY_POLICY',
 function_schema => 'V5100',
policy_function => 'my_security_function',
 statement_types => 'select, insert, update, delete' ,
 update_check => TRUE );
 end;
--

Followup:
Just return whatever predicate you need?  your code isn't correct it would be:

 if ( user = 'V5100CUBE' ) then
     return 'be_id in ( ''HTE-004'', ''HTE'' ) ';
 elsif ( user = 'JOHN' ) then
     return 'be_id = ''KCT'' ';
 else
     return '';
 end if;

if you have something to add like "and company = ..." just add it:

 if ( user = 'V5100CUBE' ) then
     return 'be_id in ( ''HTE-004'', ''HTE'' ) AND company = <whatever> ';
 elsif ( user = 'JOHN' ) then
     return 'be_id = ''KCT'' ';
 else
     return '';
 end if;

Rating: 4
ashraf November 21, 2002
Reviewer: ashraf from luwait
when i use a discoverer with analysis 2 million records with sql that return
about 750 records i get a message call not all data is retrived the report will
be inacurate

how and why

Followup:
Derrick says:

yes.  There is a setting that sets the maximum number of rows that can be
returned, and when you have an aggregated crosstab query (with page items),
Disco will retrieve up to your max, but warns you the aggregations are based on
what is available and not necessarily what is in the table

so, how to get the entire thing..

a couple of things... when a summary query is issued to the db, the aggregation
is done there.  But, it is quite possible that even after the aggregation the
number of rows exceeds your limit.  This is often caused by the inclusion of a
page item (LOV on the top) which drives the detail up.  Notwithstanding that, if
you do indeed have a lot to display they you can bump up the row limit.  It is
fairly low in default... maybe 1000, 5000 i don't know.  I aways bump up to 50k
or 100k - but then there is network issues to consider.  It is not practical to
view very large volumns of data so I think the approach is sound (setting
limits, where processing is done, etc.).  If you set a condition, again the
filter is set in the query at the db, so that can be used.

Rating: 4
Implementiong a security policy November 22, 2002
Reviewer: Navaz from SINGAPORE
create or replace  function my_security_function(p_schema in varchar2,
                                    p_object in varchar2)
                                    return varchar2
as
begin
if ( user = 'V5100CUBE' ) then
     return 'be_id in ( ''HTE-004'', ''HTE'' ) ';
 elsif ( user = 'JOHN' ) then
     return 'be_id = ''KCT'' ';
 else
     return '';
 end if;
end;

begin
dbms_rls.add_policy
 ( object_schema => 'V5100',
 object_name => 'INVENTORY_SERIAL_NUMBERS',
 policy_name => 'MY_POLICY',
 function_schema => 'V5100',
policy_function => 'my_security_function',
 statement_types => 'select, insert, update, delete' ,
 update_check => TRUE );
 end;

The above functions are created. It’s working fine, But I need to control by
session id, how to do it,
We have the Discoverer 4.1.43.07.00 versions, Client server based reports.
While we login to Discoverer user editions connecting by v5100cube, we already
control by company using dbms_rls policy ( ''HTE-004'', ''HTE'' ) so all the
reports filtering by based on policy. So it’s working fine, but the discoverer
session is still open. We are not close that discoverer.

After that we changed the company name (conditions)
 ( ''HTE-001'', ''HTE-002'' )  then again compile the above my security
functions. Now we again open discoverer user edition for another session but
connecting same user id (v5100cube). Based on the new condition now all the
reports filtering. It’s working fine.

Now two discoverer session is open, our problem is again open the first session
to refresh that particular report the filtering conditions is automatically
changed by new conditions. How can we avoid this?  Is there any way to control
by session id in dbms_rls policy?  Please explain

Followup:
I don't see how you would know what policy to apply to which session.  What is
the difference between these sessions?

If you can answer that -- then you can write procedural logic to return the
correct predicate.  You should NEVER rewrite the security policy function -- you
should have a single predicate policy that returns the correct and proper
predicate based on the state of the session.

I think you've missed the point of the predicate policy here.  You might want to
re-read that paper linked to above.  If you are changing the predicate function
-- you are doing it very wrong.

Rating: 4
Implementing security policy November 23, 2002
Reviewer: Navaz from SINGAPORE
Hope i have not explained the problem clearly in the previous mail.Now i tried
to explain little bit more clear
hopefully,thank you.

Step 1:
    Logging into Discoverer  user edition  with userid : xy and company =
'HTE01'
    now the reports are filtered based on the company HTE01 and its working
fine.
    The above session still remains open.
Step 2:
    Opening a new Discoverer user edition with same userid :xy and
   different company ='HTE02'
   the reports are filtered based on the company and its working fine.

Now the Problem
   If i returned to first session of discoverer now the reports are
filtered by the company='HTE02' which is wrong,by right it should
filter based on 'HTE01.

How to control of overlapping of filtering?

Overlapping here means the second session of discoverer
over writes the first session

-- The below function returns the be(company) and based on this discoverer
reports filters the report.

create or replace  function my_security_function(p_schema in varchar2,
                                    p_object in varchar2)
                                    return varchar2
as
V_BE VARCHAR2(50);
Begin
   v_be := ret_be; -- here i  am assigning company using function.
   v_be := 'be_id'||' '||'in'||' '||v_be;
    if (user = 'xy') then
       return (v_be); --here company will be returned based on user id
   elsif (user = 'pq') then
         return (v_be); --here company will be returned based on user id
    else
         return ' ';

   end if;
 end;

--the below is used for security policy
begin
dbms_rls.add_policy
 ( object_schema => 'V5100',
 object_name => 'INVENTORY_SERIAL_NUMBERS',
 policy_name => 'MY_POLICY',
 function_schema => 'V5100',
policy_function => 'my_security_function',
 statement_types => 'select, insert, update, delete' ,
 update_check => TRUE );
 end;

Followup:
If what you say is true -- then you have a bug in your v_be logic and you are
not returning the correct "company".

re-read the above link, get the "debug" package i have there that you can use in
your functions to see what they are doing.

Rating: 5
Using a password enabled role February 05, 2003
Reviewer: GVN from Houston
Can a password enabled role be used for restricting access so that the user can
access the views only through Discoverer  and not through any other tool (ex.
MSAccess).
If this can be done, how can this be implemented through Discoverer?

Thank you for your valuable advise

Followup:
not really -- and why would you limit it to discoverer?  seems funky to me.
either I can or I cannot access the data in a read only fashion.

Rating: 4
Using a password enabled role February 06, 2003
Reviewer: GVN from Houston
The reason for limiting it to Discoverer is
with Discoverer you cannot make wrong joins,
with Discoverer you can set up limits on the number of rows retrieved, time of
query execution etc.
If the user uses someother tool like MSAccess, it is possible to set up wrong
joins/queries therby slowing down performance of the database.

Followup:
Oracle9i  -- resource manager -- predicative query time estimation.  Proactively
cancel a query before it starts...

Oracle8i and before -- PROFILES -- limit CPU per call.  Reactively kill a query
when it is taking too long.

You can do stupid things in DISCO too.

=================================================================

Begin by creating the resource plan.  It limits statements to
1 second in duraction for members of the plan...

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> begin
  2          dbms_resource_manager.create_pending_area;
  3
  4          dbms_resource_manager.create_plan
  5          ( 'est_max_time',
  6            comment => 'limit by estimated time demo' );
  7
  8          dbms_resource_manager.create_consumer_group
  9          ( consumer_group => 'demo',
 10            comment => 'yes it is a demo' );
 11
 12          dbms_resource_manager.create_plan_directive
 13          ( plan => 'est_max_time',
 14            group_or_subplan => 'demo',
 15            comment => 'its a demo',
 16            MAX_EST_EXEC_TIME => 1 );
 17
 18          dbms_resource_manager.create_plan_directive
 19          ( plan => 'est_max_time',
 20            group_or_subplan => 'OTHER_GROUPS',
 21            comment => 'its a demo' );
 22
 23          dbms_resource_manager.validate_pending_area;
 24
 25          dbms_resource_manager.submit_pending_area;
 26
 27  end;
 28  /

PL/SQL procedure successfully completed.

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

Lets create that poor end user who is limited to 1 second statements
and assign him to this plan

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> drop user low_priority cascade;
drop user low_priority cascade
          *
ERROR at line 1:
ORA-01918: user 'LOW_PRIORITY' does not exist

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> create user low_priority identified by x;

User created.

demo@ORA920.US.ORACLE.COM> grant connect, resource to low_priority;

Grant succeeded.

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> begin
  2          dbms_resource_manager_privs.grant_switch_consumer_group
  3          ( 'low_priority', 'demo', TRUE );
  4
  5          dbms_resource_manager.set_initial_consumer_group
  6          ( 'low_priority', 'demo' );
  7  end;
  8  /

PL/SQL procedure successfully completed.

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

Now, we need to make the plan active

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> alter system set resource_manager_plan = est_max_time
scope = memory;

System altered.

demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> connect low_priority/x
Connected.
demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM>
demo@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

Now, we'll create a table, try to select from it and then see
what happens when we attempt a stupid query

low_priority@ORA920.US.ORACLE.COM>
low_priority@ORA920.US.ORACLE.COM> create table t as select * from all_objects;

Table created.

low_priority@ORA920.US.ORACLE.COM>
low_priority@ORA920.US.ORACLE.COM> analyze table t compute statistics;

Table analyzed.

low_priority@ORA920.US.ORACLE.COM>
low_priority@ORA920.US.ORACLE.COM> select count(*) from t;

  COUNT(*)
----------
     24696

low_priority@ORA920.US.ORACLE.COM>
low_priority@ORA920.US.ORACLE.COM> select count(*) from t,t;
select count(*) from t,t
                     *
ERROR at line 1:
ORA-07455: estimated execution time (9840 secs), exceeds limit (1 secs)

low_priority@ORA920.US.ORACLE.COM>

Rating: 3
Discoverer ASK ? December 10, 2003
Reviewer: Sue A from Burton On Trent (beer capital of England)
Tom
your advice is invaluable but you don't want to get loaded down with people
asking Discoverer-related questions which Derrick Cameron could answer.  Any
chance of an Ask Derrick for us poor benighted Discoverer users out here ?

Followup:
Derrick works for me -- i forward them on.

December 10, 2003 Raghu -- Thanks for the question regarding "moving tables across tablespaces", version
You Asked
Tom, What is the best way to move tables from one
tablespace to another tablespace in the same instance?
and we said...
I'll give you the Oracle8.0 and before answer as well as the Oracle8i and above
answer (in 8i, this is *trivial*, in 8.0 and before, it takes practice and
time)....

There are 2 methods we can use to do this.  One is to use a combination of
"alter table X move tablespace Y" and "alter index X rebuild tablespace Y"  --
this works in Oracle8i release 8.1 and up ONLY.  Attached is a script called
moveall.sql.

Moveall.sql
set echo off

column order_col1 noprint
column order_col2 noprint

set heading off
set verify off
set feedback off
set echo off

spool tmp.sql

select decode( segment_type, 'TABLE',
                       segment_name, table_name ) order_col1,
       decode( segment_type, 'TABLE', 1, 2 ) order_col2,
      'alter ' || segment_type || ' ' || segment_name ||
      decode( segment_type, 'TABLE', ' move ', ' rebuild ' ) ||
      chr(10) ||
      ' tablespace &1 ' || chr(10) ||
      ' storage ( initial ' || initial_extent || ' next ' ||
        next_extent || chr(10) ||
      ' minextents ' || min_extents || ' maxextents ' ||
        max_extents || chr(10) ||
      ' pctincrease ' || pct_increase || ' freelists ' ||
        freelists || ');'
  from user_segments,
       (select table_name, index_name from user_indexes )
 where segment_type in ( 'TABLE', 'INDEX' )
   and segment_name = index_name (+)
 order by 1, 2
/

spool off

set heading on
set verify on
set feedback on
set echo on

REM UNCOMMENT TO AUTO RUN the generated commands
REM ELSE edit tmp.sql, modify as needed and run it
REM @tmp

 --- eof ----

It uses the user_segments table to generate all of the needed "alter table move"
and "alter index rebuild" statements to move a table/index into another
tablespace preserving the storage characteristics currently assigned to the
object.  For example, when we run moveall.sql in the SCOTT schema, we might see:

scott@ORACLE> @moveall
scott@ORACLE> set echo off

alter TABLE ACCOUNT move
 tablespace users
 storage ( initial 10240 next 10240
 minextents 1 maxextents 121
 pctincrease 50 freelists 1);

alter TABLE BONUS move
 tablespace users
 storage ( initial 10240 next 10240
 minextents 1 maxextents 121
 pctincrease 50 freelists 1);

alter TABLE DEPT move
 tablespace users
 storage ( initial 10240 next 10240
 minextents 1 maxextents 121
 pctincrease 50 freelists 1);

alter INDEX PK_DEPT rebuild
 tablespace users
 storage ( initial 10240 next 10240
 minextents 1 maxextents 121
 pctincrease 50 freelists 1);

....

It begins by moving a table and then rebuilding each of the indexes on that
table.  Since the indexes on the tables being moved will become unusable after
the table, this script rebuilds them right after moving a table -- before moving
the next table (to reduce downtime).

Running the moveall.sql script is harmless as it is written.  It generates the
SQL you need to run and saves the sql into yet another script file "tmp.sql".
You should edit tmp.sql, review it, modify it if you want (eg: if you have a
multi-cpu system, you could modify the index rebuilds to be "parallel N",
"unrecoverable" and add other options to make them go faster on your system),
and then run it.

Another method would be to use EXPort and IMPort.  You would

o EXP the user account.
o drop all of the objects this user owns.  You can 'select' the drop statements
you need (script attached) in much the same way we 'select' the alter
table/index statements
o revoke UNLIMITED TABLESPACE from the user
o alter the users default tablespace to the target tablespace
o give the user an unlimited quota on this new tablespace and their temporary
tablespace
o IMP this users data.

So, the process to move SCOTT's objects from their current tablespace to a
NEW_TABLESPACE would be:

1) do an export of all of scott's objects.  Make sure no one modifies them after
you begin this process.  You will lose these changes if they do.

   $ exp userid=scott/tiger owner=scott

2) you would drop all of scotts tables.  This will get the indexes as well.  I
don't suggest dropping the user SCOTT but rather dropping scott's objects.
Dropping scott would cause any system priveleges SCOTT has to disappear and the
import would not restore them.  This script can be used to drop someones tables:

--------------------------------------
set heading off
set feedback off
set verify off
set echo off

spool tmp.sql
select 'drop table &1..' || table_name || ' cascade constraints;'
  from dba_tables
 where owner = upper('&1')
/
spool off
@tmp.sql
--------------------------------------

3) You would modify the user to *not* have unlimited tablespace (else the IMP
will just put the objects right back into the tablespace they came from) and
then give them unlimited quota's on the new tablespace you want the objects to
go into and on their temporary tablespace (for the sorts the index creates will
do)

alter user SCOTT default tablespace NEW_TABLESPACE
/
revoke unlimited tablespace from SCOTT
/
alter user SCOTT quota unlimited on NEW_TABLESPACE
/
alter user SCOTT quota unlimited on SCOTTS_TEMPORARY_TABLESPACE
/

4) you will IMP the data back in for that user.  IMP will rewrite the create
statements to use the users default tablespace when it discovers that it cannot
create the objects in their original tablespace.  Please make sure to review the
file imp.log after you do this for any and all errors after you import.

imp userid=scott/tiger full=y ignore=y log=imp.log

5) you can optionally restore 'unlimited tablespace' to this user (or not).  If
you do not, this user can only create objects in this new tablespace and temp
(which in itself is not a bad thing)...

As with any operation of this magnitude -- please test these procedures on a
small test account (such as SCOTT) to become familar with them.

A couple of side notes:

o the alter table move/alter index rebuild is more flexible and faster the
exp/imp (and less error prone -- you never actually drop the objects).
Additionally, it would be easy to modify the script to move TABLES to one
tablespace and INDEXES to a different tablespace.  The drawback to using this
method is the you cannot move a table with a LONG or LONG RAW.  You must exp
that table and imp it into a table.  You can do this easily  by exporting the
table with the LONG/LONG RAW, dropping that table -- creating an empty version
of this table in the new tablespace and importing just that table.

o if you use the exp/imp, it is upto you to ensure that no modifications happen
to the tables after you begin the export.  There are no mechanisms in place to
ensure this -- you must do this (else you will lose changes)

followup to comment #8

regarding the order columns.

No, it should not be like that.  It is correct as presented:

select decode( segment_type, 'TABLE',
                       segment_name, table_name ) order_col1,
       decode( segment_type, 'TABLE', 1, 2 ) order_col2,

If the segment_type is a table, return the SEGMENT_NAME (which is actually the
table name).  Else, if the segment type is not a table (it is an index) return
the TABLE_NAME that the index is on.  This'll group a table and all of its
indexes together.

The second column just makes sure that tables are done PRIOR to their indexes.
Review & Followup
Rating: 5
Great Answer. March 21, 2001
Reviewer: Irshad from KSA
This is a great answer with excellent examples.

Rating: 5
April 07, 2001
Reviewer: Pauline from U.S.A
Great answer!

Rating: 5
Excellent April 25, 2001
Reviewer: Syed Hadi from Kuwait
I was in need of this solution. Great!!!

Rating: 5
moving tables across tablespaces May 07, 2001
Reviewer: Roland Scheifele from Stuttgart, Germany
Very helpful tip! I could solve a topical problem.

Rating: 5
Reorganization May 08, 2001
Reviewer: Vikram from New Delhi, India
very usefull tips

Rating: 4
Awesome example explaining the feature May 25, 2001
Reviewer: Sai from USA
I am planning to do the "alter table move" in a couple of weeks as most of the
tables were confined to one single tablespace, and this example is very clear on
the procedure to be followed to move tables across tablespace.

Absolutely Fantastic.

Rating: 5
June 12, 2001
Reviewer: Rui Gongalves from Portugal - Lisbon

Rating: 5
Code Review July 02, 2001
Reviewer: Joydeep Ghosh from USA
Is not it should be like this...
decode( segment_type, 'TABLE',
                       table_name, segment_name ) order_col1
In code
'alter ' || segment_type || ' ' || segment_name ||....
segment_name replaced by
like this
decode(segment_type, 'TABLE', 'table_name ', 'segment_name' )

Rating: 5
Great!! July 10, 2001
Reviewer: Lynn from Bahrain
This was just what I was looking for!!

Rating: 5
Oracle DBA July 12, 2001
Reviewer: A reader
it just what i am looking for, working on,  and i would not get from another
experience oracle DBA.  Thank you very much.

Rating: 3
how can i move copy form tablespace July 15, 2001
Reviewer: sohel from Bangladesh
yes, it was very helpful for me, i am an oracle user, so i need this things
frequently, thanks for everything...

sohel

Rating: 3
Its amazing August 08, 2001
Reviewer: K.Srinivasan from Madurai, Tamil Nadu, India
Now only I come to know that it is possible to move a table from one tablespace
to another. Its very useful for me.

Rating: 5
November 01, 2001
Reviewer: Mohammed Abubakker from London
Excellent Most helpful

Rating: 5
Excellent November 20, 2001
Reviewer: Boris Oblak from Slovenia
Excellent

Rating: 5
Sooo Great a Solution January 28, 2002
Reviewer: Srinivas Kuncham from India
i was very very much pleased to see the solution. it had saved much of my time
and headache. Thank u Tom Thank u very mcuh for ur solution...

Rating: 3
Table Space March 16, 2002
Reviewer: R.RamNarayan from INDIA
The Solution you provided was useful. My Question is

I have two Schemas let us say PAY1 and PAY2. Both Contain
Tables with Similar Structure. I want to move the Data from
PAY1 to PAY2 i.e Insert data in the respective tables of PAY1 from PAY2. Could
you please tell me as i want it during Year Forwarding

Followup:
insert into pay2.table select * from pay1.table;

is the easiest way to do that.

Rating: 5
Parag's Feedback March 17, 2002
Reviewer: Parag from India
Too good Tom.
Appriciate ur hardwork with fantastic EXAMPLE.

Carry On Boss.....

Rating: 5
follow up March 24, 2002
Reviewer: Irfan from Kuwait
Can I use the first method (decode) to move table from dictionary managed
tablespace to locally managed tablespace
without making any change in the script ?

Followup:
sure, this works from any tablespace to any other tablespace.  for an LMT, I
would just as soon leave the storage off all together and use uniformt extents.

Rating: 5
What happens to un-specified table properties ? March 24, 2002
Reviewer: Sameer Utrankar from NJ
You generated alter table move script with storage parameters and tablespace but
what happens to un-specified table properties (eg cache. monitoring etc) or
physical properties (organization heap/index, cluster, lob etc) ?

The question is not really for these specific parameters that I mentioned above
but since 'create table' has whole bunch of options and only a few are used in
your script, my question is 'will the unspecified paramters be retained by
default from what the table originally had' or 'will these unspecified
parameters be lost' ?

If they are lost then that would just mean, one need to be careful before
generating a script for all tables and must take care of special parameters that
table has before moving it.

Your comment please ?

Followup:
It'll keep heap/iots in place:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int primary key )
organization index;

Table created.

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

OBJECT_TYPE  OBJECT_NAME                    S TABLESPACE_NAME
------------ ------------------------------ - ------------------------------
INDEX        SYS_IOT_TOP_28786                USERS

TABLE        T

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t move tablespace system;

Table altered.

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

OBJECT_TYPE  OBJECT_NAME                    S TABLESPACE_NAME
------------ ------------------------------ - ------------------------------
INDEX        SYS_IOT_TOP_28786                SYSTEM

TABLE        T

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

clusters, they are "special", they cannot be moved -- you cannot move a TABLE in
a cluster (that would sort of defeat the purpose of a cluster).  You'll get:
ops$tkyte@ORA817DEV.US.ORACLE.COM> create index emp_dept_cluster_idx
  2  on cluster emp_dept_cluster
  3  /

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table dept
  2  ( deptno number(2) primary key,
  3    dname  varchar2(14),
  4    loc       varchar2(13)
  5  )
  6  cluster emp_dept_cluster(deptno)
  7  /

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table dept move tablespace system
  2  /
alter table dept move tablespace system
            *
ERROR at line 1:
ORA-14512: cannot perform operation on a clustered object

instead.

Lobs, lobs are lobs -- they are stored in a lob segment, they are NOT moved with
this, that is an entirely different command to move them!

but I would say YES, you must be CAREFUL with this as you should be with
anything that affects your data!

Rating: 4
What about the quota on the original tablespace March 26, 2002
Reviewer: vk from India
Excellent answer Tom. Just one point. We would need to check and remove the
quota on the original tablespaces, right?? That is in case we have allocated
specific quota to users rather then using Unlimited Tablespace system priv

Followup:
sure.  Just note that granting RESOURCE or DBA to a user implicitly grants
UNLIMITED TABLESPACE directly...

Rating: 5
May 15, 2002
Reviewer: A reader

Rating: 4
COPY COMMAND August 04, 2002
Reviewer: Basharat from Dubai
Tom, Just for my clarity, Can we use Copy command to move data from one table to
another in a different schema. If not Why?

Followup:
Yes you can.

Rating: 5
rebuilding of indexes September 10, 2002
Reviewer: MS from USA
If indexes were in a different tablespace then we don't have to rebuild them
right?
Let's say scott's tables are in USERS tablespace..but indexes are in index1.
Now we want to move scott's tables to another_ts tablespace.
So after we do this..we don't need to rebuild indexes..do we?

Thanks

Followup:
No, you need to rebulid them -- think about it, the rowids of each and every row
in the table have changed!  That is why you need to rebuild the indexes, they
are totally useless after the move.  They point to where the data WAS, not where
it IS.

Rating: 5
index rebuild September 10, 2002
Reviewer: MS from USA
So after moving the tablespace...we will do:
(1) alter INDEX PK_DEPT rebuild;
and that will rebuilt it and point it to
another_ts tablespace.
or we have to do:
(2) alter INDEX PK_DEPT rebuild
 tablespace another_ts
 storage ( initial 10240 next 10240
 minextents 1 maxextents 121
 pctincrease 50 freelists 1);

Will (2) rebuild them in another_ts.
if it does then should we do:
(3) alter INDEX PK_DEPT rebuild
 tablespace index1
 storage ( initial 10240 next 10240
 minextents 1 maxextents 121
 pctincrease 50 freelists 1);

Followup:
If you want it in another tablespace, you will include a tablespace clause, else
it goes right back where it came from.

PCTINCREASE STINKS.  don't use it. Use locally managed tablespaces, stop using
storage clauses and if you do use a storage clause on a dictionary managed
tablespace use INITIAL=NEXT and PCTINCREASE=0

Rating: 5
alter move command from DMT to LMTs September 22, 2002
Reviewer: ms from usa
Tom,
(8.1.7.3.0 in windows going to 8.1.7.4.0 in unix)
I brought a database from windows to unix....
I want to change form Dictionary managed Tablespaces (DMTs) to Locally Managed
tablespaces (LMTs).
I broght all the objects for all the schemas into one big tablespace...
Now I want to rearrange them into my small, medium, and large LMT's (tables and
indexes)
I have analyzed all the schemas with DBMs_STATS to get no of rows etc.
Now I am about to rerrange...

This is a small database (3 gig or so)
I have arranged 6 tablespaces (small, large, and medium...three for tables and
three for indices)

I am going to run commands like this following sample:

for tables:

select 'ALTER TABLE ' || ds.segment_name || ' MOVE TABLESPACE TS_SMALL;'
from dba_segments ds, dba_tables dt, v$parameter vp
where 1=1
  and vp.name = 'db_block_size'
  and ds.segment_type = 'TABLE'
  and ds.owner = 'A_SCHEMA'
  and ds.segment_name = dt.table_name
  and DS.TABLESPACE_NAME = DT.TABLESPACE_NAME
 and (dt.blocks*vp.value) <= 1000000
  and (dt.blocks*vp.value) IS not NULL;

and then changing the "and (dt.blocks*vp.value) <= 1000000"
according to size...and changing ' MOVE TABLESPACE TS_SMALL;'  to ' MOVE
TABLESPACE TS_MEDIUM;'
AND ' MOVE TABLESPACE TS_LARGE'  AND SO ON

Now I will run for index rebuild:

select 'ALTER INDEX ' || dba_segments || ' REBUILD TABLESPACE TS_IDX_SMALL;'
where SEGMENT_TYPE = 'INDEX'
AND OWNER = 'MANAGER'
and bytes is not null
and bytes <= 1000000
and bytes <> 0

and keep changing it for size to get those statements

Is my approach O.K. to rearrange tablespaces like that..
As I understood..for LMT's all the tables and indices will take the default for
the corresponding tablespace (like extent size storage etc...)
and we just divide tables and indexes according to size.

And yes, I did do the export as compress=N

Followup:
looks good to me -- just a variation on the approach above.

If you got "fancy", you would just run a single query for the tables and use
CASE  to decide the tablespace.

select 'ALTER TABLE ' || ds.segment_name || ' MOVE TABLESPACE ' ||
        case when dt.blocks*vp.value <= 100000 then 'TS_SMALL'
             when dt.blocks*vp.value <= NNNNNNN then 'TS_MED'
             else 'TS_BIG'
        end || ';'
.....

Rating: 5
(dt.blocks*vp.value) is null September 22, 2002
Reviewer: MS from USA
Thanks for a quick reply Tom...
Which tablespace do we put the tables that have (dt.blocks*vp.value) as null...

SEGMENT_NAME
 TABLESPACE_NAME                   EXTENTS     BLOCKS BYTES_ALLOC BYTES_USED
---------------------------------------------------------------------------------
 ------------------------------ ---------- ---------- ----------- ----------
EVT_DEST_PROFILE
 TS_ALL                                  5         10       81920
EVT_PROFILE
 TS_ALL                                  5         10       81920
MICROSOFTDTPROPERTIES
 TS_ALL                                  5         10       81920
MICROSOFTDTPROPERTIES
 TS_ALL                                  5         10       81920
SMP_AD_ADDRESSES_
 TS_ALL                                  5         10       81920
SMP_AD_DISCOVERED_NODES_
 TS_ALL                                  5         10       81920
SMP_AD_NODES_
 TS_ALL                                  5         10       81920
SMP_AD_PARMS_
 TS_ALL                                  5         10       81920
SMP_AUTO_DISCOVERY_ITEM_
 TS_ALL                                  5         10       81920
SMP_AUTO_DISCOVERY_PARMS_
 TS_ALL                                  5         10       81920
SMP_REP_VERSION
 TS_ALL                                  5         10       81920
SMP_SERVICE_DATA_
 TS_ALL                                  5         10       81920
SMP_SERVICE_GROUP_DEFN_
 TS_ALL                                  5         10       81920
SMP_SERVICE_GROUP_ITEM_
 TS_ALL                                  5         10       81920
SMP_SERVICE_ITEM_
 TS_ALL                                  5         10       81920
SMP_UPDATESERVICES_CALLED_
 TS_ALL                                  5         10       81920

16 rows selected.

Followup:
if that is null, then blocks is null and either

a) it is not a segment with data
b) you didn't analyze it

so put it where you want ;)

Rating: 5
ORA-03113: end-of-file on communication channel September 22, 2002
Reviewer: MS from USA
I was trying to run the statements to move tables to tablespaces.....
built all the statements dynamically....
and ran them..
it kept altering tables for a while....
then all of a sudden with one table...it gave the error:
ORA-03113: end-of-file on communication channel

any idea about this error?

and after that it gave :
ORA-03114: not connected to ORACLE
for all the others, ofcourse.

this was on oracle 8i sqlplus worksheet..connected from a windows workstation to
Oracle 8.1.7.4.0 on a UNIX box.

The statement it failed on was a snapshot...so, I thought may be snapshots
tables can not be moved...snapshots have to be dropped and recreated in correct
tablespace...but then I saw..some of the other snapshots tables statements
worked succesfully...

Also, what happen if accidently you run a move tablespace twice....by mistake..
like ALTER TABLE TEST MOVE TABLESPACE TS_SMALL;
AND THEN AGAIN
ALTER TABLE TEST MOVE TABLESPACE TS_SMALL;
will this mess anything up?

Thanks.

Followup:
3113 = call support, you should have a trace file on the server, a message in
your alert log.  They can diagnose the problem

Moving twice just consumes resources, no harm done otherwise.

Rating: 5
review September 23, 2002
Reviewer: Irfan from Kuwait
1. What changes should be done in script of first method moveall.sql, for moving
tables from 9i Locallay managed tablespace?

2. When I run the moveall.sql  from 9i database(locally managed tablespace) I
got the following script:

alter table EMP move  tablespace users
storage(initial 65536 next
minextents 1 maxextents 2147483645
pctincrease freelists 1);

In the above script next and pctincrease does not have any
value. Can this be safely run to move the table ?

Thanks.

Followup:
drop initial, next, minextents, maxextents pctincrease from the query.

Rating: 4
moving tables across tablespaces September 23, 2002
Reviewer: Bill Sonia from Birmingham, AL USA
I'm actually doing a similar process to 'rebuild' tables with chained rows but
have the added overhead of Replication.

Can I simply quiesce the group, perform this ddl and then restart replication?

Followup:
depends on the type of replication.

advanced, peer to peer, which is 100% based on primary key, yes.

snapshots -- only if the snapshots are based on PRIMARY KEY and not rowid.

In any case, if you simply "move the chained rows" (insert into temp, delete
from prod, insert temp into prod) -- you can do this with replication on.  If
the number of rows as a percentage of the table is "small", that might be
prefered.

Rating: 5
moving tables from 9i LMT to 9iLMT September 24, 2002
Reviewer: Irfan from Kuwait
I have changed query MOVEALL.sql as per your suggestion (removed initial, next,
minextents,maxextents, pctincrease )for 9i LMT, which is as follows.
Moveall.sql
-----------

set echo off
column order_col1 noprint
column order_col2 noprint

set heading off
set verify off
set feedback off
set echo off
spool ind.sql

select decode(segment_type,'TABLE',
                      segment_name,table_name) order_col1,
       decode(segment_type,'TABLE',1,2) order_col2,
       'alter '||segment_type||' '||segment_name||' '||
       decode(segment_type,'TABLE','move','rebuild')||
       chr(10) ||
       'tablespace &1'||chr(10)||
       'storage(  freelists '|| freelists||');'
  from  user_segments,
        (select table_name,index_name from user_indexes)
 where  segment_type in ('TABLE','INDEX')
   and  segment_name = index_name(+)
order by 1,2
/
spool off
---------------

When I run this I am getting following output

alter TABLE EMPBNF move
tablespace users
storage(  freelists 1);

alter TABLE EMPCVR move
tablespace users
storage(  freelists 1);

.........

Please confirm what I did is correct?
Thanks.

Followup:
is that what you wanted to output?  If so, that is correct. If not, try again.

I mean -- is that the statement that you wanted?  It is syntatically correct.

Rating: 5
Rearranging snapshots into LMTs after a move September 24, 2002
Reviewer: MS from USA
ALTER SNAPSHOT <SN_NAME> MOVE TABLESPACE <TS_NAME>;
will move snapshot into ts_small etc.
and it will also move the underlying table to the same tablespace..
Then we can run separate "ALTER INDEX REBUILD INDEX <IND_NAME> '
for all the indexes for that snapshot...
is there anything else that we need to worry about in case of snapshots....

Thanks.

Followup:
that should do it.

Rating: 5
Review September 24, 2002
Reviewer: Irfan from Kuwait
My original question was :
1. Should I specify the storage values for moving LMT to another LMT. As I know
that in LMT system decide the appropriate storage allocations.

So, moving the tables from LMT to another LMT is it necessary to give storage
allocations ? If it is necessary than I shall use your original Moveall.sql,
otherwise I shall ignore the storage allocations and will use the modified
script?

Thanks

Followup:
1) no, you shouldn't, with the exception of maybe freelists.

Rating: 5
ORA-00997: illegal use of LONG datatype September 30, 2002
Reviewer: MS from USA
(8.1.7.4.0 on UNIX)

While running alter table move tablespace (DMT to LMT)..I am getting this
error....
The tables that are giving this error have either LONG or LONG RAW columns in
them..
So how do you move them....

Thanks

Followup:
export/import.

better yet, create table as select using the TO_LOB function to get rid of the
nasty LONG/LONG RAW and convert it into a clob/blob

Rating: 4
what is the query used in dbls.sql December 20, 2002
Reviewer: Rob Gibson from Atlanta, GA
In you March 24, 2002 follow-up to Sameer Utrankar from NJ, you demonstrated the
location of an IOT using a sql script called dbls.sql.

Could you post the content of dbls.sql?

Thank you.

Followup:
column object_name format a30
column tablespace_name format a30
column object_type format a12
column status format a1

break on object_type skip 1

select object_type, object_name,
       decode( status, 'INVALID', '*', '' ) status,
       decode( object_type,
                'TABLE', (select tablespace_name from user_tables where
table_name = object_name),
                'INDEX', (select tablespace_name from user_indexes where
index_name = object_name),
                null ) tablespace_name
from user_objects a
where object_name like upper('%&1.%')
order by object_type, object_name
/

Rating: 5
A question on moving tables January 31, 2003
Reviewer: p001
I am using Oracle 9.2 version and I have a 1GB database. Right now all objects
(tables, indexes etc) sits in one table space. I wanted to reorganize these into
multiple tablespaces.

I saw in some threads above moving tables based on size, like large, medium and
small. What is the advantage of doing this?

Do we really see any significant performance improvements by moving objects into
different tablespaces?

Followup:
do you have more then one disk -- it is all about DISKS, nothing about
tablespaces really.

tablespaces can be used to place an object on a specific disk.
tablespaces can be made up from files from many disks (sort of like striping)

unless you have lots more "physical disks" and given that 1gig is pretty small
-- you probably won't see anything from this exercise.

it is not "different tablespaces" you want to look at but rather "even spread of
IO across all devices you own".  You may be able to use tablespaces to help you
achieve this on a big database.

Rating: 5
How to move LOBS February 04, 2003
Reviewer: Anton from NZ
Hi Tom.

I have just moved a table constaining a LOB column to a new tablespace.  How do
I move the LOB segment to the same new tablespace ?

Followup:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:378418239571

      Rating: 5
Problem in
      moving tables across tablespaces  February 05, 2003

Reviewer:  Ashiq Shamsudeen  from Trichy, Tamil Nadu,
      India
Hello Tom,

   I've problem in moving tables from one TBS(tablespace) to another.All the
objects of scott schema is in system tables so i

took a dump of it and created another user ashiq and assigned default TBS and
temp TBS , and granted all necessary privileges

which u've show us .Its throwing up error during importing of constraints saying
objects already exists,but as such no object

exists .See...

scott@RMS8> sho user
USER is "SCOTT"
scott@RMS8>
scott@RMS8> select * from tab;

TNAME                          TABTYPE  CLUSTERID
------------------------------ ------- ----------
BONUS                          TABLE
DEPT                           TABLE
DUMMY                          TABLE
EMP                            TABLE
SALGRADE                       TABLE
TEST                           TABLE
TTK                            TABLE

8 rows selected.

scott@RMS8> select constraint_name, constraint_type, table_name
  2  from user_constraints;

CONSTRAINT_NAME                C TABLE_NAME
------------------------------ - ------------------------------
PK_DEPT                        P DEPT
SYS_C007564                    C EMP
PK_EMP                         P EMP

3 rows selected.

scott@RMS8> select table_name, tablespace_name from user_Tables;

TABLE_NAME                     TABLESPACE_NAME
------------------------------ ------------------------------
BONUS                          SYSTEM
DEPT                           SYSTEM
DUMMY                          SYSTEM
EMP                            SYSTEM
SALGRADE                       SYSTEM
TEST                           SYSTEM
TTK                            SYSTEM

8 rows selected.

scott@RMS8>
scott@RMS8> conn system/manager@rmsreal8
Connected.
scott@RMS8> sho user
USER is "SYSTEM"
system@RMS8> create user ashiq identfied by ashiq;
create user ashiq identfied by ashiq
                  *
ERROR at line 1:
ORA-00922: missing or invalid option

system@RMS8> ed
Wrote file afiedt.buf

  1* create user ashiq identified by ashiq
system@RMS8> /

User created.

system@RMS8> grant connect , resource to ashiq;

Grant succeeded.

system@RMS8> alter user ashiq default tablespace users temporary tablespace
temp;

User altered.

system@RMS8> revoke unlimited tablespace from ashiq;

Revoke succeeded.

system@RMS8> alter user ashiq quota unlimited on users;

User altered.

system@RMS8>  alter user ashiq quota unlimited on temp;

User altered.

system@RMS8> host exp userid=scott/tiger@rmsreal8 owner=scott

system@RMS8> conn ashiq/ashiq@rmsreal8
Connected.

system@RMS8> sho user
USER is "ASHIQ"

system@RMS8>select constraint_name, constraint_type, table_name
  2  from user_constraints;

no rows selected

system@RMS8> select * from tab;

no rows selected

     As u see there are no objects before importing.Now , i get connected to
scott user to import the dump..

system@RMS8> conn scott/tiger@rmsreal8
Connected.
system@RMS8> sho user
USER is "SCOTT"
system@RMS8> host imp userid=scott/tiger@rmsreal8 parfile=ashiq.par

    The parfile has the following values ,they are...

FILE=expdat.dmp
IGNORE=n
gRANTS=y
constraints=y
fromuser=scott
toUSER=ashiq
log=ashiq_log.log

       Error what it gave is(i took from ashiq_log.log) ...

Connected to: Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production
With the Partitioning option
JServer Release 8.1.7.0.0 - Production

Export file created by EXPORT:V08.01.07 via conventional path
import done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set
. importing SCOTT's objects into ASHIQ
. . importing table                        "BONUS"          0 rows imported
. . importing table                         "DEPT"          4 rows imported
IMP-00015: following statement failed because the object already exists:
<=======
 "ALTER TABLE "DEPT" ADD  CONSTRAINT "PK_DEPT" PRIMARY KEY ("DEPTNO") USING I"
 "NDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 65536 NEXT 65536 MI"
 "NEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 50 FREELISTS 1 FREELIST GROUPS"
 " 1 BUFFER_POOL DEFAULT) TABLESPACE "SYSTEM" ENABLE "
. . importing table                        "DUMMY"          1 rows imported
. . importing table                          "EMP"         14 rows imported
IMP-00015: following statement failed because the object already exists:
<======
 "ALTER TABLE "EMP" ADD  CONSTRAINT "PK_EMP" PRIMARY KEY ("EMPNO") USING INDE"
 "X PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 65536 NEXT 65536 MINEX"
 "TENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 50 FREELISTS 1 FREELIST GROUPS 1 "
 "BUFFER_POOL DEFAULT) TABLESPACE "SYSTEM" ENABLE "
.
.
.
.
.

   Why its throwing this error??Though there is no objects before importing why
its showing the
error "Objects already exist" ??

     Why alter table tname move tablespace  doesn't work for global temporary
tables??If i tried to move i'm getting the

error "ORA-14451: unsupported feature with temporary table" .Then where it
actually  stores till a session get over??
  DB is 8.1.7

          Followup:

You would have to give me a full test case -- showing how the constraints were
created in the first place (since scott/tiger normally doesn't have them).

I cannot reproduce.

It doesn't make sense to move a global temporary table.  They don't "exist"
anywhere until someone uses them -- and then they "exist" in the users temporary
tablespace.

      Rating: 5
Problem in
      moving tables across tablespaces   February 06, 2003

Reviewer:  Ashiq Shamsudeen  from Trichy , Tamil
      Nadu, India
As u said ,Yes constraints won't be there for emp/dept tables. I added
constraints separately . Moreover i'm running this on client machine.

  Regarding the performance which is the best ,i mean
alter table Y move tablespace or exp/imp??

          Followup:

like I said -- I would need a full example.

I rebuilt scott/tiger.
I added constraints in every possible manner I could imagine anyone doing --
fully qualified names, from another schema, index owned by some other user, etc

could not reproduce.

So, If you can do the blow by blow -- like I do --- you know

o step 1, run demodrop
o step 2, run demobld
o step 3, <how to add constraint>
o step 4, exp/imp --> error

I would use alter table move in all cases if at all possible.  anytime you take
the data OUT of the database to put it back IN you stand a chance of losing
something, the data, an index, a constraint, grant something.  alter table move
-- no worries, you will not lose anything (and you don't read -> write to disk +
read from disk -> write -- you just read -> write, less io going on there)

      Rating:
      3
Confused  February 06, 2003
Reviewer:
      Helen  from UK
I may be totally wrong here but shouldn't:

  host imp userid=scott/tiger@rmsreal8 parfile=ashiq.par
be
  host imp userid=ashiq/ashiq@rmsreal8 parfile=ashiq.par

???

          Followup:

if scott/tiger is a DBA, the first one works but you know, I didn't try that.

But -- it works:

ops$tkyte@ORA817DEV> drop user ashiq cascade;

User dropped.

ops$tkyte@ORA817DEV> drop user scott cascade;

User dropped.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create user scott identified by tiger;

User created.

ops$tkyte@ORA817DEV> grant connect, resource, dba to scott;

Grant succeeded.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> alter user scott default tablespace users temporary
tablespace temporary;

User altered.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create user ashiq identified by ashiq;

User created.

ops$tkyte@ORA817DEV> grant connect, resource to ashiq;

Grant succeeded.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> host demobld scott tiger

SQL*Plus: Release 8.1.7.0.0 - Production on Thu Feb 6 09:01:20 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production

Building demonstration tables.  Please wait.
Demonstration table build is complete.
Disconnected from Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production

ops$tkyte@ORA817DEV> pause

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> @connect scott/tiger
ops$tkyte@ORA817DEV> set termout off
scott@ORA817DEV> REM GET afiedt.buf NOLIST
scott@ORA817DEV> set termout on
scott@ORA817DEV>
scott@ORA817DEV> alter table scott.dept add constraint dept_pk primary
key(deptno);

Table altered.

scott@ORA817DEV> alter table emp add constraint emp_pk primary key(empno);

Table altered.

scott@ORA817DEV>
scott@ORA817DEV> host exp userid=scott/tiger owner=scott

Export: Release 8.1.7.4.0 - Production on Thu Feb 6 09:01:21 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production
Export done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set

About to export specified users ...
. exporting pre-schema procedural objects and actions
. exporting foreign function library names for user SCOTT
. exporting object type definitions for user SCOTT
About to export SCOTT's objects ...
. exporting database links
. exporting sequence numbers
. exporting cluster definitions
. about to export SCOTT's tables via Conventional Path ...
. . exporting table                          BONUS          0 rows exported
. . exporting table                           DEPT          4 rows exported
. . exporting table                          DUMMY          1 rows exported
. . exporting table                            EMP         14 rows exported
. . exporting table                       SALGRADE          5 rows exported
. exporting synonyms
. exporting views
. exporting stored procedures
. exporting operators
. exporting referential integrity constraints
. exporting triggers
. exporting indextypes
. exporting bitmap, functional and extensible indexes
. exporting posttables actions
. exporting snapshots
. exporting snapshot logs
. exporting job queues
. exporting refresh groups and children
. exporting dimensions
. exporting post-schema procedural objects and actions
. exporting statistics
Export terminated successfully without warnings.

scott@ORA817DEV> host imp userid=scott/tiger ignore=n grants=y constraints=y
fromuser=scott touser=ashiq

Import: Release 8.1.7.4.0 - Production on Thu Feb 6 09:01:26 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production

Export file created by EXPORT:V08.01.07 via conventional path
import done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set
. importing SCOTT's objects into ASHIQ
. . importing table                        "BONUS"          0 rows imported
. . importing table                         "DEPT"          4 rows imported
. . importing table                        "DUMMY"          1 rows imported
. . importing table                          "EMP"         14 rows imported
. . importing table                     "SALGRADE"          5 rows imported
About to enable constraints...
Import terminated successfully without warnings.

      Rating: 4
Move objects
      with large extents takes enormous time  February 06, 2003

Reviewer:  Ramanathan Subramanian  from California
      USA
Hi Tom,

I am working on Oracle RDBMS version 8.1.7.3 (32 BIT)
I have more than 25 tables in a tablespace USERS having extent size of 16K and
number of extents range from 10,000 to 25,000. Yesterday I tried to rebuild one
table in another tablespace with extent size of 4MB. The table size was 400MB
and it had 12000 extents. It took 3 hrs to move this object to another
tablespace. When I was monitoring the process, the creation of new table
happened quickly, but the dropping of extents on the old tablespace took very
long time.

I used the following SQL.

alter session set sort_area_size=200000000;
alter session set db_file_multiblock_Read_count=128;
alter session set sort_multiblock_read_count=128;

alter table CUST.LOADTRACK move tablespace USR01 storage ( initial 4M next 4M
pctincrease 0 maxextents unlimited) ;

This table was moved from USERS tablespace.
I have a task of moving all the 25 objects to new tablespace with better storage
parameters. Some of the tables are of size 2GB.

How can I speed/tune this process to accomplish this task in 12 hours.

I appreciate your quick reply.
Thanks, Tom.

          Followup:

Are you using dictionary managed tablespaces.

DDL on those (drop, truncate) is painfully slow.

What you might want to do (little more involved) is:

create table NEW_Loadtrace tablespace usr01 .....
as select * from loadTrack;

(can be done nologging, in parallel even)

rename loadTrack to oldLoadTrack;
rename new_LoadTrack to loadTrack;
grant on it, index it, etc

that'll take the time the move would have if the object didn't have so many
extents.

Then, drop the table in the background -- when no one really cares how long it
takes....

      Rating: 5
sys schema
      maintenance  February 14, 2003
Reviewer:
      David
Tom, this is an old question of mine. I searched your site but at first glance I
couldn't find the answer.

As with other schemas that need some housekeeping, shouldn't the following be
tackled in the sys schema ?

1 Holes in tables due to deletes
2 Index rebuilds
3 Tablespace fragmentation (in DMT, not in the case of LMT/auto)
4 Statistics collection

I have this feeling that sys objects are kind of "stay" and thir management is a
bit "slack" ? Please comment on 1,2,3,4 individually.

Thank you

          Followup:

1) nope
2) nope
3) nope
4) not yet...

1,2,3 are things that most people do to their own objects to make themselves
feel better.

"Holes", well, they get reused.

"index rebuilds", easily 99% or more of these are 100% un-neccessary.  Many of
them are even harmful, but sigh, no one really measures the return then get so
they don't know...

"tablespace frag" -- happens only when you drop or truncate stuff -- two things
that NEVER should happen in system ;)

9i, you can analyze the sys schema, but it is not necessary (yet)... coming soon
to a theatre near you though.

      Rating: 5
On holes and
      rebuilds  February 14, 2003
Reviewer:  David

Tom, you sort of surprised me... I thought the items I wrote above would be
normal DBA tasks. It wasn't clear to me if you were talking of SYS or other
schemas too.

1. You mean I don't have to worry about holes because they get reused, even in
other schemas than sys ?

2. You mean I don't have to treat them ? Even if I do some operation that
deletes lots of rows from a crowded table ? And if it is supposed not to be
crowded any more (shouldn't I reorganize the table, say ALTER TABLE MOVE) ?

3. I hear about DBAs rebuilding indexes every "major" DML operations. Isn't it
necessary though ?

4. How about the so-called "sliding index syndrome" ? ( index extending because
of old keys and leaf rows with few entries )

5. Which rebuilds would be "un-necessary" and which would be "harmful" ?

6. When would it be really "necessary" to rebuild ? Isn't it when we have a lot
of deleted leaf rows ?

Thanks you for the answers. Please, point me to some threads of interest in this
regard.

          Followup:

1) yup
2) a "crowded table"?  I've never heard of a table being crowded but I think I
know what you mean.

You have a purge process.  A better way to do a purge, if you are deleting a
large percentage of the table is:

a) create table as rows to keep
b) drop old table
c) rename new table to old

3) not only NOT NECESSARY, but
   a) a waste of time typically
   b) counter productive
   c) never measured -- did they show that the index performs better tomorrow?
   d) sometimes even causes NEGATIVE impacts to performance

bitmap indexes are a glaring exception to this... b*trees pretty much take care
of themselves with the exeception of one or two de-generative cases, but they
are the exception and many of us will never see them in their systems.

4) give me a concrete example to work with here -- a simulation if you will.  It
is  what I would do for you.

5) most all and many.

6) No, not at all -- they get reused and it can be quite expensive to get those
"holes back".  See

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6601312252730
for example.  there, they rebuild an index and watch the system take the next
week and a half to get back to "normal" -- only to do it ALL OVER again next
month.  How productive.

Here is a case when you might COALESCE (not rebuild -- COALESCE)...

o you have an index on a column

o the values in this column are increasing (or decreasing) -- eg a sequence.
Specifically the values are not random - they are somewhat predicable -- always
bigger then before (or smaller)

o you delete from this table as you process rows (eg: this is a table where you
insert records to be processed and as you process them -- you delete them)

o you do not delete 100% of the records over time.  That is, record #55 might be
exceptional -- never processed for some reason.  It stays in this table forever.
 Record #1032 might too - and so on.

o you therefore end up with an index that has lots of leaf blocks with 1 or 2
rows on them on the "left" of the index and you'll NEVER insert a row into these
blocks agains (cause the new values are ALWAYS higher -- there will NEVER be a
row less then 1032 ever again.

In order to reclaim these very sparse blocks -- we can coalesce the index, merge
together adjacent index leaf blocks and taking the newly emptied block and
putting it on the free list.  No need to rebuild the entire structure, just get
back that which is yours period.

A finely tuned index will run at 75% plus or minus utilization by design on
average.  They are not built to be rebuilt -- they almost always take care of
themselves.

      Rating:
      5
ctas+drop+rename X alter table move  February 15,
      2003
Reviewer:  David
Tom, I gotta say... In a few lines you changed a lot of my concepts.

a) The "sliding index window" (I've heard people calling this many ways) is
exactly what you mentioned in your coalesce example. This is usually quoted as
the classic case why indexes SHOULD be rebuilt. I just changed my mind -- your
colasce description clears any doubt. Since coalesce was introduced in 8i (I
guess), what would people do before that... rebuild ?

b) Besides backup, I thought these were some of the most important DBA
housingkeeping tasks:

1 Holes in tables due to deletes
2 Index rebuilds
3 Tablespace fragmentation (in DMT, not in the case of LMT/auto)
4 Statistics collection

From what you said, I understand the only one I should care about is 4 --
gathering stats. Do you agree ?

c) In question 2 above, instead of

o create table as rows to keep
o drop old table
o rename new table to old

couldn't I just:

o delete table rows to be purged
o alter table move (+ eventual index rebuild)

Wouldn't the latter be better since I may have other "attached" objects and
such, like indexes, grants, etc ?

          Followup:

a) correct -- and these indexes are *rare*.  Tell me -- other then the example I
gave, how often does something like that really happen?

A purge of "old data" would get rid of all of them (leave no left behinds)...

If you don't delete (very common) -- it is not a problem...

b) 3 isn't an issue anymore and was only an issue in the past if you let it be
(eg: you should encounter that issue once and then you learn and you don't let
it happen anymore)

#4 is valid.

c) tell me -- which has the opportunity to be

o easily parallelized
o done without undo generation
o done without redo generation
o the fastest.....

Yes, you could delete + move + IMMEDIATE index rebuild as moving invalidates the
indexes.

Indexes would have to be rebuilt (created really -- the rebuild would be in
effect a create since the existing indexes are useless -- have bogus rowids in
them) in both cases.

Grants run really fast....

So, if this is something you do once or twice a year -- create as keep, drop,
rename is perhaps the most efficient method.

      Rating: 5
more and more
      questions  February 15, 2003
Reviewer:
      David
Tom, it sounds strange to me... despite the performance and space (no user undo)
advantages, delete purge/alter table move seems to be much simpler.

All table characteristics are kept (pctfree, pctused, storage and other clauses,
dependent and depending objects like indexes, grants, etc etc etc. Also I can do
it online (ok, index would be useless before rebuilding them) -- less to worry
about -- fewer points of failure -- only rebuilding the indexes.

In case of a table with specific properties, ctas/drop/rename would entail quite
a lot of research to reconstruct its orginal format -- not to mention downtime.

a) In my opinion, choosing one of them is a matter of tradeoffs --
ctas=performance and move=ease of use. Don't you think ? Aren't they just
alternatives -- both with advantages and disadvantages ?

b) Do you have any scripts/methodology for reconstructing the original table
properties in the ctas scenario ?

c) In the case of the the sliding index (your coalesce example), how do I
monitor so I know what indexes need coalescing ? How often should I do that ?
Maybe automate that ?

          Followup:

Simple is one thing.  Simple is easy.  Simple is simple.  Simple is something
that could take 5 days.

create as rows to keep
index
grant

might take a couple of minutes in comparision.

If you have a large table (hundreds of megs in size -- gigabytes in size -- or
more)

And you are going to purge much of the data (say 40% or more)

And you are going to rebuild anyway afterwards (alter move)

THEN

CTAS + index + grant will be many many times faster then

delete + move + index

a) yes, but if you have to do this in a brief period of time, your choices will
be constrained.

b) look right above - CTAS is not significantly different from generating alter
table moves ;)

c) you tell me -- you maintain the system.  if you insert 100 rows a day and
delete 99 of them a day, it could be months between coalescing.  If you insert
100,000 rows a day and delete 999,990 of them a day during processing, it might
be a more regular thing.  You can automate it if you like but the period will be
based on how you use it.

one more thing on the coalescing - in this case, it'll do two things

a) reclaim space (thats OK, thats good, do that when you want to)

b) if your queries are in the form of

"select * from ( select * from rows_to_process where processed = 'N' order by id
) where rownum = 1"

that is -- you are using the index in question to find the minimum non-processed
row, you will want to use coalesce more often as you are reading perhaps lots of
virtually empty index blocks in order to find the row you want.

      Rating: 4
Question on
      moving clustered tables...  February 16, 2003

Reviewer:  Matt  from Australia
I want to set up a procedure to move 30 or so clustered tables into one date
range partitioned table. From this thread I understand that the 'alter table
move...' will not work. So what would be the best high level approach to do
this?

I am thinking a CTAS to convert each clustered table to an ordinary heap
organised table (maybe splitting each table so that one clustered table goes
into two or more partitions). Each table and then be swapped into a pre-defined
partition (in the target table).

Is there anything else that I might need to consider?
Is there a better (and more efficient) means to do this?

Thanks and Regards,

          Followup:

CTAS will work nicely -- just make sure to test that the swap afterwards is
being done without validation (or you can end up reading and reading and reading
the data over and over -- you just want a dictionary update at the end).

      Rating: 5
last row
      factor  February 17, 2003
Reviewer:  David

Tom, you are saying index leaf rows are usually reused after deletes, so I don't
have to worry about rebuilding -- okay.

If I'm not mistaken, leaf blocks only get reused after the last row is deleted.
That's one of the main reasons some people say these indexes should be rebuilt.

a) Doesn't that make those blocks prone to be underutilized ? Hence, wouldn't it
be proper to perform rebuilds after major deletes ?

b) I had asked you above how I can monitor index holes. From some papers I've
read, they say the ratio of deleted rows from index_stats being around 40% is a
good hint for me to rebuild the index. Is that metric ok ?

c) Because there can be so many indexes in a database, I should keep procedures
to know what indexes should be coalesced (rebuild or what). To me, only "having
the feeling" that some table is purged in bulk may be a bit slack. What
methodology would you use to spot such indexes ? Runinng the query from b) from
time to time maybe ? What else ?

Thanks

          Followup:

"If I'm not mistaken, leaf blocks only get reused after the last row is deleted.

That's one of the main reasons some people say these indexes should be rebuilt."

well, consider

create index t_idx on t(last_name);

Ok, say you fire Bill Smith.  Delete from t where last_name = 'Smith', there is
now a deleted entry.

Is that block "underutilized"???  Not if you hire someone named S<omething> --
any name that "fits" on that block.

Is that block "underutilized"??? Not if you have someone get married and change
their last name....

You see, with the exception of indexes are "things that only increase" - data is
moving about in an index structure all over the place (and these are the vast
majority of indexes)

Also -- as I said over and over (and over, and over) -- a "mass delete" would be
 performed as a CTAS anyway!  It is called a "purge", it is an infrequent
operation.  It is something that can be (nay, I would say should be) optimized
to be very efficient.

a) no, I do not believe in "major deletes".  And even so --  if it is deleting
historical data then the index blocks for sequence/date columns would go 100%
free (you are wiping out OLD DATA -- all the sequences less then 54231 will
"disappear", all of the data before "01-jan-2001" will go away.

Indexes on lastnames, firstnames -- etc (random strings) -- we'll just reuse
that space over time naturally....

b) NO (i don't really know how to say this better other then to shout NO).
Reread this:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6601312252730
and read it again and again.

How did you get that 40% in there in the first place -- that will be key to
understanding whether that ROT (rule of thumb -- i despise ROT) is valid in your
case or not.

Say you have an index with 40% deleted lf rows.  Ok, now say that index is on a
character string with lots of different values ALL over the place.  What'll
happen if you rebuild?  Well, MOST LIKELY you'll end up over the course of the
next couple of days resplitting every block in that index -- getting the index
"fat" again -- back to 40% empty, using great resources to do so in the process,
to have gained nothing.

I have no metrics by which I say "hey, rebuild that thing".  Look at the usage
(i explained the case where it makes sense -- the sweeping index where you leave
behind like 1 row on each leaf block).  Rebuild based on HOW the index is used,
not by some formula.

c) sigh.  I give.  you don't know what tables are purged from? (you do, you
better)....  they shouldn't be done with a delete in all probability -- unless
they are tiny (like 1 gig or less) tables.

You cannot identify the tables that cause the very uncommon situation of the
sweeping index?  where you leave 1 row/leaf block?

      Rating: 3
But how about
      last row factor ?  February 17, 2003
Reviewer:
      David
Okay, Tom... rewind please.

I have some very basic questions, and you just mixed me up:

a) Once I upon a time I learned in an Oracle class that an index leaf block only
gets reused when the last row from that block is deleted, as if pctused were
zero. Correct ?

b) In this case, if you delete just one row from the block, you'll render it
completely useless for new inserts until the block is empty. Correct ? (Above
you mentioned you deleted Smith and then ineserted another row)

c) So, these blocks will have holes until the last row is deleted. Correct ?

d) In time, these holes will get larger and larger because of eventual deletes.
Correct ?

e) So, when these deleted rows get to a threshold (40% used, say) it's good to
rebuild, because there will be more holes than used space. Correct ?

The dynamics above makes a lot of sense to me AS LONG AS the premisses are true
(leaf block being reused only after the last row is deleted). Please be
*straightforward*, so I can understand it better.

          Followup:

1) define reused.  I define it as "being used day to day".

If I create an index on emp(ename) and then I delete BLAKE and then later I
insert BAILEY -- will that index leaf block be "reused"??  Yes, sure.

Now, if I put 100,000 rows in emp (lots of enames, assume many leaf blocks) and
I delete BLAKE and later I insert ZALUBA -- will I reuse that index leaf block
BLAKE was on? No, I cannot -- ZALUBA belongs wwwwaaaay at the other end of the
index.  But later I update some other row to BANKS -- now I will.  The data in
the index moves about from place to place.  I frequently use and reuse blocks
ALL OVER the place.

Now, imagine you have an index on a column populated by a sequence.  further,
assume all by 1% of the data gets deleted over time (we process rows and get rid
of them but 1 out of 100 "hangs around forever" for some reason.

Now, over time, the index will accumulate leaf blocks with one or two entries on
them since the OTHER 99 rows were deleted AND we cannot reuse the slots on these
blocks with new inserts since the sequence numbers are always too big and
getting bigger.  This is that rare case that needs a coalesce from time to time
in order to take all of those 1% rows and clump them together.

Most indexes are more like the first case -- you use and reuse the space
CONSTANTLY as you insert randomly into the structure, delete randomly from the
structure (and since an update is logically a DELETE+INSERT in an index....)

b) false, wrong, totally erroneous.  The blocks ARE ALWAYS AVAILABLE for new
inserts, updates -- always.  The index is a data structure, data goes where it
must go.  You goto insert the row "Smythe" -- Oracle will search throught the
index looking for where "Smythe" should go and

   -- if there is space on the block -- it'll put it there, it just needs
      space

   -- if there is NO MORE space on the block -- it'll split the block into
      two and put Smythe on one of them -- where it belongs -- in sorted order.

c) FALSE.  Easy test here:

ops$tkyte@ORA920> create table t ( x int, pad char(255) );

Table created.

ops$tkyte@ORA920> create index t_idx on t(x,pad);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( 1, 'x' );

1 row created.

ops$tkyte@ORA920> insert into t values ( 2, 'x' );

1 row created.

ops$tkyte@ORA920> insert into t values ( 999999999 , 'x');

1 row created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2          for i in 3 .. 100000
  3          loop
  4                  delete from t where x = i-1;
  5                  commit;
  6                  insert into t values ( i, i );
  7                  commit;
  8          end loop;
  9  end;
 10  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> analyze index t_idx validate structure;
Index analyzed.

ops$tkyte@ORA920>  select height, lf_rows, lf_blks, br_blks from index_stats;

    HEIGHT    LF_ROWS    LF_BLKS    BR_BLKS
---------- ---------- ---------- ----------
         1          3          1          0

See -- now, I put in 1, 2, 999999999 -- 2 is between them.  I deleted 2, put in
3.  delete 3, put in 4 and so on.  is that space between 1 and 99999999 reused?
sure -- the index never grew at all.  Perhaps more compelling:

ops$tkyte@ORA920> create table t ( x char(7), pad char(255) );

Table created.

ops$tkyte@ORA920> create index t_idx on t(x,pad);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t
  2  select to_char(rownum+0.1,'fm0000.00'), 'x'
  3    from all_objects
  4   where rownum < 5000;

4999 rows created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze index t_idx validate structure;

Index analyzed.

ops$tkyte@ORA920> select height, lf_rows, lf_blks, br_blks, DEL_LF_ROWS from
index_stats;

    HEIGHT    LF_ROWS    LF_BLKS    BR_BLKS DEL_LF_ROWS
---------- ---------- ---------- ---------- -----------
         2       4999        179          1           0

ops$tkyte@ORA920>
ops$tkyte@ORA920> delete from t where mod(x,28) <> 0;

4999 rows deleted.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t
  2  select to_char(rownum+0.2,'fm0000.00'), 'x'
  3    from all_objects
  4   where rownum < 5000;

4999 rows created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze index t_idx validate structure;

Index analyzed.

ops$tkyte@ORA920> select height, lf_rows, lf_blks, br_blks, DEL_LF_ROWS from
index_stats;

    HEIGHT    LF_ROWS    LF_BLKS    BR_BLKS DEL_LF_ROWS
---------- ---------- ---------- ---------- -----------
         2       4999        179          1           0

d) Nope, you bought into the MYTHOLOGY that index space is not reused, it is
false, index space is very much reused, index structures are much more robust
and resiliant then people give them credit for.

e) nope.

All of the premises you have are incorrect.

o index space is available for reuse
o the deleted row entries are very reusable by any row that fits there (doesn't
  have to be the same value as that which was deleted as demonstrated)
o indexes behave AS IF pctfree = 0, they fill them up -- all of the way up

o pctused has no meaning when applied to NON-HEAP tables.  You only need to
  manage freelists like that when data goes into the object "anywhere".  in an
  index - -data must go where data must go -- no if and or buts.  If you insert
  the number 55 into an index -- it MUST go between 54 and 56 -- it cannot go
  after 1000, or before 32.  So oracle will find the place it MUST go and put
  it right there.  If there is free space -- great, if not, we split the block
  and MAKE space

It is that last point -- that we split the block and make space -- that should
make you hesitate to rebuild nilly willy -- block splitting takes a bit of time,
energy, effort, resources.  If you rebuild an index and squash out our freespace
-- well, we'll just split over and over again (riddle me this batman comes to
mind!)

      Rating: 5
Some
      nuggets...  February 17, 2003
Reviewer:
      A reader
I just happened to open Don Burleson's "High Performance Oracle8 Tuning" that
was on my desk. Here's when he claims it's necessary to rebuild indexes (page
335):

From index_stats:

- height -> ...whenever the value of height is more than 3, you might benefit
from dropping and re-creating the index. Oracle will not spawn a 4th level on a
clean rebuild until more than about 10 million nodes have been added to the
index.

- distinct keys -> ... values less than 20 are candidates for being recreated as
bitmapped indexes.

- del_lf_rows -> (page 339 -- he has a script that creates dynamic sql to
rebuild the index whenever: height > 3 OR del_lf_rows >10)

So ?

          Followup:

read

http://asktom.oracle.com/~tkyte/index_rebuild.html
(redirects to google, Don was there himself)

the distinct keys thing is *just plain wrong advice*.  20 distinct values --
hmm, i have a 2,000,000 table.  20 distinct values in column X.  It is an OLTP
system (transactional, not DW).  Of the 20 distinct value, 19 of them only point
to 200,000 rows (19 of them point to 10% of the data).  The remaining 1,800,000
all have a value "X".  The 19 point to more or less uniform numbers of rows, or
about 10,526 each.  Ok -- so each value there points to 0.5263% of the data .
We frequently query "where x = 'A'", "where x = 'B'", and so on.  We do not
query for "where x = 'X'" (they are processed rows or whatever).

Still thinking "bitmap"?  Bitmaps are

o not based on raw numbers of distinct values (like "20" or "15" or "100")
o not for anything other then read mostly systems
o best explained by Jonathan Lewis ;)

http://www.dbazine.com/jlewis3.html
http://www.dbazine.com/jlewis6.html
http://www.dbazine.com/jlewis7.html

and if he really rebuilds when del_lf_rows exceed 10, well, I would be able to
debate that and win (that it is a bad idea)

      Rating: 5
Last DDL
      time  February 19, 2003
Reviewer:
      A reader
Tom, I was trying to check the last ddl time from user_objects during a
long-running index rebuild.

The last DDL time is the time it started or ended (or what), since the rebuild
can take some hours ?

Thanks

          Followup:

appears to be the time the ddl started:

big_table@ORA920> set timing on
big_table@ORA920> select sysdate, last_ddl_time from user_objects where
object_name = 'BIG_TABLE_PK'
  2  /

SYSDATE              LAST_DDL_TIME
-------------------- --------------------
19-feb-2003 08:55:44 12-sep-2002 08:50:01

1 row selected.

Elapsed: 00:00:00.01
big_table@ORA920> alter index big_table_pk rebuild;

Index altered.

Elapsed: 00:00:08.54
big_table@ORA920> select sysdate, last_ddl_time from user_objects where
object_name = 'BIG_TABLE_PK'
  2  /

SYSDATE              LAST_DDL_TIME
-------------------- --------------------
19-feb-2003 08:55:52 19-feb-2003 08:55:44

1 row selected.

Elapsed: 00:00:00.01
big_table@ORA920>

      Rating: 5
Date printing
      on new site  February 19, 2003
Reviewer:
      Paul  from ottawa, ON Canada
Don't know if it is my IE browser or not (my linux box is rebuilding a kernel)
but I see this for dates on Answers
"originally submitted on  16-¿¿¿-2003 18:34 Eastern US time, last updated
19-¿¿¿-2003 8:52"

Like the new site features, thanks very much for this wonderful resource.
Specifically this question as I am building routines to move tables to LMTs now.

          Followup:

sorry about that -- we are fixing that now ;)  slight NLS_LANG issue... should
be corrected actually

      Rating: 5
moving tables
      across tablespaces  April 21, 2003
Reviewer:
      Alvin Panugayan  from Philippines
Greetings,

What happens if most of the production tables resides in the system tablespace
??

i plan to use your script to migrate the tables which resides on the system tbs
onto another tbs.

          Followup:

system is just a tablespace -- same procedures work.

      Rating: 5
default
      tablespace as SYSTEM  May 16, 2003
Reviewer:
      Mike  from TX
Tom,
I have installed 9i R2 enterprise edition.

SQL> select username, default_tablespace from dba_users where
default_tablespace='SYSTEM'
  2  and username not in ('SYS','SYSTEM', 'SCOTT');

USERNAME                       DEFAULT_TABLESPACE
------------------------------ ------------------------------
DBSNMP                         SYSTEM
MBURK                          SYSTEM
OUTLN                          SYSTEM
WMSYS                          SYSTEM
ORDSYS                         SYSTEM
ORDPLUGINS                     SYSTEM
MDSYS                          SYSTEM

Why do these product owner use SYSTEM as default?

          Followup:

they are our accounts, we can put them were we like.  there is nothing inheritly
"special" about system as far as object allocation goes.

      Rating:
      3
  May 17, 2003
Reviewer:  Mike
       from TX
"
they are our accounts, we can put them were we like.  there is nothing inheritly

"special" about system as far as object allocation goes.
"

I raised the question is because of Oracle Expert-- the tuning tool gives the
following recommendation:

       Oracle Expert has discovered tablespace SYSTEM is dedicated to data
       dictionary segments.

       Oracle Expert recommends relocating:
$$$$$
           index WMSYS.WM$LOCKROWS_INFO_IDX to a tablespace dedicated to
           application index segments (INDX,TCO_INDEX)

           index WMSYS.WM$VT_ERRORS_PK to a tablespace dedicated to application
           index segments (INDX,TCO_INDEX)

           index WMSYS.WM$TMP_DBA_CONS_IND to a tablespace dedicated to
           application index segments (INDX,TCO_INDEX)

           index WMSYS.SYS_C001463 to a tablespace dedicated to application
           index segments (INDX,TCO_INDEX)

           index WMSYS.WM$NESTED_COLUMNS_PK to a tablespace dedicated to
           application index segments (INDX,TCO_INDEX)
....
$$$$$
The TCO is my cutomer application owner.

Why Oracle Expert gives such recomm?

Thanks

Mike

          Followup:

I guess they were the default tablespace of your account that was running this.

Ignore it.

      Rating:
      3
  May 17, 2003
Reviewer:  Mike
       from TX
By the way, the Oracle Expert was installed with 9i R2. I beleive it is 9.2.0.1

      Rating:
      5
  June 25, 2003
Reviewer:
      A reader

      Rating: 4
initial and lmt
      - 8i  July 09, 2003
Reviewer:  M. Bento
       from Lisbon, Portugal
When moving a table into a uniform LMT using alter table without the storage
clause the table's initial is not set to the LMT default initial parameter but
keeps its previous value ending up with tables with different initial extents
inside the same LM tablespace.
Should one set the initial along with the alter table in order to prevent
fragmentation?
Thanks.

          Followup:

you are confusing the initial extent sizes with actual extent sizes.

Rest assured, the EXTENTS actually allocated are all the same size in that
UNIFORM LMT -- use user_extents to verify that.

The INITIAL EXTENT (and next extent and pctincrease and minextents) are used
during the initial creation to figure out HOW MANY extents to create.  Consider:

ops$tkyte@ORA920> create tablespace testing
  2  extent management local
  3  uniform size 512k
  4  /

Tablespace created.

ops$tkyte@ORA920> compute sum of mbytes on report
ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t ( x int )
  2  tablespace testing
  3  storage ( initial 1m );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select extent_id, bytes/1024/1024 mbytes
  2    from user_extents
  3   where segment_name = 'T'
  4   order by extent_id;

 EXTENT_ID     MBYTES
---------- ----------
         0         .5
         1         .5
           ----------
sum                 1

see, 2 extents of 512k -- for a total of 1m, because the 1m was requested..

ops$tkyte@ORA920>
ops$tkyte@ORA920> drop table t;

Table dropped.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t ( x int )
  2  tablespace testing
  3  storage ( initial 1m next 1m minextents 4 pctincrease 100 );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select extent_id, bytes/1024/1024 mbytes
  2    from user_extents
  3   where segment_name = 'T'
  4   order by extent_id;

 EXTENT_ID     MBYTES
---------- ----------
         0         .5
         1         .5
         2         .5
         3         .5
         4         .5
         5         .5
         6         .5
         7         .5
         8         .5
         9         .5
        10         .5
        11         .5
        12         .5
        13         .5
        14         .5
        15         .5
           ----------
sum                 8

16 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select 1 + 1 + 2 + 4 from dual;

   1+1+2+4
----------
         8

there, Oracle took the initial, next, minextents and pctincrease and computed
"hey, it would have taken 8m in a DMT, please grab 8m of space" and did so, 512k
at a time...

ops$tkyte@ORA920>
ops$tkyte@ORA920> drop table t;

Table dropped.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table t ( x int )
  2  tablespace testing
  3  storage ( initial 64k minextents 10 );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select extent_id, bytes/1024/1024 mbytes
  2    from user_extents
  3   where segment_name = 'T'
  4   order by extent_id;

 EXTENT_ID     MBYTES
---------- ----------
         0         .5
         1         .5
         2         .5
         3         .5
         4         .5
         5         .5
         6         .5
         7         .5
         8         .5
         9         .5
           ----------
sum                 5

10 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920>

Here Oracle did 64k * 10 = 640k, but also saw the minextents 10 -- and said,
allocate at least 10 extents, as long as 10 extents of space is larger then
640k

But there is NO fragmentation here, they are all 512k extents.

      Rating: 5
CTAS and alter
      table move  August 21, 2003
Reviewer:
      reader
Is the concept behind moving a table using alter table move command the same as
CTAS in the sense that oracle will directly copy the rows to the blocks in the
datafiles directly? Is it correct? if not how does oracle do it when we use CTAS
and alter table move? Thanks.

          Followup:

same concept -- but you don't lose you grants, index definitions, table name,
etc.

use ctas to copy the data.
use alter table move to MOVE the data.

      Rating: 5
redo and undo
      generation  August 22, 2003
Reviewer:  Raman
       from Salt Lake City
Tom, do both CTAS and alter table move commend generate redo and undo if I don't
put the table in nologging mode before the operation? Thanks.

          Followup:

no undo.

redo in archivelog mode unless table is NOLOGGING.
no redo in noarchivelog mode.

      Rating: 4
What are the
      uniform size values you use for LMT?  August 22, 2003

Reviewer:  Laurent  from FRANCE
Tom,

Great examples, I'm also planning to move data from dictionnary managed tbs to
LMT with uniform size extents.

If I'm calculating the size of each table with your method found at:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:266215435203

what are the uniform size I should use?

can you tell me what ranges do you usually use? say for example :

tables < 10Mb goes in a tablespace with 128k extent size
10 Mb < tables < 100 Mb goes in tbs w/ 1M  extent size
--- these are exemples ---

Thanks

          Followup:

128k would be good for 10m -- that would be about 80 extents, managable -- great
(higher is fine as well)

10 .. 100mb with 1m, fine...

your goal, come up with an extent size that

a) gives you the max extents you are personally comfortable with
b) doesn't "waste" too much space.  using 5meg extents for an object that will
be about 6meg for example would "waste" 4meg at the end.

      Rating: 5
about indexes
      size this time  August 22, 2003
Reviewer:
      Laurent  from FRANCE
Hi again,

For the tables considering the 2 methods below:

the first method fond at:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:266215435203
 EXTENT_ID      BYTES     BLOCKS
---------- ---------- ----------
         0     131072         16
         1     131072         16
         2     131072         16
         3     131072         16
         4     131072         16
         5     131072         16
         6     131072         16
                      ----------
sum                          112

    BLOCKS EMPTY_BLOCKS  AVG_SPACE NUM_FREELIST_BLOCKS
---------- ------------ ---------- -------------------
       100           11        844                   1

Ok, the above shows us :

o we have 112 blocks allocated to the table
o 11 blocks are totally empty (above the HWM)
o 100 blocks contains data (the other block is used by the system)
o we have an average of .8k free on each block used.

Therefore, our table

o consumes 100 blocks of storage in total
o of which 717.6k is used for our data.

-------------------------------------------------------------------------
the second method using your show_space procedure found at :

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:231414051079
select SEGMENT_NAME, PARTITION_NAME, SEGMENT_TYPE, FREE_BLOCKS,
TOTAL_BLOCKS,UNUSED_BLOCKS from table (show_space_for('X12_NPLAN','X84','%'));

SEGMENT_NAME                   PARTITION_NAME
------------------------------ ------------------------------
SEGMENT_TYPE                   FREE_BLOCKS TOTAL_BLOCKS UNUSED_BLOCKS
------------------------------ ----------- ------------ -------------
X12_NPLAN
TABLE                                    1          112            11

Total Blocks  : Total blocks allocated to the table
Unused Blocks : Blocks that have never contained data (above the HWM)

we can only found that our table consumes 100 blocks of storage in total
(112 TOTAL_BLOCKS - 11 UNUSED_BLOCKS - 1 BLOCK USED BY THE SYSTEM)

my first question is :
Is there always 1 block used by the system for each table,index...

so I guess this method is less accurate than the first one.
-----------------------------------------------------------------------

My 2nd question is about indexes:

select SEGMENT_NAME, PARTITION_NAME, SEGMENT_TYPE, FREE_BLOCKS,
TOTAL_BLOCKS,UNUSED_BLOCKS from table (show_space_for('X18_IND1','X84','%'));

SEGMENT_NAME                   PARTITION_NAME
------------------------------ ------------------------------
SEGMENT_TYPE                   FREE_BLOCKS TOTAL_BLOCKS UNUSED_BLOCKS
------------------------------ ----------- ------------ -------------
X18_IND1
INDEX                                    0         6608             5

here we find out that:
index X18_IND1 uses 6608-5(-1??) blocks
                           ^^^^
                          depending from question 1
Is that right?
Is there a more accurate way to get an index size (aka knowing the average space
used in an index blocks)?

Thanks again,

          Followup:

there is at least one block, yes.

is it less accurate?  no.
does it give you less information?  yes.

a single block is NOT going to make or break you in the end.

      Rating: 5
it does not
      bypass buffer cache  August 24, 2003
Reviewer:
      Reader
"redo in archivelog mode unless table is NOLOGGING."

So, alter table move regardless of whether the table is in nologging or logging
mode, oracle does not bypass buffer cache when it moves (rebuilds) the table
using alter table ..... command. Tom, is it like it is doing conventional
inserts using the blocks in the free list all over again for all of the rows?
That means if the table is huge, it takes a lot of time. Thanks.

          Followup:

huh?

it generates redo

it bypasses undo.

it does direct writes to datafiles.

redo generation is not at all linked to "buffer cache".   It does not do
convention path inserts.  it is like a direct path load -- read data, format
blocks, write blocks.  the only difference is the "read data" isn't reading a
file, it is reading database data.

      Rating: 5
how it corrects
      chain count  August 24, 2003
Reviewer:
      Reader
Thanks for the clarification. But I am not clear about how alter table move
takes care of row migration when it does a direct path load. While moving, does
it measure the row size and puts only a fewer rows in the blocks depending on
the size of the blocks and current pctfree values to eliminate row migration
issue? Thanks.

          Followup:

with migrated rows -- all we do is IGNORE the head rowid piece and pick up the
migrated row piece.

Just like in a full scan, when we read a block with a head row piece that points
to a migrated row, we ignore it, knowing that later when we hit that row for
real, we'll get it then.

      Rating: 5
Good
      Work  September 24, 2003
Reviewer:  Rajat
      Garg  from India
Very usefull script to move tables across tablespaces.

      Rating: 4
Great! And, how
      do we MOVE Nested Tables ?  December 10, 2003

Reviewer:  Arul kumar  from Milton Keynes, United
      Kingdom
Tom,

We have lots of nested tables as well.
Can you suggest us on how to move them to a different tablespace?

Thanks,
Arul.

          Followup:

ops$tkyte@ORA9IR2> !oerr ora 22931
22931, 00000,"MOVE of nested table to a different tablespace not supported"
//  *Cause:  Attempt to move a nested table to a different tablespace.
//  *Action: Nested tables always colocate in the same tablespace as the parent.
//           A nested table can be moved to a different tablespace only by
moving
//           its containing table to the target tablespace.

move parent and nested table goes with.

December 10, 2003 Jonathan -- Thanks for the question regarding
      "Encrytion and Decrytion", version 6.0.8

      You Asked
Hi, Tom.

I made a form to encrypt and decrypt text string (more than 4000 Chr long).

Basically, I have to read a encrypted text file and decrypt from that file, and
also have read simple text file and encrypt the file before sending it to
client.

I can encrypt the simple text and save it fine, but If I read the saved encrypt
file to decrypt, it contained some broken decrypt string.

I suspect when my from write to emcrypted ascii text file, it write ASC(15) to
ASC(29) to same ascii text(black square box).

I don't know if it is true or not.

Please help me.

Jonathan

Here is my encrypt and decrypt procedure.

PROCEDURE DO_DECRYPT IS
     V_ENCRYPT                LONG default NULL ;
     V_DECRYPT               LONG default NULL ;
BEGIN
       V_ENCRYPT := :DECRYPT_FILE.TXT_ENC;

    dbms_obfuscation_toolkit.DESDecrypt
        ( input_string => V_ENCRYPT,
              key_string   => :DECRYPT_FILE.TXT_KEY,
              decrypted_string=> V_DECRYPT );

         :DECRYPT_FILE.TXT_DEC         := V_DECRYPT;
END;

PROCEDURE DO_ENCRYPT IS
     V_ENCRYPT                LONG default NULL ;
     V_DECRYPT               LONG default NULL ;
     V_INPUT                   LONG default NULL ;
BEGIN
    V_INPUT := :DECRYPT_FILE.TXT_DEC;
  V_INPUT := rpad( V_INPUT, (trunc(length(V_INPUT)/8)+1)*8, chr(0));

  dbms_obfuscation_toolkit.DESEncrypt
      ( input_string => V_INPUT,
        key_string   => :DECRYPT_FILE.TXT_KEY,
        encrypted_string=> V_ENCRYPT );

    :DECRYPT_FILE.TXT_ENC := V_ENCRYPT;
END;

And, procedure to save encrypt ascii file

Procedure Save_enc_file

    V_Filename                VARCHAR2(255);
    V_ONE_CHR                    VARCHAR2(1);
    V_FILETYPE               Text_io.file_type;
    i                                    NUMBER;
BEGIN
    V_Filename := :DECRYPT_FILE.TXT_DIRECTORY || :DECRYPT_FILE.TXT_FILENAME;
  V_FILETYPE := TEXT_IO.FOPEN(V_Filename, 'w' );

  FOR i IN 1..LENGTH(:DECRYPT_FILE.TXT_ENC)
    LOOP
        V_ONE_CHR := SUBSTR(:DECRYPT_FILE.TXT_ENC, i, 1);
        TEXT_IO.PUT (V_FILETYPE, V_ONE_CHR);
    END LOOP;

  TEXT_IO.FCLOSE(V_FILETYPE);
END;

      and we said...
encryption will create binary data.

Text_IO will write ascii data only.

You'll want to write only ascii data.  I suggest your do_encrypt is coded like
this:

PROCEDURE DO_ENCRYPT IS
     V_ENCRYPT                LONG default NULL ;
     V_DECRYPT               LONG default NULL ;
     V_INPUT                   LONG default NULL ;
BEGIN
    V_INPUT := :DECRYPT_FILE.TXT_DEC;
  V_INPUT := rpad( V_INPUT, (trunc(length(V_INPUT)/8)+1)*8, chr(0));

  dbms_obfuscation_toolkit.DESEncrypt
      ( input_string => V_INPUT,
        key_string   => :DECRYPT_FILE.TXT_KEY,
        encrypted_string=> V_ENCRYPT );

    :DECRYPT_FILE.TXT_ENC := utl_raw.cast_to_raw(V_ENCRYPT);
END;

That utl raw call would turn the varchar2 into a RAW. when you assign the RAW to
the varchar2 -- it would do it in HEX.  (your VARCHAR2 out parameter would need
to be 2 times as long as the input string was!!!)

Then, you can save the hex string safely.  Later, to decrypt, you would:

PROCEDURE DO_DECRYPT IS
     V_ENCRYPT                LONG default NULL ;
     V_DECRYPT               LONG default NULL ;
BEGIN
       V_ENCRYPT := utl_raw.cast_to_varchar2(
hextoraw(:DECRYPT_FILE.TXT_ENC) );

    dbms_obfuscation_toolkit.DESDecrypt
        ( input_string => V_ENCRYPT,
              key_string   => :DECRYPT_FILE.TXT_KEY,
              decrypted_string=> V_DECRYPT );

         :DECRYPT_FILE.TXT_DEC         := V_DECRYPT;
END;

Just beware of the doubling in size -- it is unavoidable if you want to use
TEXT_IO however.
Review & Followup

      Rating: 5
Most
      interesting and useful, as usual !  February 26, 2002

Reviewer:  Andre Whittick Nasser  from Rio de
      Janeiro, Brazil

      Rating: 5
It
      works!!!!!!!!!!!!!!  February 27, 2002
Reviewer:
      Jonathan  from Rockville, MD

      Rating:
      4
dbms_obfuscation_toolkit.DESDecrypt  April 01, 2002

Reviewer:  mins  from NJ USA
Is there a way to avoid the doubling in size.

I am encrypting the data and the encrypted data can't fit in the table because
of the doubling of size.

(what I do is convert VARCHAR2(30) and the encrypted output
is more than 50 characters)
Is there any other encryption other than dbms_obfuscation_toolkit provided by
Oracle.

Thanks

          Followup:

YOU don't need to double it.

THEY did.  They wanted to write it to an ascii file using TEXT_IO, they were
limited therefore by the API (text_io) they wanted to use.  They needed ascii
strings, hex code, not binary bytes.

You can deal with binary bytes.  The data is not in general "doubled".  Only
when you treat a binary as a hex string would it be doubled.

My advice:

o always always always stored encrypted data in a raw
o plan on multiples of 8 bytes.

So, for your varchar2(30), I would suggest you set up a field that is raw(32).
Use UTL_RAW.CAST_TO_RAW to turn the varchar2(30) into a RAW -- encrypt the raw
(32 bytes long) and put that in the database table.  When you fetch it out --
decrypt it and use UTL_RAW.CAST_TO_VARCHAR2 to put it back "right" again.

      Rating: 4
Encrypting
      varchar2 and storing..  April 02, 2002
Reviewer:
      Mins  from NJ USA
Tom,
Thanks for the reply , but I still have a problem.
I am encrypting VARCHAR2(30) data from the tables and storing the encrypted
values in the same table.

The output raw and the size is the double or more of the orignal input.

How could I encrypt the varchar2 and store it in the same field  of the same
size.

Thanks
the e

          Followup:

Well the problem is two fold:

o encryption works 8 bytes at a time, that 30 character field will be 32 bytes
after you are done.  No avoiding that.  That field will grow.

o the output size is NOT double, it is 2 bytes longer at most (the contents of
that field will be 0,8,16, 24 or 32 bytes)

o there is a chance of screwing the data up if you store binary data in the
varchar2.  If you ALWAYS insert clear text and have a trigger encrypt the data
(binary data stays in the database) and you never fetch the RAW data out --
rather you apply a decrypt function in the database and retrieve clear text --
you'll be OK.

This is how I view this whole discussion....

You are adding new functionality to an existing application.

Most times, in any case, this cannot be done 100% transparently.

Adding encryption -- ANYONES encryption -- is one of those times.

You will have to change.

      Rating: 4
Am I doing
      something wrong  May 08, 2002
Reviewer:  Srimal
       from Trenton, USA
clts@test.WORLD> drop table t
  2  /

Table dropped.

clts@test.WORLD> create table t (data varchar2(9), data_enc raw(16));

Table created.

I use the crypt function that you published here

function crypt( p_str in varchar2 ) return varchar2
    as
        l_data  varchar2(255);
    begin
        l_data := rpad( p_str, (trunc(length(p_str)/8)+1)*8, chr(0) );
        dbms_obfuscation_toolkit.DESEncrypt
            ( input_string => l_data,
             key_string   => 'MagicKey',
             encrypted_string=> l_data );
       return l_data;
   end;

clts@test.WORLD> insert into t (data, data_enc) values
('012345678',crypt('012345678'));
insert into t (data, data_enc) values ('012345678',crypt('012345678'))
                                                   *
ERROR at line 1:
ORA-28234: key length too short
ORA-06512: at "SYS.DBMS_OBFUSCATION_TOOLKIT_FFI", line 0
ORA-06512: at "SYS.DBMS_OBFUSCATION_TOOLKIT", line 33
ORA-06512: at "CLTS.CRYPT", line 6
ORA-06512: at line 1

What am I doing wrong?

          Followup:

I don't think the code you show is exactly the code you are using?

select text from user_source where name = 'CRYPT' order by line;

see that it is the right code.  You will have to modify your routine slightly:

ops$tkyte@ORA8I.WORLD> create or replace function crypt( p_str in varchar2 )
return varchar2
  2  as
  3      l_data  varchar2(255);
  4  begin
  5      l_data := rpad( p_str, (trunc(length(p_str)/8)+1)*8, chr(0) );
  6      dbms_obfuscation_toolkit.DESEncrypt
  7          ( input_string => l_data,
  8           key_string   => 'MagicKey',
  9           encrypted_string=> l_data );
 10      return UTL_RAW.CAST_TO_RAW( l_data );
 11  end;
 12  /

Function created.

so that it returns in fact a RAW type..

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select crypt( '012345678' ) from dual;

CRYPT('012345678')
---------------------------------------------------------------------------------
--------------------------------------------------
07CF8213EDC46C277C720164D915B70C

that is the raw in "hex"

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> drop table t;

Table dropped.

ops$tkyte@ORA8I.WORLD> create table t ( data varchar2(9), data_enc raw(16) );

Table created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> insert into t values ( '012345678',crypt('012345678'));

1 row created.

this just shows that it works...

      Rating: 5
Excellent! You
      are too good  May 08, 2002
Reviewer:  Srimal
       from Trenton, USA
Hello Mr Tom,

I need to get a smack on my head!!!

You are right.... I was trying some options for the keystring and when changing
back left the "c" in "MagicKey"..

I learnt a new lessan today, to see the user source before crying fowl.

Thanks a lot and sorry for wasting your time!

      Rating: 3
Is there any
      equivalent package exists before 8i?  July 10, 2002

Reviewer:  Nirmal kumar  from Qatar
Tom,

I need to apply encryption on data of a column in a table. The DB box version is
7.3.1.

Any direct oracle tools are there to implement this like
DBMS_OBFUSCATION_toolkit package?...

Thanks.

          Followup:

Nope, you need to get to 8i before you have that functionality.

Prior to that, the functionality just plain and simply "did not exist" in the
database.

      Rating: 5
why it not
      work  July 10, 2002
Reviewer:  ashraf
       from kuwait
QL> PROCEDURE DO_ENCRYPT IS
 2       V_ENCRYPT                LONG default NULL ;
 3       V_DECRYPT               LONG default NULL ;
 4       V_INPUT                   LONG default NULL ;
 5  BEGIN
 6      V_INPUT := :DECRYPT_FILE.TXT_DEC;
 7    V_INPUT := rpad( V_INPUT, (trunc(length(V_INPUT)/8)+1)*8, chr(0));
 8
 9    dbms_obfuscation_toolkit.DESEncrypt
10        ( input_string => V_INPUT,
11          key_string   => :DECRYPT_FILE.TXT_KEY,
12          encrypted_string=> V_ENCRYPT );
13
14      :DECRYPT_FILE.TXT_ENC := utl_raw.cast_to_raw(V_ENCRYPT);
15  END;
16  /
P2-0552: Bind variable "DECRYPT_FILE" not declared.
QL>

          Followup:

line 6, you are trying to use a bind variable in DDL, that is not possible.

I don't know what you are attempting to do with :DECRYPT_FILE.TXT_DEC but it
isn't even remote "valid syntax"

      Rating: 5
password
      encryption  September 04, 2002
Reviewer:  mo
       from Vienna, VA
Tom:

I am using the crypt and decrypt functions for encrypting application users that
are stored in a table.

However, do you say that this is not really safe method if someone knew what the
key "word" was.

2.  If this is the case, then what do you use these for?

3.  Do you recommend using Hash methods for password encryption.

4.  You say to use 8 bytes. is that for the key?

Thank you,

          Followup:

Your encrypted data is only as safe as the keys -- obviously.

2) don't understand what you mean?

3) yes, yes and double yes.

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:95412348059
4) key and data -- the toolkit needs data in 8 byte multiples.

      Rating:
      5
encryption  September 04, 2002
Reviewer:
      mo  from vienna, va
AS a followup:

1.  You say that the data is as safe as the key?

Since they key can be any combination of letters and numbers how in the world
can someone guess it? ALso are you assuming that someone goes in and know how to
use the decrypt package to try to figure out what passwords are?

2.  You say use hashing for appliction or oracle passwords (I assume).  Then I
am wondering what is the encryption packages used for then?

3.  How can hashing be safer than encryption?

4. to implement hashing in your given code below:

 a. When admin creates a userid/password you use "digest" to read a username and
a password and store the hashed result as a password in the database?

b.  Then when someone wants to log in you take whatever he keyed in, hash it and
compare that to the hashed value in the database?

c.  how is that safer than encrypted value.

ops$tkyte@8i> declare
  2      function digest( p_username in varchar2, p_password in varchar2 )
return varchar2
  3      is
  4      begin
  5          return ltrim( to_char( dbms_utility.get_hash_value(
upper(p_username)||'/'||upper(p_password),
  6                                                       1000000000,
power(2,30) ),
  7                          rpad( 'X',29,'X')||'X' ) );
  8      end digest;
  9  begin
 10      for x in ( select username from all_users where rownum < 20 )
 11      loop
 12          dbms_output.put_line( 'User: ' || rpad( x.username , 30 ) ||
 13                                ' digest: ' || digest( x.username, 'TIGER' )
);
 14      end loop;
 15  end;
 16  /

          Followup:

1) many people use psuedo random number generators to generate keys.  In doing
so, they generate keys in a predicable fashion (all psuedo random number
generators have a period -- show me a couple of keys in sequence and I can
predict the keys that are coming)

Also, many people use algorithms to generate keys.  For example really early
Netscape SSL implementations used an algorithm to generate the session key used
to encrypt the conversation.  Unfortunately, once anyone knew the algorithm used
to generate the key -- they too could generate the key and listen in on the
conversation.  Here encryption was useless as it was trivial to reverse!

Your password to your systems is as you say "any combination of letters and
numbers" so how is it that many HACKS start with password guessing???

2) when you need to see the value back.  For passwords, all you need to do is
verify that the username/password pair a user presents to you is correct.  So,
you hash the password they gave you and compare to the hash in the database.
You never need to REVERSE the hashed password in the database (that is one of
the beauties of using the HASH, even if your user table with usernames/hashed
passwords is stolen-- it provides no useful information to anyone in any case!)

But suppose that was a credit card number in there -- username -> credit card
number.  Well, if you are an online store, you obviously need to get the credit
card number out of there so you can get paid right?  So, you cannot hash it, you
encrypt it.  That way, if your user table with credit card numbers is stolen, it
is "safe" unless the robber has your key(s) (which goes to point #1 again, the
data is only as safe as the keys)

3) See #2, it not easier in that you don't have the HASSLE of generating a
secure key, maintaining the key, presenting the key.  You can also sleep easier
at night cause you don't have to worry about if your key has been comprimised.

4) read #3 in your previous comment and follow the link.

      Rating: 5
For
      mo...  September 05, 2002
Reviewer:  Connor
       from UK
hashing is "more" secure that encryption because decryption is not possible.
Thus a brute force attack is basically the only way to go (assuming you have a
good hashing function).

The issue with the key is not possible guessing it, but them finding some way to
see it.  For example, when you call dbms_obfuscation_..., you need to pass the
key.  If you pass it as a literal, then it might be caught by snooping v$sql.
If its a bind variable, then it might be caught in a 10046 level 4 trace.  If
its in a table, then you've got to be very sure that this is well protected.  If
its in the PLSQL source code, then you have got to make sure that its wrapped
and that even the wrapped code does not give any clues.  If its being run across
a network, then the key cannot be passed in clear text....

The moment you embrace database encryption, you're up a LOT of work

hth
connor

          Followup:

Or if you don't embrace the "work" concept -- all you've done is built a false
sense of security.

It is like the people that say "Oh, I've got it so my application is the only
thing that can access the data.  We did that by password protecting the role,
pretty neat eh?"  Even when I describe how it would take less then a couple of
seconds to break their scheme in most every case -- they say "oh, our users are
not that smart, not to worry".  Total and utter false sense of security by
obscurity...

      Rating: 5
...our users
      aren't that smart...  September 05, 2002
Reviewer:
      Frank  from NL
Indeed. Which always makes me wonder why the h#ll they're going through all the
work to encrypt it in the first place :-))

      Rating: 3
Usefulness of
      hashed passwords  September 05, 2002
Reviewer:
      Gary Myers  from Sydney, Australia
"even if your user table with usernames/hashed
passwords is stolen-- it provides no useful information to anyone in any case!)"

I'd have to disagree with that (though I could be persuaded to agree if you
changed 'it provides' to 'it should provide').

If I get to see the hashed passwords in DBA_USERS, I can run a bit of PL/SQL on
my own database on my home PC that does an 'ALTER USER SYSTEM IDENTIFIED BY' for
a few thousand possible passwords until it gets one with a hashed password that
matches the one for your system account. If the system password is a normal word
that can be found in a hacker's dictionary, it's vulnerable. [Yes, system
passwords shouldn't be that simple, but that's why I think the phrase would be
better with 'should'.]

Trying to run that same quantity of passwords against the real database is more
likely to get noticed.

Accounts with access to hashed passwords should be treated as a security risk.
If it's your own hashing mechanism, then the table should not be accessible to
the users except via a function, with username and password parameters,
returning true or false and locking out after a certain number of failures. The
passwords should also be a minimum length, and matched against an exclusion list
of 'easily guessable' entries.

I've worked on an application that had an authorisation feature using a hashed 4
digit pin. It's amazing how quickly PL/SQL can go through the 10,000
combinations to find a success (and how many were either 9999 or 1111).

          Followup:

Ok, beyond the typical "brute force, I'll try every possible password and see
what happens -- given that I have access to your hash function and magic "salt"
if you use one", you are right.

Add a little salt, hide the salt, and there you go.

      Rating: 5
encryption and
      hashing  September 07, 2002
Reviewer:  mo
       from vienna, va
Tom:

1.  Can you verify for me what is the difference between binay, raw and hex
data.

2.  When you encrypt date it created a binary data (0's and 1's) while hashing
creates hex code (0..9 A..F).
Correct?

3. You say encryption will work on 8 bytes at a time . Does this mean if I have
a credit card or password field that is 10 characters in length I have to make
it varchar2(16) before applying the encrypt function.

4. When you say encryption is as safe as the key do you mean that all it takes
is to figure out the word "Magic Key"?  Can this also be replaced by any word of
any length?

5. Since encryption creates a binary and you store this in a varchar2 column
would it work or you have to  change the column type of encrypted data before
storing it?

6.  If I encrypt aplication passwords (which you do not recommend), then If a
user logs in why do I have to decrypt the password from the database, why not
encrypt the entered password again and compare it to the encrypted password in
the database?  Would not this be more like hashing?

7. I this correct:
 If you want to read the data from the database like credit card numbers use
encryption (actually only way of security).  If you do not need read data back
like user passwords use hashing instead?

8.  To hash data you use dbms_utility.get_hash_value and you define a string
there which works like a key in the encryption scheme. Correct?

9.  Can't a user break into the system by hashing his random password exactly
like trying to guess a key in a an encryption data?

          Followup:

1) RAW is a datatype that can hold upto 2000 bytes of BINARY data in a database
table (or 32k of it in a PLSQL routine).

HEX is text data (not binary, just normal ascii, printable, 'safe' characters)
that is a textual representation of binary data.

2) wrong.  Encrypting the data results in binary data.  Hashing the data results
in a number (which I used a fixed length HEX representation of

3) yes.

4) the key length is dictated by the algorithm.  single des = 8byte key, double
des = 16byte and des3 (triple des) wants a 24byte key.

Yes, the key can be ANYTHING in the world you want.

5) you would want to use a RAW datatype to store it, yes.

6) it is what I call "6 one way, 1/2 dozen the other".  No, it is not like
hashing AT ALL since you can decrypt the password and the reason you hash is so
YOU CANNOT.  That is the basic point -- hash = no decrypt = less chance of
compromise

7) yes.  If you do not need to decrypt the data, don't encrypt it.

8) no, not even close.  re-read that thread again (and read up on hashing, it is
a basic computer science "thing").  The string is what we are hashing.  It is
not the key, it is the value

9) HUH?  doesn't compute.

      Rating:
      3
Reader  September 09, 2002
Reviewer:
      A reader
Why Oracle does not HASH the password for Database Links
created with
create database linke <link> connect to <user>
identified by <password> using <connect string>

          Followup:

because there we need to present the password from DB1 to DB2.  In order to log
in, you need to present a username+password that is hashed to see if it is you.
You cannot just present the username and hash.

      Rating:
      4
Reader  September 11, 2002
Reviewer:
      A reader
Tom,

There are two parameters
DBLINK_ENCRYPT_LOGIN and
ORA_ENCRYPT_LOGIN

If we use DBLINK_ENCRYPT_LOGIN = true, does Oracle
send the *key* and the encrypted password via network
from DB1 to DB2 and enable DB2 decrypt the password using
the key, and authenticate the password.

Thanks

          Followup:

Just the encrypted password is sent.

the password is encrypted with false as well -- only sent in the clear if the
first attempt fails.

      Rating:
      4
Reader  September 16, 2002
Reviewer:
      A reader
Tom,

Given that, a database link is set up
successfully between local DB1 and
remote DB2, and that the application
has been tested successfully using the db_link
before going into production, what is
the chances of DB_LINK to fail in the
first attempt in a PRODUCTION environment.

If the password is changed at the remote
DB2, the link will fail. In this case it
is not a concern.

Is there any other scenarios for the first
attempt to fail and subsequent attempt
to succeed

Thanks

      Rating: 4
Encrytion and
      Decrytion  November 08, 2002
Reviewer:  Dave
      Fowler  from St.Louis MO USA
Good example of use of dbms_obfucation toolkit. and encryption/decryption
technology.

      Rating: 4
How do you do
      this?  December 04, 2002
Reviewer:  Eric
      Givler  from Harrisburg, PA USA
You said:

"It is like the people that say "Oh, I've got it so my application is the only
thing that can access the data.  We did that by password protecting the role,
pretty neat eh?"  Even when I describe how it would take less then a couple of
seconds to break their scheme in most every case -- they say "oh, our users are
not that smart, not to worry".  Total and utter false sense of security by
obscurity... "

HOW CAN YOU DO THIS IN A FEW SECONDS?  THE ROLE PASSWORD IS HASHED IF ONE
APPLIES.  CAN YOU ELABORATE?

Two cases I thought this would be an issue are:
a) if someone with DBA rights (or GRANT ANY ROLE) creates a newuser and then
grants the "magic role" to the newly created account.  That role now becomes a
DEFAULT ROLE, and the user can then begin snooping.

b) The user who logged in has ALTER ROLE, and removes the password from the
protected role.

How can a non-privileged, non-DBA users break this scheme in a couple of
seconds?

          Followup:

The application will be sending the password over the wire to the database.
I'll turn in sqlnet tracing and I'll see your password.

You would have to be using ASO (advanced security) and network encryption in
order to stop me.

I would just run your application (as I'm supposed to), then I'll have your
password.  You cannot stop me from turning on sqlnet tracing on my machine.

      Rating: 4
How does that
      apply?  December 07, 2002
Reviewer:  Eric
      Givler  from Harrisburg, PA USA
I realize this is probably not the way to continue asking questions (based on
instructions to the side), but how would a user turn on SQL-tracing?

The application is not sitting on their desktop.  The application is on the
application server, or possibly on a citrix server.

Now they still have to find that machine and then login with a valid oracle
account, then view the trace files after modifying sqlnet.ora.  How did they get
privileges to get to all of these files and folders?

Am I missing something obvious?

          Followup:

It is not sql_trace.

It applies to client server (which is what the vast preponderance of people
using this approach have done -- client server).

It is net tracing, set in the sqlnet.ora

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/network.817/a76933/params.htm#437951
it can dump everything that goes back and forth.

      Rating: 5
Use of
      char(0)  December 24, 2002
Reviewer:  Pooja
       from USA
I am noticing that you some times  use chr(0) in your code (including this one).
After some research I found the
following.
1) This is equivalent to ascii U000.
2) select 'abc' || chr(0) || 'de' from dual
   seems to return  just abc.
   String after chr(0) does n't seem to be returned.
3) substr('abc' || chr(0) || 'de', 4) returns nothing.
   But, substr('abc' || chr(0) || 'de', 5) returns de.

Can you please tell me

a) What is this character called as?
b) When do we usually use this?

Appreciate your help.

Thanks

          Followup:

It is generally call NUL in ascii speak.

In C -- it is the end of string marker..  that is why select 'abc' || chr(0) ||
'de' from dual; does that -- sqlplus is using

   printf( "%s", field );

and the chr(0) which is '\0' in C denotes end of string.

In this example, I'm just using it as a "safe" character to embedd in my string.

      Rating: 3
Differnce
      between 8.1.7 and 9.0.1.1.1  January 29, 2003

Reviewer:  Jonathan Lee  from Rockville, MD

Yes, it work on same version.
But, if I encrypt with 8.1.7 and try to decrypt in 9.0.1 , it doesn't decrypt
correctly.

Any thought?

          Followup:

there is no difference -- DES is DES is DES.  it is the same.

More then likely -- you are storing the encrypted data which is binary in a
varchar2 field instead of raw?  and you are getting bit by a character set
conversion?

check that out -- make sure the character sets are the SAME
that the raw DATA is the same (use the DUMP() builtin to verify that)

Here is an example that shows this works just dandy:

ops$tkyte@ORA817DEV> create or replace function crypt( p_str in varchar2 )
return raw
  2  as
  3      l_data  varchar2(255);
  4  begin
  5      l_data := rpad( p_str, (trunc(length(p_str)/8)+1)*8, chr(0) );
  6          return dbms_obfuscation_toolkit.DESEncrypt
  7             ( input => utl_raw.cast_to_raw(l_data),
  8                           key => utl_raw.cast_to_raw('MagicKey') );
  9  end;
 10  /

Function created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace function decrypt( p_str in raw ) return
varchar2
  2  as
  3  begin
  4          return utl_raw.cast_to_varchar2(
  5                dbms_obfuscation_toolkit.DESdecrypt
  6                ( input => p_str,
  7                  key   => utl_raw.cast_to_raw('MagicKey') ) );
  8  end;
  9  /

Function created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> column data format a10
ops$tkyte@ORA817DEV> drop table t;

Table dropped.

ops$tkyte@ORA817DEV> create table t ( data varchar2(9), data_enc raw(16) );

Table created.

ops$tkyte@ORA817DEV> insert into t values ( '012345678',crypt('012345678'));

1 row created.

ops$tkyte@ORA817DEV> commit;

Commit complete.

ops$tkyte@ORA817DEV> select data,
  2             data_enc,
  3             decrypt(data_enc) data
  4    from t;

DATA       DATA_ENC                         DATA
---------- -------------------------------- ----------
012345678  07CF8213EDC46C277C720164D915B70C 012345678

$ exp userid=/ tables=t

Export: Release 8.1.7.4.0 - Production on Thu Jan 30 08:13:57 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production
Export done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set

About to export specified tables via Conventional Path ...
. . exporting table                              T          1 rows exported
Export terminated successfully without warnings.

$ imp userid=/ full=y

Import: Release 9.2.0.1.0 - Production on Thu Jan 30 08:14:21 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to: Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

Export file created by EXPORT:V08.01.07 via conventional path
import done in WE8ISO8859P1 character set and AL16UTF16 NCHAR character set
export server uses WE8ISO8859P1 NCHAR character set (possible ncharset
conversion)
. importing OPS$TKYTE's objects into OPS$TKYTE
. . importing table                            "T"          1 rows imported
Import terminated successfully without warnings.

ops$tkyte@ORA920> select data,
       data_enc,
       decrypt(data_enc) data
  from t;  2    3    4

DATA      DATA_ENC
--------- --------------------------------
DATA
---------------------------------------------------------------------------------
--------------------------------------------------
012345678 07CF8213EDC46C277C720164D915B70C
012345678

Now, if i stored the data in varchar2 and did a character set switcheroo
during the exp/imp step -- this would *not* work since the data would *not* be
the same

make sure you have the same data!

      Rating: 5
Is there a
      way?  April 22, 2003
Reviewer:  TH
       from Manama, BAH
Dear Tom,

Is there a way to hide all the schema objects when we are supplying a software?

For example the public should not know how we defined our tables, procedures,
functions etc. I heared we can encrypt procs. But wht about tables?

          Followup:

nope, tables are tables are tables -- it would defeat the purpose of being a
database otherwise.

It is THEIR data remember.  NOT yours.

      Rating: 1
Encrypt And
      Decrypt  May 18, 2003
Reviewer:  Deval Shah
       from India
 Here i am sending you one code which i had copide from your answer.

 I am using version 8.1.7

  Here i have written two trigger on the table where i am storing my pass word
one is on Before insert and another on Before Update

This code is written in version 8.1.7

The table like this
Table := USER_DETAIL
   USER_ID NUMBER(10),
   PASSPHRSE VARHCAR2(50),
   PASSPHRSE2 RAW(100)

and here two triggers and both the trigger is same

This is Before Update trigger
create or replace trigger TRIG_PASS_BEF_UPD
DECLARE
vTabPass VARCHAR2(100);
vEncrDncrPass raw(100);
BEGIN
  vTabPass := rpad( :NEW.PASSPHRSE,(trunc(length(:NEW.PASSPHRSE)/8)+1)*8,
chr(0));
     :NEW.PASSPHRSE2 := dbms_obfuscation_toolkit.DESEncrypt
        ( input => utl_raw.cast_to_raw(vTabPass),
         key => utl_raw.cast_to_raw('MagicKey') );
END;

after i apply update query to change the password

UPDATE USER_DETAIL
   SET PASSPHRSE = 'deval';

which will update all the records in table.

i have also written function to get that password back from the table.

create or replace function get_password
 (vEncyPassword raw) return VARCHAR2
as
begin
   return utl_raw.cast_to_varchar2(
     dbms_obfuscation_toolkit.DESdecrypt
     ( input => vEncyPassword ,
      key   => utl_raw.cast_to_raw('MagicKey') ) );
end;

On version 8.1.7 it is working very nice.

but after that i had taken export of this user and i had imported it on the
another server which is having version 9.2.0

here i am checking my password by using get_password function but it is not
giving me junk password so could you tell me where i am making mistake?

currently my live system is working on the client in USA and all the coding is
done perfectly so i don't want to change the frontend code so i had taken two
field to encrypt the password. if it is working well then i will do something to
remove one field.

please help me.

          Followup:

well, beyond being the LEAST SECURE implementation of security I've seen in a
while.....

so, what is the datatype of the password column.

I'll guess.  You left it as varchar2.  It is a varchar2.

When you exported, the database had characterset X, the client had character set
Y (nls_lang setting).

When you imported the client had characterset A and the database had character
set B.

One of X, Y, A, B was different from the others.  characterset conversion kicked
in (as it is supposed to) and changed the varchar2 data.

All you'll need to do is

select dump( password, 16 ) from thistable;

in both databases and compare a couple, you'll see the bytes have changed.

only solution:

make sure X, Y, A, B all agree 100%

long term solution:

don't store passwords!  that is the key to "insecurity" -- store passwords and
I'll get them.  especially when you make it so easy to do so.

or if you persist.  store the passwords in a RAW field so the conversion does
not take place.

      Rating: 4
Response to
      Deval Shah from India   May 20, 2003
Reviewer:
      Jonathan Lee  from Rockville, MD
I found hard way why to same encrypt and decrypt function is not working on
Oracle 9i (but works fine on 8i)

Change the KEY_STRING.

If you use Key_string as 12345678 on 8i, change to 43218765 on 9i.

It will work.

      Rating: 4
Encrypt &
      Decrypt  September 16, 2003
Reviewer:  Bonnie
      Tang  from Hong Kong
Hi! Tom,

does your encrypt and decrypt procedure work on any characterset ? UTF8 ? or
ZHT16MSWIN950 (which is for traditional chinese)?.  How about the length of raw
we should use ?

Please help.

Bonnie

          Followup:

for multi-byte data, you probably need to use lengthb (length in bytes, not
characters) and use a character you know is a single byte in your character set
to pad with.

      Rating: 4
Encryption
      & Decryption for multi byte data  September 17, 2003

Reviewer:  Bonnie Tang  from HK
Tom,

Can utl_raw.cast_to_varchar2 give me back the original text in multi-bytes ?
Why do I need to pad a single byte character in encrypting ?  Suppose your
chr(0) is single byte, then encrypt/descrypt procedures should look like the
following. Right ?

create or replace function crypt( p_str in varchar2 )
return raw
as
  l_data  varchar2(255);
begin
   l_data := rpad( p_str, (trunc(lengthb(p_str)/8)+1)*8, chr(0) );
       return dbms_obfuscation_toolkit.DESEncrypt
          ( input => utl_raw.cast_to_raw(l_data),
                        key => utl_raw.cast_to_raw('MagicKey') );
end;
/

create or replace function decrypt( p_str in raw ) return
varchar2
as
begin
       return utl_raw.cast_to_varchar2(
              dbms_obfuscation_toolkit.DESdecrypt
            ( input => p_str,
              key   => utl_raw.cast_to_raw('MagicKey') ) );
end;
/

Many thanks.

Bonnie

      Rating: 5
infinitive
      loop  September 26, 2003
Reviewer:  Hui Li
       from CA USA
We found that in SOME Oracle database enviroments, the following line gives you
infinitive loop since the "length" function does not count the "char(0)". Do you
know why?

rpad( p_str, (trunc(length(p_str)/8)+1)*8, chr(0) )

          Followup:

an infinite loop?  how so?

and why would p_str have chr(0) in it ?  i choose chr(0) cause it is a character
one would not see in a string in a single byte characterset.

      Rating: 4
does not return
        October 08, 2003
Reviewer:  Hui Li
       from CA, USA
Tom,
We found that in some 9i Oracle DBs, the following line of code does not finish

rpad(p_input_string, (trunc(length(p_input_string)/8)+1)*8, chr(0) )

However, it works if we changed it to the following

l_length := (trunc(length(p_input_string)/8)+1)*8;
l_input_string := p_input_string;
WHILE length(l_input_string) < l_length
   LOOP
      l_input_string := l_input_string || chr(0);
   END LOOP;
I am thinking the "length" function does not work with the "chr(0)" for some 9i
Oracle DBs

          Followup:

"does not finish".

if you have a database where that "does not finish" -- (do you mean hang) -- i
would STRONGLY encourage you to file a bug with support, you have a really
simple testcase -- they would collect the configuration information and get it
fixed.

      Rating: 5
sql net
      tracing  October 18, 2003
Reviewer:
      A reader
You mention sql net tracing above - Have you ever used this
tool to troubleshoot or debug an issue - could you kindly give an example?

Thank you!

          Followup:

No many times.  I use it to see whats going on in a connection problem (packet
dumps, see whats going back and forth, in the early days of NAT (network address
transalation) i remember seeing the actual IP address (not hostname) go back --
leading me to discover why we couldn't connect).

Or to satisfy curiosity -- or demonstate that the data is cleartext over the net
unless you enable encryption.

      Rating: 4
Eliminate
      Special Characters  October 21, 2003
Reviewer:
      Mohan  from USA
Is there a way to eliminate (#, ?) in the encrypted string? We need to pass this
encrypted string via URL and is giving a problem.

Thanks

          Followup:

well, encrypted data should be in RAW fields first of all

I would use utl_raw.cast_to_raw to convert the string into a raw.

then encrypt the raw data.

then assign the raw data back to a varchar2 that is 2x the size of the raw.

You'll have a nice hex string consisting of 0..9A..F only

to decrypt, assign varchar2 back to raw (converts)

decrypt raw

utl_raw.cast_to_varchar2 the decrypted data

      Rating:
      5
DECRYPT  November 13, 2003
Reviewer:
      Junior  from VA, USA
Tom:

I tried your example, but have problem for the decrypt data size. You have
explained it, but I still not understand, so... How can I get rid of those extra
invisible characters.... Thanks.

create table t ( data varchar2(9), data_enc raw(16) );

 1  create or replace function crypt( p_str in varchar2 )
 2  return raw
 3    as
 4        l_data  varchar2(255);
 5    begin
 6        l_data := rpad( p_str, (trunc(length(p_str)/8)+1)*8, chr(0) );
 7            return dbms_obfuscation_toolkit.DESEncrypt
 8               ( input => utl_raw.cast_to_raw(l_data),
 9                            key => utl_raw.cast_to_raw('MagicKey') );
10* end;

 1  create or replace function decrypt( p_str in raw ) return
 2  varchar2
 3  as
 4  begin
 5        return utl_raw.cast_to_varchar2(
 6              dbms_obfuscation_toolkit.DESdecrypt
 7              ( input => p_str,
 8                key   => utl_raw.cast_to_raw('MagicKey') ) );
 9* end;

  1  select data, LENGTH(DATA),
  2         data_enc, LENGTH(DATA_ENC),
  3         decrypt(data_enc), LENGTH(decrypt(data_enc))
  4* from t
SQL> /

DATA      LENGTH(DATA) DATA_ENC                         LENGTH(DATA_ENC)
--------- ------------ -------------------------------- ----------------
DECRYPT(DATA_ENC)
-------------------------------------------------------------------------
LENGTH(DECRYPT(DATA_ENC))
-------------------------
012345678            9 07CF8213EDC46C277C720164D915B70C               32
012345678
                       16 ( why this is 16? Are there extra invisible
characters?)

Thank you again.

      Rating: 5
Points raised
      by Connor McDonald  November 20, 2003
Reviewer:
      SA
Hi Tom:

We are considering use of Encryption in our project and this thread is so
helpful. Could you please give your openion on how to tackle some of these
issues raised by Connor in the same thread:

<QUOTE>
For example, when you call dbms_obfuscation_..., you need to pass the key.  If
you pass it as a literal, then it might be caught by snooping v$sql.  If its a
bind variable, then it might be caught in a 10046 level 4 trace.  If
its in a table, then you've got to be very sure that this is well protected.  If
its in the PLSQL source code, then you have got to make sure that its wrapped
and that even the wrapped code does not give any clues.  If its being run across
a network, then the key cannot be passed in clear text....
<END QUOTE>

For example, what is the solution to DBA seeing the key from a 10046 trace? How
do we avoid it? Also, how do we avoid the key to appear in an sql_trace?

Is there any examples of a real-life implementation of Encryption within the
database anywhere in OTN? I could not find any. Will be greatful if you can
help.

Thanks a lot.
SA

          Followup:

to tackle these issues - simple

BE AWARE OF THEM.

there is no avoiding them.  they are things you must be aware of and take
whatever precautions YOU feel is sufficient to prevent them from falling into
the wrong hands.

for example, to do a 10046 trace -- you need to have "alter session".  don't
grant that.

if it is in a table, secure the table.

if it is in code, wrap it and obscure it in the code.

the DBA must be trusted, regardless.  Any semi intelligent DBA would be able to
figure anything you do out in the end.  Vett your DBA's (clear them).

      Rating: 4
Input on keys
      is very helpful  November 21, 2003
Reviewer:
      Eric Givler  from PA
In regards to some of the facts above, you noted originally about storing
encrypted data in varchar2 is ok, under certain conditions, then later talked
about chr(0)'s, and it did not appear to cause a problem.  I have to allow
"special" administrative users to query our data by a number that is encrypted.
What I have is the encoded varchar2 column and an index on it.  I pass their
query prm to a function and do return what this result should be encoded.  When
I attempt the lookup, I get a "no data found".  The raw trace appears to stop
when it sees a null byte in the bind.

However, I've created dummy table, stored a string with an embedded chr(0), like
'0000001'||chr(0)||'A', and then later queried by this literal without a
problem.

I tried the same "technique" against this problem by using an open refcur for
'select '... embed literal with chr(0) in the string.  The trace shows an open
statement with a bind that is truncated at the chr(0), no rows returned.  It
also shows a "good statement" with the original encoded data, which is
BYTE-by-BYTE equivalent to the encoded data, but appears to not return a row.

What gives?

Should I simply work it into my plans to undo this column as varchar2, and make
it a raw, and slightly change my existing interaction with it in my
encrypt/decrypt functions?  Or possibly even just widening the column and
replacing each existing value with a utl_raw.cast_to_raw, then recast back to
varchar2 to save a "hex string", which I simply "like to look at".  I can
understand reading hex digits, but hate writing funky little routines to spit
out the bytes, their hex digits, and the character just to see why this does not
work.

Any thoughts would be helpful, and I'm hoping this might be useful to others.

          Followup:

what gives?  need an example, depends on the language and method of interface.
In C and other languages -- chr(0) means "END OF STRING" -- "STOP".  So, i would
guess your language you program in is one of them that says "STOP" and when you
bind -- it stops.

i would use RAW for encrypted data (based on experiences).  it is the safest all
around.

      Rating: 4
more input
      about chr(0) issue  November 22, 2003
Reviewer:
      eric givler  from pa
The language is SQL, and or Pl/sql.

For example, I query the database like this:

variable x varchar2(32)

begin
   -- this is the "known" problematic record
   select cc_encoded into :x from receipts
   where receipt_num = 12041231;
end;
/

print x

(I see a string of chars here.)

I write:

select receipt_num
from receipts
where cc_encoded = :x
/

no rows returned

I examine the tracefile for my select and it shows 11 bytes where sent.

So, I type:

variable rc refcursor

begin
   open :rc for 'select *
     from receipts where cc_encoded=''' ||
     substr(:x,1,11)||chr(0)||substr(:x,13)||
     '''';
end;
/

print rc

no rows returned

The trace now shows "two statements".  (I'm using 8i R3 on Windows 2000).  The
first statement shows the pl/sql block and open for "cursor".  The second shows
a statement that looks like this:

select * from receipts
where cc_encoded = '<bytes here>'

I open the trace file using "hex mode" (hex bytes on left, and normal text on
right).  I scroll to the statement and it shows that the '<bytes here>' contains
the exact string of bytes that I've printed in sqlplus, as I listed each byte in
the string using a substr() loop and displayed it in hex.

I still get no rows when I print rc though.

NOTE: I can't get to my work machine right now to give you the raw trace files.
I'm tempted to open a TAR.

          Followup:

give me the entire test case (insert et. al.)

plus is probably binding a regular "ascii z string" (null terminated)

having chr(0) in a "string" is a truly (110%) bad idea

      Rating: 4
it's not the
      null byte  November 23, 2003
Reviewer:  eric
       from PA
I forgot to ever dump this out, but here's the difference, and I can't explain
it though:

PARKS_DBA@test [NRBITORAS03] > select dump(:x) from dual;

DUMP(:X)
---------------------------------------------------------------------------------
--------------------------
Typ=1 Len=24:
179,162,44,255,228,81,251,191,205,191,94,0,12,188,14,230,60,86,127,183,177,94,191
,33

1 row selected.

PARKS_DBA@test [NRBITORAS03] > select dump(cc_encoded)
  2  from transaction_groups@prrsdb where eid=1288792
  3  /

DUMP(CC_ENCODED)
---------------------------------------------------------------------------------
--------------------------
Typ=1 Len=24:
179,162,44,255,228,81,251,142,205,151,94,0,12,188,14,230,60,86,127,183,177,94,137
,33

1 row selected.

The difference being the next to the last byte.  Why does selecting it into the
Varchar2 "change it"? or more simply:

PARKS_DBA@test [NRBITORAS03] > ed
Wrote file afiedt.buf

  1  begin
  2  :y :=   chr(179) ||
  3          chr(162) ||
  4          chr(44)  ||
  5          chr(255) ||
  6          chr(228) ||
  7  chr(81)||chr(251)||chr(142)||chr(205)||chr(151)||
  8  chr(94)||chr(0)||chr(12)||chr(188)||chr(14)||chr(230)||
  9  chr(60)||chr(86)||chr(127)||chr(183)||chr(177)||
 10  chr(94)||chr(137)||chr(33);
 11* end;

PARKS_DBA@test [NRBITORAS03] > select dump(:y) from dual;

DUMP(:Y)
---------------------------------------------------------------------------------
---------------------
Typ=1 Len=24:
179,162,44,255,228,81,251,191,205,191,94,0,12,188,14,230,60,86,127,183,177,94,191
,33

1 row selected.

why does this change?

          Followup:

character set translations no doubt.

You are storing binary information in a varchar2, you want to be using a RAW for
that.  You are using characters that "do not exist" in your characterset and we
are converting them.

      Rating: 5
YES YOU ARE
      CORRECT AGAIN.  November 24, 2003
Reviewer:
      Eric Givler  from HBG, PA
I did not think it was character set translation at first due to the TRACE
showing the "shorter" bind.  However, that trace was from FORMS.  A subsequent
trace was from Plus (9iR2), and when I had looked in the registry for the
NLS_LANG, I looked in the wrong spot.  The DB's NLS was:
AMERICAN_AMERICA.WE8ISO8859P1 and the 9i registry NLS was:
AMERICAN_AMERICA.WE8MSWIN1252

Changing the NLS so they match, does allow me to query in PLUS, but forms still
fails due to the chr(0).  Anyway, I'm moving all the code for the querying off
the form to base it on a stored procedure or ref cursor.  I'll handle the logic
in there temporarily, then correct the column issue thereafter (more work
involved).

Thanks for all the help!

      Rating: 5
infinit loop
        December 10, 2003
Reviewer:  Djordje
       from Ontario, Canada
We have experienced the same thing: code hanging with chr(0) appended to the
end.

Environment: Solaris 2.8, Oracle 9.2.0, Enterprise

Specifically this statement was hanging (all variables are VARCHARs):

l_data := rpad(l_in_data, (trunc(length(in_data)/8)+1)*8, chr(0) );

We changed the algorith to pad with the space.  Later I saw on Metalink (Note
123091) a suggestion to pad the string using the PKCS5 method ('1', or '22', or
'333', etc.).

Could you comment on that?

Thanks.

          Followup:

sounds like a "bug" -- but i don't use multi-byte character sets and I'm not
sure what impact chr(0) might have in them.

December 10, 2003 Indrakala -- Thanks for the question regarding
      "Group By Clause", version 8.1.7

      You Asked
Tom,
    Is there any advantage of using primary keys in the GROUP BY clause. Will
the query performance improve that way? I believe that it doesnt and all you
have to take care is that your sortkey should be as small a value as possible.
Is it correct?

regards
ik

      and we said...
Umm -- the columns in a group by clause are dictated PURELY by the question
being asked.  It has nothing to do with performance.

Besides, if the primary key is in the group by - the group by is REDUNDANT isn't
it.  I mean, you'll NEVER have two rows with the same keys, so the group by is
not relevant.
Review & Followup

      Rating: 3
Just
      clarifying  August 31, 2002
Reviewer:  ik
       from Bangalore
Thanks Tom. Yes, GROUP BY on Primary Keys will result in only one row, but my
case is when multiple tables are joined.
For example :-
I have a table tab1 having primary key on col_pk. col_id is another field which
is unique for a given col_pk. There is a child table having multiple values for
a given col_pk. So, if i want to select statistics grouped on col_pk or col_id
from a child table and i want col_id in the selected list, i would naturally
group on col_id. My question is that would there be a difference in performance
if the gouping was on the primary key field?

Also, can you please explain how oracle computes group by. Does it happen after
all data is fetched? What is the sequence if a query has GROUP BY, Analytic
functions and an ORER BY Clause.

regards
ik

          Followup:

The question isn't relevant (that is my point).

Look -- if you NEED something grouped by X,Y,Z it really just doesn't matter AT
ALL if it would be faster to group by A, B does it?  It isn't what you need.  It
is not like "grouping by a primary key is an ALTERNATIVE".  When tuning, you are
looking for same result alternatives -- this just isn't one of them!

(as an aside, there is perhaps an advantage to getting the first rows back
fastest when the group by columns are indexed -- which will be the case in a
primary key situation.  That does not mean "index all group by columns"!!!  Not
even a little)

As for the "when/how" -- the answer is "it depends".  It depends on the
predicate, the table(s) involved, the lack or presence of indices and so on.

Sometimes after all data, sometimes as soon as the first group is known to be
done.

GROUPING is logically done first.
Then Analytics.
And finally sorting.

We can see this with this query:

scott@ORA920.US.ORACLE.COM> select deptno, max(sal), lag(deptno) over ( order by
deptno )
  2    from emp
  3   group by deptno
  4   order by 2
  5  /

DEPTNO   MAX(SAL) LAG(DEPTNO)OVER(ORDERBYDEPTNO)
------ ---------- ------------------------------
    30       2850                             20
    20       3000                             10
    10       5000

scott@ORA920.US.ORACLE.COM>

Now, the analytics can only work on group by columns in this case (when you have
an aggregate, analytics can only "see" the aggregated columns, this perhaps
shows that even better:
scott@ORA920.US.ORACLE.COM> select deptno,
  2         max(sal),
  3         lag(deptno) over ( order by deptno ),
  4         sum(max(sal)) over ()
  5    from emp
  6   group by deptno
  7   order by 2
  8  /

DEPTNO   MAX(SAL) LAG(DEPTNO)OVER(ORDERBYDEPTNO) SUM(MAX(SAL))OVER()
------ ---------- ------------------------------ -------------------
    30       2850                             20               10850
    20       3000                             10               10850
    10       5000                                              10850

scott@ORA920.US.ORACLE.COM>

we can sum() over() the max(sal) -- meaning the analytic MUST be done after the
grouping has already taken place.

The order by is always done dead last.

      Rating: 4
Just what I
      wanted to know  September 16, 2002
Reviewer:
      Dave  from Ohio
I have the same kind of situation, where I GROUP BY primary key columns, such as
...

SELECT PK1,PK2,PK3,PK4,SUM(NUM1) NUM1
FROM TABLE_WITH_4PK_COLS
GROUP BY PK1,PK2,PK3,PK4
HAVING SUM(NUM1) > 1000

... and obviously it is non-optimum, as you say. It would be better to ...

SELECT PK1,PK2,PK3,PK4,NUM1
FROM TABLE_WITH_4PK_COLS
WHERE NUM1 > 1000

... but I do not have a choice in this. Like many organizations out there we use
a COTS query tool that generates SQL for us, and the tool will not use PK
information to change the query appropriatly. I've used a tool that does, but
I've also found that it sometimes did so in inappropriate circumstances. I guess
that's what can happen when you have multiple applications storing metadata
redundantly.

I've been very impressed with the level of "intelligence" shown by the Oracle
optimizer when using materialized views and applying query rewrite, and it was a
real surprise to find that this optimization is not automatically performed. It
would seem to me to be an ideal candidate for a performance-enhancing
optimization. The tools I use are also "aggregate-aware", but I
strongly/fanatically believe in Oracle's approach to performing these
optimizations in the layer that has the most robust metadata on data
integrity/relationships/quality -- the database itself.

Just my 2 cents Tom, but perhaps you could pick up the phone and tell the
developers to add a couple of lines of code to 9i release 3 to do this ;-).
Maybe there's actually a mathematical/logical reason not to do it, who knows?

      Rating: 4
GROUP BY - a
      pseudo column (back to SQL 102)  November 14, 2002

Reviewer:  robert  from PA
Tom, about the SQL below...
on the "decode(emp_status, 'ACTIVE', hiredate, firedate)"
in the GROUP BY...I am not sure this is OK.
Will you pls confirm that Oracle, although
will not accept in the GROUP BY clause the pseudo
column name (l_date), will do the "decode..." evaluation,
"gather" the result-set and do the aggregation on the
l_date  column of the result-set. Thanks

SELECT deptno, count(*),
decode(emp_status, 'ACTIVE', hiredate, firedate) l_date
FROM  emp
WHERE (
(hiredate BETWEEN '01-Jan-2002' AND '13-Nov-2002'        AND emp_status =
'ACTIVE')
OR
(firedate BETWEEN '01-Jan-2002' AND '13-Nov-2002'
 AND emp_status = 'GONE'))
GROUP BY
deptno,
decode(emp_status, 'ACTIVE', hiredate, firedate)

          Followup:

sorry -- not following you on this one.

If you are asking "will the above work and work correctly", yes -- it will.  it
will group by the decode.

if you don't like the double decode coding

select deptno, count(*), thedate
  from ( select deptno, decode(emp_status, 'ACTIVE', hiredate, firedate) thedate
           from emp
          where .... )
 group by thedate

      Rating: 3
GROUP BY and
      performance  November 15, 2002
Reviewer:
      Oleksandr Alesinskyy  from Germany
Hi Tom,

Y say: "the columns in a group by clause are dictated PURELY by the question
being asked.  It has nothing to do with performance.", but this isn't true
completely.
E.g. we have view that allow us to obtain some data grouped by city, but
underlining table is indexed only by countries and we don't want to add more
indexes. In this case it is quite reasonable to add to GROUP BY clause of the
view coutry, which is redudant but maz drastically improve performance for some
queries from this view. I suppose that similar results maz be achieved with
DIMENSIONS, but sometimes just addition of 1 column is much simpler solution.

          Followup:

If the index was on city, country -- you would have no need to add the extra and
most like very confusing group by column.

      Rating: 3
Quick
      question  December 04, 2002
Reviewer:  A
      reader  from India
Tom,
  If a query with a SELECT DISTINCT and a SELECT.. GROUP BY would return the
same data which would you prefer and why. It's a choice between sort unique and
sort group by.

For eg:-
SELECT DISTINCT a FROM tab;
or
SELECT a FROM tab GROUP BY a;

regards

          Followup:

tell me -- do you want distinct rows -- is that your goal?  then by all means
use distinct.

tell me -- do you want to group by, to perform aggregates?  then by all means
use group by (as nothing else will do)

Use the one that has more semantic meaning so that 2 years from now after you've
moved on -- it is more clear to the person inheriting your code what you meant.

      Rating:
      3
  December 05, 2002
Reviewer:  A
      reader  from India
Tom,

This is the scenario.

TABLE -tab1
name    role

1       A
1       B
1       C
2       A
2       C
3       A
3       D
3       E

The result expected is -
name
1
2
3

Which would be a better option considering that tab1 would have thousands of
rows. No aggregation required, just the name column is required.

Both DISTINCT and Group By name gives me same result. Since they are two
different operations altogether i wanted to know given such a situation which
one would be better?

          Followup:

the answer is to use distinct.

the optimizer knows what to do in both cases.  but only distinct is semantically
correct.

      Rating: 4
Group By
      Data  December 06, 2002
Reviewer:  Harry
       from India
Hi Tom!
  Your information is very good. But i have to get a group by column along with
a non-grouped column too.
  Like i need to select product_code,dealer_code as grouped columns and distinct
shipment code to be compared within the inner loop.
  If i use group by all 3, i will get duplicate code for each dealer. I want
only distinct product code,dealer code and shipment code. The data is in 3
tables.

  Sql> Select distinct product_code,dealer_code,shipment_code from
product_master a,dealer_master b,ship_maaster c where a.product_code =
b.product_code and b.shipment_code = c.shipment_code and
 dealer_code = 'XX';

  This query works for a single dealer well. If i need to query for all the
dealers then i am not able group.
 like

Sql> Select distinct product_code,dealer_code,shipment_code from product_master
a,dealer_master b,ship_maaster c where a.product_code = b.product_code and
b.shipment_code = c.shipment_code and
 dealer_code like '%';

  This brings in duplicate entries. Can you give me a solution for this?
Thanks
 Harry

          Followup:

"Like i need to select product_code,dealer_code as grouped columns and distinct
shipment code to be compared within the inner loop."

makes no sense whatsoever to me.

I fail to see what "duplicate" entries you could be getting from the query with
distinct.

Tell me this -- if there is more then one shipment_code for each product_code,
dealer_code -- which "random" shipment_code do you want???

You'll need to use an example here with data -- not getting your point.

      Rating: 3
Need help on
      this one...  December 06, 2002
Reviewer:  A
      reader  from India
Tom,
   Thanks for earlier clarification-

I have yet another situation on hand.

DESC tab1;

a VARCHAR2(20), --- PK
NAME VARCHAR2(100),
salary NUMBER,
flg VARCHAR2(1),
address1 VARCHAR2(200),
address2 VARCHAR2(200));

The developer has coded as -
SELECT
 a,
 name,
 SUM(salary),
 flg,
 address1,
 address2
FROM
 tab1
GROUP BY
 a,
 name,
 flg,
 address1,
 address2;

This is what i propose.

SELECT
 a,
 MAX(name) name,
 SUM(salary),
 MAX(flg) flg,
 MAX(address1) address1,
 MAX(address2) address2
FROM
 tab1
GROUP BY
 a;

I have to justify why this piece of code needs to be changed to the second one.
The reasons i gave was less sort memory usage and hence better performance and
also easier to understand.

Both queries return same result. But, however i cannot approve the first one.
Please help me out on this. Am i doing the right thing here.

regards

          Followup:

if A is the primary key -- i propose this

SELECT
 a,
 name,
 salary,
 flg,
 address1,
 address2
FROM
 tab1

done -- same answer, less work.  if you group by primary key -- you get exactly
the same thing as if you DO NOT group by

      Rating:
      3
Ooops...sorry  December 06, 2002
Reviewer:
      A reader  from India
Tom,
    That was just an example query. Sorry, i kind of goofed up the whole thing.
Here it is again...Assuming that we join tab1 to a child table tab2 which has a
whole set of numerics which i need to aggregate on. For every row in tab1 i have
numerous records in tab2. So in that, what difference would it make if i group
by just the Primary Key alone or if i group by this and a whole lot of other
columns?

regards

          Followup:

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

Table created.

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

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t2 select * from
t2;

23655 rows created.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t2 select * from
t2;

47310 rows created.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t2 select * from
t2;

94620 rows created.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> update t2 set object_name = rownum;

189240 rows updated.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t1 add constraint t1_pk primary
key(object_id);

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t2 add constraint t2_pk primary
key(object_id,object_name);

Table altered.

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

Table analyzed.

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

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.object_id, a.object_name, a.owner,
a.created, sum(b.data_object_id)
  2    from t1 a, t2 b
  3   where a.object_id = b.object_id
  4   group by a.object_id, a.object_name, a.owner, a.created
  5  /

23654 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4467 Card=23654 Bytes=1513856)
   1    0   SORT (GROUP BY) (Cost=4467 Card=23654 Bytes=1513856)
   2    1     MERGE JOIN (Cost=1382 Card=189232 Bytes=12110848)
   3    2       SORT (JOIN) (Cost=203 Card=23654 Bytes=1111738)
   4    3         TABLE ACCESS (FULL) OF 'T1' (Cost=10 Card=23654 Bytes=1111738)
   5    2       SORT (JOIN) (Cost=1179 Card=189240 Bytes=3217080)
   6    5         TABLE ACCESS (FULL) OF 'T2' (Cost=75 Card=189240
Bytes=3217080)

Statistics
----------------------------------------------------------
          0  recursive calls
         49  db block gets
       2934  consistent gets
        977  physical reads
          0  redo size
    1778152  bytes sent via SQL*Net to client
     175361  bytes received via SQL*Net from client
       1578  SQL*Net roundtrips to/from client
          0  sorts (memory)
          3  sorts (disk)
      23654  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.object_id, max(a.object_name),
max(a.owner), max(a.created), sum(b.data_object_id)
  2    from t1 a, t2 b
  3   where a.object_id = b.object_id
  4   group by a.object_id
  5  /

23654 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1382 Card=23654 Bytes=1513856)
   1    0   SORT (GROUP BY NOSORT) (Cost=1382 Card=23654 Bytes=1513856)
   2    1     MERGE JOIN (Cost=1382 Card=189232 Bytes=12110848)
   3    2       SORT (JOIN) (Cost=203 Card=23654 Bytes=1111738)
   4    3         TABLE ACCESS (FULL) OF 'T1' (Cost=10 Card=23654 Bytes=1111738)
   5    2       SORT (JOIN) (Cost=1179 Card=189240 Bytes=3217080)
   6    5         TABLE ACCESS (FULL) OF 'T2' (Cost=75 Card=189240
Bytes=3217080)

Statistics
----------------------------------------------------------
          0  recursive calls
         47  db block gets
       2934  consistent gets
        734  physical reads
          0  redo size
    1778173  bytes sent via SQL*Net to client
     175361  bytes received via SQL*Net from client
       1578  SQL*Net roundtrips to/from client
          2  sorts (memory)
          2  sorts (disk)
      23654  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off

that shows they use the same memory but differing amounts of temp in different
ways (sorts memory/disk counts are different).

I suggest you stick with the way you have (group by c1, c2, c3, c4, ... )
instead of applying MAX()

reasoning:  it is semantically more meaningful to the developers looking at your
query 3 years from now.

      Rating: 4
Group By
      Month  May 23, 2003
Reviewer:  Rohit Gulati
       from Ambala(India)
Hi Tom
        I have a table containing the invoice number,Date adn other detail. If i
use group by clause on month then it gives results for every month. But my
problem is that i have to group the month from 8th of current month to the 7th
of next month i.e for April months it should show all the invoices from 8th
April to 7 may.
I want these results by single sql command.
will you please help me.
thanks & regards

          Followup:

group by trunc(date_column-7,'mm')

      Rating: 3
How to do this
      Group by  May 23, 2003
Reviewer:  Reader
       from US
I am having a problem.I have a SQL which is not giving the desired result.Can
you help me.

select
a.storeID,
a.STORE_NM,
SUM(decode(upper(a.MTH),'JAN',a.MTHQ,null)) ||'|'||
SUM(decode(upper(a.MTH),'FEB',a.MTHQ,null)) ||'|'||
SUM(decode(upper(a.MTH),'MAR',a.MTHQ,null))||chr(10)||
SUM(decode(upper(a.MTH),'JAN',a.FCST_AMT,null)) ||'|'||
SUM(decode(upper(a.MTH),'FEB',a.FCST_AMT,null)) ||'|'||
SUM(decode(upper(a.MTH),'MAR',a.FCST_AMT,null))||chr(10)||
SUM(decode(upper(a.MTH),'JAN',a.DSAR_FRD_AMT_PRIOR,null)) ||'|'||
SUM(decode(upper(a.MTH),'FEB',a.DSAR_FRD_AMT_PRIOR,null)) ||'|'||
SUM(decode(upper(a.MTH),'MAR',a.DSAR_FRD_AMT_PRIOR,null)) ||'|'||chr(10)||
SUM(decode(upper(a.MTH),'JAN',a.DSAR_FRD_AMT,null))||'|'||
SUM(decode(upper(a.MTH),'FEB',a.DSAR_FRD_AMT,null))||'|'||
SUM(decode(upper(a.MTH),'MAR',a.DSAR_FRD_AMT,null))||'|'||chr(10)||
SUM(decode(upper(b.CATG_ALT_CD),'ANL',b.ALLOC_AMT,null)) ||'|'||
SUM(decode(upper(b.CATG_ALT_CD),'SKC',b.ALLOC_AMT,null))||'|'||
SUM(decode(upper(b.CATG_ALT_CD),'CLD',b.ALLOC_AMT,null)) ||'|'||
SUM(decode(upper(b.CATG_ALT_CD),'DIA',b.ALLOC_AMT,null)) ||'|'||
SUM(decode(upper(b.CATG_ALT_CD),'OTH',b.ALLOC_AMT,null)) ||'|'||chr(10)||
SUM(decode(upper(b.CATG_ALT_CD),'ANL',b.PLAN_PMT,null)) ||'|'||
SUM(decode(upper(b.CATG_ALT_CD),'SKC',b.PLAN_PMT,null))||'|'||
SUM(decode(upper(b.CATG_ALT_CD),'CLD',b.PLAN_PMT,null)) ||'|'||
SUM(decode(upper(b.CATG_ALT_CD),'DIA',b.PLAN_PMT,null)) ||'|'||
SUM(decode(upper(b.CATG_ALT_CD),'OTH',b.PLAN_PMT,null)) ||'|'||chr(10)||
SUM(decode(upper(b.CATG_ALT_CD),'ANL',b.PRIOR_PMT_AMT,null)) ||'|'||
SUM(decode(upper(b.CATG_ALT_CD),'SKC',b.PRIOR_PMT_AMT,null))||'|'||
SUM(decode(upper(b.CATG_ALT_CD),'CLD',b.PRIOR_PMT_AMT,null)) ||'|'||
SUM(decode(upper(b.CATG_ALT_CD),'DIA',b.PRIOR_PMT_AMT,null)) ||'|'||
SUM(decode(upper(b.CATG_ALT_CD),'OTH',b.PRIOR_PMT_AMT,null)) ||'|'||chr(10)||
SUM(decode(upper(b.CATG_ALT_CD),'ANL',b.PMT_AMT,null)) ||'|'||
SUM(decode(upper(b.CATG_ALT_CD),'SKC',b.PMT_AMT,null))||'|'||
SUM(decode(upper(b.CATG_ALT_CD),'CLD',b.PMT_AMT,null)) ||'|'||
SUM(decode(upper(b.CATG_ALT_CD),'DIA',b.PMT_AMT,null)) ||'|'||
SUM(decode(upper(b.CATG_ALT_CD),'OTH',b.PMT_AMT,null)) ||'|'||
c.brd_alt_ds || '|' ||
to_char(c.event_start_dt,'yyyy-mm-dd') || '|' ||
to_char(c.price_amt,'999.99')
from     cso.VOLUME_QTRLY_TMP a,
cso.TRADE_QTRLY_TMP b,
cso.PROMO_QTRLY_TMP c
where     a.storeID in ( '007300')
and a.storeID =b.storeID
and a.storeID =c.storeID
group by a.storeID,a.STORE_NM,c.BRD_ALT_DS,c.EVENT_START_DT,c.PRICE_AMT

SQL> /

STORE STORE_NM
------ -----------------------------------
SUM(DECODE(UPPER(A.MTH),'JAN',A.MTHQ,NULL))||'|'||SUM(DECODE(UPPER(
--------------------------------------------------------------------------------

EVENT_STAR  PRICE_AMT
---------- ----------

007300 GROCERY STORE
||
510300|894000|1662000
1564097.16|1671413.52|2870744.28|
1020685.2|2299272.48|1888686.96|
3225000|378000|351000||154500|
5015520|102774|25455||120|
8832101.1|3443561.52|1222076.1||21888|
1719421.2|185466.96|47736||115.2|Product1
2003-01-01       3.94

007300 GROCERY STORE
||
510300|894000|1662000
1564097.16|1671413.52|2870744.28|
1020685.2|2299272.48|1888686.96|
3225000|378000|351000||154500|
5015520|102774|25455||120|
8832101.1|3443561.52|1222076.1||21888|
1719421.2|185466.96|47736||115.2|Product2
2003-02-01       3.65

007300 GROCERY STORE
||
510300|894000|1662000
1564097.16|1671413.52|2870744.28|
1020685.2|2299272.48|1888686.96|
3225000|378000|351000||154500|
5015520|102774|25455||120|
8832101.1|3443561.52|1222076.1||21888|
1719421.2|185466.96|47736||115.2|Product3
2003-02-22       6.64

007300 GROCERY STORE
||
510300|894000|1662000
1564097.16|1671413.52|2870744.28|
1020685.2|2299272.48|1888686.96|
3225000|378000|351000||154500|
5015520|102774|25455||120|
8832101.1|3443561.52|1222076.1||21888|
1719421.2|185466.96|47736||115.2|Product3
2003-01-01       9.58

007300 GROCERY STORE
||
510300|894000|1662000
1564097.16|1671413.52|2870744.28|
1020685.2|2299272.48|1888686.96|
3225000|378000|351000||154500|
5015520|102774|25455||120|
8832101.1|3443561.52|1222076.1||21888|
1719421.2|185466.96|47736||115.2|Product4
2003-01-01             12.99

Will like to get this:

007300 GROCERY STORE
||
510300|894000|1662000
1564097.16|1671413.52|2870744.28|
1020685.2|2299272.48|1888686.96|
3225000|378000|351000||154500|
5015520|102774|25455||120|
8832101.1|3443561.52|1222076.1||21888|
1719421.2|185466.96|47736||115.2|Product1|2003-01-01||| 3.94
1719421.2|185466.96|47736||115.2|Product2|2003-02-01||| 3.65
1719421.2|185466.96|47736||115.2|Product3|2003-02-22||| 6.64
1719421.2|185466.96|47736||115.2|Product3|2003-01-01||| 9.58
1719421.2|185466.96|47736||115.2|Product4|2003-01-01|||12.99

          Followup:

looks like you want to write a report then -- in master/detail style.

      Rating:
      2
Yes  May 23, 2003
Reviewer:  Reader
       from US
Thanks for the prompt reply.Actually this is currently going to be an Adhoc
report and maybe finally (with other requirements) will become a standard report
that users/management will pull into Excel/Access or any other tool.

          Followup:

great, so write a report -- not everything is doable in a single query every
time without some procedural workings on it.

      Rating:
      4
Distinct  July 31, 2003
Reviewer:
      arc  from SIN
Hi,
I have a query to retrive 1 Million records.
I need your advise in elminating duplicates.
Which is better either distinct or group by?

Q1)
SELECT    DISTINCT
    CIF_KEY,
    BANK_CODE ,
    BRANCH_CODE ,
    ACCOUNT_NUMBER,
    ISO_CURRENCY_CD,
    EFF_TRANSFER_RATE ,
    GL_CORP_CD,
    GL_ACCOUNT_ID,
    PRODUCT_COA_ID,
    TP_COA_ID
FROM    FEM_COMMERCIAL_LOANS
WHERE     AS_OF_DATE = (SELECT AS_OF_DATE FROM TETL_ASOFDATE);

Q2)
SELECT    CIF_KEY,
    BANK_CODE ,
    BRANCH_CODE ,
    ACCOUNT_NUMBER,
    ISO_CURRENCY_CD,
    EFF_TRANSFER_RATE ,
    GL_CORP_CD,
    GL_ACCOUNT_ID,
    PRODUCT_COA_ID,
    TP_COA_ID
FROM    FEM_COMMERCIAL_LOANS
WHERE     AS_OF_DATE = (SELECT AS_OF_DATE FROM TETL_ASOFDATE)
GROUP BY CIF_KEY,
    BANK_CODE ,
    BRANCH_CODE ,
    ACCOUNT_NUMBER,
    ISO_CURRENCY_CD,
    EFF_TRANSFER_RATE ,
    GL_CORP_CD,
    GL_ACCOUNT_ID,
    PRODUCT_COA_ID,
    TP_COA_ID;

          Followup:

benchmark it.

in either case, it'll only take a couple of seconds/minutes to retrieve both
result sets so you can easily test both and see what happens.  Just

alter session set timed_statistics=true;
alter session set sql_trace=true;
set arraysize 100
set termout off
run query 1
run query 2
set termout on
exit

run that in plus and then use tkprof

      Rating: 4
A
      grouping  December 05, 2003
Reviewer:  A
      reader
Hello Tom!
Can you please suggest how to create a query?

For table:
create table t (d1 date, d2 date, v number)
/

And data:
insert into t values (sysdate - 20, sysdate - 15, 1);
insert into t values (sysdate - 15, sysdate - 5, 1);
insert into t values (sysdate - 5, sysdate - 1, 2);
insert into t values (sysdate - 1, sysdate + 15, 1);
insert into t values (sysdate + 15, sysdate + 25, 1);
insert into t values (sysdate + 25, sysdate + 45, 3);
insert into t values (sysdate + 45, sysdate + 55, 3);
insert into t values (sysdate + 55, sysdate + 75, 3);

How to get min and max date inside groups by value of column v, that are
continuous (not broken by another value of v).
I.e. from
15.11.2003  20.11.2003   1
20.11.2003  30.11.2003   1
30.11.2003  04.12.2003   2
04.12.2003  20.12.2003   1
20.12.2003  30.12.2003   1
30.12.2003  19.01.2004   3
19.01.2004  29.01.2004   3
29.01.2004  18.02.2004   3

get
15.11.2003 30.11.2003 1
30.11.2003 04.12.2003 2
04.12.2003 30.12.2003 1
30.12.2003 18.02.2004 3

Thank you in advance!

          Followup:

ops$tkyte@ORA9IR2> select min(d1), max(d2), v
  2    from (
  3  select d1, d2, v, max(rn) over ( order by d1 ) max_rn
  4    from (
  5  select t.*,
  6         case when (lag(v) over (order by d1) <> v)
  7              then row_number() over (order by d1)
  8          end rn
  9    from t
 10         )
 11         )
 12   group by v, max_rn
 13   order by 1
 14  /

MIN(D1)   MAX(D2)            V
--------- --------- ----------
15-NOV-03 30-NOV-03          1
30-NOV-03 04-DEC-03          2
04-DEC-03 30-DEC-03          1
30-DEC-03 18-FEB-04          3

run the inline views one by one by one -- wrapping the innermost with the next
layer and so on to see what I did.

      Rating: 5
Thank you,
      Tom!!  December 05, 2003
Reviewer:  A reader

      Rating: 4
Interesting; Is
      there a way to make grouping perform faster  December 10,
      2003
Reviewer:  Tom  from MA USA
Group by is a great tool.  We have a consistent problem with them here in that
we construct queries that do the selective part well (i.e. use the expected
indexes and return the data quickly) but when we aggregate using GROUP BY, the
queries "go away and return hours later."  For example, one query took 4.5
seconds to select 350K records, however when we add some AVG functions and GROUP
BY the query flys off to neverneverland and returns 2 plus hours later.  What is
going on and how can we improve the performance?

          Followup:

it'll be a function of your SORT_AREA_SIZE if you are using 8i and before --
pga_aggregate_target | sort_area_size in 9i and up

so -- what are you (version)
and what are they (init.oras)

while this does not show "2 hours" (a TKPROF would be really really handy) it
does show that sort area size can affect this greatly

big_table@ORA9IR2> alter session set workarea_size_policy = manual;

Session altered.

big_table@ORA9IR2> alter session set sort_area_size = 65536;

Session altered.

big_table@ORA9IR2> set arraysize 100
big_table@ORA9IR2> set timing on
big_table@ORA9IR2> set autotrace traceonly
big_table@ORA9IR2> select * from big_table where rownum <= 350000;

350000 rows selected.

Elapsed: 00:00:04.91

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1379 Card=350000
Bytes=35000000)
   1    0   COUNT (STOPKEY)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1379 Card=999620
Bytes=99962000)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       8469  consistent gets
       4456  physical reads
          0  redo size
   24242394  bytes sent via SQL*Net to client
      38988  bytes received via SQL*Net from client
       3501  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     350000  rows processed

big_table@ORA9IR2> select id, object_name, owner, count(*), avg(data_object_id),
min(last_ddl_time), max(created)
  2    from (select * from big_table where rownum <= 350000 )
  3   group by id, object_name, owner;

350000 rows selected.

Elapsed: 00:00:43.40

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=15844 Card=350000
Bytes=20650000)
   1    0   SORT (GROUP BY) (Cost=15844 Card=350000 Bytes=20650000)
   2    1     VIEW (Cost=1379 Card=350000 Bytes=20650000)
   3    2       COUNT (STOPKEY)
   4    3         TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1379 Card=999620
Bytes=58977580)

Statistics
----------------------------------------------------------
          0  recursive calls
        689  db block gets
       5021  consistent gets
      22901  physical reads
          0  redo size
   16534945  bytes sent via SQL*Net to client
      38988  bytes received via SQL*Net from client
       3501  SQL*Net roundtrips to/from client
          0  sorts (memory)
          1  sorts (disk)
     350000  rows processed

big_table@ORA9IR2> alter session set sort_area_size = 6553600;

Session altered.

Elapsed: 00:00:00.01
big_table@ORA9IR2> select id, object_name, owner, count(*), avg(data_object_id),
min(last_ddl_time), max(created)
  2    from (select * from big_table where rownum <= 350000 )
  3   group by id, object_name, owner;

350000 rows selected.

Elapsed: 00:00:05.05

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4755 Card=350000
Bytes=20650000)
   1    0   SORT (GROUP BY) (Cost=4755 Card=350000 Bytes=20650000)
   2    1     VIEW (Cost=1379 Card=350000 Bytes=20650000)
   3    2       COUNT (STOPKEY)
   4    3         TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1379 Card=999620
Bytes=58977580)

Statistics
----------------------------------------------------------
          0  recursive calls
          6  db block gets
       5021  consistent gets
       8080  physical reads
          0  redo size
   16534945  bytes sent via SQL*Net to client
      38988  bytes received via SQL*Net from client
       3501  SQL*Net roundtrips to/from client
          0  sorts (memory)
          1  sorts (disk)
     350000  rows processed

big_table@ORA9IR2> set autotrace off
big_table@ORA9IR2> set timing off
big_table@ORA9IR2>

      Rating: 4
Thank
      you  December 12, 2003
Reviewer:  Tom
       from MA USA
This helps.  Thank you.

December 10, 2003 trinh -- Thanks for the question regarding
      "transportable tablespaces", version 817

      You Asked
We are looking into the transportable tablespaces between the two databases.
My dba concerns the data integrity of the method:
He is worry about the datafiles to be moved do not have a last committed data.
For instance,
At the time 0.0 , a transaction was committed, and the dirty blocks might be
still in the db buffer.
At the time 0.1, you issued: alter tablespace the-tbs read only. Does Oracle
force dbwr to flush the dirty blocks to the disks as the results of the
read-only made?
If not, then datafiles to be move have no the data which committed the time 0.0

      and we said...
making a tablespace read only MUST (think about it) flush the blocks.  Once that
command is executed, the files are -- read only.  If we did not flush the
blocks, we could NEVER flush the blocks.  Read only wouldn't work at all if it
did not do that.

There is nothing to be afraid of as far as that goes with transportable
tablespace.
Review & Followup

      Rating:
      3
  November 27, 2002
Reviewer:
      A reader
can the name of the datafile be changed while transporting?
Meaning, I hav datafile as abc.dbf on source server.
I want the name of the datafile to be def.dbf of the target server. can this be
done?
If so how?

      Rating:
      3
  November 27, 2002
Reviewer:
      A reader
also,
Here is teh scenario:
I have tables, packages on db1.
db2 is a new database. I want to put in only the tables
on to db2 from db1. The tables are huge and are in gigs (10 gig). Export is
taking a lot of time. The tables contain LOb and the lobs (and lob's indexes)
are  in a different tablespace(lob_tablepace) than the data (data_tablespace )
and the index (index_tablespace).
The same data_tablespace holds data for other tables too.And i have other data
and index tablespaces(data_tablespace2, index_tablespace2) which contain many
tables.

I also need to change the name of the datafile on db2(part of our standards). My
question is, when i transport the tablespace, will the packages on db 1 also be
transferred?(i dont want to do that..i want just tables and its data)
Can transportable table space be used for this?
How?

          Followup:

Only the contents of the tablespace (and if desired triggers constraints) get
moved.

the ENTIRE tablespace gets moved, the ENTIRE one.

Here is an example showing file renaming is part of the outcome -- the files
need not have the same names:

ops$ora920@ORA920.US.ORACLE.COM> create tablespace tts_test
  2  datafile '/tmp/tts_test1.dbf' size 2m reuse autoextend off
  3  uniform size 256k
  4  /

Tablespace created.

ops$ora920@ORA920.US.ORACLE.COM> alter tablespace tts_test
  2  add datafile '/tmp/tts_test2.dbf' size 2m reuse autoextend off
  3  /

Tablespace altered.

ops$ora920@ORA920.US.ORACLE.COM>
ops$ora920@ORA920.US.ORACLE.COM> create table t tablespace tts_test
  2  as
  3  select * from all_objects;

Table created.

ops$ora920@ORA920.US.ORACLE.COM> select count(*) from t;

  COUNT(*)
----------
     29364

ops$ora920@ORA920.US.ORACLE.COM> exec show_space( 'T' )
Old fashioned
Free Blocks.............................0
Total Blocks............................448
Total Bytes.............................3670016
Total MBytes............................3
Unused Blocks...........................47
Unused Bytes............................385024
Last Used Ext FileId....................12
Last Used Ext BlockId...................201
Last Used Block.........................17

PL/SQL procedure successfully completed.

ops$ora920@ORA920.US.ORACLE.COM>
ops$ora920@ORA920.US.ORACLE.COM>
ops$ora920@ORA920.US.ORACLE.COM> alter tablespace tts_test READ ONLY;

Tablespace altered.

ops$ora920@ORA920.US.ORACLE.COM>
ops$ora920@ORA920.US.ORACLE.COM> host exp transport_tablespace=y
tablespaces=tts_test

Export: Release 9.2.0.1.0 - Production on Wed Nov 27 13:00:05 2002

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Username: / as sysdba

Connected to: Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
Export done in WE8ISO8859P1 character set and AL16UTF16 NCHAR character set
Note: table data (rows) will not be exported
About to export transportable tablespace metadata...
For tablespace TTS_TEST ...
. exporting cluster definitions
. exporting table definitions
. . exporting table                              T
. exporting referential integrity constraints
. exporting triggers
. end transportable tablespace metadata export
Export terminated successfully without warnings.

ops$ora920@ORA920.US.ORACLE.COM> drop tablespace tts_test including contents;

Tablespace dropped.

ops$ora920@ORA920.US.ORACLE.COM>
ops$ora920@ORA920.US.ORACLE.COM> select count(*) from t;
select count(*) from t
                     *
ERROR at line 1:
ORA-00942: table or view does not exist

ops$ora920@ORA920.US.ORACLE.COM> exec show_space( 'T' )
BEGIN show_space( 'T' ); END;

*
ERROR at line 1:
ORA-00942: table or view does not exist
ORA-06512: at "SYS.DBMS_SPACE", line 59
ORA-06512: at "SYS.SHOW_SPACE", line 44
ORA-06512: at line 1

ops$ora920@ORA920.US.ORACLE.COM>
ops$ora920@ORA920.US.ORACLE.COM> host mv /tmp/tts_test1.dbf
/tmp/better_name1.dbf

ops$ora920@ORA920.US.ORACLE.COM> host mv /tmp/tts_test2.dbf
/tmp/better_name2.dbf

ops$ora920@ORA920.US.ORACLE.COM>
ops$ora920@ORA920.US.ORACLE.COM> host imp transport_tablespace=y
'datafiles=(/tmp/better_name1.dbf,/tmp/better_name2.dbf)'

Import: Release 9.2.0.1.0 - Production on Wed Nov 27 13:00:33 2002

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Username: / as sysdba

Connected to: Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

Export file created by EXPORT:V09.02.00 via conventional path
About to import transportable tablespace(s) metadata...
import done in WE8ISO8859P1 character set and AL16UTF16 NCHAR character set
. importing SYS's objects into SYS
. importing OPS$ORA920's objects into OPS$ORA920
. . importing table                            "T"
Import terminated successfully without warnings.

ops$ora920@ORA920.US.ORACLE.COM>
ops$ora920@ORA920.US.ORACLE.COM>
ops$ora920@ORA920.US.ORACLE.COM>
ops$ora920@ORA920.US.ORACLE.COM>
ops$ora920@ORA920.US.ORACLE.COM> select count(*) from t;

  COUNT(*)
----------
     29364

ops$ora920@ORA920.US.ORACLE.COM> exec show_space( 'T' )
BEGIN show_space( 'T' ); END;

*
ERROR at line 1:
ORA-00372: file 11 cannot be modified at this time
ORA-01110: data file 11: '/tmp/better_name2.dbf'  <<== shows new name
ORA-06512: at "SYS.DBMS_SPACE", line 74
ORA-06512: at "SYS.SHOW_SPACE", line 34
ORA-06512: at line 1

ops$ora920@ORA920.US.ORACLE.COM>
ops$ora920@ORA920.US.ORACLE.COM> alter tablespace tts_test read write;

Tablespace altered.

ops$ora920@ORA920.US.ORACLE.COM> exec show_space( 'T' )
Old fashioned
Free Blocks.............................0
Total Blocks............................448
Total Bytes.............................3670016
Total MBytes............................3
Unused Blocks...........................47
Unused Bytes............................385024
Last Used Ext FileId....................11
Last Used Ext BlockId...................201
Last Used Block.........................17

PL/SQL procedure successfully completed.

ops$ora920@ORA920.US.ORACLE.COM>

      Rating:
      5
John  November 27, 2002
Reviewer:
      John  from Pasadena, CA
In our server consolidation prohject, we need to move the source (dictionary
managed) tablspaces from a 8.1.6 system to a target 8.1.7.4 system.  Can
transportable tablespace be used when the oracle version is not exact the same?
Do I have to upgrade the source system to 8.1.7.4 first?
If I want the target system tablespaces to be LMT. Do I need to transport the
source tablespaces as dictionary managed ones first and then convert them to
LMT?

          Followup:

You will need to test it -- but barring any "issues", it should work.

I don't like migrating DMT's to LMT's, you haven't fixed the underlying issue.
I would be more prone to

o create empty LMTs
o alter table move to LMT tblspace
o alter index rebuild to LMT tblspace
o drop empty DMTs

      Rating:
      3
TTS  March 03, 2003
Reviewer:
      reader  from USA
I am implementing a new process using TTS, all works well, except that the
importing of primary schema about 20G takes 1.5 hrs.

Is there something I am missing, any way to tune this?

Thank you

          Followup:

how many objects are we talking about?

can you run sql_trace on this? (do you know how to enable sql_trace from another
session, short of that you can set it at the instance level)

      Rating:
      3
TTS  March 04, 2003
Reviewer:
      reader  from USA
Tables 450
constraints 450
indexes 500
triggers 93

No i have not tried sql_trace yet, I did notice fast full scans on the 11million
row table.

Thank you.

          Followup:

how are you noticing "fast full scans"?? -- which are things done to indexes.

      Rating:
      3
TTS  March 05, 2003
Reviewer:
      reader  from USA
I noticed it via OEM.

          Followup:

what did you notice -- I'm saying "fast full scans" = index, you are talking
"table".

use sql trace, that'll get to the bottom of it.  guis just get in the way.
tkprof is the source of historical truth, it'll tell you exactly what is going
on.

      Rating: 3
Internal
      Mechanics of TTS  March 13, 2003
Reviewer:
      reader  from USA
how does the TTS internals work. how does it create indexes, constraints,
triggers etc.

Tkprof did not show any red flags. I am using TTS for indexes and tables 10G
each.

Thanks.

          Followup:

it doesn't not CREATE them, it just inserts them into the data dictionary.  They
are known to be

o OK
o already in existence

what did the tkprof show?  do you have the last two sections -- the totals?

      Rating: 3
Internals of
      TTS  March 17, 2003
Reviewer:  reader
       from USA
Tom,
Here are the totals. Also I see the following statements:
1. Alter table
2. Create index
3. Create trigger

Why are the above being created and not being inserted in the DD ?

Thanks.
********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse     6760     70.14      70.22         10         71       1844           0
Execute   6760    104.02     123.97       1186       1572      21567        3962
Fetch        6      0.17       0.28         13       6879         12           5
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    13526    174.33     194.47       1209       8522      23423        3967

Misses in library cache during parse: 5740

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse    33561     23.03      23.29         14        335         17           0
Execute  56657     83.72      84.40        500      65175     196178       32765
Fetch    49186   5219.67    5441.13    2310783  147040514      29489       31112
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   139404   5326.42    5548.82    2311297  147106024     225684       63877

Misses in library cache during parse: 327
Misses in library cache during execute: 4

10677  user  SQL statements in session.
29644  internal SQL statements in session.
40321  SQL statements in session.
    0  statements EXPLAINed in this session.
********************************************************************************
Trace file: ora_27002_brmaxtrn.trc
Trace file compatibility: 8.00.04
Sort options: fchela
       1  session in tracefile.
   10677  user  SQL statements in trace file.
   29644  internal SQL statements in trace file.
   40321  SQL statements in trace file.
    6089  unique SQL statements in trace file.
  353475  lines in trace file.

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse     6760     70.14      70.22         10         71       1844           0
Execute   6760    104.02     123.97       1186       1572      21567        3962
Fetch        6      0.17       0.28         13       6879         12           5
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    13526    174.33     194.47       1209       8522      23423        3967

Misses in library cache during parse: 5740

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse    33561     23.03      23.29         14        335         17           0
Execute  56657     83.72      84.40        500      65175     196178       32765
Fetch    49186   5219.67    5441.13    2310783  147040514      29489       31112
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   139404   5326.42    5548.82    2311297  147106024     225684       63877

Misses in library cache during parse: 327
Misses in library cache during execute: 4

10677  user  SQL statements in session.
29644  internal SQL statements in session.
40321  SQL statements in session.
    0  statements EXPLAINed in this session.
********************************************************************************
Trace file: ora_27002_brmaxtrn.trc
Trace file compatibility: 8.00.04
Sort options: fchela
       1  session in tracefile.
   10677  user  SQL statements in trace file.
   29644  internal SQL statements in trace file.
   40321  SQL statements in trace file.
    6089  unique SQL statements in trace file.
  353475  lines in trace file.

          Followup:

let me see your EXP and IMP commands you used.

      Rating: 3
Internals of
      TTS  March 17, 2003
Reviewer:  reader
       from USA
*************** EXPORT ****************
TRANSPORT_TABLESPACE=Y
DATAFILES= '/maxdb2/brmaxtrn/maxdts01.dbf',
           '/maxdb2/brmaxtrn/maxdts02.dbf',
           '/maxdb2/brmaxtrn/maxdts03.dbf',
           '/maxdb2/brmaxtrn/maxdts04.dbf',
           '/maxdb2/brmaxtrn/maxdts05.dbf',
           '/maxdb2/brmaxtrn/maxdts06.dbf',
           '/maxdb2/brmaxtrn/maxits01.dbf',
           '/maxdb2/brmaxtrn/maxits02.dbf',
           '/maxdb2/brmaxtrn/maxits03.dbf',
           '/maxdb2/brmaxtrn/maxits04.dbf',
           '/maxdb2/brmaxtrn/maxits05.dbf'
TABLESPACES=maxdts,maxits
GRANTS=n
FILE=expdat.dmp

************* IMPORT *************
imp transport_tablespace=y
datafiles= '/maxdb2/brmaxtrn/maxdts01.dbf'
           '/maxdb2/brmaxtrn/maxdts02.dbf'
           '/maxdb2/brmaxtrn/maxdts03.dbf'
           '/maxdb2/brmaxtrn/maxdts04.dbf'
           '/maxdb2/brmaxtrn/maxdts05.dbf'
           '/maxdb2/brmaxtrn/maxdts06.dbf'
           '/maxdb2/brmaxtrn/maxits01.dbf'
           '/maxdb2/brmaxtrn/maxits02.dbf'
           '/maxdb2/brmaxtrn/maxits03.dbf'
           '/maxdb2/brmaxtrn/maxits04.dbf'
           '/maxdb2/brmaxtrn/maxits05.dbf'
tablespaces=maxdts,maxits
tts_owners=H1
fromuser=H1
touser=H1
grants=n
file=expdat.dmp

      Rating: 3
Just
      wondering-TTS performance  March 19, 2003

Reviewer:  reader  from USA
If you were able to come up some pointers....?

Thank you

          Followup:

nope, tell you what -- how big is your TRC file when it is compressed?  is is
"emailable"?

      Rating:
      3
301k  March 20, 2003
Reviewer:
      reader  from USA
301k with winzip

          Followup:

ok, email it to me, tkyte@oracle.com

      Rating: 2
TTS
      Internals  March 25, 2003
Reviewer:  reader
       from USA
Tom,
Can you please cofirm the following understanding regarding TTS:
1. Only the metadata is imported.
2. No create table/index command are executed.
3. No index fast full scans/table scans sholud take place.

thanks

          Followup:

correct.

      Rating:
      2
Parfile  March 25, 2003
Reviewer:
      reader  from USA
Tom,
Do you have the parfile for the TTS example you gave for table 'T' in this post
?

thanks

          Followup:

i didn't use one?

      Rating: 2
No idea
      why?  March 26, 2003
Reviewer:  reader
       from USA
I have no idea why TTS import is doing fast full scans/full scans.This happens
after most of the tables are added with data-I can query the tables. I am not
even analyzing..!

any ideas?

thanks.

      Rating: 2
Ok it is
      because  March 26, 2003
Reviewer:  reader
       from USA
of the constraints.

      Rating:
      5
  May 22, 2003
Reviewer:  Gautam
      Arora  from NY, USA

      Rating: 3
What 's the
      usage of TTS_OWNERS parameter  August 25, 2003

Reviewer:  Jianhui  from DC
Hi Tom,
Quoted from Oracle9i document

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96521/tspaces.htm#5757
QUOTE>>
"
TTS_OWNERS lists all users who own data in the tablespace set.

When you specify TTS_OWNERS, the user names are compared to those in the export
file. Import returns an error if there is any mismatch. Otherwise, owner names
are extracted from the export file.

"

Why do we need this parameter since the owners' names must match the names in
the dump file(s)? why don't we just let IMP to extract them from the dump file?
If my tablespace sets have 3 users' data (u1,u2,u3), and i only want to import 2
of them into a different new user schema, for example
FROMUSER=(u2,u3) TOUSER=(newu1, newu1)

I can NOT figure out the usage of the TTS_OWNERS. Could you tell me when do we
need to specify TTS_OWNERS.

          Followup:

think of it like data validation.

I'm expecting a tts with data from bob, mary and sue.  If it is not data from
bob, mary and sue -- I don't want it.

      Rating:
      5
Statistics  August 26, 2003
Reviewer:
      Martin Guillen  from Buenos Aires, Argentina.
Hi Tom:
What about statistics?
Are they imported with tts import?

          Followup:

yes it does:

ops$tkyte@ORA920> create tablespace tts datafile '/tmp/tts.dbf' size 1m
  2  /
Tablespace created.

ops$tkyte@ORA920> grant dba to tts_user identified by tts_user
  2  /
Grant succeeded.

ops$tkyte@ORA920> alter user tts_user default tablespace tts
  2  /
User altered.

ops$tkyte@ORA920> @connect tts_user/tts_user

tts_user@ORA920> create table emp as select * from scott.emp;
Table created.

tts_user@ORA920> create table dept as select * from scott.dept;
Table created.

tts_user@ORA920> create index emp_idx on emp(empno);
Index created.

tts_user@ORA920> create index dept_idx on dept(deptno);
Index created.

tts_user@ORA920> analyze table emp compute statistics;
Table analyzed.

tts_user@ORA920> analyze table dept compute statistics;
Table analyzed.

tts_user@ORA920> select table_name, num_rows from user_tables
  2  union all
  3  select index_name, num_rows from user_indexes
  4  union all
  5  select table_name, count(*) from user_tab_histograms group by table_name;

TABLE_NAME                       NUM_ROWS
------------------------------ ----------
EMP                                    14
DEPT                                    4
EMP_IDX                                14
DEPT_IDX                                4
DEPT                                    6
EMP                                    16

6 rows selected.

tts_user@ORA920> alter tablespace tts read only;
Tablespace altered.

tts_user@ORA920> !exp transport_tablespace=y tablespaces=tts

Export: Release 9.2.0.3.0 - Production on Tue Aug 26 10:47:51 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Username: / as sysdba

Connected to: Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production
Export done in WE8ISO8859P1 character set and AL16UTF16 NCHAR character set
Note: table data (rows) will not be exported
About to export transportable tablespace metadata...
For tablespace TTS ...
. exporting cluster definitions
. exporting table definitions
. . exporting table                            EMP
. . exporting table                           DEPT
. exporting referential integrity constraints
. exporting triggers
. end transportable tablespace metadata export
Export terminated successfully without warnings.

tts_user@ORA920> !cp /tmp/tts.dbf /tmp/tts2.dbf

tts_user@ORA920> alter tablespace tts read write;
Tablespace altered.

tts_user@ORA920> drop tablespace tts including contents;
Tablespace dropped.

tts_user@ORA920> select table_name, num_rows from user_tables
  2  union all
  3  select index_name, num_rows from user_indexes
  4  union all
  5  select table_name, count(*) from user_tab_histograms group by table_name;

no rows selected

tts_user@ORA920> !rm /tmp/tts.dbf
tts_user@ORA920> !cp /tmp/tts2.dbf tts.dbf

tts_user@ORA920> !imp transport_tablespace=y datafiles=/tmp/tts.dbf

Import: Release 9.2.0.3.0 - Production on Tue Aug 26 10:50:01 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Username: / as sysdba

Connected to: Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production

Export file created by EXPORT:V09.02.00 via conventional path
About to import transportable tablespace(s) metadata...
import done in WE8ISO8859P1 character set and AL16UTF16 NCHAR character set
. importing SYS's objects into SYS
. importing TTS_USER's objects into TTS_USER
. . importing table                          "EMP"
. . importing table                         "DEPT"
Import terminated successfully without warnings.

tts_user@ORA920> alter tablespace tts read write;
Tablespace altered.

tts_user@ORA920> select table_name, num_rows from user_tables
  2  union all
  3  select index_name, num_rows from user_indexes
  4  union all
  5  select table_name, count(*) from user_tab_histograms group by table_name;

TABLE_NAME                       NUM_ROWS
------------------------------ ----------
DEPT                                    4
EMP                                    14
EMP_IDX                                14
DEPT_IDX                                4
DEPT                                    6
EMP                                    16

6 rows selected.

they are back...

      Rating: 5
fantastic
      demo  August 27, 2003
Reviewer:  ashraf

      Rating: 5
Database
      migration transportable tablespaces  December 03, 2003

Reviewer:  Bart  from Las Vegas, NV
In one of your followups to this question, you mention that you prefer not to
convert dictionary managed TS to locally managed TS.  I agree, but in migrating
an entire database from 8i to 9i using Oracle's migration tools, I end up with a
9i database with a dictionary managed system tablespace.  The only option seems
to be to convert the system TS if I want it locally managed.  Is there anything
wrong with a "converted" system TS?

Also, can you use transportable tablespaces for database migration? You could
create a "starter" database directly with 9i (with everything LM), then
transport the tablespaces from the 8i instance.  Am I missing something (like
the packages stored in the 8i system TS)?

          Followup:

the other option is to create a new empty database with just system,undo and
temp (system as LMT) and transport over every thing.

Other then "you might have things that don't transport for whatever reason",
it's a workable solution -- something you can easily test for example.

      Rating: 4
Database
      migration with transportable tablespaces  December 08, 2003

Reviewer:  Bart  from Las Vegas, NV
You mentioned that you don't like to convert from DMT to LMT in this thread.  Is
it OK to convert SYSTEM?  Otherwise, it looks like the only options for
migration are import, transportable tablespaces (if it works), or leaving SYSTEM
dictionary managed.

          Followup:

i've just left system be.

else, yes, transporting is an option.

otherwise, you are left with a system that is basically a DMT masquerading as an
LMT since the existing extents are all just wierd sizes and shapes.

      Rating: 4
My
      scenario  December 10, 2003
Reviewer:
      Prasad  from UK
hi Tom,

i've a little different scnerio. let me explain that.

i have a schema in my production database having 3 huge NON-PARTITIONED tables.
the combined rows are approx 700 million, that comes upto 90 Gb of data.

now we have decided to move out this schema into a NEW database.
in this new instance the 3 tables will now be PARTITIONED.
and we would be then migrating the data.

now my question is what is the best/optimized way to carry out this operation?

can i use Transport_tablespace option?
OR i have to use export/import utility (including rows)??
but this exp/imp will take approx 30 Hrs.
whereas transport_tablespace wil allow me simply copying of datafiles  followed
by exp/imp of metadata.

i'm on Oracle 8.1.7.4!

but my SOURCE tables are non-partitioned & DESTNATION tables will be
partitioned.

please help me in this.

many thanks,

          Followup:

In order to take the data you have and partition it will require a dump and
load.

transporting would move it "as is".

you can:

a) use sqlplus copy from OLD into newly partitioned (in parallel -- do many
tables at the same time)

b) use dblinks to insert /*+ append */ into new select * from old@remote

c) unload the data to a flat file (see
http://asktom.oracle.com/~tkyte/flat
for tools) and direct path load it
with sqlldr

for example in addition to export/import.

      Rating: 3
COPY or
      DB-LINK  December 10, 2003
Reviewer:  Prasad
       from UK
Dear Tom,

Thanks for your prompt reply.
performance wise "copy" command is better or using DB-LINK is better??
i'm using this script for with a db-link.
--------------------------------------------------------
declare
incrmnt_var number := 200207030000000;
begin
dbms_output.put_line('Start ---> ' || to_char(sysdate, 'yyyymmdd hh24:miss'));
while incrmnt_var <= 200207032000000
loop
    insert /*+ append parallel(inca_nia_1_prasad,4) */  into inca_nia_1_prasad
    select /*+ parallel(remote_tab,8)*/ *
    from uhdb.inca_nia_1@UHDB_TO_LLU_PRASAD remote_tab
    where remote_tab.id_acp_yearno = 200203
    and remote_tab.uhdb_key >= incrmnt_var
    and remote_tab.uhdb_key < incrmnt_var+1000000;
    commit;
    incrmnt_var := incrmnt_var+1000000;
end loop;
dbms_output.put_line('End ---> ' || to_char(sysdate, 'yyyymmdd hh24:miss'));
end;
---------------------------------------------------------
so i'll be commiting after say 100000 rows( i can change this depending upon my
RBS size later). Also i can fire many such queries in parallel to load my
DESTINATION table (which is having 18 partitions). So in your opinion
is this approach ok or using copy is advantageous.
and what options can i provide for a better/fast "copy"  command.

Many thanks again,
            Prasad

          Followup:

just do it in ONE INSERT and size RBS big enough, you'll never ever never never
be sorry you did (only that you did not)

the parallel query with the where clause is sort of self defeating the way you
have it.  too small a result set.

(benchmark which works best for you -- dblink or copy)

      Rating: 4
comments to
      prasad question. exchange partition will help.   December
      10, 2003
Reviewer:  Thiru Pandian  from USA

If i understand your question correctly,
DB1 = Tables without partition
DB2 = Tables has partition.

I hope structure(list of columns) of both table and table with partition same.

Just two days  we did similar kind.  We did following,

DB1:  Individual tables
DB2: these tables need to go to partition

We made tablespace read only.
took export of tts
imported in production
we got all tables.
 we exchanged these tables with partition tables.
here is how.

exp userid=\"sys as sysdba\" file=/oracle/expimp/tts_test/exp_tts_tst_psy.dmp lo
g=/oracle/expimp/tts_test/exp_tts_tst_psy.log transport_tablespace=y tablespaces
=tst_psy

imp userid=\"sys as sysdba\" file=mcipsydata_tts.dmp log=mcipsydata_tts.log tran
sport_tablespace=y datafiles= /dev/vx/rdsk/dbase01dg/vinp_mcipsydata01, \
/dev/vx/rdsk/dbase01dg/vinp_mcipsydata02, \
/dev/vx/rdsk/dbase01dg/vinp_mcipsydata03, \
/dev/vx/rdsk/dbase01dg/vinp_mcipsydata04, \
/dev/vx/rdsk/dbase01dg/vinp_mcipsydata05, \
/dev/vx/rdsk/dbase01dg/vinp_mcipsydata06, \
/dev/vx/rdsk/dbase01dg/vinp_mcipsydata07, \
/dev/vx/rdsk/dbase01dg/vinp_mcipsydata08

spool exch_part.lst
timing start t1
set echo on
set timing on
set time on
alter table mciload.item_references
exchange partition part1 with table mciload.psy_item_references;
timing stop t1

-Thiru Pandian.

      Rating: 4
Comments to
      Thiru's case  December 11, 2003
Reviewer:
      Prasad  from UK
Hi,

Thanks for the reply.
But I guess you have read me wrong. I have 1 HUGE table in DB1 which I need to
PARTITION(18) in DB2.
So how can I do "exchange partition" on this single table?

I have 3 such UNPARTITIONED, DISTINCT tables in DB1 which i need to PARTITION
into 3 DISTINCT tables in DB2.

Tom,
I've to populate 18 partitiones in DB2 from 1 single table in DB1. Can i use
DBMS_JOB to call these 18 different queries at the same time, taking into
consideration that I've big enough RBS.

Thanks for your replies TOM. This site is really a BIG learning INSTITUTE.

      Rating: 4
Still you can
      do it.  December 15, 2003
Reviewer:  Thiru
      pandian  from USA
Still you can do it.
here is the test i did.

DB1

SQL> create tablespace part_test
  2  datafile '/archive/mqc1_bkup1/part_test01' size 1024m;

Tablespace created.

  1   create table non_prt_table (object_id, object_owner,
  2   object_type,
  3   object_name)  tablespace part_test nologging  as
  4   select a.object_id, a.owner,
  5   decode(a.object_type, 'TABLE',1,'INDEX',2,'PACKAGE',3,'VIEW',4,5),
  6   a.object_name
  7   from dba_objects a, dba_objects b
  8* where rownum < 1000000;

SQL> alter tablespace part_test read only;

Tablespace altered.

SQL> desc non_prt_table
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 OBJECT_ID                                          NUMBER
 OBJECT_OWNER                                       VARCHAR2(30)
 OBJECT_TYPE                                        NUMBER
 OBJECT_NAME                                        VARCHAR2(128)

oracle@dvlpq1 [/home/admin/oracle]exp TRANSPORT_TABLESPACE=y tablespaces=part>

Export: Release 8.1.7.4.0 - Production on Mon Dec 15 17:15:18 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Username: / as sysdba

Connected to: Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production
Export done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set
Note: table data (rows) will not be exported
About to export transportable tablespace metadata...
For tablespace PART_TEST ...
. exporting cluster definitions
. exporting table definitions
. . exporting table                  NON_PRT_TABLE
. exporting referential integrity constraints
. exporting triggers
. end transportable tablespace metadata export
Export terminated successfully without warnings.

DB2

oracle@dvlpq1 [/home/admin/oracle]e=y datafiles=/archive/mqc1_bkup2/part_test*

Import: Release 8.1.7.4.0 - Production on Mon Dec 15 17:25:36 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Username: / as sysdba

Connected to: Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production

Export file created by EXPORT:V08.01.07 via conventional path
About to import transportable tablespace(s) metadata...
import done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set
. importing SYS's objects into SYS
. importing PTHIRU's objects into PTHIRU
. . importing table                "NON_PRT_TABLE"

SQL> connect pthiru/pthiru
Connected.
CREATE TABLE PRT_TABLE
(
  OBJECT_ID     NUMBER,
  OBJECT_OWNER  VARCHAR2(30),
  OBJECT_TYPE   NUMBER,
  OBJECT_NAME   VARCHAR2(128)
)
NOLOGGING
PARTITION BY RANGE (OBJECT_TYPE)
(
  PARTITION P1 VALUES LESS THAN (maxvalue)
    NOLOGGING
)
/
SQL> select * from tab;

TNAME                          TABTYPE  CLUSTERID
------------------------------ ------- ----------
NON_PRT_TABLE                  TABLE
PRT_TABLE                      TABLE

/

SQL> select count(*) from prt_table;

  COUNT(*)
----------
         0

SQL> select count(*) from NON_PRT_TABLE ;

  COUNT(*)
----------
    999999

SQL> alter table prt_table exchange partition p1 with table
  2  non_prt_table without validation;

Table altered.

SQL> select count(*) from prt_table;

  COUNT(*)
----------
    999999

SQL> alter tablespace part_test read write;

Tablespace altered.

SQL>  alter table prt_table split partition
  2   p1 at (2) into (partition prt_1, partition all_part);

Table altered.

December 10, 2003 Manoharan -- Thanks for the question regarding
      "Capacity Planning", version 8.1.7

      You Asked
Hi Tom,

I have a some questions regarding the capacity planning.Thanks in advance.

1.is there any way we can match LIOs & PIOs to the no of CPUs & no of disks ?

2.is there any place , i can find documents to do capacity planning for the
oracle database/sun solaris environment?

3.I am very much confused about the sort_area_size
   my understanding is
-- sort_area_size is the max threshold to do sort on memory and only one only
sort_area_size per session .Alloc from UGA
--sort_area_retained is to store the result set from SAS and it can be
   many per session at a time.is it correct? Alloc from PGA.

  When we do first sorting which is lesser than sort_area_size,the memory
allocated from PGA or UGA? is it sort_area_size or sort_area_reatined?

Thanks in advance
Regards
Jeyaseelan.M

      and we said...
1) not really.  not even a little.  not in any way that I'm aware of at all.

2) you need to size YOUR application.  oracle -- by itself, with no data - take
the minimums documented in the install guide.

once you build YOUR application however, you have this totally 100% unique beast
that no one else on the planet has.

You might have a cpu intensive algorithm no one else does -- or not. (and
hundreds of other things)

you need to benchmark your system -- you can use modeling tools (flaky, limited
if any success with outputs) or prototype it, benchmark it and size from there.

If you are buying a packaged app -- ask that vendor, they should be able to
provide their guidelines.

3) sort area size is a maximum (we allocate bit by bit UP TO that threshold)

it is allocated from the PGA -- sort area retained is in the UGA -- but the UGA
is normally in the UGA.

sort_area_retained is the low water mark of the sort area -- it is UGA memory
(normally in the pga)

December 10, 2003 Stanley -- Thanks for the question regarding
      "DBMS_UTILITY.GET_TIME", version 9i

      You Asked
Hi Tom,
   Earlier in Oracle 7.3.4 the return value from DBMS_UTILITY.GET_TIME was
positive number but when i tried in Oracle 9i am getting a negative value.

SQL>ed gettime
Set Serveroutput On
Declare
   nTemp   Number(12);
Begin
   nTemp := Dbms_Utility.Get_Time;
   Dbms_Output.Put_Line(nTemp);
End;
/

SQL> sta gettime
-2065910589

Is this how the return value should be or is there any problem?

Thanks in Advance

Regards
Stanley

      and we said...
Its OK, as it's only useful for measuring elapsed time.

ops$tkyte@ORA9I.WORLD> exec dbms_output.put_line( dbms_utility.get_time );
-2051479853

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> variable n number
ops$tkyte@ORA9I.WORLD> exec :n := dbms_utility.get_time

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> exec dbms_lock.sleep( 1 )

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> exec dbms_output.put_line( dbms_utility.get_time - :n );
108

PL/SQL procedure successfully completed.

so it still works in that capacity.

If you don't like it being "negative" (its a function of the machine clock and
CAN be observed in 8/7.x as well), you can add power(2,32) to the number.  Here
is an example from 806 showing it can (and will) be negative (it'll EVENTUALLY
go positive again, we put it into a 32 bit signed integer, it rolls over, goes
negative then goes to zero and goes back up)

ops$tkyte@ORA806.WORLD> exec dbms_output.put_line( dbms_utility.get_time );
-2051457232

PL/SQL procedure successfully completed.

ops$tkyte@ORA806.WORLD> variable n number
ops$tkyte@ORA806.WORLD> exec :n := dbms_utility.get_time

PL/SQL procedure successfully completed.

ops$tkyte@ORA806.WORLD> exec dbms_lock.sleep( 1 )

PL/SQL procedure successfully completed.

ops$tkyte@ORA806.WORLD> exec dbms_output.put_line( dbms_utility.get_time - :n );
101

PL/SQL procedure successfully completed.

ops$tkyte@ORA806.WORLD>
ops$tkyte@ORA806.WORLD>
ops$tkyte@ORA806.WORLD>
ops$tkyte@ORA806.WORLD> exec dbms_output.put_line(
dbms_utility.get_time+power(2,32));
2243510166

PL/SQL procedure successfully completed.

ops$tkyte@ORA806.WORLD> variable n number
ops$tkyte@ORA806.WORLD> exec :n := dbms_utility.get_time+power(2,32)

PL/SQL procedure successfully completed.

ops$tkyte@ORA806.WORLD> exec dbms_lock.sleep( 1 )

PL/SQL procedure successfully completed.

ops$tkyte@ORA806.WORLD> exec dbms_output.put_line(
(dbms_utility.get_time+power(2,32)) - :n );
101

PL/SQL procedure successfully completed.

If you are UNLUCKY enough to hit it when it rolls- you'll get weird timings
(been there, done that)
Review & Followup

      Rating:
      2
DBMS_UTILITY.GET_TIME", version 9i  December 05,
      2002
Reviewer:  D.Walsh  from Louisiana
I have Oracle 9i and it seems as though dbms_lock.sleep( 1) is not a
functionality. I receive the error:

SQL> exec dbms_lock.sleep( 1 );

begin dbms_lock.sleep( 1 ); end;

ORA-06550: line 1, column 7:
PLS-00201: identifier 'DBMS_LOCK' must be declared

          Followup:

you need to have execute on dbms_lock granted to you -- see your dba to see if
that is possible.

      Rating:
      4
dbms_lock  December 10, 2003
Reviewer:
      Anuj  from USA
Tom!
Thanks in advance .Well i have to get the data from table(another database)
after its updated by some process (say process a).I can figure out ,if a is
complete that using decode statement below if it returns flag 'YES' then i can
start my process (say b) ,but when flag is 'NO' i have to keep checking for this
condition evey 10 minutes if flag is 'YES'.I can not use dbms_job for some
reason .Pl advise can if i can use dbms_lock to achieve this or there is some
other neat(your) way of doing this .

PROCEDURE proc1 IS

lv_flag             Varchar2(3);
lv_run_date         DATE;

BEGIN
      BEGIN

          SELECT DECODE(TL_STATUS,'TL COMPLETE',DECODE(LTL_STATUS,'LTL
COMPLETE','YES','NO'),'NO')
          INTO lv_flag FROM source_table
          WHERE ID = -999; --condition to see other process is complete before i
start my                            -- process

          SELECT  max(etl_date) +1+6/24 INTO lv_run_date FROM target_table ;

          IF(lv_flag ='NO' AND (lv_run_date > lv_date OR lv_run_date IS NULL))
THEN
          dbms_lock.sleep(600);

          proc1;

          ELSIF(lv_flag ='YES')THEN

              --do insert into target table

           ....................
           ....................
           ....................
end proc1 ;

          Followup:

you are using dbms_lock already?

      Rating:
      4
dbms_lock.sleep  December 10, 2003

Reviewer:  Anuj  from USA
Hi Tom,
Thats what i'm trying to figure out if there is  any better and effcient way to
do the same. If proc keeps calling itself for long hours say process a fails
then how it will impact db .

Thanks & Regards

          Followup:

dbms_lock.sleep puts you to sleep, you are blocked, just hanging out, not doing
anything.

you are OK as is

December 10, 2003 stevev -- Thanks for the question regarding
      "where is catrman?", version

      You Asked
after reading oracle mag & oracle8 recovery
I tried to execute the script 'catrman'

on oracle 8.12.5 8I for netware (enterprise edition)
But the script does not exist, even looking in the "rbdms\admin\"
directory , it cannot be seen.

any ideas.

      and we said...
catrman.sql was removed for 8.1.4.  Catalog maintenance is now done via
internal commands:

CREATE CATALOG
UPGRADE CATALOG
DROP CATALOG

Review &
      Followup

      Rating: 3
recover
      catalog  December 10, 2003
Reviewer:
      A reader
Dear Tom,

I am avid reader of this forum.
Accidently I droped recovery catalog which is used for backing up nearly 20 DBs.
I don't have a backup copy of catalog.

Is there a way to construct catalog from info stored in tape?

Any other workaround?

I know I should have recovery catalog  backup but I missed it here. Any help
would be really appreciated.

          Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96566/rcmrepos.htm#455029

December 10, 2003 -- Thanks for the question regarding "How to
      track logon & logout ...", version 7,8x

      You Asked
Hi Tom,

I'm impressed by the quality of the answers you provide in this site. It is
really a boon to us Oracle users...

I wanted your help in the following :

I would like to track the Logon & logout timings of each user on to a flat file
(.Txt). How do I do that ?
Since I'm using Forms, Kindly let me know what would be the best possible way I
could achieve this.

* Next, to ask u a little different question, can I prevent the unauthorised
distribution of my application that I develop in Forms ? The client may always
create a copy of my .FMX files and can re-sell my software. I do not want that
to happen. Kindy help.

Thanks.

Viji

      and we said...
You would enable auditing in the database and audit connects.  This will record
the login/logout activity into the audit trail.  The audit trail is either a
database table OR the system event log (OS specific as to where that is).  If
you are using the database table, creating a flat file is trivial (simple query
and spool from sqlplus).

There is no realistic way to prevent your customers from giving your software to
others.
Review
      & Followup

      Rating: 4
Trigger
      auditing  October 30, 2001
Reviewer:  Andre
      Whittick Nasser  from Rio de Janeiro, Brazil
As an alternative to auditing logons/logoffs, you might as well create LOGON and
LOGOFF triggers that populate a regular history table. I myself prefer it. It's
far simpler:

create table log_trail (name varchar2(30), time date, action varchar2(10));

create or replace trigger tr_logon
after logon on database
begin
   insert into log_trail values (user, sysdate, 'LOGON');
   commit;
end tr_logon;

create or replace trigger tr_logoff
before logoff on database
begin
   insert into log_trail values (user, sysdate, 'LOGOFF');
   commit;
end tr_logon;

Notes:

- Beware of logon triggers. If they are not working, you may not logon to
Oracle.

- You must have the CREATE (ANY) TRIGGER and ADMINISTER DATABASE TRIGGER
privileges to implement DATABASE triggers.

          Followup:

How is that far easier then a single command "audit connect"

I hate writing code when a simple command would do the trick.  Use whats builtin
to the system, don't reinvent the wheel.  Thats my mantra...

      Rating: 4
Distribution of
      application  October 30, 2001
Reviewer:  George
      Spears  from Dayton, OH
Just a quick comment.  As Tom said, while you cannot prevent the application
from being copied, you can make it very difficult to 'get working'.  As an
example, your application could check the SID of the database, and only work
against a certain database.  Other things to check could include:  The IP range
of the client machines, the domain of the database, something pulled out of the
client registry, etc...

      Rating: 4
How to handle
      siuation when one user have multiple session  October 30,
      2001
Reviewer:  Ivan Korac  from Ulm, Germany

The problem is handling of application-users wich could be connected from
diferent places and through multiple sessions. It is possible for example with:
-- Trigger LOGONAUDITING
CREATE OR REPLACE TRIGGER logonauditing
AFTER LOGON ON application.schema
DECLARE
  machinename         VARCHAR2(64);
  osuserid            VARCHAR2(30);
  session_id_part1    NUMBER;
  session_id_part2    NUMBER;
  CURSOR c1 IS
    SELECT osuser, machine, sid, serial#
      FROM v$session WHERE audsid = userenv( 'sessionid' );
BEGIN
  OPEN c1;
  FETCH c1 INTO osuserid, machinename, session_id_part1, session_id_part2;
  INSERT INTO LOGON_table(osuser, machine, logon_time, session_id_1,
session_id_2)
          VALUES ( osuserid, machinename, sysdate,session_id_part1,
session_id_part2);
  CLOSE c1;
  COMMIT;
END;
/
- similar is needed for log off, and now You have all elements for corect
counting of spent time of any user

          Followup:

I fail to see how this collects any additional information beyond what is
already collected by audit connect??  See the comment a couple down from here
where I dump dba_audit_trail.  Its all there already.

      Rating: 5
Trigger
      Auditing  October 30, 2001
Reviewer:  Lester
      Burnham  from Orlando, FL USA
Using a database trigger just adds two more objects (the trigger and the table)
into the database.  Also, it won't work for the user's 7.x databases.  It won't
track unsuccessful logon attempts (which may or may not be important).  Andre
rightly points out that the trigger may be able to prevent logons if it is not
working...

      Rating: 4
Yse
      Usful  October 30, 2001
Reviewer:  Mohammed
      Al-moayed  from Yemen
Yes, Tom You are right audit connect is the easiest way,
this will help me in the My new security Application but
could you please how  to find audit_trail table I tried but I didn't  find it
,or you mean we have to create  it .

For the trigger above I think he created it by hime self it not oracel system
table .

          Followup:

The main audit trail table is DBA_AUDIT_TRAIL (see below for an example).

See

http://technet.oracle.com/doc/oracle8i_816/server.816/a76956/audit.htm#1108
for all of the details.

      Rating: 4
Well...
        October 30, 2001
Reviewer:  Andre Whittick
      Nasser  from Rio de Janeiro, Brazil
First, I have worked a lot with Oracle auditing in very controlled environments,
and managing audit trails is not definately something you would like to do on a
sunday morning.

If you are auditing myriads of events, they get interleaved in the views and it
takes little time before you burn out brains out, believe me.

Auditing is an art. Carefully defining its scope may be one of the most
difficult things to do against the database.

Rule of thumb: Keep the audit trail as simple as possible. Focus on the events
your organization procedures have defined as relevant.

If you think LOGON/LOGOFF event are part of your organization's auditing scope,
then go ahead...

Creating these triggers and the table adds too little complexity to your
environment, and you can customize the code your own way.

Yes, indeed, you cannot audit successful/not successful events, but you have to
consider that you can also improve  your code in other directions by programming
additional features, like the other example above with multiple sessions.

As to the mantra, sorry but, despite being an Oracle afficionado, there are a
lot of features that are terribly implemented in the DBMS and you cannot just
think they are always better than some eventual code tailored to your needs.

There is a lot of extremely unfriendly stuff in Oracle, and I like to arrange my
own environment.

          Followup:

we are talking about auditing logons and logouts.

You are free to write as much code as you like (you've posted it in the past).
I, I like to use the built in feature.

Look at the question, "how to audit logins, logouts".  No art, no magic, no
muss, no fuss.

I too have some experience using Oracle stuff.  I know what its about, believe
me.

Rule of thumb: keep everything as simple as possible.  Here the correct, proper
and accurate answer in my opinion is "audit connect".

The other example -- that one makes no sense to me (as every bit and piece of
data they collect -- Oracle ALREADY collects!!!)

Lets see, they get:

    SELECT osuser, machine, sid, serial#
      FROM v$session WHERE audsid = userenv( 'sessionid' );

(never mind the trigger should be ONE line long with an "insert into select
from" for a moment).  Well, when I audit connects I get:

tkyte@TKYTE816> @printtbl8 'select * from dba_audit_trail where rownum = 1'
OS_USERNAME                   : Thomas?Kyte
USERNAME                      : TKYTE
USERHOST                      :
TERMINAL                      : TKYTE-DELL
TIMESTAMP                     : 08-may-2001 19:19:29
OWNER                         :
OBJ_NAME                      :
ACTION                        : 102
ACTION_NAME                   : LOGOFF BY CLEANUP
NEW_OWNER                     :
NEW_NAME                      :
OBJ_PRIVILEGE                 :
SYS_PRIVILEGE                 :
ADMIN_OPTION                  :
GRANTEE                       :
AUDIT_OPTION                  :
SES_ACTIONS                   :
LOGOFF_TIME                   : 08-may-2001 19:19:30
LOGOFF_LREAD                  : 79
LOGOFF_PREAD                  : 1
LOGOFF_LWRITE                 : 11
LOGOFF_DLOCK                  : 0
COMMENT_TEXT                  : Authenticated by: DATABASE
SESSIONID                     : 8233
ENTRYID                       : 1
STATEMENTID                   : 1
RETURNCODE                    : 0
PRIV_USED                     : CREATE SESSION
-----------------

As to the mantra -- we'll just have to agree to totally, 100% disagree here.
Not about how features are implemented but about how to properly implemented
the solution to the question posed here.

The easy way: audit connect.
The hard way: write code.
The end result from both: the same.
Arranging your own environment is fine, great (if you have my book -- you can
see I do the same, look at the section on UTL_SMTP, UTL_HTTP, UTL_TCP for
exmaple).   When it is needed, I'm all for it.  I just happen to very much
disagree with your analysis of auditing in this case.

      Rating: 5
Versions of
      database specified  October 30, 2001
Reviewer:
      Lester Burnham  from Orlando, FL USA
> The end result from both: the same.

In 8i and up - but doesn't the original poster indicate that they have version
*7* and version 8x?  (Maybe the version 8x means 8i maybe it means 8.0.5 -
doesn't say.)  Seems to me that the question could use a bit more detail.  For
example, maybe they just want to track logon/logoff times of their Forms
application only and not logon/logoff for all users of the database in general.
In any case, the trigger option won't work (unless this feature snuck back into
7.x and 8.0.x) for all of the poster's databases, so the only solution that
meets the needs of the original poster is to use database auditing as Tom is
suggesting.  I know I am being picky, but...

      Rating: 4
auditing using
      audit connect  October 31, 2001
Reviewer:
      shamim ahmad  from New delhi, India
tom
I tried both options
audit connect is working fine  for auditing logon and logoff of users. but how
to know through which application user logged in  at the same time.
There is one problem using logon and logoff triggers  when a user have multiple
instances,   the session id that is captured during logon trigger is diferent
from the session id  that i am catching during logoff trigger, can u tell me why
it is so.

          Followup:

You cannot know what application users are connecting from.  The information is
v$session is suspect at best (just copy sqlplus.exe to blah.exe and see what
happens -- you cannot rely on that information in any way, shape or form.

In any case, I do not see the same behavior you do with a trigger like this:

create table audit_trail
as
select sysdate x$timestamp, rpad('*',20,'*') x$action, v.*
  from v$session v
 where 1=0
/

create or replace trigger logon_trigger
after logon on database
begin
   insert into audit_trail
   select sysdate, 'LOGON', v.*
     from v$session v
    where sid = ( select sid from v$mystat where rownum = 1 );
   commit;
end;
/

create or replace trigger logoff_trigger
before logoff on database
begin
   insert into audit_trail
   select sysdate, 'LOGOFF', v.*
     from v$session v
    where sid = ( select sid from v$mystat where rownum = 1 );
   commit;
end;
/

I get the same sid/serial# regardless.  The audsid (sessionid) is consistent as
well.

      Rating: 4
Do not miss
      flexibility  October 31, 2001
Reviewer:  Ivan
      Korac  from Ulm, Germany
I am not against your solution of course, but I would like to highlight power
hidden in logon/logoff  trigger solution and additional flexibility.
Our usage include loading of some additional fields (from user interface) in
logon_table for e.g. country code and language code which brings very powerfully
setup of cubs needed for user reports.

          Followup:

How could your user interface supply a logon trigger with additional information
-- its a chicken and egg problem.

I do not and have not suggested that logon/logoff triggers aren't flexible,
aren't useful.  I have and will continue to suggest that if you want to capture
the logon and logout timings of each user -- the answer is "audit".  The answer
is not "write code".

If you have definitive requirements above and beyond that -- a logon trigger may
become something to consider.  Given this question was "how to get logon/logout
in version 7,8x", the answer remains the same -- audit.

      Rating: 5
How can I get
      audit data after enable the audit connect ?  October 22,
      2002
Reviewer:  A Reader  from USA
Hi Tom,
I tried a "after logon/before logoff" trigger method.  It works fine when every
user logoff normally.  However, when a user logoffs abnormally, eg., logoffing
by clicking the 'X' on SQLPLUSW interface, the logoff information, say, the time
(date) will not be recorded by the trigger. When the user use "EXIT", all work
as expected.  So I tried the "audit connect" method.
When I was trying to use the "audit connect" method to audit user logon and
logoff so that I know how a user had logoned to the database.  After reading the
document on this topic, I still have problem in getting the audit results.  I
guess I am missing somthing. I did the following:
1. Logon to the database as sys and issue command: audit connect;
2.  connect to the database as any users;
3.  select from dba_audit_trail (as sys user or any dba_user);

No rows returned. I even run the CATAUDIT.SQL. What else do I need to do to use
the "audit connect" correctly? Can you please help me with the steps I need to
go through to audit the connection (logon/logoff)?

Thank you in advance.

          Followup:

audit_trail=true;

in the init.ora perhaps?

      Rating: 5
Ignore the
      above question.  October 22, 2002
Reviewer:  A
      Reader  from USA
Tom,
Please ignore the above question (How can I get audit data after enable the
audit connect ?).  Metalink has the detail steps (Note:99786.1).
Thank you again.

      Rating: 5
audit connect
      & snapshots  December 16, 2002
Reviewer:
      Will  from Austin
I am finding that when a remote database connects to my database (via dblink) to
refresh (complete) a snapshot, the audit trail shows LOGOFF BY CLEANUP for these
sessions.  Both DB's are 8.0.5.

Is it normal for these sessions to not explicitly disconnect?

Does it make any difference?

Thanks in advance.

          Followup:

I do not see any problem reports filed against this, so I doubt that is is "a
problem".  Normal - no, I don't think so. When the job closes the database link,
it should close it with a normal logoff.

As it isn't causing any issues right now tho... I would ignore it.

      Rating: 4
login
      failures  April 10, 2003
Reviewer:
      shihantha  from sri lanka
on the subject of auditing, I would like to know if there is a way to trap login
failures. i.e have an audit of failed attempts to log on to the database
(invalid username or password)

          Followup:

audit connect whenever NOT successful;

      Rating:
      5
persistence  April 11, 2003
Reviewer:
      shihantha  from sri lanka
thanks a lot. just what I needed.

I would also like to know if there is any way of setting up an environment where
logon failures are recorded automatically... instead of having to run the 'audit
connect whenever not successful' statement each time the database is restarted

thanks

      Rating:
      5
oops  April 11, 2003
Reviewer:
      shihantha  from sri lanka
please ignore the earlier question.

it remains persistent untill we explicitly say NOAUDIT

sorry... must be a slow day for me.. hee hee...

      Rating:
      5
Commit  April 17, 2003
Reviewer:  Steven
      Cooper  from England
Tom,

I was under the impression that you can't commit in triggers?

e.g.
system@Dev9i> CONNECT SYS AS SYSDBA
Enter password: ********
Connected.
system@Dev9i> create table user_logon
  2  (
  3  user_name  varchar2(30),
  4  last_logon  date,
  5  constraint user_logon_pk primary key (user_name)
  6  )
  7  organization index
  8  /

Table created.

system@Dev9i>
system@Dev9i> create or replace trigger logon_trigger
  2  after logon on database
  3  begin
  4      insert into user_logon values ('test',sysdate);
  5      commit;
  6  end;
  7  /

Trigger created.

-------------------------------------------
Then tried to logon :

ERROR:
ORA-00604: error occurred at recursive SQL level 1
ORA-04092: cannot COMMIT in a trigger
ORA-06512: at line 3

Steven

          Followup:

you can if you add pragma autonomous_transaction (but beware, if you have 815
and commit in an on logon trigger with an atrans -- you'll be calling support
right afterwards -- its OK in all current releases)

      Rating:
      5
re:Commit  April 17, 2003
Reviewer:
      Steven Cooper  from England
Tom,

Thanks, that works.

Steven

      Rating: 5
Some question
      about Auditing Connect  May 03, 2003
Reviewer:
      Riaz Shahid  from PRAL, Islamabad, Pakistan
Hello Tom !

I have requirement to have the log of logons & logouts if:

(1) The time is >= 9:00 PM
OR
(2) The day is Sunday.

Should i do it using auditing connect for that (if yes then how ) or just use
the logon and logoff triggers ? In my opinion, triggers will be better.

Note that procedures at my remote client side are like :

select 1 into a from dual@DBLink_to_my_database;
if a=1 then
insert into mytab@DBLink_to_my_database
......
......
select ....
...
from LocalTable
where ,.....
;

update mytab@DBLink_to_my_database
set ....
....
...;

etc etc.

My understanding is that the statement:

select 1 into a from dual@DBLink_to_my_database

will be considered as two transactions (i.e; two records in my audit table): one
for logon and one for logout. And for each statement using insert/select/update,
there will be two rows in audit table. Am i true ?

Plz Advise

Riaz Shahid

      Rating: 5
How to format
      error stack  May 15, 2003
Reviewer:  Igor
       from France
Hi

I have following trigger:

create or replace trigger sa2_logon_trigger
after logon on database --when (user='SA2')
declare
        iworkgroup varchar2( 150 );
begin
        if (user = 'SA2' ) then
                sa.set_context_sa2;
        else
                begin
                        select work_group
                        into iworkgroup
                        from table_employee
                        where objid =
                        ( select objid from table_user where upper( login_name )
= user );
                exception when no_data_found then null;
                end;

                if ( iworkgroup = 'Concert' ) then
                        raise_application_error ( -20001, 'Login denied.' );
                end if;
        end if;
end;
/

When specific user connects with sqlplus, gets:
<quote>
ERROR:
ORA-00604: error occurred at recursive SQL level 1
ORA-20001: Login denied.
ORA-06512: at line 17
</quote>

It's nothing wrong with that but unfortunately Clarify client shows just first
line and it doesn't tell too much about real error:
"ORA-00604: error occurred at recursive SQL level 1"

I have no idea how to show just specific error message?

                                               Igor

          Followup:

that would be an issue to take up with "clarify".  we cannot make them show the
entire error message if they don't want to.

the error is the error, you cannot change it.

      Rating: 4
How to map the
      pair of logon and logoff?  May 28, 2003
Reviewer:
      Minnie  from USA
Hi tom,
in your solution,

quote:
create table audit_trail
as
select sysdate x$timestamp, rpad('*',20,'*') x$action, v.*
  from v$session v
 where 1=0
/
create or replace trigger logon_trigger
after logon on database
begin
   insert into audit_trail
   select sysdate, 'LOGON', v.*
     from v$session v
    where sid = ( select sid from v$mystat where rownum = 1 );
   commit;
end;
/
create or replace trigger logoff_trigger
before logoff on database
begin
   insert into audit_trail
   select sysdate, 'LOGOFF', v.*
     from v$session v
    where sid = ( select sid from v$mystat where rownum = 1 );
   commit;
end;
/
quote end.

one can find the pair of logon and logoff by using sid and serial#.  But it's
not as easy as a select statement to review the information.
First of all, I want to claim that I'd like to using user-defined-trigger
instead of audit connect because I need to combine restrict logon time frame and
restrict users.
I'm thinking the following solution.
create table audit_trail
as
select sysdate x$timestamp, rpad('*',20,'*') x$action, v.*, sysdate
x$logoff_time
  from v$session v
 where 1=0
/
create or replace trigger logon_trigger
after logon on database
begin
   insert into audit_trail
   select sysdate, 'LOGON', v.*, null
     from v$session v
    where sid = ( select sid from v$mystat where rownum = 1 );
   commit;
end;
/
create or replace trigger logoff_trigger
before logoff on database
begin
   update audit_trail set x$logoff_time=sysdate
   where sid = ( select sid from v$mystat where rownum=1 );
   commit;
end;
/
the audit_trail table looks like pretty similiar with sys.aud$ now.
but logoff trigger will update all the rows having the same sid. How to solve
it?

Thank you very much,

          Followup:

why not....

audit connects

have a trigger to refuse connects except when you want them.

tastes great, less filling....

      Rating: 5
TOM very
      write  May 28, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
I am 100% agreed with Mr. TOM that we should use the functions if we have with
the DB. For example if we have count(*) function then why we should code for it.
We can utilize that time to some other activities. I just like to code if I
don't find any solution from the DB which is very rare and happens when I don't
have enough knowledge about that functionality.

For example earlier i was not using analytical functions but now I came to know
from the discussion of Mr. TOM and his book that these are very useful and
really these are very useful.

Now if someone is using 7,8 then to get extra features they should migrate to
the new one. If someone is still using 7 then I don't think this is the fault of
Oracle Co.

      Rating: 5
"Very Right" in
      the above thread  May 28, 2003
Reviewer:
      Sikandar Hayat  from Pindi Bhattian - Pakistan

      Rating: 5
For Mr.
      Sikander Hayat  May 29, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Islamabad, Pakistan
Dear Sikandar !

AOA

I wanna know from where u found the book of Tom ? Since its not available in
pakistan (acccording to my knowledge)

Regards

      Rating: 5
Ya not
      available in PK  May 29, 2003
Reviewer:
      Sikandar Hayat  from Pindi Bhattian - Pakistan
You are right that the book is not available in Pakistan. I had checked from
Islamabad, Lahore and Karachi. Then I purchased it from India which is Indian
reprint but of good quality.

sikandar_h@hotmail.com

      Rating: 3
Preventing
      multiple connections?  October 06, 2003
Reviewer:
      A reader
Being able to use the audit trail to keep track of user connections is
wonderful. But what about when we're using a J2EE/Web Cache installation of iAS?
The application server can create multiple connections for the user. Is there a
way of setting the audit trail to keep only one entry, and not twelve?

          Followup:

nope, you have 12 connections to audit as far as the database is concerned.  it
has no way to distinguish between them.

      Rating: 3
Re: Preventing
      multiple connections?  October 06, 2003
Reviewer:
      A reader
In that case then, perhaps a trigger would be the better alternative? True, it
asks us to code something which the database already can do, but it would also
allow us to do only one insert, instead of twelve - say, if there is already a
connection log within the hour. It would prevent the table from getting too big?

          Followup:

tables cannot be too big

database where born to insert into (and join)

it is what they do best.

why have auditing if you just say "you know, if you are already logged on --
thats good enough -- i don't need to know you also logged in from terminal
"badguy" as well"

      Rating:
      4
clarification  October 06, 2003
Reviewer:
      Sudhir  from Lexington, KY USA
logoff trigger is only way to get v$pq_tqstat for the session which did pq work
thru an application, correct? unless you are running your parallel query in
sqlplus and have access after main job. Please clarify.

Thanks

          Followup:

why would sqlplus be any different then any other client?

      Rating: 3
Can the "after
      logon on schema" trigger be used to track schema
      logins?  October 09, 2003
Reviewer:  ht
       from California
Tom,
I would like to track when a user connects to a schema-based application.  Since
my db has multiple schemas, how would I use the "alter logon on schema" trigger
to capture that userA connected to appA, appB, and appc?

It seems that the "after logon on schema" trigger is meant to track schema
owners.  How would I track non-schema owners that have been granted private
synonyms to access the app?

Thanks,
ht

          Followup:

don't know what you mean exactly by a "schema based application"??

If you want this logon trigger to fire for all users, it would be an after logon
on database -- not schema -- trigger.

      Rating:
      3
  October 10, 2003
Reviewer:  Jamil

In Pakistan you can get this book from vengaurd Karachi.

      Rating: 2
How would an
      "after logon" trigger be used to track app logons?  October
      10, 2003
Reviewer:  ht  from California
Tom,
If I have 10 schemas in 1 instance and want to track when a non-schema owner
user logons to the schema, can that be done with an after logon schema trigger?
Thanks,
ht

          Followup:

it doesn't work like that.

I don't know what you mean by "when a non-schema owner user logons to the
schema".  that is a contradictory statement.

they either logged into the schema using that schemas credentials....
or they did not....

maybe if you give us a concrete example of what you mean we can sort it out

      Rating: 5
Will have to
      create triggers when procs are executed.  October 10, 2003

Reviewer:  ht  from California
Tom,
Thanks for the clarification.  In going through the doc, it seems that I need to
modify my stored procs to track an application's usage instead of implementing
triggers.

It seems that triggers cannot be used to track the stored procs a user has
executed in an app.  Instead, I'll modify the stored proc to write the user's
name and sysdate to a table.

I had thought that "after logon on schema" triggers provided a way to track when
a user accessed a certain schema.
Thanks,
ht

          Followup:

auditing will track the procedures (PACKAGES please use PACKAGES instead of
procedures) a user executed as well -- very very easy to turn on.  no code.

      Rating: 5
Can I easily
      implement packages using PSP  October 10, 2003

Reviewer:  ht  from California
Thanks, I'll try packages but is there a way to keep my PSP env (loadpsp ...) if
I use packages.  In other words, would I still be able to load my psps if
they're wrapped by packages?
Thanks,
ht

          Followup:

psp's are ok as procedures since they are somewhat independent of eachother (a
psp never calls another psp really)

      Rating: 5
1 insert calls
      3 psps  October 10, 2003
Reviewer:  ht
       from California
Tom,
Thanks again for the info.  Would it be better to use packages if I call 4 psps
to insert an entity?  For example, the first psp displays a form that accepts
the data to be entered and calls a second psp that displays session info in a
header.  The third psp parses the array entered and calls the fourth psp that
does the actual insert.

If I should be using packages, is there a way to implement without abandoning
the "loadpsp ..." method I've been using.  It's nice to see the html and pl/sql
commands in 1 readable file.  If the benefits outweigh this development method,
I will gladly move to packages.

Your advice is appreciated.

ht

          Followup:

they do not really call eachother -- the mod_plsql module calls them one after
the other -- they are not dependent on eachother at all in the database.

      Rating: 4
How to turn on
      auditing for a schema's objects?  October 10, 2003

Reviewer:  ht  from California
Tom,
I took your advice and enabled auditing.  2 questions:

1.  Is there a way to turn on auditing for a specific schema's objects?  I'm
getting way too much info by tracking "audit execute procedure by access" and
would like to only see when my schema is accessed.
2.  How would one prevent the %AUD% tables from fragmenting my system
tablespace?  Can I redefine the tablespace for the aud$ table to a non-system
tablespace or is there another recommended method to handle this issue?

Thanks,
ht

          Followup:

1) you can enable auditing on specific schema objects.  Have you browsed the
audit command syntax fully in the sql reference and read the relevant chapter in
the admin guide?

2) you can contact support for guidance in that area.

      Rating: 3
Schema auditing
      not possible.  October 11, 2003
Reviewer:
      ht  from California
Tom,
I did search your site, Metalink, and the doc and posed the question because it
didn't seem that one can turn on auditing for a schema without explicitly
auditing each object.
I will contact support about figuring out a way to get the *aud* tables off of
the system tbsp.
ht

          Followup:

yup, that is what:

you can enable auditing on specific schema objects

meant -- you can audit at the object level.

      Rating: 3
How to track
      logon & logout   October 12, 2003
Reviewer:
      Jamil  from Saudi
Hi Tom
When I try to access this topic

http://technet.oracle.com/doc/oracle8i_816/server.816/a76956/audit.htm#1108
I am getting this error and the topic is unreadable  and it give this error
message

http://otn.oracle.com/errors/404.html
would you please help me how to get this topic display Properly

Best regards
 Jamil Shaibani

          Followup:

http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76956/audit.htm#1108
they moved the docs on otn :(

      Rating: 5
Multiple Logon
      Triggers  December 10, 2003
Reviewer:
      A reader
Hi Tom,

If there are multiple logon triggers in the database, how do Oracle decide the
execution order?

For example, one logon trigger sets sql_trace to false, and another one sets it
to true. What will be the result?

Thanks.

          Followup:

triggers fire in a decidedly undecided fashion

the behavior is documented as "undefined order of firing" for all triggers of
the same type.

so, if you need an order, better create a SINGLE trigger.

      Rating: 4
Not sure I like
      Audit Connect  December 10, 2003
Reviewer:  eric
      givler  from hbg, pa
We tried using Audit Connect and OEM's NUMEROUS logons to the database
essentially filled up the sys.aud$ table and rendered an inability to connect to
the instance. Of course this took a few weeks, but there must not have been any
oem job that was setup to detect this situation.  At that point, we changed the
datafiles, and turned it off in favor of what we really wanted to do which was
just know the last time a user logged into the system.

That and we were too lazy to figure out what should have been done with it.
(oops)

          Followup:

that is sort of like saying

"not sure i like insert, it gives the ability for our users to fill up tables"

isn't it?

December 11, 2003 -- Thanks for the question regarding "Alter
      Database Recover Vs Recover Database - Reg.", version 8.1.7.0.0

      You Asked
Hi Tom

When I issued "Recover Database until time '2001-07-12:10:10:10'" it worked
fine.

But when i issued "Alter databae recover until time '2001-07-12:10:10:10'" media
recovery failed. Why

Working on NT.

When u have to "Alter database recover until time ' '"?

Internaly what is the difference between the two statements.

--- messages displayed at sql prompt

SQL> alter database recover until time '2001-07-12:21:11:59';
alter database recover until time '2001-07-12:21:11:59'
*
ERROR at line 1:
ORA-00279: change 399983 generated at 07/11/2001 20:01:11 needed for thread 1
ORA-00289: suggestion : E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00015.ARC
ORA-00280: change 399983 for thread 1 is in sequence #15

SQL> recover database until time '2001-07-12:21:11:59';
ORA-00279: change 399983 generated at 07/11/2001 20:01:11 needed for thread 1
ORA-00289: suggestion : E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00015.ARC
ORA-00280: change 399983 for thread 1 is in sequence #15

Specify log: {<RET>=suggested | filename | AUTO | CANCEL}
auto
ORA-00279: change 420004 generated at 07/12/2001 21:07:50 needed for thread 1
ORA-00289: suggestion : E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00016.ARC
ORA-00280: change 420004 for thread 1 is in sequence #16
ORA-00278: log file 'E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00015.ARC' no
longer needed for this recovery

ORA-00279: change 420049 generated at 07/12/2001 21:08:22 needed for thread 1
ORA-00289: suggestion : E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00017.ARC
ORA-00280: change 420049 for thread 1 is in sequence #17
ORA-00278: log file 'E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00016.ARC' no
longer needed for this recovery

ORA-00279: change 420068 generated at 07/12/2001 21:08:25 needed for thread 1
ORA-00289: suggestion : E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00018.ARC
ORA-00280: change 420068 for thread 1 is in sequence #18
ORA-00278: log file 'E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00017.ARC' no
longer needed for this recovery

ORA-00279: change 420089 generated at 07/12/2001 21:08:28 needed for thread 1
ORA-00289: suggestion : E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00019.ARC
ORA-00280: change 420089 for thread 1 is in sequence #19
ORA-00278: log file 'E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00018.ARC' no
longer needed for this recovery

ORA-00279: change 420108 generated at 07/12/2001 21:08:30 needed for thread 1
ORA-00289: suggestion : E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00020.ARC
ORA-00280: change 420108 for thread 1 is in sequence #20
ORA-00278: log file 'E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00019.ARC' no
longer needed for this recovery

ORA-00279: change 420128 generated at 07/12/2001 21:08:34 needed for thread 1
ORA-00289: suggestion : E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00021.ARC
ORA-00280: change 420128 for thread 1 is in sequence #21
ORA-00278: log file 'E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00020.ARC' no
longer needed for this recovery

ORA-00279: change 420146 generated at 07/12/2001 21:08:35 needed for thread 1
ORA-00289: suggestion : E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00022.ARC
ORA-00280: change 420146 for thread 1 is in sequence #22
ORA-00278: log file 'E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00021.ARC' no
longer needed for this recovery

ORA-00279: change 420166 generated at 07/12/2001 21:08:40 needed for thread 1
ORA-00289: suggestion : E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00023.ARC
ORA-00280: change 420166 for thread 1 is in sequence #23
ORA-00278: log file 'E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00022.ARC' no
longer needed for this recovery

ORA-00279: change 420188 generated at 07/12/2001 21:08:42 needed for thread 1
ORA-00289: suggestion : E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00024.ARC
ORA-00280: change 420188 for thread 1 is in sequence #24
ORA-00278: log file 'E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00023.ARC' no
longer needed for this recovery

ORA-00279: change 420208 generated at 07/12/2001 21:08:47 needed for thread 1
ORA-00289: suggestion : E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00025.ARC
ORA-00280: change 420208 for thread 1 is in sequence #25
ORA-00278: log file 'E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00024.ARC' no
longer needed for this recovery

ORA-00279: change 420230 generated at 07/12/2001 21:08:49 needed for thread 1
ORA-00289: suggestion : E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00026.ARC
ORA-00280: change 420230 for thread 1 is in sequence #26
ORA-00278: log file 'E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00025.ARC' no
longer needed for this recovery

ORA-00279: change 420250 generated at 07/12/2001 21:08:53 needed for thread 1
ORA-00289: suggestion : E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00027.ARC
ORA-00280: change 420250 for thread 1 is in sequence #27
ORA-00278: log file 'E:\ORACLE\ORADATA\BACK1\ARCHIVE\BACK1T001S00026.ARC' no
longer needed for this recovery

Log applied.
Media recovery complete.
SQL>

Regards

      and we said...
The alter database command allows you to do things manually (which in your case
is why it failed).  From the sql ref manual for alter database recover:

Note: If you do not have special media requirements, Oracle Corporation
recommends that you use the SQL*Plus RECOVER statement.

You see, RECOVER is not even a SQL command, rather it is a SQLPlus command
(RECOVER isn't documented in the SQL reference, its not SQL).  It is an
automated command to do the "normal" recovery steps and works in most cases.

When the alter database raised an error, it also suggested the logfile to apply.
 You could then use the alter database command once again to apply that redo log
file using the LOGFILE clause. You would continue on applying logs until it was
done.

Or, you can use the RECOVER sqlplus command and let it do the work for you.

Review &
      Followup

      Rating:
      4
  July 16, 2001
Reviewer:
      A reader
cleared my confusion

      Rating: 4
Alter Database
      Recover Vs Recover Database - Reg  July 16, 2001

Reviewer:  A reader
It was very useful for me and recently i faced the same problem. Thanks TOM.

      Rating:
      4
  November 17, 2003
Reviewer:  Yanis
      Siminelakis  from Athens, HELLAS (Greece)

      Rating: 2
Totally
      confused  December 11, 2003
Reviewer:  Anil
      Pant  from Hyderabad
hi,
Your response has confused me. Can u explain it ?

          Followup:

it is relatively straightforward

a) use ALTER DATABASE if you want to do the recoverying step by step, manually
-- issueing each and every single solitary command to recover the database
(apply logfiles).

b) use the SQLPLUS 'recover' command to have sqlplus automate the steps in a)
for you.

December 11, 2003 wallacel -- Thanks for the question regarding
      "ORA-12571: TNS:packet writer failure", version 8.1.6

      You Asked
Tom:

I got this error very often recently.
when i go to Oracle8i error messages release 8.1.5  A67785-01
i found that error between 12495 and 12700 is missing.

i was wondering why, also why did i got this message?

Regards,

      and we said...
Error:    ORA-12571 / TNS-12571
Text:    TNS:packet writer failure
-----------------------------------------------------------------
----------
Cause:    An error occurred during a data send.
Action:    Not normally visible to the user. For further
details, turn on tracing and reexecute the operation. If error
persists, contact Oracle Customer Support.

(the last part of that Action being perhaps the most
important)...

As for the 'missing' messages -- I see them in

http://technet.oracle.com/doc/server.815/a67785/tnsus.htm#1000527
those are TNS, not ORA, maybe you looked in the wrong section?
Review &
      Followup

      Rating: 3
ora -
      12571  December 11, 2003
Reviewer:  apl
       from india
i am getting this error
ora - 12571 tns:packet writer failure
i got this error  only in one client machine. Rest of the client machines can
connect to the server.
My client machine o/s is windows 2000. i can ping to the server. but i am not
getting tnsping

thanking You

          Followup:

please contact support for an issue like this.

      Rating:
      5
  December 11, 2003
Reviewer:
      A reader
I wish Oracle Support was as efficient/effective as Tom.

December 11, 2003 Ahmed -- Thanks for the question regarding "What
      is the meaning of schema?", version 8.1.7

      You Asked
Hello Mr. Tom
i read so many book for oracle and java and every book speak about schema but
did not explain the actual meaning of it.once give the meanning of "user and all
objects it own".other give the meaning of
" Structure of table".So this cause me to be confused about the accurate
meanning of schema.please help me to understand the meaning of schema spcially i
will use it in my next java developer certified exam.
thank you in advanced.

      and we said...
You should consider a schema to be the user account and collection of all
objects therein as a schema for all intents and purposes.

SCOTT is a schema that includes the EMP, DEPT and BONUS tables with various
grants, and other stuff.

SYS is a schema that includes tons of tables, views, grants, etc etc etc.

SYSTEM is a schema.....

Technically -- A schema is the set of metadata (data dictionary) used by the
database, typically generated using DDL. A schema defines attributes of the
database, such as tables, columns, and properties.   A database schema is a
description of the data in a database.

Review &
      Followup

      Rating:
      4
confused  April 04, 2003
Reviewer:
      Tanmoy  from INDIA
tom ,
  thanks for your reply ..i have still some doubt..
  if i create a user will a schema automatically created ...
  is schema and user are synonym only ...
  can i change the schema ..
  can you please tell me more about schema and user

          Followup:

think of then synonomously, the same

      Rating: 5
schema and
      User  April 04, 2003
Reviewer:
      A reader
IF Oracle user has objects
     then
         call it SCHEMA
     else
         call it USER
END IF;

A USER may be given access to  SCHEMA OBJECTS owned by different USERS.

Hope to (DE-)Confuse you more

      Rating: 4
Oracle
      DBA  April 04, 2003
Reviewer:  Arash
       from Canada
Hi Tom,

  First of all thanks for your information. Second of all I still don't
understand why Oracle have Schema & User which in my point of view are the same
!?

I would appreciate it if you tell us the major reason of having both.

Thanks in advance,
Arash

          Followup:

schema is a fancy name for user most of the time.  A schema is really that
collection of objects owned by a user (as someone pointed out above)

oracle didn't do it -- all relational databases have this

      Rating: 2
Schema Vs.
      User  April 04, 2003
Reviewer:  josh
       from oregon
Can you have a user without a schema? It appears to me you can.

          Followup:

yes, technically speaking....

lets put it this way -- for all intents and purposes just consider user = schema
= user = schema = the same thing.

      Rating: 5
Please Digest
      this for once and for all  April 04, 2003

Reviewer:  A reader
Why is every body asking the same question in different ways and Thanx Tom is
answering.
Tom would you agree
  IF Oracle user has objects
     then
         call it SCHEMA
     else
         call it USER
END IF;

A USER may be given access to  SCHEMA OBJECTS owned by different USERS.

          Followup:

you say potato, i say potatah -- its all the same to me.

      Rating:
      4
  April 05, 2003
Reviewer:  a reader

In Relational Algebra (mathematical theory behind relational databases), a table
is called a relation and the schema of the relation is the basic information
describing this relation (column names and data types).

          Followup:

and rows are tuples, and non key attributes are functional dependencies, yadda
yadda yadda.  Lots of fancy terms to describe users, tables, rows, columns....

      Rating:
      5
Great!!  April 06, 2003
Reviewer:
      tanmoydc  from india
thanks tom for reply ...it cleared my doubt ..

      Rating: 3
Difference
      between User and Schema  December 11, 2003

Reviewer:  Kunjan  from Bangalore, India
Hi Tom,

this topic is interesting. I would like to share my thoughts.

Hi Ahmed, Tanmoy, 'A Reader', Arash, Josh,

There CAN be a USER can exist without a SCHEMA.
There CAN be a SCHEMA can exist without a USER.

As Tom said, for all practical purposes, eg. design, development etc. you can
use them interchangeably. But still there is slight difference between them.
(Tom please correct me if wrong!)

(1) User without a Schema -
When a user is created in a database, that user is without any schema. The
schema is created (in fact perhaps only conceptualized) when that user creates
the FIRST object in the database. The name of this schema is same as that of
user. and yes, it is a collection of all the object created/owned by
corresponding user.

(If you are using any GUI tool which lists User and Schema e.g. SQL Navigator OR
TOAD, you can see the schmea name being added in schema list when u create FIRST
object for a user, and being removed from the schema list when u delete LAST
object for any user)

(2) Schema without a User -
There is only 1 schema which does not have a user associated with it. 'PUBLIC'.
This is a schema, as there are objects in it, but there is no user with name
'PUBLIC'.

again repeating : for all practical purposes and intents, you don't need to know
the difference and can use them synonomously.

          Followup:

once you create a user in the database -- there is a schema.  it is empty, but
there is a schema.

In fact, we have the concept of a schemaless user (user is stored outside the
database, in an ldap respository for example).  Here, there is no schema, not
even the empty one.

and in fact:

[tkyte@localhost tkyte]$ sqlplus "/ as sysoper"

SQL*Plus: Release 9.2.0.4.0 - Production on Thu Dec 11 12:22:20 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.4.0 - Production

public@ORA9IR2> show user
USER is "PUBLIC"
public@ORA9IR2>
public@ORA9IR2> select user from dual;

USER
------------------------------
PUBLIC

There is a public user!

December 11, 2003 Miguel -- Thanks for the question regarding "how
      to do outer joins in multiples tables.", version 8i

      You Asked
I'm trying to covert an Access db to an Oracle db. I have a view with this
query:

In access a inner join returns the rows that are equal in both tables.
A rigth join returns all the rows of the rigth table and the coincidences in the
table of the left side, is to say, all the rows of the rigth table and the
values in the left table of the fields that should coincide. I expect to have
expressed correctly. I think this is an outer join in Oracle isn't it?

SELECT DISTINCTROW MODULOS_DATOS_ECONOMICOS.CURSO,
MODULOS_DATOS_ECONOMICOS.MODULO, MODULOS_DATOS_ECONOMICOS.PRESUPUESTO,
MODULOS_DATOS_ECONOMICOS.GASTO, MODULOS_DATOS_ECONOMICOS.DESVIACION,
MODULOS_DATOS_ECONOMICOS.DESVIACIONPCT, CURSOS_DATOS_ECONOMICOS.OBSERVACIONES,
GASTOS.CARPETAS, GASTOS.ALQUILERES, GASTOS.LIBRERIA,
conGastosAgenciaModulo.SumaDeIMP_VIAJE, conGastosAgenciaModulo.SumaDeIMP_HOTEL,
conGastosAgenciaModulo.SumaDeIMP_TRASLADOS,
conGastosAgenciaModulo.SumaDeIMP_MANUTENCION,
conGastosFotocopiasModulo.SumaDeIMPORTE,
conGastosRetribucionesModulo.SumaDeRETRIB, conGastosOtrosGastosModulo.SumaDePTAS
FROM (conGastosOtrosGastosModulo RIGHT JOIN (conGastosRetribucionesModulo RIGHT
JOIN (conGastosFotocopiasModulo RIGHT JOIN (conGastosAgenciaModulo RIGHT JOIN
(GASTOS RIGHT JOIN MODULOS_DATOS_ECONOMICOS ON
(GASTOS.MODULO = MODULOS_DATOS_ECONOMICOS.MODULO) AND
(GASTOS.CURSO = MODULOS_DATOS_ECONOMICOS.CURSO)) ON
(conGastosAgenciaModulo.MODULO = MODULOS_DATOS_ECONOMICOS.MODULO) AND
(conGastosAgenciaModulo.CURSO = MODULOS_DATOS_ECONOMICOS.CURSO)) ON
(conGastosFotocopiasModulo.MODULO = MODULOS_DATOS_ECONOMICOS.MODULO) AND
(conGastosFotocopiasModulo.CURSO = MODULOS_DATOS_ECONOMICOS.CURSO)) ON
(conGastosRetribucionesModulo.MODULO = MODULOS_DATOS_ECONOMICOS.MODULO)
AND (conGastosRetribucionesModulo.CURSO = MODULOS_DATOS_ECONOMICOS.CURSO)) ON
(conGastosOtrosGastosModulo.MODULO = MODULOS_DATOS_ECONOMICOS.MODULO) AND
(conGastosOtrosGastosModulo.CURSO = MODULOS_DATOS_ECONOMICOS.CURSO)) INNER JOIN
CURSOS_DATOS_ECONOMICOS ON MODULOS_DATOS_ECONOMICOS.CURSO =
CURSOS_DATOS_ECONOMICOS.CURSO;

in Oracle i do it like this:

select GCDAT.CURSOS_DATOS_ECONOMICOS.OBSERVACIONES, GCDAT.GASTOS.
    ALQUILERES, GCDAT.GASTOS.CARPETAS, GCDAT.GASTOS.LIBRERIA, GCDAT.
    MODULOS_DATOS_ECONOMICOS.CURSO, GCDAT.MODULOS_DATOS_ECONOMICOS.
    DESVIACION, GCDAT.MODULOS_DATOS_ECONOMICOS.DESVIACIONPCT, GCDAT.
    MODULOS_DATOS_ECONOMICOS.GASTO, GCDAT.MODULOS_DATOS_ECONOMICOS.
    MODULO, GCDAT.MODULOS_DATOS_ECONOMICOS.PRESUPUESTO,
    GCDAT.CONGASTOSAGENCIAMODULO.SumaDeIMP_VIAJE,
GCDAT.CONGASTOSAGENCIAMODULO.SumaDeIMP_HOTEL,
    GCDAT.CONGASTOSAGENCIAMODULO.SumaDeIMP_TRASLADOS,
GCDAT.CONGASTOSAGENCIAMODULO.SumaDeIMP_MANUTENCION,
    GCDAT.CONGASTOSFOTOCOPIASMODULO.SumaDeIMPORTE,
GCDAT.CONGASTOSRETRIBUCIONESMODULO.SumaDeRETRIB,
    GCDAT.CONGASTOSOTROSGASTOSMODULO.SumaDePTAS
    from GCDAT.CURSOS_DATOS_ECONOMICOS, GCDAT.GASTOS, GCDAT.
MODULOS_DATOS_ECONOMICOS,GCDAT.CONGASTOSOTROSGASTOSMODULO,GCDAT.CONGASTOSRETRIBUC
IONESMODULO,GCDAT.CONGASTOSFOTOCOPIASMODULO,GCDAT.CONGASTOSAGENCIAMODULO
    where ((GCDAT.GASTOS.MODULO = GCDAT.MODULOS_DATOS_ECONOMICOS.MODULO(+)) AND
(GCDAT.GASTOS.CURSO = GCDAT.MODULOS_DATOS_ECONOMICOS.CURSO(+))  AND
    (GCDAT.CONGASTOSAGENCIAMODULO.MODULO(+) =
GCDAT.MODULOS_DATOS_ECONOMICOS.MODULO) AND
(GCDAT.CONGASTOSAGENCIAMODULO.CURSO(+) = GCDAT.MODULOS_DATOS_ECONOMICOS.CURSO)
AND
    (GCDAT.CONGASTOSFOTOCOPIASMODULO.MODULO(+) =
GCDAT.MODULOS_DATOS_ECONOMICOS.MODULO) AND
(GCDAT.CONGASTOSFOTOCOPIASMODULO.CURSO(+) =
GCDAT.MODULOS_DATOS_ECONOMICOS.CURSO) AND
    (GCDAT.CONGASTOSRETRIBUCIONESMODULO.MODULO(+) =
GCDAT.MODULOS_DATOS_ECONOMICOS.MODULO) AND
(GCDAT.CONGASTOSRETRIBUCIONESMODULO.CURSO(+) =
GCDAT.MODULOS_DATOS_ECONOMICOS.CURSO) AND
    (GCDAT.CONGASTOSOTROSGASTOSMODULO.MODULO(+) =
GCDAT.MODULOS_DATOS_ECONOMICOS.MODULO) AND
(GCDAT.CONGASTOSOTROSGASTOSMODULO.CURSO(+) =
GCDAT.MODULOS_DATOS_ECONOMICOS.CURSO) AND
    (GCDAT.MODULOS_DATOS_ECONOMICOS.CURSO(+) =
GCDAT.CURSOS_DATOS_ECONOMICOS.CURSO))

this returns:
ERROR at line 13:
ORA-01417: a table may be outer joined to at most one other table

I try it in other forms but do not gives the output i want it only gives me 1
row.

Cuold you help me? please.

Thanks.

      and we said...
You have the (+) on the wrong side.  You pue that after the table you want to
outer join TO.

MODULOS_DATOS_ECONOMICOS is your "driving table".  everything else is outer
joined to "it" (or just joined to it).  So the (+) goes after the other table,
never after MODULOS_DATOS_ECONOMICOS.

This part of your query:
GCDAT.GASTOS.MODULO = GCDAT.MODULOS_DATOS_ECONOMICOS.MODULO(+)) AND
(GCDAT.GASTOS.CURSO = GCDAT.MODULOS_DATOS_ECONOMICOS.CURSO(+))  AND

is what messed it all up.  You were saying (most of the time) outer join to
MODULOS_DATOS_ECONOMICOS, but that time you said the opposite (which doesn't
make sense)

Your query in 8i would be:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2      from CURSOS_DATOS_ECONOMICOS,
  3                   GASTOS,
  4                   MODULOS_DATOS_ECONOMICOS,
  5                   CONGASTOSOTROSGASTOSMODULO,
  6                   CONGASTOSRETRIBUCIONESMODULO,
  7                   CONGASTOSFOTOCOPIASMODULO,
  8                   CONGASTOSAGENCIAMODULO
  9  where GASTOS.MODULO(+) = MODULOS_DATOS_ECONOMICOS.MODULO
 10    AND GASTOS.CURSO(+) = MODULOS_DATOS_ECONOMICOS.CURSO
 11    AND CONGASTOSAGENCIAMODULO.MODULO(+) = MODULOS_DATOS_ECONOMICOS.MODULO
 12    AND CONGASTOSAGENCIAMODULO.CURSO(+) = MODULOS_DATOS_ECONOMICOS.CURSO
 13    AND CONGASTOSFOTOCOPIASMODULO.MODULO(+) = MODULOS_DATOS_ECONOMICOS.MODULO
 14    AND CONGASTOSFOTOCOPIASMODULO.CURSO(+) = MODULOS_DATOS_ECONOMICOS.CURSO
 15    AND CONGASTOSRETRIBUCIONESMODULO.MODULO(+) =
MODULOS_DATOS_ECONOMICOS.MODULO
 16    AND CONGASTOSRETRIBUCIONESMODULO.CURSO(+) =
MODULOS_DATOS_ECONOMICOS.CURSO
 17    AND CONGASTOSOTROSGASTOSMODULO.MODULO(+) =
MODULOS_DATOS_ECONOMICOS.MODULO
 18    AND CONGASTOSOTROSGASTOSMODULO.CURSO(+) = MODULOS_DATOS_ECONOMICOS.CURSO
 19    AND MODULOS_DATOS_ECONOMICOS.CURSO = CURSOS_DATOS_ECONOMICOS.CURSO
 20  /

no rows selected

In 9i you can use the right join/inner join syntax if you like.

Review &
      Followup

      Rating:
      5
Thanks  January 10, 2002
Reviewer:
      Miguel Garcia  from Canary Islands/Spain
You have solve my problem, Thank you very much.

      Rating: 5
Follow-UP :
      join to the same Table more than Once 8.1.6  January 11,
      2002
Reviewer:  Pascal  from HH,Germany
Hi Tom ,

Just a Follow-up on  OUTER JOINs :

I have this QUERY:

select op.name OP , nvl(tz.name,'No TZ')  TZ , nvl(pg.DESCRIPTION,null)  PG
,( case
when ptz.price is null then  pop.price
when pop.price is null then  ptz.price
else 0  end ) price
from CLIENTS op, TZ_OPE t , PRICE_OPE pop , CLIENT_ZONES tz ,PRICE_TZ ptz  ,
PRICE_LISTS pg
where     op.id    =   t.ope_id               and
      op.id    =   pop.ope_id             and
      pg.id    =   pop.PG_ID              and
      pg.id    =   ptz.PG_ID              and
      tz.id    =   t.TZ_ID                 and
      tz.id    =   ptz.TZ_ID

In the above Query:

CLIENTS  <1 to many>   PRICE_OPE  <many to 1>  PRICE_LISTS
CLIENTS  <1 to many>   TZ_OPE     <many to 1>  CLIENT_ZONES  <1 to many>
PRICE_TZ  <many to 1>  PRICE_LISTS

All  id 's  are assumed to be primary keys and  ope_id, pg_id , tz_id are
foreign keys.

Hint: Take <many to 1>  or  <1 to many>  as Foreign key Relationships between
the tables

Now, i would like to  return  all  op.name  from  CLIENTS ,
all  pg.DESCRIPTION from  PRICE_LISTS  ,
all  tz.name from CLIENT_ZONES  ,
all price  from either  PRICE_OPE  or  PRICE_TZ :

There could be some records in  PRICE_LISTS/CLIENTS  which don't have matches in
  PRICE_OPE
but i would like to return all of them  through  OUTER JOINs

There could be records in  CLIENT_ZONES which don't have matches in   TZ_OPE /
PRICE_TZ
but i would also like to return all of them.

I have tried some OUTER JOINs myself but everytime i find myself OUTER JOINING
to the same table
more than ONCE - especially for tables :  CLIENTS op and  PRICE_LISTS pg .

Your help would be greatly appreciated.

Regards,

Pasko

      Rating: 5
Outer Join
      returning different value when using NVL   October 09, 2002

Reviewer:  B  from India
Hi Tom,

There are three tables in a query

The header table(ar_payment_schedules) has one entry for an invoice. If there
are any payments recd against that invoice then it is available in the lines
table (i.e. a view) ar_app_adj_v. The third
table is used to join the the above two.

The requirement : when the query is run for a period
it should pick up all the invoices available in the
header table that have the gl_date less than the passed period parameter
and also the lines, if any, from the lines table that have the gl_date
less than the passed period parameter.

This required an outer join between the header and the lines table as there
may not be an entry in the lines table.

There is a customer_trx_id (43397) available in both the header and line
table. But the GL_DATE for the header table is 31-Jul-02 and GL_DATE for
the lines table is 15-Aug-02. And we had used the following condition in
the lines table.

AND      NVL(app.gl_date,TO_DATE(:P_PERIOD)) <= TO_DATE(:P_PERIOD)

So when the :P_PERIOD passed to the query was 31-Jul-02, we expected a row for
43397 with only the information related to header table and not line table.
But the query did not return any rows. Later the where condition
was changed to the following condition and it produced the desired result.

AND      app.gl_date(+) <= TO_DATE(:P_PERIOD)

Can you explain the logic behind this.

SELECT
ps.due_date,
ps.gl_date head_date,
ps.AMOUNT_DUE_ORIGINAL,
ps.tax_original,
trx.customer_trx_id,
trx.INTERFACE_HEADER_ATTRIBUTE1,
app.AMOUNT,
app.TAX_AMOUNT,
app.gl_date
FROM     ar_payment_schedules ps
        ,ra_customer_trx trx
    ,ar_app_adj_v app
WHERE    PS.GL_DATE <= TO_DATE(:P_PERIOD)
AND      trx.customer_trx_id = ps.customer_trx_id
AND      trx.INTERFACE_HEADER_ATTRIBUTE1 = '13017'
AND      trx.trx_number = '120124'
AND      ps.CUSTOMER_TRX_ID = app.CUSTOMER_TRX_ID(+)
-- AND      NVL(app.gl_date,TO_DATE(:P_PERIOD)) <= TO_DATE(:P_PERIOD)
AND      app.gl_date(+) <= TO_DATE(:P_PERIOD)

          Followup:

Unless and until you used the outer join (the second predicate) there is no
reason we would "make up" a row in in APP.  So, app.gl_date would never be "made
up" for you.

If there was NO row in app that matched the criteria, there is nothing to "join
to" and in the absence of an outer join -- there would be no data returned.

      Rating:
      3
Joins  November 07, 2002
Reviewer:  Juan
      Guascarancas Pena  from Mexico
Hi Tom,

Where can I learn more about joins (left join, right join, inner join)?
What is this "inner join" syntax Oracle now uses?
How do you translate a left join syntax to an Oracle (<9i) syntax?

Thanks!!

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/statements_103a.htm#2076215

      Rating:
      5
Thanks  November 08, 2002
Reviewer:
      Juan Guascarancas Pena  from Mexico
Thanks Tom!

Just one more question: Is it possible to do a Full Outer Join on Oracle 8.0.4,
or is it time for an upgrade?

Mr J.

          Followup:

The syntax "full outer join" was added in 9i and up.  Basically you can do it in
any release and it would look like this:

ops$tkyte@ORA920.US.ORACLE.COM> select nvl( dept.dname, 'MADE UP') dname,
  2         nvl( emp.ename, 'MADE UP' ) ename
  3    from emp, dept
  4   where emp.deptno = dept.deptno(+)
  5   union all
  6  select nvl( dept.dname, 'MADE UP') dname,
  7         nvl( emp.ename, 'MADE UP' ) ename
  8    from emp, dept
  9   where emp.deptno(+) = dept.deptno
 10     and emp.deptno is null
 11  /

DNAME          ENAME
-------------- ----------
ACCOUNTING     FORD
ACCOUNTING     ADAMS
ACCOUNTING     SCOTT
ACCOUNTING     JONES
ACCOUNTING     SMITH
RESEARCH       JAMES
RESEARCH       TURNER
RESEARCH       BLAKE
RESEARCH       MARTIN
RESEARCH       WARD
RESEARCH       ALLEN
MADE UP        MILLER
MADE UP        KING
MADE UP        CLARK
SALES          MADE UP
x              MADE UP
OPERATIONS     MADE UP

17 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=10 Card=28 Bytes=448)
   1    0   UNION-ALL
   2    1     HASH JOIN (OUTER) (Cost=5 Card=14 Bytes=224)
   3    2       TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=14 Bytes=98)
   4    2       TABLE ACCESS (FULL) OF 'DEPT' (Cost=2 Card=5 Bytes=45)
   5    1     FILTER
   6    5       HASH JOIN (OUTER)
   7    6         TABLE ACCESS (FULL) OF 'DEPT' (Cost=2 Card=5 Bytes=45)
   8    6         TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=14 Bytes=98)

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select nvl( dept.dname, 'MADE UP') dname,
  2         nvl( emp.ename, 'MADE UP' ) ename
  3    from emp full outer join dept on ( emp.deptno = dept.deptno )
  4  /

DNAME          ENAME
-------------- ----------
ACCOUNTING     FORD
ACCOUNTING     ADAMS
ACCOUNTING     SCOTT
ACCOUNTING     JONES
ACCOUNTING     SMITH
RESEARCH       JAMES
RESEARCH       TURNER
RESEARCH       BLAKE
RESEARCH       MARTIN
RESEARCH       WARD
RESEARCH       ALLEN
MADE UP        MILLER
MADE UP        KING
MADE UP        CLARK
SALES          MADE UP
OPERATIONS     MADE UP
x              MADE UP

17 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=7 Card=16 Bytes=256)
   1    0   VIEW (Cost=7 Card=16 Bytes=256)
   2    1     UNION-ALL
   3    2       HASH JOIN (OUTER) (Cost=5 Card=14 Bytes=224)
   4    3         TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=14 Bytes=98)
   5    3         TABLE ACCESS (FULL) OF 'DEPT' (Cost=2 Card=5 Bytes=45)
   6    2       NESTED LOOPS (ANTI) (Cost=2 Card=2 Bytes=22)
   7    6         TABLE ACCESS (FULL) OF 'DEPT' (Cost=2 Card=5 Bytes=45)
   8    6         INDEX (RANGE SCAN) OF 'EMP_DEPT_IDX' (NON-UNIQUE)

As you can see -- the plans are roughly equivalent -- a full outer join could be
equated to setting "slow=true" if you are not careful!  It is an expensive
operation.

      Rating: 5
Thanks
      again!  November 08, 2002
Reviewer:  Juan
      Guascarancas Pena  from Mexico
Thank you Tom!

      Rating: 3
Please solve
      this doubt!  November 14, 2002
Reviewer:
      Abhijit  from Mumbai
Hi Tom,
I have got a query in SQL Server as follows.
SELECT
A.CHECKLIST_NAME,A.CHECKED,A.FUNCTIONNAME,A.MAKER_NAME,A.MAKER_DATE,B.DESCRIPTION
,C.MANDATORY,A.CHECK_SERIAL_NO,A.LEVEL_NO
    FROM TABLE_CHECK A LEFT OUTER JOIN
    LEFT OUTER JOIN TABLE_RECORD B ON
    (A.CHECKLIST_NAME=B.VALUE AND B.RECORD_TYPE='CHK')
    LEFT OUTER JOIN TB_IW_CHECKLIST C ON
    (A.CHECKLIST_NAME=C.CHECKLIST_NAME
    AND A.FUNCTIONNAME=C.FUNCTIONNAME)
    AND  A.WORKITEM_ID = '02318ESWM047119'

This is what I did to the query in Oracle 8i. Now I am not able to execute it.

SELECT
A.CHECKLIST_NAME,A.CHECKED,A.FUNCTIONNAME,A.MAKER_NAME,A.MAKER_DATE,B.DESCRIPTION
,C.MANDATORY,A.CHECK_SERIAL_NO,A.LEVEL_NO
    FROM TABLE_CHECK A,  TABLE_RECORD B, TABLE_LIST C
    WHERE(A.CHECKLIST_NAME=B.VALUE(+)
    AND B.RECORD_TYPE='CHK')
    AND (A.CHECKLIST_NAME=C.CHECKLIST_NAME(+)
    AND A.FUNCTIONNAME=C.FUNCTIONNAME(+)
    AND  A.WORKITEM_ID = '02318ESWM047119'

What did I do wrong?

Doubtfully,
Abhijit

          Followup:

Hmm, lets see -- I'm not actually a SQL compiler (contrary to popular belief)
and I don't just happen to have your tables (no create tables to help me out)
so....

I'll have to PASS - no error messages, no easy way to reproduce, no info to go
on.

Why would you port from SQL Server to 8i in 2002???  there have been 2
production releases of Oracle since that version.  If you are going through the
heartburn of migration -- why would you do it to an already outdated database
version??

      Rating: 4
Outer joining
      in the case of a join table (8.1.7)  January 10, 2003

Reviewer:  "Ted Billings"  from Berkeley, CA

/*
Im curious how one can deal with situations where multiple outer joins are
required on a join table? Is the only solution to work with a cartesian product?
Or is this the point where switching to plsql or asking for schema modification
should take place.

I need a query similar to this:
*/

select db_developer.developer_id as developer_id,
       nvl(schema_developer.has_written_good_query, 0)
                                  as has_written_good_query
  from db_schema,
       db_developer,
       schema_developer
 where schema_developer.schema_id (+) = db_schema.schema_id
   and schema_developer.developer_id (+) = db_developer.developer_id;

/*
The general case that I need is which developers
have never written a bad query. If a developer has not written a query against
schema, they are considered as having written a bad query  against that schema.

Based on these results, I can in turn run a query similar to this:
*/

select db_schema.schema_name                as schema_name,
       db_developer.developer_name          as developer_name,
       rank () over (partition by schema_developer.schema_id
                     order by schema_developer.power_ranking desc)
                                             as power_ranking
from  db_schema,
      db_developer,
      schema_developer
where db_schema.schema_id = schema_developer.schema_id
  and db_developer.developer_id = schema_developer.schema_id
  and db_developer.developer_id = (
      select distinct developer_id
        from (  select db_developer.developer_id as developer_id,
                       nvl(schema_developer.has_written_good_query, 0)
                                                 as has_written_good_query
                  from db_schema,
                       db_developer,
                       schema_developer
                 where schema_developer.schema_id (+) = db_schema.schema_id
                   and schema_developer.developer_id (+) =
db_developer.developer_id)
        where  has_writen_good_query = 1)
order by schema_name,
         developer_name;

/*
Now the only way I was able to solve this (so far) was to do a cartesian product
(ick!!) on schema and developer. I would hope there is a better way.
*/
select distinct developer_id
from (
    select db_schema.schema_id,
           db_developer.developer_id,
           0 as has_written_good_query
    from   db_schema,
           db_developer
    minus
    select schema_developer.schema_id,
           schema_developer.developer_id,
           0 as has_written_good_query
    from   schema_developer
    union
    select schema_developer.schema_id,
           schema_developer.developer_id,
           schema_developer.has_written_good_query
    from   schema_developer
    where  has_written_good_query = 0);

/*
which in turn is used in this context:
*/
select db_schema.schema_name                as schema_name,
       db_developer.developer_name          as developer_name,
       rank () over (partition by schema_developer.schema_id
                     order by schema_developer.power_ranking desc)
                                             as rank
from  db_schema,
      db_developer,
      schema_developer
where db_schema.schema_id = schema_developer.schema_id
  and db_developer.developer_id = schema_developer.developer_id
  and db_developer.developer_id not in (
            select distinct developer_id
            from (
                select db_schema.schema_id,
                       db_developer.developer_id,
                       0 as has_written_good_query
                from   db_schema,
                       db_developer
                minus
                select schema_developer.schema_id,
                       schema_developer.developer_id,
                       0 as has_written_good_query
                from   schema_developer
                union
                select schema_developer.schema_id,
                       schema_developer.developer_id,
                       schema_developer.has_written_good_query
                from   schema_developer
                where  has_written_good_query = 0))
order by schema_name,
         rank;

/*
Here's the sample ddl and data:
*/
-- Drop the example tables, if they exist
drop table schema_developer;
drop table db_schema;
drop table db_developer;

-- Create our schema table
create table db_schema (
   schema_id int primary key,
   schema_name varchar2(255) not null
);

-- Create our developer table
create table db_developer (
   developer_id int primary key,
   developer_name varchar2(255) not null
);

-- Create our schema developer join table
create table schema_developer (
   schema_id int,
   developer_id int,
   has_written_good_query number(1),
   power_ranking number(2)
);

alter table schema_developer
       add ( primary key (schema_id, developer_id) );

-- Populate our schema table
insert into db_schema values (1, 'prod');
insert into db_schema values (2, 'stage');
insert into db_schema values (3, 'integration');
insert into db_schema values (4, 'dev');

-- Populate our developer table
insert into db_developer values (1, 'Tom Kyte');
insert into db_developer values (2, 'Sue Smith');
insert into db_developer values (3, 'Ted Billings');
insert into db_developer values (4, 'Pete Hanger');

-- Populate our schema developer join table

-- For the first two schemas everyone has
-- written good queries
insert into schema_developer values (1, 1, 1,99);
insert into schema_developer values (1, 2, 1,50);
insert into schema_developer values (1, 3, 1,25);
insert into schema_developer values (1, 4, 1,10);
insert into schema_developer values (2, 1, 1,99);
insert into schema_developer values (2, 2, 1,40);
insert into schema_developer values (2, 3, 1,53);
insert into schema_developer values (2, 4, 1,77);

-- For the third schema, Ted blew it and wrote
-- a query that performed a cartesian product
-- on two huge tables
insert into schema_developer values (3, 1, 1,99);
insert into schema_developer values (3, 2, 1,32);
insert into schema_developer values (3, 3, 0,17);
insert into schema_developer values (3, 4, 1,84);

-- For the fourth schema, Pete hasn't actually
-- written a query against that schema yet.
insert into schema_developer values (4, 1, 1,99);
insert into schema_developer values (4, 2, 1,66);
insert into schema_developer values (4, 3, 1,33);

-- Commit our changes
commit;

          Followup:

not 100% following you and a little large for a "comment" -- but here is an
idea.

will

select ..., (SELECT .... <subquery> )
  from ....

be useful here.  like:

select deptno, (select count(*) from emp where emp.deptno = dept.deptno)
  from dept

is -- for removing outer joins.

      Rating: 3
Double Outer
      Join  May 07, 2003
Reviewer:  Reader
       from USA
Tom,
Have learnt a lot from this site. I have a problem:
I need all the customers from the CUSTOMER table.
There are 8 distinct PFMS_CATEGORY in the
PRODUCT_HIERARCHY table and only 7 in the
FUND_BALANCES table. What I need is the ALLOCATED_AMOUNT
for all the Customers (field =CORP_CUSTOMER_DESCRIPTION)
for all the PFMS_CATEGORY in the PRODUCT_HIERARCHY table even
if they don't have any entry for the FUND_BALANCES. For Example
CORP_CUSTOMER    PFMS_CATEGORY    sum
_DESCRIPTION     _DESCRIPTION
__________________________________________
THE LOCAL STORES  Analgesics        4321
THE LOCAL STORES  Nutritional       1234
THE LOCAL STORES  Cough/Cold
THE LOCAL STORES  Portfol
----
--
8 Records ( 1 for each Category)

But instead I am getting only two records

THE LOCAL STORES  Analgesics        4321
THE LOCAL STORES  Nutritional       1234
----
Here is my query:
select
c.CORP_CUSTOMER_DESCRIPTION,
PH.PFMS_CATEGORY_DESCRIPTION,
sum(FB.ALLOCATED_AMOUNT)
from
SPOT.PRODUCT_HIERARCHY PH,
SPOT.FUND_BALANCES FB,
SPOT.CUSTOMER C
where
FB.PFMS_CATEGORY =PH.PFMS_CATEGORY(+)
and FB.PPL_NUMBER  (+) = c.PPL_NUMBER
group by c.CORP_CUSTOMER_DESCRIPTION,
PH.PFMS_CATEGORY_DESCRIPTION

          Followup:

tell you what

when I am taking questions (from the home page) feel free to supply me a
question with

o table create statements
o inserts to populate
o sample desired output
o what you actually get

and I'll take a look see.

\

      Rating: 2
Adding Table
      Descriptions  May 08, 2003
Reviewer:  A
      Reader  from US
I missed the table Descriptions in my last post
DESC SPOT.CUSTOMER

NAME                            Null?     Type
------------------------------- --------- -----
PPL_NUMBER                      NOT NULL  VARCHAR2(10)
PPL_DESCRIPTION                           VARCHAR2(35)
CORP_CUSTOMER                             VARCHAR2(10)
CORP_CUSTOMER_DESCRIPTION                 VARCHAR2(35)

DESC SPOT.PRODUCT_HIERARCHY

NAME                            Null?     Type
------------------------------- --------- -----
HC_PC                           NOT NULL  VARCHAR2(2)
PFMS_CATEGORY                   NOT NULL  VARCHAR2(3)
BRAND                           NOT NULL  VARCHAR2(4)
BRAND_FORM                      NOT NULL  VARCHAR2(4)
BRAND_SIZE                      NOT NULL  VARCHAR2(4)
HC_PC_SHORT_DESCRIPTION                   VARCHAR2(2)
PFMS_CATEGORY_DESCRIPTION                 VARCHAR2(40)
BRAND_DESCRIPTION                         VARCHAR2(40)
BRAND_FORM_DESCRIPTION                    VARCHAR2(40)
BRAND_SIZE_DESCRIPTION                    VARCHAR2(40)
DIVISION_CODE                             VARCHAR2(2)
DIVISION_DESCRIPTION                      VARCHAR2(40)
PES_CATEGORY                              VARCHAR2(4)
PES_CATEGORY_DESCRIPTION                  VARCHAR2(40)

DESC SPOT.FUND_BALANCES

NAME                            Null?     Type
------------------------------- --------- -----
PPL_NUMBER                      NOT NULL  VARCHAR2(10)
PFMS_CATEGORY                   NOT NULL  VARCHAR2(3)
FUND_YEAR                       NOT NULL  NUMBER(4)
FUND_ID                         NOT NULL  VARCHAR2(10)
DISTRIBUTION_CHANNEL                      VARCHAR2(2)
ALLOCATED_AMOUNT                          NUMBER(13,2)
COMMITTED_AMOUNT                          NUMBER(13,2)
PAID_AMOUNT                               NUMBER(13,2)
TRANSFERRED_AMOUNT                        NUMBER(13,2)

          Followup:

see above, table CREATES, insert intos -- always appreciated.  i've no clue what
was in your test table.

insert intos are really appreciated. the output of a select * from means I have
to edit them to turn them into inserts in the end!

      Rating: 3
Query for this
      double outer join  May 26, 2003
Reviewer:
      SSA  from India
Can you pls give the query for this double outer join??

          Followup:

what double outer join?  if you mean a full outer join, look up in this page.
there are examples of 8i and before (with a union all) as well as full outer
joins in this page already.

      Rating: 3
Query for outer
      join  May 27, 2003
Reviewer:  SQL user
       from India
There was a query posted in this thread regarding outer join. I wanted to know
the excat query to achieve the required output. It would be helpful if you could
give the query.

Following are the details posted earlier.
I need all the customers from the CUSTOMER table.
There are 8 distinct PFMS_CATEGORY in the
PRODUCT_HIERARCHY table and only 7 in the
FUND_BALANCES table. What I need is the ALLOCATED_AMOUNT
for all the Customers (field =CORP_CUSTOMER_DESCRIPTION)
for all the PFMS_CATEGORY in the PRODUCT_HIERARCHY table even
if they don't have any entry for the FUND_BALANCES. For Example
CORP_CUSTOMER    PFMS_CATEGORY    sum
_DESCRIPTION     _DESCRIPTION
__________________________________________
THE LOCAL STORES  Analgesics        4321
THE LOCAL STORES  Nutritional       1234
THE LOCAL STORES  Cough/Cold
THE LOCAL STORES  Portfol
----
--
8 Records ( 1 for each Category)

But instead I am getting only two records

THE LOCAL STORES  Analgesics        4321
THE LOCAL STORES  Nutritional       1234
----
Here is my query:
select
c.CORP_CUSTOMER_DESCRIPTION,
PH.PFMS_CATEGORY_DESCRIPTION,
sum(FB.ALLOCATED_AMOUNT)
from
SPOT.PRODUCT_HIERARCHY PH,
SPOT.FUND_BALANCES FB,
SPOT.CUSTOMER C
where
FB.PFMS_CATEGORY =PH.PFMS_CATEGORY(+)
and FB.PPL_NUMBER  (+) = c.PPL_NUMBER
group by c.CORP_CUSTOMER_DESCRIPTION,
PH.PFMS_CATEGORY_DESCRIPTION

          Followup:

You have one customer record in C apparently.

It is joined to BOTH records you appear to have in FB.

These two in turn are joined to their mates in PH

I fail to see any logical relationship between C and PH here without an
associated FB record.

This is not an outer join problem at all (looks like a data model problem
perhaps).

I don't have an example, the data doesn't make sense.  No inputs, no outputs.
Simple create tables + inserts are generally **VERY** useful.  I've no clue what
data is where.

Tell you what -- when I'm taking questions, feel free to put this there.

      Rating: 5
Proper Use of
      Outer Joins  October 16, 2003
Reviewer:  Su
      Baba  from Ca, USA
Hi Tom,

I'd like to know if the following is proper use of outer joins
and also if the PL/SQL code at the bottom is at all necessary.

I have 3 tables as shown below. The objective is that for every
matching record (by product) between referrals and orders tables,
there should be one or more matching records (defined by order +
line + referral) in the accruals table. In addition, if the
order line is cancelled (orders.stauts = 'Cancelled'), we can
just ignore that line. If the above condition is met, then the
order is considered complete.

In the following example, "Output 1" shows that the current order
is not complete since there is an order line that does not have
a matching accrual.

If we create another record in accruals table for order line L3,
the order would be complete.

Is the following SQL the right approach to this problem?

thanks

SQL>
SQL> DROP TABLE referrals;

Table dropped.

SQL> DROP TABLE orders;

Table dropped.

SQL> DROP TABLE accruals;

Table dropped.

SQL>
SQL> CREATE TABLE referrals (
  2  referral     VARCHAR2(10),
  3  product      VARCHAR2(10)
  4  );

Table created.

SQL>
SQL> CREATE TABLE orders (
  2  order_id     VARCHAR2(10),
  3  line_id      VARCHAR2(10),
  4  product      VARCHAR2(10),
  5  status       VARCHAR2(10)
  6  );

Table created.

SQL>
SQL> CREATE TABLE accruals (
  2  accrual      VARCHAR2(10),
  3  order_id     VARCHAR2(10),
  4  line_id      VARCHAR2(10),
  5  referral     VARCHAR2(10)
  6  );

Table created.

SQL>
SQL>
SQL> INSERT INTO referrals VALUES ('R1', 'P1');

1 row created.

SQL> INSERT INTO referrals VALUES ('R1', 'P2');

1 row created.

SQL> INSERT INTO referrals VALUES ('R1', 'P3');

1 row created.

SQL>
SQL> INSERT INTO orders VALUES ('O1', 'L1', 'P1', 'Normal');

1 row created.

SQL> INSERT INTO orders VALUES ('O1', 'L2', 'P1', 'Cancelled');

1 row created.

SQL> INSERT INTO orders VALUES ('O1', 'L3', 'P2', 'Normal');

1 row created.

SQL>
SQL> INSERT INTO accruals VALUES ('A1', 'O1', 'L1', 'R1');

1 row created.

SQL> INSERT INTO accruals VALUES ('A2', 'O1', 'L2', 'R1');

1 row created.

SQL>
SQL> commit;

Commit complete.

SQL>
SQL> select * from referrals;

REFERRAL   PRODUCT
---------- ----------
R1         P1
R1         P2
R1         P3

SQL> select * from orders;

ORDER_ID   LINE_ID    PRODUCT    STATUS
---------- ---------- ---------- ----------
O1         L1         P1         Normal
O1         L2         P1         Cancelled
O1         L3         P2         Normal

SQL> select * from accruals;

ACCRUAL    ORDER_ID   LINE_ID    REFERRAL
---------- ---------- ---------- ----------
A1         O1         L1         R1
A2         O1         L2         R1

---------------------------OUTPUT 1-----------------------------------
SQL>
SQL>
SQL> SELECT x.referral, x.order_id, x.line_id, x.product, x.status, ACR.accrual
  2  FROM   accruals ACR,
  3    (
  4     SELECT REF.referral, ORD.order_id, ORD.line_id, ORD.product,
  5            ORD.status
  6     FROM   referrals REF, ORDERS ORD
  7     WHERE  REF.product  = ORD.product AND
  8            REF.referral = 'R1' AND
  9            ORD.status   <> 'Cancelled'
 10     ) x
 11  WHERE  x.order_id       = ACR.order_id (+) AND
 12         x.line_id        = ACR.line_id  (+) AND
 13         x.referral       = ACR.referral (+);

REFERRAL   ORDER_ID   LINE_ID    PRODUCT    STATUS     ACCRUAL
---------- ---------- ---------- ---------- ---------- ----------
R1         O1         L1         P1         Normal     A1
R1         O1         L3         P2         Normal

SQL>
SQL>
SQL>
SQL> DECLARE
  2     CURSOR c IS
  3        SELECT x.referral, x.order_id, x.line_id,
                  x.product, x.status, ACR.accrual
  4        FROM   accruals ACR,
  5          (
  6           SELECT REF.referral, ORD.order_id, ORD.line_id, ORD.product,
  7                  ORD.status
  8           FROM   referrals REF, ORDERS ORD
  9           WHERE  REF.product  = ORD.product AND
 10                  REF.referral = 'R1' AND
 11                  ORD.status   <> 'Cancelled'
 12           ) x
 13        WHERE  x.order_id       = ACR.order_id (+) AND
 14               x.line_id        = ACR.line_id  (+) AND
 15               x.referral       = ACR.referral (+);
 16
 17     i NUMBER := 0;
 18     l_order_incomplete BOOLEAN := FALSE;
 19
 20  BEGIN
 21     FOR x IN c LOOP
 22        i := i + 1;
 23        IF (x.accrual IS NULL) THEN
 24           l_order_incomplete := TRUE;
 25        END IF;
 26     END LOOP;
 27
 28     IF ((NOT l_order_incomplete) AND i > 0) THEN
 29        dbms_output.put_line('ORDER COMPLETE');
 30     ELSE
 31        dbms_output.put_line('ORDER NOT COMPLETE');
 32     END IF;
 33  END;
 34  /
ORDER NOT COMPLETE

PL/SQL procedure successfully completed.

SQL>
SQL> spool off

          Followup:

Looks ok, but you should consider that accruals just seems to be additional
attributes of ORDERS.  unless something more then I see is afoot, you might
consider just makeing the accruals attributes part of the orders entity (not
being a finance guy, that might not make sense -- i just don't know, looking at
the example, it would make sense)

      Rating: 5
Proper Use of
      Outer Joins - Followup  October 16, 2003
Reviewer:
      Su Baba  from Ca, USA
Thanks, Tom.

Orders and accruals are two separate tables because they don't even belong to
the same application. Orders belongs to Order Management while accruals belongs
to Trade Management. Also an order line can have multiple accruals.

If the accruals table is a huge table, would the performance of the query suffer
because of the outer join?

          Followup:

so?  they "belong" to different apps?  so what, order mgmt touches "its columns"
and "trade mgmt" touches its.  you don't design tables for applications, you
design tables for holding data effectively, efficiently - for all.  applications
come, applications go -- the data is key.  if these are your own applications --
time to rethink your implementation.

applications are eye candy.

data is data -- data is real.  data lives on after applications come and go.

outer joins that are not necessary can be a performance drain - however, in your
case it is "sort of moot".  unless you go with the single table - you don't have
much of a choice

      Rating: 3
Joining 50
      Tables together  December 02, 2003
Reviewer:
      Hector  from UK
Tom

I have been asked to build a table that contains a series of  Y/N flags,

e.g

create table all_flags (
ref_no   number(10),
flag_1   varchar2(1),
flag_2   varchar2(1),
flag_3   varchar2(1),
.
.
flag_50  varchar2(1));

I have 50 tables of ref_no's that refer to 'Y' for that particular flag/ref_no,
and a table of all ref_no's.
The number of records in each flag table could vary from 1 rec to 5 million,
with all_ref_no having 5 million

Would you recommend a huge outer join or a series of smaller outer joins
followed by a normal join.
Or can you suggest an alternate approach.

Method 1

create table all_flags as (
select t0.ref_no,
       nvl(F1,'N')  FLAG_01,
       nvl(F2,'N')  FLAG_02,
       nvl(F3,'N')  FLAG_03,
       .
       .
       nvl(F50,'N')  FLAG_50
from  all_ref_nos t0,
      (select ref_no,'Y' F1 from flag1_tab) t1,
      (select ref_no,'Y' F2 from flag1_tab) t2,
      (select ref_no,'Y' F3 from flag1_tab) t3,
      .
      .
      (select ref_no,'Y' F50 from flag1_tab) t50,
where t0.ref_no = t1.ref_no(+)
  and t0.ref_no = t2.ref_no(+)
  and t0.ref_no = t3.ref_no(+)
  and .
  and .
  and t0.ref_no = t50.ref_no(+));

Method 2

As above but only joining some of the tables in each query followed by

create table all_flags as (
select flag1_10.ref_no, f1, f2, f3, .. f50
from flag1_10,
     flag11_20,
     flag21_30,
     flag31_40,
     flag41_50
where flag1_10.ref_no = flag11_20.ref_no
  and flag1_10.ref_no = flag21_30.ref_no
  and flag1_10.ref_no = flag31_40.ref_no
  and flag1_10.ref_no = flag41_50.ref_no);

          Followup:

tell me what this here table will be used for -- will you look at the flags for
"a" refno at a time or what?

(cause I'm leaning towards "let's have a view so we don't have to rebuild this
redundant table all of the time")

      Rating: 3
Joining 50
      Tables together   December 02, 2003
Reviewer:
      Hector  from UK
Hi Tom

these 50 or so flags (derived from various tables within our warehouse) will
eventually be joined to another table to form an 'Account' table within a mart.
These flags are basically summary flags such as 'Had more than 5 transactions in
the past month'

Our application will then select accounts based upon these flags.

I hope this helps

          Followup:

the massive outer join

consider big enough pga_aggregate_target/hash_area_size and make sure to use the
CBO for the hash joins.

      Rating: 5
Joining 50
      Tables together   December 10, 2003
Reviewer:
      Hector  from UK
Thanks Tom

Would indexed organised tables and/or hash partitioned tables improve
performance here.

Or will it be test it and see

          Followup:

if this is your most important thing -- you should consider

NOT HAVING 50 TABLES IN THE VERY VERY VERY FIRST PLACE

if you are looking at this this hard, perhaps you need to look at the design in
the first place.  Nothing is going to make a 50 table OUTER JOIN efficient

      Rating: 1
multiple table
      join  December 11, 2003
Reviewer:  Jon McRea
       from Scotland
Tom

You often say in your follow ups  'WHY DO YOU SHOUT'

As far as I can see, Hector is just asking a simple question.

I have been following this thread with interest as I am in a very similar
position of having to create a table that contains some summary flags, and was
considering an outer join.   I note from Hectors original post that he asked "Or
can you suggest an alternate approach."

You seemed to steer him towards the outer join

Whenever anybody writes code, performance is one of the major considerations, so
I would have thought that his follow on question was a good one to ask.

          Followup:

i do not "often" say that.  and when I do it is because the entire question is
phrased in upper case.

i used upper case here for EMPHASIS.  to stress something.  to say it
emphatically.

I did not do so to offend.

"WHY do you have 50 tables" -- they are trying to speed up something, appear to
be working very hard to do so -- when the underlying design is just something
that will never lend itself towards that.

I would recommend an entire redesign of the entire underlying schema.  there is
nothing that will make a 50 table outer join "better" short of removing 50
tables.

December 11, 2003 Raj -- Thanks for the question regarding
      "DBMS_PROFILER", version 8.1.5.0.0

      You Asked
Hi Tom,

I have two questions:
Ques1. I am executing a package which calls another package(thru DB link) which
is in different instance. It was working fine but suddenly now I started getting
this error below-

ORA-02064: distributed operation not supported
ORA-06512: at "SCPEP.NDP_CON", line 145

Ques2. I have seen the use of DBMS_PROFILER package in one of your solutions
below. You have run some scripts to view the report on this package. I am just
curious about how to see the outcome of this package. What are the scripts did
you run??

I really appreciate your help.

Thanks.
Raj

--------------------------------------------------------------------
The fastest is to bite the bullet, configure sufficient rollback and do the
update in a single update statement.

The second fastest is to bit the bullet, configure sufficient rollback and do
the transaction in a single loop without commits.

The commit is what will slow you down.  You'll have to consider how you will
restart this process as well.  Watch out for the ora-01555 which will inevitably
get you if you are updating the table you are reading as well.

As for the code above, we can use the source code profiler provided in the 8i
database to analyze this.  I created the following procedures and ran them:

create or replace procedure do_mod as
    cnt number := 0;
begin
    dbms_profiler.start_profiler( 'mod' );
    for i in 1 .. 500000
    loop
        cnt := cnt + 1;
        if ( mod(cnt,1000) = 0 )
        then
            commit;
        end if;
    end loop;
    dbms_profiler.stop_profiler;
end;
/
create or replace procedure no_mod as
    cnt number := 0;
begin
    dbms_profiler.start_profiler( 'no mod' );
    for i in 1 .. 500000
    loop
        cnt := cnt + 1;
        if ( cnt = 1000 )
        then
            commit;
            cnt := 0;
        end if;
    end loop;
    dbms_profiler.stop_profiler;
end;
/
exec do_mod
exec no_mod

Now, after running the profiler reports I find:
=
=
====================
Total time

GRAND_TOTAL
------------------
      11.41
=
=
====================
Total time spent on each run

RUNID RUN_COMMENT    SECS
----- -----------    -------
    1 mod            8.18
    2 no mod         3.23
=
=
====================
Percentage of time in each module, for each run separately

RUNID RUN_COMMENT UNIT_OWNER UNIT_NAME    SECS  PERCEN
----- ----------- ---------- ------------ ----- ---------
    1    mod      OPS$TKYTE     DO_MOD     8.18  100.0
    2    no mod   OPS$TKYTE     NO_MOD     3.23  100.0
=
=
====================
Percentage of time in each module, summarized across runs

UNIT_OWNER  UNIT_NAME       SECS    PERCENTAG
----------- --------------  ------  ---------------
OPS$TKYTE    DO_MOD         8.18     71.67
OPS$TKYTE    NO_MOD         3.23     28.32
SYS          DBMS_PROFILER   .00       .00

So already, this shows that the MOD function takes longer.  Just to prove that
it is MOD
=
=
====================
Lines taking more than 1% of the total time, each run separate

RUNID HSECS  PCT  OWNER  UNIT_NAME  LINE#  TEXT
----- ------ ---- ------ ---------- ------ --------------------
    1 550.06 48.2 OPS$TKYTE  DO_MOD    9   if ( mod(cnt,1000) = 0 )
    1 135.22 11.9 OPS$TKYTE  DO_MOD    8   cnt := cnt + 1;
    2 107.71  9.4 OPS$TKYTE  NO_MOD    8   cnt := cnt + 1;
    2 104.34  9.1 OPS$TKYTE  NO_MOD    9   if ( cnt = 1000 )
    1  67.95  6.0 OPS$TKYTE  DO_MOD    6   for i in 1..500000
    2  64.66  5.7 OPS$TKYTE  NO_MOD   12   cnt := 0;
    1  64.64  5.7 OPS$TKYTE  DO_MOD   11   commit;
    2  44.99  3.9 OPS$TKYTE  NO_MOD    6   for i in 1..500000

8 rows selected.

that clinches it -- using MOD took about 5.5 seconds, doing if ( cnt=1000 ) took
1 second PLUS the time to do cnt := 0 gives a grant total of about 1.5 seconds.

5.5 for mod
1.5 for cnt=1000; cnt:=0;

      and we said...
1) sounds like somebody modified the remote procedure and added a COMMIT or
ROLLBACK or something that does a commit or rollback implicitly (DDL for
example).  Find out what has changed in the remote procedure -- you cannot
commit in a remote procedure -- only the invoking procedure can do that.

2)

Your DBA may have to install the profiler in your database.   The procedure for
installing this package is simple:

ƒá cd $ORACLE_HOME/rdbms/admin
ƒá Using SVRMGRL you would connect as SYS or INTERNAL
ƒá Run profload.sql

In order to actually use the profiler after that, you will need to have the
profiling tables installed.  You can install these once per database but I
recommend each developer would have their own copy.  Fortunately, the
DBMS_PROFILER package is built with invokers rights and unqualified tablenames
so that we can install the tables in each schema and the profiler package will
use them.  The reason you each want your own tables is so that you only see the
results of your profiling runs, not those of your coworkers.  In order to get a
copy of the profiling tables in your schema, you would run
$ORACLE_HOME\rdbms\admin\proftab in SQLPlus.  After you run proftab.sql, you¡¦ll
need to run profrep.sql as well.  This script creates views and packages to
operate on the profiler tables in order to generate reports.  This script is
found in $ORACLE_HOME\plsql\demo\profrep.sql.  You should run this in your
schema as well after creating the tables.

I like to keep a small script around to reset these tables (clear them out).
After I¡¦ve done a run or two and have analyzed the results ¡V I run this script
to reset the tables.  I have the following in a script I call profreset.sql:

delete from plsql_profiler_data;
delete from plsql_profiler_units;
delete from plsql_profiler_runs;

finally, and to answer the question, you will find
$ORACLE_HOME/plsql/demo/profsum.sql.  that is what generates the report.

Review &
      Followup

      Rating:
      4
dbms_profiler  May 23, 2001
Reviewer:
      Pramod  from Raleigh, NC

      Rating:
      4
dbms_profiler  May 23, 2001
Reviewer:
      Pramod  from Raleigh, NC

      Rating:
      5
DBMS_PROFILER  October 15, 2001
Reviewer:
      Milen Alexiev  from Germany
Very detailed  and  clear explanations and instructions
how to install  the package

      Rating:
      5
  May 03, 2002
Reviewer:
      A reader  from Ca, USA
Hi Tom,

Can you please post the script (profsum.sql) you have on p. 1164 ~ 1166 of your
book on this website?  I tried many times to find it on www.wrox.com, but could
never find it.

thanks

          Followup:

set echo off
set linesize 5000
set trimspool on
set serveroutput on
set termout off

column owner format a11
column unit_name format a14
column text format a21 word_wrapped
column runid format 9999
column secs  format 999.99
column hsecs format 999.99
column grand_total  format 9999.99
column run_comment format a11 word_wrapped
column line# format 99999
column pct format 999.9
column unit_owner format a11

spool profsum.out

/* Clean out rollup results, and recreate */
update plsql_profiler_units set total_time = 0;

execute prof_report_utilities.rollup_all_runs;

prompt =
prompt =
prompt ====================
prompt Total time
select grand_total/1000000000 as grand_total
  from plsql_profiler_grand_total;

prompt =
prompt =
prompt ====================
prompt Total time spent on each run
select runid,
       substr(run_comment,1, 30) as run_comment,
       run_total_time/1000000000 as secs
  from (select a.runid, sum(a.total_time)  run_total_time, b.run_comment
          from plsql_profiler_units a, plsql_profiler_runs b
         where a.runid = b.runid group by a.runid, b.run_comment )
 where run_total_time > 0
 order by runid asc;

prompt =
prompt =
prompt ====================
prompt Percentage of time in each module, for each run separately

select p1.runid,
       substr(p2.run_comment, 1, 20) as run_comment,
       p1.unit_owner,
       decode(p1.unit_name, '', '<anonymous>',
                    substr(p1.unit_name,1, 20)) as unit_name,
       p1.total_time/1000000000 as secs,
       TO_CHAR(100*p1.total_time/p2.run_total_time, '999.9') as percentage
  from plsql_profiler_units p1,
       (select a.runid, sum(a.total_time)  run_total_time, b.run_comment
          from plsql_profiler_units a, plsql_profiler_runs b
         where a.runid = b.runid group by a.runid, b.run_comment ) p2
 where p1.runid=p2.runid
   and p1.total_time > 0
   and p2.run_total_time > 0
   and  (p1.total_time/p2.run_total_time)  >= .01
 order by p1.runid asc, p1.total_time desc;

column secs form 9.99
prompt =
prompt =
prompt ====================
prompt Percentage of time in each module, summarized across runs
select p1.unit_owner,
       decode(p1.unit_name, '', '<anonymous>', substr(p1.unit_name,1, 25)) as
unit_name,
       p1.total_time/1000000000 as secs,
       TO_CHAR(100*p1.total_time/p2.grand_total, '99999.99') as percentage
  from plsql_profiler_units_cross_run p1,
       plsql_profiler_grand_total p2
 order by p1.total_time DESC;

prompt =
prompt =
prompt ====================
prompt Lines taking more than 1% of the total time, each run separate
select p1.runid as runid,
       p1.total_time/10000000 as Hsecs,
        p1.total_time/p4.grand_total*100 as pct,
       substr(p2.unit_owner, 1, 20) as owner,
       decode(p2.unit_name, '', '<anonymous>', substr(p2.unit_name,1, 20)) as
unit_name,
       p1.line#,
       ( select p3.text
           from all_source p3
          where p3.owner = p2.unit_owner and
                p3.line = p1.line# and
                p3.name=p2.unit_name and
                p3.type not in ( 'PACKAGE', 'TYPE' )) text
  from plsql_profiler_data p1,
       plsql_profiler_units p2,
       plsql_profiler_grand_total p4
 where (p1.total_time >= p4.grand_total/100)
   AND p1.runID = p2.runid
   and p2.unit_number=p1.unit_number
 order by p1.total_time desc;

prompt =
prompt =
prompt ====================
prompt Most popular lines (more than 1%), summarize across all runs
select p1.total_time/10000000 as hsecs,
        p1.total_time/p4.grand_total*100 as pct,
       substr(p1.unit_owner, 1, 20) as unit_owner,
       decode(p1.unit_name, '', '<anonymous>',
                 substr(p1.unit_name,1, 20)) as unit_name,
       p1.line#,
       ( select p3.text from all_source p3
          where (p3.line = p1.line#) and
                (p3.owner = p1.unit_owner) AND
                (p3.name = p1.unit_name) and
                (p3.type not in ( 'PACKAGE', 'TYPE' ) ) ) text
  from  plsql_profiler_lines_cross_run p1,
        plsql_profiler_grand_total p4
 where (p1.total_time >= p4.grand_total/100)
 order by p1.total_time desc;

execute prof_report_utilities.rollup_all_runs;

prompt =
prompt =
prompt ====================
prompt  Number of lines actually executed in different units (by unit_name)

select p1.unit_owner,
       p1.unit_name,
       count( decode( p1.total_occur, 0, null, 0))  as lines_executed ,
       count(p1.line#) as lines_present,
       count( decode( p1.total_occur, 0, null, 0))/count(p1.line#) *100
                                       as pct
  from plsql_profiler_lines_cross_run p1
 where (p1.unit_type in ( 'PACKAGE BODY', 'TYPE BODY',
                          'PROCEDURE', 'FUNCTION' )  )
 group by p1.unit_owner, p1.unit_name;

prompt =
prompt =
prompt ====================
prompt  Number of lines actually executed for all units
select count(p1.line#) as lines_executed
  from plsql_profiler_lines_cross_run p1
 where (p1.unit_type in ( 'PACKAGE BODY', 'TYPE BODY',
                          'PROCEDURE', 'FUNCTION' )  )
    AND p1.total_occur > 0;

prompt =
prompt =
prompt ====================
prompt  Total number of lines in all units
select count(p1.line#) as lines_present
  from plsql_profiler_lines_cross_run p1
 where (p1.unit_type in ( 'PACKAGE BODY', 'TYPE BODY',
                          'PROCEDURE', 'FUNCTION' )  );

spool off
set termout on
edit profsum.out
set linesize 131

      Rating: 4
Can you explain
      it a bit more, preferably with an example......  May 06,
      2002
Reviewer:  Ashok  from Seoul
Hi Tom
You said "Find out what has changed in the remote procedure -- you cannot commit
in a remote procedure -- only the invoking procedure can do that."
When you say 'Commit in a remote procedure', does it mean that the remote
procedure should not have a commit statement in it? Also if a DDL is allowed via
a remote procedure then why should a commit not be given in it? I hope my
question/doubt is properly phrased.

      Rating: 3
Can you clear
      my doubt which i had posted in my earlier review?  May 08,
      2002
Reviewer:  Ashok

          Followup:

the remote procedure should have no transaction control statements in it, DDL
commits (transaction control) hence no DDL should be done (use DBMS_JOB to
remotely SCHEDULE a job to do the DDL after you commit)

      Rating:
      4
  March 08, 2003
Reviewer:  Mike
       from TX
"
the remote procedure should have no transaction control statements in it, DDL
commits (transaction control) hence no DDL should be done (use DBMS_JOB to
remotely SCHEDULE a job to do the DDL after you commit)
"

I am not very sure what you meant in above. Could you please give an example
(demo)

Thanks

          Followup:

If you have my book "Expert one on one Oracle" -- i cover this but what I mean
is you should not do DDL in the remote procedure, the remote procedure should
instead do:

  ...
  dbms_job.submit( l_job, 'execute immediate ''create table t ( x int )'';' );
  .....

Now, when the remote procedure is committed, the job will become visible and
will run shortly after.

      Rating: 4
Which chapter
      covers that?  March 08, 2003
Reviewer:  Mike
       from TX

          Followup:

I talk about it in the autonomous transaction chapter.

I demonstrate DDL (like analyze and such) in the necessary supplied packages
appendix on dbms_job.

Doing DDL is not really any different then calling a procedure at the end of the
day -- it's just an execute immediate.

      Rating:
      4
ORA-2064  March 18, 2003
Reviewer:
      Sean  from Atlanta
Tom,
I want to test/recreate on ORA-02064. But I am confused after all.
ORA8i -- remote db, jnas -- local db

owb_repos@ora8i> drop table test;

Table dropped.

owb_repos@ora8i> CREATE TABLE TEST (ID NUMBER, NAME VARCHAR2(25));

Table created.

owb_repos@ora8i> create or replace package call_fdm is
  2  procedure pop_test(p_id in number, p_name in varchar2);
  3  end call_fdm;
  4  /

Package created.

owb_repos@ora8i>
owb_repos@ora8i> create or replace package body call_fdm
  2  is
  3  procedure pop_test(p_id in number, p_name in varchar2)
  4  is
  5  begin
  6  insert into test values(p_id, p_name);
  7  commit;
  8  end;
  9  End call_fdm;
 10  /

Package body created.

owb_repos@ora8i>
owb_repos@ora8i> create or replace package body call_fdm
  2  is
  3  procedure pop_test(p_id in number, p_name in varchar2)
  4  is
  5  begin
  6  insert into test values(p_id, p_name);
  7  commit;
  8  end;
  9  End call_fdm;
 10  /

Package body created.

owb_repos@ora8i>

You see I have COMMIT in the remote packge.

From the local JNAS
owb_repos@jnas> exec call_fdm.pop_test@ORA8I_OWBLINK.US.ORACLE.COM(4,'d');

PL/SQL procedure successfully completed.

owb_repos@jnas> select * from test@ORA8I_OWBLINK.US.ORACLE.COM;
        ID NAME
---------- -------------------------
         4 d

Why Oracle did not return with ORA-02064?

          Followup:

they are trying to relax restrictions all of the time...

I will say I sometimes wish plsql did not support commit and rollback at all.  I
firmly believe transaction control belongs in the CLIENT and no where else.

      Rating:
      4
  March 18, 2003
Reviewer:  Sean
       from Atlanta
ORA-02064 returned as COMMIT with an OUT parameter.
I have just read it document carefully.

      Rating: 3
Please
      explain  August 06, 2003
Reviewer:  Yogesh
       from Pune, India
I was going thru the doc I came across following explainations for ORA-02064
distributed operation not supported

Cause: One of the following unsupported operations was attempted:

Array execute of a remote update with a subquery that references a database
link, or

What is array execute ?

An update of a long column with bind variable and an update of a second column
with a subquery that both references a database link and a bind variable, or

A commit is issued in a coordinated session from an RPC with OUT parameters.

can't understand 2nd and 3rd .. can you please explain in simple words ...

          Followup:

it is a bulk bind in plsql talk.  when you bind a host array with many inputs.
in pro*c might look like this:

int  x[50];

  ... code to fill up x....

  exec sql update t@remote_site set x = :x where exists ( select null from
t@remote_site where y = :x );

for example -- that update will be executed 50 times by the server since we used
an array bind.

the

"An update of a long column with bind variable and an update of a second column
with a subquery that both references a database link and a bind variable, or"

seems obvious

update t
  set LONG_COLUMN = :x,
      y = (select y from t2@remote where z = :z )

and the third just means "you called a remote stored procedure (RPC) from a
distributed transaction and the remote stored procedure has OUT parameters"

      Rating: 3
More
      Info  August 06, 2003
Reviewer:  Yogesh
       from Pune India
My application is designed in such a way that remote procedure will send a Table
type and my proc will process entire records in that table and then send a set
of errors (if any) for those records in the table .....

Now if I can't commit in that proc and lets say first record from my table type
updated record no 10, column c1  in one database table .. now my second update
again trying to update same record but some different column .. lets say c2 ..
but as old changes are not comitted this update don't work ...

Is my understand right ? if yes what could be the solution in such scenarios

          Followup:

why won't the update work?  you can see your changes, you just cannot "see"
anyone elses uncommitted changes.

      Rating: 4
More
      Info  August 06, 2003
Reviewer:  Yogesh
       from Pune, India
Thanks for the explanation .... I was getting the error due to 3 i.e. commit.
But the scenario whcih I have mentioned is something like following

I get following from remote db call

featurename featurevalue
abcd            10
xyz             20

now my code read that record by record.

so I have first record which is mapped to table1, row1, column1, so I issued a
command

update table1 set column1=10 where row1=abcd

there is no commit after that as I get error, now second statement

update table1 set column2=20 where row1=xyz

I'm facing problem here... when it tried updating table .. it waited lond and
gave me "dead lock" error and came out. What could be causing this error ?

But when I added commit in the code, and tested locally (not using dblink) ...
it worked fine.  I could not make out what was causing prob!!! can you please
suggest something

          Followup:

that row, "where row1=xyz" is locked by someone else -- it is that simple.

      Rating: 4
DBMS_Profiler -
      Time Statistics  August 06, 2003
Reviewer:
      Steven Cooper  from England
Tom,

I am using your profsum.sql script to test a basic pl/sql procedure / trigger
combination to insert 10 rows into a dummy table just to get the hang of
DBMS_Profiler, but I'm not completely convinced about the timed statistics it is
showing me. I ran the below code and it all returned instantly, but according to
DBMS_Profiler it took 6.6 Secs, It no way took that long:
Is it me or is time running fast in england?  :)

Heres the Code:

@reset_prof
drop table t;
drop procedure test;
drop sequence t_seq;

create table t
(
x int,
y date
)
/
create sequence t_seq start with 1 increment by 1 cache 10;

create or replace trigger t_bi
before insert on t for each row
begin
  select     t_Seq.nextval
  into        :new.x
  from        dual;
end;
/

create or replace procedure test
as
begin
for x in 1 .. 10
loop
  insert into t values (null,sysdate);
end loop;
end;
/

EXEC DBMS_PROFILER.START_PROFILER('Test');
exec test;
EXEC DBMS_PROFILER.STOP_PROFILER;

Heres the profiler report:

7 rows updated.

PL/SQL procedure successfully completed.

=
=
====================
Total time

GRAND_TOTAL
-----------
       6.61

=
=
====================
Total time spent on each run

RUNID RUN_COMMENT    SECS
----- ----------- -------
   10 Test           6.61

=
=
====================
Percentage of time in each module, for each run separately

RUNID RUN_COMMENT UNIT_OWNER  UNIT_NAME         SECS PERCEN
----- ----------- ----------- -------------- ------- ------
   10 Test        BANK        TEST              4.28   64.8
   10 Test        BANK        T_BI              2.21   33.4

=
=
====================
Percentage of time in each module, summarized across runs

UNIT_OWNER  UNIT_NAME       SECS PERCENTAG
----------- -------------- ----- ---------
BANK        TEST            4.28     64.79
BANK        T_BI            2.21     33.38
<anonymous> <anonymous>      .12      1.83

=
=
====================
Lines taking more than 1% of the total time, each run separate

RUNID   HSECS    PCT OWNER       UNIT_NAME       LINE# TEXT
----- ------- ------ ----------- -------------- ------ ---------------------
   10  426.33   64.5 BANK        TEST                6 insert into t values
                                                       (null,sysdate);

   10  219.21   33.2 BANK        T_BI                2

=
=
====================
Most popular lines (more than 1%), summarize across all runs

  HSECS    PCT UNIT_OWNER  UNIT_NAME       LINE# TEXT
------- ------ ----------- -------------- ------ ---------------------
 426.33   64.5 BANK        TEST                6 insert into t values
                                                 (null,sysdate);

 219.21   33.2 BANK        T_BI                2
  12.07    1.8 <anonymous> <anonymous>         1

PL/SQL procedure successfully completed.

=
=
====================
Number of lines actually executed in different units (by unit_name)

UNIT_OWNER  UNIT_NAME      LINES_EXECUTED LINES_PRESENT    PCT
----------- -------------- -------------- ------------- ------
BANK        TEST                        3             3  100.0

=
=
====================
Number of lines actually executed for all units

LINES_EXECUTED
--------------
             3

=
=
====================
Total number of lines in all units

LINES_PRESENT
-------------
            3

Also I ran the normal profsum.sql in [Oracle_Home]\plsql\demo and that reports
the same times.  any ideas other than I have no concept of time or my watch
stopped? :)

Thanks in advance
Steven

          Followup:

version?  (platform is obvious - the one that couldn't figure out the rest of
the free world uses \ as the escape character...)

      Rating:
      5
Oooops!  August 10, 2003
Reviewer:
      Steven Cooper  from England
Sorry Tom ,

We just had a heat wave over here (26C) ;) it must have gone to my head. or
maybe I typed too quick for windoZZZZzzzzzzz (2000) to keep up.

Anyway I'm running a local version of 8.1.7. on my desktop Win 2K machine and it
was the only session running.

Cheers

Steven

          Followup:

unfortunately for you (for most fortunate for me), I do not have a windoze
machine to test with.

On my solaris box -- 8174 -- this worked just fine (when I wrote expert one on
one, I was using windows -- version 816 -- and it worked there as well).

when I run this I get:

9 rows updated.

PL/SQL procedure successfully completed.

=
=
====================
Total time

GRAND_TOTAL
-----------
        .02

=
=
====================
Total time spent on each run

RUNID RUN_COMMENT    SECS
----- ----------- -------
    1 Test            .02

....

Is there a chance your numbers are "100 times greater"?  Perhaps it is that --
if you set timing on, does it seem to run in about 0.6 seconds?

      Rating:
      5
  August 10, 2003
Reviewer:  Steven
      Cooper  from England
Tom,

I ran the test again with timing on:

scott@dev> exec test;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.70

I then ran it again with DBMS_Profiler:

Elapsed: 00:00:00.21

GRAND_TOTAL
-----------
      13.65

Elapsed: 00:00:00.60
=
=
====================
Total time spent on each run

RUNID RUN_COMMENT    SECS
----- ----------- -------
   10 Test           6.61
   11 Test2          7.04  <-- This is the Second test

If I adjust your summary script and divide by ten zeros instead of 9 I get:

Elapsed: 00:00:00.20
=
=
====================
Total time

GRAND_TOTAL
-----------
       1.36

Elapsed: 00:00:00.10
=
=
====================
Total time spent on each run

RUNID RUN_COMMENT    SECS
----- ----------- -------
   10 Test            .66
   11 Test2           .70

Elapsed: 00:00:00.10

This looks more like it.  what do you think?

Steven

          Followup:

I would concurr -- I see no "bugs" filed against it -- but it does look like an
order of 100 problem for some reason.

      Rating: 5
ORA-02064 and
      out parameters  August 20, 2003
Reviewer:
      Yogesh  from Pune
what is the reason why we can't use OUT parameters in a RPC call ? asking just
for curiosity.

Secondly, can I make any transaction autonomous by using PRAGMA
AUTONOMOUS_TRANSACTION; especially when transaction is a RPC. If no then is
there any other way we can make a part of transaction autonomous in case of RPC
?

          Followup:

you can?

ops$tkyte@ORA920> variable x number
ops$tkyte@ORA920> print x

         X
----------

ops$tkyte@ORA920> exec p@aria_dev( :x )

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> print x

         X
----------
        55

ops$tkyte@ORA920>

you cannot do autonomous transactions over dblink.  documented restriction.

      Rating: 5
dbms_profiler
      reports  August 23, 2003
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
Tom,
We use ref cursor in all stored procs. If I use profiler,  will it give correct
results since the query associated with ref cursor is not executed till I do a
fetch from it. I do as:
a) Start profiler
b) exec package.stored_proc(ref_cursor);
c) Stop profiler
d) Run report
What would be the right way to do this?
Thanks

          Followup:

the profiler only shows code, not sql execution.  for sql execution -- use
tkprof and sql_trace

      Rating:
      5
ORA-02064  September 01, 2003
Reviewer:
      Yogesh  from Pune India
I'm getting ORA-02064 exception when a procedure is being called over dblink and
when that procedure tries to create a table dynamically ... it works from other
user  from the same instance .. but not on dblink ... what is the solution ?

my procdure which creates the table dynamically have authid as definer ...

          Followup:

[tkyte@tkyte-pc-isdn tkyte]$ oerr ora 2064
02064, 00000, "distributed operation not supported"
// *Cause: One of the following unsupported operations was attempted:
//         1. array execute of a remote update with a subquery that references
//            a dblink, or
//         2. an update of a long column with bind variable and an update of
//            a second column with a subquery that both references a dblink
//            and a bind variable, or
//         3. a commit is issued in a coordinated session from an RPC with
//            OUT parameters.
// *Action: simplify remote update statement
[tkyte@tkyte-pc-isdn tkyte]$

(ddl commits)

That aside, excessively poor practice to be creating objects in a stored
procedure -- it is slow, it is error prone, it is just all around "bad".  You
should rethink your approach.  the object in most cases should exist already.

some of the exceptions might be a maintainence routine to implement a sliding
window on a partitioned table or something -- but in general -- my eyebrow goes
up a notch when someone says "trying to create a table in a procedure..."

      Rating: 4
Error while
      creating dbms_profiler package  December 09, 2003

Reviewer:  p.s.vinodh  from India, chennai

hi tom,
iam trying to creat dbms_profile server side package but it is showing error i
could not able to trace it , can you throw some light on it tom.

i connected  user-- sys as sysdba
and then  executed profload file but it is showing invalid sql error so i tried
to execute the part by part to i executed dbmspbp it executed fine . next i
executed prvtpbp.plb but its is showing error as it is wrapped procedure i not
able to trace what was the problem.

kindly help me out to tom.

thanks in advance.

Connected to Oracle9i Enterprise Edition Release 9.2.0.1.0
Connected as sys

SQL> @F:\ORACLE\ORA92\RDBMS\ADMIN\dbmspbp.sql

Package created

Grant succeeded

Synonym created

SQL> @F:\ORACLE\ORA92\RDBMS\ADMIN\prvtpbp.plb

0
0
9
2
0 a0 109 :2 a0 ff 1d 17 b5
9
2
0 3 7 b f 13 17 18
21
9
2
0 1 d 1f 2a :4 1
9
4
0 :8 1
23
4
:3 0 1 :3 0 2
:3 0 3 :3 0 4
:3 0 2 0 3
:2 0 6 5 8
:8 0
3
4
:3 0
1
4
0
7
0
1
14
0
0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0
0

ORA-00900: invalid SQL statement

Warning: Package body created with compilation errors

SQL> show err
Errors for PACKAGE BODY SYS.DBMS_PROFILER:

LINE/COL ERROR
--------
----------------------------------------------------------------------------
21/11    PLS-00201: identifier 'SYS.DBMS_PROFILER_LIB' must be declared
16/3     PL/SQL: Item ignored
34/11    PLS-00201: identifier 'SYS.DBMS_PROFILER_LIB' must be declared
32/3     PL/SQL: Item ignored
43/11    PLS-00201: identifier 'SYS.DBMS_PROFILER_LIB' must be declared
41/3     PL/SQL: Item ignored
51/11    PLS-00201: identifier 'SYS.DBMS_PROFILER_LIB' must be declared
49/3     PL/SQL: Item ignored
59/11    PLS-00201: identifier 'SYS.DBMS_PROFILER_LIB' must be declared
57/3     PL/SQL: Item ignored
69/11    PLS-00201: identifier 'SYS.DBMS_PROFILER_LIB' must be declared
67/3     PL/SQL: Item ignored
89/9     PLS-00307: too many declarations of 'INTERNAL_VERSION_CHECK' match this
call
89/5     PL/SQL: Statement ignored
111/9    PLS-00307: too many declarations of 'INTERNAL_VERSION_CHECK' match this
call
111/5    PL/SQL: Statement ignored
140/9    PLS-00307: too many declarations of 'INTERNAL_VERSION_CHECK' match this
call
140/5    PL/SQL: Statement ignored
152/9    PLS-00307: too many declarations of 'INTERNAL_VERSION_CHECK' match this
call
152/5    PL/SQL: Statement ignored

SQL>

          Followup:

you use @profload.sql to install this package.

      Rating: 4
dbms_profiler
      errorr  December 09, 2003
Reviewer:
      p.s.vinodh  from India, Chennai
Hi tom,
thanks for your prompt reply ,
well i tried that, but it is showing same error to identify that, i tried to run
the script which is refered inside profload.sql.

ok here i will paste the error message which comes if i run profload.sql file

thanks,

Connected to Oracle9i Enterprise Edition Release 9.2.0.1.0
Connected as sys

SQL> @F:\ORACLE\ORA92\RDBMS\ADMIN\profload

Package created

Grant succeeded

Synonym created

0
0
9
2
0 a0 109 :2 a0 ff 1d 17 b5
9
2
0 3 7 b f 13 17 18
21
9
2
0 1 d 1f 2a :4 1
9
4
0 :8 1
23
4
:3 0 1 :3 0 2
:3 0 3 :3 0 4
:3 0 2 0 3
:2 0 6 5 8
:8 0
3
4
:3 0
1
4
0
7
0
1
14
0
0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0
0

ORA-00900: invalid SQL statement

Warning: Package body created with compilation errors

Testing for correct installation
PACKAGE BODY sys.DBMS_PROFILER - missing or invalid

PL/SQL procedure successfully completed

SQL>

          Followup:

what sqlplus are you using -- try the command line one and make sure its the
9ir2 version.

      Rating:
      5
Thanks,  December 10, 2003
Reviewer:
      p.s.vinodh  from India,Chennai
Hi tom,
Thanks, Actually i was using 8.0.4 SQLPLUS version.
It is working fine with 9iR2 version tom..

i worked with profiler as in your book.Thats working great and its very much
useful to tune and identify the bottleneck in procedures and packages.

Thanks,

      Rating: 5
My grand_total
      is 0  December 10, 2003
Reviewer:  Lewis
       from St Pete, Florida, USA
I've run this many times now and I keep getting a grand_total of sero which
raises several divide by zero errors.  All of the timings in the profiler tables
are zero.  I've run your profsum and the demo with the same results.

timed_statistics was false so I changed that to true but still no love.

Any ideas?

Thanks,

Lewis

          Followup:

are you running something that actually takes a MEASURABLE amount of time?

how's about an example?

      Rating: 5
The overall
      process is taking about 10 minutes  December 10, 2003

Reviewer:  Lewis  from St Pete, Florida, USA

It's a process that's reading a few thousand rows for about 10 tables and
aggregating them and uses utl_file to create a file to send to an external
system.  There is definately time involved here.  I can't give this as an
example without giving you a bunch of packages and an export of the database.
It does take time though.

To test that it wasn't something wierd in my code, I wrote a procedure that
looped 1000 times and inserted into a table each time through the loop.  Same
thing, 0 on total_time and min/max times.

It doesn't matter if I do 1000 times or 10000, or 250000.  Always 0 for all
times.  I'm in 8.1.7.4.  timed_statistics is NOT set for the DB.  I did an alter
session to turn timed_statistics on.  Does that make any difference?

I can send you anything you might need, my code example, DB parameters, the
actual values in the profiler tables, etc.  Just let me know.

Thanks,

Lewis

          Followup:

what platform are you using?

      Rating: 5
0
      totals  December 11, 2003
Reviewer:  Lewis
       from St Pete, FL, USA
I'm on Compaq TRU64.

Thanks,

Lewis

          Followup:

Profiling makes use of the underlying time functions available on the Operating
System. Solaris, AIX and HP support time functions with nanosecond granularity.
 In particular they support the gethrtime() time  function which returns time
with nanosecond granularity. Tru64, Data General and VMS support time functions
with  millisecond granularity. On these  platforms profiling can only support up
to millisecond granularity.  Nanosecond granularity is needed to get decent
profiling data on "lines of code that execute really fast".

so, you should have some aggregate times but the code will appear to run
"infinitely fast" since the underly OS you are using does not support a clock
with a fine enough resolution.

      Rating: 5
Wow! Thank
      you  December 11, 2003
Reviewer:  Lewis
       from St Pete, FL, USA
I would have beat my head against the wall forever over that one.

December 11, 2003 -- Thanks for the question regarding "Summing the
      values from multiple tables", version 9.0.1

      You Asked
Hi Tom,

I've got 3 tables with the same set of columns.

Table 1: Morn_Call
Columns: Call_code    Call_Duration   Call_amt
Values:    CH               3            23.00
           TS               4            45.00

Table 2: Noon_Call
Columns: Call_code    Call_Duration   Call_amt
Values:    WC               3            23.00
           TS               4            45.00
           FC               5            70.00
           CH               2             3.00

Table 3: Eve_Call
Columns: Call_code    Call_Duration   Call_amt
Values:    CH               3            23.00
           WC               4            48.00
           XL               2            90.00

I wanted to make a summary by call_code from the three tables. Is it possible to
do it in one query? What's the best approach to do this?

Here's the result I wanted to achieve.
         Call_code    Call_Duration   Call_amt
           CH               8            49.00
           TS               8            90.00
           FC               5            70.00
           WC               7            71.00
           XL               2            90.00

Thanks,
Chneih

      and we said...
hmmm, looks like a "bad design" - you should rethink these sorts of designs.
looks like you really wanted a single table with a TIME column.

Anyway

select call_code, sum(call_duration), sum(call_amount)
from ( select * from morn_call UNION ALL select * from noon_call UNION ALL
select * from eve_call )
group by call_code

or

select call_code, sum(call_duration), sum(call_amount)
from (
select call_code, sum(call_duration) call_duration, sum(call_amount) call_amount

from morn_call group by call_code
UNION ALL
select call_code, sum(call_duration) call_duration, sum(call_amount) call_amount
from noon_call  group by call_code
UNION ALL
select call_code, sum(call_duration) call_duration, sum(call_amount) call_amount
from eve_call  group by call_code)
group by call_code

Review &
      Followup

      Rating:
      5
Excellent  December 11, 2003
Reviewer:
      chneih  from USA
I tried to bribe our analyst to change the design but it didn't work....Anyway,
thanks a lot!

December 11, 2003 -- Thanks for the question regarding "Date
      Format", version 8.1.7

      You Asked
Hi Tom,

I was browsing thru one of the certification exam (1Z0-001 -  Introduction to
Oracle8i: SQL and PL/SQL) and came acrosss the following question:

What will be the result of this query ?

SELECT
  LOWER(
     SUBSTR(
          TO_CHAR(
               TO_DATE('10-JUN-2000','DD-MON-YYYY')+6,'DD-Month-YY'),8))
FROM DUAL;

a) "16-June-"
b) "16-JUN-2000"
c) "16-jun-2"
d) "10-JUNE-"
e) "10-june-00"
f) "16-june-"

I was pretty much sure that the answer is "f", but to make sure I ran the query
and the result is:

SQL> SELECT
  2    LOWER(
  3       SUBSTR(
  4            TO_CHAR(
  5                 TO_DATE('10-JUN-2000','DD-MON-YYYY')+6,'DD-Month-YY'),8))
  6  FROM DUAL;

LOWER(SU
--------
     -00

Please explain..

Thankx,
SB

      and we said...
Peeling back the layers (remove the lower and substr) we see:

ops$tkyte@ORA817DEV.US.ORACLE.COM> SELECT
  2            TO_CHAR(
  3                 TO_DATE('10-JUN-2000','DD-MON-YYYY')+6,'DD-Month-YY')
  4  FROM DUAL
  5  /

TO_CHAR(TO_DATE
---------------
16-June     -00

That is correct -- we format the Month into a field big enough to hold the
longest named month....

So, when we substr FROM 8 on.... we get:

ops$tkyte@ORA817DEV.US.ORACLE.COM> SELECT
  2    LOWER(
  3       SUBSTR(
  4            TO_CHAR(
  5                 TO_DATE('10-JUN-2000','DD-MON-YYYY')+6,'DD-Month-YY'),8))
  6  FROM DUAL
  7  /

LOWER(SU
--------
     -00

I think they are missing the 1, in there:

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> SELECT
  2    LOWER(
  3       SUBSTR(
  4            TO_CHAR(
  5
TO_DATE('10-JUN-2000','DD-MON-YYYY')+6,'DD-Month-YY'),1,8))
  6  FROM DUAL
  7  /

LOWER(SU
--------
16-june

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

So, the really cool thing here is

a) the query probably has a typo
b) even if you fix the query, none of the answers are right.  There is a space
after june, not a -

;)

Maybe the query was to be:

ops$tkyte@ORA817DEV.US.ORACLE.COM> SELECT
  2    LOWER(
  3       SUBSTR(
  4            TO_CHAR(
  5                 TO_DATE('10-JUN-2000','DD-MON-YYYY')+6,
  6                                             'fmDD-Month-YY'),1,8))
  7  FROM DUAL
  8  /

LOWER(SU
--------
16-june-

ops$tkyte@ORA817DEV.US.ORACLE.COM>
Review &
      Followup

      Rating: 5
Date Format
      'fmdd-month-yy'  August 14, 2002
Reviewer:
      SB  from CA
Hi Tom,
The format you used in the query 'fmdd-month-yy' does not return the YEAR in
2-digits.

 SELECT
       LOWER(
           SUBSTR(
                TO_CHAR(
                     TO_DATE('10-JUN-2000','DD-MON-YYYY')+6,
                                  'fmDD-Month-YY'),1,20))
    FROM DUAL
SQL> /

LOWER(SUB
---------
16-june-0

SQL> select to_char(sysdate,'fmDD-Month-YY') from dual;

TO_CHAR(SYSDATE
---------------
14-August-2

Is this the intended behaviour for this format? Please explain.

Moreover, before submitting my review, I tried to search in your website for the
format 'fmDD-Month-YY' and it retrieved zero hits. It should have atleast
fetched my question, right?

Thankx,
SB

          Followup:

fm removes whitespace and leading zeroes.

But, you can control whether its on or off:

ops$tkyte@ORA9I.WORLD> select to_char(sysdate,'fmDD-Month-YY') from dual
  2  /

TO_CHAR(SYSDATE
---------------
14-August-2

ops$tkyte@ORA9I.WORLD> select to_char(sysdate,'DD-fmMonthfm-YY') from
dual
  2  /

TO_CHAR(SYSDATE
---------------
14-August-02

As for the search, it returns this one for me, but only cause you put in
fmDD-Month-YY !!!

Me, I put in <b>fm</b>DD-Month-YY :)

ahh, html -- there is tags in that there text.

      Rating:
      5
  August 14, 2002
Reviewer:  SB
       from CA
Got it !! Thankx Again !!!!

      Rating:
      3
Nice  December 11, 2003
Reviewer:
      Thomas  from Kottayam,Kerala,India
Dear Sir,
I need a loop structure that prints the month names from
January..December.Could you please help?

          Followup:

  1  select to_char(add_months(to_date( '01-jan', 'dd-mon' ),rownum-1), 'Month'
)
  2    from all_objects
  3*  where rownum <= 12
ops$tkyte@ORA9IR2> /

TO_CHAR(A
---------
January
February
March
April
May
June
July
August
September
October
November
December

12 rows selected.

December 11, 2003 chirayu -- Thanks for the question regarding "few
      questions...", version 8.1.6

      You Asked
Hi Tom,

 I have a couple of questions...three of them because It's been quite a while I
last had my turn

( 1) please go thru' the following steps

SQL> create table test ( a number, b varchar2(5

Table created.

SQL> insert into test values ( 2, 'TESTS');

1 row created.

SQL> VARIABLE X NUMBER ;

SQL> SELECT * INTO :X FROM TEST ;

        A B
--------- -----
        2 TESTS

Can you please explain as to why 'SELECT * INTO :X FROM TEST ;'
behaves like this in sql plus...

(2) is is possible to write a cursor wherein the table name  and the no of
columns are unknown until runtime.. Please show with an example...

(3) also in one of the the pl/sql book of oracle corporation I came across this
statement in a chapter that explained cursors..

   ' Prior to 9i we could not fetch rows back, our work area can only be read
ahead'.

    Please elaborate on this..

Thanks in advance
chirayu Sutaria

Thanks is advance.
Chirayu

      and we said...
1) it just does.  it is "valid".  the query has an out bind parameter.  sqlplus
just executes the query.  the query is "valid"

2) using dbms_sql, use.  see

http://asktom.oracle.com/~tkyte/flat/index.html
for a pointer to a plsql based "unloader".  it does exactly that.  dbms_sql is
the only way.

3) it means "cursors go forward".  once you fetched a row, it was fetched, you
could not "scroll backwards" in the result set.
Review &
      Followup

      Rating: 5
scrolling
      backward  December 11, 2003
Reviewer:
      A reader
Prior to 9i we could not fetch rows back, our work area can only be read
ahead'

Does that mean in 9i we can scroll backward? How do we do that?

      Rating: 4
great as usual
      and how quick  December 11, 2003
Reviewer:
      chirayu sutaria  from Ahmedabad, Gujarat, India
However, I didn't fully understand the response to query 1 can you please
elaborate it...

with response to query 3, is it possible in 9i and above and how please explain
with an example...

          Followup:

select <columns>
  into <host variables>
 from ....

is just "sql", it's the way it works.  just like:

declare
   n number;
begin
   select 1 into n from dual;
end;
/

works - but in this case SQLPlus is only binding on "input", it is ignoring the
returned value (so :x does not get set but only because sqlplus said "ah, a
select, lets bind inputs, execute, print results".  If you do a PLSQL block then
sqlplus says "ahh, bind inputs, execute, RETRIEVE outputs" and x would be set:

ops$tkyte@ORA9IR2> variable x number
ops$tkyte@ORA9IR2> select 1 into :x from dual;

         1
----------
         1

ops$tkyte@ORA9IR2> print x

         X
----------

ops$tkyte@ORA9IR2> begin select 1 into :x from dual; end;
  2  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> print x

         X
----------
         1

in 9i, with the jdbc and oci that comes with, you have backwards scrolling
cursors.

http://download-west.oracle.com/docs/cd/B10501_01/java.920/a96654/resltset.htm#1017513

      Rating: 5
lead and
      lag  December 11, 2003
Reviewer:
      A reader
Hi tom, YOu already lead and lag fns. in 8i
so you are actually traversing back ... No ?

          Followup:

lag and lead do not allow you to refetch a row in a client after its been
fetched already.

that is what the question was about -- scrolling a result set.

      Rating: 4
Response was
      useful, but...  December 11, 2003
Reviewer:
      Jon  from Hartford, CT
any hope for similar functionality in PL/SQL?

          Followup:

nope, doesn't even make sense to me.  plsql not being an interactive "thing".
it is not sitting there waiting for you to "page back"

these scrollable result sets are a client side thing as well typically.

December 11, 2003 Velayutham -- Thanks for the question regarding
      "ASP Error In Oracle 8i Rel 8.1.5", version Ora 8i Rel 8.1.5

      You Asked
Dear Tom,

Getting the Error:[Oracle][ODBC][Ora]ORA-01013: user requested
cancel of current operation

I am having a table which is having a 60,000 records.   If I try
to retrieve 10 records
from the table I am getting the followig error:

Microsoft OLE DB Provider for ODBC Drivers error '80040e31'

[Oracle][ODBC][Ora]ORA-01013: user requested cancel of current
operation

bug.asp, line 20 (i.e in the rs.open statement)

I am getting the above error exactly after 30 seconds.

I have checked with the Oracle documentation the error says that
the user has pressed the
CTRL+C

I have not done anything.  I think so the IIS has fired the key.

I am using the following configuration:

WinNT 4.0,IIS 4.0, Oracle 8i(8.1.5), Windows 98 Nodes,IE5.0 is
the browser.
Session Timeout 20 minutes
ASP Script timeout 90 seconds
I am using Oracle ODBC Driver for dsn connection.

The following is the source code.

<%@ Language=VBScript %>
<HTML>
<HEAD>
<META NAME="GENERATOR" Content="Microsoft Visual Studio 6.0">
</HEAD>
<BODY>
<!--#include file="adovbs.inc"-->
<%
  Dim rs,sql,dsn
  set rs =  server.createObject("ADODB.Recordset")
  sql    =   " SELECT  Col1 FROM CLIENT "
  dsn = "dsn=oracleserver;uid=dp2000;pwd=dp2000;"
  rs.activeconnection = dsn
  rs.cursorlocation = 3
  rs.cursortype = adopenstatic
  rs.source = sql
  rs.open
  rs.activeconnection = nothing
  rs.pagesize = 10
  rs.MoveFirst
  Response.Write("<br>")
  Response.Write("Client Name")
  Response.Write("<br>")
  For i = 1 to 10
     Response.Write(rs.Fields("col1"))
     Response.Write("<br>")
     rs.MoveNext
  Next
%>
</BODY>
</HTML>

Thanking You.

Regards

S. Velayutham

      and we said...
I believe you'll be able to fix this by reconfiguring your
DSN...

Problem Description
-------------------

You recently upgraded to oracle 8.x ODBC driver, and now
occasionally receive the following error during execution of a
query:

    ORA-1013 user requested cancel of current operation

This occurs particularly with long running queries.

The same query ran fine with the Oracle 7 ODBC driver.

Explanation
-----------

The Oracle 8 driver implemented a timeout feature, which did not
exist with the 7 driver.  The base version of the driver does
not provide facilities to disable the timeout.  Later versions
of the odbc driver provide a feature to disable query timeout.

Solution Description
--------------------

Upgrade to the latest version of the driver if you are using
8.0.5.2 or less, reconfigure the DSN, and uncheck the checkbox
that says 'Enable Query Timeout'.

To reconfigure the DSN, open the odbc administrator, select the
DSN you are using, and click the 'configure' button.

Any version after 8.0.5.3.0 (for an 805 client) or 8.1.5.1.0
(for 8.1.5) will have the option.

The latest version is available at:

  ftp://oracle-ftp.oracle.com/dev_tools/patchsets/mslang/odbc/

Review &
      Followup

      Rating: 5
odbc
      ORA-01013  May 30, 2001
Reviewer:  John S.
      Hambleton jhamblet@nmu.edu  from Northern Michigan University

So glad I found this.
We are having the exact same problem.

      Rating: 5
odbc ORA-01013
        January 31, 2002
Reviewer:  Jean Paul
      Braun  from CHILE
It was the solution for me. I changed The Timeout Attribute directly in my DNS
file, from QTO=T, to QTO=F (i think that means "Query Time Out", and i changed
from "True" to "False", cause it worked!

      Rating: 5
ODBC
      ORA-01013  February 27, 2002
Reviewer:  Tim
      Lindgren  from Bloomington (Minneapolis), MN USA
Installing the 8.0.5 patch fixed my problem as well.

Thanks!

      Rating: 4
Query
      Timeout  December 11, 2003
Reviewer:  Jack

Is there something that can be done to cancel a query after it has been running
for a specific amount of time and has not come back with data?

          Followup:

look at the resource profiles  -- you can limit many many many things.  see the
admin guide.

December 11, 2003 Thomas -- Thanks for the question regarding "How
      can I do a variable "in list"", version 8.1.5

      You Asked
I have a simple stored procedure, that I would like to have a
passed in string(varchar2) for used in select from where col1 in
(var1) in a stored procedure.  I've tried everything but doesn't
work.  Followed is my proc.

Thanks

CREATE OR REPLACE PROCEDURE WSREVSECT_5

pSectNos varchar2,
  pRetCode OUT varchar2
)
AS
nCount number;

BEGIN

SELECT count(fksrev) into nCount
FROM SREVSECT
WHERE sectno IN  (pSectNos )  /* as in 'abc', 'xyz', '012' */
;
pRetCode:=to_char(ncount);

End;

      and we said...
it works -- the above is the same as

where sectno = pSectNos

though, not what you want.  You want it to be:

where sectno in ( 'abc', 'xyz', '012' )

NOT:

where sectno in ( '''abc'', ''xyz'', ''012''' )

which is effectively is (else you could never search on a string
with commas and quotes and so on -- it is doing the only logical
thing right now).

You can do this:

SQL> create or replace type myTableType as table
     of varchar2 (255);
  2  /

Type created.

ops$tkyte@dev8i> create or replace
     function in_list( p_string in varchar2 ) return myTableType
  2  as
  3      l_string        long default p_string || ',';
  4      l_data          myTableType := myTableType();
  5      n               number;
  6  begin
  7    loop
  8        exit when l_string is null;
  9        n := instr( l_string, ',' );
10         l_data.extend;
11         l_data(l_data.count) :=
                 ltrim( rtrim( substr( l_string, 1, n-1 ) ) );
12         l_string := substr( l_string, n+1 );
13    end loop;
14
15    return l_data;
16  end;
17  /

Function created.

ops$tkyte@dev8i> select *
  2    from THE
        ( select cast( in_list('abc, xyz, 012') as
                              mytableType ) from dual ) a
  3  /

COLUMN_VALUE
------------------------
abc
xyz
012

ops$tkyte@dev8i> select * from all_users where username in
  2  ( select *
  3    from THE ( select cast( in_list('OPS$TKYTE, SYS, SYSTEM')

                         as mytableType ) from dual ) )
  4  /

USERNAME                          USER_ID CREATED
------------------------------ ---------- ---------
OPS$TKYTE                           23761 02-MAY-00
SYS                                     0 20-APR-99
SYSTEM                                  5 20-APR-99

Review &
      Followup

      Rating: 5
Good ideas;
      nice to see all the parts  April 05, 2001

Reviewer:  Kristy  from Centreville, VA USA

      Rating: 2
Beware of the
      performance hit  November 01, 2001
Reviewer:
      Andy Barker  from York, UK
Nice idea but the execution path can be made much worse such that the execution
time becomes much slower than if Oracle has to hard parse a new query each time.
You can no longer use the elements in the list as keys for index lookups, which
may mean full table scans with hash/merge joins rather than nested loop index
joins.

          Followup:

On the flip side of life - the execution path could become much better (look on
the bright side).

You know, there is an "evil" side and a "good" side to everything.  Here, you
might have to tune your query in order to make use of bind variables.   I can
assure you 100% that if you do not use bind variables and just glue the values
in -- your system will

o run slower.
o not scale.
o perhaps not run at all.

It can run just as well (or better) then an explicit inlist.  I have to make the
assumption sometimes that people can analyze performance, figure somethings out.
 Hopefully they would recognize if the plan wasn't what they wanted and would
know a thing or two to correct that.  For example:

ops$tkyte@ORA717DEV.US.ORACLE.COM> variable x varchar2(255)
ops$tkyte@ORA717DEV.US.ORACLE.COM> define x="1,2,3,4,5"
ops$tkyte@ORA717DEV.US.ORACLE.COM> exec :x := '&X'
PL/SQL procedure successfully completed.

ops$tkyte@ORA717DEV.US.ORACLE.COM> /*
DOC>drop table t;
DOC>create table t as select * from all_objects;
DOC>alter table t add constraint t_pk primary key(object_id);
DOC>analyze table t compute statistics for table for all indexes for all indexed
columns;
DOC>*/

ops$tkyte@ORA717DEV.US.ORACLE.COM> set autotrace traceonly

ops$tkyte@ORA717DEV.US.ORACLE.COM> select * from t where object_id in ( &X );
old   1: select * from t where object_id in ( &X )
new   1: select * from t where object_id in ( 1,2,3,4,5 )

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=7 Card=5 Bytes=485)
   1    0   INLIST ITERATOR
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=7 Card=5 Bytes=485)
   3    2       INDEX (RANGE SCAN) OF 'T_PK' (UNIQUE) (Cost=2 Card=5)

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

ops$tkyte@ORA717DEV.US.ORACLE.COM> select /*+ first_rows */ * from t where
object_id in (
  2  select *
  3    from TABLE(cast( in_list( :x ) as myTableType) )
  4  )
  5  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=8182 Card=8168
Bytes=1845968)
   1    0   NESTED LOOPS (Cost=8182 Card=8168 Bytes=1845968)
   2    1     VIEW OF 'VW_NSO_1' (Cost=14 Card=8168 Bytes=1053672)
   3    2       SORT (UNIQUE) (Cost=14 Card=8168)
   4    3         COLLECTION ITERATOR (PICKLER FETCH)
   5    1     TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=1 Card=17083
Bytes=1657051)
   6    5       INDEX (UNIQUE SCAN) OF 'T_PK' (UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         19  consistent gets
          0  physical reads
          0  redo size
       1269  bytes sent via SQL*Net to client
        430  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          4  sorts (memory)
          0  sorts (disk)
          3  rows processed

Not significantly different in my case.

      Rating: 4
Execute
      Immediate  November 12, 2001
Reviewer:  Jan
       from Romania
I created this FUNCTION:

CREATE OR REPLACE FUNCTION Get_Count (p_string VARCHAR2)
  RETURN INTEGER
IS

v_count INTEGER;

BEGIN

IF p_string IS NOT NULL THEN
EXECUTE IMMEDIATE
'SELECT COUNT(1) FROM POLICY_STATUS_TYPES
  WHERE STATUS_CODE IN ('''||p_string||''')'
  INTO v_count;
ELSE
 EXECUTE IMMEDIATE
 'SELECT COUNT(1)  FROM POLICY_STATUS_TYPES'
  INTO v_count;
END IF;

RETURN v_count;

END Get_Count;

If I want to use this function with 0..x parameters - e.g. WHERE IN ('AA','BB'),
I just call it like this :

DECLARE

v_string VARCHAR2(200):='AA'',''BB';
v_count  INTEGER;
BEGIN

v_count:=Get_Count(v_string);

dbms_output.put_line(v_count);
END;

Jan

          Followup:

And that function does not use bind variables which I consider to be the
cardinal sin to end all sins in Oracle programming.

Additionally, it uses dynamic sql in PLSQL when it doesn't need to -- another
performance hit (no cursor caching, more parses per session, less scalable, less
performant).

At the VERY LEAST, in 816 and up, code this like this:

ops$tkyte@ORA717DEV.US.ORACLE.COM> create or replace function foo( p_string in
varchar2 ) return number
  2  as
  3          l_cnt number;
  4  begin
  5          execute immediate
  6          'alter session set cursor_sharing = force';
  7
  8          execute immediate
  9          'select count(*) from dual where dummy in ( ' || p_string ||
                ')' into l_cnt;
 10
 11          execute immediate
 12          'alter session set cursor_sharing = exact';
 13
 14          return l_cnt;
 15  end;
 16  /

Function created.

Cursor sharing is a "crutch" that can help you out (it is NOT the solution to
bind variable problems -- it can help lighten the load but is not the answer)

Using that, we'll find:

ops$tkyte@ORA717DEV.US.ORACLE.COM> exec dbms_output.put_line( foo( ' ''A'',
''B'' ' ) );
0

PL/SQL procedure successfully completed.

ops$tkyte@ORA717DEV.US.ORACLE.COM> exec dbms_output.put_line( foo( ' ''A'',
''B'', ''C'' ' ) );
0

PL/SQL procedure successfully completed.

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> select sql_text from v$sql where
upper(sql_text) like 'SELECT COUNT(*) FROM DUAL%';

SQL_TEXT
---------------------------------------------------------------------------------
--------------------------------------------------
select count(*) from dual where dummy in (  :"SYS_B_0", :"SYS_B_1", :"SYS_B_2" )
select count(*) from dual where dummy in (  :"SYS_B_0", :"SYS_B_1" )

We get as many copies of the query as we have elements in the inlist -- we won't
get a QUERY per inlist -- but rather a query plan for queries with 1 element, 2
elements, 3 elements, 4 elements and so on.

This works in 816 and up ONLY.

      Rating: 4
How can I do a
      variable "in list  December 06, 2001
Reviewer:
      Niloufar  from Austin, TX USA
I was working on a similar procedure where I needed to be able to have a "WHERE
column_name IN (my_listof_strings)" clause.  I tried your solution which works
fine in SQLPLUS but when I try to do the same thing in a PL/SQL procedure, I get
the following error in the "select ... from dual" statement:

 PLS-00513: PL/SQL function called from SQL must return
 value of legal SQL type

Which makes sense as you also have mentioned in another article we can't use
PLSQL types in SQL.  But then, how do I implement this in PL/SQL?

Thanks for your help.

          Followup:

It works "as is" in PLSQL -- as long as you created the TYPE as a SQL type and
didn't try to hide it inside of a spec or body.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace type myTableType as table
  2  of varchar2(4000)
  3  /

Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace
  2  function in_list( p_string in varchar2 ) return myTableType
  3  as
  4      l_string        long default p_string || ',';
  5      l_data          myTableType := myTableType();
  6      n               number;
  7  begin
  8    loop
  9        exit when l_string is null;
 10        n := instr( l_string, ',' );
 11        l_data.extend;
 12        l_data(l_data.count) :=
 13              ltrim( rtrim( substr( l_string, 1, n-1 ) ) );
 14        l_string := substr( l_string, n+1 );
 15   end loop;
 16
 17   return l_data;
 18  end;
 19  /

Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2    from THE
  3       ( select cast( in_list('abc, xyz, 012') as
  4                             mytableType ) from dual ) a
  5  /

COLUMN_VALUE
---------------------------------------------------------------------------------
--------------------------------------------------
abc
xyz
012

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from all_users where username in
  2    ( select *
  3      from THE ( select cast( in_list('OPS$TKYTE, SYS, SYSTEM')
  4                        as mytableType ) from dual ) )
  5  /

USERNAME                          USER_ID CREATED
------------------------------ ---------- ---------
OPS$TKYTE                              63 05-NOV-01
SYS                                     0 28-AUG-01
SYSTEM                                  5 28-AUG-01

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2    for x in ( select * from all_users where username in
  3                   ( select *
  4                        from THE ( select cast( in_list('OPS$TKYTE, SYS,
SYSTEM')
  5                                as mytableType ) from dual ) )
  6             )
  7    loop
  8      dbms_output.put_line( x.username );
  9    end loop;
 10  end;
 11  /
OPS$TKYTE
SYS
SYSTEM

PL/SQL procedure successfully completed.

that shows its no different in plsql than sql.

      Rating: 5
Very Cool, just
      what I need  December 07, 2001
Reviewer:  Paul
      Duer  from Carmel, IN
I really like this example, but I have a bit of a silly question I am afraid.

What if I need to send a select statement to the IN clause? So that instead of
selecting from a set of variables, I would select from a subquery, but have the
ability to change the subquery to multiple different ones?

Is this possible?

          Followup:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace
  2  function in_list( p_string in varchar2 ) return myTableType
  3  as
  4          type rc is ref cursor;
  5      l_cursor     rc;
  6      l_tmp        long;
  7      l_data       myTableType := myTableType();
  8  begin
  9    open l_cursor for p_string;
 10    loop
 11        fetch l_cursor into l_tmp;
 12        exit when l_cursor%notfound;
 13        l_data.extend;
 14        l_data(l_data.count) := l_tmp;
 15   end loop;
 16   close l_cursor;
 17   return l_data;
 18  end;
 19  /

Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2    from table( cast( in_list( 'select 1 from dual' ) as myTableType )  )
  3  /

COLUMN_VAL
----------
1

ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2    from table( cast( in_list( 'select ename from emp' ) as myTableType )  )
  3  /

COLUMN_VAL
----------
A
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
KING
TURNER
ADAMS
JAMES
FORD
MILLER
ant
ant

16 rows selected.

      Rating: 4
Could I pass a
      refcursor?!  October 11, 2002
Reviewer:
      Niloufar  from Austin USA
Tom,

Thank you again for providing us with very helpful information.

Could this function be written such that it accepts a refcursor?  I'd like to
pass a refcursor that contains a list of order numbers to this function and in
return be able to use that list in the IN CLAUSE of a SELECT statement in a
procedure.  Here is what I am trying to achieve:

-- Call to a procedure that returns the refcursor (c_order)
-- which contains the list of order numbers.

call_api.find_order_p(p_start_date, p_end_date, c_order);

-- Fetch the refcursor content into c_order_list which is
-- a PL/SQL:
--   TYPE OrderList IS TABLE OF VARCHAR2(15) INDEX BY BINARY_INTEGER;

-- The anyTableType is a SQL type TABLE OF VARCHAR2(2000);

    c_data               anyTableType := anyTableType();

FETCH c_order BULK COLLECT INTO c_order_list;
     FOR i IN 1..c_order_list.COUNT LOOP
         c_data.EXTEND;
         c_data(c_data.COUNT) := c_order_list(i);
       dbms_output.put_line('Post C_DATA is: '||c_data(c_data.COUNT));
     END LOOP;

     CLOSE c_order;

From here, I'd like to be able to use the result that is in c_data in the "IN
CLAUSE" of another SQL statement that is used for another refcursor.  In other
words:

OPEN result_set FOR
'SELECT * FROM order WHERE order_num IN (the list in c_data)';

Is this possible?  Am I doing this right?

Thank you.

          Followup:

In 9i, sure -- but with your example, you don't need to.

You would write a procedure that called "find_order_p", built the collection and
returned it -- just like my in_list function does.

      Rating: 4
ORA-00600 when
      creating and describing a NT  October 12, 2002

Reviewer:  Robert C  from PA
Tom, the error I got below...Is this BAD news ?
Do you know what's going on ?

LMS1@dns > create or replace type typeNestedTableVarchar_255 as table of
varchar2(255)
/
create or replace type typeNestedTableVarchar_255 as table of varchar2(255)
*
ERROR at line 1:
ORA-00600: internal error code, arguments: [6002], [32], [6], [2], [0], [], [],
[]

"SCRP_CTR_TableType" has been in use successfully as a
dynamic in_list solution as you demoed sometime ago...
but when I desc it now....

LMS1@dns > desc SCRP_CTR_TableType
ERROR:
OCI-21500: internal error code, arguments: [koxsihread1], [0], [0], [4], [], [],
[], []

And just now I exit the server is telling me this:
Disconnected from Oracle8i....<snip>
....64bit Production (with complications) <-- !

Wow...did I just screw up something  ?

          Followup:

well, I would contact support -- the issues I see surrounding this all seem
related to an upgrade that wasn't done 100% right -- you don't mention an
upgrade (or os, or version, or ... ) so you'll need to open a tar and see what
they say.

      Rating: 4
Using BULK
      COLLECT in the In_list function.  October 12, 2002

Reviewer:  Robert C  from PA
Tom, sorry, another thought...
I am trying to modify your In_List function to use BULK COLLECT, but can't make
it work, Oracle complain about:
"ORA-01001: invalid cursor"

FETCH l_cursor BULK COLLECT INTO l_data_list ; --l_tmp;

Is BULK COLLECT available for this purpose ?

thanks

          Followup:

how's about the entire example as I've no idea what code might possiblly have
preceded this.

      Rating: 4
BULK COLLECT
      with Cursor Variable  October 14, 2002
Reviewer:
      Robert C  from PA
>>Followup:
>>how's about the entire example as I've no idea what code >>might possiblly
have preceded this.

Thanks Tom, basically my question was about using BULK COLLECT with Cursor
Variable and subsequent search on this site I learned that this can not be done
in 8i.

          Followup:

Yes, it can  -- just NOT with a dynamically opened ref cursor.

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2          type rc is ref cursor;
  3          type array is table of varchar2(1) index by binary_integer;
  4
  5          l_cur rc;
  6          l_array array;
  7  begin
  8          open l_cur for select * from dual;
  9          begin
 10                  fetch l_cur BULK COLLECT into l_array;
 11                  dbms_output.put_line( 'Fetched ' || L_array.count || '
rows' );
 12          exception
 13                  when others then
 14                          dbms_output.put_line( 'ERROR: ' || sqlerrm );
 15          end;
 16          close l_cur;
 17
 18          open l_cur for 'select * from dual';
 19          begin
 20                  fetch l_cur BULK COLLECT into l_array;
 21                  dbms_output.put_line( 'Fetched ' || L_array.count || '
rows' );
 22          exception
 23                  when others then
 24                          dbms_output.put_line( 'ERROR: ' || sqlerrm );
 25          end;
 26          close l_cur;
 27  end;
 28  /
Fetched 1 rows
ERROR: ORA-01001: invalid cursor

PL/SQL procedure successfully completed.

static open, works, dynamic open, fails.

In 9i:

ops$tkyte@ORA9I.WORLD> ops$tkyte@ORA9I.WORLD> ops$tkyte@ORA9I.WORLD> declare
  2          type rc is ref cursor;
  3          type array is table of varchar2(1) index by binary_integer;
  4
  5          l_cur rc;
  6          l_array array;
  7  begin
  8          open l_cur for select * from dual;
  9          begin
 10                  fetch l_cur BULK COLLECT into l_array;
 11                  dbms_output.put_line( 'Fetched ' || L_array.count || '
rows' );
 12          exception
 13                  when others then
 14                          dbms_output.put_line( 'ERROR: ' || sqlerrm );
 15          end;
 16          close l_cur;
 17
 18          open l_cur for 'select * from dual';
 19          begin
 20                  fetch l_cur BULK COLLECT into l_array;
 21                  dbms_output.put_line( 'Fetched ' || L_array.count || '
rows' );
 22          exception
 23                  when others then
 24                          dbms_output.put_line( 'ERROR: ' || sqlerrm );
 25          end;
 26          close l_cur;
 27  end;
 28  /
Fetched 1 rows
Fetched 1 rows

PL/SQL procedure successfully completed.

both work

      Rating: 5
varying
      elements in IN list  December 27, 2002
Reviewer:
      Rizwan Qazi  from Hayward, CA
Tom,
   Thanks again for this great forum!
   I have a dynamic sql (a part of which is given below). I have used your
example to bind the columns using contexts.
Now I have a problem, how to bind the values in a for loop below.
Can you please help me out?

    IF (p_selected_feature_set <> '0' AND p_selected_feature_set <> '-1') THEN
                    dbms_session.set_context( 'feat_utils_ctx', 'fs',
p_selected_feature_set);
                    sql_image_ids := sql_image_ids || '         AND
i.feature_set_id IN ' ;
                    sql_image_ids := sql_image_ids || '          ( select * from
THE ( select cast( in_list(sys_context(''feat_utils_ctx'', ''fs'')) as
idTableType ) from dual ) a ) ' ;
   END IF;

                    sql_image_ids := sql_image_ids || '         AND EXISTS   (
';

                FOR i IN 1..v_image_count LOOP

                    sql_temp_image_ids := sql_temp_image_ids || '
SELECT  ';
                    sql_temp_image_ids := sql_temp_image_ids || '
 1 ';
                    sql_temp_image_ids := sql_temp_image_ids || '
FROM ';
                    sql_temp_image_ids := sql_temp_image_ids || '
 fa_feature fa ';
                    sql_temp_image_ids := sql_temp_image_ids || '
WHERE ';
                    sql_temp_image_ids := sql_temp_image_ids || '
 fa.feature_id = ( ' || TO_NUMBER(vit_image_ids(i)) || ') ';
                    sql_temp_image_ids := sql_temp_image_ids || '
 AND fa.image_id = i.image_id ';

                    IF (i < v_image_count) THEN
                        sql_temp_image_ids := sql_temp_image_ids || '
 INTERSECT ' ;
                    END IF;

                END LOOP;

As you can see there is an intersect between two statements in the loop.

          Followup:

actually -- i cannot see much as it wraps pretty badly

not really sure what you are trying to do.  Maybe if you post it as a new
question when I'm taking questions....

      Rating: 4
ORA-03113 while
      using the cast to table...  December 27, 2002

Reviewer:  KU  from Nashville
O Oracle of Oracle,

I tried the following:
create type vc_array as table of varchar2(2000);

SELECT t.column_value
FROM table(cast(in_list('123,234') as VC_ARRAY)) t
;

COLUMN_VALUE
------------
123
234

So far so good.

My next query
SELECT
distinct
pol_id
FROM pol_ent
WHERE ent_id IN
(SELECT t.column_value
FROM table(cast(in_list('829740,1234') as vc_array)) t)
;
crashes with ORA-03113 (end of communicaion channel).

If I remove the 'distinct', it works, but I need the 'distinct' key word.

My actual query has this sql as a subquery, as in

select col1, col2, col3 from pol where polid in
(SELECT
--distinct
pol_id
FROM pol_ent
WHERE ent_id IN
(SELECT t.column_value
FROM table(cast(in_list('829740,1234') as vc_array)) t)
)
;

crashes all the time (with or without distinct/orderby/groupby) with ORA-03113.

(Database has abundant memory allocation to largepool, shared pool, javapool etc
and db is java enabled).

The whole query works if there is no table(cast(... thing.

What shall I do to get around this error?

Regards

KU

          Followup:

step 1: contact support and open a tar as you should for all ora-03113 and
ora-00600 type errors

step 2: try this:

select col1, col2, col3 from pol where polid in
(SELECT
distinct
pol_id
FROM pol_ent
WHERE ent_id IN
(SELECT t.column_value
FROM table(cast(in_list('829740,1234') as vc_array)) t
where rownum > 0 )
)
;

or

select col1, col2, col3 from pol where polid in
(SELECT
distinct
pol_id
FROM pol_ent
WHERE ent_id IN
(SELECT t.column_value
FROM table(cast(in_list('829740,1234') as vc_array)) t
group by t.column_value )
)
;

      Rating:
      4
Why  January 03, 2003
Reviewer:
      A reader
Hi, Tom,

I got this error in PLSQL when run your example:

FOR x IN (select *
  from THE
  ( select cast( in_list('abc, xyz, 012') as
                       mytableType ) from dual ) a) LOOP
...
END LOOP;

ERROR:
PLS-00231:
function in_list may not be used in SQL

Please advice

          Followup:

      Rating: 4
forget above
      question  January 03, 2003
Reviewer:
      A reader
Hi, Tom,

Please forget that above question, the reason is
I have to declare IN_LIST() in the SPEC of a package
even it is only called within this package and there will be no other
packages/procedures outside this package call the in_list() function, wired.

Thanks

      Rating: 4
Can somebody
      help me with those things?  January 03, 2003

Reviewer:  Mike F.  from NJ, USA
I don't know how the "THE" and "cast" work in the following:

select * from THE
     ( select cast( in_list('abc, xyz, 012') as
     mytableType ) from dual ) a

Can Tom or anybody please point me in the right direction?  Searching on the two
words would be futile.  Thanks!

          Followup:

Ingore them -- they are the obsolete way.  Use this instead:

ops$tkyte@ORA817DEV> begin
  2          for x in ( select * from TABLE ( cast(in_list('abc,xyz,012') as
myTabletype) ) )
  3          loop
  4                  dbms_output.put_line( x.column_value );
  5          end loop;
  6  end;
  7  /
abc
xyz
012

PL/SQL procedure successfully completed.

see

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a85397/expressi.htm#1017895
for CAST documentation -- just a function to ensure a datatype:

ops$tkyte@ORA817DEV> select cast( '0' as number(7,2) ) from dual;

CAST('0'ASNUMBER(7,2))
----------------------
                     0

sort of a conversion function.

      Rating: 3
By the way, a
      comment  January 03, 2003
Reviewer:  Mike F.
       from NJ, USA
To put a result set returned from a stored function in
a "in" list would be so easy and nature in SQL Server.
Just define the stored function to return a table.
Then you can do this:

select * from my_table where col in my_func(arg)

or you can use this:

select t.* from my_table t, my_func(arg) f
   where t.col = f.col

          Followup:

  1* select * from all_users where user_id in (select * from
TABLE(in_list('1,2,3,4,5')))
ops$tkyte@ORA920> /

USERNAME                          USER_ID CREATED
------------------------------ ---------- ---------
SYSTEM                                  5 12-MAY-02

  1  select *
  2    from all_users, TABLE(in_list('1,2,3,4,5'))
  3*  where all_users.user_id = column_value
ops$tkyte@ORA920> /

USERNAME                          USER_ID CREATED   COLUMN_VALUE
------------------------------ ---------- --------- -------------------------
SYSTEM                                  5 12-MAY-02 5

not significantly different.  And it is standard (so it should theoritically
work in all sql99 databases).

      Rating: 3
Not very
      different, but  January 08, 2003
Reviewer:  Mike
      F.  from NJ, USA
Thanks, Tom.

The variable "in" list solution might look easy and
natural to you, but might not to some others:

1. One has to declare a global type just for the list
   to be used in the "in" list.  If one has many types
   of such lists, the global space could be a bit clogged,
   not a particular bad thing, but consider multi-column
   in lists, it is not very pleasant as well.

2. I am not sure why one still needs to cast the return
   value of in_list, which is already of type myTableType,
   into myTableType again?

3. The result value of in_list is already a table, why would
   we do the select like this: "select <table> from dual"
   in order to make it into a "table"?

4. OK, assume you do need to do the above select, now one
   might think now we have a result set that we can use "in",
   just as in:

     select * from all_users
       where user_name in (select user_name in old_users)

    Wrong again.  One has to put a "table" keyword in front
    of the result set in order to put it in the "in" list,
    even though it is already a "table".

Quite a few tricks to learn, it seems.  Let alone one might get lost about the
concept of "table" itself. I doubt if many
Oracle developers know about these tricks.  It is at least
not mentioned in any PL/SQL book I have read (at least 4 of them).

          Followup:

1) there are like three types in the world:  strings, numbers and dates.  3
doesn't seem to be CLUTTER does it?

where does a multi-column inlist come from?

anyway, I think of them as views, views are metadata -- it is actually good
documentation.

2) I'm not really worried about it -- I'd be worried if I couldn't do it.  You
know, for everything you think "oracle got wrong", I'll find at least one thing
I think MS got wrong.  We are *different* (thank goodness).

3) because it is officially a COLLECTION -- does SQL server have the concept of
a collection?  Like an array of values that can live in a row?  A collection is
a scalar type -- we turn it into a TABLE() by using TABLE.

4) It isn't a table (maybe it is the sql server limitation that is hiding that
fact from you).  It is a SCALAR -- like a NUMBER is.  You have to convert it
into a table.

You didn't read my book then!

      Rating: 4
Good info.
      Thanks.  January 08, 2003
Reviewer:  Mike F.
       from NJ, USA
Tom,

Thanks a lot for your explanations.  I was not implying that MS SQL Server is a
better product.
Sure I am aware of many Oracle features that are
not supported in MS SQL Server.  I just found it
easier for me to learn Oracle by comparing the two
and figuring out the differences.  Maybe I wish
it could have done better in some areas.
That's about it.

One particular issue I am having with learning
Oracle is the documentations.  I found it's a
little hard to navigate to where I need to know more
about.  Probably it is the lack of good indexing
and searching capabilities in the docs?
Perhaps the sheer complexity of Oracle
prevents it to have something as organized
as SQL Server's Book On Line and Knowledgment Base?

One last question, in order to turn a "collection"
into a table, one need to do this:

    TABLE (select <collection> from dual)

right?  What's the rational, please?  Thanks a lot!

      Rating: 4
Oh, so-called
      "multi-column in list"  January 08, 2003
Reviewer:
      Mike F.  from NJ, USA
Tom,

Forgot to explain my so-called "multi-column in list".  Please forgive the bad
name.  But here is what I mean:

    select * from all_users
       where (first_name, last_name)
           in (select first_name, last_name from some_users)

Say, we have a stored function that returns a table (or a ref cursor) that has
two columns (first_name, last_name).  Now we want to make use of the stored
function in the above select.   My guess is that we need to turn the two column
table, or collection, or whatever, into a real table somehow.  To do this it
seems to me we need an another table type for it, right?  Sorry if I am wrong.

          Followup:

Yes you do -- just like you would have to use a subquery -- or to simplify it
you might use a view -- metadata.

Again, you would have a very finite set of these objects.  Sorry - it is
different but equivalent.  We are not MS, they are not us.  Different strokes
for different folks.

      Rating: 4
Can I do
      this?  January 12, 2003
Reviewer:  Mike F.
       from NJ, USA
Tom,

Now I have another question.  Another way to do the following:

v_str := '1,2,3,4,5';
...
select count(*)
into v_cnt
from all_users
where user_id in
   (select * from TABLE(in_list(v_str)))

seems to be:

v_str := '1,2,3,4,5';
...
select *
into v_cnt
from all_users
where instr(',' || v_str            || ',',
            ',' || to_char(user_id) || ',') > 0

How do think about the this approach?
It seems to me pretty straightforward, and pretty
fast as well.  Please comment.  Thanks a lot!

          Followup:

do it with 100,000 rows and tell me how fast it is.

where in () = possible to use index range scan on user_id

where instr() = NOT POSSIBLE to use index range scan on user_id

big_table@ORA920> set autotrace traceonly
big_table@ORA920> select *
  2    from big_table
  3   where id in ( select to_number(column_value)
  4                   from TABLE( cast(in_list('1,2,3,4,5,6,7,8,9,10') as
myTableType) )
  5                  where rownum > 0
  6               )
  7  /

10 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=1672 Card=8168
Bytes=931152)
   1    0   NESTED LOOPS (Cost=1672 Card=8168 Bytes=931152)
   2    1     VIEW OF 'VW_NSO_1' (Cost=11 Card=8168 Bytes=106184)
   3    2       SORT (UNIQUE)
   4    3         COUNT
   5    4           FILTER
   6    5             COLLECTION ITERATOR (PICKLER FETCH) OF 'IN_LIST'
   7    1     TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=2 Card=1
Bytes=101)
   8    7       INDEX (UNIQUE SCAN) OF 'BIG_TABLE_PK' (UNIQUE) (Cost=1 Card=1)

Statistics
----------------------------------------------------------
         22  recursive calls
          0  db block gets
         43  consistent gets
          0  physical reads
          0  redo size
       1948  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          6  sorts (memory)
          0  sorts (disk)
         10  rows processed

big_table@ORA920> select *
  2    from big_table
  3   where instr( ',' || '1,2,3,4,5,6,7,8,9,10' || ',', ',' || to_char(id) ||
',' ) > 0
  4  /

10 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=1253 Card=50000
Bytes=5050000)
   1    0   TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1253 Card=50000
Bytes=5050000)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      13022  consistent gets
      12170  physical reads
          0  redo size
       1948  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         10  rows processed

big_table@ORA920> set autotrace off

      Rating: 2
Can not figure
      out the   February 25, 2003
Reviewer:  Ivan
       from USA
Hi Tom,

I am getting the PLS-00513: PL/SQL function called from SQL must return value of
legal SQL type for my call to mev_sic.get_company_sic_info(co.company_id) from
the select statement below.  I realize what the problem is but I can not figure
out how to create a table type like you described for more then one row (per
your example for Niloufar).  My table type needs to send more then one column.

TYPE CompanySicInfo_rectype  IS RECORD
  ( sic_source          sic_codes.sic_source%TYPE,
    sic_code            sic_codes.sic_code%TYPE,
    industry_class_id   sic_codes.industry_class_id%TYPE,
    sic_type            company_sic_codes.sic_type%TYPE,
    short_description   sic_codes.short_description%TYPE
   );

TYPE SicCodeTblTyp  IS TABLE OF CompanySicInfo_rectype
                                      INDEX BY BINARY_INTEGER;

PROCEDURE get_company_dbupdate
   ( i_attr_info_tbl           IN attr_name_value_tbltype,
     i_matching_criteria       IN  POSITIVE,
     o_company_info_rec        OUT company_info_rectype,
     o_company_sic_info        OUT SicCodeTblTyp  )
IS
  v_attr_id                      identifying_attrs.attribute_id%TYPE;
  v_attr_value                   company_attrs.attribute_value%TYPE;
  v_company_id                   company_attrs.company_id%TYPE;
  v_last_company_id              company_attrs.company_id%TYPE;
  i                              BINARY_INTEGER;
  v_cnt_matches                  BINARY_INTEGER DEFAULT 0;
  v_coa_rowid                    ROWID;
  KeyIDConflicts                 BOOLEAN DEFAULT FALSE;
BEGIN
  o_company_info_rec := NULL;  -- Clear the output record
  set_matching_criteria(i_matching_criteria);
  FOR i IN 1..i_attr_info_tbl.COUNT LOOP

logedit_attr_value(i_attr_info_tbl(i).attr_name,i_attr_info_tbl(i).attr_value,v_a
ttr_id,v_attr_value);
    IF i_attr_info_tbl(i).attr_value IS NOT NULL THEN
      BEGIN
        SELECT
            coa.ROWID,
            co.company_id,
            co.name,
            co.address_1,
            co.address_2,
            co.city,
            co.district,
            co.country_id,
            co.telephone_number,
            co.business_description,
            co.quotation_symbol,
            co.parent_id_usage,
            co.parent_id,
            co.scale,
            co.employees,
            co.url,
            igrp.industry_group_id,
            igrp.industry_mnemonic,
            igrp.name,
            mev_sic.get_company_sic_info(co.company_id)
        INTO
            v_coa_rowid,
            v_company_id,
            o_company_info_rec.name,
            o_company_info_rec.address_1,
            o_company_info_rec.address_2,
            o_company_info_rec.city,
            o_company_info_rec.district,
            o_company_info_rec.country_id,
            o_company_info_rec.telephone_number,
            o_company_info_rec.business_description,
            o_company_info_rec.quotation_symbol,
            o_company_info_rec.parent_id_usage,
            o_company_info_rec.parent_id,
            o_company_info_rec.scale,
            o_company_info_rec.employees,
            o_company_info_rec.url,
            o_company_info_rec.industry_group_id,
            o_company_info_rec.industry_mnemonic,
            o_company_info_rec.industry_name,
            o_company_sic_info
        FROM
            company_attrs     coa,
            companies         co,
            industry_groups   igrp
        WHERE coa.attribute_id      = v_attr_id
        AND   coa.attribute_value   = v_attr_value
        AND   coa.company_id        = co.company_id
        AND   co.industry_group_id  = igrp.industry_group_id (+);

        IF UpdateReadDate THEN
          UPDATE company_attrs
            SET last_read_date = SYSDATE
            WHERE ROWID = v_coa_rowid;
        END IF;

        IF MatchFirst THEN
          v_cnt_matches := i;
          EXIT;
        ELSIF MatchAll THEN
          v_cnt_matches := v_cnt_matches + 1;
          IF v_last_company_id IS NULL THEN
            v_last_company_id := v_company_id;
          END IF;

          IF v_company_id <> v_last_company_id THEN
            KeyIDConflicts := TRUE;
          END IF;
        END IF;
      EXCEPTION
        WHEN no_data_found THEN
          NULL;
      END;
    END IF;
  END LOOP;
  mev_common.perform_commit;
  o_company_info_rec.matching_attr := v_cnt_matches;

  IF KeyIDConflicts THEN
    o_company_info_rec.company_id := -1;
    o_company_info_rec.name := NULL;
  ELSE
    o_company_info_rec.company_id := v_company_id;
  END IF;
EXCEPTION
  WHEN others THEN
    mev_common.perform_rollback;
    mev_audit.sql_error;
    RAISE;
END get_company_dbupdate;

Thanks as always,
Ivan

          Followup:

sorry -- not making sense -- need a small (much smaller) yet COMPLETE example

You cannot -- repeat cannot -- use plsql record types in sql, you must MUST use
object types created in sql, not plsql.

Also -- it is really hard to see what the function in question does -- since it
isn't there.

So -- use sql object types.
Create a small, yet 100% complete example

      Rating: 5
Please
      disregard my post above...  February 25, 2003

Reviewer:  Ivan  from USA
I have figured out my problem.

Thanks,

Ivan

      Rating: 3
How to use list
      generated by select  March 03, 2003
Reviewer:
      hk
select * from entry_in_group where group_id in (select * from THE ( select
cast(str2tbl('10, 20, 30') as mytableType ) from dual ) )

  ENTRY_ID GROUP_ID
---------- ----------
   1000002 10
   1000004 10
   1000003 20

Why this is not working(no rows returned):

select * from entry_in_group where group_id in (select * from THE ( select
cast(str2tbl('select * from test') as mytableType ) from dual ) )

select * from test;
ASD
-----------------
10,20,30

          Followup:

because that was just saying:

select * from entry_in_group
where group_id in ( 'select', '*', 'from', 'test' );

what might work -- didn't try it, would be:

select * from entry_in_group where group_id in (select * from THE ( select
cast(str2tbl(  (select * from test)  ) as mytableType ) from dual ) )

using a scalar subquery.

      Rating:
      5
...  March 04, 2003
Reviewer:  hk

It worked, thanks!

      Rating: 2
Problem
      implementing cast  March 21, 2003
Reviewer:
      Max
Tom,

I have a query which returns me the list of items from various tables for an
order. While processing my order, I frequently need these list of items --
sometimes a count and sometimes the actual items. So in various sqls I have this
common where clause, "where item in (select item ...<common query>)".

How can I avoid executing this common query multiple times ? I tried collecting
this item list into an array using bulk fetch but could not implement further
using cast. Can you please help ?

select count(*) ctr from itempack
where packid in
( /* this is the common query block */
  select item
  from orditem
  where order_nbr = I_order_nbr
  union
  select item
  from orditem_tmp
  where order_nbr = I_order_nbr);

Thanks in advance !

          Followup:

why couldn't you implement further?

show us what you tried -- make it as teeny tiny as possible.  9999 times out of
10000 -- I find my mistake coming up with a small test case to demonstrate my
issue.  hopefully you will too -- but if not, post your teeny tiny example here.

      Rating: 1
Here is what I
      tried  March 21, 2003
Reviewer:  Max

Ok, here is what i tried. For testing purpose, I am trying to get the count. If
I am able to achieve this, I can use it further in my package. I know I am not
able to implement table/cast correctly.

declare
  cursor c_items is
    select item
    from orditem
    where order_nbr = 18312
    union
    select item
    from orditem_tmp
    where order_nbr = 18312;

  TYPE itemList IS TABLE OF item_mst.item%TYPE;
  item_arr      itemList;
  ctr           number;
begin
  open c_items;
  fetch c_items bulk collect into item_arr;
  close c_items;

  select count(*) into ctr
  from table(cast(L_sku_arr as number));
end;
/

          Followup:

the type MUST be a SQL type -- just like my example above is.

SQL> create or replace type myTableType as table
     of varchar2 (255);
  2  /

and oh,

  l_sku_array.count

would be infinitely less resource intensive then "select count(*)"

      Rating:
      1
  March 21, 2003
Reviewer:  Max

Ok, I created the type as below.

create or replace type myTableType as table of varchar2(255)
/

Ran the script and still getting the below error :(

select count(*) into ctr from TABLE(cast(item_arr as myTableType));
                                         *
ERROR at line 23:
ORA-06550: line 23, column 44:
PLS-00382: expression is of wrong type
ORA-06550: line 23, column 3:
PL/SQL: SQL Statement ignored

What exactly should I change in my script to make it working ???

and oh, I knew you would ask me to use "count" thats why I mentioned "For
testing purpose, I am trying to get the count" in the beginning.

          Followup:

ops$tkyte@ORA817DEV> create or replace type myTableType as table of
varchar2(255)
  2  /

Type created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> declare
  2          l_data myTableType;
  3          l_cnt  number;
  4  begin
  5          select username bulk collect into l_data
  6            from all_users;
  7
  8          select count(*) into l_cnt
  9            from TABLE( cast( l_data as myTableType ) );
 10  end;
 11  /

PL/SQL procedure successfully completed.

      Rating: 5
Thank You Thank
      You Thank You Very Much !!!  March 21, 2003

Reviewer:  Max
It worked ! I am a happy camper now -- you made my friday & weekend :-)

Does the type myTableType have to be in the database only ? I tried defining the
type in the pl/sql script but get an error "local collection types not allowed
in SQL statements".

I dont know, but I am finding it HARD to understand the TABLE/CAST usage from
the documentation :( Any tips to overcome this Tom ???

          Followup:

the type MUST be a SQL type -- just like my example above is.

SQL> create or replace type myTableType as table
     of varchar2 (255);
  2  /

it cannot be a plsql type.

cast just "casts", you can

ops$tkyte@ORA920> select cast( 1.4213234 as number(7,2) ) from dual;

CAST(1.4213234ASNUMBER(7,2))
----------------------------
                        1.42

TABLE() just takes a collection and says "pretend it is a table"

      Rating: 5
How to compare
      arrays ???  March 26, 2003
Reviewer:  Max

Hi Tom,

First of all, THANKS for all the answers -- they were extremly useful & helped
me tune my pl/sql scripts. But then, one solution leads to another question ;)

Now say I want to find if an element in the 1st array exists in the 2nd array. I
achieved that by writing the below code -- works fine & gives me what I want.

for i in 1..L_newsku_arr.last loop
  for j in 1..L_ordsku_arr.last loop
    if L_ordsku_arr(j) = L_newsku_arr(i) then
      dbms_output.put_line('match found for '||L_newsku_arr(i));
      exit;
    end if;
  end loop;
end loop;

Would it give better performance if I write a sql to find the match ? Also, I
had trouble getting the column name when I cast the array to a table.

select * from TABLE(cast(L_newsku_arr as myTableType));

Thanks !

          Followup:

ops$tkyte@ORA920> create or replace
  2  procedure isIn_procedural( p_array1 in numArray,
  3                             p_array2 in numArray,
  4                             p_print in boolean default false )
  5  is
  6  begin
  7      for i in 1 .. p_array1.count
  8      loop
  9          for j in 1 .. p_array2.count
 10          loop
 11              if ( p_array1(i) = p_array2(j) )
 12              then
 13                  if ( p_print )
 14                  then
 15                      dbms_output.put_line
 16                      ( 'Matched ' || p_array1(i) );
 17                  end if;
 18                  exit;
 19              end if;
 20          end loop;
 21      end loop;
 22  end;
 23  /

Procedure created.

Elapsed: 00:00:00.18
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace
  2  procedure isIn_sql( p_array1 in numArray,
  3                      p_array2 in numArray,
  4                      p_print in boolean default false )
  5  is
  6  begin
  7      for x in ( select * from TABLE(cast(p_array1 as numArray))
  8                 intersect
  9                 select * from TABLE(cast(p_array2 as numArray)) )
 10      loop
 11          if ( p_print )
 12          then
 13              dbms_output.put_line( 'Matched ' || x.column_value );
 14          end if;
 15      end loop;
 16  end;
 17  /

Procedure created.

Elapsed: 00:00:00.19
ops$tkyte@ORA920>
ops$tkyte@ORA920> set timing on
ops$tkyte@ORA920> declare
  2      l_array1 numArray := numArray();
  3      l_array2 numArray := numArray();
  4  begin
  5      for i in 1 .. 200
  6      loop
  7          l_array1.extend;
  8          l_array1(l_array1.count) := i;
  9          if ( mod(i,2) = 0 )
 10          then
 11              l_array2.extend;
 12              l_array2(l_array2.count) := i;
 13          end if;
 14      end loop;
 15
 16      for i in 1 .. 500
 17      loop
 18          isIn_Procedural(l_array1,l_array2);
 19      end loop;
 20  end;
 21  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:14.79
ops$tkyte@ORA920> declare
  2      l_array1 numArray := numArray();
  3      l_array2 numArray := numArray();
  4  begin
  5      for i in 1 .. 200
  6      loop
  7          l_array1.extend;
  8          l_array1(l_array1.count) := i;
  9          if ( mod(i,2) = 0 )
 10          then
 11              l_array2.extend;
 12              l_array2(l_array2.count) := i;
 13          end if;
 14      end loop;
 15
 16      for i in 1 .. 500
 17      loop
 18          isIn_SQL(l_array1,l_array2);
 19      end loop;
 20  end;
 21  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:01.94

      Rating:
      3
  March 27, 2003
Reviewer:
      A reader
hhmmm ! So for the query "select * from TABLE(cast(p_array1 as numArray))", the
column name is column_value. Is that right ?

Had you not been there Tom, we would have never known. Where in the world is
this mentioned in the Oracle documentation ???

          Followup:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76976/adobjvew.htm#434487
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76976/adobjadv.htm#1002819

or, by discovery:

ops$tkyte@ORA920> create or replace type array as table of number
  2  /
Type created.

ops$tkyte@ORA920> select * from table(array(1));

COLUMN_VALUE
------------
           1

ops$tkyte@ORA920> variable x refcursor

ops$tkyte@ORA920> declare
  2      l_data array := array(1,2,3);
  3  begin
  4      open :x for
  5      select * from TABLE( cast( l_data as array ) );
  6  end;
  7  /
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> print x

COLUMN_VALUE
------------
           1
           2
           3

ops$tkyte@ORA920> create or replace function f return array
  2  as
  3  begin
  4      return null;
  5  end;
  6  /
Function created.

ops$tkyte@ORA920> create or replace view v
  2  as
  3  select * from table( cast( f as array ) );

View created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> desc v
 Name                          Null?    Type
 ----------------------------- -------- --------------------
 COLUMN_VALUE                           NUMBER

ops$tkyte@ORA920> declare
  2      l_theCursor     integer default dbms_sql.open_cursor;
  3      l_colCnt        number := 0;
  4      l_descTbl       dbms_sql.desc_tab;
  5  begin
  6      dbms_sql.parse(  l_theCursor,
  7                          'select * from table( array(1,2,3) )',
  8                                           dbms_sql.native );
  9
 10      dbms_sql.describe_columns
 11          ( l_theCursor, l_colCnt, l_descTbl );
 12
 13      for i in 1 .. l_colCnt loop
 14          dbms_output.put_line( l_descTbl(i).col_name );
 15      end loop;
 16  end;
 17  /
COLUMN_VALUE

PL/SQL procedure successfully completed.

      Rating: 5
wow ! more than
      most useful ...   March 27, 2003
Reviewer:
      Max
Tom, you are great with examples -- just no words to praise and much more than 5
stars to rate your answers.

      Rating: 5
RE: myTableType
      data type  March 27, 2003
Reviewer:  Max

Tom, does it matter what type myTableType is ? I tried switching it between
varchar2(255) & number and the script just ran fine -- so I have just kept it as
varchar2(255).

          Followup:

if you use delimited strings of numbers -- "table of number" works dandy.  If
you use strings as my example did, it would not work.

      Rating:
      5
  March 27, 2003
Reviewer:  Murugan
       from Orlando, FL
Thomas, you can try this too.

SQL> select job, count(*)
  2  from emp
  3  group by job
  4  /

JOB        COUNT(*)
--------- ---------
ANALYST           4
CLERK             7
MANAGER           3
PRESIDENT         1
SALESMAN          4

SQL> declare
  2    v_char  varchar2(100) := '''CLERK'', ''SALESMAN'', ''PRESIDENT''';
  3    v_count number;
  4  begin
  5    select count(*)
  6    into v_count
  7    from emp
  8    where instr(v_char, ''''||job||'''') > 0;
  9    dbms_output.put_line('Count: '||v_count);
 10  end;
 11  /
Count: 12

          Followup:

sure -- but consider if

o emp has more then a toy amount of data...
o the thing you IN on should use an index...
o you preclude that from being possible...

That is the objective here -- to use a true IN, not apply a function to the
column and preclude an index path.

      Rating: 5
Just a
      Query?  April 16, 2003
Reviewer:  SD
       from UK
What is the diff. between a Collection and an Object type
When I say

create or replace type bat_type as table  of number (9) index by binary_integer
;
it creates it as object type
and
create or replace type bat_type as table  of number (9) ;
gets created as collection , why?

          Followup:

funny, when I do it:

  1* create or replace type bat_type as table  of number (9) index by
binary_integer
ops$tkyte@ORA920LAP> /

Warning: Type created with compilation errors.

ops$tkyte@ORA920LAP> show err
Errors for TYPE BAT_TYPE:

LINE/COL ERROR
-------- -----------------------------------------------------------------
0/0      PL/SQL: Compilation unit analysis terminated
1/18     PLS-00355: use of pl/sql table not allowed in this context

I get an error.  index by binary_integer is valid ONLY in plsql -- it is a plsql
index by table.

A collection defined using CREATE TYPE is an object type.

      Rating: 5
New
      feature  April 16, 2003
Reviewer:  Martin
       from UK
Tom, the CREATE TYPE .. INDEX BY BINARY_INTEGER is a 10i feature. Didn't you
know that? ;-)

      Rating:
      4
Sorry  April 17, 2003
Reviewer:  SD
       from UK
I'm sorry for this stupid mistake, actually I was using some third party tool
and it showed me under Object_type but did not give any compilation error, but
after looking into it I found that the complete attribute was False. Thanks for
your support.
Thanks Martin ;)

      Rating: 5
Good technique,
      but watch out for partitions  April 18, 2003

Reviewer:  Basil
I've been making heavy use of the IN (SELECT * FROM TABLE(CAST (x AS
numTableType))) for a while now. However, I recently converted the most critical
tables involved to list-based partitioned tables (I'm running 9.2.0.1.0).

If I partition my table (containing perhaps 30 million rows) on, say, cust_id,
running a query like this:

INSERT INTO targettable
SELECT * FROM srctable WHERE cust_id IN (SELECT * FROM TABLE(CAST( x AS
numTableType)))

is taking some 4 minutes to run, when x contains one value representing 80,000
rows out of the 30 million.

Re-doing the query as dynamic SQL doing something like:
INSERT INTO targettable
SELECT * FROM srctable WHERE cust_id IN (42);

is taking 28 seconds to run for the same volume of data. This is all running on
a laptop - P4 2 GHz, 1 MB RAM, 400 MB or so SGA for Oracle.

Examining the trace files with tkprof reveals that the first query isn't pruning
partitions, while the second one is. Ouch.

      Rating:
      5
ORA-06530  June 02, 2003
Reviewer:
      Paul  from UK
Tom,

I am trying to use this method for a similar problem (in 8.1.7.4).  I have a
string that contains pairs of data, so that I would want to convert
12345,A,23456,B,67890,C to

12345 A
23456 B
67890 C

No problemo, I thought, but when I do the following, I get the ORA-06530 error:

create or replace type my_obj as object(
col1 varchar2(20),
col2 varchar2(1)
)
/
create or replace type my_tab_type as table of my_obj
/

create or replace function f_my_convert(
   p_string   in   varchar2
)
   return my_tab_type as
   l_string   varchar2(2000)  default p_string || ',';
   l_data     my_tab_type := my_tab_type();
   l_instr    number;
begin
   loop
      exit when l_string is null;
      l_data.extend;
      -- first in pair
      l_instr := instr(l_string,',');
      l_data(l_data.count).col1 := trim(substr(l_string,1,l_instr - 1));
      l_string := substr(l_string,l_instr + 1);
      -- second in pair
      l_instr := instr(l_string,',');
      l_data(l_data.count).col2 := trim(substr(l_string,1,l_instr - 1));
      l_string := substr(l_string,l_instr + 1);
   end loop;
   return l_data;
end;
/

select *
from the
        ( select cast( f_my_convert('12345,A,23456,B,67890,C') as
                              my_tab_type ) from dual ) a
/

I know I must be doing something stupid, but I can't see what.  I'd be grateful
if you had any ideas.

Thanks

          Followup:

you are getting:

        ( select cast( f_my_convert('12345,A,23456,B,67890,C') as
                       *
ERROR at line 3:
ORA-06530: Reference to uninitialized composite
ORA-06512: at "OPS$TKYTE.F_MY_CONVERT", line 14

you are missing the setting of the i'th element of the collection to an empty
object instance:

   loop
      exit when l_string is null;
      l_data.extend;
      l_data(l_data.count) := my_obj(null,null);

      Rating:
      5
Paul  June 02, 2003
Reviewer:  Paul
       from UK
Thanks very much (again!!!)

Paul

      Rating: 5
arguments to a
      proceudre  August 19, 2003
Reviewer:  santhanam
       from India
Excellent
thanks tom

      Rating:
      5
  August 20, 2003
Reviewer:  Rick
       from KC
Thanks Tom,

I have implemented this method where the application passes in a string of IDs.
This has helped solve a few problems.

One thing I have noticed is performance is not very good using the STR2TBL
function and MYTABLETYPE collection.  Here is what I have.

Table is:
SQL> desc CNA;
 Name               Null?    Type
 ------------------ -------- ------------
 CNA_ID             NOT NULL NUMBER(15)
 NOTIFYAPPROVETYPE  NOT NULL NUMBER(2)
 DELETED            NOT NULL NUMBER(2)
 CNA_WHO_ID         NOT NULL NUMBER(15)
 CNA_WHO_TYPE       NOT NULL NUMBER(15)
 ASSOC_ID1          NOT NULL NUMBER(15)
 ASSOC_ID2          NOT NULL NUMBER(15)
 ASSOC_ID3          NOT NULL NUMBER(15)

SQL> select count(1) from CNA;

  COUNT(1)
----------
    122827

COL INDEX_NAME format A30 heading 'INDEX_NAME'
COL COLUMN_NAME format A30 heading 'COLUMN_NAME'
COL COLUMN_POSITION format 99 heading 'COLUMN_POSITION'

SQL> select INDEX_NAME, COLUMN_NAME, COLUMN_POSITION
  2   from user_ind_columns where table_name = 'CNA';

INDEX_NAME       COLUMN_NAME    COLUMN_POSITION
---------------- -------------- ---------------
PK_CNA         CNA_ID         1

AK_CNA_EMP       CNA_WHO_ID     1
AK_CNA_EMP       CNA_WHO_TYPE   2

AK_CNA_ASSOC     ASSOC_ID1      1
AK_CNA_ASSOC     ASSOC_ID2      2
AK_CNA_ASSOC     ASSOC_ID3      3
AK_CNA_ASSOC     DELETED        4

AK_CNA_ASSOC2    ASSOC_ID2      1
AK_CNA_ASSOC3    ASSOC_ID3      1

Now I do use variables for the IN statements and the Count  (The values in the
IN statements may be different for each ASSOC_ID but are the same in this case).

The query looks like this:

SQL> SELECT   NOTIFYAPPROVETYPE, CNA_WHO_ID, CNA_WHO_TYPE
  2      FROM CNA
  3     WHERE DELETED = 0
  4       AND ASSOC_ID1 IN (0,4525, 420, 81644)
  5       AND ASSOC_ID2 IN (0,4525, 420, 81644)
  6       AND ASSOC_ID3 IN (0,4525, 420, 81644)
  7  GROUP BY NOTIFYAPPROVETYPE,
  8           CNA_WHO_ID,
  9           CNA_WHO_TYPE
 10    HAVING COUNT (1) > (0)
 11  ORDER BY 3;

NOTIFYAPPROVETYPE CNA_WHO_ID CNA_WHO_TYPE
----------------- ---------- ------------
                2       3984            1
                2       9569            1
                2      23805            1
                2      24061            1
                2      29514            1
                2      35844            1
                2      36816            1
                2      38201            1
                2      38612            1
                2      38824            1
                2      39739            1
                2      42216            1
                2      53138            1

13 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=1 Bytes=18)
   1    0   FILTER
   2    1     SORT (GROUP BY) (Cost=5 Card=1 Bytes=18)
   3    2       INLIST ITERATOR
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'CNA' (Cost=3 Card=1
Bytes=18)
   5    4           INDEX (RANGE SCAN) OF 'AK_CNA_ASSOC' (NON-UNIQUE) (Cost=2
Card=1)

Here is the Query with the Function and Collection:

SQL> SELECT   NOTIFYAPPROVETYPE, CNA_WHO_ID, CNA_WHO_TYPE
  2      FROM CNA
  3     WHERE DELETED = 0
  4       AND ASSOC_ID1 IN (SELECT *
  5                           FROM THE (SELECT CAST
  6           (STR2TBL ('0,4525, 420, 81644') AS MYTABLETYPE)
  7                           FROM DUAL))
  8       AND ASSOC_ID2 IN (SELECT *
  9                           FROM THE (SELECT CAST
 10           (STR2TBL ('0,4525, 420, 81644') AS MYTABLETYPE)
 11                           FROM DUAL))
 12       AND ASSOC_ID3 IN (SELECT *
 13                           FROM THE (SELECT CAST
 14           (STR2TBL ('0,4525, 420, 81644') AS MYTABLETYPE)
 15                           FROM DUAL))
 16  GROUP BY NOTIFYAPPROVETYPE,
 17           CNA_WHO_ID,
 18           CNA_WHO_TYPE
 19    HAVING COUNT (1) > (0)
 20  ORDER BY 3;

NOTIFYAPPROVETYPE CNA_WHO_ID CNA_WHO_TYPE
----------------- ---------- ------------
                2       3984            1
                2       9569            1
                2      23805            1
                2      24061            1
                2      29514            1
                2      35844            1
                2      36816            1
                2      38201            1
                2      38612            1
                2      38824            1
                2      39739            1
                2      42216            1
                2      53138            1

13 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=19871045 Card=52 Bytes=2964)
   1    0   FILTER
   2    1     SORT (GROUP BY) (Cost=19871045 Card=52 Bytes=2964)
   3    2       HASH JOIN (Cost=47803 Card=697807331 Bytes=39775017867)
   4    3         VIEW OF 'VW_NSO_3' (Cost=21 Card=8168 Bytes=106184)
   5    4           SORT (UNIQUE) (Cost=21 Card=8168 Bytes=16336)
   6    5             COLLECTION ITERATOR (PICKLER FETCH)
   7    6               TABLE ACCESS (FULL) OF 'DUAL' (Cost=1 Card=1)
   8    3         HASH JOIN (Cost=1156 Card=19051302 Bytes=838257288)
   9    8           VIEW OF 'VW_NSO_1' (Cost=21 Card=8168 Bytes=106184)
  10    9             SORT (UNIQUE) (Cost=21 Card=8168 Bytes=16336)
  11   10               COLLECTION ITERATOR (PICKLER FETCH)
  12   11                 TABLE ACCESS (FULL) OF 'DUAL' (Cost=1 Card=1)
  13    8           HASH JOIN (Cost=95 Card=545789 Bytes=16919459)
  14   13             VIEW OF 'VW_NSO_2' (Cost=21 Card=8168 Bytes=106184)
  15   14               SORT (UNIQUE) (Cost=21 Card=8168 Bytes=16336)
  16   15                 COLLECTION ITERATOR (PICKLER FETCH)
  17   16                   TABLE ACCESS (FULL) OF 'DUAL' (Cost=1 Card=1)
  18   13             TABLE ACCESS (FULL) OF 'CNA' (Cost=40 Card=61408
Bytes=1105344)

Database is currently 8.1.7.4.  We will be moving to 9iR2 the first of the year.

Thanks for your time.

          Followup:

it doens't know the cardinality of the str2tbl and is guessing 8,168 rows.

i would do two things:

a) add "where rownum >= 0" to the subqueries
b) add /*+ FIRST_ROWS */ to the outer query

      Rating:
      5
  August 21, 2003
Reviewer:  Rick
       from KC
Didn't Help.  I got the same execution plan as without the "where rownum >= 0"
and "/*+ FIRST_ROWS */ "
I even tried it with a hint to use the index.  The query is not all that bad
now.  But it is one of the heavier ones and it gets executed about 400-500 times
a day.  The table growth is about 20K rows a month so it will just keep getting
worse.  I have built a test script to help.

Thanks for your time and knowledge.

CREATE OR REPLACE
TYPE MYTABLETYPE
 AS TABLE OF NUMBER(15,0)
/

CREATE OR REPLACE
FUNCTION STR2TBL (
      P_STR                      IN       VARCHAR2
   )
      RETURN MYTABLETYPE AS
      L_STR     LONG        DEFAULT P_STR || ',';
      L_N       NUMBER;
      L_DATA    MYTABLETYPE := MYTABLETYPE ();
      V_ID      NUMBER;
   BEGIN
      LOOP
         L_N := INSTR (L_STR, ',');
         EXIT WHEN (NVL (L_N, 0) = 0);
         V_ID := NVL (LTRIM (RTRIM (
               SUBSTR (L_STR, 1, L_N - 1))), -9999999999);

         IF V_ID != -9999999999 THEN
            L_DATA.EXTEND;
            L_DATA (L_DATA.COUNT) := V_ID;
         END IF;

         L_STR := SUBSTR (L_STR, L_N + 1);
      END LOOP;

      RETURN L_DATA;
   END;
/

CREATE TABLE T AS (
SELECT
   OBJECT_ID + 100000 CNA_ID,
   OBJECT_NAME,
   OBJECT_TYPE,
   0    DELETED,
   OBJECT_ID ASSOC_ID1,
   0    ASSOC_ID2,
   0    ASSOC_ID3
FROM ALL_OBJECTS
UNION
SELECT
   OBJECT_ID + 200000 CNA_ID,
   OBJECT_NAME,
   OBJECT_TYPE,
   1    DELETED,
   0    ASSOC_ID1,
   OBJECT_ID ASSOC_ID2,
   0    ASSOC_ID3
FROM ALL_OBJECTS
UNION
SELECT
   OBJECT_ID + 300000 CNA_ID,
   OBJECT_NAME,
   OBJECT_TYPE,
   0    DELETED,
   0    ASSOC_ID1,
   0    ASSOC_ID2,
   OBJECT_ID ASSOC_ID3
FROM ALL_OBJECTS
UNION
SELECT
   OBJECT_ID + 400000 CNA_ID,
   OBJECT_NAME,
   OBJECT_TYPE,
   0    DELETED,
   OBJECT_ID ASSOC_ID1,
   OBJECT_ID ASSOC_ID2,
   0    ASSOC_ID3
FROM ALL_OBJECTS
UNION
SELECT
   OBJECT_ID + 500000 CNA_ID,
   OBJECT_NAME,
   OBJECT_TYPE,
   1    DELETED,
   OBJECT_ID ASSOC_ID1,
   0    ASSOC_ID2,
   OBJECT_ID ASSOC_ID3
FROM ALL_OBJECTS
UNION
SELECT
   OBJECT_ID + 600000 CNA_ID,
   OBJECT_NAME,
   OBJECT_TYPE,
   0         DELETED,
   OBJECT_ID ASSOC_ID1,
   OBJECT_ID ASSOC_ID2,
   OBJECT_ID ASSOC_ID3
FROM ALL_OBJECTS);

CREATE INDEX AK_T_ASSOC ON T
  (
    ASSOC_ID1                       ASC,
    ASSOC_ID2                       ASC,
    ASSOC_ID3                       ASC,
    DELETED                         ASC
  )
/

ALTER TABLE T
ADD CONSTRAINT PK_T PRIMARY KEY (CNA_ID)
USING INDEX
/

analyze table t compute statistics;

SET AUTOTRACE ON

SELECT   CNA_ID, OBJECT_NAME, OBJECT_TYPE
FROM T
WHERE DELETED = 0
    AND ASSOC_ID1 IN (0,4525, 420, 81644)
    AND ASSOC_ID2 IN (0,4525, 420, 81644)
    AND ASSOC_ID3 IN (0,4525, 420, 81644)
GROUP BY CNA_ID, OBJECT_NAME, OBJECT_TYPE
HAVING COUNT (1) > (0)
ORDER BY 3;

SELECT   CNA_ID, OBJECT_NAME, OBJECT_TYPE
FROM T
WHERE DELETED = 0
  AND ASSOC_ID1 IN (SELECT * FROM THE (SELECT CAST
    (STR2TBL ('0,4525, 420, 81644') AS MYTABLETYPE)
           FROM DUAL))
  AND ASSOC_ID2 IN (SELECT * FROM THE (SELECT CAST
    (STR2TBL ('0,4525, 420, 81644') AS MYTABLETYPE)
           FROM DUAL))
  AND ASSOC_ID3 IN (SELECT * FROM THE (SELECT CAST
    (STR2TBL ('0,4525, 420, 81644') AS MYTABLETYPE)
           FROM DUAL))
GROUP BY CNA_ID, OBJECT_NAME, OBJECT_TYPE
HAVING COUNT (1) > (0)
ORDER BY 3;

SELECT  /*+ FIRST_ROWS */
 CNA_ID, OBJECT_NAME, OBJECT_TYPE
FROM T
WHERE DELETED = 0
  AND ASSOC_ID1 IN (SELECT * FROM THE (SELECT CAST
    (STR2TBL ('0,4525, 420, 81644') AS MYTABLETYPE)
           FROM DUAL) where rownum >= 0)
  AND ASSOC_ID2 IN (SELECT * FROM THE (SELECT CAST
    (STR2TBL ('0,4525, 420, 81644') AS MYTABLETYPE)
           FROM DUAL) where rownum >= 0)
  AND ASSOC_ID3 IN (SELECT * FROM THE (SELECT CAST
    (STR2TBL ('0,4525, 420, 81644') AS MYTABLETYPE)
           FROM DUAL) where rownum >= 0)
GROUP BY CNA_ID, OBJECT_NAME, OBJECT_TYPE
HAVING COUNT (1) > (0)
ORDER BY 3;

      Rating: 4
multiple column
      subquery  August 28, 2003
Reviewer:  umesh
       from bangalore India
Tom
Extremely Sorry for asking this silly thing here
I am involved in migration of database from sqlserver to Oracle  and I caught
one of the queries in SQL server wrong and I dont know whom do i contact

In Oracle I would have simply written
select * from emp where
( empno,deptno) in ( select empno,deptno from emp_1) ;

Any help can I get in converting this to SQL server
becoz the view they have is based on this query;

          Followup:

guess sql server isn't as "sql-9x" as the say....

use a where exists if they don't support that construct

where exists ( select null from emp_1 where emp_1.empno = emp.empno and
emp_1.deptno = emp.deptno )

      Rating: 5
clob instead of
      varchar2 in list  September 17, 2003
Reviewer:
      Sandeep  from Germany
I was going to change your procedure to accept clob instead of varchar2...
I need Pstring to be more than 4000 characters...hence I thought about clobs..
I changed it as follows and got errors...can this be done..if yes...how?
Thanks... and clob parameter will go to 32k right?:

create or replace
  function in_list( p_string in clob ) return myTableType
  as
      l_string        clob default p_string || ',';
      l_data          myTableType := myTableType;
      n               number;
  begin
    loop
        exit when l_string is null;
        n := instr( l_string, ',' );
        l_data.extend;
        l_data(l_data.count) :=
              ltrim( rtrim( substr( l_string, 1, n-1 ) ) );
        l_string := substr( l_string, n+1 );
   end loop;

   return l_data;
 end;
 /

Function created.

SQLWKS> create or replace
     2>   function in_list( p_string in clob ) return myTableType
     3>   as
     4>       l_string        clob default p_string || ',';
     5>       l_data          myTableType := myTableType;
     6>       n               number;
     7>   begin
     8>     loop
     9>         exit when l_string is null;
    10>         n := instr( l_string, ',' );
    11>         l_data.extend;
    12>         l_data(l_data.count) :=
    13>               ltrim( rtrim( substr( l_string, 1, n-1 ) ) );
    14>         l_string := substr( l_string, n+1 );
    15>    end loop;
    16>
    17>    return l_data;
    18>  end;
    19>  /

LINE/COL ERROR
-------- -----------------------------------------------------------------
3/23     PL/SQL: Item ignored
3/36     PLS-00306: wrong number or types of arguments in call to '||'
8/9      PL/SQL: Statement ignored
8/19     PLS-00320: the declaration of the type of this expression is
         incomplete or malformed

9/9      PL/SQL: Statement ignored
9/21     PLS-00320: the declaration of the type of this expression is
         incomplete or malformed

11/9     PL/SQL: Statement ignored
12/37    PLS-00320: the declaration of the type of this expression is
         incomplete or malformed

13/9     PLS-00320: the declaration of the type of this expression is
         incomplete or malformed

13/9     PL/SQL: Statement ignored

          Followup:

until 9ir2, you have to use the dbms_lob package to add stuff to the end of a
lob like that.

if you have such a seriously big inlist -- i would suggest you do not use this
technique but consider loading the stuff into a global temporary table using
bulk inserts and then query "where in (select * from gtt)"

it'll be bothersome to use a clob for this.

      Rating: 5
CLOB in
      list  September 17, 2003
Reviewer:  Sandeep
       from Germany
Database version is 8.1.7.4

Thanks.

      Rating: 4
no rows
      selected  October 07, 2003
Reviewer:  Bayo
       from England
Thanks for the trick. I've tried your example using the all_users table, but
when I try to use one of my own tables, the result is always no rows selected.
see what I did below.

create or replace type myTableType as table of varchar2 (255);

/
create or replace
     function in_list( p_string in varchar2 ) return myTableType
    as
        l_string        long default p_string || ',';
        l_data          myTableType := myTableType();
        n               number;
    begin
      loop
          exit when l_string is null;
          n := instr( l_string, ',' );
         l_data.extend;
         l_data(l_data.count) := ltrim( rtrim( substr( l_string, 1, n-1 ) ) );
         l_string := substr( l_string, n+1 );
    end loop;

    return l_data;
  end;
  /

 select * from all_users where username in ( select * from THE ( select cast(
in_list('OPS$TKYTE, SYS, SYSTEM')  as mytableType ) from dual ) );

The result for the above selected stsement is:

USERNAME                          USER_ID CREATED
------------------------------ ---------- ---------
SYS                                     0 20-MAY-99
SYSTEM                                  5 20-MAY-99

Here is a select statement using on of my own table.
select * from CSHPTDETAILS where CASHPOINTCODE in ( select * from THE ( select
cast( in_list('1,1ABC,TCD')  as myTableType ) from dual ) );

The result for the above query is:
no rows selected

This is despite the fact that I have data with 1,1ABC,TCD as cashpointcode in
the CSHPTDETAILS table..

Many Thanks for your anticipated help.

          Followup:

one needs a FULL test case to get any sort of meaningful feedback.  For example
-- I'll post two -- one that "works" and one that "works differently but looks
like your example" (eg: both work -- do what they are supposed to)

ops$tkyte@ORA920> drop table CSHPTDETAILS;

Table dropped.

ops$tkyte@ORA920> create table CSHPTDETAILS ( cashpointcode varchar2(20) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into CSHPTDETAILS values ( '1' );

1 row created.

ops$tkyte@ORA920> insert into CSHPTDETAILS values ( '1ABC' );

1 row created.

ops$tkyte@ORA920> insert into CSHPTDETAILS values ( 'TCD' );

1 row created.

ops$tkyte@ORA920> insert into CSHPTDETAILS values ( 'Hello World' );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from CSHPTDETAILS where CASHPOINTCODE in ( select *
from THE ( select
  2  cast( in_list('1,1ABC,TCD')  as myTableType ) from dual ) );

CASHPOINTCODE
--------------------
1
1ABC
TCD

that shows, this "works", but how to make it "work" like yours?  I'll guess --
you are using a 'CHAR' datatype (bad idea if you are)

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> drop table CSHPTDETAILS;

Table dropped.

ops$tkyte@ORA920> create table CSHPTDETAILS ( cashpointcode char(20) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into CSHPTDETAILS values ( '1' );

1 row created.

ops$tkyte@ORA920> insert into CSHPTDETAILS values ( '1ABC' );

1 row created.

ops$tkyte@ORA920> insert into CSHPTDETAILS values ( 'TCD' );

1 row created.

ops$tkyte@ORA920> insert into CSHPTDETAILS values ( 'Hello World' );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from CSHPTDETAILS where CASHPOINTCODE in ( select *
from THE ( select
  2  cast( in_list('1,1ABC,TCD')  as myTableType ) from dual ) );

no rows selected

when you compare the char to the varchar2 -- they do not compare since the
varchar2 doesn't have the trailing blanks (that just waste space in your
database and cause all kinds of heartburn like this)

Just a guess -- for since I don't have a full test case, thats the best I can
do.

      Rating: 4
no rows
      selected  October 07, 2003
Reviewer:  bayo
       from England
Thanks Tom,

Yes I'm using 'CHAR' datatype. This is a legacy system, and I'm not allowed to
start changing the datatype.

But is there any way of getting row returned if 'CHAR' data type is used.

Or is there any other method that works with both Varchar2 and CHAR datatypes.

Many Thanks for your help.

          Followup:

just rpad out the column_value column the nested table type returns to be the
same length as your database column

ops$tkyte@ORA920> select *
  2    from CSHPTDETAILS
  3   where CASHPOINTCODE in ( select rpad(column_value,20)
  4                from TABLE( cast( in_list('1,1ABC,TCD')  as myTableType )  )
  5                          )
  6  /

CASHPOINTCODE
--------------------
1
1ABC
TCD

      Rating: 4
no rows
      selected  October 08, 2003
Reviewer:  Bayo
       from England
Thanks Tom,

I got the following error when I rpad out the column_value column to return
the same length as my database column.

SP2-0642: SQL*Plus internal error state 2133, context 0:0:0
Unsafe to proceed
SP2-0642: SQL*Plus internal error state 2133, context 0:0:0
Unsafe to proceed

I think the problem may have something to do with the fact that my table has
more than one column.

Anyway, I've got it working by using CHAR instead of VARCHAR2.

This is what I did.

create or replace type myTableType as table of CHAR (3000);
/
create or replace
     function in_list( p_string in CHAR ) return myTableType
    as
        l_string        long default p_string || ',';
        l_data          myTableType := myTableType();
        n               number;
    begin
      loop
          exit when l_string is null;
          n := instr( l_string, ',' );
         l_data.extend;
         l_data(l_data.count) :=   ltrim( rtrim( substr( l_string, 1, n-1 ) ) )
;
         l_string := substr( l_string, n+1 );
    end loop;

    return l_data;
  end;
  /
Thanks very much for your help.

      Rating: 5
Optimizer
      problems with variable in-list  October 19, 2003

Reviewer:  Mark Manning  from Atlanta, GA
With regards to the problem Rick from NC listed, here is an
alternative.  Try rewriting the inlist into a inline view and
join it back to the table.  The situtation is rare, however,
I have encountered my index range scans becoming full table scans.

Here is my example:

CREATE TABLE INLIST_TEST NOLOGGING AS SELECT * FROM DBA_OBJECTS
/

BEGIN
FOR I IN 1 .. 67
LOOP
INSERT INTO INLIST_TEST
SELECT * FROM DBA_OBJECTS;
END LOOP;
COMMIT;
/

CREATE INDEX X_INLIST_TEST_N01 ON INLIST_TEST (OBJECT_ID)
NOLOGGING COMPUTE STATISTICS
/

ANALYZE TABLE INLIST_TEST COMPUTE STATISTICS
FOR TABLE FOR ALL INDEXED COLUMNS
/

My original SQL statement:

SELECT COUNT(*) FROM INLIST_TEST X
WHERE X.OBJECT_ID IN (1,2,3,4,5)

SELECT STATEMENT OPTIMIZER MODE=CHOOSE        Rows=1     Cost=4
  SORT AGGREGATE
    INLIST ITERATOR
      INDEX RANGE SCAN    X_INLIST_TEST_N01

Returns in less than 1 second.

Implement dynamic in-list and run.

SELECT COUNT(*) FROM INLIST_TEST X
WHERE X.OBJECT_ID IN
    (SELECT * FROM TABLE(CAST(CONVERT_INLIST('1,2,3,4,5') AS MY_NUM_TYPE)))

SELECT STATEMENT OPTIMIZER MODE=CHOOSE        Rows=1     Cost=11 M
  SORT AGGREGATE
    NESTED LOOPS SEMI
      TABLE ACCESS FULL     INLIST_TEST
      COLLECTION ITERATOR PICKLER FETCH    CONVERT_INLIST

Returns in 30 seconds.

Ok.  Lets force index usage.

SELECT /*+ INDEX(X) */ COUNT(*) FROM INLIST_TEST X
WHERE X.OBJECT_ID IN
    (SELECT * FROM TABLE(CAST(CONVERT_INLIST('1,2,3,4,5') AS MY_NUM_TYPE)))

SELECT STATEMENT OPTIMIZER MODE=CHOOSE        Rows=1  Cost=12 M
  SORT AGGREGATE
    NESTED LOOPS SEMI
      INDEX FAST FULL SCAN    X_INLIST_TEST_N01
      COLLECTION ITERATOR PICKLER FETCH    CONVERT_INLIST

Returns in 27 seconds.

Rewrite variable in-list into a inline view.

SELECT COUNT(*) FROM INLIST_TEST X,
(SELECT COLUMN_VALUE AS OBJ_ID FROM TABLE(CAST(CONVERT_INLIST('1,2,3,4,5') AS
MY_NUM_TYPE))) Y
WHERE X.OBJECT_ID = Y.OBJ_ID

SELECT STATEMENT OPTIMIZER MODE=CHOOSE        Rows=1  Cost=792
  SORT AGGREGATE
    HASH JOIN
      COLLECTION ITERATOR PICKLER FETCH    CONVERT_INLIST
      INDEX FAST FULL SCAN    X_INLIST_TEST_N01

Returns in 3 seconds.

Force index usage with inline view.

SELECT /*+ INDEX(X) */ COUNT(*) FROM INLIST_TEST X,
(SELECT COLUMN_VALUE AS OBJ_ID FROM TABLE(CAST(CONVERT_INLIST('1,2,3,4,5') AS
MY_NUM_TYPE))) Y
WHERE X.OBJECT_ID = Y.OBJ_ID

SELECT STATEMENT OPTIMIZER MODE=CHOOSE        Rows=1  Cost=8149
  SORT AGGREGATE
    NESTED LOOPS
      COLLECTION ITERATOR PICKLER FETCH    CONVERT_INLIST
      INDEX RANGE SCAN    X_INLIST_TEST_N01

Returns in less than 1 second.

      Rating: 3
error
      sp2-0642......  December 09, 2003
Reviewer:
      Anurag  from INDIA
Dear Tom,

You've been always giving a right solution to our problem. I am having a testing
Oracle 8i and Oracle 9i both installed on same machine with winxp. I tried to
install D2k ver 6.0 . Now starting with sql*plus it pops a message "???.msb
error" . I tried to uninstall and removed D2k entries from registry. Now while
starting SQL*PLUS of 8i/9i it displays sp2-0642:sql*plus internal error 2167,
context 4294967294:2:0 unable to proceed.

What to do??? pl. Help.

          Followup:

sounds like your oracle home is set wrong

use the oracle home selector tool from the start menu.

      Rating: 2
TNS
      error  December 10, 2003
Reviewer:  Anurag
       from INDIA
Yes, After setting OracleHome, it says
Ora-12560 TNS Protocol Adapter error

May be the protocols are over-written by older ver.??

Do the needful.

Regards,

Anurag

          Followup:

please contact support -- i don't use windows -- there is some configuration
issue here -- they are excellent at helping with that.

(could be that the database isn't even started, you'll get that trying to
connect locally to a "not up" database on windows as I recall from the distant
past)

      Rating: 5
Is there a
      simple way to get distinct values from the array?  December
      11, 2003
Reviewer:  Meyer  from Baltimore, MD

I read above (and a few others) and want to make sure I am going to take the
correct aproach....in 9Ir2 I am doing this...

declare
 l_vc_arr2    wwv_flow_global.vc_arr2;
begin

l_vc_arr2 := WWV_FLOW_UTILITIES.STRING_TO_TABLE(l_the_colon_delim_string);

       FOR z IN 1..l_vc_arr2.count LOOP

---processing here, only want to do once for each UNIQUE value instead of each
individual value as there can be duplicates in the array.

end loop;
end;

The array typically contains 1-10 email addresses (that started in the colon
delim list) but can contain duplicates in that set (due to logic I introduced
but want to keep), is there a simple way to select a distinct set from the array
for processing?

Thanks,
Meyer

          Followup:

well, if you use a SQL TYPE, you can use my in_list logic and "select distinct"
from it.

haven't really seen which would be faster for 1..10 elements tho.  might be 6
one way, 1/2 dozen the other.

      Rating: 5
Very
      Useful  December 24, 2003
Reviewer:  Sami

December 11, 2003 Mark -- Thanks for the question regarding "J2EE
      Sister Site", version 9.0.3

      You Asked
Hi Tom,

Once upon a month or so ago I found a link on the Ask Tom page to a sister site
covering Oracle related J2EE. I did not bookmark it and can no longer find it.
Would you be so kind as to publish the link again.

Thanks for this service, I have been a regular reader for some time now and have
recommended your One-On-One book many time.

Mark

      and we said...
Sure - it is:

http://devtrends.oracle.com/
run by Cameron O'Rourke....
Review &
      Followup

      Rating: 5
Quick
      Question  November 16, 2002
Reviewer:  Doug
       from CT, USA
Which came first - project Marvel or asktom? And now devtrends.

          Followup:

well -- asktom predates marvel being what it is.  but the genesis of marvel
predates asktom.

asktom started in january of 2000.  Iinitially, asktom was "perl" and "email" (i
had nothing to do with that!).  No database.  That didn't "scale" very well (eg:
i had to way to turn it OFF and my inbox would fill up).  So, I wrote "asktom
version 2" using the htp/htf packages by hand.  Then Marvel was up and running
and the guys there wanted an interactive "support site" concept similar to
asktom.  So, they built the shell that is asktom and we swapped out my stuff for
theirs (they took my low level APIs to insert/update/delete my base tables and
put a new skin on it).  Devtrends is the same software (as is the marvel support
site, among others)

Project Marvel began life way be as something called "webview".  "Webview"
became Webdb which became portal.  Now, Portal is a portal not really an
application development environment and the guys who built webview felt that was
really what they wanted.  So, they picked up webview where it left off -- beefed
it up tons and It is now "project marvel, the software formally known as
webview"

So -- it is a chicken and egg question -- they both evolved wrapped around
eachother over time.

      Rating: 5
Quick
      Question  November 16, 2002
Reviewer:  Doug
       from CT, USA
Which came first - project Marvel or asktom? And now devtrends.

      Rating: 5
Need to contact
      DevTrends  December 01, 2003
Reviewer:
      Steven  from St. Louis, Missouri USA
2003-12-01  DevTrends web site has been down for 3 days with a tablespace error.
 I cannot find a link anywhere on that error page or AskTom that allows direct
communication with the webmaster - so - I am posting this here.  Please delete
it after transmitting to the responsible part.

      Rating: 3
2003-12-11
      DevTrends down again  December 11, 2003
Reviewer:
      sPh  from St. Louis, MO USA
2003-12-11
OK, now this error on DevTrends for two days:
  ORA-04063: has errors ORA-04063: package body "DEVTRENDS.WWC_ASK_ID" has
errors ORA-06508: PL/SQL: could not find program unit being called

sPh

          Followup:

thanks -- fixed.

December 11, 2003 Benny -- Thanks for the question regarding "best
      method for statistics", version 8.1.7

      You Asked
Can you explain the differences between the following methods in analyzing
objects, and if you have a preference, thanks!

alter table/index ... compute statistics;
dbms_utility.analyze_schema
dbms_stat

      and we said...
For quick and dirty examples -- I use analyze table t compute statistics for
table for all indexes for all indexed columns.

In real life, one would tend to use dbms_stats instead.  It supports the
gathering of statistics in parallel (and also the manual setting of stats) which
analyze does not.  It is easier to automate with dbms_job as it is a plsql
interface and doesn't require the use of dynamic sql.

Dbms_utility.analyze_schema is sort of a "left over" from 7.x -- before the
DBMS_STATS package was introduced.
Review &
      Followup

      Rating:
      4
  August 31, 2001
Reviewer:
      A reader
Thanks for the answer, just a quick follow-up, would using
the analyze command and dbms_stat produce the same statistical values?

          Followup:

As long as you used them in the equivalent fashion, yes they should.

      Rating:
      5
  September 05, 2001
Reviewer:
      Bhawna  from India
Hi Tom,
Due to the difference in time-zones, when we come to office, we always see the
message "Sorry, I already have a backlog of questions, ask later." on the
home-page.
Can we have some email ID, which we could use for posting questions to you ?
Thanks.

          Followup:

Well, the way it works is like this (i take 10 questions at a time):

in the morning (about 7-8am east coast US time) I do a batch.  Usually about
9am-10am or so, I am open for questions.  I take 10 more.

After lunch, if I can, I do another batch.  Usually around 1-2pm east coast
time, I take some more.

If I'm really lucky -- or the questions are very easy, I sometimes do another in
the afternoon (sometimes)... So 5pm east coast time is another good time to
look.

After I put the kids to bed -- I might do another set.  So, 9-10pm east coast
time is another great time to look....

Now, I ask people to put locations in when they submit questions.  What they put
into there is free form -- but a quick query sorted by count shows me:

... (thousands of records deleted)
Kuwait                                                60
Hyderabad                                             63
Mumbai                                                64
Malaysia                                              66
Pakistan                                              68
Ny                                                    69
Atlanta                                               75
Nj                                                    90
Bangalore                                             98
New Jersey                                           113
Singapore                                            197
San Jose                                             211
India                                                577

If I just look at locations with "india" in them:

  1  select count(*)
  2    from WWC_ASK_SUBMITTED_QUESTIONS$
  3*  where location is not null and upper(location) like '%INDIA%'
ask_tom@OSI816> /

  COUNT(*)
----------
      1403

ask_tom@OSI816> select count(*) from WWC_ASK_SUBMITTED_QUESTIONS$;

  COUNT(*)
----------
     11159

about 12% of the questions come from there (i think almost all of the 10pm
questions come from there actually)...

I've played with other schemes (eg: take 1 question every hour, take 30 all at
once, etc)  but the current scheme seems to work well as far as getting
questions from all over.

      Rating: 5
Analyze Any
      system privilege  September 07, 2001
Reviewer:
      D Tran  from Canada
I do like the solution of dbms_stats and dbms_job to gather schema statistics.
I submit the job as system to analyze another schema but get insufficient
privilege errors in the alert log -- the job is submitted cleanly but get
failures.
Only after granting the above sys priv to sys and system do they run.  Now sys
and system should get Analyze Any through the DBA role already, shouldn't they?

          Followup:

See

http://osi.oracle.com/~tkyte/Misc/RolesAndProcedures.html
for why privs in the stored procedure must be granted directly, not via a role.

      Rating:
      5
  October 01, 2002
Reviewer:
      A reader
"WWC_ASK_SUBMITTED_QUESTIONS$" - Is the $ sign there signify anything or is it
just the way you named it? (It is always curious to have a peek into your
applications internals....!!)

          Followup:

thats the way Joel Kallman named it - he did the Marvel layer for me (the skin
and branching from page to page)

It is just the way their group names things. (i find it annoying cause my telnet
window doesn't select the $ when I double click on the table name -- making me
type the $ in manually !)

      Rating: 4
DBMS_STATS PACK
      VERY SLOW COMPARED TO OLD ''ANALYZE''   December 09, 2003

Reviewer:  Billy L.  from Irvine, ca
We are trying to convert all our statistics collection from "analyze" to
"DBMS_STATS" pack, because this will be the only statistics collection tools
being enhanced and also strongly recommended by Oracle. However, the time it
takes to gather statistics with this package (gather_schema_stats,
gather_table_stats) is a lot longer than using the old "analyze" statement.
For example, to collect statistics on one of our smaller schemas:
1. Analyze (10%) - 23 minutes
2. Gather_schema_stats (10%, degree 2) - 41 minutes
3. Gather_schem_stats (Anto_sample_size, degree 2) - 2 hours 51 minutes
Option 3 (auto_sample_size) is recommended by Oracle, but it takes more than 7
times that of the analyze statement. It also runs with degree 2 (and spawns up
to 4 parallel processes) that consumes more resources when it runs. Even doing a
sample of 10% takes almost twice as long while consuming a lot more
resources.

Our concern is that with this kind of performance, we wouldn't be able to gather
whole schema statistics without impacting our online applications. It does
consume significant amount of system resources.

I understand there is an option to gather only on tables with stale stats.
However, we still need/want to gather all stats initially, and also once for a
while.  In your opinion, based on what you read, what can cause this?

          Followup:

dbms_stats does things with sql (thats why you can parallelize it).  it does
things differently.

with gather stale, you would significantly (probably) reduce the amount of time
it takes from day to day ( you would need to do an initial gather of stats on
all objects -- but if you wanted, you could kick start that with analyze -- but
after that you would never gather all stats on all objects )

      Rating: 5
Remeber reading
      a bug with dbms_stats on 9.2.0.3  December 10, 2003

Reviewer:  Scott Watson  from Montreal, Quebec Canada

I had to revert to analyze commands as dbms_stats in 9.2.0.3 was taking too
long.  After digging on Metalink for awhile I came across a bug logged against
9.2.0.3 for dbms_stats. If I remember correctly the performance problem was
fixed in 9.2.0.4.  However, I have not upgraded yet to test this.

          Followup:

i looked around -- there is a permanent change in dbms_stats in 9ir2 as to the
way it gathers index stats (used to use analyze in 9ir1 and before, uses SQL in
9ir2 and after).

      Rating: 4
Bug noticed on
      which platform?  December 10, 2003
Reviewer:
      A reader
Hi Scott,
We are also planning to move from 8.1.6.3 to 9iR2 on AIX platform, and our
scripts are using dbms_stats on Oracle 8.1.6.3. Could you tell on which platform
you faced this bug?

          Followup:

(its not a bug)

      Rating:
      4
Suggestion  December 10, 2003
Reviewer:
      A reader
Tom,
Thanks for confirming. It means we need not to worry about scripts written with
dbms_stats after upgrade.
Thanks

          Followup:

it means that a serial execution of dbms_stats may be penalized to the benefit
of parallelized dbms_stats actually - more operations are parallelizable now.

      Rating: 2
Uh,
      right...  December 11, 2003
Reviewer:  fred
       from Irvine, CA
It means with the major changes, dbms_stats will run slower than an analyze.

      Rating:
      4
Ouch!  December 11, 2003
Reviewer:
      A reader
In Oracle 8.1.6.3, I changed scripts to use dbms_stats from analyze, and one of
the main reason was to use parallelism, being a large DW environments. Does
9.2.0.4 or greater use parallelism?

          Followup:

all dbms_stats versions do? or capable of doing so.

this just allows more operations to be done in parallel

December 11, 2003 ashish -- Thanks for the question regarding
      "Chained rows", version 8.0.5

      You Asked
Hi tom,

Is there any way to remove chaining other
than export and import of table ?

Is there any procedure/utility/script to calculate/set PCTFREE
and PCTUSED parameter for a table .

Thanx
- Ashish

      and we said...
To remove chaining -- I don't think I would resort to
export/import.  Typically it is a small percentage of rows that
are affected.  Rather I would do:

o analyze the table, list chained rows

o create table temp_table as select * from old_table where rowid
in ( select ... from the exceptions table )

o delete from old_table where rowid in ( select ... from the
exceptions table )

o insert into old_table select * from temp_table;

o drop temp_table;

Someone just asked the pctfree/used Q this morning.  They had
some formulas that look at good as anything else -- see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:733825532206
for more info.  Basically, if it could be cut and dry computed -
we would.  It is a function of the block size, the storage
parameters like inittrans, the initial and final size of the
row, and so on...

Review &
      Followup

      Rating:
      5
  December 10, 2003
Reviewer:
      A reader
Tom,

Your suggestion is pretty good, how would you handle if the table has foreign
key reference.

Thanks.

          Followup:

you would have to either

a) use deferrable constraints
b) disable/move/re-enable constraints
c) alter table Parent move; alter index (all indexes on p) rebuild;

i can think of a trigger trick but it's nasty.

      Rating:
      5
  December 11, 2003
Reviewer:
      A reader
Tom,

Can you please elaborate on the trigger trick;

Thanks.

          Followup:

now that i think about it -- it wouldn't work.  so, nope, wont elaborate.  we
need to actually delete the row (i was thinking i could use a technique similar
to my update cascade trick -- but that works with updates -- but not deletes).

This will work:

analyze the table list chained rows to get the rowids of migrated rows.

let's assume the primary key is a sequence generated primary key starting at 1
and going up.....

insert into T
select -1 * primary_key, other_columns.....
  from t
 where rowid in (rowids of chained rows);

update t set primary_key = -1 * primary_key
where primary_key < 0
   or rowid in ( rowids of chained rows );

delete from t where rowid in ( rowids of chained rows );

of course, if you have other unique columns -- it gets trickier still (doable
with a correlated update but trickier still).

if the key isn't a sequence generated key -- then you have to make up a "nice
function" that can be used to generate a new unused primary key based on old
primary key that can easily be found (like where primary_key < 0) and easily
mapped back.

      Rating:
      5
  December 11, 2003
Reviewer:
      A reader
Tom forgot to mention, do you think its a good idea to use option c considering
i have 5 million records in the table, out of which round about 150,000 records
are migrated.

Database is in logging mode, lets say if i use nologging then is it gonna affect
my Standby database.

          Followup:

that is 3% of the rows.

unless I had massive counts of table fetch continued row -- i mean massive -- i
would do "nothing"

      Rating:
      5
ROT  December 11, 2003
Reviewer:
      Reader
Tom, Is there a ROT for what % of rows that are migrated from a table then, it
needs to be fixed? Thanks.

          Followup:

lots of them -- a % is so "weak".

I'd want the percentage AND the fact that table fetch continued row was
happening tons and tons of times.

so what if 90% of the rows are migrated -- but you never really touch them (eg:
you insert short rows, query them lots and lots, then update them -- causing
them to migrate and never index access them much again).

or, what if 10% of the rows are migrated -- but they migrate because you update
them and the rows that are updated are the MOST POPULAR rows.  that would be a
problem.

so, look at table fetch continued row, see if it is big enough to even care
about looking at this issue.

      Rating:
      5
  December 11, 2003
Reviewer:
      A reader
Tom,

Here is what i see, do you suggest it is worth doing this exercise?

I have 36 tables that are chained and their count is below.

Thanks.

SQL> select * from v$sysstat where name like '%table fetch continued%';

STATISTIC# NAME
---------- ----------------------------------------------------------------
     CLASS      VALUE
---------- ----------
       191 table fetch continued row
        64   35255817

SQL> select num_rows,chain_cnt from dba_tables where chain_cnt>0;

  NUM_ROWS  CHAIN_CNT
---------- ----------
       509          1
    377709        545
    823210     132532
    115347       3150
    362687      60455
    362687      36950
      4920         18
    217029      18104
       558          4
     11221        224
    145307          6
    313613       4842
      4361        557
     82071      25888
      1498        384
    205145        358
    362687       1191
     83365      39938
     87931       7567
    130755      10249
    300533     107047
    212620        476
    311288        272
     19379       1357
    116709       4491
   1109079      26537
     51691         69
    422134         48
      1014         26
  12325992      96647
  33357184      14785
     61239        272
     26091       2010
     23655        536
      2624        182
  19497946       6779

36 rows selected.

          Followup:

questions

is that 35,255,817 over 5 years, 5 months, 5 weeks, 5 days, 5 hours or what?
(statspack gives you a nice ability to get the number of a stat over a fixed
period of time)

is that 35 million a large pct of "table fetch by rowid"  if you have 3.5
billion of those, only 1% of the rows you fetch are migrated -- that might be
OK.

I would suggest looking at why i have so many chained rows.  are they MIGRATED
or CHAINED.  chained rows -- we cannot fix (well, without getting a bigger block
size).  migrated we can.  exclude from your report all tables with longs and
long raws -- they are almost always chained near 100% and are unfixable.  also,
flag tables with rows that are potentially larger than your blocksize.

then, ask "why do i have so many". you might consider rebuilding with larger
pctfree to avoid in the future (alter table T move ........ ) with a new
storage/pctfree/pctused clause.

      Rating:
      5
  December 11, 2003
Reviewer:
      A reader
questions

1. is that 35,255,817 over 5 years, 5 months, 5 weeks, 5 days, 5 hours or what?
(statspack gives you a nice ability to get the number of a stat over a fixed
period of time)

6 days worth.

2. is that 35 million a large pct of "table fetch by rowid"  if you have 3.5
billion of those, only 1% of the rows you fetch are migrated -- that might be
OK.

did not understand this question

3. I would suggest looking at why i have so many chained rows.  are they
MIGRATED or CHAINED.  chained rows -- we cannot fix (well, without getting a
bigger block size).  migrated we can.  exclude from your report all tables with
longs and long raws -- they are almost always chained near 100% and are
unfixable.  also, flag tables with rows that are potentially larger than your
blocksize.

then, ask "why do i have so many". you might consider rebuilding with larger
pctfree to avoid in the future (alter table T move ........ ) with a new
storage/pctfree/pctused clause.

Will definately look into those points.None of these tables thave long or long
raws. These are regular tables and yes the pctfree and pctused i have is
default.

db_block_size is 8k
d/b version is 9.2.0.3

  1* select avg_space,avg_row_len,num_rows,chain_cnt from dba_tables where
chain_cnt > 0
 AVG_SPACE AVG_ROW_LEN   NUM_ROWS  CHAIN_CNT
---------- ----------- ---------- ----------
      4903         107        509          1
       767          21     377709        545
       402         373     823210     132532
       271          85     115347       3150
       532         242     362687      60455
       333         296     362687      36950
      4137          73       4920         18
       256         193     217029      18104
      3943          72        558          4
      1001          67      11221        224
       236         122     145307          6
       950         212     313613       4842
       465         185       4361        557
       322         174      82071      25888
      1228          58       1498        384
      1106          76     205145        358
       831          58     362687       1191
      2901         318      83365      39938
      3965         286      87931       7567
      3984         117     130755      10249
       303         238     300533     107047
       226         203     212620        476
       814          20     311288        272
       736          74      19379       1357
       867          64     116709       4491
       463         174    1109079      26537
      1611         120      51691         69
       905         386     422134         48
      1589         422       1014         26
       874          51   12325992      96647
       500         255   33357184      14785
       791         109      61239        272
       272         217      26091       2010
       245          62      23655        536
       350         250       2624        182
       446         131   19497946       6779

36 rows selected.

          Followup:

2) look at the table fetch by rowid statistic (right next to the table fetch
continued row in a statspack or if you order by name).

that is the number of row fetches that COULD end up doing a table fetch
continued row.

What percentage of all rows fetched are chained rows?  35 million is X% of
<table fetch by rowid>.  What is X%?

3) with rows that "small", your chaining is probably all migratged rows.  you
need to look at pctfree larger than 10 in the future (or ASSM -- automatic
segment space managment in 9i)

      Rating: 4
Changing
      Tablespace  December 11, 2003
Reviewer:  Ronald
      Villard  from Stamford, Ct (USA)
What about moving table to a new tablespace, is it good to get rid of chaining.
I used it for small tables, what implications involve with big tables.

          Followup:

if you move a table to a new tablespace -- every row that CAN BE unchained will
be.

with big tables?  no implications other then "time", "redo"

      Rating:
      5
  December 11, 2003
Reviewer:
      A reader
SQL> select * from v$sysstat where name like '%table fetch%';

STATISTIC# NAME                                          CLASS      VALUE
---------- ---------------------------------------- ---------- ----------
       190 table fetch by rowid                             64 1141507679
       191 table fetch continued row                        64   37221488

Thanks.

          Followup:

so, about 3.2 out of every 100 rows

ops$tkyte@ORA9IR2> select 37221488/ 1141507679 * 100 from dual;

37221488/1141507679*100
-----------------------
             3.26073041

in your system causes an extra LIO.

you need to ask yourself "is fixing this worth a 3% reduction in LIO's"

but really -- you need to look at a much smaller window -- during your peak and
see what % of chained rows you hit then.

Problem is with "big averages" you have "not meaningful data"

look at a smaller window, during your busiest sorts of times -- see if the
migrated rows are "more" or "less" or the "same" popular during those times.
for a 3% reduction in table access by index rowid (which is only about 1/3 to
1/4th of the IO for an index read in the first place), I'd probably not do it --
unless they account for a big % of the LIOs during a busy time.

      Rating:
      5
  December 11, 2003
Reviewer:
      A reader
Thanks Tom for your valuable feedback ;-)

December 13, 2003 MEhmet -- Thanks for the question regarding
      "UPDATE using COLLECTION", version 9.0.1

      You Asked
ptyp_MyTypeList is MY Local variable..
MY_COLLECTION_TYPE is an collection obj which has elements
col1,col2,col3,col4.

In PL/SQL Code I try to update table_1 table using values in my collection.

     UPDATE
           (SELECT h.aa h_aa, z.bb z_bb
              FROM table_1 h,
                  (SELECT col1, col2, col3, col4 bb
                     FROM the (SELECT CAST( ptyp_MyTypeList AS
MY_COLLECTION_TYPE )
                                  FROM dual)) z
             WHERE h.col1 = z.col1
               AND h.col2 = z.col2
               AND h.col3 = z.col3
)
       SET h_aa = z_bb

I think it is not valid.
Should I ask why?

Is there any way to update a table using collection without FOR LOOP or FORALL
LOOP?

Thanks
meksi

      and we said...
there is a problem with key preservation in this example -- there isn't any.  A
collection type cannot have a primary key -- hence Oracle doesn't know that your
collection doesn't have it in:

ptyp_MyTypelist := my_collection_type( my_scalar_type( 1,1,1,55 ),
                                       my_scalar_type( 1,1,1,66 ) );

You see the results of that update would be ambigous -- would H_AA have 55 or 66
after the update?

So, the collection would need a primary key, but collections cannot have primary
keys -- hence -- no go.  You could:

ops$tkyte@ORA920> create type my_scalar_type as object
  2  ( col1 number, col2 number, col3 number, col4 number )
  3  /
Type created.

ops$tkyte@ORA920> create type my_collection_type as table of my_scalar_type
  2  /
Type created.

ops$tkyte@ORA920> drop table table_1;
Table dropped.

ops$tkyte@ORA920> create table table_1
  2  ( col1 number, col2 number, col3 number, col4 number,
  3    primary key(col1,col2,col3) )
  4  /
Table created.

ops$tkyte@ORA920> insert into table_1
  2  select rownum, rownum, rownum, null from all_users
  3  where rownum < 5;

4 rows created.

ops$tkyte@ORA920> declare
  2     ptyp_MyTypelist my_collection_type :=
  3         my_collection_type( my_scalar_type( 1,1,1,55 ),
  4                             my_scalar_type( 2,2,2,66 ) );
  5  begin
  6          merge into TABLE_1 H
  7          using (select col1, col2, col3, col4 bb
  8             from TABLE( cast( ptyp_myTypelist as my_collection_type ) ) ) z
  9          on (h.col1 = z.col1 and h.col2 = z.col2 and h.col3 = z.col3 )
 10      when matched then
 11                  update set h.col4 = z.bb
 12          when not matched then
 13              insert (col1) values (null);
 14  end;
 15  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from table_1;

      COL1       COL2       COL3       COL4
---------- ---------- ---------- ----------
         1          1          1         55
         2          2          2         66
         3          3          3
         4          4          4

now that did the update for you -- why I used INSERT NULL -- we'll see in a
moment -- first, we'll see what happens when we try to update with DUPS

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2     ptyp_MyTypelist my_collection_type :=
  3         my_collection_type( my_scalar_type( 1,1,1,55 ),
  4                             my_scalar_type( 1,1,1,66 ) );
  5  begin
  6          merge into TABLE_1 H
  7          using (select col1, col2, col3, col4 bb
  8                   from TABLE( cast( ptyp_myTypelist as my_collection_type )
) ) z
  9          on (h.col1 = z.col1 and h.col2 = z.col2 and h.col3 = z.col3 )
 10      when matched then
 11                  update set h.col4 = z.bb
 12          when not matched then
 13              insert (col1) values (null);
 14  end;
 15  /
declare
*
ERROR at line 1:
ORA-30926: unable to get a stable set of rows in the source tables
ORA-06512: at line 6

we caught the ambiguity and FAIL the update -- no telling what value should be
used, so we kill it.  Now, why did I do the insert NULL -- to make this fail:

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2     ptyp_MyTypelist my_collection_type :=
  3         my_collection_type( my_scalar_type( 1,1,1,55 ),
  4                             my_scalar_type( 100,1,1,66 ) );
  5  begin
  6          merge into TABLE_1 H
  7          using (select col1, col2, col3, col4 bb
  8                   from TABLE( cast( ptyp_myTypelist as my_collection_type )
) ) z
  9          on (h.col1 = z.col1 and h.col2 = z.col2 and h.col3 = z.col3 )
 10      when matched then
 11                  update set h.col4 = z.bb
 12          when not matched then
 13              insert (col1) values (null);
 14  end;
 15  /
declare
*
ERROR at line 1:
ORA-01400: cannot insert NULL into ("OPS$TKYTE"."TABLE_1"."COL1")
ORA-06512: at line 6

I did not want to CREATE rows (maybe you do -- then change the insert), just
update the existing ones.  Hence I either need to make sure I only have valid
entries in the collection (insert NULL does that nicely) or I can use a subquery
to filter the collection:

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2     ptyp_MyTypelist my_collection_type :=
  3         my_collection_type( my_scalar_type( 3,3,3,77 ),
  4                             my_scalar_type( 100,1,1,66 ) );
  5  begin
  6          merge into TABLE_1 H
  7          using (select col1, col2, col3, col4 bb
  8                   from TABLE( cast( ptyp_myTypelist as my_collection_type )
)
  9   where (col1, col2, col3) IN (select col1,col2,col3 from table_1) ) z
 10          on (h.col1 = z.col1 and h.col2 = z.col2 and h.col3 = z.col3 )
 11      when matched then
 12                  update set h.col4 = z.bb
 13          when not matched then
 14              insert (col1) values (null);
 15  end;
 16  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from table_1;

      COL1       COL2       COL3       COL4
---------- ---------- ---------- ----------
         1          1          1         55
         2          2          2         66
         3          3          3         77
         4          4          4

Review &
      Followup

      Rating:
      3
ORA-30926  June 19, 2003
Reviewer:
      A reader
tom,

Please explain me why i am getting this error. frankly i did not understand much
with the above explanation - sorry about that.

what should i do to avoid this error

17:44:38 ge$matrix@links.us.oracle.com>create table tb_today2 as select * from
tb_today;
17:46:32 ge$matrix@links.us.oracle.com>create table tb_today3 as select * from
tb_today;

18:27:56 ge$matrix@links.us.oracle.com>sta c:\ge\links\proc\practice\merge1.sql;
begin
*
ERROR at line 1:
ORA-30926: unable to get a stable set of rows in the source tables
ORA-06512: at line 2

18:27:58 ge$matrix@links.us.oracle.com>select count(*) from tb_today2 e1,
tb_today3 e2  where
18:28:44   2  e1.customer_index_num=e2.customer_index_num and
e1.requisition_num=e2.requisition_num and
e1.system_origin_nam=e2.system_origin_nam and e1.line_num=e2.line_num and
e1.sequence_num=e2.sequence_num;

  COUNT(*)
==========
      9873

18:28:55 ge$matrix@links.us.oracle.com>alter table tb_today3  add flag
varchar2(2);

--merge1.sql--
begin
 merge into tb_today2 e1 using tb_today3 e2 on
(e1.customer_index_num=e2.customer_index_num and
e1.requisition_num=e2.requisition_num and
e1.system_origin_nam=e2.system_origin_nam and e1.line_num=e2.line_num and
e1.sequence_num=e2.sequence_num)
 when matched then
 update set e1.flag='U'
 when not matched then
 insert (customer_index_num) values (e2.customer_index_num);
 commit;
 end;

Thanks for your consideration.

          Followup:

consider:

ops$tkyte@ORA920LAP> create table t1 ( x int, y int );
Table created.

ops$tkyte@ORA920LAP> create table t2 ( x int, y int );
Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> insert into t1 values ( 1, 1 );
1 row created.

ops$tkyte@ORA920LAP> insert into t2 values ( 1, 2 );
1 row created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> merge into t1 using t2
  2  on ( t1.x = t2.x )
  3  when matched then update set t1.y = t2.y
  4  when not matched then insert (x,y) values ( t2.x, t2.y);
1 row merged.

Ok, here it is clear what will happen right -- we'll do an update of Y from 1
to 2.  BUT

ops$tkyte@ORA920LAP> insert into t2 values (1, 3 );
1 row created.

Now tell me -- what SHOULD happen now?  should y be 2 or 3 at the end?

ops$tkyte@ORA920LAP> merge into t1 using t2
  2  on ( t1.x = t2.x )
  3  when matched then update set t1.y = t2.y
  4  when not matched then insert (x,y) values ( t2.x, t2.y);
merge into t1 using t2
           *
ERROR at line 1:
ORA-30926: unable to get a stable set of rows in the source tables

that is what this means.  It means that your merge key of

(e1.customer_index_num=e2.customer_index_num and
e1.requisition_num=e2.requisition_num and
e1.system_origin_nam=e2.system_origin_nam and e1.line_num=e2.line_num and
e1.sequence_num=e2.sequence_num)

has DUPLICATES in tb_today3 e2 and we don't know WHICH of the N rows in e2
should be used to update the row in e1.

Your "on" clause should be "on to a unique key"

      Rating:
      3
  June 19, 2003
Reviewer:
      A reader
tom,
please ignore this line in the above message..
18:28:55 ge$matrix@links.us.oracle.com>alter table tb_today3  add flag
varchar2(2);

both the tables are of same strucutre.

thanks again

      Rating: 5
Excellent - yet
      an another thankyou!  June 19, 2003
Reviewer:
      A reader

      Rating: 5
Merge :
      output  July 03, 2003
Reviewer:  arjun
       from Red wood shores - CA
hi:   tom,

excellent  as always:

I tried examples using merge : I get

"Operation 189 succeeded." instead of "n rows merged"

following is my version:

BANNER
------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
PL/SQL Release 9.2.0.3.0 - Production
CORE    9.2.0.3.0       Production
TNS for Solaris: Version 9.2.0.3.0 - Production
NLSRTL Version 9.2.0.3.0 - Production

please let me know.

regards

arjun

          Followup:

but what is your sqlplus version -- oh, that is 8i or before....

ok, sqlplus at that release level has no clue what "merge" is, hence is just
says "whatever you typed was amazing and it worked"

      Rating: 4
excellent
      explanation  October 01, 2003
Reviewer:
      A reader
hi tom,

  hats off to your amazing explanations and solution. The above error as u
explained is caused mainly due to having multiple rows in the source table and
if we use MERGE statement.

 We are facing a strange situation. We have an oracle stored PROCEDURE that has
simple update
 statements AS follows......

   UPDATE BAK_LYENTITYPROPERTY
   SET    keyname     = 'CLASSNAME'
   WHERE  UPPER(name) IN ( 'GENERALCLASSKEY','LIMITEDCLASSKEY','LLCCLASSKEY',

'DEFAULTNONRECOURSEPARTNERCLASSKEY','DEFAULTOTHERPARTNERCLASSKEY',

'DEFAULTQUALIFIEDPARTNERCLASSKEY','DEFAULTSCHKPARTNERCLASSKEY');
   -- END 003

   --update LYENTITY related keyvalues
   UPDATE BAK_LYENTITYPROPERTY T
   SET    keyvalue = ( SELECT F.entitykey
                       FROM   LYENTITY  F
                       WHERE  NVL(RTRIM(F.dcorp), ' ') = NVL(RTRIM(T.value), '
') --002
                       AND    F.enterprisekey          = v_enterprisekey
                       AND    T.keyname                = 'DCORP' ) --002
   WHERE EXISTS      ( SELECT 1
                       FROM   LYENTITY  F
                       WHERE  NVL(RTRIM(F.dcorp), ' ') = NVL(RTRIM(T.value), '
') --002
                       AND    F.enterprisekey          = v_enterprisekey
                       AND    T.keyname                = 'DCORP' ); --002

  ..............

  some how, we get the following error message.

....  ORA-30926: unable to get a stable set of rows in the source tables

reading ur explanations i could conclude that
  the above error msg IS meant FOR merge statements WHERE the join condition
  returns multiple ROWS FROM the source table. But our PROCEDURE uses no MERGE
statment
  AND we are working ON oracle version 8.1.7 IN which merge STATEMENT IS NOT
available AT all.

Can u let me know the cause of this error in a situation other than the MERGE
statement. Or can we replicate the behaviour of a MERGE statment , using simple
updates/inserts/selects ???

thanx ,nikhil

          Followup:

be there a bitmap index involved?

      Rating: 5
Merge strange
      behaviour  December 11, 2003
Reviewer:
      karma  from MA USA
here i have simple merger but its not doing as expected:
    MERGE INTO a_acct xx
    USING
    (
        (SELECT a.firm_c,a.brch_c,a.base_c,sysdate,a.citz_c
         FROM a_acct_mstr a
         WHERE (a.firm_c,a.brch_c,a.base_c) NOT IN (SELECT
firm_c,brch_c,base_c
FROM i_acct_mstr)
        )
        UNION
        (SELECT a.firm_c,a.brch_c,a.base_c,sysdate cycle_d,a.citz_c
         FROM i_acct_mstr i, a_acct_mstr a
         WHERE a.firm_c = i.firm_c
         AND   a.brch_c = i.brch_c
         AND   a.base_c = i.base_c
         AND   (a.citz_c <> i.citz_c or i.citz_c IS NULL)
         AND   a.citz_c IN ('3', '4','B','C','D')

        )
    ) yy
    ON (xx.firm_c = yy.firm_c and  xx.brch_c = yy.brch_c and xx.base_c =
yy.base_c)
    WHEN matched THEN
         UPDATE SET xx.eff_date = sysdate+10
    WHEN not matched THEN
       INSERT
(firm_c,brch_c,base_c,eff_date,doc_category,doc_code,doc_status,doc_short_name,
               doc_code_desc,source_sys,prtn_id,insert_tmst,update_tmst)
       VALUES (yy.firm_c,yy.brch_c,yy.base_c,sysdate,'XXX','-',yy.citz_c,'-',
               '-','BVVV','RRRR',sysdate,sysdate) ;

Here is the scoop:
select before union return 1 row I.e i got new row
select after union returns no rows i.e. nothing changed
A_acct is EMPTY.
First time i ran merge it says succesfully completed not rows affected. Now
since i got 1 NEW row and there is nothing in a_acct table should'nt it insert ?
It does not.

So, I changed data so that 2nd part of SQL ( after Union) gets me 1 row.
Now i ran merge stmt. and it says 2 rows affected. It inserted 2 rows and this
is correct.

Now i changed back data so that 2nd part of union returns no data and first part
of union returns 1 row and ran merge again and it should update 1 row since this
row do exist in the a_acct table now because of 2nd run but merge says no rows
affected.

I am going crazy and wondering if i am doing something wrong?

          Followup:

hows about a 100% complete, yet as concise as possible example.  including
little create tables, the inserts needed, etc -- you know, just like I do in my
examples!

remember -- CONCISE is as important as COMPLETE.  get rid of everything that is
not relevant to the example, use as few columns as possible.  I find many (most)
times that in the course of making a tiny example -- I find the errors of my
ways (I find the mistake i was making whilst in the course of trying to
reproduce it with as few moving pieces as possible)

December 13, 2003 Sudhanshu -- Thanks for the question regarding
      "Explicit & Implicit Cursor", version 8.1.7

      You Asked
Hi Tom,

I have visited the Explicit & Implicit cursor archive question in this forum.

Is it true that Oracle 7.3 onwards implicit cursors are optimized and they don't
do double fetch Or Does they do an internal array fetch.

The following implicit cursor runs faster than the below explicit if I table
blah have an index on x column otherwise explicit runs faster.

Could you please tell me the reason for this awkard behavior ?

        select x
        into   y
        from   blah
        where  x = j;
--------------------------------
    cursor c(p number) is
    select x
    from blah
    where x = p;

   for j in 1 .. 999 loop
       open c(j);
       fetch c into y;
       close c;
    end loop;

Cheers
Sudhanshu

      and we said...
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1544606261686
shows going way back to 7.1 (and 7.0 but I don't have that installed) this has
been so.

tkyte@ORA716.WORLD> variable x varchar2(25)
tkyte@ORA716.WORLD>
tkyte@ORA716.WORLD> alter session set sql_trace=true;

Session altered.

tkyte@ORA716.WORLD>
tkyte@ORA716.WORLD> exec select * into :x from dual HELLO_THERE;

PL/SQL procedure successfully completed.

tkyte@ORA716.WORLD> print x

X
--------------------------------
X

tkyte@ORA716.WORLD>

and tkprof clearly shows:

SELECT *
FROM
 DUAL HELLO_THERE
...
Fetch        1      0.00       0.00          0          1          3           1
....

one fetch call -- not two.

The reason for your behavior is because you are comparing an apple with a
toaster oven.  Compare:

   for j in 1 .. 999 loop
       open c(j);
       fetch c into y;
       if ( c%notfound ) then raise NO_DATA_FOUND; end if;
       fetch c into y;
       if ( c%found ) then raise TOO_MANY_ROWS; end if;
       close c;
    end loop;

The select into looks for the first two matching rows -- it looks to the end of
the table to ensure that you are getting at least one and at most one row (that
is what select into it for).

Your test -- it would find the first row and STOP.  If the data was physically
organized so that your data was "near the top" of the table -- it would run much
faster.  If it was such that your data was "near the bottom" of the table -- it
would run the same speed.

But -- test apples vs apples and you'll see that the implicit cursor is faster
(when you compare it to the same logic procedurally)

If you care just about getting the first row, use:

select x in to y from blah where x= j AND ROWNUM=1;

it'll be faster then your loop
Review &
      Followup

      Rating: 5
Always useful
      of course!  July 18, 2002
Reviewer:  Chuck
      Jolley  from OKC, OK USA
Is there a general rule about how much faster the implicit cursors are?
I have a few proc that are >1000 lines long that I use explicit cursors in for
clarity.
It puts the SQL at the top and the logic in the body.
(wheels within wheels)
They are only run by DBAs, not users, as they take between 30 min and one hour.
I am perfecly willing to rewrite them, but will it pay?
thanks,
chuck

          Followup:

Well, I use implicit cursors myself -- for clarity.

begin
   open c1;
   fetch c1 into x;
   close c1;
end;

begin
   select count(*) into x
     from t
    where .....;
end;

to me -- the second is more "clear".  I don't have to constantly page up to see
"what the heck do that cursor do again"...

the only thing you can do -- benchmark.

My 2cents -- you have the code, the code works, you seem "happy" or at least OK
with the performance -- it ain't broke -- this won'[t make it run 10X faster, i
would leave it be.

      Rating: 4
cursor for
      loop: explicit or implicit?  July 18, 2002

Reviewer:  Freek D'Hooge  from Belgium
Tom,

the last couple of days there is an ongoing discussion on technet
http://www.oracle.com/forums/message.jsp?id=1126873&gid=478021
about
implicit cursor vs explicit cursors. (the question about the indexes was
original also posted there).
For me it is clear that implicit cursors are faster then explicit ones, but is
the following a implicit cursor or an explicit one:

DECLARE
cursor c_cur is
select * from t;
BEGIN
for r_cur in c_cur loop
do something;
end loop;
END;

I know you look at this as an implicit cursor, but according to oracle
documentation it is an explicit one?

          Followup:

it is a bit of both.  I tend to call it implicit because there is no OPEN/FETCH
or CLOSE.  But it falls into the definition of both.

The docs state:

....
Explicit Cursors

The set of rows returned by a query can consist of zero, one, or multiple rows,
depending on how many rows meet your search criteria. When a query returns
multiple rows, you can explicitly declare a cursor to process the rows.
Moreover,
you can declare a cursor in the declarative part of any PL/SQL block,
subprogram,
or package.

You use three commands to control a cursor: OPEN, FETCH, and CLOSE. First, you
initialize the cursor with the OPEN statement, which identifies the result set.
Then, you can execute FETCH repeatedly until all rows have been retrieved, or
you can use the BULK COLLECT clause to fetch all rows at once. When the last row
has been processed, you release the cursor with the CLOSE statement. You can
process
several queries in parallel by declaring and opening multiple cursors.

....

      Rating: 3
Explicit
      Implicit Cursors in Forms  July 18, 2002
Reviewer:
      Gary Myers  from Australia
Note 1003695.6 in Metalink and a recent Forum posting state that in forms "A
separate cursor is allocated for each implicit cursor, and implicit cursors are
kept open until the form is exited. "

In Forms 6i, the cursors seem to be closed at the completion of the trigger.
{ I tried the following in a New Form trigger :
declare
    v_temp varchar2(2000);
    cursor c_1 is select 3 from dual;
    cursor c_2 is select 4 from dual;
begin
    select 1 into v_temp from dual;
    select 2 into v_temp from dual;
    open c_1;
    fetch c_1 into v_temp;
    close c_1;
    for c_rec in c_2 loop
         null;
    end loop;
    message(v_temp);
    pause;
end;

The cursors were in visible v$open_cursor at the PAUSE, but vanished after that,
while the form was still open.).

I recall when using Forms 5.0, needing a large MAX_OPEN_CURSORS database
parameter because of forms cursors.

Did this behaviour change in 6i or earlier ?
Are there any problems in keeping a large MAX_OPEN_CURSORS ?

          Followup:

they are cached, they are replaceable.  You want large max  open cursors for
performance more then anything else.

It consumes more memory to have more open cursors -- that is the "downside"

      Rating:
      5
ROWNUMskull  July 19, 2002
Reviewer:
      Mike  from St. Louis, MO
Tom: in this thread you state:

> If you care just about getting the first row, use:
>
> select x in to y from blah where x= j AND ROWNUM=1;

I may be taking this out of context but whenevery I see ROWNUM I pause (just as
you might when seeing "WHEN OTHERS THEN").  I thought ROWNUM could lead to
unpredictable results as the row selected may have nothing to do with the ORDER
BY criteria.

I'm not trying to nit pick you, rather I've been reading the explicit vs.
implicit cursor discussion and learned a ton.  However, I'm still undecided on
the safest technique for selecting only the first row of a multi-row result set.
 Both implicit or explict cursors work but I'm left wondering if explicit is not
safer (e.g. not subjet to handling behavior changes in new releases).  Your
ROWNUM idea is another approach but not if results are unpredictable.
Pondering...

Thanks

          Followup:

rownum.

It is as predicable as

open
fetch
close

and in fact will tend to return the same exact thing.

Rownum does work predicably with order by.  If you code:

select * from t where rownum <= 5 order by x;

that says "take 5 rows, sort them by x"

if you code:

select * from ( select * from t order by x ) where rownum <= 5;

that says "order t by X and then take the first 5 -- AFTER the sort".

It is 100% predicable and reliable.

      Rating: 5
ROWNUM num
      num  July 19, 2002
Reviewer:  Mike
       from St. Louis, MO
Ahhh, the lights come on. Thank you sir for the ROWNUM goodies!

I'm still curious how you, in general, code a cursor that returns 0, 1 or more
ordered rows but you only care about the first row returned.  I see three
aproaches:

open
fetch
close
-- Intention is clear.

--------------------------------------------

select * from ( select * from t order by x )
where rownum = 1 into l_rec;
-- Is this a potential maintence programmer
-- chuck hole?

--------------------------------------------

<<myloop>>
for rec in (select * from t order by x)
loop
   l_first_row_stuff_to_keep := rec
   exit myloop;  -- premature termination
                    but has "goto" feel and
                    seems inappropriate.
end loop;

Curious about your thoughts on these options (or others I've overlooked).  Thank
you.

          Followup:

select * into l_rec
  from ( select * from t order by x )
 where rownum = 1;

is

a) infinitely faster then open/fetch/close in most every case (at least as fast,
generally much faster)

b) should be obvious "rownum=1" seems *obvious* to me.  If not code it as:

  -- Hey, look at that rownum = 1, the INTENT here is
  -- to get the first row after ordering.  Since we want
  -- this to be FAST, we don't use the procedural approach of
  -- open/fetch/close
  select * into l_rec
    from ( select * from t order by x )
   where rownum = 1;

Now, maintenance programmer should be AOK.

      Rating: 5
Thanks
      Tom  July 20, 2002
Reviewer:  Mike
       from St. Louis, MO
Just wanted to say thanks for your experienced perspective on these details.
Also, a HUGE tip-o-the-hat on your "Expert One on One" book -- it's truly a
superb text.

I have a ton of Oracle/Unix books in my cubicle but YOURS is always the one on
my desktop cracked open and in use.  Of course, I have to keep it cracked open
so I don't see your mug on the cover all day :-))))))  Just kidding and thanks
again!

      Rating:
      5
  February 13, 2003
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating: 4
Might want to
      let Oracle know about this :-)  March 31, 2003

Reviewer:  Rick  from Davenport, IA USA
Oracle Applications Developers Guide Release 11i, SQL Coding Guidelines:
"All SELECT statements should use an explicit cursor. Implicit
SELECT statements actually cause 2 fetches to execute: one to get
the data, and one to check for the TOO_MANY_ROWS
exception. You can avoid this by FETCHing just a single record
from an explicit cursor."

Thanks for clearing up myths and providing informed, practical information.

      Rating: 4
Implicit cursor
      in Pro*C  July 18, 2003
Reviewer:  Tony
       from India
Is it possible to use implicit cursor in Pro*C? If yes, can you give an example?

          Followup:

exec sql select count(*) into :n from dual;

      Rating: 4
is this right
      way ?  September 11, 2003
Reviewer:  Deepak
      Gupta  from Delhi, India
Hi Mr. Tom,

I am doing  payroll processing in following way :-

I find there are too much phy i/o

The logic like this
( main loop select valid employee and for each employee further information is
fetch through "select ... into" clause from different tables )

What do u suggest me ?

Is this right way ?

--------

declare
cusor is SELECT EMPLOYEE_NUMBER
    FROM EMPLOYEE_MASTER
    WHERE  STATUS = 'A';

XEMP    VARCHAR2(5);

begin

OPEN EMP
LOOP
     FETCH EMP INTO XEMP;
     EXIT WHEN EMP%NOTFOUND;

    select BASIC,HRA....... INTO ....
        FROM SALARY_MASTER
        WHERE EMPLOYEE_NUMBER=XEMP;

    select PAID_DAYS  INTO ....
        FROM MONTH_PAID_DAYS
        WHERE EMPLOYEE_NUMBER=XEMP;

    select LOAN_AMT  INTO ....
        FROM LOAN_MASTER
        WHERE EMPLOYEE_NUMBER=XEMP;

    select INCOME_tAX  INTO ....
        FROM INOCMETAX_MASTER
        WHERE EMPLOYEE_NUMBER=XEMP;

---
  XMONTHLY_SALARY_PAID = XBASIC+XHRA ......
XMONTHLY_SALARY_DED = xINCOMETAX+xLOAN_dED....

......

.....

END ;

Thanks + Regards

          Followup:

one word for you

join.

      Rating: 4
Best approach
      when NOT sorting?  December 11, 2003
Reviewer:
      Kurt  from Springfield, VA USA
Tom,
This question is similar to the original question by Sudhanshu - I'm trying to
find the "best" way to get the first row from a query. The difference with my
query is that the data does not need to be sorted.

I need to find any record that satisfies a query, and return an ID from that
record. The query seeks to detect "bad" records, and if I didn't need to
actually return the ID, I'd try using "where exists" rather than the 3
approaches below. I don't need to sort the records in the query because I don't
care which record is returned. The query can return 0, 1, or more records. I
ignore the possibility of NO_DATA_FOUND in this comparison test, but I'm
handling it in the real code.

I've tried to execute a simple test below using 3 different approaches -
explicit cursor, implicit cursor, select into where rownum=1 (which I believe is
a specialized case of implicit cursor).

In the real application, the query would join 2 tables and would include other
predicates as well. I'm hypothesizing that the actual query is irrelevant for
the purpose of this comparison test, as long as the query is identical (or as
close as possible) in all three approaches. So the query I use below is simple.

Here are the statements used to create the objects and trace the statements, and
below that are the results of TKPROF.

I'm using Oracle EE 9.2.0.1.0 on Windows2000 Pro - my workstation at home.

Please take a look and consider the questions at the end. Thanks.

--***********************************************

create table t as
    select * from all_objects;

alter table t
    add constraint t_pk primary key (object_id);

exec dbms_stats.gather_schema_stats(user);

-- Use an explicit cursor
create or replace function f1
    return t.object_id%type
    is

    l_object_id t.object_id%type;

    cursor c is
        select object_id explicit from t;
begin
    open c;
    fetch c into l_object_id;
    close c;

    return l_object_id;
end;
/

-- Use an implicit cursor
create or replace function f2
    return t.object_id%type
    is

    l_object_id t.object_id%type;

begin
    for x in (
        select object_id implicit from t
    )
    loop
        l_object_id := x.implicit;
        exit;
    end loop;

    return l_object_id;
end;
/

-- Use a "select into" construct
create or replace function f3
    return t.object_id%type
    is

    l_object_id t.object_id%type;

begin
    select object_id sel_into into l_object_id from t
    where rownum = 1;

    return l_object_id;
end;
/

-- Pre-load the library cache
select f1 from dual;
select f2 from dual;
select f3 from dual;

alter session set sql_trace = true;

declare
    l_object_id t.object_id%type;

begin

    for i in 1..100000 loop
        l_object_id := f1;
    end loop;

    for i in 1..100000 loop
        l_object_id := f2;
    end loop;

    for i in 1..100000 loop
        l_object_id := f3;
    end loop;

end;
/

alter session set sql_trace = false;

--***********************************************
-- Results of TKPROF
--***********************************************

declare
 l_object_id t.object_id%type;
begin
 for i in 1..100000 loop
  l_object_id := f1;
 end loop;
 for i in 1..100000 loop
  l_object_id := f2;
 end loop;
 for i in 1..100000 loop
  l_object_id := f3;
 end loop;
end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.01          0          0          0           0
Execute      1    155.23     164.08          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2    155.25     164.10          0          0          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 67
********************************************************************************

SELECT object_id explicit
from
 t

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute 100000      8.16       7.34          0          0          0           0
Fetch   100000     13.75      12.47          0     600000          0      100000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   200000     21.92      19.82          0     600000          0      100000

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 67     (recursive depth: 1)
********************************************************************************

select object_id implicit from t

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute 100000      8.22       7.47          0          0          0           0
Fetch   100000     13.82      13.11          0     600000          0      100000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   200000     22.05      20.59          0     600000          0      100000

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 67     (recursive depth: 1)
********************************************************************************

SELECT object_id sel_into from t
 where rownum = 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute 100000      7.92       7.00          0          0          0           0
Fetch   100000     13.80      13.20          0     600000          0      100000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   200000     21.73      20.21          0     600000          0      100000

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 67     (recursive depth: 1)

--***********************************************

After calling each function 100,000 times, it seems like the performance
difference between all 3 approaches in negligible, and therefore I don't get a
clear feeling that any approach is superior to any other. I almost never use
explicit cursors, so I'd shy away from the function 1 approach. Function 3 seems
better than function 2 to me because it's more clear that we're after only 1
record.

A co-worker chose the implicit cursor approach (function 2), and I was about to
suggest that the "select into" approach (function 3) would be much faster - but
after benchmarking it, I'm not so sure.

Did I perform a valid test, or did I fall victim to the apples vs. toasters
trap? Are there other statistics I should be considering? Is there another
approach I should consider? Or is this a situation where any subjective decision
is fine?

Thanks, Kurt

          Followup:

if I had to do this -- i would either:

select max(id) into l_id from T where rownum = 1;

or

begin
   select id into l_id from t where rownum = 1;
exception
   when no_data_found then id := null;
end;

probably the max(id) with a comment as to why i was doing what I did.

your test should have included "what happens when no_data_found" to be unbiased.

      Rating: 2
The max()
      approach appears to be the slowest!  December 11, 2003

Reviewer:  Kurt  from Springfield, VA USA
Tom,
Thanks for the quick response!

I've added the max() approach as function 4, modified function 3 to account for
no data found, and I ran the complete test again.

Unfortunately, function 4 appears to be the slowest approach.

Any thoughts?

Thanks, Kurt

--************************************************

(previous table setup and calls to functions 1-3 as before)

-- Use the max() function
create or replace function f4
    return t.object_id%type
    is

    l_object_id t.object_id%type;

begin
    select max(object_id) max_meth into l_object_id from t;

    return l_object_id;

    exception
    when NO_DATA_FOUND then return null;
end;
/

-- Pre-load the library cache
select f1 from dual;
select f2 from dual;
select f3 from dual;
select f4 from dual;

alter session set sql_trace = true;

declare
    l_object_id t.object_id%type;

begin

    for i in 1..100000 loop
        l_object_id := f1;
    end loop;

    for i in 1..100000 loop
        l_object_id := f2;
    end loop;

    for i in 1..100000 loop
        l_object_id := f3;
    end loop;

    for i in 1..100000 loop
        l_object_id := f4;
    end loop;

end;
/

alter session set sql_trace = false;

--***********************************************
-- Results of TKPROF
--***********************************************

declare
    l_object_id t.object_id%type;
begin
    for i in 1..100000 loop
        l_object_id := f1;
    end loop;
    for i in 1..100000 loop
        l_object_id := f2;
    end loop;
    for i in 1..100000 loop
        l_object_id := f3;
    end loop;
    for i in 1..100000 loop
        l_object_id := f4;
    end loop;
end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.07          0          0          0           0
Execute      1    209.30     218.39          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2    209.32     218.46          0          0          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 67
********************************************************************************

SELECT object_id explicit
from
 t

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute 100000      8.03       7.31          0          0          0           0
Fetch   100000     13.76      12.79          0     600000          0      100000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   200000     21.80      20.11          0     600000          0      100000

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 67     (recursive depth: 1)
********************************************************************************

select object_id implicit from t

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute 100000      8.13       7.39          0          0          0           0
Fetch   100000     14.12      13.33          0     600000          0      100000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   200000     22.25      20.73          0     600000          0      100000

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 67     (recursive depth: 1)
********************************************************************************

SELECT object_id sel_into from t
    where rownum = 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute 100000      7.77       6.93          0          0          0           0
Fetch   100000     14.50      13.62          0     600000          0      100000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   200000     22.27      20.55          0     600000          0      100000

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 67     (recursive depth: 1)
********************************************************************************

SELECT max(object_id) max_meth
from
 t

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute 100000     20.73      20.41          0          0          0           0
Fetch   100000      8.29       7.38          0     200000          0      100000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   200000     29.03      27.80          0     200000          0      100000

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 67     (recursive depth: 1)

          Followup:

ok, in looking deeper at your example -- you would be best suited with where
rownum=1

you have no predicate.  you just want "the first row".  where rownum=1 is the
right approach.

      Rating: 5
Oops - Ignore
      the exception handler in function 4.  December 11, 2003

Reviewer:  Kurt  from Springfield, VA USA
Tom,
The exception handler in function 4 should be removed - it was a copy-and-paste
holdover.

I ran the test again, and here is the revised function and the TKPROF output.

Kurt

-- Use the max() function
create or replace function f4
    return t.object_id%type
    is

    l_object_id t.object_id%type;

begin
    select max(object_id) max_meth into l_object_id from t;

    return l_object_id;
end;
/

SELECT max(object_id) max_meth
from
 t

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute 100000     21.97      21.75          0          0          0           0
Fetch   100000      8.04       7.65          0     200000          0      100000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   200000     30.01      29.41          0     200000          0      100000

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 67     (recursive depth: 1)

December 13, 2003 wallacel -- Thanks for the question regarding "do
      i need to hardcode password inside the program?", version 8.1.6

      You Asked
Tom:

oracle8i concepts page 1-41.
Typically,an application is designed so that when iti starts,it enables the
proper role.
As a result, an application user does not need to know the password for an
application's
role.

Does that mean i need to hard code password inside the application?
if that's case, how can i insure the security.
also, when sometime i need to change the password, does that mean i need to
modify the
application?

      and we said...
You don't have to hard code it into the application, there are many creative
ways you can do it.

I myself prefer to put the application into the database (write all of the logic
in PLSQL) in that way -- I never need to worry about it because my application
is the ONLY way to get access to the data.  I don't care if you call my
application from sqplus or from the pretty GUI i created.  In that way, the
roles are enabled by default.

I find applications that rely on password protected roles to "protect"
themselves to be security holes in disguise.  The concept (the theory) is:

o we'll grant INSERT/UPDATE/DELETE on our tables to a role
o we'll password protect the role and make it non-default
o our application will be the only thing to enable the role
  so it will be the only way the people can insert/update/delete
  out tables.

The hole is point #3 above.  The problem is you have to give your application
SOME way to enable the role.  If your application can enable the role -- so can
I from SQLPLUS.  Lets say you create a procedure to enable the role -- so you
hide the password for it in the database (perhaps as a wrapped PLSQL routine so
no one can read the code).  It would look like this:

ops$tkyte@DEV816> l
  1  create or replace procedure turn_on_role
  2  authid current_user
  3  as
  4  begin
  5     execute immediate
            'set role new_role identified by password';
  6* end;

ops$tkyte@DEV816> create role new_role identified by password;
Role created.

ops$tkyte@DEV816> set role none;
Role set.

ops$tkyte@DEV816> select * from session_roles;
no rows selected

ops$tkyte@DEV816> set role new_role;
set role new_role
*
ERROR at line 1:
ORA-01979: missing or invalid password for role 'NEW_ROLE'

ops$tkyte@DEV816> exec turn_on_role;
PL/SQL procedure successfully completed.

ops$tkyte@DEV816> select * from session_roles;

ROLE
------------------------------
NEW_ROLE

ops$tkyte@DEV816>

So, I can hide the password in the database (doesn't have to be hard coded into
the application).  But the fact remains that i still have to give the end users
the privilege to execute that procedure "turn_on_role".  As shown above, if I
can execute it in the application -- I sure can execute it in sqlplus.

Well, we'll just have our procedure query v$session for the program -- if the
program is not myapp.exe, we'll not turn it on.  Ok, I'll just copy sqlplus.exe
to myapp.exe -- now I'll apear to be your application.

Ok, we'll hard code into the application some magic piece of data that it'll
send into turn_on_role -- Ok, I'll just set trace_level_client = 16 in my
sqlnet.ora and see what it is you are sending.

If you were to use ASO (advanced security) and encrypted the data stream between
client and server and had the application send over some magic bit of data and
could be assured this magic bit of data could never be comprimised -- then you
might have something.

followup to comment

I don't agree that that would be more secure.

At the end of the day -- the application will retreive and decode the password
and then send it back to the database.  We are back to the same case as when you
just hard code it into the application itself.  Also -- you now have key
managment problems -- where do you store the key used to decrypt the data?  the
key is as immportant as the data itself!

Review &
      Followup

      Rating: 4
Very Good way
      of implementing the security at the appicatiion level  June
      13, 2001
Reviewer:  Senthil  from Qatar
I am looking for this solution. If the password of the role
is stored in a table in an encrypted way and retrived ,it will be more secured.

      Rating: 3
Good
      one  June 14, 2001
Reviewer:  Gururaj
       from GA, USA

      Rating:
      3
  June 18, 2002
Reviewer:
      A reader
http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96624/c_wrap.htm#2884
Wrapping a procedure helps to hide the algorithm and prevent
reverse-engineering, but it is not a way to hide passwords or table names that
you want to be secret.

So the user can login and
select text from user_source
where name='   '

and find the password in the wrapped text!

          Followup:

 perhaps I wasn't 100% explicit, but my point was to hide the password in the
database (say in a table).

Also, it is quite trivial to hide the password in wrapped code.  Think of what
you can do with some substrs, translates, replaces, use of multiple variables,
concatenation, etc.

If you just code;

is
 hey_look_at_this_password  varchar2(20) default 'abcdefg';
begin

you are right, if you do it obscured, no.

      Rating: 4
Nice stuff
      there.  June 18, 2002
Reviewer:  Godwin
       from Ghana
Tom u are doing great job there. In fact you are a role model to some of us
which even ginger me to develop such love for Oracle.
But Tom may be i seem to be troublesome by my questions but i think it is
because am naive in the oracle platform.
Please look at this issue for me. After i have developed an application to keep
track on Accommodation for University staff i decided to restrict users to
specific task to be performed.I am therefore deciding to implement these actions
and i found answer to the already answered question as very useful. But besides
that can i make use of the ff to acheive what am trying to arrive at?
eg 1. create synonym  public syn_table1 on table1;
     grant select,update on syn_table1 to user1;

   2. grant insert,update,select on table1 to role1;
      grant role1 to user1;

Now the problem is when i connect to sqlplus as user1 and issue the statement as
"select * from syn_table1" it works alright but if i log on to my application
through Forms 6 to retrieve data from that particular table i don't get any
record. What is the cause of this?
Now with the example 2 can you please throw some light on how it is used and for
what it is used for.

          Followup:

I will hazzard a guess (I've been right on this guess 100% of the time so far,
with almost exactly the same question)

you are using a different userid in forms then in sqlplus.  that different
userid sees a different synonym.

I'm not sure what you mean by your request for #2 there.  You are giving some
privs to a role.  You grant the role to a user.  The user now has those privs.
If you grant/revoke privs to/from that role -- that user (and in fact, all users
that have that role) will have those changes as well.  It is a way to manage
privs for thousands of users and thousands of objects with less typing/overhead.

      Rating: 4
Could you
      please elaborate a bit more?  December 11, 2003

Reviewer:  gs
Tom,

You said
"I myself prefer to put the application into the database (write all of the
logic in PLSQL) in that way -- I never need to worry about it because my
application is the ONLY way to get access to the data.  I don't care if you call
my
application from sqplus or from the pretty GUI i created.  In that way, the
roles are enabled by default."

Now, to invoke that application, you have to connect to the database. To connect
you need password.

Suppose, I have a web application.

How does one achive the same, without hardcoding (it could be some encrypted
value wihc the web application decrypt before connecting) the password?

Could you please give the steps (algorithm) how you will achieve this?

What about if the web application (php lang) has many queries scattered
throughout the application (not calls to  server side procedures)

ex.
 Create user x and ggrant only create session
 Create role y and grant required object priv
 ...

Thanks,

          Followup:

depends on the tool you use.

with mod_plsql - just choose to not store the password with the DAD. then the
browser uses basic authentication to ask you and sends it to the server.

      Rating: 3
Thanks Tom!
        December 11, 2003
Reviewer:  gs

I have Apache/PHP/Oracle. No mod_plsql. The application logs in with a fixed
userid (say appuser) with privileges to do certain operation such as insert,
update, irrespective of who the web user is.

The application reads the encrypted password from a file and decrypts before
connecting to the db.

What is the best wasy to achieve the connection without compromising the
security?

          Followup:

If you have Oracle -- you got mod_plsql.  But, anyway.

You've already pretty much compromised security by using a common userid ;)  The
database isn't part of your security infrastructure anymore, you've written us
out of the equation.

Don't know exactly what you are looking for from me?  What are you trying to
secure against?

      Rating: 5
authenticating
      client  December 11, 2003
Reviewer:  Sudhir

Hi Tom,

one suggestion on that subject,

lets say myapp.exe has a routine which calls
-os function call to get binary size(s) of the client(s)
-calls database to get connection time
-jumbles the size with time in some manner
-call a procedure on the database with jumbled number as
 parameter

on the database side for every release we have provided
to customers, we keep track of various byte sizes. we
already know the algorithm used for creating jumble, thus
we unjumble it, check if the login time matches, check the
byte size matches to our repository. then we enable the role.

So even if you run level 16 trace on client, you can't do much because the
jumbled number will be different each time you login and its not going to work
from sqlplus or other clients as there is slim chance the sizes will match (you
can add size of couple of delivered products to make this even more complicated
and secure) and parameters will be messed up.

what do you think?

Thanks as usual

          Followup:

seems plausible if you use secure application roles in 9i (roles that can only
be enabled by running a stored procedure to enable them)

      Rating: 5
wrapped
      procedure  December 11, 2003
Reviewer:
      Sudhir
Tom,

" perhaps I wasn't 100% explicit, but my point was to hide the password in the
database (say in a table).

Also, it is quite trivial to hide the password in wrapped code.  Think of what
you can do with some substrs, translates, replaces, use of multiple variables,
concatenation, etc.

If you just code;

is
 hey_look_at_this_password  varchar2(20) default 'abcdefg';
begin

you are right, if you do it obscured, no.
"

I thought if you develop a pl/sql package. then wrap it. then drop original
package and load wrapped package. Then it should not be readable to even dba,
correct? you pretty much need the code or sccs to get access to password,
correct? what am i missing here? I thought this make wrap pretty good solution
..

Thanks again,

          Followup:

see the comment below....

in 10g, it does work differently:

[tkyte@megan tkyte]$ cat > test.sql
create or replace procedure test_warp is
      key_str1   varchar2(30) default 'Visibleeeiii' ;
      key_str2   varchar2(30) default
chr(86)||chr(105)||chr(115)||chr(105)||chr(98)||chr(108)||chr(101)||chr(101)||chr

(101)||chr(105)||chr(105)||chr(105);
begin
        null;
end test_warp ;
/
[tkyte@megan tkyte]$ wrap iname=test.sql

PL/SQL Wrapper: Release 10.1.0.1.0- Beta on Sat Dec 13 10:43:56 2003

Copyright (c) 1993, 2003, Oracle.  All rights reserved.

Processing test.sql to test.plb
[tkyte@megan tkyte]$ cat test.plb
create or replace procedure test_warp wrapped
a000000
1
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
7
110 df
EUPWGsLmPwrYk9/hF/AOeo0q9BMwg5nnm7+fMr2ywFznUrLL9Ci/dJ5ppXSLCaYdILGFzo/I
yoTKgkd/hTFqf4VaarRnXq+VPdd/d0miNQBWA1eCPkkqKiqCgl5Mr6tcE/PReXH/HDWrb8ns
KirKQGs3Pl8BERioyd77H+ZLdLOTcRas6FSTPHHc4rlBP2l4xRbOApLOVMe+kr7KOmjs+6Yw
5elI

/

      Rating: 3
Followup to
      Sudhir's comments  December 12, 2003
Reviewer:
      gs
When you wrap the code the variables and the values still are visible unless you
do something similar to tom has suggested. It is better, the key string to have
repetitive charactes.

It looks like warp is kind of compress + some kind of crypting. Tom, can you
please verify this?

When the key has repettitive characters they are displayed once.

ex.

create or replace procedure test_warp is
      key_str1   varchar2(30) default 'Visibleeeiii' ;
      key_str2   varchar2(30) default
chr(86)||chr(105)||chr(115)||chr(105)||chr(98)||chr(108)||chr(101)||chr(101)||chr
(101)||chr(105)||chr(105)||chr(105);
begin
        null;
end test_warp ;
/

part of wraped code:
====================

1TEST_WARP:
1KEY_STR1:
1VARCHAR2:
130:
1Visibleeeiii:
1KEY_STR2:
1CHR:
186:
1||:
1105:
1115:
198:
1108:
1101:
0

0
0
70

      Rating: 5
Thnaks to you
      and a reader for the clarification. EOM  December 13, 2003

Reviewer:  Sudhir

December 13, 2003 Sanjoy -- Thanks for the question regarding
      "Creating a table using Dynamic SQL in stored procedure get error
      ORA-00942", version 9.0.2

      You Asked
Tom,

I am using dynamic sql in my stored procedure to create a table 'A', the table
table 'A' gets re-created every night in a batch job and may have additional
columns depending on product attributes, therefore I have to drop the table and
recreate it.
When I am running the script for the fist time the drop table statement gives me
an error ORA-00942 table or view does not exist which is very obvious as the
table is getting created for the first time and it does not exists.
I do not have control as the table get created based on product category, so
every time a new product category gets created a new table has to be created.
How do I manage this error is there a way I can check if the table does not
exists I create the table first and do not try to drop the table. I do have DBA
rights to the database.

my procedure looks like:
PROCEDURE SCP_test (STAGE_TABLE_DEFN IN VARCHAR2 )IS
   v_sql LONG;
BEGIN
   v_sql := 'DROP TABLE BK_STG_' || STAGE_TABLE_DEFN ||'';
   DBMS_OUTPUT.PUT_LINE('v_sql: ' || v_sql);
   EXECUTE IMMEDIATE v_sql;
   v_sql := 'CREATE TABLE BK_STG_' || STAGE_TABLE_DEFN ||' AS SELECT * FROM
STG_' || STAGE_TABLE_DEFN ||'';
   DBMS_OUTPUT.PUT_LINE('v_sql: ' || v_sql);
   EXECUTE IMMEDIATE v_sql;
EXCEPTION
    WHEN others THEN
      DBMS_OUTPUT.PUT_LINE('Validation Script SCP_STAGE_PRODUCT_CONFIGURATOR:
failed at ERROR: ' || SQLCODE || SQLERRM);
END;

      and we said...
ops$tkyte@ORA920> desc t
ERROR:
ORA-04043: object t does not exist

ops$tkyte@ORA920> declare
  2          table_does_not_exist exception;
  3          pragma exception_init( table_does_not_exist, -942 );
  4  begin
  5          begin
  6                  execute immediate 'drop table t';
  7          exception
  8                  when table_does_not_exist
  9                  then NULL;
 10          end;
 11
 12          execute immediate 'create table t ( x int )';
 13  end;
 14  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> desc t
 Name                    Null?    Type
 ----------------------- -------- ----------------
 X                                NUMBER(38)

a programming hint to take to heart.

It is true that 9,999 times out of 10,000 that a "WHEN OTHERS" exception handler
that does not include "RAISE" in it -- is a bug.

All you have done is SILENTLY IGNORED an error.  sure, you print out a pretty
message -- but so what.  the invoker gets "return code 0, all is well in the
world".

I wish when others was "not supported".  how many bugs I've seen cause people
did not realize a low level routine actually failed -- the error was totally
hidden.

don't do it!

Review &
      Followup

      Rating:
      4
  August 11, 2003
Reviewer:  Sanjoy

Thanks Tom for your advice and a prompt response

      Rating: 4
Why 942 and not
      4043 in exception_init????  August 12, 2003

Reviewer:  DD  from MN, USA
ops$tkyte@ORA920> desc t
ERROR:
ORA-04043: object t does not exist

But then why did you use

pragma exception_init( table_does_not_exist, -942 );

Why did you use 942 and not 4043 for the exception init??
does a drop table which does not exist give 942??
I assume so but the first line was confusing.

Thanks
DD

          Followup:

because a drop gives a 942.

the first line was there just to show you that T in fact did not exist so the
exception handler MUST have been invoked, caught the exception and ignored it.

      Rating:
      4
  August 12, 2003
Reviewer:  Sanjoy

The above code works fine but when I use it in a loop I get error, here is how
my proc looks like
CREATE OR REPLACE PROCEDURE SCP_TESTA IS
   tmpVar NUMBER;
   CURSOR stage_cursor is
      SELECT stage_table, product_category, product_attribute
      FROM   pcm;
   rec_stage_cursor stage_cursor%rowtype;
   v_sql LONG;
   table_does_not_exist exception;
   pragma exception_init( table_does_not_exist, -942 );
BEGIN
   tmpVar := 0;
   OPEN stage_cursor;
   LOOP
      FETCH stage_cursor into rec_stage_cursor;
      EXIT WHEN stage_cursor%NOTFOUND;
      BEGIN
        v_sql := 'SELECT count(*) into tmpVar FROM STG_' ||
rec_stage_cursor.stage_table ||'';
        EXECUTE IMMEDIATE v_sql;
      EXCEPTION
         WHEN table_does_not_exist THEN
         v_sql := 'CREATE TABLE STG_' || rec_stage_cursor.stage_table ||' AS
SELECT sysdate dummy_date FROM dual';
         EXECUTE IMMEDIATE v_sql;
      END;
      SCP_SPC ( rec_stage_cursor.stage_table, rec_stage_cursor.product_category,
rec_stage_cursor.product_attribute );
         DBMS_OUTPUT.PUT_LINE('EXECUTION OF PROC COMPLETED');
   END LOOP;
END SCP_TESTA;
The compilation of the above works fine but at execution time I get the error
ERROR at line 1:
ORA-00905: missing keyword
ORA-06512: at "SYSADM.SCP_TESTA", line 18
ORA-06512: at line 2

          Followup:

that is not how to do a select into using dynamic sql -- that is why.

Also, that is not the question you asked.  That is a horse of a totally
different color.

This is how to do the above:

CREATE OR REPLACE PROCEDURE SCP_TESTA
as
    table_already_exists exception;
    pragma exception_init( table_already_exists, -955 );
BEGIN
    for x in (select stage_table, product_category, product_attribute from pcm)
    loop
        begin
            execute immediate 'create table stg_'||x.stage_table ||
                              ' as select sysdate dummy_date from dual';
        exception
            when table_already_exists then NULL;
        end;
        scp_spc( x.stage_table, x.product_category, x.product_attribute );
    end loop;
END SCP_TESTA;
/

and just fyi -- a select into using dynamic sql is:

ops$tkyte@ORA920> declare
  2          l_cnt number;
  3  begin
  4          execute immediate 'select count(*) from dual' INTO l_cnt;
  5  end;
  6  /

PL/SQL procedure successfully completed.

but here, doing a count(*) is pointless.

      Rating: 5
execute
      immediate and sql  December 11, 2003
Reviewer:
      Jerry  from US
Can I run ddl using execute immediate together with other sq code?  I got errors
when running:

SQL> begin
  2  execute immediate 'create table t (t1 integer)';
  3  delete from t;
  4  commit;
  5  end;
  6  /
delete from t;
            *
ERROR at line 3:
ORA-06550: line 3, column 13:
PL/SQL: ORA-00942: table or view does not exist
ORA-06550: line 3, column 1:
PL/SQL: SQL Statement ignored

However, if I remove the delete, it works.
SQL> begin
  2  execute immediate 'create table t (t1 integer)';
  3  end;
  4  /

PL/SQL procedure successfully completed.

Could you please help me understand this?  Thanks!

          Followup:

think about it -- when that block of code is compiled -- does T exist?

Nope.

Therefore, the block of code cannot possibly compile.

If you dynamically CREATE the table, you are forced to dynamically ACCESS the
table.

Chicken and Egg problem here, you cannot compile the code because T does not
exist so the static sql cannot be processed.

      Rating: 5
Great
      help  December 13, 2003
Reviewer:  Jerry

Thanks so much for the explaination!  It's great help!

December 13, 2003 Orlando -- Thanks for the question regarding
      "Database Links", version 8i 1.5

      You Asked
Hi Tom,

I have a couple of questions regarding Database Links.

1) I have a link going from Database GDSPROD on an NT server
GDS5 to a database GDSDEV on an NT server GDS5, when I select
something from GDSDEV to insert on GDSPROD everything is okay
but if I try to describe a table from GDSDEV using the link I
get the error: ORA-02019: connection description for remote
database not found. Do you know why if the link work for DML
operations it does not work for the DESC command?

2) I need to create a link from GDSDEV on GDS4 to GDSPROD on
GDS5, when I do it the link takes the name GDSPROD.WORLD and
when I try to use it I get the error: ORA-02085: database link
GDSPROD.WORLD connects to GDSPROD.

Now When I look at the global name of my GDSPROD database I see
it is just GDSPROD and not GDSPROD.WORLD as the name of the link
on GDSDEV, I tried to change the name on the listener using net8
assistent, now even though I see the global name of the database
being DGSPROD.WORLD in the listener, when I query the
GLOBAL_NAME view the name is just GDSPROD, I assume that is why
the link does not work because it has a differnet name to the
DB.

So my questions would be how do I create the link with just the
GDSPROD name without having oracle adding the suffix .WORLD or
how do I change the global name of the GDSPROD DB to
GDSPROD.WORLD and equalize the names? Or do I have to do
something different all together?.

I would really appreciate you comments and ideas abour this
matter.

Thank you for your help and I hope to hear from you soon.

Have a nice holiday weekend.

Orlando Reyes

      and we said...
1)
Please verify the following:

1. Check the value of parameter db_domain, ( ex,  us.oracle.com
)

2. Recreate db link :

create public database link v815.us.oracle.com
                              /* Append db_domain */
connect to system identified by manager
using 'V815';

2) You have some default domains setup in your NET8.  See

http://asktom.oracle.com/pls/ask/f?p=4950:7:::::F4950_P7_SEARCH_CRITERIA:global_names
for more info on this.  You can do:

o alter session set global_names = false;
o alter database rename global_name = gdsprod.world;
o change your default domain.

Review &
      Followup

      Rating: 4
db_link without
      global_names attachment  March 18, 2002
Reviewer:
      mohammad taha  from Dubai,UAE
I have three Servers named DISPSVR2,LOCAL PC and DISPSVR1. I connect to DISPSVR1
from both DISPSVR2 and LOCAL PC. Below I have given details of all the three
databases togetherwith mu doubts for clarity.Oracle version in DISPSVR1 and
DISPSVR2   is 7.3.4.0 and 8.1.6.0 in Local PC.
---------------------------------------------------------------------------------
-----------------------------------------------------------------
SQL>  CONNECT  DEWADBA/DEWADBA@DISPSVR2    -- tnsnames is DISPSVR2 for Server
DISPSVR2
Connected.

SQL>  SHOW PARAMETER GLOBAL_NAME

NAME                   TYPE          VALUE
-------------------------- -------------    ---------------
global_names         boolean     FALSE

SQL> SELECT * FROM GLOBAL_NAME;

GLOBAL_NAME
------------------------------------------------------
ORACLE.WORLD

In DISPSVR2 database the Entries are as follow:

A. Paramaeter File

        db_name = oracle

        No mention of Global_name in the parameter file.

B. Listener.ora File

        No mention of Global_name in the Listener.ora file

C. SQLNET.ORA file consists of :

        # names.default_domain = world
        # name.default_zone = world

        names.default_domain=world

D. tnsnames.ora file

dispdc.world =
  (DESCRIPTION =
    (ADDRESS_LIST =
        (ADDRESS =
          (COMMUNITY = tcp.world)
          (PROTOCOL = TCP)
          (Host = DISPSVR1)
          (Port = 1521)

Please see here:

I have commented (names.default_domain=world) as ( # names.default_domain =
world) .And global_names is false.

But still whenever:

1.I create a Net Service Name ,the tnsnames always comes with .WORLD attachment
at the end.

2.I find a new entry of ( names.default_domain=world ) after the earlier
commented one.

How to get rid of the above two from occurring again and again where I don't
want to see (.WORLD)
at the end of tnsnames?

=================================================================================
=======

SQL> CONNECT  DEWADBA/DEWADBA        -- local PC
Connected.

SQL>  SHOW PARAMETER GLOBAL_NAME

NAME                   TYPE          VALUE
-------------------------- -------------    ---------------
global_names         boolean     FALSE

SQL>  SELECT * FROM GLOBAL_NAME;

GLOBAL_NAME
------------------------
FATIMA.TAHA

A. Parameter file consists of :

        db_name = "fatima"

        db_domain = taha

        instance_name = fatima

        service_names = fatima.taha

        global_names = false

B. Listener.ora file consists of :

(SID_DESC =
      (GLOBAL_DBNAME = fatima.taha)
      (ORACLE_HOME = e:\Oracle\Ora81)
      (SID_NAME = fatima)

C. No mention of Global_name in SQLNET.ORA file.

D. I have created a tnsnames (  shown below) through Net8 that makes an entry in
the tnsnames.ora file
    without attaching  global_name ,of coutrse, which is also false in this
case.

DISPSVR1 =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = DISPSVR1)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SID = ORCL)

Please see here:

1.I have created a Net Service Name ,the tnsnames  comes without (.WORLD)
attachment at the end
   what I am expecting.

=================================================================================
=====

SQL> CONNECT  DEWADBA/DEWADBA@DISPSVR1    -- tnsnames is DISPSVR1 for Server
DISPSVR1
Connected.

SQL>  SHOW PARAMETER GLOBAL_NAME

NAME                   TYPE          VALUE
-------------------------- -------------    ---------------
global_names         boolean     FALSE

SQL>  SELECT * FROM GLOBAL_NAME;

GLOBAL_NAME
-----------------------------
ORACLE.WORLD

In DISPSVR1 database the Entries are as follow:

A. Paramaeter File

        db_name = oracle

        No mention of Global_name in the parameter file.

B. Listener.ora File

        No mention of Global_name in the Listener.ora file

C. SQLNET.ORA file consists of :

        # names.default_domain = world
        # name.default_zone = world

        names.default_domain=world

---------------------------------------------------------------------------------
-----------------------------------------------------------------------

      Rating: 5
database
      link  April 18, 2002
Reviewer:  raju

tom,

i also had the simular problem, i saw ur answer for this questn and now its
fine.
thanks so much.

can you please also help me in clarifying the following

i need to query some tables in remote database.

which way is better create a view(that is derived by joining some of the tables)
at the remote DB and
access that view from my db

or

directly access those tables and join them at our side

like select * from emp@mylink a, dept@mylink b where a.deptno = b.deptno

          Followup:

You might find the view "better", in my experience I would prefer the view at
the remote site.  It helps to ensure the query optimization takes place over
there, where it should

      Rating: 4
database link
      -- view  April 23, 2002
Reviewer:  raju

if i create view on a remote database and if i join view with the tables of the
present database, now where the query optimization takes place.

          Followup:

both places.  some over there, some over here.

      Rating: 5
Read the
      following link  June 28, 2002
Reviewer:
      Sikandar Hayat Awan  from Pindi Bhattian - Pakistan

Please read the following link from top to bottom for more details,

http://otn.oracle.com/docs/products/oracle9i/doc_library/901_doc/server.901/a90117/ds_admin.htm#23649

      Rating: 5
Link
      problem  December 02, 2002
Reviewer:  atul
       from India
Sir,

For test purpose...i have created two databases of same version ie 8.1.7 on same
machine which is standalone(not connected to network)...

For creating this two databases i installed oracle software 2 times by creating
2 differnt users oracle and oracle1...

Now i am getting problem while creating database link..
error is ORA-02019:connection description for remote database not found..

What is the cause????
also by my method can i implement snapshot/replication???

Thanks.
atul

          Followup:

why would you install the software twice?

The cause is you haven't setup the tnsnames.ora, you are not running the
listener -- you need to emulate a networked environment on this standalone
machine in order to get the two databases talking.

Yes, you can play with replication using a setup like this.

      Rating: 5
link problem
      reply  December 02, 2002
Reviewer:  atul
       from india
Sir,

As you replied about problem of tnsnames.ora..
But i can connect to scott/tiger@oracle on one and
scott/tiger@oracle1 TO other instance successfully..

On oracle one in scott i created one table test..
I want to see this table from oracle1's scott..

so on both instances i chaned "alter system set global_names=false"

In oracle's scott i create db-link as
"create database link ora connect to scott identified by tiger using
'oracle1.localdomain'

link gets created..
but when i do desc scott.test@ora i get the same error
"ora-02019:connection description for remote database not found"

what else should i check..

Thanks.
atul

      Rating: 4
both db are of
      8.1.7.4 why do i get insuffecient privilege  December 12,
      2003
Reviewer:  umesh  from bangalore india

SQL> ed
Wrote file afiedt.buf

  1  begin
  2  execute immediate ' create database link l1 connect to tp2 identified by
tp2 using ''sabadev_bn
  3* end;
SQL> /

PL/SQL procedure successfully completed.

SQL> select count(*) from dual@l1;
select count(*) from dual@l1
                          *
ERROR at line 1:
ORA-02085: database link L1.US.ORACLE.COM connects to SABADEV

SQL> create or replace function f return number is
  2  begin
  3  execute immediate ' create database link lg1 connect to tp2 identified by
tp2 using ''sabadev_b
ng''';
  4  return 0;
  5  end;
  6  /

Function created.

SQL> declare
  2  n number;
  3  begin
  4  n:=f;
  5  end;
  6  /
declare
*
ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "SCOTT.F", line 3
ORA-06512: at line 4

          Followup:

http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html
roles are not enabled during the execution of definers rights procedures.

after you read that, read about GLOBAL NAMEING - it is the cause of your
ora-2085, you'll need to name the database link properly (don't disable global
names, its good, use the proper name for the link instead and hide the link from
applications using VIEWS (my preference) or SYNONYMS (second best) so you can
rename the dblink in the future if you need to.

December 13, 2003 Anthony -- Thanks for the question regarding
      "Copying Data ?", version Version 8.1.5

      You Asked
What is the easiest way of copying all the data from one table
in a database to another table in a second database ? I have
tried using copy but without any luck.

      and we said...
Well, the sqlplus copy command is pretty easy -- you don't
mention what "without any luck" means but if you can:

- sqlplus user/password    into the local database AND
- sqlplus user/password@REMOTE into the remote database...

You can sqlplus copy the data from T1( x, y, z ) (located on
REMOTE) into T2(x,y,z) (local) using:

SQL> copy from user/password@REMOTE insert t2(x,y,z) using
select x,y,z from t1;

If you have a specific error you are getting with the COPY
command -- please add that ( a cut and paste of your attempt and
the error would be great )

Another option is

o export the table from REMOTE using exp.
o import the table into local using imp.

Another option is to use a database link and simply:

insert into t2 select * from t1@remote_db_link;

Review &
      Followup

      Rating: 2
copying data...
        September 28, 2001
Reviewer:  ASP
       from INDIA
But what if remove database table has around 20,000 rows ? and what about
exception trapping while inserting ?

      Rating: 2
lob
      columns?  October 25, 2001
Reviewer:  Mikito
      Harakiri
With imp/exp I hit a problem that on remote database users tablespace is called
'users', while on local it's 'users_data'. Now I have to go to documentation to
figure out if those stupid switches would save the day...

Also with schlobs the elegant
insert into t2 select * from t1@remote_db_link;
doesn't work.

I wonder why export/import is not plain sqlplus statements where I can just
specify the right 'where' clause...

          Followup:

Yes, when you deal with multi segment objects (tables with LOBS, partitioned
table, IOTs with overflows for example), using EXP/IMP is complicated if the
target database doesn't have the same tablespace structure.  That is because the
CREATE statement contains many tablespaces and IMP will only "rewrite" the first
TABLESPACE in it (it will not put multi-tablespace objects into a single
tablespace, the object creation will fail of the tablespaces needed by that
create do not exist).

I dealt with this issue in my book,  in there, I recommend you do an:

imp .... full=y indexfile=temp.sql

In temp.sql, you will have all of the DDL for indexes and tables.  Simply delete
all index creates and uncomment any table creates you want.  Then, you can
specify the tablespaces for the various components -- precreate the objects and
run imp with ignore=y.  The objects will now be populated.

You are incorrect with the "schlobs" comment (both in spelling and in
conclusion).

scott@ORA815.US.ORACLE.COM> create table t ( a int, b blob );

Table created.

scott@ORA815.US.ORACLE.COM> desc t
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 A                                            NUMBER(38)
 B                                            BLOB

scott@ORA815.US.ORACLE.COM> select a, dbms_lob.getlength(b) from t;

no rows selected

scott@ORA815.US.ORACLE.COM> insert into t select x, y from t@ora8i.world;

1 row created.

scott@ORA815.US.ORACLE.COM> select a, dbms_lob.getlength(b) from t;

         A DBMS_LOB.GETLENGTH(B)
---------- ---------------------
         1               1000011

So, the "elegant insert into select * from" does work.

imp/exp can be plain sqlplus statements -- use indexfile=y (if you get my book,
I use this over and over in various places to get the DDL).  In 9i, there is a
simple stored procedure interface as well.

      Rating: 5
complicate data
      coping  October 26, 2001
Reviewer:  nk
       from india
tom,
that is a good answer.
i have a situation where the data transfer should be done after some
manipulations, which i
cannot do with a single sql, so i go for pl/sql procedure.
in the procedure i used pl/sql tables for intermediate purpose.
so there i want to sort the data present in pl/sql tables? is it possible?

          Followup:

No, there is no builtin facility to sort plsql tables.  You would need to "sort
it" yourself.

Best to use a global temporary table perhaps instead of plsql arrays.

      Rating: 4
if oracle would
      write web browser  October 26, 2001
Reviewer:
      Mikito Harakiri
OK, I see how I can handle tablespaces.

With "create from select" I actually stepped upon "proccess is out of memory" on
big table, but you are right, this is not logical error. Still, it doesn't work
for objects and nested tables, right? And I'm not happy with lob, because there
are so many limitations, for example innocent:
 FOR lobrec In (select lob_col from tbl@dblink) LOOP
doesn't work.

One last remark about oracle storage model. Are people happy with necessity to
micromanage it? Could I simply tell oracle what the path to data files is and
forget about tablespaces/extents/datafiles altogether? I always thought that
machines are better than humans in such routine tasks as memory allocation. If
oracle was in web browser business, before launching the browser I would have to
allocate memory for images, then, separately, for cached pages, etc...

          Followup:

Sorry you are not happy.  I guess I won't be able to fix that.

Yes, I believe people are "happy" with the storage model and all databases have
them.

I would say there are darn FEW limitations on LOBs, given they are 4 gig things
potentially and are really implemented as pointers.  There are few in 8i and
fewer in 9i.

In 9i, if you like, you can setup the database to manage all of the files.  You
can just issue:

   create tablespace foo;

we'll create the file, name it, size it, etc.

Oracle actually was in the web browser business.  It was called Power browser
(search google for "power browser oracle").  You'll be disappointed to find out
that no, you did not have to allocate your own memory for images and then
separately for cached pages, etc....

      Rating: 4
Is this a
      restriction ...  October 24, 2002
Reviewer:
      Dinesh Sivasankaran  from St. Petersburg
Hi Tom,

Desc t1: (Table has only 2 columns)
  ITEM  NUMBER(8)
  QA_STATUS VARCHAR2(1)

Desc t2:
item number(8),
qa_status varchar2(1),
.
. ( This has lot of columns)

When I try to do this,

set echo on
set arraysize 15
set copycommit 0
set long 5000

copy from admin/admin@dev -
insert t1 ( item ) -
using -
select ITEM -
from t2 ;

Array fetch/bind size is 15. (arraysize is 15)
Will commit when done. (copycommit is 0)
Maximum long size is 5000. (long is 5000)

CPY0007: Select list has fewer columns than destination table

Is this a restriction in copy command? If the table t1 has only 1 column (item)
then it works.

Please advise.

Thanks,
dinesh ...

          Followup:

It is a limitation -- here is a workaround to the limitiation:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t1 ( x int, y int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> drop table t2 cascade constraints;

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t2 ( x int, y int, z int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t2 select rownum, rownum, rownum
from all_users;

46 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> copy from ops$tkyte/x@ora817dev.us.oracle.com
-
> insert t1 ( x ) using select x from t2;

Array fetch/bind size is 15. (arraysize is 15)
Will commit when done. (copycommit is 0)
Maximum long size is 5000. (long is 5000)

CPY0007: Select list has fewer columns than destination table

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace view v1 as select x from
t1;

View created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> copy from ops$tkyte/x@ora817dev.us.oracle.com
-
> insert v1 ( x ) using select x from t2;

Array fetch/bind size is 15. (arraysize is 15)
Will commit when done. (copycommit is 0)
Maximum long size is 5000. (long is 5000)
   46 rows selected from ops$tkyte@ora817dev.us.oracle.com.
   46 rows inserted into V1.
   46 rows committed into V1 at DEFAULT HOST connection.

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

A view to hide the other columns.

      Rating: 3
Unsuccessful
      copy   October 25, 2002
Reviewer:  Sasa
       from Belgrade, Yugoslavia
Hi Tom,

I tried to copy from remote to local DB and made a following test:

-- remote -
 CREATE TABLE test_copy AS SELECT owner, object_name, object_type FROM
all_objects;

-- local  --
 CREATE TABLE test_copy AS SELECT owner, object_name, object_type FROM
all_objects WHERE 1 = 2;

---

copy FROM test/test@remote  INSERT test_copy(owner, object_name,object_type)
USING SELECT owner, object_name,object_type FROM test_copy;

and got message:

Array fetch/bind size is 15. (arraysize is 15)
Will commit when done. (copycommit is 0)
Maximum long size is 5000. (long is 5000)

ERROR:
ORA--3930258: Message -3930258 not found;  product=RDBMS; facility=ORA

Thanks,

Saša

          Followup:

try doing it the other way -- instead of copy FROM, connect to the other
database and copy TO.  push it, don't pull it.

      Rating: 2
Unsuccessful
      again   October 25, 2002
Reviewer:  Sa?a
       from Belgrade, Yugoslavia
Hi Tom,

I did it what you had recomended but something was wrong again(connect to the
other DB and copy TO - "push it dont pull it" as you said)

copy to test/test@localDB  INSERT into TEST_COPY(owner, object_name,object_type)
using SELECT owner, object_name,object_type FROM TEST_COPY;

but got error :

SP2-0498: missing parenthetical column list or USING keyword

I am not aware of SP2 error message till now and couldn't find any of these.

Regards,

Saša

          Followup:

lose the INTO in "insert into test_copy"

it doesn't belong there.

      Rating: 4
How is this
      efficient over database link  December 11, 2003

Reviewer:  Atul  from Mumbai, India
Tom,
which is the FASTEST way to migrate data from one database to other across the
network.
Long bacnk, I read in some discussion forum that SQL*Plus copy command is more
efficient than that of database link. Can you please provide more insight on
this?
Regards,
Atul

          Followup:

dblinks are faster
sqlplus copy is faster

both are true (sometimes)
both are false (sometimes)

my answer is, has been and will be -- benchmark it with some of your data in
your environment and see what works best for you.

sqlplus copy -- array fetches, incremental commits (which can be very very bad),
conventional path writes, always logged and always generates undo.  Just like
writing a program that reads N rows from db1 and inserts N rows into db2 and
commits every M fetches (in fact, thats exactly what it is).  can work with
longs (but not long raws)

dblinks -- single row stream, no incremental commits, can direct path (using
APPEND or CREATE TABLE AS SELECT), can be done nologging, can bypass undo.

which works best depends on various factors (network latency, location of the
sqlplus client, etc)

      Rating: 4
How is this
      efficient over database link  December 12, 2003

Reviewer:  Atul  from Mumbai, India
Tom,
Thanks for the reply.
You said:
-- SQLPLUS Copy: ..... always generates undo

How about if I have table created with nologging option and I am inserting into
it using copy command.
Will this still generate undo?
Regards,
Atul

          Followup:

yes.

it just uses "insert into values" that always generates undo.

you would have to use insert /*+ APPEND */ to bypass undo -- that is a dblink
option, not a sqlplus copy option.

(and undo is not related at all to NOLOGGING -- nologging is all about redo, not
undo.  you could skip redo generation using the direct path insert above -- with
the append -- as well as the undo generation if you use nologging).

December 13, 2003 razvan -- Thanks for the question regarding
      "archivelog mode - some questions", version 7.3.2

      You Asked
hello tom
please can you asked me the following questions?

1.if i put a database in archivelog mode but i don't enable ARCH
process or i don't  ever manual archive redo logs  -  can affect
in some way the database?
2. there is posibility that when i tried to reused a full log
file the ARCH don't finish the work over it(that can happen when
i have an intense activity who make redo log full in a short
time which is faster then archiving activity) and so the
database hangs up.How can i prevent it beside i use more redo
log files(5 instead 3 for example) .
3.when the ARCH tried to archive, the destination files what
permission must have (in unix parlance)? It must have oracle/dba
ownweships and rw-rw---- or another permissions?
4.All the files worked by LGWR and DBWR what permissions might
have?
5. all the operations over archiving can be done by another user
other than dba? and how?

with respect
razvan
thanks a lot

      and we said...
1) yes, the database will eventually halt.  In archive log mode
we will refuse to reuse an online redo log until it has been
archived - either automatically or manually.  If the file is not
archived, we will eventually get to the point where we need to
reuse it but cannot and we will stop until it is archived.

2) yes, if arch has not finished copying a redo log file, we
will not be able to reuse it, we will HALT.  The solution is to
speed up arch (removing any contention).  You may also consider
tuning with the init.ora parameter: log_archive_buffer_size

adding more online redo logs will in all probability help the
problem as well.

3) the destination files will typically be assigned "-rw-r-----"
perms (owner = rw, group = r, other= nothing)

4) they are typically -rw-r-----  as well.

5) archiving is performed by the database either automatically
or manually via a SQL command.  It will be done by the owner of
the database software, it can be initiated by anyone with the
appropriate "alter system" privs.

Review &
      Followup

      Rating: 5
What does
      archive log mode command do?  July 22, 2003

Reviewer:  Martin Astill
Tom,

what does running the alter db archive log mode do?
(besides the obvious). I mean does it update
header files, oracle dictionary etc etc

my reason for asking is this, I know of someone who
took a cold backup of a Db in non archive log mode
when they restarted they switched to archive log mode.
Can any of the files backedup in non archive log mode
be used in a recovery scenario and apply archived redo logs
that were generated since the DB was placed in archive log mode?

Regards

Martin

          Followup:

you cannot use a NOARCHIVELOG mode database backup for anything other then a
full restore AS OF the point in time of the backup (eg: you cannot roll forward
and recover the work performed during the time between the backup and the
recovery)

      Rating:
      5
  July 22, 2003
Reviewer:  Martin
      Astill
Hey! aren't you supposed to be on vacation

Thanks very much for taking the time to answer my
question.

Regards
Martin

      Rating: 4
One
      Question  July 23, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Lahore,Pakistan
How can we delete archive logs ??? I've delted them but they are still shown as
'not deleted'.

Regards

          Followup:

what is showing them as "not deleted"

      Rating:
      2
Information  July 23, 2003
Reviewer:
      Riaz Shahid  from PRAL,Lahore,Pakistan
The deleted column in V_$ARCHIVED_LOG has value "NO".

Riaz

          Followup:

so?  did you use our tools to manage the logs or are you just "erasing them"

as far as we know, they are NOT deleted -- they are on tape, they are moved,
they are shipped to another system -- they could exist.  so what if you erased
them?

the definition of the deleted column, as per the documentation (the Oracle
Reference Guide) is simply:

Specifies (YES | NO) whether an RMAN DELETE command has physically deleted the
archived log file from disk, as well as logically removing it from the control
file of the target database and from the recovery catalog.

So, unless you are using RMAN, this column isn't meaningful.

      Rating: 3
More question
      on Archived log files  October 02, 2003
Reviewer:
      David Jiang  from Houston, Texas, USA
Tom,

Would you please tell me in detail:
1. if I can delete the archived log files after each backup? when can I delete
it if not?
2. How can I delete the archived log files mannually? What tool do you have to
manage/delete the archived log?
3. How much or how long back should I keep the archived log file after backup on
each weekend?
4. What is the best recommendation you have for me to manage the archived log
file so that the files would not pile up and take a lot of spaces?

Thanks a lot.

          Followup:

I would recommend you use RMAN -- it'll be your "book-keeper" and won't delete
things you actually need.

For every backup you have -- you need every archive log generated since the
backup BEGAN.

So, if you keep 3 backups -- as you must -- and the oldest started at midnight
on sunday, you need ALL of the archives generated since then.

You need to get the archives off of your system -- onto something else (eggs,
single basket pops into mind otherwise).  Once you get them off - it is common
practice to keep as many on disk as possible -- faster then getting them back
onto the system when you need them.  You can get rid of whatever ones you want
though (on disk) at that point in time -- once they are safely copied 2 or 3
other places.

Use rman.

      Rating: 5
FGRD Vs ARCH
        December 12, 2003
Reviewer:  Sami
       from NJ,USA
Dear Tom,
What is the difference FGRD and ARCH in v$archived_log.creator column.
I understand FGRD is foreground process. Exactly in what scenario the value will
be populated as FGRD?

I see something like below in my db.
select recid,creator from v$archived_log
RECID CREATOR
----- -------
    1 FGRD
    2 ARCH
    3 ARCH
    4 ARCH
    5 ARCH
    6 ARCH
    7 ARCH
    8 FGRD
8 rows selected

          Followup:

Ok, so, i set log_archive_start so I could manually archive then:

ops$tkyte@ORA920> @a

     RECID CREATOR
---------- -------
        61 ARCH
        62 ARCH
        63 ARCH
        64 ARCH
        65 ARCH
        66 ARCH
        67 ARCH
        68 ARCH
        69 ARCH
        70 ARCH

10 rows selected.

all things had been done in the past by arch -- now:

ops$tkyte@ORA920> alter system switch logfile;

System altered.

ops$tkyte@ORA920> alter system archive log all;

System altered.

ops$tkyte@ORA920> @a

     RECID CREATOR
---------- -------
        63 ARCH
        64 ARCH
        65 ARCH
        66 ARCH
        67 ARCH
        68 ARCH
        69 ARCH
        70 ARCH
        71 FGRD
        72 FGRD

10 rows selected.

some of them are done by the foreground (me, my process) not arch.  This just
means someone manually archived at some point

      Rating: 4
Not able to
      reproduce Win/2000 Oracle 9.2.0.4  December 14, 2003

Reviewer:  Saminathan Seerangan  from NJ,USA

Dear Tom

I am not able reproduce the same using both SYS and NON-SYS users.

SQL> connect sys/change_on_install@sami as sysdba
Connected.
SQL> alter system switch logfile;

System altered.

SQL> alter system switch logfile;

System altered.

SQL> alter system switch logfile;

System altered.

SQL> alter system switch logfile;

System altered.

SQL> archive log list
Database log mode              Archive Mode
Automatic archival             Enabled
Archive destination            D:\oracle\ora92\RDBMS
Oldest online log sequence     1241
Next log sequence to archive   1243
Current log sequence           1243
SQL> connect test/test@sami
Connected.
SQL> alter system switch logfile;

System altered.

SQL> alter system switch logfile;

System altered.

SQL> alter system switch logfile;

System altered.

SQL> select sequence#,creator,registrar from v$archived_log where sequence# >=12

 SEQUENCE# CREATOR               REGISTRAR
---------- --------------------- ---------------------
      1234 ARCH                  ARCH
      1235 ARCH                  ARCH
      1236 ARCH                  ARCH
      1237 ARCH                  ARCH
      1238 ARCH                  ARCH
      1239 ARCH                  ARCH
      1240 ARCH                  ARCH
      1241 ARCH                  ARCH
      1242 ARCH                  ARCH
      1243 ARCH                  ARCH
      1244 ARCH                  ARCH

 SEQUENCE# CREATOR               REGISTRAR
---------- --------------------- ---------------------
      1245 ARCH                  ARCH

12 rows selected.

SQL>

      Rating: 5
Ignore my last
      posting  December 14, 2003
Reviewer:
      A reader
I should use "alter system archive log all"

December 13, 2003 Suresh -- Thanks for the question regarding
      "dbms_job.submit", version 8.1.7

      You Asked
Tom,
  I am required to run a procedure "analyze_user" at 9 AM every morning. For
this I am submitting a job as shown below.

 BEGIN
 DECLARE
 v_JobNum   NUMBER;
 BEGIN
 DBMS_JOB.SUBMIT(v_JobNum,'analyze_user;',sysdate,'sysdate +1');
 commit;
 end;
 END;

If I run the above block and submit a job by 10 AM , then is this job going to
be executed every morning 10 AM? If so, how do I make it run at 9 AM every
morning, irrespective of the time that I submitted the job. Thanks a lot.

      and we said...
The old "sliding job".  You want to make sure your times are anchored -- fixed
-- so that no matter WHEN they are evaluated, they give the correct date/time.

In your case, the interval should be:

'trunc(sysdate)+1+9/24'

take todays date, put it back to midnight, add one day (tomorrow at midnight)
and then add 9 hours.
Review & Followup

      Rating: 3
DBMS_JOB.SUBMIT
      doesn't start a job whatever we do  March 14, 2002

Reviewer:  john  from Ukraine
Hi, Tom,

I would really appreciate your help here...
We have a job to be run and we submit it
using dbms_job.submit(:job, job_block, SYSDATE, 'SYSDATE + 1/24/60/60');
But this job doesn't start neither right now nor at any point of time later.
We have job_queue_processes = 10 and job_queue_interval = 5.
We checked it out: all SNPX processes are started and running (but are they
running properly?)

What makes us think we are stupid is that we have another server with only one
difference - default codepage. and the job starts with no problems on the second
server.

We have oracle 8.1.7 with patch 3 installed on both servers

...?

          Followup:

commit;

I'll bet you are forgetting the commit;

If you go into sqlplus and issue:

SQL> exec dbms_job.submit( :n, 'null;' );

that job will never run until you commit it.

      Rating: 3
I wish it
      would've been that easy  March 15, 2002
Reviewer:
      A reader
I wish it would have been that easy...

Well, we DO issue commit; but i repeat,
the job does NOT start.
And what is more confusing is that i
can run the job explicitly using dbms_job.run.
And it's executed, but only once.
further executions that have to occur almost
every second do not occur.

Alert and smon trace files are clean.

We have no clue what to do...

          Followup:

show me the output of

o you submitting something, eg:

  variable n number;
  exec dbms_job.submit( :n, 'null;' );
  commit;

o the output of show parameter job, eg:

  show parameter job

o the output of

  set serveroutput on size 1000000
  @printtbl8 'select * from user_jobs'

o the output of

  exec dbms_lock.sleep( 30 );
  @printtbl8 'select * from user_jobs'

      Rating: 3
Here we
      go  March 18, 2002
Reviewer:  john

SQL> variable n number;
SQL>   exec dbms_job.submit( :n, 'null;' );

PL/SQL procedure successfully completed.

SQL>   commit;

Commit complete.

SQL> show parameter job

NAME                                 TYPE           VALUE
------------------------------------ -------------- ----------------------------
--
job_queue_interval                   integer        5
job_queue_processes                  integer        10

SQL> select * from user_jobs;

  JOB LOG_USER    PRIV_USER   SCHEMA_USER   LAST_DATE LAST_SEC    THIS_DATE
THIS_SEC
----- ----------- ----------- ------------- --------- ----------- ---------
-----------
  140 QIM         QIM         QIM

NEXT_DATE NEXT_SEC    TOTAL_TIME BR INTERVAL     FAILURES WHAT
--------- ----------- ---------- -- ---------- ---------- -------
18-MAR-02 10:19:22             0 N  null                  null;

NLS_ENV
---------------------------------------------------------------------------------
--------
NLS_LANGUAGE='AMERICAN' NLS_TERRITORY='CHINA' NLS_CURRENCY='RMB'
NLS_ISO_CURRENCY='CHINA'

---------------------------------------------------------------------------------
--------------------------
 NLS_NUMERIC_CHARACTERS='.,' NLS_DATE_FORMAT='DD-MON-RR'
NLS_DATE_LANGUAGE='AMERICAN' NLS_SORT='BINARY'

MISC_ENV              INSTANCE
------------------- ----------
0102000200000000             0

SQL> exec dbms_lock.sleep( 30 );

PL/SQL procedure successfully completed.

SQL> select * from user_jobs;

  JOB LOG_USER    PRIV_USER   SCHEMA_USER   LAST_DATE LAST_SEC    THIS_DATE
THIS_SEC
----- ----------- ----------- ------------- --------- ----------- ---------
-----------
  140 QIM         QIM         QIM

NEXT_DATE NEXT_SEC    TOTAL_TIME BR INTERVAL     FAILURES WHAT
--------- ----------- ---------- -- ---------- ---------- -------
18-MAR-02 10:19:22             0 N  null                  null;

NLS_ENV
---------------------------------------------------------------------------------
---------
NLS_LANGUAGE='AMERICAN' NLS_TERRITORY='CHINA' NLS_CURRENCY='RMB'
NLS_ISO_CURRENCY='CHINA'

---------------------------------------------------------------------------------
-------------------------
NLS_NUMERIC_CHARACTERS='.,' NLS_DATE_FORMAT='DD-MON-RR'
NLS_DATE_LANGUAGE='AMERICAN' NLS_SORT='BINARY'

MISC_ENV              INSTANCE
------------------- ----------
0102000200000000             0

That's it.

          Followup:

If you do a "ps" in your OS (hopefully you are unix) do you see the SNP
processes?

eg:

$ ps -auxww | grep snp._$ORACLE_SID
ora817   25484  0.1  5.610719285056 ?        S 07:54:16  0:00 ora_snp0_ora817dev
ora817   25488  0.1  5.510716082904 ?        S 07:54:16  0:00 ora_snp2_ora817dev
ora817   25490  0.1  5.510716082904 ?        S 07:54:16  0:00 ora_snp3_ora817dev
ora817   25486  0.1  5.510716082992 ?        S 07:54:16  0:00 ora_snp1_ora817dev
ora817   25492  0.1  5.510716082904 ?        S 07:54:16  0:00 ora_snp4_ora817dev
tkyte    25500  0.0  0.1  984  712 pts/2    S 07:54:46  0:00 grep snp._ora817dev

shows I have 5 running snp processes.  Do you see the snp's on your system.  If
not unix, fill in the blanks (OS, version, dbversion, etc etc)

      Rating: 4
We are
      NT.  March 18, 2002
Reviewer:  john
       from ukraine
Hi, Tom,

unfortunately i'm NT, well in fact win2000
here is our OS info (tell me if the info is not enough):
System:
  MS Win 2000 5.00.2195 SP 2
Computer:
  x86 Family 6 Model 8 Stepping 3 GenuineIntel
  AT/AT 1Gb RAM
some db info:

SQL> select * from v$version
  2  /

BANNER
----------------------------------------------------------------
Oracle8i Enterprise Edition Release 8.1.7.3.0 - Production
PL/SQL Release 8.1.7.3.0 - Production
CORE    8.1.7.2.1       Production
TNS for 32-bit Windows: Version 8.1.7.3.0 - Production
NLSRTL Version 3.4.1.0.0 - Production

what else?

yes, i did some "alter system" stuff:

SQL> alter system set job_queue_processes = 11
  2  /

System altered.

  >> after this ALRT.LOG showed this:
   ALTER SYSTEM SET job_queue_processes=11;
   Mon Mar 18 15:30:46 2002
   Restarting dead background process SNPA
   SNPA started with pid=25
  >> and the number of oracle.exe's threads in Windows Task Manager increased by
1 (=36)

SQL> alter system set job_queue_processes = 12
  2  /

System altered.

  >> after this ALRT.LOG showed this:
   ALTER SYSTEM SET job_queue_processes=12;
   Mon Mar 18 15:30:58 2002
   Restarting dead background process SNPB
   SNPB started with pid=29
  >> and the number of oracle.exe's threads in Windows Task Manager increased by
1 (=37)

          Followup:

Ok -- do the jobs run now?

      Rating: 2
some more
      output  March 19, 2002
Reviewer:
      A reader
No,

I've done one more query for you, Tom,

SQL> SELECT RAWTOHEX(paddr) paddr_hex, name FROM v$bgprocess
  2      WHERE RAWTOHEX(paddr) <> HEXTORAW(0)
  3      AND name LIKE 'SNP%';

PADDR_HEX        NAME
---------------- ----------
067BC31C         SNP0
067BC644         SNP1
067BC96C         SNP2
067BCC94         SNP3
067BCFBC         SNP4
067BD2E4         SNP5
067BD60C         SNP6
067BD934         SNP7
067BDC5C         SNP8
067BDF84         SNP9
067BF8C4         SNPA
067C0564         SNPB

12 rows selected.

          Followup:

One more thing -- what happens if you

select * from dba_jobs_running;

is it possible to shutdown/restart to see if they are "stuck" on something?

Also -- its getting close to the time to open a tar with support.  We might not
be able to resolve this here.  Consider doing that as well.

      Rating: 5
Thank
      you  March 19, 2002
Reviewer:  john
       from ukraine
Hi, Tom,

quering dba_jobs_running produced zero rows in result.

Well, yes we can restart the server, it's the
development instance, but we restarted it twice
during previous week with no changes to jobs' status.

We'll think about forwarding this issue to support
when we have an emergency.

Thank you very much for your support, Tom.

      Rating:
      5
DBA_JOBS_RUNNING  October 04, 2002

Reviewer:  Gururaj Kulkarni  from Bangalore,India

Tom,

I have two objects of DBA_JOBS_RUNNING with types 1)Synonym 2)View. When I query
on DBA_JOBS_RUNNING, which object will be used to get the reuslt ?

  1* select object_name,object_type from all_OBJECTS where object_name like
'%DBA_JOBS_RUNNING%'
SQL> /

OBJECT_NAME                    OBJECT_TYPE
------------------------------ ------------------
DBA_JOBS_RUNNING               VIEW
DBA_JOBS_RUNNING               SYNONYM

SQL> select object_name,object_type from dba_OBJECTS where object_name like
'%DBA_JOBS_RUNNING%';

OBJECT_NAME       OBJECT_TYPE

---------------   ------------

DBA_JOBS_RUNNING   VIEW

DBA_JOBS_RUNNING   SYNONYM

SQL> select text from all_views where view_name='DBA_JOBS_RUNNING';

TEXT

--------------------------------------------------------------------------------

select v.SID, v.id2 JOB, j.FAILURES,

    LAST_DATE, substr(to_char(last_date,'HH24:MI:SS'),1,8) LAST_SEC,

    THIS_DATE, substr(to_char(this_date,'HH24:MI:SS'),1,8) THIS_SEC,

    j.field1 INSTANCE

  from sys.job$ j, v$lock v

  where v.type = 'JQ' and j.job (+)= v.id2

SQL>  select * from all_synonyms where synonym_NAME='DBA_JOBS_RUNNING'
  2  ;

OWNER   SYNONYM_NAME     TABLE_OWNER    TABLE_NAME  DB_LINK

------- --------------   ------------   ----------- ---------
PUBLIC  DBA_JOBS_RUNNING  SYS DBA_JOBS_RUNNING

          Followup:

the synonym points to the view, the view queries some tables.

If you are logged in NOT AS SYS, you will be using the synonym to query the view
to query the tables.

If you are logged in AS SYS, you will be using the view to query the tables
directly.

      Rating:
      5
DBMS_JOB.CHANGE  October 24, 2002

Reviewer:  sm  from usa
Tom,
I recently changed times on my snapshot refreshes.
There were couple that showed  that they ran in dba_jobs and dba_snapshots.
but in dba_snapshots the "START_WITH" column was null.
Although...dba_jobs had next_date, next_sec column populated with correct
values...
There is no error in the alert log about these jobs..
Can you help?

Thanks

          Followup:

what's the problem exactly?

are they running (refreshing) as expected?

      Rating:
      5
dba_snapshots.start_with  October 24, 2002

Reviewer:  sm  from usa
snapshot ran..bt dba_snapshot.start_with column was null

          Followup:

dba_snapshots is superceded by dba_mviews, try that one

      Rating:
      3
dbms_job  October 25, 2002
Reviewer:
      Ram  from Ct, USA
tom,

I have a requirement, where I need to run a packaged procedure when a set of
tables are populated. ( Tables A, B, c). Ex:Data (from flat files ) would be
loaded  into A, B, C every morning at 12:30 AM .After each table is loaded the
process updates a log table (D) and sets up a flag saying  table is loaded. I
need to check that all the 3 tables are loaded and then kick off my procedure.
What would be the best option?
Here is the scenario for the log table.
filename
statusflag
start_time
end_time

Tables A,B,C would be populated with filenames that start with 'XYZ'. While the
data table (A,B,C) is beong loaded, the status flag would have value of "IN
PROGRESS" and once finished would have value of "LOADED SUCCESSFULLY".

I can query distinct statusflag for these filenames and if I get a single name
("LOADED SUCCESSFULLY"), I need to kick off my procedure.
My question is How/where do I ste up my job ?

Thanks for your help !

          Followup:

Instead of (or in addition to) doing an update, I would submit a JOB via
DBMS_JOB to process the tables.

That would do it.

See the supplied packages guide that documents all of the dbms_* and utl_*
packages for details.

      Rating:
      5
dba_mviews  October 25, 2002
Reviewer:
      sm  from usa
I tried this view...it had totally absurd results
Last_refresh dates are old...where as dba_snapshots..and dba_jobs are all
current...compile_state has error???
LAST_REFR COMPILE_STATE AFTER_FAST_REFRESH
--------- ------------- ---------
12-JUL-01 ERROR         UNDEFINED
20-JUN-02 ERROR         UNDEFINED
10-JUL-01 ERROR         UNDEFINED
02-AUG-02 ERROR         UNDEFINED
10-JUL-01 ERROR         UNDEFINED
06-SEP-01 ERROR         UNDEFINED
06-SEP-01 ERROR         UNDEFINED
16-JUL-01 ERROR         UNDEFINED
12-JUL-01 ERROR         UNDEFINED
12-JUL-01 ERROR         UNDEFINED
05-MAR-02 ERROR         UNDEFINED
20-SEP-02 ERROR         UNDEFINED
10-JUL-01 ERROR         UNDEFINED
27-JUL-01 ERROR         UNDEFINED
10-JUL-01 ERROR         UNDEFINED
10-JUL-01 ERROR         UNDEFINED
10-JUL-01 ERROR         UNDEFINED
23-AUG-02 ERROR         UNDEFINED
10-JUL-01 ERROR         UNDEFINED
13-SEP-02 ERROR         UNDEFINED
13-SEP-02 ERROR         UNDEFINED
13-SEP-02 ERROR         UNDEFINED
13-SEP-02 ERROR         UNDEFINED
13-SEP-02 ERROR         UNDEFINED
13-SEP-02 ERROR         UNDEFINED
10-JUL-01 ERROR         UNDEFINED
10-JUL-01 ERROR         UNDEFINED
10-JUL-01 ERROR         UNDEFINED
10-JUL-01 ERROR         UNDEFINED
10-JUL-01 ERROR         UNDEFINED
25-MAR-02 ERROR         UNDEFINED
10-JUL-01 ERROR         UNDEFINED
10-JUL-01 ERROR         UNDEFINED

          Followup:

contact support, open a tar.

      Rating:
      2
DBMS_JOB.SUBMIT  October 29, 2002

Reviewer:  George Thomas  from Cleveland, OH USA

I am having the same issue the user "john  from Ukraine".
I installed Oracle8.1.7.3.3 on Windows2000(SP2) PentiumIV Server. I enabled
JOB_QUEUE_PROCESS=2, JOB_QUEUE_INTERVAL=1.
I put commit after submitting my job. It does't start until I run
dbms_job.run(1). Only after execute dbms_job.run(1), I could see
LAST_SEC,LAST_DATE,FAILURES values on USER_JOBs.
These values were null before execute dbms_job.run(1).
Moreover it is not starting again.

          Followup:

you didn't restart the database.

do this:

show parameter job

if job queue processes is not set - or job queue interval is too big -- no go.
you need to restart after changing some parameters (you can alter system set
job_queue_processes=n without restarting!)

if all is "correct", please contact support and open a tar -- they'll need
various things like "version of software" and such.

      Rating:
      5
DBMS_JOB.SUBMIT  November 07, 2002

Reviewer:  George Thomas  from Cleveland, OH USA

I set JOB_QUEUE_PROCESSES=2 and JOB_QUEUE_INTERVAL=2
Even after restart my database Job has't started.
(I did't try to reboot system). But I set  "ALTER SYSTEM SET
JOB_QUEUE_PROCESSES=2". The output in SIDalert.log file is "Restarting dead
background process SNP1". It works.
I opened tar too, but your suggestion helped me.

      Rating: 5
Prioritize
      jobs  December 17, 2002
Reviewer:
      A reader  from India
I have 2 jobs hitting the server at the same moment. Is there any way I can
prioritize them
Thanks

          Followup:

read about the resource manager in the server admin guide.

      Rating: 5
only on week
      days ?  December 17, 2002
Reviewer:
      A reader
Hi tom,

 can you please confirm that the folling job will be
executed only on week-days and not on week-ends and
this is the good way to do that or please suggest me
what will be the better way.

 I want to set up a db-job which should run every
weekday @8.0 (hh24)

my solution

declare

jobno number;

begin

  DBMS_JOB.SUBMIT( jobno,'update_current_flag;',
              sysdate,
                 select trunc(decode( to_char( d+1, 'DY') ,
                              'SAT', next_day(d,'MON'),
                             'SUN',next_day(d,'MON'),
                          d+1 )
                           )+8/24
                 from ( select sysdate+rownum d  from dual)
                );

  commit;

end;
/

--Thanks,

          Followup:

I cannot confirm that at all -- in fact I can confirm the job COULD run on sat
for sure....

how?  database goes down friday more, or you accidently stopped the job queue
for some reason, the job fails for whatever reason ---

then

database comes up on saturday after a point in time restore from backups, or you
noticed the job queues were shutdown, or the reason for the job failing is
suddenly fixed - the job runs on sat....

In anycase -- you can use this decode statement (obviously replacing sat/sun/mon
with your local spelling):

ops$tkyte@ORA920> alter session set nls_date_format = 'Dy DD-Mon-YYYY hham';

Session altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select dt,
  2
trunc(decode(to_char(dt+1,'DY'),'SAT',next_day(dt,'MON'),'SUN',next_day(dt,'MON')
,dt+1))+8/24
  3    from ( select sysdate+rownum dt from all_objects where rownum < 32 )
  4  /

DT                   TRUNC(DECODE(TO_CHAR
-------------------- --------------------
Thu 19-Dec-2002 10am Fri 20-Dec-2002 08am
Fri 20-Dec-2002 10am Mon 23-Dec-2002 08am
Sat 21-Dec-2002 10am Mon 23-Dec-2002 08am
Sun 22-Dec-2002 10am Mon 23-Dec-2002 08am
Mon 23-Dec-2002 10am Tue 24-Dec-2002 08am
Tue 24-Dec-2002 10am Wed 25-Dec-2002 08am
Wed 25-Dec-2002 10am Thu 26-Dec-2002 08am
Thu 26-Dec-2002 10am Fri 27-Dec-2002 08am
Fri 27-Dec-2002 10am Mon 30-Dec-2002 08am
Sat 28-Dec-2002 10am Mon 30-Dec-2002 08am
Sun 29-Dec-2002 10am Mon 30-Dec-2002 08am
Mon 30-Dec-2002 10am Tue 31-Dec-2002 08am
Tue 31-Dec-2002 10am Wed 01-Jan-2003 08am
Wed 01-Jan-2003 10am Thu 02-Jan-2003 08am
Thu 02-Jan-2003 10am Fri 03-Jan-2003 08am
Fri 03-Jan-2003 10am Mon 06-Jan-2003 08am
Sat 04-Jan-2003 10am Mon 06-Jan-2003 08am
Sun 05-Jan-2003 10am Mon 06-Jan-2003 08am
Mon 06-Jan-2003 10am Tue 07-Jan-2003 08am
Tue 07-Jan-2003 10am Wed 08-Jan-2003 08am
Wed 08-Jan-2003 10am Thu 09-Jan-2003 08am
Thu 09-Jan-2003 10am Fri 10-Jan-2003 08am
Fri 10-Jan-2003 10am Mon 13-Jan-2003 08am
Sat 11-Jan-2003 10am Mon 13-Jan-2003 08am
Sun 12-Jan-2003 10am Mon 13-Jan-2003 08am
Mon 13-Jan-2003 10am Tue 14-Jan-2003 08am
Tue 14-Jan-2003 10am Wed 15-Jan-2003 08am
Wed 15-Jan-2003 10am Thu 16-Jan-2003 08am
Thu 16-Jan-2003 10am Fri 17-Jan-2003 08am
Fri 17-Jan-2003 10am Mon 20-Jan-2003 08am
Sat 18-Jan-2003 10am Mon 20-Jan-2003 08am

31 rows selected.

Just replace DT with SYSDATE -- lose the select from dual

ops$tkyte@ORA920> variable n number
ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2    dbms_job.submit( :n,
  3                    'null;',
  4                     sysdate,
  5                    'trunc(decode(to_char(sysdate+1,''DY''),''SAT'',' ||
  6                    'next_day(sysdate,''MON''),''SUN'',' ||
  7                    'next_day(sysdate,''MON''),sysdate+1))+8/24'
  8    );
  9  end;
 10  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_job.run( :n );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select what, next_date from user_jobs;

WHAT                           NEXT_DATE
------------------------------ --------------------
null;                          Thu 19-Dec-2002 08am

ops$tkyte@ORA920>

      Rating: 4
won't it submit
      the job for 3 - times ??  December 17, 2002

Reviewer:  A reader

      Rating: 5
Not in week
      days  December 17, 2002
Reviewer:  Connor
      McDonald  from UK
dbms_job.submit(:j,
  'if to_char(sysdate,''DY'') not in (''SAT'',''SUN'') then
     my_proc;
   end if;',...)

or thereabouts

hth
connor

      Rating: 4
Just a
      query  December 17, 2002
Reviewer:  Bhavesh

Hi Tom,
   Well I have a procedure and job as follows :

create or replace procedure index_sync    is
begin
   ctx_ddl.sync_index('faq_question');
end;
/

declare
  jobno number;
  begin
  dbms_job.submit( job=>jobno, what=>'index_sync;', next_date=>sysdate,
interval=>'sysdate+1/24');
  end;
/
 commit;

This job runs every hour. Say I execute this procedure on the server at 0923 hrs
so this process will run at 1023 hrs, 1123 hrs and so on.
Is it possible to start the job at next rounded hour i.e in this example say if
I submit at 0923 hrs the job gets started at 1000 hrs and then run every hour.
Thanks a lot in advance.
Best Regards

          Followup:

interval should be

'trunc(sysdate,''hh'')+1/24'

      Rating:
      5
dbms_job  December 18, 2002
Reviewer:
      umesh  from india
i have 2 jobs coming to database from 2 procedures
Incase if they both hot the database at the same time is there a way where in I
can set the priority for them?

          Followup:

read the admin guide, the resource manager.

      Rating: 5
why 3- times
      ?  December 30, 2002
Reviewer:
      A reader
so tom,

as the output of the query..

/**************************************************/
ops$tkyte@ORA920> select dt,
  2
trunc(decode(to_char(dt+1,'DY'),'SAT',next_day(dt,'MON'),'SUN',next_day(dt,'MON')

,dt+1))+8/24
  3    from ( select sysdate+rownum dt from all_objects where rownum < 32 )
  4  /

DT                   TRUNC(DECODE(TO_CHAR
-------------------- --------------------
Thu 19-Dec-2002 10am Fri 20-Dec-2002 08am
Fri 20-Dec-2002 10am Mon 23-Dec-2002 08am   <---
Sat 21-Dec-2002 10am Mon 23-Dec-2002 08am   <---
Sun 22-Dec-2002 10am Mon 23-Dec-2002 08am   <---
Mon 23-Dec-2002 10am Tue 24-Dec-2002 08am
Tue 24-Dec-2002 10am Wed 25-Dec-2002 08am
Wed 25-Dec-2002 10am Thu 26-Dec-2002 08am
Thu 26-Dec-2002 10am Fri 27-Dec-2002 08am
Fri 27-Dec-2002 10am Mon 30-Dec-2002 08am
Sat 28-Dec-2002 10am Mon 30-Dec-2002 08am
Sun 29-Dec-2002 10am Mon 30-Dec-2002 08am
Mon 30-Dec-2002 10am Tue 31-Dec-2002 08am
Tue 31-Dec-2002 10am Wed 01-Jan-2003 08am
Wed 01-Jan-2003 10am Thu 02-Jan-2003 08am
Thu 02-Jan-2003 10am Fri 03-Jan-2003 08am
Fri 03-Jan-2003 10am Mon 06-Jan-2003 08am
Sat 04-Jan-2003 10am Mon 06-Jan-2003 08am
Sun 05-Jan-2003 10am Mon 06-Jan-2003 08am
Mon 06-Jan-2003 10am Tue 07-Jan-2003 08am
Tue 07-Jan-2003 10am Wed 08-Jan-2003 08am
Wed 08-Jan-2003 10am Thu 09-Jan-2003 08am
Thu 09-Jan-2003 10am Fri 10-Jan-2003 08am
Fri 10-Jan-2003 10am Mon 13-Jan-2003 08am
Sat 11-Jan-2003 10am Mon 13-Jan-2003 08am
Sun 12-Jan-2003 10am Mon 13-Jan-2003 08am
Mon 13-Jan-2003 10am Tue 14-Jan-2003 08am
Tue 14-Jan-2003 10am Wed 15-Jan-2003 08am
Wed 15-Jan-2003 10am Thu 16-Jan-2003 08am
Thu 16-Jan-2003 10am Fri 17-Jan-2003 08am
Fri 17-Jan-2003 10am Mon 20-Jan-2003 08am
Sat 18-Jan-2003 10am Mon 20-Jan-2003 08am

31 rows selected.

/**************************************************/

will the job be submitted for 3-times on monday ?
( 1 for sat, 1 for sun and 1 for mon )

Thanks,

          Followup:

It wont be -- that is just showing that if:

CURRENT DATE
Thu 19-Dec-2002 10am Fri 20-Dec-2002 08am
Fri 20-Dec-2002 10am Mon 23-Dec-2002 08am   <---
Sat 21-Dec-2002 10am Mon 23-Dec-2002 08am   <---
Sun 22-Dec-2002 10am Mon 23-Dec-2002 08am   <---

the current date is fri, next will be mon.  Suppose however the DB was down on
friday for some reason and the job runs on SAT -- the next date will be MON.  Or
it was down on Fri and Sat -- then runs on SUN -- the next date will be SUN.

This is just showing that no matter when the job runs -- the next SCHEDULED run
will be when you wanted it to me.

      Rating:
      5
cool  December 30, 2002
Reviewer:
      A reader

      Rating:
      4
Why  February 06, 2003
Reviewer:
      A reader
I have a package procedure:
package_1.proc_1(date_in IN DATE, argument1 IN NUMBER, argument2 OUT
VARCHAR2(20));
I want to schedule a job start to RUN this procedure in early morning tomorrow
at 3:00AM, and RUN it at this
time(3:00AM) daily afterwards.

declare
  jobno      number;
  arg_in     NUMBER := 1;
  arg_out    VARCHAR2(2);
BEGIN
  DBMS_JOB.SUBMIT(jobno,
'package_1.proc_1(syadate,'||TO_CHAR(arg_in)||','||ls_success||');',
                         TRUNC(SYSDATE)+1+3/24, 'TRUNC(SYSDATE)+1+3/24');
  commit;
END;
/

But I got the error:
declare
*
ERROR at line 1:
ORA-06550: line 1, column 142:
PLS-00103: Encountered the symbol ")" when expecting one of the following:
( - + mod not null others <an identifier>
<a double-quoted delimited-identifier> <a bind variable> avg
count current exists max min prior sql stddev sum variance
execute forall time timestamp interval date
<a string literal with character set specification>
<a number> <a single-quoted SQL string>
The symbol "null" was substituted for ")" to continue.
ORA-06512: at "SYS.DBMS_JOB", line 79
ORA-06512: at "SYS.DBMS_JOB", line 131
ORA-06512: at line 7

BTW, is this NEXT and INTERVAL setup correctly?

Please help

THanks

          Followup:

No idea what ls_success is.

or syadate for that matter ;)

You would submit a block that looks like this:

'declare
   l_out_arg  varchar2(20);
 begin
   package_1.proc_1( sysdate, 1, l_out_arg );
 end;'

in any case -- when you want to run a job that gives OUT parameters, you have to
submit a block that defines storage for them.

      Rating:
      5
More  February 06, 2003
Reviewer:
      A reader
Thanks for you usful response.

Could you check the NEXT and INTERVAL are correct or not
above according to my criteria please?

THanks

          Followup:

looks ok to me.

      Rating: 4
concurret
      jobs  February 28, 2003
Reviewer:  umesh
       from bangalore India
sorry for asking this again
I have 2 procedures hitting the DB at the same time I at the DB level must
specify the pririty , You had suggested resource manager , I am not able to
follow how do i implement, can u give me a small example explaining the same
Thanks

          Followup:

Better then that -- I can show you the documentation:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96521/part5.htm#435958

      Rating:
      3
  May 07, 2003
Reviewer:
      A reader
can DBMS_JOB be used to schedule jobs to be executed x times, starting at a
given date with a given period of time in between each execution (is SQL valid
for parameter 'intervall')?

          Followup:

there is at least two ways to do this easily

first way -- use an interval input function that returns NULL when you want to
stop, the next date otherwise.

second way -- have the procedure itself say when it wants to run next, return
NULL when you want to stop, next date otherwise.  Consider:

ops$tkyte@ORA920> create table t ( msg varchar2(50) );
Table created.

ops$tkyte@ORA920> create or replace procedure method1( p_job in number )
  2  as
  3  begin
  4       insert into t values ( systimestamp || ' job = ' || p_job );
  5  end;
  6  /
Procedure created.

ops$tkyte@ORA920> create or replace procedure method2( p_job in number,
  2                                       p_next_date in out date,
  3                                       p_stop_after in date )
  4  as
  5  begin
  6      p_next_date := sysdate + 1/24/60;
  7      if (p_next_date > p_stop_after)
  8      then
  9          p_next_date := null;
 10      end if;
 11       insert into t values ( systimestamp || ' job = ' || p_job );
 12  end;
 13  /

Procedure created.

and we'll run the jobs every minute for the next 5 minutes:

ops$tkyte@ORA920> column s new_val stop
ops$tkyte@ORA920> select to_char(sysdate+5/24/60,'yyyymmddhh24miss') S from
dual;

S
--------------
20030507074933

ops$tkyte@ORA920> variable n number

ops$tkyte@ORA920> begin
  2      dbms_job.submit( :n,
  3                       'method1(JOB);',
  4                       sysdate,
  5                       '(case when
sysdate>to_date(''&stop'',''yyyymmddhh24miss'') then null else sysdate+1/24/60
end)' );
  6  end;
  7  /
old   5:                      '(case when
sysdate>to_date(''&stop'',''yyyymmddhh24miss'') then null else sysdate+1/24/60
end)' );
new   5:                      '(case when
sysdate>to_date(''20030507074933'',''yyyymmddhh24miss'') then null else
sysdate+1/24/60 end)' );

PL/SQL procedure successfully completed.

that case expression will be selected from dual when the job is executed.
it'll return NULL when we go past 5 minutes and the job will "unschedule"
itself

ops$tkyte@ORA920> begin
  2      dbms_job.submit( :n,
  3
'method2(JOB,NEXT_DATE,to_date(''&stop'',''yyyymmddhh24miss'') );' );
  4  end;
  5  /
old   3:
'method2(JOB,NEXT_DATE,to_date(''&stop'',''yyyymmddhh24miss'') );' );
new   3:
'method2(JOB,NEXT_DATE,to_date(''20030507074933'',''yyyymmddhh24miss'') );' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> commit;

Commit complete.

Here we are letting the job say when it wants to run next procedurally.  our
procedure will compute next date and return it.  this is really useful for truly
complex intervals like:

run every other tuesday unless it is the 11th of the month, then run wed instead

or something wacky

So, now the jobs are scheduled, we can watch them run

ops$tkyte@ORA920> exec print_table( 'select * from user_jobs' );
JOB                           : 100287
LAST_DATE                     :
LAST_SEC                      :
THIS_DATE                     :
THIS_SEC                      :
NEXT_DATE                     : 07-may-2003 07:44:33
NEXT_SEC                      : 07:44:33
WHAT                          : method1(JOB);
-----------------
JOB                           : 100288
LAST_DATE                     :
LAST_SEC                      :
THIS_DATE                     :
THIS_SEC                      :
NEXT_DATE                     : 07-may-2003 07:44:33
NEXT_SEC                      : 07:44:33
WHAT                          :
method2(JOB,NEXT_DATE,to_date('20030507074933','yyyymmddhh24miss') );
-----------------
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec dbms_lock.sleep(60);
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec print_table( 'select * from user_jobs' );
JOB                           : 100287
LAST_DATE                     : 07-may-2003 07:44:35
LAST_SEC                      : 07:44:35
THIS_DATE                     :
THIS_SEC                      :
NEXT_DATE                     : 07-may-2003 07:45:35
NEXT_SEC                      : 07:45:35
INTERVAL                      : (case when
sysdate>to_date('20030507074933','yyyymmddhh24miss') then null else
sysdate+1/24/60 end)
WHAT                          : method1(JOB);
-----------------
JOB                           : 100288
LAST_DATE                     : 07-may-2003 07:44:35
LAST_SEC                      : 07:44:35
THIS_DATE                     :
THIS_SEC                      :
NEXT_DATE                     : 07-may-2003 07:45:38
NEXT_SEC                      : 07:45:38
WHAT                          :
method2(JOB,NEXT_DATE,to_date('20030507074933','yyyymmddhh24miss') );
-----------------
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec dbms_lock.sleep(60);
PL/SQL procedure successfully completed.

as you can see the jobs ran after a minute.. as expected.  this went on for a
while and then

...

ops$tkyte@ORA920> exec dbms_lock.sleep(60);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec print_table( 'select * from user_jobs' );
JOB                           : 100287
LAST_DATE                     : 07-may-2003 07:48:41
LAST_SEC                      : 07:48:41
THIS_DATE                     :
THIS_SEC                      :
NEXT_DATE                     : 07-may-2003 07:49:41
NEXT_SEC                      : 07:49:41
INTERVAL                      : (case when
sysdate>to_date('20030507074933','yyyymmddhh24miss') then null else
sysdate+1/24/60 end)
WHAT                          : method1(JOB);
-----------------
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec dbms_lock.sleep(60);
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec print_table( 'select * from user_jobs' );
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec dbms_lock.sleep(60);
PL/SQL procedure successfully completed.

ops$tkyte@ORA920>

they both "disappeared" by themselves.

      Rating:
      5
  May 08, 2003
Reviewer:
      A reader
GREAT JOB: thank you so much!

      Rating:
      5
  May 09, 2003
Reviewer:
      A reader
""
show me the output of

    o you submitting something, eg:

      variable n number;
      exec dbms_job.submit( :n, 'null;' );
      commit;

    o the output of show parameter job, eg:

      show parameter job

    o the output of

      set serveroutput on size 1000000
      @printtbl8 'select * from user_jobs'

    o the output of

      exec dbms_lock.sleep( 30 );
      @printtbl8 'select * from user_jobs'
""

why do you want dbms_lock.sleep( 30 ) there?

Thanks alot

          Followup:

i wanted them to wait 30 seconds to see if the status of the job changed.

      Rating: 5
Setting up a
      job from a different schema  July 26, 2003

Reviewer:  A reader  from Houston
Tom,
We are running into a problem setting up a batch job.

When I execute the procedure SCHEMA_A.P_RUN_PROC (SQLPlus)
a) from schema_a - runs fine
b) from schema_b - runs fine
c) as a batch job(dbms_job) from schema_a - runs_fine
d) as a batch job(dbms_job) from schema_b - job fails

For case d), the error in the dump file is show below.  Why is this error
message coming up and what are we doing wrong?

--Setting up the job

variable jobno number
BEGIN
    DBMS_JOB.SUBMIT
    ( JOB        => :jobno
    , WHAT       => 'schema_a.p_run_proc(100);'
    , NEXT_DATE  =>  TRUNC(SYSDATE,  'hh' ) + 1/24
    , INTERVAL   => 'TRUNC(SYSDATE, ''hh'') + 1/24 + 15/(24 * 60)'
    );
    END;
/

Dump file /u03/app/oracle/admin/beta/bdump/beta_j001_87432.trc
Oracle9i Enterprise Edition Release 9.2.0.3.0 - 64bit Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production
ORACLE_HOME = /u01/app/oracle/product/9.2.0
System name: AIX
Node name: abcibmdevl1
Release: 3
Version: 4
Machine: 0003B20S4CA10
Instance name: beta
Redo thread mounted by this instance: 1
Oracle process number: 23
Unix process pid: 87432, image: oracle@abcibmdevl1 (J001)
*** SESSION ID:(58.14907) 2003-07-18 15:17:09.260
*** 2003-07-18 15:17:09.260
ORA-12012: error on auto execute of job 83
ORA-06550: line 1, column 96:
PLS-00201: identifier 'SCHEMA_A.P_RUN_PROC' must be declared
ORA-06550: line 1, column 96:
PL/SQL: Statement ignored

Thank you

          Followup:

I believe schema_b has the rights to run schema_a's procedure via a ROLE and
roles are not enabled during job execution.

Grant execute on p_run_proc to schema_b and retry.

      Rating:
      3
Gr8  July 30, 2003
Reviewer:  Bipin
      Ganar  from INDIA
Tom,
What u have answered is correct.But if i set time daily 6.00 PM and if job fails
bcos of some reason my next time should be after 1.00 Hour i.e. 7.00 PM on the
same day and at 7PM it's successfull it should start 6PM on next date

          Followup:

and if you use

trunc(sysdate)+1+6/24

it'll do so?  not sure what you are saying -- or are you saying "if the job
fails, I would like it to run an hour later"?  if so, submit THIS as the job:

begin
    your_procedure;
    next_date := trunc(sysdate)+1+6/24;
exception
    when others
    then
         next_date := trunc(sysdate,'hh')+1/24;
         (might email yourself or write a message into a log somewhere)
end;

Use the ability for the job to schedule its next run by setting next_date
itself.

      Rating: 4
Very
      Nice  August 30, 2003
Reviewer:
      R.Chacravarthi  from Chennai,India
Dear Sir,
I tried to create a job which runs every second.It inserts
into a target table .But it fails to do so.Here is the code
what I have tried.

> desc track
 Name                            Null?    Type
 ------------------------------- -------- ----
 A                                        DATE

SQL> create or replace procedure ins_track
  2  as
  3   begin
  4    insert into track values(to_char(sysdate,'hh24:mi:ss'));
  5   end;
  6  /

Procedure created.

SQL> var v number
SQL> begin
  2   dbms_job.submit(:v,'ins_track;',sysdate,'(sysdate+1/(24*60*60*1000))');
  3  end;
  4  /

PL/SQL procedure successfully completed.

SQL> print v

         V
----------
         6

SQL> select * from track;

no rows selected
// Track table shows no rows.what could be the problem?
 could you please look into this?I use oracle v8.0 personal
edition
Thanks.

          Followup:

every second isn't going to fly -- every 5, 10 or more seconds, sure.  every
second, too small a time period.

but in your case, set your job_queue_processes and job_queue_interval parameters
to get the job queues going

and don't forget

SQL> commit;

otherwise that job just isn't "visible" to anyone but you (eg: the job queues
cannot see it until you commit)

one second is simply:

sysdate+1/24/60/60

      Rating: 5
Need urgent
      help on the dead job queue system.  December 09, 2003

Reviewer:  Richard Tan  from Washington
Hi Tom,

We are running similiar problem that job queue system is not functioning.  Our
production DBA suggest to rebuild AQ - job queue subsystem, I do not where to
start.  Please help.
Please see the following,

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.4.0 - Production

SQL> spool c:\job_queue_log.txt;
SQL> show parameter job;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
_job_queue_interval                  integer     60
job_queue_processes                  integer     100
SQL> ed;
Wrote file afiedt.buf

  1* create table test_job_queue (id number, content varchar2(100))
SQL> /

Table created.

SQL> ed;
Wrote file afiedt.buf

  1  declare
  2     p_job integer;
  3  begin
  4      dbms_job.submit( p_job,
  5      'begin'||chr(10)||
  6        'insert into test_job_queue values(1,''test number
one'');'||chr(10)||
  7        'end;',
  8       sysdate);
  9      commit;
 10* end;
SQL> /

PL/SQL procedure successfully completed.

SQL> exec dbms_lock.sleep(600);

SQL> select * from test_job_queue;

no rows selected

SQL> /

no rows selected

SQL> select LAST_DATE, NEXT_DATE, BROKEN, INTERVAL, WHAT   from all_jobs where
job = (select max(job
) from all_jobs);

LAST_DATE NEXT_DATE B
--------- --------- -
INTERVAL
--------------------------------------------------------------------------------
WHAT
--------------------------------------------------------------------------------
          09-DEC-03 N
null
begin
insert into test_job_queue values(1,'test number one');
end;

We have more than five recurring jobs in the development server, all stop
working one month ago.  We are under window 2000 environment.

Thank you very much for help.  We really appreciate it.

          Followup:

AQ and dbms_job are left and right, nothing to do with eachother at all.

try setting job queue processes to 0 and then back to >0, see if that does not
"touch it" and get it going.

      Rating: 5
Follow up the
      last question  December 09, 2003
Reviewer:
      RICHARD TAN  from WASHINGTON
Hi Tom,

Thank you very much for your quick response.

I have tried your approach.  The job queue system still not react (dead).

Please help more...

          Followup:

you'll either have to

a) bounce the instance
b) contact support and open a tar

(by the way, what did you do to your system to get "_" parameters to come out
like that.  you didn't play with the v$ views or anything did you?)

      Rating: 5
Follow
      up  December 10, 2003
Reviewer:  RICHARD TAN
       from WASHINGTON
Hi Tom,

Thanks for your help.

_job_queue_interval is intentionally-used, because oracle 9i has depreciated
job_queue_interval, oracle will not accept job_queue_interval anymore or it will
be ignored anyway.

We did restart the instance.

It is still not functioning.

Thanks for help.

          Followup:

my point is -- show parameter WILL NOT SHOW _ PARAMETERS AT ALL

ops$tkyte@ORA9IR2> show parameter job

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
job_queue_processes                  integer     10
ops$tkyte@ORA9IR2>

Connected to:
Oracle9i Enterprise Edition Release 9.0.1.2.0 - Production
With the Partitioning option
JServer Release 9.0.1.2.0 - Production

ops$tkyte@ORA9I> show parameter job

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
job_queue_processes                  integer     1
ops$tkyte@ORA9I>

so tell me -- what wacky things have you done to your system to cause _
parameters to appear.

has someone been playing with the data dictionary????

      Rating: 5
Follow
      up  December 10, 2003
Reviewer:  RICHARD TAN
       from WASHINGTON
Hi Tom,

Thanks for help.

Since oracle 9i depreciated job_queue_interval, initially, we did not have this
parameter.  Because our job queue system of oracle is not functioning on our
development server, we intentionally add job_queue_interval, oracle does not
accept job_queue_interval, so we add _job_queue_interval instead.  We have not
touch data dictionary at all.

SQL> ed;
Wrote file afiedt.buf

  1* select name from v$parameter where name like '%job_queue%'
  2  /

NAME
------------------------------------------------------------job_queue_processes
_job_queue_interval

SQL> show parameter job;

NAME                                 TYPE        VALUE
------------------------------------ ----------- -----------_job_queue_interval
                integer     1
job_queue_processes                  integer     50

job_queue_interval is basically depreciated in oracle 9i.

Thanks for advice.

          Followup:

it is a HIDDEN PARAMETER in 9i - you are NOT to set it at all.  get rid of it.

I know it is "deprecated", you are not to set it at all.

what other "_" parameters and events are you setting (and why)

what is the output of

alter session set nls_date_format='dd-mon-yyyy hh24:mi';
select what, broken, next_date, last_date, sysdate from user_jobs;

      Rating: 5
Follow
      up  December 11, 2003
Reviewer:  RICHARD TAN
       from WASHINGTON
Hi Tom,

Thank you very much for your precious time.

Here is the output:

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.4.0 - Production

SQL> alter session set nls_date_format = 'dd-mon-yyyy hh24:mi';

Session altered.

SQL> select what, broken, next_date, last_date, sysdate from user_jobs;

WHAT
--------------------------------------------------------------------------------
B NEXT_DATE         LAST_DATE         SYSDATE
- ----------------- ----------------- -----------------
begin
insert into test_job_queue values(1,'test_job_queue1');
end;
N 11-dec-2003 15:29                   11-dec-2003 15:48

Still not working.

By the way, since oracle 9i increases the job_queue_processes up to 1000 from 36
(8i), what is your general suggestion to set that value considering row-level
trigger to fire jobs to send out emails?  Right now, in our test and production
environment, we use job_queue_processes = 5, we prefer the value to be a bit
larger, but fear that will more likely to cause email jam...  What could be the
best value to strike a balance, generally-speaking?

Please advice.  Thanks for help.

          Followup:

as for the "number to have" -- that is 100% entirely up to you.  you and only
you know what your system is

a) responsble for doing
b) capable of doing

one more thing to ask

select count(*) from dba_jobs_running;

what does that show?

      Rating: 5
follow
      up  December 12, 2003
Reviewer:  RICHARD TAN
       from WASHINGTON
Hi Tom,

Thanks for help.

Here is the result.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.4.0 - Production

SQL> select count(*) from dba_jobs_running;

  COUNT(*)
----------
         0

Thanks.

          Followup:

going to refer you to support at this point in time.  sorry, nothing more i can
do here.  everything looks OK from this end so far.

      Rating: 5
Follow
      up  December 17, 2003
Reviewer:  Richard Tan
       from Washington
Hi Tom,

Thank you very much for your help,  I sincerely appreciate it.

We have contacted the oracle support, first, oracle asks us to install the
remote diagnose agent...finally,

exec dbms_ijob.set_enabled(true);

The job queue system starts to work immediately.

Thanks.

December 13, 2003 Krish -- Thanks for the question regarding
      "dbms_job", version 8.1.7

      You Asked
I am trying to queue a database job using dbms_job as following:

declare
jn binary_integer;
begin
dbms_job.SUBMIT(jn,'MYJOBS.MYPROCNAME(1234,''5678'');'
   ,sysdate + 1/1440);
-- one minute delay, execute once only,delete upon success.
dbms_output.put_line(jn);
end;

job_queue_processes is set to 2,
job_queue_interval is set to 60.

(Database is on WinNT, if is any relevant info)

The job sits in the queue as seen in USER_JOBS and never gets executed.

I expect the queue process to run the job per schedule.

How can I get it to work?

(I cannot afford to run dbms_job.run separately for it)

Thanks in advance.

Krish

      and we said...
COMMIT;

until you commit -- the data you just put into the job queue is visible only to
YOUR session.

Review &
      Followup

      Rating:
      4
dbms_job  December 28, 2001
Reviewer:
      Krish  from Nashville, TN
It worked and Thanks for the same.

      Rating: 4
Need
      calrify  February 06, 2002
Reviewer:
      A reader
Hi, Tom,

I have:
---------
function fork( procname varchar2 ) return binary_integer as
    pid binary_integer;
    nid binary_integer;
  begin
      dbms_job.submit( pid, procname, sysdate, null, false );
  1--->    dbms_job.submit(nid, 'NULL;', sysdate, null, false);
  2--->    dbms_job.run(nid);
     return pid;
 end;

where procname is mainly an "insert" statement without "commit".

I use the anonymous pl/SQL block to call the "fork"
function and simulate the multithread process in PL/SQL.

Question:
How the record commited here without any "commit" statement presented? What is
the 1 and 2 statements special meaning as shown above? Is it means if we run
another job then the formaer job will be commit automatically?

THanks

          Followup:

dbms_job.run does a commit.

...
Rem      DBMS_JOB and DBMS_IJOB are the only interface for manipulating jobs.
Rem      Queries against the catalog should be used for examining jobs.
Rem      The catalog view dba_jobs and dba_jobs_running are in catjobq.sql.
Rem      Out of all these routines, only dbms_job.run and dbms_ijob.run have
Rem        implicit commits.
Rem
.......

also, dbms_job.run is a SYNCRONOUS call -- when it returns -- the job will
already have "finished" and will have run in your session!

If you want a "fork" it would look more like:

create function fork( p_what in varchar2 ) return number
as
   pragma autonomous_transaction;
   l_job number;
begin
   dbms_job.submit( l_job, p_what );
   commit;
   return l_job;
end;

You would NOT run the job, that defeats the whole purpose.  See

http://osi.oracle.com/~tkyte/autonomous/index.html
for info on autonomous transactions.

      Rating:
      4
  February 08, 2002
Reviewer:
      Harold  from Managua, Nicaragua

      Rating: 4
DBMS_JOB and
      Global Temp Table --- no data in   January 07, 2003

Reviewer:  Robert  from PA
Tom,
I have a report st. procedure that uses Ref Cursor to pass the cursor (via OO4O)
to Excel client.

The procedure does "INSERT INTO ....SELECT" on the GTT...
While its running and it can be few minutes, that makes the Win app *appears* to
be hanging. I am thinking of submitting the procedure to DBMS_JOB to run
immediately (like your case with sending emails) but testing shows that
apparently the GTT data ends up in some other universe.

Is there a way I can make this work ? else I have to decide whether just leave
it as is or insert into a regular table.

Thanks

          Followup:

jobs run in another session, not another universe.  GTT's are local to a
session.

You would have to use a "permanent table"

      Rating: 5
schedule a
      sql*plus script  March 07, 2003
Reviewer:
      mo
Tom:

I have a sql*plus script that runs queries and spools data to several files.
Can I scedule this file to run every week by dbms_job?  If no, then do I
schedule it using unix cron or convert it to pls/sql which can not sppol data
and i have to use more complex UTL_FILE?

Thank you,

          Followup:

if you have the ability to run sqlplus from plsql -- which you may or may not --
depends if you wrote the necessary java stored procedure (search for

dbms_java host

on this site for an example).  then yes you can use dbms_job.  Else no, you
cannot.

but then again, you know ALL about dump_csv, that utility you already have that
takes a query and creates a flat file -- you know, just like spool does.  That
would work as well -- hiding that more complex utl_file from you entirely...

but yes, you can use cron as well.

      Rating:
      5
job  March 27, 2003
Reviewer:  mo

Tom:

User enters a request on a web form and it is saved into a request table.  One
column of the table "assigned" defaults to null until the shipper is ready to
ship the material. I need to send an email if the "assigned" colum stayed null
(not assigned within two days).  How would you implement this?

DO i write a trigger (what kind) that runs via a job every night to check the
"assigned" value and send an email based on that?

          Followup:

Add a column to said table -- "job_id"

In a trigger on that table -- do this:

dbms_job.submit( l_job, 'send_email(JOB);', sysdate+2 );
insert into email_parameters values ( l_job, ......... );
:new.job_id := l_job;

(read my chapter on dbms_job in "Expert one on one Oracle" to see what I'm doing
with (JOB) there)

Then, in an on update of assigned trigger have code like:

begin
dbms_job.remove( :old.job_id );
exception
  when whatever error is raised when a job doesn't exist then NULL;
end;
:new.job_id := null;

That way, when you insert, a job will be scheduled to run 2 days from now.  If
you update that row in the meanwhile, if you update the assigned field within 2
days, remove the job...

      Rating:
      5
trigger  April 28, 2003
Reviewer:
      mo
Tom:

1.  I did a before-insert trigger on the request table since that is only type
where you can access :new

create or replace trigger send_email
before insert ON SHIPM_REQUEST
FOR EACH ROW

DECLARE
  l_job                 number;
  v_data                long ;

BEGIN
 dbms_job.submit(l_job,'SEND_EMAIL_REQUEST(JOB);',sysdate+2 );

        FOR x in (select * from Request where request_id=:new.request_id)
        LOOP
        v_data := v_data||'Request ID: '||x.request_id||chr(10);
        v_data := v_data||'Request Date: '||x.request_date||chr(10);
        v_data := v_data||'Contact: '||x.contact||chr(10);
  END LOOP;

        insert into email_parameters(ID,request_id,request_date,
        email_data,request_Section)
         values
          (l_job,:new.request_id,:new.request_Date,
          v_data,:new.request_section);

         :new.job_id := l_job;

END;

The problem is that v_data is not picking up any data and the email is not sent.
Any ideas what is happening. When I default v_Data to 'test' it gets sent only
with that word.

2. On your second suggestion do you mean putting a when others exception like
this :

create or replace trigger assign_request
AFTER UPDATE ON REQUEST
FOR EACH ROW
WHEN(new.assigned_to is not null)

BEGIN
  dbms_job.remove(:old.job_id);

EXCEPTION
    WHEN OTHERS THEN
    :new.job_id:=null;
    null;
END;

3.  If I want to send an email alert if shipment was not done by required date
on the request, you would set up a before-insert trigger on request table that
runs the job on the required date and then check if there was a hispment record
or not?

Thank you,

          Followup:

1) well, there must not be ANY DATA IN THERE yet -- have you looked at your
transaction, in what order do you actually insert the data?

it really appears to me that :new.request_id is the thing you want to insert
into the email_parameters and the send_email_request routine is the one that
would run the query to collect the data to email....  just a thought... make the
trigger small, make the send email request responsible for assembling the data.

2) that does resemble my suggestion -- yes.  Ignore errors coming from
dbms_job.remote. it must already have been removed

3) whatever you need to do -- think about what is happening here.  when you
initially insert, you set up a job to run two days from now.  updates will
remove the job.  two days from now, if the job runs -- the update must not have
happened -- so the email should be sent.

      Rating: 5
email job
      trigger  April 28, 2003
Reviewer:  mo

Tom:

1.  AS a followup to #1, I did what you suggested previously where you insert
the message usually when you insert into the email_parameters table. I had an
error when i did "After-insert" because it did not realize the :new.request_id.

As far as my transaction I insert request id first then all other data.

I will try to move the select statement into the procedure that sends the email
but this way I will not be inserting the message to the email_parameter table?

          Followup:

"had an error"?  "it did not realize"?

sorry - i'm not understanding anything here.

you can use (should use) and AFTER INSERT FOR EACH ROW trigger.

You should insert the request_id.

Your email sending routine will fetch this request id and assemble all of the
other data.

      Rating:
      5
"commit"  April 29, 2003
Reviewer:
      Suresh  from India
Tom,

    Regarding the procedure that Mo has published, I do not see a "commit" in
there...Could that be the issue?

          Followup:

it was a trigger.  a commit would only make it fail.

      Rating:
      5
trigger  May 01, 2003
Reviewer:  mo

Tom:

1.  I got the following error when I tried to make it after-insert-trigger
10:43:27 AM  Error: ORA-04084: cannot change NEW values for this trigger type

this is resulting from
  :new.job_id := l_job;

I tried to change that to an update statement and then I got the mutating error
because I am updating the job id for the same table.

Any hints?

      Rating:
      5
errror  May 02, 2003
Reviewer:  mo

Tom:

1.  Do you know what may be causing this error in the trace file. Is this
something to worry about. Could be the result of scheduling more jobs than my
variable allows for (2).

/ora817/admin/ips/snp0_301110_imsd.trc
Oracle8i Enterprise Edition Release 8.1.7.2.0 - 64bit Production
With the Partitioning option
JServer Release 8.1.7.2.0 - 64bit Production
ORACLE_HOME = /ora817
System name:    AIX
Node name:    xxxx
Release:    3
Version:    4
Machine:    xxxxx
Instance name: ips
Redo thread mounted by this instance: 1
Oracle process number: 8
Unix process pid: 301133, image: oracle@xxxx (SNP0)

*** SESSION ID:(8.25046) 2003-04-28 17:42:56.725
*** 2003-04-28 17:42:56.725
ORA-12012: error on auto execute of job 26
ORA-06533: Subscript beyond count
ORA-06512: at "IPSD.SEND_EMAIL_REQUEST", line 37
ORA-06512: at line 1
*** SESSION ID:(8.25062) 2003-04-28 17:45:00.368
*** 2003-04-28 17:45:00.368
ORA-12012: error on auto execute of job 26
ORA-06533: Subscript beyond count
ORA-06512: at "IMSADMIN.SEND_EMAIL_REQUEST", line 37
ORA-06512: at line 1
*** 2003-04-28 19:03:36.206
*** SESSION ID:(8.25674) 2003-04-28 19:03:36.206
java.lang.NullPointerException: Null buffer
  at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java)
  at java.net.SocketOutputStream.write(SocketOutputStream.java)
  at oracle.plsql.net.TCPConnection.write(TCPConnection.java:202)
  at oracle.plsql.net.TCPConnection.write(TCPConnection.java:395)
*** 2003-04-28 19:03:36.700
ORA-12012: error on auto execute of job 32
ORA-29532: Java call terminated by uncaught Java exception:
java.lang.NullPointerException: Null buffer
ORA-06512: at "SYS.UTL_TCP", line 678
ORA-06512: at "SYS.UTL_TCP", line 350
ORA-06512: at "SYS.UTL_TCP", line 428
ORA-06512: at "SYS.UTL_SMTP", line 263
ORA-06512: at "IMSADMIN.MAIL_PKG", line 69
ORA-06512: at "IMSADMIN.SEND_EMAIL_REQUEST", line 57

          Followup:

is is not the number of job queue processes at all - -that is nothing to do with
it.

these all look like application issues (eg: they would happen in sqlplus if you
just ran them as well)

ORA-12012: error on auto execute of job 26
ORA-06533: Subscript beyond count
ORA-06512: at "IPSD.SEND_EMAIL_REQUEST", line 37
ORA-06512: at line 1

your plsql code went beyond the allocated collection bounds (on that line of
code)

      Rating:
      5
dbms_job  May 03, 2003
Reviewer:  mo

Tom:

WHat is allocated collection bounds in pl/sql and is this something that can be
enlarged? I was suspecting a  variable I used in the cc_parameter that when it
is null (no value) this thing happens.  Of course it could be just a coincidence
too.

          Followup:

depends. plsql table types -- +- 2 Billion.  collection types -- you tell us,
you allocate them, you extend them.  varray's have a fixed upper bound set by
you.

      Rating: 5
Are there any
      limits to the number of jobs that should have pending?  May
      04, 2003
Reviewer:  Andy McGoldrick  from Scotland

Tom,

I can see an area in a system I work on where some of the techniques above would
be very useful but have a few queries, as I think we would end up producing a
high number of jobs that may be scheduled for upto 1 year advance.

1) Is there a limit to the number of jobs you would have submitted on one of
your systems? 1000, 10000, 100000?

2) Is there a practicle limit on how far in advance you would schedule the jobs,
3 months, 6 months etc?

Thanks

Andy

          Followup:

I've done single digit thousands -- I've not done 100,000 -- so, I set it up to
see what effect it might have.

I loaded up 100,000 jobs in the job queue.  Ran statspack on an otherwise idle
system.  After about 8 minutes, the top sqls (all background sql here -- nothing
else going on) were:

<some other non-relevant background queries removed>

  Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
...
            368           92            4.0    3.0     0.00      0.03 1316169839
select job, nvl2(last_date, 1, 0) from sys.job$ where (((:1 <= n
ext_date) and (next_date < :2))    or  ((last_date is null) and
(next_date < :3))) and (field1 = :4 or (field1 = 0 and 'Y' = :5)
) and (this_date is null) order by next_date, job

            184           92            2.0    1.5     0.00      0.02 1693927332
select count(*) from sys.job$ where (next_date > sysdate) and (n
ext_date < (sysdate+5/86400))
.......

These queries -- the ones the job queues use to find out what to run -- seem to
scale rather well regardless of the number of jobs.

There is no practical limit on how far out a job is to be scheduled.

      Rating: 5
dbms
      job  May 19, 2003
Reviewer:  mo
Tom:

I have two triggers that schedule two jobs after insert of a record.  The first
one supposed to schedule the job two days after creation date and the other one
will schedule it two days after required date.  Surprisingly, one record was
created May 15 and the first job was scheduled May 19. the second trigger
schedule the job May 20 even though required date was May 19.  Does oracle skip
weekend days when scheduling. How do you explain this?

 dbms_job.submit(l_job,'SEND_EMAIL_REQUEST(JOB);',sysdate + 2);

 dbms_job.submit(l_job,'SEND_EMAIL_REQUEST_DATE(JOB);',:new.required_Date+2);

REQUEST_ID                    : 555
REQUEST_DATE                  : 15-may-2003 14:47:20
REQUIRED_DATE                 : 15-may-2003 00:00:00
CREATION_DATE                 : 15-may-2003 14:47:20

JOB                           : 59
NEXT_DATE                     : 19-may-2003 10:07:40
NEXT_SEC                      : 10:07:40
TOTAL_TIME                    : 35
BROKEN                        : N
INTERVAL                      : null
FAILURES                      : 11
WHAT                          : SEND_EMAIL_REQUEST_DATE(JOB);
NLS_ENV                       : NLS_LANGUAGE='AMERICAN' NLS_TERRITORY='AMERICA'
NLS_CURRENCY='$'
NLS_ISO_CURRENCY='AMERICA' NLS_NUMERIC_CHARACTERS='.,'
NLS_DATE_FORMAT='DD-MON-RR'
NLS_DATE_LANGUAGE='AMERICAN' NLS_SORT='BINARY'
MISC_ENV                      : 0102000000000000
INSTANCE                      : 0
-----------------

JOB                           : 60
LAST_DATE                     :
LAST_SEC                      :
THIS_DATE                     :
THIS_SEC                      :
NEXT_DATE                     : 20-may-2003 00:54:33
NEXT_SEC                      : 00:54:33
TOTAL_TIME                    : 26
BROKEN                        : N
INTERVAL                      : null
FAILURES                      : 11
WHAT                          : SEND_EMAIL_REQUEST(JOB);
NLS_ENV                       : NLS_LANGUAGE='AMERICAN' NLS_TERRITORY='AMERICA'
NLS_CURRENCY='$'
NLS_ISO_CURRENCY='AMERICA' NLS_NUMERIC_CHARACTERS='.,'
NLS_DATE_FORMAT='DD-MON-RR'
NLS_DATE_LANGUAGE='AMERICAN' NLS_SORT='BINARY'
MISC_ENV                      : 0102000000000000
INSTANCE                      : 0
-----------------

          Followup:

you must have a bug in your code.  we schedule it precisely when you tell us to.

you told us to do this on may 19th and may 20th.  period.

      Rating: 5
dbms
      job  May 19, 2003
Reviewer:  mo
Tom:

I tested the code in development instance numerous times and it works fine. I
even showed you the real record data.

The only think I suspect is that if they take the system down on Saturday or
Sunday Does oracle re-schedule those jobs or do something to it?

2.  If i want to skip Saturday and Sunday and work solely based on business days
from the scheduling would I check todays date and add days to it based on that
or there is an automatic feature in the dbms job?

Thank you,

      Rating: 3
question
      regarding book example...  May 20, 2003
Reviewer:
      Alvin  from Philippines
In your book pp 1065 the code are...

----- start -----
create or replace
PROCEDURE fast_send_mail (p_sender    IN VARCHAR2,
                          p_recipient IN VARCHAR2,
                          p_message   IN VARCHAR2)
as
        l_job   number;
begin
        dbms_job.submit( l_job, 'background_send_mail( JOB );' );
        insert into send_mail_data
        ( id, sender, recipient, message )
        values
        ( l_job, p_sender, p_recipient, p_message );
end;

-----End-----

I tried hard finding the declaration of JOB (supposedly a constant?) that is
found inside

   dbms_job.submit( l_job, 'background_send_mail( JOB );' );

will dbms_job.sumbit automatically send it as 'background_send_mail( l_job );'
???

          Followup:

read page 1061 -  i go through it all there.

      Rating: 5
very useful -
      question on job and cpu consumption  June 16, 2003

Reviewer:  A reader
Hi Tom
"I've done single digit thousands -- I've not done 100,000 -- so, I set it up to

see what effect it might have.

I loaded up 100,000 jobs in the job queue.  Ran statspack on an otherwise idle
system.  After about 8 minutes, the top sqls (all background sql here -- nothing

else going on) were:
"

In the above reply, you suggest that jobs scale pretty well.
One of the arguments I hear is " dbms job takes a long time and consumes much
cpu."

I am not sure how to verify this claim
because the argument does not compare it with anything else. The argument is
supporting another
argument that is in favor of moving some of the
code that is in the database to middle tier based
on following assumptions:

a.  jobs are cpu intensive - if we remove some of the
   functionality from jobs to middle tier we can
   employ cheaper boxes instead of using more powerful
   databases (Which could be costlier)
b.  The database mechanism (DBMS_JOBS) is fairly
    inefficient compared to Java threading when it comes
    to short interval polling.

How can I verify whether these claims are true? How
would you benchmark and verify or refute these claims

Thanx a lot - please keep up the good work!

          Followup:

dbms_job takes virtually no time.

the JOBS THAT YOU CODE MAY WELL.

jobs by itself -- noise.
jobs written by you -- could be really slow, you can do whatever you like.

a) jobs are not cpu intensive.  poorly written code well can be.
b) give me a break.

You will never convince some people, they have java on the brain. "java good,
java great, write it in java and it'll just be so cool, so good.  java solves
world hunger, stops war, brings about good will towards all people......"

      Rating:
      3
  June 28, 2003
Reviewer:  j.

taken from your book as for jobs:

<quote>
they run in the same environment (user, characterset, and so on) they were
submitted in (minus roles).
</quote>

but: jobs always get NULL back from SYS_CONTEXT('USERENV', 'OS_USER') - at least
at my local instance.

is this something one can rely on in general to let a certain piece of code
identify whether it is executed via by a DBMS job or just the "normal" way?
or if not: what is the best solution to achieve this?

          Followup:

the OS user would be the oracle software owner, it would be constant.  the os
user is "gone" by then.

have you looked at fg_job_id with sys_context?

ops$tkyte@ORA920> create table t ( msg varchar2(50) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace procedure fg_job_id( p_text in varchar2 )
  2  as
  3  begin
  4          insert into t select p_text || sys_context( 'userenv', 'fg_job_id'
) from dual;
  5  end;
  6  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec fg_job_id( 'in plus: ' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> variable n number
ops$tkyte@ORA920> exec dbms_job.submit( :n, 'fg_job_id( ''in job using run: ''
);' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec dbms_job.run(:n);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> print n

         N
----------
    100687

ops$tkyte@ORA920> exec dbms_job.submit( :n, 'fg_job_id( ''in job normal: '' );'
);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> print n

         N
----------
    100688

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920> exec dbms_lock.sleep(30);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from t;

MSG
--------------------------------------------------
in plus: 0
in job using run: 100687
in job normal: 100688

      Rating: 5
didn't even
      know about that ...  June 29, 2003
Reviewer:
      j.
thank you, tom, for the hint and your sample.

now I've checked the documentation for "FG_JOB_ID":

<quote>
Job ID of the current session if it was established by a client foreground
process. Null if the session was not established by a foreground process.
</quote>

I've to admit I didn't really get it from there: I did NOT expect the condition
"if it was established by a client FOREground process" to hold true for sessions
"doing the work" on behalf of DBMS jobs.
I thought: jobs are "established" by client FOREground processes, but the
sessions for those jobs get "established" by a BACKground process (processsing
the DBMS jobs).

looking at your real world test case one would come to the conclusion:
SYS_CONTEXT('USERENV', 'FG_JOB_ID') returns the ID of the DBMS job (if any)
related to the current session or 0 otherwise. is that right?

is the documentation wrong? at least SYS_CONTEXT('USERENV', 'FG_JOB_ID') returns
0 instead of NULL from within the current session.

and why doesn't SYS_CONTEXT('USERENV', OS_USER') return the ORACLE software
owner on my LOCAL database?

          Followup:

the documentation is wrong on the point about nullness yes.

I cannot answer why on the OS_USER, I would suppose because it is an internal
session of sorts, not done in the normal fashion.

      Rating: 4
why the job is
      not executing ????  October 07, 2003
Reviewer:
      Deepak Gupta  from Delhi, India
Dear Mr. Tom ,

 I submit job queue following way :

o/s : Window  NT 4.0
Oracle8 Release 8.0.4.0.0 - Production

variable  l_job number
declare
   l_job number;
begin
    dbms_job.submit( l_job,
                     'BEGIN tuning; END;',
                       trunc(sysdate)+12.25/24,
                      'trunc(sysdate)+12.25/24',FALSE);
commit;
end;

sql>alter session set nls_date_format='dd-mm-yy:hh:mi:ss'

SQL> select job,
  2  NEXT_DATE ,
  3  NEXT_SEC ,
  4  TOTAL_TIME,
  5  BROKEN  ,
  6  INTERVAL ,
  7  FAILURES
  8  from dba_jobs
  9  /

      JOB NEXT_DATE         NEXT_SEC TOTAL_TIME B INTERVAL

--------- -------------- -------- ---------- - ------------
        1 09-03-99:12:46:01 12:46:01  -3.09E+09 N sysdate+1

 -->   87 07-10-03:12:15:00 12:15:00          0 N trunc(sysdate)+12.25/24
       86 07-10-03:04:30:00 16:30:00          3 N trunc(sysdate)+16.5/24

       88 07-10-03:01:15:00 13:15:00          0 N trunc(sysdate)+13.25/24

in the alert.ora file the following error message appearing
++++++++++++++++
Tue Oct 07 12:18:18 2003
Errors in file M:\orant\RDBMS80\trace\orclSNP1.TRC:
ORA-12012: error on auto execute of job 87
ORA-12005: may not schedule automatic refresh for times in the past

1. why is there different between next_Date, next_sec time  ?
2. what is reason that above error message is coming or  why job is not
executing  ?

Thanks+Regards

          Followup:

ok, look at what you've done here.

your first run time is

trunc(sysdate)+12.25/24  -- or "12:15pm today"

that job runs and the job queue system says "ok, lets evaluate the next date".
that too is trunc(sysdate)+12.25/24 -- again, 12:15pm today.  Well, by the time
the job runs -- it is AFTER 12:15pm today -- so your next date evaluates to a
date in the PAST.  and that is not allowed.

did you mean to say that next date should be

trunc(sysdate+1)+12.25/24

or "12:15pm TOMORROW"??

      Rating: 3
How to run a
      script in background?  December 10, 2003
Reviewer:
      Martin van Donselaar  from The Netherlands
Tom,

what i'm looking for is how to run a script on the server
so that I can close my client without stopping the executing of the script. Is
it possible to do this from a sqlplus client connection somehow? I searched your
site for a while but couldn't find what i need.

          Followup:

you sort of have to log INTO the server to run a script ON the server

if you want to run a plsql routine in the background:

variable n number
exec dbms_job.submit( :n, 'storedprocedure;' );
exit;

if you want to run a script on the server, log into the server and use whatever
'background' facilities it has (eg: & on unix, start on windoze)

      Rating: 4
May be a dumb
      question but.....  December 12, 2003
Reviewer:
      A reader
Does dbms_job scheduled jobs commit automatically or we have to include commit
explicitly in the job?

          Followup:

dbms_job does NOT autocommit on submit (run does, not submit)

so, after you submit, the job will appear in YOUR session but will not be
visible to the job queue processes until your session commits the transaction
that created the job.

December 13, 2003 Balaji -- Thanks for the question regarding
      "Calculating Size of SGA", version 8.0.5

      You Asked
How do I calculate the size of the SGA ?

Where do I find the High Water Mark ?

      and we said...
Well, the easiest method to get the size of an SGA is to issue
"show sga" in svrmgrl.

You'll get a display like:

SVRMGR> show sga
Total System Global Area                         93248912 bytes
Fixed Size                                          64912 bytes
Variable Size                                    66887680 bytes
Database Buffers                                 26214400 bytes
Redo Buffers                                        81920 bytes

To approximate the size of the SGA (Shared Global Area), use the
following  formula:

 ( (db_block_buffers * block size) +
           sum( *_pool_size ) + log_buffers) /.9

where *_pool_size is the java_pool_size, large_pool_size, and
shared_pool_size.  (java_pool_size is for 8.1 and up only)

The division by .9 is used to take into account the variable
portion of  the SGA -- this is only an approximation of the
actual value, it will never be dead on.   The only way to get
the exact size is show sga (or other queries against the v$
tables).

See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:231414051079,%7BHWM%7Dfor
an example that shows lots of space details about
tables -- including the HWM.

Review &
      Followup

      Rating: 4
Optimum SGA
      size ??  August 07, 2002
Reviewer:  Rahul
       from India
Hi Tom

I have run a script which I downloded from Oracle Metalink
, which shows the SGA usage at the peak time of working.
I run the script at peak working hour and got the following output...

DOC>*********************************************************
DOC>*                                                       *
DOC>* TITLE        : Shared Pool Estimation                 *
DOC>* CATEGORY     : Information, Utility                   *
DOC>* SUBJECT AREA : Shared Pool                            *
DOC>* DESCRIPTION  : Estimates shared pool utilization      *
DOC>*  based on current database usage. This should be      *
DOC>*  run during peak operation, after all stored          *
DOC>*  objects i.e. packages, views have been loaded.       *
DOC>*                                                       *
DOC>*                                                       *
DOC>********************************************************/
Obj mem:  6373189 bytes
Shared sql:  3234215 bytes
Cursors:  430625 bytes
Free memory: 3831272 bytes (3.65MB)
Shared pool utilization (total):  12045635 bytes (11.49MB)
Shared pool allocation (actual):  15728640
bytes (15MB)
Percentage Utilized:  77%

PL/SQL procedure successfully completed.

As you can see at the last Percentage utilizied is 77%.
So I want to know that is it ok which setting I have in my database for memory
parameters or should I increase the values for that parameters and also suggest
me the name of parameters.
Can you tell me that is there any fix rule for SGA to be sized according to the
usage which I shown you above?

Please tell me how to size the SGA with optimum setting.

Thanks alot in Advance.

          Followup:

Insufficient data to say anything about anything.

what is your shared_pool_reserved?  (i would set to zero or very very small).
good chance that it is the other 23% of the shared pool and YOU'LL NEVER USE IT.

So, you might actually be 100% utilized and swapping stuff in and out like
crazy.  Look at reloads in v$librarycache and see if they are high.  A 15m
shared pool is pretty small

      Rating: 4
recommended
      shared pool size  April 08, 2003
Reviewer:
      reader
tom,

i had a look at metalink - Doc ID:  Note:61340.1 Subject:  V7: Calculating the
Size of the Shared Pool - Do you recommmend the below formula

Formula
-------

Max Session Memory * No. of concurrent users

+ Total Shared SQL Area Usage

+ PLSQL Sharable Memory

+ Minimum 30% Free Space
----------------------------------------------
= Minimum Allowable Shared Pool

the note says :
While analyzing shared pool sizing, it is helpful to first increase the shared
pool to a very large value, so that the dynamically allocated SGA structures may
be allowed to expand to a desirable size.  Once this sizing exercise has been
completed, the shared pool may be downsized to the appropriate value.

What is Minimum 30% Free Space ? - how to calculate this ?

Please share your thoughts on this.

Thanks

          Followup:

V7 -- sort of old...

session memory should never be in the shared pool in v8 and up -- it'll be in
the large pool.

30% free?  way too much 10% or even less is dandy.  If you are constantly at
100% and have alot of hard parses -- it's too small.

read up on statspack for a tool to monitor what is going on.

      Rating:
      1
  August 20, 2003
Reviewer:
      A reader
Tom,
stats pack etc can be used once the system is either in end of testing like
performing regression testing etc or maybe when the system actually goes live,
In a real life business scenario before we start a application we have to give
certain hardware and software requirements which can be a little volatile, in
order to provide this estimate we should be able make a informed decision about
the sgA, harware RAM based on some parameters like 1)number of Users, O/S,Number
of transactions,Kind of appliction etc,
i think based on this you should probably define some thumb rules which should
be used to decide on things like size of SGA, CPU utilization etc,
These things will be a boon to ppl trying to promote Oracle as the database for
the application.
I am writing this out of personal experience where i hv found lack of
information in this areas,

          Followup:

you can certainly SWAG but only a benchmark will be real.

swags?

2/3 meg per conncurrent user
80 users per some sort of cpu (YMMVW) your mileage may vary WIDELY.
risc/cisc/1mhz/3mhz/etc etc etc etc etc etc etc

SGA - no way, not doable until you define what/how the app will do what/how it
does.

So, based on your personal experience - what "swags" do you have?  then we will
proceed to "make them not look reasonable for our system".

      Rating:
      3
  August 21, 2003
Reviewer:  Anthony
      Reddy Thumma  from KL, Malaysia

      Rating: 5
how to estimate
      shared pool size  December 06, 2003
Reviewer:
      A reader
Hi

From Metalink they say to estimate shared pool you have to put your application
in production first then just sumthe values in some v$ views.

I dont think this is very professional way! How are you going to tell the
customer "Sir we need to go to production first then tune from there" doh

          Followup:

you never tested your application before selling to the customer?  not very
professional.  doh.

you discover what you need based on many factors, how well (or not) your
application is written, what features you use, how many different sql's you
have, their complexity.

You discover, during your load tests (which of course, you do BEFORE shipping to
your customers right???  I mean, before you inflict your application on them,
you've actually tested it for functionality, load, and scale -- right?) what you
really need.

you can start with initial swags.

In 10g, you can even just say "make the sga so large" and forget about the rest
of it (we'll size/resize the components as needed).

I cannot imagine telling the customer "sir, we've never tested our application
under load, with your sort of user configuration, so we are going to use you as
the guinea pig and see how it goes -- ok?"....

I think the metalink note says "test your application, then use these metrics to
size with", not "inflict your application on your end users, let it fall over
and then figure out how to fix it"

      Rating: 4
regarding
      Estimate Shared Pool script  December 12, 2003

Reviewer:  James Blanding  from Kingston, RI USA

I downloaded the script referred to by the second poster from metalink (I
think), Doc ID 30487.1.  It calculates the amount of used space in the shared
pool in bytes with the following formula:

-- For non-MTS add up object, shared sql, cursors and 30% overhead.
used_pool_size := round(1.3*(object_mem+shared_sql+cursor_mem));

It then goes on to divide that number by the number from the shared_pool_size
parameter to produce a "Percentage Utilized".  Where does the 30% overhead come
from?  I have seen the utilization go over 100% briefly, though not over
130%...does this suggest that the overhead is really included in the shared pool
itself?  Ah confusion... :/

Your site is proving to be an excellent resource.  I'm adding your books to my
Christmas list...

          Followup:

they are just guestimating -- rounding up -- the amount of memory really used.
30% for "overhead" -- data structures, other supporting structures we maintain
to manage this data in the shared pool

December 13, 2003 Satish -- Thanks for the question regarding
      "Storing Word Documents", version 9.0.1.3

      You Asked
Hi Tom,
We need to develop an whereby we require to store word documents in the
database(CLOB fields I guess) and have them indexed(using Intermedia Text ?)
 We also need able to have make them searchable i.e. the user able retreive the
documents using  key words.
The users would need to have this through a web-based application. (I would
prefer using the pl/sql module of 9iAS if possible)

So Tom, I would really appreciate,if you could help me identify the various
pieces , pitfalls and provide some examples or a pointer to documents whereby I
would be able to get an idea about how to proceed. I've not been able to see an
clear example of how to store a word document in a CLOB field and retreive it.

Thanks Tom

Regards
Satish

      and we said...
Well, it would be BLOB fields and it would be Oracle Text (that is the name for
interMedia in 9i)

This documents how to do file upload and download with 9iAS mod_plsql:

http://technet.oracle.com/docs/products/ias/doc_library/90200doc_otn/web.902/a90855/feature.htm#1005870

So, your users will upload a document, you'll have a Text index on this table
they upload into, you can search on that using the contains clause and then they
download what they want.

Alternatively, you can just use portal -- it does this wihtout having to code
anything.

Review &
      Followup

      Rating: 4
Excellent
      Response  June 30, 2002
Reviewer:  Satish
       from Seattle , WA
Thanks Tom, following the lead you gave and with some more research I am almost
ready with a proof of concept to present to our management. Hats off to you for
providing such a valuable service to the Oracle Community.

      Rating: 3
Genrate
      letters  September 21, 2003
Reviewer:
      Trevor
Tom,

Do you know of any examples that show
storing MS word templates in Oracle DB
and using customer details from same DB
generate letters using templates?

          Followup:

storing -- easy, that is just a blob

search for

load lobs

for lots of examples -- read also:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-APP
the application developers guide for LOBS

now, the merge, that is something done on the client if you want to use word as
word is not a "server" thing.

(me, i would use HTML -- you have the formatting you need and it is a bit more
"open" and flexible and can be done on the server)

      Rating:
      5
  September 22, 2003
Reviewer:
      Trevor
Thanks Tom...

I maybe be forced to use Word, but I like the
HTML suggestion... nice and easy to spit out
via a stored proc etc

          Followup:

(and openable in word!)

      Rating:
      4
  December 12, 2003
Reviewer:
      A reader  from CA, USA
Hi Tom,

We have an application where in we would like to provide the end-users the
facility to upload any document. The uploaded document needs to be stored in our
database - so that the end users can refer back to it.

I tried clicking through the links mentioned in this article, but was unable to
find any information as to how should I resolve this issue. Can you please
provide the new links so that I can read through.

          Followup:

http://otn.oracle.com/pls/db92/db92.docindex?remark=homepage#index-APP
for the application developers guides...

http://download-west.oracle.com/docs/cd/A97329_03/web.902/a90855/feature.htm#1005870
for file upload/download with mod_plsql

December 13, 2003 Bhavani -- Thanks for the question regarding
      "Bind Variables", version 8.1.6

      You Asked
Hi Tom,

I tried to use Bind Variables in my Script.  But that script is taking more time
than expected.  Please have a look at the below script.

area@DBWH> truncate table t1;

Table truncated.

Elapsed: 00:00:00.50
area@DBWH> declare
  2  begin
  3   for i in 1..1000000
  4   loop
  5    insert into t1 values(i);
  6   end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

Elapsed: 00:10:625.30
area@DBWH> truncate table t1;

Table truncated.

Elapsed: 00:00:07.70
area@DBWH> declare
  2
  3  begin
  4   for i in 1..1000000
  5   loop
  6    execute immediate 'insert into t1 values(:x)' using i;
  7   end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

Elapsed: 00:12:731.50
area@DBWH>

If I use Bind variables it is taking, 00:12:731.50 whereas without bind
variables it is taking 00:10:625.30.

Can you please explain where I am wrong?  Thanks for your time.

Bhavani

      and we said...
hey -- it is a TRICK question.

why?  Because BOTH are using bind variables.  What you have just proven is that
static SQL in plsql is much much more efficient then using dynamic sql to do the
SAME!!!

The beauty, they sheer beauty of PLSQL is that the things you have to be
concerned with in other languages like

o parse once -- execute many
o use bind variables

is done AUTOMAGICALLY and TRANSPARENTLY.  let me demonstrate with your example
using sql_trace and tkprof.  I run:

ops$tkyte@ORA920> create table t ( x int );

Table created.

ops$tkyte@ORA920> set timing on
ops$tkyte@ORA920> alter session set sql_trace=true;

Session altered.

Elapsed: 00:00:00.06
ops$tkyte@ORA920> begin
  2          for i in 1 .. 100000
  3          loop
  4                  insert /* STATIC SQL */ into t values ( i );
  5          end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:13.52
ops$tkyte@ORA920> begin
  2          for i in 1 .. 100000
  3          loop
  4                  execute immediate
  5                  'insert /* DYNAMIC SQL */ into t values ( :i )'
  6                   using i;
  7          end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:37.84

now that is interesting but TKPROF is fascinating:

INSERT /* STATIC SQL */ into t values ( :b1 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          1          0           0
Execute 100000      6.56       7.12          0        260     103411      100000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   100001      6.57       7.12          0        261     103411      100000
********************************************************************************

not that our query was rewritten.  ALL PLSQL VARIABLES IN SQL ARE BINDS!!!
Look at the parse to execute numbers there -- parse once, execute many, use
binds.  All done, all magic, all transparent.

Looking at dynamic sql:

insert /* DYNAMIC SQL */ into t values ( :i )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse   100000      4.56       4.40          0          0          0           0
Execute 100000     10.97      11.04          0     100168     302764      100000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   200000     15.53      15.44          0     100168     302764      100000

look at that -- terrible.  parse counts like crazy.  way over parsed.  at
least we bound -- but the price we paid for parsing is extremely high.  They
were soft parses but a parse is a parse and something to be avoided when
possible.

Review &
      Followup

      Rating:
      5
tkprof  February 13, 2003
Reviewer:
      mo
Tom:

I replicated your examples and set sql_trace=true but I do not see the tkprof
output as you show. Any ideas why?

Thanks

          Followup:

umm, did you run tkprof -- the command line tool that turns the trace file that
was generated in the user dump destination on the server into a readable file?

see chapter 10 of "the book".  I go into how to use tkprof in depth.

      Rating: 4
For
      Bhavani  February 13, 2003
Reviewer:  Paul
       from UK
If you really want to see the effect of not using bind variables use this:

begin
 for i in 1..1000000 loop
  execute immediate 'insert into t1 values('||i||')';
  end loop;
 end;
/

Unfortunately, I don't have the time (unlike mo, apparently ;-))

      Rating: 2
Bind Variable
      Peeking feature  May 21, 2003
Reviewer:  Vikas
       from INDIA
Hi Tom,

Can you please explain me the concept of Peeking of User-Defined Bind Variables
which is a new feature for 9.0.1

It would be nice if you can come up with a demo example.

Thanks
Vikas

          Followup:

basically -- it means when the query is first hard parsed, the optimizer
will peek at the binds in order to determine how to optimize the query.  Stress
the word hard there, it is not every parse, only on the hard parse.

So, setup a test like this:

drop table t;

create table t
as
select 1 id, a.* from all_objects a;

update t set id = 99 where rownum = 1;

create index t_idx on t(id);

analyze table t compute statistics
for table
for all indexes
for all indexed columns size 2;

massively skewed data.  index on t(id) should be used if id is NOT 1... So, we
run these queries against this data.  Note that we run 2 queries -- and each one
twice.  the one tagged N_WAS_1 is hard parsed with the bind set to the value of
1.  the one taggedt with N_WAS_99 is hard parsed with the bind set to the value
of 99

variable n number;

exec :n := 1;

alter session set sql_trace=true;
set termout off

select * from t n_was_1 where id = :n;

exec :n := 99;

select * from t n_was_99 where id = :n;

and now we execute (using soft parses) the SAME queries -- but with the inputs
flipped flopped:

select * from t n_was_1 where id = :n;

exec :n := 1;

select * from t n_was_99 where id = :n;

exit

Now, I ran tkprof with AGGREGATE=NO so we would see each of the 2 queries 2
times:

select * from t n_was_1 where id = :n

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     2004      0.41       0.39        124       2404          0       30045
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2006      0.41       0.39        124       2404          0       30045

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 126

Rows     Row Source Operation
-------  ---------------------------------------------------
  30045  TABLE ACCESS FULL T (cr=2404 r=124 w=0 time=174004 us)

That is what you expect when the bind was set to 1 during the hard parse.  A
full tablescan.  On the other hand:

********************************************************************************
select * from t n_was_99 where id = :n

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          3          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          3          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 126

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID T (cr=3 r=0 w=0 time=116 us)
      1   INDEX RANGE SCAN T_IDX (cr=2 r=0 w=0 time=73 us)(object id 35628)

that is the plan you expect if the bind is 99 -- index range scan.  But watch
out:

********************************************************************************
select * from t n_was_1 where id = :n

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.01       0.01        124        428          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.01        124        428          0           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 126

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS FULL T (cr=428 r=124 w=0 time=19046 us)

here the bind variable is in fact 99 if you remember from the test case -- we
can see that by the very number of rows.  The PLAN WAS FIXED DURING THE HARD
PARSE BASED ON THE INITIAL INPUTS

********************************************************************************
select * from t n_was_99 where id = :n

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     2004      0.60       0.58        206       4445          0       30045
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2006      0.60       0.58        206       4445          0       30045

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 126

Rows     Row Source Operation
-------  ---------------------------------------------------
  30045  TABLE ACCESS BY INDEX ROWID OBJ#(35627) (cr=4445 r=206 w=0 time=338282
us)
  30045   INDEX RANGE SCAN OBJ#(35628) (cr=2059 r=0 w=0 time=154266 us)(object
id 35628)

same here -- now the index range scan isn't appropriate

So where does this come into play, when is this a "good thing" (this example
shows it can be a confusing thing.  Just think, you start up on monday morning
and the first query you submit uses a bind of :N := 1, full scan plan that week.
 You start up next monday and the first query you submit using a bind of :N :=
99 -- index range scan plan that week).

Well, suppose you use LIKE lots -- this could be very beneficial.  Your
application always uses LIKE without leading '%' -- meaning an index would be
helpful generally.  Here, the optimizer will parse

select * from t where t like :x

as if :x had a value, was a literal.  That would lead it towards the index.
Consider:

create table t
as
select a.* from all_objects a;
create index t_idx on t(object_name);

analyze table t compute statistics
for table for all indexes for all indexed columns
/

variable x varchar2(50)
@trace
set termout off
exec :x := '%';
select * from t x_was_percent where object_name like :x;
exec :x := 'Y%';
select * from t x_was_NOT_PCT where object_name like :x;
exit

tkprof shows us this time:

select *
from
 t x_was_percent where object_name like :x

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     2005      0.41       0.41        182       2391          0       30046
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2007      0.41       0.41        182       2391          0       30046

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 126

Rows     Row Source Operation
-------  ---------------------------------------------------
  30046  TABLE ACCESS FULL T (cr=2391 r=182 w=0 time=189486 us)

select *
from
 t x_was_NOT_PCT where object_name like :x

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          2          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          2          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 126

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS BY INDEX ROWID OBJ#(35635) (cr=2 r=0 w=0 time=70 us)
      0   INDEX RANGE SCAN OBJ#(35636) (cr=2 r=0 w=0 time=64 us)(object id
35636)

but since you never parse with :x := '%' and the index should be used here,
this bind variable "peeking" works to your advantage

      Rating:
      4
  May 21, 2003
Reviewer:  Sagi
       from India
Hi Tom,

In the demo for the reply to Bhavani's question, you said

"They were soft parses but a parse is a parse and something to be avoided when
possible."

So the parses in the output of tkprof is soft parses? If yes then how do we see
hard parses out of the tkprof.

It would be great, if you could give a demo with tkprof output that explains
soft and hard parses.

Regards,
Sagi.

          Followup:

I covered tkprof is great detail in my book "Expert one on one Oracle" but in a
tkprof -- it is trivial to see the hard/soft parses.

you will see a parse count (soft+hard)

you will see a "misses in library cache" and this will generally be 0 or 1.  If
it is 0, all of the parses were soft.  If it is not zero, that is the number of
hard parses it underwent.

      Rating: 4
"Bind
      Variables", version 8.1.6  May 21, 2003
Reviewer:
      Raj
Absolutely proven!

      Rating:
      4
  May 21, 2003
Reviewer:  Sagi
       from India
Thanx tom.

As usual you are very great.

Regards,
Sagi.

      Rating: 5
How come my
      explain plans are different ?  October 16, 2003

Reviewer:  Ma$e
Hi Tom:

I have a few Questions w.r.t to the explain plans generated by the tkprof.

How come your sample displays more info in the row source section than what I
get ? And what do those numbers mean ?

Also: How come I don't see the "row source" section for the last sql statement ?

What is the difference between the explain plans in the  "row source operation"
and the "explain plan section".
Which one should we use to determine the explain plan used at run-time.

I'm using V 9.2.0.1.0 EE

Here are my results

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 82  (SCOTT)
********************************************************************************

select *
from
 t n_was_1 where id = :n

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     2065      0.25       0.55        180       2471          0       30956
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2067      0.25       0.55        180       2471          0       30956

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 82  (SCOTT)

Rows     Row Source Operation
-------  ---------------------------------------------------
  30956  TABLE ACCESS FULL T

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
  30956   TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'T'

********************************************************************************
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 82  (SCOTT)
********************************************************************************

select *
from
 t n_was_99 where id = :n

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          3          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          3          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 82  (SCOTT)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID T
      1   INDEX RANGE SCAN T_IDX (object id 34487)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'T'

********************************************************************************

select *
from
 t n_was_1 where id = :n

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.17        163        440          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.17        163        440          0           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 82  (SCOTT)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS FULL T

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'T'

********************************************************************************
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 82  (SCOTT)
********************************************************************************

select *
from
 t n_was_99 where id = :n

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     2065      0.36       3.05        179       4593          0       30956
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2067      0.36       3.05        179       4593          0       30956

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 82  (SCOTT)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'T'

********************************************************************************

Thanks
Ma$e

          Followup:

9202 added:

INDEX RANGE SCAN OBJ#(35636) (cr=2 r=0 w=0 time=64 us)

which shows the consistent reads (2), physical reads (0), physical writes (0)
and time (65) millionths of a second.  so, in 9202, you'll start seeing this
additional, useful data.

As for not seeing it for the last statement -- the STAT records (with the
rowsources) are only put into the trace file when the cursors are closed -- so
you must not have exited sqlplus or whatever tool you were using, the last
cursor, still open.

I would suggest NOT USING explain=u/p with tkprof.  It

a) makes it run lots slower
b) shows the plan that would be used in the environment that tkprof had when it
connected

that last point means that if the application had some some alter session or
used a different schema, or if some other index existed at runtime or if stats
had been gathered -- whatever, the EXPLAIN PLAN will be not what was used -- but
rather what would be used right now, in that session.

just don't use explain= anymore.

      Rating: 5
Excellent...I
      guess a good excuse to upgrade the test server  October 17,
      2003
Reviewer:  Ma$e
Hi Tom:

Just curious. Is this because the tkprof exposes this additional information in
the report or is it now being collected by the trace utility in 9.2.2 ?

Thanks for your response.

Ma$e

          Followup:

tkprof is just reporting whats in the trace file:

STAT #1 id=1 cnt=1 pid=0 pos=1 obj=222 op='TABLE ACCESS FULL OBJ#(222) (cr=3 r=0
w=0 time=103 us)'

thats the stat record from a 9203 trace -- the cr/r/w/time wont be there before.

      Rating: 5
Binds not good
      for predicates on columns with skewed data  October 17, 2003

Reviewer:  Ma$e
Hi Tom:

Thanks..

Ma$e

          Followup:

let me phrase that differently.

binds -- mandatory on systems that parse many queries/second (transactional, no
full scans going on there)

binds -- not mandatory on systems that have many seconds between parses (data
warehouses, queries run for minutes or hours)

forget skewed for a moment -- the type of system will dictate it.  why?  you
will either ALWAYS use an index or NEVER use an index in the system that parses
queries/second (you are transactional there, everything should be short).

      Rating: 5
Following up on
      the original answer  October 17, 2003
Reviewer:
      Meyer  from Baltimore, MD
Think about a standard lookup on table with a
fname varchar2(50) column.

IN pl/sql, when you need to use a parameter passed into the procedure with
wildcards "%" for starts with, ends with, or contains search against the
varchar2 column.

1)Does the wildcard affect parsing and if yes how, do you ever consider instr?
What is your preferred method to code this basic thing.

2)Do the benefits of not parsing change w/ html-db regions w
(:F4950_P9_DISPLAYID) vs pl/sql packages and standard local variables. In
html-db I am talking about a sql-query regions or pl/sql-returning-sql-query
regions.

3)In your books in pl/sql you sometimes use format ":name" with "using"...is
this for example or are do you get a benefit that you would not get using a
standard local pl/sql variable that turns into a bind?

Thanks,
Meyer

          Followup:

1) a %...% search on a varchar2 field with like is pretty much going to be "full
scan city"

instr = full scan city as well.

Now, if it was a sentence in a varchar2 field and you were looking for a WORD in
the sentence -- I would

create index t_idx on t(fname) indextype is ctxsys.context;

a Text index.  then I can "where contains( fname, l_word ) > 0" rather then
where fname like l_word;

2) when you have SQL in PLSQL -- all plsql variables ARE bind variables.  It is
the same either way -- both are bound into the statement.

3) when doing dynamic sql:

   execute immediate 'update t set c = :name' using l_variable;

it is the only way to use binds with dynamic sql.  I only "sometimes" use that
construct when doing dynamic sql and I want to bind.

      Rating: 5
Thank
      You  October 17, 2003
Reviewer:  Meyer
       from Baltimore, MD

      Rating: 4
Why was peeking
      introduced in the first place  October 29, 2003

Reviewer:  Bala  from Kuala Lumpur
Tom,
I don't really understand how Peeking really helps queries chose the best plan?
as you have demonstrated above it could work the other way round many times.
When we really need to run a query that needs to use histograms(typical dw
queries) we could have used hard variables(where execution time is many many
times the parsing or optimization time). I still can not make out how it helps.
And one last thing, how can we switch off peeking in 9i?

          Followup:

in a DW, you would generally not use binds since you run few queries that take
long times.

in OLTP, you would always use binds since you run tons of queries, all that
should complete very quickly, tons of times.

bind variable peeking is for OLTP, not for DW.  You cannot switch it off -- you
do not want to switch it off (to what end would switching it off be?).  For the
environment it was designed for, it does its job.  the concept is -- you hard
parse a query for the first time with some binds.  in OLTP those binds are
representative for the system - that query should (or should not) use index "X".
 This will make it so.  That query will use index "X" all of the time.
Elsewhere in your system is a similar query -- it is hard parsed for the first
time with its representative inputs and could come to a different (yet correct)
query plan since the inputs are "different"

      Rating: 5
Confused with
      your response.  November 01, 2003
Reviewer:
      Ma$e
Hi Tom:

Now I'm really confused.

In your response:

"...you start up on monday morning and the first query you submit uses a bind of
:N := 1, full scan plan that week.
 You start up next monday and the first query you submit using a bind of :N :=
99 -- index range scan plan that week)."

This could spell disaster for our OLTP application where we have used bind vars
and the data is skewed and if the first SQL that executes is resolved to using a
plan that does not bode well with the next execution of the same query with
different values for the bind vars.

Later on you comment:
"binds -- mandatory on systems that parse many queries/second (transactional, no
full scans going on there)

binds -- not mandatory on systems that have many seconds between parses (data
warehouses, queries run for minutes or hours)

forget skewed for a moment -- the type of system will dictate it.  why?  you
will either ALWAYS use an index or NEVER use an index in the system that parses
queries/second (you are transactional there, everything should be short)."

I'm having problem digesting the last part of the above response "ALWAYS .... or
NEVER". But in your example of bind var peeking you proved that you want to
encourage use indexes sometimes and fts other times.

Can you please clarify my confusion or lack of understanding.

Thanks
Ma$e

          Followup:

well, it won't in reality -- will it.

You see -- your OLTP application, it will NEVER NEVER use ":n = 1" will it?  (if
it did, it would be the kiss of death for it would it not?)

You see -- your application is going to run a query at "point X in the code".
THAT query should either

a) use an index or
b) not use an index

each and every time at that point in the code.  Bind variable peeking is the
tool that permits this. Without bind variable peeking the query would always
either full scan or index range scan regardless of where in the code it was!
With bind variable peeking -- the query will be able to use one or the other
plan -- depending on WHO (what part of the code) is executing it.

This is really what you want.  Really -- especially -- ESPECIALLY in oltp!
Without bind variable peeking, skewed data would almost certainly imply "full
scan" as the number of distinct values would be insufifcient for the optimizer
to range scan.  Only by looking at representative inputs for your query -- based
on where in the code it executes -- will you get the right answer!

That is what I meant by that always or never.  If you have a piece of code in
your OLTP system that one time the query is executed uses an index range scan
(few rows) and then later would want to full scan (tons of rows) -- you have
totally unpredicable response patterns/usage there.  Some users get immediate
responses -- others get responsed in minutes.  Something really wrong there --
therefore it would either ALWAYS range scan there (interactive) or ALWAYS full
scan (batch report).

      Rating: 4
Beginning to
      make some sense....  November 03, 2003
Reviewer:
      Ma$e
Thanks Tom.

It seems I am having a total eclipse in comprehension.

"each and every time at that point in the code.  Bind variable peeking is the
tool that permits this. Without bind variable peeking the query would always
either full scan or index range scan regardless of where in the code it was!"

But I thought that the peeking is only performed when the query is hard parsed.
I think we agree on that.

Also: Since the app will always generate the same sql syntax at the same point
in the code (irrespective of who ran the code and how it got there) except for
the value of the bind, the peeking will only be done the first time (at which
point the plan will be generated) and not subsequently.

You are not implying that instead of using bind in my code, we use
"cursor_sharing=force" ? I don't think so.

I'm in synch with everything you said in the last response (I think).

Thanks for your patience and dedication.

If I get over this issue, I'll owe you a drink/dinner at the next OOW.

Ma$e

          Followup:

what I'm saying is -- ok, you have a piece of code.  It does a poplist,
populates some list box.

that piece of code will either ALWAYS index range scan (most likely) or NEVER.
The design of your interactive OLTP application is such that it is ALWAYS going
to use inputs that permit an efficient index range scan -- or, it is not.  It is
the very nature of your application here.

      Rating: 5
bind
      variable  December 06, 2003
Reviewer:  Dillip K.
      Praharaj  from CA USA
Oracle uses the literal value for hard parsing a SQL because of
_optim_ppek_user_binds set to true in Oracle 9i.

So it means that the plan could be different than what we see in the explain
plan, how can I make sure that the plan it show by explain plan is the one it
has hard parsed ?

What I do is replace the binds with the literals for generating the plan.

Tom, any other easy way to do this ?

          Followup:

explain plan is a guestimate.  I use sql_trace + tkprof -- it shows reality.
there are many times that explain plan will be "tricked".

ops$tkyte@ORA920> create table t as select 99999 x, a.* from all_objects a;

Table created.

ops$tkyte@ORA920> update t set x = 1 where rownum = 1;

1 row updated.

ops$tkyte@ORA920> create index t_idx on t(x);

Index created.

ops$tkyte@ORA920> exec dbms_stats.gather_table_stats( user, 'T',
method_opt=>'for all indexed columns',cascade=>true);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> variable x number
ops$tkyte@ORA920> exec :x := 1

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select * from t where x = :x;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=54 Card=15851 Bytes=1585100)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=54 Card=15851 Bytes=1585100)

ops$tkyte@ORA920> set autotrace off
ops$tkyte@ORA920>
ops$tkyte@ORA920> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA920> declare
  2          l_x number := 1;
  3          cursor c is select * from t where x = l_x;
  4  begin
  5          open c;
  6          close c;
  7  end;
  8  /

PL/SQL procedure successfully completed.

and tkprof says....

SELECT *
from
 t where x = :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          0          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 549     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS BY INDEX ROWID T (cr=0 r=0 w=0 time=0 us)
      0   INDEX RANGE SCAN T_IDX (cr=0 r=0 w=0 time=0 us)(object id 58964)

      Rating: 3
Thank
      you  December 07, 2003
Reviewer:  wonder
       from China
Thank you , Tom

      Rating: 4
Bind
      variable  December 12, 2003
Reviewer:  Dillip K.
      Praharaj  from CA USA
Thanks Tom.

How about generating the plan from the v$SQL_PLAN ?

          Followup:

yes, that is another option -- same PLSQL block needed (to open/close cursor to
get the plan generated)

if you have my new book "Effective Oracle By Design", i demonstrate in there how
to use dbms_xplan easily to get a nicely formatted query plan out of there by
creating a view on v$sql_plan that makes it look just like a plan table with a
statement id....

December 13, 2003 Murali -- Thanks for the question regarding "How
      to Update millions or records in a table", version 8.1.7

      You Asked
Good Morning Tom.

I need your expertise in this regard.  I got a table which contains millions or
records.   I want to update and commit every time for so many records ( say
10,000 records).  I dont want to do in one stroke as I may end up in Rollback
segment issue(s).  Any suggestions please ! ! !

Murali

      and we said...
If I had to update millions of records I would probably opt to NOT update.

I would more likely do:

CREATE TABLE new_table as select <do the update "here"> from old_table;

index new_table
grant on new table
add constraints on new_table
etc on new_table

drop table old_table
rename new_table to old_table;

you can do that using parallel query, with nologging on most operations
generating very little redo and no undo at all -- in a fraction of the time it
would take to update the data.
Review &
      Followup

      Rating: 4
updating
      millions of records  November 11, 2002
Reviewer:
      Om  from India
Hi Tom,

Could you please elaborate

CREATE TABLE new_table as select <do the update "here"> from old_table;

the above statement with a suitable example please

          Followup:

ok, say you wanted to update emp to set ename = lower(ename).  Instead, you
could do this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table new_emp as
  2  select empno, LOWER(ename) ename, JOB,
  3     MGR, HIREDATE, SAL, COMM, DEPTNO
  4   from emp;

Table created.

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> rename new_emp to emp;

Table renamed.

      Rating: 3
Million records
      update  November 11, 2002
Reviewer:  Ramesh
      G  from U.K
What if a table has over 100 million records and if i only want to update 1
million? If your method is still applicable could you elaborate it.

Many thanks in advance.

          Followup:

most likely -- yes.  I don't have a 100million row table to test with for you
but -- the amount of work required to update 1,000,000 indexed rows is pretty
large.   Fortunately, you are probably using partitioning so you can do this
easily in parallel -- bit by bit.

      Rating: 5
"How to Update
      millions or records in a table", version 8.1.7  November 11,
      2002
Reviewer:  John Bittner  from Hunt Valley, MD
      USA
This is absolutely a viable approach, and one we have used repeatedly.  One of
our apps updates a table of several hundred million records.

The cursor..For loop approach for the update was calculated to take 53.7 years
to complete!
We institued the Insert into a dummy table append with nologging, and were able
to complete the "update" in under 30 minutes.

With nologging, if the system aborts, you simply re-run the 'update' again, as
you have the original data in the main table.  When done, we swap the partition
of original data with the 'dummy' table  (the one containing new values),
rebuild indexes in parallel, and wha-la!  Our update is complete.

i.e, to update field2 in a table:

1) First create your dummy hold table:  create table xyz_HOLD as select * from
xyz where rownum<1. Alter tablexyz nologging.

2) insert /*+ append parallel (xyzhold,12) */ into xyz_hold xyzhold (field1,
field2, field3) select /*+ parallel (x,12) */ xyz.field1,
my_new_value_for_field2, xyz.field3 from xyz x where blah blah blah.

3) when done, either rename the table, or swap the partition if your original
table is partitioned, and you only updated one partition as we do.  Obviously
you need to rebuild indecies, etc as required.

Hope this helps!

      Rating: 5
updating
      millions of records  November 11, 2002
Reviewer:
      A.Ashiq  from Trichy,Tamil Nadu ,India
Hi Tom,

As u suggested us to create a new table ,then drop the original table and rename
the new table to original table instead of updating a table with millions of
records.But what happen to dependent objects ,everything will get
invalidated.Yeah ,of course it'll recompile itself when it called next time.But
again it(dependent objects) has to do parsing.Is it Ok.

          Followup:

It is OK.

      Rating: 3
in case of
      delete  November 12, 2002
Reviewer:
      A reader
We've a similar situation., We delete around 3 million  records from 30 million
rows table everyday.
There is no logical column to do partition.,
I guess the insert into a new table will take considerable time with 27 mil
records.. Please let me know what is the best approach.

          Followup:

wait 10 days so that you are deleting 30 million records from a 60 million
record table and then this will be much more efficient.

Time it some day though.  3 million records on an indexed table will take
considerable time.  There is a chance that INSERT /*+ append */ select <rows to
keep> done nologging, parallel with create indexes in parallel could best the
delete.

      Rating:
      4
  November 12, 2002
Reviewer:
      A reader
Tom,

Recently I had conducted a interview in which one the dba mentioned that they
had a table that might conatin 10 million records or might be 1 million. He
meant to say they delete the records and some time later the table will be
populated again and viceversa.

Tom according to you do you consider partitions for such tables and if yes which
type of partition..

Thanks.

          Followup:

hard to tell -- is the data deleted by something that is relatively constant
(eg: the value in that column doesn't change - so the row doesn't need to move
from partition to partition).  If so -- sure, cause we could just drop
partitions (fast) instead of deleting the data.

      Rating: 5
Agree with John
      - insert append / CTAS / partition swap is the only way to
      fly  November 12, 2002
Reviewer:  Jack
      Silvey  from Richardson, TX
I work with John Bittner, one of the previous reviewers. I second what he said
absolutely. It is the only way to fly. We also have an absolutely incredible
stored procedure that rebuilds all of our indexes concurrently after the load,
using the Oracle job scheduler as the mechanism of allowing separate threads in
pl/sql.

      Rating:
      5
addendum  November 13, 2002
Reviewer:
      A reader
This process was introduced to our environment by a master tuner and personal
friend, Larry Elkins. This was a totally new paradigm for the application, and
one that saved the entire mission-critical application. The in-place updates
would not have worked with the terrabytes of data that we have in our database.

      Rating: 5
How to Update
      millions or records in a table  November 19, 2002

Reviewer:  Boris Milrud  from San Jose, CA USA

In response to the Jack Silvey (from Richardson, TX ) review, where he wrote "It
is the only way to fly. We also have an absolutely incredible stored procedure
that rebuilds all of our indexes concurrently after the load, using the Oracle
job scheduler as the mechanism of allowing separate threads in pl/sql":

Could you provide more information about that procedure and how to rebuild
multiple same-table indexes concurrently using Oracle job scheduler?

Thanks,
Boris.

          Followup:

instead of

begin
   execute immediate 'alter index idx1 rebuild';
   execute immediate 'alter index idx2 rebuild';
end;

you can code:

declare
  l_job number;
begin
  dbms_job.submit( l_job, 'execute immediate ''alter index idx1 rebuild'';' );
  commit;
  dbms_job.submit( l_job, 'execute immediate ''alter index idx2 rebuild'';' );
  commit;
end;

Now, just set job_queue_processes > 0 to set the "degree of threads" and in 8i
and before set job_queue_interval to say 60 or so and there you go.

      Rating: 5
How to Update
      millions or records in a table  November 19, 2002

Reviewer:  Boris Milrud  from San Jose, CA USA

Thanks, Tom.
Your status said that you had a large backlog, so I decided not to wait for your
response and tried myself using dbms_job.submit() calls. At first, it did not
work (job_queue_processes was 0), but after I set it to 12 it started working.
The only one difference between your code and mine is that I issue just one
commit at the end. It should not matter, right?

I selected 1 mln. rows table and rebuild 5 non-partitioned indexes with 'compute
statistics parallel nologging' clause.
Here is the numbers I've got: rebuilding indexes sequentually consistently took
76 sec., while using dbms_job.submit() calls took around 40 - 42 sec.

I said "around", because the technique I used may not be perfect, though it
served the purpose. I recorded the time right after the commit statement at the
end of PL/SQL block - that's the start time. Then I kept querying user_jobs view
every 2 - 3 sec, until the last of the 5 jobs were gone. That's was the end
time.

The last question on this topic: is user_jobs view is the right place to look in
order to determine that's rebuilding is done and how long it took? In package I
am writing, I do massive delete operation, then rebuilding indexes, then
starting the next routine. What would be the best way to detect the end of
rebuiding, in order to proceed with the next call?

Thanks.
Boris.

          Followup:

You can use user_jobs or dba_jobs but -- you might just want to put some
"logging" into your jobs themselves so you can monitor their progress and record
their times.

      Rating: 5
You can even
      rebuild all the indexes in a partition
      simultaneously  November 21, 2002
Reviewer:
      ramakrishna  from India
Thanks Tom,
  In our environment, we have partitioned tables and we use:
ALTER TABLE table_name MODIFY PARTITION part_name REBUILD UNUSABLE LOCAL INDEXES

and this rebuilds all the indexes in the partition at one shot.  We found this
much faster than doing the indexes one by one.  We will now try out if we can
submit multiple such jobs in parallel (one for each partition of the table).

regards
ramakrishna

      Rating: 4
in case of
      deletes  November 29, 2002
Reviewer:  carl
       from moondog, mo
Hi Tom,

Thanks so much for your web site and help.  It is our
number 1 reference in times of fear and loathing.

This is what we came up with concerning mass updates

INV    50M
INVINS 10M
INVDEL  7M

There are indexes on INV.KEY and INVDEL.KEY

Execution Plan ( for deletes and updates )
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   FILTER
   2    1     TABLE ACCESS (FULL) OF 'INV'
   3    1     INDEX (RANGE SCAN) OF 'IX_INVDEL01' (NON-UNIQUE)

alter table INVTMP nologging;

-- INVINS contains delta inserts and updates

insert /*+ APPEND */
into   INVTMP
select *
from   INVINS  t1;

-- INVDEL contains delta deletes and updates

insert /*+ APPEND */
into   INVTMP
select *
from   INV t1
where  not exists ( select null
                    from   INVDEL t2
                    where  t2.KEY = t1.KEY );

alter table INVTMP logging;
drop table INV;
rename INVTMP to INV

-- build indexs etc

This is what we came up with and is to the fastest
approach we've tested.

Any comments or suggestions are welcome and appreciated.

          Followup:

I might try an anti-join on the NOT EXISTS and doing the work in a single
parallel CTAS

create table invtmp as
select * from invins
UNION ALL
select t1.*
  from inv t1, invdel t2
 where t1.key = t2.key(+)
   and t2.key is null;

add parallel /nologging as needed.

      Rating: 5
in case of
      deletes - many thanks  December 01, 2002
Reviewer:
      carl  from moondog, mo
Ran the test cases at home 500K rows in INV and 50K rows in
INVINS and INVDEL

My way:   16.33 secs
Your way: 08.35 secs

Many thanks.

      Rating: 3
A bit
      confused  March 17, 2003
Reviewer:  Choche
       from England
I was just wandering that none of the reviews made mention if these techniques
could be applied in a multi-user environment where multiple users could be
updating the same table at the same time.

          Followup:

sorry -- I though it obvious that in most cases "no" is the answer.  we are
copying the data or locking excessive amounts of it -- or disabling indexes and
the like.

This is a "batch" process here, to update millions of records.

      Rating: 5
Replacing PK
      values  July 25, 2003
Reviewer:  Michael
       from San Diego, CA, USA
We need to replace the main ID for all entries in the database which is the PK
in about 100 tables, largest of them have about 2 mln rows. New and old values
for ID are stored in the lookup table, and there about half a million of them.
The original approach was to replace PK in all tables for one ID value, commit
the changes and move on to the next ID. This way referential integrity would be
guaranteed even if update fails at any stage, and we do not need a long rollback
segments and can restart the process at the point it aborted. But this update is
too lengthy, and in our case would take almost a month of continuous processing.
Do you have any suggestions on how to improve the performance of this critical
update? Would it be better to add new ID as an additional column into one table
at a time and populate all of it at once as "create table ... as select" (with
nologging option), and then rename table and drop old ID column?

          Followup:

sounds like I would:

create table new_table as select <join>;
create table new_table2 as select <join>;
etc etc etc etc

drop old tables
add indexes
add primary/fkeys/constraints etc....

that is -- rebuild in bulk, using nologging (no redo, no undo generated), using
parallel when appropriate.

      Rating: 5
Update million
      Rows  July 26, 2003
Reviewer:  Reaz
       from Dhaka, Bangladesh
Dear Tom,

We have this situation, where we load data from external files  numbering 7.
Next we had to run a transformation process, which checks if the row exists in
the target table and updates the existing row with the new one. Otherwise
inserts the row to the target table.

Our current process is very slow, sometimes taking a night long to complete for
say 200,000 rows.

The procedures discussed so far meant of updating only, but in our case we need
to update and insert too.

I would appreciate your suggestion.

Thanks.
Reaz.

          Followup:

read about MERGE in the sql reference guide.

      Rating: 5
A single
      batch?  July 26, 2003
Reviewer:
      A reader
Tom,
Is it still okay, if these steps (create a new table, drop the old one, and them
rename the new table as old, create indexes etc.) are coded as the batch update
program? OR would you suggests these should be detailed as the Steps that should
be performed under the supervision of a DBA? What I would like to know is that
can we still have the steps in a single batch routine (as update would have had)
or instead of the single batch these should be DBA instructions instead?
Thanks

          Followup:

I would have these performed under the watchful eye of someone.

it is a one time bulk manipulation.  it won't be a "pretty program" with tons of
gracefully logging/error handling.  You'll be doing it "down and dirty" (at
least I would be).  It would need someone to watch over it.

      Rating: 1
What about
      space consideration  July 28, 2003
Reviewer:
      A reader
Hi Tom,
I had a similar problem where my table had gone big and i wanted to truncate
some old data, my table was big more then 20 million records, i could not use
the create temp table truncate and insert flow as the space of the database was
not enough to hold 2 copies of the large table, I thought of doing a partition
on table and then truncate one partition of table but that does not work.
Can you suggest some way which caters for this space constraint also

          Followup:

why didn't the partitioning work for you.

      Rating: 5
How to update
      part of a Very Large Table  July 28, 2003

Reviewer:  Mohan K  from Bangalore, India
The update operation can be made simple by sepearting out the query and update
operation. To do that create another table which stores the rowids of the
records in the original table which has to be updated along with the the value
to be updated. Then run the PL/SQL script to update the records one by one. To
test the above method the following scripts can be used.

SQL> create table test_update(id number, name varchar2(100), description
varchar2(4000)) storage initial(48M next 4m);

SQL> declare
v_n number;
v_name number;
v_desc number;
i number;
begin
for i in 1..1000000 LOOP
insert into test_update(id, name, description) values(i, 'Test Name'||i, 'Test
Name '||i||' description ');
END LOOP;
end;
/

Elapsed: 00:04:277.23

The above script will insert 1 million rows.

SQL> select count(*) from test_update where description like '%5 description%';

  COUNT(*)
----------
    100000

Elapsed: 00:00:02.63

SQL> create table test_update_rowids as select rowid rid, description from
test_update where description like '%5 description%';

Elapsed: 00:00:54.58

The table test_update_rowids stores the rowids and the new values that has to be
updated. ie 100000 rows needs to be updated.

SQL>  declare
begin
for c1 in(select rid, description from test_update_rowids)
LOOP
update test_update set description=c1.description||' after update' where
rowid=c1.rid;
END LOOP;
end;
/

Elapsed: 00:01:82.17

The above script performs the updation.

Mohan

          Followup:

the above script performs an "updation" in the worst possible way, consuming the
most resources.

it should have been simply:

update test_udpate set description = description || ' after update ' where
description like '%5 description%';

Never do procedurally that which can be done in a single sql statement.

      Rating: 5
Updating 100K
      of 1 Million rows  July 28, 2003
Reviewer:
      Mohan K  from Bangalore, India
I ran the update statement as a single SQL statement and the time elapsed is
slightly more than the above method. It has to be tested against update
statements containing complex queries or join operations.

SQL> update test_update set description = description || ' after update ' where
description like '%5 description%';

Elapsed: 00:01:100.13

Mohan

          Followup:

trust me -- that difference is neglible and the amount of time you spent writing
code wasn't included.

Also, you FORGOT to add in the time to populate your temporary table -- no?

if you can do it in a single sql statment (and you almost always can) you
SHOULD.

Lets do you benchmark a little better:

ops$tkyte@ORA920> create table test_update(id number, name varchar2(100),
description varchar2(4000));

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert /*+ append */
  2  into test_update
  3  select rownum, 'Test Name' || rownum, 'Test Name ' || rownum || '
description '
  4    from big_table.big_table
  5   where rownum <= 1000000;

1000000 rows created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select count(*)
  2    from test_update
  3   where description like '%5 desc%' ;

  COUNT(*)
----------
    100000

ops$tkyte@ORA920>
ops$tkyte@ORA920> select count(*)
  2    from test_update
  3   where description like '%6 desc%' ;

  COUNT(*)
----------
    100000

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec runstats_pkg.rs_start

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> update test_update
  2     set description = description || ' after update'
  3   where description like '%6 description%';

100000 rows updated.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec runstats_pkg.rs_middle

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table test_update_rowids
  2  as
  3  select rowid rid, description from test_update where description like '%5
description%';

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2     for c1 in(select rid, description from test_update_rowids)
  3     LOOP
  4        update test_update
  5           set description=c1.description||' after update'
  6         where rowid=c1.rid;
  7     END LOOP;
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec runstats_pkg.rs_stop
Run1 ran in 1376 hsecs
Run2 ran in 9806 hsecs
run 1 ran in 14.03% of the time

so, according to me, the SINGLE (efficient, effective, correct way) update
statment runs in about 14% of the time.  furthermore:

Name                                  Run1        Run2        Diff
STAT...recursive cpu usage               5       4,662       4,657
STAT...CPU used by this sessio         448       5,397       4,949

only 4,949 MORE cpu seconds using the row by row approach...

STAT...CPU used when call star         448       5,397       4,949
STAT...free buffer inspected        10,162      17,039       6,877
STAT...db block gets               111,705     104,450      -7,255
STAT...dirty buffers inspected       9,016      16,620       7,604
STAT...calls to kcmgas               7,755          68      -7,687
STAT...Cached Commit SCN refer       7,718           0      -7,718
STAT...switch current to new b       7,719           0      -7,719
STAT...cleanouts only - consis           0       7,720       7,720
STAT...immediate (CR) block cl           0       7,720       7,720
STAT...commit txn count during           0       7,721       7,721
STAT...cleanout - number of kt           3       7,726       7,723
STAT...consistent gets - exami           3       7,760       7,757
LATCH.undo global data                 221       7,985       7,764
STAT...redo entries                100,700     108,727       8,027
LATCH.redo allocation              100,795     108,945       8,150
STAT...db block changes            202,723     210,907       8,184
STAT...Elapsed Time                  1,385       9,825       8,440
STAT...physical reads                7,894      16,476       8,582
LATCH.checkpoint queue latch        26,845      38,012      11,167
LATCH.cache buffers lru chain       35,638      52,827      17,189
STAT...no work - consistent re       7,741     100,072      92,331
STAT...table scan blocks gotte       7,719     107,721     100,002
STAT...table scan rows gotten    1,000,000   1,100,002     100,002
STAT...execute count                    27     100,092     100,065
STAT...buffer is not pinned co       7,763     107,836     100,073
STAT...calls to get snapshot s          56     100,241     100,185
LATCH.shared pool                      172     100,778     100,606
STAT...session logical reads       119,503     220,165     100,662
STAT...consistent gets               7,798     115,715     107,917
LATCH.library cache pin                233     200,865     200,632
STAT...recursive calls                 177     200,870     200,693
LATCH.library cache                    332     201,322     200,990
LATCH.cache buffers chains         587,377     835,167     247,790
STAT...redo size                31,323,804  31,836,904     513,100
STAT...session pga memory max            0     524,824     524,824
STAT...session pga memory                0   1,049,112   1,049,112

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
754,602   1,553,986     799,384     48.56%

PL/SQL procedure successfully completed.

and worse, it used 2x the number of latches!  (scalability inhibitors)

There is nothing good about row at a time processing, it is something to do ONLY
when a set based operation isn't feasible

      Rating: 5
This is very
      good example for updating the records  July 28, 2003

Reviewer:  Bipin Ganar  from INDIA
Hi Tom,
Yes this is good example to update the record but my problem
is i have 1Million records in one table. I want update and give hike of 10% of
their salaries in one table but that table is accessed by n no of people at the
same moment. I can not drop the table and update and second how can i define
tablespace in this script
Create Table a as select * from emp_sal;

          Followup:

create table a
tablespace foo
as
select * from emp_sal;

      Rating: 4
slightly
      different problem!  July 28, 2003
Reviewer:
      Srikanth Adiga  from Pune, India
My table:
5M records are inserted each month.

Operations:
5M records inserted on the first day of the month
5M records updated on rest of the days on the month.
Note that this update is update each row with diff values.

I am using OCI and using batch operations. Can you please suggest me what else
can I do within my program to improve the performance of my updates? I tried
multithreading, but that did not help.

anything else?

          Followup:

it only takes a couple of minutes to update 5 million rows on my desktop pc --
so, there must be something more involved here.

I'd be able to do that in minutes -- why is it taking you so long -- what is
your bottleneck.

      Rating: 3
space
      considerations  July 28, 2003
Reviewer:
      A reader
could not create another temp table which holds the data same as teh table being
partitioned as there was not enuff space in the harddisk to hold 2 or 1.5 to be
precise copies of the same huge table,is there a way to partition without having
to drop of truncate or transfer data to some temp location and then partition

          Followup:

disk is cheap.

time is money.

your time spent just thinking about this problem cost much more then the disk to
solve it would.

penny wise, pound foolish.  How big is this 20million row table (assuming you
are the same "a reader" from above).  Maybe 1-3 gig?

      Rating: 4
slightly
      different problem!  July 29, 2003
Reviewer:
      Srikanth Adiga  from Pune,India
couple of minutes!!! Oh dear, thats really too good.

Let me reitterate my case:
My table schema is something like this:
SubscriberVsQtyTable:
int client-id
int year
int month
int quantity
primary key - (client-id, year,month)

This table has 5M client ids.

Now every day, I have update the quantity field with the txns made for that day.
Note that, I have to update one subscriber at a time since the number of txns
made by each subscriber if different.

So, even now do you believe you can do this in couple of minutes? My app takes
about 30 to 50 mins even on higher machines like 8 CPU.

As I said, this is a C++ app, running Solaris + Oracle 8i/9i. We are using OCI.

Thanks,

          Followup:

if you are doing that work with more then a single sql statement, I could see it
taking a while.

If you do that work in a single update, no, I don't see it taking more then a
couple of minutes.

      Rating: 5
Time for
      Update  July 29, 2003
Reviewer:  Mohan K
       from Bangalore, India
It is my understanding that the time for update depends on the following
factors. There may be something else also involved.

1. Row Chaining/Migration caused by update
2. The where cluase is used to select the rows to be updated.
3. Percentage rows updated in a block. The more the better. Even if one row is
updated in a block containing 10 records the entire block has to written by DBWR
and LGWR.

So just the number of records is not that matters. 100 million recorsd may takes
days for one table and may take less than a hour for another table with few
columns.

Mohan

          Followup:

1) updating a quantity (number) field is unlikely to do that.
2) correct
3) correct

no, i disagree with the last part.  to update 5million rows, a single unindexed
column, should not take "a really long time".  I was answering their direct
question -- they update every row, on every block.  so #1 and #3 really don't
come into play.  The biggest factor is #2 here

      Rating: 4
Verifing if
      neccesary to update, before update  July 29, 2003

Reviewer:  juan carlos reyes  from Bolivia

Hi Tom, I had seen several times that you can
increase the performance by evaluating if you really need to update, or maybe it
was only my impression.

For example
Update table set income = 500
where level = 7
AND NOT INCOME = 500

The AND NOT INCOME = 500, allows to avoid unncesary updates,
my point is a question

How advisable is it, does it really increase performance?

As you are always thinking how to increase performance, tt could be a good idea
in newer version of Oracle, that if Oracle verifies that the value was set to
that value, not update
:)

          Followup:

if there are more then a handful of records where income = 500, then yes, this
will definitely increase performance by

a) decreasing the latching
b) decreasing the redo
c) decreasing the undo

we cannot make that optimization for you -- it would stop triggers and other
EXPECTED side effects from happening.

      Rating:
      5
  July 29, 2003
Reviewer:
      A reader
:) Thanks,

Maybe then it could be an optional feature
 UPDATE_ONLY_IF_DIFFERENT hint
I think it could interest several people
who don't use trigger, neither constraints ,etc.

      Rating:
      5
  July 29, 2003
Reviewer:  Stu
      Charlton  from Toronto Canada
"Note that, I have to update one subscriber at a time since the number of txns
made by each subscriber if different."

That still seems doable in 1 SQL statement, even if you wanted to merge the
updates.  Couldn't you just do a COUNT(*) grouped by client_id, year, and month?

          Followup:

I agree -- most things CAN be done in a single sql statement.

      Rating: 2
easier said
      then done in real life situations  July 29, 2003

Reviewer:  A reader
Hi Tom,
I agree you may think getting a disk is cheaper, that is only when u are
considering getting up or just going to the store and getting a disk,
In real life conditions specially corporates this is not as easy as it seems nor
is it less costly.
A Simple purchase of disk has to first be justified
which is quite difficuult, i dont think i have justification as yet)then be
approved then be purchased by purchase dept then be installed or fixed by the
server teams, server team may want to take the system down which means more
dollars much more.These are just things off my head, when we acutually inititate
the purchase process , there will be definitly some more steps and processes.

Coming back to my question, am i right in saying that we can't partion an
existing table without copy the data to some temp location,Let me know if there
is any other way.

Thanks

          Followup:

justification

you pay me alot
disk is cheap.
think about it.

(sometimes it really can be that simple)

we are talking about a couple of gig (like 1 to 3 -- small, tiny -- my /tmp has
more room then that) here, something my LAPTOP would not have an issue with.

in order to partition -- think about it -- you'll need the SOURCE DATA and the
PARTITIONED DATA -- at the same time, for a period of time.  no magic there.

      Rating: 4
slightly
      confused !  July 30, 2003
Reviewer:  Srikanth
      Adiga  from Pune, India.
Thanks, Stu and rest for your updates.

Taking this buit more further, since I am confused :)

[Sri]
"Note that, I have to update one subscriber at a time since the number of txns
made by each subscriber if different."
[Sri]

[Stu Charlton]
That still seems doable in 1 SQL statement, even if you wanted to merge the
updates.  Couldn't you just do a COUNT(*) grouped by client_id, year, and month?

Followup:
I agree -- most things CAN be done in a single sql statement.
[Stu Charlton]

Sorry, how would one do this?
If my table has two rows;
clientid =1,year=july,year=2003,quantity=10
clientid =2,year=july,year=2003,quantity=20
Now I have to update (clientid=1)'s quantity by 15 and (clientid=2)'s quantity
by 25.
How would you manage this in a single SQL? Like this there would be 5M rows to
be updated.

Btw, my table is indexed on clientid,year and month.

          Followup:

what is the OTHER table you are updating from?  you must have a detail table
elsewhere from where you derive that 15 and 25.  So, assuming something like
this:

ops$tkyte@ORA920> create table t ( clientid int, month int, year int, quantity
int );

Table created.

ops$tkyte@ORA920> create table txns ( clientid int, month int, year int );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( 1, 7, 2003, 10 );

1 row created.

ops$tkyte@ORA920> insert into t values ( 2, 7, 2003, 20 );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into txns select 1, 7, 2003 from all_objects where
rownum <= 15;

15 rows created.

ops$tkyte@ORA920> insert into txns select 2, 7, 2003 from all_objects where
rownum <= 25;

25 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t;

  CLIENTID      MONTH       YEAR   QUANTITY
---------- ---------- ---------- ----------
         1          7       2003         10
         2          7       2003         20

ops$tkyte@ORA920> update t
  2     set quantity = quantity + ( select count(*)
  3                                   from txns
  4                                  where txns.clientid = t.clientId
  5                                    and txns.month = t.month
  6                                    and txns.year = t.year );

2 rows updated.

ops$tkyte@ORA920> select * from t;

  CLIENTID      MONTH       YEAR   QUANTITY
---------- ---------- ---------- ----------
         1          7       2003         25
         2          7       2003         45

there you go.

(it is a shame you are using the wrong datatypes -- only a DATE should be used
to hold, well, DATES.  Using numbers to hold a year and a month isn't a good
practice)

      Rating: 1
SCN,CHECK
      POINT  July 30, 2003
Reviewer:  AATIF
       from PAKISTAN
i ask about whta is difference  SCN system commit number,systemt change number
and check point.
Which base oracle sync the database SCN or CHECKPOINT ,
when check point ocur who number write in control file headre,datafile and log
file header.
dear tom.
your web site say me not ask more question now but i need clear my point.
AATIF.

          Followup:

er?

i remember you asking this on another page -- yes.

and I also remember answering this on that other page -- yes.

I fail to see how this relates to this page.

anyway there IS no difference between

scn
system commit number
system change number

they are the same.

as for the rest of the comment -- it doesn't "make sense" (i do suggest a read
through of the server concepts guide, many questions are answered there!)

      Rating: 4
More
      clarification  August 01, 2003
Reviewer:
      Srikanth Adiga  from Pune,India
>what is the OTHER table you are updating from?  you must
>have a detail table elsewhere from where you derive that
>15 and 25.
It is coming from an OCI application after processing some files.

          Followup:

use bulk processing - update between 100 and 500 rows at a time in a single call
using array processing.

Or, put the data where it belongs - not in files, in tables -- and process the
data in the database rather then in C

      Rating:
      4
  August 06, 2003
Reviewer:  Srikanth
      Adiga  from Pune, India
>what is the OTHER table you are updating from?  you must
>have a detail table elsewhere from where you derive that
>15 and 25.
It is coming from an OCI application after processing some files.

>Followup:
>use bulk processing - update between 100 and 500 rows at a >time in a single
call using array processing.
Yes, that is what we do. It takes about 60 mins to update 5M records on a 2CPU
machine. Is this much expected?

If I do the same in multiple threads I do not see any performance improvement
i.e. 1M records parallely updated in 5 threads.
Any idea why?

          Followup:

are you sure they are going in parallel and not blocking/locking each other.

verify that all 5 sessions are in fact not only ACTIVE (v$session) at the same
time but that you are not blocking yourself.

and you have taken some statspacks during your testing to see if you have any
obvious bottlenecks right?

      Rating: 4
Explicit commit
      !  August 07, 2003
Reviewer:  Nathan
       from London
Tom,

Why do we commit explicitly after submitting jobs   ?

Thanks
Nathan
/*------------------------------------------------
Followup:
instead of

begin
   execute immediate 'alter index idx1 rebuild';
   execute immediate 'alter index idx2 rebuild';
end;

you can code:

declare
  l_job number;
begin
  dbms_job.submit( l_job, 'execute immediate ''alter index idx1 rebuild'';' );
  commit;
  dbms_job.submit( l_job, 'execute immediate ''alter index idx2 rebuild'';' );
  commit;
end;
---------------------------------*/

          Followup:

because if you do not YOUR session can "see" the job in the job queue but the
job queue processes cannot!

How many of us have sat there for minutes going "when is the stupid thing going
to run" and until we exit sqlplus it does nothing :)

      Rating: 4
Explicit
      Commit  August 07, 2003
Reviewer:  Nathan
       from London
Apologies,
I must withdraw my previous question... i was obviously not thinking straight. I
was thinking about a similar sitaution where a commit was issued ( before
dbms_job.submit ) with a comment /*  do not remove */ without any
explanations... I'm still pondering why the commit should be there.

Sorry for the trouble.

Regards
Nathan

      Rating: 4
I have to take
      issue....  August 07, 2003
Reviewer:  cheaper
      than disk  from USA
Your response about justification for buying disk space is outdated...

"justification

you pay me alot
disk is cheap.
think about it.

(sometimes it really can be that simple)"

Anybody else out there NOT paid alot ANYMORE?!

;-)

      Rating: 5
Re: Explicit
      Commit - To Nathan  August 07, 2003
Reviewer:
      A reader
Tom is out till weekend..
"I'm still pondering why the commit should be there. "
The commit after dbms_job is requierd so that other sessions can see the
job_queue...which typically is the
requirement...As usual, Tom can confirm..

Menon:)

      Rating:
      5
Reader  August 10, 2003
Reviewer:
      A reader
"
create table invtmp as
select * from invins
UNION ALL
select t1.*
  from inv t1, invdel t2
 where t1.key = t2.key(+)
   and t2.key is null;
"

Like to know why
"
where t1.key = t2.key(+)
   and t2.key is null
"
better than
where t1.key !=  t2.key with ALWAYS_ANTI_JOIN hint

          Followup:

because there is no "always anti join" hint?

you could of course use the CBO, and a NOT IN subquery in which case it'll do it
itself.

      Rating:
      5
  August 14, 2003
Reviewer:  Su Baba

So if I have a big table, x, that needs to be updated, I would

create table y nologging as select * from x...
retrieve index definition from x
build indexes on y
drop table x
rename y to x

If I have all these done in a PL/SQL batch job, what's the best way to retrieve
index definition and form create index statement from table x?  I want to create
indexes on y that have the exact storage parameter, tablespace, maybe partition,
etc. as the ones on table x.

By the way, the database version is 8.1.7.

thanks

          Followup:

you COULD, not WOULD, it is an option.

you don't have your create indexes somewhere?

guess I would exp rows=n, imp indexfile=foo to get them -- or
DBMS_METADATA.get_ddl in 9i

      Rating: 4
How to update
      part of a Very Large Table  August 28, 2003

Reviewer:  Mohan K  from Bangalore, India
please refer "How to update part of a Very Large Table  July 28, 2003"

When updating a large table in a database having comparatively less buffer cache
one thing to make sure is that blocks are updated in a continuous order. Suppose
some records of block1 is updated first in the beginning and next after updating
some other blocks, then block1 will be written twice by DBWR and LGWR. After the
first update block1 may get flushed out by LRU algorithm. If all records in
block1 is updated at a time then block1 is wriiten by LGWR and DBWR only once.
If the update statement is doing a full table scan then bloacks are updated in
order. But the order can be different when the is using an index for selecting
the rows.

The SQL statement that makes sure of that is

update (select id, description from test_update where rowid in(select rowid from
(select rowid from test_update order by rowid))) set description=description||'
new val' where description like '%5%';

          Followup:

not necessarily so -- first lgwr doesn't write blocks -- it writes redo.
second, dbwr may or may not write the block more then once.

I would never never never use your approach.  why I would want to

a) read the entire table
b) sort the rowids
c) distinct that set (implied)
d) join to it (and you are making the assumption the table in the IN will be the
driving table and that a hash join will not be used and ..... lots of other
stuff)

when a simple:

update T set d = d || 'x' where d like '%5%'

will do it -- if you are worried about the index on d being used - you would
simply FULL hint it -- but doubtful that it would be necessary.

the additional work you add would more then offset the work you are trying to
save.

      Rating: 5
update large
      table  August 28, 2003
Reviewer:  Vera
       from D.C.
Hi Tom and thank you again and again for all your work.

My problem is that I cannot simply replace update with creating a new table. The
table I need to update - I'll call it 'pension table' - has about 1.5 mln rows.
I have to update "amount" fields for 1994, 1995,1995 etc. years, and the way how
I update these fields depends on status of the participant at each particular
year. To determine this status I built a function that looks through another
tables and takes several input parameneters including each particular financial
year because it determines not just status of a participant, but status he/she
had in particular FY. (This function I call a "box of cockroaches"). Basically,
I ask Oracle to bring me annual monthly amount for Mr/Mrs 'X' for, let's say,
1994 and if 'X' had status 'retired' in 1994 then I want this amount to be
calculated one way, if 'X' was 'deferred' -another way and if 'X' was 'dead'
third way.
As you alredy guessed, updates take long time - about an hour for each year. I
wonder what approach would you use in a situation like this?
Thank you
By the way, what is a status of your new book?

          Followup:

I would use a CASE statement in SQL without using PLSQL to compute the value.

optionally -- i would turn it inside out.  instead of sql calling plsql, this
might be one of the times when plsql calling sql is more appropriate.  build up
procedurally an array of values to update -- forall update them -- and do it
again until the rows are processed.  with a little foresight, you can even
parallelize this processing and run your procedure N times -- each instance of
it running against a different slice of the table...

book is "real soon" -- should be in trucks right now.

      Rating: 5
update large
      table  August 28, 2003
Reviewer:  Luc
      Gyselinck  from Belgium
For large tables, if I have to update more than 20 percent of the rows, I do it
like Tom: write a query to insert the 'updated' records in a new table, using
the /*+ APPEND */ hint. You have no indexes to update, no constraints to
validate and you generate almost no redo. Next, I truncate the old table, after
I have disabled all constraints and triggers (once again, almost no redo, very
fast), and then I inject, again with the /*+ APPEND */ hint, the data from the
new table into the old (very fast, little redo).  Indexes get automaticaly
rebuild AFTER the extents are populated. I reenable the triggers, the
constraints (with or without validation, as you wish).

If the new values for the columns being updated must come from other tables, I
NEVER write something like

update t1
set c1 = (select c2 from t2 where t2.c3 = t1.c4)

In fact, you are performing NESTED LOOPs. Using functions to get the new values
for the columns is much the same : NESTED LOOPS (even worse: SQL / PLSQL engine
context switches, open/close cursors in PL/SQL, NO read consistency).

Whenever I find myself in such a situation (typicaly when writing batch
procedures, during data migrations, data transformations), I make sure my
queries use HASH joins, I give the session more resources (higher
SORT_AREA_SIZE, higher HASH_AREA_SIZE, DB_MULTIBLOCK_READ_COUNT to the max),
avoid the INDEX RANGE SCANS, do FULL (yes FULL) table scans, do FAST FULL INDEX
scans, thus bypassing the buffer pool which otherwise gets flushed by the data
from my queries. And by the way, use LMTs with uniform size extents.

As an example, I rewrote a batch procedure that took 3 days to complete (once,
many years ago), written the old way (see the update statement above), that now
does the same job in only 4 hours, on the same (old) hardware, same database,
but different Oracle version: Oracle 7 / Oracle 8i.

      Rating: 4
Yes, you are
      right,  August 28, 2003
Reviewer:  Vera
       from D.C.
but right now I don't see how can I perform my task without update statement and
a function. In other cases I do exactly the same thing you do on large tables -
create new table rather than update old one and then rename tables.
As for LM tablespaces I was begging our DBAs to switch on LMT since I don't know
when and they keep promising to do it.

      Rating: 4
Does parallel
      insert just imply direct?  September 08, 2003

Reviewer:  Dennis  from Missouri, USA
Tom (or anyone),

Does a parallel insert imply direct (append) insert?   I read something in the
documentation (or is that forbidden territory) ;) awhile back about the append
hint not being needed on a parallel insert because it was implied.

I issued the following:
INSERT /*+ append, nologging, parallel(ext, 8) */ INTO
hrdmadm.ext_xpayd_pay_details ext
SELECT /*+ parallel(ext2, 8) */ *
FROM hrdmadm.ext_xpayd_pay_details@hip2 ext2

and while I saw the parallel processes spawn on hip2 (for the select), I didn't
notice any spawn where I was.

I was wondering if that was because parallel insert was synonymous with direct
insert, or did I mess up the syntax somehow?  Is the insert in fact parallel,
and it's just 'hidden' from us?

Thanks,
Dennis

          Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96524/c21dlins.htm#10629
quote:

Serial and Parallel Direct-Path INSERT

When you are inserting in parallel DML mode, direct-path INSERT is the
default. In order to run in parallel DML mode, the following requirements must
be met:
......

      Rating:
      5
Thanks!  September 08, 2003
Reviewer:
      Dennis  from Missouri, USA
My bad was that I didn't alter session.  Thanks Tom for your valuable insight.
I guess I was just too afraid to read down a few more pages when I was in the
documentation, with that being forbidden territory and all ;)  Well maybe not
forbidden...just hard to put that 'haunted/fear-inspiring' word that would fit
in there.

Thanks,
Dennis

      Rating: 4
Optimizing
      Query  September 11, 2003
Reviewer:  Mohan

BULK collect and BULK insert can improve the performance. Create SQL objects of
type t1%rowtype and t2%rowtype.
BULK collect 100 records at a time to the collection array. Create seperate
collection arrays to store values that are computed or obtained after searching
the database.
Do BULK insert/update into the target table. Search this site using key words
"bulk insert" or "bulk collect" or "bulk update" for more examples.

Other things are
1. Improve query performance by proper indexing
2. Remove fragmentation by "alter table move" command
3. Rebuild indexes
4. Allow large pctfree if the update causes row migration/row chaining
5. Size rollback segments and rollback tablespaces

Mohan

          Followup:

#2 isn't really "a good thing" in general.
#3 is really not "a good thing" in general.

reorgs -- so so overrated.

      Rating:
      3
  September 11, 2003
Reviewer:
      Kailash  from NJ
Can you explain which of the 3 operations will be faster. If I want to update
millions of rows,
1. then would delete/reinsert be faster or
2. mere update will be faster
3. the one you suggested will be faster.
Can you advise as to why the method you had suggested will be faster than 1 and
2.

Can you explain why updating millions of rows is not a good idea. Is it based on
the amount of redo generated and what if the columns I update are not part of
the index?

Which operation will be the fastest and I request you to explain the various DML
operations and the amount of redo they generate. ie. I would appreciate if you
could explain me which DML generates most redo, followed by the second followed
by the 3rd which generates the least redo.

Currently we have an application that checks for a condition in a table A and
obtains the unique id. if a record is found, then flushes all of the dependent
tables for the given id and re-inserts these records back into the dependent
tables with a set of new values. Is this a good approach? If so, what would be
the ideal approach?

          Followup:

I can say yes to all three -- each is the fastest.

each is the slowest.

If you have a large bulk update that is done once and hits most of the of rows
-- you may be best off by doing a CTAS, drop old and rename new.

the CTAS, indexing, etc can be done in nologging mode -- no undo, no redo.  you
end up getting done much faster.

      Rating: 5
updating
      millions of records   October 03, 2003
Reviewer:
      b  from austin TX
Hi,
The approach of updating the view instead of a table works great.
But here i want to update multiple colums of the same table based on different
table queries.
we have around 600,000 records in the base table.
what could be the most inexpensive query.
We run oralce 9i.

UPDATE user1.UserSummary
Set
Column1  = (
         SELECT Count(DISTINCT WebOrderID)
         FROM user1.OrderTotal ot
         WHERE total>0 and
               (ot.ostatus <> -1 and
               ot.ostatus <> -1) and
               ot.UREFITEM = user1.UserSummary.UREFITEM)),
    Column2 = (
           SELECT NVL(Sum(Total),0) FROM
           user1.OrderTotal ot
           WHERE (ot.ostatus <> -1 and ot.ostatus <> -1)
           and ot.UREFITEM = user1.UserSummary.UREFITEM),
  Column3 = (
          SELECT min(sodate) FROM orders.salesorders so left join
          user1.ordertotal ot on ot.sorefitem = so.sorefitem
          WHERE total>0 and (so.ostatus <> -1 and so.ostatus <> -1)
          and so.UREFITEM = user1.UserSummary.UREFITEM),
    Column4 =  (
          SELECT max(sodate) FROM orders.salesorders so
          left join user1.ordertotal ot on ot.sorefitem = so.sorefitem
          WHERE total>0 and (so.ostatus <> -1 and so.ostatus <> -1) and
          so.UREFITEM = user1.UserSummary.UREFITEM)
  ;
the above is the sample of my query

thanks in advance
;;)

          Followup:

if you can come up with a single query that pulls all 4 columns together in a
single result set -- we can optimize this.  Left as an exercise for you -- see
if you cannot merge the above 4 separate query into a single query

      Rating: 5
great
        October 17, 2003
Reviewer:  b  from
      tx
thanks
i finally made my query simple

      Rating: 4
Great
        October 21, 2003
Reviewer:  Raj  from
      San Diego CA USA
Great technique never thought of it.

      Rating:
      4
  October 22, 2003
Reviewer:  Anthony
      Reddy  from Malaysia, KL

      Rating: 4
Updating 1
      million records  October 22, 2003
Reviewer:
      Kailash  from NJ, USA
I have a situation where I have a unique key on 3 columns (c1,c2,c3) on a table
T and I want to update c2 from A to B for some records that match certain
criteria. There are a total of 30 million records in the table and there will be
about 1 million records in the table that match the criteria to be updated. The
table is not partitioned.
1. Your suggestion to drop the old table and to re-create a new table and
re-naming it to the old table may not be possible because I am updating only a
subset of the table. Is there any other method to acheive this ?
2. Which one of the following will be a better solution?
      a. Drop index and update the rows and recreate the
         index
      b. Delete the rows with C2 = A and insert new rows
         with C2 = B. My DBA says that this will create
         fragmentation and is not a better approach. Is
         that true?
      c. Do you suggest any better solution under these
         scenarios since I keep getting these type of
         questions from my manager often.

Your suggestion in this regard is highly appreciated.

          Followup:

have you considered just "update set c2 = b where ...."?

1,000,000 rows out of 30,000,000 isn't alot.  if c2 is indexed, you might
benefit from a set unusable, skip unusable, update, rebuild.

but it won't take that long to update 1,000,000 rows.  couple of minutes on my
laptop

big_table@ORA920> update big_table set created = created+1;

1833792 rows updated.

Elapsed: 00:02:13.28

not a big deal.

      Rating: 4
How about
      rollback segment problem when updating million
      rows.  October 22, 2003
Reviewer:  Ramasamy
       from NC USA
Tom ,

Thanks lot for your service.

I have to update a single column NUMBER(9) where
the table has got more than 80 columns.
This table has got 30 million rows and i have to update
around 17 million rows each night.

As you always insisted ,
i tried with a single update like

UPDATE t1 SET batchno = (A constant )
WHERE batchno is NULL;

1.)I came across the rollback segment issue.
2.) It is taking around 3 hrs to update.

Your input is greatly appreciated to solve the above issues.

Thanking You,
Ramasamy

          Followup:

there is no problem -- other then you haven't sized your system to do the things
you require it to do!!!!!!!!

1) the rollback segment "issue" is a "non issue".  size things for what you do
and there are NO ISSUES.  disk is cheap -- compared to what they pay us all to
have this discussion.  consider that.  the amount of time you've spent thinking
about this already cost your employer much more then the disk it would take to
do this correctly.

2) you are changing a null to a non-null -- increasing the width of the field.
make sure you are not causing massive row migration (check your pctfree, make
sure it is set appropriately here).  is batchno "indexed"?

      Rating: 4
MILLION RECORD
      INSERT/SELECT/UPDATE TAKES TOO MUCH TIME  October 23, 2003

Reviewer:  Santosh Das  from Bangalore, INDIA.

Hi Tom,
I have a table A and soon after creating it 1million records are put into it.
Then it being in a real time environment its size goes on increasing atleast
half million each day.However each day a cleanup application cleans half million
every day.After inserting 1 million at the creation of table its put in the
realtime environment.When ever a new record is inserted a contuniously listening
application detects it and applies a select operation on table A. Then it send
the selected items to some table B and then updates the table A.
The listening operation may be 3 or 4 in number.How ever the select and update
operations are taking a lot of time and the initial insertion of 1 million
records is taking 11 hours to complete so what can be the possible architecture.
I tried sending the rowid to a queue/pipe after inserting in table A and then
used it to select and update.But still its taking a lot of time. Plz suggest
some method.
Thanks and regards,
Sandy..

          Followup:

sounds like you should just be using AQ -- plop the record in as a message --
have as many dequeuers as you like.

not sure what else to say, not a ton of detail here.

      Rating: 5
Select and
      update of a table Having Millions of Records.  October 23,
      2003
Reviewer:  Jack  from NY
Hi Tom,
I have the following queries which execute on a table of million records.That
table has a PK but its not used in both the queries given below.CMTIME,SAD and
STATUS all are NUMBER fields.If I make index on cmtime then it takes longer.
Presently i dont have a index field.Can you plz tellme how i can optimize these
or where the performance bottleneck is ?

update SD set CMTIME = 1064929834 where (CMTIME is NULL or 1064929834 - CMTIME
>= 10) and (1064929834 - SAD >= 0) and 1064929834 not in (select CMTIME from SD
where CMTIME is not NULL)

SELECT * from SD where (CMTIME is not NULL and (CMTIME = 1064929834 OR
1064929834 - CMTIME >= 10)) AND (ED - 1064929834 > 0 and (STATUS = 1 OR STATUS =
0) and 1064929834 - SAD >= 0)

Thanks and regards,
Jack.

          Followup:

are you using the CBO.

      Rating: 5
Followup to
      your question  October 23, 2003
Reviewer:
      Ramasamy  from NC USA
2) you are changing a null to a non-null -- increasing the width of the field.
make sure you are not causing massive row migration (check your pctfree, make
sure it is set appropriately here).  is batchno "indexed"?

Yes Tom. The Batchno is indexed like
index on (BATCHno,STATUS);

Will it be worth Droping the index and recreating it?.

Thanks,
Ramasamy

          Followup:

could definitely help, yes.

      Rating:
      4
  October 23, 2003
Reviewer:  George
      Lee  from Hong Kong
I tried to update 30 million records using Tom's solution.
It works fine. Using approximately 1.5 hours to finish.

George (Broadway Photo Supply Ltd. HK)

      Rating: 5
whats
      CBO  October 24, 2003
Reviewer:  Jack
       from NY

          Followup:

cost based optimizer

      Rating:
      4
No  October 27, 2003
Reviewer:  jack
       from NY
We are not using any cost based optimizers.
It's a simple query which we are using to retrieve data from a table having
millions of records and it's performance is not satisfactory.
How to optimize it so that it runs faster ?
jack

          Followup:

you are kidding right?

this goes right up there with "my car won't start, why not?"

there is so much missing from this "request" as to make it impossible to say
anything sensible.

      Rating:
      4
  November 11, 2003
Reviewer:  George
      Lee  from Hong Kong
I have a fact table being partitioned by month. The indexes are built local to
partition.

The data population is working fine.

But the rebuild index fails at the end.

I don't know how to rebuild indexes in the partitions.
Can you help me ?

          Followup:

why do you need to rebuild here at all??

but why don't you show us what you are doing -- at the very least -- define what
"fails at the end" means (error codes, messages, details)

      Rating:
      5
  November 12, 2003
Reviewer:  George
      Lee  from Hong Kong
Dear Tom,

Yes, I should provide enough information to you. Sorry for that.

Here is the story,

I create a table as following,

drop table D_DYNRPT_SALES_FACT;

create table D_DYNRPT_SALES_FACT
(
  TX_DATE           DATE,
  SHOP_NO           VARCHAR2(20),
  ITEM_NO           VARCHAR2(12),
  PARENT_ITEM_NO    VARCHAR2(12),
  BRAND_NO          VARCHAR2(5),
  VENDOR_NO         VARCHAR2(20),
  ITEM_GROUP        VARCHAR2(5),
  ITEM_TYPE         VARCHAR2(5),
  CONSIGNMENT       VARCHAR2(1),
  ITEM_DESC         VARCHAR2(40),
  BRAND_DESC        VARCHAR2(30),
  VENDOR_DESC       VARCHAR2(30),
  CATEGORY_DESC     VARCHAR2(30),
  QTY_SOLD          NUMBER(14,2),
  NET_SALES_AMT     NUMBER(14,2),
  GROSS_PROFIT_AMT  NUMBER(14,2)
)
PARTITION BY RANGE (TX_DATE)
( PARTITION D_DYNRPT_SALES_FACT_2000_09 VALUES LESS THAN
(TO_DATE('01/10/2000','DD/MM/YYYY'))
            TABLESPACE D_DYNRPT_SALES_FACT_2000_09,
  PARTITION D_DYNRPT_SALES_FACT_2000_10 VALUES LESS THAN
(TO_DATE('01/11/2000','DD/MM/YYYY'))
            TABLESPACE D_DYNRPT_SALES_FACT_2000_10,
  PARTITION D_DYNRPT_SALES_FACT_2000_11 VALUES LESS THAN
(TO_DATE('01/12/2000','DD/MM/YYYY'))
            TABLESPACE D_DYNRPT_SALES_FACT_2000_11,
  PARTITION D_DYNRPT_SALES_FACT_2000_12 VALUES LESS THAN
(TO_DATE('01/01/2001','DD/MM/YYYY'))
            TABLESPACE D_DYNRPT_SALES_FACT_2000_12,
  PARTITION D_DYNRPT_SALES_FACT_2001_01 VALUES LESS THAN
(TO_DATE('01/02/2001','DD/MM/YYYY'))
            TABLESPACE D_DYNRPT_SALES_FACT_2001_01,
  PARTITION D_DYNRPT_SALES_FACT_2001_02 VALUES LESS THAN
(TO_DATE('01/03/2001','DD/MM/YYYY'))
            TABLESPACE D_DYNRPT_SALES_FACT_2001_02,
  PARTITION D_DYNRPT_SALES_FACT_2001_03 VALUES LESS THAN
(TO_DATE('01/04/2001','DD/MM/YYYY'))
            TABLESPACE D_DYNRPT_SALES_FACT_2001_03,
  PARTITION D_DYNRPT_SALES_FACT_2001_04 VALUES LESS THAN
(TO_DATE('01/05/2001','DD/MM/YYYY'))
            TABLESPACE D_DYNRPT_SALES_FACT_2001_04,
  PARTITION D_DYNRPT_SALES_FACT_2001_05 VALUES LESS THAN
(TO_DATE('01/06/2001','DD/MM/YYYY'))
            TABLESPACE D_DYNRPT_SALES_FACT_2001_05,
  PARTITION D_DYNRPT_SALES_FACT_2001_06 VALUES LESS THAN
(TO_DATE('01/07/2001','DD/MM/YYYY'))
            TABLESPACE D_DYNRPT_SALES_FACT_2001_06,
  PARTITION D_DYNRPT_SALES_FACT_2001_07 VALUES LESS THAN
(TO_DATE('01/08/2001','DD/MM/YYYY'))
            TABLESPACE D_DYNRPT_SALES_FACT_2001_07,
  PARTITION D_DYNRPT_SALES_FACT_2001_08 VALUES LESS THAN
(TO_DATE('01/09/2001','DD/MM/YYYY'))
            TABLESPACE D_DYNRPT_SALES_FACT_2001_08,
  PARTITION D_DYNRPT_SALES_FACT_2001_09 VALUES LESS THAN
(TO_DATE('01/10/2001','DD/MM/YYYY'))
            TABLESPACE D_DYNRPT_SALES_FACT_2001_09,
  PARTITION D_DYNRPT_SALES_FACT_2001_10 VALUES LESS THAN
(TO_DATE('01/11/2001','DD/MM/YYYY'))
            TABLESPACE D_DYNRPT_SALES_FACT_2001_10,
  PARTITION D_DYNRPT_SALES_FACT_2001_11 VALUES LESS THAN
(TO_DATE('01/12/2001','DD/MM/YYYY'))
            TABLESPACE D_DYNRPT_SALES_FACT_2001_11,
  PARTITION D_DYNRPT_SALES_FACT_2001_12 VALUES LESS THAN
(TO_DATE('01/01/2002','DD/MM/YYYY'))
            TABLESPACE D_DYNRPT_SALES_FACT_2001_12,
  PARTITION D_DYNRPT_SALES_FACT_2002_01 VALUES LESS THAN
(TO_DATE('01/02/2002','DD/MM/YYYY'))
            TABLESPACE D_DYNRPT_SALES_FACT_2002_01,
  PARTITION D_DYNRPT_SALES_FACT_2002_02 VALUES LESS THAN
(TO_DATE('01/03/2002','DD/MM/YYYY'))
            TABLESPACE D_DYNRPT_SALES_FACT_2002_02,
  PARTITION D_DYNRPT_SALES_FACT_2002_03 VALUES LESS THAN
(TO_DATE('01/04/2002','DD/MM/YYYY'))
            TABLESPACE D_DYNRPT_SALES_FACT_2002_03,
  PARTITION D_DYNRPT_SALES_FACT_2002_04 VALUES LESS THAN
(TO_DATE('01/05/2002','DD/MM/YYYY'))
            TABLESPACE D_DYNRPT_SALES_FACT_2002_04,
.
.
.
  PARTITION D_DYNRPT_SALES_FACT_2004_12 VALUES LESS THAN
(TO_DATE('01/01/2005','DD/MM/YYYY'))
            TABLESPACE D_DYNRPT_SALES_FACT_2004_12,
  PARTITION D_DYNRPT_SALES_FACT_MAXVALUE VALUES LESS THAN (maxvalue)
            TABLESPACE D_DYNRPT_SALES_FACT_MAXVALUE
)
/

CREATE INDEX D_DYNRPT_SALES_FACT_I01 ON D_DYNRPT_SALES_FACT
(TX_DATE) LOCAL;

CREATE INDEX D_DYNRPT_SALES_FACT_I02 ON D_DYNRPT_SALES_FACT
(TX_DATE, ITEM_GROUP, ITEM_TYPE) LOCAL;

CREATE INDEX D_DYNRPT_SALES_FACT_I03 ON D_DYNRPT_SALES_FACT
(TX_DATE, SHOP_NO, ITEM_NO, ITEM_GROUP) LOCAL;

CREATE INDEX D_DYNRPT_SALES_FACT_I04 ON D_DYNRPT_SALES_FACT
(TX_DATE, BRAND_NO, ITEM_GROUP, ITEM_TYPE) LOCAL;

CREATE INDEX D_DYNRPT_SALES_FACT_I05 ON D_DYNRPT_SALES_FACT
(TX_DATE, ITEM_NO) LOCAL;

CREATE OR REPLACE PUBLIC SYNONYM D_DYNRPT_SALES_FACT FOR
BPSADM.D_DYNRPT_SALES_FACT;

then, populate data into the table as the following,

alter index D_DYNRPT_SALES_FACT_I01 unusable;
alter index D_DYNRPT_SALES_FACT_I02 unusable;
alter index D_DYNRPT_SALES_FACT_I03 unusable;
alter index D_DYNRPT_SALES_FACT_I04 unusable;
alter index D_DYNRPT_SALES_FACT_I05 unusable;

alter session set skip_unusable_indexes=true;

@pop_d_dynrpt_sales_fact.sql '2000/09/01' '2000/01/30';
.
.
.
@pop_d_dynrpt_sales_fact.sql '2003/11/01' '2003/11/30';

alter index D_DYNRPT_SALES_FACT_I01 rebuild nologging;
alter index D_DYNRPT_SALES_FACT_I02 rebuild nologging;
alter index D_DYNRPT_SALES_FACT_I03 rebuild nologging;
alter index D_DYNRPT_SALES_FACT_I04 rebuild nologging;
alter index D_DYNRPT_SALES_FACT_I05 rebuild nologging;

the data_population is working fine, but alter index rebuild nologging fail with
the error as following,

alter index D_DYNRPT_SALES_FACT_I01 rebuild nologging
            *
ERROR at line 1:
ORA-14086: a partitioned index may not be rebuilt as a whole

alter index D_DYNRPT_SALES_FACT_I02 rebuild nologging
            *
ERROR at line 1:
ORA-14086: a partitioned index may not be rebuilt as a whole

alter index D_DYNRPT_SALES_FACT_I03 rebuild nologging
            *
ERROR at line 1:
ORA-14086: a partitioned index may not be rebuilt as a whole

alter index D_DYNRPT_SALES_FACT_I04 rebuild nologging
            *
ERROR at line 1:
ORA-14086: a partitioned index may not be rebuilt as a whole

alter index D_DYNRPT_SALES_FACT_I05 rebuild nologging
            *
ERROR at line 1:
ORA-14086: a partitioned index may not be rebuilt as a whole

I checked the reference, it should be something syntax error on my rebuild index
statement, it is like alter index D_DYNRPT_SALES_FACT_I05 rebuild partition...,
I don't know the exact statement, can you help me, thanks.

          Followup:

ops$tkyte@ORA920LAP> CREATE TABLE t
  2  (
  3    data       char(255),
  4    temp_date  date
  5  )
  6  PARTITION BY RANGE (temp_date) (
  7    PARTITION part1 VALUES LESS THAN (to_date('13-mar-2003','dd-mon-yyyy')) ,
  8    PARTITION part2 VALUES LESS THAN (to_date('14-mar-2003','dd-mon-yyyy')) ,
  9    PARTITION part3 VALUES LESS THAN (to_date('15-mar-2003','dd-mon-yyyy')) ,
 10    PARTITION part4 VALUES LESS THAN (to_date('16-mar-2003','dd-mon-yyyy')) ,
 11    PARTITION part5 VALUES LESS THAN (to_date('17-mar-2003','dd-mon-yyyy')) ,
 12    PARTITION part6 VALUES LESS THAN (to_date('18-mar-2003','dd-mon-yyyy')) ,
 13    PARTITION junk VALUES LESS THAN (MAXVALUE)
 14  )
 15  ;

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create index t_idx1 on t(temp_date) LOCAL nologging;

Index created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> alter index t_idx1 unusable;

Index altered.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> begin
  2          for x in ( select 'alter index ' || index_name ||
  3                            ' rebuild partition ' || partition_name stmt
  4                   from user_ind_partitions
  5                                  where index_name = 'T_IDX1' )
  6          loop
  7                  dbms_output.put_line( x.stmt );
  8                  execute immediate x.stmt;
  9          end loop;
 10  end;
 11  /
alter index T_IDX1 rebuild partition PART1
alter index T_IDX1 rebuild partition PART2
alter index T_IDX1 rebuild partition PART3
alter index T_IDX1 rebuild partition PART4
alter index T_IDX1 rebuild partition PART5
alter index T_IDX1 rebuild partition PART6
alter index T_IDX1 rebuild partition JUNK

PL/SQL procedure successfully completed.

      Rating:
      5
  December 11, 2003
Reviewer:
      A reader
Hi Tom,
I am running below update statement from past 24 hr and it's still running..

Table S_REVN has 1.5 Million and Table S_OPTY_PROD has 1.2 Million Record.. It's
doing full table scan of both table sinace I am using function in where clause..
We using RULE base optimizer and oracle 8.1.7.4

Here is sql statement

Update siebel.S_REVN r
set x_opty_prod_city=(select     X_CITY    from siebel.S_OPTY_PROD p where
replace(replace(replace(p.ROW_ID,'ce','='),'-','='), '@', '$') = r.row_id),
X_opty_prod_STATE    =(select     X_STATE    from siebel.S_OPTY_PROD p where
replace(replace(replace(p.ROW_ID,'ce','='),'-','='), '@', '$') = r.row_id),
X_PR_YEAR_QTY    =(select X_PR_YEAR_QTY    from siebel.S_OPTY_PROD p where
replace(replace(replace(p.ROW_ID,'ce','='),'-','='), '@', '$') = r.row_id)

   0      UPDATE STATEMENT Optimizer=RULE
   1    0   UPDATE OF 'S_REVN'
   2    1     TABLE ACCESS (FULL) OF 'S_REVN'
   3    0   TABLE ACCESS (FULL) OF 'S_OPTY_PROD'
   4    0   TABLE ACCESS (FULL) OF 'S_OPTY_PROD'
   5    0   TABLE ACCESS (FULL) OF 'S_OPTY_PROD'

Please advice
Thanks

          Followup:

you understand that it is doing 3 full scans of S_OPTY_PROD for EACH and EVERY
row in S_REAVN yes???

how about

update t
set (a,b,c) = (select a,b,c from t2 where ... )

at the very least (reduce the full scans from 3 to 1)

at the very most, consider creating an index:

create index
temp_index_to_make_this_complete_before_my_grandchildren_have_grandchildren
on p(replace(replace(replace(p.ROW_ID,'ce','='),'-','='), '@', '$'))

and useing a hint (to let sighbul use the cbo) to get the index used.

      Rating:
      5
  December 12, 2003
Reviewer:
      A reader
Hi Tom,

I am following your advice on update I've changed sql statement

Update /*+ INDEX(S_OPTY_PROD T_TEMP) */  siebel.S_REVN r
set (x_opty_prod_city,X_opty_prod_STATE,X_PR_YEAR_QTY)  =
(select X_CITY,X_STATE,X_PR_YEAR_QTY   from siebel.S_OPTY_PROD p
where replace(replace(replace(p.ROW_ID,'ce','='),'-','='), '@', '$') = r.row_id)

created function based index on S_OPTY_PROD

create index T_TEMP
on S_OPTY_PROD(replace(replace(replace(ROW_ID,'ce','='),'-','='), '@', '$'))

BUT above sql statement in not using index eventhough I supply hint (Is it b/c
function based index not reconized by Rule based optimizer?)

Please help
Thanks

          Followup:

you put the hint in the wrong place :)

and you might not have met all of the requirements for FBI's.  read:

http://asktom.oracle.com/~tkyte/article1/index.html
but it is not the RBO (the well formed hint -> cbo)....

your hint is wrong (must use the correlation name of P) and in the wrong place
(should be in the subquery).

you need query_rewrite_enabled and (depending on version) might need
query_rewrite_integrity (see the above link).

but here is an example:
ops$tkyte@ORA920> create table t1( x_opty_prod_city int, x_opty_prod_state int,
x_pr_year_qty int, row_id varchar2(25) );

Table created.

ops$tkyte@ORA920> create table t2( x_city int, x_state int, x_pr_year_qty int,
row_id varchar2(25) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index t2_idx on t2(
replace(replace(replace(ROW_ID,'ce','='),'-','='), '@', '$') );

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> delete from plan_table;

4 rows deleted.

ops$tkyte@ORA920>
ops$tkyte@ORA920> ALTER SESSION SET QUERY_REWRITE_ENABLED=true;

Session altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> explain plan for
  2  Update t1 r
  3  set (x_opty_prod_city,X_opty_prod_STATE,X_PR_YEAR_QTY)     =
  4      (select /*+ INDEX( t2 t2_idx ) */ X_CITY,X_STATE,X_PR_YEAR_QTY
  5             from t2 p
  6        where replace(replace(replace(p.ROW_ID,'ce','='),'-','='), '@', '$')
= r.row_id)
  7  /

Explained.

ops$tkyte@ORA920>
ops$tkyte@ORA920> prompt @?/rdbms/admin/utlxpls
@?/rdbms/admin/utlxpls
ops$tkyte@ORA920> set echo off

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
--------------------------------------------------

-------------------------------------------------------------------------------
| Id  | Operation                    |  Name       | Rows  | Bytes |Cost(%CPU)|
-------------------------------------------------------------------------------
|   0 | UPDATE STATEMENT             |             |    82 |  4346 |   3  (34)|
|   1 |  UPDATE                      | T1          |       |       |          |
|   2 |   TABLE ACCESS FULL          | T1          |    82 |  4346 |   3  (34)|
|   3 |   TABLE ACCESS BY INDEX ROWID| T2          |     1 |    53 |   2  (50)|
|*  4 |    INDEX RANGE SCAN          | T2_IDX      |     1 |       |   2  (50)|
-------------------------------------------------------------------------------

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

   4 -
access(REPLACE(REPLACE(REPLACE("P"."ROW_ID",'ce','='),'-','='),'@','$')=:B
              1)

16 rows selected.

      Rating:
      3
  December 12, 2003
Reviewer:
      A reader
hello Tom,I create table as following using the parallel hint,but the cost is
different:

sql>insert /*+ append parallel(demo01,4) */ into demo01 select /*+
parallel(customer,4) */ * from customer;
the cost --42s
and the Execution Plan is:
Execution Plan
----------------------------------------------------------
ERROR&#65306;
ORA-12838:
&#26080;&#27861;&#22312;&#24182;&#34892;&#27169;&#24335;&#19979;&#20462;&#25913;&
#20043;&#21518;&#35835;/&#20462;&#25913;&#23545;&#35937;
SP2-0612: &#29983;&#25104;AUTOTRACE
EXPLAIN&#25253;&#21578;&#26102;&#20986;&#29616;&#38169;&#35823;

Statistics
----------------------------------------------------------
1698 recursive calls
455 db block gets
8728 consistent gets
8075 physical reads
66492440 redo size
630 bytes sent via SQL*Net to client
525 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
11 sorts (memory)
0 sorts (disk)
390126 rows processed

sql>insert * from demo02 select * from customer;
cost --26s
and the Execution Plan is:
Execution Plan
----------------------------------------------------------
0 INSERT STATEMENT Optimizer=CHOOSE (Cost=1226 Card=659568 Byt
es=680674176)

1 0 PARTITION RANGE (ALL)
2 1 TABLE ACCESS (FULL) OF 'CUSTOMER' (Cost=1226 Card=659568
Bytes=680674176)

Statistics
----------------------------------------------------------
1939 recursive calls
44876 db block gets
20918 consistent gets
1 physical reads
64954576 redo size
646 bytes sent via SQL*Net to client
467 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
7 sorts (memory)
0 sorts (disk)
390126 rows processed

The customer table have 0.4 million records and the machine is 2 CPU's SUN
E420,which runiing Oracle database.

Can you tell me why the physical reads increase so much  using parallel hint in
the DML sql,thanks

          Followup:

well, for a tiny number of records like this, i would not even consider PQ at
this point.

But -- to answer your questions

the cost, well the cost is affected by thousands of things.  HINTS definitely
affect the cost (That is in part how hints work -- by playing with costs).
Ignore the cost of two queries that are not the same, they are NOT comparable.

the PIO's -- well, you ran one before the other?  That and parallel query
prefers to checkpoint -- and do direct io many times (you might find PQ doing a
global checkpoint before the query begins to get current images onto disk in
order to just all hit slam the data as fast as it can without messing with the
buffer cache)

but for so few records -- it would seem that regular sql is what you want.

      Rating:
      5
  December 13, 2003
Reviewer:
      A reader
Hi Tom,
Followed you advice...WORKS LIKE A CHARM..updated 1.5 Million recod in 10 Min.
EXCELLENT!!!!!!!

Thanks a LOTTTTTTTTTTT

Update   siebel.S_REVN r
set (x_opty_prod_city,X_opty_prod_STATE,X_PR_YEAR_QTY)  =
(select /*+ INDEX(S_OPTY_PROD T_TEMP) */ X_CITY,X_STATE,X_PR_YEAR_QTY   from
siebel.S_OPTY_PROD p
where replace(replace(replace(p.ROW_ID,'ce','='),'-','='), '@', '$') = r.row_id)

December 13, 2003 Basil -- Thanks for the question regarding "Good
      AQ example?", version 9.2.0.1.0

      You Asked
I'm trying to get a simple batch job scheduler together: queue up a job,
immediately running it if no other job like it is running, putting it in line
behind the others if another job like it IS running. This seems like an ideal
fit for Advanced Queues. However, this is a simple multiple-producer,
single-consumer sample. The Oracle docs on this contain LOTS more detail than I
think is needed here. Do you have any pointers to simpler examples?

      and we said...
That is the first time I've heard someone say our examples have too much detail!

Anyway, the AQ docs do go through this in various levels of detail.  Steps are:

a) create queue table

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96587/apexampl.htm#62620
b) enqueue a message (put one on the queue)

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96587/apexampl.htm#60515
c) dequeue a message ( retrieve it)

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96587/apexampl.htm#60515
and there you go.

Review &
      Followup

      Rating:
      5
  December 14, 2002
Reviewer:
      A reader
Hi Tom,

There are many features in oracle which only few people is using.Like
   DBMS_ALERT,
   DBMS_PIPE,
   DBMS_AQ,
   FGAC etc....

So there is lots of misunderstanding regarding this features wheather to use it
or not.On your web site you have some discusson regarding FGAC.

 Could you please(As an oracle expert) spread awareness to oracle community
regarding this feature(If those feature are really good to implement)

Thanks,

          Followup:

I did -- see asktom.oracle.com and click on my face.  True, I don't have AQ in
that book however -- the links right above 100% show you the very very simple
calls you need to make.

      Rating: 3
update
      links  December 12, 2003
Reviewer:
      A reader  from NYC, NY
Hi Tom,

Could you please update above links?
Thanks!

Steve

          Followup:

http://otn.oracle.com/pls/db92/db92.docindex?remark=homepage#index-APP
that points to the application developers guide for AQ

December 13, 2003 Rainer -- Thanks for the question regarding "HTP,
      HTF, OWA_UTIL, HTML-Packages : Documentation ?, Future ?", version 8.1.7

      You Asked
Hi Tom,

just trying to fix some bugs/features in a html-producing-package I wrote some
time ago using htp,htf,owa functions.

Being on my weekend remote outpost, listening to Cannonball Adderley's "Mercy,
Mercy" (great music, highly recommanded) I'm desperately trying to find a full
documentation of htp,htf,... on OTN-website.

I'm sure I found it once, it's at my office, but somehow it is really difficult
to find it (i.e. I didn't find it right now) . Seems Oracle is not really
promoting it, or is it just my Saturday-night paranoia ?

Seriously : we are using it heavily on an european-wide reporting system, users
love it for it's speed, they are serious enough not to care about the missing
fancy layout.

Is there any danger of not being supported in future by Oracle? Will there be a
more sophisticated tool to provide the same functionality and speed, just being
able to make it look nicer in an easy way ? What is the position of Marvel in
this context ?

Have a nice weekend, appreciate your website, regards,
Rainer

      and we said...
must be saturday night paranoia

see

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96590/adgweb.htm#998000
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96590/adgweb.htm#998000
http://download-west.oracle.com/docs/cd/A97335_01/apps.102/a90101/toc.htm

Review &
      Followup

      Rating: 4
Future
      ?  July 06, 2003
Reviewer:  sreekanth
      gadhiraju  from tampa, fl usa
Hi tom,

We run oracle application 11i, the initial releases of Self Service applications
Internet Expenses, Internet time and ireceivables used pl/sql procedures to
generate the web pages. but the latest releases(formally Second Generation
interface/user interface) uses JSP to generate the web pages using some kind of
frame work.

My question is why oracle it self is discontinuing using pl/sql and using JSP
instead.

thanks
sreekanth.g

          Followup:

you mean your apps install isn't installing any PL/SQL?

They are using java for the UI and PL/SQL for much of the data logic.

Look -- there are literally 1,000's of ways to do this stuff.  "discontinuing"
-- that is a rather strong term.  If you look at the various Oracle web sites,
you'll see a mixture of lots of stuff.  Wait for the next release of the
database -- you'll see.

      Rating: 5
OWA_UTIL,HTP
      packages in Java sp,  November 10, 2003
Reviewer:
      RAJESH  from COLORADO
Tom,

I'm planning to develop a web based sqlplus tool using mod_plsql(ORACLE 8.1.7.2
db).

we have somany database instances for development/systemtest and I dont want to
create my DB packages in all the boxes to genarate HTML output.

I just want to use one dev instance for all the packages and use some Java
stored procedures to connect to different instances(passing connect string ,
username,password). For testing purpose, I just hard coded these values.

something like

create or replace and compile java source named "test"
as
import java.sql.*;
import java.io.*;
import java.math.*;
import oracle.sql.*;
import oracle.jdbc.driver.*;

public class test1
{
 public static void get_connection() throws Exception
 {

      DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
      Connection conn =
DriverManager.getConnection("jdbc:oracle:thin:@dev_host:1521:dev_sid","dev_user",
"dev_passwd");
               CallableStatement cstmt = conn.prepareCall(

                          "declare  nm  owa.vc_arr;"+     //got these things
from your site
                                "vl  owa.vc_arr;"+
                                " begin nm(1) := 'WEB';"+
                                "vl(1) := 'WHATEVER';" +
                                "owa.init_cgi_env( nm.count, nm, vl );" +
                                "htp.htmlopen;"+
                                "htp.bodyopen;"+
                                "htp.print('Print Something');" +
                                "htp.print('It is printing');"+
                                "htp.bodyclose;"+
                                "htp.htmlclose; "+
                                "end;");
        cstmt.execute();

  }
}

I have a procedure in the package like this:

procedure test
is language java
name 'test.get_connection()';

when I tried to execute this pckage in a browser

with
http://localhost:7777/pls/devdad/raj_util.test
It is giving "The document contain no data..." browser error!!

I tried to execute the procedure in SQLPLUS , it is not generating any output!!.

SQL> set serveroutput on
SQL> exec raj_util.test;

PL/SQL procedure successfully completed.

SQL> exec owa_util.showpage;

PL/SQL procedure successfully completed.

How to execute htp or owa_* packages in java to get HTML outputs?
I like PL/SQL . I'm not good at java servlets/JSP stuff and I don't want to use
them.

          Followup:

a web based sqlplus tool?

htmldb.oracle.com <<<=== already done.  really cool, most excellent sqlplus
"replacement" tool

to test the output in sqlplus, you need to issue

set serveroutput on

first

      Rating: 4
OWA_UTIL,HTP
      packages in Java sp  November 11, 2003
Reviewer:
      Rajesh  from COLORADO
Thanks for your reply!!

I creatd my account in HTML DB.It is really cool.

I think I confused you with "sqlplus tool" .

My actual problem is, I can't display anything on the browser by executing the
above java SP(i just want to know why).

I used "serveroutput on" before executing the procedure(in sqlplus).But it
didn't work.

My question is, can we use statements like htp.print in java stored procedures
to get HTML output?.

If your answer is yes, then how can we do that?

I'm asking this because, I know, we cant display any output by using
dbms_output.put_line in a Java stored procedure( with prepareCall).

          Followup:

you can call dbms_output???  why can't you????

but, in your case -- you created an ENTIRELY DIFFERENT NEW session (look at your
connect url).  You were in "session A", called the java SP, it created "session
B", produced a web page and then returned.  The web page is hanging out in
session B, session A cannot see it!!!

Here is an example showing htp and dbms_output output from java:

ops$tkyte@ORA920LAP> create or replace and compile java source named "test"
  2  as
  3  import java.sql.*;
  4  import java.io.*;
  5  import java.math.*;
  6  import oracle.sql.*;
  7  import oracle.jdbc.driver.*;
  8
  9  public class test1
 10  {
 11   public static void get_connection() throws Exception
 12   {
 13
 14        DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
 15        Connection conn = new OracleDriver().defaultConnection();
 16
 17        CallableStatement cstmt = conn.prepareCall
 18        (  "begin dbms_output.put_line( 'Hello World' ); end;" );
 19        cstmt.execute();
 20
 21        cstmt = conn.prepareCall
 22        ( "begin htp.p( 'hello world wide web' ); end;" );
 23        cstmt.execute();
 24    }
 25  }
 26  /

Java created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create or replace procedure test
  2  is language java
  3  name 'test1.get_connection()';
  4  /

Procedure created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> set serveroutput on
ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> @owainit
ops$tkyte@ORA920LAP> declare
  2          nm      owa.vc_arr;
  3          vl      owa.vc_arr;
  4  begin
  5          nm(1) := 'WEB_AUTHENT_PREFIX';
  6          vl(1) := 'WEB$';
  7          owa.init_cgi_env( nm.count, nm, vl );
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> exec test
Hello World

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec owa_util.showpage
Content-type: text/html
Content-length: 21
hello world wide web

PL/SQL procedure successfully completed.

      Rating: 5
OWA_UTIL,HTP
      packages in Java sp  November 12, 2003
Reviewer:
      Rajesh  from COLORADO
Thanks TOM

Thanks for pointing out the bug in my program.
I totally forgot about the "SESSIONS".

You are really the best!!

      Rating: 3
Peeking htp
      buffer content   December 04, 2003
Reviewer:
      Robert  from CT
I been trying for hours to output to file stuff in the htp buffer.
I want to be able to view the buffer content at ANY one time so I doctored a
copy of htp.showpage to output to file instead of dbms_output.put_line calls.

But it seems I kept getting *nothing* back, whether directly out to file or to
my index_by table....

Is this possible - viewing the htp buffer content in the middle of page
processing ?

Thanks Tom

          Followup:

if you use showpage in the middle of a mod_plsql function, that dumps it to the
bit bucket (gone).

you would have to access the private array directly.

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:347617533333
it shows how to "get the buffer" but once you get it -- it is "gone", it won't
be sent back to the end user, you'd have to re"put" it again

      Rating: 3
Keep getting
      just nothing...  December 04, 2003
Reviewer:
      Robert  from CT
Thanks, but I don't know...
I tried:
1) Putting your procedure dump_page into HTP pkg,
and in the mod_plsql procedure  I made the call like this:
 <snip>
  sys.htp.dump_page ; --< compiler insists I put the owner..
  HTP.BODYCLOSE;
  HTP.HTMLCLOSE;
 EXCEPTION
 <snip>

Empty file created...

2) Insert a call at the very end of
HTP procedure prn(cbuf in varchar2 DEFAULT NULL)
to collect whatever goes in there..
like:
<snip>
 myprn(cbuf);
end;

Also got an empty file...

          Followup:

you are not using SYS for anything are you????????????

don't do that, don't rewrite htp.  stop

restore the database back to its installed state (put htp back)

install this procedure in YOUR SCHEMA.

do not -- do not - repeat -- do not - use sys/system/etc.  they are not yours,
they are Oracle's -- leave them be.

      Rating: 5
htmldb vs.
      iSQL*Plus  December 04, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Tom,

You say...

<quote>
a web based sqlplus tool?

htmldb.oracle.com <<<=== already done.  really cool, most excellent sqlplus
"replacement" tool
</quote>

What about just using iSQL*Plus?  Isn't that the primary purpose of that tool?
Oracle is committed to continuing to ship iSQL*Plus I am guessing (as it is
mentioned in Metalink as the replacement for sqlplusw.exe on Windows)?

Thanks,

Mark

          Followup:

- well, some people like "toad"
- some people like character mode sqlplus
- some people like isqlplus
- some people are really going to like htmldb and it's sql workshop.  (i
particularly enjoy the pagination of result sets!)

Tom
--------------------------------------------------------------------
Tyler here.  Additionally, HTML DB's SQL Workshop incorporates some features
that you won't find in SQL Plus or iSQLPlus such as:
- Wizards to create objects such as tables (incuding a sequence and trigger to
populate the primary key)
- Wizard to create an API package for a table
- Ability to show tables related to the current table, a simple er diagram if
you will
- Generate DDL for any database object
- Query by example of any table
- Load spreadsheet data into an existing OR new table using copy and paste.
Users love this wizard.  You simply copy and paste your spreadsheet into a
textarea then go through 3 more steps in a wizard and you have a table WITH your
data in it, a primary key, sequence, and a trigger using the sequence to
populate your primary key
- Generate lookup table wizard.  This is great for tables that you want to
normalize.  You pick a table and a column in that table that has repetitive data
and the wizard creates a new table for you that contains the distinct values
from that column and a corresponding id.  It then updates your original table to
replace the data in the column with the primary key of the corresponding data in
the lookup table AND adds a foriegn key constraint to the lookup table.

If all of these features aren't enough to sway you, keep in mind that with HTML
DB you can acutally build a web application on your data which is the primary
function of HTML DB.

Hope this helps,
Tyler

      Rating: 5
htmldb vs.
      iSQL*Plus  December 05, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
But you need to install htmldb into each database?  You can't point htmldb at
other databases, right?  That is, say I have 100 databases, using a single
"instance" of iSQL*Plus, I can connect to any of those databases easy as pie...
would I have to install htmldb into all 100 databases to use sql workshop?
(Plus run an instance of http server on each host)?  But I can't because not all
of them are 9iR2+...  I agree that htmldb can do more than "straight" sqlplus
for certain "user-type activities", but use it as a sqlplus replacement tool? Of
that I am not so sure...

Thanks,

Mark

          Followup:

I cannot imagine why every database on the planet will not have htmldb in it :)

you only need "a" http server with N dads setup.

      Rating: 5
htmldb vs.
      iSQL*Plus  December 06, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
> I cannot imagine why every database on the planet will not have htmldb in it

Because every database on the planet is not 9iR2 or higher?

Seriously, since we are in a regulated environment, something as simple as a
database upgrade can be a very involved task.  If vendor software is only
supported on 9.0, 8.1, or even 8.0, we can't upgrade those databases.  So, while
it would be great if we could upgrade everything to 9iR2 or 10g on release, it
just is not a real possibility.

Perhaps I was reading too much into your statement about sql workshop being a
replacement (as in total replacement) for sqlplus...  but, for example, it is
trivial to startup and shutdown a database via sqlplus - can you do that through
sql workshop?

I am not "knocking" htmldb, so I hope it did not come off that way.  I will have
to re-install it to experiment a bit more I guess.  As an aside, I do not use
iSQL*Plus much myself as I prefer command-line sqlplus, but there are a few
people at my shop that do use iSQL*Plus.

Thanks,

Mark

      Rating: 4
Saminathan
      Seerangan  December 07, 2003
Reviewer:  Sami
       from NJ,USA
Dear All,

DB version :9.2.0.4
OS: Windows 2000

Installed HTMLDB without any error.
mod-pl sql is working fine.

I invoked HTML DB admin page using the follwoing URL

http://localhost/pls/simpledad/htmldb_admin
1)The page came up with out images(this is fine, still I can type username &
password)

2) After typing username & password, clicked on Login button,
it redirects me to
http://localhost/pls/simpledad/f?p=4550:10:6743357292495279243
and I got the java script error.(Object Expected)

Any idea what I would be missing here.

      Rating: 4
Got
      it  December 07, 2003
Reviewer:  Sami
       from NJ,USA
Alias /i/ "<HRML_DB_INSTALL_DIR>\htmldb\htmldb/images/" was missing in apache
hhtpd.conf file.

Thanks

      Rating: 4
result
      pagination  December 11, 2003
Reviewer:
      Robert  from CT
>> i particularly enjoy the pagination of result sets!

But owa_util.cellsprint already did that years ago...
What's so impressive with HTML DB way of doing this ?
(well...it has few more presentation options)
Better algorithm or performance ?

Thanks

          Followup:

it does it all.

no code.

templates (change look and feel immediately)

many times -- don't even have to write the sql.

navigation (tabs, etc)

big difference between "<tr><td>KING</td><td>1234</td></tr>" and a fully
navigatible application with all of the bells/whistles.

(but thanks, cellsprint was originally calls "cells_from_query" and I wrote that
:)

      Rating: 5
why not to use
      it for big sites  December 12, 2003
Reviewer:
      A reader
Hi Tom

 Regarding HTP, HTF, OWA_UTIL packages

i read in your forums, you mentioned to use this technology for small to medium
sites & cannot be used to build sites something of the size AMAZON.COM

 Can you ellabaorate a bit more on your statement. I would like why htp , htf
packages wont scale for such a high load

          Followup:

how many users do you think amazon has for example?

Now, a simple page in html db, lets say it takes 0.05 cpu seconds.

So, you get about 20 pages per second per cpu in the IDEAL world.  (if every
request arrives perfectly "normal" in sequence -- (if you overload, you do not
get anywhere NEAR 20 pages/second since context switching will start to kill
performance)

So, say you get a big whopping 64 cpu machine.  You could, in the IDEAL world,
do 1,280 pages per second. (you'll never get anywhere near this).

Thats not enough so you get a cluster -- say 4 64cpu machines.  Now, you might
be able to do 5,000 per second (in an ideal world, never will actually happen).

That probably still isn't enough for peak processing time.  That and consider
the pages on amazon might take a little more then 0.05 cpu seconds.

So, you introduce caching technologies.  Edge servers.  move processing out of
the database onto app server farms.  spread it out wide and fat.  You use lots
of smaller (cheaper) machines (price a 64 cpu box someday).

It is just "math".  I would say for most applications we ourselves (we being
you, me and most people building application for the places we work), a database
is all we'll ever need.  For the truly "large", hugmongous, many 10's to 100's
of developers building an application -- perhaps you need a different model.

      Rating: 3
To "A Reader":
      Why not use mod_plsql gor big sites  December 12, 2003

Reviewer:  Jens  from Germany
I could imagine, that also big sites would run with mod_plsql. But nobody tried
it.

Tom, do you know the biggest (hits + database size) mod_plsql-solution?

          Followup:

No, I don't -- I do know that when I ordered my aircard from verizon wireless
for my laptop, it used mod_plsql for example.

Its all over the place out there, sometimes you can see it, sometimes you
cannot.  mod_plsql can get you very far indeed.

      Rating:
      4
cellsprint  December 12, 2003
Reviewer:
      Robert  from CT
>>(but thanks, cellsprint was originally calls "cells_from_query" and I wrote
that
:)

Is that right...
I wish you put in a couple more parameters so as to have
option to display rows in alternate colors.
Do you think I should I hack it (owa_util) ?

Thanks

          Followup:

hey -- when I wrote it -- such things did not even EXIST in html!!!

the year was 1995.... (the last mod date I have on my copy of what I called
owaext - owa extensions) is March 15, 1996.  Not too many wild "html" options
back then.  Hard to anticipate all of the new extentions that have come along.

but sure, read the code out of the database -- rename it, modify it to your
hearts content (it is what i would do :)

I would not change owa_util, i would make my own.

      Rating: 5
Hack
      It?  December 12, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
> Do you think I should I hack it (owa_util) ?

SQL> select distinct owner from all_source where name = 'OWA_UTIL';

OWNER
------------------------------
SYS

1 row selected.

Anyone want to place bets on Tom's answer?

I am, of course, not speaking for Tom, but I am gonna step out on a limb and say
he might be against "hacking" stuff in the sys schema.  Possibly.

- Mark

          Followup:

;)

read it out
modify it
put it back in UNDER A DIFFERENT NAME in your OWN schema (not sys)

thats the ticket

      Rating:
      4
  December 13, 2003
Reviewer:  huss
       from kuwait
dear tom ,
i never use Mod_plsql ,owa_util or htp before ! so
where can i find a good referance for Mod_plsql ,owa_util  and HTML Packages ?
i need a nice referance from A to Z.

          Followup:

http://download-west.oracle.com/docs/cd/A97329_03/mix.902/q20102/toc.htm
is the list of all books - including:

mod_plsql User's Guide
PL/SQL Web Toolkit Reference

which is what you are looking for.

      Rating:
      5
  December 13, 2003
Reviewer:  huss
       from Kuwait
Thankx tom ,
you are great :-)

December 13, 2003 Eva -- Thanks for the question regarding "Oracle
      for Windows or Linux", version 9.2.0

      You Asked
Dear Tom,

I want to know, What the big different between Oracle running under Windows and
Linux ?
And if I want to migrate from windows to linux, what the significant impact that
I have ?

Best regards

Eva

      and we said...
other then linux is a much cooler OS -- no differnce.

That is the key fundemental thing about Oracle -- we are the same on all
platforms.  Write to us and you are portable.

Write OS specific code and you are not.

Your client application, whatever it is, will not know if you are on linux or
windows or a mainframe.
Review & Followup

      Rating: 5
Follow up
      Example  September 29, 2003
Reviewer:  Tyler D.
      Muth  from Reston, VA
As an example, the new feature of Oracle 10g called HTML DB (a web development
tool, htmldb.oracle.com) is written entirely in SQL and PL/SQL and therefore
lives 100% in the database.  We use the db as our virtual OS.  I regularly write
code against our Linux development instance during the day and my windows laptop
at night.  It compiles without fail on ALL platforms that Oracle runs on.  We
performed 0 (zero, zip, none, no) porting for an enitre web development tool to
make it availible on 25+ (however many Oracle runs on) platforms.

Two related comments.
1.  Same database version running the same version of our code, runs at LEAST
25% faster on Linux (RedHat Advanced Server 2.1) than Windows (XP).  I don't
think I'm allowed to give any numbers for 10g yet, but let's just say it's a
whole lot faster than 9iR2 running the same code.  10g + Lniux = blazing speed
(sorry for the marketing blurb, but our development team still can't get over
how much faster it is).
2.  If for some reason you are evaluating MySQL vs Oracle (not sure why, not
even in the same league), don't get burnt going from Windows where table and
column names are NOT case sensitive to Linux / Unix / Mac where table and column
names ARE case senstive.

Hope this helps,
Tyler

      Rating: 3
How about
      multiple process vs multiple thread  September 29, 2003

Reviewer:  Eva  from Indo
Dear Tom,

How about Multiple process in unix or linux family VS multiple thread in windows
? is there spesific impact on Oracle ?
If I migrate from windows to linux, can my database performance is increasing ?

          Followup:

windoze is an os that prefers threads.

linux is not.

we do threads on windows.
we do processes on unix

we do whatever the underlying os "likes"

      Rating: 4
Thread and
      Processes  September 30, 2003
Reviewer:  Jeremy
      Smith  from Salt Lake City, UT
As I understood it, the thread/process difference on UNIX has less to do with
raw speed and more to do with the fact that UNIX version have about ten
threading API's while only one for process creation and management.  To their
credit, the Linux kernel hackers have been doing great work on improving thread
creation and destruction.

I would expect that Oracle could benefit from switching to a multi-threaded
model...if they haven't already for some future version...

Here is some information from redhat:

http://people.redhat.com/drepper/nptl-design.pdf

      Rating: 2
Oracle for
      Windows or Linux  December 13, 2003
Reviewer:
      koth  from Columbus, OH
Dear Tom,

Regarding your statement

"Same database version running the same version of our code, runs at LEAST 25%
faster on Linux (RedHat Advanced Server 2.1) than Windows (XP)".

Aren't you comparing an enterprise server version linux to a desktop version of
Windows(XP). Is this comparision a true comparision.

I did some test by running the same database version running the same code on
RedHat Advanced version Server 3.0
and Windows 2000 Advanced Server on the Same Hardware(Pentium III 800 Mhz, 512
MB RAM). Oracle on Windows 2000 was running 25-40 % faster than RedHat AS 3.0.

I think you guys at Oracle know that oracle runs faster on windows but for some
reason promote Linux more than Windows.

Best regards koth

          Followup:

(that wasn't my statement bear in mind)

but, i beg to differ with you.  I do not know that Oracle on windows runs faster
then Oracle on linux.  Not at all.

tell me -- do you know linux enough to say "yup, linux was doing good on this
box".

I can honestly (and do honestly) say that I cannot say that I can say that about
windoze.  I found it personnally impossible to tune.

So, give me a linux server and away we go.

But -- NO -- i emphatically "do not know" any such thing that you attribute me
as "knowing" (and please don't put words in my mouth -- and I won't put any in
yours)

Actually -- please go back and read MY responses -- ok.  I seem to be saying
"hey, go with what you know -- that more then anything is what is relevant"

I've said it over and over and over again.  When asked at talks -- I give the
same answer "what do you know, what do you like, what do you have experience in
-- fine, go for it"

I can give you my answer "I like linux"
I now can guess what your answer would be -- and you should go for it.

      Rating: 5
If you like
      Windows...  December 13, 2003
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
No, this is not a flame thread! :)

If you like Windows then you might be excited about the "fibre mode" for Oracle
10g on Windows.  I saw a presentation on it at OracleWorld, but have no
experience with it yet myself.  Basically instead of threads inside of the
Oracle process, Oracle can now utilize fibres...

Anyway, just thought that was interesting.

- Mark

      Rating: 3
Oracle for
      Windows or Linux  December 14, 2003
Reviewer:
      koth  from Columbus, OH
Dear Tom,

I am extremelly sorry if i offended you or anyone. i didn;t mean to.
I agree with your statement "what do you know, what do you like, what do you
have experience in "

Personally i like Windows and feel that a DBA with good understanding of the
windows OS will tune and optimize the database the same way on Linux.

best regards koth

      Rating:
      5
  December 15, 2003
Reviewer:
      A reader
That's interesting Oracle DBA likes windoze..hhhmmm...

      Rating:
      2
Nice  December 29, 2003
Reviewer:
      Siva  from Hyd,India
Dear Sir,
One very cheap question for you.My browser misses the
 <Back> button on left top of the browser screen.Is there any keyboard shortcut
for that?

December 13, 2003 Elie -- Thanks for the question regarding "XML
      Questions", version 9.2.0

      You Asked
Hi, Tom.

I recently saw an answer you gave with respect to creating XML from a SQL query.

Here is the code that I copied from your answer.

==========================================================
create or replace package xml_handler_pack is
    procedure xml_data( p_sql         in varchar2,
                        p_max_rows in number );
end xml_handler_pack;
/

create or replace package body xml_handler_pack is

       procedure print_xml(p_xml in out nocopy clob) is
         l_xml  varchar2(30000);
         l_line varchar2(2000);
         l_amt  pls_integer := 30000;
         l_idx  pls_integer := 1;
       begin
         for i in 1 .. ceil(dbms_lob.getlength(p_xml)/l_amt) loop
           l_xml := dbms_lob.substr(p_xml, l_amt, l_idx);
           loop
             exit when l_xml is null;
             l_line := substr(l_xml, 1, instr(l_xml, chr(10))-1);
             dbms_output.put_line('|' || l_line);
             l_xml := substr(l_xml, instr(l_xml, chr(10))+1);
           end loop;
         end loop;
       end print_xml;

       procedure xml_data( p_sql         in varchar2,
                           p_max_rows in number ) is
         l_ctx    dbms_xmlquery.ctxType;
         l_xml    clob;
       begin
         dbms_lob.createtemporary( l_xml, true, dbms_lob.session );
         l_ctx := dbms_xmlquery.newContext( p_sql );

         -- setup the context
         dbms_xmlquery.setRaiseNoRowsException( l_ctx,true );
         dbms_xmlquery.setMaxRows( l_ctx, p_max_rows );

         -- get the xml fm the context
         l_xml := dbms_xmlquery.getXML( l_ctx );

         -- print out the xml result set
         print_xml( l_xml );

         -- determine the no of rows processed
         dbms_output.put_line( '' );
         dbms_output.put_line( 'No. of rows processed: ' ||
                               dbms_xmlquery.getnumrowsprocessed( l_ctx ) );

         dbms_xmlquery.closeContext(l_ctx);
       exception
         when others then
         dbms_xmlquery.closeContext(l_ctx);
       end xml_data;

end xml_handler_pack;
/
==========================================================

I then ran this code as in the following:

set serveroutput on size 1000000

begin
        xml_handler_pack.xml_data('&query', &num_rows_desired);
end;
/

This code displayed an XML listing of my SQL result set.

However, I've noticed that only non-null columns are being
displayed.  Any columns that are NULL are not shown.

Here's an example of what I mean.

==========================================================
SQL> create table t (c1 number, c2 number);

SQL> Table created

SQL> insert into t values (1,1);
SQL> insert into t values (2,2);
SQL> insert into t values (3,3);
SQL> insert into t values (NULL,NULL);
SQL> insert into t values (5,NULL);
SQL> insert into t values (NULL,6);

SQL> commit;

SQL> select * from t;

        C1         C2
---------- ----------
         1          1
         2          2
         3          3

         5
                    6

6 rows selected.

SQL> @run_xml_handler_pack
Enter value for query: select * from t
Enter value for num_rows_desired: 1000
|<?xml version = '1.0'?>
|<ROWSET>
|   <ROW num="1">
|      <C1>1</C1>
|      <C2>1</C2>
|   </ROW>
|   <ROW num="2">
|      <C1>2</C1>
|      <C2>2</C2>
|   </ROW>
|   <ROW num="3">
|      <C1>3</C1>
|      <C2>3</C2>
|   </ROW>
|   <ROW num="4"/>
|   <ROW num="5">
|      <C1>5</C1>
|   </ROW>
|   <ROW num="6">
|      <C2>6</C2>
|   </ROW>
|</ROWSET>
No. of rows processed: 6

PL/SQL procedure successfully completed.
==========================================================

ROW num="4" shows no values at all because both columns in this row are NULL.
And ROW num="5" shows only the non-NULL C1 column whereas ROW num="6" shows
only the non-NULL C2 column.

1) Is there some way to have Oracle display the NULL columns as well as the
non-NULL columns
   in the generated XML listing?

2) Is there a way to re-name the titles "ROWSET" and "ROW" and "num=" in the XML
listing?

3) I saw on your website how to create an ASCII file from this generated XML
listing.
   I have a need to automatically FTP such an XML file to some address.  I
realize
   that I can do this via a Bourne-Shell script (on UNIX) but I was wondering if
   Oracle is able to FTP this XML file directly?

Thank you in advance for your help.

      and we said...
I asked Sean Dillon, our local XML Technologist, to take a look at this and
here's what he had to say:
---------------------------

Most of what you are asking is INCREDIBLY easy to do.  Not only can you do what
you asked for but there are a variety of other features available to
DBMS_XMLQuery, just check out Chapter 20 in the XML API Reference for Oracle9i.

1)  Is there some way to have Oracle display the NULL columns as well as the
non-NULL columns in the generated XML listing?  Yes, there is, although the XDK
places a boolean attribute inside the element that has the null value (i.e.,
<ROW num="4"><C1 NULL="TRUE"><C2 NULL="TRUE"></ROW>)

2)  Is there a way to re-name the titles "ROWSET" and/or "ROW" and "num=" in the
generated XML?  Here's a modified example of your code from above:

SQL> create or replace package xml_handler_pack is
  2      procedure xml_data( p_sql            in varchar2,
  3                          p_max_rows       in number,
  4                          p_num_val        in varchar2 default 'num',
  5                          p_show_null_cols in boolean  default false,
  6                          p_rowsettag      in varchar2 default 'ROWSET',
  7                          p_rowtag         in varchar2 default 'ROW' );
  8  end xml_handler_pack;
  9  /
Package created.

SQL> create or replace package body xml_handler_pack is
  2
  3         procedure print_xml(p_xml in out nocopy clob) is
  4           l_xml  varchar2(30000);
  5           l_line varchar2(2000);
  6           l_amt  pls_integer := 30000;
  7           l_idx  pls_integer := 1;
  8         begin
  9           for i in 1 .. ceil(dbms_lob.getlength(p_xml)/l_amt) loop
 10             l_xml := dbms_lob.substr(p_xml, l_amt, l_idx);
 11             loop
 12               exit when l_xml is null;
 13               l_line := substr(l_xml, 1, instr(l_xml, chr(10))-1);
 14               dbms_output.put_line('|' || l_line);
 15               l_xml := substr(l_xml, instr(l_xml, chr(10))+1);
 16             end loop;
 17           end loop;
 18         end print_xml;
 19
 20
 21         procedure xml_data( p_sql            in varchar2,
 22                             p_max_rows       in number,
 23                             p_num_val        in varchar2 default 'num',
 24                             p_show_null_cols in boolean  default false,
 25                             p_rowsettag      in varchar2 default 'ROWSET',
 26                             p_rowtag         in varchar2 default 'ROW' ) is
 27           l_ctx    dbms_xmlquery.ctxType;
 28           l_xml    clob;
 29         begin
 30           dbms_lob.createtemporary( l_xml, true, dbms_lob.session );
 31           l_ctx := dbms_xmlquery.newContext( p_sql );
 32
 33           -- setup the context
 34           dbms_xmlquery.setRaiseNoRowsException( l_ctx,true );
 35           dbms_xmlquery.setMaxRows( l_ctx, p_max_rows );
 36           dbms_xmlquery.setRowsetTag( l_ctx, p_rowsettag );
 37           dbms_xmlquery.setRowTag( l_ctx, p_rowtag );
 38           dbms_xmlquery.useNullAttributeIndicator( l_ctx, p_show_null_cols
);
 39           if p_num_val is null then
 40             dbms_xmlquery.useNullAttributeIndicator( l_ctx, p_show_null_cols
);
 41           elsif p_num_val = 'num' then
 42             null;
 43           else
 44             dbms_xmlquery.setRowIdAttrName( l_ctx, p_num_val );
 45           end if;
 46
 47           -- get the xml fm the context
 48           l_xml := dbms_xmlquery.getXML( l_ctx );
 49
 50           -- print out the xml result set
 51           print_xml( l_xml );
 52
 53           -- determine the no of rows processed
 54           dbms_output.put_line( '' );
 55           dbms_output.put_line( 'No. of rows processed: ' ||
 56                                 dbms_xmlquery.getnumrowsprocessed( l_ctx )
);
 57
 58           dbms_xmlquery.closeContext(l_ctx);
 59         exception
 60           when others then
 61           dbms_xmlquery.closeContext(l_ctx);
 62         end xml_data;
 63
 64  end xml_handler_pack;
 65  /
Package body created.

SQL> drop table t;
Table dropped.

SQL> create table t (c1 number, c2 number);
Table created.

SQL> insert into t values (1,1);
1 row created.

SQL> insert into t values (2,2);
1 row created.

SQL> insert into t values (3,3);
1 row created.

SQL> insert into t values (NULL,NULL);
1 row created.

SQL> insert into t values (5,NULL);
1 row created.

SQL> insert into t values (NULL,6);
1 row created.

SQL> commit;
Commit complete.

SQL> select * from t;

        C1         C2
---------- ----------
         1          1
         2          2
         3          3

         5
                    6
6 rows selected.

SQL> -- two examples, first your original then a new one w/ some of your
requested functionality
SQL> begin
  2    xml_handler_pack.xml_data( 'select * from t', 1000 );
  3    xml_handler_pack.xml_data( p_sql            => 'select * from t',
  4                               p_max_rows       => 1000,
  5                               p_num_val        => null,
  6                               p_show_null_cols => true,
  7                               p_rowsettag      => 'VALUESET',
  8                               p_rowtag         => 'VALUES' );
  9  end;
 10  /
|<?xml version = '1.0'?>
|<ROWSET>
|   <ROW num="1">
|      <C1>1</C1>
|      <C2>1</C2>
|   </ROW>
|   <ROW num="2">
|      <C1>2</C1>
|      <C2>2</C2>
|   </ROW>
|   <ROW num="3">
|      <C1>3</C1>
|      <C2>3</C2>
|   </ROW>
|   <ROW num="4"/>
|   <ROW num="5">
|      <C1>5</C1>
|   </ROW>
|   <ROW num="6">
|      <C2>6</C2>
|   </ROW>
|</ROWSET>
No. of rows processed: 6
|<?xml version = '1.0'?>
|<VALUESET>
|   <VALUES num="1">
|      <C1>1</C1>
|      <C2>1</C2>
|   </VALUES>
|   <VALUES num="2">
|      <C1>2</C1>
|      <C2>2</C2>
|   </VALUES>
|   <VALUES num="3">
|      <C1>3</C1>
|      <C2>3</C2>
|   </VALUES>
|   <VALUES num="4">
|      <C1 NULL="TRUE"/>
|      <C2 NULL="TRUE"/>
|   </VALUES>
|   <VALUES num="5">
|      <C1>5</C1>
|      <C2 NULL="TRUE"/>
|   </VALUES>
|   <VALUES num="6">
|      <C1 NULL="TRUE"/>
|      <C2>6</C2>
|   </VALUES>
|</VALUESET>
No. of rows processed: 6

3) Can Oracle FTP files automatically?  Absolutely, you can use a Java Stored
Procedure to do this for you.  Mark Piermarini, a teammate of mine, co-authored
a book entitled "Oracle9i Java Programming" by Wrox Press Ltd.  In this book,
there's an example of FTP'ing from the database using a Java stored procedure.
You could simply write a PL/SQL wrapper to this Java stored procedure, and then
you can FTP any time you need to.  You could also mix this with DBMS_JOB to
automate the sending of files to another FTP site right out of the database with
no user intervention whatsoever!

One other thing I wanted to mention, since you are generating XML from the
database, and you are using Oracle9iR2, you can also use SQLX to generate
instead of the XML SQL Utility (XSU).  SQLX is an emerging standard which
consists of SQL operators for generating XML from relational databases.  The XSU
will work just fine, but SQLX gives you more granular control of what is
generated.

SQL> select xmlelement("ROWSET",
  2           xmlagg(xmlelement("ROW",
  3             xmlattributes(rownum as "num"),
  4             xmlforest(c1, c2)))) as "RESULT"
  5    from t;

RESULT
---------------------------------------------------------------------------------
-------------------
<ROWSET>
  <ROW num="1">
    <C1>1</C1>
    <C2>1</C2>
  </ROW>
  <ROW num="2">
    <C1>2</C1>
    <C2>2</C2>
  </ROW>
  <ROW num="3">
    <C1>3</C1>
    <C2>3</C2>
  </ROW>
  <ROW num="4"/>
  <ROW num="5">
    <C1>5</C1>
  </ROW>
  <ROW num="6">
    <C2>6</C2>
  </ROW>
</ROWSET>

You can read more about SQLX and SQLX operators here:
http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96620/xdb12gen.htm#1027862
Hope that helps!

_smd_
_____________________________________________________________________
Sean Dillon - Principal Technologist, Oracle Corporation
Author "Beginning Oracle Programming"

http://www.amazon.com/exec/obidos/ASIN/186100690X
Review
      & Followup

      Rating: 5
XML
      Questions  December 19, 2002
Reviewer:  Elie
      Grunhaus  from Rochester, NY USA
Thanks so much.  This response is the kind of platform I can jump off into
learning more about how Oracle handles XML.

You and your merry helpers (Sean, in this case) are quite an asset to the Oracle
community.

      Rating: 5
Few simple XML
      Doubts  March 30, 2003
Reviewer:  A friend of
      you (Shall I?)  from BAH
I know how to write an XML file an how to represent data.

My doubts are:

1. Why do we need XML. What is its advantage than a comma separated text file,
if it defines the field names on top? An XML file do contain Junk field
description with each datum. Right?

2. Where is the security for XML if everything is readable?

3. Where exactly is the XML used? What  is its advantage over other database
mechanisms? Is it only the simplicity of representation?

4. Even now, we are able to transfer data by various means. If so, where is the
use of XML?

Thanks a bunch!

          Followup:

1) represent for me a purchase order in a reasonable fashion that any program
could at least parse and represent it on screen as you could with XML.

Variation on this question -- Why do we need HTML?

Now, going a step further with this purchase order which has a header record and
many detail records -- it also has structured fields such as "ship to address"
and "bill to address" that must conform to a set of rules (a schema or DTD).
Flat files have no such "rules".  Hence, given an XML input from a trading
partner that is to conform to a certain DTD implies something about its content.
 You can in fact run a tool against the XML document to verify it conforms to
the agreed upon standard interchange format.

And so on.

2) is it?  or might encryption play a role?

3) I don't really see XML as a database mechanism, not any more then JPEG,
Raster, Tiff, Video, etc.  It is a data exchange format.  We are all talking the
same language.  Look at common webservices implementations.  What a great
standard way to format a request (give inputs) and recieve a response.

4) it brings ordered structure to chaos.  Why do we need HTML? Answer that and
you have most of the answer for why XML.

      Rating: 5
I
      understood!  March 30, 2003
Reviewer:  TH
       from BAH
I understood! I understood!

I like the 3rd point in your answer.

So XML is not a Wonder or Magic. It's a requirement. Right?

          Followup:

it is not a requirement -- it is a technical solution to a requirement.

requirement "easy exchange of data"
possible solution "XML"

      Rating: 5
Does Oracle use
      this technique?  March 30, 2003
Reviewer:
      TH  from Manama, BAH
Dear Tom,

Does Oracle use some mechanism to search an XML and replace the repeating Field
Description with a rather small description(say id) and tie the id and
description at the end of the XML - While transfering from one location to
another? Won't this technique make the XML smaller in size while transfering?

This technique is only for transfering...

Example:

1 means <NAME>
2 means <ADDRESS1>
3 means <ADDRESS2>

like that?

A simple find and replace kind of stuff!

- Thaha Hussain

          Followup:

No, the xml is stored "as is".

      Rating: 5
How do you
      represent Binary Data with XML  April 01, 2003

Reviewer:  TH  from Manama, BAH
That's OK!

How do you represent Binary Data with XML? Or it is not supported in XML?

          Followup:

there is a way, there is a cdata tag but for many reasons -- it should be
avoided.  You can easily base64 encode the data -- that is what we would
suggest.

      Rating: 3
pros and cons
      of xml generation types  April 26, 2003
Reviewer:
      YK from India
Tom/Sean Dillon,

>since you are generating XML from the database, and you are using Oracle9iR2,
you can also use SQLX to generate instead of the XML SQL Utility (XSU).

i see so many ways of generating xml from db,

Generating XML from the Database Using SQLX Functions
Generating XML from Oracle9i Database Using DBMS_XMLGEN
Generating XML Using Oracle-Provided SQL Functions
Generating XML Using XSQL Pages Publishing Framework
Generating XML Using XML SQL Utility (XSU)
.
.
.
.
since i am new to XML, i find it confusing to proceed with,..
1. can you give me the different ways of generating xml (high level view), if
the above one is not correct.
2. which one i should choose for generating xml from 9iR2. pros and cons of each
3. which is the most flexible and scalable way of generating xml.

Thanks a lot

          Followup:

have you read the application developers guide for XML supplied in the
documentation set?  It'll help you through these issues.

there is no single answer (or there would be but one API).  It depends on your
needs, your language, your application development environment.

      Rating: 2
just getting
      frustrated  December 11, 2003
Reviewer:  George
      Liblick  from Drexel Hill, PA
Tom,

This XML stuff seems like it should be easy enough, but then I  can't even get
your examples to work with my data :-(   I have reduced my problem to a simple
example and ask, please, that you help me to understand what I'm doing wrong (I
suspect I don't truly understand the XMLAGG function.)

I have the table:

SQL> select * from acct_feature_list
  2  /

   ACCT_NO FEATURE_NO FEATURE_IND
---------- ---------- -----------
      1107          1           1
      1107          2           0
      1107          3           1
      1107          7           0

and I need to generate an XML document that looks like:

XML_DOC
----------------------------------------------------
<account>
  <ariaacctno>1107</ariaacctno>
  <service>
    <serviceno>1</serviceno>
    <serviceprovind>1</serviceprovind>
  </service>
  <service>
    <serviceno>2</serviceno>
    <serviceprovind>0</serviceprovind>
  </service>
  <service>
    <serviceno>3</serviceno>
    <serviceprovind>1</serviceprovind>
  </service>
  <service>
    <serviceno>7</serviceno>
    <serviceprovind>0</serviceprovind>
  </service>
</account>

but the best I have been able to do (afer several days), is:

SQL> get latest
  1  select xmlelement( "account",
  2                             xmlforest( acct_no "acctno",
  3          xmlagg(c2) "service" )) XML_DOC
  4  from ( select acct_no,
  5         xmlforest( feature_no "serviceno", feature_ind "serviceprovind" ) c2
  6         from acct_feature_list ) src
  7* group by acct_no
SQL> /

XML_DOC
----------------------------------------------------
<account>
  <acctno>1107</acctno>
  <service>
    <serviceno>1</serviceno>
    <serviceprovind>1</serviceprovind>
    <serviceno>2</serviceno>
    <serviceprovind>0</serviceprovind>
    <serviceno>3</serviceno>
    <serviceprovind>1</serviceprovind>
    <serviceno>7</serviceno>
    <serviceprovind>0</serviceprovind>
  </service>
</account>

my database is 9.2

thanks for this site, thanks for your help.

George

          Followup:

I'll ask Sean to take a peek at this as well, but my first thoughts were
dbms_xmlgen (its the one I keep falling back to for simplicity :)

this is almost what you want -- and I'm sure using dbms_xmlgen, you can get it
dead on

ops$tkyte@ORA920> select dbms_xmlgen.getxml
  2  ( 'select acct_no, cursor(select feature_no "serviceno", feature_ind
"serviceprovind"
  3       from t t2
  4      where t2.acct_no = t1.acct_no) "service"
  5       from (select distinct acct_no from t) t1' )
  6    from dual;

DBMS_XMLGEN.GETXML('SELECTACCT_NO,CURSOR(SELECTFEATURE_NO"SERVICENO",FEATURE_IND
--------------------------------------------------------------------------------
<?xml version="1.0"?>
<ROWSET>
 <ROW>
  <ACCT_NO>1107</ACCT_NO>
  <service>
   <service_ROW>
    <serviceno>1</serviceno>
    <serviceprovind>1</serviceprovind>
   </service_ROW>
   <service_ROW>
    <serviceno>2</serviceno>
    <serviceprovind>0</serviceprovind>
   </service_ROW>
   <service_ROW>
    <serviceno>3</serviceno>
    <serviceprovind>1</serviceprovind>
   </service_ROW>
   <service_ROW>
    <serviceno>7</serviceno>
    <serviceprovind>0</serviceprovind>
   </service_ROW>
  </service>
 </ROW>
</ROWSET>

ops$tkyte@ORA920>

------------------------------------------------------------------

Hi George, Sean Dillon here.

You were close.  Instead of burying the XMLAGG inside your element, I simply
query it as a separate column and it gets placed into the resulting XML document
following the <acct_no> element.  By breaking that out as a separate query (much
the same way Tom did in his earlier example), we're able to query the <service>
element's data separately, keying it back to the distinct acct_no outer query:

select xmlelement( "account" ,
         xmlforest( acct_no as "ariaacctno" ),
         ( select xmlagg( xmlelement( "service",
                            xmlforest( feature_no as "serviceno",
                                       feature_ind as "serviceprovind" )
                          )
                  )
             from t
            where t.acct_no = t2.acct_no
         )
       ) xmldoc
  from ( select distinct acct_no from t ) t2
/

...yields...

<account>
  <ariaacctno>1107</ariaacctno>
  <service>
    <serviceno>1</serviceno>
    <serviceprovind>1</serviceprovind>
  </service>
  <service>
    <serviceno>2</serviceno>
    <serviceprovind>0</serviceprovind>
  </service>
  <service>
    <serviceno>3</serviceno>
    <serviceprovind>1</serviceprovind>
  </service>
  <service>
    <serviceno>7</serviceno>
    <serviceprovind>0</serviceprovind>
  </service>
</account>

Plenty of examples and good information on XMLAGG can be found in the Database
Developer's Guide - Oracle XML DB in Chapter 10 Generating XML Data from the
Database (page 10-17 XMLAgg() Function).

Hope that helps!

December 13, 2003 Sridevi -- Thanks for the question regarding
      "Combination of Locally Managed and Dictionary Managed TS", version 9i
      Release 2

      You Asked
In a Oracle 9i Database, we are planning to have a Database with a combination
of Locally Managed and Dictionary managed tablespaces. Is this possible?

The basic idea is that the Master and Transaction Tables will be dictionary
managed. Other temporary tables where the data is often truncated and inserted
through procedures (for generating reports), will be in a locally managed
tablespace.These tables are temporary in the sense that data stored in them is
temporary. But the table structure is pre-defined. There are scenrios where we
prefer to use such temporary tables instead of global temporary tables.

With your vast experience, can you tell us if there are any drawbacks/
limitations in using such a combination of tablecpaces in the same database - if
it is possibl to use such a combintion in the first place.

Thank You

      and we said...
Yes, but you'll have to create system as DICTIONARY managed, else you'll get

12913, 00000, "Cannot create dictionary managed tablespace"
// *Cause: Attemp to create dictionary managed tablespace in database
//         which has system tablespace as locally managed
// *Action: Create a locally managed tablespace.

Why Why Why do you want to use the slower, dumber, bad dictionary managed
tablespaces?  What possible theoritical (but wrong) reasoning could you have
behind that?

The drawbacks are -- that dictionary managed tablespaces are bad, locally
managed tablespaces are good and there is no reason to use a DMT at all.

Review &
      Followup

      Rating: 4
Combination of
      Locally Managed and Dictionary Managed TS  September 15,
      2002
Reviewer:  Ravi  from Herndon, VA
From your answer you said we have to create system TS as dictionary managed, if
we want to use both locally managed and dictionary managed tablespaces in DB.
Does it mean can I create system TS with locally managed extent management? What
I understood uptill 9iR2 is, system tablespace has to be created dictionary
managed at all times!!

          Followup:

And in 9iR2, system can be locally managed and in fact the DEFAULT is for system
to be locally managed (making dictionary managed OBSOLETE)

      Rating: 4
question about
      SPFILE and dictionary extent management  December 13, 2003

Reviewer:  Fernando Sanchez  from Madrid, Spain

I get the ORA-12913 error when trying to create a dictionary extent managed
tablespace.

I understand that Oracle does not recommend but I'd like to know if there is a
way to modify my instance spfile in order to allow that management.

          Followup:

nothing to do with spfiles.

it is a choice you made at database creation time, you created the system
tablespace as an LMT.

that means -- all other tablespaces will be, that database has no support for
DMT's.

As there are no reasons I can dream of to use a DMT, this is a "good thing"

[tkyte@tkyte-pc-isdn tkyte]$ oerr ora 12913
12913, 00000, "Cannot create dictionary managed tablespace"
// *Cause: Attemp to create dictionary managed tablespace in database
//         which has system tablespace as locally managed
// *Action: Create a locally managed tablespace.
[tkyte@tkyte-pc-isdn tkyte]$

      Rating: 5
temp tablespace
        December 13, 2003
Reviewer:  Reader

Tom, in 9i R2 database, can i create a temp tablespace with datafile clause
using CREATE TABLESPACE datafile ....... TEMPORARY syntax? or should it be
CREATE TEMPORARY syntax with tempfile clause? thanks.

          Followup:

with tempfiles.

December 13, 2003 Deepak -- Thanks for the question regarding "SQL
      LOADER VS. Normal Insert Statements", version 9i

      You Asked
Hi,

Putting up a question for the first time.

Please find the scenario.

1. We have an highly OLTP based enviornement, using 3 tier architecture for
prodcution application.

2. Using Oracle 9i database and Websphere 5.0 as the application server.

3. We have a upload module which picks up a row from Text or CSV file and
inserts it to the databases, these inserts are not done in batches but are
independently inserted i.e. Each row is inserted and commited ... one row at one
time.

4. The problem is that this upload module is impacting our production and making
the response slow...we have observed that when the uploader is run .. the
Application response to the users become slow.

5. We have done an index analysis and found that index are appropriate.

Now the Question is that :

We are thinking of using SQLLOADER instead of custom made Uploader..but we dont
have a comparison of the perormance (I/O, Memory and CPU utilisation) of SQL
LOADER Vs. Normal Insert statements ....please let me know which will be the
best option in our scenario.

REgards,
Deepak Uniyal

      and we said...
sqllodr will quite simply blow away like crazy and row by row (which is the same
as "slow by slow") processing you've done in the middle tier.

BLOW IT AWAY.

literrally.

it will be lots less code (well, none really).

Actually -- there is something even better if you get the file to the server --
you can use an external table and just "insert into real_table select * from
FILE", your loader will be reduced to ONE LINE of sql!

At the very very very least, you must BATCH the inserts and COMMIT AFTER THE
LAST ROW is inserted (tell me, what happens when your app server crashes after N
rows of N+M rows are processed??? how do you restart that properly -- use
transactions for what they are designed for)

I hope (pray) that you are at least using bind variables -- but you must batch
them, if you can use EXTERNAL TABLES, use them, if you can use SQLLDR use that.
Write as little code as you can.

Review &
      Followup

      Rating: 2
Thanks
        December 13, 2003
Reviewer:  Deepak
       from INDIA
HI Tom,

Thanks a ton for the response...wud be testing the Inserts through Batches and
SQLLOADER and monitoring the performance

Regards,
Deepak Uniyal

      Rating: 5
This is a FIVE
      STAR answer, not only TWO.... :-(  December 15, 2003

Reviewer:  Franco  from Denmark
At least give the right prize to Tom and his wonderful work to support our
day-by-day tasks!

      Rating:
      5
  December 15, 2003
Reviewer:
      A reader
Right price & complete words ( don't use a broken keyboard ).

December 14, 2003 Venkatesh -- Thanks for the question regarding
      "FORALL does not work", version 9.2

      You Asked
Dear Tom,

Thanx for the wonderful site.

I have a couple of questions...

1) I am trying to use FORALL in my code.. and i am getting the following error

PLS-00436: implementation restriction: cannot reference fields of BULK In-BIND
table of records

i am trying to do it in the following way.

FORALL i in tabType.first..tabType.last
  INSERT INTO t1 (c1, c2, c3)
  VALUES (tabType(i).c1, tabType(i).c2, tabType(i).c3));

is there anything wrong in the above code???

2) Is there any way to have a search in a PL/SQL table like wht we do for a
normal tables...

The thing is i am trying to search for a string in a column in a PL/SQL table
(which has millions of records) with FOR LOOP, which consumes lot of time. is
there any optimized way to do the above task?

Thanx in advance.

Venkatesh

      and we said...
1) you cannot insert from a table of records -- a record of tables, yes, table
of records -- no.

Now, in 9ir2 -- if your record maps column by column (postitionally -- not by
name) to the base table, you can:

ops$tkyte@ORA9IR2> create table t ( x int, y int );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2     type array is table of t%rowtype index by binary_integer;
  3     l_data array;
  4  begin
  5     l_data(1).x := 1;
  6     l_data(1).y := 2;
  7     l_data(2).x := 3;
  8     l_data(2).y := 4;
  9
 10     forall i in 1 .. l_data.count
 11        insert into t values L_DATA(i);
 12  end;
 13  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> select * from t;

         X          Y
---------- ----------
         1          2
         3          4

2) if you use a sql type -- yes:

ops$tkyte@ORA9IR2> create or replace type myScalarType as object
  2  ( x int, y int )
  3  /

Type created.

ops$tkyte@ORA9IR2> create or replace type myArrayType as table of myScalarType
  2  /

Type created.

ops$tkyte@ORA9IR2> declare
  2     l_data myArrayType := myArrayType( myScalartype(1,2), myScalartype(3,4)
);
  3  begin
  4     insert into t select * from TABLE( cast(l_data as myArrayType) );
  5
  6     for x in ( select * from TABLE( cast(l_data as myArrayType) ) T where
T.x = 1 )
  7     loop
  8        dbms_output.put_line( x.x );
  9        dbms_output.put_line( x.y );
 10     end loop;
 11  end;
 12  /
1
2

PL/SQL procedure successfully completed.

that shows how to "bulk insert" from the collect and how to query it.
Review &
      Followup

      Rating: 4
Did I miss
      something???  December 10, 2003
Reviewer:
      Jon  from Sydney
Didn't Venkatesh say he had a PL/SQL table with *millions* of rows in it?   Bet
ya it'll be slow whatever way you slice it or dice it.  How do you get millions
of rows into a PL/SQL table anyway... lots of spare RAM?  Maybe a convential
table, with appropriate indexing, might be better in this case...

          Followup:

excellent point - just read the searching part.

anything more then a couple of hundred, maybe - maybe thousand to 1's of
thousands of entries sure.

millions -- forget about it, erase the answer above, nothing will be efficient
using a plsql table

      Rating: 3
What about
      temporary tables  December 11, 2003
Reviewer:
      Martien van den Akker  from The Netherlands
Isn't it an idea to use temporary tables for this purpose? At Oracle in the
Netherlands our Headstart group developed CDM Ruleframe. A framework for
business rules in the application. It used to use pl/sql tables for building a
rule-stack and a stack of 'touched records'. This was a perfomance issue for
tables with many business-rules and for batch jobs. Now the pl/sql tables are
replaced by temp-tables and de performance raised significantly.

          Followup:

i would question even the use of temporary tables -- why not just work on the
real ones.  I've used temp tables for one purpose myself - to hold temporary
input data that needed to be processed (like an interface table)

      Rating: 3
PL/SQL into
      Refcursor  December 14, 2003
Reviewer:
      Venkatesh  from Chennai, India
Fine Tom,

you have also suggested the FOR LOOP for searching a string in PL/SQL table. Is
it possible to transfer the records in the pl/sql table to REF CURSOR???

          Followup:

open ref_cursor
for
select * from TABLE( cast(l_data as myArrayType) ) T;

"transfers" records from the plsql "table" to a "ref cursor"

December 14, 2003 -- Thanks for the question regarding "how to do
      this in sqlplus", version 9.2.0.1

      You Asked
Hi

I am trying to make a sql script for sqlplus so when you pass several index
names it returns index columns

select a.owner, a.table_name,
       a.index_name,
       b.column_name,
       a.num_rows,
       a.clustering_factor,
       a.distinct_keys
  from dba_indexes a, dba_ind_columns b
 where a.table_owner = b.table_owner
   and a.index_name = b.index_name
   and a.table_name = b.table_name
   and a.index_name in ( '''' || replace('&1', ' ', ''',''') || '''' )
order by column_position;

it does not work... I dont understand why, do you know?

      and we said...
ops$tkyte@ORA920> @test "CBT_PK DEPT_PK"
old  11:    and a.index_name in ( '''' || replace('&1', ' ', ''',''') || '''' )
new  11:    and a.index_name in ( '''' || replace('CBT_PK DEPT_PK', ' ', ''','''
) || '''' )

ops$tkyte@ORA920> select '''' || replace('CBT_PK DEPT_PK', ' ', ''',''' ) ||
'''' from dual;

''''||REPLACE('CBT
------------------
'CBT_PK','DEPT_PK'

you see what is happening here -- unless you have an index named

'CBT_PK','DEPT_PK'

you aren't getting any data.  You are thinking perhaps the replace happens AND
THEN the query -- but the replace is part of the query itself.  a function to be
evaluated at runtime.

You can try this technique....

column inlist new_val indexes

select '''' || replace(upper('&1'), ' ', ''',''') || '''' INLIST from dual;

select a.owner, a.table_name,
       a.index_name,
       b.column_name,
       a.num_rows,
       a.clustering_factor,
       a.distinct_keys
  from dba_indexes a, dba_ind_columns b
 where a.table_owner = b.table_owner
   and a.index_name = b.index_name
   and a.table_name = b.table_name
   and a.index_name in ( &INDEXES )
order by index_name, column_position;
Review &
      Followup

      Rating:
      5
Fantastic!!  December 13, 2003
Reviewer:
      A reader

      Rating:
      5
whitespace  December 14, 2003
Reviewer:
      A reader
Hi

In Oracle 8i and 9i how can we deal with whitespaces as regular expressions so
instead of

replace(string, ' ', ',')

to something like

replace(string, 'ANY_WHITESPACE_OR_TABS', ',')

No such functionality in these version right?

          Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:13912710295209

      Rating: 5
evaluated at
      run time?  December 14, 2003
Reviewer:
      A reader
What do you mean evaluated at run time?

ops$tkyte@ORA920> @test "CBT_PK DEPT_PK"
old  11:    and a.index_name in ( '''' || replace('&1', ' ', ''',''') || '''' )
new  11:    and a.index_name in ( '''' || replace('CBT_PK DEPT_PK', ' ', ''','''

) || '''' )

You pass 'CBT_PK','DEPT_PK' to IN isnt that good enough?

          Followup:

they didn't not pass in 'CBT_PK', 'DEPT_PK', they wanted to pass in a space
separated list.  we had to convert to quotes and commas.

December 14, 2003 Shah -- Thanks for the question regarding
      "SQ*Plus /nolog", version 7.3.3.6

      You Asked
Can you please explain to me, what happens if I issue
"sqlplus /nolog" command. Am I connecting to the database?

Thanks,

      and we said...
From the sqlplus guide:

<quote>
/NOLOG
Establishes no initial connection to Oracle. Before issuing any
SQL commands, you must issue a CONNECT command to establish a
valid logon. Use /NOLOG when you want to have a SQL*Plus command
file prompt for the username, password, or database
specification. The first line of this command file is not
assumed to contain a logon.
</quote>
Review & Followup

      Rating: 4
Why does
      login.sql run ?  October 08, 2003
Reviewer:
      Matt  from UK
Why does login.sql get executed on sqlplus /nolog ?

Is there any way to detect a /nolog and exit the login.sql when it is detected ?

Thanks in advance.

          Followup:

I have this in my login.sql:

set termout off
define gname=idle
column global_name new_value gname
select lower(user) || '@' || substr( global_name, 1, decode( dot, 0,
length(global_name), dot-1) ) global_name
  from (select global_name, instr(global_name,'.') dot from global_name );
set sqlprompt '&gname> '
set termout on

You cannot "exit" a script but you can look at &gname and when set to "idle"
just not "do something"

For example, you could have your login.sql be:

set termout off
define gname=idle
column global_name new_value gname
select 'real_login' global_name from global_name;
set termout on
@&gname

that'll either run a script idle.sql (when not connected) or real_login.sql
(when connected)

      Rating: 3
Not be able to
      login to sql  December 11, 2003
Reviewer:
      Jamil  from Saudi
Dear  Sir Tom
   I have install the developer  2000 in my workstation,but when I try to
connect to SQL
I am getting this message .

SQL*Plus: Release 3.3.2.0.2 - Production on Thu Dec 11 10:30:27 2003

Copyright (c) Oracle Corporation 1979, 1994.  All rights reserved.

ERROR: ORA-12505: TNS:listener could not resolve SID given in connect descriptor

Enter user-name:

My setting in the Register  Editor

NLS_LANG = AMERICAN_AMERICA.AR8MSAWIN
Local jflf

Best Regards

Jamil

          Followup:

means the sid in the connect descriptor is not the sid of the database you
thought you were trying to connect to (eg: tnsnames.ora entry is botched)

Here, I'll show an example using the tns connect string direct -- it shows, if
you use a junk SID, you get that error

$ /home/ora9ir2/bin/sqlplus
'scott/tiger@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(Host=localhost.localdomain)(Por
t=1521))(CONNECT_DATA=(SID=garbage)))'

SQL*Plus: Release 9.2.0.4.0 - Production on Thu Dec 11 10:54:38 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

ERROR:
ORA-12505: TNS:listener could not resolve SID given in connect descriptor

$ /home/ora9ir2/bin/sqlplus
'scott/tiger@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(Host=localhost.localdomain)(Por
t=1521))(CONNECT_DATA=(SID=ora9ir2)))'

SQL*Plus: Release 9.2.0.4.0 - Production on Thu Dec 11 10:54:44 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.4.0 - Production

scott@ORA9IR2>

so, check your tnsnames.ora, you've entered it wrong (watch out for CASE as
well)

      Rating: 1
ERROR:
      ORA-12505: TNS:listener could not resolve SID given in connect
      descriptor  December 13, 2003
Reviewer:
      Jamil  from Saudi
Dear sir

   This my TNSNAMES.ORA FILE contains, that is created by SQL NET EASY
CONFIGURATION

When change the first SID in this file to SID= SID=ora9ir2 as , you suggest but
still not working do I have change the other SID in this file to SID=ora9ir2 or
not can make the necessary change  to my TNSNAMES.ORA this the original contains
that is created by the system .

#This is a SQL*Net Configuration file generated by SQL*Net Easy Configuration.
#Attention: Do not modify this file yourself.
#If you do, your SQL*Net Easy Configuration may not function properly.

Example1.world =
  (DESCRIPTION =
    (ADDRESS_LIST =
        (ADDRESS =
          (COMMUNITY = tcp.world)
          (PROTOCOL = TCP)
          (Host = Production1)
          (Port = 1521)
        )
    )
    (CONNECT_DATA = (SID = SID1)
    )
  )
Example2.world =
  (DESCRIPTION =
    (ADDRESS_LIST =
        (ADDRESS =
          (COMMUNITY = spx.world)
          (PROTOCOL = SPX)
          (Service = Server_lsnr)
        )
    )
    (CONNECT_DATA = (SID = ORCL)
    )
  )
Example3.world =
  (DESCRIPTION =
    (ADDRESS_LIST =
        (ADDRESS =
          (COMMUNITY = nmp.world)
          (PROTOCOL = NMP)
          (Server = FinanceServer1)
          (Pipe = ORAPIPE)
        )
    )
    (CONNECT_DATA = (SID = ORCL)
    )
  )
Tcp-loopback.world =
  (DESCRIPTION =
    (ADDRESS_LIST =
        (ADDRESS =
          (COMMUNITY = tcp.world)
          (PROTOCOL = TCP)
          (Host = 127.0.0.1)
          (Port = 1521)
        )
    )
    (CONNECT_DATA = (SID = ORCL)
    )
  )
Beq-Local.world =
  (DESCRIPTION =
    (ADDRESS_LIST =
        (ADDRESS =
          (COMMUNITY = beq.world)
          (PROTOCOL = BEQ)
          (PROGRAM = oracle73)
          (ARGV0 = oracle73ORCL)
          (ARGS = '(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))')
        )
    )
    (CONNECT_DATA = (SID = ORCL)
    )
  )
jedf.world =
  (DESCRIPTION =
    (ADDRESS_LIST =
        (ADDRESS =
          (COMMUNITY = tcp.world)
          (PROTOCOL = TCP)
          (Host = 192.168.0.6)
          (Port = 1521)
        )
        (ADDRESS =
          (COMMUNITY = tcp.world)
          (PROTOCOL = TCP)
          (Host = 192.168.0.6)
          (Port = 1526)
        )
    )
    (CONNECT_DATA = (SID = ORCL)
    )
  )

Thank you very much for your help

Best regards

Jamil

          Followup:

so?  i do not know what your SID should be -- sqlnet easy config only does what
you tell it to do.

i've told you what is wrong -- you have the wrong sid in there.  do you have a
dba or anyone that can tell you the proper one to use.

      Rating: 1
Connection to
      Sql  December 14, 2003
Reviewer:  Jamil
       from Saudi
Dear Sir

No I do not have dba , I am the dba in my organization , my oracle version as
follow :
Oracle9i Enterprise Edition Release 9.2.0.1.0
Can you guide me.

Best Regards

          Followup:

then you quite simply need to "remember what SID you used when you installed the
database"

look in your list of windows services, the SID name will be part of the service
name.

December 14, 2003 -- Thanks for the question regarding "rowid",
      version 8.1.7

      You Asked
Tom

Can you please tell how the rowid format changed from version 7 to 10 g.

 I have seen developers using rowid in there dml and feel that it is only a
matter of time the data gets corrupt as rowid is prone to change. Under what
circumstances does the rowid of a row changes.

Thanks

      and we said...
well, the external format changed from 7.x to 8.0.  It is still the same width,
so in all probability, the format change won't affect you unless the programs
"look" at the rowid and assume block.file.slot which was the v7 format.

A rowid of a row can change in these cases:

a) the table is an index organized table and you update the primary key.
b) the table is a partitioned table and you "enable row movement" and you update
the partition key and the update causes the row to physically move

otherwise, it takes a complete rebuild of the table (export/import -- alter
table move -- dbms_redefinition for example) to "change" the rowid.

Now, (a) should never happen right -- so that is OK.  (b) can happen but is
pretty rare.  And even if it happens -- if the code is written correctly, it
won't MATTER.

As long as the developers are NOT STORING the rowid persistently, just using it
in session -- rowid is very safe, very fast.

You would use rowid when you select data out so that before your interface
changes it -- you can use the rowid AND THE OTHER COLUMN VALUES to lock the row
and then later just use rowid with the new values to very safely UPDATE the row.

If you look at how "update where current of" works in PLSQL, you'll see it is
100% rowid based in fact!

Forms for example does this automagically.
December 14, 2003 Vrishali -- Thanks for the question regarding
      "Archive Logging", version 8.1.6.2.0

      You Asked
Hi Tom,

   Our database is in archive log mode. Database version is
8.1.6.2.0 on solaris 2.7. All archive logs are getting created
inside one directory on solaris box. Sometimes directory is
getting full and we are deleting old archive logs from
thatdirectory. Is is good to delete old archive logs files. Do
they really not needed for recovery if dataabse will suppose
crash in future and then needs recovery? If not could you please
tell me how the recovery process works without using old archive
logs?

Thanks
Vrishali

      and we said...
The backup and recovery guide should be read from cover to cover
-- as well as the server concepts manual.

If you do not understand the backup and recovery process -- and
you value your data -- you need to get a good handle on that.

Archive logs are needed to perform media recovery.  You need all
of the archives accumulated from your last backup (and optimally
your last 2 or 3 backups!! incase there is some error that
prevents you from using the last backup).

When you restore a datafile from a backup -- you need to "catch
it up" to the rest of the database.  In order to do that, we
need all of the generated log from the time of the backup of
that datafile until now.  If you have deleted some archive file
that was generated AFTER your backup -- you have just removed
the ability to recover the database with that backup.

I would suggest you use a tool such as RMAN to automate this as
it will take care of keeping what you need (or not).

You should store your archives OFF of the machine you are
archiving on at some point.  If the entire machine fails (eg: a
water pipe bursts over the disk drives or the building burns
down) -- having your archives on disk will do nothing for you.
You should really be getting those onto tape and hopefully in
another physical location to prevent loss of data.
Review &
      Followup

      Rating: 4
What about the
      question of archive log location  November 04, 2002

Reviewer:  David  from Nashville
Without using RMAN, can you have archive logs in multiple locations?  We have
the same problem in that the disk where the logs are written to will fill up
during heavy processing.  Sometimes this is overnight and we come in to find the
database is stopped.

          Followup:

You can have archives goto multiple destinations without RMAN in fact RMAN won't
help you at all here (ARCH archives, RMAN backs up).  Read up on the log_
init.ora parameters -- how to set multiple destinations, how to make some
optional

Sounds like someone hasn't sized their system properly for the work they do?

      Rating:
      4
  November 05, 2002
Reviewer:  David
       from Nashville
Well, could be...  We were running okay for quite a while but then our volume of
batch processing increased significantly.  We've reduced the amount of logging
by not commiting so often (originally I was commiting every row under the
impression it would improve performance).  I've been working with the DBA on the
archive log location.  Since you can specify multiple locations - will it use
the first location up totally and then the next?

          Followup:

"(originally I was commiting every row under the
impression it would improve performance)"

Hah -- not only tons slower but as you said -- generates MORE redo MORE undo
MORE work and goes as slow as you can.  Sort of like setting "slow=true".  Not
only that but it kills your transactional integrity!!!

Thanks for the EXCELLENT example -- hope lots and lots and lots of people read
this one!  committing frequently is like setting "slow=true" and
"do_more_work=yes_please".

No, we do not "rotate" locations -- you need to have multiple archive
destinations for recovery purposes -- not all of them need to be MANADATORY
though.  You need *at least* one mandatory one that has sufficient space (DBA
should have tape device, DBA should be getting archives OFF of the system, DBA
can have archives compresses at the very least)

      Rating:
      4
  November 05, 2002
Reviewer:  David
       from Nashville
Yes, it was very enlightening to use Log Miner and see what was being logged.
For every commit there were at least three other lines - stuff like "set
transaction read write" and blank lines with the operation as "INTERNAL"
(v8.1.7).

Many thanks

      Rating:
      3
  November 06, 2002
Reviewer:  Sagi
       from India
Hi Tom,

I know that frequent commit untill necessary should not be used because we loose
integritiy. Also that it would give additional work for the Log Writer to flush
Redo Log Buffers to Redo Log files, which makes it slower. Correct me if I am
wrong.

You said "generates MORE redo MORE undo MORE work" But did not get get this.

Could you please explain this .

Regards,
Sagi

          Followup:

which part was unclear?

when you commit prematurely you

o generate more stuff
o do more work

if you have my book -- I have tons and tons of examples on this in the first
part (first 5 chapters).  if you do not have my book, well -- Sagi -- you need
to get it given the number of followups and questions you ask here.  The book
answers many of those questions (as this site was the genesis for the book
itself)

      Rating:
      3
  November 06, 2002
Reviewer:  Sagi
       from India
Hi Tom,

Thanx for your advice. I know that your Book is Oracle Bible. I have decided to
buy it. But since I am reading the Oracle docs at the movement which itself is
huge enough thought of buying it sometime little later.

We all really appreciate you work.

Thanx once again.

Regards,
Sagi

      Rating: 3
backup the
      archived log files   October 28, 2003
Reviewer:
      jianhui  from DC
Tom,
My client requires to transfer archived log files to IBM TSM every 30 minutes,
and do log switch manually every 30 minutes to force the archiver to archive the
current redo log. When the database activity is low, the archived log is much
smaller (few Megabyte or less than 1M) than the redo log file size (100MB). This
increases the number of logfiles dramatically, is there any negative impact of
doing so?  I want to decrease the frequency of doing the log switch but the
client strictly requires less than 30 minutes data loss in case of losing all
the archived logfiles, so we have to ship the archived log files every 30
minutes to the storage manager in different location via network. How do you
usually do to move the archived log files to the safe backup place? Any, If the
number of small archived log files isnt the big deal, we can live with this. I
just want to know if there is any potential problems of doing so. Thanks!

          Followup:

no problems at all -- depending on your version, you can even just set an
init.ora parameter to do this.

ARCHIVE_LAG_TARGET

      Rating: 5
archivelog
      space  October 29, 2003
Reviewer:  Reader

tom, is there any thumb rule for how much space we need for archivelog files?
Thanks.

          Followup:

ROT - rules of thumb.

well, you need "enough" :)

you tell me -- how much redo do you generate?  how much redo do you want to keep
online for faster recovery if you need it?  how fast do you back off the
archives to some other storage?  how important is it for the database to not
stop (insufficient space to archive = stopped database)

      Rating: 4
ROT
        October 29, 2003
Reviewer:  Jeff Hunter
       from Greenwich, CT
ROT = "enough" * 2 for those heavy days...

      Rating: 4
RMAN:
      archivelog backup after FULL database backup  December 14,
      2003
Reviewer:  Sami  from NJ,USA
Dear Tom,

We have changed the database mode from NOARCHIVELOG to ARCHIVELOG mode at
sequence # 951. Starting sequence # from 951 to 1217 we did not backup
archivelog files and also deleted from DISK.

We took RMAN FULL database backup at sequence # 1250. Now we need to backup all
the archivelog files
after our FULL database backup.(I mean I need to backup archive log files
starting from seq# 1250)

Is it possible to do that? If so how do I do that?

RMAN> run {
2> allocate channel dev1 type disk;
3> backup
4>   format 'D:\oracle\ora92\database\log_t%t_s%s_p%p'
5>   (archivelog all delete input);
6> release channel dev1;
7> }

allocated channel: dev1
channel dev1: sid=23 devtype=DISK

Starting backup at 14-DEC-03
current log archived
released channel: dev1
RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-03002: failure of backup command at 12/14/2003 12:09:43
RMAN-06059: expected archived log not found, lost of archived log
compromises recoverability
ORA-19625: error identifying file D:\ORACLE\ORA92\RDBMS\ARC00951.001
ORA-27041: unable to open file
OSD-04002: unable to open file
O/S-Error: (OS 2) The system cannot find the file specified.

RMAN>

          Followup:

use the

FROM TIME = 'date string'
FROM SCN n
FROM SEQUENCE n

or something other than ALL.  See the archivelogRecordSpecifier in the 9i
"Recovery Manager Reference"

      Rating: 4
Thanks
      Tom  December 14, 2003
Reviewer:  Sami
       from NJ,USA
resolved.

RMAN>crosscheck archivelog all;
:
:
RMAN> run {
2> allocate channel dev1 type disk;
3> backup
4>   format 'D:\oracle\ora92\database\log_t%t_s%s_p%p'
5>   (archivelog all delete input);
6> release channel dev1;
7> }

Let me try the other methods whcih you mentioned as well.(FROM Sequence,FROM SCN
and From Time)

December 14, 2003 Michael -- Thanks for the question regarding
      "Export and ora-01013", version version 7.3.2.3

      You Asked
Dear Tom,
        recently (last week actually!) I started exporting some of my Bases via
MS-DOS direct to my PC using either 'exp' or 'exp80' depending on the Base. With
one of them (Bases that is!)I keep on getting the following error
'exporting table  TDATOSGENERALES
EXP-00014: error on row 583691 of table TDATOSGENERALES
EXP-00008: ORACLE error 1013 encountered
ORA-01013: user requested cancel of current operation
exporting table TDATOSGENERALESANT     778522 rows exported
etc etc.

This error is random i.e occuring on different tables every time I attempt to
export but always with the same Schema/User. There is absolutely no activity on
that Schema when it happens so can you help me find a solution.

My first thought was that it was something to do with the size of the table as
it seems to occur only on tables with large number of rows but what is driving
me up the wall is the randomness of it!

I did see the archive where you speak about this viz. a viz SQLPlus and Unix
terminals etc.  Do you know if the equivilant of 'stty isig' exists in the
MS-DOS world.  My PC is running W95 and the Base I am trying to export is Oracle
7.3.2.3

I would like to take this opportunity to thank you for the most usefull response
you gave me recently viz. a viz ORA-1555 and Export.  The support note 40689.1
was most comprehensive and clear.

While I am on the subject can you tell me where I can find an archive of Support
notes on the OTN, or if such a thing exists.  If 40689.1 was anything to go by I
feel I might progress faster with my Oracle education with them rather than the
manuals!

Regards,
       Michael

      and we said...
Well, yet another support note to the rescue maybe.....

Article-ID:         <Note:1037541.6>
Circulation:        PUBLISHED (EXTERNAL)
Folder:             server.Utilities.ExportImport
Topic:              4.1 --- Problems and Solutions V7
Title:              EXP-00014 EXP-00008 ORA-01013 DURING DATABASE EXPORT
Document-Type:      PROBLEM
Impact:             LOW
Skill-Level:        CASUAL
Server-Version:     V07.03.03.XX
Updated-Date:       08-OCT-1998 14:53:33
References:         <BUG:430972>
Shared-Refs:
     <SOLUTION:2084577.6>  TURN OFF DEAD CONNECTION DETECTION
Attachments:        NONE
Content-Type:       TEXT/PLAIN
Keywords:           EXPORT;
Errors:             EXP-00014;  EXP-00008;  ORA-01013;
Products:           5/RDBMS (07.03.03.XX);
Platforms:          GENERIC (VXX);

Problem Description:
====================

You receive the following errors while running a database export:

    EXP-00014: error on row %lu of table %s
        Cause: Export encountered an Oracle error while fetching
               rows.
       Action: Note the Oracle error message that follows and
               take appropriate action.

    EXP-00008: ORACLE error 1013 encountered
        Cause: Export encountered the referenced Oracle error.
       Action: Check Oracle Message manual for the Oracle error
               and take appropriate action.

    ORA-01013: user requested cancel of current operation
        Cause: The user interrupted an Oracle operation by
               entering
               CTRL-C, Control-C, or another cancelling
               operation.
               This forces the current operation to end.  This
               is an informational message only.
       Action: Continue with the next operation.

You have sqlnet.expire_time set in your "sqlnet.ora" file. Removing this
parameter, or setting it to a period longer than the duration of the export will
allow the export to run without error.

You have not set TWO_TASK and you are not using a connection string on the
export command to force the use of SQL*Net.

Problem Explanation:
====================

You have encountered bug 430972, which is fixed in version 2.3.4 of SQL*Net. A
local export uses the BEQ (bequeath) driver which reads the sqlnet.ora and hence
enables dead connection detection (DCD) if sqlnet.expire_time is set.

-------------------------------------------------------

As for where can you get these --
http://metalink.oracle.com/
is the online support site.  These are
all there...

Review &
      Followup

      Rating: 4
error 6550
      during export  February 08, 2002
Reviewer:
      aspen  from USA
Hi tom,
When i do an export (full or user) i get this message after it starts exporting
for sometime:

EXP-00008: ORACLE error 6550 encountered
ORA-06550: line 1, column 18:
PLS-00201: identifier 'SYS.OUTLN_PKG' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored
ORA-06512: at "SYS.DBMS_SYS_SQL", line 1118
ORA-06512: at "SYS.DBMS_SQL", line 316
ORA-06512: at "SYS.DBMS_EXPORT_EXTENSION", line 84
ORA-06512: at "SYS.DBMS_EXPORT_EXTENSION", line 135
ORA-06512: at line 1
EXP-00000: Export terminated unsuccessfully

          Followup:

Sounds like you upgraded but did not follow the steps in the migration guide
(hence the dictionary didn't get upgraded).

Did you upgrade this database and if so, did you read and follow the steps in
the migration guide?

      Rating: 4
^d or EOF might
      do the same  February 10, 2002
Reviewer:
      Mohammed Ahmed
if ^d or EOF is in the data being exported might give the same problem???

          Followup:

no.

      Rating:
      5
dcd  February 11, 2002
Reviewer:
      Alan  from Frankfurt
Is this error database version specific, or will dead connection detection do
this everytime?

          Followup:

In the problem report above, it has:

... which is fixed in version 2.3.4 of SQL*Net
...

      Rating: 5
Sorry for being
      dense!!  February 11, 2002
Reviewer:  Alan
       from Frankfurt
I'll try to read more thoroughly next time. thanks.

      Rating: 4
Exp with error
      - EXP-00008 & ORA-01455  March 19, 2002

Reviewer:  Sivababu  from GERMANY
Hello TOM,
    I got you answer regarding add log file. It is very nice.
While I exporting the user from the linux(oracle 8i) i got the following error.
EXP-00008 Oracler-Fehler 1455 gefunden
ORA-01455 überlauf Von integer-datentyp bei umwantlung der spalte.
it is installed in german.germany_WE8IOS...

Thanks and expecting your reply.

regards,
siva

          Followup:

try adding "statistics=none" to the EXP command line and see what happens.

      Rating: 5
Export
      Errors  July 02, 2003
Reviewer:  atul
       from India
Hi tom,

I'm getting following errors while doing export...

Connected to: Oracle8i Enterprise Edition Release 8.1.7.2.0 - Production
With the Partitioning option
JServer Release 8.1.7.2.0 - Production
Export done in US7ASCII character set and US7ASCII NCHAR character set
Note: indexes on tables will not be exported
Note: constraints on tables will not be exported

About to export specified tables via Conventional Path ...
Current user changed to VESOWNER
. . exporting table                 VES_TRADELANES        376 rows exported
. . exporting table            VES_TRADELANE_DATAS
EXP-00056: ORACLE error 1013 encountered
ORA-01013: user requested cancel of current operation
. . exporting table              VES_DATA_VERSIONS          1 rows exported
. . exporting table            VES_STRETCH_VOYAGES
EXP-00056: ORACLE error 1013 encountered
ORA-01013: user requested cancel of current operation
Export terminated successfully with warnings.

Could you kindly suggest me a cause for that..

Or its because of DCD?

Thanks.
atul

          Followup:

are you using a 7.3 client?

did you try simply disabling dcd (simple sqlnet.ora edit) to see if that was in
fact the cause?

      Rating: 3
ORA-21779:
      duration not active  December 14, 2003
Reviewer:
      Sami  from NJ,USA
Dear Tom,

Could you please help me to resolve this issue? I am trying to take full export
logical backup for my database and getting "ORA-21779: duration not active"
error.

Connected to: Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.4.0 - Production
Export done in UTF8 character set and AL16UTF16 NCHAR character set
server uses WE8MSWIN1252 character set (possible charset conversion)

About to export the entire database ...
. exporting tablespace definitions
. exporting profiles
. exporting user definitions
. exporting roles
. exporting resource costs
. exporting rollback segment definitions
. exporting database links
. exporting sequence numbers
. exporting directory aliases
. exporting context namespaces
. exporting foreign function library names
. exporting PUBLIC type synonyms
. exporting private type synonyms
. exporting object type definitions
. exporting system procedural objects and actions
. exporting pre-schema procedural objects and actions
EXP-00008: ORACLE error 21779 encountered
ORA-21779: duration not active
ORA-06512: at "SYS.DBMS_AW", line 106
ORA-06512: at "SYS.DBMS_AW", line 222
ORA-06512: at "SYS.DBMS_AW_EXP", line 264
ORA-06512: at line 1
EXP-00083: The previous problem occurred when calling
SYS.DBMS_AW_EXP.schema_info_exp
. exporting cluster definitions
EXP-00056: ORACLE error 31600 encountered
ORA-31600: invalid input value EMIT_SCHEMA for parameter NAME in function
SET_TRANSFORM_PARAM
ORA-06512: at "SYS.DBMS_SYS_ERROR", line 105
ORA-06512: at "SYS.DBMS_METADATA_INT", line 3926
ORA-06512: at "SYS.DBMS_METADATA_INT", line 4050
ORA-06512: at "SYS.DBMS_METADATA", line 836
ORA-06512: at line 1
EXP-00056: ORACLE error 31600 encountered
ORA-31600: invalid input value EMIT_SCHEMA for parameter NAME in function
SET_TRANSFORM_PARAM
ORA-06512: at "SYS.DBMS_SYS_ERROR", line 105
ORA-06512: at "SYS.DBMS_METADATA_INT", line 3926
ORA-06512: at "SYS.DBMS_METADATA_INT", line 4050
ORA-06512: at "SYS.DBMS_METADATA", line 836
ORA-06512: at line 1
EXP-00000: Export terminated unsuccessfully

D:\oracle\oradata>

Thanks
Sami

          Followup:

contact support for this one, never saw it before and don't see any record of it
happening in the problem database.

December 14, 2003 -- Thanks for the question regarding "Redo Log
      Buffers - Your Book - Expert one-on-one", version 8.1.6

      You Asked
Hi Tom,
On page 77 of your book 'Expert one-on-one' you mentioned that
"When it gets one third full or contains 1 MB of cached redo log data"
but on page 78 you gave example of 2MB full...
How can redo buffer reach to 2 MB, if redo buffers are flushed once they are 1
MB full, as mentioned on page 77?
Thanks

      and we said...
that is what trips lgwr to start dumping them out.

say you are running something really big.
generating GOBS of redo.
we can generate redo really fast (it just goes to the redo log buffer).
LGWR can only write it out so fast.

So, as you hit 1meg, LGWR is triggered to start flushing.
Meanwhile, you are STILL generating -- generating at this point much faster then
LGWR can dump.

So, it is because lgwr is writing -- but you are still generating -- that you
can actually have much more then 1m in the redo log buffer.  We don't WAIT while
lgwr is flushing (unless we issued a commit and that's what triggered lgwr --
then we do wait).
December 14,
      2003 Gregory -- Thanks for the question
      regarding "Condition from multiple columns", version 8.1.7

      You Asked
The columns named Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
may have a value of YES or NO. Amongst other columns, how can I read one value
which will determine if any of these day columns has a values of YES.

      and we said...
select ....,
       case when 'YES' in ( monday, tuesday, wednesday, thursday,
                            friday, saturday, sunday )
            then 'YES'
            else 'NO'
        end YES_NO,
        ......

or

select ....,
       decode( 'YES', monday, 'YES', tuesday, 'YES', ...., sunday, 'YES', 'NO'
),
       .....

for example (just using m and t for monday/tuesday in this example)

ops$tkyte@ORA920> select m, t,
  2         case when 'YES' in ( m, t )
  3              then 'YES'
  4                          else 'NO'
  5              end
  6    from t;

M   T   CAS
--- --- ---
no  no  NO
no  YES YES
YES no  YES
YES     YES
    YES YES
        NO

6 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select m, t,
  2         decode( 'YES', m, 'YES', t, 'YES', 'no' )
  3    from t;

M   T   DEC
--- --- ---
no  no  no
no  YES YES
YES no  YES
YES     YES
    YES YES
        no

6 rows selected.

ops$tkyte@ORA920>

Review & Followup

      Rating:
      5
  December 16, 2003
Reviewer:
      A reader

      Rating: 5
Condition from
      multiple columns  December 21, 2003
Reviewer:
      Gregory  from /\/*****\/\ ,Cape Town, South Africa
The response was very quick and the answer was exactly what was needed.

Thank You

December 14, 2003 -- Thanks for the question regarding "delete
      documents from operating system files", version 9.2.0

      You Asked
Is there a supplied package for deleting operating system files?

We're engaged in a "Document Tracking Database", that keeps track of 11,000,000
documents stored in the Solaris 8 operating system file systems.  The document
tracking database records each document, its location, and its status.  It would
be convenient to delete the document from the file system when its status is set
to "DELETED".

My scan through "Oracle supplied packages" didn't turn up anything that would
accomplish this.  I had assumed that since Oracle does a lot of file management,
there would be an Oracle procedure or function that would delete files.  Am I
wrong to make this assumption?

Thanks in advance,

Gus

      and we said...
oh my, 11,000,000 documents -- those belong IN THE DATABASE.

If they contain anything of value to you, they belong IN THE DATABASE.

Then, a simple "delete" would be the ticket for you -- instant "delete".

Then you need not worry about syncronizing the OS file system with the database
(what happens when a disk fails -- no point in time recovery for you, what
happens when a user "accidently" erases a file but doesn't tell you about it,
and so on)

At Oracle - we have many more then 11,000,000 documents in a single database
(terabytes of storage).  A single database, all of our files, managed by
professionals that make sure we never lose them -- they are backed up, in sync,
always there.

You've put these things in the totally wrong place!

To erase the file from the OS, you would have to use a java stored procedure or
a C based external routine.  But -- again, I'll reiterate -- you've got these
things in the wrongest place on the planet.
Review &
      Followup

      Rating:
      3
utl_file  December 14, 2003
Reviewer:
      Freek  from Belgium
You could use the utl_file.fremove procedure to delete a file from disk.

But storing them in the db would be indeed better.

          Followup:

doh, i keep forgetting about that -- utl_file was greatly expanded in 9i

o support for directory objects (no more utl_file_dir)
o fcopy
o fgetattr (file attributes)
o fgetpos
o FREMOVE
o frename
o fseek
o get_RAW
o put_RAW

thanks :)

      Rating: 5
cheating...
        December 15, 2003
Reviewer:  Connor
       from Perth, Western Australia
Or you could have a tinker with DBMS_BACKUP_RESTORE for those on pre-9
databases.

      Rating: 5
Remove
      documents from file system  December 15, 2003

Reviewer:  Gus Spier  from Middleburg, VA
Yes, sir!  You are right and I've told them and told them ... and in the next
release of the system, I think the documents will be in the database.  For now,
the operation insists on storing in the file system.

Thanks for the answer!  (fremove, huh? Where's that documentation again?)

Gus

December 14, 2003 atul -- Thanks for the question regarding
      "CURSOR_SHARING=FORCE", version 8.0.4

      You Asked
Sir,

recently in one book i read,

"In the old days, it was up to the developers to build applications
which relied upon sql statements which contained bind variables.

This forced the Oracle kernel to avoid caching similar sql statements
in the SQL cache.

The CURSOR_SHARING=FORCE feature is a huge gain for some applications which do
not use
many bind variables, or perhaps none at all."

so is that correct???

We just have to change init parameter so database will start using bind
varables???

Thanks.
atul

      and we said...
Well, you are on 8.0.4 (very old, unsupported) and this was introduced in 816.

To get the OTHER side of the coin, you can read another book "Expert one on one
Oracle" (written by me).  Specifically in chapter 10 I wrote in part:

<quote>
In Oracle8i, release 2 (version 8.1.6), Oracle introduced a new feature called
CURSOR_SHARING. Cursor sharing is an 'auto binder' of sorts.  It causes the
database to rewrite your query using bind variables before parsing it. This
feature will take a query such as:

scott@TKYTE816> select * from emp where ename = 'KING';

and will automatically rewrite it as:

select * from emp where ename = :SYS_B_0

in 8.1.6 and or

select * from emp where ename = :”SYS_B_0”

in 8.1.7.  This is a step in the right direction, but should not be the final
solution to the problem and should not be used long term. There are side effects
from cursor sharing that you must aware of as well. A poorly written program may
experience a large gain in performance by setting the CURSOR_SHARING=FORCE
parameter, but it will still be running slower than it should, and it will still
be limited in the degree to which it can scale. As we observed above, we were
able to reduce by a large percentage many of the waits and the time spent
waiting by simply using bind variables. We could achieve the same exact results
using cursor_sharing=force.  It was not until we avoided the soft parses
however, that we were able to avoid 100 percent of the waits.  Cursor sharing
will NOT avoid these soft parses for us. It is a fact that if you can benefit
greatly from cursor sharing, you are causing Oracle to parse many queries too
frequently. If you are causing many queries to be parsed frequently, then adding
cursor sharing will fix the bind variable issue, but you will still be left with
the soft parse overhead. While not as egregious as the overhead of bind
variables, high soft parse counts will limit your performance and scalability as
well. The only correct solution is to use bind variables in the first place and
to reuse your cursors whenever possible. For example, if I were coding a Java
application I would never code a routine like this:

...
String getWordb(int ID, int IDLang, Connection conn) throws SQLException
{
  CallableStatement stmt = null;

  stmt = conn.prepareCall("{ call get.wordb (?,?,?)}");
  stmt.setInt(1,ID);
  stmt.setInt(2,IDLang);
  stmt.registerOutParameter (3, java.sql.Types.VARCHAR);
  stmt.execute();
  String word = stmt.getString (3);
  stmt.close();
  return word;
}
...

I would code it like this:

...
CallableStatement stmt = null;

String getWordb(int ID, int IDLang, Connection conn) throws SQLException
{
  if ( stmt == null ) {
    stmt = conn.prepareCall("{ call get.wordb (?,?,?)}");
    stmt.registerOutParameter (3, java.sql.Types.VARCHAR);
  }
  stmt.setInt(1,ID);
  stmt.setInt(2,IDLang);
  stmt.execute();
  return stmt.getString (3);
}
...

Here, I am ensuring the use of bind variables by using the placeholders in the
statement. Additionally, I am parsing that statement at most once per program
execution.  This will make a huge difference in performance. In one test, I
called the 'bad' and 'good' implementations 1000 times each. The bad
implementation, with the soft parses for each call, took two and a half seconds
to execute. The good implementation took one second. That was in single user
mode. We know by now that adding additional users, each doing thousands of soft
parses, will just slow each other down with latch waits during the soft parse.
This is overhead we can, and must, avoid in our applications.

Now back to cursor sharing for a moment. I also said above that there are some
side effects of cursor sharing that you must be aware of. They fall into the
following categories:

o Optimizer related issues. CURSOR_SHARING will remove all character string and
numeric constants from the query – the optimizer will have less information to
work with. This may very well result in different query plans.
o Query output related issues. The lengths of columns that your query fetches
will unexpectedly change.  Queries that used to return a varchar2(5) and a
number(2) might start returning a varchar2(30) and a number(5).  The actual size
of the data returned won’t change but the application will be told the potential
for a 30 byte column is there – reports and other related applications may be
affected.
o Query plans are harder to evaluate. This is due to the fact that explain plan
will 'see' a different query than the database does. This makes query tuning
more difficult. Features like autotrace in SQL*Plus are unreliable with
CURSOR_SHARING.

</quote>

in short, cursor sharing is a crutch that MIGHT be useful in some cases to
help a poorly written program survive for the period of time the developers are
hard at work CORRECTING their bug and putting bind variable support into their
application

Review &
      Followup

      Rating: 5
Very good
      Explanation.  August 28, 2002
Reviewer:  Rajesh

      Rating: 4
Exactly what i
      want to know about CURSOR_SHARING  February 04, 2003

Reviewer:  Alexey Simonov  from Moscow, Russia

This note is exactly what i want to know about CURSOR_SHARING. Thanks Tom!

      Rating: 5
Excellent
      explanation  April 05, 2003
Reviewer:  Peter
      Tran  from Houston, TX USA
We noticed that our application was breaking which was caused from the padding
returned from a varchar() column when we set cursor_sharing='FORCE'.  You've
only confirmed that this is the correct behavior.

Due you know if cursor_sharing='SIMILAR' has the same issues in 9i (9.2.0.3)?

As I told the Director at my company, Tom Kyte is a freaking walking ORACLE
encyclopedia.  :D

Thanks!

          Followup:

It has to (similar).  You are using a crutch to help a poorly written
application "walk".  You have to expect some issues.  With crutches it is harder
to get up and down stairs, you cannot more as fast and your arms hurt.  No
different here, when you have to use a crutch, you are slower then you should be
and in some amount of pain and should be working really hard to remove the need
for crutches.

You've taken away from us the FACT that substr( cname, 1, 10 ) is 10, it is not
substr( cname, :x, :y ) where both x and y are somewhat variable APPARENTLY.
Since the size of :x and :y in this case would not affect the plan materially,
similar will work the SAME as force.

      Rating: 3
cursor
      sharing  April 06, 2003
Reviewer:  Vikas
      Sharma  from Delhi India
Hi Tom,
I have a procedure which creates a dynamic select string then open the cursor
for that string and creates a insert string then executes the insert string. For
eg.

Procedure p (p_1 varchar2) as

Begin
--    here I create a dynamic select string. Which is like
--    SELECT col1,col2,col3 from mytable where col1 = ‘abc’; (number of col can
be change)
--- here I open my dbms_sql cursor.for the select string…
LOOP ---retrieve more then 100000 rows and more..
--- here I create a insert string like
myinstring := ‘insert into ‘||p_1 ‘|| (col1,col2,col3) values (
‘’||valuesfromcursorcol1||’’, ‘’valuesfromcursorcol2||’’,’’||
valuesfromcursorcol3||’’)’;

--then I  execute this insert string ..
execute immediate myinstring..
--- so every time this is a new insert in for database so every time it is a
hard parse. ----When it get executes it is like :
 -- insert into mytab (col1,col2,col3) values (‘val1’,’val2’,’val3’);
END LOOP;
--so everytine it is a hard parse.

End;

So if I use cursor sharing= force will it improves my performance.

This insert stament once created will be same for all rows return but values
will be changed. Ie val1 val2 val3 will change.. There is anyway the treat them
as bind variable. I am using dbms_sql for opening the dynamically created SELECT
string. And execute immediate to execute the insert instring.

Regards,

Vikas Sharma

          Followup:

so, why don't  you just build an INSERT as SELECT statement and do it right?

short of that -- use BIND VARIABLES -- it isn't hard.  You will avoid not only
the hard parse -- but that dreaded and expensive soft parse as well....

do it right.  use binds, avoid cursor_sharing -- you don't need it, you don't
want it, it would be the wrong approach. (the right approach is insert as select
really, but at the VERY least -- use binds and parse the insert ONCE)

      Rating: 3
cursor
      sharing  April 07, 2003
Reviewer:  Vikas
      Sharma  from Delhi India
Hi Tom,

Thanks.

You are right the insert as select would be the best approach. I am sorry for
not providing the complete info, that i want to log my failed statements also
and complete it for all rows. But if is use the insert as select if one row
fails the execution will stop.

This is the requirment of my application.

Pl suggest how can i use bind variables in the above situtation as my no. of
variables are not known. This process runs from browswer(mod_plsql), can i mak
the use of owa_util.bind_variables but that also has the limt of variables
declared.

Is the cursor sharing works only with SELECT, not with INSERTS.

Regards,

Vikas Sharma

          Followup:

If you can dynamically process the SELECT using dbms_sql.column_value -- you
should be able to figure out how to dynamically process the insert using
dbms_sql.bind_variable.

      Rating: 4
One more
      query..  April 07, 2003
Reviewer:  Swapnil
       from UK
Hi!
Just wanted to know that , which one is faster...

open a cursor
loop
  insert into some table using cursor's values
end loop

OR

direct insert into some table as select some columns from other table.

          Followup:

insert into some_table
select * from some_other_table;

is going to be faster then a procedural loop.

      Rating:
      5
???  April 07, 2003
Reviewer:  Riaz
      Shahid  from PRAL, Islamabad, Pakistan
Hi there !

There should be no doubt in answering this question. The second statement
(insert into...select) will be much faster than the first one. Just imagine, you
open a cursor, fetch rows one by one, and then close cursor.

"If you can do some task using single statement then JUST DO IT....don't use
PL/SQL for that.....it will degrade performance".

Riaz

      Rating: 5
Evaluating the
      CURSOR_SHARING parameter  April 25, 2003
Reviewer:
      Juan Carlos Reyes Pacheco  from Bolivia
Hi Tom, which way would you suggest to evaluate
the CURSOR_SHARING parameter:
1. The need for using SIMILAR
2. The need for using FORCE
Which are the main signs that tell you when to use SIMILAR and when FORCE.
Which of this views are the most useful
V$SQL_BIND_DATA,V$SQL,V$SQL_BIND_METADATA
:) Please

          Followup:

OLTP -- force
Mixed workload similar (where you need a different plan for some of the queries)
DSS/DW -- exact

      Rating: 5
more
      explanation please  April 28, 2003
Reviewer:
      juancarlosreyesp@yahoo.com  from Bolivia
Thanks tom,
As I understand what you say is
IN      TRY
-------------
OLTP -- force
Mixed workload similar (where you need a different plan for some of the queries)
DSS/DW -- exact

And see if the performance increases?

Because as I understand the goal is fix the code and set this parameter as EXACT

Thanks :)

          Followup:

yes.  that is basically what I said.

      Rating:
      5
  April 28, 2003
Reviewer:
      A reader
Thanks :)

      Rating: 4
cursor_sharing
      = force and histograms  April 29, 2003
Reviewer:
      Arul  from CA, USA
"Query output related issues. The lengths of columns that your query fetches
will unexpectedly change.  Queries that used to return a varchar2(5) and a
number(2) might start returning a varchar2(30) and a number(5).  The actual size

of the data returned won&#8217;t change but the application will be told the
potential for a 30 byte column is there &#8211; reports and other related
applications may be
affected."

Tom,

1. I am not sure if I understood the above response. Is this due to any Oracle
bug? Can you please elaborate?

2. On Oracle 9.2.0.1, I need to use cursor_sharing = force for an OLTP app where
we cannot change the app. code to use bind variables. For a couple of important
tables to get good execution plans I am using histograms. The decision to use
'force' and not 'similar' is because it is an OLTP app. and 'similar' being a
new feature seems to have certain bugs (per Oracle tech support). So, I am
wondering if cursor_sharing=force (with histograms) is the right mix. I will be
evaluating the execution plans -- I thought I will get clarifications from you.

I would really appreciate your suggestions. Thank you.

          Followup:

1) no, it is not a bug, just a side effect.

You have a query:

select substr( c1, 1, 5 ) c1 from t;

at parse time, with 1, 5 in there -- the database KNOWS "that is 5 characters".
No, introduce cursor_sharing and that becomes:

select substr( c1, :bv1, :bv2 ) c1 from t;

because that query could be:

select substr( c1, 1, 5 ) c1 from t;
select substr( c1, 1, 50 ) c1 from t;
select substr( c1, 1, 500 ) c1 from t;

it no longer KNOWS, so it has to generalize the size.  Now, a reporting tool
might describe that query to set up an output template.  Before
cursor_sharing=force, it knew "5 characters" -- after it "knows" NN characters
where NN <> 5.  report looks bad due to this.

Just a side effect, totally anticipatable if you understand cursor sharing and
think through all of the possible ramifications.

2) force is going to give you -- one plan.  since 9i does bind variable
"peeking", you could find this "unpredicable".  Watch:

create table t as select decode(rownum,1,0,1000) id, all_objects.* from
all_objects;

create index t_idx on t(id);

analyze table t compute statistics for table for all indexes for all indexed
columns;

alter session set cursor_sharing=force;
@trace
set termout off
select * from t query1 where id = 0;
select * from t query1 where id = 1000;
select * from t query2 where id = 1000;
select * from t query2 where id = 0;
set termout on

now, if id = 0, use index.  if id = 1000, don't.  Arguably -- the four queries
above are the same under cursor_sharing -- BUT and this is a big but -- they
will result in different plans being used.

Pretend the two query1 queries were run in that order on monday -- that is, "id
= 0" got in there first.  Now -- for the rest of the day, the "id = :bv" with
:bv = 0 (peeking) will be in place.  You shutdown monday night and restart
tuesday.  Now the id = 1000 gets in there first....

Using tkprof with aggregate=no, we can see the effect of this:

select * from t query1 where id = :"SYS_B_0"

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          4          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          4          0           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID T (cr=4 r=0 w=0 time=155 us)
      1   INDEX RANGE SCAN T_IDX (cr=3 r=0 w=0 time=115 us)(object id 49531)
********************************************************************************
select * from t query1 where id = :"SYS_B_0"

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     2109      0.57       0.57          0       4686          0       31612
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2111      0.57       0.57          0       4686          0       31612

Rows     Row Source Operation
-------  ---------------------------------------------------
  31612  TABLE ACCESS BY INDEX ROWID T (cr=4686 r=0 w=0 time=317553 us)
  31612   INDEX RANGE SCAN T_IDX (cr=2166 r=0 w=0 time=161543 us)(object id
49531)

see, id=0 caused the plan -- the generic plan - to be an index access path.
but -- tomorrow, simply cause the queries are in different orders:

select * from t query2 where id = :"SYS_B_0"

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     2109      0.38       0.40          0       2531          0       31612
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2111      0.38       0.40          0       2531          0       31612

Rows     Row Source Operation
-------  ---------------------------------------------------
  31612  TABLE ACCESS FULL T (cr=2531 r=0 w=0 time=172266 us)
********************************************************************************
select * from t query2 where id = :"SYS_B_0"

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.01       0.01          0        448          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.01          0        448          0           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS FULL OBJ#(49530) (cr=448 r=0 w=0 time=13570 us)

I get full scans instead

So, beware (not a bug, desired intended side effect)

      Rating: 5
cursor_sharing
      = force and histograms  April 30, 2003
Reviewer:
      Arul  from CA, USA
Tom,
Many thanks for that clear explanation.

      Rating: 4
Partially
      dynamic query  June 12, 2003
Reviewer:
      Anurag  from USA
I have a query like

select a,b,c from t1 where d=25 and e= ?

I know that all my queries are going to use d=25.  In this case, is it okay to
use partialy binding.

          Followup:

100% yes.

      Rating: 5
cursor_sharing
      = force and binding all constants as well  July 26, 2003

Reviewer:  A reader
Tom
1. Is there any difference between following a strategy of
binding constants also (along with variables) and
using cursor_sharing = FORCE?
2. Are there any advantages of binding contants and variables.

From what I cam tell from your book the answers to these
questions are:
1. No
2. No - only disadvantages in terms of suboptimal query
plans at times and other side effects as mentioned in your
book.

Please confirm

Thanx!

          Followup:

1) yes, you should not bind all constants, only those that make sense.  eg:

select substr( x, 1, 5 ) x, y, to_char( z, 'dd-mon-yyyy' ) z
  from t
 where a = 55;

of the constants:

1, 5, dd-mon-yyyy, and 55

which should be bound???  exactly 1 -- 55.  see what happens in sqlplus:

ops$tkyte@ORA920> create table t ( a int, x varchar2(2000), y int, z date );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( 55, 'abcdefg', 2, sysdate );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> alter session set cursor_sharing = exact;

Session altered.
ops$tkyte@ORA920> variable a number
ops$tkyte@ORA920> exec :a := 55;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select substr( x, 1, 5 ) x, y, to_char( z, 'dd-mon-yyyy' ) z
  2    from t t1
  3   where a = :a;

X              Y Z
----- ---------- -----------
abcde          2 27-jul-2003

ops$tkyte@ORA920> alter session set cursor_sharing = force;

Session altered.

ops$tkyte@ORA920> select substr( x, 1, 5 ) x, y, to_char( z, 'dd-mon-yyyy' ) z
  2    from t t2
  3   where a = 55;

X
---------------------------------------------------------------------------------
--------------------------------------------------
         Y Z
----------
---------------------------------------------------------------------------
abcde
         2 27-jul-2003

wow, what happened to X and Z on that second query???  Hmmm

cursor_sharing = force is a crutch you might be able to use temporarily until
the developers fix the bug in their code and bind correctly, where appropriate.

2) yes, obviously. I wail on about BINDING all of the time.  It is how our
shared sql works.  In the above example where I bound 55 -- that query can be
executed over and over with different values for :a without being reparsed.  If
other sessions execute that query, they will SOFT parse.

definitely using BINDS where they make sense, literalls otherwise, will have a
massive (cannot be overstated -- MASSIVE in capital letters) effect on
performance.

You need to re-read chapter 10 if you think #1 was "no"

      Rating: 5
sorry fot the
      poorly worded qs!  July 27, 2003
Reviewer:
      A reader
What I meant to ask was:

1. Is cursor_sharing=force any different from binding
all constants? (Here , I am trying to evaluate the
disadvantages of binding constants (alongwith
variables.)?

From your answer, it seems there are differences - e.g. in terms of lengths of
results returned as is clear from your example.)

2. My second question should have read:
 Are there any disadvantages of binding constants
 *always* instead of using literals for constants?
I understand that variabels should be bound always.

And as I said, the answer is that - YES - there are
disadvantages - for example, you can get suboptimal
query plans because the optimizer has less information
to deal with when you bind a constant rather than
specifying it as a literal - esp. in tables with
skewed data. This is mitigated with the "peeking" of
values by optimizer in 9i but the point still remains.

Apologies for not wording questions correctly!

Of course, I stand corrected on my first question.
Looks like there is a difference between
  a. using cursor_sharing=force, and
  b. binding all constants yourself while keeping
     cursor_sharing=exact.
So, If I am trying to prove that binding constants
is disadvantageous, I cant simply point out to the
disadvanatges of cursor_sharing=force option - they
are not equivalent.

Thank you!

      Rating: 5
trying to
      reproduce your example of cursor_sharing on p443-446  July
      27, 2003
Reviewer:  A reader
Hi Tom
I am unable to reproduce the results of your example on
pages 443-446 (in your expert one on one book)
One reason may be that 9i works differently.
The example tried to prove that cursor_sharing would
result in suboptimal quey plans being generated.

---test@apps> set echo on
test@apps> @schema2
test@apps>
test@apps> set serveroutput on size 200000
test@apps> --create a table
test@apps>
test@apps> drop table t1 cascade constraints;

Table dropped.

Elapsed: 00:00:00.95
test@apps>
test@apps> create table t1
  2  as select * from all_objects;

Table created.

Elapsed: 00:00:06.93
test@apps>
test@apps> create index t1_idx1 on t1( object_name );

Index created.

Elapsed: 00:00:04.23
test@apps> create index t1_idx2 on t1( object_type );

Index created.

Elapsed: 00:00:03.45
test@apps> analyze table t1 compute statistics for table for all indexes for all
indexed columns;

Table analyzed.

Elapsed: 00:00:04.65
test@apps>
test@apps> @trace
test@apps> set echo on
test@apps> alter session set timed_statistics=true;

Session altered.

Elapsed: 00:00:00.01
test@apps> alter session set events '10046 trace name context forever, level
12';

Session altered.

Elapsed: 00:00:00.02
test@apps> @sel33
test@apps> variable a varchar2(18);
test@apps> exec :a := '%';

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
test@apps> select count(*)
  2  from t1 no_binding
  3  where object_name like :a
  4    and object_type in ( 'PROCEDURE','FUNCTION', 'TRIGGER' );

       160

Elapsed: 00:00:00.09
test@apps>
test@apps>
test@apps> @force
test@apps> alter session set cursor_sharing=force;

Session altered.

Elapsed: 00:00:00.01
test@apps> @sel33
test@apps> variable a varchar2(18);
test@apps> exec :a := '%';

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
test@apps> select count(*)
  2  from t1 no_binding
  3  where object_name like :a
  4    and object_type in ( 'PROCEDURE','FUNCTION', 'TRIGGER' );

       160

Elapsed: 00:00:00.08
test@apps>
test@apps>
test@apps>
---

From tkprof output (from command "tkprof apps_ora_3591.trc tk.out aggregate=no")
we see that same plans are generated
regardless of cursor_sharing=exact or force option.

---
select count(*)
from t1 no_binding
where object_name like :a
  and object_type in ( 'PROCEDURE','FUNCTION', 'TRIGGER' )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.07       0.07          0        173          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.07       0.07          0        173          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 228

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
    160   VIEW index$_join$_001
    160    HASH JOIN
    160     INLIST ITERATOR
    160      INDEX RANGE SCAN (object id 112714)
  34175     INDEX RANGE SCAN (object id 112713)
.....
alter session set cursor_sharing=force
....
select count(*)
from t1 no_binding
where object_name like :a
  and object_type in ( 'PROCEDURE','FUNCTION', 'TRIGGER' )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.08       0.07          0        173          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.08       0.07          0        173          0           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 228

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
    160   VIEW index$_join$_001
    160    HASH JOIN
    160     INLIST ITERATOR
    160      INDEX RANGE SCAN (object id 112714)
  34175     INDEX RANGE SCAN (object id 112713)

          Followup:

things change -- yes.  see how even the original plan is TOTALLY different from
the plan you got?

same caveat applies, you can come up with a test case easily -- once you
understand what I was testing (selectivity)

      Rating: 5
trying to prove
      constant binding is not good  July 28, 2003

Reviewer:  A reader
Hi Tom
I am finding it hard to prove that binding constants in where clause can give us
suboptimal query plan. This may be due to the peeking of bind variables that 9i
does? I tried to create table with skewed data and then executed selects where I
expect the optimizer to do a full scan if
the constant is not bound. It ends up using same indexe
in both cases.

Any help would be appreciated as usual!

Thank you so much!!

Following is what I ran
---
QL> set echo on
SQL> @schema
SQL>
SQL> set serveroutput on size 200000
SQL> --create a table
SQL>
SQL> drop table t1 cascade constraints;

Table dropped.

SQL>
SQL> create table t1 ( name varchar2(20), data varchar2(4000) );

Table created.

SQL>
SQL> create index t1_idx1 on t1( name, data );

Index created.

SQL>
SQL> @data
SQL> set echo on
SQL> set serveroutput on size 200000
SQL> truncate table t1;

Table truncated.

SQL> insert into t1
  2  select 'name1', rpad( 'data' || rownum, 200)
  3  from all_objects
  4  where rownum <= 20
  5  union
  6  select 'name2', rpad( 'data' || rownum, 200)
  7  from all_objects
  8  where rownum <= 30;

50 rows created.

SQL>
SQL> insert /*+ APPEND */ into t1
  2  select 'name3', rpad( 'data' || rownum, 1000)
  3  from all_objects, all_objects
  4  where rownum <= 10000;

10000 rows created.

SQL> /*
DOC>analyze table t1 compute statistics for table for all indexes for all
indexed columns;
DOC>*/
SQL> analyze table t1 compute statistics for table for all indexes;

Table analyzed.

SQL> set autotrace traceonly
SQL> @trace
SQL> set echo on
SQL> alter session set timed_statistics=true;

Session altered.

SQL> alter session set events '10046 trace name context forever, level 12';

Session altered.

SQL> @sel3
SQL> select *
  2  from t1 no_binding
  3  where data like 'data%';

10050 rows selected.

Statistics
----------------------------------------------------------
         42  recursive calls
        229  db block gets
       2019  consistent gets
        258  physical reads
       1320  redo size
   10513241  bytes sent via SQL*Net to client
      88142  bytes received via SQL*Net from client
        671  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      10050  rows processed

SQL>
SQL>
SQL> @sel4
SQL> variable x varchar2(20);
SQL> exec :x := 'data';

PL/SQL procedure successfully completed.

SQL> select *
  2  from t1 binding
  3  where data like :x || '%';

10050 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
          8  db block gets
       2015  consistent gets
          0  physical reads
          0  redo size
   10513241  bytes sent via SQL*Net to client
      88142  bytes received via SQL*Net from client
        671  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      10050  rows processed
SQL>
---

and tkprof output portions are
--
select *
from t1 no_binding
where data like 'data%'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch      671      1.00       1.02        258       2015        229       10050
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      673      1.00       1.02        258       2015        229       10050

Misses in library cache during parse: 1
...
select *
from t1 binding
where data like :x || '%'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch      671      0.93       0.95          0       2015          8       10050
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      673      0.93       0.95          0       2015          8       10050

Misses in library cache during parse: 1
.....
--

          Followup:

in 9i, you have "bind variable peeking" meaning the first time the plan is
optimized -- we'll peek at the bind variables and use those "facts" to develop
the plan.

This offsets much of the issue with overbinding.  It is not perfect for it in
itself introduces other issues.

search this site for

bind variable peeking

to see other articles on this topic.

      Rating:
      5
thanx  July 29, 2003
Reviewer:
      A reader
that is what I thought (as I mentioned in the
question )..I read your articles that you pointed to.
Great stuff as usual!

Please keep up the great work!

      Rating: 5
cursor sharing
      similar  July 29, 2003
Reviewer:  Reader

(1) My understanding is that FORCE option will consider two sql statements that
are identical except the literal values in where clause are the same and use the
same shared cursor. Is it correct?

(2) What does cursor_sharing = similar do?

(3) also, how oracle treats dynamic sqls? Does oracle use shared pool for
dynamic sqls also?

(4) what is the difference between using v$sql and v$sqlarea?

Thanks in advance. I hope you would consider these questions in this thread.

          Followup:

1) correct.

2) search for

cursor_sharing similar

on this site -- couple of examples.

3) ALL sql is dynamic sql under the covers in Oracle.  Yes, dynamic sql uses the
shared pool

4) v$sqlarea is an aggregate (that is expensive to query) of v$sql.  v$sql shows
child cursors, v$sqlarea rolls up the child cursors

      Rating: 5
what is a child
      cursor?  July 29, 2003
Reviewer:  Reader

          Followup:

they are cursors for the same sql_text that are different for some reason.

eg:

connect scott/tiger
select * from emp;

connect someone_else_that_has_emp/tiger
select * from emp;

you'll have two cursors for "select * from emp" -- BUT they are different plans
and all since there is a scott.emp and a someone_else_that_has_emp.emp.

one is the child cursor of the other.

      Rating: 5
example
      ....  July 29, 2003
Reviewer:  Reader

I looked ata your example. could you give me your code for exec gen_data( 'EMP',
50000 )

so I can try on my database. Thanks.

          Followup:

set define off

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

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

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

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

      Rating: 5
default
      behavior is .....  July 30, 2003
Reviewer:
      Reader
Following is your quote from another thread. I thought the optimizer will do the
same even if I don't set the CURSOR_SHARING to similar. Optimizer will go for
Full table scan if the result set is going to be 99% of the table by ignoring
the index and the optimizer will use the index if it is there, if the result set
is just a single row. So, what is so special about SIMILAR option? I don't
understand. May be I miss something here. Thanks.

<quote>
cursor_sharing=similar will recognize when substituting a bind variable COULD
radically change a plan (eg: "select * from t where x = 5" would not use an
index since 99.9% of the data is 5, but "select * from t where x = 6" would use
an index -- just putting in a bind would be fatal) and will generate multiple
plans based on the bind variable value.  It recognizes that more then one plan
is necessary and permits that.
<quote.

          Followup:

if you submitted queries with literals and cursor sharing = exact (bad in most
systems, ok in a data warehouse environment) -- then yes, the optimizer would do
that.

If you submitted a query with binds and cursor sharing = exact:

select * from t where x = :x

then what you say would NOT be true.  the plan would either be a full scan OR
index scan -- for both of 5 and 6.

if you submit a query with literals and cursor sharing = force, then it would be
just like submitting it with binds.  the plan would ALWAYS be either full scan
or index scan regardless.

if you submit a query with literals and cursor sharing = similar, then you have
the opportunity for 2 different plans like you did with the literals and cursor
sharing = exact

      Rating: 4
CBO with
      several alternative plans  July 31, 2003
Reviewer:
      Piotr Jarmuz  from Braunschweig, Germany
Why does not CBO create several alternative plans for query execution and use
them dynamically when bind variables are bound and thus their values known?

It should not be that CPU intensive nor hard to program it into CBO to match few
plans with few bind variables. Assuming that statistics are kept up-to-date it
should work at least in most common cases.

Besides I read your article in this month's Oracle magazine about the query
executed under 9iR2 where CBO basically came up with two plans: one for null and
one for non null values. Explain plan showed CONCATENATION where the plan
bifurcated.

So why not generalize this feature for data ranges for example?

      Rating: 5
To Piotr Jarmuz
        July 31, 2003
Reviewer:  A reader

"Why does not CBO create several alternative plans for query execution and use
them dynamically when bind variables are bound and thus their values known?"

I guess following could be the reasons:
1. This feature would be mostlikely useful only for skewed data - not always the
most common scenario - btw, Oracle's peeking the bind variables feature does
this (only once though)
2. Query plan generation is very costly - remember the
difference between soft and hard parses is the query plan generation - you may
end up generating multiple plans
(equivalent to multiple hard parses per query) and
many of them would never get used. Even if this is
a one-time cost per query (in a well-tuned system that
is), it may be expensive.

Tom, over to you for the real story!:)
Thanx!
Menon:)

      Rating: 3
DSS and Forcing
      Cursor Sharing  September 19, 2003
Reviewer:
      Richard  from Oxfordshire, UK
Hi,

1. We have a third party DSS system, which runs over our Oracle tables
(currently 8i, but soon to be 9i).

I know that forcing cursor sharing is ordinarily regarded as a temporary fix,
until the *faulty* SQL is rewritten, but in our case, the DSS SQL is generated
via GUI input from our Account Managers and is, thus, unpredictable in its
content. As we have no way of controlling how the SQL is generated, might
setting CURSOR_SHARING=FORCE be a good thing to do?

2. Fuzzy, imprecise question.I know, but have you any *top tips* for setting
up/running Oracle 9i for such a DSS system?

Thanks.

          Followup:

1) yes, as long as the side effects are OK...

however, in a DSS system, it is sometimes acceptable to use literals (in a DW it
is common in fact).  binds are mandatory in transactional systems where you are
doing queries per second.  binds are not as mandatory in reporting systems where
you do minutes per query.

2)  well, it is a 3rd party app -- they should have guidelines based on their
experiences.  it is really all about the application here.

      Rating:
      2
DSS  September 22, 2003
Reviewer:
      Richard  from Thame, Oxfordshire, UK
Thank you!

      Rating:
      4
DBMS_SHARED_POOL  November 17, 2003

Reviewer:  Denise  from Virginia, USA
Tom

I believe we found the source of our performance issues.

When I read chapter 2 in your book on the Oracle Architecture and Shared Pool
segment your explanation of performance degradation described what we are
experiencing to a tee...especially the decline in performance as the day wears
on and having to shutdown the server to 'clear out' or flush out the shared_pool
and start the cycle all over again.

I've attached the error message we are receiving regarding
invalid objects.

If the cause of the problem is resulting from the software
developers not using bind variables it is beyond our capacity to correct.

We can only hope that the software vendor employs a competent DBA that knows
about this stuff.

By looking at the below message can you assess what the
problem is.

forever thankful...

Errors for PACKAGE BODY DBMS_SHARED_POOL:

LINE/COL ERROR
-------- -----------------------------------------------------------------
23/7     PL/SQL: SQL Statement ignored
23/48    PLS-00201: identifier 'X$KGLOB' must be declared
79/9     PL/SQL: SQL Statement ignored
84/14    PLS-00904: insufficient privilege to access object SYS.V_$SQLAREA
101/7    PL/SQL: Statement ignored
101/10   PLS-00364: loop index variable 'REC' use is invalid

SUBSTR(OBJECT_NAME,1,40)                 OBJECT_TYPE
---------------------------------------- ------------------
DBMS_SHARED_POOL                         PACKAGE BODY

1 row selected.

          Followup:

it looks like you installed dbms_shared_pool as someone other then SYS or "as
SYSDBA".  It must be installed as SYS or "as SYSDBA"

      Rating: 5
Thanks
      Tom!  November 19, 2003
Reviewer:  Denise
       from Virginia, USA
I should have known better....DBMS packages are System
packages owned by SYS.

Hope everything is going ok at the seminar in Quebec...
wish I was there. Will you be doing a seminar on the
east coast anytime soon...D.C. perhaps.

take care...

      Rating: 4
from your
      example above  December 14, 2003
Reviewer:
      A reader
Hi

From this example you posted

alter session set cursor_sharing=force;
@trace
set termout off
select * from t query1 where id = 0;
select * from t query1 where id = 1000;
select * from t query2 where id = 1000;
select * from t query2 where id = 0;

You said bind peeking set a generic optimized plan and this plan may change day
after day or when the shared sql is fluhsed from shared pool. Am I right?

How can we disable bind peeking?

          Followup:

you do not want to disable bind peeking.  It is there for your benefit.

The concept is -- you have an application.  It has a query.  The query uses
binds.  The first execute of the query will determine how the query is to be
executed optimally for this application at that point.  The application will
either always use inputs that use the index or never use inputs that use the
index.  The application "context" (the point at which that query is executed) is
what determines that plan more then anything else.

So while the plan "could change", the fact is the plan won't change cause the
inputs fed to that query will be enough to tell us "plan a" or "plan b" and
either of plan a or plan b will be used every time.

To "disable" bind peeking -- don't use binds.  ONLY appropriate in a data
warehouse.

      Rating: 3
exmaple of how
      cursor_sharing can break an application  December 14, 2003

Reviewer:  A reader
Hi

I read from a poster that this parameter broke his application... how can that
be?

          Followup:

well, i gave a couple of reasons in the original answer -- but there were issues
with earlier releases (eg: in the first release they did not quote the
identifiers, that could cause real issues)..

December 15, 2003 Paulo Roberto -- Thanks for the question
      regarding "windows 2000 patch", version nono

      You Asked
helo, Tom!

I'd like to ask you about the oracle 8 installation...
hope you can help me... please :)..

here is it... is it necessary to get some patch to allow the
oracle 8 work in windows 2000? or... is there any problem with
the execusion of the oracle 8 in windows 2000?

thank you so much, tom.
[[ ]]'S paulo.

      and we said...
You need to have Oracle8i release 2, version 8.1.6 or higher.

Prior releases, those releases that existed prior to win2k, are
not supported and may or may not install/run on Win2k.
Review &
      Followup

      Rating: 1
Oracle
      8i  December 15, 2003
Reviewer:  Thato
       from South Africa
I have 8i release 2 but when I try to install on win2000 I get an error message
"No top level NT products".

          Followup:

well, 8iR2 predates windoze XP by quite a while.

I had similar problems getting Word to install on my linux machine :)

Same answer as above -- just switch the operating system name.  8iR2 is so far
out of support at this point in time -- you would have to use an operating
system that was a contemporary of it.  Windows NT or 2000.

December 15, 2003 Chris -- Thanks for the question regarding
      "Application Testing", version 9.2

      You Asked
Hi, Tom.

Both on this site and in Expert One-On-One you alluded to some of the procedures
you expect your own team of developers to follow.  Could you expand on that a
little bit? For example, what do you tell a new developer that joins your team?
Do you have specific requirements when it comes to unit, application and system
testing?  Benchmarking?  Ongoing performance testing?

I realize the question is more than a little broad.  Any insight you can give is
very much appreciated.

Thanks

Chris

      and we said...
I expect them to learn fully the tools at their disposal.

Not just a programming language, but everything.  The database, the OS, whatever
they are using.

Sometimes the answer is grep, sed and awk.

Other times, it is something deep in the database.

Unless you know fully what is in your toolbox, you'll never be able to use it --
all you'll have is a hammer.

I only ask them to keep learning something new -- every day.  Find new ways to
do the same things.

Then, benchmark them.

Scientifically find out which is best, and realize that best only applies in the
case they tested for.  (thats why I hate questions that start with "which is the
best ....." -- for example "whats the best type of table.".  That is a not
"good" question.  If there were  a universal best -- we would have only created
one kind.  No need to wasted time on other types)

I expect them to build to perform (not tune) from day 1 -- in the design.  Tune
is a 4 letter word.

I expect them to have complete instrumentation everything -- so when we do need
to tune, we have what we need.

defensive programming -- 1/2 of my code is probably what others would call
"overhead".

do it in SQL.  think in sets.  slow by slow (whoops - meant row by row) is so
1980's.

thats a good start.
Review & Followup

      Rating:
      5
  December 14, 2003
Reviewer:
      A reader

      Rating:
      3
Sets  December 15, 2003
Reviewer:
      DXL  from UK
Can you expand and explain a bit more about " sql, thinking in sets" i've seen
you mention this before. I understand what sets are but I would just like you to
explain (maybe with an example or two) of how thinking in sets rather than row
by row can help you write sql.
I believe you have said it helps with very complicated queries.

Many thanks

          Followup:

instead of:

  for x in ( select * from t1 )
  loop
      update t2 set ..... where key = t1.key;
      if ( sql%rowcount = 0 )
      then
         insert into t2 values X;
      end if;
  end loop;

think "merge"

Instead of coding a join yourself (you would not BELIEVE how many times I see
this) think "single query"

Anytime you see a cursor for loop ask yourself "why -- maybe this is just a
single DML statement"

Anytime you see someone taking a flat file and reading it row by row (slow by
slow) -- doing some lookup -- and then inserting the data -- ask yourself "why
isn't that a single sql statement with an external table and maybe a pipelined
function"

see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:13972936942324
for example.

Consider what this query does:
with players as
( select 'P'||rownum username
    from all_objects
   where rownum <= 8),
weeks as
( select rownum week
    from all_objects
   where rownum <= 7 )
select week,
       max(decode(rn,1,username,null)) u1,
       max(decode(rn,2,username,null)) u2,
       max(decode(rn,3,username,null)) u3,
       max(decode(rn,4,username,null)) u4,
       max(decode(rn,5,username,null)) u5,
       max(decode(rn,6,username,null)) u6,
       max(decode(rn,7,username,null)) u7,
       max(decode(rn,8,username,null)) u8
 from ( select username,
               week,
               row_number() over (partition by week order by rnd) rn
          from ( select username, week, dbms_random.random rnd
                   from players, weeks
               )
     )
 group by week
/

It is a "program" to randomly assign 8 player to one of two sets of 4 players
for 7 weeks.

In short, it creates a golf roster for a tournament.

Think in sets, not procedurally.

Look at my analytic SQL answers -- think in "sets".

      Rating:
      5
  December 15, 2003
Reviewer:
      A reader

      Rating:
      5
  December 15, 2003
Reviewer:
      A reader
I love this school of Tom. :-)

December 15, 2003 Robyn -- Thanks for the question regarding
      "Analyzing LOB segments in an Auto Segment Space Managed Tablespace",
      version 9.2.0

      You Asked
Hi Tom,

Can you tell me if there is any way of analyzing space usage for LOB segments
which are stored in an ASSM tablespace ?

I can't use dbms_space.free_blocks or dbms_space.unused_space on segments in a
tablespace with auto segment space management.

I can't use dbms_space.space_usage on a lobsegment. (only works for tables,
indexes and clusters)

There must be a way to do it - can you shed any light ?

      and we said...
You can use dbms_lob.space_usage?

ops$tkyte@ORA920> variable l_unformatted_blocks number
ops$tkyte@ORA920> variable l_unformatted_bytes number
ops$tkyte@ORA920> variable l_fs1_blocks number
ops$tkyte@ORA920> variable l_fs1_bytes number
ops$tkyte@ORA920> variable l_fs2_blocks number
ops$tkyte@ORA920> variable l_fs2_bytes number
ops$tkyte@ORA920> variable l_fs3_blocks number
ops$tkyte@ORA920> variable l_fs3_bytes number
ops$tkyte@ORA920> variable l_fs4_blocks number
ops$tkyte@ORA920> variable l_fs4_bytes number
ops$tkyte@ORA920> variable l_full_blocks number
ops$tkyte@ORA920> variable l_full_bytes number
ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2       dbms_space.space_usage
  3       ( USER, 'SYS_LOB0000059125C00002$$',
  4             'LOB',
  5             :l_unformatted_blocks,
  6         :l_unformatted_bytes, :l_fs1_blocks, :l_fs1_bytes,
  7         :l_fs2_blocks, :l_fs2_bytes, :l_fs3_blocks, :l_fs3_bytes,
  8         :l_fs4_blocks, :l_fs4_bytes, :l_full_blocks, :l_full_bytes );
  9  end;
 10  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select
  2             :l_unformatted_blocks,
  3         :l_unformatted_bytes, :l_fs1_blocks, :l_fs1_bytes,
  4         :l_fs2_blocks, :l_fs2_bytes, :l_fs3_blocks, :l_fs3_bytes,
  5         :l_fs4_blocks, :l_fs4_bytes, :l_full_blocks, :l_full_bytes
  6   from dual;

:L_UNFORMATTED_BLOCKS :L_UNFORMATTED_BYTES :L_FS1_BLOCKS :L_FS1_BYTES
:L_FS2_BLOCKS :L_FS2_BYTES :L_FS3_BLOCKS :L_FS3_BYTES
--------------------- -------------------- ------------- ------------
------------- ------------ ------------- ------------
:L_FS4_BLOCKS :L_FS4_BYTES :L_FULL_BLOCKS :L_FULL_BYTES
------------- ------------ -------------- -------------
                    2                16384             0            0
 0            0             0            0
            0            0              3         24576

but you might be interested in my script show_space -- this is from my book
Effective Oracle By Design and works in 9i with ASSM (search on this site for
8.0/8i versions)

ops$tkyte@ORA920> exec show_space( 'SYS_LOB0000059125C00002$$', user, 'LOB' )
Unformatted Blocks .....................               2
FS1 Blocks (0-25)  .....................               0
FS2 Blocks (25-50) .....................               0
FS3 Blocks (50-75) .....................               0
FS4 Blocks (75-100).....................               0
Full Blocks        .....................               3
Total Blocks............................               8
Total Bytes.............................          65,536
Total MBytes............................               0
Unused Blocks...........................               0
Unused Bytes............................               0
Last Used Ext FileId....................               9
Last Used Ext BlockId...................             360
Last Used Block.........................               8

PL/SQL procedure successfully completed.

create or replace procedure show_space
( p_segname in varchar2,
  p_owner   in varchar2 default user,
  p_type    in varchar2 default 'TABLE',
  p_partition in varchar2 default NULL )
-- this procedure uses authid current user so it can query DBA_*
-- views using privileges from a ROLE and so it can be installed
-- once per database, instead of once per user that wanted to use it
authid current_user
as
    l_free_blks                 number;
    l_total_blocks              number;
    l_total_bytes               number;
    l_unused_blocks             number;
    l_unused_bytes              number;
    l_LastUsedExtFileId         number;
    l_LastUsedExtBlockId        number;
    l_LAST_USED_BLOCK           number;
    l_segment_space_mgmt        varchar2(255);
    l_unformatted_blocks number;
    l_unformatted_bytes number;
    l_fs1_blocks number; l_fs1_bytes number;
    l_fs2_blocks number; l_fs2_bytes number;
    l_fs3_blocks number; l_fs3_bytes number;
    l_fs4_blocks number; l_fs4_bytes number;
    l_full_blocks number; l_full_bytes number;

    -- inline procedure to print out numbers nicely formatted
    -- with a simple label
    procedure p( p_label in varchar2, p_num in number )
    is
    begin
        dbms_output.put_line( rpad(p_label,40,'.') ||
                              to_char(p_num,'999,999,999,999') );
    end;
begin
   -- this query is executed dynamically in order to allow this procedure
   -- to be created by a user who has access to DBA_SEGMENTS/TABLESPACES
   -- via a role as is customary.
   -- NOTE: at runtime, the invoker MUST have access to these two
   -- views!
   -- this query determines if the object is a ASSM object or not
   begin
      execute immediate
          'select ts.segment_space_management
             from dba_segments seg, dba_tablespaces ts
            where seg.segment_name      = :p_segname
              and (:p_partition is null or
                  seg.partition_name = :p_partition)
              and seg.owner = :p_owner
              and seg.tablespace_name = ts.tablespace_name'
             into l_segment_space_mgmt
            using p_segname, p_partition, p_partition, p_owner;
   exception
       when too_many_rows then
          dbms_output.put_line
          ( 'This must be a partitioned table, use p_partition => ');
          return;
   end;

   -- if the object is in an ASSM tablespace, we must use this API
   -- call to get space information, else we use the FREE_BLOCKS
   -- API for the user managed segments
   if l_segment_space_mgmt = 'AUTO'
   then
     dbms_space.space_usage
     ( p_owner, p_segname, p_type, l_unformatted_blocks,
       l_unformatted_bytes, l_fs1_blocks, l_fs1_bytes,
       l_fs2_blocks, l_fs2_bytes, l_fs3_blocks, l_fs3_bytes,
       l_fs4_blocks, l_fs4_bytes, l_full_blocks, l_full_bytes, p_partition);

     p( 'Unformatted Blocks ', l_unformatted_blocks );
     p( 'FS1 Blocks (0-25)  ', l_fs1_blocks );
     p( 'FS2 Blocks (25-50) ', l_fs2_blocks );
     p( 'FS3 Blocks (50-75) ', l_fs3_blocks );
     p( 'FS4 Blocks (75-100)', l_fs4_blocks );
     p( 'Full Blocks        ', l_full_blocks );
  else
     dbms_space.free_blocks(
       segment_owner     => p_owner,
       segment_name      => p_segname,
       segment_type      => p_type,
       freelist_group_id => 0,
       free_blks         => l_free_blks);

     p( 'Free Blocks', l_free_blks );
  end if;

  -- and then the unused space API call to get the rest of the
  -- information
  dbms_space.unused_space
  ( segment_owner     => p_owner,
    segment_name      => p_segname,
    segment_type      => p_type,
    partition_name    => p_partition,
    total_blocks      => l_total_blocks,
    total_bytes       => l_total_bytes,
    unused_blocks     => l_unused_blocks,
    unused_bytes      => l_unused_bytes,
    LAST_USED_EXTENT_FILE_ID => l_LastUsedExtFileId,
    LAST_USED_EXTENT_BLOCK_ID => l_LastUsedExtBlockId,
    LAST_USED_BLOCK => l_LAST_USED_BLOCK );

    p( 'Total Blocks', l_total_blocks );
    p( 'Total Bytes', l_total_bytes );
    p( 'Total MBytes', trunc(l_total_bytes/1024/1024) );
    p( 'Unused Blocks', l_unused_blocks );
    p( 'Unused Bytes', l_unused_bytes );
    p( 'Last Used Ext FileId', l_LastUsedExtFileId );
    p( 'Last Used Ext BlockId', l_LastUsedExtBlockId );
    p( 'Last Used Block', l_LAST_USED_BLOCK );
end;
/

Review &
      Followup

      Rating:
      5
  December 15, 2003
Reviewer:
      A reader
Excellent !!

      Rating:
      5
Thankyou  December 15, 2003
Reviewer:
      Robyn Gotterson  from Australia
I must admit that I didn't try using 'LOB' as the segment_type in
dbms_space.space_usage as the doco says:
segment_type - Type of the segment to be analyzed (TABLE, INDEX, or CLUSTER.
Thanks for your help.

December 15, 2003 Lisa -- Thanks for the question regarding
      "Stripping special characters", version 8i

      You Asked
How would you go about stripping special characters from a
partnumber.  I do not know of an Oracle function to do this. Are
there any packages that do this?  I want to strip everything
except A-Z, a-z, 0-9.
Thanks for your help
Lisa

      and we said...
There is no native function but we can build one.  It could be:

ops$tkyte@8i> create or replace package utils
  2  as
  3      function strip_bad( p_string in varchar2 )
  4      return varchar2;
  5  end;
  6  /

Package created.

ops$tkyte@8i>
ops$tkyte@8i>
ops$tkyte@8i> create or replace package body utils
  2  as
  3
  4  g_bad_chars   varchar2(256);
  5  g_a_bad_char  varchar2(256);
  6
  7  function strip_bad( p_string in varchar2 ) return varchar2
  8  is
  9  begin
 10      return replace(
 11                 translate( p_string,
 12                            g_bad_chars,
 13                            g_a_bad_char ),
 14                 substr( g_a_bad_char, 1, 1 ),
 15                 '' );
 16  end;
 17
 18
 19
 20  begin
 21      for i in 0..255 loop
 22          if ( i not between ascii('a') and ascii('z') AND
 23               i not between ascii('A') and ascii('Z') AND
 24               i not between ascii('0') and ascii('9') )
 25          then
 26              g_bad_chars := g_bad_chars || chr(i);
 27          end if;
 28      end loop;
 29      g_a_bad_char := rpad(
 30                        substr(g_bad_chars,1,1),
 31                        length(g_bad_chars),
 32                        substr(g_bad_chars,1,1));
 33  end;
 34  /

Package body created.

ops$tkyte@8i> select
  2  utils.strip_bad( 'How is this?' ) ,
  3  dump( utils.strip_bad( 'How is this?' )  )
  4  from dual;

UTILS.STRIP_BAD('HOWISTHIS?')
-----------------------------------------------------------------
-----------------------------------
DUMP(UTILS.STRIP_BAD('HOWISTHIS?'))
-----------------------------------------------------------------
-----------------------------------
Howisthis
Typ=1 Len=9: 72,111,119,105,115,116,104,105,115

Review &
      Followup

      Rating: 5
Very good
      but....  May 17, 2002
Reviewer:  Eric W.
       from Denver, CO , USA
Was wondering why all the code isn't inside the function. Kinda confused me.

Thanks.

          Followup:

Well, I needed a custom string of characters that was built procedurally.

I want to procedurally build this string of characters ONCE per session.  Hence
I used the automatic startup code in the package body that will be run once the
first time the package is used in the session.

      Rating: 5
can you explian
      little more this code ?  June 25, 2002
Reviewer:
      A reader
if you are calling just a function, is that loop executed ?
if yes how can we trace it ?
what would be the logical flow ?

 if we have signigicantly large string varchar2(4000)
and around 2M rows ?
( I have a comment field which has this kind of funny chars
  i want to either remove it or convert it into space
  the commets varchar2(4000) !!
)

can we do the same thing ?

what about char like '~!@#$%^&*()'

 do we have reverse function for ascii() ?
so that I can just specify the rage and sould opereate on the string ?

Thanks,

          Followup:

That loop is executed when the package is used for the first time in a session.
So yes, when you call that package for the first time -- that loop is executed.
None of the subsequent calls in your session will "loop"

"trace it" -- what do you mean?

the logical flow would be -- that code is silently and magically invoked by the
plsql runtime BEFORE you call that package for the first time.  Just like a
constructor in Java or C++ is.  It is "magic"

Yes, you can do that to a varchar2(4000) and 2 million rows.  Be patient.

what about those characters? If you want to keep them -- modify the logic to
accomidate that.

chr = reverse of asscii

      Rating:
      5
Thanks  June 25, 2002
Reviewer:
      A reader
Thanks tom and your reply is very fast.

 trace meance just a debug but I can do it with dbms_output
so sorry for that question.

thanks again
v--

      Rating: 5
useful for
      checking passwords  July 17, 2002
Reviewer:
      Howard Hammermann  from Crystal City, VA
I used part of this code on a form that validates a user's password. It checks
to be sure their password does not include any special characters. Excellent.

      Rating: 5
how to print
      the chars which are not ther in table.  May 22, 2003

Reviewer:  Chandra S.Reddy  from India
Hi Tom,
I have a table like below.

desc t1
Name
-------
S

select S, count(s) from t1
group by S ;

S   COUNT(*)
- ----------
A         17
B         78
C         84
F         23
G         30
I         30
O         40
P         24
R         60
S         36
T        108
U         24
W         18

In the above list some characters are missing, since they are not there in table
t1 itself.(say letters like D,M,N,O,X,Y,Z). these characters are not at all
there in table.
But my requirement is missing chars in the list of A,B,C, D, E...XYZ should
display with the count zero.

like
D  0
M  0
N  0
O  0
X  0
Y  0
Z  0

Will it be possible through the SQL query.

Have a great time.

          Followup:

select a.s, nvl(b.cnt,0)
  from (select chr(ascii('A')+rownum-1) s from all_objects where rownum <= 26 )
a,
       (select S, count(s) cnt from t1 group by S ) b
 where a.s = b.s(+)
/

      Rating: 4
What else you
      got....  May 23, 2003
Reviewer:  DH
       from USA
in that "utils" package of yours?  :-)

          Followup:

nothing actually, it was a "demo" ;)

      Rating: 4
I'll
      bet...  May 23, 2003
Reviewer:  DH
       from USA
that you've probably got dozens of such 'demos' that would make a great utility
package.

      Rating:
      5
Great.  May 25, 2003
Reviewer:
      A reader
Great answer.

      Rating: 2
Issue with
      number datatype accessed from java   December 08, 2003

Reviewer:  raj  from US
We have a number(12,2) columns for *amounts* all over the database. Somehow bad
data is stored in these columns and core-dumps or throws null pointer error to
java programs.
Eg:

select number_col, dump(number_col, 10)
from table_xxx
where number_col < 1 and number_col > 0.9999999999
/

0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1
0    Typ=2 Len=2: 193,1

select dump(0, 10) from dual
/
Typ=2 Len=1: 128
select dump(0.0) from dual;
Typ=2 Len=1: 128

I have already read how Oracle stores numbers in metalink.
Our developer also has did quite some research from the java side and tried to
insert such a value, but failed.
{ zero (0.0) is inserted with bad data, the dumps above show the difference }

Tom,
Could you please clarify how this would happen ? We have been using 9.0.1 and
currently running 9.2.0.4 and we have
always kept our jdbc versions upto date.

Here is an excerpt of what the java developer had reported:
===========================================================
These are the details.
A zero is stored in Oracle as -128
A one is stored in Oracle as -63 2 (2 bytes)
This particular digit (in which the null pointer is thrown) is stored as -63 1

Now, Oracle translates dobles through a particularly goofy logic.
This is inside classes12.jar oracle.sql.LnxLibThin class

    public byte[] lnxren(double d) throws SQLException {
    byte[] is = new byte[20];
    int i = 0;
    boolean bool = !(d < 0.0);
    d = Math.abs(d);
    if (d < 1.0) {
        for (int i_475_ = 0; i_475_ < 8; i_475_++) {
        if (powerTable[i_475_][2] >= d) {
            i -= (int) powerTable[i_475_][0];
            d *= powerTable[i_475_][1];
        }
        }
        if (d < 1.0) {
        i--;
        d *= 100.0;
        }
    } else {
        for (int i_476_ = 0; i_476_ < 8; i_476_++) {
        if (powerTable[i_476_][1] <= d) {
            i += (int) powerTable[i_476_][0];
            d *= powerTable[i_476_][2];
        }
        }
    }
    if (i > 62)
        throw new SQLException(CoreException.getMessage((byte) 3));
    if (i < -65)
        throw new SQLException(CoreException.getMessage((byte) 2));
    boolean bool_477_ = !(d >= 10.0);
    byte i_478_ = 8;
    int i_479_ = 0;
    byte i_480_ = (byte) (int) d;
    for (/**/; i_479_ < i_478_; i_479_++) {
        is[i_479_] = i_480_;
        d = (d - (double) i_480_) * 100.0;
        i_480_ = (byte) (int) d;
    }
    i_479_ = 7;
    if (bool_477_) {
        if (i_480_ >= 50)
        is[i_479_]++;
    } else
        is[i_479_] = (byte) ((is[i_479_] + 5) / 10 * 10);
    for (/**/; is[i_479_] == 100; is[i_479_]++) {
        if (i_479_ == 0) {
        i++;
        is[i_479_] = (byte) 1;
        break;
        }
        is[i_479_] = (byte) 0;
        i_479_--;
    }
    for (i_479_ = 7; i_479_ != 0 && is[i_479_] == 0; i_479_--)
        i_478_--;
    byte[] is_481_ = new byte[i_478_ + 1];
    is_481_[0] = (byte) i;
    System.arraycopy(is, 0, is_481_, 1, i_478_);
    return NUMBER._toLnxFmt(is_481_, bool);
    }

If,
    if (i > 62)
        throw new SQLException(CoreException.getMessage((byte) 3));
    if (i < -65)
        throw new SQLException(CoreException.getMessage((byte) 2));

is commented out then this will give -63 1 if a zero goes into this method as an
argument. However,
such a thing won't happen because one those code is not commented out and two
there is error check before that in classes12.jar oracle.sql.NUMBER
which is the caller to this method:

    public static byte[] toBytes(double d) throws SQLException {
    if (d == 0.0 || d == -0.0)
        return _makeZero();
    if (d == Double.POSITIVE_INFINITY)
        return _makePosInf();
    if (d == Double.NEGATIVE_INFINITY)
        return _makeNegInf();
    return _getLnxLib().lnxren(d);
    }

The makezero returns -128. And hence -63 1 should not go in, and I have not been
able to get it in either,
and I did try a lot of permutations.
Now, we can again have a fail safe mechansim to check whether it is -63, 1 and
treat it as zero. But
again it does not address why it happened.
==========================================================

Sorry if the information is overwhelming.

We are currently updating these *bad* values to zero on tables that have
millions of rows as a work-around.

I have seen this type of data break ASP pages in my previous project.

Can you please provide some insight on how this could
occur and any other solution than updating ?

          Followup:

are you using anything other then jdbc to insert this data?

9999999 times out of 10000000 i see this, there is a C program out there using
the native (binary) format of a number (performance they say) and they write bad
data into the database (when you use binary formats for dates and numbers, we
just accept the binary data, we don't verify it at all - sort of a "direct
mode")

      Rating: 5
Checking for
      special characters in CLOB  December 15, 2003

Reviewer:  A reader
Tom,
Is there a way to find all special characters that may exist in a clob column.
Users have entered special characters into the clob columns by doing a copy/past
from MSWord.
Ex. go into MSWord symbol (or character map) and copy/past the fraction 1/3.

Thank you

          Followup:

finding "all" -- would be procedural.

finding "if there exists" would be a simple dbms_lob.instr.

take a look at

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1533006062995

December 15, 2003 Sateesh -- Thanks for the question regarding
      "Freelists and Initrans", version 8.1.5

      You Asked
This following is the view from v$waitstat

CLASS                  COUNT      TIME
------------------ --------- ---------
data block             74454         0
sort block                 0         0
save undo block            0         0
segment header           984         0
save undo header           0         0
free list                  0         0
extent map                 0         0
bitmap block               0         0
bitmap index block         0         0
unused                     0         0
system undo header         0         0
system undo block          0         0
undo header             2061         0
undo block              6301         0

The data reflects for the past 5days.

The datbase has voluminous updates throught out the day.

The undo header and block might be reduced bu increasing the
number of rollback segments.

But the  contention in data block can be redurced by changing
the value of initrans and freelists. When i refer to oracle
documentation, it says not to modify the default value.

Can you give some info as how to go about tuning these
parameters

Thanks Tom

      and we said...
In 8.1.6, you can dynamically alter the freelists of a table --
in 8.1.5 and before, you will have to RECREATE the object in
order to change the freelists (alter table move can do it as
well).

You want the number of freelists in general to be close to the
number of concurrent insert|update sessions you think will be
happening on a table|index (upto some reasonable number -- say
20).

You would like the initrans to be the number of sessions you
believe will be concurrently accessing a given BLOCK within a
table.

INITRANS rarely needs to be adjusted -- freelists frequently
need to be adjusted.

It is hard to say without having timed_statistics on whether the
count of 75k over 5 days is high (sounds like it is probably
though).  Adjusting freelists on the objects (including their
indexes) that are accessed concurrently for updates and
especially inserts will reduce this contention.
Review &
      Followup

      Rating:
      4
  April 18, 2001
Reviewer:  Markus
      Kauch  from Frankfurt, Germany

      Rating: 4
confused by the
      sentences  February 04, 2002
Reviewer:
      A reader  from florida, usa
quote:
You want the number of freelists in general to be close to the number of
concurrent insert|update sessions you think will be happening on a
table|index(upto some reasonable number -- say 20).
You would like the initrans to be the number of sessions you believe will be
concurrently accessing a given BLOCK within a table.

I am confused here, doesnt number of concurrent insert|update sessions equal to
the number of sessions accessing a given BLOCK?

          Followup:

Ok, a little clarification-

you want freelists to be sized around the number of sessions that will be
issuing "insert" or "update" at the same exact time regardless of transactional
boundaries.  If 5 people issue insert at the same instant -- 5 or so freelists
would be good.

you want initrans to be the number of concurrent transactions you anticipate on
a given block at the same time (much harder to compute unless its a really small
table).  If 5 sessions have an open transaction on table T at the same time AND
all 5 use the same block....

      Rating: 4
how to
      determine which table is causing the contention?  February
      04, 2002
Reviewer:  A reader
how can we determine which table/index is causing data block waits..?

          Followup:

SELECT name, count
  FROM x$kcbfwait, v$datafile
 WHERE indx + 1 = file#;

will show you the waits by file.  You can also use v$sesstat/v$statname to find
buffer waits by session and look at the P1/P2 values to see what FILE/BLOCK is
being waited on.  That query you need to keep running over and over for some
period of time to capture a couple of candidate segments.

      Rating: 4
difference
      between freelist and data blocks in v$waitstat  February 07,
      2002
Reviewer:  A reader
hi

what is the difference between contention of data block waits and freelist waits
in v$waitstat??

      Rating: 3
Chaging
      freelists for partitioned table  April 10, 2002

Reviewer:  Ravi Sharma  from Banglore, India

Hi Tom,
I am using the version 8.1.5.
I want to change the freelists of one of my tables.
The thing is that table is partitioned table.
So I can not use "alter table move tablespace" option for it. What do I need to
do so that I can change its freelists.
Can you give me options and steps ?

Thank You
Ravi

          Followup:

In 815, you need to DROP and CREATE the table all over.  That is the only way I
know to do it.

      Rating:
      4
freelists  March 12, 2003
Reviewer:
      Venkatasubramanian  from India
Tom,

The output of the query suggested returned the following information in our
environment.

NAME                    COUNT
-------------------- --------

/orbicore/oradata/or      133
bicore/hist01.dbf

/orbicore/oradata/or       46
bicore/idx02.dbf

/orbicore/oradata/or       34
bicore/idx03.dbf

How to drill down further and find out the correct objects?
Can you please help me out as this is often an issue for us.

Thanks and Regards,
Venkat

          Followup:

unless those numbers represent a 5 second period, I really don't think you have
a problem.

      Rating: 5
very
      good.  March 12, 2003
Reviewer:  gs

Tom,

If the table has , say 5 indices, should they also have the same number of
freelists? if not, would increasing the freelists on just the table have any
effect?

          Followup:

no, there is no relationship whatsoever between the number of indexes on a table
and the number of freelists.  It is all about how many concurrent modifications
(inserts+updates) you might have.

      Rating: 4
Query to find
      out which table or index is waiting for freelist or
      initrans  March 13, 2003
Reviewer:  Vivek
      Sharma  from India
Hi Tom,

Good Input. A reader has asked how to find out whether which table or index is
waiting for freelist or initrans. I have a query which is helpful in finding out
waits on an objects. The query is

select e.event, substr(s.segment_name,1,30) object_name,
       substr(s.segment_type,1,30) object_type,
       substr(f.file_name,17,20) datafile_name
       from dba_extents s, dba_data_files f,
       (select substr(event,1,30) event, p1, p2 from v$session_wait
               where event = 'buffer busy waits') e
where s.file_id = e.p1
and   e.p2 between s.block_id and s.block_id+s.blocks-1
and   s.file_id = f.file_id;

I think this query should work. Am I correct ? Correct me if I am wrong.

Thanks & Regards
Vivek

          Followup:

it'll show you what is currently being waited on -- so you need to have a really
serious contention issue.

What you might want to do is:

o save p1, p2 from v$session wait a couple of times
o THEN do the join on the ones really waited on lots

that's an expensive query -- and remember -- heavy querying of v$ tables on a
really busy system will -- make it even busier.

      Rating:
      3
Re-phrase  March 13, 2003
Reviewer:
      A reader
"If the table has , say 5 indices, should they also have the same number of
freelists? if not, would increasing the freelists on just the table have any
effect?
"
I think the reader meant, if the table has 10 freelists then should the indexes
on the table also, have 10 freelists each or should they be left at the default.

I think it is "yes", the indexes on the table should have the same number of
freelists as we have for the table. Since an insert or an update will probably
do the same operation on the index.

Thanks

          Followup:

ahh -- ok, i see that now.

yes, the indices should have at least, if not more freelists then the table --
since you fit many more rows/block in an index.

      Rating: 3
a bit of
      confusion...  March 13, 2003
Reviewer:  gs

I meant, if there is lot of contention on the table say "x" and the freelists
are increased, should all the indices of "x" also have to have the same number
of "freelists".

I understand, there is no relation between number of indices and freelists.

          Followup:

see above -- sorry for the confusion.

      Rating: 3
Initrans
      effect  April 09, 2003
Reviewer:  abhi
       from INDIA
hello Sir..

i create a table with 1 initrans and maxtrans 1
 .. but i m able to
update this table from two session concurrently..
mean half rows from one session and rest from other.

sir where i m wrong to understand the effect of maxtrans..
.. pls explain ..

thanx Sir

          Followup:

well, maxtrans is block by block -- so, if rows are on different blocks no
worries.

also -- initrans slipped to 2 silently in some version (thinking 9i...) and
maxtrans with it.  so maxtrans of 1 might be 2 really.  Try three sessions, on a
single block.

and in a subsequent release of oracle -- there will be no such setting anymore
;)

      Rating: 5
Freelist
      contention  April 23, 2003
Reviewer:  Saminathan
      Seerangan  from CA,USA
Dear Tom,

DB Version 8.1.7, Solaris.

We are in the process of moving from DMT to LMT(to take advantages of LMT) and
the same time trying to "find out"/"fine tune" the storage clause parameters as
well.

In order to find out the freelists contention, i did execute the following SQL
query during peak hrs and getting "no records selected",

1)Meaning no object is waiting for freelists. Am i correct?

2)If so i don't need to change FREELISTS and INITRANS parameter, Correct? Please
let me whether i'm right or wrong?

select e.event, substr(s.segment_name,1,30) object_name,
       substr(s.segment_type,1,30) object_type,
       substr(f.file_name,17,20) datafile_name
       from dba_extents s, dba_data_files f,
       (select substr(event,1,30) event, p1, p2 from v$session_wait
               where event = 'buffer busy waits') e
where s.file_id = e.p1
and   e.p2 between s.block_id and s.block_id+s.blocks-1
and   s.file_id = f.file_id;

Expecting your valuable comments as always..

          Followup:

look at v$waitstat and see if you have lots waits with lots of time for free
lists or segment headers.

Your query would show you everyone waiting at that instant -- very hard to see
that as these waits would be very small (short).

      Rating:
      5
  July 07, 2003
Reviewer:
      A reader

      Rating: 4
the 5 second
      clause is applicable  August 05, 2003
Reviewer:
      Ashwani  from India
Dear tom,
The query
SELECT substr(name,1,50), count
  FROM x$kcbfwait, v$datafile
 WHERE indx + 1 = file#
and count>0
order by count desc

resulted in following output

SUBSTR(NAME,1,50)                                       COUNT
-------------------------------------------------- ----------
/ofin6/app/proddata/applsysd01.dbf                        513
/ofin7/app/proddata/applsysx03.dbf                        167
/ofin7/app/proddata/applsysx02.dbf                        150
/ofin6/app/proddata/rbs02.dbf                             134
/ofin6/app/proddata/apd01.dbf                             116
/ofin7/app/proddata/applsysx01.dbf                        100
/ofin6/app/proddata/rbs03.dbf                              88
/ofin6/app/proddata/applsysd02.dbf                         81
/ofin6/app/proddata/rbs01.dbf                              71
/ofin6/app/proddata/applsysd03.dbf                         69
/ofin7/app/proddata/applsysd04.dbf                         61
/ofin6/app/proddata/system03.dbf                           12
/ofin6/app/proddata/system05.dbf                            7
/ofin6/app/proddata/system01.dbf                            2
/ofin6/app/proddata/axd01.dbf                               1
/ofin6/app/proddata/igid01.dbf                              1

The count for some of the files is blow 5 is there a need of worry???

Waiting for a quick response...

          Followup:

and exactly what do you think you are looking at here?

suppose I told you "hey, during my last car trip I experienced 50 red lights I
had to wait at.  Is that good or bad?"

no matter what you say, I will argue successfully that you are wrong.

insufficient data to say anything about anything.

suggestion:  stick with the documented tools (statspack) and views (v$)

      Rating: 5
freelist and
      cpu  August 08, 2003
Reviewer:  Reader

tom, is it a fair statement to say that number of freelists correspond to number
of cpus? I mean on a single cpu system, defining a table with more than one
freelist does not help the concurrent transactions inserting. Thanks.

          Followup:

not a fair statement.  there will still be contention because the OS switches
from process to process to simulate true concurrency.

If you have my book "expert one on one Oracle" -- the scenario I demonstrate in
there on freelist contention was done on a single CPU machine, adding freelists
removed the waits and increased overall through put.

      Rating: 5
freelists in
      index  December 02, 2003
Reviewer:
      A reader
Hi

Since we dont have pctused in index segments what#'s the use of freelists in
index?

          Followup:

for getting freespace

you are inserting lots of A's

I am inserting lots of Z's

you are going to the right hand side of the index.
I'm on the left hand side

we BOTH need free space (new blocks) to insert............

hence, more then one freelist can help..

      Rating: 5
Can more than
      one extent be active to avoid freelist issue?  December 02,
      2003
Reviewer:  Sudhir
Tom,

For a particular segment, oracle is always inserting into one particular extent
(not much deletes in the table lets say) correct?, thus is there some way to
make inserts round robin between all available extents.

To clarify what I am looking for. Lets say I have a very busy table T, in
tablespace TBSP

TBSP is made up of

/d01/tbsp1.dbf
/d02/tbsp2.dbf
/d03/tbsp3.dbf
/d04/tbsp4.dbf

Thus all I/O will go to one disk at any point in time for inserts (assuming
there is not much deletes). If however, I have preallocated one extent in each
file, can oracle use all of them such that some users inserts end up in d01 some
in d02 etc?

Thank you

          Followup:

no, that is not correct.  in a heap based table (normal table) we pull space off
of the freelist and that could give us a block in extent 1, then extent 2 then
extent 1 then extent 55 then extent 1 and so on.

free space is not organized by "extent"

it would not make sense to round robin by "extent"

(the best way to get even io -- striping)

but if you have 4 files like that -- the EXTENTS themselves will be round robin
allocated -- in 1 then 2 then 3 then 4 then 1 then 2 then ..... and so on.

don't over analyze this either -- at the block level, it just would not even
make sense to "round robin".  remember the IO is done in general in the
background by DBWR (big old cache), your insert IO issues are most likely to be
centered around the redo logs

      Rating: 5
Sorry my last
      post title was not correct ...  December 02, 2003

Reviewer:  Sudhir
I didn't mean to say freelist is avoided. I was trying to word question such
that can contention for freelist reduce and/or concurrency on the block reduce

Thank you

      Rating: 5
freelists in
      index  December 03, 2003
Reviewer:
      A reader
Hi

How is freelist controlled in index if there are no pctused...? In tables a
block goes to freelist when it's usage drops below pctused how about index?

          Followup:

when an index block is entirely emptied, we can unlink it from the index and put
it back onto the freelist.

so, the block goes onto the freelist when the block is empty

alter index iname coalesce;

will also probably put blocks back on the freelist as it tries to take adjacent
blocks in the index and see if they can fit on a single block --freeing one
block for reuse elsewhere.

      Rating: 5
I tried to test
      extent use  December 03, 2003
Reviewer:
      Sudhir
Tom,

I am not understanding per earlier discussion how to interpret following.
Can you please tell why data was inserted from one extent to next in
very orderly fashion. Although, I admit I didn't give any freelist clause

This 9.2.0.3 database

Thank you

create tablespace testtablesp datafile '/db01/testtablesp.dbf' size 10m
extent management local uniform size 40k;

create table t tablespace testtablesp storage (minextents 254)
as select * from dba_objects where 1=2;

alter table t add (rn number);

insert into t select d.*,rownum from dba_objects d;

commit;

set linesize 255
column to_block format a30

SQL> l
  1  select extent_id,block_id from_block,
  2         '--> '||(block_id+blocks-1)|| '( '||blocks||' )' to_block
  3  from dba_extents where segment_name='T' and segment_type='TABLE'
  4* order by 1,2
SQL> /

 EXTENT_ID FROM_BLOCK TO_BLOCK
---------- ---------- ------------------------------
         0          9 --> 13( 5 )
         1         14 --> 18( 5 )
         2         19 --> 23( 5 )
         3         24 --> 28( 5 )
         4         29 --> 33( 5 )
         5         34 --> 38( 5 )
         6         39 --> 43( 5 )
         7         44 --> 48( 5 )
         8         49 --> 53( 5 )
         9         54 --> 58( 5 )
        10         59 --> 63( 5 )
        11         64 --> 68( 5 )
        12         69 --> 73( 5 )
        13         74 --> 78( 5 )
        14         79 --> 83( 5 )
        15         84 --> 88( 5 )
        16         89 --> 93( 5 )
        17         94 --> 98( 5 )
        18         99 --> 103( 5 )
        19        104 --> 108( 5 )
        20        109 --> 113( 5 )
        21        114 --> 118( 5 )
        22        119 --> 123( 5 )
        23        124 --> 128( 5 )
        24        129 --> 133( 5 )
        25        134 --> 138( 5 )
        26        139 --> 143( 5 )
        27        144 --> 148( 5 )
        28        149 --> 153( 5 )
        29        154 --> 158( 5 )
        30        159 --> 163( 5 )
        31        164 --> 168( 5 )
        32        169 --> 173( 5 )
        33        174 --> 178( 5 )
        34        179 --> 183( 5 )
        35        184 --> 188( 5 )
        36        189 --> 193( 5 )
        37        194 --> 198( 5 )
        38        199 --> 203( 5 )
        39        204 --> 208( 5 )
        40        209 --> 213( 5 )
        41        214 --> 218( 5 )
        42        219 --> 223( 5 )
        43        224 --> 228( 5 )
        44        229 --> 233( 5 )
        45        234 --> 238( 5 )
        46        239 --> 243( 5 )
        47        244 --> 248( 5 )
        48        249 --> 253( 5 )
        49        254 --> 258( 5 )
        50        259 --> 263( 5 )
        51        264 --> 268( 5 )
        52        269 --> 273( 5 )
        53        274 --> 278( 5 )
        54        279 --> 283( 5 )
        55        284 --> 288( 5 )
        56        289 --> 293( 5 )
        57        294 --> 298( 5 )
        58        299 --> 303( 5 )
        59        304 --> 308( 5 )
        60        309 --> 313( 5 )
        61        314 --> 318( 5 )
        62        319 --> 323( 5 )
        63        324 --> 328( 5 )
        64        329 --> 333( 5 )
        65        334 --> 338( 5 )
        66        339 --> 343( 5 )
        67        344 --> 348( 5 )
        68        349 --> 353( 5 )
        69        354 --> 358( 5 )
        70        359 --> 363( 5 )
        71        364 --> 368( 5 )
        72        369 --> 373( 5 )
        73        374 --> 378( 5 )
        74        379 --> 383( 5 )
        75        384 --> 388( 5 )
        76        389 --> 393( 5 )
        77        394 --> 398( 5 )
        78        399 --> 403( 5 )
        79        404 --> 408( 5 )
        80        409 --> 413( 5 )
        81        414 --> 418( 5 )
        82        419 --> 423( 5 )
        83        424 --> 428( 5 )
        84        429 --> 433( 5 )
        85        434 --> 438( 5 )
        86        439 --> 443( 5 )
        87        444 --> 448( 5 )
        88        449 --> 453( 5 )
        89        454 --> 458( 5 )
        90        459 --> 463( 5 )
        91        464 --> 468( 5 )
        92        469 --> 473( 5 )
        93        474 --> 478( 5 )
        94        479 --> 483( 5 )
        95        484 --> 488( 5 )
        96        489 --> 493( 5 )
        97        494 --> 498( 5 )
        98        499 --> 503( 5 )
        99        504 --> 508( 5 )
       100        509 --> 513( 5 )
       101        514 --> 518( 5 )
       102        519 --> 523( 5 )
       103        524 --> 528( 5 )
       104        529 --> 533( 5 )
       105        534 --> 538( 5 )
       106        539 --> 543( 5 )
       107        544 --> 548( 5 )
       108        549 --> 553( 5 )
       109        554 --> 558( 5 )
       110        559 --> 563( 5 )
       111        564 --> 568( 5 )
       112        569 --> 573( 5 )
       113        574 --> 578( 5 )
       114        579 --> 583( 5 )
       115        584 --> 588( 5 )
       116        589 --> 593( 5 )
       117        594 --> 598( 5 )
       118        599 --> 603( 5 )
       119        604 --> 608( 5 )
       120        609 --> 613( 5 )
       121        614 --> 618( 5 )
       122        619 --> 623( 5 )
       123        624 --> 628( 5 )
       124        629 --> 633( 5 )
       125        634 --> 638( 5 )
       126        639 --> 643( 5 )
       127        644 --> 648( 5 )
       128        649 --> 653( 5 )
       129        654 --> 658( 5 )
       130        659 --> 663( 5 )
       131        664 --> 668( 5 )
       132        669 --> 673( 5 )
       133        674 --> 678( 5 )
       134        679 --> 683( 5 )
       135        684 --> 688( 5 )
       136        689 --> 693( 5 )
       137        694 --> 698( 5 )
       138        699 --> 703( 5 )
       139        704 --> 708( 5 )
       140        709 --> 713( 5 )
       141        714 --> 718( 5 )
       142        719 --> 723( 5 )
       143        724 --> 728( 5 )
       144        729 --> 733( 5 )
       145        734 --> 738( 5 )
       146        739 --> 743( 5 )
       147        744 --> 748( 5 )
       148        749 --> 753( 5 )
       149        754 --> 758( 5 )
       150        759 --> 763( 5 )
       151        764 --> 768( 5 )
       152        769 --> 773( 5 )
       153        774 --> 778( 5 )
       154        779 --> 783( 5 )
       155        784 --> 788( 5 )
       156        789 --> 793( 5 )
       157        794 --> 798( 5 )
       158        799 --> 803( 5 )
       159        804 --> 808( 5 )
       160        809 --> 813( 5 )
       161        814 --> 818( 5 )
       162        819 --> 823( 5 )
       163        824 --> 828( 5 )
       164        829 --> 833( 5 )
       165        834 --> 838( 5 )
       166        839 --> 843( 5 )
       167        844 --> 848( 5 )
       168        849 --> 853( 5 )
       169        854 --> 858( 5 )
       170        859 --> 863( 5 )
       171        864 --> 868( 5 )
       172        869 --> 873( 5 )
       173        874 --> 878( 5 )
       174        879 --> 883( 5 )
       175        884 --> 888( 5 )
       176        889 --> 893( 5 )
       177        894 --> 898( 5 )
       178        899 --> 903( 5 )
       179        904 --> 908( 5 )
       180        909 --> 913( 5 )
       181        914 --> 918( 5 )
       182        919 --> 923( 5 )
       183        924 --> 928( 5 )
       184        929 --> 933( 5 )
       185        934 --> 938( 5 )
       186        939 --> 943( 5 )
       187        944 --> 948( 5 )
       188        949 --> 953( 5 )
       189        954 --> 958( 5 )
       190        959 --> 963( 5 )
       191        964 --> 968( 5 )
       192        969 --> 973( 5 )
       193        974 --> 978( 5 )
       194        979 --> 983( 5 )
       195        984 --> 988( 5 )
       196        989 --> 993( 5 )
       197        994 --> 998( 5 )
       198        999 --> 1003( 5 )
       199       1004 --> 1008( 5 )
       200       1009 --> 1013( 5 )
       201       1014 --> 1018( 5 )
       202       1019 --> 1023( 5 )
       203       1024 --> 1028( 5 )
       204       1029 --> 1033( 5 )
       205       1034 --> 1038( 5 )
       206       1039 --> 1043( 5 )
       207       1044 --> 1048( 5 )
       208       1049 --> 1053( 5 )
       209       1054 --> 1058( 5 )
       210       1059 --> 1063( 5 )
       211       1064 --> 1068( 5 )
       212       1069 --> 1073( 5 )
       213       1074 --> 1078( 5 )
       214       1079 --> 1083( 5 )
       215       1084 --> 1088( 5 )
       216       1089 --> 1093( 5 )
       217       1094 --> 1098( 5 )
       218       1099 --> 1103( 5 )
       219       1104 --> 1108( 5 )
       220       1109 --> 1113( 5 )
       221       1114 --> 1118( 5 )
       222       1119 --> 1123( 5 )
       223       1124 --> 1128( 5 )
       224       1129 --> 1133( 5 )
       225       1134 --> 1138( 5 )
       226       1139 --> 1143( 5 )
       227       1144 --> 1148( 5 )
       228       1149 --> 1153( 5 )
       229       1154 --> 1158( 5 )
       230       1159 --> 1163( 5 )
       231       1164 --> 1168( 5 )
       232       1169 --> 1173( 5 )
       233       1174 --> 1178( 5 )
       234       1179 --> 1183( 5 )
       235       1184 --> 1188( 5 )
       236       1189 --> 1193( 5 )
       237       1194 --> 1198( 5 )
       238       1199 --> 1203( 5 )
       239       1204 --> 1208( 5 )
       240       1209 --> 1213( 5 )
       241       1214 --> 1218( 5 )
       242       1219 --> 1223( 5 )
       243       1224 --> 1228( 5 )
       244       1229 --> 1233( 5 )
       245       1234 --> 1238( 5 )
       246       1239 --> 1243( 5 )
       247       1244 --> 1248( 5 )
       248       1249 --> 1253( 5 )
       249       1254 --> 1258( 5 )
       250       1259 --> 1263( 5 )
       251       1264 --> 1268( 5 )
       252       1269 --> 1273( 5 )
       253       1274 --> 1278( 5 )

254 rows selected.

SQL> set pagesize 20
SQL> set pause on
SQL> select rowid,rn from t order by rn
/
  2

ROWID                      RN
------------------ ----------
AACHiFANGAAAAAKAAA          1
AACHiFANGAAAAAKAAB          2
AACHiFANGAAAAAKAAC          3
AACHiFANGAAAAAKAAD          4
AACHiFANGAAAAAKAAE          5
AACHiFANGAAAAAKAAF          6
AACHiFANGAAAAAKAAG          7
AACHiFANGAAAAAKAAH          8
AACHiFANGAAAAAKAAI          9
AACHiFANGAAAAAKAAJ         10
AACHiFANGAAAAAKAAK         11
AACHiFANGAAAAAKAAL         12
AACHiFANGAAAAAKAAM         13
AACHiFANGAAAAAKAAN         14
AACHiFANGAAAAAKAAO         15
AACHiFANGAAAAAKAAP         16
AACHiFANGAAAAAKAAQ         17

ROWID                      RN
------------------ ----------
AACHiFANGAAAAAKAAR         18
AACHiFANGAAAAAKAAS         19
AACHiFANGAAAAAKAAT         20
AACHiFANGAAAAAKAAU         21
AACHiFANGAAAAAKAAV         22
AACHiFANGAAAAAKAAW         23
AACHiFANGAAAAAKAAX         24
AACHiFANGAAAAAKAAY         25
AACHiFANGAAAAAKAAZ         26
AACHiFANGAAAAAKAAa         27
AACHiFANGAAAAAKAAb         28
AACHiFANGAAAAAKAAc         29
AACHiFANGAAAAAKAAd         30
AACHiFANGAAAAAKAAe         31
AACHiFANGAAAAAKAAf         32
AACHiFANGAAAAAKAAg         33
AACHiFANGAAAAAKAAh         34

ROWID                      RN
------------------ ----------
AACHiFANGAAAAAKAAi         35
AACHiFANGAAAAAKAAj         36
AACHiFANGAAAAAKAAk         37
AACHiFANGAAAAAKAAl         38
AACHiFANGAAAAAKAAm         39
AACHiFANGAAAAAKAAn         40
AACHiFANGAAAAAKAAo         41
AACHiFANGAAAAAKAAp         42
AACHiFANGAAAAAKAAq         43
AACHiFANGAAAAAKAAr         44
AACHiFANGAAAAAKAAs         45
AACHiFANGAAAAAKAAt         46
AACHiFANGAAAAAKAAu         47
AACHiFANGAAAAAKAAv         48
AACHiFANGAAAAAKAAw         49
AACHiFANGAAAAAKAAx         50
AACHiFANGAAAAAKAAy         51

^C

  1  select dbms_rowid.ROWID_BLOCK_NUMBER(rowid), min(rn), max(rn) from
  2  (select rn,rowid from t order by rn)
  3* group by dbms_rowid.ROWID_BLOCK_NUMBER(rowid)
SQL> /

DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)    MIN(RN)    MAX(RN)
------------------------------------ ---------- ----------
                                  10          1         80
                                  11         81        159
                                  12        160        237
                                  13        238        316
                                  14        317        396
                                  15        397        475
                                  16        476        553
                                  17        554        632
                                  18        633        709
                                  19        710        783
                                  20        784        858
                                  21        859        933
                                  22        934       1010
                                  23       1011       1084
                                  24       1085       1160
                                  25       1161       1240
                                  26       1241       1324

DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)    MIN(RN)    MAX(RN)
------------------------------------ ---------- ----------
                                  27       1325       1411
                                  28       1412       1500
                                  29       1501       1588
                                  30       1589       1676
                                  31       1677       1759
                                  32       1760       1839
                                  33       1840       1919
                                  34       1920       1998
                                  35       1999       2080
                                  36       2081       2160
                                  37       2161       2241
                                  38       2242       2320
                                  39       2321       2402
                                  40       2403       2482
                                  41       2483       2561
                                  42       2562       2640
                                  43       2641       2720
^C

          Followup:

lots of data there -- but not really sure

a) what you are looking for or
b) why you are looking at this?

can you concisely state what you were trying to prove/disprove/figure out?

what I see above is that the data went into the table "at the end" as
expected???

not sure what you were looking for tho or what you expected to find.

      Rating: 5
My earlier
      question  December 03, 2003
Reviewer:
      Sudhir
Was it due to fact I had just one datafile that extents got used in orderly
manner?

Thanks

          Followup:

no, extents get used in an orderly fashion -- why wouldn't they?

using ASSM (automatic segment space mgmt) you may well find something utterly
different, but for a single user in a single session -- i would definitely
expect to see what you saw.

      Rating: 5
buffer busy
      waits and hash partitioning  December 15, 2003

Reviewer:  A reader
Hi

I read in Practical Oracle 8i by Jonathan Lewis that hash partitioning can
reduce buffer busy waits. I dont see how... do you?

          Followup:

sure -- say you have a table.

it has a primary key index on it.

it is inserted into heavily by many concurrent sessions.

with a single segment for index/table -- you have 1 root block in the index and
some heavily traversed branch blocks down the right hand side to the right most
leaf block that everyone is after to insert a new row with.  Also -- you have
lots of people going after the freelist(s) for this table segment as well.

Now, you hash partition it into 8 hash partitions.

it is "as if" you reduced the concurrent inserts into this table by a factor of
8.  Each segment (index and table) will recieve 1/8th the inserts as before.
You have 8 times the freelist(s), 1/8th the contention.

You may well have to globally range partition other indexes on this table -- to
avoid hurting your retrievals -- but they'll benefit from being chopped up as
well if you have this really busy table.

      Rating: 5
last
      comment  December 15, 2003
Reviewer:
      A reader
You may well have to globally range partition other indexes on this table -- to
avoid hurting your retrievals -- but they'll benefit from being chopped up as
well if you have this really busy table.

Hmm range partitioned also benefits? I can see Hash partition indexes benefits
from hot index block but range partitioned... how?

          Followup:

same reason.

You have N index partitions, not 1.  you have 1/n the amount of "contention"

December 16, 2003 Neelabh -- Thanks for the question regarding
      "DBMS_UTILITY.COMMA_TO_TABLE limitations ?", version 8.1.6

      You Asked
Hi Tom,

when I use the following code :
------------------------------------------------
CREATE OR REPLACE procedure pr1
as
mytab  dbms_utility.uncl_array;
mystring varchar2(200);
len binary_integer;
begin
mystring := 'yes,no,this,that,is';
dbms_utility.COMMA_TO_TABLE(mystring,len,mytab);

 for i in 1..len
 loop
 dbms_output.put_line('The '||i||' name is
'||substr(mytab(i),1,25));
 end loop;
end;
/
-----------------------------------------------
It gives me the following error
SQL> exec pr1
BEGIN pr1; END;

*
ERROR at line 1:
ORA-00931: missing identifier
ORA-06512: at "SYS.DBMS_UTILITY", line 79
ORA-06512: at "SYS.DBMS_UTILITY", line 108
ORA-06512: at "NEELABH.PR1", line 18
ORA-06512: at line 1

while if I replace line 7 which is
mystring := 'yes,no,this,that,is';
BY
mystring := 'yes,no,this,that,are';
it works to give me the following

SQL> exec pr1
The 1 name is yes
The 2 name is no
The 3 name is this
The 4 name is that
The 5 name is are

PL/SQL procedure successfully completed.

Why is the behaviour like this. Are there some limitations in
the string values which one can use ??

Regards
Neelabh

      and we said...
In a nutshell, these procedures do not do what you think.  These
were built for replication and parsing of snapshot group
tablename strings.  They parse IDENTIFIERS.  In order to
understand COMMA_TO_TABLE, you need to understand NAME_TOKENIZE:

NAME_TOKENIZE

This utility routine simply takes a string that represents some
object name and breaks it into its component pieces for you.
Objects are referenced via:

[schema].[object_name].[procedure|function]@[database link]

NAME_TOKENIZE simply takes a string in that form and breaks it
out into the three leading pieces and the last (database link)
piece.  Additionally, it tells us what byte it stopped parsing
the object name at.  Here is a small example showing what you
can that you might expect back from various object names you
pass to it.  Note that you do not have to use REAL object names
(these tables and procedures do not have to exist)  – but you
must use VALID object identifiers.  If you do not use a valid
object identifier, NAME_TOKENIZE will raise an error.  That
makes NAME_TOKENIZE suitable as a method to discover whether a
given string of characters will be a valid identifier or not:

scott@TKYTE816> declare
  2      l_a      varchar2(30);
  3      l_b      varchar2(30);
  4      l_c      varchar2(30);
  5      l_dblink varchar2(30);
  6      l_next   number;
  7
  8      type vcArray is table of varchar2(255);
  9      l_names vcArray :=
 10          vcArray( 'owner.pkg.proc@database_link',
 11                   'owner.tbl@database_link',
 12                   'tbl',
 13                   '"Owner".tbl',
 14                   'pkg.proc',
 15                   'owner.pkg.proc',
 16                   'proc',
 17                   'owner.pkg.proc@dblink with junk',
 18                   '123' );
 19  begin
 20      for i in 1 .. l_names.count
 21      loop
 22      begin
 23          dbms_utility.name_tokenize(name   => l_names(i),
 24                                     a      => l_a,
 25                                     b      => l_b,
 26                                     c      => l_c,
 27                                     dblink => l_dblink,
 28                                     nextpos=> l_next );
 29
 30          dbms_output.put_line( 'name    ' || l_names(i) );
 31          dbms_output.put_line( 'A       ' || l_a );
 32          dbms_output.put_line( 'B       ' || l_b );
 33          dbms_output.put_line( 'C       ' || l_c );
 34          dbms_output.put_line( 'dblink  ' || l_dblink );
 35          dbms_output.put_line( 'next    ' || l_next || ' '
||
 36                                  length(l_names(i)));
 37          dbms_output.put_line( '-----------------------' );
 38      exception
 39          when others then
 40              dbms_output.put_line( 'name    '||l_names(i) );
 41              dbms_output.put_line( sqlerrm );
 42      end;
 43      end loop;
 44  end;
 45  /
name    owner.pkg.proc@database_link
A       OWNER
B       PKG
C       PROC
dblink  DATABASE_LINK
next    28 28

As you can see – this breaks out the various bits and pieces of
our object name for us.  Here the NEXT is set to the length of
the string – parsing ended when we hit the end of the string in
this case.  Since we used every possible piece of the object
name, all 4 components are filled in.  Now for the remaining
examples:

name    owner.tbl@database_link
A       OWNER
B       TBL
C
dblink  DATABASE_LINK
next    23 23
-----------------------
name    tbl
A       TBL
B
C
dblink
next    3 3
-----------------------

Notice here how B and C are left NULL.  Even though an object
identifier is SCHEMA.OBJECT.PROCEDURE, NAME_TOKENIZE makes no
attempt to put the “TBL” into the B out parameter.  It simply
takes the first part it finds and puts it in A, the next into B
and so on.  A, B and C do not represent specific pieces of the
object name – just the first found, next found and so on.

name    "Owner".tbl
A       Owner
B       TBL
C
dblink
next    11 11
-----------------------

Here is something interesting.  In the previous examples,
NAME_TOKENIZE uppercased everything.  That is because
identifiers are in upper case unless you use QUOTED identifiers.
 Here, we used a quoted identifier and NAME_TOKENIZE will
preserve that for us and remove the quotes!

name    pkg.proc
A       PKG
B       PROC
C
dblink
next    8 8
-----------------------
name    owner.pkg.proc
A       OWNER
B       PKG
C       PROC
dblink
next    14 14
-----------------------
name    proc
A       PROC
B
C
dblink
next    4 4
-----------------------
name    owner.pkg.proc@dblink with junk
A       OWNER
B       PKG
C       PROC
dblink  DBLINK
next    22 31
-----------------------

There is an example where the parsing stopped BEFORE we ran out
of string.  NAME_TOKENIZE is telling us it stopped parsing at
byte 22 out of 31.  That is the space right before “with junk”.
 It simply ignores the remaining pieces of the string for us.

name    123
ORA-00931: missing identifier

PL/SQL procedure successfully completed.

And lastly, this shows if we use an invalid identifier,
NAME_TOKENIZE will throw and exception.  It checks all tokens
for being valid identifiers before returning.  That makes it
useful as a tool to validate object names if you are building an
application that will create objects in the Oracle database.
For example, if you are building a data modelling tool and would
like to validate that the name the end user wants to use for a
table or column name is valid – NAME_TOKENIZE will do the work
for you)

COMMA_TO_TABLE, TABLE_TO_COMMA

These two utilities either take a comma delimited string of
IDENTIFIERS and parse them into a PLSQL table (COMMA_TO_TABLE)
or table a PLSQL table of any type of string and make a comma
delimited string of them (TABLE_TO_COMMA).  I stress the work
IDENTIFIERS above because COMMA_TO_TABLE uses NAME_TOKENIZE to
parse the strings – hence as we saw in that section, we need to
use valid Oracle identifiers (or quoted identifiers).  This
still limits us to 30 characters per element in our
comma-delimited string however.

This utility is most useful for applications that want to store
a list of tablenames in a single string for example and have
them easily converted to an array in PLSQL at runtime.
Otherwise, it is of limited use.

Here is an example using this routine and demonstrating how it
deals with long identifiers and invalid identifiers:

scott@TKYTE816> declare
  2      type vcArray is table of varchar2(4000);
  3
  4      l_names  vcArray := vcArray( 'emp,dept,bonus',
  5                                   'a,  b,   c',
  6                                   '123, 456, 789',
  7                                   '"123", "456", "789"',
  8 '"This is a long string, longer then 32 characters","b",c');
  9      l_tablen number;
 10      l_tab    dbms_utility.uncl_array;
 11  begin
 12      for i in 1 .. l_names.count
 13      loop
 14          dbms_output.put_line( chr(10) ||
 15                                '[' || l_names(i) || ']' );
 16      begin
 17
 18          dbms_utility.comma_to_table( l_names(i),
 19                                       l_tablen, l_tab );
 20
 21          for j in 1..l_tablen
 22          loop
 23              dbms_output.put_line( '[' || l_tab(j) || ']' );
 24          end loop;
 25
 26          l_names(i) := null;
 27          dbms_utility.table_to_comma( l_tab,
 28                                       l_tablen, l_names(i)
);
 29          dbms_output.put_line( l_names(i) );
 30      exception
 31          when others then
 32              dbms_output.put_line( sqlerrm );
 33      end;
 34      end loop;
 35  end;
 36  /

[emp,dept,bonus]
[emp]
[dept]
[bonus]
emp,dept,bonus

So, that shows that it can take the string “emp,dept,bonus” and
break it into a table and put it back together again.

[a,  b,    c]
[a]
[  b]
[    c]
a,  b,    c

This example shows that if you have white space in the list, it
will be preserved. You would have to use the rtrim function to
remove leading white space if you do not want any.

[123, 456, 789]
ORA-00931: missing identifier

This shows that to use this procedure on a comma delimited
string of numbers, we must go one step further as demonstrated
below:

["123", "456", "789"]
["123"]
[ "456"]
[ "789"]
"123", "456", "789"

Here is is able to extract the numbers from the string.  Note
however how it not only retains the leading whitespace but it
also retains the quotes.  It would be upto you to remove them if
you so desire.

["This is a long string, longer then 32 characters","b",c]
ORA-00972: identifier is too long

PL/SQL procedure successfully completed.

And this last example shows that if the identifier is too long
(longer then 30 characters) it will raise an error as well –
these routines are only useful for strings of 30 characters or
less.  While it is true that TABLE_TO_COMMA will take larger
strings then 30 characters, COMMA_TO_TABLE will not be able to
undo that work.

Review &
      Followup

      Rating: 4
a question of
      reserverd words?  August 10, 2001
Reviewer:
      Andrew  from CA, USA
The original example showed one string which worked and one which didn't. I
guess the conclusion is that the example which failed did so because "is" is a
reserved word. "are" is not.
Naming rules:

http://www.oradoc.com/ora816/server.816/a76989/ch29.htm#27571
Reserved words:

http://www.oradoc.com/ora816/server.816/a76989/ap_keywd.htm#617080

      Rating: 5
why isnt this
      working  August 15, 2003
Reviewer:
      A reader
SQL> var lv1 varchar2(30);
SQL> var lv2 varchar2(30);
SQL>  var lv3 varchar2(30);
SQL> var lv4 varchar2(30);
SQL> var lv5 number;
SQL> var lv6 number;
SQL> exec dbms_utility.name_resolve('EMP',1,:lv1,:lv2,:lv3,:lv4,:lv5,:1v6) ;
SP2-0552: Bind variable "1" not declared.

Tom , I expec to see the details of 'EMP' table, but it is not happening so.

Can you please explain why?

          Followup:

:1v6
 ^

you gotta one there.

      Rating: 5
why is my local
      table 'emp' not being recognized by
      dbms_utility.name_resolve  August 15, 2003

Reviewer:  A reader
SQL> @LOCAL_OBJECTS

OBJECT_NAME                                        OBJECT_TYPE
--------------------------------------------------
-------------------------------------------------
BONUS                                              TABLE
CONTRACTS                                          TABLE
DEPT                                               TABLE
DEPT_PK                                            INDEX
DUMMY                                              TABLE
EMP                                                TABLE
PLAN_TABLE                                         TABLE
SALGRADE                                           TABLE
T                                                  TABLE
TEST                                               TABLE

10 rows selected.

SQL> DESC EMP
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
------------------------------------
 EMPNO                                                 NOT NULL NUMBER(4)
 ENAME                                                          VARCHAR2(10)
 JOB                                                            VARCHAR2(9)
 MGR                                                            NUMBER(4)
 HIREDATE                                                       DATE
 SAL                                                            NUMBER(7,2)
 COMM                                                           NUMBER(7,2)
 DEPTNO                                                         NUMBER(2)

SQL> exec dbms_utility.name_resolve('EMP',1,:lv1,:lv2,:lv3,:lv4,:lv5,:lv6) ;
BEGIN dbms_utility.name_resolve('EMP',1,:lv1,:lv2,:lv3,:lv4,:lv5,:lv6) ; END;

*
ERROR at line 1:
ORA-06564: object EMP does not exist
ORA-06512: at "SYS.DBMS_UTILITY", line 68
ORA-06512: at line 1

          Followup:

The dbms_utility.name_resolve procedure is supported only for procedures,
functions, and packages, not tables.  Note the valid return types for
part1_type - 5,6,7 and 9.

      Rating: 5
is
      dbms_utility.name_resolve useful for a regular
      developer  August 15, 2003
Reviewer:
      A reader
is dbms_utility.name_resolve useful for a regular developer, if so in what way..

      Rating: 3
full scan takes
      place  September 04, 2003
Reviewer:  Vipin
       from NYC
Hi Tom,

Creating a type of table and accessing the comma delimited parameter in IN
clause is the best way of handling these scenarios. But I did the following
testcase and realized that the approach always go for a full table scan if the
statistics are available and time taken is also pretty high.

Please see the test case below:-

SQL>  Create table t
  2   (id number primary key,
  3   name varchar2(100))
  4
SQL> /

Table created.

SQL>
SQL>
SQL>  INSERT INTO t
  2   SELECT Id, NAME
  3   FROM   big_table
  4
SQL> /

516430 rows created.

SQL> commit;

Commit complete.

SQL>
SQL> select count(*)
  2  from   t
  3
SQL> /

  COUNT(*)
----------
    516430

SQL>
SQL> set autotrace on
SQL> set timing on
SQL>
SQL>
SQL> CREATE OR REPLACE TYPE my_table IS TABLE OF NUMBER;
  2  /

Type created.

Elapsed: 00:00:00.07
SQL> SELECT * FROM t
  2  WHERE id IN (SELECT * FROM table(CAST(my_table(34,78) AS my_table)))
  3  /

        ID
----------
NAME
--------------------------------------------------------------------------------
        34
/11799933_SchemaProtectionDoma

        78
/12f385b1_DefaultCellEditor3

Elapsed: 00:00:00.06

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     VIEW OF 'VW_NSO_1'
   3    2       SORT (UNIQUE)
   4    3         COLLECTION ITERATOR (CONSTRUCTOR FETCH)
   5    1     TABLE ACCESS (BY INDEX ROWID) OF 'T'
   6    5       INDEX (UNIQUE SCAN) OF 'SYS_C008705' (UNIQUE)

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

Please observe that the time taken is 0.06 seconds and INDEX UNIQUE SCAN is
used.

SQL> analyze table t compute statistics for table for all indexes for all
indexed columns;

Table analyzed.

Elapsed: 00:00:13.04
SQL> SELECT * FROM t
  2  WHERE id IN (SELECT * FROM table(CAST(my_table(34,78) AS my_table)))
  3  /

        ID
----------
NAME
--------------------------------------------------------------------------------
        34
/11799933_SchemaProtectionDoma

        78
/12f385b1_DefaultCellEditor3

Elapsed: 00:00:01.09

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5680951 Card=1 Bytes
          =30)

   1    0   NESTED LOOPS (SEMI) (Cost=5680951 Card=1 Bytes=30)
   2    1     TABLE ACCESS (FULL) OF 'T' (Cost=221 Card=516430 Bytes=1
          5492900)

   3    1     COLLECTION ITERATOR (CONSTRUCTOR FETCH)

Statistics
----------------------------------------------------------
         22  recursive calls
          0  db block gets
       2289  consistent gets
        814  physical reads
          0  redo size
        520  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed

Please observe that the time taken is 1.09 seconds and TABLE FULL SCAN is used.

SQL>
SQL>
SQL>
SQL> analyze table t delete statistics;

Table analyzed.

Elapsed: 00:00:00.00
SQL>
SQL>
SQL> SELECT * FROM t
  2  WHERE id IN (SELECT * FROM table(CAST(my_table(34,78) AS my_table)))
  3  /

        ID
----------
NAME
--------------------------------------------------------------------------------
        34
/11799933_SchemaProtectionDoma

        78
/12f385b1_DefaultCellEditor3

Elapsed: 00:00:00.05

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     VIEW OF 'VW_NSO_1'
   3    2       SORT (UNIQUE)
   4    3         COLLECTION ITERATOR (CONSTRUCTOR FETCH)
   5    1     TABLE ACCESS (BY INDEX ROWID) OF 'T'
   6    5       INDEX (UNIQUE SCAN) OF 'SYS_C008705' (UNIQUE)

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

SQL>

Please observe that the time taken is 0.05 seconds and INDEX UNIQUE SCAN is
used.

Could you please explain why table full scan is used when statistics are
available and more over doesn't this limit us from using this technique.

          Followup:

try first_rows optimization

      Rating: 2
Still having
      problem  September 05, 2003
Reviewer:  Vipin
       from NYC
Hi Tom,

Please find the results of trial with the first_rows hint:-

SQL> SELECT /*+first_rows */ T.*
  2  FROM t
  3      WHERE id IN (SELECT * FROM table(CAST(my_table(34,78) AS my_table)))
  4  /

        ID
----------
NAME
--------------------------------------------------------------------------------
        34
/11799933_SchemaProtectionDoma

        78
/12f385b1_DefaultCellEditor3

Elapsed: 00:00:01.02

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=5680951 Ca
          rd=1 Bytes=30)

   1    0   NESTED LOOPS (SEMI) (Cost=5680951 Card=1 Bytes=30)
   2    1     TABLE ACCESS (FULL) OF 'T' (Cost=221 Card=516430 Bytes=1
          5492900)

   3    1     COLLECTION ITERATOR (CONSTRUCTOR FETCH)

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

SQL> SELECT T.*
  2  FROM t
  3      WHERE id IN (SELECT * FROM table(CAST(my_table(34,78) AS my_table)))
  4  /

        ID
----------
NAME
--------------------------------------------------------------------------------
        34
/11799933_SchemaProtectionDoma

        78
/12f385b1_DefaultCellEditor3

Elapsed: 00:00:01.02

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5680951 Card=1 Bytes
          =30)

   1    0   NESTED LOOPS (SEMI) (Cost=5680951 Card=1 Bytes=30)
   2    1     TABLE ACCESS (FULL) OF 'T' (Cost=221 Card=516430 Bytes=1
          5492900)

   3    1     COLLECTION ITERATOR (CONSTRUCTOR FETCH)

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

As you see there is no much of a difference. Could you please explain why is the
optimizer going for a full scan here? And if this is the case I think this
technique is not able to handle the requirement. Please comment.

          Followup:

use my buddy rownum:

big_table@ORA920> select /*+ first_rows */ * from big_table
  2  where id in ( select * from table(my_table(1,2,3)) where rownum >= 0 )
  3  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=16427 Card=8168
Bytes=841304)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=3 Card=1
Bytes=90)
   2    1     NESTED LOOPS (Cost=16427 Card=8168 Bytes=841304)
   3    2       VIEW OF 'VW_NSO_1' (Cost=12 Card=8168 Bytes=106184)
   4    3         SORT (UNIQUE)
   5    4           COUNT
   6    5             FILTER
   7    6               COLLECTION ITERATOR (CONSTRUCTOR FETCH)
   8    2       INDEX (RANGE SCAN) OF 'BIG_TABLE_PK' (UNIQUE) (Cost=2 Card=1)

i tend to use it on queries against a collection to avoid calling the plsql
stuff over and over...

      Rating:
      4
  September 06, 2003
Reviewer:
      A reader
Hi Tom
could you please explain what is happening in the above case. How optimizer is
influenced by the rownum methode and how this leads to do an index scan over the
outer query.

Thanks

          Followup:

rownum is a non-mergable "thing", like an aggregate.

so, the subquery is materialized to temp - totally different processing takes
place

      Rating: 4
so, a temporary
      table is better here?  November 11, 2003
Reviewer:
      sury
Tom,

clarification for your last follow-up:

"rownum is a non-mergable "thing", like an aggregate.

so, the subquery is materialized to temp - totally different processing takes
place"

so, the values 1,2,3

of the clause
"in ( select * from table(my_table(1,2,3)) "

are put into a temporary table and the the sub-query is executed?

why i asked this question is, i have similar requirement, where in i need to
parse the comma delimited string, and put into a nested table and use that in
the subquery like above. but is it better to use a temporary table to hold the
parsed values, so that temporary table can be used in the subquery?

          Followup:

if you use rownum, it'll just happen automagically for you.

      Rating: 1
Once more about
      name_resolve  December 15, 2003
Reviewer:
      Oleksandr Alesinskyy  from Bad Soden a. Ts. Germany

First of all it is very strange that context parameter is not really documnted.
Doc states that it shall be between 0 and 9, but procedure itself allows only
from 1 to 7 (on 0,8 and 9 it raises exception with list of permitted values in
message text).

Secondly, meaning of context values is unknown (and undocumented). In one of
the replies above was an attempt to use procedure to resolve name of emp table
without success with context speciifed as 1. But if repeat this attempt with
context equal to 2 ateempt will sucsseed. And so on and so forth.

Is there a way to resolve name save the direct iterationover dicitionary views?

          Followup:

well -- it seems you discovered a way -- for tables (and synonyms that point to
tables) and views -- a parameter of "2" does seem to do it.

      Rating: 3
Again about
      name resolve  December 16, 2003
Reviewer:
      Oleksandr Alesinskyy  from Bad Soden am Ts., Germany

Yes, I have discovered a way, but I have to know object type in advance (p.e.
with  context = 2 synonyms for packages aren't resolved). I dare to say that
name_resolve is a crap (at least with existing "documentation").
.

December 16, 2003 -- Thanks for the question regarding "nested
      subquery", version 8.1.6

      You Asked
tom:

one book I read mention that
using nested subqueries instead of joining tables in a single query can lead to
dramatic performance gains ( at times over 1000 percent).
only certain queries will meet the criteria for making this modification . when
you find the right one, this trick will take performace improvement to an
exponentially superior height. The conditions for changing a query to a nested
subquery occur when:
1. table are being joined to return the rows from only one table;
2. conditions from each table will lead to a reasonable percentage of rows to be
retrieved ( more than 10 percent).
example:

select ordno ,custno from order_line ol , order
where order.ordno=ol.ordno
and order.custno=5 and ol.price=200;
execution time 240 minutes.

solution:
select ordno,custno
from order where custno=5
and exists (select 'x'
from order_line ol
where order.ordno=ol.ordno and ol.price=200);
execution time : 9 seconds

***********************************************************
i tested it myself.
create table a ( a int,b int,c int);
create table b ( a int,b int,c int);
begin
for i in 1..1000000 loop
insert into a values (i,i,i);
insert into b values (i,i,i);
end loop;
end;

create index ac on a(c);
create index bc on b(c);

SQL> select a,b from a where a.c=10 and exists (select 'x' from b where a.b=b.b
and b.c=10);

         A          B
---------- ----------
        10         10

Elapsed: 00:00:00.90

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=12)
   1    0   FILTER
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'A' (Cost=2 Card=1 Byte
          s=12)

   3    2       INDEX (RANGE SCAN) OF 'AC' (NON-UNIQUE) (Cost=1 Card=1
          )

   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'B' (Cost=4 Card=1 Byte
          s=8)

   5    4       INDEX (RANGE SCAN) OF 'BC' (NON-UNIQUE) (Cost=3 Card=1
          )

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

SQL> select a.a, a.b from a,b where a.c=10 and a.b=b.b and b.c=10;

         A          B
---------- ----------
        10         10

Elapsed: 00:00:00.90

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=1 Bytes=20)
   1    0   NESTED LOOPS (Cost=5 Card=1 Bytes=20)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'A' (Cost=2 Card=1 Byte
          s=12)

   3    2       INDEX (RANGE SCAN) OF 'AC' (NON-UNIQUE) (Cost=1 Card=1
          )

   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'B' (Cost=3 Card=1 Byte
          s=8)

   5    4       INDEX (RANGE SCAN) OF 'BC' (NON-UNIQUE) (Cost=2 Card=1
          )

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

it gives me almost same plan and execution time.
why?

      and we said...
1) it is called a correlated subquery -- a nested subquery would be a subquery
in a subquery.

2) I do not believe they are telling you the whole story with regards to:

select ordno ,custno from order_line ol , order
where order.ordno=ol.ordno
and order.custno=5 and ol.price=200;
execution time 240 minutes.

solution:
select ordno,custno
from order where custno=5
and exists (select 'x'
from order_line ol
where order.ordno=ol.ordno and ol.price=200);
execution time : 9 seconds

There should not be any reason why those two queries would not perform similarly
if the optimizer was aware of all of the facts.  I'll take a GUESS as to what
happened.

In query #1:
select ordno ,custno from order_line ol , order
where order.ordno=ol.ordno
and order.custno=5 and ol.price=200;

the optimizer sees order joined to order_line and the predicates:

order.custno = 5
ol.price = 200

Now, if the optimizer decided to drive the query off of the order table and an
index existed on CUSTNO, it would index range scan that index, find the order
records and join to every record in that order_line table for that customer
looking for price's = 200.  Lets say that the customer #5 had many orders with
thousands of line items.  We have to make every join and then weed out the ones
where price <> 200.  We do alot of work to find the order line items with price
= 200.

Another way the optimizer could have attacked this problem if there as an index
on price in order_line would be to find ALL order lines such that the price was
200 and then join to find customer 5.  If we have millions of rows with
price=200 and customer 5 has only one of those rows, we'll do a lot of work to
assemble the answer.

On the other hand, with:

select ordno,custno
from order where custno=5
and exists (select 'x'
from order_line ol
where order.ordno=ol.ordno and ol.price=200);

the optimizer would start out with the index on custno finding the records for
customer number five. It would then look for the FIRST record for that customer
in order line such that price = 200 and it would STOP when it found it.

Now, the bottom line is that they are missing another VITAL piece of
information!  It is not only relevant that you select from just one table -- it
is much more relevant that the join is not being used to "multiply" the result
set.  Those two queries above are as different as night and say when it comes to
getting an answer (they give DIFFERENT ANSWERS, there is NO WAY they are
equivalent!)

Unless they give you LOTS MORE information -- their conclusions are not
justified.  One would need to know the query plans, the outcome expected, the
indexes in place to make these "rule of thumb" judgements.  Consider the
following example where I show three things

1) the answers you get from the two queries are totally different
2) sometimes the correlated subquery is faster, sometimes not.
3) if I put an index on to help answer this query -- the response times from
both are the same.

tkyte@TKYTE816> create table order_line
  2  ( ordno int references orders,
  3    line_item int,
  4    price int,
  5    qty int,
  6    primary key( ordno, line_item )
  7  )
  8  /

Table created.

tkyte@TKYTE816>
tkyte@TKYTE816> insert into orders
  2  values ( 1, 5 );

1 row created.

tkyte@TKYTE816>
tkyte@TKYTE816> begin
  2          insert into order_line values ( 1, 0, 200, 1 );
  3          for i in 1 .. 10000
  4          loop
  5                  insert into order_line values ( 1, i, 200+i-1, i );
  6          end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

there is my data.  Note that I carefully placed 200 in there two times (and i
put it in "first")

Now for the queries:

tkyte@TKYTE816> set autotrace on

tkyte@TKYTE816> select orders.ordno ,orders.custno from order_line ol , orders
  2  where orders.ordno=ol.ordno
  3  and orders.custno=5 and ol.price=200;

     ORDNO     CUSTNO
---------- ----------
         1          5
         1          5

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'ORDERS'
   3    2       INDEX (RANGE SCAN) OF 'ORDER_CUST_INDEX' (NON-UNIQUE)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'ORDER_LINE'
   5    4       INDEX (RANGE SCAN) OF 'SYS_C004612' (UNIQUE)

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

tkyte@TKYTE816>
tkyte@TKYTE816> select ordno,custno
  2  from orders where custno=5
  3  and exists (select 'x'
  4  from order_line ol
  5  where orders.ordno=ol.ordno and ol.price=200);

     ORDNO     CUSTNO
---------- ----------
         1          5

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   FILTER
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'ORDERS'
   3    2       INDEX (RANGE SCAN) OF 'ORDER_CUST_INDEX' (NON-UNIQUE)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'ORDER_LINE'
   5    4       INDEX (RANGE SCAN) OF 'SYS_C004612' (UNIQUE)

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

so, basically the same sort of plan (note the NESTED LOOPS versus FILTER
thought).

Two things here though:  the answers are different -- 1 row vesus 2 rows.  The
queries are NOT equivalent.  The second query is "much better" isn't it - only 5
consistent gets.  It was able to stop processing as soon as it found data.  But,
look at what happens when the data is "last" in the index structure (going after
a different price this time)

tkyte@TKYTE816> select orders.ordno ,orders.custno from order_line ol , orders
  2  where orders.ordno=ol.ordno
  3  and orders.custno=5 and ol.price=10199;

     ORDNO     CUSTNO
---------- ----------
         1          5

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'ORDERS'
   3    2       INDEX (RANGE SCAN) OF 'ORDER_CUST_INDEX' (NON-UNIQUE)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'ORDER_LINE'
   5    4       INDEX (RANGE SCAN) OF 'SYS_C004612' (UNIQUE)

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

tkyte@TKYTE816>
tkyte@TKYTE816> select ordno,custno
  2  from orders where custno=5
  3  and exists (select 'x'
  4  from order_line ol
  5  where orders.ordno=ol.ordno and ol.price=10199);

     ORDNO     CUSTNO
---------- ----------
         1          5

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   FILTER
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'ORDERS'
   3    2       INDEX (RANGE SCAN) OF 'ORDER_CUST_INDEX' (NON-UNIQUE)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'ORDER_LINE'
   5    4       INDEX (RANGE SCAN) OF 'SYS_C004612' (UNIQUE)

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

Hmmm, they are the same now.  the correlated subquery didn't find the data
right away -- it did as much work as the join did....  Now, putting in place an
index to help this query:

tkyte@TKYTE816> create index order_line_idx on order_line( ordno, price );

Index created.

tkyte@TKYTE816>
tkyte@TKYTE816> select orders.ordno ,orders.custno from order_line ol , orders
  2  where orders.ordno=ol.ordno
  3  and orders.custno=5 and ol.price=200;

     ORDNO     CUSTNO
---------- ----------
         1          5
         1          5

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'ORDERS'
   3    2       INDEX (RANGE SCAN) OF 'ORDER_CUST_INDEX' (NON-UNIQUE)
   4    1     INDEX (RANGE SCAN) OF 'ORDER_LINE_IDX' (NON-UNIQUE)

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

tkyte@TKYTE816>
tkyte@TKYTE816> select ordno,custno
  2  from orders where custno=5
  3  and exists (select 'x'
  4  from order_line ol
  5  where orders.ordno=ol.ordno and ol.price=200);

     ORDNO     CUSTNO
---------- ----------
         1          5

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   FILTER
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'ORDERS'
   3    2       INDEX (RANGE SCAN) OF 'ORDER_CUST_INDEX' (NON-UNIQUE)
   4    1     INDEX (RANGE SCAN) OF 'ORDER_LINE_IDX' (NON-UNIQUE)

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

tkyte@TKYTE816>
tkyte@TKYTE816> set autotrace off

they are the same now -- the join will be just as quick as the correlated
subquery.

So, the correlated subquery can be used in case where the short-circut
evaluation can be used to remove lots of processing.  this is evidenced in the
first queries without the index. worst case -- the correlated subquery does the
SAME amount of work -- best case it does lots less work.  You must be aware
however that it CHANGES the answer when the tables being joined are 1:M in
relation!!

Review &
      Followup

      Rating: 5
Lots of good
      stuff to abuse our brains with  May 06, 2001

Reviewer:  Harrison  from HockeyTown, VA USA

and a lot better than the response to a user who
wanted to save a backup on the instance machine, where
you said "What happens when the machine goes south?"
First, South is capital, it is an important word, north
is spelled with lower case, because it is.  Machines
going South is a good thing, as is Yankees going north.
(this PC shit is really fun when you get the hang of
it. I am off to reward myself with a doughnut;-)

      Rating: 5
hardcodeing Vs
      single row table Vs inline view  November 20, 2002

Reviewer:  steve  from Montreal
Hi Tom,

I have a simple problem involving 2 tables:

Table 1   site_data_99   contains 1 row of type DATE
-------

select * from site_data_99

    VALUE
    -------------------
    2002-11-19 00:00:00

Table 2:  M  contains close to 2 million rows
--------  and has several columns, 8 partitions.

  2 of the columns in table M are:

     ANSWER_TIME         ->       NOT NULL DATE
     TCH_RELEASE_TIME    ->       NOT NULL DATE

Indexes:
--------

     select index_name,column_name,column_position
     from user_ind_columns where table_name='M'
     order by 1,3

INDEX_NAME                     COLUMN_NAME                    COLUMN_POSITION
------------------------------ ------------------------------ ---------------
MOC_ANSWER_TIME_I              ANSWER_TIME                                  1
MO_TCH_RELEASE_TIME_I          TCH_RELEASE_TIME                             1

Statistics are up to date. These are local partitioned indexes.

Slow Query:
===========

SQL> SELECT NVL(INCOMING_TRUNK_GROUP,'0'),
  2         E_EXCHANGE_IDENTITY
  3  FROM MOBILEORIGINATING,site_data_99
  4   WHERE TCH_RELEASE_TIME >= value
  5         and ANSWER_TIME < value+1;

no rows selected

Elapsed: 00:01:45.89

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=11029 Card=676170
Bytes=30427650)

   1    0   MERGE JOIN (Cost=11029 Card=676170 Bytes=30427650)
   2    1     SORT (JOIN) (Cost=6 Card=164 Bytes=1476)
   3    2       TABLE ACCESS (FULL) OF 'SITE_DATA_99' (Cost=1
Card=164Bytes=1476)

   4    1     FILTER
   5    4       SORT (JOIN)
   6    5         PARTITION RANGE (ALL)
   7    6           TABLE ACCESS (FULL) OF 'M' (Cost=5569 Card=1649196
Bytes=59371056)

Statistics
----------------------------------------------------------
          8  recursive calls
         70  db block gets
      36694  consistent gets
      40290  physical reads
          0  redo size
        266  bytes sent via SQL*Net to client
        277  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          3  sorts (memory)
          1  sorts (disk)
          0  rows processed

However, if I hardcode the value in site_data_99.value into the
query (which in fact is a view), I get:

  1  SELECT NVL(INCOMING_TRUNK_GROUP,'0'),
  2         E_EXCHANGE_IDENTITY
  3  FROM M
  4  WHERE     TCH_RELEASE_TIME >= to_date('2001-11-16','yyyy-mm-dd')
  5*       AND ANSWER_TIME <  to_date('2001-11-16','yyyy-mm-dd') + 1
SQL> /

no rows selected

Elapsed: 00:00:01.98

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=10 Card=1 Bytes=36)
   1    0   PARTITION RANGE (ALL)
   2    1     TABLE ACCESS (BY LOCAL INDEX ROWID) OF 'M' (Cost=10 Card=1
Bytes=36)

   3    2       INDEX (RANGE SCAN) OF 'MOC_ANSWER_TIME_I' (NON-UNIQUE) (Cost=9
Card=1)

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

I also tried:

 SELECT NVL(INCOMING_TRUNK_GROUP,'0'), E_EXCHANGE_IDENTITY
   FROM M WHERE TCH_RELEASE_TIME >= (select value from site_data_99)
        and ANSWER_TIME < (select value+1from site_data_99)
and performace wasalso bad.

What would be the best way out of this (i.e. we cannot hardcode the date in the
query which is actually a view).
Thanks Tom

Steve

          Followup:

How ofen is this query executed.  This is a classic "to bind or not to bind"
question.

The skew of the data is such that

o sometimes an index should be used.
o sometimes an index should definitely NOT be used.

The general plan -- when the optimizer doesn't know the value of VALUE -- is
"full scan"

So tell me -- how many times

o a day
o an hour
o a minute
o or a second

is the query executed?

      Rating:
      4
Followup  November 21, 2002
Reviewer:
      steve  from Mtl
Hi Tom,

It's executed once a day.

Regarding you point:

The general plan -- when the optimizer doesn't know the value of VALUE -- is
"full scan"

You mean a full scan of table "M" - right?

          Followup:

yes, so what I suggest is this (since it is once a day)

HARDCODE IT for this one.

Use a stored procedure that dynamically opens a ref cursor if a client app
fetches from it.

Use a dynamically executed insert into as select if thats what you do.

You can fetch the value out - -build the query and execute it.

(short of hinting it which is something I'd rather not do)

      Rating:
      5
Thanks  November 21, 2002
Reviewer:
      steve  from Mtl
Thanks Tom,

I will do that.

Steve

      Rating: 3
Using Subquery
      when selecting data from 1 table  December 15, 2003

Reviewer:  A reader  from NJ, USA
Tom,

Some of the tuning book I read had the following :

If there are 2 tables say A and B and if you want to select columns only from A
based on some join condition between A and B then use the following construct :

SELECT A.col1,A.Col2,A.col3,...,A.coln
  FROM A
 WHERE A.Col5 = (SELECT B.Col10
                   FROM B
                  WHERE B.Col8 = A.Col4
                    AND ...
                )

This is based on the premise that the join with B can be avoided (since columns
are solely selected from A). However, when I ran through this with my DBA, he
said it is always better to use JOIN even in the above case rather than using
the above construct. My questions are :
1. When will you be using the above construct and under what circumstances will
that be efficient
2. Will it be inefficient even if the above subquery returns only 1 row.
3. Will the query above be efficient only when it is not performed as a
correlated subquery as above but only if it is performed once. Even in that will
join be better rather than construct above. If so, why does the book mention
that?
4. Will be join efficient rather than the above query

          Followup:

well, using a JOIN in general *CHANGES THE ANSWER*  -- what then?

use the CBO and write the query as you like using subqueries, even correlated
ones.  the optimizer itself will do semi-joins when appropriate.

I might be tempted to use an IN

where (a.col5,a.col4) in ( select b.col10, b.col3 from ..... )

that'll be sort of a middle ground here -- it'll be just like a join -- but
won't have side effects like changing the result set!

December 16, 2003 Nag -- Thanks for the question regarding "compare
      range of dates with range of dates", version 8.1.7

      You Asked
Tom

I have a table t with column c_begining and c_ending which are date column type.
It also has a column called skey which is a number.

Now I have two parameters passed into my program p_beginging and p_ending which
are also dates.

I need to compare for every record in t , where p_beginging and p1p_ending (
they may not be equal to c_begining and c_ending ) fall within the range of  c
and c1.

i.e. c_begining and c_ending might be aug1st 2002 and dec12 2002 , but they fall
within the range of p_beginging and p_ending if they are jan1st 2000 and jan1st
2003.

How should I do this comparision in sql itself without using any pl/sql.

Thanks

      and we said...
Do you need to be concerned with all overlaps?  Eg:

in database you have

  01-jan-2001      07-jan-2001

you are given as inputs:

  15-dec-2000     15-jan-2001   - wider then database
  15-dec-2000     04-jan-2001   - overlaps but not contained within
  04-jan-2001     15-jan-2001   - overlaps but not contained within
  02-jan-2001     04-jan-2001   - overlaps and contained within

should all four return a "hit"?

select *
  from t
  where ( c_beginning between p_beginning and p_ending      -- rule 1
       OR c_ending between p_beginning and p_ending         -- rule 2
       OR ( c_beginning < p_beginning and p_ending < c_ending ) );  -- rule 3

will do that.  If you use the following "chart", it depicts all of the possibles
and which "rule" in the predicate above will catch them:

.............................................
             CB                CE
PB                                     PE     -- rule 1
PB                   PE                       -- rule 1
                     PB                PE     -- rule 2
                  PB     PE                   -- rule 3
PB  PE                                        -- no hit
                                        PB PE -- no hit

Review &
      Followup

      Rating:
      4
  August 07, 2002
Reviewer:
      A reader

      Rating: 5
is the below
      approach valid...  August 07, 2002
Reviewer:
      Nag
select from_date,to_date
  from t,(select '01-JuN-02' a , '30-DEc-02' b from dual) x
  where x.a between from_date and to_date and
        x.b between from_date and to_date
        OR ( from_date < x.a and x.b < to_date )

and is this kind of comparision(x.b < to_date ) i.e. value to the left and
column to the right( in your solution) .. is it healthy..

Thanks

          Followup:

define "healthy".  what the heck do you mean by that?

(and please please please -- compare dates to dates, strings to strings, numbers
to numbers.  wrap those dates in a TO_DATE() call and always use 4 digit years
and use an explicit date format to convert the string to a date)

If you are afraid of

    x.b < to_date

for some reason, code

   to_date > x.b

instead.

      Rating: 5
Correction or a
      typo  August 08, 2002
Reviewer:  Logan
      Palanisamy  from Santa Clara, CA 95051
Tom,

.............................................
             CB                CE
PB                                     PE     -- rule 1
PB                   PE                       -- rule 1
                     PB                PE     -- rule 2
                  PB     PE                   -- rule 3
PB  PE                                        -- no hit
                                        PB PE -- no hit

In your chart above, the third row where PE is between CB and CE, belong to rule
2, not rule 1.

The correct one is,
.............................................
             CB                CE
PB                                     PE     -- rule 1
PB                   PE                       -- rule 2 <-- correction
                     PB                PE     -- rule 2
                  PB     PE                   -- rule 3
PB  PE                                        -- no hit
                                        PB PE -- no hit

I guess it was a typo on your part.

          Followup:

rule 1 =  where ( c_beginning between p_beginning and p_ending      -- rule 1

On that row in question, c_beginning is in fact between PB and PE so -- rule 1
gets it in my mind since I was reading the list from TOP to BOTTOM.  Since the
order of evaluation of the predicate in SQL is not deterministic (eg: rule 3
then 2 then 1, or 2 then 3 then 1 or ..... and so on could be the order of
evaluation) you might be right.

But, if you read the "rules" from top to bottom -- rule 1 is the one that
catches row 3.

      Rating: 4
My sql with the
      trick is performing faster.. why any idea...  August 10,
      2002
Reviewer:  Nag
Create table t (from_date date, to_date date);
insert into t values ('01-jan-02', '31-dec-02');

Solution 1.

select *
  from t
  where ( from_date between :p_beginning :and p_ending
       OR to_date between :p_beginning and :p_ending
       OR ( from_date < :p_beginning and :p_ending <to_date ) );

Solution 2.

select from_date,to_date
  from t,(select '01-JuN-02' a , '30-DEc-02' b from dual) x
  where from_date between x.a and x.b and
        to_date between x.a and x.b or
        OR ( from_date < x.a and x.b < to_date )

Tom,

I incorporated solution 1 as advetised by you.
It was working but taking enormous amount of time.

So , I used the trick which I invented as shown in solution 2.

I'm cleverly making the two parameters an inlineview and using the values in my
where clause.
Surprise-o-rama.. I was amazed to see that the performanceof the query as such
report increased
enormously.

What is the difference between what you suggested and what I wrote. What could
be the reason
that the second solution was faster.

          Followup:

Umm, your query is radically different then mine and returns a different
(perhaps wrong, perhaps not wrong) answer.

In any case -- only one of the two queries above could be valid!  They answer
totally different questions.

(i removed a duplicate OR OR in the second query and I moved the colon in ":and
p_ending" to the right place.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( from_date date, to_date date
);

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> variable pb varchar2(20)
ops$tkyte@ORA817DEV.US.ORACLE.COM> variable pe varchar2(20)
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec :pb := '01-jun-2002'; :pe :=
'30-dec-2002';

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( to_date( '22-NOV-2002'
),  to_date( '25-JUN-2004' ) );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2    from t
  3    where ( from_date between to_date(:pb,'dd-mon-yyyy') and
to_date(:pe,'dd-mon-yyyy')
  4         OR to_date between to_date(:pb,'dd-mon-yyyy') and
to_date(:pe,'dd-mon-yyyy')
  5         OR ( from_date < to_date(:pb,'dd-mon-yyyy') and
to_date(:pe,'dd-mon-yyyy') <to_date ) )
  6  /

FROM_DATE TO_DATE
--------- ---------
22-NOV-02 25-JUN-04

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select from_date,to_date
  2    from t,(select to_date(:pb,'dd-mon-yyyy') a , to_date(:pe,'dd-mon-yyyy')
b from dual) x
  3    where from_date between x.a and x.b and
  4          to_date between x.a and x.b
  5                  OR ( from_date < x.a and x.b < to_date )
  6  /

no rows selected

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

My query returned it (overlaps).

Yours does not.

Only you know which one is correct!

      Rating: 5
OOPS... sorry
      my fault .. small typo...   August 10, 2002

Reviewer:  Nag
select from_date,to_date
  from t,(select '01-JuN-02' a , '30-DEc-02' b from dual) x
  where from_date between x.a and x.b and  --mistake here...
        to_date between x.a and x.b or
        OR ( from_date < x.a and x.b < to_date )

above was meant to be...

select from_date,to_date
  from t,(select '01-JuN-02' a , '30-DEc-02' b from dual) x
  where from_date between x.a and x.b OR ..
        to_date between x.a and x.b or
        OR ( from_date < x.a and x.b < to_date )

Now.. for the above comment , both the queries return the same rows...

I tried and the results are as follows...

SQL> ED
Wrote file afiedt.buf

  1  select from_date,to_date
  2        from t,(select '01-JUN-02' a , '01-JUN-03' b from dual) x
  3     where from_date between x.a and x.b OR -- corrected
  4              to_date between x.a and x.b
  5*                     OR ( from_date < x.a and x.b < to_date )
SQL> /

FROM_DATE TO_DATE
--------- ---------
01-JAN-02 31-DEC-02
22-NOV-02 25-JUN-04

Now Tom, going back to the original comment i.e. the immediate above one..

Solution 2 is much faster than solution 1. Believe me it runs very very fast
compared to the solution 1.

Can you please give me an insight into why this is happening..

          Followup:

Nag, you've been coming here long enough -- you should know the answer to this.

Set up the test case, show us the tkprof with timed statistics and all.  Help
use understand what indexes you have.  Look at the differences in the query
plans.

Then, all will be revealed.

Sort of bothersome that you STILL have typos in the queries.  "or .." "or or"
for example.  Be nice to put up stuff that actually runs.  Makes me question
whether what I am looking at is really what you are using or not (eg: am i
totally wasting my time or what)

You should know -- if you want answers, you need to supply information!  Also,
it should be easy for you to look at the traces and *see* what is different and
do some of this analysis yourself.

      Rating:
      2
Programmer  August 11, 2002
Reviewer:
      Jamil Shaibani  from Saudi
Dear Tom
When I try to run the Example I got this error

SQL> insert into t values (to_date('22-NOV-2002'),
to_date('25-JUN-2004'))              *

ERROR at line 1:
ORA-01858: a non-numeric character was found where a numeric was expected

Thank you very much for your help

          Followup:

Your default date format is not dd-mon-rr, must be something else

use to_date( '22-nov-2002', 'dd-mon-yyyy' )

      Rating: 5
Huge apology..
      feeling happy at being admonished by Tom  August 11, 2002

Reviewer:  Nag
Tom, I consider it a privilege to be admonished by you..
But please set your doubts at rest about my integrity in asking this question.
I'm genuinely asking this question.

Now for the trace contents..

select *
      from t
      where ( from_date between to_date(:pb,'dd-mon-yyyy') and
to_date(:pe,'dd-mon-yyyy')
           OR to_date between to_date(:pb,'dd-mon-yyyy') and
to_date(:pe,'dd-mon-yyyy')
           OR ( from_date < to_date(:pb,'dd-mon-yyyy') and
to_date(:pe,'dd-mon-yyyy') <to_date )  )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          2          4           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          2          4           2

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 32

Rows     Row Source Operation
-------  ---------------------------------------------------
      2  TABLE ACCESS FULL T

********************************************************************************

select from_date,to_date
      from t,(select to_date(:pb,'dd-mon-yyyy') a , to_date(:pe,'dd-mon-yyyy')
b from dual) x
      where from_date between x.a and x.b OR
            to_date between x.a and x.b
                    OR ( from_date < x.a and x.b < to_date )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          3          8           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          3          8           2

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 32

Rows     Row Source Operation
-------  ---------------------------------------------------
      2  NESTED LOOPS
      2   TABLE ACCESS FULL DUAL
      2   TABLE ACCESS FULL T

********************************************************************************

ALTER SESSION SET SQL_TRACE=FALSE

In the solution you have provided, I see that we are simply doing a full table
scan of T.

After I rewrote the query using the trick I thought will be useful, then we are
doing a nested loops join i.e. we are full table scanning dual and comparing it
with all the rows returned by T.

So though the time statistics above show 0, when I'm applying the same logic to
my real time situation it is making a lot of difference.

But I have a question here ..
Rows     Row Source Operation
-------  ---------------------------------------------------
      2  NESTED LOOPS
      2   TABLE ACCESS FULL DUAL
      2   TABLE ACCESS FULL T
above which table is being r3ead first dual or T ( as both of them are at the
same level).

Can you also suggest the easiest way to insert a whole lot of data into T, so
that I can get some time on timed statistics.

          Followup:

Looks like the times are the same to me -- 0.00 seconds?

Also the tables in question are trivial.

Show us the example where your query blows the one without dual away.......

You apparently MUST have that data somewhere?  You made the claim already.  Use
the table you used to do your intial tests.

      Rating: 3
Simpler
      alternative  August 24, 2002
Reviewer:  Tony
       from London, UK
I do date overlap checks a lot, and always use this method:

(c_beginning <= p_ending) AND (c_ending >= p_beginning)

which seems more elegant to me.

      Rating: 4
Calculate
      fraction of date range  January 31, 2003
Reviewer:
      A reader  from USA
Tom,

What if I have Prompt StartDate(PS) and Prompt EndDate(PE) as well as in the
database fields: StartDate and DueDate and want to calculate fraction of how
many days of my prompt to the total days between StartDate(SD) and DueDate(DD)?
For example.,
SD - 1/1/2003
DD - 2/15/2003
PS - 12/15/2002
PE - 1/31/2003

So, the calculated field that I am interested in:

PE-1/1/2003 over DD-SD,

I don't count days from 12/15 to 1/1/2003  because they are outside of my SD and
DD range(~31/46)

          Followup:

just do it i guess?  I me

(pe-sd)/(dd-sd)

is perfectly valid.

      Rating: 5
gap analysis
      !!  February 12, 2003
Reviewer:
      A reader
hi tom,

 I want to just do this and I am open for sigle sql or pl/sql program unit.

You said..

15-dec-2000     15-jan-2001   - wider then database
15-dec-2000     04-jan-2001   - overlaps but not contained within
04-jan-2001     15-jan-2001   - overlaps but not contained within
02-jan-2001     04-jan-2001   - overlaps and contained within

 I have records in the database and valid value for that dates.

 for me starting date is fixed let's say 15-dec-2000
eg..

startdate      enddate    value  code
-----------   ---------   -----  ------
15-dec-2000   15-jan-2001  X     ABC123-- flag this gap out
20-jan-2001   19-jan-2002  Y     ABC123
15-jan-2002   null         X     ABC123-- flag overlap
 //so for ABC123 code I want to report gap and overlap

15-dec-2000   15-jan-2001  P     ABC456
15-jan-2001   19-jan-2002  Q     ABC456
19-jan-2002   17-jan-2002  Q     ABC456--- flag there is no
                             value from 17th jan to sysdate
20-jan-2001   19-jan-2002  M     ABC789
15-jan-2002   15-dec-2002  N     ABC789
// flag no value is available from 15-dec-00 to 20-jan-01
   for ABC789 code

15-dec-2000   15-jan-2001  P     ABC012
16-jan-2001   19-jan-2002  Q     ABC012
20-jan-2002   null         Q     ABC012
// copletely valid record...

/***********************************
Note : null implies sysdate
***/

 I can use utl_fie or table to insert to
comment/flag/errored  report.

can you please help..

there are 100,000 distinct records..
all together 242,000 records.

Thanks,

          Followup:

not sure if I have all of your cases -- but this should get you going.  Using
lag and lead we can look forward and back a row and assign whatever flags you
need:

ops$tkyte@ORA920> break on code skip 1
ops$tkyte@ORA920>
ops$tkyte@ORA920> select startdate,
  2         enddate,
  3             last_enddate,
  4             flag,
  5             code,
  6             case when last_enddate is null then null
  7                  when last_enddate < startdate-1 then 'Gap'
  8                          when last_enddate > startdate   then 'Overlap'
  9                          when enddate is not null and next_startdate is null
then 'Missing next'
 10                          else null
 11                  end another_flag
 12    from (
 13  select t.*,
 14         lag(enddate) over (partition by code order by startdate)
last_enddate,
 15             lead(startdate) over (partition by code order by startdate)
next_startdate
 16    from t
 17   order by code, startdate
 18         )
 19  /

STARTDATE ENDDATE   LAST_ENDD F CODE   ANOTHER_FLAG
--------- --------- --------- - ------ ------------
15-DEC-00 15-JAN-01           P ABC012
16-JAN-01 19-JAN-02 15-JAN-01 Q
20-JAN-02           19-JAN-02 Q

15-DEC-00 15-JAN-01           X ABC123
20-JAN-01 19-JAN-02 15-JAN-01 Y        Gap
15-JAN-02           19-JAN-02 X        Overlap

15-DEC-00 15-JAN-01           P ABC456
15-JAN-01 19-JAN-02 15-JAN-01 Q
19-JAN-02 17-JAN-02 19-JAN-02 Q        Missing next

20-JAN-01 19-JAN-02           M ABC789
15-JAN-02 15-DEC-02 19-JAN-02 N        Overlap

11 rows selected.

      Rating:
      5
Thanks,  February 12, 2003
Reviewer:
      A reader
There is no alternative for you at least on this planet i think.

now, can you tell how did you "develop" this query setp by setp. I tried to
create it by breaking it down in pice by pice but it was very difficut to put it
together

-- also in this query how can I drop those rows which
    are 'VALID'? ( The last case in my question example )

Thanks,

          Followup:

Well, it was pretty easy actually.

Looking at a row for a given CODE, what did we need?

o the prior rows enddate -- to look for gaps and overlaps
o the next rows startdate -- to look for missing end records with NULL

that dictates "lag" and "lead".  Once you have those -- CASE is pretty easy to
use to come up with the logic to figure out "gap", "overlap", etc...

to drop the "valid" rows -- just wrap:

ops$tkyte@ORA920> select startdate,
  2         enddate,
  3             last_enddate,
  4             flag,
  5             code,
  6             case when last_enddate is null then null
  7                  when last_enddate < startdate-1 then 'Gap'
  8                          when last_enddate > startdate   then 'Overlap'
  9                          when enddate is not null and next_startdate is null

then 'Missing next'
 10                          else null
 11                  end another_flag
 12    from (
 13  select t.*,
 14         lag(enddate) over (partition by code order by startdate)
last_enddate,
 15             lead(startdate) over (partition by code order by startdate)
next_startdate
 16    from t
 17   order by code, startdate
 18         )
 19  /

as an inline view an select * from ( that query ) where another_flag is not
null;

      Rating:
      5
cool  February 12, 2003
Reviewer:
      asktom fan  from NY
Thanks Tom,

      Rating: 5
Need Help with
      this  July 03, 2003
Reviewer:  Kishan
       from Singapore
Dear Tom:
I need a solution to this query. Could this be done in a single SQL statement.
I'm using 8.1.7

sql> DESC roombooking
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 ROOM_ID                                   NOT NULL VARCHAR2(8) -- Room ID
 BKING_ST_TIME                             NOT NULL DATE    -- Start time
 BKING_END_TIME                            NOT NULL DATE    -- End time
BKING_STS_C                               NOT NULL VARCHAR2(1) -- Booking Status

ROOM_ID  BKING_ST_TIME         BKING_END_TIME        B
-------- -------------------- -------------------- -
(SR1)    07-JAN-2002 11:00:00 07-JAN-2002 13:00:00 C
(SR2)    07-JAN-2002 18:00:00 07-JAN-2002 21:00:00 C

(SR1)    08-JAN-2002 09:00:00 08-JAN-2002 12:00:00 C
(SR2)    08-JAN-2002 12:00:00 08-JAN-2002 15:00:00 C
(SR2)    08-JAN-2002 18:00:00 08-JAN-2002 21:00:00 C

(SR1)    09-JAN-2002 08:00:00 09-JAN-2002 11:00:00 C
(SR1)    09-JAN-2002 11:00:00 09-JAN-2002 13:00:00 C
(SR1)    09-JAN-2002 15:00:00 09-JAN-2002 21:00:00 C

(SR1)    10-JAN-2002 16:00:00 10-JAN-2002 18:00:00 C
(SR1)    10-JAN-2002 18:00:00 10-JAN-2002 21:00:00 C
(SR2)    10-JAN-2002 08:00:00 10-JAN-2002 10:00:00 C
(SR2)    10-JAN-2002 10:00:00 10-JAN-2002 13:00:00 C
(SR2)    10-JAN-2002 13:00:00 10-JAN-2002 15:00:00 C

(SR1)    11-JAN-2002 09:00:00 11-JAN-2002 12:00:00 C
(SR1)    11-JAN-2002 13:00:00 11-JAN-2002 15:00:00 C
(SR1)    11-JAN-2002 18:00:00 11-JAN-2002 21:00:00 C

(SR1)    14-JAN-2002 11:00:00 14-JAN-2002 13:00:00 C
(SR1)    14-JAN-2002 18:00:00 14-JAN-2002 21:00:00 C

The table roombooking stores all bookings made against a set of rooms on a daily
basis. My users want to find out the period when those rooms are empty so that
they can book for someone else..
For example, if they want the information for rooms available between
07-Jan-2002 to 15-Jan-2002 and between 14:00-16:00, the following information
should be given to them, so that the rooms can be booked.

SR1 07-Jan-2002
SR2 07-Jan-2002

SR1 08-Jan-2002

SR2 09-Jan-2002

SR2 11-Jan-2002

SR1-12-Jan-2002
SR2-12-Jan-2002

SR2-14-Jan-2002

          Followup:

All we need for that is

A) a set of dates 07-jan-2002 .. 15-jan-2002, that is inline view A below
B) a set of distinct rooms that are possible, that is B below (best if you have
a room table elsewhere -- the "parent" lookup table)

And then we simply cartesian product these together, generating an inclusive
list of DATES and ROOMS -- looking for DATES+ROOMS that don't have an overlap:

ops$tkyte@ORA920> ops$tkyte@ORA920> select * from
  2    (select to_date( '07-jan-2002') + rownum -1 dates
  3       from all_objects
  4      where rownum <= to_date('15-jan-2002')-to_date('07-jan-2002')+1) a,
  5    (select distinct room_id
  6       from t ) b
  7   where not exists ( select null
  8                        from t
  9                       where bking_st_time <= a.DATES + 16/24
 10                         and bking_end_time >= a.DATES +14/24
 11                         and room_id = b.room_id
 12                      )
 13   order by dates, room_id
 14  /

DATES                ROOM_ID
-------------------- --------
07-jan-2002 00:00:00 (SR1)
07-jan-2002 00:00:00 (SR2)
08-jan-2002 00:00:00 (SR1)
09-jan-2002 00:00:00 (SR2)
11-jan-2002 00:00:00 (SR2)
12-jan-2002 00:00:00 (SR1)
12-jan-2002 00:00:00 (SR2)
13-jan-2002 00:00:00 (SR1)
13-jan-2002 00:00:00 (SR2)
14-jan-2002 00:00:00 (SR1)
14-jan-2002 00:00:00 (SR2)
15-jan-2002 00:00:00 (SR1)
15-jan-2002 00:00:00 (SR2)

13 rows selected.

      Rating: 5
That's
      cool...thank you for your valuable service to Oracle
      community  July 03, 2003
Reviewer:  Kishan

      Rating: 5
Loan amount and
      Loan installment months  September 01, 2003

Reviewer:  A reader
SQL> CREATE TABLE LOANS( LOAN_NUM NUMBER, LOAN_BEGIN_DATE DATE, LOAN_END_DATE
DATE, INSTALLMENT_AMOUNT NUMBER);

Table created.

SQL> INSERT INTO LOANS VALUES( 1, SYSDATE, SYSDATE +365, 1000);

1 row created.

SQL> INSERT INTO LOANS VALUES( 2, SYSDATE + 60, SYSDATE +365, 2000);

1 row created.

SQL> COMMIT;

Commit complete.

SQL> SELECT * FROM LOANS;

 LOAN_NUM LOAN_BEGI LOAN_END_    INSTALLMENT_AMOUNT
--------- --------- --------- ---------
        1 01-SEP-03 31-AUG-04      1000
        2 31-OCT-03 31-AUG-04      2000

We need to display the sum of installment amount for each month for the next 3
years( starting from the current month.)
With the given data, 1.we have to show the data for each month and the total of
installments
for those months,plus 2. rest of the months should be shown as zero.

We need to take care of the over laps, all of the below..

 - wider then database
 - overlaps but not contained within
 - overlaps and contained within

The resulting grid should have summary for each row returned by the following...

select to_char(add_months(sysdate, (rownum-1)),'MM RRRR')  dt
from all_objects
where rownum <37

Please train into the insights of how you verbaged the problem, and how you
arrived a the solution i.e. how you broke it into pieces.

Thanks

      Rating: 5
What
      helps  September 02, 2003
Reviewer:
      A reader
Tom, what helps is the reason why you did not deem it fit to answer the above
request.

          Followup:

didn't see like sufficient information to give you an answer.  i don't really
know what "verbiage" you are looking for or really what problem you were  trying
to solve.

      Rating:
      5
  September 03, 2003
Reviewer:
      A reader
select to_char(add_months(sysdate, (rownum-1)),'MM RRRR')  dt from all_objects
where rownum <37

The above query returns 36 months forward from the current month , i.e. 09 2003.

For each of those months, I have to display , the total installments due for all
the loans put together.

The final output, I foresee will be something like this

09 2003      10,000
10 2003      14,000
11 2003      12,000
12 2003      15,000
01 2003      17,000
.....
......
etc

DATABASE VALUES might be

loan_no     loan_begin_date     loan_end_date  installment
  1             01/01/2002        01/01/2004    1,500
  2             09/01/2003        01/01/2004    1,500
  3             10/01/2003        01/01/2004    1,500

I have to display a grid of month and the total installments
for the next 36 months, from the month of the current month.
The above data will show total installments for upto 01/01/2004 , rest of the
months should come up as zero.

Is this helpful.

      Rating:
      5
  September 03, 2003
Reviewer:
      A reader
Do you need more information /explanation of the solution I'm looking for?

          Followup:

you know what -- they won't let me use my aircard on a plane.  so, when I'm
actually not able to be on the net -- I actually don't answer questions.
HMMM...

if you have that table T with the months in it -- the one you made with
all_objects, you can easily:

select month, (select sum(whatever) from another_table where start_date <=
T.month and end_date >= T.month )
  from t;

to achieve your report.

      Rating: 5
Range
      constraint  October 16, 2003
Reviewer:
      Tracy  from UK
If you have two number columns in a table say c1 and c2, where c2 is always
higher than c1 and the rows contain ranges eg

c1        c2
1          5
6         20
24        28
36       134
145      148

etc
and you want to add a constraint such that no ranges are contained within others
or overlap in anyway, how would you recommend doing it? Given the above data the
following inserts would fail:

c1        c2
1          2
18        23
28        30
39        58

but these would be allowed:
c1        c2
21        22
29        34
149      999

etc

          Followup:

its really hard and relies on triggers that basically force serialization on
modifications to the table and query the results in AFTER triggers to see if
everything is OK.

      Rating: 1
Searching a
      range of dates on a range of dates  October 16, 2003

Reviewer:  Ray DeBruyn Enterprise Information Systems
       from Canada
This is type of query I use to search a range of dates:

select * from t
where c_beginning < p_ending
and c_ending > p_beginning;

It will return any records where the span of c_beginning and c_ending overlaps
the sapn of p_beginning and p_ending. If you want it to return records where the
spans of time touch (ie p_ending = c_beginning) change the condition to <= and
>=.

          Followup:

it is still "really hard" as it mandates

a) serialization of modifications to the table and
b) an AFTER trigger that has access to the modified data

      Rating: 3
I don't
      understand  October 17, 2003
Reviewer:  John
      Gilmore  from United Kingdom
You are telling Ray that his query "won't catch all overlaps -- only some
kinds".

But you then use his same query "select * from t where cb < pe and ce > pb" to
retieve the row representing the range 5..10 and 2..11 which you say his query
doesn't get.

Am I missing something here?

          Followup:

you're right -- i was just reading over it too fast last night.  sorry about
that.  it does look right -- specially with the proof below.

and an example of all 4 overlaps:

ops$tkyte@ORA920> create table t ( cb int, ce int, pb int, pe int, msg
varchar2(20) );

Table created.

ops$tkyte@ORA920> insert into t values ( 5, 10, 1, 6,  'low end overlap' );

1 row created.

ops$tkyte@ORA920> insert into t values ( 5, 10, 6, 11, 'hi end overlap' );

1 row created.

ops$tkyte@ORA920> insert into t values ( 5, 10, 6, 7,  'interior overlap' );

1 row created.

ops$tkyte@ORA920> insert into t values ( 5, 10, 1, 11, 'exterior overlap' );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from t where cb < pe and ce > pb;

        CB         CE         PB         PE MSG
---------- ---------- ---------- ---------- --------------------
         5         10          1          6 low end overlap
         5         10          6         11 hi end overlap
         5         10          6          7 interior overlap
         5         10          1         11 exterior overlap

shows that -- sorry about that.

      Rating: 3
A Different
      Explanation  October 17, 2003
Reviewer:  John
      Gilmore  from United Kingdom
Perhaps the easiest way to think of this problem is to start with the opposite
question: "when do two ranges NOT overlap?"

Well, they don't overlap in two, and only two, cases:

  o range_1.end < range_2.begin
  or
  o range_1.begin > range_2.end

Phrased in SQL this becomes

(R1_end < R2_begin
 or
 R1_begin > R2_end)

We're interested in the inverse of this; that is

  not (
    R1_end < R2_begin
    or
    R1_begin > R2_end)

Using Boolean algebra we can transform this to an equivalent expression:

 R1_end >= R2_begin
 and
 R1_begin <= R2_end

which is pretty much what Ray suggested.

      Rating: 3
Difference
      between tow fields and use it in the sql statement condition
        December 15, 2003
Reviewer:  Jamil
       from Saudi
Dear Sir Tom
    Can I make calculation in the SQL statements and make the condition on the
result of calculation
For example  I have this table contains

J_D             J_B
--------------------
 10               5
 20               15
 15               10
 10               5
 20               15
15                15
10                5
20                15
15                15

My SQL statements as follow :

select J_D,J_B,R
FROM T,(SELECT NVL(J_D,0) - NVL(J_B,0) FROM T) R
WHERE R > 0

So I want the result as follow  :

J_D              J_B        R
------------------------------
 10               5        5
 20               15       5
 15               10       5
 10               5        5
 20               15       5
 11               5        6
 20               15       5

How can I do that in select statement? Please give an example details  how to
solve it .

Best regards

Jamil

          Followup:

well, (SELECT NVL(J_D,0) - NVL(J_B,0) FROM T) returns a row for every row in T,
doesn't make sense....

not sure what you are trying to do here but

select j_d, j_b, nvl(j_d,0)-nvl(j_b,0)
  from t

gets you what you want.

      Rating: 1
Using the
      result of the calculation in the where clause  December 16,
      2003
Reviewer:  Jamil  from Saudi
Dear Sir

I want to use the result of the  calculation
of  this nvl(j_d,0)-nvl(j_b,0) in the where clause
so if I have for example table t contains data as follow :
J_D        J_B
-------------------
 10          5
 20          15
 15          10
 10          10
 15          15
 20          20

so I want my query to return different between J_D and  J_B , and the result of
the subtraction should be used in the where clause like this

select J_D,J_B,R
FROM T,(SELECT NVL(J_D,0) - NVL(J_B,0) FROM T) R
WHERE R > 0

So the result of the above query should be as follow :

J_D        J_B     R
---------------------
 10         5      5
 20         15     5
 15         10     5

How can I use the result of the calculation in the where clause , Or how can I
make the result of my query
control by the result of the calculation
Please help with example details, with any other idea
if it is not possible with the above idea.

Best regards

Jamil

          Followup:

that is just

select *
  from t
 where (j_d <> j_b or
        ((j_d is null and j_b is not null) or (j_d is not null and j_b is null))

you can use any computation in the where clause -- but in your case, using that
nvl() function would NOT answer the query "show me the rows where they are
different".  My predicate will.

December 16, 2003 Anthony -- Thanks for the question regarding
      "logical OR optimization", version 9.2.0.4

      You Asked
Tom,
 2 questions:

1. In a language like C, the || operator functions so that if the first
condition is true then the second doesn't have to be evaluated. Is there a way
to achieve this in sql? Obviously, I would not want this to happen for every
scenario, but I have a query that looks like this:

select ...
  from ..
 where id = some_value_passed_in
    or ( some expensive subquery ).

Basically, if the id is found, then i wouldn't want to evaluate the subquery. If
this is not possible, what's a good work around? We're currently doing a
count(*) ( with rownum=1, so it's not too bad ), but i thought there may be a
better solution. ( this is in a for loop, so catching it with no_data_found
hasn't been an option ).

2. We make heavy use of pipelined functions. Seeing how stragg outperforms a
similar function ( that doesn't use ODCI ), i was wondering if ODCITABLEFETCH,
would outperform pipelined functions.
Would this be an appropriate use of the ODCI? Would you mind providing a small
example that doesn't use an oci or java callout?

thanks,
 ant

      and we said...
1) decode and CASE permit this easily.
ops$tkyte@ORA920> create or replace function f1(x in number) return number
  2  as
  3  begin
  4      dbms_application_info.set_client_info( userenv('client_info')+1 );
  5      return x;
  6  end;
  7  /

Function created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> variable input varchar2(5)
ops$tkyte@ORA920> exec :input := 'X'

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec dbms_application_info.set_client_info( 0 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select *
  2    from dual
  3   where case when :input = dummy
  4              then to_number(null)
  5              else (select f1(55) from dual)
  6         end = 55
  7  /

no rows selected

ops$tkyte@ORA920>
ops$tkyte@ORA920> select *
  2    from dual
  3   where decode( :input, dummy, to_number(null), (select f1(55) from dual) )
= 55;

no rows selected

ops$tkyte@ORA920>
ops$tkyte@ORA920> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
0

never called F1, subquery not executed...

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec :input := 'Y'

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec dbms_application_info.set_client_info( 0 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select *
  2    from dual
  3   where case when :input = dummy
  4              then to_number(null)
  5              else (select f1(55) from dual)
  6         end = 55
  7  /

D
-
X

ops$tkyte@ORA920>
ops$tkyte@ORA920> select *
  2    from dual
  3   where decode( :input, dummy, to_number(null), (select f1(55) from dual) )
= 55;

D
-
X

ops$tkyte@ORA920> select userenv('client_info') from dual;

USERENV('CLIENT_INFO')
----------------------------------------------------------------
2

ops$tkyte@ORA920>

see how f1 is called only when dummy is not :input...

2) well, I don't see the other function ?  I'll presume the other function is an
"old fashioned" plsql function that does recursive sql to do stragg.  That is we
are comparing:
ops$tkyte@ORA920> create or replace function pivot( p_deptno in number ) return
varchar2
  2  as
  3          l_data long;
  4  begin
  5          for x in ( select ename from emp where deptno = p_deptno )
  6          loop
  7                  l_data := x.ename || ',' || l_data;
  8          end loop;
  9          return rtrim(l_data,',');
 10  end;
 11  /

Function created.

ops$tkyte@ORA920> column x format a40
ops$tkyte@ORA920>
ops$tkyte@ORA920> select deptno, stragg(ename) x from emp group by deptno;

    DEPTNO X
---------- ----------------------------------------
        10 CLARK,KING,MILLER
        20 SMITH,FORD,ADAMS,SCOTT,JONES
        30 ALLEN,BLAKE,MARTIN,TURNER,JAMES,WARD

ops$tkyte@ORA920> select deptno, pivot(deptno) x from ( select distinct deptno
from emp);

    DEPTNO X
---------- ----------------------------------------
        10 MILLER,KING,CLARK
        20 FORD,ADAMS,SCOTT,JONES,SMITH
        30 JAMES,TURNER,BLAKE,MARTIN,WARD,ALLEN

Here, the differences are obvious -- the distinct, the recursive SQL... That is
why stragg outperforms it (guessing as to your implementation)

You can use the examples from the data cartridge developers guide on
otn.oracle.com in appendix A.  yes, they are in C and java, but the concept is
100% the same there.  Just reimplement the type body in PLSQL instead of having
the type spec be a wrapper to extprocs and/or java.

When using PLSQL, you have the option of doing it really simple with pipe row(),
with C and java you do not (thats why there is no plsql example for the odci
interface, its just way harder then it needs to be in plsql)

Review &
      Followup

      Rating: 5
Great answers,
      I should've been a bit more clear though...  December 15,
      2003
Reviewer:  ant
Tom,
 Thank you for your quick responses.

1. Your answer is similar to what I've been trying:

create or replace procedure p ( p_empno number )
is
    function f ( x number )
    return number
    is
        l_empno emp.empno%type;
    begin
        select e.empno into l_empno
          from dept d, emp e
         where d.deptno  = e.deptno
           and e.empno   = x
           and d.loc     = 'NEW YORK';
        return l_empno;
        exception
          when no_data_found then
            return to_number(null);
    end f;
begin
    for i in (
        select e.ename, e.deptno
          from emp e
         where case when p_empno = e.empno
                    then to_number(null)
                    else ( select f( p_empno ) from dual )
               end = e.empno
    )
    loop
        dbms_output.put_line( i.ename ||' '|| i.deptno );
    end loop;
end p;
/

ofcourse, the above proc doesn't compile, but it's along the lines of what i'm
looking for.
If the empno is found, great, return that row, if not, check if that person
works in NY
( which is a VERY expensive subquery on my system,
  it's the reason i do not want to just join and loop though in the first place
).

any ideas?

2. Correct, I meant stragg compared to an "old-fashioned" pl/sql function.
Should I be using ODCI to replace our pipelined functions?
   Is this an appropriate use of the ODCI? I ask because, pipelined functions
are becoming very widespread on our system.
   I will DEFINITELY ( absolutely ) benchmark on my systems and see for myself
what's best, but I'm curious if you ever noticed some
   performance improvements using ODCI rather than pipelined functions.

 thanks again,
    - ant

          Followup:

1) it is not any different then my example????

I showed you how to selectivly call a subquery -- matters not what the subquery
does -- just that it is called selectively.

don't know what else you need?  everything you need is stated above.

2) I've never used the odci interface for pipelined functions -- that interface
is for 3gls like C and Java since they cannot be written as EASILY as plsql can
be in this case.  a pipelined function is the easy way to do it.

If you find some measurable difference -- feel free to followup -- that'd be
great. (but I believe the pipelined interface is just a simple layer on the odci
more complex interface)

      Rating: 5
all
      good...  December 15, 2003
Reviewer:  ant

Tom,
 1. out of laziness, i was testing with a standalone proc and was getting
PLS-00231 errors. That's why i followed up with the second question. Once i
moved the test into a package ( like where the actual code i will write will be
) it was fine.
So, thank you, Your example was perfect.

 2. Yeah, pipelines are quick to whip together and use, but I'm curious to see
if i can get better performance with ODCI since we have these functions all over
the place.
Will benchmark with sql_trace and runstats and then post my results.

thanks again,
  ant

      Rating: 5
Is DECODE or
      similar really necessary  December 16, 2003

Reviewer:  Xavier Irias  from Oakland CA
It seems to me that Oracle at least sometimes is smart enough to execute only
the left half of an OR, just like C would, without use of a construct like
DECODE.  I have large tables that are queried with SQL that includes a WHERE
clause like this:

WHERE ((somefield IS NULL) OR (someExpensiveFunc(somefield)=somevalue)) AND ....

The expensive function is called only when needed.

          Followup:

It always is HOWEVER, the order of operation is NOT defined, and can EASILY
switch.  do NOT rely on the order of operations!

December 16, 2003 -- Thanks for the question regarding "Select in
      Select", version 8.1.7

      You Asked
Hi Tom,

Happy new year! I have a quick question for you.

For the following sql,

select b.c1, b.c2,
   (select a.c1 from a where a.c1  = some_exp(b.c1) ) as C3
from b
where ...

it works fine. and some C3 are NULL. I'd like to replace NULL with some
constant. so I added NVL or decode function in following sql

select b.c1, b.c2,
   (select Nvl(a.c1, XX) from a where a.c1  = some_exp(b.c1) )
from b
where ...

It still returned NULL.
Can I use a function in a nested SELECT in SELECT? If it can't
Can I have some ways to work around?

Thanks!

Steve

      and we said...
nvl( (select.....), 'XX' ) as C3

nvl the select, don't select an NVL -- the select doesn't return any data --
hence there is no NVL selected.  Instead, select nvl( (select), 'xx' )

Review &
      Followup

      Rating: 5
select in
      select  January 02, 2003
Reviewer:
      A reader
Thank you! TOM!

      Rating: 5
How long has
      this syntax been legal?  January 03, 2003

Reviewer:  John Gilmore  from United Kingdom

I knew that you could have Query Table expressions in the "from" clause but I
was not aware that Oracle allowed you to have these in the "select" clause also.

Indeed, I checked the Oracle 8.1.7 SQL Reference manual (A85397-01) and from
what I can see from the examples and the syntax diagrams of the "select"
statement it does not seem to allow this syntax.  However, a quick test in
SQL*Plus proved to me that this works.

How long has this been available?

          Followup:

815..

they are called "scalar subqueries" and yeah, I agree, it isn't obvious that
they can be used there.  But most anywhere you could use a CONSTANT in sql
before, you can use a scalar subquery.

      Rating:
      4
Select-a-select  February 21, 2003

Reviewer:  Mike  from St. Louis, MO
Tom:

This "select a select"/"select in select" example looks very much like a
correlated sub query which I tend to avoid by replacing with in-line views in
the FROM clause.  Are there similar correlated sub-query performance penalties
of using "select a select"?

Interestingly, I just learned of in-house use of "select a select" where approx.
16 of 20 columns selected in a query are sub-selects similar to the first
example in this thread.  In our case, "select a select" is used to avoid outer
joins between tables with loose RI enforcement/null foreign key values.   We
found  that "select a select" performs quite well (1 sec query time) whereas the
outer join query with all "select a selects" removed was much slower (minutes).

Thanks for any general opinions you may have on performance penalties/benefits
of "select a select".

-Mike

          Followup:

why are you afraid of correlated subqueries in the first place?

consider this, which query would YOU rather run:

ops$tkyte@ORA817DEV> set autotrace traceonly statistics;
ops$tkyte@ORA817DEV> set echo on
ops$tkyte@ORA817DEV> @test
ops$tkyte@ORA817DEV> select username from all_users
  2  where exists (select null from all_objects where owner = username);

40 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
       1070  consistent gets
          4  physical reads
          0  redo size
       1602  bytes sent via SQL*Net to client
        647  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         40  rows processed

ops$tkyte@ORA817DEV> select username
  2    from all_users, (select distinct owner from all_objects)
  3   where username = owner;

40 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
     105444  consistent gets
        300  physical reads
          0  redo size
       1602  bytes sent via SQL*Net to client
        647  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         40  rows processed

ops$tkyte@ORA817DEV>

Me, I would prefer the correlated subquery.  If they were always evil, always
bad, they would not exist...

There are NO performance penalties with corrlated subqueries.  Just as there no
none with inline views -- they just do things differently and under different
circumstances are better then the other.

All of these are techniques, tools, things to be used.

One of my favorite tuning tools is -- well -- rownum.  Go figure eh..  Try this
little test case someday:

drop TABLE T1;

CREATE TABLE t1
( prc_chk_key                    NUMBER(9) NOT NULL,
  prod_key                       NUMBER(12) NOT NULL,
  cmpt_loc_key                   NUMBER(5) NOT NULL,
  loc_key                        NUMBER(5) NOT NULL,
  prc_chk_dt                     DATE
)
/
insert into t1 select 2, 3, 4, 5, sysdate from all_objects where rownum <= 50;
pause

drop TABLE T2;
CREATE TABLE T2
( prc_chk_key                    NUMBER(9) NOT NULL,
  prc_chk_typ_desc               VARCHAR2(35) NOT NULL,
  cmpt_loc_key                   NUMBER(5),
  loc_key                        NUMBER(5) NOT NULL
)
/
insert into t2 select 2, 'x', 4, 5 from all_objects where rownum <= 50;
pause

create or replace context my_ctx using F
/
pause

CREATE OR REPLACE function F
 (v_prod_key     IN number default NULL,
  v_prc_chk_key  IN number default NULL,
  v_return       IN varchar2 default NULL,
  v_want_sr      IN varchar2 default NULL,
  v_offset       in number default NULL) RETURN  varchar2
as
begin
    if ( v_offset = -1 )
    then
        dbms_session.set_context('my_ctx', 'cnt', -1 );
    end if;
    dbms_session.set_context
    ( 'my_ctx', 'cnt', nvl(sys_context('my_ctx','cnt'),0)+1 );

    return 1;
end;
/

exec dbms_output.put_line( F( v_offset => -1 ) );
pause

set autotrace traceonly
select   /*+ USE_HASH( a11, a12 ) */ a12.PRC_CHK_TYP_DESC  PRC_CHK_TYP_DESC,
         a11.PRC_CHK_DT  PRC_CHK_DT,
         a11.CMPT_LOC_KEY  CMPT_LOC_KEY,
         a11.PROD_KEY  UPC_PROD_KEY,
         a11.LOC_KEY  LOC_KEY,
         max(F(a11.PROD_KEY, a11.PRC_CHK_KEY, 'QTY', 'D', 1) ) COMPPREVIOUS,
         max(F(a11.PROD_KEY, a11.PRC_CHK_KEY, 'AMT', 'D',1) ) COMPPREVIOUS1,
         max(F(a11.PROD_KEY, a11.PRC_CHK_KEY, 'CODE', 'D', 1) ) COMPPREVIOUS2,
         max(F(a11.PROD_KEY, a11.PRC_CHK_KEY, 'PRC', 'D',1) ) COMPPREVIOUS3
 from    T1 a11,
         T2 a12
 where   a11.CMPT_LOC_KEY = a12.CMPT_LOC_KEY
   and   a11.LOC_KEY = a12.LOC_KEY
   and   a11.PRC_CHK_KEY = a12.PRC_CHK_KEY
 group by a12.PRC_CHK_TYP_DESC, a11.PRC_CHK_DT,
          a11.CMPT_LOC_KEY, a11.PROD_KEY, a11.LOC_KEY;
set autotrace off
pause

column data format a20
select sys_context( 'my_ctx', 'cnt' ) data from dual;
exec dbms_output.put_line( F( v_offset => -1 ) );
pause

set autotrace traceonly
select   /*+ USE_HASH( a11, a12 ) */ a12.PRC_CHK_TYP_DESC  PRC_CHK_TYP_DESC,
         a11.PRC_CHK_DT  PRC_CHK_DT,
         a11.CMPT_LOC_KEY  CMPT_LOC_KEY,
         a11.PROD_KEY  UPC_PROD_KEY,
         a11.LOC_KEY  LOC_KEY,
         max(COMPPREVIOUS),
         max(COMPPREVIOUS1),
         max(COMPPREVIOUS2),
         max(COMPPREVIOUS3)
 from    (select a11.*,
              F(a11.PROD_KEY, a11.PRC_CHK_KEY, 'QTY', 'D', 1 ) COMPPREVIOUS,
              F(a11.PROD_KEY, a11.PRC_CHK_KEY, 'AMT', 'D',1 ) COMPPREVIOUS1,
              F(a11.PROD_KEY, a11.PRC_CHK_KEY, 'CODE', 'D', 1 ) COMPPREVIOUS2,
              F(a11.PROD_KEY, a11.PRC_CHK_KEY, 'PRC', 'D',1 ) COMPPREVIOUS3,
              ROWNUM r
         from t1 a11 ) a11,
         T2 a12
 where   a11.CMPT_LOC_KEY = a12.CMPT_LOC_KEY
   and   a11.LOC_KEY = a12.LOC_KEY
   and   a11.PRC_CHK_KEY = a12.PRC_CHK_KEY
 group by a12.PRC_CHK_TYP_DESC, a11.PRC_CHK_DT,
          a11.CMPT_LOC_KEY, a11.PROD_KEY, a11.LOC_KEY;
set autotrace off
pause

select sys_context( 'my_ctx', 'cnt' ) data from dual;
spool off

the hints are there to emulate a customer issue they were having -- try the
second query with and without rownum and see what happens

      Rating:
      5
  February 21, 2003
Reviewer:
      A reader
Tom,

Please explain to me in detail what each step is supposed to do...what will
rownum do? it suddently brought down the cnt from 10,000 to 200? what exactly is
happening? what is the purpose of rownum?

          Followup:

it is a side effect of view merging:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96533/opt_ops.htm#1009807
this "view"

(select a11.*,
              F(a11.PROD_KEY, a11.PRC_CHK_KEY, 'QTY', 'D', 1 ) COMPPREVIOUS,
              F(a11.PROD_KEY, a11.PRC_CHK_KEY, 'AMT', 'D',1 ) COMPPREVIOUS1,
              F(a11.PROD_KEY, a11.PRC_CHK_KEY, 'CODE', 'D', 1 ) COMPPREVIOUS2,
              F(a11.PROD_KEY, a11.PRC_CHK_KEY, 'PRC', 'D',1 ) COMPPREVIOUS3,
              ROWNUM r
         from t1 a11 ) a11

cannot be merged into since it includes rownum -- so the optimizer
"materialized" it off to the side into temp and then joined to it.

So the function F was called 4*50 times = 200 in order to create this temp
object.

In the other case, where the rownum was not used -- the function f() was called
over and over and over in order to do the MAX processing (sort of like sorting).
 The function was evaluated and re-evaluated many times.

Rownum let us call the function 4 times per row (50 rows) and be done with it.

      Rating:
      5
Thanks!  February 22, 2003
Reviewer:
      Mike  from St. Louis, MO
Tom:

Thanks for your detailed response and examples.  You have dispelled yet another
myth or bad habit I've learned in the past (avoid corellated subqueries).

Thank you,

-Mike

          Followup:

if you do this:

o ignore all advice that sounds like a rule of thumb, pretend it doesn't exist,
was never said. rule of thumb = ROT and ROT is as bad as it sounds

o ignore all advice that says "feature X" or "approach Y" is bad

o ask for the science behind the claim, facts to support the advice and if you
get none, ignore it

I know that means that many performance books on your shelves are now obsolete,
to be put in the circular filing cabinet -- but that is the way it is.  If they
just say "don't use not in", "don't do this" "don't do that" and do not have a
scientific proof as to why -- they are wrong.

That and every feature/function has a time and place so anyone that says "never
use X" is wrong -- period -- no if and or buts about it -- they are just wrong.

      Rating:
      5
  February 22, 2003
Reviewer:
      A reader
Thanks for explaining in detail Tom...

      Rating: 5
Thanks
      again  February 22, 2003
Reviewer:  Mike
       from St. Louis, MO
Tom:

It's challenging when respected expert opinions differ.  My older Guy H. and
Gaja V. tuning books and IOUG-Live hand-outs warn of performance issues with
correlated sub-queries so I've avoided them as a general rule.  That's probably
my fault for taking something in their work out of context or generalizing it.

However, you always stress that the proof is in the numbers.  Tracing and tkprof
seem like the only way to know for sure which query is the best choice for a
situation.

Thanks again,

-Mike

          Followup:

Things change over time -- I know Gaja (he is at Oracle now, a very good thing
for us!)

With the RBO, it could have been true -- lots less optimization paths, less
intelligent optimizer.

It is a generalization however, correlated subqueries can be great.  They can be
horrible.  But -- so can anything and everything else!

There are people who avoid "not in" like the plague.  With the CBO and
anti-joins, they can blow away the other options -- totally.

      Rating: 4
Great
      guy  February 22, 2003
Reviewer:  Mike
       from St. Louis, MO
Tom:

> Things change over time -- I know Gaja (he is at
> Oracle now, a very good thing for us!)

He's a great guy.  Please say hi to him from the St. Louis Oracle User Group.
He spoke twice to our group and took time out for a book signings and off-line
conversations.  Hmmmm, now if we could only lure you to St. Louis for a day ;-))

-Mike

      Rating:
      3
  March 07, 2003
Reviewer:
      A reader
M.b. I'm wrong putting this question here. But this one about tuning.
I have written this sql, it takes pretty much time.
Is there other sql that can change this one:
select t1.name name1, t2.name name2
from t t1, t t2, r
where r.id=t1.type1_id
and r.id=t2.type2_id
and t1.type='type1'
and t2.type='type2'
order by 1;

thank you.

          Followup:

if you add "and rownum = 1" it'll probably go lots faster.

is either of t1.type or t2.type "selective".

if so, is either of t1.type or t2.type "indexed".

An index on t1(type,name) could be useful if the goal is to get the first row
back ASAP as the "t1.type='type1' " and "order by 1" would be done  quickly.

      Rating: 5
Great
      information!  March 07, 2003
Reviewer:
      Robert  from Memphis, USA
Tom,

Thanks for the great information...

o The ROWNUM trick
o The reminder (again) on ROT!

Thanks,

Robert.

          Followup:

I hope everyone knows the "and rownum = 1" was a joke, sort of tongue in cheek.

Here is another:

question: how can I make this query go faster
answer: change that tablename to DUAL, queries against dual go really fast.

      Rating:
      5
Dual...  March 07, 2003
Reviewer:  Mark
      A. Williams  from Indianapolis, IN USA
Yeah, but what about x$dual???  You know that one has super secret magical
powers that make your database run with really_fast=true in the spfile, right?

- Mark

      Rating: 5
What is
      @test?  March 07, 2003
Reviewer:  Reader
       from Mayberry
In the above:
ops$tkyte@ORA817DEV> set autotrace traceonly statistics;
ops$tkyte@ORA817DEV> set echo on
ops$tkyte@ORA817DEV> @test

What is @test?

Thanks!

          Followup:

it was a script that had the subsequent sql queries in it.

      Rating: 3
Here we
      go  March 07, 2003
Reviewer:  Mike
       from St. Louis, MO
Ahhh Tom, your sarcasm always makes my day.  Keep your _tk_smart_ass parameter
set to 100 or higher...  Anticipating your response to "What is @test" ;-)

      Rating:
      5
Doh!  March 07, 2003
Reviewer:
      Reader  from Mayberry

      Rating: 5
Do Scalar
      Subqueries Work in PL/SQL?  March 19, 2003

Reviewer:  John Gilmore  from United Kingdom

In SQL*Plus

   select (select dummy from dual) from dual;

works fine.  But in PL/SQL I get the error message

  PLS-00103: Encountered the symbol "SELECT" when expecting one of the following

Is this feature not yet supported by PL/SQL?

          Followup:

in 8i, you need to "hide" that construct from plsql -- in 9i, it is ok

either:

o use a view

o use dynamic sql

 open ref_cursor for 'select (select dummy from dual) from dual';

      Rating:
      5
  March 19, 2003
Reviewer:  Ik
       from BG
Tom,

Would these scalar subqueries be merged with the rest of the query the same way
as complex view merging.

When we do an explain plan we do not see these scalar sub queries. So, iam
wondering whether the plan iam seeing would differ from what would actually get
executed.

Thanks in advance.

          Followup:

in the same way?  probably not, in some way?  yes.

      Rating: 4
why dont this
      work  May 22, 2003
Reviewer:  Asim Naveed
       from Pakistan
Hi,
This problem is related to
scalar subqueries and WITH clause

Please Consider following queries.
SQL> L
  1  with aquery as (select 1 from dual)
  2* select 1,2 from aquery
SQL> /

         1          2
---------- ----------
         1          2

SQL> ED
Wrote file afiedt.buf

  1  with aquery as (select 1 from dual)
  2* select 1,2, (select 1 from dual) from aquery
SQL> /

         1          2 (SELECT1FROMDUAL)
---------- ---------- -----------------
         1          2                 1

SQL> ed
Wrote file afiedt.buf

  1  with aquery as (select 1 from dual)
  2* select 1,2, aquery from aquery
SQL> /
select 1,2, aquery from aquery
            *
ERROR at line 2:
ORA-00904: "AQUERY": invalid identifier

SQL> ed
Wrote file afiedt.buf

  1  with aquery as (select 1 from dual)
  2* select 1,2, (aquery) from aquery
SQL> /
select 1,2, (aquery) from aquery
             *
ERROR at line 2:
ORA-00904: "AQUERY": invalid identifier

why did the third and fourth query faild. May be third
query failed due to absence of brackets but why fourth
failed.

          Followup:

aquery is NOT a scalar subquery -- it is a "result set" if you will.  it cannot
be used there anymore then:

select 1, all_objects from dual

could be expected to work -- you can use aquery where you can use a view,
period.

      Rating: 4
Selects within
      selects  June 02, 2003
Reviewer:  Richard
       from Oxfordshire, UK
Hi,

Does the following make sense, with respect to Oracle?:

==============================================================================
The queries:

SELECT * FROM table1 WHERE id NOT IN (SELECT id FROM table2);
SELECT * FROM table1 WHERE NOT EXISTS (SELECT id FROM table2
                                       WHERE table1.id=table2.id);

Can be rewritten as:

SELECT table1.* FROM table1 LEFT JOIN table2 ON table1.id=table2.id
                                       WHERE table2.id IS NULL;

Using a LEFT [OUTER] JOIN is generally much faster than an equivalent subquery.
===============================================================================

Thanks

          Followup:

no, because NOT IN and NOT EXISTS are NOT EQUIVALENT.   They give different
answsers depending on the NULLALITY of ID in T2.

That, and when left to its job, the CBO will do an infinitely better job all by
itself.

for example:

ops$tkyte@ORA920> create table t1 as select * from all_objects where rownum <=
10000;
Table created.

ops$tkyte@ORA920> create table t2 as select * from all_objects where rownum <=
9950;
Table created.

ops$tkyte@ORA920> insert /*+ APPEND */ into t2 select * from t2;
9950 rows created.

ops$tkyte@ORA920> commit;
Commit complete.

ops$tkyte@ORA920> insert /*+ APPEND */ into t2 select * from t2;
19900 rows created.

ops$tkyte@ORA920> commit;
Commit complete.

ops$tkyte@ORA920> insert /*+ APPEND */ into t2 select * from t2;
39800 rows created.

ops$tkyte@ORA920> commit;
Commit complete.

ops$tkyte@ORA920> insert /*+ APPEND */ into t2 select * from t2;
79600 rows created.

ops$tkyte@ORA920> commit;
Commit complete.

ops$tkyte@ORA920> create index t2_idx on t2(object_id);
Index created.

ops$tkyte@ORA920> analyze table t1 compute statistics for table;
Table analyzed.

ops$tkyte@ORA920> analyze table t2 compute statistics for table for all indexes
for all indexed columns;
Table analyzed.

Now, TKPROF shows us:

select * from t1 where object_id not in ( select object_id from t2 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        5      1.05       1.06        126        490          0          50
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        7      1.05       1.06        126        490          0          50

Rows     Row Source Operation
-------  ---------------------------------------------------
     50  HASH JOIN ANTI (cr=490 r=126 w=0 time=1060428 us)
  10000   TABLE ACCESS FULL T1 (cr=138 r=126 w=0 time=23315 us)
 159200   INDEX FAST FULL SCAN T2_IDX (cr=352 r=0 w=0 time=230111 us)(object id
37263)
********************************************************************************
select * from
 t1 where not exists ( select null from t2 where t2.object_id = t1.object_id )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        5      0.20       0.20        126      20142          0          50
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        7      0.20       0.20        126      20142          0          50

Rows     Row Source Operation
-------  ---------------------------------------------------
     50  FILTER  (cr=20142 r=126 w=0 time=204251 us)
  10000   TABLE ACCESS FULL T1 (cr=142 r=126 w=0 time=29849 us)
   9950   INDEX RANGE SCAN T2_IDX (cr=20000 r=0 w=0 time=103559 us)(object id
37263)
********************************************************************************
select t1.*
from
 t1 left join t2 on (t1.object_id=t2.object_id) where t2.object_id is null

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        5      1.48       1.72        126        490          0          50
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        7      1.48       1.72        126        490          0          50

Rows     Row Source Operation
-------  ---------------------------------------------------
     50  FILTER  (cr=490 r=126 w=0 time=1725038 us)
 159250   HASH JOIN OUTER (cr=490 r=126 w=0 time=1472166 us)
  10000    TABLE ACCESS FULL OBJ#(37261) (cr=138 r=126 w=0 time=25879 us)
 159200    INDEX FAST FULL SCAN OBJ#(37263) (cr=352 r=0 w=0 time=260624
us)(object id 37263)

The superior one is the one that says what you "mean".  The outer join was
inferior in all cases here.

      Rating:
      5
Cheers!  June 02, 2003
Reviewer:
      Richard  from Oxfordshire, UK
Thank you, that's great!

      Rating: 4
About the
      ROWNUM trick  December 15, 2003
Reviewer:
      A reader
Hi Tom,

Is there other way that I can achieve the same result as the ROWNUM trick, such
as HINT or (hidden) parameters?

          Followup:

same result as "what" exactly?

      Rating:
      4
ROWNUM  December 16, 2003
Reviewer:
      A reader
In this query, you used ROWNUM to change the behavior of CBO. I am wondering if
I can do the same by other (more obvious) ways.

set autotrace traceonly
select   /*+ USE_HASH( a11, a12 ) */ a12.PRC_CHK_TYP_DESC  PRC_CHK_TYP_DESC,
         a11.PRC_CHK_DT  PRC_CHK_DT,
         a11.CMPT_LOC_KEY  CMPT_LOC_KEY,
         a11.PROD_KEY  UPC_PROD_KEY,
         a11.LOC_KEY  LOC_KEY,
         max(COMPPREVIOUS),
         max(COMPPREVIOUS1),
         max(COMPPREVIOUS2),
         max(COMPPREVIOUS3)
 from    (select a11.*,
              F(a11.PROD_KEY, a11.PRC_CHK_KEY, 'QTY', 'D', 1 ) COMPPREVIOUS,
              F(a11.PROD_KEY, a11.PRC_CHK_KEY, 'AMT', 'D',1 ) COMPPREVIOUS1,
              F(a11.PROD_KEY, a11.PRC_CHK_KEY, 'CODE', 'D', 1 ) COMPPREVIOUS2,
              F(a11.PROD_KEY, a11.PRC_CHK_KEY, 'PRC', 'D',1 ) COMPPREVIOUS3,
              ROWNUM r
         from t1 a11 ) a11,
         T2 a12
 where   a11.CMPT_LOC_KEY = a12.CMPT_LOC_KEY
   and   a11.LOC_KEY = a12.LOC_KEY
   and   a11.PRC_CHK_KEY = a12.PRC_CHK_KEY
 group by a12.PRC_CHK_TYP_DESC, a11.PRC_CHK_DT,
          a11.CMPT_LOC_KEY, a11.PROD_KEY, a11.LOC_KEY;

Thanks

          Followup:

NO_MERGE might be useful -- works a little differently, but can work.

WITH subquery factoring can be used as well.

GROUP BY or ORDER BY could have done it.

December 16, 2003 Suresh -- Thanks for the question regarding "How
      does the profile parameter 'idle_time' work?", version 8.1.6

      You Asked
Hi,

We need to use the profile parameter idle_time in our database. Even if we
specify 10 minutes as the parameter value, the user session expires after 11
minutes or 12 minutes. It appears that some oracle process periodically checks
the user sessions for idle time out. But we are not able to find out any more
details on this parameter. Can you tell us which Oracle process takes care of
expiring the user session and in which V$ view the user session is marked as
expired?

Thanks for your help.

      and we said...
PMON is responsible for sniping the sessions.  It will do this periodically.
HOWEVER

A session will continue to show as idle even after the idle_time for that user,
as specified in that user's profile, has expired. When the user attempts to run
a transaction against the database after the idle_time has expired, the
database will disconnect the user by terminating the session. After this, the
session will no longer show in v$session.  So, even if the session appears to be
idle for a duration slightly more then your 10 minutes -- it is already "dead",
it just doesn't show as dead yet.  PMON will eventually snipe the session,
marking it dead in v$session.

Review &
      Followup

      Rating:
      5
  April 04, 2001
Reviewer:  Pauline
       from U.S.A

      Rating: 3
About Inactive
      Session  April 04, 2001
Reviewer:  Jerrywang
       from CHINA
I find there is some sessions that status is marked 'INACTIVED'  in my Oracle
Server,I do not know whether it is normal.Can I let it disapper automatic by
setting some paramters?

      Rating: 4
Connect time
      not working.  June 03, 2003
Reviewer:
      SUBBARAO
I need to enforce the resource limit of connect time for a session to 30 min. As
part of this exercise I am trying to simulate the case with 1 min. I am not
getting the result. Here are the steps I have followed:
Plese correct me.

I have set the connect_time = 1 for a profile, and assigned the profile to the
scott user.

select * from user_resource_limits a
where a.resource_name in ('IDLE_TIME','CONNECT_TIME');

RESOURCE_NAME    LIMIT
----------------------
IDLE_TIME    UNLIMITED
CONNECT_TIME    1

Connected to the database with scott user and execute the following query:

select to_char(sysdate,'dd-mm-yyyy hh24 mi') from dual;

TO_CHAR(SYSDATE,
----------------
03-06-2003 13 08

Now I have left the session with out executing any transactions.
After some time(2 min) I have again executed the same query. Here I expected a
message saying the session is killed. But I am getting the result of the query
again.

SQL> /

TO_CHAR(SYSDATE,
----------------
03-06-2003 13 10

          Followup:

so, did you set RESOURCE_LIMIT=true in your init.ora

and wait longer then what you did.

for all we know that could be

13:08:59
13:10:00

(eg: 1 minute)

      Rating:
      3
Thanks  June 04, 2003
Reviewer:
      A reader
Thanks, problem resolved after setting the resource_limit parameter in init
file.

      Rating: 4
Thanks - one
      clarification question  December 15, 2003

Reviewer:  Doug  from Salt Lake City, Utah

Tom,
You stated PMON will eventually snipe an expired session. If the SESSIONS parm
in the init.ora is set to 200, and say ther are 190 active sessions and 10 that
have 'expired' via the idle_time setting, can 10 new sessions be started or do
they wait?

          Followup:

they will be SNIPED -- still there -- until the client acknowledges that it was
"killed".

PMON won't "remove it" from v$session -- the session will be dead but still
there.

you'll have hit max sessions at that point.

      Rating: 5
Why sniped
      sessions are not removed  December 15, 2003

Reviewer:  Arun Gupta  from Harrisburg, PA USA

Tom,
Is there a reason you can think of why PMON doesn't clean sniped sessions? The
user whose session has been sniped can no longer do anything with that session,
except get an error message.
Thanks

          Followup:

exactly -- "except get an error message"

if we just killed everything, they would get:

ORA-3113 End of file on communication channel

which would significantly increase calls to your helpdesk/support as it surely
looks like a bug.

it is waiting to tell the client "sorry, you are dead, goodbye"

      Rating:
      5
  December 16, 2003
Reviewer:  Arun
      Gupta  from Harrisburg, PA USA
If a session is holding objects locks and is sniped before releasing the locks,
are the locks still held?
Thanks

          Followup:

the locks and other resources are released.

December 16, 2003 RAGUPATHI -- Thanks for the question regarding
      "How to uset get_file_name function in WEB.", version FORMS 6.0

      You Asked
Hi Tom,
i have a get_file_name function to open file, i tried to run it with clik the
run form client server on toolbar, it run well i could see the open file, but if
i clik the run form web compiler has done but the open file could not see. and
there didn't any error messages.

The command i gave.

:load_blk.nbc_file_name:= get_file_name('..',null,File_Filter=> 'XLS Files
(*.csv)|*.csv|');

      and we said...
Here is a support note on this topic, available via metalink as well.

Article-ID:         <Note:74140.1>
Circulation:        REVIEWED (EXTERNAL)
Folder:             client.Dev2000.Forms.V6
Topic:              6.0 - Functionality and the Web
Title:              Implementing get_file_name for Webforms
Open-Remarks:       See <RemarksOn:NOTE:74140.1>
Document-Type:      BULLETIN
Impact:             MEDIUM
Skill-Level:        NOVICE
Updated-Date:       27-FEB-2001 08:23:28
Attachments:        NONE
Content-Type:       TEXT/PLAIN
Keywords:           GET_FILE_NAME; JAVABEAN; WRAPPER;
Products:           45/WEBFORMS;
Platforms:          GENERIC;

PURPOSE
=======

The get_file_name built-in of forms  is currently not implemented for Web.
You could create a Javabean and use it to implement this functionality
for WebForms.

Attached below are the source code of JavaBean & the Wrapper Class for the Bean.

CREATING JAVABEAN
=================

The Bean uses the following methods and Classes
-----------------------------------------------

PropertyChangeSupport for the file_name property.

java.awt.FileDialog class is used to show the File Dialog.

firePropertyChange method is called to pass a PropertyChange event to all
classes which are registered as listeners to this class.

addPropertyChangeListener is used by other classes to register themself as
PropertyChangeListener for this class.

import java.awt.*;
import java.beans.*;

  public class FDialog extends Canvas  {
  private String file_name ;
  private PropertyChangeSupport pcs;

public FDialog() {
  file_name = "";
  pcs = new PropertyChangeSupport(this);
  }

  public void set_file_name()
  {
  Frame frame = new Frame("Sample");
  java.awt.FileDialog  OpenDlg = new FileDialog(frame, "Open ",
java.awt.FileDialog.LOAD);
  OpenDlg.setFile("*.*");
  OpenDlg.show();
  String old_file_name = file_name;
  file_name=OpenDlg.getDirectory() + OpenDlg.getFile();
  pcs.firePropertyChange("file_name", old_file_name, file_name);
  }

  public void addPropertyChangeListener(PropertyChangeListener pcl)
  {
  pcs.addPropertyChangeListener(pcl);
  }

  public void removePropertyChangeListener(PropertyChangeListener pcl)
  {
  pcs.removePropertyChangeListener(pcl);
  }
  }

CREATING WRAPPER FOR JAVABEAN
=============================

The Wrapper class implements the PropertyChangeListener interface.
It registers itself as a PropertyChangeListener for the Bean class.
The propertyChange method is executed in response to a PropertyChange event.
The propertyChange method dispatches a CustomEvent to the Handler.
This event is then trapped in forms using a when-custom-item-event trigger.

package oracle.forms.demos;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import oracle.forms.properties.ID;
import oracle.forms.handler.IHandler;
import oracle.forms.ui.CustomEvent;
import oracle.forms.ui.VBean;

public class FDialogPJC extends VBean implements PropertyChangeListener{
    private Component mComp;
    private IHandler mHandler;
    private static final ID SHOWFILEDIALOG   =
ID.registerProperty("showfiledialog");
    private static final ID FILENAME         = ID.registerProperty("filevalue");
    private static final ID FILECHANGEEVENT  =
ID.registerProperty("filechangeevent");

  public FDialogPJC() {
    super();
    try{

      ClassLoader cl = getClass().getClassLoader();
      Object obj = Beans.instantiate(cl,"oracle.forms.demos.FDialog");
      mComp = (Component)obj;
      mComp.setVisible(true);
      mComp.setEnabled(true);
      ((oracle.forms.demos.FDialog)mComp).addPropertyChangeListener(this);

      // add the component to this

      add("Center",mComp);
    }
    catch(Exception e){
      e.printStackTrace();
    }
  }

  public void init(IHandler handler)
  {
    super.init(handler);
    mHandler = handler;
  }

  public boolean setProperty(ID pid, Object value)
  {

    if(pid ==SHOWFILEDIALOG) {
      ((oracle.forms.demos.FDialog)mComp).set_file_name();
      return true;
                             }
    else
      return super.setProperty(pid,value);
  }

  public void propertyChange(PropertyChangeEvent pce) {
  String new_file_name=(String)pce.getNewValue();
  try {
        mHandler.setProperty(FILENAME, new_file_name);
      }
  catch(Exception e) {}
  CustomEvent ce = new CustomEvent(mHandler, FILECHANGEEVENT );
  dispatchCustomEvent(ce);
  }

}

Create a Form with a text_item and bean area. The text_item should be named
FILE_NAME and have a length of 255 chars. The Bean_Area should be named
FDIALOG_BEAN and have the implementation class set to
oracle.forms.demos.FDialogPJC

Code to bring up the FileDialog
===============================

declare
       FDialog_Bean        Item;
begin
       FDialog_Bean:=find_item('FDIALOG_BEAN');

         /*  FDIALOG_BEAN is the name of the Bean item */

      if NOT id_null(FDialog_Bean) then
           set_custom_item_property(FDialog_Bean,'showfiledialog', 1);

        /* This invokes the setProperty method of the Bean Wrapper using
           SHOWFILEDIALOG as ID. The wrapper in turn calls the set_file_name
           method of the Bean.
        */

      end if;
end;

Code to handle the PropertyChangeEvent  ( when-custom-item-event trigger
======================================              attached to the Bean )

declare

        FDialog_Bean        Item;
        BeanValListHdl ParamList;
    paramType      Number;
    EventName      VarChar2(20);
        file_name      varchar2(255);
Begin
    FDialog_Bean   := find_item('FDIALOG_BEAN');
    BeanValListHdl := get_parameter_list(:system.Custom_Item_Event_Parameters);
    EventName      := :SYSTEM.Custom_Item_Event;

    if (EventName = 'filechangeevent') then
       get_parameter_attr(BeanValListHdl,'filevalue',ParamType, file_name);

          /* file_name variable contains the file_name */
          :file_name := file_name;
        end if;
end;

-----------------------------------------------------------------------------
                                                   Oracle Support Services

Review &
      Followup

      Rating:
      5
  December 16, 2003
Reviewer:  huss
       from kuwait
where i can find the classes :
import oracle.forms.properties.ID;
import oracle.forms.handler.IHandler;
import oracle.forms.ui.CustomEvent;
import oracle.forms.ui.VBean;

it gives me that error  :
Compilation errors for JAVA SOURCE HRA_OWNER.FDIALOGPJC

Error: Class oracle.forms.properties.ID not found in import.
Line: 6
Text: import oracle.forms.properties.ID;

Error: Class oracle.forms.handler.IHandler not found in import.
Line: 7
Text: import oracle.forms.handler.IHandler;

Error: Class oracle.forms.ui.CustomEvent not found in import.
Line: 8
Text: import oracle.forms.ui.CustomEvent;

Error: Class oracle.forms.ui.VBean not found in import.
Line: 9
Text: import oracle.forms.ui.VBean;

Error: Superclass oracle.forms.ui.VBean of class oracle.forms.demos.FDialogPJC
not found.
Line: 11
Text: public class FDialogPJC extends VBean implements PropertyChangeListener{

          Followup:

sorry - i don't do forms myself.  try otn.oracle.com -> discussion forums OR
metalink.oracle.com

      Rating:
      4
  December 16, 2003
Reviewer:  huss
       from kuwait
Who says it related to forms ?!!
i miss the class files
it's a compile error when create the class you write above , casue not find that
java classes you use

          Followup:

see  -- now you are confusing me as the SUPPORT NOTE (not my note) starts with:

The get_file_name built-in of forms  is currently not implemented for Web.
You could create a Javabean and use it to implement this functionality
for WebForms.

it is FOR FORMS.  if you don't have forms, this note does not apply to you
whatsoever.

that is why i said it was related to forms.  no forms, no java classes but that
is OK since THIS ONLY applies to forms.

apparently you do not have forms, so ignore this entire web page!

      Rating:
      1
  December 17, 2003
Reviewer:  huss
       from kuwait
Thankx for ur advice !

December 16, 2003 sundar -- Thanks for the question regarding
      "Adding a column to all the tables in the Database", version 9.0.2

      You Asked
Hai Tom,
I had one question which is like this. We have more than 200 tables in our
database in one user and more than 300 tables in the other three users. Now I
have a situation I want to add a column in all the tables, the column is used to
identify the name of the node from where the dml operations are taking place,
connections to the database will take place (will like to add the IP address of
the nodes) and save them in the tables. Now this column has to be added as the
column which should be the fourth from the end column of the table. Suppose let
the last three columns of the table are user login code, dml operation date, dml
updatation date. Now i want to add the column called Node_ID before login code.
In all the tables the data is existing. With out disturbing the data I want to
add this into the table. If this can happen with any procedure or a package give
me solution. Once that is done can any trigger can be placed on the database to
record this IP address, if not we will make the changes to the forms in stages
based upon the importance of the tables in which i want to store this
information.
Thanks you very much for the DBMS_METADATA solution that you gave me previously
and the cons.sql script file. That is working very well and we are able to the
required output that we have expected.
Thank you once again for the DBMS_METADATA.
Sundar

      and we said...
4th from the end?  why -- what possible difference could it make?

there is no such concept as "4th from the end".  It is like "the order of rows
returned from a query".  Unless you ask for rows to be ordered by an order by --
they come out any old way.

While we've historically expanded a select * "in some semblance of order", it is
not codified anywhere that we always will.  If you need it "4th from the end",
you would select it "4th from the end".

You can easily populate this column via a trigger.  A simple package:

create or replace package my_host_name
as
   g_hostname varchar2(255);
end;
/
create or replace package body my_host_name
as

begin
   select host_name into g_hostname from v$instance;
end;
/

and then a trigger:

begin
  :new.host_name := my_host_name.g_hostname;
end;
/

will do that.

But -- don't EVER count on the order of columns in a table.  If you need
something "4th from the end" -- then select it 4th from the end.

Review &
      Followup

      Rating: 4
What does
      "Don't count on" means ?  February 06, 2003

Reviewer:  A reader  from Turkey
Hi Tom,

What do you mean with "Don't count on the order of columns in a table?"

Does it mean if  we select * from t, then the order of the columns in the result
could be different from the order they created (or added later) ?

          Followup:

show me in the documentation where we say otherwise?

show me where it states "we promise the order of columns in a select * will
always be the same"

sure historically, it has been that way -- but hey, who knows?  do i think it'll
change -- no.  but really -- if something is supposed to be 4th from last, the
only way to ensure that is to select it 4th from last

Just like the only way to ensure a set of rows is in some order is to use order
by.

Putting SELECT * into any application (other than plsql) is just asking for
*trouble*.  Say we did add this thing as the 4th to last -- so now youre going
to have to recode all of those things that did select *

if this is for some "reporting tool" - use a VIEW and select the columns out in
order of preference and have the tool use the view.

PLSQL is somewhat IMMUNE from this as it'll auto recompiled and fix the
structures -- if you use implicit cursors or do explicit cursors right (eg:
fetch into a record defined as CURSOR%ROWTYPE -- not into a user defined record
that cannot "fix itself" when the output of the query changes)

Also -- select * is a performance issue, you should select only that which you
truly need (even in plsql)

      Rating: 3
add new columns
      and statistics behaviour -- 9.2.0.3  June 08, 2003

Reviewer:  Baqir Hussain  from Emeryville, CA USA

Tom,
We are going to add new columns into to few tables.
Few questions regarding columns add and stats:

1.  By adding a new column, does stats on the table get changed?

2. Do we need to gather stats every time a new column is added?

Thanks

          Followup:

1) you would analyze the table afterwards -- but only after the column was
populated, initially the column would be entirely NULL or entirely a constant...
other then the fact that it could affect the average row width, it isn't going
to change anything really

2) depends.  add a null column, no.  add a column with a constant -- not really,
it isn't going to change much.

      Rating: 3
Adding columns
      to all tables - atomically  June 24, 2003

Reviewer:  Jim Dickson  from London, UK
I think i already know the answer to this, but big fan of asking stupid
questions just in case.

In 9i, is there any technique to add a column to >1 table atomically so that
either all tables have new col or none do?

I know DDL is self-committing etc.

PS What/where is the cons.sql scripts that Sundar refers to?

          Followup:

no, they would be added one by one..

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:399218963817
has cons.sql

      Rating: 4
Adding columns
      to all tables - atomically   June 27, 2003

Reviewer:  Jim Dickson  from London, UK
I thought that was the case
- but i was hoping for some kind of ALTER SCHEMA technique.

Thanks for the site and the books.

      Rating: 4
column order --
      select * from table  December 15, 2003
Reviewer:
      Baqir Hussain  from Emeryville, CA USA
Tom,

I have created a table (oracle9i R2)and desc is as follows:

SQL> desc metro
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 METRO                                     NOT NULL VARCHAR2(128)
 PERSONNEL_ID                              NOT NULL NUMBER(10)
 SHORT_NAME                                         VARCHAR2(32)
 LONG_NAME                                          VARCHAR2(128)
 STATE                                              CHAR(2)
 SEARCH_TYPE                                        VARCHAR2(32)
 IS_LOCALIZED_SEARCH                                NUMBER(1)
 IS_LOCALIZED_RESULTS                               NUMBER(1)

1. Is " select * from metro" ALWAYS give us the result in the same column order
as the table was created?

2. OR is there any possibility that the columns order might change during
"select * from metro" ?

Thanks
Baqir Hussain

          Followup:

1) empirically we can say "yes"

would I rely on it in code?  Only in PLSQL -- not in any other lanaguage.  In
PLSQL

  for x in ( select * from t )

will create a record X with attributes metro, state, etc.  the order doesn't
really matter.  PLSQL is very self correcting in this manner.  No other language
is.

Don't take shortcuts -- in real code (outside of plsql) use column names and in
ALL languages -- always list the column names in an insert statement.

2) sure -- someone might move the columns around, not aware you are relying on
them being "in order".  if you need them in order - ask for them in that order!

      Rating: 5
Not as unlikey
      as you think Tom.  December 16, 2003
Reviewer:
      Chuck Jolley  from OKC, OK USA
The question is it's own example of why not to do it that way....

What happens to the SELECT * if the guy in the next office desides he wants to
add a new column "5th from the end" later.

After all, this type of change is already being contemplated right now, in a
recursive, cascading disaster kind of way.;)
chuck

          Followup:

i didn't say it was unlikely -- in fact, it is probable over time!

December 16, 2003 Vikas -- Thanks for the question regarding
      "Number Datatype", version 8.0.3

      You Asked
Hi Tom,

I declared the datatype of a column of a table as NUMBER & Presumed it to be
Number(38) to be specific.

But What I found to be strange that, it is accepting digits beyong 38 digits.ie.
1.111E+125. If the Datatype's precision is 38 and scale can range from -84 to
127 how this value can insert into the column.

In one of the System owned tables, USER_TAB_PRIVS there is a column known as
Table_Name but under this column you will find all the objects of a schema which
has been granted object privs. whether it is table,view or any sequence.

Shouldn't it be more precise to define this column name as Object_name rather
that the table_name.

Please do explain & clarify.

Thanks in Anticipation,
Vikas Khanna
Vkhanna@quark.stpm.soft.net

      and we said...
1.111e+125 only has 4 digits of precision (number of digits needed to represent
the number -- in this case 1111).

The 38 is for 38 digits of precision.  The SCALE dictates how many places to the
right or left of the decimal place you may have.

It would be more clear to have USER_TAB_PRIVS say that -- however, it would
break lots of stuff.  User_tab_privs pre-dates many of these object types and
retains its naming for backwards compatibility reasons.
Review &
      Followup

      Rating: 2
More
      Clarification  October 16, 2001
Reviewer:  Vikas
      Khanna  from INDIA
I entered a number
12345678901234567890123456789012345678901234567890123456789012345678901234567890
into that particular column.

It showed me the result as
12345678901234567890123456789012345678900000000000000000000000000000000000000000,
 when set numwidth 2000 was issued.
(SQL Worksheet accepts it)

When issued set numwidth 80 it displayed ,
1.2345678901234567890123456789012345678900000000000000000000000000000000000000000
E+79

Why it is so??Moreover I was not able to understand the concept behind this.Can
u please explain in more detail how Oracle does store the Numbers internally.
Take relevant examples for precision & scale.

          Followup:

Well first, I hope you have read the documentation (always a great place to
start). see

http://technet.oracle.com/doc/oracle8i_816/server.816/a76965/c10datyp.htm#743
there is a nice table there with various precisions/scales that demonstrate the
outcome.

Numbers are stored with an assured (at least) 38 digits of precision.  It is
allowed to go higher (you got 39 digits of precision).  Numbers that exceed the
PRECISION when the precision is not specified are rounded to fit in there (just
like if you stuff a 15 digit number into a C float/double -- it'll be made to
"fit" since they hold numbers with 6/13 digits of precision only)

The number is like number(*) (wildcard)

ops$tkyte@ORA717DEV.US.ORACLE.COM> create table t ( x number(*) );
Table created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> insert into t values (
12345678901234567890123456789012345678901234567890  );

1 row created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> select to_char(x,rpad('9',60,'9')) from t;

TO_CHAR(X,RPAD('9',60,'9'))
-------------------------------------------------------------
           12345678901234567890123456789012345678900000000000

With a numwidth of 2000 -- there was enough room to display the number in its
entirety without using scientific notation -- so it did.  With a numwidth of 80,
there was NOT enough room -- so it used scientific notation to display the
number in your requested field size.  That part seems all very logical (to me
anyway, it was just doing exactly what you asked it to do).

      Rating:
      4
  July 04, 2002
Reviewer:  Vijay
       from India
When I create a table like:
create table test(no number(7)) ;

insert into test values(1.00e+25) ;
it gives the following error message:
ORA-01438: value larger than specified precision allows for this column.

Can u pls. explain this.

          Followup:

you said 7 digits, you gave it 25

number(7) is very different from number.  Number is really like number(*) -- an
number(*) is acceptable and has a meaning.

read the link provided above (well, what the link points to)

      Rating: 4
Regarding
      Number datatype  July 04, 2002
Reviewer:
      A reader
Vijay..

     Better go back and read the Oracle manual before coding.....

      Rating: 5
declaring
      number vs number(n)  June 30, 2003
Reviewer:
      A reader
Hi

What the difference between declaring a column using datatype

number
and
number(n)?

Ay advantage or disadvantge declaring one way and the another?

          Followup:

number(n) is a number with a constraint and edit.

ops$tkyte@ORA920> create table t ( x number, y number(5) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( 123.456, 123.456 );

1 row created.

ops$tkyte@ORA920> insert into t values ( 123.999, 123.999 );

1 row created.

ops$tkyte@ORA920> insert into t values ( 12345, 12345 );

1 row created.

ops$tkyte@ORA920> insert into t values ( 123456, 123456 );
insert into t values ( 123456, 123456 )
                               *
ERROR at line 1:
ORA-01438: value larger than specified precision allows for this column

ops$tkyte@ORA920> select * from t;

         X          Y
---------- ----------
   123.456        123
   123.999        124
     12345      12345

See how the number(5) behaves?  5 digits, no decimals, rounded.

The advantage is

a) you have 5 digits only, never more
b) you have no decimals, it is an integer type

consider it a constraint -- like a primary key, NOT NULL, check, whatever.

If your data is such that the number should never be more then 5 digits, the
only correct way to implement it would be as a number(5)

      Rating: 5
How do we
      express infinity in Oeacle?  June 30, 2003

Reviewer:  A Reader  from USA
Like 1 divided by infinity=0.
Thanks.

          Followup:

null

there is no "infinity".

      Rating:
      5
  June 30, 2003
Reviewer:  Dave
       from Colorado
Time to roll out the trivia.

You could have a positive or negative "infinity" value by importing it from
Oracle 5, apparantly.

5 was before my time, but are operations on such special numbers still supported
in 9i Tom? Not the generation of the value, but multiplication, SUM() etc?

          Followup:

those NAN and infinity values from v5 and before would cause some amount of
"issues"

      Rating: 3
how number and
      char datatype behave different from varchar/varchar2  July
      23, 2003
Reviewer:  A reader
Dear Tom,

My question is how number and char datatype behave different from
varchar/varchar2?
As you see in my example, I can't reduce the size for Number & Char datatype
when the table has data in it,
but I can reduce the size of varchar/varchar2 even if the table is not empty.

1- Is it because when you define a column as Number(n) or Char(n) it allocates
all storage? Aren't they dynamic?
2- Is the only difference between Number and Number(n) is a constraint

Could you please explain this.

Thanks in advance,
Arash

system@T800> create table t (x number(10));

Table created.

system@T800> alter table t modify x number(9);

Table altered.

system@T800> insert into t values(10);

1 row created.

system@T800> alter table t modify x number(8);
alter table t modify x number(8)
                     *
ERROR at line 1:
ORA-01440: column to be modified must be empty to decrease precision or scale

system@T800> delete from t;

1 row deleted.

system@T800> alter table t modify x number(5);

Table altered.

---------------------------------------------------

system@T800> create table t1(x varchar(10));

Table created.

system@T800> alter table t1 modify x varchar(9);

Table altered.

system@T800> insert into t1 values('aaa');

1 row created.

system@T800> alter table t1 modify x varchar(8);

Table altered.

system@T800> alter table t1 modify x char(8);

Table altered.

system@T800> alter table t1 modify x char(7);
alter table t1 modify x char(7)
                      *
ERROR at line 1:
ORA-01441: cannot decrease column length because some value is too big

system@T800> delete from t1;

1 row deleted.

system@T800> alter table t1 modify x char(7);

Table altered.

          Followup:

it is relatively new that you can reduce the size of a varchar.

a CHAR by very definition cannot be as the entire table needs to be rewritten --
a char(8) has 8 characters -- to make it a char(7) would involve shrinking each
and every column.

A number type just doesn't support being "sized down" right now.

      Rating:
      3
  July 24, 2003
Reviewer:
      A reader  from Canada
Just one word Tom. You are great! Thanks for your time. Thanks for all of your
support even during your vacation.

Have a pleasant vacation,
Arash

      Rating: 5
How to
      distinguish between NUMBER and FLOAT datatypes?  October 15,
      2003
Reviewer:  onsh76
Hi Tom,

Will you please explain a little bit more about new feature introduced in Oracle
9i patchset#2761332  for Windows, release 9.2.0.3. event 10499:
--------------------------------
It is not sufficient to just test the scale alone to distinguish between these
two datatypes, one needs to test for a scale of -127 and a non-zero precision to
identify a FLOAT.
--------------------------------

how can there be scale=-127 and precision=0?
What's Oracle saying, that would not be a FLOAT?

Thanks,

onsh76

          Followup:

don't really know anything about it personally.  float has always been
synonymous with number -- we don't really "do floats" (until 10g)

      Rating: 5
Number size and
      space usage  December 16, 2003
Reviewer:
      A reader
Hi Tom,

Regarding space usage for number types, do I need two times of space to store a
column of NUMBER(10) than a column of NUMBER(5)?

I read in an article that they are the same, even a NUMBER(38), but I am not
sure.

Thanks.

          Followup:

number(n) is an edit -- restricting the number to n digits in length.

if you store the number '55', it takes the same space in a number(2) as it does
in a number(38).

the storage required = function of the number actually stored.

99999 takes more space to store then
10000 does

(you can use vsize() to see the size)

December 16, 2003 Arjun -- Thanks for the question regarding "How
      to avoid the full table scan", version 8.1.7

      You Asked
Hi Tom,
I have the situation as defined below:

I have created two tables
create table I1(n number primary key, v varchar2(10));
create table I2(n number primary key, v varchar2(10));

and a map table
create table MAP(n number primary key, i1 number referencing I1(n),
i2 number referencing I2(n));

I have an index created on MAP
create unique index IDX_MAP on MAP(i1, i2)

Now when I take the explain plan for the query :
 select *
 from
       i1,
       map,
       i2
 where  i1.n = map.i1
 and i2.n = map.i2
 and i1.v = 'x'
 and i2.v = 'y';

I see the plan as

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     NESTED LOOPS
   3    2       TABLE ACCESS (FULL) OF 'MAP'
   4    2       TABLE ACCESS (BY INDEX ROWID) OF 'I2'
   5    4         INDEX (UNIQUE SCAN) OF 'SYS_C00683648' (UNIQUE)
   6    1     TABLE ACCESS (BY INDEX ROWID) OF 'I1'
   7    6       INDEX (UNIQUE SCAN) OF 'SYS_C00683647' (UNIQUE)

Is there any way to avoid the full table scan on MAP table?

What ever I try, one table is always going for a full scan. Like if I change the
query like
select *
     from
               map,
           i1,
               i2
     where  i1.n = map.i1
     and i2.n = map.i2
     and i1.v = 'x'
     and i2.v = 'y';
I get the plan
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     NESTED LOOPS
   3    2       TABLE ACCESS (FULL) OF 'I1'
   4    2       TABLE ACCESS (BY INDEX ROWID) OF 'MAP'
   5    4         INDEX (RANGE SCAN) OF 'IDX_MAP' (UNIQUE)
   6    1     TABLE ACCESS (BY INDEX ROWID) OF 'I2'
   7    6       INDEX (UNIQUE SCAN) OF 'SYS_C00688807' (UNIQUE)

What should I do to avoid a full scan in such a case?

Thanks in advance
Arjun

      and we said...
first of all -- just keep saying to yourself over and over

"full scans are not evil, indexes are not good"
"full scans are not evil, indexes are not good"
"full scans are not evil, indexes are not good"
"full scans are not evil, indexes are not good"
"full scans are not evil, indexes are not good"
"full scans are not evil, indexes are not good"

over and over and over...

Then, look at your query -- tell me, how can a full scan be avoided?

tell me -- using the EXISTING data structures you have -- what plan could YOU
yourself as a human come up with that does not involve a full table or index
scan?  Given your existing structures, the INDEXES are actually deadly here --
the CBO would be smarter and stop using them.

ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920>
ops$tkyte@ORA920>  select *
  2   from
  3         i1,
  4         map,
  5         i2
  6   where     i1.n = map.i1
  7   and i2.n = map.i2
  8   and i1.v = 'x'
  9   and i2.v = 'y';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     NESTED LOOPS
   3    2       TABLE ACCESS (FULL) OF 'MAP'
   4    2       TABLE ACCESS (BY INDEX ROWID) OF 'I2'
   5    4         INDEX (UNIQUE SCAN) OF 'SYS_C003735' (UNIQUE)
   6    1     TABLE ACCESS (BY INDEX ROWID) OF 'I1'
   7    6       INDEX (UNIQUE SCAN) OF 'SYS_C003734' (UNIQUE)

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_stats.set_table_stats( user, 'I1', numrows => 10000,
numblks => 10000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec dbms_stats.set_table_stats( user, 'I2', numrows => 10000,
numblks => 10000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec dbms_stats.set_table_stats( user, 'MAP', numrows =>
100000, numblks => 10000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920>  select *
  2   from
  3         i1,
  4         map,
  5         i2
  6   where     i1.n = map.i1
  7   and i2.n = map.i2
  8   and i1.v = 'x'
  9   and i2.v = 'y';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2891 Card=10 Bytes=790)
   1    0   HASH JOIN (Cost=2891 Card=10 Bytes=790)
   2    1     TABLE ACCESS (FULL) OF 'I2' (Cost=963 Card=100 Bytes=2000)
   3    1     HASH JOIN (Cost=1927 Card=1000 Bytes=59000)
   4    3       TABLE ACCESS (FULL) OF 'I1' (Cost=963 Card=100 Bytes=2000)
   5    3       TABLE ACCESS (FULL) OF 'MAP' (Cost=963 Card=100000
Bytes=3900000)

as designed, those indexes would KILL YOU -- literally KILL you.  Watch:

ops$tkyte@ORA920> insert into i1 select rownum, rpad('*',10,'*') from
all_objects;

30020 rows created.

ops$tkyte@ORA920> insert into i2 select rownum, rpad('*',10,'*') from
all_objects;

30020 rows created.

ops$tkyte@ORA920> insert into map select rownum, rownum, rownum from
all_objects;

30020 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly
ops$tkyte@ORA920>
ops$tkyte@ORA920>  select *
  2   from
  3         i1,
  4         map,
  5         i2
  6   where     i1.n = map.i1
  7   and i2.n = map.i2
  8   and i1.v = 'x'
  9   and i2.v = 'y';

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     NESTED LOOPS
   3    2       TABLE ACCESS (FULL) OF 'MAP'
   4    2       TABLE ACCESS (BY INDEX ROWID) OF 'I2'
   5    4         INDEX (UNIQUE SCAN) OF 'SYS_C003755' (UNIQUE)
   6    1     TABLE ACCESS (BY INDEX ROWID) OF 'I1'
   7    6       INDEX (UNIQUE SCAN) OF 'SYS_C003754' (UNIQUE)

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

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table i1 compute statistics;

Table analyzed.

ops$tkyte@ORA920> analyze table i2 compute statistics;

Table analyzed.

ops$tkyte@ORA920> analyze table map compute statistics;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920>  select *
  2   from
  3         i1,
  4         map,
  5         i2
  6   where     i1.n = map.i1
  7   and i2.n = map.i2
  8   and i1.v = 'x'
  9   and i2.v = 'y';

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=21 Card=1 Bytes=40)
   1    0   NESTED LOOPS (Cost=21 Card=1 Bytes=40)
   2    1     HASH JOIN (Cost=20 Card=1 Bytes=26)
   3    2       TABLE ACCESS (FULL) OF 'I1' (Cost=10 Card=1 Bytes=14)
   4    2       TABLE ACCESS (FULL) OF 'MAP' (Cost=9 Card=30020 Bytes=360240)
   5    1     TABLE ACCESS (BY INDEX ROWID) OF 'I2' (Cost=1 Card=1 Bytes=14)
   6    5       INDEX (UNIQUE SCAN) OF 'SYS_C003755' (UNIQUE)

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

ops$tkyte@ORA920> set autotrace off

tell me -- which looks better to you??  to me -- that second one is looking
pretty good

creating an index on i1.v or i2.v would be helpful there maybe?

ops$tkyte@ORA920> create index i1_idx on i1(v);

Index created.

ops$tkyte@ORA920> create index i2_idx on i2(v);

Index created.

ops$tkyte@ORA920> analyze table i1 compute statistics;

Table analyzed.

ops$tkyte@ORA920> analyze table i2 compute statistics;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly
ops$tkyte@ORA920>
ops$tkyte@ORA920>  select *
  2   from
  3         i1,
  4         map,
  5         i2
  6   where     i1.n = map.i1
  7   and i2.n = map.i2
  8   and i1.v = 'x'
  9   and i2.v = 'y';

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=13 Card=1 Bytes=40)
   1    0   NESTED LOOPS (Cost=13 Card=1 Bytes=40)
   2    1     HASH JOIN (Cost=12 Card=1 Bytes=26)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'I1' (Cost=2 Card=1 Bytes=14)
   4    3         INDEX (RANGE SCAN) OF 'I1_IDX' (NON-UNIQUE) (Cost=1 Card=1)
   5    2       TABLE ACCESS (FULL) OF 'MAP' (Cost=9 Card=30020 Bytes=360240)
   6    1     TABLE ACCESS (BY INDEX ROWID) OF 'I2' (Cost=1 Card=1 Bytes=14)
   7    6       INDEX (UNIQUE SCAN) OF 'SYS_C003755' (UNIQUE)

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

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace off

Also -- you really need to ANALYZE your tables and use the CBO to its fullest.

But remember, keep saying

"full scans are not evil, indexes are not good"
"full scans are not evil, indexes are not good"
"full scans are not evil, indexes are not good"
"full scans are not evil, indexes are not good"

until you believe it.

Review &
      Followup

      Rating: 5
I would
      rephrase that  April 25, 2003
Reviewer:
      A reader
"full scans are NOT ALWAYS evil, indexes are NOT ALWAYS good"
"full scans are NOT ALWAYS evil, indexes are NOT ALWAYS good"
"full scans are NOT ALWAYS evil, indexes are NOT ALWAYS good"
"full scans are NOT ALWAYS evil, indexes are NOT ALWAYS good"

          Followup:

good point...

nothing is black or white, it is all shades of grey

      Rating: 5
JUST
      COOL  April 26, 2003
Reviewer:  kiro
       from BG

      Rating: 5
Indexes are
      (usually) good?   October 27, 2003
Reviewer:
      Dragan Novicic  from Belgrade
I have seen the previous example in a sample Chapter 2 (Page 30) of your new
book and spent a day analyzing it. Something confused me.
You were definite in your attitude that having a predefined database structure
and a query to run against it, it is not possible to avoid a full table scan.
For me, the answer is simply creating a missing index on table I1 on column v.
Actually, you were proposing such an index in your answer, but the real cause
for confusion was in choosing a constant value for v column (‘**********’).
If we change your example slightly, by filling v column with
substr(object_name,1,10), we will come to totally different
conclusions.

dnovicic@NOKI>  select * from i1, map, i2
  2   where i1.n=map.i1
  3   and i2.n=map.i2
  4   and i1.v='x'
  5   and i2.v='y';

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=15 Card=1 Bytes=40)
   1    0   NESTED LOOPS (Cost=15 Card=1 Bytes=40)
   2    1     NESTED LOOPS (Cost=12 Card=3 Bytes=78)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'I1' (Cost=3 Card=3 Bytes=42)
   4    3         INDEX (RANGE SCAN) OF 'I1_IDX' (NON-UNIQUE) (Cost=1 Card=3)
   5    2       TABLE ACCESS (BY INDEX ROWID) OF 'MAP' (Cost=3 Card=21570
Bytes=258840)
   6    5         INDEX (RANGE SCAN) OF 'IDX_MAP' (UNIQUE) (Cost=2 Card=21570)
   7    1     TABLE ACCESS (BY INDEX ROWID) OF 'I2' (Cost=1 Card=3 Bytes=42)
   8    7       INDEX (UNIQUE SCAN) OF 'SYS_C002343' (UNIQUE)

As we can see, CBO has produced the execution plan, without using full table
scan. What you were demonstrating was more like an exception (tables filled with
constant values), than a rule, for a given table structure and a given query
against them.

          Followup:

i was adamant that given the structures the person asking the question was using
-- a full scan was the only way.  I said "could add an index to avoid it" but
saw a better way.

      Rating: 5
Some other
      aspects  October 27, 2003
Reviewer:  Dragan
      Novicic  from Belgrade, SCG
One remark also stands here: measuring efficiency of query plan by statistics of
consistent gets in a case when result contains no rows at all, is misleading.
Because query plan contains steps that were not executed simply because access
to the first of three joined tables produced no rows, we can not have a picture
of what will happen if these tables were accessed during query execution.
I will recommend you to use excellent example of three tables and a given query
against them, from original question, to discuss influence of cardinality on
mapping table. When accessing MAP table from the side of I1 table, as in the
query plan above, it would be interesting to see what will happen when the
mapping is actually 1:N and not 1:1 as in your example. In addition, the
influence of CLUSTERING_FACTOR of index on map(i1,i2) could be demonstrated
here. Maybe even the use of over-indexing on map table by producing index on
map(i1,i2,n) to avoid access to a map table in joining operation at all.
Demonstration of index inefficiency on low cardinality columns should be
demonstrated on a single table and not using such a complex, three tables
scenario.

      Rating: 5
Scan
      Waits?  November 05, 2003
Reviewer:  Mike

Hi Tom,

In our *large* warehouse, we do a lot of full table scans.  Our statspack
reports usually show something like:

Top 5 Timed Events
~~~~~~~~~~~~~~~~~~                                                     % Total
Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
db file scattered read                             28,040       1,771    54.79
free buffer waits                                   1,464       1,252    38.73
CPU time                                                          109     3.38
db file sequential read                            12,462          87     2.69
log file parallel write                               172           4      .12
          -------------------------------------------------------------
Wait Events for DB:

                                                                   Avg
                                                     Total Wait   wait    Waits
Event                               Waits   Timeouts   Time (s)   (ms)     /txn
---------------------------- ------------ ---------- ---------- ------ --------
db file scattered read             28,040          0      1,771     63  9,346.7
free buffer waits                   1,464      1,129      1,252    855    488.0
db file sequential read            12,462          0         87      7  4,154.0

Are these waits due to the full table scans?

Do you have any suggestions for increasing the performance of full table scans?
Block buffers are maxed out and disk system is already tuned.

Thank you.

          Followup:

have you looked at MV's to help with the full scan issue (full scan 10meg
instead of 10gig)

have you efficiently partitioned in order to help reduce the io?

are you using bitmap indexes in order to help reduce the io?

we are not going to look at "how to full scan faster", we are going to look at
"how to NOT have to full scan"

the best way to speed up something is to not do it in the first place.

      Rating: 5
Scan
      Waits  November 06, 2003
Reviewer:
      A reader
We have implemented all of your suggestions.  However, we get a number of one
time query requests that often result in doing a full table scan on a billion
plus row table (12 partitions).

Am I correct to say that the waits are due to Oracle trying to find blocks in
the buffer cache?

      Rating: 5
how to use Full
      Table Scan  November 28, 2003
Reviewer:
      A reader
Hi

My question is the other way round.
I want to use FTS but Oracle does not do it!

Here are the informations:

TABLE_NAME       NUM_ROWS     BLOCKS EMPTY_BLOCKS  AVG_SPACE AVG_ROW_LEN
-------------- ---------- ---------- ------------ ---------- -----------
PSCLASSDEFN           300          3            4       1804          61
PSROLECLASS          2216         12            3       1651          33
PSROLEUSER           9160         39            0       1154          28

TABLE_NAME     INDEX_NAME       COLUMN_NAME
-------------- ---------------- ----------------
PSCLASSDEFN    PS_PSCLASSDEFN   CLASSID
PSCLASSDEFN    PSAPSCLASSDEFN   CLASSID
PSCLASSDEFN    PSAPSCLASSDEFN   VERSION
PSCLASSDEFN    PSBPSCLASSDEFN   VERSION
PSROLECLASS    PS_PSROLECLASS   ROLENAME
PSROLECLASS    PS_PSROLECLASS   CLASSID
PSROLEUSER     PS_PSROLEUSER    ROLEUSER
PSROLEUSER     PS_PSROLEUSER    ROLENAME
PSROLEUSER     PSAPSROLEUSER    ROLENAME

dbms_space information

Owner        Type            Name                          Blocks Next Ext
Unused Free
------------ --------------- ---------------------------- ------- --------
------ ----
SYSADM       TABLE           PSROLEUSER                        40       0    4

SYSADM       TABLE           PSROLECLASS                       16       3    2

SYSADM       TABLE           PSCLASSDEFN                        8       4    1

SYSADM       TABLE           PSROLEUSER_RMT                     8       7    0

SYSADM       INDEX           PSAPSROLEUSER                     64       3   44

SYSADM       INDEX           PS_PSROLEUSER                     64       3   43

SYSADM       INDEX           PS_PSROLECLASS                    24       3   10

SYSADM       INDEX           PSAPSCLASSDEFN                     8       6    0

SYSADM       INDEX           PSBPSCLASSDEFN                     8       6    0

SYSADM       INDEX           PS_PSCLASSDEFN                     8       6    0

SYSADM       INDEX           PS_PSROLEUSER_RMT                  8       6    0

variable a varchar2(30)

exec :a := 'ABILEY'

SELECT DISTINCT (b.classid)
           FROM SYSADM.PSROLEUSER A, SYSADM.PSROLECLASS B
          WHERE a.rolename = b.rolename
            AND a.roleuser = :A
            AND NOT EXISTS (SELECT c.classid
                              FROM SYSADM.PSCLASSDEFN C
                             WHERE c.classid = b.classid)

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=48 Card=43 Bytes=2365)
   1    0   SORT (UNIQUE) (Cost=5 Card=43 Bytes=2365)
   2    1     FILTER
   3    2       NESTED LOOPS (Cost=3 Card=43 Bytes=2365)
   4    3         TABLE ACCESS (FULL) OF 'PSROLECLASS' (Cost=3 Card=111
Bytes=3330)
   5    3         INDEX (UNIQUE SCAN) OF 'PS_PSROLEUSER' (UNIQUE)
   6    2       INDEX (UNIQUE SCAN) OF 'PS_PSCLASSDEFN' (UNIQUE)
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       2233  consistent gets
          0  physical reads
          0  redo size
        275  bytes sent via SQL*Net to client
        460  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          0  rows processed

HERE if I use FTS

SELECT /*+ FULL(A) */ DISTINCT (b.classid)
           FROM SYSADM.PSROLEUSER A, SYSADM.PSROLECLASS B
          WHERE a.rolename = b.rolename
            AND a.roleuser = :A
            AND NOT EXISTS (SELECT  c.classid
                              FROM SYSADM.PSCLASSDEFN C
                             WHERE c.classid = b.classid)

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=57 Card=43 Bytes=2365)
   1    0   SORT (UNIQUE) (Cost=14 Card=43 Bytes=2365)
   2    1     FILTER
   3    2       HASH JOIN (Cost=12 Card=43 Bytes=2365)
   4    3         TABLE ACCESS (FULL) OF 'PSROLEUSER' (Cost=8 Card=22 Bytes=550)
   5    3         TABLE ACCESS (FULL) OF 'PSROLECLASS' (Cost=3 Card=111
Bytes=3330)
   6    2       INDEX (UNIQUE SCAN) OF 'PS_PSCLASSDEFN' (UNIQUE)

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

42 consistent gets vs 2233!!!!!

Oracle 9.2.0.2 64 bits on HP-UX 64 bits

Why is the optimizer so stupid :-(

          Followup:

seems to me that your statistics must be way out of whack.  You see -- the
optimizer thinks "111 rows from psroleclass"

so, lets see the tkprof.

and remember the old saying "garbage in, garbage out", things are only as smart
as the information provided them.

      Rating: 4
analyze or
      dbms_stats.gather_table_stats  November 28, 2003

Reviewer:  Robin H  from Winnipeg, MB Canada

I was told in 9i to stop using the feature "analyze table" and replace it with
dbms_stats.gather_table_stats().

Does it matter, are they the same?

          Followup:

the recommendation going forward is "dbms_stats"

it is more flexible (you'll love gather stale and alter table monitoring -- you
only gather stats on that which you need).  can run in parallel.  is more
automated/automatable.

as with ANY big change -- testing is important.

in general,

dbms_stats.gather_table_stats( user, 'T',  method_opt => 'for all indexed
columns', cascade => true )

is more or less the same as

analyze table t compute statistics for table for all indexes for all indexed
columns;

method opt gets histograms
cascade gets indexes
gather_table_stats does the table

      Rating: 5
42 consistent
      gets vs 2233!!!!!  December 01, 2003
Reviewer:
      A reader
hi again

I re-analyzed the table with this syntax

exec dbms_stats.gather_table_stats(OWNNAME => 'SYSADM', -
                                   TABNAME => 'PSROLEUSER', -
                                   ESTIMATE_PERCENT => 99, -
                                   METHOD_OPT => 'FOR ALL INDEXED COLUMNS SIZE
75', -
                                   DEGREE => 2, -
                                   CASCADE => TRUE);

for all three involved tables

and still got this plan

  1  SELECT DISTINCT (b.classid)
  2             FROM SYSADM.PSROLEUSER A, SYSADM.PSROLECLASS B
  3            WHERE a.rolename = b.rolename
  4              AND a.roleuser = :A
  5              AND NOT EXISTS (SELECT c.classid
  6                                FROM SYSADM.PSCLASSDEFN C
  7*                              WHERE c.classid = b.classid)

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=11 Card=6 Bytes=324)
   1    0   SORT (UNIQUE) (Cost=5 Card=6 Bytes=324)
   2    1     FILTER
   3    2       NESTED LOOPS (Cost=3 Card=6 Bytes=324)
   4    3         TABLE ACCESS (FULL) OF 'PSROLECLASS' (Cost=3 Card=111
Bytes=3330)
   5    3         INDEX (UNIQUE SCAN) OF 'PS_PSROLEUSER' (UNIQUE)
   6    2       INDEX (UNIQUE SCAN) OF 'PS_PSCLASSDEFN' (UNIQUE)

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

As you said it´s taking 111 as cardinality, not sure why because if I do

select count(*) from SYSADM.PSROLECLASS;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'PSROLECLASS' (Cost=3 Card=2216)

as you can see it does know 2216 as the *real* cardinality

Now, let´s see the tkprof

SELECT DISTINCT (b.classid)
           FROM SYSADM.PSROLEUSER A, SYSADM.PSROLECLASS B
          WHERE a.rolename = b.rolename
            AND a.roleuser = :A
            AND NOT EXISTS (SELECT c.classid
                              FROM SYSADM.PSCLASSDEFN C
                             WHERE c.classid = b.classid)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.02       0.02          0       2233          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.02       0.02          0       2233          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 56  (SYSTEM)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  SORT UNIQUE (cr=2233 r=0 w=0 time=24878 us)
      0   FILTER  (cr=2233 r=0 w=0 time=24848 us)
      0    NESTED LOOPS  (cr=2233 r=0 w=0 time=24844 us)
   2216     TABLE ACCESS FULL OBJ#(40626) (cr=15 r=0 w=0 time=4385 us)
      0     INDEX UNIQUE SCAN OBJ#(40650) (cr=2218 r=0 w=0 time=11728 us)(object
id 40650)
      0    INDEX UNIQUE SCAN OBJ#(39824) (cr=0 r=0 w=0 time=0 us)(object id
39824)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   SORT (UNIQUE)
      0    FILTER
      0     NESTED LOOPS
   2216      TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'PSROLECLASS'
      0      INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF 'PS_PSROLEUSER'
                 (UNIQUE)
      0     INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF 'PS_PSCLASSDEFN'
                (UNIQUE)

I am puzzled

          Followup:

what is the output of

SQL> show parameter optimizer

      Rating: 5
show
      parameter  December 01, 2003
Reviewer:
      A reader
show parameter optimizer

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
optimizer_dynamic_sampling           integer     1
optimizer_features_enable            string      9.2.0
optimizer_index_caching              integer     0
optimizer_index_cost_adj             integer     100
optimizer_max_permutations           integer     2000
optimizer_mode                       string      CHOOSE

all default!

      Rating: 5
try to
      rebuild  December 02, 2003
Reviewer:
      A reader
hi

have you reorganized your database? tables and indexes?

please do so and see your database fly

          Followup:

or not, or maybe see your system grind to a halt.

point is MEASURE and make sure you DO MORE GOOD THEN HARM.

(like a doctor)

      Rating: 2
Setting Table
      Statistics  December 03, 2003
Reviewer:
      A reader  from NJ, USA
Tom,

1. In the above queries, what was your observation that made you to conclude
that an index will be a poor performer. It would be of help if you could explain
your observation on that and how you decided that using an index will be a
performance killer. What statistics we need to consider?
2. My understanding is that set_table_stats can be used to simulate a production
volume on a development environment to test queries. Is that correct? Also, what
does numblks option mean?
3. If 2 is true, then is there a way to simulate things such as
co-located/disorganized. ie I want to simulate a condition where a table value
is spread across multiple blocks (ie. want to simulate a condition where data is
disorganized and data is colocated). Are there any procedures like
set_table_stats available to simulate the situation. A good example will be of
great help

          Followup:

1) no statistics, just looked at the structures:

create table I1(n number primary key, v varchar2(10));
create table I2(n number primary key, v varchar2(10));

create table MAP(n number primary key, i1 number referencing I1(n),
i2 number referencing I2(n));

create unique index IDX_MAP on MAP(i1, i2)

 select * from i1, map, i2 where  i1.n = map.i1 and i2.n = map.i2
 and i1.v = 'x' and i2.v = 'y';

now, there are no indexes on v.  So, we have to join from i1 to map to i2 (if we
want to use any indexes -- the index on map leads with i1)

So, we could

  full scan i1 -> filter on v='x' -> index scan to map -> index scan to i2 ->
filter on v='y'

say there are 100,000 records in i1.

most or many are v='x'.

so we'll do tons of index range scans on map's index (for each row in i1 such
that v='x') and then do tons of index ranges on i2 to do a table access by index
rowid to determine if v='y' in that table.

hip hop, skip, jump, io io io io io (after io after io)

indexes would not be "a good thing"

2) it can not be used for that AT ALL.  not AT ALL.  you can use it to see what
the plan would be if the table in dev was that size (the size production was).

but -- so what????  what would that do for you?

scenario:

table as 1,000,000 rows in production.  you export stats and import then into
test (where the table has 1,000 rows) or you just set them.

you run a query in test.  it runs GREAT.  superb.  fantastic.

what can you say about its performance in prod?

nothing, less then nothing actually (because you are falsely influenced by
"great performance in test with prod stats")

you run another query in test.  it runs terrible, horrible, really bad.

what can you say about its performance in prod?

nothing, less then nothing actually (because you are falsely influenced by
"great performance in test with prod stats")

I find exporting of stats from prod into test good for:

a) reproducing an optimizer "bad plan" for support
b) testing different hints, to see which "stick"

thats about it.

3) I call this "restore the backup of prod to test".  that buys you two things

a) you discover for the first time whether you actually can (restore :)
b) you can do real load testing, real performance testing, real QA testing.

you cannot test on an empty database.
you cannot test on a database with 10% of production

and get many "meaningful data points" from it.  You can test functionality, but
not performance -- not scale.

      Rating: 5
what is the
      percentage of rows   December 04, 2003
Reviewer:
      A reader
Hi

If we have 1000000 rows, at how much percent Oracle would decide to do a full
table scan? I read somewhere (cant find it now) that if Oracle has to return
more than 10 percent of rows it would go FTS even there are indexes

Is it true?

          Followup:

it is not a %, it is not true.

for the RBO, it would use an index if it can - at all costs it would (stupidly)
use an index.

for the CBO, it is a matter of "how many LIO's will i have to do in order to
process the query".  It is a function (among many things) of the clustering
factor.

Consider these two tables:

ops$tkyte@ORA920PC> create table orderly
  2  as
  3  select *
  4    from all_objects
  5   order by object_id;

Table created.

ops$tkyte@ORA920PC> create index orderly_idx on orderly(object_id);

Index created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> analyze table orderly compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;

Table analyzed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create table disorganized
  2  as
  3  select *
  4    from orderly
  5   order by reverse(object_id);

Table created.

ops$tkyte@ORA920PC> create index disorganized_idx on disorganized(object_id);

Index created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> analyze table disorganized compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;

Table analyzed.

same exact amount of data -- down to the last bit.  Should behave the same
right?

wrong.  the first table just happens to be "sorted" by object_id (so a range
scan on the index will hit the minimum number of blocks).  the second is
randomly organized (so a range scan on the index will hit many many blocks)

the clustering factor computed for each is:

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select a.table_name, b.index_name, a.blocks,
b.CLUSTERING_FACTOR
  2    from user_tables a, user_indexes b
  3   where a.table_name in ( 'ORDERLY', 'DISORGANIZED' )
  4     and a.table_name = b.table_name
  5  /

TABLE_NAME        INDEX_NAME                         BLOCKS CLUSTERING_FACTOR
----------------- ------------------------------ ---------- -----------------
DISORGANIZED      DISORGANIZED_IDX                      429             30240
ORDERLY           ORDERLY_IDX                           429               413

orderly has a cluster factor near the NUMBER OF BLOCKS (that means an index
range scan will hit as few table blocks as possible -- the data in the table
just happens to be sorted by the index key)

disorganized has a cluster factor near the number of rows in the table (that
means an index range scan will hit as many blocks as possible -- each row in the
index points to a block different from the row in front and behind it)

Optimizer says:

ops$tkyte@ORA920PC> set autotrace traceonly
ops$tkyte@ORA920PC> set pagesize 900
ops$tkyte@ORA920PC> set pause on
ops$tkyte@ORA920PC> select *
  2    from orderly
  3   where object_id between 10000 and 12500
  4  /

2501 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=42 Card=2495 Bytes=239520)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'ORDERLY' (Cost=42 Card=2495
Bytes=239520)
   2    1     INDEX (RANGE SCAN) OF 'ORDERLY_IDX' (NON-UNIQUE) (Cost=7
Card=2495)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        375  consistent gets
         36  physical reads
          0  redo size
     172161  bytes sent via SQL*Net to client
       2325  bytes received via SQL*Net from client
        168  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       2501  rows processed

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select *
  2    from disorganized
  3   where object_id between 10000 and 12500
  4  /

2501 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=43 Card=2495 Bytes=239520)
   1    0   TABLE ACCESS (FULL) OF 'DISORGANIZED' (Cost=43 Card=2495
Bytes=239520)

Statistics
----------------------------------------------------------
        242  recursive calls
          0  db block gets
        605  consistent gets
         44  physical reads
          0  redo size
     209302  bytes sent via SQL*Net to client
       2325  bytes received via SQL*Net from client
        168  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       2501  rows processed

 range scan for one and full scan for the other -- note the index scan seems
more efficient -- used less consistent gets, but...

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select /*+ index( disorganized disorganized_idx ) */ *
  2    from disorganized
  3   where object_id between 10000 and 12500
  4  /

2501 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2503 Card=2495 Bytes=239520)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'DISORGANIZED' (Cost=2503 Card=2495
Bytes=239520)
   2    1     INDEX (RANGE SCAN) OF 'DISORGANIZED_IDX' (NON-UNIQUE) (Cost=7
Card=2495)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       2675  consistent gets
         33  physical reads
          0  redo size
     172161  bytes sent via SQL*Net to client
       2325  bytes received via SQL*Net from client
        168  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       2501  rows processed

we can see how much less efficient in this case the index is... for THIS
table

      Rating: 3
Different
      Plan  December 04, 2003
Reviewer:
      A reader  from NJ, USA
Tom,

I created the sample tables I1,I2,MAP on my development environment using the
same scripts given in the discussion. Also, I populated I1 with around 25000
rows and no rows in I2 and MAP. But the execution plan was different.

The following was the execution plan :

  Nested Loops
    Nested Loops
      Table Access FULL MAP
      Table Access by rowid I2
          Index Unique scan
    Table Access by rowid I1
       Index unique scan

It did not do a Full table scan on I1 and did not start with I1 as you
explained. Is this because of the data? If the tables are ANALYZED will it get
the execution path you mentioned.
2. Is the CBO execution plan dependent on data volumes and data distribution. If
so, how do we write a query that can run efficiently in both production and
development environments. What should we do to run efficiently. ie. What to look
for in a query so that if I run it on development environment (low data volume),
so that the same query will run efficiently with large data volumes.
development, it will run efficiently

          Followup:

you got the same plan as the original questioner did (and didn't want).

I did not explain that it would start with I1, i was "hypothesizing that if it
did in fact start with I1 this is what would happen and it would be *bad*"

      Rating: 4
Clustering
      factor not considered. Why?  December 05, 2003

Reviewer:  A reader  from NJ, USA
Tom,

I have a table TRADE that has a VARCHAR2(15) column TICKET_NUMBER which has a
nonunique index TRADE_IDX1. Here are the statistics from the table :

Total Rows : 2179860
Clus factor for TRADE_IDX1 : 1845610
Total Table Blocks : 215187
Optimizer Feature : 9.2

When I do a query something like SELECT * FROM TRADE
WHERE TICKET_NUMBER LIKE '2003%', it has about 1702348 rows. I expected the
EXPLAIN PLAN to show TABLE ACCESS FULL because you are retreiving a large
percentage of rows and that the clustering factor is almost equal to the number
of rows. But the EXPLAIN PLAN showed a INDEX RANGE SCAN TRADE_IDX1 followed by a
TABLE ACCESS BY ROWID which will not be very efficient in this case. Why does
the optimizer generate this plan? Also, per your books, you suggested that it
would be better to do a full scan in such circumstances since the index approach
will involve more logical I/Os compared to full scan which can make use of
db_file_multiblock_read_count parameter to read as many blocks in one I/O. Also,
when I force the OPTIMIZER to do a FULL scan, the query runs very slow. Has
something changed in 9i mode. I saw this happening in some other tables in my
database. Pls. help.

          Followup:

hows about showing us the ESTIMATED CARDINALITY that would be in the explain
plan as well -- that'll tell us why.

but here I'm confused

a) optimizer uses indexes
b) force to use full scan and find much slower

???  umm, maybe it is doing the right thing for you already??

      Rating: 3
Why this
      happens  December 06, 2003
Reviewer:
      A reader  from NJ, USA
Tom,

Per your book, the CBO will automatically perform a FTS when the tables are
properly analyzed and the clustering factor is equal to number of rows and you
select a significant proportion of rows (in this case almost 80% of rows are
being selected). Why in the above case the CBO performs an index-range scan
followed by a table-access by ROWID instead of full-table scan. I have read
similar examples in your book where you show difference between colocated and
disorganized and in that discussion you discuss about looking at the
CLUSTERING_FACTOR. Why is it different in the above case?
2. Suppose you create a non-unique index on a unique column and do a equality
check. Will Oracle still perform an index range scan?

          Followup:

1) hows about you provide the requested information (autotrace traceonly
explain).  Then, then and only then, can we comment.

2) yes. (easy enough to test no?)

      Rating: 3
ESTIMATED
      CARDINALITY  December 08, 2003
Reviewer:
      A reader  from NJ, USA
Tom,

In the above response, you had requested for ESTIMATED CARDINALITY in the
EXPLAIN PLAN. Where should I look for that requested info. Is it Card=....

          Followup:

just cut and paste the output of an autotrace traceonly explain

yes, it is the CARD= part

      Rating: 2
Hash
      joins  December 09, 2003
Reviewer:  kit
       from england
I did place a question on hash join and you asked for more examples. As i was
browsing this area, you covered some of the points I wanted to ask

please see below

select *
  2   from
  3         i1,
  4         map,
  5         i2
  6   where     i1.n = map.i1
  7   and i2.n = map.i2
  8   and i1.v = 'x'
  9   and i2.v = 'y';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=13 Card=1 Bytes=40)
   1    0   NESTED LOOPS (Cost=13 Card=1 Bytes=40)
   2    1     HASH JOIN (Cost=12 Card=1 Bytes=26)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'I1' (Cost=2 Card=1 Bytes=14)
   4    3         INDEX (RANGE SCAN) OF 'I1_IDX' (NON-UNIQUE) (Cost=1 Card=1)
   5    2       TABLE ACCESS (FULL) OF 'MAP' (Cost=9 Card=30020 Bytes=360240)
   6    1     TABLE ACCESS (BY INDEX ROWID) OF 'I2' (Cost=1 Card=1 Bytes=14)
   7    6       INDEX (UNIQUE SCAN) OF 'SYS_C003755' (UNIQUE)

re the above plan

my understanding is

    Hash table Map on column - i2 and create temp sort area temp_a

    Index scan on table I1    -

     As each row is found, the column n from table i1 is hashed and looks up
temp_a to get selected columns from Map table

     The rows that match

         for each row

         scan unique index on table I2

   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=21 Card=1 Bytes=40)
   1    0   NESTED LOOPS (Cost=21 Card=1 Bytes=40)
   2    1     HASH JOIN (Cost=20 Card=1 Bytes=26)
   3    2       TABLE ACCESS (FULL) OF 'I1' (Cost=10 Card=1 Bytes=14)
   4    2       TABLE ACCESS (FULL) OF 'MAP' (Cost=9 Card=30020 Bytes=360240)
   5    1     TABLE ACCESS (BY INDEX ROWID) OF 'I2' (Cost=1 Card=1 Bytes=14)
   6    5       INDEX (UNIQUE SCAN) OF 'SYS_C003755' (UNIQUE)

For the plan B above
    Perform full scan on I1 and Map

    I believe oracle then splits this into many partitions as possible. Is this
based on the join columns
    i2.n = map.i2. How does it partition, set number of rows etc ?. What memory
is it checking for to see how many partitions
    it can make

    How does it choose which one to hash

    could you please give an example to explain the below two statements from
oracle

    2. Oracle builds a hash table from one of the partitions (if possible,
Oracle
    selects a partition that fits into available memory). Oracle then uses the
    corresponding partition in the other table to probe the hash table. All
    partition pairs that do not fit into memory are placed onto disk.

    3. For each pair of partitions (one from each table), Oracle uses the
smaller
    one to build a hash table and the larger one to probe the hash table.

thanks
kit

          Followup:

1) if you want to understand "which one to hash" i go over the ins and outs of
that in my book 'effective oracle by design'

it trys to hash the smaller of the two.

2) not a question
3) not a question

      Rating: 1
avoiding full
      table scans  December 10, 2003
Reviewer:
      kit  from england
Hi Tom,
On question review it indicates my latest question has been
answered but I cannot see it.I would be obliged if you could confirm or correct
my understanding

          Followup:

where do you see that?  anyway.  I don't see each and ever review every time.  i
browse what I can but not all of them.

      Rating: 2
how to avoid
      full table scans  December 11, 2003
Reviewer:
      kit  from england
Thanks tom but can you please confirm my understanding is correct

    Hash table Map on column - i2 and create temp sort area temp_a

    Index scan on table I1    -

     As each row is found, the column n from table i1 is hashed and looks up
temp_a to get selected columns from Map table

     The rows that match

         for each row

         scan unique index on table I2

 on the first plan
and

For the plan B above
    Perform full scan on I1 and Map

    I believe oracle then splits this into many partitions as possible. Is this
based on the join columns
    i2.n = map.i2. How does it partition, set number of rows etc ?.

Questions

(1)What memory is it checking for to see how many partitions    it can make

(2) How does it choose which one to hash
(3)can you please explain what is meant by the below two statments from oracle

A. Oracle builds a hash table from one of the partitions (if possible,
Oracle
    selects a partition that fits into available memory). Oracle then uses the
    corresponding partition in the other table to probe the hash table. All
    partition pairs that do not fit into memory are placed onto disk.

    B. For each pair of partitions (one from each table), Oracle uses the
smaller
    one to build a hash table and the larger one to probe the hash table.

thanks
Kit

          Followup:

   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=13 Card=1 Bytes=40)
   1    0   NESTED LOOPS (Cost=13 Card=1 Bytes=40)
   2    1     HASH JOIN (Cost=12 Card=1 Bytes=26)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'I1' (Cost=2 Card=1 Bytes=14)
   4    3         INDEX (RANGE SCAN) OF 'I1_IDX' (NON-UNIQUE) (Cost=1 Card=1)
   5    2       TABLE ACCESS (FULL) OF 'MAP' (Cost=9 Card=30020 Bytes=360240)
   6    1     TABLE ACCESS (BY INDEX ROWID) OF 'I2' (Cost=1 Card=1 Bytes=14)
   7    6       INDEX (UNIQUE SCAN) OF 'SYS_C003755' (UNIQUE)

psuedo code for above:

   find rows in I1 using index
   create hash table on I1

   for x in ( select * from map )
   loop
       hash column in map row, look up in hash_table_I1
       if found
       then
          look up row in I2
          if found
          then
              OUTPUT ROW
          end if
       end if
   end loop

hash joins want to take the smallest table (result) and hash it.  It'll reorder
them from the plan and hash the smaller of the two.

   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=21 Card=1 Bytes=40)
   1    0   NESTED LOOPS (Cost=21 Card=1 Bytes=40)
   2    1     HASH JOIN (Cost=20 Card=1 Bytes=26)
   3    2       TABLE ACCESS (FULL) OF 'I1' (Cost=10 Card=1 Bytes=14)
   4    2       TABLE ACCESS (FULL) OF 'MAP' (Cost=9 Card=30020 Bytes=360240)
   5    1     TABLE ACCESS (BY INDEX ROWID) OF 'I2' (Cost=1 Card=1 Bytes=14)
   6    5       INDEX (UNIQUE SCAN) OF 'SYS_C003755' (UNIQUE)

psuedo code for that

   find rows in I1 using full scan
   create hash table on I1

   for x in ( select * from map )
   loop
       hash column in map row, look up in hash_table_I1
       if found
       then
          look up row in I2
          if found
          then
              OUTPUT ROW
          end if
       end if
   end loop

oracle uses hash_area_size in 8i -- what it gets allocated from
pga_aggregate_target in 9i -- to determine the partition size.

it wants to hash which ever segment it thinks will be SMALLER

the hash join algorithm is complex -- I've documented it fully in my book if you
are interested (with lots of examples).  It is too complex to fit here.  Here is
a short excerpt from the book on the "when it's too big to fit in memory" issue:

....
The processing is a little different if the hash table being developed does not
fit entirely in memory. In that case, Oracle will use TEMP space to hold
portions (partitions) of the hash table and perhaps of the table that will be
used to probe this hash table. When the hash area fills up and Oracle needs to
use TEMP, it will find the largest partition within the hash table and write
that out to TEMP. (Oracle actually uses a two-part hash table: there is a
partition, or section, of the hash table a row belongs to and then a position in
that partition.) Any new row retrieved that belongs to that on-disk partition
will modify the on-disk image to contain it.

This process continues until the entire hash table is built, with part of it in
memory and part of it on disk. Then Oracle begins reading from the other source.
As before, the join key will be hashed in order to look up the other value. If
the hashed value points to a row in memory, the join is completed and the row is
returned from the join. If the row points to a hash partition on disk, Oracle
will store this row in TEMP, using the same partitioning scheme it did for the
original source data. So, when the first pass is done-all of the rows that could
be joined to the in-memory hash table have been joined and some number of
partitions remain in TEMP-each partition will be joined only to rows in the
other corresponding partition. Oracle will then process each on-disk temporary
partition one by one, reading them into memory and joining them, resulting in
the final answer.

......

      Rating: 4
Avoid full
      table scans  December 15, 2003
Reviewer:
      kit  from england
thanks, I've read pg 468 - 470 many times now and some of its helping but still
do not understand

section talks about when when does not fit into memory

-read it few times, is there an example of explaining the text with examples

- what criteria to work out the partitions of the hash table

-under what circumstance will it put the table used to probe the hash table into
temp

- What is meant by  "using the same partitioning scheme it did for the
                     original source data" ?

- how can we tell best not use a hash, is it when more 70 % still in temp. how
can we tell when the hash table fits into memory

can you get scenarios as
    index scan I, index scan table B

    find the smaller result set and hash.

    For each bigger result set , hash its key and do a lookup

    would this be shown as

    hash join
        index Table I
        Index Table B

    Or would performance be bad and with a hash join of the table must be full
table scan

    eg

    Table 1 driving table - 215 M rows of which 170,000 rows selected
- not long
    Table 2                 26 M       Joined via unique index, stil 170 k rows
selected

    Table 3                      564 rows

                      still 170k

    Woud this be best via hash or NL. the query is in MV and issue about
returning 1st row back soon as possible is not an issue

Thanks for your patience

          Followup:

sorry -- but i guess I lose on this one.  I cannot explain it in less words then
I did in the book.

Basically -- there is a two part key to the hash (that is the partitioning
scheme, that is what I mean by "the same scheme").  So, when we hash something
-- it is put into a hash table in a particular partition.

If that hash table doesn't fit in ram -- we write out N-1 of the partitions
(keeping one in memory).

We finish hashing now.  as we add data to the hash table -- if the partition is
in memory -- it goes there.  All other stuff goes onto disk.

So, now we have this big hash table -- part in memory, part on disk.  We start
reading the other result set.  We look at the hash key and determine what
partition it goes with.  If it goes with the partition we have in memory -- we
output that row, else the row from the other result set we just started reading
goes to disk in an "analogous" partition -- meaning, all of the data to hit the
first partition we swapped out will be put into the same temp partition, all of
the data to hit the second partition of the hash table will be in the same temp
partition and so on.

When we finish with this first hash table partition -- we get rid of it and load
up the first swapped out one.  Now we read that first "temp partition" we just
created above and we know every row in there will be hashed to a row in the
partition we just loaded up.

and so on.

the "criteria" is just a hashing algorithm.  internal, cannot be influenced by
you.  a function of the join key.

Anytime part of the table used to probe the hash table tries to prode a
partition that is NOT IN RAM, that probing row goes to temp.  Waits till we load
the hash partition it wants into ram.

"We" do not want to make the judgement when best not to use hash joins.  "It"
(the CBO) has that job.

      Rating: 4
full table
      scan  December 16, 2003
Reviewer:  kit
       from england
hi, thanks again

slowly going in
however, please see below my assumptions

assumptions
============
partitioning is done internally, we cannot control how it partitions

if 10 partitions and does not fit into ram, then writes 9 paritions onto disk

Need more clarification
========================
Did not understand, could this be expanded

"We finish hashing now.  as we add data to the hash table -- if the partition is

in memory -- it goes there.  All other stuff goes onto disk."

- what data are we adding to the adding to the hash table once finished hasing

"all of the data to hit the
first partition we swapped out will be put into the same temp partition, all of
the data to hit the second partition of the hash table will be in the same temp
partition and so on."

- swapped out, is the ones that could not fit in memory initially, i.e n -1

what stats can we look at to see how much in ram and on disk

Previous questions
===================

did you get chance to look at

can you get scenarios as
    index scan I, index scan table B

    find the smaller result set and hash.

    For each bigger result set , hash its key and do a lookup

    would this be shown as

    hash join
        index Table I
        Index Table B

    Or would performance be bad and with a hash join of the table must be full
table scan
    eg
    Table 1 driving table - 215 M rows of which 170,000 rows selected

- not long
    Table 2                 26 M       Joined via unique index, stil 170 k rows
selected

    Table 3                      564 rows

                      still 170k

    Woud this be best via hash or NL. the query is in MV and issue about
returning 1st row back soon as possible is not an issue

Thanks for your time again

          Followup:

when i say "we finish hashing now" what I mean is:

we were hashing into ram.  we ran out of room.  we paused.  we wrote on n-1
partitions to disk. NOW we finish hashing -- reading the source to hash, hashing
and putting the hashed results into the right place.

In 9iR2, you can see the "swapping" in a tkprof, on the query plan lines (you'll
see a w=NNNNN showing the number of writes involved), also available in v$
tables (but as an aggregate for the query)

as for the last question -- i'll just keep reverting to "analyze, and ask the
CBO".  that is the answer.

      Rating: 4
merry xmas and
      happy new year to you  December 23, 2003
Reviewer:
      kit  from england
tom,

Thanks for your help, still trying to understand the hashing but for time have
an excellent xmas and new year

December 16, 2003 -- Thanks for the question regarding "Select for
      Update with MIN()", version 8.1.7

      You Asked
Hi Tom,

I have the following problem:

Table t (a NUMBER(10),b VARCHAR2(10),c VARCHAR2(10))

I need to:
 select MIN(A) from t
where b is not null
and c='XXX'
for update;

However I cant use for upate with MIN()(gives ORA-01786: FOR UPDATE of this
query expression is not allowed) , and I need to use this form of pessimistic
locking as the data held in the table peratins to transaction charges and we
dont wont to double chargs customers.

PLease could you give an example of a way around this problem.

Thanks

      and we said...
min(a) does not point to a single row..

consider

select min(object_type) from all_objects where status is not null and owner =
'SYS'

that -- that points to many rows.

So, how to do this?

  1  select *
  2    from emp
  3   where rowid = ( select rowid
  4                     from ( select rowid
  5                              from emp
  6                                        where job is not null
  7                                          and deptno = 10
  8                                                order by sal )
  9                              where rownum = 1 )
 10* for update
scott@ORA920LAP> /

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
----------
      7934 MILLER     CLERK           7782 23-JAN-82       1300
  10

is one approach -- here SAL = A, JOB = B, and deptno = C

there are others but basically, you are getting a subquery to find the exact row
you want to select for update....

Review &
      Followup

      Rating: 5
Select for
      Update with MIN()  September 10, 2003
Reviewer:
      A reader

      Rating: 4
Select for
      Update concepts  December 16, 2003
Reviewer:
      Kashif  from London
Tom,

I've had a bug in my code due to using a min with select for update.  Now I'm
using rownum=1 (which as you have mentioned selects 1 row), which has fixed my
problem.

However please could you explain the following to me:

CREATE TABLE t   (id   VARCHAR2(2) NOT NULL,  status    VARCHAR2(2));
ALTER TABLE t ADD CONSTRAINT t_pk PRIMARY KEY (id);
CREATE INDEX t_idx ON t (status  );

insert into t (id) values('a1');
insert into t (id) values('a2');
insert into t (id) values('a3');

In session 1:
select id from t
where  id=(SELECT MIN(id)FROM t WHERE status IS NULL)
for update;

ID
--
a1

Now repeat the same select for update in another session (session2)

session 1:
UPDATE t   SET status = 'X'   WHERE id = 'a1';
commit;

session2 returns:
ID
--
a1

Which is incorrect as t.status is now not null for this row???

Using rownum=1 fixes this
As you have already mentioned min() does not point to a single row, so dont use
it, but why does the select for update get back the wrong row, I thought that
the query is re-executed once the row is no longer locked?

          Followup:

does not happen for me -- are you sure you COMMITED the inserts?  (eg: is your
UPDATE actually updating anything!!!!)

ops$tkyte@ORA9IR2> CREATE TABLE t        (id   VARCHAR2(2) NOT NULL,  status
VARCHAR2(2));

Table created.

ops$tkyte@ORA9IR2> ALTER TABLE t ADD CONSTRAINT t_pk PRIMARY KEY (id);

Table altered.

ops$tkyte@ORA9IR2> CREATE INDEX t_idx ON t (status  );

Index created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t (id) values('a1');

1 row created.

ops$tkyte@ORA9IR2> insert into t (id) values('a2');

1 row created.

ops$tkyte@ORA9IR2> insert into t (id) values('a3');

1 row created.

ops$tkyte@ORA9IR2> commit;

Commit complete.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select id from t
  2  where  id=(SELECT MIN(id)FROM t WHERE status IS NULL)
  3  for update;

ID
--
a1

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2     pragma autonomous_transaction;
  3  begin
  4          update t set status = 'x';
  5      commit;
  6  end;
  7  /
declare
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
ORA-06512: at line 4

ops$tkyte@ORA9IR2>

if you run that block of code in your database, what do you see?

      Rating: 4
Select for
      Update concepts   December 16, 2003
Reviewer:
      Kashif  from london
I ran the block of code and got the same error as you.

I think I wasnt clear enough with my example, ill try again.  I've 2 sqlplus
sessions with prompts "session1>" and "session2>" respectively:

session1>CREATE TABLE t   (id   VARCHAR2(2) NOT NULL,  status    VARCHAR2(2));

Table created.

session1>ALTER TABLE t ADD CONSTRAINT t_pk PRIMARY KEY (id) ;

Table altered.

session1>CREATE INDEX t_idx ON t (status  );

Index created.

session1>insert into t (id) values('a1');

1 row created.

session1>insert into t (id) values('a2');

1 row created.

session1>insert into t (id) values('a3');

1 row created.

session1>commit;

Commit complete.

session1>select * from t;

ID ST
-- --
a1
a2
a3

3 rows selected.

--check from session2:
session2>select * from t;

ID ST
-- --
a1
a2
a3

3 rows selected.

--Now we have our baseline. Table t with 3 commited rows, lets start our
transactions:

session1>select id from t
  2  where  id=(SELECT MIN(id)FROM t WHERE status IS NULL)
  3  for update;

ID
--
a1

1 row selected.

session2>select id from t
  2  where  id=(SELECT MIN(id)FROM t WHERE status IS NULL)
  3  for update;

--session2 waits as expected

session1>UPDATE t   SET status = 'X'   WHERE id = 'a1' ;

1 row updated.

session1>select * from t;

ID ST
-- --
a1 X
a2
a3

3 rows selected.

session1>commit;

Commit complete.

session2>  --now the hung session returns:
ID
--
a1

1 row selected.

session2>select * from t;

ID ST
-- --
a1 X
a2
a3
--proves that the wrong row has been returned

--also
session2>select id from t
  2  where  id=(SELECT MIN(id)FROM t WHERE status IS NULL);

ID
--
a2

1 row selected.
--shows what session2 should have returned.

Hope this clears thing up.

As I've mentioned when I use :
select id from t where id =(SELECT id FROM t WHERE status IS NULL and rownum=1);
everything is OK, but what is going on with MIN()?

Thanks

          Followup:

search this site for "write consistency"

it is an interesting topic.

add "and status is null" to the outer query and see what happens then.

      Rating: 5
select for
      update concepts  December 18, 2003
Reviewer:
      Kashif  from London
Yep, I added "and status is null" to the outer query and it worked!.  I also
tried adding status to the cols being returned i.e select id,status from t
.......for update
and that works too.  I'm now totally baffled as to what is going on.

I'm off to read your articles on write consistency

Cheers

December 16, 2003 KK -- Thanks for the question regarding "Compile
      procedure automatically -- how to avoid cascading invalidations", version
      7.3.4 or later

      You Asked
I've two procedures:
A calls B to do something.  If I compile B, then A will
become invalid.  Can I have any setting in the database
in order to compile A automatically when B is compiled?

Thank you for your sincere help!

      and we said...
Don't use standalone procedures!  Use packages.  Packages break
the dependency chain.

If procedure A calls procedure B and B is "changed", then A is
directly affected and must be recompiled.  There is no "auto
recompile A when B changes" option -- although A will
recompile itself automatically the next time it is run.

On the other hand, If I create a package PKG_A with a procedure
A and a package PKG_B with a procedure B then PKG_A will be
dependent on PKG_B's specification.  I can compile and recompile
PKG_B's Body as often as I like without affecting PKG_A's
state.  As long as PKG_B's specification or interface does
not change -- dependent objects are not affected by the
recompilation of the body.

Here is a small example showing the differences:

ops$tkyte@8i> create or replace procedure B
  2  as
  3  begin
  4          null;
  5  end;
  6  /

Procedure created.

ops$tkyte@8i>
ops$tkyte@8i> create or replace procedure A
  2  as
  3  begin
  4          B;
  5  end;
  6  /

Procedure created.

So procedure A calls B.  One of the downsides to procedures,
in addition to the direct dependency, is that you have to create
them in the "right" order if you want everything valid in the
database after an install.  I had to create B before A.
Packages don't have this nuance either -- as shown below

ops$tkyte@8i> create or replace package pkg_a
  2  as
  3          procedure a;
  4  end;
  5  /

Package created.

ops$tkyte@8i> create or replace package pkg_b
  2  as
  3          procedure b;
  4  end;
  5  /

Package created.

Here I created the specs for the packages.  They are not
depenedent on each other and the specs can in fact be created in
any order

ops$tkyte@8i>
ops$tkyte@8i> create or replace package body pkg_a
  2  as
  3          procedure a
  4          is
  5          begin
  6                  pkg_b.b;
  7          end;
  8  end;
  9  /

Package body created.

ops$tkyte@8i> create or replace package body pkg_b
  2  as
  3          procedure b
  4          is
  5          begin
  6                  null;
  7          end;
  8  end;
  9  /

Package body created.

There I create the bodies -- I can create the bodies in any
order after the specs (in general).  PKG_A is dependent on
PKG_B's spec -- not its body -- so we don't have an ordering
problem...

ops$tkyte@8i>
ops$tkyte@8i> @invalid
ops$tkyte@8i> break on object_type skip 1
ops$tkyte@8i> column status format a10
ops$tkyte@8i> select object_type, object_name, status
  2  from user_objects
  3  where status = 'INVALID'
  4  order by object_type, object_name
  5  /

no rows selected

nothing is invalid.  Now, lets "reimplement" procedure B in
the standalone procedure and in the packaged procedure...

ops$tkyte@8i>
ops$tkyte@8i> create or replace procedure B
  2  as
  3  begin
  4          null;
  5  end;
  6  /

Procedure created.

ops$tkyte@8i>
ops$tkyte@8i> @invalid
ops$tkyte@8i> break on object_type skip 1
ops$tkyte@8i> column status format a10
ops$tkyte@8i> select object_type, object_name, status
  2  from user_objects
  3  where status = 'INVALID'
  4  order by object_type, object_name
  5  /

OBJECT_TYPE        OBJECT_NAME                    STATUS
------------------ ------------------------------ ----------
PROCEDURE          A                              INVALID

Standalone procedure A immediately goes invalid.  B was
changed and so A needs to be recompiled

ops$tkyte@8i>
ops$tkyte@8i> create or replace package body pkg_b
  2  as
  3          procedure b
  4          is
  5          begin
  6                  null;
  7          end;
  8  end;
  9  /

Package body created.

ops$tkyte@8i> @invalid
ops$tkyte@8i> break on object_type skip 1
ops$tkyte@8i> column status format a10
ops$tkyte@8i> select object_type, object_name, status
  2  from user_objects
  3  where status = 'INVALID'
  4  order by object_type, object_name
  5  /

OBJECT_TYPE        OBJECT_NAME                    STATUS
------------------ ------------------------------ ----------
PROCEDURE          A                              INVALID

ops$tkyte@8i>

PKG_A never goes invalid -- never needs to be recompiled since
only PKG_B's body was modified.  As long as the spec does not
change, PKG_A will remain valid

For this reason -- i urge people to only use packages for 'real'
code.  It can seriously improve performance in a database by
reducing or removing the amount of times things get compiled.
If you do everything in standalone procedures -- a simple fix to
one procedure might make your entire set of routines go invalid
(a calls b, c calls a, d calls c and so on.  if you update B, a
goes invalid -- when a recompiles c will -- then d and so on).
Packages can make it so that none of this happen.

Review &
      Followup

      Rating:
      5
  April 16, 2001
Reviewer:  Harshan
       from Cincinnati,OH, USA

      Rating:
      5
  July 02, 2001
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating: 5
Well
      explained  July 17, 2001
Reviewer:  Kavitha
       from Birmingham, United Kingdom
The explanation was very clear and most helpful. It gave the most important
information in the right order in the right manner.

Thanks a lot!

      Rating:
      5
  September 14, 2001
Reviewer:  Georgi
      Mechev  from Frankfurt, Germany

      Rating: 3
procedures or
      packages  September 25, 2001
Reviewer:  ajay
       from india
hi tom,

thanks for ur valuable suggestion its really a good to know such things in that
way.

ur explanation with eaxmple is good,

      Rating: 5
More
      information  June 26, 2002
Reviewer:  Paulo
      Motta  from Rio, Brazil
Does anyone have an additional bibliografy about this stuff ?

Thanks.

          Followup:

Huh??  I looked up that term:

Main Entry: bib·li·og·ra·phy
Pronunciation: "bi-blE-'ä-gr&-fE
Function: noun
Inflected Form(s): plural -phies
Etymology: probably from New Latin bibliographia, from Greek, the copying of
books, from bibli- + -graphia -graphy
Date: 1802
1 : the history, identification, or description of writings or publications
2 a : a list often with descriptive or critical notes of writings relating to a
particular subject, period, or author b : a list of works written by an author
or printed by a publishing house
3 : the works or a list of the works referred to in a text or consulted by the
author in its production

what exactly might you be looking for?

      Rating: 5
If I recompile
      Pcg, they are working after 2-cnd touch  June 26, 2002

Reviewer:  Jan  from Slovakia
How we can avoid this without reconnection? Our testing env. is complex and
sometimes I need to change some part of code.Ofter I recompile it and run in
some session which were using the older version of the code, it will not work,
only after 2-cnd calling, what makes data incosintent because only part of the
operation is done. How can I avoid that? That users will be not forced to
reconnect again.
Thanks,

          Followup:

You have to do the double touch on a package like that -- think about it.

The package maintains a state, your user started using it.  They have a state
established.

You come along and recompile the code -- change it.  You have (by definition)
wiped out their state.  It is gone.

If we did not raise an error, they would blindly go along thinking "everything
is OK", when in fact -- their session state was wiped out.  The program will be
working erroneously at that point.  They MUST retry the operation.

You need not "reconnect", you need to catch the error and retry the operation
AFTER acknowledging the fact that your state was wiped out.  That data
inconsistency problem you mention is EXACTLY the reason the error is raised!  We
give you the opportunity to deal with it (or not -- upto you).

      Rating: 3
order of the
      private procedures  October 30, 2002
Reviewer:
      Anil  from Hyderabad, India
Comment: This site and your book are really helping me master Oracle.Great help

Version: Oracle version 8.1.7
Problem: PLS-00201: identifier 'SP_Private_proc' must be declared
Explaination: In our package we had three private procedures which have been
ordered after all the public procedures have been coded in the package body - so
the compile failed. I have reordered the procedures and moved the private
procedures to the beginning of the package body it works fine.
Now my question is If in a package we have 50 private procedures and 50 public
procedures. Do we need to order the private procedures in the order they are
used.OR is there any other way of doing it.?

Thanks & Regards,
Anil

          Followup:

use a forward declare:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package demo_pkg
  2  as
  3          procedure public_proc;
  4  end;
  5  /

Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package body demo_pkg
  2  as
  3
  4  procedure private_proc;
  5
  6  procedure public_proc
  7  is
  8  begin
  9          private_proc;
 10  end;
 11
 12  procedure private_proc
 13  is
 14  begin
 15          null;
 16  end;
 17
 18  end;
 19  /

Package body created.

Although my convention is to put ALL private routines at the top and all public
routines at the bottom -- but the above works just as well.

      Rating: 5
Very clear
      explanation  October 31, 2002
Reviewer:
      Thiyagarajan  from Grand Rapids, MI USA

      Rating: 5
amusing
      yourself  October 31, 2002
Reviewer:  asktom
      fanatic  from Dayton, OH
I like how you amuse yourself at the expense of our non-english speaking
contributors.  Your response to the Brazilian surely befuddled him.

any plans for an asktom.grammar.com or asktom.spelling.com site?

          Followup:

Hey -- they confused me as much as they got confused I guess.  I really didn't
know what they were asking at all (and I do like the site:

http://www.m-w.com/netdict.htm
alot -- use it all of the time...)

I still don't know what they meant by "bibliografy"

      Rating: 4
Changing to
      signature can help for remote procedures  November 04, 2002

Reviewer:  cachitok@yahoo.com
Because when usign SIGNATURE ( instead TIMESTAMP )

If the timestamp had changed, but the signature (contains timestamp and
signature:name of construct, base type of parameters, modes of parameters,
number of paramters, etc.) had not changed, the local procedure can execute the
remote procedure, while using TIMESTAMP will raise an error.

Now the question I have is why Oracle suggest to use TIMESTAMP instead of
SIGNATURE

Thanks

          Followup:

where is that suggestion made -- which doc?  I want to see it in the context of
the surrounding text.

      Rating:
      3
  November 05, 2002
Reviewer:
      cachitok@yahoo.com
Where I got it:
TBT: Oracle PL/SQL DB Programming Unit 3 Lesson 2 Topic 4 Page 21.
"If A remote program is changed and recompiled , but the signature does not
change, then the local procedure can execute the remote procedure. Using
timestamp method, and error will be raised, as the timestamp would not match."

As the question is
A calls B to do something.  If I compile B, then A will
become invalid.  Can I have any setting in the database
in order to compile A automatically when B is compiled?

This tip helps to avoid the problem of getting an error on a remote procedure.

Did I said something wrong?

          Followup:

I don't know what "TBT" is or whatever but anyway -- my read of that says

"Use SIGNATURE to avoid the error that TIMESTAMP would through".  I do not read
it as you did -- I do NOT read it as saying "use timestamp, it is better".  I
read it as "use signature, it avoids the error that would otherwise happen due
to a recompilation of the remote object"

      Rating:
      5
Bibliografia  November 05, 2002
Reviewer:
      Jeremy Smith  from Salt Lake City, UT
I'm pretty sure that Paulo Motta is just asking for a list of books and/or
documentation that discusses the same type of information you explained to us,
i.e., how Oracle goes about deciding what it needs to
compile/recompile/invalidate.

          Followup:

Ahh -- ok (i asked him to clarify) anyway - -that would be:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/c19depnd.htm#369
(that pesky server concepts manual again!)

      Rating:
      4
  November 05, 2002
Reviewer:
      cachitok@yahoo.com
Oh, about that....

Really Sorry, I misunderstand what I read in the documentation,

This paragraph,
from "Application Developer’s Guide - Fundamentals"
answers the question.
"
Suggestions for Managing Dependencies
Oracle recommends that you follow these guidelines for setting the
REMOTE_DEPENDENCIES_MODE parameter:
n Server-side PL/SQL users can set the parameter to TIMESTAMP (or let it default
to that) to get the timestamp dependency mode.
n Server-side PL/SQL users can choose to use the signature dependency mode if
they have a distributed system and they want to avoid possible unnecessary
recompilations.
n Client-side PL/SQL users should set the parameter to SIGNATURE. This allows:
– Installation of new applications at client sites, without the need to
recompile procedures.
– Ability to upgrade the server, without encountering timestamp mismatches.
n When using signature mode on the server side, add new procedures to the end
of the procedure (or function) declarations in a package specification. Adding a
new procedure in the middle of the list of declarations can cause unnecessary
invalidation and recompilation of dependent procedures.
"

      Rating: 4
Object
      Dependency Checked Every Time Pkg is Called  December 02,
      2002
Reviewer:  Robert  from PA
Wow , I wasn't aware of this until it happened - someone dropped a table
referenced in a pkg while I was not around during the holiday...

And I thought dependency/references is VERIFIED ONLY at pkg compile time...but
now the whole process aborted because of
this...

LMS1@dns > exec tdi_aolw.aolw_test

Hi, this is tdi_aolw.aolw_test, I dont ref any thing !

PL/SQL procedure successfully completed.

/* EMP is NOT referenced by the above (aolw_test)
but by another pkg body program */

LMS1@dns > alter table emp rename to emp2;
Table altered.

LMS1@dns > exit
-- RECONNECT ----
LMS1@dns > exec tdi_aolw.aolw_test
BEGIN tdi_aolw.aolw_test; END;

*
ERROR at line 1:
ORA-04068: existing state of packages has been discarded
ORA-04063: package body "LMS.TDI_REPORT2" has errors
ORA-06508: PL/SQL: could not find program unit being called
ORA-06512: at "LMS.TDI_AOLW", line 747
ORA-06512: at line 1

Tom, can you give a little explanation why it appears Oracle
either recompiles(?) or re-validate ALL object references in the entire package
everytime any one of the pkg modules
is called,
Thanks

          Followup:

But everything did recompile -- it just failed.

EMP is used by LMS.TDI_REPORT2

LMS.TDI_REPORT2 is called by TDI_AOLW.AOLW_TEST

LMS.TDI_REPORT2 must be available in order to run TDI_AOLW.AOLW_TEST (it calls
it after all)

LMS.TDI_REPORT2 is not able to be compiled -- the table EMP it relies on has
gone missing.  When TDI_AOLW.AOLW_TEST tried to call it on line 747 -- it failed
(the code is *missing* and cannot be recompiled)

Consider this example:

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

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package lms
  2  as
  3          procedure tdi_report2;
  4  end;
  5  /
Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package body lms
  2  as
  3          procedure tdi_report2
  4          is
  5          begin
  6                  for x in ( select * from emp )
  7                  loop
  8                          null;
  9                  end loop;
 10          end;
 11  end;
 12  /
Package body created.

so, you have a packge body LMS that is dependent on EMP

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package tdi_aolw
  2  as
  3          procedure aolw_test;
  4          procedure aolw_test2;
  5  end;
  6  /

Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package body tdi_aolw
  2  as
  3          procedure aolw_test
  4          as
  5          begin
  6                  dbms_output.put_line( 'Hello World' );
  7                  lms.tdi_report2;
  8          end;
  9
 10          procedure aolw_test2
 11          as
 12          begin
 13                  dbms_output.put_line( 'Hello World' );
 14          end;
 15  end;
 16  /

Package body created.

and now a package that is dependent on the SPEC of LMS *and* the correct
functioning of the body.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> @invalid
ops$tkyte@ORA817DEV.US.ORACLE.COM> break on object_type skip 1
ops$tkyte@ORA817DEV.US.ORACLE.COM> column status format a10
ops$tkyte@ORA817DEV.US.ORACLE.COM> select object_type, object_name, status
  2  from user_objects
  3  where status = 'INVALID'
  4  order by object_type, object_name
  5  /

no rows selected

all is valid...

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec tdi_aolw.aolw_test2
Hello World

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec tdi_aolw.aolw_test
Hello World

PL/SQL procedure successfully completed.

and every works but...

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> @invalid
ops$tkyte@ORA817DEV.US.ORACLE.COM> break on object_type skip 1
ops$tkyte@ORA817DEV.US.ORACLE.COM> column status format a10
ops$tkyte@ORA817DEV.US.ORACLE.COM> select object_type, object_name, status
  2  from user_objects
  3  where status = 'INVALID'
  4  order by object_type, object_name
  5  /

OBJECT_TYPE        OBJECT_NAME                    STATUS
------------------ ------------------------------ ----------
PACKAGE BODY       LMS                            INVALID

we dropped emp (you renamed it, same thing) and the body of LMS went invalid
and cannot be recompiled

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec tdi_aolw.aolw_test2
Hello World

PL/SQL procedure successfully completed.

that works -- TDI_AOLW is dependent on the spec of LMS, the spec is valid, we
didn't actually CALL LMS so no error.  But

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec tdi_aolw.aolw_test
BEGIN tdi_aolw.aolw_test; END;

*
ERROR at line 1:
ORA-04068: existing state of packages has been discarded
ORA-04063: package body "OPS$TKYTE.LMS" has errors
ORA-06508: PL/SQL: could not find program unit being called
ORA-06512: at "OPS$TKYTE.TDI_AOLW", line 7
ORA-06512: at line 1

that of course MUST fail since LMS is invalid and cannot possibly recompile
itself, parts of what it needs have gone missing

      Rating: 4
Right but if I
      call a pkg module that depends on nothing,
      nada....  December 02, 2002
Reviewer:
      Robert  from PA
>> that of course MUST fail since LMS is invalid and cannot >> possibly
recompile
>> itself, parts of what it needs have gone missing

Right, but in your example Procedure aolw_test
calls a module which "really, really" depends on the object from the get-go.
Oracle complains when the obj is not there, that's reasonable...

What caught me off-guard is the fact that even if
I call a single module in LMS that depends on NOTHING, Oracle errrors out on me
, that is --- the entire LMS pkg is unusable if *any* table referenced
*anywhere* in the pkg is missing, this sums it up right ?
Thanks

          Followup:

yes, the entire LMS package body either

o can compile
o cannot compile

the dependency is at the OBJECT level and the object here is the package body
itself.

Individual routines in LMS are not compiled -- LMS is the compilation unit.  It
either entirely compiles, or as in your case -- NOT.

      Rating: 3
Compile
      procedure automatically  December 02, 2002

Reviewer:  Steve Booth  from Waukesha, WI USA

I've been using Packages but am having a situation where the code needs to be
recompiled (doesn't seem to happen automatically) when a partition is dropped or
added. Any ideas?

TIA

          Followup:

got an example?

      Rating:
      5
packages  January 03, 2003
Reviewer:
      mo
Tom:

IF you have a web application with 100 stored procedures and does not use
packages and you are using SQL Navigator to create those:

1.  How do you decide how many packages to create.  Would you create one package
per one procedure or you would bundle several procedures into one package or it
does not really make a difference.

2.  If procedures do not call each other would it still make sense to use
packages since you have not listed another reason for the advantages other than
the "invalidation" and "recompilation".

3. If I create the packages, then you would be editing the code of package body
and not touch the procedure code at all listed under Procedures in navigator?

4. Do you use any naming convention for name of package. i.e same as procedure
PKG_PROC_NAME.

Thank you,

          Followup:

1) use common sense, that is all.  group related functions together logically.
If it boils down to 1 procedure/package -- so be it (in fact, you would probably
find that your procedures are larger then a screen, that is you have to page
up/down through them.  If so - use the fact you are in a package to MODULARIZE
your code -- break it into several private routines inside the package body to
make it more readable, more maintainable).

2) packages are good.  packages have many features -- you would benefit from
having those private procedures so you wouldn't be afraid to go crazy and write
a couple of subroutine to break an otherwise monolithic routine into several
smaller routines -- something you would be hesitant to do if you use standalone
procedures

3) i use sqlplus.  i do not use "navigator".  i have no idea what I would do in
navigator.

4) something_pkg  where something represents what the package is all about.

      Rating: 5
Same or
      different package  January 06, 2003
Reviewer:
      sean  from NJ, USA
Hi Tom,

I have an application which has about 80 stored procedures in one schema without
using packages.  Now I am going to add package to each stored procedure.

Some of the stored procedure are related, such as A calls B and C, and C calls
D.  Do I have to put these procedures in one package?

It will be nice if you can give me some examples of procedures using scott
schema and then put them in different or same packages according to the
principles you defined.

Thanks,

Best,

Sean

          Followup:

You don't have to -- you would put procedures together in a package because they
are RELATED something -- eg: p1 inserts into T, p2 updates T, p3 deletes from T
-- p1, p2, p3 should probably be together.

Common sense rules here -- do what feels right, group them together based on
functionality.  Take advantage of the fact that they are in a package.  Many
times I see procedures that are bigger then I would have a package be!  Take the
time not only to put into a package but to modularize the existing code.  if you
have a stored procedure more then 60-80 lines long - break it up into many
smaller procedures.  put them all in a package -- you only have to "export" the
definition of the one procedure the rest can (should) remain private

      Rating:
      5
  January 07, 2003
Reviewer:
      cachitok@yahoo.com
If you have not too much experience letme giveme some suggestions to avoid
problems:

If you are going to group as much procedures
I suggest you that clearly in the package body set the limit between procedures.
for example
---------------------------------------------
**********************************************
---------------------------------------------

Don't group too much procedures in only one package if you are modifying
frequently. Debugging could be some difficult.

Remember that everywhere you call them, you must change the calling name. Test
every thing.

Don't forget you can use share private and global variables in the package. that
at the end of the package you can initialize variables, etc. That could help to
optimize and simplify your process, check the documentation.
Packages are really excellent but you need to take the time to learn about it.

      Rating: 4
Most of the
      problems Solved  March 14, 2003
Reviewer:  Ashok
      Shinde  from India
Tom,

    Following your suggestion we modified most the code so that we are no longer
dependent on the procedure compilation resulting in others becoming invalid. But
we have a package body with is called from the web server. This package body
becomes invalid (leaving package specifications valid) whenever it is called
from web server. Once we bounce and restart the web server the body becomes
valid. We are not able to trace why this package body becomes invalid. This
package refers some remote database tables which have select privileges granted
to public.

   Can you please explain me what would cause this package body to become
invalid ? And why it is not compiled whenever the any procedure in the body is
called ?

Regards,
Ashok Shinde.

          Followup:

look in dba_dependencies, see what it is dependent on.

      Rating: 5
State of the
      package when auto compile  August 29, 2003

Reviewer:  A reader
Tom,

Your test case in response to Robert  from PA .

I slightly changed the package to add a state to it.

create or replace package lms as
                        procedure tdi_report2;
       y number(2):=0;
       end;

Package created.

create or replace package body lms
    as
            procedure tdi_report2
            is
            begin
                    for x in ( select * from T1 )
                    loop
                            null;
                    end loop;
      y:=y+1;

           end;
          end;

In session-2:

SQL> set serveroutput on size 100000
SQL> exec tdi_aolw.aolw_test2;
Hello World

PL/SQL procedure successfully completed.

SQL> exec tdi_aolw.aolw_test;
Hello World

PL/SQL procedure successfully completed.

In session-1:

SQL> alter table t1 add partition p2 values less than (to_date('01-feb-2003','dd
-mon-yyyy'));

Table altered.

In Session-2:

SQL> select object_type, object_name, status
    from user_objects
    where status = 'INVALID'
    order by object_type, object_name;
  2    3    4  PACKAGE BODY
LMS
INVALID

SQL> exec tdi_aolw.aolw_test;
Hello World

PL/SQL procedure successfully completed.

SQL> exec tdi_aolw.aolw_test2;
Hello World   ---> No Error Here.

However:

When i do the below:

if in session-1:

SQL> alter table t1 drop partition p2;

Table altered.

SQL> alter package lms compile body; --> The state of the package is discarded.

Package body altered.

Then when running Session -2:

SQL> exec tdi_aolw.aolw_test;
BEGIN tdi_aolw.aolw_test; END;

*
ERROR at line 1:
ORA-04068: existing state of packages has been discarded
ORA-04061: existing state of package body "SCOTT.LMS" has been invalidated
ORA-04065: not executed, altered or dropped package body "SCOTT.LMS"
ORA-06508: PL/SQL: could not find program unit being called
ORA-06512: at "SCOTT.TDI_AOLW", line 7
ORA-06512: at line 1

SQL> exec tdi_aolw.aolw_test2;

PL/SQL procedure successfully completed.

Why do we not get 4068 error in case-1? What is the state of global variable "y"
in case-1?

Thanks,

          Followup:

me thinks you did something wrong....

the alter add did not invalidate for me.

the alter drop did

your names don't match up, incomplete test case -- show us the "real" stuff.

      Rating: 5
Complete
      case:  August 29, 2003
Reviewer:
      A reader
Tom,

SQL> create table t1(id number, dt1 date)
  2   partition by range(dt1)
  3  (partition p1 values less than (to_date('1-jan-2003','dd-mon-yyyy')),
  4  partition p2 values less than (to_date('2-feb-2003','dd-mon-yyyy')));

  create or replace package lms as
                        procedure tdi_report2;
       y number(2):=0;
       end;

create or replace package body lms
    as
            procedure tdi_report2
            is
            begin
                    for x in ( select * from T1 )
                    loop
                            null;
                    end loop;
      y:=y+1;

           end;
          end;

create or replace package tdi_aolw
    as
            procedure aolw_test;
            procedure aolw_test2;
    end;

create or replace package body tdi_aolw
    as
            procedure aolw_test
            as
            begin
                    dbms_output.put_line( 'Hello World' );
                    lms.tdi_report2;
            end;

           procedure aolw_test2
           as
           begin
                   dbms_output.put_line( 'Hello World' );
           end;
   end;

Session-1:

SQL> select object_type, object_name, status
    from user_objects
    where status = 'INVALID'
    order by object_type, object_name;
  2    3    4
no rows selected

Session-2:

SQL> select object_type, object_name, status
    from user_objects
    where status = 'INVALID'
    order by object_type, object_name;
  2    3    4
no rows selected

Elapsed: 00:00:00.01

Session-1:

SQL> alter table t1 add partition p2 values less than (to_date('01-feb-2003','dd
-mon-yyyy'));

Table altered.

Elapsed: 00:00:00.11

Session-2:

SQL> /
PACKAGE BODY
LMS
INVALID

Elapsed: 00:00:00.00

In Session-2:

SQL> exec tdi_aolw.aolw_test;
Hello World

PL/SQL procedure successfully completed.

SQL> exec tdi_aolw.aolw_test2;
Hello World   ---> No Error Here.

However if i compile body i get 4068 error.

I am doing something wrong here.

Thanks in advance.

          Followup:

so, the package didn't have a state yet?  no problem.

      Rating: 5
RE: Alter table
      add partition...  August 29, 2003
Reviewer:
      Mark A. Williams  from Indianapolis, IN USA
How are you adding the table partition?  Why is it named the same as an existing
partition?

SQL> alter table t1 add partition p2 values less than
(to_date('01-feb-2003','dd-mon-yyyy'));
alter table t1 add partition p2 values less than
(to_date('01-feb-2003','dd-mon-yyyy'))
                             *
ERROR at line 1:
ORA-14074: partition bound must collate higher than that of the last partition

Just curious...

- Mark

      Rating: 3
What is a
      package state?  August 29, 2003
Reviewer:
      A reader
Tom,

What is a package state? What defines a package state? when will the package
state change?

Mark,

I had a step before my test where i dropped the partition p2.

Thanks,

          Followup:

 create or replace package lms as
                        procedure tdi_report2;
       y number(2):=0;  <<<<=== package state, global variable

       end;

      Rating: 5
changing
      repository on a database  October 17, 2003

Reviewer:  A reader
Hi Tom
I am going to recreate schema and pl/sql packages
related to a very small independent functionality in a production
database (minor downtimes are ok). The middletier
uses one of these packages to get the data (the middle
tier code does not change as we don;t change
the "select clause" in the package used by middle tier.
My question is:
How do I avoid the existing state problem in the
package? Can I exercise the middle tier code twice
(that means visit the page twice) for it to start
working. I dont want to bounce the app server on
which the middle tier runs.

          Followup:

depends on how robust the middle tier is at handling errors -- and in general,
no, visting the page 2 times won't fix it as you have to hit EACH cached
connection.

If you hit this error with a middle tier app, you probably have bigger bugs to
fry anyway.  that means your connection pool is actually maintaining a connected
state and NOT reseting it in the database!  that means you can grab a connect
that has "leftovers" from my previous connection.  not a very good thing.

      Rating: 5
thanx
      Tom!  October 20, 2003
Reviewer:
      A reader
"depends on how robust the middle tier is at handling errors -- and in general,
no, visting the page 2 times won't fix it as you have to hit EACH cached
connection."

Excellent point! However, how does the middle tier
handle this error - should it catch the error and
issue "Recompile" of the packages - that would be
too tedious, no?

"If you hit this error with a middle tier app, you probably have bigger bugs to
fry anyway"

Also what did you mean by the above point.
Do you mean that the middle tier should work
flawlessly regardless of if I change the pl/sql
package in without bouncing the app server?
I think perhaps you are advocating a "stateless"
middle tier?

          Followup:

it doesn't have to "recompile the packages", it just must understand that "the
package it was using, which maintained a state, has had the state destroyed".
It is up to the application (middle tier) to decide if

a) this is OK
b) this is fatal

If you have a connection pool -- you DARN SURE BETTER have a stateless middle
tier as far as the database is concerned.  You

a) grab connection
b) make a page
c) give up connection
d) goto a) when user does something

each time you goto a) you are grabbing a new connection.  If you maintain a
state in that database connection -- you have really unreliable behaviour as
you'll get a DIFFERENT state potentially each time, worse -- someone else will
get YOUR state!

(and yes, I advocate a stateless middle tier regardless, I never maintain a
state in the middle tier)

      Rating: 5
thanx
      Tom!!  October 20, 2003
Reviewer:
      A reader

      Rating: 4
In 9i Release 2
      : Removes double invalidations ??  October 20, 2003

Reviewer:  Raj.N
What is Double invalidation?.Not able to find more information about this
statement.I hope my question is related to this thread.Can you please provide
little more explainations.

I am refering to whitepaper :
http://otn.oracle.com/products/oracle9i/pdf/9idb_rel2_features.pdf
Speed up of application and database upgrades

"provides fast loading of wrapped source, knows to do nothing upon loading an
unchanged package ,
view or synonym, removes double invalidations and does parallel compilation in
dependency order."

          Followup:

it is just saying "we can, during upgrades, detect that stuff didn't really
change and avoid tons and tons of work".

that is all -- it just knows that something that was recompiled didn't really
change and skips tons of work.

      Rating: 4
No implied
      dependences b/w package specs?  October 27, 2003

Reviewer:  Dan Loomis  from Raleigh, NC
Tom - we use packages here exclusively for the many benefits they provide over
standalone functions/procedures.  One such benefit is they break the dependency
chain, but we do have one situation where that is not the case.

13:49:32 apps@CTSGLD> create or replace package test_constants as
13:52:55   2
13:52:55   3  g_some_value CONSTANT varchar2(10) := 'SOME VALUE';
13:52:55   4
13:52:55   5  end;
13:52:55   6  /

Package created.

13:52:55 apps@CTSGLD>
13:52:55 apps@CTSGLD> create or replace package test_package as
13:52:55   2
13:52:55   3  procedure test (
13:52:55   4    p_input IN VARCHAR2 DEFAULT test_constants.g_some_value
13:52:55   5  );
13:52:55   6
13:52:55   7  end;
13:52:55   8  /

Package created.

Now if I drop the first package spec, the other immediately is invalidated, for
obvious reasons.  We leverage package specs for constants all the time, so the
dependency issue still exists for us.

          Followup:

that is a direct dependency.  If you did something like this:

ops$tkyte@ORA920> create or replace package test_constants
  2  as
  3          function get_str_val( p_name in varchar2 ) return varchar2;
  4  end;
  5  /

Package created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package body test_constants
  2  as
  3          type array is table of varchar2(255) index by varchar2(30);
  4          g_data array;
  5
  6          function get_str_val( p_name in varchar2 ) return varchar2
  7          is
  8          begin
  9                  return g_data(p_name);
 10          end;
 11
 12  begin
 13          g_data( 'SOME_VALUE' ) := 'Some Value';
 14  end;
 15  /

Package body created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package test_pkg
  2  as
  3
  4          procedure test( p_input in varchar2
  5             default test_constants.get_str_val( 'SOME_VALUE' ) );
  6
  7  end;
  8  /

Package created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package body test_pkg
  2  as
  3
  4          procedure test( p_input in varchar2
  5             default test_constants.get_str_val( 'SOME_VALUE' ) )
  6      is
  7          begin
  8                  dbms_output.put_line( p_input );
  9          end;
 10
 11  end;
 12  /

Package body created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec test_pkg.test
Some Value

PL/SQL procedure successfully completed.

you can avoid that cascading invalidation at the slight cost of invoking a
function for the defaults.

      Rating: 5
Cannot trap
      ora-04068 with exception handler  October 27, 2003

Reviewer:  Henry  from South Kingstown, RI USA

Tom,
Previously in this thread you said, with respect to loss of state (ora-04068),
that "you need to catch the error and retry the operation AFTER acknowledging
the fact that your state was wiped out...We give you the opportunity to deal
with it" (from June 26, 2002).

I have been trying, unsuccessfully to trap the 4068 (using version 8.1.7.4). A
search through Metalink didn't reveal any bugs, but there were some indirect
comments which alluded to the fact that maybe this error couldn't be trapped.
(these were more along the lines of omitting this method as a solution as
opposed to directly stating it couldn't be done)

Here are my test package spec and package body.

create or replace package state_test as
    procedure increment;
    procedure show;
    procedure independent;
end;
/

create or replace package body state_test as
    y number(2) := 0;
    state_lost        exception;
    pragma exception_init(state_lost, -04068);

    procedure increment
    as
    begin
        y:=y+1;
    end;

    procedure show
    as
    begin
        dbms_output.put_line('y = '||y);
    exception
        WHEN state_lost THEN
        dbms_output.put_line('It is an error');
    end;

    procedure independent
    as
    d number;
    begin
        select * into d from state_test_tbl;
    end;
end;
/

I then:
1. ran state_test.show with one session
2. dropped and recreated state_test_tbl (to invalidate my package body)
3. ran state_test.show from another session (to recompile my invalidated package
body and wipe out the initial state)
4. ran state_test.show from my original session

When I did this, I got

ERROR at line 1:
ORA-04068: existing state of packages has been discarded
ORA-04061: existing state of package body "SYSTEM.STATE_TEST" has been
invalidated
ORA-04065: not executed, altered or dropped package body "SYSTEM.STATE_TEST"
ORA-06508: PL/SQL: could not find program unit being called
ORA-06512: at line 1

What am I missing here?

Thanks.

Henry

          Followup:

the client catches this.  there is nothing to "catch" in the code above -- the
code was *WIPED OUT* -- it isn't runnable, it isn't running.  You never get
within a mile of the exception block, it was "erased from memory"

      Rating: 4
Table
      Dependency  November 30, 2003
Reviewer:
      Robert  from CT
Is there a way to find out what tables/views a package references ?

Thanks

          Followup:

user|all|dba_dependencies

(a view)

      Rating: 5
How many
      packages  December 16, 2003
Reviewer:  B
       from New Jersey
Hi Tom,

Someone asked you:
"How do you decide how many packages to create.  Would you create one package
per one procedure or you would bundle several procedures into one package or it
does not really make a difference."

And you answered:
"use common sense, that is all.  group related functions together logically. If
it boils down to 1 rocedure/package -- so be it (in fact, you would probably
find that your procedures are larger then a screen, that is you have to page
up/down through them.  If so - use the fact you are in a package to MODULARIZE
your code -- break it into several private routines inside the package body to
make it more readable, more maintainable)."

We currently do not have any packages.  We have about 20 procedures and 3
functions.  Now our manager has asked us to place all the pieces of code into
one package.  That way everything will be in one place.  But if we make a change
to any procedure the entire code will be migrated to production.

I know you do not have any hard nad fast rules of how to group procs and
functions but is that the best option to put everything into one package?  I
cannot think of any good reason why not other than it will be more difficult to
scroll through 20 pieces of code to debug than one piece of code.

Thank you for any advice you could give me!

          Followup:

while I'm against "put them all in one just because" (there should be some
thought put into it) what is wrong with having one package to zap into
production?  I don't see any fundemental issues there?  what is your concern?

the number of procedures to put into a package is somewhere between 1 and
infinity.

You debug SMALL, modular routines.  I'll betcha your code in the standalong
routines does not "fit on the screen".  this'll be the perfect opportunity to
make each procedure about 60-80 lines long max -- making the code infinitely
more maintainable, understandable and debuggable.

Hopefully you'll go from 20 top level procedures to a package with 20 exposed
procedure and 40 hidden ones (40 is just a number, use any number you want)

      Rating:
      5
OK  December 16, 2003
Reviewer:  B
       from New Jersey
We have large procedures beacuse we have large queries that return a lot of
data.

But I understand your point that this is an opportunity to see if we can make
our code even more modular.

Thanks.

          Followup:

(views = way to make large queries seem really small)

think of a view like a subroutine

December 16, 2003 Bhavesh -- Thanks for the question regarding
      "Pblm in creating MATERIALIZED VIEW", version 8i (ver 8.1.5)

      You Asked
Hello Tom:
I have a problem in ceating MATERIALIZED VIEW.
My goal is, to create a MATERIALIZED VIEW which gives me
the sum of salary of all the employees in deptno = 10.
Here is what commands I issued in user SCOTT:
(I have given GRANT CREATE MATERIALIZED VIEW TO SCOTT; )

SQL> CREATE SNAPSHOT LOG ON emp WITH PRIMARY KEY;

Snapshot log created.

SQL> CREATE MATERIALIZED VIEW mv_1 REFRESH FORCE ON COMMIT
  2  AS select sum(sal) as sum_sal from emp
  3  where deptno = 10
  4  BUILD IMMEDIATE
  5 /
BUILD IMMEDIATE
*
ERROR at line 4:
ORA-00933: SQL command not properly ended

SQL>  CREATE MATERIALIZED VIEW mv_2 REFRESH on commit
  2  AS select sum(sal) as sum_sal from emp
  3  where deptno = 10
  4 /
AS select sum(sal) as sum_sal from emp
                                      *
ERROR at line 2:
ORA-12051: ON COMMIT attribute is incompatible with other options

SQL> CREATE MATERIALIZED VIEW mv1 REFRESH force
  2  AS select sum(sal) as sum_sal from emp
  3  where deptno = 10
  4  /

Snapshot created.

My Qs are here:
a) Is 1st query required in order to have rest working?
b) Why 2nd and 3rd qry giving problems? It there a syntax
   error, or some init.ora parameter setting required?
c) 4th qry worked, but in order to get what I want,
   (I want to reflect the changes in the materialized
    view immediately, as I change it in emp table)
   I need to execute (schedule ?) DBMS_MVIEW.REFRESH()
   procedure, right ?
d) Is there any way I can get what I want without
   executing (scheduleing) DBMS_MVIEW.REFRESH()
   procedure?
e) Do I need to set following 3 init.ora variables in
   order to get what I want ?
   .  QUERY_REWRITE_ENABLED = True
   .  QUERY_REWRITE_INTEGRITY = enforced
   .  COMPATIBLE = 8.1.5

Thanks in advance,
Bhavesh

      and we said...
There are a couple of things here:

o build immediate goes at the "top" of the create command.  Before the refresh
component.

o if you look at the chart in:

http://technet.oracle.com/doc/oracle8i_816/server.816/a76994/mv.htm#42194
it shows that in order to have SUM(SAL), we need to have COUNT(SAL) and that all
single table aggregates must have COUNT(*) as well.

o if you look at:

http://technet.oracle.com/doc/oracle8i_816/server.816/a76994/mv.htm#32933
one of the Restrictions on Fast Refresh on Materialized Views with Single-Table
Aggregates is they cannot have a WHERE clause.

So, to get what you want -- you cannot "slice" the table that way.  You would:

ops$tkyte@ORA8I.WORLD> create table emp as select * from scott.emp
  2  /

Table created.

ops$tkyte@ORA8I.WORLD> alter table emp add constraint emp_pk
  2  primary key(empno)
  3  /

Table altered.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> CREATE SNAPSHOT LOG ON emp WITH rowid( sal, deptno )
  2  INCLUDING NEW VALUES
  3  /

Materialized view log created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> create materialized view mv_1
  2  build immediate
  3  refresh force on commit
  4  as
  5  select deptno, count(*) cnt1, count(e.sal) cnt2, sum(e.sal) as sum_sal
  6    from emp  e
  7   group by deptno
  8  /

Materialized view created.

Now, another approach would be to create an MV of an MV. subset the data you
want and then aggregate the subset.
Review &
      Followup

      Rating: 4
materialized
      view  May 07, 2002
Reviewer:  sathyamurthy.s
       from india
Hi
i am using oracle 8i , i want to create materialized view.

but it is giving the problem .

i am giving the script

create table t1 (a int,b int, y number,primary key(a,b));

Table created.

create materialized view log on t1 with rowid(a,y) including new values;

Materialized view log created.

create materialized view t2
 build immediate
  refresh force on commit
    as
    select dno,  sum(sal)
as sum_sal
    from emp
    group by dno

cannot set the ON COMMIT refresh attribute for the materialized view

what is the problem please give solutions
Regards

sathya

          Followup:

Please re-read my answer to the original question.

Pay close attention to second point I make about including "sum(sal)"

      Rating: 5
MATERIALIZED
      VIEW  May 07, 2002
Reviewer:  sathyamurthy
       from india
Hi Tom,

After adding count in the query it is working fine.

Thanks to you.

      Rating: 4
How to know
      ?  June 14, 2002
Reviewer:  Raman Pujani
       from India
Hi Tom,

Nice explanation. How do we come to know whether the query executed by user is
actually using the materialized view, means user's query is rewrited ?

Is it true that, we should not include joins with SYSDATE (end_date > SYSDATE)
while creating materialized view?

Regards,
Raman.

          Followup:

explain plans.

you can explain the query,
you can use autotrace,
you can use (my favorite) sql_trace+tkprof

As for the last part -- think about it.  as sysdate is an ever changing thing,
the mat. view is out of date the second it finished refreshing.

      Rating: 4
mat. view , why
      do i get this   June 13, 2003
Reviewer:
      umesh  from bangalore india
  1  CREATE SNAPSHOT LOG ON emp WITH rowid(  deptno,sal )
  2*   INCLUDING NEW VALUES
  3  /

Materialized view log created.

  1  create materialized view t2
  2   build immediate
  3    refresh force on commit
  4      as
  5      select deptno,  sum(sal)
  6  as sum_sal, count( *) cnt
  7      from emp
  8*     group by deptno
scott>/
    from emp
         *
ERROR at line 7:
ORA-12054: cannot set the ON COMMIT refresh attribute for the materialized view

          Followup:

amazingly, this is exactly the same thing that is addressed in the original
question... repitition being the key to sucess....  I'll do it again :)

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96520/mv.htm#42218
That table shows in order to have

SUM(expr)

You must also include

COUNT(expr) and COUNT(*)

You do not have COUNT(expr)

ops$tkyte@ORA817DEV> create materialized view emp_mv
  2  build immediate
  3  refresh force on commit
  4  as
  5  select deptno, sum(sal) sum_sal, count(sal) count_sal, count(*) cnt
  6    from emp
  7   group by deptno
  8  /

Materialized view created.

      Rating:
      5
query_rewrite_enabled   July 07, 2003

Reviewer:  Tony  from Canada
Oracle recommends to set query_rewrite_enabled = true for Mviews but I'm working
on mviews created by someone else and the DDL starts like :
CREATE MATERIALIZED VIEW test_mv
PCTFREE 0 TABLESPACE MV_DATA STORAGE (INITIAL 1M NEXT 1M MINEXTENTS 1 MAXEXTENTS
UNLIMITED PCTINCREASE 0)
NOLOGGING NOCACHE
BUILD IMMEDIATE
USING INDEX TABLESPACE MV_INDEX STORAGE (INITIAL 1M NEXT 1M MINEXTENTS 1
MAXEXTENTS UNLIMITED PCTINCREASE 0)
REFRESH COMPLETE ON DEMAND
START WITH (TRUNC(SYSDATE) + 1 + (4/24))
NEXT (TRUNC(SYSDATE) + 1 + (4/24) + (1/24))
WITH PRIMARY KEY
USING LOCAL ROLLBACK SEGMENT MV_RB
DISABLE QUERY REWRITE
AS
SELECT  sa.sale_num         AS sale_number,
        sa.sale_desc        AS sale_type,
        or.cust_cd          AS customer_code,
        or.item             AS item_id,
        or.item_desc          AS item_description,
...........

my question is why DISABLE QUERY REWRITE is used in this case. (we have some
functions used in mviews)

          Followup:

you would have to ask the guy who wrote it why they chose that option (and why
they would use a storage clause in this day and age...)

      Rating: 5
That is
      true  July 07, 2003
Reviewer:  Tony
       from Canada
But my dilemma is that this guy is no more working with us and I'm working on
mviews for the first time. Any input will be highly appreciated.

Regards

          Followup:

my input is "i have no clue what he was thinking and why he might have not
wanted query rewrite enabled"

      Rating:
      5
  July 07, 2003
Reviewer:  bharath
       from usa
>>and why they would use a storage clause in this day and >>age...)

what do you mean by that.

          Followup:

I mean, use locally managed tablespaces and never use initial, next,
pctincrease, minextents, maxextents - let it just happen.

      Rating: 5
ORA-01001:
      invalid cursor  September 14, 2003
Reviewer:
      Kamal Kishore  from New Jersey, USA
Hi Tom,
When I try to create a MVIEW using the UNION query, I get the Invalid Cursor
error. Then I re-create the MVIEW without the UNION query, then DROP it, and
then when I re-create the MVIEW again with the UNION QUERY, it is successful.

What could be the reason?
Any input will be appreciated.
Thanks,

All tables have MVIEW LOG created on them and SCOTT.BONUS table has ENAME as the
PRIMARY KEY.

SQL> create materialized view mview_emp_dept
  2  build immediate
  3  refresh fast on demand
  4  as
  5  select * from scott.emp e
  6  where
  7    exists (select 1 from scott.dept d1
  8            where
  9                d1.deptno = e.deptno)
 10  union
 11  select * from scott.emp e
 12  where
 13    exists (select 1 from scott.bonus b
 14            where
 15                b.ename = e.ename)
 16  /
select * from scott.emp e
                    *
ERROR at line 5:
ORA-01001: invalid cursor
ORA-00600: internal error code, arguments: [21051], [], [], [], [], [], [], []

SQL>
SQL> create materialized view mview_emp_dept
  2  build immediate
  3  refresh fast on demand
  4  as
  5  select * from scott.emp e
  6  where
  7    exists (select 1 from scott.dept d1
  8            where
  9                d1.deptno = e.deptno)
 10  /

Materialized view created.

SQL>
SQL> drop materialized view mview_emp_dept ;

Materialized view dropped.

SQL>
SQL> create materialized view mview_emp_dept
  2  build immediate
  3  refresh fast on demand
  4  as
  5  select * from scott.emp e
  6  where
  7    exists (select 1 from scott.dept d1
  8            where
  9                d1.deptno = e.deptno)
 10  union
 11  select * from scott.emp e
 12  where
 13    exists (select 1 from scott.bonus b
 14            where
 15                b.ename = e.ename)
 16  /

Materialized view created.

SQL>
SQL> disconnect
Disconnected from Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production
SQL>

      Rating: 5
materialized
      view over a database link  December 16, 2003

Reviewer:  vll  from USA
Tom,

are there any additional restrictions on creating materialized views over a
database link? Is it possible to have "refresh fast", "refresh on commit"? Thank
you very-very much

          Followup:

you cannot refresh on commit -- but you can refresh fast, yes (i prefer to call
it "refresh incremental" since it can be slower then a complete refresh of
course!)

December 16, 2003 Michael -- Thanks for the question regarding
      "Analytics question", version 9.2.0.

      You Asked
I have a table from a 3rd party application that is used to track
an order through the various manufacturing operations.  A subset of
the information looks like this:

   ORDER  OPN  STATION  CLOSE_DATE
   -----  ---  -------  ----------
   12345   10  RECV     07/01/2003
   12345   20  MACH1    07/02/2003
   12345   25  MACH1    07/05/2003
   12345   30  MACH1    07/11/2003
   12345   36  INSP1    07/12/2003
   12345   50  MACH1    07/16/2003
   12345   90  MACH2    07/30/2003
   12345  990  STOCK    08/01/2003

Where each row is a process that the order had to go through,
with OPN being the order of the processes.

What I would like to receive is the output grouped by consecutive
STATION values and include the start and close dates for each
STATION group.  The start date is defined as the date the prior
station closed.  So the output expected from the above data subset
would be:

   ORDER  STATION  START_DATE  CLOSE_DATE
   -----  -------  ----------  ----------
   12345  RECV                 07/01/2003
   12345  MACH1    07/01/2003  07/11/2003
   12345  INSP1    07/11/2003  07/12/2003
   12345  MACH1    07/12/2003  07/16/2003
   12345  MACH2    07/16/2003  07/30/2003
   12345  STOCK    07/30/2003  08/01/2003

Is this possible?  I've tried using analytics, but I can't seem to
get what I want.  I can use the LAG function to get the start and
close dates, grouped by STATION, but it will group all the different
STATION values together (i.e. all MACH1 STATIONS will be grouped
together), not just the consecutive STATION values.  I could use
procedural code to get this answer, but I was wanting to see if
it could be done in 1 statement.

I'm sure it will be something easy, but I've been racking my tiny
brain over this for the last few days and can't come up with a
solution.  Can you help?

Many thanks,

Michael T.

      and we said...
Analytics rock
Analytics roll

been thinking about writing a book just about analytics (but wait'll you see the
SQL Model clause in 10g)

ops$tkyte@ORA920> select order#, station, lag_close_date, close_date
  2    from (
  3  select order#,
  4         lag(station) over (partition by order# order by close_date)
                                                                 lag_station,
  5         lead(station) over (partition by order# order by close_date)
                                                                 lead_station,
  6             station,
  7             close_date,
  8         lag(close_date) over (partition by order# order by close_date)
                                                                lag_close_date,
  9         lead(close_date) over (partition by order# order by close_date)
                                                                lead_close_date
 10    from t
 11         )
 12   where lag_station is null
 13          or lead_station is null
 14          or lead_station <> station
 15  /

    ORDER# STATION    LAG_CLOSE_ CLOSE_DATE
---------- ---------- ---------- ----------
     12345 RECV                  07/01/2003
     12345 MACH1      07/05/2003 07/11/2003
     12345 INSP1      07/11/2003 07/12/2003
     12345 MACH1      07/12/2003 07/16/2003
     12345 MACH2      07/16/2003 07/30/2003
     12345 STOCK      07/30/2003 08/01/2003

6 rows selected.

Review &
      Followup

      Rating:
      5
Excellent!!!  October 07, 2003
Reviewer:
      A reader
Hi Tom,

 'been thinking about writing a book just about analytics ' ... please make this
book available soon and am sure it will be yet another gift from you to Oracle
World :)

      Rating:
      5
Wow!!  October 07, 2003
Reviewer:
      Michael T  from Dallas, Tx
This is exactly what I needed!  Analytics do rock!  I just
need to understand them better.  If you do decide to write a
book on analytics, it would be at the top of my must have
list.  Thanks again!!!

      Rating: 5
Small
      correction  October 07, 2003
Reviewer:  Michael
      T  from Dallas, Tx
After looking at it a little closer it looks like there is
one small error.  The start date for the first MACH1 entry
should be the close date of the prior different station.  In
this case 07/01/2003.  However, by making some small changes
to your query I can get the results I want.

SELECT order#,
       station,
       lag(close_date) over (partition by order# order by close_date)
           start_date,
       close_date
  FROM (SELECT order#,
               station,
               close_date
          FROM (SELECT order#,
                       lag(station) over (partition by order# order by
                                          close_date) lag_station,
                       lead(station) over (partition by order# order by
                                          close_date) lead_station,
                       station,
                       close_date
                  FROM t)
 WHERE lead_station <> station
    OR lead_station is null
    OR lag_station is null)

There might be an easier way to construct this query, but
it works great for me.  Thanks a lot for your help!

          Followup:

sorry about that -- you are right -- when we have "a pair", we want to use
lag/lead again to get and keep the right dates.

So, we want to keep rows that are:

a) the first row in the partition  "where lag_station is null"
b) the last row in the partition "where lead_station is null"
c) the first of a possible pair "where lag_station <> station"
d) the second of a possible pair "where lead_station <> station"

This query does that:

ops$tkyte@ORA920> select order#,
  2         station,
  3         lag_close_date,
  4         close_date,
  5         decode( lead_station, station, 1, 0 ) first_of_pair,
  6         decode( lag_station, station, 1, 0 ) second_of_pair
  7    from (
  8  select order#,
  9         lag(station) over (partition by order# order by close_date)
 10                                                              lag_station,
 11         lead(station) over (partition by order# order by close_date)
 12                                                              lead_station,
 13             station,
 14             close_date,
 15         lag(close_date) over (partition by order# order by close_date)
 16                                                             lag_close_date,
 17         lead(close_date) over (partition by order# order by close_date)
 18                                                             lead_close_date
 19    from t
 20         )
 21   where lag_station is null
 22          or lead_station is null
 23          or lead_station <> station
 24          or lag_station <> station
 25  /

ORDER# STATION LAG_CLOSE_ CLOSE_DATE FIRST_OF_PAIR SECOND_OF_PAIR
------ ------- ---------- ---------- ------------- --------------
 12345 RECV               07/01/2003             0              0
 12345 MACH1   07/01/2003 07/02/2003             1              0
 12345 MACH1   07/05/2003 07/11/2003             0              1
 12345 INSP1   07/11/2003 07/12/2003             0              0
 12345 MACH1   07/12/2003 07/16/2003             0              0
 12345 MACH2   07/16/2003 07/30/2003             0              0
 12345 STOCK   07/30/2003 08/01/2003             0              0

7 rows selected.

we can see with the 1's the first/second of a pair in there.  All we need to
do now is "reach forward" for the first of a pair and grab the close date from
the next record:

ops$tkyte@ORA920> select order#,
  2         station,
  3         lag_close_date,
  4         close_date
  5    from (
  6  select order#,
  7         station,
  8         lag_close_date,
  9         decode( lead_station,
 10                 station,
 11                 lead(close_date) over (partition by order# order by
close_date),
 12                 close_date ) close_date,
 13         decode( lead_station, station, 1, 0 ) first_of_pair,
 14         decode( lag_station, station, 1, 0 ) second_of_pair
 15    from (
 16  select order#,
 17         lag(station) over (partition by order# order by close_date)
 18                                                              lag_station,
 19         lead(station) over (partition by order# order by close_date)
 20                                                              lead_station,
 21             station,
 22             close_date,
 23         lag(close_date) over (partition by order# order by close_date)
 24                                                             lag_close_date,
 25         lead(close_date) over (partition by order# order by close_date)
 26                                                             lead_close_date
 27    from t
 28         )
 29   where lag_station is null
 30          or lead_station is null
 31          or lead_station <> station
 32          or lag_station <> station
 33         )
 34   where second_of_pair <> 1
 35  /

ORDER# STATION LAG_CLOSE_ CLOSE_DATE
------ ------- ---------- ----------
 12345 RECV               07/01/2003
 12345 MACH1   07/01/2003 07/11/2003
 12345 INSP1   07/11/2003 07/12/2003
 12345 MACH1   07/12/2003 07/16/2003
 12345 MACH2   07/16/2003 07/30/2003
 12345 STOCK   07/30/2003 08/01/2003

6 rows selected.

and discard the second of pairs row

That is another way to do it (and an insight into how I develop analytic queries
-- adding extra columns like that just to see visually what I want to do)

      Rating: 4
another good
      book on the list please go ahead on this one too  October
      07, 2003
Reviewer:  Vijay Sehgal  from India

Best Regards,
Vijay Sehgal

      Rating: 5
Very
      useful  October 07, 2003
Reviewer:  Michael
      T.  from Dallas, Tx
Excellent, as always!

      Rating: 5
Can we reach to
      the end of the group?  December 15, 2003
Reviewer:
      Steve  from UK
For example, say our analytic query returns the following result:

master_record    sub_record   nxt_record
95845433           25860032     95118740
95118740           25860032     95837497
95837497           25860032

What I'd like is to do is grab the final master_record, 95837497, and have that
populated in the final column.  There could be 2,3 or more in each group.

          Followup:

so the nxt_record of the last record should be the master_record of that row?

then just select

  nvl( lead(master_record) over (....), master_record ) nxt_record

when the lead is NULL, return the master_record of the current row

      Rating:
      5
Almost....  December 15, 2003
Reviewer:
      Steve  from UK
but I dodn't explain it well enough.  What I'd like to see is a result set that
looks like:

master_record    sub_record   nxt_record
95845433           25860032     95837497
95118740           25860032     95837497
95837497           25860032     95837497

The data comes from this:

table activity
cllocn        moddate

25860032      18/06/2003
95118740      26/08/2003
95837497      15/12/2003
95845433      19/08/2003

table ext_dedupe

master_cllocn    dupe_cllocn
25860032         95118740
25860032         95837497
25860032         95845433

My query is:

select * from ( select master_record, sub_record, lead(master_record) over
(partition by sub_record order by lst_activity asc) nxt_activity
from ( select * from (select case when dupelast_ackdate>last_ackdate then
dupe_cllocn
when last_ackdate>dupelast_ackdate then master_cllocn
else master_cllocn
end master_record, greatest(last_ackdate,dupelast_ackdate) lst_activity,
case when dupelast_ackdate>last_ackdate then master_cllocn
when last_ackdate>dupelast_ackdate then dupe_cllocn
else dupe_cllocn
end sub_record
from (select master_cllocn, (select max(moddate) from activity a where
a.cllocn=ed.master_cllocn) last_ackdate,
dupe_cllocn, (select max(moddate) from activity a where a.cllocn=ed.dupe_cllocn)
dupelast_ackdate
from ext_dedupe ed))))

Am I on the right track or is there a simpler way to this?

Thanks

          Followup:

can you explain in "just text" how you got from your inputs to your outputs.

it is not clear (and i didn't feel like parsing that sql to reverse engineer
what it does)

      Rating: 3
Is this what
      you are looking for ?  December 15, 2003
Reviewer:
      Venkat  from Detroit, MI USA
select master, sub, moddate
       , min(master) keep (dense_rank first order by moddate) over (partition by
sub) first_in_list
       , max(master) keep (dense_rank last order by moddate) over (partition by
sub) last_in_list
  from (select master, sub, moddate from (
        select 95845433 master, 25860032 sub, to_date('19-aug-03','dd/mon/yy')
moddate from dual union all
        select 95118740, 25860032, to_date('26-aug-03','dd/mon/yy') from dual
union all
        select 95837497, 25860032, to_date('15-dec-03','dd/mon/yy') from dual))

MASTER    SUB    MODDATE    FIRST_IN_LIST    LAST_IN_LIST
95845433    25860032    8/19/2003    95845433    95837497
95118740    25860032    8/26/2003    95845433    95837497
95837497    25860032    12/15/2003    95845433    95837497

      Rating: 4
Tom's Book
        December 16, 2003
Reviewer:  umesh
       from blore india
Tom
Do not announce until you are finished with the book .. when you talk of a book
..can't wait until We have it here
Analytics Book That must be real good

      Rating: 5
Is it possible
      to get the same result in standard edition ?  December 16,
      2003
Reviewer:  Ninoslav  from croatia
Hi Tom,
yes, analitic functions are great. However, we can use it only in enterprise
edition of database. We have a few small customers that want only a standard
edition.
So, is it possible in this question to get the same result without analitic
functions ?
It would be nice to have some kind of mapping between analitics and 'standard'
queries. But, that is probabaly impossible...

          Followup:

Oracle 9iR2 and up -- analytics are a feature of standard edition.

there are things you can do in analytics that are quite simply NOT PRACTICAL in
any sense without them.

      Rating:
      5
ok  December 16, 2003
Reviewer:
      Steve  from uk
I have two tables - activity and ext_dedupe.

table activity
cllocn        moddate
25860032      18/06/2003
95118740      26/08/2003
95837497      15/12/2003
95845433      19/08/2003

table ext_dedupe
master_cllocn    dupe_cllocn
25860032         95118740
25860032         95837497
25860032         95845433

Ext_dedupe is a table created by a third party app which has identified
duplicate records within our database.  The first column is supposed to be the
master and the second the duplicate.  The idea is to mark as archived all our
duplicate records with a pointer to the master.  Notwithstanding the order of
the columns, what we want to do is find out which record has the most recent
activity (from the activity table) and archive off the others.

So, in this example although the master is listed as 25860032 against the other
3, an examination of the activity dates mean I want to keep 95837497 and mark
the others as archived and have a pointer on each of them to 95837497.  That's
why I thought if I could get to the following result it would make it simpler.

master_record    sub_record   nxt_record
95845433           25860032     95837497
95118740           25860032     95837497
95837497           25860032     95837497

Hope that makes sense!

          Followup:

oh, then nxt_record is just

last_value(master_record) over (partition by sub_record order by moddate)

      Rating:
      5
Why...  December 16, 2003
Reviewer:
      Steve  from UK
it didn't work for me.  I had to change it to

first_value(master_record) over (partition by sub_record order by moddate desc)

Is there a reason for that?

          Followup:

doh, default window clause is current row and unbounded preceding

i would have needed a window clause that looks forwards rather then backwards
(reason #1 why I should always set up a test case instead of just answering on
the fly)

your solution of reversing the data works just fine.

      Rating: 3
Another
      solution  December 16, 2003
Reviewer:
      A reader
The following gives the same result ...

select cllocn master_record, nvl(master_cllocn,cllocn) sub_record
       , max(cllocn) keep (dense_rank last order by moddate)
                     over (partition by nvl(master_cllocn,cllocn)) nxt_record
  from activity, ext_dedupe where cllocn = dupe_cllocn

MASTER_RECORD    SUB_RECORD    NXT_RECORD
95118740        25860032    95837497
95837497        25860032    95837497
95845433        25860032    95837497

          Followup:

yes, there are many many ways to do this.

first_value
last_value

substring of max() without keep

sure.

      Rating:
      3
  December 16, 2003
Reviewer:
      A reader
Actually the nvl(master_cllocn...) is required only if you need all 4 rows in
the output as follows(there is an outer join involved). If you need only the 3
rows as shown in the above post, there is no need for the nvl's....

select cllocn master_record, nvl(master_cllocn,cllocn) sub_record
       , max(cllocn) keep (dense_rank last order by moddate)
                     over (partition by nvl(master_cllocn,cllocn)) nxt_record
       , last_value(cllocn) over (partition by nvl(master_cllocn,cllocn) order
by moddate) nxt
  from activity, ext_dedupe where cllocn = dupe_cllocn (+)

MASTER_RECORD    SUB_RECORD    NXT_RECORD
25860032        25860032    95837497
95118740        25860032    95837497
95837497        25860032    95837497
95845433        25860032    95837497

December 16, 2003 Sam -- Thanks for the question regarding "generic
      trigger for auditing column level changes", version

      You Asked
I'm trying to create a generic before update trigger
which will compare all :old.column_values to all
:new.column_values.  If the column_values are different, then I
would like to log the change to a separate table.  When I try to

compile :old.<column_variable_name>, Oracle return an
"(1):PLS-00049: bad bind variable 'NEW."  Can you recommend a
dynamic way to accomplish this?  Thanks in advance.

      and we said...
:new and :old are like bind variables to the trigger, they are
not 'regular' variables.  you cannot dynamically access them,
only 'statically'.

I suggest you consider writing a stored procedure or sql*plus
script to write a trigger that statically references the new/old
values.  For example, if you wanted to save in a table the time
of update, who updated, table updated, column modified and
new/old values, you could code a sql*plus script like:

-----------------------------------------------------------------
---
create table audit_tbl
(    timestamp    date,
    who            varchar2(30),
    tname        varchar2(30),
    cname        varchar2(30),
    old            varchar2(2000),
    new            varchar2(2000)
)
/

create or replace package audit_pkg
as
    procedure check_val( l_tname in varchar2,
                             l_cname in varchar2,
                 l_new in varchar2,
                             l_old in varchar2 );

    procedure check_val( l_tname in varchar2,
                             l_cname in varchar2,
                     l_new in date,
                             l_old in date );

    procedure check_val( l_tname in varchar2,
                             l_cname in varchar2,
                 l_new in number,
                             l_old in number );
end;
/

create or replace package body audit_pkg
as

procedure check_val( l_tname in varchar2,
                     l_cname in varchar2,
             l_new in varchar2,
                     l_old in varchar2 )
is
begin
    if ( l_new <> l_old or
         (l_new is null and l_old is not NULL) or
         (l_new is not null and l_old is NULL) )
    then
        insert into audit_tbl values
        ( sysdate, user, upper(l_tname), upper(l_cname),
                             l_old, l_new );
    end if;
end;

procedure check_val( l_tname in varchar2, l_cname in varchar2,
             l_new in date, l_old in date )
is
begin
    if ( l_new <> l_old or
         (l_new is null and l_old is not NULL) or
         (l_new is not null and l_old is NULL) )
    then
        insert into audit_tbl values
        ( sysdate, user, upper(l_tname), upper(l_cname),
          to_char( l_old, 'dd-mon-yyyy hh24:mi:ss' ),
          to_char( l_new, 'dd-mon-yyyy hh23:mi:ss' ) );
    end if;
end;

procedure check_val( l_tname in varchar2, l_cname in varchar2,
             l_new in number, l_old in number )
is
begin
    if ( l_new <> l_old or
         (l_new is null and l_old is not NULL) or
         (l_new is not null and l_old is NULL) )
    then
        insert into audit_tbl values
        ( sysdate, user, upper(l_tname), upper(l_cname),
                                 l_old, l_new );
    end if;
end;

end audit_pkg;
/
set serveroutput on
set feedback off
set verify off
set embedded on
set heading off
spool tmp.sql

prompt create or replace trigger aud#&1
prompt after update on &1
prompt for each row
prompt begin

select '    audit_pkg.check_val( ''&1'', ''' || column_name ||
          ''', ' || ':new.' || column_name || ', :old.' ||
             column_name || ');'
from user_tab_columns where table_name = upper('&1')
/
prompt end;;
prompt /

spool off
set feedback on
set embedded off
set heading on
set verify on

@tmp
-------------

That will build the generic table and package plus generate a
trigger that would look like:

SQL> @thatscript dept

create or replace trigger aud#dept
after update on dept
for each row
begin
    audit_pkg.check_val( 'dept', 'DEPTNO', :new.DEPTNO,
:old.DEPTNO);
    audit_pkg.check_val( 'dept', 'DNAME', :new.DNAME,
:old.DNAME);
    audit_pkg.check_val( 'dept', 'LOC', :new.LOC, :old.LOC);
end;
/

Review &
      Followup

      Rating:
      5
  May 24, 2001
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating:
      5
  May 31, 2001
Reviewer:  VJ
       from Boston, MA USA

      Rating:
      4
  June 04, 2001
Reviewer:  Kenan
      BEKTAS  from Canada

      Rating:
      5
  July 12, 2001
Reviewer:  Yuval
      Lavi  from Israel

      Rating:
      5
  July 25, 2001
Reviewer:
      A reader

      Rating:
      5
  August 29, 2001
Reviewer:  Froggy
       from Regensburg, Germany

      Rating:
      4
  October 15, 2001
Reviewer:  ali
       from hyderabad, pakistan

      Rating: 2
Not quite
      dynamic enough.  October 26, 2001
Reviewer:
      Steve Rock  from Phoenix AZ, USA
I've been reading several related scenarios to this one.  As usual, you do a
great job of answering them and I greatly appreciate your insights.

My problem is that I need something much more dynamic.  In my situation, I need
to identify changes on a sub-set of columns based on a customer id.  The column
list can change at any time.  I am not allowed to keep building/rebuilding
triggers or sp's.

I want to store the columns of interest in a meta-data table by customer id and
when the trigger fires, determine if the any of that customer's columns of
interest have changed.

Any ideas?

Thanks in advance!

          Followup:

well, simply add the logic to do that then.  Sounds easy enough to me.  Change
the check_val to always take a customer_id as input and add a check for the
column name after seeing it the value changed:

procedure check_val( l_cust_id in number,
                     l_tname in varchar2,
                     l_cname in varchar2,
                     l_new in varchar2,
                     l_old in varchar2 )
is
begin
    if ( l_new <> l_old or
         (l_new is null and l_old is not NULL) or
         (l_new is not null and l_old is NULL) )
    then
        for x in ( select *
                     from LOOKUP_TABLE
                    where cust_id = l_cust_id
                      and table_name = l_tname
                      and instr( column_list, '/' || l_cname || '/' ) > 0
                      and rownum = 1 )
        loop
           insert into audit_tbl values
           ( sysdate, user, upper(l_tname), upper(l_cname),
                                l_old, l_new );
        end loop;
    end if;
end;

Now, if you have a lookup table like:

CUST_ID         TABLE_NAME     COLUMN_LIST
1               EMP            /ENAME/HIREDATE/
2               EMP            /JOB/SAL/

eg:

create table lookup_table( cust_id number, table_name varchar2(30), column_list
varchar2(4000), primary key( cust_id, table_name) ) ORGANIZATION INDEX;

you'd have what you want.

      Rating:
      5
  December 31, 2001
Reviewer:
      A reader

      Rating: 5
view?
      sql?  March 04, 2002
Reviewer:
      A reader
Is there a way to use the audit table for viewing the record history??, like:
Empno   Ename      Hiredate     Sal    Version
 233     AA        2000-02-01   1000     1
 233     BB        2000-03-01    800     2

          Followup:

yes, you could do whatever you want.  Going this far however and you probably
want to start looking at the automated feature of "workspace mgmt" supplied for
817 and as part of 9i and above.

http://otn.oracle.com/products/workspace_mgr/content.html
...

      Rating: 4
Dynamic
      binding  June 26, 2002
Reviewer:  reader
       from India
Tom,

We have got lot of solutions for our problems from your site. We appreciate your
deemed valued suggestions.

we have the following questions :

1) Here we are using the package you have given for audit trail. Instead of
using the package for auditing each change in column as you have specified in
your article.

i.e     audit_pkg.check_val( 'dept', 'DEPTNO', :new.DEPTNO, :old.DEPTNO);
        audit_pkg.check_val( 'dept', 'DNAME', :new.DNAME, :old.DNAME);
    audit_pkg.check_val( 'dept', 'LOC', :new.LOC, :old.LOC);

How can we dynamically get the old and new value for all the columns of a
particular table using cursor as given below.

...

FOR x IN (select column_name from user_tab_columns where table_name =
upper('dept'))
LOOP

    AUDIT_PKG.CHECK_VAL(l_tname => 'DEPT',
                          l_cname => x.column_name,
                          l_new => ?
                          l_old => ?
                          l_primary => 'DEPTNO',
                          l_primary_value => :old.deptno,
                          l_tpid => TPID,
                          l_trigger => TRIG_CD) ;

END LOOP;
.....

As you said
":new and :old are like bind variables to the trigger, they are not 'regular'
variables.  you cannot dynamically access them, only 'statically'."

Is there any way or workaround to access these old and new values for particular
table as number of columns in a table can be more than 50 and we want to audit
all the columns.

2) We are thinking of storing the audit of all the tables in one table. Is it a
better a suggestion to maintain different audit tables for different tables.

please don't mind this inane question.
Thanx in advance.

          Followup:

Well, I thought that I was clear in the original answer:

...
and we said...

:new and :old are like bind variables to the trigger, they are not 'regular'
variables.  you cannot dynamically access them, only 'statically'.
.......

*you cannot dynamically access them, only statically*

The solution I provided cares NOT if there are 5, 50, 500 or 1000 columns -- it
generates the code for you so YOU do not care if there are 5, 50, 500 or 1000
columns.  To you it is the same amount of effort (the software does the heavy
lifting).

2) storing all of the audit information in a single table is perfectly
acceptable.  In fact, that is exactly what this example does!

      Rating: 5
Use of advanced
      queue  November 06, 2002
Reviewer:  David
      Pujol  from Barcelona, Catalonia
Good. I need something similar. My system is 24x7 and there are many concurrent
proceses. I need to save all old values and all new values of record updated in
other table with another data manipulations. The problem is the performance of
updates. Do you think that I could use advanced queue for this additional work
(data manipulation)?

Thanks is advance

          Followup:

Yes, if the manipulations take a while, can be done after the transaction and
you don't want to make the end user wait while they are performed -- AQ is a
good approach (thats what message queues were developed for, this and building
loosely coupled systems)

      Rating: 5
generic trigger
      for auditing column level changes  November 06, 2002

Reviewer:  A reader  from Phoenix, AZ

      Rating:
      4
  January 17, 2003
Reviewer:
      A reader

      Rating:
      4
  January 17, 2003
Reviewer:
      A reader

      Rating:
      4
  January 30, 2003
Reviewer:
      A reader

      Rating: 5
It works in
      8.1.7!!!  February 18, 2003
Reviewer:  Damon
       from Victoria, BC Canada
Thanks Tom,

The fact that workspace management works in 8.1.7 may have just saved me hours
and hours and hours of coding and had it not been for your site I would have
never known that...

          Followup:

Test it out and let us know how it goes

      Rating: 5
how to add keys
      to the audit_tbl  April 14, 2003
Reviewer:  Tong
      Siew Pui  from SIN
your example is very useful and simple. thanks.

but, how can we add additional info, like the primary key, to the audit_tbl,
since this is going to be the 'generic table' to keep track of changes on all
'audited table' ?

thanks/rgds
tsp

          Followup:

that is for you to design and implement -- you got the shell of the idea, go for
it.

but you might also look at:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4529781014729

      Rating: 2
the key of the
      current updated row  April 21, 2003
Reviewer:
      A reader
I actually wanted to keep the key of the audited table in audit_tbl (the audit
table).
but I have pbm finding out what is/are the key(s) of the currently updated
record.

How can I achieve that ?

Thanks/Rgds

          Followup:

umm, when you generate the trigger as I did, I used use_tab_columns.  You can
extend the example to meet your needs and include the use of USER_CONS_COLUMNS
and USER_CONSTRAINTS to determine what the primary key columns are.

      Rating: 5
I've found a
      little bug...  July 31, 2003
Reviewer:
      Marcio  from Brazil
Tom, take a look on your first code post on this thread, where you put to_char
to convert date there is a tiny bug hh23:mi:ss
<quote>
       ( sysdate, user, upper(l_tname), upper(l_cname),
          to_char( l_old, 'dd-mon-yyyy hh24:mi:ss' ),
          to_char( l_new, 'dd-mon-yyyy hh23:mi:ss' ) );
    end if;
</quote>
but -- any way! my review is...
I used you example to audit some tables so consider that package created as is.

ops$marcio@MARCI9I1> @askq
ops$marcio@MARCI9I1> drop table c;

Table dropped.

ops$marcio@MARCI9I1> drop table p;

Table dropped.

ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1> create table p (
  2     id int constraint p_pk primary key,
  3     y varchar2(20),
  4     z date )
  5  /

Table created.

ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1> create table c (
  2     id int constraint c_pk primary key,
  3     p_id int references p on delete cascade,
  4     z varchar2(20) )
  5  /

Table created.

-- I changed the trigger to catch update, delete and insert.

ops$marcio@MARCI9I1> set echo off
create or replace trigger aud_u#c
after update on c
for each row
begin
    audit_pkg.check_val( 'c', 'ID', :new.ID, :old.ID, 'UPDATE');
    audit_pkg.check_val( 'c', 'P_ID', :new.P_ID, :old.P_ID, 'UPDATE');
    audit_pkg.check_val( 'c', 'Z', :new.Z, :old.Z, 'UPDATE');
end;
/
create or replace trigger aud_i#c
after insert on c
for each row
begin
    audit_pkg.check_val( 'c', 'ID', :new.ID, :old.ID, 'INSERT' );
    audit_pkg.check_val( 'c', 'P_ID', :new.P_ID, :old.P_ID, 'INSERT' );
    audit_pkg.check_val( 'c', 'Z', :new.Z, :old.Z, 'INSERT' );
end;
/
create or replace trigger aud_d#c
after delete on c
for each row
begin
    audit_pkg.check_val( 'c', 'ID', :new.ID, :old.ID, 'DELETE' );
    audit_pkg.check_val( 'c', 'P_ID', :new.P_ID, :old.P_ID, 'DELETE' );
    audit_pkg.check_val( 'c', 'Z', :new.Z, :old.Z, 'DELETE' );
end;
/

Trigger created.

Trigger created.

Trigger created.

create or replace trigger aud_u#p
after update on p
for each row
begin
    audit_pkg.check_val( 'p', 'ID', :new.ID, :old.ID, 'UPDATE');
    audit_pkg.check_val( 'p', 'Y', :new.Y, :old.Y, 'UPDATE');
    audit_pkg.check_val( 'p', 'Z', :new.Z, :old.Z, 'UPDATE');
end;
/
create or replace trigger aud_i#p
after insert on p
for each row
begin
    audit_pkg.check_val( 'p', 'ID', :new.ID, :old.ID, 'INSERT' );
    audit_pkg.check_val( 'p', 'Y', :new.Y, :old.Y, 'INSERT' );
    audit_pkg.check_val( 'p', 'Z', :new.Z, :old.Z, 'INSERT' );
end;
/
create or replace trigger aud_d#p
after delete on p
for each row
begin
    audit_pkg.check_val( 'p', 'ID', :new.ID, :old.ID, 'DELETE' );
    audit_pkg.check_val( 'p', 'Y', :new.Y, :old.Y, 'DELETE' );
    audit_pkg.check_val( 'p', 'Z', :new.Z, :old.Z, 'DELETE' );
end;
/

Trigger created.

Trigger created.

Trigger created.

-- several inserts and deletes (cascade includind) to populate our audit_dml

ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1> begin
  2  for i in 1..5
  3  loop
  4          insert into p values ( i, 'DESC-'||i, sysdate );
  5
  6  end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1> begin
  2  for i in 1..10
  3  loop
  4      insert into c values ( i, mod(i,2) + 1, 'VALOR: '||i );
  5  end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1> set echo off
ops$marcio@MARCI9I1> select * from p;

        ID Y                    Z
---------- -------------------- ---------
         1 DESC-1               31-JUL-03
         2 DESC-2               31-JUL-03
         3 DESC-3               31-JUL-03
         4 DESC-4               31-JUL-03
         5 DESC-5               31-JUL-03

5 rows selected.

ops$marcio@MARCI9I1> select * from c;

        ID       P_ID Z
---------- ---------- --------------------
         1          2 VALOR: 1
         2          1 VALOR: 2
         3          2 VALOR: 3
         4          1 VALOR: 4
         5          2 VALOR: 5
         6          1 VALOR: 6
         7          2 VALOR: 7
         8          1 VALOR: 8
         9          2 VALOR: 9
        10          1 VALOR: 10

10 rows selected.

ops$marcio@MARCI9I1> delete from p where id = 2;

1 row deleted.

ops$marcio@MARCI9I1> select * from p;

        ID Y                    Z
---------- -------------------- ---------
         1 DESC-1               31-JUL-03
         3 DESC-3               31-JUL-03
         4 DESC-4               31-JUL-03
         5 DESC-5               31-JUL-03

4 rows selected.

ops$marcio@MARCI9I1> select * from c;

        ID       P_ID Z
---------- ---------- --------------------
         2          1 VALOR: 2
         4          1 VALOR: 4
         6          1 VALOR: 6
         8          1 VALOR: 8
        10          1 VALOR: 10

5 rows selected.

-- here is my generic table, so...

ops$marcio@MARCI9I1> set lines 50
ops$marcio@MARCI9I1> desc audit_dml
 Name                    Null?    Type
 ----------------------- -------- ----------------
 TIMESTAMP                        DATE
 WHO                              VARCHAR2(30)
 OP                               VARCHAR2(6)
 TNAME                            VARCHAR2(30)
 CNAME                            VARCHAR2(30)
 OLD                              VARCHAR2(2000)
 NEW                              VARCHAR2(2000)

ops$marcio@MARCI9I1> select * from audit_dml;

TIMESTAMP WHO        OP     TNAME CNAME      OLD        NEW
--------- ---------- ------ ----- ---------- ---------- --------------------
31-JUL-03 marcio     INSERT P     ID                    1
31-JUL-03 marcio     INSERT P     Y                     DESC-1
31-JUL-03 marcio     INSERT P     Z                     31-jul-2003 01:17:36
31-JUL-03 marcio     INSERT P     ID                    2
31-JUL-03 marcio     INSERT P     Y                     DESC-2
31-JUL-03 marcio     INSERT P     Z                     31-jul-2003 01:17:36
31-JUL-03 marcio     INSERT P     ID                    3
31-JUL-03 marcio     INSERT P     Y                     DESC-3
31-JUL-03 marcio     INSERT P     Z                     31-jul-2003 01:17:36
31-JUL-03 marcio     INSERT P     ID                    4
31-JUL-03 marcio     INSERT P     Y                     DESC-4
31-JUL-03 marcio     INSERT P     Z                     31-jul-2003 01:17:36
31-JUL-03 marcio     INSERT P     ID                    5
31-JUL-03 marcio     INSERT P     Y                     DESC-5
31-JUL-03 marcio     INSERT P     Z                     31-jul-2003 01:17:36
31-JUL-03 marcio     INSERT C     ID                    1
31-JUL-03 marcio     INSERT C     P_ID                  2
31-JUL-03 marcio     INSERT C     Z                     VALOR: 1
31-JUL-03 marcio     INSERT C     ID                    2
31-JUL-03 marcio     INSERT C     P_ID                  1
31-JUL-03 marcio     INSERT C     Z                     VALOR: 2
31-JUL-03 marcio     INSERT C     ID                    3
31-JUL-03 marcio     INSERT C     P_ID                  2
31-JUL-03 marcio     INSERT C     Z                     VALOR: 3
31-JUL-03 marcio     INSERT C     ID                    4
31-JUL-03 marcio     INSERT C     P_ID                  1
31-JUL-03 marcio     INSERT C     Z                     VALOR: 4
31-JUL-03 marcio     INSERT C     ID                    5
31-JUL-03 marcio     INSERT C     P_ID                  2
31-JUL-03 marcio     INSERT C     Z                     VALOR: 5
31-JUL-03 marcio     INSERT C     ID                    6
31-JUL-03 marcio     INSERT C     P_ID                  1
31-JUL-03 marcio     INSERT C     Z                     VALOR: 6
31-JUL-03 marcio     INSERT C     ID                    7
31-JUL-03 marcio     INSERT C     P_ID                  2
31-JUL-03 marcio     INSERT C     Z                     VALOR: 7
31-JUL-03 marcio     INSERT C     ID                    8
31-JUL-03 marcio     INSERT C     P_ID                  1
31-JUL-03 marcio     INSERT C     Z                     VALOR: 8
31-JUL-03 marcio     INSERT C     ID                    9
31-JUL-03 marcio     INSERT C     P_ID                  2
31-JUL-03 marcio     INSERT C     Z                     VALOR: 9
31-JUL-03 marcio     INSERT C     ID                    10
31-JUL-03 marcio     INSERT C     P_ID                  1
31-JUL-03 marcio     INSERT C     Z                     VALOR: 10
31-JUL-03 marcio     INSERT P     ID                    1
31-JUL-03 marcio     INSERT P     Y                     DESC-1
31-JUL-03 marcio     INSERT P     Z                     31-jul-2003 01:18:46
31-JUL-03 marcio     INSERT P     ID                    2
31-JUL-03 marcio     INSERT P     Y                     DESC-2
31-JUL-03 marcio     INSERT P     Z                     31-jul-2003 01:18:46
31-JUL-03 marcio     INSERT P     ID                    3
31-JUL-03 marcio     INSERT P     Y                     DESC-3
31-JUL-03 marcio     INSERT P     Z                     31-jul-2003 01:18:46
31-JUL-03 marcio     INSERT P     ID                    4
31-JUL-03 marcio     INSERT P     Y                     DESC-4
31-JUL-03 marcio     INSERT P     Z                     31-jul-2003 01:18:46
31-JUL-03 marcio     INSERT P     ID                    5
31-JUL-03 marcio     INSERT P     Y                     DESC-5
31-JUL-03 marcio     INSERT P     Z                     31-jul-2003 01:18:46
31-JUL-03 marcio     INSERT C     ID                    1
31-JUL-03 marcio     INSERT C     P_ID                  2
31-JUL-03 marcio     INSERT C     Z                     VALOR: 1
31-JUL-03 marcio     INSERT C     ID                    2
31-JUL-03 marcio     INSERT C     P_ID                  1
31-JUL-03 marcio     INSERT C     Z                     VALOR: 2
31-JUL-03 marcio     INSERT C     ID                    3
31-JUL-03 marcio     INSERT C     P_ID                  2
31-JUL-03 marcio     INSERT C     Z                     VALOR: 3
31-JUL-03 marcio     INSERT C     ID                    4
31-JUL-03 marcio     INSERT C     P_ID                  1
31-JUL-03 marcio     INSERT C     Z                     VALOR: 4
31-JUL-03 marcio     INSERT C     ID                    5
31-JUL-03 marcio     INSERT C     P_ID                  2
31-JUL-03 marcio     INSERT C     Z                     VALOR: 5
31-JUL-03 marcio     INSERT C     ID                    6
31-JUL-03 marcio     INSERT C     P_ID                  1
31-JUL-03 marcio     INSERT C     Z                     VALOR: 6
31-JUL-03 marcio     INSERT C     ID                    7
31-JUL-03 marcio     INSERT C     P_ID                  2
31-JUL-03 marcio     INSERT C     Z                     VALOR: 7
31-JUL-03 marcio     INSERT C     ID                    8
31-JUL-03 marcio     INSERT C     P_ID                  1
31-JUL-03 marcio     INSERT C     Z                     VALOR: 8
31-JUL-03 marcio     INSERT C     ID                    9
31-JUL-03 marcio     INSERT C     P_ID                  2
31-JUL-03 marcio     INSERT C     Z                     VALOR: 9
31-JUL-03 marcio     INSERT C     ID                    10
31-JUL-03 marcio     INSERT C     P_ID                  1
31-JUL-03 marcio     INSERT C     Z                     VALOR: 10
31-JUL-03 marcio     DELETE C     ID         1
31-JUL-03 marcio     DELETE C     P_ID       2
31-JUL-03 marcio     DELETE C     Z          VALOR: 1
31-JUL-03 marcio     DELETE C     ID         3
31-JUL-03 marcio     DELETE C     P_ID       2
31-JUL-03 marcio     DELETE C     Z          VALOR: 3
31-JUL-03 marcio     DELETE C     ID         5
31-JUL-03 marcio     DELETE C     P_ID       2
31-JUL-03 marcio     DELETE C     Z          VALOR: 5
31-JUL-03 marcio     DELETE C     ID         7
31-JUL-03 marcio     DELETE C     P_ID       2
31-JUL-03 marcio     DELETE C     Z          VALOR: 7
31-JUL-03 marcio     DELETE C     ID         9
31-JUL-03 marcio     DELETE C     P_ID       2
31-JUL-03 marcio     DELETE C     Z          VALOR: 9
31-JUL-03 marcio     DELETE P     ID         2
31-JUL-03 marcio     DELETE P     Y          DESC-2
31-JUL-03 marcio     DELETE P     Z          31-jul-200
                                             3 01:18:46

I would like to achieve build a query to return this:

insert into p ( id, y, z ) values ( 1, 'DESC-1', to_char('31-jul-2003 01:18:46',
'dd-mon-yyyy hh24:mi:ss')
/
after that
insert into c ...
after that
delete from c ...
after
delete from p

sort by timestamp to respect RI (Referencial Integrity).

thanks,
Marcio

          Followup:

you might consider then adding a sequence to the mix and:

create or replace trigger aud_i#p
after insert on p
for each row
declare
    l_seq number;
begin
    select s.nextval into l_seq from dual;
    audit_pkg.check_val( l_seq, 'p', 'ID', :new.ID, :old.ID, 'INSERT' );
    audit_pkg.check_val( l_seq, 'p', 'Y', :new.Y, :old.Y, 'INSERT' );
    audit_pkg.check_val( l_seq, 'p', 'Z', :new.Z, :old.Z, 'INSERT' );
end;
/

passing it into the audit pkg.  that would let you group the statements
together.

bear in mind, concurrent transactions are going to KILL you as the timestamp
shows the modification order, NOT the commit order!  be careful there.

      Rating:
      5
Performance.  July 31, 2003
Reviewer:
      Marcio  from BR
Yes I know this will kill my performance. I've read enough issue here. However,
my problem with that is contractor client REQUIREMENT and didn't see any other
way to do that.
I'm doing a conversion/migration data from sheet Excell to Oracle tables to each
filial of company. I've already finished around 60% of work. But, the guy of
company wants to start work with this before I get done, so I will go on with
migration and he will do modifications. Well, we have problem here ( two schemas
"unsync" ). How can I keep this sync? The company use 817.

Marcio

          Followup:

no, it is not that it'll kill performance -- it is that it'll kill you trying to
come up with the serial order of operations from this audit trail in a
multi-user environment.

Ordering by TIMESTAMP isn't good enough in a multi-user system in every case.
Something to watch out for is all.

      Rating: 5
What do you
      suggest for?  July 31, 2003
Reviewer:
      Marcio  from Br
Any suggest for this case?

          Followup:

it starts to get *really* complicated.

we call it replication and it took a long time to code.

streams in 9ir2 may be of use if you want to get down and dirty.

      Rating: 4
Query on audit
      trail  August 03, 2003
Reviewer:  Marcio
       from br
Again! I've investigated about environment and there no is mass event
update/delete/insert on it. It is no critical table and just head office have
rights to change it. So I thought about your old solution ( trigger dml event )
-- I put sequence on trigger as your advice and re-do test I got something like
that:

ops$marcio@MARCI9I1> @audit_dml
ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1> drop table audit_dml
  2  /

Table dropped.

ops$marcio@MARCI9I1> create table audit_dml
  2  (    timestamp    date,
  3      who            varchar2(30),
  4      op           varchar2(6),
  5      seq          number(6),
  6      tname        varchar2(30),
  7      cname        varchar2(30),
  8      old            varchar2(2000),
  9      new            varchar2(2000)
 10  )
 11  /

Table created.

ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1> drop sequence x#_seq;

Sequence dropped.

ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1> create sequence x#_seq;

Sequence created.

ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1> create or replace package audit_pkg
  2  as
  3      procedure check_val(
  4          l_tname in varchar2,
  5          l_cname in varchar2,
  6          l_new in varchar2,
  7          l_old in varchar2,
  8          l_op in varchar2,
  9          l_seq in number
 10                          );
 11
 12      procedure check_val(
 13          l_tname in varchar2,
 14          l_cname in varchar2,
 15          l_new in date,
 16          l_old in date,
 17          l_op in varchar2,
 18          l_seq in number
 19                          );
 20
 21      procedure check_val(
 22          l_tname in varchar2,
 23          l_cname in varchar2,
 24          l_new in number,
 25          l_old in number,
 26          l_op in varchar2,
 27          l_seq in number
 28                          );
 29  end;
 30  /

Package created.

ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1> create or replace package body audit_pkg
  2  as
  3
  4  procedure check_val( l_tname in varchar2,
  5                       l_cname in varchar2,
  6               l_new in varchar2,
  7                       l_old in varchar2,
  8          l_op in varchar2,
  9          l_seq in number)
 10  is
 11  begin
 12      if ( l_new <> l_old or
 13           (l_new is null and l_old is not NULL) or
 14           (l_new is not null and l_old is NULL) )
 15      then
 16          insert into audit_dml values
 17          ( sysdate, sys_context('USERENV','OS_USER'), l_op, l_seq,
upper(l_tname), upper(l_cname),
 18                               l_old, l_new );
 19      end if;
 20  end;
 21
 22  procedure check_val( l_tname in varchar2, l_cname in varchar2,
 23               l_new in date, l_old in date, l_op in varchar2, l_seq in
number )
 24  is
 25  begin
 26      if ( l_new <> l_old or
 27           (l_new is null and l_old is not NULL) or
 28           (l_new is not null and l_old is NULL) )
 29      then
 30          insert into audit_dml values
 31          ( sysdate, sys_context('USERENV','OS_USER'), l_op,l_seq,
upper(l_tname), upper(l_cname),
 32            to_char( l_old, 'dd-mon-yyyy hh24:mi:ss' ),
 33            to_char( l_new, 'dd-mon-yyyy hh24:mi:ss' ) );
 34      end if;
 35  end;
 36
 37  procedure check_val( l_tname in varchar2, l_cname in varchar2,
 38               l_new in number, l_old in number, l_op in varchar2, l_seq in
number )
 39  is
 40  begin
 41      if ( l_new <> l_old or
 42           (l_new is null and l_old is not NULL) or
 43           (l_new is not null and l_old is NULL) )
 44      then
 45          insert into audit_dml values
 46          ( sysdate, sys_context('USERENV','OS_USER'), l_op, l_seq,
upper(l_tname), upper(l_cname),
 47                                   l_old, l_new );
 48      end if;
 49  end;
 50
 51  end audit_pkg;
 52  /

Package body created.

ops$marcio@MARCI9I1> set echo off
ops$marcio@MARCI9I1> @askq
ops$marcio@MARCI9I1> drop table c;

Table dropped.

ops$marcio@MARCI9I1> drop table p;

Table dropped.

ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1> create table p (
  2     id int constraint p_pk primary key,
  3     y varchar2(20),
  4     z date )
  5  /

Table created.

ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1> create table c (
  2     id int constraint c_pk primary key,
  3     p_id int references p on delete cascade,
  4     z varchar2(20) )
  5  /

Table created.

ops$marcio@MARCI9I1> set echo off
create or replace trigger aud#c
after update or insert or delete on c
for each row
declare
l_seq   number;
l_dml   varchar2(6);
begin
select x#_seq.nextval into l_seq from dual;
if inserting
then l_dml := 'INSERT';
elsif updating
then l_dml := 'UPDATE';
else l_dml := 'DELETE';
end if;
    audit_pkg.check_val( 'c', 'ID', :new.ID, :old.ID, l_dml, l_seq);
    audit_pkg.check_val( 'c', 'P_ID', :new.P_ID, :old.P_ID, l_dml, l_seq);
    audit_pkg.check_val( 'c', 'Z', :new.Z, :old.Z, l_dml, l_seq);
end;
/

Trigger created.

create or replace trigger aud#p
after update or insert or delete on p
for each row
declare
l_seq   number;
l_dml   varchar2(6);
begin
select x#_seq.nextval into l_seq from dual;
if inserting
then l_dml := 'INSERT';
elsif updating
then l_dml := 'UPDATE';
else l_dml := 'DELETE';
end if;
    audit_pkg.check_val( 'p', 'ID', :new.ID, :old.ID, l_dml, l_seq);
    audit_pkg.check_val( 'p', 'Y', :new.Y, :old.Y, l_dml, l_seq);
    audit_pkg.check_val( 'p', 'Z', :new.Z, :old.Z, l_dml, l_seq);
end;
/

Trigger created.

ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1> begin
  2  for i in 1..5
  3  loop
  4          insert into p values ( i, 'DESC-'||i, sysdate );
  5
  6  end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1> begin
  2  for i in 1..10
  3  loop
  4      insert into c values ( i, mod(i,2) + 1, 'VALOR: '||i );
  5  end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1> delete from p where id = 2;

1 row deleted.

ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1> update c set z = 'MUDEI' where id = 10;

1 row updated.

ops$marcio@MARCI9I1>
ops$marcio@MARCI9I1> col WHO format a10
ops$marcio@MARCI9I1> col OP format a6
ops$marcio@MARCI9I1> col TNAME format a4
ops$marcio@MARCI9I1> col CNAME format a4
ops$marcio@MARCI9I1> col OLD format a30
ops$marcio@MARCI9I1> col NEW format a30
ops$marcio@MARCI9I1> select * from audit_dml;

TIMESTAMP WHO        OP       SEQ TNAM CNAM OLD                            NEW
--------- ---------- ------ ----- ---- ---- ------------------------------
------------------------------
03-AUG-03 marcio     INSERT     1 P    ID                                  1
03-AUG-03 marcio     INSERT     1 P    Y
DESC-1
03-AUG-03 marcio     INSERT     1 P    Z
03-aug-2003 23:19:08
03-AUG-03 marcio     INSERT     2 P    ID                                  2
03-AUG-03 marcio     INSERT     2 P    Y
DESC-2
03-AUG-03 marcio     INSERT     2 P    Z
03-aug-2003 23:19:09
03-AUG-03 marcio     INSERT     3 P    ID                                  3
03-AUG-03 marcio     INSERT     3 P    Y
DESC-3
03-AUG-03 marcio     INSERT     3 P    Z
03-aug-2003 23:19:09
03-AUG-03 marcio     INSERT     4 P    ID                                  4
03-AUG-03 marcio     INSERT     4 P    Y
DESC-4
03-AUG-03 marcio     INSERT     4 P    Z
03-aug-2003 23:19:09
03-AUG-03 marcio     INSERT     5 P    ID                                  5
03-AUG-03 marcio     INSERT     5 P    Y
DESC-5
03-AUG-03 marcio     INSERT     5 P    Z
03-aug-2003 23:19:09
03-AUG-03 marcio     INSERT     6 C    ID                                  1
03-AUG-03 marcio     INSERT     6 C    P_ID                                2
03-AUG-03 marcio     INSERT     6 C    Z
VALOR: 1
03-AUG-03 marcio     INSERT     7 C    ID                                  2
03-AUG-03 marcio     INSERT     7 C    P_ID                                1
03-AUG-03 marcio     INSERT     7 C    Z
VALOR: 2
03-AUG-03 marcio     INSERT     8 C    ID                                  3
03-AUG-03 marcio     INSERT     8 C    P_ID                                2
03-AUG-03 marcio     INSERT     8 C    Z
VALOR: 3
03-AUG-03 marcio     INSERT     9 C    ID                                  4
03-AUG-03 marcio     INSERT     9 C    P_ID                                1
03-AUG-03 marcio     INSERT     9 C    Z
VALOR: 4
03-AUG-03 marcio     INSERT    10 C    ID                                  5
03-AUG-03 marcio     INSERT    10 C    P_ID                                2
03-AUG-03 marcio     INSERT    10 C    Z
VALOR: 5
03-AUG-03 marcio     INSERT    11 C    ID                                  6
03-AUG-03 marcio     INSERT    11 C    P_ID                                1
03-AUG-03 marcio     INSERT    11 C    Z
VALOR: 6
03-AUG-03 marcio     INSERT    12 C    ID                                  7
03-AUG-03 marcio     INSERT    12 C    P_ID                                2
03-AUG-03 marcio     INSERT    12 C    Z
VALOR: 7
03-AUG-03 marcio     INSERT    13 C    ID                                  8
03-AUG-03 marcio     INSERT    13 C    P_ID                                1
03-AUG-03 marcio     INSERT    13 C    Z
VALOR: 8
03-AUG-03 marcio     INSERT    14 C    ID                                  9
03-AUG-03 marcio     INSERT    14 C    P_ID                                2
03-AUG-03 marcio     INSERT    14 C    Z
VALOR: 9
03-AUG-03 marcio     INSERT    15 C    ID                                  10
03-AUG-03 marcio     INSERT    15 C    P_ID                                1
03-AUG-03 marcio     INSERT    15 C    Z
VALOR: 10
03-AUG-03 marcio     DELETE    16 C    ID   1
03-AUG-03 marcio     DELETE    16 C    P_ID 2
03-AUG-03 marcio     DELETE    16 C    Z    VALOR: 1
03-AUG-03 marcio     DELETE    17 C    ID   3
03-AUG-03 marcio     DELETE    17 C    P_ID 2
03-AUG-03 marcio     DELETE    17 C    Z    VALOR: 3
03-AUG-03 marcio     DELETE    18 C    ID   5
03-AUG-03 marcio     DELETE    18 C    P_ID 2
03-AUG-03 marcio     DELETE    18 C    Z    VALOR: 5
03-AUG-03 marcio     DELETE    19 C    ID   7
03-AUG-03 marcio     DELETE    19 C    P_ID 2
03-AUG-03 marcio     DELETE    19 C    Z    VALOR: 7
03-AUG-03 marcio     DELETE    20 C    ID   9
03-AUG-03 marcio     DELETE    20 C    P_ID 2
03-AUG-03 marcio     DELETE    20 C    Z    VALOR: 9
03-AUG-03 marcio     DELETE    21 P    ID   2
03-AUG-03 marcio     DELETE    21 P    Y    DESC-2
03-AUG-03 marcio     DELETE    21 P    Z    03-aug-2003 23:19:09
03-AUG-03 marcio     UPDATE    22 C    Z    VALOR: 10                      MUDEI

Can you show me if is possible write a query that return inserts... deletes...
and updates...

I was thinking about

select op || case op
               when 'INSERT' then ' INTO ' || tname || ' VALUES ( '|| new
               when 'UPDATE' then tname || ' set ' || cname || ' = ' || new
               when 'DELETE' then ' from ' || tname || ' where ' || cname || ' =
' || old
             end
  from audit_dml

but, update won't work and delete I have put some WHERE clause.

Thanks in Advanced
Marcio

          Followup:

yes it is possible -- it is just "more code".

you'll probably want to code a stored procedure for it.

      Rating:
      5
Tips  August 04, 2003
Reviewer:
      Marcio  from Br
Tom, I wouldn't like that you do it for me, i just want to some tips/clue how to
do this stored procedure. Could you?
Thanks,

          Followup:

short of writing the code?  no, not really.  come on -- this is just some simple
code here, nothing very complex.  you have everything you need.

      Rating: 4
Really Useful
      stuff  August 04, 2003
Reviewer:  Abhijit
       from Mumbai

      Rating:
      4
Performance  October 17, 2003
Reviewer:
      michael  from CA
Tom,

What is better for performance in an update trigger - the first IF condition or
the second IF condition?

The second is definitely easier to code and read.

create or replace trigger bur$t1
before update on t1 for each row
begin
IF ((:new.name IS NULL AND :old.name IS NOT NULL) or
    (:new.name IS NOT NULL AND :old.name IS NULL) or
    (:new.name <> :old.name)) THEN
  update t2 set name=:new.name where id=:old.id;
END IF;

IF(NVL(:new.name,0) <> NVL(:old.name,0)) then
  update t2 set name=:new.name where id=:old.id;
END IF;
end;
/

Thanks.

          Followup:

*benchmarking*

it is so *easy*....

when I am faced with a question like this -- it is the first thing I do.
Runstats makes it fairly easy.  Consider:

ops$tkyte@ORA920> /*
DOC>
DOC>drop table t1;
DOC>drop table t2;
DOC>
DOC>create table t1 ( name varchar2(30) );
DOC>
DOC>create or replace trigger t1
DOC>before update on t1 for each row
DOC>begin
DOC>IF ((:new.name IS NULL AND :old.name IS NOT NULL) or
DOC>    (:new.name IS NOT NULL AND :old.name IS NULL) or
DOC>    (:new.name <> :old.name)) THEN
DOC>  null;
DOC>END IF;
DOC>end;
DOC>/
DOC>
DOC>create table t2 ( name varchar2(30) );
DOC>
DOC>create or replace trigger t2
DOC>before update on t2 for each row
DOC>begin
DOC>IF(NVL(:new.name,'0') <> NVL(:old.name,'0')) then
DOC>  null;
DOC>END IF;
DOC>end;
DOC>/
DOC>*/
ops$tkyte@ORA920>
ops$tkyte@ORA920> truncate table t1;

Table truncated.

ops$tkyte@ORA920> truncate table t2;

Table truncated.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t1 select decode( mod(rownum,25), 0, null,
object_name ) from all_objects;

31665 rows created.

ops$tkyte@ORA920> insert into t2 select * from t1;

31665 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec runstats_pkg.rs_start

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> update t1 set name = substr(name,1,29)||'x';

31665 rows updated.

ops$tkyte@ORA920> exec runstats_pkg.rs_middle;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> update t2 set name = substr(name,1,29)||'x';

31665 rows updated.

ops$tkyte@ORA920> exec runstats_pkg.rs_stop(10000)
Run1 ran in 377 hsecs
Run2 ran in 667 hsecs
run 1 ran in 56.52% of the time

Name                                  Run1        Run2        Diff

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
269,830     272,593       2,763     98.99%

PL/SQL procedure successfully completed.

that shows that if you do a couple of extra keystrokes and avoid the function
calls to nvl stuff and compare temporaries from functions and such -- it is
faster

      Rating: 4
It can be more
      usefull if ...  October 28, 2003
Reviewer:  Anil
      Sharma  from India
This can even more useful if we can use new and old dynamically.
Is it possible to create one row for each record for changed value i.e
old  f1->value1||f2->value2...etc     new f1->newval1||f2->newval2......etc.

Thanks for usefull solution.

      Rating: 5
Workspace
      Management  November 04, 2003
Reviewer:
      A reader
Hi Tom,
What would you recommend Workspace Management to audit a table or create a DML
triggers like these?
Please tell, if only update is to be audited, then will a trigger be a better
option, or, Workspace Management would still be better?
Also, please tell the changes, which oracle makes to enable workspace mangement,
have side effects? Like the original table becomes a View, how to view
constraints that were applicable on the original table after enabling the
Workspace management. There are certain constraints like after enabling
workspace management, primary key cannot be updated, so if this may sometimes
happen, a trigger would be better?
Thanks for your time.

          Followup:

If workspace mgmt met my requirements and I could work within its documented
implementation constraints -- I would certainly choose that 100% of the time
over writing lots of code.  Absolutely.

There is an entire "application developers guide for workspace mgmt", read it,
see it if meets your needs.  if so, absolutely use it.  If not, write lots of
code that you will maintain yourself forever.

It is the "make vs buy" decision.  You buy lots of stuff with compromises --
meaning, even if workspace mgmt does not meet 100% of your needs (maybe 90%) you
should give some serious consideration to changing your requirements to permit
its use.  Using builtin functionality -- in order to avoid writing scads of code
-- is to me always a good idea.

      Rating: 5
using
      UPDATING() call  December 04, 2003
Reviewer:
      vll  from USA
We are using
if updating('{field name'}) then
  {insert something in audit table}
end if;
instead of
IF ((:new.name IS NULL AND :old.name IS NOT NULL) or
    (:new.name IS NOT NULL AND :old.name IS NULL) or
    (:new.name <> :old.name)) THEN
  {insert something in audit table}
end if;

As for me it's more easy. Am I missing something? May be there are performance
issues with using UPDATING()?

Thank you very much, Tom!

          Followup:

that is dandy -- the original question that prompted this only wanted to audit
changed data (some applications update ALL columns to update a single column)

      Rating: 3
IF updating
      ('colname') - doesn't do the job...  December 04, 2003

Reviewer:  Mark  from USA
If the update statement references the column, it will still write the audit row
even if :old.colval = :new.colval.

      Rating: 5
Using
      UPDATING() - it depends on what we really need  December 05,
      2003
Reviewer:  vll  from USA
I think it's a question of what we really need. If we really need to track only
CHANGES, then you are absolutely right, it will create redundant record in audit
table. But in all the cases I happened to deal with before, we needed to record
UPDATE EVENT. In other words, we NEEDED this record in audit table. Let's say
you have a user that updates some column in the whole table to some value. With
UPDATING() call we have the record in audit table for each record. By using
audit table we can get info about the actual UPDATE event (who, when and etc.)
updated this column for ANY record. With :new.value<>:old.value (plus NULL
checking) approach we have this data only for records that have this column
changed and we are loosing UPDATE EVENT by some user.
Again - it's a question of what we really need. As for me its better to have
MORE data in audit table. If we want to get rid of redundant records we can
always do it later by deleting all duplicate records except of the very first
one historically.
Thank you all!

      Rating: 4
Audit
      Dilemna  December 16, 2003
Reviewer:  Mike
       from Allentown, PA
I have found many of your solutions out here both highly informative and
entertaining. I especially love the rants about barnyard database design.

I have read the entire thread above and found it very useful, but I have a
question about how to find out what value(s) changed in a row by examining audit
trail records. However, let me give some background information. I inherited
this scheme upon arrival.

The developers are programmaticaly generating the audit table ddl and triggers
for each audited table from the Rose Object Model they are using. These are
simple audit triggers, such that all inserts, updates, and deletes result in the
entire row being inserted into an audit table that has all of the production
table columns. In addition to those columns, however, there are 5 extra audit
columns. OPS specifies the operation (insert, update, delete) and then there are
columns to capture the system time, application name, os user, and terminal. In
the production tables themselves, there are extra columns in every table for the
name of the user who created the row and date, and the name of the user who
updated the row and date. This is a web front end and uses a pooled connection,
but the developers have written code to capture the real user id in the app and
it gets passed down to the “security columns” in the production table when they
commit some work. Whenever they do so, as noted above, the entire row is sent to
the counterpart audit table.

Sorry for the long setup, but now here is my immediate problem. For updates
only, the requirement is to capture only what changed in a row by looking at a
row in the audit trail table and comparing it to the last “version” of that row.
I have been told that the dumb (primary) keys will never be updated. Please
picture, a row is inserted with a dumb id as the PK. Then, Column 2 is updated
from "nice" to "naughty". So now there is an audit row showing column 2 with
"naughty" as the value. It has timestamp and user id. Column 5 is then updated
from "check" to "coal". So now there is a row showing Column 2 as "naughty" and
Column 5 as "coal". How can I generate a report that will only show what
value(s) changed when a row has been labeled “update” in the audit table. The
old and new values are required. So they would want to see something like
PKVal: 123 Column2  OldValue: Nice   NewValue: Naughty     Updt User: John
Updt Date: 03-DEC-2003
PKVal: 123 Column5  Old Value: Check New Value: Coal     Updt User: Mary
Updt Date: 04-DEC-2003

In addition, they would like to see the entire history of a specific row (or
rows) when requested, including every update with old and new values as shown
above.

I work with a developer who, inspired by one of your solutions above, developed
complex triggers with logic to do this up front, but the PM voted it down. In
our alternate approach, the audit tables had “old” and “new” columns for each
column in the production tables. For inserts, only the NEW values were
populated. For deletes, the OLD values were populated. For updates, we captured
the new and old values ONLY for the column(s) that changed. We thought is was a
good approach, but the PM signalled "thumbs down" and said we should stick to
the simple triggers and extract the “deltas” (column value differences) with
code at the back end. Any ideas about how to get these "deltas" will be
appreciated.

          Followup:

using lag you can do this pretty easy.  consider:

ops$tkyte@ORA9IR2> select * from t;

        ID DT                C1 C2        C3
---------- --------- ---------- --------- --------------------
         1 27-OCT-03          1 16-DEC-03 nice
         1 28-OCT-03          1 16-DEC-03 naughty
         1 29-OCT-03          1 14-DEC-03 naughty
         1 30-OCT-03          2 14-DEC-03 naughty
         1 30-OCT-03          3 13-DEC-03 nasty
         2 27-OCT-03         11 16-NOV-03 xnice
         2 28-OCT-03         11 16-NOV-03 xnaughty
         2 29-OCT-03         11 14-NOV-03 xnaughty
         2 30-OCT-03         12 14-NOV-03 xnaughty
         2 30-OCT-03         13 13-NOV-03 xnasty

10 rows selected.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select id, dt,
  2         nullif( c1, lag(c1) over (partition by id order by dt) ) c1,
  3         nullif( c2, lag(c2) over (partition by id order by dt) ) c2,
  4         nullif( c3, lag(c3) over (partition by id order by dt) ) c3
  5    from t;

        ID DT                C1 C2        C3
---------- --------- ---------- --------- --------------------
         1 27-OCT-03          1 16-DEC-03 nice
         1 28-OCT-03                      naughty
         1 29-OCT-03            14-DEC-03
         1 30-OCT-03          2
         1 30-OCT-03          3 13-DEC-03 nasty
         2 27-OCT-03         11 16-NOV-03 xnice
         2 28-OCT-03                      xnaughty
         2 29-OCT-03            14-NOV-03
         2 30-OCT-03         12
         2 30-OCT-03         13 13-NOV-03 xnasty

10 rows selected.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select id, dt,
  2         decode( c1, old_c1, null, ' c1 was: ' || old_c1 || ' is: ' || c1 )
||
  3         decode( c2, old_c2, null, ' c2 was: ' || old_c2 || ' is: ' || c2 )
||
  4         decode( c3, old_c3, null, ' c3 was: ' || old_c3 || ' is: ' || c3 )
  5    from (
  6  select id, dt, c1, c2, c3,
  7         lag(c1) over (partition by id order by dt) old_c1,
  8         lag(c2) over (partition by id order by dt) old_c2,
  9         lag(c3) over (partition by id order by dt) old_c3
 10    from t )
 11    ;

        ID DT
---------- ---------
DECODE(C1,OLD_C1,NULL,'C1WAS:'||OLD_C1||'IS:'||C1)||DECODE(C2,OLD_C2,NULL,'C2WAS:
'||OLD_C2||'IS:'||C2)||DECODE(C3,OLD_C3,NULL,'C3WA
---------------------------------------------------------------------------------
--------------------------------------------------
         1 27-OCT-03
 c1 was:  is: 1 c2 was:  is: 16-DEC-03 c3 was:  is: nice

         1 28-OCT-03
 c3 was: nice is: naughty

         1 29-OCT-03
 c2 was: 16-DEC-03 is: 14-DEC-03

         1 30-OCT-03
 c1 was: 1 is: 2

         1 30-OCT-03
 c1 was: 2 is: 3 c2 was: 14-DEC-03 is: 13-DEC-03 c3 was: naughty is: nasty

         2 27-OCT-03
 c1 was:  is: 11 c2 was:  is: 16-NOV-03 c3 was:  is: xnice

         2 28-OCT-03
 c3 was: xnice is: xnaughty

         2 29-OCT-03
 c2 was: 16-NOV-03 is: 14-NOV-03

         2 30-OCT-03
 c1 was: 11 is: 12

         2 30-OCT-03
 c1 was: 12 is: 13 c2 was: 14-NOV-03 is: 13-NOV-03 c3 was: xnaughty is: xnasty

10 rows selected.

I'll leave any more formatting to you -- but you get the gist.

      Rating:
      5
  December 17, 2003
Reviewer:
      A reader  from UK.
Just a quick thought for Mike to take to his PM.

We have the same situation with regards auditting.

Our requirement is further complicated in that the client,
when viewing audit histories, wishes to be able to see what a "record" would
have shown at a given point in time.

This sounds no different than that already discribed.

However, consider foreign key lookup values.

Eg a lookup table 'COLOUR'
Point In Time    ID    CODE
1                1     RED
1                2     BLUE
2                1     GREEN
2                2     YELLOW.

Then when table T (id, f_colour_id) changes from
(1,1) to (1,2). What will the client see when viewing the "full record" audit
trail.

Does he see the change as RED to BLUE
or GREEN to YELLOW
or RED to YELLOW.

December 16, 2003 wallacel -- Thanks for the question regarding
      "practice backup recovery and have a question", version 8.1.6

      You Asked
Tom:

I am practice backup and recover, one scenario is:

1. make a cold whole database backup, ( include all datafile,
controlfile);
2. do some batch job
3. media crash destroy all datafile, controlfile and ONLINE REDO
LOGFILES.( what i do is shutdown database, manually delete all
datafile,controlfile and online redo logfiles)
4  media problem being fixed.
*****************************************************************

*****************************************************************

5. restore backup. (but THERE ARE NO ONLINE REDO LOGFILES
anywhere)
6. startup mount
7. alter database backup controlfile to trace;
8. get error create controlfile fail since no online redo
logfile available.
9. how should i recover online redo logfile?
i try to drop all online redo log and recreate it, but the
current redo log can't be droped , and also i can't switch log
because database is not open.

Regards,

      and we said...
You cannot recover the online redo logfiles that you erased (you
can recover but only upto the point in time right before those
logs were filled with data).  Typcially you have multiplexed
redo logs so that at least one copy exists somewhere -- same
with control files.  Only if you lose every single drive would
you lose all redo.  This is why it is important to have multiple
archive destinations and why you should get ARCHIVES off of the
machine itself to some other location.

See

http://technet.oracle.com/doc/oracle8i_816/server.816/a76993/recoscen.htm#11724
"Recovering After the Loss of Online Redo Log Files"
Review &
      Followup

      Rating: 5
Restore and
      Recover With RMAN  October 10, 2001
Reviewer:
      Milton Rodriguez  from Venezuela
Tom. I have three script to take backup with RMAN, all the scripts are almost
the same, the difference is the incremental level and the format of the channel:
#########
create scrip backup_weekly {
allocate channel d1 type disk
format '/backup/weekly/week_%d_%s_%p.bak';
backup
      incremental level = 1
      filesperset 2 tag=weekly
      (database include current controlfile);
      sql 'alter database archive log current';
release channel d1; }
#########
well, I have erased all the datafiles, controlfiles and redologs and I have
restored all the datafiles, controlfiles and archive log with rman, but when i
have tried to recover the database i have had problem because of the redologs,
the scn actual are in the redolog and not in the archive logs. How can I solve
this?

          Followup:

Sorry -- please contact support.  I don't know fully what I'm looking at here.
I don't know why you would erase all of that stuff (never the redo logs -- you
never backup online redo logs -- you don't every want to erase them? )

      Rating:
      3
misunderstand  October 11, 2001
Reviewer:
      Catherine.  from England, UK
Tom, maybe you misunderstood what info was required from you. This is a valid
scenario to test out (removal of redo logs) as a) someone may have deleted them
b) media failure means you've lost them.

After 8.0.x you do not need online redo logs to do a recovery as they are
recreated as part of the recovery. Think its something like recover database
until time '....' or recover database until an scn number. Basically a point in
time that was not lost in the online redo logs. when you open the database with
resetlogs the online redos will be created.

If you've lost controlfiles as well, either use backup controlfile option or
recreate the control file using sql.

          Followup:

Oh trust me -- I know all about that (and actually -- with the hot backup you
don't need the online redo log files since way before version 8, its not a new
feature -- the PIT (point in time recovery) has been around for a very long
time.  You open the database with the resetlogs option after you cancel the
recovery).

I'm just not going to copy the entire backup and recovery manual here.  He
didn't give enough information to give him good steps (and actually, the steps
are all documented in the backup and recovery guide).

It sounds like he is trying to do a simple consistent restore of the database,
but doesn't really supply the exact error message he is getting, etc.  (that and
the fact that I ask people specifically not to use this space as a place to ASK
questions -- but rather to comment on or clarify the existing question on the
page)

      Rating: 2
Look tar
      1675995.995  October 23, 2001
Reviewer:  Milton
      Rodriguez  from Venezuela
Thank you !

      Rating: 4
rman -- point
      in time recovery  January 25, 2003
Reviewer:
      Baqir Hussain  from Emeryville, CA USA
Tom,
I am very new to rman and learning a lot by reading over this site and oracle9.2
recovery documentation.  I am still missing some links in understanding the
following test scenerio in rman.  I would appreciate your help in advance:

1. db is running  on a machine ORACLE1, in archive mode, and using rman
scripting to copy archive log every hour to a disk on other system ORACLE2.
2. also has recovery catalog on a third system TESTORA
3. at 11pm took a compelete database back using rman and sotred all
datafiles,controlfile to ORACLE2.
4. at 11am db oracle1 crached
How would we achive point in time recovery at 11am without havivg a current
control file and/or on-line redo log files??
Without the current control file and/or on redo logs, how the oracle will know
to do a point in time recovery by looking at the archive logs?? (need to clear
some basic cocept).

          Followup:

you would follow the directions for recovering after loss of all online redo
logs and control files.

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96566/rcmrecov.htm#1009235

      Rating: 5
copy redo log
      for online backups  March 12, 2003
Reviewer:
      A reader
hi

can we copy online redolog for an online backup? ay drawback or benefit?

          Followup:

NEVER NEVER NEVER copy online redolog when your database is in archivelog mode

The ONLY thing you will achieve by doing that is:

o wasting cycles copying something you can never restore with
o accidently wiping out the current online redo logs, losing data, when you do
  restore.

they serve no purpose in the backup and the only thing I've seen people do with
them is accidently restore them over the REAL online's -- wiping out their last
transactions forever..

      Rating: 5
what do you
      mean by...  March 12, 2003
Reviewer:
      A reader
o wasting cycles copying something you can never restore with

we cant never use redologs from a hot backup? there must be some situations we
can use it... (incomplete recovery)

for example consider I am making online backup,

SCN 100
SCN 200 finishes the backup and I copy the archived logs and redologs (which
contains SCN up to 220)

one day I need to restore the backup and noticed I only have archived logs till
SCN 190 only. Cant I use the redologs I copied to recover till SCN 220?

          Followup:

you have done it totally wrong.

do it right.

Look - there is one thing and one thing only a DBA is not allowed to mess up.
We can fix everything EXCEPT a bad backup.  Everything else is just a mistake.
Screwing up your backups is far far beyond a mistake -- it is grounds for
immediate "good bye, you are the weakest link".

could you, in theory, perhaps -- maybe -- get lucky and be able to use them?
maybe, sometimes, on tuesday when it is raining.

Is it smart (NOT)
Is it the right way (NOT)
Is it even a teeny tiny bit reliable (NOT)

I fail to see how your example even "makes sense".  If you don't have the
archives past 190 -- how do you have the corresponding redo logs and how did you
correlate them.  I mean the last archives OVERLAP with these onlines.

Don't even try to dream up hypothetical situations where they might be deemed
"useful" because all of those situations mean YOU HAVEN'T DONE YOUR JOB IN THE
FIRST PLACE.

      Rating:
      3
  March 14, 2003
Reviewer:  Vj
       from india
These are the setups and error messages and it is not in archive mode.

NOS : Novel Netware 5.1
Oracle : version no is 8.1.7

Message :

00314 : log 1 of thread 1 , expected sequence # 40683 doesn't match # 40685

00312 : online log 1 thread 1 : '%path%'  log2orcl.ora

system is asking for a immediate recovery of the logfile

PLEASE HELP

i could able to startup mount.

Rgds

          Followup:

contact support.  insufficient data -- don't panic, file a tar.

      Rating: 5
FURTHER ON THIS
      ISSUE OF RECOVERY  March 15, 2003
Reviewer:
      Anand H S  from New Delhi, India
Hi Tom,
Just like the above mentioned scenario, I tried a similiar experiment with the
folowing changes:
1. My database runs in archive log mode.
2. I have a cold backup of the entire database (including datafiles,controfiles,
redo log files) except the archive log files.

Now this is what I did:
1. I did a clean shutdown of my database and then took a backup of all the
files.
2. started up the database and did an large amount of DML.
3. I committed it.
4. I shutdown the database and delete the files.
5. I restore from the backup.
6. BUT THESE FILES DONT HAVE THE HUGE AMOUNT OF DMLS.
7. The question is how to implement the data in the archive log, since it is a
clean shutdown backup, database does not ask for MEDIA RECOVERY.

  One thing that comes to my mind is that I use LOGMINER to extract all the sql
and fire them as a script to get back my data into consistent state!

Am I on the right track??

Regards,
Anand H S

          Followup:

7) have you read the backup and recovery guides?

do you understand the somewhat non-trivial relationship between control files,
online redo log files, archives?

you restored everything to exactly the way it was when the db was shutdown. when
you started up, it was NO DIFFERENT then when you started up after the cold
backup.

You do NOT want to backup everything as you are.

Specifically --

o online redo logs, what a waste.
o control files in binary format.  The only thing a binary control file is good
for is -- well, creating a text version of one so you can do incomplete (cancel
based) media recovery.

You really (really -- i mean like "really") need to read those guides and
understand them if you are the DBA.

If you are not the DBA, then you don't have to worry about it -- ask them to do
it for you.

Your problem - you restored the control files when you should have followed the
procedure for "total loss of all online control files"

Never but never but never restore the online redo log files.

      Rating: 5
create control
      file   April 30, 2003
Reviewer:
      A reader
Tom,

Assuming that all control files are lost (it should never happen if multiplexed)
and I have a backup controlfile to trace. I edit that trace file and run the
create controlfile script to create a brand new controlfile. I was thinking
about it. Now the situation is that all of the information including the
checkpoint info of all the database files were lost when I lost the
controlfiles. How oracle creates the controlfile back with all of the
information reflecting the current status of the database. That too, I will be
running the create controlfile script in nomount stage. At this stage, how
Oracle gets all of the information. Can Oracle read the file headers to get the
info. The first statement in create controlfile says "use database". Could you
please explain, how oracle does it? Thanks.

          Followup:

http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96572/osrestore.htm#28521
you would recreate the control files (basically tells us where everything,
include logs, are)

you recover database (reads through to the end of the logs -- like instance
recover would)

thats it -- it is like instance recovery -- the datafiles headers have enough.

      Rating:
      3
reset-logs  May 08, 2003
Reviewer:
      vj  from in
Dear Tom..

After 'database mounted' message line following errors are coming

ORA-00312
-------------------

00312, 00000, "online log %s thread %s: '%s'"
// *Cause:  This message reports the filename for details of another message.
// *Action: Other messages will accompany this message. See the
//          associated messages for the appropriate action to take.
$ oerr ora 00341

ORA-00341
----------------
00341, 00000, "log %s of thread %s, wrong log # %s in header"
// *Cause:  The internal information in an on-line log file does not
//          match the controlfile.
// *Action: Restore correct file or reset logs.

ORA-00312: online log 1 thread 1: '/ORACLE/ORADATA/H817/REDO01.LOG'
ora-314

ORA-00314: log %s of thread %s, expected sequence# %s doesn't match %s"
Cause: The online log is corrupted or is an old version.
Action: Find and install correct version of log or reset logs.

What can be done ? How can we reset the log files? The database is running on
noarchieve log.

          Followup:

first -- don't panic, don't do anything rash - in fact, unless you know what you
are doing, don't do anything (other then shutdown abort and do a full OS backup
so you can at least get to this point again)

contact support -- not knowing how you got to where you are, lots of information
-- I'm not going to through out random commands that could get you into more
trouble then you are.

fortunately, the data must not be important - that is what noarchivelog means.
It means you have been willing from day 1 to lose this database and go back to
your last full backups.

      Rating: 4
test recovery
      using create controlfile  June 05, 2003
Reviewer:
      Reader
Please see below. I thought recover tablespace can be used only in archivelog
mode. As you can see below,i could use it in noarchivelog mode as well. Please
help me to understand. Thanks.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

SQL> alter database backup controlfile to trace;

Database altered.

SQL> shutdown immediate;
Database closed.
Database dismounted.
ORACLE instance shut down.

-- I deleted all of the controlfiles.

SQL> startup
ORACLE instance started.

Total System Global Area  135338868 bytes
Fixed Size                   453492 bytes
Variable Size             109051904 bytes
Database Buffers           25165824 bytes
Redo Buffers                 667648 bytes
ORA-00205: error in identifying controlfile, check alert log for more i

SQL> @c:\cre_ctl.sql <----- create controlfile script from backup controlfile
ORA-01081: cannot start already-running ORACLE - shut it down first

Control file created.

ORA-00283: recovery session canceled due to errors
ORA-00264: no recovery required

Database altered.

Tablespace altered.

SQL> select name,open_mode from v$database;

NAME      OPEN_MODE
--------- ----------
TEST      READ WRITE

SQL> create tablespace test
  2  datafile 'C:\oracle\oradata\test\test01.dbf' size 5M
  3  uniform size 128k;

Tablespace created.

NOTE: I DID NOT TAKE A BACKUP OF CONTROL FILE TO TRACE after creating test
tablespace.

SQL> connect scott/tiger;
Connected.
SQL> create table t (x number)
  2  tablespace test;

Table created.

SQL> insert into t values (10);

1 row created.

SQL> commit;

Commit complete.

SQL> connect /as sysdba;
Connected.
SQL> shutdown immediate;
Database closed.
Database dismounted.
ORACLE instance shut down.

-- HERE AGAIN I DELETED ALL OF THE CONTROLFILES.

SQL> startup
ORACLE instance started.

Total System Global Area  135338868 bytes
Fixed Size                   453492 bytes
Variable Size             109051904 bytes
Database Buffers           25165824 bytes
Redo Buffers                 667648 bytes
ORA-00205: error in identifying controlfile, check alert log for more i

SQL> @c:\cre_ctl.sql <-------I PURPOSELY USED THE SAME SCRIPT TO RECREATE
CONTROLFILE

ORA-01081: cannot start already-running ORACLE - shut it down first

Control file created.

ORA-00283: recovery session canceled due to errors
ORA-00264: no recovery required

Database altered.

Tablespace altered.

SQL> select tablespace_name from dba_tablespaces;

TABLESPACE_NAME
------------------------------
SYSTEM
UNDOTBS1
CWMLITE
DRSYS
EXAMPLE
INDX
ODM
TOOLS
USERS
XDB
TEMP1

TABLESPACE_NAME
------------------------------
TEST

12 rows selected.

SQL> select * from scott.t;
select * from scott.t
                    *
ERROR at line 1:
ORA-00376: file 11 cannot be read at this time
ORA-01111: name for data file 11 is unknown - rename to correct file
ORA-01110: data file 11: 'C:\ORACLE\ORA92\DATABASE\MISSING00011'

SQL> col file_name for a55
SQL> select file_name,tablespace_name from dba_data_files
  2  where tablespace_name = 'TEST';

FILE_NAME
-------------------------------------------------------
TABLESPACE_NAME
------------------------------
C:\ORACLE\ORA92\DATABASE\MISSING00011
TEST

SQL> alter tablespace test offline immediate;

Tablespace altered.

SQL> alter tablespace test rename
  2  datafile 'C:\ORACLE\ORA92\DATABASE\MISSING00011'
  3  to 'C:\oracle\oradata\test\test01.dbf';

Tablespace altered.

SQL> alter tablespace test online;
alter tablespace test online
*
ERROR at line 1:
ORA-01113: file 11 needs media recovery
ORA-01110: data file 11: 'C:\ORACLE\ORADATA\TEST\TEST01.DBF'

SQL> archive log list
Database log mode              No Archive Mode <------------- NOARCHIVELOG MODE!
Automatic archival             Disabled
Archive destination            C:\oracle\ora92\RDBMS
Oldest online log sequence     2
Current log sequence           4

SQL> recover tablespace test; <---- hOW IS IT POSSIBLE IN NOARCHIVELOG MODE?
Media recovery complete.
SQL> alter tablespace test online;

Tablespace altered.

SQL> select * from scott.t;

         X
----------
        10

          Followup:

it just used the online redo logs. its always been "possible"

      Rating:
      4
ok  June 05, 2003
Reviewer:  Reader

If i had done alter system switch logfile a couple of times until it gets
overwritten, then, oracle would not have let me use recover tablespace command
in noarchivelog mode? I was under the impression, oracle would not let me use
recover tablespace command in noarchivelog database. it is strange.

      Rating: 4
Practise backup
      and Recovery  July 10, 2003
Reviewer:  js
       from INDIA
Hi Sir,

When the last time i asked you certain questions relating to the domain of
backup/recovery you provided me the link of ORACLE Doc. .I have gone through it
in a fairly good manner but perhaps my mind is not that sharp and i still have
some doubts and so i am bothering you once
again (I am sorry for that).
Here are my queries...

1. When one issues "Recover Database untill cancel" then  what's the stopping
condition for this recovery process ? Normally the DBA itself stops ("Cancel")
the process after any desired archive file but in certain cases it doesn't
proceeds any further( or stops itself, e.g. when a fully consistent cold backup
is restored and we have to generate EMPTY REDO LOGS and the command is issued
under these circumstances).

Now if the reason for the above is that ORACLE stops the process as soon as it
finds a consistent state between Control File and Data File, then each time the
DataBase is shut down then each of those states (when it is shut down) should be
counted as a consistent state and this probably means that one cannot recover
the DataBase beyond a certain point (i.e. shutdown/startup) but ORACLE itself
certainly goes beyond .

2. Consider another scenerio..

 suppose ... time A.   i take a database hot backup [control/datafiles]
             time B. ...
             time C. ...
             time D. ... add  another tablespace named "Test"
             time E. ...
             time F .... drop the tablespace
             Time G
             Time H ...  now i have current controlfile and datafiles. ...

and now i m trying  to recover my database with [ TIME A ] backup.. then at
"TIME D" oracle
automaticaly adds an unnamed datafile ... [ cause the  archive files contain
such command ] ..
its all fine ...

but suppose if i do the same but this time with [ TIME H control file i.e.
current controlfile ]
and "TIME A" DATAFILES then oracle does not adds that  unnamed datafile ... Pls
Explain ...

3. Yet another scenerio ...

             Time A  -- Hot backup of all datafiles ...
             Time B. ...
             Ttme C. ...
             Time D. ...
             Time E. ...
             Time F. ...
             Time G.
             Time H. .. At this time i lose all my datafiles and redo logs ...[
Except current controlfile ]
now i if i try an incompelete recovery Using Time A datafiles and current
controlfile then i find that sometimes
the database does't opens as per the Archive File Of [ TIME B or TIME C or else
] "says : System file require more recovery"  but opens using any other archive
file ...[ TIME D or any TIME etc.. ] .. So .. pls Explain why database
can't open at using certain archvie files  while it can on certain others ...

          Followup:

1) with a recover until cancel, it'll stop when
   a) the datafiles get caught up to the control files
   b) the dba cancels the recovery

shutdowns don't come into play -- it's driven by what the control file says is
"the end"

2) you should optimally be using time H's control file -- cause then Oracle
understands what the end result should look like.  By using the old backup
control file, it does -- so it doesn't know "that file isn't relevant" so it
creates and and maintains it.

3) that means time b and or c actually predate the backup of system at time a or
were generated while the backup was taking place.

you need to catch up all of the datafiles to at least the SAME point in time --
that is just telling you you haven't yet HIT that point in time.

If you backup at time A and then you switch logfile and copy all of the
archives -- that represents the least amount of "stuff" you need to fully
restore and roll forward through.  So, I would guess that time b and c represent
archive redo logs really and they were the active online redo logs at time a.

      Rating: 4
Thanx
      Sir  July 11, 2003
Reviewer:  Js  from
      INDIA
Thanx Very Much.

      Rating: 4
Once
      Again  July 11, 2003
Reviewer:  Js
       from INDIA
hello Sir  .. once again ...

... i was trying to do this practicaly ...

** select tablespace_name,name from v$datafile_header;

TABLESPACE_NAME                NAME
------------------------------ ------------------------------
SYSTEM                         C:\ORACLE\DB1\SYSTEM.DBF
UNDO                           C:\ORACLE\DB1\UNDO.DBF
MLM                            C:\ORACLE\DB1\MLM.DBF

** alter tablespace system begin backup;
** alter tablespace undo begin backup;
** alter tablespace mlm begin backup;

** ho copy c:\oracle\db1\*.dbf    c:\oracle\db1\backup\

** alter tablespace system end backup;
** alter tablespace mlm end backup;
** alter tablespace undo end backup;
**
** alter database backup controlfile to 'c:\oracle\db1\control1.ora' ;
** alter system switch logfile;

System altered.

** /

System altered.

** /

System altered.

** create tablespace test datafile 'c:\oracle\db1\test.dbf' size 1m;

Tablespace created.

** create table a tablespace test as select * from dict;

Table created.

**
** alter system switch logfile;

System altered.

** /

System altered.

** select dbms_flashback.get_system_change_number from dual;

GET_SYSTEM_CHANGE_NUMBER
------------------------
                 1097235

**
**
** alter system switch logfile;

System altered.

** /

System altered.

** drop tablespace test including contents and datafiles;

Tablespace dropped.

**
**
**
** shut
Database closed.
Database dismounted.
ORACLE instance shut down.

** ho del c:\oracle\db1\*.dbf

** ho copy c:\oracle\db1\backup\*.dbf  c:\oracle\db1\

**
**
** conn / as sysdba
Connected to an idle instance.
** startup
ORACLE instance started.

Total System Global Area   59535032 bytes
Fixed Size                   282296 bytes
Variable Size              33554432 bytes
Database Buffers           25165824 bytes
Redo Buffers                 532480 bytes
Database mounted.
ORA-01113: file 1 needs media recovery
ORA-01110: data file 1: 'C:\ORACLE\DB1\SYSTEM.DBF'

** recover database until change  1097235 ;
ORA-00279: change 1097144 generated at 07/11/2003 17:34:07 needed for thread 1
ORA-00289: suggestion : C:\ORACLE\DB1\ARCHIVE\DB1__1.ARC
ORA-00280: change 1097144 for thread 1 is in sequence #1

Specify log: {<RET>=suggested | filename | AUTO | CANCEL}

ORA-00279: change 1097166 generated at 07/11/2003 17:37:03 needed for thread 1
ORA-00289: suggestion : C:\ORACLE\DB1\ARCHIVE\DB1__2.ARC
ORA-00280: change 1097166 for thread 1 is in sequence #2
ORA-00278: log file 'C:\ORACLE\DB1\ARCHIVE\DB1__1.ARC' no longer needed for this
recovery

Specify log: {<RET>=suggested | filename | AUTO | CANCEL}

ORA-00279: change 1097168 generated at 07/11/2003 17:37:07 needed for thread 1
ORA-00289: suggestion : C:\ORACLE\DB1\ARCHIVE\DB1__3.ARC
ORA-00280: change 1097168 for thread 1 is in sequence #3
ORA-00278: log file 'C:\ORACLE\DB1\ARCHIVE\DB1__2.ARC' no longer needed for this
recovery

Specify log: {<RET>=suggested | filename | AUTO | CANCEL}

ORA-00279: change 1097172 generated at 07/11/2003 17:37:13 needed for thread 1
ORA-00289: suggestion : C:\ORACLE\DB1\ARCHIVE\DB1__4.ARC
ORA-00280: change 1097172 for thread 1 is in sequence #4
ORA-00278: log file 'C:\ORACLE\DB1\ARCHIVE\DB1__3.ARC' no longer needed for this
recovery

Specify log: {<RET>=suggested | filename | AUTO | CANCEL}

ORA-00279: change 1097233 generated at 07/11/2003 17:37:56 needed for thread 1
ORA-00289: suggestion : C:\ORACLE\DB1\ARCHIVE\DB1__5.ARC
ORA-00280: change 1097233 for thread 1 is in sequence #5
ORA-00278: log file 'C:\ORACLE\DB1\ARCHIVE\DB1__4.ARC' no longer needed for this
recovery

Specify log: {<RET>=suggested | filename | AUTO | CANCEL}

Log applied.
Media recovery complete.

**  alter database open resetlogs;

Database altered.

**
** desc a
 Name
 -----------------------------------------------------------------------
 TABLE_NAME
 COMMENTS

** select * from a;
select * from a
              *
ERROR at line 1:
ORA-00376: file 4 cannot be read at this time
ORA-01111: name for data file 4 is unknown - rename to correct file
ORA-01110: data file 4: 'C:\ORACLE\ORA90\DATABASE\MISSING00004'

** select tablespace_name from dba_tablespaces;

TABLESPACE_NAME
------------------------------
SYSTEM
UNDO
MLM
TEST

** select name from v$datafile;

NAME
--------------------------------------
C:\ORACLE\DB1\SYSTEM.DBF
C:\ORACLE\DB1\UNDO.DBF
C:\ORACLE\DB1\MLM.DBF
C:\ORACLE\ORA90\DATABASE\MISSING00004

** alter database create datafile 'C:\ORACLE\ORA90\DATABASE\MISSING00004'
  2  as
  3  'c:\oracle\db1\test.dbf';
alter database create datafile 'C:\ORACLE\ORA90\DATABASE\MISSING00004'

*
ERROR at line 1:
ORA-01178: file 4 created before last CREATE CONTROLFILE, cannot recreate
ORA-01111: name for data file 4 is unknown - rename to correct file
ORA-01110: data file 4: 'C:\ORACLE\ORA90\DATABASE\MISSING00004'

...

no luck ...

but i was able to recover this using old controlfile ...
oracle added a unnamed datafile ..
alter database create datafile .......
alter database open resetlogs ...

pls expl. how can i recover this tablespace using current controlfile ...

Thanx ...

          Followup:

you should have just read the error message:

ERROR at line 1:
ORA-00376: file 4 cannot be read at this time
ORA-01111: name for data file 4 is unknown - rename to correct file
ORA-01110: data file 4: 'C:\ORACLE\ORA90\DATABASE\MISSING00004'

[tkyte@tkyte-pc Desktop]$ oerr ora 1111
01111, 00000, "name for data file %s is unknown - rename to correct file"
// *Cause:  The data file was missing from a CREATE CONTROLFILE command or
//          backup controlfile recovery was done with a controlfile that
//          was saved before the file was created.
// *Action: Rename the MISSING file to the name of the real file.
[tkyte@tkyte-pc Desktop]$

you tried to create the datafile, when it told you "please rename the file"

the current control file says "I demand you give me a file named
c:\oracle\db1\test.dbf"

the old control file didn't know the name of the file in the first place.

      Rating:
      3
Re:  July 12, 2003
Reviewer:  js
       from INDIA
Sir, if u can give me an example ...of this ..
.. i would be thankfull to u ...

          Followup:

please don't post page after page after page of stuff -- keep it lean and mean.

anyway, just check your work more thoroughly:

** alter database rename file 'C:\ORACLE\ORA90\DATABASE\MISSING00004'
  2  to 'c:\orale\db1\test.dbf'
  3  /
alter database rename file 'C:\ORACLE\ORA90\DATABASE\MISSING00004'
*
ERROR at line 1:
ORA-01511: error in renaming log/data files
ORA-01141: error renaming data file 4 - new file 'c:\orale\db1\test.dbf' not
found
ORA-01111: name for data file 4 is unknown - rename to correct file
ORA-01110: data file 4: 'C:\ORACLE\ORA90\DATABASE\MISSING00004'
ORA-27041: unable to open file
OSD-04002: unable to open file
O/S-Error: (OS 3) The system cannot find the path specified.

for starters.  but -- offline the tablespace (if it is not already), rename the
file in the OS, then issue the PROPER and CORRECT name in the alter database
command.

Or, if you like MISSING00004 as the name, you can rename test.dbf to
missing00004.dbf without renaming at the OS level.

      Rating:
      3
Same  July 12, 2003
Reviewer:  Js
       from INDIA
  1* select tablespace_name,name from v$datafile_header

TABLESPACE_NAME                NAME
------------------------------ ------------------------------
SYSTEM                         C:\ORACLE\DB1\SYSTEM.DBF
UNDO                           C:\ORACLE\DB1\UNDO.DBF
MLM                            C:\ORACLE\DB1\MLM.DBF

** get 0
  1* alter system switch logfile
** /

System altered.

** /

System altered.

** alter tablespace system begin backup;

Tablespace altered.

** alter tablespace undo begin backup;

Tablespace altered.

** alter tablespace mlm begin backup;

Tablespace altered.

** ed test

** ho copy c:\oracle\db1\*.dbf  c:\oracle\db1\backup\

**
**
** alter tablespace system end backup;

Tablespace altered.

** alter tablespace undo end backup;

Tablespace altered.

** alter tablespace mlm end backup;

Tablespace altered.

** get 0
  1* alter system switch logfile
** /

System altered.

** /

System altered.

** /

System altered.

** create tablespace test datafile 'c:\oracle\db1\test.dbf'  size 1m
  2  /

Tablespace created.

**
** create table a tablespace test as select * from dict
  2  /

Table created.

** alter system switch logfile
  2  /

System altered.

**
** alter system switch logfile
  2  /

System altered.

**
** alter system switch logfile
  2  /

System altered.

**
** select dbms_flashback.get_system_change_number from dual
  2  /

GET_SYSTEM_CHANGE_NUMBER
------------------------
                 1179194

**
** alter system switch logfile
  2  /

System altered.

** alter system switch logfile
  2  /

System altered.

** drop tablespace test including contents and datafiles
  2  /

Tablespace dropped.

**
**
**
** shut
Database closed.
Database dismounted.
ORACLE instance shut down.
**
**
** conn / as sysdba
Connected to an idle instance.
**
**
** ho del c:\oracle\db1\*.dbf             --- Delete all Datafiles

** ho  copy c:\oracle\db1\backup\*.dbf  c:\oracle\db1\  --- restore backup

** conn / as sysdba
Connected to an idle instance.
** startup
ORACLE instance started.

Total System Global Area   59535032 bytes
Fixed Size                   282296 bytes
Variable Size              33554432 bytes
Database Buffers           25165824 bytes
Redo Buffers                 532480 bytes
Database mounted.
ORA-01113: file 1 needs media recovery
ORA-01110: data file 1: 'C:\ORACLE\DB1\SYSTEM.DBF'

                                           <<--  Change 1179194
** recover database until change 1179194;
ORA-00279: change 1179113 generated at 07/12/2003 14:38:46 needed for thread 1
ORA-00289: suggestion : C:\ORACLE\DB1\ARCHIVE\DB1__5.ARC
ORA-00280: change 1179113 for thread 1 is in sequence #5

Specify log: {<RET>=suggested | filename | AUTO | CANCEL}

ORA-00279: change 1179132 generated at 07/12/2003 14:40:40 needed for thread 1
ORA-00289: suggestion : C:\ORACLE\DB1\ARCHIVE\DB1__6.ARC
ORA-00280: change 1179132 for thread 1 is in sequence #6
ORA-00278: log file 'C:\ORACLE\DB1\ARCHIVE\DB1__5.ARC' no longer needed for this
recovery

Specify log: {<RET>=suggested | filename | AUTO | CANCEL}

ORA-00279: change 1179134 generated at 07/12/2003 14:40:45 needed for thread 1
ORA-00289: suggestion : C:\ORACLE\DB1\ARCHIVE\DB1__7.ARC
ORA-00280: change 1179134 for thread 1 is in sequence #7
ORA-00278: log file 'C:\ORACLE\DB1\ARCHIVE\DB1__6.ARC' no longer needed for this
recovery

Specify log: {<RET>=suggested | filename | AUTO | CANCEL}

ORA-00279: change 1179135 generated at 07/12/2003 14:40:51 needed for thread 1
ORA-00289: suggestion : C:\ORACLE\DB1\ARCHIVE\DB1__8.ARC
ORA-00280: change 1179135 for thread 1 is in sequence #8
ORA-00278: log file 'C:\ORACLE\DB1\ARCHIVE\DB1__7.ARC' no longer needed for this
recovery

Specify log: {<RET>=suggested | filename | AUTO | CANCEL}

ORA-00279: change 1179191 generated at 07/12/2003 14:41:40 needed for thread 1
ORA-00289: suggestion : C:\ORACLE\DB1\ARCHIVE\DB1__9.ARC
ORA-00280: change 1179191 for thread 1 is in sequence #9
ORA-00278: log file 'C:\ORACLE\DB1\ARCHIVE\DB1__8.ARC' no longer needed for this
recovery

Specify log: {<RET>=suggested | filename | AUTO | CANCEL}

ORA-00279: change 1179193 generated at 07/12/2003 14:41:43 needed for thread 1
ORA-00289: suggestion : C:\ORACLE\DB1\ARCHIVE\DB1__10.ARC
ORA-00280: change 1179193 for thread 1 is in sequence #10
ORA-00278: log file 'C:\ORACLE\DB1\ARCHIVE\DB1__9.ARC' no longer needed for this
recovery

Specify log: {<RET>=suggested | filename | AUTO | CANCEL}

Log applied.
Media recovery complete.
**
**
** select name from v$datafile;

NAME
------------------------------
C:\ORACLE\DB1\SYSTEM.DBF
C:\ORACLE\DB1\UNDO.DBF
C:\ORACLE\DB1\MLM.DBF                    << At This time there is no UNnamed
datafile ..

**
** alter database open resetlogs;

Database altered.

**
** select * from a;
select * from a
              *
ERROR at line 1:
ORA-00376: file 4 cannot be read at this time
ORA-01111: name for data file 4 is unknown - rename to correct file
ORA-01110: data file 4: 'C:\ORACLE\ORA90\DATABASE\MISSING00004'

** select name from v$datafile;

NAME
-------------------------------------
C:\ORACLE\DB1\SYSTEM.DBF
C:\ORACLE\DB1\UNDO.DBF
C:\ORACLE\DB1\MLM.DBF
C:\ORACLE\ORA90\DATABASE\MISSING00004

** alter database rename file 'C:\ORACLE\ORA90\DATABASE\MISSING00004'
  2  to 'c:\orale\db1\test.dbf'
  3  /
alter database rename file 'C:\ORACLE\ORA90\DATABASE\MISSING00004'
*
ERROR at line 1:
ORA-01511: error in renaming log/data files
ORA-01141: error renaming data file 4 - new file 'c:\orale\db1\test.dbf' not
found
ORA-01111: name for data file 4 is unknown - rename to correct file
ORA-01110: data file 4: 'C:\ORACLE\ORA90\DATABASE\MISSING00004'
ORA-27041: unable to open file
OSD-04002: unable to open file
O/S-Error: (OS 3) The system cannot find the path specified.

      Rating:
      2
Re:  July 13, 2003
Reviewer:  Js
       from INDIA
ok .. Sorryyyy for posting "Page after page after page"
 want to know ..how can i rename my file ..that does not exist..
i had lost my file ...using
"drop tablespace ... inculde contents and datafiles "

          Followup:

and I told you

a) rename the file in the OS
b) alter database rename file 'missing' to 'test'

      Rating: 4
ALTER DATABASE
      RENAME FILE 'MISSING00035'  July 24, 2003

Reviewer:  kelly  from USA
We had disk failure about 2 months ago.  I thought I had recovered all the
databases on the machine then.  Today, I ran backup controlfile to trace and
found that one database has these entries at the end of the script
ALTER DATABASE RENAME FILE 'MISSING00035'
  TO '/HR1/hrtrng/pjcpay_01.dbf';

All the information for this tablespace and datafile are in the dictionary
v$datafile and dba_data_files. I tried to offline the tablespace and ran the
'alter database rename...' but got
ERROR at line 1:
ORA-01511: error in renaming log/data files
ORA-01523: cannot rename data file to '/HR1/hrtrng/pjcpay_01.dbf' - file
already part of database

Should I run the backed up controlfile script to re-create controlfile? Will
this recover my datafile?  This database is hardly updated.

Thanks so much.

          Followup:

when was your last backup?

      Rating: 4
readonly
      tablespace  July 30, 2003
Reviewer:  kelly
       from USA
It turns out to be readonly tablespaces.  I havn't dealt with readonly
tablespace much.  But seems like it's in the data dictionary but then it doesn't
know in the control file.

      Rating: 5
backup
      controlfile......  October 11, 2003
Reviewer:
      Shankar  from Houston, TX
If I use backup controlfile to perform recovery, Oracle would ask me to open the
database in resetlogs mode. However, If I used the trace version of the
controlfile (alter database backup controlfile to trace) that was created, say,
two days ago, to recreate a controlfile, oracle would let me open the database
without resetlogs. I was wondering even the trace controlfile was like a backup
controlfile as it does not have the current status of the database. How come
oracle let me open the database without resetlogs? It is puzzling to me. Thanks.

          Followup:

the backup controlfile will "stop" the recovery as of the point in time of the
backup of the control file.  we need to "reset logs" at that point cause you
have not applied them all.  you have recovered to some point in the past.

using a create control file statment means you did a cancel based recovery, you
applied ALL logs including the online redo's and you are "current" with respect
to everything.  no need to reset - we are back where we would  have been with a
regular instance crash recovery.

      Rating: 5
create control
      file  October 12, 2003
Reviewer:  reader

<quote>using a create control file statment means you did a cancel based
recovery<quote>

If it is a cancel based recovery using a create control file, then we should
open the database in resetlogs? right? Thanks.

          Followup:

yup

      Rating: 5
I have some
      doubts?  December 14, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
I have two database servers (prod & test) of Oracle 8i same versions and same
OS. I am taking fulll backup of db1 with rman and also taking backup of control
file to os and trace. Now I want to clone my server with test server so let me
know what steps I should take to bring the database of test at the same level
the prod is. I mean complete recovery while prod is online and I don't want to
use cold backup to clone the servers.

The name of database is same on both servers.

If the above is not possible then let me know what should I do for complete
recovery while the db1 is online.

          Followup:

check it out -- we've documented this sort of stuff (strange but true, i know!)

http://download-west.oracle.com/docs/cd/A87860_01/doc/index.htm

      Rating: 5
restore from
      different location?  December 15, 2003
Reviewer:
      Sikandar Hayat  from Pindi Bhattian - Pakistan
On host A I took the rman back in E:\back folder and copied to host B D:\back
folder. Now how I can restore the backup with rman from this location (in 8i)?
Actually on host B I don't have E drive so unable to copy it there.

          Followup:

using subst you can have an E: drive easily

c:\> help subst

      Rating: 5
Great
      tip....  December 15, 2003
Reviewer:  Sikandar
      Hayat  from Pindi Bhattian - Pakistan
TOM you are not only expert of Oracle but also windows.....Thanks it worked.

          Followup:

i think i've just been insulted :)

i remember subst not from windoze but rather from my DOS days way back when.

      Rating: 5
LOL -- that was
      a good one  December 16, 2003
Reviewer:
      Shrek
I hope you are not insulted if I ask, is there a problem if I multipex redo logs
with an alternate location being an n fs mount? if so why?

          Followup:

unless it is certified NAS (like a netapp), you cannot use NFS for ANY database
files.

nfs is just wholly unreliable for the kind of assured writes we require.

      Rating: 5
arch
      process  December 16, 2003
Reviewer:
      Shankar  from Houston, TX
Tom, how do i find out that my database needs more than one ARCH process to
archive files? What should I look for to determine the number of ARCH processes
for my instance? Thanks.

          Followup:

if you have cannot allocate new log, archival required -- and it was because
arch couldn't keep up, that would be an indication you might need more.

you'd see that in your alert log.


Recent

Archives

Advanced Search

FAQ by Category

Most Popular

Hot Articles

Your Questions

[ Listing ]

Subject

Article

Last Updated (East coast time)

Consistent Gets, Trace output

article only

Dec 17, 2003 06:43 am

Standby Database/Alert Log

article only

Dec 17, 2003 06:52 am

open_links parameter

article only

Dec 17, 2003 06:55 am

Initializing a PLSQL table of records

article only

Dec 17, 2003 07:00 am

Public synonym

article only

Dec 17, 2003 07:01 am

write to file in PL/SQL and sort that file

article only

Dec 18, 2003 07:05 am

Soft sessionspace limit

article only

Dec 18, 2003 08:29 am

Date Format

article only

Dec 18, 2003 08:31 am

getting rows N through M of a result set

article only

Dec 18, 2003 08:36 am

Java Stored procedures from pl sql

article only

Dec 18, 2003 08:38 am

sql to get data from flat file-- this is mind blowing

article only

Dec 18, 2003 08:44 am

Idle Oracle Server Processes

article only

Dec 18, 2003 08:48 am

tkprof wrong result?

article only

Dec 18, 2003 10:21 am

Export

article only

Dec 18, 2003 10:27 am

Fined Grained Access Control

article only

Dec 18, 2003 10:29 am

Index not used.

article only

Dec 18, 2003 10:31 am

UGA/PGA: what are they and where are they ?

article only

Dec 18, 2003 10:42 am

Dynamic Dynamic SQL

article only

Dec 18, 2003 10:46 am

Differrent Character set and NLS

article only

Dec 18, 2003 10:49 am

Dynamic Menus

article only

Dec 18, 2003 11:07 am

Usage of Analytic Functions within a query having grouping

article only

Dec 18, 2003 11:13 am

vertical & horizontal partitioning table: replication

article only

Dec 18, 2003 11:14 am

Oracle - Optimising the pattern matching (LIKE '%ABC')

article only

Dec 18, 2003 11:17 am

cannot connect to oracle 92 database

article only

Dec 18, 2003 11:28 am

PL/SQL Wrapper

article only

Dec 18, 2003 11:30 am

Domain Indexes

article only

Dec 18, 2003 11:37 am

Question about Intermedia and Score

article only

Dec 18, 2003 11:39 am

Why there is so much logging for "insert /+* append */ into tab select"?

article only

Dec 18, 2003 03:47 pm

How to get dependencies among pl/sql objects

article only

Dec 18, 2003 03:50 pm

re: prior posting on index

article only

Dec 18, 2003 04:07 pm

Column Frequecy Table

article only

Dec 18, 2003 04:19 pm

Business Logic - PL/SQL Vs Java - Reg

article only

Dec 18, 2003 06:15 pm

sqlloader versus external table.

article only

Dec 18, 2003 06:25 pm

reclaiming space after delete of LOB data

article only

Dec 19, 2003 06:58 am

Statspack report

article only

Dec 19, 2003 07:02 am

LOB - Replacing a part of / Not overwriting

article only

Dec 19, 2003 07:17 am

Using SQL Loader

article only

Dec 19, 2003 07:23 am

How communicates an SQL client with Oracle Database

article only

Dec 19, 2003 07:51 am

Selecting from user_role_privs

article only

Dec 19, 2003 07:53 am

creating table

article only

Dec 20, 2003 09:26 am

Oracle Session Management in Web Applications

article only

Dec 20, 2003 09:36 am

Unsauble/Rebuild/Export

article only

Dec 20, 2003 09:39 am

How To Tune This Procedure (May be using EXIST)

article only

Dec 20, 2003 10:24 am

query

article only

Dec 20, 2003 10:29 am

"log file sync" during import

article only

Dec 20, 2003 06:48 pm

questions on views

article only

Dec 20, 2003 06:48 pm

Parametrized reports with range buckets

article only

Dec 21, 2003 10:13 am

Drop column command

article only

Dec 21, 2003 10:23 am

few burning questions

article only

Dec 22, 2003 09:10 am

difference between 2 dates

article only

Dec 22, 2003 09:11 am

bulk

article only

Dec 22, 2003 09:13 am

owa_util.cellsprint

article only

Dec 22, 2003 10:14 am

Dynamic Table name in Cursor

article only

Dec 22, 2003 11:00 am

Convert varchar2 to Date format

article only

Dec 22, 2003 02:43 pm

union question

article only

Dec 22, 2003 03:52 pm

export to CSV

article only

Dec 22, 2003 05:45 pm

ORA-14130: UNIQUE constraints mismatch ... got the solution ...please review

article only

Dec 22, 2003 06:41 pm

PCTVERSION

article only

Dec 22, 2003 06:43 pm

difference between dropping rebuilding indexes and using update global indexes

article only

Dec 23, 2003 11:10 am

Questions on Locking

article only

Dec 23, 2003 11:12 am

sort_area_retained_size

article only

Dec 23, 2003 11:20 am

Oracle: New world

article only

Dec 23, 2003 11:30 am

Indexes - indexing only selected rows - possible?

article only

Dec 23, 2003 11:37 am

How to split a partition

article only

Dec 23, 2003 11:38 am

PCTVERSION settings for blobs

article only

Dec 23, 2003 11:49 am

Bulk insert of records , using something like a user defined record ???

article only

Dec 23, 2003 11:57 am

Performance Tuning

article only

Dec 23, 2003 04:52 pm

Assign return value to Unix Variable

article only

Dec 23, 2003 05:23 pm

Show_Space Procedure Usage Problem

article only

Dec 23, 2003 05:28 pm

IOUG Conference........... Recommendations

article only

Dec 23, 2003 05:35 pm

SGA size

article only

Dec 23, 2003 05:45 pm

Moving from RBO to CBO.

article only

Dec 23, 2003 05:54 pm

Meaning of values of status column in V$LOGMNR_CONTENTS

article only

Dec 23, 2003 07:52 pm

Transforming row to columns

article only

Dec 23, 2003 07:59 pm

row(s) 1 - 74 of 74

December 17, 2003 Kailash -- Thanks for the question regarding "Consistent Gets, Trace output", version 8.1.7
You Asked
Tom,

I have the following questions :

1. In your book "Expert One-on-One Oracle" and in the discussions, you have been
insisting that the goal of SQL tuning is to reduce the number of logical I/Os. I
have the following thing to be clarified :

My understanding is that whenever a SQL query statement needs to be satisfied,
Oracle searches for the data block in the buffer cache and performs a logical
I/O if it can. Otherwise, it initiaties a physical I/O to read the block from
disk. I have read the www.hotsos.com article, but I am not still clear about
what it means to "Minimize Logical I/O". My understanding is that "Oracle has to
either perform a logical I/O or a physical I/O (one of the two things) to get
the block and satisfy the query". If that is the case, can you please clarify me
as to what you mean by "Minimize the logical I/Os". Also please provide me an
example of doing that or the things I should take care in my query to reduce
logical I/Os.

2. I have generated a TKPROF trace file and the INSERT statements show me a wait
for 'db file scattered read' and 'db file sequential read'. My understanding is
that this will occur only in case of queries performing a full table scan or an
index scan. Why does those wait events show up in an INSERT statement (which is
a DML)

3. Can you explain CLUSTERING_FACTOR in INDEXES?
and we said...
If you get my other book "Effective Oracle By Design" as well -- I go into this
further.  But basically -- my point is "igore physical IO for a moment while
tuning queries.  Concentrate on the logical IO for PIO's turns into LIO's (eg:
PIO's are in general a subset of LIO's -- if we do a physical read of a block
into the buffer cache, you still have to do a LIO to get it from the buffer
cache)"

So, you want to reduce the consistent gets for a query -- if you take care of
the LIO's, you'll indirectly take care of the PIO's!  (just like if you watch
your pennies, your dollars take care of themselves)

It is not "oracle must do either lio or pio", it is "when Oracle does an LIO, it
may have to do a PIO in order to satisfy the request"

What you want to do is minimize LIO's.  There are many ways to achieve this -
one is by writing a better query.  Another is using physical structures such as
IOT's and clusters.  Another might be using an index.  Another might well be NOT
using an index.  I have many examples of this in my newer book -- such as using
an IOT to physicaly cluster data together in order to make it so that commonly
requested data is stored "near" eachother -- reducing the LIO's by many orders
of magnitude.

Here is a short excerpt of what I mean from the new book:

<quote>
Use IOTs to Colocate Randomly Inserted Data

In addition to being a space-saving device, by obviating the need for some
redundant tables, IOTs excel in their ability to physically colocate related
information for fast access. If you recall, one of the downsides to a cluster is
that you need to have some control over the arrival of the data in order to
optimize the physical colocation of the data. IOTs do not suffer from this
condition, because they will readjust themselves structurally in order to
accommodate the data as it is inserted.

Consider an application that frequently retrieves a list of documents owned by a
given user. In the real world, the user would not insert all of the documents he
or she will ever own in a single session. This is a dynamic list of
unpredictable size that will constantly be changing as the user adds and removes
documents in the system. Therefore, in a traditional heap table, the rows that
represent this user's documents would be scattered all over the place. Consider
what would happen when you run a query such as this:

select * from document table where username = :bind_variable

Oracle would use an index to read many dozens of blocks from all over the table.
If we used an IOT to physically cluster the data together, this would not
happen. We can observe this behavior with a simple simulation and AUTOTRACE. For
this example, we'll set up a pair of tables: one using an IOT and the other
using a heap-based implementation.

ops$tkyte@ORA920> create table iot
  2  ( username varchar2(30),
  3    document_name varchar2(30),
  4    other_data       char(100),
  5    constraint iot_pk
  6    primary key (username,document_name)
  7  )
  8  organization index
  9  /
Table created.

ops$tkyte@ORA920> create table heap
  2  ( username varchar2(30),
  3    document_name varchar2(30),
  4    other_data       char(100),
  5    constraint heap_pk
  6    primary key (username,document_name)
  7  )
  8  /
Table created.

We use a CHAR(100) just to make the average width of a row in these tables about
130 bytes or so, since a CHAR(100) will always consume 100 characters of storage
(it is a fixed-width datatype). So, the only difference between these two tables
(besides their names) is the addition of the ORGANIZATION INDEX clause on the
second one. That instructs Oracle to store the table data in an index segment
instead of a table segment, so that all of the data for that table will be
stored in an index structure.

Next, we populate these tables with some sample data. We construct a loop that
adds 100 documents for each user in the ALL_USERS table. We do this in a fashion
that emulates real life, in that the documents for a given user are added not
all at once, but rather over time after many other documents have been added by
other users.

ops$tkyte@ORA920> begin
  2      for i in 1 .. 100
  3      loop
  4          for x in ( select username
  5                       from all_users )
  6          loop
  7              insert into heap
  8              (username,document_name,other_data)
  9              values
 10              ( x.username, x.username || '_' || i, 'x' );
 11
 12              insert into iot
 13              (username,document_name,other_data)
 14              values
 15              ( x.username, x.username || '_' || i, 'x' );
 16          end loop;
 17      end loop;
 18      commit;
 19  end;
 20  /
PL/SQL procedure successfully completed.

And now we are ready for our performance comparison. Here, we will read all of
the data from our table, user by user; that is, for USER1, we'll read out all of
the rows that correspond to that user, and then for USER2, and so on.
Additionally, we'll do this reading in two ways-one time using BULK COLLECT and
the next using single-row fetches-just to see the difference that array
processing can have on performance and scalability, and to show how
significantly different this IOT can be from a heap table. Our benchmark routine
is as follows:

ops$tkyte@ORA920> alter session set sql_trace=true;
Session altered.

ops$tkyte@ORA920> declare
  2      type array is table of varchar2(100);
  3      l_array1 array;
  4      l_array2 array;
  5      l_array3 array;
  6  begin
  7  for i in 1 .. 10
  8  loop
  9      for x in (select username from all_users)
 10      loop
 11          for y in ( select * from heap single_row
 12                      where username = x.username )
 13          loop
 14              null;
 15          end loop;
 16          for y in ( select * from iot single_row
 17                      where username = x.username )
 18          loop
 19              null;
 20          end loop;
 21          select * bulk collect
 22            into l_array1, l_array2, l_array2
 23            from heap bulk_collect
 24           where username = x.username;
 25          select * bulk collect
 26            into l_array1, l_array2, l_array2
 27            from iot bulk_collect
 28           where username = x.username;
 29      end loop;
 30  end loop;
 31  end;
 32  /

PL/SQL procedure successfully completed.

The TKPROF report for the single-row fetches shows the following statistics:

select * from heap single_row where username = :b1

call     count    cpu elapsed disk   query current     rows
------- ------  ----- ------- ---- ------- -------  -------
Parse        1   0.00    0.00    0       0       0        0
Execute    440   0.05    0.05    0       0       0        0
Fetch    44440   1.50    1.42    0   88886       0    44000
------- ------  ----- ------- ---- ------- -------  -------
total    44881   1.56    1.48    0   88886       0    44000

Rows     Row Source Operation
-------  ---------------------------------------------------
  44000  TABLE ACCESS BY INDEX ROWID HEAP
  44000   INDEX RANGE SCAN HEAP_PK (object id 43271)
***********************************************************
select * from iot single_row where username = :b1

call     count    cpu elapsed disk   query current     rows
------- ------  ----- ------- ---- ------- -------  -------
Parse        1   0.00    0.00    0       0       0        0
Execute    440   0.07    0.05    0       0       0        0
Fetch    44440   1.11    0.99    0   44987       0    44000
------- ------  ----- ------- ---- ------- -------  -------
total    44881   1.18    1.04    0   44987       0    44000

Rows     Row Source Operation
-------  ---------------------------------------------------
  44000  INDEX RANGE SCAN IOT_PK (object id 43273)

The heap table, with its read-the-index-then-the-table approach, will do at
least twice the I/O row by row by row. It must read the index block, and then
read the table block. The IOT approach, on the other hand, simply reads the
index and is finished. So, this is pretty good. Any day we can cut in half the
number of I/Os our system must perform is a good day. Can it be even better?
Yes, it can.

In Chapter 2, we used AUTOTRACE to demonstrate the effect that different array
sizes (how many rows Oracle fetches in response to each fetch request) could
have on an application. We saw that we can significantly reduce the I/O
performed by a query if we fetch many rows at a time instead of fetching single
rows. Here, we can really show the power of the IOT over the heap table in this
regard. Reading further on in the TKPROF report we see this:

SELECT * from heap bulk_collect where username = :b1

call     count    cpu elapsed disk   query current     rows
------- ------  ----- ------- ---- ------- ------- --------
Parse        1   0.00    0.00    0       0       0        0
Execute    440   0.06    0.05    0       0       0        0
Fetch      440   0.49    0.48    0   36100       0    44000
------- ------  ----- ------- ---- ------- ------- --------
total      881   0.55    0.54    0   36100       0    44000

Rows     Row Source Operation
-------  ---------------------------------------------------
  44000  TABLE ACCESS BY INDEX ROWID HEAP
  44000   INDEX RANGE SCAN HEAP_PK (object id 43271)
***********************************************************
SELECT * from iot bulk_collect where username = :b1

call     count    cpu elapsed disk   query current     rows
------- ------  ----- ------- ---- ------- -------  -------
Parse        1   0.00    0.00    0       0       0        0
Execute    440   0.06    0.05    0       0       0        0
Fetch      440   0.24    0.24    0    2110       0    44000
------- ------  ----- ------- ---- ------- -------  -------
total      881   0.31    0.30    0    2110       0    44000

Rows     Row Source Operation
-------  ---------------------------------------------------
  44000  INDEX RANGE SCAN IOT_PK (object id 43273)

The IOT using bulk fetches did less than 6% of the I/O of the query against the
heap table using the same bulk collect. When running this test with Runstats
instead of TKPROF, I discovered the heap table approach used 600% more latches.
These are some serious differences here!

</quote>

See how using an IOT to store THE SAME EXACT DATA can significantly -- many
times over -- reduce the amount of work your system must do.  Remember -- every
consistent get is a LATCH and a LATCH is a LOCK and LOCKS are serialization
devices and serialization devices inhibit scalability.

2) insert statements must read data just as any statement might.  In order to
insert we need to maintain indexes (must read the indexes), we must find a block
to put the data in (read freelists).  If this data is not in the buffer cache,
we will read it.  Don't forget about triggers and constraints -- a foreign key
can cause the parent table to be read....

3) see page 283 of "Expert one on one Oracle"
Review & Followup

Rating: 4
Wonderful explanation October 27, 2003
Reviewer: A reader
Thanks much Tom for your time and wonderful explanation. But one final question
for confirmation :

The example you have demonstrated is almost the same as the
COLOCATED/DISORGANIZED example on Indexes chapter in Expert-one-on-one Oracle.
Right?

Also, is clustering factor helpful only for large index-range scans. Right?
Followup:
yes, it demonstrates what happens when data that needs to be referenced together
is stored

o haphazardly
o organized

in the book -- with that example, i showed the "issue" but offered "no
solution", this is the "solution"

clustering is useful for multi-table joins as well.  and is "5" a large index
range scan?  what if the user has 5 documents.  Would you want 8 block reads (3
for the index range scan plus 5 table access by rowid) or 4?  I'd take 5.
Rating: 4
Wonderful Explanation October 28, 2003
Reviewer: A reader
Thanks. Very clear explanation.
Rating: 4
Little confusion October 30, 2003
Reviewer: A reader from NJ
1. The example you had discussed is related to the locale of the data. ie. how
the related data are closely existing in the table. Is that correct?
2. It is indirectly related to the clustering factor also. Right?
3. If 2 is true, you mention that the clustering factor is to do with index
range scan. However, the example you show is an equality condition. So, does it
apply to equality condition also? If this is not correct, please clarify how
clustering factor and locale of data are related or is they both different?
4. In the Oracle8 Advanced Tuning and Admin, they explain about data proximity.
Is it the same as clustering factor?

As always, thanks in advance for your wonderful and consise explanations
Followup:
1) locale is usually "location" as in a timezone or something but it sort of
applies here, yes.

if you find yourself "listing all of the stuff for 'X'", then you would like all
of the stuff for 'X' to be "near" eachother to reduce the amount of work it
takes to get that.

2) could be -- if you are considering heap tables with b*tree indexes.  the
cluster factor would tell you if the data was accidently nicely clustered or
not.

3) equals do range scans????  you do not need <, > to range scan.  I did a range
scan

4) proximity is about closeness.  clustering factor can give you a measure of
"closeness" so yes.
Rating: 5
Equals do range scans November 01, 2003
Reviewer: A reader from NJ, USA
Tom

1.In response to my question above, you mention that equals do range scans. In
this case it did a range scan because you use the leading column of the index
username of the composite index (username,document_name). Is that correct?
2. I have a query
select *
  from T
 where col1 = 'xxx'
   and col2 = 'yyy'

As clustering factor is to index range scans, is there way to determine for the
above query if a composite index will be useful and if so in what order or will
be a table scan be more efficient.
3. My DBA put a TKPROF trace on a PL/SQL program and showed me that
non-recursive SQL statements took almost half of the time and says that
non-recursive SQL statements are the non-SQL component ie. pure PL/SQL component
of the PL/SQL program and claims that PL/SQL components such as variable
declarations, assignments etc would be very very slow.
   a. Is that claim true
   b. What are non-recursive SQL in the trace file. Are they any component which
are not pure PL/SQL components. If not what are non-recursive SQL statements and
what are recursive SQL statements.
   c. Is there any way to determine how much time the pure PL/SQL components
took from the TKPROF output
4. Are there any overhead involved when a block is to be removed from FREELIST
frequently because of insufficient space in the blocks during frequent inserts.
Followup:
1) create table t ( x int );
   create index on t(x);
   insert into t select 5 from all_users;
   select * from t where x = 5; <<<<==== range scan

anytime you can get more then one row (eg: you did not use all of the columns in
a unique index), its a range scane.

2) whats the cardinality of the table and the selectivity of col1/col2 with
regards to that?  it is all a matter of numbers really.

as for the order, for that query -- not relevant.  you would need to ask "do I
also query "where col1 = 'xxxx'" -- if so, col1 goes first.  If you ask "where
col2 = 'yyy'", col2 goes first.  if you ask both -- then you might consider
putting the "least selective first" (eg: if col1 has 4 distinct values and col2
has 50,000 -- use (col1,col2)) then we can index skip scan in 9i (search for
that "skip scan" on this site to read more about it)

3) all sql in a plsql program is recursive sql!  I'm not sure what he is talking
about.

tkprof really only reports on sql performance -- dbms_profiler on plsql
performance.

you need to use dbms_profiler to fine tune plsql.  tkprof to tune sql.

4) no, overhead is something we can cut out and not lose anything.  overhead is
"gravy".  consider the overhead of leaving the block full on the freelist (gotta
look at it each time and disregard it)
Rating: 4
Least selective November 03, 2003
Reviewer: A reader from NJ, USA
By "Least Selective First", do you mean those columns that have very few
discrete values. eg. Y/N columns and gender columns (M/F). Does "Least
Selective" also mean "Low cardinality"
Followup:
yes, low cardinality, least selective

Y/N
M/F
Rating: 4
Non-Recursive SQL November 03, 2003
Reviewer: A reader from NJ, USA
Tom,

For qn. 3 the scenario is as follows :

We have a PL/SQL process that is called from an outside 3rd party program (the
calling program being a black-box) for each record. We have put a trace on the
PL/SQL process and our DBA figured out that half of the processing time is spent
in non-recursive SQL statements which he claims as pure PL/SQL code such as
FOR/WHILE LOOPS, PL/SQL variable declarations, assignment statements etc that
are not SQL involved. DBA thinks these statements consume majority of processing
time.
  a. Is it true? Do those statements consume that amount of time?
  b. Is it what it means by non-recursive SQL statements. If not what does
non-recursive SQL statements in TKPROF mean?
  c. How do you figure out process time for PL/SQL components mentioned above ?
Followup:
use the dbms_profiler.
Rating: 4
Two related questions. November 03, 2003
Reviewer: Kashif from Reston, VA
Hi Tom,

Recently I came across a query which had surprising results. My objective in
tuning the query was to reduce LIOs, and I managed to bring the LIOs down
significantly. However, to my surprise, the runtime of the query went up by
nearly 30%, even though the LIOs had gone down significantly.

I know my query might be impossible to answer, but I wanted to know if you could
think of any possible reasons for this behavior. The things I could think of was
that I was working in an RBO environment, so maybe the query was not adequately
optimized; also, there were no indexes in place on any of the tables, so there
were full scans out the wazoo, though I'm not sure how this might have affected
the results since both versions of the queries resulted in FTS on all tables;
finally, the PIOs were consistently higher than LIOs in multiple executions of
the same query. The physical reads were usually double or triple the consistent
gets, across several executions.

Also, what does it mean when PIOs are consistently higher than LIOs for a given
query? I think it might be due to a high number of full table scans (as was the
case in my query above), but can you please verify this?

Thanks in advance.

Kashif
Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:880343948514#13453235906579
funny how these topics all seem to pop up simultaneously.  I was just discussing
this.

when PIO > LIO for the query -- you are doing lots of direct reads/writes to
TEMP.  Perhaps your sort area size is the default tiny 64k?
Rating: 4
IOTs - Storing secondary columns in a separate tablespace November 03, 2003
Reviewer: A reader from NJ, USA
Hi Tom,

Some of the Oracle documents that I read, mention that in order to speed up
performance, we can direct some of the non-key columns of an IOT to another
tablespace.
a.Does that not defeat the purpose of IOT as it has to do another block read to
get the data?
b. Also, how data proximity/nearness can be maintained with this approach.

Your explanation in this regard is highly appreciated. As always, thanks in
advance for your time and suggestion
Followup:
a) yes it does
b) by definition it does not

The concept here would be you have a table with some ALWAYS accessed columns
that would benefit from an IOT.  You have some other columns that are
INFREQUENTLY accessed that live with these other columns.

If you were to put all columns on the leaf blocks -- the index structure would
be huge and constantly splitting (few rows/leaf block) or maybe the row would
not even fit on the leaf block.

So, you can move the INFREQUENTLY accessed columns to an overflow segment.  Now
the ALWAYS accessed columns are right there and the infrequently accessed ones
are just a read away.

without an overflow segment, you would have modeled the data physically as:

create table always_accessed ( c1 primary key, c2, c3, ... c7 ) organization
index;

create table infrequently_accessed( c1 primary key references always_accessed,
c8, c9, c10, .... );

and joined when you wanted the extra columns.  that would have added:

a duplicate storing of C1 the primary key.

a primary key index on C1

an index range scan on this primary key (to join)

followed by a table access by index rowid

As opposed to using an overflow segment where we just:

o find a row in the IOT
o use the pointer to the overflow segment to access directly the overflow
columns

So, it is a more effecient way to break this data up -- giving you the natural
"organization" of the important columns, with effecient access to the rest.

If you ALWAYS access these overflow columns -- then the IOT structure is
somewhat compromised in its efficiency.
Rating: 5
PIOs > LIOs. November 04, 2003
Reviewer: Kashif from Reston, VA
Thanks Tom, the discussion in the link you provided makes sense. Our sort area
size is 258408, but there are around 14 sorts done in the query. I played around
with the sort_area_size parameter and set values up to 5,000,000, and it made a
significant difference in the runtime of the query (went down by 50%). Since
this is a DSS, I can probably afford to set sort area to something high while
running this query, and then set it back to the current setting when done. Any
advice or guidelines to follow on this?

Also, do you have any thoughts on why LIOs could go down and actually increase
the runtime of the query? I'll try posting the query I had mentioned and results
when you're accepting questions, if you'd like. Thanks.

Kashif
Followup:
if you are DSS, just set it high, you are only using it while you are using it
-- if you don't use it, you lose it.

in 9i, use workarea size policy = auto and use the pga_aggregate_target.

the LIO's when down, but your PIO's (reads/writes to/from temp) when up right.
In this case -- the additional PIO's due to temp outweighed the reduction in
LIO.

Normally -- if you watch the LIOs, the PIOs take care of themselves.  When the
plan switches to TEMP swapping however, this might not be the case.
Rating: 5
There is a break of continuity November 04, 2003
Reviewer: mikito Harakiri
so 0.01 is lower selectivity than 0.1
However, 0.01 is very selective, while 0.1 is not very selective. Being very
selective means more selective. Contradiction:-)
Followup:
no, 0.01 is more selective.

if something returns 0.01% of the data, it is more selective then something that
returns 0.1% of the data.

unless you meant something totally different?

not sure what 0.01 and 0.1 in your context means exactly.
Rating: 5
November 04, 2003
Reviewer: A reader
Tom

From your example

##############

SELECT * from iot bulk_collect where username = :b1

call     count    cpu elapsed disk   query current     rows
------- ------  ----- ------- ---- ------- -------  -------
Parse        1   0.00    0.00    0       0       0        0
Execute    440   0.06    0.05    0       0       0        0
Fetch      440   0.24    0.24    0    2110       0    44000
------- ------  ----- ------- ---- ------- -------  -------
total      881   0.31    0.30    0    2110       0    44000

###############

the total of cpu should be 0.30 and for elapsed should be 0.29 .why this
difference?
Followup:
rounding errors -- 9i uses millionths of a second.
Rating: 4
Avg Blocks per key November 05, 2003
Reviewer: A reader from NJ, USA
Tom,

Can you please explain as to what avg_leaf_blocks_per_key and
avg_data_blocks_per_key in *_INDEXES table represent after the index is
analyzed. What is the significance of the above two columns. If they are 1 and
10 respectively for an index, does that mean it will read the index block once
and for every entry, it has to access the table block 10 times resulting in 11
(1+10) logical reads?
Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96536/ch252.htm#1292860
it depends on how it is used as to how many blocks will be accessed.

if you do a single BULK fetch with your 1/10 setting, yes, it means on average
that "select * from t where indexed_column = :bv" will access 11 LIO's

if you do 2 bulk fetches, you'll lio the index block 2 times and the data blocks
at least once and maybe one of them 2 times (so 13 lios)

so it will really depend on your array size.
Rating: 5
low cardinality, lest selective November 05, 2003
Reviewer: Mikito Harakiri
I was referring to your earlier comment where you said

"yes, low cardinality, least selective"

Low cardinality, say 1 row out of 1000 is very selective! It is more selective
than 10 out of 1000:-)

Terminology here is fundamentally not consistent, as sometimes we refer to the

selected_rows/total_rows

while in the others to

total_rows/selected_rows

with the same word "selectivity".
Followup:
huh??????

low cardinality is in general going to be "least selective".  if 1 row out of
1000 is "true" and the cardinality is "2" -- then the other value returns 999
rows.  "not selective" to me.

i am talking about the CARDINALITY of the distinct values in the column.  I'm
not sure what your point is.

but i think we are mincing terms.  is the gist of the above clear?
Rating: 5
November 05, 2003
Reviewer: A reader
Tom,

Coming back to rounding errors, am going through the book that
you had on your web page Optimizing Oracle Performance.

on page 84 Chapter 5, Cary says
"Oracle's tkprof utility produces erroneous results in more cases than ou might
have imagined, especially in STAT line processig"

Tom, I have learned a lot from you, your advice is more valuable to me than any
other Oracle Expert, what is your response to this?

Thanks..

Thanks.
Followup:
he also says "it is a most invaluable tool" and its not "tkprof" but the sql
trace facility (tkprof just formats reports, the trace file contains the raw
data it uses)

Let me put it this way -- can you tune the appliation without the information
provided by trace?  (rhetorical question)

the errors introduced are typically in the "noise spectrum".  it is rare,
exceedingly so, for them to be large enough to materially affect anything. (here
for example, did the different make any real difference??)
Rating: 4
Avg Blocks and Avg Data Blocks November 06, 2003
Reviewer: A reader from NJ, USA
Tom,

In your explanation you mention 2 bulk fetches = 13 LIOs. Can you provide me an
example of what you mean by this or could you please explain me a little more in
detail. Thanks
Rating: 4
Colocated vs Disorganized November 06, 2003
Reviewer: A reader from NJ, USA
Tom,

In the example you explained in your book about colocated and disorganized, the
disorganized table was a heap table with data values spread across multiple
table blocks. This table was not an IOT. In that case, the trace output should
have only shown a huge difference in physical reads as opposed to consistent
gets. ie. My point is, even if the data is colocated, Oracle will perform a
minimum of 2 consistent reads per row. ie. index block access + table access.
The same would be true for disorganized table except that since the table is
disorganized, the next index entry would point to a table block not in the
buffer cache and so will perform a physical read to bring the block into the
buffer cache and then perform a logical I/O on it. So going by the above logic,
I should have find a major difference only in the physical reads and not in
terms of consistent gets. Why is the difference in consistent gets and not
physical reads. Here is an example that can provide you a clear picture of what
I am not clear about:

Select x from colocated where x between 5 and 8

Index Entry (data colocated)
Key(x) Rowid
5      B1xxx
6      B1yyy
6      B1zzz
8      B1aaa

So in the above case, all data is in block B1 and so Oracle will perform 8
consistent gets (assuming row-by-row process) + (0 or 1 physical read depending
on if B1 is already in buffer cache).

Select x from disorganized where x between 5 and 8

Index Entry (data disorganized)
Key(x)  RowId
5       B1xxx
6       B5yyy
6       B6zzz
8       B1aaa

In the above case also, Oracle has to perform 8 consistent gets (assuming
row-by-row processing) + 3 physical reads since the data is in 3 different
blocks B1,B5 and B6. So the only difference should be physical reads and not
consistent gets.

Can you explain why difference is in "Consistent gets". Am I missing something
or is my understanding not correct.

As always, thanks much for your clear explanation
Rating: 4
Array Fetches using Index November 06, 2003
Reviewer: A reader from NJ, USA
Tom,

In the topic on "Consistent Gets", you mentioned how Oracle processes full-table
scans and showed examples of how consistent gets changes based on array size. If
the table is accessed using index range scan followed by table access by rowid
and array fetch (say 15 rows per fetch), how will "consistent gets" look like
when the table data is
1. Colocated
2. Table is disorganized

It would be really helpful if you can provide an example.
Followup:
the same....

if you access via an index -- you

a) index read
b) table block read

so, if you index range scan 100 rows using 1 row at a time, you will have 200
consistent gets.

If you index range scan 100 rows using 100 row fetch, you might do 2 (colocated)
or 101 (disorganized)

so.... consider:

drop table t1;
drop table t2;

create table t1 ( x int primary key , y int );
create table t2 ( x int primary key , y int, z varchar2(4000) default
rpad('*',4000,'*') );

insert into t1 select rownum, rownum from all_objects where rownum <= 100;
insert into t2 (x,y) values ( 1 , 1 );
alter table t2 minimize records_per_block;
insert into t2 (x,y) select rownum+1, rownum+1 from all_objects where rownum <
100;

alter session set sql_trace=true;

declare
    type array is table of number;
    l_array array;
    l_number number;
begin
    select y bulk collect into l_array from t1 where x >= 0;
    select y bulk collect into l_array from t2 where x >= 0;

    for x in ( select y from t1 where x >= 0 ) loop null; end loop;
    for x in ( select y from t2 where x >= 0 ) loop null; end loop;
end;
/

Now, tkprof shows the consistent gets:

SELECT y from t1 where x >= 0

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          2          0         100

Rows     Row Source Operation
-------  ---------------------------------------------------
    100  TABLE ACCESS BY INDEX ROWID T1 (cr=2 r=0 w=0 time=527 us)
    100   INDEX RANGE SCAN SYS_C006977 (cr=1 r=0 w=0 time=253 us)(object id
41233)
********************************************************************************
SELECT y from t2 where x >= 0

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0        101          0         100

Rows     Row Source Operation
-------  ---------------------------------------------------
    100  TABLE ACCESS BY INDEX ROWID T2 (cr=101 r=0 w=0 time=1183 us)
    100   INDEX RANGE SCAN SYS_C006978 (cr=1 r=0 w=0 time=308 us)(object id
41235)
********************************************************************************
select y from t1 where x >= 0

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      103      0.00       0.00          0        200          0         100

Rows     Row Source Operation
-------  ---------------------------------------------------
    100  TABLE ACCESS BY INDEX ROWID T1 (cr=200 r=0 w=0 time=1524 us)
    100   INDEX RANGE SCAN SYS_C006977 (cr=100 r=0 w=0 time=785 us)(object id
41233)
********************************************************************************
select y from t2 where x >= 0

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      103      0.00       0.00          0        200          0         100

Rows     Row Source Operation
-------  ---------------------------------------------------
    100  TABLE ACCESS BY INDEX ROWID T2 (cr=200 r=0 w=0 time=1597 us)
    100   INDEX RANGE SCAN SYS_C006978 (cr=100 r=0 w=0 time=785 us)(object id
41235)
Rating: 5
cardinality of the distinct values in the column November 06, 2003
Reviewer: Mikito Harakiri
is called NDV (Number of Distinct Values)!

Assuming that we agree upon

"total cardinality"/"cardinality after filter predicates applied"

value as selectivity, then the "higher" selectivity means that we filter away
more rows. This definition is inconsistent, however, with the selectivity
variable used in 10053. The latter is

"cardinality after filter predicates applied"/"total cardinality"

which is, assuming uniform distribution, equal to 1/NDV.

In short, when we say "selectivity is high" we mentally refer to some value that
is inverse to the one that we see printed in 10053. If we try to correct the
language to mean selectivity from 10053, then have counter intuitive selectivity
meaning.
Followup:
"we" is too broad a term because obviously "we" did not include "me"

when I (and many people) talk of the selectivity of a column -- "is column X
selective" -- "we" mean "will 'where x = :x' return TONS of rows, or just a few"

If a column is highly selective, it returns few rows.
If a column is not selective, it returns tons.

this is (in my experience) extremely common terminology when talking about
columns and indexes.
Rating: 4
Pls. Help November 06, 2003
Reviewer: A reader from NJ, USA
Tom,

In the example you explained in your book about colocated and disorganized, the
disorganized table was a heap table with data values spread across multiple
table blocks. This table was not an IOT. In that case, the trace output should
have only shown a huge difference in physical reads as opposed to consistent
gets. ie. My point is, even if the data is colocated, Oracle will perform a
minimum of 2 consistent reads per row. ie. index block access + table access.
The same would be true for disorganized table except that since the table is
disorganized, the next index entry would point to a table block not in the
buffer cache and so will perform a physical read to bring the block into the
buffer cache and then perform a logical I/O on it. So going by the above logic,
I should have find a major difference only in the physical reads and not in
terms of consistent gets. Why is the difference in consistent gets and not
physical reads. Here is an example that can provide you a clear picture of what
I am not clear about:

Select x from colocated where x between 5 and 8

Index Entry (data colocated)
Key(x) Rowid
5      B1xxx
6      B1yyy
6      B1zzz
8      B1aaa

So in the above case, all data is in block B1 and so Oracle will perform 8
consistent gets (assuming row-by-row process) + (0 or 1 physical read depending
on if B1 is already in buffer cache).

Select x from disorganized where x between 5 and 8

Index Entry (data disorganized)
Key(x)  RowId
5       B1xxx
6       B5yyy
6       B6zzz
8       B1aaa

In the above case also, Oracle has to perform 8 consistent gets (assuming
row-by-row processing) + 3 physical reads since the data is in 3 different
blocks B1,B5 and B6. So the only difference should be physical reads and not
consistent gets.

1. Can you explain why difference is in "Consistent gets". Am I missing
something or is my understanding not correct. 2.Also, how does the clause
"minimize records per block" clause of the "Alter table" statement make the
table disorganized.
3. How did you arrive at a figure of 101 "consistent gets" in the above example
if you do a bulk fetch.

As always, thanks much for your clear explanation
Followup:
there is this thing called a buffer cache.  That the data is disorganized does
not mean "physical io WILL be higher everytime".  It means "there is a better
chance that physical io will be higher"

the trace output does not, did not, should not always show a huge disparity
here.

What I was trying to show was "consistent gets" differences.

I like to say "if you watch the LIO's -- the PIO's will take care of themselves"
 (a blatant ripoff of the saying "if you watch your pennies, the dollars will
take care of themselves").  We were looking 100% at LIO's.

if the data is in the cache, the PIO's will stay the same.  thats all.
Rating: 3
Pls Help November 07, 2003
Reviewer: A reader from NJ, USA
Tom,

I think I understand that there is a chance that the physical I/O may be higher.
But what is not clear to me is that if Oracle is doing a index block read + a
table block read (if the access is via an index), then I like to know how the
data proximity be of any significance if we do a row-by-row fetch. ie. it is
doing to do two "consistent gets" (one for index and one for table) when the
data is either colocated or disorganized and "consistent gets" will be the same
in both cases. However, if we do an array fetch, then it might be of great
significance. If this understanding of mine is not correct, please let me know
Followup:
that is the point

if you do row by row -- it isn't going to matter much as for the LIO's

however, good, smart developers won't be doing row by slow row :)

Also, BULK statements (updates, inserts, deletes, selects with aggregates, etc)
will all have reduced LIO's with "clustered" data like that (assuming they
access the clustered data of course)
Rating: 4
Consistent Gets - Index Root/Branch block November 11, 2003
Reviewer: A reader from NJ, USA
Tom,

In one of the example above, you mentioned that you would perform 2 consistent
gets per row (accessing 1 row at a time via the index) e.g 200 consistent gets
for 100 rows

1. What about accessing the index root/branch blocks. My understanding is that
it will be included as 2 physical reads (if they are not already read into the
buffer cache) or added to current mode gets as a ONE-TIME AFFAIR FOR ALL of the
100 rows and would NOT appear as consistent gets for each row processed. Is my
understanding correct?
2. If a block is not in the buffer cache, Oracle would direct the O/S to read
the block from the disk. This will be considered as "1 physical read" + ("1
consistent gets" or " 1 current mode gets).
3. If the block is already in the buffer cache, then it will be considered as
either a "consistent gets" or "current mode" gets and the "physical reads" will
NOT be incremented.

Is my understanding for all of the above 3 correc? If not, pls. advise.

Thanks
Followup:
1) you only read the root/branches ONCE in the query -- once we get to a leaf
block, we can get to the next one just by going there.  leaf blocks are linked
like a linked list.

I was being "conceptual" when I said 200 for 100 rows.  there would be a little
more - overhead to get the very first row.

2) sure

3) correct
Rating: 3
Question - Count(*) November 18, 2003
Reviewer: A reader from NJ, USA
Tom,

I have created a IOT table called POS_IOT from a heap table called POSITION. The
IOT and the heap tables both have the primary key position_Id. Per this
discussion, I executed the following queries on the IOT and heap tables and I
have the following autotrace output.

SQL/SPD6>select count(*) from pos_iot where position_id > 0;

  COUNT(*)
----------
     98242

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=13)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'POS_IOT_PK' (UNIQUE) (Cost=2 Card
          =1 Bytes=13)

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

SQL/SPD6>select count(*) from position where position_id > 0;

  COUNT(*)
----------
     98242

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=30 Card=1 Bytes=5)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FAST FULL SCAN) OF 'SYS_C002471' (UNIQUE) (Cost=3
          0 Card=98242 Bytes=491210)

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

Question

1. I executed this script via SQL*plus first with a default array size and then
with array size of 20. My understanding is that in an IOT, the data will be
clustered together and so an index range scan will produce lower LIO in IOT than
in case of heap table. However, I see that I always get 2420 consistent gets for
IOT vs 530 consistent gets for heap. Can you help me out as to why it is
happening. Does COUNT(*) behave differently in these situations.
2. What if the table is huge (in terms of number of columns) and if I do a
SELECT *, which will perform better. IOT or heap?
3. I analyzed the heap table and I have the following info :

Primary key constraint/index : SYS_C002471
Total blocks allocated for table : 32768
Blocks  : 1725
Empty Blocks : 31022
Clustering Factor : 7479
Num_Rows : 98242
Column_Name : Position_Id
4. I have another column UPDATE_DATE in the POSITION heap table that has an
index with clustering_factor 7385. Since this clustering factor (7385) < Primary
key clustering factor (7479), and since 7385 is close to 1725 (no. of table
blocks) than 7479, does that mean performing an index range scan on update_date
will result in much lower LIO than performing index range scan on position_id?
5. When you say that "if clustering_factor is close to the number of
blocks....", what does blocks mean. Is blocks = total blocks allocated to the
segment or all blocks below HWM or blocks below HWM that have rows and that are
not empty? Pls. clarify
Followup:
1) arraysize only affects queries that return lots of rows.

your count(*) returns 1 row, 1 row -- not lots.  arraysize = 1, 10, 100, 1000 -
would all result in the same number of round trips!

Now, why the difference?  You don't share your table with us -- but, the IOT has
the entire ROW in the index, the primary key index DOES NOT.

the IOT is fatter then the traditional INDEX.

So, in this case, counting 98k rows in the IOT must inspect many more blocks
since you have less "rows" on each index leaf node since they are "fat"

2) either or, they will both have to return the entire row.

3) great?

4) MUCH LOWER -- no, a little lower (since the number is just a little lower
itself) - maybe

5) the number of blocks in the base table below the hwm.
Rating: 3
Bulk collects?? November 20, 2003
Reviewer: Duncan from UK
Tom

Here is an extract from one of our tkprof reports:

SELECT PRACTITIONER_ID,PRACTITIONER_CODE,CLAIM_TYPE,PAYMENT_ORG_ID,
  PAYMENT_ORG_CODE,SIGHT_TEST_DATE,VALUE_CURR_SIGHT_TEST,
  OPT_DOMICILIARY_VISIT,PATIENT_DV_VISITED,VALUE_VISIT,CLAIM_ID
FROM
 PAID_CLAIM  WHERE PAYMENT_ORG_ID = :b1  AND PAYMENT_ORG_CODE = :b2  AND
  PRACTITIONER_ID = :b3  AND PRACTITIONER_CODE = :b4  AND PAYMENT_CYCLE_ID =
  :b5  AND CLAIM_TYPE = 'GOS1'  AND CLAIM_STATUS = 4

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      5.78       6.45          0         61          0           0
Execute    127     19.63      20.46          0          0          0           0
Fetch     1734     37.72     300.08       3117     178955          0        1608
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1862     63.13     326.99       3117     179016          0        1608

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 41     (recursive depth: 1)
********************************************************************************

The sql comes from a pl/sql procedure cursor call which is:

FOR rec_PaidClaim IN (    SELECT  Practitioner_ID,
                Practitioner_Code,
                Claim_Type,
                Payment_Org_Id,
                Payment_Org_Code,
                Sight_Test_Date,
                Value_Curr_Sight_Test,
                OPT_Domiciliary_Visit,
                Patient_DV_Visited,
                Value_Visit,
                Claim_ID
            FROM    Paid_Claim
            WHERE    Payment_Org_ID         = P_Payment_Org_ID
            AND    Payment_Org_Code     = P_Payment_Org_Code
            AND    Practitioner_ID     = P_Practitioner_ID
            AND    Practitioner_Code    = P_Practitioner_Code
            AND    Payment_Cycle_ID    = P_Payment_Cycle_ID
            AND    CLAIM_TYPE        = 'GOS1'
            AND    Claim_Status        = 4            ) LOOP

 --lots of work done within block

END LOOP;

Looking at the (fetch count)/(rows fetched) ratio which is 1734/1608 = 1.08
then does this imply i am not doing bulk fetches?  Since this is within a pl/sql
procedure is this a good candidate to use BULK COLLECT ??
If so please can you show me how i would use it in this case, as i am unfamiliar
with this syntax and use.

Thanks for your help.
Followup:
  1* select 1608/127 from dual
ops$tkyte@ORA920LAP> /

  1608/127
----------
12.6614173

you get about 13 rows per execution.  Your bulk collect would get about 13 rows.

I've found it takes a little more then 13 rows (nearing 3 digits) to get
appreciable "benefit" from bulk collects.  My advice -- BENCHMARK your case.

generally, the additional code/memory needed for bulk collect pays off after 50
or more rows per cursor.

what I would be looking at is.... how can I make this query execute ONCE instead
of calling it procedurally 127 times :)
Rating: 3
IOT vs Heap November 22, 2003
Reviewer: A reader from NJ, USA
Tom,

In the discussion with reference to IOT vs heap table (the discussion prior to
the last one), I used the following script to create the IOT.

CREATE TABLE POS_IOT
(
  position_id number,
  ....
  ....
  constraint pos_iot_pk primary key(position_id)
)
organization index;

create table position
(
  position_id number primary key,
  ...
  ...
  ...
)

The records were inserted into the position table (heap) in a random order of
primary key.

The following script was used to populate the IOT table

insert into pos_iot as select * from position;

Sorry that I forgot to include/share the table structure in my previous
discussion.

Please let me know why the IOT took more consistent gets with this kind of
setup. ie. why it took more consistent gets in the case of IOT even though the
IOT was not fat and had only the position_id as the primary key.
Followup:
it should be obvious?

to do the count(*) -- the heap table looked solely at the index (only thing in
this index -- position).  the IOT had to scan the leaf blocks that had in
addition to the position column -- ALL OF THE OTHER COLUMNS.

If your primary, main, #1 goal is "count positions greater then some number",
the primary key index on the heap table is going to win.

However, I doubt that is your primary, #1, main goal (eg: seems like a strange
question to ask frequently).  So, suggest you test your real questions.  Do you
for example:

select * from t where position_id = :x

frequently?  (iot will win)

do you join by position_id from some other table frequently?  (iot will win)

if the IOT was the hands down winner in each and every case -- we would not have
heap tables.  It is just a tool you can use to get similar data clumped
together, to avoid having a redundant table (association tables for example) and
so on.
Rating: 3
Minimize records per block December 16, 2003
Reviewer: A reader from NJ, USA
Tom,

In one of the discussion above, you had inserted a record in a table T2 and did
a ALTER TABLE MINIMIZE RECORDS PER BLOCK. My question is
1. The table is already fitted with more than 4000 bytes (VARCHAR2(4000) column
which has been RPAD with '*' to fill entire 4000 bytes) + Bytes occupied by 2
integer columns. So only 1 row will be placed in each block assuming a block
size of 8K. What is the purpose/significance of MINIMIZE RECORDS PER BLOCK in
this case. Any reason for doing that. Please explain? What will happen if we do
not give it.
Followup:
it just make it obvious and clear that the records per block were being
constrained.

to find out what will happen without it - well, i would have run it myself :)
December 17, 2003 Debra -- Thanks for the question regarding "Standby Database/Alert Log", version 8.1.7
You Asked
Standby Databases:
Whenever I create a new tablespace in my default database, the tablespace is not
getting recreated in my standby database, causing me to manually recover the
standby database. Could you offer suggestions on what I may be doing wrong?

Alert Log:
Is there any way to set up my alert log such that changes to my database such as
Create/Alter/Drop tablespaces are tracked according to the user responsible for
performing the action?

Procedures/Functions:
Is their a data dictionary view I can access to see the names of
packages/procedures/functions that have been created in my database?

Thank you for your help!
and we said...
standbys --
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76995/standbym.htm#30738
It is very "abnormal" in a production instance to be creating new tablespaces
frequently isn't it?  I mean -- it is almost unheard of? (at least in my
experience).  It is so rare that the fact that this is a manual operation
typically is not an issue at all.

In 9i data guard (standby) there is a new feature:

...
You can add new datafiles to the primary database without having to manually
add the corresponding datafile to the standby databases. The new datafile is
created and added to the standby databases if the DBA has set up the
STANDBY_FILE_MANAGEMENT and DB_FILE_NAME_CONVERT initialization
parameters.
......

2) no, you would use auditing at the database level to capture this information
in the sys.aud$ or OS audit trail

3) all_objects has that, where object_type in ( 'PACKAGE', 'PROCEDURE',
'FUNCTION' )
Review & Followup

Rating: 2
Tablespaces June 26, 2002
Reviewer: Andre from Brazil
Debra did NOT say she is creating tablespaces "frequently".

There is nothing "abnormal" in creating tablespaces every now and then in
production -- especially on systems that are beginning to operate, or have just
come from a migration. I would only say it is not SO usual.

I myself am working on a project right now where we are migrating lots of
applications from other SGDBs and adding them slowly to Oracle, obviously after
testing them in UAT.

The only thing she is missing is that her version of the standby database does
not propagate the modifications, as in 9i.
Followup:
Hey, they say "whenever I create a enw tablespace in my default dateabase
......" -- that indicates "frequently" to me.  Means "i do this more then once".

I would so say "it is unusual".  100%.

You are in an "unusual" position yourself.  It is a position you won't be in for
long -- and then its over.
Rating: 1
tablespaces June 26, 2002
Reviewer: Andre from Brazil
Sorry, I will have to disagree with you:

1. "Whenever" does not at all imply "frequently". She might be doing it yearly
and still say "whenever".

2. Now I agree with you. It is UNUSUAL -- not "abnormal", "unheard of" or
"rare", as you had put. In fact, I have NEVER seen a production system where you
don't add tablespaces someday -- 3th party systems, like ERPs, CRMs are the only
exceptions I can think of. New applications are often poping up and they have to
be migrated to production.

3. To cap it all, why in the world would it be such an interesting feature --
and in fact it IS -- in 9i if it were so "abnormal", "unheard of" or "rare" ?
Rating: 5
Adding tablespaces frequently can be 'normal' June 27, 2002
Reviewer: Johan Snyman from JHB SA
We have 3 partitioned tables with all the partitions relating to a specific
month in a tablespace. We have a monthly procedure whereby we add a tablespace,
add partitions to the tables in this new tablespace and get rid of the oldest
partitions and associated tablespace, thereby implementing a rolling window.

Therefore, in this quite 'normal' situation, we create a tablespace every month
in the production system
Followup:
I would recommmend that you REUSE tablespaces on a rolling window situation.
(if you have Jonathan Lewis's awesome book -- practical Oracle8i -- you'll know
why).  I would NOT recommend adding and dropping uniquely named tablespaces for
a long period of time (watch what happens to SMON if you do over time).

So, have an empty tablespace -- put your new partition in that -- drop the old
partition and shrink the datafiles if you like.  Next month, reuse that
tablespace you just freed up and so on in a cyclical process.

So, again -- I'll gladly stick with "abnormal" (abnormal being a synonym for
unusual and since unusual seems to offend less people -- maybe I'll use unusual
instead...)
Rating: 1
Really need this!? June 27, 2002
Reviewer: HK from Finland
I think it's best to all of us that we don't spend Tom's time to argue issue
like this(is something abnormal or what). We have different needs of course and
Tom helps us to solve those needs.
Rating: 4
New DataFile December 10, 2003
Reviewer: DXL from UK
Tom

i have added a new datafile to my primary 8.1.7.4 database obviously this needs
to be propogated to the standby machine.
I have read 2 conflicting ways of doing this.  The OCP text book states:

1) shutdown standby db

2) Create new datafile in primary db

3) ftp the new datafiles to the standby server and place in correct dir

4) start standby db in nomount mode then change to mount mode:

SVRMGR>startup nomount
SVRMGR>alter database mount standby database;

5) place standby in managed or manual recovery mode:

SVRMGR>recover managed standby database;

6) Archive all logs on primary db and ftp to standby machine

SVRMGR>alter system switch logfile

7) recover the standby database so that all the archive logs with new control
 file changes are recorded in the standby database

SVRMGR> recover managed standby database cancel;

8) now add the new data files to the standby db.

SVRMGR>  alter database create datafile '/data/OP3/oph_idx_12.dbf' as
'/data/OP3/oph_idx_12.dbf';

9) place standby db back in recovery mode again:

SVRMGR>  recover managed standby database;

However althought very similiar the ORACLE documentation states:

To add a tablespace or datafile to the primary database and create the datafile
in the standby database:

1) Create a tablespace on the primary database as usual. For example, to create
new datafile t_db2.f in tablespace tbs_2, issue:

SQL> CREATE TABLESPACE tbs_2 DATAFILE 't_db2.f' SIZE 2M;

2) If the standby database is shut down, start the standby instance without
mounting it. For example, enter:

SQL> STARTUP NOMOUNT pfile=/private1/stby/initSTANDBY.ora

If the standby database is currently in managed recovery mode, skip to step 4.

3) Mount the standby database, then place it in managed recovery mode:

SQL> ALTER DATABASE MOUNT STANDBY DATABASE;
SQL> RECOVER MANAGED STANDBY DATABASE;

4) Switch redo logs on the primary database to initiate redo archival to the
standby database:

SQL> ALTER SYSTEM SWITCH LOGFILE;

If the recovery process on the standby database tries to apply the redo
containing the CREATE TABLESPACE statement, it stops because the new datafile
does not exist on the standby site.

5) Either wait for the standby database to cancel recovery because it cannot
find the new datafile, or manually cancel managed recovery:

SQL> RECOVER MANAGED STANDBY DATABASE CANCEL;

Note that CREATE TABLESPACE redo adds the new filename to the standby control
file. The following alert.log entry is generated:

WARNING! Recovering datafile 2 from a fuzzy file. If not the current file it
might be an online backup taken without entering the begin backup command.
Successfully added datafile 2 to media recovery
Datafile #2: '/private1/stby/t_db2.f'

6) Create the datafile on the standby database. For example, issue:

SQL> ALTER DATABASE CREATE DATAFILE '/private1/stby/t_db2.f'
                                 AS '/private1/stby/t_db2.f';

7) Place the standby database in managed recovery mode:

SQL> RECOVER MANAGED STANDBY DATABASE;

The difference between the two being that the oracle docs DON'T say you have to
ftp your new datafile from the primary to the standby.  So which is the correct
way (or does either way work)??

My problem with the first method is that you issue the archive logs to the
standby before you issue the "create datafile" on the standby.  So what happens
if after creating the datafile on the primary, before you ftp it, users actually
cause data to be stored in the datafile. So then when you apply the archive logs
to the standby how does the database know about the new datafile if the "create
datafile" has not been issued??
And if it already does know about the new datafile, why does it need the "create
datafile" statement anyway??

Please can you shed some light on this!
Followup:
you don't need to ftp.

use the docs, not ocp "guide material"

the answer to you last question is answered with the docs:

If the recovery process on the standby database tries to apply the redo
containing the CREATE TABLESPACE statement, it stops because the new datafile
does not exist on the standby site.
Rating: 5
about standby mode December 17, 2003
Reviewer: andrew sun
my database's standby mode is
SQL> SELECT t.PROTECTION_MODE FROM v$database t;

PROTECTION_MODE
--------------------
MAXIMUM PERFORMANCE

i want to know in this mode,can i get the synchronization between primary and
the standby at transcation level.in other words,in this mode can the physical
standby database have the highest protect and when the primary database
crash,the standby can have no data loss?(my precondition is not change mode,i
know maximum availability maybe can!)
Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96653/log_transport.htm#1071134
max performance says "I'm willing to lose some data -- either the last archive
or the last couple of transactions -- in order to run as fast as I possibly can"

you'd need to go up a level to get no data loss protection.

you would have to either go up a level or use remote disk mirroring for your
online redo logs (which is just like going up a level but costs you lots and
severely limits the distance at which you can put between standby and
production)
December 17, 2003 -- Thanks for the question regarding "open_links parameter", version 8.1.6
You Asked
I have five questions regarding the open_links parameter.

1)  What resources (eg. memory, processes, etc) does each open_link use/require
?  For instance, what resources are used  when we have open_links = 100 ?

2)  How can we monitor the number of active open_links (v$view?) ?

3)  Can we manually close an active open_link ?

4)  What is the lru algorithm for open_links ?

5)  Are there any references that go into details about open_links and how they
work ?

Thanks,

Jim
and we said...
1) setting open_links to 100 won't have much of an effect on either the PGA or
SGA size.  It simply sets an upper bound on the number of open connections that
will be maintained.

It appears that each dblink takes about 500k of PGA memory after opening.

2) select * from v$dblink
will show you what dblinks are open in the current session.

3)  alter session close database link <linkname>

will close them.

4) it just finds the first NON-used link and closes it.  If you have open_links
set to 1 for example and attempt:

tkyte@TKYTE816> select * from dual@l1;

D
-
X

tkyte@TKYTE816> select * from dual@l5;
select * from dual@l5
                   *
ERROR at line 1:
ORA-02020: too many database links in use

That fails until you commit (freeing it up)

tkyte@TKYTE816> commit;

Commit complete.

tkyte@TKYTE816> select * from dual@l5;

D
-
X

It is not an LRU, its first I find that is not being used right now in the
session gets closed.

5) this chapter in the distributed systems guide covers this (and answers most
of the above as well)
http://technet.oracle.com/doc/oracle8i_816/server.816/a76960/ds_admin.htm#22888
....
Review & Followup

Rating: 4
close dblink in PL/SQL January 13, 2003
Reviewer: June from MD, USA
Tom,

thanks for all useful information on database links. However I couldn't close
dblink in PL/SQL code after I explicitly commit and 'execute immediate alter
session close dblink linkname' which raised error of ORA-02020: too many
database links in use. I plan to check data in one procedure across 5-7 database
links, and I know I could change open_links parameter or break one procedure to
two. well, before I bounce production database or schedule two daily jobs in
production, I would like to know if it is possible to close dblink in PL/SQL.
any suggestion is appreciated.

my piece of code is as following for checking one source system and there will
be 5-7 same blocks to check against different source system:

....
sql_stmnt varchar2(50):='ALTER SESSION CLOSE DATABASE LINK';
-- check for MSA source systems
BEGIN

SELECT COUNT(*) INTO V_MSA_CNT
FROM  msa.dstrt@msa_link
;

IF V_MSA_CNT IS NULL OR V_MSA_CNT=0 THEN
    v_error_value_tx := 'ERROR-NO DATA FOR DSTRT on SOURCE SYSTEM MSA';
    pop_batch_error_log(v_objct_nm, NULL, v_error_value_tx, FALSE) ;
    v_ABEND_FL:='Y';
END IF;

EXCEPTION

WHEN OTHERS THEN

    v_error_code  := SQLCODE;
    v_error_value_tx := 'ERROR-for MSA on '||V_PROCESS_DT;
    pop_batch_error_log(v_objct_nm, NULL, v_error_value_tx, FALSE);
    v_ABEND_FL:='Y';

END;

commit;

sql_stmnt1 := sql_stmnt||' msa_link';
EXECUTE IMMEDIATE sql_stmnt1;
dbms_output.put_line(sql_stmnt1);
....
up to check 4 source system, I could get output on screen like
...
ALTER SESSION CLOSE DATABASE LINK  msa_link

however, once exceed 4, ORA-02020 raises.
Followup:
The problem is that when you statically reference them -- the database is trying
to make sure they are all ok and valid before it runs the code.

Use dynamic references and it won't (cannot) try to validate them.  The error is
not when the code executes but immediately PRIOR.

scott@ORA815> create or replace procedure p
  2  as
  3          l_cnt number;
  4  begin
  5          execute immediate 'select count(*) from dual@d1' into l_cnt;
  6          commit;
  7          execute immediate 'alter session close database link d1';
  8
  9          execute immediate 'select count(*) from dual@d2' into l_cnt;
 10          commit;
 11          execute immediate 'alter session close database link d2';
 12
 13          execute immediate 'select count(*) from dual@d3' into l_cnt;
 14          commit;
 15          execute immediate 'alter session close database link d3';
 16
 17          execute immediate 'select count(*) from dual@d4' into l_cnt;
 18          commit;
 19          execute immediate 'alter session close database link d4';
 20
 21          execute immediate 'select count(*) from dual@d5' into l_cnt;
 22          commit;
 23          execute immediate 'alter session close database link d5';
 24  end;
 25  /

Procedure created.

scott@ORA815>
scott@ORA815> exec p

PL/SQL procedure successfully completed.

That runs -- but if you use static sql for the select's -- it fails.
Rating: 5
It works January 14, 2003
Reviewer: J from MD, USA
Thanks for quick reply. It works!
Rating: 3
closing links after select June 10, 2003
Reviewer: Scott Wesley from Perth, WA
G'day

Why do we need to issue a commit, even when we have only *selected* data over
the link, before we issue a close.

I created a simple test case, and it always raises
ORA-02080: database link is in use
when I did not have a commit/rollback.

select x into y from z@remote;
exec immediate 'alter session close database link remote';
*
error
ORA-02080: database link is in use

This also happens when I declare an explicit cursor, and close the cursor.

The documentation doesn't seem to cover this issue greatly...

Your assistance is always appreciated.

Thanks.
Followup:
touching a database link implicitly begins a transaction - you can see that in
v$transaction:

ops$tkyte@ORA817DEV> select a.addr , b.sid
  2    from v$transaction a, v$session b
  3   where a.addr = b.taddr
  4     and b.sid = (select sid from v$mystat where rownum = 1)
  5  /

no rows selected

ops$tkyte@ORA817DEV> select * from dual@ora920.us.oracle.com;

D
-
X

ops$tkyte@ORA817DEV> select a.addr , b.sid
  2    from v$transaction a, v$session b
  3   where a.addr = b.taddr
  4     and b.sid = (select sid from v$mystat where rownum = 1)
  5  /

ADDR            SID
-------- ----------
803732B8          8

ops$tkyte@ORA817DEV> alter session close database link ora920.us.oracle.com;
ERROR:
ORA-02080: database link is in use

ops$tkyte@ORA817DEV> commit;

Commit complete.

ops$tkyte@ORA817DEV> alter session close database link ora920.us.oracle.com;

Session altered.

ops$tkyte@ORA817DEV>

"why" -- it just does, always has.  you've done something "complex" -- opened a
remote session, that begins a transaction.
Rating: 5
Internal Behaviour December 17, 2003
Reviewer: vaibhav from India
I would like to know why does oracle needs to start a transaction when it
fetches records from the other database.

A handshake mechanism is useful when PL/SQL is at play, but SQL statements like
"SELECT" starting a transaction is hard to digest.

Is there a separate engine for processing SQL and PL/SQL? two extra statements
(rollback and alter session close dblink) have to be fired, which i suppose are
an overhead.

Generally, oracle has a very wonderful and simple way of doing things.Could you
please describe as to why oracle behaves in such a manner when it comes to
database links ?
Followup:
as i said once above

...
"why" -- it just does, always has.  you've done something "complex" -- opened a
remote session, that begins a transaction.
......

select's can start transactions in a single instance of Oracle -- is there an
issue you are having with the fact that the transaction is "started"?  If so, we
can work on solving that issue (since changing the way it works is not really an
option)
December 17, 2003 Jack -- Thanks for the question regarding "Initializing a PLSQL table of records", version 9.2.0
You Asked
Tom,

How do you initialize a PL/SQL table of records in the Declaration section of a
PL/SQL block?

In the following snippet, I can successfully initialize a normal scalar PL/SQL
table but am unsuccessful initializing a table of records.  Can it be done?

Also, why is the last loop bombing with "Reference to uninitialized collection"?

SET SERVEROUTPUT ON
DECLARE
  TYPE t_type IS TABLE OF VARCHAR2 (30);
  l_array    t_type  := t_type ('X1', 'X2', 'X3');

  TYPE r_type IS RECORD (
    code          VARCHAR2 (3),
    description   VARCHAR2 (30)
  );
  TYPE tr_type IS TABLE OF r_type;
  l_rarray   tr_type; -- := tr_type... ?
  l_rec r_type;
BEGIN
  FOR i IN l_array.FIRST .. l_array.LAST LOOP
    DBMS_OUTPUT.put_line (l_array (i));
  END LOOP;
  --
  -- I'd like to move these initializations to the declaration
  -- section above.
  l_rec.code        := 'R1';
  l_rec.description := 'R1 Description';
  l_rarray(1) := l_rec;
  --
  -- why is this bombing with "Reference to uninitialized collection"
  FOR i IN l_rarray.FIRST .. l_rarray.LAST LOOP
    DBMS_OUTPUT.put_line (l_rarray (i).code || ' - ' || l_rarray
(i).description);
  END LOOP;
END;
/

Thanks!
and we said...
There is no syntax for initializing in the declare section with a table of
records.

A collection of objects -- yes.

ops$tkyte@ORA920> create type myScalarType as object
  2  ( x int, y date, z varchar2(20) );
  3  /

Type created.

ops$tkyte@ORA920> create type myArrayType as table of myScalarType
  2  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2     l_data myArrayType := myArrayType( myScalarType( 1, sysdate, 'hello' ),
  3                                        myScalarType( 2, sysdate, 'world' )
);
  4  begin
  5          null;
  6  end;
  7  /

PL/SQL procedure successfully completed.

As for the second question -- the LOOP is not "bombing out", the assignment to
l_rarray(1) is.  l_rarray is not initialized at all, and if we initialized it:

ops$tkyte@ORA920> SET SERVEROUTPUT ON
ops$tkyte@ORA920> DECLARE
  2    TYPE r_type IS RECORD (
  3      code          VARCHAR2 (3),
  4      description   VARCHAR2 (30)
  5    );
  6    TYPE tr_type IS TABLE OF r_type;
  7    l_rarray   tr_type := tr_type();
  8    l_rec r_type;
  9  BEGIN
 10    l_rec.code        := 'R1';
 11    l_rec.description := 'R1 Description';
 12    l_rarray(1) := l_rec;
 13  END;
 14  /
DECLARE
*
ERROR at line 1:
ORA-06533: Subscript beyond count
ORA-06512: at line 12

you'll see we did not allocate it (collections work differently then plsql
table types).  So, we'll allocate it

ops$tkyte@ORA920>
ops$tkyte@ORA920> DECLARE
  2    TYPE r_type IS RECORD (
  3      code          VARCHAR2 (3),
  4      description   VARCHAR2 (30)
  5    );
  6    TYPE tr_type IS TABLE OF r_type;
  7    l_rarray   tr_type := tr_type();
  8    l_rec r_type;
  9  BEGIN
 10    l_rec.code        := 'R1';
 11    l_rec.description := 'R1 Description';
 12
 13    l_rarray.extend;
 14    l_rarray(1) := l_rec;
 15  END;
 16  /

PL/SQL procedure successfully completed.

or, we can allocate and initialize in one step:

ops$tkyte@ORA920>
ops$tkyte@ORA920> DECLARE
  2    TYPE r_type IS RECORD (
  3      code          VARCHAR2 (3),
  4      description   VARCHAR2 (30)
  5    );
  6    TYPE tr_type IS TABLE OF r_type;
  7    l_rarray   tr_type ;
  8    l_rec r_type;
  9  BEGIN
 10    l_rec.code        := 'R1';
 11    l_rec.description := 'R1 Description';
 12    l_rarray := tr_type( l_rec );
 13
 14    FOR i IN l_rarray.FIRST .. l_rarray.LAST
 15    LOOP
 16          dbms_output.put_line( i );
 17      DBMS_OUTPUT.put_line (l_rarray(i).code || ' - ' ||
l_rarray(i).description);
 18    END LOOP;
 19  END;
 20  /
1
R1 - R1 Description

PL/SQL procedure successfully completed.
Review & Followup

Rating: 3
why not use "INDEX BY BINARY_INTEGER" December 17, 2003
Reviewer: Kelvin Tian from China
i added "INDEX BY BINARY_INTEGER" to the statements,so it worded correctly.
i want to know why not use "INDEX BY BINARY_INTEGER" ?

Thanks.

DECLARE
  TYPE t_type IS TABLE OF VARCHAR2 (30);
  l_array    t_type  := t_type ('X1', 'X2', 'X3');

  TYPE r_type IS RECORD (
    code          VARCHAR2 (3),
    description   VARCHAR2 (30)
  );
  TYPE tr_type IS TABLE OF r_type  INDEX BY BINARY_INTEGER;
  l_rarray   tr_type; -- := tr_type... ?
  l_rec r_type;
BEGIN
  FOR i IN l_array.FIRST .. l_array.LAST LOOP
    DBMS_OUTPUT.put_line (l_array (i));
  END LOOP;

  l_rec.code        := 'R1';
  l_rec.description := 'R1 Description';
  l_rarray(1) := l_rec;

  FOR i IN l_rarray.FIRST .. l_rarray.LAST LOOP
    DBMS_OUTPUT.put_line (l_rarray (i).code || ' - ' || l_rarray
(i).description);
  END LOOP;
END;
Followup:
you certainly can use that.  it is a different datatype at that point -- i
answered "how to initialize my data"
December 17, 2003 Frédéric -- Thanks for the question regarding "Public synonym", version 8.1.6
You Asked
Hi Tom,
We created a “IND” schema in which we created a “TEST” procedure and granted
execute on “TEST” to public.  When connected as IND or any other user, execution
of “TEST” fails:
SQL> exec ind.test
BEGIN ind.test; END;

          *
ERROR at line 1:
ORA-06550: line 1, column 11:
PLS-00302: component 'TEST' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

We know of the public synonym IND which points to USER_INDEXES, but to us this
error is nonsense.  How can Oracle be confused between a synonym and a schema?
And why isn’t IND one of Oracle’s reserved words?

Could you tell us how Oracle is resolving this call.  Thank you.
and we said...
Hey, yet another reason to avoid public synonyms -- thanks for that.

IND isn't a reserved word -- it just is public synonym to a dictionary table.

Now, the name resolution routines take ind.test and say "hmmm, what could ind
be.  is it a local object (eg: is there a package IND in the current_schema or a
private synonym).  No, there isn't.  Ok, is there a public synonym?  yes, yes
there is -- ok, replace ind with the synonym".

It would goto checking to see if IND was a schema after that.  Consider this --
what if it went after the schema IND first and not the public synonym?

select * from ind;

would be non-functional.  Something has to give here -- in this case, the schema
"loses".

You will have to find another schema name in order to avoid this issue entirely.

Oracle is not "confused" here at all -- IND to Oracle is first and foremost a
public synonym.  The fact that there is a schema IND is somewhat secondary.
Review & Followup

Rating: 5
February 10, 2003
Reviewer: A reader
Rating: 4
Is there any case? February 10, 2003
Reviewer: Bo from Washington, DC USA
Hi, Tom.
There're several discussions about public synonym on this web site.  You
suggested to avoid public synonym if it's possible.

Is there any case to show advantage of public synonym?  Otherwise, why do we
need it?
Followup:
I use it for a few top level things to get into the application.  for example,
look at the URL above.  F is a public synonym in my database.  there are
hundreds of stored procedures "below f", but we do not use synonyms for anything
other then F.

it is purely a matter of convienence - it has its good sides (ease of use) and
its downsides (scalability, resource consumption, confusion)
Rating: 5
Is this a bug? November 26, 2003
Reviewer: A reader
I ran the following in 9.2.0.4.

SQL> conn scott/tiger@test
Connected.
SQL> select count(*) from emp;

  COUNT(*)
----------
        14

SQL> create public synonym emp for hr.employees;

Synonym created.

SQL> conn hr/hr@test
Connected.
SQL> grant select on hr.employees to scott;

Grant succeeded.

SQL> select count(*) from emp;

  COUNT(*)
----------
       107

SQL> conn scott/tiger@test
Connected.
SQL> select count(*) from emp;

  COUNT(*)
----------
        14

SQL> rename emp to emps;

Table renamed.

SQL> select count(*) from emp;

  COUNT(*)
----------
       107

SQL> rename emps to emp;

Table renamed.

SQL> select count(*) from emp;

  COUNT(*)
----------
       107

SQL> select count(1) from emp;

  COUNT(1)
----------
        14

Why do the last two queries return different results?
Followup:
whats session_cached_cursors set to?
Rating: 5
Thanks Tom. Here are the parameters related with cursors: November 26, 2003
Reviewer: A reader
SQL> show parameter cursor

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
cursor_sharing                       string      EXACT
cursor_space_for_time                boolean     FALSE
open_cursors                         integer     300
session_cached_cursors               integer     0
Followup:
that, that would be an issue -- yes.  I've a simplier testcase.  i filed a bug
on this.  #3285915 (its not public right at this instant until it gets screened
and all)
Rating: 3
Public Synonym under Public Schema December 17, 2003
Reviewer: Abhi from IND
hello Sir,

Could you pls. Explain relationship between

OBJECT_NAME                              OWNER    OBJECT_TYPE
---------------------------------------- -------- ----------------
GV_$MAP_FILE_EXTENT                      SYS      VIEW
V_$MAP_FILE_EXTENT                       SYS      VIEW
GV$MAP_FILE_EXTENT                       PUBLIC   SYNONYM
V$MAP_FILE_EXTENT                        PUBLIC   SYNONYM

How could v$map and gv$map be created under public schema .. and whats is reason
for that.

Thanks
Followup:
public synonyms are always in the schema -- PUBLIC.

v_$<name> is a view (as all v_$.... views are)

v$<name> is the public synonym we all use to access this view.

GV is for RAC (clustered environments) -- lets you see V$ views from all
instances in a single monster view.

It is just the way it is.  there is a view (v_$....) and a public synonym that
points to the view (v$.....)
Rating: 4
Thanks Sir, December 17, 2003
Reviewer: Abhi from INDIA
Thanks Sir ... :)
A personal Request.
Sir From where we can get your pics.
I have searched many times at www.oracle.com for your
Video Clips .. etc..  but did not get any.

Thanks
Abhi
Rating: 5
December 17, 2003
Reviewer: A reader
Abhi, the lastest issue of Oracle Nov/Dec 03
has a picture of Tom on the cover page and inside as well,
didn't you see it.

FYI last row 2nd column.
Rating: 3
Reply to Reader December 17, 2003
Reviewer: A reader
Dear Reader,

 In last two lines ..

 I said , "I have searched for VIDEO CLIPS"

 VIDEO CLIPS != PICS

 FYI

 Sir, I am expecting your answer .. :)
Rating: 1
Stalker Alert ! December 18, 2003
Reviewer: Another Reader from UK
Scarey..... VERY Scarey
December 18, 2003 -- Thanks for the question regarding "write to file in PL/SQL and sort that file", version 9.0.2
You Asked
Tom --

I was wondering if you could provide an example of how to read from  a file in
PL/SQL, then sort it by, say, two fields - first name and zip code and read the
result into variables.

thank you in advance!

AM
and we said...
don't use PLSQL!

use an EXTERNAL TABLE.  it can't get any easier then this:

ops$tkyte@ORA9IR2> create or replace directory data_dir as '/tmp/'
  2  /

Directory created.

ops$tkyte@ORA9IR2> host flat scott/tiger emp > /tmp/emp.dat

ops$tkyte@ORA9IR2> host head /tmp/emp.dat
7369,SMITH,CLERK,7902,17-DEC-80,800,,20
7499,ALLEN,SALESMAN,7698,20-FEB-81,1600,300,30
7521,WARD,SALESMAN,7698,22-FEB-81,1250,500,30
7566,JONES,MANAGER,7839,02-APR-81,2975,,20
7654,MARTIN,SALESMAN,7698,28-SEP-81,1250,1400,30
7698,BLAKE,MANAGER,7839,01-MAY-81,2850,,30
7782,CLARK,MANAGER,7839,09-JUN-81,2450,,10
7788,SCOTT,ANALYST,7566,19-APR-87,3000,,20
7839,KING,PRESIDENT,,17-NOV-81,5000,,10
7844,TURNER,SALESMAN,7698,08-SEP-81,1500,0,30

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create table external_table
  2  (EMPNO NUMBER(4) ,
  3   ENAME VARCHAR2(10),
  4   JOB VARCHAR2(9),
  5   MGR NUMBER(4),
  6   HIREDATE DATE,
  7   SAL NUMBER(7, 2),
  8   COMM NUMBER(7, 2),
  9   DEPTNO NUMBER(2)
 10  )
 11  ORGANIZATION EXTERNAL
 12  ( type oracle_loader
 13    default directory data_dir
 14    access parameters
 15    ( fields terminated by ',' )
 16    location ('emp.dat')
 17  )
 18  /

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select empno, ename from external_table order by empno;

     EMPNO ENAME
---------- ----------
      7369 SMITH
      7499 ALLEN
      7521 WARD
      7566 JONES
      7654 MARTIN
      7698 BLAKE
      7782 CLARK
      7788 SCOTT
      7839 KING
      7844 TURNER
      7876 ADAMS
      7900 JAMES
      7902 FORD
      7934 MILLER

14 rows selected.

ops$tkyte@ORA9IR2> select empno, ename from external_table order by ename;

     EMPNO ENAME
---------- ----------
      7876 ADAMS
      7499 ALLEN
      7698 BLAKE
      7782 CLARK
      7902 FORD
      7900 JAMES
      7566 JONES
      7839 KING
      7654 MARTIN
      7934 MILLER
      7788 SCOTT
      7369 SMITH
      7844 TURNER
      7521 WARD

14 rows selected.
Review & Followup

Rating: 5
write to file in PL/SQL and sort that file December 11, 2003
Reviewer: A reader
Thank you for response. Do you think your approch will work if the name of the
file is actually supposed to be supplied by user as a parameter to this program,
part of which is supposed to read data from the file(they do it by the means of
an Oracle Form, which passes parameters to our programs)?
Although I do realize that reading a file with PL/SQL is like trying to open a
can of peas with a hammer, I could not think of another way to incorporate this
task in a program (which is going to be run by the user as descirbed above)..

thanx for great response!

anya
Followup:
that is a horse of a different color.

is this a FORMS application running on a CLIENT that is to read a file on the
CLIENT machine and not the SERVER?
Rating: 5
write to file in PL/SQL and sort that file December 11, 2003
Reviewer: A reader
The way our applicaton is set up is that we have Oracle Form that the users
access to submitt parameters to various jobs (Pro*C, SQL, SQRs, PL/SQLs). There
is actually a shell script that is connected to this form that passes these
parms to various programs. Anyway, by the means of various includes that we
wrote we get these parms from the shell.
The user wants to use this form as they always do to submitt a name of the file
and its directory (/userdir/filename.dat) as one of the parameters of this job.
So that the job (supposed to be written in PL/SQL) can read the records from the
file (which is by the way not delemeted by anything) and try to match these
records with the existing records in the database. So, the task here is to try
to minimize the work for the user..I dont think they will be able to load this
file into external tables :)
Followup:
you'll have to get the file from the USERS system to the SERVER.

how do you plan on doing that?  else the SERVER (where the job runs I assume)
will not be able to read said file.
Rating: 5
write to file in PL/SQL and sort that file December 11, 2003
Reviewer: A reader
Usually the user keeps this file in her/his Unix directory and they want to
specify this dir/filename as one of the parameters. We actually used to write
SQRs that would read the file from the specified dir and then sort it and
process it. However, I do not see how this will be feasible in PL/SQL...
Followup:
as much as I hate doing ddl in plsql -- you might consider it here.

how many times a day will this happen.
Rating: 5
write to file in PL/SQL and sort that file December 11, 2003
Reviewer: anya
The user will be submitting a job once a week actually..
Followup:
just use execute immediate to create a table, use it, execute immediate to drop
it.
Rating: 5
December 11, 2003
Reviewer: A reader
Tom, I never used external table feature before. Is it possible to put it all in
PL/SQL program? Can you give an example of how to put the data into table using
external table (inside the PL/SQL program)?
Followup:
declare
   l_cursor sys_refcursor;

begin
   execute immediate 'create table t' || userenv('sessionid') || '.......
organization external';

   execute immediate open l_cursor for 'select * from t' || userenv('sessionid')
||  ' order by whatever';

   process l-cursor

   execute immediate 'drop table t' || userenv( 'sessionid' );

end;
Rating: 5
write to file in PL/SQL and sort that file December 12, 2003
Reviewer: A reader
Tom --

one last thing, can  you provide an example on how to copy a file from one
directory to another form within PL/SQL? The thing is that our users want to
submit the directory/filename as parameter to a PL/SQL package. Fine, but in
order to use UTL_FILE procedures utl_dir has to be specifed in init.ora file (or
in 9i as I understand it you can use create direcrtory..). The problem is that
we do not want to specify these directories in the init.ora because different
users might run the program with different directories and we dont want to put
them all in init.ora.
So, I was thinking that my PL/SQL package would have a procedure to copy the
file form whatever directory the user specifies and to put it in the directory
that is specified in init.ora file - this way UTL_FILE package can do its majic
there.

thank you!
Followup:
you'd most likely be looking at a java stored procedure to do this.

plsql isn't appropriate right now for doing a file copy.

bfiles cannot be written to
utl_file does text only in 8i

think about your question -- if plsql could move a file from somewhere it isn't
allowed to read -- to somewhere it is, of what use would be restricting where it
could read!!!!

with a java sp, you can use dbms_java to grant read/write on specific
directories/files very granularly at runtime without restarting.
Rating: 5
write to file in PL/SQL and sort that file December 14, 2003
Reviewer: anya
That's a good suggestion.
I am on 9i and I tried the following:

ORA-29283: invalid file operation with UTL_FILE

My configurations: AIX 4.3
Oracle9i Enterprise Edition Release 9.2.0.1.0 - 64bit Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

1.

SQL> CREATE OR REPLACE DIRECTORY gwcustom as '/utl/oracle_data';

Directory created.

SQL> GRANT READ ON DIRECTORY gwcustom to public;

Grant succeeded.

2.

SQL> CREATE OR REPLACE DIRECTORY test_dir as '/5u/tst000/tstaog/test';

Directory created.

SQL> GRANT READ ON DIRECTORY test_dir to public;

Grant succeeded.

3.

SQL> select * from dba_directories;

OWNER DIRECTORY_NAME
------------------------------ ------------------------------
DIRECTORY_PATH
--------------------------------------------------------------------------------

SYS GWCUSTOM
utl/oracle_data

SYS DATAHOME_FINAID
/jobout/faid

SYS DATA_DIR
/tmp/

OWNER DIRECTORY_NAME
------------------------------ ------------------------------
DIRECTORY_PATH
--------------------------------------------------------------------------------

SYS TEST_DIR
/5u/tst000/tstaog/test

When I cd to the /url/oracle_data it is there - I see the directory, but when I
cd to
this directory it is not there!

3. Trying to take a file from a directory (say my home dir) and copy it to
GWCUSTOM directory (created by using create dir command)
I try the following:

declare
file_suffix VARCHAR2 (100)
:= TO_CHAR (SYSDATE, 'YYYYMMDDHHMISS');
begin

UTL_FILE.fcopy (
src_location => '/my_home_dir/home',
src_filename => 'test.dat',
dest_location => 'GWCUSTOM',
dest_filename => 'anya'
|| file_suffix

|| '.dat'
);

end;

I get the following error:

declare
*
ERROR at line 1:
ORA-29283: invalid file operation
ORA-06512: at "SYS.UTL_FILE", line 18
ORA-06512: at "SYS.UTL_FILE", line 1064
ORA-06512: at line 36

Any ideas about what can be wrong here?

Also, I could not find anywhere in the documentation whether you  have to create
both directories by  create directory command (the one you are taking the file
from and the one you are copying it to). My understanding was that I have to
just specify one dir with this command and that I will be able to copy files
there from wherever I want...
I searched metalink and it seems that 80% of the people do not have any luck
with this new 9i feature and 20% do....
Followup:
quote:

"When I cd to the /url/oracle_data it is there - I see the directory, but when I

cd to this directory it is not there! "

HUH?

but in anycase, your query shows "no leading / on utl/oracle"  so, I think the
command you cut and pasted here is differen then the one you actually executed
OR the query cut and paste was done wrong -- something does not add up there.

You use DIRECTORY objects in utl_file in 9i - not "real paths", why would we use
"directory objects" in one place and "real paths" in another place?  You use
directory objects -- or a path specified in UTL_FILE_DIR (the init.ora
parameter)

I sort of disagree with your 80/20 -- for you see, the vast majority of users
never file a tar in the first place!

But here is a simple, teeny example showing you the basics here.  But, I don't
know why you would do this (the copy) since you need to either

a) have utl_file_dir set (meaning plsql could already read it)
b) have a directory object created (meaning plsql could already read it)

ops$tkyte@ORA920> create or replace directory my_home as '/home/tkyte'
  2  /

Directory created.

ops$tkyte@ORA920> create or replace directory tmp_dir as '/tmp'
  2  /

Directory created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> !ls -l /tmp/test.dat /home/tkyte/test.dat
ls: /tmp/test.dat: No such file or directory
ls: /home/tkyte/test.dat: No such file or directory

ops$tkyte@ORA920> !echo hello world > /home/tkyte/test.dat

ops$tkyte@ORA920> !ls -l /tmp/test.dat /home/tkyte/test.dat
ls: /tmp/test.dat: No such file or directory
-rw-rw-r--    1 tkyte    tkyte          12 Dec 14 10:50 /home/tkyte/test.dat

ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2     UTL_FILE.fcopy ( src_location => 'MY_HOME',
  3                      src_filename => 'test.dat',
  4                      dest_location => 'TMP_DIR',
  5                      dest_filename => 'test.dat' );
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> !ls -l /tmp/test.dat /home/tkyte/test.dat
-rw-rw-r--    1 tkyte    tkyte          12 Dec 14 10:50 /home/tkyte/test.dat
-rw-rw-r--    1 ora920   ora920         12 Dec 14 10:50 /tmp/test.dat

ops$tkyte@ORA920> !cat /tmp/test.dat
hello world

ops$tkyte@ORA920>
Rating: 5
write to file in PL/SQL and sort that file December 15, 2003
Reviewer: anya
sorry, it was late..What I meant to say is that when I query dba_directories, I
see that my dir got created, but when I cd there, I get "no such file or
directory"..Am I totally off track here?
Most importanlty, do you have to create (with create dir command - I am on 9i)
BOTH directories - the one you are copying files from and the one you are
copying files to? I thought ( and, again, I could have been totally wrong). The
thing is that the directory from which I will be copying will be different every
time the program is run...I thought it was possible, am I wrong?
Followup:
how do you "cd" from within the database?

createing a directory object creates a MAPPING in the database.  It does nothing
t the file system.  You weren't expecting it to create a file system directory
were you?

the directory can be different every time AS LONG AS

a) the directory exists in the file system
b) you issued the create directory command
Rating: 5
write to file in PL/SQL and sort that file December 15, 2003
Reviewer: A reader
I was expecting a real dir..ok, thank you, Tom!!!!
Rating: 3
Deleting after file is processed December 15, 2003
Reviewer: Ahmad from Memphis-TN USA
Tom,

I have a similar situation and I am using your example described at
http://asktom.oracle.com/pls/ask/f?p=4950:8:4902156151350304293::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:439619916584
 and
would like to delete the file when its processed. I looked in metalink and found
Note:190311.1 and got the following  java procedure :

CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED "DelFile" AS
import java.io.*;
  public class  DelFile
  {
   public static void remove(String filename)
   {        File myfile = new File(filename);
     try
     {
       System.out.println("Deleting ..... "+filename );
       boolean b = myfile.delete();
     }
     catch (Exception e)
     {
      System.out.println(e);
     }
    }
  }
/

and created a pl/sql wrapper called deletefile
and my DBA has given me appropriate privilege ie

EXEC Dbms_Java.Grant_Permission('SCHEMA-NAME', 'java.io.FilePermission',
'/export/data/*', ' delete');

and when I tried to delte a file from /export/data , file is not being deleted.
I did

SQL> exec dbms_java.set_output(1000);
SQL> exec deletefile('/export/data/test1.csv');
Deleting...../export/data/test1.csv

I can see this message , however file is not being deleted in the unix
directory.

Any idea what may be wrong here. We have 9i-9.0.1.4.0

Thanks
Followup:
I hate code that catches and hides all exceptions.

get rid of the try/catch block.  let whatever error is happening propagate up.

at the very least, put a system.out.println AFTER the delete saying "it appears
to have worked"
Rating: 3
I did but no effect December 15, 2003
Reviewer: Ahmad from Memphis- TN USA
I get rid of try catch and   System.out.println("Deleting ..... "+filename ) and
still same effects. It does not delete. My oracle unix login has capability to
read/delete/write files in the directory. I donot understand whats the problem?
Followup:
what is the boolean being returned?

and do you want to just try utl_file.fremove?

(i didn't want to get RID of the system.out.printlns -- i wanted MORE of them
Rating: 4
December 16, 2003
Reviewer: reader
Tom, Why you use word "then" instead of "THAN" I think you missed the lecture
when your teacher was teaching about than.
Rating: 3
we have Rel 1- 9i December 16, 2003
Reviewer: A reader
Tom,

 we cannot use utl_file.fremove, since  we have rel 1 - 9i. The boolean variable
returns FALSE. So its not deleting the file. Any idea what should be the
problem?

Thanks

Ahmad
Followup:
permissions.

what are the permissions on the file itself in the OS (can Oracle erase that
file)

probably not.  consider

ops$tkyte@ORA9IR2> CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED "DelFile" AS
  2  import java.io.*;
  3    public class     DelFile
  4    {
  5     public static void remove(String filename)     throws Exception
  6     {
  7     File myfile = new File(filename);
  8
  9         System.out.println("Deleting ..... "+filename );
 10         if ( myfile.delete() )
 11            System.out.println( "success" );
 12         else
 13            System.out.println( "failure" );
 14    }
 15   }
 16  /

Java created.

ops$tkyte@ORA9IR2> create or replace procedure delfile( p_string in varchar2 )
  2  as language java
  3  name 'DelFile.remove( java.lang.String )';
  4  /

Procedure created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> EXEC Dbms_Java.Grant_Permission(user,
'java.io.FilePermission', '/tmp/*', ' delete');

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> @connect /
ops$tkyte@ORA9IR2> set termout off
ops$tkyte@ORA9IR2> REM GET afiedt.buf NOLIST
ops$tkyte@ORA9IR2> set termout on
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> !echo Hello > /tmp/t1.dat

ops$tkyte@ORA9IR2> !ls -l /tmp/t?.dat
-rw-rw-r--    1 tkyte    tkyte           6 Dec 16 11:43 /tmp/t1.dat
-rw-rw-r--    1 ora9ir2  ora9ir2         6 Dec 16 11:43 /tmp/t3.dat

ops$tkyte@ORA9IR2> exec dbms_java.set_output(100000);

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec delfile( '/tmp/t1.dat' );
Deleting ..... /tmp/t1.dat
failure

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec delfile( '/tmp/t3.dat' );
Deleting ..... /tmp/t3.dat
success

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> !ls -l /tmp/t?.dat
-rw-rw-r--    1 tkyte    tkyte           6 Dec 16 11:43 /tmp/t1.dat

t3.dat -- gone, t1.dat -- still there.  if I su'ed to ora9ir2 -- it cannot erase
/tmp/t1.dat, not allowed.

[tkyte@localhost tkyte]$ msu - ora9ir2
[ora9ir2@localhost ora9ir2]$ rm /tmp/t1.dat
rm: remove write-protected regular file `/tmp/t1.dat'? y
rm: cannot remove `/tmp/t1.dat': Operation not permitted
[ora9ir2@localhost ora9ir2]$

so, must be a perm problem at the OS level, remember the java proc is running as
ORACLE.
Rating: 3
permission is Ok December 16, 2003
Reviewer: Ahmad from TN, USA
Yes, I can telnet and remove the file in the directory.
Followup:
do it like I did then -- show us.

ls -l

lets see everything like I did (showing this WORKS when you can in fact ERASE
the file)

show us the userid under which the TNSLISTENER is running if you connect over
the network (since the dedicated server will run as that user)

show us everything we need to see that show this "can work"
Rating: 3
I am not sure if Oracle has permission December 16, 2003
Reviewer: Ahmad
Tom,

I will ask our DBA if Oracle has permission to delete file in the directory? I
can howevere, delete with mu userid.

Thanks
Followup:
whats the "mu" userid?
Rating: 4
checking with DBA December 16, 2003
Reviewer: Ahmad from TN, USA
I am checking with my DBA, he is outside somewhere. I donot think Oracle has
permission on my directory.
"mu" is a typo ,it is my userid.

Also I donot hasve sqlplus access in UNIX environment , I can only do it from
windows so I cannot perform your example ie !echo Hello >
/export/home/nyssis/staging/t1.dat.

But I am pretty sure Oracle does not have permission , so you are saying that
Oracle must be given OS level permission on the directory ie
/export/home/nyssis/staging in my case?

I will make sure this and will update to you.

Thanks for all your help.
Followup:
yes, that is exactly what I'm saying.  java will not give you the ability to do
something you cannot normally do.  oracle isn't allowed to erase your files,
java won't let you do it (the OS stops it)
Rating: 4
thanks it work December 17, 2003
Reviewer: Ahmad from TN- USA
Tom,
Thank you and it works. However, I have one question. I was able to read the
file using DBMS_JAVA and as you described at the thread
http://asktom.oracle.com/pls/ask/f?p=4950:8:4902156151350304293::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:439619916584
 and no permission
was given to Oracle to read files from my directory. I just granted read
permission to my user/schema to read the files. Why this works withought
granting read permission to Oracle and delete does't work?

Ahmad
Followup:
because Oracle did in fact have the permission apparently.

we do not, cannot bypass the OS here.  The user the dedicated server or shared
server you were using was running as HAD access to the file.

log into the OS and try it, you'll see.
December 18, 2003 -- Thanks for the question regarding "Soft sessionspace limit", version 8.1.7
You Asked
I got a trace file in UDUMP with contents:
*** SESSION ID:(7.39) 2001-02-15 14:29:27.726
Session 6 exceeded soft sessionspace limit of 0x100000 bytes.

what is soft sessionsspace limit ?
How can we find out what the limit is ?
HOw can we increase the limit ?
What problems will be caused if limit is too low or too high ?

Thanks.
and we said...
Check the following settings and increase as appropriate:

   JAVA_MAX_SESSIONPSPACE_SIZE
   JAVA_SOFT_SESSIONSPACE_LIMIT

Increasing the JAVA_SOFT_SESSIONSPACE_LIMIT should suppress this
warning message.
Review & Followup

Rating: 2
Session 22 exceeded soft sessionspace limit of 0x100000 bytes. December 17, 2003
Reviewer: Denise from Virginia,USA
Hi Tom

Could you please expand a little more on this issue..or
provide a link where causes and solutions are explained.

I noticed these messages in the last couple of udump/.trc
files that we never experienced before.

Couldn't find any definitive answers researching on line or in your book.

have a safe..great x-mas holiday...and thanks.
Followup:
Check the following settings and increase as appropriate:

   JAVA_MAX_SESSIONSPACE_SIZE
   JAVA_SOFT_SESSIONSPACE_LIMIT

Increasing the JAVA_SOFT_SESSIONSPACE_LIMIT should suppress this warning
message.

The above are new parameters with 8.1.6 and are documented in the "Oracle8i
Reference Manual".
December 18, 2003 San -- Thanks for the question regarding "Date Format", version 8.1.7
You Asked
Our Java application uses to_date function with 'MM/DD/YY' format. When they
query the database for 1997 it dosent fetches any record. Also they are not
ready to change the format to RR or YYYY.

Is there any way we can make change to the init parameters that will force the
query to use 1997 instead of 2097 if 'YY' format is used?

The query they using is,

SELECT  branch, customer, program, inital_rate, contract_value,      initial_dt,
status, FROM vr_service_agree_mas
WHERE
  status_date BETWEEN TO_DATE('09/01/99','MM/DD/YY') AND
TO_DATE('09/16/99','MM/DD/YY')
 AND
 agreement_no = 15067 AND
status LIKE 'Cancelled'  AND (  branch IN (22) )  ORDER BY branch, route

Thanks.
and we said...
Boy, you really think -- YOU WOULD REALLY THINK (especially with a new language
like java, where they didn't have a ton of "legacy" code) people would have
learned that it take 4 characters to represent a date!  You would really think
they would!

Your java application has a bug in it.

Your java developers are paid I would assume to fix bugs in their code.

Have them please fix their code.

YY means YY.  It means "use this century".  It cannot be overridden (it IS the
override -- when you say "to_date( .... FORMAT )" you are overriding the
databases date format.

They better be ready to change the format, else no one can use their program and
they are out of a job.
Review & Followup

Rating: 5
January 02, 2003
Reviewer: A reader
Rating: 4
January 02, 2003
Reviewer: A reader
Wasn't there some obscure rumor about some crazy people doing some bizarre thing
called Y2K-compliance and spending some obscene amount of money on it a few
years ago? Hmmm, I guess I must be mistaken. Maybe that happened in some
parallel universe...
Rating: 4
Maybe the developers were born after 1999 :-) January 02, 2003
Reviewer: Jim
Rating: 3
Oracle problem December 12, 2003
Reviewer: Palash from Detroit, MI
I am using this condition in my WHERE clause to filter records during my
migration :
1) from Sybase Data migrated via OMWorkbennch92 into an Oracle schema 1.
2) Now I am getting data from Oracle schema1 to final Oracle schema2.

AND A.DOCUMENT_DATE BETWEEN TO_DATE('09/17/2002 00:00:00','MM/DD/YYYY
HH24:MI:SS') AND TO_DATE('10/17/2003 23:59:59','MM/DD/YYYY HH24:MI:SS')

and trust me it's fetching recods for 1902 aswell.. Do I have to use century for
this aswell.. what of my filter laps between 2 centuries... 1997 to 2003?

what do you think is the problem? Please give us an example this time.. thanks
Tom..
Followup:
I don't trust you

prove it to us.

show us the table, show us the data, show us that query returning data from
1902.
Rating: 3
OK December 17, 2003
Reviewer: Sam Augustine from Hyderabad,India
Dear Sir,
Could you please help to print the weekday names from
Sunday .. saturday using a sql command?Is it possible to
use Next_day function here?
Thanks in advance
Followup:
just pick ANY sunday and......

  1   select to_char( to_date('7-dec-2003','dd-mon-yyyy')+rownum-1, 'Day')
  2*  from all_objects where rownum <= 7
ops$tkyte@ORA920> /

TO_CHAR(T
---------
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday

7 rows selected.
December 18, 2003 Rajesh -- Thanks for the question regarding "getting rows N through M of a result set", version
You Asked
I would like to fetch data after joining 3 tables and
sorting based on some field. As this query results into approx
100 records, I would like to cut the result set into 4, each of
25 record. and I would like to give sequence number to each
record. Can I do using SQL Plus ?
and we said...
In Oracle8i, release 8.1 -- yes.

select *
  from ( select a.*, rownum rnum
           from ( YOUR_QUERY_GOES_HERE -- including the order by
) a
          where rownum <= MAX_ROWS )
 where rnum >= MIN_ROWS
/

that'll do it.  It will *not* work in 8.0 or before.
Review & Followup

Rating: 5
Pagination August 03, 2001
Reviewer: Karthik from India
It was to the point and very very useful
i would keep pestering you with more questions in the weeks to come.
Rating: 4
Yes, September 25, 2001
Reviewer: A reader
it was useful.
Rating: 5
Lifesaver.... October 16, 2001
Reviewer: Robert Jackson from Dallas, Texas
This information was invaluable... I would have had to "kludge" something....
Rating: 5
Parag March 31, 2002
Reviewer: Parag Mehta from India
Tom :

Great .... I think Ora ( Oracle ) has been made for u.
I am highly impressed by ur answere.

Regards
- Parag
Followup:
you = u
your = ur

is your keyboard broken such that Y and O do not work anymore?  cle might be the
next to go.

(there are enough abbreviations and three letter acronyms in the world, do we
really have to make it HARDER to read stuff everyday by making up new ones all
of the time)
Rating: 1
Upset March 31, 2002
Reviewer: Parag from India
I am very Upset with "YOUR" Behaviour. I have not expected the same from " YOU".
You could have convey the same in a different Professional Words.

For " YOUR" kind information Dear Tom , My KEYBOARD has not broken down at all.
It's working perfectly.
Rating: 5
With you tom on 'YOUR' comment on 'u' or 'ur' March 31, 2002
Reviewer: Sean from Atlanta
Mr. Parag,

You just way over reacted.
Rating: 5
U R GR8 April 01, 2002
Reviewer: Mark A. Williams from Indianapolis, IN USA
Tom,

Maybe you could put something on the main page indicating appropriate use of
abbreviations?  Although, now that I think about it, it probably wouldn't do
much good, as it appears people ignore what is there (and on the 'acceptance'
page) anyway...

- Mark
Followup:
Already there ;)

It's my new crusade (along with bind variables).  But yes, you are correct --
most people don't read it anyway.

You would probably be surprised how many people ask me "where can I read about
your book" -- surprising given that it is right there on the home page...
Rating: 5
Saw it was there after the fact April 01, 2002
Reviewer: Mark A. Williams from Indianapolis, IN USA
Tom:

Saw that you had added the message about the abbreviations after the fact.
That's what I get for having my bookmark point to the 'Search/Archives' tab
instead of the main page...

- Mark
Rating: 5
April 01, 2002
Reviewer: A reader from Los Angeles, CA
Excellent query. I just want to be sure I understand it.
You run the query 4 times, each time changeing the MAX and MIN rownumbers.
Correct?
Followup:
You just change min and max to get different ranges of rows, yes.
Rating: 5
Very good April 01, 2002
Reviewer: Natthawut from IA,USA
This will be useful for me in the future.
Thanks.

PS. Don't listen to Mr.Parag. He just envy you ;)
Rating: 4
between April 01, 2002
Reviewer: Mikito harakiri
Returning to the old discussion about difference between

select p.*, rownum rnum
    from (select * from hz_parties ) p
          where rownum between 90 and 100

vs

select * from (
   select p.*, rownum rnum
           from (select * from hz_parties ) p
          where rownum < 100
) where rnum >= 90

I claim that they are identical fron perfomance standpoint. Indeed, the plan for
the first one

  SELECT STATEMENT  20/100
      VIEW  20/100
          Filter Predicates
              from$_subquery$_001.RNUM>=90
          COUNT (STOPKEY)
              Filter Predicates
                  ROWNUM<=100
              TABLE ACCESS (FULL) hz_parties 20/3921

seems to be faster than

  SELECT STATEMENT  20/100
      COUNT (STOPKEY)
          Filter Predicates
              ROWNUM<=100
          FILTER
              Filter Predicates
                  ROWNUM>=90
              TABLE ACCESS (FULL) hz_parties 20/3921

But, note that all nodes in the plan are unblocking!. Therefore, it doesn't
matter which condition is evaluated earier...
Followup:
Please don't claim -- benchmark and PROVE (come on -- I do it all of the time).

Your first query "where rownum between 90 and 100" never returns ANY data.  that
predicate will ALWAYS evaluate to false -- always.

I've already proven in another question (believe it was with you again) that

select * from (
   select p.*, rownum rnum
           from (select * from hz_parties ) p
          where rownum < 100
) where rnum >= 90

is faster then:

select * from (
   select p.*, rownum rnum
           from (select * from hz_parties ) p
) where rnum between 90 and 100

which is what I believe you INTENDED to type.  It has to do with the way we
process the COUNT(STOPKEY) and the fact that we must evaluate

   select p.*, rownum rnum
           from (select * from hz_parties ) p

AND THEN apply the filter where as the other will find the first 100 AND THEN
stop.

so, say I have an unindexed table:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from big_table;

  COUNT(*)
----------
   1099008

(a copy of all_objects over and over and over) and I run three queries.  Yours
to show it fails (no data), what I think you meant to type and what I would
type:

select p.*, rownum rnu
  from ( select * from big_table ) p
 where rownum between 90 and 100

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      6.17      15.31      14938      14985         81           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      6.17      15.31      14938      14985         81           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 216

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  COUNT STOPKEY
      0   FILTER
1099009    TABLE ACCESS FULL BIG_TABLE

your query -- no data found....  Look at the number of rows inspected
however

select *
from (
select p.*, rownum rnum
  from ( select * from big_table ) p
)
 where rnum between 90 and 100

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      7.93      17.03      14573      14986         81          11
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      7.93      17.03      14573      14986         81          11

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 216

Rows     Row Source Operation
-------  ---------------------------------------------------
     11  VIEW
1099008   COUNT
1099008    TABLE ACCESS FULL BIG_TABLE

what I believe you mean to type in -- agein -- look at the rows processed!

Now, what I've been telling everyone to use:

select * from (
   select p.*, rownum rnum
           from (select * from big_table ) p
          where rownum < 100
) where rnum >= 90

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.01          1          7         12          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.01          1          7         12          10

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 216

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  VIEW
     99   COUNT STOPKEY
     99    TABLE ACCESS FULL BIG_TABLE

HUGE difference.  Beat that...

Claims -- don't want em.
Benchmark, metrics, statistics -- love em -- want em -- need em.
Rating: 5
Over the top! April 01, 2002
Reviewer: Thevaraj Subramaniam from Kuala Lumpur, Malaysia
Tom, I am really very impressed with the way you prove it with examples and
explanations. Answering questions from around the world, and at the same time
facing hurdles along the way and overcoming it. You are the best! Will be always
supporting asktom.oracle.com. Cheers.
Rating: 5
Thank goodness! April 03, 2002
Reviewer: Jim from Oz
 Tom,

 Liked the solution and your new rule.

 You have my vote on the rule not to use "u" for you
 and "ur" for your. It's not clever, it simply makes
 things harder to read, in fact I think it's just plain
 lazy

 Anyone that doesn't like it can simply ask someone else.
Rating: 5
between April 03, 2002
Reviewer: Mikito harakiri
Thanks Tom. I finally noticed that you have rownum in one predicate and rnum in
the other and they are different:-)

sql>select * from (
  2     select p.*, rownum rnum
  3             from (select * from hz_parties ) p
  4            where rownum < 100
  5  ) where rnum >= 90

Statistics
----------------------------------------------------------
          7  consistent gets
          5  physical reads

The best solution I was able to get:

appsmain>select * from (
  2   select * from (
  3     select p.*, rownum rnum
  4             from (select * from hz_parties ) p
  5   ) where rnum between 90 and 100
  6  ) where rownum < 10

Statistics
----------------------------------------------------------
         15  consistent gets
          5  physical reads

It's neither faster, nor more elegant:-(
Rating: 3
actual "between" test April 03, 2002
Reviewer: Mikito harakiri
Tom,

Sorry, but I see no difference:

public static void main(String[] args) throws Exception {
    Class.forName("oracle.jdbc.driver.OracleDriver");
    System.out.println(execute("select * from (select p.*, rownum rnum "
+ "  from (select * from hz_parties ) p "
+ "           where rownum < 100 "
+ " ) where rnum >= 90  "));
    System.out.println(execute("select * from ( \n"
+ "   select p.*, rownum rnum "
+ "             from (select * from hz_parties ) p "
+ "   ) where rnum between 90 and 100"));

}
static long execute( String query ) throws Exception {
    Connection con =
DriverManager.getConnection("jdbc:oracle:thin:@dlserv7:1524:main","apps","apps");

    con.setAutoCommit(false);

    con.createStatement().execute("alter system flush shared_pool");
    long t1 = System.currentTimeMillis();
    ResultSet rs = con.createStatement().executeQuery(query);
    rs.next();
    rs.next();
    rs.next();
    rs.next();
    rs.next();
    rs.next();
    long t2 = System.currentTimeMillis();

    con.rollback();
    con.close();
    return t2 - t1;
}

Both queries return in 0.6 sec. Here is my interpretation: The "between" query
in the context where we open cursor, read first rows, and then discard the rest
is essentially the same as "between" query with stopcount (that goofy sql in my
last reply). The execution engine doesn't seem to go forward and check the
between predicate for the whole table, or does it?
Followup:
TKPROF, TKPROF, TKPROF.

thats all you need to use.

This query:

select *
  from ( select p.*, rownum rnum
           from ( YOUR_QUERY )
          where rownum < 100
       )
  where rnum >= 90

runs your query and gathers the first 100 rows and stops.  IF YOUR_QUERY must
materialize all of the rows before it can get the first row (eg: it has certain
constructs like groups by and such) -- then the difference in your case may not
be as large -- but its there.  Use TKPROF to get RID of the java overhead in the
timings (timing in a client like that isn't very reliable).

Consider:

here we obviously don't need to get the last row before the first row -- it's
very "fast"

select *
  from ( select p.*, rownum rnum
           from ( select owner, object_name, object_type
                    from big_table
                ) p
          where rownum <= 100
       )
 where rnum >= 90

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.01       0.00         63          7         12          11
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.00         63          7         12          11

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 216

Rows     Row Source Operation
-------  ---------------------------------------------------
     11  VIEW
    100   COUNT STOPKEY
    100    TABLE ACCESS FULL BIG_TABLE

Now, lets add an aggregate -- here we do have to process all rows in the table
HOWEVER, since the rownum is pushed down as far as we can push it - we can do
some suboptimizations that make this faster

select *
  from ( select p.*, rownum rnum
           from ( select owner, object_name, object_type, count(*)
                    from big_table
                   group by owner, object_name, object_type
                ) p
          where rownum <= 100
       )
 where rnum >= 90

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      5.78      18.08      14794      14985         81          11
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      5.79      18.08      14794      14985         81          11

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 216

Rows     Row Source Operation
-------  ---------------------------------------------------
     11  VIEW
    100   COUNT STOPKEY
    100    VIEW
    100     SORT GROUP BY STOPKEY
1099008      TABLE ACCESS FULL BIG_TABLE

Lastly, we'll do it your way -- here we don't push the rownum down, the chance
for optimization is gone and you run really slow

select *
  from ( select p.*, rownum rnum
           from ( select owner, object_name, object_type, count(*)
                    from big_table
                   group by owner, object_name, object_type
                ) p
       )
 where rnum between 90 and 100

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.03          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        2     20.15     112.44      24136      14985        184          11
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5     20.15     112.47      24136      14985        184          11

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 216

Rows     Row Source Operation
-------  ---------------------------------------------------
     11  VIEW
  17172   COUNT
  17172    VIEW
  17172     SORT GROUP BY
1099008      TABLE ACCESS FULL BIG_TABLE

I guess, at the end of the day, it is up to you.  I can only show you that it
is faster so many times.  In the end -- it is your choice.

In your case, this is what I am guessing:

o hz_parties is a view (recognize it from apps)
o its a view that gets the last row before it can get the first
o the number of rows you can see is not significant (maybe a thousand or so,
something that fits in RAM nicely)
o the rownum optimization in your case doesn't do much -- if you see the tkprof,
you'll be able to quantify what it does for you.

In general I can say this:

you would be doing the wrong thing to use "where rnum between a and b" when you
can push the rownum DOWN into the inner query and achieve PHENOMEMAL performance
gains in general.  But again, that is your choice.

nuff said
Rating: 5
Performance difference July 25, 2002
Reviewer: Ken Chiu from toronto, ON canada
The 1st query below is more than half faster than the 2nd query, please explain
what happened ?

select b.*
(Select * from A Order by A.Id) b
where rownum<100

select * from
(select b.*,rownum rnum
(Select * from A Order by A.Id) b
where rownum<100)
and rnum >= 50

thanks.
Followup:
half faster... Hmmm.... wonder what that means.

I can say that (after fixing your queries) -- My findings differ from yours.  In
my case, big_table is a 1,000,000 row table and I see:

big_table@ORA920.US.ORACLE.COM> set autotrace traceonly
big_table@ORA920.US.ORACLE.COM> select b.*
  2  from (Select * from big_table A Order by A.Id) b
  3  where rownum<100
  4  /

99 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=15735 Card=99 Bytes=141000000)
   1    0   COUNT (STOPKEY)
   2    1     VIEW (Cost=15735 Card=1000000 Bytes=141000000)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=15735
Card=1000000 Byte
          s=89000000)

   4    3         INDEX (FULL SCAN) OF 'BIG_TABLE_PK' (UNIQUE) (Cost=2090
Card=1000000)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         19  consistent gets
          0  physical reads
          0  redo size
       9701  bytes sent via SQL*Net to client
        565  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         99  rows processed

big_table@ORA920.US.ORACLE.COM>
big_table@ORA920.US.ORACLE.COM> select * from
  2  (select b.*,rownum rnum
  3  from (Select * from big_table A Order by A.Id) b
  4  where rownum<100)
  5  where rnum >= 50
  6  /

50 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=15735 Card=99 Bytes=15246)
   1    0   VIEW (Cost=15735 Card=99 Bytes=15246)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=15735 Card=1000000 Bytes=141000000)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=15735
Card=1000000 By
          tes=89000000)

   5    4           INDEX (FULL SCAN) OF 'BIG_TABLE_PK' (UNIQUE) (Cost=2090
Card=1000000)

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

big_table@ORA920.US.ORACLE.COM>
big_table@ORA920.US.ORACLE.COM> set autotrace off
big_table@ORA920.US.ORACLE.COM> spool off

The second query is more efficient then the first.
Rating: 4
Food for thought September 09, 2002
Reviewer: Mike Moore from Santa Clara, CA
The testing shows stats for a range at the beginning of a large table. I wonder
what that stats look like when selecting rows 999000 thru 999100 ... in other
words, rows at the end of a large table?
I'd try it myself if I could.
Followup:
Every subsequent query as you page down can get slower and slower (goto google,
you'll see that there as well)

HOWEVER, common sense says that no end user will have the patience 10 or 25 rows
at a time to get to rows 999000 thru 999100 -- even google cuts you off WAY
before you get crazy.  A result set that large is quite simply meaningless for
us humans.

But then again you can goto asktom and search from somthing and keep paging
forward till you get board.  It is true you'll get 18,000 hits at most since
thats all thats in there so for -- but your NEVER have the patience to get to
the end.

Sort of like the old commericial if you remember the wise old owl "how many
licks does it take to get to the center of a tootsie pop" (i think the owl only
got to three before he just bit the lollipop).  For those not in the US and who
didn't grow up in the 70's -- ignore that last couple of sentences ;)
Rating: 4
Food for thought (cont) September 10, 2002
Reviewer: Michael J. Moore
Good point! I mean about nobody actually going to page throught that much data.
I confess that I don't completely understand how to read an EXECUTE PLAN so my
question is only intended to prove to myself that I do or don't understand what
is actually going on. Suppose a person wanted to use your SELECT technique for
choosing rows N thru M towards the end of a large table as I earlier suggested.
Maybe they are not using it for paging, but for some bizarre twilight zone
reason that is what they want to do. Is it true that one could expect the
performance of the SELECT to degrade as ranges deeper and deeper into the table
are selected? If 'yes' then I say 'great, I understand what is happening. If
'no', then I say, "darn, I still don't have a clue."
As for the 70's, I voted for McCarthy, but Dick Nixon won.
Followup:
I would order the result set backwards and get the first page instead of the
last then (flip the order of the data around).

Yes, it'll take longer to get the last N rows then the first N rows in general
(not every time, but you can reasonable expect it to be the case)
Rating: 4
problem in query September 21, 2002
Reviewer: Ankit Chhibber from India
I tried this query on an ordered view, the view has about 7000 records with
eventseverity as 64.

select * from
( select fmeventsview.* , rownum rnum from
(select * from fmeventsview where EventSeverity = 64 )fmeventsview where rownum
<=500 ) where rnum >0;

but i get just 234 rows in the result set.

if i fire the embeded query

"select fmeventsview.* , rownum rnum from
(select * from fmeventsview where EventSeverity = 64 )fmeventsview where rownum
<=500 "
i do get 500 records with RNUM values from 1-500

I don't know where i am goofing up :-(
please advice on the same
Followup:
I hate views with order bys.  Add the order by to the query itself.  The order
by doesn't have to be specifically obeyed in the view once you start doing wacky
things to the query.  It must be throwing off the rownum somehow -- but not have
a test case to play with -- I cannot say.
Rating: 5
Getting rows 10,00,001 to 10,00,010 - Query taking forever to execute September 22, 2002
Reviewer: Brijesh
Hi Tom,
The query which you've shown is very good and working very fast within a range
of 100,000 to 150,000 rows but
when trying to get rows more than 500,000's it is taking a minute for doing so.

The query :

select fatwaid,fatwatitle
   from (select a.*,rownum r
           from (select * from fatwa order by fatwaid) a
   where rownum <= &upperbound )
where r >= &lowerbound

when executed with 150001 and 150010 gives me
following output and plan

10 rows selected.

Elapsed: 00:00:02.01

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=826 Card=150010 Byte
          s=11700780)

   1    0   VIEW (Cost=826 Card=150010 Bytes=11700780)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=826 Card=1282785 Bytes=83381025)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'FATWA' (Cost=826 C
          ard=1282785 Bytes=2837520420)

   5    4           INDEX (FULL SCAN) OF 'PK_FATWA' (UNIQUE) (Cost=26
          Card=1282785)

When executed with values of
1000001 and 1000010

Following is the plan and time
10 rows selected.

Elapsed: 00:01:01.08

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=826 Card=1000010 Byt
          es=78000780)

   1    0   VIEW (Cost=826 Card=1000010 Bytes=78000780)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=826 Card=1282785 Bytes=83381025)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'FATWA' (Cost=826 C
          ard=1282785 Bytes=2837520420)

   5    4           INDEX (FULL SCAN) OF 'PK_FATWA' (UNIQUE) (Cost=26
          Card=1282785)

How Can I speed up the process of getting last rows?
Followup:
Nope, no go -- this is good for paging through a result set.  Given that HUMANS
page through a result set and pages are 10-25 rows and we as humans would NEVER
in a billion years have the patience to page down 100,000 times -- it is very
workable.

Perhaps you want to order by DESC and get the first page?

(think about it -- to get the "last page", one must iterate over all of the
preceding pages.  a desc sort would tend to read the index backwards)
Rating: 3
invalid coloumn name exception October 04, 2002
Reviewer: Ankit Chhibber from India
Hi,
when this query is fired simultanously ( from Java application using JDBC) from
multiple threads, oracle sometimes gives an exception "invalid coloumn name" :-(
can you please explain the reason ???
Followup:
Umm, magic.  A bug.  Programmer error.  I don't know.

Sounds like time to file a TAR with support.  One would need tons of information
such as (and don't give it to me, give it to support) type of driver used,
version of driver, version of db, a test case (as small as humanly possible)
that can be run to reproduce the issue.

that last part will be the hard part maybe.  but you should be able to start up
a small java program with a couple of threads all that just wildly parse and
execute queries that eventually hits this error.
Rating: 4
An old question revivied again October 21, 2002
Reviewer: Ankit Chhibber from India
Hi Tom,
I am using your queryto do a lot of DB operations :-), I am reading records 1000
at a time based on your approach. when there are 100,000 records in DB (This is
acceptable situation, that is what people tell me :-) ), the fetch for first
1000 rows takes about 50 seconds :-(.(Our Os is solaris, and i use JDBC for
accesing the DB)
I am doing a sort (order by) on one of the primary keys.
Can you suggest some way of improving the performance here ???

It would be of real help

regards
Ankit
Followup:

http://asktom.oracle.com/~tkyte/tkprof.html
use that tool (sql_trace + TIMED_STATISTICS) to see the query plan, rows flowing
through the steps of the plan and use that as your jump off point for tuning.

You might be a candidate for FIRST_ROWS optimization.

why 1000 rows, 25 or 100 is more reasonable.  But anyway -- it is probably the
fact that you need to sort 100k rows each time -- check your sort area size as
well.
Rating: 5
Getting rows 10,00,001 to 10,00,010 - Query taking forever to execute October 22, 2002
Reviewer: Brijesh
Now i've got it,
its just a matter of thinking why would a user would page through all the 100000
pages to go to 100001.

Even me searched on google many times but never went beyond the tenth page.

Thanks for all you are doing for developers,
and for the reply.
Regards Brijesh
Rating: 5
get the count for my query November 11, 2002
Reviewer: Cesar
How i can get the count in my query?

select *
  from ( select a.*, rownum rnum
           from ( YOUR_QUERY_GOES_HERE -- HOW GET HOW MANY ROWS ARE HERE?? ) a
          where rownum <= MAX_ROWS )
 where rnum >= MIN_ROWS
Rating: 5
Excellent stuff December 12, 2002
Reviewer: Harpreet from India
hi
 I was having the same problem for a few days of how to do pagination. Howard
suggested to look in to your site and i found the answer, with some very good
discussions.

 this is really good work.
Rating: 5
how to group it by December 17, 2002
Reviewer: A reader
I have table t as

select * from t;

        T1 COLOR
---------- --------------------
         1 PINK
         2 GREEN
         3 BLUE
         4 RED
         5 YELLOW

select * from xabc;
COLOR                C_DATE
-------------------- -----------------
RED                  MON
RED                  TUE
RED                  WED
RED                  THU
RED                  FRI
RED                  SAT
RED                  SUN
PINK                 MON
PINK                 TUE
PINK                 WED

now I need get the resuleset as follows

COLOR                C_DATE
-------------------- -----------------
RED                  MON
RED                  TUE
RED                  WED
RED                  THU
PINK                 MON

  because red = 4 in t and pink = 1 in t
how to do it ?

TIA
Followup:
does not compute.  No idea what you mean.  so what if red = 4 and pink = 1?
Rating: 5
Thanks, December 17, 2002
Reviewer: A reader
don't spend time answering that I got it !!

 1  select p.*
 2               from (
 3                        select x.color,x.c_date,
 4                               row_number() over (partition by x.color order
by c_date) r
 5                        from xabc x,t
 6                        where x.color = t.color
 7                      ) p , t
 8              where p.color = t.color
 9*        and   r <= t.t1
nydev168-->/

COLOR                C_DATE                        R
-------------------- -------------------- ----------
PINK                 MON                           1
RED                  FRI                           1
RED                  MON                           2
RED                  SAT                           3
RED                  SUN                           4

Thanks :)
Rating: 3
Scrollable cursors December 18, 2002
Reviewer: A reader from moscow
Tom,

Are scrollable cursors (9.2) available in pl/sql and jdbc, or only pro c/c++?

If not, when will this feature become available from pl/sql?
Followup:
jdbc has then.

I cannot imagine a case whereby plsql would need/desire them.  I can see their
usefulness in a situation where you have a client/server stated connection and
want to page up/down through a result set - but plsql does not lend itself to
that sort of environment?  We rely on the client to do that (eg: something like
forms, or jdbc).  In a stored procedure -- when would you want to "go
backwards"?
Rating: 5
what if red = 4 and pink = 1? December 20, 2002
Reviewer: A reader
it means there should be only 4 rows returned for red
and only 1 row should be returned for pink evenif ther are 10 rows for pink
Rating: 5
Master Oracle Guru February 05, 2003
Reviewer: Denise from Virginia Beach, VA
Tom

I wish I had 1/5 your knowledge...everytime I come
here seeking answers and solutions you always seem to
hit the target head on...and then top it off with superb
code that is easy to understand and apply.

Everytime I come here my answers are solved and I learn
something new everytime.

I am DEFINITELY buying your book!

as a newbie I can't express enough how important it is
for those of us venturing into this brave new world of
Oracle to have someone of your stature, expertise & knowledge paving the way.

I think your(errrrr...'ur') TERRIFIC!!!
Denise
Rating: 5
February 13, 2003
Reviewer: Helena Markova from Bratislava, Slovakia
Rating: 5
Excellent. February 20, 2003
Reviewer: Chandra S.Reddy from India
Hi Tom,
You are really great. This solution is very much useful for me.
I believe, there will not be any much resource utilization with this approach.
Is that right Tom?
Followup:
there will be as much resource utilitization as needed to process the query?
Rating: 4
How can I do this in sql? February 22, 2003
Reviewer: A reader
Tom,
If I want to return set n from each group of records based upon key, for
example, the data is like this..
store     customer   qty
1         10         10
1         100        20
1         1000       30
.......................
2         20        20
2         200       200
...........
...........
I want to return any two records from each group of store i.e, two records for
each store.
Thanks
Followup:
select *
  from ( select t.*, row_number() over (partition by store order by customer) rn
           from t
       )
 where rn <= 2;

would do it -- or
Rating: 4
Thanks a lot ! February 22, 2003
Reviewer: A reader
Tom,
This is regarding followup:
"select *
  from ( select t.*, row_number() over (partition by store order by customer) rn
           from t
       )
 where rn <= 2;
would do it -- or"

1.What is or....?
2.Where can I find more about such type of queries?

Thanks
Followup:
ignore the or ;)

analytics are documented in

o sql reference manual
o data warehousing guide

and I think the write up I have on them in my book "Expert one on one Oracle" is
pretty understandable if you have that - I have a chapter on analytics.

row_number is one of about 40 analytic functions we have
Rating: 1
Upset February 23, 2003
Reviewer: A reader
u r the hard 1 not Parag
Followup:
maybe if you use real words next time, I'll actually understand what you are
trying to communicate.

it is not that much to ask is it?  T

That is what is known as a rhetorical question, I don't expect a response.  The
answer is "no, when communicating, it is not too much to ask people to use a
common agreed upon language as opposed to making one up"...

so, don't go away mad, just....
Rating: 4
Rownum February 25, 2003
Reviewer: phunghung from VIE
Exellent !!!
  It's very useful for me
  Special thanks :D
Rating: 5
Pagination in other scenario. February 28, 2003
Reviewer: Chandra S.Reddy from India
Tom,
This is further R&D for the approach you have provided.
If someone wants to get M to N records of Dept Number instaed of Emp Number.

select * from (select tmp1.*, rownum1 rnum
            from (select e.* from scott.emp e, scott.dept d where e.deptno =
d.deptno)tmp1,
            (select deptno, rownum rownum1 from scott.dept)tmp2
            where tmp1.deptno = tmp2.deptno and
        rownum1 <= :End ) where rnum >= :Start  ;
/
Followup:
Well, there are other ways to fry that fish.

analytics rock and roll:

scott@ORA920> select dept.deptno, dname, ename,
  2         dense_rank() over ( order by dept.deptno ) dr
  3    from emp, dept
  4   where emp.deptno = dept.deptno
  5  /

    DEPTNO DNAME          ENAME              DR
---------- -------------- ---------- ----------
        10 ACCOUNTING     CLARK               1
        10 ACCOUNTING     KING                1
        10 ACCOUNTING     MILLER              1
        20 RESEARCH       SMITH               2
        20 RESEARCH       ADAMS               2
        20 RESEARCH       FORD                2
        20 RESEARCH       SCOTT               2
        20 RESEARCH       JONES               2
        30 SALES          ALLEN               3
        30 SALES          BLAKE               3
        30 SALES          MARTIN              3
        30 SALES          JAMES               3
        30 SALES          TURNER              3
        30 SALES          WARD                3

14 rows selected.

scott@ORA920>
scott@ORA920> variable x number
scott@ORA920> variable y number
scott@ORA920>
scott@ORA920> exec :x := 2; :y := 3;

PL/SQL procedure successfully completed.

scott@ORA920>
scott@ORA920> select *
  2    from (
  3  select dept.deptno, dname, ename,
  4         dense_rank() over ( order by dept.deptno ) dr
  5    from emp, dept
  6   where emp.deptno = dept.deptno
  7         )
  8   where dr between :x and :y
  9  /

    DEPTNO DNAME          ENAME              DR
---------- -------------- ---------- ----------
        20 RESEARCH       SMITH               2
        20 RESEARCH       ADAMS               2
        20 RESEARCH       FORD                2
        20 RESEARCH       SCOTT               2
        20 RESEARCH       JONES               2
        30 SALES          ALLEN               3
        30 SALES          BLAKE               3
        30 SALES          MARTIN              3
        30 SALES          JAMES               3
        30 SALES          TURNER              3
        30 SALES          WARD                3

11 rows selected.
Rating: 5
Using dates is giving error. March 02, 2003
Reviewer: Chandra S.Reddy from India
Tom,
Very nice to see many approaches to implent  the pagination.

When I try to implement one of your method, I got some problems.

Issue #1.

Please see below.

SQL> create or replace procedure sp(out_cvGenric OUT
PKG_SWIP_CommDefi.GenCurTyp) is
  2  begin
  3
  4  OPEN out_cvGenric FOR
  5  select *
  6      from (
  7    select dept.deptno, dname, ename,to_char(hiredate,'dd-mm-yyyy'),
  8           dense_rank() over ( order by dept.deptno ) dr
  9      from emp, dept
 10     where emp.deptno = dept.deptno and hiredate between '17-DEC-80' and
'17-DEC-82'
 11           )
 12  where dr between 2 and 3;

 19  end ;
 20  /

Warning: Procedure created with compilation errors.
SQL> show err;

LINE/COL ERROR
-------- -----------------------------------------------------------------
8/28     PLS-00103: Encountered the symbol "(" when expecting one of the
         following:
         , from

I managed this problem by keeping the query in strings(OPEN out_cvGenric FOR
'select * from ... ' ) and using USING clause. It worked very fine.

Why is this error Tom.?

Issue #2.

Please check below code. This is my actual implementation.Above is PL/SQL shape
for your answer.

procedure sp_clips_reports_soandso (
                in_noperationcenterid in number,
                in_dreportfromdt in  date ,
                in_dreporttodt in date ,
                in_cusername in varchar2,
                in_ntirestatuscode in number,
                in_cwipaccount in varchar2,
                in_npagestart in  number,
                in_npageend in  number ,
                out_nrecordcnt out number ,
                out_nstatuscode out number,
                out_cvgenric out pkg_clips_commdefi.gencurtyp,
                out_cerrordesc out varchar2) is

            v_tempstart    number(5) ;
            v_tempend    number(5) ;
begin
        out_nstatuscode := 0;

            select count(tire_trn_number) into out_nrecordcnt
            from    t_clips_tire
            where     redirect_operation_center_id = in_noperationcenterid
                and    tire_status_id = in_ntirestatuscode
                and    tire_date >= in_dreportfromdt
                and tire_date <= in_dreporttodt
                and wip_account = in_cwipaccount ;

        if in_npagestart =  -1 and in_npageend = -1 then

            v_tempstart    := 1;
            v_tempend    := out_nrecordcnt ;
        else
              v_tempstart :=   in_npagestart ;
              v_tempend :=    in_npageend ;

        end if ;
open out_cvgenric for
'select *
    from (
  select tire.tire_trn_number tiretrnnumber,
                    to_char(tire.tire_date,''mm/dd/yy''),
                    tire.tire_time,
                    tire.direct_submitter_name user_name,
                dense_rank() over ( order by tire.tire_trn_number ) dr
            from    t_clips_tire tire,
                t_clips_afs_transaction transactions,
                t_clips_transaction_code transactionscd
            where
                tire.tire_trn_number = transactions.tire_trn_number and
                transactions.tran_code = transactionscd.tran_code and
                redirect_operation_center_id = :opp and
                tire.tire_status_id = :stcode  and
                tire.wip_account = :wip and
                tire.tire_date > :reportfromdt and
                tire.tire_date < :reporttodt and
            order by transactions.tire_trn_number,tran_seq
         )
where dr between :start and :end' using
in_noperationcenterid,in_ntirestatuscode,in_cwipaccount,v_tempstart,v_tempend;

end sp_clips_reports_soandso;
/
show err;
no errors.
sql> var out_cvgenric refcursor;
sql> var out_nstatuscode  number;
sql> declare
  2  out_cerrordesc varchar2(2000) ;
  3  --var out_nrecordcnt number ;
  4  begin
  5  sp_clips_reports_soandso(4,'16-feb-02',
'16-feb-03',null,2,'0293450720',1,10,:out_nrecordcnt, :out_nstatuscode
,:out_cvgenric,out_cerrordesc);
  6  dbms_output.put_line(out_cerrordesc);
  7  end ;
  8  /
declare
*
error at line 1:
ora-00936: missing expression
ora-06512: at "CLIPStest2.sp_clips_reports_soandso", line 40
ora-06512: at line 5

In the above code,query is in string,program got compiled.
But while calling it is showing errors.
If I remove "tire.tire_date > :ReportFromDt and tire.tire_date < :ReportToDt"
from the WHERE clause, the query is working fine and giving results.
If the dates are in query, it is going wrong.

To say, this pagination in SP, will remove much burdens on the application
server. But unfortunately am not coming with the solution.

Could you please provide me the solution.
Thanks in advance.
Followup:
1) see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3027089372477
same issue -- same workaround in 8i and before, native dynamic sql or a view

2) why are you counting -- that is a very very very bad idea.  First -- the
answer can and will change (your count is a "guess").  Second, it is the best
way to make a system CRAWL to a halt.  "Oh, I think I'll do a bunch of work and
then do it all over again".  Time to double up the machine.

you have a buggy sql statement -- two things I see straight off:

        tire.tire_date < :reporttodt and
            order by transactions.tire_trn_number,tran_seq
         )

AND ORDER BY -- you are "missing an expression" in there.

Second - you are missing a pair of binds.  I see 5 "using" variables but count 7
binds.

it is not the dates in the query -- it is the invalid query itself.

Suggestion -- this is how I diagnosed this -- cut and paste the query into
sqlplus, change '' into ' globally in the query.  and run it after doing
"variable" statements:

ops$tkyte@ORA920> variable opp varchar2(20)
ops$tkyte@ORA920> variable stcode varchar2(20)
ops$tkyte@ORA920> variable wip varchar2(20)
ops$tkyte@ORA920> variable reportfromdt varchar2(20)
ops$tkyte@ORA920> variable reporttodt varchar2(20)
ops$tkyte@ORA920> variable start varchar2(20)
ops$tkyte@ORA920> variable end varchar2(20)
ops$tkyte@ORA920>
ops$tkyte@ORA920> select *
  2      from (
  3    select tire.tire_trn_number tiretrnnumber,
  4                      to_char(tire.tire_date,'mm/dd/yy'),
  5                      tire.tire_time,
  6                      tire.direct_submitter_name user_name,
  7                  dense_rank() over ( order by tire.tire_trn_number ) dr
  8              from    t_clips_tire tire,
  9                  t_clips_afs_transaction transactions,
 10                  t_clips_transaction_code transactionscd
 11              where
 12                  tire.tire_trn_number = transactions.tire_trn_number and
 13                  transactions.tran_code = transactionscd.tran_code and
 14                  redirect_operation_center_id = :opp and
 15                  tire.tire_status_id = :stcode  and
 16                  tire.wip_account = :wip and
 17                  tire.tire_date > :reportfromdt and
 18                  tire.tire_date < :reporttodt and
 19              order by transactions.tire_trn_number,tran_seq
 20           )
 21  where dr between :start and :end
 22  /
            order by transactions.tire_trn_number,tran_seq
            *
ERROR at line 19:
ORA-00936: missing expression

Now it becomes crystal clear where the mistake it.
Rating: 5
Using dates is giving error. March 02, 2003
Reviewer: Chandra S.Reddy from India
Hi Tom,
In my previous question with the same title, USING caluse is wrong. There bind
variables for 'tire_date' fields are missing. It was wrongly pasted. Sorry for
that.
Please find the correct one below.
--
USING  in_noperationcenterid,in_ntirestatuscode,
in_cwipaccount,in_dreportfromdt, in_dreporttodt,v_tempstart, v_tempend ;
----

Thanks you very much.
Followup:
still -- missing expression -- figure it out, not hard given information I
already supplied.
Rating: 5
Thank you. March 02, 2003
Reviewer: A reader from India
Tom,
Thank you for the suggestion.
COUNT is bad idea, but I should return this to application. There application
will decide the pagination factor depending on the no. of records. So am using
count there.
Rating: 5
Why does between not work? March 26, 2003
Reviewer: Errick from Cincinnati, Oh
Tom,
  I've been reading through this set of posts, and was curious. Why exactly does
the between 90 and 100 not work, whereas just select * from bigtable where
rownum < 100 work? Maybe Im missing something from the article. Just curious.
Followup:
because rownum starts at 1 and is incremented only when a row is output.

so,

select * from t where rownum between 90 and 100 would be like this:

rownum := 1;
for x in ( select * from t )
loop
   if ( rownum between 90 and 100 )
   then
        output
        rownum := rownum+1;
   end if;
end loop;

nothing ever comes out of that loop.
Rating: 5
Let me understand it better... April 07, 2003
Reviewer: David
Tom,

I am a DBA and I am sometimes a bit confused when it it comes to supporting web
applications.

The web development people have asked me how to implement pagination, since
their connection is stateless.

I would like to submit the query one time only, but I ended up creating
something like below, which "re-parses", "re-executes" and "re-fetches" for each
page:

select * from
  (select b.*,rownum rnum
   from (Select * from big_table a order by a.id) b
   where rownum < :max )
where rnum >= :min ;

1) To my knowledge, each time I do this I have to "re-parse", "re-execute" and
"re-fetch" the data. The bind variable values are kept and incremented for each
page in the application. Is this a good approach ?

2) Wouldn't it be better if I could return the entire set (with first_rows) ?

3) How would be a mechanism for that (how would I code that) ?

4) Using this last approach, couldn't I do some kind of "pipelining" so the rows
are returned to the application, submitting the query only once and without
having to return the whole set -- since the entire table is too large.

Thanks
Followup:
1) yes, it is what I do.  Short of maintaining a connection and becoming a
client server application -- there is no real avoiding this.

Me -- I'd rather have to parse (soft) for each page then to keep a physical,
private connection (and all of the associated resources) open for that user IN
CASE they hit page forward.

2) and you have a 500 row result set -- and the user only looks at the first ten
-- and never ever goes to page 2?  So you do 50 times the LIO you need to?
Probably at least 40 times as much LIO as you ever would (pages are like 10 rows
and the users NEVER goto page 11).

No, you want to do as little work as possible, save in the knowledge that people
get bored and go away after hitting page down once or twice.

3) you'd be on your own...

4) that would be back to "i'm client server, I always have a stated connection,
I always consume massive resources on your machine -- even when I'm not using
it"
Rating: 4
A Belated Defence of Parag April 07, 2003
Reviewer: Richard from Oxfordshire, UK
With reference to Parag's use of abbreviations: Parag's meaning was clear; it
must have been to you, too, or you wouldn't have known to put u = you, ur =
your.

Yes, the world IS awash with abbreviations (3-letter and otherwise)and acronyms,
but that's because they usually (as in Parag's case) make perfect sense and
would be likely to confuse or befuddle only the elderly and the infirm!

yrs etc.,

Richard
Followup:
Elmer Fudd here,

Weww, I disagwee. You see, I gets wots of qwestions -- some in pewfect engwish,
some in bwoken engwish, some in foweign wanguages. Oh, dat scwewy wabbit! I twy
to pawse these qwestions -- make sense of them and evewy woadbwock someone puts
in thewe makes it hawd fow me to do that. Just wike weading this pawagwaph is
hawd fow you now. I do not think it is too much to ask to use pwopew wowds in a
fowum wike this. Oh, dat scwewy wabbit! Dis is NOT a ceww phone hewe, this is
not instant messaging. Dis is a discussion pwace, a pwace to wead things. Oh,
dat scwewy wabbit! Using made up things just makes it hawdew to undewstand. I
don't ask fow too many things, this is one that I keep asking fow though.

that really hard to read text brought to you by the dialectizer:
http://www.rinkworks.com/dialect/
Well, I disagree.  You see, I gets lots of questions -- some in perfect english,
some in broken english, some in foreign languages.  I try to parse these
questions -- make sense of them and every roadblock someone puts in there makes
it hard for me to do that.

Just like reading this paragraph is hard for you now.

I do not think it is too much to ask to use proper words in a forum like this.
This is NOT a cell phone here, this is not instant messaging.  This is a
discussion place, a place to read things.  Using made up things just makes it
harder to understand.

I don't ask for too many things, this is one that I keep asking for though.
Rating: 4
Sending results to the Internet application April 07, 2003
Reviewer: B. Robinson from Hartford, CT
DBA David,

It is not just that the connections are stateless, but the connections are
pooled and rotated such that there may be a different database connection used
for every web page request from a given user.

So the only way to avoid requerying for every subset of the large result set
would be to return the whole massive result set to the web app, and the web app
would cache the all results in memory, reading each subset from memory as
needed.  But since this would require the entire result set to be read from the
database, it would make more sense to use all_rows.

Naturally, that approach uses up gobs of memory on the app server or web server,
so it may not be feasible for a web app with thousands of users.
Followup:
the connection from the client (browser) to the app server is stateless.
Rating: 3
time April 07, 2003
Reviewer: A reader
just a note.

on toms site,

 to load first 3-4 pages is very fast about < 2 secs.
when we go to 490-500 of 501 takes 10 sec. to load a very simple page
Followup:
and it gets worse the further you go.  my stuff is optimized to get your the
first rows fast -- I do not give you the ability to goto to "row 3421" -- what
meaning would that have in a search like this?

google search for Oracle

Results 1 - 10 of about 6,840,000. Search took 0.11 seconds.
Results 91 - 100 of about 7,800,000. Search took 0.24 seconds.
Results 181 - 190 of about 6,840,000. Search took 0.49 seconds.
(wow, thats wacky - the counts change too)
Results 811 - 820 of about 6,840,000. Search took 0.91 seconds.
Results 901 - 908 of about 6,840,000. Search took 0.74 seconds.

what?  they cut me off -- I'm sure my answer was 909, I'm just sure of it!
Rating: 4
Results xxx of about xxxxxx April 08, 2003
Reviewer: A reader
I recently went through a load of code removing every count(*) that there was
before the actual query that was done by a developer before I came on the
project.

It was amazing the argument I had with the (PHP) web developer about it.  I just
made the change and let the users decide if they liked the improved performance
more than the missing bit of fairly pointless information.  Guess what they
preferred!

The thing that is missing is the "results 1-10 of about 500" (or whatever),
which would be useful.  The user might well want to know if there are just a few
more records to look at, in which case it might well be worth paging, or whether
there are lots, so that they would no to refine the search.

I know Oracle Text can do this sort of thing, but is there anything that can
help in "Standard" Oracle?  Using Oracle Text would need quite a re-write of the
system.

What we could do is have the application ask for 21 rows of data.  If the cursor
came back with 10-20 more rows, the screen would say ">> 7 more rows" (or
whatever), and if it hits the 21, then display ">> at least 11 more rows".

Have you any comments?

Thanks
Followup:
...
The thing that is missing is the "results 1-10 of about 500" (or whatever),
.....

if using Oracle Text queries (like I do here) there is an API for that.

if using the CBO in 9i -- you can get the estimated cardinality for the query in
v$sql_plan...
Rating: 5
For: Srinivas M April 08, 2003
Reviewer: A Reader from UK
Hi,

All those fieldx IS LIKE '''' OR fieldX IS NULL .... what is that for ?!!  don't
you just want fieldX is null ??  Anyway...maybe I missed something...

I'm sure Tom will have lots to say on this, and apologies for 'butting in' but I
thought i'd give my opinion and if its off base at least I'll learn :)

Do you need that table to be deleted and inserted into each time (looks like a
pseudo-temporary table) ?  All that looping and fetching - and it looks to me
like if you had a llimit of 1000, you are going to fetch and do nothing with
1000 rows ??!  Can't you change your query to use the constructs Tom has already
defined in this article i.e.

SELECT * FROM (YOUR QUERY IN HERE, BUT SELECTING rownum BACK ALSO) WHERE rownum
BETWEEN llimit and ulimit

??

Then I suspect you don't need your table, your delete, your loops and fetches,
you can just open this and return the cursor.

Regards,

Paul
Rating: 2
April 08, 2003
Reviewer: A reader
Hi Srinivas,
Sorry to jump in between but i would like to say one thing. Tom has already
given us his views and coding tips and tricks. Lets not waste his time by asking
him to correct our code. I think this site provides us enough knowledge and
tools. Only thing required on our part is applying it correctly and doing some
research.
Rating: 3
Screwy Rabbit! April 08, 2003
Reviewer: Richard from Oxfordshire, UK
Hi,

Elmer Fudd... priceless! Seldom has an explanation been so funny! Point taken,
though.

How about always translating your pages? Daffy Duck's my favourite.

Wegards,

Wichard
Rating: 4
is this the proc. you are using for your site ? April 08, 2003
Reviewer: A reader
is this the proc. you are using for your site ?

 if you bind the variable in a session and the
http connection is state less so how will you
do it?

pls explain
Followup:
yes, this is the procedure I use here...

the "bind variables" are of course passed from page to page -- in my case I use
a sessionid (look up at that really big number in the URL) and your session
"state" is but a row in a table to me.

Hidden fields, cookies -- they work just as well.
Rating: 5
Thanks April 08, 2003
Reviewer: A reader
Rating: 5
Want a trick on this April 28, 2003
Reviewer: DeeeBeee Crazeee from India
Hi Tom,

I just wanted to know if there is a trick of combining multiple rows into a
single row with values comma seperated.

For example, I have the department table :

Dept:

Dept_name
---------
ACCOUNTS
HR
MARKETING

I need a query that would return me...

Dept_name
---------
ACCOUNTS, HR, MARKETING

....is there a way with SQL or do we have to use PL/SQL. The number of rows are
not fixed.

thanks a lot

PS: Just wanted to check if I can post my questions here (in this section,
without asking it afresh) ....because, I just happened to come accross a page
wherein a reader was apologizing for having asked a question in the comments. Do
let me know on this, so that I can apologise too when I ask you a question in
this section the next time ;)
Followup:
search this site for

stragg
Rating: 5
What about this ? May 16, 2003
Reviewer: A reader
I happened to found this in an article on pagination:

select rownum, col1
from foobar
group by rownum, col1
having rownum >= :start and rownum < :end

What do you think ? How does it compare to your solution to the original
question ?
Followup:
try it, it doesn't work.

set start to 10 and end to 15

you'll never get anything.

the way to do it -- it is above, my method works and is the most efficient
method (as of May 16 2003, maybe some day in the furture there will be another
more efficient method)
Rating: 4
paging result set May 17, 2003
Reviewer: lakshmi from Bnaglaore India
Excellent
Rating: 5
Dynamic order by using rownum May 27, 2003
Reviewer: vinodhps from India,chennai
Hi tom ,
our current oracle version is 8.0.4 , i got one query which has to be ordered
dynamically ie. if Max_ind is X then low_value column has to be order Ascending
or if Max_ind is N then low_value column has to be ordered Descending. But i
could i do that in query.. iam using this in my form
in the below query order by Desc  or Asc is depend on the value passing for the
max_ind(N or X).

SELECT insp_dtl.test_value,
              insp_dtl.lab_test_sno,
         purity.low_value, purity.high_value,
         purity.pro_rata_flag, purity.pro_rata_type,
         purity.cumulative_flag, purity.incr,
         purity.prcnt, purity.flat_rate,
         purity.cal_mode, NVL (purity.precision, 1) precision,
              purity.min_max_ind
       FROM   t_las_matl_insp_hdr insp_hdr,
         t_las_matl_insp_dtl insp_dtl,
         t_pur_po_matl_purity_fact_dtl purity
       WHERE  insp_hdr.lab_test_sno = insp_dtl.lab_test_sno
       AND    insp_hdr.cnr_no = 200300905
       AND    purity.po_no = 200200607
--       AND    purity.matl_code = f_matl_code
       AND    purity.para_code = insp_dtl.para_code
  --     AND    purity.para_code = f_para_code
       ORDER BY low_value;

LAB_TEST_SNO LOW_VALUE HIGH_VALUE Max_ind
------------ --------- ---------- ---------
   200300208       1.1        1.5   X
   200300208       1.1          2   N
   200300208       1.6          2   N
   200300208        86       87.9   X
   200300208        88       89.9   N
Followup:
great, thanks for letting us know?

Not really sure what you are trying to say here.
Rating: 4
dynamically order by clause May 27, 2003
Reviewer: vinodhps from India, Chennai.
Hi Tom,
Thanks for your immediate response,
well i will put my question in this way..

SQL> create table order_by
  2  (low_value number(5),
  3   max_ind   varchar2(1));

Table created.

  1  insert into order_by
  2  select rownum ,'X' from all_objects
  3  where rownum < 10
  4* order by rownum desc
SQL> /

9 rows created.

  1  insert into order_by
  2  select rownum ,'N' from all_objects
  3  where rownum < 10
  4* order by rownum
SQL> /

9 rows created.

Now i would like to select all the values from the table by passing a value for
max_ind(a indicator) whether its maximum or minimum value here in this query if
i pass variable X then the query order by clause must be descending or else it
should be ascending, actually it is a cursor .

SQL> select low_value  from order_by order by low_value desc;

LOW_VALUE
---------
        9
        9
        8
        8
        7
        7
        6
        6
        5
        5
        4
        4
        3
        3
        2
        2
        1
        1

18 rows selected.

This DESC or ASC will be decided dynamically.

Is it possible to do it dynamically Tom,

I  think above statements are clear.
Followup:
you would use native dynamic sql to get the optimum query plan.

 l_query := 'select .... order by low_value ' || p_asc_or_desc;

where p_asc_or_desc is a variable you set to ASC or DESC.

that would be best.

you can use decode, but you'll never use an index to sort with if that matters
to you

   order by decode( p_input, 'ASC', low_value, 0 ) ASC,
            decode( p_input, 'DESC', low_value, 0 ) DESC
Rating: 5
Thank you tom May 27, 2003
Reviewer: vinodhps from India, chennai
Thank you tom for your immediate responce...

Hope to see more from you.

Thank you,
Rating: 3
Very useful, thanks Tom. One more question. June 02, 2003
Reviewer: Jack Liu from IL USA
1. Did you get total result number by count(*)? this pagin needs to know the
count(*) or not, because select count take longer time.

2. How to optimize order by, it will use only 3s for below query but 44s with
order?
select *   from
( select qu.*, rownum rnum
  from ( select issn,volume,issue,foa,title,author,subtitle,a.aid,rtype
     from article a , ec.language l where 1=1
     AND rtype in ('ART','REV','SER')
     AND a.aid=l.aid AND l.langcode='eng'
     AND a.issue is not null ORDER BY a.ayear desc ) qu
   where rownum < 61)
 where rnum >= 31
Followup:
1) no, i use text's "get me an approximation of what you think the result set
size might be" function.  (its a text query for me)

2) /*+ FIRST_ROWS */

do you have an index on a.ayear?
is ayear NOT NULL?

if so, it could use the index, with first_rows, to read the data descending.
Rating: 5
Very, Very...Helpful June 02, 2003
Reviewer: Ralph from USA
Along those lines...How can we get the maximum rows that will be fetched? i.e.
to be able to show 1-10 of 1000 records, how do we know that there are total
1000 records without writing another select with count(*)
Followup:
you don't -- all you need to show is

"you are seeing 1-10 of more then 10, hit next to see what might be 11-20 (or
maybe less"

If you use text, you can approximate the result set size.
If you use the CBO and 9i, you can get the estimated cardinality from v$SQL_PLAN
Rating: 4
Very helpful, thanks Tom, follow up with my question. June 03, 2003
Reviewer: Jack from IL USA
Tom,
Thanks for your quick response. This is really a very good place for Oracle
Users. Just follow up my original question:

1) Is this "get me an approximation of what you think the result set size might
be" function only in Oracle Text? If I use Oracle Intermedia text, any solution
to show total result?

2) a.ayear is indexed but has some null. I know it can not use index to replace
order in this situation, but why I use /*+ INDEX_ASC (article article_ayear) */,
it doesn't work either? The optimizer mode is "choose" in svrmgrl> show
parameter optimizer_mode;

Many many thanks.

Jack
I am planing to buy "expert one-on-one".
Followup:
1) the approximation I'm showing is from text and only works with text.

you can get the estimated cardinality from an explain plan for other queries --
in 9i, that is right in v$sql_plan so you do not need to explain the query using
explain plan

2) you answered your own question.  The query does not permit the index to be
used since using the index would miss NULL entries -- resulting in the wrong
answer.

can you add "and a.ayear IS NOT NULL"  or "and a.ayear > to_date(
'01010001','ddmmyyyy')" to the query.  then, an index on ayear alone can be
used.

better be quick on the book purchase (bookpool still has it as of jun/3/2003 --
see link on homepage)

Publisher went under, book no longer printed ;(
New book in august though ;)
Rating: 4
Thank you for your quick response! June 03, 2003
Reviewer: Jack from IL USA
Tom,
Thanks, actually I want the order is Ascending by ayear, since oracle default
use descending for index, that's the reason I use /*+ INDEX_ASC (article
article_ayear) */ hint.
Just don't know why it doesn't work, here is the explain plan with INDEX_ASC
hint, I don't know why the hint is still choose.
SELECT STATEMENT Hint=CHOOSE 6 K     6911            VIEW        6 K    1 M
6911
COUNT STOPKEY                                    NESTED LOOPS        6 K    736
K    6911                  TABLE ACCESS FULL LANGUAGE    6 K    113 K    56
TABLE ACCESS BY INDEX ROWID    ARTICLE    63 K    5 M    1  INDEX UNIQUE SCAN
SYS_C004334    63 K

Thanks,

Jack
Followup:
Oracle uses indexes ASCENDING by default.

I told you why the index cannot be used -- ayear is nullable, using that index
would (could) result in missing rows that needed to be processed.

hence, add the predicate I described above to make it so that the index CAN in
fact be used.
Rating: 3
paging and a join June 05, 2003
Reviewer: marc from nyc
Which way would be better with a large table and the user wants to see an
average of 500 rows back. The query has a main driving table and a 2nd table
that will only be used to show a column's data.  The 2nd table will not be used
in the where or the order of the main select.

option 1(all table joined in the main select):

select name,emp_id,salary from (
select a.*, rownum rnum from (
SELECT emp.name,emp.emp_id,salary.salary FROM EMP,SALARY
where zip = someting and
EMP.emp_id = salary.emp_id order by name
) a where rownum <= MAX_ROWS )
where rnum >= MIN_ROWS

option 2(only the driving table is in the main select and joins are done at the
higher level.  Then oracle would only have to join the 2 tables with the data
the the user will show. ):
select a.name,a.empid,salary.salary from (
select a.*, rownum rnum from (
SELECT emp.name,emp.emp_id FROM where zip = someting order by name
) a ,SALARY
where a.emp_id = salary.emp_id where rownum <= MAX_ROWS )
where rnum >= MIN_ROWS
Followup:
users don't want to see 500 rows.  500 rows -- waayyy too much data, you cannot
consume that much information...

option 1.  using first_rows hint.

it'll only access the other table as much as it needs to and is much easier to
understand.

but benchmark it on your data, that'll tell YOU for sure what is best in YOUR
case.
Rating: 3
June 06, 2003
Reviewer: marc
My users must be supper users, because they would like to see all that data
(page by page of course) so they can eyeball a forecast and see a trend.  These
people are brokers that look at 1 column of the dataset, which is price per
share (money), So they do look at many rows at a time.  The extra data is
superfluous.  500 rows is nothing for these users.

I was asking your opinion if you think it is better to do the join in the main
dataset and not in the pagination piece.  My perfmaince tuning told me it
putting the data in the main or subset is all related on the type of data in I
need to show,  Example a join to get the name can be in main select, but the get
the (select count(trades) from othertable) works better in the pagination
section.
Followup:
as long as the query is a "first rows" sort of query that can terminate with a
COUNT STOPKEY -- the join can go anywhere.
Rating: 4
Using index with order by July 15, 2003
Reviewer: Jon from Sydney
Will an index be used with an order by if the table has already been accessed
via another index?  I thought the CBO would only work with one index per table
(except with bitmap indexes).

I'm working on returning search results.  Users want first 2000 rows (I know, I
know... what will they do with it all - was originally top 500 rows, but that
wasn't enough for them).  The main table is already being accessed via another
index to limit the result set initially.  Explain Plan tells me that the index
on the order by column is not being used.  How to use the index for ordering?

Actually as I'm writing this, I think the answer came to me - concatenated
indexes - of form (limit_cols, order_by_col), and then include the leading index
column(s) in the order by clause.

Secondly, if I work with a union clause on similar, but not identical, queries,
can an index be used for ordering in this case?

E.g.
select * from (
  select * from (
    select ... from x,y,z where ...
    union all
    select ... from x,y,w where ...
  ) order by x.col1
) where rownum <= 2000

or would we get better results with this approach:

select * from (
  select * from (
    select * from (
      select ... from x,y,z where ...
      order by x.col1
    ) where rownum <= 2000
    union all
    select * from (
      select ... from x,y,w where ...
      order by x.col1
    ) where rownum <= 2000
  ) order by x.col1
) where rownum <= 2000

So, if the result is partially sorted, does an order by perform better than if
not sorted (this brings make memories of sorting algorithms many years ago...).
I would think yes - but not sure of Oracle's internal sort algorithm?
Followup:
if you use the "index to sort", how can you use another index "to find"

You can either use an index to sort OR you can use an index to find, but tell me
-- how could you imagine using both?

Your concatenated index will work in some cases -- yes.

the 2cnd approach -- where you limit all of the subresults - will most likely be
the better approach.

You cannot go into the "does an order by perform better ....", that is so far
out of the realm of your control at this point as to be something to not even
think about.
Rating: 3
July 15, 2003
Reviewer: Jon from Sydney
"how could you imagine using both?" - not sure I understand you here.  Wanting
to use two indexes is a common requirement - so I can easily imagine it:

select *
from emp
where hire_date between to_date('01/01/2002','DD/MM/YYYY')
    and to_date('01/02/2002','DD/MM/YYYY')
order by emp_no

If this was a large table, the ability to use an index to filter and an index to
order by would seem advantageous.

As for internal sort algorithms  - do you know what Oracle uses - or is it
secret squirrel stuff?
Followup:
so tell me -- how would it work, give us the "psuedo code", make it real.
Rating: 4
Hmmm... July 16, 2003
Reviewer: Jon from Sydney
I mean Oracle does that fancy index combine operation with bitmap indexes.  I
guess I'll just have to build it for you.

Tell you what, if I come up with a way of doing something similar for b*tree's,
I'll sell it to Oracle... then I'll retire :-)
Followup:
Oh, we can use more then one index

we have index joins -- for example:

create table t ( x int, y int );

create index t_idx1 on t(x);
create index t_idx2 on t(y);

then select x, y from t where x = 5 and y = 55;

could range scan both t_idx1, t_idx2 and then hash join them together by rowid.

We have bitmaps where by we can AND and OR bitmaps together...

BUT - I want you to explain an algorithm that would permit you to

a) range scan by index index in order to locate data
b) use another index to "sort it"

None of the multi-index approaches "sort" data, they are used to find data.
Rating: 4
All this thinking makes my brain hurt. July 16, 2003
Reviewer: Jon from Sydney
Well, since we CAN combine two indexes, how about:

1) Use idx1 to range scan
2) Hash join rowids to idx2 to produce result set
3) Do a sort-merge between 2) result set and idx2 to order

The efficiency of doing 2) & 3) over sort of table data would probably depend on
cardinality of 1).

More fun for the CBO team and the Oracle mathematics dept...
Followup:
it would depend on cardinality of 1 and 2 really.

if card of 1 is small but card of 2 is big and you have to (must) full scan idx2
a block at a time to look for matches (we have to inspect every index entry) --
full scanning the index could take a really really long time

step 3 would would not be neccesary in this scenario as the full scan of index 2
would be 'sorted' and would just probe the hash table you built in 1
Rating: 4
To clarify July 16, 2003
Reviewer: Jon from Sydney
By sort-merge in 3) I mean a set intersection operation.
Rating: 4
getting rows N through M of a result set July 17, 2003
Reviewer: Mohan from Bangalore, India
Regarding the discussion regarding pagination of the result set into random
chunks and sequencing them

consider the table customer_data

create table customer_data(custno number, invoiceno number);
insert into customer_data(custno, invoiceno) values(1,110);
insert into customer_data(custno, invoiceno) values(1,111);
insert into customer_data(custno, invoiceno) values(1,112);
insert into customer_data(custno, invoiceno) values(2,1150);
insert into customer_data(custno, invoiceno) values(2,1611);
insert into customer_data(custno, invoiceno) values(3,1127);
insert into customer_data(custno, invoiceno) values(2,3150);
insert into customer_data(custno, invoiceno) values(2,3611);
insert into customer_data(custno, invoiceno) values(3,3127);

The following query will break the result sets based on custno and sequences
each chunk.

select b.rnum-a.minrnum+1 slno, a.custno, b.invoiceno from(select custno,
min(rnum) minrnum from(select rownum rnum, custno, invoiceno from (select
custno, invoiceno from customer_data order by custno, invoiceno)) group by
custno) a, (select rownum rnum, custno, invoiceno from (select custno, invoiceno
from customer_data order by custno, invoiceno)) b where a.custno=b.custno;

Mohan
Followup:
ok, put 100,000 rows in there and let us know how it goes... (speed and resource
useage wise)
Rating: 4
It works.. July 17, 2003
Reviewer: DD from MN, USA
<quote>
What about this ?  May 16, 2003
Reviewer:  A reader

I happened to found this in an article on pagination:

select rownum, col1
from foobar
group by rownum, col1
having rownum >= :start and rownum < :end

What do you think ? How does it compare to your solution to the original
question ?

Followup:
try it, it doesn't work.

set start to 10 and end to 15

you'll never get anything.

the way to do it -- it is above, my method works and is the most efficient
method (as of May 16 2003, maybe some day in the furture there will be another
more efficient method)
</quote>

Tom,
  Your reply above states that this does not work. Infact it does work and it
MUST work. The group by will be done before the having clause is applied and so
we will get the correct result set. Please let me know your views and what is it
that makes you think this wont work. Here are my results.

RKPD01> select rownum, object_id from big_table
  2  group by rownum, object_id
  3  having rownum > 10 and rownum < 15;

    ROWNUM  OBJECT_ID
---------- ----------
        11        911
        12        915
        13       1091
        14       1103

I havent tried to see if it is efficient but I wanted to verify why it wouldnt
work when it should. Hope to Hear

Thanks
DD
Followup:
oh, i messed up, saw the having and read it as 'where'

totally 100% inefficient, not a good way to do it.  it does the entire result
set and then gets rows 10-15

as opposed to my method which gets 15 rows, then throws out the first couple.
Rating: 4
getting rows N through M of a result set July 19, 2003
Reviewer: Mohan K from Bangalore, India
Refer to the review on July 17, 2003

If the custno column is not indexed, then the performance will be a problem.

Run the following scripts to test the above query.

create table customer_data(custno number, invoiceno number);

declare
n1 number;
n2 number;
begin
for n1 in 1..2500 LOOP
for n2 in 1..100 LOOP
insert into customer_data(custno, invoiceno) values(n1, n2);
END LOOP;
END LOOP;
end;
/

commit;

create index customer_data_idx on customer_data(custno);

The first sql statement will create the table. The PL/SQL script will populate
the table with 250000 rows. The next statement will create an index.

Now run the query as given below

select b.rnum-a.minrnum+1 slno, a.custno, b.invoiceno from(select custno,
min(rnum) minrnum from
(select rownum rnum, custno, invoiceno from (select custno, invoiceno from
customer_data order by custno, invoiceno)) group by custno) a,
(select rownum rnum, custno, invoiceno from (select custno, invoiceno from
customer_data order by custno, invoiceno)) b
where a.custno=b.custno;

Mohan
Rating: 5
Is it Possible? July 23, 2003
Reviewer: A reader from India
Hi Tom,

I have a table like this

Name
Date
Amount

Data will be like

User1       01-JAN-03       100
User1       22-JUL-03        20
......
User2       23-JUL-03        90

Is there any way, I can get the last 6 (Order by Date desc)records for each user
with a Single query?

I need get the output like

User1      22-JUL-03      20
User1      01-JAN-03     100
....
User2      23-JUL-03........

Thank you very much Tom.  (I am using 8.1.7)
Followup:
select *
  from (select name, date, amount,
               row_number() over (Partition by user order by date DESC ) rn
          from t )
 where rn <= 6;
Rating: 5
For Query example on CUSTOMER_DATA table posted above... July 23, 2003
Reviewer: Kamal Kishore from New Jersey, USA
It is my understanding that the same output can be produced by using the
following query:

SELECT row_number() over(PARTITION BY custno ORDER BY custno, invoiceno) slno,
       custno,
       invoiceno
FROM   customer_data
WHERE  custno IN (1, 2)
ORDER  BY custno,
          invoiceno
/

I may be understanding wrong. Maybe, Tom can verify this.

I ran the two queries on the CUSTOMER_DATA table (with 250000 rows) and below
are the statistics. I ran both queries several times to remove any benefit of
doubt, but results were similar.

I see a huge performance difference on the two queries.

Waiting for inputs/insights from Tom.
Thanks,

==========================================================

SQL> SELECT row_number() over(PARTITION BY custno ORDER BY custno, invoiceno)
slno,
  2         custno,
  3         invoiceno
  4  FROM   customer_data
  5  WHERE  custno IN (1, 2)
  6  ORDER  BY custno,
  7            invoiceno
  8  /

200 rows selected.

Elapsed: 00:00:00.02

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   WINDOW (SORT)
   2    1     CONCATENATION
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMER_DATA'
   4    3         INDEX (RANGE SCAN) OF 'CUSTOMER_DATA_IDX' (NON-UNIQU
          E)

   5    2       TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMER_DATA'
   6    5         INDEX (RANGE SCAN) OF 'CUSTOMER_DATA_IDX' (NON-UNIQU
          E)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          8  consistent gets
          0  physical reads
          0  redo size
       2859  bytes sent via SQL*Net to client
        510  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
        200  rows processed

SQL> SELECT b.rnum - a.minrnum + 1 slno,
  2         a.custno,
  3         b.invoiceno
  4  FROM   (SELECT custno,
  5                 MIN(rnum) minrnum
  6          FROM   (SELECT rownum rnum,
  7                         custno,
  8                         invoiceno
  9                  FROM   (SELECT custno,
 10                                 invoiceno
 11                          FROM   customer_data
 12                          ORDER  BY custno,
 13                                    invoiceno))
 14          GROUP  BY custno) a,
 15         (SELECT rownum rnum,
 16                 custno,
 17                 invoiceno
 18          FROM   (SELECT custno,
 19                         invoiceno
 20                  FROM   customer_data
 21                  ORDER  BY custno,
 22                            invoiceno)) b
 23  WHERE  a.custno = b.custno AND a.custno in (1, 2)
 24  ORDER  BY custno,
 25            invoiceno
 26  /

200 rows selected.

Elapsed: 00:00:20.08

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   MERGE JOIN
   2    1     VIEW
   3    2       COUNT
   4    3         VIEW
   5    4           SORT (ORDER BY)
   6    5             TABLE ACCESS (FULL) OF 'CUSTOMER_DATA'
   7    1     SORT (JOIN)
   8    7       VIEW
   9    8         SORT (GROUP BY)
  10    9           VIEW
  11   10             COUNT
  12   11               VIEW
  13   12                 SORT (ORDER BY)
  14   13                   TABLE ACCESS (FULL) OF 'CUSTOMER_DATA'

Statistics
----------------------------------------------------------
          0  recursive calls
         88  db block gets
       1740  consistent gets
       8679  physical reads
          0  redo size
       2859  bytes sent via SQL*Net to client
        510  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          2  sorts (disk)
        200  rows processed

SQL>
Rating: 4
Example on customer_data table July 24, 2003
Reviewer: Mohan K from Bangalore, India
Specify the where clause in the inner query. The same where clause has to be
applied twice.

select b.rnum-a.minrnum+1 slno, a.custno, b.invoiceno from(select custno,
min(rnum) minrnum from
(select rownum rnum, custno, invoiceno from (select custno, invoiceno from
customer_data where custno in(2,3) order by custno, invoiceno)) group by custno)
a,
(select rownum rnum, custno, invoiceno from (select custno, invoiceno from
customer_data where custno in(2,3) order by custno, invoiceno)) b
where a.custno=b.custno
/

Mohan
Rating: 5
tkprof results on CUSTOMER_DATA query... July 24, 2003
Reviewer: Kamal Kishore from New Jersey, USA
SELECT row_number() over(PARTITION BY custno ORDER BY custno, invoiceno) slno,
       custno,
       invoiceno
FROM   customer_data
WHERE  custno IN (1, 2)
ORDER  BY custno,
          invoiceno

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        3      0.02       0.01          0          8          0         200
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      0.02       0.01          0          8          0         200

SELECT b.rnum - a.minrnum + 1 slno, a.custno, b.invoiceno
FROM   (SELECT custno, MIN(rnum) minrnum
        FROM   (SELECT rownum rnum, custno, invoiceno
                FROM   (SELECT custno, invoiceno
                        FROM   customer_data
                        WHERE  custno IN (1, 2)
                        ORDER  BY custno, invoiceno))
        GROUP  BY custno) a,
       (SELECT rownum rnum, custno, invoiceno
        FROM   (SELECT custno, invoiceno
                FROM   customer_data
                WHERE  custno IN (1, 2)
                ORDER  BY custno, invoiceno)) b
WHERE  a.custno = b.custno

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.04          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        3      0.06       0.05          0         16          0         200
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      0.07       0.10          0         16          0         200

**********************************************************
==========================================================
**********************************************************

SELECT row_number() over(PARTITION BY custno ORDER BY custno, invoiceno) slno,
       custno,
       invoiceno
FROM   customer_data
ORDER  BY custno,
          invoiceno

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     2501     16.53      19.38       2080        436         50      250000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2503     16.53      19.38       2080        436         50      250000

SELECT b.rnum - a.minrnum + 1 slno, a.custno, b.invoiceno
FROM   (SELECT custno, MIN(rnum) minrnum
        FROM   (SELECT rownum rnum, custno, invoiceno
                FROM   (SELECT custno, invoiceno
                        FROM   customer_data
                        ORDER  BY custno, invoiceno))
        GROUP  BY custno) a,
       (SELECT rownum rnum, custno, invoiceno
        FROM   (SELECT custno, invoiceno
                FROM   customer_data
                ORDER  BY custno, invoiceno)) b
WHERE  a.custno = b.custno

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.03          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     2501     71.99      82.11       5007        872        100      250000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2503     71.99      82.14       5007        872        100      250000
Rating: 5
ALL_ROWS or FIRST_ROWS ? August 05, 2003
Reviewer: Tatiane
After all, using your pagination method, what optimization mode (or goal) should
we use ?
Followup:
FIRST_ROWS definitely
Rating: 4
August 05, 2003
Reviewer: A reader
Maybe FIRST_ROWS_1, 10, 100, 1000 ????

From the 9.2 Reference:

<q>
first_rows_n

The optimizer uses a cost-based approach, regardless of the presence of
statistics, and optimizes with a goal of best response time to return the first
n rows (where n = 1, 10, 100, 1000).

first_rows

The optimizer uses a mix of costs and heuristics to find a best plan for fast
delivery of the first few rows.
</q>

What is the difference in this case ?
Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:313616750808#11436946922740
for an example
Rating: 2
I still don't get it August 11, 2003
Reviewer: Sudha Bhagavatula from Buffalo,NY
I'm trying to run this and I get only 25 rows:

select *
  from (select cl.prov_full_name full_name,
               cl.spec_desc specialty_dsc,
               sum(cl.plan_liab_amt) tot_pd,
               sum(cl.co_ins_amt+cl.ded_amt+cl.copay_amt) patient_resp,
               count(distinct clm10_id) claims
          from aso.t_medical_claims_detail cl,
               aso.t_employer_groups_data g,
               aso.t_categories_data c
         where g.emp_super_grp_id||g.emp_sub_grp_id = cl.emp_grp_id
           and c.cat_dim_id = g.cat_dim_id
           and c.cat_name like 'America%'
           and cl.paid_date between to_date('01/01/2003','mm/dd/yyyy')
                                and to_date('06/30/2003','mm/dd/yyyy')
           and prov_full_name not like '*%'
           and spec_desc not like '*%'
       group by prov_full_name,
                spec_desc
       order by count(distinct clm10_id) desc )
where rownum < 26
union
select decode(full_name,null,' ', 'All Other Providers')
full_name,decode(specialty_dsc,null,' ','y')
specialty_dsc,tot_pd,patient_resp,claims
  from (select cl.prov_full_name full_name,
               cl.spec_desc specialty_dsc,
               sum(cl.plan_liab_amt) tot_pd,
               sum(cl.co_ins_amt+cl.ded_amt+cl.copay_amt) patient_resp,
               count(distinct clm10_id) claims
          from aso.t_medical_claims_detail cl,
               aso.t_employer_groups_data g,
               aso.t_categories_data c
         where g.emp_super_grp_id||g.emp_sub_grp_id = cl.emp_grp_id
           and c.cat_dim_id = g.cat_dim_id
           and c.cat_name like 'America%'
           and cl.paid_date between to_date('01/01/2003','mm/dd/yyyy')
                                and to_date('06/30/2003','mm/dd/yyyy')
           and prov_full_name not like '*%'
           and spec_desc not like '*%'
       group by prov_full_name,
                spec_desc
       order by count(distinct clm10_id) desc )
where rownum >= 26
Followup:
it by very definition will only ever return 25 rows at most.

"where rownum > 26" is assured to return 0 records.

rownum is assigned to a row like this:

rownum = 1
loop over potential records in the result set
    if predicate satisified
    then
         OUTPUT RECORD
         rownum = rownum + 1
    end if
end loop

So, you see -- rownum is ALWAYS 1 since rownum is never > 26 and rownum never
gets incremented.
Rating: 3
So how do I get the rows August 12, 2003
Reviewer: Sudha Bhagavatula from Buffalo,NY USA
So how do I get the result that I'm trying to achieve ? Can it be done ?

Thanks
Sudha
Followup:
I don't know -- why don't you phrase IN ENGLISH what you are trying to achieve.

The sql parser in my brain doesn't like to parse big queries and try to reverse
engineer what you MIGHT have wanted (given that the question isn't phrased
properly in the first place and all)....
Rating: 3
This is my question August 12, 2003
Reviewer: Sudha Bhagavatula
I have to create a report showing the top 25 providers based on the number of
distinct claims. Get the the total for the 25 providers, compute percentages
against the total for all the providers, and then total the claims for the
providers not in the top 25.

This is how the report should be :

provider                #claims   %of total

xxxxxxx                 1234      14%
yyyyyyy                 987       11%
-------

---till the top 25
All other providers     3210      32%

Thanks
Sudha
Followup:
ops$tkyte@ORA920> /*
DOC>
DOC>drop table t1;
DOC>drop table t2;
DOC>
DOC>create table t1 ( provider int );
DOC>
DOC>create table t2 ( provider int, claim_no int );
DOC>
DOC>
DOC>-- 100 providers...
DOC>insert into t1 select rownum from all_objects where rownum <= 100;
DOC>
DOC>insert into t2
DOC>select dbms_random.value( 1, 100 ), rownum
DOC>  from all_objects;
DOC>*/
ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select case when rn <= 25
  2              then to_char(provider)
  3              else 'all others'
  4         end provider,
  5         to_char( round(sum( rtr ) * 100 ,2), '999.99' )  || '%'
  6    from (
  7  select provider, cnt, rtr, row_number() over (order by rtr) rn
  8    from (
  9  select provider, cnt, ratio_to_report(cnt) over () rtr
 10    from (
 11  select t1.provider, count(*) cnt
 12    from t1, t2
 13   where t1.provider = t2.provider
 14   group by t1.provider
 15         )
 16         )
 17         )
 18   group by case when rn <= 25
 19                 then to_char(provider)
 20                 else 'all others'
 21             end
 22   order by count(*), sum(rtr) desc
 23  /

PROVIDER                                 TO_CHAR(
---------------------------------------- --------
69                                           .97%
45                                           .97%
14                                           .97%
99                                           .97%
27                                           .97%
43                                           .97%
5                                            .96%
72                                           .96%
2                                            .96%
61                                           .96%
78                                           .96%
29                                           .95%
92                                           .95%
88                                           .95%
63                                           .95%
91                                           .95%
35                                           .94%
67                                           .93%
77                                           .93%
60                                           .91%
76                                           .91%
55                                           .91%
79                                           .88%
1                                            .48%
100                                          .48%
all others                                 77.24%

26 rows selected.
Rating: 5
Great solution August 12, 2003
Reviewer: Sudha Bhagavatula
Tom,

That worked like a charm, thanks !

Sudha
Rating: 5
Works great, but bind variables giving bad plan August 22, 2003
Reviewer: Mike Madland from Bellevue, WA
Hi Tom,

Thanks for a great web site and a great book.

I'm using your awesome paginate query and getting great
results but I'm running into issues with the optimizer
giving me a bad plan when I use bind variables for the
beginning and ending row numbers.  I've tried all kinds
of hints but ended up resorting to dynamic sql to get the
fastest plan.

Do you have any ideas on why my query with the bind
variables is insisting on doing a hash join (and thus is
slower) and if there is any fix?  Thanks in advance.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production

SQL> create sequence s;

Sequence created.

SQL> create table t as
  2  select s.nextval pk, object_name, created, object_type
  3   from all_objects;

Table created.

SQL> insert /*+ append */
  2    into t (pk, object_name, created, object_type)
  3  select s.nextval,  object_name, created, object_type
  4   from t;

21158 rows created.

SQL> commit;

Commit complete.

SQL> insert /*+ append */
  2    into t (pk, object_name, created, object_type)
  3  select s.nextval,  object_name, created, object_type
  4   from t;

42316 rows created.

SQL> commit;

Commit complete.

SQL> insert /*+ append */
  2    into t (pk, object_name, created, object_type)
  3  select s.nextval,  object_name, created, object_type
  4   from t;

84632 rows created.

SQL>  commit;

Commit complete.

SQL> insert /*+ append */
  2    into t (pk, object_name, created, object_type)
  3  select s.nextval,  object_name, created, object_type
  4   from t;

169264 rows created.

SQL> commit;

Commit complete.

SQL> alter table t add constraint pk_t primary key (pk);

Table altered.

SQL> create index t_u on t (lower(object_name), pk);

Index created.

SQL> analyze table t compute statistics
  2    for table for all indexes for all indexed columns
  3  /

Table analyzed.

SQL> set timing on
SQL> alter session set sql_trace=true;

Session altered.

SQL> SELECT t.pk, t.object_name, t.created, object_type
  2    FROM (SELECT *
  3            FROM (select innermost.*, rownum as rowpos
  4                    from (SELECT pk
  5                            FROM t
  6                           ORDER BY LOWER(object_name)
  7                         ) innermost
  8                   where rownum <= 10 )
  9           where rowpos >= 1) pg
 10         INNER JOIN t ON pg.pk = t.pk
 11   ORDER BY pg.rowpos;

        PK OBJECT_NAME                    CREATED   OBJECT_TYPE
---------- ------------------------------ --------- -------------
         1 /1005bd30_LnkdConstant         13-AUG-03 JAVA CLASS
     10352 /1005bd30_LnkdConstant         13-AUG-03 SYNONYM
     21159 /1005bd30_LnkdConstant         13-AUG-03 JAVA CLASS
     31510 /1005bd30_LnkdConstant         13-AUG-03 SYNONYM
     42317 /1005bd30_LnkdConstant         13-AUG-03 JAVA CLASS
     52668 /1005bd30_LnkdConstant         13-AUG-03 SYNONYM
     63475 /1005bd30_LnkdConstant         13-AUG-03 JAVA CLASS
     73826 /1005bd30_LnkdConstant         13-AUG-03 SYNONYM
     84633 /1005bd30_LnkdConstant         13-AUG-03 JAVA CLASS
     94984 /1005bd30_LnkdConstant         13-AUG-03 SYNONYM

10 rows selected.

Elapsed: 00:00:00.03
SQL>
SQL> variable r            refcursor
SQL>
SQL> declare
  2  i_endrow    integer;
  3  i_startrow  integer;
  4
  5  begin
  6  i_endrow   := 10;
  7  i_startrow := 1;
  8
  9  open :r FOR
 10  SELECT t.pk, t.object_name, t.created, object_type
 11    FROM (SELECT *
 12            FROM (select innermost.*, rownum as rowpos
 13                    from (SELECT pk
 14                            FROM t
 15                           ORDER BY LOWER(object_name)
 16                         ) innermost
 17                   where rownum <= i_endrow )
 18           where rowpos >= i_startrow) pg
 19         INNER JOIN t ON pg.pk = t.pk
 20   ORDER BY pg.rowpos;
 21  END;
 22  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SQL>
SQL> print :r

        PK OBJECT_NAME                    CREATED   OBJECT_TYPE
---------- ------------------------------ --------- -------------
         1 /1005bd30_LnkdConstant         13-AUG-03 JAVA CLASS
     10352 /1005bd30_LnkdConstant         13-AUG-03 SYNONYM
     21159 /1005bd30_LnkdConstant         13-AUG-03 JAVA CLASS
     31510 /1005bd30_LnkdConstant         13-AUG-03 SYNONYM
     42317 /1005bd30_LnkdConstant         13-AUG-03 JAVA CLASS
     52668 /1005bd30_LnkdConstant         13-AUG-03 SYNONYM
     63475 /1005bd30_LnkdConstant         13-AUG-03 JAVA CLASS
     73826 /1005bd30_LnkdConstant         13-AUG-03 SYNONYM
     84633 /1005bd30_LnkdConstant         13-AUG-03 JAVA CLASS
     94984 /1005bd30_LnkdConstant         13-AUG-03 SYNONYM

10 rows selected.

Elapsed: 00:00:02.05

---- From TKPROF ----

SELECT t.pk, t.object_name, t.created, object_type
  FROM (SELECT *
          FROM (select innermost.*, rownum as rowpos
                  from (SELECT pk
                          FROM t
                         ORDER BY LOWER(object_name)
                       ) innermost
                 where rownum <= 10 )
         where rowpos >= 1) pg
       INNER JOIN t ON pg.pk = t.pk
 ORDER BY pg.rowpos

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.36         22         25          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.38         22         25          0          10

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
     10   SORT (ORDER BY)
     10    NESTED LOOPS
     10     VIEW
     10      COUNT (STOPKEY)
     10       VIEW
     10        INDEX   GOAL: ANALYZED (FULL SCAN) OF 'T_U'
                   (NON-UNIQUE)
     10     TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF 'T'
     10      INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF 'PK_T' (UNIQUE)

********************************************************************************

SELECT t.pk, t.object_name, t.created, object_type
  FROM (SELECT *
          FROM (select innermost.*, rownum as rowpos
                  from (SELECT pk
                          FROM t
                         ORDER BY LOWER(object_name)
                       ) innermost
                 where rownum <= :b1 )
         where rowpos >= :b2) pg
       INNER JOIN t ON pg.pk = t.pk
 ORDER BY pg.rowpos

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      2.34       2.55       1152       2492          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      2.34       2.56       1152       2492          0          10

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
     10   SORT (ORDER BY)
     10    HASH JOIN
     10     VIEW
     10      COUNT (STOPKEY)
     10       VIEW
     10        INDEX   GOAL: ANALYZED (FULL SCAN) OF 'T_U'
                   (NON-UNIQUE)
 338528     TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'T'
Followup:
first_rows all of the subqueries.  that is appropriate for pagination.  I should
have put that into the original response I guess!

consider:

SELECT t.pk, t.object_name, t.created, object_type
  FROM (SELECT *
          FROM (select innermost.*, rownum as rowpos
                  from (SELECT pk
                          FROM t
                         ORDER BY LOWER(object_name)
                       ) innermost
                 where rownum <= :b1 )
         where rowpos >= :b2) pg
       INNER JOIN t ON pg.pk = t.pk
 ORDER BY pg.rowpos

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      1.92       2.03          2       2491          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      1.92       2.03          2       2491          0          10

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  SORT ORDER BY (cr=2491 r=2 w=0 time=2033994 us)
     10   HASH JOIN  (cr=2491 r=2 w=0 time=2033701 us)
     10    VIEW  (cr=3 r=2 w=0 time=1029 us)
     10     COUNT STOPKEY (cr=3 r=2 w=0 time=955 us)
     10      VIEW  (cr=3 r=2 w=0 time=883 us)
     10       INDEX FULL SCAN T_U (cr=3 r=2 w=0 time=848 us)(object id 55317)
 350000    TABLE ACCESS FULL T (cr=2488 r=0 w=0 time=598964 us)

versus:

********************************************************************************
SELECT t.pk, t.object_name, t.created, object_type
  FROM (SELECT /*+ FIRST_ROWS */ *
          FROM (select /*+ FIRST_ROWS */ innermost.*, rownum as rowpos
                  from (SELECT /*+ FIRST_ROWS */ pk
                          FROM t
                         ORDER BY LOWER(object_name)
                       ) innermost
                 where rownum <= :b1 )
         where rowpos >= :b2) pg
       INNER JOIN t ON pg.pk = t.pk
 ORDER BY pg.rowpos

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0         20          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0         20          0          10

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  SORT ORDER BY (cr=20 r=0 w=0 time=954 us)
     10   TABLE ACCESS BY INDEX ROWID OBJ#(55315) (cr=20 r=0 w=0 time=749 us)
     21    NESTED LOOPS  (cr=15 r=0 w=0 time=589 us)
     10     VIEW  (cr=3 r=0 w=0 time=278 us)
     10      COUNT STOPKEY (cr=3 r=0 w=0 time=210 us)
     10       VIEW  (cr=3 r=0 w=0 time=151 us)
     10        INDEX FULL SCAN OBJ#(55317) (cr=3 r=0 w=0 time=98 us)(object id
55317)
     10     INDEX RANGE SCAN OBJ#(55316) (cr=12 r=0 w=0 time=188 us)(object id
55316)
Rating: 5
August 25, 2003
Reviewer: A reader
Rating: 5
Perfect September 03, 2003
Reviewer: Mike Madland from Bellevue, WA USA
Tom, thank you so much.  I had tried first_rows, but not on *all* of the
subqueries.  This is great.
Rating: 3
how about 8.0. September 13, 2003
Reviewer: s devarshi from Bhubaneswar ,India
what if ia want to do the same in 8.0.4 version

plsql ?

i have few other problem and wanted to ask you about it,
'ask your question later ' is blocking me

devarshi
Followup:
you cannot use order by in a subquery in 8.0 so this technique doesn't apply.

you have to open the cursor.

fetch the first N rows and ignore them

then fetch the next M rows and keep them

close the cursor

that's it.
Rating: 5
One question about your approach September 25, 2003
Reviewer: julie from Ohio
My java developer is asking me how he will know
how many rows are in the table for him to pass me
the minimum and maximun number. So that he can
pass  20, 40 and so on on the jsp page.
Followup:
you have a "first page"

you have a "next page"

when "next page" returns less rows then requested -- you know you have hit "last
page"

it is the way I do it... works great.  uses least resources.
Rating: 5
ORDER OF SELECTS October 08, 2003
Reviewer: Tracy from UK
I have a table accounts with a varchar2(50) column accountnumber.

I want to select the row with the highest value in accountnumber where the
column contains numbers only so I do this:

test> select max(acno)
  2  from
  3  (select to_number(ACCOUNTNUMBER) acno
  4  from ACCOUNTS
  5  where replace(translate(ACCOUNTNUMBER, '1234567890', 'aaaaaaaaaa'),'a','')
is null);

   MAX(ACNO)
------------
179976182723

which works fine. (May not be the best way of doing it, but it works.)

I then want to refine it by adding 'only if the number is less than 500000' so I
add

where acno < 500000

and then I get ORA-01722: invalid number.

test> l
  1  select max(acno)
  2  from
  3  (select to_number(ACCOUNTNUMBER) acno
  4  from ACCOUNTS
  5* where replace(translate(ACCOUNTNUMBER, '1234567890', 'aaaaaaaaaa'),'a','')
is null) where acno < 500000
test> /
where replace(translate(ACCOUNTNUMBER, '1234567890', 'aaaaaaaaaa'),'a','') is
null) where acno < 500000

         *
ERROR at line 5:
ORA-01722: invalid number

Presumably this is to do with the order in which the selects work, but I thought
that because the inner select is returning numbers
only that the outer select would work ok?
Followup:
you are ascribing procedural constructs to a non-procedural language!

you are thinking "inline view done AND then outer stuff"

in fact that query is not any different then the query with the inline view
removed -- the acno < 50000 is done "whenever".

you can:

where
  decode( replace(translate( accountNumber, '1234567890','0000000000'),'0',''),
          NULL, to_number( accountNumber ),
          NULL ) < 50000

hint: don't use 'a', else a string with 'a' in it would be considered a valid
number!
Rating: 5
search producing wrong results October 09, 2003
Reviewer: Paul Druker from Fairfax, VA USA
Tom, I was looking for from$_subquery$ combination on your site (I saw it in
dba_audit_trail.obj_name). However, search for from$_subquery$  provides
approximately 871 records, which is not correct. For example, this page does
contain this word, but almost all extracted pages don't. It's interesting that
search for from_subquery (without underscoire and $ sign) provides the same
result. Search for "from$_subquery$" provides the same 871 results. I'd
understand "special treatment" of underscore sign, but why $ sign?
Followup:

http://download-west.oracle.com/docs/cd/B10501_01/text.920/a96518/cqoper.htm#14499
$ is a stem search.
Rating: 5
Implementing dynamic query to suggested pagination query October 24, 2003
Reviewer: Stephane Gouin from Ottawa, ON, Canada
Hi Tom

Using owa_util.cellsprint, but wanting to customize the table rows a little
(adding a style sheet to hilight every other row, as a visual aid to users), so
forced to look at the following query, as given in this thread:

select *
  from ( select a.*, rownum rnum
           from ( YOUR_QUERY_GOES_HERE -- including the order by ) a
          where rownum <= MAX_ROWS )
 where rnum >= MIN_ROWS
/

My question is however, how do you introduce a dynamic query into the mix, given
I want to build a re-usable module to others can implement. This is exactly what
owa_util.cellsprint with the dynamic cursor accomplishes, but I can't get in
there to tweak it the layout.

Thanks for your help
Followup:
cellsprint only supports dynamic sql?  not sure what the issue is here?
Rating: 5
October 24, 2003
Reviewer: Stephane Gouin from Ottawa, ON Canada
Hi Tom,

Sorry, wasn't clear in my question. I was using cellsprint, but I realized I
can't insert a style in the table row tag for instance (ie <tr class="h1">) The
objective is to add a style to the row, where via a CSS, I can hilite alternate
rows, giving user a little contrast when dealing with long lists.

I want to extend the cellsprint function, by allowing further control over the
table tags... (ie style sheets, alignment, widths etc...)

Using a REF Cursor (or owa_util.bind_variables) for the subquery, how could I
implement it using the pagination query.

Hope I clarified the question enough..
Followup:
you actually have access to the source code for cellsprint (its not wrapped).
just copy it as your own and modify as you see fit.
Rating: 5
getting rows N through M of a result se October 30, 2003
Reviewer: Edward Girard from Dallas, TX
Excelllent thread
Rating: 5
Very useful for web-based applications November 01, 2003
Reviewer: Saminathan Seerangan from NJ,USA
Rating: 4
HAVING can be efficient November 12, 2003
Reviewer: Joe Levy from Cambridge MA
Agreed that this

<quote>
select rownum, col1
from foobar
group by rownum, col1
having rownum >= :start and rownum < :end
</quote>

is inefficient.  But

select rownum, col1
from foobar
where rownum < :end -- added line to improve performance
group by rownum, col1
having rownum >= :start and rownum < :end

is almost as efficient as your preferred method.  And it has the advantage of
being usable in a scalar subquery.  (The additional nesting required by your
preferred method puts columns from tables in the outer query out of scope.)

Is there a reason not to use a HAVING clause with ROWNUM when variable scope
demands it?
Followup:
why would a scalar subquery need the N'th row?

but yes, that would work (don't need the second and rownum < :end)
Rating: 4
Row_Number() or ROWNUM November 19, 2003
Reviewer: Ranjit Desai from India
Hi Tom,

We do use row_Number() and other Analytical functions. But recently came across
some limitation. Oracle 8i standard edition don't support this functions. They
are only available in Enterprise Edition. Many of our sites are on Standard
edition on Oracle 8i.

So current method to use Row_NUmber() to get required output needs to be
changed.

SELECT deptno, ename, hiredate,
ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY hiredate) AS emp_id
FROM emp

To get similar output in SELECT query what can we do? Is it possible to use
ROWNUM?? or Used defined function?

Please help us. As we have already tried some options without success.

Thanks & Regards,

Ranjit Desai
Followup:
you cannot use rownum to achieve that.  analytics are mandatory for getting
those numbers "partitioned"

9iR2 SE (standard) offers analytics as a feature.
Rating: 5
Fetching rows N-M November 26, 2003
Reviewer: Stevef from Europe
Can the first N rows optimization feature be used in association with the paging
technique to enhance the performance these queries ?

SELECT /*+ FIRST_ROWS(N) */ ....
http://otn.oracle.com/products/oracle9i/daily/jan28.html

Followup:
yes, i usually just use first_rows myself.
Rating: 1
Fetching rows N-M November 27, 2003
Reviewer: Stevef from Europe
Actually, Weird effects. The first query below returns 10 rows as expected but
the second returns 19 rows !!!!
(Oracle9i Enterprise Edition Release 9.2.0.2.1 Win2000)

select*
   from (select a.*,rownum r
           from (select /*+ first_rows */ customerid from customer order by 1) a
   where rownum <= 10+9 )
where r >= 10

select*
   from (select a.*,rownum r
           from (select /*+ first_rows(10) */ customerid from customer order by
1) a
   where rownum <= 10+9 )
where r >= 10
Followup:
confirmed -- filed a bug, temporary workaround is to add "order by r"

we can see they lose the filter using dbms_xplan:

ops$tkyte@ORA920> delete from plan_table;
6 rows deleted.

ops$tkyte@ORA920> explain plan for
  2  select*
  3     from (select a.*,rownum r
  4             from (select /*+ first_rows(10) */ empno from scott.emp order by
1) a
  5     where rownum <= 19 )
  6  where r >= 10
  7  /

Explained.

ops$tkyte@ORA920> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

-------------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost (%CPU)|
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             |    14 |   364 |     2  (50)|
|   1 |  VIEW                |             |    14 |   364 |            |
|*  2 |   COUNT STOPKEY      |             |       |       |            |
|   3 |    VIEW              |             |    14 |   182 |            |
|   4 |     INDEX FULL SCAN  | EMP_PK      |    14 |    42 |     2  (50)|
-------------------------------------------------------------------------

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

   2 - filter(ROWNUM<=19)

15 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> delete from plan_table;

5 rows deleted.

ops$tkyte@ORA920> explain plan for
  2  select*
  3     from (select a.*,rownum r
  4             from (select /*+ first_rows(10) */ empno from scott.emp order by
1) a
  5     where rownum <= 19 )
  6  where r >= 10
  7  order by r
  8  /

Explained.

ops$tkyte@ORA920> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

-------------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost (%CPU)|
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             |    14 |   364 |     3  (67)|
|   1 |  SORT ORDER BY       |             |    14 |   364 |     3  (67)|
|*  2 |   VIEW               |             |    14 |   364 |            |
|*  3 |    COUNT STOPKEY     |             |       |       |            |
|   4 |     VIEW             |             |    14 |   182 |            |
|   5 |      INDEX FULL SCAN | EMP_PK      |    14 |    42 |     2  (50)|
-------------------------------------------------------------------------

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

   2 - filter("from$_subquery$_001"."R">=10)
   3 - filter(ROWNUM<=19)

17 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select*
  2     from (select a.*,rownum r
  3             from (select /*+ first_rows(10) */ empno from scott.emp order by
1) a
  4     where rownum <= 19 )
  5  where r >= 10
  6  order by r
  7  /

     EMPNO          R
---------- ----------
      7844         10
      7876         11
      7900         12
      7902         13
      7934         14

ops$tkyte@ORA920>
Rating: 5
rows N-M November 28, 2003
Reviewer: Stevef
Gosh Tom, your momma sure raised you smart!
Great detective work!
Rating: 4
Getting total rows.. December 04, 2003
Reviewer: Naveen from India
Hi Tom:

The application we are developing should use pagination to show the results. The
devlopers want me to get the total rows that the query return so that they can
display that many pages. (Say, if the total rows returned are 100 and the number
of results that have to be displayed in each page is 10 rows, they can set 10
pages to display results sets.) The requirement is that we have to display the
page number with hyperlink, so when user clicks on page 3, we have to display
rows 31-40.

To do this i have to first find the count of rows that the query returns and
then fire the query to return the rows N through M. This is two I/O calls to the
database and two queries to be parsed to display a page. Is there any work
around.

Thanks
Nav.
Followup:
I have a very very very very simple solution to this problem.

DON'T DO IT.

Your developers probably love www.google.com right?
they appreciate its speed, accuracy, usefulness.

All you need to do is tell them "use google as the gold standard for searching.
DO WHAT IT DOES"

Google lies constantly.  the hit count is never real.  It tells you "here are
the first 10 pages" -- but you'll find if you click on page 10, you'll be on
page 7 (there wasnt any page 8, 9 or 10 -- they didn't know that)

google guesses.  (i guess -- search on asktom, "approximately")

google is the gold standard -- just remember that.

In order to tell the end user "hey, there are 15 pages" you would have to run
the entire query to completion on page one

and guess what, by the time page 1 is delivered to them (after waiting and
waiting for it) there is a good chance their result set won't have 15 pages!!!
(it is a database after all, people do write to it).  they might have 16 or
maybe 14, or maybe NONE or maybe lots more the next time they page up or down!!

google is the gold standard.

did you know, you'll never go past page 100 on google - try it, they won't let
you.

Here is a short excerpt from my book "Effective Oracle By Design" where I talk
about this very topic (pagination in a web environment)

<quote>
Keep in mind that people are impatient and have short attention spans. How many
times have you gone past the tenth page on a search page on the Internet? When I
do a Google (www.google.com) search that returns more hits than the number of
hamburgers sold by McDonald's, I never go to the last page; in fact, I never get
to page 11. By the time I've looked at the first five pages or so, I realize
that I need to refine my search because this is too much data. Your end users
will, believe it or not, do the same.

Some Advice on Web-based Searches with Pagination

My advice for handling web-based searches that you need to paginate through is
to never provide an exact hit count. Use an estimate to tell the users about N
hits. This is what I do on my asktom web site, for example. I use Oracle Text to
index the content. Before I run a query, I ask Oracle Text for an estimate. You
can do the same with your relational queries using EXPLAIN PLAN in Oracle8i and
earlier, or by querying V$SQL_PLAN in Oracle9i and up.
You may want to tell the end users they got 1,032,231 hits, but the problem with
that is twofold:

o It takes a long time to count that many hits. You need to run that ALL_ROWS
type of query to the end to find that out! It is really slow.
o By the time you count the hits, in all probability (unless you are on a
read-only database), the answer has already changed and you do not have that
number of hits anymore!

My other advice for this type of application is to never provide a Last Page
button or give the user more than ten pages at a time from which to choose. Look
at the standard, www.google.com, and do what it does.

Follow those two pieces of advice, and your pagination worries are over.
</quote>
Rating: 5
Thanks Tom.. December 04, 2003
Reviewer: Naveen from India
Hi Tom,

Got what you said. I'll try to convince my developers with this information. Day
by day the admiration for you keeps growing.

Thank you

Nav.
Rating: 4
Displaying Top N rows in 8.0.6 December 09, 2003
Reviewer: Russell from Melbourne, Australia
G'day Tom,

On September 13, 2003  or there abouts you left the following:
 ----------
you cannot use order by in a subquery in 8.0 so this technique doesn't apply.

you have to open the cursor.

fetch the first N rows and ignore them

then fetch the next M rows and keep them

close the cursor
that's it.

----------

I have an application, where a set of grouped records is in the vicinity of 800
combinations.  For the purposes of analysis, 80% of work is in top 20% of
grouped entries.  As such most gains will be achieved by analysing these entries
with the most reords.  As I am trying to do the majority of the grunt work in
Oracle, parameters are passed by users, to a procedure with a ref Cursor being
OUTput to a Crystal report.

One of the parameters I am inputting, is TopN hoping to return grouped entries
with the greatest Record counts by the grouping needed.

I include this statement in a cursor, and loop through for 1 to TopN, appending
the resulting Group Names to a varchar2 variable hoping to include the contents
of this string in a subsequent where statement.

A possible example:

declare
TopN := 3;  -- return all records matching the group identifer with TopN most
records

Counter Number := 0;
vchString varchar2(200);
begin
for I in (Select Dept, count(*) from employees
    where ....
    group by Dept order by count(*) desc)
Loop

if counter > TopN then
    exit;
end if;

if counter > 0 then
    vchString:=vchstring||',';
end if;
vchString:=vchString||i.dept;
 -- or vchstring:=vchstring|| ''''||string||''''
 -- for columns containing varchar data....
end loop;

I then have a statement
Cursor is ....
select ......
from employees
where .....
AND  DEPT in ( vchString);

end;

with the hope that the original cursor might return something like
DEPT  COUNT(*)
30    8
20    7
19    6
10    5
15    3
 7    2
 1    1
 4    1

and the returning cursor in the optimum world would therefore become something
like
select ......
from employees
where .....
AND  DEPT in ( 30,20,19);

Hence having to select, group, sum, and display the 12(TopN) group entries
instead of 800 ish.

The loop works, and populates the varchar2 variable, but the contents of that
variable don't seem to be transposed or included into the IN statement.  As
mentioned above I am using Oracle database version 8.0.6, and having read a
number of threads on your site, don't think I can use the Analytic functions
included in 8.1 and above.

Please advise what my problem is, or if there is a better way to try and do what
I am after.

Thanks in advance.
Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:110612348061
Rating: 5
Catastrophic Performance Degredation December 16, 2003
Reviewer: Doh!
Any ideas as to why the act of putting an outer "wrapper" on an inner query

select * from ( my_inner_query )

can cause the performance of a query to degrade by a factor of 3000 ?

First the innermost query:

SQL>     ( SELECT a.*, ROWNUM RECORDINDEX FROM
  2      ( SELECT 'Townland Boundary' "FEATURENAME", mME.MinX, mME.MaxX,
mME.MinY, mME.MaxY,  gL.LayerName,
  3        gL.LayerAlias,  TOWNLAND.GEONAME COLUMN1 , COUNTY.GEONAME COLUMN2
  4       FROM COUNTY ,TOWNLAND , MinMaxExtent mME, GeoLayer gL
  5        WHERE COUNTY.GEONAME = 'L123'
  6         AND  TOWNLAND.GEONAME LIKE 'BALL%'
  7         AND TOWNLAND.COUNTYID = COUNTY.COUNTYID
  8         AND mME.ForeignID = TOWNLAND.GEOMETRYID
  9        AND  mME.TableName = 'TOWNLAND'
 10        AND gL.TableName = mME.TableName
 11        AND gL.LayerName = 'TOWNLAND'
 12      ORDER BY  TOWNLAND.GEONAME , COUNTY.GEONAME)
 13      a WHERE ROWNUM <= 10)
 14  /

10 rows selected.

Elapsed: 00:00:00.01
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=169 Card=2 Bytes=3214)
   1    0   COUNT (STOPKEY)
   2    1     VIEW (Cost=169 Card=2 Bytes=3214)
   3    2       SORT (ORDER BY STOPKEY) (Cost=169 Card=2 Bytes=224)
   4    3         NESTED LOOPS (Cost=167 Card=2 Bytes=224)
   5    4           NESTED LOOPS (Cost=163 Card=2 Bytes=158)
   6    5             MERGE JOIN (CARTESIAN) (Cost=3 Card=1 Bytes=57)
   7    6               TABLE ACCESS (BY INDEX ROWID) OF 'GEOLAYER' (Cost=2
Card=1 Bytes=45)
   8    7                 INDEX (RANGE SCAN) OF 'GEOLAYER_LAYERNAME_IDx'
(NON-UNIQUE) (Cost=1 Card=1)
   9    6               BUFFER (SORT) (Cost=1 Card=1 Bytes=12)
  10    9                 TABLE ACCESS (BY INDEX ROWID) OF 'COUNTY' (Cost=1
Card=1 Bytes=12)
  11   10                   INDEX (RANGE SCAN) OF 'COUNTY_GEONAME_IDX'
(NON-UNIQUE)
  12    5             TABLE ACCESS (BY INDEX ROWID) OF 'TOWNLAND' (Cost=163
Card=1 Bytes=22)
  13   12               BITMAP CONVERSION (TO ROWIDS)
  14   13                 BITMAP AND
  15   14                   BITMAP CONVERSION (FROM ROWIDS)
  16   15                     INDEX (RANGE SCAN) OF 'TOWNLAND_COUNTYID_IDX'
(NON-UNIQUE) (Cost=4 Card=1950)
  17   14                   BITMAP CONVERSION (FROM ROWIDS)
  18   17                     SORT (ORDER BY)
  19   18                       INDEX (RANGE SCAN) OF 'TOWNLAND_GEONAME_IDX'
(NON-UNIQUE) (Cost=14 Card=1950)
  20    4           TABLE ACCESS (BY INDEX ROWID) OF 'MINMAXEXTENT' (Cost=2
Card=50698 Bytes=1673034)
  21   20             INDEX (UNIQUE SCAN) OF 'MINMAXEXT_UK' (UNIQUE) (  Cost=1
Card=4)

Statistics
----------------------------------------------------------
          0  recursive calls
          6  db block gets
        530  consistent gets
         21  physical reads
          0  redo size
       1458  bytes sent via SQL*Net to client
        498  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          2  sorts (memory)
          1  sorts (disk)
         10  rows processed

Now the final outer wrapper:

SQL> SELECT a.* FROM
  2      ( SELECT a.*, ROWNUM RECORDINDEX FROM
  3      ( SELECT 'Townland Boundary' "FEATURENAME", mME.MinX, mME.MaxX,
mME.MinY, mME.MaxY,  gL.LayerName,
  4        gL.LayerAlias,  TOWNLAND.GEONAME COLUMN1 , COUNTY.GEONAME COLUMN2
  5       FROM COUNTY ,TOWNLAND , MinMaxExtent mME, GeoLayer gL
  6        WHERE COUNTY.GEONAME = 'L123'
  7         AND  TOWNLAND.GEONAME LIKE 'BALL%'
  8         AND TOWNLAND.COUNTYID = COUNTY.COUNTYID
  9         AND mME.ForeignID = TOWNLAND.GEOMETRYID
 10        AND  mME.TableName = 'TOWNLAND'
 11        AND gL.TableName = mME.TableName
 12        AND gL.LayerName = 'TOWNLAND'
 13      ORDER BY  TOWNLAND.GEONAME , COUNTY.GEONAME)
 14      a WHERE ROWNUM <= 10) a
 15  /

10 rows selected.

Elapsed: 00:00:32.00

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=466 Card=2 Bytes=3240)

   1    0   VIEW (Cost=466 Card=2 Bytes=3240)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=466 Card=2 Bytes=3214)
   4    3         SORT (ORDER BY STOPKEY) (Cost=466 Card=2 Bytes=224)
   5    4           TABLE ACCESS (BY INDEX ROWID) OF 'TOWNLAND' (Cost=464 Card=1
Bytes=22)
   6    5             NESTED LOOPS (Cost=464 Card=2 Bytes=224)
   7    6               NESTED LOOPS (Cost=68 Card=722 Bytes=64980)
   8    7                 MERGE JOIN (CARTESIAN) (Cost=3 Card=1 Bytes=57)
   9    8                   TABLE ACCESS (BY INDEX ROWID) OF 'COUNTY'(Cost=2
Card=1 Bytes=12)
  10    9                     INDEX (RANGE SCAN) OF 'COUNTY_GEONAME_IDX'
(NON-UNIQUE) (Cost=1 Card=1)
  11    8                   BUFFER (SORT) (Cost=1 Card=1 Bytes=45)
  12   11                     TABLE ACCESS (BY INDEX ROWID) OF 'GEOLAYER'
(Cost=1 Card=1 Bytes=45)
  13   12                       INDEX (RANGE SCAN) OF 'GEOLAYER_LAYERNAME_IDX'
(NON-UNIQUE)
  14    7                 TABLE ACCESS (BY INDEX ROWID) OF 'MINMAXEXTENT'
(Cost=65 Card=1 Bytes=33)
  15   14                   INDEX (RANGE SCAN) OF 'MINMAXEXT_UK' (UNIQUE)
(Cost=43 Card=2112)
  16    6               BITMAP CONVERSION (TO ROWIDS)
  17   16                 BITMAP AND
  18   17                   BITMAP CONVERSION (FROM ROWIDS)
  19   18                     INDEX (RANGE SCAN) OF 'TOWNLAND_PK' (UNIQUE)
  20   17                   BITMAP CONVERSION (FROM ROWIDS)
  21   20                     INDEX (RANGE SCAN) OF 'TOWNLAND_COUNTYID_IDX'
(NON-UNIQUE) (Cost=4 Card=12)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     254501  consistent gets
        847  physical reads
          0  redo size
       1458  bytes sent via SQL*Net to client
        498  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
         10  rows processed
Followup:
if you push a first_rows hint into the innermost queries -- what happens then
(no answer for why this is happening -- don't know in this case -- for that,
suggest a tar but lets try to find a way to workaround the issue here)
Rating: 5
Improvement December 17, 2003
Reviewer: A reader
Query elapsed time falls to about 1 second. Huge improvement but still not as
snappy as the original query at 0.01 seconds!

  1    SELECT a.* FROM
  2           ( SELECT  a.*, ROWNUM RECORDINDEX FROM
  3           ( SELECT /*+ FIRST_ROWS */ 'Townland Boundary' "FEATURENAME",
mME.MinX, mME.MaxX, mME.MinY, mME.MaxY,  gL.LayerName,
  4             gL.LayerAlias,  TOWNLAND.GEONAME COLUMN1 , COUNTY.GEONAME
COLUMN2
  5            FROM COUNTY ,TOWNLAND , MinMaxExtent mME, GeoLayer gL
  6             WHERE COUNTY.GEONAME = 'L123'
  7              AND  TOWNLAND.GEONAME LIKE 'BALL%'
  8             AND TOWNLAND.COUNTYID = COUNTY.COUNTYID
  9              AND mME.ForeignID = TOWNLAND.GEOMETRYID
 10            AND  mME.TableName = 'TOWNLAND'
 11           AND gL.TableName = mME.TableName
 12            AND gL.LayerName = 'TOWNLAND'
 13          ORDER BY  TOWNLAND.GEONAME , COUNTY.GEONAME)
 14*         a WHERE ROWNUM <= 10) a
SQL> /

10 rows selected.

Elapsed: 00:00:01.08

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=8578 Card=2
Bytes=3240)
   1    0   VIEW (Cost=8578 Card=2 Bytes=3240)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=8578 Card=2 Bytes=3214)
   4    3         SORT (ORDER BY STOPKEY) (Cost=8578 Card=2 Bytes=224)
   5    4           NESTED LOOPS (Cost=8576 Card=2 Bytes=224)
   6    5             MERGE JOIN (CARTESIAN) (Cost=8264 Card=156 Bytes=12324)
   7    6               NESTED LOOPS (Cost=8108 Card=156 Bytes=5304)
   8    7                 TABLE ACCESS (BY INDEX ROWID) OF 'TOWNLAND'(Cost=4052
Card=4056 Bytes=89232)
   9    8                   INDEX (RANGE SCAN) OF 'TOWNLAND_GEONAME_IDX'
(NON-UNIQUE) (Cost=15 Card=4056)
  10    7                 TABLE ACCESS (BY INDEX ROWID) OF 'COUNTY' (Cost=1
Card=1 Bytes=12)
  11   10                   INDEX (UNIQUE SCAN) OF 'COUNTY_UK' (UNIQUE)
  12    6               BUFFER (SORT) (Cost=8263 Card=1 Bytes=45)
  13   12                 TABLE ACCESS (BY INDEX ROWID) OF 'GEOLAYER' (Cost=1
Card=1 Bytes=45)
  14   13                   INDEX (RANGE SCAN) OF 'GEOLAYER_LAYERNAME_IDX'
(NON-UNIQUE)
  15    5             TABLE ACCESS (BY INDEX ROWID) OF 'MINMAXEXTENT'(Cost=2
Card=1 Bytes=33)
  16   15               INDEX (UNIQUE SCAN) OF 'MINMAXEXT_UK' (UNIQUE)(Cost=1
Card=1)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      10413  consistent gets
       4174  physical reads
          0  redo size
       1458  bytes sent via SQL*Net to client
        498  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
         10  rows processed
Followup:
do you have a tkprof -- are the "estimations" in the autotrace anywhere near the
"real numbers" in the tkprof -- are the stats current and up to date.
Rating: 5
December 17, 2003
Reviewer: A reader
Stats are current.

Here we have tkprof first with and then without first_rows hint:

    SELECT a.* FROM
           ( SELECT  a.*, ROWNUM RECORDINDEX FROM
           ( SELECT /*+ FIRST_ROWS */ :"SYS_B_0" "FEATURENAME",
             mME.MinX, mME.MaxX, mME.MinY, mME.MaxY,  gL.LayerName,
             gL.LayerAlias,  TOWNLAND.GEONAME COLUMN1 , COUNTY.GEONAME COLUMN2
           FROM COUNTY ,TOWNLAND , MinMaxExtent mME, GeoLayer gL
            WHERE COUNTY.GEONAME = :"SYS_B_1"
             AND  TOWNLAND.GEONAME LIKE :"SYS_B_2"
             AND TOWNLAND.COUNTYID = COUNTY.COUNTYID
              AND mME.ForeignID = TOWNLAND.GEOMETRYID
           AND  mME.TableName = :"SYS_B_3"
           AND gL.TableName = mME.TableName
            AND gL.LayerName = :"SYS_B_4"
          ORDER BY  TOWNLAND.GEONAME , COUNTY.GEONAME)
          a WHERE ROWNUM <= :"SYS_B_5") a

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        3      0.68      11.24       8694      20432          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        7      0.68      11.25       8694      20432          0          10

Misses in library cache during parse: 1
Optimizer goal: FIRST_ROWS
Parsing user id: 89

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  VIEW  (cr=10019 r=4443 w=0 time=9453637 us)
      0   COUNT STOPKEY (cr=10019 r=4443 w=0 time=9453630 us)
      0    VIEW  (cr=10019 r=4443 w=0 time=9453627 us)
      0     SORT ORDER BY STOPKEY (cr=10019 r=4443 w=0 time=9453621 us)
      0      NESTED LOOPS  (cr=10019 r=4443 w=0 time=9453566 us)
      0       MERGE JOIN CARTESIAN (cr=10019 r=4443 w=0 time=9453562 us)
      0        NESTED LOOPS  (cr=10019 r=4443 w=0 time=9453555 us)
   5011         TABLE ACCESS BY INDEX ROWID TOWNLAND (cr=5006 r=4438 w=0
time=9339026 us)
   5011          INDEX RANGE SCAN TOWNLAND_GEONAME_IDX (cr=21 r=0 w=0 time=30694
us)(object id 55399)
      0         TABLE ACCESS BY INDEX ROWID COUNTY (cr=5013 r=5 w=0 time=84014
us)
   5011          INDEX UNIQUE SCAN COUNTY_UK (cr=2 r=1 w=0 time=32598 us)(object
id 55015)
      0        BUFFER SORT (cr=0 r=0 w=0 time=0 us)
      0         TABLE ACCESS BY INDEX ROWID GEOLAYER (cr=0 r=0 w=0 time=0 us)
      0          INDEX RANGE SCAN GEOLAYER_LAYERNAME_IDX (cr=0 r=0 w=0 time=0
us)(object id 55930)
      0       TABLE ACCESS BY INDEX ROWID MINMAXEXTENT (cr=0 r=0 w=0 time=0 us)
      0        INDEX UNIQUE SCAN MINMAXEXT_UK (cr=0 r=0 w=0 time=0 us)(object id
55602)

********************************************************************************

    SELECT a.* FROM
           ( SELECT  a.*, ROWNUM RECORDINDEX FROM
           ( SELECT  :"SYS_B_0" "FEATURENAME",
             mME.MinX, mME.MaxX, mME.MinY, mME.MaxY,  gL.LayerName,
             gL.LayerAlias,  TOWNLAND.GEONAME COLUMN1 , COUNTY.GEONAME COLUMN2
           FROM COUNTY ,TOWNLAND , MinMaxExtent mME, GeoLayer gL
            WHERE COUNTY.GEONAME = :"SYS_B_1"
             AND  TOWNLAND.GEONAME LIKE :"SYS_B_2"
             AND TOWNLAND.COUNTYID = COUNTY.COUNTYID
              AND mME.ForeignID = TOWNLAND.GEOMETRYID
           AND  mME.TableName = :"SYS_B_3"
           AND gL.TableName = mME.TableName
            AND gL.LayerName = :"SYS_B_4"
          ORDER BY  TOWNLAND.GEONAME , COUNTY.GEONAME)
          a WHERE ROWNUM <= :"SYS_B_5") a

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2    136.84     139.22        851     254502          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4    136.84     139.22        851     254502          0          10

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 89

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  VIEW  (cr=254502 r=851 w=0 time=139222875 us)
     10   COUNT STOPKEY (cr=254502 r=851 w=0 time=139222837 us)
     10    VIEW  (cr=254502 r=851 w=0 time=139222808 us)
     10     SORT ORDER BY STOPKEY (cr=254502 r=851 w=0 time=139222779 us)
    130      TABLE ACCESS BY INDEX ROWID TOWNLAND (cr=254502 r=851 w=0
time=139221108 us)
  51817       NESTED LOOPS  (cr=254226 r=695 w=0 time=138882960 us)
  50698        NESTED LOOPS  (cr=628 r=597 w=0 time=1651025 us)
      1         MERGE JOIN CARTESIAN (cr=5 r=0 w=0 time=319 us)
      1          TABLE ACCESS BY INDEX ROWID COUNTY (cr=3 r=0 w=0 time=127 us)
      1           INDEX RANGE SCAN COUNTY_GEONAME_IDX (cr=1 r=0 w=0 time=56
us)(object id 55401)
      1          BUFFER SORT (cr=2 r=0 w=0 time=96 us)
      1           TABLE ACCESS BY INDEX ROWID GEOLAYER (cr=2 r=0 w=0 time=31 us)
      1            INDEX RANGE SCAN GEOLAYER_LAYERNAME_IDX (cr=1 r=0 w=0 time=18
us)(object id 55930)
  50698         TABLE ACCESS BY INDEX ROWID MINMAXEXTENT (cr=623 r=597 w=0
time=1548952 us)
  50698          INDEX RANGE SCAN MINMAXEXT_UK (cr=154 r=137 w=0 time=592031
us)(object id 55602)
   1118        BITMAP CONVERSION TO ROWIDS (cr=253598 r=98 w=0 time=136695508
us)
   1118         BITMAP AND  (cr=253598 r=98 w=0 time=136580794 us)
  50698          BITMAP CONVERSION FROM ROWIDS (cr=50804 r=98 w=0 time=1338601
us)
  50698           INDEX RANGE SCAN TOWNLAND_PK (cr=50804 r=98 w=0 time=960672
us)(object id 55131)
  27108          BITMAP CONVERSION FROM ROWIDS (cr=202794 r=0 w=0 time=134967141
us)
56680364           INDEX RANGE SCAN TOWNLAND_COUNTYID_IDX (cr=202794 r=0 w=0
time=76815579 us)(object id 55416)
Followup:
I wanted to compare the first_rows to the one that is "fast", not the slow one.
Rating: 5
additional tkprof December 17, 2003
Reviewer: A reader
here's the tkprof for the query without the outer wrapper:

 SELECT  a.*, ROWNUM RECORDINDEX FROM
           ( SELECT  :"SYS_B_0" "FEATURENAME",
             mME.MinX, mME.MaxX, mME.MinY, mME.MaxY,  gL.LayerName,
             gL.LayerAlias,  TOWNLAND.GEONAME COLUMN1 , COUNTY.GEONAME COLUMN2
           FROM COUNTY ,TOWNLAND , MinMaxExtent mME, GeoLayer gL
            WHERE COUNTY.GEONAME = :"SYS_B_1"
             AND  TOWNLAND.GEONAME LIKE :"SYS_B_2"
             AND TOWNLAND.COUNTYID = COUNTY.COUNTYID
              AND mME.ForeignID = TOWNLAND.GEOMETRYID
           AND  mME.TableName = :"SYS_B_3"
           AND gL.TableName = mME.TableName
            AND gL.LayerName = :"SYS_B_4"
          ORDER BY  TOWNLAND.GEONAME , COUNTY.GEONAME)
          a WHERE ROWNUM <= :"SYS_B_5"

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.03       0.08         21        530          6          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.03       0.08         21        530          6          10

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 89

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  COUNT STOPKEY (cr=530 r=21 w=21 time=85530 us)
     10   VIEW  (cr=530 r=21 w=21 time=85489 us)
     10    SORT ORDER BY STOPKEY (cr=530 r=21 w=21 time=85462 us)
    130     NESTED LOOPS  (cr=530 r=21 w=21 time=84925 us)
    130      NESTED LOOPS  (cr=138 r=21 w=21 time=82066 us)
      1       MERGE JOIN CARTESIAN (cr=4 r=0 w=0 time=236 us)
      1        TABLE ACCESS BY INDEX ROWID GEOLAYER (cr=2 r=0 w=0 time=93 us)
      1         INDEX RANGE SCAN GEOLAYER_LAYERNAME_IDX (cr=1 r=0 w=0 time=56
us)(object id 55930)
      1        BUFFER SORT (cr=2 r=0 w=0 time=92 us)
      1         TABLE ACCESS BY INDEX ROWID COUNTY (cr=2 r=0 w=0 time=38 us)
      1          INDEX RANGE SCAN COUNTY_GEONAME_IDX (cr=1 r=0 w=0 time=19
us)(object id 55401)
    130       TABLE ACCESS BY INDEX ROWID TOWNLAND (cr=134 r=21 w=21 time=81527
us)
    130        BITMAP CONVERSION TO ROWIDS (cr=26 r=21 w=21 time=80386 us)
      1         BITMAP AND  (cr=26 r=21 w=21 time=80228 us)
      1          BITMAP CONVERSION FROM ROWIDS (cr=5 r=0 w=0 time=2782 us)
   1118           INDEX RANGE SCAN TOWNLAND_COUNTYID_IDX (cr=5 r=0 w=0 time=1551
us)(object id 55416)
      1          BITMAP CONVERSION FROM ROWIDS (cr=21 r=21 w=21 time=77315 us)
   5011           SORT ORDER BY (cr=21 r=21 w=21 time=72098 us)
   5011            INDEX RANGE SCAN TOWNLAND_GEONAME_IDX (cr=21 r=0 w=0
time=20016 us)(object id 55399)
    130      TABLE ACCESS BY INDEX ROWID MINMAXEXTENT (cr=392 r=0 w=0 time=2105
us)
    130       INDEX UNIQUE SCAN MINMAXEXT_UK (cr=262 r=0 w=0 time=1126
us)(object id 55602)

********************************************************************************
Followup:
wonder if it is a side effect of cursor sharing here -- hmm.  The difference
between the plans is one is using b*tree - bitmap conversions, avoiding the
table access by rowid (and that is what is causing the "slowdown", all of the IO
to read that table a block at a time)

what happens to the plans if you turned off cursor sharing for a minute (alter
session set cursor_sharing=exact).  just curious at this point.
Rating: 5
December 18, 2003
Reviewer: A reader
Attached:

First the fastest and then the slower with first_rows hint:

alter session set cursor_sharing=exact

SELECT a.*, ROWNUM RECORDINDEX FROM
             ( SELECT 'Townland Boundary' "FEATURENAME", mME.MinX, mME.MaxX,
mME.MinY, mME.MaxY,  gL.LayerName,
               gL.LayerAlias,  TOWNLAND.GEONAME COLUMN1 , COUNTY.GEONAME COLUMN2
              FROM COUNTY ,TOWNLAND , MinMaxExtent mME, GeoLayer gL
               WHERE COUNTY.GEONAME = 'L123'
                AND  TOWNLAND.GEONAME LIKE 'BALL%'
               AND TOWNLAND.COUNTYID = COUNTY.COUNTYID
                AND mME.ForeignID = TOWNLAND.GEOMETRYID
             AND  mME.TableName = 'TOWNLAND'
            AND gL.TableName = mME.TableName
             AND gL.LayerName = 'TOWNLAND'
           ORDER BY  TOWNLAND.GEONAME , COUNTY.GEONAME)
          a WHERE ROWNUM <= 10

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.40       0.41          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.09       0.28        120        530          6          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.50       0.69        120        530          6          10

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 89

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  COUNT STOPKEY (cr=530 r=120 w=21 time=284532 us)
     10   VIEW  (cr=530 r=120 w=21 time=284494 us)
     10    SORT ORDER BY STOPKEY (cr=530 r=120 w=21 time=284464 us)
    130     NESTED LOOPS  (cr=530 r=120 w=21 time=283692 us)
    130      NESTED LOOPS  (cr=138 r=119 w=21 time=270656 us)
      1       MERGE JOIN CARTESIAN (cr=4 r=1 w=0 time=11295 us)
      1        TABLE ACCESS BY INDEX ROWID GEOLAYER (cr=2 r=0 w=0 time=89 us)
      1         INDEX RANGE SCAN GEOLAYER_LAYERNAME_IDX (cr=1 r=0 w=0 time=52
us)(object id 55930)
      1        BUFFER SORT (cr=2 r=1 w=0 time=11101 us)
      1         TABLE ACCESS BY INDEX ROWID COUNTY (cr=2 r=1 w=0 time=11002 us)
      1          INDEX RANGE SCAN COUNTY_GEONAME_IDX (cr=1 r=1 w=0 time=10963
us)(object id 55401)
    130       TABLE ACCESS BY INDEX ROWID TOWNLAND (cr=134 r=118 w=21
time=258993 us)
    130        BITMAP CONVERSION TO ROWIDS (cr=26 r=40 w=21 time=138838 us)
      1         BITMAP AND  (cr=26 r=40 w=21 time=138608 us)
      1          BITMAP CONVERSION FROM ROWIDS (cr=5 r=0 w=0 time=2838 us)
   1118           INDEX RANGE SCAN TOWNLAND_COUNTYID_IDX (cr=5 r=0 w=0 time=1601
us)(object id 55416)
      1          BITMAP CONVERSION FROM ROWIDS (cr=21 r=40 w=21 time=135671 us)
   5011           SORT ORDER BY (cr=21 r=40 w=21 time=130355 us)
   5011            INDEX RANGE SCAN TOWNLAND_GEONAME_IDX (cr=21 r=19 w=0
time=78502 us)(object id 55399)
    130      TABLE ACCESS BY INDEX ROWID MINMAXEXTENT (cr=392 r=1 w=0 time=11953
us)
    130       INDEX UNIQUE SCAN MINMAXEXT_UK (cr=262 r=0 w=0 time=1625
us)(object id 55602)

********************************************************************************

SELECT a.* FROM
             ( SELECT /*+ first_rows */  a.*, ROWNUM RECORDINDEX FROM
             ( SELECT 'Townland Boundary' "FEATURENAME", mME.MinX, mME.MaxX,
mME.MinY, mME.MaxY,  gL.LayerName,
               gL.LayerAlias,  TOWNLAND.GEONAME COLUMN1 , COUNTY.GEONAME COLUMN2
              FROM COUNTY ,TOWNLAND , MinMaxExtent mME, GeoLayer gL
               WHERE COUNTY.GEONAME = 'L123'
                AND  TOWNLAND.GEONAME LIKE 'BALL%'
               AND TOWNLAND.COUNTYID = COUNTY.COUNTYID
                AND mME.ForeignID = TOWNLAND.GEOMETRYID
             AND  mME.TableName = 'TOWNLAND'
            AND gL.TableName = mME.TableName
             AND gL.LayerName = 'TOWNLAND'
           ORDER BY  TOWNLAND.GEONAME , COUNTY.GEONAME)
          a WHERE ROWNUM <= 10) a

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.02          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.56      12.38       3889      10413          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.59      12.41       3889      10413          0          10

Misses in library cache during parse: 1
Optimizer goal: FIRST_ROWS
Parsing user id: 89

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  VIEW  (cr=10413 r=3889 w=0 time=12388676 us)
     10   COUNT STOPKEY (cr=10413 r=3889 w=0 time=12388629 us)
     10    VIEW  (cr=10413 r=3889 w=0 time=12388592 us)
     10     SORT ORDER BY STOPKEY (cr=10413 r=3889 w=0 time=12388562 us)
    130      NESTED LOOPS  (cr=10413 r=3889 w=0 time=12387024 us)
    130       MERGE JOIN CARTESIAN (cr=10021 r=3888 w=0 time=12380531 us)
    130        NESTED LOOPS  (cr=10019 r=3888 w=0 time=12377287 us)
   5011         TABLE ACCESS BY INDEX ROWID TOWNLAND (cr=5006 r=3888 w=0
time=12252430 us)
   5011          INDEX RANGE SCAN TOWNLAND_GEONAME_IDX (cr=21 r=15 w=0
time=41073 us)(object id 55399)
    130         TABLE ACCESS BY INDEX ROWID COUNTY (cr=5013 r=0 w=0 time=88160
us)
   5011          INDEX UNIQUE SCAN COUNTY_UK (cr=2 r=0 w=0 time=28698 us)(object
id 55015)
    130        BUFFER SORT (cr=2 r=0 w=0 time=1745 us)
      1         TABLE ACCESS BY INDEX ROWID GEOLAYER (cr=2 r=0 w=0 time=38 us)
      1          INDEX RANGE SCAN GEOLAYER_LAYERNAME_IDX (cr=1 r=0 w=0 time=25
us)(object id 55930)
    130       TABLE ACCESS BY INDEX ROWID MINMAXEXTENT (cr=392 r=1 w=0 time=4649
us)
    130        INDEX UNIQUE SCAN MINMAXEXT_UK (cr=262 r=0 w=0 time=2697
us)(object id 55602)

********************************************************************************
December 18, 2003 Krishnan -- Thanks for the question regarding "Java Stored procedures from pl sql", version 8.1.6
You Asked
Hi Tom,
I am able to execute a small java program that takes in a string and return a
string as a pl/sql function.
However when I started to use the System.getProerty("line.separator')
in my java program, I get the following error. It looks like in oracle the jdk
policy permissions are to be set.

ORA-29532: Java call terminated by uncaught Java exception:
java.security.AccessControlException:
the Permission (java.util.PropertyPermission * read,write) has not been granted
by
dbms_java.grant_permission to
SchemaProtectionDomain(DBADMIN|PolicyTableProxy(DBADMIN))

What should be done? Any pointers would help.
Thanks

Krishnan
and we said...
Use DBMS_JAVA to grant yourself the privs you need:

begin
  dbms_java.grant_permission( 'thatusername',
                              'java.util.PropertyPermission',
                              '*',
                              'read,write' );
end;
/
Review & Followup

Rating: 5
Do not forget to use the java privileges instead of roles ! March 19, 2001
Reviewer: Andre Whittick Nasser from Rio de Janeiro, Brazil
Up to version 8.1.5, the user should use the JAVASYSPRIV (for system prvileges),
JAVAUSERPRIV (for user privileges) and JAVADEBUGPRIV (for debugging privileges)
roles. In 8.1.6, Oracle intruced Java 2 security, where direct privileges (like
above) are recommended instead of those roles.
Rating: 4
Java Stored procedures in PLSQl March 20, 2001
Reviewer: Krishnan Natesan from Stamford USA
Good pointers and prompt
Rating: 5
"Global" classes December 17, 2003
Reviewer: douglas
Tom, I'm sure this is easy for you...

I use pieces of Java code in difrent schemas. However, they all "import" the
same classes. How can I made these classes "public" or "global" (i.e. so anyone
can import them), without having to load them to each new schema ?

Thanks a lot
Followup:
not sure what you mean?

you aren't putting huge large scale applications written in java in the database
are you?
December 18, 2003 Nag -- Thanks for the question regarding "sql to get data from flat file-- this is mind blowing", version 8.1.7
You Asked
"In 9i, with the addition of external tables ( the ability to query a FLAT FILE
with SQL) -- SQLLDR might be "moot".  Using the external table and the insert
/*+ append */  I can basically do a direct path load from file to database
without running a command outside of the database."

Tom

We just cant wait, to see how the above is done, what are the new sql clauses we
need to use to achieve the above( there should be some new clauses to do the
above).

Kindly demonstrate the above, this feature will be trend setting and path
breaking.

Nag
and we said...
Ok, here is a demo I use for training internally:

For external tables, we need to use a directory object -- we'll
start with that, mapping to the temp directory

create or replace directory data_dir as 'c:\temp\'
/

Now, we'll create the external table.
part of its definition is what looks like a control file -- it is

create table external_table
(EMPNO NUMBER(4) ,
 ENAME VARCHAR2(10),
 JOB VARCHAR2(9),
 MGR NUMBER(4),
 HIREDATE DATE,
 SAL NUMBER(7, 2),
 COMM NUMBER(7, 2),
 DEPTNO NUMBER(2)
)
ORGANIZATION EXTERNAL
( type oracle_loader
  default directory data_dir
  access parameters
  ( fields terminated by ',' )
  location ('emp.dat')
)
/

In \temp\emp.dat I have a file that looks like this:

7369,SMITH,CLERK,7902,17-DEC-80,800,,20
7499,ALLEN,SALESMAN,7698,20-FEB-81,1600,300,30
7521,WARD,SALESMAN,7698,22-FEB-81,1250,500,30
....

(its a dump of the emp table in csv format)

Now you can just:

select * from external_table
/

and now, if you modified the scott/tiger EMP table:

delete from emp where mod(empno,2) = 1
/
update emp set sal = sal/2
/
commit;

You could sync up the flat file with the database table using this single
command:

merge into EMP e1
using EXTERNAL_TABLE e2
on ( e2.empno = e1.empno )
when matched then
    update set e1.sal = e2.sal
when not matched then
    insert ( empno, ename, job, mgr, hiredate, sal, comm, deptno )
    values ( e2.empno, e2.ename, e2.job, e2.mgr, e2.hiredate, e2.sal, e2.comm,
e2.deptno )
/
commit;

that'll update the records in the EMP table from the flat file if they exist OR
it will insert them.

Doing a direct path load would simply be:

insert /*+ append */ into emp select * from external_table;

See
http://download-west.oracle.com/otndoc/oracle9i/901_doc/server.901/a90192/part3.htm#436392
for the details.
Review & Followup

Rating: 5
This is amazing... October 15, 2001
Reviewer: AB from London
No more UPDATE ; IF SQL%NOTFOUND THEN INSERT...

9i seems to be able to do all those things that 8i ALMOST gave you.  And I'm
probably 18 months away from 9i in my place....... I'm going to be suffering !
Rating: 3
reading records from a diskette December 11, 2001
Reviewer: Richard from Ghana
Please Tom can you give me the codes to achieve this in 8i and before especially
7.3.2? please am a beginner
Thank You for the answer.
Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:322417112080
...
Rating: 5
Directory December 12, 2001
Reviewer: Sa?a
Could you please confirm that a directory should exist on server machine or it
could be on client machine (doubt so)?
Followup:
the directory is on the server, the dedecated/shared server that processes your
sql statements is the one that will be doing the reading, the file must be
accessible to it.
Rating: 3
Multiple files September 19, 2002
Reviewer: Rob from Pittsburgh, PA
Tom:

In our shop we tend to use sqlldr to load multiple files (hundreds each month)
all with the same format to a staging table. We tend to do this by having all of
the files we want to load in a directory and calling a shell script that loops
through all of the files calling sqlldr and passing the file name to sqlldr as
part of the command line. With external tables it seems like the file name is
hard coded into the definition of the table i.e. location ('emp.dat'). Is there
any way to use external tables in our scenario with multiple files?

Thanks.
Followup:
UTL_FILE in 9iR2 can RENAME a file.  So, the external table could be based on
"current_file_to_process.dat" and you would frename a file to be "current".
Rating: 5
broke link needs fixing? December 10, 2002
Reviewer: Eric D. Pierce from slightly to the right of the left coast, USA

http://download-west.oracle.com/otndoc/oracle9i/901_doc/server.901/a90192/part3.htm#436392
found at:
http://asktom.oracle.com/pls/ask/f?p=4950:8:1707778::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1615330671789,%7Bflat%7D%20and%20%7Bfile%7D
Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96524/c21dlins.htm#10778
Rating: 3
Can I NULL the WHEN NOT MATCHED? January 15, 2003
Reviewer: AJ Allen from Motown
I would like to try using the merge to do a table-to-table update.  I do not
want any action when there is no match.  I tried leaving the WHEN NOT MATCHED
clause off -- no dice.  Same with WHEN NOT MATCHED NULL.  Is there a way to do
nothing when not matched?
Followup:
you do not need a MERGE then -- a simple update of a join will do it.
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:273215737113

Rating: 5
Why the two step process? February 19, 2003
Reviewer: Kashif from Houston, TX
Hi Tom,

Thanks for that excellent explanation of two important new features in 9i.
Followup question: If I can use the external table directly in my application,
is there any reason not to do that, e.g. performance considerations? Why the
need to create the external table (external_table), and then merge it into
another table (emp)? I'm assuming you do that because the emp table is already
being used inside the application. Which begs the question, why not recreate
that pre-existing emp table as an external table, if one has the liberty to do
so?

Thanks in advance.

Kashif
Followup:
No indexes on external tables.

No insert/update/delete on external tables.

No backup / recovery on external tables.

External tables are just there to get the data in the database so you can make
use of it.  I would not be querying them day to day like a "real" table.
Rating: 4
Thank GOD!! February 19, 2003
Reviewer: Patrick from Denver, Co
Thanks Tom for the example! This is a biggy for me!
Rating: 5
Hint on merge ? February 21, 2003
Reviewer: Michel Cadot from France
Tom,

Can we use the PARALLEL hint in the ON and INSERT clauses and the APPEND hint in
the INSERT clause of the MERGE statement?

We want to use this to synchronize flat file and database table.

Thanks
Followup:
you can enable parallel dml and do the merge in parallel.

no "appending" though -- the insert will be a conventional insert.
Rating: 4
External tables rollbacks all the records even if one insert fails? June 08, 2003
Reviewer: A reader
Tom,

I came across an article by Jonathan Gennick
http://gennick.com/ETLandBad.html
)... i am surprised (since i
am new to ext tables) to know that oracle rollback all the records even if any
one of the insert/update fails.

Is that so?
Any work around there to maintain those valid data in the inserted table?

Thanks for your time
Followup:
A statement is ATOMIC.

so, if you

insert into t select * from external_table;

and some rule is violated on table T, then yes, of course the insert statement
must fail and fail entirely.

Now, external tables will have to some degree validated the data -- at least as
far as SIZE and DATATYPE is concerned.  Here, the records that are
inappropriate, would be recorded in a BAD file.  Others would be passed through
(all dependning on the number of errors you specified to be allowed when you
created the table).

But an external table behaves transactionally NO DIFFERENT then any other table
does.
Rating: 5
June 08, 2003
Reviewer: A reader
Rating: 5
Receive err when col width exceeds 255 ? June 09, 2003
Reviewer: C.P. Bhattarai from Nepal
Tom,

The following error is returned when JOBS_DONE col contains more than 255 (it
may be upto 1000 chars).
external table def is :
drop table external_VHistory;

create table external_VHistory
    ( CHASSIS      VARCHAR2 (15),
      JCARDNO      NUMBER (7),
      KM           NUMBER (8),
      SERV_TYPE    VARCHAR2 (2),
      FREE         VARCHAR2 (1),
      PROB_DESC    VARCHAR2 (500),
      JOBS_DONE    VARCHAR2 (1000),
      JOPEN_DATE   DATE,
      MEMBER_ID    VARCHAR2 (15)
    )
    ORGANIZATION EXTERNAL
    ( type oracle_loader
      default directory data_dir
      access parameters
     ( fields terminated by '|'
        missing field values are null
(chassis, jcardno, km,serv_type, free, prob_desc,jobs_done , jopen_date char
date_format date mask "dd/mm/yyyy", member_id ))
     location ('VHistory.ocp')
   )
   /

*** error message *************
Field Definitions for table EXTERNAL_VHISTORY
  Record format DELIMITED BY NEWLINE
  Data in file has same endianness as the platform

  Fields in Data Source:

    CHASSIS                         CHAR (255)
      Terminated by "|"
      Trim whitespace same as SQL Loader
    JCARDNO                         CHAR (255)
      Terminated by "|"
      Trim whitespace same as SQL Loader
    KM                              CHAR (255)
      Terminated by "|"
      Trim whitespace same as SQL Loader
    SERV_TYPE                       CHAR (255)
      Terminated by "|"
      Trim whitespace same as SQL Loader
    FREE                            CHAR (255)
      Terminated by "|"
      Trim whitespace same as SQL Loader
    PROB_DESC                       CHAR (255)
      Terminated by "|"
      Trim whitespace same as SQL Loader
    JOBS_DONE                       CHAR (255)
      Terminated by "|"
      Trim whitespace same as SQL Loader
    JOPEN_DATE                      CHAR (10)
      Date datatype DATE, date mask dd/mm/yyyy
      Terminated by "|"
      Trim whitespace same as SQL Loader
    MEMBER_ID                       CHAR (255)
      Terminated by "|"
      Trim whitespace same as SQL Loader
KUP-04021: field formatting error for field JOBS_DONE
KUP-04026: field too long for datatype
KUP-04101: record 2 rejected in file c:\ws\import\VHistory.ocp
Followup:
 ( fields terminated by '|'
   missing field values are null
(chassis,
 jcardno,
 km,
 serv_type,
 free,
 prob_desc,
 jobs_done CHAR(1000),
 jopen_date char date_format date mask "dd/mm/yyyy",
 member_id ))
location ('VHistory.ocp')
)
Rating: 4
Your are right June 10, 2003
Reviewer: C.P. Bhattarai from Kathmandu, Nepal
Exactly it works.
Actually it was triying with VARCHAR2(1000).
Rating: 5
TOM U ARE "GOD".. June 26, 2003
Reviewer: Palash Sarkar from Pune, India
Why don't u come up with a poster of urs with Oracle9i Data Dictionary in the
background ??
Followup:
that would be scary.
Rating: 5
Valid counts from External tables June 30, 2003
Reviewer: Raghu Raman from PA, USA
Dear Tom,

We are currently using sqlloader to load the staging tables and sending a load
reconciliation report with the following format:

DATA FILE       FILE CNT  FILE SUM STAGE TABLE STAGE CNT REJECTED
-------------- --------- --------- ----------- --------- --------
DATAFILE11.TXT      5417     14026 STG_TABLE1      14026        0
DATAFILE12.TXT      2879
DATAFILE13.TXT      5730

DATAFILE21.TXT      5687      5687 STG_TABLE2       5687        0

DATAFILE31.TXT      4000      4000 STG_TABLE3       3000     1000

Going forward we would like to use external tables that would give us the
ability to process the data without loading it into staging tables.

I have created an external table for testing purpose as shown below:

create table STG_TABLE3 (
   Field1      Number,
   Field2      varchar2(10),
   Field3      varchar2(50),
   Field4      varchar2(10),
   Field5      varchar2(10),
   Field6      varchar2(25),
   Field7      varchar2(25)
)
organization external (
   type oracle_loader
   default directory datadir
   access parameters  (
      records delimited by newline skip 1
      badfile 'STG_TABLE3.bad'
      logfile 'STG_TABLE3.log'
      fields terminated by ',' optionally enclosed by '"' LRTRIM
   )
   location('DATAFILE31.csv')
)
reject limit unlimited
/

When I do a count on the external table it gives the actual count from the
datafile including the records that are supposed to be rejected. Although it
creates the log file it does not create the bad file.

SQL> select count(*) from stg_table3;

  COUNT(*)
----------
      4000

However, when I specify a column for count it creates a bad file with the
records that are violating the definition of that specific column.

SQL> select count(field2) from stg_table3;

COUNT(FIELD2)
-------------
         3800

Contents of bad file
--------------------

error processing column FIELD2 in row 2 for datafile I:\data\DATAFILE31.csv
ORA-01401: inserted value too large for column
...

When I use the following SQL it is gives the actual count of what I am expecting
and also creates the bad file with all rejected records.

SQL> select count(*) from (
  2    select * from stg_table3
  3    union
  4    select * from stg_table3 where 1=2
  5  );

  COUNT(*)
----------
      3000

Here is an another example:

SQL> select count(*) from stg_table3;

COUNT(*)
----------
4000

SQL> create table test_table3 as select * from stg_table3;

Table created.

SQL> select count(*) from test_table3;

COUNT(*)
----------
3000

So executing a count(*) from the staging table does not validate the records in
the datafile and consequently not generating the bad file with rejected records.
However, if we do a union or create table as select * from the external table it
gives the correct result set and also creates the bad file with rejected
records.

Is this the way it supposed to work or am I missing something?

Regards
Followup:
it is supposed to work that way - the rejects were not rejected.

You might want to

a) map the "bad" file as yet another external table
b) count(*) it
c) do the load
d) count(*) it again and subtract

that is how many records did not get loaded.

so, if you try to load a, b, c from the external table -- but d would have
failed, you'll get a different count then if you load a,b,c,d
Rating: 4
June 30, 2003
Reviewer: Vipin from New york
Hi Tom,

As you said in this article external tables doesn't support
insert/updates/deletes on them. so we r left with only MERGE statement to deal
with it. suppose my data loading logic involved , loading some data into the
staging table (external table)and then applying some logic over it to reject
certain data, which might involve deleting them from the staging table (external
table)and then finally loading the actual table.

Here EXTERNAL table prooves unworthy for me since no DELETEs are allowed.

Or else if I find a round about way for this by updating the rejectable records
in the staging table (external table)with a flag and then using this table to
load my actual table, still EXTERNAL TABLE concept won't help me since it will
not allow to update.

So I think these are situations when SQLLOADER comes for my help.

I think Oracle might support INSERT/UPDATE/DELETE on external tables in
future.Right tom?
Followup:
"unworthy"?

rejected rows goto "bad_table"
accepted rows goto "good_table"

now are they "worthy"?  heck, I just let the external table reject the rows into
a BAD file for me.

why load the entire file into a staging table and then put all good records into
another table (deleting from stage) when you can just

insert good
insert bad <maybe, I don't even see why this is needed if you have a bad file>

you will not see insert/update/delete on external tables (short of "vi" of
course, just edit the thing)
Rating: 5
Valid counts from External tables July 01, 2003
Reviewer: Raghu Raman from PA, USA
I really appreciate for your qucik reply.
Rating: 5
Response to Vipin, July 01, 2003
Reviewer: Martin Burbridge from MA
The external table isn't meant to equate or replace the stage table in the
data loading process it replaces the input data file and SQL*Loader. The
stage table, if needed, is always a table in the database.

Flat File |-- SQL*Loader --> Stage Table |----- ETL  ----> Live Table
                                           (SQL, PL/SQL)

External  |-- SQL Insert --> Stage Table |----- ETL  ----> Live Table
Table                                      (SQL, PL/SQL)

Oracle doesn't need to support modifications to the external table, just
as you dont delete or modify records in the input data file using SQL*Loader.
Followup:
I would propose that with external tables -- you seriously consider that stage
table to be obsolete, not necessary, just something to slow you down -- whenever
possible.

Best to load straight from it.

External Table -> insert/merge, using pipelined functions if needed -> live.
Rating: 4
Martin.. the purpose of an external table is defeated if used that way July 01, 2003
Reviewer: A reader
Martin,
  I feel the External table was designed as a replacement for the SQL*Loader
step while loading data. if you are again going to insert that data into a
staging table the purpose of an external table is defeated. i suppose oracle
will support DML on external tables in future releases.. they work great already
:)
Rating: 5
I don't see any defeat July 01, 2003
Reviewer: A reader
I guess I should have emphasized the stage table is optional part ('if needed').
In the second flow using the external table there is no SQL*Loader, so I don't
see how you can say it defeats the purpose of replacing it. Seems to have done a
good job there.

My main point was you cannot reasonably say I must still use SQL*Loader until I
can write to external tables, when SQL*Loader does not write to the input data
file. If you want to start modifying the data it should be in the database, I
still see no reason to support modifications of external tables, I don't think
it looks like a good idea.
Rating: 5
I don't see any defeat July 01, 2003
Reviewer: Martin Burbridge from MA
I guess I should have emphasized the stage table is optional part ('if needed').
In the second flow using the external table there is no SQL*Loader, so I don't
see how you can say it defeats the purpose of replacing it. Seems to have done a
good job there.

My main point was you cannot reasonably say I must still use SQL*Loader until I
can write to external tables, when SQL*Loader does not write to the input data
file. If you want to start modifying the data it should be in the database, I
still see no reason to support modifications of external tables, I don't think
it looks like a good idea.
Rating: 5
July 15, 2003
Reviewer: A reader
Great! I am tring to use the external table with TOm's example, but always got
errors. Am I missing something?
Here is the flat file:
"id","name","use_type","date_modified"
1,"Cash Issue","abuse","10/10/2002"
2,"Customer Service","abuse","10/10/2002"

Here is the script to create the external table:

create or replace directory external_dir as 'c:\data';

drop table test;
CREATE TABLE test (
       EXTERNAL_ID          number(2) ,
       NAME                  varchar2(64) ,
       USE_TYPE             VARCHAR2(32) ,
       DATE_MODIFIED        date
)
    organization external
    (
        type oracle_loader
        default directory external_dir
       access parameters
       (
           records delimited by newline skip 1
           fields terminated by ','
       ENCLOSED BY '"' and '"'
           LRTRIM
    )
              location ('test.txt')
   );
I always getting the following error:

select * from test;
ERROR at line 1:
ORA-29913: error in executing ODCIEXTTABLEFETCH callout
ORA-30653: reject limit reached
ORA-06512: at "SYS.ORACLE_LOADER", line 14
ORA-06512: at line 1
Followup:
two things

1) fields appear to be OPTIONALLY enclosed, not enclosed by "
2) your date mask is missing

       access parameters
       (
           records delimited by newline skip 1
           fields terminated by ','
           OPTIONALLY ENCLOSED BY '"' and '"'
           LRTRIM
           ( external_id, name, use_type, date_modified DATE "dd/mm/yyyy" )
    )
              location ('test.txt')
   );
Rating: 5
external table July 15, 2003
Reviewer: Judy from Boston, MA,USA
Great! I am tring to use the external table with TOm's example, but always got
errors. Am I missing something?
Here is the flat file:
"id","name","use_type","date_modified"
1,"Cash Issue","abuse","10/10/2002"
2,"Customer Service","abuse","10/10/2002"

Here is the script to create the external table:

create or replace directory external_dir as 'c:\data';

drop table test;
CREATE TABLE test (
       EXTERNAL_ID          number(2) ,
       NAME                  varchar2(64) ,
       USE_TYPE             VARCHAR2(32) ,
       DATE_MODIFIED        date
)
    organization external
    (
        type oracle_loader
        default directory external_dir
       access parameters
       (
           records delimited by newline skip 1
           fields terminated by ','
       ENCLOSED BY '"' and '"'
           LRTRIM
    )
              location ('test.txt')
   );
I always getting the following error:

select * from test;
ERROR at line 1:
ORA-29913: error in executing ODCIEXTTABLEFETCH callout
ORA-30653: reject limit reached
ORA-06512: at "SYS.ORACLE_LOADER", line 14
ORA-06512: at line 1
Rating: 5
SQL TO GET DATA FROM FLAT FILE July 31, 2003
Reviewer: Arun from Atlanta,USA
Excellent.  This is awesome.  Thank you very much.
Rating: 4
Can we get the filename? September 15, 2003
Reviewer: Steve from The back of beyond!
Hi Tom
We load into one external table using several (>50) files - so the location is
(1_dir:abc.txt, 2_dir: cde.txt, etc etc).  My question is, can we tell which row
in the external table came from which file?
Followup:
no, you cannot, they are treated as a virtual "single file"
Rating: 3
Keepdown the log file EXTERNAL_TABLE...... September 15, 2003
Reviewer: Hector Gabriel Ulloa Ligarius from Santiago of Chile
Hi Tom ...

 How i suppress the generation of log file for the External table ....

 regards

Hector Ulloa
Followup:
use nologfile option in the "records" clause
Rating: 5
Excellent most helpful September 26, 2003
Reviewer: Kandan Kanakaraj from New York
Rating: 5
Null Date September 30, 2003
Reviewer: Anuj from Arkansas ,USA
Tom! Your explanation was very helpful as always. Well i was trying to load the
date field using Date mask (FIRST_CHG_DATE   DATE "mm/dd/yyyy") and all other
access parameters like 'missing field VALUES are NULL' but in the data set i
have one date field "FIRST_CHG_DATE" which has all null date values .It Creates
table but when i query it throws error when i look at the log it says 'error in
processing column FIRST_CHG_DATE ORA-01843:not a valid month'.Any Ideas....

Thanks!
Followup:
use char for everything and see whats actually coming in to each field (use
varchar2(4000) in the table create, just dump the data)
Rating: 5
TOM U ARE "GOD" October 01, 2003
Reviewer: Neil from UK
Nah - everyone knows Clapton is... *_-
Rating: 5
external table November 11, 2003
Reviewer: karma from MA USA
my external file has header and trailer record and i can skip header by using
skip 1 but is there a way to skip/ignore last or trailer record?
Followup:
the trail should error out right?  it won't be loadable -- so if you set errors
to one and then just check the bad file, it should be in there.
Rating: 5
Dynamic creation of external table December 02, 2003
Reviewer: Chneih from USA
Tom,

The filename of our extract file, which contains 3 million records, is
datestamped everyday(example:  EMP20030101.txt EMP20030102.txt). In order to
load this via external table, which approach is better.

Approach 1: Dynamically recreating the external table everytime the filename
changes? Or

Approach 2: Renaming the extract file's name to the filename setuped for the
external table like EMP.txt?

What are the pros and cons of approach 1 as against to approach 2?

Thanks,
Chneih
Followup:
to do this "every day" -- doing the DDL with the table name (via execute
immediate) would be OK - not a problem.

i would be OK with either approach.  the DDL create might be "safer" as the
rename would require an erase of data -- data that you might need later.
Rating: 3
Speed Up Updates from External Tables? December 03, 2003
Reviewer: Robin H from Winnipeg, MB Canada
We are using external tables to load data.  The problem is that we have an
external table with 75974 rows and a table in oracle with 173130 rows.  When we
update a selected group of rows

Update Oracle_table ot
Set birth_date = ( select et.birth_date
                    from ext_table et
                   where et.birth_date is not null
                         and ot.id=et.id)
where ot.mem_birth >= '01-Jan-2000'      <=== only 3000 of 170000 records
updated

we find that the update takes a VERY long time.  In fact, I've waited hours for
it to return a result to analyze in tkprof.

If I run the same update on an staging table of the external table it runs in
less than 2 seconds.

The Oracle_table is analyze and indexes with Primary key on ID.

Is there any way to speed up this process?  Using External tables is useful, but
I'm having difficultly using them for updates (inserts seem very fast).
Followup:
use MERGE.  watch this:

ops$tkyte@ORA9IR2> create table external_table
  2  (EMPNO NUMBER(10) ,
  3   ENAME VARCHAR2(10),
  4   JOB VARCHAR2(9),
  5   MGR NUMBER(4),
  6   HIREDATE DATE,
  7   SAL NUMBER(7, 2),
  8   COMM NUMBER(7, 2),
  9   DEPTNO NUMBER(2)
 10  )
 11  ORGANIZATION EXTERNAL
 12  ( type oracle_loader
 13    default directory data_dir
 14    access parameters
 15    ( fields terminated by ',' )
 16    location ('emp.dat')
 17  )
 18  /

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> !wc /tmp/emp.dat
  75974   75974 4725204 /tmp/emp.dat

ops$tkyte@ORA9IR2> select count(*) from external_table;

  COUNT(*)
----------
     75974

I'm going to do lots more then you do -- but, that external table has the same
number of rows as you have...

ops$tkyte@ORA9IR2> select count(*) from emp2;

  COUNT(*)
----------
    227922

we'll update emp2 from external table

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select empno, ename, hiredate from external_table
  2  where rownum <= 5
  3  /

     EMPNO ENAME      HIREDATE
---------- ---------- ---------
         1 iaSuDambZr 03-JUL-04
         2 wshxtfJPaX 05-FEB-06
         3 OcvujPmlXf 26-SEP-04
         4 SAfRewaoZt 20-JUN-05
         5 mYfXtNuPgR 26-JUN-06

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> set linesize 70
ops$tkyte@ORA9IR2> desc emp2
 Name                                Null?    Type
 ----------------------------------- -------- ------------------------
 EMPNO                               NOT NULL NUMBER primary key....
 ENAME                                        VARCHAR2(10)
 JOB                                          VARCHAR2(9)
 MGR                                          NUMBER(4)
 HIREDATE                                     DATE
 SAL                                          NUMBER(7,2)
 COMM                                         NUMBER(7,2)
 DEPTNO                                       NUMBER(2)

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> set timing on
ops$tkyte@ORA9IR2> merge into EMP2 e1
  2  using (select * from external_table where hiredate is not null and empno in
(select empno from emp2) ) e2
  3  on ( e2.empno = e1.empno )
  4  when matched then
  5          update set e1.hiredate = e2.hiredate
  6  when not matched then
  7          insert ( empno ) values (NULL)
  8  /

75974 rows merged.

Elapsed: 00:00:03.18
ops$tkyte@ORA9IR2> set timing off

3.18 seconds....  on a laptop....

we put the "date is not null" in the inline view AND used a subquery to make
sure the rows coming from the external table would be used to update a row (eg:
the INSERT part *never* happens)...

ops$tkyte@ORA9IR2> commit;

Commit complete.
Rating: 5
utl_file December 03, 2003
Reviewer: Mariano from Cordoba, Argentina
Tom,
excellent as always.
Is there something we can use as utl_file but on client side files?
regards.-
Followup:
you need to run a client tool.  what is on "the client"

(eg: sqlplus can create and write to files...., your VB program can, etc )
Rating: 5
Amazing December 03, 2003
Reviewer: Robin H from Winnipeg MB, Canada
Using the merge as described, the merge was faster 1.03 secs versus 2.31 secs
with an update.

Thank you
Rating: 3
error while using it December 17, 2003
Reviewer: Eugene from Seattle
Tom,
I am trying to use external table, but getting an error:

File format:
81,847W,Mens/Boys Woven Other Fiber Pants -
6203.49.8045,0,CHENDRIC,13-Jul-00,DFERGUSO,29-Jan-02,5
81,847W1,Mens/Boys Woven Other Fiber Shorts -
6203.49.8060,0,CHENDRIC,13-Jul-00,DFERGUSO,29-Jan-02,3
81,847W2,Womens/Girls Woven Other Fiber Pants
-6204.69.9044,0,CHENDRIC,13-Jul-00,DFERGUSO,29-Jan-02,3
81,847W3,Womens/Girls Woven Other Fiber Shorts -
6204.69.9046,0,CHENDRIC,13-Jul-00,DFERGUSO,29-Jan-02,3
81,859K,Mens/Boys Knit Other Fiber Vests -
6110.90.9064,0,CHENDRIC,13-Jul-00,DFERGUSO,29-Jan-02,4

When I open the file in Excel, then I can see all the dates in it. I don't
understand the error message.
Please help.

Steps taken:
-------------
create or replace directory data_dir as 'c:\temp\db_files';

create table hts_ext_table
(
  BUSINESS_UNIT_ID  VARCHAR2(2),
  HTS_CODE          VARCHAR2(13),
  HTS_DESCRIPTION   VARCHAR2(150),
  HTS_STATUS        VARCHAR2(1),
  HTS_CREATED_BY    VARCHAR2(30),
  HTS_CREATED_DATE  DATE,
  HTS_MOD_BY        VARCHAR2(30),
  HTS_MOD_DATE      DATE,
  HTS_LOCK_COUNTER  NUMBER(10)
)
ORGANIZATION EXTERNAL
( type oracle_loader
  default directory data_dir
  access parameters
  ( fields terminated by ','
    OPTIONALLY ENCLOSED BY '"' and '"'
    LRTRIM (BUSINESS_UNIT_ID,HTS_CODE,HTS_DESCRIPTION,HTS_STATUS,HTS_CREATED_BY,
            HTS_CREATED_BY date "dd/mm/yy",HTS_MOD_BY,HTS_MOD_DATE date
"dd/mm/yy",HTS_LOCK_COUNTER))
  location ('hts.csv')
);

select *
from hts_ext_table;

ORA-29913: error in executing ODCIEXTTABLEOPEN callout
ORA-29400: data cartridge error
KUP-04043: table column not found in external source: HTS_CREATED_DATE
ORA-06512: at "SYS.ORACLE_LOADER", line 14
ORA-06512: at line 1
Followup:
you have hts_created_by in there TWICE in the list of columns

   LRTRIM
(BUSINESS_UNIT_ID,HTS_CODE,HTS_DESCRIPTION,HTS_STATUS,HTS_CREATED_BY,
            HTS_CREATED_BY date "dd/mm/yy",HTS_MOD_BY,HTS_MOD_DATE date
"dd/mm/yy",HTS_LOCK_COUNTER))

so it is saying "hey, i cannot find where this hts_created_date column is!!!!"

when you fix that you'll get:

select * from hts_ext_table
*
ERROR at line 1:
ORA-29913: error in executing ODCIEXTTABLEFETCH callout
ORA-30653: reject limit reached
ORA-06512: at "SYS.ORACLE_LOADER", line 14
ORA-06512: at line 1

because your dates obviously do not match your format (that and you used 2
characters for a field that takes AT LEAST 4 CHARACTERS TO RERPRESENT.  remember
1999 and what we all did then???????  i beg you -- use 4 characters for years,
you'll never be sorry you did, you'll only be sorry if you DON'T!!!)

use this:

    (BUSINESS_UNIT_ID,
     HTS_CODE,
     HTS_DESCRIPTION,
     HTS_STATUS,
     HTS_CREATED_BY,
     HTS_CREATED_DATE date "dd-mon-rr",
     HTS_MOD_BY,
     HTS_MOD_DATE date "dd-mon-rr",
     HTS_LOCK_COUNTER))
December 18, 2003 Suren -- Thanks for the question regarding "Idle Oracle Server Processes", version 8.1.7.2
You Asked
Greetings Tom,

I have come across the following scenario. I would appreciate if you can give a
detailed explanation of what exactly is happening.

We have requests from 5 different webservers coming into a single database which
uses both JDBC & ODBC.The Web connections use one common username/password to
access the database. Every night we have a load process which runs against the
database during which the application team doesn`t want any requests coming to
the database. So what they do is lock the user by changing the password.

Sometimes a number of users try to feed in data during this loading time. Now
each time a request from any of the Webserver comes to the database during the
loading process, a server process is created and just sits there in an idle
state.Each request might even create multiple processes.

The database is configured for a maximum processes of 100 in a dedicated server
mode. Atleast once in a month they reach the max processes for the database and
the load process fails because it is unable to create a new process when
required.The load process runs in four parallel sessions and goes to a maximum
of 15 process at any given time.

According to Oracle, a Server process is created only when a connection is
established. I reconfirmed this with the following shell script.

#!/bin/ksh
cmd1="/usr/local/oracle/product/8.1.7/bin/sqlplus"
cmd2="test1/testconn@DAD1"
count=0
echo " Script Started" >/var/tmp/xyz
while test $count -lt 10
do
let count=$count+1
echo $count
$cmd1 $cmd2 >>/var/tmp/xyz 2>&1 &
echo "$cmd1 $cmd2 --->step $count "
done
echo " Script Ended" >>/var/tmp/xyz

I could`nt see any server processes created for the above script, because the
connection was not established.

Here is my listener.ora file
################
# Filename......: listener.ora
# Name..........: test1.world
# Date..........: 13-AUG-02 15:14:25
################
SQLNET.AUTHENTICATION_SERVICES = (NONE)

USE_PLUG_AND_PLAY_LSNR234 = OFF
USE_CKPFILE_LSNR234 = OFF
LSNR234 =
  (ADDRESS_LIST =
 (ADDRESS=
   (PROTOCOL=IPC)
   (KEY= DAD1.world)
 )
 (ADDRESS=
   (PROTOCOL=IPC)
   (KEY= DAD1)
 )
(ADDRESS=
   (PROTOCOL=IPC)
   (KEY= DAD2.world)
 )
        (ADDRESS=
          (PROTOCOL=IPC)
          (KEY= DAD2)
        )
        (ADDRESS =
          (COMMUNITY = TCP.world)
          (PROTOCOL = TCP)
          (Host = TEST1)
          (Port = 1522)
        )
  )
STARTUP_WAIT_TIME_LSNR234 = 0
CONNECT_TIMEOUT_LSNR234 = 10
TRACE_LEVEL_LSNR234 = OFF
SID_LIST_LSNR234 =
  (SID_LIST =
    (SID_DESC =

      (GLOBAL_DBNAME = DAD1.world)
      (SID_NAME = DAD1)
      (ORACLE_HOME = /usr/local/oracle/product/8.1.7)
      (PRESPAWN_MAX = 10)
    )
    (SID_DESC =

      (GLOBAL_DBNAME = DAD2.world)
      (SID_NAME = DAD2)
      (ORACLE_HOME = /usr/local/oracle/product/8.1.7)
      (PRESPAWN_MAX = 10)
    )
  )

Here is my Sqlnet.ora

################
# Filename......: sqlnet.ora
# Name..........: TCP.world
# Date..........: 05-AUG-02 14:22:30
################
AUTOMATIC_IPC = ON
TRACE_LEVEL_CLIENT = OFF
SQLNET.EXPIRE_TIME = 10
NAMES.DEFAULT_DOMAIN = world
NAME.DEFAULT_ZONE = world
SQLNET.CRYPTO_SEED = "xyz"
BEQUEATH_DETACH=yes
~

I know this problem can be fixed by the application/web team by providing time
out sessions to the request.I suppose they are already working on it.

But my question is

# 1: "WHY" are the server processes created when requests to the
    database are coming from the Webserver, eventhough that particular
    user`s password is changed and there is no connection established
    to the database. ( Tom i would appreciate if you can give a
    detailed explanation for this question )

# 2: What would be the ideal way for a dba to restrict or clean up
     such idle server processes.

Thanks in advance,
Suren
and we said...
1) a login to Oracle is a two step process

a) physical connection -- sets up the network path from client to server.  A
process is created at this point.  You are not logged in but v$process has an
entry for you.

b) authentication -- sets up a session (and a single process, a single physical
conection can have many sessions in it).  Now an entry (or more then one if you
use more then one session) is made in v$session for you.

So, it sounds like you are getting connected but during the authenticate stage
-- they are failing and not closing the connection.  I'd be looking to the
client code to see if they cleanup after a failure (and make sure you are using
current jdbc drivers and such).

Now, using the 8174 jdbc THICK and THIN drivers, I was not able to reproduce
this.  It will be a function of the drivers you are using and how they were
coded.

I used:

import java.sql.*;
import oracle.jdbc.driver.*;
import oracle.sql.*;

class test
{

public static void main(String args[])throws Exception
{
    DriverManager.registerDriver
     (new oracle.jdbc.driver.OracleDriver());

    try
    {
      Connection conn=DriverManager.getConnection
       ("jdbc:oracle:oci8:@ora817dev","scott", "tigerx");
      //("jdbc:oracle:thin:@aria-dev:1521:ora817dev" ,"scott","tiger");
    }
    catch (SQLException e)
    {
        System.out.println(e.toString());
    }
    System.out.println("Sleeping 30 seconds" );
    java.lang.Thread.sleep( 30 * 1000 );
}

}

as a simple test program.  When connected -- process (i saw it with ps).  When
not connected, the process went away (i saw it come and go every now and then
with:

while(1)
ps -auxww | grep ora817dev | tee trace
end

(ora817dev was my sid).  If you run that with your drivers and the process
doesn't go away, it is a problem with that driver.  If you are using connection
pooling and other advanced features, make sure to use that in the test program.

Another way -- that would PRECLUDE these processes -- would be the simply
shutdown the listener.  Then, they have NO WAY to create a process (and you
don't have to do any DDL to alter the users password).
Review & Followup

Rating: 4
Idle Server Processes September 23, 2002
Reviewer: Suren from Milwaukee, WI
Tom,

1) Do you mean to say that a server(unix) process would be
   created even when the authentication fails and if the
   code doesn`t close the connection it would remain there
   sitting in an idle state. ???

2) If yes, how would the DCD( Dead Client Detection ) work
   for my scenario.

Thanks,
Suren
Followup:
1) in order to authenticate -- you need a process!  so yes, process created AND
THEN authentication.

The code *should not* have to close the connection (in fact, the connection
object doesn't even get returned to me for closing using the 8174 jdbc drivers
from oracle)

I cannot replicate your issue using our jdbc drivers -- can you?

2) it would not, that cleans up a session, you don't have a session.
Rating: 4
single process , multiple sessions ? September 24, 2002
Reviewer: Ram from India
Hello Tom,

This particular sentence is confusing - 'a process is created before
authentication and a session is established after being authenticated. a process
can have multiple sessions'.

how can a single process support multiple sessions ?
a client (lets say , sqlplus) gets connected to the server (for the very first
time ) - a session is established.

now, if i open another sqlplus and connect , will that be a separate session but
with the same process ? how ?
does this mean that the process part is client terminal driven ? that is, a
client terminal to the server is a single process where as client appls getting
connected to the server is multiple sessions.

i am missing something here.
Followup:
A single client program CAN if it wants, open more then one session.

SQLPlus does this when you set autotrace on for example.  The V$ queries are
executed in ANOTHER SESSION so as to not affect the V$ queries themselves.

If you fire up sqlplus two times tho -- you'll have two processes, two sessions.

If you are writing your OWN program, you can fire up multiple sessions on the
same physical connection -- if you want.
Rating: 3
Good to know December 17, 2003
Reviewer: Kees Oskam from The Netherlands
I was looking for situations where one process could have more than one session.
Well, this clearly answers it. Now I want to find out how PGA memory is
alloceted to processes versus sessions. That part is not yet clear to me.
Followup:
PGA is process memory -- meaning, allocated in a process.  Session memory may be
allocated in a processes PGA (dedicated server more) or in the SGA (shared
server mode).

Both modes interestingly allow a single process to have more then one session ;)
December 18, 2003 zhu -- Thanks for the question regarding "tkprof wrong result?", version 817
You Asked
From top result i noticed that there is an oracle server process consuming much
cpu, so i traced it and tkprofed it and i got the following result:
select user_account into :b0
from
UserPrivateInfo where user_id=:b1

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 5 0.02 0.00 0 0 0 0
Execute 30964 2.76 2.44 0 0 0 0
Fetch 30964 3.49 2.64 0 123865 0 30964
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 61933 6.27 5.08 0 123865 0 30964

Misses in library cache during parse: 1
Optimizer goal: FIRST_ROWS
Parsing user id: 32

Rows Row Source Operation
------- ---------------------------------------------------
1 TABLE ACCESS BY INDEX ROWID USERPRIVATEINFO
2 INDEX UNIQUE SCAN (object id 6593)
as you see, this is just a select statement, there should not be any execute
cpu, how now there is so much, as you see.
Can someone help me on this issue?

It is just during a about 5-minute's trace.It seems it is impossible that it
really ran this query for that 20k times!
Some possible reason? I asked it with developers, there is no loop statement
like that.

another question:
This is oracle 8.172 on solaris 7.
The tkprof is using oracle9i tkprof on another solaris machine.
the wait event is enabled via event 10046 level 8.
select JUDGE_ID into :b0
from
USERJUDGE where (((PRODUCT_ID=:b1 and AUCTION_ID=:b2) and ((AUCTION_TYPE='A'
and :b3=0) or (AUCTION_TYPE='F' and :b3=1))) and CRITIC_IDENTITY=1)

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.01 0.00 0 0 0 0
Execute 1 0.00 0.01 0 0 0 0
Fetch 1 1.83 1.85 769 14020 5 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 3 1.84 1.86 769 14020 5 1

Misses in library cache during parse: 1
Optimizer goal: FIRST_ROWS
Parsing user id: 32

Rows Row Source Operation
------- ---------------------------------------------------
1 TABLE ACCESS FULL USERJUDGE

Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
db file sequential read 608 0.01 0.04
db file scattered read 71 0.01 0.01
latch free 1 0.01 0.01
SQL*Net message to client 1 0.00 0.00
SQL*Net message from client 1 0.00 0.00

Since it is full table scan(i did not use explain, so this is the actual
execution path), why there is wait event on db file sequential read? and so much
time?

I asked it in dbforums, but nobody answer it.
So i have to refer to you for help,Thanks.

Third question:
statement like:

select * from (
select
ucm_msg_no,
row_numBER() over (order by ucm_msg_no) RN
from
usercs.UCM_USERCOMMENT_MAINTAIN
where
        ( ucm_status = 20
                 )
                and ucm_create_dtm > (sysdate - 7)
)
where rn>10    and rn<20
It works in sqlplus, but in pro*c, it failed to compile.
Can i use it in pro*c?How to use it?
Thanks again.
and we said...
tkprof execute time question

No bug here -- execute time applies for selects.  It is just that you typically
see big executes for inserts/updates/deletes (as they really don't have a fetch
phase)

I ran this just to show you:

declare
    l_dummy    dual.dummy%type;
begin
    for i in 1 .. 10000
    loop
        select dummy into l_dummy from dual;
    end loop;
end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      2.04       2.21          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      2.05       2.22          0          0          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 216  (OPS$TKYTE)
********************************************************************************

SELECT DUMMY
FROM
 DUAL

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute  10000      0.50       0.40          0          0          0           0
Fetch    10000      1.00       1.05          0      10000      40000       10000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    20001      1.51       1.45          0      10000      40000       10000

So, yes, there should and will be execute time there -- it is atypical of a
select into (single row fetch) to see this.  Normally, the fetch time is soooo
much larger then the execute time (because you fetch N rows/execute).

Now, as for the number of executions -- your programmers are incorrect in their
analysis.  Might not be in a loop -- but SOMEONE is executing that thing that
many time.  I assure you of that.

The tkprof is 100% correct here.  Your developers need to look a little closer
at the code surrounding this query.
----------------------------------------------------------------------

db file sequential read question

Well, it could be chained/migrated rows -- it could be normal as well.

I ran this test.  I created a table with columns X and C (relevant columns).  I
made sure one table had X and C on different blocks (chained).  I made sure on
the other table that X and C were on the same block.  Then I selected x from t
where c = value.  Here is the setup (8k blocksize):

ops$tkyte@ORA9I.WORLD> create table chained ( x int, a varchar2(4000), b
varchar2(4000), c char(1) );
Table created.

ops$tkyte@ORA9I.WORLD> insert into chained (x) select rownum from all_objects
where rownum <= 1000;
1000 rows created.

ops$tkyte@ORA9I.WORLD> commit;
Commit complete.

ops$tkyte@ORA9I.WORLD> update chained set a = rpad('*',4000,'*'), b =
rpad('*',4000,'*'), c = 'x';
1000 rows updated.

ops$tkyte@ORA9I.WORLD> commit;
Commit complete.

ops$tkyte@ORA9I.WORLD> drop table not_chained;
Table dropped.

ops$tkyte@ORA9I.WORLD> create table not_chained ( x int, c char(1), a
varchar2(4000), b varchar2(4000) );
Table created.

ops$tkyte@ORA9I.WORLD> insert into not_chained (x) select rownum from
all_objects where rownum <= 1000;
1000 rows created.

ops$tkyte@ORA9I.WORLD> commit;
Commit complete.

ops$tkyte@ORA9I.WORLD> update not_chained set a = rpad('*',4000,'*'), b =
rpad('*',4000,'*'), c = 'x';
1000 rows updated.

ops$tkyte@ORA9I.WORLD> commit;
Commit complete.

ops$tkyte@ORA9I.WORLD> REM force physical io, flush buffer cache
ops$tkyte@ORA9I.WORLD> alter tablespace users offline;
Tablespace altered.

ops$tkyte@ORA9I.WORLD> alter tablespace users online;
Tablespace altered.

ops$tkyte@ORA9I.WORLD> alter session set events '10046 trace name context
forever, level 12';
Session altered.

ran this with set termout off....
ops$tkyte@ORA9I.WORLD> prompt select y from CHAINED;
select y from CHAINED
ops$tkyte@ORA9I.WORLD> @mystat continued
ops$tkyte@ORA9I.WORLD> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  --and b.value > 0
  6  /
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('continued')||'%'

NAME                                VALUE
------------------------------ ----------
table fetch continued row               0

ops$tkyte@ORA9I.WORLD> set termout off
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> @mystat continued
ops$tkyte@ORA9I.WORLD> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  --and b.value > 0
  6  /
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('continued')||'%'

NAME                                VALUE
------------------------------ ----------
table fetch continued row            1000

that did 1000 "build me a row by getting yet another block" operations

ops$tkyte@ORA9I.WORLD> prompt select y from NOT_CHAINED;
select y from NOT_CHAINED
ops$tkyte@ORA9I.WORLD> set termout off
ops$tkyte@ORA9I.WORLD> @mystat continued
ops$tkyte@ORA9I.WORLD> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  --and b.value > 0
  6  /
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('continued')||'%'

NAME                                VALUE
------------------------------ ----------
table fetch continued row            1000

that incurred no such type of processing.  Now the 9i tkprof shows me:

select x
from
 CHAINED where c = 'x'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch       68      0.61      19.40       2075       4868          0        1000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       70      0.62      19.40       2075       4868          0        1000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 130

Rows     Row Source Operation
-------  ---------------------------------------------------
   1000  TABLE ACCESS FULL CHAINED

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                      68        0.00          0.00
  db file sequential read                       107        0.50          0.77
  db file scattered read                         47        1.02         10.45
  SQL*Net message from client                    68        0.02          0.12
  free buffer waits                              17        1.00          7.42
********************************************************************************

select x
from
 NOT_CHAINED where c = 'x'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch       68      0.62      18.99       1969       3866          0        1000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       70      0.62      18.99       1969       3866          0        1000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 130

Rows     Row Source Operation
-------  ---------------------------------------------------
   1000  TABLE ACCESS FULL NOT_CHAINED

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                      68        0.00          0.00
  db file sequential read                        10        0.44          0.58
  SQL*Net message from client                    68        0.04          0.16
  db file scattered read                         52        1.34          7.37
  free buffer waits                              23        1.00         10.27

Huge difference there.  In this case -- due to the chained rows.  When I went
and inspected the 10 waits for the "non chained" example, I discovered almost
all of them were for the first blocks of the table -- segment header.  The
others -- I mark them up to migrated rows.  My update would not only CHAIN some
rows in NOT_CHAINED (which I should have called something else ;) but MIGRATE
some as well...

---------------------------------------------------------------
analytic function question

It is the same issue plsql has -- the precompiler does not understand the
construct being used (fixed in 9i).  The work arounds are:

a) use a view
b) use dynamic sql

In this case however -- a better way to write that query would be:

select ucm_msg_no
  from ( select A.*, rownum rnum
           from ( select ucm_msg_no
                    from usercs.UCM_USERCOMMENT_MAINTAIN
                   where ucm_status = 20 and ucm_create_dtm > (sysdate - 7)
                   order by ucm_msg_no ) A
          where rownum < :MAX_ROW
       )
 where rnum > :MIN_ROW;

NOT that proc will like that "better".  The order by in the subquery will upset
it...

This demonstrates how to do what you need for now:

static void process( )
{
EXEC SQL BEGIN DECLARE SECTION;
int       min = 10;
int       max = 20;
VARCHAR   query[512];
VARCHAR   ename[30];
EXEC SQL END DECLARE SECTION;

    strcpy( query.arr, "select ename from  \n\
                          (select a.*, rownum rnum from  \n\
                            ( select ename  \n\
                                from scott.emp \n\
                                order by sal )a \n\
                           where rownum < :max ) \n\
                           where rnum > :min" );
    query.len = strlen(query.arr);
    printf( "%s\n", query.arr );

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();
    exec sql declare s statement;
    exec sql prepare s from :query;
    exec sql declare c cursor for s;

    exec sql open c using :max, :min;
    for(;;)
    {
        exec sql whenever notfound do break;
        exec sql fetch c into :ename;
        printf( "%.*s\n", ename.len, ename.arr );
    }
    exec sql whenever notfound continue;
    exec sql close c;
}
Review & Followup

Rating: 5
June 08, 2002
Reviewer: A reader
Great
Rating: 5
Total CPU greater than the elapsed CPU November 24, 2003
Reviewer: Arun Mathur from Marietta,GA
Tom,

Occasionally, I'll get a tkprof report where my CPU time exceeds the Elapsed
time. I was wondering if you can help me understand why this can occur?

Thanks. BTW, do you ever come by the Oracle offices in Atlanta?

Arun
Followup:
timing errors.

things are measured in 1/100ths or 1/1000000ths of a second.

each measurement can be off by that much each time -- if we had to do 5000
measurements (5000 fetches for example), the time can get out of whack by a tiny
bit.

typically -- this error is in the "noise" level (law of averages will have you
sometimes over contributing time and sometimes under contributing).  the elapsed
time was probably very very close to the cpu.

i'm scheduled to do the Atlanta OUG in March 2004
Rating: 5
Thanks November 24, 2003
Reviewer: Arun Mathur from Marietta,GA
The query I was focusing on originally had about 20 seconds total elapsed time
and 4 seconds CPU time.  For some reason, the timings went down to about 1.4
seconds, both elapsed and cpu, after the database server shutdown over the
weekend. I didn't change any database parameters, so I was wondering you've ever
come across a situation like this?

Hope to see you in Atlanta next March.

Arun
Followup:
you'd have to see what it was waiting for.  have i seen machines run faster
after a reboot?  sure -- cleans out "dead" stuff.  (have i seen things run
slower after a reboot -- sure -- cleans out good stuff too like the buffer
cache)
Rating: 5
CPU and elapsed timings during fetch December 17, 2003
Reviewer: Arun Mathur from Marietta,GA
Tom,

I noticed a large discrepancy between the CPU and elapsed time during a fetch
operation. The only waits I saw was the SQL*Net message from/to client, and
nothing else. Do you have any suggestions as to what other events I can check?

Thanks.
Arun
Followup:
is the machine doing lots of stuff?

large elapsed, with small CPU and no real waits (although the TO client is a
wait that counts here -- does that account for much of it?) can definitely mean
"we waited for that darn CPU"
Rating: 5
Tom in ATL December 18, 2003
Reviewer: Jeff from Atlanta, GA
That's going to rock! Look forward to seeing you there.
December 18, 2003 Tunde -- Thanks for the question regarding "Export", version 8.1.6
You Asked
Hi Tom,
How can I add sysdate + database name in a database export file
logfile?
Thanks in advance.
Greetings,
Tunde Akande
The Netherlands
and we said...
The way I do it is to use the shell script to echo some things
to the log file and then redirec the output of export to this
same log file.  For example, my export script is:

------------------------------------------------------------
#!/bin/csh -vx

setenv UID /
setenv FN  exp.`date +%j_%Y`.dmp
setenv PIPE /tmp/exp_tmp_ora8i.dmp

setenv MAXSIZE 500m
setenv EXPORT_WHAT "full=y COMPRESS=n buffer=2000000"

echo $FN

cd /nfs/atc-netapp1/expbkup_ora8i
ls -l

rm expbkup.log export.test exp.*.dmp* $PIPE
mknod $PIPE p

date > expbkup.log
( gzip < $PIPE ) | split -b $MAXSIZE - $FN. &
#split -b $MAXSIZE $PIPE $FN. &

exp userid=$UID file=$PIPE $EXPORT_WHAT >>& expbkup.log
date >> expbkup.log

date > export.test
cat `echo $FN.* | sort` | gunzip > $PIPE &
#cat `echo $FN.* | sort` > $PIPE &
imp userid=$UID file=$PIPE show=y full=y >>& export.test
date >> export.test

tail expbkup.log
tail export.test

ls -l
rm -f $PIPE
-------------------------------------------------------

so, it starts by

date > expbkup.log

and then export also writes to this log using redirection
(instead of the command line parameter).

You could have it echo the ORACLE_SID and ORACLE_HOME or
whatever you wanted before export started....
Review & Followup

Rating: 5
direct = y December 17, 2003
Reviewer: Shankar from Houston, TX
If i export in direct path mode, what do I loose or what I cannot do? As direct
parth export speeds up the process, why would someone use conventional path
export? I am trying to understand the implications if any in using direct path
export. Thanks for your time.
Followup:
in 815, lobs could not be direct path exported (they can now)

see also
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96652/ch01.htm#1011906
December 18, 2003 Jose -- Thanks for the question regarding "Fined Grained Access Control", version 8.7.1.2
You Asked
Tom,

I have implemented Fine Grained Access Control and it was quite easy to
implement.

However I have problem with 8 tables. The FGAC doesnot work on those tables,but
if I were to drop and recreate the table( I tested 2 tables) the functionality
works fine.

Is there a way I can find (from some sys tables/views) whether a policy exists
on a table and if so what is the policy?
What is the easiest way for a user to check whether a table has a policy (who
has no access to dba views)

I find that after implementing the VPD, we have a performance issue with certain
queries ( not using the index if VPD is implemented else uses the index) and
also the export/import takes more time to complete. (Rewriting query especially
a third party one is not practical.)

Any suggestions?
and we said...
user_policies
dba_policies
all_policies

have this information.

As for performance, all VPD does is rewrite the query. Lets say you have a
policy that returns:

    return 'x = 123';

and you attach this policy to a table T.  Now, all queries such as:

  select * from t;

will be rewritten as:

   select * from ( select * from t where x = 123 );

thats what the app will run, exp will run -- everything.  If that is not an
efficient query without VPD -- it'll be no more or less efficient WITH IT.  VPD
just runs the query as if you entered it yourself.  You need to use conventional
tuning methods (indexes and such -- you might need to add X to many indexes to
make it efficient again).
Review & Followup

Rating: 5
Improve export speed using schema SYS November 16, 2001
Reviewer: Georg Feinermann from Germany
You can improve the export speed using the schema SYS. SYS is the only user
who's free of policies.

This also guarantees that ALL rows are exported - this is probably important to
you :)
Followup:
Yes, using SYS or any user connected "as sysdba" will effectively bypass RLS.

and in 9i, you can grant the EXTREMELY powerful privilege EXEMPT ACCESS POLICY
to any DBA, giving the same bypass ability.
Rating: 4
Performance seems a problem. November 16, 2001
Reviewer: Jose from NJ USA
We had VPD implemented, the cpu usage seems higher and also the users have
longer waits.In general system was very slow.
We drop the VPD, the problem seems to vanish..
We have been trying to do trace/tkprof, elapsed time and what not to figure out
the cause of the problem.

Can someone shed a light on the issue or how to figure out the problem?
Thanks
Followup:
VPD does the same thing as if you created individual views.

If you have a predicate policy that returns:

  return 'x = 5'

that where clause will be added to your query, so a simple:

   select * from t

becomes

   select * from (select * from t where x=5);

Nothing more, nothing less.  If you run the queries as VPD would have made them
right now -- you'll get the SAME performce as you did when VPD was on.  You need
to look at your predicates and assure yourself that the proper indexes and
everything are in place.
Rating: 4
Parallel query and FGAC November 26, 2001
Reviewer: A reader
I read from documentation that these two are not compatible (PQ and FGAC) when
using Application context. Is there a way to avoid this?
Followup:
It is NOT a problem with FGAC, sys_context, and PARALLEL query.  Here is an
example that shows this works just fine (bigger_table is a table with 37+
million rows in it, empid is NOT null -- has values 1, 2, 3, .... 37+ million.
I used empid+0 to obviate the index I have on empid -- to help PQ get choosen
for sure)

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace context my_ctx using
my_ctx_proc
  2  /
Context created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure my_ctx_proc(
p_name in varchar2, p_val in varchar2 )
  2  as
  3  begin
  4      dbms_session.set_context( 'my_ctx', p_name, p_val );
  5  end;
  6  /
Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace function
my_security_function( p_schema in varchar2,
  2                   p_object in varchar2 )
  3  return varchar2
  4  as
  5  begin
  6      return 'empid+0 = to_number( sys_context( ''my_ctx'', ''empid'' ) )';
  7  end;
  8  /

Function created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2      begin
  3              dbms_rls.drop_policy( user, 'BIGGER_TABLE', 'MY_POLICY' );
  4      exception
  5              when others then null;
  6      end;
  7      dbms_rls.add_policy
  8      ( object_schema => user,
  9        object_name => 'BIGGER_TABLE',
 10        policy_name => 'MY_POLICY',
 11        function_schema => user,
 12        policy_function => 'MY_SECURITY_FUNCTION',
 13        statement_types => 'select, insert, update, delete',
 14        update_check => true );
 15  end;
 16  /
PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec my_ctx_proc( 'empid', '1' );
PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from bigger_table;

     EMPID     DEPTID
---------- ----------
         1          1

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

ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ FULL(bigger_table)
PARALLEL(bigger_table,8) */ * from bigger_table
  2  /

     EMPID     DEPTID
---------- ----------
         1          1

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2203 Card=62500 Bytes=1625000)
   1    0   TABLE ACCESS* (FULL) OF 'BIGGER_TABLE' (Cost=2203 Card=62500
Bytes=1625000)                        :Q32000

   1 PARALLEL_TO_SERIAL            SELECT /*+ Q32000 NO_EXPAND ROWID(A1) */ A1.
                                   "EMPID",A1."DEPTID" FROM "OPS$TKYTE"."BIGGER
                                   _TABLE" PX_GRANULE(0, BLOCK_RANGE, DYNAMIC)
                                    A1

Now, if the context values had not been passed down to the PQ slaves here, all
of the rows would have come back OR none of the rows would have come back.
Instead only THE right row came back.

Sys_context is treated like a bind variable in a query -- its value is BOUND in
just like any other value would be.  It is true the using SYS_CONTEXT in
FUNCTIONS in parallel query is not advisable (as it doesn't work), but in this
case the predicate policy function is called long before PQ kicks in -- it
rewrites the query which is then sent to the optimizer which then decided to use
PQ and rewrote the SYS_CONTEXT calls as bind variables.

So, it works just fine, security is NOT compromised -- you get the right answer
-- with PQ.
Rating: 4
Further Information February 06, 2003
Reviewer: John Gilmore from United Kingdom
To carry out performance analysis of SQL statements executing within FGAC, is it
possible to view the modified statement; that is, after the FGAC predicates have
been added?

Also, am I to understand that export should be performed while connected as the
sys user in FGAC environments?
Followup:
there are ways to get it but not really.  I use standard "development technique
101" to deal with that (printing of trace information using utl_file)

SYS or someone with the priv to by pass FGAC (new in 9i)
Rating: 3
Yet More Information February 07, 2003
Reviewer: John Gilmore from United Kingdom
Sorry, but you lost me there.  Could you be a bit more specific about "printing
of trace information using utl_file".

Also, I don't want to push the point, but can you direct me to some references
on methods for actually getting the final query text after the FGAC predicates
have been added?
Followup:
see
http://asktom.oracle.com/~tkyte/article2/index.html
I talk of a debug package I use (and the code is available there for download).
You just add "printf" like statements to your predicate policy.
Rating: 5
Can FGAC Restrict Columns as well as Rows? February 28, 2003
Reviewer: John Gilmore from United Kingdom
Using FGAC we can restrict the rows that a user sees depending on some predicate
which we define.  However, I want to restrict the values in a column to be
visible or not depending on a predicate.

For example, suppose in a world-wide manufacturing system users have
responsibilities according to which countries they control.

We might want to let them see parts made anywhere in the world but only see
pricing information for parts manufactured in the countries for which they are
responsible.  So the price column would be populated only for countries they
control and null otherwise.

How would you go about implementing such a scheme within FGAC?
Followup:
You cannot currently use FGAC to implement column wise security.

You would either

o vertically partition the data (split into separate tables)

o consider encrypting the data with different keys/role.  No key, no see.

o use a view

select ...., decode( f(x,y,z), 1, price, null ) price, .....

where f(x,y,z) is some function you write that looks at the values in the row
and decides -- can they see it or not?

It would be most optimal if f() could be "inlined" via a CASE statement rather
then written in PLSQL (faster)
Rating: 5
Using Context Variables with the View March 05, 2003
Reviewer: John Gilmore from United Kingdom
Thanks for your ideas Tom.  My plan had actually been to use a view with the
"price" column wrapped in a decode statement with a function.

When you talk of an optimal solution being to "inline" the function I assume you
mean replacing the function by a case statement, which would be possible only if
all the data necessary to determine access rights was already contained in the
view columns.  Correct?

Unfortunately this is not really possible as this information is stored in
another table and there may be multiple rows per user, only one of which will be
relevant depending on the contents of the view row.

So rather than using a function, which would involve accessing the other table
for each row in the view, I was thinking of using context variables to indicate
access rights and accessing these in the case statement instead.

That way, the other table is accessed once only in the logon trigger and the
context variable(s) set.  I expect that accessing the context variable(s) in the
case statement will be more efficient than a function which accesses the
physical table.  Any thoughts or comments?

Which begs another question: in general, what overhead is involved in accessing
context variables?
Followup:
not correct.  with scalar subqueries you can get quite sophisticated:

ops$tkyte@ORA920> create table security_tab
  2  ( username varchar2(30),
  3    empno    number,
  4    primary key(username,empno)
  5  )
  6  organization index
  7  /

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into security_tab
  2  select user, empno from emp where mod(empno,2) = 0;

10 rows created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select empno, ename, SAL the_real_deal,
  2         case when (select username
  3                      from security_tab
  4                     where username = USER
  5                       and empno = EMP.EMPNO) = user
  6              then sal
  7              else null
  8          end and_the_function_says
  9    from emp
 10  /

     EMPNO ENAME      THE_REAL_DEAL AND_THE_FUNCTION_SAYS
---------- ---------- ------------- ---------------------
      7369 SMITH                800
      7499 ALLEN               1600
      7521 WARD                1250
      7566 JONES               2975                  2975
      7654 MARTIN              1250                  1250
      7698 BLAKE               2850                  2850
      7782 CLARK               2450                  2450
      7788 SCOTT               3000                  3000
      7839 KING                5000
      7844 TURNER              1500                  1500
      7876 ADAMS               1100                  1100
      7900 JAMES                950                   950
      7902 FORD                3000                  3000
      7934 MILLER              1300                  1300

14 rows selected.

ops$tkyte@ORA920>

But if you can use SYS_CONTEXT that is penultimate.

select * from emp where ename = sys_context( 'x','y' )

is the same as:

select * from emp where ename = :x

they are bind variables.
Rating: 5
How Much Better Is the Inline Implementation? March 05, 2003
Reviewer: John Gilmore from United Kingdom
As you stated, we could also write something like the following with f
containing the code from the case statement.

  select empno, ename, SAL the_real_deal,
         decode (f(user, empno)), 1, sal, null ) fn
  from emp

How much more efficient is the case statement implementation?  And why?

I would have thought that both implementations would still have to issue the
subquery once for each row of the view.
Followup:
conceptually they do.  however one is pure sql and the other is sql calling
plsql over and over and over.

Here is the big test then:

ig_table@ORA920> create table security_tab
  2  ( username varchar2(30),
  3    id       number,
  4    primary key(username,id)
  5  )
  6  organization index
  7  /

Table created.

big_table@ORA920>
big_table@ORA920> insert into security_tab
  2  select user, id from big_table where mod(id,2) = 0;

500000 rows created.

big_table@ORA920>
big_table@ORA920> commit;

Commit complete.

big_table@ORA920>
big_table@ORA920> create or replace function f( p_id in number ) return varchar2
  2  as
  3          l_username varchar2(30);
  4  begin
  5          select username into l_username
  6        from security_tab
  7       where username = USER
  8         and id = p_id;
  9
 10          return l_username;
 11  exception
 12          when no_data_found then return null;
 13  end;
 14  /

Function created.

And TKPROF says:

select id, object_id the_real_deal,
       case when (select username
                    from security_tab
                   where username = USER
                     and id = big_table.id) = user
            then object_id
            else null
        end and_the_function_says
  from big_table

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch    66668     29.98      30.15       6335    2145471          0     1000000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    66670     29.98      30.15       6335    2145471          0     1000000

Rows     Row Source Operation
-------  ---------------------------------------------------
1000000  TABLE ACCESS FULL BIG_TABLE (cr=78803 r=6335 w=0 time=6459080 us)
********************************************************************************

Now, the following shows the aggregate time for the decode(f(x)) query
HOWEVER, you must add in the query/disk counts from the recursive sql manually
to get the real picture

select id, object_id the_real_deal,
       decode( f(big_table.id), user, object_id, null ) and_the_function_says
  from big_table

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch    66668    336.42     367.66       6337      78803          0     1000000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    66670    336.42     367.66       6337      78803          0     1000000

Rows     Row Source Operation
-------  ---------------------------------------------------
1000000  TABLE ACCESS FULL BIG_TABLE (cr=78803 r=6337 w=0 time=16970634 us)
********************************************************************************

SELECT username from security_tab
     where username = USER
       and id = :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute 1000000    255.65     271.04          0          0          0
0
Fetch   1000000     65.23      67.23          0    3000000          0
500000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   2000001    320.88     338.27          0    3000000          0
500000

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 64     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
 500000  INDEX UNIQUE SCAN SYS_IOT_TOP_39558 (cr=3000000 r=0 w=0 time=55388135
us)(object id 39560)

so, 30 seconds or 336 seconds....

avoiding calling PLSQL from SQL whenever possible will

o let the optimizer do smarter things (2145471 lios vs 3078803)
o avoids the context switch from sql to plqsl (1,000,000 times)
o go faster
Rating: 5
Extremely Useful Information. March 06, 2003
Reviewer: John Gilmore
I take my hat off to you sir.
Rating: 5
Simple question, multiple references to the same table. April 08, 2003
Reviewer: Martin from UK
Hi Tom,

quick question : If I reference the same table multiple times within a given SQL
statement, and the table has a policy on it, is the policy applied multiple
times, or is oracle clever enough to apply it once "across the board"? The event
10730 testing i've done seems to imply the policy gets applied n times.

Thanks in advance
Followup:
the policy -- which should be a trivial (eg: fast) piece of code (since you
setup the application contexts way ahead of time -- the policy isn't more then a
couple of if then else statements) is executed each time the object is
referenced.
Rating: 5
Can FGAC Restrict Columns as well as Rows? May 13, 2003
Reviewer: John Gilmore from United Kingdom
Hi Tom,

In your reply above you stated that one "cannot currently use FGAC to implement
column wise security".

I was reading "Oracle 9I Database Summary, An Oracle White Paper" (May 2002)
recently wherein the section on Deep Data Protection states that "application
logic no longer needs to enforce which rows and columns of a data table are seen
by the application, it is automatically and transparently limited by the
security policy."

Are they actually refering to some form of FGAC column-wise security here or are
they just referring to a combination of FGAC and the normal security privileges?

Do you know of any plans to introduce column-wise security to FGAC in the
future?

Thanks.
Followup:
combination of FGAC and encryption actually.  We would use fgac to restrict
rows, selective encryption to obscure columns.

I am not aware of plans to "hide columns" so that a "select * from T" would
return different sets of columns given the same table T and users A and B.  In
fact, I would consider that a nightmarish feature to debug, maintain and use --
can you imagine your code where something column "X" is there and sometimes not?
 ugh.
Rating: 3
The Data, Not the Column May 13, 2003
Reviewer: John Gilmore from United Kingdom
I was thinking more along the lines of the data in the column rather than the
column itself.  That is, a particular column is populated only if you have a
specific privilege on the row in question.
Rating: 5
One more thing..... May 15, 2003
Reviewer: Bharath from USA
To carry out performance analysis of SQL statements executing within FGAC, is it
possible to view the modified statement(ie.like in TKPROF or V$SQl); that is,
after the FGAC predicates have
been added?
Version:8.1.7

I tried Generating TKPROF and tried Shared pool content, i couldnt see the
modifed sql.I want to know where exactly oracle will append the modified
predicate.
Followup:
it is not visible.
Rating: 2
Visible in 9i ? May 16, 2003
Reviewer: A reader
Is the sql statement visible in 9i ? Thanks Tom.
Rating: 5
One more thing..... May 16, 2003
Reviewer: Bharath from USA
Will this will help in 8.1.7

ALTER SESSION SET EVENTS '10730 trace name context forever, level 12';
Rating: 4
FGAC and export May 23, 2003
Reviewer: hrishab from india
Hi Tom,

    I tried to implement FGAC on a table in oracle 8.1.6 using the example that
you have posted on this site. I was successful in doing so. Thanks for the
wonderful example and explaination. Now I understood how to implement FGAC and
what are their advantages. As of now everything is ok. Now I take a export
backup of whole database as normal user and not sysdba. It gave me an error when
I tried to export the table which had policy on it. The error is given below:

EXP-00079: Data in table "testtable" is protected. Conventional path may only be
exporting partial table.

I know that user with sysdba privilege bypass the policies set for any user
while backing up. I want to know how oracle internally handles this scenario.

Your comments will be greatly appreciated
Followup:
it is not an error.

it is a helpful warning.  telling you that only the data that user would see via
"select * from testtable" will be exported.

which leads to an interesting useful "use" of this feature.  Say you wanted to
export a schema with lots of tables.

however, there was one really big audit trail table you didn't care to get the
data for.

export doesn't help you much here -- there is no "everything BUT this table"
option.

So, just use FGAC to say:

begin
   if ( user = 'guy doing the export' ) then return 1=0; else return null; end
if;
end;

and export.  no data for that table would be exported for the 'guy doing the
export'.
Rating: 5
Different results on 9.2.0.3 June 10, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
I executed the example that you gave in your section titled as "Important
Caveat" for Example 1 in the following article:
http://osi.oracle.com/~tkyte/article2/index.html
In my testing, the DUMP_T procedure call at the end was always producing rows
for both the cases (whereas in your article you mention that the first query
will return zero rows).
Is this because of a change in behaviour in Oracle9i (9.2.0.3)?
Below are the results of test runs:

======================================================
KKISHORE @ ORA9203.WORLD> create or replace function rls_examp
  2  ( p_schema in varchar2, p_object in varchar2 )
  3  return varchar2
  4  as
  5  begin
  6          if ( sys_context( 'myctx', 'x' ) is not null )
  7          then
  8                  return 'x > 0';
  9          else
 10                  return '1=0';
 11          end if;
 12  end;
 13  /

Function created.

KKISHORE @ ORA9203.WORLD>
KKISHORE @ ORA9203.WORLD> create table t ( x int );

Table created.

KKISHORE @ ORA9203.WORLD>
KKISHORE @ ORA9203.WORLD> insert into t values ( 1234 );

1 row created.

KKISHORE @ ORA9203.WORLD>
KKISHORE @ ORA9203.WORLD> begin
  2     dbms_rls.add_policy
  3     ( object_schema   => user, object_name => 'T',
  4       policy_name => 'T_POLICY', function_schema => user,
  5       policy_function => 'rls_examp', statement_types => 'select' );
  6  end;
  7  /

PL/SQL procedure successfully completed.

KKISHORE @ ORA9203.WORLD> create or replace procedure set_ctx( p_val in varchar2
)
  2  as
  3  begin
  4          dbms_session.set_context( 'myctx', 'x', p_val );
  5  end;
  6  /

Procedure created.

KKISHORE @ ORA9203.WORLD> create or replace context myctx using set_ctx;

Context created.

KKISHORE @ ORA9203.WORLD> exec set_ctx( null );

PL/SQL procedure successfully completed.

KKISHORE @ ORA9203.WORLD>
KKISHORE @ ORA9203.WORLD> select * from t;

no rows selected

KKISHORE @ ORA9203.WORLD>
KKISHORE @ ORA9203.WORLD> exec set_ctx( 1 ) ;

PL/SQL procedure successfully completed.

KKISHORE @ ORA9203.WORLD>
KKISHORE @ ORA9203.WORLD> select * from t;

         X
----------
      1234

1 row selected.

KKISHORE @ ORA9203.WORLD> create or replace procedure dump_t
  2  ( some_input in number default NULL )
  3  as
  4  begin
  5          dbms_output.put_line
  6          ( '*** Output from SELECT * FROM T' );
  7
  8          for x in (select * from t ) loop
  9                  dbms_output.put_line( x.x );
 10          end loop;
 11
 12
 13          if ( some_input is not null )
 14          then
 15                  dbms_output.put_line
 16                  ( '*** Output from another SELECT * FROM T' );
 17
 18                  for x in (select * from t ) loop
 19                          dbms_output.put_line( x.x );
 20                  end loop;
 21          end if;
 22  end;
 23  /

Procedure created.

KKISHORE @ ORA9203.WORLD> set serveroutput on
KKISHORE @ ORA9203.WORLD> exec set_ctx( NULL )

PL/SQL procedure successfully completed.

KKISHORE @ ORA9203.WORLD> exec dump_t
*** Output from SELECT * FROM T

PL/SQL procedure successfully completed.

KKISHORE @ ORA9203.WORLD> exec set_ctx( 1 )

PL/SQL procedure successfully completed.

KKISHORE @ ORA9203.WORLD> exec dump_t( 0 )
*** Output from SELECT * FROM T
1234
*** Output from another SELECT * FROM T
1234

PL/SQL procedure successfully completed.

KKISHORE @ ORA9203.WORLD>
KKISHORE @ ORA9203.WORLD>
KKISHORE @ ORA9203.WORLD> alter session set session_cached_cursors = 100 ;

Session altered.

KKISHORE @ ORA9203.WORLD> exec set_ctx( null );

PL/SQL procedure successfully completed.

KKISHORE @ ORA9203.WORLD> exec dump_t
*** Output from SELECT * FROM T

PL/SQL procedure successfully completed.

KKISHORE @ ORA9203.WORLD> exec set_ctx( 1 )

PL/SQL procedure successfully completed.

KKISHORE @ ORA9203.WORLD> exec dump_t( 0 )
*** Output from SELECT * FROM T
1234
*** Output from another SELECT * FROM T
1234

PL/SQL procedure successfully completed.

KKISHORE @ ORA9203.WORLD>
Followup:
they changed the behaviour in 817 and up (i just annotated that page).

in 817, when you change an application context, they invalidate the sessions
cursors that are dependent on it -- causing a reparse automatically on the next
execute, to pick up the (possibly) new predicate.
Rating: 5
But in another case... June 11, 2003
Reviewer: Oleg Letaev from Russia
But if the predicate is changing based on, for example, time of the day, the
problem persists. It's still not desirable to change the predicates in the
middle of the session.

Right?
Followup:
if the predicates change based on time of day OR are table driven, you must use
dbms_rls to refresh the policy (and flush the cached cursors that are dependent
on that policy) when the time of day passes your thresholds OR the table is
modified.
Rating: 4
Can FGAC Restrict Columns as well as Rows? June 12, 2003
Reviewer: John Gilmore from United Kingdom
Hi Tom,

In your reply above to this topic you stated that "selective encryption" can be
used "to obscure columns" in a query result.

Can you describe how this works please?
Followup:
you would encrypt columns, the user would insert encrypted data.
Rating: 3
Can FGAC Restrict Columns as well as Rows? June 13, 2003
Reviewer: John Gilmore from United Kingdom
Sorry, I don't understand how this would work.  Is it possible to point me to an
example?
Followup:
umm, it's not really that "hard".  you have the dbms_obfuscation_toolkit (does
encryption -- search for it on this site, lots of examples).

It does decryption as well.

You would code a small "encrypt" and "decrypt" routine.

You would

insert into t values( a,b,c,encrypt(d,KEY));

you would

select a,b,c,decrypt(d,KEY) from t

you would have to perform key management.
You would want to use network encryption (advanced security option) to prevent
"sniffing of the network" to find out what these keys are.

No key, no data.
Key, data.

Don't lose the keys, lose the keys and it takes quite a few thousand cpus
running for quite a while (trillions of years with 3des) to get your data back.
Rating: 5
FGAC question August 14, 2003
Reviewer: A reader
Hi Tom
Currently, do we have data centers that store multiple
customer data in the same Oracle database using FGAC?
I had two doubts:

1. If only one customer wants to roll back the data (say
there were some errors in the data) to a particular time
how would that be achieved? (would you just export/import
logged in as that customer's user identity or is their
a better way
2. For sys FGAC is not applicable. So is that a concern
for the customer because now we have one person who has
access to multiple customer's (potentially competing)
data and could create problems (e.g. by cross selling
info etc.)

Thanx!
Followup:
1) not feasible unless the application does it.  tell me, REALLY, how often has
this happened in a hosted environment?

if it is something you anticipate, the application itself should have done it.

2) no concern -- no more concern then a single dba managing databases for
customer 1 and customer 2.
Rating: 4
thanx Tom! August 15, 2003
Reviewer: A reader
"1) not feasible unless the application does it.  tell me, REALLY, how often has
this happened in a hosted environment?  "

Well, I can imagine that this happens in a non-hosted
environment. Customer wants to rollback because she has
entered a bunch of wrong data for example. I am not a DBA
but I am sure you can tell whether this is a real world
scenario. If it is , then surely this can happen in a
hosted environment as well? And very likely, you will
have to selectively rollback the customer's data only.
2. Well, the difference is that separate dbas can be managing different
customers - one guy does not have
access to both databases which probably creates new
security concerns. The above scenario is similar
to giving sys password of multiple legal entities to one
person.Imagine Oracle and MS have their information in their databases. Now for
some reason they go with the
CoolHostingServices (cost cutting, say.) Would they
be comfortable knowing that there is a single DBA who
can see information belonging to both companies? Perhaps
not.

Thanx for an awesome site - Love your book and waiting
for the new one!
Followup:
1) and in this environment -- do you really do a full PITR for that database for
that person?  what about the other dozens of persons work??????????

2) if you do not trust your asp, you've got much larger problems.  you are
giving them your data for goodness sake.  physical separation in an environment
where YOU do not control who works where doesn't buy you anything.

would you really set up an ASP environment where by you had to have separate dba
teams for EACH CUSTOMER?
Rating: 5
thanx Tom! August 15, 2003
Reviewer: A reader
"1) and in this environment -- do you really do a full PITR for that database
for
that person?  what about the other dozens of persons work??????????"

That is a good point :) So how do you recover from these
kinds of issues? Has this ever happenned to you - that
you had to rollback the database by one or more incorrect transactions in
Oracle?

Also, may be workspaces may help here if this is anticipated perhaps?

"2) if you do not trust your asp, you've got much larger problems.  you are
giving them your data for goodness sake.  physical separation in an environment
where YOU do not control who works where doesn't buy you anything.

would you really set up an ASP environment where by you had to have separate dba
teams for EACH CUSTOMER?
"

Another good point - hmm...the last point is very valid -
by the very nature of ASP, you will have one DBA managing
more than one customer...

Thanx!!!
Followup:
flashback query in 9i can be very useful.

many more "flashback" things coming in 10....

only have had to reset a full table to a full point in time in the past because
it was soooo messed up.

workspaces will help you -- but like I said -- the application sort of has to be
willing to anticipate this and help us out.  so the DESIGN would take into
consideration that people want to have the ability to back things out.
Rating: 5
thanx Tom - that clarifies my doubts!! August 15, 2003
Reviewer: A reader
Rating: 5
The same policy function for many tables August 25, 2003
Reviewer: Alex from Russia
Hi, Tom,
We are going to use the same policy function for several tables.

For example

the first one:
exec dbms_rls.add_policy
  ( object_schema => 'SCOTT',
   object_name => 'EMP',
   policy_name => 'POLICE_FOR_SCOTT',
   function_schema => 'SCOTT',
   policy_function => 'RLS_EXAMPLE',
   statement_types => 'SELECT',
   static_policy => TRUE);

the second one:
exec dbms_rls.add_policy
  ( object_schema => 'SCOTT',
   object_name => 'DEPT',
   policy_name => 'POLICE_FOR_SCOTT',
   function_schema => 'SCOTT',
   policy_function => 'RLS_EXAMPLE',
   statement_types => 'SELECT',
   static_policy => TRUE);

The function's body:
create or replace function rls_example
  (p_schema in varchar2, p_object in varchar2)
return varchar2
as
begin
  if p_object='EMP' then
    return <predicate for EMP>;
  elsif p_object='DEPT' then
    return <predicate for DEPT>;
  else
    <something other predicate>;
  end if;
end;

As far as I understood from Oracle DOCs:
1) If I have (..., static_policy => FALSE), I don't have problems describing you
in section "Important Caveat" (because of automatic reparsing).
2) If I have (..., static_policy => TRUE), I don't have problems describing you
in section "Important Caveat", only if policy function returns the same
predicate for the session.

But I'm going to use (..., static_policy => TRUE) to avoid excessive reparsing
and executing policy function. And it will be one policy function for many
tables. And this function will return the same predicate for the same table
during user's session.

Please, any comments. Am I right in my reasoning?
Thanks.
Followup:
as long as the return value doesn't change in the session, it is "safe" -- yes.
Rating: 3
FGAC policy reference December 15, 2003
Reviewer: Vadim Kudryavtsev from Montreal, QC Canada
is there a way to attach comments to policies, and or policy functions, or some
other way of reference to business logic implemented in them?
it would be very convenient to be able access the business logic attached to a
particular object.
thanks for all the good job!
Followup:
not sure what you mean?  you can comment the code (the policy function) as it is
just PL/SQL code.

if you mean "i'd like to be able to comment on it like commenting on a table --
the sql command", you'd have to set up your own "comments table" for that
purpose and insert comments.
Rating: 3
Fined Grained Access Control December 17, 2003
Reviewer: Vadim Kudryavtsev from Montreal, QC Canada
not really.
we don't want to create any additional structure it needs extra resources for
maintenance and security issues. it would be handy to have comments attached to
either or both policy and associated function. for now we are using convention
of placing comments in the security function in a format
'function_name->'...comment text...
then by querying structures all_policies and user_source with protected object
owner/name we are able to pull out policy information and function comments.
SELECT
      S.TEXT POLICY_COMMENT,
      P.OBJECT_OWNER || '.'|| P.OBJECT_NAME OBJECT,
      P.PF_OWNER || '.' || P.PACKAGE || '.'
               || P.FUNCTION  AS POLICY_FUNCTION,
      ENABLE ENABLED,
      SEL,
      INS,
      UPD,
      DEL
FROM  ALL_POLICIES P,
      USER_SOURCE S
WHERE UPPER(P.OBJECT_OWNER) = UPPER(v_OWNER) AND
      UPPER(P.OBJECT_NAME) = UPPER(v_ONAME) AND
      S.TYPE = 'PACKAGE BODY' AND
      S.NAME = P.PACKAGE AND
      S.TEXT LIKE '%' || P.FUNCTION || '->%';
note our security functions are all in packages therefore we are looking only
for particular type of source code.
it seems a little odd, however comments are stored along with function, so that
maintenance and security taken care of.
regarding performance issues, on VPD it always comes down to indexing and
normalization.
thanks,
vadim
Followup:
don't understand the "maintenance and security issues" comments at all.

you either have the structures you need for your requirements

OR

you do not.

Seems like your method is more of a maintenance issue then just maintaining them
in a table (if you ask me)
December 18, 2003 Irshad -- Thanks for the question regarding "Index not used.", version 8.0.5
You Asked
Hi,

I created an index name_idx on a table temp on its column
name(varchar2(20)). This column name does not have a not NULL
constraint on it.

When I issue a query like

select ... from temp
where name='JAMES';

I found out ( using autotrace )that this index is not being
used. I made this index to work using a hint with this query.

However if I issue

select count(*) from temp
where name='JAMES';

 this index gets used.

Am I missing something. Any pointers please.

Irshad.

*******************

Tom,

I created the temp table as scott.emp. It has 16 rows and
count(*) returns 1.
and we said...
Ok, you are using the cost based optimizer (CBO) and it is doing
absolutely the most correct thing possible.

You have a very small table.  It has 1 block basically.  The CBO
understands it is very very small.  It understands that to use
an index to answer the select .... from temp where name =
'JAMES', it would have to read an index and then do a table
access by rowid.  It also understands that it could just read
the entire table in 1 IO and get the data just as easy.  Here,
on such a small table -- the index increases overhead, not
descreases it.

When you do the count(*), it understands it does not need to
access the table hence the query can be entirely answered by
reading the equally tiny index -- so it does.

Using the rule based optimizer, which does not consider the size
of tables, we can get the less optimal plan of "index range
scan, table access by rowid".

The bottom line here is that INDEXES != FASTER STUFF in all
cases.  The optimizer is doing the correct thing here given the
stats on the table.

Here is the example for all to see the behavior:

ops$tkyte@ORA8I.WORLD> create table emp
  2  as
  3  select * from scott.emp;
Table created.

ops$tkyte@ORA8I.WORLD> create index emp_idx on emp(ename);
Index created.

ops$tkyte@ORA8I.WORLD> set autotrace traceonly explain

ops$tkyte@ORA8I.WORLD> select empno, ename
  2  from emp
  3  where ename = 'JAMES';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
   2    1     INDEX (RANGE SCAN) OF 'EMP_IDX' (NON-UNIQUE)

So, using RBO which uses rules only to develop a query plan,
we use the index.  Using the index is actually NOT the optimal
plan given the size of this table!

ops$tkyte@ORA8I.WORLD> select count(*)
  2  from emp
  3  where ename = 'JAMES';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'EMP_IDX' (NON-UNIQUE)

Here is uses the smaller even index -- since it knows it need
not goto the table to pick up other columns.... This is
optimal

ops$tkyte@ORA8I.WORLD> analyze table emp compute statistics;
Table analyzed.

ops$tkyte@ORA8I.WORLD> select empno, ename
  2  from emp
  3  where ename = 'JAMES';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1
   1    0   TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=1
Bytes=27)

Now it picks the BETTER plan for this query given this small
amount of data.

ops$tkyte@ORA8I.WORLD> select count(*)
  2  from emp
  3  where ename = 'JAMES';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'EMP_IDX' (NON-UNIQUE)

and the count(*) is unaffected...
Review & Followup

Rating: 5
Indexes should not be used in case of CBO- in the given example November 08, 2001
Reviewer: Nag
SQL> CREATE UNIQUE INDEX  DEPTNO_IDX ON DEPT(DEPTNO);

Index created.

First case:

SQL> ANALYZE TABLE EMP COMPUTE STATISTICS
SQL> ANALYZE TABLE DEPT COMPUTE STATISTICS

SQL> set autotrace on explain
SQL> SELECT ENAME,EMP.DEPTNO
  2  from emp,dept
  3  where emp.deptno=dept.deptno ;

ENAME          DEPTNO
---------- ----------
SMITH              20
ALLEN              30
WARD               30
JONES              20
MARTIN             30
BLAKE              30
CLARK              10
SCOTT              20
KING               10
TURNER             30
ADAMS              20

ENAME          DEPTNO
---------- ----------
JAMES              30
FORD               20
MILLER             10

14 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=14 Bytes=126)
   1    0   NESTED LOOPS (Cost=1 Card=14 Bytes=126)
   2    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=14 Bytes=98)
   3    1     INDEX (UNIQUE SCAN) OF 'DEPTNO_IDX' (UNIQUE)

Second Case:

SQL> analyze table emp delete statistics;

Table analyzed.

SQL> analyze table dept delete statistics;

Table analyzed.

we have deleted the statistics so we now should switch to RBO. ( Isnt it ?)

SQL> SELECT ENAME,EMP.DEPTNO
  2  from emp,dept
  3  where emp.deptno=dept.deptno ;

ENAME          DEPTNO
---------- ----------
SMITH              20
ALLEN              30
WARD               30
JONES              20
MARTIN             30
BLAKE              30
CLARK              10
SCOTT              20
KING               10
TURNER             30
ADAMS              20

ENAME          DEPTNO
---------- ----------
JAMES              30
FORD               20
MILLER             10

14 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     TABLE ACCESS (FULL) OF 'EMP'
   3    1     INDEX (UNIQUE SCAN) OF 'DEPTNO_IDX' (UNIQUE)

In the first case the indexes should not be used in the light of the fact that
the tables are very small, and a full table scan will be much more beneficial.
But it isnt happening.

Could you give your thoughts on why the above is not happening according to  my
expectations.

thank you
Followup:
In this case, since you are not accessing the DEPT table really -- this query is
pretty much the same as:

select ename, deptno
  from emp
  where exists ( select null from dept where dept.deptno = emp.deptno )

and thats how its being processed.  Its using the index instead of sorting the
table and merging (its using the already sorted dept index).

If you add a column from dept -- making it so the index cannot by itself be used
-- the plan can and will change:

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

Table created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> create table dept as select * From
scott.dept;

Table created.

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> alter table dept add constraint dept_pk
primary key(deptno);

Table altered.

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> analyze table emp compute statistics;

Table analyzed.

ops$tkyte@ORA717DEV.US.ORACLE.COM> analyze table dept compute statistics;

Table analyzed.

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> SELECT ENAME,EMP.DEPTNO
  2    from emp,dept
  3   where emp.deptno=dept.deptno ;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=14 Bytes=126)
   1    0   NESTED LOOPS (Cost=1 Card=14 Bytes=126)
   2    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=14 Bytes=98)
   3    1     INDEX (UNIQUE SCAN) OF 'DEPT_PK' (UNIQUE)

ops$tkyte@ORA717DEV.US.ORACLE.COM>
ops$tkyte@ORA717DEV.US.ORACLE.COM> SELECT ENAME,EMP.DEPTNO, dept.dname
  2    from emp,dept
  3   where emp.deptno=dept.deptno ;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=14 Bytes=252)
   1    0   NESTED LOOPS (Cost=5 Card=14 Bytes=252)
   2    1     TABLE ACCESS (FULL) OF 'DEPT' (Cost=1 Card=4 Bytes=44)
   3    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=14 Bytes=98)
Rating: 5
agreed...but need your suggestions May 03, 2002
Reviewer: Senthil from Boston, MA
Tom-

I agree with your reply for the above query. but i have got something where i am
facing the same performance issue. I would like to have your suggestions to
redesign the select statement in such a way to perform better. have changed the
tablename and column name for question purpose. I am using 8.1.7 for Solaris.

SQL> show parameter optimizer_mode

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
optimizer_mode                       string  CHOOSE
SQL> show parameter compatible

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
compatible                           string  8.1.5.0.0

=============================================================================

Query 1:

select   sum.table_name
from     summary sum, dv dvn
where    sum.reference = :b1
and      sum.vol_num = dvn.vol_num
and      dvn.expired is null
and      dvn.accepted is not null
and      ((sum.oldest_date >= :b2 and sum.oldest_date <= :b3 )
or        (sum.latest_date >= :b2 and sum.latest_date <= :b3 )
or        (sum.oldest_date <= :b2 and sum.latest_date >= :b3 ))
group by  sum.table_name
order by max(sum.latest_date) desc,
         max(sum.table_ref) desc

Trace details:

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=11 Card=2 Bytes=76)
   1    0   SORT (ORDER BY) (Cost=11 Card=2 Bytes=76)
   2    1     SORT (GROUP BY) (Cost=11 Card=2 Bytes=76)
   3    2       HASH JOIN (Cost=3 Card=3 Bytes=114)
   4    3         INDEX (FULL SCAN) OF 'DV_IDX1' (NON-UNIQUE) (Cost=1 Card=17
Bytes=289)
   5    3         TABLE ACCESS (FULL) OF 'SUMMARY' (Cost=1 Card=3 Bytes=63)

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

Index list:

SQL> select index_name, column_name, column_position from user_ind_columns where
table_name = '&tab' order by 1,3;
Enter value for tab: SUMMARY

INDEX_NAME                     COLUMN_NAME                    COLUMN_POSITION
------------------------------ ------------------------------ ---------------
SUMMARY_IDX1                   REFERENCE                                    1
SUMMARY_IDX2                   OLDEST_DATE                                  1
SUMMARY_IDX3                   LATEST_DATE                                  1
PK_SUMMARY                     VOL_NUM                                      1

SQL> /
Enter value for tab: DV

INDEX_NAME                     COLUMN_NAME                    COLUMN_POSITION
------------------------------ ------------------------------ ---------------
DV_IDX1                        VOL_NUM                                      1
DV_IDX1                        EXPIRED                                      2
DV_IDX1                        ACCEPTED                                     3
PK_DV                          VOL_NUM                                      1

SQL> select count(*) from summary;

  COUNT(*)
----------
        23

SQL> select count(*) from dv;

  COUNT(*)
----------
        23

>>The Query1 is not using the index even though its analyzed and used properly
in the query.

=============================================================================

Query 2:

select   ti.ji_id,
         ti.seq,
         ti.status,
         ti.state_value,
         ti.status_date_time
from     ji,
         ti
where    ji.job_id = :b1
and      ji.id = ti.ji_id
and      ti.jtt_id = :b2
order by ji.schedule_date_time desc,
         ti.seq desc

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=8 Card=6 Bytes=570)
   1    0   SORT (ORDER BY) (Cost=8 Card=6 Bytes=570)
   2    1     HASH JOIN (Cost=4 Card=6 Bytes=570)
   3    2       TABLE ACCESS (FULL) OF 'JI' (Cost=1 Card=2 Bytes=26)
   4    2       TABLE ACCESS (FULL) OF 'TI' (Cost=2 Card=75 Bytes=6150)

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

SQL> select index_name, column_name, column_position from user_ind_columns where
table_name = '&tab' order by 1,3;
Enter value for tab: JI

INDEX_NAME                     COLUMN_NAME                    COLUMN_POSITION
------------------------------ ------------------------------ ---------------
JI_IDX_1                       JOB_ID                                       1
PK_JI                          ID                                           1

SQL> /
Enter value for tab: TI

INDEX_NAME                     COLUMN_NAME                    COLUMN_POSITION
------------------------------ ------------------------------ ---------------
PK_TI                          JI_ID                                        1
PK_TI                          JTT_ID                                       2
PK_TI                          SEQ                                          3

SQL> select count(*) from ji;

  COUNT(*)
----------
        25

SQL> select count(*) from ti;

  COUNT(*)
----------
       264

>>Same as Query1, isn't using the index properly. Both the tables(ji & ti) could
grow fastly.

=============================================================================

select   ji.id,
         ji.schedule_id,
         dname.name,
         j.name,
         ji.schedule_date_time,
         ji.status,
         ji.elapsed_seconds,
         ji.start_date_time,
         ji.end_date_time,
         t.name,
         jtt.task_order,
         ji.input_value
from     ji,
         jtt,
         task t,
         job j,
         dname
where    ji.status = :b1
and      ji.schedule_date_time <= sysdate
and      ji.job_id = j.id
and      j.expired is null
and      j.reference = dname.reference
and      dname.expired is null
and      ji.last_job_type_task_id = jtt.id (+)
and      jtt.task_id = t.id (+)
order by ji.schedule_date_time,
         ji.start_date_time,
         ji.id

Trace details:

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=9 Card=1 Bytes=745)
   1    0   SORT (ORDER BY) (Cost=9 Card=1 Bytes=745)
   2    1     NESTED LOOPS (OUTER) (Cost=5 Card=1 Bytes=745)
   3    2       NESTED LOOPS (OUTER) (Cost=4 Card=1 Bytes=714)
   4    3         NESTED LOOPS (Cost=3 Card=1 Bytes=675)
   5    4           NESTED LOOPS (Cost=2 Card=1 Bytes=642)
   6    5             TABLE ACCESS (FULL) OF 'JI' (Cost=1 Card=1 Bytes=589)
   7    5             TABLE ACCESS (BY INDEX ROWID) OF 'JOB' (Cost=1 Card=3
Bytes=159)
   8    7               INDEX (UNIQUE SCAN) OF 'PK_JOB' (UNIQUE)
   9    4           TABLE ACCESS (BY INDEX ROWID) OF 'DNAME' (Cost=1 Card=3
Bytes=99)
  10    9             INDEX (UNIQUE SCAN) OF 'PK_DNAME' (UNIQUE)
  11    3         TABLE ACCESS (BY INDEX ROWID) OF 'JTT' (Cost=1 Card=41
Bytes=1599)
  12   11           INDEX (UNIQUE SCAN) OF 'PK_JTT' (UNIQUE)
  13    2       TABLE ACCESS (BY INDEX ROWID) OF 'TASK' (Cost=1 Card=41
Bytes=1271)
  14   13         INDEX (UNIQUE SCAN) OF 'PK_TASK' (UNIQUE)

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

SQL> select index_name, column_name, column_position from user_ind_columns where
table_name = '&tab' order by 1,3
Enter value for tab: JI

INDEX_NAME                     COLUMN_NAME                    COLUMN_POSITION
------------------------------ ------------------------------ ---------------
JI_IDX_1                       JOB_ID                                       1
JI_IDX_2                       SCHEDULE_ID                                  1
JI_IDX_3                       LAST_JOB_TYPE_TASK_ID                        1
JI_IDX_4                       SCHEDULE_DATE_TIME                           1
JI_IDX_5                       START_DATE_TIME                              1
JI_IDX_6                       STATUS                                       1
PK_JI                          ID                                           1

7 rows selected.

SQL> /
Enter value for tab: JOB_TYPE_TASK

INDEX_NAME                     COLUMN_NAME                    COLUMN_POSITION
------------------------------ ------------------------------ ---------------
JTT_1                          TASK_ID                                      1
PK_JTT                         ID                                           1
UNQ_JTT_1                      JOB_TYPE_ID                                  1
UNQ_JTT_1                      TASK_ID                                      2
UNQ_JTT_1                      TASK_ORDER                                   3

SQL> /
Enter value for tab: TASK

INDEX_NAME                     COLUMN_NAME                    COLUMN_POSITION
------------------------------ ------------------------------ ---------------
PK_TASK                        ID                                           1
TASK_IDX_1                     TASK_TYPE_ID                                 1
UNQ_TASK_1                     NAME                                         1
UNQ_TASK_1                     DATE_EXPIRED                                 2

SQL> /
Enter value for tab: JOB

INDEX_NAME                     COLUMN_NAME                    COLUMN_POSITION
------------------------------ ------------------------------ ---------------
JOB_IDX_1                      REFERENCE                                    1
JOB_IDX_2                      JOB_TYPE_ID                                  1
PK_JOB                         ID                                           1
UNQ_JOB_1                      NAME                                         1
UNQ_JOB_1                      REFERENCE                                    2
UNQ_JOB_1                      DATE_EXPIRED                                 3

6 rows selected.

SQL> /
Enter value for tab: DNAME

INDEX_NAME                     COLUMN_NAME                    COLUMN_POSITION
------------------------------ ------------------------------ ---------------
PK_DNAME                       REFERENCE                                    1
UNQ_DNAME_1                    NAME                                         1
UNQ_DNAME_1                    DATE_EXPIRED                                 2

SQL> select count(*) from ji;

  COUNT(*)
----------
        25

SQL> select count(*) from jtt;

  COUNT(*)
----------
        19

SQL> select count(*) from task;

  COUNT(*)
----------
        15

SQL> select count(*) from job;

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

SQL> select count(*) from dname;

  COUNT(*)
----------
         3

>>Its different in this query, even though its using the index, due to the outer
join, its taking more time to process the query. Is therey any workaround to
avoid using outer join in this query or to improve further.
Followup:
I see *no timings* here.

I see *very very small consistent gets*

it will not use an index unless an index would be measurably better.  You seem
to be running these queries against empty or almost empty tables.

remember these facts:

indexes are not always faster
indexes are sometimes slower
indexes are not always faster
indexes sometimes take longer then not using the index
indexes are not always faster

Cannot help you on this one as there is no apparent problem to be solved.  On
tables of this size, indexes are a waste.
Rating: 4
This query *not always* use the PK index. Any ideas? July 14, 2003
Reviewer: ER from Argentina
Hi Tom,
I have this table ACCOUNTS which primary key is ID. The table is analyzed as
well as all its indexes.
For some reason that I'm still figuring out, with different values of ID, oracle
is using different plans (I'm using CBO). These are the min and max values for
ID in this table:

select min(id), max(id) from accounts;
   MIN(ID)    MAX(ID)
---------- ----------
     10000    2700000

Now look at these execution plans:

SQL> Select * from (SELECT * from ACCOUNTS a where id > 10000 order by id) where
rownum < 5;
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=119118 Card=2413500
          Bytes=347544000)
   1    0   COUNT (STOPKEY)
   2    1     VIEW* (Cost=119118 Card=2413500 Bytes=347544000)         :Q119700
                                                                      1
   3    2       SORT* (ORDER BY STOPKEY) (Cost=119118 Card=2413500 Byt :Q119700
         es=222042000)                                                1
   4    3         PARTITION RANGE* (ALL)                               :Q119700
                                                                       1
   5    4           SORT* (ORDER BY STOPKEY)                           :Q119700
                                                                       0
   6    5             TABLE ACCESS* (FULL) OF 'ACCOUNTS' (Cost=12606 C :Q119700
          ard=2413500 Bytes=222042000)                                 0

   2 PARALLEL_TO_SERIAL            SELECT C0,C2,C3,C4,C5,C6,C7,C8,C9,C10,C11,C1
                                   2,C13,C14,C15,C1 FROM (SELECT A1.C0
   3 PARALLEL_COMBINED_WITH_PARENT
   4 PARALLEL_COMBINED_WITH_PARENT
   5 PARALLEL_TO_PARALLEL          SELECT A1.C0 C0,A1.C1 C1,A1.C2 C2,A1.C3 C3,A
                                   1.C4 C4,A1.C5 C5,A1.C6 C6,A1.C7 C7,A
   6 PARALLEL_COMBINED_WITH_PARENT

But, if I query the maximun value for ID, I get this plan

SQL> c/10000/2700000
  1* Select * from (SELECT * from ACCOUNTS a where id > 2700000 order by id)
where rownum < 5
SQL> /
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3223 Card=11789 Byte
          s=1697616)

   1    0   COUNT (STOPKEY)
   2    1     VIEW (Cost=3223 Card=11789 Bytes=1697616)
   3    2       TABLE ACCESS (BY GLOBAL INDEX ROWID) OF 'ACCOUNTS' (Co
          st=3223 Card=11789 Bytes=1084588)

   4    3         INDEX (RANGE SCAN) OF 'ACCOUNTS_PK' (UNIQUE) (Co
          st=642 Card=11789)

Also the timings are very different. The first query (full scan) took 6.23
seconds while the second (index range scan) took 0.06 second.
Additional information:
- The table is partitioned into 3 partitions by ID (the same column as the PK).
- The unique index of the PK is a global index.
- All the data resides in one partition (the values I'm querying exists in that
partition).
- There are about 2 million rows in the table.

If I hint the first query to use the PK it works as fast as the second query. I
really would like to avoid that hint here cause I guess there is something else
affecting the plan.
Any advice would be very appreciated.
Thanks.
Followup:
you should hint the inner query to be FIRST_ROWS as that is what you really
want.

FIRST_ROWS and ALL_ROWS are two "safe" hints, they tell the optimizer exactly
what you want, what you mean.

this query:

SELECT * from ACCOUNTS a where id > 10000 order by id

should NOT use an index, and does not.  the other one should and does

If you FIRST_ROWS hint it, both will.
Rating: 4
Thanks for your answer but... July 15, 2003
Reviewer: ER from Argentina
Tom, I'm sorry but I still don't understand "why" the first query should not use
an index. Why CBO prefers a full scan instead of an index access in this case.
The cost for the query is greater when it is hinted, and that is what I don't
understand.
What is the difference between 10000 and 27000000 from the CBO point of view?
I detected this behavior on all my partitioned tables, and I'd like to
undesrtand it.
Thank you.
Followup:
in the first case -- it says "hmmmm, lots of rows are > 10000, full scan"

if you tell it "but wait -- I really truly interested in response time, not
throughput" it will say "ok, so maybe this index is best for you.
Rating: 5
Sorry, I got it ! July 15, 2003
Reviewer: ER from Argentina
Sorry Tom, I didn't think in depth your answer.
The first query is not using the index because CBO knows that it has to visit
all the rows in the table since I'm looking for the IDs greater than the minimum
ID in the table. The index is not useful in that case.
Is that rule that states "if you gonna select more that 25% of the table you
should avoid the index" still valid?
Thanks again for your answer.
Followup:
it is not 25% -- it is much more complex then that.  25% is ROT (rule of thumb).

it is a function of the estimated cardinality, the clustering factor, the row
widths, the db_file_multi_block_read_count, etc etc etc etc.
Rating: 4
How to avoid <> condition in queries to make use of indexes? August 12, 2003
Reviewer: Tony from India
My customer table has millions of rows. I want to update the table as below:

    UPDATE cust
       SET column1 = 'some value'
    WHERE cust_type <> '12';

This update is very slow as the index on cust_type column is not used due to <>
condition. I'm forced to use <> condition as its not known what are the other
values the column have and going to have in future. I come across similar
scenario in many places in pro*c application as well as in PL/SQL. what should
be done to make use of the index? Is there any alternative to speed up the
update? I'm using 8.1.6. Please help me out.
Followup:
umm, tell me -- how many rows have cust_type <> 12?

full scanning a table with "millions" of rows takes just a couple of
seconds/minutes.

An index would most likely make this run SLOWER....
Rating: 4
Index FFS or Full table Scan ? August 12, 2003
Reviewer: Haratsaris from Rio de Janeiro,RJ BRAZIL
Tom,
I've got this situation. The optimizer mode is RULE with 8.1.7.4

SQL> select count(*) from table_a;

 COUNT(*)
---------
     1058

SQL> set autotrace traceonly explain

SQL> select count(*) from table_a;

Execution Plan
-----------------------------------------------------
     SELECT STATEMENT Optimizer=RULE
   0   SORT (AGGREGATE)
   1     TABLE ACCESS (FULL) OF 'RA_INTERFACE_LINES_ALL'

SQL> alter session set optimizer_mode=CHOOSE;

Session altered.

SQL> select count(*) from RA_INTERFACE_LINES_ALL;

Execution Plan
-----------------------------------------------------
     SELECT STATEMENT Optimizer=CHOOSE (Cost=18 Card=1)
   0   SORT (AGGREGATE)
   1     INDEX (FAST FULL SCAN) OF 'indtab_4' (NON-U
     NIQUE) (Cost=18 Card=1058)

That seems reasonable and acceptable on performance , 'cause my cardinality on
index is the same as from the table. But I don´t understand two things :

- What says to optimizer that index FFS is better than Full table scan is this
case ?
- Note that there is no where clause on the statement...so  why indexing ?

Thanks !
Followup:
The CBO, having in its tool box many more access methods then the RBO will ever
have, saw that there was an index on the table -- and that the index was on a
NOT NULL set of columns (hence for every row in the table, there must be an
entry in the index itself).  It also new the index consumed less blocks on disk
then the table did.

Hence, it fast full scanned (read using multi-block io) the index and counted
rows in the index rather then full scanning the larger table structure.
Rating: 5
Index FFS or Full table Scan ? August 12, 2003
Reviewer: Haratsaris from Rio de Janeiro,RJ BRAZIL
OK, that's alright. CBO made the job.
But, for example, my APP(Oracle E-Business 11.0.3) requires setting RULE
optimizer as default. So then I replicate the database for testing it with Cost
optimizer, previous analyzing everything and so on...and Explain plains becomes
terrible...with unacceptable performance diagnostics.
So, is it true that even with 8i(even 9i) advanced features avaiable I'm a
"prisioner" of the standard rules for old written SQL/PL-SQL code ( C "Black
box" programs included )?
Thanks !
Followup:
when using COTS applications that use the database, you must follow the rules
laid out by the application vendor.
Rating: 5
Set expire data to far future instead of NULL October 06, 2003
Reviewer: A reader
Hi Tom,

In our system, we have many queries of the following types:

select ...
from ...
where effective_date <= p_date
and (expiry_date >= p_date or expiry_date is null)

May I ask what is the best way to boost the performance of this type of query.

Do you think it a good idea to make all NULL expiry date to a date in the far
future, such as '99991231'.

Thanks a lot.
Followup:
ops$tkyte@ORA920> create table t1 ( effective_date date, expiry_date date, data
char(40) );

Table created.

ops$tkyte@ORA920> create table t2 ( effective_date date, expiry_date date, data
char(40) );

Table created.

ops$tkyte@ORA920> create table t3 as select distinct created dt from
all_objects;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t1
  2  select created, decode( mod( rownum, 10 ), 0, to_date(null), last_ddl_time
), 'x'
  3    from all_objects;

30211 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t2
  2  select effective_date, nvl(expiry_date,to_date('01-jan-4000')), data from
t1;

30211 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index t1_idx on t1(effective_date,expiry_date);

Index created.

ops$tkyte@ORA920> create index t2_idx on t2(effective_date,expiry_date);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_stats.gather_table_stats(user,'T1',method_opt=>'for
all indexed columns',cascade=>true);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec dbms_stats.gather_table_stats(user,'T2',method_opt=>'for
all indexed columns',cascade=>true);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> @trace
ops$tkyte@ORA920> alter session set events '10046 trace name context forever,
level 12';

Session altered.

and then we run:
declare
    type array is table of char(40) index by binary_integer;
    l_data array;
begin
    for x in ( select * from t3 )
    loop
        select data bulk collect into l_data
          from t1
         where effective_date <= x.dt
           and (expiry_date >= x.dt or expiry_date is null);
        select data bulk collect into l_data
          from t2
         where effective_date <= x.dt
           and expiry_date >= x.dt;
    end loop;
end;

with sql trace and find:

SELECT data from t1
         where effective_date <= :b1
           and (expiry_date >= :b1 or expiry_date is null)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute   2123      1.01       1.20          0          0          0           0
Fetch     2123    272.33     339.27         50   21178876          0    13432142
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     4247    273.35     340.47         50   21178876          0    13432142

Rows     Row Source Operation
-------  ---------------------------------------------------
13432142  TABLE ACCESS BY INDEX ROWID T1 (cr=21178876 r=50 w=0 time=284842603
us)
36392296   INDEX RANGE SCAN T1_IDX (cr=136753 r=0 w=0 time=93162618 us)(object
id 40855)
********************************************************************************

SELECT data from t2
         where effective_date <= :b1
           and expiry_date >= :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute   2123      0.97       1.10          0          0          0           0
Fetch     2123    154.79     188.79         44    9133953          0    13432142
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     4247    155.76     189.90         44    9133953          0    13432142

Rows     Row Source Operation
-------  ---------------------------------------------------
13432142  TABLE ACCESS BY INDEX ROWID T2 (cr=9133953 r=44 w=0 time=137071559 us)
13432142   INDEX RANGE SCAN T2_IDX (cr=140470 r=0 w=0 time=48335407 us)(object
id 40856)

there is the potential for savings -- as we can scan less rows via the index
with the simple "and" query -- lots less LIO's, lots less CPU.

remember -- your mileage may vary, it depends on your DATA.  You might consider
copying your data into t1/t2 (your dates) as I did and then running a similar
test with YOUR common inputs to see if such a change would be beneficial to you
or not.
Rating: 5
Set expire data to far future instead of NULL October 06, 2003
Reviewer: A reader
P.S. - I am asking the second question because then one can write the SQL as:

where effective_date >= p_date
and expiry_date <= p_date
Rating: 5
index behavior November 05, 2003
Reviewer: Saminathan Seerangan from NJ,USA
Dear Tom,

Could you please throw ligts on the following index behavior

(a)"name" is an unique index column
(b) table has been analyzed before run the query

1) select id from table1 where name like 'ABC%';
 179 record returned
==>FULL Table scan

2) select id from table1 where name like 'AB%';
 180 record returned
==>Index scan

Your help is always appreciated.
Followup:
lets see the autotrace traceonly explain and the EXACT command used to analyze
the table
Rating: 5
more info about the table November 06, 2003
Reviewer: Saminathan Seerangan from NJ,USA
Dear Tom, as you requested...

Oracle8i Enterprise Edition Release 8.1.7.3.0 - Production
With the Partitioning and Parallel Server options
JServer Release 8.1.7.3.0 - Production

We use OPS(Oracle Parallel Server)

select count(*) from profileduser
  COUNT(*)
----------
     60820
1 row selected

set linesize 200
select index_name,index_type,uniqueness,BLEVEL,CLUSTERING_FACTOR,leaf_blocks
from dba_indexes
where owner='CRMF'
and table_name='PROFILEDUSER'

INDEX_NAME              INDEX_TYPE    UNIQUENESS     BLEVEL CLUSTERING_FACTOR
LEAF_BLOCKS
----------------------- ------------- ---------- ---------- -----------------
-----------
PROFILEDUSER_AK1        NORMAL        UNIQUE              2             59970
    2204
PROFILEDUSER_AK2        NORMAL        UNIQUE              0                 0
       0
PROFILEDUSER_I03        NORMAL        NONUNIQUE           1              1036
      35
PROFILEDUSER_IX01       NORMAL        NONUNIQUE           1              1749
      47
PROFILEDUSER_IX02       NORMAL        NONUNIQUE           1              1824
      43
PROFILEDUSER_IX03       NORMAL        NONUNIQUE           1              1139
      47
PROFILEDUSER_PK         NORMAL        UNIQUE              2             60807
    2295
7 rows selected

set linesize 200
select dcc.constraint_name,constraint_type,column_name
from dba_cons_columns dcc,dba_constraints dc
where dcc.constraint_name=dc.constraint_name
and      dcc.owner=dc.owner
and dcc.owner='CRMF'
and dcc.table_name='PROFILEDUSER'

CONSTRAINT_NAME                CONSTRAINT_TYPE COLUMN_NAME
------------------------------ --------------- --------------------------------
PROFILEDUSER_FK01              R               ROLEPLAYERID
PROFILEDUSER_FK02              R               DEFAULTCONTACT
PROFILEDUSER_PK                P               PROFILEDUSERID
SYS_C007887                    C               PROFILEDUSERID
SYS_C007888                    C               USERID
SYS_C007889                    C               CREATEDATE
SYS_C007890                    C               UPDATEDATE
SYS_C007891                    C               MEMORABLE_COUNT
SYS_C007892                    C               LOGIN_FAIL_COUNT
SYS_C007893                    C               TRANSIENT_LOGIN_FAIL_COUNT
SYS_C008514                    C               PASSWORD_CONVERTED
11 rows selected

select count(*) from profileduser
  COUNT(*)
----------
     60820
1 row selected

SQL> analyze table  crmf.profileduser compute statistics
for table
for all indexes
for all indexed columns;

Table analyzed.

SQL> set autotrace on
SQL> select * from profileduser where userid like 'ABC%';
<<Actual data bas been removed for display purpose>>
1 row selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=681 Card=12607 Bytes
          =1739766)

   1    0   TABLE ACCESS (FULL) OF 'PROFILEDUSER' (Cost=681 Card=12607
           Bytes=1739766)

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

SQL> l
  1* select * from profileduser where userid like 'ABC%'
SQL> c/ABC/AB
  1* select * from profileduser where userid like 'AB%'
SQL> /
<<Actual data bas been removed for display purpose>>
1 row selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=138)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'PROFILEDUSER' (Cost=4 Ca
          rd=1 Bytes=138)

   2    1     INDEX (RANGE SCAN) OF 'PROFILEDUSER_AK1' (UNIQUE) (Cost=
          3 Card=1)

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

SQL> l
  1* select * from profileduser where userid like 'AB%'

SQL> c/AB/HCR
  1* select * from profileduser where userid like 'HCR%'
SQL> /

no rows selected

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=681 Card=12607 Bytes
          =1739766)

   1    0   TABLE ACCESS (FULL) OF 'PROFILEDUSER' (Cost=681 Card=12607
           Bytes=1739766)

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

SQL> l
  1* select * from profileduser where userid like 'HCR%'
SQL> c/HCR/HC
  1* select * from profileduser where userid like 'HC%'
SQL> /

<<Actual data bas been removed for display purpose>>

220 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=2 Bytes=276)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'PROFILEDUSER' (Cost=5 Ca
          rd=2 Bytes=276)

   2    1     INDEX (RANGE SCAN) OF 'PROFILEDUSER_AK1' (UNIQUE) (Cost=
          3 Card=2)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        228  consistent gets
          0  physical reads
          0  redo size
      59178  bytes sent via SQL*Net to client
       1984  bytes received via SQL*Net from client
         16  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
        220  rows processed

SQL> l
  1* select * from profileduser where userid like 'HC%'
SQL> c/HC/HCT
  1* select * from profileduser where userid like 'HCT%'
SQL> /

<<Actual data bas been removed for display purpose>>

207 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=681 Card=12607 Bytes
          =1739766)

   1    0   TABLE ACCESS (FULL) OF 'PROFILEDUSER' (Cost=681 Card=12607
           Bytes=1739766)

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

SQL> spool off
Rating: 5
Sorry for the incomplete info in my previous posting November 06, 2003
Reviewer: Saminathan Seerangan from NJ,USA
select
di.index_name,substr(column_name,1,20),column_position,uniqueness,BLEVEL,CLUSTERI
NG_FACTOR,leaf_blocks
from dba_indexes di,dba_ind_columns dic
where di.index_name=dic.INDEX_NAME
and di.owner='CRMF'
and di.table_name='PROFILEDUSER'

INDEX_NAME                     SUBSTR(COLUMN_NAME,1,20)   COL_POSI UNIQUENESS
 BLEVEL CLUSTERING_FACTOR LEAF_BLOCKS
------------------------------ -------------------------- -------- ----------
---------- ----------------- -----------
PROFILEDUSER_AK1               USERID                            1 UNIQUE
      2             59970        2204
PROFILEDUSER_AK2               LDAPCOMMONNAME                    1 UNIQUE
      0                 0           0
PROFILEDUSER_PK                PROFILEDUSERID                    1 UNIQUE
      2             60807        2295
PROFILEDUSER_I03               BUSINESS_COUNTRY_ID               1 NONUNIQUE
      1              1036          35
PROFILEDUSER_IX01              SEARCH_FIRST_NAME                 1 NONUNIQUE
      1              1749          47
PROFILEDUSER_IX02              SEARCH_LAST_NAME                  1 NONUNIQUE
      1              1824          43
PROFILEDUSER_IX03              SEARCH_COMPANY_NAME               1 NONUNIQUE
      1              1139          47
7 rows selected
Rating: 3
Block Selectivity December 17, 2003
Reviewer: A reader from NJ, USA
Tom,

In the discussions above, in one example, you mention that 25% is a ROT and that
the determination to use or not to use an index depends on lot of factors such
as estimated cardinality, clustering factor, db_file_multiblock_read_count etc.
My questions are :
1. What if the clustering factor is poor and the FIRST_ROWS is used. Will it
still use the index?
2. Does the etc include the BLOCK SELECTIVITY and parameter settings for
..._INDEX_COST_ADJ?
3. What other factors are considered in determining that?
4. How do you identify the estimated cardinality of a query from the EXPLAIN
PLAN? Which of the output info do you look at such as Card=, Bytes= etc. I am
asking this because sometimes the EXPLAIN PLAN output does not reflect the
correct values for these fields. e-g even if a query retrieives say 1000 rows,
the Card= says something like 900. What info. do you use to get the ESTIMATED
CARDINALITY for a query from the EXPLAIN PLAN/TRACE output
Followup:
1) sure.

2) sure.

3) lots -- sort area size, pga aggregate target, db file multi block read count,
too numerous to enumerate

4) CARD *is* the estimated cardinality.

that IS the estimated number of rows.  You get 1,000 -- the explain plan
ESTIMATED "about 900".  that is what an estimation, by definition, is.  A best
guess.
December 18, 2003 sam -- Thanks for the question regarding "UGA/PGA: what are they and where are they ?", version 8.1.5
You Asked
What are UGA and PGA?
When we have dedicated connections,  Are they part of the memory
associated with the
shadow process ?
Suppose I have a SQLPLUS session running on the same UNIX box as
the DB instance.

I did ps -aux|egrep 'LOCAL |COM'
       to  list the shadow process of the SQLPLUS session:

USER       PID %CPU %MEM   SZ  RSS TT       S    START  TIME
COMMAND
oracle   24510  0.0  0.1  896  640 pts/1    S 14:13:20  0:00
egrep LOCA|COM
oracle   22681  0.0  3.3162880132360 ?        S 08:53:19  0:03
oracleprod99

(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL....

The UGA and PGA are parts of the memory associated with process
22681 ?
How large should they be (percentage wise of total memory) ?
Any rule of thumb such as if each of these %MEM should be less
then 5% of total
memory utilization in the 'ps' command ?

Thanks.
and we said...
The User Global Area (UGA) is memory that is associated with a
user session.  The UGA normally comes from the SGA to allow
migration of sessions across processes.  If session migration is
disabled, UGA memory will be allocated from the PGA.

The Process Global Area (PGA) is memory which is physically
private to a process (cannot be accessed by another process).
Note that an OS process and an Oracle session are not
equivalent. The PGA, unlike the SGA, can grow at runtime.

So, in dedicated server mode, when session migration is not
needed, the UGA is in the PGA is in the individual process.

It looks like you are using Solaris.  On Solaris, ps, top and
other tools are virtually useless for looking at memory
usage of Oracle processes.  They report back not only the
process memory but all shared memory attached by the process.
You are seeing much of the  SGA as well as process memory in
that report.

You should investigate the /usr/proc/bin/pmap utility
instead.  It'll report back the breakdown of memory, showing
shared memory segments and shared data from .so files and the
like.
Review & Followup

Rating: 4
Memory per process May 02, 2002
Reviewer: Rukshan Soza from England
Tom,

We are not running on Solaris therefore pmap is not available. Is there any
other way, perhaps querying v$ tables to get this information (ie. PGA memory
usage per session).

Thanks & Rgds

Rukshan
Followup:
v$sesstat joined to v$statname.
Rating: 4
Reader May 02, 2002
Reviewer: A reader
Tom,
"
The UGA normally comes from the SGA to allow migration of sessions across
processes.
"

Are you referring to "migration of session enabled" as MTS
and "across processes" to shared servers

Thanks
Followup:
MTS is shared server.  They are synonymous.

I am referring to the ability to use MTS/shared server, yes.
Rating: 5
parameters for PGA February 05, 2003
Reviewer: GVN from HOUSTON
In your book you mention that
<>One of the largest impacts of size on your PGA/UGA will be the init.ora or
session-level parameters, sort_area_size and sort_area_retained_size.<>

1) What other ini parameterts determine the size of PGA and UGA in a dedicated
server connection?
2) The values sort_area_size and sort_area_retained_size are for each session
and not for the whole database.  Right?
3) If you get a large pool error in a dedicated server connection, what would
that mean?  since the UGA is part of PGA why would I be getting a large pool
error?
4) How would I know if the DB is running out of PGA or UGA?
5) How do you decide on setting the # of db_block_buffers?
Followup:
1) hash area size, bitmap merge area size are the other two.

use of features is the last.  if you fill a plsql table type up with a million
entries, that'll take a bit of memory from your UGA

2) they are session level, yes..

3) show me the error stack and I'll comment.  we use the large pool for other
things (like parallel query)

4) ora-4030 tells you that.  OS tools can watch real memory on the OS (thats
PGA) and if you use MTS, looking at the v$sgastat can help you watch the amount
of large pool you are using.

If you have 9i -- use workarea_size_policy = auto and pga_aggregate_target and
forget about sort/hash/bitmap sizes.  It uses memory in a much nicer way - can
even release memory back to the OS when it doesn't need it anymore.

5) depends on the system.....  depends on the amount of ram, number of users,
configuration (shared/dedicated).  You want to

a) use all of the memory since you cannot put it in the bank and save it for
later
b) use at MOST all of the memory (mimize paging/eliminate swapping)
c) use it for the RIGHT things -- if you put it all in the buffer cache but
short change the shared pool -- it could be deadly.  if you put it all in the
large pool and dont use it, you wasted it and so on.
Rating: 5
pmap example February 05, 2003
Reviewer: A reader
Tom, below is an output from pmap. Could you please break it down and explain
the main memory segments used here ? What are the main SGA and PGA structures
here ?

oracle[oraprd]@srv16:/ora/92> pmap 10355
10355:  oracleoraprd (LOCAL=NO)
0000000100000000  52360K read/exec         /ora/92/bin/oracle
0000000103420000   1040K read/write/exec   /ora/92/bin/oracle
0000000103524000    528K read/write/exec     [ heap ]
0000000380000000 364544K read/write/exec/shared  [ ism shmid=0x3e8 ]
FFFFFFFF7AC00000      8K read/write          [ anon ]
FFFFFFFF7AC10000      8K read/write          [ anon ]
FFFFFFFF7AC50000    192K read/write          [ anon ]
FFFFFFFF7CE00000     24K read/exec         /usr/lib/sparcv9/nss_files.so.1
FFFFFFFF7CF06000      8K read/write/exec   /usr/lib/sparcv9/nss_files.so.1
FFFFFFFF7D000000      8K read/write/exec     [ anon ]
FFFFFFFF7D100000     16K read/exec
/usr/platform/sun4u/lib/sparcv9/libc_psr.so.1
FFFFFFFF7D200000     16K read/exec         /usr/lib/sparcv9/libmp.so.2
FFFFFFFF7D304000      8K read/write/exec   /usr/lib/sparcv9/libmp.so.2
FFFFFFFF7D400000     88K read/exec         /usr/lib/sparcv9/libm.so.1
FFFFFFFF7D516000      8K read/write/exec   /usr/lib/sparcv9/libm.so.1
FFFFFFFF7D600000      8K read/exec         /usr/lib/sparcv9/libkstat.so.1
FFFFFFFF7D702000      8K read/write/exec   /usr/lib/sparcv9/libkstat.so.1
FFFFFFFF7D800000      8K read/write/exec     [ anon ]
FFFFFFFF7D900000     32K read/exec         /usr/lib/sparcv9/librt.so.1
FFFFFFFF7DA08000      8K read/write/exec   /usr/lib/sparcv9/librt.so.1
FFFFFFFF7DB00000     32K read/exec         /usr/lib/sparcv9/libaio.so.1
FFFFFFFF7DC08000      8K read/write/exec   /usr/lib/sparcv9/libaio.so.1
FFFFFFFF7DD00000    712K read/exec         /usr/lib/sparcv9/libc.so.1
FFFFFFFF7DEB2000     56K read/write/exec   /usr/lib/sparcv9/libc.so.1
FFFFFFFF7DEC0000      8K read/write/exec   /usr/lib/sparcv9/libc.so.1
FFFFFFFF7E000000     32K read/exec         /usr/lib/sparcv9/libgen.so.1
FFFFFFFF7E108000      8K read/write/exec   /usr/lib/sparcv9/libgen.so.1
FFFFFFFF7E200000     56K read/exec         /usr/lib/sparcv9/libsocket.so.1
FFFFFFFF7E30E000     16K read/write/exec   /usr/lib/sparcv9/libsocket.so.1
FFFFFFFF7E400000   5264K read/exec         /ora/92/lib/libjox9.so
FFFFFFFF7EA22000    376K read/write/exec   /ora/92/lib/libjox9.so
FFFFFFFF7EA80000      8K read/write/exec   /ora/92/lib/libjox9.so
FFFFFFFF7EB00000      8K read/write/exec     [ anon ]
FFFFFFFF7EC00000    664K read/exec         /usr/lib/sparcv9/libnsl.so.1
FFFFFFFF7EDA6000     56K read/write/exec   /usr/lib/sparcv9/libnsl.so.1
FFFFFFFF7EDB4000     40K read/write/exec   /usr/lib/sparcv9/libnsl.so.1
FFFFFFFF7EE00000     32K read/exec         /ora/92/lib/libskgxn9.so
FFFFFFFF7EF06000      8K read/write/exec   /ora/92/lib/libskgxn9.so
FFFFFFFF7F000000      8K read/exec         /ora/92/lib/libskgxp9.so
FFFFFFFF7F100000      8K read/write/exec   /ora/92/lib/libskgxp9.so
FFFFFFFF7F200000      8K read/write/exec     [ anon ]
FFFFFFFF7F300000      8K read/exec         /ora/92/lib/libodmd9.so
FFFFFFFF7F400000      8K read/write/exec   /ora/92/lib/libodmd9.so
FFFFFFFF7F500000      8K read/exec         /usr/lib/sparcv9/libdl.so.1
FFFFFFFF7F600000    136K read/exec         /usr/lib/sparcv9/ld.so.1
FFFFFFFF7F720000     16K read/write/exec   /usr/lib/sparcv9/ld.so.1
FFFFFFFF7F724000      8K read/write/exec   /usr/lib/sparcv9/ld.so.1
FFFFFFFF7FFE2000    120K read/write          [ stack ]
         total   426632K
Followup:
No, I cannot really.  I can say:

0000000380000000 364544K read/write/exec/shared  [ ism shmid=0x3e8 ]

is the shared memory (obviously) that's the SGA this process has attached.

The rest -- just code, data, stuff.

heap -- that will be pga, thats dynamically allocated memory.
Rating: 5
Great lesson on pmap December 17, 2003
Reviewer: Mike from St. Louis
I ran a test on an 8.1.7 database (dedicated server) where I did the following
in an interactive SQL*Plus session:

  SQL> alter session set sort_area_size = 100000000;
  Ran a query with order-by/group-by that threw > 100M of sort.

I then ran "pmap" and found an expected growth in the heap size:

oracle@grover$ pmap 12662
12662:  oraclelims16 (DESCRIPTION=(LOCAL=no)(ADDRESS=(PROTOCOL=BEQ)))
00010000  26576K read/exec         /app/oracle/product/817/bin/oracle
01A12000    272K read/write/exec   /app/oracle/product/817/bin/oracle
01A56000 102952K read/write/exec     [ heap ]
80000000 277472K read/write/exec/shared  [ ism shmid=0x3eb ]
FEA82000      8K read/write/exec     [ anon ]
FEA94000      8K read/write/exec     [ anon ]
.
.
.

Now, let's say above example is NOT an interactive SQL*Plus session but rather a

background snapshot processes ( ora_snp0_SID - ora_snp3_SID).  Those processes
do not disconnect from the database, right?  Therefore, won't the heap size
persist until the instance is bounced?

That seems to be our situation now because if I run "pmap" on our background
snapshot processes, the heap size for all of them is about 100MB.  We have a
sort-intensive job that runs via dbms_job every hour.  I suspect the job is
getting distributed round-robin between the four background processes and that
explains why each process has a heap size of 100MB.

Can I/should I free the heap memory between dbms_job runs?  It would seem the
memory would be better utilized within the hour if the four background processes
freed the heap for the O/S to reclaim and use.  What do you think?

Thanks for your time,  -Mike
Followup:
yup, the heap will persist HOWEVER, operating systems are smart.

They "use it" or "lose it".  if the snp process isn't really using that 100meg
of allocated memory, well -- it gets paged out.  that is what operating systems
do.

Have you IDENTIFIED THIS HEAP ISSUE as a real issue?  I mean, have you zeroed in
on this and said "hey, you know what, we've figured out that our 100%
performance hit is due exactly to the fact these guys have 100meg heaps".  Or,
is it just sort of "bothering you"?

If you use 9i, with a pga_aggregate_target, you'll find all of these work areas
are dynamically allocated using memmap() on OS's that allow for it and then
released.  the heaps won't grow.

You can alter system set job_queue_processes=0 and then alter system set
job_queue_processes=4

ops$tkyte@ORA817DEV> !ps -auxww | grep '^ora817.*snp'
ora817   24793  0.0  4.79864071392 ?        S 10:45:58  0:00 ora_snp0_ora817dev

ops$tkyte@ORA817DEV> alter system set job_queue_processes=0;

System altered.

ops$tkyte@ORA817DEV> !ps -auxww | grep '^ora817.*snp'

ops$tkyte@ORA817DEV> alter system set job_queue_processes=4;

System altered.

ops$tkyte@ORA817DEV> !ps -auxww | grep '^ora817.*snp'
ora817   25163  0.2  4.79860870392 ?        S 10:51:54  0:00 ora_snp1_ora817dev
ora817   25165  0.1  4.79860870392 ?        S 10:51:54  0:00 ora_snp2_ora817dev
ora817   25167  0.1  4.79860870392 ?        S 10:51:54  0:00 ora_snp3_ora817dev
ora817   25161  0.1  4.79864071320 ?        O 10:51:54  0:00 ora_snp0_ora817dev

all new snaps.
Rating: 5
Thanks again December 18, 2003
Reviewer: Mike from St. Louis
> Have you IDENTIFIED THIS HEAP ISSUE as a real issue?

No real issue yet.  Our site is blessed with a great Solaris Sys Admin who
pro-actively monitors our system metrics.  One of his metrics, freemem (from sar
-r), as been diminishing over time and so we dug into the system/database
processes to find out why.  This led us to the snapshot background processes
with RSS values significantly higher than others on the system.

> ops$tkyte@ORA817DEV> alter system set job_queue_processes=0;
> ops$tkyte@ORA817DEV> alter system set job_queue_processes=4;

Interesting idea.  I tried this and it indeed freed the memory held (per
vmstat).

> They "use it" or "lose it".  if the snp process isn't really using that
> 100meg of allocated memory, well -- it gets paged out.  that is what
> operating systems do.

Hmmm, good point.  Although, it still seems like something to watch especially
if free memory were tight to avoid excessive paging.

Thank you sir for the great lesson on using "pmap" to show the PGA memory
allocated to a process as well as the shared memory segment for the SGA.

-Mike
December 18, 2003 Quinn -- Thanks for the question regarding "Dynamic Dynamic SQL", version 8.1.5
You Asked
Tom, sorry about that I figured it out finally. Thanks. -quinn

I am somewhat familiar with dynamic sql and
can substitute values in the sql e.g.

st := 'select acno from prod.acc where acp = ''PI'' and acno =
:1';

execute immediate st into acno using 500000;

dbms_output.put_line ('st = '||st);
dbms_output.put_line (' acno = '||to_char(acno));

The above is just for example, but what I really want to
substitute is the
table name and column name e.g.

st := 'select max (:1) from :2';
execute immediate st into acno using 'acno','prod.acc';
and we said...
Identifiers cannot be used as bind variables -- ever.
Identifiers must be "hard coded" into the query.

To test if something can be used as a bind variable -- you only
have to consider if you could go into sqlplus and use a
character string constant in that place.   If you can -- you can
use a bind variable.  If you cannot -- you cannot use a bind
variable there.  For example:

SQL> select * from emp where ename = 'KING';

I can put a bind variable in place of 'KING' because I can put a
character string constant there.  On the other hand:

SQL> select * from 'emp' where ename = 'KING';

I cannot put a bind variable in for 'emp' because I cannot
use a character string constant for emp.

The reason is quite simple -- a query with bind variables is
parsed and optimized once before the bind variables are
evaluated.  If I could code:

select * from :x

the optimzer would not have any idea what plan to come up with,
security (access rights) could not be evaluated, and so on.  We
could not develop a plan for that query -- not enough
information.  Therefore, no bind variables for identifiers.

Your example should be:

st := 'select max(' || 'acno' || ') from ' || 'prod.acc';
execute immediate st into acno;
Review & Followup

Rating: 4
one more dynamic dynamic query March 06, 2002
Reviewer: nagaraju
tom,

  in this following how do we do it.

  declare

     a number;
     b varchar2(1) : 'c';
     c varcha2(2) := 'XX';
     d varchar2(2) := 'YY';
     e varchar2(2);

  begin

    if a is null then
      b := 'c';
    else
      b := 'd';
    end if;

    execute immediate 'select ' || b || ' from dual ' into e;

end;

i get an error invalid column name.

can't we refer dynamaically a variable. i know in forms we do it using name_in.
is there any way to do this.

nagaraju
Followup:
execute immediate 'select :x from dual' INTO e USING b;
Rating: 1
i need to refer the value of the variable dynamically not thename March 06, 2002
Reviewer: nagaraju
i run it.

declare
  2
  3  a number;
  4  b varchar2(1) := 'c';
  5  c varchar2(2) := 'XX';
  6  d varchar2(2) := 'YY';
  7  e varchar2(2);
  8
  9  begin
 10
 11  if a is null then
 12  b := 'c';
 13  else
 14  b := 'd';
 15  end if;
 16  execute immediate 'select :x from dual' INTO e USING b;
 17  dbms_output.put_line(e);
 18  end;
 19  /
c

PL/SQL procedure successfully completed

i need the value of the variable c i.e. 'XX'..

again like you refer int FORMS 4.5 ONWARDS NAME_IN OR COPY functions
Followup:
Oh, well, that was not at all clear.

You can ONLY do this with package global variables -- not local variables.

If you had:

create package demo_pkg
as
  a number
  b .....
  e varchar2(2);
end;
/

then

  execute immediate 'begin :x := demo_pkg.' || demo_pkg.b || '; end;'
  using OUT demo_pkg.e;

would put into demo_pkg.e the value of demo_pkg.c if demo_pkg.b had the value of
C in it.
Rating: 5
I guess this fall sunder the perview of dynamic sql June 12, 2002
Reviewer: Nag
I have table t which contains tablenames.

I need to check if value 'xyz' EXISTS IN  a column which exists in all these
tables. The column name in which value 'xyz' exists in all these tables is
CHECK_VERIFY.

What is the best way to select all these table names out of
table t and verify if such values exists in column CHECK_VERIFY.

I cant do it with a regular cursor loop style as I cannot use a cursor value as
table name in the from clause..

How should I do it using dynamic sql..

Thanks
Followup:
for x in ( select table_name from T )
loop
   execute immediate
  'select count(*)
     from dual
    where exists ( select null
                     from ' || x.table_name || '
                    where check_value = :x'
   USING l_some_value
   INTO l_cnt;

   if ( l_cnt > 0 )
   then
       it exists
   else
       it does not
   end if;
end loop;

did not actually run that -- but it's the gist of the answer (might not compile,
but it is very close)
Rating: 5
Interesting question June 13, 2002
Reviewer: A reader
I tried to use your code in Reports . But it complained.
can execute immediate be used in oracle reports and oracle forms
Followup:
execute immediate requires the plsql engine that comes with 8i (the 8.1 plsql
release).

You can put it in a stored procedure and call it.
Rating: 5
sqlplus, reports, forms June 13, 2002
Reviewer: A reader
Does sqlplus, reports, forms have an inbuilt pl/sql engine.
I could not use execute immediate from reports, so it obvious that it does not
have pl/sql engine.

Does sqlplus have a pl/sql engine or does forms does.

So is it like  that, the clients have only sql engines so that sql is checked
for syntax, and when it is sent to the database, the sql and pl/sql are
separated and processed by the respective engines.

Please clarify. This understanding will help a lot.
Followup:
reports and forms have their own engine.  that is it.

sqplus does not, sqlplus is simply an interface to submit sql/plsql to the
database.

the reports/forms clients have BOTH a client side plsql engine and a server side
engine.  They can use both / either or.
Rating: 5
why does execute immediate require a pl/sql engine June 13, 2002
Reviewer: Nag
and

usually there is a database engine containing both sql and pl/sql parser, and
the clients will contain only the sql parser. I'm I right?

(Do you cover the sqlengine and plsql engine , and the database engine stuff
anywhere in your book.)
Followup:
the plsql "engine" is in both reports and forms -- as well as the database.

execute immediate is a plsql "verb", that is why it requires a plsql engine.
Rating: 5
WELL! June 13, 2002
Reviewer: Nag
Tom

1.so can we conclude that the pl/sql engine in oracle forms and reports 6i is
not 8.1 , as such we cannot use execute immediate in them..
2.Well, then what is the version of pl/sql engine in oracle forms and reports
6i?

3.execute immediate is a plsql "verb", -- do you mean that execute immediate is
a plsql construct when you say 'verb'
Followup:
1) in the releases you have, yes.  when you start these tools up, they tell you
what versions they are using (and help -> about should tell you as well)

2) use help -> about to get the definitive answer

3) yes.
Rating: 4
June 13, 2002
Reviewer: A reader
Is this PL/SQL Engine just a compiler of PL/SQL code
Followup:
and interpreter (runtime)

it compiles it
it runs it.
Rating: 5
Coolest July 23, 2002
Reviewer: Rahul from India
Hi Tom

There is little problem for you...

when I execute the below procedure it generates a dynamic insert and executed
but givig following error....

SQL> EXEC SP_COPYAUDITDATA_V1
DELETE
INSERT INTO CODE_AUDIT VALUES( '801','TEST','TEST
TEST','','AUD','','1','23-jul-02','7832','D',SEQ_CODE_AUDIT.NEXTVAL);
BEGIN SP_COPYAUDITDATA_V1; END;

*
ERROR at line 1:
ORA-00911: invalid character
ORA-06512: at "FPIMLVER103.SP_COPYAUDITDATA_V1", line 84
ORA-06512: at line 1

but when I copied the same insert statement and and run it explicitly from
sql*plus it works fine as u can see below..

SQL> INSERT INTO CODE_AUDIT VALUES(
'801','TEST','TEST
TEST','','AUD','','1','23-jul-02','7832','D',SEQ_CODE_AUDIT.NEXTVAL);

Pls. tell me where is the problem...

I'm sending each value with in quotes because avoiding to identify the char
and number becasue oracle implicitly treats the number in single quotes as
numeric value
and except number everything should be in quotes.

Thanks.
Followup:
Thank goodness it FAILED!!!!  It is your LUCKY DAY.

You are not using bind variables -- that is the hugest NO-NO in the world as far
as oracle programming goes.  You have the golden opportunity to correct this
right now!

The error you have is that you included the ';' in the insert statement.  ';'
only means something to sqlplus (means -- finished typing, please run it).  ';'
is not part of the insert itself.

So -- recode the procedure to use bind variables!!!! and lose the ';'
Rating: 4
Small Problem July 25, 2002
Reviewer: Rahul from India
Hi Tom
How r u ??? I'm fine and hope for the same...
one problem for you..

declare
v varchar2(100):='name,roll,class,marks';        --field names of a table
begin
open for cur 'select ' || v || ' ,city,pin from table';
loop
fetch...........
processing....
end loop;
close cur;

above is just sample not actual code..in which dynamic select is firing and
variabke "v" contains few the field names and few are hardcoded.

my prob is that ...if we don't know that what fields, variable "v" will have
next time then how can we fetch the values from dynamic cursor???????

Thanks.
Followup:
You must use the procedural method available via DBMS_SQL in this case.  It lets
you process a query where the number and types of the columns are not known
until runtime.

You can search for dbms_sql on this site to see lots of examples, read the
Supplied packages guide for the documentation -- search for dump_csv on this
site to see an example that takes any query and processes it at runtime.
Rating: 4
Sun Chen Hong Kong October 15, 2002
Reviewer: Sun Chen from Hong Kong
HI, Tom
 I write the stored procedure like the following to get value based on the
column_name, but it get me the actually column name and does not the values of
that columns I want!
How can I modify it? please give me some suggestions, thanks.

DECLARE
v_val_1    VARCHAR2(60);
v_val_2    number := 92;
BEGIN
    FOR rec IN (SELECT column_name
                  FROM user_tab_columns
                 WHERE table_name = 'AC_PR'
                   AND column_name <> 'RUN_NO')
    LOOP
       execute immediate
       'SELECT :X
          FROM ac_pr
         WHERE run_no = :Y'
       INTO v_val_1
       USING rec.column_name,
             v_val_2  ;
    DBMS_OUTPUT.PUT_LINE(v_val_1);
    END LOOP;
END;
/
Followup:
first line of my answer tells you the problem:

....
Identifiers cannot be used as bind variables -- ever.  Identifiers must be "hard

coded" into the query.
.......

loop
   execute immediate
  'select ' || rec.column_name || '
     from ac_pr
    where run_no = :y'
   into v_val_1 using v_val_2;

   ......
Rating: 3
Dynamic SQL Doubt November 26, 2002
Reviewer: Abhijit from Mumbai
Hi Tom,
The previous developer in my project has written the following stored procedure
to execute dynamic sql.

CREATE OR REPLACE PROCEDURE EXEC_SQL(STRING IN VARCHAR2) AS
/* Local Variable Declaration Block */
CURSOR_NAME  INTEGER;
RET          INTEGER;
/* end of MICROSOFTDTPROPERTIES */

BEGIN        -- Start Begin Block 1
 CURSOR_NAME := DBMS_SQL.OPEN_CURSOR;
 BEGIN      -- Start Begin Block 2
  DBMS_SQL.PARSE(CURSOR_NAME,
STRING, DBMS_SQL.native);
  RET := DBMS_SQL.EXECUTE(CURSOR_NAME);
 EXCEPTION    -- Exception for Select Statement
  WHEN OTHERS THEN
   DBMS_SQL.CLOSE_CURSOR(CURSOR_NAME);
   RAISE;
 END         -- End of Begin Block 2
; DBMS_SQL.CLOSE_CURSOR(CURSOR_NAME);
END;        -- End of Procedure

I have written the following stored procedure for achieving the same result.

Create or replace procedure SP_ExecuteSQL(in_String IN varchar2)
Is
begin
  Execute Immediate in_String;
  Exception
          When Others Then
          dbms_output.put_line(SQLERRM);
end;

Could you please tell me what is the difference between the two?

Regards,
Abhijit
Followup:
Neither is as efficient as it could be and yours uses that nasty nasty SQLServer
construct of predicating stored procedures with a pedantic "sp_" ;)

Yours also has a very very nasty bug!

  when others then
      ... let us just silently ignore any and all errors ....

So, when the passed in statement fails -- what do you do?  You ignore it.  A
when others that is not followed by a RAISE statement is almost always a BUG IN
DISGUISE.  In your case, it is most certainly a BUG.

I would SKIP the procedure all together and just code:

    execute immediate p_the_string <using BIND VARIABLES>;

Neither of you are supporting bind variables in your dynamic sql and bind
variables are the single most important thing you can learn.  In SQLServer,
given their "architecture" maybe binds were not important but in Oracle -- since
you do want to scale up lots more -- you'll be wanting to use them.

So, erase the stored procedures.
Use execute immediate

and PLEASE use bind variables!!!
Rating: 4
One More Doubt December 02, 2002
Reviewer: Abhijit from Mumbai
Hi Thomas,
Thanks for getting that across.
I have now dropped both of those stored procedures as advised by you.

I am using EXECUTE IMMEDIATE along with Bind Variables as follows.

           sSQL = "EXECUTE IMMEDIATE " & _
                  "'Insert Into Poets(PID, FNAME, LNAME, COUNTRY)
values(:w,:x,:y,:z)' " & _
                  " USING '" & txtPID.Text & "','" & txtFName.Text & "','" & _
                  txtLname.Text & "','" & txtCountry.Text & "';"

This SQL Statement is printed out as follows.
EXECUTE IMMEDIATE
'Insert Into Poets(PID, FNAME, LNAME, COUNTRY) values(:w,:x,:y,:z)'  USING
'A7','Emily','Bronte','england'

My Poets Table is as follows
Create Table Poets
(
PID char(2),
FNAME varchar2(40),
LNAME varchar2(40),
COUNTRY varchar2(50)
)

Could you please tell me what am I doing wrong?

Cheers!
Abhijit
Followup:
whoa -- hold on there.

Are you writing code in VB or Java?  Looks like VB.

Don't do that AT ALL then.  Use a prepared statement, bind to it and execute it.

for example:
http://groups.google.com/groups?selm=MPG.174376807c7a2b7a98968a%40news.t-online.de&output=gplain
...

when in PLSQL and you are wanting to do dynamic sql -- use execute immediate.
Rating: 4
Useful response December 02, 2002
Reviewer: Abhijit from Mumbai
Hi Tom,
Thanks for your immediate response on EXECUTE IMMEDIATE.
I have figured out another way to do this apart from the link that you sent. Yes
the code is in Visual Basic.

Here is my method.

          sSQL = "BEGIN" & Chr(13)
           sSQL = sSQL & "EXECUTE IMMEDIATE " & _
                  "'Insert Into Poets(PID, FNAME, LNAME, COUNTRY) values(:w,:x,:
y,:z)' " & _
                  " USING '" & txtPID.Text & "','" & txtFName.Text & "','" & _
                  txtLname.Text & "','" & txtCountry.Text & "';"
           sSQL = sSQL & "END;"
            mCn.Execute sSQL 'mCn is my connection object.

This works out as well. Is this binding variables?

Regards,
Abhijit
Followup:
That is NOT BINDING.

Please read the above referenced google link to see the correct and ONLY way you
should be doing this.

If you do not see ? (question marks) in your statement -- you are not using bind
variables.  You are making a sql statement in a string with literals in it.  You
are doing it 100% wrong.

do not use a begin/end block with execute immediate.

Just use an INSERT -- period.  Just like the above google link very clearly
demonstrates!
Rating: 5
Thanks Tom! December 05, 2002
Reviewer: Abhijit from Mumbai
That has cleared up my other doubts as well.

Cheers!
Abhijit
Rating: 4
MS Visual C ++ 6.0 with ADO connection March 13, 2003
Reviewer: Marcio from Brazil
Tom,
Hard to convince my developer colegue Visual-C that bind variable is a solution
to improve performance. This is scenario: he has to call the SAME query about
100 times with distincts values on WHERE clause like 'select * from table_a
WHERE b = 100' and 'select * from table_a WHERE b = 102' and so on.
I am thinking give up to convince him to use bind variable. He says there are
many trouble to use this in his language and bla bla bla ...
I thought use stored procedure, but he refute saying "doesn't work here -- you
don't know write any line on C program, it is different from your pl/sql and SQL
[magic] *Plus ".

Tom, could you help with some piece of information?

Thanks alot!
Marcio
Followup:
the programmer is a wuss -- plain and simple.  A wuss.

send him here:
http://asktom.oracle.com/~tkyte/runstats.html
and use the last link on the page -- where after I explain how the test harness
works, they'll see the results of such a test.
Rating: 5
Similar Situation, would a procedure be good option? May 12, 2003
Reviewer: Meyer from Baltimore, MD
Tom,

I also have a person using VB and ADO that wants to inserts into my database. He
set it up to use non binding SQL and I said no, especially since the insert on
his side will be executed in a loop (1000 times per night into a wide table).

I can make him bind as in the google link or wondering if I created a procedure
like the psudo code below. what effect on the shared pool and would you consider
this binding before oracle? and a good or bad idea?

procedure insert_Row_from_bs_platform
(p1 varchar2, p2 varchar2)
is
begin
insert into table_name
(f1, f2) values
(to_date(p1,'mm/dd/yyyy HH:mi:ss am'), (p2) );
end;

I would put in a package and let him call that procedure (instead of the
insert)...from ADO/VB w/text strings as inputs.

Would that be binding before the shared pool, how would this fit on your scale
of doing things the "right" way?

Also wants to execute many that will fail for dup value...to let the few new
records captured in (and avoid dups as there is no good ID in the other BS
system)...should I force him to program a mechanism for the bulk of that on his
side...or let the dup_val_on_index error in Oracle be his tool.

Other suggestions?

Thanks,
Meyer
Followup:
it he does not bind the procedure call -- you are just as sunk.

Call them a nasty name and then make them AT LEAST code:

alter session set cursor_sharing=force;

before submitting any statements to your database.

For 1,000 rows, let him do it row by row if they want -- just make them use
cursor sharing (even though BINDING is actually easier then not binding....)
Rating: 5
Awesome... May 12, 2003
Reviewer: Meyer from Baltimore, MD
That makes sense...Thanks Tom.
Rating: 4
Can I do dynamic lookups through dynamic sql? May 12, 2003
Reviewer: A reader
Hi Tom,
Source tables data needs to be inserted into target tables (the definitions of
target tables are same as source tables)after converting some of the input
values (standard codes)to the lookup values. The lookup table contains table
name(s) and column name(s)along with input value and lookup value. If the lookup
value is found, substitute the input value with lookup value, insert row in
target table else insert in reject table.
Thanks
Followup:
you could but it would be slower then slow itself.  generic is great, generic is
dog slow for big data loads.

give me more details about the process itself.  sounds like a "join" to me.
sounds like you might want to build:

insert into target
select .....
  from source, lkup1, lkup2, lkup3, ....
 where (joins, outer joins if need be, to lookups)
/

use your generic table to build this SPECIFIC insert and execute it.
Rating: 3
Here are more details... May 13, 2003
Reviewer: A reader
There are plenty of tables to be loaded with weekly run. The source tables
(directly loaded from raw data)contain some code columnns +others with
source_system_code. For example, for codes, employer table has employer status,
employer type columns, and policy has policy type, policy category etc. The
lookup table is built to standardise the codes, as data is coming from disparate
systems. The lookup table contains source_system, table_name, column_name,
date_effective from, date_effective_to, source code, standard code, columns. If
for one system (SS1)  the employer status 'A' means Active, other (SS2) may have
'R' (Recent) as Active. So, the table is built as
SS1, employer (table name), '01-JAN-2000','31-DEC-999', employer status (column
name), A, C (current);
SS2,.........................,R, C. The target tables have referential integrity
with standard codes tables with description like, table employer status table
has status -> A, Description-> Active.
I was asking about building a generic script passing table name as an argument,
get the lookup code columns, go to lookup table, return standard code, and
finally insert into target the record with standard code. If the source code is
not found then reject the record in rejects table.
Is it possible to do through views without the reject part? as I have a generic
script which loads data in target, and puts records in rejects table if FK of
codes tables is violated. In this way, I can use view as input to generic
loading script. If possible, could you please give an example?
Thanks
Followup:
read my first response right above -- after seeing this, it just reaffirms what
I would do.

It would be a single insert into statement.
It would be built at runtime.
It would involve no procedural code to move the data from the source to the
target, only procedural code will be to build the insert into as select
statement.
Rating: 4
How to build lookups at run time... May 15, 2003
Reviewer: A reader
Tom,
Since I did't know all of the outputs at compile time, so I used DBMS_SQL to
insert from source to target tables. The script works fine, but now the source
tables columns having codes need to lookup in the lookup tables before insert
into target tables. As DBMS_SQL's API's DEFINE_COLUMN and COLUMN_VALUE works
with position, how to I lookup in lookup table i.e., how to determine how many
lookup aliases (lkp1, lkp2....) are needed to dynamically passed table?
Followup:
as i SAID (over and over) just build an INSERT INTO as SELECT.

no lookups - JOIN -- databases do that well, they JOIN.  it will do your lookup.

no code, just a query.
Rating: 3
7. Dynamic Dynamic SQL 15 May 2003 18 May 2000 3 years old May 15, 2003
Reviewer: Jigar Shah from Lexington, MA USA
subject: i need to refer the value of the variable dynamically not thename
March 06, 2002
Reviewer:  nagaraju

*I thought it might be helpful to them...

declare
     a number;
     b varchar2(10) := 'c';
     c varchar2(10) := 'XX';
     d varchar2(10) := 'YY';
     e varchar2(10);
abc varchar2(240);
  begin
    if a is null then
      b := '''c''';
    else
      b := 'd';
    end if;
    abc := 'select ' || b || ' from dual ';
    execute immediate abc into e;
dbms_output.put_line(e);
end;
/
Rating: 5
Update where rownum<var_name May 15, 2003
Reviewer: Preeti from USA
Tom,
I have the following question about update query:

we have a loan table: state_cd,
                      user_id,
                      status_cd,
            ....

user_state_served: user_id,
                   state_cd,
                   last_asgnmt_dt

We want to distribute the loans equally to employees authorized to work in the
state.
I do that by updating last_asgnmt_dt  = sysdate and taking the user_id
with min(last_asgnmt_dt) for next assignment.

However, after the initial assignment the workload may be different between
employees
based on number of loans processed in a state.
How do I take the average number of loans in a state and divide equally among
employees

for example for the state of TX, there are 400 loans.
There 4 workers in that state. This the distribution right now:

emp 1:   150
emp2 :      50
emp3:      75
emp 4 : 0

unassigned: 125.

I want to equally divide all the active loans among 4 employees.
Rating: 3
Java Prepared statement and Java dynamic sql mutually exclusive ? May 30, 2003
Reviewer: Alvin from Philippines
Greetings,

Is it possible to use prepared statements (PS)on an dynamic SQL generated by a
front end web app ?

Our head Java programmer told me that only static queries can be PS'ed.

If PS'ed dynamic sql is possible can you provide a simple example ?

Thanks.
Followup:
there is no such thing as "static" queries in Java unless you are using SQLJ
(and then you won't be using jdbc)...

You need a better HEAD java programmer.  Of course, 100% of course, you can use
prepared and even callable statements for dynamic sql generated by a front end
application.

All sql in jdbc is dynamic sql.
There is no such thing as static sql in jdbc.

what a way to start the morning, coding java.  oh well... so we have a web front
end.  It collects inputs from the user (from text fields whatever). It collects
an array of "inputs".  It builds a query based on that.

Say the user has a form like:

Ename Like:       __________
Sal Greater Than: __________
Job Equal To:     __________

and so on.  They enter '%A%' in ename like, they enter 50 in Sal Greater than
(hey, sort of like my advanced search...)

The application builds a query then:

select * from emp where ename like ? and sal > ?

and an array with "%A%", "50" in it.  Then the code that cannot exist according
to your developer would be:

import java.sql.*;
import oracle.jdbc.driver.*;
import oracle.sql.*;

class dynsql
{

public static void run_my_query_properly( Connection conn,
                                          String query,
                                          String[] binds )
throws Exception
{
PreparedStatement pstmt;

    pstmt = conn.prepareCall( query );
    for( int i = 0; i < binds.length; i++ )
        pstmt.setString(i+1,binds[i]);

    ResultSet rset = pstmt.executeQuery();

    while( rset.next() )
        System.out.println( rset.getString(1) );

    rset.close();
    pstmt.close();
}

public static void main(String args[]) throws Exception
{

DriverManager.registerDriver
              (new oracle.jdbc.driver.OracleDriver());

Connection conn =
    DriverManager.getConnection
                     ("jdbc:oracle:thin:@aria-dev:1541:ora817dev",
                      "scott",
                      "tiger");

    String[] values = { "%A%", "50" };

    run_my_query_properly( conn, "select * from emp where ename like ? and sal >
?", values );
}
}

While you might not be able to keep the ps open since the query changes (sort of
like a ref cursor in plsql) in this case -- you CAN (and must -- must must must)
use it so as to support BIND VARIABLES.

I wish, truly wish, jdbc didn't have statements at all -- #1 leading cause of
really poor performance they are.
Rating: 5
Thank you for taking the time and for being so generous in sharing ! June 01, 2003
Reviewer: Alvin from Philippines
Respectfully yours,
Alvin
Rating: 5
Dynamic SQL for private procedures August 07, 2003
Reviewer: Fan from Canada
create or replace package test1 as
  procedure p_public1;
  procedure p_public2;
end;
/

create or replace package body test1 as
  procedure p_private as
  begin
    null;
  end;

  procedure p_public1 as
  begin
    p_private;
  end;

  procedure p_public2 as
  begin
    execute immediate 'begin p_private; end;';
  end;
end;
/

SQL> exec test1.p_public1;

PL/SQL procedure successfully completed.

SQL> exec test1.p_public2;
BEGIN test1.p_public2; END;

*
ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00201: identifier 'P_PRIVATE' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored
ORA-06512: at "PAGO_V20SUNP2.TEST1", line 14
ORA-06512: at line 1

How could I invoke a private procedure using Dynamic SQL?

Thanks.
Followup:
you cannot -- it is not in scope.

consider "execute immediate" to be just like a function -- defined in some other
package.  It is not "inline" -- it is a function call.

hence that private function is not visible.

Just use a CASE of if/then/else -- not only will it be much faster, use less
resources -- but you KNOW you have to add code if you add a function anyway, you
are already modifying that package.
Rating: 3
Using IN for Prepared Statement. September 07, 2003
Reviewer: Divakar from Bangalore,India
Tom,

 Let's assume that we have an input field that can accept multiple values from
user.

 Corresponding SQL statement would be

 Select * from TABLENAME WHERE COL_VAL IN (val1,val2,val3)

 How can we do this in Prepared statement?

 Do we need to create three bind variables in statement?

Regards,
Divakar
Followup:
if there is a reasonable fixed upper bound to the number of inlist items, you
can use

where col_val in (?,?,....,?)

and bind N items (binding nulls when the user supplied no inputs)

if there is no reasonable upper limit, then
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:210612357425
is one method and a global temporary table would be yet another (bulk insert
them and just use "where in (select * from gtt)"
Rating: 5
check for data October 09, 2003
Reviewer: A reader
Hi

I amd trying to write a pl/sql to find invalid synonyms as follows

declare
  x number;
begin
   for i in (select synonym_name from user_synonyms)
   loop
         execute immediate 'select 1 from ' || i.synonym_name || ' where rownum
= 1' into x;
         if x is null then
            dbms_output.put_line(i.synonym_name);
         end if;
   end loop;
end;
/

it seems like X is never null which cannot be true because I have invalid
synonyms. How so? Why x is never null?
Followup:
because, it is throwing an ORA 980 when the synonym is bad:

ops$tkyte@ORA920> create synonym totally_bogus for nothing_up_my_sleeve;

Synonym created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2    x number;
  3  begin
  4     for i in (select synonym_name from user_synonyms)
  5     loop
  6           execute immediate 'select 1 from ' || i.synonym_name || ' where
rownum = 1' into x;
  7           if x is null then
  8              dbms_output.put_line(i.synonym_name);
  9           end if;
 10     end loop;
 11  end;
 12  /
declare
*
ERROR at line 1:
ORA-00980: synonym translation is no longer valid
ORA-06512: at line 6

It never gets to the code "if x is null".  even if it did -- X would just have
the LAST value of whatever was in it -- not null.

ops$tkyte@ORA920> create synonym totally_bogus for nothing_up_my_sleeve;

Synonym created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      synonym_bogus EXCEPTION;
  3          pragma exception_init( synonym_bogus, -980 );
  4  begin
  5     for i in (select synonym_name from user_synonyms)
  6     loop
  7     null;
  8               begin
  9                      execute immediate
 10                        'create or replace view v
                              as select * from ' || i.synonym_name;
 11                   exception
 12                      when synonym_bogus then
 13                              dbms_output.put_line( i.synonym_name );
 14                   end;
 15     end loop;
 16  end;
 17  /
TOTALLY_BOGUS

PL/SQL procedure successfully completed.

is one technique.
Rating: 4
x is never null October 10, 2003
Reviewer: A reader
tried this

set serverout on

select table_name from user_tables where table_name = 'EMP';

TABLE_NAME
------------------------------
EMP

select table_name from user_tables where table_name = 'MP';

no rows selected

declare
  x number;
begin
   for i in (select table_name from user_tables where table_name = 'MP')
   loop
         execute immediate 'select 1 from ' || i.table_name || '
                             where rownum = 1' into x;
         if x is null then
            dbms_output.put_line(i.table_name);
         end if;
   end loop;
end;
/

PL/SQL procedure successfully completed.

We should have seen table_name above

declare
  x number;
begin
   for i in (select table_name from user_tables where table_name = 'EMP')
   loop
         execute immediate 'select 1 from ' || i.table_name || '
                             where rownum = 1' into x;
         if x is null then
            dbms_output.put_line(i.table_name);
         end if;
   end loop;
end;
/

Expected behaviour

It seems that X is never null?
Followup:
I don't understand what your example is trying to show here.

The first loop never executes (there are no tables named MP).  So, it is sort of
"meaningless".  none of the code in the loop was run.

In the second case, EMP did exist, the code DID run, X has the value of 1, 1 is
NOT NULL, hence the body of the if statement was not executed.

expected behaviour all around.
Rating: 4
Stored proc to build tables December 17, 2003
Reviewer: A Reader from CA, USA
Tom:

Thanks much for all the help.

I have a question for you. The following code builds the table when it is called
with no argument at all. Attempt to build the objects in other schemas failed
but the proc successfully completed. What am I doing wrong here? Thanks.

CREATE OR REPLACE PROCEDURE BUILD_SCHEMA (p_schema_name IN VARCHAR2) AS

p_schema VARCHAR2(64);
p_sql    VARCHAR2(2000);
results  VARCHAR2(1000);

BEGIN

  IF p_schema_name IS NOT NULL THEN
     p_schema := p_schema_name || '.';
  END IF;

  p_sql := 'CREATE TABLE ' || p_schema || 'PERSONZ (
    id              NUMBER(*) PRIMARY KEY,
    ts              DATE NOT NULL,
    last_name       VARCHAR2(40) NOT NULL,
    middle_name     VARCHAR2(40),
    first_name      VARCHAR2(40) NOT NULL,
    phone_num       VARCHAR2(32) NOT NULL,
    email_addr      VARCHAR2(241),
    street_addr_1   VARCHAR2(80),
    street_addr_2   VARCHAR2(80),
    city            VARCHAR2(64),
    state           VARCHAR2(3),
    zip             VARCHAR2(16),
    country         VARCHAR2(3),
    sel_security_ques    VARCHAR2(32),
    security_answer      VARCHAR2(32)
    ) TABLESPACE xyz_ds';

   EXECUTE IMMEDIATE p_sql INTO results;

   COMMIT;

   DBMS_OUTPUT.PUT_LINE(p_sql);
   DBMS_OUTPUT.PUT_LINE(results);

   EXECUTE IMMEDIATE 'CREATE SEQUENCE personz_seq';

EXCEPTION  --Error handler
WHEN OTHERS THEN
   DBMS_OUTPUT.PUT_LINE (results);
END;
Followup:
the procedure "completed successfully" because you used the EXCESSIVELY,
extremely bad practice of a "when others" that is not followed by a RAISE
statement.

you are hiding the errors here.  The create table is failing, raising an
exception and exiting the procedure.  since p_results is NULL, you cannot "see"
the error.

Couple of other points

a) the into results -- you are expecting what exactly to be returned??? results
will ALWAYS be null in this procedure.  the into results is meaningless.

b) DDL commits.  The commit is "redundant"

c) this procedure can run once and then will fail on the create sequence every
time afterwards (sequence already exists)

d) get RID of the exception block.  THE ONLY time you want an exception block is
when you are expecting some 'exception' to be thrown (like NO_DATA_FOUND for
example, thats a common one) and you can DEAL WITH it (eg: it is not an error).
Never never code an exception block like you have here.
December 18, 2003 robert -- Thanks for the question regarding "Differrent Character set and NLS", version 8.1.6 and 7.3.4
You Asked
Hi TOM,

 Some question on Character set and NLS.

1) What is the different between US7ASCII and WE8ISO8859P1
character set and NLS?

2) Is there any problem for the DB with two diferrent character
sets to interact,
- trigger
- dblink
- export and import accross the DB.

3) In your opinion, which is more popular or "flexible", US7ACII
or WE8ISO8859P1.

4) I have dbs with US7ASCII and WE8ISO8859P1, do I need to worry
?

Thanks in advance for you response.
robert
and we said...
1) will NLS is national language support and encompasses lots of
things like how to display currency, whether we use a comma or a
dot to separate numbers, how the name of a day is spelled and so
on.

character sets are how we store data.  US7ASCII for example is a
7bit character set, the high bit is "stripped".  The
WE8ISO8859P1 is the standard western european 8 bit character
set.  It can store 2 times as many characters as the 7bit one
can (special characters with umlats and such).

2) there are no "problems" just considerations.  If I export
from an 8bit database and import into a 7bit -- I'll undergo a
characterset conversion.  My 7bit databasebase cannot store any
8bit characters, they will be mapped into the 7bit characterset
(eg: the data will be DIFFERENT, I'll lose some characters that
have the high bit set, they'll become some other character based
on the rules for converting 8bit to 7bit).

Going the other way, from 7bit to 8bit won't encounter any
issues since the 7bit US7ASCII is a subset of the 8bit
WE8ISO8859P1.

You must consider this when using multiple charactersets.  The
strings will be converted from one to the other and you may
"lose" some characters.

3) neither is more popular or flexible.  They both do exactly
what they do.  the 8bit can hold more types of characters so
unless you are doing a US only application, 8bit might be more
appropriate.  I've taken to creating all of my databases with
8bit now as I must support many countries and they get upset
when we lose their special characters.

4) cannot answer that for you.  It depends on what you put in
there.  You can convert the US7ASCII into WE8ISO8859P1 with
later releases of Oracle (not 7.3, do not believe the
functionality was implemented then).  See the NLS guide, chapter
3 for details:
http://technet.oracle.com/doc/oracle8i_816/server.816/a76966/ch3.htm#47136
actually, you may want to read through the entire document as it
answers your questions directly in greater detail then I can
here:
http://technet.oracle.com/doc/oracle8i_816/server.816/a76966/toc.htm
...
Review & Followup

Rating: 4
US7ASCII October 29, 2002
Reviewer: Brigitte from Germany
Hallo Tom,
We use Oracle 8.1.7.2 and an AIX server.
We have a database with the character set US7ASCII. I know it can present
ASCII-Code from 0 to 127. I wonder why the client (works on the AIX server) can
present characters with an ASCII-Code > 127. I read characters with an
ASCII-Code > 127 were convert to ? So we test it with the SVRMGR.

spk15se:astadm 13> echo $NLS_LANG
AMERICAN_AMERICA.US7ASCII

SVRMGR> select * from v$NLS_PARAMETERS;
PARAMETER                                 VALUE

----------------------------------------- ------------------
NLS_LANGUAGE                              AMERICAN
NLS_TERRITORY                             AMERICA
NLS_CURRENCY                              $
NLS_ISO_CURRENCY                          AMERICA
NLS_NUMERIC_CHARACTERS                    .,
NLS_CALENDAR                              GREGORIAN
NLS_DATE_FORMAT                           DD-MON-YY
NLS_DATE_LANGUAGE                         AMERICAN
NLS_CHARACTERSET                          US7ASCII
NLS_SORT                                  BINARY
NLS_NCHAR_CHARACTERSET                    US7ASCII

SVRMGR> select * from nls_database_parameters where parameter
='NLS_CHARACTERSET';
PARAMETER                      VALUE
------------------------------ ------------------------------
NLS_CHARACTERSET               US7ASCII

SVRMGR> update equi set typbz = 'Ö ä € Ü' where equnr like '%400059';

SVRMGR> select typbz from equi where equnr like '%400059';
TYPBZ
--------------------
Ö ä  Ü

SVRMGR> select dump (TYPBZ) from equi where equnr like '%400059';
DUMP(TYPBZ)
----------------------------------------------------
Typ=1 Len=7: 214,32,228,32,128,32,220

How is it possible to store 8bit characters in 7 bit?
Followup:
Because when the client characterset = server character set the data is treated
"as if it were binary" and no conversion is done.  the client is trusted to be
inserting data is says it will (we have to assume the data is in the us7ascii
characterset -- if not, what characterset IS it in and how could we translate
that?  since we cannot -- we do not.
Rating: 4
Good Reminder October 29, 2002
Reviewer: Anthony, Cheuk Tung, LAI from Hong Kong SAR
Actually, this is gray area for me for character set and I will pay more
attention on the characterset the database are using when import/export needs to
be conducted.
Rating: 4
Euro symbol October 30, 2002
Reviewer: Michel Cadot from France
In addition, Euro symbol is neither in US7ASCII nor WE8ISO8859P1 character set.
You have to use WE8ISO8859P15, WE8MSWIN1252, UTF8...
As i notice, in your example, your code for Euro symbol is 128, you have to
choose WE8MSWIN1252. In WE8ISO8859P15 code is 164, and in UTF8 226,130,172
(multibytes character set).

Hope this helps
Rating: 5
Stored bits October 30, 2002
Reviewer: Brigitte from Germany
Hallo Tom,
your answer is very interested, I thought the 8. bit is removed in US7ASCII.

Where can I find Information how many bits are really stored for one character
dependent on the character set if no conversion is done.

Thanks
Brigitte
Followup:
It is ALWAYS bytes -- never bits.  We store BYTES per character, not bits.  We
just don't edit the bytes if the client character set = server character set.
Rating: 5
Conversion at Client? October 30, 2002
Reviewer: Sikandar Hayat Awan from Pindi Bhattian - Pakistan
Hi TOM,
On the server there is US7ASCII character set but I need 128,129,130 and 131 at
client. Is there any way that without converting character set at server from
US7ASCII to WE8ISO8859P1 so I can get the results at client?
Followup:
You have a very square peg and a totally round hole.  All a hammer will do here
is splinter the square peg and splinters hurt lots.

No joy for you here -- the database character set just won't support it -- and
even if you use a US7ASCII client -- you'll just be heading for certain disaster
as someone will eventually use WE8ISO8859P1 or something else -- stripping the
data (that and windows clients default the character set to something OTHER then
US7ASCII)...

You need a database that supports the character set you wish to use.
Rating: 5
Question regarding Nls_date_format October 31, 2002
Reviewer: Ash from Australia
Hi Tom,
     I am not sure whether I should ask this question here or should I raise it
seperately. If so, Please feel free to ignore it.

How come NLS_DATE_FORMAT appears to be different in Unix from that in windows.
I did a simple test. Login through sqlplus in both environments & "select
sysdate from dual ;"
Unix output is in DD-MON-YYYY format while in windoes its
DD-MON-RR format.
Also, values for NLS_DATE_FORMAT in "V$NLS_PARAMETERS" are different.
Shouldn't they be same at database level unless changed using "Alter session..".
Thanks for your wondeful informative site.
Regards,
Ash
Followup:
see
http://asktom.oracle.com/~tkyte/Misc/NLSDateFormat.html
Here is my guess:

a) the init.ora has dd-mon-yyyy or dd-mon-rrrr as the nls_date_format
b) the unix client has a US7ASCII default character set (by default)
c) the database is US7ASCII
d) the windows client is NOT us7ascii (by default they are NOT)
e) the windows client is using the default default NLS_DATE_FORMAT of dd-mon-rr

thats why....
Rating: 4
Character sets are same October 31, 2002
Reviewer: Ash from Australia
Thanks Tom for immediate response.
Below is ourput from v$NLS_PARAMETERS from both env.

UNIX
---
PARAMETER                      VALUE
------------------------------ ----------------------------------
-
NLS_LANGUAGE                   AMERICAN
NLS_TERRITORY                  AMERICA
NLS_CURRENCY                   $
NLS_ISO_CURRENCY               AMERICA
NLS_NUMERIC_CHARACTERS         .,
NLS_CALENDAR                   GREGORIAN
NLS_DATE_FORMAT                DD-MON-YYYY
NLS_DATE_LANGUAGE              AMERICAN
NLS_CHARACTERSET               US7ASCII
NLS_SORT                       BINARY
NLS_TIME_FORMAT                HH.MI.SSXFF AM
NLS_TIMESTAMP_FORMAT           DD-MON-RR HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT             HH.MI.SSXFF AM TZH:TZM
NLS_TIMESTAMP_TZ_FORMAT        DD-MON-RR HH.MI.SSXFF AM TZH:TZM
NLS_DUAL_CURRENCY              $
NLS_NCHAR_CHARACTERSET         US7ASCII
NLS_COMP                       BINARY

17 rows selected.

Windows NT
----------
PARAMETER                      VALUE
------------------------------ --------------------------------------
NLS_LANGUAGE                   AMERICAN
NLS_TERRITORY                  AMERICA
NLS_CURRENCY                   $
NLS_ISO_CURRENCY               AMERICA
NLS_NUMERIC_CHARACTERS         .,
NLS_CALENDAR                   GREGORIAN
NLS_DATE_FORMAT                DD-MON-RR
NLS_DATE_LANGUAGE              AMERICAN
NLS_CHARACTERSET               US7ASCII
NLS_SORT                       BINARY
NLS_TIME_FORMAT                HH.MI.SSXFF AM
NLS_TIMESTAMP_FORMAT           DD-MON-RR HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT             HH.MI.SSXFF AM TZH:TZM
NLS_TIMESTAMP_TZ_FORMAT        DD-MON-RR HH.MI.SSXFF AM TZH:TZM
NLS_DUAL_CURRENCY              $
NLS_NCHAR_CHARACTERSET         US7ASCII
NLS_COMP                       BINARY

17 rows selected.

Also, I have checked this in another organisation from my friend. They also have
this behaviour.
THis seems to me as if we use default settings, we would get this different
behaviour.
Regards,
Ash
Followup:
You are looking in the wrong place.  Here is what I believe to be true:

a) your INIT.ORA on the server has DD-MON-YYYY as the nls_date_format this is
what you need to check

b) your UNIX clients have the same character set as the database - hence they
take the database default which was over-ridden by your init.ora

c) your WINDOWS clients do NOT have the same character set -- they are setting
the NLS_LANG in the registry to something other then US7ASCII.  Hence -- they
are NOT getting the default NLS_LANG from the database -- they are using the
"default default" NLS_LANG (as described in that link i sent you before)

All you've shown is that -- yes, the databases character set is US7ASCII:

[tkyte@dhcp-reston-gen-3-east-123-45 tkyte]$ export
NLS_LANG=AMERICAN_AMERICA.US7ASCII
[tkyte@dhcp-reston-gen-3-east-123-45 tkyte]$ echo $NLS_LANG
AMERICAN_AMERICA.US7ASCII
[tkyte@dhcp-reston-gen-3-east-123-45 tkyte]$ plus

SQL*Plus: Release 9.2.0.1.0 - Production on Fri Nov 1 06:36:12 2002

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

ops$tkyte@ORA920.US.ORACLE.COM> select * from nls_database_parameters;

PARAMETER                      VALUE
------------------------------ ----------------------------------------
NLS_LANGUAGE                   AMERICAN
NLS_TERRITORY                  AMERICA
NLS_CURRENCY                   $
NLS_ISO_CURRENCY               AMERICA
NLS_NUMERIC_CHARACTERS         .,
NLS_CHARACTERSET               WE8ISO8859P1
NLS_CALENDAR                   GREGORIAN
NLS_DATE_FORMAT                DD-MON-RR
NLS_DATE_LANGUAGE              AMERICAN
......

ops$tkyte@ORA920.US.ORACLE.COM> select * from v$nls_parameters;

PARAMETER                                                        VALUE
----------------------------------------------------------------
----------------------------------------------------------------
NLS_LANGUAGE                                                     AMERICAN
NLS_TERRITORY                                                    AMERICA
NLS_CURRENCY                                                     $
NLS_ISO_CURRENCY                                                 AMERICA
NLS_NUMERIC_CHARACTERS                                           .,
NLS_CALENDAR                                                     GREGORIAN
NLS_DATE_FORMAT                                                  DD-MON-RR
NLS_DATE_LANGUAGE                                                AMERICAN
NLS_CHARACTERSET                                                 WE8ISO8859P1
.....

that is NOT the clients NLS_LANG in there, thats the databases.

It is as I described previously.
Rating: 3
Help with Character Set December 11, 2002
Reviewer: Abhijit from Mumbai
Hi Tom,
I was earlier able to run a function, but now it gives me an error stating

select patindex('a',loginid) from logintable
       *
ERROR at line 1:
ORA-06553: PLS-561: character set mismatch on value for parameter 'S2'

However when I change the function to use a convert like this

select patindex('a',convert(loginid,'US7ASCII')) from logintable

PATINDEX('A',CONVERT(LOGINID,'US7ASCII'))
-----------------------------------------
                                        4
                                        4
                                        0
                                        0
                                        4
                                        1
                                        0

It returns me the correct output.

I was wondering, how do I re-set this situation.
Is it possible to alter the character set of a database.
I am no expert at Oracle Databases and I was wondering what should I do to
correct
this situation? I don't want to change the patindex function call,
because that would mean changes to a number of stored procedures in the
database.

These are my current parameters.

SELECT * FROM V$NLS_PARAMETERS;

PARAMETER                      VALUE
------------------------------ -------------------------
NLS_LANGUAGE                   AMERICAN
NLS_TERRITORY                  AMERICA
NLS_CURRENCY                   $
NLS_ISO_CURRENCY               AMERICA
NLS_NUMERIC_CHARACTERS         .,
NLS_CALENDAR                   GREGORIAN
NLS_DATE_FORMAT                DD-MON-RR
NLS_DATE_LANGUAGE              AMERICAN
NLS_CHARACTERSET               US7ASCII
NLS_SORT                       BINARY
NLS_TIME_FORMAT                HH.MI.SSXFF AM

PARAMETER                      VALUE
------------------------------ -------------------------
NLS_TIMESTAMP_FORMAT           DD-MON-RR HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT             HH.MI.SSXFF AM TZH:TZM
NLS_TIMESTAMP_TZ_FORMAT        DD-MON-RR HH.MI.SSXFF AM
                               TZH:TZM

NLS_DUAL_CURRENCY              $
NLS_NCHAR_CHARACTERSET         JA16SJISFIXED
NLS_COMP                       BINARY

What should I do to correct the above situation?

Cheers!
Abhijit
Followup:
patindex?  thats a sybase function -- not familar with it in an Oracle context.

maybe if you tell me:

o what patindex is
o what the nls_lang of the client is
o what the table is, how it was defined

I might be able to look at this.  You should work on figuring out what changed
in your environment to break it
Rating: 3
More info December 12, 2002
Reviewer: Abhijit from Mumbai
Hi Tom,
its me again. Patindex is a function which we wrote to simulate the sybase
function. Basically it returns the location of the character inside a string.
That has two input parameters.
For example -
1) Select PATINDEX('Y','THOMAS KYTE') From DUAL will return a 9

I am sure there are oracle functions which would return the same, but we prefer
to use PATINDEX as a matter of legacy.
Here is the code for patindex.

CREATE OR REPLACE FUNCTION PATINDEX(
S1 VARCHAR2,
S2 VARCHAR2)
RETURN INTEGER
AS
PPOS
INTEGER;
OPOS INTEGER := 0;
TYPE STABTYP IS TABLE OF VARCHAR2(100) INDEX
BY BINARY_INTEGER;
STAB STABTYP;
LOOPC INTEGER;
STARTPOS INTEGER;
CURPOS
INTEGER;
SSTRING VARCHAR2(100);
BEGIN
    PPOS := INSTR(S1,'%');
    WHILE PPOS
!= 0 LOOP
        SSTRING := SUBSTR(S1,OPOS+1,PPOS-OPOS-1);
        IF SSTRING IS NOT
NULL THEN
            STAB(STAB.COUNT+1) := SSTRING;
        END IF;
        OPOS := PPOS;
        PPOS
:= INSTR(S1,'%',OPOS+1);
    END LOOP;
    SSTRING := SUBSTR(S1,OPOS+1);
    IF
SSTRING IS NOT NULL THEN
        STAB(STAB.COUNT+1) := SSTRING;
    END IF;
    STARTPOS
:= INSTR(S2,STAB(1));
    IF STARTPOS > 0 THEN
        CURPOS := STARTPOS + 1;
        FOR
LOOPC IN 2..STAB.COUNT LOOP
            CURPOS := INSTR(S2,STAB(LOOPC),CURPOS);
            IF
CURPOS = 0 THEN
                STARTPOS := 0;
                EXIT;
            END IF;
            CURPOS := CURPOS
+ 1;
        END LOOP;
    END IF;
    RETURN STARTPOS;
END PATINDEX;

Looking at this patindex function, I personally think that an INSTR would
achieve the same result. What is your opinion?

My problem is actually with the NLS_SETTINGS. I am using ORacle 8i on Windows NT
Server.

Funnily enough, when I re-started the server, the settings went back to normal.

PARAMETER                                                        VALUE
----------------------------------------------------------------
----------------------------------------------------------------
NLS_LANGUAGE                                                     AMERICAN
NLS_TERRITORY                                                    AMERICA
NLS_CURRENCY                                                     $
NLS_ISO_CURRENCY                                                 AMERICA
NLS_NUMERIC_CHARACTERS                                           .,
NLS_CALENDAR                                                     GREGORIAN
NLS_DATE_FORMAT                                                  DD-MON-RR
NLS_DATE_LANGUAGE                                                AMERICAN
NLS_CHARACTERSET                                                 WE8ISO8859P1
NLS_SORT                                                         BINARY
NLS_TIME_FORMAT                                                  HH.MI.SSXFF AM
NLS_TIMESTAMP_FORMAT                                             DD-MON-RR
HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT                                               HH.MI.SSXFF AM
TZH:TZM
NLS_TIMESTAMP_TZ_FORMAT                                          DD-MON-RR
HH.MI.SSXFF AM TZH:TZM
NLS_DUAL_CURRENCY                                                $
NLS_NCHAR_CHARACTERSET                                           WE8ISO8859P1
NLS_COMP                                                         BINARY

I want to know how do they get altered in the first place and how do I set them
back?

Cheers!
Abhijit
Followup:
that is a truly horrid idea.  What you are basically saying is:

"hey, we bought Oracle but are so hung up on this sybase stuff we'll just make
Oracle run really slow"

the function you are looking for is instr.

ops$tkyte@ORA920> select instr( 'thomas kyte', 'y' ) from dual
  2  /

INSTR('THOMASKYTE','Y')
-----------------------
                      9

That is my opinion - that you use native database functionality and create plsql
routines ONLY WHEN THERE IS NO FUNCTION built already.

Use it.
Rating: 3
Session level NLS settings December 12, 2002
Reviewer: KU from Nashville, TN, USA
I would like to know if it is possible to check the client side NLS_LANG
settings  in any of v$ or nls_ views once connected to the database.

I would also like to know if there is any way to force a client to use a
different NLS_LANG session level setting thru application in java/cobol/plsql.
(I think in C getenv and setenv might help.)

(e.g: if database NLS_LANGUAGE is WE8ISO8859P1 and client has it set to WE8DEC,
I want to force use of WE8ISO8859P1 to avoid characterset conversion and loss of
data.)

Thanks for taking time to answer this question.

KU
Followup:
the client application would have to check this.  Java is dicey, as Java is all
about UCS and is going to override you anyway in the end.

It is not "loss of data", it is converted data.

there is no way to "force" a client that I know of -- it is the client that
picks this based on what they want.  only the client knows their character set
as well -- it is not available "in the database"
Rating: 5
nls_Date_format December 12, 2002
Reviewer: mo
Tom:

what is the difference between nls_database_parameters and
V$nls_parameters?

How do you change any parameter in a client session?

Thanks,
Followup:
fortunately, we document these:
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96536/ch3119.htm#1095949

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96536/ch2424.htm#1175134
one is the database "permanent" settings, the other is the current settings.

alter session.....
Rating: 4
Do session settings like this apply to work done by a database job ? February 11, 2003
Reviewer: A Reader from UK
Hi Tom,

I need to change the date format behaviour of a pl/sql packaged procedure which
gets executed by a job.  Can you tell me if I do the 'ALTER SESSION SET NLS_'
etc. for date formats will it then affect all work done by the server process
(the snp process) responsible for executing the job after that ?  Or is each job
executed in a new session ?  I tried to find info on how sessions are handled
for dbms_job but failed miserably.

I am thinking the alternative would be to select the current settings from
v$nls_parameter and store them, re-setting them at the end of the job or if the
job fails.

Hopefully you can tell me that it is just for the life of the job, but even then
if the job executes multiple packages I'd rather ensure that this particular
package doesn't affect the others.

Regards,

Paul
Followup:
Actually -- the nls_ info is picked up by default when you submit the job - if
you query user/dba jobs you'll see stuf flike:

         0 clbeck.status_mail( 'TKYTE' );
NLS_LANGUAGE='AMERICAN' NLS_TERRITORY='AMERICA' NLS_CURRENCY='$'
NLS_ISO_CURRENCY='AMERICA' NLS_NUMERIC_CHARACTERS='.,' NLS_DATE_FO
RMAT='DD-MON-RR' NLS_DATE_LANGUAGE='AMERICAN' NLS_SORT='BINARY'
0102000200000000

so you could actually do this at job submission time -- but the job queues will
set the proper NLS settings for each job.
Rating: 5
Excellent. February 11, 2003
Reviewer: A reader
So I have choices....I can configure it in the job, OR from what you are saying
it sounds like I can alter it within the procedure, and it won't affect other
jobs/processes kicked-off by the same server process...i.e. it sounds like (in
effect) a new session is created for each job ?
Followup:
a pristine environment is setup for each job, yes.
Rating: 2
need more infor March 06, 2003
Reviewer: Sandra Chen from Philadelphia,PA
Hi Tom:
We use HEAT as a HELPDESK application.
We set character equal to 'TH8TISASCII' in database (oracle 8.1.7 )base on HEAT
application request.
This time they need to upgrade the HEAT system and requested to change the
character set to 'WE8ISO8859P1'.

but WE8ISO8859P1 is not the superset of TH8TISASCII,
so we got oracle error when we try to change it.

as we go through the document seems the only way is to recreat the database or
use import/export.

1.for using import/export...is that mean do a system level export and drop the
system user, change the character set ,than do a system level import is the
whole procedure ?

If this is right, either recreate database and import/export would be a pain to
us.

2. Is there any possibility can work this around in ORACLE 9i
later on if we still need to do the things like that for the front end
application ?

Thanks
Sandra
Followup:
1) i would just export/import the SCHEMA's I needed.  I don't like to do full
database exports.

Just create a new database.
Export the schemas you want.

you'll need to move public synonyms and users "by hand", but that is a GREAT way
to clean up the clutter.

2) if the app says "i need this character set" and you cannot change to that
characterset, no -- they are forcing your hand here.
Rating: 4
How about using CSSCAN (Character Set Scanner Utility) March 06, 2003
Reviewer: Logan Palanisamy from Santa Clara, CA USA
Tom,

How about using the Character Set Scanner Utility (CSSCAN) and identifying the
problem data, fixing it and then doing 'alter database character set ....'
command.

Chapter 10 of the Globalization Support Guide talks about this utility.
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/server.901/a90236.pdf

Rating: 4
Store special characters in 8.1.7 Database June 12, 2003
Reviewer: S.M. Sundaram from Bangalore, India
How do we store and retrieve characters like &#945; (Alpha), &#915; (gamma), š
(S with reverse cap)

The init.ora parameters are
NLS_CHARACTERSET,WE8ISO8859P15
NLS_NCHAR_CHARACTERSET,WE8ISO8859P15

For alpha when i retrieve data it displayed as "?".
Followup:
but what are the CLIENTS nls settings - that is just as relevant.

My guess - client was different and character set conversion took place,
changing the data.
Rating: 4
still not getting &#945; ß &#915; June 18, 2003
Reviewer: umesh from bangalore, india
&#945;, ß, &#915; , &#960; , &#931;, &#963; , µ in database and applications

I have an application in html which has all html components i.e text box ( input
type = text) . I store data in database which has alpha (&#945;) beta (ß) and so
on special characters when i retreive it it appears as a with 2 dots on it
my nls_character_set is american_america.WE8ISO8859P1
also my nls_lang paramater of client machine on windows 2000
=american_america.WE8ISO8859P1

additionally when i display those records in line mode sqlplus it works fine but
if i try displaying the same in sqlplusw it does not give me a proper display of
special characters even after setting the client settings with nls_lang on
registry
please advice
Rating: 4
About NLS Sort June 20, 2003
Reviewer: David from Toronto, ON, Canada
Hi Tom,

I'm working on a project for a school in Montreal, Quebec. There character set
is WE8ISO8859P1, but most of the data is French. The saving of the French data
is OK. But how can I sort the data in French character order? For example, é
goes between d and f. I've tried to use

select City from City order by nlssort(city,'NLS_SORT=FRENCH')

But I got the following result:

...
Saint-Dunstan-du-Lac-Beauport
Sainte-Adèle
...
Sainte-Croix
Saint-Edmond
Sainte-Dominique
Saint-Édouard-de-Fabre
Sainte-Edwidge-de-Clifton
...

It seemed like when Oracle is doing French NLSSORT, the special characters such
as -/_... are ignored.

Can you help me on this? Any help will be appreciated.

Thanks.

David
Followup:
sorry, that is the way nls sorting is designed to work, those "-"'s just don't
"exist"

I do not know of any method to alter this behaviour.
Rating: 3
Followup NLS Sort June 24, 2003
Reviewer: David from Toronto, ON, Canada
Hi Tom,

Thank you for your response. Do you think changing the character set and using
normal sort will solve this problem?

David
Followup:
not sure, I don't work with different characterset myself
Rating: 5
What's wrong ? June 25, 2003
Reviewer: Ashiq Shamsudeen from Trichy, Tamil Nadu, India
Hi Tom,

  I've 2 database ,one is oracle 9.2.0 its on sun box(solaris 8) and another is
oracle 8.1.7 its on windoze 2000 server.
  nls_characterset and nls_nchar_characterset is set to UTF8 for oracle9i and
WE8ISO8859P1 for oracle8i.

 I did a export from oracle9i and import it to oracle8i.

  I  exported scott schema from a client machine(client version is 8.1.7 on w2k
professional) ,before export I changed the  nls_lang parameter to UTF8 .As
expected , export went through without any error.

see the log file...

Connected to: Oracle9i Enterprise Edition Release 9.2.0.1.0 - 64bit Production
With the Partitioning and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
Export done in UTF8 character set and UTF8 NCHAR character set
. exporting pre-schema procedural objects and actions
. exporting foreign function library names for user SCOTT
. exporting object type definitions for user SCOTT
About to export SCOTT's objects ...
. exporting database links
. exporting sequence numbers
. exporting cluster definitions
. about to export SCOTT's tables via Conventional Path ...
. . exporting table                          BONUS          0 rows exported
. . exporting table                           DEPT          4 rows exported
. . exporting table                            EMP         14 rows exported
. . exporting table                       SALGRADE          5 rows exported
.
.
.

But when i did import from client I'm seeing these messages like the below
one...

import done in UTF8 character set and WE8ISO8859P1 NCHAR character set
import server uses WE8ISO8859P1 character set (possible charset conversion)
export server uses UTF8 NCHAR character set (possible ncharset conversion)
.
.
.

  How to get rid of this message.
Followup:
short of converting the we8iso database to utf8?

You can "change" the message, but you won't get rid of it unless both source and
target are the same.

What it says is "true", there will be a character set conversion -- some data
may change.  That is a fact of life when using multiple charactersets -- you can
put stuff in utf8 that just has NO corresponding character in we8iso...
Rating: 5
July 18, 2003
Reviewer: A reader
Hi Tom,

 If I have set NLS_SORT on database as BINARY sort and on oracle client machine
I don't have NLS_SORT entry on registry (HKEY_LOCAL_MACHINE\Software\Oracle).

 What kind of sort order we would get on oracle client machine if I run
application? (i.e  Would it be BINARY or Others?)

Thanks
Followup:
the default is based on a function of the NLS_LANG , generally binary
Rating: 4
Database characterset September 19, 2003
Reviewer: Naveen from India
Hi Tom,
Our Database character set is WE8ISO8859P1 and the national character set is
AL16UTF16. For the application we are developing, i need to store some Japanese
and Chinese characters. Could you please through some light on how i should be
doing this.

Thank you
Nav.
Followup:
you would put them into an NVARCHAR field as utf can hold them were as
we8iso8859p1 cannot.

have you read through the globalization guide?
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-GLO
Rating: 4
Database characterset September 19, 2003
Reviewer: Naveen
Hi Tom,
I forgot to include the database version. It is  9.2.0.1.0.

Thank you
Nav
Rating: 5
Database Characterset September 22, 2003
Reviewer: Naveen from India
Hi Tom,

Thanks for the reply.

Nav
Rating: 5
Loading data from flat file in different characterset using UTF File package November 10, 2003
Reviewer: Bhavesh from Singapore
Hi Tom,
 I am short of words to express my gratitude for the service you are providing
to a vast Oracle Users community. Actually there is one issue which is bugging
me for a while on Database Characterset. Let me try
to explain you the scenario.
Every month I receive a flat file containing some Japanese, korean characters
and English characters which I need to upload in my database. Currently
everything is working fine. I am using UTL_FILE package where by I
i) read one line of the flat file
ii) based on fixed length break up the columns
iii) upload them in my database.

Following is an excerpt from the program :
procedure read_data_from_file(str in varchar2) is
     begin
        r.cod_category := substr(str, 1, 2);
        r.nam__desc := substr(str, 3, 30);
        r.txt_option := substr(str, 33, 15);
        r.cod_type := substr(str, 48, 8);
        r.cod_model := rtrim(substr(str, 56, 20));
        r.pri_list := to_number(nvl(substr(str, 76, 11),0));
        r.pri_purchase := to_number(nvl(substr(str, 87, 11),0));
        r.num_weight := to_number(nvl(substr(str, 98, 6),0));
........
and so forth.

Later I insert these values into the database. Now the problem is that when I
use the flat file with a database having character set WE8ISO8859P1 the program
works fine.
Next I created another database in UTF8 character set and increase the column
length(s) as desired. Now if I try to import the same file it starts giving me
errors ? Surprisingly the values it is now reading from file is quite different
from the one above i.e. substr(str, 48,8) yields different values if the
database is in WE8ISO8859P1 and in UTF ?
I was under the impression that when reading from flat file with fixed length
delimiter the database should be consistent in reading.
Can you please explain what is happening here and how can I overcome this issue.

Thanking you in advance.
Regards

Bhavesh
Followup:
it is expecting the file to be utf encoded and it must not be.

how is the file itself encoded in the first place???  if it has those multibyte
characters in it?????
Rating: 3
Further Update December 17, 2003
Reviewer: Bhavesh
Hi Tom,
   I have made a variation in my flat file. Instead of fixed width file now I
have got tab delimited file.
   As you have asked "it is expecting the file to be utf encoded and it must not
be".
Actually the files was created from legacy system, on a Japanese OS with the
characterset JA16SJIS. When I open the file on Japanese OS everything looks
fine. Even when I open the txt file in IE and set the Encoding to Auto-Select it
automatically select the JA16SJIS character set and shows the data to me.

"how is the file itself encoded in the first place???  if it has those multibyte
characters in it????? "
The file is a flat file created in JA16SJIS character set and I do not think it
is encoded. My database is UTF8 and the server on which we are doing the
processing is having Windows 2000 SP4 English. My goal is to store both the
Japanese and English characters in UTF8 database properly from this file.
I have gone through a note (Note:227531.1) at metalink
which suggest to convert the file using following command and then insert into
database.
......
line_conv := CONVERT(line_in, 'UTF8', 'JA16SJIS');
insert into utl_conv_example values(line_conv);
......
I tried to replicate the same. When I read one line of file and insert into
database as a single column everything looks fine. But actually the file has got
various columns which are delimited by tab. I want to read each column, store it
in a local variable (within package) and then insert all columns of one record
into database.
When I try to do this, then in some records the file skips one tab there by
making two columns as one. What I now want to know is :
A) Should I read one line as a whole, convert it into UTF and then use the
instr, substr command to find columns. OR
B) Should I read each column delimited by tab (using instr, substr) and then use
convert function to convert data to UTF8.
Can you please throw some light on how to accomplish this. Thanking you in
advance.
Followup:
open it with a binary editor and you'll probably find it is in fact encoded.
The fact that IE can auto detect says "this is the case" (that it is encoded)

you can specify the files character set right in sqlldr.
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96652/ch01.htm#1011906

Rating: 5
Thanks a million December 21, 2003
Reviewer: Bhavesh
Thanks Tom. After going through your response things are more clear and now I
think I should be able to get the desired results. Thanks once again.
December 18, 2003 Oliver -- Thanks for the question regarding "Dynamic Menus", version 8.1.5
You Asked
Is it possible to generate menus dynamically, say users want to
add, update, delete menu items in forms.  The forms version is
6.0.5.31.0
and we said...
You cannot create them on the fly but...

you can build menus with lots of items on them.  At runtime you
can

o hide them
o show them
o set their label
o enable/disable them (grey them out)
o check/uncheck them

So, in effect, if you built a menu with labels

"item1"
"item2"
"item3"

At runtime, it could be changed by you to be simply:

"Open"
"Exit"

for example.
Review & Followup

Rating: 3
December 18, 2003
Reviewer: huss from kuwait
i want to scan some menus files (*.mmb) to get out the menu items details
i.e : items name , items lable
do u have any idea about how do that??!
Followup:
nope, try otn.oracle.com -> discussion forums

I don't really do forms
December 18, 2003 Rennie -- Thanks for the question regarding "Usage of Analytic Functions within a query having grouping", version 8.1.7
You Asked
Tom,
Table tab1 has 3 columns col1,col2 and col3
   I have a query grouped on col1. Columns col2 and col3 have non unique values
for a particular value of col1. I want the value of col2 for the row having
maximum value of col3 pertaining to the col1 grouping.
Tab1
col1 col2 col3
'A'  'x'   1
'A'  'x'   3
'A'  'y'   5
'B'  'c'   9
'B'  'd'   2
'B'  'c'   7
   In this case i want my output to be

col1 max_val
'A' 'y'
'B' 'c'

Its something like, if you treat col1 as a class and col2 as student and col3 as
marks. I want the student having maximum marks in that class. But, the column
col3 contains marks for all subjects. SO, the student will have multiple records
under that class.

CREATE TABLE tab1 (col1 VARCHAR2(10),col2 VARCHAR2(10), col3 NUMBER(10));

        INSERT INTO tab1(col1,col2,col3) VALUES('A','x',1);
        INSERT INTO tab1(col1,col2,col3) VALUES('A','x',3);
        INSERT INTO tab1(col1,col2,col3) VALUES('A','y',5);

        INSERT INTO tab1(col1,col2,col3) VALUES('B','c',9);
        INSERT INTO tab1(col1,col2,col3) VALUES('B','d',2);
        INSERT INTO tab1(col1,col2,col3) VALUES('B','c',7);

        SELECT col1, <greatest col2> FROM tab1 GROUP BY col1;

Please suggest me a method other than to use an inline view. Because in the real
scenario col2 and col3 come from an inline view are they are obtained using
analytic functions.

Iam not able to combine analytic function within a grouping and it gives me
wrong result. Please throw some light on this in general also, since i have lot
many queries affected by this. The error i get is that 'Window functions are not
allowed here'.

Please advice.

regards
rennie

Hi Tom,
     Iam sorry that i replied to this id. Its just that your site does not
reflect multiple updations. Here is the query i was trying on. I have added
the table creation and insert sql's but could not add this also.

I wanted something like this.

SELECT col1, MAX(FIRST_VALUE(col2) OVER (PARTITION BY col1 ORDER BY col3
DESC))
FROM tab1
GROUP BY col1;

This gives error - ORA-30483. My question is that on a query that has a
group by clause, how do i use an analytic function.
and we said...
Let's say it was valid -- what would it return?  It would return the MAX(
constant ) for a given col1.  It seems you want

select DISTINCT col1,
                FIRST_VALUE(col2) OVER (PARTITION BY col1 ORDER BY col3 DESC)
  from tab1;

Since first_value (being partitioned by col1) will effectively be a CONSTANT by
col1 -- distinct is what you are looking for.

scott@ORA817DEV.US.ORACLE.COM> select deptno,
  2             max(first_value(sal) over (partition by deptno order by job DESC
)) fv
  3    from emp
  4   group by deptno
  5  /
           max(first_value(sal) over (partition by deptno order by job DESC ))
fv
               *
ERROR at line 2:
ORA-30483: window  functions are not allowed here

scott@ORA817DEV.US.ORACLE.COM> select DISTINCT deptno,
  2             first_value(sal) over (partition by deptno order by job DESC )
fv
  3    from emp
  4  /

    DEPTNO         FV
---------- ----------
        10       5000
        20       2975
        30       1250

scott@ORA817DEV.US.ORACLE.COM>

Alternatively, but not as good, you can

scott@ORA817DEV.US.ORACLE.COM> select deptno, max(fv)
  2    from ( select deptno,
  3            first_value(sal) over (partition by deptno order by job DESC ) fv
  4             from emp
  5             )
  6   group by deptno
  7  /

    DEPTNO    MAX(FV)
---------- ----------
        10       5000
        20       2975
        30       1250

And yes, I allow you ONE chance to update a question -- your update will flip a
bit and the question becomes mine again -- so if you prematurely submit the
update -- the question goes into my queue and your updates don't "stick".  I've
added an error message to that effect now -- you'll be told "the question is in
tom's queue"...
Review & Followup

Rating: 3
Analytic Function June 03, 2002
Reviewer: Rennie Sreekumar from Boston,US
Thanks tom,
     Your response was fast. What i wanted was to have it in the same query
avoiding an inline view. Seems that is not possible.
     Can you pls tell me why oracle does not support some thing like GROUP
FUNCTION on (<analytic function>).
For eg:-
MAX(FIRST_VALUE(col2) OVER (PARTITION BY col1 ORDER BY col3DESC))

regards
rennie
Followup:
Because it doesn't make SENSE?  Your query doesn't really "compute" (to me
anyway)

Distinct is exactly what you are looking for.

select DISTINCT col1,
                FIRST_VALUE(col2) OVER (PARTITION BY col1 ORDER BY col3 DESC)
  from tab1;

that

a) has no inline view
b) gets you the answer
c) makes "sense"
Rating: 2
Could Analytic Functions be Used Here? December 16, 2003
Reviewer: Richard from Oxfordshire, UK
Hi,

My set up:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

I have a table named myTable, created as follows:
DROP TABLE  myTable
/

CREATE TABLE  myTable (
                       Ship_date  DATE         NOT NULL
                      ,Ship_type  VARCHAR2(10) NOT NULL
                      ,Shipment   NUMBER(6,0)  NOT NULL
                      )
/

It has data in it as follows:
SHIP_DATE  SHIP_TYPE    SHIPMENT
---------- ---------- ----------
01-01-2000 SHIP1              27
01-01-2000 SHIP1              26
01-01-2000 SHIP1              25
01-01-2000 SHIP2              24
01-01-2000 SHIP2              23
01-01-2000 SHIP2              22
01-01-2000 SHIP3              21
01-01-2000 SHIP3              20
01-01-2000 SHIP3              19
01-01-2001 SHIP1              18
01-01-2001 SHIP1              17
01-01-2001 SHIP1              16
01-01-2001 SHIP2              15
01-01-2001 SHIP2              14
01-01-2001 SHIP2              13
01-01-2001 SHIP3              12
01-01-2001 SHIP3              11
01-01-2001 SHIP3              10
01-01-2002 SHIP1               9
01-01-2002 SHIP1               8
01-01-2002 SHIP1               7
01-01-2002 SHIP2               6
01-01-2002 SHIP2               5
01-01-2002 SHIP2               4
01-01-2002 SHIP3               3
01-01-2002 SHIP3               2
01-01-2002 SHIP3               1

I wish to select only those rows that have the greatest Shipment value, per
Ship_type, per Ship_date. The result I want is:
SHIP_DATE  SHIP_TYPE    SHIPMENT
---------- ---------- ----------
01-01-2002 SHIP1               9
01-01-2002 SHIP2               6
01-01-2002 SHIP3               3

I have created a query which does this, as follows:
SELECT  ship_date, ship_type, shipment
  FROM  myTable
 WHERE  TO_CHAR( TRUNC( ship_date ))
        ||
        ship_type
        ||
        TO_CHAR( shipment )
        IN
        (
          SELECT  MAX
                  (
                  TO_CHAR( TRUNC( ship_date ))
                  ||
                  ship_type
                  ||
                  TO_CHAR( shipment )
                  )
            FROM  myTable
        GROUP BY  ship_TYPE
        )
/

However, I think it's clunky and is probably a rubbishy way of going about it.
Is my suspicion that Analytics might be of use?

Thanks in advance.

P.S. myTable can be populated with:
BEGIN
INSERT INTO myTable VALUES('01-01-2000', 'SHIP1', 27);
INSERT INTO myTable VALUES('01-01-2000', 'SHIP1', 26);
INSERT INTO myTable VALUES('01-01-2000', 'SHIP1', 25);
INSERT INTO myTable VALUES('01-01-2000', 'SHIP2', 24);
INSERT INTO myTable VALUES('01-01-2000', 'SHIP2', 23);
INSERT INTO myTable VALUES('01-01-2000', 'SHIP2', 22);
INSERT INTO myTable VALUES('01-01-2000', 'SHIP3', 21);
INSERT INTO myTable VALUES('01-01-2000', 'SHIP3', 20);
INSERT INTO myTable VALUES('01-01-2000', 'SHIP3', 19);
INSERT INTO myTable VALUES('01-01-2001', 'SHIP1', 18);
INSERT INTO myTable VALUES('01-01-2001', 'SHIP1', 17);
INSERT INTO myTable VALUES('01-01-2001', 'SHIP1', 16);
INSERT INTO myTable VALUES('01-01-2001', 'SHIP2', 15);
INSERT INTO myTable VALUES('01-01-2001', 'SHIP2', 14);
INSERT INTO myTable VALUES('01-01-2001', 'SHIP2', 13);
INSERT INTO myTable VALUES('01-01-2001', 'SHIP3', 12);
INSERT INTO myTable VALUES('01-01-2001', 'SHIP3', 11);
INSERT INTO myTable VALUES('01-01-2001', 'SHIP3', 10);
INSERT INTO myTable VALUES('01-01-2002', 'SHIP1',  9);
INSERT INTO myTable VALUES('01-01-2002', 'SHIP1',  8);
INSERT INTO myTable VALUES('01-01-2002', 'SHIP1',  7);
INSERT INTO myTable VALUES('01-01-2002', 'SHIP2',  6);
INSERT INTO myTable VALUES('01-01-2002', 'SHIP2',  5);
INSERT INTO myTable VALUES('01-01-2002', 'SHIP2',  4);
INSERT INTO myTable VALUES('01-01-2002', 'SHIP3',  3);
INSERT INTO myTable VALUES('01-01-2002', 'SHIP3',  2);
INSERT INTO myTable VALUES('01-01-2002', 'SHIP3',  1);
COMMIT;
END;
/
Followup:
ops$tkyte@ORA9IR2> select * from (
  2  select myTable.*, max(shipment) over (partition by ship_date, ship_type)
max_shipment
  3    from myTable
  4    ) where shipment = max_shipment;

SHIP_DATE  SHIP_TYPE    SHIPMENT MAX_SHIPMENT
---------- ---------- ---------- ------------
01-01-2000 SHIP1              27           27
01-01-2000 SHIP2              24           24
01-01-2000 SHIP3              21           21
01-01-2001 SHIP1              18           18
01-01-2001 SHIP2              15           15
01-01-2001 SHIP3              12           12
01-01-2002 SHIP1               9            9
01-01-2002 SHIP2               6            6
01-01-2002 SHIP3               3            3

9 rows selected.
Rating: 5
December 16, 2003
Reviewer: A reader
!!!!!
Rating: 3
A Bit More Help, Please! December 18, 2003
Reviewer: Richard from Oxfordshire, UK
Hi,

I didn't word my initial request very well (my apologies).

What I SHOULD have asked is:

I wish to select only those rows that have the greatest Shipment value, per
Ship_type, per Ship_date, showing ONLY those records with greatest Ship_date per
Ship_type. The result I want is:
SHIP_DATE  SHIP_TYPE    SHIPMENT
---------- ---------- ----------
01-01-2002 SHIP1               9
01-01-2002 SHIP2               6
01-01-2002 SHIP3               3

i.e. using the data I supplied, I would wish to see the 3 records above only.

As an aside, does using TO_CHAR (as I have done in my supplied example) *on the
fly* perform better, worse, or the same than a function that would do the same?

Merry Christmas & Happy New Year!
Followup:
just use the same technique!  a simple variation on a theme.

ops$tkyte@ORA920PC> select *
  2    from (
  3  select myTable.*, max(ship_date) over (partition by ship_type)
max_ship_date
  4    from (
  5  select myTable.*, max(shipment) over (partition by ship_date, ship_type)
max_shipment
  6    from myTable
  7    )  myTable
  8   where shipment = max_shipment
  9   )
 10   where ship_date = max_ship_date
 11  /

SHIP_DATE  SHIP_TYPE    SHIPMENT MAX_SHIPMENT MAX_SHIP_D
---------- ---------- ---------- ------------ ----------
01-01-2002 SHIP1               9            9 01-01-2002
01-01-2002 SHIP2               6            6 01-01-2002
01-01-2002 SHIP3               3            3 01-01-2002

I don't know how to answer your other question -- as I don't see how "a
function" (whatever you mean by that -- as TO_CHAR is itself a function) would
help at all.  not sure what you are asking to compare.
Rating: 5
Excellent Answers! December 20, 2003
Reviewer: Richard from Oxfordshire, UK
Thank you very much for your help!

Forget about my second question regarding TO_CHAR... I'm no longer sure what I
meant myself!!!
December 18, 2003 Mitra -- Thanks for the question regarding "vertical & horizontal partitioning table: replication", version 8i
You Asked
Hi Tom
Thanks alot for your helps.
I'm sorry for last events in US.
we have multi master replication with 3 master site.
and we have a table that want to replicate them in 3 master site a,b,c.
we want replicate only 2 columns of table at site c.
and replicate rows with condition at site b.
how can I do it?
with the best regards
Mitra
and we said...
You will use read only or updatable snapshots.  They allow you to vertically or
horizontally slice and dice the data in replication.

See
http://technet.oracle.com/doc/oracle8i_816/server.816/a76959/mview.htm#25269
for details.
Review & Followup

Rating: 1
vertical partitioning December 18, 2003
Reviewer: ankit sood from India
I have not any Document on this vertical partitioning.
Can you provide me the same.
Followup:
umm, it is just putting some columns in table 1, some in table 2.

A veritical "slice"

only "some of the columns"

whereas a horizontal slice would be "some of the rows".
December 18, 2003 Vikas -- Thanks for the question regarding "Oracle - Optimising the pattern matching (LIKE '%ABC')", version 8.1.7
You Asked
We store all the vehicle details in a table. In the display screen we allow the
users to search for a vehicle by providing some part of chassis number.
Presently we allow only for initial part i.e. ABC% or A% queries. However users
sometimes know the last digits/alphabets & would like to query as %12 or %64. If
we provide for such queries the performance is low as the index on the chassis
number is not used. Is there any new feature or some workaround to improve the
performance.

We will not allow for
select * from vehicle_table where chassis_number like '%56%' queries.

So the query for optimization will be:
select * from vehicle_table where chassis_number like '%56'.
and we said...
function based indexes could work very well here (see
http://asktom.oracle.com/~tkyte/article1/index.html
or I have a chapter on them in my book "Expert one on one Oracle")...

I used the undocumented reverse function (which might not work in PLSQL, you
might have to use DYNAMIC SQL in plsql to use it)

You could write your OWN reverse function in PLSQL instead to avoid using the
undocumented (and hence, not really "supported") function....

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t as select * from all_users;
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx1 on t(reverse(username));
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx2 on t(username);
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_stats.set_table_stats( user, 'T',
numrows => 100000, numblks => 100000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set query_rewrite_enabled=true;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set
query_rewrite_integrity=trusted;

Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where username like 'OPS$%';

USERNAME                          USER_ID CREATED
------------------------------ ---------- ---------
OPS$CLBECK                             30 09-SEP-01
OPS$ORA817                             27 28-AUG-01
OPS$TKYTE                             578 14-OCT-02

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=10 Card=5000 Bytes=195000)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=10 Card=5000
Bytes=195000)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX2' (NON-UNIQUE) (Cost=2 Card=5000)

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where reverse(username) like
reverse('%KYTE');

USERNAME                          USER_ID CREATED
------------------------------ ---------- ---------
OPS$TKYTE                             578 14-OCT-02

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=10 Card=5000 Bytes=195000)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=10 Card=5000
Bytes=195000)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX1' (NON-UNIQUE) (Cost=2 Card=5000)

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off

see, they both use indexes!
Review & Followup

Rating: 5
Brilliant October 14, 2002
Reviewer: Jim
Right... that one is going straight into the
"toolkit"
Rating: 4
December 15, 2002
Reviewer: Gisel from '%ABC%'
Your approach solves problems like '%ABC' and 'ABC%' but if you'd want to solve
'%ABC%' using only one index how would you do?

Regards
Gisel
Followup:
You could use the index as a "skinny table" is all.  Something to generate
rowids from to access the base table.

Think about it -- every single row in the entire table must be inspected in
order to do %abc%.  An index cannot be used to inspect just "some" of the rows
in this case obviously.

big_table@ORA920.US.ORACLE.COM> select object_name from big_table where
object_name like '%ABC%';

1088 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=978 Card=93821 Bytes=1594957)
   1    0   INDEX (FAST FULL SCAN) OF 'BIG_TABLE_IDX1' (NON-UNIQUE) (Cost=978
Card=93821 Bytes=1594957)
Rating: 5
N-grams December 15, 2002
Reviewer: Anders from Denmark
If you need to be able to search for '%ABC%', you could have a look at n-grams.
If you have the word "oracle" with the id 34, you would in an index-table insert
(34, "ora"), (34, "rac"), (34, "acl") and (34, "cle"). For a search on "acl",
you can do a direct lookup on the index-table and get the ids of the rows
containing the substring.
Can a function based index be used here? (i.e. can the function "return more
than one row"?)
This might be a good time to roll out intermedia.
Rating: 3
What about Oracle text???? December 15, 2002
Reviewer: Dan from Philadelphia PA USA!
I would use a context index on this problem.
Rating: 5
excellent January 16, 2003
Reviewer: A reader
Rating: 2
Intermedia performance not so great August 05, 2003
Reviewer: Xavier from Oakland, CA
I wish Intermedia indexing worked better.  I have a table with about 600,000
rows, having both conventional and Intermedia indexing on some important text.
I find a query this:

foo like '%str%' or foo like '%bar%' actually runs quite a bit faster than a
CONTAINS query on str or bar.  The % query runs in <10 secs, the CONTAINS query
can take over a minute.  This is quite absurd and I've been looking for
something I'm doing wrong.  A contains query on a phrase works very fast, e.g.
CONTAINS with 'foo bar' runs like greased lightning.  It's the OR conditions
that kill it.
Followup:
give me a concrete example to work with.  you aren't calling contains over and
over are you - you are using contains correctly right?

ask_tom@ASKUS> select count(*) from wwc_ask_indexed_questions$ where contains(
text, 'hello' ) > 0;

  COUNT(*)
----------
       709
Elapsed: 00:00:00.38
ask_tom@ASKUS> select count(*) from wwc_ask_indexed_questions$ where contains(
text, 'world' ) > 0
  2  /

  COUNT(*)
----------
       918
Elapsed: 00:00:00.50
ask_tom@ASKUS> select count(*) from wwc_ask_indexed_questions$ where contains(
text, 'hello OR world' ) > 0
  2  /

  COUNT(*)
----------
      1436
Elapsed: 00:00:00.74
Rating: 4
LIKE vs. Intermedia December 17, 2003
Reviewer: Andy from Germany
I need to compare a column to a set of values, and include this comparison in a
FGAC policy. The "set of values" are read from another table at runtime. The
comparisons are all against short, distinct words, or against trailing wildcards
(i.e. Z% and not %Z or %Z%). The easiest way to do this for me would be to use
intermedia i.e.

select count(*) from fulltext where contains(oe,'test OR Z%', 1) > 0

...but an alternative would be to examine the target values, and contruct
individual LIKE clauses for each one - which would be a pain:

select count(*) from fulltext where oe like 'test' or oe like 'Z%'

(in this example I've got two terms to compare against - 'test' and Z%' - but I
might have just 1 or as many as 10).

Because this comparison will be used by FGAC (and therefore applied to just
about every single query), I'm concerned about the performance. For example, a
simple test in SQL*Plus shows the following in TKPROF:-

TKPROF: Release 9.2.0.1.0 - Production on Wed Dec 17 10:51:30 2003
********************************************************************************

select count(*) from fulltext where contains(oe,'test OR Z%', 1) > 0

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.09       0.27          0         30          0           0
Execute      2      0.03       0.03          0          0          0           0
Fetch        4      0.04       0.11          0         26          0           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        8      0.16       0.42          0         56          0           2

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 62

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
    499   DOMAIN INDEX  FTI_OE

********************************************************************************

select count(*) from fulltext where oe like 'test' or oe like 'Z%'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.04       0.06          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.01       0.02          0         17          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.05       0.08          0         17          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 62

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
    499   INDEX FAST FULL SCAN IX_COMBO1 (object id 33071)

********************************************************************************

i.e. the CONTEXT search seems to do more work. What I'd really like to be able
to do would be:

select .... where oe in('test','Z%'...)

but that won't work because of the wildcards! Would you go with the LIKE option?

(I'm on Oracle 9.2.0.1.0).
Followup:
you have only 499 rows?

how big was your test.....

and if you scale it up what does it do...

have you looked at the ctxcat index type instead
http://download-west.oracle.com/docs/cd/B10501_01/text.920/a96517/cdefault.htm#1006111
(and this approach is not going to be "the most performant in the world", you
have to expect a measurable impact -- very measurable impact -- with this
particular requirement)
Rating: 4
why is reverse undocumented/unsupported? December 17, 2003
Reviewer: pete
Tom,

Why is an obviously useful function like REVERSE not supported by Oracle?  Am I
right in thinking that the only reason it exists is that it is used internally
by Oracle?  If that is the case then why not include it in the supported
features and budle it into PL/SQL?

Thanks,
Pete
Followup:
don't know - there are lots of little internal functions (bitand used to be one
until recently).  the way to get them out there is to file an enhancement
request via support (thats how bitand got "published" and alter session set
current_schema and so on)
Rating: 2
How about utl_raw.REVERSE Function December 18, 2003
Reviewer: pasko from Germany, Hamburg
Hi Tom,
i just came across another Reverse Function in UTL_RAW Package.

may be this could be used but the problem is that it works only on RAWs.

For Example:
SQL> SELECT object_name , utl_raw.cast_to_varchar2(
  2       utl_raw.REVERSE(
  3       utl_raw.cast_to_raw( object_name ))) rev_name
  4  from all_objects where object_name like 'USER_%' and rownum < 5 ;

OBJECT_NAME                    REV_NAME
------------------------------ ------------------------------
USER$                          $RESU
USER_ALL_TABLES                SELBAT_LLA_RESU
USER_APPLICATION_ROLES         SELOR_NOITACILPPA_RESU
USER_AQ_AGENT_PRIVS            SVIRP_TNEGA_QA_RESU

Also, if the Undocumented REVERSE works fine, is there any harm if we use it ?

Thanks.
Followup:
the harm in using undocumented features/functions is that

a) they can disappear
b) change behaviour
c) break

and there is nothing you can do about it.
Rating: 4
CTXCAT vs. CONTEXT vs. LIKE December 19, 2003
Reviewer: Andy from Germany
Thanks for your feedback to my comment above, Tom. I scaled up and it didn't
take long before LIKE was clearly slower. I looked into CTXCAT, but the nature
of the queries in the FGAC policy would make that hard to implement, so I'll go
with a CONTAINS clause.
December 18, 2003 tammy -- Thanks for the question regarding "cannot connect to oracle 92 database", version 9.2.0.1.0
You Asked
Hi Tom:
when I tried to connect to oracle 9.2.0.1.0 from C:\orant\BIN\PLUS33W.EXE I got
error: PLS-00553, what should I do? I have tried to change the NLS_LANG from
AMERICAN_AMERICA.WE8ISO8859P1 to AMERICAN_AMERICA.WE8MSWIN1252, it still doesn't
work. by the way the oracle 92 database is: uni code. this is pretty urgent,
thank you for your help!

SQL*Plus: Release 3.3.4.0.0 - Production on Wed Sep 25 18:25:43 2002

Copyright (c) Oracle Corporation 1979, 1996.  All rights reserved.

Error accessing package DBMS_APPLICATION_INFO
ERROR:
ORA-06550: line 1, column 40:
PLS-00553: character set name is not recognized
ORA-06550: line 0, column 0:
PL/SQL: Compilation unit analysis terminated

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

ERROR:
ORA-06550: line 1, column 29:
PLS-00553: character set name is not recognized
ORA-06550: line 0, column 0:
PL/SQL: Compilation unit analysis terminated
and we said...
you will not be able to use 7.x clients to connect to 9.2 (Oracle 9iR2)

7.3.x was de-supported soooo long ago (years) that they decided

a) not to test
b) definitely not to supprt

and the end result was that -- 7.x clients cannot in general connect to a 9.2
(9iR2 and up) database.

on unix, the result is "segmentation fault" upon connect

There was a support alert on this -- but 7.x is just not supported anymore.
Review & Followup

Rating: 5
why oracle 8.0.5 client can't connect to oracle 9i db September 25, 2002
Reviewer: A reader
Thank you, Tom. That is very helpful.

However, I do have another client 8.0.5.0.0, it can connect to 8.1.7 database,
but not 9i, here is the error, it is not tnsnames related because I did use 8i
client was able to connect to this 9i database with the same tnsnames.ora entry,
plus I was able to use tnsping fine.

C:\Oracle\Ora81\BIN>tnsping dows
TNS Ping Utility for 32-bit Windows: Version 2.3.4.0.0 - Production on 25-SEP-02
 19:39:45

Copyright (c) Oracle Corporation 1995.  All rights reserved.

Attempting to contact (ADDRESS=(PROTOCOL=TCP)(HOST=ausdfsdpast01)(PORT=1521))
OK (230 msec)

SQL*Plus: Release 8.0.5.0.0 - Production on Wed Sep 25 19:34:31 2002

(c) Copyright 1998 Oracle Corporation.  All rights reserved.

Enter user-name: vvvv/vvvv@dowp

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.3.0 - Production
With the Partitioning option
JServer Release 8.1.7.3.0 - Production

xxxx@DOWP> connect xxxx@dows
Enter password:
ERROR:
ORA-12203: TNS:unable to connect to destination
Followup:
12203, 00000, "TNS:unable to connect to destination"
// *Cause: Invalid TNS address supplied or destination is not listening.
// This error can also occur because of underlying network transport
// problems.
// *Action: Verify that the service name you entered on the command line
// was correct. Ensure that the listener is running at the remote node and
// that the ADDRESS parameters specified in TNSNAMES.ORA are correct.
// Finally, check that all Interchanges needed to make the connection are
// up and running.

tnsping doesn't prove that you can connect. your SID or SERVICE it probably
wrong in the tnsnames entry.

tnsping only shows that "there be a listener on that port".  It does not show
that "the database you want that listener to hook you up with exists and is up"
Rating: 5
September 25, 2002
Reviewer: tammy pickett from USA
Tom, I totall understanding what you are talking about.. However the oracle
8.1.7.0.0 client is able to connect to the same 82 db: dows....

C:\>cd oracle\ora817\bin
C:\Oracle\Ora817\bin>sqlplus xxxx@dows
SQL*Plus: Release 8.1.7.0.0 - Production on Wed Sep 25 21:38:48 2002
(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Enter password:

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

in both 8.0.5 and 8.1.7 client tnsnames file are as following:
ifile=H:\Network\Admin\tnsnames.ora

which H drive is the shared drive I use. so it means that both TNSNAMES.ORA
files are the same; also sqlnet file are as following:
LOG_DIRECTORY_CLIENT   = d:\Apps\Ora81\network\log
LOG_FILE_CLIENT        = sqlnet.log

LOG_DIRECTORY_SERVER   = d:\Apps\Ora81\network\log
LOG_FILE_SERVER        = sqlnet_server.log

TRACE_DIRECTORY_CLIENT = d:\Apps\Ora81\network\TRACE
TRACE_FILE_CLIENT      = sqlnet_client.trc

TRACE_DIRECTORY_SERVER = d:\Apps\Ora81\network\TRACE
TRACE_FILE_SERVER      = sqlnet_server.trc

# ---------------------------------------------------------

TNSPING.TRACE_DIRECTORY = d:\Apps\Ora81\network\TRACE
TNSPING.TRACE_LEVEL     = OFF

# ---------------------------------------------------------

NAMES.DIRECTORY_PATH = (TNSNAMES)

SQLNET.AUTHENTICATION_SERVICES = (BEQ, NTS)

SQLNET.EXPIRE_TIME = 10
# ---------------------------------------------------------
# TRACE_LEVEL_SERVER  = USER
# TRACE_LEVEL_SERVER  = ADMIN
# TRACE_LEVEL_SERVER  = SUPPORT
# ---------------------------------------------------------
# TRACE_LEVEL_CLIENT  = USER
# TRACE_LEVEL_CLIENT  = ADMIN
# TRACE_LEVEL_CLIENT  = SUPPORT
# TRACE_UNIQUE_CLIENT = ON

AUTOMATIC_IPC = OFF
TRACE_LEVEL_CLIENT = OFF
names.directory_path = (TNSNAMES)
# names.default_domain = world
# name.default_zone = world

why client 8.0.5.0.0 can't connect while client 8.1.7.0.0 is able to connect?

C:\Oracle\Ora81\BIN\plus80.exe

SQL*Plus: Release 8.0.5.0.0 - Production on Wed Sep 25 21:48:4 2002

(c) Copyright 1998 Oracle Corporation.  All rights reserved.

Enter user-name: xxxx@dows
Enter password:
ERROR:
ORA-12203: TNS:unable to connect to destination

C:\>cd oracle\ora817\bin

C:\Oracle\Ora817\bin>sqlplus xxxx@dows

SQL*Plus: Release 8.1.7.0.0 - Production on Wed Sep 25 21:38:48 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Enter password:

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
Followup:
first -- try it without ifile, ifile is not a documented supported technique.

Second, lets see the tnsnames.ora entry, perhaps you are using some syntax that
was new with 8i and not available in 8.0

Also, try it like this:

sqlplus
'scott/tiger@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=aria-dev)(PO
RT=1529)))(CONNECT_DATA=(ORACLE_SID=ora9i)))'

where you put the tnsnames.ora entry right in there -- and lets see the cut and
paste of that from 8i and 80
Rating: 5
still not working September 26, 2002
Reviewer: tammy pickett from USA
xxxx:xxxx> connect xxxx/xxxx@(DESCRIPTION=(ADDRESS_LIST =(ADDRESS =(PROTOCOL =
TCP)(HOST = ausdf
sdpast01)(PORT = 1521)))(CONNECT_DATA =(SERVER = DEDICATED)(SERVICE_NAME =
DOWS)));
Invalid option.
Usage: CONNECT <username> [AS SYSDBA|SYSOPER]

Tom: I am more confused when I connect use this tnsnames.ora entry through 8.0.5
client, I got above error.But this client is able to connect to other 8i
database no problem.

also this 9i database is able to be connected through 8i client.
also if I connect directly by using database aliase, I got following error, I am
going to send you with email since I can't paste here.
Followup:
sqlplus 'scott/tiger@(.......)'

one line, no spaces
Rating: 3
September 26, 2002
Reviewer: A reader
tom: actually command line sqlplus exeucte plus33.exe,not plus80.exe. I can't
show you the plus80.exe error if  use command line.

c:\orant\bin sqlplus.exe

Enter user-name: cccc/dddd@(DESCRIPTION=(ADDRESS_LIST =(ADDRESS =(PROTOCOL = T
CP)(HOST = ausdfsdpast01)(PORT = 1521)))(CONNECT_DATA =(SERVER = DEDICATED)(SERV
ICE_NAME = DOWS)))
Error accessing package DBMS_APPLICATION_INFO
ERROR:
ORA-06550: line 1, column 40:
PLS-00553: character set name is not recognized
ORA-06550: line 0, column 0:
PL/SQL: Compilation unit analysis terminated

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

ERROR:
ORA-06550: line 1, column 29:
PLS-00553: character set name is not recognized
ORA-06550: line 0, column 0:
PL/SQL: Compilation unit analysis terminated
Followup:
well, we know that plus33 won't work.

if you have the 8.0 client -- why don't you have the 8.0 character mode??????
Rating: 3
September 26, 2002
Reviewer: A reader
DOWS:cccc> SELECT VALUE FROM NLS_database_parameters where parameter =
'NLS_CHARACTERSET';

VALUE
---------------------------------------------------------------------------------
-------------------
AL32UTF8

Tom:
I did connect through plus33 to the 9i database, found out the characterset for
our 9i database. so why you say that oracle 805 client doesn't have correct
character set?
Followup:
plus33 is 7.x code.

That will not work reliably, it is most definitely not supported.  There are
tons of known issues with it.

I don't remember saying the 805 client doesn't have the correct character set --
where did I say that?
Rating: 3
Having a similar problem with Oracle9i Release 1 September 27, 2002
Reviewer: K Kishore from USA
Tom,
We had a similar issue with Oracle9i Release 1. We had two
machines with One machine having Oracle 8.0.5 client and
another one having Oracle 8.1.7 client.
When we try to connect to the Oracle9i Rel 1 database from
machine with 8.0.5 cleient, we get the same error.
But when we try to connect using other machine with 8.1.7
client, we can connect successfully.
In both cases tnsnames entry were exactly same.
Does this mean that Oracle8 client are not certified for
Oracle9i?
Followup:
I no longer have an 805 instance, but using 817/806

$ setenv TNS_ADMIM /export/home/oracle9i/network/admin

$ sqlplus scott/tiger@ora9i

SQL*Plus: Release 8.1.7.0.0 - Production on Fri Sep 27 14:00:23 2002
(c) Copyright 2000 Oracle Corporation.  All rights reserved.
Connected to:
Oracle9i Enterprise Edition Release 9.0.1.2.0 - Production
With the Partitioning option
JServer Release 9.0.1.2.0 - Production

scott@ORA9I.WORLD> Disconnected from Oracle9i Enterprise Edition Release
9.0.1.2.0 - Production
With the Partitioning option
JServer Release 9.0.1.2.0 - Production

$ dev806

$ !sql
sqlplus scott/tiger@ora9i

SQL*Plus: Release 8.0.6.0.0 - Production on Fri Sep 27 14:00:36 2002

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.0.1.2.0 - Production
With the Partitioning option
JServer Release 9.0.1.2.0 - Production

scott@ORA9I.WORLD>

it works, i'm not aware of any big issue in this regards, no
Rating: 4
Assumption September 30, 2002
Reviewer: K Kishore from USA
Based on this outcome, is it fair to say that the minimum
version of Oracle client required to connect to Oracle9i
is Oracle 8.0.6?
Followup:
9iR2 yes, 9iR1 works with 7.x
Rating: 4
Connecting Oracle8i server from Oracle 9i Client May 20, 2003
Reviewer: Nikhil Saxena from Gurgaon, India
I have a oracle8i database i am trying to connect to it using oracle 9i client,
it is giving me error as TNS could not resolve service name.

The entry is there in tnsname.ora
Followup:
then the entry is wrong.  it works fine, it works the same as 8i connecting to
8i or 9i to 9i or 8i to 9i or 9i to 8i.
Rating: 5
segmentation fault on 8.1.7.4 July 06, 2003
Reviewer: A reader
We had cloned an Oracle Apps 11.5.7 , 8.1.7.4 database on the same box Sun
Solaris. The original env. GOLD, and the new env. DEV2.

Checked the new Apps DEV2, and all worked except the users other than the oracle
can not use sqlplus to access the database and got as issued sqlplus:
SEGMENTATION FAULT (coredump). The oracle account has no the problem. The users
have had no problem to access the GOLD database from the GOLD env.

The stack trace created on the core dump, which was shown the privilege problem
on the sqlplus:
---------------------------------
$ gdb $ORACLE_HOME/bin/sqlplus core
GDB is free software and you are welcome to distribute copies of it
 under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.15.1 (sparc-sun-solaris2.4), Copyright 1995 Free Software Foundation,
Inc...

/oraclebase_dev2/product/8.1.7/bin/sqlplus: Permission denied.

Core was generated by `sqlplus'.
Program terminated with signal 11, Segmentation fault.
#0  0xff3d04d0 in ?? ()
(gdb)
(gdb) q

I checked:
$ ls -l /oraclebase_dev2/product/8.1.7/bin/sqlplus
-rwxr-x--x   1 oracle   dba       842772 Jun  2 03:44
/oraclebase_dev2/product/8.1.7/bin/sqlplus

The privilege is the same as the GOLD's

Why do the users have the "Permission denied" on the sqlplus?

TIA

Sean
Rating: 5
followed up "segmentation fault..." July 06, 2003
Reviewer: Sean
It looked like you had answered my query, because it showed in today's
asktom.com. But there is no shown yours "followed up"?

Thanks.
Followup:
it showed up there cause you touched the question.  any followup does that.

In short, I don't have sufficient data to diagnose this issue.  Looks 100% like
an environment issue.  I do not run apps myself, so I don't know what "cloning
apps" does -- looks like it copies the oracle install based on the path names.
Have you tried the "other" sqlplus over net8 to see if you can connect.  suggest
you open a tar with support for this one.
Rating: 3
nothing July 07, 2003
Reviewer: SD from UK
Just curious!
What it means when you say open a tar with support, is it a jargon or a key word
to say call the support guys/ contact support..etc
TIA
Followup:
tar = technical assistance request
Rating: 5
9i Release 2 - Win 2003 September 03, 2003
Reviewer: TH from Manama, BAH
Dear Tom,
Today I tried to install 9i rel 2 on a Win 2003 server.

I noted down the SID while installation.

I tried to connect SYSTEM/PWD through SQL plus. It get connected. While I'm
giving the SID as the host string, it is not connecting.  I rechecked the
Instance name through the sql statement,

SQL> select instance_name from v$instance;

Under Services, OracleService<SID> also shows the same name.

What will be my mistake? Pls help me in configuring connections.

TH
Followup:
you do not use a sid in a connect string.

you set up a tnsnames.ora file that tells the client how to connect to which
machine using which service or sid.

locally, you just have the sid set in the environment -- it is not used on the
"connect" string.
Rating: 4
Oracle CLient for XP November 17, 2003
Reviewer: mo
Tom:

I got a new PC that has P4/2.4 GHZ and XP O/s.  My old one was P3/500 and Win
98.

When I unzipped the "win817client.zip" to c:\OraHome1
and tried to run setup.exe to install the client software nothing happens. I
looked at the directories created and there is no /bin.

I tried to look at OTN to see if there are special client software for XP and
there is none.

Do you know if there is a client for XP systems that can connect to 8i/9i
databases?

Thanks,
Followup:
i don't know what "win817client.zip" is?

do you have the real install cd's
Rating: 3
oracle client November 19, 2003
Reviewer: mo
Tom:

win817client is the 8.1.7 client software fom oracle.  I do have the CD, however
I install oracle 9.2.0 client software and it works fine.

1.  Is there any issues with a oracle 9.2 client connecting to 8i database?

I think the original problem is not with oracle client but with the P-4
processor and a file named symcjit.dll.

Here is intel description of the problem:
http://www.intel.com/support/processors/pentium4/issues.htm
Thank you,
Followup:
1) no

there is a patch for the p4 issue (the p4 post dates 8i software -- no one had
any idea there would be an issue). you can contact support for it.
Rating: 3
symcjit.dll November 20, 2003
Reviewer: Tomas Ericson from Sweden
Work Around:
1. Create a temporary directory on your server.

2. Copy the contents of the Oracle REBMS Server CD to the temporary
directory created in step 1.

3. Search the directory structure created in step 1 for the existence of
the filename symcjit.dll.

4. Rename each copy of the symcjit.dll to symcjit.old.

5. Run the setup.exe from the \install\win32 directory and install
Oracle 8.1.x.

That should do it. Make sure you don't have a jre installed already and if you
do I believe you also have to rename the symcjit.dll as well.
Rating: 3
November 20, 2003
Reviewer: shiv from UK
Hi,
Just to update what I infer from below metalink alert is connectivity from 9.2
to 7.x is officially desupported and not the other way around.

ALERT: Connections from Oracle 9.2 to Oracle7 are Not Supported Type: Note Doc
ID: 207319.1

My client has many databases on 7.x clients happily connecting to 9.2. see one
below.

isd250> svrmgrl

Oracle Server Manager Release 2.3.4.0.0 - Production

Copyright (c) Oracle Corporation 1994, 1995. All rights reserved.

Oracle7 Server Release 7.3.4.0.1 - Production
With the distributed, replication, parallel query and Spatial Data options
PL/SQL Release 2.3.4.0.0 - Production
SVRMGR> quit
Server Manager complete.

isd250> sqlplus dbawk/xxxx@t_cusmarp2iam alive

SQL*Plus: Release 3.3.4.0.1 - Production on Thu Nov 20 08:39:50 2003
Copyright (c) Oracle Corporation 1979, 1996.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.4.0 - 64bit Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.4.0 - Production
SQL> quit
Followup:
7.3 is implicitly not supported connecting to 92 since 7.3 is "not supported"

that it works by accident for what you are doing is great -- but there are cases
where will will cease working AFTER you are connected.
Rating: 4
random PLS-00553 when using Web-Packages (htp,..) December 18, 2003
Reviewer: Rainer Uhr from Hamburg
Hi Tom,

using htp,htf,owa for HTML reporting.
stable and fast.

Now all of a sudden randomly error PLS-00553 appears (roughly 5% of cases) .
A refresh in 99% of the cases makes problem disappear.

Any idea ?

Rainer
Followup:
care to share the entire error stack there with me (the text would be nice too)
December 18, 2003 Krishna -- Thanks for the question regarding "PL/SQL Wrapper", version 8.1.6
You Asked
Accessing contents of Wrapped pl/sql code - Is it possible to
view wrapped Pl/SQL code? If  so , please let me know how

Thanks

Krishna
and we said...
No, you cannot unwrap wrapped code.  There are no utilities to
decompile it either.

Wrapping is a one way process.  The goal of wrap is to
obfusicate the code to prevent anyone from being able to read it.
Review & Followup

Rating: 4
How is the performance of the wrapper PL/SQL code ? March 28, 2003
Reviewer: Jack chin from Malaysia
Another question is after wrap the pl/sql code I had discovered that the line
number of the package and procedure increased, may I know that does it increase
the size of the database and how is the performance of the wrapped pl/sql code
in turn of the speed and the cpu usage of the system.
I am currently using the 9ir1 and running at SUN solaris. Please advise.

Thanks in advanced
Followup:
input lines of code has no bearing on resulting code size really -- no a direct
one to one relationship anyhow.

One generally "benchmarks" to see what impact something may have on them --
however, in this case -- direct measurement is possible.  owaext.sql is clear
text, owaext.plb is the wrapped code:

ops$tkyte@ORA920> set echo on
ops$tkyte@ORA920>
ops$tkyte@ORA920> drop package owa_sql;

Package dropped.

ops$tkyte@ORA920> set echo off
@owaext.sql

Package created.

No errors.

Package body created.

No errors.
ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from sys.code_size where obj# in
  2  (select object_id from user_objects where object_name = 'OWA_SQL' )
  3  /

      OBJ#      BYTES
---------- ----------
     45580       1248
     45586      13258

ops$tkyte@ORA920>
ops$tkyte@ORA920> drop package owa_sql;

Package dropped.

ops$tkyte@ORA920> set echo off
@owaext.plb

Package created.

No errors.

Package body created.

No errors.
ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from sys.code_size where obj# in
  2  (select object_id from user_objects where object_name = 'OWA_SQL' )
  3  /

      OBJ#      BYTES
---------- ----------
     45580       1248
     45587      13258

they result in the same compiled code.  One is just obscured
Rating: 4
Intall wrapped objects December 18, 2003
Reviewer: Robert from ct
Is it possible to (read out and) compile wrapped objects in another schema ?
Is there a way to get them compiled on another database instance ?

Thanks
Followup:
sure, it is just "code", code you and i cannot read, but just "code"

just read it out of user_source, spool it.

[tkyte@tkyte-pc tkyte]$ wrap iname=test.sql

PL/SQL Wrapper: Release 9.2.0.4.0- Production on Thu Dec 18 11:34:10 2003

Copyright (c) Oracle Corporation 1993, 2001.  All Rights Reserved.

Processing test.sql to test.plb
[tkyte@tkyte-pc tkyte]$ plus

SQL*Plus: Release 9.2.0.4.0 - Production on Thu Dec 18 11:34:12 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.4.0 - Production

ops$tkyte@ORA920PC> @test.plb

Procedure created.

ops$tkyte@ORA920PC> @getcode p

ops$tkyte@ORA920PC> !cat p.sql
set define off

create or replace procedure p wrapped
0
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
....
0
12
0
1
14
1
1
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0
1 0 1
0

/
set define on

ops$tkyte@ORA920PC> @p

Procedure created.

my getcode script is
set feedback off
set heading off
set termout off
set linesize 1000
set trimspool on
set verify off
spool &1..sql
prompt set define off
select decode( type||'-'||to_char(line,'fm99999'),
               'PACKAGE BODY-1', '/'||chr(10),
                null) ||
       decode(line,1,'create or replace ', '' ) ||
       text text
  from user_source
 where name = upper('&&1')
 order by type, line;
prompt /
prompt set define on
spool off
set feedback on
set heading on
set termout on
set linesize 100
December 18, 2003 Yogesh -- Thanks for the question regarding "Domain Indexes", version 8.1.7
You Asked
Hi Tom,

I have created domain index using  following statement

create index idx_news_body on NEWS(body) indextype is ctxsys.context;

This Index always get created in system TBS. I want to shift this index to new
TBS  so I use

alter index idx_news_body rebuild tablespace <new TBS>;

but oracle don't allow this, as well at the time of creation also we can't
specify TBS.

Why this is so ? and if I want to do so what has to be done ?

Plz explain

Regards
Yogesh
and we said...
You really might want to stop for a moment and take a second to read the docs
from cover to cover (at least browse them!)

A domain index typically isn't a single segment like a regular index. intermedia
uses a couple of tables AND indexes to store its stuff.  You can control where
they all go in the create index command.

If you read the create index command:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/inter.817/a77063/csql5.htm#19446
you'll find it takes parameters.  One of these parameters is a storage
preference.  If you jump from the create index command to storage preferences:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/inter.817/a77063/cdatadi7.htm#43016
You can see you can tell it where to put the 6 objects it creates for your
index.
Review & Followup

Rating: 2
Domain Indexes March 15, 2002
Reviewer: palash from India
Hi! Tom
Well I tried ur solution but the Index still remains in the SYSTEM tablespace. I
created "storage" preferences for the tables and this intermedia Index and
specified it's creation in some other tablespace(default TBS) but it gets
created in SYSTEM.
Is it a bug in Oracle8i?
And this is a very serious issue coz this is happening at our production DB.
Need a solution asap.
Thanx
Followup:
As I said in the other followup, need the test case.
Rating: 2
Domain Indexes in SYSTEM tablespace March 16, 2002
Reviewer: palash from India
-- I create a table...USERS tablespace is the default TBS for this schema...
Create table news_test(ID NUMBER, BODY CLOB)
/
--Declare the preferences as suggested in Oracle8i docs...
begin
ctx_ddl.create_preference('store_clob', 'BASIC_STORAGE');
ctx_ddl.set_attribute('store_clob', 'I_TABLE_CLAUSE', 'tablespace users storage
(initial 1M)');
ctx_ddl.set_attribute('store_clob', 'K_TABLE_CLAUSE', 'tablespace users storage
(initial 1M)');
ctx_ddl.set_attribute('store_clob', 'R_TABLE_CLAUSE', 'tablespace users storage
(initial 1M)');
ctx_ddl.set_attribute('store_clob', 'N_TABLE_CLAUSE', 'tablespace users storage
(initial 1M)');
ctx_ddl.set_attribute('store_clob', 'I_INDEX_CLAUSE', 'tablespace users storage
(initial 1M)');
end;
/

--And create the index using the Parameter string...
create index idx_news_body_test on news_test(body) indextype is ctxsys.context
parameters('storage store_clob')
/

I agree that the other objects created eg... supporting tables and indexes get
created in the USERS tablespace but when u query
the users_indexes table then:

select index_name , tablespace_name from user_indexes
/

INDEX_NAME                     TABLESPACE_NAME
------------------------------ ----------------------
DR$IDX_NEWS_BODY_TEST$X        USERS
IDX_NEWS_BODY_TEST             SYSTEM

So here's what all I did maybe I missed out on some important step...
Need a quick solution...maybe an alter statement or some other attribute that
needs to be added...
coz I can't see any attribute being defined for the main INDEX
"IDX_NEWS_BODY_TEST"...

Thanx in advance...
Palash
Followup:
As I said, that is a "fake" index, it's a domain index -- it is NOT REAL.  Look
in user_extents or user_segments instead:

ops$tkyte@ORA817DEV.US.ORACLE.COM> Create table news_test(ID NUMBER, BODY CLOB)
  2  /

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2  ctx_ddl.create_preference('store_clob', 'BASIC_STORAGE');
  3  ctx_ddl.set_attribute('store_clob', 'I_TABLE_CLAUSE', 'tablespace users
storage (initial 1M)');
  4  ctx_ddl.set_attribute('store_clob', 'K_TABLE_CLAUSE', 'tablespace users
storage (initial 1M)');
  5  ctx_ddl.set_attribute('store_clob', 'R_TABLE_CLAUSE', 'tablespace users
storage (initial 1M)');
  6  ctx_ddl.set_attribute('store_clob', 'N_TABLE_CLAUSE', 'tablespace users
storage (initial 1M)');
  7  ctx_ddl.set_attribute('store_clob', 'I_INDEX_CLAUSE', 'tablespace users
storage (initial 1M)');
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create index idx_news_body_test on
news_test(body) indextype is ctxsys.context
  2  parameters('storage store_clob')
  3  /

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select segment_name, segment_type,
tablespace_name, blocks
  2    from user_extents
  3   order by segment_type, segment_name
  4  /

SEGMENT_NAME                   SEGMENT_TYPE       TABLESPACE     BLOCKS
------------------------------ ------------------ ---------- ----------
DR$IDX_NEWS_BODY_TEST$X        INDEX              USERS              64
DR$IDX_NEWS_BODY_TEST$X        INDEX              USERS              64
SYS_IOT_TOP_28452              INDEX              USERS              64
SYS_IOT_TOP_28452              INDEX              USERS              64
SYS_IOT_TOP_28457              INDEX              USERS              64
SYS_IOT_TOP_28457              INDEX              USERS              64
SYS_IL0000028445C00002$$       LOBINDEX           USERS              64
SYS_IL0000028449C00006$$       LOBINDEX           USERS              64
SYS_IL0000028454C00002$$       LOBINDEX           USERS              64
SYS_LOB0000028445C00002$$      LOBSEGMENT         USERS              64
SYS_LOB0000028449C00006$$      LOBSEGMENT         USERS              64
SYS_LOB0000028454C00002$$      LOBSEGMENT         USERS              64
DR$IDX_NEWS_BODY_TEST$I        TABLE              USERS              64
DR$IDX_NEWS_BODY_TEST$I        TABLE              USERS              64
DR$IDX_NEWS_BODY_TEST$R        TABLE              USERS              64
DR$IDX_NEWS_BODY_TEST$R        TABLE              USERS              64
NEWS_TEST                      TABLE              USERS              64

17 rows selected.

There is NOTHING there!  Don't worry about it.
Rating: 5
totally agree but!!!! December 17, 2003
Reviewer: p from AL
hi tom
i totally agree with you.. but why is it creating an object in system
tablespace. or is it just a pointer to the real object...what is going on behind
the scene here

p!
Followup:
that "index" consumes no storage.  it doesn't really "exist" anywhere.

all of the tables and such that are the index do consume storage -- but they are
not in system.
Rating: 4
why there is a dummy/false index in system tablespace? December 18, 2003
Reviewer: P from AL
hi tom,
i know all you said is true but i am more interested in whu there is this false
index in system tablespace. There has to be some use of it internally. what is
oracle is doing with this index?
i appreciate if you could explain what is going on under the hood and why this
false index?
thank you!
P!

PS-i know that this is nor taking any space but i am wondering what is the
reason that its in system tablespace
Followup:
it is meta data.

just like a global temporary table -- it is in "system" (no it is not, it is in
TEMP)

it is just metadata. you asked us "create index T", would you not be UPSET if T
did not appear in the data dictionary????
Rating: 5
i thought so December 18, 2003
Reviewer: p from AL
thanks tom!
December 18, 2003 Raj -- Thanks for the question regarding "Question about Intermedia and Score", version 8.1.6
You Asked
Hi Tom,

First all wanted to thank you for having this site available.
It has helped me in many occasions.

I am trying to use Intermedia context search. I am using the
score method

select message_id, score(100) as the_score
from context_log
where
contains(message_text,'hello there',100) > 0
order by score(100) desc;

This returns data as follows

Message_id the_score
31912316    35
31912587    23
31912588    12
31912601    12

Is there a way to get the results back in percentage where it reflects as 100%
found, 90% found.

How is it possible to return the percentage value in the
result set.

table message_text
message_id number(10)
message_text long

I saw in one of you responses "interMedia Text questions",
where you had given an example of how you use Intermedia to
maintain this site and for one of the query, the results
did show score results as 100, 90 etc. Is there some setting
for the fuzzy logic that I need to do to get the results in
that format.

Thanks for the help
and we said...
I'm sorry - but I don't understand what you mean by reflecting 100% found, 90%
found.  What exactly do you mean by that???  The score doesn't mean 100%, its a
score, a rank.

The example you saw -- just happened to start at 100, if you search for

interMedia Text questions

on asktom you'll find the score starts at 18 for that.  Thats due to the number
of HITS it got.

This is from appendix G of the intermedia guide:

<quote>
Scoring Algorithm for Word Queries

To calculate a relevance score for a returned document in a word query, Oracle
uses an inverse frequency algorithm based on Salton?s formula.

Inverse frequency scoring assumes that frequently occurring terms in a document
set are noise terms, and so these terms are scored lower. For a document to
score high, the query term must occur frequently in the document but
infrequently in the document set as a whole.

The following table illustrates Oracle?s inverse frequency scoring. The first
column shows the number of documents in the document set, and the second column
shows the number of terms in the document necessary to score 100.
This table assumes that only one document in the set contains the query term.

Number of Documents in         Occurrences of Term in Document Needed to Score
Document Set                   100

1                              34
5                              20
10                             17
...
100,000                        5
1,000,000                      4

</quote>

so, the more hits you get, the easier it is to get 100 (sort of like grading on
a curve).
Review & Followup

Rating: 5
Thanks October 25, 2001
Reviewer: Raj Tuli from LA CA, USA
Makes sense after your explaination.

Just curious, is Intermedia used to display the score for searches in Oracle's
MetaLink. Over there I do see results which reflect score of 100%, 96% etc.

Also, I had read somewhere that the logic that Intermedia uses can be changed. I
was hoping to get results back like what Metalink search does.

Thanks again
Followup:
When I run a query on metalink, I do not get 100%.

Go there, search for "tkyte" for example.  My first hit came back with 17%.
What do you see?
Rating: 5
Score, rank, % of matches December 17, 2003
Reviewer: Sanjay Jha from Toronto
Tom,
I understand that Score, Rank, Weight are all related with each other and
essentially depends on how frequently the words, used in search text were found
at the time of index creation (or sync up later on) within and across the
documents on which index has been created. Accordingly in each query we can get
different ranks based on score (which again goes back to the weight assigned to
the word used in search text).
So, when we rank as suggested by you, the rank is nothing but listing the
results using the score and therefore, it gives us an idea as how "closely" it
met the search criteria amongst the result set, which is "relative" amongst the
result set. What if I want this in "absolute" terms?
My requirement is that let us say we have 5 search texts to be used in my query
and I want to get the result only when it meets atleast 2 of the search texts?
(Actually client in my case is interested to see the results coming only if it
matches the 50% of a list of search texts they have provided us, that list will
remain the same in future).
Any solution? I know we can rank them based on score (which might not serve the
purpose as it will return the results anyway).
I can't use AND(&) operator or "OR(|)operator here as that is not intended. If
intermedia has no such solution, can we write some stored procedure to
accomplish this?
Followup:
you would have to use multiple contains clauses and then check out the scores
from each to see if you got two hits.

DOC>drop table t;
DOC>
DOC>create table t ( x varchar2(255) );
DOC>
DOC>insert into t values ( 'how now brow cow' );
DOC>insert into t values ( 'how now brow cow the fox ran away with the spoon' );
DOC>
DOC>create index t_idx on t(x) indextype is ctxsys.context;
DOC>*/
ops$tkyte@ORA920>
ops$tkyte@ORA920> select x, score(1), score(2)
  2    from t
  3   where contains( x, 'cow', 1 ) > 0
  4      or contains( x, 'fox', 2 ) > 0
  5  /

X
---------------------------------------------------------------------------------
--------------------------------------------------
  SCORE(1)   SCORE(2)
---------- ----------
how now brow cow
         3          0

how now brow cow the fox ran away with the spoon
         3          4

Now, assuming we had "n" scores in there, you would:

ops$tkyte@ORA920> select x
  2    from (
  3  select x, score(1) s1, score(2) s2
  4    from t
  5   where contains( x, 'cow', 1 ) > 0
  6      or contains( x, 'fox', 2 ) > 0
  7        )
  8   where decode( s1, 0, 0, 1 )+decode( s2, 0, 0, 1 ) >= 2
  9  /

X
---------------------------------------------------------------------------------
--------------------------------------------------
how now brow cow the fox ran away with the spoon

use decode to see how many scores where non-zero and filter on that.
Rating: 5
You are simply the greatest in Oracle World! December 18, 2003
Reviewer: Sanjay Jha from Toronto
Tom, that is what exactly I was looking for. You seem to have the best knowledge
on Oracle concepts, hats off to you!
We were almost resigned and was to tell the client that we can't deliver a
solution for this requirement, but then I thought of referring this to you and
here it goes..I was elated in our morning review meeting to share this news!
(We are developing a Job Search site and there all the resume will be submitted
and stored in a blob column. They wanted to give a feature to the employers to
select the desired skill sets from a set of skill sets , and get the resume of
the candidates from database only if it meets AT LEAST 50% of the skills they
are looking for)
Thanks a lot Tom for your timely and superb help!

One small clarification. In your site when I give no search criteria and hit GO
button does it use a different query? Because when in our case the dynamic query
translates into
.... where contains(col, ' ')>0;
it returns, an error. So what we have done is that check for the search text and
if it is null, then go for a normal full table query without using Intermedia
Index. Is there a better solution?
Followup:
I use a dynamically built DBMS_SQL cursor that uses bind variables.  If you give
me no inputs -- there is no contains at all.  on the advanced search, i build a
query based on what inputs I actually recieve and bind them.
December 18, 2003 Prince -- Thanks for the question regarding "Why there is so much logging for "insert /+* append */ into tab select"? ", version 8.1.7
You Asked
Hi Tom,

Q1
~~~~

I was executing the following sql statement with the table in nologging mode.
But it seems the changes are logged (not just the dictionary changes).

Please have a look at the following.

Case1:
========

SQL> insert /*+ append */ into testusr.contactversion nologging select * from
perm_owner.contactversion ;

297585 rows created.

Execution Plan
----------------------------------------------------------
   0      INSERT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=781)
   1    0   LOAD AS SELECT
   2    1     TABLE ACCESS (FULL) OF 'CONTACTVERSION' (Cost=1 Card=1 B
          ytes=781)

Statistics
----------------------------------------------------------
      12975  recursive calls
    1798143  db block gets
      56538  consistent gets
       9882  physical reads
  125409912  redo size
       1015  bytes sent via SQL*Net to client
        874  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
         31  sorts (memory)
          0  sorts (disk)
     297585  rows processed

Case2:
==========

SQL>  alter table contactversion nologging;

Table altered.

SQL> insert /*+ append */ into testusr.contactversion  select * from
perm_owner.contactversion ;

297585 rows created.

Execution Plan
----------------------------------------------------------
   0      INSERT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=781)
   1    0   LOAD AS SELECT
   2    1     TABLE ACCESS (FULL) OF 'CONTACTVERSION' (Cost=1 Card=1 B
          ytes=781)

Statistics
----------------------------------------------------------
      11544  recursive calls
    1798175  db block gets
      55735  consistent gets
      10464  physical reads
  125449904  redo size
       1020  bytes sent via SQL*Net to client
        865  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
     297585  rows processed

SQL> commit ;

Commit complete.

SQL> truncate table testusr.contactversion ;

Table truncated.

Case3:
==========

SQL>  alter table contactversion logging;

Table altered.

SQL> insert into testusr.contactversion  select * from perm_owner.contactversion
;

297585 rows created.

Execution Plan
----------------------------------------------------------
   0      INSERT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=781)
   1    0   TABLE ACCESS (FULL) OF 'CONTACTVERSION' (Cost=1 Card=1 Byt
          es=781)

Statistics
----------------------------------------------------------
      11782  recursive calls
    1798464  db block gets
      56059  consistent gets
       9720  physical reads
  125413008  redo size
       1020  bytes sent via SQL*Net to client
        851  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
         10  sorts (memory)
          0  sorts (disk)
     297585  rows processed

Q2:
~~~~
Why there is no user_context table? Is cotexts global to the DB and whats the
reason for it?

Thanks,
and we said...
q1) you have indexes on the table.  indexes cannot be "appended" to, they must
be modified.  These modifications must be logged.  The redo/undo for the table
itself is not generated, the redo/undo for the INDEXES always is.

Consider:

ops$tkyte@ORA8I.WORLD> create table t nologging as select* from all_objects
where 1=0;
Table created.

ops$tkyte@ORA8I.WORLD> set autotrace on statistics;

ops$tkyte@ORA8I.WORLD> insert /*+ append */ into t select * from all_objects;
34855 rows created.

Statistics
----------------------------------------------------------
        151  recursive calls
        116  db block gets
     474434  consistent gets
       2173  physical reads
      10208  redo size
       1000  bytes sent via SQL*Net to client
        827  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
      34855  rows processed

ops$tkyte@ORA8I.WORLD> truncate table t;
Table truncated.

ops$tkyte@ORA8I.WORLD> create index object_name_idx on t(object_name);
Index created.
ops$tkyte@ORA8I.WORLD> insert /*+ append */ into t select * from all_objects;

34856 rows created.

Statistics
----------------------------------------------------------
        205  recursive calls
       3424  db block gets
     474764  consistent gets
       2465  physical reads
    4963388  redo size
       1000  bytes sent via SQL*Net to client
        827  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          3  sorts (memory)
          1  sorts (disk)
      34856  rows processed

ops$tkyte@ORA8I.WORLD>
q2) don't know what you mean by "user_context".  if you are referring to a
SYS_CONTEXT context -- then that is in session_context (since a context is
specific to a SESSION not a user)
Review & Followup

Rating: 3
I will try disabling the index and do the test. but mean time... March 28, 2002
Reviewer: Prince
Tom,

Thanks for the response.

Sorry if I was not clear on Q2:

What I meant there was, the dictionary view like ???_CONTEXT.

ex.

SQL> desc dba_context
 Name                                                  Null?    Type
 ----------------------------------------------------- --------
------------------------------------
 NAMESPACE                                             NOT NULL VARCHAR2(30)
 SCHEMA                                                NOT NULL VARCHAR2(30)
 PACKAGE                                               NOT NULL VARCHAR2(30)

SQL> desc user_context
ERROR:
ORA-04043: object user_context does not exist

Also, in Q1:

SQL>  alter table contactversion nologging;

Table altered.

SQL> insert /*+ append */ into testusr.contactversion  select * from
perm_owner.contactversion ;

125449904  redo size

SQL>  alter table contactversion logging;

Table altered.

SQL> insert into testusr.contactversion  select * from perm_owner.contactversion
;

125413008  redo size

SQL> select sum(bytes) from dba_extents where segment_name = 'CONTACTVERSION'
and owner = 'PERM_OWNER' ;

SUM(BYTES)
----------
  43515904

Shouldn't the redo log size should be much lesser, even though the index entries
are logged?. From the above two their doesn't seem to be much difference at all
(infact the one with nologging generated more redolog).

Also, I was testing this in a test DB which is not in "ARCHIVE LOG" mode. Does
this has anything to do with the disparity?

Thanks,
Followup:
q2) there is no "user_context" because a context (like a rollback segment, or
directory object or a public synonym) isn't really "owned" by anyone.

The package/procedure used to set it is owned by someone but the context itself
is "public" in nature.  Hence no "user_context"

q1) no one said that append generated NO redo.  The redo you see generated in
the non-indexed example is used to protect the data dictionary.  We allocated
space above the HWM for the table.  If the system failed in the middle of that
insert /*+ append */ -- we need to roll back that data dictionary update.  There
will be redo generated but it is to protect the data dictionary -- not your
newly inserted data (we just undo the space allocation if it fails, your data
"disappears" as if by magic)

As for the indexed example -- you have to remember that an index is a complex
(very very complex) data structure.  As you add each row blocks are splitting
perhaps, new leaf nodes added, data moved from block to block and so on.  Every
4 byte key you add might need to move many kilobytes of data (generating REDO to
redo that operation).  Index maintainence is expensive.  There is never really a
one to one correspondance between bytes inserted and redo generated when it
comes to an index.
Rating: 4
WHY March 29, 2002
Reviewer: A reader
Hi,

Why "10208  redo size" when there is no index? Is it
suppose to be 0 redo size?

What is the reason, please clarify?

Thanks
Followup:
no one said that append generated NO redo.  The redo you see generated in
the non-indexed example is used to protect the data dictionary.  We allocated
space above the HWM for the table.  If the system failed in the middle of that
insert /*+ append */ -- we need to roll back that data dictionary update.  There

will be redo generated but it is to protect the data dictionary -- not your
newly inserted data (we just undo the space allocation if it fails, your data
"disappears" as if by magic)
Rating: 3
But, why more redo log when "nologging" compared to logging? March 29, 2002
Reviewer: Prince.
I understand completely, the insert operation may generate log for dictionay
operation. But, my question is, shouldn't the redo log generated be
comparitively less when the table is in "nologging" mode.

Thanks,
Followup:
not neccessarily.  MOST all of the redo generated by an insert is due to its
indexes.  The index maintenance is done very differently for /*+ append */
inserts then for non-append inserts.  This difference accounts for what you are
seeing here.

/*+ append */ into a heavily indexed (or just indexed for that matter ) table is
mostly a waste of keystrokes.  It's best for a bulk load into a table with no
indexes.
Rating: 4
How about PK March 29, 2002
Reviewer: Rob Balter from Pittsburgh, PA
Tom:
You say
/*+ append */ into a heavily indexed (or just indexed for that matter ) table is
mostly a waste of keystrokes.  It's best for a bulk load into a table with no
indexes.

Does this apply if the only index is a Primary Key or
unique index, or is it better to drop the PK, bulk
load and then recreate the index
Followup:
an index is an index is an index -- be it primary key or otherwise.

Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on statistics
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select rownum
from all_objects;

17183 rows created.

Statistics
----------------------------------------------------------
         11  recursive calls
          7  db block gets
      80334  consistent gets
          0  physical reads
        224  redo size
        996  bytes sent via SQL*Net to client
        839  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
      17183  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> truncate table t;

Table truncated.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select rownum from all_objects;

17183 rows created.

Statistics
----------------------------------------------------------
          7  recursive calls
        220  db block gets
      80371  consistent gets
          0  physical reads
     257208  redo size
       1012  bytes sent via SQL*Net to client
        825  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      17183  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> truncate table t;

Table truncated.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t add constraint t_pk primary
key(x);

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select rownum
from all_objects;

17184 rows created.

Statistics
----------------------------------------------------------
        162  recursive calls
        772  db block gets
      80425  consistent gets
          0  physical reads
     920844  redo size
        995  bytes sent via SQL*Net to client
        839  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          8  sorts (memory)
          0  sorts (disk)
      17184  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> truncate table t;

Table truncated.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select rownum from all_objects;

17184 rows created.

Statistics
----------------------------------------------------------
          7  recursive calls
        995  db block gets
      80459  consistent gets
          1  physical reads
    1179008  redo size
       1013  bytes sent via SQL*Net to client
        825  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      17184  rows processed

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

it would be better to disable the index, do the insert and then rebuild it
unrecoverable.
Rating: 4
July 29, 2002
Reviewer: Mahomed Suria from England, U.K
This was very helpful. Sorry, but I have to tried to get a question in for a
long time and failed. In desperation I have resorted to getting my question in
this way.

I have a problem that my database has suddenly started producing too many redo
log switches. From 10 switches per day to 300 a day. How do I find out what has
caused this. i.e whisch SQL(s) and user(s) .

Mahomed
Followup:
Log Miner is useful here.
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76956/archredo.htm#12680
...
Rating: 5
July 29, 2002
Reviewer: A reader
Hi tom,

 In your response below i saw sort operation.

 ops$tkyte@ORA8I.WORLD> insert /*+ append */ into t select * from all_objects;
34855 rows created.

Statistics
----------------------------------------------------------
        151  recursive calls
        116  db block gets
     474434  consistent gets
       2173  physical reads
      10208  redo size
       1000  bytes sent via SQL*Net to client
        827  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          3  sorts (memory) <----------------
          0  sorts (disk)
      34855  rows processed

We are just inserting rows.So how come sort occur here.

 Thanks,
Followup:
select * from all_objects;

that query required some sorts.
Rating: 4
DMT or LMT July 29, 2002
Reviewer: Rob from Pittsburgh, PA
You said:

q1) no one said that append generated NO redo.  The redo you see generated in
the non-indexed example is used to protect the data dictionary.  We allocated
space above the HWM for the table.  If the system failed in the middle of that
insert /*+ append */ -- we need to roll back that data dictionary update.  There
will be redo generated but it is to protect the data dictionary -- not your
newly inserted data (we just undo the space allocation if it fails, your data
"disappears" as if by magic)

Tom:

Are you assuming a Dictionary managed tablespace when you talk about the redo
generated in the above insert. Would your answer be different if it was an LMT.
Followup:
Nope, you would still get some redo generated, just probably less.

Here users is an LMT, system is DMT:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t tablespace users as select *
from all_objects where 1=0;
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on statistics

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from
all_objects;

22829 rows created.

Statistics
----------------------------------------------------------
         91  recursive calls
         67  db block gets
      74678  consistent gets
          0  physical reads
       5960  redo size
        996  bytes sent via SQL*Net to client
        827  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
      22829  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set sql_trace=false;

Session altered.

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t tablespace system as select *
from all_objects where 1=0;

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from
all_objects;

22829 rows created.

Statistics
----------------------------------------------------------
       2740  recursive calls
        382  db block gets
      76122  consistent gets
          0  physical reads
      48708  redo size
        997  bytes sent via SQL*Net to client
        827  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
      22829  rows processed

I traced it and found:

update tsq$ set blocks=:3,maxblocks=:4,grantor#=:5,priv1=:6,priv2=:7,priv3=:8
where ts#=:1 and user#=:2

for the LMT (more for the DMT). TSQ$ -- tablespace quotas.  Still some
dictionary stuff happening, albeit it alot less.
Rating: 5
Thanks a lot!!! October 25, 2002
Reviewer: Anny from Ukraine
I hade the same bottle neck!!!
And I overcome it!
Rating: 4
Could you pl give an example of disable pk, insert, rebuild nologging? January 10, 2003
Reviewer: Ramakrishna from India
Dear Tom,
  I have the following situation - a large table with a primary key index.  My
aim is to minimize logging for this table (without resorting to partitioning if
possible) during daily data loads.  I want to:
a.  Disable the pkey index
b.  Insert /*+ APPEND */ data into it
c.  Rebuild the pkey index nologging
However, I am unable to do the insert once the pkey constraint is disabled.
Could you please explain how this can be done?

thanks
ramakrishna
Followup:
alter session set skip_unusable_indexes=true;
Rating: 4
And in this case ? Is it waste o keystrokes here ? January 10, 2003
Reviewer: A reader
INSERT /*+ append */ INTO t1
         SELECT * FROM t2
                 WHERE NOT EXISTS ( SELECT 'X'
                                      FROM t1
                                     WHERE t1.f1 = t2.f1);
   commit;

If both t1 and t2 have an non unique index on f1.
Followup:
no?
Rating: 4
large insert February 25, 2003
Reviewer: Vikas Sharma from Delhi India
Hi Tom,

I have the following secnario.
user A has a  table tab_A.
user b has a table tab_b.

When connected to user a i want to load data from to temp_a to user.temp_b.

I want to be use
insert into b.temp_b(i,j,k) select i,j,k from a.temp_a;

this insert statment of mine is dynamically created. i dont know how many cols
will be selected by user. so i have to use execute immediate.

Now i should first disable the primary key of temp_b then
disable the index if any on temp_b and create a statment like :

insert /*+ append */ into b.temp_b(i,j,k) select i,j,k from a.temp_b;

and finally execute the statement for better performance by generating the less
redo.

what if insert fails in between.

2.What will the follwing
 alter session set skip_unusable_indexes=true;

will do. Do i need to set this before start my insert.

Regards,
Vikas Sharma
Followup:
1) the premise that disabling the indexes will create better run times may be
flawed.

Are you adding 1,000,000 rows to a 50,000,000 row table?  Just insert.
Are you adding 1,000,000 rows to an empty table?  disable/rebuild
Are you adding N rows to M rows ?  depends, might disable might not.  use common
sense and testing to see what is best.

what if the insert fails indeed?  so?

2) you need to DROP unique indexes they are not skipped.  but yes, you would
need that.
Rating: 5
Saminathan May 06, 2003
Reviewer: Saminathan from CA,USA
Dear Tom,

I'm getting UNEXPECTED result in both 8i and 9iR2.

Could you please tell me why insert /*+APPEND*/ generates more redo log.

I'm pretty sure that there is NO index involved in my test table(redolog_test).
Also the user is not a SYS and SYSTEM.
The DB is running in ARCHIVE LOG mode.

SQL> show user
USER is "MUT"

SQL> drop table redolog_test;
Table dropped.

SQL> create table redolog_test pctfree 99 pctused 1 as select * from all_objects
where 1=2;
Table created.

SQL> insert into redolog_test select * from all_objects;
19412 rows created.

Statistics
----------------------------------------------------------
       2003  recursive calls
      89992  db block gets
     161720  consistent gets
          0  physical reads
   10661144  redo size  <========
        438  bytes sent via SQL*Net to client
        433  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      19412  rows processed

SQL> truncate table redolog_test;
Table truncated.

SQL> insert /*+APPEND*/ into redolog_test select * from all_objects;
19412 rows created.

Statistics
----------------------------------------------------------
       2012  recursive calls
        365  db block gets
     142229  consistent gets
          0  physical reads
  159534376  redo size  <=======(15 Times higher)
        430  bytes sent via SQL*Net to client
        445  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      19412  rows processed
SQL>

What could be wrong?
Followup:
Interesting.  INSERT /*+ APPEND */ into a table in an archivelog mode database
that is not marked "nologging" appears to do full block logging instead of
changed bytes only.

You have an absurdly high pctfree.  This causes every row to go into a separate
block.  Normally, insert /*+ append */ (built for speed) fills a block up --
logs it.  You are causing each and every row to fill up a block and instead of
taking the time to parse out just the changed bytes -- it logs the block.

If you alter the table to be NOLOGGING (then it would actually make sense to use
append) OR if you set pctfree to a reasonable value, this does not occur.
Consider:

ops$tkyte@ORA9I> create table t pctfree 99 pctused 1 as select * from
all_objects where 1=2;
Table created.

ops$tkyte@ORA9I> set autotrace on statistics
ops$tkyte@ORA9I> insert /*+ append */ into t select * from all_objects where
rownum <= 19412;

19412 rows created.

Statistics
----------------------------------------------------------
  162,794,128  redo size
       19,412  rows processed

ops$tkyte@ORA9I> set autotrace off

ops$tkyte@ORA9I> analyze table t compute statistics for table;
Table analyzed.

ops$tkyte@ORA9I> select blocks from user_tables where table_name = 'T';

    BLOCKS
----------
     19412

one row per block, 8k blocks,

ops$tkyte@ORA9I> select 19412*8*1024 from dual;

19412*8*1024
------------
 159,023,104

we logged full block images and since you had pctfree 99, most of the block was
in fact *empty*...

ops$tkyte@ORA9I> truncate table t;
Table truncated.

ops$tkyte@ORA9I> set autotrace on statistics
ops$tkyte@ORA9I> insert into t select * from all_objects where rownum <= 19412;
19412 rows created.

Statistics
----------------------------------------------------------
 11,116,860  redo size
     19,412  rows processed

ops$tkyte@ORA9I> set autotrace off
ops$tkyte@ORA9I> analyze table t compute statistics for table;
Table analyzed.

ops$tkyte@ORA9I> select blocks from user_tables where table_name = 'T';

    BLOCKS
----------
     19416

same number of blocks -- however, "changed bytes" logging -- not full block

ops$tkyte@ORA9I> truncate table t;
Table truncated.

ops$tkyte@ORA9I> alter table t nologging;
Table altered.

ops$tkyte@ORA9I> set autotrace on statistics
ops$tkyte@ORA9I> insert /*+ append */ into t select * from all_objects where
rownum <= 19412;

19412 rows created.

Statistics
----------------------------------------------------------
     447,376  redo size
      19,412  rows processed

ops$tkyte@ORA9I> set autotrace off

ops$tkyte@ORA9I> analyze table t compute statistics for table;
Table analyzed.

ops$tkyte@ORA9I> select blocks from user_tables where table_name = 'T';

    BLOCKS
----------
     19412

ops$tkyte@ORA9I> set autotrace off

so, if you use APPEND in conjunction with NOLOGGING -- very little redo -- of
if you use a sane pctfree and actually fill up the blocks:

ops$tkyte@ORA9I> truncate table t;

Table truncated.

ops$tkyte@ORA9I> alter table t logging;

Table altered.

ops$tkyte@ORA9I> alter table t pctfree 10;

Table altered.

ops$tkyte@ORA9I>
ops$tkyte@ORA9I> set autotrace on statistics
ops$tkyte@ORA9I> insert /*+ append */ into t select * from all_objects where
rownum <= 19412;

19412 rows created.

Statistics
----------------------------------------------------------
        301  recursive calls
         55  db block gets
      93649  consistent gets
        164  physical reads
    2212388  redo size
        779  bytes sent via SQL*Net to client
        844  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          6  sorts (memory)
          0  sorts (disk)
      19412  rows processed

ops$tkyte@ORA9I> set autotrace off
ops$tkyte@ORA9I> analyze table t compute statistics for table;

Table analyzed.

ops$tkyte@ORA9I> select blocks from user_tables where table_name = 'T';

    BLOCKS
----------
       267

it generates about the same amount of redo as a "normal" insert would:

ops$tkyte@ORA9I> truncate table t;

Table truncated.

ops$tkyte@ORA9I> set autotrace on statistics;
ops$tkyte@ORA9I> insert into t select * from all_objects where rownum <= 19412;

19412 rows created.

Statistics
----------------------------------------------------------
         91  recursive calls
       1549  db block gets
      93947  consistent gets
          0  physical reads
    2166700  redo size
        794  bytes sent via SQL*Net to client
        830  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      19412  rows processed

So here -- it is because

a) you have a very very high pctfree leaving almost the entire block empty
b) the table is in LOGGING mode

If you want insert /*+ append */ to work fast you will set the table NOLOGGING
anyhow.  So, this is something of "academic interest" since I cannot fathom the
business case whereby you would leave most of the block empty OR use append
reguarly on a LOGGING table.
Rating: 5
Dear Guru, Thank you so much for the explanation with an example May 06, 2003
Reviewer: Saminathan from CA,USA
Sorry for bothering you again...

One more question,

What is the difference between below <CASE1> and
<CASE2> w.r.t redolog?  What is the logic behind in CASE1
to generate huge volume of REDO.

Kindly explain why <CASE1> generates
~30MB redologs and <CASE2> generates ~ <= 5MB redolog.

<CASE1>
SQL> create table redolog_test1 pctfree 99 pctused 1
as select * from all_objects;
Table created.
<CASE1>
5MB redolog (Log switch happend 6 times)

<CASE2>
SQL> create table redolog_test2 pctfree 99 pctused 1
as select * from all_objects where 1=2;
Table created.

SQL> insert into redolog_test2 select * from
all_objects where rownum <=10000;
2741 rows created.
</CASE2>
No log switch at all.

Since CASE1 generates more REDO(basically more I/O), it would be better to avoid
CASE1. Am i right?
Need your comment Tom.
Followup:
I told you --

when you do an insert /*+ append */ AND the database is in archivelog mode AND
the table is LOGGING

THEN

full blocks are written to the redo log.  In most every case -- this is a
performance enhancing feature -- your mixture of an excessively high pctfree
combined with logging mode defeated that.  In your extreme case, the blocks were
almost entirely EMPTY due to the excessively high pctfree setting.

when you do an insert and the database is in archivelog mode

THEN

changed bytes are painstakingly computed and written to the redo log -- so the
90% or more of the block that was not used in your case was not written to the
redo log.

The bottom line to using APPPEND

a) the table should be NOLOGGING for it to make total sense.

done, period -- as demonstrated above -- either set the table NOLOGGING or use a
REASONABLE pctfree (as demonstrated)
Rating: 5
May 19, 2003
Reviewer: A reader from ca, usa
It seems that /*+ APPEND */ only works for INSERT SELECT not for just INSERT.

FOr example,

INSERT /*+ APPEND */ INTO a VALUES (:1, :2);

will still generate redo's.

Is it true?

thanks
Followup:
as the documentation says -- insert append is for BULK inserts done with insert
select.

It would be utterly a terrible idea to direct path insert a single row.  think
of what a direct path insert does and what would happen.

you would

a) get one row per block
b) never reuse any free space

all to save a couple of bytes of redo.  it would not be even remotely close to
"worth it"
Rating: 5
May 27, 2003
Reviewer: A reader
Rating: 4
June 05, 2003
Reviewer: Vipin from New york
Hi Tom,

I think the second question was, how will anyone know what are the CONTEXTS
created in my database. With sys_context and session_context I think we will be
able to know only which all contexts are active.

Please tell how we will get this info.
Followup:
you mean like ALL_CONTEXT, did you look at the data dictionary tables.  That is
where we register pretty everything.
Rating: 4
June 06, 2003
Reviewer: Vipin from New york
Hi Tom,

What I mean is suppose I create contexts using following statements:-

Create context context1 using package1;
Create context context2 using package2;

Now where I can go and see that I have two contexts which can be used.
Followup:
dba_context = all contexts that can be used.
all_context = all contexts in use in your session.

if you, as the developer, don't know the context -- you have a bigger problem at
hand.  You have to create the package after all to populate it.

as an end user, you don't have a need to know the universe of context's out
there, it is simply "not relevant".
Rating: 4
more redo? June 06, 2003
Reviewer: Reader
Please see below <---------comments. Why direct insert as nologging into my
table which is defined as logging produces a lot of redo?

SQL> create table t ( x int ) tablespace users; <------------- LOGGING table

Table created.

SQL> insert /*+ append */ into t select rownum from all_objects;

29106 rows created.

Statistics
----------------------------------------------------------
        135  recursive calls
        158  db block gets
     133565  consistent gets
          0  physical reads
     383520  redo size <--------------------------- A lot of REDO - no surprise
        612  bytes sent via SQL*Net to client
        554  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      29106  rows processed

SQL> alter table t nologging; <----------------------- NOLOGGING

Table altered.

SQL> insert /*+ append */ into t select rownum from all_objects;

29106 rows created.

Statistics
----------------------------------------------------------
        285  recursive calls
        164  db block gets
     133582  consistent gets
          2  physical reads
      11952  redo size <------------------------ much reduced REDO - no surprise
        612  bytes sent via SQL*Net to client
        554  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          6  sorts (memory)
          0  sorts (disk)
      29106  rows processed

SQL> truncate table t;

Table truncated.

SQL> alter table t logging; <------------ again LOGGING table

Table altered.

SQL> insert /*+ append */ into t nologging select rownum from all_objects;<----
NOLOGGING

29106 rows created.

Statistics
----------------------------------------------------------
        285  recursive calls
        164  db block gets
     133582  consistent gets
          2  physical reads
     383840  redo size    <------------------------ surprise- why more redo as
1st case
        612  bytes sent via SQL*Net to client
        564  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          6  sorts (memory)
          0  sorts (disk)
      29106  rows processed
Followup:
why surprise? the table is logging the inserts as directed?

the only thing "surprising" is the surprise you are experiencing....
Rating: 4
BUT June 06, 2003
Reviewer: Reader
I specified nologging in my direct insert as below.

insert /*+ append */ into t nologging select rownum from all_object
Followup:
Oh, sorry -- didn't notice that nologging -- but only cause it is not
relevant...  It is not any different then:

insert /*+ append */ into t foobar_this_is_just_an_alias select rownum from
all_objects;

that "nologging" word was just a correlation name, an alias for T in the query,
not a keyword.
Rating: 4
why the syntax has nologging June 06, 2003
Reviewer: Reader
I think i am confused with the syntax.

INSERT /*+APPEND */ INTO [SCHEMA. ] table
[ [NO]LOGGING ]
sub-query;

also, oracle doc says, If the NOLOGGING option is used, changes to data are not
recored in the redolog buffer.

am i missing something here? you are right and i tried by specifying alias in
place of nologging and the behavior was same. It took it as an alias. So, what
is this NOLOGGING clause in the syntax? and how should I use it properly to not
generate redo as suggested by the doc? Thanks.
Followup:
where do you see that?

give me a pointer into otn.oracle.com please.

I looked at the 817 and 9iR2 SQL Reference and didn't see that.

the docs I'm reading say "if the TABLE is in nologging mode or the database is
in NOARCHIVELOG mode"
Rating: 4
in OU course book June 06, 2003
Reviewer: Reader
Tom, Please look in Oracle University 9i DBA fundamentals 1 course book on page
19-4 and 19-5 (chapter 19 Loading data into a database). I consider these course
books also as oracle docs! I am glad I am checking with you. Because that syntax
and explanation in the course book do not reflect what we/I see from testing.
Followup:
I don't consider them as docs and haven't really ever seen them.

It is *wrong*, dead wrong.  It conflicts with the real documentation.  You can
use the contact links on education.oracle.com to ask them to update it.
Rating: 5
alter index idx nologging June 19, 2003
Reviewer: A reader
hi

if we set index to nologging can we avoid redo in insert append operation?
Followup:
no
Rating: 5
All this nologging for performance..what happened to recoverability? June 20, 2003
Reviewer: Chuan Satcha from VA USA
Tom

    This nologging seems to be a great performance tool when doing right.
However since not all REDOs are generated and other redo transactions occur with
other tables, will the database be recoverable even in archivelog mode?
Followup:
the "database" -- yes, always.

the "objects you performed non-logged operations on", no NOT until you backup
the affected datafiles.
Rating: 4
Question June 20, 2003
Reviewer: PRS from USA
Hi Tom,
   Can I use /*+ append */  for new oracle9i feature insert all (MULICAST
INSERT)? Do I need to put all the tables into NOLOGIGN mode?

Thanks,
PRS
Followup:
Yes and

Yes if you are in archive log mode
No if you are in noarchive log mode (which would only be true on your
prototype/test/scratch database)

so, Yes and Yes

ops$tkyte@ORA920> create table t ( x varchar2(50) );

Table created.

ops$tkyte@ORA920> alter table t nologging;

Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace on statistics
ops$tkyte@ORA920>
ops$tkyte@ORA920> insert all
  2  into t (x) values (object_id)
  3  into t (x) values (object_name)
  4  select * from all_objects;

63156 rows created.

Statistics
----------------------------------------------------------
        495  recursive calls
       3004  db block gets
     146505  consistent gets
          0  physical reads
    1667532  redo size
        772  bytes sent via SQL*Net to client
        874  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          6  sorts (memory)
          0  sorts (disk)
      63156  rows processed

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert /*+ APPEND */ all
  2  into t (x) values (object_id)
  3  into t (x) values (object_name)
  4  select * from all_objects;

63156 rows created.

Statistics
----------------------------------------------------------
         28  recursive calls
         33  db block gets
     146023  consistent gets
          0  physical reads
       2564  redo size
        772  bytes sent via SQL*Net to client
        888  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      63156  rows processed

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace off
ops$tkyte@ORA920> select * from t;
select * from t
              *
ERROR at line 1:
ORA-12838: cannot read/modify an object after modifying it in parallel
Rating: 5
Hmm....insert all....I've never seen that before..... June 20, 2003
Reviewer: Mark J Bobak from Ann Arbor, MI
It's interesting how the conventional insert interleaved
rows in blocks of 255, but for direct path, the interleave
factor was 1.

-Mark
Rating: 5
Insert /*+ Append */ June 23, 2003
Reviewer: Bharath from USA
Can Direct Insert can be used in Distributed transcation.We have a java
procedure (i.e Simple Java Class) which uses JDBC to populate the Data from the
distributed Datbase using DBLINK.
E.g

Insert /*+ Append*/ into Table A as select * from remotetableA@dblink

Insert /*+ Append*/ into Table B as select * from remotetableB@dblink

When executing the second statement i am getting an oracle error or if i do any
select statement from the local database i am getiing the same error.Is this a
limitation.
Followup:
well, if you are getting and ora-12840 (guessing, you opted not to share today),
it is fairly self explanatory:

scott@ORA817DEV> !oerr ora 12840
12840, 00000, "cannot access a remote table after parallel/insert direct load
txn"
// *Cause: Within a transaction, an attempt was made to perform distributed
// access after a PDML or insert direct  statement had been issued.
// *Action: Commit/rollback the PDML transaction first, and then perform
// the distributed access, or perform the distributed access before the
// first PDML statement in the transaction.

So, can a direct path insert be used in a distributed transaction?

scott@ORA817DEV> insert into emp@ora920.us.oracle.com
  2  select -empno, ename, job, mgr, hiredate, sal, comm, deptno
  3  from t1;

14 rows created.

scott@ORA817DEV> insert /*+ append */ into t1 select * from
emp@ora920.us.oracle.com;

28 rows created.

scott@ORA817DEV> commit;

Commit complete.

Yes

But, there is the limitation that only one direct path load may take PLACE when
using a distributed query in a single transaction.
Rating: 5
Thanks June 23, 2003
Reviewer: A reader
Rating: 4
question June 24, 2003
Reviewer: PRS from USA
Tom,
   Once I put the table into nologging mode and do the
data load using insert all with APPEND hint(Direct Path Load) then rebuild the
index in nologging mode, then
    I turn the table and all its indexes in log mode.
    Then take the cold backup as there were no arcivelog.

    So my question is, it will start generating the archive log for tables and
index after truning back to logging mode?
    Is this assumption right?

Thanks,
PRS
Followup:
nologging only applies to direct path type operations.

all insert/update/delete after that would be logged regardless of the logging
mode.
Rating: 5
Thanks a lot June 25, 2003
Reviewer: A reader
Rating: 4
Question June 25, 2003
Reviewer: PRS from USA
Hi Tom,
    Why is the below SELECT statement is failing with APPEND hint with
ORA-01401: inserted value too large for column. As soon as I remove the APPEND
hit, it does insert rows. We are on ORACLE 9.2.0.3.0.

         insert /*+ APPEND */ all
          into PS_RSF_OPP_NOTE
            (OPPORTUNITY_ID     -- VARCHAR2 (15)  NOT NULL
            ,NOTE_SEQ_NBR       -- INTEGER        NOT NULL
            ,RC_SUMMARY         -- VARCHAR2 (80)  NOT NULL
            ,RC_NOTE_TYPE       -- VARCHAR2 (5)   NOT NULL
            ,RC_VISIBILITY      -- VARCHAR2 (4)   NOT NULL
            ,ROW_ADDED_DTTM     -- DATE
            ,ROW_ADDED_OPRID    -- VARCHAR2 (30)  NOT NULL
            ,ROW_LASTMANT_DTTM  -- DATE
            ,ROW_LASTMANT_OPRID -- VARCHAR2 (30)  NOT NULL
            ,SYNCID             -- INTEGER
            ,SYNCDTTM           -- DATE
            ,DESCRLONG)         -- LONG
           values (SWOBJECTID              -- NUMBER (15,5)    OPPORTUNITY_ID
 VARCHAR2 (15)  NOT NULL
                  ,note_seq_num            --                  NOTE_SEQ_NBR
 INTEGER        NOT NULL
                  ,WSI_SUBJECT             -- VARCHAR2 (30)    RC_SUMMARY
 VARCHAR2 (80)  NOT NULL
                  ,' '                     --                  RC_NOTE_TYPE
 VARCHAR2 (5)   NOT NULL
                  ,' '                     --                  RC_VISIBILITY
 VARCHAR2 (4)   NOT NULL
                  ,SWDATECREATED           -- DATE             ROW_ADDED_DTTM
 DATE,
                  ,SWCREATEDBY             -- VARCHAR2 (20)    ROW_ADDED_OPRID
 VARCHAR2 (30)  NOT NULL
                  ,null                    --                  ROW_LASTMANT_DTTM
 DATE,
                  ,' '                     --
ROW_LASTMANT_OPRID VARCHAR2 (30)  NOT NULL
                  ,WSIOPPORTUNITYCOMMENTID -- NUMBER (15,5)    SYNCID
 INTEGER,
                  ,sysdate                 --                  SYNCDTTM
 DATE,
                  ,WSI_NOTES)              -- VARCHAR2 (4000)  DESCRLONG
 LONG
          select WSIOPPORTUNITYCOMMENTID -- NUMBER (15,5)
                ,SWOBJECTID              -- NUMBER (15,5)
                ,SWOBJECTTYPE            -- VARCHAR2 (20)
                ,WSI_SUBJECT             -- VARCHAR2 (30)
             -- ,WSI_NOTE_SAMPLE         -- VARCHAR2 (75)
                ,WSI_NOTES               -- VARCHAR2 (4000)
             -- ,SWMASTERVER             -- VARCHAR2 (30)
                ,SWDATECREATED           -- DATE
                ,SWCREATEDBY             -- VARCHAR2 (20)
             -- ,TIMESTAMP               -- VARCHAR2 (16)
                ,row_number() over (partition by SWOBJECTID order by
WSIOPPORTUNITYCOMMENTID) note_seq_num
            from WSI_OPPORTUNITY_COMMENT@vant8iqa
           where (SWOBJECTID >= 1) and (SWOBJECTID < 100000)
/

Thanks,
PRS
Followup:
seems to be that long column...

Here is a simple test case you can "bug" with support if you like:

ops$tkyte@ORA920> create table t ( x long );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t select * from dual@ora920@loopback;

1 row created.

ops$tkyte@ORA920> insert /*+ APPEND */ into t select * from
dual@ora920@loopback;
insert /*+ APPEND */ into t select * from dual@ora920@loopback
                                   *
ERROR at line 1:
ORA-01401: inserted value too large for column

ops$tkyte@ORA920>
ops$tkyte@ORA920> drop table t;

Table dropped.

However, you should be using a CLOB -- and if you do:

ops$tkyte@ORA920> create table t ( x clob );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t select * from dual@ora920@loopback;

1 row created.

ops$tkyte@ORA920> insert /*+ APPEND */ into t select * from
dual@ora920@loopback;

1 row created.
Rating: 5
ORA-12838 July 11, 2003
Reviewer: A reader
Hi

I am testig two insert append sql statements and one I get ORA-12838 and the
other not. The test cases are as follows

>INSERT /*+ APPEND */ INTO y select * from dba_objects;

26006 rows created.

Statistics
----------------------------------------------------------
        688  recursive calls
        458  db block gets
      20029  consistent gets
          2  physical reads
      33876  redo size
        604  bytes sent via SQL*Net to client
        549  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      26006  rows processed

>r
  1* INSERT /*+ APPEND */ INTO y select * from dba_objects
INSERT /*+ APPEND */ INTO y select * from dba_objects
                          *
ERROR at line 1:
ORA-12838: cannot read/modify an object after modifying it in parallel

working case (small table)

>INSERT /*+ APPEND */ INTO x select * from dept;

4 rows created.

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

>r
  1* INSERT /*+ APPEND */ INTO x select * from dept

4 rows created.

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

how so?
Followup:
all that means is that X is NOT a candidate for a direct path insert, so the
append hint was ignored.  I can do simulate your findings but since you gave us
an incomplete example from which to start -- I don't know if it is exactly your
case.

In a nutshell, the first insert was in fact a direct path, the second was just a
normal old fashioned insert.

(does make my job more interesting when I play forensic scientist to try and
reassemble what might have happened -- but it would be easier to answer if I
knew all of the facts :)

ops$tkyte@ORA817DEV> create table t1 ( x int primary key );

Table created.

ops$tkyte@ORA817DEV> create table t2 ( x int primary key ) organization index;

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert /*+ append */ into t1 select rownum from dual;

1 row created.

ops$tkyte@ORA817DEV> select * from t1;
select * from t1
              *
ERROR at line 1:
ORA-12838: cannot read/modify an object after modifying it in parallel

ops$tkyte@ORA817DEV> insert /*+ append */ into t2 select rownum from dual;

1 row created.

ops$tkyte@ORA817DEV> select * from t2;

         X
----------
         1

since an IOT is one of the objects you cannot direct path load, the append
hint was ignored -- as hints will be when then do not apply
Rating: 5
direct insert and benchmark July 17, 2003
Reviewer: A reader
Hi Tom
While creating benchmark - sometimes you need to insert
lots of data. is it "safe" to use direct insert in
terms of benchmarking? What I mean is do I risk getting
misleading results if I use direct insert versus
normal insert simply because the actual application
uses normal insert?

thanx!
Followup:
direct path inserts result in the same data being loaded.

it is NOT a benchmark special tool -- it is a tool to use when appropriate, in
the real world.
Rating: 5
Why in procedure July 18, 2003
Reviewer: Ashok from USA
>>12840, 00000, "cannot access a remote table after >>parallel/insert direct
load txn"

i am not getting 12840 when i execute multiple direct path insert inside the
procedure and execute...

for e.g
Create or replace proc
as
Insert /*+append*/ from tableA@remote
Insert /*+append*/ from tableB@remote
commit;
end;
No problem when i execute the above procedure.But if i execute individual
statements why i am getting 12840

Pls clarify.
Followup:
that is not a stored procedure in oracle -- don't know what you are doing wrong
based on that.
Rating: 5
Why In procedure July 21, 2003
Reviewer: Ashok from USA
I was trying to do direct insert(i.e APPEND + NoLogging)   from  multiple remote
table . I am getting an oracle error
12840, 00000, "cannot access a remote table after parallel/insert direct  load
txn" if i try to execute the statement(i.e Insert  /*+APPEND*/ into )from JDBC i
am getting the above error(i.e on the second execute of the direct path insert
statement).But if i put all the direct path insert statement in Strored proc and
execute .No problem all the direct path insert statement was sucessfull.Why?

(Note :All the views(v_edgetrc_) has accessed from the remotetable thru DBLINK)

for e.g
procedure TR_PROC_REFRESH_AGENT_PLUS
IS

BEGIN
    /* Insert Into tr_ap_deals */
    insert/*+APPEND*/ into tr_ap_deals(FNEN, ENTITY, DEAL_ID, DEAL_NAME)
        select FNEN, ENTITY, DEAL_ID, DEAL_NAME from v_edgetrc_deals;

    /* Insert Into tr_ap_borrowers */
    insert /*+APPEND*/ into tr_ap_borrowers(DEAL_ID, BORROWER_ID, BORROWER_NAME,
PARENT_BORROWER_IND, MHUCN, MHUCN_SFX)
        select            VEB.DEAL_ID
                    ,        VEB.BORROWER_ID
                    ,        UPPER(VEB.BORROWER_NAME)
                    ,        VEB.PARENT_BORROWER_IND
                    ,        VEB.MHUCN
                    ,        VEB.MHUCN_SFX
        from                v_edgetrc_borrowers VEB
                    ,        v_edgetrc_deals VED
        where                VEB.PARENT_BORROWER_IND = 'Y'
                    and    VEB.DEAL_ID = VED.DEAL_ID;

    /* Insert Into tr_ap_investors */
    insert /*+APPEND*/ into tr_ap_investors(INVESTOR_ID, INVESTOR_NAME,
PARENT_SAME_AS_INVESTOR_IND, PARENT_INVESTOR_ID, MHUCN, MHUCN_SFX)
        select INVESTOR_ID, UPPER(INVESTOR_NAME), PARENT_SAME_AS_INVESTOR_IND,
PARENT_INVESTOR_ID, MHUCN, MHUCN_SFX from v_edgetrc_investors;

    /* Insert Into tr_ap_fac_info */
    insert /*+APPEND*/ into tr_ap_fac_info
                (            FACILITY_ID
                    ,        FAC_NAME
                    ,        FAC_DESC
                    ,        FAC_TYPE
                    ,        FAC_TYPE_DESC
                    ,        BASE_CURRENCY_CODE
                    ,        OPEN_FAC_AMT
                    ,        LC_LINE_AMT
                    ,        BORROWING_BASE_AMT
                    ,        CREDIT_AGRMNT_DATE
                    ,        UNDER_AGRMT_DATE
                    ,        MATURITY_DATE
                    ,        CANCEL_DATE
                    ,        ENTRY_DATE
                    ,        BILATERAL_IND
                    ,        GES_FACID
                    ,        CLIENT_ID
                    ,        RRB_NAME
                    ,        RRB_DESC
                    ,        CUR_FAC_AMT
                    ,        FAC_OUTS
                    ,        NOTE_OUTS
                    ,        LC_OUTS
                    ,        OTHER_OUTS
                )
        select            VEFS.FACILITY_ID
                    ,        VEFS.FAC_NAME
                    ,        VEFS.FAC_DESC
                    ,        VEFS.FAC_TYPE
                    ,        VEFS.FAC_TYPE_DESC
                    ,        VEFS.BASE_CURRENCY_CODE
                    ,        VEFS.OPEN_FAC_AMT
                    ,        VEFS.LC_LINE_AMT
                    ,        VEFS.BORROWING_BASE_AMT
                    ,        VEFS.CREDIT_AGRMNT_DATE
                    ,        VEFS.UNDER_AGRMT_DATE
                    ,        VEFS.MATURITY_DATE
                    ,        VEFS.CANCEL_DATE
                    ,        VEFS.ENTRY_DATE
                    ,        VEFS.BILATERAL_IND
                    ,        VEFS.GES_FACID
                    ,        VEFS.CLIENT_ID
                    ,        VEFC.RRB_NAME
                    ,        VEFC.RRB_DESC
                    ,        VEFC.CUR_FAC_AMT
                    ,        VEFO.FAC_OUTS
                    ,        VEFO.NOTE_OUTS
                    ,        VEFO.LC_OUTS
                    ,        VEFO.OTHER_OUTS
        from                v_edgetrc_fac_select VEFS
                    ,        v_edgetrc_fac_calc VEFC
                    ,        v_edgetrc_fac_outs VEFO
        where                VEFS.FACILITY_ID = VEFC.FACILITY_ID (+)
                    and    VEFS.FACILITY_ID = VEFO.FACILITY_ID (+);

    /* Insert Into tr_ap_ishare_open */
    insert /*+APPEND*/ into tr_ap_ishare_open(FACILITY_ID, INVESTOR_ID,
INV_SHARE, SHARE_PRO_RATA)
        select            VEIO.FACILITY_ID
                    ,        VEIO.INVESTOR_ID
                    ,        VEIO.INV_SHARE
                    ,        VEIO.SHARE_PRO_RATA
        from                v_edgetrc_ishare_open VEIO
                    ,        tr_ap_fac_info tafi
        where                VEIO.FACILITY_ID = TAFI.FACILITY_ID;
END TR_PROC_REFRESH_AGENT_PLUS;
Followup:
I'm going to guess - you are running a really old version of Oracle and if you
set sql-trace=true, and looked at the tkprof file, you would find that the FIRST
character in a /*+APPEND*/ hint is missing so it is really /*+PPEND*/ - hence,
it is NOT doing a direct path insert.

there was a bug, a long time ago, whereby the first character in a hint was
"gone", so you needed to /*+ APPEND */  (add a space)

The problem however is intractable here, you CANNOT do those multiple direct
path inserts, so "don't"
Rating: 5
Why in a procedure July 21, 2003
Reviewer: Ashok from USA
Thanks!
Rating: 4
Problem with insert /*+append*/ October 09, 2003
Reviewer: Sridhar from USA
Tom,
   I have a problem while inserting.
I have a unique index on 3 columns together.
ALTER index idx_U1 unusable;
ALTER SESSION SET skip_unusable_indexes=true;
when i do an insert/*+append*/ it says
ORA-26026: unique index idx_u1 initially in unusable state.
there are so many indexes on this table which iam able to mark them unusable and
do an insert ,but when i mark this one unusable it throws an error.the only
difference is that this index is a unique index and the rest are nonunique
indexes.
Thanks,
Sridhar
Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96540/statements_22a.htm#2182515
doesn't apply to UNIQUE indexes.
Rating: 4
Question regarding Direct load December 17, 2003
Reviewer: Suhail from USA
I have a customer table which has millions of rows. I receive rows in text file
and using UTL_FILE package , I extract and load the data into first a staging
table staging_customer. This table is a staging table in nature ie after every
30 days we delete the rows from the table. From this table we load data to
another permanent table  and we use INSERT INTO customer ( field1,filed2....)
values clause to insert data ie we load row by row ( which is synonyms to slow
by slow).

Is there a better way to load data into this table, should I create this table
with no logging option and use direct load approach ie  insert /* append */ into
customer as select * from staging_customer where fileid=p_fileid
or
should I  enable parallel DML ie

execute immediate "ALTER SESSION ENABLE PARALLEL DML"
INSERT INTO customer SELECT * FROM  staging_customer where fileid=p_fileid;

Is there any problem in using one on another? Is nologging option means , incase
of some database failure, recovery is not possible?

Please exaplain.

Thank you
Followup:
I would

a) skip utl_file
b) skip stage table
c) use an external table
d) with insert /*+ APPEND */
Rating: 4
I agree with you December 18, 2003
Reviewer: Suhail from USA
Tom,
I agree with you that I should use external tables but client is not ready and
we consultant has to do our job so I amusing UTL and staging tables.

So if I am right to undrstand, your recommendation is that my main table ( not
staging) should be created with nologging option and I will use insert /+*
append*/ option.?

Thanks
Followup:
tell them "hey, if you want this to go faster, we need to change the process,
else it runs at this speed"

then, see what they say.

UTL_FILE will be the choke point in this processing.  It is the low hanging
fruit here.
Rating: 3
I tried December 18, 2003
Reviewer: Suhail from USA
Tom,

Thanks for your help. I have one more question . How to write muti insert in my
situation. ie I am reading data from the staging table and I have to insert data
into multiple tables which are joined by sequence id.

Following are my tables

1)Stage_cust   table
SSD_SEQ_ID
FILE_SEQID
LAST_NAME
FIRST_NAME
MID_INITIAL
GENDER
DATE_OF_BIRTH
ADDRESS1
ADDRESS2
CITY
STATE
ZIP_CODE
PHONE_cell
PHONE_home
PHOME_office
)
My other two main tables are

Master table Customer
2) CUST_MASTER
(CUST_ID   -- a sequence id -PK
LAST_NAME
FIRST_NAME
MID_INITIAL
GENDER
DATE_OF_BIRTH
ADDRESS1
ADDRESS2
CITY
STATE
ZIP_CODE
)
Detail table
3)cust_phones
 (ph_id    a sequence id PK
  cust_id
  phone
  phone_type
)

I have database trigger which handles automatice generation of sequence id when
data is loaded into these tables. I know there is a way to do multi-insert using
INSERT ALL, could I do it in  my case, could you please give me the code , I
will only add record in cust_phone table if
phone_cell, phone_home etc are not null , how am I going to write all this in
one INSERT ALL and still use /*+append */ clause.
 I would like to do
insert /*+ append*/ into cust_master
insert /*+ append*/ into cust_phones
select from stage_cust where file_seqid=200 and saimilary for detail table.
Thanks
Followup:
code your triggers like I coded the one on "P" below -- so you can supply the
sequence value in the insert itself if you want (and you want)

ops$tkyte@ORA920PC> create table t
  2  ( lname varchar(20),
  3    phone_cell varchar2(20),
  4    phone_home varchar2(20),
  5    phone_office varchar2(20)
  6  )
  7  /

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create table c
  2  ( id number constraint c_pk primary key,
  3    lname varchar2(20)
  4  )
  5  /

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create table p
  2  ( id number constraint p_pk primary key,
  3    c_id references p,
  4    phone varchar2(20),
  5    phone_type varchar2(20)
  6  )
  7  /

Table created.

ops$tkyte@ORA920PC> create sequence s1;

Sequence created.

ops$tkyte@ORA920PC> create sequence s2;

Sequence created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create or replace trigger p_trigger
  2  before insert on p for each row
  3  begin
  4          if ( :new.id is null )
  5          then
  6                  select s2.nextval into :new.id from dual;
  7          end if;
  8  end;
  9  /

Trigger created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert into t values ( 'xxx', 'abc', 'def', 'ghi' );

1 row created.

ops$tkyte@ORA920PC> insert into t values ( 'yyy', 'lmn', 'opq', 'rst' );

1 row created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert all
  2  into c ( id, lname ) values ( s1.nextval, lname )
  3  into p ( c_id, phone, phone_type ) values ( s1.currval, phone_cell, 'cell'
)
  4  into p ( c_id, phone, phone_type ) values ( s1.currval, phone_home, 'home'
)
  5  into p ( c_id, phone, phone_type ) values ( s1.currval, phone_office,
'office' )
  6  select * from t
  7  /

8 rows created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select *
  2    from c, p
  3   where c.id = p.c_id;

        ID LNAME         ID       C_ID PHONE PHONE_TYPE
---------- ----- ---------- ---------- ----- --------------------
         1 xxx            1          1 abc   cell
         1 xxx            2          1 def   home
         1 xxx            3          1 ghi   office
         2 yyy            4          2 lmn   cell
         2 yyy            5          2 opq   home
         2 yyy            6          2 rst   office

6 rows selected.
Rating: 4
Good one December 18, 2003
Reviewer: Suhail from USA
Tom,

I tried following on my set of tables EMP, EMP_ADDR and EMP_PH

and the code is as follows  but its giving me ORA error. It seems to me a bug.
SQL> desc emp
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 EMP_ID                                    NOT NULL NUMBER
 LAST_NAME                                          VARCHAR2(20)
 FIRST_NAME                                         VARCHAR2(20)

SQL> desc emp_addr
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 ADDR_ID                                   NOT NULL NUMBER
 ADDRESS                                            VARCHAR2(30)
 CITY                                               VARCHAR2(30)
 EMP_ID                                    NOT NULL NUMBER

SQL> desc emp_ph
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 PH_ID                                     NOT NULL NUMBER
 PHONE                                              VARCHAR2(10)
 EMP_ID                                             NUMBER
 TYPE                                               VARCHAR2(10)

SQL>
SQL> insert all
  2  into emp ( emp_id,last_name,first_name)
values(SEQ_EMP.nextval,last_name,first_name)
  3  into emp_addr(emp_id,address,city) values(seq_emp.nextval,address,city)
  4  into emp_ph(emp_id,phone,type) values(seq_emp.nextval,phone1,'Cell')
  5  into emp_ph(emp_id,phone,type) values(seq_emp.nextval,phone2,'Home')
  6  select last_name,first_name,address,city,phone1,phone2
  7  from emp_temp;
into emp ( emp_id,last_name,first_name)
values(SEQ_EMP.nextval,last_name,first_name)
                                               *
ERROR at line 2:
ORA-00600: internal error code, arguments: [15852], [4], [3], [], [], [], [],
[]

I am using 9i Rel 1  9.0.1.4.0.
Followup:
the multiple references to the "nextval" are going to be problematic.

don't you mean to use currval (but yes, ora-600 = please open a tar)
Rating: 3
however, this does not faile December 18, 2003
Reviewer: Suhail from USA
Tom,

Following INSERT ALL works but when commint , it disconnect with db.

SQL>  insert all
  2   into emp ( emp_id,last_name,first_name)
values(SEQ_EMP.nextval,last_name,first_name)
  3   into emp_addr(emp_id,address,city) values(seq_emp.nextval,address,city)
  4   select last_name,first_name,address,city
  5  from emp_temp;

10 rows created.

SQL> commit;
commit
     *
ERROR at line 1:
ORA-03113: end-of-file on communication channel

From TOAD, this INSERT ALL works OK and from sqlplus this and previous one got
failed

Thanks
Followup:
ora-3113 also means "please open a tar with support"
Rating: 4
with CURRVAL sam eproblem December 19, 2003
Reviewer: Suhail from USA
Sorry, Tom,

I would had used CURRVAL but its having the same problem ie

 1   insert all
 2       into emp ( emp_id,last_name,first_name)
 3   values(SEQ_EMP.nextval,last_name,first_name)
 4       into emp_addr(emp_id,address,city) values(seq_emp.currval,address,city)
 5       into emp_ph(emp_id,phone,type) values(seq_emp.currval,phone1,'Cell')
 6       into emp_ph(emp_id,phone,type) values(seq_emp.currval,phone2,'Home')
 7       select last_name,first_name,address,city,phone1,phone2
 8*      from emp_temp
QL> /
values(SEQ_EMP.nextval,last_name,first_name)
       *
RROR at line 3:
RA-00600: internal error code, arguments: [15852], [4], [3], [], [], [], [],

Well I had opened a TAR , it seems my version of 9i has some bug.

Thanks for your help.
December 18, 2003 Pratap -- Thanks for the question regarding "How to get dependencies among pl/sql objects", version 8.1.6
You Asked
Hi Tom

We have a large number of pl/sql packages and procedures (.sql files). Now I
want to create a file that will create all the packages and procedures in the
database like this -

create_objects.sql, this will have following contents -

@@pack1.sql
@@pack2.sql

Can I generate create_objects.sql using sql/plsql ? In this case how do we find
the sequence in which the packages are to be created taking in view the
dependencies among them.

Thanks and regards

Pratap
and we said...
well, you cannot (there can be recursive relationships) -- and the really cool
thing is -- you DON'T need to!!!

Just create all of the specifications.
Then create all of the bodies.

There you go.  Everything will compile and everything will be valid.  Never
never use standalone procedures/functions -- always use packages -- and
installation is a snap.  specs, then bodies.  Even if the specs depend on
eachother -- it won't matter WHAT order you create them in as the compilation of
the bodies will validate the specs.  In the end -- everything is AOK.
Review & Followup

Rating: 5
Thanks!! November 27, 2001
Reviewer: Pratap from Pune, India
Rating: 4
Great question... November 27, 2001
Reviewer: Tony from Philadelphia, PA
But how would you automatically create the files?  I need to do this frequently
when moving new/modified packages up the chain to production.  My current method
is a bit embarrassing to reveal...
Followup:
Well, something like this:

set feedback off
set heading off
set termout off
set feedback off
set heading off
set termout off
set linesize 1000
set trimspool on
set verify off
spool &1..sql
prompt set define off
prompt select sysdate from dual

select decode(line,1,'/' || chr(10) || 'create or replace ', '' ) ||
       text text
  from user_source
 where type = 'PACKAGE'
 order by name, line;
prompt /
prompt set define on
spool off
set feedback on
set heading on
set termout on
set linesize 100

will get all of the specs, change PACKAGE to PACKAGE BODY and you would get all
of the bodies...
Rating: 5
Why never never November 28, 2001
Reviewer: Jim from Australia
Tom
If you get a minute, please list your reasoning
behind never never using procedure etc always use packages.
I know of a couple of reasons (compilation like you said above, executing one
procedure in a package causes the whole package to be loaded into the shared
pool so if you have packaged procedures commonly used together there maybe some
benefit) but would be interested in your thoughts
Followup:
1) packages break the dependency chain
2) packages break the dependency chain
and
3) packages break the dependency chain

did I mention -- they also break the dependency chain.
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:286816015990
Also, entire packages are NOT LOADED -- they are demand paged in as needed, just
like an operating system.  This is one reason people claim to not want to use
packages -- they assume they are fully loaded -- but they are not.

Packages increase your namespace -- you get Pkg.Proc.  Instead of 5,000 little
procedures with meaningless names, you can have 500 packages with good module
names, each with 10 procedures that have meaningful names (that might be the
same as the name of a procedure in another package).  Not only that -- but the
procedures are logically grouped together.

Packages allow for overloading.

Packages allow for procedure hiding -- I might have 10 procedures in the spec,
20 in the body.  You never need know about the other 10.

Packages promote good coding.  When I see people using standalone procedures
what I many times see are 5,000 or more LINES OF CODE in a single procedure.
That totally freaks me out (big time disbelief).  Then they get upset when it
gets too large to compile.  I have no sympathy there -- I've never come anywhere
close to hitting any limits, but then again -- I try to make sure my subroutines
fit on a screen so I can see the top of the procedure and the bottom of the
procedure without having to hit page up/down.
Packages allow you to maintain a state.

Packages allow you to create types.

and so on....

I use standalone procedures/functions only to demonstrate a concept, never for
real code.
Rating: 4
Getting the dependencies. November 28, 2001
Reviewer: Kamaljeet Singh from NewCastle
Tom the queries might be bad , but this does works. And why i might need this is
because whne we create a copy of an existing schema. The log generates a lot's
of error if code is not compiled in dependency order. Hence debugging is  a
problem.

I faced the same problem. One way out is

first Compile the package
then Procedures and functions
then package bodies.

Now for dependency part..
/* Get the dependencies */
insert into temp_dep select name,type,referenced_name,referenced_type from
all_dependencies where owner = &schemaName and
type  in ('PROCEDURE','FUNCTION') and                referenced_type in
('PROCEDURE','FUNCTION');

and then
following query will give the code in order of dependencies.

Select nvl(a.lvl,0),b.name,b.line,
decode(b.line,1,'CREATE OR REPLACE '||b.text,b.text) stmt
from ( select max(level) lvl,name from temp_dep where
decode(type,'FUNCTION','PROCEDURE',type) = 'PROCEDURE'
start with name like '%' and decode(type,'FUNCTION','PROCEDURE',type) =
'PROCEDURE'
connect by prior name = referenced_name
group by name ) a , dba_source b
where b.owner = &schemaName
and decode(b.type,'FUNCTION','PROCEDURE',type) = 'PROCEDURE'
and b.name = a.name(+)
order by 1,2,3;

Since we cannot create a procedure wihich can refer to non exixtent procedure or
function. There should be no loop dependencies in the query.
Also since we compile the packages first. Procedures and functions compile oK.

It works for me nicely.
Followup:
I don't use procedures, I don't use functions. (see other comment above for why
this is)

If you just use packages -- you need no such complicated processing at all.

All it takes is one recursive dependency and your method is broken.

You can certainly create a procedure which refers to a non-existent procedures.
It compiles with errors (its invalid).  We don't care what order you create
stuff in (just look at an IMP!)

There are in most all 8i databases -- built in dependency loops if you have java
installed (unfortunately, it can cause issues with queries such as yours)

Besides, I don't really ever care what order things are created in -- they'll
all create.  Some will be invalid, some won't.  As people run them - they will
fix themselves.

oh well, if you have a query that works on your system, go for it -- it takes
along time to run and if you just use packages -- is totally not necessary (i
mean running that query is just step one.  Now you have to extract the code --
IN that order!  -- something like "@getspec", "@getbody" just looks sooo nice in
comparision)
Rating: 5
Thanks for the followup! November 28, 2001
Reviewer: Tony from Philadelphia, PA
Tom - the code in your reply worked great.  The decode statement at the
beginning is very clever - just the *magic* I needed.
Rating: 5
Never Never continued November 29, 2001
Reviewer: Jim from Australia
:) Now I get it packages break the dependancy chain !
Very interesting about the entire package NOT getting
loaded, I was told on an Oracle Education course by
the instructor that it was !
Thanks
Rating: 4
Loading Of Package In Memory November 29, 2001
Reviewer: Anup from India
Hi Tom

I Was Also Aware That Entire Packages Gets Loaded Into Memory When The Procedure
Within The Package Is Called For The First Time.

I Checked The Following Link And It Also Mention The Same
http://technet.oracle.com/doc/oracle8i_816/server.816/a76965/c17pckgs.htm#5556
Under The Heading Performance Improvement.

Thanks For Your Great And Simple Explanations.
Rating: 4
compilation in Oracle Applications April 15, 2003
Reviewer: A reader
Hi

I wonder why we normally have to run utlrp.sql several times to compile invalid
objects in Oracle Applications? Once I had 100 invalid objects, after first run
I ended up with over 500 invalid packages, I had to run the script 4 or 5 times
to compile everything, I understand it may be because of dependencies. How can
we compile PL/SQL objects successfully in the first go?
Followup:
just let them compile themselves?

that is what i do.  I've never lost sleep over it knowing they will do it all by
themselves.

If you have my book "Expert one on one Oracle" -- i describe a technique whereby
you can compile all things in a schema successfully.  Basically the psuedo code
is:

create table already_tried( object_type, object_name );

create function get_invalid_object return varchar2
as
begin
   select ... into ... from user_objects
    where not exists ( select null from already_tried a
                         where a.object_type = user_objects.object_type ...)
     and status  = 'INVALID'
     and rownum = 1;

   return ...
end;

begin
    delete from already_tried;
    loop
       compile the output of get_invalid_object;
       insert into already_tried;
    end loop;
exception
    when no_data_found then done
end;

it works by getting AN invalid object that we haven't yet tried to compile.  It
compiles it (which will recursively compile all needed objects -- the first
invalid object may well fix ALL invalid objects).

We remember we did that one

Go onto the next.

done.  Now at the end, you might have invalid objects -- but they will remain
invalid forever as they quite simple cannot be compiled.
Rating: 5
Invalid Function June 25, 2003
Reviewer: atul from india
Hi tom,

i have one query..
We use only packages not standalone functions/procedures..

When i see status of all objects in my database
it shows one function as invalid ..

How should i debug this??
as i'm not getting any clues??

Thanks.
atul
Followup:
confusion

a) we only use packages
b) we have an invalid function

I cannot fathom b from a so you will have to be "more clear"
Rating: 3
Who uses what?... December 18, 2003
Reviewer: J from Vancouver
Hello!

Here's my situation: I've inherited an undocumented system - 40+ packages, each
with approx 15-20 procedures, and (suprise!) 30+ stand-alone procedures and
functions.  Trying to get a flow of the system is quite a challenge due to
inter/intra-package calls, etc.

Is there anything in the DD that I can query to help build a flow of how this
multitude of code ties together?  If not, can you suggest an approach (other
than hari-kari) that I could undertake to get a handle on this beastie?

Many Thanks!
- J
Followup:
DBA_DEPENDENCIES shows who calls who at the package/procedure level. You can use
that as a starting point.

recommend -- copy dba_dependencies into a temporary work table (create table as
select) and index/query the copy.  doing tons of queries on the view will be
"painful"
Rating: 4
Who uses what... (PS...) December 18, 2003
Reviewer: J from Vancouver
Oops.

Forgot to mention that all of the stored procedures are within the same schema -
there's no remote invocations, etc.

Thanks again!
- J
December 18, 2003 ted -- Thanks for the question regarding "re: prior posting on index", version 8.1.6
You Asked
Thomas, clarification related to null in indexed fields:

1. I read from several book and got the impression if higher
order (leftest field) is null an index entry will not be
generated. Because null value is not equal to any thing. Is this
a misconception ?
2. If null is the leftest of a compound index, where would it be
located in a binary tree ?

3. can you send me your print_table procedure ?
thnx in advance as always
Ted Chyn
===========================================

Only totally NULL records are not indexed.. If any component of
the index is not null for a given row -- there is an index entry

made.

This can be seen this way:

ops$tkyte@ORA8I.WORLD> create table test ( a int,b int);

Table created.

ops$tkyte@ORA8I.WORLD> create index test_a_b on test(a,b);

Index created.

ops$tkyte@ORA8I.WORLD> insert into test values(1,2);

1 row created.

ops$tkyte@ORA8I.WORLD> insert into test values(1,null);

1 row created.

ops$tkyte@ORA8I.WORLD> insert into test values(null,2);

1 row created.

ops$tkyte@ORA8I.WORLD> insert into test values(null,null);

1 row created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> analyze index test_a_b validate
structure;

Index analyzed.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> exec print_table( 'select * from
index_stats' );
HEIGHT                        : 1
BLOCKS                        : 64
NAME                          : TEST_A_B
PARTITION_NAME                :
LF_ROWS                       : 3   <<<======
LF_BLKS                       : 1
LF_ROWS_LEN                   : 47
LF_BLK_LEN                    : 7996
BR_ROWS                       : 0
BR_BLKS                       : 0
BR_ROWS_LEN                   : 0
BR_BLK_LEN                    : 0
DEL_LF_ROWS                   : 0
DEL_LF_ROWS_LEN               : 0
DISTINCT_KEYS                 : 3
MOST_REPEATED_KEY             : 1
BTREE_SPACE                   : 7996
USED_SPACE                    : 47
PCT_USED                      : 1
ROWS_PER_KEY                  : 1
BLKS_GETS_PER_ACCESS          : 2
PRE_ROWS                      : 0
PRE_ROWS_LEN                  : 0
and we said...
Well, the example you cut and pasted in shows that index entries
are made for indexes with leading nulls.  One of the rows I
added was (null,2).  Only the totally null row (null,null) is
missing in the index.

As for the sort order, nulls are considered "greater" for
sorting:

ops$tkyte@ORA8I.WORLD> set null null
ops$tkyte@ORA8I.WORLD> select 1 a, 2 b from dual
  2  union all
  3  select to_number(null) a, 2 b from dual
  4  union all
  5  select 1 a, to_number(null) b from dual
  6  union all
  7  select to_number(null) a, to_number(null) b from dual
  8  order by 1, 2
  9  /

         A          B
---------- ----------
         1          2
         1 null
null                2
null       null

For print_table, thats easy.  Here is the Oracle8i specific one.
 It uses authid_current user so you can install it ONCE per
database and many people can use it (with roles and all intact):

create or replace procedure print_table( p_query in varchar2 )
AUTHID CURRENT_USER
is
    l_theCursor     integer default dbms_sql.open_cursor;
    l_columnValue   varchar2(4000);
    l_status        integer;
    l_descTbl       dbms_sql.desc_tab;
    l_colCnt        number;
begin
    execute immediate
    'alter session set
        nls_date_format=''dd-mon-yyyy hh24:mi:ss'' ';

    dbms_sql.parse(  l_theCursor,  p_query, dbms_sql.native );
    dbms_sql.describe_columns
    ( l_theCursor, l_colCnt, l_descTbl );

    for i in 1 .. l_colCnt loop
        dbms_sql.define_column
        (l_theCursor, i, l_columnValue, 4000);
    end loop;

    l_status := dbms_sql.execute(l_theCursor);

    while ( dbms_sql.fetch_rows(l_theCursor) > 0 ) loop
        for i in 1 .. l_colCnt loop
            dbms_sql.column_value
            ( l_theCursor, i, l_columnValue );
            dbms_output.put_line
            ( rpad( l_descTbl(i).col_name, 30 )
              || ': ' ||
              l_columnValue );
        end loop;
        dbms_output.put_line( '-----------------' );
    end loop;
    execute immediate
        'alter session set nls_date_format=''dd-MON-rr'' ';
exception
    when others then
      execute immediate
          'alter session set nls_date_format=''dd-MON-rr'' ';
      raise;
end;
/

The above, you would run as:

SQL> exec print_table( 'select * from T where a = ''X'' ' );

(note the doubling of the quotes for character string
constants!!)

In Oracle8.0 and before, I use this sqlplus script instead:

declare
    l_theCursor     integer default dbms_sql.open_cursor;
    l_columnValue   varchar2(4000);
    l_status        integer;
    l_descTbl       dbms_sql.desc_tab;
    l_colCnt        number;

    procedure execute_immediate( p_sql in varchar2 )
    is
    BEGIN
        dbms_sql.parse(l_theCursor,p_sql,dbms_sql.native);
        l_status := dbms_sql.execute(l_theCursor);
    END;
begin
    execute_immediate( 'alter session set nls_date_format=
                        ''dd-mon-yyyy hh24:mi:ss'' ');
    dbms_sql.parse(  l_theCursor,
                     replace( '&1', '"', ''''),
                     dbms_sql.native );

    dbms_sql.describe_columns( l_theCursor,
                               l_colCnt, l_descTbl );

    for i in 1 .. l_colCnt loop
        dbms_sql.define_column( l_theCursor, i,
                                l_columnValue, 4000 );

    end loop;

    l_status := dbms_sql.execute(l_theCursor);

    while ( dbms_sql.fetch_rows(l_theCursor) > 0 ) loop
        for i in 1 .. l_colCnt loop
            dbms_sql.column_value( l_theCursor, i,
                                   l_columnValue );
            dbms_output.put_line
                ( rpad( l_descTbl(i).col_name,
                  30 ) || ': ' || l_columnValue );
        end loop;
        dbms_output.put_line( '-----------------' );
    end loop;
    execute_immediate( 'alter session set nls_date_format=
                           ''dd-MON-yy'' ');
exception
    when others then
        execute_immediate( 'alter session set
                         nls_date_format=''dd-MON-yy'' ');
        raise;
end;
/

You would run this as:

SQL> @printtbl 'select * from T where a = "X" '

Note my use of " instead of '' here.  Important for the script
to work as is.
Review & Followup

Rating: 5
Print_Table utility May 09, 2001
Reviewer: Logo Palanisamy from San Francisco, Bay Area
Very very useful.
Rating: 5
Most useful July 09, 2001
Reviewer: Ashish from Tysons Corner, VA
Rating: 5
very very useful, life made so easy August 06, 2001
Reviewer: A reader
Rating: 5
Reader January 01, 2002
Reviewer: Reader from USA
Tom,

When using print_table for the query that returns about
18 rows, I got the overflow:

BEGIN print_table('select * from v$sqlarea where upper(sql_text) like ''%EMP%''
'); END;

*
ERROR at line 1:
ORA-20000: ORU-10028: line length overflow, limit of 255 bytes per line
ORA-06512: at "SYSTEM.PRINT_TABLE", line 42
ORA-06512: at line 1

Can this script be improved to capture the output with a
utl_file call ;)

Thanks
Followup:
how would that "improve" it??  If it used UTL_FILE, you would actually never
"see" it.

change this:

dbms_output.put_line
            ( rpad( l_descTbl(i).col_name, 30 )
              || ': ' ||
              l_columnValue );

to

dbms_output.put_line
            (substr( rpad( l_descTbl(i).col_name, 30 )
              || ': ' ||
              l_columnValue, 1, 255 ) );

and realize that if the column is >225 characters, it'll be truncated.  I
suppose you could write a fancier word wrap routine to display really long stuff
-- and if you do, post it back here (so the answer is yes, the script can be
improved)
Rating: 5
Extemely Useful January 01, 2002
Reviewer: Reader from USA
Tom,

Thanks for this enhancement

I am just learning PL/SQL kudos and caveats from your
web site. Very practical and extremely helpful

Even so, I’ll attempt to write word wrap routine, if I do I’ll share it
in this site.
Rating: 3
best way in 9.2? September 27, 2002
Reviewer: George from Chester Springs, PA USA
Tom,

I need to write a procedure that will execute an arbitrary query that will
return 1 to 40 columns - the caller gets to write the whole query and I only get
to execute and return data, or fail it.

I'm pretty sure I can solve my problem by using dbms_sql.parse to find the
number of columns in the requested query, then writing a dynamic query that
lists just those variables that I actually intend to populate.
I just can't help thinking there should be an easier way...
can you think of one?

here's the first pass I wrote, "hoping" the database might just ignore the extra
output variables... no luck of course ;-)

create type string_list_t as table of varchar2(32767);
/

create procedure exec_dyn_fetch
( p_stmt  in varchar2,
  p_out1  out string_list_t,
  p_out2  out string_list_t,
  p_out3  out string_list_t,
  p_out4  out string_list_t
)
is
begin

   execute immediate p_stmt
   bulk collect into
   p_out1, p_out2, p_out3, p_out4;

end;

and then an anonymous block to test:

declare
   v_var1 string_list_t;
   v_var2 string_list_t;
   v_var3 string_list_t;
   v_var4 string_list_t;
begin
   exec_dyn_fetch( 'select table_name from user_tables',
      v_var1, v_var2, v_var3, v_var4 );
end;
/

*
ERROR at line 1:
ORA-01007: variable not in select list
ORA-06512: at "GLIBLICK.EXEC_DYN_FETCH", line 10
ORA-06512: at line 7

thanks in advance, you're always a big help :-)

George
Followup:
When you do not know the number of input (bind) or output (defines) columns -
you will not be using native dynamic sql.

You'll use DBMS_SQL from start to finish.

Just like I did in print_table.
Rating: 3
sorry September 27, 2002
Reviewer: George from Chester Springs, PA USA
about my previous posting here... I didn't plan to put it here, but rather
http://asktom.oracle.com/pls/ask/f?p=4950:8:879134::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:352271617074,%7Bdynamic%7D%20and%20%7Boutput%7D
I tried hard to find an archived answer first, then got confused with all the
windows I had open.  It's been a long day :-(
Followup:
Thats OK -- the print_table example is perfect to go with the question.
Rating: 5
[OFFTOPIC] Tom, Print_table is most usefull but.. February 07, 2003
Reviewer: Martin from The Netherlands
Just curious,

Why did you use the 'When others' exception in your procedure whereas you kinda
say this is bad practice here
http://asktom.oracle.com/pls/ask/f?p=4950:8:2036387::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1155066278457,%7Bwhen%7D%20and%20%7Bothers%7D
Personally I never use When others, I think it stinks.
(Sorry for being offtopic)
Followup:
Well, what I said in that link is:

A when others is almost always a BUG unless it is immediately followed by a
RAISE.

And fortunately I coded:

exception
    when others then
      execute immediate
          'alter session set nls_date_format=''dd-MON-rr'' ';
      raise;
end;
/

I just wanted to put the default date format back -- so many things can get
screwed up if it isn't what it should be.

Of course, it would be even better if i queried the value of nls_date_format out
and used that -- but for my purposes -- dd-mon-rr is just what I use.
Rating: 4
print_table wrap routine February 07, 2003
Reviewer: Andrew from Troy, MI USA
I took up your challenge (???) to improve the print_table proc so that it wraps
before hitting the 255 char limit of dbms_output.put_line.  Here it is...  (In
case it matters to anyone, I did this in 9.2)
- - - - -
CREATE OR REPLACE
PROCEDURE print_table( p_query IN VARCHAR2 )
AUTHID CURRENT_USER
IS
/*
   these two constants manage output formatting for strings.
   adjust them as you wish.
 */
    c_text_seg_len       CONSTANT NUMBER        := 65;   -- do not exceed 224
    c_cont_char          CONSTANT VARCHAR2(30)  := '.';  -- for continuation

    Csr_Da_Cursor                 INTEGER DEFAULT dbms_sql.open_cursor;
    v_Column_Value                VARCHAR2(4000);
    v_Column_Title                VARCHAR(30);
    v_Status                      INTEGER;
    v_Table_Desc                  dbms_sql.desc_tab;
    v_Column_Count                NUMBER;
    v_nls_date_restore            VARCHAR2(1000);
BEGIN
/*
   this section prepares for resetting then nls_date_format.
   adjust it to meet your needs.
 */
--  SELECT 'ALTER SESSION SET nls_date_format = ''' || value ||''''
--    INTO v_nls_date_restore
--    FROM v$parameter
--   WHERE name = 'nls_date_format';

  SELECT 'ALTER SESSION SET nls_date_format = ''DD-MON-RR'''
    INTO v_nls_date_restore
    FROM dual;

  EXECUTE IMMEDIATE 'ALTER SESSION SET nls_date_format = ''dd Mon yyyy
hh24:mi:ss'' ';

  dbms_sql.parse( Csr_Da_Cursor,
                  p_query,
                  dbms_sql.native );

  dbms_sql.describe_columns ( Csr_Da_Cursor,
                              v_Column_Count,
                              v_Table_Desc  );

  FOR i IN 1 .. v_Column_Count
  LOOP
     dbms_sql.define_column ( Csr_Da_Cursor,
                              i,
                              v_Column_Value,
                              4000 );
  END LOOP;

  v_Status := dbms_sql.execute( Csr_Da_Cursor );

  WHILE ( dbms_sql.fetch_rows( Csr_Da_Cursor ) > 0 )
   LOOP
     FOR i IN 1 .. v_Column_Count
     LOOP
        dbms_sql.column_value ( Csr_Da_Cursor,
                                i,
                                v_Column_Value );

        IF v_Table_Desc(i).col_type = 1
        THEN
           IF LENGTH(v_Column_Value) > 0
           THEN
           BEGIN
             v_Column_Title    := RPAD( v_Table_Desc(i).col_name, 30 );
             FOR j in 1 .. CEIL(LENGTH(v_Column_Value)/c_text_seg_len)
             LOOP
               dbms_output.put_line(v_Column_Title || ': ' ||
                      SUBSTR(v_Column_Value, (((j-1)*c_text_seg_len)+1),
c_text_seg_len));
               v_Column_Title := RPAD( c_cont_char, 30 );
             END LOOP;
           END;
           ELSE
 dbms_output.put_line ( RPAD( v_Table_Desc(i).col_name, 30 ) || ': ' ||
v_Column_Value );
           END IF;
        ELSIF v_Table_Desc(i).col_type IN ( 8, 23, 24, 112, 113, 114, 115)
        THEN
           dbms_output.put_line ( RPAD( v_Table_Desc(i).col_name, 30 ) || ': **
UNPRINTABLE **' );
        ELSE
           dbms_output.put_line ( RPAD( v_Table_Desc(i).col_name, 30 ) || ': '
|| v_Column_Value );
        END IF;

     END LOOP;

    dbms_output.put_line( '-----------------' );
   END LOOP;

  EXECUTE IMMEDIATE v_nls_date_restore;
EXCEPTION
     WHEN others THEN
        EXECUTE IMMEDIATE v_nls_date_restore;
        RAISE;
END;
/
show errors
Rating: 5
Very Nice August 08, 2003
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
I was getting the following error,

ORA-20000: ORU-10028: line length overflow, limit of 255 bytes per line

and I used

SET SERVEROUT ON SIZE 1000000

it worked for me.
Rating: 4
BLOBS? December 18, 2003
Reviewer: Vinnie from Orlando
Tom,

How can I modify the print_table procedure
to process or skip blob data?
Followup:
here is my "coolest" printtbl so far, look for the bolded code and add other
types as you like (113 is "blob")

create or replace
procedure print_table
( p_query in varchar2,
  p_date_fmt in varchar2 default 'dd-mon-yyyy hh24:mi:ss' )

-- this utility is designed to be installed ONCE in a database and used
-- by all.  Also, it is nice to have roles enabled so that queries by
-- DBA's that use a role to gain access to the DBA_* views still work
-- that is the purpose of AUTHID CURRENT_USER
AUTHID CURRENT_USER
is
    l_theCursor     integer default dbms_sql.open_cursor;
    l_columnValue   varchar2(4000);
    l_status        integer;
    l_descTbl       dbms_sql.desc_tab;
    l_colCnt        number;
    l_cs            varchar2(255);
    l_date_fmt      varchar2(255);

    -- small inline procedure to restore the sessions state
    -- we may have modified the cursor sharing and nls date format
    -- session variables, this just restores them
    procedure restore
    is
    begin
       if ( upper(l_cs) not in ( 'FORCE','SIMILAR' ))
       then
           execute immediate
           'alter session set cursor_sharing=exact';
       end if;
       if ( p_date_fmt is not null )
       then
           execute immediate
               'alter session set nls_date_format=''' || l_date_fmt || '''';
       end if;
       dbms_sql.close_cursor(l_theCursor);
    end restore;
begin
    -- I like to see the dates print out with times, by default, the
    -- format mask I use includes that.  In order to be "friendly"
    -- we save the date current sessions date format and then use
    -- the one with the date and time.  Passing in NULL will cause
    -- this routine just to use the current date format
    if ( p_date_fmt is not null )
    then
       select sys_context( 'userenv', 'nls_date_format' )
         into l_date_fmt
         from dual;

       execute immediate
       'alter session set nls_date_format=''' || p_date_fmt || '''';
    end if;

    -- to be bind variable friendly on this ad-hoc queries, we
    -- look to see if cursor sharing is already set to FORCE or
    -- similar, if not, set it so when we parse -- literals
    -- are replaced with binds
    if ( dbms_utility.get_parameter_value
         ( 'cursor_sharing', l_status, l_cs ) = 1 )
    then
        if ( upper(l_cs) not in ('FORCE','SIMILAR'))
        then
            execute immediate
           'alter session set cursor_sharing=force';
        end if;
    end if;

    -- parse and describe the query sent to us.  we need
    -- to know the number of columns and their names.
    dbms_sql.parse(  l_theCursor,  p_query, dbms_sql.native );
    dbms_sql.describe_columns
    ( l_theCursor, l_colCnt, l_descTbl );

    -- define all columns to be cast to varchar2's, we
    -- are just printing them out
    for i in 1 .. l_colCnt loop
        if ( l_descTbl(i).col_type not in ( 113 ) )
        then
            dbms_sql.define_column
            (l_theCursor, i, l_columnValue, 4000);
        end if;
    end loop;

    -- execute the query, so we can fetch
    l_status := dbms_sql.execute(l_theCursor);

    -- loop and print out each column on a separate line
    -- bear in mind that dbms_output only prints 255 characters/line
    -- so we'll only see the first 200 characters by my design...
    while ( dbms_sql.fetch_rows(l_theCursor) > 0 )
    loop
        for i in 1 .. l_colCnt loop
            if ( l_descTbl(i).col_type not in ( 113 ) )
            then
                dbms_sql.column_value
                ( l_theCursor, i, l_columnValue );
                dbms_output.put_line
                ( rpad( l_descTbl(i).col_name, 30 )
                || ': ' ||
                substr( l_columnValue, 1, 200 ) );
            end if;
        end loop;
        dbms_output.put_line( '-----------------' );
    end loop;

    -- now, restore the session state, no matter what
    restore;
exception
    when others then
        restore;
        raise;
end;
/
December 18, 2003 Robin -- Thanks for the question regarding "Column Frequecy Table", version 9.2.0.4
You Asked
Hello,

I am having difficulty creating a procedure that will insert frequencies into a
normalized table.

The table would look something like:

create table freq_check (
schema_name varchar2(32),
table_name varchar2(32),
column_name varchar2(32),
column_value varchar2(2000),
frequency number(9,0)
)

I would like the procedure to accept a schema, table and column name and insert
the distinct values in that column and the count of the occurrences of that
value.

First off, should I use dbms_metadata or all_tables/user_tables.

This seems simple, but I also would like to do it securely.

The end result is a form or page that allows a user to select columns from any
of their tables and the procedure would generate a normalized table that counts
occurrences for the columns selected.  This table could hold frequencies for
multiple tables.
and we said...
why would you store this in a table??? given that the answer is wrong right
after the data is generated (assuming a database with updates against it).

why would you not just query the table and display the results??????

but in any case

ops$tkyte@ORA920> create or replace procedure count_em_up
  2  ( p_owner in varchar2, p_tname in varchar2, p_cname in varchar2 )
  3  authid current_user
  4  as
  5  begin
  6        execute immediate
  7        'insert into freq_check
  8         select :x, :y, :z, '  || p_cname || ', count(*)
  9           from ' || p_owner || '.' || p_tname || '
 10          group by ' || p_cname
 11        using upper(p_owner), upper(p_tname), upper(p_cname);
 12  end;
 13  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec count_em_up( 'scott', 'emp', 'deptno' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec count_em_up( 'scott', 'dept', 'dname' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from freq_check;

SCHEMA_NAM TABLE COLUMN_NAM COLUMN_VAL  FREQUENCY
---------- ----- ---------- ---------- ----------
SCOTT      EMP   DEPTNO     10                  3
SCOTT      EMP   DEPTNO     20                  5
SCOTT      EMP   DEPTNO     30                  6
SCOTT      DEPT  DNAME      ACCOUNTING          1
SCOTT      DEPT  DNAME      OPERATIONS          1
SCOTT      DEPT  DNAME      RESEARCH            1
SCOTT      DEPT  DNAME      SALES               1

7 rows selected.

I used an invokers rights routine so the procedure runs as the caller (making it
so the owner of this procedure need not have access to the base tables)
Review & Followup

Rating: 5
Perfect November 26, 2003
Reviewer: Robin Halabicki from Winnipeg, MB Canada
The reason we needed this was so that a very basic user without sql knowledge
could select from an app their tables and do frequency responses (ie a web page
that would select the schema thru pulldown list, then that schemas tables, then
that tables columns).  Also, by storing them in a table, the frequencies can be
compared over time.  I like your procedure using sql in it to ensure data
security.

Now that I see your response I see how simple it is.
Rating: 4
December 18, 2003
Reviewer: Rob from Winnipeg MB Canada
I been testing your code and I've come up with this so far using what you gave
me and information I looked at in Chapeter 8 of Developers Guide

Create or Replace Procedure freqs
    (p_owner VARCHAR2, p_tname VARCHAR2) authid current_user IS

    TYPE loc_array_type IS TABLE OF VARCHAR2(30)
        INDEX BY binary_integer;
    dml_str VARCHAR2        (250);
    loc_array    loc_array_type;
    dt date := sysdate;
BEGIN
    -- bulk fetch the list of columns
    SELECT column_name BULK COLLECT INTO loc_array
        FROM all_tab_columns
        WHERE table_name=p_tname and owner=p_owner;

    FOR i IN loc_array.first..loc_array.last LOOP
        dml_str :=
        'insert into freq_check (schema_name, table_name, column_name,
column_value, frequency, run_date)
         select :x,:y,:z, trim('||loc_array(i)||'), count(*), :d from ' ||
p_owner || '.' || p_tname || ' group by '||loc_array(i)||'';
    EXECUTE IMMEDIATE dml_str using upper(p_owner), upper(p_tname),
upper(loc_array(i)), dt;
    END LOOP;
    commit;
END;

From what I read in the chapter (and in your forums), EXECUTE IMMEDIATE does not
share sql.  So would using DBMS_SQL be better in this case?
Followup:
execute immediate does not KEEP THE CURSOR OPEN.

execute immediate creates sharable sql.

since you are in fact NOT executing the same sql over and over in this session,
the fact it does not keep the cursor open is OK.

If you execute this procedure in two sessions -- only ONE of them will hard
parse at most, the other will soft parse and use the shared sql.

the sql is shared.
Rating: 4
Something like December 18, 2003
Reviewer: Robin from Winnipeg MB Canada
Create or Replace Procedure freqs2
    (p_owner VARCHAR2, p_tname VARCHAR2) authid current_user IS

    TYPE loc_array_type IS TABLE OF VARCHAR2(30)
        INDEX BY binary_integer;
    dml_str VARCHAR2        (250);
    loc_array    loc_array_type;
    dt date := sysdate;
    cur_hdl INT;
    rows_processed NUMBER;

BEGIN
    -- bulk fetch the list of columns
    SELECT column_name BULK COLLECT INTO loc_array
        FROM all_tab_columns
        WHERE table_name=p_tname and owner=p_owner;

    FOR i IN loc_array.first..loc_array.last LOOP
        dml_str :=
        'insert into freq_check (schema_name, table_name, column_name,
column_value, frequency, run_date)
         select :x,:y,:z, trim('||loc_array(i)||'), count(*), :d from ' ||
p_owner || '.' || p_tname || ' group by '||loc_array(i)||'';
    cur_hdl := dbms_sql.open_cursor;
    dbms_sql.parse (cur_hdl, dml_str, dbms_sql.native);
    dbms_sql.bind_variable (cur_hdl, ':d', dt);
    dbms_sql.bind_variable (cur_hdl, ':x', upper(p_owner));
    dbms_sql.bind_variable (cur_hdl, ':y', upper(p_tname));
    dbms_sql.bind_variable (cur_hdl, ':z', upper(loc_array(i)));
    rows_processed := dbms_sql.execute(cur_hdl);
    END LOOP;
    dbms_sql.close_cursor(cur_hdl);
    commit;
END;

And timing like

SQL> exec dbo.freqs('ML','MEMBERSHIP_DATA');

PL/SQL procedure successfully completed.

Elapsed: 00:00:59.07
SQL> exec dbo.freqs2('ML','MEMBERSHIP_DATA');

PL/SQL procedure successfully completed.

Elapsed: 00:00:50.09
Followup:
so, rerun freqs again and watch it go faster as well.

you perhaps warmed up the buffer cache.

but, you would want to compare a TKPROF, not wall clock.  wall clock is
meaningless.  run the procedure 50 times on a busy machine and watch the
variations in wall clock.
Rating: 4
TKProf December 18, 2003
Reviewer: Rob
Yes, I am in the process of checking, i notice that the time can vary on both
quite a bit.  I have tkprof reports for both and am reviewing.  Is there
anything you suggest I focus on?
Followup:
nope, just wanted you to see that "they are really the same" is all
December 18, 2003 -- Thanks for the question regarding "Business Logic - PL/SQL Vs Java - Reg", version 8.1.7.0
You Asked
Hi Tom

With oracle 9i where do u suggest to write the business logic
PL/SQL or Java.

Which is faster/flexible/portable.
Is oracle going to support PL/SQL in future.

Regards
and we said...
PLSQL isn't going away -- PLSQL was greatly added to in 9i.

For example -- native compilation was added -- turned plsql into object code
that runs natively on the OS.

I prefer to put all logic that deals with data in PLSQL.  There is no more
natural language to interact with SQL data then PLSQL.  None.
Review & Followup

Rating: 4
July 02, 2001
Reviewer: A. Vijayan from Singapore
We have done this in our project and this will help us inform our clinet that we
have given one of the best methods.
Rating: 2
PL/SQL vs. Java February 12, 2003
Reviewer: John from SJ, CA
Tom,

You said you prefer to put business logical that deal with data in PL/SQL.

Could you be more specific in term of what kind of data and how much in term of
quantity, what kind of transactions, etc. It will be nice you can give some
examples.
Followup:
if it touches data -- plsql.

If it is computing a fourier transformation -- java.

If it is processing data -- plsql.

If it is generating a graph -- java.

If it is doing a transaction of any size, shape or form against data -- plsql.
Rating: 4
what about EJB's? February 13, 2003
Reviewer: Dev from Mumbai India
Hey Tom,
  I thought EJB's were created for the very purpose of storing business logic
that interacts with data.
Any pointers on when to use EJB's vs PL/SQL ? I know you prefer to have
everything in the database as far as possible but any occasion in which you
would recommend using EJB's ?

Thanks for the great work keep it up.

Regards Dev
Followup:
Me, recommend a bean, for doing things to data... hmmm

As long as the bean does not cache the data outside of the database (else data
integrity is moot)

As long as the bean does not assume "only my bean logic does things to this data
(else why bother with a database)

but many times both points are violated - the java encapsulation logic caches
and assumes it owns the data -- making the data really not useful to any other
programming paradigm, of which there are still others out there.

In the end it is up to you -- but I will point you to this "interesting"
article:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6692296628899
I only published that one as a warning to others (since I never really answered
their un-answerable question)
Rating: 5
9i pl/sql February 13, 2003
Reviewer: mo
Tom:

<For example -- native compilation was added -- turned plsql into object code
that runs natively on the OS>

You mean I can run pl/sql like a unix shell script. Does not it need pl/sql
engine? do you run sql*plus and then the code like I used to do before from unix
or not?

2. Is there a good summay of the new 9i plsql features anywhere?

Thank you
Followup:
No, that is not what it means at all.  It means that when Oracle runs plsql it
can either

o interpret the pcode as it did forever before
o let the os run object code

It needs the database, the plsql engine, etc.  It is not a compiler to build
plsql programs outside of the database.

2) goto my homepage.  click on tidbits and howtos -- goto the bottom of that, i
have various presentations

Or -- we have this neat document entitled "Oracle9i New Features" -- it is on
otn under documention for the database ;)
Rating: 5
native February 13, 2003
Reviewer: mo
Oh is see like compiling java into bytecode that any o/s can interperet?

you compile pl/sql into o/s object code and then o/s will understand it?
Followup:
we translate PLSQL into C, the C compiler on the system compiles it into a
shared library (dll on windoze) and we call that compiled code.
Rating: 5
SQL in Java February 13, 2003
Reviewer: Sean from NJ, USA
Hi Tom,

This topic is very helpful to us.  Are you saying that ideally there should be
no select and DML statement in Java or VB?

Sean
Followup:
In *my* view of a perfect world -- yes ;)

It is an opinion, grounded in experience, but an opinion none the less.  There
are quite a few people with different opinions on that topic out there.
Rating: 4
adding Object-oriented flavor into PLSQL February 14, 2003
Reviewer: Jianhui(Frank) from Washington, DC
Tom,
  I'm personally a strong dictator of puting business logics into the plsql code
for the same reasons you mentioned here.
  I strongly oppose the object-oriented database or object-relational database
model. The reason is there is really no such OODB or ORDB models. The relational
model is very good for the OLTP system to manipulate the data that need to be
stored. The OO model is wonderful for applications which are the objects in the
memory and talking to each others, but not good at how to store&share the data
into the persistent database. There are just two different things and have their
own applications. Because the data should be shared and seen by the users that's
what database is all about. Although some DB vendors including Oracle add some
object flavor into the relational db, but those really aren't the essense of the
OO. For example, you cant make SQL object-oriented and take advantage of the
encapsulatioin, polymorsims, inheritance. So i would say the OO is good for
application analysis, design and programming (OOA/OOD/OOP). But relational model
is good to provide the views of data to multiple users by perciving data as
tables(relations), and relational operations(algebra) against these
tables(relations).
  OO is good but is not almighty, it has it's onwn applications. I havnt seen
any OO database models have the beauty of the relational model(simplicity, solid
methmatic basis, easy to use). We still need to do the research of the OO model
for database, and SQL is not a OO at all.
  As for OLAP applications, the relational model isnt good enough in my
oppionion. Since the fact tables have huge redundancy on the demention columns.
For example, a simple sales table (month, region, sales). for 12 months period,
100 regions, we have to stored 12*100 redundant data of the months and regions.
Imagine we have more demensions, d1*d2*d3*...*dn redundant data for only ONE
column real sales data we care. The overhead is singnifacant!!! we may use 90%
of the table just for these redundant data. Because it's relational model!!! If
we use multi-dementional model, we just need to store d1+d2+d3+...+dn ,
drastically reduce the redundancy and increase the performance by reducing the
amount of data we need to scan.
  All above i said just for a point which is everything has it's own limitation
and application.
  BTW, I have a suggestion for Oracle, which is adding some OO flavor to PLSQL
and let's call it PLSQL++. So i would create objects in the PLSQL just like java
or c++, inside of the methods of the objects, it's still the tradional PL + SQL
which are powerful to manipulate the data. But you overall plsql application is
OO and will take the advantage of OO. I would like to see my PLSQL++ code look
like this:

public class OrderProcess {
 ...
 public void addOrder (int OrderId, int customerId, ...){
   INSERT INTO order (orderId, customerId)
   VALUES (....)

}
So we can use it as following code:
  OrderProcess op = new OrderProcess();
  op.addOrder(OrderId, customerId, ...)

I hope Oracle10i would have PLSQL++ implemented!!! But the DB itself is still
based on the relational model!!! Until the computer scientists build a solid
OO-DB model, we still can see the long life of the relational model in the
market.
Followup:
Guess what -- your wish is our command -- we do that.  It is called "Object
Types" in oracle.

ops$tkyte@ORA920> create or replace type OrderProcess
  2  as object
  3  (       orderId int,
  4          customerId int,
  5
  6          member procedure addOrder
  7  )
  8  /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace type body orderProcess
  2  as
  3
  4  member procedure addOrder
  5  is
  6  begin
  7          dbms_output.put_line( 'insert ' || self.orderId || ' here..' );
  8  end;
  9
 10  end;
 11  /

Type body created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          x orderProcess;
  3  begin
  4          x := orderProcess( 11, 532 );
  5          x.addOrder;
  6  end;
  7  /
insert 11 here..

PL/SQL procedure successfully completed.

it already exists.  polymorphism, inheritance, encapsulation -- it is all there.
 No curly braces, but hey, you cannot have it all.

This is where I find the OR stuff useful actually - in making PLSQL easier to
code in some cases.
Rating: 4
adding Object-oriented flavor into PLSQL March 28, 2003
Reviewer: steve from NYC, USA
Hi Tom,

You said " it already exists.  polymorphism, inheritance, encapsulation -- it is
all there.
 No curly braces, but hey, you cannot have it all."

Could you please give me some example for polymorphism (not static overload),
inheritance of Oracle Object?

Thanks!

Steve
Followup:
We'll start off with the spec of the PARENT type.  We'll have
a function that is inherited, one we will overload and another
that we will override

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace type parent_type
  2  as
  3  object
  4  (       x int,
  5
  6      member function demo_inherited return varchar2,
  7          member function demo_overload( x int ) return varchar2,
  8      member function demo_override return varchar2
  9  )
 10  NOT FINAL
 11  /

Type created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

The body is trivial, just shows what function is actually being
called at runtime.  When we invoke a method, it will be clear
what method is really getting run

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace type body parent_type
  2  as
  3
  4  member function demo_inherited return varchar2
  5  is
  6  begin
  7          return 'I am defined in parent_type';
  8  end;
  9
 10  member function demo_overload( x int ) return varchar2
 11  is
 12  begin
 13          return 'I am parent_type with a number ' || x;
 14  end;
 15
 16  member function demo_override return varchar2
 17  is
 18  begin
 19          return 'I am parent_type';
 20  end;
 21
 22  end;
 23  /

Type body created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

Now, lets create the subclass type.  It will inherit, override
and overload from its parent

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace type subclass_type
  2  under parent_type
  3  ( member function demo_overload( x date ) return varchar2,
  4    OVERRIDING member function demo_override return varchar2
  5  )
  6  /

Type created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

And its type body now...

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace type body subclass_type
  2  as
  3
  4  member function demo_overload( x date) return varchar2
  5  is
  6  begin
  7          return 'I am subclass_type with a date ' || x;
  8  end;
  9
 10  OVERRIDING member function demo_override return varchar2
 11  is
 12  begin
 13          return 'I am subclass_type';
 14  end;
 15
 16  end;
 17  /

Type body created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

We'll use this procedure to see polymorphism at work in PLSQL

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace procedure demo_proc( p_object
in parent_type )
  2  as
  3  begin
  4          dbms_output.put_line( p_object.demo_inherited() );
  5          dbms_output.put_line( p_object.demo_overload(55) );
  6          dbms_output.put_line( p_object.demo_override() );
  7  end;
  8  /

Procedure created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

Note the procedures that get invoked via a DIRECT dispatch on the
instance variable as well as a dispatch via the procedure above (which
thinks its working on a parent_type object all of the time)

ops$tkyte@ORA920.US.ORACLE.COM> declare
  2          l_parent_type parent_type := parent_type(0);
  3          l_subclass_type subclass_type := subclass_type(0);
  4  begin
  5          dbms_output.put_line( 'parent_type Direct Call...' );
  6          dbms_output.put_line( l_parent_type.demo_inherited() );
  7          dbms_output.put_line( l_parent_type.demo_overload(55) );
  8          dbms_output.put_line( l_parent_type.demo_override() );
  9          dbms_output.put_line( 'Via procedure...' );
 10          demo_proc(l_parent_type);
 11
 12
 13          dbms_output.put_line( 'subclass_type Direct Call...' );
 14          dbms_output.put_line( l_subclass_type.demo_inherited() );
 15          dbms_output.put_line( l_subclass_type.demo_overload(55) );
 16          dbms_output.put_line( l_subclass_type.demo_overload(sysdate) );
 17          dbms_output.put_line( l_subclass_type.demo_override() );
 18          dbms_output.put_line( 'Via procedure...' );
 19          demo_proc(l_subclass_type);
 20  end;
 21  /
parent_type Direct Call...
I am defined in parent_type
I am parent_type with a number 55
I am parent_type
Via procedure...
I am defined in parent_type
I am parent_type with a number 55
I am parent_type
subclass_type Direct Call...
I am defined in parent_type
I am parent_type with a number 55
I am subclass_type with a date 29-MAR-03
I am subclass_type
Via procedure...
I am defined in parent_type
I am parent_type with a number 55
I am subclass_type

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM>
Rating: 4
BC4J and E-Business Suite March 30, 2003
Reviewer: Rasin
Dear Mr. Tom,
In my 4 years of IT experience
I've seen many applications which wrote all their business logic in PL/SQL and
are running successfully till now.

I was with Oracle Financials for 1 year, and saw each bit of business logic
stored in DB (PL/SQL) and called from front end, although the front end was
Oracle Forms.

According to Oracle's new statement
http://otn.oracle.com/products/jdev/htdocs/bc4j/bc4jsod.html
I think they are doing everything on middle tier with BC4J and version 13 of
E-Business suite will be fully J2EE based with 9iAS.

Can you put some light on the above.

Business logic which is nothing but business rules for the organisation are
ultimately the validations which we write.
Here is what I do
do validations with triggers or in stored procedures.
repeat the validation on the middle tier by calling the stored procedures or
directly executing sql and also on the client side (values entered are in proper
format or not)with javascript if using browser or in applications either VB or
Forms.

Why do I repeat validations on the middle tier or the application client ?
why because you need to tell the user of the application what he did wrong, not
just displaying annoying messages like "Something went wrong with your data
entry please chek it"

I want to know whether I am right, repeating validation logic on each tier ? Or
just validate it at the database level.

Also what is your opinion about implementing business rules. How does you go
about writing it.

Thanks
Rasin
Followup:
do you think that middle tier programming is the end game?  The last of the
last?  The final programming paradigm?

If not, you need to put security and data integrity logic right next to the data
in the database.  Else when next great programming paradigm comes along, you'll
be ripping it out yet again from the applications and redoing it all over again.

If you do believe it is the final programming paradigm, I've got a bridge in
Brooklyn....
Rating: 3
I did'nt mean it is the final programming paradigm March 30, 2003
Reviewer: Rasin
I am sorry Mr. Tom you've taken my views the other way.

>I want to know whether I am right, repeating validation logic on each tier ? Or

just validate it at the database level.

I know that i am wrong but not this way

>If you do believe it is the final programming paradigm, I've got a bridge in
Brooklyn....

I beleive that security logic after implementing at the DB level is also
repeated at the application level, FGAC an exception to this.
I create screens and menus (either web pages or application forms) based on
users permissions and he/she should not be allowed to see the screens which
he/she does'nt have permission.

BTW
Oracle applications is a huge product, do you think that oracle choose BC4J just
to promote its J2EE products ?
Followup:
Don't get me wrong -- I'm agreeing with you.

You must do it at the database level, whether you want to do it for UI
convienence at the middle tier is up to you -- there are pros and cons for doing
it.  My point is -- you have no choice but to do it at the database level, in
the middle tier, go for it if it makes your application more usable.

BC4J is a framework -- J2EE is a standard.  We have products that conform to the
J2EE standard -- BC4J is just a framework on top of that to speed development.

Oracle invented BC4J (a framework).
We implemented it on top of J2EE (a standard).
so we can rapidly and reliable develop data based applications (apps)
Rating: 3
now we have PL/SQL++ March 31, 2003
Reviewer: jianhui from washington, DC
Thanks Tom for pointing it out, now i'm gonna implement lots of my PL/SQL code
by adding the OO flavor just like i have the new language PL/SQL++.

OO is good for analysis, design and programming. But i still dont see it's good
for data storage where the relational model is so graceful to map the logic view
of the data into table and connect the relationship by values instead of by
pointers.

If you mean OR is object oriented programing(plsql+type) plus relational model,
i think it's good methodology. I have no idea and dont see the necessaty of
using OO model to store the data, actually we need a new language other than SQL
to do this. SQL was designed for the relational algebra like projection,
selection, join, etc. Hard to imagine usging sql to run any type of OO database.
Followup:
if you have my book "expert one on one Oracle" -- read my chapter on OR
features.  we are in agreement here.
Rating: 5
April 02, 2003
Reviewer: A reader
I need polymorphism with out inheritance
is it possible ?
Followup:
and how would that work?  what do you mean?  polymorphism without inheritance
does not compute to me.
Rating: 3
I wonder how to store OOP data April 08, 2003
Reviewer: iamweng from China
I have saw the OOP character in Oracle 9i before, But I have not understood how
they are stored in ORACLE, and how to tune them. For an Instance, I can create
an abstract OOP type and an object table, then save all his child objects and
sub-child objects in this table. That is to say , I can store all the data in
one table from OOP view. However, Is it feasible?
Followup:
if you have my book "Expert One On One Oracle" (i know it is in china -- i have
a chinese copy, not that I can read it or anything...) I do a chapter on HOW
these things are stored.

In short -- not much different then if you did it yourself.  We are a relational
database -- we have object RELATIONAL extensions.  They are stored in relational
tables.  For example, A nested table is just a parent child table in disguise.
Rating: 5
more questons on what to put on middle tier versus database June 20, 2003
Reviewer: A reader
Tom
Please keep up the great work!!

Q1. "As long as the bean does not cache the data outside of the database (else
data integrity is moot)"

a. Could you elaborate how data integrity may be compromised
   by a middle tier cache?
b. Does this mean that one should not ever cache
   anything in the middle tier?
c. What are the precautions one should take
   and when would you think a cache of database data is
   justified/recommended?

Q2. An argument is that since middle tier runs on
cheaper hardware, for smaller business to scale in
a cost effective manner) we can move some of the
logic to middle tier. These could scale based on
appropriate load balancers.

a. What are your thoughts?
b. According to you what business logic(if any at all)
   should be retained in middle tier ?

Thank you!
Followup:
q1a) go into sqlplus and update the data after the bean retrieved it.  now what?

using that bean means "forget the fact you have an open database -- you cannot
use it, you can only use these beans over here for if you were to update the
data via some other method (like SQL), we will overwrite it later -- poof, by by
data"

b) if you ask me......  besides static reference data (lookups), no I do not
believe you should.

c) static read only reference data....

q2a) why is it cheaper?  I sort of thought a cpu cycle was a cpu cycle?

I put application logic in an application.  Data rules and Data processing in
the database.  Unless an application developer can tell me what ACID means, why
it is important, and how the database does it -- I'm not sure I want them owning
my data in that fashion, much much rather have it protected and managed in the
database where I know it is being used and maintained correctly.
Rating: 5
re: more questons on what to put on middle tier versus database June 23, 2003
Reviewer: A reader
Thank you for your help as always!!

"go into sqlplus and update the data after the bean retrieved it.  now what? "

I guess most developers would scream - hey you cant do
that - we dont guarantee that if you start updating
tables from sqlplus it would not screw things up!!
Also, what if you bypass the plsql procedures (That do
updates etc) and run a simple update (which for
example does not do some of the checks that the plsql procedure did?) I have a
feeling you would claim that
a user would not have the privilege to do this in
the first place? What I am saying is that if you start
mucking around in sqlplus you are toast anyways regardless
of whether your logic is in plsql or not, is nt it?

"why is it cheaper?  I sort of thought a cpu cycle was a cpu cycle?"

well, I guess we are talking about web farms with
appropriate load balancers - versus RAC. I am not
sure about the cost involved but I am told that the
former is much cheaper (in terms of expertise involved,
ease of attaching/detaching from the system/
maintenance etc). The above solution is being
proposed for smaller deployments where the customer
wants a cheaper solution.

Thank you once again for the great service that you
are doing to the Oracle community!!
Followup:
No, the developers are going to use some new programming paradigm -- they own
the data, why can't they update it?  If you want, use a PLSQL routine to update
the data -- does the same "bad cache thing".

My point is -- you start "beaning the data" with caching and all of a sudden you
no longer have an open system with a SQL RDBMS -- you have bean access --
period, zippo, thats all she wrote.

So, will this bean thing be "the last programming paradigm"? (don't even go
there...)

Lets see, one professionally managed database machine, or a "farm" -- which
would I rather have?

If you have the need for a farm, you need farmers.  You've just moved the
goalposts.

Did you ask this question of a database guy or an app server developer?
Different perspectives might bring about different answers.  Not saying I have
the right answer for you -- just that my answer comes based on my background --
where does theirs come from?

Most importantly, hugely important....
Why would a small deployment need anything above and beyond a SINGLE server?
Asktom is bigger then most small deployments here. I do about 30-50,000
transactions a day, servicing about 4-6,000 users a day, 15-20,000 a week.
About 1,000,000 transactions a month.

I've a rather small (old) machine running a database, mod_plsql and apache.
Spare Cycles (and I am definitely not the only application on here --
promedmail.org, oai.oracle.com and others are all hosted on this same machine,
same database, same apache).

You can squeeze quite a bit of juice out of this fruit, if you do it right.  App
server farms?  I see them being used for systems that need nothing bigger then
my desktop PC -- managed by an army of farmers.  cheaper?
Rating: 5
June 23, 2003
Reviewer: A reader
"I put application logic in an application.  Data rules and Data processing in
the database."

What is the boundary of application logic and data rules?
What is an "application logic" according to you?
If I am selecting rows from the database, doing some transformation with it and
displaying it, which portions
of the logic are "application logic" out of these steps?

Is application logic only about the "ui" portion - meaning
serving up jsp etc in case of a web application?

thank you!
Followup:
Application Logic = print numbers below 50 in red, between 51 and 100 in green,
above 100 in black.

Data Logic = numbers must be between 0 and 1000

for example.

selecting rows and transforming it for display -- depends on the transformation.
 I find SQL to be alot more efficient then most lovingly hand crafted, developed
code so I tend to do it there -- but that is somewhere between the data and the
application (eg: the query could be in the application, could be in the
database)

Specific application only rules -- rules that do not apply universally -- they
need be in the application (maybe this application can only work with "green and
black numbers" -- no red)...
Rating: 4
generally agree, but times are changing July 11, 2003
Reviewer: Stu Charlton from Toronto Canada
"My point is -- you start "beaning the data" with caching and all of a sudden
you no longer have an open system with a SQL RDBMS -- you have bean access --
period, zippo, thats all she wrote."

At an Oracle presentation at JavaOne, the presenter suggested that you guys are
working on ways to keep the database and EJB cache in synch, thus eliminating
this problem.  So I'm not sure this point will be relevent forever.

Caching data in a middle tier is a trade-off that's almost no different from the
one implict in a materialized view refresh policy.  If the data can diverge
(i.e. it's "read mostly"), then perhaps a middle tier cache works.  If it's
subject to highly concurrent reads & writes, then you're probably best to forego
the caching.

Here's the fundamental problem:  most of the problems of middle-tier vs.
database split have nothing to do wtih technology and everything to do with
developer culture and knowledge. The general trend is to develop systems with
object oriented techniques for greater flexibility and maintainability.  Whether
you agree with the effectiveness of that or not, it is the trend.  The problem
is that Oracle only very recently has included basic features for enabling this
kind of processing *inside* the database.   And even then, the language for
doing so is PL/SQL, not exactly a language that most OO developers are familiar
with.

Tom is correct that most systems do not need these fancy OO frameworks or
application servers.  They're overkill in a lot of ways.  But I'm not interested
in simple systems.  I'm interested with complex systems: really beefy, thorny,
hairy systems with terabytes of data, mixed workloads (OLTP and OLAP), and
enough divergent and varied business rules to make your head spin.

In my experience many cases of complex business systems that are difficult to
maintain precicely because the level of expression in the language is not as
appropriate as one at a higher level.  I used to be an object database
programmer (GemStone).   For a developer, it was the best thing since sliced
bread - you could create extremely sophisticated systems with it.  I know of
systems that even developed frames & slots based knowledge representation
frameworks -- and it performed extremely well.

 It's clear today that object databases are not workable for a variety of
reasons.  But we're still stuck in a bit of a straightjacket here -- if we want
to create highly sophisticated systems, the only workable solution is to use the
higher level languages and framewokrs -- which are only available in these
massive middle-tier application server monstrocities that take over many of the
functions of a database (poorly), and usually have horrible network overhead to
pull the data across from the database.

So I see a need to be filled by the dearth of rich toolsets to develop complex,
sophisticated information applications.  These are the kinds of applications
that the mainstream will be developing in 10 years.  And Oracle seems to be the
best company to get there, to me, but it's still not there yet, it still doesn't
"quite" have the flexibility of development we had with Gemstone 10 years ago.
OO is fundamentally about combining data and function together, and dividing
modules along lines of logical responsibility -- hence business logic probably
is best residing along side the data.  The problem is, we don't have the
realistic capability of doing that today for complex object-oriented systems.

In short, we really need a better integration between a host language and the
relational model.   Tom keeps on suggesting that J2EE will not be the last
paradigm -- I agree.  What should be next?  I think we need to bridge the
cultural gap between set-orientation, record-orientation and object-orientation.
 And we need a new host language to enable all of this.  Once that happens, this
"middle tier" debate might go away.  All business logic will be stored in the
database.  No questions, no philosophical struggles, no religious debates.

<end rant>
Rating: 3
OK August 10, 2003
Reviewer: R.Chakravarthi from Chennai,India
Dear Sir,
I have some questions for you.
1)Is there any parameter which automatically turns off redo
generation or we have to go for the command
 Alter table emp nologging.
2)Do you know the names of procedural languages of other databases like
Ingres,Mysql,Guptasql?If you know please inform me.
Followup:
1) you have to go far far out of your way to disable it on a very small set of
commands (things that do BULK operations).  disabling of redo is not something
we are "very into" as it sort of defeats the purpose of a database.

If you are doing a large bulk operation such as

o direct path load
o direct path insert
o create table as select
o alter move/alter rebuild
o create index

we can bypass redo generation (but you better coordinate with your DBA as they
need to schedule a backup right away)

for pretty much every other command -- no way.  redo is what databases do for
you.

2) nope.
Rating: 5
Contradiction? October 29, 2003
Reviewer: Kimathi from Los Angeles
Here is an interesting snippet of contradiction.

Chapter 7 - Data Access in J2EE Applications
Expert One-on-One J2EE Design and Development
by Rod Johnson

"Important     Do not use stored procedures to implement business logic. This
should be done in Java business objects. ....."

Your thoughts Tom?
Followup:
my thoughts -- don't ask java programmers for best practices on database design
and implementation?

ask database people (who can actually be java programmers as well).

depends on what school of thought you are coming from.  If you truly believe
that java is the last language, and j2ee is the the programming paradigm --
sure, hide all of your business logic in that code making it 100% sure that for
you java is the last language and j2ee is the last programming paradigm (until
some other generation of developer must rip the logic back out, like we did with
green screen apps)

there are times when it makes sense to use stored procedures
there are times when it makes sense to use java
Rating: 5
polymorphism question December 18, 2003
Reviewer: vll from USA
Hi, Tom!

I have one question/problem. Let's say we have a parent_type and a child_type
under it. We create a table with a field of parent_type. Everything is fine at
this point - we can insert objects of both types in a table. Now we create a new
child_type1 under existing parent_type. And as soon as we are done with this -
no way to insert in a table any of child_types - only parent_type. If we
recreate the table - everything is fine again, we can insert all 3 types. At
least this is what I'm getting. Is it OK or I'm doing something wrong? If this
is how it should be - it's very-very inconvenient :-(((.
Thanks a lot for your help, Tom!!!
Followup:
give me tiny, yet 100% complete, yet concise - example of your issue.  I'll
build on that.
Rating: 5
polymorphism question December 18, 2003
Reviewer: vll from USA
Never mind, Tom!
The problem was that I was doing everything in one session. (I mean adding new
derived child_type and trying to insert new values in an object table). It works
fine! Oracle 9i is really great!!!
thanks a lot!
Rating: 3
Java & Pl/sqlObjects December 18, 2003
Reviewer: Govind Agnihotri from San Rafael, CA USA
Tom ,
 Thank you for the great service you provide for Oracle community.

Currently we are redesigning out System architechture. It is very clear that
data validation and business logic belongs  with pl/sql. We  made a policy that
the presentation layer  should  not handle any  business logic what so ever but
will "control" the  end users  errors.

So  we have
Browser ->App Server -->JDBC --> Pl/sql ( Oracle).

We have designed "pl/sql type" hirearchy for most the data in the database  and
coded pl/sql objects which will be passed to the presentation layer API's
written in Java. There will be JDBC calls for the  object/data  exchange.

Currently we have few users testing it,  We have challenges keeping the pl/sql
objects valid when underlying type definitions change, ( Calls for a robust
change control)

 Do you forsee any performance issues  for us in the future? What do you think
of the  design? Your comments would be much appreciated.

Thanks
Govind.
Followup:
what does this type hierarchy look like?  what are we talking about exactly (too
vague to say if I can forsee any performance issues)
December 18, 2003 nikhil -- Thanks for the question regarding "sqlloader versus external table.", version 9.0.1
You Asked
hi Tom,

  Is there any advantage of sql* Loader over the new 9i feature external table.
Which of them is faster. sql* loader direct path load or direct path load from
the external table.

 thanx in advance

Nikhil
India
and we said...
well, lets do a simple example and see what we see:

ops$tkyte@ORA920.US.ORACLE.COM> create or replace directory data_dir as '/tmp/'
  2  /
Directory created.

ops$tkyte@ORA920.US.ORACLE.COM> create table external_table
  2  (EMPNO NUMBER(4) ,
  3   ENAME VARCHAR2(10),
  4   JOB VARCHAR2(9),
  5   MGR NUMBER(4),
  6   HIREDATE DATE,
  7   SAL NUMBER(7, 2),
  8   COMM NUMBER(7, 2),
  9   DEPTNO NUMBER(2)
 10  )
 11  ORGANIZATION EXTERNAL
 12  ( type oracle_loader
 13    default directory data_dir
 14    access parameters
 15    ( fields terminated by ',' )
 16    location ('emp.dat')
 17  )
 18  /
Table created.

ops$tkyte@ORA920.US.ORACLE.COM> create or replace view emp_view
  2  as
  3  select * from scott.emp, all_objects;
View created.

ops$tkyte@ORA920.US.ORACLE.COM> host flat / emp_view > /tmp/emp.dat

see
http://asktom.oracle.com/~tkyte/flat/index.html
for 'flat'

ops$tkyte@ORA920.US.ORACLE.COM> !wc /tmp/emp.dat
 410340  568568 57946304 /tmp/emp.dat

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select count(*) from external_table
  2  /

  COUNT(*)
----------
    410340

ops$tkyte@ORA920.US.ORACLE.COM> set timing on
ops$tkyte@ORA920.US.ORACLE.COM> create table emp as select * from
external_table;

Table created.

Elapsed: 00:00:03.13

ops$tkyte@ORA920.US.ORACLE.COM> insert /*+ append */ into emp select * from
external_table;

410340 rows created.

Elapsed: 00:00:07.69

ops$tkyte@ORA920.US.ORACLE.COM> truncate table emp;

Table truncated.

versus

[tkyte@tkyte-pc sqlstuff]$ time sqlldr / /tmp/emp.ctl direct=true

SQL*Loader: Release 9.2.0.1.0 - Production on Mon Nov 25 09:35:34 2002

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Load completed - logical record count 410340.

real    0m8.747s
user    0m2.422s
sys     0m0.588s
[tkyte@tkyte-pc sqlstuff]$

totally non-scientific but here is my opinion

External tables are so much easier the sqlldr -- insert /*+ append */ is showing
about the same performance, CTAS even better.

You can use SQL to filter and transform the data much easier.  True, sqlldr in
direct path can now use sql functions -- but the filtering is really powerful

multi-table inserts, merge's -- can't be beat.

I really dig external tables.
Review & Followup

Rating: 5
Very good example. November 25, 2002
Reviewer: Leo from Boston, MA
Rating: 4
external tables May 11, 2003
Reviewer: A reader from USA
My understanding is that external tables are read only and cannot permit any
DML. We cannot create any indexes or constraints. How external tables will be
useful (only for queries that too without any indexes)? If I used sqlldr, I can
load the data into a table that I can use for further DML in my database. am i
missing anything?

apologize for my ignorance. Could you please clarify? thanks.
Followup:
you are using external tables *instead of* sqlldr.

with external tables you can

o merge a flat file with an existing table in one statement.
o sort a flat file on the way into a table you want compressed nicely.
o do a parallel direct path load -- without splitting up the input file, writing

  umpteen scripts and so on
o run sqlldr in effect from a stored procedure or trigger (insert is not sqlldr)
o do multi-table inserts
o flow the data through a pipelined plsql function for cleansing/transformation

and so on.  they are *instead of* sqlldr -- to get data into the database
without having to use sqlldr in the first place.

You would not normally query them day to day in an operational system, you use
them to load data.
Rating: 4
Thanks a lot! May 11, 2003
Reviewer: A reader from USA
Rating: 4
My 2.5 cents... May 11, 2003
Reviewer: kashif from Houston, TX
Hi Tom,

I've noticed that timings on both SQL Loader and External Table loads (using
insert /*+ append */) are roughly comparable, but the real boost I get is when I
need to update data post-load, from different tables. For example, recently I
modified a process which was as follows:

Use sqlldr to upload file to table A.
Update table A using codes from table B and table C.

Now, I simply do:
Rename file to name of external table source (e.g. 123.txt).
Insert into table A from external table. Use previous updates as embedded
selects in insert statements.

The real difference I get is when I can simply select into the target table's
column values I would otherwise have updated after the load, using selects. This
way, I have one insert statement, with all the updates I need to do as part of
the same statement, in the form of selects. To give you some timing numbers, the
sqlldr and updates took almost an hour and a half. With the external tables and
the embedded selects the process finishes in about 15 minutes. And it is so much
easier to use, compared to sqlldr.

Kashif
Rating: 5
Sqlldr vs Temprary Table May 12, 2003
Reviewer: Reaz/Dhaka from Bangladesh
I am using it in a real time project which is underway. Frankly , this new
feature of 9i has made our ETL process lot simpler then it was.

For example, the whole of filtering/cleansing could now be done in flat file
without loading it in database.

This is definetley a powerful feature.
Rating: 5
Excellent Example May 12, 2003
Reviewer: Abubaker Khered from Jeddah, Saudi Arabia
Rating: 5
Some difficulties in doing the example May 13, 2003
Reviewer: Abubaker from Jeddah, Saudi Arabia
Hi Tom,

I liked the example above very much, because it is going to make my live a lot
easier. But when I am about to do it I was stuck-up at:

SQL> !wc /tmp/emp.dat
SP2-0734: unknown command beginning "!wc /tmp/e..." - rest of line ignored.

Also is there anyway to check the result of the following command:
SQL> host flat / emp_view > /tmp/emp.dat

Although I have copied the files you mentioned in the above link (flat,
sqldr_exp) to the working directory where I run the sqlplus from.

I am having oracle 92 on NT server.

Would you please explain what I was doing wrong.

Regards,
Abubaker
Followup:
windoze does not do "wc", windows does not do !, you must use HOST.

I do have flat and sqlldr_exp for windows here:
http://asktom.oracle.com/~tkyte/flat/index.html
you would of course use windoze file names, not /tmp/emp.dat
Rating: 5
External tables and zoned data June 25, 2003
Reviewer: Bob Rich from IN, USA
Tom,
  We have an existing application that uses sqlloader to load zoned data from a
mainframe.  There seems to be a bug with 9.2 (bug 3 3022089) that is delaying an
upgrade. Can an external table be used for this application?
Followup:
did you try?  i have no "zoned data" with which to do so myself.
Rating: 4
bug with external tables and ltrim (or lrtrim) June 26, 2003
Reviewer: Freek from Belgium
Tom,

When I use the lrtrim or the ltrim keyword in an external table definition,then
I get the field too long for datatype error when the value of the field are
spaces.
When I use no trim or rtrim then I don't get this error.

Is this a bug or am I doing something wrong?

thanks

Freek

system@dev01> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
PL/SQL Release 9.2.0.1.0 - Production
CORE    9.2.0.1.0    Production
TNS for 32-bit Windows: Version 9.2.0.1.0 - Production
NLSRTL Version 9.2.0.1.0 - Production

system@dev01> create directory external_table_dir as 'd:\arch\debug';

Directory created.

system@dev01> grant read, write on directory external_table_dir to freek;

Grant succeeded.

system@dev01> @connect freek@dev01

freek@dev01> create table test
  2  (
  3      test1               varchar2(50),
  4      test2               varchar2(50)
  5  )
  6  organization external
  7  (
  8      type oracle_loader
  9      default directory external_table_dir
 10      access parameters
 11      (
 12          records delimited by newline
 13          skip 1
 14          fields terminated by 0x'09'
 15          lrtrim
 16          missing field values are null
 17          (
 18              test1               char(50),
 19              test2               char(50)
 20          )
 21      )
 22      location ('test.txt')
 23  )
 24  /

Table created.

freek@dev01> select * from test;
select * from test
*
ERROR at line 1:
ORA-29913: error in executing ODCIEXTTABLEFETCH callout
ORA-30653: reject limit reached
ORA-06512: at "SYS.ORACLE_LOADER", line 14
ORA-06512: at line 1

freek@dev01> drop table test;

Table dropped.

freek@dev01> create table test
  2  (
  3      test1               varchar2(50),
  4      test2               varchar2(50)
  5  )
  6  organization external
  7  (
  8      type oracle_loader
  9      default directory external_table_dir
 10      access parameters
 11      (
 12          records delimited by newline
 13          skip 1
 14          fields terminated by 0x'09'
 15          rtrim
 16          missing field values are null
 17          (
 18              test1               char(50),
 19              test2               char(50)
 20          )
 21      )
 22      location ('test.txt')
 23  )
 24  /

Table created.

freek@dev01> column test1 format a30
freek@dev01> column test2 format a30
freek@dev01> select * from test;

TEST1                          TEST2
------------------------------ ------------------------------
test                           test
                               test

content test.txt
----------------

test1    test2
test    test
        test

output from log file
--------------------

 LOG file opened at 06/24/03 16:57:29

Field Definitions for table TEST
  Record format DELIMITED BY NEWLINE
  Data in file has same endianness as the platform
  Rows with all null fields are accepted

  Fields in Data Source:

    TEST1                           CHAR (50)
      Terminated by "09"
      Trim whitespace from left and right
    TEST2                           CHAR (50)
      Terminated by "09"
      Trim whitespace from left and right
KUP-04021: field formatting error for field TEST1
KUP-04026: field too long for datatype
KUP-04101: record 3 rejected in file d:\arch\debug\test.txt

 LOG file opened at 06/24/03 16:58:49

Field Definitions for table TEST
  Record format DELIMITED BY NEWLINE
  Data in file has same endianness as the platform
  Rows with all null fields are accepted

  Fields in Data Source:

    TEST1                           CHAR (50)
      Terminated by "09"
      Trim whitespace from right
    TEST2                           CHAR (50)
      Terminated by "09"
      Trim whitespace from right

content of bad file:
--------------------

        test
Followup:
appears to be an issue in 9201, cannot reproduce it in 9203.  it is caused by
the null field apparently.

suggestion until you get to 9203:

o use a view

organization external
(
    type oracle_loader
    default directory external_table_dir
    access parameters
    (
        records delimited by newline
        skip 1
        fields terminated by 0x'09'
        missing field values are null
        (
            test1  char(50),
            test2  char(50)
        )
    )
    location ('test.txt')
)
/
create or replace view v
as
select trim( test1 ) test1, trim( test2 ) test2 from test;

to trim.....
Rating: 3
Not bad, but there's better ways of doing this June 30, 2003
Reviewer: Nigel Heffernan from ULondon, England
The lowly MS-Access programmers have a slower, but much, much easier method. The
Oracle target table and the source file can both be declared as 'Linked' tables
- definitions are created, but no data is imported in an Access database - and
then its just a case of writing (or visually building) a single JET-SQL
'INSERT...' query.

MS-Access was designed for botched, bodged and cobbled-together data operations.
Oracle wasn't, and it's unreasonable to expect a 'professional' RDBMS to do
anything more than simple flat file imports.

The 9i external table is cool, because data in external files is now part of
your data 'world' and accessible to SQL sorting and searching - but you're never
going to get the Oracle database engine performance until its in a real Oracle
table.

For large-scale operations - a million records and upwards - some kind of GREP
or VB File I/O and string-handling  should be used to concatenate 'INSERT INTO'
statements. These can be sent directly to the Database using an
OracleInProcServer object, and you'll find it's an order of magnitude faster
than the Oracle 8 Loader.

Here's an example:

Option Explicit

Public Function TransferCSV( _
                    CsvFileName As String, _
                    HostName As String, _
                    DbName As String, _
                    DbPassword As String, _
                    DestinationTable As String, _
                    ParamArray DestinationFields()) _
                As Long

'Loads comma-separated data to an Oracle table.
'Works faster than JET text ISAM into an ODBC-Linked table
'And can load more than the Excel 65535 row limit

'Timed at 1200 records per second

'SAMPLE USAGE:

'lngRet = TransferCSV( _
'           "D:\Program Files\Develop\LoadData.csv", _
'           "**HOST**", "**USER**", "**PASSWORD**", _
'           "tblPrice", _
'           "PriceID", "StockID", "TradingDay", "ClosingPrice", "logChangeOnDay"
_
'           )

' DestinationFields can be a VBA array, a parameter set, or the boolean value
' TRUE denoting that the first line of the output file specifies the field names

'Author: Nigel Heffernan, Credit Lyonnais Securities Feb 2003
'        Heffernann@YAH00.com

'Thanks to Denis Duchene for Oracle OO40 sample VBA code

Dim SQL As String
Dim SQLstub As String
Dim SQLdata As String

Dim iField As Integer
Dim lngProgress As Long
Dim lngFile As Long

Dim strData As String

Const ORADB_NO_REFETCH As Long = &H4&

TransferCSV = False

'Early binding code for performance: requires VBE reference to
\Orant\Bin\OIP.tlb
Dim oraSession As OracleInProcServer.OraSessionClass
Dim oraDatabase As OracleInProcServer.oraDatabase
Set oraSession = New OracleInProcServer.OraSessionClass

'Late-Binding code for compatibility across all Oracle clients:
'Dim oraSession As Object
'Dim oraDatabase As Object
'Set oraSession = CreateObject("OracleInProcServer.XOraSession")

'VBA File I/O

    lngFile = FreeFile
    Open CsvFileName For Input As #lngFile

        'SQL stub String-handling: the target is this type of SQL statement:
        'SQL = "INSERT INTO tblPrice ( PriceID, StockID, TradingDay,
ClosingPrice, logChangeOnDay) Values ( -2147483641, 1194, 36795, 310, -0.009631
)"

        SQLstub = ""
        SQLstub = SQLstub & "INSERT INTO " & DestinationTable & " ("

        If InStr(TypeName(DestinationFields(0)), "(") > 0 Then
           'Its an array of field Names

            For iField = LBound(DestinationFields(0)) To
UBound(DestinationFields(0))
                SQLstub = SQLstub & " " & DestinationFields(0)(iField) & ","
            Next iField

        ElseIf IsNumeric(DestinationFields(0)) Then
            'Use the first row of the file

            If DestinationFields(0) = True Then

                strData = ""
                Line Input #lngFile, strData
                strData = ReplaceString(strData, Chr(34), "")
                strData = ReplaceString(strData, Chr(39), "")
                strData = strData & ","
                SQLstub = SQLstub & strData

            End If

        Else
            'It's an element parameter list array

            For iField = LBound(DestinationFields) To UBound(DestinationFields)
                SQLstub = SQLstub & " " & DestinationFields(iField) & ","
            Next iField

        End If

        'Trim trailing comma
        SQLstub = Left(SQLstub, Len(SQLstub) - 1)
        SQLstub = SQLstub & " ) Values ( "

    Set oraDatabase = oraSession.OpenDatabase(HostName, DbName & "/" &
DbPassword, ORADB_NO_REFETCH)
    oraSession.BeginTrans

    Do While Not EOF(lngFile)

        SQL = ""
        strData = ""

        Line Input #lngFile, strData

        SQL = SQLstub & strData & " )"

        oraDatabase.ExecuteSQL SQL

        lngProgress = lngProgress + 1

        If lngProgress Mod 1000 = 0 Then

            oraSession.CommitTrans
            oraSession.BeginTrans

            '***** Comment this out if your VB host app doesn't support
.Statusbar
            Application.StatusBar = "Loading to Oracle... " & lngProgress & "
records"
            '*****

        End If

    Loop 'EOF(lngFile)

ExitFunction:

    Close #lngFile
    oraSession.CommitTrans
    oraDatabase.Close

    Set oraSession = Nothing

    TransferCSV = lngProgress

    '**** Comment this out if your VB host app doesn't support .Statusbar
    Application.StatusBar = "Ready  ...Loaded " & lngProgress & " records to
Oracle."
    '**** MS-Access 97 code is syscmd acsyscmdsetstatus, "Ready  ...Loaded " &
lngProgress & " records to Oracle."

End Function

Public Function ReplaceString(InputString, SearchString, InsertString) As String
On Error Resume Next

'Replaces all instances of search string in input string with an insert string
'Regardless of relative lengths of search and input string

Dim i As Integer
Dim iPosition As Integer

Dim strTemp As String
Dim strOutput As String

strOutput = ""
strTemp = ""
iPosition = 1

If SearchString = InputString Then
    ReplaceString = InsertString
    Exit Function
End If

If InStr(InputString, SearchString) < 1 Then
    ReplaceString = InputString
    Exit Function
End If

If SearchString = "" Then
    ReplaceString = InputString
    Exit Function
End If

Do Until iPosition > VBA.Len(InputString)
    i = InStr(iPosition, InputString, SearchString)
    If i < 1 Then
        strOutput = strOutput & VBA.Right(InputString, 1 + VBA.Len(InputString)
- iPosition)
        Exit Do
    End If

    strOutput = strOutput & VBA.Mid(InputString, iPosition, i - iPosition) &
InsertString
    iPosition = i + VBA.Len(SearchString)

Loop

If VBA.Len(strOutput) < 1 Then
    strOutput = InputString
End If

ReplaceString = strOutput

End Function
Followup:
i have to believe (hope anyway) that you are being tongue in cheek sarcastic?

Not that I can benchmark your code as it just refused to be compiled on open
systems...

But, I see not a bind variable anywhere.  Very scary, I wouldn't let that code
near my database.

How this is easier then

Insert into T select * from external_table;

I just cannot fathom.
Rating: 1
Nigel Heffernan - Benchmarks to prove it? June 30, 2003
Reviewer: Kamal Kishore from New Jersey, USA
<quote>
and you'll find it's an order of magnitude faster
than the Oracle 8 Loader.
</quote>

But, I do NOT see any benchmark test(s) to prove that this load mechanism is
"magnitude" faster???

We would like to hear more from the poster on this statement before this can be
taken any seriously or it should be kept aside as a slower and bulky alternative
(I do not think this can be categorized as an alternative, but, for the sake of
argument...).
Rating: 3
To Nigel...pls take your VB stuff..... June 30, 2003
Reviewer: Robert from PA, USA
>> Not bad, but there's better ways of doing this

Wa..ha..ha..ha...And then there you go entertaining us
with your bit of VB "magic" (got another word for it).

Well, chap you're not entirely hopeless, at least you're doing OO4O - but do it
via st. procedure and use BV for crying out loud.

Tom is too nice a bloke to say this. So I will be bold & say: VB/Acce$$ stuff
don't belong here.
Take it to the M$ newsgroups or put it where the sun don't shine.

We dig open source here.
Followup:
I dig open systems.
Rating: 4
Yeah, that's what i mean......sorry June 30, 2003
Reviewer: Robert from PA
>..I dig open systems.

Got a bit brainwashed by this GNU/FSF/"Open whatever"/"Peace on Earth"
these days...they all began to mean the same thing to me...
Rating: 3
an order of magnitude faster July 01, 2003
Reviewer: Nigel Heffernan from London, England
Hmmm... I post an easy fix and I get grief from the 'open source' guys. Me, I
think ease-of-use is more valuable than ideological purity. It's also the reason
this board exists: nobody comes here because of problems with Oracle
performance.

Anyway, in answer to the more serious questions on speed, here's the file,
fields delimited by comma, records separated by Chr(10):

"PriceID","StockID","TradingDay","ClosingPrice","logChangeOnDay"
-2147483581,1027,33379,375.90,0.01
-2147483189,1834,35384,10.65,-.00292356
-2147480993,1299,36780,968.00,-0.01
-2147480914,505,36928,80.00,0.03
-2147480587,1021,36879,823.01,0.00120362

You'll need another 3,500,000 records to replicate the live operation and I have
no plans to post them.

PriceID is a primary key, long integer; StockID is a foreign key, indexed, long
int; TradingDay is long int, indexed; the other two fields are single-precision
floats.

I had to do this four times: the Oracle 8 loader, when I finally got it working,
took 3:35 then 4:10 hours to load it all. Two VB runs took 44 and 46 minutes.

...On NT 4.0. I suspect it would be faster still on any UNIX system, as the file
i/o functions are better than anything available in VB and NT.

Viewed purely from an Oracle perspective, maybe my code doesn't belong here.
Viewed from the perspective of a developer who works with a mix of RDBMS
systems, and is constantly asked to deal with file imports from legacy systems,
the answer is: what works, works.

People come here for easy fixes, not for lectures on the evils of MS
applications.

I've uploaded similar files to an IBM mainframe, a proprietary trading system
and NT SQL Server databases. I used the same VB script (albeit with ADO, which
is slower and less stable than OO40) and it ran faster than the native text-file
import drivers two of those databases, too.

Given the headaches I get dealing with all those proprietary systems, I might
have a complete sense-of-humour failure when told how much people in academia
and single-vendor environments dig 'Open Source' solutions. The real world is
locked into a balkanised mess, all of us lowly programmers would like the
authority and the budget to replace it, and you can't get there from here, even
by digging.
Followup:
people don't come here for performance? kidding right.

anyway - an order of magnitude is 10x, but the math using your numbers is 5x -
NOT that sqlldr would ever take that long -- not even close.

I will say this -- you messed something up hugely in your test.  I've loaded
millions of records using sqlldr (in a similar format) and it takes MINUTES, not
hours.

Minutes -- not hours.  Really, minutes.

To me, the 44minute VB run is way too slow (about 2 orders of magnitude too
slow).  That should not take more then a couple of minutes to load up. (and
since vb doesn't run on unix, it would be hard to "test")

You did something seriously big time *wrong*, I would find 44minutes to be about
42 minutes too long.  Here we are -- on a small machine (my desktop PC -- intel
based, IDE drives, running RedHat Linux).  I created a flat file:

tkyte@tkyte-pc Desktop]$ head test.dat
1,17602,17602,17602,17602
2,7516,7516,7516,7516
3,23098,23098,23098,23098
4,13926,13926,13926,13926
5,22663,22663,22663,22663
6,10130,10130,10130,10130

loaded it:

[tkyte@tkyte-pc Desktop]$ sqlldr / test data=test.dat direct=y

and the timing is

Table V:
  3817192 Rows successfully loaded.
  0 Rows not loaded due to data errors.
  0 Rows not loaded because all WHEN clauses were failed.
  0 Rows not loaded because all fields were null.

Bind array size not used in direct path.
Column array  rows :    5000
Stream buffer bytes:  256000
Read   buffer bytes: 1048576

Total logical records skipped:          0
Total logical records read:       3817192
Total logical records rejected:         0
Total logical records discarded:        0
Total stream buffers loaded by SQL*Loader main thread:      799
Total stream buffers loaded by SQL*Loader load thread:        0

Run began on Tue Jul 01 08:27:37 2003
Run ended on Tue Jul 01 08:30:00 2003

Elapsed time was:     00:02:22.17
CPU time was:         00:00:10.19

The table V was:

ps$tkyte@ORA920> @desc v
 Name                                 Null?    Type
 ------------------------------------ -------- -------------------------
 C1                                   NOT NULL NUMBER
 C2                                            NUMBER
 C3                                            NUMBER
 C4                                            NUMBER
 C5                                            NUMBER

Indexes on v

Index                          Is
Name                           Unique COLUMNS
------------------------------ ------ --------------------------------
V_IDX1                         No     C3
V_IDX2                         No     C5
SYS_C005924                    Yes    C1

So, very similar to your setup (since numbers are numbers are numbers in Oracle
-- everything is a NUMBER)

4 hours -- not a chance, not a chance at all. Even if I go with the slower
conventional path load (which one would not really do in the real world), the
times are no where near 45 minutes.

and it is about open SYSTEMS, not source really.  Your VB implementation is
something I would avoid like the plague as I cannot use it anywhere (really -- I
could not)

I live in the real world -- but in my real world, that sqlldr would take 4 hours
to load a measely 3.5 million records would be a huge red flag that "I've done
something horribly wrong" -- not motivation to write code that can only be
slower.

I'm not even sure your machine could parse 3.5 million UNIQUE sql statements in
44minutes -- and if it did, 99% of your run time was spent KILLING the server --
forcing it to hard parse like that.

Single row inserts
No binds
Ugh
Rating: 5
Other part of the load? July 01, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Maybe he can post for all of us to see as to how the alternative load approach
(that took 4 Hours!!!) was actually performed.

We would like to see that approach as an example to be able to take the posted
run times differences seriously.
Rating: 3
I like to see the DBA's face...really.... July 01, 2003
Reviewer: Robert from PA
>>......3.5 Mil records
>> Single row inserts
>> No binds
>> Ugh

Nigel, does (any of) your DBA has ANY CLUE that you're doing this ??? Very
curious...reall
Maybe you should show & impress him/her like you're trying to convince us
"unbelivers".
Go knock her socks off ! :)
Rating: 4
Humble pie... July 02, 2003
Reviewer: Nigel Heffernan from London
> but in my real world, that sqlldr would take 4 hours
to load a measely 3.5 million records would be a huge red flag that "I've done
something horribly wrong"

I work to a different benchmark: it works and its faster than MS-Access, so I
must be doing something right.

(I look forward to reading the responses to that)

On a more serious note, what are the most common errors in importing data into
Oracle?

All replies appreciated

Nigel Heffernan

And finally, a sour note:

> I'm not even sure your machine could parse 3.5 million UNIQUE sql statements
in 44minutes -

I doubt it even raised a sweat. Companies nowadays - especially banks - spend a
fortune on high-performance hardware because its cheaper than efficient code.
Until it eventually breaks down...

     ...Which is why there are so many MS-Access systems out there, fine for the
dozen users they were designed for, until until you're told the extra memory
they bought isn't working and discover 50 to 100 users on three continents
generating data volumes in the millions, rather than thousands of records that
were originally anticipated.

And that is a matter of commercial interest to Oracle Corporation.

Nowadays more systems are upsized than designed from scratch, and M$ made it
almost a single-click operation to move the back end of an Access system up to
SQL Server. Which is unfortunate, because a well-designed Access system can act
as the client to any server and performs significantly better with Oracle.

Let's face it, the task isn't made easy for us. Years ago I found Sybase a lot
more helpful. The learning curve is very steep and every VB or Access developer
who do tries to work out how to do it is left wondering whether anybody,
anywhere, has ever upsized to Oracle before.

Most database administrators have even less knowledge of client design than us
front-end developers have of Oracle coding. The constant complaint is: "Oracle
people live in the server room" and you're lucky if they'll build the tables for
you. I've worked in two companies where the DBAs flatly deny that 'parameter
queries' - functions returning a cursor - can be implemented in Oracle and I
suspect that those who *do* know how its done are unaware of the importance of
parameter queries in small desktop databases.

We'll ignore the vocal minority who are resolutely 'open source' and disdain all
effluvia from Redmond - which goes down a treat when most developers have to
write the client apps in VB and run it on NT.

Why complain?

I hate reinventing the wheel. I particularly hate discovering, months later,
that square wheels worked great for sliding things downhill but now reveal
fundamental conceptual flaws. It's all very well saying: "read the introductory
manuals again, bozo" but I am beginning to suspect that there are no part-time
Oracle developers, only dedicated experts.

I'll be spending most of next year separating out the back-end and front-end
operations of Access databases, turning them into proper client-server
applications. Plus a set of Excel spreadsheets that act as front ends to
MS-Access 'servers' that are approaching the golden gigabyte that marks complete
collapse.

The current situation is that I can get dedicated servers but not an Oracle
developer or a training budget: hardware 'fixes' really are that cheap. Luckily
the benchmark is that if it works and runs faster than Access, nobody's going to
complain. Least of all the DBA, given the workload they get.

Lucky too, that Oracle's a lot more stable, even under adverse conditions, than
its next-biggest rival.

Lucky for you, too, that you won't read many gripes like this: most VB
developers just give up on Oracle and choose an intellectually-undemanding
solution.
Rating: 2
creating external sequence not right? July 09, 2003
Reviewer: David from Dallas
Hi Tom,

Following your example given, I was trying to practice this by myself and there
are some problems that I encountered. I am not sure if your answer really was
what you did.

1. creating external_table before creating emp_view will not load emp.dat data
into the external table.

2. The host flat / emp_view > /tmp/emp.dat generated file is a tab diliminated
file rather than a comma diliminated file as it showed in your create
external_table syntax. So when create external_table the data will not be loaded
because the flat generate data file is not compabible as your FIELDS DILIMINATED
BY ',' syntax

It took me quite a bit time to figure out the correct sequence and get the
correct results. So here is my question

Was that the exact sequence and syntax in your responds for the question?

Best Regards,
Followup:
that was a straight cut and paste from a sqlplus session, it is exactly what I
did.

1) that is correct but so?  I didn't query the external table until after I
filled up the file.

2) the flat I used myself:

[tkyte@tkyte-pc Desktop]$ flat scott/tiger emp
7369,SMITH,CLERK,7902,17-DEC-80,800,,20
7499,ALLEN,SALESMAN,7698,20-FEB-81,1600,300,30
7521,WARD,SALESMAN,7698,22-FEB-81,1250,500,30
7566,JONES,MANAGER,7839,02-APR-81,2975,,20
7654,MARTIN,SALESMAN,7698,28-SEP-81,1250,1400,30
7698,BLAKE,MANAGER,7839,01-MAY-81,2850,,30
7782,CLARK,MANAGER,7839,09-JUN-81,2450,,10
7788,SCOTT,ANALYST,7566,09-DEC-82,3000,,20
7839,KING,PRESIDENT,,17-NOV-81,5000,,10
7844,TURNER,SALESMAN,7698,08-SEP-81,1500,0,30
7876,ADAMS,CLERK,7788,12-JAN-83,1100,,20
7900,JAMES,CLERK,7698,03-DEC-81,950,,30
7902,FORD,ANALYST,7566,03-DEC-81,3000,,20
7934,MILLER,CLERK,7782,23-JAN-82,1300,,10

uses commas. the one on the download site does use tabs, you are correct --
sorry about that.

All you needed was a ',' delimited file.  sorry about that, the sequence of
steps however, is correct.
Rating: 5
Thanks Tom July 09, 2003
Reviewer: David from Dallas
Thanks Tom for clarifying my concern. Now I understand that we don't have to
have the emp.dat data ready when we create external_table.
Rating: 3
External tables and EBCDIC July 10, 2003
Reviewer: Adrian from UK
Tom

Is there any way of using external tables with EBCDIC files? Our database is
ASCII-7. Furthermore, the files are littered with COMP-3 (packed decimal)
fields. So far I've tried the "dd" UNIX command to convert to ASCII but the
COMP-3's get screwed. Our source system is outsourced so to get them to batch to
ASCII rather than EBCDIC is a non-starter (just getting them to make a coffee
would cost us hundreds of thousands, be 9 months late and be wrong anyway).

Any assistance you could offer on this would be invaluable...
Followup:
it might look something like this:

CREATE table....
(
  X NUMBER,
  Y VARCHAR2(25),
  Z DATE
)
ORGANIZATION external
(
  TYPE oracle_loader
  DEFAULT DIRECTORY ....
  ACCESS PARAMETERS
  (
    RECORDS FIXED 80 CHARACTERSET WE8EBCDIC500
    READSIZE 1048576
    FIELDS LDRTRIM
    REJECT ROWS WITH ALL NULL FIELDS
    (
....
    )
  )
  location
  (
    'test.dat'
  )
)

if you have that character set installed...
Rating: 5
Couldn't find that WE8EBCDIC500 anywhere... July 10, 2003
Reviewer: Adrian Billington from UK
Tom thanks. Used that WE8EBCDIC500 in SQL*Loader all over the place in my last
job but couldn't find the charset name anywhere on the web or in the docs at
tahiti/otn. I knew it wasn't simply "EBCDIC" but just couldn't crack it. Thanks
again.
Rating: 4
External Table Load - From a file on a different Host September 09, 2003
Reviewer: HB from Canada
Hi Tom,
       Your explanation was super. I have one simple question, - can we use ET
to load data from a file that is on different machine? If answer is "no" can
this is done in future Oracle release.
We currently use SQL*Loader to load data from a VMS machine to Oracle server on
Windows 2000 and would like to use ET?
FTping the files would not be an option as files are big and have do every time
there is data change in the base file.
Thanks
Followup:
the file sort of needs to be ACCESIBLE to the server -- think about it, how can
Oracle reach out and grab a file from "just anywhere"???

the files need to be moved over the network regardless right -- (yes, rhetorical
question).  whether you do that via FTP or via an nfs mount or a samba share --
doesn't matter.  they'll be going over the network regardless.
Rating: 4
Why isn't it working for me ? October 24, 2003
Reviewer: Sonali from waltham, ma
Do I need any init.ora parameters ? Am I missing something ?
I have a tab delimited text file and here is the script I wrote after reading
this ...

Connect system @emerald;
create directory external_table_dir as 'd:\dumps\test';
grant read, write on directory external_table_dir to sak;
connect sak@emerald;

Create table test_mweborg
(
ORG_ID                          NUMBER  ,
ORG_ITEM_CODE                           VARCHAR2(15),
ORG_DESCRIPTION                         VARCHAR2(80),
ORG_LEVEL                               NUMBER,
ORG_COMPANY_NUMBER                      NUMBER,
ORG_PAR1                                NUMBER,
ORG_PAR2                                NUMBER,
ORG_PAR3                                NUMBER,
ORG_PAR4                                NUMBER,
ORG_PAR5                                NUMBER,
ORG_PAR6                                NUMBER,
ORG_PAR7                                NUMBER,
ORG_PAR8                                NUMBER,
ORG_PAR9                                NUMBER,
ORG_PAR_ITEM_CODE1                      VARCHAR2(15),
ORG_PAR_ITEM_CODE2                      VARCHAR2(15),
ORG_PAR_ITEM_CODE3                      VARCHAR2(15),
ORG_PAR_ITEM_CODE4                      VARCHAR2(15),
ORG_PAR_ITEM_CODE5                      VARCHAR2(15),
ORG_PAR_ITEM_CODE6                      VARCHAR2(15),
ORG_PAR_ITEM_CODE7                      VARCHAR2(15),
ORG_PAR_ITEM_CODE8                      VARCHAR2(15),
ORG_PAR_ITEM_CODE9                      VARCHAR2(15),
ORG_STATUS                              NUMBER,
ORG_CREATE_DATE                         DATE,
ORG_EFFECTIVE_DATE                      DATE,
ORG_MODIFY_DATE                         DATE,
ORG_MANAGER                             NUMBER,
ORG_HISTORY_ID                          NUMBER
)
   organization external
    (
        type oracle_loader
        default directory external_table_dir
       access parameters
       (
           records delimited by newline
           skip 1
           fields terminated by 0x'09'
           lrtrim

       )
       location ('test_revised_tab.txt')
    )
   /
I get this error why ?
SQLWKS> select count(*) from test_mweborg
     2>
COUNT(*)
----------
ORA-29913: error in executing ODCIEXTTABLEOPEN callout
ORA-29400: data cartridge error
KUP-04063: unable to open log file TEST_MWEBORG_1376_3900.log
 OS error The data is invalid.
ORA-06512: at "SYS.ORACLE_LOADER", line 14
ORA-06512: at line 1

Thanks
Followup:
is d:\dumps\test

a) on the windows server the database runs on
b) accessible to oracle itself

at the very least, it seems that the directory is not writable at the OS level
for Oracle.
Rating: 4
forgot to mention we have 9.2.0.1.0 version October 24, 2003
Reviewer: sonali from waltham, ma
Rating: 4
Now I get this error October 24, 2003
Reviewer: sonali from waltham, ma
SQLWKS> create or replace view v
     2> as
     3> select * from test_mweborg;
Statement processed.
SQLWKS>
SQLWKS> select * from v where rownuM<2;
ORG_ID     ORG_ITEM_CODE   ORG_DESCRIPTION
                           ORG_LEVEL  ORG_COMPAN ORG_PAR1   ORG_PAR2   ORG_PAR3
 ORG_PAR4   ORG_PAR5   ORG_PAR6   ORG_PAR7   ORG_PAR8   ORG_PAR9
ORG_PAR_ITEM_CO ORG_PAR_ITEM_CO ORG_PAR_ITEM_CO ORG_PAR_ITEM_CO ORG_PAR_ITEM_CO
ORG_PAR_ITEM_CO ORG_PAR_ITEM_CO ORG_PAR_ITEM_CO ORG_PAR_ITEM_CO ORG_STATUS
ORG_CREATE_DATE      ORG_EFFECTIVE_DATE   ORG_MODIFY_DATE      ORG_MANAGE
ORG_HISTOR
---------- ---------------
--------------------------------------------------------------------------------
---------- ---------- ---------- ---------- ---------- ---------- ----------
---------- ---------- ---------- ---------- --------------- ---------------
--------------- --------------- --------------- --------------- ---------------
--------------- --------------- ---------- --------------------
-------------------- -------------------- ---------- ----------
ORA-29913: error in executing ODCIEXTTABLEFETCH callout
ORA-30653: reject limit reached
ORA-06512: at "SYS.ORACLE_LOADER", line 14
ORA-06512: at line 1

I figured out the other error, but then I hit
ORA-29913: error in executing ODCIEXTTABLEFETCH callout
ORA-30653: reject limit reached
ORA-06512: at "SYS.ORACLE_LOADER", line 14
ORA-06512: at line 1
this error, so I created view like you said, but still can't select from that
view...Sorry about the ignorence, but trying this for the 1st time.
Followup:
check out your logs -- by default they are generated into the same directory.

you probably have some bad dates or numbers in there.

I like to start with all VARCHAR2 columns just to see whats where and then add
the "real" types in after the fact.
Rating: 4
Thanks.. October 24, 2003
Reviewer: sonali from waltham, ma
I think you are right, it has to with data, the thing is I can't figure out what
it is ??

drop table test_mweborg;
Create table test_mweborg
(
ORG_ID                          varchar2(10)  ,
ORG_ITEM_CODE                           VARCHAR2(15),
ORG_DESCRIPTION                         VARCHAR2(80),
ORG_LEVEL                               varchar2(10),
ORG_COMPANY_NUMBER                      varchar2(10),
ORG_PAR1                                varchar2(10),
ORG_PAR2                                varchar2(10),
ORG_PAR3                                varchar2(10),
ORG_PAR4                                varchar2(10),
ORG_PAR5                                varchar2(10),
ORG_PAR6                                varchar2(10),
ORG_PAR7                                varchar2(10),
ORG_PAR8                                varchar2(10),
ORG_PAR9                                varchar2(10),
ORG_PAR_ITEM_CODE1                      VARCHAR2(15),
ORG_PAR_ITEM_CODE2                      VARCHAR2(15),
ORG_PAR_ITEM_CODE3                      VARCHAR2(15),
ORG_PAR_ITEM_CODE5                      VARCHAR2(15),
ORG_PAR_ITEM_CODE6                      VARCHAR2(15),
ORG_PAR_ITEM_CODE7                      VARCHAR2(15),
ORG_PAR_ITEM_CODE8                      VARCHAR2(15),
ORG_PAR_ITEM_CODE9                      VARCHAR2(15),
ORG_STATUS                              varchar2(10),
ORG_MANAGER                             varchar2(10),
ORG_HISTORY_ID                          varchar2(10)
)
   organization external
    (
        type oracle_loader
        default directory external_table_dir
       access parameters
       (
           records delimited by newline
           skip 1
           fields terminated by 0x'09'
            missing field values are null
        (ORG_ITEM_CODE                           VARCHAR2(15),
        ORG_DESCRIPTION                         VARCHAR2(80),
        ORG_LEVEL                               varchar2(10),
        ORG_COMPANY_NUMBER                      varchar2(10),
        ORG_PAR1                                varchar2(10),
        ORG_PAR2                                varchar2(10),
        ORG_PAR3                                varchar2(10),
        ORG_PAR4                                varchar2(10),
        ORG_PAR5                                varchar2(10),
        ORG_PAR6                                varchar2(10),
        ORG_PAR7                                varchar2(10),
        ORG_PAR8                                varchar2(10),
        ORG_PAR9                                varchar2(10),
        ORG_PAR_ITEM_CODE1                      VARCHAR2(15),
        ORG_PAR_ITEM_CODE2                      VARCHAR2(15),
        ORG_PAR_ITEM_CODE3                      VARCHAR2(15),
        ORG_PAR_ITEM_CODE5                      VARCHAR2(15),
        ORG_PAR_ITEM_CODE6                      VARCHAR2(15),
        ORG_PAR_ITEM_CODE7                      VARCHAR2(15),
        ORG_PAR_ITEM_CODE8                      VARCHAR2(15),
        ORG_PAR_ITEM_CODE9                      VARCHAR2(15),
        ORG_STATUS                              varchar2(10),
        ORG_MANAGER                             varchar2(10),
        ORG_HISTORY_ID                          varchar2(10)
        )

       )
       location ('test_revised_tab.txt')
    )
   /

I changed all the numbers to varchar as you suggested..  I keep getting this
error now, which does not generate logs anymore
ORA-29913: error in executing ODCIEXTTABLEOPEN callout
ORA-29400: data cartridge error
KUP-00554: error encountered while parsing access parameters
KUP-01005: syntax error: found "identifier": expecting one of: "comma, char,
date, defaultif, decimal, double, float, integer, (, nullif, oracle_date,
oracle_number, position, raw, recnum, ), unsigned, varrawc, varchar, varraw,
varcharc, zoned"
KUP-01008: the bad identifier was: varchar2
KUP-01007: at line 6 column 35
ORA-06512: at "SYS.ORACLE_LOADER", line 14
ORA-06512: at line 1

It was generating the logs before, till I changed all NULL ( spelled out words)
in the text file to nothing doing find and replace, because number datatype did
not like NULL (word)..
How do I make it work ? What am I missing now ?

Thanks
Sonali
Followup:
don't use varchar2 down at the bottom -- in fact, don't use anything, just list
the column names.
Rating: 5
There is a very easy way to use External Table--Read Tom Book October 24, 2003
Reviewer: Ajeet from OH.USA
Guys,
If you have used SQLLOADER earlier --Tom has made External table very simple for
you --in his latest book , he has told how to convert sqlloader script to
external table..as we all have been using sqlloader since long--just use that
commnad...
it is something like
sqlldr userid/password name.ctl external_table=generate_only...
and you are done..it tells you allmost all you need to know.Also you don;t have
to worry about data types etc..

Just a thought..however.I felt it alot easir than writing a script for External
tables specially if you have file having alot of fields in it...

Thanks
Rating: 5
Very good example to help us to understand October 25, 2003
Reviewer: Pauline from NY, U.S.A.
Tom,
In order to follow your sample to practice create external
table, I use one of our 9i database to test. Also in order
to create external user, I edit the pfile and then shutdown
db, create spfile to pfile.

The exact things I did :
 <dbdev1:dev3:/appl/oracle/product/9.2.0.1.0/dbs> export ORACLE_SID=gp9i
 <dbdev1:gp9i:/appl/oracle/product/9.2.0.1.0/dbs> sqlplus '/ as sysdba'

 SQL*Plus: Release 9.2.0.4.0 - Production on Fri Oct 24 15:29:50 2003

 Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

 Connected to an idle instance.

 SQL> startup
 ORACLE instance started.

 Total System Global Area  320309648 bytes
 Fixed Size                   731536 bytes
 Variable Size             285212672 bytes
 Database Buffers           33554432 bytes
 Redo Buffers                 811008 bytes
 Database mounted.
 Database opened.
 SQL> select name from v$database;

 NAME
 ---------
 GP9I

SQL> create or replace directory data_dir as '/tmp/';
 Directory created.

 SQL> create table external_table
      (EMPNO NUMBER(4) ,
      ENAME VARCHAR2(10),
      JOB VARCHAR2(9),
      MGR NUMBER(4),
      HIREDATE DATE,
      SAL NUMBER(7, 2),
      COMM NUMBER(7, 2),
      DEPTNO NUMBER(2)
    )
    ORGANIZATION EXTERNAL
    ( type oracle_loader
      default directory data_dir
      access parameters
      ( fields terminated by ',' )
      location ('emp.dat')
    );

 Table created.

 SQL> sho parameter os_authent_prefix

 NAME                                 TYPE        VALUE
 ------------------------------------ -----------
 ------------------------------
 os_authent_prefix                    string      ops$

 Now I want to change the parameter for os_authent_prefix and then create
 an ORACLE external user, so I do

 SQL> shutdown immediate;
 Database closed.
 Database dismounted.
 ORACLE instance shut down.

 At this moment, I see:

 <dbdev1:dev3:/appl/oracle/product/9.2.0.1.0/dbs> ls -l *gp9i*
 -rw-r--r--   1 oracle   dba         1024 Sep 12 10:40 initgp9i.ora
 -rwSr-----   1 oracle   dba         1536 Oct 24 15:33 orapwgp9i
 -rw-r-----   1 oracle   dba         2560 Jul 16 19:08 spfilegp9i.ora

 Then I edit  initgp9i.ora to add one more line below.

 <dbdev1:dev3:/appl/oracle/product/9.2.0.1.0/dbs> vi initgp9i.ora

 *.os_authent_prefix=""

 SQL> create spfile from pfile;

 File created.

 <dbdev1:dev3:/appl/oracle/product/9.2.0.1.0/dbs> ls -l *gp9i*
 -rw-r--r--   1 oracle   dba         1047 Oct 24 16:11 initgp9i.ora
 -rwSr-----   1 oracle   dba         1536 Oct 24 15:33 orapwgp9i
 -rw-r-----   1 oracle   dba         3584 Oct 24 16:12 spfilegp9i.ora

SQL> startup
 ORACLE instance started.
 ORA-24324: service handle not initialized
 ORA-24323: value not allowed
 ORA-03113: end-of-file on communication channel

SQL> shutdown abort
 ORA-24324: service handle not initialized
 ORA-01041: internal error. hostdef extension doesn't exist

 SQL> create pfile from spfile;
 create pfile from spfile
 *
 ERROR at line 1:
 ORA-03114: not connected to ORACLE

 SQL> conn / as sysdba
 ERROR:
 ORA-03113: end-of-file on communication channel

Now I run into big problem. I can't conn sys to start or shutdown database. The
db is generating gp9i_pmon_N.trc (800k size) every 3-4 minutes and core dump is
generating every 5 minutes.

The content for gp9i_pmon_N.trc is something like:

Fri Oct 24 16:38:47 2003
Errors in file /appl/oracle/admin/gp9i/bdump/gp9i_pmon_25647.trc:
ORA-00445: background process "QMN0" did not start after 120 seconds
Fri Oct 24 16:41:56 2003
Restarting dead background process QMN0

By read oracle trace file and see:

/appl/oracle/admin/gp9i/udump/gp9i_ora_6276.trc
Oracle9i Enterprise Edition Release 9.2.0.4.0 - 64bit Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.4.0 - Production
ORACLE_HOME = /appl/oracle/product/9.2.0.1.0
System name:    SunOS
Node name:      dbdev1
Release:        5.8
Version:        Generic_108528-13
Machine:        sun4u
Instance name: gp9i
Redo thread mounted by this instance: 0 <none>
Oracle process number: 0
6276

Exception signal: 11 (SIGSEGV), code: 1 (Address not mapped to object), addr:
0x64, PC: [0x1027b25b0, 00000001027B25B0]
*** 2003-10-24 17:39:25.526
ksedmp: internal or fatal error
ORA-07445: exception encountered: core dump [00000001027B25B0] [SIGSEGV]
[Address not mapped to object] [0x000000064] [] []
Current SQL information unavailable - no session.

Now I only can guess the another DBA created this instance from 9.2.0.1.0. But
she moved all other instances to 9.2.0.4.0 and this instance uses spfilegp9i.ora
in same $ORACLE_HOME/dbs with other instance and same environment variables for
9.2.0.4.0 software. So the instance first starup without problem. Once create
spfile from pfile,
the problem happened.

Please help how to stop this instance running. Now I could
not connect sys as sysdba to shutdown. So many trace and core dump files will
fill up file system soon.

As always, thanks for your help.

Pauline
Followup:
just exit sqlplus, go back in, shutdown abort, exit sqlplus and startup.

it'll be fine.

you did do this "the hard way"

ops$tkyte@ORA920> show parameter os

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
optimizer_index_cost_adj             integer     100
os_authent_prefix                    string      ops$
....

ops$tkyte@ORA920> alter system set os_authent_prefix='' scope=spfile;
System altered.

sys@ORA920> startup force
ORACLE instance started.

Total System Global Area  190386792 bytes
Fixed Size                   451176 bytes
Variable Size             117440512 bytes
Database Buffers           67108864 bytes
Redo Buffers                5386240 bytes
Database mounted.
Database opened.
sys@ORA920>
Rating: 5
Very good example to help us to understand October 25, 2003
Reviewer: Pauline from NY, U.S.A.
Tom,
In order to follow your sample to practice create external
table, I use one of our 9i database to test. Also in order
to create external user, I edit the pfile and then shutdown
db, create spfile to pfile.

The exact things I did :
 <dbdev1:dev3:/appl/oracle/product/9.2.0.1.0/dbs> export ORACLE_SID=gp9i
 <dbdev1:gp9i:/appl/oracle/product/9.2.0.1.0/dbs> sqlplus '/ as sysdba'

 SQL*Plus: Release 9.2.0.4.0 - Production on Fri Oct 24 15:29:50 2003

 Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

 Connected to an idle instance.

 SQL> startup
 ORACLE instance started.

 Total System Global Area  320309648 bytes
 Fixed Size                   731536 bytes
 Variable Size             285212672 bytes
 Database Buffers           33554432 bytes
 Redo Buffers                 811008 bytes
 Database mounted.
 Database opened.
 SQL> select name from v$database;

 NAME
 ---------
 GP9I

SQL> create or replace directory data_dir as '/tmp/';
 Directory created.

 SQL> create table external_table
      (EMPNO NUMBER(4) ,
      ENAME VARCHAR2(10),
      JOB VARCHAR2(9),
      MGR NUMBER(4),
      HIREDATE DATE,
      SAL NUMBER(7, 2),
      COMM NUMBER(7, 2),
      DEPTNO NUMBER(2)
    )
    ORGANIZATION EXTERNAL
    ( type oracle_loader
      default directory data_dir
      access parameters
      ( fields terminated by ',' )
      location ('emp.dat')
    );

 Table created.

 SQL> sho parameter os_authent_prefix

 NAME                                 TYPE        VALUE
 ------------------------------------ -----------
 ------------------------------
 os_authent_prefix                    string      ops$

 Now I want to change the parameter for os_authent_prefix and then create
 an ORACLE external user, so I do

 SQL> shutdown immediate;
 Database closed.
 Database dismounted.
 ORACLE instance shut down.

 At this moment, I see:

 <dbdev1:dev3:/appl/oracle/product/9.2.0.1.0/dbs> ls -l *gp9i*
 -rw-r--r--   1 oracle   dba         1024 Sep 12 10:40 initgp9i.ora
 -rwSr-----   1 oracle   dba         1536 Oct 24 15:33 orapwgp9i
 -rw-r-----   1 oracle   dba         2560 Jul 16 19:08 spfilegp9i.ora

 Then I edit  initgp9i.ora to add one more line below.

 <dbdev1:dev3:/appl/oracle/product/9.2.0.1.0/dbs> vi initgp9i.ora

 *.os_authent_prefix=""

 SQL> create spfile from pfile;

 File created.

 <dbdev1:dev3:/appl/oracle/product/9.2.0.1.0/dbs> ls -l *gp9i*
 -rw-r--r--   1 oracle   dba         1047 Oct 24 16:11 initgp9i.ora
 -rwSr-----   1 oracle   dba         1536 Oct 24 15:33 orapwgp9i
 -rw-r-----   1 oracle   dba         3584 Oct 24 16:12 spfilegp9i.ora

SQL> startup
 ORACLE instance started.
 ORA-24324: service handle not initialized
 ORA-24323: value not allowed
 ORA-03113: end-of-file on communication channel

SQL> shutdown abort
 ORA-24324: service handle not initialized
 ORA-01041: internal error. hostdef extension doesn't exist

 SQL> create pfile from spfile;
 create pfile from spfile
 *
 ERROR at line 1:
 ORA-03114: not connected to ORACLE

 SQL> conn / as sysdba
 ERROR:
 ORA-03113: end-of-file on communication channel

Now I run into big problem. I can't conn sys to start or shutdown database. The
db is generating gp9i_pmon_N.trc (800k size) every 3-4 minutes and core dump is
generating every 5 minutes.

The content for gp9i_pmon_N.trc is something like:

Fri Oct 24 16:38:47 2003
Errors in file /appl/oracle/admin/gp9i/bdump/gp9i_pmon_25647.trc:
ORA-00445: background process "QMN0" did not start after 120 seconds
Fri Oct 24 16:41:56 2003
Restarting dead background process QMN0

By read oracle trace file and see:

/appl/oracle/admin/gp9i/udump/gp9i_ora_6276.trc
Oracle9i Enterprise Edition Release 9.2.0.4.0 - 64bit Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.4.0 - Production
ORACLE_HOME = /appl/oracle/product/9.2.0.1.0
System name:    SunOS
Node name:      dbdev1
Release:        5.8
Version:        Generic_108528-13
Machine:        sun4u
Instance name: gp9i
Redo thread mounted by this instance: 0 <none>
Oracle process number: 0
6276

Exception signal: 11 (SIGSEGV), code: 1 (Address not mapped to object), addr:
0x64, PC: [0x1027b25b0, 00000001027B25B0]
*** 2003-10-24 17:39:25.526
ksedmp: internal or fatal error
ORA-07445: exception encountered: core dump [00000001027B25B0] [SIGSEGV]
[Address not mapped to object] [0x000000064] [] []
Current SQL information unavailable - no session.

Now I only can guess the another DBA created this instance from 9.2.0.1.0. But
she moved all other instances to 9.2.0.4.0 and this instance uses spfilegp9i.ora
in same $ORACLE_HOME/dbs with other instance and same environment variables for
9.2.0.4.0 software. So the instance first starup without problem. Once create
spfile from pfile,
the problem happened.

Please help how to stop this instance running. Now I could
not connect sys as sysdba to shutdown. So many trace and core dump files will
fill up file system soon.

As always, thanks for your help.

Pauline
Rating: 3
October 25, 2003
Reviewer: Pauline from NY,U.S.A.
Tom,
Thanks very much for the response.
Now if I can conn sys in sqlplus then I can do what you said . But now the
problem is I can't connect sys in sqlplus.
<dbdev1:dev3:/appl/oracle/admin/gp9i/bdump> export ORACLE_SID=gp9i
<dbdev1:gp9i:/appl/oracle/admin/gp9i/bdump> sqlplus /nolog

SQL*Plus: Release 9.2.0.4.0 - Production on Sat Oct 25 09:44:14 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

SQL> sho user
USER is ""
SQL> conn / as sysdba
ERROR:
ORA-01041: internal error. hostdef extension doesn't exist

This instance generate gp9i_qmn0_N.trc per 5 minutes with 800k and core dump per
5 minutes.

I have no way to stop this instance inside oracle. Can I kill all processes for
this instance on O/S level? We may recreate it later.

Please help me to stop it.

Thank you again.
Followup:
i see a mixture of 9204 and 9201 here -- whats going on with that?  are you
using the RIGHT software here?  I'm thinking "no" maybe.

verify your path, make sure your ORACLE_HOME is set to the 9204 code tree.  Make
sure your PATH references that as well.

Yes, you can kill it -- but you've done something wacky with your environments
here -- I believe you are mixing 9201 and 9204 code together with bad results.
Rating: 5
Tablespace for External Tables October 25, 2003
Reviewer: A reader from OH,USA
Tom:
I have seen that external tables are being created in system tablespace.Can we
specify a tablespace for external tables?

Thanks
Ajeet
Followup:
umm, they consume no space IN the database whatsoever (they are external)

They don't "have" a tablespace really.  The have no storage inside at all.

But no, you cannot -- but it is 1000% OK since they do not really consume any
storage, anywhere inside of the database
Rating: 5
Tom's insight is always helpful October 25, 2003
Reviewer: Pauline from NY,U.S.A.
Tom,
Thanks for your continuing response over the weekend. The another DBA created
this instance when install 9.2.0.1.0.
The $ORACLE_HOME is /appl/oracle/product/9.2.0.1.0/dbs.
Then used same $ORACLE_HOME to install 9.2.0.4.0. and upgraded other instances
from 8i to 9.2.0.4.0. except left this one with 9.2.0.1.0 software. So its
spfile, pfile all sitting in /appl/oracle/product/9.2.0.1.0/dbs which is really
pointing to 9.2.0.4.0 software. That is why it used mixture code from 9.2.0.1
and 9.2.0.4 and once we touched
spfile, we ran into trouble. Now I only can stop it by kill -9 for all processes
of this instance.

Thanks very much for your help.
Rating: 3
One BIG difference is missed October 26, 2003
Reviewer: Olesksandr Alesinskyy from Geramny
Ther IS one really big difference between SQL*Loader abd external tables - with
SQL*Loader files with data may be loaded to a DB directly from client but to be
used as exteranal tables they have to reside on the server (or on network share,
mounted on the server).
Rating: 5
Import Just Data November 04, 2003
Reviewer: Fred from irvine, ca
As merely a data loader, how does import stack up against external tables?  Or
is this comparing apples and oranges?
Followup:
apples to toaster ovens.

import can only process a dmp file created by export.

external tables can handle regular old fashioned flat files (but not an export
dmp file in 9i and before)
Rating: 3
External tables and EBCDIC December 18, 2003
Reviewer: David Piazza from San Mateo, CA
I have a file transferred from a db2 db on the mainframe.  The transfer was
selected as ASCII.  The data file is as follows:
212^ASTANDARD
I created an external table with:
drop table test_load;
CREATE TABLE test_load
 (ZIP3_CODE          VARCHAR2(3)
 ,FAC_ID             INTEGER
 ,BOX_TYPE_CODE      VARCHAR2(10))
  ORGANIZATION EXTERNAL
  (
    TYPE ORACLE_LOADER
    DEFAULT DIRECTORY test_etl
    ACCESS PARAMETERS
   ( records delimited by newline
     CHARACTERSET WE8EBCDIC500
     FIELDS LDRTRIM
     missing field values are null
 (ZIP3_CODE          CHAR(3)
 ,FAC_ID             CHAR(1)
 ,BOX_TYPE_CODE      CHAR(10)))
                          LOCATION ('TEST.DAT')) nologging;

The table created sucessfully, but when I select from it, I get:
SQL> select * from test_load;
select * from test_load
*
ERROR at line 1:
ORA-29913: error in executing ODCIEXTTABLEFETCH callout
ORA-30653: reject limit reached
ORA-06512: at "SYS.ORACLE_LOADER", line 14
ORA-06512: at line 1

The log file says:
error processing column FAC_ID in row 1 for datafile /data/TEST.DAT ORA-01722:
invalid number

What did I do wrong??
Followup:
the data isn't in ebcdic but your control file says it is?

also "^" doesn't seem to be a number?
December 19, 2003 -- Thanks for the question regarding "reclaiming space after delete of LOB data", version 8.1.6
You Asked
Hi Tom,

I have a very simple table:

create table foo (
num  integer,
bar  clob);

The initial size of the LOB segment (taken from the user_segments table) was
81,920 bytes.

I inserted three rows into this table, to get the bytes allocated to exceed the
original extent;  I ended up at 327,680 bytes.  I then deleted the three rows,
but the space occupied by the LOB segment did not change.

After some reading I came upon this command:

alter table foo modify lob (bar) (deallocate unused);

Which got me down to 286,720 bytes.  An improvement, but still a lot of wasted
space.

So the question is, how can I reclaim this unused space?

thanks,

janine
and we said...
Once allocated and used, space belongs to the segment it was allocated to.  The
space is in fact "reclaimed".  Subsequent inserts WILL reuse that space --
however that space forever belongs to that segment unless you

o drop it.
o truncate it.
Review & Followup

Rating: 4
Thanks, that was very helpful October 20, 2001
Reviewer: Janine Sisk from Mont Vernon, NH USA
Thanks Tom, that was exactly what I needed to know, and on a Saturday no less!
Rating: 4
freelists December 19, 2003
Reviewer: A reader
Hi

We dont have freelists in LOBs, how does Oracle "reclaim" free blocks if there
is no freelists?

AFIAK LOBs are inserted above high water mark always
Followup:
not always, it has to do with the lob versioning percents.  lobs manage their
own space and reuse it effectively (else lobs would only grow -- but they do
not).
December 19, 2003 -- Thanks for the question regarding "Statspack report", version 9.2.0
You Asked
I am new to DB administration world. I generated statspack report. I got a
couple of pages report. How to find out the problematic areas in this report?
Could you please give some tips to understand this report?
and we said...
you don't have anyone experienced to work with? or tutor you?  or any training
available to be taken?

the problem here is "a little knowledge is a dangerous thing", tuning with
statspack implies you have good broad knowledge of the database.  What I mean
is, if you need to be taught what is on the statspack report -- then alot of the
basics are missing from your knowledge. (eg: i was never taught to read a
statspack report, I was able to read the very first one I saw -- because of the
prior knowledge)

I do have a reading list put together:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6112959647233
everyone starts with the Concepts guide and moves on from there.

Sorry -- this is a non-answer to your question sort of, but until you have a
good broad understanding of the database itself, being able to read a statspack
report would be like me being able to read chinese poetry -- maybe I could read
it but without an understanding of the culture, the perspective, the time it was
written -- it would still be meaningless.
Review & Followup

Rating: 5
Thanks February 20, 2003
Reviewer: A reader
Thanks for your answer. I started learning DB concepts. We dont have a DBA right
now and I was asked to check the performence using statspack, that was the
reason for my question.

Is it the right link for mentioned concepts guide
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/toc.htm

Followup:
Yes -- that is it.  I use OTN myself though, white papers, discussion forums,
articles, samples, documentation...
Rating: 5
Thank You! February 20, 2003
Reviewer: A reader
Rating: 3
Your comment on statspack report March 19, 2003
Reviewer: Shailandra from PA
Hi Tom,

My statpack report looks like this. I am enclosing some of my observation from
the report. Let me know I am correct?

STATSPACK report for

DB Name         DB Id    Instance     Inst Num Release     Cluster Host
------------ ----------- ------------ -------- ----------- ------- ------------
OMRL           778651830 OMRL                1 9.2.0.3.0   NO      earth

            Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:       6 18-Mar-03 13:53:59       41      16.3
  End Snap:      16 18-Mar-03 15:36:39      228      30.2
   Elapsed:              102.67 (mins)

Cache Sizes (end)
~~~~~~~~~~~~~~~~~
               Buffer Cache:       208M      Std Block Size:         8K
           Shared Pool Size:       208M          Log Buffer:     1,024K

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:            138,814.54              2,348.50
              Logical reads:             23,494.89                397.49
              Block changes:                938.87                 15.88
             Physical reads:                  3.03                  0.05
            Physical writes:                 16.68                  0.28
                 User calls:              2,543.37                 43.03
                     Parses:              1,865.46                 31.56
                Hard parses:                  6.30                  0.11
                      Sorts:                 42.92                  0.73
                     Logons:                  1.06                  0.02
                   Executes:              4,626.91                 78.28
               Transactions:                 59.11

  % Blocks changed per Read:    4.00    Recursive Call %:    66.65
 Rollback per transaction %:    0.00       Rows per Sort:   164.73

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:   99.99
            Buffer  Hit   %:   99.99    In-memory Sort %:  100.00
            Library Hit   %:  100.08        Soft Parse %:   99.66
         Execute to Parse %:   59.68         Latch Hit %:   97.42
Parse CPU to Parse Elapsd %:   70.03     % Non-Parse CPU:   77.65

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   38.97   92.64
    % SQL with executions>1:   80.34   56.58
  % Memory for SQL w/exec>1:   79.24   71.13

Top 5 Timed Events
~~~~~~~~~~~~~~~~~~                                                     % Total
Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
CPU time                                                       23,174    80.50
latch free                                        866,415       3,789    13.16
log file sync                                     349,051       1,227     4.26
log file parallel write                           359,340         470     1.63
db file parallel write                              2,355          59      .20
          -------------------------------------------------------------
 Wait Events for DB: OMRL  Instance: OMRL  Snaps: 6 -16
                                                                   Avg
                                                     Total Wait   wait    Waits
Event                               Waits   Timeouts   Time (s)   (ms)     /txn
---------------------------- ------------ ---------- ---------- ------ --------
latch free                        866,415    170,093      3,789      4      2.4
log file sync                     349,051        153      1,227      4      1.0
log file parallel write           359,340    355,845        470      1      1.0
db file parallel write              2,355          0         59     25      0.0
SQL*Net break/reset to clien       40,270          0         26      1      0.1
wait list latch free                  678          0         11     17      0.0
buffer busy waits                   4,181          0          7      2      0.0
 Background Wait Events for DB: OMRL  Instance: OMRL  Snaps: 6 -16
                                                                   Avg
                                                     Total Wait   wait    Waits
Event                               Waits   Timeouts   Time (s)   (ms)     /txn
---------------------------- ------------ ---------- ---------- ------ --------
log file parallel write           359,347    355,852        470      1      1.0
db file parallel write              2,355          0         59     25      0.0
control file parallel write         2,433          0          6      3      0.0
LGWR wait for redo copy             5,530         47          3      1      0.0
latch free                            148         14          0      3      0.0
 SQL ordered by Gets for DB: OMRL  Instance: OMRL  Snaps: 6 -16
                                                    CPU      Elapsd
  Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
     39,912,633   13,315,755            3.0   27.6  2090.54   3640.27 3574766763
Module: dllhost.exe
SELECT sys_context(:b2,:b1) from sys.dual

     23,492,315        2,782        8,444.4   16.2   766.38    946.35 3283574751
Module: dllhost.exe
SELECT CLA.idn_csld AS "idn_csld"    , get_user_nam(SCP.idn_user
_profl_sc) AS "nam_sc"    , get_user_nam(NVL(SCP.idn_user_profl_
supvr_sc, SCP.idn_user_profl_sc)) AS "nam_sup_sc"  --  , get_eli
gbl_case_count(CLA.idn_csld) AS "Number of Inviduals"    , COUNT
(C.IDN_CASE) AS "cnt_indiv"    , get_user_nam(SCP.idn_user_profl

     14,840,125          364       40,769.6   10.3   252.00    309.80 3162160343
Module: dllhost.exe
SELECT  C.idn_cnsr              ,C.nam_last || ', ' || C.nam_fir
st AS nam_cnsr              ,CAS.idn_case              ,CC.dte_c
ntc        ,CC.idn_cmts_case        ,SUBSTR(CC.txt_cmts_case, 1,
 15) AS txt_cmts_case        ,DECODE(CC.idn_by_crtd, NULL, NULL,
 Get_User_Nam(CC.idn_by_crtd)) AS nam_by_crtd        ,F.idn_up_f
SQL ordered by Parse Calls for DB: OMRL  Instance: OMRL  Snaps: 6 -16
-> End Parse Calls Threshold:      1000

                           % Total
 Parse Calls  Executions   Parses  Hash Value
------------ ------------ -------- ----------
   4,809,210    2,404,964    41.85  864682358
Module: dllhost.exe
begin hcsis_app_pkg.usp_state(:V00001,:V00002,:V00003,:V00004,:V
00005); end;

     455,508      455,508     3.96 3665633299
Module: dllhost.exe
declare p varchar2(32767);                    begin  p := PKG_MA
STER_DOC_TYPE_SCTY.MASTER_DOC_ACCESS(:sn, :on);
   :v1 := substr(p,1,4000); :v2 := substr(p,4001,4000);
           :v3 := substr(p,8001,4000); :v4 := substr(p,12001,400
0);                    :v5 := substr(p,16001,4000); :v6 := subst

     455,466      455,468     3.96 1182061949
Module: dllhost.exe
declare p varchar2(32767);                    begin  p := PKG_IN
CDT_SCTY.INCDT_IDN_ACCESS(:sn, :on);                    :v1 := s
ubstr(p,1,4000); :v2 := substr(p,4001,4000);
:v3 := substr(p,8001,4000); :v4 := substr(p,12001,4000);
            :v5 := substr(p,16001,4000); :v6 := substr(p,20001,4

     415,026      415,024     3.61 2226163060
Module: dllhost.exe
declare p varchar2(32767);                    begin  p := PKG_IN
CDT_SCTY.CERTD_INVESR_CNSR_ACCESS(:sn, :on);
:v1 := substr(p,1,4000); :v2 := substr(p,4001,4000);
        :v3 := substr(p,8001,4000); :v4 := substr(p,12001,4000);
                    :v5 := substr(p,16001,4000); :v6 := substr(p

     393,777      393,774     3.43 3151763676
Module: dllhost.exe
declare p varchar2(32767);                    begin  p := PKG_GE
OG_COUNTY_SCTY.RES_COUNTY_ACCESS(:sn, :on);                    :
v1 := substr(p,1,4000); :v2 := substr(p,4001,4000);
       :v3 := substr(p,8001,4000); :v4 := substr(p,12001,4000);
                   :v5 := substr(p,16001,4000); :v6 := substr(p,

     390,087      390,089     3.39 3073412805
Module: dllhost.exe
declare p varchar2(32767);                    begin  p := PKG_LO
GCL_DELETE_SCTY.LOGCL_DELETE_ACCESS(:sn, :on);
  :v1 := substr(p,1,4000); :v2 := substr(p,4001,4000);
          :v3 := substr(p,8001,4000); :v4 := substr(p,12001,4000
);                    :v5 := substr(p,16001,4000); :v6 := substr
Instance Activity Stats for DB: OMRL  Instance: OMRL  Snaps: 6 -16

Statistic                                      Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------
CPU used by this session                   2,317,384          376.2          6.4
CPU used when call started                 2,317,379          376.2          6.4
CR blocks created                             36,901            6.0          0.1
DBWR checkpoint buffers written              101,603           16.5          0.3
DBWR checkpoints                                  36            0.0          0.0
DBWR transaction table writes                  1,046            0.2          0.0
DBWR undo block writes                        44,953            7.3          0.1
SQL*Net roundtrips to/from client         18,550,671        3,011.5         51.0
active txn count during cleanout              38,735            6.3          0.1
background checkpoints completed                  36            0.0          0.0
background checkpoints started                    36            0.0          0.0
background timeouts                            7,234            1.2          0.0
branch node splits                                 7            0.0          0.0
buffer is not pinned count                78,660,488       12,769.6        216.0
buffer is pinned count                    94,826,641       15,393.9        260.4
bytes received via SQL*Net from c      1,109,332,214      180,086.4      3,046.8
bytes sent via SQL*Net to client         665,131,799      107,975.9      1,826.8
calls to get snapshot scn: kcmgss         49,751,137        8,076.5        136.6
calls to kcmgas                              432,277           70.2          1.2
calls to kcmgcs                               15,037            2.4          0.0
change write time                             15,859            2.6          0.0
cleanout - number of ktugct calls             42,989            7.0          0.1

Latch Activity for DB: OMRL  Instance: OMRL  Snaps: 6 -16
->"Get Requests", "Pct Get Miss" and "Avg Slps/Miss" are statistics for
  willing-to-wait latch get requests
->"NoWait Requests", "Pct NoWait Miss" are for no-wait latch get requests
->"Pct Misses" for both should be very close to 0.0

                                           Pct    Avg   Wait                 Pct
                              Get          Get   Slps   Time       NoWait NoWait
Latch                       Requests      Miss  /Miss    (s)     Requests   Miss
------------------------ -------------- ------ ------ ------ ------------ ------
Consistent RBA                  359,468    0.0    0.4      0            0
FAL subheap alocation                 0                    0            2    0.0
FIB s.o chain latch               1,834    0.0             0            0
FOB s.o list latch                8,565    0.0             0            0
SQL memory manager latch             10    0.0             0        2,000    0.0
SQL memory manager worka        699,891    0.0    0.3      0            0
Token Manager                         0                    0            2    0.0
active checkpoint queue           4,503    0.0             0            2    0.0
alert log latch                       0                    0            2    0.0
archive control                     208    0.0             0            0
cache buffer handles            297,696    0.0    0.1      0            0
cache buffers chains        258,793,406    1.8    0.0      0       86,155    0.1
cache buffers lru chain         119,501    0.0    0.0      0    1,642,360    0.0
channel handle pool latc         12,923    0.0             0            0
channel operations paren         23,667    0.0             0            0
checkpoint queue latch          938,445    0.0    0.0      0      101,662    0.0
child cursor hash table         402,340    0.0    0.0      0            0
commit callback allocati              2    0.0             0            0
dml lock allocation           1,305,704    0.2    0.1      0            0
dummy allocation                 12,923    0.0             0            0
enqueue hash chains           2,174,052    0.1    0.1      0            0
enqueues                      3,050,271    0.1    0.2      2            0
event group latch                 6,555    0.0             0            0
event range base latch                2    0.0             0            0
global ctx hash table la      3,584,756    0.0             0            0
global tx hash mapping           19,656    0.0             0            0
hash table column usage             320    0.0             0       52,042    0.0
hash table modification               2    0.0             0            2    0.0
i/o slave adaptor                     0                    0            2    0.0
job_queue_processes para            100    0.0             0            2    0.0
ksfv messages                         0                    0            4    0.0
ktm global data                      94    0.0             0            0
lgwr LWN SCN                    360,450    0.1    0.0      0            0
library cache               179,373,373    3.5    0.1   1827       23,432   21.0
library cache load lock           3,884    0.0             0            0
library cache pin           157,862,444    1.0    0.0    263            0
library cache pin alloca      3,627,054    0.0    0.1      0            0
list of block allocation          9,783    0.0             0            0
loader state object free            432    0.0             0            0
messages                      1,874,347    0.0    0.1      0            0
mostly latch-free SCN           369,933    1.2    0.0      0            0
multiblock read objects          10,868    0.0             0            2    0.0
name-service namespace o              0                    0            2    0.0
ncodef allocation latch             115    0.0             0            0
non-pers queues instance              0                    0            2    0.0
object stats modificatio          1,039    0.0             0            2    0.0
post/wait queue                 525,079    0.0    0.0      0      349,237    0.0
process allocation                6,555    0.0             0        6,555    0.0
process group creation           12,923    0.0             0            0
redo allocation               3,718,789    0.3    0.0      1            0
 Latch Activity for DB: OMRL  Instance: OMRL  Snaps: 6 -16
->"Get Requests", "Pct Get Miss" and "Avg Slps/Miss" are statistics for
  willing-to-wait latch get requests
->"NoWait Requests", "Pct NoWait Miss" are for no-wait latch get requests
->"Pct Misses" for both should be very close to 0.0

                                           Pct    Avg   Wait                 Pct
                              Get          Get   Slps   Time       NoWait NoWait
Latch                       Requests      Miss  /Miss    (s)     Requests   Miss
------------------------ -------------- ------ ------ ------ ------------ ------
redo copy                             0                    0    3,004,502    0.3
redo writing                  1,087,207    0.0    0.0      0            0
row cache enqueue latch      49,631,027    0.7    0.0      2            0
row cache objects            55,351,596    1.5    0.0     62        2,197    0.0
sequence cache                  318,153    0.0    0.0      0            0
session allocation           35,285,315    3.3    0.0    190            0
session idle bit             34,276,738    0.1    0.2      0            0
session switching                   115    0.0             0            0
session timer                     2,148    0.0             0            0
shared pool                  94,437,267    8.8    0.0    160            0
simulator hash latch          4,431,315    0.0    0.1      0            0
simulator lru latch               4,840    0.0    0.0      0       34,318    0.3

My comments:

1. Execute to parse ratio is less.
2. Latch free are high.
3. In Sql ordered by Parse call I see more Parses then executions or 1 to 1
ratio. It could be due to library contention.
4. In Latch Activity, I see 21% waits for library cache for nowait calls.

Every thing else looks ok. Should I increase my shared pool size.

Thanks
Followup:
suggestion -- create a table:

create table my_dual( dummy varchar2(1) primary key ) organization index;
insert into my_dual values ( 'X' );

and change

SELECT sys_context(:b2,:b1) from sys.dual

to

SELECT sys_context(:b2,:b1) from my_dual

Also, looks like this was a "cold system" at the beginning (just after a
startup)?  if so -- you only want to do a 15 minute snapshot after it is all
warmed up.
Rating: 4
Continue March 19, 2003
Reviewer: Shailandra from PA
Tom,

Thanks for your valuable suggestion.

Is it normal to have high parse to execution ratio. In the report I see very low
hard parse. So most of the parsing is soft parse. But high soft parse to execute
ration is still mystry to me. In some of my instances I see execute to parse
ratio of 1%, but that database is new and fresh. But the above database was used
to run load test and this database is reverted back to baseline after each test.
Followup:
depends on the environment.  with web based apps, parse = execute could be very
normal.  with a stated connection, I would expect parse < execute.
Rating: 5
March 19, 2003
Reviewer: Mike from TX
"
suggestion -- create a table:

create table my_dual( dummy varchar2(1) primary key ) organization index;
insert into my_dual values ( 'X' );

and change

SELECT sys_context(:b2,:b1) from sys.dual

to

SELECT sys_context(:b2,:b1) from my_dual

"
I understood you do not want to use the SYS object -- DUAL, then what's the
different in functionalities of sys.dual and my_dual?
Followup:
ops$tkyte@ORA920> create table my_dual ( dummy varchar2(1) primary key )
organization index;

Table created.

ops$tkyte@ORA920> insert into my_dual values ( 'X' );

1 row created.

ops$tkyte@ORA920> @trace
ops$tkyte@ORA920> alter session set events '10046 trace name context forever,
level 12';

Session altered.

ops$tkyte@ORA920> declare
  2          l_namespace varchar2(25) default 'userenv';
  3          l_variable  varchar2(25) default 'sessionid';
  4          l_value     varchar2(25);
  5  begin
  6          for i in 1 .. 1000
  7          loop
  8                  select sys_context( l_namespace, l_variable )
  9                    into l_value
 10                    from dual;
 11                  select sys_context( l_namespace, l_variable )
 12                    into l_value
 13                    from my_dual;
 14          end loop;
 15  end;
 16  /

PL/SQL procedure successfully completed.

SELECT sys_context( :b2, :b1 ) from dual

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute   1000      0.26       0.27          0          0          0           0
Fetch     1000      0.10       0.14          0       3000          0        1000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2001      0.37       0.41          0       3000          0        1000

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 309     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
   1000  TABLE ACCESS FULL DUAL

********************************************************************************

SELECT sys_context( :b2, :b1 ) from my_dual

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute   1000      0.28       0.28          0          0          0           0
Fetch     1000      0.06       0.07          0       1000          0        1000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2001      0.34       0.36          0       1000          0        1000

check out the reduction in LIOs between the two.  I think we can take:

  Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
     39,912,633   13,315,755            3.0   27.6  2090.54   3640.27 3574766763
Module: dllhost.exe
SELECT sys_context(:b2,:b1) from sys.dual

and make it become:

  Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
     13,304,211  ......

with that minor, tiny change.
Rating: 4
why cannot I repprodue the result March 19, 2003
Reviewer: Winston from Canada
I did the following on 9iR2/Windows attemtping to get the same result but I
couldn't.

1. created table my_dual
2 turned on the trace
3. ran pl/sql
4 took snap(10),
5, ran pl/sql
6, took snap(10)

Here was the result

*******************************************************************************

SELECT sys_context( :b2, :b1 )
                      from dual

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute   2000      0.15       0.15          0          0          0           0
Fetch     2000      0.00       0.04          0       6000          0        2000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     4002      0.15       0.19          0       6000          0        2000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 59     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
   1000  TABLE ACCESS FULL DUAL

********************************************************************************

SELECT sys_context( :b2, :b1 )
                     from my_dual

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute   2000      0.18       0.16          0          0          0           0
Fetch     2000      0.01       0.06          0       6000          0        2000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     4002      0.20       0.23          0       6000          0        2000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 59     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
   1000  INDEX FAST FULL SCAN SYS_IOT_TOP_30486 (object id 30487)

********************************************************************************

                                                     CPU      Elapsd
  Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------

          3,000        1,000            3.0   23.5     0.11      0.10 1003994554
Module: SQL*Plus
SELECT sys_context( :b2, :b1 )                      from my_dual

          3,000        1,000            3.0   23.5     0.08      0.10 3806032834
Module: SQL*Plus
SELECT sys_context( :b2, :b1 )                       from dual
Followup:
Hmm, interesting -- I get a

Rows     Row Source Operation
-------  ---------------------------------------------------
   1000  INDEX FULL SCAN SYS_IOT_TOP_45419 (object id 45420)

which is what I wanted - the INDEX FAST FULL scan you have means it is treating
it just like a table (just like dual)....

Can you

a) analyze the table compute

and if that doesn't do it

b) add "where dummy = 'X'"

what non-default parameters do you have set?

whats the exact version - for me, 9201 and 9202 did the index full scan
naturally on Linux
Rating: 5
It worked after analyzing March 19, 2003
Reviewer: Winston from Canada
The result changed to :
********************************************************************************

SELECT sys_context( :b2, :b1 )
                      from dual

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.05          0          0          0           0
Execute   1000      0.10       0.38          0          0          0           0
Fetch     1000      0.01       0.02          0       3000          0        1000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2001      0.12       0.46          0       3000          0        1000

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 59     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
   1000  TABLE ACCESS FULL DUAL

********************************************************************************

SELECT sys_context( :b2, :b1 )
                     from my_dual

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute   1000      0.12       0.39          0          0          0           0
Fetch     1000      0.00       0.03          0       1000          0        1000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2001      0.12       0.43          0       1000          0        1000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 59     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
   1000  INDEX FULL SCAN SYS_IOT_TOP_30486 (object id 30487)

********************************************************************************

                                                     CPU      Elapsd
  Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------

          3,002        1,000            3.0   24.1     0.13      0.47 3806032834
Module: SQL*Plus
SELECT sys_context( :b2, :b1 )                       from dual
          1,000        1,000            1.0    8.0     0.13      0.43 1003994554
Module: SQL*Plus
SELECT sys_context( :b2, :b1 )                      from my_dual
Rating: 4
what's the outcome if sys.dual got analyzed? March 19, 2003
Reviewer: A reader
Followup:
well, it depends.

As best, not much, there isn't an index on dual, so it'll have to full scan and
have the same exact LIO's

As worst, you are still using the RBO, you've analyzed a single table in your
system (dual) and any query that uses dual would use the CBO -- making up stats
for all other objects and your system goes down the tubes...
Rating: 4
What could be the reason for parse calls double of executions March 26, 2003
Reviewer: Shailandra from PA
Hi Tom,

In the statspack report I see most of the package procedure executions showing
parse calls double of no of executions. What could be the reason for it? I see
this behavior for all package proceure/functions executions only.

 Parse Calls  Executions   Parses  Hash Value
------------ ------------ -------- ----------
      16,952       16,952    16.27 3767315403
Module: dllhost.exe
BEGIN SYS.DBMS_DESCRIBE.DESCRIBE_PROCEDURE(:object_name,:res1,:r
es2,:overload,:position,:level,:argument,:datatype,:default,:in_
out,:length,:precision,:scale,:radix,:spare); END;

       3,814        1,907     3.66 1460202175
Module: dllhost.exe
begin PKG_MEMBER.memGetCase(I_IDN_MEMBER=>:V00001, I_CDE_REL_MEM
BER=>:V00002, REF_CURSOR=>:R000C000); end;

       3,130        1,565     3.00 1004854589
Module: dllhost.exe
begin PKG_MEMBER.viewPayeePayment(I_IDN_MEMBER=>:V00001, REF_CUR
SOR=>:R000C000); end;

       2,292        1,146     2.20 1602445198
Module: dllhost.exe
begin PKG_MEMBER.memlogin(I_IDN_USER_LOGIN=>:V00001, I_ADR_IP_LO
GIN_LAST=>:V00002, I_TXT_PSWD=>:V00003, I_CDE_REL_MEMBER=>:V0000
4, REF_CURSOR=>:R000C000); end;

       2,288        1,144     2.20 1775764703
Module: dllhost.exe
begin PKG_MEMBER.memalert(I_IDN_MEMBER=>:V00001, REF_CURSOR=>:R0
00C000); end;

       2,260        1,130     2.17 1389231390
Module: dllhost.exe
begin PKG_PACSES_COMMON.logout(I_IDN_MEMBER_EMPLR=>:V00001, I_CD
E_MEMBER_ROLE=>:V00002, REF_CURSOR=>:R000C000); end;

       2,250        1,125     2.16 1516293048
Module: dllhost.exe
begin PKG_PACSES_COMMON.AccountInfo(I_IDN_MEMBER_EMPLR=>:V00001,
 I_CDE_MEMBER_ROLE=>:V00002, REF_CURSOR=>:R000C000); end;

       2,236        1,118     2.15 1766718981
Module: dllhost.exe
begin PKG_MEMBER.memgetOpponent(I_IDN_MEMBER=>:V00001, REF_CURSO
R=>:R000C000); end;

       2,236        1,118     2.15 4126796864
Module: dllhost.exe
begin PKG_MEMBER.viewScheduleActivity(I_IDN_MEMBER=>:V00001, REF
_CURSOR=>:R000C000); end;

       2,216        1,108     2.13 2207805897

Thanks
Followup:
your application is apparently parsing the statement two times.

I see this mostly with "generic wrapper layers" where someone got the brainy
idea that writing SQL code is too hard for everyone so they'll protect them from
the horrid complexity.

So, they write or buy a generic wrapper layer that does everything dynamically.
This layer generally does a "parse" in order to "describe" what the heck is
being called -- figure out what is going on.  This wrapper then generally
ignores the fact that they've parsed the statement and parses it all over again
in order to actually execute it.

You end up with parses EXCEEDING executes -- even worse then parse=execute.

It is an error in the code.
Rating: 5
RE: parse calls double... March 26, 2003
Reviewer: Mark A. Williams from Indianapolis, IN USA
Are you using ADO/OLEDB?

If so, search MetaLink for bug #1727475 (which is supposed to be fixed in the
Oracle 9.2 provider)...

Here is a short excerpt from Oracle Support in one of the posts:

"Apparently what is happening is the provider (ADO/OLEDB)calls OCIStmtExecute
twice. Once with DESCRIBE_ONLY to do an explicit describe and again to execute.
The parse stat count is being incremented twice, once for each execute call. The
parse happens only during the first call which is a hard parse. On the second
call, the count is being incremented even though a hard parse is not made. As a
result, the parse count shows 2 even though it is actually being parsed only
once, the first time."

Don't know if that is helpful for you, but it may be a possibility.

- Mark
Rating: 4
A small doubt! March 27, 2003
Reviewer: Anand H S from New Delhi, India
Hi Tom,
I have a small query from my statspack report!
--
 Rollback Segment Storage for DB: DB01  Instance: anand  Snaps: 14 -23
->Optimal Size should be larger than Avg Active

RBS No    Segment Size      Avg Active    Optimal Size    Maximum Size
------ --------------- --------------- --------------- ---------------
     0         425,984               0                         425,984
     1       2,224,128         264,838                       2,224,128
     2       1,765,376           6,553                       1,765,376
     3         323,584         108,417                         978,944
     4       2,224,128         342,294                       3,862,528
     5       2,224,128       1,313,158                       6,418,432
     6       1,175,552         351,333                       2,224,128
     7         323,584         116,896                       2,682,880
     8       1,175,552         325,653                       2,224,128
     9         454,656         135,844                         978,944
    10       1,175,552         508,344                       3,141,632
          -------------------------------------------------------------

it says "Optimal Size should be larger than Avg Active"
BUT THE OPTIMAL SIZE IS BLANK!
Is this due to automatic undo management??

Please Advise.

Regards,
Anand H S
Followup:
means that you are wisely NOT using optimal.
Rating: 5
Double Parsing March 28, 2003
Reviewer: Steven Cooper from England
Tom,

RE:
"your application is apparently parsing the statement two times.

I see this mostly with "generic wrapper layers" where someone got the brainy
idea that writing SQL code is too hard for everyone so they'll protect them from

the horrid complexity.

So, they write or buy a generic wrapper layer that does everything dynamically.

This layer generally does a "parse" in order to "describe" what the heck is
being called -- figure out what is going on.  This wrapper then generally
ignores the fact that they've parsed the statement and parses it all over again
in order to actually execute it.

You end up with parses EXCEEDING executes -- even worse then parse=execute.

It is an error in the code. "

------------------
Would this apply to Report Builder 6.0.5.34.1 ?
I think it does e.g.

1) Conneted to Oracle
2) Opened Report
(report as "after param form" and "after report" triggers to enable/ disable
tracing)
3) Compile report
4) Run Report
5) Disconnect

TKPROF says: Parsed twice
I initially thought that the compile would be the extra parse, but tracing won't
get enabled until I run, which is after the report as already been  compiled. Is
this a case of the wrapper (Report Builder) parsing once to see what its got and
the second to actually run it? ....If so to coin your phrase "It is an error in
the code. " ;)

SELECT         uio.Fes_Uins_Instance_Code             UIO_Unit_Ins,
        uio.Calocc_Occurrence_Code            UIO_Occ,
        uio.Uio_Id                UIO_Link,
        uio.Offering_Organisation            UIO_Division,
        uio.Owning_Organisation            UIO_Faculty,
        ui.Fes_Long_Description            UIO_Description

FROM        Unit_Instances        ui,
        Unit_Instance_Occurrences    uio
WHERE         NVL(uio.Offering_Organisation    ,'%')        LIKE
NVL(:U_DIVISION,'%')
AND        uio.Owning_Organisation            =          :U_CENTRE
AND        ui.Fes_Unit_Instance_Code            =
uio.Fes_Uins_Instance_Code
AND        ui.Unit_Category                =        'C'
AND        uio.Fes_End_Date                >=
TO_DATE(:U_COURSE_START,'DD-MM-YYYY')
AND        uio.Fes_Start_Date                <=
TO_DATE(:U_COURSE_END,'DD-MM-YYYY')
AND        uio.Fes_Moa_Code                BETWEEN    :U_MOA_START
                            AND        :U_MOA_END
ORDER BY    uio.Fes_Uins_Instance_Code,
        uio.Calocc_Occurrence_Code

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        5      0.11       0.11        239       1776          0         347
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        8      0.12       0.12        239       1776          0         347

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 619
Followup:
I would say it is an error in the code -- yes.  Our own tools developers are not
immune from "product issues"
Rating: 5
Tkprof Report March 28, 2003
Reviewer: Steven Cooper from England
Tom,

Whilst running the above example, I couldn't get tkprof to show me the Explain
plan for my SQL, is this somthing to do with Report Builder also?  I definately
disconnected and closed report builder before I generated the tkprof report, and
explain plans existed for some of the recursive sql.

---------

TKPROF: Release 8.1.7.0.0 - Production on Fri Mar 28 11:37:53 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Trace file: ora_22975.trc
Sort options: default

********************************************************************************
count    = number of times OCI procedure was executed
cpu      = cpu time in seconds executing
elapsed  = elapsed time in seconds executing
disk     = number of physical reads of buffers from disk
query    = number of buffers gotten for consistent read
current  = number of buffers gotten in current mode (usually for update)
rows     = number of rows processed by the fetch or execute call
********************************************************************************

alter session set sql_trace true

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      1      0.00       0.01          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        1      0.00       0.01          0          0          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 619     (recursive depth: 1)
********************************************************************************

declare ret binary_integer; begin ret := PBSDE.DEBUG_LOOP; end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          0          0           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 619     (recursive depth: 1)
********************************************************************************

SELECT         uio.Fes_Uins_Instance_Code             UIO_Unit_Ins,
        uio.Calocc_Occurrence_Code            UIO_Occ,
        uio.Uio_Id                UIO_Link,
        uio.Offering_Organisation            UIO_Division,
        uio.Owning_Organisation            UIO_Faculty,
        ui.Fes_Long_Description            UIO_Description

FROM        Unit_Instances        ui,
        Unit_Instance_Occurrences    uio
WHERE         NVL(uio.Offering_Organisation    ,'%')        LIKE
NVL(:U_DIVISION,'%')
AND        uio.Owning_Organisation            =          :U_CENTRE
AND        ui.Fes_Unit_Instance_Code            =
uio.Fes_Uins_Instance_Code
AND        ui.Unit_Category                =        'C'
AND        uio.Fes_End_Date                >=
TO_DATE(:U_COURSE_START,'DD-MM-YYYY')
AND        uio.Fes_Start_Date                <=
TO_DATE(:U_COURSE_END,'DD-MM-YYYY')
AND        uio.Fes_Moa_Code                BETWEEN    :U_MOA_START
                            AND        :U_MOA_END
ORDER BY    uio.Fes_Uins_Instance_Code,
        uio.Calocc_Occurrence_Code

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        5      0.11       0.11        239       1776          0         347
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        8      0.12       0.12        239       1776          0         347

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 619
********************************************************************************

SELECT DISTINCT    ru.Fes_Per_Person_Code
P_Age_Person_Code,
        ru.Uio_Id                                    P_Uio_Link,
        TO_NUMBER(DECODE(SIGN(TO_NUMBER(ru.Fes_User_4)-19),-1,NULL,1) )
P_Age_19,
        DECODE(TO_NUMBER(ru.Fes_User_4),16,1,17,1,18,1,NULL)
P_Age_16_18,
        DECODE( ru.Fes_Progress_Code, 'F', 1, 'C', 1, 0 )
Finished_Count,
--Note: The october census date is at position 4 in the string, not first.
        DECODE(SUBSTR(ru.Fes_User_6,4,1),'1',1,0 )                    Oct_Count,
        DECODE(SUBSTR(ru.Fes_User_6,1,1),'1',1,0 )                    Nov_Count,
        DECODE(SUBSTR(ru.Fes_User_6,2,1),'1',1,0)                    Feb_Count,
        DECODE(SUBSTR(ru.Fes_User_6,3,1),'1',1,0 )                    May_Count,
        DECODE(SUBSTR(RPAD(ru.Fes_User_6,4,'0'),1,4),'0000', 0,1)
P_Retention
FROM        Registration_Units     ru
  WHERE ( ru.Fes_Progress_Code != 'N' ) AND ( :UIO_Link = ru.Uio_Id)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.01          0          0          0           0
Execute    347      0.09       0.05          0          0          0           0
Fetch      354      0.88       1.76       1825       3653          0        2861
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      703      0.97       1.82       1825       3653          0        2861

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 619
********************************************************************************

SELECT        cla.Ach                      Outcome_Count,
        cla.Uio_Id                Outcome_Uio_Link
FROM        College.College_Learner_Aims        cla
  WHERE ( cla.Learning_Aim <> : U_OMIT_QUAL_AIM ) AND ( :UIO_Link = cla.Uio_Id)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute    347      0.05       0.12          0          0          0           0
Fetch      347      0.24       0.55        500       2661          0        2243
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      696      0.29       0.67        500       2661          0        2243

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 619
********************************************************************************

select i.obj#,i.ts#,i.file#,i.block#,i.intcols,i.type#,i.flags, i.property,
  i.pctfree$,i.initrans,i.maxtrans,i.blevel,i.leafcnt,i.distkey, i.lblkkey,
  i.dblkkey,i.clufac,i.cols,i.analyzetime,i.samplesize,i.dataobj#,
  nvl(i.degree,1),nvl(i.instances,1),i.rowcnt,mod(i.pctthres$,256),
  i.indmethod#,i.trunccnt,nvl(c.unicols,0),nvl(c.deferrable#+c.valid#,0),
  nvl(i.spare1,i.intcols),i.spare4,spare2,decode(i.pctthres$,null,null,
  mod(trunc(i.pctthres$/256),256))
from
 ind$ i, (select enabled, min(cols) unicols, min(to_number(bitand(defer,1)))
  deferrable#, min(to_number(bitand(defer,4))) valid# from cdef$ where obj#=
  :1 and enabled > 1 group by enabled) c where i.obj#=c.enabled(+) and i.bo#=
  :1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      3      0.00       0.00          0          0          0           0
Fetch        7      0.00       0.00          8         18          0           4
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       12      0.00       0.00          8         18          0           4

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 2)
********************************************************************************

select pos#,intcol#,col#,spare1
from
 icol$ where obj#=:1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      4      0.00       0.00          0          0          0           0
Fetch       15      0.00       0.01          2         30          0          11
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       21      0.00       0.01          2         30          0          11

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 2)
********************************************************************************

select name,intcol#,segcol#,type#,length,nvl(precision#,0),decode(type#,2,
  nvl(scale,-127/*MAXSB1MINAL*/),178,scale,179,scale,180,scale,181,scale,182,
  scale,183,scale,231,scale,0),null$,fixedstorage,nvl(deflength,0),default$,
  rowid,col#,property, charsetid,charsetform,spare1,spare2
from
 col$ where obj#=:1 order by intcol#

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      4      0.00       0.00          0          0          0           0
Fetch       32      0.01       0.01          2         12          0          28
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       38      0.01       0.01          2         12          0          28

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 2)
********************************************************************************

select /*+ index(idl_sb4$ i_idl_sb41) +*/ max(version)
from
 idl_sb4$ where obj#=:1 and version<=:2 and   (part=0 or part=2) and piece#=0

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        5      0.02       0.02          0          0          0           0
Execute      5      0.01       0.01          0          0          0           0
Fetch        5      0.01       0.00          4         20          0           5
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       15      0.04       0.03          4         20          0           5

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
      1   INLIST ITERATOR
      1    INDEX RANGE SCAN (object id 113)

********************************************************************************

select /*+ index(idl_sb4$ i_idl_sb41) +*/ max(version)
from
 idl_sb4$ where obj#=:1 and version<=:2 and part=1 and piece#=0

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.01          0          0          0           0
Fetch        1      0.00       0.00          0          2          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.01          0          2          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
      1   INDEX RANGE SCAN (object id 113)

********************************************************************************

select i.obj#
from
 ind$ i, obj$ o    where i.obj# = o.obj# and i.bo# = :1 and o.name = :2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.01       0.01          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          1         12          0           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        6      0.01       0.01          1         12          0           2

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 2)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  NESTED LOOPS
      1   TABLE ACCESS CLUSTER IND$
      1    INDEX UNIQUE SCAN (object id 3)
      1   TABLE ACCESS BY INDEX ROWID OBJ$
      1    INDEX UNIQUE SCAN (object id 33)

********************************************************************************

select text
from
 view$ where rowid=:1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          1          2          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.01       0.00          1          2          0           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY USER ROWID VIEW$

********************************************************************************

select col#, grantee#, privilege#,max(nvl(option$,0))
from
 objauth$ where obj#=:1 and col# is not null group by privilege#, col#,
  grantee# order by col#, grantee#

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          2          2          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          2          2          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 1)
********************************************************************************

select grantee#,privilege#,nvl(col#,0),max(nvl(option$,0))
from
 objauth$ where obj#=:1 group by grantee#,privilege#,nvl(col#,0) order by
  grantee#

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          1          3          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.01          1          3          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 1)
********************************************************************************

select parent_owner,parent_name,parent_link_name,parent_type,parent_timestamp,
  property
from
 ora_kglr7_dependencies where owner=:1 and name=:2 and type=:3 and obj#=:4
  order by order_number

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        3      0.04       0.04          8         48          0           0
Execute      3      0.02       0.03          0          0          0           0
Fetch        7      0.24       0.80         29       7777          0          35
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       13      0.30       0.87         37       7825          0          35

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 619

Rows     Row Source Operation
-------  ---------------------------------------------------
     34  SORT ORDER BY
     34   VIEW ORA_KGLR7_DEPENDENCIES
     34    SORT UNIQUE
     34     UNION-ALL
     34      FILTER
     35       NESTED LOOPS
     35        NESTED LOOPS
     35         NESTED LOOPS
      2          NESTED LOOPS
      2           TABLE ACCESS BY INDEX ROWID USER$
      2            INDEX UNIQUE SCAN (object id 41)
      2           TABLE ACCESS BY INDEX ROWID OBJ$
      2            INDEX UNIQUE SCAN (object id 33)
     35          TABLE ACCESS BY INDEX ROWID DEPENDENCY$
     35           INDEX RANGE SCAN (object id 116)
     68         TABLE ACCESS BY INDEX ROWID OBJ$
     68          INDEX UNIQUE SCAN (object id 33)
     68        TABLE ACCESS CLUSTER USER$
     68         INDEX UNIQUE SCAN (object id 11)
      0      FILTER
      1       NESTED LOOPS
    642        NESTED LOOPS
      2         NESTED LOOPS
      2          TABLE ACCESS BY INDEX ROWID USER$
      2           INDEX UNIQUE SCAN (object id 41)
      2          TABLE ACCESS BY INDEX ROWID OBJ$
      2           INDEX UNIQUE SCAN (object id 33)
    642         VIEW GV$FIXED_TABLE
    641          UNION-ALL
    258           FIXED TABLE FULL X$KQFTA
    370           FIXED TABLE FULL X$KQFVI
     13           FIXED TABLE FULL X$KQFDT
    641        TABLE ACCESS BY INDEX ROWID DEPENDENCY$
   1349         INDEX RANGE SCAN (object id 117)
      0      FILTER
      1       NESTED LOOPS
     14        NESTED LOOPS
      2         NESTED LOOPS
      2          TABLE ACCESS BY INDEX ROWID USER$
      2           INDEX UNIQUE SCAN (object id 41)
      2          TABLE ACCESS BY INDEX ROWID OBJ$
      2           INDEX UNIQUE SCAN (object id 33)
     14         FIXED TABLE FULL X$KQFP
     13        TABLE ACCESS BY INDEX ROWID DEPENDENCY$
     13         INDEX RANGE SCAN (object id 117)
      0       FILTER
      0        NESTED LOOPS
      0         FIXED TABLE FULL X$KZSRO
      0         INDEX UNIQUE SCAN (object id 102)
      0       NESTED LOOPS
      0        FIXED TABLE FULL X$KZSRO
      0        TABLE ACCESS BY INDEX ROWID OBJAUTH$
      0         INDEX RANGE SCAN (object id 101)
      0       FILTER
      0        NESTED LOOPS
      0         FIXED TABLE FULL X$KZSRO
      0         INDEX UNIQUE SCAN (object id 102)
      0       NESTED LOOPS
      0        FIXED TABLE FULL X$KZSRO
      0        TABLE ACCESS BY INDEX ROWID OBJAUTH$
      0         INDEX RANGE SCAN (object id 101)
      0       FILTER
      0        NESTED LOOPS
      0         FIXED TABLE FULL X$KZSRO
      0         INDEX UNIQUE SCAN (object id 102)
      0       NESTED LOOPS
      0        FIXED TABLE FULL X$KZSRO
      0        TABLE ACCESS BY INDEX ROWID OBJAUTH$
      0         INDEX RANGE SCAN (object id 101)

********************************************************************************

select o.owner#,o.name,o.namespace,o.remoteowner,o.linkname,o.subname,
  o.dataobj#,o.flags
from
 obj$ o where o.obj#=:1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          3          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.01       0.01          0          3          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 1)

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        9      0.05       0.06          8         48          0           0
Execute    698      0.16       0.20          0          0          0           0
Fetch      713      1.47       3.22       2593      15867          0        5486
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1420      1.68       3.48       2601      15915          0        5486

Misses in library cache during parse: 1

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       19      0.06       0.05          0          0          0           0
Execute     25      0.01       0.03          0          0          0           1
Fetch       67      0.02       0.02         21        104          0          54
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      111      0.09       0.10         21        104          0          55

Misses in library cache during parse: 6

   11  user  SQL statements in session.
   18  internal SQL statements in session.
   29  SQL statements in session.
********************************************************************************
Trace file: ora_22975.trc
Trace file compatibility: 8.00.04
Sort options: default

       1  session in tracefile.
      11  user  SQL statements in trace file.
      18  internal SQL statements in trace file.
      29  SQL statements in trace file.
      16  unique SQL statements in trace file.
    1985  lines in trace file.

Why won't it show me a plan??

Thanks
Steven
Followup:
does your trc file end with

*** DUMP FILE SIZE IS LIMITED TO xxxx BYTES ***

if so, up your max_dump_file_size
Rating: 5
Trace File March 31, 2003
Reviewer: Steven Cooper from England
Tom,

I tailed the trc file but didn't see that.  I tried another test with " select
'test' from dual" in both SQLPlus and Report Builder and It worked in SQLPLUS
but not in reports.

Report Builder
---------------

TKPROF: Release 8.1.7.0.0 - Production on Mon Mar 31 13:13:05 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Trace file: ora_19772.trc
Sort options: default

********************************************************************************
count    = number of times OCI procedure was executed
cpu      = cpu time in seconds executing
elapsed  = elapsed time in seconds executing
disk     = number of physical reads of buffers from disk
query    = number of buffers gotten for consistent read
current  = number of buffers gotten in current mode (usually for update)
rows     = number of rows processed by the fetch or execute call
********************************************************************************

alter session set sql_trace true

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      1      0.01       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        1      0.01       0.00          0          0          0           0

Misses in library cache during parse: 0
Misses in library cache during execute: 1
Optimizer goal: CHOOSE
Parsing user id: 619     (recursive depth: 1)
********************************************************************************

declare ret binary_integer; begin ret := PBSDE.DEBUG_LOOP; end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.02          0          0          0           0
Execute      1      0.00       0.00          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.03       0.02          0          0          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 619     (recursive depth: 1)
********************************************************************************

SELECT 'Test'
FROM
 DUAL

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      0      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        1      0.00       0.01          0          0          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 619
********************************************************************************

ALTER SESSION SET TIMED_STATISTICS=FALSE

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          0          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 619
********************************************************************************

alter session set sql_trace false

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          0          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 619     (recursive depth: 1)

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.01          0          0          0           0

Misses in library cache during parse: 2

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.03       0.02          0          0          0           0
Execute      3      0.01       0.00          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      0.04       0.02          0          0          0           1

Misses in library cache during parse: 2
Misses in library cache during execute: 1

    5  user  SQL statements in session.
    0  internal SQL statements in session.
    5  SQL statements in session.
********************************************************************************
Trace file: ora_19772.trc
Trace file compatibility: 8.00.04
Sort options: default

       1  session in tracefile.
       5  user  SQL statements in trace file.
       0  internal SQL statements in trace file.
       5  SQL statements in trace file.
       5  unique SQL statements in trace file.
      46  lines in trace file.

----------------------------------------------------------
SQLPLUS
-------

TKPROF: Release 8.1.7.0.0 - Production on Mon Mar 31 13:14:29 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Trace file: ora_4208.trc
Sort options: default

********************************************************************************
count    = number of times OCI procedure was executed
cpu      = cpu time in seconds executing
elapsed  = elapsed time in seconds executing
disk     = number of physical reads of buffers from disk
query    = number of buffers gotten for consistent read
current  = number of buffers gotten in current mode (usually for update)
rows     = number of rows processed by the fetch or execute call
********************************************************************************

The following statement encountered a error during parse:

insert into plan_table (statement_id, timestamp, operation, options,object_node,
object_owner, object_name, object_instance, object_type,search_columns, id,
parent_id, position, other,optimizer, cost, cardinality, bytes, other_tag,
partition_start, partition_stop, partition_id, distribution )
values(:1,SYSDATE,:2,:3,:4,:5,:6,:7,:8,:9,:10,:11,:12,:13,:14,:15,:16,:17,:18,:19
,:20,:21,:22)

Error encountered: ORA-00904
********************************************************************************

alter session set sql_trace=true

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        1      0.00       0.00          0          0          0           0

Misses in library cache during parse: 0
Misses in library cache during execute: 1
Optimizer goal: CHOOSE
Parsing user id: 619
********************************************************************************

alter session set timed_statistics=true

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          0          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 619
********************************************************************************

select *
from
 dual

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          1          4           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          1          4           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 619

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS FULL DUAL

********************************************************************************

DELETE FROM PLAN_TABLE
WHERE
 STATEMENT_ID=:1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.01          0          0          0           0
Execute      2      0.01       0.00          1          2         10           2
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.01          1          2         10           2

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 619

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  DELETE PLAN_TABLE
      1   TABLE ACCESS FULL PLAN_TABLE

********************************************************************************

BEGIN DBMS_OUTPUT.GET_LINES(:LINES, :NUMLINES); END;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      2      0.01       0.00          0          0          0           2
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.00          0          0          0           2

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 619
********************************************************************************

EXPLAIN PLAN SET STATEMENT_ID='PLUS175652' FOR select * from dual

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.01       0.02          1          0          2           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.01       0.02          1          0          2           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 619

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS FULL DUAL

********************************************************************************

insert into plan_table (statement_id, timestamp, operation, options,
  object_node, object_owner, object_name, object_instance, object_type,
  search_columns, id, parent_id, position, other,optimizer, cost, cardinality,
   bytes, other_tag, partition_start, partition_stop, partition_id )
values
(:1,SYSDATE,:2,:3,:4,:5,:6,:7,:8,:9,:10,:11,:12,:13,:14,:15,:16,:17,:18,:19,
  :20,:21)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      2      0.00       0.00          0          1          2           2
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          1          2           2

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 619     (recursive depth: 1)
********************************************************************************

select o.name, u.name
from
 sys.obj$ o, sys.user$ u where obj# = :1 and owner# = user#

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      0      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        1      0.01       0.00          0          0          0           0

Misses in library cache during parse: 1
Parsing user id: SYS   (recursive depth: 1)
********************************************************************************

SELECT ID ID_PLUS_EXP,PARENT_ID PARENT_ID_PLUS_EXP,LPAD(' ',2*(LEVEL-1))
  ||OPERATION||DECODE(OTHER_TAG,NULL,'','*')||DECODE(OPTIONS,NULL,'','
  ('||OPTIONS||')')||DECODE(OBJECT_NAME,NULL,'',' OF '''||OBJECT_NAME||'''')
  ||DECODE(OBJECT_TYPE,NULL,'',' ('||OBJECT_TYPE||')')||DECODE(ID,0,
  DECODE(OPTIMIZER,NULL,'',' Optimizer='||OPTIMIZER))||DECODE(COST,NULL,'','
  (Cost='||COST||DECODE(CARDINALITY,NULL,'',' Card='||CARDINALITY)
  ||DECODE(BYTES,NULL,'',' Bytes='||BYTES)||')') PLAN_PLUS_EXP,OBJECT_NODE
  OBJECT_NODE_PLUS_EXP
FROM
 PLAN_TABLE START WITH ID=0 AND STATEMENT_ID=:1 CONNECT BY PRIOR ID=PARENT_ID
  AND STATEMENT_ID=:1 ORDER BY ID,POSITION

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          5         12           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.01          0          5         12           2

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 619

Rows     Row Source Operation
-------  ---------------------------------------------------
      2  SORT ORDER BY
      2   CONNECT BY
      2    TABLE ACCESS FULL PLAN_TABLE
      1    TABLE ACCESS BY USER ROWID PLAN_TABLE
      3    TABLE ACCESS FULL PLAN_TABLE

********************************************************************************

SELECT ID ID_PLUS_EXP,OTHER_TAG OTHER_TAG_PLUS_EXP,OTHER OTHER_PLUS_EXP
FROM
 PLAN_TABLE WHERE STATEMENT_ID=:1 AND OTHER_TAG IS NOT NULL ORDER BY ID

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.01       0.00          0          1          4           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.01       0.01          0          1          4           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 619

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  SORT ORDER BY
      0   TABLE ACCESS FULL PLAN_TABLE

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        9      0.01       0.03          0          0          0           0
Execute     10      0.03       0.02          2          2         12           4
Fetch        5      0.01       0.00          0          7         20           3
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       24      0.05       0.05          2          9         32           7

Misses in library cache during parse: 7
Misses in library cache during execute: 1

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.01       0.00          0          0          0           0
Execute      2      0.00       0.00          0          1          2           2
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.00          0          1          2           2

Misses in library cache during parse: 2

   11  user  SQL statements in session.
    1  internal SQL statements in session.
   12  SQL statements in session.
********************************************************************************
Trace file: ora_4208.trc
Trace file compatibility: 8.00.04
Sort options: default

       4  sessions in tracefile.
      27  user  SQL statements in trace file.
       2  internal SQL statements in trace file.
      12  SQL statements in trace file.
      10  unique SQL statements in trace file.
     114  lines in trace file.

Any Ideas????

Thanks
Steven
Followup:
Only that report builder is not exiting the session...
Rating: 4
sys_context from my_dual -- yes, but... April 25, 2003
Reviewer: Gabor ILLES from Hungary
I also tried to select sys_context() from my_dual,
because we have very high number of buffer gets
when using sys_context.
Environment is 8.1.7.3.0 (SuSE Linux).

create table my_dual( dummy varchar2(1) primary key )
 organization index storage (buffer_pool keep);

insert into my_dual values ( 'X' );

analyze table my_dual compute statistics;

declare
  l_namespace varchar2(25) default 'userenv';
  l_variable  varchar2(25) default 'sessionid';
  l_value     varchar2(250);
  l_time number;
begin
  l_time := dbms_utility.get_time;
  for i in 1 .. 1000 loop
    select sys_context( l_namespace, l_variable )
    into l_value from dual;
  end loop;
  dbms_output.put_line( 'With dual:    ' || to_char( dbms_utility.get_time -
l_time, '990.9999' ));

  l_time := dbms_utility.get_time;
  for i in 1 .. 1000 loop
    select sys_context( l_namespace, l_variable )
    into l_value from my_dual;
  end loop;
  dbms_output.put_line( 'With my_dual: ' || to_char( dbms_utility.get_time -
l_time, '990.9999' ));

  l_time := dbms_utility.get_time;
  for i in 1 .. 1000 loop
    l_value := sys_context( l_namespace, l_variable );
  end loop;
  dbms_output.put_line( 'Without dual: ' || to_char( dbms_utility.get_time -
l_time, '990.9999' ));
end;
/

With dual:      64.0000
With my_dual:   89.0000
Without dual:  118.0000

Some statspack details:

SQL_TEXT                                    buffer_gets       rows executions
------------------------------------------- ----------- ---------- ----------
SELECT SYS_CONTEXT(:b1,:b2)   FROM DUAL            5000       1000       1000
SELECT SYS_CONTEXT(:b1,:b2)   FROM MY_DUAL         1000       1000       1000
SELECT SYS_CONTEXT(:b1,:b2)   FROM SYS.DUAL        5000       1000       1000

Here are the trace details:
DEV.ILLES> set autotrace traceonly explain
DEV.ILLES> select sys_context( 'userenv','sessionid') from dual;

Execution Plan
----------------------------------------------------------
    0       SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1)
    1     0   TABLE ACCESS (FULL) OF 'DUAL' (Cost=2 Card=1)

DEV.ILLES> select sys_context( 'userenv','sessionid') from my_dual;

Execution Plan
----------------------------------------------------------
    0       SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1)
    1     0   INDEX (FULL SCAN) OF 'SYS_IOT_TOP_174708' (UNIQUE) (Cost=1 Card=1)

My conclusion is that the # of LIO really decreased, but elapsed time is a
little more. How is this possible? Or I missed something...
Followup:
I did it 10,000 times and get:

With dual:     228.0000
With my_dual:  163.0000
Without dual:  304.0000

PL/SQL procedure successfully completed.

but elapsed times are very variable (outside influences).

The best approach right now is "MY_DUAL" -- less LIOs and it gets the absolute
correct answer everytime - there are cases where sys_context only returns the
correct results when select'ed from the database (some amount of caching going
on)
Rating: 3
dual Vs my_dual June 18, 2003
Reviewer: Ram from CA, USA
Tom,
Sorry to ask this, after all that explanation from you ....

What exactly is the difference between sys.dual and my_dual? what contributes to
the reduction in LIOs when my_dual is used?

It seems to me that then we could use my_dual for all frequent usages on
sys.dual ( select 'x' from dual, select count(*) from dual, etc.,). Any
comments?

Thanks as always
Rating: 4
dual Vs my_dual June 18, 2003
Reviewer: Ram from CA, USA
Tom,
Please ignore my previous message. I got the answer from
http://asktom.oracle.com/pls/ask/f?p=4950:8:320168708929186561::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1562813956388,

Rating: 3
July 17, 2003
Reviewer: SHGoh from Malaysia
Dear Tom,

    Sorry for interruption. I have been waiting for chance to create a new
thread but couldn't get it in the past few weeks. Just a simple question related
to statspack report.

    1. I am running statspack report for 5 mins interval. The total wait time
(s) in second for enqueue is 2,382. What does this mean ? Like what you said
earlier, this is cumulative value. This might not reflect the actual picture if
you don't know the number of users accessing it at that time. As a tuner, how do
you intepret it? Assuming that there are only 10 users accessing at that time.
Does it indicate some bottleneck of the system 2382sec wait time for the period
of 5min or it is ok.

     Your advice is highly appreciated. Thanks.

Rgds,
Goh
Followup:
given that there are only 300 seconds in 5 minutes, having 2,382 seconds of wait
means you have like 10 sessions all waiting for the ENTIRE 5 minutes for a lock.

Yes, it would be something to take a serious look at
Rating: 4
Thanks Tom July 17, 2003
Reviewer: SHGoh from Malaysia
Dear Tom,

   Thanks for clarification. Just to make sure my understanding is correct.

    From the statspack report. The column Total Wait Time (s)for enqueue event
shows 2,382. That's mean the total wait time for the number of users is 2382sec
for the duration of 5minutes snapshot. Just wonder why the Avg wait (ms) only
2877(ms). Thanks.
                                                                   Avg
                                                     Total Wait   wait    Waits
Event                               Waits   Timeouts   Time (s)   (ms)     /txn
---------------------------- ------------ ---------- ---------- ------ --------
enqueue                               828        769      2,382   2877      1.0

Rgds
Goh
Followup:
you had N users waiting an average of M = n*m total wait.

so, each waited an average of 2.877 seconds and since we time them out at 3
second intervals.

10 users.
waiting for 3 seconds each.
over and over.
for 5 minutes.

10 * 5 * 60 = 3000 ~ 2,382
Rating: 2
If you don't mind July 18, 2003
Reviewer: SHGoh from Malaysia
Dear Tom,

   Please elaborate by giving an example (for the above mention case) if you
don't mind. Thanks.

you had N users waiting an average of M = n*m total wait.

so, each waited an average of 2.877 seconds and since we time them out at 3
second intervals.

10 users.
waiting for 3 seconds each.
over and over.
for 5 minutes.

10 * 5 * 60 = 3000 ~ 2,382

Rgds
Goh
Followup:
You had 10 users.

the all waited on an enqueue for the entire period of observation.

so you have 10users * 5 minutes * 60 seconds in a minute = 3000 seconds.  that
is your 2,382 seconds of observed wait.  some waited just a little less then the
entire 5 minutes since we don't have 3000 but only 2382 seconds of wait.

When we wait on an enqueue -- we timeout every 3 seconds to look for deadlocks
and such.  So, if you look at the total waits and timeouts -- you'll see that.
The average wait therefore is just under 3 seconds (since some did not wait the
entire time)
Rating: 5
July 18, 2003
Reviewer: Pinto from Bangalore
Hi Tom,
This is the statspack report that i took from the test database that will go
into production couple of months from now. The load on the system is almost
similar to what our system will support.
I notice that the execute to parse ratio is really low. I find that there are
lot of sqls parsed many times but not executed even once. i am working on it to
find out why this is happening.
Other than this.
top wait event it io done and log file sync
log file sync i think is because of frequent commits (18 per second). am i
right?
what is the reason for io done? What should i look for?

what else can i improve on after looking into this report?
Thanks a ton for this site. its a great help.
Thank you,
Pinto
STATSPACK report for

DB Name         DB Id    Instance     Inst Num Release     Cluster Host
------------ ----------- ------------ -------- ----------- ------- ------------
ENM707        1117206426 nm45                1 9.2.0.3.0   NO      A2FS

            Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:                           36 12-Jul-03 01:12:38      253       1.6

  End Snap:                           37 12-Jul-03 01:27:38      383       1.5

   Elapsed:                               15.00 (mins)

Cache Sizes (end)
~~~~~~~~~~~~~~~~~
               Buffer Cache:       469M      Std Block Size:
        8K
           Shared Pool Size:        96M          Log Buffer:
    3,072K

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:             23,062.08              1,273.68
              Logical reads:              5,336.21                294.71
              Block changes:                142.02                  7.84
             Physical reads:                  3.62                  0.20
            Physical writes:                  4.90                  0.27
                 User calls:                533.11                 29.44
                     Parses:                162.37                  8.97
                Hard parses:                  1.03                  0.06
                      Sorts:                 32.11                  1.77
                     Logons:                  2.84                  0.16
                   Executes:                169.19                  9.34
               Transactions:                 18.11

  % Blocks changed per Read:    2.66    Recursive Call %:                17.82
 Rollback per transaction %:    0.06       Rows per Sort:                 9.29

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:              100.00
            Buffer  Hit   %:   99.96    In-memory Sort %:               99.99
            Library Hit   %:   99.21        Soft Parse %:               99.37
         Execute to Parse %:    4.03         Latch Hit %:               99.88
Parse CPU to Parse Elapsd %:   96.05     % Non-Parse CPU:               88.88

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   70.89   80.11
    % SQL with executions>1:   13.93   10.69
  % Memory for SQL w/exec>1:   18.73   15.27

Top 5 Timed Events
~~~~~~~~~~~~~~~~~~                                                     % Total
Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
io done                                            20,973         131    25.48
log file sync                                      16,790         129    25.09
CPU time                                                          118    23.05
log file parallel write                            16,675         108    20.98
db file parallel write                                276          14     2.80

Statistic                                      Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------
CPU used by this session                      11,821           13.1          0.7
CPU used when call started                    11,812           13.1          0.7
CR blocks created                                 97            0.1          0.0
DBWR buffers scanned                               0            0.0          0.0
DBWR checkpoint buffers written                2,620            2.9          0.2
DBWR checkpoints                                   1            0.0          0.0
DBWR free buffers found                            0            0.0          0.0
DBWR lru scans                                     0            0.0          0.0
DBWR make free requests                            0            0.0          0.0
DBWR revisited being-written buff                  0            0.0          0.0
DBWR summed scan depth                             0            0.0          0.0
DBWR transaction table writes                     60            0.1          0.0
DBWR undo block writes                           942            1.1          0.1
SQL*Net roundtrips to/from client            692,711          769.7         42.5
active txn count during cleanout                  83            0.1          0.0
background checkpoints completed                   1            0.0          0.0
background checkpoints started                     1            0.0          0.0
background timeouts                            1,073            1.2          0.1
branch node splits                                 0            0.0          0.0
buffer is not pinned count                 1,728,696        1,920.8        106.1
buffer is pinned count                     9,165,038       10,183.4        562.4
bytes received via SQL*Net from c         60,092,745       66,769.7      3,687.6
bytes sent via SQL*Net to client     665,033,138,657  738,925,709.6 ############
calls to get snapshot scn: kcmgss            492,103          546.8         30.2
calls to kcmgas                               17,340           19.3          1.1
calls to kcmgcs                                  211            0.2          0.0
change write time                                114            0.1          0.0
cleanout - number of ktugct calls                181            0.2          0.0
cleanouts and rollbacks - consist                 16            0.0          0.0
cleanouts only - consistent read                   1            0.0          0.0
cluster key scan block gets                    2,824            3.1          0.2
cluster key scans                                178            0.2          0.0
commit cleanout failures: buffer                   0            0.0          0.0
commit cleanout failures: callbac                  0            0.0          0.0
commit cleanout failures: cannot                   6            0.0          0.0
commit cleanouts                              38,121           42.4          2.3
commit cleanouts successfully com             38,115           42.4          2.3
commit txn count during cleanout                 282            0.3          0.0
consistent changes                               127            0.1          0.0
consistent gets                            4,672,456        5,191.6        286.7
consistent gets - examination              1,660,785        1,845.3        101.9
cursor authentications                            22            0.0          0.0
data blocks consistent reads - un                115            0.1          0.0
db block changes                             127,817          142.0          7.8
db block gets                                130,133          144.6          8.0
deferred (CURRENT) block cleanout             27,279           30.3          1.7
dirty buffers inspected                            0            0.0          0.0
enqueue conversions                            1,532            1.7          0.1
enqueue releases                              47,237           52.5          2.9
enqueue requests                              47,237           52.5          2.9
enqueue timeouts                                   0            0.0          0.0
enqueue waits                                      9            0.0          0.0
exchange deadlocks                                 0            0.0          0.0
execute count                                152,275          169.2          9.3
free buffer inspected                              1            0.0          0.0
free buffer requested                          3,676            4.1          0.2
 Instance Activity Stats for DB: ENM707  Instance: nm45  Snaps: 36 -37

Statistic                                      Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------
hot buffers moved to head of LRU                  46            0.1          0.0
immediate (CR) block cleanout app                 17            0.0          0.0
immediate (CURRENT) block cleanou                713            0.8          0.0
index fetch by key                           212,699          236.3         13.1
index scans kdiixs1                        9,731,613       10,812.9        597.2
leaf node 90-10 splits                             6            0.0          0.0
leaf node splits                                 121            0.1          0.0
logons cumulative                              2,557            2.8          0.2
messages received                             16,932           18.8          1.0
messages sent                                 16,932           18.8          1.0
no buffer to keep pinned count                     0            0.0          0.0
no work - consistent read gets             1,403,088        1,559.0         86.1
opened cursors cumulative                    146,135          162.4          9.0
parse count (failures)                             1            0.0          0.0
parse count (hard)                               926            1.0          0.1
parse count (total)                          146,137          162.4          9.0
parse time cpu                                 1,314            1.5          0.1
parse time elapsed                             1,368            1.5          0.1
physical reads                                 3,262            3.6          0.2
physical reads direct                          1,201            1.3          0.1
physical reads direct (lob)                        0            0.0          0.0
physical writes                                4,413            4.9          0.3
physical writes direct                         1,793            2.0          0.1
physical writes non checkpoint                 3,065            3.4          0.2
pinned buffers inspected                           0            0.0          0.0
prefetched blocks                                481            0.5          0.0
prefetched blocks aged out before                  0            0.0          0.0
process last non-idle time         2,690,374,449,838 ############## ############
recursive calls                              104,043          115.6          6.4
recursive cpu usage                            1,182            1.3          0.1
redo blocks written                           49,344           54.8          3.0
redo buffer allocation retries                     2            0.0          0.0
redo entries                                  64,913           72.1          4.0
redo log space requests                            3            0.0          0.0
redo log space wait time                          51            0.1          0.0
redo ordering marks                                0            0.0          0.0
redo size                                 20,755,872       23,062.1      1,273.7
redo synch time                               12,825           14.3          0.8
redo synch writes                             16,755           18.6          1.0
redo wastage                               3,716,000        4,128.9        228.0
redo write time                               11,527           12.8          0.7
redo writer latching time                          0            0.0          0.0
redo writes                                   16,651           18.5          1.0
rollback changes - undo records a                 65            0.1          0.0
rollbacks only - consistent read                  82            0.1          0.0
rows fetched via callback                    178,307          198.1         10.9
session connect time               2,690,374,449,838 ############## ############
session cursor cache count                       492            0.6          0.0
session cursor cache hits                    128,458          142.7          7.9
session logical reads                      4,802,592        5,336.2        294.7
session pga memory                       472,255,208      524,728.0     28,979.8
session pga memory max                   472,255,208      524,728.0     28,979.8
session uga memory                        34,514,620       38,349.6      2,118.0
session uga memory max                   355,100,256      394,555.8     21,790.6
shared hash latch upgrades - no w          1,434,957        1,594.4         88.1
shared hash latch upgrades - wait                 59            0.1          0.0
 Instance Activity Stats for DB: ENM707  Instance: nm45  Snaps: 36 -37

Statistic                                      Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------
sorts (disk)                                       4            0.0          0.0
sorts (memory)                                28,892           32.1          1.8
sorts (rows)                                 268,491          298.3         16.5
summed dirty queue length                          0            0.0          0.0
switch current to new buffer                     301            0.3          0.0
table fetch by rowid                       4,372,148        4,857.9        268.3
table fetch continued row                         94            0.1          0.0
table scan blocks gotten                     513,338          570.4         31.5
table scan rows gotten                    30,458,697       33,843.0      1,869.1
table scans (long tables)                      1,939            2.2          0.1
table scans (short tables)                    86,841           96.5          5.3
transaction rollbacks                             53            0.1          0.0
user calls                                   479,799          533.1         29.4
user commits                                  16,286           18.1          1.0
user rollbacks                                    10            0.0          0.0
write clones created in backgroun                  0            0.0          0.0
write clones created in foregroun                 23            0.0          0.0

O7_DICTIONARY_ACCESSIBILITY   TRUE
background_dump_dest          E:\SCR102\SC10.2\Databases\Oracle
compatible                    8.1.7
control_files                 E:\SCR102\SC10.2\Databases\Oracle
cursor_sharing                FORCE
cursor_space_for_time         TRUE
db_block_buffers              60000
db_block_size                 8192
db_domain                     A2OMCSTD
db_file_multiblock_read_count 32
db_files                      1020
db_name                       ENM707
db_writer_processes           1
dbwr_io_slaves                1
dispatchers                   (description=(address=(protocol=T
dml_locks                     200
global_names                  TRUE
java_pool_size                8388608
job_queue_processes           7
large_pool_size               83886080
local_listener                (ADDRESS_LIST=(Address=(Protocol=
log_archive_dest              E:\SCR102\SC10.2\Databases\Oracle
log_archive_format            ARC%T.%S
log_archive_start             TRUE
log_buffer                    3145728
log_checkpoint_interval       8000
log_checkpoint_timeout        0
max_dump_file_size            10240
max_shared_servers            40
open_cursors                  150
open_links                    8
os_authent_prefix             EXT$
parallel_max_servers          10
parallel_min_servers          2
pre_page_sga                  TRUE
processes                     1000
remote_login_passwordfile     EXCLUSIVE
remote_os_authent             TRUE
resource_limit                FALSE
rollback_segments             RB1, RB2, RB3, RB4, RB5, RB6, RB7
service_names                 ENM707.A2OMCSTD
session_cached_cursors        100
shared_pool_size              100663296
shared_servers                20
sort_area_size                524288
timed_statistics              TRUE
transaction_auditing          FALSE
user_dump_dest                E:\SCR102\SC10.2\Databases\Oracle
utl_file_dir                  E:\SCR102\SC10.2\Temp
Followup:
io done is used by the IO slave processes when waiting for slave IO processes to
complete a request or by idle slave IO processes waiting for work.  It is
generally an "idle wait" event.

log file sync is a wait by the foreground (you that is) for io on the log files
-- solution is to make "log faster" generally (or make sure you are bulking up
operations when possible as that generates less redo, only commit when you are
really done).  This is fixed by reducing redo and increasing log disk
performance.

the execute to parse ratio is low -- what is the client?  is it well behaved?
does it "parse" in order to describe the query and then "parse" again to
execute?  is it a web app? client server?  if this is a web app, an exec/parse
ratio of 40-60 might be the best you can do.  if this is client server -- much
higher numbers should be achieved.

I see cursor sharing = false, someone not using bind variables?  they ALWAYS
over parse as well.

given the number of sessions and the tps you have -- the fact you have only 118
seconds of CPU time recorded must mean you have some long running batch jobs
right?  CPU time can be misleading cause it is recorded only after the call --
if if something started before you snapped and finished after you snapped -- it
could contributed 0 cpu seconds.  On the other hand, if it started 5 hours
before you snapped and finished during your period of observation -- it could
contribute 5 hours of cpu time, so be careful looking at that number...

that is a HUGE amount of data sent to the client there
Rating: 5
July 18, 2003
Reviewer: A reader
Tom,

Rather than creating my_dual,can we modify dual as organizational index.

Thanks
Followup:
NO, absolutely NOT.
Rating: 5
log July 30, 2003
Reviewer: junior from va, usa
Tom:
My boss did not want anyone to connect to serval tables. Lockly, all the tables
are in the same tablespace. I turned off a tablespace, so no one can read or
write to it. Which met his requirement. Question is, my boss need a log file,
report, or something to prove during this period of time, no one access those
tables, so any suggestions?

Thank you very much.
Followup:
how can you prove a negative?

how about this:

SQL> select 'no one accessed these tables' from dual;

?

seriously -- i suppose you can enable auditing and you can enable auditing on
these tables.  the audit trails LACK of rows would show you no one queried
them...
Rating: 5
July 30, 2003
Reviewer: A reader
Tom,

In my d/b i have noticed extensive use  of these commands by user "joe"
1.select sysdate from sys.dual
2.select user from sys.dual;

I cannot change application code and use my_dual instead of dual as per your
suggestion.Can i create my_dual in some other schema and force use of my_dual
(maybe through stored outlines) instead of sys.dual if user "joe"  issues these
commands.

Thanks.
Followup:
no.
Rating: 3
What does "PX Deq: Execution msg" means in Top waits August 26, 2003
Reviewer: Shailandra from NJ
Hi Tom,

My top waits in the statspack are as follows.

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
PX Deq: Execution Msg                               2,144      431,736   90.42
db file scattered read                             56,141       29,174    6.11
db file sequential read                            23,028       10,915    2.29
latch free                                          2,482        1,914     .40
enqueue                                                71        1,627     .34
          -------------------------------------------------------------

Can you shed some light on "Px Deq: Execution Msg" waits which consists of 90%
of my waits.

Best Regards
Followup:
This is effectively an idle wait.  Like sqlnet message from client.  newer
statspack filter it.  you can add it to this table:

perfstat@ORA920> select * from stats$IDLE_EVENT
  2  /

EVENT
----------------------------------------------------------------
smon timer
pmon timer
rdbms ipc message
Null event
parallel query dequeue
pipe get
client message
SQL*Net message to client
SQL*Net message from client
SQL*Net more data from client
dispatcher timer
virtual circuit status
lock manager wait for remote message
PX Idle Wait
PX Deq: Execution Msg
PX Deq: Table Q Normal
wakeup time manager
slave wait
i/o slave wait
jobq slave wait
null event
gcs remote message
gcs for action
ges remote message
queue messages

25 rows selected.
Rating: 5
statspack report October 12, 2003
Reviewer: A reader
I would appreciate it if you can give your opinion on this statspack report and
also on my observations below
1) % of rollback transcations high 13.48% - check application for large or
frequent rollbacks
2) Increase shared pool size and check application to make sure bind variables
are used, since.
a) 4 hard parses/sec (3.76 - Load profile)
b) soft parse 89.66% (Instance efficiency percentages)
c) time spent doing real work vs time spent parsing queries  - % Non-Parse CPU:
73.85%.  26.15% of the time spent parsing queries
d) Memory usage high: 95.24% - Shared pool statistics
e) % of SQLs with executions > 1 low: 38.16%

3)Execute to parse ratio high - 79.30% - Shared pool statistics
Check and increase value of session cached cursors
Check application code to decrease # of soft parses

4)Parse CPU to parse elapsed low - 72.42%
Every CPU second spent parsing, spent 1.4 seconds wall clock time - time spent
waiting for resources.
5412 seconds spent waiting on resources(Top 5 Timed Events)

--Questions
Why is the CPU time coming up in the top 5 timed events?
How to interpret the value for the wait event log file parallel write and
virtual circuit stats?
How to interpret the value for the Background wait event - RDBMS ipc message?

----------Report-----------------------

STATSPACK report for

DB Name         DB Id    Instance     Inst Num Release     Cluster Host
------------ ----------- ------------ -------- ----------- ------- ------------
xxx2000       1498780025 xxx2000             1 9.2.0.3.0   NO      xxxora19.xxx
                                                                   .com

            Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:       1 09-Oct-03 09:02:03      131       5.3
  End Snap:       2 09-Oct-03 11:04:24      107       6.8
   Elapsed:              122.35 (mins)

Cache Sizes (end)
~~~~~~~~~~~~~~~~~
               Buffer Cache:     1,024M      Std Block Size:         8K
           Shared Pool Size:       256M          Log Buffer:    10,240K

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:             33,061.20             48,912.18
              Logical reads:             29,594.98             43,784.11
              Block changes:                283.89                419.99
             Physical reads:              1,361.97              2,014.95
            Physical writes:                 39.72                 58.77
                 User calls:                 97.18                143.78
                     Parses:                 36.35                 53.77
                Hard parses:                  3.76                  5.56
                      Sorts:                 25.33                 37.47
                     Logons:                  0.18                  0.26
                   Executes:                175.60                259.80
               Transactions:                  0.68

  % Blocks changed per Read:    0.96    Recursive Call %:    78.52
 Rollback per transaction %:   13.48       Rows per Sort:    51.16

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:  100.00
            Buffer  Hit   %:   95.48    In-memory Sort %:   99.98
            Library Hit   %:   97.82        Soft Parse %:   89.66
         Execute to Parse %:   79.30         Latch Hit %:   99.97
Parse CPU to Parse Elapsd %:   72.42     % Non-Parse CPU:   73.85

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   95.24   92.32
    % SQL with executions>1:   38.16   34.44
  % Memory for SQL w/exec>1:   36.48   34.87

Top 5 Timed Events
~~~~~~~~~~~~~~~~~~                                                     % Total
Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
CPU time                                                        5,412    67.24
SQL*Net message from dblink                       254,303       1,070    13.29
db file sequential read                         2,442,517         536     6.65
SQL*Net more data from dblink                     746,267         497     6.17
log file sync                                      11,206         151     1.88
          -------------------------------------------------------------
 Wait Events for DB: xxx2000  Instance: xxx2000  Snaps: 1 -2
-> s  - second
-> cs - centisecond -     100th of a second
-> ms - millisecond -    1000th of a second
-> us - microsecond - 1000000th of a second
-> ordered by wait time desc, waits desc (idle events last)

                                                                   Avg
                                                     Total Wait   wait    Waits
Event                               Waits   Timeouts   Time (s)   (ms)     /txn
---------------------------- ------------ ---------- ---------- ------ --------
SQL*Net message from dblink       254,303          0      1,070      4     51.3
db file sequential read         2,442,517          0        536      0    492.2
SQL*Net more data from dblin      746,267          0        497      1    150.4
log file sync                      11,206        133        151     13      2.3
db file scattered read          1,478,321          0        143      0    297.9
SQL*Net more data to client        42,751          0        141      3      8.6
ARCH wait on SENDREQ                  754          0         58     77      0.2
library cache pin                      15          7         21   1407      0.0
latch free                          3,055        389          6      2      0.6
PX Deq: Parse Reply                     5          1          3    614      0.0
log file sequential read              262          0          2      8      0.1
control file parallel write         2,529          0          2      1      0.5
enqueue                                 7          0          2    218      0.0
db file parallel read                  89          0          1     16      0.0
SQL*Net message to dblink         254,303          0          1      0     51.3
SQL*Net break/reset to clien          170          0          1      7      0.0
process startup                        29          0          1     25      0.0
log file parallel write            16,593     16,491          0      0      3.3
PX Deq: Execute Reply                  35          0          0      5      0.0
control file sequential read        3,165          0          0      0      0.6
switch logfile command                  5          0          0     25      0.0
PX Deq: Signal ACK                      5          2          0     22      0.0
direct path read                   12,226          0          0      0      2.5
log file single write                   8          0          0     10      0.0
direct path write                   9,462          0          0      0      1.9
PX Deq: Join ACK                        5          0          0      9      0.0
LGWR wait for redo copy               108          1          0      0      0.0
slave TJ process wait                   4          4          0      5      0.0
library cache load lock                 4          0          0      5      0.0
db file parallel write              1,217          0          0      0      0.2
PX Deq: Msg Fragment                    2          0          0      4      0.0
buffer busy waits                      83          0          0      0      0.0
PX Deq Credit: need buffer            104          0          0      0      0.0
async disk IO                         480          0          0      0      0.1
PX Deq Credit: send blkd               56          0          0      0      0.0
direct path read (lob)                148          0          0      0      0.0
db file single write                    4          0          0      0      0.0
direct path write (lob)                63          0          0      0      0.0
virtual circuit status            996,634        497     32,432     33    200.9
SQL*Net message from client       542,659          1     30,758     57    109.4
jobq slave wait                       552        516      1,621   2936      0.1
PX Idle Wait                          755        750      1,492   1977      0.2
SQL*Net more data from clien        7,779          0        630     81      1.6
PX Deq: Execution Msg                  54          3         10    182      0.0
SQL*Net message to client         542,660          0          2      0    109.4
PX Deq: Table Q Normal                 78          0          0      0      0.0
          -------------------------------------------------------------
 Background Wait Events for DB: xxx2000  Instance: xxx2000  Snaps: 1 -2
-> ordered by wait time desc, waits desc (idle events last)

                                                                   Avg
                                                     Total Wait   wait    Waits
Event                               Waits   Timeouts   Time (s)   (ms)     /txn
---------------------------- ------------ ---------- ---------- ------ --------
ARCH wait on SENDREQ                  754          0         58     77      0.2
log file sequential read              262          0          2      8      0.1
control file parallel write         2,521          0          2      1      0.5
db file scattered read                266          0          1      2      0.1
log file parallel write            16,593     16,491          0      0      3.3
db file sequential read               293          0          0      1      0.1
control file sequential read        3,039          0          0      0      0.6
log file single write                   8          0          0     10      0.0
LGWR wait for redo copy               108          1          0      0      0.0
db file parallel write              1,217          0          0      0      0.2
rdbms ipc reply                        23          0          0      0      0.0
latch free                              9          0          0      0      0.0
async disk IO                         472          0          0      0      0.1
enqueue                                 1          0          0      2      0.0
direct path read                      528          0          0      0      0.1
buffer busy waits                       6          0          0      0      0.0
direct path write                     288          0          0      0      0.1
rdbms ipc message                  42,759     23,567     40,750    953      8.6
pmon timer                          2,554      2,554      7,165   2805      0.5
smon timer                             30         17      6,758 ######      0.0
          -------------------------------------------------------------
Followup:
concurr with the analysis -- the coders didn't use binds.

CPU time is in the top 5 TIMED events (not waits!!! see they renamed it).

It is there to show you the amount of cpu time you used during the period of
observation.

Your report ran about 1:45, 1:30 hours:minutes TOO LONG.  take snaps 15/30
minutes apart at most.

you didn't have measurable waits for the events you ask about?
Rating: 5
Why parse=execute in web based system ,could you explain this !! October 12, 2003
Reviewer: Ak
Followup:
because in most web based systems  you

a) grab connection
b) generate page
c) release connection

as opposed to client server where you

a) log in at 9am
b) repeatedly do stuff over and over
c) log out at 5pm

you have less chance for reuse in web based systems due to the short nature of
the "session" - a fraction of a second (hopefully!) vs many hours.
Rating: 5
Statspack report October 12, 2003
Reviewer: A reader
Tom,
Thank you for checking my analysis.

>>concurr with the analysis -- the coders didn't use binds.
Is it more of the bind variables or is it increasing the shared pool?
One other thing that I have not mentioned is the same application is slow during
peak usage (weekdays 9 am - 11 am) and runs fine between 1:00 pm and 3:00 pm.
This is making me believe that the shared pool size is not big enough during
peak usage.
Can you please give your opinion on this?

Also do I need to be concerned about the numbers for the wait event log file
parallel write, virtual circuit stats and for the background wait event - RDBMS
ipc message?
Can you also let me know how to interpret these 3 events?
Followup:
geez, just noticed the dates.  march?  it is october.  and look at the times --
you did a stats pack when things were at their BEST!  it would be nice to
compare a 15 minute snap from when things are good to a 15 minute snap from when
things are bad but what we have here is really "not that useful"

it is binds.  your shared pool is already 2 to 3 times bigger then I like (use).

I fail to see how you made your conclusion on the shared pool.  "it runs slow
from X to Y.  it runs fine from A to B.  Must be the shared pool?????"  nope, i
don't see it.

you could make the shared pool as large as you like.  what you would most likely
achieve is slower performance.

lack of binds and hard parsing -- they are the biggest scalability inhibitors
out there.

ignore those events for now, they are not even on the radar screen here.  they
are background waits and should *generally* be ignored.  lgwr waits on log file
parallel write -- but YOU wait on log file sync for example.  (that lgwr was
waiting does not mean the end user was)
Rating: 5
statspack report October 14, 2003
Reviewer: A reader
Thank you
Rating: 5
Top 5 Timed Events December 06, 2003
Reviewer: A reader
Top 5 Timed Events
~~~~~~~~~~~~~~~~~~                                                     % Total
Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
CPU time                                                       23,174    80.50
latch free                                        866,415       3,789    13.16
log file sync                                     349,051       1,227     4.26
log file parallel write                           359,340         470     1.63
db file parallel write                              2,355          59      .20

Hi is the sume of these 5 events the reponse time? i.e

(service + wait) time
Followup:
not really, not in any meaningful way.

response time is something you need to measure at the application level,
TYPICALLY in the client (you need to factor in all of the junk in between your
client and this database server).

that is just a list of numbers.  (if you really had 23,174 CPU seconds, I hope
you had a 12 or 24 cpu machine, else you took a snapshot longer then 15/30
minutes :)

clients don't wait for all events -- db file parallel write, that is something
db writer might wait for.  it won't really factor into the "end user experience"
(they would manifest as other types of waits, like buffer busy waits for
example).

clients don't wait for log file parallel write -- lgwr does.  clients wait for
other things like log file sync (part of which may be generated by lgwr waiting
on log file parallel write, but the client isn't waiting on that wait, they are
waiting on log file sync)

So, no, you cannot add them up to get service time.

You must instruement your CLIENT APPLICATIONS to get this in any reliable
fashion (every application I do has this capability)
Rating: 2
ok just got confused totally now December 06, 2003
Reviewer: A reader
Hi

I am reading this metalink note 223117.1, Tuning I/O-related waits which states
this

Response Time = Service Time + Wait Time
In the context of an Oracle database, 'Service Time' is measured using the
statistic 'CPU used by this session' and 'Wait Time' is measured using Wait
Events.

From the results someone posted before we got this

Top 5 Timed Events
~~~~~~~~~~~~~~~~~~                                                     % Total
Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
CPU time                                                       23,174    80.50
latch free                                        866,415       3,789    13.16
log file sync                                     349,051       1,227     4.26
log file parallel write                           359,340         470     1.63
db file parallel write                              2,355          59      .20

Statistic                                      Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------
CPU used by this session                   2,317,384          376.2          6.4
CPU used when call started                 2,317,379          376.2          6.4

so CPU time is CPU used by this session , in other words according to Metalink
note this is the service time and the service time PLUS the wait time would be
the response time at the database level

Or this note is another bad Metalink paper (there are quite a few)

And by the way addint the top5  timed events we always get roughly 100%, if you
say that's not the response time what are we measuring using these events?
Followup:
i do not agree with the generalizations, no.

two out of 4 of the waits there are background waits, not counted in user
"service time".

the cpu time -- well, that could be off by quite a bit -- you have to be careful
with that.  CPU time is recorded when a call finishes.  Assume the following
time line:

T0            T2                T3               T4
      x-------|----------process1|---------------x
x-------------|-process2-------x |
              |    x---process3--|--x

Snaps taken at T2 and T3

Now, during the period of observation -- only PROCESS2 will contribute any cpu
to the report.  The cpu used by process1 (performing a very long call) and
process2 -- won't be there (they would be in a snapshot taken from t3 to t4).

So, cpu time -- on a system with long running batches, can be very unreliable at
the aggregate level -- you need to focus in on APPLICATIONS if you want real
service times.

What are you measuring with those wait events?  background and foreground waits.
 You have foreground (client) waits for latches (probably not using binds) and
log syncs.  You have background (oracle background processes) waits as well.
They cannot be counted in the service time for clients -- you cannot use
statspack or anything really at the server level to compute that.  must be done
in the application.
Rating: 5
Statspack Report December 16, 2003
Reviewer: S.Darshan from Singapore
Dear Mr.Tom,

        I have enclosed a part of the Statspack report.  My top 5 wait evnts
says that % Total wait time for "wait for unread message on broadcast channel"
is 98.81.  How can I reduce?  Can you help me provie some more information about
watit events?

STATSPACK report for
DB Name         DB Id    Instance     Inst Num Release     Cluster Host
------------ ----------- ------------ -------- ----------- ------- ------------
prod1             15861197 prod1                 1 9.0.1.3.0   NO      prod1db

            Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:      71 17-Dec-03 09:49:57       84      28.2
  End Snap:      72 17-Dec-03 10:21:10       86      21.3
   Elapsed:               31.22 (mins)

Cache Sizes (end)
~~~~~~~~~~~~~~~~~
               Buffer Cache:       256M      Std Block Size:         8K
           Shared Pool Size:       256M          Log Buffer:     1,024K

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:             16,891.03              3,153.91
              Logical reads:             17,998.48              3,360.70
              Block changes:                117.11                 21.87
             Physical reads:                207.93                 38.82
            Physical writes:                  4.45                  0.83
                 User calls:                 30.60                  5.71
                     Parses:                 48.25                  9.01
                Hard parses:                  2.05                  0.38
                      Sorts:                 11.35                  2.12
                     Logons:                  0.32                  0.06
                   Executes:                130.06                 24.29
               Transactions:                  5.36

  % Blocks changed per Read:    0.65    Recursive Call %:    90.45
 Rollback per transaction %:    0.43       Rows per Sort:     8.46

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:  100.00
            Buffer  Hit   %:   98.85    In-memory Sort %:   99.99
            Library Hit   %:   99.13        Soft Parse %:   95.75
         Execute to Parse %:   62.90         Latch Hit %:   99.73
Parse CPU to Parse Elapsd %:   76.55     % Non-Parse CPU:   98.30

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   92.78   94.34
    % SQL with executions>1:   47.41   51.03
  % Memory for SQL w/exec>1:   37.93   50.53

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                            Wait     % Total
Event                                               Waits  Time (s)   Wt Time
-------------------------------------------- ------------ ----------- -------
wait for unread message on broadcast channel       90,410      70,677   98.81
db file sequential read                           185,065         250     .35
SQL*Net message from dblink                         2,393         207     .29
log file parallel write                             9,140          82     .11
latch free                                          2,899          81     .11
          -------------------------------------------------------------
 Wait Events for DB: prod1  Instance: prod1  Snaps: 71 -72
-> s  - second
-> cs - centisecond -     100th of a second
-> ms - millisecond -    1000th of a second
-> us - microsecond - 1000000th of a second
-> ordered by wait time desc, waits desc (idle events last)
                                                                   Avg
                                                     Total Wait   wait    Waits
Event                               Waits   Timeouts  Time (s)    (ms)     /txn
---------------------------- ------------ ---------- ---------- ------ --------
wait for unread message on b       90,410     68,828     70,677    782      9.0
db file sequential read           185,065          0        250      1     18.4
SQL*Net message from dblink         2,393          0        207     86      0.2
log file parallel write             9,140      9,138         82      9      0.9
latch free                          2,899      2,277         81     28      0.3
db file scattered read             25,464          0         79      3      2.5
log file sync                       1,402          1         55     39      0.1
db file parallel write                258        258         51    196      0.0
async disk IO                         893          0         15     17      0.1
direct path write                     191          0         13     69      0.0
control file parallel write           605          0         12     19      0.1
enqueue                                27          1          5    171      0.0
buffer busy waits                     119          0          1      7      0.0
SQL*Net break/reset to clien           30          0          0     15      0.0
process startup                        11          0          0     38      0.0
LGWR wait for redo copy                70          7          0      3      0.0
resmgr:waiting in end wait             96          0          0      2      0.0
control file sequential read          263          0          0      0      0.0
direct path read                      115          0          0      0      0.0
SQL*Net more data to client           324          0          0      0      0.0
SQL*Net message to dblink           2,393          0          0      0      0.2
jobq slave wait                    10,845      9,824     31,243   2881      1.1
SQL*Net message from client        53,701          0      5,520    103      5.4
wakeup time manager                   118        117      1,682  14251      0.0
virtual circuit status             46,898          4      1,532     33      4.7
SQL*Net more data from clien          103          0          4     42      0.0
SQL*Net message to client          53,709          0          0      0      5.4
          -------------------------------------------------------------
 Background Wait Events for DB: prod1  Instance: prod1  Snaps: 71 -72
-> ordered by wait time desc, waits desc (idle events last)

                                                                   Avg
                                                     Total Wait   wait    Waits
Event                               Waits   Timeouts  Time (s)    (ms)     /txn
---------------------------- ------------ ---------- ---------- ------ --------
log file parallel write             9,141      9,139         82      9      0.9
db file parallel write                258        258         51    196      0.0
control file parallel write           605          0         12     19      0.1
db file scattered read                195          0          1      7      0.0
LGWR wait for redo copy                70          7          0      3      0.0
process startup                         4          0          0     37      0.0
latch free                             13         10          0     11      0.0
db file sequential read               123          0          0      1      0.0
rdbms ipc reply                        23          0          0      1      0.0
control file sequential read          183          0          0      0      0.0
buffer busy waits                       4          0          0      0      0.0
rdbms ipc message                  28,397     20,092      7,231    255      2.8
smon timer                             18          2      1,836 ######      0.0
pmon timer                            664        659      1,831   2757      0.1
          -------------------------------------------------------------
Followup:
The Oracle process is waiting for a message on a broadcast channel. This is
normally an idle wait - the process is waiting for an AQ message on a subscribed
queue.

that means -- you are using AQ, your AQ dequeuers are just sitting there
"waiting", no one is telling them to do anything.

it would appear you have no waits of any significance here.

are you having an identified performance related issue?

(your soft parse % is too low for a transactional system -- you must have some
code out there not using binds efficiently)
Rating: 5
Statspack Report December 18, 2003
Reviewer: S.Darshan from Singapore
Thanks for your reply. We are using AQ with 2 CPU. Last few days we are getting
performance issue. CPU Idle status goes 0 to 5%. I have enclosed statspack
report. Particulary how can I Identify about is it a bind variable issue or CPU
issue?
STATSPACK report for

DB Name         DB Id    Instance     Inst Num Release     Cluster Host
------------ ----------- ------------ -------- ----------- ------- ------------
prod1             15861197 prod1                 1 9.0.1.3.0   NO      prod1db

            Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:     102 18-Dec-03 17:20:01       92      31.8
  End Snap:     103 18-Dec-03 17:40:01       91      29.1
   Elapsed:               20.00 (mins)

Cache Sizes (end)
~~~~~~~~~~~~~~~~~
               Buffer Cache:       256M      Std Block Size:         8K
           Shared Pool Size:       256M          Log Buffer:     1,024K

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:             52,037.37              6,626.85
              Logical reads:             42,195.83              5,373.55
              Block changes:                398.28                 50.72
             Physical reads:              5,452.27                694.34
            Physical writes:                 51.67                  6.58
                 User calls:                  6.10                  0.78
                     Parses:                 43.04                  5.48
                Hard parses:                  2.90                  0.37
                      Sorts:                  7.11                  0.90
                     Logons:                  0.42                  0.05
                   Executes:                 95.42                 12.15
               Transactions:                  7.85

  % Blocks changed per Read:    0.94    Recursive Call %:    97.42
 Rollback per transaction %:    0.03       Rows per Sort:    15.16

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:   99.98       Redo NoWait %:  100.00
            Buffer  Hit   %:   87.09    In-memory Sort %:   99.98
            Library Hit   %:   98.05        Soft Parse %:   93.27
         Execute to Parse %:   54.89         Latch Hit %:   99.26
Parse CPU to Parse Elapsd %:   45.27     % Non-Parse CPU:   98.86

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   91.89   89.15
    % SQL with executions>1:   57.81   61.68
  % Memory for SQL w/exec>1:   46.90   50.77

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                            Wait     % Total
Event                                               Waits  Time (s)   Wt Time
-------------------------------------------- ------------ ----------- -------
wait for unread message on broadcast channel      290,758      45,650   91.80
enqueue                                               325         701    1.41
SQL*Net message from dblink                         5,330         560    1.13
latch free                                         20,060         540    1.09
db file sequential read                           715,617         498    1.00
          -------------------------------------------------------------
 Wait Events for DB: prod1  Instance: prod1  Snaps: 102 -103
-> s  - second
-> cs - centisecond -     100th of a second
-> ms - millisecond -    1000th of a second
-> us - microsecond - 1000000th of a second
-> ordered by wait time desc, waits desc (idle events last)

                                                                   Avg
                                                     Total Wait   wait    Waits
Event                               Waits   Timeouts  Time (s)    (ms)     /txn
---------------------------- ------------ ---------- ---------- ------ --------
wait for unread message on b      290,758     39,661     45,650    157     30.9
enqueue                               325        231        701   2157      0.0
SQL*Net message from dblink         5,330          0        560    105      0.6
latch free                         20,060     15,595        540     27      2.1
db file sequential read           715,617          0        498      1     75.9
async disk IO                      13,043          0        486     37      1.4
direct path write                   3,982          0        449    113      0.4
db file scattered read          1,040,720          0        242      0    110.4
log file sync                       2,429          1        219     90      0.3
queue messages                         13         13        127   9777      0.0
log file parallel write             7,429      7,426        113     15      0.8
db file parallel write                184        184         79    430      0.0
buffer busy waits                   7,292          3         49      7      0.8
control file parallel write           398          0          9     21      0.0
LGWR wait for redo copy               297        108          2      8      0.0
direct path read                      315          0          2      6      0.0
resmgr:waiting in end wait             17          0          1     30      0.0
log buffer space                        3          0          0     85      0.0
process startup                         9          0          0     25      0.0
SQL*Net break/reset to clien           10          0          0     21      0.0
db file single write                    3          0          0     65      0.0
control file sequential read          206          0          0      0      0.0
SQL*Net more data to dblink           506          0          0      0      0.1
db file parallel read                   1          0          0     29      0.0
SQL*Net message to dblink           5,327          0          0      0      0.6
SQL*Net more data to client            23          0          0      0      0.0
resmgr:waiting in check2                3          0          0      0      0.0
buffer deadlock                        13         13          0      0      0.0
jobq slave wait                     6,687      6,009     19,206   2872      0.7
SQL*Net message from client         4,153          0      6,700   1613      0.4
virtual circuit status                865         24      1,194   1381      0.1
wakeup time manager                   200        198        886   4431      0.0
SQL*Net more data from clien            5          0          0     15      0.0
SQL*Net message to client           4,155          0          0      0      0.4
          -------------------------------------------------------------
 Background Wait Events for DB: prod1  Instance: prod1  Snaps: 102 -103
-> ordered by wait time desc, waits desc (idle events last)

                                                                   Avg
                                                     Total Wait   wait    Waits
Event                               Waits   Timeouts  Time (s)    (ms)     /txn
---------------------------- ------------ ---------- ---------- ------ --------
log file parallel write             7,429      7,426        113     15      0.8
db file parallel write                184        184         79    430      0.0
control file parallel write           397          0          8     21      0.0
LGWR wait for redo copy               297        108          2      8      0.0
db file scattered read                168          0          2     11      0.0
async disk IO                         102          0          1      9      0.0
latch free                             31         30          1     24      0.0
db file single write                    2          0          0     94      0.0
db file sequential read               121          0          0      1      0.0
process startup                         2          0          0     51      0.0
control file sequential read          141          0          0      0      0.0
direct path read                       93          0          0      0      0.0
direct path write                      51          0          0      0      0.0
rdbms ipc reply                        11          0          0      0      0.0
buffer busy waits                       3          0          0      0      0.0
rdbms ipc message                  20,751     16,017      3,538    171      2.2
pmon timer                            453        428      1,172   2587      0.0
smon timer                             18          0      1,169  64963      0.0
          -------------------------------------------------------------
 SQL ordered by Gets for DB: prod1  Instance: prod1  Snaps: 102 -103
-> End Buffer Gets Threshold:   10000
-> Note that resources reported for PL/SQL includes the resources used by
   all SQL statements called within the PL/SQL code.  As individual SQL
   statements are also reported, it is possible and valid for the summed
   total % to exceed 100

                                                     CPU      Elapsd
  Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
     16,327,996            1   16,327,996.0   32.2   591.47   1284.20  759208178
SELECT A.BESLOC_ID||','||A.TRANS_DATE||','||B.BEI_ID||','||SUM(B
.QTY_ORDERED)|| ','||SUM(B.NET_AMT)||','||SUM(B.TOTAL_AMT) FROM
SALES_ORDERS A, SALES_ORDER_ITEMS B WHERE A.TRANS_STATUS <> 'CAN
CELLED' AND B.UNIT_PRICE <> 1 AND A.TRANS_DATE >= '01-DEC-2003'
AND A.TRANS_DATE <= '03-DEC-2003' GROUP BY A.BESLOC_ID, A.TRANS_

      6,745,960          502       13,438.2   13.3     0.03      0.02  788799371
DELETE FROM AQ$_PENDING_MESSAGES WHERE SEQUENCE <  (SELECT NVL(M
IN(DECODE(STATUS,3,SEQUENCE, NULL )),MAX(DECODE(STATUS,1,SEQUENC
E,0)))   FROM SYS.AQ$_PROPAGATION_STATUS )

      1,543,658          240        6,431.9    3.0    23.55     86.97  238087931
select t.schema, t.name, t.flags, q.name from system.aq$_queue_t
ables t, sys.aq$_queue_table_affinities aft,      system.aq$_que
ues q where aft.table_objno = t.objno and aft.owner_instance = :
1 and        q.table_objno = t.objno and q.usage = 0 and       b
itand(t.flags, 4+16+32+64+128+256) = 0 for update of t.name, aft

        987,029          290        3,403.5    1.9     0.03      0.08   59186625
SELECT JOBNO   FROM AQ$_SCHEDULES  WHERE ROWNUM < 2  AND JOBNO N
OT IN (SELECT JOB   FROM JOB$ )FOR UPDATE

        431,257          149        2,894.3    0.9     0.00      0.00 2617444063
INSERT into pilid                 (pili_pil_be_id,
    pili_pil_id,                  pili_id,                  isn_
id,                  bei_id,                  beloc_id,
         beuom_id,                  cv_rate,                  be
iqt_id,                  berev_id,                  serial_no,

        396,208            4       99,052.0    0.8    13.78     20.17 3284478371
select isn.* from inventory_serial_numbers isn  where isn.be_id
= 'VH'  and   isn.bei_id = '0100396'  and   isn.qty_available >
0  and isn.beloc_id    = 'TM'  and beloc_id in (      select  id

          -------------------------------------------------------------
 Instance Activity Stats for DB: prod1  Instance: prod1  Snaps: 102 -103

Statistic                                      Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------
CPU used by this session                     177,872          148.2         18.9
CPU used when call started                   177,871          148.2         18.9
CR blocks created                             42,997           35.8          4.6
Cached Commit SCN referenced                       0            0.0          0.0
DBWR buffers scanned                         371,715          309.8         39.5
DBWR checkpoint buffers written                   46            0.0          0.0
DBWR checkpoints                                   0            0.0          0.0
DBWR free buffers found                      362,024          301.7         38.4
DBWR lru scans                                    75            0.1          0.0
DBWR make free requests                           75            0.1          0.0
DBWR revisited being-written buff                 11            0.0          0.0
DBWR summed scan depth                       371,715          309.8         39.5
DBWR transaction table writes                     99            0.1          0.0
DBWR undo block writes                         4,086            3.4          0.4
SQL*Net roundtrips to/from client              4,502            3.8          0.5
SQL*Net roundtrips to/from dblink              5,326            4.4          0.6
background checkpoints completed                   1            0.0          0.0
background checkpoints started                     0            0.0          0.0
background timeouts                            1,388            1.2          0.2
branch node splits                                 2            0.0          0.0
buffer is not pinned count                31,963,454       26,636.2      3,392.1
buffer is pinned count                    86,549,711       72,124.8      9,184.9
bytes received via SQL*Net from c            683,980          570.0         72.6
bytes received via SQL*Net from d            647,771          539.8         68.7
bytes sent via SQL*Net to client             779,410          649.5         82.7
bytes sent via SQL*Net to dblink           2,405,138        2,004.3        255.2
calls to get snapshot scn: kcmgss          1,195,433          996.2        126.9
calls to kcmgas                              108,604           90.5         11.5
calls to kcmgcs                                2,342            2.0          0.3
change write time                              1,356            1.1          0.1
cleanouts and rollbacks - consist             12,252           10.2          1.3
cleanouts only - consistent read                 652            0.5          0.1
cluster key scan block gets                    2,313            1.9          0.3
cluster key scans                              1,048            0.9          0.1
commit cleanout failures: block l                442            0.4          0.1
commit cleanout failures: buffer                  10            0.0          0.0
commit cleanout failures: callbac              1,136            1.0          0.1
commit cleanout failures: cannot                 190            0.2          0.0
commit cleanouts                              69,352           57.8          7.4
commit cleanouts successfully com             67,574           56.3          7.2
consistent changes                         1,553,009        1,294.2        164.8
consistent gets                           50,239,925       41,866.6      5,331.6
consistent gets - examination              1,857,762        1,548.1        197.2
current blocks converted for CR               24,038           20.0          2.6
cursor authentications                         1,415            1.2          0.2
data blocks consistent reads - un          1,484,632        1,237.2        157.6
db block changes                             477,938          398.3         50.7
db block gets                                395,086          329.2         41.9
deferred (CURRENT) block cleanout             22,659           18.9          2.4
dirty buffers inspected                          110            0.1          0.0
enqueue conversions                            6,072            5.1          0.6
enqueue deadlocks                                  0            0.0          0.0
enqueue releases                           2,548,547        2,123.8        270.5
enqueue requests                           2,551,022        2,125.9        270.7
enqueue timeouts                               2,519            2.1          0.3
enqueue waits                                     93            0.1          0.0
 Instance Activity Stats for DB: prod1  Instance: prod1  Snaps: 102 -103

Statistic                                      Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------
exchange deadlocks                                13            0.0          0.0
execute count                                114,498           95.4         12.2
free buffer inspected                         10,734            9.0          1.1
free buffer requested                      6,598,627        5,498.9        700.3
hot buffers moved to head of LRU             354,694          295.6         37.6
immediate (CR) block cleanout app             12,904           10.8          1.4
immediate (CURRENT) block cleanou              4,805            4.0          0.5
index fast full scans (full)                       0            0.0          0.0
leaf node splits                                 367            0.3          0.0
logons cumulative                                505            0.4          0.1
messages received                              7,547            6.3          0.8
messages sent                                  7,547            6.3          0.8
no buffer to keep pinned count                     0            0.0          0.0
no work - consistent read gets            39,979,174       33,316.0      4,242.7
opened cursors cumulative                     52,317           43.6          5.6
parse count (failures)                             4            0.0          0.0
parse count (hard)                             3,474            2.9          0.4
parse count (total)                           51,646           43.0          5.5
parse time cpu                                 2,019            1.7          0.2
parse time elapsed                             4,460            3.7          0.5
physical reads                             6,542,725        5,452.3        694.3
physical reads direct                          3,656            3.1          0.4
physical writes                               62,002           51.7          6.6
physical writes direct                        52,285           43.6          5.6
physical writes non checkpoint                61,430           51.2          6.5
pinned buffers inspected                       2,129            1.8          0.2
prefetch clients - default                         0            0.0          0.0
prefetched blocks                          4,784,597        3,987.2        507.8
prefetched blocks aged out before                117            0.1          0.0
process last non-idle time           516,578,590,549  430,482,158.8 ############
recovery blocks read                               0            0.0          0.0
recursive calls                              276,579          230.5         29.4
recursive cpu usage                          113,568           94.6         12.1
redo blocks written                          129,359          107.8         13.7
redo buffer allocation retries                     3            0.0          0.0
redo entries                                 245,864          204.9         26.1
redo log space requests                            0            0.0          0.0
redo log space wait time                           0            0.0          0.0
redo ordering marks                                1            0.0          0.0
redo size                                 62,444,840       52,037.4      6,626.9
redo synch time                               22,421           18.7          2.4
redo synch writes                              2,289            1.9          0.2
redo wastage                               1,814,604        1,512.2        192.6
redo write time                               34,895           29.1          3.7
redo writer latching time                        243            0.2          0.0
redo writes                                    7,420            6.2          0.8
rollback changes - undo records a                 34            0.0          0.0
rollbacks only - consistent read              56,312           46.9          6.0
rows fetched via callback                     86,463           72.1          9.2
session connect time                 516,578,590,549  430,482,158.8 ############
session logical reads                     50,634,999       42,195.8      5,373.6
session uga memory                         3,313,480        2,761.2        351.6
session uga memory max                   246,195,368      205,162.8     26,127.1
shared hash latch upgrades - no w          7,800,825        6,500.7        827.9
shared hash latch upgrades - wait                 50            0.0          0.0
sorts (disk)                                       2            0.0          0.0
 Instance Activity Stats for DB: prod1  Instance: prod1  Snaps: 102 -103

Statistic                                      Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------
sorts (memory)                                 8,525            7.1          0.9
sorts (rows)                                 129,287          107.7         13.7
summed dirty queue length                      9,947            8.3          1.1
switch current to new buffer                  11,847            9.9          1.3
table fetch by rowid                      41,870,124       34,891.8      4,443.4
table fetch continued row                     42,722           35.6          4.5
table scan blocks gotten                  25,982,177       21,651.8      2,757.3
table scan rows gotten                   335,301,262      279,417.7     35,583.3
table scans (long tables)                      3,702            3.1          0.4
table scans (short tables)                   103,138           86.0         11.0
transaction rollbacks                             22            0.0          0.0
transaction tables consistent rea                  1            0.0          0.0
transaction tables consistent rea                176            0.2          0.0
user calls                                     7,323            6.1          0.8
user commits                                   9,420            7.9          1.0
user rollbacks                                     3            0.0          0.0
write clones created in backgroun                  0            0.0          0.0
write clones created in foregroun                421            0.4          0.0
          -------------------------------------------------------------

Tablespace IO Stats for DB: prod1  Instance: prod1  Snaps: 102 -103
->ordered by IOs (Reads + Writes) desc

Tablespace
------------------------------
                 Av      Av     Av                    Av        Buffer Av Buf
         Reads Reads/s Rd(ms) Blks/Rd       Writes Writes/s      Waits Wt(ms)
-------------- ------- ------ ------- ------------ -------- ---------- ------
VIN_GROUP2
     1,569,581   1,308    0.2     4.1          535        0      8,761    3.1
VIN_GROUP1
       117,572      98    1.6     1.0          679        1          2    0.0
VIN_GROUP5
        22,676      19    3.4     1.0        1,437        1          4    5.0
VIC_HV
        19,716      16    3.8     1.0        1,194        1        806   14.3
SYSTEM
        17,250      14    3.8     1.1          201        0        353   33.9
TEMP
           344       0   24.2    10.5        4,580        4        387   31.4
UNDOTBS
           641       1    8.4     1.0        4,231        4         57   11.4
VIN_I_GROUP5
         3,445       3    4.8     1.0          137        0          0    0.0
VIN_I_GROUP4
         1,821       2    5.9     1.0          478        0          0    0.0
VIN_I_GROUP1
         1,731       1    5.7     1.0          367        0          0    0.0
VIN_I_GROUP2
         1,076       1    5.0     1.0          108        0          0    0.0
TOOLS
           529       0    4.5     1.0          175        0          0    0.0
TS_MONTH6
           152       0    4.5     1.0            1        0          0    0.0
VIN_I_GROUP6
            63       0   16.5     1.0           72        0          0    0.0
VIN_GROUP6
            41       0    7.6     1.0           67        0          0    0.0
VIC_HV_I
            86       0    5.3     1.0            1        0          0    0.0
VIN_I_ARCHIVE
            12       0   32.5     1.0            1        0          0    0.0
TS_MONTH10
             3       0  126.7     1.0            1        0          0    0.0
TS_MONTH11
             3       0  140.0     1.0            1        0          0    0.0
TS_MONTH12
             3       0  170.0     1.0            1        0          0    0.0
TS_MONTH5
             3       0  200.0     1.0            1        0          0    0.0
TS_MONTH7
             3       0   93.3     1.0            1        0          0    0.0
TS_MONTH8
             3       0  143.3     1.0            1        0          0    0.0
TS_MONTH9
             3       0  123.3     1.0            1        0          0    0.0
VIC_LV
             3       0  126.7     1.0            1        0          0    0.0
VIC_LV_I
             3       0  170.0     1.0            1        0          0    0.0
 Tablespace IO Stats for DB: prod1  Instance: prod1  Snaps: 102 -103
->ordered by IOs (Reads + Writes) desc

Tablespace
------------------------------
                 Av      Av     Av                    Av        Buffer Av Buf
         Reads Reads/s Rd(ms) Blks/Rd       Writes Writes/s      Waits Wt(ms)
-------------- ------- ------ ------- ------------ -------- ---------- ------
VIN_ARCHIVE
             3       0   80.0     1.0            1        0          0    0.0
TS_MONTH1
             2       0  130.0     1.0            1        0          0    0.0
TS_MONTH10_IND
             2       0  305.0     1.0            1        0          0    0.0
TS_MONTH11_IND
             2       0  140.0     1.0            1        0          0    0.0
TS_MONTH12_IND
             2       0  215.0     1.0            1        0          0    0.0
TS_MONTH1_IND
             2       0  245.0     1.0            1        0          0    0.0
TS_MONTH2
             2       0  120.0     1.0            1        0          0    0.0
TS_MONTH2_IND
             2       0  120.0     1.0            1        0          0    0.0
TS_MONTH3
             2       0  210.0     1.0            1        0          0    0.0
TS_MONTH3_IND
             2       0  170.0     1.0            1        0          0    0.0
TS_MONTH4
             2       0  215.0     1.0            1        0          0    0.0
TS_MONTH4_IND
             2       0  110.0     1.0            1        0          0    0.0
TS_MONTH5_IND
             2       0  130.0     1.0            1        0          0    0.0
TS_MONTH6_IND
             2       0  120.0     1.0            1        0          0    0.0
TS_MONTH7_IND
             2       0  210.0     1.0            1        0          0    0.0
TS_MONTH8_IND
             2       0  215.0     1.0            1        0          0    0.0
TS_MONTH9_IND
             2       0  300.0     1.0            1        0          0    0.0
CWMLITE
             1       0  490.0     1.0            1        0          0    0.0
DRSYS
             1       0  240.0     1.0            1        0          0    0.0
EXAMPLE
             1       0  340.0     1.0            1        0          0    0.0
INDX
             1       0  220.0     1.0            1        0          0    0.0
USERS
             1       0  240.0     1.0            1        0          0    0.0
VIN_GROUP3
             1       0  600.0     1.0            1        0          0    0.0
VIN_GROUP4
             1       0  610.0     1.0            1        0          0    0.0
VIN_I_GROUP3
             1       0  370.0     1.0            1        0          0    0.0
          -------------------------------------------------------------
Followup:
that your CPU is pegged means you have some nasty things going on in your system

this statspack in the waits section shows that AQ is probably NOT the culprit
(it spent most of its time, well, waiting)

However, hmmm

  Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
     16,327,996            1   16,327,996.0   32.2   591.47   1284.20  759208178
SELECT A.BESLOC_ID||','||A.TRANS_DATE||','||B.BEI_ID||','||SUM(B
.QTY_ORDERED)|| ','||SUM(B.NET_AMT)||','||SUM(B.TOTAL_AMT) FROM
SALES_ORDERS A, SALES_ORDER_ITEMS B WHERE A.TRANS_STATUS <> 'CAN
CELLED' AND B.UNIT_PRICE <> 1 AND A.TRANS_DATE >= '01-DEC-2003'
AND A.TRANS_DATE <= '03-DEC-2003' GROUP BY A.BESLOC_ID, A.TRANS_

thats a nasty query with lots of logical IO's.  It ran for quite a while and did
lots and lots of logical IO's (a slightly cpu intensive thing)

I could well imagine that query causing a bit of a CPU spike -- couldn't you?

Looking at this:

Statistic                                      Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------
CPU used by this session                     177,872          148.2         18.9

(you used "148 cpu seconds per second") it indicates you have lots of long
running queries/processes flooding your system all of a sudden.  You didn't use
148 cpu seconds per second of course -- but the way the stats are collected --
you did have lots of long running "things" finish during your statspack report.

For example, say a nasty query started at 9am.  It is going to take 3 hours to
get the first row back (lots of work to do).  You take a statspack snapshot at
11:55am and 12:05pm.  The query, which finished AT NOON, dumps all 3hours of CPU
use into this snapshot window

(just saying this so you are not too confused by those numbers)

So, my advice to you is to find your high load SQL (figure out "what did we
change two days ago" and don't say "nothing".  you've added a new application,
dropped an index, added an index -- you did SOMETHING and now you have same
nasty queries chewing up your cpu).  Fix it.  And get the cpu back down where it
was before the changes.

Oh yeah -- find the developer that wrote this query and educate them on:

a) implicit conversions are bad bad bad -- use to_date to convert a string to a
date

b) not using bind variables is bad bad bad -- learn to bind.

that developer is probably the cause of your low soft parse %
Rating: 5
Statspack Report December 18, 2003
Reviewer: S.Darshan from Singapore
Continued from previous follow up.
File IO Stats for DB: prod1  Instance: prod1  Snaps: 102 -103
->ordered by Tablespace, File

Tablespace               Filename
------------------------ ----------------------------------------------------
                 Av      Av     Av                    Av        Buffer Av Buf
         Reads Reads/s Rd(ms) Blks/Rd       Writes Writes/s      Waits Wt(ms)
-------------- ------- ------ ------- ------------ -------- ---------- ------
CWMLITE                  /oracle/app/oradata/prod1/cwmlite01.dbf
             1       0  490.0     1.0            1        0          0

DRSYS                    /oracle/app/oradata/prod1/drsys01.dbf
             1       0  240.0     1.0            1        0          0

EXAMPLE                  /oracle/app/oradata/prod1/example01.dbf
             1       0  340.0     1.0            1        0          0

INDX                     /oracle/app/oradata/prod1/indx01.dbf
             1       0  220.0     1.0            1        0          0

SYSTEM                   /oracle/app/oradata/prod1/system01.dbf
        17,250      14    3.8     1.1          201        0        353   33.9

TEMP                     /data4/oracle/oradata/temp/temp02.dbf
           110       0   35.9     8.9        1,025        1         34    5.6
                         /oracle/app/oradata/prod1/temp01.dbf
           234       0   18.8    11.2        3,555        3        353   33.9

TOOLS                    /oracle/app/oradata/prod1/tools01.dbf
           529       0    4.5     1.0          175        0          0

TS_MONTH1                /data2/v5lmts/month/data/ts_month1.ora
             2       0  130.0     1.0            1        0          0

TS_MONTH10               /data2/v5lmts/month/data/ts_month10.ora
             3       0  126.7     1.0            1        0          0

TS_MONTH10_IND           /data3/v5lmts/month/index/ts_month10_ind.ora
             2       0  305.0     1.0            1        0          0

TS_MONTH11               /data2/v5lmts/month/data/ts_month11.ora
             3       0  140.0     1.0            1        0          0

TS_MONTH11_IND           /data3/v5lmts/month/index/ts_month11_ind.ora
             2       0  140.0     1.0            1        0          0

TS_MONTH12               /data2/v5lmts/month/data/ts_month12.ora
             3       0  170.0     1.0            1        0          0

TS_MONTH12_IND           /data3/v5lmts/month/index/ts_month12_ind.ora
             2       0  215.0     1.0            1        0          0

TS_MONTH1_IND            /data3/v5lmts/month/index/ts_month1_ind.ora
             2       0  245.0     1.0            1        0          0

TS_MONTH2                /data2/v5lmts/month/data/ts_month2.ora
             2       0  120.0     1.0            1        0          0

TS_MONTH2_IND            /data3/v5lmts/month/index/ts_month2_ind.ora
             2       0  120.0     1.0            1        0          0
 File IO Stats for DB: prod1  Instance: prod1  Snaps: 102 -103
->ordered by Tablespace, File

Tablespace               Filename
------------------------ ----------------------------------------------------
                 Av      Av     Av                    Av        Buffer Av Buf
         Reads Reads/s Rd(ms) Blks/Rd       Writes Writes/s      Waits Wt(ms)
-------------- ------- ------ ------- ------------ -------- ---------- ------

TS_MONTH3                /data2/v5lmts/month/data/ts_month3.ora
             2       0  210.0     1.0            1        0          0

TS_MONTH3_IND            /data3/v5lmts/month/index/ts_month3_ind.ora
             2       0  170.0     1.0            1        0          0

TS_MONTH4                /data2/v5lmts/month/data/ts_month4.ora
             2       0  215.0     1.0            1        0          0

TS_MONTH4_IND            /data3/v5lmts/month/index/ts_month4_ind.ora
             2       0  110.0     1.0            1        0          0

TS_MONTH5                /data2/v5lmts/month/data/ts_month5.ora
             3       0  200.0     1.0            1        0          0

TS_MONTH5_IND            /data3/v5lmts/month/index/ts_month5_ind.ora
             2       0  130.0     1.0            1        0          0

TS_MONTH6                /data2/v5lmts/month/data/ts_month6.ora
           152       0    4.5     1.0            1        0          0

TS_MONTH6_IND            /data3/v5lmts/month/index/ts_month6_ind.ora
             2       0  120.0     1.0            1        0          0

TS_MONTH7                /data2/v5lmts/month/data/ts_month7.ora
             3       0   93.3     1.0            1        0          0

TS_MONTH7_IND            /data3/v5lmts/month/index/ts_month7_ind.ora
             2       0  210.0     1.0            1        0          0

TS_MONTH8                /data2/v5lmts/month/data/ts_month8.ora
             3       0  143.3     1.0            1        0          0

TS_MONTH8_IND            /data3/v5lmts/month/index/ts_month8_ind.ora
             2       0  215.0     1.0            1        0          0

TS_MONTH9                /data2/v5lmts/month/data/ts_month9.ora
             3       0  123.3     1.0            1        0          0

TS_MONTH9_IND            /data3/v5lmts/month/index/ts_month9_ind.ora
             2       0  300.0     1.0            1        0          0

UNDOTBS                  /data4/oracle/oradata/prod1/undotbs02.dbf
           282       0   10.6     1.0        1,884        2         23   20.0
                         /oracle/app/oradata/prod1/undotbs01.dbf
           359       0    6.7     1.0        2,347        2         34    5.6

USERS                    /oracle/app/oradata/prod1/users01.dbf
             1       0  240.0     1.0            1        0          0

VIC_HV                   /data3/v5lmts/vic/vic_hv.ora
 File IO Stats for DB: prod1  Instance: prod1  Snaps: 102 -103
->ordered by Tablespace, File

Tablespace               Filename
------------------------ ----------------------------------------------------
                 Av      Av     Av                    Av        Buffer Av Buf
         Reads Reads/s Rd(ms) Blks/Rd       Writes Writes/s      Waits Wt(ms)
-------------- ------- ------ ------- ------------ -------- ---------- ------
        19,716      16    3.8     1.0        1,194        1        806   14.3

VIC_HV_I                 /data2/v5lmts/vic/vic_hv_i.ora
            86       0    5.3     1.0            1        0          0

VIC_LV                   /data3/v5lmts/vic/vic_lv.ora
             3       0  126.7     1.0            1        0          0

VIC_LV_I                 /data2/v5lmts/vic/vic_lv_i.ora
             3       0  170.0     1.0            1        0          0

VIN_ARCHIVE              /data5/v5lmts/vin_archive.ora
             3       0   80.0     1.0            1        0          0

VIN_GROUP1               /data1/v5lmts/vin_group1.ora
       117,572      98    1.6     1.0          679        1          2    0.0

VIN_GROUP2               /data2/v5lmts/vin_group2.ora
     1,569,581   1,308    0.2     4.1          535        0      8,761    3.1

VIN_GROUP3               /data1/v5lmts/vin_group3.ora
             1       0  600.0     1.0            1        0          0

VIN_GROUP4               /data1/v5lmts/vin_group4.ora
             1       0  610.0     1.0            1        0          0

VIN_GROUP5               /data6/v5lmts/vin_group5.ora
        22,676      19    3.4     1.0        1,437        1          4    5.0

VIN_GROUP6               /data6/v5lmts/vin_group6.ORA
            41       0    7.6     1.0           67        0          0

VIN_I_ARCHIVE            /data3/v5lmts/vin_i_archive.ora
            12       0   32.5     1.0            1        0          0

VIN_I_GROUP1             /data4/v5lmts/vin_i_group1.ora
         1,731       1    5.7     1.0          367        0          0

VIN_I_GROUP2             /data4/v5lmts/vin_i_group2.ora
         1,076       1    5.0     1.0          108        0          0

VIN_I_GROUP3             /data4/v5lmts/vin_i_group3.ora
             1       0  370.0     1.0            1        0          0

VIN_I_GROUP4             /data4/v5lmts/vin_i_group4.ora
         1,821       2    5.9     1.0          478        0          0

VIN_I_GROUP5             /data3/v5lmts/vin_i_group5.ora
         3,445       3    4.8     1.0          137        0          0

VIN_I_GROUP6             /data6/v5lmts/vin_i_group6.ORA
            63       0   16.5     1.0           72        0          0
 File IO Stats for DB: prod1  Instance: prod1  Snaps: 102 -103
->ordered by Tablespace, File

Tablespace               Filename
------------------------ ----------------------------------------------------
                 Av      Av     Av                    Av        Buffer Av Buf
         Reads Reads/s Rd(ms) Blks/Rd       Writes Writes/s      Waits Wt(ms)
-------------- ------- ------ ------- ------------ -------- ---------- ------

          -------------------------------------------------------------
 Buffer Pool Statistics for DB: prod1  Instance: prod1  Snaps: 102 -103
-> Standard block size Pools  D: default,  K: keep,  R: recycle
-> Default Pools for other block sizes: 2k, 4k, 8k, 16k, 32k

                                                           Free    Write  Buffer
     Number of Cache      Buffer    Physical   Physical  Buffer Complete    Busy
P      Buffers Hit %        Gets       Reads     Writes   Waits    Waits   Waits
--- ---------- ----- ----------- ----------- ---------- ------- --------  ------
D       31,824  86.5  48,615,562   6,552,397      9,717       0        0   9,984
          -------------------------------------------------------------

Instance Recovery Stats for DB: prod1  Instance: prod1  Snaps: 102 -103
-> B: Begin snapshot,  E: End snapshot

  Targt Estd                                    Log File   Log Ckpt   Log Ckpt
  MTTR  MTTR   Recovery    Actual     Target      Size     Timeout    Interval
   (s)   (s)   Estd IOs  Redo Blks  Redo Blks  Redo Blks  Redo Blks  Redo Blks
- ----- ----- ---------- ---------- ---------- ---------- ---------- ----------
B   204    23      31824     138121     137286     184320     137286 ##########
E   204    23      31824     172666     176293     184320     176293 ##########
          -------------------------------------------------------------

Buffer wait Statistics for DB: prod1  Instance: prod1  Snaps: 102 -103
-> ordered by wait time desc, waits desc

                                 Tot Wait    Avg
Class                    Waits   Time (s) Time (ms)
------------------ ----------- ---------- ---------
data block               9,896         50         5
undo block                  33          1        18
undo header                 24          0         3
          -------------------------------------------------------------

PGA Memory Stats for DB: prod1  Instance: prod1  Snaps: 102 -103
-> WorkArea (W/A) memory is used for: sort, bitmap merge, and hash join ops

Statistic                                  Begin (M)          End (M)     % Diff
----------------------------------- ---------------- ---------------- ----------
maximum PGA allocated                        123.645          123.645        .00
          -------------------------------------------------------------
 Enqueue activity for DB: prod1  Instance: prod1  Snaps: 102 -103
-> Enqueue stats gathered prior to 9i should not be compared with 9i data
-> ordered by waits desc, requests desc

                                                       Avg Wt        Wait
Eq     Requests    Succ Gets Failed Gets       Waits  Time (ms)    Time (s)
-- ------------ ------------ ----------- ----------- ----------- ------------
TX       29,114       29,116           0          89   10,349.63          921
SQ          118          118           0           4        2.50            0
          -------------------------------------------------------------
 Rollback Segment Stats for DB: prod1  Instance: prod1  Snaps: 102 -103
->A high value for "Pct Waits" suggests more rollback segments may be required
->RBS stats may not be accurate between begin and end snaps when using Auto Undo
  managment, as RBS may be dynamically created and dropped as needed

        Trans Table       Pct   Undo Bytes
RBS No      Gets        Waits     Written        Wraps  Shrinks  Extends
------ -------------- ------- --------------- -------- -------- --------
     0           19.0    0.00               0        0        0        0
     1        1,895.0    0.00         418,250        0        0        0
     2        2,513.0    0.00       6,593,204        9        1        7
     3        1,877.0    0.00         364,160        0        0        0
     4        1,848.0    0.00         383,990        0        0        0
     5        2,181.0    0.00       2,753,534        2        0        2
     6        1,850.0    0.00         470,244        0        0        0
     7        1,929.0    0.00         559,702        3        1        1
     8        1,499.0    0.00         441,936        0        0        0
     9        1,917.0    0.00         413,788        3        1        1
    10        1,857.0    0.00         427,630        0        0        0
    11        1,938.0    0.05         483,484        3        1        1
    12        1,955.0    0.00         427,074        0        0        0
    13        1,885.0    0.00         397,384        0        0        0
    14        1,888.0    0.00         516,926        1        0        0
    15        1,929.0    0.00         508,076        1        0        0
    16        1,899.0    0.00         483,480        0        0        0
    17        1,900.0    0.00         404,548        1        0        0
    18        1,961.0    0.00         571,210        3        1        1
    19        1,858.0    0.00         485,152        2        0        1
    20        2,128.0    0.00         719,638        3        1        1
    21        1,928.0    0.00         419,604        0        0        0
    22        1,862.0    0.00         443,412        0        0        0
    23        1,918.0    0.00         502,612        3        1        1
    24        1,217.0    0.08         333,600        1        0        0
    25        2,293.0    0.00       1,017,648        1        0        0
    26        1,982.0    0.00         567,614        1        0        0
    27        1,887.0    0.00         504,094        0        0        0
    28        1,934.0    0.00         568,922        0        0        0
    29        1,993.0    0.00         632,136        3        1        1
    30        1,909.0    0.00         371,110        0        0        0
    31        2,021.0    0.00         796,230        1        0        0
    32        1,924.0    0.00         484,742        0        0        0
    33           19.0    0.00               0        0        0        0
    34        1,895.0    0.05         491,282        3        1        1
    35        1,905.0    0.00         445,406        3        1        1
    36        1,912.0    0.00         546,046        0        0        0
    37        1,902.0    0.00         548,668        1        0        0
    38        1,871.0    0.00         487,828        0        0        0
          -------------------------------------------------------------
 Rollback Segment Storage for DB: prod1  Instance: prod1  Snaps: 102 -103
->Optimal Size should be larger than Avg Active

RBS No    Segment Size      Avg Active    Optimal Size    Maximum Size
------ --------------- --------------- --------------- ---------------
     0         401,408           8,192                         401,408
     1       2,220,032         534,244                       4,317,184
     2       8,511,488       2,474,137                       8,511,488
     3       2,220,032         956,473                       6,414,336
     4       2,220,032         489,739                       4,317,184
     5       5,365,760       1,672,933                       8,511,488
     6       2,220,032         484,390                       4,317,184
     7       2,220,032         475,990                       4,317,184
     8       2,220,032         774,009                       8,511,488
     9       3,268,608         516,678                       5,365,760
    10       2,220,032         476,046                       3,268,608
    11       3,268,608       1,050,015                       5,365,760
    12       2,220,032         532,673                       4,317,184
    13       2,220,032         545,634                       4,317,184
    14       2,220,032         546,622                       5,365,760
    15       2,220,032         551,600                       5,365,760
    16       2,220,032         543,862                       5,365,760
    17       2,220,032         964,041                       6,414,336
    18       2,220,032         480,764                       4,317,184
    19       2,220,032         501,877                       6,414,336
    20       2,220,032         890,454                       6,414,336
    21       2,220,032         576,748                       5,365,760
    22       2,220,032         895,598                       6,414,336
    23       2,220,032         712,850                       6,414,336
    24       2,220,032         529,048                       4,317,184
    25       2,220,032         543,101                       4,317,184
    26       2,220,032         530,473                       4,317,184
    27       6,414,336       1,539,814                       6,414,336
    28       2,220,032         884,009                       6,414,336
    29       2,220,032         515,935                       6,414,336
    30       2,220,032         479,980                       4,317,184
    31       2,220,032         783,215                       4,317,184
    32       2,220,032         546,197                       5,365,760
    33       2,220,032         824,802                       5,365,760
    34       9,560,064       1,161,636                       9,560,064
    35       2,220,032         559,024                       6,414,336
    36       2,220,032       1,122,082                       9,560,064
    37       2,220,032         690,384                       6,414,336
    38       2,220,032         594,133                       7,462,912
          -------------------------------------------------------------
 Undo Segment Summary for DB: prod1  Instance: prod1  Snaps: 102 -103
-> Undo segment block stats:
-> uS - unexpired Stolen,   uR - unexpired Released,   uU - unexpired reUsed
-> eS - expired   Stolen,   eR - expired   Released,   eU - expired   reUsed

Undo           Undo        Num  Max Qry     Max Tx Snapshot Out of uS/uR/uU/
 TS#         Blocks      Trans  Len (s)   Concurcy  Too Old  Space eS/eR/eU
---- -------------- ---------- -------- ---------- -------- ------ -------------
   1          2,894     22,111    1,283         38        0      0 0/0/0/0/0/0
          -------------------------------------------------------------

Undo Segment Stats for DB: prod1  Instance: prod1  Snaps: 102 -103
-> ordered by Time desc

                     Undo      Num Max Qry   Max Tx  Snap   Out of uS/uR/uU/
End Time           Blocks    Trans Len (s)    Concy Too Old  Space eS/eR/eU
------------ ------------ -------- ------- -------- ------- ------ -------------
18-Dec 17:35        1,138   10,627   1,283       22       0      0 0/0/0/0/0/0
18-Dec 17:25        1,756   11,484     461       38       0      0 0/0/0/0/0/0
          -------------------------------------------------------------
 Latch Activity for DB: prod1  Instance: prod1  Snaps: 102 -103
->"Get Requests", "Pct Get Miss" and "Avg Slps/Miss" are statistics for
  willing-to-wait latch get requests
->"NoWait Requests", "Pct NoWait Miss" are for no-wait latch get requests
->"Pct Misses" for both should be very close to 0.0
-> ordered by Wait Time desc, Avg Slps/Miss, Pct NoWait Miss desc

                                           Pct    Avg   Wait                 Pct
                              Get          Get   Slps   Time       NoWait NoWait
Latch                       Requests      Miss  /Miss    (s)     Requests   Miss
------------------------ -------------- ------ ------ ------ ------------ ------
AQ Propagation Schedulin          9,527    0.1    0.0      0            0
checkpoint queue latch           52,601    0.0    0.0      0            0
messages                         52,909    0.0    0.0      0            0
list of block allocation         58,723    0.0    0.0      0            0
post/wait queue latch             4,841    0.0    0.0      0            0
resmgr:resource group CP            239    1.3    0.0      0            0
user lock                         1,269    0.2    0.0      0            0
channel operations paren     29,707,910    0.6    0.0      0            0
undo global data                695,949    0.0    0.1      0            0
message pool operations          21,396    0.1    0.1      0            0
cache buffers chains        109,982,484    0.0    0.1      0   12,075,871    0.1
hash table column usage               3    0.0             0            7    0.0
resmgr:schema config            207,752    0.0             0          446    0.0
kmcpvec latch                         0                    0           11    0.0
process allocation                  165    0.0             0          165    0.0
redo copy                             0                    0      245,902    0.2
job workq parent latch                0                    0          631    1.0
AQ Propagation Schedulin            295    0.0             0            0
FIB s.o chain latch                   4    0.0             0            0
FOB s.o list latch                  572    0.0             0            0
child cursor hash table          25,025    0.0             0            0
SQL memory manager worka             67    0.0             0            0
kwqit: protect wakeup ti            651    0.0             0            0
ktm global data                      55    0.0             0            0
kmcptab latch                        11    0.0             0            0
job_queue_processes para            407    0.0             0            0
file number translation          29,132    0.0             0            0
event group latch                   165    0.0             0            0
channel handle pool latc            553    0.0             0            0
active checkpoint queue             509    0.0             0            0
virtual circuits                  1,607    0.0             0            0
virtual circuit queues            3,290    0.0             0            0
transaction branch alloc             31    0.0             0            0
temporary table state ob             37    0.0             0            0
sort extent pool                  2,342    0.0             0            0
session timer                       446    0.0             0            0
session switching                    31    0.0             0            0
session idle bit                 15,753    0.0             0            0
resmgr:vc list latch              1,588    0.0             0            0
resmgr:actses change sta             12    0.0             0            0
resmgr:actses change gro            149    0.0             0            0
resmgr:actses active lis          1,011    0.0             0            0
process group creation              332    0.0             0            0
ncodef allocation latch              31    0.0             0            0
longop free list                      4    0.0             0            0
loader state object free             20    0.0             0            0
library cache load lock             136    0.0             0            0
archive control                       1    0.0             0            0
cache buffer handles              1,620    0.0             0            0
Rating: 4
service time and Metalink December 19, 2003
Reviewer: A reader
Hi

You said we cannot compute service time using statspack but there are several
notes on Metalink which teach us how to do so.... Such as

Note: 232443.1 How to identify resource intensive SQL for tuning

states

Service Time = CPU Parse + CPU Recursive + CPU Other
Service Time from CPU used by this session
CPU Parse from parse time cpu
CPU Recursive from recursive cpu usage

Example from Statspack: (pre-Oracle9i Release 2)
Here is an example where CPU Other was found to be a significant component of
total Response Time:

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
direct path read                                    4,232       10,827   52.01
db file scattered read                              6,105        6,264   30.09
direct path write                                   1,992        3,268   15.70
control file parallel write                           893          198     .95
db file parallel write                                 40          131     .63
         -------------------------------------------------------------

Statistic                                    Total   per Second    per Trans
--------------------------------- ---------------- ------------ ------------
CPU used by this session                   358,806        130.5     12,372.6
parse time cpu                                  38          0.0          1.3
recursive cpu usage                        186,636         67.9      6,435.7
From these figures we can obtain:
Wait Time = 10,827 x 100% / 52,01% = 20,817 cs
Service Time = 358,806 cs
Response Time = 358,806 + 20,817 = 379,623 cs
CPU Other = 358,806 - 38 - 186,636 = 172,132 cs

Is this note totally useless...? I mean if service time cannot be determined
using Statspack why Metalink is promoting it
Followup:
you would have to ask the authors of the notes (eg: file a tar with metalink) to
ask that.

control file parallel write -your END USERS do not wait for that.  it is a
background wait.

they just need to refine it.

However, I am strongly of the mindset that you cannot get a reliable -- even
remotely reliable "service time" from a statspack.

first -- statspack shows the entire database.  You might have one transaction
with a service time of 1000 seconds.  you might have 1,000 others with service
times of 1 second.  You have an average service time of "2 seconds" -- what???
no one has a 2 second service time in your system.  that is a meaningless random
number.

You have to compute service time in the client, based on transaction.  I don't
see any other way to do it.  Every other number is just a random number, as
useful as:

select dbms_random.random from dual;
December 19, 2003 Nick -- Thanks for the question regarding "LOB - Replacing a part of / Not overwriting", version 8.1.6
You Asked
Hi Tom,

Presently, it is not possible to replace a part of a LOB (the
DBMS_LOB package does not have a replace function). The write
procedure overwrites.

Is it possible, if not why, to write at a certain position (not
append) and offset the remaining portion while not overwriting.
For example, say that in a CLOB column on a table, I want to
replace the string '1234' with the string '123456890' (which is
bigger than the one to replaced, and we can't overwrite as this
string might be in the middle).

I am curious as to the rationale behind only only appends or
overwrites, is it a technical/architectural issue.

Thanks in advance,

Nick
and we said...
Well, an overwrite is a replace, what you want is a "global
change".

Think of the lob as a big stream of characters like this:

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
                         ^

each x represents a database block. Say you wanted to modify the
1234 on the block with the caret ^ under it.  That block would
have to have all of its data pushed down by 6 bytes.  That would
push the last 6 bytes off of each subsequent block into the head
of the next block.  You are rewriting the end of the entire lob
-- from that position onwards.

You can do this with dbms_lob.copy - but it will involve
rewriting the ENTIRE end of the lob:

ops$tkyte@DEV816> create table t ( x clob );

Table created.

ops$tkyte@DEV816>
ops$tkyte@DEV816>
ops$tkyte@DEV816> create or replace
  2  procedure lob_replace( p_lob in out clob,
  3                         p_what in varchar2,
  4                         p_with in varchar2 )
  5  as
  6      n    number;
  7  begin
  8      n := dbms_lob.instr( p_lob, p_what );
  9      if ( nvl(n,0) > 0 )
 10      then
 11          dbms_lob.copy( p_lob,
 12                         p_lob,
 13                         dbms_lob.getlength(p_lob),
 14                         n+length(p_with),
 15                         n+length(p_what) );
 16
 17          dbms_lob.write( p_lob, length(p_with), n, p_with );
 18          if ( length(p_what) > length(p_with) )
 19          then
 20              dbms_lob.trim( p_lob,
 21
dbms_lob.getlength(p_lob)-(length(p_what)-length(p_with)) );
 22          end if;
 23      end if;
 24  end;
 25  /

Procedure created.

ops$tkyte@DEV816>
ops$tkyte@DEV816> declare
  2      l_lob clob;
  3  begin
  4      insert into t values ( 'Hello 1234 World' ) returning x
into l_lob;
  5      lob_replace( l_lob, '1234', '1234567890' );
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@DEV816>
ops$tkyte@DEV816> select * from t;

X
-----------------------------------------------------------------
---------------
Hello 1234567890 World

ops$tkyte@DEV816>
ops$tkyte@DEV816> begin
  2      for x in ( select * from t ) loop
  3          lob_replace( x.x, '1234567890', '1234' );
  4      end loop;
  5  end;
  6  /

PL/SQL procedure successfully completed.

ops$tkyte@DEV816> select * from t;

X
-----------------------------------------------------------------
---------------
Hello 1234 World

ops$tkyte@DEV816>
A LOB works just like an OS file does in this regards.  If you
open a file for write/append -- you can seek around the file all
over the place.  You can write random bytes anywhere in the
file.  That simply overwrites the bytes that were there.  If you
needed to change the 1234 into 1234567890 in the middle of a
file -- you have to AT LEAST rewrite the entire end of the file
to open up a 6 byte slot for the extra characters.  A lob is
just like that -- you have to rewrite the entire end of the lob.
Review & Followup

Rating: 5
Solved exactly my problem November 18, 2001
Reviewer: Marc Blum from Aachen, Germany
Problem was: how to replace a substring inside a CLOB.

Solution: just ask tom!
Rating: 3
Error doing something like this June 19, 2002
Reviewer: Bob Maggio from Troy NY USA
ORA-22920 row containing the LOB value is not locked

When I try the last part of this. I am not sure what it means as the help says
Lock the row before updating the row???
Followup:
In my example, I did an insert then then modified it.  I had the row locked
(since I inserted it and didn't commit yet)

You are probably trying this on an existing row.  You need to "SELECT ... FOR
UPDATE" that row to lock it.
Rating: 5
In 9i replace function works March 05, 2003
Reviewer: A reader
Followup:
same caveat applies -- be careful what you ask for.  it still has to "slide" the
data, it is expensive.
Rating: 4
What if it's a "small" CLOB? July 07, 2003
Reviewer: Adrian from Exeter England
If I know that my CLOB is > 4K, but < 32K would it be easier/quicker to write it
to a variable, do my replace, then write it to a new CLOB?
Followup:
when you benchmarked it, what did you discover?  :)

in 9i, you can actually just use REPLACE() right in SQL now.
Rating: 4
ORA-22994 July 15, 2003
Reviewer: Karin from Denmark
I have tried the code you mentioned above about replacing a string with a string
in clob fields. And it works on a few rows I have tested it on - but I get an
error ORA-22994 "source offset is beyond the end of the source LOB" when I run
it on the whole table. I need to replace \n with newline in 14000 rows.

Do you have any suggestions to solve this problem.
Followup:
find the row in question that is causing the problem and debug the code -- there
must be a "bug" in the routine.
Rating: 4
multiple repalces December 18, 2003
Reviewer: umesh from bangalore india
Tom
I tried this and have some problems Need your help SOS
I have a situation where multiple entries of the string needs to be replaced and
that string can be at the end
I did try my LEVEL BEST and at the end resorted to trial & error

 create or replace procedure usk_clob_replace
      ( p_lob in out clob,
        p_what varchar2,
        p_with varchar2) is
     n number;
     x number;
     dupe_i number;
     type  tabtype  is table of number index by binary_integer;
     tab1 tabtype;
     begin
    for i in 1..dbms_lob.getlength(p_lob)/2
    loop
    tab1(i)  := dbms_lob.instr( p_lob, p_what,1,i );
    exit when tab1(i) =0;
    end loop;
   x:=tab1.count ;
    dbms_output.put_line(x) ;
    for i in 1..tab1.count
    loop
    dupe_i :=i;
    if ( nvl(tab1(i) ,0) > 0 ) then
    dbms_lob.copy( p_lob,
            p_lob,dbms_lob.
            getlength(p_lob) ,
            tab1(i)+length(p_with) ,
            tab1(i)+length(p_what));
    dbms_lob.write( p_lob, length(p_with), tab1(i), p_with );
     if ( length(p_what) > length(p_with) ) then
        dbms_lob.trim( p_lob,
dbms_lob.getlength(p_lob)-(length(p_what)-length(p_with)) );
     end if;
    end if;
    end loop;
 exception
  when others then
--ORA-22994:
--source offset is beyond the end of the source LOB
 dbms_lob.writeappend(p_lob,
             length(p_with) + length(p_what) ,
             rpad( ' ', length(p_with) + length(p_what),' '));
    dbms_lob.copy( p_lob,p_lob,
            dbms_lob.getlength(p_lob) ,
            tab1(dupe_i)+length(p_with) ,
            tab1(dupe_i)+length(p_what) );
   dbms_lob.write( p_lob,
            length(p_with),
            tab1(dupe_i),
             p_with );
   end;

create table t
( x clob);

insert into t values ( 'abc abc abc');

  declare
       l_lob clob;
      begin
      select * into l_lob from t for update;
 usk_clob_replace ( l_lob,'213', 'aa');
  end;

SQL> select * from t;

X
---------------------------------------------------
aa 2aa21aa
Followup:
sorry but I cannot fathom what you are trying to accomplish here at all.

you'll need to specify in "text" what you are trying to do.
Rating: 4
replace December 19, 2003
Reviewer: umesh from blore india
Tom
what i meant with the above question is that i want to make this procedure work
just the way replace works
SQL> select replace ( 'abcda', 'a', 1) from dual;

REPLA
-----
1bcd1

like multiple times "a" is replaced with 1
Followup:
I think this does it -- test it fully, did it rather quick.

create or replace
procedure lob_replace( p_lob in out clob,
                       p_what in varchar2,
                       p_with in varchar2 )
as
    n        number;
    l_offset number := 1;
begin
loop
    n := dbms_lob.instr( p_lob, p_what, l_offset );
    if ( nvl(n,0) > 0 )
    then
        if ( (n+length(p_what)) < dbms_lob.getlength(p_lob) )
        then
           dbms_lob.copy( p_lob,
                          p_lob,
                          dbms_lob.getlength(p_lob),
                          n+length(p_with),
                          n+length(p_what) );
        end if;

        dbms_lob.write( p_lob, length(p_with), n, p_with );
        if ( length(p_what) > length(p_with) )
        then
            dbms_lob.trim( p_lob,
               dbms_lob.getlength(p_lob)-(length(p_what)-length(p_with)) );
        end if;
        l_offset := l_offset + length(p_with);
    else
        exit;
    end if;
end loop;
end;
/

ops$tkyte@ORA9IR2> insert into t values ( 'a b a c d e a' );

1 row created.

ops$tkyte@ORA9IR2> declare
  2      l_clob clob;
  3  begin
  4          select x into l_clob from t for update;
  5      lob_replace( l_clob, 'a', 1 );
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> select * from t;

X
--------------------------------------------------------------------------------
1 b 1 c d e 1

ops$tkyte@ORA9IR2> update t set x = replace( x, 1, 'aaa' );

1 row updated.

ops$tkyte@ORA9IR2> select * from t;

X
--------------------------------------------------------------------------------
aaa b aaa c d e aaa

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> delete from t;

1 row deleted.

ops$tkyte@ORA9IR2> insert into t values ( 'e a b a c d e a e' );

1 row created.

ops$tkyte@ORA9IR2> declare
  2      l_clob clob;
  3  begin
  4          select x into l_clob from t for update;
  5      lob_replace( l_clob, 'a', 1 );
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> select * from t;

X
--------------------------------------------------------------------------------
e 1 b 1 c d e 1 e

ops$tkyte@ORA9IR2> update t set x = replace( x, 1, 'aaa' );

1 row updated.

ops$tkyte@ORA9IR2> select * from t;

X
--------------------------------------------------------------------------------
e aaa b aaa c d e aaa e
December 19, 2003 S. -- Thanks for the question regarding "Using SQL Loader", version 8.1.7
You Asked
Dear Tom,
Hope you are fine.
I am facing a problem using SQL Loader.
I have a text file and I have to take thses value into table.
I have also a table consists of 2 fields name and details.
Here I give some content of that text file:
LANGUILLI
     Come on, girl-san.  Tell me how you say that
     one more time.

BIENSTOCK
     Hey, why don't you ask her how to say
     'jailbreak'...in Vietnamese?

 and so many lines like these...
Here I want to insert these text into that table.
As example LANGUILLI will insert into 'name' field and
the remain text which is not fixed length will insert into 'details' field.

There is no terminated symbol here except 'Enter' and 'Double Enter'. How I will
set terminated point in sql loader?
This text file is very large. I don't want to do manually.
I want to use sql loader.
What I can do?
Is there any solution?
Please give me solution.

with best regards,

-S. Rahman, Bangladesh.
and we said...
You will load into a "temp" staging table and use a procedure to reformat the
data.

Suppose you have:

drop table t;
create table t ( name varchar2(30), text clob );

drop table temp;
create table temp ( seqno int primary key, text varchar2(4000) )
organization index overflow tablespace users;

T is where you ultimately want the data. You can write a simple procedure:

create or replace procedure reformat
as
    l_name varchar2(80);
    l_text varchar2(32000);
begin
    for x in ( select * from temp order by seqno )
    loop
        if ( x.text = chr(10) )
        then
            null; -- skip it
        elsif ( substr( x.text, 1, 1 ) <> ' ' )
        then
            if ( l_name is not null )
            then
                insert into t values ( l_name, l_text );
            end if;
            l_name := x.text;
            l_text := null;
        else
            l_text := l_text || x.text || chr(10);
        end if;
    end loop;
    insert into t values ( l_name, l_text );
end reformat;
/

And then given a input file like T.DAT (dashes  are not part of the file):

--------------------------------------------------------NAME
  text text text text text text text
  text text text text text text text
  text text text text text text text

NAME2
  more text more text more text more text
  more text more text more text more text
  more text more text more text more text
  more text more text more text more text
-----------------------------------------------------------

and a control file like:

LOAD DATA
INFILE t.dat
INTO TABLE temp
replace
( seqno recnum,
  text position(1:1024)
)

you can load the file up, run reformat and then truncate temp.
Review & Followup

Rating: 3
SQL Loader March 08, 2002
Reviewer: S. Rahman from Bangladesh
Thank you.
Rating: 3
SQL Loader March 09, 2002
Reviewer: S. Rahman from Bangladesh
Thank you for your excellent solution.
But would you please expalin some points?
I can not understand why the following line is used in control file:
     text position(1:1024).
Why do you use 'position' here?

I did as per your instructions.
But I faced some problems.
When I sql loader data is inserted from text file to temp table.
Suppose 40 rows were inserted into temp table.
But when I execute reformat procedure only one row was inserted into
T table from temp table.

I have understood the reason.
Actually the text file is exactly as follows:
=========================================
LANGUILLI
     Come on, girl-san.  Tell me how you say that
     one more time.

                    BIENSTOCK
     Hey, why don't you ask her how to say
     'jailbreak'...in Vietnamese?

                    BELETKY
     Look at him man, who does he think he's
     kidding over there?
=============================================
Here I give the sample for 3 rows.

Would you please give me solution?
Followup:
I used posititon cause I was not loading delimited data -- I wanted to load an
entire line.  I assume your lines of text are less then or equal to 1k in
length.

Well, after looking at what gets loaded in TEMP, I simply rewrote reformat to
accomidate what your input loads:

set define off

create or replace procedure reformat
as
    l_name varchar2(80);
    l_text varchar2(32000);
begin
    for x in ( select * from temp order by seqno )
    loop
        if ( x.text is null )
        then
            if ( l_name is not null )
            then
                insert into t values ( l_name, l_text );
            end if;
            l_name := null;
            l_text := null;
        elsif ( l_name is null )
        then
            l_name := rtrim( ltrim( x.text ) );
        else
            l_text := l_text || x.text || chr(10);
        end if;
    end loop;
    insert into t values ( l_name, l_text );
end reformat;
/
set define on

should do it (did for that file).  Straightforward "programming", you can adjust
as necessary.
Rating: 3
But problem when Data loading March 09, 2002
Reviewer: S. Rahman from Bangladesh
Thank you for your good solution.
Could you please explain some points that you use in Procedure?
text position(1:1024) why do you use in  LOAD statement in the control file?

I could load data into TEMP table but when I execute
REFORMAT procedure, onely one row was inserted into T table.
The text file is exactly like the following:
(= is not the content of text file)
===============================================
LANGUILLI
     Come on, girl-san.  Tell me how you say that     one more time.
                   BIENSTOCK
     Hey, why don't you ask her how to say     'jailbreak'...in Vietnamese?
                   BELETKY
     Look at him man, who does he think he's     kidding over there?
=====================================================
The contents for 2nd row starts after the 'carriage
return' of the 1st row and so on.

Please give me solution.

With best regards,
Rating: 4
clob insertion using sqlloader from comma delimited file April 02, 2003
Reviewer: A reader
i have a comma delimited data file like :
1,"abc def efg",3,4
2,"fff degf efg",7,2
3,"xcx def yu",3,5

The fields are comma seperated. The second field is to be inserted a sclob into
the table. How do i do it using sqlloader.
Followup:
ops$tkyte@ORA920> drop table t;

Table dropped.

ops$tkyte@ORA920> create table t ( a int, b clob, c int, d int );

Table created.

ops$tkyte@ORA920> !cat t.ctl
load data
INFILE *
INTO TABLE t
APPEND
FIELDS TERMINATED BY ','
(a,b,c,d)
begindata
1,"abc def efg",3,4
2,"fff degf efg",7,2
3,"xcx def yu",3,5

ops$tkyte@ORA920> !sqlldr / t

SQL*Loader: Release 9.2.0.3.0 - Production on Wed Apr 2 12:58:06 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Commit point reached - logical record count 3

ops$tkyte@ORA920> select * from t;

         A B
                    C          D
----------
--------------------------------------------------------------------------------
---------- ----------
         1 "abc def efg"
                    3          4
         2 "fff degf efg"
                    7          2
         3 "xcx def yu"
                    3          5
Rating: 5
Assign row numbers in a detail record that will recycle for every master record? May 27, 2003
Reviewer: Fred Shimaya from Irvine, CA
Tom,

Using SQL*Loader, is there a way to assign row numbers in a detail record that
will recycle for every master record?  For example:

Input file for detail table contains:

1,TEST1
1,TESTTEST
1,MYTEST
1,YOURTEST
1,EVERYONESTEST
2,HISTEST
2,HERTEST
3,HETEST
3,SHETEST

where the first column represents a unid and the second column represents a
varchar value.

And the tables are:

create table MASTER_TABLE
(MASTER_UNID     NUMBER  NOT NULL,
 ACCOUNT_NAME      VARCHAR2(30))
/

create table DETAIL_TABLE
(MASTER_UNID     NUMBER  NOT NULL,
 LINE_NUMBER     NUMBER  NOT NULL,
 SOMETHING       VARCHAR2(10))
/

So, using the input file for the detail table, I want SQL*Loader to load the
data with the result looking like:

1,1,TEST1
1,2,TESTTEST
1,3,MYTEST
1,4,YOURTEST
1,5,EVERYONESTEST
2,1,HISTEST
2,2,HERTEST
3,1,HETEST
3,2,SHETEST

We are currently using 8i, so I hope there is a way to do this in 9i.

Thanks.

Fred
Followup:
Nope and the concept is sort of "foreign" to me.  Seems you just want to be able
to "order them".  the concept of 1, 2, 3, 4 -- just doesn't sit properly in a
relational database.

(but in 9i, with an external table and using INSERT instead of the command line
tool sqlldr, we would be able to do this with analytics and ROW_NUMBER(), but
then again, I would just use ROW_NUMBER() upon output always -- and just use an
increasing sequence)
Rating: 5
Ok... May 27, 2003
Reviewer: Fred Shimaya from Irvine, CA
Thanks.  I thought about using EXTERNAL TABLES as an alternative.  I just wanted
to ask you that question hoping that there is a way in SQL*Loader to do that.
The reason why it appears "foreign" is that we are migrating data from a network
(CODASYL) database to Oracle 9i.  To perform the unload, we are using
DBO/UNLOAD, which does not offer much in creating the sequences we need to
establish order.  In RDBMS, the order in the input file is moot when loading
data into tables.  The problem is that in CODASYL, the records all tied
internally via "sets".  You can have no keys identified to establish the order.
The order is internal.  So when the records are loaded into RDBMS, my concern
was how can we ensure that the order is maintained.  The only way to do that, it
seems, is to provide sequencing.  That's why I asked.

Your suggestion of using EXTERNAL TABLE helped reaffirm my thoughts, though.
Thanks!

Fred
Followup:
you only need a sequence then -  you need not have it "reset to one" for each
detail group -- you just need it to increase with each insert.

A sequence is all you need.

row_number() -- that implies SORTING to assign the number and you probably don't
have a sort key (else this would be moot)
Rating: 5
True... May 27, 2003
Reviewer: Fred Shimaya from Irvine, Ca
I thought about that too.  Using just sequence in the SQL*Loader.  But we have
millions of detail records.  I am wondering if a sequence reaching over several
million is a waste.  What impact, if any, does that have on performance?  Sorry
to keep bugging you about this.

Fred
Followup:
sequences have 27 digits.

It would be millions of millions of years to exhaust one at thousands of
retrievals per second.

they are the correct thing to use.  when you create it -- do this

create sequence s cache 10000;

do your load and then set the cache back down to say 100 or so.  It'll be the
fastest way to generate a highly concurrent -- yet unique -- increasing number
for you.
Rating: 5
Thank You! May 28, 2003
Reviewer: Fred Shimaya from irvine, ca
Thanks.  Your responses really help me out a lot.
Rating: 5
concatenate and continueif July 03, 2003
Reviewer: Reader
Tom, if time permits, could you please help me understanding the usage of
CONCATENATE and CONTINUIF while using sqlloader. The doc discusses about
'logical record-forming' stratgies. I am hard time understanding how this can be
done using CONCATENATE and CONTINUIF. I would appreciate a demo, if possible.
Thanks. Happy July 4th.
Followup:
concatenate is useful to take N records in a file and make them one:

suppose you have:

this is
all the
same record
this is
all the
same record

concatenate 3 would make us see

this is all the same record
this is all the same record

continueif is when you have a marked up file, like this:

 this is
+one record that spans
+three lines
 this is
+another record
+that spans
+four lines

here, continueif the first position = '+' would result in

this is one record that spans three lines
this is another record that spans four lines
Rating: 5
Thanks July 03, 2003
Reviewer: Reader
Rating: 3
referring to a non-targetted input column in the control file August 12, 2003
Reviewer: david from USA
hi, Tom.

we need to take the first column of a comma-delimited input file and perform a
calculation involving another column in the input file, but this second column
is not actually being loaded into a target column.  From everything I've read
(and I've been reading all day), I haven't been able to find a way to refer to
this input column in a delimited file unless it has a target column (ie.
:<target column>).

other than asking the user to give me a fixed width input file, or worse yet,
asking them to do the calculation themselves, or making two passes on the input,
do you know of a way to do this?

as awlays, thanks.
Followup:
see
http://asktom.oracle.com/~tkyte/SkipCols/index.html
and the delimited package for an idea.
Rating: 5
I think I get it August 13, 2003
Reviewer: david from USA
Very, very nice.
Rating: 5
had to modify it a little to do what i needed August 13, 2003
Reviewer: david from USA
combined with your 'strip_bad' and changed word to accept 2 column arguments so
that I could do the math.

Thanks so much!

       type vcArray is table of varchar2(2000) index by binary_integer;
        g_words                 vcArray;
        g_empty                 vcArray;
        g_last_string           varchar2(4096);
        g_bad_chars             varchar2(256);
        g_a_bad_char            varchar2(256);

        function de_quote( p_str in varchar2, p_enc_by in varchar2 ) return
varchar2
        is
        begin
                return replace( ltrim( rtrim( p_str, p_enc_by ), p_enc_by ),
                                 p_enc_by||p_enc_by, p_enc_by );
        end de_quote;

        procedure parse( p_str   in varchar2,
                         p_delim in varchar2,
                         p_sep   in varchar2 )
        is
                l_n                     number  default 1;
                l_in_quote      boolean default FALSE;
                l_ch            char(1);
                l_len           number default nvl(length( p_str ),0);
        begin
                if ( l_len = 0 ) then
                        return;
                end if;
                g_words := g_empty;
                g_words(1) := NULL;
                for i in 1 .. l_len loop
                        l_ch := substr( p_str, i, 1 );
                        if ( l_ch = p_delim ) then
                                l_in_quote := NOT l_in_quote;
                        end if;
                        if ( l_ch = p_sep AND NOT l_in_quote ) then
                                l_n := l_n + 1;
                                g_words(l_n) := NULL;
                        else
                                g_words(l_n) := g_words(l_n)||l_ch;
                        end if;
                end loop;
                for i in 1 .. l_n loop
                        g_words(i) := de_quote( g_words(i), p_delim );
                end loop;
        end parse;

        function word( p_str            in varchar2,
                       p_n1             in varchar2,
                       p_n2             in varchar2,
                       p_enclosed_by    in varchar2 default '''',
                       p_separated_by   in varchar2 default ',' ) return number
        is
        begin
               g_last_string := p_str;
               parse( p_str, p_enclosed_by, p_separated_by );
               --begin changes **********
               if length(g_words(p_n1)) =
length(delimited.strip_bad(g_words(p_n1))) then
                  if length(g_words(p_n2)) =
length(delimited.strip_bad(g_words(p_n2))) then
                    return (to_number(g_words(p_n1)) * 10000) +
to_number(g_words(p_n2));
                  else
                    return 0;
                  end if;
               else
                  return 0;
               end if;
               -- end changes ********
        exception
                when no_data_found then return NULL;
        end;

    function strip_bad( p_string in varchar2 ) return varchar2
    is
    begin
       return replace(
                  translate( p_string,
                             g_bad_chars,
                             g_a_bad_char ),
                  substr( g_a_bad_char, 1, 1 ),
                  '' );
    end strip_bad;

   begin
       for i in 0..255 loop
           if ( i not between ascii('0') and ascii('9') )
           then
               g_bad_chars := g_bad_chars || chr(i);
           end if;
       end loop;
       g_a_bad_char := rpad(
                         substr(g_bad_chars,1,1),
                         length(g_bad_chars),
                         substr(g_bad_chars,1,1));
   end;
Rating: 4
package vs. package body in 'delimited' August 15, 2003
Reviewer: David from USA
How is it that the package body has functions that aren't defined in the
package?  I didn't realize that was valid, but what's the reason for doing it
this way?

Thanks as always.
Followup:
just like everyone has deep dark secrets they keep bottled up, packages are
designed to let you "expose" that which you want exposed, whilst keeping private
that which is not designed to be "exposed"

(delimited btw is at
http://asktom.oracle.com/~tkyte/SkipCols/delimited.sql
)

Now in that spec, you see only:

a) a function "word"

that is the external interface, the only thing the rest of the world needs to
know about.

In the body, you see lots of stuff:

create or replace package body delimited
as
        type vcArray is table of varchar2(2000) index by binary_integer;

        g_words                 vcArray;
        g_empty                 vcArray;
        g_last_string   varchar2(4096);

there is a private type.  I wanted an array.  No else on the planet needs or
wants to know about this internal datatype.  Additionally -- i have a state (so
we can parse a line of text onces and use it over and over and over).  This
state is private as well -- not exposed.  No one but my package body can
read/write this state...

        function de_quote( p_str in varchar2, p_enc_by in varchar2 ) return
varchar2
        is
        begin
                return replace( ltrim( rtrim( p_str, p_enc_by ), p_enc_by ),
                                 p_enc_by||p_enc_by, p_enc_by );
        end de_quote;

there is a private function -- de_quote.  I use it internally.  I chose NOT to
expose it.  I don't have to document it, I don't have to support it for other
usages.  It does what I want it to do, period.  Could it have been in a support
"utility" package -- sure.  But it is small and for self containment, I put it
here.

Then there is procedure parse.  That is really specific to this package.  It is
private code that no one else need know about or call.  They could only get in
trouble if they did.  Hence it is hidden, visible ONLY to code in the body
itself.

It is a method of encapsulation.  I put lots of private functions/procedures in
my body so my code remains modular (term dates me doesn't it...), easy to
understand, bite sized.  I don't clutter up the interface, the spec, with stuff
that is specific to my package.
Rating: 3
How To load following data September 15, 2003
Reviewer: Vikas Sharma from Delhi India
Hi Tom,

I would like to load the following data from the txt file.

=========================

first rec >>HILLAMD   980321    8/2/2002  00:00:00 {Resign
due change in roster oc:Richard Thomas King

Roster was changed a fortnight ago, which resulted in his being unable to meet
early turn start.}

next rec >> ALLEND    7755553   8/2/2002  00:00:00  {RAIL EUROPE - WEST
MALLING!! (oc:Heather Stoate)
Heather was going to be
How good is that?!

cc: SCOTTK, ALLEND}
third_record starts here >>
.....
===============

The braces are just to show that it is one col and this column has carrage
return also.

I can use the fix position for this but the problem is how can i tell loader the
for the end of record.

Like the last column i have three more column in the data file.

There is any way to load the data stored in this format.

Many thanks in advance.

Regards,
Vikas Sharma
Followup:
show me the "real" file -- i'm sure there isn't a "first rec >>" / "next rec >>"

if the braces are there -- that would be good.

tell me, how would YOU know the end of record was found?
Rating: 2
RE- How to... September 15, 2003
Reviewer: Vikas Sharma from Delhi India
Hi Tom,

You are right. There is no "first rec >>"  i had put it to make it clear to you.
And same there are no braces i had put that to make things clear. Sorry, for the
confusion.

I dont know where is the end of the record as thre is no termination of the
record ,can i take the maximun width of the one line, NOT record, which is 774
charecters.

I am putting the first record from the datafile which i have. it may be not
clear but any way i am put there.But if you want i can email it to you (some
part 10kb or so, as it is large) if you give me the email address.

Here is the first record and the second rec =============.

SMITHJ                      MMG        19/6/1998 00:00:00 14:15       1        0
      membership clean up                                              Someone
emailed me at 1pm today re the above but although it appears in my callender I
get an error when I try to read it so I don't know who it was from or what it
said (obviously).

Do you know who you are ?

Oh and by the way to anyone that doesn't work on the third floor and hasnt herd
this a thousand times already - I am on holiday for the next two weeks.

Bye Bye

cc: SMITHJ, +RECRUIT, ALLEND, ALLENJ, ANDERSON, ASHFORDD, CAMEROND, CANAVANP,
CHALKLEY, CHAPMANP, CHESHERA, DAVIESN, DICKR, DOHERTY
/K-A]$%%E9E= 2SMITHJ  19/6/1998 00:00:00 14:09SMITHJ  19/6/1998 00:00:00
14:1864VJ28Z(>0<+= 2

WARDF                       MMG        22/6/1998 00:00:00 12:39       1        0
      STAFF REPRESENTATIVES MEETING                                    Can you
please advise when you are available on the following days so that I can arrange
a meeting:-

week commencing 27 July - 31 July
3 August
4 August
5 August
10 August
11 August
13 August
14 August

Proposed agenda is as follows:-

1.    IiP - progress report
    1.1    Progress report (Management Side)
    1.2    Personal development reviews (questions and comments) concerning the
procedure from         Staff Side
2.    Business Plan (Management Side)
3.    Media Monitoring (Management Side) (Nancy Platts to be
/Z3H-S%D]MY-71ORMESJ  22/6/1998 00:00:00 12:05WARDF   22/6/1998 00:00:00
12:396532GYG&&MZX-71

==============
Every reocrd start with a name in uppercase as above first recrod start with
SMITHJ next record with WARDF
Hope i am clear this time.

Thanks & Regards

Vikas Sharma
Followup:
are the records separated by anything a COMPUTER can understand?  like "two
newlines" or something?  anything -- anything at all?
Rating: 2
RE - How To September 15, 2003
Reviewer: Vikas Sharma from Delhi India
Hi Tom,

The record are not seprated by any thing. I think it may not be possible to make
you clear here. May be possible if i can mail you some part of the datafile.

Sorry to trouble you again.

Thanks,

Vikas
Followup:
lets put it this way -- if you could not describe in text how to parse the file
definitively, we cannot codify it.

it looks like free form text with no way to tell a computer "this is record 1",
"this is record 2"
Rating: 4
how to trim the spaces from flat file. September 16, 2003
Reviewer: A reader
Tom,

I wanted to know this from you,

I have a variable-length delimted file,delimiter(|).I am able to read all the
fields except the last one, i mean its failing there at the last field in the
flat file. because the last field has to be only 10 character(table has the
field column with date(YYYY-MM-DD), but because of spaces, sqlloader reads it as
10+spaces as number of character and its failing with the following error
message

Record 45: Rejected - Error on table TB_MWP020_CPMIDS_STAGE, column
ORDER_CREATION_DT.
Field in data file exceeds maximum length

CTL file..

LOAD DATA
   APPEND
   INTO TABLE TB_MWP020_CPMIDS_STAGE
       FIELDS TERMINATED BY "|"
    trailing NULLCOLS
    (
        PROJECT_ID           CHAR(30) "LTRIM(RTRIM(:PROJECT_ID))",
        REQUISITION_NUM_DSC  CHAR(8) "LTRIM(RTRIM(:PROPOSAL_NUM_DSC))",
.
.
.
.
.
        ORDER_CREATION_DT    DATE 'YYYY-MM-DD'"LTRIM(RTRIM(:ORDER_CREATION_DT))"
    )

Please help me in resolving this ...

Thanks for you time and consideration
Followup:
you'll have to give me a "for example" set of data.

i cannot reproduce the issue as described (with 9iR2 anyway).

I can suggest you try

ORDER_CREATION_DT  "to_date( LTRIM(RTRIM(:ORDER_CREATION_DT)), 'yyyy-mm-dd' )"

that is, don't have sqlldr do the date, have sql do the date.
Rating: 4
Sample data and ctl file September 16, 2003
Reviewer: A reader
BIG HORN|TD100334|10601751000|3420701|GEORGIA POWER|REQUEST DATE
TEST1|120.00|2002-07-25|N|34207TD|103013733|103013809|103013733||351|106|A|GEORGI
A POWER|DISBURSEMENT ACCTG|PO BOX 3115|ATLANTA
GA||ATLANTA|GA|30302|US|PP|FOB|GEORGIA POWER|DISBURSEMENT ACCTG|PO BOX
3115|ATLANTA                       GA||ATLANTA|GA|30302|US|09||17|2002-07-25

BIG HORN|TD100335|10601751000|3420701|GEORGIA POWER|REQUEST DATE
TEST2|240.00|2002-07-25|N|34207TD|103013733|103013809|103013733||351|106|A|GEORGI
A POWER|DISBURSEMENT ACCTG|PO BOX 3115|ATLANTA
GA||ATLANTA|GA|30302|US|PP|FOB|PACIFICORP ELEC|213 EAST 3RD STREET||LOVELL
                 WY||LOVELL|WY|82431|US||||2002-07-25

ctl file:

LOAD DATA
   APPEND
   INTO TABLE TB_MWP020_CPMIDS_STAGE
       FIELDS TERMINATED BY "|"
    trailing NULLCOLS
    (
        PROJECT_ID              CHAR(30) "LTRIM(RTRIM(:PROJECT_ID))",
        REQUISITION_NUM_DSC     CHAR(8) "LTRIM(RTRIM(:REQUISITION_NUM_DSC))",
        PROPOSAL_NUM_DSC        CHAR(12) "LTRIM(RTRIM(:PROPOSAL_NUM_DSC))",
        CUSTOMER_INDEX_NUM_DSC  CHAR(9) "LTRIM(RTRIM(:CUSTOMER_INDEX_NUM_DSC))",
        CUSTOMER_NAM            CHAR(15) "LTRIM(RTRIM(:CUSTOMER_NAM))",
        CUSTOMER_ORDER_ID_DSC   CHAR(30) "LTRIM(RTRIM(:CUSTOMER_ORDER_ID_DSC))",
        TOTAL_ORDER_VAL_AMT     integer external,
        CUST_ORDER_DT           DATE 'YYYY-MM-DD'"LTRIM(RTRIM(:CUST_ORDER_DT))",
        PROGRESS_PAY_IND_CD     CHAR(1) "LTRIM(RTRIM(:PROGRESS_PAY_IND_CD))",
        CHARGE_TO_NUM_DSC       CHAR(9) "LTRIM(RTRIM(:CHARGE_TO_NUM_DSC))",
        PROJECT_LEAD_NAM        CHAR(9) "LTRIM(RTRIM(:PROJECT_LEAD_NAM))",
        SALES_ENGG_DSC          CHAR(9) "LTRIM(RTRIM(:SALES_ENGG_DSC))",
        TD_SPECIALIST_DSC       CHAR(9) "LTRIM(RTRIM(:TD_SPECIALIST_DSC))",
        SE_USER_NUM_DSC         CHAR(9) "LTRIM(RTRIM(:SE_USER_NUM_DSC))",
        OFFICE_TAKING_CD        CHAR(10) "LTRIM(RTRIM(:OFFICE_TAKING_CD))",
        DISTRICT_CD             CHAR(3) "LTRIM(RTRIM(:DISTRICT_CD))",
        PAYMENT_TERMS_CD        CHAR(2) "LTRIM(RTRIM(:PAYMENT_TERMS_CD))",
        BILL_TO_CUST_NAM        CHAR(32) "LTRIM(RTRIM(:BILL_TO_CUST_NAM))",
        BILL_TO_ADDRESS_LN_1    CHAR(32) "LTRIM(RTRIM(:BILL_TO_ADDRESS_LN_1))",
        BILL_TO_ADDRESS_LN_2    CHAR(32) "LTRIM(RTRIM(:BILL_TO_ADDRESS_LN_2))",
        BILL_TO_ADDRESS_LN_3    CHAR(32) "LTRIM(RTRIM(:BILL_TO_ADDRESS_LN_3))",
        BILL_TO_ADDRESS_LN_4    CHAR(32) "LTRIM(RTRIM(:BILL_TO_ADDRESS_LN_4))",
        BILL_TO_CITY_NAM        CHAR(28) "LTRIM(RTRIM(:BILL_TO_CITY_NAM))",
        BILL_TO_STATE_NAM       CHAR(2) "LTRIM(RTRIM(:BILL_TO_STATE_NAM))",
        BILL_TO_ZIP_CD          CHAR(9) "LTRIM(RTRIM(:BILL_TO_ZIP_CD))",
        BILL_TO_COUNTRY_NAM     CHAR(2) "LTRIM(RTRIM(:BILL_TO_COUNTRY_NAM))",
        SHP_PMT_METH_CD         CHAR(2) "LTRIM(RTRIM(:SHP_PMT_METH_CD))",
        TRANS_TERM_CD           CHAR(3) "LTRIM(RTRIM(:TRANS_TERM_CD))",
        SHIP_TO_CUST_NAM        CHAR(32) "LTRIM(RTRIM(:SHIP_TO_CUST_NAM))",
        SHIP_TO_ADDRESS_LN_1    CHAR(32) "LTRIM(RTRIM(:SHIP_TO_ADDRESS_LN_1))",
        SHIP_TO_ADDRESS_LN_2    CHAR(32) "LTRIM(RTRIM(:SHIP_TO_ADDRESS_LN_2))",
        SHIP_TO_ADDRESS_LN_3    CHAR(32) "LTRIM(RTRIM(:SHIP_TO_ADDRESS_LN_3))",
        SHIP_TO_ADDRESS_LN_4    CHAR(32) "LTRIM(RTRIM(:SHIP_TO_ADDRESS_LN_4))",
        SHIP_TO_CITY_NAM        CHAR(28) "LTRIM(RTRIM(:SHIP_TO_CITY_NAM))",
        SHIP_TO_STATE_NAM       CHAR(2) "LTRIM(RTRIM(:SHIP_TO_STATE_NAM))",
        SHIP_TO_ZIP_CD          CHAR(9) "LTRIM(RTRIM(:SHIP_TO_ZIP_CD))",
        SHIP_TO_COUNTRY_NAM     CHAR(2) "LTRIM(RTRIM(:SHIP_TO_COUNTRY_NAM))",
        DESTINATION_CD          CHAR(10) "LTRIM(RTRIM(:DESTINATION_CD))",
        TAX_EXEMPT_FLAG_DSC     CHAR(1) "LTRIM(RTRIM(:TAX_EXEMPT_FLAG_DSC))",
        TAX_CD                  CHAR(3) "LTRIM(RTRIM(:TAX_CD))",
        ORDER_CREATION_DT  "to_date( LTRIM(RTRIM(:ORDER_CREATION_DT)),
'yyyy-mm-dd' )"

    )

Please see, the version of oracle is 8.1.7.

The same problem i have even with other datatype, i mean not only date, if i
have character or number also.

Thank you so much for your prompt reply
Followup:
ok, try

ORDER_CREATION_DT  char(4000) "to_date( LTRIM(RTRIM(:ORDER_CREATION_DT)),
'yyyy-mm-dd' )"

looks like the whitespace could exceed 255 characters -- the default character
buffer size in sqlldr.  just make the default buffer "much larger"
Rating: 4
September 16, 2003
Reviewer: A reader
Tom,
thanks for your prompt replies..

Sorry, even this time it throws the error.

also how to set the buffer size for sqlldr?/

pls let me know
thanks
Followup:
when I did it without char(4000), it failed.

when I did it with char(4000), it succeeded.

using:

        TAX_EXEMPT_FLAG_DSC     CHAR(1) "LTRIM(RTRIM(:TAX_EXEMPT_FLAG_DSC))",
        TAX_CD                  CHAR(3) "LTRIM(RTRIM(:TAX_CD))",
        ORDER_CREATION_DT char(4000) "to_date(
LTRIM(RTRIM(:ORDER_CREATION_DT)), 'yyyy-mm-dd' )"
    )

corrects it.  verify that you are doing that. (and that 4000 is in fact "long
enough" given your data.
Rating: 5
Great solution .. Thanks Thanks Thanks .... September 16, 2003
Reviewer: A reader
Tom,

Thank you so much. I have no words to express my satisfaction.

You are simply great.

Thanks
Rating: 2
Import from Access September 19, 2003
Reviewer: Vivek from UK
Tom,

I need to import a table data from access. I have converted the data into text
file and written ctl file to load it. Problem i am facing is one of the field is
of "free text" nature and has got carriage returns and line feed in it.
When i run loader for text file with CR and LF i got strange result
MY sample text file is
---------------
2,"Aberdaron","03405","7714","529316",,"Weather Station (10m lattice tower),
Garreg Fawr, Aberdaron, Pwllehli,
Gwynedd",0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,"1993",,"Lattice ( unknown
make)",10,"Unknown",95,"UK","SH","2","152","3","248",52,47,21,"N",4,44,31,"W","Gw
ilym Roberts","01758 760357",,"01758 760679",,,,"South

Ht 94mtr
3m SW of Aberdaron

 (SAWS Line PW71408)

Distance from Aberporth 140m.  4.0 Hours

Directions:
From Pwllheli on A497, A499.
At Llanbedrog turn R onto B4413, on edge of Aberdaron take R turn by telephone
box, towards Uwchmynydd.
Past crossroads then into valley and over bridge - uphill to staggered
crossroads - straight on (not left towards Aberdaron)
After 300 mr turn L & after 1km  take second of two close L turns
In 300 mr turn R to farmyard - L through gate up steep track to the site.

Site is immediately W of Garreg Fawr Farm about 4km W of Aberdaron.

Alternativly turn R after narrow bridge in Aberdaron, 1m over hill turn L at
crossroads, after 300m turn L etc.

Anemometer is on a 10m climbable mast.
There are two signal junction boxes in the TB10 position (Pressure sensor box)

The BT wiring is taken from its in/out box via the second box to the
SAWS.","14/04/106","ON100F",,1161,0

-------2ND RECORD----------------

38,"Barra","03035","0510","738179","EGPR","Barra Airport, Isle of Barra, Western
Isles,
HS9",0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,"1987",,"WEC",10,"Client/Others",3,"UK","NF","
0","694","8","060",57,1,59,"N",7,27,0,"W","Mrs Janet
MacLean","01871-890-283",,,,,,"Scotland

NOTE:  These notes refer to the old Barra Aux station before the SAMOS
installation.  It has been assumed that the SAMOS is in the same location and
that the AUX station is now discontinued.

Contact (LoganAir) for free tickets
       0141-889-1311 Xtn 237

Grid Ref NF 694 060  57 02'N  7 27'W  Station Ht 3m/10ft
Mag Variation 9.5 W in 1993
Contact Capt Hauzzard 041-889-1311 x237 To Authorise Free Flight Travel for 6
Monthly Routines.
Depending on timetables  it may not be possible to make a day visit. There are 2
hotels in Castlebay. A taxi fare if no lift available is approx £8 single. If
the weather proves unsuitable on the day LoganAir are usually quite happy to
rebook you for another day.

Wind Mast is a sectional guyed pole which requires the assistance of Aiport Fire
Service to lower. Advance courtesy call of ETA helps to prepare them!  Serco
report this mast to be a 10m WEC tower in Jan 2003.

Loganair usually supply you with cups of tea but minor beverages and biscuits
etc are available in the small departure hall in the same building.

Set of wind dials are on the office wall. The 50V XFMR and substitution box are
in the porch nearest to the mast.
MAGNETIC VARIATION +9.5 DEGREE IN 1993 from aviation map.
Calculated at 9.0 in 1994 for compass use.

Barometer 3 mtr  Runway 0 mtr!",,"ON100F",,82,0
--------------------------------------------------
and log for this is

Record 1: Rejected - Error on table SITE, column NOTES.
second enclosure string not present
Record 2: Discarded - all columns null.
Record 5: Discarded - all columns null.
Record 7: Discarded - all columns null.
Record 4: Rejected - Error on table SITE.
ORA-01401: inserted value too large for column

Record 6: Rejected - Error on table SITE.
ORA-01401: inserted value too large for column
...
Record 48: Rejected - Error on table SITE.
ORA-01401: inserted value too large for column

Table SITE:
  2 Rows successfully loaded.
  29 Rows not loaded due to data errors.
  0 Rows not loaded because all WHEN clauses were failed.
  17 Rows not loaded because all fields were null.
----------------------------------------------
But when i run without that field its fine
and log is like
----------
Table SITE:
  957 Rows successfully loaded.
  0 Rows not loaded due to data errors.
  0 Rows not loaded because all WHEN clauses were failed.
  0 Rows not loaded because all fields were null.
-------------------

How can i handle this field data with in SQLLOADER.

Thanks

Vivek
Followup:
well, first, you don't need to export it at all, you can just:

insert into oracle_table select * from access_table@access_database;
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4406709207206
but unless you can have access put an END OF RECORD marker, there is no way to
tell when the "end of record" is encountered.  so the default end of record
marker is a newline -- hence it won't work.

if you could make each line end with some special character, you can use the STR
directive

search for

str sqlldr

on this site for details.
Rating: 2
just to know how a column can be auto increase using sequence number December 19, 2003
Reviewer: vipin
I am looking for information how we can use sequence nect val for a column in
the control file itself.....

any help.........
Followup:
ops$tkyte@ORA9IR2> create table t ( id int, a int, b varchar2(15), c int, d int
);

Table created.

ops$tkyte@ORA9IR2> create sequence S;

Sequence created.

ops$tkyte@ORA9IR2> !cat t.ctl
load data
INFILE *
INTO TABLE t
APPEND
FIELDS TERMINATED BY ',' trailing nullcols
(a,b,c,d, ID "s.nextval")
begindata
1,abc def efg,3,4
2,fff degf efg,7,2

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> !sqlldr / t

SQL*Loader: Release 9.2.0.4.0 - Production on Fri Dec 19 07:24:18 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Commit point reached - logical record count 2

ops$tkyte@ORA9IR2> select * from t;

        ID          A B                        C          D
---------- ---------- --------------- ---------- ----------
         1          1 abc def efg              3          4
         2          2 fff degf efg             7          2
December 19, 2003 Lubos -- Thanks for the question regarding "How communicates an SQL client with Oracle Database", version 8.1.5
You Asked
How communicates an SQL client with Oracle Database (Server). I mean, for
example SQL WorkSheet or SQL Plus. Communicates such client via ODBC, or is
there some other native, or direct communication, which is more powerfull?
and we said...
ODBC is just an API.

OCI is a lower level API we provide that ODBC sits on top of.

Below OCI is upi, the lowest level call interface.  It rides on top of the
network to do the real work.

sqlplus and such would use OCI.  MS tools would use odbc.  We would not use
ODBC, which is a layer on a layer to talk to Oracle, it adds layers of
unneccesary code.
Review & Followup

Rating: 4
December 02, 2002
Reviewer: A reader
Tom, where can I look to find out about UPI? The net80 book?
Followup:
nowhere, it is internal, undocumented and not of any interest to you or me.
Rating: 3
Thank You December 03, 2002
Reviewer: Lubos Kucera from Praha Czech republic
Dear Tom,
Thank you for your response. It is helpfull.

Thanks Lubos
Rating: 4
ODBC adds unnecessary layer of code,,, How to tune that August 06, 2003
Reviewer: Kumar from San Diego
I have a well tuned application runs on Oracle 8i and 9i with VB as front end
connects the DB through RDO connections. ie uses Oracle ODBC drivers. Well in my
recent tuning of some expensive SQL statements that I captured in v$Sql view
hold my breath. After a thorough investigations I found those are the SQL always
being generated when that application connects the DB and believe me those SQL
statements are consuming a lot amount of

Sharable Memory = 303180
persistemt_memory = 1548
runtime_memory=78172
sorts=2
buffer_gets = 37059

But when I check the entire SQL statements the top 50 or 100 statements of such
(below ) consuming a lot resources. How can I avoid such time taking statements
run in the Shared-pool and is there any way to by pass this kind of SQL
generation or any suggestions to my situations. I am presenting below few of
such statements that showing more resoures consumable but the final rows process
are only 17 or 2. I have also planned at one time either I should create on any
additional index on sys base tables accrodig to these SQL statements ..but
couldn't gain anything.

SQL statements found in v$SQL:-
================================

SELECT '', a.owner,       decode (b.object_type,            'PACKAGE', CONCAT(
CONCAT (b.object_name, '.'), a.object_name),
b.object_name),decode(a.position, 0, 'RETURN_VALUE', a.argument_name),
decode(a.position, 0, 5,       decode(a.in_out, 'IN', 1, 'IN/OUT', 2, 'OUT',
4)),       0, a.data_type, a.data_precision, a.data_length,       a.data_scale,
a.radix, 2, ''       FROM ALL_ARGUMENTS a, ALL_OBJECTS b WHERE            (
      b.object_type = 'PROCEDURE' OR            b.object_type = 'FUNCTION'
    )       AND b.object_id = a.object_id       AND a.data_level = 0       AND
a.OBJECT_NAME LIKE 'EP_UPDATE_SERVICE_DELIVERY'        ORDER BY a.position

SELECT '', a.owner,       decode (b.object_type,            'PACKAGE', CONCAT(
CONCAT (b.object_name, '.'), a.object_name),
b.object_name),decode(a.position, 0, 'RETURN_VALUE', a.argument_name),
decode(a.position, 0, 5,       decode(a.in_out, 'IN', 1, 'IN/OUT', 2, 'OUT',
4)),       0, a.data_type, a.data_precision, a.data_length,       a.data_scale,
a.radix, 2, ''       FROM ALL_ARGUMENTS a, ALL_OBJECTS b WHERE            (
      b.object_type = 'PROCEDURE' OR            b.object_type = 'FUNCTION'
    )       AND b.object_id = a.object_id       AND a.data_level = 0       AND
a.OBJECT_NAME LIKE 'EF_PHONE'        ORDER BY a.position

Appreciate any suggestions or any clues "How to avoid to generate such expesive
SQL for the current situation of my application which uses Oracle ODBC" Due to
this expensive SQL my initial connections take a bit longer and when I ran this
query as separate then I figured it out these statements are the main culprits
to run at least sometimes 25 seconds run time to produce result sets.

Thanks
Followup:
are you using the 9203 drivers?  I see a performance issue lodged against them
for this.
Rating: 4
ODBC adds unnecessary layer of code August 11, 2003
Reviewer: Kumar from San Diego
Tom,

My product runs on three different versions of Oracle successfully as of today
and those are:-

Oracle DB Version              Oracle ODBC client
===================            ===================
8.1.5.0 No patch                  8.1.5.5 ODBC release

8.1.6.0 Release 2                 8.1.6.3.4 ODBC Release

9.i Relese 2 with patch           9.02.00.00 ODBC Release.

I presented those above SQL statements were generating in all the versions of
databases commonly and they are cosuming a lot memory too. I am thankful for
your reply and watching these postings
9.2.0.2.1
Rating: 4
version of ODBC December 19, 2003
Reviewer: Seemakiran Upadhya from India, Bangalore
Tom,

How did you find out from the SQL statements in v$SQL that the version of the
ODBC drivers used was 9203?
Followup:
i queried the bug database on metalink with the query provided, saw an issue
logged against it.
December 19, 2003 Les -- Thanks for the question regarding "Selecting from user_role_privs", version 8.1.5
You Asked
Good day Tom,

I have been trying to build a function for fine grained
security,
and am having a stop of trouble.

The basic idea is as follows:

1. I have created two roles, one called ALL_ACCESS the other is
called PARENT_ACCESS. These roles have the alter session and
create
session privledges (the get-by minimum for logging in).

2. I created 2 users, user1 has ALL_ACCESS, and user2 has
PARENT_ACCESS
roles assigned respectivly.

3. My function does a select count(*) from user_role_privs
where granted_role = 'ALL_ACCESS'. The idea being that the
predicate returned will be determined by the role assigned.

My spot of trouble is when the function executes, to return a
predicate
the count(*) of granted_role from user_role_privs is always 0
(zero).

When I execute the same select (without the into var_name of
course)
from a SQL*PLUS session, the count returned is always the
number of roles assigned (I have logged on as both user1 and
user2
to be sure that the select statement is correct).

It seems that for some reason (hopefully you will know what that

reason is ) that the function cannot select the count(*) from
user_role_privs.

My question is how do I get that function to return the number
of roles for the user?

Here is one of the select statements from the function:

        select count(*) into assignedRole
           from user_role_privs
          where username = sys_context('userenv','session_user')
            and granted_role = 'ALL_ACCESS';

I granted execute to public for the package.

I know the function is executing, becuase I put some debug
statements in it, that indicate it is executing (I used that
debug package by Christopher Beck that I got from one of your
articles on fine grained security.

I just know there is some silly thing I am doing (or not),
I just do not seem to be able to figure out what that could be.

Many thanks in advance Tom.
and we said...
Well, I've a couple of things to say for this one....

One is, if you haven't, you might want to read:
http://osi.oracle.com/~tkyte/article2/index.html
That is a paper I've written on fine grained access control.
One of the topics I have in there is "Handling Errors and
debugging".  In the debugging part (which is really hard with
FGAC), I suggest using a "debug.f" routine we have.  It lets you
easily create trace files from PLSQL.  I'm going to use it to
demonstrate to you what is happening.

In a nutshell -- user_* views, when queried in PLSQL, always
return the rows for the user who created the procedure
(unless they are invokers rights but thats another story).  So,
by querying user_* views, you've effectively fixed the rows that
can be returned at compile time.  You need to query the
DBA_* views in order to see anyones privs.  In fact, since the
dba_user_privs view performs pretty poorly, I'm going to suggest
going directly after the underlying tables in this case (a
rarity).

Lets start by writing a policy that simply dumps the
user_roles_privs view, the dba_roles_privs for the current user
and the modified query to get the roles.  We'll time them as
well. That policy is:

ops$tkyte@DEV8I.WORLD> create or replace
  2  function my_security_function( p_schema in varchar2,
  3                                 p_object in varchar2 )
  4  return varchar2
  5  as
  6      l_start number;
  7  begin
  8      debug.f( 'User Role Privs' );
  9      l_start := dbms_utility.get_time;
 10      for x in ( select * from user_role_privs )
 11      loop
 12          debug.f( 'Username = "%s" Granted_Role = "%s"',
 13                    x.username, x.granted_role );
 14      end loop;
 15      debug.f( 'end, time = %d',
 16          round( (dbms_utility.get_time-l_start)/100, 2 ) );
 17
 18      debug.f( 'Dba Role Privs' );
 19      l_start := dbms_utility.get_time;
 20      for x in ( select *
 21                   from dba_role_privs
 22                   where grantee =
 23                     sys_context('userenv','session_user') )
 24      loop
 25          debug.f( 'Username = "%s" Granted_Role = "%s"',
 26                    x.grantee, x.granted_role );
 27      end loop;
 28      debug.f( 'end, time = %d',
 29          round( (dbms_utility.get_time-l_start)/100, 2 ) );
 30
 31      debug.f( 'Direct query' );
 32      l_start := dbms_utility.get_time;
 33      for x in ( select u1.name granted_role,u2.name grantee
 34                   from sys.user$ u1,
 35                        sys.user$ u2,
 36                        sys.sysauth$ sa
 37                  where u1.user# = sa.privilege#
 38                    and u2.user# = sa.grantee#
 39                    and u2.name =
 40                    sys_context('userenv','session_user') )
 41      loop
 42          debug.f( 'Username = "%s" Granted_Role = "%s"',
 43                    x.grantee, x.granted_role );
 44      end loop;
 45      debug.f( 'end, time = %d',
 46          round( (dbms_utility.get_time-l_start)/100, 2 ) );
 47
 48
 49      return '';
 50  end;
 51  /

Function created.

Now, lets create a table and attach this policy on it:

ops$tkyte@DEV8I.WORLD> drop table t;
Table dropped.

ops$tkyte@DEV8I.WORLD> create table t ( x int );
Table created.

ops$tkyte@DEV8I.WORLD> grant all on t to public;
Grant succeeded.

ops$tkyte@DEV8I.WORLD> begin
  2  dbms_rls.add_policy
  3  ( object_schema   => user,
  4    object_name        => 'T',
  5    policy_name        => 'MY_POLICY',
  6    function_schema => user,
  7    policy_function => 'My_Security_Function',
  8    statement_types => 'select, insert, update, delete' ,
  9    update_check       => TRUE );
 10  end;
 11  /

PL/SQL procedure successfully completed.

Now, lets set up to "trace".  I'll enable tracing for all
modules to the file /tmp/tkyte.dbg for the current user (me)

ops$tkyte@DEV8I.WORLD> host rm /tmp/tkyte.dbg
ops$tkyte@DEV8I.WORLD> exec debug.init( 'all', '/tmp/tkyte.dbg',
USER );

PL/SQL procedure successfully completed.

ops$tkyte@DEV8I.WORLD> select * from t;

no rows selected

Now, lets see what we got:

ops$tkyte@DEV8I.WORLD> host cat /tmp/tkyte.dbg

User Role Privs
Username = "OPS$TKYTE" Granted_Role = "CONNECT"
Username = "OPS$TKYTE" Granted_Role = "DBA"
Username = "PUBLIC" Granted_Role = "IAP_USER"
Username = "PUBLIC" Granted_Role = "PLUSTRACE"
Username = "PUBLIC" Granted_Role = "SURVEY_USER"
Username = "PUBLIC" Granted_Role = "WWV_PALM_SYNC_ROLE"
end, time = 2.29

Dba Role Privs
Username = "OPS$TKYTE" Granted_Role = "CONNECT"
Username = "OPS$TKYTE" Granted_Role = "DBA"
end, time = 1.9

Direct query
Username = "OPS$TKYTE" Granted_Role = "CONNECT"
Username = "OPS$TKYTE" Granted_Role = "DBA"
end, time = .01

That looks OK so far -- user_role_privs and dba_role_privs
agree with each other so far.  But thats ONLY because the
currently logged in user = the owner of the procedure.

I would like to point out that in my database I have over 30k
users created.  The performance of the *_role_privs view is not
very good in my case.  It takes far too long to get the answer.
The last query, directly against the SYS tables is very very
fast however...

Now, lets try this as another user and you'll see where you went
wrong...

ops$tkyte@DEV8I.WORLD> connect scott/tiger
Connected.
ops$tkyte@DEV8I.WORLD> @login

scott@DEV8I.WORLD> host rm /tmp/scott.dbg

scott@DEV8I.WORLD> exec debug.init( 'all', '/tmp/scott.dbg',
USER );

PL/SQL procedure successfully completed.

scott@DEV8I.WORLD> select * from ops$tkyte.t;

no rows selected

scott@DEV8I.WORLD> host cat /tmp/scott.dbg

User Role Privs
Username = "OPS$TKYTE" Granted_Role = "CONNECT"
Username = "OPS$TKYTE" Granted_Role = "DBA"
Username = "PUBLIC" Granted_Role = "IAP_USER"
Username = "PUBLIC" Granted_Role = "PLUSTRACE"
Username = "PUBLIC" Granted_Role = "SURVEY_USER"
Username = "PUBLIC" Granted_Role = "WWV_PALM_SYNC_ROLE"
end, time = 2.33

Notice how the username above is still OPS$TKYTE -- thats
cause the USER_* view in a procedure will return the data for
the owner of the procedure (except with invokers rights..)

Dba Role Privs
Username = "SCOTT" Granted_Role = "CONNECT"
Username = "SCOTT" Granted_Role = "DBA"
Username = "SCOTT" Granted_Role = "RESOURCE"
end, time = 1.89

But dba role privs gives us the correct info (what we wanted)
albeit slowly...

Direct query
Username = "SCOTT" Granted_Role = "CONNECT"
Username = "SCOTT" Granted_Role = "RESOURCE"
Username = "SCOTT" Granted_Role = "DBA"
end, time = .03

The direct query gives us what we want but fast...

scott@DEV8I.WORLD>
Review & Followup

Rating: 3
Difference between sys_context('userenv','session_user') & user April 17, 2003
Reviewer: Tony
Tom,

Will the below variables always have the same value ?

l_user1 := sys_context('userenv','session_user');
l_user2 := user;

Thanks !
Followup:
suppose you create a trigger like this:

drop table log;
drop trigger ddl_trigger;
create table log
( uname varchar2(30),
  session_user varchar2(30)
)
/

create or replace trigger ddl_trigger
before create or alter or drop
on database
declare
begin
        insert into log
        ( uname, session_user )
        values
        ( user, sys_context('userenv','session_user') );
end;
/

and do a full database import into that database.  you'll discover USER is sys
and sys_context('session_user') is a bunch of different names.

full imports use the "become user" priv to switch identities mid stream.

don't know of any "normal" cases (including ntier proxy) that would do this tho
Rating: 2
April 17, 2003
Reviewer: Tony
So what should I use if I want a procedure to insert the user name in a table
for the rows created ?

Say if I connect as scott to the database, will the above assignments anytime
give me different values ? If yes, when ???
Followup:
(it is best to ask the question you meant, rather then something else -- you get
 your answers faster that way)

does scott have become user?  does scott do a full database import?  does your
trigger exist under such circumstances?

I would just default the column to USER, create a view that selects all but that
column -- have the applications use that view.
Rating: 5
Security? December 19, 2003
Reviewer: Tom from UK
Tom,

Are there any security issues we should be aware of if we create a view based on
your query and grant this to the role used by users connecting through the web.
The grants I need to be able to do this are

grant select on sysauth$ to <code owner> with grant option;
grant select on user$ to <code owner> with grant option;

I assume that granting select on these tables is fine as long as we don't grant
update, insert or delete!
Followup:
correct.

"security issues" are such that "code owner" has access to this info and may
convey access to this info (eg: security issues are "yourself", is code owner
protected enough to allow this access)
December 20, 2003 Margaret -- Thanks for the question regarding "creating table", version 8.0.5
You Asked
Dear Tom:
I would like to create a table by copying from an existing table with the
following sql:
SQL> create table emp2
  2  tablespace tools
  3  as select empno, ename, hiredate
  4  from emp
  5  order by hiredate;
order by hiredate
*
ERROR at line 5:
ORA-00933: SQL command not properly ended

This sql is not working with an order by clause.Is there any ways to achieve
this fast?

Thank you very much for your help.
and we said...
You will need Oracle8i release 1 (version 815) or up -- that is when "order by"
in views, CTAS, and subqueries was permitted.

Prior to that:

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

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into emp select * from
scott.emp order by ename;

14 rows created.

is one approach to inserting "sorted" data.
Review & Followup

Rating: 2
create table select order July 15, 2002
Reviewer: A reader
I didnt got one thing why one need to order a select statement when the order of
retriving the data whithout a order by cause is not predictable, also is against
the RDBMS concept.
Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3479923945167
for why this is relevant -- particularly in a data warehouse.

It is for clustering of like or related values together -- something that could
be achieved also via

o an index organized table
o a b*tree cluster

for example.  it is for tuning purposes.
Rating: 5
July 15, 2002
Reviewer: Dave from Ohio
In response to the last readers comment, I have used physical row ordering for
quite a while to get performance improvements, on data warehouse fact tables in
particular.

Put simply, you arrange for rows that are likely to be selected together to be
physically located together.

If you consider a table with 1,000,000 rows in it, with 50 rows per block, then
your table covers about 20,000 blocks. Suppose you have a column 'COUNTY' with
an even distribution of 20 values, you have about 50,000 rows per COUNTY. If you
select all rows where COUNTY=5, and the rows you need are mixed in with all the
others in some random fashion, Oracle will have to 'touch' a great many blocks
in order to get the rows you want -- probably leading to a full table scan (not
neccessarily a bad thing in itself, of course). Should your rows be physically
ordered by COUNTY than Oracle only needs to read about 50,000 rows, which will
only occupy about 1000 blocks, and would promote the use of an index scan. Less
blocks read = faster performance (generalizing a little there). Oracle can
detect such a situation from the "CLUSTERING_FACTOR" of the index.

On large fact tables I have reduced I/O by a factor of over 10 by using this
simple technique.

By the way, if you arrange for frequently accessed columns to be physically
located at the beginning of the row rather than at the end, you can also improve
performance -- only worth doing on pretty wide tables, and it saves CPU cycles,
not I/O. Didn't believe it myself till I tested it on a 160 column datamart fact
table, and reduced a full scan selecting SUM() of the column in question from 49
seconds to 17 seconds (repeatable result, no-one else on the system, same
execution plan etc).
Followup:
wait'll see what Oracle9i release 2 can do!!!!

I took my "famous" big table with 1,000,000 rows created like this:

create table big_table as select * from all_objects;
insert /*+ append */ into big_table select * from big_table;
commit;
<above 2 statements done until there are 1,000,000 rows).  Then using the new
feature of table compression -- and knowledge of the data, I did this:

ops$tkyte@ORA920.US.ORACLE.COM> @compressed
DOC>create table big_table_compressed
DOC>COMPRESS
DOC>as
DOC>select *
DOC>  from big_table
DOC> order by owner, object_type, created, last_ddl_time, object_name;
DOC>*/
big_table@ORA920.US.ORACLE.COM>

a compressed table works like a compressed index sort of.  A symbol table is
formed and repeated values on a block are represented once. The savings can be
incredible (from 3-1 to 12-1).

Here, I used my knowledge of the data to put the least selective data first

big_table@ORA920.US.ORACLE.COM> exec show_space( 'BIG_TABLE' )
Free Blocks.............................
Total Blocks............................14336
Total Bytes.............................117440512
Total MBytes............................112
Unused Blocks...........................532
Unused Bytes............................4358144
Last Used Ext FileId....................9
Last Used Ext BlockId...................13320
Last Used Block.........................492

PL/SQL procedure successfully completed.

big_table@ORA920.US.ORACLE.COM> exec show_space( 'BIG_TABLE_COMPRESSED' )
Free Blocks.............................
Total Blocks............................1792
Total Bytes.............................14680064
Total MBytes............................14
Unused Blocks...........................75
Unused Bytes............................614400
Last Used Ext FileId....................9
Last Used Ext BlockId...................18184
Last Used Block.........................53

PL/SQL procedure successfully completed.

big_table@ORA920.US.ORACLE.COM> select (14336-532)/(1792-75) from dual;

(14336-532)/(1792-75)
---------------------
           8.03960396

That shows I achieved an 8:1 compression ratio.  My compressed table is 1/8th
the size of the original!

big_table@ORA920.US.ORACLE.COM>
big_table@ORA920.US.ORACLE.COM> alter tablespace users offline;

Tablespace altered.

big_table@ORA920.US.ORACLE.COM> alter tablespace users online;

Tablespace altered.

flush the buffer cache..

big_table@ORA920.US.ORACLE.COM>
big_table@ORA920.US.ORACLE.COM> set autotrace on
big_table@ORA920.US.ORACLE.COM> set timing on
big_table@ORA920.US.ORACLE.COM> select /*+ FULL(BIG_TABLE) */ count(*) from
big_table;

  COUNT(*)
----------
   1000000

Elapsed: 00:00:28.66

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1329 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1329 Card=1000000)

Statistics
----------------------------------------------------------
        245  recursive calls
          0  db block gets
      13717  consistent gets
      13647  physical reads
         60  redo size
        379  bytes sent via SQL*Net to client
        503  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          6  sorts (memory)
          0  sorts (disk)
          1  rows processed

big_table@ORA920.US.ORACLE.COM> select /*+ FULL(BIG_TABLE_COMPRESSED) */
count(*) from big_table_compressed;

  COUNT(*)
----------
   1000000

Elapsed: 00:00:04.07

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=167 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE_COMPRESSED' (Cost=167
Card=1000000)

Statistics
----------------------------------------------------------
        245  recursive calls
          0  db block gets
       1744  consistent gets
       1682  physical reads
          0  redo size
        379  bytes sent via SQL*Net to client
        503  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          6  sorts (memory)
          0  sorts (disk)
          1  rows processed

big_table@ORA920.US.ORACLE.COM> set autotrace off

1/8th the data, 1/8th the IO time.  Imagine your data warehouse with all of
its existing data but at say 1/3 or less the size!  Through in a couple of
compressed indexes -- compressed materialized views (group by tends to sort --
can be very compressable if you group by in a MV)...
Rating: 3
July 15, 2002
Reviewer: A reader
 begin
   insert into x (select * from user_objects order by object_name);
 end;

will not work unless i use dynamic SQL. What is the better option when there are
global temporary tables created for reporting purposes and the ordered data is
required?

should we use 1. Dynamic SQL 2. IOT ?
Followup:
Well, now I am worried.

If ordered data is required for the REPORT (and you are not trying to simply
order the data for performance in a data warehouse for example) -- you must,
have to, need to use an order by on the query that retrieves the data.  No if
and or buts on that one

Even with an IOT -- YOU MUST USE AN ORDER BY on the select, else the data will
come back in any order we like.
Rating: 3
July 15, 2002
Reviewer: A reader
Tom,

Currently, the reporting requirements are like

1. A report calls a back-end procedure

2. A back-end procedure is written to populate a table which
is temporary. (The SQL is usually a join between 3 or 4 tables)

3. The procedure manipulates the data

4. The reporting tool presents the data....with very little manipulations.

The problem here is the data which is getting inserted into the temporary table
should be in specific order.

Now here we cannot use insert into select ... order by

Should our reporting procedures use native_dynamic SQL
to populate this temporary tables?
Followup:
NO, data cannot be inserted in any sort of order to predict the OUTCOME.

Data does NOT come out in the order it went in.

The ONLY way to realiably do what you are doing is by having the reporting tool
use ORDER BY on the select -- repeat - THE ONLY WAY TO GET SORTED DATA -- is to
use order by.  Inserting it "sorted" won't do it.

You can show me as many "examples" as you like.  It just doesn't work that way,
it not assured to work that way.  If the order of the data is at all relevant to
you -- you will use order by on the select.

PERIOD.

So, stop playing with the insert and work on the SELECT.
Rating: 5
Compressed tables July 15, 2002
Reviewer: Dave from Ohio
Ooooh! very nice.
Rating: 5
Compressed tables! July 16, 2002
Reviewer: Mark J. Bobak from Dearborn Heights, MI
Now, that is just too cool!
Rating: 5
Did not work in Oracle 8.0.5 July 29, 2002
Reviewer: A reader
Hi Tom.

Exactly what I need but I could not get this to work in my Oracle 8.0.5
database.  Example:

SQL> create table tt as select * from dual where 1=0;

Table created.

SQL> insert into tt select * from dual order by dummy desc;
insert into tt select * from dual order by dummy desc
                                  *
ERROR at line 1:
ORA-00933: SQL command not properly ended

SQL> show version
unknown SHOW option "version"
SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle8 Enterprise Edition Release 8.0.5.1.1 - Production
PL/SQL Release 8.0.5.1.1 - Production
CORE Version 4.0.5.0.0 - Production
TNS for SEQUENT DYNIX/ptx: Version 8.0.5.0.0 - Production
NLSRTL Version 3.3.2.0.0 - Production
Followup:
sorry, guess I messed up on that one.  I should have run it in 817.

Won't work in 805, I knew that.  order by in a subquery, view, select in insert,
etc -- added in Oracle8i release 1 and up
Rating: 5
Compressed Tables August 04, 2002
Reviewer: Sikandar Hayat Awan from Pindi Bhattian - Pakistan
Excellent solution in 9i.
With reference to above post for reporting my point of view is like explained by
TOM,

When data is inserted in a temp table the report will display all the data in
the table so no where. If the temp table occupied 100 blocks then the report has
to display the data from all these 100 blocks. Now for performace as u will use
order by in select statement just improve the sort operations by sort_area_size,
TTS parameters.
Rating: 4
November 26, 2002
Reviewer: Sagi from India
Hi Tom,

In your example of COMPRESS table you said

"A symbol table is formed and repeated values on a block are represented once."

Firstly, What is the Data dictionary view where we can see the associated symbol
table for a compressed table. Is it readable?

Secondly, If we try to create more than one compressed table and say that the
values match from the SYMBOL table created for the first compressed table. Now
when we are creating the second compressed table will it again create a new
SYMBOL table or make use of the first one. Can you give a example. It would be
very helpful.

Also can you explain a bit more about the SYMBOL table.

Thanx in advance.

Regards,
Sagi
Followup:
You cannot view the symbol table -- it is stored on the block header itself.
There is a symbol table/block.

Each block has a symbol table that is local and specific to only the data on
that particular block.
Rating: 4
More on compressed table December 11, 2002
Reviewer: Rob from Pittsburgh, PA
Tom:

If I create a table with the compress option specified and then do a direct path
load, is the data compressed? (your example was a ctas). If I do further inserts
into the table are these new rows compressed? If rows are updated do they stay
compressed?

Compressed indexes:

Is the compression applicable only to create/rebuild of indexes. Will rows added
to the table have compressed entries in the index? What is the effect of
updates, are these rows uncompressed?

Thanks,

Rob
Followup:
yes -- direct path loads are compressed.

inserts done either a row at a time (insert ... values) or insert as select
without append -- they are not compressed.

updates -- no compress.

index key compression -- for everything.  all DML.
Rating: 3
Is this true December 12, 2002
Reviewer: A reader
One of the reviewer said

" By the way, if you arrange for frequently accessed columns to be physically
located at the beginning of the row rather than at the end, you can also improve

performance -- only worth doing on pretty wide tables, and it saves CPU cycles,
not I/O. Didn't believe it myself till I tested it on a 160 column datamart fact

table, and reduced a full scan selecting SUM() of the column in question from 49

seconds to 17 seconds (repeatable result, no-one else on the system, same
execution plan etc). "

Tom. is this true ???
Followup:
well, your tests indicate that it could be....

There could be two things at work here.

a) the rows in this table exceeded the size of a database block -- the last
column was on a block far away and we had to walk through many blocks to get to
it (row chaining)

b) it takes longer to parse out the last column of a table then it does the
first column cause Oracle stops processing the block when it hits the data it
needs.

ops$tkyte@ORA920> drop table t;

Table dropped.

ops$tkyte@ORA920> declare
  2          l_stmt long;
  3  begin
  4          l_stmt := 'create table t ( c001 number';
  5          for i in 2 .. 159
  6          loop
  7                  l_stmt := l_stmt || ', c' || to_char(i,'fm000') || '
varchar2(5) default ''12345'' ';
  8          end loop;
  9          l_stmt := l_stmt || ', c160 number )';
 10          execute immediate l_stmt;
 11  end;
 12  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> insert into t (c001,c160)
  2  select rownum, rownum from all_objects where rownum <= 10000;

10000 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select avg_row_len from user_tables where table_name = 'T';

AVG_ROW_LEN
-----------
        959

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      l_start number;
  3      l_run1  number;
  4      l_run2  number;
  5          l_n     number;
  6  begin
  7      insert into run_stats select 'before', stats.* from stats;
  8
  9      l_start := dbms_utility.get_time;
 10      for i in 1 .. 10
 11      loop
 12          select sum(c001) into l_n from t;
 13      end loop;
 14      l_run1 := (dbms_utility.get_time-l_start);
 15      dbms_output.put_line( l_run1 || ' hsecs' );
 16
 17      insert into run_stats select 'after 1', stats.* from stats;
 18      l_start := dbms_utility.get_time;
 19      for i in 1 .. 10
 20      loop
 21          select sum(c160) into l_n from t;
 22      end loop;
 23      l_run2 := (dbms_utility.get_time-l_start);
 24      dbms_output.put_line( l_run2 || ' hsecs' );
 25      dbms_output.put_line
 26      ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) || '% of the time' );
 27
 28      insert into run_stats select 'after 2', stats.* from stats;
 29  end;
 30  /
11 hsecs
27 hsecs
run 1 ran in 40.74% of the time

so, wall clock time - tis faster for sure

that is strictly due in this case to the fact that getting column 1 requires
less work parsing the block then getting column 160 does (which requires
"walking the row" column by column to get there).  The "statistics" of runstats
that I use shows there was no fundemental changes:

ops$tkyte@ORA920> select a.name, b.value-a.value run1, c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

....
LATCH.simulator hash latch            896       1025        129
LATCH.cache buffers chains          29014      29187        173

57 rows selected.

between the two -- it was 100% cpu related... Now, it gets even worse when:

ops$tkyte@ORA920> drop table t;

Table dropped.

ops$tkyte@ORA920> declare
  2          l_stmt long;
  3  begin
  4          l_stmt := 'create table t ( c001 number';
  5          for i in 2 .. 155
  6          loop
  7                  l_stmt := l_stmt || ', c' || to_char(i,'fm000') || '
varchar2(5) default ''12345'' ';
  8          end loop;
  9          for i in 156 .. 159
 10          loop
 11                  l_stmt := l_stmt || ', c' || to_char(i,'fm000') || '
char(2000) default ''x'' ';
 12          end loop;
 13          l_stmt := l_stmt || ', c160 number )';
 14          execute immediate l_stmt;
 15  end;
 16  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> insert into t (c001,c160)
  2  select rownum, rownum from all_objects where rownum <= 10000;

10000 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select avg_row_len from user_tables where table_name = 'T';

AVG_ROW_LEN
-----------
       8957

ops$tkyte@ORA920> declare
  2      l_start number;
  3      l_run1  number;
  4      l_run2  number;
  5          l_n     number;
  6  begin
  7      insert into run_stats select 'before', stats.* from stats;
  8
  9      l_start := dbms_utility.get_time;
 10      for i in 1 .. 10
 11      loop
 12          select sum(c001) into l_n from t;
 13      end loop;
 14      l_run1 := (dbms_utility.get_time-l_start);
 15      dbms_output.put_line( l_run1 || ' hsecs' );
 16
 17      insert into run_stats select 'after 1', stats.* from stats;
 18      l_start := dbms_utility.get_time;
 19      for i in 1 .. 10
 20      loop
 21          select sum(c160) into l_n from t;
 22      end loop;
 23      l_run2 := (dbms_utility.get_time-l_start);
 24      dbms_output.put_line( l_run2 || ' hsecs' );
 25      dbms_output.put_line
 26      ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) || '% of the time' );
 27
 28      insert into run_stats select 'after 2', stats.* from stats;
 29  end;
 30  /
849 hsecs
1364 hsecs
run 1 ran in 62.24% of the time

PL/SQL procedure successfully completed.

but here, it is not all about just "cpu" and parsing.  There was a ton more
other work going on:

ops$tkyte@ORA920> select a.name, b.value-a.value run1, c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

NAME                                 RUN1       RUN2       DIFF
------------------------------ ---------- ---------- ----------
...
STAT...recursive cpu usage            851       1365        514
LATCH.multiblock read objects       24480      25680       1200
STAT...redo size                    27628      30040       2412
STAT...prefetched blocks           168546     174110       5564
LATCH.simulator lru latch           11456      19637       8181
LATCH.simulator hash latch          23995      37354      13359
STAT...physical reads              180792     264069      83277
STAT...free buffer requested       180798     264079      83281
LATCH.cache buffers lru chain      180791     264122      83331
STAT...table fetch continued r          0     100000     100000
ow

STAT...no work - consistent re     200418     300421     100003
ad gets

STAT...buffer is not pinned co     200426     300431     100005
unt

STAT...consistent gets             200544     300556     100012
STAT...session logical reads       200594     300631     100037
LATCH.cache buffers chains         581982     865504     283522

72 rows selected.

In this case, in order to get column 160 -- we had to do Table Fetch Continued
Row (see how we did exactly 100,000 of them -- 10 * 10000... every single row
was chained and required "being put back together again")

On a really big table -- this could be really disasterous if every row were
chained.
Rating: 5
December 12, 2002
Reviewer: Dave from Ohio
Another Myth/Rule-of-thumb bites the dust! (with all the usual "in certain
circumstances" caveats)

"Column order is irrelevant" - bah.

I wonder whether the relatively higher consumption of CPU time on selecting the
first and second columns would be exacerbated by having the table compressed?
Would you care to use your same test case on a compressed version of the table
Tom?
Followup:
Ok, so I did this:

declare
        l_stmt long;
begin
        l_stmt := 'create table t ( c001 number';
        for i in 2 .. 159
        loop
                l_stmt := l_stmt || ', c' || to_char(i,'fm000') || ' varchar2(5)
default ''12345'' ';
        end loop;
        l_stmt := l_stmt || ', c160 number )';
        execute immediate l_stmt;
end;
/

insert into t (c001,c160)
select rownum, rownum from big_table.big_table where rownum <= 100000;

create table t2 compress
as
select * from t;

(big_table.big_table just has 2million rows -- so each table t and t2 has
100,000 rows exactly)...

Now, since t2 was infinitely compressable -- it was a whopping 2 megabytes in
size and t was 120 megabytes! (unrealistic). but the results where interesting:

ops$tkyte@ORA920> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA920> declare
  2          l_n number;
  3  begin
  4          for i in 1 .. 10
  5          loop
  6                  select sum(c001) into l_n from t;
  7                  select sum(c001) into l_n from t;
  8                  select sum(c160) into l_n from t;
  9                  select sum(c160) into l_n from t;
 10                  select sum(c001) into l_n from t2;
 11                  select sum(c001) into l_n from t2;
 12                  select sum(c160) into l_n from t2;
 13                  select sum(c160) into l_n from t2;
 14          end loop;
 15  end;
 16  /

PL/SQL procedure successfully completed.

SELECT sum(c001)
from
 t

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute     20      0.00       0.00          0          0          0           0
Fetch       20     18.95      23.19     264014     288800          0          20
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       42     18.96      23.19     264014     288800          0          20

SELECT sum(c160)
from
 t

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute     20      0.00       0.00          0          0          0           0
Fetch       20     21.43      26.14     264019     288800          0          20
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       42     21.44      26.14     264019     288800          0          20

SELECT sum(c001)
from
 t2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute     20      0.00       0.00          0          0          0           0
Fetch       20      6.12       8.35          0       4640          0          20
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       42      6.12       8.36          0       4640          0          20

SELECT sum(c160)
from
 t2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute     20      0.00       0.00          0          0          0           0
Fetch       20      6.20       6.67          0       4640          0          20
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       42      6.20       6.68          0       4640          0          20
Rating: 5
December 12, 2002
Reviewer: Dave from Ohio
It looks like the difference in CPU usage was very slight on the compressed
table -- I would guess that the overhead of uncompressing the block is very high
relative to the overhead of finding the end of the rows in it, and that the
benefits of getting a high compression ratio will outweigh the overhead on
finding the last column.

Makes sense really -- I'm guessing that the whole block will be uncompressed
whether the first column of the first row or the last column of the last row is
requested.

Thanks Tom.
Followup:
well -- actually -- here, the compression of the block helped.

We didn't have to "uncompress" columns 1..159 -- they were fixed length pointers
into a symbol table stored on the block. This is a bad example -- very
degenerate case where the data was super compressable.
Rating: 5
NEW LOV June 30, 2003
Reviewer: Zo from Mcar
Dear Tom,
Look at this

Declare

  TYPE Dtl_Typ IS TABLE OF agc_encaissements_details%ROWTYPE
      INDEX BY BINARY_INTEGER;
   Dtl_Tab Dtl_Typ;
   i BINARY_INTEGER := 0;
   z BINARY_INTEGER := 0;

  last_rec    binary_integer;

  begin

  if :ecs_montant_regl >= nvl(:hb_sum,0) then
     go_block('FACTURES');
     last_record;
     last_rec:=:system.cursor_record;

     first_record;
     i:=i+1;
     while (i<=last_rec) loop
    if :factures.hb_tick='Y' then
        z:=z+1;
        dtl_tab(z).edt_numencais:=:agc_encaissements.ecs_numencais;
        dtl_tab(z).edt_numfacture:=:factures.hb_facture;
        dtl_tab(z).edt_numfacture:=:factures.hb_facture;
        dtl_tab(z).edt_montant_facture:=:factures.hb_solde;
        dtl_tab(z).edt_montant_regl:=:factures.hb_montant;
        dtl_tab(z).edt_observation:=:agc_encaissements.ecs_codclient;
    end if;
    next_record;
    i:=i+1;
     end loop;

     Clear_Block(No_Validate);
     i:=0;
     i:=i+1;
     go_block('AGC_ENCAISSEMENTS_DETAILS');

     for rec in i..z loop
    create_record;
    :edt_numencais:=dtl_tab(rec).edt_numencais;
    :edt_numfacture:=dtl_tab(rec).edt_numfacture;
    :edt_numfacture:=dtl_tab(rec).edt_numfacture;
    :edt_montant_facture:=dtl_tab(rec).edt_montant_facture;
    :edt_montant_regl:=dtl_tab(rec).edt_montant_regl;
    :edt_observation:=dtl_tab(rec).edt_observation;
     end loop;

     go_item('AGC_ENCAISSEMENTS.ECS_NUMENCAIS');
     :toolbar.comment:=null;
     synchronize;
  else
     raise form_trigger_failure;
  end if;
end;

Purpose:
It display query into block (A) (non-database) and insert all ticked records
from (A) as a new records data into block (B) (database).

My Objective:
You can use a LOV but only one record at a time can be brought back (i.e n times
query required). Is there anyway to implement a SHIFT key for a LOV so that it
can bring out more than one records.

Q1) This code works fine but take much time to insert data into block (B), is
there any code that should be tuned ? Do you have any other solution.

Q2) Could you be able to show a sample source code behind this LOV so that I can
create my own LOV ?

NB. Sorry Tom if my question may not involve the above subject at all but I see
no-where to ask and perhaps this is useful to most of us using Oracle Forms

Kindly
Zks
Followup:
try

otn.oracle.com -> discussion forums.  there is one for forms there.  I have not
touched forms since 1995 myself.
Rating: 5
NEW LOV !!! It works fine July 02, 2003
Reviewer: Zo from Mcar
Reply for my question above.

Q1) I worked on it yesterday and It works fine since I set 'Validate from LOV'
to 'FALSE' on the item property.

Thanks Tom for your link, there is also www.experts-exchange.com may help
sometimes.

Zo
Rating: 5
full scan of compressed tables July 04, 2003
Reviewer: D.B.Nemec from Austria
Hi Tom,

in the "compressed" example above the compressed table has (1792-75 = 1717)
blocks. In a full scan used for count(*) are only 1682  physical reads.

a) How is it possible after a buffer cache flush? Are some chained rows on play?
I guess this have nothing to do with compression, because the not compressed
table gives a similar result.

b) Are there any known trade-offs of the compression? I.e. some statements (e.g.
some hash joins, exists, subqueries etc.) on compressed tables with less I/O but
greater elapsed time due to decompression. In other word, is there a checklist
for a decision if a data warehouse table should be compressed?

Thanks

Jaromir
http://www.db-nemec.com/
Followup:
(speek is speak btw -- from your home page ;)

Interesting -- it is a side effect of ASSM (automatic segment space management)
in 9i.  Had me going there for a while -- I couldn't reproduce straight away...

I can tell I was using it since the free blocks print out as NULL in the show
space report.  Both tables come up "short".  Apparently, the bitmap blocks must
be in the cache still -- when I edit the raw trace file, I can see it is
skipping blocks here and there in the extents.

Here is the demo:

ops$tkyte@ORA920> column tablespace_name new_val T

ops$tkyte@ORA920> exec show_space( 'BIG_TABLE_COMPRESSED' );
Unformatted Blocks .....................               0
FS1 Blocks (0-25)  .....................               0
FS2 Blocks (25-50) .....................               0
FS3 Blocks (50-75) .....................               0
FS4 Blocks (75-100).....................               0
Full Blocks        .....................           1,696
Total Blocks............................           1,792
Total Bytes.............................      14,680,064
Total MBytes............................              14
Unused Blocks...........................              60
Unused Bytes............................         491,520
Last Used Ext FileId....................               9
Last Used Ext BlockId...................          30,600
Last Used Block.........................              68

PL/SQL procedure successfully completed.

that is the output from my current show_space routine -- see how there are
1,792 total blocks -- but only 1696 "full" ones...

ops$tkyte@ORA920> analyze table big_table_compressed compute statistics for
table;
Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select table_name, blocks, tablespace_name,
  2        (select SEGMENT_SPACE_MANAGEMENT
  3           from dba_tablespaces
  4              where tablespace_name = user_tables.tablespace_name) ssm
  5  from user_tables
  6  where table_name = 'BIG_TABLE_COMPRESSED';

TABLE_NAME                         BLOCKS TABLESPACE_NAME                SSM
------------------------------ ---------- ------------------------------ ------
BIG_TABLE_COMPRESSED                 1732 USERS                          AUTO

appears there are 1732 blocks to be read -- however:

ops$tkyte@ORA920> alter tablespace &t offline;
old   1: alter tablespace &t offline
new   1: alter tablespace USERS offline
Tablespace altered.

ops$tkyte@ORA920> alter tablespace &t online;
old   1: alter tablespace &t online
new   1: alter tablespace USERS online
Tablespace altered.

ops$tkyte@ORA920> set autotrace traceonly statistics;
ops$tkyte@ORA920> select count(*) from big_table_compressed;

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

there were only 1697 physical IO's -- but 1697 is the number of "full blocks"
plus the extent header -- the difference were the bitmap blocks used to manage
space, they appear not to get flushed -- moving the table to a manually managed
tablespace:

ops$tkyte@ORA920> set autotrace off

ops$tkyte@ORA920> select decode( '&t', 'BIG_TABLE', 'USERS', 'BIG_TABLE')
tablespace_name
  2    from dual;
old   1: select decode( '&t', 'BIG_TABLE', 'USERS', 'BIG_TABLE') tablespace_name
new   1: select decode( 'USERS', 'BIG_TABLE', 'USERS', 'BIG_TABLE')
tablespace_name

TABLESPAC
---------
BIG_TABLE

ops$tkyte@ORA920> alter table big_table_compressed move tablespace &t;
old   1: alter table big_table_compressed move tablespace &t
new   1: alter table big_table_compressed move tablespace BIG_TABLE
Table altered.

ops$tkyte@ORA920> exec show_space( 'BIG_TABLE_COMPRESSED' );
Free Blocks.............................               0
Total Blocks............................           1,792
Total Bytes.............................      14,680,064
Total MBytes............................              14
Unused Blocks...........................              97
Unused Bytes............................         794,624
Last Used Ext FileId....................               6
Last Used Ext BlockId...................          16,009
Last Used Block.........................              31

PL/SQL procedure successfully completed.

show_space shows us now that there are the same number of total blocks but:

ops$tkyte@ORA920> analyze table big_table_compressed compute statistics for
table;
Table analyzed.

ops$tkyte@ORA920> select table_name, blocks, tablespace_name,
  2        (select SEGMENT_SPACE_MANAGEMENT
  3           from dba_tablespaces
  4              where tablespace_name = user_tables.tablespace_name) ssm
  5  from user_tables
  6  where table_name = 'BIG_TABLE_COMPRESSED';

TABLE_NAME                         BLOCKS TABLESPACE_NAME                SSM
------------------------------ ---------- ------------------------------ ------
BIG_TABLE_COMPRESSED                 1694 BIG_TABLE                      MANUAL

only 1694 contain data, and

ops$tkyte@ORA920> alter tablespace &t offline;
old   1: alter tablespace &t offline
new   1: alter tablespace BIG_TABLE offline
Tablespace altered.

ops$tkyte@ORA920> alter tablespace &t online;
old   1: alter tablespace &t online
new   1: alter tablespace BIG_TABLE online
Tablespace altered.

ops$tkyte@ORA920> set autotrace traceonly statistics;
ops$tkyte@ORA920> select count(*) from big_table_compressed;

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

that read every one of them...

good eyes there.
Rating: 5
Interesting Result! July 07, 2003
Reviewer: Jaromir from Austria Austria
Nice to know, that in case of space management auto you can omit some blocks on
full table scan.

Any idea to b) above? I mean if there are no trade-offs by compressing of
tables, all the data warehouse tables should be compressed some day.

Thanks for your peap (sorry peep – like my english) on my page!

Jaromir
Followup:
you didn't omit them, we read them -- look at the LIO's.  it is just that that
alter tablespace apparently didn't not flush them.

There are tradeoffs --

o it takes significantly more CPU to load
o there are cases where it can take more CPU to process
o if the data is not organized to be compressible, it can add lots of CPU to
load
  (to try and compress) with no benefits in storage.

So, just like its an OPTION for filesystems sometimes, it'll be an option for
segments.
Rating: 5
alter table compress December 19, 2003
Reviewer: A reader
Hi Tom,

Could you explain what is the effect of issuing a "alter table ... compress" to
a normal table?

It finished for me very fast so I think it's not _compressing_ is the rows. Will
it only compress new rows when they are inserted?

Thanks
Followup:
it set the attribute "compress" to true for the table meaning any FUTURE bulk
insert /*+append*/'s, direct path loads, alter moves will be done in compress
mode.
December 20, 2003 Ed -- Thanks for the question regarding "Oracle Session Management in Web Applications", version 8.1.7
You Asked
Tom,

I'm having discussions at my organisation on the merits of using Oracle HTTP
Server for our web applications rather than PHP.

I would be grateful if you could comment on a couple of things.

1) How does the Oracle HTTP Server manage Oracle connections?  It is my
understanding that when an HTTP request is received, an Oracle session is opened
up.  This connection is kept open in anticipation of other requests, until it is
closed after a period of time (configurable?) with no request.  If more requests
are made at the same time, then multiple sessions are opened.  Is this a
reasonable summary?

2) PHP (or the Apache configuration of it) can be set up to either use
persistant connections or simply connect, make a call to Oracle and disconnect.
We originally tried using persistant connections.  What appeared to happen was
that for each request, an Oracle session was opened (and left open) for each of
the first 10 requests received (I guess this is configurable).  At this point
subsequent requests were cycled round the open sessions.  However, we kept
getting loads of ORA-02012, ORA-03113, ORA-03114 errors, so it was decided to
use the connect/disconnect option for all requests.  My question is, what is the
overhead in doing this?  There didn't appear to be much of a performance
degredation, but I can't help feeling it's very inefficient to be opening and
shutting Oracle sessions all the time.

Thanks

Ed
and we said...
1) it really depends on the technology.

I think you are comparing apples to toaster ovens tho:

PHP -- language
HTTP server -- infrastructure

the http server could run PHP, mod_plsql, servlets, beans, whatever.

In mod_plsql -- it is normal that a pool a connections is maintained and reused.
 Eg: on asktom, we use mod_plsql.  I might have 5-10 connections in a pool
depending on load.  As you hit next page -- we grab a connection from the pool,
you use it and give it back at the end of the page.  After a connection has
idled for a while, we get rid of it.  Here the length of the session between
browser and web server is a page.  The duration of the connection between web
server and database is as long as we need but shared across all connections.

If you used java servlets -- YOU determine the lenght of the session between the
browser and the web server (you control that).  You also control the length of
the session between the web server and the database -- some servlets will grab a
connection from a connection pool and stay connected to the database for the
duration of the web session (similar to client server).  Others will use
connections much in the way mod_plsql does.

2) sounds like a bug in your PHP driver as this is how we do it ourselfs with
mod_plsql and jdbc connection pools.  On asktom we do this and have never had
that issue.  (don't see how the 2012 fits in).

It is infinitely faster to use a connection pool.
Review & Followup

Rating: 4
Re: Oracle Session Management in Web Applications November 04, 2002
Reviewer: Ed from Cheshire, UK
Tom,

I take your point.  I'm afraid I don't have much knowledge of the ins and outs
of PHP!  I guess the two approaches are either:
a) Using Oracle, the PL/SQL web toolkit, mod_plsql, Apache
OR
b) Using PHP, the Oracle8 Call Interface (OCI8, standard part of PHP, apparently
-
http://www.php.net/manual/en/ref.oci8.php
 and Apache.

I agree that there does appear to be something wrong with the PHP driver, but I
guess that's what you get for using unsupported code :-(

Sorry I did make a typo - I meant "ORA-01012: not logged on in", of course!

You say "it is infinitely faster to use a connection pool".  What do you really
mean by this - infinity is quite a big number!
Followup:
thats right -- infinity is a big number.  Infinitely faster is generally taken
to mean "lots faster".  The slowest thing in a web page is generally
connect/disconnect.  If you remove that every page is faster AND the system as a
whole is faster and more scalable since it is doing lots less work.
Rating: 2
evidence November 04, 2002
Reviewer: Ryan from PA
"The slowest thing in a web page is generally connect/disconnect"

What evidence do you have?
Followup:
7 years of experience doing it (on the web).  15 years working with databases.

Time it.  Tell me what takes long -- setting up the physical circut from the
client to the server or running some tuned queries?

Why do you think "connection pools" and their ilk were even invented.

The most expensive thing you can do on an OS is process(/thread) creation and
process(/thread) destruction.  Connect/Disconnect generally equates to just
that.  If you say "well, we will just use MTS (aka shared server)" you will find
the code path to connect via that is many times longer then dedicated hence you
will not achieve anything going down that path either.

This is one of those commonly accepted things that is actually true.
Rating: 3
November 04, 2002
Reviewer: A reader
Tom,

Do you'll use Dead Connection Detection..If yes can you elaborate a little more
on that..

Thanks.
Followup:
don't understand where DCD (dead client detection) came into play here?

You would use that if you expect your app server to crash totally, leaving
orphaned connections to your database.  DCD was invented for windows basically
-- cause it would crash, leaving orphaned connections.  DCD allows the database
to ping the client from time to time to see if it is still there.  In a mature
environment you do not need it.  I do not use it.
Rating: 2
Re : evidence November 05, 2002
Reviewer: Ryan from PA
You know it's kinda funny reading this when I figured one of your mantras was to
always benchmark to prove a point.

You write "This is one of those commonly accepted things that is actually true",
so I guess that gives the green light for not benchmarking if you don't feel
like it when you think something's "commonly accepted", whatever that means :-)
Followup:
I've benchmarked this dozens, no wait, probably hundreds of times.

My comment that

"This is one of those commonly accepted things that is actually true"

was made in light of that actually -- this is actually true AND it happens to be
commonly accepted which is typically an OXYMORON.  I was pointing out in this
case

o commonly accepted
o and true

happen to happily coincide.

Can I benchmark it here?  No, Have I benchmarked it -- yes, over and over and
over and over and over (i invented the reUseConnect wdbsvr configuration setting
that later became part of mod_plsql for this sole purpose)
Rating: 2
Re: evidence November 06, 2002
Reviewer: Ryan from PA
Well I'm sorry that I didn't equate "This is one of those commonly accepted
things that is actually true" with "I've benchmarked this dozens, no wait,
probably hundreds of times" - I guess this is because I have a problem you seem
to get - MY crystal ball is in the shop right now!

I can't benchmark it, as I don't have the resources at the moment, but believe
me, I would if I could.  My gut feeling based on 4plus years of web development
is that the connection/disconnection overhead is maybe one quarter second, maybe
one half.

Now whether thats important to the users that their pages are that much slower
is entirely subjective, but if a query is taking 12 seconds to come back, I
don't think an extra half second is gonna piss the users too much.
Followup:
you are kidding right -- the problem isn't the .5 seconds -- it is the massive
hit your system takes whilst 10 people at the same time all try to fork and exec
a dedicated server or create a thread and the network connections get set up and
the system context switches all over the place to do these things.

Riddle me this batman:

o why did "fastcgi" where by cgi bins (old stuff from 95 when the web was
"newish") were cached and not forked and exec become really necessary?

o why does apache use mod's instead of cgi bin?

o why do we need connection pools?

o why do we have app servers and not just web servers with cgi's like we used
to?

because -- process creation and process destruction is the most expensive thing
you can do.  It makes your system *not scalable*.  Try to log in in single user
mode -- no problem.  Now, have 10 people log in AT THE SAME INSTANT.  Now, tell
me, what happens?

Do what you will.  Sorry my comment "this is one of the commonly accepted things
*that is actually true*" was so obscure.

Me -- I would never touch a system (as an end user) where the web pages took 12
seconds to generate.  Wouldn't touch it.  But you are right -- if your pages are
taking SECONDS to generate -- you do have much bigger fish to fry, other things
to be worried about.  But -- if you do want to make it run slower, by all means
-- don't reuse connections, use cgi-bin!
Rating: 3
Re: evidence November 07, 2002
Reviewer: Ryan from PA
I don't disagree with anything you write.

Hell, it isn't even an issue for me!  I use 9iAs for everything I can - session
management isn't even an issue, everything gets done for you!  If your in a
situation where your just dealing with Oracle, I wouldn't use anything else.

I guess I didn't express myself very well - I just want to say to the OP that if
you've already gone down the road where your stuck with bad session management,
it's maybe not worth your time trashing everything and starting again.  But
maybe I'm wrong.

As for all you "why" questions, I dont know.  Now why does Microsoft release
Office 97 / 2000 / XP / whatever, when MS Word 2 was 99.9% adequate for
everything?

Oh, your comment "this is one of the commonly accepted things *that is actually
true*" wasnt so obscure, just sometimes I like to see some EVIDENCE to back up
claims like this - dont you?
Rating: 4
Just want to confirm and elaborate on the part about pool of connections November 07, 2002
Reviewer: Pat from MD USA
Since I have queried v$session many times and seen that there would always be
just 1 session for 1 web page, can I say this: the pool of connections is
managed by the HTTP server(program apache.exe), which actually connects to
Oracle and therefore is listed in v$session, and it handles the creation/killing
of connections, as wells as passes input/output between the browser and database
server. So 1 web page with any 1 user request would show up as 1 session in
v$session, no matter how many users are actually visiting the page. But once you
get out of that page, assuming you are the only user, the session would still be
there till the configured connection time in HTTP server runs out?

Is there any pointer for us to read more about the configuration of pool of
connections, e.g. how many depending on load, how long each would be, or these
are totally transparent so they are not configurable?
Followup:
depends on the tool you are using -- servlets, jdbc connection pools -- you
control it.  mod_plsql, just "happens", you either set it on or set it off
pretty much.

All of the configuration parameters for all of the mod's are documented.
Rating: 5
connection pooling January 01, 2003
Reviewer: mo
Tom:

<In mod_plsql -- it is normal that a pool a connections is maintained and
reused>.

1.  I assume you mean the connection is the physical NET8 connection between app
server and dbms and not browser/app server?

2.  When you have a connection like sql*plus connection you create it using
userid/password.  What  userid/password the app server use to create all those
connections in the pool?

3.  Is one connection (from pool) defined with whatever is defined in the DAD?

4. If we have blank DAD then we can't use connection pooling?
Followup:
1) correct

2) depends -- did you store the username/password with the DAD?  if so, that is
where.  if not, the user gives it to us.

3) as needed they are created

4) don't know what a "blank" DAD is -- assume you mean "created with out
username/password" -- if so, you can.  There are "switch user" operations we can
perform.  As well, we try to hook you back up with your connection if possible.
Rating: 5
connection pool January 01, 2003
Reviewer: mo
Tom:

OK I think I understand how it works now.

1. App server creates all connections in the pool based on how many DADs are
created and assigns the appropriate connections to users based on their login.
Correct?

2. If system was configured with blank DAD, i.e. let user enter userid/password
first time he logs in, the app server will already have a pool of generic
connections but then will switch userid/password info in connection to whatever
he enters? Correct.

Thank you
Rating: 3
Updating session data and redo generation December 19, 2003
Reviewer: David Piazza from San Mateo, CA
Tom,

  We are using Oracle 9.2, a web server using cold fusion, and have a web
application that stores session data in the database, "session data" meaning
state information for currently logged-in users.  The session data includes
various information such as the user's ID and security level, and occasional
other chunks of information that have to follow the user around as she/he is
using the application.  The storage of session information in the
database is pretty common in web-based applications.(ColdFusion calls this
feature "client variables" and WebSphere calls it "persistent sessions.")  It's
not universal - sessions can be stored in RAM when it's guaranteed that the
userwill connect to only one application server during a given work session.
But when there are multiple servers behind a "non-sticky" load balancer, the
servers need a way to share users' states, and the database is the mechanism for
doing this.
  If the user state is being updated frequently, this can translate to a lot of
database writes - often one write per webpage-hit.  The impact on performance
can be significant.  During testing of the application, using load runner, the
amount of redo generated was reduced by recoding parts of the application so
that the session state was being changed much less often.  But, not all
applications will have the luxury of stable session state.
  Now, in our application, the session state has no long-term meaning.  If the
database were to fail and then be restored, users reconnecting to the
application would be expected to establish new sessions, that is, to log in
again (whether manually or via V-GO or true SSO, it would still be a new login
and a new session), so there would be no need to restore the session data up to
the point of failure.
  Since logging, cannot be turned off for single inserts, do you have any
suggestions on limiting redo from the frequent session updates?
Followup:
this is exactly what html db does as well - -in the database ALWAYS.

I have not measured any significant impact on my performance (and my site is
fairly busy).

So..... give me the numbers, show me the science, prove to us that redo
generation is a significant bottleneck/constraint in your system.

Because -- remember -- databases were born to join and born to write.  Every
page on asktom not only does this state thingy, but I have the nerve to write a
big audit record upon entry to the page (every page) and then update that entry
at the end.  Along with other audits as well.  So, every page view on this site
is a transaction with lots of writes.  I'm not worried about it and log file
sync has never been a major "wait" for me.

So, show us why you believe redo generation to be "your very big problem".  Need
to see the math behind this thought.
December 20, 2003 Chandra -- Thanks for the question regarding "Unsauble/Rebuild/Export", version 8.1.7.1
You Asked
Hi Tom,
I am in a Datawarehouse environment.
We have created few indexes based on user querry performance.
We need to either
1. Make the index unusable before datamart load.
2. Drop the index and recreate once the datamart is fullu loaded.
   This we are doing becaue the load would be faster.
If we take the first option we are unable to analyze the table after the table
is loaded because some indexes are made unusable.
The  problem with the next option is we are loosing the index info once we drop
the index.
We started with using export utility.
Here is the problem.
Example:
There are tables A and B
Indexes on Table A are
           Indexname = "LOAD#donor_id....."
           Indexname = "USER#spon_id......"
Indexes on Table B are
           Indexname = "LOAD#donor_id....."
           Indexname = "USER#spon_id......"

Our process is
           Drop all the index other than "LOAD%" on table A
           Unsuable all the "LOAD% Indexes" on table A
           Start loading table A
           Rebuild all the "LOAD% Indexes" on table A
           Analyze the tables table A.
           Recreate all the index other than "LOAD%" on table A

Can you please help me solving the problem.
I need to create the all indexes once the tables are loaded,
What would be the best way to tackle this.
We started with
Step 1.Unsuable (All Indexes on table )
Step 2.Load Table
Step 3.Rebuild (Only "LOAD%" Indexes)
Step 4. Analyze table

Step 5.Do this process for all the table A to Z
Step 6.Rebuild all the "USER%" Indexes.
We are failing at step 4 becuase some of the indexes on some table are
made unsauble.
Please help me in solving this situation.
Thanks,
Chandra.
and we said...
In step 4, you don't say what analyze command you are using but it sounds like:

analyze table t compute statistics;

If you instead:

analyze table t compute statistics for TABLE;

for example, it'll work.  Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int, y int );
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx1 on t(x);
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx2 on t(y);
Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter index t_idx1 unusable;
Index altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;
analyze table t compute statistics
*
ERROR at line 1:
ORA-01502: index 'OPS$TKYTE.T_IDX1' or partition of such index is in unusable
state

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics for table
for all indexed columns;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze index t_idx2 compute statistics;
Index analyzed.

Now you can rebuild the other indexes and then analyze them one by one.
Review & Followup

Rating: 5
Index Unauable?rebuild August 31, 2001
Reviewer: Chandra nandamuru from Seattle ,USA
The reply to the question was very much accurate, very very fast and on top all
these points the clarification is working perfectly.
Thanks to Tom
Chandra
Rating: 5
Good to know the different flavors of ANALYZE .... August 31, 2001
Reviewer: Andrew from USA
Thanks for the example - it shows a difference between the various flavors of
the ANALYZE command.
Rating: 5
December 19, 2003
Reviewer: A reader
Tom,

1.Is rebuilding index faster than dropping and recreating??

2.I have table t which has index i1 in tablespace ts1 now if i give this command
SQL>Alter index i1 rebuild tablespace ts2;

in this case will the index be dropped and recreated in tablespace ts2

Thanks.
Followup:
1) index coalescing is even faster and much better IMHO. but yes, a rebuild can
sometimes skip the sort and skip scanning the table -- so it can be faster then
drop + create.  but give coalesce a try!!!!!!

2) yes, conceptually, the index will be build in t2, then dropped in t1.
Rating: 5
December 20, 2003
Reviewer: A reader
Thanks
December 20, 2003 Ashwin -- Thanks for the question regarding "How To Tune This Procedure (May be using EXIST)", version 8.1.7
You Asked
Hi Tom,
     I'm still not familier using "EXIST"  instead of "IN".
I need your help in tuning the following code.

declare
  cursor cl is select tel_no, cmr, cmr_dt from tomrcmrerror for update;
  subs number(10);
  tel  number(8);
  mr   number(10);
  dt   date;
  ctr  number(1) := 0;
begin
  open cl;
  loop
    fetch cl into tel, mr, dt;
    exit when cl%notfound;
    subs := 0;
    begin
      select subs_no into subs from msubscrbr
       where tel_no = tel and status in ('OPEN', 'DNPJUMPER') and
             rownum < 2;
      and rownum < 2;
    exception
      when no_data_found then null;
    end;
    begin
      ctr := 0;
      select 1 into ctr from tomrcmr
          where subs_no = subs and tel_no = tel and cmr_dt > dt and cmr > mr and
             rownum < 2;
    exception when no_data_found then  null;
    end;
    if ctr is null then
      ctr := 0;
    end if;
    if ctr > 0 then
      delete from tomrcmrerror where current of cl;
    end if;
  end loop;
end;

Thanks in advance.
and we said...
its all about bulking up (just answered another question like this -- take alot
of procedural code and "unprocedure" it)...

I'm a little worried about your "and rownum < 2" in the query against msubscrbr.
 If it is in fact necessary -- it is "scary" (means that the order the data is
searched in the table could have an effect on what rows are deleted!)

delete from tomrcmrerror t1
 where exists ( select null
                  from tomrcmr t2
                 where t2.subs_no = ( select t3.subs_no
                                        from msubscrbr t3
                                       where t3.tel_no = t2.tel_no
                                         and status in ( 'OPEN', 'DNPJUMPER')
                                         and rownum = 1
                                    )
                  and t2.tel_no = t1.tel_no
                  and t2.cmr_dt > t1.cmr_dt
                  and t2.cmr > t1.cmr
              )
/
Review & Followup

Rating: 4
Unprocedure Code November 26, 2003
Reviewer: Ashwin from India
Hi Tom,
       I will test the code and let you know.
Thanks Tom.
Followup:
look at the logic -- it would be exceedingly difficult not to inspect each row.

Look at the question being asked of the database:

delete from tomrcmrerror t1
 where exists ( select null
                  from tomrcmr t2
                 where t2.subs_no = ( select t3.subs_no
                                        from msubscrbr t3
                                       where t3.tel_no = t2.tel_no
                                         and status in ( 'OPEN', 'DNPJUMPER')
                                         and rownum = 1
                                    )
                  and t2.tel_no = t1.tel_no
                  and t2.cmr_dt > t1.cmr_dt
                  and t2.cmr > t1.cmr
              )

In english, that is saying:

please look at each row in tomrcmrerror and if a row is found in tomrcmr such
that the tel_no is the same AND the cmr_dt/cmr columns are greater then my value
AND the subs_no = the subs_no from msubscrbr for that same tel_no and the status
is in that pair of values -- please delete it.

You have a non-equi join from tomrcmrerror to tomrcmr (the cmr_dt/cmr columns).
Makes it quite difficult to use an index on tomrcmrerror here.

The full scan, as a % of the runtime for the delete, it going to be
inconsequential I believe

Do this for us:

alter session set timed_statistics=true;
alter session set sql_trace=true;
set autotrace traceonly
select * from tomrcmrerror
exit

and show us the tkprof.  (and please remember, full scans are NOT EVIL, they are
NOT EVIL, they are NOT EVIL.  just keep saying it over and over)
Rating: 3
Not Using Index... November 27, 2003
Reviewer: Ashwin from India
Hi Tom,
    I have taken the execution plan for the above query which is as below,
Execution Plan
----------------------------------------------------------
          0
SELECT STATEMENT Optimizer=CHOOSE (Cost=3101 Card=99704 Bytes=1794672)
          1                  0
  FILTER
          2                  1
    TABLE ACCESS (FULL) OF 'T_OMRCMRERROR' (Cost=3101 Card=99704 Bytes=1794672)
          3                  1
    FILTER
          4                  3
      TABLE ACCESS (BY INDEX ROWID) OF 'T_OMRCMR' (Cost=46 Card=1 Bytes=26)
          5                  4
        INDEX (RANGE SCAN) OF 'IDX_TOMRCMR_TEL' (NON-UNIQUE) (Cost=3 Card=1)
          6                  3
      COUNT (STOPKEY)
          7                  6
        TABLE ACCESS (BY INDEX ROWID) OF 'M_SUBSCRBR' (Cost=4 Card=1 Bytes=20)
          8                  7
          INDEX (RANGE SCAN) OF 'IDX1_MSUBSCRBR' (NON-UNIQUE) (Cost=3 Card=1)

Can I aviod FULL SCAN OF tomrcmrerror.
Indexes available on tomrcmrerror is as,
(TEL_NO, CMR_DT) and (TEL_NO,CMR,CMR_DT)
Rating: 4
TKPROF December 20, 2003
Reviewer: Ashwin from india
SELECT /*+ index(b)  */A.TEL_NO,CMR,CMR_DT,SUBS_NO
FROM
 TOMRCMRERROR A,MSUBSCRBR B  WHERE A.TEL_NO = B.TEL_NO  AND B.STATUS IN (
  'OPEN','DNPJUMPER'  )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     1805     12.05     107.17      20083      33327          6        1805
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1807     12.06     107.18      20083      33327          6        1805

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 11  (DBMAINT)   (recursive depth: 1)
Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   HASH JOIN
      0    INLIST ITERATOR
      0     TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF
                'M_SUBSCRBR'
      0      INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'IDX2_MSUBSCRBR'
                 (NON-UNIQUE)
      0    INDEX   GOAL: ANALYZED (FAST FULL SCAN) OF
               'IDX_OMRCMRERROR_DEL' (NON-UNIQUE)

********************************************************************************

SELECT 1
FROM
 TOMRCMR  WHERE SUBS_NO = :b1  AND TEL_NO = :b2  AND CMR_DT > :b3  AND CMR >
  :b4  AND ROWNUM < 2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute   1805      0.15       0.16          0          0          0           0
Fetch     1804      2.22     109.27       6855      57111          0         546
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     3610      2.37     109.43       6855      57111          0         546

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 11  (DBMAINT)   (recursive depth: 1)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   COUNT (STOPKEY)
      0    TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF 'T_OMRCMR'
      0     INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'IDX_TOMRCMR_TEL'
                (NON-UNIQUE)

DELETE FROM TOMRCMRERROR
WHERE
 TEL_NO = :b1  AND CMR = :b2  AND CMR_DT = :b3

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.03          0          0          0           0
Execute    546      0.31       7.63        451       1645       3891         546
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      547      0.31       7.66        451       1645       3891         546

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 11  (DBMAINT)   (recursive depth: 1)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  DELETE STATEMENT   GOAL: CHOOSE
      0   DELETE OF 'T_OMRCMRERROR'

 0  DELETE STATEMENT   GOAL: CHOOSE
      0   DELETE OF 'T_OMRCMRERROR'
      0    INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'IDX_OMRCMRERROR_DEL'
               (NON-UNIQUE)

********************************************************************************

select /*+ rule */ bucket, endpoint, col#, epvalue
from
 histgrm$ where obj#=:1 and intcol#=:2 and row#=:3 order by bucket

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      3      0.00       0.00          0          0          0           0
Fetch        3      0.00       0.16          1          6          0          60
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        7      0.00       0.16          1          6          0          60

Misses in library cache during parse: 0
Optimizer goal: RULE
Parsing user id: SYS   (recursive depth: 2)
Followup:
uh huh?  what I am looking for here.
December 20, 2003 -- Thanks for the question regarding "query", version 8.1.7
You Asked
HI tom

first i just receive your book it is very organaize

second

i have three tablee commencment ,totalhours,statusreport

commencment contain undublicate recored ,totalhours is contain the total hours
for each commencment and statusreport contain detail for each commencment i need
to run a report to get the last date only in the status table which is
(action_date) i ran this query but i can't get

SELECT ALL COMMENCMENT.ASSIGNMENTNO, COMMENCMENT.ICA, COMMENCMENT.ASSISTINGA,
COMMENCMENT.DATECOMMEN, COMMENCMENT.BUDGETMAN, COMMENCMENT.EXPECTED,
COMMENCMENT.BUDGET, COMMENCMENT.AUDITEENAME, LEAST(STATUSREPORT.ACTION)
DISPLAY_COL9,
LEAST(STATUSREPORT.ACTION_DATE) DISPLAY_COL10, LEAST(STATUSREPORT.TOCOMPLETE)
DISPLAY_COL11,
TOTALHOURS.TOTAL/8.5 DISPLAY_COL12
FROM STATUSREPORT, COMMENCMENT, TOTALHOURS
WHERE ((STATUSREPORT.ASSIGNMENTNO = COMMENCMENT.ASSIGNMENTNO)
 AND (COMMENCMENT.ASSIGNMENTNO = TOTALHOURS.ASSIGNMENTNO))
GROUP BY COMMENCMENT.ASSIGNMENTNO, COMMENCMENT.ICA, COMMENCMENT.ASSISTINGA,
COMMENCMENT.DATECOMMEN, COMMENCMENT.BUDGETMAN, COMMENCMENT.EXPECTED,
COMMENCMENT.BUDGET,
COMMENCMENT.AUDITEENAME, (STATUSREPORT.ACTION), (STATUSREPORT.TOCOMPLETE),
TOTALHOURS.TOTAL/8.5, LEAST(STATUSREPORT.ACTION),
LEAST(STATUSREPORT.ACTION_DATE),
LEAST(STATUSREPORT.TOCOMPLETE), TOTALHOURS.TOTAL/8.5
ORDER BY COMMENCMENT.DATECOMMEN

also ichang least with max(action_date) but also the output is dublicate for
statusreport
and we said...
try:

SELECT COMMENCMENT.ASSIGNMENTNO,
       COMMENCMENT.ICA,
       COMMENCMENT.ASSISTINGA,
       COMMENCMENT.DATECOMMEN,
       COMMENCMENT.BUDGETMAN,
       COMMENCMENT.EXPECTED,
       COMMENCMENT.BUDGET,
       COMMENCMENT.AUDITEENAME,
       STATUSREPORT.ACTION DISPLAY_COL9,
       STATUSREPORT.ACTION_DATE DISPLAY_COL10,
       STATUSREPORT.TOCOMPLETE DISPLAY_COL11,
       TOTALHOURS.TOTAL/8.5 DISPLAY_COL12
  FROM STATUSREPORT,
       COMMENCMENT,
       TOTALHOURS
WHERE STATUSREPORT.ASSIGNMENTNO = COMMENCMENT.ASSIGNMENTNO
  AND COMMENCMENT.ASSIGNMENTNO = TOTALHOURS.ASSIGNMENTNO
  AND statusreport.action_date =
            ( select min(action_date)
                from statusreport sr
               where sr.assignmentno = commencment.assignmentno)
 ORDER BY COMMENCMENT.DATECOMMEN
/

I don't know what you were attempting with LEAST -- or the group by.  LEAST
returns the least element from a list of values:

    least( a, b, c, d, e )

will return the least of a, b, c, d, e -- it does not work across rows.

The above query will join commencment to totalhours and join that to the SINGLE
record from status report that is the MIN() action_date for that assignmentno.

If there are TWO records in statusreport with the SAME action date for a given
assignmentno, you will get "dups"
Review & Followup

Rating: 4
i am happy January 22, 2002
Reviewer: A reader
as your answer is very quick

thanks Tom
happy day
Rating: 4
LEAST December 20, 2003
Reviewer: tony from US
Tom,

Don't know what's wrong with this...Pls. correct me

declare
  -- Local variables here
  i integer;
  cStr Varchar2(200);
  pPar1 Varchar2(50) := ''''||'FEG'||''''||','||''''||'BOW'||'''';
begin
  dbms_output.put_line(pPar1);
  cStr := Least(pPar1);
  dbms_output.put_line(cStr);

end;

why this is not executing...

Rgrd
Tony
Followup:
it executed for me.

you have a string, pPar1.

you have a SINGLE STRING, pPar1.

It has SOME TEXT in it. (forget your text, it just has SOME text in it)

you apply a function LEAST to it.  LEAST expects ONE or MORE inputs and returns
the LEAST of the inputs.  You gave LEAST a single input, a single STRING (forget
about the content of the string -- totally not relevant here, put hello world in
the string for all we care right now) -- hence LEAST has no option but to return
that SINGLE STRING.

So, it worked as coded.

seems you might have wanted to find the "least" of feg and bow -- but you put
feg and bow into a single string -- so you changed the question.

 var1 varchar2(10) := 'feg';
 var2 varchar2(10) := 'bow';
begin
 cStr := least( var1, var2 );

would do that.

  cStr := least( pPar1 );

is no different then

  cStr := pPar1;

REGARDLESS of what pPar1 contains.  search for

varying in list

on this site for a way to turn a quoted/delimited string into a "set" if you
like.
December 20, 2003 Joe -- Thanks for the question regarding ""log file sync" during import", version 9.2.0.4
You Asked
Tom,

I'm importing about 50 tables and each table has about 20 partitions.  Each
partition contains at least 500,000 rows and at most 4 million rows.
The most prominent wait event is "log file sync". How can I reduce this?
Is it due to the fact that every row has a date column?  Here's a section of
the Utilities guide on import where it describes the COMMIT=y option of import:

"For tables containing LONG, LOB, BFILE, REF, ROWID, UROWID, DATE, or type
columns,
 array inserts are not done. If COMMIT=Y, Import commits these tables after each
row. "

Is this commit causing my extremely large amount of "log file sync" waits and if
so
is there a way around this other than using COMMIT=N?

Thanks,

Joe
and we said...
a date column?

To reduce log file sync waits, you commit LESS FREQUENTLY so -- yes, commit=n is
my 100% preferred method for doing an import.  rollback is not so much an issue
with inserts (a "delete rowid" is put there basically) and beside -- rollback
should be sized big anyway (people way undersize this basic thing in the
database).

to remove log file syncs:

a) commit less frequently (commit=n or use a bigger buffer but with lobs/longs
-- you are getting single rows so commit=n is the only real way)

or

b) make the log disks really really fast somehow (remove all contention).  use
RAW disk or directio mount options (in unix) to simulate RAW performance on
cooked filesystems with your logs.
Review & Followup

Rating: 5
log file sync December 20, 2003
Reviewer: dan malumphy from Cleveland,OH
Funny, I had recently looked at the utilities  manual myself, wondering how I
could speed an import.  I found it surprising the also, that if the commit =y,
then a row with a date column will commit every row.  Tom, you seemed surprised
also, based on your first comment.  Any idea why this is?
Followup:
Ok, had to check this out myself.... looks like a "doc bug" to me.

created a table T as select * from all_users.  Then, (using logon trigger with
sql_trace=true), imported it using " imp userid=/ full=y ignore=y commit=y".
CREATED is a date column -- there was one insert -- no single row inserts:

INSERT /*+NESTED_TABLE_SET_REFS+*/ INTO "T" ("USERNAME", "USER_ID", "CREATED")

VALUES
 (:1, :2, :3)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          4          0           0
Execute      1      0.00       0.00          0          1          7          51
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.01       0.01          0          5          7          51

that shows 51 rows inserted in one fell swoop

Then I:

ops$tkyte@ORA920> create table t ( username clob, user_id number, created date
);

Table created.

ops$tkyte@ORA920> insert into t select * from all_users;

51 rows created.

and now the tkprof shows:

SELECT /*+NESTED_TABLE_GET_REFS+*/ "USERNAME"
FROM
 "T" WHERE ROWID = :1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute     51      0.00       0.00          0          0          0           0
Fetch       51      0.01       0.00          0         51          0          51
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      103      0.01       0.01          0         51          0          51

51 single row inserts....

So, doc bug if you ask me.  would have noticed this a long long time ago since
almost every table has a DATE in it somewhere -- array inserts just would be
"not happening" with import otherwise.
Rating: 5
December 20, 2003
Reviewer: A reader
Excellent Stuff !!!
Rating: 5
for the brave of heart... December 21, 2003
Reviewer: Connor McDonald from Perth
you can always play with the parameters that disabling the logging, or remove
the wait for the sync...

(Of course, the other two parameters:

pray_for_no_corruption = true
ensure_cv_is_up_to_date = true

also come into play :-)
December 20, 2003 Reddi -- Thanks for the question regarding "questions on views", version 8.1.7
You Asked
Hi Tom

1.What is the purpose of using the force option while creating views. Whatis the
use of a view if there is no underlying table.

2.What does the force option acutally mean ?

and

3.What is the difference between a regular view and an updatable view( from
which version of oracle do we have updatable views)?

and

4.scenario:
---------

I create a view  as select * from emp;

Whose performance is faster  the emp table or the emp view, and why ?

Thanks
and we said...
1) used by import and other installation tools to create a view before the
underlying objects are there -- so as to not have to figure out what things need
to be created in what order to suceed.

2) creates the view even if the underlying objects are not there.

3) There is no such thing as a "regular view vs an updatable view" -- they are
all "regular views", some views are updatable, others are not.  all oracle
versions support updatable views.  I suppose you mean UPDATABLE JOIN views.
that was added in 7.3.  It allows many views that are the results of a JOIN to
be updated.

4) they are the same more or less.  You will not notice the difference.
Review & Followup

Rating: 2
In depth parse of queries in view and a table February 01, 2002
Reviewer: Geetha Nadiger from India
I was searching for perfomance between using views over tables. But, this thread
says more or less the same which does not satisfy to my question. Can u explain
me in depth y we have to go for updateable views rather than a table ?
Followup:
you don't HAVE TO do anything.

Views are a development tool, a thing of convienence.  They provide a layer of
abstraction.

I cannot tell you how many times people ask "how can I rename a column", "how
can I change the order of the columns", "how can I add a new column in the
'middle' of a table".

If you EVER think you'll be asking those questions yourself someday, use views.
Views let you do all of the above trivially.

If you want to put the complex SQL into views and have simple SQL in your apps
-- use views (i use them ALOT in my apps).

A view is nothing more then a stored query.  It will run no slower nor faster
then a query directly against the base tables.
Rating: 3
Caveat - understand merging of where clauses... February 01, 2002
Reviewer: A reader from ca, usa
Tom

I find the understanding of where clause merging into the view where clause to
be useful. Not understanding this and it's limitations (views containing group
functions, distinct etc.) could lead to over zealous use of views.

Maybe a word or two from you on this??
Followup:
search for

views of views

on this site to read about that.  A look at a view like a function or procedure.
 It to me is a standalone thing.  You must take care when querying from multiple
views or joining views willy - nilly to other objects.  In the end, there is NO
difference between:

select * from v1, v2 where ....;

and

select *
  from ( select ..... /* the TEXT of v1 */ ) v1,
       ( select ..... /* the TEXT of v2 */ ) v2
  where ...

they will behave the same.  Issues can arise when doing either of those queries
is "non-performant" -- that is simply because the views were not designed to be
used that way.

Here, in this question, with the simple views -- there aren't any concerns like
that.
Rating: 2
view performance February 01, 2002
Reviewer: Peggy Morgan from Los Angeles, CA
I am still very confused by the performance of views compared to tables. Your
answer said, "A view is nothing more then a stored query.  It will run no slower
nor faster then a query directly against the base tables."

I did this on one of my views:

select count(*)
from view_x
where pa_date between '01-dec-01' and 01-jan-02';

and got an elapsed time of over 8 minutes.

Then I did the same query on the base table:

select count(*)
from table_x
where pa_date between '01-dec-01' and 01-jan-02';

and the count came back in 56 seconds.

The table has 36 columns and the view looks at 34 columns.

The query on the base table is clearly faster. What am I missing here?
Followup:
Lets see the view text please, and a describe of the table, and the results of:

SQL> set autotrace traceonly explain
SQL> select count(*) from view_x where pa_date between ....
SQL> select count(*) from table_x where pa_date between ....

And also -- please (I would have thought we would have all learned this lesson)
DON'T USE 2 DIGIT DATES.  and further, don't compare dates to strings and
strings to dates.  Use this:

where pa_date between to_date( '01-dec-2001', 'dd-mon-yyyy' ) and
                      to_date( '01-jan-2002', 'dd-mon-yyyy' )

in all real code (eg: everything beyond an example like this)
Rating: 5
Using Built in Functions in Views February 02, 2002
Reviewer: Lakshmi Narasimhan R from Dubai UAE
Hi tom

Your Answers are justified my claims on Views in terms of Performance with my
collegue. First of all thanks for that. I have one more doubt.  We are having
interface with outside source and there is no guarantee that a particular column
will come as a Charecter or Number, But the Width is rigid. It can range from 1
to 2000. so I have created a Table with 40 columns all are of V2(2000).  I have
created a view like the below
Create View View1 as
Select substr(Colnum001,1,10) Alias1, Substr(Colnum002,1,3) Alias2......
From Table 1

Is there any performance overhead on giving the Substr while selecting a Column.
 I hope it wont since most of the oracle Internal views of V$ tables are having
lot of functions associated still works fast.

I am correct in my conclusion.

Please suggest me

Thanks in advance
Followup:
Well, it won't slow down the actual "fetching" too much HOWEVER, unless you use
function based indexes:
http://osi.oracle.com/~tkyte/article1/index.html
searching on these columns could be very slow (full table scans).  If you have a
view like:

create view v as select substr( column1, 1, 10 ) c1 from t;

And you query:

select substr( column1, 1, 10 ) c1 from t where column1 = 'foo';

that can use an index on column1, whereas:

select c1 from v where c1 = 'foo';

will not unless the index was created on t(substr(column1,1,10))

If you never search on the columns, this is not a concern.  If you do, learn
about function based indexes.
Rating: 2
view performance (part b) February 04, 2002
Reviewer: Peggy Morgan from Los Angeles, CA
Here is the information you requested; the view text, the table description, and
the autotraces. The reason the timings are faster than in my original question
is that this database was moved to a different machine over the weekend.

SQL> l
  1  select text from user_views
  2* where view_name = 'PA_COST_DISTRIBUTION_LINES'
SQL> /

TEXT
------------------------------------
SELECT
       EXPENDITURE_ITEM_ID
     , LINE_NUM
     , CREATION_DATE
     , CREATED_BY
     , TRANSFER_STATUS_CODE
     , AMOUNT
     , QUANTITY
     , BILLABLE_FLAG
     , REQUEST_ID
     , PROGRAM_APPLICATION_ID
     , RESOURCE_ACCUMULATED_FLAG
     , PROGRAM_ID
     , PROGRAM_UPDATE_DATE
     , PA_DATE
     , DR_CODE_COMBINATION_ID
     , GL_DATE
     , TRANSFERRED_DATE
     , TRANSFER_REJECTION_REASON
     , BATCH_NAME
     , ACCUMULATED_FLAG
     , REVERSED_FLAG
     , LINE_NUM_REVERSED
     , SYSTEM_REFERENCE1
     , SYSTEM_REFERENCE2
     , SYSTEM_REFERENCE3
     , CR_CODE_COMBINATION_ID
     , IND_COMPILED_SET_ID
     , LINE_TYPE
     , BURDENED_COST
     , ORG_ID
     , BURDEN_SUM_SOURCE_RUN_ID
     , BURDEN_SUM_REJECTION_CODE
     , PROJECT_ID
     , TASK_ID
 FROM PA_COST_DISTRIBUTION_LINES_ALL
  WHERE
    NVL(ORG_ID,NVL(TO_NUMBER(DECODE(SUBSTRB(USERENV('CLIENT_INFO'),1,1),
 ' ',NULL,SUBSTRB(USERENV('CLIENT_INFO'),1,10))), -99)) =
   NVL(TO_NUMBER(DECODE(SUBSTRB(USERENV('CLIENT_INFO'),1,1),
 ' ',NULL,SUBSTRB(USERENV('CLIENT_INFO'),1,10))),-99)
SQL>

SQL> desc PA_COST_DISTRIBUTION_LINES_ALL
 Name                            Null?    Type
 ------------------------------- -------- ----
 EXPENDITURE_ITEM_ID             NOT NULL NUMBER(15)
 LINE_NUM                        NOT NULL NUMBER(15)
 CREATION_DATE                   NOT NULL DATE
 CREATED_BY                      NOT NULL NUMBER(15)
 TRANSFER_STATUS_CODE            NOT NULL VARCHAR2(1)
 AMOUNT                          NOT NULL NUMBER(22,5)
 QUANTITY                        NOT NULL NUMBER(22,5)
 BILLABLE_FLAG                   NOT NULL VARCHAR2(1)
 REQUEST_ID                               NUMBER(15)
 PROGRAM_APPLICATION_ID                   NUMBER(15)
 RESOURCE_ACCUMULATED_FLAG                VARCHAR2(1)
 PROGRAM_ID                               NUMBER(15)
 PROGRAM_UPDATE_DATE                      DATE
 FUNCTION_TRANSACTION_CODE                VARCHAR2(30)
 PA_DATE                                  DATE
 DR_CODE_COMBINATION_ID                   NUMBER(15)
 GL_DATE                                  DATE
 TRANSFERRED_DATE                         DATE
 TRANSFER_REJECTION_REASON                VARCHAR2(250)
 BATCH_NAME                               VARCHAR2(30)
 ACCUMULATED_FLAG                         VARCHAR2(1)
 REVERSED_FLAG                            VARCHAR2(1)
 LINE_NUM_REVERSED                        NUMBER(15)
 SYSTEM_REFERENCE1                        VARCHAR2(30)
 SYSTEM_REFERENCE2                        VARCHAR2(30)
 SYSTEM_REFERENCE3                        VARCHAR2(30)
 CR_CODE_COMBINATION_ID                   NUMBER(15)
 IND_COMPILED_SET_ID                      NUMBER(15)
 LINE_TYPE                       NOT NULL VARCHAR2(1)
 BURDENED_COST                            NUMBER(22,5)
 ORG_ID                                   NUMBER(15)
 CODE_COMBINATION_ID                      NUMBER(15)
 BURDEN_SUM_SOURCE_RUN_ID                 NUMBER(15)
 BURDEN_SUM_REJECTION_CODE                VARCHAR2(30)
 PROJECT_ID                               NUMBER(15)
 TASK_ID                                  NUMBER(15)

SQL>

SQL> l
  1  select count(*)
  2  from pa_cost_distribution_lines_all
  3* where pa_date between to_date('01-dec-2001', 'dd-mon-yyyy') and
to_date('01-jan-2002')
SQL> /
Elapsed: 00:00:10.14

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=RULE
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'PA_COST_DISTRIBUTION_LINES_N7' (N
          ON-UNIQUE)

SQL>

SQL> l
  1  select count(*)
  2  from pa_cost_distribution_lines
  3* where pa_date between to_date('01-dec-2001', 'dd-mon-yyyy') and
to_date('01-jan-2002')
SQL> /
Elapsed: 00:03:59.89

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=RULE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'PA_COST_DISTRIBUTION_L
          INES_ALL'

   3    2       INDEX (RANGE SCAN) OF 'PA_COST_DISTRIBUTION_LINES_N7'
          (NON-UNIQUE)

SQL>
Followup:
This is classic -- you are comparing APPLES to toaster ovens (and there is
simply no way to compare them)

Look at your view text:

... WHERE
    NVL(ORG_ID,NVL(TO_NUMBER(DECODE(SUBSTRB(USERENV('CLIENT_INFO'),1,1),
 ' ',NULL,SUBSTRB(USERENV('CLIENT_INFO'),1,10))), -99)) =
   NVL(TO_NUMBER(DECODE(SUBSTRB(USERENV('CLIENT_INFO'),1,1),
 ' ',NULL,SUBSTRB(USERENV('CLIENT_INFO'),1,10))),-99)

Your two queries return DIFFERENT (totally different) answers.  It is not that
the view is SLOW, it is that the question the view answers is totally different
from the query against the base tables.

These counts are different.

Look at the plan -- the view must access the table in order to evaluate the
predicate "where nvl(......".  The simple select count(*) against the base table
does not need to do that.  It is the cost of doing the table access by index
rowid that makes the view run slower (but remember -- they are answering 100%
different questions)

This is similar to comparing the performance of:

select count(*) from emp;

to

select count(*) from dept;

you cannot -- the view is not "evil" here -- you simply are comparing two things
that are not comparable.

Compare the performance of:

select count(*)
  from pa_cost_distribution_lines
 where pa_date between to_date('01-dec-2001', 'dd-mon-yyyy')
                              and to_date('01-jan-2002')

to

select count(*)
 FROM PA_COST_DISTRIBUTION_LINES_ALL
where pa_date between to_date('01-dec-2001', 'dd-mon-yyyy')
                      and to_date('01-jan-2002')
  AND
    NVL(ORG_ID,NVL(TO_NUMBER(DECODE(SUBSTRB(USERENV('CLIENT_INFO'),1,1),
 ' ',NULL,SUBSTRB(USERENV('CLIENT_INFO'),1,10))), -99)) =
   NVL(TO_NUMBER(DECODE(SUBSTRB(USERENV('CLIENT_INFO'),1,1),
 ' ',NULL,SUBSTRB(USERENV('CLIENT_INFO'),1,10))),-99)

and then talk to us -- you'll find they perform the same.
Rating: 4
Question on View Constraints February 05, 2002
Reviewer: Ravi from NY,NY
Tom,

I am a bit confused about Constraints on Views. As per the documentation, they
are not enforced. So what is the use for constraints on views ?

As per "SQL Reference" documentation :
<QUOTE>
View constraints (at the view and at the column or attribute level) are
declarative only. That is, Oracle does not enforce them. However, operations on
views are subject to the integrity constraints defined on the underlying base
tables, so you can enforce constraints on views through constraints on base
tables.
</QUOTE>

Thanks for your time.
Followup:
They are used for complex query rewrites with materialized views and such.  It
is more "meta data" -- it gives the optimizer more information, allows for a
broader range of query rewriting to take place.
Rating: 4
Want solution February 05, 2002
Reviewer: A reader
Hi, Tom,

View is as good as base table, i agree with that.

Our senario is:

We have a transactional DB, and we are developing forcast&analysis system based
on these tables at the same time with lots of transactions happened.
In the newly developed system, we normally use forms as interface to provide
various demand forcast to the end user.
Problem is: we created lots of views which involved lots of analytical
functions, and the forms are based on these views. By this way it results in
VERY slow performance even for a sigle query.
Do you have any suggestions about it? We don't have money to build a data
warehouse BTW.

Thanks
Followup:
Quite often the structure used for your operational system does not lend itself
to efficient DSS/OLAP querying.

Have you looked at MATERLIALIZED views?
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76994/mv.htm#721
You can "pre-answer" these slow queries and then query the answers very quickly.
Rating: 5
We join two different schemas to create a view February 05, 2002
Reviewer: Alex Norman from Dayton, OH, USA
We have a slighly different scenario. We have two schemas with exactly same
tables but the data is different. We need to present a read only user a unified
view of the whole database. My approach is to create a third schema containing
views on the two schemas to be used for queries. For example, a view
THIRDSCHEMA.DEPT is a SELECT * FROM FIRSTSCHEMA.DEPT UNION ALL
SECONDSCHEMA.DEPT. Read only users will query the THIRDSCHEMA.DEPT. The problem
is most of the queries are fairly complicated with GROUP BY, EXISTS, etc on
these views and I am wondering if performance will ever be hit on those views.
The tables are fairly huge, 1 -15 GB each.

If performance may pose a problem, how would you approach this? We considered
replication/materialized views from the first two schemas to the third one, but
dropped it due to lack of space. Thanks a lot in advance.
Followup:
I would have tried as hard as possible to avoid the use of multiple tables in
different schemas if I had a requirement to present the information as a single
instance.

I would have most likely looked at using VPD (see
http://osi.oracle.com/~tkyte/article2/index.html
) and table partitioning.  I would have added a column (say "schema_name") and
the VPD would normally return the predicate "where schema_name = sys_context(
'my_ctx', 'schema_name' )".  For the read only user it would return null (no
predicate -- sees all data).

Physical partioning on the schema name would be very efficient (partition
elimination would kick in based on the predicate) and each app would think they
had their own schema and the read only users would have no issues with UNIONS
getting in the way.

You may run into performance issues with UNION ALLS in the views and performing
complex operations on this (not because they are VIEWS but because there is a
UNION ALL that takes place before the aggregation can)
Rating: 4
View Performance February 09, 2002
Reviewer: Jyotiprakash Nandi from Calcutta
Hello Tom

Thanks a lot for your valuable suggestion.

Regarding View Performance a have a bit doubt. Suppose I have two tables
Account_Master and Invoice_Details. Basically Invoice table is dynamic in nature
and it’s volume more than one million at a time. I have created a view with
hints on those and join condition is account no. Now I am retrieving data from
the view, passing Account Number as a parameter.

Following performance problem I have noticed.

1. When I am retrieving data from tables, passing
   Account Number as a parameter and scanning index it
   takes very less time rather than from view that takes a
   lot.

2. In case of optimization when I retrieve data from view
   it takes lot of resource rather than simple table
   joining.

Now

1. Can I create view dynamically by passing parameters as
   on required basis?

2. When I retrieve data from view on the basis of
   parameters is it loading all the data i.e. the result
   set of Create View in memory?

3. What is the best way to handle this type of view on
   which where clause will be parameterized.

Please suggest.

Thanks in advance.

Regards
JP
USHACOMM INDIA
Followup:
1) that does not make syntactic sense to me.

2) impossible.  A VIEW is nothing more then a stored query (i'll say it again A
VIEW is NOTHING more then a STORED QUERY).  If you run:

   select * from view;
   select * from ( <text of view> );

they are the SAME.  Much like the comment directly above -- I believe you are
once again comparing apples to toaster ovens and your VIEW is nothing like your
QUERY (else they would in fact be the SAME)
Rating: 5
View Performance - Follow Up February 10, 2002
Reviewer: Jim from Oz
RE - Comment above by Jyotiprakash Nandi

Why don't you post you view definition
and test results ?

I have completed my own testing on views and results
were as per Tom has stated.
Rating: 4
Inline Views as an optimization option July 15, 2002
Reviewer: Subhro from Mumbai, India
I am using a Oracle Transparent Gateway. I have statement wherein a local table
and a remote database table are joined. The statement is like...

Select l.nme NAME, r.adrs_text ADDRESS
from cstmr_nms l, cstmr_adrs@rmt_db r
where r.adrs_id=l.adrs_id
r.STATE='CA'
and l.nme like 'JOHN%';

Will rewriting the statement as

Select l.nme NAME, r.adrs_text ADDRESS
from cstmr_nms l, (SELECT adrs_id, adrs_text from cstmr_adrs@rmt_db where
STATE='CA') r
where r.adrs_id=l.adrs_id
and and l.nme like 'JOHN%';

provide any performance improvement...

If it does, does it have to explicitly written like this or the Oracle Optimizer
may rewrite the first statment in the form of the second statement...
Followup:
I've no idea.

given I have no idea what your structures, indexes, volume of data, existing
performance, desired performance, etc etc etc etc.....
Rating: 4
Question on columns of views August 08, 2002
Reviewer: Kapil from INDIA
Hi Tom,
Do the columns of a view get stored in the data dictionary the way the columns
of a table are stored in all_tab_columns?
My requirement is to find out the columns of a view in a PRO*C code.

Kapil
Followup:
yes.

just query all_tab_columns
Rating: 4
September 12, 2002
Reviewer: Kapil from INDIA
Thanks, Tom
That was helpful
Rating: 4
to_date( '15-Nov-2002') without 'dd-mon-yyyy' November 15, 2002
Reviewer: Robert from PA
>>> And also -- please (I would have thought we would have all learned this
lesson)
>> DON'T USE 2 DIGIT DATES...Use this:
>>where pa_date between to_date( '01-dec-2001', 'dd-mon-yyyy' )

Tom, is there anything bad about doing just
to_date( '01-dec-2001') w/o the 'dd-mon-yyyy' ?

Thanks
Followup:
yes, big time.

Some day your application will be installed into a database where the default
date mask isn't dd-mon-rr

some day your application will be run by a user that set the default date mask
to something other then dd-mon-rr

never use two digit years.
always use formats on the way INTO the database and the way OUT of the database
to avoid any chance for confusion/errors/bugs in that regards.
Rating: 5
Your view on a view please :;-)) December 11, 2002
Reviewer: Kalita from UK
I am not really sure if this is relevant to the original post. If it’s not,
please ignore it. Sorry about that.
We have some set of tables that hold Quotation data for individuals as well as
group schemes. The group id would be null in case of non-group scheme related
data. Now for group scheme quote sets we created a view from all relevant tables
but did not include 'where group id is not null'. I don’t like it because its
called group scheme quote sets but includes all quote sets. The person who
created the view tells me that it doesn't make a difference since the view would
always be accessed with the group scheme id and the performance would also be
same. I feel he is right about performance but don’t like the view definition
without the group id not null check. Would like to know your view on this.

Thanks.
Followup:
well, it can affect the plans -- consider the difference in query plans between
v1 and v2:

ops$tkyte@ORA920.LOCALHOST> create table t1 ( x int primary key );
Table created.

ops$tkyte@ORA920.LOCALHOST> create table t2 ( x int );
Table created.

ops$tkyte@ORA920.LOCALHOST> exec dbms_stats.set_table_stats( user, 'T1', numrows
=> 10000, numblks => 10000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.LOCALHOST> exec dbms_stats.set_table_stats( user, 'T2', numrows
=> 10000, numblks => 10000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.LOCALHOST> create or replace view v1
  2  as
  3  select * from t2;

View created.

ops$tkyte@ORA920.LOCALHOST> create or replace view v2
  2  as
  3  select * from t2 where x is not null;

View created.

ops$tkyte@ORA920.LOCALHOST> set autotrace traceonly explain

ops$tkyte@ORA920.LOCALHOST> select * from t1 where x not in ( select x from v1
);

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=481504 Card=500 Bytes=6500)
   1    0   FILTER
   2    1     INDEX (FAST FULL SCAN) OF 'SYS_C003648' (UNIQUE) (Cost=4 Card=500
Bytes=6500
          )

   3    1     TABLE ACCESS (FULL) OF 'T2' (Cost=963 Card=500 Bytes=6500)

that'll full scan T1 (via the primary key in this example) and then for EVERY
ROW in T1, full scan T2

ops$tkyte@ORA920.LOCALHOST> select * from t1 where x not in ( select x from v2
);

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=974 Card=9500 Bytes=247000)
   1    0   HASH JOIN (ANTI) (Cost=974 Card=9500 Bytes=247000)
   2    1     INDEX (FAST FULL SCAN) OF 'SYS_C003648' (UNIQUE) (Cost=4
Card=10000 Bytes=13
          0000)

   3    1     TABLE ACCESS (FULL) OF 'T2' (Cost=963 Card=500 Bytes=6500)

that will outer join T1 to T2 and only keep the rows in T1 that don't have a
mate in T2.  In general, this is infinitely prefered.  But it is only
semantically possible if it is known that X is not null!

So, if X is not null -- it wouldn't hurt to state that in the view...
Rating: 5
December 11, 2002
Reviewer: Kalita
Thanks for you quick reply Tom.
Rating: 4
using a Materialized view instead of a view December 27, 2002
Reviewer: Moh'd Saeed
I have an app that uses a view. I want to experiment with replacing that view
with a Materialized view  but don't have access to the original sql in the app.
Will the query rewrite option allow me to achieve it ? I can ofcourse change the
view code.

The view does a comparrison using sysdate like:

and (x is null or ( x is not null and sysdate >= x ))

When I create the MV using the select in the view it rejects that statement!! I
also could not create a refresh on commit for a MV based on multiple tables, is
that a restriction ? I am using 8.1.7

So I created the MV without the above "and" and instead used it in a sql
statement. Then it does not use the MV and instead reverts back to the base
tables. If I query the original view it does not use the MV either. I also
created another view without the sysdate comparrison and it still did not use
the MV.

What are my choices ? Response with an example would be greatly appreciated.

Thanks in advance for your assistance.

I purchased your book (worth it's weight in gold) and read Ch 13, but it seems
you did not touch on that specific issue. I have also searched this site and did
not seem to find any question along this line.
I apologize in advance if you feel this is not that correct thread to post this
question to.

Thanks
Followup:
basically -- how could query rewrite work on that view?  Think about it:

...
and (x is null or ( x is not null and sysdate >= x ))
.....

the instant after the MV is created -- it is STALE already.  You can create it
as a MV for sure, and you can query it directly but no query rewrite.

You need to make sure you really want to do this first however -- keeping in
mind that the instant after the MV is created, it is out of date -- would return
a different answer then the real view would since it is based on SYSDATE.

I'm at a loss as to give you an example since I have no real idea what your
particular issue is.  SYSDATE isn't going to work real well.  Refresh on commit
restrictions are documented here:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76994/mv.htm#721
Rating: 5
using a Materialized view instead of a view December 30, 2002
Reviewer: A reader
Hi Tom:
Absolutely agreed, that the MV is stale as soon as it is created if I use the
sysdate (I guess that's why it is a restriction).
I was trying to find if there is a work around for that.

See, I can't change the application code, in which the queries access the view.
The view contains the "and with the sysdate". So I was thinking of creating a
MV, minus the and condition with the sysdate. That is possible and I have done
that. So I have a MV which is identical to the view without the "sysdate".

What I was hoping for is that when the app accesses the view, the oracle
optimizer will see a MV with the same sql syntax (minus the and with the
sysdate) and rewrite the query to use the MV to generate a partial result set
and then apply the "and with the sysdate" conditon against that result set.

Would that work, or am I missing something ?
Followup:
No workaround - the MV would have to be in a continous state of REFRESH!!!  it
could never refresh on commit -- because it would never finish refreshing.

now, if you drop the entire "x is null or ( x is not null and sysdate >= x )"
from the MV, we would be able to use the MV -- sure -- but it really depends on
the question being asked and the underlying complexity of the view itself as to
whether this makes sense or not.

Insufficient data to make a response (and please don't post it here -- it you
get all of the info together -- you can post it as a question when I'm taking
them)
Rating: 5
View on remote tables January 15, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom
We are using 9iR2 with a db link to 8i(8.1.7.4) database. On the 9i server, I
created a view as:

create view v_local as select * from remote_schema.t_remote@db_link;

When I checked the user_tab_columns on 9i server, I found that all the column
definitions of remote table t_remote were present in the local data dictionary
view as columns of view v_local. Even if I dropped the link, my stored
procedures referencing v_local would still compile, though they won't run.

The problem happened when someone changed a column name in remote table
t_remote. Now when I do a select against the view v_local, it gave <column name>
invalid identifier...I did

create or replace view v_local as select * from remote_schema.t_remote@db_link;

This didn't solve the problem. I had to drop view and recreate and grant
permissions.

My question is if view is just a stored query, why the remote table column
definitions are being stored in the local database? Why Oracle doesn't just
execute the stored statement select * from remote_schema.t_remote@db_link?

Thanks
Followup:
Well, this is actually one of the nice features of a view with dblinks -- it
imports the meta data OVER into your local database.  Makes compiling faster
(and more "robust"), makes query tools happy (they go after user_tab_columns and
such)...

If you don't want that effect, use a synonym.

Now, I cannot reproduce your issue -- I did this:

scott@ORA920> create view emp_view as select * from emp@ora817dev.us.oracle.com;
View created.

scott@ORA920> select * from emp_view;

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

14 rows selected.

then in the 817 instance I:

  1* alter table emp add  new_job varchar2(30)
scott@ORA817DEV> /

Table altered.

scott@ORA817DEV> update emp set new_job = job;

14 rows updated.

scott@ORA817DEV> alter table emp drop column job;

Table altered.

Now, in 9iR2 I found:

scott@ORA920> /
select * from emp_view
              *
ERROR at line 1:
ORA-00904: "JOB": invalid identifier

but a simple:

scott@ORA920> create or replace view emp_view as select * from
emp@ora817dev.us.oracle.com;

View created.

scott@ORA920> select * from emp_view;

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

14 rows selected.

fixed it right up -- so, can you do a cut and paste in the manner I do - just
like above (unedited, straight cut and pastes) and we'll see what might have
gone wrong.
Rating: 5
It works January 15, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom
I tried again today morning and create or replace view thing worked. The reason
it didn't work before was that when I was playing with views on the local
database, the remote database DBA was dropping the underlying tables and
changing column names, almost during the same time. That is why I was getting
weird results.

Actually storing the remote table meta data locally is a blessing. In case the
remote database is unavailable because of network or maintenance, local
developers can still at least compile their packages.

Thanks for your prompt response and perfectly clear examples as ever...
Rating: 5
What is the difference February 10, 2003
Reviewer: Nasser
Tom

What is the difference between inserting a date like

Create table t(x date);
SQL> Insert into t values('2003-01-01');

1 row created.

SQL> commit;

Commit complete.

and like below, i.e. after applying a date format....

SQL> ed
Wrote file afiedt.buf

  1* Insert into t values(to_date('2003-01-01','yyyy-mm-dd'))
SQL> /

1 row created.

SQL> commit;

Commit complete.

SQL> select * from t;

X
----------
2003-02-08
2003-02-08
2003-02-07
2003-02-08
2003-01-01
2003-01-01

The output(last 2 rows ) as you see is the same), if I'm importing data into a
different database, I can do to_char and to_date into appropriate format , Isnt
it.. plsease discuss..
Followup:
the formats are just that -- FORMATS.

they tell the database what to expect.

Your DEFAULT format is apparently yyyy-mm-dd, hence

Insert into t values('2003-01-01');
Insert into t values(to_date('2003-01-01','yyyy-mm-dd'))

are exactly the same in your database -- but the better coding practice is the
second one.  I would encourage you not to rely on a default format mask (it can
change) but rather use an explicit one on input and output.

At the end of the day in your example, the 7 bytes ultimately stored were
identical.
Rating: 5
Simple thing for you..Great thing for me.. March 09, 2003
Reviewer: Thaha Hussain from Manama, Bahrain
I have 3  tables in Oracle

1. Students
----------------
-RegisterNumber
-Name

2. Admission_Docs
-----------------
-RegisterNumber
-DocumentID
-Date

3.DocumentMaster
-----------------
-DocumentID
-Description

'Students' and 'DocumentMaster' are Master Tables while Admission_Docs is a
transaction table. Students those who submit a particular document only will be
appended to 'Admission_Docs'

Sample Data:

STUDENTS
------------
RegisterNumber    Name
123               Moosa Davood
342               usman
987               John

ADMISSION_DOCS
----------------
RegisterNumber   DocumentID     Date
342                1            12/12/2002
342                2            13/12/2002
987                2            11/11/2002

DOCUMENT_MASTER
--------------------
DocumentID       Description
    1            Transfer Certificate
    2            Nativity Certificate
    3            Characer Certificate

For a particular student with RegisterNumber 342, I need the output like this

RegisterNumber     Name       Document                Submitted     Date
---------------------------------------------------------------------------------
-
342                Usman      Transfer Certificate       Yes        12/12/2002
342                Usman      Nativity Certificate       Yes        13/12/2002
342                Usman      Character Certificate       No

1. Is it possible to create an updatable view for this?
2. Else which is the best query to display this?

Thaha Hussain
Followup:
anything is possible.

The real question is "is it in any way sensible".

I would say it is not sensible to create or update STUDENT records here.  Why?
they are not key preserved.  The student record will appear as many times as
there are documents.  So, no, I would not allow modifications to the STUDENT
object here.

Would it be sensible to update the DOCUMENT_MASTER here?  I don't think so.  It
doesn't to me make sense to allow updates to the document master table
semantically speaking in the context of a student.  Also -- each document will
appear in this view for every student -- doesn't make sense.

So, that'll leave ADMISSION_DOCS.  This is one that might make sense HOWEVER,
since we'll have to outer join to it -- it won't be POSSIBLE to modify it
through the view UNLESS we trained the view to do that via instead of triggers.
So while it might make sense for this one, it isn't something that will happen
naturally and I would advise against it.

So, I would probably not use a view, but rather query:

ps$tkyte@ORA920> variable bv number;
ops$tkyte@ORA920> exec :bv := 456;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from students;

REGISTERNUMBER NAME
-------------- -----
           123 tom
           456 bill
           789 mary

ops$tkyte@ORA920> select * from document_master;

DOCUMENTID DESCRI
---------- ------
         1 t cert
         2 n cert
         3 c cert

ops$tkyte@ORA920> select * from admission_docs;

REGISTERNUMBER DOCUMENTID DT
-------------- ---------- ---------
           456          1 09-MAR-03
           456          2 09-MAR-03
           789          2 09-MAR-03

ops$tkyte@ORA920> select a.* , decode(b.dt,null,'No','Yes') submitted, b.dt
  2   from (
  3  select *
  4    from students, document_master
  5   where students.registerNumber = :bv
  6        ) a, admission_docs b
  7   where a.registerNumber = b.registerNumber(+)
  8     and a.documentId = b.documentId (+)
  9  /

REGISTERNUMBER NAME  DOCUMENTID DESCRI SUB DT
-------------- ----- ---------- ------ --- ---------
           456 bill           1 t cert Yes 09-MAR-03
           456 bill           2 n cert Yes 09-MAR-03
           456 bill           3 c cert No

ops$tkyte@ORA920>

And directly modify the 3 objects as needed -- nothing fancy, don't get carried
away.
Rating: 5
- March 09, 2003
Reviewer: A reader from Bangalore
Tom,

This has nothing to do with the thread...but this is taken from one of your
answers.

You were demonstrating the difference 'IS NOT NULL' clause can make.

<<
ops$tkyte@ORA920.LOCALHOST> select * from t1 where x not in ( select x from v1
);

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=481504 Card=500 Bytes=6500)
   1    0   FILTER
   2    1     INDEX (FAST FULL SCAN) OF 'SYS_C003648' (UNIQUE) (Cost=4 Card=500
Bytes=6500
          )

   3    1     TABLE ACCESS (FULL) OF 'T2' (Cost=963 Card=500 Bytes=6500)

that'll full scan T1 (via the primary key in this example) and then for EVERY
ROW in T1, full scan T2
>>

My question is on your sentence here. I thought only nested loops did this. I
mean for every row in the driving table it looks for the matching record in the
second table.

How did you tell from the plan that it gets the records this way?

Any info would be greatly appreciated.

regards
Followup:
thats what the filter is doing.  filter is like "for each row"
Rating: 5
Continuation of the discussion... March 10, 2003
Reviewer: K.O. Thaha Hussain from Manama, Bahrain
I.

I understood the view stuff and your query is simply great! I doubt you
misunderstood me.

I'm using Delphi with Oracle.

Actually what I meant was - if there is some technique to get the output of your
query as an object, it is easy for me to upgrade the Admission_docs through a
Grid in the Front End connected to this view.

For example by Double clicking on a particular row of the grid, It should come
as 'Yes' another click 'No'  and a provision to supply the date if it is 'Yes'.
Those work should immediately append a row in 'admission_docs' Or should remove
the curresponding row from 'admission_docs' according to the status.

II.
If the 'admission_docs' transaction table design is wrong, please suggest the
best practice. Please... I like you very much..

'Having little knowledge of what is available is a bad thing'. So these
questions ;-)

Thanx in advance,
Thaha Hussain
Followup:
Just be careful -- this sort of stuff can get confusing.  In this example, I'll
permit the modification of submitted and dt but not any other columns - if you
want to support that -- you'll have to write the code (but I don't recommend it
as the records are "repeated" in the set -- gets ugly and ambigous).

admission_docs is the only table that it makes set to use in there.

admission_docs is a traditional association object, it is not a bad design -- it
is very standard.

ops$tkyte@ORA920> create or replace view V
  2  as
  3  select a.* , decode(b.dt,null,'No','Yes') submitted, b.dt
  4   from (
  5  select *
  6    from students, document_master
  7        ) a, admission_docs b
  8   where a.registerNumber = b.registerNumber(+)
  9     and a.documentId = b.documentId (+)
 10  /

View created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace trigger v_trigger
  2  instead of update on v
  3  begin
  4      delete from admission_docs
  5       where registerNumber = :old.registerNumber
  6         and documentid = :old.documentId;
  7
  8      if ( :new.submitted = 'Yes' )
  9      then
 10          insert into admission_docs
 11          (registerNumber,documentId,dt)
 12          values
 13          (:new.registerNumber,:new.documentId,:new.dt);
 14      end if;
 15  end;
 16  /

Trigger created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> update v
  2     set submitted = 'Yes', dt = sysdate
  3   where registerNumber = 456 and documentid = 3;

1 row updated.

ops$tkyte@ORA920>
ops$tkyte@ORA920> update v
  2      set submitted = 'No'
  3   where registerNumber = 456 and documentid = 1;

1 row updated.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from v where registerNumber = :bv;

REGISTERNUMBER NAME  DOCUMENTID DESCRIPTION          SUB DT
-------------- ----- ---------- -------------------- --- ---------
           456 bill           1 t cert               No
           456 bill           2 n cert               Yes 10-MAR-03
           456 bill           3 c cert               Yes 10-MAR-03

ops$tkyte@ORA920>
Rating: 5
Thank You! March 11, 2003
Reviewer: Thaha Hussain from Manama, BAH
Thank you very much! Yesterday night, me and my wife talked a lot about such a
wonderful person like you.

I believe, that simplicity is your style!

Let Allah help you!

Thaha Hussain
Rating: 5
Master or Masters? March 11, 2003
Reviewer: Thaha Hussain from Manama, BAH
Which one you like - 'Document_Master' or 'Document_Masters'?
Followup:
masters would be in keeping with students

but master sounds better ;)

totally up to you.
Rating: 4
Question on view performance April 14, 2003
Reviewer: Eric
Hi Tom,

I firmly believed that
1. View is a stored query
2. The performance impact of view is subtle
until I did the following simple test about it...

Senario:
1. table1 with 1.5M records
2. v_table1 is a view of table1 ( create view v_table1 as select * from table1;
)
3. To query 10,000 times from table1 and v_table1 using index keys, and it took
1.75s and 3.14s respectively.

Do you think the 80% overhead is reasonable? Please correct me if I am wrong.

P.S. SQLs
    select count(*) from table1 where <using index key>;
    select count(*) from v_table1 where <using index key>;
    and I confirmed the SQLs is ok by using tkprof.

TIA,
Eric
Followup:
I see no wide disparity.  A view is a stored query -- so in fact there is more
code executed by the server when processing a view -- however the overhead is
marginal when compared to the potential gains.

Measuring elapsed time isn't the best (well, it is one of the worst) ways to
measure this.  Run that test 100 times and you'll see what I mean.  Here is one
run I did (big_table has 4 million rows but the size isn't relevant here at all.
 The impact of a view is entirely during the PARSE phase - after that, it is
just "not relevant")

big_table@ORA920> create or replace view v as select * from big_table;

View created.

big_table@ORA920>
big_table@ORA920> declare
  2      l_start number;
  3      l_run1  number;
  4      l_run2  number;
  5      l_rec   big_table%rowtype;
  6  begin
  7      insert into run_stats
  8      select 'before', stats.* from stats;
  9
 10      l_start := dbms_utility.get_time;
 11      for i in 1 .. 10000
 12      loop
 13          select * into l_rec from big_table where id = i;
 14      end loop;
 15      l_run1 := (dbms_utility.get_time-l_start);
 16      dbms_output.put_line( l_run1 || ' hsecs' );
 17
 18      insert into run_stats
 19      select 'after 1', stats.* from stats;
 20      l_start := dbms_utility.get_time;
 21      for i in 1 .. 10000
 22      loop
 23          select * into l_rec from v where id = i;
 24      end loop;
 25      l_run2 := (dbms_utility.get_time-l_start);
 26      dbms_output.put_line( l_run2 || ' hsecs' );
 27      dbms_output.put_line
 28      ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) ||
 29        '% of the time' );
 30
 31      insert into run_stats
 32      select 'after 2', stats.* from stats;
 33  end;
 34  /
191 hsecs
195 hsecs
run 1 ran in 97.95% of the time

PL/SQL procedure successfully completed.

And tkprof says:

SELECT * from big_table where id = :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute  10000      2.13       2.01          0          0          0           0
Fetch    10000      0.89       0.86          0      40000          0       10000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    20001      3.03       2.88          0      40000          0       10000

Rows     Row Source Operation
-------  ---------------------------------------------------
  10000  TABLE ACCESS BY INDEX ROWID BIG_TABLE (cr=40000 r=0 w=0 time=651858 us)
  10000   INDEX UNIQUE SCAN BIG_TABLE_PK (cr=30000 r=0 w=0 time=396834
us)(object id 31697)
********************************************************************************
SELECT * from v where id = :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute  10000      2.22       2.04          0          0          0           0
Fetch    10000      0.90       0.84          0      40000          0       10000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    20001      3.13       2.89          0      40000          0       10000

Rows     Row Source Operation
-------  ---------------------------------------------------
  10000  TABLE ACCESS BY INDEX ROWID BIG_TABLE (cr=40000 r=0 w=0 time=637516 us)
  10000   INDEX UNIQUE SCAN BIG_TABLE_PK (cr=30000 r=0 w=0 time=382123
us)(object id 31697)

for all intents and purposes -- they are exactly the same
Rating: 5
Thanks! April 14, 2003
Reviewer: Eric
Hi Tom,

Thanks very much, you answered all my questions (actually my second question is
the statistics of tkprok).

Regards,
Eric
Rating: 5
Describing a table June 18, 2003
Reviewer: TH from Manama, BAH
Without using a view, is it possible to decribe the structure of a table
alphabetically?
Followup:
sure, you can query the base sys tables (but that wouldn't be a good idea).

describe returns the columns in the order they appear in the data dictionary
only.
Rating: 5
http://osi.oracle.com/~tkyte/article1/index.html June 22, 2003
Reviewer: A reader
tom,

is this the url you post your articles - other than asktom.oracle.com, i see
only article 1,2 . how to go to the index page for your articles - cause its a
wealth for all of us.

Please provide me with the link if one exists.

thank you
Followup:

http://asktom.oracle.com/~tkyte/
is the page, available from the asktom home page under "tidbits"
Rating: 5
view on remote server June 27, 2003
Reviewer: A reader
Tom,

I am querying over a db link, a view exists on the remote server, my query takes
so much time just for count(*) and i have provided the autotrace, with this
information, will you be able to help me in understanding why its taking so much
time to execute the query....

thanks for all your help

CREATE OR REPLACE VIEW OMDATAPULL AS
SELECT
GEIORDERS.CUSTNUM,
GEIORDERS.PONUM,
GEIORDERS.REQNUM,
GEIORDERS.SYSTEMORIGIN,
GEIORDERS.DATELASTUPDATED,
GEIORDERLINES.REQUESTDATE,
GEIORDERLINES.LINENUM,
--GEIORDERLINES.REQNUM,
GEIORDERLINES.SEQNUM,
--GEIORDERLINES.SYSTEMORIGIN,
GEIORDERLINES.CURRENCYTYPE,
GEIORDERLINES.CATALOGNUM,
--GEIORDERLINES.PONUM,
GEIORDERLINES.STATUS,
GEIORDERLINES.NOTES,
GEIORDERLINES.INVOICENUM,
GEIORDERLINES.CARRIER,
GEIORDERLINES.BILLOFLADING,
GEIORDERLINES.CARRIERPHONE,
GEIORDERLINES.SHIPMETHOD,
GEIORDERLINES.EXTPRICE,
GEIORDERLINES.MARKS,
GEIORDERLINES.CUSTREQUESTDATE,
GEIORDERLINES.ACTUALSHIPDATE,
GEIORDERLINES.SCHSHIPDATE,
GEIORDERLINES.DELIVERYDATE,
--GEIORDERLINES.DATELASTUPDATED,
GEIORDERLINES.SHIPPINGLOCATION,
GEIORDERLINEDATES.ACTUALDFARETURNDATE,
GEIORDERLINEDATES.ACTUALAPPRDRAWISSUEDATE,
GEIORDERLINEDATES.ACTUALAPPRCONSTRUCTDATE,
GEIORDERLINEDATES.ACTUALEQUIPMANUFDATE,
GEIORDERLINEDATES.ACTUALEQUIPTESTDATE,
GEIORDERLINEDATES.BASEDFARETURNDATE,
GEIORDERLINEDATES.BASEEQUIPTESTDATE,
GEIORDERLINEDATES.BASEAPPRCONSTRUCTDATE,
GEIORDERLINEDATES.SCHAPPRCONSTRUCTDATE,
GEIORDERLINEDATES.SCHAPPRDRAWISSUEDATE,
GEIORDERLINEDATES.SCHEQUIPTESTDATE,
GEIORDERLINETRACKING.TRACKINGNUM,
GEIORDERLINETRACKING.CARRIERSTATUS
--GEIORDERLINETRACKING.DELIVERYDATE,
--GEIORDERLINETRACKING.DATELASTUPDATED
FROM
GEIORDERS,GEIORDERLINES,GEIORDERLINEDATES,GEIORDERLINETRACKING
WHERE
GEIORDERS.REQNUM=GEIORDERLINES.REQNUM AND GEIORDERS.REQNUM=GEIORDERLINES.REQNUM
AND
GEIORDERLINES.REQNUM= GEIORDERLINEDATES.REQNUM    AND
GEIORDERLINES.CUSTNUM= GEIORDERLINEDATES.CUSTNUM    AND
GEIORDERLINES.SYSTEMORIGIN= GEIORDERLINEDATES.SYSTEMORIGIN AND
GEIORDERLINES.LINENUM= GEIORDERLINEDATES.LINENUM AND
GEIORDERLINES.SEQNUM= GEIORDERLINEDATES.SEQNUM AND
GEIORDERLINES.REQUESTDATE= GEIORDERLINEDATES.REQUESTDATE AND
GEIORDERLINES.REQNUM= GEIORDERLINETRACKING.REQNUM AND
GEIORDERLINES.CUSTNUM= GEIORDERLINETRACKING.CUSTNUM AND
GEIORDERLINES.SYSTEMORIGIN= GEIORDERLINETRACKING.SYSTEMORIGIN AND
GEIORDERLINES.LINENUM= GEIORDERLINETRACKING.LINENUM AND
GEIORDERLINES.SEQNUM= GEIORDERLINETRACKING.SEQNUM AND
GEIORDERLINES.REQUESTDATE= GEIORDERLINETRACKING.REQUESTDATE

SQL>set autotrace on
SQL>set time on
17:29:51 SQL>select count(*) from OMDATAPULL@OMDATA;

  COUNT(*)
----------
       138

Elapsed: 00:12:744.40

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT (REMOTE) Optimizer=CHOOSE (Cost=14896 Card=
          1 Bytes=121)

   1    0   SORT (AGGREGATE)
   2    1     NESTED LOOPS (Cost=14896 Card=1 Bytes=121)
   3    2       NESTED LOOPS (Cost=14884 Card=4 Bytes=336)
   4    3         MERGE JOIN (Cost=14884 Card=4 Bytes=184)
   5    4           SORT (JOIN) (Cost=1075 Card=125795 Bytes=1132155)
   6    5             INDEX (FAST FULL SCAN) OF 'XPKGEIORDERS' (UNIQUE DEVECOM.
          ) (Cost=96 Card=125795 Bytes=1132155)                        US.ORACL
                                                                       E.COM

   7    4           SORT (JOIN) (Cost=13809 Card=473406 Bytes=17516022
          )

   8    7             INDEX (FAST FULL SCAN) OF 'XPKGEIORDERLINES' (UN DEVECOM.
          IQUE) (Cost=505 Card=473406 Bytes=17516022)                  US.ORACL
                                                                       E.COM

   9    3         INDEX (UNIQUE SCAN) OF 'XPKGEIORDERLINEDATES' (UNIQU DEVECOM.
          E)                                                           US.ORACL
                                                                       E.COM

  10    2       INDEX (RANGE SCAN) OF 'XPKGEIORDERLINETRACKING' (UNIQU DEVECOM.
          E) (Cost=3 Card=340573 Bytes=12601201)                       US.ORACL
                                                                       E.COM

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

17:42:33 SQL>
Followup:
and if you run it at the remote site, what then. (hint try driving site hint)
Rating: 5
view on remote server June 27, 2003
Reviewer: A reader
Tom,
The version is 9iR2 and OS is Windows 2000.

thanks
Rating: 4
June 27, 2003
Reviewer: A reader
tom,

please see below for the  trace...
i am sorry, i did not get you .. what do you mean by "(hint try driving site
hint")

19:15:27 SQL>conn bomdata/bomdatadv@om
Connected.
19:15:42 SQL>set autotrace on
19:15:54 SQL>set time on
19:15:57 SQL>select count(*) from OMDATAPULL@OMDATA;
select count(*) from OMDATAPULL@OMDATA
                                *
ERROR at line 1:
ORA-02019: connection description for remote database not found

Elapsed: 00:00:00.91
19:16:02 SQL>select count(*) from OMDATAPULL;

  COUNT(*)
----------
       138

Elapsed: 00:05:305.07

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=14896 Card=1 Bytes=1
          21)

   1    0   SORT (AGGREGATE)
   2    1     NESTED LOOPS (Cost=14896 Card=1 Bytes=121)
   3    2       NESTED LOOPS (Cost=14884 Card=4 Bytes=336)
   4    3         MERGE JOIN (Cost=14884 Card=4 Bytes=184)
   5    4           SORT (JOIN) (Cost=1075 Card=125795 Bytes=1132155)
   6    5             INDEX (FAST FULL SCAN) OF 'XPKGEIORDERS' (UNIQUE
          ) (Cost=96 Card=125795 Bytes=1132155)

   7    4           SORT (JOIN) (Cost=13809 Card=473406 Bytes=17516022
          )

   8    7             INDEX (FAST FULL SCAN) OF 'XPKGEIORDERLINES' (UN
          IQUE) (Cost=505 Card=473406 Bytes=17516022)

   9    3         INDEX (UNIQUE SCAN) OF 'XPKGEIORDERLINEDATES' (UNIQU
          E)

  10    2       INDEX (RANGE SCAN) OF 'XPKGEIORDERLINETRACKING' (UNIQU
          E) (Cost=3 Card=340573 Bytes=12601201)

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

19:21:13 SQL>
19:21:13 SQL>
19:22:11 SQL>
Followup:
driving site:
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96533/hintsref.htm#5699
Looking at the query performance on the REMOTE site -- doesn't look like you are
going to get it to go faster (eg: not a distributed database issue as much as
"query slow in the first place").

Look at the underlying query behind this view, look at the settings on the
server -- I see merge join, unusual -- do they have hash joins disabled?
Rating: 4
view on remote June 27, 2003
Reviewer: A reader
Tom,
hash join is enabled...

please see..

SQL>sho parameter

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
O7_DICTIONARY_ACCESSIBILITY          boolean TRUE
active_instance_count                integer
always_anti_join                     string  NESTED_LOOPS
always_semi_join                     string  STANDARD
aq_tm_processes                      integer 0
audit_trail                          string  NONE
background_core_dump                 string  partial
background_dump_dest                 string  e:\oracle\admin\DEVECOM\bdump
backup_tape_io_slaves                boolean FALSE
bitmap_merge_area_size               integer 1048576
blank_trimming                       boolean FALSE

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
buffer_pool_keep                     string
buffer_pool_recycle                  string
commit_point_strength                integer 1
compatible                           string  8.1.0
control_file_record_keep_time        integer 7
control_files                        string  e:\oracle\oradata\DEVECOM\cont
                                             rol01.ctl, e:\oracle\oradata\D
                                             EVECOM\control02.ctl, e:\oracl
                                             e\oradata\DEVECOM\control03.ct
                                             l
core_dump_dest                       string  %ORACLE_HOME%\RDBMS\TRACE

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
cpu_count                            integer 1
create_bitmap_area_size              integer 8388608
cursor_sharing                       string  EXACT
cursor_space_for_time                boolean FALSE
db_block_buffers                     integer 6225
db_block_checking                    boolean FALSE
db_block_checksum                    boolean FALSE
db_block_lru_latches                 integer 1
db_block_max_dirty_target            integer 6225
db_block_size                        integer 8192
db_domain                            string

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
db_file_direct_io_count              integer 64
db_file_multiblock_read_count        integer 8
db_file_name_convert                 string
db_files                             integer 1024
db_name                              string  DEVECOM
db_writer_processes                  integer 1
dblink_encrypt_login                 boolean FALSE
dbwr_io_slaves                       integer 0
disk_asynch_io                       boolean TRUE
distributed_transactions             integer 500
dml_locks                            integer 748

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
enqueue_resources                    integer 1792
event                                string
fast_start_io_target                 integer 6225
fast_start_parallel_rollback         string  LOW
fixed_date                           string
gc_defer_time                        integer 10
gc_files_to_locks                    string
gc_releasable_locks                  integer 0
gc_rollback_locks                    string  0-128=32!8REACH
global_names                         boolean FALSE
hash_area_size                       integer 131072

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
hash_join_enabled                    boolean TRUE
hash_multiblock_io_count             integer 0
hi_shared_memory_address             integer 0
hs_autoregister                      boolean TRUE
ifile                                file
instance_groups                      string
instance_name                        string  DEVECOM
instance_number                      integer 0
java_max_sessionspace_size           integer 0
java_pool_size                       string  20971520
java_soft_sessionspace_limit         integer 0

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
job_queue_interval                   integer 10
job_queue_processes                  integer 4
large_pool_size                      string  614400
license_max_sessions                 integer 0
license_max_users                    integer 0
license_sessions_warning             integer 0
lm_locks                             integer 12000
lm_ress                              integer 6000
local_listener                       string
lock_name_space                      string
lock_sga                             boolean FALSE

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
log_archive_dest                     string
log_archive_dest_1                   string
log_archive_dest_2                   string
log_archive_dest_3                   string
log_archive_dest_4                   string
log_archive_dest_5                   string
log_archive_dest_state_1             string  enable
log_archive_dest_state_2             string  enable
log_archive_dest_state_3             string  enable
log_archive_dest_state_4             string  enable
log_archive_dest_state_5             string  enable

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
log_archive_duplex_dest              string
log_archive_format                   string  ARC%S.%T
log_archive_max_processes            integer 1
log_archive_min_succeed_dest         integer 1
log_archive_start                    boolean FALSE
log_archive_trace                    integer 0
log_buffer                           integer 32768
log_checkpoint_interval              integer 10000
log_checkpoint_timeout               integer 1800
log_checkpoints_to_alert             boolean FALSE
log_file_name_convert                string

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
max_commit_propagation_delay         integer 700
max_dump_file_size                   string  10240
max_enabled_roles                    integer 30
max_rollback_segments                integer 37
mts_circuits                         integer 170
mts_dispatchers                      string  (PROTOCOL=TCP)(PRE=oracle.auro
                                             ra.server.SGiopServer)
mts_listener_address                 string
mts_max_dispatchers                  integer 5
mts_max_servers                      integer 20
mts_multiple_listeners               boolean FALSE

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
mts_servers                          integer 1
mts_service                          string  DEVECOM
mts_sessions                         integer 165
nls_calendar                         string
nls_comp                             string
nls_currency                         string
nls_date_format                      string
nls_date_language                    string
nls_dual_currency                    string
nls_iso_currency                     string
nls_language                         string  AMERICAN

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
nls_numeric_characters               string
nls_sort                             string
nls_territory                        string  AMERICA
nls_time_format                      string
nls_time_tz_format                   string
nls_timestamp_format                 string
nls_timestamp_tz_format              string
object_cache_max_size_percent        integer 10
object_cache_optimal_size            integer 102400
open_cursors                         integer 300
open_links                           integer 4

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
open_links_per_instance              integer 4
ops_interconnects                    string
optimizer_features_enable            string  8.1.7
optimizer_index_caching              integer 0
optimizer_index_cost_adj             integer 100
optimizer_max_permutations           integer 80000
optimizer_mode                       string  CHOOSE
optimizer_percent_parallel           integer 0
oracle_trace_collection_name         string
oracle_trace_collection_path         string  %ORACLE_HOME%\OTRACE\ADMIN\CDF
                                             \

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
oracle_trace_collection_size         integer 5242880
oracle_trace_enable                  boolean FALSE
oracle_trace_facility_name           string  oracled
oracle_trace_facility_path           string  %ORACLE_HOME%\OTRACE\ADMIN\FDF
                                             \
os_authent_prefix                    string
os_roles                             boolean FALSE
parallel_adaptive_multi_user         boolean FALSE
parallel_automatic_tuning            boolean FALSE
parallel_broadcast_enabled           boolean FALSE
parallel_execution_message_size      integer 2148

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
parallel_instance_group              string
parallel_max_servers                 integer 5
parallel_min_percent                 integer 0
parallel_min_servers                 integer 0
parallel_server                      boolean FALSE
parallel_server_instances            integer 1
parallel_threads_per_cpu             integer 2
partition_view_enabled               boolean FALSE
plsql_v2_compatibility               boolean FALSE
pre_page_sga                         boolean FALSE
processes                            integer 150

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
query_rewrite_enabled                boolean FALSE
query_rewrite_integrity              string  enforced
rdbms_server_dn                      string
read_only_open_delayed               boolean FALSE
recovery_parallelism                 integer 0
remote_dependencies_mode             string  TIMESTAMP
remote_login_passwordfile            string  EXCLUSIVE
remote_os_authent                    boolean FALSE
remote_os_roles                      boolean FALSE
replication_dependency_tracking      boolean TRUE
resource_limit                       boolean FALSE

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
resource_manager_plan                string
rollback_segments                    string
row_locking                          string  always
serial_reuse                         string  DISABLE
serializable                         boolean FALSE
service_names                        string  DEVECOM
session_cached_cursors               integer 0
session_max_open_files               integer 10
sessions                             integer 170
shadow_core_dump                     string  partial
shared_memory_address                integer 0

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
shared_pool_reserved_size            string  1572864
shared_pool_size                     string  31457280
sort_area_retained_size              integer 65536
sort_area_size                       integer 65536
sort_multiblock_read_count           integer 2
sql92_security                       boolean FALSE
sql_trace                            boolean FALSE
sql_version                          string  NATIVE
standby_archive_dest                 string  %ORACLE_HOME%\RDBMS
star_transformation_enabled          string  FALSE
tape_asynch_io                       boolean TRUE

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
text_enable                          boolean FALSE
thread                               integer 0
timed_os_statistics                  integer 0
timed_statistics                     boolean FALSE
tracefile_identifier                 string
transaction_auditing                 boolean TRUE
transactions                         integer 187
transactions_per_rollback_segment    integer 5
use_indirect_data_buffers            boolean FALSE
user_dump_dest                       string  e:\oracle\admin\DEVECOM\udump
utl_file_dir                         string
SQL>

you have any comments on this?

Thanks as always
Followup:
wow, that sure makes this page large doesn't it.

the hash and sort areas are teeny tiny don't you think.  I see lots of swapping
to temp in your life.
Rating: 5
lots of swapping to temp June 28, 2003
Reviewer: A reader
how did you make it our from the above paramters that the db is - " I see lots
of swapping to temp in your life. "

Thanks Tom!
Followup:
because you have a teeny tiny sort area size of 64k and you are doing big sorts
apparently (your query above was anyway)
Rating: 5
Remote view vs. local view. August 11, 2003
Reviewer: Kashif from Reston, VA
Hi Tom,

If I create a view on a remote database entirely (Option 1), and then use that
view to get my data to the local database, how is that different
performance-wise from creating a local view using dblinks to the remote database
and its tables (Option 2)? Setting aside for a moment the advantages of using
Option 2 such as metadata imported into local database, dependent objects
compile even if remote db is unavailable etc. Wouldn't the Option 1 view be
optimized far better than the Option 2 view?

Option 1:
REMOTE_DB> create view v1 as select * from t1, t2
where t1.id = t2.id;
...
cursor c1 is select * from v1@rdb;
...

Option 2:
LOCAL_DB> create view v1 as select * from t1@rdb, t2@rdb
where t1.id = t2.id;
...
cursor c1 is select * from v1;
...

Thanks in advance.

Kashif
Followup:
in option 1 -- the query plan for the view is generated at the remote site, this
in general is "better"

you can get the advantages of option 2 by simply creating a local view that
refereces the remote one.
Rating: 3
Creating Views with value passing October 13, 2003
Reviewer: Denise from Virginia
Tom

As you say Views are nothing more than stored queries.
I've created a View for users to access columns from 3
separate tables. They are now wanting to be able to pass
values to the View for querying.

example:
"select * from payack_view
 where batchno between &batch1 and &batch2"

within the Payack_View itself how would this be accomplished?

The view itself is as follows:

CREATE OR REPLACE VIEW payack_view AS
SELECT a1,b2,c3,......etc
from a,b,c
where batchno ????? and b1=a1 and c1=b1.

would this require use of a function(I was hoping not)

thanks for any tips.
Followup:
you have a view:

create view V as
SELECT a1,b2,c3,......etc
from a,b,c
where b1=a1 and c1=b1.

you want only certain batchnos, you would query:

select * from v where batchno between :1 and :2;
Rating: 4
thanks Tom October 14, 2003
Reviewer: Denise from Virginia
didn't realize it was that simple....I always tend to see
things as being more complicated than they really are...

as always..thanks for all you do here.
Denise
Rating: 4
Constraints on views October 15, 2003
Reviewer: A reader
Hi Tom,

I have a doubt. While going through the SQL reference i read that we can create
unique and primary key constraints on views using NOVALIDATE DISABLE command.
But what i was thinking if we cannot enable those constraints then whats the use
of having disabled constraints on a view.

As far as i understand, we cannot enable them because enabling the PK or UK (if
on a table) will create an index if its not associated with any index. But in
views this is not possible. So why is this clause available. Im not able to
figure out the purpose of having a disabled constraint on a view.

Thanks and Regards
Followup:
it is called METADATA

it gives the database (optimizer) more information.

the optimizer can use this in materialized view rewrites.

They will also be used in ad-hoc query tools (if the tools choose to) to help
you build queries.
Rating: 3
Few more doubts October 15, 2003
Reviewer: A reader
Thanks for answering Tom, but i have few more doubts..

o it gives the database (optimizer) more information.

-- more information about what? i mean can we have a test case where this is
useful.

o They will also be used in ad-hoc query tools (if the tools choose to) to help
you build queries.

-- How do we know when to create such constraints. I have doubt on who decides
when to have such a constraint or is it something the tool does internally.

Thanks and Regards
Followup:
if you have either of my two books -- expert one on one oracle or effective
oracle by design -- look at my writings on MV's there.

it shows that if the optimizer doesn't understand the fkey relationships between
objects, nullality of columns, keys and such -- it cannot rewrite queries to use
MV's as often.  That is what this is for.  It is just more META DATA.

the owner (creator) of a view would be responsible for this - just as a table
owner would
Rating: 5
view constraints on MV? December 20, 2003
Reviewer: reader
Tom, does it mean that view constraints are useful for MVs only? thanks.
Followup:
well, they are metadata as well -- "more data about your data" so they are
useful that way.

would it not be nice if --- the v$ tables had this defined on them so you would
know what joins to what ..... (for example)
December 21, 2003 -- Thanks for the question regarding "Parametrized reports with range buckets", version 8.1.7
You Asked
I had posted this as a review to an existing thread on analytic functions, but
didnt get a response, so I am asking it as a new question.

I have a table with a start_date column.

I have a parametrized report where the user can enter 4 dates i.e. a start range

(from/to date) and a end range (from/to date).

I want to get something like this in addition to the other columns in the table

count(*) of records that fall in the first range
count(*) of records that fall in the second range
difference and % difference between the above 2

Ideally, since this is a report where the users can enter any values at
run-time, this would be best exposed as a view into my base table with the
appropriate 4 date columns exposed and my report can just plug in the 4 date
parameters.

I was thinking a 4-way self-join of the table using analytic functions to get my
count(*)s but cant quite write the SQL.

Help? Thanks

More info:

create table activity
(
username varchar2(25),
activity_date date,
activity_type varchar2(1),
activity_desc varchar2(255));

Populate the table with a uniform distribution of dates.

I want a view/report where the user can plug in 4 dates
a. start_date_from
b. start_date_to
c. end_date_from
d. end_date_to

and get the following

1. username,
2. activity_type,
3. count(*) of rows between start_date_from and start_date_to
4. count(*) of rows between end_date_from and end_date_to
5. diff and % diff between 3 and 4 above

Thanks
and we said...
select username, activity_type,
       count( case when activity_date between :SDF and :SDT then 1 end ) cnt1,
       count( case when activity_date between :EDF and :EDT then 1 end ) cnt2
  from t
 group by username, activity_type
/

and if you inline view that:

select t.*,
       abs(cnt1-cnt2) diff,
       decode(cnt2,0,to_number(null),cnt1/cnt2*100) pct
  from (
select username, activity_type,
       count( case when activity_date between :SDF and :SDT then 1 end ) cnt1,
       count( case when activity_date between :EDF and :EDT then 1 end ) cnt2
  from t
 group by username, activity_type
       ) t
/

and if you cannot use CASE for whatever reason, you can use DECODE with SIGN to
do the "between" logic as follows:

ops$tkyte@ORA920> select activity_date, sdf, sdt,
  2         case when activity_date between sdf and sdt then 1 end w1,
  3         decode( sign(activity_date - sdf), -1, null, decode(
sign(sdt-activity_date), -1, null, 1 ) ) w2
  4    from t, (select sysdate activity_date from dual )
  5  /

ACTIVITY_ SDF       SDT               W1 W
--------- --------- --------- ---------- -
20-DEC-03 19-DEC-03 21-DEC-03          1 1
20-DEC-03 10-DEC-03 15-DEC-03
20-DEC-03 21-DEC-03 25-DEC-03
Review & Followup

Rating: 4
View? December 20, 2003
Reviewer: A reader
Thanks, but how can I create a view based on my base table with all this logic
inside the view and the 4 date paramters exposed.

Then, the query can just be against this view with different values for these 4
date "parameters".

i.e. I want something like

create or replace view v1 as
select username,activity_type,activity_desc,
sdf, sdt, edf, edt
from ....
Followup:
well, you cannot really -- think about it.  How do you "parameterize" a view?

You can use an application context -- that would require calling a stored
procedure BEFORE querying the view to set the parameters.

You can use a temporary table -- put the parameters in there AND THEN query the
view.

But nothing is going to be "100% transparent".  the view needs some inputs here
-- the 4 dates.  The only way to provide that is by setting them or not using a
view at all.
December 21, 2003 kakali -- Thanks for the question regarding "Drop column command", version Oracle 8i
You Asked
My question is regarding the drop column command which can be
used in 8i to drop the column...

One command is drop column column_name

Another one is through alter table command

alter table table_name set unused column column_name;

alter table table_name drop unused columns;

What's the difference between the two ways..

Rgds
kakali
and we said...
They are both done via the alter table command.

alter table T drop column....
alter table T set unused.....

The drop column physically removes the column from the table.
It rewrites the entire table.

The set unused "flag" deletes the column.  It is effectively
gone as far as you are concerned but the space is not reclaimed.
Review & Followup

Rating: 3
impact to RBS and redo April 04, 2002
Reviewer: an from DE, U.S.A
Hi Tom:

what different between those two alter command will impact rbs and redo?
thanks,
an
Followup:
alter set unused -- thats a flag delete.  no effect on undo/redo really.

alter drop -- thats a rewrite, potentially big effect on undo/redo.
Rating: 3
Need More Info. April 05, 2002
Reviewer: Parag from India
Can you be more Precise with your answere Tom.
Followup:
not really.  which part isn't making sense --

set unused = "flag" delete.  No work done to the table, just zap the data
dictionary, column appears to have gone missing -- data is not rewritten.  no
undo, no redo to speak of.

drop column = "really" delete.  table is effectively rewritten.  lots of undo,
lots of redo.

really don't know how I could be more "precise"
Rating: 3
Drop columns in unused tables April 05, 2002
Reviewer: Reddy from Bangalore,India,
Hi tom

I am using somany tables. If I want to drop all columns which is not usefull.
how can i drop. Here I would like to that table created before 4months, these
table columns only should drop which is not used.

Thanks
Followup:
If you have sooo many tables with columns you do not use, I rather question your
development environment.  What leads you to be in such a position?

Anyway, If you read the alter table command, you'll learn all about "drop
column" and "set unused".

As far as Oracle is concerned there is no such thing as a "column that is not
used".  It will be up to you to identify those columns which are not used by
your application (and if you cannot do that, stop development, get it under
control)
Rating: 3
alter table drop unused columns April 06, 2002
Reviewer: Reddy from Bangalore, India
Hi tom

I have some confusion.

pls look up this.

SQL>DESC BUDG_INDIRECT_COST_MAST_TBL;
 Name        Null?    Type
----------------------------
VOUCH_NO     NOT NULL VARCHAR2(50)
VOUCH_DT              DATE
PHY_DATE              DATE
LOG_DATE              DATE
PROC_DATE             DATE

SQL> Select PHY_DATE,LOG_DATE,PROC_DATE FROM
     BUDG_INDIRECT_COST_MAST_TBL;

    PHY_DATE   LOG_DATE    PROC_DATE
    --------   --------    ----------

SQL>
SQL> ALTER TABLE BUDG_INDIRECT_COST_MAST_TBL DROP UNUSED COLUMNS;

Tabe altered.
SQL>DESC BUDG_INDIRECT_COST_MAST_TBL;
 Name        Null?    Type
----------------------------
VOUCH_NO     NOT NULL VARCHAR2(50)
VOUCH_DT              DATE
PHY_DATE              DATE
LOG_DATE              DATE
PROC_DATE             DATE

SQL> Select PHY_DATE,LOG_DATE,PROC_DATE FROM
     BUDG_INDIRECT_COST_MAST_TBL;

    PHY_DATE   LOG_DATE    PROC_DATE
    --------   --------    ----------

SQL>

my doubt why this alter command not drop columns, if not why message "Table
altered" came.

Thanks in advance

Reddy
Followup:
What you showed doesn't demonstrate what you are trying to show.

drop unused is not there to remove all columns that contain just NULLS.  drop
unused (as per the documentation) :

<quote>
DROP UNUSED COLUMNS

removes from the table all columns currently marked as unused. Use
this statement when you want to reclaim the extra disk space from
unused columns in the table. If the table contains no unused columns,
the statement returns with no errors.
</quote>

A column that is marked UNUSED is "flag" deleted -- you WILL NOT see it in a
describe by definition (it is as good as gone, it just is consuming space at
that point).

Here is an example that shows this command in action:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t
  2  pctfree 0 pctused 99
  3  as
  4  select * from all_objects
  5  /
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec show_space( 'T' )
Free Blocks.............................0
Total Blocks............................256
Total Bytes.............................2097152
Unused Blocks...........................44
Unused Bytes............................360448
Last Used Ext FileId....................7
Last Used Ext BlockId...................64137
Last Used Block.........................20

PL/SQL procedure successfully completed.

so, here we have a table that is "packed" -- 256-44 blocks are used (212 of
them)

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t set unused ( owner,
object_name, object_id, created, last_ddl_time );

Table altered.

Elapsed: 00:00:00.38

we marked all of the non-null columns UNUSED.  Notice that this took no time
at all.  Far too little time to have actually REMOVED them.  This was a data
dictionary update only.  We can see that easily by dumping the space again:

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec show_space( 'T' )
Free Blocks.............................0
Total Blocks............................256
Total Bytes.............................2097152
Unused Blocks...........................44
Unused Bytes............................360448
Last Used Ext FileId....................7
Last Used Ext BlockId...................64137
Last Used Block.........................20

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.01

see, no change in utilization.... So, lets DROP UNUSED:

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t drop unused columns;

Table altered.

Elapsed: 00:00:28.74

well, that took quite a bit of time -- lets see what happened with the
space:

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec show_space( 'T' )
Free Blocks.............................211
Total Blocks............................256
Total Bytes.............................2097152
Unused Blocks...........................44
Unused Bytes............................360448
Last Used Ext FileId....................7
Last Used Ext BlockId...................64137
Last Used Block.........................20

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.03

that shows the entire table was rewritten -- EVERY block is on the free list
now (there is one block overhead for the table itself -- 211+1 = 212, all blocks
accounted for)

The database does not magically all by itself determine which columns are
"unused" (what if you have an application that had an insert into t
(object_name) values ( null ) statement - that column is MOST CERTAINLY used.
NULL does not imply "not used", rather it means "value is not known").

YOU have to tell US what is not used, we do not tell you.
Rating: 2
Be careful with DROP COLUMN April 08, 2002
Reviewer: Adrian Billington from UK
Drop column generates proportionately massive amounts of redo and in my
experience is a horrible way to remove columns. The table is locked until the
column is removed and its data deleted (all rows are re-written to exclude the
drop column data). On big tables this can be horrible. You can force regular
checkpoints to avoid blowing your rollback, but the whole process can be
painful.

Your alternatives are to set all the columns you want to drop to unused and drop
them all in one go. Personally, I prefer to set all the unused columns and then
either perform a CTAS to create a new copy of the table without the unused
columns or even an ALTER TABLE MOVE which is a very quick way of re-writing the
data without the unused columns. Either way you will need to consider your
indexes.

If it's a small table with just one or two columns to remove, then fine, go
ahead and drop, but if the table is of any size at all, then use with caution...

Regards

Adrian
Followup:
It surprises me, especially after seeing all of the information presented here,
that the above comment is not "intuitively obvious".

I've said it quite a few times -- DROP COLUMN rewrites the entire table.  This
operation OF COURSE must be protected by undo and redo (in the event of a
server/instance failure -- you'd be really truly mad at us if we didn't).

The alter table MOVE is a much better approach overall them CTAS as it retains
grants, constraints, triggers, indexes, etc...
Rating: 2
Followup April 08, 2002
Reviewer: Adrian Billington from UK
Tom

All of the above might be "intuitively obvious" to us as we have both
experienced the ups and downs of the drop / unused commands first hand. I was
merely trying to assist some of the reviewers with my experience and to offer
some friendly advice in addition to your technical answers. When people see the
DROP COLUMN command for the first time, they go "oh great we can use that"
without sometimes being aware of the underlying implications. Just because a
feature is there doesn't always make it the right choice.

I agree that the MOVE methodology is the preferred option...

Regards

Adrian
Followup:
I wasn't pinging you.  I've just been surprised by the back and forth I've had
to go on with something as simple as "drop column" and "drop unused".

Sorry if you took offense, I was sort of trying to commiserate with you - not
ping you on being blatantly obvious...
Rating: 2
Healthy exhange April 08, 2002
Reviewer: A reader
Tom

None taken merely a healthy exhange of opinions ;)

Adrian
Rating: 4
Reader July 08, 2002
Reviewer: A reader
Tom,

Did the following test:

created t as select * from dba_users for a few times

SQL>alter table t add (x number(1));

04:21:31 SQL> desc t
 Name                              Null?    Type
 ------------------------------- -------- ---------
 OWNER                                    VARCHAR2(30)
 OBJECT_NAME                              VARCHAR2(128)
 SUBOBJECT_NAME                           VARCHAR2(30)
 OBJECT_ID                                NUMBER
 DATA_OBJECT_ID                           NUMBER
 OBJECT_TYPE                              VARCHAR2(18)
 CREATED                                  DATE
 LAST_DDL_TIME                            DATE
 TIMESTAMP                                VARCHAR2(19)
 STATUS                                   VARCHAR2(7)
 TEMPORARY                                VARCHAR2(1)
 GENERATED                                VARCHAR2(1)
 SECONDARY                                VARCHAR2(1)
 X                                        NUMBER(1)

Total number of blocks = 23856 (8k)

02:41:56 SQL> select count(*) from t where x is null;

  COUNT(*)
----------
   1985024

03:57:12 SQL> update /*+ parallel(t, 2) */ t t set x=0 where x is null and
rownum < 500000;

499999 rows updated.

Elapsed: 00:03:45.54

Execution Plan
----------------------------------------------------------
   0      UPDATE STATEMENT Optimizer=CHOOSE (Cost=2295 Card=99252 Byte
          s=1290276)

   1    0   UPDATE OF 'T'
   2    1     COUNT (STOPKEY)
   3    2       TABLE ACCESS* (FULL) OF 'T' (Cost=2295 Card=99252 Byte :Q28000
          s=1290276)

   3 PARALLEL_TO_SERIAL            SELECT /*+ NO_EXPAND ROWID(A1) */ A1.ROWID,A
                                   1."X" FROM "T" A1 WHERE ROWID BETWEE

Statistics
----------------------------------------------------------
          0  recursive calls
    2031184  db block gets
       7412  consistent gets
      11853  physical reads
  249434528  redo size
       1187  bytes sent via SQL*Net to client
        781  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
     499999  rows processed

04:01:23 SQL> commit;

Commit complete.

Elapsed: 00:00:00.01

Questions:

1. why the 2, 031, 184 for update of
Rating: 4
Reader July 08, 2002
Reviewer: A reader
Tom,

Did the following test:

created t as select * from dba_users for a few times

SQL>alter table t add (x number(1));

04:21:31 SQL> desc t
 Name                              Null?    Type
 ------------------------------- -------- ---------
 OWNER                                    VARCHAR2(30)
 OBJECT_NAME                              VARCHAR2(128)
 SUBOBJECT_NAME                           VARCHAR2(30)
 OBJECT_ID                                NUMBER
 DATA_OBJECT_ID                           NUMBER
 OBJECT_TYPE                              VARCHAR2(18)
 CREATED                                  DATE
 LAST_DDL_TIME                            DATE
 TIMESTAMP                                VARCHAR2(19)
 STATUS                                   VARCHAR2(7)
 TEMPORARY                                VARCHAR2(1)
 GENERATED                                VARCHAR2(1)
 SECONDARY                                VARCHAR2(1)
 X                                        NUMBER(1)

Total number of blocks = 23856 (8k)

02:41:56 SQL> select count(*) from t where x is null;

  COUNT(*)
----------
   1985024

03:57:12 SQL> update /*+ parallel(t, 2) */ t t set x=0 where x is null and
rownum < 500000;

499999 rows updated.

Elapsed: 00:03:45.54

Execution Plan
----------------------------------------------------------
   0      UPDATE STATEMENT Optimizer=CHOOSE (Cost=2295 Card=99252 Byte
          s=1290276)

   1    0   UPDATE OF 'T'
   2    1     COUNT (STOPKEY)
   3    2       TABLE ACCESS* (FULL) OF 'T' (Cost=2295 Card=99252 Byte :Q28000
          s=1290276)

   3 PARALLEL_TO_SERIAL            SELECT /*+ NO_EXPAND ROWID(A1) */ A1.ROWID,A
                                   1."X" FROM "T" A1 WHERE ROWID BETWEE

Statistics
----------------------------------------------------------
          0  recursive calls
    2031184  db block gets
       7412  consistent gets
      11853  physical reads
  249434528  redo size
       1187  bytes sent via SQL*Net to client
        781  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
     499999  rows processed

04:01:23 SQL> commit;

Commit complete.

Elapsed: 00:00:00.01

Questions:

1. why the 2, 031, 184 "db block gets"for update of 499999
   rows

2. Are there ways to tune initiallizing of the the
   new column. It takes lot of  time and consumes lot
   of redo.

Thanks
Rating: 4
Reader July 08, 2002
Reviewer: A reader
In the previous posting,

I also created a temporary index t_x on t(x), to keep
track of the batch of rows that have been already updated
Followup:
Not that your question has anything at all to do with anything else on this page
but.... it is the index maintainence. Every row updated updated your index right
-- index blocks split, move about -- you could have 2 or 3 blocks updated in an
index for every updated row.

You have at least 1,000,000 db block gets (499,999 rows updated + 499,999 index
blocks).  Since x started at NULL (eg: not in the index, NULL entries are not
made) every update you do adds (increases the size of) your index.  All of the
values are the same -- hence they all tend to goto the same place in the index
-- hence the index on x is way hot in one spot, splitting and splitting and
splitting.

My advice -- it looks like you are trying to "break an update up" into smaller
chunks.  Just bite the bullet, size your rollback correctly and do it in one
statement.  The amount of time you've spent going down this path probably cost
the equivalent of 10 36gig SCSI drives.  It would have been cheaper just to buy
them, use them once for the mass update and throw them away afterwards.
Rating: 4
Reader July 08, 2002
Reviewer: A reader
Tom,

Thanks very much for the explanation. I'll increase the
RBS, "set transaction ..." and do the updates. It takes
about 30 minutes to update 5 million rows with 5 new
columns.

I was thinking in terms of increasing the degree of
parallelism, which would re-write the blocks in parallel.
Would it have an improve the processing

In your web-site, I have read that, each time a row is
updated, Oracle get the block in CU mode and then gives it
back. Would it have any benefit, if when adding a column
set default value to 0. From what I tested, it seems there
is not much of a difference, if updated subsequently or
declare the column with default value. Does Oracle
handle the re-write of db blocks the same way in both
scenarios

Thanks
Followup:
In 9iR1 and before, parallel update works only on PARTITIONED data -- in your
example, the FULL scan is parallelized --- but the update itself is serial.

It seems that it would be more work to add a column with a default of 0 and then
update it.  The alter table would take that much longer (adding a NULLABLE,
DEFAULT NULL column to a table is virtually instantaneous, adding a column with
a non-null default value takes considerably longer as it has to rewrite the
table)
Rating: 5
drop columns February 10, 2003
Reviewer: mo
Tom:

Is there  a way to drop multiple columns in SQL statement or you have to do one
column at a time?
Followup:
when you checked the docs for ALTER TABLE -- what did they tell you?
Rating: 5
Drop column December 14, 2003
Reviewer: A reader from Houston
Tom,
We need to drop a column on a table having close to a million  records.  The DBA
says that there will be empty spaces once the column drop is done and an
export/import of the table needs to be done after the column drop.  My
understanding was that the space on the whole table gets re-organized once the
column is dropped(and there is no need to do an export/import)?
Part of my understanding based on
<quote>
>>
http://asktom.oracle.com/pls/ask/f?p=4950:8:3155039141185500108::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1381803609490,
>>The drop column physically removes the column from the table.  It rewrites the
entire table.
</quote>

My question
1) Once a column is dropped from a table, is there any command that needs to be
executed to organize the space in the table?  Should an export/import of the
table be done after a column drop?
2) Will query execution be slow after a column drop?
3) What are the things that happen on the DB when a column is dropped from a
table?

Thanks a lot for having such a wonderful forum.
Followup:
well -- if you have a version of Oracle that can do a drop column
AND
the dba wants to export/import to reorg an object
THEN
the dba needs to get a refresher :)

alter table T move;
alter index I rebuild;

that is what the DBA really really wants to do.  Not only will it be faster (can
be done nologging, without undo generation using direct path operations rather
then row by row inserts with impo) -- but it will be incredibably SAFE (might
you lose a grant, index, constraint, something by taking the data OUT of the
database and putting it back IN........

but, lets see what happens when we drop a column.  I'll use manual segment space
mgmt here (not assm) since thats what most people are probably using but....

ops$tkyte@ORA920> drop table big_table;

Table dropped.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table big_table
  2  pctfree 10 pctused 90
  3  as
  4  select *
  5    from big_table.big_table
  6   where rownum <= 1000000;

Table created.

I used pctused of 90 so as to demonstrate what'll happen when we drop the
column.  If I left it at the default of 40 -- none of my blocks in my example
would have gone on the freelist list (since the blocks were all nicely packed
full -- not normal in the real world... anyway..

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table big_table compute statistics for table;

Table analyzed.

ops$tkyte@ORA920> begin
  2  print_table( '
  3  select TABLE_NAME, PCT_FREE, PCT_USED,
  4         NUM_ROWS, BLOCKS, EMPTY_BLOCKS, AVG_SPACE,
  5         AVG_ROW_LEN, AVG_SPACE_FREELIST_BLOCKS, NUM_FREELIST_BLOCKS
  6    from user_tables where table_name = ''BIG_TABLE''
  7    ' );
  8  end;
  9  /
TABLE_NAME                    : BIG_TABLE
PCT_FREE                      : 10
PCT_USED                      : 90
NUM_ROWS                      : 1000000
BLOCKS                        : 12637
EMPTY_BLOCKS                  : 674
AVG_SPACE                     : 857
AVG_ROW_LEN                   : 89
AVG_SPACE_FREELIST_BLOCKS     : 0
NUM_FREELIST_BLOCKS           : 0
-----------------

PL/SQL procedure successfully completed.

remember those numbers.....

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table big_table drop column object_name;

Table altered.

ops$tkyte@ORA920> analyze table big_table compute statistics for table;

Table analyzed.

ops$tkyte@ORA920> begin
  2  print_table( '
  3  select TABLE_NAME, PCT_FREE, PCT_USED,
  4         NUM_ROWS, BLOCKS, EMPTY_BLOCKS, AVG_SPACE,
  5         AVG_ROW_LEN, AVG_SPACE_FREELIST_BLOCKS, NUM_FREELIST_BLOCKS
  6    from user_tables where table_name = ''BIG_TABLE''
  7    ' );
  8  end;
  9  /
TABLE_NAME                    : BIG_TABLE
PCT_FREE                      : 10
PCT_USED                      : 90
NUM_ROWS                      : 1000000
BLOCKS                        : 12637
EMPTY_BLOCKS                  : 674
AVG_SPACE                     : 2765
AVG_ROW_LEN                   : 65
AVG_SPACE_FREELIST_BLOCKS     : 2765
NUM_FREELIST_BLOCKS           : 12637
-----------------

PL/SQL procedure successfully completed.

see how every block jumped onto the freelist.  they are available for reuse --
new rows.  the table did not shrink here but each and every block was rewritten.
 the space reclaimed.  and the block put onto the freelist.

This might not be 100% desirable -- but it is only a 1million row table (not
very large).  Over time, the freespace would be reused as new inserts are made.

Using the alter table move:

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table big_table MOVE;

Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table big_table compute statistics for table;

Table analyzed.

ops$tkyte@ORA920> begin
  2  print_table( '
  3  select TABLE_NAME, PCT_FREE, PCT_USED,
  4         NUM_ROWS, BLOCKS, EMPTY_BLOCKS, AVG_SPACE,
  5         AVG_ROW_LEN, AVG_SPACE_FREELIST_BLOCKS, NUM_FREELIST_BLOCKS
  6    from user_tables where table_name = ''BIG_TABLE''
  7    ' );
  8  end;
  9  /
TABLE_NAME                    : BIG_TABLE
PCT_FREE                      : 10
PCT_USED                      : 90
NUM_ROWS                      : 1000000
BLOCKS                        : 9262
EMPTY_BLOCKS                  : 977
AVG_SPACE                     : 838
AVG_ROW_LEN                   : 65
AVG_SPACE_FREELIST_BLOCKS     : 0
NUM_FREELIST_BLOCKS           : 0
-----------------

PL/SQL procedure successfully completed.

we shrink the table.  BUT, if you are going to "shrink the table", if that is
the goal -- DO NOT USE drop, do this instead:

ops$tkyte@ORA920> drop table big_table;

Table dropped.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table big_table
  2  pctfree 10 pctused 90
  3  as
  4  select *
  5    from big_table.big_table
  6   where rownum <= 1000000;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table big_table compute statistics for table;

Table analyzed.

ops$tkyte@ORA920> begin
  2  print_table( '
  3  select TABLE_NAME, PCT_FREE, PCT_USED,
  4         NUM_ROWS, BLOCKS, EMPTY_BLOCKS, AVG_SPACE,
  5         AVG_ROW_LEN, AVG_SPACE_FREELIST_BLOCKS, NUM_FREELIST_BLOCKS
  6    from user_tables where table_name = ''BIG_TABLE''
  7    ' );
  8  end;
  9  /
TABLE_NAME                    : BIG_TABLE
PCT_FREE                      : 10
PCT_USED                      : 90
NUM_ROWS                      : 1000000
BLOCKS                        : 12637
EMPTY_BLOCKS                  : 674
AVG_SPACE                     : 857
AVG_ROW_LEN                   : 89
AVG_SPACE_FREELIST_BLOCKS     : 0
NUM_FREELIST_BLOCKS           : 0
-----------------

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table big_table set unused column object_name;

Table altered.

ops$tkyte@ORA920> analyze table big_table compute statistics for table;

Table analyzed.

ops$tkyte@ORA920> begin
  2  print_table( '
  3  select TABLE_NAME, PCT_FREE, PCT_USED,
  4         NUM_ROWS, BLOCKS, EMPTY_BLOCKS, AVG_SPACE,
  5         AVG_ROW_LEN, AVG_SPACE_FREELIST_BLOCKS, NUM_FREELIST_BLOCKS
  6    from user_tables where table_name = ''BIG_TABLE''
  7    ' );
  8  end;
  9  /
TABLE_NAME                    : BIG_TABLE
PCT_FREE                      : 10
PCT_USED                      : 90
NUM_ROWS                      : 1000000
BLOCKS                        : 12637
EMPTY_BLOCKS                  : 674
AVG_SPACE                     : 857
AVG_ROW_LEN                   : 89
AVG_SPACE_FREELIST_BLOCKS     : 0
NUM_FREELIST_BLOCKS           : 0
-----------------

PL/SQL procedure successfully completed.

see, nothing changed -- but the set unused was like "really fast".  now, we'll
rebuild easily:

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table big_table MOVE;

Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table big_table compute statistics for table;

Table analyzed.

ops$tkyte@ORA920> begin
  2  print_table( '
  3  select TABLE_NAME, PCT_FREE, PCT_USED,
  4         NUM_ROWS, BLOCKS, EMPTY_BLOCKS, AVG_SPACE,
  5         AVG_ROW_LEN, AVG_SPACE_FREELIST_BLOCKS, NUM_FREELIST_BLOCKS
  6    from user_tables where table_name = ''BIG_TABLE''
  7    ' );
  8  end;
  9  /
TABLE_NAME                    : BIG_TABLE
PCT_FREE                      : 10
PCT_USED                      : 90
NUM_ROWS                      : 1000000
BLOCKS                        : 9398
EMPTY_BLOCKS                  : 841
AVG_SPACE                     : 836
AVG_ROW_LEN                   : 66
AVG_SPACE_FREELIST_BLOCKS     : 0
NUM_FREELIST_BLOCKS           : 0
-----------------

PL/SQL procedure successfully completed.

and there we go.  No chance to lose grants, constraints, indexes, nothing.
Data is transactionally "protected" (anytime the data LEAVES my database, I can
easily lose it.  maybe the dmp file goes bad.  maybe I do something wrong.  If I
leave it in the database -- I'm protected).

It is only 1,000,000 rows -- not very big, this only takes a couple of
seconds.
Rating: 5
Reader December 14, 2003
Reviewer: A reader
<quote>
done nologging, without undo generation using direct path operations rather
then row by row inserts with impo
<quote>

Is this direct path operation bypass buffer cache
Followup:
yes.
Rating: 5
Drop column December 14, 2003
Reviewer: Houston
Tom,
Thank you for the excellent explanation.  Couldn't asked for more.
The version of Oracle we are using is 9.2.0.4.  If shrinking the table is not
the objective, then we don't have to issue
>>alter table T move;
Right?

If we have to do a
alter table T move;
Does this generate any redo log?  Is the redo generated significant?  We cannot
go with the option of nologging or no undo generation - because we have a stand
by db and a replicated db.

Thank you
Followup:
it generates redo, yes (else the standby would not get the changes!)

it generates enough redo to redo the operation.  is that significant?  guess it
depends on your definition of significant.

the drop column itself generates redo.

Given that we are probably talking about a couple of meg AT MOST here, suggest
you consider just setting the column unused and forgetting about it.  fast, no
redo to speak of, column gone
Rating: 5
dropping a a column December 14, 2003
Reviewer: A reader from Houston
Thank you for making the concept clear
Rating: 5
what if the column to be dropped is a clob December 19, 2003
Reviewer: A reader from Houston
Tom,
Since LOB columns are not managed through freelists, Does 'alter table big_table
MOVE;' make the NUM_FREELIST_BLOCK to 0 when a lob column is dropped?  Should
export/import be used to reorg the object with the column being dopped is a
clob?

Thank you
Followup:
lobs are not moved when the table is moved -- lobs are stored in a separate
segment, you move them all by themselves.

you use alter move to "reorg" -- don't use export import.
Rating: 5
dropping a lob December 21, 2003
Reviewer: A reader from Houston
Tom,
>>lobs are not moved when the table is moved
If lobs are not moved when we do
alter table big_table MOVE;

Then if we drop a lob column how is the space, how is the space occupied by the
lob column from the table recovered?  (ie. what is the equivalent of table
shrinking in this situation?)
Can you please explain
>>you move all by themselves

Thank you
Followup:
what does "dropping a column" have to do with "moving a table" and why do you
think they are related?

the act of dropping a column frees the storage in the table for reuse in the
table (table blocks might go onto the freelist).  The log segment is "dropped"
at that point.  The lob INDEX and lob DATA is just "dropped".

you have table data (includes the lob locator).
you have a lob index (separate segment, not in table).
you have lob data pages (separate segment again, not in table).

dropping a lob column clears the lob locator out, drops the index segment and
data pages for the lob (as it does when you "drop the table")

see:

ops$tkyte@ORA920> create table t ( x int, y clob, z blob );

Table created.

ops$tkyte@ORA920> set echo off

OBJECT_TYPE  OBJECT_NAME                    S TABLESPACE_NAME
------------ ------------------------------ - ------------------------------
LOB          SYS_LOB0000059367C00002$$        USERS
             SYS_LOB0000059367C00003$$        USERS

TABLE        T                                USERS

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter table t drop column y;

Table altered.

ops$tkyte@ORA920> set echo off

OBJECT_TYPE  OBJECT_NAME                    S TABLESPACE_NAME
------------ ------------------------------ - ------------------------------
LOB          SYS_LOB0000059367C00002$$        USERS

TABLE        T                                USERS

ops$tkyte@ORA920> alter table t drop column z;

Table altered.

ops$tkyte@ORA920> set echo off

OBJECT_TYPE  OBJECT_NAME                    S TABLESPACE_NAME
------------ ------------------------------ - ------------------------------
TABLE        T                                USERS

ops$tkyte@ORA920>

 the segments just "disappear", that is how the lob space is recovered
December 22, 2003 Sudhir -- Thanks for the question regarding "few burning questions", version 9.2.0.3
You Asked
Tom,

Thanks for taking my questions.

1. Oracle's obcession with single ticks

I had a situation where once a month report need to create some tables and
populate it. There is a sql to do this. Unfortunately, it need to be done in
pl/sql. Converting create table as select, create index commands look so bad
after escaping ticks, the visibility has become very poor.

For example, inside pl/sql

execute immediate 'alter session set events ''trace name context forever, level
12''';

why does oracle not accept

execute immediate "alter session set events 'trace name context forever, level
12'";

Not only code will become readable but making change will be easy as well. I can
convert from ddl to execute immediate without any problem. I am sure escaping
ticks with ticks is got on few nerves.

Do you have any tricks to achieve this?

2. Oracle's lack of disclosure of official policy on perl (the greatest language
on earth :-) ) that I know of

Oraperl, DBI has done great job and in fact helped build loyalty among oracle
users - since they can do more with less (perl motto). Its almost perfect
situation! I would rather use dbi than pl/sql (for example sqlunloader type of
dbi code is not only easy but very very fast as well). I would love to see
PERL_POOL as a nice gesture from oracle :-) (wait a minute, whats that MSFT_POOL
variable in 10g :-)

3. Can oracle provide a tool to build indexes on external tables. Not only it
will help performance, it will allow end users to prepare text files, indexes
and put them on shared drives so database would care less about doing it.

I think that will do it for cold sunday.

Thanks for all your help

Sudhir
and we said...
1) we have no "obsession" -- tell you what.  Go into java and try to code this
string into a string:

   He said "How's it going George?"

all programming languages have "issues" with some characters. But anyway, what
I've done is this:

declare
   l_cmd long := 'alter session set events "trace name ...."';
begin
   execute immediate replace( l_cmd, '"', '''' );
end;

that is, I use SOMETHING OTHER than a single quote and replace that with a
single quote.  Makes it quite readable.

In 10g, there will be another solution.

insert into t values ( q'|Don't do that.|' )

will work. the q in front of the string says -- hey, the character AFTER the '
will be considered part of the '.  So, if you have:

   q'x

look for x'  as the "end of string".......

2) I don't know what you mean by that (and in my opinion, having programming in
lots of languages, i despise "perl" as a programming language -- that is just an
opinion AS ALL COMMENTS about ALL LANGUAGES are at the end of the day)

I cannot imagine writing in perl personally.  But -- dbi perl exists, it is out
there, it is "open source" (eg: we cannot just take it over can we?  no)

3) external files are designed for one thing -- to LOAD DATA.  It not only does
not make sense to "index" an external table for any reasons (you are not
querying them more then ONCE typically!) it cannot be done.

Consider

- you "index" the E.T.
- someone else "edits" the E.T.
- the index is now garbage -- worse than garbage actually cause it points to the
WRONG DATA (eg: use that index, get the wrong answer)

you prepare a text file for loading into a database where is is professionally
mangaged, backed up, maintained.  a system with text files?  that is a system
where you've basically said "the data isn't really relevant or important at all"
-- you don't even need a database.
Review & Followup

Rating: 5
Thanks for your comments December 20, 2003
Reviewer: Sudhir
#1 - really great idea. I will play with it

#2 - Ok, you don't like perl. fine. However, there are many many oracle users
who are fan of oraperl. Frankly, I have seen some of Pro*C code (and it is very
complex) and if you don't like perl, that is odd. perl looks a lot cleaner and
pretty good performance, much smaller time to develop etc.

I am not trying to convert you from pl/sql, java and sql. I know perl is "open
source" - but then why oracle never says a word about it, when it says a lot
about java? I guess one Larry doesn't like other Larry!

Just look at 10g solution you provided

insert into t values ( q'|Don't do that.|' )

That looks so much like perl.

print q(Don't do that);
print qq(Don't do that);

#3 I agree on that.
Rating: 4
could you please elaborate December 22, 2003
Reviewer: pete
Tom,

Could you please elaborate more, or provide a URL so I can read up on, the new
q' syntax which you mentioned was going to appear in 10g - that sounds quite
intresting, but I dont totally get what you mean by it.

thanks,
Pete
Followup:
when 10g comes out -- sure, it'll be documented in the manuals.

It is quite simple:

  1* select q'|How's it going|', q'#How's it going#', q'xHow's it goingx' from
dual
ops$tkyte@ORA10G> /

Q'|HOW'SITGOIN Q'#HOW'SITGOIN Q'XHOW'SITGOIN
-------------- -------------- --------------
How's it going How's it going How's it going

if the literal begins with q -- then the next two characters represent the
"begin of string" marker and the reverse of those two represent the "end of
string" marker.
December 22, 2003 Prakash -- Thanks for the question regarding "difference between 2 dates", version
You Asked
I want to get difference between two dates say

(10-FEB-2000 - 02-JAN-1999)

and the result has to be displayed in following format
1 year, 1 month, 8 days
and we said...
See
http://osi.oracle.com/~tkyte/Misc/DateDiff.html
for some more info.

We can do this with some date arithmetic as follows:

ops$tkyte@8i> create table t ( end_date date, start_date date );
Table created.

ops$tkyte@8i> insert into t values ( '10-FEB-2000',
'02-JAN-1999' );
1 row created.

ops$tkyte@8i> insert into t values ( '02-FEB-2000',
'10-JAN-1999' );
1 row created.

ops$tkyte@8i> select months_between( end_date, start_date ),
  2                  trunc( months_between( end_date, start_date
) /12 ) Years,
  3                  mod( trunc( months_between( end_date,
start_date ) ), 12 ) months,
  4                  end_date - add_months(start_date,trunc(
months_between( end_date, start_date ) )) days
  5  from t
  6  /

MONTHS_BETWEEN(END_DATE,START_DATE)      YEARS     MONTHS
DAYS
----------------------------------- ---------- ----------
----------
                         13.2580645          1          1
  8
                         12.7419355          1          0
 23
Review & Followup

Rating: 5
Perfect November 21, 2002
Reviewer: Kim Njeru from Los Angeles, CA USA
You did it again Tom!
Date calculations are something that most DBA's don't know enough about but
should know more about.
Rating: 2
I think there is a flaw in the solution January 16, 2003
Reviewer: Jon from Connecticut
Tom, I think there is a problem with this solution, at least in my mind.  When
trying to get the difference between 28-feb-1994 and 31-mar-1996, I get 2 years,
1 month and 0 days.  I happen to think that the answer should be 2 years, 1
month, and 3 days, but perhaps that is just definitional, as 28-feb-1994 and
31-mar-1996 are both the last day of the month.  But that got me to wondering –
what would we get if we looked for the difference between 28-feb-1994 and
30-mar-1996.  The answer is 2 years, 1 month and MINUS 1 days.  Can a date
difference be both positive and negative?  Similarly, 29-MAR yields –2 days and
28-MAR yields –1 day.  27-MAR correctly yields 2 years, 0 months and 27 days.

Here is the script to create the data:

drop table t1;
create table t ( end_date date, start_date date );
insert into t values ( '27-MAR-1996','28-FEB-1994' );
insert into t values ( '28-MAR-1996','28-FEB-1994' );
insert into t values ( '29-MAR-1996','28-FEB-1994' );
insert into t values ( '30-MAR-1996','28-FEB-1994' );
insert into t values ( '31-MAR-1996','28-FEB-1994' );

and here are the results:

SQL> select end_date, start_date ,
  2         trunc( months_between( end_date, start_date ) /12 ) Years,
  3         mod( trunc( months_between( end_date, start_date ) ), 12 ) months,
  4         end_date - add_months(start_date,trunc(
months_between(end_date,start_date
  5   ) )) days
  6  from t
  7  /

END_DATE    START_DATE      YEARS    MONTHS      DAYS
----------- ----------- --------- --------- ---------
27-mar-1996 28-feb-1994         2         0        27
28-mar-1996 28-feb-1994         2         1        -3
29-mar-1996 28-feb-1994         2         1        -2
30-mar-1996 28-feb-1994         2         1        -1
31-mar-1996 28-feb-1994         2         1         0

Is there a better way to do this?
Followup:
cool -- needs a tad bit of work eh...

boundary values, gotta hate them...

easiest thing is to zero out negative days -- it is as valid as anything else.
But if you figure out what you *want* returned, it should be easy to add it.

You can use a simple greatest( 0, end_date - .... ) to zero it out.
Rating: 3
Difference Between Two Dates September 02, 2003
Reviewer: Khawar Iqbal Pasha from Islamabad, Pakistan
 Accumulating your tips and some of my experience i've written this function.
May it would be helpful for others.

create or replace function date_diff ( p_date1 DATE , p_date2 DATE)
return char
is

 Years        NUMBER;
 months       NUMBER;
 days         NUMBER;
 day_fraction NUMBER;
 hrs          NUMBER;
 mints        NUMBER;
 sec          NUMBER;

begin

 Years :=trunc( months_between( p_date2 , p_date1 ) /12 );
 months:=mod( trunc( months_between( p_date2, p_date1 ) ), 12 );
 days  :=trunc(p_date2 -
add_months(p_date1,trunc(months_between(p_date2,p_date1) )));

 day_fraction:= (p_date2-p_date1)-trunc(p_date2-p_date1);

 hrs   :=trunc(day_fraction*24);
 mints :=trunc((((day_fraction)*24)-(hrs))*60);
 sec   :=trunc(mod((p_date2-p_date1)*86400,60));

 return(years||' Years '||months||' Months '||days||' Days '||hrs||' Hours
'||mints||' Minutes '||sec||' Seconds');

end;
/
Followup:

http://asktom.oracle.com/~tkyte/Misc/DateDiff.html
Rating: 5
Reverse function September 10, 2003
Reviewer: A reader from VA
What if I have want to convert 31000003170010.6 seconds into yyyy/mm/dd hh:mi:ss
Followup:
hmm, is that just a random number or what ?
Rating: 5
Not a random number September 11, 2003
Reviewer: A reader
I was actually asked this question?  My initial response, That's just insane.  I
realize it would approximately equate to 100,000 years.

But, using a more realistic number, say 1,728,100 seconds, is it possible ?
Followup:
sure, assuming the seconds are "since some time".

eg: lets say that is a "C time" or "unix time" -- the number of seconds since
1/1/1970

ops$tkyte@ORA920> variable theTime number
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec :theTime := 1728100

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> SELECT to_char(
  2          new_time( to_date('01011970','ddmmyyyy') + 1/24/60/60 * :theTime,
  3          'GMT', 'EDT' ), 'dd-mon-yyyy hh24:mi:ss' )
  4  from dual;

TO_CHAR(NEW_TIME(TO_
--------------------
20-jan-1970 20:01:40

ops$tkyte@ORA920>

does that.
Rating: 3
Calculating the difference between fields values to days and hours and minutes December 20, 2003
Reviewer: Jamil from Saudi
Dear Sir

I have this problem ,my program calculate the journey times and  install it in
to this fields as the following
Journey_day           char(5)
Journey_hour          char(5)
Journey_ Minutes      char(5)

And this field have a fixed value for  expected time consume ,as the following
fields

Expected_day            char(5)
Expected_hour          char(5)
Expected_ Minutes  char(5)

So I want to calculate the difference between the above fields , for example
If the above fields have this values as follow :
-------------------
Varying fields values
Journey_day          = 0
Journey_hour         = 23
Journey_ Minutes     = 30
 ------------------
This fixed fields values
Expected_day        =  1
Expected_hour       =  10
Expected_ Minutes   =  0

So I want to calculated the difference between the above fields And install it
in this  fields as follow :

Result_day        = to_number(Journey_day - Expected_day)
Resul_hour        = to_number(Journey_hour - Expected_hour)
Result_ Minutes   =to_number(Journey_Minutes- Expected_ Minutes)
The above statements it will not give the Wright result .

The required thing how can I calculate the difference between the above fields
so that I will get the correct result in to those fields Result_day ,
Resul_hour, and Result_ Minutes

Please help
Best regards

Jamil
Followup:
ops$tkyte@ORA920> select t.*,
  2         trunc( e-j ) "Dy",
  3             trunc( mod( (e-j)*24, 24 ) )  "Hr",
  4             trunc( mod( (e-j)*24*60, 60 ) )  "Mi"
  5    from (
  6  select t.*,
  7         trunc(sysdate,'y')+jd+jh/24+jm/24/60 j,
  8         trunc(sysdate,'y')+ed+eh/24+em/24/60 e
  9    from t
 10         ) t
 11  /

 JD  JH  JM  ED  EH  EM J        E         Dy  Hr  Mi
--- --- --- --- --- --- -------- -------- --- --- ---
  0  23  30   1  10   0 01 23:30 02 10:00   0  10  30

Use date arithmetic.  if you are using 9i, you can use an interval type instead
of individual fields.
Rating: 5
Calculating the difference between fields values to days and hours and minutes December 21, 2003
Reviewer: Jamil from Saudi
Dear Sir ,
 Thank you very much for your valuable answer , that is what I want
 but my data in tow tables and the table structure
as follow :

SQL> descr transaction_file
 Name                  Null?       Type
 -----------------------------------------
 TRANSACTION_CODE                  NUMBER
 B_CODE                            NUMBER
 JD                                CHAR(5)
 JH                                CHAR(5)
 JM                                CHAR(5)

SQL> descr city
 Name                Null?         Type
 -------------------------------------------
 CITY_CODE                         NUMBER
 ED                                CHAR(5)
 EH                                CHAR(5)
 EM                                CHAR(5)

And the selected data from both tables as follow :

Table transaction_file contains:

TRANSACTION_CODE     B_CODE      JD    JH    JM
-------------------------------------------------
  1                  2           0     23    30
  2                  2           1     17    21
  3                  1           0     2     0

Table city  contains:

CITY_CODE   ED    EH    EM
-------------------------------
 1          0     2     0
 2          1     10    0

so I want the select statement to select from the above
tow tables
and  give the number of days , hours ,and  minutes
and the link between the tow tables as follow
B_CODE = CITY_CODE  , for each transaction_code
it  should calculate the result .

Notice:

I want to install the number of days ,number of hours
and the number of minutes in a variable
So that I want to install the result in another table.

I try to do it like this, but it does not work

line 12 truncated.
  1  select         trunc( e-j ) "Dy",
  2                 trunc( mod( (e-j)*24, 24 ) )  "Hr",
  3                 trunc( mod( (e-j)*24*60, 60 ) )  "Mi"
  4        from (
  5      select
  6             trunc(sysdate,'y')+jd+jh/24+jm/24/60 j,
  7             trunc(sysdate,'y')+ed+eh/24+em/24/60 e
  8         from city A
  9              WHERE A.CITY_CODE = B.B_CODE
 10              and   B.TRANSACTION_CODE =  B.TRANSACTION_CODE
 11*           ) TRANSACTION_FILE B INTO R_DAYS,R_HOURS,R_MINUTES
 12  /
          ) TRANSACTION_FILE B INTO R_DAYS,R_HOURS,R_MINUTES
                             *
ERROR at line 11:
ORA-00933: SQL command not properly ended

Tank you very much for your great help.

Best regards

Jamil
Followup:
lose the "into r_days,r_hours,r_minutes" first of all.

that only works in programatic environments.

it would be after the select list -- NOT at the end of the list.

actually -- your entire query is sort of "not really SQL at all"

but, your description -- if accurate -- makes this a pretty simple problem.
Just work with the data:

ops$tkyte@ORA920> select *
  2    from city c, transaction_file tf
  3   where c.city_code = tf.b_code
  4  /

 CITY_CODE         ED         EH         EM TRANSACTION_CODE     B_CODE
JD         JH         JM
---------- ---------- ---------- ---------- ---------------- ----------
---------- ---------- ----------
         1          0          2          0                3          1
0          2          0
         2          1         10          0                1          2
0         23         30
         2          1         10          0                2          2
1         17         21

Now, as that is a multi-row result set there will be no "into" clause (that
works with a row only)... so

ops$tkyte@ORA920> select transaction_code,
  2         trunc(sysdate,'y')+jd+jh/24+jm/24/60 j,
  3         trunc(sysdate,'y')+ed+eh/24+em/24/60 e
  4    from city c, transaction_file tf
  5   where c.city_code = tf.b_code
  6  /

TRANSACTION_CODE J                    E
---------------- -------------------- --------------------
               3 01-jan-2003 02:00:00 01-jan-2003 02:00:00
               1 01-jan-2003 23:30:00 02-jan-2003 10:00:00
               2 02-jan-2003 17:21:00 02-jan-2003 10:00:00

but that points out an interesting anomoly in your data -- your end times are
sometimes before the "starts", so, you'll get negative results:

ops$tkyte@ORA920> select transaction_code,
  2         trunc( e-j ) "Dy",
  3         trunc( mod( (e-j)*24, 24 ) )  "Hr",
  4             trunc( mod( (e-j)*24*60, 60 ) )  "Mi"
  5    from (
  6  select transaction_code,
  7         trunc(sysdate,'y')+jd+jh/24+jm/24/60 j,
  8         trunc(sysdate,'y')+ed+eh/24+em/24/60 e
  9    from city c, transaction_file tf
 10   where c.city_code = tf.b_code
 11         )
 12  /

TRANSACTION_CODE         Dy         Hr         Mi
---------------- ---------- ---------- ----------
               3          0          0          0
               1          0         10         30
               2          0         -7        -21
Rating: 5
Calculating the difference between fields values to days and hours and minutes December 22, 2003
Reviewer: Jamil from Saudi
Dear Sir
As usual great response from Tom,Thank you very much for your answer and it
works fine, that is actually what I
want.
Can you clarify these statements is not possible to do the calculation without
this statements
trunc(sysdate,'y')

Best regards

Jamil
Followup:
you certainly can do it without trunc(sysdate,'y')

i used that cause printing out the dates looks "sensible" (they'll be offset
from the first day of the year.  so your duration of 5 days 10 hours 20 minutes
would be 10:20 am on jan 5th -- that was sensible to me as I was developing the
query, something I could understand.  You could use sysdate or any date really
-- but using the first of the year made debugging this easy)
December 22, 2003 -- Thanks for the question regarding "bulk ", version 91
You Asked
Tom:

The following is an exmaple of bulk binding, which is very good.
my question is: if my table have hundreds of column, it is tedious to declare
100 index_table. is there any easy way?

Thanks

 DECLARE
 2     TYPE NumTab IS TABLE OF NUMBER(4) INDEX BY BINARY_INTEGER;
 3     TYPE NameTab IS TABLE OF CHAR(15) INDEX BY BINARY_INTEGER;
 4     pnums  NumTab;
 5     pnames NameTab;
 6     t1 NUMBER(5);
 7     t2 NUMBER(5);
 8     t3 NUMBER(5);
 9
10
11  BEGIN
12     FOR j IN 1..5000 LOOP  -- load index-by tables
13        pnums(j) := j;
14        pnames(j) := 'Part No. ' || TO_CHAR(j);
15     END LOOP;
16     t1 := dbms_utility.get_time;
17     FOR i IN 1..5000 LOOP  -- use FOR loop
18        INSERT INTO parts VALUES (pnums(i), pnames(i));
19     END LOOP;
20     t2 := dbms_utility.get_time;
21     FORALL i IN 1..5000  -- use FORALL statement
22        INSERT INTO parts VALUES (pnums(i), pnames(i));
23     get_time(t3);
24     dbms_output.put_line('Execution Time (secs)');
25     dbms_output.put_line('---------------------');
26     dbms_output.put_line('FOR loop: ' || TO_CHAR(t2 - t1));
27     dbms_output.put_line('FORALL:   ' || TO_CHAR(t3 - t2));
28* END;
SQL> /
and we said...
use records instead.

ops$tkyte@ORA920LAP> declare
  2          type array is table of emp%rowtype index by binary_integer;
  3
  4          l_data array;
  5  begin
  6          select * bulk collect into l_data from emp;
  7
  8          forall i in 1 .. l_data.count
  9                  insert into emp values l_data(i);
 10  end;
 11  /

PL/SQL procedure successfully completed.

now, you used version "91" which was never a version... this works in 9iR2 (9.2)
and up.
Review & Followup

Rating: 5
Bulk collect September 08, 2003
Reviewer: Santhanam from Bangalore India
Excellent
Rating: 4
Java & Blobs November 13, 2003
Reviewer: Vinnie from Orlando
Can this be used with Java & Blob Data?
Followup:
what do you mean?
Rating: 4
performance November 14, 2003
Reviewer: Rukshan Soza from England
How can this be quicker than insert into ... select *
Followup:
the assumption would be of course that line 7 of the example on "how to bulk
bind" would have some amount of nasty procedural code that cannot be performed
in SQL alone

(good point though, if you can do it in a SINGLE SQL statment -- by all means --
DO IT)
Rating: 3
Java Bulk Insert with a BLOB November 14, 2003
Reviewer: Vinnie from Orlando
I am looking for an example of the fastest way from within a JAVA app to insert
a record which contains a BLOB.
We currently have it working with bind variables doing 1 insert/commit at a
time.  How can we speed this up?  Bulk inserts? Using a ORACLE Procedure?
Followup:
less commits = better.  commit when you are DONE working.

any chance you can use sqlldr? (you know, don't write code....)

are you streaming?
are you using the THICK jdbc driver?

whats the SMALLEST example you have that demonstrates HOW you are doing it
(small, yet complete -- emphasis on small)
Rating: 4
Java Bulk Insert with a BLOB November 14, 2003
Reviewer: Vinnie from Orlando
Ok, this is a snippet!

insertEventCall = con.prepareCall( "begin INSERT INTO ...
(a,b,c,BLOB_DATA) VALUES (?,?,? empty_blob())
return BLOB_DATA into ?; end; );

protected void insertEvent()
{
try
{

syncronized( this.con)
{
CallableStatement stmt = insertEventCall;
stmt.setlong (1,...);
stmt.setString (2,...);
stmt.setString (3,...);
stmt.registerOutParameter(4, java.sql.Types.BLOB);
stmt.executeUpdate();
BLOB blob = (BLOB) stmt.getBlob(4);
OutputStream writer = blob.getBinaryOutputStream();
byte[]theBytes =
 this.toByteArray(event.getRaw().getElement() );
writer.write(theBytes);
writer.flush;
writer.close;
con.commit;
} // end syncronize
...
...

Can we use bulk inserts or call a procedure & pass everything in & Let ORACLE
handle it?  Examples are greatly appreciated:)

Hope this is enough
Followup:
ugh,

parse
bind
execute
commit

terrible.... do this

parse ONCE -- ONCE, not more then ONCE. make the insert a "global" class
variable.  parse it ONCE

bind/execute over and over and over

commit as infrequently possible

how big are the blobs?
Rating: 4
BLOBs again! November 14, 2003
Reviewer: Vinnie from Orlando
The insert is only being parsed once.  I just show it there to let you know what
the statement looked like.

The Blobs vary in size.  Average lets say 4k but can grow to 500k at times.

How can I commit infrequently?  Can you give me an example of this with the
blob?
Followup:
thats why I asked for "a simple, short, example that shows HOW you do what you
do"

that's really important, need to see HOW.

to commit less frequently?  don't call commit?  thats the only way.

have you identified the database as being the performance issue here? is it the
bottleneck?
Rating: 5
looking for comments on types based alternative November 14, 2003
Reviewer: Kevin from CT USA
Hi Tom, nice threads, can you please comment on the differences (resource
utilization, etc.) between your first bulk bind exmample and a variation using
objece types.  I can do my own testing for my specific situations to see which
is faster and so on but I was hoping you could give us a little indepth
knowledge about what is happening differently between the two behind the looking
glass.  I am wondering about memory consumption on the client vs. server, how
many copies of things there might be, and most importantly, whatever you figure
I should know about it.  Curiosity may have killed the cat but I find
satisfaction always brings him back.  Your bluk bind example from above was:

ops$tkyte@ORA920LAP> declare
  2          type array is table of emp%rowtype index by binary_integer;
  3
  4          l_data array;
  5  begin
  6          select * bulk collect into l_data from emp;
  7
  8          forall i in 1 .. l_data.count
  9                  insert into emp values l_data(i);
 10  end;
 11  /

PL/SQL procedure successfully completed.

Using objects one would code something like this:

SQL> desc emp
 Name                          Null?    Type
 ----------------------------- -------- --------------------
 EMPNO                         NOT NULL NUMBER(4)
 ENAME                                  VARCHAR2(10)
 JOB                                    VARCHAR2(9)
 MGR                                    NUMBER(4)
 HIREDATE                               DATE
 SAL                                    NUMBER(7,2)
 COMM                                   NUMBER(7,2)
 DEPTNO                                 NUMBER(2)

SQL> create table new_emp as select * from emp where rownum < 1;

Table created.

SQL> create or replace type o_emp is object
  2  (
  3    EMPNO                          NUMBER(4)
  4  , ENAME                          VARCHAR2(10)
  5  , JOB                            VARCHAR2(9)
  6  , MGR                            NUMBER(4)
  7  , HIREDATE                       DATE
  8  , SAL                            NUMBER(7,2)
  9  , COMM                           NUMBER(7,2)
 10  , DEPTNO                         NUMBER(2)
 11  )
 12  /

Type created.

SQL> show errors
No errors.
SQL> create or replace type c_emp is table of o_emp
  2  /

Type created.

SQL> show errors
No errors.
SQL> declare
  2     c_emp_v c_emp;
  3  begin
  4     select cast(multiset(select * from emp) as c_emp) into c_emp_v from
dual;
  5     insert into new_emp select * from table(cast(c_emp_v as c_emp));
  6  end;
  7  /

PL/SQL procedure successfully completed.
Followup:
well, neither will affect client memory consumption since the client (sqlplus)
isn't really involved.

use runstats
http://asktom.oracle.com/~tkyte/runstats.html
and benchmark it!

I would use the plsql only method unless i needed the extra capabilities of the
collection.  I believe you'll find the plsql index by table ( a plsql ONLY thing
) to be slightly more efficient in plsql.  the collection type is nice to return
things to other languages (plsql table works with plsql ONLY) and when you need
to use SQL on it.
Rating: 5
Simply superb November 14, 2003
Reviewer: "Fan"
Rating: 2
Bulk Insert with a BLOB November 14, 2003
Reviewer: Vinnie from Orlando
I sent a short snippet a few posts ago.  Do you need more?

I do realize that I can commit less.  What we would really like to be able to do
is do a batch insert, something like addBatch with a certain batch size.  But we
are not sure if we can do the batch insert with the BLOB because you first have
to get the blob locator back from the insert.

Here is the code again:
 insertEventCall = con.prepareCall( "begin INSERT INTO ...
(a,b,c,BLOB_DATA) VALUES (?,?,? empty_blob())
return BLOB_DATA into ?; end; );

protected void insertEvent()
{
try
{

syncronized( this.con)
{
CallableStatement stmt = insertEventCall;
stmt.setlong (1,...);
stmt.setString (2,...);
stmt.setString (3,...);
stmt.registerOutParameter(4, java.sql.Types.BLOB);
stmt.executeUpdate();
BLOB blob = (BLOB) stmt.getBlob(4);
OutputStream writer = blob.getBinaryOutputStream();
byte[]theBytes =
 this.toByteArray(event.getRaw().getElement() );
writer.write(theBytes);
writer.flush;
writer.close;
con.commit;
} // end syncronize
...
...
Perhaps I am asking the question wrong...let me know.
Followup:
the short snippet did not represent reality.  so, i spent time telling you how
to do some tuning that "already happened" but I did not know that since you
posted a fake example that did not represent what you really did - which is all
I asked for:

whats the SMALLEST example you have that demonstrates HOW you are doing it
(small, yet complete -- emphasis on small)

I was just expressing my disappointment in the fact that we didn't get that.

again, tell us the process here.

o how many blobs are you loading
o why AREN'T we using a simple tool (why are we writing code here)
o how long is this taking and how long must it take
Rating: 4
Bulk - missing something November 14, 2003
Reviewer: Phil
SQL> select banner from v$version
  2  /

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
PL/SQL Release 9.2.0.1.0 - Production
CORE    9.2.0.1.0       Production
TNS for 32-bit Windows: Version 9.2.0.1.0 - Production
NLSRTL Version 9.2.0.1.0 - Production

SQL> desc big_all_tables
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 OWNER                                              VARCHAR2(30)
 TABLE_NAME                                         VARCHAR2(30)
 COLUMN_NAME                                        VARCHAR2(30)
 DATA_TYPE                                          VARCHAR2(106)
 DATA_TYPE_MOD                                      VARCHAR2(3)
 DATA_TYPE_OWNER                                    VARCHAR2(30)
 DATA_LENGTH                                        NUMBER
 DATA_PRECISION                                     NUMBER
 DATA_SCALE                                         NUMBER
 NULLABLE                                           VARCHAR2(1)
 COLUMN_ID                                          NUMBER
 DEFAULT_LENGTH                                     NUMBER
 NUM_DISTINCT                                       NUMBER
 LOW_VALUE                                          RAW(32)
 HIGH_VALUE                                         RAW(32)
 DENSITY                                            NUMBER
 NUM_NULLS                                          NUMBER
 NUM_BUCKETS                                        NUMBER
 LAST_ANALYZED                                      DATE
 SAMPLE_SIZE                                        NUMBER
 CHARACTER_SET_NAME                                 VARCHAR2(44)
 CHAR_COL_DECL_LENGTH                               NUMBER
 GLOBAL_STATS                                       VARCHAR2(3)
 USER_STATS                                         VARCHAR2(3)
 AVG_COL_LEN                                        NUMBER
 CHAR_LENGTH                                        NUMBER
 CHAR_USED                                          VARCHAR2(1)
 V80_FMT_IMAGE                                      VARCHAR2(3)
 DATA_UPGRADED                                      VARCHAR2(3)

SQL> desc million_all_tables
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 OWNER                                              VARCHAR2(30)
 TABLE_NAME                                         VARCHAR2(30)
 COLUMN_NAME                                        VARCHAR2(30)
 DATA_TYPE                                          VARCHAR2(106)
 DATA_TYPE_MOD                                      VARCHAR2(3)
 DATA_TYPE_OWNER                                    VARCHAR2(30)
 DATA_LENGTH                                        NUMBER
 DATA_PRECISION                                     NUMBER
 DATA_SCALE                                         NUMBER
 NULLABLE                                           VARCHAR2(1)
 COLUMN_ID                                          NUMBER
 DEFAULT_LENGTH                                     NUMBER
 NUM_DISTINCT                                       NUMBER
 LOW_VALUE                                          RAW(32)
 HIGH_VALUE                                         RAW(32)
 DENSITY                                            NUMBER
 NUM_NULLS                                          NUMBER
 NUM_BUCKETS                                        NUMBER
 LAST_ANALYZED                                      DATE
 SAMPLE_SIZE                                        NUMBER
 CHARACTER_SET_NAME                                 VARCHAR2(44)
 CHAR_COL_DECL_LENGTH                               NUMBER
 GLOBAL_STATS                                       VARCHAR2(3)
 USER_STATS                                         VARCHAR2(3)
 AVG_COL_LEN                                        NUMBER
 CHAR_LENGTH                                        NUMBER
 CHAR_USED                                          VARCHAR2(1)
 V80_FMT_IMAGE                                      VARCHAR2(3)
 DATA_UPGRADED                                      VARCHAR2(3)

SQL> @bulk_collect_record
SP2-0310: unable to open file "bulk_collect_record.sql"
SQL> @bulk_coll_record
declare
*
ERROR at line 1:
ORA-03113: end-of-file on communication channel

SQL>

Hello Tom,

I have created another procedure for bulk collect using your site that works
fine. This new concept I am not able use. It just disconnects me.
Followup:
9201 is quite a few patch sets ago.  9204 is the current patch set.  that is
what support will say when you go to file this tar (which is what you must do at
this point)
Rating: 4
Sorry , missed the code November 14, 2003
Reviewer: Phil
declare
          type array is table of big_all_tables%rowtype index by binary_integer;
          l_data array;
begin
          select * bulk collect into l_data from big_all_tables;
          forall i in 1 .. l_data.count
                 insert into million_all_tables values l_data(i);
end;
/
Followup:
oh my -- is big_all_tables really big????

you are NOT array fetching like 1,000,000 rows are you?????????

that must be:

   open c;
   loop
      fetch c bulk collect into l_data limit N;
      forall i in 1 .. l_data.count insert into....
      exit when c%notfound;
   end loop;
   close c;
Rating: 2
More Blobs November 15, 2003
Reviewer: vinnie from orlando
Ok, lets try again.

We are loading a record being read from another source.  The source has a
constant flow of data.  At times, the rate can be much greater than other times.
 The data is currently read using a JAVA app.  At the current time we can't
change that architecture.  We currently insert 1 record at a time.
The table struct it:
event_id   number
event_type varchar2
event_data blob
We are wondering if we can batch insert these rows, because I know that batch
inserts dramatically increase insert  performance.  We know how long the current
insert rate is, but feel if we can use batch inserts this can be dratically
increased.
Followup:
you can batch rows  when the blob is 4000 bytes or less since you can just bind
a RAW and the blob will be created implicitly.

as you pointed out with the lob locator however, you need it right after you
insert in order to be able to stream to it -- so by very definition you cannot
batch the inserts since you must "insert, get locator, stream".  if you batched
-- you would not "get locator"

so, you could set up two statements -- one for blobs of less then 4000 bytes and
another for > 4000 bytes.  You can set the "batch" size using a method of the
prepared statement -- so that the 4000 byte blobs will be array inserted (and
sent anytime you commit).  that way you could batch the small rows and just
insert the big.

you are using the THICK oci driver right?
also -- you have IDENTIFIED a PERFORMANCE ISSUE right?
and you have diagnosed that it is this, the insert?
have you played with cached vs non-cached blobs?  (this might be a good
candidate for cached blobs)
Rating: 4
Bulk inserts, Limit Mising November 15, 2003
Reviewer: Phil
SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
PL/SQL Release 9.2.0.1.0 - Production
CORE    9.2.0.1.0       Production
TNS for 32-bit Windows: Version 9.2.0.1.0 - Production
NLSRTL Version 9.2.0.1.0 - Production

SQL> select count(*) from big_all_tables;

  COUNT(*)
----------
     31322

SQL> select count(*) from million_all_tables;

  COUNT(*)
----------
         0

SQL> get bulk_coll_record
  1  create or replace procedure insert_million_limit
  2  as
  3            type array is table of big_all_tables%rowtype index by
binary_integer;
  4            l_data array;
  5     cursor c1 is select
  6     OWNER,
  7     TABLE_NAME,
  8     COLUMN_NAME,
  9     DATA_TYPE,
 10     DATA_TYPE_MOD,
 11     DATA_TYPE_OWNER,
 12     DATA_LENGTH,
 13     DATA_PRECISION,
 14     DATA_SCALE,
 15     NULLABLE,
 16     COLUMN_ID,
 17     DEFAULT_LENGTH,
 18     NUM_DISTINCT,
 19     LOW_VALUE,
 20     HIGH_VALUE,
 21     DENSITY,
 22     NUM_NULLS,
 23     NUM_BUCKETS,
 24     LAST_ANALYZED,
 25     SAMPLE_SIZE,
 26     CHARACTER_SET_NAME,
 27     CHAR_COL_DECL_LENGTH,
 28     GLOBAL_STATS,
 29     USER_STATS,
 30     AVG_COL_LEN,
 31     CHAR_LENGTH,
 32     CHAR_USED,
 33     V80_FMT_IMAGE,
 34     DATA_UPGRADED from big_all_tables;
 35  begin
 36       open c1;
 37       loop
 38       fetch c1 bulk collect into l_data LIMIT 5;
 39            forall i in 1 .. l_data.count
 40                   insert into million_all_tables values l_data(i);
 41       exit when c1%notfound;
 42       end loop;
 43       close c1;
 44* end;
 45  /

Procedure created.

SQL> execute insert_million_limit
BEGIN insert_million_limit; END;

*
ERROR at line 1:
ORA-03113: end-of-file on communication channel

SQL>

Hello Tom,

I am sorry to bother you again, but again this is the same error.
Followup:
read my original answer, up 2 reviews.
Rating: 4
Blobs November 17, 2003
Reviewer: Vinnie from Orlando
Just wanted to say thanks for the help.
I'm sorry I was not explaining or giving the details
required.

This dialog was most usefull!!
Rating: 4
Bulk insert error November 19, 2003
Reviewer: Venkat from Detroit, MI USA
We recently moved to 9.2 and I was trying your example for bulk insert and got
this error. Can you please help? Thanks..

  1  declare
  2   type array is table of ta%rowtype index by binary_integer;
  3   l_data array;
  4  begin
  5   select * bulk collect into l_data from ta;
  6   forall i in 1 .. l_data.count
  7    insert into ta values l_data(i);
  8* end;
SQL> /
declare
*
ERROR at line 1:
ORA-06550: line 7, column 24:
PLS-00518: This INSERT statement requires VALUES clause containing a
parenthesised list of values
ORA-06550: line 7, column 2:
PL/SQL: SQL Statement ignored
ORA-06550: line 7, column 2:
PLS-00435: DML statement without BULK In-BIND cannot be used inside FORALL
Followup:
ops$tkyte@ORA920LAP> create table ta( x int, y int );

Table created.

ops$tkyte@ORA920LAP> insert into ta values ( 1, 1 );

1 row created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> declare
  2   type array is table of ta%rowtype index by binary_integer;
  3   l_data array;
  4  begin
  5   select * bulk collect into l_data from ta;
  6   forall i in 1 .. l_data.count
  7    insert into ta values l_data(i);
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP>

i cannot reproduce in 9203/9204?
Rating: 4
Continuing the previous question... December 18, 2003
Reviewer: A reader
Does the compatible parameter have to be 9.2.0.4.0 for this to work? The value
in our db is 9.2.0.0.0 (and I got the previous error "PLS-00435: DML statement
without BULK In-BIND cannot be used inside FORALL")

v$version values :
...
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
PL/SQL Release 9.2.0.4.0 - Production
...

Thanks!
Followup:
nope, show us this same output please:

ops$tkyte@ORA920PC> create table ta( x int, y int );

Table created.

ops$tkyte@ORA920PC> insert into ta values ( 1, 1 );

1 row created.

ops$tkyte@ORA920PC> declare
  2   type array is table of ta%rowtype index by binary_integer;
  3   l_data array;
  4  begin
  5   select * bulk collect into l_data from ta;
  6   forall i in 1 .. l_data.count
  7    insert into ta values l_data(i);
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920PC> show parameter compat

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
compatible                           string      9.2.0.0.0
plsql_v2_compatibility               boolean     FALSE
ops$tkyte@ORA920PC> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
PL/SQL Release 9.2.0.4.0 - Production
CORE    9.2.0.3.0       Production
TNS for Linux: Version 9.2.0.4.0 - Production
NLSRTL Version 9.2.0.4.0 - Production
Rating: 3
output... December 19, 2003
Reviewer: A reader
SQL> create table ta( x int, y int );

Table created.

SQL> insert into ta values ( 1, 1 );

1 row created.

SQL> declare
  2     type array is table of ta%rowtype index by binary_integer;
  3     l_data array;
  4  begin
  5     select * bulk collect into l_data from ta;
  6     forall i in 1 .. l_data.count
  7      insert into ta values l_data(i);
  8  end;
  9  /
    insert into ta values l_data(i);
                          *
ERROR at line 7:
ORA-06550: line 7, column 27:
PLS-00518: This INSERT statement requires VALUES clause containing a
parenthesised list of values
ORA-06550: line 7, column 5:
PL/SQL: SQL Statement ignored
ORA-06550: line 7, column 5:
PLS-00435: DML statement without BULK In-BIND cannot be used inside FORALL

SQL> show parameter compat
unknown SHOW option "parameter"
compatibility version NATIVE
SQL> select name, value from v$parameter where name like '%compat%';

NAME
----------------------------------------------------------------
VALUE
---------------------------------------------------------------------------------
-------------------
compatible
9.2.0.0.0

plsql_v2_compatibility
FALSE

2 rows selected.

SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
PL/SQL Release 9.2.0.4.0 - Production
CORE    9.2.0.3.0       Production
TNS for Solaris: Version 9.2.0.4.0 - Production
NLSRTL Version 9.2.0.4.0 - Production

5 rows selected.

SQL>
Followup:
hmm, wonder if it is the client is the issue (not using the 9i plus client I
see)

but -- I cannot reproduce this -- i've connected to 9204 from 734, 806, 817,
9ir2 and could not reproduce this so.....

time to file a tar with support.
Rating: 4
Comparing records in pl/sql table December 22, 2003
Reviewer: newton from USA
Tom,

I have this problem that I can't compare more than records here...

Begin
       Select <columnBulk Collect Into lc_id
         From <table>;

       i := lc_id.First;
       n := pcId.First;

/* in pcId Variable...it has only 3 rows */

       While i Is Not Null Loop
        If pcId.Exists(n) Then
          If lc_id(i) <> pcId(n) Then
             dbms_output.put_line(i);
          Else
            n := pcId.Next(n);
          End If;
        End If;
          i := lc_id.Next(i);
       End Loop;

/* How to get those records which are not same as lc_id */
Followup:
a simple array compare?

you need a double loop.  compare each p(n) to each l(i).  very straighforward
programming exercise.
December 22, 2003 Justin -- Thanks for the question regarding "owa_util.cellsprint", version 9.2.0.1
You Asked
1) Why will this call to a parameter (p_atty_id) in owa_util.cellsprint not
work? (something to do with having to put the query through
owa_util.bind_variables??)
2) And why?

I am studying one of your other posts, and trying to see if I simply need to use
owa_util.bind_variables to fix this.

I have greatly simplified the query. I thought I had this working, but I must
have had my hard coded value (''M5119'' .. which works fine) set.

The error in the log is:

[Mon Jan 27 10:47:28 2003] [error] mod_plsql: /gen_inq/apps/garn_query HTTP-404
ORA-00904: "P_ATTY_ID": invalid identifier
ORA-06512: at "SYS.DBMS_SYS_SQL", line 1476
ORA-06512: at "SYS.OWA_UTIL", line 1681
ORA-06512: at "SYS.OWA_UTIL", line 1788
ORA-06512: at "SHELBY.GARN_QUERY", line 24
ORA-06512: at line 10

CREATE OR REPLACE PROCEDURE garn_query (p_atty_id IN VARCHAR2 DEFAULT NULL) AS
  BEGIN
    HTP.htmlOpen;
    HTP.headOpen;
    HTP.title('Attorney Garnishment Report for Date Range' );
    HTP.linkRel( crel => 'STYLESHEET', curl => '/gs/gi/shelby.css', ctitle =>
'text/css');
    HTP.headClose;
    HTP.bodyOpen;
    HTP.div('center','class = title');
    HTP.p('Attorney Garnishment Report');
    HTP.br;
    HTP.br;
    HTP.p('</DIV>');
    HTP.tableOpen('border = 1', 'center');
    htp.p(p_atty_id); -- works fine
      OWA_UTIL.cellsPrint('SELECT p_atty_id from dual'); -- invalid identifier
    HTP.tableClose;
    HTP.hr;
    HTP.p('<CENTER>');
    HTP.anchor( '/gen_inq/apps/garn_inquiry', 'Go Back');
    HTP.bodyClose;
    HTP.htmlClose;
  END;
/

Thanks so much for your help.

Justin
and we said...
If you went into sqlplus and typed in

select p_atty_id from dual

would it work -- if not, it'll not work in cellsprint.  First, just think about
normal PLSQL scoping -- in the subroutine cellsprint, would p_atty_id be
"visible"?  Nope, not unless it was a formal parameter (and it isn't).  Second
-- inside of cellsprint, the query is actually parsed by dbms_sql.parse --
inside of that subroutine -- would p_atty_id be visible?  No -- it wouldn't.
Lastly -- at the SQL layer -- is there anything such as p_atty_id?  No -- there
isn't.

So, you have to BIND that variable value into the query.  That is how you pass
inputs to SQL -- just like we use parameters to pass inputs to subroutines -- we
use bind variables to pass inputs to SQL.

Use the supplied routines for binding:

ops$tkyte@ORA920> declare
  2          p_atty_id varchar2(25) := 'Hello World';
  3  begin
  4          owa_util.cellsprint( owa_util.bind_Variables( 'select :bv1 from
dual', 'bv1', p_atty_id ) );
  5  end;
  6  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> @owainit
ops$tkyte@ORA920> declare
  2          nm      owa.vc_arr;
  3          vl      owa.vc_arr;
  4  begin
  5          nm(1) := 'WEB_AUTHENT_PREFIX';
  6          vl(1) := 'WEB$';
  7          owa.init_cgi_env( nm.count, nm, vl );
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec owa_util.showpage;
<TR>
<TD>Hello World</TD>
</TR>

PL/SQL procedure successfully completed.

and it'll work
Review & Followup

Rating: 5
Thank you very much January 27, 2003
Reviewer: Justin from PA USA
Your description of why it wouldn't work was great. Thank you.

I didn't really follow your supplied sub-routines for binding. (It's me, not
you)

And I must say the most important thing is that I have it working now :)
Rating: 5
January 28, 2003
Reviewer: A reader from Washington
TOM,
Is there any function where I can split concatenated name to
first name, last name, MI. For eg:- like
col
----------
Firt Last
Fistname Lastname .M

fist_name  last_name   mi
--------   ---------  ---
First       Last
Firstname   Lastname M

Regards,
Followup:
substr + instr

ps$tkyte@ORA920> select x,
  2         substr(x,1,instr(x||' ',' ')-1 ) firstname,
  3  substr(x,instr(x||' ',' ')+1, instr(x||' ',' ',1,2)-instr(x||' ',' ') )
lastName,
  4             substr(x,instr(x||' ',' ',1,2)+1 ) MI
  5    from t;

X                    FIRSTNAME  LASTNAME   MI
-------------------- ---------- ---------- ----
first last           first      last
firstNm lastNm m.    firstNm    lastNm     m.
Rating: 5
more January 28, 2003
Reviewer: Justin from USA
Tom,

I am writing an application that is supposed to spit out rows of paginated data,
allowing a user to select rows they want to be saved to a text file for their
download.

I used cellsprint to get the paginated data. That works.
Now I am thinking about check boxes for each row, and see if checked = true, to
gather what they have checked, to put into the txt file.

Two problems there I see(probably more), 1) can you keep track of checked = true
from page to nextpage etc? (I saw a note on flexible parameters you answered
today? Is that the route?)
2) With cellsprint, I can't get in the "loop" that creates the rows to place
checkboxes, can I?

Can you do pagination without using cellsprint?

Here is my code. I understand if this is too much. If so, just look at my code,
and see if there are parts that are written poorly, so I can make it better.
(The query is brutal.. any suggestions? Have to get the cases the atty has
worked on in order to get the defendant for the case )

CREATE OR REPLACE PROCEDURE garn_query (p_start IN NUMBER DEFAULT 0,
                                        p_atty_id IN VARCHAR2 DEFAULT NULL)
AS
   l_more_data BOOLEAN DEFAULT false;  -- More rows left?
   g_rows_per_page CONSTANT NUMBER := 20;
   v_sql   LONG;           -- Query text
   csr     INTEGER;        -- Cursor identifier
   v_total NUMBER DEFAULT 0;
  BEGIN
  -- Debug message
  v_sql := 'select rownum, a.*
            from (SELECT
                   cdrcpty.cdrcpty_case_id
  "Case ID",
                   initcap(spriden.spriden_last_name)
  "Defendant Name",
                   TO_CHAR(cdrjaga.cdrjaga_amt,''$999,999.99'')
  "Judgement Amount",
                   TO_CHAR(sum(cbraccd.cbraccd_balance),''$999,999.99'')
  "Balance Due"
--
TO_CHAR(max(cbrappl.cbrappl_activity_date),''DD-fmMonth-YYYY'') "Last Payment
Date"
           FROM
                   cdrcpty, spriden, cbraccd, cdrjaga, cdbcase
                   --, cbrappl
           WHERE
                   cdbcase.cdbcase_id in (select distinct
cdrcpty.cdrcpty_case_id
                                           from
                                                     cdrcpty, spriden
                                           where
                                                     spriden.spriden_id =
:attyid AND
                                                     cdrcpty_pidm =
spriden_pidm) AND
                   cdrcpty.cdrcpty_ptyp_code = ''DFT'' AND
                   cdrcpty.cdrcpty_pidm = spriden.spriden_pidm AND
                   cdrcpty.cdrcpty_case_id = cdbcase.cdbcase_id AND
                   cdbcase.cdbcase_id = cbraccd.cbraccd_case_id AND
                   cdbcase.cdbcase_id = cdrjaga.cdrjaga_case_id AND
--                 cdbcase.cdbcase_id = cbrappl.cbrappl_case_id AND
                   cdbcase.cdbcase_case_security <= 5 AND
                   spriden.spriden_change_ind is null AND
                   cdrcpty.cdrcpty_end_date is null AND
                   cdrjaga.cdrjaga_activity_date < (sysdate - 10) AND
                   cdrjaga.cdrjaga_amt > 0 AND
                   cbraccd.cbraccd_balance > 0 AND
                   rownum < 1000
           GROUP BY
                   cdrcpty.cdrcpty_case_id,
                   spriden.spriden_last_name,
                   cdrjaga.cdrjaga_amt) a';
  csr := owa_util.bind_variables(v_sql, 'attyid', p_atty_id);
    HTP.htmlOpen;
    HTP.headOpen;
    HTP.title('Attorney Garnishment Report for Date Range' );
    HTP.linkRel( crel => 'STYLESHEET', curl => '/gs/gi/shelby.css', ctitle =>
'text/css');
    HTP.headClose;
    HTP.bodyOpen;
    HTP.div('center','class = title');
    HTP.p('Attorney Garnishment Report');
    HTP.br;
    HTP.br;
    HTP.p('</DIV>');
    HTP.tableOpen('border = 1', 'center');
    HTP.tableHeader('Line #', 'center');
    HTP.tableHeader('Case ID', 'center');
    HTP.tableHeader('Defendant Name', 'center');
    HTP.tableHeader('Judgement Amount', 'center');
    HTP.tableHeader('Balance Due', 'center');
--    HTP.tableHeader('Last Payment Date', 'center');
      OWA_UTIL.cellsPrint(csr, g_rows_per_page, null, p_start, l_more_data);
    HTP.tableClose;
--    HTP.hr;
    HTP.p('<CENTER>');
--    htp.p('Total Number of Cases Returned:');
    HTP.hr;
      HTP.anchor('garn_query?p_start=0&p_atty_id=' || urlencode(p_atty_id),
'&nbsp;First Page&nbsp;');
      htp.p('&nbsp;&nbsp;&nbsp;&nbsp;');
      IF (p_start > 0 ) THEN
         HTP.anchor ('garn_query?p_start='||greatest((p_start -
g_rows_per_page),0) || '&p_atty_id=' || urlencode(p_atty_id),
'&nbsp;Prev&nbsp;');
      END IF;
      htp.p('&nbsp;&nbsp;&nbsp;&nbsp;');
      IF (l_more_data) THEN
         HTP.anchor('garn_query?p_start='|| (p_start + g_rows_per_page) ||
'&p_atty_id=' || urlencode(p_atty_id), '&nbsp;Next&nbsp;');
      END IF;
    HTP.hr;
    HTP.anchor('/gen_inq/apps/garn_inquiry', 'Go Back');
    HTP.p('</CENTER>');
    HTP.bodyClose;
    HTP.htmlClose;
  END;
/

Thank you so much for your time.

Justin
Followup:
1) yes, you would just use an array.  it's a big answer if you need the code so
next time you see that I'm taking q's you can ask it there if you cannot figure
it out.

2) sure

select *
  from ( select a.*, rownum R
           from ( YOUR_QUERY_HERE ) a
          where rownum < :max_row_you_want )
 where r >= :min_row_you_want;

gets you a slice of a result set -- then you can print it yourself.
Rating: 5
utl_file to create downloadable file January 29, 2003
Reviewer: Justin from PA USA
I didn't know whether to post a new question, or continue here. I continued
because I wanted you to remember the context. Let me know for future ref. if I
should spawn a new question for this type of situation. thx

I have simplified the goal of my application for the time being. I am working on
having only ONE returned row (the attorney will search by case id, rather than
by their id which would give them all their cases.) So no pagination, or
multiple checkboxes results to keep track of.

** What I need to do is create a text file, from utl_file that the user can
download, containing the row information.

Right now, my last procedure (create_file) simply shows me the data in a new
page.

**Could you help me out with the utl_file piece, and how to give the user a
button that will let them download the file?

(I am going to be looking at it now, I just wanted to post the question before
your queue filled up. :)

Please also let me know if there are parts of it that are not written well. I'm
pretty new to programming in general.
One part I'm not sure is appropriate is jamming the data into that checkbox
value variable??

#############        #############           ############
CREATE OR REPLACE PROCEDURE garn_caseid_query (p_atty_id IN VARCHAR2 DEFAULT
NULL)
AS
   v_sql       LONG;           -- Query text
   csr         INTEGER;        -- Cursor identifier
   v_min_rownum    NUMBER DEFAULT 0;
   v_max_rownum NUMBER DEFAULT 20;

  CURSOR c_garn_caseid_info IS
       SELECT
                   cdrcpty.cdrcpty_case_id
"Case ID",
                   initcap(spriden.spriden_last_name)
"Defendant Name",
                   TO_CHAR(cdrjaga.cdrjaga_amt,'fm$999,999.99')
  "Judgement Amount",
                   TO_CHAR(sum(cbraccd.cbraccd_balance),'fm$999,999.99')
  "Balance Due"
--
TO_CHAR(nvl(max(cbrappl.cbrappl_activity_date),0),'fmDD-Month-YYYY') "Last
Payment Date"
           FROM
                   cdrcpty, spriden, cbraccd, cdrjaga, cdbcase
                   --, cbrappl
           WHERE
                   cdbcase.cdbcase_id = p_atty_id AND
                   cdrcpty.cdrcpty_ptyp_code = 'DFT' AND
                   cdrcpty.cdrcpty_pidm = spriden.spriden_pidm AND
                   cdrcpty.cdrcpty_case_id = cdbcase.cdbcase_id AND
                   cdbcase.cdbcase_id = cbraccd.cbraccd_case_id AND
                   cdbcase.cdbcase_id = cdrjaga.cdrjaga_case_id AND
--                 cdbcase.cdbcase_id = cbrappl.cbrappl_case_id AND
                   cdbcase.cdbcase_case_security <= 5 AND
                   spriden.spriden_change_ind is null AND
                   cdrcpty.cdrcpty_end_date is null AND
                   cdrjaga.cdrjaga_activity_date < (sysdate - 10) AND
                   cdrjaga.cdrjaga_amt > 0 AND
                   cbraccd.cbraccd_balance > 0
           GROUP BY
                   cdrcpty.cdrcpty_case_id,
                   spriden.spriden_last_name,
                   cdrjaga.cdrjaga_amt;
  BEGIN
    HTP.htmlOpen;
    HTP.headOpen;
    HTP.title('Attorney Garnishment Report for Date Range' );
    HTP.linkRel( crel => 'STYLESHEET', curl => '/gs/gi/shelby.css', ctitle =>
'text/css');
    HTP.headClose;
    HTP.bodyOpen;
    HTP.div('center','class = title');
    HTP.p('Attorney Garnishment Report');
    HTP.br;
    HTP.br;
    HTP.p('</DIV>');
    HTP.tableOpen('border = 1', 'center');
    HTP.tableHeader('Add?', 'center');
    HTP.tableHeader('Case ID', 'center');
    HTP.tableHeader('Defendant Name', 'center');
    HTP.tableHeader('Judgement Amount', 'center');
    HTP.tableHeader('Balance Due', 'center');
--    HTP.tableHeader('Last Payment Date', 'center');
    HTP.formOpen('create_file');
      HTP.formHidden('cols','dummy');
        FOR case_rec IN c_garn_caseid_info LOOP
          HTP.tableRowOpen;
            HTP.tableData(HTF.formCheckbox('cols',trunc(case_rec."Case
ID")||'#'||case_rec."Defendant Name"||'#'||case_rec."Judgement
Amount"||'#'||case_rec."Balance Due"), 'center');
            HTP.tableData(case_rec."Case ID", 'center');
            HTP.tableData(case_rec."Defendant Name", 'center');
            HTP.tableData(case_rec."Judgement Amount", 'center');
            HTP.tableData(case_rec."Balance Due", 'center');
--            HTP.tableData(case_rec."Last Payment Date", 'center');
          HTP.tableRowClose;
        END LOOP;
    HTP.tableClose;
    HTP.p('<CENTER>');
    HTP.hr;
      HTP.formSubmit(NULL,'Create Text File');
    HTP.formClose;
    HTP.hr;
    HTP.anchor('/gen_inq/apps/garn_caseid_inquiry', 'Go Back');
    HTP.p('</CENTER>');
    HTP.bodyClose;
    HTP.htmlClose;
  END;
/

#############        #############           ############
compliments of your site

create or replace package my_types
as
   type myArray is table of varchar2(4000)
         index by binary_integer;

   empty myArray;
end;
/

#############        #############           ############

CREATE OR REPLACE PROCEDURE create_file
  (cols in MY_TYPES.myArray default MY_TYPES.empty)
  IS
--     col_counter INTEGER;
BEGIN
   htp.htmlopen;
   htp.headopen;
   htp.title('File would have');
   htp.headclose;
   htp.bodyopen;
   htp.line;
--   htp.img('/ows-img/ows.gif');
   htp.header(1, 'File would have');
   htp.line;
   htp.paragraph;
--   col_counter := 2;
--   LOOP
--      htp.print(cols(col_counter)|| ' has some very interesting writings.');
      htp.p(cols(2));
      htp.paragraph;
--      col_counter := col_counter + 1;
--   END LOOP;
EXCEPTION
   WHEN no_data_found
     THEN
      htp.paragraph;

htp.anchor('/sample/bin/show-sample-source.sh?FILENAME=/owa/tables.sql','Show
Source');
      htp.line;
      htp.address('(c)1996 Oracle Corporation');
      htp.bodyclose;
      htp.htmlclose;
END;
/
Followup:
You don't need UTL_FILE at all -- you just need to write back to the browser.

do you want the data formatted HOW?

o in a spreadsheet?
o just as a text report?

what would this file look like?
Rating: 5
follow up January 29, 2003
Reviewer: Justin from PA USA
Are you just saying have them do file>save as from the menu bar with my data in
the screen? (:-) Cause that sure makes sense now. lol

The bottom line is, they will be taking the data (from the txt file I give them)
and doing an MS Word mail merge. So I looked at mail merge, and it accepts #
delimiters fine, so that is what I chose.

After I enter a CaseID, and click the Retreive button, I get back a page with
one row and a checkbox beside it, and a create file button.

When I check the checkbox, and click the button to kick off create_file, the
data shows up in the next page as:

408120#Stegall, Debra#$10,606.21#$11,556.96

I know the user could just select the txt here, and save it to a file, but I'm
being asked to let them download the info in a file. So is what I said at the
top what you are saying?

2) In that download vein, (assuming I were to use a link) I know browsers just
display the contents of a text file rather than prompt with a box to save the
file, due to mime type stuff. Can I force a mime type that will cause them to
get the dialogue?

Thanks so much
Followup:
just return a page like this:

...
begin
  owa_util.mime_header( 'application/octet-stream' );
  htp.p( '408120#Stegall, Debra#$10,606.21#$11,556.96' );
end;

you'll get the file save as box.

2) see #1
Rating: 5
owa_util always with invokers right? November 10, 2003
Reviewer: Holger Bär from Germany
Hello Tom,

does owa_util always use invokers rights? The following
small test case seems to indicate this:

create table t (val varchar2(10));

insert into t values ('Hello');
insert into t values ('World');

create or replace procedure p is
  l_cursor         integer;
  l_status integer;
  l_colval varchar2(10);
begin
/*
  l_cursor := dbms_sql.open_cursor;
  dbms_sql.parse (l_cursor, 'select val from t', dbms_sql.native);
  dbms_sql.define_column (l_cursor, 1, l_colval, 10);
  l_status := dbms_sql.execute (l_cursor);
  while ( dbms_sql.fetch_rows (l_cursor) > 0 )
  loop
    dbms_sql.column_value (l_cursor, 1, l_colVal);
    dbms_output.put_line (l_colval);
  end loop;
  dbms_sql.close_cursor (l_cursor);
*/
  l_cursor := owa_util.bind_variables ('select val from t');
  owa_util.cellsprint (l_cursor);
end;

a@CSBILD>exec p;

a@CSBILD>exec owa_util.showpage;
Content-type: text/html
Content-length: 52
<TR>
<TD>Hello</TD>
</TR>
<TR>
<TD>World</TD>
</TR>

PL/SQL-Prozedur wurde erfolgreich abgeschlossen.

a@CSBILD>grant execute on p to b;
a@CSBILD>@connect b/b
b@CSBILD>@owainit

PL/SQL-Prozedur wurde erfolgreich abgeschlossen.

b@CSBILD>set serveroutput on
b@CSBILD>exec a.p;
BEGIN a.p; END;

*
FEHLER in Zeile 1:
ORA-00942: Tabelle oder View nicht vorhanden
ORA-06512: in "SYS.DBMS_SYS_SQL", Zeile 1505
ORA-06512: in "SYS.OWA_UTIL", Zeile 1681
ORA-06512: in "A.P", Zeile 18
ORA-06512: in Zeile 1

If I instead of owa_util use the block currently commented
out, every thing works as I expected.

What am I missing?

Thanks, Holger
Followup:
yes, owa_util runs as caller when parsing sql.
Rating: 4
Erase htp buffer and starting over December 22, 2003
Reviewer: Asktom Fan from CT
I want to erase the current cumulative content of the HTP buffer and start
writing a new page like :

owa_util.cellsprint(....p_reccnt => l_returncount) ;
IF l_returncount <= 0 THEN
    -- Erase buffer content
ELSE
    -- continue
END IF;

Should I user htp.showpage or htp.init or something else ?

Thanks
Followup:
owa_util.showpage will dump it to dbms_output and "reset it"
December 22, 2003 Matt -- Thanks for the question regarding "Dynamic Table name in Cursor", version 9.0.1
You Asked
Tom,

I have a cursor defined statically currently which I now need to make dynamic
based on the fact that the table name will change.  I would like to know the
recommended approach for this type of scenario.

My code essentially will behave like the sample below but I will need to be able
to switch out "all_tables".

DECLARE

    CURSOR inrec_cursor IS
         SELECT * from all_tables
         where rownum < 10;
BEGIN

     FOR inrec IN inrec_cursor LOOP

       DBMS_OUTPUT.PUT_LINE('Tablename = ' || inrec.table_name);

     END LOOP;

END;
/

UPDATED INFO:  Yes all of the tables have the exact same structure.  We are
running a data transformation process with multiple streams.  The number of
streams can be dynamic so at run time we look at a list of available tables
(i.e. process_1, process_2, etc.) to determine which table to use.  We are now
implementing this dynamic table lookup which explains the need to be able to
replace the table name.

I apologize if you have answered this question previously.  I was unable to find
the answer.

Thanks,

Matt
and we said...
in 9iR1, this would look like this:

ops$tkyte@ORA9I> create table t1 as select owner, object_name, object_id,
created from all_objects where rownum < 500;

Table created.

ops$tkyte@ORA9I> create table t2 as select owner, object_name, object_id,
created from all_objects where rownum < 1000;

Table created.

ops$tkyte@ORA9I>
ops$tkyte@ORA9I>
ops$tkyte@ORA9I>
ops$tkyte@ORA9I> create or replace procedure process( p_tname in varchar2,
p_array_size in number default 100 )
  2  as
  3      l_cursor sys_refcursor;
  4      l_owner  dbms_sql.varchar2_table;
  5      l_object_name  dbms_sql.varchar2_table;
  6      l_object_id  dbms_sql.number_table;
  7      l_created  dbms_sql.date_table;
  8      l_processed_cnt number := 0;
  9  begin
 10      open l_cursor for 'select * from ' || p_tname;
 11      loop
 12          fetch l_cursor BULK COLLECT
 13           into l_owner, l_object_name, l_object_id, l_created
 14          LIMIT p_array_size;
 15
 16          for i in 1 .. l_owner.count
 17          loop
 18              l_processed_cnt := l_processed_cnt+1;
 19          end loop;
 20
 21           exit when l_cursor%notfound;
 22      end loop;
 23      close l_cursor;
 24
 25      dbms_output.put_line( 'processed ' || l_processed_cnt || ' records' );
 26  end;
 27  /

Procedure created.

ops$tkyte@ORA9I>
ops$tkyte@ORA9I> exec process( 't1' );
processed 499 records

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I> exec process( 't2' );
processed 999 records

In 9iR2, it would be simplified to this:
ops$tkyte@ORA920> create or replace procedure process( p_tname in varchar2,
p_array_size in number default 100 )
  2  as
  3      type array is table of t1%rowtype index by binary_integer;
  4      l_data   array;
  5      l_cursor sys_refcursor;
  6      l_processed_cnt number := 0;
  7  begin
  8      open l_cursor for select * from t1;
  9      loop
 10          fetch l_cursor BULK COLLECT into l_data limit p_array_size;
 11          for i in 1 .. l_data.count
 12          loop
 13              l_processed_cnt := l_processed_cnt+1;
 14          end loop;
 15          exit when l_cursor%notfound;
 16      end loop;
 17      close l_cursor;
 18      dbms_output.put_line( 'processed ' || l_processed_cnt || ' records' );
 19  end;
 20  /

Procedure created.

ops$tkyte@ORA920> exec process( 't1' );
processed 499 records

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec process( 't2' );
processed 499 records

PL/SQL procedure successfully completed.
Review & Followup

Rating: 5
Exactly what was needed August 12, 2003
Reviewer: Matt from Chicago, IL USA
Tom,

Thanks for your quick and concise answer.  This is exactly what I was looking
for and written in such a way as to make it instantly applicable.

Thanks again.
Rating: 3
Typo ? August 12, 2003
Reviewer: Jay from Largo, FL
Tom, I am a little confused for your 2nd example, as I am not familiar with 9i
at all. Your example hard code table t1, and the result also showed processed
499 instead of 999. Please advise.
Followup:
whoop.  change

 8      open l_cursor for select * from t1;

to

 8      open l_cursor for 'select * from '||p_tname;

ops$tkyte@ORA920> create or replace procedure process( p_tname in varchar2,
p_array_size in number default 100 )
  2  as
  3      type array is table of t1%rowtype index by binary_integer;
  4      l_data   array;
  5      l_cursor sys_refcursor;
  6      l_processed_cnt number := 0;
  7  begin
  8      open l_cursor for 'select * from '||p_tname;
  9      loop
 10          fetch l_cursor BULK COLLECT into l_data limit p_array_size;
 11          for i in 1 .. l_data.count
 12          loop
 13              l_processed_cnt := l_processed_cnt+1;
 14          end loop;
 15          exit when l_cursor%notfound;
 16      end loop;
 17      close l_cursor;
 18      dbms_output.put_line( 'processed ' || l_processed_cnt || ' records' );
 19  end;
 20  /

Procedure created.

ops$tkyte@ORA920> exec process( 't1' );
processed 499 records

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec process( 't2' );
processed 999 records

PL/SQL procedure successfully completed.
Rating: 4
DYNAMIC SQL August 12, 2003
Reviewer: Santhanam from India
Good example
Rating: 5
Good example for Nice feature August 13, 2003
Reviewer: Keyan from USA
Tom

Thanks for very nice example.
Rating: 3
Still not generic? August 14, 2003
Reviewer: Phil
Hi Tom,
Good example of 9.2 but there is still the reference in there to t1...

type array is table of t1%rowtype index by binary_integer
Followup:
so?  i used t1 as the template to cut down on the code.  I assumed that
naturally there would be AT LEAST one table there I could rely on.
Rating: 4
Nice But What If.... December 19, 2003
Reviewer: Jason from Lowell, AR USA
I need to return my cursor as an out parameter, in this example I cannot because
it has to be of an sql type (error: PLS-00457)inorder to open into a cursor, if
I create the needed types as db ojbects then I get the error "ORA-00932" when
performing the fetch.

I need to be able to bulk collect into a table type and pass back the table type
in the form of a cursor.  Any ideas?
Followup:
in which example specifically are you talking about here?

since none of them are about returning ref cursors?

hows about an example of what you are trying to accomplish -- we can work with
that.
Rating: 3
my example December 22, 2003
Reviewer: jason from Lowell, AR USA
--here's my example
--I'm having conflicts between local and db defined types..

CREATE OR REPLACE TYPE type_record_object AS OBJECT
( num NUMBER,
  dt  DATE,
  txt VARCHAR2(100));

CREATE TYPE type_table_ojbect AS TABLE OF type_record_object;
CREATE OR REPLACE PACKAGE a_cursor_test IS

  TYPE type_cursor IS REF CURSOR;

  TYPE type_private_record IS RECORD (num NUMBER,
                                      dt  DATE,
                                      txt VARCHAR2(100));

  TYPE type_private_table IS TABLE OF type_private_record;

  PROCEDURE my_test (p_out_cursor OUT type_cursor);

END;
/

CREATE OR REPLACE PACKAGE BODY a_cursor_test IS

  PROCEDURE my_test (p_out_cursor OUT type_cursor) IS
    ---local pkg defined types
    v_cursor  type_cursor;
    v_record  type_private_record;
    v_table   type_private_table := type_private_table();

    --sql types (db objects)
    v_record_object type_record_object;
    v_table_object  type_table_ojbect := type_table_ojbect();
    v_sql_txt VARCHAR2(200):= 'SELECT 1, sysdate, ''one'' from dual';

    CURSOR X IS SELECT 1 AS A, SYSDATE AS B, 'ONE' AS C FROM DUAL;

  BEGIN

    ---try with local types
    /*
    here the type has to be local to use with dynamic sql
    the problem is I can't open the curor with a local type
    */
    EXECUTE IMMEDIATE v_sql_txt BULK COLLECT INTO v_table; --works
    --next line will compile with error PLS-00642: Local collection types not
allowed in sql statement
    OPEN p_out_cursor FOR select * from table ( cast ( v_table as
type_table_ojbect ) );--doesn't work

    ---try with db defined types
    /*
    Here the open cursor works because I'm using the db type, but my dynamic
    sql does not work with the db type.
    */
    --next line produces (ORA-00932 inconsistent datatypes) on call to proc.
    EXECUTE IMMEDIATE v_sql_txt BULK COLLECT INTO v_table_object; --doesn't work

    OPEN p_out_cursor FOR select * from table ( cast ( V_TABLE_OBJECT as
type_table_ojbect ) ) ;--works

  END;
END;
/
Followup:
ops$tkyte@ORA9IR2> CREATE OR REPLACE PACKAGE a_cursor_test
  2  IS
  3    TYPE type_cursor IS REF CURSOR;
  4
  5    PROCEDURE my_test (p_out_cursor OUT type_cursor);
  6  END;
  7  /

Package created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> CREATE OR REPLACE PACKAGE BODY a_cursor_test IS
  2
  3  PROCEDURE my_test (p_out_cursor OUT type_cursor)
  4  IS
  5      v_table_object  type_table_ojbect;
  6      v_sql_txt VARCHAR2(200):= 'SELECT type_record_object( 1, sysdate,
''one'' ) from dual';
  7  BEGIN
  8      EXECUTE IMMEDIATE
  9         'select cast( multiset( ' || v_sql_txt || ') as type_table_ojbect )
from dual'
 10      INTO v_table_object;
 11
 12      OPEN p_out_cursor
 13      FOR select *
 14            from table ( cast ( V_TABLE_OBJECT as type_table_ojbect ) );
 15    END;
 16  END;
 17  /

Package body created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> variable x refcursor
ops$tkyte@ORA9IR2> exec a_cursor_test.my_test( :x )

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> print x

       NUM DT        TXT
---------- --------- ------------------------------
         1 22-DEC-03 one
Rating: 4
Work great (as usual).. December 22, 2003
Reviewer: Jason from Lowell, AR USA
Thanks a million.
December 22, 2003 partha -- Thanks for the question regarding "Convert varchar2 to Date format", version 8.1.6
You Asked
how should i get following format by combining 2 varchar2 columns named
test1,test2.

in the databse these fields data is stored in this following format

test1 column format - 07/23/2001
test2 column format - 02:20:55

i want's to display in the following format

'YYYY-MM-DDHH24.MI.SS'

i tried several ways of doing with to_date function still doesn't workout.

How can i use same scenario in a stored procedure, if i want's to insert above
same format by using above two varchar2 datatype columns into date datatype
column called test3.

Thanks in advance

Partha
and we said...
ops$tkyte@ORA817.US.ORACLE.COM> drop table t;

Table dropped.

ops$tkyte@ORA817.US.ORACLE.COM> create table t ( test1 varchar2(10), test2
varchar2(10) );

Table created.

ops$tkyte@ORA817.US.ORACLE.COM> insert into t values ( '07/23/2001', '02:20:55'
);

1 row created.

  1  select to_char(
  2           to_date( test1 || test2, 'mm/dd/yyyyHH24:mi:ss' ),
  3              'yyyy-mm-ddhh24.mi.ss' )
  4* from t
ops$tkyte@ORA817.US.ORACLE.COM> /

TO_CHAR(TO_DATE(TE
------------------
2001-07-2302.20.55

Use to_date( test1 || test2, 'mm/dd/yyyyHH24:mi:ss' ) on the insert in PLSQL
Review & Followup

Rating: 5
Convert Varchar2 datatype to Date datatype July 26, 2001
Reviewer: Partha from USA
Good technique to implement conversion of varchar2 to date
Rating: 2
Convert Varchar2 datatype to Date datatype August 01, 2001
Reviewer: devadathan@hotmail.com OCP DBA 7.3 & 8.x from INDIA
You can do this conversion in little more simpler way like the following :

Create Table DTCONV(DT Varchar(25))
/

Insert Into DTCONV Select To_Char(SYSDATE,'DD-MON-YYYY HH:MI:SS') From Dual
/

Select To_Char(To_Date(DT,'DD-MM-YY HH:MI:SS'),'DD-MM-YY HH:MI:SS') from DTCONV
/

Try this and see...
Rating: 5
re: devadathan@hotmail.com OCP DBA 7.3 & 8.x August 02, 2001
Reviewer: A reader
Maybe you should re-read the question...  The question concerns a table that has
the date component in one column (as varchar) and the time component in another
column (also as varchar).  The original question was in regard to combining the
two varchar columns and inserting the result into a third column of type date.
All your example does is get the sysdate, convert it into char, insert it into a
single column of type varchar, select it back out again, and convert it to a
date.  Using to_date with the concatenation operator is the most obvious
solution to the original question.
Rating: 4
Very Helpfule March 27, 2002
Reviewer: Kimathi from El Segundo, CA, USA
You did it again Tom.
Rating: 4
Drew April 10, 2002
Reviewer: A reader from Lansdale US
Hi Tom,
I need to know more about the date conversion in Oracle.
We have the following query:
select * from t_torder where to_char(order_date,'DAY')='SUNDAY';
This query does not return any data even though there are data there.  However,
if we use 'WEDNESDAY', the query works fine.  The DAY is right padded.  If we do

where to_char(order_date,'DAY')='SUNDAY   ';
or
where trim(to_char(order_date,'DAY'))='SUNDAY';
The query works fine.  The same thing with MONTH, only the longest month works.
I was asked why and I could not answer clear.  Can you please give some
explainations?  How the date is saved and coverted?
Thanks in advance.
Followup:
It has nothing to do with how the date data is saved (it is saved in a 7 byte
binary format with century, year, month, day, hour, minute, second and in 9i
upto 11 bytes with fractional seconds.

It has EVERYTHING to do with how the format mask you used formated the data.

Consider:

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

'"'||TO_CHA '"'||TO_CHA DT
----------- ----------- ---------
"THURSDAY " "THURSDAY"  11-APR-02
"FRIDAY   " "FRIDAY"    12-APR-02
"SATURDAY " "SATURDAY"  13-APR-02
"SUNDAY   " "SUNDAY"    14-APR-02
"MONDAY   " "MONDAY"    15-APR-02
"TUESDAY  " "TUESDAY"   16-APR-02
"WEDNESDAY" "WEDNESDAY" 17-APR-02

7 rows selected.

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

WEDNESDAY works cause (in our language), it is the longest.  Now, a user in
Germany might be confused by your issue:

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set nls_language=German;

Session altered.

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

'"'||TO_CHAR '"'||TO_CHAR DT
------------ ------------ ---------
"DONNERSTAG" "DONNERSTAG" 11-APR-02
"FREITAG   " "FREITAG"    12-APR-02
"SAMSTAG   " "SAMSTAG"    13-APR-02
"SONNTAG   " "SONNTAG"    14-APR-02
"MONTAG    " "MONTAG"     15-APR-02
"DIENSTAG  " "DIENSTAG"   16-APR-02
"MITTWOCH  " "MITTWOCH"   17-APR-02

7 rows selected

Since Mittwoch is not as long as Wednesday....

If you use the fm (format modifier) mask -- you'll get what you want.  It'll
blank trim things.  Using the '"' || ... || '"' trick is something I frequently
do to see whats up with the data (dump(to_char(dt,'DAY')) would be useful as
well)
Rating: 5
to_date exceptions April 10, 2002
Reviewer: Johan Snyman from JHB SA
Tom,

I have to convert date and time varchar2 columns to an Oracle date field while
transferring data from one table to the next. The problem is that the data in
the varchar2 columns may not be valid dates and times.

I currently solve the problem by writing a PL/SQL function:

FUNCTION checkcalldatetime
(
  v_calldate IN VARCHAR2,
  v_calltime IN VARCHAR2,
  v_datetimefmt IN VARCHAR2
)
  RETURN NUMBER
IS
  v_dummydate            DATE;
BEGIN
  v_dummydate := TO_DATE(v_calldate||v_calltime,v_datetimefmt);
  RETURN (0);
EXCEPTION
  WHEN OTHERS THEN
  RETURN (1);
END;

and then calling this function from the insert into ... select ... statement to
retrieve only rows with valid date and time columns.

The problem is that this can be slow for large data volumes (probably due to
context switches between SQL engine and PL/SQL engine). What would you suggest
to speed up this process ?
Followup:
is the format fixed (always the same)?  if so, share that with me along with
what the valid data would look like -- database version appreciated as well.
with CASE we can do some tricky things.
Rating: 5
to_date exceptions April 11, 2002
Reviewer: Johan Snyman from JHB SA
Tom,

The date and time format is always the same, i.e.

Calldate format = 'yyyymmdd'
Calltime format = 'hh24:mi:ss'

Passed into the function as 'yyyymmddhh24:mi:ss'

Typical valid data would be:
Calldate : 20020411
Calltime : 14:22:00

We have Oracle 8.1.6.2 Enterprise Edition
Followup:
Here is an example then:

scott@ORA817DEV.US.ORACLE.COM> create table t ( calldate number, calltime
varchar2(8) );
Table created.

scott@ORA817DEV.US.ORACLE.COM> exec gen_data( 'T', 500 );
PL/SQL procedure successfully completed.

gen_data is just something I have to generate random data -- that generated
500 "bad" rows for us, rows that will not be dates (almost certainly)

scott@ORA817DEV.US.ORACLE.COM> insert into t select to_char(created,'yyyymmdd'),
to_char(created,'hh24:mi:ss' )
  2   from all_objects;
14773 rows created.

and that created 14,773 "good" rows for us

scott@ORA817DEV.US.ORACLE.COM> create or replace function my_to_date( p_num in
number, p_str in varchar2 ) return date
  2  as
  3  begin
  4      return to_date( p_num || p_str, 'yyyymmddhh24:mi:ss' );
  5  exception
  6      when others then
  7          return null;
  8  end;
  9  /
Function created.

there is the PLSQL way to do this..

scott@ORA817DEV.US.ORACLE.COM> set timing on

scott@ORA817DEV.US.ORACLE.COM> drop table new_t;
Table dropped.
Elapsed: 00:00:00.35

scott@ORA817DEV.US.ORACLE.COM> create table new_t ( x date );
Table created.
Elapsed: 00:00:00.07

scott@ORA817DEV.US.ORACLE.COM> alter session set sql_trace=true;
Session altered.

scott@ORA817DEV.US.ORACLE.COM> insert into new_t
  2  select dt from (
  3  select case  when substr( calltime, 1, 2 ) between '00' and '23'
  4                    AND
  5                    substr( calltime, 4, 2 ) between '00' and '59'
  6                    AND
  7                    substr( calltime, 7, 2 ) between '00' and '59'
  8                    AND
  9                    substr( calltime, 3, 1) = ':'
 10                    AND
 11                    substr( calltime, 6, 1) = ':'
 12                    AND
 13                    substr( calldate, 1, 4 ) between '1970' and '2020'
 14                    AND
 15                    substr( calldate, 5, 2 ) between '01' and '12'
 16                    then
 17                      case when substr( calldate, 7, 2 ) between '01' and
 18              to_char(last_day(to_date(substr(calldate,1,6),'yyyymm')),'dd')
 19                        then to_date(calldate||calltime,'yyyymmddhh24:mi:ss')
 20                        else null
 21                      end
 22                else null
 23          end dt
 24   from t
 25  )
 26  where dt is not null
 27  /

14773 rows created.

Elapsed: 00:00:02.28

That shows how we can use SQL to "edit" the data -- verify it falls within our
limits.  I did a nested case to ensure that the YYYY and MM part are validated
before we even attemp to use to_date on them...

I used an inline view to make the SQL easier..

Now lets see that with PLSQL

scott@ORA817DEV.US.ORACLE.COM> drop table new_t;
Table dropped.
Elapsed: 00:00:00.35

scott@ORA817DEV.US.ORACLE.COM> create table new_t ( x date );
Table created.
Elapsed: 00:00:00.06

scott@ORA817DEV.US.ORACLE.COM> insert into new_t
  2  select dt from
  3  (
  4  select my_to_date( calldate, calltime )  dt from t
  5  )
  6  where dt is not null
  7  /

14773 rows created.

Elapsed: 00:00:06.18
scott@ORA817DEV.US.ORACLE.COM>

same number of rows, 3x the runtime.  Easier to code but runs slower

TKPROF shows the same timings.  You might even be able to speed up the process
using something like:

scott@ORA817DEV.US.ORACLE.COM> insert into new_t
  2  select dt from (
  3  select case
        when calltime like '__:__:__' AND calldate between 19700100 and 20201231
  4               then to_date( calldate || calltime, 'yyyymmddhh24:mi:ss' )
  5                           else null
  6          end dt
  7   from t
  8  )
  9  where dt is not null
 10  /

14773 rows created.

Elapsed: 00:00:00.77

depending on "how dirty" your data is.  If you can assume that if the colons are
in the right place --  you have the time and if the date is between those
numbers, you have the date -- you can short circut alot of the evaluation.
Rating: 5
to_date exceptions April 11, 2002
Reviewer: Johan Snyman from JHB SA
Tom,

Thanks for a great answer provided in record time !
Rating: 3
But how about this one? April 26, 2003
Reviewer: DeeeBeee Crazeee from India
Hi Tom,

I have a situation wherein I have a column that can hold valid dates as well as
junk (including null). The dates are not in a standard format. It could be
either mm/dd/yy or mm/dd or yyyy. Ex:

Col_val      Shld_return
------------------------
2003       -->  01/01/2003
Junk       -->  NULL
01/12/2002 -->  01/12/2002
NULL       -->  NULL
01/12      -->  01/12/2002

Can this be done using SQL? Is there a function to check for valid dates without
providing the format?

thanks in advance
Followup:
if you can have mm/dd or dd/mm you are really hosed.  01/02 is what date.

You have garbage. garbage in, garbage out.

you can write a PLSQL to try many different date formats, catching the exception
when a string fails -- returning a date if it converted, NULL otherwise.

but you have garbage.
Rating: 4
December 22, 2003
Reviewer: huss from kuwait
hi tom.
i was search in how make numbers spells out
i.e making a function that take
 '125' => to retrive =>'One Hundred Twenty-Five'

and i found some nice select that says :
SELECT     to_char(to_date(125,'J'),'JSP') FROM DUAL ;
and it work great
but
1- i want the output be in arabic language
   i try 'NLS_DATE_LANGUAGE = EGYPTIAN' but no use
2- i want to understand what 'J' mean in to_date function
   and what 'JSP' mean in to_char function
Followup:
jsp doesn't do other languages.

j = julian
sp = spell

see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1407603857650#4232815086519
Rating: 5
December 22, 2003
Reviewer: huss from kuwait
Thankx tom ,
i found the answer in
<a href='
http://asktom.oracle.com/pls/ask/f?p=4950:8:5114008654502725066::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1407603857650,'>

http://asktom.oracle.com/pls/ask/f?p=4950:8:5114008654502725066::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1407603857650,
</a>
Rating: 5
December 22, 2003
Reviewer: huss from kuwait
OOoops !
sorry
i still have the quesion of how make number spell out in Arabic ?!
December 22, 2003 ls -- Thanks for the question regarding "union question", version 8.1.7
You Asked
Hi Tom

I have following query with follwoing result set

select to_char(null) as username, max(length(username)) longi
  from dba_users
  where username in ('ARPA_OWN', 'BSCS_OWN',
  'CAU', 'CM','CMGED', 'CMSAPGED', 'METADATOS_OWN', 'METRICAS',
  'OPSC','PERFSTAT', 'RECHAZOS')
union
select username, to_number(null) as longi
  from dba_users
  where username in ('ARPA_OWN', 'BSCS_OWN',
 'CAU', 'CM','CMGED', 'CMSAPGED', 'METADATOS_OWN', 'METRICAS',
 'OPSC','PERFSTAT', 'RECHAZOS');

USERNAME                            LONGI
------------------------------ ----------
ARPA_OWN
BSCS_OWN
CAU
CM
CMGED
CMSAPGED
METADATOS_OWN
METRICAS
OPSC
PERFSTAT
RECHAZOS
                                       13

this return 12 rows, one more I need

I would like to know how could I put that 13 next to username such as

USERNAME                            LONGI
------------------------------ ----------
ARPA_OWN                               13
BSCS_OWN                               13

etc

also can analytic functions used in cursors?

like

declare
cursor cursor_username is select username, max(length(username)) over ()
longitud
  from dba_users
  where username in ('ARPA_OWN', 'BSCS_OWN',
 'CAU', 'CM','CMGED', 'CMSAPGED', 'METADATOS_OWN', 'METRICAS',
 'OPSC','PERFSTAT', 'RECHAZOS');
l_analyzed date;

etc etc

I get an error saying it expects "," instead of "(" after over in the analytic
function

cheers
and we said...
Either of:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select username,
  2         count(*) over ( order by username
  3                         rows between unbounded preceding
  4                                  and unbounded following ) cnt
  5    from dba_users
  6   where username in ( 'A', 'B', 'C' )
  7  /

USERNAME                              CNT
------------------------------ ----------
A                                       3
B                                       3
C                                       3

ops$tkyte@ORA817DEV.US.ORACLE.COM> select username, cnt
  2    from dba_users, ( select count(*) cnt
  3                        from dba_users
  4                       where username in ( 'A', 'B', 'C' )
  5                    )
  6   where username in ( 'A', 'B', 'C' )
  7  /

USERNAME                              CNT
------------------------------ ----------
C                                       3
B                                       3
A                                       3

will do that.  As for the error, see:
http://asktom.oracle.com/pls/ask/f?p=4950:9:382842::NO::F4950_P9_DISPLAYID:186812348071
it is the same issue with the same workaround.
Review & Followup

Rating: 4
the first part I need to max lengthnot count January 08, 2002
Reviewer: ls from spain
Hi Tom

In the first question I was tryinbg to get the longest username string but the
SQL you used is to count the number of users...? May you show me how would you
do it with union?

cheers
Followup:
find -- it works for any aggregate:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select username,
  2         max(length(username)) over ( order by username
  3                         rows between unbounded preceding
  4                                  and unbounded following ) cnt
  5    from dba_users
  6   where username in ( 'A', 'B', 'C' )
  7  /

USERNAME                              CNT
------------------------------ ----------
A                                       1
B                                       1
C                                       1

ops$tkyte@ORA817DEV.US.ORACLE.COM> select username, cnt
  2    from dba_users, ( select max(length(username)) cnt
  3                        from dba_users
  4                       where username in ( 'A', 'B', 'C' )
  5                    )
  6   where username in ( 'A', 'B', 'C' )
  7  /

USERNAME                              CNT
------------------------------ ----------
C                                       1
B                                       1
A                                       1

I cannot show you how to do this with a UNION as a UNION will not be able to
achieve your desired result -- having the longest length associated with each
row.
Rating: 5
sorry I got it January 08, 2002
Reviewer: A reader
I got it

select a.username, b.longi
from dba_users a,
(select max(length(username)) longi
 from dba_users
 where username in ('ARPA_OWN', 'BSCS_OWN',
 'CAU', 'CM','CMGED', 'CMSAPGED', 'LSC', 'METADATOS_OWN',
'METRICAS','OPSC','PERFSTAT', 'RECHAZOS')) b
where username in ('ARPA_OWN', 'BSCS_OWN',
  'CAU', 'CM','CMGED', 'CMSAPGED', 'LSC', 'METADATOS_OWN', 'METRICAS',
'OPSC','PERFSTAT', 'RECHAZOS')
/

thanx
Rating: 3
use of Union and its alternate July 22, 2002
Reviewer: PN
Hi Tom
Thanks for previous helps.
We are using query like below for user selection in D2k reports run time
parameter ,We are using union to combine "All Plants" in the list,then we are
using Decode in main query to filter the data based on user selection.
As tables I_codes, I_names are growing table so we can not fix the values of
code for selection .But due to Union its take a long time in loading the report
itself and also return High Optimizer cost.

SELECT 'All Codes' , NULL FROM DUAL
UNION
select distinct(codes),NAMEs
from I_codes, I_names
WHERE I_codes.codes = I_names .CODEs

out put like=

ALLCODES NULL
All Codes
FY     F PLANT
PY     P PLANT
RW     R PLANT
SP     S PLANT
.....
....

Is any way to recreate this type of query to serve the purpose with enough
speed.
Thanks
PN
Followup:
Well, you are using two distincts here.  You should either use UNION ALL and
keep the distinct (if it is in fact NEEDED -- is it?) or use UNION but drop the
distinct.

consider:

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x varchar2(2), y
varchar2(20) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1, 'hello' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1, 'hello' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1, 'hello' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 1, 'hello' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 2, 'goodbye' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select 'All Codes', NULL from dual
  2  union
  3  select x, y from t;

'ALLCODES NULL
--------- --------------------
1         hello
2         goodbye
All Codes

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select 'All Codes', NULL from dual
  2  union ALL
  3  select distinct x, y from t;

'ALLCODES NULL
--------- --------------------
All Codes
1         hello
2         goodbye

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

you are doing 2x the work.
Rating: 4
Doesn't seem to be doing 2x work July 23, 2002
Reviewer: Logan Palanisamy from Santa Clara, CA 95051
Tom,

Thanks for all the wonderful job you are doing in constantly educating people
like me. I am learning something new everyday through this forum.

As given in the second query below, the UNION and DISTINCT combination doesn't
seem to be doing twice the work. It does only one sort, the outermost sort.
There is no separate sort for the DISTINCT clause. This is on 8.1.6.

The optimizer seems to be intelligent. It does the same work with or without the
DISTINCT clause.

SQL> select 'All Codes', NULL from dual
  2  union
  3  select x, y from t;

'ALLCODES NULL
--------- --------------------
1         hello
2         goodbye
All Codes

Elapsed: 00:00:00.01

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=8 Card=83 Bytes=1230
          )

   1    0   SORT (UNIQUE) (Cost=8 Card=83 Bytes=1230)
   2    1     UNION-ALL
   3    2       TABLE ACCESS (FULL) OF 'DUAL' (Cost=2 Card=1)
   4    2       TABLE ACCESS (FULL) OF 'T' (Cost=2 Card=82 Bytes=1230)

SQL>
SQL> select 'All Codes', NULL from dual
  2  union
  3  select distinct x, y from t;

'ALLCODES NULL
--------- --------------------
1         hello
2         goodbye
All Codes

Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=8 Card=83 Bytes=1230
          )

   1    0   SORT (UNIQUE) (Cost=8 Card=83 Bytes=1230)
   2    1     UNION-ALL
   3    2       TABLE ACCESS (FULL) OF 'DUAL' (Cost=2 Card=1)
   4    2       TABLE ACCESS (FULL) OF 'T' (Cost=2 Card=82 Bytes=1230)

SQL>
SQL> select 'All Codes', NULL from dual
  2  union ALL
  3  select distinct x, y from t;

'ALLCODES NULL
--------- --------------------
All Codes
1         hello
2         goodbye

Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=83 Bytes=1230
          )

   1    0   UNION-ALL
   2    1     TABLE ACCESS (FULL) OF 'DUAL' (Cost=2 Card=1)
   3    1     SORT (UNIQUE) (Cost=4 Card=82 Bytes=1230)
   4    3       TABLE ACCESS (FULL) OF 'T' (Cost=2 Card=82 Bytes=1230)

SQL> exit
Disconnected from Oracle8i Enterprise Edition Release 8.1.6.3.0 - Production
With the Partitioning option
JServer Release 8.1.6.3.0 - Production
Followup:
Look at that -- you are correct.  Guess you'll just have to figure out how to
speed up that second query.

If the second query doesn't produce dups -- remove the distinct and change the
union to union all

If it does produce dups - question "why, should it?"  Seems to me that a CODES
table shouldn't have duplicate codes in it.
Rating: 4
Got it Thanks July 24, 2002
Reviewer: PN
Rating: 5
what is rows between unbounded preceding and unbounded followin September 05, 2002
Reviewer: A reader
hi

what does

rows between unbounded preceding
and unbounded following

do?

SQL> select username,
  2             max(length(username)) over ( order by username
  3                             rows between unbounded preceding
  4                                      and unbounded following ) cnt
  5        from dba_users
  6  where rownum <= 11;

USERNAME                              CNT
------------------------------ ----------
DBSNMP                                 10
HR                                     10
LSC                                    10
OEM                                    10
ORDPLUGINS                             10
ORDSYS                                 10
OUTLN                                  10
SYS                                    10
SYSTEM                                 10
TEST                                   10
WMSYS                                  10

SQL> select username,
  2             max(length(username)) over ( order by username) cnt
  3        from dba_users
  4  where rownum <= 11;

USERNAME                              CNT
------------------------------ ----------
DBSNMP                                  6
HR                                      6
LSC                                     6
OEM                                     6
ORDPLUGINS                             10
ORDSYS                                 10
OUTLN                                  10
SYS                                    10
SYSTEM                                 10
TEST                                   10
WMSYS                                  10

I get diff result sets if I dont use that clause
Followup:
Well, if you have my book "Expert one on one Oracle" -- i go into great depth on
this.

Else, you'll want to read
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76994/analysis.htm#11237
It is a complex subject -- you need to understand the basics of how the
partitioning works, what windows are in effect and what rows are counted as part
of the window.

(actually -- I think between unbounded preceding and unbounded following is
rather clear.  It is working on ALL of the rows in the current partition and
since your partition is simply the entire result set -- it is finding the max
length out of ALL of the rows in the result set.

The second one is sorting the partition by USERNAME and then when using the MAX,
only looks at rows that are less then or equal to the current username.

That is why the first 4 rows have 6 -- DBSNMP is 6 characters long, HR = 2,
LSC/OEM = 3.  It is not until we get to ORDPLUGINS that we hit 10 and that
overrides the 6.  So the over (order by username) window is simply all of the
rows with the username of the current row on back in your case.

See also
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:750625651018
in particular the first review
Rating: 5
Very helpful, but the link didn't work. September 05, 2002
Reviewer: Chuck Jolley from Oklahoma City, OK USA
I found it at:
http://asktom.oracle.com/pls/ask/f?p=4950:8:1252835::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:186812348071
Rating: 3
order of applying analitic function September 05, 2002
Reviewer: A reader
Hi Tom

in

SQL> select username,
  2             max(length(username)) over ( order by username) cnt
  3        from dba_users
  4  where rownum <= 11;

whatñs the order of max(length(username)) over ( order by username) cnt applied?

I understand that analytic functions are applied after we obtain the result set,
in this case how is length() and max() applied? In what order?
Followup:
length first - -then max -- just like any function call:

  f1( f2( x ) )

would be -- max is a function, its input is length, its input is username.  so
the length of username is passed to max
Rating: 5
still doubts September 06, 2002
Reviewer: A reader
I still dont have this very clear with second sorting

SQL> select username, length(username),
  2         max(length(username)) over ( order by username) cnt
  3    from dba_users
  4   where rownum <= 11;

USERNAME                       LENGTH(USERNAME)        CNT
------------------------------ ---------------- ----------
DBSNMP                                        6          6
HR                                            2          6
LSC                                           3          6
OEM                                           3          6
ORDPLUGINS                                   10         10
ORDSYS                                        6         10
OUTLN                                         5         10
SYS                                           3         10
SYSTEM                                        6         10
TEST                                          4         10
WMSYS                                         5         10

How does Oracle know that for user HR CNT should be 6 and not 10?
Followup:
Ok, I'll do it step by step....

Oracle builds a set of USERNAME, LENGTH(USERNAME).  So, we have the rows above
(but not the CNT column yet).

Oracle then partitions that result set by the PARTITION BY elements (which we
don't have -- so the partition is the entire set in this case).

It then orders the rows in that set by the ORDER BY (so we have:

DBSNMP     6
HR         2
LSC        3
OEM        3
ORDPLUGINS 10
.....
and so on now).

It applies the RANGE for analytic function.  Since NO RANGE/ROWS were specified,
the default range of UNBOUNDED PRECEDING is used.

Now we start processing the rows.  Starting with the first row, DBSNMP -- we
look at its value and all of the rows that have a username LESS THEN or EQUAL to
it in the partition.  So far the max(length(username)) over ( order by username
) is 6.

We move onto the second row, HR.  Looking at all of the usernames <= HR, we see
the MAX(length(username)) is 6.  We looked at the lengths of HR and DBSNMP.

We move onto the third row.  Looking at length(LSC), length(HR), length(DBSNMP)
we find the max is 6 still.

We move onto OEM...   still 6 (looking at rows unbounded preceding which are now
DBSNMP, HR, LSC and OEM).

Now, we hit ORDPLUGINS.  The length is 10.  That is the max length username for
all rows <= ORDPLUGINS -- hence the length is 10

and so on.....
Rating: 5
understood now September 06, 2002
Reviewer: A reader
excellent answer tom!

cheers
Rating: 5
Why union in delete statement so slow or does not work? October 28, 2002
Reviewer: A Reader from USA
We are trying to delete the primary keys that are not refrerenced by any child
tables.  There are eight child tables that references the primary key.  All
together, there are about 1 million rows need to be deleted in the pk table.  As
long as no child records in any of the eight child tables, the records in the PK
table should be deleted.  I use the following SQL to do the work.

delete from pk_table where pk_col not in (
  select fk_col from fk_table1
  union
  select fk_col from fk_table2
  union
  select fk_col from fk_table3
  union
  select fk_col from fk_table4
  union
  select fk_col from fk_table5
  union
  select fk_col from fk_table6
  union
  select fk_col from fk_table7
  union
  select fk_col from fk_table8
  );

When I run this query, it tables forever to come back.  When I run the select
statement that union the eight tables, it takes about 70 seconds to come back.
I thought it was because of the "NOT IN" which does not use indexes.  I change
the SQL to the following:

delete from pk_table where pk_col in (
  select pk_col from pk_table
  minus
  (select fk_col from fk_table1
  union
  select fk_col from fk_table2
  union
  select fk_col from fk_table3
  union
  select fk_col from fk_table4
  union
  select fk_col from fk_table5
  union
  select fk_col from fk_table6
  union
  select fk_col from fk_table7
  union
  select fk_col from fk_table8
  ));

Again, the select union query come back in about 70 seconds and the delete takes
forever.
Tom, why the deletion takes so long?  Is that the right way to do the work?  Do
you have a better way to do the similar job?  Thank you for your help in
advance.
Followup:
because deleting 1 million records will take an extremely considerable amount of
time -- especially if there are lots of indexes to be maintained.

No matter how you slice it -- a delete of 1,000,000 records is something that
will take "patience".

I would use the IN instead of the NOT IN -- unless all of the fkeys are NOT NULL
*and* you are using the CBO.

You may be better off doing this:

create table KEEP as select * from pk_table
where pk_col in ( select fk_col from fk_table1
                  UNION ...... );

run my cons.sql script:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:863253291155
to capture the fkeys and then drop them

truncate the pk_table
insert /*+ append */ into pk_table select * from temp;

re-implement the fkeys using the script generated by cons.sql.
Rating: 4
May 31, 2003
Reviewer: srinivasa rao bachina from banalore
Hi tom i am having one doubt while using union in the query
Ex: select *...
     where cut_Of_date < sysdate(format 'ddmmyyyyhh24miss')
    union
     select *...
     where cut_Of_date < sysdate(format 'ddmmyyyyhh24miss')
    union
     select *...
     where cut_Of_date < sysdate(format 'ddmmyyyyhh24miss')
if i execute the query,it will execute the 1 statement then 2 statement and last
3 statement OR all togther.
if it executes statement wise
  i will get the rows with diff cut of dates
pls clarify me
Followup:
no idea what sysdate(format 'ddmmyyyyhh24miss') but hopefully you are not
comparing strings instead of dates -- especially a string that doesn't sort
right (it would have to be yyyymmddhh24miss)

A
union
B
union
C

is the same as

DISTINCT( A+B+C )

it will run all three queries, put them together and then distinct the result.

A
union ALL
B
union ALL
C

is the same as

(A+B+C)

it'll just run the three queries in sucession and give you the results,
including duplicates.
Rating: 5
Distinct & Order by October 27, 2003
Reviewer: A reader
SQL> ed
Wrote file afiedt.buf

  1  select distinct ename
  2  from emp
  3* order by empno
SQL> /
order by empno
         *
ERROR at line 3:
ORA-01791: not a SELECTed expression

why is it mandatory that while doing a disitnct select, the order by columns
must come from the select list of columns only. Please explain.
Followup:
ok, say the data is this:

EMPNO    ENAME
------   --------
1        X
2        Y
3        X
4        Y

tell me, should X come before or after Y and how come?

you distincted out EMPNO -- it is "gone".  we (no one) can sort by something
that doesn't exist anymore.
Rating: 3
October 27, 2003
Reviewer: A reader
In the given example ename is distincted out and not empno...
Followup:
no, EMPNO was "distincted out of the result set"

EMPNO was "made to disappear"

ENAME was preserved, EMPNO "poofed", "gone", "distincted out", "removed"
Rating: 5
To "A Reader" October 28, 2003
Reviewer: Sai from Mountain View, CA.
Sql, for that matter ANSI SQL, was designed to throw out error on syntax related
errors during compile time, not during runtime. It was not designed to work
successfully with specific set of data but not with other set of data.
Rating: 3
I want to know how UNION function affects the optimizer December 17, 2003
Reviewer: MK from Dover, DE USA
Hi Tom,
Your reviews are excellent.
I have a query (uses EXISTS), which runs very slow. If I use IN, it makes it
faster (as it is using Index now). Now, if I use a dummy row and UNION it with
EXISTS query, It runs fast and makes use of Index (reducing physical reads and
consistent gets). However, with UNION ALL, it again runs slow ( and does FTS).
Can you please expalin this?

Query 1:
========

select to_char(SVCPRV_ID) from SERVICE_PROVIDER
where exists
    ( select 'X'   FROM "CONTRACT"
   WHERE CONTRACT.SRVC_PRVDR_ID  = SERVICE_PROVIDER.SVCPRV_ID  and
     "CONTRACT"."CLOSEOUT_DATE" is null    AND
     "CONTRACT"."CONSTR_GROUP_CODE" is not null   AND
         ("CONTRACT"."CONSTRUCTION_JOB_FLAG" <> 'N' OR
         "CONTRACT"."CONSTRUCTION_JOB_FLAG" is null )
      ) ;

46 rows selected.

Elapsed: 00:04:251.18

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=239 Bytes=1434)
   1    0   FILTER
   2    1     INDEX (FAST FULL SCAN) OF 'PK_SVC_PROVIDER' (UNIQUE) (Cost=2
Card=239 Bytes=
          1434)

   3    1     TABLE ACCESS (FULL) OF 'CONTRACT' (Cost=26 Card=3 Bytes=36)

Statistics
----------------------------------------------------------
          0  recursive calls
      19124  db block gets
    1234163  consistent gets
    1171457  physical reads
          0  redo size
        990  bytes sent via SQL*Net to client
        518  bytes received via SQL*Net from client
          5  SQL*Net roundtrips to/from client
          4  sorts (memory)
          0  sorts (disk)
         46  rows processed

Query 2:
========

select '"SVCPRV_ID"' from dual
union
select to_char(SVCPRV_ID) from SERVICE_PROVIDER
where exists
    ( select 'X'   FROM "CONTRACT"
   WHERE CONTRACT.SRVC_PRVDR_ID  = SERVICE_PROVIDER.SVCPRV_ID  and
     "CONTRACT"."CLOSEOUT_DATE" is null    AND
     "CONTRACT"."CONSTR_GROUP_CODE" is not null   AND
         ("CONTRACT"."CONSTRUCTION_JOB_FLAG" <> 'N' OR
         "CONTRACT"."CONSTRUCTION_JOB_FLAG" is null )
      ) ;

47 rows selected.

Elapsed: 00:00:02.13

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=32 Card=587 Bytes=9090)
   1    0   SORT (UNIQUE) (Cost=32 Card=587 Bytes=9090)
   2    1     UNION-ALL
   3    2       TABLE ACCESS (FULL) OF 'DUAL' (Cost=1 Card=82)
   4    2       NESTED LOOPS (Cost=26 Card=505 Bytes=9090)
   5    4         TABLE ACCESS (FULL) OF 'CONTRACT' (Cost=26 Card=505
Bytes=6060)
   6    4         INDEX (UNIQUE SCAN) OF 'PK_SVC_PROVIDER' (UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          8  db block gets
       1801  consistent gets
        262  physical reads
          0  redo size
       1197  bytes sent via SQL*Net to client
        518  bytes received via SQL*Net from client
          5  SQL*Net roundtrips to/from client
          8  sorts (memory)
          0  sorts (disk)
         47  rows processed

Query 3:
========

select '"SVCPRV_ID"' from dual
union all
select to_char(SVCPRV_ID) from SERVICE_PROVIDER
where exists
    ( select 'X'   FROM "CONTRACT"
   WHERE CONTRACT.SRVC_PRVDR_ID  = SERVICE_PROVIDER.SVCPRV_ID  and
     "CONTRACT"."CLOSEOUT_DATE" is null    AND
     "CONTRACT"."CONSTR_GROUP_CODE" is not null   AND
         ("CONTRACT"."CONSTRUCTION_JOB_FLAG" <> 'N' OR
         "CONTRACT"."CONSTRUCTION_JOB_FLAG" is null )
      ) ;

47 rows selected.

Elapsed: 00:04:251.87

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=321 Bytes=1434)
   1    0   UNION-ALL
   2    1     TABLE ACCESS (FULL) OF 'DUAL' (Cost=1 Card=82)
   3    1     FILTER
   4    3       INDEX (FAST FULL SCAN) OF 'PK_SVC_PROVIDER' (UNIQUE) (Cost=2
Card=239 Byte
          s=1434)

   5    3       TABLE ACCESS (FULL) OF 'CONTRACT' (Cost=26 Card=3 Bytes=36)

Statistics
----------------------------------------------------------
          0  recursive calls
      19128  db block gets
    1234163  consistent gets
    1155465  physical reads
          0  redo size
       1197  bytes sent via SQL*Net to client
        518  bytes received via SQL*Net from client
          5  SQL*Net roundtrips to/from client
          5  sorts (memory)
          0  sorts (disk)
         47  rows processed
Followup:
are the statistics anywhere near close to reality on these tables?  to get this
many physical reads:

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=239 Bytes=1434)
   1    0   FILTER

   2    1     INDEX (FAST FULL SCAN) OF 'PK_SVC_PROVIDER' (UNIQUE) (Cost=2
Card=239 Bytes=
          1434)

   3    1     TABLE ACCESS (FULL) OF 'CONTRACT' (Cost=26 Card=3 Bytes=36)

Statistics
----------------------------------------------------------
          0  recursive calls
      19124  db block gets
    1234163  consistent gets
    1171457  physical reads

from a fast full scan and full scan, with such tiny costs -- I cannot imagine
they are?
Rating: 3
The statistics are real December 18, 2003
Reviewer: MK
The statistics are correct. This is an application in our production database.
The statistics can be regenerated. I analyzed the tkprof reports as well, but it
tells the same story. I am also surprised with the performance difference, the
tables are also not big (contract : 7929 rows and service_provider has 4780 rows
only). Thanks.
Followup:
please query the data dictionary and VERIFY the stats are good.

show us the tkprofs
Rating: 3
Please see the tkprof December 19, 2003
Reviewer: MK
alter session set events '10046 trace name context forever, level 12';

tkprof <sid>_ora_18319.trc tkprof_18319.out explain=system/<PW>

Query 1:
--------
select to_char(SVCPRV_ID) from SERVICE_PROVIDER
where exists
 ( select 'X'   FROM "CONTRACT"
   WHERE CONTRACT.SRVC_PRVDR_ID  = SERVICE_PROVIDER.SVCPRV_ID  and
  "CONTRACT"."CLOSEOUT_DATE" is null    AND
  "CONTRACT"."CONSTR_GROUP_CODE" is not null   AND
         ("CONTRACT"."CONSTRUCTION_JOB_FLAG" <> 'N' OR
         "CONTRACT"."CONSTRUCTION_JOB_FLAG" is null )
   )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.02          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2     81.06      81.18     356281     389997       6048          16
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4     81.08      81.20     356281     389997       6048          16

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 5  (SYSTEM)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   FILTER
      0    INDEX   GOAL: ANALYZED (FAST FULL SCAN) OF 'PK_SVC_PROVIDER'
               (UNIQUE)
      0    TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'CONTRACT'

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.02          0          0          0           0
Execute      2      0.00       0.01          0          0          0           0
Fetch        2     81.06      81.18     356281     389997       6048          16
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5     81.08      81.21     356281     389997       6048          16

Misses in library cache during parse: 1
Misses in library cache during execute: 1

Query 2:
--------

select '"SVCPRV_ID"' from dual
union
select to_char(SVCPRV_ID) from SERVICE_PROVIDER
where exists
 ( select 'X'   FROM "CONTRACT"
   WHERE CONTRACT.SRVC_PRVDR_ID  = SERVICE_PROVIDER.SVCPRV_ID  and
  "CONTRACT"."CLOSEOUT_DATE" is null    AND
  "CONTRACT"."CONSTR_GROUP_CODE" is not null   AND
         ("CONTRACT"."CONSTRUCTION_JOB_FLAG" <> 'N' OR
         "CONTRACT"."CONSTRUCTION_JOB_FLAG" is null )
   )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.09       0.09          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        5      0.25       0.28        257       1806          8          47
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        7      0.34       0.37        257       1806          8          47

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 5  (SYSTEM)

Rows     Row Source Operation
-------  ---------------------------------------------------
     47  SORT UNIQUE
    183   UNION-ALL
      1    TABLE ACCESS FULL DUAL
    182    NESTED LOOPS
   1545     TABLE ACCESS FULL CONTRACT
    182     INDEX UNIQUE SCAN (object id 147731)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
     47   SORT (UNIQUE)
    183    UNION-ALL
      1     TABLE ACCESS (FULL) OF 'DUAL'
    182     NESTED LOOPS
   1545      TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'CONTRACT'
    182      INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF 'PK_SVC_PROVIDER'
                 (UNIQUE)

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.09       0.09          0          0          0           0
Execute      2      0.00       0.01          0          0          0           0
Fetch        5      0.25       0.28        257       1806          8          47
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        8      0.34       0.38        257       1806          8          47

Misses in library cache during parse: 1

1. I analyzed the tables just before this (Writing here just to assure you).
Using "analyze table <schema>.contract compute statistics for table for all
indexes for all indexed columns;"
And same for second table.

TABLE_NAME                       NUM_ROWS  INI_TRANS  FREELISTS AVG_ROW_LEN
CHAIN_CNT  AVG_SPACE
------------------------------ ---------- ---------- ---------- -----------
---------- ----------
LAST_ANAL
---------
CONTRACT                             7932          1          1         213
   25       1505
19-DEC-03

SERVICE_PROVIDER                     4794          1          1         120
    0       1947
19-DEC-03

Autotrace for query 1:

46 rows selected.

Elapsed: 00:04:258.10

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=240 Bytes=1440)
   1    0   FILTER
   2    1     INDEX (FAST FULL SCAN) OF 'PK_SVC_PROVIDER' (UNIQUE) (Cost=2
Card=240 Bytes=
          1440)

   3    1     TABLE ACCESS (FULL) OF 'CONTRACT' (Cost=26 Card=3 Bytes=36)

Statistics
----------------------------------------------------------
          0  recursive calls
      19180  db block gets
    1237789  consistent gets
    1139732  physical reads
          0  redo size
       1138  bytes sent via SQL*Net to client
        518  bytes received via SQL*Net from client
          5  SQL*Net roundtrips to/from client
          4  sorts (memory)
          0  sorts (disk)
         46  rows processed

For Query 2:

47 rows selected.

Elapsed: 00:00:02.81

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=32 Card=588 Bytes=9108)
   1    0   SORT (UNIQUE) (Cost=32 Card=588 Bytes=9108)
   2    1     UNION-ALL
   3    2       TABLE ACCESS (FULL) OF 'DUAL' (Cost=1 Card=82)
   4    2       NESTED LOOPS (Cost=26 Card=506 Bytes=9108)
   5    4         TABLE ACCESS (FULL) OF 'CONTRACT' (Cost=26 Card=506
Bytes=6072)
   6    4         INDEX (UNIQUE SCAN) OF 'PK_SVC_PROVIDER' (UNIQUE)

Statistics
----------------------------------------------------------
          0  recursive calls
          8  db block gets
       1806  consistent gets
        238  physical reads
          0  redo size
       1197  bytes sent via SQL*Net to client
        518  bytes received via SQL*Net from client
          5  SQL*Net roundtrips to/from client
          8  sorts (memory)
          0  sorts (disk)
         47  rows processed

Other observation:
I copied both tables to my local database (on Windows) and ran both the queries
there. The queries ran faster (as compared to Solaris machine), but again the
UNION query runs much faster (Elapsed: 00:00:00.12) than the single query itself
(Elapsed: 00:00:38.75). The execution path are same (with different cost &
cards), but no physical reads on Windows machine.
Thanks.
Followup:
Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   FILTER
      0    INDEX   GOAL: ANALYZED (FAST FULL SCAN) OF 'PK_SVC_PROVIDER'
               (UNIQUE)
      0    TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'CONTRACT'

missing the rows -- need the rows, that was what I was looking for was the rows.

make sure to EXIT sqlplus before running tkprof to close all cursors.  No need
to use explain=, don't use that with tkprof.
Rating: 2
new tkprof output December 22, 2003
Reviewer: MK
Did as per your advice, This times it is saying 31 rows (actual rows fetched are
46 though). Here is tkprof output.

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.01          0          0          0           0
Execute      2      0.00       0.01          0          0          0           0
Fetch        3    186.59     187.75     702845    1006040      15576          31
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        6    186.61     187.77     702845    1006040      15576          31
Followup:
if it says 31 and you saw 46, you probably didn't exit sqlplus before running
tkprof -- the trace file is incomplete (perhaps truncated - look at the raw
trace file, maybe your max dump size is too small)
Rating: 3
December 22, 2003
Reviewer: MK
You are right, Tom. Trace was truncated. Please find the new output here.
Thanks.

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.04       0.05          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        5    258.50     259.35    1093131    1237789      19180          46
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        7    258.54     259.40    1093131    1237789      19180          46

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 5

Rows     Row Source Operation
-------  ---------------------------------------------------
     46  FILTER
   4795   INDEX FAST FULL SCAN (object id 147731)
   4794   TABLE ACCESS FULL CONTRACT
Followup:
  0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=240 Bytes=1440)
   1    0   FILTER
   2    1     INDEX (FAST FULL SCAN) OF 'PK_SVC_PROVIDER' (UNIQUE) (Cost=2
Card=240 Bytes=
          1440)

   3    1     TABLE ACCESS (FULL) OF 'CONTRACT' (Cost=26 Card=3 Bytes=36)

Ok, the plan things "3 rows", you got 4,794 rows.

select to_char(SVCPRV_ID) from SERVICE_PROVIDER
where exists
 ( select 'X'   FROM "CONTRACT"
   WHERE CONTRACT.SRVC_PRVDR_ID  = SERVICE_PROVIDER.SVCPRV_ID  and
  "CONTRACT"."CLOSEOUT_DATE" is null    AND
  "CONTRACT"."CONSTR_GROUP_CODE" is not null   AND
         ("CONTRACT"."CONSTRUCTION_JOB_FLAG" <> 'N' OR
         "CONTRACT"."CONSTRUCTION_JOB_FLAG" is null )
   )

so, the optimizer is thinking that when

closeout_date is null and
constr_group_code is not null
and construction_job_flag <> 'N' or construction_job_flag is null

gets 3 rows.  it is wrong.  do we have histograms and if you query:

and nvl(construction_job_flag,'X') <> 'N'

what happens then
December 22, 2003 shipra -- Thanks for the question regarding "export to CSV", version
You Asked
Tom,
I need to export data from a table into a .csv file.  I need to
have my column headres in between "" and data separated by ','.
also depending on the  column values a row may be printed upto 5

times with data differing in only one field.
Also I need to run this program
on a daily basis.
and we said...
I'm going to suggest UTL_FILE for this task since:

- you have great control over the output file format and you
seem to need that here.
- you can easily use dbms_job to schedule this procedure to run
automatically every day at a certain time.

Here is a working example.  I dump dates in a non-standard
format (but one that saves all of the fields).  you can modify
as you wish.

ops$tkyte@8i> create or replace procedure dump_table_to_csv(
p_tname in varchar2,
  2                                                 p_dir   in
varchar2,
  3                                                 p_filename
in varchar2 )
  4  is
  5      l_output        utl_file.file_type;
  6      l_theCursor     integer default dbms_sql.open_cursor;
  7      l_columnValue   varchar2(4000);
  8      l_status        integer;
  9      l_query         varchar2(1000)
 10                      default 'select * from ' || p_tname;
 11      l_colCnt        number := 0;
 12      l_separator     varchar2(1);
 13      l_descTbl       dbms_sql.desc_tab;
 14  begin
 15      l_output := utl_file.fopen( p_dir, p_filename, 'w' );
 16      execute immediate 'alter session set
nls_date_format=''dd-mon-yyyy hh24:mi:ss'' ';
 17
 18      dbms_sql.parse(  l_theCursor,  l_query, dbms_sql.native
);
 19      dbms_sql.describe_columns( l_theCursor, l_colCnt,
l_descTbl );
 20
 21      for i in 1 .. l_colCnt loop
 22          utl_file.put( l_output, l_separator || '"' ||
l_descTbl(i).col_name || '"' );
 23          dbms_sql.define_column( l_theCursor, i,
l_columnValue, 4000 );
 24          l_separator := ',';
 25      end loop;
 26      utl_file.new_line( l_output );
 27
 28      l_status := dbms_sql.execute(l_theCursor);
 29
 30      while ( dbms_sql.fetch_rows(l_theCursor) > 0 ) loop
 31          l_separator := '';
 32          for i in 1 .. l_colCnt loop
 33              dbms_sql.column_value( l_theCursor, i,
l_columnValue );
 34              utl_file.put( l_output, l_separator ||
l_columnValue );
 35              l_separator := ',';
 36          end loop;
 37          utl_file.new_line( l_output );
 38      end loop;
 39      dbms_sql.close_cursor(l_theCursor);
 40      utl_file.fclose( l_output );
 41
 42      execute immediate 'alter session set
nls_date_format=''dd-MON-yy'' ';
 43  exception
 44      when others then
 45          execute immediate 'alter session set
nls_date_format=''dd-MON-yy'' ';
 46          raise;
 47  end;
 48  /

Procedure created.

ops$tkyte@8i> exec dump_table_to_csv( 'emp', '/tmp', 'tkyte.emp'
);

PL/SQL procedure successfully completed.

ops$tkyte@8i> host cat /tmp/tkyte.emp
"EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO"
7369,SMITH,CLERK,7902,17-dec-1980 00:00:00,800,,20
7499,ALLEN,SALESMAN,7698,20-feb-1981 00:00:00,1600,300,30
7521,WARD,SALESMAN,7698,22-feb-1981 00:00:00,1250,500,30
7566,JONES,MANAGER,7839,02-apr-1981 00:00:00,2975,,20
7654,MARTIN,SALESMAN,7698,28-sep-1981 00:00:00,1250,1400,30
7698,BLAKE,MANAGER,7839,01-may-1981 00:00:00,2850,,30
7782,CLARK,MANAGER,7839,09-jun-1981 00:00:00,2450,,10
7788,SCOTT,ANALYST,7566,09-dec-1982 00:00:00,3000,,20
7839,KING,PRESIDENT,,17-nov-1981 00:00:00,5000,,10
7844,TURNER,SALESMAN,7698,08-sep-1981 00:00:00,1500,0,30
7876,ADAMS,CLERK,7788,12-jan-1983 00:00:00,1100,,20
7900,JAMES,CLERK,7698,03-dec-1981 00:00:00,950,,30
7902,FORD,ANALYST,7566,03-dec-1981 00:00:00,3000,,20
7934,MILLER,CLERK,7782,23-jan-1982 00:00:00,1300,,10

See the Supplied Packages guide for info on utl_file.  You must
set some init.ora parameters to enable it to work correctly....

This can be done via sqlplus as well -- just not as nicely in my
opinion.
Review & Followup

Rating: 5
Spool Vs utl_file when # of records are 90 Million December 22, 2003
Reviewer: Venkat from India
Hi tom,

We have a table with 90 Million Records.  We need to create a CSV file and need
to send this file to some external third party.  Right now, we are spooling into
some OS file ("," Separated) and sending this file.  But to spool 90 Million
records, its taking almost 4 hours.

Will using utl_file instead of spool, increase the speed?  which one is better??
 Please suggest.

thanks
Venkat
Followup:
none of the above, you want speed -- use a little c.

here:
http://asktom.oracle.com/~tkyte/flat/index.html
you'll find in order of speed:

utl_file (slowest but really handy when it is "fast enough")
sqlplus
C
December 22, 2003 sj -- Thanks for the question regarding "ORA-14130: UNIQUE constraints mismatch ... got the solution ...please review", version 8.1.7
You Asked

http://asktom.oracle.com/pls/ask/f?p=4950:9:::NO::F4950_P9_ORIG,F4950_P9_DISPLAYID:Y,963478374614
Finally Tom, I understood what you were saying..

I did the following

create table emppart
( EMPNO                                             NUMBER(4) ,
 ENAME                                              VARCHAR2(10),
 JOB                                                VARCHAR2(9),
 MGR                                                NUMBER(4),
 HIREDATE                                           DATE,
 SAL                                                NUMBER(7,2),
 COMM                                               NUMBER(7,2),
 DEPTNO                                             NUMBER(2))
partiton by range(hiredate)
(partition p1 values less than (to_date('07-07-2001','dd-mm-yyyy')),
 partition p2 values less than (MAXVALUE))

create index idx_emppart on emppart(empno,hiredate,deptno) local;

create table emp
( EMPNO                                             NUMBER(4) ,
 ENAME                                              VARCHAR2(10),
 JOB                                                VARCHAR2(9),
 MGR                                                NUMBER(4),
 HIREDATE                                           DATE,
 SAL                                                NUMBER(7,2),
 COMM                                               NUMBER(7,2),
 DEPTNO                                             NUMBER(2))

create  index idx_emp on emppart(empno,hiredate,deptno);

alter table emppart exchange partition p1 with emp including indexes;

'IT WORKED......'

I them tried...UNIZUE INDEXES

drop index idx_emp;
drop index idx_emppart;

create UNIQUE index idx_emp on emppart(empno,hiredate,deptno);
create index idx_emppart on emppart(empno,hiredate,deptno) local;

alter table emppart exchange partition p1 with emp including indexes;

'IT WORKED..'

TOM,

basically, I haved avoided using primary key on the partitioned table.. and
created a UNIQUE indexes..

TOM, can you explain to me what is the difference of declaring a primary key ,
or instead of doing that creating a UNIQUE  index with the same columns..

i.e. PRIMARY KEY Vs Unique index..

Thank you very much .
and we said...
You only half got it, close but no cigar.

there isn't any difference except that one is a primary key (includes not null
constraints) and the other isn't a primary key.

The only thing to consider here is the type of index used.  LOCAL or GLOBAL.
You made the primary key be included in the partition key -- now you can use a
primary key IF you specify that a local index is to be used.

After fixing syntax in your example, we have:

ops$tkyte@ORA815> create table emppart
  2  ( EMPNO                                             NUMBER(4) ,
  3   ENAME                                              VARCHAR2(10),
  4   JOB                                                VARCHAR2(9),
  5   MGR                                                NUMBER(4),
  6   HIREDATE                                           DATE,
  7   SAL                                                NUMBER(7,2),
  8   COMM                                               NUMBER(7,2),
  9   DEPTNO                                             NUMBER(2))
 10  partition by range(hiredate)
 11  (partition p1 values less than (to_date('07-07-2001','dd-mm-yyyy')),
 12   partition p2 values less than (MAXVALUE))
 13  /
Table created.

ops$tkyte@ORA815> alter table emppart add constraint emppart_pk
  2  primary key ( empno, hiredate, deptno )
  3  using INDEX local
  4  /
Table altered.

ops$tkyte@ORA815> create table emp
  2  ( EMPNO                                             NUMBER(4) ,
  3   ENAME                                              VARCHAR2(10),
  4   JOB                                                VARCHAR2(9),
  5   MGR                                                NUMBER(4),
  6   HIREDATE                                           DATE,
  7   SAL                                                NUMBER(7,2),
  8   COMM                                               NUMBER(7,2),
  9   DEPTNO                                             NUMBER(2))
 10  /
Table created.

ops$tkyte@ORA815> alter table emp add constraint emp_pk primary key (empno,
hiredate, deptno )
  2  /
Table altered.

ops$tkyte@ORA815> alter table emppart exchange partition p1 with table emp
including indexes;

Table altered.

there you go, exchanged a table with a partition of a partitioned table that had
a primary key including indexes.
Review & Followup

Rating: 4
difference between unique key and unique index July 24, 2002
Reviewer: A reader
tom,
is there a difference between creating an unique index on a,b,c columns of table
t

and

creating unique key on the table t with the columns a,b and c.

does the unique index enforce the uniqueness on the columns or we have to create
the unique seperately i need not create unique key

i don't see any key in constraints table user_constraints when i create an
unique index.

in the same way vice versa

the same way
Followup:
YES.

a unique constraint is used by the optimizer with query rewrites and other
features.

a unique index is not used by the optimizer for query rewrites.

see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3051352977204
for details.
Rating: 3
Question from Answer July 24, 2002
Reviewer: Sajid from Islamabad, Pakistan
Sir I want to know details of function
>>partiton by range(hiredate)<<
Please explian it.
Followup:
In general you want to read:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/toc.htm
from cover to cover.  The syntax you ask about is part of partitioning -- the
ability to split a big object into lots of smaller ones.  The chapter for that
one is:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/c09parti.htm#436962

Rating: 4
alter table... exchange partition October 28, 2003
Reviewer: Andy from Texas
Hi Tom,

I am loading data into a temp table T and then exchanging the temp table with a
partitioned table A. The temp table is an exact replica of the partitioned table
A. Infact, I generated DDL from table A and then created T from it. I see no
difference between A and T.

But, when I do an EXCHANGE PARTITION, it fails and errors out with ORA-14118
"CHECK constraint mismatch in ALTER TABLE EXCHANGE PARTITION". Why does this
happen?

Both A and T have 8 check constraints each and all were enabled. When we
disabled all the check constraints, it worked fine.

My question is :

1. Is it absolutely neccessary to disable CHECK constraints on both the tables A
and T before exchanging partitions?

2. Can a table have a PRIMARY KEY constraint and a NOT NULL constraint in the
same column ?

Thanks

Anand
Followup:
hows about an example?????????  like I give all of the time??
Rating: 5
CHECK constraint mismatch in ALTER TABLE EXCHANGE PARTITION December 22, 2003
Reviewer: karma from MA USA
I am getting the error  "ORA-14118: CHECK constraint mismatch in ALTER TABLE
EXCHANGE PARTITION". I have one table that is partioned and one that is not
partitioned and all i do is exchange partition as :
ALTER TABLE RW1.AB_DET_HST  EXCHANGE PARTITION PART_BAL_DET_HST_20031202 WITH
TABLE RW1.AB_DET  ;

Here is table structure : Non Partioned Table
CREATE TABLE RW1.AB_DET
(
  F_FIRM_C  VARCHAR2(4 BYTE)                 NOT NULL,
  F_BR_C  VARCHAR2(3 BYTE)                 NOT NULL,
  F_BA_C  VARCHAR2(6 BYTE)                 NOT NULL,
  EFF_D        DATE                             NOT NULL,
  ACCT_TY_C    VARCHAR2(1 BYTE)                 NOT NULL,
  MKT_VALU_A   NUMBER(13,2)                         NULL,
  LOAN_A       NUMBER(11,2)                         NULL,
  PRTN_ID      VARCHAR2(6 BYTE)                     NULL,
  INSERT_TMST  DATE                                 NULL,
  UPDATE_TMST  DATE                                 NULL
)
LOGGING
NOCACHE
NOPARALLEL
/

CREATE UNIQUE INDEX RW1.ABDET_PK ON RW1.AB_DET
(F_FIRM_C, F_BR_C, F_BA_C, ACCT_TY_C, EFF_D)
LOGGING
NOPARALLEL
/

ALTER TABLE RW1.AB_DET ADD (
  CONSTRAINT ABDET_PK PRIMARY KEY (F_FIRM_C, F_BR_C, F_BA_C, ACCT_TY_C, EFF_D))
/

Here is script for partitioned table :

CREATE TABLE RW1.AB_DET_HST
(
  F_FIRM_C  VARCHAR2(4 BYTE) CONSTRAINT SYS_C003290 NOT NULL,
  F_BR_C  VARCHAR2(3 BYTE) CONSTRAINT SYS_C003291 NOT NULL,
  F_BA_C  VARCHAR2(6 BYTE) CONSTRAINT SYS_C003292 NOT NULL,
  EFF_D        DATE CONSTRAINT SYS_C003293      NOT NULL,
  ACCT_TY_C    VARCHAR2(1 BYTE) CONSTRAINT SYS_C003294 NOT NULL,
  MKT_VALU_A   NUMBER(13,2)                         NULL,
  LOAN_A       NUMBER(11,2)                         NULL,
  PRTN_ID      VARCHAR2(6 BYTE)                     NULL,
  INSERT_TMST  DATE                                 NULL,
  UPDATE_TMST  DATE                                 NULL
)
PARTITION BY RANGE (EFF_D)
(
  PARTITION PART_BAL_DET_HST_20031120 VALUES LESS THAN (TO_DATE(' 2003-11-20
00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN'))
    LOGGING
    NOCOMPRESS,
  PARTITION PART_BAL_DET_HST_20031202 VALUES LESS THAN (TO_DATE(' 2003-12-03
00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN'))
    LOGGING
    NOCOMPRESS
)
NOCACHE
NOPARALLEL
/

CREATE UNIQUE INDEX RW1.ABDETH_PK ON RW1.AB_DET_HST
(F_FIRM_C, F_BR_C, F_BA_C, ACCT_TY_C, EFF_D)
LOGGING
NOPARALLEL
/

ALTER TABLE RW1.AB_DET_HST ADD (
  CONSTRAINT ABDETH_PK PRIMARY KEY (F_FIRM_C, F_BR_C, F_BA_C, ACCT_TY_C, EFF_D))
/

What am i doing wrong that is giving me error :
ORA-14118: CHECK constraint mismatch in ALTER TABLE EXCHANGE PARTITION

What is correct way of doing it ?

Thanks
Followup:
works for me -- you must have 9ir2 like me (nocompress) so.... you must have
some other constraint out there your example doesn't show us

ops$tkyte@ORA9IR2> drop TABLE AB_DET;

Table dropped.

ops$tkyte@ORA9IR2> CREATE TABLE AB_DET
  2  (
  3    F_FIRM_C  VARCHAR2(4 BYTE)                 NOT NULL,
  4    F_BR_C  VARCHAR2(3 BYTE)                 NOT NULL,
  5    F_BA_C  VARCHAR2(6 BYTE)                 NOT NULL,
  6    EFF_D        DATE                             NOT NULL,
  7    ACCT_TY_C    VARCHAR2(1 BYTE)                 NOT NULL,
  8    MKT_VALU_A   NUMBER(13,2)                         NULL,
  9    LOAN_A       NUMBER(11,2)                         NULL,
 10    PRTN_ID      VARCHAR2(6 BYTE)                     NULL,
 11    INSERT_TMST  DATE                                 NULL,
 12    UPDATE_TMST  DATE                                 NULL
 13  )
 14  LOGGING
 15  NOCACHE
 16  NOPARALLEL
 17  /

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> CREATE UNIQUE INDEX ABDET_PK ON AB_DET
  2  (F_FIRM_C, F_BR_C, F_BA_C, ACCT_TY_C, EFF_D)
  3  LOGGING
  4  NOPARALLEL
  5  /

Index created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> ALTER TABLE AB_DET ADD (
  2    CONSTRAINT ABDET_PK PRIMARY KEY (F_FIRM_C, F_BR_C, F_BA_C, ACCT_TY_C,
EFF_D))
  3  /

Table altered.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> drop TABLE AB_DET_HST;

Table dropped.

ops$tkyte@ORA9IR2> CREATE TABLE AB_DET_HST
  2  (
  3    F_FIRM_C  VARCHAR2(4 BYTE) CONSTRAINT SYS_C003290 NOT NULL,
  4    F_BR_C  VARCHAR2(3 BYTE) CONSTRAINT SYS_C003291 NOT NULL,
  5    F_BA_C  VARCHAR2(6 BYTE) CONSTRAINT SYS_C003292 NOT NULL,
  6    EFF_D        DATE CONSTRAINT SYS_C003293      NOT NULL,
  7    ACCT_TY_C    VARCHAR2(1 BYTE) CONSTRAINT SYS_C003294 NOT NULL,
  8    MKT_VALU_A   NUMBER(13,2)                         NULL,
  9    LOAN_A       NUMBER(11,2)                         NULL,
 10    PRTN_ID      VARCHAR2(6 BYTE)                     NULL,
 11    INSERT_TMST  DATE                                 NULL,
 12    UPDATE_TMST  DATE                                 NULL
 13  )
 14  PARTITION BY RANGE (EFF_D)
 15  (
 16    PARTITION PART_BAL_DET_HST_20031120 VALUES LESS THAN (TO_DATE('
2003-11-20 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN'))
 17      LOGGING
 18      NOCOMPRESS,
 19    PARTITION PART_BAL_DET_HST_20031202 VALUES LESS THAN (TO_DATE('
2003-12-03 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN'))
 20      LOGGING
 21      NOCOMPRESS
 22  )
 23  NOCACHE
 24  NOPARALLEL
 25  /

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> CREATE UNIQUE INDEX ABDETH_PK ON AB_DET_HST
  2  (F_FIRM_C, F_BR_C, F_BA_C, ACCT_TY_C, EFF_D)
  3  LOGGING
  4  NOPARALLEL
  5  /

Index created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> ALTER TABLE AB_DET_HST ADD (
  2    CONSTRAINT ABDETH_PK PRIMARY KEY (F_FIRM_C, F_BR_C, F_BA_C, ACCT_TY_C,
EFF_D))
  3  /

Table altered.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> ALTER TABLE AB_DET_HST       EXCHANGE PARTITION
PART_BAL_DET_HST_20031202 WITH
  2  TABLE AB_DET
  3  /

Table altered.
December 22, 2003 Subhakar -- Thanks for the question regarding "PCTVERSION", version 8.1.7
You Asked
Hi Tom,

Can U please explain me with an example on how to set/tune the
parameter PCTVERSION ?

Thankx,
Subhakar
and we said...
If there is a high amount of concurrent updates as well as reads
of the LOB column, choose a high value for PCTVERSION.

PCTVERSION is the percent of LOB storage space kept for old
versions of LOB pages to maintain read consistency for reads
started before an update.  The default value is 10 percent.

You use the "store as" clause to set this

CREATE TABLE "OPS$TKYTE"."CLOB_TABLE" ("X" CLOB)
PCTFREE 10 PCTUSED 40
INITRANS 1 MAXTRANS 255
LOGGING STORAGE(INITIAL 524288)
TABLESPACE "USERS"
LOB ("X") STORE AS (TABLESPACE "USERS" ENABLE STORAGE IN ROW
CHUNK 8192
PCTVERSION 10
NOCACHE STORAGE(INITIAL 524288)) ;

PCTVERSION can be changed after creation using:
   ALTER TABLE tabname MODIFY LOB (lobname) ( PCTVERSION n );

If a session is attempting to use an OLD version of a LOB
and that version gets overwritten (because PCTVERSION is too
small) then the user will typically see the errors:

ORA-01555: snapshot too old:
    rollback segment number  with name "" too small
ORA-22924: snapshot too old

PCTVERSION can prevent OLD pages being used and force the
segment to extend instead.
Review & Followup

Rating: 5
snapshot too old November 10, 2003
Reviewer: A reader
Hi

We tried to export our table which contains BLOBs and it is impossible. We run
our export at 3 o´clock in the morning, there is absolutely noone in the
database and it always end up wth ORA-01555 error (cant see why if there are DML
going on). We even increased PCTVERSION to 60 and same...

Searched Metalink, it seems that this happen to many people but there is no
solution at all... Do you have any suggestion?
Followup:
your RBS's are too small, does the error reference a RBS.  you need to
permanently increase the size of your RBS's.

but, if you are using export as a "backup" you probably want to rethink that.
"backups" are "backups" -- export is a tool to get data out to put into another
database.  it is not a backup tool.

proper backups won't every get a 1555.
Rating: 5
yes it does references RBS November 10, 2003
Reviewer: A reader
Hi

It does references RBS number however we dont understand, there is noone except
the export process in the database how come it get ORA-01555?

Also, isnt PCTVERSION like RBS but it is for LOBs...? Or LOBs still use RBS to
give consistent reads?
Followup:
lobs version themselves in the lob space -- not in the rbs.  so, if you are
getting "rbs such and such too small", it isn't a lob that is the issue.

there are others in the database (there are always others).  Oracle backgrounds
for example.  and they need not be modifying anything you are touching.

but again -- are you using export as a backup?  that, that in itself would be
"wrong"
Rating: 5
no we are not using export as backup November 11, 2003
Reviewer: A reader
No! Export is NOT our backup strategy! RMAN + Veritas Netbackup

The problem is we have been trying to export this table for over 4 months! We
even set PCTVERSION to a very high value and no luck! Will try to increase RBS
Followup:
transport the tablespace.

(pctversion wasn't the right thing to modify in this case, if it was pointing to
a RBS)
Rating: 5
we also have same problem November 11, 2003
Reviewer: A reader
We also face export problems with BLOBs

EXP-00056: ORACLE error 1555 encountered
ORA-01555: snapshot too old: rollback segment number  with name "" too small
ORA-22924: snapshot too old

So far there is no slution for us, setting PCTVERSION to 70 does not help....
same situation too, noone is in the database....
Followup:
use a transport then.
Rating: 5
cannot November 11, 2003
Reviewer: A reader
we are using oracle 8.0.6 :-\
Rating: 5
pctfree December 22, 2003
Reviewer: A reader
Hi

is pctversion like pctfree???

==========
PCTVERSION is the percent of LOB storage space kept for old versions of LOB
pages to maintain read consistency for reads started before an update.

Do you mean if my LOB uses 1M and pctversion 10% my LOB will actually have a
1.1MB extent?

What is a page? Is its size same as chunk size? Is page same as chunk?
Followup:
pctversion is like "undo retention".  specifies how much of the old stuff to
keep around for subsequent reads of the data.

page is block.
December 23, 2003 David -- Thanks for the question regarding "difference between dropping rebuilding indexes and using update global indexes", version 9.2.04
You Asked
hi Tom

we have recently migrated to 9iR2 and wanted to use some new features to stop
our indexes going valid.

What we have is a partitioned table called cs_actype_map with one partition
called part.

Now we are loading reference data into a table called cs_actype_map_tmp and then
using exchange partition to swap the data over.

Now in 8i the two indexes went unusable so we dropped and recreated them.

So now in 9i we added the update global indexes clause so now they indexes are
ok, but what we saw was it was a lot slower than rebuilding,

here is my test

zhl4s002:22 % more cim_results.txt
17:52:21 SQL> ALTER TABLE CS_ACTYPE_MAP EXCHANGE PARTITION part WITH TABLE
CS_ACTYPE_MAP_TMP;

Table altered.

Elapsed: 00:00:13.78

17:53:17 SQL> CREATE INDEX I1_CS_ACTYPE_MAP ON CS_ACTYPE_MAP (ACCOUNT_KEY)
TABLESPACE mz_lcustom_ind UNRECOVERABLE;

Index created.

Elapsed: 00:01:20.55

17:54:51 SQL> CREATE INDEX I2_CS_ACTYPE_MAP ON CS_ACTYPE_MAP (TEXT1) TABLESPACE
mz_lcustom_ind UNRECOVERABLE;

Index created.

Elapsed: 00:01:25.85

17:57:43 SQL> truncate table cs_actype_map;

Table truncated.

Elapsed: 00:00:00.47
17:58:07 SQL> truncate table cs_actype_map_tmp;

Table truncated.

Elapsed: 00:00:00.08
17:58:24 SQL> insert /*+APPEND*/ into cs_actype_map_tmp select * from ds;

5871902 rows created.

Elapsed: 00:01:15.33
17:59:46 SQL> 17:59:46 SQL> commit;

Commit complete.

Elapsed: 00:00:00.13
17:59:52 SQL> ALTER TABLE CS_ACTYPE_MAP EXCHANGE PARTITION part WITH TABLE
CS_ACTYPE_MAP_TMP UPDATE GLOBAL INDEXES;

Table altered.

Elapsed: 00:25:21.39
18:25:37 SQL>

Overall took around 20 mins longer, I did increase my sort area size to around
15m and it went down about 5 mins, but overall time was much longer.

My question is should it take longer?

I understand there are better ways to get the data into the table in the first
place but this is what I have to use at the moment

Thanks
and we said...
rebuild is a "bulk operation"

it is true that SOMETIMES, disabling an index, doing a large bulk load,
re-enabling and rebuild the index is FASTER than "leaving the index in place
during the load"

This is true for single partition tables (normal tables) as well as partitioned
tables.

HOWEVER, during the load -- the indexes are "not there".  So, you have to make
the choice:

a) fast load, but no other work gets done

OR

b) slower load, but queries and all other work continues uninterrupted

This is only true sometimes -- if you add say 1,000 rows to 100,000,000 rows --
leaving the indexes on probably flies as compared to disable, load, rebuild.

So, as with everything in life -- it is a tradeoff.  do you want:

a) continous operation
b) downtime

the decision, at the end of the day, is up to you.  Maintaining the index in
your case takes longer then a disable, load, and rebuild.  BUT during the
disable/load/rebuild time -- no work gets done other then the load.  If that is
acceptable -- that is the option you want.  If you primary goal is continous
operation -- the penalty is "longer load time" in your case (but not in
EVERYONES case)

in your case, the real difference is 5 mins versus 3 mins -- forget that 25
minute one (you already figured that out on your own).

5 minutes
vs
3 minutes

it is not that large.
Review & Followup

Rating: 5
Very nice explanation of tradeoffs December 21, 2003
Reviewer: J.Nemec from Austria
Hi Tom,

I agree that the “UPDATE GLOBAL INDEXES” technique is very useful in case of
global indices or unique constraints with lot of partitions (i.e. rolling window
publishing).
In the case above there is only one partition in the table and no unique index.
Apparently this is a total refresh of a table.
What about the approach
a) build the indexes on the CS_ACTYPE_MAP_TMP table
b) exchange the CS_ACTYPE_MAP_TMP  table “INCLUDING INDEXES”

I guess it will perform similar to the disable/load/rebuild case above and the
“downtime” (i.e time of exchange) will be very short. But I am not sure what is
the influence on other processing. I total refresh of a table is performed, at
one moment old data are there, on second moment new version of data is valid.

Can you please explain the difference of

UPDATE GLOBAL INDEXES
INCLUDING INDEXES
INCLUDING INDEXES WITH VALIDATION

from a point of view of concurrent queries.

Thanks

Jaromir Nemec
Followup:
including indexes ONLY applies to local -- not global -- indexes.  It is only
useful for getting local indexes changed.  will not affect globals at all.
Rating: 5
including indexes December 22, 2003
Reviewer: A reader
Hi

Is including indexes a dictionary operation as exchange partition?

For the guy who asked, this is from the Documentation

The partition DDL statement takes longer to execute since indexes which were
previously marked UNUSABLE are updated. However, this must be compared against
the time it takes to execute DDL without updating indexes, and then rebuilding
all indexes. A rule of thumb is that it is faster to update indexes if the size
of the partition is less that 5% of the size of the table.
Rating: 5
global index on table with one partition December 23, 2003
Reviewer: Nemec
If the design decision is that the table has only one partition (as described
above), what sense it makes to define global index?
Followup:
to split the index out, decreasing contention perhaps.  but in general, I don't
see it being done.
December 23, 2003 Gururaj -- Thanks for the question regarding "Questions on Locking", version 8.X
You Asked
Hi Tom,

Here are the few questions on Locking.

1)How do I use v$lock to figure out lock details ? Details like
whos has locked,on which table,who else is waiting, and what type
of locking (shared or exlusive ?), since when it is locked.

2)Why and when exclusive/shared locks are used ? Could you give me
example please ?

Thanks for the answer, I understand better.

3)Could you tell me significance of v$mystat and v$locked_object ?

Thank you .

4) Do we have page level locking in Oracle ?

Thank you

Thanks in advance,

Gururaj
and we said...
1) Here is a small example showing how this happens. We will use three V$ tables
in order to see how this works; V$TRANSACTION, V$SESSION, and V$LOCK.
V$TRANSACTION contains an entry for every active transaction. V$LOCK contains an
entry for all locks held as well as locks being waited on. V$SESSION shows us
the sessions logged in. We'll start by starting a transaction in one session and
looking at the state of the system at that point:

tkyte@TKYTE816> update dept set deptno = deptno+10;
4 rows updated.

tkyte@TKYTE816> select username,
  2         v$lock.sid,
  3         trunc(id1/power(2,16)) rbs,
  4         bitand(id1,to_number('ffff','xxxx'))+0 slot,
  5         id2 seq,
  6         lmode,
  7         request
  8  from v$lock, v$session
  9  where v$lock.type = 'TX'
 10    and v$lock.sid = v$session.sid
 11    and v$session.username = USER
 12  /

USERNAME        SID        RBS       SLOT        SEQ      LMODE    REQUEST
-------- ---------- ---------- ---------- ---------- ---------- ----------
TKYTE             8          2         46        160          6          0

tkyte@TKYTE816> select XIDUSN, XIDSLOT, XIDSQN
  2    from v$transaction
  3  /

    XIDUSN    XIDSLOT     XIDSQN
---------- ---------- ----------
         2         46        160

The interesting things to note here are:

ƒá The LMODE is 6 in the V$LOCK table and the request is 0. If you refer to the
definition of the V$LOCK table in the Oracle Server Reference, you will find
that LMODE=6 is an exclusive lock. A value of 0 in the request means we are not
making a request ¡V we have the lock.
ƒá There is only one row in this table. This V$LOCK table is more of a queuing
table than a lock table. Many people expect there would be four rows in V$LOCK
since we have four rows locked. What you must remember however is that Oracle
does not store a master list of every row locked anywhere. To find out if a row
is locked, we must go to that row.
ƒá I took the ID1 and ID2 columns, and performed a bit of bit manipulation on
them. Oracle needed to save three 16bit numbers, but only had two columns in
order to do it. So, the first column ID1 holds two of these numbers. By dividing
by 2^16 with trunc(id1/power(2,16)) rbs and by masking out the high bits with
bitand(id1,to_number('ffff','xxxx'))+0 slot, I am able to get the two numbers
that are hiding in that one number back out.
ƒá The RBS, SLOT, and SEQ values match the V$TRANSACTION information. This is my
transaction ID.

Now I'll start another session using the same user name, update some rows in
EMP, and then try to update DEPT:

tkyte@TKYTE816> update emp set ename = upper(ename);
14 rows updated.

tkyte@TKYTE816> update dept set deptno = deptno-10;

I am now blocked in this session. If we run the V$ queries again, we see:

tkyte@TKYTE816> select username,
  2         v$lock.sid,
  3             trunc(id1/power(2,16)) rbs,
  4             bitand(id1,to_number('ffff','xxxx'))+0 slot,
  5             id2 seq,
  6         lmode,
  7             request
  8  from v$lock, v$session
  9  where v$lock.type = 'TX'
 10    and v$lock.sid = v$session.sid
 11    and v$session.username = USER
 12  /

USERNAME        SID        RBS       SLOT        SEQ      LMODE    REQUEST
-------- ---------- ---------- ---------- ---------- ---------- ----------
TKYTE             8          2         46        160          6          0
TKYTE             9          2         46        160          0          6
TKYTE             9          3         82        163          6          0

tkyte@TKYTE816> select XIDUSN, XIDSLOT, XIDSQN
  2    from v$transaction
  3  /

    XIDUSN    XIDSLOT     XIDSQN
---------- ---------- ----------
         3         82        163
         2         46        160

What we see here is that a new transaction has begun ¡V (3,82,163) is the
transaction ID. It has two rows in V$LOCK this time. One row represents the
locks that it owns (where LMODE=6). It also has a row in there that shows a
REQUEST with a value of 6. This is a request for an exclusive lock. The
interesting thing to note here is that the RBS/SLOT/SEQ values of this request
row are the transaction ID of the holder of the lock. We can easily see that the
transaction with SID=8 is blocking the transaction with SID=9. Now, if we commit
in SID = 8 the above changes:

tkyte@TKYTE816> select username,
  2         v$lock.sid,
  3             trunc(id1/power(2,16)) rbs,
  4             bitand(id1,to_number('ffff','xxxx'))+0 slot,
  5             id2 seq,
  6         lmode,
  7             request, block
  8  from v$lock, v$session
  9  where v$lock.type = 'TX'
 10    and v$lock.sid = v$session.sid
 11    and v$session.username = USER
 12  /

USERNAME        SID        RBS       SLOT        SEQ      LMODE    REQUEST
-------- ---------- ---------- ---------- ---------- ---------- ----------
TKYTE             9          3         82        163          6          0

tkyte@TKYTE816> select XIDUSN, XIDSLOT, XIDSQN
  2    from v$transaction
  3  /

    XIDUSN    XIDSLOT     XIDSQN
---------- ---------- ----------
         3         82        163

that request row has gone ¡V it disappeared the instant the other session gave
up its lock. That request row was the queuing mechanism. The database is able to
wake up the blocked sessions the instant the transaction is completed. Note that
the above gives us a very easy way to see blockers and waiters:

tkyte@TKYTE816> select
           (select username from v$session where sid=a.sid) blocker,
  2         a.sid,
  3        ' is blocking ',
  4         (select username from v$session where sid=b.sid) blockee,
  5             b.sid
  6    from v$lock a, v$lock b
  7   where a.block = 1
  8     and b.request > 0
  9     and a.id1 = b.id1
 10     and a.id2 = b.id2
 11  /

BLOCKER         SID 'ISBLOCKING'  BLOCKEE         SID
-------- ---------- ------------- -------- ----------
TKYTE             8  is blocking  TKYTE             9

simply by doing a self-join of V$LOCK with itself (I ran this query before
committing the session with SID=8).

2) exclusive lock -- I updated a row.  no one else can update it until I commit.
 I have an X lock on that row and only one person at a time can have an X lock.
an X lock provides serialization to a resource.

A shared lock -- when I update a table, I take a shared lock on the DEFINITION
of the table.  Everyone else can do that as well (more then one session can get
a shared lock on the table definition).  So, more than one person at a time can
update the table.  If you wanted to ALTER the table, you would need an X lock on
the defintion.  You cannot get an X lock when there are shared locks so you wait
until there are no shared locks.

3) mystat has the statistics (cpu use, blocks read, cursors opened, etc) for
your session only.  v$locked_object shows you all of the locks in the system.

4) no, never.
Review & Followup

Rating: 5
Locks and Blockers July 12, 2001
Reviewer: Aslam Momin from Woodbridge, NJ USA
Hi Tom (and Team !!!),

Excellent answer.
Thanks for imparting the technical knowledge.
Rating: 3
How can i find the name of the table January 23, 2002
Reviewer: sivababu from GERMANY
Hello TOM,
   This is superb to find the locking. But how can i find the table name?.

Thanks and waiting for ur reply.

regards
sivababu
Rating: 4
Find the table name -- Urgent January 24, 2002
Reviewer: sivababu from Germany
Hello TOM,
    I'm strugling with last two days to find out which table is locked. But i
couldn't find out. When i was using the JAVA MAILING SYSTEM. some tables are
locked. I don't know why it is locked. So I'm want to know which tables are
locked. So that i can find the problem.

Thank you and expecting your reply.

regards,
sivababu
Followup:
search this site for showsql.sql

it'll show you what sql people are executing.  when you get a blocked session,
run showsql and you'll see what SQL they are trying to execute but cannot.

That should help you ID the table in question.
Rating: 3
Thanks January 25, 2002
Reviewer: sivababu from germany
Hello TOM,
   Thanks very much. I got this query from your book.

regards,
sivababu
Rating: 4
Reader February 04, 2002
Reviewer: Reader
Tom,

You have used
"trunc(id1/power(2,16)) rbs"

Many books have used 65535 instaed of power(2,16)= 65536

Do these both approches result in the same value
consistently, if so why
65536 = 1000000000000000
65535 = 111111111111111
Rating: 4
Reader February 05, 2002
Reviewer: Reader
Never mind about the previous posting.
It was 65536 in other books
Rating: 4
Questions on Locking March 21, 2002
Reviewer: A reader from India
Dear Tom,

Your queries to identify waiting sessions was really very useful.

Can we find out for how long a transaction is locked or alternatively for how
long a a session is waiting to acquire a lock.

Thanks
Followup:
You can see how long its been waiting by looking at the last_call_et column in
v$session (if timed statistics is on this is updated frequently, else not so
frequently)
Rating: 3
April 02, 2002
Reviewer: A reader from India
What is the overhead of having Timed_Statistics on.
Does this make the system very slow.

last_column_et has some number. Does this mean time elapsed in seconds since
this query is running.

If timed_statistics is not on then will this give wrong information.Does this
gets reset everytime a new query is tun.

Thanks
Followup:
It is very very very low.  The general feeling is less then 5%.

I run all of my systems with it on as I find the value it provides far outweighs
any overhead it might be percieved to have.

I believe you mean last_CALL_et -- its the time in seconds of the last call to
the database.  If the session is active -- its the amount of time spent
processing the last statement.  If inactive -- its a measure of the idle time.
Only valid when timed_stats is on

It gets reset with every new "call" to the database.
Rating: 5
July 26, 2002
Reviewer: A reader
Tom,

I want to know sql statements associated with one transaction. How can i know
that?

Can I join v$transaction,v$session and v$sqlarea ?

 Like
select c.name, b.username,b.osuser,a.ses_addr,b.saddr, xidusn,xidslot,
xidsqn,used_ublk,a.start_time,d.sql_text
from v$transaction a,v$session b,v$rollname C,v$sqlarea d
where a.addr    = b.taddr
  and b.saddr   = a.ses_Addr
  and a.xidusn  = c.usn
  and v.sql_address = d.address

Thanks,
Followup:
You can only know the current statement, the other statements might not even be
there anymore (in the shared pool)

search this site for showsql.sql -- it does that.
Rating: 5
Locks July 29, 2002
Reviewer: suresh from chennai
Exclusive Locks - row level as well as table level

Shared Locks - Table level only.

Is it Correct?

Thanks for your help
Followup:
the locks are all table level entries actually.  You will not see individual row
level locks in v$lock.

Yes, Oracle has no shared row lock.  Not necessary.
Rating: 5
Questions on Locking July 30, 2002
Reviewer: Bharath from India
Hi Tom,

Thanks for all the answers that you provide.

1. One of our report populates a temporary table before it starts running and
the same is deleted after the report is run. The order of
manipulations/selection on the table is as follows

     1. Insert
     2. Select
     3. Select
     3. Update/Insert
     4. Delete
     5. Commit

When the report is run more than once at the same time then it takes lot of time
to get completed. When querying DBA_DML_LOCKS, we see lots of exclusive locks on
this table(which is fine, because there are lot of DML operations happening on
the table) which is 'Not Blocking'. But the problem is the locks are held for
quite some time which does not happen when only once the report is run. But when
I add COMMIT after each DML operation, the report runs normally. What is the
reason behind this and how often should we COMMIT and when should we SELECT?

2. There is also another report, which also has temporary tables. Once I got a
call saying that the report is running slowly. I ran the report after enabling
the TRACE. The trace file was generated in a couple of minutes but the report
took more that 2-3 hours to complete. Later I found that the report was taking
time because of high watermark on the temporary table. My question is, how was
the TRACE file generation was completed in a couple of minutes.

Thanks,
Bharath
Followup:
1) not clear from what you describe as to what is happening here.  The DML are
all concurrent so committing won't affect anything at all.  In fact, all the
commit will do is let others see the data of others -- probably a bad idea.

I can say, you should be using a GLOBAL TEMPORARY TABLE and skipping the delete
all together.

2) use global temporary tables and your problem will go away.
Rating: 3
Which Rows are locked by a session August 29, 2002
Reviewer: Vikas Khanna from india
Hi,

I am doing an exercise to prove that Oracle never escalates locks and for this,
I created a one million row table. Opened one session and started updating the
rows in batches of 10. What I observed is that in the V$lock table the sid for
that session has created an Exclusive Row Lock and a table share lock. But if in
the same session I am updating them in batches, should it show me Row locks as
new rows, as I keep on getting these two rows as the only rows for that session.

How could I have this information that what rows are locked by a session. Which
is the V$ table from which this can be obtained.

Is there a limit for the Lock Manager for the no. of locks to handle? I think a
big no. but my superiors have differences with me on this and they compare this
with SQl Server escalation from Row to Page and then to Table.

A detailed explanation would be appreciated.

Thanks
Followup:
If you have my book -- "expert one on one" i go into this.

We do NOT have a table of locks.  That would be a serious scalability limiter
and ram eater (we are not DB2, we are not SQLserver).

The lock is stored as an attribute of the data...

Tell you what -- to do your test, do this:

create table t ( x int primary key );
insert into t 1,000,000 (or whatever) rows with x = 1, 2, 3, .....

update t set x = x=1 where x <=  999,999;

and in another session do:

update t set x = x+1 where x = 1000000;

WE DO NOT HAVE A LOCK MANAGER -- they are the folly of other RDBMS.  We have
no such concept.

That is the downfall of SQLServer -- they have to have a serialized, limiting,
scalability inhibiting lock manager.  We have none.

You are trying to compare apples to flying toaster ovens (remember that screen
saver?)

Apples are good, flying toasters -- interesting but not much use ;)
Rating: 3
Which Rows are locked by a session August 29, 2002
Reviewer: Vikas Khanna from india
Hi,

I am doing an exercise to prove that Oracle never escalates locks and for this,
I created a one million row table. Opened one session and started updating the
rows in batches of 10. What I observed is that in the V$lock table the sid for
that session has created an Exclusive Row Lock and a table share lock. But if in
the same session I am updating them in batches, should it show me Row locks as
new rows, as I keep on getting these two rows as the only rows for that session.

How could I have this information that what rows are locked by a session. Which
is the V$ table from which this can be obtained.

Is there a limit for the Lock Manager for the no. of locks to handle? I think a
big no. but my superiors have differences with me on this and they compare this
with SQl Server escalation from Row to Page and then to Table.

A detailed explanation would be appreciated.

Thanks
Rating: 4
Locking Concepts August 30, 2002
Reviewer: Vikas Khanna from India
Tom.Excellent Reply. I do not think these toasters would ever become apples in
real life.

But I am curious to know details of this process :
1) When I am locking a table's row two entries are shown to me in V$lock.
Another row lock of the same table does not show be another entry related to row
Exlusive lock.
What I need to know is where does Oracle store this information that this user
has locked so many rows for this table.

2) If by any means I need to know, that a particular row is locked by whom where
could I read this information from. Is there any Virtual table or it is in the
X$ base tables of Oracle.

3) Meaning of this statement 'The lock is stored as an attribute of the data...'
in detail

Curiosity to know the answers to these questions before this settles for the
time being.
Followup:
I'll refer you to a book that goes through this in some detail -- "Expert one on
one Oracle" (my book).  I spend quite a few pages going over topics like this in
detail.

In brief

1) we do NOT store the nitty gritty details in some lock table -- that is the
beauty of this implementation.  The "locks" are attributes of the data itself.
The "locks" you are looking for are just information in the block header (the
ITL portion to be specific - a transaction entry).  We need not mark down
somewhere that you have 999,999 row locks.  That is specifically why we can
support an infinite number of locks (whereas the others -- cannot).  Locks are
not a scarce resource in Oracle as they are in other databases.

2) That information is quite simply not recorded anywhere.  If you have
someone being blocked by someone else -- I can tell you who is blocking who but
you can only look at a row and say "it is not locked" or "it is locked" (using
select FOR UPDATE NOWAIT).  You cannot say by "whom" (unless you get blocked --
then you can)

3) read my book -- don't know how to say it briefly here other then the lock is
just part of the block data, it is always there, it is never there.  It is just
an attribute of the data.
Rating: 3
Explicit locking November 05, 2002
Reviewer: Praveen from Bangalore, India
Hi Tom,

Please help me to clear a couple of doubts..

1) Is it possible to obtain a 'table-level' exclusive lock?
For ex: I perform an UPDATE like this:

UPDATE Table1 SET field1 = value1;

This statement is going to update all rows in the table.

The question is _

Whether Oracle will first attempt a SELECT operation to determine which rows are
going to be updated and issue row-level X-lock  ?
OR
Will it obtain a 'table-level' exclusive lock immediately?

2) Here I came across "Two Important Reasons" for using explicit locking:

>> Raising error with NOWAIT option to prevent waiting for unspecified time for
other transactions to be commited;

>> Locking ALL the table in EXCLUSIVE mode to eliminate overhead of MULTIPLE
consecutive row-level locks during bulk operations;

How can we relate this to
>> "Transaction level read consistency" and
>> "Exclusive access to resources without waiting"

These are the two 'useful situation to override default locking' sited by the
oracle doc?

3) Could you please provide us a few more similar examples so that we can decide
when to go for exclusive locking?

Thanks and regards

Praveen
Followup:
1) it would put a lock at the table level saying in effect "there are rows
locked in this table".  It would not lock the table in exclusive mode (as there
could be rows in the table that will not be updated!!!!  How so you say?
Someone inserted 50 rows -- did not commit -- you issue your update in another
session, it'll NEVER update those 50 outstanding rows).

2) where is "here".  I don't understand either of those two statements really.

First -- you typically use the select for update statement in order to avoid the
concurrency problem known as "LOST UPDATE".  You can put a nowait on there in
order to avoid waiting for long periods (eg: I want to report back to the user
that "hey, the row is locked right now").  The fact that you are doing explicit
locking ISN'T to avoid waiting (thats a side effect).  You are doing explicit
locking to avoid LOST UPDATES (if you don't know what they are -- my book
"Expert one on one Oracle" goes into that in great detail)

The second one might apply to Informix or DB2 or SQLServer (any flavor -- the MS
or Sybase one).  To Oracle -- nope.  We do not have an external lock manager
like they do, we do not have their inherit overhead.  There is no overhead here
(and in fact, if you do a lock table + row level operations we do the same work
as if you just do row level operations).

I would need to have locations in the documentation to find the statements to
which you refer in order to read them in context.  I don't comment on 5 word
snippets out of context.

3) do you mean EXPLICIT?  If so, I'll refer you to my book again.  I covered
this topic in great detail.
Rating: 3
Re: Follow up: Explicit Locking November 06, 2002
Reviewer: Praveen from India
Hi Tom,

Thankyou very much for the quick answer. I think I need to brush up my knowledge
more thoroughly, because your answer (regarding explicit locking) infact arises
quite a few number of questions.

Thanks once again

Praveen
Rating: 5
How do I find out ITL Waits December 04, 2002
Reviewer: A reader from USA
Excellent thread and dicussion, Tom.

You mentioned here the ITL section of the data block. How do I know whether
sessions are waiting for the ITL since the ITL section can't grow due to no
space, or MAXTRANS is defined very low? Is there a row in V$SYSSTAT,
V$SESSION_WAIT or V$SESSTAT views, somewhere?

Thanks a lot in advance.
Followup:
If there is no free 'ITL' then the requesting session will wait on one
of the active transaction locks in mode 4.

you can see this by:

create table t ( x int ) maxtrans 2;
insert into t values ( 1 );
insert into t values ( 2 );
insert into t values ( 3 );
commit;
update t set x = 1 where x = 1;

and in another session "update t set x = 2 where x = 2"
and then in a third, "update t set x = 3 where x = 3"

that third guy will get hit -- you can query:

       select SID,TYPE,ID1,ID2,LMODE,REQUEST from v$lock where type='TX';

    SID        TY ID1        ID2        LMODE      REQUEST
    ---------- -- ---------- ---------- ---------- ----------
             8 TX     327688         48          6          0
            10 TX     327688         48          0          4

This shows SID 10 is waiting for the TX lock held by SID 8 and it
wants the lock in share mode (as REQUEST=4).

One way to see this is by seeing large enqueue waits in v$system_event.
Rating: 4
Lock Appearing While Referring a Database Link December 04, 2002
Reviewer: Baiju from India
Hello Tom,

    When ever I refer a Database link in a query , i see a TX lock appearing in
the v$lock for that session. Even if it is a SELECT statement also i see this
lock. But if a simply start my session as READ ONLY and issue the same query , I
don't see this lock...
   Whether the lock is on the Database link object itself ?

Baiju
Followup:
TX are just transaction entries -- they aren't locking anything.

It just means you've begun a transaction -- waiting for commit or rollback.  Tis
normal in a distributed situation - the query coordinator may well as you to
prepare to commit -- even if you've done no work (cause the QC doesn't know you
did no work).  So, you need a transaction.
Rating: 5
ITL Waits December 05, 2002
Reviewer: Arup Nanda from Norwalk, CT, USA
Superb response to the original question in this thread and look how much it has
expanded!

In as much as I try not to step over Tom's feet, I want to share another tip
with the reviewer asking about ITL Waits. If you are lucky enough to be using 9i
Release 2 (only release 2, not 1), you can enable segment level stats collection
by setting STATISTIC_LEVEL = TYPICAL and then issuing the following query

SELECT OWNER, OBJECT_NAME, VALUE
FROM V$SEGMENT_STATISTICS
WHERE STATISITC_NAME = 'ITL wait'
AND VALUE > 0

This will immediately tell you all the segments that experienced ITL waits since
the instance startup. Unfortunately this is only for 9iR2. Helps immensly.
Rating: 5
OWB sessions lock to each other January 16, 2003
Reviewer: Sean from GA
Tom,
I have been using OWB9iR2 to generate ETL. As ETL been executed, and never come
back. I have used your script to find out what's going on:

Could you tell me why OWB sessions locked? Who is the blocker?

owb_repos@db99> select username,
  2           v$lock.sid,
  3           trunc(id1/power(2,16)) rbs,
  4           bitand(id1,to_number('ffff','xxxx'))+0 slot,
  5           id2 seq,
  6           lmode,
  7           request
  8    from v$lock, v$session
  9    where v$lock.type in ( 'TX','TM')
 10      and v$lock.sid = v$session.sid
 11      and v$session.username = 'OWB_REPOS'
 12  /

Thu Jan 16

                                                           User Sessions

ORACLE
USERNAME     SID       RBS      SLOT       SEQ     LMODE   REQUEST
---------- ----- --------- --------- --------- --------- ---------
OWB_REPOS     17         4         2      8476         6         0
OWB_REPOS     17         0     53323         0         0         2
OWB_REPOS     21         2         9      9584         6         0
OWB_REPOS     21         0     53350         0         2         0
OWB_REPOS     21         0     53323         0         6         0
OWB_REPOS     22         3        77     13712         6         0
OWB_REPOS     22         0     53323         0         0         2
OWB_REPOS     25         2        71      9586         6         0
OWB_REPOS     25         0     53323         0         0         2
OWB_REPOS     29         4        71      8484         6         0
OWB_REPOS     29         0     53323         0         0         2
OWB_REPOS     32         3        65     13711         6         0
OWB_REPOS     32         0     53323         0         0         2

13 rows selected.

owb_repos@db99>
owb_repos@db99> select XIDUSN, XIDSLOT, XIDSQN
  2      from v$transaction
  3  /

Thu Jan 16

                                                           User Sessions

   XIDUSN   XIDSLOT    XIDSQN
--------- --------- ---------
        4         2      8476
        2         9      9584
        2        71      9586
        4        71      8484
        3        65     13711
        3        77     13712

6 rows selected.

owb_repos@db99>
owb_repos@db99> @vlock

Thu Jan 16

                                                           User Sessions

ADDR     KADDR      SID TY       ID1       ID2     LMODE   REQUEST     CTIME
BLOCK
-------- -------- ----- -- --------- --------- --------- --------- ---------
---------
23B533F8 23B5340C    17 TM     53323         0         0         2      1690
    0
23B53374 23B53388    22 TM     53323         0         0         2      1690
    0
23B53584 23B53598    25 TM     53323         0         0         2      1690
    0
23B53500 23B53514    29 TM     53323         0         0         2      1690
    0
23B5347C 23B53490    32 TM     53323         0         0         2      1690
    0

And the following shows each session doing what:

session 17,
22,
25,
29,
32,

they are doing:

INSERT /*+ APPEND */ INTO
"DSS02"."WORK_ORDER_02"("AGREEMENT_SID","WORK_ORDER_NB","ALLOCATION_RULE_C
_OPERATION_NB","CURRENT_OPERATION_ACTIVE_DT","WORK_ORDER_PRIORITY_CD","TOTAL_ACTU
AL_MOVE_HR","TOTAL_
ORK_HR","TOTAL_ACTUAL_SETUP_HR","WORK_ORDER_TRACKING_CD","PART_FIRST_RCVD_DT")
SELECT C0,C1,C2,C3,C4
 :Q62000
SQL*Plus
2003-01-16/19:54:17

But session 21:

INSERT       /*+ APPEND PARALLEL("WORK_ORDER_02", DEFAULT, DEFAULT)*/       INTO
        "WORK_ORDER
_02"         ("AGREEMENT_SID",         "WORK_ORDER_NB",
"ALLOCATION_RULE_CD",         "WORK_
ORDER_TYPE_CD",         "CURRENT_OPERATION_NB",
"CURRENT_OPERATION_ACTIVE_DT",         "WORK
_ORDER_PRIORITY_CD",         "TOTAL_ACTUAL_MOVE_HR",
"TOTAL_ACTUAL_RUN_HR",         "TOTAL_A
CTUAL_REWORK_HR",         "TOTAL_ACTUAL_SETUP_HR",
"WORK_ORDER_TRACKING_CD",         "PART_F
IRST_RCVD_DT")         (SELECT         /*+ NO_MERGE*/
"AGREEMENT_SEQ_02".NEXTVAL "NEXTVAL"
,           "ORD_TABLE_WDS01_MRPD"."WO_NBR" "WO_NBR",
TRIM("ORD_TABLE_WDS01_MRPD"."ALLOC_C
TRL") "Result",           TRIM("ORD_TABLE_WDS01_MRPD"."TYPE") "Result$0",
   TRIM("ORD_TABLE_
WDS01_MRPD"."HIGH_OPN") "Result$1",
"ORD_TABLE_WDS01_MRPD"."HI_OPN_DATE" "HI_OPN_DATE",
        TO_CHAR("ORD_TABLE_WDS01_MRPD"."PRI") "Result$2",
"ORD_TABLE_WDS01_MRPD"."ACT_HRS

Looks to me the session21 blocking all of the rest?
Rating: 5
January 16, 2003
Reviewer: Sean from GA
By the way, I am sure it is 100% dead lock, because the logical and physical
reads for all the sessions have never been changing over the loong time.
Followup:
and i'm 100% sure it is not a deadlock as we automagically detect those and
rectify them.

I'm 100% sure it is a "lock" if it is anything.

Anyway - use utllockt (in $ORACLE_HOME/rdbms/admin) to print out a
blocker/waiter graph to verify your random queries there -- to see who it is.
then, you can use my showsql.sql script (just search for that) to see what each
session is actually running
Rating: 5
January 16, 2003
Reviewer: Sean from GA
FYI,
I only started one OWB_REPOS session, it spurn all the rest of OWB_REPOS's
Rating: 5
January 16, 2003
Reviewer: SEan from GA
Here is the UTLLOCKT output

-------------------------

WAITING_SESSION
---------------------------------------------------------------------------------
-------------------
LOCK_TYPE                  MODE_REQUESTED MODE_HELD      LOCK_ID1
LOCK_ID2
-------------------------- -------------- -------------- ----------------------
--------------------
35
None

   17
DML                        Row-S (SS)     Exclusive      53323
0

   21
DML                        Row-S (SS)     Exclusive      53323
0

   22
DML                        Row-S (SS)     Exclusive      53323
0

   32
DML                        Row-S (SS)     Exclusive      53323
0

   36
DML                        Row-S (SS)     Exclusive      53323
0

6 rows selected.

Which shows session 35 blocks all the rest.

SEssion 35 doing?
Question for Tom, what's the 35 waits for??????

BUSZ@> @showsql_uid
DOC>  show sql syntax
DOC>  Sanny Subowo, Oracle Corporation, 4/16/99
DOC>#
Enter value for usrsid: 35
old   4: c.sid = '&usrsid'
new   4: c.sid = '35'

Thu Jan 16

                                                           User Sessions

SQL_TEXT
---------------------------------------------------------------------------------
-------------------
MODULE                                                           FIRST_LOAD_TIME
----------------------------------------------------------------
-------------------
INSERT     /*+ APPEND PARALLEL("WORK_ORDER_02", DEFAULT, DEFAULT)*/     INTO
  "WORK_ORDER_02"       ("AGREEM
RK_ORDER_NB",       "ALLOCATION_RULE_CD",       "WORK_ORDER_TYPE_CD",
"CURRENT_OPERATION_NB",       "CURRENT
DT",       "WORK_ORDER_PRIORITY_CD",       "TOTAL_ACTUAL_MOVE_HR",
"TOTAL_ACTUAL_RUN_HR",       "TOTAL_AC
  "TOTAL_ACTUAL_SETUP_HR",       "COST_METHOD_TYPE_CD",
"PART_FIRST_RCVD_DT")       (SELECT       /*+ NO_MERG
EMENT_SEQ_02".NEXTVAL "NEXTVAL",         "ORD_TABLE_WDS01_MRPD"."WO_NBR"
"WO_NBR",         "ORD_TABLE_WDS01_MRPD
C_CTRL",         "ORD_TABLE_WDS01_MRPD"."TYPE" "TYPE",
"ORD_TABLE_WDS01_MRPD"."HIGH_OPN" "HIGH_OPN",
1_MRPD"."HI_OPN_DATE" "HI_OPN_DATE",         "ORD_TABLE_WDS01_MRPD"."PRI" "PRI",
        "ORD_TABLE_WDS01_MRPD
HRS_MOVE",         "ORD_TABLE_WDS01_MRPD"."ACT_HRS_RUN" "ACT_HRS_RUN",
SQL*Plus
2003-01-16/21:05:29

ANd the rest of the blocked sessiona are all doing:

Thu Jan 16

                                                           User Sessions

SQL_TEXT
---------------------------------------------------------------------------------
-------------------
MODULE                                                           FIRST_LOAD_TIME
----------------------------------------------------------------
-------------------
INSERT /*+ APPEND */ INTO
"DSS02"."WORK_ORDER_02"("AGREEMENT_SID","WORK_ORDER_NB","ALLOCATION_RULE_C
_OPERATION_NB","CURRENT_OPERATION_ACTIVE_DT","WORK_ORDER_PRIORITY_CD","TOTAL_ACTU
AL_MOVE_HR","TOTAL_
ORK_HR","TOTAL_ACTUAL_SETUP_HR","COST_METHOD_TYPE_CD","PART_FIRST_RCVD_DT")
SELECT C0,C1,C2,C3,C4,C5
63000
SQL*Plus
2003-01-16/21:05:30

The ETL extract data from the remote database, and there is no shown any locks
overthere (checked with utllockt.sql)

the remote session doing:

SQL_TEXT
---------------------------------------------------------------------------------
-------------------
MODULE                                                           FIRST_LOAD_TIME
----------------------------------------------------------------
-------------------
SELECT
"WO_NBR","TYPE","HAS_TRK","ALLOC_CTRL","HIGH_OPN","HI_OPN_DATE","PRI","ACT_HRS_MO
VE","ACT_HRS
_RUN","ACT_HRS_RW","ACT_HRS_SU","DATE_FST_RCV" FROM "ORD_TABLE"
"ORD_TABLE_WDS01_MRPD"

2003-01-16/19:54:17
Followup:
only one session at a time can insert append into a table.  the rest are waiting
for the first guy to finish.
Rating: 5
January 16, 2003
Reviewer: Sean from GA
Tom,
The following is the ETL code generated by OWB9iR2:

CREATE OR REPLACE PACKAGE "WORK_ORDER_01" AS
-- Status variable for Batch cursors
"WORK_ORDER_02_St" BOOLEAN;

-- Procedure Main -- Entry point in package "WORK_ORDER_01"
PROCEDURE Main;

END "WORK_ORDER_01";

/

CREATE OR REPLACE PACKAGE BODY "WORK_ORDER_01" AS

---------------------------------------------------------------------------
-- Function "WORK_ORDER_02_Bat"
--   performs batch extraction
--   Returns TRUE on success
--   Returns FALSE on failure
---------------------------------------------------------------------------
FUNCTION "WORK_ORDER_02_Bat"   RETURN BOOLEAN IS

BEGIN
  EXECUTE IMMEDIATE 'ALTER SESSION ENABLE PARALLEL DML';

  BEGIN

    INSERT
    /*+ APPEND PARALLEL("WORK_ORDER_02", DEFAULT, DEFAULT)*/
    INTO
      "WORK_ORDER_02"
      ("AGREEMENT_SID",
      "WORK_ORDER_NB",
      "ALLOCATION_RULE_CD",
      "WORK_ORDER_TYPE_CD",
      "CURRENT_OPERATION_NB",
      "CURRENT_OPERATION_ACTIVE_DT",
      "WORK_ORDER_PRIORITY_CD",
      "TOTAL_ACTUAL_MOVE_HR",
      "TOTAL_ACTUAL_RUN_HR",
      "TOTAL_ACTUAL_REWORK_HR",
      "TOTAL_ACTUAL_SETUP_HR",
      "COST_METHOD_TYPE_CD",
      "PART_FIRST_RCVD_DT")
      (SELECT
      /*+ NO_MERGE*/
        "AGREEMENT_SEQ_02".NEXTVAL "NEXTVAL",
        "ORD_TABLE_WDS01_MRPD"."WO_NBR" "WO_NBR",
        "ORD_TABLE_WDS01_MRPD"."ALLOC_CTRL" "ALLOC_CTRL",
        "ORD_TABLE_WDS01_MRPD"."TYPE" "TYPE",
        "ORD_TABLE_WDS01_MRPD"."HIGH_OPN" "HIGH_OPN",
        "ORD_TABLE_WDS01_MRPD"."HI_OPN_DATE" "HI_OPN_DATE",
        "ORD_TABLE_WDS01_MRPD"."PRI" "PRI",
        "ORD_TABLE_WDS01_MRPD"."ACT_HRS_MOVE" "ACT_HRS_MOVE",
        "ORD_TABLE_WDS01_MRPD"."ACT_HRS_RUN" "ACT_HRS_RUN",
        "ORD_TABLE_WDS01_MRPD"."ACT_HRS_RW" "ACT_HRS_RW",
        "ORD_TABLE_WDS01_MRPD"."ACT_HRS_SU" "ACT_HRS_SU",
        "ORD_TABLE_WDS01_MRPD"."HAS_TRK" "HAS_TRK",
        "ORD_TABLE_WDS01_MRPD"."DATE_FST_RCV" "DATE_FST_RCV"
      FROM
        "ORD_TABLE"@"WDS01_MRPD" "ORD_TABLE_WDS01_MRPD"
      );
    COMMIT;
  EXCEPTION WHEN OTHERS THEN
    ROLLBACK;
    COMMIT;
    RETURN FALSE;
  END;
  COMMIT;
  RETURN TRUE;
END "WORK_ORDER_02_Bat" ;

PROCEDURE Main IS
get_batch_status           BOOLEAN := TRUE;
BEGIN

  -- Initialize all batch status variables
  "WORK_ORDER_02_St" := FALSE;

  "WORK_ORDER_02_St" := "WORK_ORDER_02_Bat";

END Main;
END "WORK_ORDER_01";

/
Rating: 5
January 16, 2003
Reviewer: Sean from GA
The following query, which is in the ETL returns immediately by OWB_REPOS

SELECT
      /*+ NO_MERGE*/
        "AGREEMENT_SEQ_02".NEXTVAL "NEXTVAL",
        "ORD_TABLE_WDS01_MRPD"."WO_NBR" "WO_NBR",
        "ORD_TABLE_WDS01_MRPD"."ALLOC_CTRL" "ALLOC_CTRL",
        "ORD_TABLE_WDS01_MRPD"."TYPE" "TYPE",
        "ORD_TABLE_WDS01_MRPD"."HIGH_OPN" "HIGH_OPN",
        "ORD_TABLE_WDS01_MRPD"."HI_OPN_DATE" "HI_OPN_DATE",
        "ORD_TABLE_WDS01_MRPD"."PRI" "PRI",
        "ORD_TABLE_WDS01_MRPD"."ACT_HRS_MOVE" "ACT_HRS_MOVE",
        "ORD_TABLE_WDS01_MRPD"."ACT_HRS_RUN" "ACT_HRS_RUN",
        "ORD_TABLE_WDS01_MRPD"."ACT_HRS_RW" "ACT_HRS_RW",
        "ORD_TABLE_WDS01_MRPD"."ACT_HRS_SU" "ACT_HRS_SU",
        "ORD_TABLE_WDS01_MRPD"."HAS_TRK" "HAS_TRK",
        "ORD_TABLE_WDS01_MRPD"."DATE_FST_RCV" "DATE_FST_RCV"
      FROM
        "ORD_TABLE"@"WDS01_MRPD" "ORD_TABLE_WDS01_MRPD"
      ;
Rating: 5
New founding. January 16, 2003
Reviewer: SEAN from GA
There seams the enqueue blocked by 35:

owb_repos@db99> l
  1  select event, sid, seq#,
  2         wait_time,
  3         seconds_in_wait,
  4  /*     state,
  5         p1text, p1, p1raw,
  6         p2text, p2, p2raw,
  7         p3text, p3, p3raw
  8         p1text || ' = ' || p1 parm1,
  9         p2text || ' = ' || p2 parm2,
 10         p3text || ' = ' || p3 parm3
 11  */
 12         decode( p1text, null, null,
 13                 p1text || ' = ' || p1 ) ||
 14         decode( p2text, null, null,
 15                 ', ' || p2text || ' = ' || p2 ) ||
 16         decode( p3text, null, null,
 17                 ', ' || p3text || ' = ' || p3 )
 18         parameters
 19    from v$session_wait
 20   where event not in ( 'pmon timer', 'rdbms ipc message', 'smon timer',
 21                        'WMON goes to sleep', 'Null event',
 22                        'SQL*Net message from client' )
 23*  order by event, p1, p2
owb_repos@db99>

Thu Jan 16

                                                           Session Waits

                                           WAIT SECONDS
EVENT                           SID    SEQ TIME IN WAIT PARAMETERS
----------------------------- ----- ------ ---- -------
--------------------------------------------
PX Deq Credit: send blkd         35   2877            3 sleeptime/senderid =
268500993, passes = 2563, qref = 0
enqueue                          17   1712            3 name|mode = 1414332418,
id1 = 53323, id2 = 0
                                 21   1711            3 name|mode = 1414332418,
id1 = 53323, id2 = 0
                                 32   1711            3 name|mode = 1414332418,
id1 = 53323, id2 = 0
                                 22   1712            3 name|mode = 1414332418,
id1 = 53323, id2 = 0
                                 36   1709            3 name|mode = 1414332418,
id1 = 53323, id2 = 0

HOW CAN WE RESOLVE IT?!

THANKS IN ADVANCE.
Rating: 5
Becuase of the Parallel inserts! the #35 parallel processs blocks others? January 16, 2003
Reviewer: Sean from Ga
Followup:
yes, ONE session at a time can direct path insert into a table.
Rating: 4
Still I have a puzzle in mind... January 17, 2003
Reviewer: Shailesh from India
Thank you very much for such a nice discription. Just came across similar
Locking situation..

Executed few statements WITHOUT ISSUING COMMIT following way...

Session 1 :
------------
Update Table1 set column1 ='123' where Column1 = 4;

Update Table2 set column1 ='ABC' where Column1 = 'XYZ';

Session 2 :
--------------
Update Table1 set column1 ='123' where Column1 = 4;

Session 3 :
---------------
Update Table2 set column1 ='ABC' where Column1 = 'XYZ';

When tried to search which row has locked which other session.

SELECT B.OSUSER USERNAME, C.SID SID, C.OBJECT OBJECT, B.LOCKWAIT, A.SQL_TEXT SQL

FROM V$SQLTEXT A, V$SESSION B, V$ACCESS C
WHERE A.ADDRESS=B.SQL_ADDRESS AND
A.HASH_VALUE=B.SQL_HASH_VALUE AND
B.SID = C.SID AND C.OWNER != 'SYS'
AND SID IN (1,2,3);

I see..

Sid             SQL
------ -----------------------------------------------
1         Update Table2 set column1 ='ABC' where Column1 = 'XYZ';

2         Update Table1 set column1 ='123' where Column1 = 4;

3         Update Table2 set column1 ='ABC' where Column1 = 'XYZ';

session 2 is locked by first statement executed by session 1.

But I can't see which row is locked by which row, rather than session. Because
in above case it shows me that Session 1 has EXCLUSIVE LOCK for Table2 update
statement, Then why session 2 is waiting for?

Can I get All statements executed by a particular session. I tried this but
doesn't give required output.

SELECT * FROM V$OPEN_CURSOR WHERE SID = 1;

Thanks & Regards,

Shailesh
Followup:
we do not keep a list of row locks anywhere - that would be inheritly
non-scalable and put a limit on the number of locks.

Transactions get blocked by other transactions.  What you see is that ssession 2
and 3 are blocked by session 1, that is all.  You can see the sql statments that
sessions 2 and 3 are waiting to execute and that is all.

You cannot find all the statements for a session they
o may close cursors
o have their sql aged out
o etc
Rating: 5
Does commit release all locks before returning? January 20, 2003
Reviewer: Sam from San Mateo, CA
Tom,

We have two database sessions that are processing the same set of rows (our
client PL/SQL program runs in one session, and communicates with some
transaction manager written in Pro*C running in some other session thru
dbms_pipe).

The client program sends a request to the transaction manager, and the
transaction manager will start processing (insert/update a set of rows).  At the
end, the transaction manager will commit or rollback and return control to the
calling program.

The calling program will then lock the same set of rows just processed by doing
a SELECT...FOR UPDATE NOWAIT.

We noticed that this is returning a "resource busy and nowait specified" error.
If we remove the NOWAIT from the SELECT...FOR UPDATE, the 10046 level 8 trace
shows a wait of about 1 second on a TX lock.

Does commit releases all the locks before returning control to the calling
procedure?  Or does some background process have to clean up the locks?  Any
ideas on what might be happening?  We are pretty sure the client program and the
transaction manager are the only two sessions modifying the set of rows.
Followup:
A TX lock is the transaction itself -- and a commit would wipe that out.

"pretty sure" or 100% sure?
Rating: 4
Insert blocked ?! January 22, 2003
Reviewer: Robert from Memphis, USA
Tom,

I have been digging into lock investigation using a combination of scripts and
ideas from your book, your website, Metalink, Oracle Internal Services by Steve
Adams, etc. I think I have learned some useful things.

But I have a baffling situation...
We have a situation with our 3rd party app, where INSERT statements are being
blocked. These appear to be just plain old insert statements. The blocker is
blocking them with a lock_mode/type of 6/TX. The waiters are waiting with a
lock_mode/type of 4/TX.

What could the blocker possibly be doing to block an insert statement?

Thanks,

Robert.
Followup:
o look for unindexed foreign keys
o unique/primary key constraints (both inserting same value)

eg:

ops$tkyte@ORA817DEV> create table p ( x int primary key );
Table created.

ops$tkyte@ORA817DEV> create table c ( x references p );
Table created.

ops$tkyte@ORA817DEV> insert into p values ( 1 );
1 row created.

ops$tkyte@ORA817DEV> insert into p values ( 2 );
1 row created.

ops$tkyte@ORA817DEV> insert into c values ( 2 );
1 row created.

ops$tkyte@ORA817DEV> commit;
Commit complete.

ops$tkyte@ORA817DEV> update p set x = 1 where x = 1;
1 row updated.

that'll lock the child table in 8i and below...

ops$tkyte@ORA817DEV> insert into p values ( 3 );
1 row created.

that'll block anyone from inserting into p the value 3

ops$tkyte@ORA817DEV> select SID, TYPE, ID1, ID2, LMODE,
  2        (select object_name from user_objects where object_id=id1) oname,
  3             block
  4    from v$lock
  5   where sid = (select sid from v$mystat where rownum=1 )
  6   or block = 1;

       SID TY        ID1        ID2      LMODE ONAME           BLOCK
---------- -- ---------- ---------- ---------- ---------- ----------
        10 TM      51210          0          3 P                   0
        10 TM      51212          0          4 C                   0
        10 TX     262198       5165          6                     0

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> set echo off
either run:
insert into c values(2)
insert into p values(3)
in another session and then hit / again here...

this is after trying to insert into C:

ops$tkyte@ORA817DEV> /

       SID TY        ID1        ID2      LMODE ONAME           BLOCK
---------- -- ---------- ---------- ---------- ---------- ----------
        10 TM      51210          0          3 P                   0
        10 TM      51212          0          4 C                   1
        10 TX     262198       5165          6                     0

and then I ctl-c'ed and tried to insert into P

ops$tkyte@ORA817DEV> /

       SID TY        ID1        ID2      LMODE ONAME           BLOCK
---------- -- ---------- ---------- ---------- ---------- ----------
        10 TM      51210          0          3 P                   0
        10 TM      51212          0          4 C                   0
        10 TX     262198       5165          6                     1
Rating: 3
More Locking ... Row or Block ? January 23, 2003
Reviewer: Christophe Parmentier from France
Hi, got some more strange behaviours with locks.

We have 2 or more parallele processes working 2 tables but NOT on the same rows,
they both make insert/delete and sometimes it looks like the row locking do
block locking (we got a lot of rows...):

Process 1:
Insert Into T1 select from T3 where p = 1;
Insert Into T2 select from T3 where p = 1 and q = 0;
Commit;
Delete from T2 where p = 1;
Delete from T1 where p = 1;
Commit;

The Process 2 runs meanwhile doing the same with p = 2 and so on for others
process...

We got ORA-02049: timeout: distributed transaction waiting for lock error.

It looks like process 1 and 2 cause a deadlock cause we don't insert/delete in
the same order and that some rows that should not be locked ( different p ) are
in fact block-locked... (Only explanation i get)

I've already been reordering the delete statements and removed the bitmap
indexes (we heard that could be the problem in 8.1.7) but that look wired to me
:)

If you have some explanations, i'm curious.

Thanks a lot and Regards.
Followup:
t1, t2, t3 -- same machine, different machines what?

bitmaps!!!  you cannot have bitmaps on tables that you do operations like that
on.  No way.  get rid of them.  it is not 817, 80, 9i or version 55c -- bitmaps
and modifications like that are like oil and water.
Rating: 4
Additional informations... January 23, 2003
Reviewer: Christophe Parmentier from France
T1 and T2 are on a database and T3 on another instance... same machine, acces
through DB Link.

We've removed the Bitmap Indexes and i wanted to know if this was enough.
Followup:
It was probably the bitmaps -- but it could be ANY OTHER concurrently executing
process if there were any.
Rating: 4
Again... January 23, 2003
Reviewer: Christophe Parmentier from France
Just parallel processeses working on the T1 and T2 But on different rows (A
lot!)

We have a dbms_lock that prevent parallel processes from running the same "p"
parameters...

That was strange.

Thanks anyway.
Rating: 4
Locking behavior April 22, 2003
Reviewer: Kamal from India, Mumbai
Hi,

It was a real good experience to gain good knowledge like this.
I have a strange doubt/problem. We have a table structure whereby TABLE2 is
referecing PK of TABEL1. i.e. TABLE1 is PARENT table and TABLE2 is CHILD table.
We have noticed that when we delete some record from CHILD table, TABLE2, I
could see TABLE1 is getting locked in RS (row share) mode. and above all, we
could not perform any DML on TABLE1, parent table. We were not having index on
FK in child table. but that limitation of locking CHILD table is there when we
perform any DML on parent table. This is almost reverse behavior. When we add
index on FK of child table, we could perform DML on parent table.

Could you plz explain me this locking behavior ?

Thanx,
Kamal
Followup:

http://asktom.oracle.com/~tkyte/unindex/index.html

Rating: 4
Is select a DML April 24, 2003
Reviewer: kumar from India
Tom,

A couple of niggling doubts..

1.Why is SELECT statement listed as a DML in Oracle SQL Reference ?

2. Will a select statement obtain a lock anytime ( excluding select for update )
?

3. Why oracle is allowing me to drop a table when some other session is querying
that table? Doesn't that violates the read consistency?
Followup:
1) because it is DML -- dml = data manipulation lanaguage (as opposed to DDL --
data definition lanaguage).

SELECT is DML, many people consider DML only to be "modifications" but that is
technically inaccurate.

2) select for update -- will.  during parse time, it will.

3) no read consistency says "we'll ensure a read consistent view and give you an
error otherwise -- we'll never give you the wrong answer"

dropping the table doesn't violate that -- any many queries that were in process
may return successfully -- whereas others may return with an

[tkyte@tkyte-pc-isdn mesg]$ oerr ora 8103
08103, 00000, "object no longer exists"
// *Cause:  the object has been deleted by another user since the operation
//          began
Rating: 4
Thanks April 24, 2003
Reviewer: kumar from India
Tom,

Thanks for your response. One more doubt. When I am running a query on a table
and some other session comes and truncates the table, Oracle throws an "Object
Not Found" error. Why is this happening ?

Thanks again for answering the questions.
Followup:
cause the data object id it was querying no longer exists.

The object "no longer exists" - Oracle *might* raise the error -- might not,
depends on the query itself.

A truncate is just shorthand for:

o drop
o create
o add indexes and grants

the data_object_id changes with each truncate, its a whole new object.
Rating: 5
Why the OBJECT_ID does not change... April 24, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
If truncate command is shorthand for DROP, CREATE sequence, then whey the
OBJECT_ID of the table does not change after TRUNCATE?

SQL> create table t (x integer) ;

Table created

SQL> insert into t values (1) ;

1 row inserted

SQL> commit ;

Commit complete

SQL> select object_id, object_name from user_objects where object_name = 'T' ;

 OBJECT_ID OBJECT_NAME
----------
--------------------------------------------------------------------------------
    169100 T

1 row selected

SQL> truncate table t ;

Table truncated

SQL> select object_id, object_name from user_objects where object_name = 'T' ;

 OBJECT_ID OBJECT_NAME
----------
--------------------------------------------------------------------------------
    169100 T

1 row selected

SQL>
Followup:
thats just a surrogate key.  look at the DATA_OBJECT_ID, that is what I said is
really important here -- the object (the data object) no longer exists...

ops$tkyte@ORA920> select object_id, data_object_id from user_objects where
object_name= 'T';

 OBJECT_ID DATA_OBJECT_ID
---------- --------------
     34408          34408

ops$tkyte@ORA920> truncate table t;

Table truncated.

ops$tkyte@ORA920> select object_id, data_object_id from user_objects where
object_name= 'T';

 OBJECT_ID DATA_OBJECT_ID
---------- --------------
     34408          34418
Rating: 4
Excellent - as always April 24, 2003
Reviewer: A reader
Rating: 5
Great response April 24, 2003
Reviewer: kumar from India
Tom,
Thats something I haven't read anywhere before this.
When I got the error first time, I thought it could be a bug.
Thanks for the response.
Rating: 5
May 13, 2003
Reviewer: A reader
Rating: 5
TX without TM June 09, 2003
Reviewer: David Pujol from Barcelona, Catalonia
Hi Tom, I've seen that in v$lock I look a transactions TX without TM lock. I've
been studing this topic, and I'he seen some notes that talks about that this is
a normal behaviour when dblinks are used. But, my question, can you explain me
the reason of why Oracle take a TX lock and don`t take a TM lock too?, Is TM
lock in remote database? Why?

Thanks in advance
David Pujol
Followup:
using a dblink starts a transaction, yes.

you will see the TX lock as soon as you touch the dblink, even with a query.
there need not be a tm lock at all.  But, if the table is remote and you update
it, the tm lock will be on the OTHER site's v$lock view, not yours.
Rating: 4
bitand June 10, 2003
Reviewer: Bala from Kuala Lumpur
Tom,
I tried learning from various source why this bitand thing is used. I am still
not clear. Can you please explain me the basics of bitand function and how did
you use it here.

bitand(id1,to_number('ffff','xxxx'))+0
Followup:
the number id1 is a 4 byte integer.

there are really two 2 byte numbers hiding in that single field.  So, if the two
numbers were 2 and 3, then id1 would be 0x00020003 which is decimal

ops$tkyte@ORA920LAP> select to_number( '20003', 'xxxxx' ) from dual;

TO_NUMBER('20003','XXXXX')
--------------------------
                    131075

We needed the low 2 bytes only, we want '3' as the result.  So, we need to "mask
off" the high 2 bytes and just keep the low two bytes.  In order to do that, we
need the number such that all of the bits are "on" in the low 2 bytes and off in
the high ones.  In binary, that would be:

00000000000000001111111111111111

and in hex

0x0000FFFF

Now, bitand is a function that takes two numbers and performs a classic "bit
anding" on them.  that is, given two binary numbers:

01010101010101
01000100011100
---------------
01000100010100  <<<=== the bit and'ed result, only when BOTH bits are on...

so, if we use that number with all 1's on the end, we would only get the bits of
the first number from the low two bytes.  so:

ops$tkyte@ORA920LAP> select bitand( 131075, to_number( 'ffff','xxxx') ) from
dual;

BITAND(131075,TO_NUMBER('FFFF','XXXX'))
---------------------------------------
                                      3

Now, some releases of sqlplus would not recognize the output of bitand
correctly, so I just added 0 to it, that made it a number always and made
sqlplus happy.
Rating: 4
bitand - Found the answer June 10, 2003
Reviewer: Bala from Kuala Lumpur
simple search for bitand...
Sorry.
http://asktom.oracle.com/pls/ask/f?p=4950:8:280872129227966139::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:375818206678,

Rating: 2
Need more clarification June 11, 2003
Reviewer: A reader
Version : Oracle 8.1

Tom, Sometimes, in SQL*Plus, if I give a select statement, the system seems to
be hanging indefitely.
I open another session, check the lock mode, it says "3" , not "6" which means
it is not exclusively locked. But then why does it hang when :
1. Multiple users are doing a "Select"
2. Multiple users are doing a "Update" of different rows in the same table.
Followup:
need an example...

it doesn't work the way you describe.

you can leave the users doing select out of the equation, they do not count.
unless of course, it is a select FOR UPDATE.
Rating: 3
Procedure issue June 11, 2003
Reviewer: sonali kelkar from waltham, ma
I have a strange problem with 9.2.0 version of Oracle it it not in 8.1.7.. I
have a stored procedure, it inserts a project row in work table then the trigger
is fired which inserts into some other table...it comes back to the procedure..
now it inserts the task into the same work table but this when the same trigger
is fired, I have a procedure with autonomous transaction to read the earlier
row, it does not find it.. where is it lost, I can do dbmsoutput and see the row
in original procedure, why can't the other procedure read it ? I have only one
commit at the end of the original procedure.. does this have to with locking or
a bug in 9.2 ??
Followup:
if you have this sequence of events:

a) call stored procedure
b) insert a row in T1
c) trigger on T1 inserts it into T2
d) insert a row in T1 again
e) trigger tries to read any of the rows from (b), (c), (d) using an autonomous
   transaction

(e) is fatally flawed and never worked that way in ANY release.

an autonomous transaction, by its very definition -- by its VERY definition --
is like a separate transaction.  Therefore, it can see

a) data that was committed when it begin
b) data it modifies

since the data in (b), (c), (d) is NOT YET committed, an atran in (e) will not
see it.  It is easy to see this behaviour:

ops$tkyte@ORA920> create table t1 ( x int );

Table created.

ops$tkyte@ORA920> create table t2 ( x int );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace procedure autonomous_proc
  2  as
  3          pragma autonomous_transaction;
  4  begin
  5          dbms_output.put_line( 'Enter.....' );
  6
  7          for x in ( select * from t2 )
  8          loop
  9                  dbms_output.put_line( 'data = ' || x.x );
 10          end loop;
 11          dbms_output.put_line( 'Exit.....' );
 12  end;
 13  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace trigger t1_trigger
  2  after insert on t1 for each row
  3  begin
  4          insert into t2 values ( :new.x );
  5  end;
  6  /

Trigger created.

ops$tkyte@ORA920> create or replace trigger t2_trigger
  2  after insert on t2 for each row
  3  begin
  4          autonomous_proc;
  5  end;
  6  /

Trigger created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t1 values ( 1 );
Enter.....
Exit.....

1 row created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920> insert into t1 values ( 2 );
Enter.....
data = 1
Exit.....

1 row created.

ops$tkyte@ORA920> commit;

Commit complete.

and you will observe exactly the same behaviour in 8i

Now, if you believe they are behaving differently -- supply us a full test case
as I have -- in as few lines of code as humanly possible -- that is really
important -- as small as you can

odds are, you find your mistake in the test case generation -- i usually find
mine that way.
Rating: 5
YOu are the best as always.... June 12, 2003
Reviewer: Sonali Kelkar from waltham, ma
I tought that the code was wrong but as it works in 8i and has stopped working
in 9i, I thought it was something else. I will try small test case in 8i now,
like you said... because this code is huge, I may have missed something.
The sequence of events you have said is correct, thats how I want to do it...
How do I do this ? I do not want to commit in the Procedure 1 untill all is
done, I want to be able to rollback if anything goes wrong at any stage...

Thanks again for all your help, like always
Sonali
Followup:
I don't understand why you would use an atran

you do know how transactions and the ACID properties work in Oracle right?  you
are NOT applying SQLServers somewhat "poor" transactional practices on Oracle
are you?

autonomous transactions have so few limited uses, that whenever I see them used,
I immediately get suspicious -- 99999 times out of 99999+1 times, it is a bug in
the developed code (to be using them)
Rating: 2
Mutating trigger June 12, 2003
Reviewer: sonali kelkar from waltham ma
If I do not use atran I get mutating trigger error... I cannot read the table
work, when a TR_ETActInsert - insert trigger on work is fired...thats the reason
I have to use atran.. so I can read the row just inserted. How do I get around
mutating table error then ?

I know how transaction works, atleast I feel so...no I am not using sql server
logics here...
Here is the small portion of code

1. Procedure create_project

has 1st the insert into work table, for project.. this insert trigger gets fired
but as enity_type=3 for project it does not do anything.. then 2nd insert is
done thro' the procedure which for the task, this trigger on work insert is
fired again, this time it inserts into table T2, for that its has to read the
project row which was inserted before in the work table, but as I have a insert
trigger on work, I cannot read the row from that table, so I used atran to
achive this, but then in doing that I cannot read the earlier row because its
not commited as yet, so its like cat and mouse !!
Can you help please ?

2. procedure with atran to read the project row of work table

Create or replace procedure get_project_data(  inParchk in number,
p_StrProjCode OUT varchar2,p_StrProjDesc OUT varchar2,
               p_inSelfAssign OUT number)
as
pragma autonomous_transaction;
begin

    SELECT Work_Item_Code, Work_description, Work_SelfAssign
    INTO p_StrProjCode, p_StrProjDesc, p_inSelfAssign
    FROM work
    WHERE Work_ID = inParChk;

    DBMS_OUTPUT.PUT_LINE (p_StrProjDesc);
   commit;
end;
/

3. Procedure with atran to get the project level, this procedure works, as it
has nothing do with the project or task row inserted

Create or replace procedure get_project_level(P_inProjLevel OUT number)
as
pragma autonomous_transaction;
begin
    SELECT max(Work_Level)+ 1 INTO P_inProjLevel FROM Work WHERE
work_entity_type=2;
    commit;
end;
/

4. Trigger without atran, which is calling the other atran procedures to read
the project information.. I took out lots of code from this trigger which was
not relevant to this problem..

CREATE or replace TRIGGER TR_ETActInsert
AFTER INSERT on mwebWork
For each row
Declare
-- variables here
Begin
    get_project_level(inProjLevel);

    SELECT Count(*)
    INTO inCnt
    FROM ETActivity
    WHERE Act_Impt_ID = :New.Work_ID and Act_Source=20 and Act_Impt_ID is not
NULL;

    if inCnt = 0 then
       If :New.Work_Entity_Type = 4 OR :New.Work_Entity_Type = 30 then

            If :New.Work_Entity_Type = 4 then

                Select  :New.work_par5
                into inParchk
                from dual;

                If inParchk <> 0 then
                    get_project_data(inParchk,StrProjCode, StrProjDesc,
inSelfAssign);
                Else
                    StrProjCode :='0';
                    StrProjDesc :='0';
                    inSelfAssign :=0;
                End if;

                -- xyz....here
            ELSE
                    inSelfAssign := 10;
                    StrProjCode := 'Admin';
                    StrProjDesc := 'Admin';

            END IF;

            if inSelfAssign = 10 then
                insert INTO ETActivity     (a, b, c)
                values     ( a,b,    inETManID,  :New.Work_Plan_Finish,
                    :New.Work_Text_Ext4, StrProjDesc,
                    DECODE (:NEW.Work_Entity_Type, 4, :NEW.Work_MgrRevExp, 20));
            End if;
        End if;
    end if;

end TR_ETActInsert;
/
Followup:
funny, I have a chapter in my new book I'm working on where I state "when I see
the autonomous transaction, in a trigger, to avoid a mutating table error -- I'm
almost assured that I'm looking at a bug (in the code)"

You never NEED TO READ the row you just inserted, we give it to you in the :new
record in the trigger itself.  You already HAVE the just inserted row value.

The only correct implementation would pass these column values as formal
parameters to subroutines.  do NOT read the row out of the table.

max(x)+1 -- ugg, yuck, blah -- terrible.

The mutating table constraint (believe it or not) is there to protect you from
yourself.  That you hit it means there is some error in your transactional way
of thinking.  Getting around it with an atran almost certainly means you are
writing buggy code from a transactional perspective.
Rating: 2
Not reading the latest row June 12, 2003
Reviewer: sonali kelkar from waltham, ma
I guess, I wasn't clear in my last post...NO I am not reading the row just
addeded..

1st the procedure inserts the project information in work table, that time when
this trigger is fired it does not do anything(not supposed too)..say ROW 1
insert
Then the procedure inserts another row for the task in work table say ROW 2
insert, that time this trigger is fired again...this time I want to read the row
which was inserted before ROW 1 (thro' the same procedure) but is not committed
as yet... but this row, ROW 2 is not the one I want to read so I cannot user
:NEW to get the value..as its ROW 1 need to be read inside trigger, if I don't
use atran I get mutating trigger error...
Thanks again
Followup:
that logic hurts my head so badly, I cannot even fathom it.

trying to do this in TRIGGERS is just so wrong (and it never worked, not in 8i).

You have a process, seems like you do NOT want to simply insert into this table,
you want to pass some data to a PROCESS and have it PROCESS the data, inserting
as needed.

Tell me, if I

insert
commit
insert
commit

versus

insert
insert
commit

you cannot tell the difference, yet your trigger would think the first and
second cases are the same.

I would look at removing this level of complexity, doing it simple,
straightforward, with as little code as possible (less bugs that way)
Rating: 3
Yes, you are right...always I guess June 12, 2003
Reviewer: sonali kelkar from waltham, ma
I tried small test case in both 9.2 and 8.1.7... the realized what was happening
in the original code.. there was call to other procedure right after the 1st
insert which was commiting not in the 2nd case... so I was getting no data found
error...
Here is the test case I created

set serveroutput on;
drop table t1;
drop table t2;

create table t1 ( x int, y int );
create table t2 ( x int, y int );

create or replace procedure pr_checkcommit
as
begin
dbms_output.put_line('Ooooops other proc committing it ! ');
commit;
end;
/

create or replace procedure test_proc
    as
    begin
         dbms_output.put_line( 'Enter.....' );
       insert into t1 values(10, 15);
       pr_checkcommit;
     dbms_output.put_line( 'commit done.....' );
       insert into t1 values(20, 30);
       dbms_output.put_line( 'Exit.....' );
       commit;
   end;
   /

create or replace procedure t2_proc (inrow1 in number, p_test OUT number)
as
pragma autonomous_transaction;
begin
dbms_output.put_line( 'IN the aTRAN.....' );
 select y into p_test from t1 where x=inrow1;
end;
/

create or replace trigger t1_trigger
    after insert on t1 for each row
    declare
    testrow1 int;
    begin
    dbms_output.put_line( 'IN the trigger.....' );
            if :new.x = 20 then
                t2_proc(10,testrow1);
                insert into t2 values ( :new.x, testrow1 );
            end if;
    end;
    /

execute test_proc;

If I comment out call to teh procedure pr_checkcommit I get error !! It took me
2 days to figure out what was different in both cases.. sometimes making small
test case does HELP !!

I still have to NOW figure out what and how to change this code to make it work
!

If you have any suggestions for that I will much appreciate it.

Thanks for help

Sonali
Followup:
my suggestion is and will be:

forget the triggers.  This is something that should be "front ended".  someone
calls a procedure that does the right thing, rather they trying to have 15
mysterious side effects from magic triggers to make things "look transparent"
Rating: 4
One more try on row level locks :-) August 13, 2003
Reviewer: Piotr Jarmuz from Braunschweig, Germany
Hello Tom,

Just one more try on how to get the info row level locks. I have read your book
and know less or more the Oracle architecture. And I think there is teoretically
a way to learn who is locking a particular row even without getting blocked.

So let's have a scenarion:

Session A locks one or more rows in table T with the following DML:

"update T set x=x+1 where y='foo'"

so, now

v$lock has the following entries:

1. TM shared lock on Table "T" by session A
2. TX(1234) transaction row (not associated with a particular object nor row)

After that table "layout" may look less or more like this:

TABLE "T"
--------------------------------------

--------------------
block #1
--------------------
Transaction Table
TX1(1234)
TX2
...

TXn
--------------------
Row directory
*(TX1) row100
  row101
 ...

  row110
--------------------

DATA
....
--------------------

--------------------
block #2
--------------------
Transaction Table
TX1(1234)
TX2
...

TXn
--------------------
Row directory
  row110
*(TX1) row111
 ...

  row120
--------------------

DATA
....
--------------------

* - rows are locked by session A (locks in Oracle are attributes of data)

Suppose that this query started the transaction ID=1234 and locked row100 in
block #1 and row111 in block #2 in table "T". So entry in the row directory for
row100 and row111 point to the transaction table entry TX1 in transaction table
on corresponding blocks which in turn point to the TX lock seen in v$lock.

Now Session B executes "select * from T where y='foo' for update nowait" and
then query goes to block1 finds row100 and sees it points to TX1 entry which
holds TX ID=1234 and is still active. So now the row is locked and Oracle raises
ORA-00054 Resource Busy Exception but it at least has theoretical knowledge of
EXACTLY who locked that particular row. If the session B issued query without
"nowait" option we would get TM lock entry with value of request=6 and we could
know who is blocking who. But with nowait option that knowledge seem to be lost.

Sure there could be potentially many blockers to have locked session B. And we
wouldn't be sure who exactly blocked us.
But at least in keyed queries by session B (0 or more rows returned) we would
know exactly the blocker even with nowait option.

So if this is a theoretically possible then maybe it is also doable in practice?

If I am wrong please, correct me.

P.S.

I have exactly such a situation in my project (keyed selects for update) and I
need to know who is locking my row (or object as I have OO approach on top of
RDBMS). Currently I have a work-around for that with an extra table that stores
Oracle session SID and IDs (primary key, all objects have surogate integers as
PK which also allows for simple and efficient polymorphism, objects know the
class they belong just by their ID) of locked objects that are inserted by a
lock() method (the one doing select for update) and commited as autonomous
transactions so that the blockee may get to learn the blocker.

All accesses to tables are exclusively via stored procedures, tables are
invisible to users so this is OK but adds a performance overhead. I wondered if
there would be faster way to do it using that internal Oracle info?

Best regards.
Followup:
whether it is "theorectically" possible (it could be) isn't relevant.  It
doesn't happen.

good luck with your oo stuff.

still don't see why "bob" needs to know "mary" had the object.  what is "bob"
going to do about it?
Rating: 5
BTW, I'm so glad this site is back August 13, 2003
Reviewer: Shrek from DC
I don't know if the problem was on the asktom end or mine. I couldn't access
asktom from home or work for about a day.

--Shrek.
Followup:
it was a power failure caused by a backhoe ;)
Rating: 4
Lock info August 13, 2003
Reviewer: Piotr Jarmuz from Braunschweig, Germany
In need that because so says specification :-). Although I was one of the
designers I thought it would be a peanut in Oracle and agreed to this
requirement...

This is going to be (among others) CMS system where many web editors will work
with objects: like articles, items, users... etc. And they will possibly work
remotely. And in case if someone locks an object and goes away from terminal
(end users tend to do so) that I also need to edit it will help me a lot to know
who did that so that I can call him and ask what's going on. There is also a
hard limit implemented with user profiles on an idle session but I believe it is
better to be co-operative as humans. It is more of a sociological requirement
rather than technical.

One more correction to my previous post: keyed query is of course 0 or 1 (not 0
or many).

Thanks for your good-luck-wish. Working with Oracle is fun as much as reading
your forum.

Regards.
Followup:
terminal application should time out locks then.

and if they are remote, again - what is bob going to do when he finds out mary
has his object and mary is gone to lunch?  how does bob know shes at lunch? what
can bob do about it?
Rating: 5
urgent - select on system tables hangs! August 20, 2003
Reviewer: A reader
Hi Tom
We have a situation where the following select
seems to hang (in a customer system). usually selects
should not hang - so I am wondering whether their
database system schema is messed up (e.g. lost indexes etc) - even then when and
why can a select hang?!
In a similar sized database, this select works
just fine.
Any clues?

Thanx for an awesome site!

-
SELECT  A.TABLESPACE_NAME oracle_database_tablespace,
        B.FILE_NAME file_name,
        B.BYTES sizeb,
        SUM(A.BYTES) free
FROM DBA_FREE_SPACE A,DBA_DATA_FILES B
                WHERE
                        A.FILE_ID = B.FILE_ID
                GROUP BY
                        A.TABLESPACE_NAME, B.FILE_NAME, B.BYTES
Followup:
might not be hung, might be a database with billions of dictionary managed
extents or something

look in v$session_event or v$session_wait to see what it is doing/waiting for
Rating: 5
Re: urgent - select on system tables hangs! August 21, 2003
Reviewer: A reader
Hi Tom
The sql wasnt hanging - it took some 5 hours to
get around 300 odd records!!! I think their database's
system accounts are perhaps messed up...

Thanx!
Followup:
look for lots of dictionary managed extents.
Rating: 5
right you are as always! August 21, 2003
Reviewer: A reader
"might not be hung, might be a database with billions of dictionary managed
extents or something

look in v$session_event or v$session_wait to see what it is doing/waiting for
"
Yup it was not hung - just took 5 hours or so to
complete!
How does the DMT affect a select statement. What can
a select be waiting on - I was thinking that a select
is never "blocked" by any locks?

If the above is the issue - then apart from the suggestion
of moving to LMT (a long term solution), is their
anything else we can do in the short term(e.g. reduce the
number of extents or any other DBA stuff?)

Thank you so much!!!!
Followup:
it wasn't blocked -- it just had a gazillion rows to process.

reducing the extents would help -- but it takes a really really really long time
to significantly reduce extents.
Rating: 5
thanx Tom! August 21, 2003
Reviewer: A reader
"it just had a gazillion rows to process.  "

I am not able to understand why you have to allocate
extents during select. In this table one table has around 3000 rows - the other
has around 300 rows. For
all other customers this query returns in minutes.
So, what exactly is causing this select to process
so many rows - is it some recursive sql that gets
executed?

Thanx!!!
Followup:
version?
Rating: 5
hi Tom! August 22, 2003
Reviewer: A reader
It is 8.1.7.3

The exact # of records in dba_free_space = 2266
and in dba_data_files = 374

Thank you so much!!
Followup:
if you do a "set autotrace traceonly explain" -- what does their plan look like?

cbo
or
rbo?

do you see cost/card/bytes or not??
Rating: 5
ok here is some more info August 22, 2003
Reviewer: A reader
I found the id of the session1 by using
select sid from v$mystat where rownum = 1;
the answer was 75

Then I ran the following query in session2
select sid, event, time_waited , total_waits , average_wait
  2  from v$session_event
  3  where rownum <= 10
  4    and sid = 75
  5  order by time_waited desc, total_waits desc, average_wait desc;

This gave me a before snapshot of top 10 events as
follows:
----
 SID EVENT                TIME_WAITED TOTAL_WAITS AVERAGE_WAIT

---------- -------------------- ----------- ----------- ------------

        75 SQL*Net message from       58530          90   650.333333

            client

        75 db file sequential r          17          28   .607142857

           ead

        75 latch free                     3           3            1

        75 control file sequent           1          31   .032258065

           ial read

        75 SQL*Net message to c           0          92            0

           lient

        75 file open                      0           7            0

        75 SQL*Net more data to           0           6            0

            client

        75 SQL*Net break/reset            0           3            0

           to client

        75 refresh controlfile            0           2            0

           command
 ----

Then I ran the problem query in the session1 (id = 75).

After sometime I ran the same query as above in session2
to get an "after" snapshot as follows:
---- after snapshot

SID EVENT                TIME_WAITED TOTAL_WAITS AVERAGE_WAIT

---------- -------------------- ----------- ----------- ------------

        75 SQL*Net message from       67115          95   706.473684

            client

        75 db file sequential r          18         146   .123287671

           ead

        75 latch free                    11           8        1.375

        75 db file scattered re          10          90   .111111111

           ad

        75 control file sequent           1          69   .014492754

           ial read

        75 SQL*Net message to c           0          97            0

           lient

        75 file open                      0           8            0

        75 SQL*Net more data to           0           6            0

            client

        75 refresh controlfile            0           4            0

           command

        75 SQL*Net break/reset            0           3            0

           to client

----

The problem query of course is still going strong.
the db file scattered read event perhaps indicates
a full table scans (or full index scans)

Please let me know if you need any more data and
how to get it?

Thanx a ton!!
Rating: 5
thanx Tom!!!! August 22, 2003
Reviewer: A reader
"if you do a "set autotrace traceonly explain" -- what does their plan look
like?

cbo
or
rbo?
"

Unfortunately, I cannot do an autotrace - the plan table
does not exist!! Since it is a customer set up I don't have
the system login :( ...Is there any other way to find out
if a query is using RBO or cbo? Sorry to keep bothering you!! Anyways, how does
it matter - I mean what
would your answer be if I said CBO (which is 99% likely
given our product is supposed to follow CBO)?

I guess you may be thinking along that if it is CBO and if
stats have not been generated (do the system tables
also need stats????) then that may explain - if we get say
a lousy plan for that select...

Thank you!!
Followup:
there was a release where a hint was accidently added to a data dictionary view
that made queries of this fashion go really bad (cause the dictionary wasn't
analyzed).

you can create a plan table on your own, $ORACLE_HOME/rdbms/admin/utlxplan.sql
has the schema.
Rating: 5
Gr8 August 23, 2003
Reviewer: Bipin Ganar from INDIA
Hi Tom,
here i have small question
I have one table 138 people are using that table.
One of the user got into deadlock. While trying to
insert the same table by the user gets hanged
Is there any way i can automatically remove /Kill session
if deadlock occurs after 1/2 Hour (Any parameter to set in file)
Followup:
deadlocks are automatically detected and one of the offending statements is
aborted within 3 seconds.

please clarify what you mean by deadlock.
Rating: 4
table lock in share or exclusive more August 23, 2003
Reviewer: A Reader
Tom, I was reading the online doc re lock table <tablename> iN share (S) or
share row exclusive (SRX) exclusive mode. It says, it is implicitly used for
referential integrity. Could you please help me in understanding how and why
oracle uses these table level locks to maintain referential integrity? also, is
this why we need index on foreign key columns? thanks and I appreciate your
time.
Followup:
always helps to have a pointer to what you were reading so I can see it in
context.  but:
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96524/c22integ.htm#2161
describes the 9i behaviour with locking and fkeys.  there are similar chapters
in the 8i and before docs as well. (concepts guide)
Rating: 5
thanx Tom!! August 23, 2003
Reviewer: A reader
"there was a release where a hint was accidently added to a data dictionary view

that made queries of this fashion go really bad (cause the dictionary wasn't
analyzed).

you can create a plan table on your own, $ORACLE_HOME/rdbms/admin/utlxplan.sql
has the schema. "

Hi tom
Do you know what release it was (Was it 8.1.7.3)?
Most likely we are using CBO - I can confirm and
get back! Is there any metalink reference for this?
Unfortunately, I can't
create anything (including a plan table)
in this schema as it is a customer database. Thanx for your help so far!!
Followup:
ask your customer to do it in order to help you RESOLVE THEIR ISSUE.

I forget the release 8.1.something -- could be .7.3 -- believe it was 816 but
only 8174 has all patches.
Rating: 5
Tom August 23, 2003
Reviewer: A reader
"there was a release where a hint was accidently added to a data dictionary view

that made queries of this fashion go really bad (cause the dictionary wasn't
analyzed).
"

I guess, one solution is to try and analyze the affected
tables and then rerun the query then?
Followup:
or fix the view def, yes, but the tables in question are DICTIONARY tables which
you would have to analyze ALL of and then test.
Rating: 5
another questions August 23, 2003
Reviewer: A reader
Just realized that the dba_Data_files and dba_free_space
are views basedon some system tables. If we want to
analyze them we would have to analyze the underlying tables, right?

Thanx!!!!
Followup:
yah
Rating: 5
sorry Tom! August 23, 2003
Reviewer: A reader
I just found out that the db was 8.1.7.4 - (someone
gave me wrong info)!
Sorry about that - my only guess is that the dba tables
have not been analyzed properly.

Also, I know that app tables should be analyzed periodically using monitoring +
gather stale stats
- is it the same in case of system tables?

Thanx and I apologize once again for the wrong info
about the patchset version of the database.
Followup:
i would need a tkprof to comment further.
Rating: 5
hi Tom! August 26, 2003
Reviewer: A reader
I don't have the tkprof, but I have confirmed that
none of the underlying tables have been analyzed!

My understanding is that the data dictionary tables
should be analyzed. Now these tables don't change a
lot( that too depends but in general,) but at least
they should have been analyzed once!

btw, do the data dictionary tables use the monitoring clause so that
we can periodically use the "gather stale statistics"
approach. What is the best practice when it comes
to analyzing data dictionary tables - are there
any differences when compared to application tables
that one should be aware of?

Thanx!
PS: btw, I found that a utiity that gives you all the
tables underneath a view (expanding any other views)
would be useful - Do you know of or have such a utility?
Followup:
in 8i, probably you don't want to analyze the dictionary
in 9i, yes, you very well might
in future releases -- definitely.

as for the view thing -- *_DEPENDENCIES will give you that info.
Rating: 5
thank you Tom! August 27, 2003
Reviewer: A reader
The tip on *_dependencies is useful.

Is it possible for you to briefly elaborate on why
we don't need to analyze data dictionary tables in 8i and why in 9i and future
versions we need to analyze these tables?

Thank you soooo much!!!
Followup:
in 8i -- it is supported, but generally not needed.... if you haven't been doing
it, no reason to start (no need to change that which is working)

in 9i, it is supported and many do it.

in 10 -- the rbo is not supported anymore.
Rating: 5
thanx Tom! August 27, 2003
Reviewer: A reader
so it is RBO usage etc that is dictating this
decision. I guess in 9i, you perhaps lose more of the
enhancements if you dont do CBO - hence a little more
compelling reason to do it.

Thanx!
Rating: 5
question... September 16, 2003
Reviewer: A reader
"SELECT  A.TABLESPACE_NAME oracle_database_tablespace,
        B.FILE_NAME file_name,
        B.BYTES sizeb,
        SUM(A.BYTES) free
FROM DBA_FREE_SPACE A,DBA_DATA_FILES B
                WHERE
                        A.FILE_ID = B.FILE_ID
                GROUP BY
                        A.TABLESPACE_NAME, B.FILE_NAME, B.BYTES
"

In your new book on page 495.
You specify a rownum based trick for "Join Queries"
In the above case also, we had views where individual
queries to views were very fast (minutes) but the join took
more than 5 hours - in 8174 database.
Do you think the same trick can be applied in this case?
(Wanted to verify with you before I try and run it in
the customer machine)
Followup:
why don't you try it on your machines?

if the two individual queries run fast -- doing the rownum trick almost always
works.
Rating: 5
what I mean is an alternative query like September 16, 2003
Reviewer: A reader
SELECT  A.TABLESPACE_NAME oracle_database_tablespace,
        B.FILE_NAME file_name,
        B.BYTES sizeb,
        SUM(A.BYTES) free
FROM (select *, rownum from DBA_FREE_SPACE) A,
      (select *, rownum from DBA_DATA_FILES) B
WHERE A.FILE_ID = B.FILE_ID
GROUP BY A.TABLESPACE_NAME, B.FILE_NAME, B.BYTES
Followup:
should be more like

select ...
  from ( select <just columns you need>, sum(bytes) from dba_free_space where
rownum >= 0 group by <just columns you need> ) a,
       ( select <just columns you need> from dba_data_files where rownum >= 0 )
b
where ....
Rating: 5
thanx Tom! September 16, 2003
Reviewer: A reader
"why don't you try it on your machines?

if the two individual queries run fast -- doing the rownum trick almost always
works.
"
In our machine the problem is not reproducible - All
queries run pretty fast.
Anyways, I have passed it to the elevant people who
should get back to me. I will let you know what happens
Thanx a lot!!!
Rating: 5
oops! September 16, 2003
Reviewer: A reader
"should be more like

select ...
  from ( select <just columns you need>, sum(bytes) from dba_free_space where
rownum >= 0 group by <just columns you need> ) a,
       ( select <just columns you need> from dba_data_files where rownum >= 0 )
b
where ....
"

So the query that I gave - is it not equivalent to
the original query - or you are suggesting a more performant alternative?

Thanx!
Followup:
you should aggregate dba_free_space and then join to dba_datafiles -- that is
what that (my) query does.

instead of joining to every row in dba_free_space and then aggregating

the two queries do the same thing, just one of them should do less work

aggregate, then join

instead of

join, then aggregate.
Rating: 5
sorry to repost September 16, 2003
Reviewer: A reader
Sorry Tom - I actually already forwarded a query as:

"SELECT  A.TABLESPACE_NAME oracle_database_tablespace,
        B.FILE_NAME file_name,
        B.BYTES sizeb,
        SUM(A.BYTES) free
FROM (select file_id, tablespace_name, bytes, rownum from DBA_FREE_SPACE) A,
      (select file_id, file_name, bytes, rownum from DBA_DATA_FILES) B
WHERE A.FILE_ID = B.FILE_ID
GROUP BY A.TABLESPACE_NAME, B.FILE_NAME, B.BYTES;
"

This one should be equivalent to the original query.
The one that you suggested - I am not sure whether that
is a performance improvement or a correction!

Sorry for the hassle!!
Rating: 5
thanx Tom!! September 17, 2003
Reviewer: A reader
for your help!!!!!!
Rating: 4
September 18, 2003
Reviewer: FZ from CA USA
Tom, in case of serial blocking (e.g., A blocks B, B blocks C, and C blocks D),
how to find the order of blocking (A->B->C->D)?  It seems v$lock (or dba_lock)
only shows A blocks others.
Followup:
if a is blocking b.  and b is blocking c.  then v$lock will show you a blocks b.
 b blocks c. and so on.

utllockt (in $ORACLE_HOME/rdbms/admin) shows a little tree -- but it v$lock
would show c getting blocked by b if b was blocking C
Rating: 4
September 18, 2003
Reviewer: FZ from CA, USA
Tom,  here's an example:
sid=12: delete from tt;
sid=16: delete from tt where a=1;
sid=11: delete from tt where a>=1;

SQL> select * from v$lock where sid in (12,16,11);

ADDR     KADDR           SID TY        ID1        ID2      LMODE    REQUEST
CTIME      BLOCK
-------- -------- ---------- -- ---------- ---------- ---------- ----------
---------- ----------
13B4E17C 13B4E190         11 TM      30687          0          3          0
    0          0
142BDDF0 142BDE00         11 TX     327727      20151          0          6
    0          0
13B890B0 13B891BC         12 TX     327727      20151          6          0
   20          1
13B4E074 13B4E088         12 TM      30687          0          3          0
   20          0
13B4E0F8 13B4E10C         16 TM      30687          0          3          0
   10          0
142BDDA4 142BDDB4         16 TX     327727      20151          0          6
   10          0

I understand #11 is not blocked directly by #16 now, but it'll happen when #12
release the lock.
How to find #16 (not #11) will be the next one on the waiting list?
Rating: 5
thanx Tom!!! October 05, 2003
Reviewer: A reader
"you should aggregate dba_free_space and then join to dba_datafiles -- that is
what that (my) query does.
"

I am the reader who asked you for a solution - The
rownum trick from your book coupled with above tip
helped me solve the issue!!!

Please keep up the great work - You are the best!!
Rating: 5
Insert ... Select .. For Update November 04, 2003
Reviewer: A reader
Hi Tom,

I tried sth. like the following in SQL:

CREATE TABLE TAB2 AS SELECT * FROM TAB1 WHERE 1 = 0;

INSERT INTO TAB2 SELECT * FROM TAB1 FOR UPDATE;

It doesn't allow me to use the FOR UPDATE clause.

Do I have to issue a separate SELECT statement for locking the rows?
Followup:
yes.
Rating: 5
November 12, 2003
Reviewer: A reader
Tom,
If there is a solution, you have it. Our database, once in few weeks, suddenly
starts getting into "one session starts blocking all others" mode. This happens
typically for a couple of minutes. Since the database is heavily used, the
number of users sessions being blocked may increase up to more than 200.
Sometimes this condition clears on its own, sometimes we have to kill the
blocker. Do you have a script which I can run in this 2-3 minute interval (as a
shell script or batch file) which spools the following information to a file:
a) Which session(s) is the blocker, including username(s).
b) which session(s) the blocker is blocking, including username(s).
c) Which SQL statement the blocker is running which resulted in the block.
d) Which statements the blocked sessions were trying to run when blocked.

There are many scripts on Metalink, but I am not sure if they will work or are
they good enough for 9i. Some of them cannot be automated. The narrow time
window will not permit me to substitute 200 session IDs in an IN list.

Thanks
Followup:
you have a, b here.

to get c, d, you just need to also run my "showsql.sql" script.

you'll have everything.  if you want to "merge them", go for it.
Rating: 5
November 13, 2003
Reviewer: A reader
Tom,
I tried to run the following script in the database:
tkyte@TKYTE816> select username,
  2         v$lock.sid,
  3         trunc(id1/power(2,16)) rbs,
  4         bitand(id1,to_number('ffff','xxxx'))+0 slot,
  5         id2 seq,
  6         lmode,
  7         request
  8  from v$lock, v$session
  9  where v$lock.type = 'TX'
 10    and v$lock.sid = v$session.sid
 11    and v$session.username = USER
 12  /

It takes upwards of 2 minutes to return results. Sometimes it just seems to hang
and no results even after 5 minutes. Looks like the more the number of sessions,
the longer it takes. When the database experiences the problem, the number of
user sessions can go into several hundered. Is there a way to make this script
faster so it returns results quickly.
Thanks
Rating: 5
ITL and Unindexed Foreign Keys Lock December 20, 2003
Reviewer: Vivek Sharma from Bombay, India
Dear Tom,

Based on the understanding from this site, please let mw know whether I have
understood the Locking issues in terms of ITL and Unindexed Foreign Key cases.

In the case of ITL (Max Trans too low) :

The request column of the waiter will have a value 4 and type 'TX' with id1 and
id2 pointing to anyone of the blockers (probably the first one). It won't have
its own TX entry since the transaction did not began at that point of time.

Whereas in case of Unindexed Foriegn Key

The request column of the waiter will have a value 4 and type TX with id1 and
id2 pointing to the blocker and will also have its own id1 and id2 with request
as 0.

Am I correct ?

Question 2:

In your book Expert one on one, you have showed alongwith the example that 2
sessions are simulataneously inserting rows into a table with freelist 1. Data
Block waits were increased due to this. When the freelist was increased to 2,
the waits of Data Block remain constant (did not increase).

I am confused here. I use to think that if the waits on Data blocks is too high
then I need to Increase Initrans or set the Pctfree / pctused paramaters to
accomadate lesser rows in a block and if the waits on segment header is too
high, I need to increase freelist. But your example shows that the waits on
freelists increases the data block waits. Can you please clarify my above
doubts.

Regards
Vivek
Followup:
<quote>
The request column of the waiter will have a value 4 and type 'TX' with id1 and
id2 pointing to anyone of the blockers (probably the first one). It won't have
its own TX entry since the transaction did not began at that point of time.
</quote>

well, how could it have a TX record if it doesn't have a TX record? :)  you sort
of contradict yourself.

A small test was easy to set up.

In one session:

drop table t;

                                   create table t ( x int ) initrans 2 maxtrans
2;

                                   insert into t select rownum from all_users;
commit;
select distinct dbms_rowid.rowid_block_number(rowid) from t;

                                   update t set x = x where x = 1;

Now, open two more windows and in the second:
update t set x = x where x=2;

and in the third
update t set x=x where x=3;

What I observed was:

ops$tkyte@ORA920> select sid, type, id1, id2, lmode, block, request from v$lock
where sid in ( 8, 9, 11 )
  2  /

       SID TY        ID1        ID2      LMODE      BLOCK    REQUEST
---------- -- ---------- ---------- ---------- ---------- ----------
         8 TX     131106      68619          6          0          0
         8 TM      59314          0          3          0          0
         9 TX     458777      67872          6          1          0
         9 TM      59314          0          3          0          0
        11 TM      59314          0          3          0          0
        11 TX     458777      67872          0          0          4

6 rows selected.

the blocked sid (11) is actually blocked waiting on the SECOND (not the first
here -- but that doesn't "prove" it couldn't be the first, just proves that it
doesn't have to be the first).

It does have a TX record.

The blocked fkey is also easy to setup (you all can try these things
yourselves!!! you too have the power.  simulations are the way to learn -- it is
how i figure out most of the answers here!)

Run this in session 1:

create table p ( x int primary key );
create table c ( x references p );

insert into p values ( 1 );
insert into c values ( 1 );
insert into p values ( 2 );
commit;

In another window, do this:

insert into c values ( 2 );

and then in the first window do this:

update p set x=x where x=2;

Now, in a free session, look at v$lock (remember, change the sid's to be the
sid's on your system!)
ops$tkyte@ORA920> select sid, type, id1, id2, lmode, block, request from v$lock
where sid in ( 8, 9, 11 );

       SID TY        ID1        ID2      LMODE      BLOCK    REQUEST
---------- -- ---------- ---------- ---------- ---------- ----------
         8 TM      59330          0          0          0          4
         8 TM      59328          0          3          0          0
        11 TX     262179      69553          6          0          0
        11 TM      59330          0          3          1          0
        11 TM      59328          0          2          0          0

See -- now you can see for yourself exactly what will happen and when.

As for q2 -- data block waits are caused by many many things.  You could be
waiting for some other session to read the block into the buffer cache, you
could have itl issues, you could be contending for the data block that is the
head of the freelist (as was the case in my example in the book).

I removed the contention by having sufficient freelists.  In 9i, you might look
at ASSM (automatic segment space management) in order to not have to think about
freelists as well.
Rating: 5
ITL and Freelist Waits December 22, 2003
Reviewer: Vivek Sharma from Bombay, India
Dear Tom,

Thanks a lot for your prompt reply. I think this forum is for Locking but
unnecessarily I am asking this question on Data Blocks Contention.

Question 1
----------
Uptill Now, my thinking for the solution for wait events were( Please correct me
if I am wrong) :

1. Waits on Data Blocks : Increase PCTFREE / Decrease PCTUSED to reduce number
of rows in a block which will decrease the contention on a block. Else Increase
the value of Initrans.

2. Waits on Segment Header : Increase value of Freelist of tables.

3. Waits on Undo Header : Increase the number of Rollback Segments as there is a
contention on Transaction Table of Rolback Segment which reside on the Header of
RBS.

4. Waits of Undo Block : Increase the Size of the rollback segments.

Please let me know if I am correct in my approach.

Question 2
----------
Also if the freelist contention can cause wait event on Data Block (as
demonstrated in your book Expert One on One) then what does segment header wait
events specify and what is the remedy to remove the waits on this.

Question 3
----------
Suppose my approach to increase pctfree and decrease pctused to lower the number
of rows in a table so as to reduce the waits on Data Block then I need to
recreate the object with this new setting. Because if the change the settings
this will only be applicable for new blocks. If I recreate the object with this
new settings, will it not affect the query response time of the queries.

Suppose I have a query which fetches row 1 and 10 from a table which are on same
block. The user process will get the block from the disk to buffer and fetch
both the rows from same block.
If the settings are changed and assuming these rows are splitted into 2
different rows, then user process will now have to read 2 blocks into buffer and
then fetch the rows.

Will it affect the performance ?

Though these are all taken care off in Oracle 9i but since many sites are still
on Oracle 8i, these answers will help me understand the importance and remedies
of each and every wait events listed in v$waitstat.

Thanks and Regards,
Regards
Vivek
Followup:
q1) that is basically the rule of thumb advice found in support note Article-ID:
        <Note:62172.1>, yes. (but they have a tad more there)

q2) can be caused by rapid growth of an object (extends).  not normally an
"issue"

q3) and it is mostly NEW blocks that have the issue (it is adding data to the
table)

it could increase OR decrease OR do nothing to the query response time.  Fewer
rows/block -- less in the buffer cache, longer full scans perhaps.  (for
example)
Rating: 5
Waits on Data Blocks December 23, 2003
Reviewer: Vivek Sharma from Bombay, India
Thanks a lot Tom for your quick response. I have gathered some more usefull
information from this website and Metalink and have understood the waits on
Segment Header. Thanks a lot.

The o/p of my v$waitstat shows

SQL> select * from v$waitstat;

CLASS                   COUNT       TIME
------------------ ---------- ----------
data block            9855031   14458609
sort block                  0          0
save undo block             0          0
segment header            760      86212
save undo header            0          0
free list                   0          0
extent map                 20         73
bitmap block                0          0
bitmap index block         27         78
unused                     28        844
system undo header          0          0
system undo block           0          0
undo header             50198    4523398
undo block             293738     332357

and my following query shows the output as

select startup_time, current_time, class, waits / time_elapsed * 100 "Time
Waited %age"
from (
select to_char(startup_time,'dd-mm-yyyy hh24:mi') startup_time,
       to_char(sysdate,'dd-mm-yyyy hh24:mi') current_time,
(sysdate-startup_time)*(24*60*60) time_elapsed, time waits, class
from v$instance, (select class, time/100 time from v$waitstat ))
/

STARTUP_TIME     CURRENT_TIME     CLASS              Time Waited %age
---------------- ---------------- ------------------ ----------------
21-12-2003 14:20 23-12-2003 14:05 data block                    84.95
                                  sort block                      .00
                                  save undo block                 .00
                                  segment header                  .50
                                  save undo header                .00
                                  free list                       .00
                                  extent map                      .00
                                  bitmap block                    .00
                                  bitmap index block              .00
                                  unused                          .00
                                  system undo header              .00
                                  system undo block               .00
                                  undo header                   26.31
                                  undo block                     1.94

14 rows selected.

It shows almost 85% of the time my users are experiencing waits on Data Blocks.
I should seriously consider increasing the value of freelist for those tables
which have concurrent inserts and updates.

Please let me know whether my findings (as per my query above) is correct. Out
of 48 hours of total uptime, 85% of the time users waiting is too much.

Thanks and Regards
Vivek
Followup:
waits on data blocks can be for many reasons, but yes, if you have a hot table
or hot block -- by all means, use the tools at your disposal to fix it.
December 23, 2003 -- Thanks for the question regarding "sort_area_retained_size", version 9.0.1
You Asked
Hello Tom,

If i have specified sort_area_retained_size to 0 and
sort_area_size = 524288

The below is the output for the query:

select a.name, b.value
from v$statname a, v$mystat b
where a.statistic# = b.statistic #
and a.name like '%ga%';

Session uga memory = 49256
Session uga memory max = 82148
Session pga memory = 4411828
Session Pga memory max  = 4411828

When I execute the query :

Select * from all_objects order by 1,2,3,4;

Since Sort_area_retained_size = 0

While processing the above query, I query v$TEMP_EXTENT_POOL
to see any extents are allocated from temporary segments:

I have one extent allocated with 128 blocks.

But after the query got executed:

The below are Statistics:

Session UGA memory =  57728
Session UGA memory max = 632392
Session PGA memory =  4411828
Session PGA memory max = 4411828

My question is:

Since a temporary extent is allocated the sort_area_size of 524288
was not sufficient to handle the sort of the query. Hence it uses a temporary
segment. Since my sort_area_retained_size = 0 every sorting
is done on the temporary segment.

q1) Why is there an increase in Session UGA memory ?
q2) why  is there no increase in the PGA memory by sort_area_size?

Regards,

PS: I am currently using linux system and its little bit tough in cutting and
pasting actual results.
and we said...
You seem to be confusing the purpose of sort_area_retained_size.

A sort area retained size will not influence whether a sort is done in RAM or
not -- just where the RESULTS of the sort will be stored.

Also, when sort_area_size = 0, it really = sort_area_size.  The valid range of
values for this is 2 blocks to sort_area_size.  By setting it to zero, you set
it actually to the sort_area_size.  Seeing as how your UGA grew by about the
sort_area_size -- this makes total sense.

The PGA is managed as a heap - in it might be many free chunks of memory that
were at one time used by are not currently used now.  We will get sort_area_size
bytes of memory from the PGA free space before asking the OS for more.
Review & Followup

Rating: 5
Reader December 12, 2003
Reviewer: A reader
From 9.2 reference manual:
sort_area_size
--------------
Only one memory area of SORT_AREA_SIZE exists for each
user process at any time.

sort_area_retained_size
-----------------------
Oracle may allocate multiple sort spaces of this size for each query. Usually,
only
one or two sorts occur at one time, even for complex queries. In some cases,
however, additional concurrent sorts are required, and each sort keeps its own
memory area.

Q: Does this imply that a process can allocate multiple sort_area_retained size
for reading the sorted result, and ONLY ONE sort_area_size for sorting row
source
even if the same query requires multiple sorts
Followup:
yes it does -- you have many retained perhaps for a query and one sort area size
per process (think PQ for a moment -- separate processes/threads).

Say you have a 1 meg retained size and a 2meg sort area size.  Oracle will begin
by allocated the retained size in the UGA (usually in the pga, but in shared
server this is in the SGA).   when that hits 1meg, it'll start allocating the
sort area size dynamically in the PGA (always the PGA).  the retained size will
stick around as long as we need it (typically till the result set is
exhausted/cursor closed).  the sort area size will be released for reuse after
the call that required it is done.
Rating: 4
Reader December 13, 2003
Reviewer: A reader
"
Say you have a 1 meg retained size and a 2meg sort area size.  Oracle will begin

by allocated the retained size in the UGA (usually in the pga, but in shared
server this is in the SGA).   when that hits 1meg, it'll start allocating the
sort area size dynamically in the PGA (always the PGA).
"

I have heard that oracle first allocates sort_area in increments upto
sort_area-size
It uses this memory to perform  multiple sort runs and merges. When the sorting
is completed
oracle uses retained_size to read the result row set and gives it to the
session. The
sort_area-size is in PGA and sort_area_retained_size is in UGA. So, if sort_area
is 2M
and retained_size is 1M. oracle will allocate upto 2M in PGA first for sorting.
When
sort is completed, it will allocate 1M from UGA ( ex: SGA for MTS). Therefore
these two
memory allocation are seperate, although they both use the same row set of the
sort, Is
my comprehension correct or correct me if I am wrong
Followup:
that is not correct.

it will allocate up to sort_area_retained_size in the UGA and the spill over
into the "pure" PGA up to sort_area_size if the sort exceeds the
sort_area_retained_size AND sort_area_size is greater then
sort_area_retained_size.

the sort_area_retained_size parameter is often misunderstood. At a high level,
this is what happens:

to do a sort, Oracle allocates chunks of memory up to a maximum of
sort_area_size.

As each chunk is allocated -- Oracle checks to see if the sort area is less then
the sort_area_retained_size.

If so, this chunk is allocated in memory that survives from call to call (in the
UGA -- where ever the UGA might be -- in the pga or sga).  This memory survives
as long as needed (across many calls potentially to fetch rows and such)

If not, this chunk is allocated in the "pure pga" and freed at the end of the
call.  This memory does not survive across calls at all.

Sort area memory is not necessarily contigous memory -- hence bits of it could
be in the SGA, some in the PGA -- it matters not to Oracle.

see also
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96536/ch1197.htm#1024241
...
Oracle may allocate multiple sort spaces of this size for each query. Usually,
only one or two sorts occur at one time, even for complex queries. In some
cases, however, additional concurrent sorts are required, and each sort keeps
its own memory area. If the shared server is used, allocation is to the SGA
until the value in SORT_AREA_RETAINED_SIZE is reached. The difference between
SORT_AREA_RETAINED_SIZE and SORT_AREA_SIZE is allocated to the PGA.
........
Rating: 5
Continuation from previous posting .... December 13, 2003
Reviewer: A reader
From RDBMS 9.2 Referencxe Manual
"
SORT_AREA_SIZE specifies in bytes the maximum amount of memory Oracle will
use for a sort. After the sort is complete, but before the rows are returned,
Oracle releases all of the memory allocated for the sort, except the amount
specified by the SORT_AREA_RETAINED_SIZE parameter. After the last row is
returned, Oracle releases the remainder of the memory.
"

This seems to imply that SORT_AREA_SIZE and SORT_AREA_RETAINED_SIZE are
both come from the same memory source unless they are specifically referring
to dedicated servers. Could you please clarify

Thanks
Followup:
sorts are done in the UGA upto sort_area_retained_size and then spill into the
PGA.

In shared server, the UGA is in the SGA.
In dedicated server, the UGA is in the PGA.

that is the difference.
Rating: 5
Thanks very much for the clarification December 13, 2003
Reviewer: A reader
Rating: 4
sort_area_retained_size December 22, 2003
Reviewer: A reader
Hi

if we set WORKAREA_SIZE_POLICY to auto does this make sort_area_retained_size
obsolete?
Followup:
except for shared server connections in 9i, yes.
Rating: 5
is there a way December 23, 2003
Reviewer: A reader
to know a parameter is obsolete from v$parameter?

In this case, sort_retained_size I was looking a doc which states automatic PGA
will make this parameter obsolete but found none
Followup:
it is not that it is "formally obsolete"

it is that it is not used when workarea_size_policy=auto.

the docs for that init.ora parameter state that when set to manual, the *_size
parameters are used. (else, not)

the docs for sort_area_size state that:

...
Note: Oracle does not recommend using the SORT_AREA_SIZE parameter unless the
instance is configured with the shared server option. Oracle recommends that you
enable automatic sizing of SQL working areas by setting PGA_AGGREGATE_TARGET
instead. SORT_AREA_SIZE is retained for backward compatibility.
........

so, just check out the reference guide and read about the two parameters.
December 23, 2003 Federico -- Thanks for the question regarding "Oracle: New world", version 8.1.7
You Asked
I just start this year to use Oracle and I'm so exciting about the new world I
found.

I want to start the Oracle Certification Path (to become OCP), I just wonder
myself What things Tom Kyte did to become what he is right now?

You share all your knowledge(I know that people appreciate that) but I want you
to share the techniques, methods, aids, books, tools and so on that help you to
acquire Oracle knowledge (Think about your early years of your career).

I dont wanna a fish I wanna learn fishing !.

I know that out there, there are so many people that you inspire to keep pushing
hard in this process, thanks for that.

Thanks in advance for your time,

Federico
and we said...
Well, I actually get this questions lots, so I'll publish my answer this time.

it took me 16 years of "all about oracle" to be here.  started as a
programmer/analyst on the mainframe writing PL/I code against SQL/DS and DB2
(PL/I is still by far the coolest language I've ever used).  That started my SQL
career.  I bought Oracle version 5.1.5c from Dr. Dobbs Journal for $99.00 in the
late 80's and started building systems with it.  Then I was "forced" to use
Sybase, Informix, Ingress, Gupta SQLBase and other "rdbms's" as well.  That was
actually a good thing -- i learned in NO UNCERTAIN TERMS that database
"independence" was something achievable for toy like systems (trivial things) or
at the total cost of

a) performance
b) development time (doing it generically took MORE TIME then doing it right for
each database!)

I also learned all about databases then -- read the theory (like Jim Grays
Transaction Processing Book, not really light reading but taught me lots of
stuff).  Then read the practical (the reference manuals).

So I spent six years as a developer/dba.  Even though I was a coder -- i found
doing things in the database to make tons more sense then writing tons of code
to do the same.  I actually quit my first job after six years soley for the
reason that they wouldn't let me exploit the software they paid so much for
(they were so into this "generic database", the "database is a commidity"
concept that they had me coding our own two phase commit protocol, our own
stored procedure language, our own compilers for the same, our own sql parser --
it was frightening.  Funny thing is -- I meet with customers all of the time
still doing just that!)

the first six years is what really did it.  on the job training, reading -- tech
books (egads -- the manuals!) lots of magazines (i was a magazine junkie, dr
dobbs, the c programmers journal, you name it).

Oh yeah, the most important thing -- i had a mentor the first 2 years, a really
great guy.  taught me the important stuff.  how to write good code.  how to do
things right.  how to do things "simply".  how to not over engineer.  how to
think through things.  i wouldn't be doing what I do without his jump start i
don't think.  (thats my biggest hint -- find a mature, grounded, experienced
person and latch onto them for a while.  many times they really dig having a
"protege" and you definitely benefit from it)

That is when I came to Oracle in 1993.  (purely by accident, I didn't really
intend coming to Oracle at all - i had another job lined up that I never showed
up for as Oracle made me an offer instead)

I really got started in October of 1994 when I responded to my first usenet
posting ever:
http://www.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=383oai%24e57%40dcsun4.us.oracle.com
great way to get started -- if you can answer the questions -- if you know the
questions, thats what it is all about.

After a couple thousand postings on the usenet newsgroups, I started asktom in
2000.  The rest - well, you've seen it here.
Review & Followup

Rating: 5
A suggestion October 24, 2003
Reviewer: A reader
hi Tom
Great thread! Thanx for sharing your secrets!:)

Of all the things that you do one of the toughest
( from a developer's point of view - not a dba) is
to write good sql statements. Is it possible for you
to somehow have a way so that we get practice of
answering sql statements (before you do that for us)?
Basically, can this site be used to motivate people to
come up with answers to sql problems - I think one of
the biggest reasons you are where you are is that you
keep practising daily on all aspects of Oracle (including
sql).
I am not sure how the above can be done - but it
would be great - if we can exploit this site for
that purpose.
Rating: 5
Where is your mentor now? October 24, 2003
Reviewer: A reader
And, what's he doing?
Followup:
i lost touch with him a while ago -- after I left the other company and moved
around.  I don't know where he is now.
Rating: 4
October 24, 2003
Reviewer: A reader
May be, if you can publish his name here, somebody might find him. After all,
its a small world :-)
Rating: 5
Inspired!!!! October 24, 2003
Reviewer: Pawan
Really inspired. Will try and see how much I can achieve.
Rating: 5
October 24, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom,
This is really inspirational and educational. Thanks for sharing with us your
knowledge, wisdom, career path and how you got where you are.
Rating: 5
Don't you think it takes alot in becoming like you.. October 24, 2003
Reviewer: Ajeet from OH,USA
Tom,
Many times I think you are exceptional...it is not easy to be like you as for as
Knowledge goes...how many we have more like you in this world--who answers
questiones..from strangers with such an intrest and responsibility .oh..yes
sometime you scold too.
But true -- if all of us can not become like you,we can at least improve a
bit...and yes alot of credit for this would go.Not sure about others but you
inspired me alot.

Regards,
Ajeet
Rating: 5
and what about your motivations ... October 25, 2003
Reviewer: Alberto Dell'Era from Milan, Italy
... (if you don't mind my curiosity) for running asktom ?

That is, you answer questions even when you are on vacation, during weekends,
waiting for planes, etc - definitely, for you asktom is not merely a job, but
much much more.

I'm interested especially on what makes "a question that you enjoy" - this would
have an immediate payback for my question compiling and selecting skills ...
Followup:
some people use crossword puzzles or some other mental trick to wake themselves
up in the morning.  I've used this technique since 1994.  I would answer
questions first thing in the morning.  It wakes me up (actually, i just now got
up from a little saturday afternoon nap :)

My favorite questions -- they are the ones that turn out different from what I
expect or cause me to learn something new, which happens just about every single
day.  My favorite questions are the ones about the database that I have to
research or setup a simulation to discover myself.
Rating: 4
Has it always been happy memory ? October 25, 2003
Reviewer: Robert from CT
Tom,
Did you ever feel frustrated with Oracle...the old releases,
of course ? and What ticked you off ?
Followup:
Every release is better then the last.  No, I was never "unhappy" with it.

I am "unhappy" when forced to work within the confines of a really old release
(like when I get a 7.3 question today -- a full 7 releases AFTER the fact).
Then I know what was missing back then.

But, its like a laptop/desktop.  If you don't know what features you are
missing, you are happy mostly with your existing equipment.  It is only when you
play on someone elses brand new hardware that your old stuff starts to look
pretty lame :)
Rating: 5
now I understand! October 25, 2003
Reviewer: Alberto Dell'Era from Milan, Italy
You are a research scientist targeting Oracle as his research interest, and we
act as the Professor's students that ask "fresh mind" questions during lectures,
preventing his interest from fading into routine.

Thanks, my curiosity is 100% happy now ...
Alberto
Rating: 5
What are you up to ? October 25, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
I've been looking to your newly answered questions from the past several weeks,
and there are not so many. I am planning on gradualetelly reading all your
answers, not just whenever I am looking for something specific.

The last time this happened, was just before the Oracle 10G presentation in
Ottawa, after that .. at least 20 new questions were answered in just 2 days.

So last time this happened, you were preparing the 10G presention. Or simply
playing arround with 10G. Or maybe something different.

So what are you up to this time ? :)))
Followup:
well, it was actually just AFTER Ottawa that I had a free period where I did
take lots of questions, these are new questions by day:

...
14-AUG-03          9
15-AUG-03         15
16-AUG-03         29
17-AUG-03         11
18-AUG-03          3
22-AUG-03         14
23-AUG-03          9
24-AUG-03          7
29-AUG-03          9
30-AUG-03          7
02-SEP-03          9
06-SEP-03         35
07-SEP-03         37
08-SEP-03         22
09-SEP-03         19
11-SEP-03         10
12-SEP-03         11
13-SEP-03         10
14-SEP-03          8
15-SEP-03          2
16-SEP-03         15
17-SEP-03         10
20-SEP-03         10
21-SEP-03         22
23-SEP-03         13
26-SEP-03         21
27-SEP-03         17
28-SEP-03         25
29-SEP-03         13
01-OCT-03         13
02-OCT-03         24
04-OCT-03         24
05-OCT-03         25
06-OCT-03         12
10-OCT-03         14
11-OCT-03         25
12-OCT-03         15
19-OCT-03          8
22-OCT-03          5
24-OCT-03         10
25-OCT-03         22

1012 rows selected.

so you see -- I've been taking them.  BUT... if we look at published vs
unpublished:

  1  select trunc(timestamp),
  2         count( decode( status, 1, 1 ) ) "Not Read",
  3         count( decode( status, 2, 1 ) ) "Waiting",
  4         count( decode( status, 3, 1 ) ) "Processing",
  5         count( decode( status, 4, 1 ) ) "Not Published",
  6         count( decode( status, 5, 1 ) ) "Published",
  7         count(*)
  8  from WWC_ASK_SUBMITTED_QUESTIONS$ where timestamp >= to_date(
'14-aug-2003')
  9  group by  trunc(timestamp)
 10* order by 1
ask_tom@ASKUS> /

TRUNC(TIM   Not Read    Waiting Processing Not Published  Published   COUNT(*)
--------- ---------- ---------- ---------- ------------- ---------- ----------
14-AUG-03          0          0          0             5          4          9
15-AUG-03          0          0          0            11          4         15
16-AUG-03          0          0          0            25          4         29
17-AUG-03          0          0          0             9          2         11
18-AUG-03          0          0          0             3          0          3
22-AUG-03          0          0          0            11          3         14
23-AUG-03          0          0          0             8          1          9
24-AUG-03          0          0          0             5          2          7
29-AUG-03          0          0          0             5          3          9
30-AUG-03          0          0          0             4          3          7
02-SEP-03          0          0          0             6          3          9
06-SEP-03          0          0          0            25         10         35
07-SEP-03          0          0          0            33          4         37
08-SEP-03          0          0          0            14          8         22
09-SEP-03          0          0          0            16          3         19
11-SEP-03          0          0          0             6          4         10
12-SEP-03          0          0          0             5          6         11
13-SEP-03          0          0          0             9          1         10
14-SEP-03          0          0          0             4          4          8
15-SEP-03          0          0          0             0          2          2
16-SEP-03          0          0          0            12          3         15
17-SEP-03          0          0          0             4          6         10
20-SEP-03          0          0          0             4          6         10
21-SEP-03          0          0          0            16          6         22
23-SEP-03          0          0          0            11          2         13
26-SEP-03          0          0          0            14          7         21
27-SEP-03          0          0          0            11          6         17
28-SEP-03          0          0          0            19          6         25
29-SEP-03          0          0          0             9          4         13
01-OCT-03          0          0          0            12          1         13
02-OCT-03          0          1          0            18          5         24
04-OCT-03          0          1          0            21          2         24
05-OCT-03          0          0          0            20          5         25
06-OCT-03          0          0          0            10          2         12
10-OCT-03          0          1          0            11          1         14
11-OCT-03          0          0          0            14         11         25
12-OCT-03          0          1          0            11          1         15
19-OCT-03          0          1          0             6          1          8
22-OCT-03          0          0          0             3          2          5
24-OCT-03          0          0          0             8          2         10
25-OCT-03          0          1          2             9          2         22

41 rows selected.

so, while I've been taking the same number of questions -- the
"publishability" of them isn't very "high"

which led me to ask "is this a trend", is the ratio of published to submitted
questions going down over time:

ask_tom@ASKUS> ask_tom@ASKUS> select trunc(timestamp,'mm'),
       count( decode( status, 4, 1 ) ) "Not Published",
       count( decode( status, 5, 1 ) ) "Published",
       round( count( decode( status, 5, 1 ) )/count(*)*100, 2 ) avg_pub,
       count(*)
from WWC_ASK_SUBMITTED_QUESTIONS$
group by  trunc(timestamp,'mm')
order by 1
/
  2    3    4    5    6    7    8    9
TRUNC(TIM Not Published  Published    AVG_PUB   COUNT(*)
--------- ------------- ---------- ---------- ----------
01-MAY-00           861        372      30.17       1233
01-JUN-00           278        282      50.36        560
01-JUL-00           241        190      44.08        431
01-AUG-00           453        404      47.14        857
01-SEP-00           521        350      40.18        871
01-OCT-00           320        245      43.36        565
01-NOV-00           285        234      45.09        519
01-DEC-00           327        223      40.55        550
01-JAN-01           361        252      41.11        613
01-FEB-01           485        286      37.09        771
01-MAR-01           568        280      33.02        848
01-APR-01           329        193      36.97        522
01-MAY-01           634        280      30.63        914
01-JUN-01           477        179      27.29        656
01-JUL-01           473        180      27.57        653
01-AUG-01           404        192      32.21        596
01-SEP-01           388        135      25.81        523
01-OCT-01           542        179      24.83        721
01-NOV-01           453        155      25.49        608
01-DEC-01           451        132      22.64        583
01-JAN-02           354        130      26.86        484
01-FEB-02           207         86      29.35        293
01-MAR-02           305        203      39.96        508
01-APR-02           300        210      41.18        510
01-MAY-02           281        161      36.43        442
01-JUN-02           289        180      38.38        469
01-JUL-02           250        158      38.73        408
01-AUG-02           402        211      34.42        613
01-SEP-02           331        206      38.36        537
01-OCT-02           263        196       42.7        459
01-NOV-02           287        147      33.87        434
01-DEC-02           485        212      30.42        697
01-JAN-03           562        190      25.27        752
01-FEB-03           300        124      29.25        424
01-MAR-03           399        161       28.7        561
01-APR-03           226         85      27.33        311
01-MAY-03           245         77      23.91        322
01-JUN-03           380        115      23.23        495
01-JUL-03           152         44      22.45        196
01-AUG-03           167         61      26.64        229
01-SEP-03           218         91      29.45        309
01-OCT-03           143         35      17.77        197

42 rows selected.

and sure enough, it does look like it is trending "down".  It historically has
been for every one you see, there are 2 more you don't.  Now it looks like that
is going up a bit -- meaning the questions are redundant (asked and answered 100
times before) or just so not relevant to the general populace.

It is that and the fact the I've been on the road 4 or 5 days a week since the
end of August :)

You can see when I started writing in earnst for this last book -- noticable dip
in April.  I finished in July (but spent 2 weeks on vacation in july/august.
Rating: 4
Theory book October 26, 2003
Reviewer: Doug from CT, USA
Out of curiosity - is this the light reading?
Transaction Processing: Concepts and Techniques (Morgan Kaufmann Series in Data
Management Systems)
by Jim Gray, Andreas Reuter  ??
Followup:
Not a chance, it is a college level textbook and above.

It is very deep and full of theory as I recall (its been a couple of years, i
read it in its first year of publication).  It was not something to read from
cover to cover but rather section by section as you became interested in the
technology/area.

I still remember sections of it, I really liked the analogy of two phase commit
to a marriage ceromony.  That exactly describes the situation.....  The
"preacher" if you will is the commit point.  "do you take this woman" (are you
prepared to commit), "do you take this man" (are you prepared to commit), if
both sites vote yes, then broadcast the outcome "i now pronounce you...."  else
rollback.

the reviews on amazon convey the basic "goodness" of this book and its breadth.
Rating: 5
Do you still read the news in Oracle newsgroup? October 26, 2003
Reviewer: David
Hi Tom,

  I initially post my Oracle questions to this newsgroup when I am an Oracle
beginner. There are many great posters in this group such as Daniel, Howard or
Norman. Do you still read the questions in this group?

David
Followup:
Not really -- something had to give.  I used to troll them regularly from 1994
till about 2001 or so.

Between

o my job
o my life
o writing
o asktom
o oracle magazine
o travel

i had to give up something.  The newsgroups were it.  As you said, there are
many people out there such as HJR, Richard Foote, Jonathan Lewis, Connor
MacDonald, Dan Morgan, and others (bad to put a list, i've left out more than I
put in) so I constantly refer people to it, but I've not had the bandwidth to
participate recently.
Rating: 3
Doing things in the database November 04, 2003
Reviewer: A reader
Hi Tom,

you wrote

<TOMKYTE>
Even though I was a coder -- i found
doing things in the database to make tons more sense then writing tons of code
to do the same.  I actually quit my first job after six years soley for the
reason that they wouldn't let me exploit the software they paid so much for
(they were so into this "generic database", the "database is a commidity"
concept that they had me coding our own two phase commit protocol, our own
stored procedure language, our own compilers for the same, our own sql parser --

it was frightening.  Funny thing is -- I meet with customers all of the time
still doing just that!)
</TOMKYTE>

Fully agree.
I am on my 4th job now in 12 years (always chose "small"
software-companys up to 100 developpers)
and programming Oracle all the time (not only, of course,
but Oracle makes the best software I know).

Got the same experience everywhere (and still get it):
"database is just a stupid data-store" is a *VERY COMMON*
meaning.
Don't know why, I show my colleagues / bosses
how easy and - most important - performant doing things
*in the database* is, but most of them just don't see it.

Still frustrated,
Matthias
Rating: 5
Great thread, I am really inspired. November 07, 2003
Reviewer: Amalorpavanathan Yagulasamy from Boston, USA
Tom,
Great thread, I am really inspired.

Regards,
Amal
Rating: 4
Thanks for the question regarding "Oracle: New world", version 8.1.7 December 22, 2003
Reviewer: Leonard Anukam from Finland
Thanks Tom, in fact you have a great influenc on us up and coming Oracle
Programmers. But one thing that stands out with you is you ability to share your
knowledge. I have approched many people to be a mentor and they fear that their
knowledge will be stolen and they refuse to be. You are so generous with you
know-how in programing.

I have a problem myself. I like PLSQL programming and find it hard to do sql
proper. What do you suggest I should concentrate more.

Thanks
Followup:
How can you "steal" knowledge -- that's silly.

In order to become proficient at something, you have to "just do it".  Try to
solve things using pure SQL (give it an extra five minutes of thought) before
dropping into a procedural row by row approach.  make sure you KNOW every tool
you have (connect by, analytics, having clauses, rollup, cube, etc) -- if you
don't have that knowledge, you cannot be successful
Rating: 3
developer and dba relationship December 22, 2003
Reviewer: Ryan from Tysons Corner, VA
It seems that most shops believe that developers and DBAs are two totally
different skill sets. Having done this for a few years now, I think the skill
sets overlap. I think a truly 'senior' developer should have strong 'dba' skills
and a DBA should be able to code at atleat a mid-level.

I think all senmior developers should should understand architecture, tuning,
some backup and recovery, etc...

Im troubled by the way organizations totally seperate roles and often run into
problems when someone who cant even code puts restrictions on development or
when a developer makes some silly assumptions about the database.

What is your opinion on this? I think it is 100% possible to be both a very good
developer and a very good DBA. You do not need to choose one or the other.

PS-- you need to do something about the certification tests. The performance
tuning OCP is TOTALLY inaccurate. They have you study for things that you tell
us not to do on this very website(namely hit ratios and simply 'make it bigger'
as a cure all).
Followup:
I'm with you 100% on the dba/developer thing.  I think they are a team, not
warring camps in a fight to the death.
Rating: 5
December 22, 2003
Reviewer: A reader
"two phase commit to a marriage ceromony" ... Hahahaha..thats a good example..
Rating: 5
Veering off the track just a little... December 22, 2003
Reviewer: A reader
Hey Tom,

Excellent thread, and a long-awaited insight into your professional development
and experience. I'd like to share my experience, and would be interested in your
feedback. In 5 plus years of programming, I have reached the conclusion that
programmers are perceived as just a group of people who should get the job done,
without much direction from the management/team leads and the larger team, and
without raising any issues. Their attitude frequently is: "how hard can it be to
program?", and think that if a programmer has some concerns/issues with
something, that he/she is incompetent. They think of programmers as a bunch of
mechanics (no disrespect intended towards the mechanic populace) who should know
how to work with a car without complaining. A thankless job, basically. Most of
the time I ignore this sentiment exuded by the team and keep my head down and
work as hard as I can, but sometimes I am not satisfied with just pleasing
myself. I mean, if you don't have the respect of your peers and superiors, even
though you might be solving hard problems using intelligent methods and
different features, after a certain point you start becoming dissatisfied with
yourself and your work, and question the wisdom in applying yourself as much as
you do. I know a job/career that a person does has to be one that he/she loves,
even if others don't love him/her for it, but there has to be some incentive at
the end of the day to excel and keep improving. I'm curious to know how you've
dealt with these types of situations (where your work is not appreciated), if
you've come across them, and what kept you on track and motivated.

Also, related to this, I agree that you're philosophy of doing it right the
first time is the way to do it, but I can't help but wonder if there is some
justification to not worrying about performance upfront, and then fixing
performance issues as they come up, the benefit being that you're perceived as
the hero when you fix the problem, and earn respect that way. If you get it
right the first time (while designing the system), people may not appreciate the
labor and effort you put in. I have countless examples in my experience where
I've been appreciated more when I've fixed performance issues after the fact on
systems I’ve inherited, but not nearly enough examples of where my superior
design has been lauded by everyone.

Thanks for any feedback.
Followup:
well, in reverse -- as to the "do it bad, then fix it"

How can you be a hero if you messed it up the first time around?  You are just
correctly your mistake.

So, when YOU do the work, do it right the first time (you'll be appreciated).
As for systems you inherit, maybe that looks good for you -- but the people who
designed it in the first place?

I had the opposite experience from you.  I went to work as a "programmer
analyst".  I grew to "acclaim" in that job because I could take many of the
problems and

a) solve them,
b) solve them quickly (i read EVERYTHING i could -- I knew what tools I had
available to me and how to use them all),
c) solve them the first time and finally (my stuff wasn't redone)

I worked in a pure software shop -- it was the only thing we did (that was our
output).  The two layers of mgmt above me was all ex-programmer/analyst (they
could tell the good from the bad).

It could be your environment more then anything -- but I find the people that do
it right, don't have to do it over, and deliver working product the first time
around definitely get noticed -- in the right environment.

But I did get into an environment (described above) where I felt much as you did
-- exactly as you describe.  Where the customer requirement of "be database
independent, don't use any database feature, write a DBMS so we can use any DBMS
(how silly is that!)" just killed me.  It was like treading water.  You don't go
forward, you just hope you don't sink.  You know you are writing shelfware
(funny -- the project is STILL going, 13 years later......)

I left.  Much happier now :)
Rating: 2
dba/developer interaction December 22, 2003
Reviewer: Ryan from tysons corner, va
i wasnt referring to warring camps. Im referring to overlapping skillsets. I
feel that good developers should also be pretty knowledgeable of dba tasks and
vice versa.

i dont like working with dbas who cannot write code and i do not like working
with developers who do not know architecture, etc...

too many mistakes. seems like most shops feel the jobs are totally seperate and
you should be one or the other. I dont see any reason why most people cant do
both, but be 'specialized' in one or the other.

how do you feel or should the skillsets be seperate?
Followup:
...
Im troubled by the way organizations totally seperate roles and often run into
problems when someone who cant even code puts restrictions on development or
when a developer makes some silly assumptions about the database
.....

sounds a little like "camps" to me -- but anyway -- I firmly believe there
should be a database development TEAM consisting of the developers and the DBAs.

how can a DBA make policy if they don't understand the ramifications.  Like the
DBA that says "no, no, no, you cannot have access to the trace files on this
development box".  They've just shot themselves in the head (not the foot, the
head).  No way that system can be tuned or inspected by the developers.  I walk
away from those sites (they usually let me have access after that).

how can a developer tune or design a system if they don't understand the
ramifications.  "ah, look, nologging, nologging is fast -- go for it".  Did they
just mess up the ability for this database to be recovered or what (yes).
Unless they work with the DBA's who understand the ramification of what they are
doing (you can use nologging safely, you just have to INCORPORATE backups with
it!)......
Rating: 5
DBA vs. Developers December 22, 2003
Reviewer: Mark A. Williams from Indianapolis, IN USA
For a good read...

Effective Oracle by Design does a nice job of covering this I think.  Tom
addresses this mentality and shows why it is so harmful right from the beginning
of the book.

We are developing an interal training course to address this particular
situation at my company for what it is worth.

- Mark
Rating: 5
seconded December 22, 2003
Reviewer: Stu Charlton from Toronto ON Canada
I second Tom's view of what it takes to be the master of your game...

a) read read read read!  concept manuals, user manuals, books, and theory
b) read the newsgroups and mailing list archives
c) try stuff out! experiment! benchmark! prove your ideas!
d) re-read stuff constantly, keep trying out old discoveries to keep them fairly
fresh in your mind.

It's funny, I read the oracle.server newsgroup semi-regularily and there was a
recent thread where one guy stated it's impossible for one man to have as much
knowledge as Tom Kyte, he must be a support shill...

I don't know why people are so down on this ability.  As long as you LIKE
technology, you can aspire to be the next Tom Kyte ... it's not impossible, just
requires passion, curiosity, and unwillingness to compromise for the "easy" rule
of thumb.
Rating: 5
proactive or reactive reading ? December 22, 2003
Reviewer: Alberto Dell'Era from Milan, Italy
> i read EVERYTHING i could -- I knew what tools I had
> available to me and how to use them all

Did you read and absorbe/experiment everything in deep, or just read "lightly"
and get back to the book/doc when need arised to use some "feature" ?

Which approach would you suggest ?

Thanks
Alberto
Followup:
i read everything quick.

interesting things I played with.

everything else i just sort of "filed away".

then when presented with a problem -- i would say "ahh, you know, that reminds
me of something I read about".  Sometimes it would be a miss (go back, read in
depth and find it doesn't really apply), sometimes it would be a hit.
Rating: 5
Nice to get detals about tom December 23, 2003
Reviewer: Tarun Babu from Inida
Hi tom,
 Its nice to see this thread in asktom.oracle.com. You really inspired me a lot.
I am follwoing this site from the begining. From last two years I going to otn
forums, posting questions, learning new things and helping people what ever I
know in oracle.
The point what you mentioned is "How can Knowledge be stolen". Perfet. I wanted
to share this with everyone, Please share your knowledge, whatever you know.
That will increase the boundaries of our knowledge day by day.

What's your view tom?
Thanks.
Rating: 5
Mentor December 23, 2003
Reviewer: umesh from blore india
Tom
Can you please share your mentor's name. really must be a GREAT GUY ( no pun )
Rating: 3
Aaaah Mentor... December 23, 2003
Reviewer: Tarry Singh from Assen, Holland
Your next door neighbour could be your mentor, you could be your own mentor.

To be succesful in any business you gotta (be)...

[1] Smart
[2] Brainy(not necessarily a rocket scientist)
[3] Practice, practice, practice
[4] Try, try ,try
[5] fail, fail, fail
[6] succeed, succeed, succeed
[7]Remember not to repeat mistakes, ever.

Then you've got a formula working right for you.

That it.
Rating: 4
Also about the DBA/Developer thingy... December 23, 2003
Reviewer: Tarry from Assen, Netherlands
The fact that developer's & dba's are on the same side is something which you
tend to realize but thanks to reading and listening to what Tom's has written in
his books and the daily reads that I do(as on all my machine's ASKTOM is my
homepage)I have learnt to converse more sensibly to the developers.

It felt real great in the new job when the most experienced and feared developer
and me were beginning to click on in a few weeks and strating to exchange ideas.
The "over the wall" mentality is changing. I was able to (with tact) bring in
ideas of how to start using the db's built in functionality instead of long
generic code and we're seeing some good team work. When I go to work I leave my
ego, whim's all behind.

It is not just that they "must"(I say should)work together but also to bridge
the (ever growing) gap between the developer's and dba's/sa's. So there's lot's
more to it than just getting bunch of "stuck up" guys to work together.
December 23, 2003 Clark -- Thanks for the question regarding "Indexes - indexing only selected rows - possible?", version 817
You Asked
Hi Tom,

I may be behind the times - 817's as far as I've got - but I just wondered if it
was possible to create indexes only on selected rows - for example, when the
column being indexed is null I've seen lots of examples in various applications
I've worked with, where I know for a fact I'm never ever going to look at rows
(using this index) that have null in the column.

It strikes me that there's a potential space-saving feature (memory as well as
disk I suppose) if it was possible to tell the index "only index columns which
have these values..." (or the converse - "don't index these columns..").

As ever, your thoughts will be much appreciated.

Cheers,
Clark.
and we said...
well, first of all -- if the index entry is entirely NULL, we'll never put that
in the index anyway!

consider:

ops$tkyte@ORA920> create table t ( x int );

Table created.

ops$tkyte@ORA920> create index t_idx on t(x);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t select null from all_users;

51 rows created.

ops$tkyte@ORA920> analyze index t_idx validate structure;

Index analyzed.

ops$tkyte@ORA920> select name, lf_rows from index_stats;

NAME                              LF_ROWS
------------------------------ ----------
T_IDX                                   0

that index is empty -- nothing in it, put in a non null row and:

ops$tkyte@ORA920> insert into t values ( 1 );

1 row created.

ops$tkyte@ORA920> analyze index t_idx validate structure;

Index analyzed.

ops$tkyte@ORA920> select name, lf_rows from index_stats;

NAME                              LF_ROWS
------------------------------ ----------
T_IDX                                   1

we finally have an entry.  make then all not null and:

ops$tkyte@ORA920> update t set x = rownum;

52 rows updated.

ops$tkyte@ORA920> analyze index t_idx validate structure;

Index analyzed.

ops$tkyte@ORA920> select name, lf_rows from index_stats;

NAME                              LF_ROWS
------------------------------ ----------
T_IDX                                  53

they are all in there...

Now, to selectively index -- we can use CASE with a complex predicate to ID the
values we want -- in this case, i'll index all rows where is less than 25:

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index t_idx2 on t( case when x < 25 then x end );

Index created.

ops$tkyte@ORA920> analyze index t_idx2 validate structure;

Index analyzed.

ops$tkyte@ORA920> select name, lf_rows from index_stats;

NAME                              LF_ROWS
------------------------------ ----------
T_IDX2                                 24

only those rows are in the index.  further, we can (should i think) use a view
to hide the complexity of querying via this index:

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace view v as select x, case when x < 25 then x
end another_x from t;

View created.

ops$tkyte@ORA920> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select * from v where another_x = 5;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=2)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=1 Bytes=2)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX2' (NON-UNIQUE) (Cost=2 Card=1)

ops$tkyte@ORA920> set autotrace off

see
http://asktom.oracle.com/~tkyte/article1/index.html
for details on function based indexes
Review & Followup

Rating: 5
December 20, 2003
Reviewer: A reader
Rating: 4
difference in explain plan using both approaches December 21, 2003
Reviewer: A reader
Hi Tom,

I followed your same example with exact index creation.
But when i looked at the query plan using autotrace, i see
the first query against the table (T) is doing INDEX range scan Where as the
second query against view (which is using selective rows indexing) doing full
table scan)and
Consistent gets are more.

Could you explain why.

The statisctics are below :

SQL> select count(*) from t ;

  COUNT(*)
----------
      3840

1 row selected.

SQL> create index t_idx2 on t( case when x < 25 then x end );

Index created.

Elapsed: 00:00:00.04

SQL> exec dbms_stats.gather_table_stats('BALA','T', estimate_percent => null,
method_opt => 'for all indexed columns size 1', cascade => true, degree => 2) ;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.17

SQL> 21:53:56 IOMDV:SQL> select * from t where x=5 ;

         X
----------
         5

1 row selected.

Elapsed: 00:00:00.01

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=1 Card=1 Bytes=4
          )

   1    0   INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=1
          Bytes=4)

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

SQL> select * from v where another_x=5 ;

         X  ANOTHER_X
---------- ----------
         5          5

1 row selected.

Elapsed: 00:00:00.01

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=1 Card=39 Bytes=
          156)

   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=1 Card=39 Bytes=156)
Followup:
you do not mention your version, but it could be 8i i suppose (that's why I
posted the link to the article with all of the details on FBI's)

you might need to have query rewrite enabled.

ops$tkyte@ORA817DEV> create table t as select object_id x from all_objects where
rownum < 4000;

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select count(*) from t ;

  COUNT(*)
----------
      3999

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create index t_idx2 on t( case when x < 25 then x end );

Index created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> exec dbms_stats.gather_table_stats(user,'T', method_opt =>
'for all indexed columns size 1', cascade => true );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace view v
  2  as
  3  select x, (case when x < 25 then x end) another_x from t;

View created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> set autotrace traceonly explain
ops$tkyte@ORA817DEV> select * from t where x=5 ;

Execution Plan
----------------------------------------------------------
   0     SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=40 Bytes=520)
   1   0   TABLE ACCESS (FULL) OF 'T' (Cost=1 Card=40 Bytes=520)

ops$tkyte@ORA817DEV> select * from v where another_x=5 ;

Execution Plan
----------------------------------------------------------
   0     SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=40 Bytes=520)
   1   0   TABLE ACCESS (FULL) OF 'T' (Cost=1 Card=40 Bytes=520)

ops$tkyte@ORA817DEV> alter session set query_rewrite_enabled=true;

Session altered.

ops$tkyte@ORA817DEV> select * from v where another_x=5 ;

Execution Plan
----------------------------------------------------------
   0     SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=13)
   1   0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=1 Card=1 Bytes=13)
   2   1     INDEX (RANGE SCAN) OF 'T_IDX2' (NON-UNIQUE) (Cost=1 Card=1)

ops$tkyte@ORA817DEV> set autotrace off
ops$tkyte@ORA817DEV>
Rating: 4
Continuation of the previous second query autotrace December 21, 2003
Reviewer: A reader
SQL> select * from v where another_x=5 ;

         X  ANOTHER_X
---------- ----------
         5          5

1 row selected.

Elapsed: 00:00:00.01

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=1 Card=39 Bytes=
          156)

   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=1 Card=39 Bytes=156)

Statistics
----------------------------------------------------------
          7  recursive calls
          4  db block gets
         10  consistent gets
          0  physical reads
          0  redo size
        420  bytes sent via SQL*Net to client
        425  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed
Rating: 5
Yes, 8i was the reason but one more thing December 22, 2003
Reviewer: muruganbalas@yahoo.com
Hi Tom,

Thank you. Yes i am using 8.1.7.4. I need to use
query rewrite feature. Now my query against view is doing index range scan.

However the consistent gets seem to be very high comparing with querying against
a table (full indexing a column).

Here are my statistics :

SQL> select count(*) from t ;

  COUNT(*)
----------
    122880

1 row selected.

SQL> exec dbms_stats.gather_table_stats('BALA','T', method_opt => 'for all
indexed columns size 1', cascade => true );

PL/SQL procedure successfully completed.

select * from t where x=5 ;

         X
----------
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5
         5

32 rows selected.

Elapsed: 00:00:00.01

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=1 Card=32 Bytes=
          128)

   1    0   INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=32
           Bytes=128)

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

SQL> select * from v where another_x=5 ;

         X  ANOTHER_X
---------- ----------
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5
         5          5

32 rows selected.

Elapsed: 00:00:00.02

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=2 Card=32 Bytes=
          128)

   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=32 Bytes
          =128)

   2    1     INDEX (RANGE SCAN) OF 'T_IDX2' (NON-UNIQUE) (Cost=1 Card
          =32)

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

Index stats :
*************

SQL> analyze index T_IDX validate structure ;

Index analyzed.

SQL> select name, lf_rows from index_stats ;

NAME                                             LF_ROWS
--------------------------------------------- ----------
T_IDX                                             122880

1 row selected.

Elapsed: 00:00:00.03
SQL> analyze index t_idx2 validate structure ;

Index analyzed.

Elapsed: 00:00:00.00
SQL> select name, lf_rows from index_stats ;

NAME                                             LF_ROWS
--------------------------------------------- ----------
T_IDX2                                               768

1 row selected.

Could you explain why consistent gets is higher while running against a VIEW
(almost 7 times bigger than against table).

Thanks
Followup:
see the recursive sql (7 recursive sql calls)

just run it again, you hard parsed.  it ran other sql for you.
Rating: 4
Thanks December 22, 2003
Reviewer: A reader
Tom,

This means that it is a expected performance behaviour on views.

I understand that it is doing recursive sql.

Then, is there any way to reduce the consistent gets while querying against
views. Why should i do this in first place using view in stead of table, if i am
doing more logical io.

Thanks for any information.
Followup:
no, just run the query again:

ops$tkyte@ORA9IR2> create table t as select rownum id, all_objects.* from
all_objects;

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace view v as select t.*, case when id < 25
then id end another_id  from t;

View created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create index t_idx1 on t(case when id < 25 then id end);

Index created.

ops$tkyte@ORA9IR2> create index t_idx2 on t(id);

Index created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> analyze table t compute statistics for table for all indexes
for all indexed columns;

Table analyzed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> set autotrace traceonly
ops$tkyte@ORA9IR2> select * from t where id < 10;

9 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=9 Bytes=909)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=9 Bytes=909)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX2' (NON-UNIQUE) (Cost=2 Card=9)

Statistics
----------------------------------------------------------
        269  recursive calls
          0  db block gets
         33  consistent gets
          0  physical reads
          0  redo size
       1869  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          9  rows processed

ops$tkyte@ORA9IR2> select * from v where another_id < 10;

9 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=9 Bytes=909)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=9 Bytes=909)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX1' (NON-UNIQUE) (Cost=1 Card=9)

Statistics
----------------------------------------------------------
         14  recursive calls
          0  db block gets
          8  consistent gets
          0  physical reads
          0  redo size
       1954  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          9  rows processed

ops$tkyte@ORA9IR2> select * from t where id < 10;

9 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=9 Bytes=909)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=9 Bytes=909)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX2' (NON-UNIQUE) (Cost=2 Card=9)

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

ops$tkyte@ORA9IR2> select * from v where another_id < 10;

9 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=9 Bytes=909)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=9 Bytes=909)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX1' (NON-UNIQUE) (Cost=1 Card=9)

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

ops$tkyte@ORA9IR2> set autotrace off
Rating: 5
Thank you for the great explanation December 22, 2003
Reviewer: A reader
Hi Tom,

Now i am getting the same statistics like yours.

querying against the view looks better now than table.

SQL> select * from t where id<10 ;

9 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=3 Card=10 Bytes=
          920)

   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=10 Bytes
          =920)

   2    1     INDEX (RANGE SCAN) OF 'T_IDX2' (NON-UNIQUE) (Cost=2 Card
          =10)

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

SQL> /

9 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=3 Card=10 Bytes=
          920)

   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=10 Bytes
          =920)

   2    1     INDEX (RANGE SCAN) OF 'T_IDX2' (NON-UNIQUE) (Cost=2 Card
          =10)

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

SQL> select * from v where another_id<10 ;

9 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=2 Card=10 Bytes=
          920)

   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=10 Bytes
          =920)

   2    1     INDEX (RANGE SCAN) OF 'T_IDX1' (NON-UNIQUE) (Cost=1 Card
          =10)

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

SQL> /

9 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=2 Card=10 Bytes=
          920)

   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=10 Bytes
          =920)

   2    1     INDEX (RANGE SCAN) OF 'T_IDX1' (NON-UNIQUE) (Cost=1 Card
          =10)

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

SQL> /

9 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=2 Card=10 Bytes=
          920)

   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=10 Bytes
          =920)

   2    1     INDEX (RANGE SCAN) OF 'T_IDX1' (NON-UNIQUE) (Cost=1 Card
          =10)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          4  consistent gets
          0  physical reads
          0  redo size
       2113  bytes sent via SQL*Net to client
        425  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          9  rows processed
Rating: 4
Learned something new today December 23, 2003
Reviewer: Mike from Minneapolis, USA
Tom, you said:

Now, to selectively index -- we can use CASE with a complex predicate to ID the
values we want -- in this case, i'll index all rows where is less than 25:

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index t_idx2 on t( case when x < 25 then x end );

Index created.

Is this basically a function based index behind the scenes?
I created one using 8i and the DDL gave me something like:

   CREATE INDEX XYZ ON T( SYS_NC00123$ );

Very interesting.  Why would one only want to index
some, but not all the rows in a table???

Thanks in advance, and have a nice holiday!
Followup:
yes, it is a function based index.

Perhaps the table is huge (millions of fat rows)

2% of the data has a status column (x) with x values <25.  The rest are all x=25
(status = completed for example, old data).

so, we need to query "where x = 5", "where x = 10" lots to get a few rows -- the
index would be pretty beefy however (and totally skewed).

We could have an index that is tiny by indexing JUST the values of interest
(everything except for 25).

fast access
small index
December 23, 2003 Seshu -- Thanks for the question regarding "How to split a partition", version 8.0.5
You Asked
Hi,

Sub  : Table  PARTITION - Split

Can we  SPLIT the lower partition of the table ?

Ex :
I've a partition table like this :

P-1_QTR date less than '04-01-2000'
P-2_QTR date less than '07-01-2000'
P-3_QTR date less than '10-01-2000'
P-4_QTR date less than '01-01-2001'.

Now,I need to seperate the first quarter data into MONTHLY
partitions like :

P1_MTH  date less than '02-01-2000'
P2_MTH  date less than '03-01-2000'
P3_MTH  date less than '04-01-2000'
P-2_QTR date less than '07-01-2000'
P-3_QTR date less than '10-01-2000'
P-4_QTR date less than '01-01-2001'.

Can we able to SPLIT the lower partiton boundary ?
If YES, could you pleae POST the SYNTAX for this.?

Thanks & Regards
Seshu Gonela
and we said...
Yes, you can split any partition you want via the ATLER TABLE
SPLIT command (see the SQL reference for ALL of the options
please..).  Here is a quick example:

tkyte@ORA806.WORLD> CREATE TABLE part
  2  ( dt date )
  3  PARTITION BY RANGE( dt )
  4  ( PARTITION p_1_qtr VALUES
  5    LESS THAN( to_date('04-01-2000','mm-dd-yyyy') ),
  6    PARTITION p_2_qtr VALUES
  7    LESS THAN( to_date('07-01-2000','mm-dd-yyyy') ),
  8    PARTITION p_3_qtr VALUES
  9    LESS THAN( to_date('10-01-2000','mm-dd-yyyy') ),
 10    PARTITION p_4_qtr VALUES
 11    LESS THAN( to_date('01-01-2001','mm-dd-yyyy') )
 12  )
 13  /

Table created.

tkyte@ORA806.WORLD> insert into part values (
to_date('02-01-2000')-1 );
...(other inserts)....

tkyte@ORA806.WORLD> select * from part partition (p_1_qtr);

DT
----------
01-31-2000
02-29-2000
03-31-2000

tkyte@ORA806.WORLD> select * from part partition (p_2_qtr);

DT
----------
06-30-2000

tkyte@ORA806.WORLD> select * from part partition (p_3_qtr);

DT
----------
09-30-2000

tkyte@ORA806.WORLD> select * from part partition (p_4_qtr);

DT
----------
12-31-2000

tkyte@ORA806.WORLD>
tkyte@ORA806.WORLD> alter table part
  2  split partition p_1_qtr
  3  at ( to_date( '02-01-2000', 'mm-dd-yyyy' ) )
  4  into ( partition p1_mth, partition p_1_qtr );

Table altered.

tkyte@ORA806.WORLD>
tkyte@ORA806.WORLD> alter table part
  2  split partition p_1_qtr
  3  at ( to_date( '03-01-2000', 'mm-dd-yyyy' ) )
  4  into ( partition p2_mth, partition p3_mth );

Table altered.

tkyte@ORA806.WORLD>
tkyte@ORA806.WORLD> select * from part partition (p1_mth);

DT
----------
01-31-2000

tkyte@ORA806.WORLD> select * from part partition (p2_mth);

DT
----------
02-29-2000

tkyte@ORA806.WORLD> select * from part partition (p3_mth);

DT
----------
03-31-2000

tkyte@ORA806.WORLD> select * from part partition (p_2_qtr);

DT
----------
06-30-2000

tkyte@ORA806.WORLD> select * from part partition (p_3_qtr);

DT
----------
09-30-2000

tkyte@ORA806.WORLD> select * from part partition (p_4_qtr);

DT
----------
12-31-2000

tkyte@ORA806.WORLD>
Review & Followup

Rating: 5
Very helpful January 02, 2003
Reviewer: A reader
If the partition is not the last one in a table.  This way could save a lot of
time on drop and recreate.
Rating: 4
rollback December 22, 2003
Reviewer: A reader
Hi

Does split partition use Rollback Segment?
I have been trying to find out myself but my tables arent big enough and the
operation is very fast so I cant check v$transaction. Do you know a way to find
out if my session has used rollback segment after committing a transaction or
DDL?

In fact... do you know what DDL uses RBS? Cant find anything in the
documentation

Cheer
Followup:
it uses rollback to protect the DDL (the data dictionary) but the direct path
operation on the data segment itself won't be recorded in the UNDO (just the DDL
changes)

Now, indexes -- if they are maintained during this operation, the will generate
UNDO.

All DDL uses UNDO.
The operation being performed may not.

for example:

create table T as select * from billion_row_table;

that'll generate UNDO to protect the data dictionary -- but the table T itself
will not contribute to UNDO.
Rating: 5
split December 22, 2003
Reviewer: A reader
Hi

A further question, if we split partitions are we working with temporary
segments then once the operation finishes these temp segments become the new
partitions? I mean we should need temp segments to provide query and ddl
protection right? In case the instance fails

Same applies to CTAS, do we need twice segment space when we run a CTAS on a big
table?
Followup:
yup.

you do not need 2x the space however, the temp segments are created in the
target tablespace and converted to permanent segments at the end of the
operation.
Rating: 3
Sidenote about split December 22, 2003
Reviewer: Skip from Conway, AR USA
Just make sure you specify the tablespace when using split.  It will not split
the tablespace and leave both partitions in the original tablespace if you do
not specify. It will put the new partitions in your default tablespace.
Rating: 4
Combine Partitions December 22, 2003
Reviewer: David from Atlanta
Tom,

Is it possible to combine partitions?  We have a large OLTP/Warehouse
application, and we have had to reduce the size of our partitions in order to
keep our load rates up.  Ideally, we would need one partition per day for data
warehousing purposes.  However, we have had to break that down into 16
partitions in order keep the load rates up.  It would be nice if we could
combine the 16 partitions into one after the inserts are finished.  All of the
queries span all 16 partitions.  We insert approximately 400 million records per
day into the table, and must maintain 10,000 records inserted per second during
the busy hours.
Followup:
that would sort of defeat the purpose of having 16 to load would it not?

(yes we can, but it would take a ton of work to put them all together)

why did you feel the need for the 16 partitions -- in the first place.  what
problem did they solve?
Rating: 3
RE: Combine Partitions December 22, 2003
Reviewer: David from Atlanta
Tom,

Thanks for the reply.  I did not think that it would be practical.

We went with 16 partitions in order to increase our load performance.  We found
that as the size of the partition increased, specifically the local indexes, the
load rates dropped off significantly to a point that the loads would not keep up
with the rate that records were coming in.
Followup:
did you determine a root cause?

perhaps it could have been as easy as "multiple freelists is what we need to
remove contention"
Rating: 3
RE:Combine Partitions December 22, 2003
Reviewer: David from Atlanta
We played around with freelist, but that did not seem to be a factor.  We are
not updating or deleting, just inserting records and dropping partitions.  The
only thing that we could see in the statspack report was a large number of
sequential reads. There were no significant waits.  We sent that stuff off to
Oracle, but their response was just to not do so many inserts.
Rating: 3
What was the result of combining the partitions? December 23, 2003
Reviewer: Bob Maggio from Troy NY
We are looking are removing a partition structure, because it was inadequatly
planned and is causing some complexity. So instead of combining partitions, we
decided to remove them completely from the table. i.e. create new_table as
select * from old_partitioned_table and rename all. The tables contain BLOB
fields. Is there any issues with doing this? Will the BLOBs affect anything
before or after the partitioning?
Followup:
the CTAS will take care of coping the blobs as well.

you can use parallel/nologging as well to help speed it up if you like.
December 23, 2003 -- Thanks for the question regarding "PCTVERSION settings for blobs", version 8.1.7
You Asked
Hi Tom,

We have an application where it loads huge amounts of data in "blobs" and then
modifies the data and may/may not delete it. Now, the pctversion settings for
these blobs are set @ 80%. When I questioned this, the reply was anything below
80%, we are getting "snapshot too old" error. Now this is a huge wastage of
space right? In my understanding, since blobs don't use rollback segments, the
pctversion setting of the blobs is used for read consistency. So, 80%
pctversion, how justifiable is that? Is there a workaround for this?
In one of the books they recomended the following assumption for pctversion
setting:
"PCTVERSION = (% of LOBs updated at any given point in time) times (% of each
LOB updated whenever a LOB is updated) times (% of LOBs being read at any given
point in time)"
Any comments?

Thankx
and we said...
Wastage to me would be if you set the pctversion to 80% but the system could
successfully run with pctversion at 10%.  Wastage is when you use a resource
indiscriminantly.

Many people undersize their rollback segments for the same reason -- considering
them "wasted space".  The converse is true however -- they are not wasted, they
are a necessary piece of storage for the database to function properly.

If we didn't need the space, then I would agree it would be wasted.  In your
case however, there must be many readers of the LOB while it is being actively
modified (maybe even the same session actually -- it is reading an modifying the
lob at the same time).  Depending on how they modify it -- they may need many
times the storage to provide the consistent read.  80% is high (meaning 4/5'ths
of the space is "old versions") but not totally unreasonable.

Perhaps a change in the way they process could dramatically cut down on the
space required.  It'll depend on your process.  Say the LOB is loaded and then
heavily read/modified (this requires the 80%) and then is only READ after that.
I could see a two LOB approach here -- Load into "lob_col_1" with pctversion =
80 and process the lob.  When the process is done, and the LOB data has
stabalized, move it to "lob_col_2" and set lob_col_1 to NULL.  Lob_col_2 would
have a much smaller pctversion.
Review & Followup

Rating: 5
PCTVERSION December 22, 2003
Reviewer: A reader
Hi

From the docs the definition of PCTVERSION is this:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
PCTVERSION is the percentage of all used LOB data space that can be occupied by
old versions of LOB data pages. As soon as old versions of LOB data pages start
to occupy more than the PCTVERSION amount of used LOB space, Oracle tries to
reclaim the old versions and reuse them. In other words, PCTVERSION is the
percent of used LOB data blocks that is available for versioning old LOB data.

Default: 10 (%) Minimum: 0 (%) Maximum: 100 (%)
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Dont understand it at all, what do they mean used LOB data space? How about
unused?

Considering initial 4m and next 4m of a blob table....
If I have a BLOB columns with 100 rows (100 photos) which uses 50MB and
pctversion setting is 10%, what will be the space which can be used fot the old
versions LOB data pages (what is a data page...?). Does this mean with this
setting I will have a 56MB (multiple of 4) segment with 14 extents? And it will
have 5MB to provide consistent view? What happen if this 5MB is not enough?

I tried very hard read the docs and searched Metalink but dont understand this
at all!!!!!
Followup:
a lob is stored in a lob segment.

these are managed in blocks (block being synonymous with page).

say you have a set of lobs that takes 1000 blocks.  This is saying that by
default, 100 blocks can contain "old" data that will be used for read
consistency -- when there are 101 "old pages" (caused by updates/modifications)
Oracle will start to reuse the oldest stuff INSTEAD of allocating more space.

forget extents.  extents don't have anything to do with this.  this just says
that X% of your space might contain "old stuff", not "current stuff" and this
old stuff is needed for read consistent views of the data -- just like UNDO (it
is in fact the old undo for the lob)
Rating: 5
LOB retention December 22, 2003
Reviewer: Reader
Tom, in 9i if I specify retention instead of pctversion, are the old LOB blocks
still saved in the LOB segment tablespace or in Undo tablespace? Thanks.
Followup:
no, that was just an analogy.

pctversion is more similar to UNDO_RETENTION

then it is to pctfree.  It is a method of retaining "before images" for
consistent reads.
Rating: 5
December 23, 2003
Reviewer: A reader
-- when there are 101 "old pages" (caused by updates/modifications) Oracle will
start to reuse the oldest stuff INSTEAD of allocating more space.

Are you saying Oracle only reuse space for provide read consistency? That is, if
we are inserting we will never use the old space
Followup:
the space is reused for subsequent inserts and updates.

read consistency USES the data on the blocks. read consistency does not cause
"old blocks to appear", read consistency does not "reuse space" -- it reads the
old data.
Rating: 5
LOB before images December 23, 2003
Reviewer: Reader
The LOB before image blocks are saved in the LOB segment tablespace NOT in the
undo tablespace. Is my understanding correct Tom? Oracle also records changes to
LOBs in the log buffer but the LOB block before images are retained in the
tablespace where the segment is depending on PCTVERSION or RETENTION clause in
the LOB definition. Is this a fair understanding? Thanks.
Followup:
correct...

lobs may be "nologging" or "logging" -- so whether they are logged or not is up
to you.
December 23, 2003 Sam -- Thanks for the question regarding "Bulk insert of records , using something like a user defined record ???", version 8.1.7
You Asked
We need to select 50 columns and insert it into another table.

Which is the quicket way.

The one which comes to my mind immedietly is as follows

declare
    type testarray is table of varchar2(3000) index by binary_integer;

    v_ename testarray;
    v_empno testarray;
    v_sal   testarray;

    cursor cu_cursor is select empno, ename, sal from emp;
begin
    open cu_cursor;

    loop
        fetch cu_cursor bulk collect into v_empno, v_ename, v_sal
                  LIMIT 1000;

         forall i in 1 .. v_empno.count
            insert into testemp_table( empno, ename, sal )
            values
            ( v_empno(i), v_ename(i), v_sal(i) );

        exit when cu_cursor%notfound;
    end loop;
    close cu_cursor;
end;

But this way I have to declare 50 variables, that is going to be very tedious,
and I also know that, I cannot declare a user defined record and select the data
into it and insert.

But I want a similar kind of functionality

I want to select my records into a something like a record( Iam I talking about
sql type ,and create objects out of that sql type ?, Iam not very sure)

and then insert them into the table. But at the same time I want the bulk insert
functionality, with the LIMIT clause.

How can I achieve this.

(I cannot use a direct insert, due to some computatations, and also due to the
fact that the table will be locked , and other processes wanting to insert or
delete records have to wait ?( clarification needed here too)

Thank you
and we said...
The fastest way is:

insert into t select * from t2;

and perform the computations in SQL.  There is not more (or less) locking with
the direct insert.

What about this then (you still have to create the object type with all 50
columns, NO ESCAPING that)

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace type myScalarType as object
  2  ( ename varchar2(30),
  3    hiredate date,
  4    sal      number
  5  )
  6  /

Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace type myArrayType as table
of myScalarType
  2  /

Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select myScalarType( ename, hiredate, sal )
from emp;

MYSCALARTYPE(ENAME,HIREDATE,SAL)(ENAME, HIREDATE, SAL)
---------------------------------------------------------------------------------
--------------------------------------------------
MYSCALARTYPE('A', '17-DEC-80', 800)
MYSCALARTYPE('ALLEN', '20-FEB-81', 1600)
MYSCALARTYPE('WARD', '22-FEB-81', 1250)
MYSCALARTYPE('JONES', '02-APR-81', 2975)
MYSCALARTYPE('MARTIN', '28-SEP-81', 1250)
MYSCALARTYPE('BLAKE', '01-MAY-81', 2850)
MYSCALARTYPE('CLARK', '09-JUN-81', 2450)
MYSCALARTYPE('SCOTT', '09-DEC-82', 3000)
MYSCALARTYPE('KING', '17-NOV-81', 5000)
MYSCALARTYPE('TURNER', '08-SEP-81', 1500)
MYSCALARTYPE('ADAMS', '12-JAN-83', 1100)
MYSCALARTYPE('JAMES', '03-DEC-81', 950)
MYSCALARTYPE('FORD', '03-DEC-81', 3000)
MYSCALARTYPE('MILLER', '23-JAN-82', 1300)

14 rows selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      cursor c is select myScalarType( ename, hiredate, sal ) from emp;
  3      l_data myArrayType;
  4  begin
  5
  6      open c;
  7      loop
  8          l_data := myArrayType();
  9          fetch c bulk collect into l_data limit 6;
 10          dbms_output.put_line( 'Fetched ' || l_data.count || ' rows' );
 11
 12          for i in 1 .. l_data.count
 13          loop
 14              l_data(i).ename := 'X' || l_data(i).ename;
 15          end loop;
 16
 17          insert into emp2 ( ename, hiredate, sal )
 18          select * from TABLE( cast (l_data as myArrayType) );
 19
 20          exit when c%notfound;
 21      end loop;
 22      close c;
 23  end;
 24  /
Fetched 6 rows
Fetched 6 rows
Fetched 2 rows

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from emp2;

ENAME      HIREDATE         SAL
---------- --------- ----------
XA         17-DEC-80        800
XALLEN     20-FEB-81       1600
XWARD      22-FEB-81       1250
XJONES     02-APR-81       2975
XMARTIN    28-SEP-81       1250
XBLAKE     01-MAY-81       2850
XCLARK     09-JUN-81       2450
XSCOTT     09-DEC-82       3000
XKING      17-NOV-81       5000
XTURNER    08-SEP-81       1500
XADAMS     12-JAN-83       1100
XJAMES     03-DEC-81        950
XFORD      03-DEC-81       3000
XMILLER    23-JAN-82       1300

14 rows selected.
Review & Followup

Rating: 5
I thought you couldn't BULK COLLECT into tables of records/objects until 9i October 10, 2001
Reviewer: Adrian from UK
Tom

I thought that you could only bulk collect into a "plsql table per column" in 8i
?? And that bulk collecting into a PL/SQL table of a record or multi-attribute
object was only introduced in 9i?

I tried a similar example to the one you gave above way back (create object in
SQL, use it in PLSQL) and it didn't work... I'm using 8.1.6...

Please respond to this as it has been one of my GREATEST disappointments with
what I considered to be PL/SQL's saviour - BULK COLLECT. I want to BULK COLLECT
50+ columns...

Thanks
Followup:
It is not a plsql table of records I used.

I used an OBJECT TYPE -- a collection -- an extensible datatype.  I didn't use
any plsql specific datatypes.

The example I provided works in Oracle8i release 2 (816) and up.
Rating: 5
Tom October 10, 2001
Reviewer: A reader
Iam very sure that the solution you provided in your answer using sql type ,
will be much slower  that the solution provided by the questioner , in the
question.

Your comments please.
Followup:
they did not provide a solution -- hence my solution is faster.

It is true that using collections of objects will be slower then using 50 simple
scalar arrays.

It is also true that both are slower then

insert into ... select ....

Scalar arrays are faster then collections of objects.  But, that did not answer
the question.
Rating: 4
October 10, 2001
Reviewer: A reader
hi tom
do we need to initilize collection when we are using
BULK COLLECT ?

declare
  2      cursor c is select myScalarType( ename, hiredate, sal ) from emp;
  3      l_data myArrayType;
  4  begin
  5
  6      open c;
  7      loop
  8          l_data := myArrayType(); <<<-----IS initilization required?
  9          fetch c bulk collect into l_data limit 6;
 10          dbms_output.put_line( 'Fetched ' || l_data.count || ' rows' );
 11
Followup:
No, it's not necessary.  the bulk collect would reset the .count as well.
Rating: 5
limit of LIMIT October 10, 2001
Reviewer: Sam
What is the limit or the upper end of LIMIT.
At what threshold of LIMIT , will we get optimum performance.

i.e. what would you recommend the LIMIT to be ,  1000 or 2000, or 3000 or 5000
or 10,000
Followup:
I'd suggest hundreds, not thousands.  There are ram concerns, you want to keep
the interaction between you and database going back and forth -- not "ok
database do tons of work, then I'll do tons of work and then I'll give you tons
of work again".  You want things to be flowing between you and the database --
not "bursting" between you and the db.

Consider this -- if you array insert 10k rows - you might have to wait for LGWR
to make room in the redo buffer cache (sync writes to the file system).  If you
send him 100 or 500 rows -- you won't wait and LGWR will flush the buffer cache
in the background whilst you are off doing the next 100/500 rows.
Rating: 5
You are right , but October 10, 2001
Reviewer: Sam
"Consider this -- if you array insert 10k rows - you might have to wait for LGWR
to make room in the redo buffer cache (sync writes to the file system).  If you
send him 100 or 500 rows -- you won't wait and LGWR will flush the buffer cache
in the background whilst you are off doing the next 100/500 rows."

Firstly, YES, I will limit it to a figures between 100 and 500, but what can be
the max number, out of academic interest.

Secondly, what if the redo buffer has a lot of memory, then even in the case I
set the limit at 5000, then it should not slow down the process, Iam I right.

Thirdly, Setting the LIMIT to a lesser number, does it also mean faster
execution. , then when you set the LIMIT to a hight number.

KIndly expand on the below--
"if you array insert 10k rows - you might have to wait for LGWR to make room in
the redo buffer cache (sync writes to the file system).  "
Followup:
Out of academic interest -- the limit is dictated by the amount of RAM you have
(and 2billion)

Secondly -- Yes, if you use 5,000 a small redo log buffer could hurt you.

Thirdly -- It might, it might not, you need to benchmark to find your "sweet
spot".  I find 100-500 the max.

I don't know how to expand on that last part.  If you send over 10k rows - and
generat 5m of log -- you might have to WAIT for log writer to make room.  If you
send over 100rows and generate 0.05meg of log -- you probably WON'T have to wait
for space.  And then log writer will write that data out in the background.  By
having a nice steady flow -- lots of back and forth -- you'll get into a nice
steady state with no log jams.
Rating: 5
October 10, 2001
Reviewer: A reader
What do you mean by

"the limit is dictated by the amount of RAM you have
(and 2billion)"

I understand that 2 billion is the max limit, what do you mean by the limit is
dictated by the amount of RAM you have
, in what way that is related to the LIMIT we set for bulk insert.

Please bear with  me and explain.
Followup:
If you use

   limit 1000000

and don't have enough ram to hold 1,000,000 array element in memory, you will
get an ora-4031 or some related memory error.  The array can only be as big as
you can allocate memory for.
Rating: 4
October 10, 2001
Reviewer: A reader
How can we know our memmory settings.
When you say memory here, are you talking about the redo log buffer cache.

How can I know what my total memory is , and how it is distributed.

Typically how much of RAM is required to hold 1,000,000  records in an array.
Followup:
The log buffer is a fixed sized piece of memory in the SGA

SQL> show parameter log_buffer

will show you its size.

You can see the amount of log/ram you are using by querying v$mystat and
v$statname.  I use a small script like:

ops$tkyte@ORA817DEV.US.ORACLE.COM> @mystat redo
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  and b.value > 0
  6  /
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('redo')||'%'

NAME                                VALUE
------------------------------ ----------
redo synch writes                       1
redo synch time                         2
redo entries                           16
redo size                            4072

ops$tkyte@ORA817DEV.US.ORACLE.COM> update emp set ename = ename;

14 rows updated.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit
  2  /

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> @mystat redo
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  and b.value > 0
  6  /
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('redo')||'%'

NAME                                VALUE
------------------------------ ----------
redo synch writes                       2
redo synch time                         3
redo entries                           31
redo size                            7928

diff the redo sizes and you get a feeling for how much you are generating.
For memory, you look at pga and uga usage:

ops$tkyte@ORA817DEV.US.ORACLE.COM> @mystat memory
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  and b.value > 0
  6  /
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('memory')||'%'

NAME                                VALUE
------------------------------ ----------
session uga memory                  55836
session uga memory max              68344
session pga memory                 228668
session pga memory max             228668
sorts (memory)                         48

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2  x long default rpad('*',32000,'*');
  3  begin null; end;
  4  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> @mystat memory
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and lower(a.name) like '%' || lower('&1')||'%'
  5  and b.value > 0
  6  /
old   4: and lower(a.name) like '%' || lower('&1')||'%'
new   4: and lower(a.name) like '%' || lower('memory')||'%'

NAME                                VALUE
------------------------------ ----------
session uga memory                  55836
session uga memory max              68344
session pga memory                 239256
session pga memory max             239256
sorts (memory)                         50
Rating: 4
Kinldy give your thoughts on the solution for this October 17, 2001
Reviewer: Jane
Iam encountering the following error
"
[1]: (Error): ORA-06550: line 18, column 12: PLS-00103: Encountered the symbol
"IF" when expecting one of the following:     . ( * @ % & - + / at mod rem
select update <an exponent (**)>    delete insert || ORA-06550: line 19, column
22: PLS-00103: Encountered the symbol "=" when expecting one of the following:
  := . ( % ; The symbol ":= was inserted before "=" to continue. ORA-06550: line
27, column 5: PLS-00103: Encountered the symbol "CLOSE" when expecting one of
the following:     begin function package pragma procedure form ORA-06550: line
30, column 0: PLS-00103: Encountered the symbol "end-of-file" when expecting one
of the following:     begin function package pragma procedure subtype type use
 <an identifier> <a double-quoted delimited-identifier> cursor    form current
"

when Iam trying to set the ename after the FORALL.

1.why
2.How should I achieve what Iam trying to do. I want to set the ename to
'HIGHSAL', before inserting into the testemp_table, if the sal is more than
1500.

Note: I want to achieve this with in the given structure.

declare
    type testarray is table of varchar2(3000) index by binary_integer;

    v_ename testarray;
    v_empno testarray;
    v_sal   testarray;

    cursor cu_cursor is select empno, ename, sal from emp;
begin
    open cu_cursor;

    loop
        fetch cu_cursor bulk collect into v_empno, v_ename, v_sal
                  LIMIT 1000;

         forall i in 1 .. v_empno.count

           if v_sal(i) >1500 then
           v_ename(i)='HIGHSAL' ;
           END IF;
            insert into testemp_table( empno, ename, sal )
            values
            ( v_empno(i), v_ename(i), v_sal(i) );

        exit when cu_cursor%notfound;
    end loop;
    close cu_cursor;
end;
Followup:
forall can only be followed by ONE dml statement.  Its goal is to cut down on
the back and forth between PLSQL and SQL -- if you embedded PLSQL in there -- it
could not possibly do that!

you would code something like:

fetch cu_cursor bulk collect into v_empno, v_ename, v_sal
                  LIMIT 1000;

for i in 1 .. v_empno.count
loop
  if v_sal(i) > 1500 then
        v_ename(i) := 'highsal';
  end if;
end loop

forall i in 1 .. v_empno.count
     insert into testemp_table( empno, ename, sal )
      values
     ( v_empno(i), v_ename(i), v_sal(i) );
Rating: 4
October 17, 2001
Reviewer: Jane
Can you give the link to the oracle objects chapter, and also the pl/sql manual.
Followup:
nag -- see
http://technet.oracle.com/doc/oracle8i_816/appdev.816/index.htm
Rating: 5
comparision October 17, 2001
Reviewer: Nag
Tom , I want to know
how much time the follwing steps are taking. How can I do it in a convenient
way.

1.
for i in 1 .. v_empno.count
loop
  if v_sal(i) > 1500 then
        v_ename(i) := 'highsal';
  end if;
end loop
;

2.
forall i in 1 .. v_empno.count
     insert into testemp_table( empno, ename, sal )
      values
     ( v_empno(i), v_ename(i), v_sal(i) );

I guess this
.
for i in 1 .. v_empno.count
loop
  if v_sal(i) > 1500 then
        v_ename(i) := 'highsal';
  end if;
end loop
;

step should be very very fast, irrespective of the number of values int the
given arrays, as no context switches are required, and everythig is done in the
memory.
Followup:
time it with dbms_utility.get_time

l_start := dbms_utility.get_time;

... code ....

dbms_output.put_line( 'That took ' ||
                       (dbms_utility.get_time-l_start) || ' hsecs' );
Rating: 4
Updating the whole of an array to one value October 17, 2001
Reviewer: Nag
Tom

I have an interesting question

I have a number array which has an array of 1000 values it it.

Now I want to update the array and set all the values to 25.

One  way of doing it is to loop throught the array and set the value.

But I dont want to loop, but want to update the whole array in a single go, is
there a way to do it.

How I achieve this functionality.
Followup:
No way to really do it in one step.  you have to loop.
Rating: 5
updating a pl/sql table October 17, 2001
Reviewer: Nag
http://info-it.umsystem.edu/oradocs/doc/server/doc/PLS23/ch4.htm#toc038
"
Using DELETE
This attribute has three forms. DELETE removes all elements from a PL/SQL table.
DELETE(n) removes the nth element. If n is null, DELETE(n) does nothing.
DELETE(m, n) removes all elements in the range m .. n. If m is larger than n or
if m or n is null, DELETE(m, n) does nothing.
DELETE lets you free the resources held by a PL/SQL table. DELETE(n) and
DELETE(m, n) let you prune a PL/SQL table. Consider the following examples:

ename_tab.DELETE(3);       -- delete element 3
ename_tab.DELETE(5, 5);    -- delete element 5
ename_tab.DELETE(20, 30);  -- delete elements 20 through 30
ename_tab.DELETE(-15, 0);  -- delete elements -15 through 0
ename_tab.DELETE;          -- delete entire PL/SQL table
If an element to be deleted does not exist, DELETE simply skips it; no exception
is raised.

Note: The amount of memory allocated to a PL/SQL table can increase or decrease
dynamically. As you delete elements, memory is freed page by page. If you delete
the entire PL/SQL table, all the memory is freed.
"

Tom

Iam curious know , that when we are allowed to delete all the values in  a
pl/sql table, what are we not allowed to update a pl/sql table or insert new
values into a pl/sql table.

what are the reasons, for oracle choosing it to doing this way.
Followup:
Umm, you can update:

  ename_tab(5).ename := 'hello';

and you can "insert"

   ename_tab(ename_tab.count+1).ename := 'this be a new record';

its an "array", not a real "table".
Rating: 3
You are right , but October 18, 2001
Reviewer: A reader
"ename_tab.DELETE;          -- delete entire PL/SQL table"

Tom

From your answer, yes, we can update a particular value in
an array, or insert a new one.

But my questions specifically, was   how do I set all the values in an array  to
a single value, i.e. for ename_tab array , I want to set all the values in it to
'hello', not the 5th one only.

When I tried to do it, I got the following error

ename_tab.ename:= 'hello';

[1]: (Error): ORA-06550: line 59, column 9: PLS-00302: component 'ENAME' must be
declared ORA-06550: line 59, column 1: PL/SQL: Statement ignored
Followup:
you have to update each component.  As i said, its really an array, not a
database table
Rating: 4
Another situation - important and critical October 18, 2001
Reviewer: A reader
Create or replace procedure pr_array(lv_comm number)

is

    type testarray is table of varchar2(3000) index by binary_integer;

    v_ename testarray;
    v_empno testarray;
    v_sal   testarray;

    cursor cu_cursor is select empno, ename, sal from emp;
begin
    open cu_cursor;

    loop
        fetch cu_cursor bulk collect into v_empno, v_ename, v_sal
                  LIMIT 5;

        forall i in 1 .. v_empno.count
         insert into testemp_table( empno, ename, sal,comm )values
         ( v_empno(i), v_ename(i), v_sal(i),lv_comm );

        exit when cu_cursor%notfound;
    end loop;
    close cu_cursor;

    commit;
end;

I execute the procedure as pr_array(20);

1.Now the procedures inserts the comm as 20 for all rows, but the question is is
it doing bulk insert, or is it doing row by row.

2.How can I confirm that bulk insert is infact occuring or not occuring.

3.If it is not, how can I make it happen in a scenario like the above.
Followup:

http://asktom.oracle.com/~tkyte/tkprof.html
Use sql_trace and TKPROF to see what it is doing. Look at the executes and the
rows -- it'll tell you (if you see 1 execute/5 rows, its doing it in bulk.
Rating: 5
Nag October 22, 2001
Reviewer: Nag

http://technet.oracle.com/doc/server.815/a67842/04_colls.htm#20425
"The SQL statement can reference more than one collection. However, the PL/SQL
engine bulk-binds only subscripted collections. So, in the following example, it
does not bulk-bind the collection sals, which is passed to the function median:

FORALL i IN 1..20
   INSERT INTO emp2 VALUES (enums(i), names(i), median(sals), ...);

The next example shows that the collection subscript cannot be an expression:

FORALL j IN mgrs.FIRST..mgrs.LAST
   DELETE FROM emp WHERE mgr = mgrs(j+1);  -- illegal subscript

"

Tom,

can you explain the phrase  'subscripted collections' and
'subscript cannot be an expression'.

( is mgrs(j+1);  being referred to as an expression)

Thank you
Followup:
In the first example, enums(i) is a "subscripted collection" in the forall
statement.  It is subscripted by the "i" in the forall.  median is not.

(j+1) is an expression, yes.
Rating: 5
rollback while bulk insert--how can we gather the details of rolled back records October 22, 2001
Reviewer: Nag
"
Rollback Behavior
If a FORALL statement fails, database changes are rolled back to an implicit
savepoint marked before each execution of the SQL statement. Changes made during
previous executions are not rolled back. For example, suppose you create a
database table that stores department numbers and job titles, as follows:

CREATE TABLE emp2 (deptno NUMBER(2), job VARCHAR2(15));

Next, you insert some rows into the table, as follows:

INSERT INTO emp2 VALUES(10, 'Clerk');
INSERT INTO emp2 VALUES(10, 'Clerk');
INSERT INTO emp2 VALUES(20, 'Bookkeeper');  -- 10-char job title
INSERT INTO emp2 VALUES(30, 'Analyst');
INSERT INTO emp2 VALUES(30, 'Analyst');

Then, you try to append the 7-character string ' (temp)' to certain job titles
using the following UPDATE statement:

DECLARE
   TYPE NumList IS TABLE OF NUMBER;
   depts NumList := NumList(10, 20);
BEGIN
   FORALL j IN depts.FIRST..depts.LAST
      UPDATE emp2 SET job = job || ' (temp)'
         WHERE deptno = depts(j);
         -- raises a "value too large" exception
EXCEPTION
   WHEN OTHERS THEN
      COMMIT;
END;

The SQL engine executes the UPDATE statement twice, once for each index number
in the specified range; that is, once for depts(10) and once for depts(20). The
first execution succeeds, but the second execution fails because the string
value 'Bookkeeper (temp)' is too large for the job column. In this case, only
the second execution is rolled back.

"

Tom, in a scenario like the above example, what I want to do is collect the
details of all the records which have been rolled back due to some error or the
other , so that I can process them later.

Is this kind of exception handling possible with bulk operations. If this is
possible , how ?

(If this kind of functionality is not available Iam afraid we cannot consider
using bulk inserts or bulk updates becasue, there will be no way to know abou
the records which have not been inserted or deleted, unless and untill we go
back into the respective tables and investigate( which is going to be a very
cumbersome process))
Followup:
You can use any combo of returning BULK COLLECT or sql%bulk_rowcount to
determine this:

ops$tkyte@ORA717DEV.US.ORACLE.COM> DECLARE
  2     TYPE NumList IS TABLE OF NUMBER;
  3     depts NumList := NumList(10, 20, 30 );
  4     l_depts NumList;
  5  BEGIN
  6     FORALL j IN depts.FIRST..depts.LAST
  7        UPDATE emp2 SET job = job || ' (temp)'
  8           WHERE deptno = depts(j) returning deptno bulk collect into
l_depts;
  9           -- raises a "value too large" exception
 10  EXCEPTION
 11     WHEN OTHERS THEN
 12            for i in 1 .. depts.count
 13            loop
 14                  dbms_output.put_line( sql%bulk_rowcount(i) );
 15            end loop;
 16            for i in 1 .. l_depts.count
 17            loop
 18                  dbms_output.put_line( 'updated ' || l_depts(i) );
 19            end loop;
 20        COMMIT;
 21  END;
 22  /
2
0
0
updated 10
updated 10

that shows the first entry updated 2 rows, the next two (20 and 30) got 0 rows
and the updated 10 entries show the value of the depts() collection that
"worked"
Rating: 4
October 23, 2001
Reviewer: A reader
hi tom,
when i run your code it is giving me error

  1  DECLARE
  2         TYPE NumList IS TABLE OF NUMBER;
  3         depts NumList := NumList(10, 20, 30 );
  4         l_depts NumList := NumList();
  5      BEGIN
  6         FORALL j IN depts.FIRST..depts.LAST
  7            UPDATE emp2 SET job = job || ' (temp)'
  8               WHERE deptno = depts(j) returning deptno bulk collect into
l_depts;
  9               -- raises a "value too large" exception
 10     EXCEPTION
 11        WHEN OTHERS THEN
 12               for i in 1 .. depts.count
 13              loop
 14                 null;
 15                     dbms_output.put_line( sql%bulk_rowcount(i) );
 16               end loop;
 17               for i in 1 .. l_depts.count
 18               loop
 19                     dbms_output.put_line( 'updated ' || l_depts(i) );
 20               end loop;
 21           COMMIT;
 22*    END;
SQL> /
DECLARE
*
ERROR at line 1:
ORA-06531: Reference to uninitialized collection
ORA-06512: at line 15
ORA-01401: inserted value too large for column
Followup:
and your version just happens to be?
Rating: 5
October 23, 2001
Reviewer: A reader
8.1.6
Rating: 3
Help October 23, 2001
Reviewer: Rahul from NJ USA
I want to pose a question can anybody help me how should
do it
Rating: 5
June 30, 2003
Reviewer: A reader
Tom,

I have a question,lets say i gave this command
sql> insert into t1 select * from t2;

Now i want to know how many records were inserted without giving a count(*), is
there something  like sql%rowcount for sql..
Followup:
in a real programming language -- sure. sqlca.sqlerrd[2] in proc for example.

in sqlplus, well, once you add a real programming language -- sure

begin
  insert into t1 select * from t2;
  dbms_output.put_line( sql%rowcount );
end;
/

remember -- sqlplus is NOT sql -- sql is sql, there are programming environments
like PLSQL, C, C++, Java and even VB that give you procedural things -- a simple
thing like sqlplus - it is just a very basic reporting tool.
Rating: 5
July 01, 2003
Reviewer: A reader
Thanks
Rating: 3
why bulk collect July 16, 2003
Reviewer: Jean Boulé from Québec , Canada
Hi Tom,

  Going back to the initial question, why declare a cursor and use bulk collect
.. I Thing i would be more simple to use cast multiset like this

declare
  l_data myArrayType;
begin
  select cast ( multiset ( select ename, hiredate, sal
                            from emp
                         )
                as myArrayType
               )
    into l_data
    from dual;
  for i in 1 .. l_data.count
  loop
    l_data(i).ename := 'X' || l_data(i).ename;
  end loop;
  insert into emp2 ( ename, hiredate, sal )
   select * from TABLE( cast (l_data as myArrayType) );
end;
/

It give the same result with less code

Am I right or am I wrong
Followup:
time for a benchmark eh?  first, I'm not sure it is "easier"

ops$tkyte@ORA920LAP> create type myScalarType as object (
  2   OWNER               VARCHAR2(30),
  3   OBJECT_NAME         VARCHAR2(30),
  4   SUBOBJECT_NAME      VARCHAR2(30),
  5   OBJECT_ID           NUMBER,
  6   DATA_OBJECT_ID      NUMBER,
  7   OBJECT_TYPE         VARCHAR2(18),
  8   CREATED             DATE,
  9   LAST_DDL_TIME       DATE,
 10   TIMESTAMP           VARCHAR2(19),
 11   STATUS              VARCHAR2(7),
 12   TEMPORARY           VARCHAR2(1),
 13   GENERATED           VARCHAR2(1),
 14   SECONDARY           VARCHAR2(1)
 15  )
 16  /

Type created.

ops$tkyte@ORA920LAP> create type myTableType as table of myScalarType
  2  /

Type created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create table t1 as select * from all_objects where 1=0;

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create or replace procedure object_way
  2  as
  3          l_data myTableType;
  4  begin
  5          select cast( multiset( select myScalarType(
  6                                    OWNER, OBJECT_NAME, SUBOBJECT_NAME,
OBJECT_ID,
  7                                    DATA_OBJECT_ID, OBJECT_TYPE, CREATED,
LAST_DDL_TIME,
  8                                    TIMESTAMP, STATUS, TEMPORARY, GENERATED,
SECONDARY)
  9                               from all_objects
 10                              where rownum <= 5000 ) as myTabletype )
 11        into l_data
 12        from dual;
 13
 14          insert into t1
 15      select * from TABLE( cast( l_data as myTableType ) );
 16  end;
 17  /

Procedure created.

versus (be a bit more code in 8i yes, you would have to declare an array per
column -- but not any more tedious then declaring an object type above...

ops$tkyte@ORA920LAP> create table t2 as select * from all_objects where 1=0;

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create or replace procedure table_way(p_n in number default
100)
  2  as
  3          type array is table of t2%rowtype;
  4          l_data array;
  5
  6          cursor c is select * from all_objects where rownum <= 5000;
  7  begin
  8          open c;
  9          loop
 10                  fetch c bulk collect into l_data LIMIT p_n;
 11
 12                  forall i in 1 .. l_data.count
 13                     insert into t2 values l_data(i);
 14
 15                  exit when c%notfound;
 16          end loop;
 17          close c;
 18  end;
 19  /

Procedure created.

and runstats says:

ops$tkyte@ORA920LAP> exec runstats_pkg.rs_start

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec begin for i in 1 .. 10 loop object_way; end loop; end;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec runstats_pkg.rs_middle

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec begin for i in 1 .. 10 loop table_way(750); end loop;
end;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec runstats_pkg.rs_stop(100)
Run1 ran in 845 hsecs
Run2 ran in 568 hsecs
run 1 ran in 148.77% of the time

objects take longer by the wall clock

Name                                  Run1        Run2        Diff
STAT...buffer is not pinned co     200,605     200,710         105
LATCH.shared pool                      524         409        -115
STAT...recursive calls                 432         555         123
LATCH.simulator hash latch          14,649      14,518        -131
LATCH.row cache enqueue latch       10,714      10,468        -246
LATCH.row cache objects             15,954      15,708        -246
STAT...Elapsed Time                    851         590        -261
LATCH.library cache                  1,047         769        -278
STAT...CPU used by this sessio         819         504        -315
STAT...CPU used when call star         819         504        -315
STAT...recursive cpu usage             816         494        -322
LATCH.cache buffers lru chain        1,001       1,666         665
STAT...dirty buffers inspected           0         985         985
STAT...free buffer inspected             0         993         993
LATCH.checkpoint queue latch           782       1,857       1,075
LATCH.cache buffers chains         434,257     435,907       1,650
STAT...redo size                 5,913,284   5,926,764      13,480
STAT...session pga memory          -65,536           0      65,536

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
487,691     489,977       2,286     99.53%

PL/SQL procedure successfully completed.

and the other issues I see are:

o no limit clause on the select into.  for "big" tables, this is not good, you
NEED limit to make this practical

o people don't like those objects "littering" their schema (for whatever reason,
it doesn't bother me personally, that is what a data dictionary is for)

o you have two pieces of "code" to fix if you chane the structure even a little
bit -- the object type and the code itself
Rating: 5
August 08, 2003
Reviewer: A reader
Hi Tom,

Back to the original question (PLSQL script duplicated below). If one of the
columns to be inserted is constant, say SYSDATE. Do I need to create a PLSQL
table for that column and populate it with SYSDATEs?

declare
    type testarray is table of varchar2(3000) index by binary_integer;

    v_ename testarray;
    v_empno testarray;
    v_sal   testarray;

    cursor cu_cursor is select empno, ename, sal from emp;
begin
    open cu_cursor;

    loop
        fetch cu_cursor bulk collect into v_empno, v_ename, v_sal
                  LIMIT 1000;

         forall i in 1 .. v_empno.count
            insert into testemp_table( empno, ename, sal )
            values
            ( v_empno(i), v_ename(i), v_sal(i) );

        exit when cu_cursor%notfound;
    end loop;
    close cu_cursor;
end;
Followup:
do you want a constant sysdate for all rows inserted?  if so, YES, you need to
get a SYSDATE and then use it over and over.

Else, each insert will get sysdate as of right then and there.  Every BULK
insert (every set of 1000 rows) could have their own unique sysdate if it takes
a second to fetch/insert 1000 rows.
Rating: 4
rowid in the object August 11, 2003
Reviewer: Rd from Sweden
Hi,

Can i get rowid into array along with empno ?

I mean can i have like
"create or replace type myScalarType as object (rowid rowid, empno varchar2(20)
)"

I gotto pass an array of rowid,empno to another procedure as an argument.

TIA
Followup:
a@ORA920> create type x as object ( rid rowid, empno varchar2(20) )
  2  /

Warning: Type created with compilation errors.

a@ORA920> show err
Errors for TYPE X:

LINE/COL ERROR
-------- -----------------------------------------------------------------
1/24     PLS-00530: Illegal type used for object type attribute: 'ROWID'.

no, no rowids however -- you can use varchar2(18) just as easily.  you would use
rowidtochar and chartorowid in your sql.
Rating: 5
Scalar type/PLSQL table August 11, 2003
Reviewer: Rd from Sweden
Hi,

Thanks for that rowidtochar solution.
I was just wondering about this creating objects.
Can we keep creating as many types as we want !!! what i mean to know is, should
we be careful and make only really useful ones.

If some other user by chance uses the same name for some other purpose by saying
"create or replace .. " then the procedure cant run.
Arent PLSQL tables more safer in such cases ??

Also can PLSQL tables be sent as arguments to procedures ?

TIA
Followup:
...
should we be careful and make only really useful ones.
......

that sounds "obvious", that would be true for every and anything!

..
If some other user by chance uses the same name for some other purpose by saying

...

sounds like you need "source code control" or some tool to help you mediate
these things?  what if some other developer does "create or replace
your_really_important_pkg"???  same thing.  types are not "special" in this
regard.

PLSQL table types are nice -- if all you need is plsql access to them.  if you
want to use SQL on them -- you need a top level type in SQL.
Rating: 4
August 11, 2003
Reviewer: A reader
Hi,

Sorry for the dumb questions.

Pls tell me how i can pass PLSQL tables as aruments to another procedure.

TIA
Followup:
just do it?  like you pass a number?  no different.
Rating: 5
thanks August 11, 2003
Reviewer: A reader
Hi,

Thanks. I was trying to pass PLSQL table as argument but it didnt work.So i
thought only way out is use scalar types.

But now since i know that it shud work i can make it work.
That is how i put some dumb Qs.
All i wanted to know is when PLSQL tables can do the job why am i creating an
object and a type ..etc etc.

If possible you may suggest me where scalar ones are more appropriate .

Thanks again
Rating: 5
Problem in Forall clause September 17, 2003
Reviewer: ARC from ind
Hi Tom,
I am using bellow code to do some inserts and updates.

type Carray is table of dwadmin.tb_sdw000_ge_gls_tmp.alt_customer_number index
by binary_integer;
cust_tab Carray;

open cur_gen(v_fm, v_fy);
    loop
        fetch cur_gen bulk collect into cust_tab LIMIT 100;
        v_rec_cnt := v_rec_cnt + 1;
        forall i in 1 .. cust_tab.count
        DBMS_OUTPUT.PUT_LINE(cust_tab.count);
--get_mtd_sales_amt(cust_tab).cust_num,p_bus_tier1,v_fm,v_fy,p_run_dt);
    exit when cur_gen%notfound;
        end loop;
        close cur_gen;
get_mtd_sales_amt is private procedure which will do inserts and updates.
getting error
ERROR at line 233:
ORA-06550: line 233, column 3:
PLS-00103: Encountered the symbol "DBMS_OUTPUT" when expecting one of the
following:
. ( * @ % & - + / at mod rem select update <an exponent (**)>
delete insert ||
The symbol "." was substituted for "DBMS_OUTPUT" to continue.
ORA-06550: line 233, column 39:
PLS-00103: Encountered the symbol ";" when expecting one of the following:
. ( * % & - + / at mod rem select update <an exponent (**)>
delete insert ||
ORA-06550: line 236, column 6:
PLS-00103: Encountered the symbol "END"

Plese help me to overcome. Thanks in advance.
Followup:
forall cannot have any code inside of it except for the SINGLE dml statement.

it is not really a "loop", it is an array (bulk) processing directive.

so, the dbms_output is misplaced.  it cannot go there, put it IN FRONT of the
forall statement.

then, follow the forall statement with dml, not a procedure.

if what you want to do is call a procedure, that is coded using a traditional
for loop

for i in 1 .. cust_tab.count
loop
   get_mts_sales_amt( .... );
end loop;
Rating: 5
Can't bulk fetch with explicit cursor into user defined record October 08, 2003
Reviewer: A reader from london
Tom can you explain this...

CAN'T bulk fetch with explicit cursor into user defined record...

SQL> ed
Wrote file afiedt.buf

  1  declare
  2     cursor c is select t.table_name, i.index_name
  3                   from all_tables t, all_indexes i
  4                  where t.table_name = i.table_name
  5                    and t.owner = i.table_owner;
  6     type t_rec is record (
  7        table_name all_tables.table_name%type,
  8        index_name all_indexes.index_name%type);
  9     type t_tab is table of t_rec;
 10     l_tab t_tab;
 11  begin
 12     open c;
 13     fetch c bulk collect into l_tab;
 14     close c;
 15* end;
SQL> /
   fetch c bulk collect into l_tab;
                             *
ERROR at line 13:
ORA-06550: line 13, column 30:
PLS-00597: expression 'L_TAB' in the INTO list is of wrong type
ORA-06550: line 13, column 4:
PL/SQL: SQL Statement ignored

CAN bulk fetch with implicit cursor into user defined record...

SQL> ed
Wrote file afiedt.buf

  1  declare
  2     type t_rec is record (
  3        table_name all_tables.table_name%type,
  4        index_name all_indexes.index_name%type);
  5     type t_tab is table of t_rec;
  6     l_tab t_tab;
  7  begin
  8     select t.table_name, i.index_name
  9       bulk collect into l_tab
 10       from all_tables t, all_indexes i
 11      where t.table_name = i.table_name
 12        and t.owner = i.table_owner;
 13* end;
SQL> /

PL/SQL procedure successfully completed.

CAN bulk fetch with explicit cursor into rowtype record...

SQL> create view v as select t.table_name, i.index_name from all_tables t, all_i
ndexes i where t.table_name = i.table_name and t.owner = i.table_owner;

View created.

SQL> ed
Wrote file afiedt.buf

  1  declare
  2     cursor c is select t.table_name, i.index_name
  3                   from all_tables t, all_indexes i
  4                  where t.table_name = i.table_name
  5                    and t.owner = i.table_owner;
  6     type t_tab is table of v%rowtype;
  7     l_tab t_tab;
  8  begin
  9     open c;
 10     fetch c bulk collect into l_tab;
 11     close c;
 12* end;
SQL> /

PL/SQL procedure successfully completed.

SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.2.0 - 64bit Production
PL/SQL Release 9.2.0.2.0 - Production
CORE    9.2.0.2.0       Production
TNS for HPUX: Version 9.2.0.2.0 - Production
NLSRTL Version 9.2.0.2.0 - Production

Why doesn't the first method work, but the next two do?

I believe I need to use an explicit cursor because I need to limit the rows
fetched (to 100 or so). Does this mean I need to type out each field of the
record in the fetch?
Followup:
ops$tkyte@ORA920LAP> declare
  2     cursor c is select t.table_name, i.index_name
  3                   from all_tables t, all_indexes i
  4                  where t.table_name = i.table_name
  5                    and t.owner = i.table_owner
  6                    and rownum < 10;
  7     type t_tab is table of c%rowtype;
  8     l_tab t_tab;
  9  begin
 10     open c;
 11     fetch c bulk collect into l_tab;
 12     close c;
 13  end;
 14  /

PL/SQL procedure successfully completed.

you made it more complicated then it needed to be!  just use c%rowtype
Rating: 5
Excellent October 08, 2003
Reviewer: A reader from london
Thanks for the very quick reply.

It's still strange tho'. Exactly how does the parser decide whether record types
match?

And I sorta wanted to have the types exposed in the package spec, and the cursor
hidden in the package body... Perhaps I'm being fussy.
Rating: 3
Having problems with your example October 08, 2003
Reviewer: Rob from Chicago, IL
SQL> declare
  2  cursor c is select t.table_name, i.index_name
  3                       from all_tables t, all_indexes i
  4                      where t.table_name = i.table_name
  5                        and t.owner = i.table_owner
  6                        and rownum < 10;
  7         type t_tab is table of c%rowtype;
  8         l_tab t_tab;
  9      begin
 10        open c;
 11        fetch c bulk collect into l_tab;
 12        close c;
 13     end;
 14  /
      fetch c bulk collect into l_tab;
                                *
ERROR at line 11:
ORA-06550: line 11, column 33:
PLS-00597: expression 'L_TAB' in the INTO list is of wrong type
ORA-06550: line 11, column 7:
PL/SQL: SQL Statement ignored

SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.2.0 - Production
PL/SQL Release 9.2.0.2.0 - Production
CORE    9.2.0.2.0       Production
TNS for Solaris: Version 9.2.0.2.0 - Production
NLSRTL Version 9.2.0.2.0 - Production

Am I missing something?

Thanks
Followup:
they seemed to have "made it work" in 9iR2 patch 9203 and above.
Rating: 3
Can this be used for the Following? December 05, 2003
Reviewer: Vinnie from Orlando
Tom,

I have a similar situation with a table containing the following attributes:
id
db_table_name
db_col_name

This contains a list of many tables each having various # of columns.
I need to be able to query this table for a given id.  Then retrieve the data in
the actual table based on the columns in the master table.  Then build inserts
from the results to another empty table.

I.e
select id,db_table_name, db_col_name from master_tab
where id = 'EMP';

result set:
EMP   EMP       emp_no
EMP   EMP       emp_fname
EMP   EMP2      emp_name
...
...

grab data:
select emp_no,emp_fname into ??? from emp;
select emp_name into ??? from emp2;

inserts:
insert into result_table (identifier, table_name, value)
values ('EMP','EMP',1);
insert into result_table (identifier, table_name, value)
values ('EMP','EMP','VS');
insert into result_table (identifier, table_name, value)
values ('EMP','EMP2','SA');

I would like to dynamically build these selects * inserts.
There may be many attributes per table.
What would the best way to accomplish this?

Thanks
Followup:
you are going to write code.  I would not write code.  I would produce a flat
file that can be loaded using any load utility (see
http://asktom.oracle.com/~tkyte/flat
Rating: 3
Unknown column name? December 08, 2003
Reviewer: Vinnie from Orlando
How can I use this if I do not know the column name
I wish to insert to?
Rating: 3
Table Name December 08, 2003
Reviewer: Vinnie from Orlando
Tom,

I figured out how to get the col_name using:
l_descTbl(i).col_name.

Is there anyway to get the actual table that was used in the query?

i.e.

Select *  from emp;

Can I get EMP somehow?
Followup:
no, because a query doesn't normally use a "single table"

select * from emp, dept....
Rating: 5
Collection of objects vs Scalar arrays December 18, 2003
Reviewer: Su from FL USA
Tom,

If I used more then one scalar array to bulk collect some
values and I like to use these arrays in a subquery,
how can I correlate the values from different arrays. Similar to column_value is
there any pseudo column (such as column_index) to correlate various arrays ?

INSERT INTO tab1
(SELECT c1.column_value, c2.column_value
 FROM table(cast(arr1 as ScalarArray)) c1,
      table(cast(arr2 as ScalarArray)) c2
 WHERE c1.column_index = c2.column_index);

I am trying to find out if collection of objects is my only
other option.

Thanks in advance,
-Su
Followup:
it would just look like this?

    loop
        fetch cu_cursor bulk collect into v_empno, v_ename, v_sal
                  LIMIT 1000;

         forall i in 1 .. v_empno.count
            insert into testemp_table( empno, ename, sal )
            values
            ( v_empno(i), v_ename(i), v_sal(i) );

not sure why you would try to join them?
Rating: 4
How can I use multiple insert in FORALL December 22, 2003
Reviewer: Suhail from NY
Tom,

I want to use multiple INSERT for master -detail insert, I am reading data from
a temp table and inserting into three difefrent tables, here is my code;

   declare
      type testarray is table of varchar2(3000) index by binary_integer;
      v_lname testarray;
      v_fname testarray;
      v_address testarray;
      v_city   testarray;
      v_phone1 testarray;
      v_address_flag testarray;
      v_phone_flag   testarray;
      v_fileid       testarray;
      cursor c is
  select fileid,last_name, first_name, address,city,phone1,address_flag
       phone_flag
  from emp_temp;
  begin
  open c;
  loop
      fetch c bulk collect into
v_fileid,v_lname,v_fname,v_address,v_city,v_phone1,
                                  v_address_flag,v_phone_flag
                                  LIMIT 10;
      FORALL i in 1..v_fileid.count
         if v_address_flag(i) = 'Y' and v_phone_flag(i) = 'Y' then
            --insert address and phone tables
            insert into emp
            values(SEQ_EMP.nextval,v_lname(i),v_fname(i));
            insert into emp_addr(address,city,emp_id)
            values(v_address(i),v_city(i),seq_emp.currval);
            insert into emp_ph(phone,emp_id,type)
            values(v_phone1(i),seq_emp.currval,'1');
          elsif v_address_flag(i) = 'Y' and v_phone_flag(i) = 'N' then
             insert into emp
            values(SEQ_EMP.nextval,v_lname(i),v_fname(i));
            insert into emp_addr(address,city,emp_id)
            values(v_address(i),v_city(i),seq_emp.currval);
          elsif v_address_flag(i) = 'N' and v_phone_flag(i) = 'N' then
             insert into emp
            values(SEQ_EMP.nextval,v_lname(i),v_fname(i));
          end if;
    exit when cr%notfound;
  end loop;
  close c;
  end;

This code does not work. How can I use conditional IF between FORALL

Thanks for your help
Followup:
ops$tkyte@ORA9IR2> create table t ( fileid int, lname varchar2(10), fname
varchar2(10),
  2                   address varchar2(10), city varchar2(10),
  3                   phone1 varchar2(10), address_flag varchar2(1), phone_flag
varchar2(1) );

Table created.

ops$tkyte@ORA9IR2> create sequence s;

Sequence created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t values ( 1, 'a1', 'b1', 'aa1', 'cc1', 'pp1',
'Y', 'Y' );

1 row created.

ops$tkyte@ORA9IR2> insert into t values ( 1, 'a2', 'b2', 'aa2', 'cc2', 'pp2',
'Y', 'N' );

1 row created.

ops$tkyte@ORA9IR2> insert into t values ( 1, 'a2', 'b2', 'aa2', 'cc2', 'pp2',
'N', 'N' );

1 row created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert
  2    when (1=1) then
  3    into emp (empno,lname,fname) values ( s.nextval, lname, fname )
  4    when (address_flag='Y') then
  5    into emp_addr( address, city, empno ) values ( address, city, s.currval )
  6    when (phone_flag='Y' and address_flag = 'Y') then
  7    into emp_ph( phone, type, empno ) values ( phone1, '1', s.currval )
  8  select * from t;

6 rows created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select * from emp;

     EMPNO LNAME      FNAME
---------- ---------- --------------------
         1 a1         b1
         2 a2         b2
         3 a2         b2

ops$tkyte@ORA9IR2> select * from emp_addr;

ADDRESS    CITY            EMPNO
---------- ---------- ----------
aa1        cc1                 1
aa2        cc2                 2

ops$tkyte@ORA9IR2> select * from emp_ph;

PHONE      TYPE            EMPNO
---------- ---------- ----------
pp1        1                   1

it is a single insert into statement.
Rating: 4
I cannot use it December 22, 2003
Reviewer: suhail from NY
Tom,
I cannot use this , I tried similar approach as indicated by you in one of the
thread but I am hitting a bug# 3334077 in 9i - 9.0.1.4.0. As of Dec 31, 2003 -
Server version 9.0.1.4 is desupported ( according to Metalink ) meaning there
will be no Error Correction Support (no bug fixes), so I am bound to use Bulk
Collection. However, I can nnot use multiple DML or IF condition in FORALL. Is
there a way that I can still use FORALL or do I have to use same old row-by-row
style.

Thanks for your input.

Suhail
Followup:
it seems you filed that bug,  it goes beyond the simple example here (you have
triggers and such which are not part of this example and they are the underlying
cause)

the bug report also states that a backport of the fix may be possible.

I cannot reproduce your issue, however, the bug seems to state it has to do with
multi-row inserts only so perhaps:

declare
    type testarray is table of varchar2(4000) index by binary_integer;
    l_fileid testarray;
    l_lname testarray;
    l_fname testarray;
    l_address testarray;
    l_city testarray;
    l_phone testarray;
    l_address_flag testarray;
    l_phone_flag testarray;
begin
    select * bulk collect into l_fileid, l_lname, l_fname, l_address, l_city,
l_phone, l_address_flag, l_phone_flag
      from t;

    forall i in 1 .. l_fileid.count
       insert
         when (1=1) then
         into emp (empno,lname,fname) values ( s.nextval, lname, fname )
         when (address_flag='Y') then
         into emp_addr( address, city, empno ) values ( address, city, s.currval
)
         when (phone_flag='Y' and address_flag = 'Y') then
         into emp_ph( phone, type, empno ) values ( phone1, '1', s.currval )
       select l_lname(i) lname, l_fname(i) fname, l_address(i) address,
l_city(i) city, l_phone(i) phone1,
              l_address_flag(i) address_flag, l_phone_flag(i) phone_flag
         from dual;
end;
/

will work.  try it out.  otherwise, you either

o do it row by row
o request the backport for the trigger fix.
Rating: 4
When clause does not work December 23, 2003
Reviewer: Suhail
Tom,

I tried this on my end , I created new_emp instead of t table and found
following.

  1  declare
  2      type testarray is table of varchar2(4000) index by binary_integer;
  3      l_fileid testarray;
  4      l_lname testarray;
  5      l_fname testarray;
  6      l_address testarray;
  7      l_city testarray;
  8      l_phone testarray;
  9      l_address_flag testarray;
 10      l_phone_flag testarray;
 11  begin
 12      select * bulk collect into l_fileid, l_lname, l_fname, l_address,
l_city,
 13  l_phone, l_address_flag, l_phone_flag
 14        from new_emp;
 15      forall i in 1 .. l_fileid.count
 16         insert
 17           when (1=1) then
 18           into emp (empno,lname,fname) values ( SEQ_EMP.nextval, lname,
fname )
 19           when (address_flag='Y') then
 20           into emp_addr( address, city, empno ) values ( address, city,
SEQ_EMP.currval
 21  )
 22           when (phone_flag='Y' and address_flag = 'Y') then
 23           into emp_ph( phone, type, empno ) values ( phone1, '1',
SEQ_EMP.currval )
 24         select l_lname(i) lname, l_fname(i) fname, l_address(i) address,
 25  l_city(i) city, l_phone(i) phone1,
 26                l_address_flag(i) address_flag, l_phone_flag(i) phone_flag
 27           from dual;
 28* end;
SQL> /
         when (address_flag='Y') then
                               *
ERROR at line 19:
ORA-06550: line 19, column 32:
PL/SQL: ORA-00904: invalid column name
ORA-06550: line 17, column 65530:
PL/SQL: SQL Statement ignored

It seems when clause does not recognise address_flag column.
Here is desc of new_emp
SQL> desc new_emp
 Name                                      Null?    Type
 ----------------------------------------- -------- -------------------
 FILEID                                             NUMBER(38)
 LNAME                                              VARCHAR2(10)
 FNAME                                              VARCHAR2(10)
 ADDRESS                                            VARCHAR2(10)
 CITY                                               VARCHAR2(10)
 PHONE1                                             VARCHAR2(10)
 ADDRESS_FLAG                                       VARCHAR2(1)
 PHONE_FLAG                                         VARCHAR2(1)
Followup:
the address_flag there is the one coming from dual -- not new_emp.

show us your v$version.  code ran for me

ops$tkyte@ORA9I> declare
  2          type testarray is table of varchar2(4000) index by binary_integer;
  3          l_fileid testarray;
  4          l_lname testarray;
  5          l_fname testarray;
  6      l_address testarray;
  7          l_city testarray;
  8      l_phone testarray;
  9          l_address_flag testarray;
 10          l_phone_flag testarray;
 11  begin
 12          select * bulk collect into l_fileid, l_lname, l_fname, l_address,
l_city, l_phone, l_address_flag, l_phone_flag
 13            from t;
 14
 15          forall i in 1 .. l_fileid.count
 16         insert
 17           when (1=1) then
 18           into emp (empno,lname,fname) values ( s.nextval, lname, fname )
 19           when (address_flag='Y') then
 20           into emp_addr( address, city, empno ) values ( address, city,
s.currval )
 21           when (phone_flag='Y' and address_flag = 'Y') then
 22           into emp_ph( phone, type, empno ) values ( phone1, '1', s.currval
)
 23         select l_lname(i) lname, l_fname(i) fname, l_address(i) address,
l_city(i) city, l_phone(i) phone1,
 24                    l_address_flag(i) address_flag, l_phone_flag(i)
phone_flag
 25           from dual;
 26  end;
 27  /

PL/SQL procedure successful

ops$tkyte@ORA9I>
ops$tkyte@ORA9I> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.0.1.2.0 - Production
PL/SQL Release 9.0.1.2.0 - Production
CORE    9.0.1.2.0       Production
TNS for Solaris: Version 9.0.1.2.0 - Production
NLSRTL Version 9.0.1.2.0 - Production

ly completed.
Rating: 4
my version December 23, 2003
Reviewer: A reader
SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.0.1.4.0 - Production
PL/SQL Release 9.0.1.4.0 - Production
CORE    9.0.1.2.0       Production
TNS for Solaris: Version 9.0.1.4.0 - Production
NLSRTL Version 9.0.1.4.0 - Production
Rating: 4
i am sorry I was referencing long column name December 23, 2003
Reviewer: A reader
On my end my tables columns are different ie first_name for fname, emp_id for
empno etc. It was paste- copy problem. I apolozise for that...
December 23, 2003 Tony -- Thanks for the question regarding "Performance Tuning", version 8.1.6
You Asked
Hi Tom,
Few weeks back, I saw your your remarks about the new book - "Optimizing Oracle
Performance" by Cary on this site's home page. I immediately ordered that book.
Now I am reading this. Every line I read, I get really surprised about Cary's
approach about the use of extended sql trace facility for performance tuning.
Only after reading same thing more than once, it starts making sense to me. As
most of the time, I approach performance tuning (I am sure others also, as Cary
mentioned in his book)by using Oracle's suggested method of performance tuning
i.e., by using execution plan, tkprof tools etc. I am surprised that why oracle
corporation Gurus did not come up with this approach before. Anyway, what are
your views about this?
Kind Regards,
and we said...
Umm, its been the way I've been expousing (and many many many many others) to
tune as well.

Tell me - how do you find the stuff to tune?  How do you zero in on what to
tune?  do you tune everything?

Ok -- after you find what you want to tune -- what then?  Ahh, then you are back
to tuning 101 again and need tools such as tkprof (you really want to read a
trace file? I don't, i tkprof it), autotrace and so on.

Cary tells of a method -- in a clear way.  The method is in fact not possible
without the high degree of instrumentation placed in the database.  If this were
not part of our "scheme", the database wouldn't be instrumented -- you would not
be able to follow this practice.

This is not really "new" -- it is the way its been done in the past.  This finds
what can be fixed, then you need to actually "fix it".

Tools like tkprof and explain plan, autotrace et al are INSTRUMENTAL during
development as well.  How do you know what your stuff is doing (and could it be
doing better) if you didn't have them?

Consider the developer that writes a query that they know will be executed 100
times a second all day long.  Might they want to understand fully the
performance characteristics of such a query?  To make it consume the least
resources?

It is all about finding big resource consumers and fixing them -- either by "not
doing it at all" or doing it in a fashion that consumes the least resources (eg:
Cary refers to using runstats, a tool I use here all of the time, in order to
find the "best, most scalable" way to do something)

Even methods need tools.
Review & Followup

Rating: 5
Tom also has been saying the same .... October 05, 2003
Reviewer: Reader from Houston, TX
In this site, Tom has been using the tools such as sqlplus autotrace to
illustrate concepts many many times.

Tony, Please read Tom's new book Effective Oracle By design as well. It helped
me a lot in understanding concepts related to performance tuning.
Rating: 4
lucky man ... October 05, 2003
Reviewer: j.
One has to wait a fortnight to get tom's new book via AMAZON in germany ...
Rating: 3
Is your book available in India also? October 05, 2003
Reviewer: Dilbert from San Jose, ca, USA
Followup:
it will be
Rating: 4
More specifically.... October 05, 2003
Reviewer: Tony
Hi Tom,
Thanks for your views.
Actually, Cary has well acknowledged your runstats.html in relation to "Querying
V$data through SQL' (page 55), which is indeed excellent, and of course, you
explicitly mentioned under requirements "This test harness works best in a
simple, controlled test environment".
What I was referring to - Cary's remarks about o/p of tkprof (before 9i) which
simply ignored the "wait event" data (page 61). Don't you think that most of the
time performance analysts are called only when there is an issue in the
production system, and that too in business critical applications, and (just to
make more scarier) in 24X7 applications. As such, diagnostic reliability (which
Cary has given 3 marks based upon V$fixed views - page 56) is very very
important. Until and unless diagnosis is reliable, how can one fix the problem,
and probably I will be more relying on trial-and-error backed by my experience.
Regards,
Followup:
yes -- but it is something many people have been exspousing for years

you need to INSTRUMENT your system
you need to be able to TRACE your system
you need to gather a history of PAST performance
yadda yadda yadda.

yes, most people do the "trial and error" (i've a couple of examples of that in
my new book -- people not bothering to find out what is really wrong, just
coming up with N things to do "that couldn't hurt" -- leading to disaster)
Rating: 4
What about statspack? October 06, 2003
Reviewer: Adrian from UK
Tom

I've read your site for some time now, have your first book, use runstats and so
on but have seen you recommend Statspack to users on several occasions. Cary, on
the other hand, seems to have little truck with Statspack as he doesn't believe
that aggregated information is good enough ( well actually he says it makes
impossible to determine the true picture and he goes on to present a nice
analogy with red rocks and grey rocks...). What's your opinion on Method R
without the use of Statspack but with lower level trace information ? At the end
of the day, we have to parse that trace file and we cannot necessarily buy the
hotsos products. Is the 9i waits information in tkprof sufficient ( I haven't
got that far in Cary's book yet to see what he recommends to parse the trace
files ) ?

Regards
Followup:
the analogy is useful to understand why you need to use small windows on a
statspack report -- why for example a report that spans HOURS is useless.

I find a 15 minute report to be very useful at a high level.

say I see a really low (under 99%) soft parse ratio -- and high latch frees.
Can I say anything about that?  Yes, yes I can (you are NOT using bind variables
and parse lots and it is hurting you)

I see large direct path read/write waits.  Can I say anything about that?  Yes,
yes I can -- you are doing lots of sorts to temp -- maybe we should look at
that.

I see the top sql was executed 10,000 times and did 1,000 LIOs each time.  Might
I want to look at that query?

It is useful for tuning in the aggregate. sort of "instance" or "database"
tuning.  It finds the painfully OBVIOUS.  It is also a great way to find out
"what changed".  Say the system was going just dandy.  Now, this morning, it is
not.  A simple comparision of today with yesterday can be very very telling --
yesterday you generated 5k of redo per transaction.  Today -- you are generating
50k of redo per transaction -- that might trigger you to look in a specific area
now to find out "why" (oh yeah, we rebuilt our indexes last night -- log miner
is showing lots of extra "internal operations" on the indexes -- the indexes are
splitting back to their steady state ---- for example)

The advisors or of use as well -- what should the pga target be?  how is my
shared pool doing?  if I were to increase the buffer cache -- would it have any
measurable effect on physical IO?

To tune a PROCESS -- statspack is useless.  tkprof rocks for that.
Rating: 4
Peformance and Tuninng October 08, 2003
Reviewer: Aída Esther Assayag from Oracle Site.
Hi,

   I´m working with bigger tables , and I need to provide performance.
   The first think that, how to analsye the order to put the tables in the
command from, order and where.
Some people, say to me , that Oracle read , botton to up, is really ?
    I will appreciate the you can answer quickly.
    Thank very much, in this moment .

       Aída
Followup:
with the CBO, you do it in any order you want.  Don't worry about it.

that advice was from the legacy RBO that you won't be using.
Rating: 5
What is good? October 13, 2003
Reviewer: Zoran Martic from Dublin, Ireland
Tom,

As you said without knowing what to tune, what is the most critical business
unit to tune (Method-R from Cary) you will not have the target.

Usually global target to tune everything is usually not good enough. You can
even tune like that but you are not sure about what will happen with your
critical business unit.

From the other side you go and trace your critical part of application. And you
found that for example buffer busy waits are the problem.
How are you going to tune that if you do not have the global knowledge what is
going on in the database (Statspack report). Even that you need to drill down to
find out all parts of applications involved in buffer busy waits or whatever.

It looks to me that everything is valuable.
Cary is not telling you that Statspack reports are bad.
He is saying to me that they are not the only source and the best source.
For him you need to start with the most critical part of the application and use
extended trace file.
Good enough, but later on I think you can find many valuable information from
Statspack report.

In any case you will probably not tune something to the perfection without
knowing bussiness logic.

At the end you need to have global knowledge about the system and also sometimes
very narrow statistics to tune to the good level.

At the end Oracle is implementing all these things inside the database with new
releases.

Tom, what about x$trace? Any news in 10g.

Regards,
Zoran
Rating: 5
Tuning from a DBA perspective December 23, 2003
Reviewer: Dana Day from Phoenix, AZ USA
Hi Tom,

Happy Holidays!

I am a DBA working with dozens of applications and architectures; from batch to
OLTP;VB to Java;Oracle to SQL Server.
Recently, I went through a tuning excercise with a web reporting application
moving from SQL Server to Oracle.
The application was migrated with minimal (read none) redesign of core sql. It
performed horribly for end users once migrated, and was unusable.
I was able to determine the access paths that had worked in SQL Server did not
work correctly for Oracle.
I used massive tracing of the application, generated lots of trace files, looked
for the "low hanging fruit", and "tuned" using optimizer_index_caching,
optimizer_index_cost_adj, and db_file_multiblock_read_count.
By setting these parameters in an on logon trigger for just the application
login, I was able to dramatically alter the performance profile of the
application. End users are now usually happy, and application developers and
managers are content.
My quandary is this. Have I really addressed the issues that caused performance
problems and end user complaints? No. The application continues to use literal
SQL, aggregate functions that cause performance headaches (even though the end
users don't see it) and is allowed to execute queries returning 100K + rows that
end users never review.
I know the design is the root cause, but the performance is "good enough", and
only took a few days to "fix", so management is content to leave well enough
alone.
Is tuning via parameter changes valid as a tuning process? Or am I simply
postponing an inevitable problem by ignoring the bind variables lack, and poor
design? If no one complains, should I?

Thank you for your insight.
Followup:
ll, well, if the system is "done", will not grow, nothing else added to the
machine ever -- perhaps you are done.

You have lots more hardware then you really need.

You could fix the application and regain that capacity allowing for future
growth and new applications.

Or, you can buy yet more hardware.

tuning via parameter changes is valid -- definitely.

But -- removing work that need never be done in the first place -- that is the
real low hanging fruit.

It is funny how the developers cared so little as to inflict this on THEIR end
users without actually testing it first.  (not funny "ha ha", bad funny).  Don't
they care what people think about their work?

You should complain the next time anyone asks you to up the load on this machine
a notch.  tell them "well, we could 10x the capacity -- if the developers fixed
the bugs in their code.  or you can buy me a brand new really big machine"
Rating: 5
Insightful and concise December 23, 2003
Reviewer: Dana Day from Phoenix, AZ USA
Excellent overview. Thank you very much!
Where can I send the holiday cards?
Followup:
no need, you just did :)
December 23, 2003 Bhaskara -- Thanks for the question regarding "Assign return value to Unix Variable", version 8.1.5
You Asked
Hi Tom,
   How to assign value from SQL Plus to Unix Shell Variable?
   Ex: $ SAL=`sqlplus scott/tiger -s << select max(sal) from
emp; exit; EOF
Thanks,
Bhaskara
and we said...
It'll vary by shell, here is a ksh example:

#!/bin/ksh

x=`sqlplus / <<endl | grep KEEP | sed 's/KEEP//;s/[   ]//g'
select 'KEEP' , max(sal) from emp;
exit
endl`

echo the answer is $x

the SED's are all on the same line and in the [ ] is a SPACE and
a TAB.  When I run this, I get:

$ ./test
the answer is 5000
Review & Followup

Rating: 5
December 23, 2003
Reviewer: A reader
Tom,

I have a similar need but i am querying v$session to see the number of sessions
and this is what i get.

$ cat t2.sh
#!/bin/ksh

x=`sqlplus "/ as sysdba" <<endl | grep KEEP | sed 's/KEEP//;s/[   ]//g'
select 'KEEP' , count(*) from  v$session;
exit
endl`
echo the answer is $x

$./t2.sh
the answer is sys@ORA9I>select'',count(*)fromv

I tried using "\" before the $ in the tablename, didn't work either.

Thanks
Followup:
well, you got '*' in there, $'s (that need to be doubly escaped....)

try this:

#!/bin/ksh

x=`sqlplus "/ as sysdba" <<endl | grep KEEP | sed 's/KEEP//;s/[     ]//g'
select 'KEEP' , count(1) from  "V\\\$SESSION";
exit
endl`
echo the answer is $x
Rating: 4
if I rememeber right December 23, 2003
Reviewer: A reader
code=`sqlplus -s / << EOF
select max(sal)
  from emp
 where empno=934;
EOF`

echo $code
December 23, 2003 Alan -- Thanks for the question regarding "Show_Space Procedure Usage Problem", version 9.2.0.1.0
You Asked
Hi Tom,

I'm trying to use your procedure SHOW_SPACE and I'm encountering oracle error
message saying "ORA-10618: Operation not allowed on this segment". I have tried
to research on the error and the error states that "This DBMS_SPACE operation is
not permitted on segments in tablespaces with AUTO SEGMENT SPACE MANAGEMENT". It
is possible for you explain to me what this means.

Thanks and more power.
and we said...
Basically -- it means you cannot call dbms_space.free_blocks on an object in a
AUTO SEGMENT SPACE managed tablespace (where segment space is not managed via
freelists anymore but in a bitmap like a locally managed tablespace)

My solution:

create or replace
procedure show_space
( p_segname in varchar2,
  p_owner   in varchar2 default user,
  p_type    in varchar2 default 'TABLE',
  p_partition in varchar2 default NULL )
authid current_user
as
    l_free_blks                 number;

    l_total_blocks              number;
    l_total_bytes               number;
    l_unused_blocks             number;
    l_unused_bytes              number;
    l_LastUsedExtFileId         number;
    l_LastUsedExtBlockId        number;
    l_LAST_USED_BLOCK           number;
    procedure p( p_label in varchar2, p_num in number )
    is
    begin
        dbms_output.put_line( rpad(p_label,40,'.') ||
                              p_num );
    end;
begin
    for x in ( select tablespace_name
                 from dba_tablespaces
                where tablespace_name = ( select tablespace_name
                                            from dba_segments
                                           where segment_type = p_type
                                             and segment_name = p_segname
                                  and SEGMENT_SPACE_MANAGEMENT <> 'AUTO' )
             )
    loop
    dbms_space.free_blocks
    ( segment_owner     => p_owner,
      segment_name      => p_segname,
      segment_type      => p_type,
      partition_name    => p_partition,
      freelist_group_id => 0,
      free_blks         => l_free_blks );
    end loop;

    dbms_space.unused_space
    ( segment_owner     => p_owner,
      segment_name      => p_segname,
      segment_type      => p_type,
          partition_name    => p_partition,
      total_blocks      => l_total_blocks,
      total_bytes       => l_total_bytes,
      unused_blocks     => l_unused_blocks,
      unused_bytes      => l_unused_bytes,
      LAST_USED_EXTENT_FILE_ID => l_LastUsedExtFileId,
      LAST_USED_EXTENT_BLOCK_ID => l_LastUsedExtBlockId,
      LAST_USED_BLOCK => l_LAST_USED_BLOCK );

    p( 'Free Blocks', l_free_blks );
    p( 'Total Blocks', l_total_blocks );
    p( 'Total Bytes', l_total_bytes );
    p( 'Total MBytes', trunc(l_total_bytes/1024/1024) );
    p( 'Unused Blocks', l_unused_blocks );
    p( 'Unused Bytes', l_unused_bytes );
    p( 'Last Used Ext FileId', l_LastUsedExtFileId );
    p( 'Last Used Ext BlockId', l_LastUsedExtBlockId );
    p( 'Last Used Block', l_LAST_USED_BLOCK );
end;
/
Review & Followup

Rating: 2
September 10, 2002
Reviewer: a reader
Tom,
Is SEGMENT_SPACE_MANAGEMENT a column name in dba_segments?
I don't see it. When run your code to create procedure, get
compile error:
LINE/COL ERROR
-------- -----------------------------------------------------------------
24/16    PL/SQL: SQL Statement ignored
30/39    PLS-00201: identifier 'SEGMENT_SPACE_MANAGEMENT' must be declared
Followup:
this only applies to 9i.

For 8i and before, see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:231414051079
Rating: 5
RE: SEGMENT_SPACE_MANAGEMENT September 10, 2002
Reviewer: Mark A. Williams from Indianapolis, IN USA
Try looking in dba_tablespaces for SEGMENT_SPACE_MANAGEMENT...

HTH,

Mark
Rating: 5
9i September 10, 2002
Reviewer: Jan van Mourik from Houston, TX, USA
You can use dbms_space.space_usage for auto segments space tablespaces. So for
9i you could do something like this:

--- code piece
  select ts.segment_space_management
  into   t_segment_space_management
  from   dba_segments     seg
  ,      dba_tablespaces  ts
  where seg.segment_name    = t_segname
  and   seg.owner           = t_owner
  and   seg.tablespace_name = ts.tablespace_name
  ;
  --
  if t_segment_space_management = 'AUTO'
  then
     dbms_space.space_usage (
       t_owner,
       t_segname,
       t_type,
       l_unformatted_blocks,
       l_unformatted_bytes,
       l_fs1_blocks, l_fs1_bytes,
       l_fs2_blocks, l_fs2_bytes,
       l_fs3_blocks, l_fs3_bytes,
       l_fs4_blocks, l_fs4_bytes,
       l_full_blocks, l_full_bytes
     );
     --
     p( 'Unformatted Blocks ', l_unformatted_blocks );
     p( 'FS1 Blocks (0-25)  ', l_fs1_blocks );
     p( 'FS2 Blocks (25-50) ', l_fs2_blocks );
     p( 'FS3 Blocks (50-75) ', l_fs3_blocks );
     p( 'FS4 Blocks (75-100)', l_fs4_blocks );
     p( 'Full Blocks        ', l_full_blocks );
  else
     dbms_space.free_blocks(
       segment_owner     => t_owner,
       segment_name      => t_segname,
       segment_type      => t_type,
       freelist_group_id => 0,
       free_blks         => l_free_blks
     );
     --
     p( 'Free Blocks', l_free_blks );
  end if;
--- end of code piece

To show related info...
Followup:
Thanks!  I'll add that
Rating: 4
Space used by a table December 23, 2003
Reviewer: Vinnie from Orlando
Tom,

How can I use show_space to show the total # of bytes a table with  blob data is
currently using.

Happy Holidays!
Followup:
you can use show space on a lob segment.

to make it show the table + log segment, you'd have to query dba_tab_cols, find
the lobs, run show space again for the lobs....

I'd just call show_space on the lob segments myself or write a higher level
routine that calls show space for the table and then any lob columns.
December 23, 2003 Robert -- Thanks for the question regarding "IOUG Conference........... Recommendations", version 8.1.7
You Asked
Hi Tom,

Can you recommend some 'must see' speakers at the IOUG conference?
I know Jonathan Lewis will be there.
Also Michael Ault.
Do you know of any others that would be valuable to see?

Thanks!

Robert.
and we said...
If I was there -- I would see

Jonathan Lewis
Cary Millsap
Stephan Haisley
Tim Gorman
Roger Snowden
Wolfgang Breitling
Anjo Kolk
Gary Goodman

with particular attention to the first two.
Review & Followup

Rating: 5
Just what I needed! April 24, 2003
Reviewer: Robert from Memphis, USA
Thanks Tom,

Wish you were speaking there, but I guess a list of your recommendations will
have to do!

Thanks again.

Robert.
Rating: 5
Followup May 05, 2003
Reviewer: Robert from Memphis, USA.
Tom,

Thanks again for your recommendations.
These speaker you recommended were great!
My only problem was that I was not able to hear Anjo Kolk because Jonathan Lewis
was speaking the same time.
It was interesting at the conference to see the hint of 'skirmishes' between the
'hit ratio' and 'wait event' camps.

Question, please: There seemed to be some sort of controversy over the
definition of 'the wait interface'.
Can you explain this? .... isn't the 'wait interface' simply the 'interface'
whereby we observe Oracle's wait events, whether it is by 10046 trace, 'V$'
views, etc..?
What is the issue at stake here?

Thanks,

Robert
Followup:
well, where is the controversy?  what is the alternative proposed definition?
Rating: 5
Never could get a clear answer on this! May 05, 2003
Reviewer: Robert from Memphis, USA
Tom,

I didn't see the controversy... but it seemed to be a delicate subject...
possibly they were somehow associating discussion of this with the hit ratio,
wait event, controversy.

Thanks,

Robert.
Rating: 5
I agree May 05, 2003
Reviewer: Richard from Florida
I was also at the conference. If my memory serves me
correctly, it was Cary Millsap in his "Oracle Operational
Timing Data" session who made mention to the supposed
controversy concerning the definition of the wait interface.
I don't know to what he was referring, either. He said he
might discuss it later in the session but never did.
Followup:
I think he was referring to the controversy about the "wait interface method of
performance analysis" existing at all.

Its very existence obsoletes most 'tuning' books out there and puts most 'tuning
experts' right back to square one.

The controversy is between those that use it and those that do not.

I'd still like someone to tell me if a cache hit ratio of 99.5% is

a) good
b) bad

I'll argue that it is really

c) indifferent and not meaningful by itself

so the whole tuning by ratio thing.....
Rating: 5
Follow up for upcoming 2004 IOUG Conference December 23, 2003
Reviewer: Robert from Memphis, USA
Hi Tom,

Are there any "must see" speakers at the 2004 IOUG conference (whose names are
not on your 2003 list)?

Thanks,

Robert
Followup:
this is dangerous, lets see who I can offend this year by leaving them off the
list....

Add
Paul Dorsey,
Julian Dyke,
Tony Jambu,
Gaja Krishna Vaidyanatha (you'll not only learn something but you'll laugh lots
too)
Rating: 5
Ha Ha... Thanks! December 27, 2003
Reviewer: Robert from Memphis, USA
Wishing you and your's a great Holiday season.

Robert.
December 23, 2003 -- Thanks for the question regarding "SGA size ", version 8.1.7
You Asked
Why is the size of shared pool in  the query not exactly the
same as in the init.ora   (52,428,800)?
It is slightly large (54,361,560).

SVRMGR> select pool,to_char(sum(bytes),'999,999,999') from
v$sgastat
group by pool      ;

POOL        TO_CHAR(SUM(
----------- ------------
java pool     20,000,768
shared pool   54,361,560
                        33,890,464
3 rows selected.
SVRMGR> show parameter shared
NAME                                TYPE    VALUE
----------------------------------- -------
------------------------------
hi_shared_memory_address            integer 0

shared_memory_address                 integer 0

shared_pool_reserved_size             string  2621440

shared_pool_size                              string  52428800

Thanks.
and we said...
the shared pool holds many structures -- including the "shared
pool".

for example the init.ora paramter CONTROL_FILES contributes 264
bytes per file to the "miscellaneous" section of the shared
pool.

the "pool" in v$sgastat is not discriminating enough to break
this all out (or if you prefer -- it is poorly named, should not
be called the shared pool).
Review & Followup

Rating: 2
How do we know which value to use? January 23, 2003
Reviewer: neil kodner from salt lake city, UT
I am developing scripts which monitor shared pool % free and space free to avoid
possible ora-04031 errors.  Since

"select sum(bytes) from v$sgastat where pool = 'shared pool'"

and

"show parameter shared_pool_size"

give me two different answers, which are about 50M apart,  which value should I
use to monitor shared pool consumption.

Interestingly enough, OEM's performance manager seems to use SUM(bytes) from
v$sgastat to monitor shared pool % free and Quest's Spotlite on Oracle uses the
value from v$parameter.

Also, is there a way to measure the fragmentation level in the shared pool?
Followup:
to avoid 4031's -- use bind variables.  I've

a) never monitored it
b) never hit a 4031 in my life

because

c) I reject code in my database that is written wrong

the shared pool's largest sizing element is the shared_pool_size -- but then a
whole myriad of other parameters (sessions, processes, transactions, etc) affect
it as well.

The size of the shared pool -- v$sgastat
The contributors to it -- many init.ora parameters.
Rating: 4
What about low shared pool % free January 23, 2003
Reviewer: neil kodner from salt lake city, ut
Throughout the day, our 900 MB shared pool goes down to about 2% free.  Is a low
shared pool %free cause for alarm?  Or is it just LRU doing its job

I understand that its fragmentation, not free % that causes ora-04031.
Followup:
do you use bind variables.  to use 900meg of shared pool -- i'll betcha you are
not.

A low % free is no cause for alarm.  after all, you cannot put extra ram in the
bank and use it later.

That you will 900m in a day -- thats food for thought.
Rating: 5
Couldn't agree more... January 23, 2003
Reviewer: Jon from Sydney, Australia
We inherited a client-server VB on Sybase system 18 months ago, where all SQL
was hardcoded strings sitting in the VB code.  Bind variable -- what were they?
To accomodate a considerable upsizing in volumes and functionality, the system
was ported to 3-tier & Oracle.

Initally, after a straight Sybase ==> Oracle conversion (VB apps left as-is),
lots of 4031's, SGA > 500MB, latch contention, inconsistent query performance,
needed to use MTS, etc..

I wanted all SQL moved into packages - despite lots of push back from the MS/VB
crowd.  You know - *can't* possibly have business rules in the DB - must be run
from COM+.  (Not that you can't use bind variables from COM+ components - but
with packages at least you can guarantee it!)

This incremental process, which is nearing completion, has resulted in the DB
running quite comfortably < 150MB SGA, even with 3 times the number of users,
quadrupling of data volumes and transaction throughput.

Now, no 4031's - haven't seen 'em for over a year.  I use all that delicious
memory for more important things - like block buffers... :-)
Rating: 4
ORA-4031 April 17, 2003
Reviewer: yazid from France
Hi,
I scheduled a full export of the database at midnight. No body work in the
database at this time. But i was surprised by a message ORA-04031 :
. . export de la table                        DEF$_LOB
EXP-00008: Erreur ORACLE 4031 rencontré
ORA-04031: impossible d'affecter 4200 octets de mémoire partagée ("shared
pool","unknown object","sga heap","state objects")
. . export de la table  DEF$_TEMP$LOB  0 lignes exporées <
.
.
. export des contraintes d'intégrité référentielle
. export des synonymes
EXP-00008: Erreur ORACLE 4031 rencontré
ORA-04031: impossible d'affecter 4096 octets de mémoire partagée ("shared
pool","DBMS_JAVA","PL/SQL MPCODE","BAMIMA: Bam Buffer")
EXP-00000: Procédure d'export terminée avec erreur

I looked at statspack reports and the result was :
Pool        Name                        Begin value      End value  Difference
----------- ------------------------ -------------- -------------- -----------
java pool   free memory                  20,594,688     20,561,920     -32,768
java pool   memory in use                   376,832        409,600      32,768
large pool  free memory                     614,400        614,400           0
shared pool db_block_buffers                376,312        376,312           0
shared pool db_block_hash_buckets           175,528        175,528           0
shared pool db_files                        370,988        370,988           0
shared pool dictionary cache                373,312        374,252         940
shared pool distributed_transactions        180,152        180,152           0
shared pool event statistics per ses        584,800        584,800           0
shared pool fixed allocation callbac            320            320           0
shared pool free memory                  11,648,920     11,345,108    -303,812
shared pool joxs heap init                    4,248          4,248           0
shared pool KGFF heap                         9,160          9,160           0
shared pool KGK heap                         17,556         17,556           0
shared pool KQLS heap                     1,007,624        830,264    -177,360
shared pool library cache                 1,126,940      1,458,176     331,236
shared pool miscellaneous                34,365,128     34,742,656     377,528
shared pool PLS non-lib hp                    2,096          2,096           0
shared pool PL/SQL DIANA                    362,128        353,196      -8,932
shared pool PL/SQL MPCODE                   272,944         89,860    -183,084
shared pool sessions                        366,520        366,520           0
shared pool sql area                      1,560,156      1,523,088     -37,068
shared pool State objects                   247,360        247,360           0
shared pool table columns                    18,952         19,704         752
shared pool table definiti                    2,160          2,024        -136
shared pool transaction_branches            368,000        368,000           0
shared pool transactions                    166,804        166,804           0
shared pool trigger inform                      656            592         -64
shared pool VIRTUAL CIRCUITS                275,752        275,752           0
            db_block_buffers             22,667,264     22,667,264           0
            fixed_sga                        75,804         75,804           0
            log_buffer                       66,560         66,560           0

We changed shared_pool_size from 50Mo to 90Mo and the problem is solved. Our
SGBD works well before with 50Mo with the same load. Now the new report of
statspack is :
Pool        Name                        Begin value      End value  Difference
----------- ------------------------ -------------- -------------- -----------
java pool   free memory                  20,971,520     20,971,520           0
large pool  free memory                     614,400        614,400           0
shared pool db_block_buffers                376,312        376,312           0
shared pool db_block_hash_buckets           175,528        175,528           0
shared pool db_files                        370,988        370,988           0
shared pool db_handles                       75,000         75,000           0
shared pool dictionary cache              6,047,224      6,333,084     285,860
shared pool distributed_transactions        180,152        180,152           0
shared pool DML locks                        89,760         89,760           0
shared pool enqueue_resources               129,024        129,024           0
shared pool event statistics per ses        584,800        584,800           0
shared pool fixed allocation callbac            320            320           0
shared pool free memory                  64,227,888      4,906,524 -59,321,364
shared pool KGFF heap                         9,160          9,160           0
shared pool KGK heap                         17,556         17,556           0
shared pool KQLS heap                     4,875,680      1,826,984  -3,048,696
shared pool ktlbk state objects              80,036         80,036           0
shared pool library cache                 8,445,060     27,538,796  19,093,736
shared pool long op statistics array         74,800         74,800           0
shared pool message pool freequeue          124,552        124,552           0
shared pool miscellaneous                   787,092      1,371,128     584,036
shared pool PLS non-lib hp                    2,096          2,096           0
shared pool PL/SQL DIANA                    796,544        630,904    -165,640
shared pool PL/SQL MPCODE                   656,700        583,136     -73,564
shared pool processes                       121,200        121,200           0
shared pool sessions                        366,520        366,520           0
shared pool sql area                      4,082,840     46,772,692  42,689,852
shared pool State objects                   247,360        247,360           0
shared pool table columns                    49,000         63,572      14,572
shared pool table definiti                    5,828          4,780      -1,048
shared pool transaction_branches            368,000        368,000           0
shared pool transactions                    166,804        166,804           0
shared pool trigger defini                      228            228           0
shared pool trigger inform                      760            700         -60
shared pool trigger source                      188            188           0
shared pool VIRTUAL CIRCUITS                275,752        275,752           0
            db_block_buffers             22,667,264     22,667,264           0
            fixed_sga                        75,804         75,804           0
            log_buffer                       66,560         66,560           0
          -------------------------------------------------------------

In your opinion :
1) What is the reasons of this. (i can't keep shared_pool_size at 90Mo). Can you
explain the reasons of the ORA-4031 error(is LARGE_POOL_SIZE very small?, ...)
and how evoid it.
2) How can i know if the shared pool is fragmented
3) I don't understand how is possible that the message ORA-4031 can happen (no
space in the shared pool). I thought that when no space is available, Oracle
unload some things from the shared pool and use this new space, but in this case
the process is longer.

thank you
Followup:
sigh, no version, no os, no export command line, nothing....

when you have an opportunity -- and I'm taking new questions -- this looks like
one.  you would have to supply that info (not asking for it HERE bear in mind).

90meg is pretty "average" for a shared pool by the way.  Export exercises
(floods if you will) the shared pool with tons of SQL.  The shared pool can only
flush that which is not being used.  Space is managed in about 4k chunks in the
shared pool (as evidenced by the request above).  It looks like your reserved
size might be set and it really need not be.  put it to zero (see how you had
11m free memory at the start and the end?  never got used, I'm assuming thats
"reserved" and you are not using it)
Rating: 5
Possibilities of bad PL/SQL code? April 18, 2003
Reviewer: Mac from Bloomington, IL
<B>
You said:

Followup:
to avoid 4031's -- use bind variables.  I've

a) never monitored it
b) never hit a 4031 in my life

because

c) I reject code in my database that is written wrong
</B>

Outside of dynamic SQL, is it possible to write PL/SQL code where bind variables
are not used? Could you please tell what to look for to identify non-use of bind
variables?

Is 8.1.7.0.0 more prone to ORA-4031 error as compoared to previous versions of
the database?
Followup:
in plsql -- you have to be using dynamic sql in order to NOT use bind variables
properly.

No, 817 is no more or less prone.
Rating: 3
Regarding bind variables in ASP/VB apps April 18, 2003
Reviewer: A reader from New Jersey
I have a question about one of the followups posted here.
===========
> We inherited a client-server VB on Sybase system
> 18 months ago, where all SQL was hardcoded strings
> sitting in the VB code.
=========

I have a lot of VB/ASP apps in my shop and, as this poster says, all of them
dynamically construct their SQL using various user-inputs, Web-page form inputs,
etc. Basically, they just glue together the query and submit to Oracle.
Obviously, this doesnt use bind variables, so this is, according to you, the
cardinal sin.

I can see the ill-effects of this in my database, very large SGA, lots of almost
identical SQLs in the sql area, etc.

How exactly can these ASP/VB developers use bind variables? I am not a ASP/VB
programmer, so, if you can, please provide some detail.

Would this entail a lot of surgery to convert existing apps to use bind
variables?

Thanks a lot.
Followup:
all they need do is -- this will be surprising -- is crack the documentation.

they need to prepare a statement
bind to it
execute it.

If they feel "this is just an oracle issue", think again.

search google for:

"sql injection"

and read on.  See what stupid SECURITY bugs arise.  Consider the login screen
that takes a username/password in.  The programmer codes something like:

sql_query = "select count(*) from password_table " +
            "where username = '" + username + "' " +
            "  and password = '" + password + "'";

Now, I enter

username = BOB
password = you_are_a_loser' or '1'='1

so, what query do they formulate?

select count(*)
  from password_table
 where username = 'BOB'
   and password = 'you_are_loser' or '1' = '1';

Oh wow, I'm in, cool -- how easy is that.  Wait till you read about sql
injection.

For this reason and this reason alone -- all "concatenation" to build a query
should be outlawed.  I'll see data I should not.  I'll be able to run commands I
was not intended to be able to.

It is NOT just about performance -- it is about doing it right, doing it secure.

Surgery?  No, it is not surgery -- but it is NECESSARY.
Rating: 5
Should I flush v$sqlarea? April 27, 2003
Reviewer: Alessandro from Genova, Italy
We are in the same position: we have a small Web application (ASP pages - Oracle
8.1.6) and we "build" SQL strings in the VB code: after reading this (and
others) post of yours we are now switching to bind variables.

Is this one of those rare cases when it is advisable to delete the contents of
v$sqlarea?

Also, do you think we should "move SQL into packages", as another poster
suggested, or using parameters & exec via VB code is giving us the same
advantages?

Thank you for your time.

Alessandro
Followup:
flushing the shared pool is something you do only when forced.

I am of the opinion that the best java/vb programs do not contain the words
select/insert/update/delete in them anywhere -- only "begin procedure; end;"
calls.

It gives the coders less opportunities to make grievous, hard to fix errors and
reduces the number of statements they MUST use bind variables for (since we can
"hide" many sql statements in a single procedure -- they only have to bind to
one procedure call instead of N sql statements)

Remember it is not only for PERFORMANCE but security.  Say you have an ASP page
that validates a username and password and right now you put the string together
like this:

"select count(*)
   from user_pw_table
  where username = '" + username_from_screen + "'
    and password = '" + password_from_screen + "'"

looks like if I put in username=scott password=tiger it would generate:

ops$tkyte@ORA920> select count(*)
  2    from dba_users
  3   where username = 'SCOTT'
  4     and password = 'TIGER'
  5  /

  COUNT(*)
----------
         0

but what if I use a username of scott and a password of

TIGER' or rownum = '1

hmmm,

ops$tkyte@ORA920> select count(*)
  2    from dba_users
  3   where username = 'SCOTT'
  4     and password = 'TIGER' or rownum = '1'
  5  /

  COUNT(*)
----------
         1

search google for

"sql injection"

and read on...  Just think of what the user might be able to do by modifying
your SQL on the fly like that -- wow.  Accidently type in some garbarge and that

delete from table where x = 5

becomes

delete from table where x = 5 or 1=1

and so on....
Rating: 4
SGA size April 27, 2003
Reviewer: Reader
you said ..

"I am of the opinion that the best java/vb programs do not contain the words
select/insert/update/delete in them anywhere -- only "begin procedure; end;"
calls."

I didnt get you. - is it really possible ? ..do you mean to say that use those
calls inside begin -end; ?

Thanks
Followup:
No, I mean java/VB programmers would call only stored procedures which have been
coded by people who know the database and know how to program the database
(meaning -- they might be one in the same, or they might be two groups)...

Talk about code reusability -- that is the way to achieve it.
Rating: 5
I Agree April 27, 2003
Reviewer: Anonymous from New Jersey, USA
I could not agree more with this.
We are doing exactly the same thing.
We have One (in many cases many) PL/SQL packages developed for each of our
applications and the VB code just calls one of the function/procedure from these
packages to perform the action requested by the client.
No hardcoded SQL of any nature within the ASP and/or VB code is our mantra.
Rating: 5
Stored Procedures: how exactly? April 28, 2003
Reviewer: Alessandro from Genova, Italy
Thanks Tom and "Anonymous from New Jersey" ;-)

--- Quote ---
We have One (in many cases many) PL/SQL packages developed for each of our
applications and the VB code just calls one of the function/procedure from these
packages to perform the action requested by the client.
--- End Quote ---

Interesting, but I'm not quite sure how should it be implemented: do you have a
function for each table and for each DML (InsertTableA, UpdateTableB,
SelectTableC, etc.) or is it possible to code, for example, a generic
Store(TableName...) that takes care of all the possibilities?

Can you provide links with examples specific to Oracle w/VB-ASP?

Thanks again.

Alessandro
Followup:
*transaction*

you do things at the *transaction* level.  stop thinking tables, start thinking
"transactions"

for example, AskTom has an API package that provides the transactions for the
guy who does the UI.  It has entry points like:

ROCEDURE SAVE_REVIEW
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 P_DISPLAYID                    VARCHAR2                IN
 P_RATING                       VARCHAR2                IN
 P_TITLE                        VARCHAR2                IN     DEFAULT
 P_NAME                         VARCHAR2                IN     DEFAULT
 P_LOCATION                     VARCHAR2                IN     DEFAULT
 P_REVIEW                       VARCHAR2                IN     DEFAULT
 P_FROM_SUBMITTER               VARCHAR2                IN     DEFAULT
 P_EMAIL                        VARCHAR2                IN     DEFAULT

PROCEDURE SAVE_REVIEW_FOLLOWUP
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 P_ID                           VARCHAR2                IN/OUT
 P_DISPLAYID                    VARCHAR2                IN
 P_REVIEWID                     VARCHAR2                IN
 P_COMMENTS                     VARCHAR2                IN

PROCEDURE SET_PREFERENCE
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 P_PREFERENCE                   VARCHAR2                IN     DEFAULT
 P_VALUE                        VARCHAR2                IN     DEFAULT
 P_USER                         VARCHAR2                IN     DEFAULT
 P_FORCE_UPPER                  BOOLEAN                 IN     DEFAULT

PROCEDURE UPDATE_CATEGORIES
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 P_CATEGORY_LIST                VARCHAR2                IN
 P_APPLICATION_CODE             VARCHAR2                IN

PROCEDURE UPDATE_QUESTION
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 P_ID                           VARCHAR2                IN
 P_EMAIL                        VARCHAR2                IN
 P_FNAME                        VARCHAR2                IN
 P_LNAME                        VARCHAR2                IN
 P_LOCATION                     VARCHAR2                IN
 P_JOB_TITLE                    VARCHAR2                IN
 P_ORG                          VARCHAR2                IN
 P_CATEGORY                     VARCHAR2                IN
 P_VERSION                      VARCHAR2                IN
 P_YEARS                        VARCHAR2                IN
 P_SUBJECT                      VARCHAR2                IN
 P_QUESTION                     VARCHAR2                IN

And so on.  Each of those transactions may update many tables (or one) -- the
only "database decision" the GUI makes is when to commit (eg: the procedures do
NOT commit).

The transactions can get quite complex -- for example, I use interMedia (Oracle
Text) to index this site.  Now, you look at this page and say "well, that is a
single page".  In my database however -- the question is stored in a column in
table T1, the answer is in another column in T1.  The "reviews" are a column in
table T2 (and span many rows -- a 1:m relationship).  The "followups" to the
reviews (what I put in -- like this text you are reading now) are a column in a
third table T3.  When I assemble a page -- we query many tables to pull it all
together.  Anytime you add a review, I add a followup or I publish a question
however -- we need to make sure Oracle Text indexes or re-indexes that page.
Hence when you insert a row into the reviews table (T2) -- we must update the
questions table (T1) in order to let Oracle text know "the table that the index
exists on was modified".  Now Oracle Text knows to reassemble the page and index
it.

This was a feature I added after the fact -- not long ago, we just indexed the
Q&A and not the reviews.  I was able to add this comlex logic to the database
API without affecting the UI code *at all*.  Further, I -- being database guy --
knew exactly what I needed to do.  Joe Programmer -- UI guy -- would have no
idea how to approach this problem *correctly*.

If you end up with just CRUD packages (CReate, Update, Delete) -- you've done it
totally wrong in my opinion.  You need to have an exposed set of transactions
that the GUI (ANY gui bear in mind -- from sqlplus to java to vb) may call.
Rating: 5
December 23, 2003
Reviewer: A reader
Tom,

I am getting  these errors, do you think this issue is related to parsing?
Should i increase the shared pool or it is sized appropriately.

Thanks for your help;

Here are the errors/values.

ORA-04031: unable to allocate 25784 bytes of shared memory ("shared
pool","unknown object","sga heap(1,0)","session param values")
ORA-02063: preceding line from DBLK_ISIS

SQL> select name,value from v$sysstat where name like '%parse%';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
parse time cpu                                                      2909898
parse time elapsed                                                 79905694
parse count (total)                                                12810787
parse count (hard)                                                   687699
parse count (failures)                                                 1075

SQL> select STARTUP_TIME from v$instance;

STARTUP_T
---------
05-DEC-03

SQL> sho sga

Total System Global Area 3635141040 bytes
Fixed Size                   735664 bytes
Variable Size             402653184 bytes
Database Buffers         3221225472 bytes
Redo Buffers               10526720 bytes
SQL>

SQL>  sho parameter shared_pool

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
shared_pool_reserved_size            big integer 12582912
shared_pool_size                     big integer 251658240

Here's the breakdown of shared pool

SQL> select name,
  2                 round(bytes/1024/1024,2) mbytes,
  3                 mbytes_tot,
  4                 round(round(bytes/1024/1024,2)/mbytes_tot*100) pct,
  5                 round(to_number(value)/1024/1024) shared_pool_size
  6        from v$sgastat,
  7                 (select round(sum(bytes)/1024/1024) mbytes_tot
  8                        from v$sgastat
  9                       where pool = 'shared pool'),
 10                (select value , rownum
 11                       from v$parameter
 12                      where name = 'shared_pool_size' )
 13      where pool = 'shared pool'
 14  /

NAME                           MBYTES MBYTES_TOT        PCT SHARED_POOL_SIZE
-------------------------- ---------- ---------- ---------- ----------------
errors                             .5        368          0              240
KGK heap                          .01        368          0              240
KQR L PO                         1.36        368          0              240
KQR L SO                          .03        368          0              240
KQR M PO                         1.65        368          0              240
KQR M SO                          .05        368          0              240
KQR S SO                          .02        368          0              240
KQR X PO                          .03        368          0              240
sql area                           90        368         24              240
KGLS heap                        1.95        368          1              240
PX subheap                        .03        368          0              240
parameters                        .05        368          0              240
free memory                     16.99        368          5              240
gcs shadows                     37.38        368         10              240
PL/SQL DIANA                     2.84        368          1              240
ges enqueues                     9.23        368          3              240
PL/SQL MPCODE                    2.09        368          1              240
gcs resources                   56.26        368         15              240
ges resources                    5.69        368          2              240
library cache                   59.38        368         16              240
miscellaneous                      64        368         17              240
KCL name table                    2.1        368          1              240
MTTR advisory                     .81        368          0              240
PLS non-lib hp                      0        368          0              240
joxs heap init                      0        368          0              240
kgl simulator                    7.11        368          2              240
sim memory hea                   2.91        368          1              240
table definiti                      0        368          0              240
temporary tabl                    .05        368          0              240
trigger defini                      0        368          0              240
trigger inform                      0        368          0              240
trigger source                      0        368          0              240
dictionary cache                 3.08        368          1              240
event statistics per sess         2.4        368          1              240
fixed allocation callback           0        368          0              240

35 rows selected.
Followup:
i'd say you are having a bind variable issue -- you are hard parsing way too
much.  have you checked that out (your shared pool is huge)

does your application use binds?
Rating: 5
December 23, 2003
Reviewer: A reader
Sorry forgot to mention I am Oracle 9204 sun box.
December 23, 2003 Matt -- Thanks for the question regarding "Moving from RBO to CBO.", version 9.0.1.0.0
You Asked
Is there some way to identify at run-time which optimiser was used? Is this
something that can be output from an SQL_TRACE?

We've been using a 7.3.4.5 database (and RBO) for some time and are working
towards an upgrade to 9.0.1.0.0. When we get there I want to move from the RBO
that we use currently to the CBO. At the moment I am considering the move by
switching different system processes that connect DB sessions over one at a time
using an 'alter session optimiser = cost', whilst the RBO is used at the system
level.

I want to see proof that the optimiser has changed at runtime.

Cheers,

Mat.
and we said...
You can use a query such as this:

ops$tkyte@ORA9I.WORLD> select /*+ all_rows */ a.user_name, a.sql_text,
b.optimizer_cost
  2  from v$open_cursor a, v$sql b
  3  where a.address = b.address
  4  and a.sid = (select sid from v$mystat where rownum=1)
  5  /

USER_NAME SQL_TEXT                                 OPTIMIZER_COST
--------- ---------------------------------------- --------------
OPS$TKYTE BEGIN DBMS_APPLICATION_INFO.SET_MODULE(:              0
          1,NULL); END;

OPS$TKYTE select /*+ all_rows */ a.user_name, a.sq            122
          l_text, b.optimizer_

OPS$TKYTE ALTER SESSION SET NLS_LANGUAGE= 'AMERICA              0
          N' NLS_TERRITORY= 'A

OPS$TKYTE SELECT CHAR_VALUE FROM SYSTEM.PRODUCT_PR              0
          IVS WHERE   (UPPER('

OPS$TKYTE BEGIN DBMS_OUTPUT.ENABLE(1000000); END;               0
OPS$TKYTE BEGIN DBMS_OUTPUT.DISABLE; END;                       0
OPS$TKYTE SELECT NULL FROM DUAL FOR UPDATE NOWAIT               0

OPS$TKYTE SELECT ATTRIBUTE,SCOPE,NUMERIC_VALUE,CHA              0
          R_VALUE,DATE_VALUE F

OPS$TKYTE SELECT ATTRIBUTE,SCOPE,NUMERIC_VALUE,CHA              0
          R_VALUE,DATE_VALUE F

OPS$TKYTE SELECT USER FROM DUAL                                 0
OPS$TKYTE SELECT DECODE('A','A','1','2') FROM DUAL              0

OPS$TKYTE SELECT DECODE('A','A','1','2') FROM DUAL              0

OPS$TKYTE commit                                                0
OPS$TKYTE select /*+ all_rows */ * from dual                    8
OPS$TKYTE select lower(user) || '@' || decode(glob              0
          al_name, 'ORACLE8.WO

15 rows selected.

I don't recommand using the HINT in the v$ query!  that was just to show
another query using CBO in this example

The rows with optimizer cost > 0, those are CBO, the others -- not.
Review & Followup

Rating: 5
CBO - Production; RBO - Development July 01, 2002
Reviewer: Yogeeraj from Mauritius
Hello,

We were thinking about setting the optimizer mode for our production database to
CBO and that of our development database to RBO.

Is this a good idea? (or would be better to have both environment set to CBO?)
What would be the advantages of "moving" statistics gathered on a production
database to a development database?

{we would ensure that all the queries written by on the development database are
well written - Rule-based; We would setup all the environment for statistics
gathering and monitoring on the production database)

Thank you for a reply

Best Regards
Yogeeraj
Followup:
You are kidding right?  wow.

Why not develop on Sybase and deploy on Oracle then?

That would be a horrible idea -- think about it.

a) the set of query plans possible with the CBO is much larger then RBO
b) what is "well written"?  If you take a "finely tuned RBO query" and through
it at a CBO database, well, it'll be optimized totally different.  Doesn't
matter how "nicely" it is written -- the CBO doesn't look at the syntax (order
of tables -- doesn't matter, order of predicates, doesn't matter, and so on)

Development should be a mirror of production (as should TEST).
Rating: 4
Could you please explain more on this July 01, 2002
Reviewer: A reader
"The rows with optimizer cost > 0, those are CBO, the others -- not."

Could you please explain more on this... I mean why would the cost be zero for
RBO? Also, I was thinking that which optimizer is used at runtime depend solely
on the OPTIMIZER_MODE parameter. Could you please explain how Oracle decide on
which optimizer to use. Thanks.
Followup:
A zero cost would be invalid (not a cost at all, the smallest cost is 1)

they are sticking a zero in there when CBO is not used, thats all. (sometimes
null).

The optimizer used is NOT dependent on the optmizer goal setting -- you can be
using CBO when you ask for RBO and you can be using RBO when you ask for CBO.

Hints will override that setting (see my example, I used hints)
Use of features that require CBO will override that setting.  Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int primary key )
organization index;

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t TKYTE_TEST;

no rows selected

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select b.sql_text, b.optimizer_cost
  2  from v$sql b
  3  where upper(b.sql_text) like '%TKYTE_TEST%';

SQL_TEXT                                 OPTIMIZER_COST
---------------------------------------- --------------
select * from t TKYTE_TEST                            1
select b.sql_text, b.optimizer_cost from              0
 v$sql b where upper(b.sql_text) like '%
TKYTE_TEST%'

and IOT (index organzed table) only works in CBO -- so, the select * from t is
CBO (cost=1, cost > 0).  The other query -- the query used to find this query --
is in RBO (cost = 0, cost is not > 0)
Rating: 5
July 01, 2002
Reviewer: A reader
Tom,

 One of the recent note in Metlink tells that RULE base optimizer is obsolete
after 9.2(Oracle won't support after 9.2 release).

 is that true?

 That mean "Optimizer stability plan" (Outline) feature
 also become obsotele after release 9.2.
 am i right ?

 Please explain.

 Thanks
Followup:
As far as I know, yes, the RBO is "dead" in the next release.

No, that does not obviate the optimizer plan stability feature -- that feature
is only needed when using the CBO.
Rating: 4
Optimiser Statistics and the data dictionary July 01, 2002
Reviewer: Matt from Australia
This definately answers my question. I am a little concerned about the comment
about the hint and the v$ views though. I guess that the query runs slowly when
the hint is included in the select is because all the base data dictionary
tables are having stats generated at runtime??

In the system I am working on we have a user defined view that joins with
v$session. Does this mean that to make sure that this user defined view performs
reasonably that I will need to generate statistics on the data dictionary? Is
there a better way around this?
Followup:
Do not generate statistics on the data dictionary.  Do not touch the data
dictionary (without support saying "do this")

You may use a hint in the query that accesses v$session if you find the
generated plan to not be optimal.
Rating: 4
Column optimizer_mode on v$sql July 02, 2002
Reviewer: Robert Hayden from Kansas City, MO USA
I was checking this out in Oracle 8.1.7.3 set in RBO mode and noted an
interesting issue that is confusing.  When I use a all_rows hint, then the
column, optimizer_mode in v$sql, is set to ALL_ROWS.  When I use an index()
hint, the optimizer_mode remains to be RULE, but the optimizer_cost is non-zero.
 Does this mean that the index hint invoked the CBO or not?
.
In Oracle 7.3, we have always thought that any Oracle hints invoked the CBO, but
I have never been able to prove it because I have focused on the optimizer_mode
and on index hints since that is what the application uses.
.
Thanks
Followup:
The optimizer mode was RULE but the query was optimized by CBO cause the HINT
overrides the optimizer mode.  It was CBO'ed

Even in optimizer_mode=rule, the CBO can (and will) be used.  The optimizer mode
is just a setting that says "hey, if you have a choice, use the RBO".  If you
don't have a choice (a hint is used, a table with a non-default degree of
parallelism was used, a partitioned table was used, and IOT was used, etc) then
use whatever you need.
Rating: 4
stats on dictionary July 02, 2002
Reviewer: A reader
What if someone makes a mistake and generates statistics on the data dictionary?
Can recreation of dictionary help?
Followup:
well, that would be like using an atom bomb to crack a walnut.

I would just delete the statistics.
Rating: 4
Which parameters influence the CBO the most? January 14, 2003
Reviewer: Matt from Australia
OK, we upgraded from 7.3.4.5.0 to 9.0.1.4.0 (the original post).We are currently
struggling with the CBO. We are trying to implement this on a module by module
basis as per the original post. Most of our modules when run vanilla with CBO
out of the box run slower than the RBO.

Typically which parameters are likely to be the culprit when looking at poor
plans? See below:

SORT_AREA_SIZE
HASH_AREA_SIZE
HASH_JOIN_ENABLED
DB_FILE_MULTIBLOCK_READ_COUNT
OPTIMIZER_INDEX_CACHING
OPTIMIZER_INDEX_COST_ADJ

others?
I will post a detailed question regarding the tuning issues at the proper time
(when I see the NO BACKLOG banner)

Regards.
Followup:
read this
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6601251003901
Rating: 2
January 14, 2003
Reviewer: Matt from Australia
Yep, I already had - mine was the first follow up comment on the linked
question.

I will continue with my investigations and lodge a question with all the
required detail later.

Regards,
Rating: 5
Mergeable and Non-mergeable Views February 06, 2003
Reviewer: GVN from Houston
Tom,

In the oracle 'Oracle8i Designing and Tuning for Performance' documentation
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76992/optimops.htm#14720
The section of 'Mergeable and Non-mergeable Views' mentions the following
<quote>The optimizer can merge a view into a referencing query block when the
view has one or more base tables</quote>
1) What is a referencing query block?

<quote>View merging is not possible for a view that has multiple base tables if
it is on the right side of an outer join. However, if a view on the right side
of an outer join has only one base table, then the optimizer can use complex
view merging, even if an expression in the view can return a non-null value for
a NULL</quote>
2) Does the above <quote> apply to an optimizer that uses CBO or does it just
apply to RBO?  I would apprecite it if you can give a little explanation.

Thank you in advance for the valuable advise.
Followup:
the outer query block

select *
  from ( select empno, ename, sal, emp.deptno, dept.dname
             from emp, dept
            where ename like '%A%'
              and emp.deptno = dept.deptno )
 where dname like '%A%'

the bold stuff is the "referencing query block" to the non-bold stuff.

<quote from same place>
Merging the View's Query into the Statement

To merge the view's query into a referencing query block in the accessing
statement, the optimizer replaces the name of the view with the names of its
base tables in the query block and adds the condition of the view's query's
WHERE clause to the accessing query block's WHERE clause.
</quote>

So here, the query will be processed as:

select empno, ename, sal, emp.deptno, dept.dname
  from emp, dept
 where ename like '%A%'
   and emp.deptno = dept.deptno
   and dname like '%A%'

Now, if the query where:

select *
  from ( select empno, ename, sal, emp.deptno, dept.dname
             from emp, dept
            where ename like '%A%'
              and emp.deptno = dept.deptno(+) )
 where dname like '%A%'

we cannot do that merging

2) complex view merging can happen for both RBO and CBO but there are sooooo
many things only the CBO can do that, well, you should be using it.
Rating: 5
Mergeable and Non-mergeable Views February 06, 2003
Reviewer: GVN from Houston
What appeared to be Greek and Latin before posting the question is quite clear
after your answer

Thank you
Rating: 4
What is the difference in the settings OPTIMIZER_GOAL and OPTIMIZER_MODE? March 04, 2003
Reviewer: Abhijit from Mumbai
Hi Tom,
I am using Oracle 8i
Could you please tell me what is the difference in the following two statements?
1) alter session OPTIMIZER_GOAL = CHOOSE;
2) alter session OPTIMIZER_MODE = CHOOSE;

Thanks & Regards,
Abhijit
Followup:
more or less the same, they both set the sessions optimizer technique to choose.
Rating: 3
Thanks for the answer March 04, 2003
Reviewer: Abhijit from Mumbai
However if my mode differs from my goal, which optimizer will be used?

Thanks & Regards,
Abhijit
Followup:
they are synonymous, whichever goes last.  watch

ops$tkyte@ORA817DEV> set autotrace traceonly explain
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select * from dual;

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

start out using RBO (no cost/card/bytes in the plan)

ops$tkyte@ORA817DEV> alter session set OPTIMIZER_GOAL = FIRST_ROWS;

Session altered.

ops$tkyte@ORA817DEV> select * from dual;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=1 Card=82 Bytes=164)
   1    0   TABLE ACCESS (FULL) OF 'DUAL' (Cost=1 Card=82 Bytes=164)

that put us in CBO....

ops$tkyte@ORA817DEV> alter session set OPTIMIZER_MODE = CHOOSE;

Session altered.

ops$tkyte@ORA817DEV> select * from dual;

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

and that took us back out

ops$tkyte@ORA817DEV> alter session set OPTIMIZER_MODE = FIRST_ROWS;

Session altered.

ops$tkyte@ORA817DEV> select * from dual;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=1 Card=82 Bytes=164)
   1    0   TABLE ACCESS (FULL) OF 'DUAL' (Cost=1 Card=82 Bytes=164)

now to show that mode is overriden by goal -- as we showed above that goal is
overriden by mode

ops$tkyte@ORA817DEV> alter session set OPTIMIZER_GOAL = CHOOSE;

Session altered.

ops$tkyte@ORA817DEV> select * from dual;

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

we are back in rbo mode

they cannot differ because they are really the same.
Rating: 4
what about response time? June 02, 2003
Reviewer: Tony from INDIA
Cost based optimizer always goes for lesser cost plan. My doubt is if the
response time is considered or not for the cost calculation. What if I'm not
bothered about the cost, but want a query to return rows as quickly as
possible?. How to achive this in CBO?
Followup:
you would use FIRST_ROWS optimization to tell the optimizer "I'm interested in
response time, the time to get the first row should be minimized"

And later, in your reports and batch programs, ALL_ROWS optimization is what you
would want.

So, your interactive apps might consider "alter session set
optimizer_goal=first_rows" where as your batch/reports might consider
"=all_rows"
Rating: 3
Response time and Cost September 08, 2003
Reviewer: Anil Pant from Hyderabad, India
Im bit confused with Cost and Response time. The response time is based on cost.
Higher the cost more the response. Or do u mean that cost incurred to fetch the
row would be different.
Followup:
think about this.

if I decrease the cost of a query, but the plan stays the same, does the query
actually execute faster????

they are loosely related.  you cannot look at cost and say 'that'll take 5
minutes to run'

response time is "real"
cost is a "guess"

cost is based loosely on anticipated response time base on estimated resources
need to execute the query.
Rating: 3
Why worry of cost ? September 09, 2003
Reviewer: Anil Pant from Hyderabad, India
Agreed. But Im more interested in response time and i want to minimize that then
why should I worry abt cost ? In this case what to do ?
Followup:
tune the query?

not sure what to say here really.

cost is sort of related to response time, but not in a fashion that you can say
"cost = 10324 means 5 minutes".

in theory a query plan with a lower cost that does the same thing as some other
plan with a higher cost would run faster

however, it is just software based on a mathematical model -- hence, it might
not be true in every case (i know it isn't)

so, you tune.
Rating: 5
interesting December 23, 2003
Reviewer: A reader
I thought I was the only seen this but it seems that you use it too Tom

OPS$TKYTE SELECT NULL FROM DUAL FOR UPDATE NOWAIT

We have SELECT NULL FROM DUAL FOR UPDATE NOWAIT in our Peoplesoft ERP, what does
this do and the purpose?

Nothing in my humble opinion.....
Followup:
it is a way to serialize operations.

only one person at a time can do that -- others block.

So, it has a profound effect on things -- it is used to serialize access to
something, like a big lock.

I don't recall what application I might have been using that did that tho, or if
it was part of another example.  But -- it is a method of serializing processes.
Rating: 5
serialize DUAL?! December 23, 2003
Reviewer: A reader
err do you mean serialize what kind of operation... I mean noone is inserting
dual isnt it?!
Followup:
no, they are using DUAL to serialize SOME operation.

suppose you have a report, you only want this report run by a single user at a
time.

make the first line of code in the report:

select null from dual for update;

and the last line:

commit;

now, one person will run the report at a time.  it is just a serialization
device
Rating: 5
hmm how does dual serialize December 23, 2003
Reviewer: A reader
Hi

It's DUAL Table magic or this works for any 1 row 1 column table?

I dont see how a report can be run by one guy only after setting that...?

If I do

select null from dual for update and select * from emp in session_a, none else
in other sessions can run select * from emp?
Followup:
works on ANY TABLE

you've said "hey,select all of the rows for update (lock them)"

no one else can do that until you commit.

no one else can select null from dual for update, anyone can select * from emp.

the point is, a program would use the for update on that table to serialize some
process -- every instance of that process would go through the same steps -- get
a lock, do something, release lock
December 23, 2003 Rob -- Thanks for the question regarding "Meaning of values of status column in V$LOGMNR_CONTENTS", version 8.1.7
You Asked
Hi Tom,
  I've been using the LogMiner utility to analyse a "data loss" problem that has
begun to appear from an in-house written application.  I noticed that there is a
status column within the v$logmnr_contents view, but cannot find any
documentation detailing the meanings of the various values that can occur.  For
instance, in a sample period I used, most had a status of 0, which I assume
means successful.  However a significant number had a status of 2 and 1 row
showed a status of 13.  Could you point me to anywhere which details the meaning
of these values?
Regards,
  Rob
and we said...
The value of 0 means the SQL_UNDO and SQL_REDO columns contain valid, useful SQL

Any other value indicates they do not.  They could be due to chained rows
(cannot generate the undo/redo sql for those), IOT overflow segments, object
types -- anything that logminer cannot currently deal with.
December 23, 2003 Raj -- Thanks for the question regarding "Transforming row to columns", version Oracle 8i
You Asked
Hi Tom,

Thanks very much for your answers for all my questions.

Now I have one more such silly question.

My requirement is I have to transform all my rows into columns.

Here is an example:

First table: t_events(event, desc, logdate)
Event    Desc       Logdate
1        abcd       xxxxxxx
2        xyz        zzzzzz

Second table:t_event_data(dataid, event, element, curval)
Dataid    Event   Element      CurrVal
1         1       userid       Mani
2         1       company      WFB
3         1       transaction  wells
4         1       userid       rao
5         1       company      NOTWFB
6         1       transaction   wires

7         2       val1         0
8         2       val2        3/12/01 etc...

The above structures are my input tables.

Now My goal is to create a view for each event. The view should should
show as follows.

View for event 1:
event_view1(evnet_id, desc, logdate, userid, company, transaction);
Event_id    Desc  Logdt    userid    company     Trasaction

1           abcd  xxxxx    Mani      WFB         wells
1           abcd  xxxxx    rai       NOTWFB      wires

View for event 2:
Event_veiw2 (event_id, desc, logdate, val1, val2);
event_id    desc   logdate    val1    val2
1           xyz    yyyyyy     0       3/12/01

Based on the above input tables I have to create the views which can
be used by my developers.

Could you please suggest me some solution for this or Do I need to change my
table structure.

This is exactly like transforming the rows to columns. Is there any
facility Oracle 8i provides to achieve this..

I appreciate your early reply.

Thank you very much.
Rao
and we said...
decode can do this.

ops$tkyte@ORA8I.WORLD> create or replace view event_view1
  2  as
  3  select t_events.event,     t_events.descript,  t_events.logdate,
  4         max( decode( element, 'userid', curval, NULL ) ) userid,
  5         max( decode( element, 'company', curval, NULL )) company,
  6         max( decode( element, 'transaction', curval, NULL)) transaction
  7    from t_events, t_event_data
  8   where t_events.event = t_event_data.event
  9   group by t_events.event,  t_events.descript,  t_events.logdate
 10  /

View created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> create or replace view event_view2
  2  as
  3  select t_events.event,     t_events.descript,  t_events.logdate,
  4         max( decode( element, 'val1', curval, NULL ) ) val1,
  5         max( decode( element, 'val2', curval, NULL )) val2
  6    from t_events, t_event_data
  7   where t_events.event = t_event_data.event
  8   group by t_events.event,  t_events.descript,  t_events.logdate
  9  /

View created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select * from event_view1;

     EVENT DESCRIPT   LOGDATE   USERID          COMPANY         TRANSACTION
---------- ---------- --------- --------------- --------------- ---------------
         1 abc        28-MAR-01 rao             WFB             wires

ops$tkyte@ORA8I.WORLD> select * from event_view2;

     EVENT DESCRIPT   LOGDATE   VAL1            VAL2
---------- ---------- --------- --------------- ---------------
         1 abc        28-MAR-01 3/12/01         0
Review & Followup

Rating: 5
Rows To Columns: What about 9i March 30, 2003
Reviewer: Georg from Germany
Hi Tom,

great "trick". What about 9i ? Same trick or is there some better one ?

Thanks in advance.
Georg
Followup:
sql is sql -- there is no "pivot" command in sql.

This is not a trick, it is a sound technique.  SQL needs to know "there are N
columns in the result set", this is the way it is done.

Now, end user reporting tools -- thats different.  Reports/Discoverer -- they
all do pivots anyway you want.  but they take a flat sql query and pivot it
manually.
Rating: 4
Pivot view without group by March 30, 2003
Reviewer: Marc from Switzerland
Hi Tom

I have to create a pivot view for a table with more than 200 attributes in the
view and 250 million rows per month. The group by operation cost many resources
in temp tablespace (size 50 GB) and huge of cpu time for sort and aggregate.

Do you know a trick for build a pivot WITHOUT group by ? Is there a way to do it
with analytic functions ? Many thanks for your feedback.

Kind Regards
Marc
Followup:
In order to pivot, you have to group by.  A group by does not have to sort
necessarily.

For what you describe, 50gig of temp isn't that much -- increasing your
sort_area_size will help considerably -- ensure your temp extents match your
sort_area_size (at least -- they can be N*sort_area_size as well)

In order to pivot --you need to find all of the rows that match your key --
thats a group by.
Rating: 5
Just FYI July 09, 2003
Reviewer: Jeff from Atlanta, GA
If you search your site for 'row to columns' this thread doesn't come up at all,
I went up to about the 50th.

Thanks so much for all of your time and effort, I and countless others really do
appreciate it
Followup:
if you search for

"row to columns"

(double quotes) it is the first of two.

if you search for

'row to columns'

which is the same as

row to columns

you get everything with row & to & columns which is over 1,000 hits!
Rating: 4
July 29, 2003
Reviewer: AKMD from Singapore
good.
I have information like below in tables T1 and T2.

SQL> desc t1;
 Name                            Null?    Type
 ------------------------------- -------- ----
 A                                        NUMBER
 B                                        NUMBER
 C                                        NUMBER
SQL> select * from t1;
        A         B         C
--------- --------- ---------
       23        45        60
       33        55        70
       33        23        70
SQL> desc t2
 Name                            Null?    Type
 ------------------------------- -------- ----
 D                                        VARCHAR2(10)
 I                                        NUMBER
SQL> select * from t2;
D                  I
---------- ---------
TEST              23
TEST1             45
TEST2             60
TEST3             33
TEST4             55
TEST5             70

I need output as folows:
        A D               B D                  C D
--------- ------------------ ---------- --------- ----------
       23 TEST            45 TEST1             60 TEST2
       33 TEST3           23 TEST              70 TEST5
       33 TEST3           55 TEST4             70 TEST5

Nothing is fixed.

Thanks in advance.
Followup:
simple -- "join"

select t1.a, t2_1.d, t1.b, t2_2.d, t1.c, t2_3.d
  from t1, t2 t2_1, t2 t2_2, t2 t2_3
 where t1.a = t2_1.i
   and t1.b = t2_2.i
   and t1.c = t2_3.i
/
Rating: 4
July 30, 2003
Reviewer: A reader
Hi,

select  q.a, q.d, p.b, p.d, s.c, s.d from
(select distinct x.a a, y.d d from t1 x, t2 y
where y.i = x.a) q,
(select distinct x.b b, y.d d from t1 x, t2 y
where y.i = x.b) p,
(select distinct x.c c, y.d d from t1 x, t2 y
where y.i = x.c) s, t1
where q.a = t1.a and p.b = t1.b and s.c = t1.c
/

I used above query to get the result.
Can u explain me what is the diff???

Thank you.
Followup:
beside that yours will run infinitely slower?

why would you make it so hard?
Rating: 4
July 30, 2003
Reviewer: A reader
I hope inline views are faster in quires any advise?
Followup:
I hope the redskins do better this year?

inline views are just a construct.  they are no different then the query against
a stored view.  they are no different then a query that doesn't use an inline
view.

They can however be used to develop queries that

a) don't need a stored view
b) could not be answered without a stored view otherwise
Rating: 5
group by does not have to sort August 02, 2003
Reviewer: A reader
" group by does not have to sort
necessarily.
"

SQL> select deptno, sum(sal)
  2  from emp1
  3  group by deptno;

    DEPTNO   SUM(SAL)
---------- ----------
        10       8750
        20      10875
        30       9400

when does a group by sorts automatically , when does it not...

Please illsutrate.
Followup:
whenever it feels like it -- whenever it can -- whenever it makes sense.

say you "group by a, b" and there is a nice index on "b,a".....  might it use
the index and the data might appear to be sorted by b,a?

say you "group by a,b" and you've hash partitioned by a.  might it return
results from each partition one after the other -- so it appears sorted by a,b
AFTER hashing (so if you had 8 hash partitions, you might get 8 "apparently
sorted" result sets one after the other -- the entire thing isn't sorted)

say your nls_sort is french.  group by sort does a binary sort, data won't be
sorted according to your sort order

and so on -- there are many ways to group by that do not return sorted data.

There is only one way to get sorted data -- order by!
Rating: 5
Rows to Columns December 10, 2003
Reviewer: M S Reddy from USA
Tom,
  can this be done in one single sql statement without looping.

set serveroutput on size 20000
declare
procedure temp is
v_str varchar2(76):= '';
begin
 for v_rec in (select 'a' col from t1 where rownum < 77)
 loop
  v_str := v_str || v_rec.col;
 end loop;
 dbms_output.put_line(v_str) ;
end;

begin
 temp;
end;
/

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

that rownum < 77 is not fixed it can vary.

Thanks,
M S Reddy
Followup:
rpad( 'a', 77, 'a')

????
Rating: 3
Rows To Columns December 11, 2003
Reviewer: M S Reddy from USA
Tom ,
   That number 77 is not fixed .The column does not always have a value 'a' it
can be anything else.
consider this example.

              T1
-------------------------------
  c1          c2           c3
-------------------------------
  a           b            c
  d           e            f
  g           h            i
  j           k            l

the result should be

-------------------------------
  c1          c2           c3
-------------------------------
 adgj        behk         cfil

Can this be done in single sql statement?

Thanks,
M S Reddy.
Followup:
search for stragg on this site ( and don't oversimplify your examples -- give
real ones.  given the question -- the answer is in fact "rpad" !!!!  77 can be a
bind, 'a' can be a bind.  It just looked like you made a really hard way to get
N letters in a row)
Rating: 3
Dinamic pivot query December 23, 2003
Reviewer: Luda from USA
Hi Tom,
I have two problems in this query:
select item_code,inventory_id,
        sum(decode(monthly,'Jan',cnt,null)) jan,
        sum(decode(monthly,'Feb',cnt,null)) feb,
        sum(decode(monthly,'mart',cnt,null)) mart
from(
select i.item_code, i.inventory_id,
       decode(to_char(b.ship_date,'mon/yy'),'jan/03','Jan'
                                           ,'feb/03','Feb'
                                           ,'mar/03','mart') as monthly,
       sum(NVL(a.itm_shipped,0)) as cnt
from inventory i,order_items a, orders b
where i.inventory_id=a.inventory_id(+)
and i.account_id=b.account_id
and a.order_id=b.order_id
and i.account_id=20
and ship_date>='01-Jan-03'
and ship_date <='18-Dec-03'
and rownum < 500
group by i.item_code ,i.inventory_id ,
       decode(to_char(b.ship_date,'mon/yy'),'jan/03','Jan'
                                           ,'feb/03','Feb'
                                           ,'mar/03','mart')
    )
group by item_code, inventory_id;
1. It's tooooo slowly.
2. I do not now months user can pick up, for all year, a few months, or just
sysdate. I need something dynamic.
I use this query in Coldfusion.
Please help.
Followup:
well -- step 1.  lose the outer join.  it doesn't count in your case.

You have:

where i.inventory_id=a.inventory_id(+)
and i.account_id=b.account_id
and a.order_id=b.order_id

So, if we actually outer join to A, then A.ORDER_ID is NULL by definition and
therefore the join to B never finds anything (since NULL is never equal to
anything).

Hence, either

a) query is buggy and returns wrong answer because you did not continue outer
joining
b) outer join is not relevant.

I'll vote for B :)  outer join not relevant so lose it.

Now -- rownum < 500????? whats up with that??? ok, i know how to make this go
faster -- change 500 to 1 or 0, it'll run much faster and give you just as
reliable an answer! (you do realize that the same query run twice with the SAME
EXACT INPUTS could give you entirely different answers here???????  You get 500
rows and then group them.

So, i'm very much thinking "this query has too many things wrong with it to even
think about tuning -- i don't think it returns anything useful as written"
(hence to make it faster, don't run it.)
Rating: 3
December 23, 2003
Reviewer: A reader
I made it different way but it takes a lot of time.
first I pick up all id from inventory table, than in loop
I pass id to another query and find sum and month.
select id,col1,col2 ... from i
 loop
   select sum(item),to_char(b.ship_date,'mon/yy'),
     from a,b
     where a.id=query1.id
     from date1 to date2
     group by  to_char(b.ship_date,'mon/yy')
 end loop
So if second query doesn't return any record I show first select. Let's say, if
first query returns 1700 rows
I have to show in report 1700 rows with blank in sum field.
Problem only in time. Too much calls and user defently will lose patient.
Can I do it in one select and faster?
Followup:
I cannot tune that which does not compute or make sense.  I don't even know the
question here (since the query is wrong one way or the other)

No one can tune this.  the question being asked of the data isn't very clear at
all.


Recent

Archives

Advanced Search

FAQ by Category

Most Popular

Hot Articles

Your Questions

[ Listing ]

Subject

Article

Last Updated (East coast time)

Rollback Segments - Unable to extend

article only

Dec 24, 2003 09:57 am

getting my row from v$session

article only

Dec 24, 2003 09:59 am

UPDATES using FORALL and FOR..LOOP

article only

Dec 24, 2003 10:00 am

Sql to retrive a columns

article only

Dec 24, 2003 10:01 am

ref cursor and %rowcount

article only

Dec 24, 2003 10:03 am

SHARED_POOL_RESERVE_SIZE parameter

article only

Dec 24, 2003 10:13 am

anything between create procedure and create any procedure privilege?

article only

Dec 24, 2003 10:14 am

Package Sizes

article only

Dec 24, 2003 10:20 am

extract DDL

article only

Dec 24, 2003 10:23 am

pctfree and pctused with regards to LOBS

article only

Dec 24, 2003 10:25 am

Complex Queries and Materialized Views

article only

Dec 24, 2003 10:34 am

Logical Reads vs Physical Reads

article only

Dec 24, 2003 11:03 am

insert into Temporary tables

article only

Dec 24, 2003 01:26 pm

why so many recursive call?

article only

Dec 24, 2003 01:31 pm

return resultset as an array from function

article only

Dec 24, 2003 01:35 pm

regular table to partition table

article only

Dec 24, 2003 01:36 pm

Pfile or SPFile

article only

Dec 25, 2003 08:21 am

db cache advice

article only

Dec 25, 2003 08:25 am

BIND VARIABLES&HOST VARIABLES

article only

Dec 25, 2003 10:10 am

Querying the Object Views

article only

Dec 25, 2003 12:02 pm

Snapshot with indexes/constraints

article only

Dec 25, 2003 03:38 pm

Performance Investigation Approach when using STATSPACK

article only

Dec 25, 2003 07:37 pm

Char Vs Varchar

article only

Dec 26, 2003 10:07 am

How to get current schema

article only

Dec 26, 2003 10:10 am

Autmatic Undo Management

article only

Dec 26, 2003 10:31 am

Index Organized Tables.

article only

Dec 26, 2003 10:58 am

Database users & Partitions

article only

Dec 26, 2003 01:21 pm

Calculating hours worked between two dates

article only

Dec 26, 2003 01:54 pm

paging through results on the web.

article only

Dec 26, 2003 02:07 pm

Multiple Processes processing rows

article only

Dec 26, 2003 07:13 pm

questions about rollbacks/snapshot too old

article only

Dec 27, 2003 09:42 am

Best way to remove a partitioned structure that includes CLOBS?

article only

Dec 27, 2003 11:24 am

How to stop truncating the table through trigger

article only

Dec 27, 2003 11:25 am

function based indexes

article only

Dec 27, 2003 01:27 pm

reading files in a directory -- how to get a list of available files.

article only

Dec 27, 2003 02:47 pm

Full outer join in 8i

article only

Dec 27, 2003 05:39 pm

join example, new vs old syntax

article only

Dec 27, 2003 07:12 pm

Generating Dates between two date ranges

article only

Dec 27, 2003 07:16 pm

DBMS_STATS

article only

Dec 28, 2003 10:39 am

Question on Logminer

article only

Dec 28, 2003 10:41 am

Calling external procedure to concatenate a string

article only

Dec 28, 2003 10:48 am

firewall rules for net8 connection

article only

Dec 28, 2003 11:04 am

Interview questions

article only

Dec 28, 2003 11:13 am

Network Configuration Parameters

article only

Dec 28, 2003 11:19 am

How do i prevent end users from connecting to the database other than my application?

article only

Dec 28, 2003 11:27 am

Partition tables

article only

Dec 28, 2003 05:33 pm

cursor_sharing similar

article only

Dec 29, 2003 09:56 am

tkprof output

article only

Dec 29, 2003 10:02 am

Locking Mechanism in Oracle

article only

Dec 29, 2003 10:19 am

Comparison between Oracle and Others

article only

Dec 29, 2003 10:25 am

Import takes more time

article only

Dec 29, 2003 10:29 am

Using LOGON tigger on Schema

article only

Dec 29, 2003 10:38 am

Two dimensional array(name-value pair) feature in 8.1.7.3.

article only

Dec 29, 2003 10:55 am

synonym for function inside a package

article only

Dec 29, 2003 01:06 pm

Real Application Clusters

article only

Dec 29, 2003 01:33 pm

Difference between cursor and a ref cursor

article only

Dec 29, 2003 02:54 pm

Bind Variable values available to Optimizer in Oracle9i

article only

Dec 29, 2003 05:55 pm

How Can i get the peak concurrent session

article only

Dec 29, 2003 05:58 pm

Few Questions !!!

article only

Dec 29, 2003 06:13 pm

rman question

article only

Dec 30, 2003 09:44 am

Sending HTML using UTL_SMTP

article only

Dec 30, 2003 09:46 am

Confusion in the interpretation of V$LIBRARYCACHE columns

article only

Dec 30, 2003 10:02 am

Redo Log Buffer Size - why is it 512k?

article only

Dec 30, 2003 10:10 am

cummualtive sum

article only

Dec 30, 2003 10:12 am

Oracle8i on Unix or OPS 8i on Win2000

article only

Dec 30, 2003 10:21 am

CURSOR

article only

Dec 30, 2003 10:21 am

Question regarding restrict_references.

article only

Dec 30, 2003 11:41 am

Restricting number of entries.

article only

Dec 30, 2003 02:13 pm

Limit in the number of indexes

article only

Dec 30, 2003 04:53 pm

DIFFERENCES BETWEEN PROCESSES, SESSIONS AND CONNECTIONS

article only

Dec 30, 2003 05:42 pm

what kind of analyze is best

article only

Dec 30, 2003 06:20 pm

unix time to oracle time

article only

Dec 30, 2003 06:26 pm

Listener Question

article only

Dec 30, 2003 07:17 pm

row(s) 1 - 73 of 73

December 24, 2003 Rama Subramanian -- Thanks for the question regarding "Rollback Segments - Unable to extend", version 8.1.6
You Asked
Hi Tom.

We use Oracle 8.1.6 Standard Edition on Sco UnixWare 7.1.1.  We have an OLTP
application running.

We have a month-end process, that inserts quite a lot of records into the
database, without commits inbetween.  The number of records are around 20000.
This process was run, at the end of the day, after having run all the
transactions for the day.  So, the rollback segments were put to good use.  We
have only public rollback segments, in a tablespace of 200MB size.  While this
process of inserts was going on, we got the error message ORA-1650.  I thought I
need to add additional datafile to the the RBS tablespace.  But, before
attempting that, I shutdown the database, the server and restarted the system.
Then before adding the datafile, I tried to run this process again, and this
time it went through. What difference has the system restart made?  I thought
the free extents in the tablespace might be fragmented, and the Oracle could not
find any appropriate extent in the former case.

Am I right?

Thanks & Regards,
Rama Subramanian G
and we said...
You have more then 1 rollback segment.

They are all different sizes (guessing on that).

Your rollback segment tablespace is in fact fully allocated (all 200m which is
allocated to the N rollback segments you have)

The first time you ran it, your transaction was assigned to "rollback segment 1"
and this rollback segment wasn't big enough.  If you had just re-run it then
(without shutting down) you would have been assigned to "rollback segment 2"
which was big enough (your shutdown had the same effect, you just used a
DIFFERENT rollback segment the second time around).

You need to (my opinion/reocmmendation here) use equi-sized rollback segments.
You need to know the size of your transactions in order to size them and make
them all be big enough to

a) not wrap during the time it takes to run your longest running query
b) hold the undo for your largest transaction TIMES (the number of concurrent
transactions DIVIDED BY the number of rollback segments).  Thats a good starting
point.
Review & Followup

Rating: 3
Thanks Tom February 22, 2002
Reviewer: A reader
Infact, the rollback segments that we have are all of the same size.  I thought
only the tablespace in which they reside, was too small for this problem to
happen.  Am I right?
Followup:
Well, make sure they are

select segment_name, blocks
  from dba_segments
 where segment_type = 'ROLLBACK';

will confirm this.  If they are then what happened is most likely this.

Assuming all rollback segments are 100 blocks in size.

At time t1, some transaction started in "rollback segment 1".  It used "block 1"
of that rbs.  It did not commit.

Over time, other sessions used "rollback segment 1", over time blocks 1, 2, ...
90 filled up in this rbs.

Now, your transaction began and was assigned to "rollback segment 1".
You use blocks 91, 92, ... 100.  The rollback segment is "full".  We try to wrap
around but cannot (there is a transaction that is not committed with data in
block 1 -- we cannot reuse that block).  So we try to extend -- but the
tablespace is full -- we cannot extend.  Hence, we fail.

If that transaction that started at time t1 had committed -- we would not have
failed -- when we used block 100 -- we would have been able to wrap around and
reuse block 1, 2, 3, ... and so on.
Rating: 3
Thanks Tom February 22, 2002
Reviewer: G Rama Subramanian from Bangalore, India
Infact, the rollback segments that we have are all of the same size.  I thought
only the tablespace in which they reside, was too small for this problem to
happen.  Am I right?
Rating: 3
April 17, 2002
Reviewer: A reader from india
I see this message in my Alert log file say after a day from the date of error.

ORA-1650: unable to extend rollback segment R03 by 1920 in tablespace RBS01

How will I know which application had failed?

What actions I should take.

Thanks
Followup:
You do not know what application failed.

If this was a batch process, I would hope your developers would be telling YOU
what failed.  (if not, you have a batch process that is failing and no
diagnostics to tell you this is so)

If this was an interactive process, you would hope your end users would be
telling you what failed.  If not, it must not have bothered them.

What action to take?  If you feel your RBS is sized appropriately, absolutely no
action need be taken.  It means someone left a transaction open longer then they
should have (an abberation) or someone did a really big transaction (another
abberation).  In either case, unless this happens over and over and over, there
is nothing you need to do.
Rating: 3
Sagi April 17, 2002
Reviewer: Sagi from India
I have 5 PUBLIC RBS from RBS[1] to RBS[5] in Tablespace RBS which was of 250MB
with AUTOEXTEND on. These RBS were created with:
   INITIAL - 5M
   NEXT    - 5M
   Min Ext - 2
   Max Ext - 249

I have executed the below two Queries:

Query-1
=======
SELECT a.tablespace_name, ROUND(a.totsize/1024/1024,0) "Tot Size (MB)",
ROUND(NVL(b.used,0)/1024/1024,0) "Used (MB)",
100 - ROUND(((a.totsize - NVL(b.used,0)) / a.totsize) * 100,0) "% Used",
ROUND(((a.totsize - NVL(b.used,0)) / a.totsize) * 100,0) "% Free"
FROM
(SELECT tablespace_name, SUM(bytes) totsize FROM dba_data_files GROUP BY
tablespace_name) a,
(SELECT tablespace_name, SUM(bytes) used FROM dba_segments
GROUP BY tablespace_name) b
WHERE a.tablespace_name=b.tablespace_name (+)
ORDER BY 1

OutPut-1:
=========

TABLESPACE_NAME  Tot Size(MB)  Used(MB)   % Used   % Free
===============  ============  =======    ======   ======
INDX                200          0          0        100
RBS                 500        395         79         21
SYSTEM              264        203         77         23
TEMP                100          0          0        100
TOOLS                12          0          0        100
USERS               460        450         98          2

Query-2:
========
select segment_name, blocks
  from dba_segments
 where segment_type = 'ROLLBACK';

Output-2:
=========
SEGMENT_NAME                      BLOCKS
============                      ======
SYSTEM                              50
RBS1                             17920
RBS2                              1920
RBS3                              4480
RBS4                              2560
RBS5                              7040

Now My Questions are:
=====================
1. Why does the Size of Used shows 395mb for Tablespace RBS. As this is being
used by Rollback Segment. At the time when I executed the above queries there
was no transaction being executed on the database, i mean all the work was
either COMMITED or ROLLBACKED. Then why does't the size come down to Zero (0).

2. All my rollback segments are created with equi-size then why the BLOCKS does
not show same size when i executed the Query-2.

Would be helpful if you could explain.
Followup:
select owner, segment_name, sum(bytes/1024/1024) mb
  from dba_segments
 where tablespace_name = 'RBS'

will tell you whats up with the storage use in RBS tablespace.

without a blocksize, it is hard to say...

the rollback segments won't shrink unless you tell them to (via optimal) and
even then only in certain circumstances.

each of your rbs's have a different number of extents, that is why.
Rating: 4
Transaction size? April 17, 2002
Reviewer: Bob Maggio from Troy NY USA
"You need to know the size of your transactions in order to size them and make
    them all be big enough to"

Not sure I understand that. How do we know the size of a transaction? Is it
simply the size (k) of the rows * (n) number of rows? Or is there another
factor?
Followup:
should have been:

You need to know the size of your transactions in order to size them and make
them all be big enough to:
                         ^^
a) not wrap during the time it takes to run your longest running query
b) hold the undo for your largest transaction TIMES (the number of concurrent
transactions DIVIDED BY the number of rollback segments).  Thats a good starting

point.

you do that by watching how fast they wrap (v$rollstat) and how long your
longest query takes to run.

in 9i, you only need to know b) and use an undo tablespace --- it is much easier
Rating: 3
NOLOGGING/UNRECOVERABLE? April 18, 2002
Reviewer: Mark J. Bobak from Ann Arbor, MI
If the problem really is as outlined in the initial post
above, (insert of 20,000 rows without incremental commit),
then I think I'd recommend investigating NOLOGGING for the
data load.  Use INSERT /*+ APPEND */.  Granted, this is
only really valid if the load is purely a bulk insert of
data, no update or deletes, but if that's the case, I think
NOLOGGING is the way to go.

Just a thought,

-Mark
Followup:
won't affect much at all.  rollback will be generated for the indexes.

Their problem is with rollbacks.  An insert of a row into a simple table
generates very very very little UNDO aka rollback (a delete ROWID message is put
into rollback).

An insert into an INDEXED table generates gobs of UNDO.  The index structure is
modified heavily and the UNDO for these structural modifications is large.

I had to assume they were not blowing out rbs on the insert into a NON indexed
table, a 20k insert just wouldn't generate that much.  Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t as select * from all_objects
where 1=0;
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select * from all_objects where
rownum <= 20000;
20000 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select used_ublk from v$transaction;

 USED_UBLK
----------
        10

10 measly little undo blocks.  Insert /*+ append */ does save some but 10 is
very tiny (8k blocks -- so that was 80k of undo generated.  even if their rows
were really huge -- it wouldn't be any more undo as the "rowid" size doesn't
change)

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> truncate table t;
Table truncated.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ APPEND */ into t select * from
all_objects where rownum <= 20000;

20000 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select used_ublk from v$transaction;

 USED_UBLK
----------
         1

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> truncate table t;
Table truncated.

Now, lets add some indexes:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_owner_idx on t(owner);

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_object_name_idx on
t(object_name);

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_object_id_idx on t(object_id);

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select * from all_objects where
rownum <= 20000;

20000 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select used_ublk from v$transaction;

 USED_UBLK
----------
       303

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> truncate table t;

Table truncated.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ APPEND */ into t select * from
all_objects where rownum <= 20000;

20000 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select used_ublk from v$transaction;

 USED_UBLK
----------
       171

It saves some -- but they still have the issue with irregularly sized RBS's
Rating: 3
April 18, 2002
Reviewer: Sagi
Thanx for the reply. It was useful.

My DB_Block_Size is 8K. All the rollback segments are of PUBLIC.

My question is how do we decide what should be the OPTIMAL size.

Also would be of great help, if you can explain the V$ROLLSTAT with example i.e.
sample output.

Regards,
Sagi
Followup:
did you run the query?  i don't see the output.

I don't use optimal.  I don't like optimal.  I decide optimal shouldn't be used.

(in 9i, i'm using undo tablespaces so the following no longer applies)

I use equi-sized rollback segments.
I put each into its own tablespace.
I start them all at the same size.
I let the underlying datafiles autoextent upto some max.
IF i want to shrink them for some reason I
  - offline it
  - drop it
  - shrink the datafile
  - recreate it.

I do that manually (well, I have a script that does it for me)

Just query v$rollstat.   Wait and hour.  Query it again, compute the difference
in wraps. That's how often you wrapped.
Rating: 5
Mark smacks his forehead! April 18, 2002
Reviewer: Mark J. Bobak from Ann Arbor, MI
You're right of course.  I didn't really let those numbers
sink in before I recommended NOLOGGING.

NOLOGGING would be far more appropriate if the volume of the
data load was much (orders of magnitude) greater, and if
you were able to drop indexes before load and re-build them
(also with NOLOGGING) afterwards.

-Mark
Rating: 4
a little bit of confusion... April 18, 2002
Reviewer: Andrew Lapides from Calgary, Canada
Tom,

You (my keyboard is working right :)) wrote:
<quote>
An insert of a row into a simple table generates very very very little UNDO aka
rollback (a delete ROWID message is put into rollback).
</quote>

And here comes my question. I am under impression that rollbacks hold the blocks
of data itself, whereas redo logs hold the statements, that were executed. This
way by looking into rollback we can dig out the old data itself (undo the
transaction, i.e. roll back), whereas by executing statements from the redo we
can roll forward (rexecute the statement).

Of course when we insert there are no old data that we overwrite, so there is
nothing to put into rollback at all (perhaps maybe SCN for the blocks affexted
by this insert?). But as your example shows the rollback is still growing (at a
very low pace). But is it a "delete new record" statement that is being saved in
rollback? Aren't rollbacks are exclusvely for data blocks?

Or this is too simplistic (I mean rollbacks - for data, redo - for statements)?
In Lewis' book in one of the first chapters he goes through an example of undo,
and I remember there is an extensive rollback/redo conversations are taking
place. It's a bit muddy. Could you please elaborate on this if you have time.
Followup:
That is an incorrect impression.

UNDO (rollback) stores just enough data to undo the change.  To undo an insert,
we store "DELETE <rowid>".  To Undo a DELETE we store "insert values ( ..... )"
-- we don't store the SQL (thats conceptual), but rather the operation to
perform and the needed data.

REDO stores just enough data to REDO to the change.  To redo an insert, we store
"insert ....".  To redo a delete we store "delete ..."

We do not store the SQL to do this.

we do not execute statements to roll forward, we read the logs and using the
data contained therein, we "fix" or redo the transaction.

We do not record full blocks in either of undo or redo.  We store data -- not
blocks of before and/or after images.

JPL's book (practical oracle8i) is GREAT.  If you have my book -- i have a
similar long winded discussion  you might like as well.
Rating: 3
Does Rollback segments affect the performance April 19, 2002
Reviewer: hrishab from India
Tom,

I have the procedure which processes around 4 million records. I'm not using any
rollback segements. Initially procedure was executing fast and as I am
committing per insert for tracking of records. It was initially inserting 200
records per second, gradually the performance started decreasing and insert
count came to 1 per second. Please can you explain what is the reason for the
degradation of the performance. Can rollback segment increase the performance
and solve my problem.

Thanks
Followup:
Umm -- you have some serious misconceptions.  They are:

1) that you were not using rbs.  You were most certainly using RBS.  You have no
choice in the matter.

2) That committing per insert is a good idea.  It is not, it is a TERRIBLE idea.

Ideas for you:

a) if you can, get my book.  I explain the how and why of "committing frequently
will only make you run slower and consume more resources".

b) read up on the dbms_application_info package, it is this package that will
allow you to "show progress" either in v$session or even better
v$session_longops.  Again, if you have my book I have an extensive writeup on
this capability.

The reasons you are going slower:

a) you are committing every record, thats bad.  You end up waiting for log file
syncs

b) you are probably also flooding your log files, and your alert log has
messages to the effect of "cannot allocate new log".  You probably need more or
bigger online redo log files.
Rating: 3
April 19, 2002
Reviewer: Sagi from India
Hi Tom !

DB_BLOCK_SIZE - 8K
O/S          - Windows 2000
Oracle        - 8.1.7.3

Before doing anything I exectued the below query:

Query-1
=======
SELECT a.tablespace_name, ROUND(a.totsize/1024/1024,0) "Tot Size (MB)",
ROUND(NVL(b.used,0)/1024/1024,0) "Used (MB)",
100 - ROUND(((a.totsize - NVL(b.used,0)) / a.totsize) * 100,0) "% Used",
ROUND(((a.totsize - NVL(b.used,0)) / a.totsize) * 100,0) "% Free"
FROM
(SELECT tablespace_name, SUM(bytes) totsize FROM dba_data_files GROUP BY
tablespace_name) a,
(SELECT tablespace_name, SUM(bytes) used FROM dba_segments
GROUP BY tablespace_name) b
WHERE a.tablespace_name=b.tablespace_name (+)
ORDER BY 1

OutPut-1:
=========
TABLESPACE_NAME  Tot Size(MB)  Used(MB)   % Used   % Free
===============  ============  =======    ======   ======
INDX                200          0          0        100
RBS                 500        395         79         21
SYSTEM              264        203         77         23
TEMP                100          0          0        100
TOOLS                12          0          0        100
USERS               460        450         98          2

My Rollback Segment Information is:
===================================
I have 5 PUBLIC RBS from RBS[1] to RBS[5] in Tablespace RBS which was of 250MB
with AUTOEXTEND on. These RBS were created with:
   INITIAL - 5M
   NEXT    - 5M
   Min Ext - 2
   Max Ext - 249

Query-2:
========
select segment_name, blocks
  from dba_segments
 where segment_type = 'ROLLBACK';

Output-2:
=========
SEGMENT_NAME                      BLOCKS
============                      ======
SYSTEM                              50
RBS1                             17920
RBS2                              1920
RBS3                              4480
RBS4                              2560
RBS5                              7040

Questions:
==========
1. After executing the Query-2, I used ALTER ROLLBACK ...... TO SHRINK all the
five PUBLIC Rollback Segments to 5MB. Then When executed the Query-1. The output
is:

TABLESPACE_NAME  Tot Size(MB)  Used(MB)   % Used   % Free
===============  ============  =======    ======   ======
INDX                200          0          0        100
RBS                 500         50         10         90
SYSTEM              264        203         77         23
TEMP                100          0          0        100
TOOLS                12          0          0        100
USERS               460        450         98          2

Now I tried to resize the Datafile of RBS to 200M. But encounter the below
error:

SQL> ALTER DATABASE DATAFILE 'C:\ORACLE\ORADATA\ORCL\RBS.DBF' RESIZE 200M

ALTER DATABASE  DATAFILE 'C:\ORACLE\ORADATA\ORCL\RBS.DBF' RESIZE 200M
*
ERROR at line 1:
ORA-03297: file contains used data beyond requested RESIZE value

Hence I droped all the five PUBLIC ROLLBACK Segments and then recreated them
once again in Tablespace RBS with STORAGE INITIAL 5M NEXT 5M MINEXTENT 2
MAXEXTENTS 249)

and then tried to resize the datafile of RBS to 200M. But still getting the same
error. Why is this so. The output shows that the total usage is 50MB then why
can it resize.

2. What do you mean by equi-rollback segments. Say i have created 5 PUBLIC
Rollback Segments with
  STORAGE (INITIAL 5M NEXT 5M MINEXTENTS 2 MAXEXTENTS 249)

   Is this not equ-size rollbace segments?

3. When I created the rollback segments i said STORAGE (INITIAL 5M NEXT 5M
MINEXTENTS 2 MAXEXTENTS 249). Now if i execute the Query:

SELECT segment_name, owner, tablespace_name "TBS",
initial_extent/1024 "Initial (KB)",
next_extent/1024 "Next (KB)", min_extents "Min", max_extents "Max", status
FROM
dba_rollback_segs
order by 1 ;

Output:
=======
SEG_NAME  OWNER  TBS  Initial(KB)  Next(KB)  Min    Max
========  =====  ===  ===========  =======   ===    ===
RBS1     PUBLIC  RBS   10240       5120       1    32765 RBS2     PUBLIC  RBS
10240       5120       1    32765 RBS3     PUBLIC  RBS   10240       5120
1    32765 RBS4     PUBLIC  RBS   10240       5120       1    32765 RBS5
PUBLIC  RBS   10240       5120       1    32765 SYSTEM   SYS    SYSTEM    56
    56       2      505

I find the INITIAL (KB) ok because we said MINEXTENTS as 2. So its correct but
the MINEXTENTS shows 1. Why? Is this because already 2 extents have been created
and alloted and now onwards it would be 1 extent...........

Thank you in Advance.

Regards.
Followup:
you know what -- you just refuse to run my query don't you.

Sorry, no info for you until you do.

select owner, segment_name, sum(bytes/1024/1024) mb
  from dba_segments
 where tablespace_name = 'RBS'

will tell you whats up with the storage use in RBS tablespace.

Asked for that 3 times.... Perhaps -- just maybe -- if you run that, you'll find
some other segment, something OTHER a rbs in there.

Also, if you are using DMT's (instead of the more efficient LMT's), space is
allocated where ever we feel like it (in a locally managed tablespace, we
allocate space from the front of the file, in a dictionary managed one,
where-ever).

You should have resized the file when the file was suppossedly empty.  Not after
putting stuff in there.

search this site for maxshrink.  It'll tell you exactly how small you can shrink
a file.  guess you didn't like the idea of a tablespace/rbs.

2) i mean they are all the same size.  I think your extents are too large and
you have too few of them.  I myself start with 1meg extents and minextents at
25.  So I have a bunch of 25meg rbs's.  If my Rbs's grow (just one of them) and
I always see that growth -- THAT IS THE SIZE all of my rbs's should be.  I want
to get to to a point where my rbs's never grow anymore.  If that is 50m / rbs,
so be it.  If that is 500m/rbs so be it.  If that is 25m (and so on).  You'll
spend the rest of your life "adjusting" these in a false attempt to "save
space".  You need that space, they always grow, just let them grow!

3) i cannot read the results there.  You really need to format it a little
better.

But in any case, looks like you might be using LMTS in which case most of the
storage parameters such as initial/next/min/max etc are not subject to the usual
interpretation.  See how the initial is 2*5 -- its accounted for.
Rating: 4
Does Committing releases the memory April 19, 2002
Reviewer: sachin from India
Tom,

Suppose I have millions of records and I am carrying out a big transaction. If
in my procedure I commit after every 500 records, Does the rollback segment
refreshes the memory occupied by those 500 records after every commit.
Followup:
rollback is not "memory".

rollback does not "refresh".

rollback is used as a circular buffer on disk.  we will reuse space sometime
after you commit.

committing before the transaction is over (actually over) will

o generate more redo
o cause more undo to be generated
o run slower
o introduce bugs.

feel free to do it tho, everyone seems to.
Rating: 4
i'm wrong ? November 25, 2002
Reviewer: Adrian from Peru
Hi tom,
I have a RBS tablespace of 750Mb and 10 30Mb rbs of each one, since each
rollback segments have autoextend on... can i resize this to a shorter size and
release space without loss performance or funtionality?
Why is the reason for BIGs RBs?
Followup:
I cannot answer -- do you have some code that does "set transaction use rollback
segment BIG_RBS".  If so, probably not.  I like to have same sized rbs's myself
-- I wouldn't shrink, so much as grow the others so that any of the RBS's is big
enough to do the work.
Rating: 5
Further more clarifications December 24, 2003
Reviewer: Sanjaya Balasuriya from Colombo, Sri Lanka
Tom,

In the follow up April 18, 2002,
you say "I put each into its own tablespace.".

Does this mean to create each rollback segment in a different tablespace ?

Thanks in advance.

-San
Followup:
yup - in 8i and before.

after that, a single undo tablespace
December 24, 2003 Arum Kusuma -- Thanks for the question regarding "getting my row from v$session", version
You Asked
could you tell me how put the information about current
username,session id,terminal on variable

like select user into v_user from dual

than i can manipulate in procedure or function or trigger.
and we said...
The query is:

   select <whatever you want> from v$session where
audsid=userenv('sessionid');

that gets the data for the currently logged in user from the
v$session table.  In order to create a trigger or procedure that
references v$session, you need to:

SQL> grant select on v_$session to <owner of the
procedure|trigger>

when connected as SYS.  v_$session is a view and can be granted
on.  v$session is a synonym for that view (and hence cannot be
granted on, thats why you grant on v_$session, not v$session).
If you want to know why you need this grant, see
http://osi.oracle.com/~tkyte/Misc/RolesAndProcedures.html
Some people create a view:

create view my_session_info
as
select * from V$session where audsid = userenv('sessionid');

grant select onmy_session_info to public;

as SYS so everyone can see their session.  You would be able to
use that grant and view in your procedure|trigger as well.
Review & Followup

Rating: 4
But how to update this view March 11, 2002
Reviewer: Edgars from Riga, Latvia
Hello Tom!

Is there a possibility to update fields in v$session table. For example suppose
I want to set terminal field to SYS_CONTEXT( 'USERENV', 'IP_ADDRESS' ) because
my client app set's this field to 'unknown', this possibility could be very
useful.
Followup:
You can set the following fields:

CLIENT_INFO
MODULE
ACTION

using the dbms_application_info package.  You cannot modify any of the other
fields.
Rating: 3
may not work properly with jobs March 11, 2002
Reviewer: Alex Rakhalski from Moscow
I think it may not work properly with jobs, because within job
USERENV('SESSIONID') returns 0 (as for all other system processes). If you have
a trigger with such logic, table probably will be non-updateable from any job. I
had a problem like this on Oracle 8.0.5.
Followup:
you can use

select * from v$session where sid = ( select sid from v$mystat where rownum=1 )

as well -- that'll work from any environment.
Rating: 1
Getting sessionid within dbms_jobs March 26, 2002
Reviewer: Marc from Switzerland
The following query

select audsid
from v$session
where sid = (select sid from v$mystat where rownum=1);

shows the same value as select userenv('sessionid') from dual. But when I submit
this query within dbms_job, the audsid is 0. For logging reasons I need the
session id. My database version is 8.1.6.3.0.

Could you tell my another way to select the session id within dbms_jobs. Many
thanks.
Followup:
There is no "audsid" for the job.  You really should be using the SID,SERIAL#
(thats the UNIQUE id for the session.  I have no idea what you might be using
the audsid for for logging)
Rating: 2
select userenv('sessionid') under control of dbms_job is zero March 26, 2002
Reviewer: Marc from Switzerland
Hi Tom

In my eventlog I write for example the current oracle_user, the os_user, the
sysdate and the session_id. This goes fine for procedures started interactive
within SQL*Plus. When I submit the same stored procedure within dbms_job, the
session_id (= audsid in v$session) is always 0. And your given workaround
'select audsid from v$session where sid = ( select sid from v$mystat where
rownum=1 );' is also 0 unter control of dbms_job. Thanks for your feedback.
Rating: 5
to previous reviewer March 27, 2002
Reviewer: Alexander Rakhalski from Moscow, Russia
Hi, Tom!
1. I has forgotten to rank your last answer on my question. It's really great! I
hasn't thought that v$mystat can be useful in application development...
2. I want to notice previous reviewer that question was "how get my ROW from
v$session", not "how get my AUDSID from v$session". There are many other useful
fields in v$session - SID, SERIAL# and so on. And they <>0 within job. You can
consider using them.
Followup:
that was my point too -- the audsid, i don't know why you would need that one.
sid, serial#, transaction id (from another table) maybe....
Rating: 4
audsid always distinct in v$session ? November 27, 2002
Reviewer: Robert from PA
>>   SELECT osuser INTO l_osuser FROM v$session
>>    WHERE audsid = USERENV('SESSIONID')

so I will never get hit with ORA-01422 for the above code ?
thanks, Happy Thankgiving !
Followup:
should be OK

select * from v$session where sid = ( select sid from v$mystat where rownum=1)

is my "new preferred" method.  I've found userenv('sessionid') and audsid to not
be set in certain event triggers.  v$mystat never fails.
Rating: 4
sys_context against v$session November 28, 2002
Reviewer: Darko from South Africa
Is using SYS_CONTEXT faster and more scalable solution then selecting from
v$session (for info which is available in both cases).
Followup:
I would use sys_context if it met my needs for then I have no need to grant
select on v_$session (more available to more users)
Rating: 5
Uniqally identifying session August 29, 2003
Reviewer: atul from India
Hi,

My requirement is,I want to identify my SESSION uniqually out of many session.

My problem is,I can identify easily if logged in from unix(character mode),but
how to get it if logged in from web?

Thanks.
Atul
Followup:
why does "character mode" differ from "the web" for you?

Oracle cannot tell the difference.
Rating: 5
ora-01422 September 02, 2003
Reviewer: A reader
Hi

I have following code

create or replace procedure test
as
l_cnt number;
begin
select count(*)
  into l_cnt
  from v$session where audsid=USERENV('sessionid');
insert into emp(empno) values(l_cnt);
end;
/

when I submit to a job and run it I see this

     EMPNO ENAME      JOB              MGR HIREDATE                 SAL
COMM     DEPTNO
---------- ---------- --------- ---------- ----------------- ----------
---------- ----------
      7499 ALLEN      SALESMAN        7698 19810220 00:00:00       1600
300         30
      7521 WARD       SALESMAN        7698 19810222 00:00:00       1250
500         30
      7654 MARTIN     SALESMAN        7698 19810928 00:00:00       1250
1400         30
      7698 BLAKE      MANAGER         7839 19810501 00:00:00       2850
          30
      7782 CLARK      MANAGER         7839 19810609 00:00:00       2450
          10
      7839 KING       PRESIDENT            19811117 00:00:00       5000
          10
      7844 TURNER     SALESMAN        7698 19810908 00:00:00       1500
0         30
      7369 SMITH      CLERK           7902 19801217 00:00:00        800
          20
      7566 JONES      MANAGER         7839 19810402 00:00:00       2975
          20
      7788 SCOTT      ANALYST         7566 19821209 00:00:00       3000
          20
      7876 ADAMS      CLERK           7788 19830112 00:00:00       1100
          20
      7900 JAMES      CLERK           7698 19811203 00:00:00        950
          30
      7902 FORD       ANALYST         7566 19811203 00:00:00       3000
          20
      7934 MILLER     CLERK           7782 19820123 00:00:00       1300
          10
        13

How come there are 13 sessions...? In fact there is a note on Metalink that is
can happen when using v$session and dbms_job. How so?

Also I can see sometime my job stops running

select LAST_DATE, next_date, broken, failures from user_jobs;

LAST_DATE         NEXT_DATE         B   FAILURES
----------------- ----------------- - ----------
20030902 04:44:50 20030903 04:44:00 N          0

!date
Tue Sep  2 04:50:34 CEST 2003

How can we fix this? I dont see why this can happen :(
Followup:
use

select count(*) from v$session where sid = ( select sid from v$mystat where
rownum = 1)

you'll find your audsid is 0 or something like that in a job.  if you want your
v$session row -- I'd use that query in all cases...  jobs or not.

as for the "my job stops running"

a) make sure job_queue_processes > 0
b) make sure job_queue_interval is set reasonable (eg, if set to 10 minutes,
your job might not run until 4:54:50
c) make sure all of the job queues are not currently running jobs (see a again)
d) make sure your job isn't actually RUNNING already (dba_jobs_running)
e) make sure the clock on your system that you did the !date from is the same
clock as on the server!!  your time might not be anywhere close to their time
Rating: 4
Audsid December 24, 2003
Reviewer: OracleO from India
Hi Tom,

You have used the column audsid from v$session. What actual is the role of this
column. The reference manual just says that its auditing Session ID. Is it
alloted by oracle for auditing the session ?

Thanks,
OracleO
Followup:
I've started to prefer this view:

select * from v$session where sid = ( select sid from v$mystat where rownum=1 );

that works in all environments.  the audsid column is populated via a sequence
and for normal sessions is the same as "userenv('sessionid')" -- but for some
background sessions -- it is not set (it comes back as "0") making the view not
work.

So, I'd just ignore "audsid" for now and use SID.
December 24, 2003 Raghu -- Thanks for the question regarding "UPDATES using FORALL and FOR..LOOP", version 8.1.7
You Asked
Hi Tom,
I was trying to see if using FORALL would be faster (than FOR..LOOP) for updates
and did not find
any significant difference. Could you please tell me
1) If the example I am using below is the correct way to test such a scenario?
2) How can I prove to myself that FORALL is indeed faster?

This test case was done against a database that had no users other than me.

Thanks for your time.
Raghu

SQL> drop TABLE part_temp_tab;

Table dropped.

SQL> CREATE TABLE part_temp_tab(
  2  part_no       VARCHAR2(35),
  3  contract      VARCHAR2(5),
  4  latest_price  NUMBER);

Table created.

SQL> INSERT INTO part_temp_tab SELECT object_name, 'ABC', rownum FROM
all_objects;

22722 rows created.

SQL> INSERT INTO part_temp_tab SELECT object_name||'_A', 'ABC', rownum FROM
all_objects;

22722 rows created.

SQL> INSERT INTO part_temp_tab SELECT object_name||'_B', 'ABC', rownum FROM
all_objects;

22722 rows created.

SQL> commit;

Commit complete.

SQL> SET SERVEROUT ON SIZE 1000000;
SQL> DECLARE
  2     CURSOR GetRec IS
  3        SELECT *
  4        FROM   part_temp_tab;
  5     time_1_  NUMBER;
  6  BEGIN
  7
  8     time_1_  := DBMS_UTILITY.GET_TIME;
  9     FOR rec_ in GetRec LOOP
 10        UPDATE part_temp_tab
 11        SET    latest_price = latest_price * 2
 12        WHERE  part_no = rec_.part_no;
 13     END LOOP;
 14     COMMIT;
 15     DBMS_OUTPUT.PUT_LINE('Time (in Seconds) taken to UPDATE 68,169 records
using FOR..LOOP: '||
(DBMS_UTILITY.GET_TIME - time_1_)/100);
 16  END;
 17  /
Time (in Seconds) taken to UPDATE 68,169 records using FOR..LOOP: 1750.85

PL/SQL procedure successfully completed.

SQL> SET SERVEROUT ON SIZE 1000000;
SQL> DECLARE
  2     TYPE Parts IS TABLE OF part_temp_tab.part_no%TYPE;
  3     TYPE Contracts IS TABLE OF part_temp_tab.contract%TYPE;
  4     TYPE Lprice IS TABLE OF part_temp_tab.latest_price%TYPE;
  5
  6     Part      Parts;
  7     Contract  Contracts;
  8     Price     Lprice;
  9
 10     time_1_   NUMBER;
 11  BEGIN
 12
 13     time_1_  := DBMS_UTILITY.GET_TIME;
 14     SELECT part_no,
 15            contract,
 16            latest_price BULK COLLECT
 17     INTO   Part,
 18            Contract,
 19            Price
 20     FROM   part_temp_tab;
 21
 22     FORALL rec_ in Part.FIRST..Part.LAST
 23        UPDATE part_temp_tab
 24        SET    latest_price = latest_price * 2
 25        WHERE  part_no = Part(rec_);
 26
 27     COMMIT;
 28     DBMS_OUTPUT.PUT_LINE('Time (in Seconds) taken to UPDATE 68,169 records
using FORALL: '|| (DB
MS_UTILITY.GET_TIME - time_1_)/100);
 29  END;
 30  /
Time (in Seconds) taken to UPDATE 68,169 records using FORALL: 1756.13

PL/SQL procedure successfully completed.
and we said...
Well, first, you are not comparing apples to apples AND you are so totally
skewing the numbers with a FULL SCAN per row to be updated (no index on part_no)
that the cost of doing the full scan 68,169 times overshadows the entire test!

So, allow me to rewrite this test as follows, using my simple test harness
http://asktom.oracle.com/~tkyte/runstats.html
ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE TABLE part_temp_tab(
  2  part_no       VARCHAR2(35) PRIMARY KEY,
  3  contract      VARCHAR2(5),
  4  latest_price  NUMBER);

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> INSERT /*+ APPEND */ INTO part_temp_tab
  2  SELECT object_name||to_char(rownum,'fm00009'), 'ABC', rownum
  3    FROM all_objects;

23643 rows created.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_start number;
  3      l_run1  number;
  4      l_run2  number;
  5
  6     TYPE Parts IS TABLE OF part_temp_tab.part_no%TYPE;
  7     Part      Parts;
  8  begin
  9      SELECT part_no BULK COLLECT
 10        INTO Part
 11        FROM part_temp_tab;
 12
 13      insert into run_stats select 'before', stats.* from stats;
 14
 15      l_start := dbms_utility.get_time;
 16      FOR rec_ in 1 .. part.count
 17      loop
 18         UPDATE part_temp_tab for_loop
 19            SET latest_price = 1
 20          WHERE part_no = Part(rec_);
 21      end loop;
 22      commit;
 23      l_run1 := (dbms_utility.get_time-l_start);
 24      dbms_output.put_line( l_run1 || ' hsecs' );
 25
 26      insert into run_stats select 'after 1', stats.* from stats;
 27      l_start := dbms_utility.get_time;
 28      FORALL rec_ in Part.FIRST..Part.LAST
 29         UPDATE part_temp_tab forall
 30            SET latest_price = 2
 31          WHERE part_no = Part(rec_);
 32      commit;
 33      l_run2 := (dbms_utility.get_time-l_start);
 34      dbms_output.put_line( l_run2 || ' hsecs' );
 35      dbms_output.put_line
 36      ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) || '% of the time' );
 37
 38      insert into run_stats select 'after 2', stats.* from stats;
 39  end;
 40  /
1027 hsecs
911 hsecs
run 1 ran in 112.73% of the time

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value-a.value run1,
c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

NAME                                 RUN1       RUN2       DIFF
------------------------------ ---------- ---------- ----------
...
LATCH.checkpoint queue latch          613        428       -185
STAT...change write time              203        646        443
STAT...recursive cpu usage            781        327       -454
STAT...redo size                  5807516    5816480       8964
LATCH.cache buffers chains         167131     143521     -23610
STAT...buffer is pinned count           0      23641      23641
STAT...consistent gets              47298      23657     -23641
STAT...execute count                23648          6     -23642
STAT...recursive calls              23675         33     -23642
STAT...session logical reads        71516      47870     -23646
LATCH.library cache                 47685        355     -47330
STAT...session pga memory max       17024     186348     169324
STAT...session pga memory            5808     197564     191756

48 rows selected.

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

So, while it is interesting to see the wall clock differences -- what is crucial
is the LATCHing that is going on.

Latches = Locks = Serialization Device = Scalability Inhibitor = Reduced
concurrency.

The forall does signifcantly LESS work in that area.

Look at other things like the session logical reads -- very impressive that they
are reduced by about 1/3 with array processing.

It is not all about speed in a single user situation (although with reasonable
array sizes, like 100 or so you'll see more benefit there, array processing of
thousands of rows just isn't as good) -- it is about building scalable solutions
and the array processing example is definitely much more scalable (and faster to
boot)
Review & Followup

Rating: 5
Excellent September 16, 2002
Reviewer: Raghu
Most informative. I was on a single track (SPEED) and overlooked other crucial
factors.Thanks.
Rating: 3
One more test September 23, 2003
Reviewer: Marcio from br
Tom, I would like have as much performance as possible, will use this fix once
and no more, so scalability is no relevant here.
The situation is:

X          Y                  ID
---------- ---------- ----------
A          101                 1
           102                 2
           103                 3
           104                 4
           105                 5
           106                 6
           107                 7
           108                 8
           109                 9
/10744837_ 110                10
           111                11
           112                12
           113                13
           114                14
           115                15
           116                16
.
.
.

I have to put the previous one into x when x is null. Like this:
X          Y                  ID
---------- ---------- ----------
A          101                 1
A          102                 2 <-
A          103                 3 <-
A          104                 4 <-
A          105                 5 <-
.
.
.

I can't see this in sql statement so I put this in stored procedure. I coded
bulk collect and when I use forall seems to me the same for x in ...
Could be true? The profit here is scalability?

The test:

ops$t_mp00@MRP9I1> @teste_harness
ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> create table t as
  2  select decode( mod(rownum, 10), 0, substr(object_name,1,10), null) x,
  3         rownum+100 y,
  4         rownum id
  5    from all_objects
  6   where rownum <= 2000
  7  /

Table created.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> update t set x = 'A' where rownum = 1;

1 row updated.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> alter table t add constraint t_pk primary key ( id );

Table altered.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> create or replace procedure fix_a ( p_array_size in number
default 5)
  2  as
  3      type trec is record
  4      ( id        dbms_sql.number_table,
  5        x         dbms_sql.varchar2_table );
  6
  7      data        trec;
  8      errors              number;
  9      l_cnt               number default 1;
 10      l_x                 varchar2(30) default null;
 11      cursor c is select id, x from t;
 12  begin
 13      open c;
 14      loop
 15          dbms_application_info.set_client_info
 16            ( 'processing fix_a ' || l_cnt || ' thru ' ||
(l_cnt+p_array_size-1) );
 17
 18          fetch c bulk collect into data.id, data.x limit p_array_size;
 19
 20          if l_x is null then l_x := data.x(1);
 21          end if;
 22
 23          begin
 24             for j in 1 .. data.id.count
 25             loop
 26
 27                debug.f( 'Antes de quebrar -> l_x: %s , data.x: %s e j: %s',
l_x, data.x(j), j );
 28                if l_x <> nvl(data.x(j),l_x) then
 29                   l_x := data.x(j);
 30                end if;
 31
 32                debug.f( 'Depois de quebrar -> l_x: %s , data.x: %s e j: %s',
l_x, data.x(j), j );
 33                if data.x(j) is null then
 34                   data.x(j) := l_x;
 35                end if;
 36
 37                debug.f( 'Depois de comparar -> l_x: %s , data.x: %s e j:
%s', l_x, data.x(j), j );
 38                update t
 39                   set x = data.x(j)
 40                 where id = data.id(j);
 41             end loop;
 42          end;
 43          exit when c%notfound;
 44          l_cnt := l_cnt + p_array_size;
 45       end loop;
 46       close c;
 47  end;
 48  /

Procedure created.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> create or replace procedure fix_b ( p_array_size in number
default 5)
  2  as
  3      type trec is record
  4      ( id        dbms_sql.number_table,
  5        x         dbms_sql.varchar2_table );
  6
  7      data        trec;
  8      errors              number;
  9      l_cnt               number default 1;
 10      l_x                 varchar2(30) default null;
 11      cursor c is select id, x from t;
 12  begin
 13      open c;
 14      loop
 15          dbms_application_info.set_client_info
 16            ( 'processing ' || l_cnt || ' thru ' || (l_cnt+p_array_size-1) );
 17
 18          fetch c bulk collect into data.id, data.x limit p_array_size;
 19
 20          if l_x is null then l_x := data.x(1);
 21          end if;
 22
 23          begin
 24             for j in 1 .. data.id.count
 25             loop
 26
 27                debug.f( 'Antes de quebrar -> l_x: %s , data.x: %s e j: %s',
l_x, data.x(j), j );
 28                if l_x <> nvl(data.x(j),l_x) then
 29                   l_x := data.x(j);
 30                end if;
 31
 32                debug.f( 'Depois de quebrar -> l_x: %s , data.x: %s e j: %s',
l_x, data.x(j), j );
 33                if data.x(j) is null then
 34                   data.x(j) := l_x;
 35                end if;
 36
 37                debug.f( 'Depois de comparar -> l_x: %s , data.x: %s e j:
%s', l_x, data.x(j), j );
 38             end loop;
 39             forall i in 1 .. data.id.count
 40                  update t forall
 41                     set x = data.x(i)
 42                   where id = data.id(i);
 43          end;
 44          exit when c%notfound;
 45          l_cnt := l_cnt + p_array_size;
 46       end loop;
 47       close c;
 48  end;
 49  /

Procedure created.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> begin
  2
  3          runstats_pkg.rs_start;
  4
  5          fix_a(100);
  6
  7          runstats_pkg.rs_middle;
  8
  9          fix_b(100);
 10
 11          runstats_pkg.rs_stop;
 12
 13  end;
 14  /
Run1 ran in 481 hsecs
Run2 ran in 384 hsecs
run 1 ran in 125,26% of the time

Name                                Run1      Run2      Diff
LATCH.active checkpoint queue          1         2         1
LATCH.session timer                    1         2         1
STAT...calls to kcmgas                12        13         1
STAT...commit cleanouts succes         1         0        -1
STAT...messages sent                   4         3        -1
STAT...parse count (hard)              2         1        -1
STAT...cursor authentications          1         0        -1
STAT...commit cleanouts                1         0        -1
LATCH.Consistent RBA                   5         3        -2
STAT...deferred (CURRENT) bloc         2         0        -2
STAT...table fetch by rowid            2         0        -2
STAT...parse time elapsed              2         0        -2
STAT...parse time cpu                  2         0        -2
STAT...index fetch by key          2,002     2,000        -2
LATCH.channel operations paren         4         2        -2
STAT...cluster key scans               2         0        -2
LATCH.dml lock allocation              9        12         3
LATCH.lgwr LWN SCN                     5         2        -3
LATCH.mostly latch-free SCN            5         2        -3
LATCH.simulator lru latch              5         2        -3
LATCH.session idle bit                24        21        -3
STAT...sorts (memory)                  7         4        -3
STAT...cluster key scan block          4         0        -4
STAT...physical reads                  4         0        -4
LATCH.library cache load lock          6         0        -6
LATCH.session allocation               6         0        -6
STAT...redo ordering marks             6         0        -6
STAT...index scans kdiixs1         6,006     6,000        -6
LATCH.child cursor hash table         20        12        -8
STAT...CR blocks created               5        13         8
STAT...enqueue releases                9         1        -8
STAT...parse count (total)            10         2        -8
STAT...rollbacks only - consis         5        13         8
STAT...opened cursors cumulati        10         2        -8
STAT...change write time              15         7        -8
STAT...calls to get snapshot s     8,014     8,005        -9
STAT...enqueue requests               10         1        -9
STAT...shared hash latch upgra     6,010     6,000       -10
STAT...table scan blocks gotte        24        34        10
LATCH.redo writing                    22        10       -12
LATCH.enqueue hash chains             31        16       -15
STAT...no work - consistent re        26        41        15
LATCH.checkpoint queue latch          64        80        16
LATCH.enqueues                        24         4       -20
LATCH.messages                        43        22       -21
STAT...buffer is not pinned co        33        54        21
LATCH.row cache enqueue latch         32        10       -22
LATCH.process queue reference        129        99       -30
LATCH.row cache objects               40        10       -30
STAT...free buffer requested          67        30       -37
LATCH.library cache pin alloca        57        19       -38
LATCH.cache buffers lru chain         67        28       -39
LATCH.SQL memory manager worka       134        67       -67
STAT...recursive cpu usage           297       212       -85
LATCH.simulator hash latch           269       516       247
STAT...consistent changes            676     1,295       619
STAT...data blocks consistent        183       802       619
LATCH.undo global data             1,108        17    -1,091
STAT...active txn count during     1,098         4    -1,094
STAT...cleanout - number of kt     1,098         4    -1,094
STAT...calls to kcmgcs             1,099         4    -1,095
STAT...db block gets               6,988     5,812    -1,176
STAT...buffer is pinned count          0     1,960     1,960
STAT...execute count               8,010     6,022    -1,988
LATCH.shared pool                  8,085     6,059    -2,026
STAT...recursive calls             8,104     6,045    -2,059
STAT...redo entries                4,704     2,487    -2,217
LATCH.redo allocation              4,715     2,491    -2,224
STAT...consistent gets            11,327     8,883    -2,444
STAT...consistent gets - exami     5,288     2,826    -2,462
STAT...session logical reads      18,315    14,695    -3,620
LATCH.library cache pin           16,105    12,084    -4,021
LATCH.library cache               16,174    12,113    -4,061
STAT...db block changes            9,437     5,008    -4,429
LATCH.cache buffers chains        46,990    35,694   -11,296
STAT...redo size               1,041,048   565,964  -475,084

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
94,180    69,399   -24,781 135.71%

PL/SQL procedure successfully completed.

ops$t_mp00@MRP9I1>
ops$t_mp00@MRP9I1> set echo off
ops$t_mp00@MRP9I1>
Followup:
that it ran faster doesn't count either?

it ran faster
it used less latches

seems to be a bonus all around?
Rating: 4
sql statement September 24, 2003
Reviewer: Marcio from br
yes for sure. But i was thinking about any analitic function could do it without
procedure code.

x   y
--- ---
a   1
    2
    3
b   4
    5
    6
.
.
.

To be...
x   y
--- ---
a   1
a   2
a   3
b   4
b   5
b   6
.
.
.

I am sure sql statement will be so faster, but I couldn't see any possibility.
Is there one?

Thks,
Marcio
Followup:
oh, ok here is sql, it was not clear what you were after.  it is a trick, we can
encode the max value using row_number to "carry forward":

ops$tkyte@ORA920LAP> drop table t;

Table dropped.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create table t
  2  as
  3  select decode( mod(rownum,10), 1, rownum, null ) a, rownum b
  4    from all_users;

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select a, b, substr(max( to_char(rn,'fm0000000000') || a )
over (order by b),11) a_carried
  2    from (
  3  select a, b, decode( a, null, to_number(null), row_number() over (order by
b)) rn
  4    from t
  5         )
  6  /

         A          B A_CARRIED
---------- ---------- -----------------------------------------
         1          1 1
                    2 1
                    3 1
                    4 1
                    5 1
                    6 1
                    7 1
                    8 1
                    9 1
                   10 1
        11         11 11
                   12 11
                   13 11
                   14 11
                   15 11
                   16 11
                   17 11
                   18 11
                   19 11
                   20 11
        21         21 21
Rating: 5
* * * * * plus * September 24, 2003
Reviewer: Marcio from br
* * * * * plus * Like The creme de la creme!
Thank you so much!

Look that:
=================================
Run1 ran in 832 hsecs
Run2 ran in 263 hsecs
run 1 ran in 316,35% of the time
---------------------------------

about 2000 rows, but will run in 70.000.000
Rating: 3
Nice December 24, 2003
Reviewer: Mary from Boulder,USA
Dear Sir,
Do you have a loop structure for the output?
  1
  1 2
  1 2 3
  1 2 3 4
Thanks in advance.
Followup:
huh?
December 24, 2003 -- Thanks for the question regarding "Sql to retrive a columns", version 8.1.7
You Asked
Hi tom

I  want to find the table_name and columns which contain a specific text.

For example if the user inputs a word India
The table_name and the column name which contain the word should be displayed.

*****Update*****

I  want to search every column of every table in a schema looking for ones that
have a row with the word "%INDIA%" in it

*****************

thanks and regards
Lakshmi
and we said...
You can start with this snippet:
http://www.oracle.com/oramag/code/tips2001/index.html?120201.html
You'll want to modify it to skip over numeric columns -- lobs/raws and such
(easy to add a where datatype in ( 'VARCHAR2', 'CHAR' ) to the process!

Be aware -- it isn't going to be *fast*.   Another enhancement to it would be to
make it search for all columns in a table at the same time (instead of a full
scan per column).

Oh well, might as well just do it then:

ops$tkyte@ORA920.US.ORACLE.COM> create or replace
  2  procedure find_string( p_str in varchar2 )
  3  authid current_user
  4  as
  5      l_query    long;
  6      l_case     long;
  7      l_runquery boolean;
  8      l_tname    varchar2(30);
  9      l_cname    varchar2(30);
 10  begin
 11      dbms_application_info.set_client_info( '%' || upper(p_str) || '%' );
 12
 13      for x in (select * from user_tables )
 14      loop
 15          l_query := 'select ''' || x.table_name || ''', $$
 16                        from ' || x.table_name || '
 17                       where rownum = 1 and ( 1=0 ';
 18          l_case := 'case ';
 19          l_runquery := FALSE;
 20          for y in ( select *
 21                       from user_tab_columns
 22                      where table_name = x.table_name
 23                        and data_type in ( 'VARCHAR2', 'CHAR' )
 24                   )
 25          loop
 26              l_runquery := TRUE;
 27              l_query := l_query || ' or upper(' || y.column_name ||
 28                         ') like userenv(''client_info'') ';
 29              l_case := l_case || ' when upper(' || y.column_name ||
 30                        ') like userenv(''client_info'') then ''' ||
 31                        y.column_name || '''';
 32          end loop;
 33          if ( l_runquery )
 34          then
 35              l_case := l_case || ' else NULL end';
 36              l_query := replace( l_query, '$$', l_case ) || ')';
 37              begin
 38                  execute immediate l_query into l_tname, l_cname;
 39                  dbms_output.put_line
 40                  ( 'Found in ' || l_tname || '.' || l_cname );
 41              exception
 42                  when no_data_found then
 43                      dbms_output.put_line
 44                      ( 'No hits in ' || x.table_name );
 45              end;
 46          end if;
 47
 48      end loop;
 49  end;
 50  /

Procedure created.

ops$tkyte@ORA920.US.ORACLE.COM> grant execute on find_string to public;
Grant succeeded.

ops$tkyte@ORA920.US.ORACLE.COM> @connect scott/tiger

scott@ORA920.US.ORACLE.COM> exec ops$tkyte.find_string( 'kin' )
No hits in BONUS
No hits in DEPT
Found in EMP.ENAME

PL/SQL procedure successfully completed.
Review & Followup

Rating: 4
Problem with searching a value in multiple columns in a table August 30, 2002
Reviewer: sai from USA
Tom,

I used your procedure "find_string" to find out columns in a table that has same
values. It doesn't seem to work for more than one column in a table having the
same value.
For eg:-

SQL>create table test(eno number,ename varchar2(20),prof varchar2(20));

Table created.

SQL>insert into test values(1,'aa','aa');

1 row created.

SQL>insert into test values(2,'bbb','ccc');

1 row created.

SQL>commit;

Commit complete.

SQL>exec find_string('aa');
Found in TEST.ENAME

PL/SQL procedure successfully completed.

I have the value 'aa' in both ENAME  and PROF columns,
But it finds it only in ENAME.

Is there anything wrong in the way I did it ,
please explain ?

Thanks,
Sai
Followup:
That is exactly the way I designed it -- it stops immediately upon finding the
first occurrence in the first column.

I took the approach that simply "knowing" would be enough.

So, here is an even slower.... one.  It full scans every single table and
reports multiple hits (it uses a distinct so the number of messages/table is a
function of the number of COLUMNS in the table -- not the number of rows that
contain the string)

ops$tkyte@ORA920.US.ORACLE.COM> create or replace
  2  procedure find_string( p_str in varchar2 )
  3  authid current_user
  4  as
  5      l_query    long;
  6      l_case     long;
  7      l_runquery boolean;
  8      l_tname    varchar2(30);
  9      l_cname    varchar2(30);
 10          type rc is ref cursor;
 11          l_cursor   rc;
 12  begin
 13      dbms_application_info.set_client_info( '%' || upper(p_str) || '%' );
 14
 15      for x in (select * from user_tables )
 16      loop
 17          l_query := 'select distinct ''' || x.table_name || ''', $$
 18                        from ' || x.table_name || '
 19                       where ( 1=0 ';
 20          l_runquery := FALSE;
 21                  l_case     := NULL;
 22          for y in ( select *
 23                       from user_tab_columns
 24                      where table_name = x.table_name
 25                        and data_type in ( 'VARCHAR2', 'CHAR' )
 26                   )
 27          loop
 28              l_runquery := TRUE;
 29              l_query := l_query || ' or upper(' || y.column_name ||
 30                         ') like userenv(''client_info'') ';
 31              l_case := l_case || '||'' ''|| case when upper(' ||
y.column_name ||
 32                        ') like userenv(''client_info'') then ''' ||
 33                        y.column_name || ''' else NULL end';
 34          end loop;
 35          if ( l_runquery )
 36          then
 37              l_query := replace( l_query, '$$', substr(l_case,8) ) || ')';
 38              begin
 39                                  open l_cursor for l_query;
 40                                  loop
 41                                          fetch l_cursor into l_tname,
l_cname;
 42                                          exit when l_cursor%notfound;
 43                          dbms_output.put_line
 44                          ( 'Found in ' || l_tname || '.' || l_cname );
 45                                  end loop;
 46                                  close l_cursor;
 47              end;
 48          end if;
 49
 50      end loop;
 51  end;
 52  /

Procedure created.

ops$tkyte@ORA920.US.ORACLE.COM> show err
No errors.
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> grant execute on find_string to public;

Grant succeeded.

ops$tkyte@ORA920.US.ORACLE.COM> @connect scott/tiger
ops$tkyte@ORA920.US.ORACLE.COM> set termout off
scott@ORA920.US.ORACLE.COM>
scott@ORA920.US.ORACLE.COM> set termout on
scott@ORA920.US.ORACLE.COM>
scott@ORA920.US.ORACLE.COM> drop table t;

Table dropped.

scott@ORA920.US.ORACLE.COM> create table t ( x varchar2(25), y varchar2(25) );

Table created.

scott@ORA920.US.ORACLE.COM> insert into t values ( 'KING', 'xxx' );

1 row created.

scott@ORA920.US.ORACLE.COM> insert into t values ( 'xxx', 'KING' );

1 row created.

scott@ORA920.US.ORACLE.COM> insert into t values ( 'KING', 'KING' );

1 row created.

scott@ORA920.US.ORACLE.COM> exec ops$tkyte.find_string( 'kin' )
Found in EMP.ENAME
Found in T. Y
Found in T.X
Found in T.X Y

PL/SQL procedure successfully completed.
Rating: 3
SQL TO RETRIVE A COLUMNS January 29, 2003
Reviewer: Richard from USA
Tom,

This would be very useful but when I tried your code but I got the following:

SQL> create procedure find_string( p_str in varchar2 )
  2  authid current_user
  3  as
  4      l_query    long;
  5      l_case     long;
  6      l_runquery boolean;
  7      l_tname    varchar2(30);
  8      l_cname    varchar2(30);
  9          type rc is ref cursor;
 10          l_cursor   rc;
 11  begin
 12      dbms_application_info.set_client_info( '%' || upper(p_str) || '%' );
 13
 14      for x in (select * from user_tables )
 15      loop
 16          l_query := 'select distinct ''' || x.table_name || ''', $$
 17                        from ' || x.table_name || '
 18                       where ( 1=0 ';
 19          l_runquery := FALSE;
 20                  l_case := NULL;
 21          for y in ( select *
 22                       from user_tab_columns
 23                      where table_name = x.table_name
 24                        and data_type in ( 'VARCHAR2', 'CHAR' )
 25                   )
 26          loop
 27             l_runquery := TRUE;
 28             l_query := l_query || ' or upper(' || y.column_name ||
 29                        ') like userenv(''client_info'') ';
30             l_case := l_case || '||'' ''|| case when upper(' || y.column_name
|| ') like userenv(''client_info'') then ''' || y.column_name || ''' else NULL
end';
31          end loop;
32          if ( l_runquery )
33          then
34             l_query := replace( l_query, '$$', substr(l_case,8) ) || ')';
35             begin
36                 open l_cursor for l_query;
37                     loop
38                         fetch l_cursor into l_tname, l_cname;
39                         exit when l_cursor%notfound;
40                         dbms_output.put_line
41                         ( 'Found in ' || l_tname || '.' || l_cname );
42                     end loop;
43                 close l_cursor;
44             end;
45          end if;
46     end loop;
47  end;
48  /

Procedure created.

SQL> EXECUTE FIND_STRING ('SANDWICH');
BEGIN FIND_STRING ('SANDWICH'); END;

*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SYSADM.FIND_STRING", line 38
ORA-06512: at line 1

SQL> EXECUTE FIND_STRING ( 'SANDWICH' );
BEGIN FIND_STRING ( 'SANDWICH' ); END;

*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SYSADM.FIND_STRING", line 38
ORA-06512: at line 1
Followup:
so, when you added some debug and checked out what table/column it failed on --
what did you discover?

What I mean is -- what have you done so far to try and figure it out?  Be great
if you did and contributed a fix??

but I think I see the problem -- if you have lots of columns with sandwich in it
in a single row -- we are just glueing them together...  so when I made the
quick fix to search for all -- not just the first, it could overflow the cname
size....

(but you know, 30 seconds of debugging on your part....)

So, for example:

ops$tkyte@ORA817DEV> create table t
  2  ( some_column        varchar2(25) default 'foo',
  3    some_column1       varchar2(25) default 'foo',
  4    some_column2       varchar2(25) default 'foo',
  5    some_column3       varchar2(25) default 'foo',
  6    some_column4       varchar2(25) default 'foo',
  7    some_column5       varchar2(25) default 'foo',
  8    some_column6       varchar2(25) default 'foo',
  9    some_column7       varchar2(25) default 'foo',
 10    some_column8       varchar2(25) default 'foo'
 11  );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert into t (some_column) values ('bar' );

1 row created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> exec find_string( 'bar' );
Found in T.SOME_COLUMN

PL/SQL procedure successfully completed.
that works but:

ops$tkyte@ORA817DEV> exec find_string( 'foo' );
BEGIN find_string( 'foo' ); END;

*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "OPS$TKYTE.FIND_STRING", line 40
ORA-06512: at line 1
doesn't cause we are glueing lots of cnames together so, change:

  6      l_runquery boolean;
  7      l_tname    varchar2(30);
  8      l_cname    varchar2(4000);
  9          type rc is ref cursor;
 10          l_cursor   rc;

and wah-lah:

ops$tkyte@ORA817DEV> exec find_string( 'foo' );
Found in T. SOME_COLUMN1 SOME_COLUMN2 SOME_COLUMN3 SOME_COLUMN4 SOME_COLUMN5
SOME_COLUMN6 SOME_COLUMN7 SOME_COLUMN8

PL/SQL procedure successfully completed.

(now aren't you all glad I didn't use a when others to do "error handling" ;)
we'd never have known of this problem....)
Rating: 2
SQL TO RETRIVE A COLUMNS January 29, 2003
Reviewer: Richard from USA
Tom,

When I tried the snippet, referred to by the hyperlink, I go the following upon
execution:

execute find_string ;

Enter value for giv_str: SANDWICH
old   3:    giv_str varchar(200):='&giv_str';
new   3:    giv_str varchar(200):='SANDWICH';
Table Name                     Column
Name
-----------------------------  -----------------------------
declare
*
ERROR at line 1:
ORA-00932: inconsistent datatypes
ORA-06512: at line 13
Followup:
....
You'll want to modify it to skip over numeric columns -- lobs/raws and such
(easy to add a where datatype in ( 'VARCHAR2', 'CHAR' ) to the process!
......

is the snippet of text I have right after that link ;)

and the reason I have that there is cause, well, cause you'll get inconsistent
datatypes
Rating: 5
January 29, 2003
Reviewer: A reader
Wonderful!
Thanks!
-Nat
Rating: 5
Sql to retrive a columns January 29, 2003
Reviewer: Richard from USA
Tom,

You are correct, I should have done more legwork on my own, I know your time is
valuable.

By the way, your script works great now.  It runs through about 9000 tables in
about 7 minutes.

I was in a rush and missed the fix for the referenced snippet.  I don't know how
long the snippet takes to run ... I stopped it after an hour of doing the same
search.  I'll try to run it to completion on another day.

Thanks for the code and the fixes.

Richard
Followup:
I can show you a case where it takes hours for a single table ;)

it is a function of the "volume" of data -- not the number of tables....
Rating: 5
Just an expert view February 06, 2003
Reviewer: Anirudh from New Delhi, India
Hi Tom,
Tried to develop the procedure for this and just wanted to have your expert
comments
on this. Its quite slow, though. I have 20 tables in the schema, some of them
are quite big
(million rows )
Anna itext_usr@SUCC1>
Anna itext_usr@SUCC1> CREATE OR REPLACE PROCEDURE proc_search_string(p_in_string
VARCHAR2) IS
  2    TYPE ref_cur IS REF CURSOR;
  3    v_ref_cur           ref_cur;
  4    found_table         user_tab_columns.table_name%TYPE;
  5    found_col        user_tab_columns.column_name%TYPE;
  6    v_found     VARCHAR2(50) := 'NOT FOUND';
  7    v_select         VARCHAR2(1000) ;
  8    v_test      VARCHAR2(1000);
  9    v_col       VARCHAR2(100);
 10    v_instance_count NUMBER := 0;
 11  BEGIN
 12  -- We'll make use of user_objects first
 13  --  dbms_output.put_line('1');
 14    FOR i IN ( SELECT *
 15               FROM user_tables)
 16    LOOP
 17  --Then user_tab_columns
 18  --dbms_output.put_line('10');
 19      FOR x IN ( SELECT column_name,data_type
 20                 FROM user_tab_columns
 21                 WHERE table_name = i.table_name
 22                 AND data_type IN ('VARCHAR2'))
 23      LOOP
 24
 25          v_select := '';
 26          v_col := x.column_name;
 27          v_select := 'SELECT '||v_col||' FROM '||i.table_name||' WHERE '||
 28                    v_col||' LIKE '||CHR(39)||'%'||p_in_string||'%'||CHR(39);
 29          OPEN v_ref_cur FOR v_select;
 30          LOOP
 31            EXIT WHEN v_ref_cur%NOTFOUND;
 32            FETCH v_ref_cur INTO found_col;
 33            IF v_ref_cur%FOUND THEN
 34              v_instance_count := v_instance_count+1;
 35              dbms_output.put_line(v_instance_count||'-->
'||i.table_name||'.'||x.column_name);
 36            END IF;
 37          END LOOP;
 38
 39
 40       --dbms_output.put_line(v_select);
 41      END LOOP;
 42    END LOOP;
 43  --dbms_output.put_line('10000');
 44
 45  END;
 46
 47
 48  /

Procedure created.

Anna itext_usr@SUCC1>  exec proc_search_string('anirudh');
1-->  ANI_TEST.VALUE
2-->  LOCATION.MAYOR

PL/SQL procedure successfully completed.
Followup:
Like I said originally:

Be aware -- it isn't going to be *fast*.   Another enhancement to it would be to

make it search for all columns in a table at the same time (instead of a full
scan per column).

you might look at the one I already coded above as it does a SINGLE full scan --
whereas yours does a full scan PER column
Rating: 5
March 25, 2003
Reviewer: A reader
Hi Tom,

 Is there any way to find table columns referenced in stored
 Objects (procedure,function etc..)?

For eg. I have xyz procedure and it is refering
   empno and ename column of table EMP.
  I need sql statement that tell me

   STORED PROCEDURE xyz IS REFERING column empno and ename
   FROM TABLE emp

Thanks,
Followup:
No, it sets up the dependency to the TABLE.  at the column level, we don't track
it.
Rating: 3
OK December 24, 2003
Reviewer: Basheer from Karachi,Pakistan
Hello Sir,
 To describe the columns and datatypes we use the following
ways
 1)describe <table_name>
 2) we can issue a statement like the one
   sql> select column_name,data_type from user_tab_columns
   where table_name = <table_name>;
 My question is
   Do you any other ways to accomplish the same?
 Thanks in advance.
Followup:
sure, in code you could "prepare" a "select * from <tablename>" and then use the
programatic api's to describe the result set.

eg: dbms_sql, do a preparse and then a describe call, you'll have a table of
records that describes each column -- its name, data type and so on.
December 24, 2003 angira -- Thanks for the question regarding "ref cursor and %rowcount", version
You Asked
WHY CANT I USE A CURSOR ATTRIBUTE
WITH REFCURSOR DATATYPE WHEN MY QUERY IS RETURNING NO ROWS
SELECTED .
I WANT TO DO A CHECK ON %ROWCOUNT = 0
AND TELL MY REF CURSOR TO POINT TO
ANOTHER QUERY WORK AREA
I AM USING ORACLE 7
and we said...
You use %rowcount only with implicit cursors that do
insert/update/delete.

You use %notfound with SELECTs to see if they return data or
not. Here is an example:

ops$tkyte@8i> declare
  2          type refCur is ref cursor;
  3
  4          l_cursor        refCur;
  5          l_rec           dual%rowtype;
  6  begin
  7          open l_cursor for select * from dual where 1 = 0;
  8
  9          fetch l_cursor into l_rec;
 10
 11          if ( l_cursor%notfound )
 12          then
 13                  dbms_output.put_line( 'We go NO Data' );
 14          end if;
 15  end;
 16  /
We go NO Data

PL/SQL procedure successfully completed.

The "if ( l_cursor%notfound )" is what you use to test for no
more data with a cursor of any type.
Review & Followup

Rating: 5
not fatching ?? November 12, 2002
Reviewer: A reader
Hi tom,

 I am returning the refcursor,
and returning thro' the procedure parameter
so I don't want to fatch inthe procedure,
-- I just want to dynamically open the cursor
=====> check if there is atleast 1 record in the cursor
--- return as refcursor

set serveroutput on size 100000

declare
         type refCur is ref cursor;
          l_cursor        refCur;
          l_rec           dual%rowtype;
  begin
         open l_cursor for select * from dual where 1 = 0;
--            fetch l_cursor into l_rec;
        if ( l_cursor%notfound )
        then
               dbms_output.put_line( 'We go NO Data' );
        end if;
 end;
/

PL/SQL procedure successfully completed.

-- so how can I find # of rows in the cursor ? with out fatching ?

Thanks,
Followup:
You cannot -- not possible.

It is like asking "how can I find out if the box is empty without opening it?"
You have to open it to find out.  You have to fetch to find out.

The CLIENT is the one that should deal with this particular detail OR you want
to have a function that returns a value, not a cursor.
Rating: 5
ref cursor the # of column is known at run time ?? January 13, 2003
Reviewer: A reader
Hi tom,

 I have this pl/sql block..
/********************************************/
declare
         vrc_entity p_entity.rc_entity;
     rec_entiy entity%ROWTYPE;
         msg varchar2(2000);
         errcd varchar2(2000);
begin

 p_entity.GET_entity(null,null,null,vrc_entity, msg,errcd);

     IF msg = 'DB-SUCCESS' THEN
      LOOP
            FETCH vrc_entity INTO rec_entity;    <<-----
         EXIT WHEN vrc_entity%NOTFOUND;
           DBMS_OUTPUT.PUT_LINE(' entity Code: ' ||   rec_entity.entity_code ||
'entity Name: ' || rec_entity.entity_name);
      END LOOP;
     END IF;
end;
/

/**********************************************/

The above proceudre does not work
                                    *
ERROR at line 171:
ORA-06550: line 171, column 23:
PLS-00597: expression 'REC_ENTITY' in the INTO list is of wrong type
ORA-06550: line 171, column 3:
PL/SQL: SQL Statement ignored
ORA-06550: line 173, column 51:
PLS-00487: Invalid reference to variable 'REC_ENTITY'
ORA-06550: line 173, column 3:
PL/SQL: Statement ignored

because the column returned and columns in the fatch into stmt are not same,

now,
    procedure p_entity.GET_entity returns a refcursor
and it is dynamic so I don't know what columns will be returned untill run time

  how can I declare a %ROWTYPE of this ref-cursor so that
it will be able to fatch,hold and display the information ?
Followup:
If the output varies from run to run (eg: different number and types of columns)
you cannot use a ref cursor in plsql.  It has to have a well known structure
at compile time

You can (and must if you have a need to do this) use DBMS_SQL which gives you
procedural methods to access each column, describe the query result set and so
on.
Rating: 5
over head ?? January 13, 2003
Reviewer: A reader
Thanks Tom for your reply....

  but it will be a big procedure. I will have to
parse each column and then move/copy the values.

  but can't we do it dynamically as soon as the
dynamic query is build, the recored type is known !!

 or even latter, as soon as I open the refcursor
can't we bind the recordtype using %rowtype so some
psudo variable because I need to know which columns
are participating when I fatch the record not before then

Thanks,
Followup:
the record is not known at COMPILE TIME -- hence plsql cannot do it.

DBMS_SQL is the only way.  I don't know what you mean by "a big procedure".  See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:68212348056
that procedure fits on a cocktail napkin and dumps most queries (doesn't do
blobs and clobs for example) do a flat file.  Its not big.

It is only big if you make it big.

Bottom line though -- you WILL NOT be using a ref cursor to do this as it is
quite impossible.  Period.  DBMS_SQL is the only way to handle a result set with
an unknown (until runtime unknown) number and type of columns in PLSQL.

(overhead = something avoidable that you do not technically need.  this
therefore is by defintion NOT overhead as there is quite simply no other way to
do it.  it need not be big as demonstrated -- in fact - if you play with a PLSQL
index by table -- you might find it can be quite small -- think about it, fetch
the i'th column into the i'th array element in a loop -- three lines of code to
"fetch a row" regardless of the number of columns in the query.  Code is only
BIG when you write alot of it.)
Rating: 5
Thanks, January 14, 2003
Reviewer: A reader
Thanks tom :)
Rating: 3
Excellent December 24, 2003
Reviewer: Sam from CT,USA
Hello sir,
 I need a query that returns the output in the following
 format
  table_name   row_count
    Emp          14
    Dept          4
 for this purpose I analyzed the schema and issued a statement like
 sql> select table_name,num_rows from user_tables where
table_name in (select table_name from user_tab_columns);
what I would like to ask is:
  Is there any other trick to do the same?
TIA
Followup:
well, you might just

select table_name, num_rows from user_tables;

the subquery is sort of redundant there.

see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1660875645686
December 24, 2003 Prashant -- Thanks for the question regarding "SHARED_POOL_RESERVE_SIZE parameter", version 8.1.7
You Asked
What actually is the use of SHARED_POOL_RESERVE_SIZE? What i mean here is how or
when do I use this area of shared pool? Is it used when I pin the packages?
and we said...
Well, this parameter was added in order to reduce the fragmentation that might
be observed in version 7.3 and before when you used MTS (multi-threaded server).

Back then -- you might have pinned large packages at startup, to get them
allocated and in the shared pool "at the front of it".

You would use the reserve size to set aside memory to be allocated for "large
allocations" (MTS memory in general).

The rest of the memory would be used by the system as needed to load plsql/sql
whatever.

With 8.0, the large pool came along -- this obviated the need for the "reserved"
portion of the shared pool.  We moved those big allocations from the shared pool
into the large pool.  So, the reserved is not needed.

For the same reason, pinning is pretty much "not needed" either.  It makes some
people feel better but in 99.999% of the cases (eg: system is running normally,
you use bind variables) pinning is pretty much a waste of programming effort on
your part as well.

I would suggest not using either really.  Ignore their existence.
Review & Followup

Rating: 3
Clarification February 07, 2003
Reviewer: Scott Watson from CANADA
Does the same hold true when you are running in dedicated server mode?  I was
under the impression that large memory requests would try to allocate memory
from the SHARED_POOL_RESERVE area when a chunk of memory was required above a
certain threshold or some other algorithm.  ( mimizing the number of ora-4031
errors )

Secondly I thought creating a large pool in dedicated server was a waste of
memory as it is only used in MTS or when running RMAN in general.

Thanks in advance for clearing this up.
Followup:
what large memory requests would be made?

pga memory is in the dedicated server (and uga memory in dedicated server)...

uga memory is the "big" allocations -- that is in the large pool with mts ...

large pool is generally not needed (parallel query changes that) in dedicated
server mode.  In the scope of my discussion -- the large allocations were all
coming from MTS.  sorry for the confusion.
Rating: 4
Closer February 07, 2003
Reviewer: Scott Watson from CANADA
>>what large memory requests would be made?
That is a good question and one I have been asking myself recently.  Our
application has been running fine and now after 3 months we are receiving
ora-4031 errors on the shared pool. (until we bounce the instance of course)

Free Memory is still high (20meg)therefore I believe this is related to a
fragmentation problem.  I queried the view V$SHARED_POOL_RESERVE and infact the
failure count had been incremented but I cannot locate what is causing the
problem.

Any ideas on how to resolve this issue?  Our largest package is nowhere close to
the amount of free memory available and as you have said before it wouldn't
matter as the code is loaded in chunks anyway.  My final guess as tp the cause
of our problem would be the app created a rogue dynamic sql statement which
could not be allocated to the pool.
Followup:
Are you using bind variables?

what is the size of your shared pool?

do you have an instance of the 4031 - it should have information in it pointing
to the failed statement.  what are all of the last columns of v$shared pool
reserved?
Rating: 4
Thanks Tom and Scott February 10, 2003
Reviewer: Prashant from London UK
Hi,
The response and the discussion thereafter has been very useful and added to my
knowledge.

--prashant
Rating: 2
large pool February 10, 2003
Reviewer: A reader
are you saying we can pin packages in large pool?
Followup:
nope, that is not what I'm saying.

I'm saying -- the large pool obviated the need to pin packages.
Rating: 4
Response.. February 10, 2003
Reviewer: Scott Watson from CANADA
>> Are you using bind variables?

Yes, in the majority of cases, but there are still some statements that could be
modified to use bind variables.  I am working with the developers on this.

>> what is the size of your shared pool?
120 megs  (most code is in the app with a couple of stored packages)  We are
slowly starting to move the logic from the app to the database.

>>do you have an instance of the 4031 - it should have >>information in it
pointing
>>to the failed statement.  what are all of the last >>columns of v$shared pool
>>reserved?

The trace files always point to the same package but different statements.

We bounced the database so I don't have access to the v$shared_pool_reserved
information anymore.  I will keep it if/when we get it again.

We are planning an upgrade to 8.1.7.4 but if it is really due to fragmentation I
don't think any upgrade will help.

Thanks.
Rating: 5
contradiction in the doc? May 14, 2003
Reviewer: PINGU
Hi

I have some doubts because of Oracle 9.2 performance tuning documentation.

In Chapter 14 when it explains about shared pool it says this

"Allocation of memory from the shared pool is performed in chunks. This allows
large objects (over 5k) to be loaded into the cache without requiring a single
contiguous area, hence reducing the possibility of running out of enough
contiguous memory due to fragmentation."

So does this mean the possibilities of getting ORA-04031 have been dramatically
reduced?

The following setence says

"Infrequently, Java, PL/SQL, or SQL cursors may make allocations out of the
shared pool that are larger than 5k. To allow these allocations to occur most
efficiently, Oracle segregates a small amount of the shared pool. This memory is
used if the shared pool does not have enough space. The segregated area of the
shared pool is called the reserved pool."

Now this is for me the contradicting part. In the first place it says now memory
is assigned in chunks so big objects does not requite contiguos space anymore,
but secondly it says we still need the reserved pool if the shared pool hasnt
got enough space, does LRU work here?
Followup:
Yes, that is what it means -- plsql and such is loaded in about 4k pages it the
shared pool and the 4k chunks need not be contigous.  It is the same concept as
locally managed tablespaces and why they cannot get fragmented.  if everyone is
using about 4k chuncks -- any chunk of free memory is as good as any other
chunk.

key word = INFREQUENTLY in the 2cnd to last paragraph.

What it is saying is that INFREQUENTLY we might allocate something larger then
5k.  when that happens, we'll try the reserved pool first - to keep these larger
then normal allocations from being in the other 4/5k chunks place -- to keep it
(the larger shared pool) from getting fragmented.
Rating: 4
reserved pool May 14, 2003
Reviewer: Reader from Houston, TX
Does it mean if i needed a memory allocation say 6kb for my package, oracle
would allocate from the shared pool reserved size based on
shared_pool_reserved_size to avoid fragmentation. So, i would say it is
important to configure shared_pool_reserved_size parameter even if i don't pin
anything? am i right? Thanks.
Followup:
no, oracle will page that in in chunks smaller then 6k
Rating: 5
clarification please July 26, 2003
Reviewer: Reader
Tom, If I configured shared_pool_reserved_size, will oracle use it? If so, when
does oracle use this reserved space if I do not pin any package in the reserved
pool?

Also, is it true that I can pin only packages not individual procedures if I
chose to do?

Thanks.
Followup:
it will not use it very often in 8 and above

it is for "large memory allocations"
it was an attempt to fix ora-4031's when using MTS with Oracle
that was really fixed with the addition of the large pool.

You cannot "pin" packages in the reserved pool, it is a pool reserved for big
allocations and plsql pages itself in in 4k chunks so it won't use that area.

You do not even want to be pinning at all.  ignore pin, not necessary.
Rating: 5
ok July 26, 2003
Reviewer: Reader
I agree. Just for academic quriosity, if I used dbms_shared_pool.keep procedure
to pin a package, for example, will that package remain in shared pool for ever
until i bounce the instance whether or not I use that package? One of my
application developers is asking me to pin sequence and STANDARD package using
the above procedure everytime when we startup the instance? Can I do it or as
you say, it is not necessary. Thanks.
Followup:
ask the developer "why"?

the purpose of an LRU is to age out that which is not used.
if you use it, it'll stay in the cache.
if you do not, it won't.
the original goal of pinning is obviated by the large pool.

there are various invalidations that would cause even pinned things to age out.
Rating: 3
Q on SHARED_POOL_RESERVE_SIZE December 24, 2003
Reviewer: Anil Pant from India
In Orcale 9, setting SHARED_POOL_RESERVE_SIZE by some value the large statements
uses this memory area. Like if I've a have big proc / package proc or func or
plsql block then this is where the statements are stored. Is my understanding
correct ?
If so, I set the value to some X and if my proc / func is bigger than this what
happens ?
Followup:
you pretty much can ignore this init.ora parameter since about version 8.0 and
the introduction of the large_pool.

plsql is paged in in 4,000 byte chunks -- so regardless of the size of the
package/procedure -- it'll not be using the reserved size since it isn't doing
large allocations.

Read the original answer for my recommendations there.
December 24, 2003 -- Thanks for the question regarding "anything between create procedure and create any procedure privilege?", version 9iR2
You Asked
two people(u1 and u2) are working on the same package, but I don't want to grant
create any procedure privilege to them. is there any way u1 can grant the so
called "edit" privilege to u2 only?
right now, what i need to do is grant create any procedure to u2 in order for u2
to modify u1's package, but
in the mean time, u2 can modify anybody else's package.
is there anyway we can fine tune this privilege control?
and we said...
You can in fact use a stored procedure for this.

U1 would create a procedure:

create or replace procedure create_procedure_p( p_str in varchar2 )
as
begin
   execute immediate 'create or replace procedure p ' || p_str;
end;
/
grant execute on create_procedure_p to U2
/

Now, U2 can:

begin
    u1.create_procedure_p(
'( x in out number )
 is
 begin
     x := x+1;
     dbms_output.put_line( ''How is that?'' );
 end;' );
end;
/

Now, it can get nasty with quotes so I recommend something like this.  use "
instead of ' in the string and code:

begin
    u1.create_procedure_p( replace(
'( x in out number )
 is
 begin
     x := x+1;
     dbms_output.put_line( "How is that?" );
 end;',
 '"', '''' ) );
end;
/

using a stored procedure allows you great flexibility in controlling access and
"fined grained security" like this.
Review & Followup

Rating: 2
too complicated December 23, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
This is a workarround, however almost inapplicabe as it is "write only" and
extremply inconvenient

If you read the procedure, you will read it with single quotes, then you have to
convert them to double quotes , and then u2 could create/replace it.

And on top of everything, most developers use some kind of a developement tool,
and they wouldn't be able to use it.

Same thing applies to tables and other object, it would be very nice if Oracle
had a "create any procedure in schema <schema name>" and similar grants.
Followup:
it would be very nice if we could anticipate each and every use case, implement
each and every request.  I agree.  wouldn't that be nice.

short of that, this be the only way to do it.  sorry you don't "like it", but
having u1 and u2 work on the same piece of code in the first place seems "fishy"
to me.  I don't see this as being a very common requirement.

Normally, u1 and u2 would use CVS or some source code repository, they would
test in THEIR schema's, they would deploy to U3 on the "shared machine" via some
process.

Having 2 cooks stirring the same soup.  recipe for disaster in the first place.
Rating: 4
Agreed. December 23, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
You are right. It is fishy, and having a version control system is way better
then 2 users modifying the same code.

I guess I was thinking at a lower level, and too much comparing to table
priviliges.
Rating: 4
something fishy December 24, 2003
Reviewer: Dan Clamage from Pittsburgh PA
We had this trouble. Two developers were working on the same package, in SQL
Navigator. Both had extracted the source code from the data dictionary (a common
capability in Nav), both had edited it, both had saved it back to the database.
But the second person overwrote the changes made by the first person. They asked
me how to avoid that. I said, use VSS (our source code control tool), one of you
check it out, only one of you edit it FROM THE SCRIPT. He said, I was afraid
you'd say that. I've seen too many developers bitten on the butt, editing
packages out of the database and saving it back to the database. I ALWAYS work
out of a text file.
Followup:
remember

code is code is code is code.

stored procedures is just "code", no different then C, VB, Java, Cobol.  It is
code and needs to be developed in the same fashion with the same rules.

If you have more then one person mucking with it -- you'll need source code
control just like you would with anything else.
December 24, 2003 Randall -- Thanks for the question regarding "Package Sizes", version 8.1.7/9
You Asked
I have a question regarding packages.  Is it better to have fewer larger
packages or more smaller ones.  And when is a Package two large?  And how can I
check them during the development stage.
I found an article on metalink but it looked outdated as it went up to Oracle
8.1.3.

Also how do procedures in packages marked as private differ from procedures that
are not declared in the package header?

If you could also point me to some documents, urls, or books that thoroughly
cover developing pl/sql applications using packages.

Thanks for your time.
and we said...
Packages should be logical collections of code.

I do not measure what should and should not be in a package based on size but
rather by content.

If you find your packages getting large, you might ask yourself "are all of
these routines *really* related in some way or am I just putting all of my eggs
into one basket".

So -- my suggestion is -- use the same technique you would for writing good
modular C++ or Java code.  Me, I find my code comes down to a couple of dozen (2
or 3 at most) routines that are logically related.  Each ROUTINE itself fits on
a screen when done properly (that means I can see line 1 of the routine on the
top of my screen and the last line of code on the bottom without having to page
up or down.  Thats about 60/70 lines of code -- a printed page -- per routine).
I find my code is 1-3000 lines max in a package.

Procedures in a package body, not in the spec, are just not callable from
outside of the package -- thats the only difference.

Any book on writing good software - thats what you want.  PLSQL coding isn't
that much different from C, C++, Java, VB or anything else.  It is a pretty rich
3gl that allows you to write good modular reusable code (gasp -- modular code,
reusable code -- even without a "new" operator!  I know, "subroutines" are out
of vogue but hey....)

Feurstein has some books on this topic for plsql directly, I don't agree with
100% of what he says but they are excellent starting points.
Review & Followup

Rating: 5
Packages and memory December 09, 2002
Reviewer: sujata from germany
Hi Tom,
I have seen some existing code at my job..where for example
all the functions for an application were in a package (may be 100 functions).
Then there are some packages that have a logical theme going, for example all
the employee related procedures and functions are in one package, all the
projects related procedures and functions in another package.
I have had this discussion with other developers about should packages be
logically divided (employee package, project package etc.) or in some random
order..for example all the selects in one package, all the updates in another
etc.
Is it true that when a user access a packaged procedure..the whole package is
loaded into the memory?
and it stays in the memory for other users to access.
Then it will make more sense to have all the employee stuff in one package, all
the projects procedures and functions in another, then what about the functions
that are accessed by both employee module and project module..
I am at a point where I need to start a fresh application design (backend
procedures, functions) and I am confused about which design to go with..and if I
go with and why?
I know only one thing for sure that I do want to go with packages (from what I
have read)..instead of stand alone procedures and functions.
Could you please shed some light on it. discuss the pros and cons of each
design.

Thanks.
Followup:
You shouldn't be worried about physical things like "memory" here (its all in
the shared pool -- you are going to be using it all pretty much, it is stored in
little 4k chunks).

Packages are primarily a programming device, use them as such.  They are a
programming device in support of modularization and encapsulation.

That they have great side effects -- like breaking the dependency chain, is an
excellent reason to use them but the primary reason is as a programming device.

You should group functions together because they LOGICALLY belong together.
Randomly -- that would be silly.  All selects -- doesn't seem to make sense.
Ahh - but to put all EMPLOYEE functionality together, and all PROJECT
functionality -- that is making great programming sense.

Use it as a programming tool.  If you do something with the employee package,
the first guy will load it up (and it'll happen so fast they won't even know
they did it) and every sussequent user will benefit from the fact they loaded it
up.  If you use it -it'll stay loaded.  If not, it'll get unloaded if and when
we need the memory.  If you group functions together LOGICALLY by related
functionality -- the odds are they will all get used, so the argument of "but
it'll load 5 bytes more then I personally need" just doesn't cut it.
Rating: 5
Our database version is 8.1.7.4.0 December 09, 2002
Reviewer: sujata from germany
Thanks
Followup:
and this applies universally....
Rating: 4
Explain benefit of Pkg to a C guy January 28, 2003
Reviewer: robert from PA
Tom,
I am trying to "convert" a C-guy whose pl/sql development
experience has been limited to a routine of copy-paste-modify non-modular, long
standalone procedures.
I want to show him the beauty and logic of pkg.

As best as I can remember from my C class, there is no such notion as "Object".
Is there an analogy I can draw that underlines some sort of similarities between
pkg benefits (modularization, re-use, encapsulation) to the same approach in C
coding ?

Thanks
Followup:
Hey -- I'm a C guy, been doing C much longer then PLSQL...

C++ has an object construct.

A C struct is very much like an object - heck, you can have pointers to
functions and everything (the first C++ compilers were really just precompilers
that spit out -- C!)

The main benefits of PLSQL over C (from a C coder)

o tighter integration with the database itself.  Tell me how many lines of C
code would it take to do:

  for x in (select * from all_objects )
  loop
     ....
  end loop;

??

o nice -- very very very nice -- dependency tracking.  I ask the question "hey,
who is using this table".  C guy goes "I don't know", plsql guy goes

  select * from all_dependencies where referenced_name = 'T' and referenced_type
= 'TABLE' and referenced_owner = 'FOO';

o easier maintenance of code -- add a column to a table and:

   for x in ( select * from t ) .....

   declare
      l_rec t%rowtype;
   begin
       ......

still work.  Just have a table and:

   declare
       x t.y%type;
   begin

does the work for you -- someone alters T and makes Y bigger -- no big deal, it
just fixes itself.

o faster -- less network round trips

o even more PORTABLE then C itself.  C is pretty portable but plsql -- well, it
is exactly the same on all Oracle ports and that is alot...

Just like pro*c is 10 times faster then OCI to code with, PLSQL is 10 times
faster then Pro*c to code with.  Will there be times you need C -- sometimes,
but it is less and less frequent that I drop down to an old fashioned compiler
and write C code.  SQL first, PLSQL second, Java third, C last -- for the
reasons above...
Rating: 4
C and PL/SQL Package January 28, 2003
Reviewer: robert from PA
>> Hey -- I'm a C guy, been doing C much longer then PLSQL...

Hey I know...that's precisely why I'm asking your input.

I am not converting the C-guy from doing C to doing PL/SQL
He is doing PL/SQL...just the wrong, dumb way -- stand-alone procedures only --
there is a bit of FUD factor with him on packages.

The thing is that I want to point to something, some technique in good C coding
that is similar - in concept - to writing plsql packages that result in
modularization, encapsulation, the goodies...and say "hey you'd do this in C,
right ?...well this is just like creating plsql packages..." -- Hope that will
help him see the "light" right away.

You did mention above about pointers to functions...ah...yes that brings back
some (painful) memories...I will try to draw the similarity that concept.
Followup:
Ask him if he ever uses static functions.  If he says "no", then it is useless
-- he is "global man"....  nothing will convince him then.

If he says yes -- then tell him "packages give you static functions, you can
hide them just like you can in C, no name clashes"...

Pointers to functions are so cool...  PL/1 -- my first language, you always
remember your first -- had them on the mainframe.  Entry Variables they were
called.  Coolest thing since sliced bread -- almost wish plsql had them ;)
Rating: 5
maximum package size December 22, 2003
Reviewer: A reader
Hi

What is the maximum size of a pl/sql package?

We have some big packages (the procedures and functions are however related) and
our senior dba wants to split them up because he says this is faster and wont
fragment the memory.

I do not agree.

Another question, if a package has 10 procedures, when we call one of them is
the whole package loaded? Or just the package specification and the
corresponding procedure?

Thank you
Followup:
there isn't one really -- they can get quite enormous.  there are limits, but
they are based on the complexity (nesting) of the code.  You can have a small
package bomb on the compile (in older releases, not so much in 8i and up)
whereas a much longer package goes no problem.

PLSQL is already paged in in 4k chunks.  It matters not if you have 1000k of
code or 10 100k pieces of code, you'll still have 1000/4 chunks of memory being
used.

Tell the DBA to scientifically prove their point and give you metrics that are
convincing enough to break your code up (they cannot, they do not exist)

The code is "paged" as needed.  Basically, it doesn't really matter here as
SOMEONE is going to load the code.
Rating: 5
Loading the packages December 22, 2003
Reviewer: A reader
Hi

Are you saying that procedures and functions of a package is loaded to the
shared pool as they are called and not all loaded at the same time :-?
Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4974546899844
Rating: 3
large packages December 24, 2003
Reviewer: Dan Clamage from Pittsburgh PA
For one app, I needed to generate a package body, based on the desired
characteristics determined by a Modeler person (actually a marketing person with
some math understanding). These characteristics only changed infrequiently, so
generating the package body was a suitable solution. There was a data structure
hierarchy, so that adding a higher dimension generated more code. I successfully
generated a package with about 75,000 lines of code. I think the compiled size
was around 300K. No problems.

I've been programming in C on or off for 18 years. Packages provide significant
additional features that stand-alone procedures don't have (encapsulation,
information hiding, overloading, to name a few). Tell "Mr. Global" that packages
are a standard in your shop, so he better get used to them. There are the rare
cases where you do want stand-alone, but it's not that often I use them anymore.

Packages are really just containers, like a basket.

One big problem with trying to break up a large package is the namespace. All
those references to packaged subroutines that will have to be edited. I wish
PL/SQL had a namespace localization feature like Java and C# has.

Large packages are also harder to maintain. It's easy to get "lost in the
sauce". They're less likely to reuse common code (I've seen the same procedure
copy/pasted into 10 different packages).

Once you start thinking in terms of building lots of small, focused packages,
you'll have an easier time working and managing them. On my latest project, I
have one package for just SQL on three very closely related tables; one for just
DML on said tables; several for just "business rules" for various conceptual
levels. My packages have names like company_qry, company_dml, company_bus,
extract_bus, rt_bus. I know exactly what kinds of routines are stored in each.

One more recommendation -- don't build more procedures than you actually have a
need for. Don't go trying to encapsulate every query you think someone *might*
need. If they need it down the road, they can always add it to the appropriate
package without breaking other stuff.
Followup:
I don't think large packages lead to "less likely to reuse common code".

That is like saying a runny nose causes a cold.  You have the symptom and cause
backwards if you ask me.  It is because the coders cut and paste INSTEAD of
reusing that you get large packages.  It is because they haven't had to maintain
a system for the long haul.  It is because they haven't learned yet (it'll come
back to bite them in five years time).  It is a learning thing -- something that
must be taught to them (whatever happened to modular design -- the code I see
getting pumped out somedays just frankly scares me to death.  no one seems to be
taught HOW to code anymore -- they are taught languages and features -- but not
common sense, modularity, thinking ahead, designing for change, rant rant
rant)....

But I agree with you pretty much 100% there....
Rating: 4
Large packages December 26, 2003
Reviewer: Dan Clamage from Pittsburgh PA
>They're less likely to reuse common code
Yeah I didn't say that clearly. What I meant was, if you're shoving everything
into your very application-specific package, and you've written something that
could actually be general enough to be used in other applications, nobody is
going to bother referencing your package for that one tidbit. I was thinking
back to a really large package I wrote that really should've been broken up into
several (at least). I did have business logic that I'd correctly encapsulated;
but improperly struck in the application package itself, reducing considerably
the likelihood that another programmer would reuse (call) it. I missed an
opportunity to produce something that would be reusable. I wasn't thinking
"modular" at the package level.
December 24, 2003 ramesh -- Thanks for the question regarding "extract DDL", version 8.0.5
You Asked
I would like to extract DDL of indexes to store it outside the
database. The purpose of this effort is to save the DDL to use
it on fly when we need. I could use export/import tool but it
requires editing when we want to run the text from SQL. I am
planning to write a procedure to do this. Please advise.
and we said...
Don't write a procedure, use exp/imp.

exp userid=/ owner=some_schema
imp userid=/ indexfile=foo.sql

that'll get you the ddl in a format that requires no editing.
Review & Followup

Rating: 5
Script for extracting DDL October 29, 2001
Reviewer: Andre Nasser from Rio de Janeiro, Brazil
This is a script I coded to help me get the DDL for my database in order to
carry out a "reverse engineering". The problem with EXP is that you cannot
format the ouput. You are stuck to the .dmp file's format. If I want something
different, I have to write the code myself. Since I did not want all clauses to
show up at this time, I programmed this.

Please note that this is a very basic script that can HELP you develop your own
DDL commands.

It works only for tables, PKs, FKs, indexes and sequences, because that is what
I need now. You can add your own clauses and specific stuff, but I believe it's
a good starting point.

------------------
-- Created by: Andre Whittick Nasser, OCP
-- Email: andre_nasser@yahoo.com.br
-- Website: www.geocities.com/andre_nasser
-- Date: Oct 24, 2001
-- Description: Reverse Engineer your existing schema objects
------------------

set termout off
set feedback off
set serveroutput on size 100000
spool schema.sql

begin
dbms_output.put_line('--');
dbms_output.put_line('-- DROP TABLES --');
dbms_output.put_line('--');
    for rt in (select tname from tab order by tname) loop
        dbms_output.put_line('DROP TABLE '||rt.tname||' CASCADE CONSTRAINTS;');
    end loop;
end;
/

declare
    v_tname  varchar2(30);
    v_cname  char(32);
    v_type     char(20);
    v_null   varchar2(10);
    v_maxcol number;
    v_virg     varchar2(1);
begin
dbms_output.put_line('--');
dbms_output.put_line('-- CREATE TABLES --');
dbms_output.put_line('--');
    for rt in (select table_name from user_tables order by 1) loop
        v_tname:=rt.table_name;
        v_virg:=',';
        dbms_output.put_line('CREATE TABLE '||v_tname||' (');
        for rc in (select table_name,column_name,data_type,data_length,
                            data_precision,data_scale,nullable,column_id
                from user_tab_columns tc
                where tc.table_name=rt.table_name
                order by table_name,column_id) loop
                    v_cname:=rc.column_name;
                    if rc.data_type='VARCHAR2' then
                        v_type:='VARCHAR2('||rc.data_length||')';
                    elsif rc.data_type='NUMBER' and rc.data_precision is null
and
                                         rc.data_scale=0 then
                        v_type:='INTEGER';
                    elsif rc.data_type='NUMBER' and rc.data_precision is null
and
                                     rc.data_scale is null then
                        v_type:='NUMBER';
                    elsif rc.data_type='NUMBER' and rc.data_scale='0' then
                        v_type:='NUMBER('||rc.data_precision||')';
                    elsif rc.data_type='NUMBER' and rc.data_scale<>'0' then

v_type:='NUMBER('||rc.data_precision||','||rc.data_scale||')';
                    elsif rc.data_type='CHAR' then
                         v_type:='CHAR('||rc.data_length||')';
                    else v_type:=rc.data_type;
                    end if;

                    if rc.nullable='Y' then
                        v_null:='NULL';
                    else
                        v_null:='NOT NULL';
                    end if;
                    select max(column_id)
                        into v_maxcol
                        from user_tab_columns c
                        where c.table_name=rt.table_name;
                    if rc.column_id=v_maxcol then
                        v_virg:='';
                    end if;
                    dbms_output.put_line (v_cname||v_type||v_null||v_virg);
        end loop;
        dbms_output.put_line(');');
    end loop;
end;
/

declare
    v_virg        varchar2(1);
    v_maxcol    number;
begin
dbms_output.put_line('--');
dbms_output.put_line('-- PRIMARY KEYS --');
dbms_output.put_line('--');
    for rcn in (select table_name,constraint_name
            from user_constraints
            where constraint_type='P'
            order by table_name) loop
        dbms_output.put_line ('ALTER TABLE '||rcn.table_name||' ADD (');
        dbms_output.put_line ('CONSTRAINT '||rcn.constraint_name);
        dbms_output.put_line ('PRIMARY KEY (');
        v_virg:=',';
        for rcl in (select column_name,position
                from user_cons_columns cl
                where cl.constraint_name=rcn.constraint_name
                order by position) loop
            select max(position)
                into v_maxcol
                from user_cons_columns c
                where c.constraint_name=rcn.constraint_name;
            if rcl.position=v_maxcol then
                v_virg:='';
            end if;
            dbms_output.put_line (rcl.column_name||v_virg);
        end loop;
        dbms_output.put_line(')');
        dbms_output.put_line('USING INDEX );');
    end loop;
end;
/

declare
    v_virg        varchar2(1);
    v_maxcol    number;
    v_tname        varchar2(30);
begin
dbms_output.put_line('--');
dbms_output.put_line('-- FOREIGN KEYS --');
dbms_output.put_line('--');
    for rcn in (select table_name,constraint_name,r_constraint_name
            from user_constraints
            where constraint_type='R'
            order by table_name) loop
        dbms_output.put_line ('ALTER TABLE '||rcn.table_name||' ADD (');
        dbms_output.put_line ('CONSTRAINT '||rcn.constraint_name);
        dbms_output.put_line ('FOREIGN KEY (');
        v_virg:=',';
        for rcl in (select column_name,position
                from user_cons_columns cl
                where cl.constraint_name=rcn.constraint_name
                order by position) loop
            select max(position)
                into v_maxcol
                from user_cons_columns c
                where c.constraint_name=rcn.constraint_name;
            if rcl.position=v_maxcol then
                v_virg:='';
            end if;
            dbms_output.put_line (rcl.column_name||v_virg);
        end loop;
        select table_name
            into v_tname
            from user_constraints c
            where c.constraint_name=rcn.r_constraint_name;
        dbms_output.put_line(') REFERENCES '||v_tname||' (');

        select max(position)
                into v_maxcol
                from user_cons_columns c
                where c.constraint_name=rcn.r_constraint_name;
        v_virg:=',';
        select max(position)
            into v_maxcol
            from user_cons_columns c
            where c.constraint_name=rcn.r_constraint_name;
        for rcr in (select column_name,position
                from user_cons_columns cl
                where rcn.r_constraint_name=cl.constraint_name
                order by position) loop
            if rcr.position=v_maxcol then
                v_virg:='';
            end if;
            dbms_output.put_line (rcr.column_name||v_virg);
        end loop;
        dbms_output.put_line(') );');
    end loop;
end;
/

begin
dbms_output.put_line('--');
dbms_output.put_line('-- DROP SEQUENCES --');
dbms_output.put_line('--');
    for rs in (select sequence_name
            from user_sequences
            where sequence_name like 'SQ%'
            order by sequence_name) loop
        dbms_output.put_line('DROP SEQUENCE '||rs.sequence_name||';');
    end loop;
dbms_output.put_line('--');
dbms_output.put_line('-- CREATE SEQUENCES --');
dbms_output.put_line('--');
    for rs in (select sequence_name
            from user_sequences
            where sequence_name like 'SQ%'
            order by sequence_name) loop
        dbms_output.put_line('CREATE SEQUENCE '||rs.sequence_name||' NOCYCLE;');
    end loop;
end;
/

declare
    v_virg        varchar2(1);
    v_maxcol    number;
begin
dbms_output.put_line('--');
dbms_output.put_line('-- INDEXES --');
dbms_output.put_line('--');
    for rid in (select index_name, table_name
            from user_indexes
            where index_name not in (select constraint_name from
user_constraints)
                and index_type<>'LOB'
            order by index_name) loop
        v_virg:=',';
        dbms_output.put_line('CREATE INDEX '||rid.index_name||' ON
'||rid.table_name||' (');
        for rcl in (select column_name,column_position
                from user_ind_columns cl
                where cl.index_name=rid.index_name
                order by column_position) loop
            select max(column_position)
                into v_maxcol
                from user_ind_columns c
                where c.index_name=rid.index_name;
            if rcl.column_position=v_maxcol then
                v_virg:='';
            end if;
            dbms_output.put_line (rcl.column_name||v_virg);
        end loop;
        dbms_output.put_line(');');
    end loop;
end;
/

spool off
set feedback on
set termout on

-- End of script

Tom, please comment !
Followup:
did you try exp/imp as I suggested?  For example, I just issued:

ops$tkyte@ORA717DEV.US.ORACLE.COM> create table t ( x int, y int, z int, primary
key(x,y) );

Table created.

ops$tkyte@ORA717DEV.US.ORACLE.COM> create index t_idx on t(z);

Index created.

and then did the exp/imp trick and have in my file system:

REM  CREATE TABLE "OPS$TKYTE"."T" ("X" NUMBER(*,0), "Y" NUMBER(*,0), "Z"
REM  NUMBER(*,0)) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 LOGGING
REM  STORAGE(INITIAL 16384 NEXT 16384 MINEXTENTS 1 MAXEXTENTS 1024
REM  PCTINCREASE 10 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
REM  TABLESPACE "SYSTEM" ;
REM  ... 0 rows
CONNECT OPS$TKYTE;
CREATE INDEX "OPS$TKYTE"."T_IDX" ON "T" ("Z" ) PCTFREE 10 INITRANS 2
MAXTRANS 255 STORAGE(INITIAL 16384 NEXT 16384 MINEXTENTS 1 MAXEXTENTS 1024
PCTINCREASE 10 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "SYSTEM" LOGGING ;
REM  ALTER TABLE "OPS$TKYTE"."T" ADD PRIMARY KEY ("X", "Y") USING INDEX
REM  PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 16384 NEXT 16384
REM  MINEXTENTS 1 MAXEXTENTS 1024 PCTINCREASE 10 FREELISTS 1 FREELIST
REM  GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "SYSTEM" ENABLE ;

Now, all I need to do is get rid of lines that start with:

REM  ...
CONNECT

and c/REM  // and I've got what I need (without any code).

My comments -- I don't like to write code unless I have to, exp/imp give me what
I need in this case.
Rating: 5
... continuing about the script above October 29, 2001
Reviewer: Andre Whittick Nasser from Rio de Janeiro, Brazil
Tom, the problem is there is too much "garbage" ganerated this way. As I stated,
I would like to get the output MY OWN WAY. That is the BIG AND ONLY reason I
wrote the code. Imagine a schema with lots of objects and clauses and I just
want to get specific (in my case, basic) parts without any editing (imagine I
continually have to send this output to my developing team, which is my case
here). I tried EXP indeed, but it was too "chaotic" for my puposes.

Thanks again !
Rating: 2
Codepages and downgrade October 31, 2001
Reviewer: Sergey Sugak from Russia
You can't just use exp/imp when you need to create schema copy from 8i DB into
7.1 Db for example. You can't use this method easy if your codepages are
different for different databases. Besides this method doesn't give you the
clear _text_ representation of your DDL.
Followup:
Yes you can.  You just use the 7.1 EXP tool against 8i.  And since you don't
care about the data (just getting DDL) character sets don't count.

Consider

$ exp userid=scott/tiger@ora817dev.us.oracle.com owner=scott rows=n

Export: Release 7.1.6.2.0 - Production on Wed Oct 31 07:47:25 2001

Copyright (c) Oracle Corporation 1979, 1994.  All rights reserved.
Connected to: Oracle8i Enterprise Edition Release 8.1.7.2.0 - Production
With the Partitioning option
JServer Release 8.1.7.2.0 - Production
Note: table data (rows) will not be exported
About to export SCOTT's objects ...
. exporting snapshots
. exporting snapshot logs
. exporting job queues
. exporting refresh groups and children
. exporting database links
. exporting sequence numbers
. exporting cluster definitions
. about to export SCOTT's tables ...
. exporting table                              A
. exporting table                          BONUS
. exporting table                              C
. exporting table                   CHAINED_ROWS
. exporting table                           DEPT
. exporting table                          DUMMY
. exporting table                            EMP
. exporting table           JAVA$CLASS$MD5$TABLE
. exporting table                      MLOG$_EMP
. exporting table                              P
. exporting table                       SALGRADE
. exporting table                              T
. exporting table                             T1
. exporting table                             T2
. exporting table                            TMP
. exporting synonyms
. exporting views
. exporting stored procedures
. exporting referential integrity constraints
. exporting triggers
. exporting posttables actions
Export terminated successfully with warnings.

$ imp userid=scott/tiger full=y indexfile=foo.sql

Import: Release 7.1.6.2.0 - Production on Wed Oct 31 07:47:40 2001

Copyright (c) Oracle Corporation 1979, 1994.  All rights reserved.

Connected to: Oracle7 Server Release 7.1.6.2.0 - Production Release
With the distributed, replication and parallel query options
PL/SQL Release 2.1.6.2.0 - Production

Export file created by EXPORT:V07.01.06
Import terminated successfully without warnings.

$ head foo.sql

REM  CREATE TABLE "SCOTT"."A" ("OWNER" VARCHAR2(30) NOT NULL,
REM  "OBJECT_NAME" VARCHAR2(30) NOT NULL, "SUBOBJECT_NAME" VARCHAR2(30),
REM  "OBJECT_ID" NUMBER NOT NULL, "DATA_OBJECT_ID" NUMBER, "OBJECT_TYPE"
REM  VARCHAR2(18), "CREATED" DATE NOT NULL, "LAST_DDL_TIME" DATE NOT NULL,
REM  "TIMESTAMP" VARCHAR2(19), "STATUS" VARCHAR2(7), "TEMPORARY"
REM  VARCHAR2(1), "GENERATED" VARCHAR2(1), "SECONDARY" VARCHAR2(1))
REM  PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 PARALLEL ( DEGREE
REM  DEFAULT) STORAGE(INITIAL 524288 NEXT 524288 MINEXTENTS 1 MAXEXTENTS
REM  2147483645 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1) TABLESPACE

The foo.sql is clear _text_ representation.  I didn't misunderstand anything
here.....
Rating: 3
IMP/EXP is good but... August 08, 2002
Reviewer: Rajesh from md, USA
Tom and others,
I hate to write PL/SQL Code for extracting DDL when good tools like IMP/EXP
exist, but I got into a situation where I have to use a "connect" account for
conversion project, the "connect" account has synonyms to the owner account
where the real tables exist.

I browsed thru metalink but could not find a way to grant the *connect* account
to export / import the owner tables. I cannot also grant the EXP_FULL_DATABASE
because of the other privileges that go with the role.

I cannot give out the owner account password to the conversion team.

The only option that i can think of is writing a PL/SQL
code to generate all the DDLs for constraints and indexes.

One more issue is the connect account should be able to
1)disable constraints, drop indexes
2)load the data
3)create the indexes and enable all disabled constraints
from the connect account.
I know i can write procedures in the owner account, which has dynamic sql to
figure out the constraints and indexes
and do (1), (2) and (3) and grant exec privs to the connect account.

Any workaround for this situation Tom?
If otherwise could you provide me with generic scripts or point me to any if
available?

Thanks.
Followup:
I'm not understanding the problem here???

You have a "connect" account -- it owns NO DATA.

You have another account "ownerAccount" -- it has the real stuff.

You want to export "ownerAccount" apparently.

If the "connect" account is to do all of those things, the connect account
should be the OWNER ACCOUNT.
Rating: 1
IMP/EXP for extracting DDL from another user August 09, 2002
Reviewer: Rajesh from md usa
Tom,
Sorry to keep this thread going. I can post it as a separate question.

The only reason I am doing EXP/IMP from the connect account is to get the DDL &
constraints of the owner account tables.

Here is a simple layout of our system:
--------------------------------------

                Synonyms
Connect Account ----------> Owner Account
                            (Tables)

Is there any way at all to extract DDL and constraints of a table without being
the owner of the table and not having DBA/EXP_FULL_DATABASE privileges ?

Unfortunately the *owner* account passwords cannot be given out to the team that
runs a conversion project. They operate only on the connect account.

Sorry if my question is still not clear.
Followup:
Then, as I said, you'll NEED to do it from the owner account.  Period.

The connect account HAS NO DDL to extract.

The owner account doesn't have to be given to a conversion team, just a DMP file
generated by someone at some point does.  Seems simple enough to me (have a DBA
to a one time export -- you have what you need)

In Oracle9i, you can also:

ops$tkyte@ORA920.US.ORACLE.COM> select dbms_metadata.get_ddl( 'TABLE', 'EMP',
'SCOTT' ) from dual;

DBMS_METADATA.GET_DDL('TABLE','EMP','SCOTT')
--------------------------------------------------------------------------------

  CREATE TABLE "SCOTT"."EMP"
   (    "EMPNO" NUMBER(4,0),
        "ENAME" VARCHAR2(10),
        "JOB" VARCHAR2(9),
        "MGR" NUMBER(4,0),
        "HIREDATE" DATE,
        "SAL" NUMBER(7,2),
        "COMM" NUMBER(7,2),
        "DEPTNO" NUMBER(2,0),
         CONSTRAINT "PK_EMP" PRIMARY KEY ("EMPNO")
  USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
  TABLESPACE "SYSTEM"  ENABLE,
         CONSTRAINT "EMP_FK_EMP" FOREIGN KEY ("MGR")
          REFERENCES "SCOTT"."EMP" ("EMPNO") ENABLE,
         CONSTRAINT "FK_DEPTNO" FOREIGN KEY ("DEPTNO")
          REFERENCES "SCOTT"."DEPT" ("DEPTNO") ENABLE
   ) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
  TABLESPACE "SYSTEM"

Note that I was ops$tkyte, not scott in this case.
Rating: 5
August 09, 2002
Reviewer: A reader
Tom, Can I create DBMS_METADATA in any version prior to Oracle 9i? If not why?
After all it is only PL/SQL code.
Followup:
no,  it is NOT just plsql code.  It is lots of underlying support code in the
database kernel itself.
Rating: 4
Very nice, but.... August 11, 2002
Reviewer: TomazZ from Slovenia
when i try to test this:
SQL> select dbms_metadata.get_ddl('TABLE','BLA') from dual;

DBMS_METADATA.GET_DDL('TABLE','BLA')
-----------------------------------------------------------------

  CREATE TABLE "TOMAZZ"."BLA"
   (    "A" NUMBER,
        "B" VARCHAR2(30)
   ) PCTFREE

SQL>

I get an invalid output script. Have I hit an issue or something? Do I need some
special privileges? I am connected as owner, though.

Second question if I may:
I was trying to get a create table script with querying the dba_tables with
cursor expression on dba_tab_columns. I have a problem getting rid of those
CURSOR STATEMENT headings in sqlplus. Any idea?

Tnx, Tomaz
Followup:
SQL> set long 50000

or some appropriately big size to get the entire statement.

You'll have to use PLSQL to use dbms_output to format the results -- sqlplus is
a rather rudimentary reporting tool.  Not much control in that regards (you have
9i, just use dbms_metadata)
Rating: 4
Dbms_metadata August 11, 2002
Reviewer: shanmugam from Singapore
SELECT DBMS_METADATA.GET_DDL('INDEX',u.index_name) FROM USER_indexes u;
By using the above query I can get all the Indexes.
Is it possible to get all the constraints in the same way?
If yes, How ?
Followup:
constraints come with the tables:

ops$tkyte@ORA9I.WORLD> create table t ( x int constraint check_x check ( x > 0 )
);

Table created.

ops$tkyte@ORA9I.WORLD> select dbms_metadata.get_ddl( 'TABLE', 'T' ) from dual;

DBMS_METADATA.GET_DDL('TABLE','T')
--------------------------------------------------------------------------------

  CREATE TABLE "OPS$TKYTE"."T"
   (    "X" NUMBER(*,0),
         CONSTRAINT "CHECK_X" CHECK ( x > 0 ) ENABLE NOVALIDATE
   ) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 LOGGING
  STORAGE(INITIAL 524288 NEXT 524288 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCRE
ASE 0
  FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "USERS"
Rating: 3
Cleaning index creation script August 12, 2002
Reviewer: Basharat from Dubai
Tom, can we use below mention procedure to remove garbage from foo.sql.
1.Create a table with one column say col1 with enough length to hold one full
line of foo.sql.
2. Load foo.sql into this table except for lines starting with REM and CONNECT
by using when cluase in Control file.
3.Spool and select clo1 from this table into a file and then use this file as
DDL script.
Followup:
what "garbage" is there in foo.sql?

REM statements = comments = they will be ignored by sqlplus.

They are also part of your DDL, removing them would remove information you need.

grep would be easier to remove unwanted stuff, heck, just a text editor would be
easier (global changes)
Rating: 3
ora-01436 August 12, 2002
Reviewer: Cesar Salas from Mexico
Hi Tom,

I have a 8.1.6 DB and I need a exp file for 8.1.5, I was triying with exp73
(7.3.2.3.1) but this error ocurs at View part:

EXP-00008: ORACLE error 1436 encountered
ORA-01436: CONNECT BY loop in user data
EXP-00000: Export terminated unsuccessfully

Can you help me?

Thanks!
Followup:
Please contact support for this one.  Java in the database put in some recursive
dependencies that messed it up.  They may have a workaround for you.

reference bug 1170962, there is an 8162 patch for that.
Rating: 4
August 13, 2002
Reviewer: A reader
Tom, I want to extract the DDL for creating the tablespaces of the database. Our
SAP stress test environment will be refreshed from the installation day backup
soon. But I have to recreate the new locally managed tablespaces we created in
this DB (There are about a hundred of them....). So I need the DDL for "create
tablespace..." and "alter tablespace add datafile..." as it is.

I know that I can create the DDL from the data dictionary. Can this be extracted
using indexfile? I just did a imp with indexfile=y, I don't see the tablespace
DDL here. Thanks for your time.
Followup:
Here is a quick and dirty way:

$ exp userid=/ full=y

Export: Release 8.1.7.3.0 - Production on Tue Aug 13 19:14:13 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.3.0 - Production
With the Partitioning option
JServer Release 8.1.7.3.0 - Production
Export done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set

About to export the entire database ...
. exporting tablespace definitions
. exporting profiles
. exporting user definitions
. exporting roles
. exporting resource costs
. exporting rollback segment definitions
. exporting database links
. exporting sequence numbers
. exporting directory aliases
. exporting context namespaces
. exporting foreign function library names
. exporting object type definitions
. exporting system procedural objects and actions^C <<<<<=== note I did that
EXP-00008: ORACLE error 1013 encountered
ORA-01013: user requested cancel of current operation
EXP-00000: Export terminated unsuccessfully

$ grep 'CREATE TABLESPACE' expdat.dmp
CREATE TABLESPACE "RBS_TS_01" DATAFILE  '/d01/oradata/ora817dev/rbs_ts_01.dbf'
SIZE 31563776 REUSE AUTOEXTEND ON NEXT 1048576  MAXSIZE 2000M EXTENT MANAGEMENT
LOCAL  UNIFORM SIZE 524288 ONLINE PERMANENT
CREATE TABLESPACE "RBS_TS_02" DATAFILE  '/d02/oradata/ora817dev/rbs_ts_02.dbf'
SIZE 157392896 REUSE AUTOEXTEND ON NEXT 1048576  MAXSIZE 2000M EXTENT MANAGEMENT
LOCAL  UNIFORM SIZE 524288 ONLINE PERMANENT
CREATE TABLESPACE "RBS_TS_03" DATAFILE  '/d03/oradata/ora817dev/rbs_ts_03.dbf'
SIZE 26320896 REUSE AUTOEXTEND ON NEXT 1048576  MAXSIZE 2000M EXTENT MANAGEMENT
LOCAL  UNIFORM SIZE 524288 ONLINE PERMANENT
CREATE TABLESPACE "RBS_TS_04" DATAFILE  '/d04/oradata/ora817dev/rbs_ts_04.dbf'
SIZE 48340992 REUSE AUTOEXTEND ON NEXT 1048576  MAXSIZE 2000M EXTENT MANAGEMENT
LOCAL  UNIFORM SIZE 524288 ONLINE PERMANENT
CREATE TABLESPACE "RBS_TS_05" DATAFILE  '/d01/oradata/ora817dev/rbs_ts_05.dbf'
SIZE 26320896 REUSE AUTOEXTEND ON NEXT 1048576  MAXSIZE 2000M EXTENT MANAGEMENT
LOCAL  UNIFORM SIZE 524288 ONLINE PERMANENT
CREATE TABLESPACE "USERS" DATAFILE  '/d04/oradata/ora817dev/users.dbf' SIZE
661127168 REUSE AUTOEXTEND ON NEXT 524288  MAXSIZE 2000M EXTENT MANAGEMENT LOCAL
 UNIFORM SIZE 524288 ONLINE PERMANENT  NOLOGGING
CREATE TABLESPACE "DRSYS" DATAFILE  '/d04/oradata/ora817dev/drsys.dbf' SIZE
36700160 REUSE AUTOEXTEND ON NEXT 524288  MAXSIZE 2000M EXTENT MANAGEMENT LOCAL
UNIFORM SIZE 524288 ONLINE PERMANENT
CREATE TABLESPACE "XXX" DATAFILE  '/tmp/xxx.dbf' SIZE 105373696 REUSE AUTOEXTEND
ON NEXT 104857600  MAXSIZE 32767M DEFAULT  STORAGE(INITIAL 40960 NEXT 40960
MINEXTENTS 1 MAXEXTENTS 505 PCTINCREASE 50) ONLINE PERMANENT
CREATE TABLESPACE "TMP" DATAFILE  '/tmp/tmp.dbf' SIZE 1048576 REUSE EXTENT
MANAGEMENT LOCAL  UNIFORM SIZE 65536
CREATE TABLESPACE "CLAMS" DATAFILE  '/d03/oradata/ora817dev/clams.dbf' SIZE
78118912 REUSE AUTOEXTEND ON NEXT 524288  MAXSIZE 2000M EXTENT MANAGEMENT LOCAL
UNIFORM SIZE 524288

They'll all be there....
Rating: 4
how to extract ddl in 9i November 05, 2002
Reviewer: steve from dallas, orlando
How do we get around the import with show=y where
now instead of REM's we have double quotes and words that wrap like the
following

"CREATE UNIQUE INDEX "PKCOURSE" ON "COURSE" ("COURSEID" )  PCTFREE 10 INITRA"
 "NS 2 MAXTRANS 255 STORAGE(INITIAL 69632 FREELISTS 1 FREELIST GROUPS 1) TABL"
 "ESPACE "USERS" LOGGING"

I want all the ddl as developers did not use any naming conventions in this db
so I want to go and recreate most of the constraint names.
Followup:
well, use indexfile=t.sql instead of show=y that is a very editable file.
fairly trivial to remove REM on the beginning of each line.

Or, use dbms_metadata.get_ddl

OR best yet!!!, in 9iR2 (only r2 and up, not r1, r2) -- just rename the
constraint:

ops$tkyte@ORA920.LOCALHOST> drop table t;

Table dropped.

ops$tkyte@ORA920.LOCALHOST> create table t ( x int primary key );

Table created.

ops$tkyte@ORA920.LOCALHOST> column constraint_name new_val C;
ops$tkyte@ORA920.LOCALHOST> select constraint_name from user_constraints;

CONSTRAINT_NAME
------------------------------
SYS_C003271

ops$tkyte@ORA920.LOCALHOST> alter table t rename constraint &c to t_pk;
old   1: alter table t rename constraint &c to t_pk
new   1: alter table t rename constraint SYS_C003271 to t_pk

Table altered.

ops$tkyte@ORA920.LOCALHOST> select constraint_name from user_constraints;

CONSTRAINT_NAME
------------------------------
T_PK

ops$tkyte@ORA920.LOCALHOST>
Rating: 3
November 06, 2002
Reviewer: Sagi from India
Hi Tom,

I have a general Doubt. Normally the validations can be done using different
ways i.e.

a) Using the CONSTRAINTS.
b) Using Triggers or
c) Packages

Why 'a' is faster when compared to the others. For example say we have a FOREIGN
KEY. Still internally something like this would be done

While inserting into EMP .....

Select count(*) from DEPT where deptno = <value> that is being inserted in EMP
table.....

Still these routines need to be parsed etc etc. They how come they work faster.
Can you tell about the depth of this. Is it something related to Kernel. I head
once someone saying that CONSTRAINTS work at Kernel level and therefore they are
faster.

Anticipating your valuable explanation.

Regards,
Sagi
Followup:
constraints = builtin code = optimized to do exactly one thing = as fast as you
can get.

triggers = you write it = does anything = generic = not as fast as you can get
if you are specific.

constraints do one thing, they are implemented to "just do that".
triggers are user code, they do anything, they are interpreted code, they cannot
do it as fast.

Also -- it is virtually impossible for YOU to write a foreign key check properly
using triggers.  If you want -- you can give it a try, post it here and I'll
tell you why your code is wrong ;)   You need to use declaritive constraints to
get it right as well as get it fast (and further -- why would you consider
writing tons of code in a trigger when "references" does all of that for you?)
Rating: 3
November 06, 2002
Reviewer: Sagi from India
Hi Tom,

Thanx for your valuable explanation.

I also do preferr constraints because its faster and easy. As you very well
mentioned that even if we right it might not be that accurate and optimized. All
I wanted to know why? Anything to do with Oracle Kernel or so.

Regards,
Sagi
Rating: 3
How to find table order to load data from flat files? November 06, 2002
Reviewer: Rishi from India
This may be a bit irrelevant in the context of ongoing discussion
but I have a query about loading data in various tables in
a database.

Assume that I have few flat files each representing new data to be inserted
into a table.
For eg I have two files emp.txt and dept.txt for data to be loaded into
EMP and DEPT tables respectively.

Now if we load data in the following order
1.emp.txt into EMP table
2.dept.txt into DEPT table

Then we might get an error
due to Foreign Key constraint on
EMP table(EMP.DEPTNO = DEPT.DEPTNO)

Whereas if we load data
1.dept.txt into DEPT table
2.emp.txt into EMP table

Then we may not encounter the above error as we are loading data in the master
table
first and then in the detail table.

So my question is how do we determine the correct order of tables
while loading data from flat files so that number of rows rejected
is minimum as in a real system we have 100s of tables.
Followup:
and you have no documentation on said system?  no er's?  no pictures?

me, I would disable the constraints...
loaded in any order I saw fit...
enable the constraints... (using exceptions into to catch bad records)
Rating: 5
Export from differ user March 19, 2003
Reviewer: Thomas from CA
Say I export the scott user data using sys user how do I import back using sys
user.
ie. I will use owner in exp but how do i re import back the scott user.
Followup:
Just run imp, it'll put the objects back into SCOTT all by itself.

use imp .... show=y to SEE what it'll do.
Rating: 5
March 19, 2003
Reviewer: Thomas from CA
I am sorry, I was not clear, my question was how to import using an
indexfile=filename option from 2 different users
Followup:
well, that is in fact totally different from above -- however, I don't
understand what you mean.

but if you have a full=y dmp file, and you would like the indexfile for usera
and userb, then:

imp userid=/ 'fromuser=(scott,ops$tkyte)' indexfile=foo

does that.
Rating: 4
problem! March 19, 2003
Reviewer: Chandru from Harlingen, TX
Tom, could you please help why I am not getting (pl see below) create tablespace
definition. Thanks.

[oracle@tux /home]$ uname -a
Linux tux 2.2.14-5.0 #1 Wed Oct 31 17:07:11 EST 2001 i?86 unknown

[oracle@tux /home]$ exp id/pwd full=y

Export: Release 8.0.5.1.0 - Production on Wed Mar 19 19:50:11 2003

(c) Copyright 1998 Oracle Corporation.  All rights reserved.

Connected to: Oracle8 Enterprise Edition Release 8.0.5.1.0 - Production
With the Partitioning and Objects options
PL/SQL Release 8.0.5.1.0 - Production
Export done in US7ASCII character set and US7ASCII NCHAR character set

About to export the entire database ...
. exporting tablespace definitions
. exporting profiles
. exporting user definitions
. exporting roles
. exporting resource costs
. exporting rollback segment definitions
. exporting database links
. exporting sequence numbers
. exporting directory aliases
. exporting foreign function library names
. exporting object type definitions
. exporting cluster definitions
EXP-00008: ORACLE error 1013 encountered
ORA-01013: user requested cancel of current operation
EXP-00000: Export terminated unsuccessfully

[oracle@tux /home]$ grep 'CREATE TABLESPACE' expdat.dmp
Binary file expdat.dmp matches
Followup:
it is the grep command -- it realizes "hey, binary file, special characters,
lets just tell him we found something"

[tkyte@tkyte-pc tkyte]$ grep 'CREATE TABLESPACE' expdat.dmp
Binary file expdat.dmp matches

[tkyte@tkyte-pc tkyte]$ grep -a 'CREATE TABLESPACE' expdat.dmp
CREATE TABLESPACE "CWMLITE" BLOCKSIZE 8192 DATAFILE
'/usr/oracle/ora920/oradata/ora920/cwmlite01.dbf' SIZE 20971520 REUSE AUTOEXTEND
ON NEXT 655360  MAXSIZE 32767M EXTENT MANAGEMENT LOCAL  AUTOALLOCATE  ONLINE
PERMANENT  NOLOGGING SEGMENT SPACE MANAGEMENT AUTO
....
Rating: 5
Thank you March 21, 2003
Reviewer: Chandru from Harlingen, TX
Rating: 5
March 25, 2003
Reviewer: Andrew Olsen from SLC Utah
Tom, you are the Master!
Rating: 5
question regarding dbms_metadata May 05, 2003
Reviewer: A reader
Hi

I have a query regarding running functions in a SQL Query, when we deal with
procedures we can say

exec PROCA(PAR1 => 'LAS', PAR2 => 'LOS') etc

however when I tried with dbms_metadata

select
  dbms_metadata.get_ddl(OBJECT_TYPE => 'TABLE', NAME => TABLE_NAME, SCHEMA =>
'HR')
from dba_tables where owner='HR'

I get ORA-00907: missing right parenthesis....

:-?
Followup:
you cannot use named notation in SQL, only positional is supported.

it is not a dbms_metadata issue -- but rather a requirement imposed by calling
plsql from sql -- only positional notation may be used.
Rating: 4
dbms_metadata error May 05, 2003
Reviewer: A reader
Hi again

forgot to say, when I run the query as

select
    dbms_metadata.get_ddl('TABLE', TABLE_NAME, 'HR')
  from dba_tables where owner='HR';
ERROR:
ORA-19206: Invalid value for query or REF CURSOR parameter
ORA-06512: at "SYS.DBMS_XMLGEN", line 83
ORA-06512: at "SYS.DBMS_METADATA", line 345
ORA-06512: at "SYS.DBMS_METADATA", line 410
ORA-06512: at "SYS.DBMS_METADATA", line 449
ORA-06512: at "SYS.DBMS_METADATA", line 615
ORA-06512: at "SYS.DBMS_METADATA", line 1221
ORA-06512: at line 1

I dont understand these errors :-((((
Followup:
I'll have to guess lots since you don't provide much info.

you are trying to generate DDL for objects you are not permitted to see.
unfortunately, the wrong error is being thrown.

Use a block like this:

a@ORA920> declare
  2      l_clob clob;
  3  begin
  4      for x in ( select * from sys.dba_tables where owner = 'SCOTT' )
  5      loop
  6          begin
  7              dbms_output.put_line
  8              ( dbms_lob.substr(
  9                   dbms_metadata.get_ddl( 'TABLE', x.table_name, 'SCOTT' ),
 10                   250, 1 ) );
 11          exception
 12              when others then
 13                  dbms_output.put_line( 'Error on ' || x.table_name );
 14          end;
 15      end loop;
 16  end;
 17  /
Error on BONUS
Error on DEPT
Error on DUMMY
Error on EMP
Error on SALGRADE

PL/SQL procedure successfully completed.
Rating: 5
ORA-06512 May 05, 2003
Reviewer: A reader
Hi

I gt ORA-06512 because it tried to extract an IOT DDL, which seems not
supported!

Thx!
Rating: 4
Create TYPE DDL generation July 08, 2003
Reviewer: Prudent from CA
Ok, I can use indexfile option in imp in 8i for tables and indexes, what if I
need create type DDL from the db, how do I get the create type ddl generation
script generated from the database.

Thanks
Followup:
> imp userid=/ full=y show=y

Import: Release 8.1.7.4.0 - Production on Wed Jul 9 10:21:16 2003

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production

Export file created by EXPORT:V08.01.07 via conventional path
import done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set
. importing OPS$TKYTE's objects into OPS$TKYTE
 "CREATE TYPE "FOOBAR" TIMESTAMP '2003-07-09:10:19:04' OID 'C208059C93C36E28E"
 "034080020884DD6'  "
 "as object"
 "( x int, y date, z varchar2(250) );"
Import terminated successfully without warnings.

You will have to edit it up, alternatively, this works well:

> strings expdat.dmp
EXPORT:V08.01.07
DOPS$TKYTE
RUSERS
1024
                                    Wed Jul 9 10:19:25 2003expdat.dmp
#G##
#G##
+00:00
8.1.6
BEGINTYPE "FOOBAR" "C208059C93C36E28E034080020884DD6"
CREATE TYPE
CREATE TYPE "FOOBAR" TIMESTAMP '2003-07-09:10:19:04' OID
'C208059C93C36E28E034080020884DD6'
as object
( x int, y date, z varchar2(250) );
ENDTYPE
ENDTABLE
EXIT
EXIT
Rating: 4
ORA-00972 July 09, 2003
Reviewer: prudent from CA
Tom,

I used strings with dmp file and this is what I got, when I ran it I got
ORA-00972.
CREATE TYPE DAT1 TIMESTAMP '2003-07-09:19:35:38' OID
'C86A1E6565764D6C8A49555B1AEA5B82'
                                                                     *
ERROR at line 1:
ORA-00972: identifier is too long

Is it ok to remove the TIMESTAMP and OID and execute the create type script like
the one below

CREATE TYPE DAT1
is object (
nm varchar2(20),
sl number)
/

PS: I am using 8.1.6 and generating the create type and running it in 9i
database.

Thanks for your input
Followup:
yes, if all you want is the DDL, you would want to remove those
Rating: 5
July 12, 2003
Reviewer: A reader
Rating: 5
long settings worked for me July 22, 2003
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
I was just searching for "set long".

Thanks
Rating: 4
Getting the DDL for sequences via exp/imp August 14, 2003
Reviewer: Ma$e
Hi Tom:

I have some sequences in my schema and they get exported.

However when I create the indexfile, I don't see the ddl for them.
Is it possible to generate DDL for this objects ?

I'm using Oralce 9i Rel 2. Am I missing something ?

Thanks.

Ma$e
Followup:
you can use

strings <dump file> | grep -i SEQUENCE

to get them fairly easily...
Rating: 4
Sometimes Scripting becomes necessary - Stuck with Initial Extents August 29, 2003
Reviewer: GovindanK from CA, USA
Hello Tom

Thanks a million for helping thousands of DBA's. Your column
brings to light many of the features which otherwise one may
not be aware of. Great!!!!.

Now onto the topic. I have a different openion on this issue.
Tell me if i am wrong.

With both exp/imp OR DBMS_METADATA one is stuck with
initial, next extents setting.  Suppose i have to get the
schema defn. from a schema with (say) initial extents as
512K, 1M, 2M, 350M etc and setit up elsewhere, i need
to have that much freespace avbl. This is because the
initial extent clause in the extracted stmt is likely to
override the tablespace default extent settings.
May be one need to use awk/perl to change the values in the
extracted ddl OR run the ddl and then deallocate unused.
(export to be down with rows=n). Is there any option in
tablespace setting which will FORCE objects to be created
with the same settings as that of the tbs?

Am i missing something? Below is an example.

<example>
$ sqlplus system@mefngSQL*Plus: Release 9.2.0.1.0 - Production on Fri Aug 29
11:36:31 2003
Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.
Enter password:
Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
SQL> create tablespace my_lmt datafile '/u005/oradata/oradata/MEFNG/my_lmt.dbf'
  2  size 2M extent management local uniform size 64K;
Tablespace created.
SQL> alter user medev quota 2M on my_lmt;
User altered.
SQL> connect mesaledevown@mefng
Enter password:
Connected.
SQL> show user
USER is "MESALEDEVOWN"
SQL> select tablespace_name, block_size, initial_extent , next_extent from
user_tablespaces;
TABLESPACE_NAME                BLOCK_SIZE INITIAL_EXTENT NEXT_EXTENT
------------------------------ ---------- -------------- -----------
MESALEOWN                            4096         163840      163840
MESALEOWN_IDX                        4096         163840      163840
SQL> show user
USER is "MESALEDEVOWN"
SQL> create table my_table (empno number ,empname varchar2(30)) storage(initial
1M next 512K);
Table created.
SQL> set linesize 132
SQL> select table_name, tablespace_name , initial_extent, next_extent from
user_tables where table_name='MY_TABLE';
TABLE_NAME                     TABLESPACE_NAME                INITIAL_EXTENT
NEXT_EXTENT
------------------------------ ------------------------------ --------------
-----------
MY_TABLE                       MESALEOWN                             1048576
 524288

SQL> quit
Disconnected from Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
$
$ exp mesaledevown@mefng file=my_table.dmp log=my_table.log tables=my_table
rows=n
Export: Release 9.2.0.1.0 - Production on Fri Aug 29 12:01:53 2003
Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.
Password:
Connected to: Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
Export done in US7ASCII character set and AL16UTF16 NCHAR character set
Note: table data (rows) will not be exported
About to export specified tables via Conventional Path ...
. . exporting table                       MY_TABLE
Export terminated successfully without warnings.
$
$
$ sqlplus system@mefng

SQL*Plus: Release 9.2.0.1.0 - Production on Fri Aug 29 12:02:52 2003
Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.
Enter password:
Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
SQL> select username,default_tablespace from dba_users where username='MEDEV';
USERNAME                       DEFAULT_TABLESPACE
------------------------------ ------------------------------
MEDEV                          MEDEV_SMALL
SQL> alter user medev default tablespace my_lmt;
User altered.
SQL> select username,default_tablespace from dba_users where username='MEDEV';
USERNAME                       DEFAULT_TABLESPACE
------------------------------ ------------------------------
MEDEV                          MY_LMT
SQL> quit
Disconnected from Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
SQL> quit
$
$ imp medev@mefng file=my_table.dmp fromuser=mesaledevown touser=medev
indexfile=foo.sql
Import: Release 9.2.0.1.0 - Production on Fri Aug 29 12:04:04 2003
Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.
Password:
Connected to: Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
Export file created by EXPORT:V09.02.00 via conventional path
Warning: the objects were exported by MESALEDEVOWN, not by you
import done in US7ASCII character set and AL16UTF16 NCHAR character set
Import terminated successfully without warnings.
$
$
$ cat foo.sql
REM  CREATE TABLE "MEDEV"."MY_TABLE" ("EMPNO" NUMBER, "EMPNAME"
REM  VARCHAR2(30)) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
REM  STORAGE(INITIAL 1064960 NEXT 524288 MINEXTENTS 1 MAXEXTENTS 249
REM  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
REM  TABLESPACE "MESALEOWN" LOGGING NOCOMPRESS ;
$
$
$ cat foo.sql|sed 's/^REM  //g' |sed 's/MESALEOWN/MY_LMT/g'
CREATE TABLE "MEDEV"."MY_TABLE" ("EMPNO" NUMBER, "EMPNAME"
VARCHAR2(30)) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
STORAGE(INITIAL 1064960 NEXT 524288 MINEXTENTS 1 MAXEXTENTS 249
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "MY_LMT" LOGGING NOCOMPRESS ;
$
$
$ sqlplus medev@mefng
SQL*Plus: Release 9.2.0.1.0 - Production on Fri Aug 29 12:04:52 2003
Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.
Enter password:
Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
SQL> REM the table will be created in the new tbs since sed too care of changing
tbs
SQL> CREATE TABLE "MEDEV"."MY_TABLE" ("EMPNO" NUMBER, "EMPNAME"
  2  VARCHAR2(30)) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
  3  STORAGE(INITIAL 1064960 NEXT 524288 MINEXTENTS 1 MAXEXTENTS 249
  4  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
  5  TABLESPACE "MY_LMT" LOGGING NOCOMPRESS ;
Table created.
SQL> set linesize 132;
SQL> select table_name, tablespace_name , initial_extent, next_extent from
user_tables where table_name='MY_TABLE';
TABLE_NAME                     TABLESPACE_NAME                INITIAL_EXTENT
NEXT_EXTENT
------------------------------ ------------------------------ --------------
-----------
MY_TABLE                       MY_LMT                                1064960
  65536
SQL>

</example>

You find now that the initial extent and next extents
are both gone.  One observes that the LMT setting had
"overwritten" the next extent of the create table stmt.
My question is , 'Is there any way to override the
Initial Extentsetting?'.

Thanks in Advance
Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7490088329317
dbms_metadata doesn't have to get the storage at all.
Rating: 5
I should have checked August 29, 2003
Reviewer: Govindan K from CA USA
Tom

Sorry that i did not look into the manual with more attention and bothered you.

It should be as follows:
begin
dbms_metadata.set_transform_param( DBMS_METADATA.SESSION_TRANSFORM,
'CONSTRAINTS_AS_ALTER', true );
dbms_metadata.set_transform_param( DBMS_METADATA.SESSION_TRANSFORM, 'STORAGE',
false );
dbms_metadata.set_transform_param( DBMS_METADATA.SESSION_TRANSFORM,
'CONSTRAINTS', false );
dbms_metadata.set_transform_param( DBMS_METADATA.SESSION_TRANSFORM,
'REF_CONSTRAINTS', false );
dbms_metadata.set_transform_param( DBMS_METADATA.SESSION_TRANSFORM,
'SQLTERMINATOR', TRUE );
end;
/

SQL> set pagesize 0
SQL> set long 1000000
SQL> select dbms_metadata.get_ddl('TABLE','REGISTRATIONS') x from dual;

  CREATE TABLE "MESALEDEVOWN"."REGISTRATIONS"
   (    "REG_SYS_NO" NUMBER(10,0),
        "REG_REG_NO" VARCHAR2(15),
        ..... column definitions.....
        "REG_ROLLED_FL" VARCHAR2(1)
   ) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
  TABLESPACE "MESALEOWN" ;
SQL>
Followup:
no worries -- there is alot of doc there :)
Rating: 5
How to suppress Schema Owner / Substitute another schema name September 05, 2003
Reviewer: GovindanK from CA, USA
Tom

Thanks a lot to the answers you are providing. "Wealth might
seek one, Wisdom must be sought" it is said. You are an
example.

Now onto the question.

Is there any option in dbms_meta data.set_transform to
suppress the name of the schema "owner." from appearing in
the get_ddl procedure output. I found options to
suppress storage,tbs etc. May be i am missing something.
Also, is there any option to replace the schema name
("OWNER." with "SCHEMA_TO_BE."); This would enable one
to generate ddls from a user with CREATE ANY TABLE privs.

TIA
Followup:
No, there is not currently any such filter.  you would have to use the xml
interface and develop a style sheet to do a custom transformation.
Rating: 5
using get_ddl() for tablesspace info October 11, 2003
Reviewer: Peter Tran from Houston, TX USA
Hi Tom,

Can you help me with the syntax on using dbms_metadata.get_ddl for tablespaces?

SQL> SELECT DBMS_METADATA.GET_DDL('TABLESPACE','FOO_TBS','USER') from dual;
ERROR:
ORA-31600: invalid input value SCHEMA for parameter NAME in function SET_FILTER
ORA-06512: at "SYS.DBMS_SYS_ERROR", line 105
ORA-06512: at "SYS.DBMS_METADATA_INT", line 1980
ORA-06512: at "SYS.DBMS_METADATA_INT", line 3665
ORA-06512: at "SYS.DBMS_METADATA", line 670
ORA-06512: at "SYS.DBMS_METADATA", line 577
ORA-06512: at "SYS.DBMS_METADATA", line 1221
ORA-06512: at line 1

Thanks,
-Peter
Followup:
ops$tkyte@ORA920> select dbms_metadata.get_ddl( 'TABLESPACE', 'USERS' ) from
dual;

DBMS_METADATA.GET_DDL('TABLESPACE','USERS')
--------------------------------------------------------------------------------

  CREATE TABLESPACE "USERS" DATAFILE
  '/usr/oracle/ora920/OraHome1/oradata/ora920/users01.dbf' SIZE 26214400 REUSE
  AUTOEXTEND ON NEXT 1310720 MAXSIZE UNLIMITED
  LOGGING ONLINE PERMANENT BLOCKSIZE 8192
  EXTENT MANAGEMENT LOCAL AUTOALLOCATE SEGMENT SPACE MANAGEMENT AUTO

don't know why you passed 'USER' in -- i doubt that you have a schema named user
and I'm sure it doesn't "own" the FOO_TBS tablespace...  No one really "owns" a
tablespace.
Rating: 5
Forgot the fundamental... October 11, 2003
Reviewer: Peter Tran from Houston, TX USA
"No one really "owns" a tablespace."

I actually knew that, but it totally slip my mind.

Argh...Time to refresh my fundamentals with a pass through the Concept guide.

Thanks again,
-Peter
Rating: 5
Importing PL/SQL code October 14, 2003
Reviewer: Arun Mathur from Marietta,GA
Tom,

You provide scripts to extract code from a certain schema and install them to
another (Chapter 8 from Expert One On One Oracle, pp.337-338).  I tried it in a
development environment, and it works great. Someone on the Metalink forum had a
question on how to import code, and I thought she'd benefit from your scripts.
However, I wanted to check with you first. Since the scripts came from your
book, I'll understand if you'd rather them not be pasted out in the open.

Thanks again for providing such a great site to everyone. It's pleasure being a
"student" of yours.

Take care,
Arun
Followup:
put them anywhere -- they are probably on here somewhere already!
Rating: 3
End of communication channel when executing get_ddl October 23, 2003
Reviewer: Sasa from Belgrade, Serbia
Hi Tom,

I tried your test case :

SELECT DBMS_METADATA.GET_DDL ('TABLE','DUAL','SYS') FROM DUAL;

and got this:

SQL>
SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
PL/SQL Release 9.2.0.4.0 - Production
CORE    9.2.0.3.0       Production
TNS for 32-bit Windows: Version 9.2.0.4.0 - Production
NLSRTL Version 9.2.0.4.0 - Production

SQL>
SQL> SELECT DBMS_METADATA.GET_DDL ('TABLE','DUAL','SYS') FROM DUAL;
ERROR:
ORA-03113: end-of-file on communication channel

Thanks
Followup:
contact support.
Rating: 5
connect problem with imp December 24, 2003
Reviewer: A reader from Boston, MA
Greetings Tom !!
I am huge fan of yours. Thank you.

db -> 9.2.0.2.
Am moving a db from HP to Tru64 platform. So, am doing a full export/import. I
am creating an indexfile(for parallelism) to create them after the import. The
question is, I see "CONNECT SUPPORT;", "CONNECT SYSTEM;" and similar connect
statements but WITHOUT any passwords. And if connected to sqlplus as sysdba and
run this script, wouldn't it complain about the password ?
The destination database was created and all the tablespaces have been created
before doing the full import.
+++++++++++++++++++++++++++++++++++++++++
======exp.sh==========
exp "'sys/test as sysdba'" full=Y file=AST_X.DMP compress=N statistics=NONE
======ind_cr.sh=======
imp "'sys/f0rb1den as sysdba'" file=AST_X.DMP full=Y indexfile=ind_cr.sql
======exp.sh==========
imp "'sys/f0rb1den as sysdba'" file=AST_X.DMP full=Y indexes=N commit=Y ignore=Y
=======
Followup:
sqlplus won't complain -- it'll simply stop and ASK you for the password.

you see -- exp doesn't have access to the password and it would be really bad to
put it in a clear text file.

So, either you edit the file beforehand, add the passwords

OR

you provide them when prompted

OR

you can use my "su.sql" script and change the connect to @su -- see
http://asktom.oracle.com/~tkyte

Rating: 5
thats awesome December 24, 2003
Reviewer: A reader from Boston ,MA
Wow, thats a fantastic script.
Thank you very much !!!
December 24, 2003 Vrishali -- Thanks for the question regarding "pctfree and pctused with regards to LOBS", version 8.1.6.2.0
You Asked
Hi Tom,

We have a table which contains clob field. Intermedia index is
created on clob field. Seperate tablelspace storage is assigned
for storing clob segment in the database.

CREATE TABLE news
 (
  document_number            NUMBER(38) NOT NULL,
  dbfile_number              NUMBER(20) NOT NULL,
  xml_object                 CLOB default empty_clob(),
  content_area_code          VARCHAR2(10)
 )
 LOB (xml_object) store as xml_object_seg4
 ( tablespace dialog_clob
    storage ( initial   100M
              next 100M
              pctincrease 0)
    chunk 32768  nocache logging
    disable storage in row)
 PCTFREE    5
 INITRANS   1
 MAXTRANS   255
 TABLESPACE dialog_data
 STORAGE   (
      INITIAL     100M
      NEXT        100M
      PCTINCREASE 0
      MINEXTENTS  1
      MAXEXTENTS  unlimited
   )

In above syntax I want to know -

1. What are the pctfree and pctused values for the clob segment
in dialog_clob tablespace? Does it use table's pctfree and
pctincrease value by default?

2. If so i have one more question - we are using java program
for dowloading data into clob field. In short this java program
works as follow -
a.It loads all other data except clob for a single row.
b.select clob from news for update of clob.
c.update clob by using java routine
d. proceed for next row insert.

Since clob is getting updated and not inserted , I feel pctfree
value of table should be high. could you please tell me if it is
right or wrong?

This table contains 6 millions of rows so every inseret follows
an update of clob.

Thanks In Advance
Vrishali
and we said...
1) lobs do not use PCTFREE storage parameter.  LOBS are big
globs of data -- you never update them to "grow" in size, so
reserving a piece of a block for future updates doesn't make
sense for them.  lobs will fill the block.

2) clobs are very different.  You don't really "update" the clob
data, you are writing to a clob file you would write to an OS
file.  ignore pctfree on the lob segment, it is not used.

You are in fact inserting the clob.  You insert empty_clob()
into the table.  That inserts a lob locator.  The lob locator is
a pointer to a lob index.  The lob index is pointer to all of
the "pages" or "chunks" of the lob.  the lob is managed much in
the same way the operating system manages a file from then on
in.  You can "seek" around in it.  You can "overwrite" a chunk.
You do not update a chunk in the conventional sense.
Review & Followup

Rating: 5
March 15, 2001
Reviewer: vrishali vekhande from NJ, USA
always helpful
Rating: 4
follow-up question in regarding CLOB tablespace December 24, 2003
Reviewer: June from MD
in which case should we separate CLOB stored in tablespace other than the
regular tablespace (for table data)? I have one table to be created with CLOB
column and not knowing if I should create one clob-tablespace. we will store XML
data in it and now the size is not that big (XML file size is less than 10k).
Thanks for any suggestion.
Followup:
I'd just put them in the same tablespace.  I see no need to separate them.
December 24, 2003 Bruno -- Thanks for the question regarding "Complex Queries and Materialized Views", version 9.2.0.1.0
You Asked
Tom,

I'm trying to create a materialized view for a "sort" of embryo DSS in my OLTP
database. In this view I want to cross many financial and sales information.
Also, I'm trying to create a fast refreshable MV, because I already know the
complete refresh takes MUCH time, and I want to try the fast (changes only)
mode.

After creating all MV Logs on the relationed tables, I issuing the following
commands and the error is as follows:

*** CODE START ***

  1  create materialized view mv_cubo_vendas
  2  on prebuilt table refresh fast start with trunc(sysdate) next
trunc(sysdate) + 1
  3  as
  4  (
  5  select ano, mes, dia, filial, pais, sigla_uf, nome_uf, regiao, nome_regiao,
microregiao, nome_microregiao,
  6       cidade, cod_agente, nome_agente, cod_cliente, nome_cliente, produto,
descricao, cod_grupo,
  7       nome_grupo, cod_subgrupo, nome_subgrupo, sum(qtd) qtd,
avg(preco_unitario) preco_medio, sum(icms) icms,
  8       sum(ipi) ipi, sum(iss) iss, sum(icms_substituicao) icms_substituicao,
prazo_recebto_medio, prazo_medio,
  9       atraso_medio, sum(valor) valor, percentual_com_medio, valor_com
 10  from (
 11  select
 12    to_char(notas.dt_emissao, 'YYYY') ano,
 13    to_char(notas.dt_emissao, 'MM') mes,
 14    to_char(notas.dt_emissao, 'DD') dia,
 15    notas.filial,
 16    pais.nome pais, uf.uf sigla_uf, uf.nome nome_uf, regiao.regiao,
 17    regiao.descricao nome_regiao, microregiao.regiao microregiao,
microregiao.descricao nome_microregiao,
 18    cidade.cidade, agente.firma cod_agente, agente.nome nome_agente,
 19    cliente.firma cod_cliente, cliente.nome nome_cliente,
 20    /*notas.dt_emissao,*/ produtos.produto, produtos.descricao,
 21    grupo.grupo cod_grupo, grupo.descricao nome_grupo, subgrupo.grupo
cod_subgrupo,
 22    subgrupo.descricao nome_subgrupo,
 23    itens_nf.qtd qtd, itens_nf.valor_unit preco_unitario,
 24    itens_nf.vl_icms icms, itens_nf.vl_ipi ipi, itens_nf.vl_iss iss,
 25    itens_nf.vl_icms_sub icms_substituicao, /*duplicata.dt_movim,
duplicata.dt_vence, duplicata.dt_baixa,*/
 26    avg(nvl(duplicata.dt_baixa, trunc(sysdate)) - duplicata.dt_movim)
prazo_recebto_medio,
 27    avg(duplicata.dt_vence - duplicata.dt_movim) prazo_medio,
 28    avg(( case when nvl(duplicata.dt_baixa, trunc(sysdate)) <
duplicata.dt_vence then 0 else nvl(duplicata.dt_baixa, trunc(sysdate)) -
duplicata.dt_vence end )) atraso_medio,
 29    itens_nf.valor_unit * itens_nf.qtd_val valor, avg(itens_com.pc_com)
percentual_com_medio, sum(round(itens_dup.valor * itens_com.pc_com / 100, 2))
valor_com,
 30    dense_rank() over (partition by notas.dt_emissao, pais.nome, uf.uf,
microregiao.regiao,
 31                                    cidade.cidade, agente.firma,
cliente.firma, notas.num_nota,
 32                    itens_nf.seq_item
 33                       order by nvl(dup_nota.num_titulo, notas.num_nota),
nvl(dup_nota.seq_titulo, 1) ) ordem
 34  from
 35    cd_firmas cliente,
 36    cd_paises pais,
 37    cd_uf uf,
 38    cd_regioes regiao,
 39    cd_regioes microregiao,
 40    cd_cidades cidade,
 41    cd_firmas agente,
 42    ft_notas notas,
 43    ft_itens_nf itens_nf,
 44    ce_produtos produtos,
 45    ce_grupos grupo,
 46    ce_grupos subgrupo,
 47    fn_orig_dup dup_nota,
 48    fn_ctrec duplicata,
 49    fn_itens_dup itens_dup,
 50    fn_itens_com itens_com,
 51    ft_oper operacao
 52  where
 53    ( cliente.firma=notas.firma ) and
 54    ( pais.pais=cidade.pais ) and
 55    ( uf.uf=cidade.uf ) and
 56    ( uf.pais=cidade.pais ) and
 57    ( regiao.regiao=lib_cniv.cod_nivel(cidade.regiao,
lib_cniv.nivel(cidade.regiao) - 1) ) and
 58    ( microregiao.regiao=cidade.regiao ) and
 59    ( cidade.cod_cidade=cliente.cod_cidade ) and
 60    ( nvl(notas.agente1, 2026501)=agente.firma ) and
 61    ( itens_nf.empresa=notas.empresa ) and
 62    ( itens_nf.filial=notas.filial ) and
 63    ( itens_nf.num_nota=notas.num_nota ) and
 64    ( itens_nf.sr_nota=notas.sr_nota ) and
 65    ( produtos.empresa=itens_nf.empresa ) and
 66    ( produtos.produto=itens_nf.produto ) and
 67    ( grupo.grupo=lib_cniv.cod_nivel(produtos.grupo,
lib_cniv.nivel(produtos.grupo) - 1) ) and
 68    ( grupo.empresa=produtos.empresa ) and
 69    ( subgrupo.empresa=produtos.empresa ) and
 70    ( subgrupo.grupo=produtos.grupo ) and
 71    ( dup_nota.empresa(+)=notas.empresa ) and
 72    ( dup_nota.filial(+)=notas.filial ) and
 73    ( dup_nota.num_nota(+)=notas.num_nota ) and
 74    ( duplicata.empresa(+)=dup_nota.empresa ) and
 75    ( duplicata.filial(+)=dup_nota.filial ) and
 76    ( duplicata.num_titulo(+)=dup_nota.num_titulo ) and
 77    ( duplicata.seq_titulo(+)=dup_nota.seq_titulo ) and
 78    ( itens_dup.empresa(+)=duplicata.empresa ) and
 79    ( itens_dup.filial(+)=duplicata.filial ) and
 80    ( itens_dup.num_titulo(+)=duplicata.num_titulo ) and
 81    ( itens_dup.seq_titulo(+)=duplicata.seq_titulo ) and
 82    ( nvl(itens_dup.produto, itens_nf.produto) = itens_nf.produto ) and
 83    ( itens_com.empresa(+)=itens_dup.empresa ) and
 84    ( itens_com.filial(+)=itens_dup.filial ) and
 85    ( itens_com.num_titulo(+)=itens_dup.num_titulo ) and
 86    ( itens_com.seq_titulo(+)=itens_dup.seq_titulo ) and
 87    ( itens_com.seq_item(+)=itens_dup.seq_item ) and
 88    ( operacao.empresa=notas.empresa ) and
 89    ( operacao.cod_oper=notas.cod_oper ) and
 90    --( notas.dt_emissao between trunc(sysdate) - 1 and trunc(sysdate) ) and
 91    ( operacao.f_ven = 'S' ) and
 92    ( notas.status <> 'C' )
 93  group by notas.dt_emissao, (to_char(notas.dt_emissao, 'YYYY'),
 94    to_char(notas.dt_emissao, 'MM'),
 95    to_char(notas.dt_emissao, 'DD'),
 96    notas.filial, pais.nome, uf.uf, uf.nome, regiao.regiao,
 97    regiao.descricao, microregiao.regiao, microregiao.descricao,
 98    cidade.cidade, agente.firma, agente.nome,
 99    cliente.firma, cliente.nome, notas.num_nota, itens_nf.seq_item,
100    dup_nota.num_titulo, dup_nota.seq_titulo,
101    /*notas.dt_emissao,*/ produtos.produto, produtos.descricao,
102    grupo.grupo, grupo.descricao, subgrupo.grupo,
103    subgrupo.descricao, itens_nf.qtd, itens_nf.valor_unit,
104    itens_nf.qtd_val, itens_nf.vl_icms, itens_nf.vl_ipi,
105    itens_nf.vl_iss, itens_nf.vl_icms_sub)
106  )
107  where ordem = 1
108  group by ano, mes, dia, filial, pais, sigla_uf, nome_uf, regiao,
nome_regiao, microregiao, nome_microregiao,
109       cidade, cod_agente, nome_agente, cod_cliente, nome_cliente, produto,
descricao, cod_grupo,
110       nome_grupo, cod_subgrupo, nome_subgrupo, prazo_recebto_medio,
prazo_medio, atraso_medio,
111       percentual_com_medio, valor_com
112* )
113  /
  cd_firmas cliente,
  *
ERRO na linha 35:
ORA-12015: cannot create a fast refresh materialized view from a complex query

SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Release 9.2.0.1.0 - Production
PL/SQL Release 9.2.0.1.0 - Production
CORE    9.2.0.1.0       Production
TNS for Linux: Version 9.2.0.1.0 - Production
NLSRTL Version 9.2.0.1.0 - Production

SQL>

*** CODE END ***

Ok, it seems complex to me  ;)
But want I really want to know ( and I didn't found in your site nor at OTN
Documentation) is the "definition" of what would be a Complex query for Oracle,
what I should avoid when writting MVs to make them fast refreshable.

Thank you a lot, Tom !
and we said...
sorry so long.  In this case, it is most likely the outer joins and the multiple
tables.  Also, the joins PLUS aggregates.

we can fast refresh

single table aggregates.

multi-table joins.

outer joins when the table outer joined to has a primary key on the join columns
(a 1 to 1 optional relationship, not a 1 to many).

you'll find:
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96520/mv.htm#721
to be a very good overview of MV's.  specifically:
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96520/mv.htm#40960
covers the "fast refresh" options -- which i prefer to call "incremental
refresh" actually, since the fast refresh can many times be much much slower
then a complete refresh! (it is sometimes much harder to "maintain" then to just
"rebuild")
Review & Followup

Rating: 5
Thankyou ! December 30, 2003
Reviewer: Bruno Di Rei Araujo from Uberlândia, MG, Brazil
December 24, 2003 Venugopal -- Thanks for the question regarding "Logical Reads vs Physical Reads", version 8.1.7
You Asked
Tom,
    Have heard DBA's mention everytime to keep the logical reads low. But, when
asked why, they can't come with an answer. When a block is requested by a query,
Oracle looks for it in the Buffer Cache and if found, it results in a Logical
read and if it does not find the Block in there it results in a physical read
(disk I/O). I understand that physical I/O is the most expensive component and
finding the block in the buffer will reduce most of the work.

Am i absolutely wrong? If not, why is Logical I/O harmful?

Can you please throw light on this.

regards
and we said...
goto
http://www.hotsos.com/
download the "are you still using cache hit ratios"

and keep an open mind.

the less work we do -- well -- the less work we do... The less LIO's you do, the
less WORK you do.

You have good DBA's -- keep them.

When tuning a query -- I ignore PIO's.  I concentrate 100% on reducing LIO's.
Review & Followup

Rating: 1
Logical Reads vs Physical Reads November 26, 2002
Reviewer: Sitarama Peruvel from India
Tom i am not able to navigate to the website mentioned. Can you please confirm
Followup:
click on it again -- it is a "real" URL, it is a real site -- hotsos.com.  The
link works for me.
Rating: 4
reduce lio - i'm missing something here ? November 26, 2002
Reviewer: Ram from India
please correct me if my understanding is wrong.
LIO - from/to the buffer cache ( not disk ).
PIO - to/from the disk.
correct ?

in many of the threads on tuning i notice that you mention, the less the number
of PIO as well as LIO the better. i can understand the 'physical' part of it but
not the logical.
the query has to get the results and it has to do as much work as is necessary
to get it, isnt it ? so at least that much of LIO is a must. how can we reduce
that ?
Followup:
LIO = logical = buffer cache.  LIO *may* have incurred a PIO in order to get
into the cache in the first place.

PIO = physical = disk read

Consider this example.  Pretend I am one of those people (of which there are
many) that believe "if my query ain't using an index, it is broken".  So, I have
a table and query:

big_table@ORA817DEV.US.ORACLE.COM> select object_id, object_name from big_table
order by object_id
  2  /

1576192 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=9086 Card=1576192
Bytes=47285760)
   1    0   SORT (ORDER BY) (Cost=9086 Card=1576192 Bytes=47285760)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=658 Card=1576192
Bytes=47285760)

Statistics
----------------------------------------------------------
          0  recursive calls
        117  db block gets
      22861  consistent gets
             physical reads
          0  redo size
   63003739  bytes sent via SQL*Net to client
   11664194  bytes received via SQL*Net from client
     105081  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
    1576192  rows processed

Now, being that person who ferverently believes "full scan = evil, index =
goodness", I "fix" the optimizer via:

big_table@ORA817DEV.US.ORACLE.COM> select /*+ index( big_table big_table_idx2 )
*/ object_id, object_name
  2  from big_table order by object_id
  3  /

1576192 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1579626 Card=1576192
Bytes=47285760)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=1579626
Card=1576192 Bytes=47285760)
   2    1     INDEX (FULL SCAN) OF 'BIG_TABLE_IDX2' (NON-UNIQUE) (Cost=3434
Card=1576192)

Statistics
----------------------------------------------------------
          8  recursive calls
          0  db block gets
    1684470  consistent gets
             physical reads
          0  redo size
   63003739  bytes sent via SQL*Net to client
   11664194  bytes received via SQL*Net from client
     105081  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
    1576192  rows processe

Now, same answer -- same number of rows -- which is better?  (yes, I purposely
removed PIO's -- assume they are the same).

Each consistent get is a latch, latches = contention. Over abused indexes can be
a killer for scaling.  A runstats of the above showed me:

                                  FULL SCAN   INDEX
LATCH.cache buffers chains          67428    6547789    6480361

6.4 million more cbc latches - ugg.
Rating: 5
pio many times == lio November 27, 2002
Reviewer: ls cheng from madrid, spain
hi Ram  from India

many times although Oracle interpret read as Physical they are actually Logical
I/O from filesystem cache so you cant really say all physical I/O seen from
v$sysstat are PIO

Many SQL statements reads more blocks than necessary, wehn we say we want to
reduce I/O we are not saying we want to reduce the resultset, rather reduce
extra reads. For example reading index blocks when not necessary. LIO uses CPU,
if you are using unix/linux check processes that uses 100% CPU and you will see
they are processes which run massive SQL statements which reads tons of blocks
(very high buffer gets which are LIO)

regards
Rating: 5
November 27, 2002
Reviewer: A reader
Hi Tom

"When tuning a query -- I ignore PIO's.  I concentrate 100% on reducing LIO's. "

Very strange........(For 90% DBA) Through some light on it.

I guess you are doing this for more scalability. Am i right?
Followup:
Scalability and because removing PIO's will come *naturally* by reducing LIO's.

Sure, I can reduce PIO's by increasing the buffer cache.  But have I fixed the
problem?  No, maybe I just made it worse (read that paper I refer to!!!)
Rating: 5
Thanks November 27, 2002
Reviewer: Venugopal from BG
Tom,
    Thanks for the response.

I need more clarity on your example showing table access by index scan and FTS.
Wasn't that a bad example for index hinting because it was a query without any
WHERE clause.

My question is -
Consistent gets of the second query (using index) is alarmingly high. Is it
because it tries to find the index blocks in the buffer cache, and, would the
setting of OPTIMIZER_INDEX_CACHING to a low value (say default value of 0) bring
down the consistent gets in this case?

We shouldn't be using indexes at all in this case...but just curious.

regards
Followup:
where clause or no -- doesn't matter.  Add where object_name = 'FRANK' to the
query if you like...  better yet, add "where object_id > 0".  Here, I'll make it
classic.

Fake question: "whats wrong with the cbo"
tom -- i analyzed my table and believe it or not, the cbo won't use the index.
when I hint to use rule it does... consider:

big_table@ORA817DEV.US.ORACLE.COM> select object_id, object_name from big_table
  2  where object_id > 0 order by object_id;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=15189 Card=1576192
Bytes=33100032)
   1    0   SORT (ORDER BY) (Cost=15189 Card=1576192 Bytes=33100032)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=658 Card=1576192
Bytes=33100032)

big_table@ORA817DEV.US.ORACLE.COM> select /*+ RULE */ object_id, object_name
from big_table
  2  where object_id > 0 order by object_id;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE'
   2    1     INDEX (RANGE SCAN) OF 'BIG_TABLE_IDX2' (NON-UNIQUE)

end of fake question....

some people believe "i created this index, i've already sorted that data, why do
I want to sort it again, darn database won't use the index, aren't those indexes
for sorting after all"

The consistent gets cannot, will not be affected by any of the optimizer_*
settings.  they affect how the optimizer costs things, they have no effect on
how things are actually processed.

My point here is people go nested loops happy on really big things cause "full
table scans are bad" (NOT).  They end up hinting to use indexes or using the RBO
which is "index happy" (index crazy sometimes is more like it) and two things
happen:

o they do tons more LIO's then they should (but they feel DARN GOOD about it
cause their indexes are being used)

o they inflate their silly cache hit ratio (hey, there is a new idea -- a cache
hit ratio ABOVE 95% is a bad thing -- means you are doing too much LIO probably,
you need to work to reduce that).  This inflation of the cache hit ratio (as
they read and re-read and re-read the same blocks over and over and over and
over and over) makes they feel *really* good, like they've done their tuning job
(cause we all know that a LIO is 10,000 times faster then a PIO right -- not,
and not that it EVEN MATTERS)
Rating: 5
November 27, 2002
Reviewer: A reader
Each consistent get is a latch, latches = contention

---Can you elaborate that a little? Thank you.
Followup:
to get a block from the cache, we have to latch (to prevent someone from
modifying the data structures we are currently reading).

A latch is a lock.

Locks are serialization devices

Serialization devices inhibit scalability, the more you use them, the less
concurrency you get.
Rating: 5
Got it November 28, 2002
Reviewer: Ram from india
great explanation !

so the bottom line seems to be - " tune so that you get your results doing ONLY
as much work as is required using MINIMUM possible resources "
and after that ...stop. Dont overtune it :)
Followup:
I would modify that slightly:

DEVELOP so that you get your results......

but yes, well said.
Rating: 3
Why is this so? November 28, 2002
Reviewer: A reader from India
Tom,

Iam just pasting your sentence here.

  "This inflation of the cache hit ratio (as
they read and re-read and re-read the same blocks over and over and over and
over and over)"

Can you please explain why a block is re-read again? Re-read from where, buffer
or disk? If it is buffer, then isn't that the way it is supposed to be. I mean,
if the bock is there in memory then it should be read from there right.
Thousand apologies if iam getting it wrong.

regards
Followup:
Look at my "big_table" example above.

I let the optimizer do what it wants.  It full scans.  It did:

Statistics
----------------------------------------------------------
          0  recursive calls
        117  db block gets
      22861  consistent gets
             physical reads
          0  redo size
....

22,861 consistent gets (LOGICAL IOS).  It read the table from head to tail and
got me the answer.

Now, I forced it to use an index cause *we all know indexes are good, full table
scans are evil* (sarcasm dripping as I type that) and we get:

Statistics
----------------------------------------------------------
          8  recursive calls
          0  db block gets
    1684470  consistent gets
             physical reads
          0  redo size

1,684,470 logical ios!!

why so many more?  Well, it read the first index leaf block.  The first row on
that leaf block said "read block 55 of the table", second row says "read block
100", 3rd = read block 1001, 4th = read block 2134 and so on.  Say there were
500 index entries on that first leaf block. We might have had to of read 500
different database blocks for this first block alone.

Now, we move onto the second leaf block.  It says "read block 2134" -- we'll, we
now need to retrieve block 2134 from the buffer cache again.  We already read it
once on the first index block but here we are back at it again.

We'll read and re-read and re-read the table blocks over and over again because
the index tells us the order of the rows to process -- we do not do the simple,
fast, full scan.

If I do 1.6 million LIO's - that'll make my cache hit ratio go up more then if I
do 22k LIO's.  The end result of this deluded "tuner" who got the query to use
the index

a) It uses an index, we all "know that is good", evil full table scan is gone
b) and look at that!  my cache hit ratio is 99.999% now!!!  After running this
query just once! isn't that fantastic (no, no it is not -- in fact the opposite
is true)

So, maybe we need to reverse the advice on the cache hit ratio.  The higher it
is, the worse your system is performing!!!
Rating: 5
Could you pl explain how logical reads work on index scans? November 29, 2002
Reviewer: Ramakrishna from India
Dear Tom,
  Since this posting is about logical reads, I wanted to clarify one point about
them:
In your book, in the chapter on indexes, you have an example of a Colocated and
a Disorganized table, both of them with the same data and the same index.  A
query that does a range scan on the two shows vastly different results based on
the clustering of the index due to the huge difference in logical reads.  My
question is as follows:
When Oracle does an index scan, does it take one index entry, go to the block
for the rowid, come back, take the next entry, go to the block for the rowid and
so on or does it "batch" a number of consecutive index entries, figure out how
many distinct blocks to visit and get them all at one time?  If Oracle steps
through the index row by row, then should we not get the exact same number of
logical reads regardless of clustering?

thanks
ramakrishna
Followup:
it is a function of your arraysize and the cluster factor.  Here is an example
using the same concept -- but different array sizes to show the difference.  I
stuck with RBO since that thing is so index happy that it'll always using the
index in my example below:

ops$tkyte@ORA920.LOCALHOST> create table colocated ( x int primary key, data
char(255) );

Table created.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> insert into colocated select rownum,
dbms_random.random from all_objects;

29319 rows created.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> create table disorganized ( x int primary key, data
char(255) );

Table created.

ops$tkyte@ORA920.LOCALHOST> insert into disorganized select * from colocated
ORDER BY DATA;

29319 rows created.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> commit;
Commit complete.

ops$tkyte@ORA920.LOCALHOST> set autotrace traceonly

ops$tkyte@ORA920.LOCALHOST> set arraysize 2

ops$tkyte@ORA920.LOCALHOST> select * from colocated where x > 0;

29319 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'COLOCATED'
   2    1     INDEX (RANGE SCAN) OF 'SYS_C003530' (UNIQUE)

Statistics
----------------------------------------------------------
...
      29890  consistent gets
...
      29319  rows processed

ops$tkyte@ORA920.LOCALHOST> select * from disorganized where x > 0;
29319 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'DISORGANIZED'
   2    1     INDEX (RANGE SCAN) OF 'SYS_C003531' (UNIQUE)

Statistics
----------------------------------------------------------
...
      45116  consistent gets
...
      29319  rows processed

See, here since the array size was 2 -- our consistent gets were pretty close
to number of rows for colocated.  Why?  Each array fetch did this:

- get index block (1 LIO)
- get row 1 from the table (1 LIO for block X)
- get row 2 from the table (0 LIO if the second row was on block X which it will
be since we put the data "in in that order)

So, every 2 rows = 2 LIO's.

Now, disoganized -- we have about 1.5 times the LIO's as we do rows.  why?  each
array fetch did this:

- get index block (1 LIO)
- get row 1 from table (1 LIO for block X)
- get row 2 from table (1 LIO for block Y, the odds that Y=X are very very small
so we'll do another LIO)

So, we do 3 LIOS on average to get 2 rows...

Now, going higher on the array size (this is the sqlplus default size):

ops$tkyte@ORA920.LOCALHOST> set arraysize 15
ops$tkyte@ORA920.LOCALHOST> select * from colocated where x > 0;

29319 rows selected.

Statistics
----------------------------------------------------------
...
       5047  consistent gets
...
      29319  rows processed

ops$tkyte@ORA920.LOCALHOST> select * from disorganized where x > 0;

29319 rows selected.

Statistics
----------------------------------------------------------
...
      31325  consistent gets
...
      29319  rows processed

the colocated table is now a fraction of the LIO's.  We did 15 row array
fetches, meaning we did about 2,000 "fetch" calls.  Apparently we did about 2.5
LIO's per fetch.  How so?  Well we did this:

- get index block (1 LIO)
- get row 1 from table for this index block (1 LIO for block X)
- get rows 2..15 from table for this index block (maybe 0 LIO's - all on block
X, maybe 1 additional LIO cause we just read all of the rows on block X)

So, we do 2-3 LIO's for every 15 rows -- 5k LIOS.

Disorganized -- for the same reason as above -- does LIO's like crazy cause we
are constantly hopping from block to block in order to get this data.  The data
is all over the place.

Stepping arraysize up again:

ops$tkyte@ORA920.LOCALHOST> set arraysize 100
ops$tkyte@ORA920.LOCALHOST> select * from colocated where x > 0;

29319 rows selected.

Statistics
----------------------------------------------------------
...
       2597  consistent gets
...
      29319  rows processed

ops$tkyte@ORA920.LOCALHOST> select * from disorganized where x > 0;

29319 rows selected.

Statistics
----------------------------------------------------------
...
      30541  consistent gets
...
      29319  rows processed

ops$tkyte@ORA920.LOCALHOST> set autotrace off
ops$tkyte@ORA920.LOCALHOST>

colocated gets better, disorganized not really.  no matter what you do,
disorganized is disorganized -- we give up the block during the scan and end up
reading and re-reading the same blocks over and over and over and over again...

The CBO is "smart" about this as well.  There is a statistic -- the cluster
factor that it looks at to see if it wants to use an index or not.  It would be
more likely to use colocated to retrieve a larger "range" of rows then it would
with disorganized.  If you have my book "Expert one on one Oracle" -- that is
the example that is in there.
Rating: 5
Thanks, it is clear now December 02, 2002
Reviewer: A reader
Dear Tom,
  Thanks for your detailed explanation.  I had not considered arraysize at all.
From your example, it appears that the block on which the LIO was done is held
on to till all the rows specified by the arraysize are fetched out and is then
released because it is now time to send data back to the client.

regards
ramakrishna
Rating: 4
How to Colocated December 05, 2002
Reviewer: H from US of A
Tom,
How do i make sure that the indexes and tables are colocated?
Followup:
(it is not that the table and indexes are colocated -- but rather that the table
is organized -- typically sorted by -- the index key values.  If you have my
book "Expert One on One Oracle" -- i go over this in more detail)

Well, first you have to drop the PLURAL.  It is doubtful that the clustering
factor of two different indexes on the same table will both be "very good".  it
can happen but it is doubtful -- why?  because the clustering factor relies on
physical location of data and data can only be sorted one way.

If table COLOCATED is sorted by ID, an index on ID will have a good clustering
factor -- BUT an index on DATA on this table would have a terrible one!

If table DISORGANIZED, containing the same data, is sorted by DATA -- an index
on ID would have a bad clustering factor -- but an index on DATA would have an
EXCELLENT one.

So, one you accept that pretty much one index is going to have a good clustering
factor -- what can you do to make it so the one you want to have a good
clustering factor does?

If you are a transactional system, you can use b*tree clusters to force physical
location of data.  A cluster had a really good clustering factor by definition
-- all of the data for a given key is stored (optimally) on the same block
(again, if you have the book -- lengthly detail on this in the chapter on
tables)

If you are a data warehouse -- doing mass loads -- you would physically sort the
data.  Eg, in 9i -- i would do a direct path load from a flat file like this:

   insert /*+ append */ into my_table
   select * from flat_file order by <thing I wanted clustered>;

In 8i -- I might use an OS sort utility, or load the data into a scratch table
and then do the insert /*+ append */ with the order by.
Rating: 4
How to reduce LIO's December 19, 2002
Reviewer: reader from PAKISTAN
What should be done to reduce LIO's?
Followup:
tuning queries
using physical structures that facilitate the queries you ask most often
using database techniques like materialized views in a data warehouse, bitmaps,
etc

among dozens of others -- using your knowledge of what Oracle provides and
applying it to your problem at hand.
Rating: 5
Re: Ramakrishna's question December 19, 2002
Reviewer: Connor McDonald from UK
On a somewhat related note, there is an oracle 9 enhancement when probing via an
index in a nested loop.  The index probing is still done as described in this
question, but when it comes to looking up the table, Oracle can choose to defer
that read until its retrieved all of the rowid from the index.

Then it sorts the rowids so that it only has to visit each table block once.
Very nifty...
Rating: 4
Latching and Consistent Gets December 30, 2002
Reviewer: Ramakrishna from India
Dear Tom,
  I have a small doubt with the way latching, consistent gets and arraysize work
together.  Could you kindly clarify?
  If I am doing a full table scan, Oracle will do the following:
1.  Compute the hash address of the table block that is to be read.
2.  Take a latch on the chain in the cache buffers hash table where this block
resides
3.  Read upto arraysize rows from the table block (this counts as one logical
I/O)
4.  Release the latch
Is this correct?

  With an index scan, I am confused about how this happens in 8i (Connor above
has mentioned how this happens in 9i).  Suppose I have an index block that is
well clustered and all the rowids in that block point to the same data block.
In this case, does Oracle:
1.  Compute the hash address of the table block corresponding to the first row
in the index block.
2.  Take a latch on the chain in the cache buffers hash table where this block
resides
3.  Read that first row value (one logical I/O)
4.  Does Oracle release the latch on the table block here?  Or, does it hold on
to it till it has fetched upto arraysize entries from the index block?  If the
latter, then what happens if I had a query that did a
SELECT COUNT(*) FROM (SELECT something via index range scan from table)?
In this case, since arraysize doesn't matter, are the latches held till the
entire range scan is complete?  But, if that is the case, what if there is
another query that is reading the same blocks 'backwards'?  How does it all work
without deadlocking?

It would be great if you could explain how the latching and logical IO is done
for an index range scan.

thanks in advance,
Ramakrishna
Followup:
as for the full scan first example -- conceptually correct, yes.

Now, Connor was talking NL joins which is different from just an index range
scan.  NL joins are bunches of separate index range scans.

The index range scan works like this:

o if the index is very well clustered (organized) the same as the table -- then
array fetching will do the consistent gets much like a full table scan would.
That is, if the TABLE is ordered the same as the INDEX is ordered -- we'll get
an index block that has our first value -- latch the table block that has that
and keep that table block until either we hit arraysize rows or the index entry
tells us to move onto a different database block.

o if the index is sorted very differently from the table data -- you'll see
higher consistent gets since we jump from table block to table block as we go
from row to row.

Here is a quick and dirty example:

ops$tkyte@ORA817DEV> create table t as select * from all_objects order by
object_id;

Table created.

ops$tkyte@ORA817DEV> alter table t add constraint t_pk primary key(object_id);

Table altered.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> set autotrace traceonly explain

ops$tkyte@ORA817DEV> select object_name from t where object_id > 0;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T'
   2    1     INDEX (RANGE SCAN) OF 'T_PK' (UNIQUE)

ops$tkyte@ORA817DEV> set autotrace traceonly statistics

ops$tkyte@ORA817DEV> set arraysize 10
ops$tkyte@ORA817DEV> select object_name from t where object_id > 0;
23712 rows selected.

Statistics
----------------------------------------------------------
       5083  consistent gets

ops$tkyte@ORA817DEV> set arraysize 100
ops$tkyte@ORA817DEV> select object_name from t where object_id > 0;
23712 rows selected.

Statistics
----------------------------------------------------------
        846  consistent gets

ops$tkyte@ORA817DEV> set arraysize 500
ops$tkyte@ORA817DEV> select object_name from t where object_id > 0;
23712 rows selected.

Statistics
----------------------------------------------------------
        472  consistent gets

ops$tkyte@ORA817DEV> set autotrace off

ops$tkyte@ORA817DEV> drop table t;
Table dropped.

ops$tkyte@ORA817DEV> create table t as select * from all_objects order by
dbms_random.random;
Table created.

ops$tkyte@ORA817DEV> alter table t add constraint t_pk primary key(object_id);
Table altered.

ops$tkyte@ORA817DEV> set autotrace traceonly explain

ops$tkyte@ORA817DEV> select object_name from t where object_id > 0;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T'
   2    1     INDEX (RANGE SCAN) OF 'T_PK' (UNIQUE)

ops$tkyte@ORA817DEV> set autotrace traceonly statistics

ops$tkyte@ORA817DEV> set arraysize 10
ops$tkyte@ORA817DEV> select object_name from t where object_id > 0;
23712 rows selected.

Statistics
----------------------------------------------------------
      26065  consistent gets

ops$tkyte@ORA817DEV> set arraysize 100
ops$tkyte@ORA817DEV> select object_name from t where object_id > 0;
23712 rows selected.

Statistics
----------------------------------------------------------
      23934  consistent gets

ops$tkyte@ORA817DEV> set arraysize 500
ops$tkyte@ORA817DEV> select object_name from t where object_id > 0;
23712 rows selected.

Statistics
----------------------------------------------------------
      23744  consistent gets

ops$tkyte@ORA817DEV> set autotrace off

see how in the second case the consistent gets didn't go down significantly --
regardless of the array size.  Each row caused us to hop from block to block to
block -- every row fetched via the index was another consistent get
Rating: 5
Thanks, that clears it December 30, 2002
Reviewer: A reader
Dear Tom,
  Thanks very much for your reply.  That explains why the logical I/O is less
for a colocated table even through an index range scan.

regards
ramakrishna
Rating: 5
LIO and tkprof January 02, 2003
Reviewer: Sam from San Mateo, CA
Tom,

Suppose I have a query that resulted in reading 10 blocks from disk.

In the tkprof output, does the value in the "query" column include the LIO that
resulted in the PIO?  Or do I need to sum disk+query+current to get the actual
number of LIOs?

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.01          0         90          0           0
Execute      1      0.06       0.08          2        274         17           4
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.08       0.09          2        364         17           4
Followup:
query+current = buffer reads.

physical IO's are *generally* part of query+current, that is a physical IO
becomes a LIO right away.

Direct READS from temp -- do not become LIO's however -- so, it is easy to have
disk > query+current in that case.

So, a physical io into the buffer cache will also bump query|current.
A physical IO from temp will not, that bypasses the cache.
Rating: 5
Re: Connro's Response January 02, 2003
Reviewer: Sam from San Mateo, CA
Tom,

Could you also explain a bit more the 9i feature that Connor mentioned, and
maybe point us to some documentation?

"On a somewhat related note, there is an oracle 9 enhancement when probing via
an index in a nested loop.  The index probing is still done as described in this
question, but when it comes to looking up the table, Oracle can choose to defer
that read until its retrieved all of the rowid from the index."

Thanks
Followup:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7110065183012

Rating: 5
Please explain this in this context January 03, 2003
Reviewer: Subrata Saha from India
if mode eq ‘consistent’, then
clone(data block address, …)
‘consistent gets’++
else /* mode eq ‘current’ */
‘db block gets’++

What is this cloning?
Followup:
you typed it in?

it looks like, from the context you typed in, it means to get a consistent read
"clone" (copy) of the block -- using RBS data if need be to rolled back a block.
 consistent gets may go up by more then one (to reflect how many RBS reads we
did as well)
Rating: 3
how about famous "readers don't block readers" February 24, 2003
Reviewer: Mikito Harakiri
>>> A latch is a lock.

>>> Locks are serialization devices

In other words, if blocks are on disk, readers don't block each other, but if
blocks are in memory, they do! So long for "readers don't block readers".
Followup:
Mikito, you are boring after a while.  really.

The only place really that Oracle reads blocks from is memory -- physical io is
used to read the blocks into the buffer cache.  The buffer cache -- being a
shared data structure -- must be protected from concurrent "readers of memory"
and "updaters of memory".  hence the latch.

Now you tell me -- can you truly, even by "reductio ad absurdum" here you cannot
actually believe what you are saying.

and "reads don't block reads" isn't famous - it is boring.

what is famous is

"reads don't block writes"
"writes don't block reads"
Rating: 3
Hash Join versus Nested Loops February 25, 2003
Reviewer: Mikito Harakiri
In your example Nested Loops has more buffer gets than, say, Hash Join. OK. This
translates into less number of latches and better concurrent performance
profile.

Now I'm trying to understand the reason. Suppose both tables are cached into the
memory. Then we can ignore Random IO versus Sequential IO difference. Is Nested
Loop algorithm inherently "nonoptimal" compared to Hash Join? What HJ does
differently? It does put a hash table in front of the inner table. That's just
extra work to me. Then, it does the similar nested looping which is only
sugarcoated by the hash table probe. Specifically, oracle probes the hash table,
so that if there is no match, then the target table is not accessed. But Indexed
Nested Loop does the similar probing of the join index! Why Hash Join is
considered "more scalable"?
Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6749454952894#6760861174154
for a better example.

Think of it like this.  Each consistent get takes CPU work.  If you do something
that take 0.0001 cpu seconds once, it is really fast.  Now, if you do it
100,000,000 times, it takes 10,000 seconds!

do something really fast and small alot and it adds up.

Lets say you have a room of boys and girls and you want to have a dance.  You
would like to have the kids paired up by size (tall girl, tall boy).

You could pick a girl and then search around for a boy about the same size, pair
them up and continue.  This works well if you have say 5 boys and 5 girls.

now, lets say you have 1000 of each.  It gets a little unwieldy now -- takes
quite a while to pair them up.  You could have the boys sort themselves by
height and then the girls -- now you pair them up quickly.

That is sort of a sort merge but it is similar to a hash join.

The first method -- pick a girl, find a boy -- output -- gets people on the
dance floor really fast (first rows back fast) but it takes a long time to get
all 1000 couples out there.

The second method keeps the dance floor empty during the sort -- but when the
rows start coming -- they are flying out there (with very little work).

Neither is "less optimal" -- no more then an 18 wheeler is less optimial then a
BMW.  One is good for big long hauls, one is good for getting to work.
Different tools for different jobs.

There are no "bad" features, just inappropriate uses of technologies.
Rating: 5
Nice example February 26, 2003
Reviewer: A reader
Rating: 5
sort-merge join versus nested loop February 26, 2003
Reviewer: Mikito harakiri
Excellent explanation.

Let me elaborate, though. Suppose we want to pair 1M girls and 1M boys. In MJ
method we sort girls -- that's about 6*1000000 operations -- then, we sort boys
-- that's another 6*1000000 operations. Then we go through the sorted lists
matching the pairs -- that's about 2*1000000 operations.

Now, for comparison, we perform indexed nested loops. For each boy we refer to
the index of girl's height. We'll do roughly 6 (logarithm of 1M) navigations in
the index structure. Index leaf refers to the exact location of the girl. Then,
the total amount of work is about the same 6*1000000 operations.

Therefore, I still don't understand how can we see Merge Join having
significantly less buffer gets than Nested Loops.
Followup:
you have it backwords -- sorts are the log, NL are linear.

for each girl -- you will do 3 index reads + one table read = 6 * 4 million
operations.

the sorts are the ones that are on the order of log(n)

try it and see -- oh wait -- I already did -- see link above.
Rating: 5
March 02, 2003
Reviewer: Jim
Excellent
Rating: 5
arraysize and full-table scan April 15, 2003
Reviewer: Sam from San Mateo, CA
Tom,

You mentioned:
"Each array fetch did this:

- get index block (1 LIO)
- get row 1 from the table (1 LIO for block X)
- get row 2 from the table (0 LIO if the second row was on block X which it will
be since we put the data "in in that order)"

In the case of a full-table scan, how does arraysize affect the number of LIOs?
I assume if there are lots of rows in a block, with a small array size then
Oracle will read the same block many times, whereas if there are only a few rows
in a block then the negative impact will be less since the same block will be
revisited fewer times.  Is my understanding correct?

Also, what does Oracle "remember" between each array fetch?Does it remember the
index block or table block it last read so it just resumes from there?
Followup:
that is correct (the arraysize / lio thing)

Oracle remembers everything it needs to in order to pick up processing where it
left off.  This could be thousands of things.  But, think of it like this.

A SQL query is a program.
Oracle is a compiler.
Oracle compiles the program into object code (shared sql)
You run the program (like running something from the command line)
The operating system (Oracle) starts the program, sets an instruction pointer,
creates a stack, initializes a data segment.
Program runs, gets pre-empted, OS restores context -- runs, gets pre-empted, OS
restores context -- runs, gets pre-empted and so on

So, just think of SQL as a program, Oracle as an OS, arraysize is the timeslice
the program ran for --  and you'll have a very nice analogy for what happens.
Rating: 5
how much logical read is too much ? May 21, 2003
Reviewer: kumar from India
Tom,

How do i know whether the logical reads done for a particular query are actually
essential ? How do i find the minimal/optimal no of reads approximately required
for a particular query that returns a certain number of rows?

Thanks
Followup:
You think about it (seriously).

If I ask a query like:

select * from emp where empno = :x

I would expect 3 logical IOs for the index and one for the table.  I would
expect 4 logical IO's

If I ask a query like:

select * from emp, dept where emp.deptno = dept.deptno and dept.deptno = 10;

I would expect

3 LIO's to read the index on deptno plus
1 LIO to do the table access by rowid on DEPT to get the dept row plus
N*3 LIO's to read the index on emp(deptno) where N is the number of emps in a
department plus
N LIO's to get the emp data

for example.  You have to sort of reason it out.
Rating: 5
Thats really a good one May 21, 2003
Reviewer: kumar from India
Tom,

Thanks for your response. I was also thinking on that line -but then i felt this
method a little cumbersome when there are more number of tables involved in a
query. I thought there might be some more efficient method of  doing it. But for
doing in this method I assume that we need to know the data completely - Am i
right ?
Followup:
if you don't know the data completely, you cannot be tuning the queries.  maybe
this is a good chicken and egg situation, in order to know if the query is tuned
-- you need to understand the data, in order to tune a query -- you need to
understand the data...
Rating: 5
the hotsos article doubt June 24, 2003
Reviewer: A reader
You said
"download the "are you still using cache hit ratios"
and keep an open mind."

from the www.hotsos.com.

Quote from the article:
"
Of course, if your database spends a lot of time parsing, then you should try to
reduce the
number of parse calls that your application requires. If your database spends a
lot of time doing language processing,
then you should carefully consider moving that application logic to a middle
tier, where scalable CPU capacity is
much less expensive than it is in your database."

I could not understand what the article meant about
language processing and the corresponding advice to
move that logic to middle tier. I also dont understand
what they mean by "scalable CPU capacity is much less
expensive than it is in your database".

Could you throw some light on it?

Thanx for the GREAT SITE!!!
Followup:
You would have to ask Cary ;)
Rating: 3
well, I asked Cary and here is what he has to say June 25, 2003
Reviewer: A reader
What are your thoughts on this Tom? I am not quite sure
if he is contradicting your opinion of putting more
stuff into database - elsewhere you said that
middle tier cpu cycle is not any more or less cheap than
a database cpu cycle - See "I sort of thought a cpu cycle was a cpu cycle?" at
http://asktom.oracle.com/pls/ask/f?p=4950:8:283718101655284345::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:883929178230
Cary says something interesting in this regard to
in answer to my second followup question.

My conversation with Cary
_________________________
Q1. I could not understand what the article meant about
language processing and the corresponding advice to
move that logic to middle tier. I also dont understand
what they mean by "scalable CPU capacity is much less
expensive than it is in your database".

Cary's response:
_______________
You often have a choice about whether to execute application logic in the
database, or in the client using the database. There are tradeoffs. On one
extreme, if you get one row at a time from the database and do all application
logic (including joins) in the application, it is bad. It&#8217;s much more
efficient to do filtering and joining, for example, in the database than in the
application. At the other extreme, if you do all your application processing in
the database (for example, CPU-intensive operations like data processing that
have nothing to do with data storage and retrieval), then you&#8217;re using
database server CPU capacity unnecessarily.

On busy databases, the Oracle database server is the bottleneck. It&#8217;s why
Oracle Corp. is focusing so much effort on trying to make RAC a scalable
solution for database systems that demand more CPU capacity than the biggest
computers can provide. But RAC (and even huge single-node Oracle boxes) are very
expensive. So, it&#8217;s smart to conserve all the CPU capacity that is
practical on your database tier. In the second extreme described above,
it&#8217;s much more scalable to move the processing to a cheap middle-tier
computer instead of having extra workload that competes against all your other
database processing for CPU capacity.

My Followup Q 2:
_______________
If you can, please give real life example of "cpu intensive" operations that
dont involve data processing that are apt for middle tier and are NOT apt for
database.

This is because sql engine itself is very rich for most of the data
transformations that I can think of.

Is "language processing" in your article the right example - if so could you
please elaborate?

At what point does the database cpu become "costlier" than the middle tier cpu?

Cary's response:
_______________
There are lots of examples. One is Oracle E-Business Suite&#8217;s Financial
Statement Generator (FSG). These things get lots of data out of the database and
then works for minutes to hours processing those rows (making balance sheets,
income statements, and such). Anything complicated that happens to data after
getting it out of the database is what I&#8217;m talking about.

By the way, by &#8220;language processing,&#8221; I mean the execution of PL/SQL
that makes no database calls.

Regarding your final question, I think that one second of CPU capacity on a
database server is more expensive than a second of CPU anywhere else if the
database server is suffering from a CPU capacity shortage (which they often do).

When you get down to it, the database server is the hardest component of an
Oracle application architecture to scale. It&#8217;s not an Oracle product
deficiency; it&#8217;s a simple matter that the single component of an
Oracle-based that gets visited repeatedly by every user of an application is
usually the database server. So, any waste that takes place at that tier is
usually the first problem to impact everyone. Therefore, it pays to be careful
what you put there. If a CPU-consuming process can be run with equal functional
effectiveness either on the database server or somewhere else, then it probably
should be run somewhere else.
Followup:
perfect, we are in aggreement....

application logic = outside, in the application, where ever that be.

data logic = inside, in the database, that be in the center.

In many (many many many) systems, the database is more then big enough for both.

In some (big big systems) the database is not.

Problem is, people think "everything big, everything needs 14 tiers".  Many
things, well, they just do not.

Like Cary, I hate things that pull data out to a middle tier a row at a time,
process it, put it back.  what a waste.

Things that pull it out, keep it out and format it for "looking at" or "reports"
-- great, if you have the cycles and want to -- keep in in the center.  If you
don't have the cycles or don't want to -- put it outside.

But never -- never get into the mode of "we cannot do anything in the DB, too
'expensive'"

It is too expensive NOT to.  The simple "pull it out, row by row, put it back"
that is so common is much less "impactful" on the database if you simply use a
single query and don't make the db throw the data out just to put it back
somewhere else.

So, would I do a fast fourier transformation in my database?
No.

Would I do all of my ETL?
Yes.

Would I do my security?
Yes.

Would I use analytics?
Yes.

Would I use dbms_jobs to run many things?
Yes.

Would I generate a gif image?
Probably Not.

Would I text search?
Yes.
........

I would just end his last paragraph with

then it probably should be run somewhere else, assuming of course you've already
gone down the path of multiple machines to service your uses.  If you use a
single machine as many people do, well then -- maybe you can just keep it in the
database.....

It is truly amazing what you can do on a single 2/4 cpu server -- "database" and
"middle tier" all on one.  It takes a fairly "big" system to make that not
feasible.
Rating: 4
decreasing Physical read July 18, 2003
Reviewer: A reader
Hi Tom,

  First I want to thank you again for your valuable information and informative
site.

I have a table as follow:

graindba@COLG> set serveroutput on
graindba@COLG> exec show_space('FIN_GL_AUDIT_TRAIL');
Free  Blocks.............................1

Total Blocks............................9045

Total Bytes.............................74096640

Unused Blocks...........................125

Unused Bytes............................1024000

Last Used Ext FileId....................4

Last Used Ext BlockId...................132091

Last Used Block.........................135

graindba@COLG> select count(*) from fin_gl_audit_trail;

  COUNT(*)

----------

    366874

The dbBlock size is 8k. As you see the table has 8919 Data Blocks. Now, when I
execute the following query it takes 26 seconds which is not good for me. I need
better performance. As you mentioned it before, I looked at the number of
consistent gets which is exactly the same as number of data blocks. So to my
knowledge it is OK. Because Oracle needs to read each block at least once. Am I
right? But I noticed it has  6127    physical reads !

My question is, how can I tune this query? Do I need to decrease Physical Reads?
If so, how?

Thanks again for your time,
Arash
                                                           P.S. The Lock_Flag
coulmn has an Index. But I think CBO because of the data distribution it doesn't
use the Index.

graindba@COLG> select 1
  2  from
  3   fin_gl_audit_trail
  4   where lock_flag ='Y'
  5  /

no rows selected

Elapsed: 00:00:26.04

Execution Plan
----------------------------------------------------------

   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1354 Card=366874 Byt

          es=366874)

   1    0   TABLE ACCESS (FULL) OF 'FIN_GL_AUDIT_TRAIL' (Cost=1354 Car

          d=366874 Bytes=366874)

Statistics
----------------------------------------------------------

          0  recursive calls

          4  db block gets

       8919  consistent gets

       6127  physical reads

          0  redo size

        210  bytes sent via SQL*Net to client

        209  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

          1  sorts (memory)

          0  sorts (disk)

          0  rows processed
Followup:
so, what is the distribution of values in that column, what does

select lock_flag, count(*) from t group by lock_flag

return?
Rating: 4
July 20, 2003
Reviewer: A reader
Hi Tom,

select 1
  2  from
  3   fin_gl_audit_trail
  4   where lock_flag ='Y'
  5  /

no rows selected

As you see, it doesn't return any value for lock_flag ='Y'. It's kind of dynamic
column which will be set to 'Y' any time a user query a record. So all records
have the value 'N' by default.

Regarding the index on lock_flag I made a mistake(sorry). It didn't have any and
when I added the index it worked ok. I mean the query execution time took less
than a second.

Now another question came to my mind. I've read in Oracle books that we don't
need to index low cardinalate columns. beause CBO won't use it and would rather
to do FULL TABLE scan. So why does CBO used the index in this case?

and another thing, why that much Physical Reads!? How can I in general reduce
Physical Reads?

Thanks millions of tons for your time,
Arash
Followup:
the CBO intelligently used indexes when they make sense.

Here -- they know that "y" has few values -- index is good.  If you did it for
='N', the CBO should go for a full scan.

You might consider indexing:

decode( lock_flag, 'Y', 'Y', NULL )

that way ONLY locked records will be in the index (index will be tiny) since
entirely null entries are not made in b*tree indexes.
Rating: 4
July 21, 2003
Reviewer: A reader
Thank you so much. I learned one more thing.
Rating: 5
November 07, 2003
Reviewer: A reader
I have a session, and its logical reads is keeping increasing. But there is not
sql statement found by the session and status shown INACTIVE based on v$sqlarea.
There do show some wait events for the session, majorally are log file sync  and
 latch free shown 100% timed out. Could you explain what happens to the session?
Even no sql statement in the sqlarea?
Followup:
you are looking at the session as "an instant in time" and each time you look,
it is inactive.

that is all, the session IS active.
Just not at the instant you look at it.
Rating: 3
Where does Oracle hold this block December 22, 2003
Reviewer: A reader from NJ, USA
Tom,

In the above discussion you mention the following :
<Quote>
See, here since the array size was 2 -- our consistent gets were pretty close to
number of rows for colocated.  Why?  Each array fetch did this:

- get index block (1 LIO)
- get row 1 from the table (1 LIO for block X)
- get row 2 from the table (0 LIO if the second row was on block X which it will
be since we put the data in that order)
</Quote>

In the above case, we're almost sure that row 2 will be on the same block X
because we put data in that order. However, I am not clear on this and I have
the following question(s) :
It has access block X from the buffer cache again to get to the second row
(which I understand is already in the buffer cache) which constitutes 1 LIO, but
you mention that it does 0 LIOs in this case.
1. Can you please clarify as to why you say that it does 0 LIOs?
2. Does that mean that Oracle keeps the block X temporarily in a memory buffer
array somewhere (without accessing the buffer cache again) after fetching row 1
but before fetching row 2. If so where does Oracle keep the block without
accessing the buffer cache?
3. If Arraysize is set to 1 instead of 2, then does that mean it will perform 1
LIO for each fetch?

As always thanks much for your help.
Followup:
1) we already had the block, we did not need to go back to the buffer cache to
re-get it in that very same call.  if the next row we need is on the block we
already have -- 0 LIO's to get it.

2) yes, in the PGA (process global area) of your dedicated or shared server
process.

3) it would perform 2 LIO's for each fetch.  1 for the index, 1 for the row.  It
would do 2x the number of fetches -- hence 2 times the LIO.
Rating: 3
One question December 22, 2003
Reviewer: A reader from NJ, USA
Tom,

Please confirm the following :

1. Is the question 1 related to latching the table block as long as the next row
is in the same tabe block being latched or the number of rows fetched exceeds
array size?
2. If we do a single row-by-row fetch, that means, it would require latch for
each table block fetched and is that what you say that latches are serialization
devices which makes the query not scalable?
3. If we do row-by-row fetch (ie. arraysize = 1), it will do the following :
a. Read 1 index block (1 LIO) + 1 table block (1 LIO) = 2 LIOs
b. Get the index block read in step 1(1 LIO) and get the next row of the table
block (1 LIO) = 2 LIOs. Is that what you say by 2 LIOs per row.

Thanks much
Followup:
1) if we already have the block we need -- we do not get it again. that is all
i'm trying to say.

2) yes

3) i mean you'll get index block (1 lio) that points to table block (1 lio) and
that equals TWO.

every FETCH will require

a) get index block we last used
b) get table block it points to
Rating: 3
Index block read December 22, 2003
Reviewer: A reader from NJ, USA
Tom,

To recap and understand/confirm what I have understood from the discussion on
index range scan.

1. The index block is read from the buffer cache (1 LIO). The table block
pointed to by the first row of the index is read from the buffer cache and
brought to the PGA(1 LIO. If the block is not already in the buffer cache, a PIO
is initiated). A latch is held on the table block if the next row of the index
block points to the block we already have and the row is put into the array.
This process continues until the number of rows exceeds the array size or the
index entry points to the another table block at which time the next table block
is accessed from the buffer cache and the LIO is incremented.
Pls. confirm if the above understanding is correct.
2. You mentioned that the table block is held in the PGA. Is the index block
also held in the PGA? If not where is the index block held?
3. Suppose the arraysize is 5 and for e-g. the first block of the index entry
points to table block X, second entry points to table block Y, third entry
points to table block Z and the fourth entry points again to X and the fifth
entry points to Z. Then in that case the following happens.  :
1 index block read (1 LIO)
1 table block (X) read into PGA (1 LIO)
1 table block (Y) read into PGA (1 LIO)
1 table block (Z) read into PGA (1 LIO)
When reading the fourth and fifth table rows from block X and Z
respectively,will Oracle use the blocks X and Z already read into the PGA or
will it go to the buffer cache to access the blocks incurring additional LIOs?
If so, will increasing the PGA size help reduce LIO and what parameters to
increase/alter.
Please clarify questions 1 to 3.

As always, your explanation will be really helpful

Thanks
Followup:
1) the latch is removed as soon as we copied the block into the pga address
space.

we latch to copy it.  then unlatch it.

2) yes, copied data is in the pga where we can work on it.

3) it'll have to reget them (you haven't any real control over PGA memory like
that, you cannot "increase it"
Rating: 3
Clarification to Qn 3 above December 23, 2003
Reviewer: A reader from NJ, USA
Tom,

What I really meant in Qn 3 above was that whether Oracle stores any information
that says 'Hey, you have read table block X while reading row 1 and is already
in the PGA. Since
the index entry for row 4 also points to the same table block X, you don't reget
it from the buffer cache, but instead use the table block X already in the PGA.
Followup:
we don't still have -- we moved off of that block onto another one.  block x
isn't there.
Rating: 3
Doubt December 23, 2003
Reviewer: A reader from NJ, USA
Tom,

Can you then explain what you mean by

'yes, copied data is in the pga where we can work on it.'

in response to question 2 above in the prior discussion
Followup:
the block(s) we are processing is(are) copied from the buffer cache into the pga
for further processing.
Rating: 3
Pls. clarify December 24, 2003
Reviewer: A reader from NJ, USA
Tom,

As a followup to the earlier discussion, you mentioned the following and I quote
here :

we don't still have -- we moved off of that block onto another one.  block x
isn't there

What do you mean by :

1. We don't still have the block - Was it not copied to PGA when reading block X
2. Block X is not there - What does it mean?
Does it mean that when Oracle processes each row pointed by index entry, it will
access the block from the PGA one-by-one until the array size is exhausted or
until the index entry points to a different block at which point, it will leave
the current block alone and fetch from the buffer cache, another table block Y
pointed by the next index entry ie. (1 more LIO).
3. If I have the following index entries :
   index entry 1 points to table block X
   index entry 2 points to table block X
   index entry 3 points to table block Y
   index entry 4 points to table block X

Will it mean Oracle will perform the following for table blocks :
1 LIO to fetch table block X for 1st row
0 LIO since the 2nd row is on same block X
1 LIO to fetch table block Y (since it is in a different block)
1 LIO again to fetch table block X (since the 4th row is in X and Oracle has to
re-read the block X. ie hop from block to block). My question is in the last
case
  Can't it use the table block X already in the PGA?.
  If not pls. explain.
4. Tom, I am almost clear as to how the LIOs and physical reads happen except
the following :
  How Oracle accesses the required table and index blocks once the first index
block and table blocks are copied onto PGA and how the LIOs and PIOs are
affected from here on.
Please explain how Oracle handles this part.

Thanks much and have a Happy and Wonderful Christmas and New Year 2004.
Followup:
1) it was, but then we moved onto block y.

2) means "not there", we used it, we saw we needed y, we moved on.  then
discovered "oh, we need x again"

3) set up the test and try it out!  (i did, its pretty easy to simulate)

4) sorry -- guess i'm not able to make it clear enough here.  no whiteboards
December 24, 2003 Chirag -- Thanks for the question regarding "insert into Temporary tables", version 8.1.6
You Asked
Hi Tom,
In a stored procedure I am trying to create a temporary table, select values
from an existing table and insert those values into the temporary table i just
created.

My stored procedure is:

create or replace procedure temp_table
as
cid INTEGER;
create_table varchar2(255);
temp_slot varchar2(255);
pl_sql varchar2(255);
iid RAW(16);
nname    varchar2(255);

cursor_name     pls_integer default dbms_sql.open_cursor;
    ignore          pls_integer;
BEGIN
    CURSOR_NAME:= DBMS_SQL.OPEN_CURSOR;

--creating a temporary table
    create_table :='CREATE GLOBAL TEMPORARY TABLE tempslot(iddd raw(16),nnname
varchar2(255))';

--my existing table
    SELECT ID, name INTO IID, nname FROM Temptest;

    PL_SQL:='INSERT INTO TEMPslot values(IID, nname)';

    dbms_sql.parse(cursor_name, CREATE_TABLE, dbms_sql.native);
    dbms_sql.parse(cursor_name, pL_SQL, dbms_sql.native);
    ignore := dbms_sql.execute(cursor_name);
    --dbms_sql.parse(cid,create_table, dbms_sql.v7);
    dbms_sql.close_cursor(cursor_name);

END;

-- drop table tempslot;

The above procedure compiles fine, but when I execute it, it gives me an error
which is:
SQL> exec temp_table;
BEGIN temp_table; END;

*
ERROR at line 1:
ORA-00984: column not allowed here
ORA-06512: at "SYS.DBMS_SYS_SQL", line 782
ORA-06512: at "SYS.DBMS_SQL", line 32
ORA-06512: at "EBYZ.TEMP_TABLE", line 24
ORA-06512: at line 1

I have tried creating 2 cursors as well in the same API, the oracle user who is
executing has the necessary previledges for DBMS_SYS_SQL and DBMS_SQL as well.
Am I missing out somewhere.
Thanks in advance

Chirag
and we said...
That is the incorrect way to use temporary tables in Oracle -- it might be the
right way in Sybase or Informix -- but not Oracle.

In Oracle, you should create the global temporary table ONCE.  DDL is hugely
expensive, it commits any outstanding work, you have to drop the table yourself
(which again commits), and unless you name them uniquely -- it will cause you to
be able to run this stored procedure serially (only ONE person at a time).

The correct way to code the above will be:

create global temporary table tempslot ( iddd raw(16), nnname varchar2(255) );

create or replace procedure temp_table
as
begin
   insert into tempslot select id, name from temptest;
end;
/

and that is it.

If you persist down this incorrect path of creating a temporary table inside of
the procedure, you would have to code like this:

create or replace procedure temp_table
as
  iid    RAW(16);
  nname  varchar2(255);
begin
   execute immediate 'create global temporary table tempslot (.....';

   SELECT ID, name INTO IID, nname FROM Temptest;

   execute immediate 'insert into tempslot values ( :id, :name )'
           using iid, nname;

   insert into tempslot select id, name from temptest;
end;
/

You have to use BIND variables - you tried to execute the insert statement:

INSERT INTO TEMPslot values(IID, nname)

here, IID and NNAME were assume to be column names -- you weren't in "plsql"
anymore.  you should have executed:

INSERT INTO TEMPslot values(:IID, :nname)

and made calls to dbms_sql.bind_variable to bind those values in.

I urge you to rethink your approach!!!! temporary tables are not to be created
"on the fly" in Oracle.

followup to first comment below.

I totally disagree.  If you use temporary tables properly -- that is you create
them ONCE and never do it "on the fly" (as urged above) -- you'll not be
dropping temporary tables.  If your application needs to drop temporary tables
it creates on the fly -- your application is not coded using best practices.
DDL is HUGELY expensive, it should not be done in a production system.

That bug you reference *might* affect a development system where you have a run
away session due to a bug, but there you would just bounce the instance if you
really need to drop that table right away to change its definition.

follow up to comment #2 below...

I suppose, if you read the answer, you would have seen thats included in the
answer as well.

Look for the paragraph that says
"if you persist down this incorrect path"...
you'll find that I told him everything you mention below (and even gave him the
code with native dynamic sql) -- told him of bind variables.

One thing i did not do however was to even suggest the:
'insert into table(col1,col2) values('||to_char(IID)||','''||namevar||''''||')'

approach as that is the single worst thing you could do -- to skip using
bind variables.

I always take the opportunity to not only ANSWER the question -- but to point
out obviously flawed approaches.  Why tell a person how to play russian roulette
but not tell them they will probably shoot themselves, causing great pain or
death??

So, the problem is in fact -- that this is NOT the right way to do it, the right
way is explained but if they wish to shoot themselves -- that is included as
well.
Review & Followup

Rating: 3
May 08, 2001
Reviewer: Kenneth Whitaker from Alexandria VA
Don't recommand using GLOBAL TEMPORARY TABLE until this bug is fixed.
BUG:1055420, CANNOT DROP A GLOBAL TEMPORARY TABLE AFTER INSERTING SESSION HAS
BEEN KILLED

It appears that PMON even after the session is killed does not release all
resources held by the killed session, which results in the ORA-14452 even though
you think the session has already been removed.
Rating: 2
dynamic sql May 08, 2001
Reviewer: andrew lapides from calgary, alberta, Canada
the problem is not whether or not to use the temp tables, but rather why the
code doesn't run.

When sql string is created in this example it is created as is. One can output
it (using dbms_outout for example) and see that the value clause will look like
this: "values(IID,..." Of course this can't be executed.

The correct approach will be either to bind variables or convert them properly:

plsql:='insert into table(col1,col2)
values('||to_char(IID)||','''||namevar||''''||')';

one can output this string and will see the correct values clause with real
values.

another way on 8 is to use NDS (native dynamic sql)
Rating: 5
May 09, 2001
Reviewer: Chi
Rating: 3
May 09, 2001
Reviewer: Kenneth Whitaker from Alexandria VA
I totally agree with you about dropping the temp table.
We starting using temp tables worked great until we needed a change to the table
- had to bounce the database - thus the fix could not be made until that weekend
- stopping their work - well they cheated and added a "x" to end of the new
table until the bounced.  I think the bug is serious enough to limit use of this
type of tables in a production database, assuming a table change may be needed.
Rating: 5
Tom September 13, 2001
Reviewer: A reader
".
BUG:1055420, CANNOT DROP A GLOBAL TEMPORARY TABLE AFTER INSERTING SESSION HAS
BEEN KILLED
"

Has this been fixed. Which version has got this fixed. What was this bug about.
Followup:
It'll be fixed in Oracle9i release 2 and patches for it may be available as
needed for 8i.

It basically meant that if a session was accessing a temp table and that session
was killed, you could not drop the temp table until the database was restarted.
This would ONLY affect a development system -- not a production system -- as you
would not be dropping and recreating temporary tables in a production system,
they would be statically defined.
Rating: 4
Use or Cursor September 13, 2001
Reviewer: Srimal from Trenton USA
Hello Mr Tom,

I come in from Sybase background and I have transitioned out of Temp tables by
effectively using Cursors and the results have been phenomenal...

Thank you for showing how to use temp tables though...

Srimal
Rating: 3
Behaviour of global temporary tables MIS database (Read only mode) March 14, 2002
Reviewer: Ramesh Golkonda from Edinburgh, U.K
I have created a package which generates a report and stores it in globla
temporary table.  The MIS database was created in read only mode would it be
possible to generate and store the report in global temporary table?
Followup:
well, tablespaces are read only and a database can be opened in read only mode
-- not sure which you mean here.

If you have read only tablespaces -- then you are OK.  If you have a database
opened in read only mode, you cannot do DML, even on temp tables.
Rating: 3
March 14, 2002
Reviewer: Ravi from USA
Hi Tom,
Can we not use this instead of your response (below):
create or replace procedure temp_test as iid RAW(16); nname varchar2(255);
BEGIN
execute immediate 'create global temporary table tempslot (.....';
insert into tempslot select id, name from temptest;
end;
/
Do we have to use Bind Variables and EXECUTE immediate with 'INSERT ...USING'??
Please clarify. Thanks. Somewhat confused.
---- Your answer:

create or replace procedure temp_table
as
  iid    RAW(16);
  nname  varchar2(255);
begin
execute immediate 'create global temporary table tempslot (.....';

   SELECT ID, name INTO IID, nname FROM Temptest;

execute immediate 'insert into tempslot values  :id, :name )'    using iid,
nname;

   insert into tempslot select id, name from temptest;
end;
/
Followup:
sure, it would not have the check that there is AT LEAST one and AT MOST one row
in the table temptest -- you should add:

if ( sql%rowcount != 1 ) then
   raise_application_error( -20001, 'expected 1 row, got ' || .... );
end if;

after the insert into.
Rating: 4
what if i have to alter the temporary table August 05, 2002
Reviewer: john
tom,

  in oracle 8i, if i have to drop a created global temporary table i understand
i have restart the system.
what if i have to alter the temporary table say for example adding a new column.
even then i have to restart the system.

can i put a temporary table in a normal tablespace.

please guide.

thanks
john
Followup:
No you do not.

You'll really need to "clarify" what you mean.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create global temporary table t ( x int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select rownum from all_objects;

22939 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> disconnect
Disconnected from Oracle8i Enterprise Edition Release 8.1.7.3.0 - Production
With the Partitioning option
JServer Release 8.1.7.3.0 - Production
ops$tkyte@ORA817DEV.US.ORACLE.COM> connect
Enter user-name: /
Connected.
ops$tkyte@ORA817DEV.US.ORACLE.COM> drop table t;

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create global temporary table t ( x int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select rownum from all_users;

43 rows created.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t add y number;

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> desc t;
 Name                                                                    Null?
 Type
 -----------------------------------------------------------------------
-------- -------------------------------------------------
 X
 NUMBER(38)
 Y
 NUMBER

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

Table dropped.

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

That shows you need not stop anything.  True -- if i was ACTUALLY USING the
table, i would not be able to drop or modify it but that is true of "real"
tables as well.
Rating: 4
placing the temporary table in tablespace August 06, 2002
Reviewer: john
1.can i place a global temporary table in a specified tablespace?
2.if yes it should be only in a temporary table space or any other tablespace
Followup:
1) no, it goes into the temp tablespace defined by the current schema id (eg: if
you access a temp table in a stored procedure, the temp space will be allocated
from the temporary tablespace of the OWNER of the procedure.  If you access a
temp table directly from SQL, it'll allocate space in the temp tablespace of the
currently logged in user).

A temp table is an interesting thing -- it is one non-partitioned table that can
exist in many tablespaces simultaneously - this is because it is really like a
"partitioned table" where the partition key is a session or transaction id.
Each "partition" is in its own (potentially) tablespace

2) only in temp tablespaces.
Rating: 4
ORA-14452 - But ME, NOBODY is using this GTT February 28, 2003
Reviewer: Robert from PA
Tom, in your reply to a 9/13/01 message, you saild :
>>This would ONLY affect a development system -- not a >>production system -- as
you would not be dropping and
>> recreating temporary tables in a production system,
>> they would be statically defined.

I am puzzled...
What do you mean "not dropping GTT in prod" ?
Things change and I'd like to drop'em and re-define them in the way I like the
columns arranged. What's wrong with that ?

And I searched out this page cause I got the error below:

LMS@LMS1 > drop table GLOBALTEMP_CBSI_DAILY_PROD ;
drop table GLOBALTEMP_CBSI_DAILY_PROD
           *
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table already
in use

I am not using this GTT, everyone has gone home...I kept re-connecting but
Oracle won't let it happen, any idea ?

Thanks
Followup:
column arrangement is IRRELEVANT, you must use insert into gtt(c1,c2,....)

Also -- in production -- you cannot tell me that you just willy nilly drop and
recreate objects just to get "columns in the right order" -- this is what VIEWS
are all about.

But -- I do recall in a very early release of temp tables -- they could get into
a state whereby you needed to "bounce" in order to drop them.  It would happen
if you KILLED a session that was inserting into them.

that was fixed in all current releases of 8iR3, 9iR1 and 9iR2

but if you are dropping them to put columns in order -- well.....
Rating: 5
Correct Use of Temporary Table August 10, 2003
Reviewer: Su Baba from Ca, USA
Hi Tom,

I have a situation where I have a big query (comlex joins and
may return anywhere between 0 to 1 million rows). Let's call
it <Big Query>.

Then I have 10 other dynamic SQL's that use <Big Query> as a
subquery.

SELECT ...
FROM   <some table>
WHERE  customer_id IN (<Big Query>);

SELECT ...
FROM   <some other table>
WHERE  customer_id IN (<Big Query>);

and repeat for 8 other tables.

Does it make sense to insert the result of the <Big Query>
into a temporary table to avoid running the <Big Query> for
more than once.

Also I'm thinking about indexing the temporary table and
use dbms_stat to creat the statistics for the table:

SELECT COUNT(*)
INTO   l_num_of_records
FROM   <temp table name>;

dbms_stats.set_table_stats(ownname => USER,
                           tabname => <temp table name>,
                           numrows => l_num_of_records,
                           numblks => ?,
                           avgrlen => ?);

So for the 10 dynamic SQL's mentioned above, I'll rewrite them to

SELECT ...
FROM   <some table>
WHERE  customer_id IN (<temp table name>);

Is this the right approach?

thanks
Followup:
version?  (makes a big difference in this case)
Rating: 5
August 10, 2003
Reviewer: Su Baba
Version: 8.1.7
Followup:
in 8i -- that would be ok - but DO NOT select count(*) from it!!! you already
KNOW how many rows you put in there (sql%rowcount, or sqlca.sqlerrd[2], or
whatever attribute your language provides)

Also, if this is used by many people at the same time -- watch out -- there can
only be ONE set of stats of course.
Rating: 5
August 10, 2003
Reviewer: Su Baba
This is a batch process that would only be run by one person at any one time.

Is there a much better way to accomplish this in 9i?
Followup:
we could skip the dbms_stats by using optimizer dynamic sampling in 9ir2, but
that is the only change really.
Rating: 5
Use dbms_stats to set statistics for a temporary table August 11, 2003
Reviewer: A reader
Hi Tom,

Taken from the above example,

dbms_stats.set_table_stats(ownname => USER,
                           tabname => <temp table name>,
                           numrows => l_num_of_records,
                           numblks => ?,
                           avgrlen => ?);

How do we figure out numblks and avgrlen programmatically?

thax
Followup:
well, you can just dbms_stats.gather_table_stats -- even on a temp table in 9i.

but you would "have to know" -- to programatically get them would be as compute
instense as asking dbms_stats to get them (so the answer would be to use
dbms_stats)
Rating: 5
August 11, 2003
Reviewer: A reader
Sorry, Tom. The database version is 8.1.7. Can I still use dbms_stats to do
that? thanks
Followup:
no, the gather_temp parameter was not available to dbms-stats in that release.
Rating: 3
August 11, 2003
Reviewer: A reader
So back to the question. How would I compute numblks and avgrlen? thanks

dbms_stats.set_table_stats(ownname => USER,
                           tabname => <temp table name>,
                           numrows => l_num_of_records,
                           numblks => ?,
                           avgrlen => ?);
Followup:
you would use GATHER, not SET
Rating: 3
August 11, 2003
Reviewer: A reader
Sorry for not being clear. I have Oracle 8.1.7 and would like to set the
statistics for a temporary table. Since "gather stats" won't work for a
temporary table in 8i, I assume I would have to compute numblks and avrglen
myself before I can set the statistics. What algorithm should I use to compute
these two parameters programmatically?
Followup:
well, I would just "know" (guesstimate) what avgrlen is (just take a nice
average given the type of data you put in there)

Then, you know how many rows you put in there (sql%rowcount)

rows * avgrlen = size of data

(blocksize - some_swag_at_overhead)/ size_of_data = nblocks estimate.

if I had an 8k block, i might use (7000) / size_of_data for example to
"guestimate" the amount of data.
Rating: 5
report print from global temporary table October 19, 2003
Reviewer: Dulal from Bangladesh
Hi Tom,

I have successfully created a global temporary table and inserted data into
this.
When I build a report using this temporary table there is no output but data in
temporary table already exits. What is the problem?

I am using client/server with Oracle8i (8.1.6), Report Builder 6i(6.0.8.8.3),
Win2000.

Kindly advice.
Followup:
sounds just like what this other guy asked:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1086033238621#13120451359967
Rating: 5
Global Temporary Table with Autonomous Transaction December 24, 2003
Reviewer: Sami from NJ,USA
Dear Tom,

Happy Holidays.

Thanks for all your support and help.

We are using GLOBAL TEMPORARY TABLE(transaction scope) in stored procedure(part
of the package). The functionality of the
stored procedure is to return CLOB in customized XML format(8i does not have
great XML features like 9i. Till 9i upgrade we have to live with this).
We use TEMPORARY table to store list of records based on the search condition
provided by Web-based application
and construct XML CLOB based on the records stored in the TEMPORARY table.Once
the XML clob is constricted
we can destroy the record by issuing COMMIT statement.
(Many web-application API is calling the stored procedure to get the result in
XML format.)
But the problem here is that the application which is invoking this stored
procedure may come up with some pending transactions, so we don't want to issue
COMMIT.

(Question-1)
What would be your recommendation here?
A) Use of autonomous transaction for the search stored procedure
B) Use DELETE statement before we store anything into TEMPORARY table

(Question-2)
Is there any known issues with connection pool application using TRANSACTION
based GLOBAL TEMPORARY TABLE.

I know we can not use "ON COMMIT PRESERVE ROWS" because the same database
session will be used by different web-application users.

Thanks in advance,
Sami
Followup:
i don't know why you need a gtt for this at all?

you can use an automous transaction, sure. (but I don't know why you would need
to put the rows into a gtt, just to read them out to format a clob?  why not
just read from the source in the first place)

there are no issues I'm aware of with connection pools and gtt's as long as they
are cleaned out between connection pool "grabs"
Rating: 5
How to use ARRAY values in "IN clause" December 24, 2003
Reviewer: Sami from NJ,USA
Dear Tom,

Thank you so much for your very quick response.
[Sorry,the TEMP table values are used in predicator not to construct XML clob]

PROCEDURE get_query_results (
      uid_array     IN       string_varray,   ----> Array of UserIDs
      name_array    IN       string_varray,   ----> this is to ensure correct
order of the SQL output
      xml_output    OUT      CLOB            ----> XML output.
) is
..
..

The stored procedure is getting String Array(user defined string_varray) from
Java(Sorry not from DB, I was wrong) and that array values should be used in "IN
clause" of select statement as a predicator. The select statements output will
be used to construct XML clob.

I use ARRAY_TO_COMMA_SEPARATED_VALUE will take String Array and return
VARCHAR2(all array values with comma separated).

A) Either I have to use dynamic SQL to prepare statement like

'select * from Tab1 where userid in (
'||ARRAY_TO_COMMA_SEPARATED_VALUE(uid_array)||');';

OR

(B)
ARRAY_TO_COMMA_SEPARATED_VALUE can insert values into TEMP table(from uid_array)
and my query can be modified as

select * from Tab1 where userid in (select userid from TEMP_TABLE)

Which one would be better in terms of performance? Do you suggest any other
better way of doing it?

Thanks Again
Sami
Followup:
i use method A myself -- but it does NOT need dynamic sql.

search for

varying in list

on this site for the technique.
December 24, 2003 -- Thanks for the question regarding "why so many recursive call?", version 8.1.6
You Asked
Tom:scott@WPDB>connect scott/tiger
Connected.
scott@WPDB>set autotrace on
scott@WPDB>select * from dept;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING     NEW YORK
        20 RESEARCH       DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS     BOSTON

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

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

then i connect system.

1* select sql_text,parse_calls,executions from v$sqlarea where
sql_text like '%dept%'
system@WPDB>/

SQL_TEXT
---------------------------------------------------------
PARSE_CALLS EXECUTIONS
----------- ----------
EXPLAIN PLAN SET STATEMENT_ID='PLUS249285' FOR select * from
dept
          1          1

select * from dept
          1          1

1.why the first one shows so many recursive calls?
2.when i run the "select * from dept" second times, the second
query shows me that parse_calls is 2, why reparse again, i think

it should in shared pool already.

3.when i select parse_calls,execution from v$sqlarea and find
that most of rows have SAME VALUE for parse_calls and execution.
anything wrong? any tuning suggestion?
there are some rows, execution is not zero while parse_calls is
zero. for some other rows, execution is zero while parse_calls
is not zero, how did this happen?
and we said...
Couple of issues here.  The bottom line in the above is that
everything is working 100% as expected.

Why 175 recursive calls?  You are seeing how expensive it is to
HARD parse a query.  It took 175 recursive calls to HARD parse
that query.  A hard parse is when we parse and cannot find the
query in the shared pool.

In the following example, I show the hard parse with 175
recursive calls followed by a re-execution of the same query --
with very few recursive calls.  The second call was a SOFT parse
-- we found a hit in the shared pool and it was much faster.

Lastly in the example, I put the query into a procedure and
execute the procedure 2 times.  This is to show how a correctly
coded program will reuse cursors efficiently.  When the query is
executed via the stored procedure, it is parsed only once --
instead of twice.  When the query is just run "as is" from
SQLPlus, it is parsed 2 times (as sql plus doesn't attempt to
cache cursors at all).

If the queries you see in your shared pool come from
applications you wrote, you need to see how they are using
cursors and whether they could use them more efficiently (eg: to
get the parse/execute ratio closer to ZERO instead of ONE).  A
soft parse is MUCH less overhead then a HARD parse but it is
overhead that can be avoided in well written programs (eg:
Prepare that statement once and execute it many times -- don't
close it until you won't ever need it again).

Here is an example demonstrating all of this:

tkyte@TKYTE816> connect scott/tiger
Connected.

scott@TKYTE816> set autotrace on
scott@TKYTE816> select * from dept;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING     NEW YORK
        20 RESEARCH       DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS     BOSTON

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

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

175 recursive calls to HARD parse the query above.  Now, lets
just re-run it:

scott@TKYTE816> select * from dept;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING     NEW YORK
        20 RESEARCH       DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS     BOSTON

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

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

Much better, no recursive calls to parse that time.  Now, lets
run a query from a procedure:

scott@TKYTE816> create or replace procedure p
  2  as
  3  begin
  4          for x in ( select * from dept ) loop
  5                  null;
  6          end loop;
  7  end;
  8  /
Procedure created.

scott@TKYTE816> exec p
PL/SQL procedure successfully completed.

scott@TKYTE816> exec p
PL/SQL procedure successfully completed.

scott@TKYTE816> connect tkyte/tkyte
Connected.

tkyte@TKYTE816> select sql_text,parse_calls,executions
  2    from v$sqlarea
  3   where lower(sql_text) like 'select%dept%'
  4  /

SQL_TEXT                                 PARSE_CALLS EXECUTIONS
---------------------------------------- ----------- ----------
SELECT *   FROM DEPT                               1          2
select * from dept                                 2          2
select sql_text,parse_calls,executions             1          1
 from v$sqlarea  where lower(sql_text) l
ike 'select%dept%'

See how the UPPER case query (the one run from PLSQL -- PLSQL
rewrites our queries and does it in UPPER case) was parsed once
and executed two times.  PLSQL very efficiently caches cursors
in the hope we will reuse them.  Sqlplus on the other hand
doesn't attempt to do that and caused us to parse the query 2
times -- once as a HARD parse and again later as a SOFT parse.

The goal is to get parse/executions to go near zero.  If a query
is executed exactly ONCE per program -- this is impossible for
that query.  If a query is executed 2 times per program, you
would execute a parse/exec ratio of 1/2 and so on.  The closer
to zero -- the better reuse you are getting.  When parse > 1 --
that means that there have been (n-1) SOFT (relatively
inexpensive) parses and 1 HARD (expensive) parses.
Review & Followup

Rating: 5
March 27, 2001
Reviewer: Ana Redondo from UK
Rating: 5
Why they are so different with and without hint? October 18, 2002
Reviewer: A Reader from USA
Hi Tom,
I have a query that joins two tables (the tables are almost identical in PKs).
The query virtuall returns all rows in the two tables. When I do the query
without any hint, it take very very long time to come back.  When I add a hint,
first_rows or rule, the query returns results in a tenth of a second.  Please
take a look at the plans. It can be seen that one uses MERG JOIN (much slower)
and the other uses NESTED LOOPS (much much faster). I have a few questions about
the plans.

I have no problem with the hint but some developers say for this simple join,
there should be no hints needed because it's a simple join, and they don't like
to use hint when doing the query that is not in a application, because at first
none knows the hint is needed and the query will "dead" there.  They just want
to see the data first.

1.  Without hint, why the "select" generates so many redos without the hint and
no redos with the hint?  I thought redos are generated only when something
change and not in the "select".
2.  Why "recursive calls" is so high without the hint?
3.  Why the "sort" are different for the two queries?
4.  Is there a way to do that other than set the Optimizer=RULE, which Oracle
does not recommend  (also,  other queries may not Benefit from setting the
Optimizer mode to rule, plus the first_rows is a cost based hint). Or, do I need
to do something different in the indexing?

Thank you very much for every thing.

  1* select * from t1 a, t2 b where a.PK_col=b.PK_col
SQL> /

777679 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=133146 Card=777669 B
          ytes=491486808)

   1    0   MERGE JOIN (Cost=133146 Card=777669 Bytes=491486808)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 't1' (Cost=48664
           Card=777669 Bytes=322732635)

   3    2       INDEX (FULL SCAN) OF 'PK1' (UNIQUE) (Cost=1618 Ca
          rd=777669)

   4    1     SORT (JOIN) (Cost=82979 Card=777679 Bytes=168756343)
   5    4       TABLE ACCESS (FULL) OF 't2' (Cost=1503 C
          ard=777679 Bytes=168756343)

Statistics
----------------------------------------------------------
      86851  recursive calls
     344362  db block gets
     223404  consistent gets
     119824  physical reads
    1944948  redo size
  212036198  bytes sent via SQL*Net to client
    5755220  bytes received via SQL*Net from client
      51847  SQL*Net roundtrips to/from client
          2  sorts (memory)
          1  sorts (disk)
     777679  rows processed

SQL> ed
Wrote file afiedt.buf

  1* select /*+ FIRST_ROWS */ * from t1 a, t2 b where a.PK_col=b.INTBLDDBF
SQL> /

777679 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=1556861 Ca
          rd=777669 Bytes=491486808)

   1    0   NESTED LOOPS (Cost=1556861 Card=777669 Bytes=491486808)
   2    1     TABLE ACCESS (FULL) OF 't2' (Cost=1503 Car
          d=777679 Bytes=168756343)

   3    1     TABLE ACCESS (BY INDEX ROWID) OF 't1' (Cost=2 Car
          d=777669 Bytes=322732635)

   4    3       INDEX (UNIQUE SCAN) OF 'PK1' (UNIQUE) (Cost=1 Car
          d=777669)

Statistics
----------------------------------------------------------
          0  recursive calls
         13  db block gets
    3187635  consistent gets
      71118  physical reads
          0  redo size
  212065498  bytes sent via SQL*Net to client
    5755220  bytes received via SQL*Net from client
      51847  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
     777679  rows processed
Followup:
tell me -- which one returned ALL of the data -- I don't believe you when you
say the second one returned in 1/10 of a second.  Was that the LAST ROW returned
or the FIRST row?
Rating: 5
We can see the result faster October 18, 2002
Reviewer: A Reader from USA
Thanks Tom.  With the hint, we can see the results in 1/10 of a second! Either
in SQLPLUS or in TOAD (that's what the developers use) the results come back
"immediatly" with the hint. Of aourse in SQLPLUS it takes long long time to
finish deploying data.  In TOAD, the results can be seen immediatly (when we try
to scroll to the end of the result set, it also takes long long time).  Without
the hint, the screen will be blank for a long long time and we cannot do
anything except waiting or terminating the process.
Anyway, what is key to this?  The merge join, the recursive calls, or the redo
sizes (please see the plans)?  Can we do it fast without the hint?
Thank you again for your help.
Followup:
Well -- it should be very clear then -- ESPECIALLY given the hint no???

For any given query, there are tons of ways to executed it.  We optimized for
throughput (GET ME THE ENTIRE RESULT SET ASAP).  In order to do that -- we do
lots of work up front and then blast the data to you.  The first query takes
longer to get the first row but gets you that last row ASAP.

The second query, using INCREDIBLY less efficient INDEXES (yes, take note:
INDEXES <> FAST), returns the first row rapidly -- but you would find that the
LAST ROW takes lots lots longer to get

The statistics show you that.

so, you used a FIRST_ROWS hint which says "please favor plans that get me the
first row ASAP".  It just did what you asked it to.

In an stored proceudre -- the first plan is right.  You want to get the last row
ASAP (cause you are sitting there waiting for the procedure to FINISH).

In an interactive application, you might want the FIRST ROW first -- meaning,
that last row will take much longer to get to but the end user can start working
with the data as soon as it is ready.
Rating: 5
What a bout the redos? October 18, 2002
Reviewer: A Reader from USA
Thank you very much.  But I still don't understand why a "select" generates so
many redos (1944948  redo size) when not using the hint (using the "rule" hint
is also fast in getting the first row because it uses nested loop when joning
the two tables).  When using the hint, the redo size is 0.
Thanks.
Followup:
And next time you run without the hint, it'll generate NO redo as well.

You are seeing the block cleanout.  If you have my book "Expert One on One
Oracle" -- i got into this in detail (search this site for

delayed "block cleanout"

for other writeups as well).  Consider this session where I artificially induce
the same thing you are seeing by playing with my cache size and doing a bulk
update:

ops$tkyte@ORA920.US.ORACLE.COM> create table t as select * from all_objects;
Table created.

ops$tkyte@ORA920.US.ORACLE.COM> insert /*+ append */ into t select * from t;
29301 rows created.

ops$tkyte@ORA920.US.ORACLE.COM> commit;
Commit complete.

....

ops$tkyte@ORA920.US.ORACLE.COM> insert /*+ append */ into t select * from t;
468816 rows created.

ops$tkyte@ORA920.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA920.US.ORACLE.COM> alter system set db_cache_size = 1048576 scope =
memory;

System altered.

ops$tkyte@ORA920.US.ORACLE.COM> update t set object_id = object_id;
937632 rows updated.

ops$tkyte@ORA920.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA920.US.ORACLE.COM> alter system set db_cache_size = 33554432 scope
= memory;
System altered.

ops$tkyte@ORA920.US.ORACLE.COM> set autotrace traceonly;
ops$tkyte@ORA920.US.ORACLE.COM> select * from t;

937632 rows selected.

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

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      87164  consistent gets
      12785  physical reads
     767160  redo size
   66677847  bytes sent via SQL*Net to client
     688087  bytes received via SQL*Net from client
      62510  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     937632  rows processed

ops$tkyte@ORA920.US.ORACLE.COM> select /*+ first_rows */ * from t;

937632 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=42 Card=33979
Bytes=4349312)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=42 Card=33979 Bytes=4349312)

Statistics
----------------------------------------------------------
        262  recursive calls
          0  db block gets
      74406  consistent gets
      11628  physical reads
          0  redo size
   66677847  bytes sent via SQL*Net to client
     688087  bytes received via SQL*Net from client
      62510  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     937632  rows processed

so, can we conclude that first_rows must mean "redo = no" -- no, you cannot:

ops$tkyte@ORA920.US.ORACLE.COM> select * from t;

937632 rows selected.

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

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      74379  consistent gets
       9706  physical reads
          0  redo size
   66677847  bytes sent via SQL*Net to client
     688087  bytes received via SQL*Net from client
      62510  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     937632  rows processed

ops$tkyte@ORA920.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA920.US.ORACLE.COM>

It is that the first query did the work we no longer had to do...

(make sure your TEMPORARY tablespace is really and honestly a temporary
tablespace too!!!)
Rating: 5
are you using c or pro * c ? October 18, 2002
Reviewer: A reader
Hi tom,

   when you say you are using c, parse plsql,
so after parsing pl/sql code do you
directly operate on the datafiles ?
 or do you connect to oracle ?

if you do it directly how ?
if you are connecting using "C" not pro*c how ?

-- thanks
Followup:
where did "c" come into this question?
Rating: 4
Recursive calls after DB upgrade May 09, 2003
Reviewer: A reader from NJ
Hi Tom,

We have recently upgraded our test database from 8i to 9i, and one of our
queries using Intermedia is now behaving poorly. The query is simply joining 2
tables, one with 190 rows, and the other with 53 rows. It is in the latter where
we have the text search. The table statistics have been updated, and indexes are
in place.

We were able to rewrite the query for it to perform better, but would really
like to know as to what could be causing this strange behaviour.

We can send you more information if needed, but in the meantime, we are
attaching some of the trace info.

Thank you very much.

----------------------------------------------------------
-- TEST ENVIRONMENT
--  190 records on RING
--  53 records on RF_STATE
--    Oracle9i Enterprise Edition Release 9.2.0.3.0 - 64bit Production
--    PL/SQL Release 9.2.0.3.0 - Production
--    CORE    9.2.0.3.0       Production
--    TNS for Solaris: Version 9.2.0.3.0 - Production
--    NLSRTL Version 9.2.0.3.0 - Production
--
--  6 seconds to run before rewrite, less than 1 second after re-write.
--
----------------------------------------------------------

    ----------------------------------------------------------
    -- Original Query
    ----------------------------------------------------------
    SELECT RING.RINGINT_ID,'RING.LOPEZ' FROM RING, RF_STATE WHERE
(RING.RING_PROV_ST = RF_STATE.STATE_ABBR AND contains(RF_STATE.STATE_DESC,
'!?({LOPEZ})',1) > 0)
        Statistics
        ----------------------------------------------------------
              31768  recursive calls
                  0  db block gets
              15680  consistent gets
                  1  physical reads
                  0  redo size
                169  bytes sent via SQL*Net to client
                234  bytes received via SQL*Net from client
                  1  SQL*Net roundtrips to/from client
                  8  sorts (memory)
                  0  sorts (disk)
              0  rows processed

    ----------------------------------------------------------
    -- Re-Written Query
    ----------------------------------------------------------
    SELECT  RING.RINGINT_ID,'RING.LOPEZ'
    FROM    RING, (select state_abbr from rf_state where
contains(RF_STATE.STATE_DESC, '!?({LOPEZ})',1) > 0) rf_state
    WHERE   RING.RING_PROV_ST = rf_state.state_abbr
        Statistics
        ---------------------------------------------------------
                165  recursive calls
                  0  db block gets
                 81  consistent gets
                  0  physical reads
                  0  redo size
                169  bytes sent via SQL*Net to client
                233  bytes received via SQL*Net from client
                  1  SQL*Net roundtrips to/from client
                  0  sorts (memory)
                  0  sorts (disk)
                  0  rows processed

----------------------------------------------------------
-- PROD ENVIRONMENT
--  930 records on RING
--  53 records on RF_STATE
--    Oracle8i Enterprise Edition Release 8.1.7.2.0 - Production
--    PL/SQL Release 8.1.7.2.0 - Production
--    CORE    8.1.7.0.0       Production
--    TNS for Solaris: Version 8.1.7.2.0 - Production
--    NLSRTL Version 3.4.1.0.0 - Production
--
--  LESS THAN 1 SECOND TO RUN BOTH BEFORE AND AFTER RE-WRITE.
--
----------------------------------------------------------

    ----------------------------------------------------------
    -- Original Query
    ----------------------------------------------------------
    SELECT RING.RINGINT_ID,'RING.LOPEZ' FROM RING, RF_STATE WHERE
(RING.RING_PROV_ST = RF_STATE.STATE_ABBR AND contains(RF_STATE.STATE_DESC,
'!?({LOPEZ})',1) > 0)
        Oracle 8.1.7
        Statistics
        ----------------------------------------------------------
                165  recursive calls
                  0  db block gets
                 81  consistent gets
                  0  physical reads
                  0  redo size
                161  bytes sent via SQL*Net to client
                258  bytes received via SQL*Net from client
                  2  SQL*Net roundtrips to/from client
                  0  sorts (memory)
                  0  sorts (disk)
                  0  rows processed

    ----------------------------------------------------------
    -- Re-Written Query
    ----------------------------------------------------------
    SELECT  RING.RINGINT_ID,'RING.LOPEZ'
    FROM    RING, (select state_abbr from rf_state where
contains(RF_STATE.STATE_DESC, '!?({LOPEZ})',1) > 0) rf_state
    WHERE   RING.RING_PROV_ST = rf_state.state_abbr
        Statistics
        ----------------------------------------------------------
                712  recursive calls
                  0  db block gets
                357  consistent gets
                  0  physical reads
                  0  redo size
                167  bytes sent via SQL*Net to client
                307  bytes received via SQL*Net from client
                  2  SQL*Net roundtrips to/from client
                  2  sorts (memory)
                  0  sorts (disk)
                  0  rows processed
Followup:
I suppose all tables are analyzed and stats are upto date right?  cause text
queries always use the CBO...

the thing one would need to see would be the output of

set autotrace traceonly explain
<run the queries>
Rating: 4
Recursive calls after DB upgrade May 09, 2003
Reviewer: A reader
Hi Tom,

Thank you very much for your quick reply. Yes, all tables are analyzed and stats
are upto date, with compute statistics.

Here's the result of the set autotrace traceonly explain...

    ----------------------------------------------------------
    -- Original Query IN TEST
    ----------------------------------------------------------
    SELECT RING.RINGINT_ID,'RING.LOPEZ' FROM RING, RF_STATE WHERE
(RING.RING_PROV_ST = RF_STATE.STATE_ABBR AND contains(RF_STATE.STATE_DESC,
'!?({LOPEZ})',1) > 0)
        Execution Plan
        ----------------------------------------------------------
           0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=21)
           1    0   NESTED LOOPS (Cost=3 Card=1 Bytes=21)
           2    1     TABLE ACCESS (FULL) OF 'RING' (Cost=2 Card=190 Bytes=950
                  )

           3    1     TABLE ACCESS (BY INDEX ROWID) OF 'RF_STATE' (Cost=3 Card
                  =1 Bytes=16)

           4    3       DOMAIN INDEX OF 'R_RF_STATE_CONTEXT_IX' (Cost=0 Card=5
                  3)

    ----------------------------------------------------------
    -- Re-Written Query  IN TEST
    ----------------------------------------------------------
    SELECT  RING.RINGINT_ID,'RING.LOPEZ'
    FROM    RING, (select state_abbr from rf_state where
contains(RF_STATE.STATE_DESC, '!?({LOPEZ})',1) > 0) rf_state
    WHERE   RING.RING_PROV_ST = rf_state.state_abbr
        Execution Plan
        ----------------------------------------------------------
           0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=21)
           1    0   TABLE ACCESS (BY INDEX ROWID) OF 'RING' (Cost=2 Card=6 Byt
                  es=30)

           2    1     NESTED LOOPS (Cost=4 Card=1 Bytes=21)
           3    2       TABLE ACCESS (BY INDEX ROWID) OF 'RF_STATE' (Cost=2 Ca
                  rd=1 Bytes=16)
           4    3         DOMAIN INDEX OF 'R_RF_STATE_CONTEXT_IX' (Cost=0)
           5    2       INDEX (RANGE SCAN) OF 'R_RING_PROV_ST_IX' (NON-UNIQUE)

    ----------------------------------------------------------
    -- Original Query IN PROD
    ----------------------------------------------------------
    SELECT RING.RINGINT_ID,'RING.LOPEZ' FROM RING, RF_STATE WHERE
(RING.RING_PROV_ST = RF_STATE.STATE_ABBR AND contains(RF_STATE.STATE_DESC,
'!?({LOPEZ})',1) > 0)
        Execution Plan
        ----------------------------------------------------------
           0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=20 Bytes=380)
           1    0   NESTED LOOPS (Cost=5 Card=20 Bytes=380)
           2    1     TABLE ACCESS (BY INDEX ROWID) OF 'RF_STATE' (Cost=1 Card
                  =1 Bytes=12)

           3    2       DOMAIN INDEX OF 'R_RF_SATE_CONTEXT_IX' (Cost=1)
           4    1     TABLE ACCESS (FULL) OF 'RING' (Cost=3 Card=927 Bytes=648
                  9)

    ----------------------------------------------------------
    -- Re-Written Query  IN PROD
    ----------------------------------------------------------
    SELECT  RING.RINGINT_ID,'RING.LOPEZ'
    FROM    RING, (select state_abbr from rf_state where
contains(RF_STATE.STATE_DESC, '!?({LOPEZ})',1) > 0) rf_state
    WHERE   RING.RING_PROV_ST = rf_state.state_abbr
        Execution Plan
        ----------------------------------------------------------
           0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=20 Bytes=380)
           1    0   NESTED LOOPS (Cost=5 Card=20 Bytes=380)
           2    1     TABLE ACCESS (BY INDEX ROWID) OF 'RF_STATE' (Cost=1 Card
                  =1 Bytes=12)

           3    2       DOMAIN INDEX OF 'R_RF_SATE_CONTEXT_IX' (Cost=1)
           4    1     TABLE ACCESS (FULL) OF 'RING' (Cost=3 Card=927 Bytes=648
                  9)
Followup:
and now the tkprofs
Rating: 4
Recursive calls after DB upgrade May 12, 2003
Reviewer: A reader
Hi Tom,

Here are the results of the tkprofs.

----------------------------------------------------------
-- TEST ENVIRONMENT
----------------------------------------------------------
TKPROF: Release 9.2.0.3.0 - Production on Mon May 12 08:47:31 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Trace file: accuring_ora_20032.trc
Sort options: default

********************************************************************************
count    = number of times OCI procedure was executed
cpu      = cpu time in seconds executing
elapsed  = elapsed time in seconds executing
disk     = number of physical reads of buffers from disk
query    = number of buffers gotten for consistent read
current  = number of buffers gotten in current mode (usually for update)
rows     = number of rows processed by the fetch or execute call
********************************************************************************

alter session set sql_trace=true

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        1      0.00       0.00          0          0          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing
Followup:
nothing there.

just cut and paste the RELEVANT portion of the report - the queries we are
looking at -- just their sections
Rating: 4
Recursive calls after DB upgrade May 12, 2003
Reviewer: A reader
Hi Tom,

The first attempt for the tkprof did not quite submit right, due to the number
of words in the tkprofs. Here's the one for Test, part 1, with original query:

SELECT RING.RINGINT_ID,:"SYS_B_0"
FROM
 RING, RF_STATE WHERE (RING.RING_PROV_ST = RF_STATE.STATE_ABBR AND
  contains(RF_STATE.STATE_DESC, :"SYS_B_1",:"SYS_B_2") > :"SYS_B_3")

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      6.33       6.59          0         24          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      6.33       6.59          0         24          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 35

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  NESTED LOOPS  (cr=15418 r=0 w=0 time=8546868 us)
    190   TABLE ACCESS FULL RING (cr=6 r=0 w=0 time=1713 us)
      0   TABLE ACCESS BY INDEX ROWID RF_STATE (cr=0 r=0 w=0 time=29775 us)
      0    DOMAIN INDEX  R_RF_STATE_CONTEXT_IX (cr=0 r=0 w=0 time=28734 us)

********************************************************************************

select metadata
from
 kopm$  where name='DB_FDO'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          2          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          2          0           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID OBJ#(286) (cr=2 r=0 w=0 time=167 us)
      1   INDEX UNIQUE SCAN OBJ#(287) (cr=1 r=0 w=0 time=27 us)(object id 287)

********************************************************************************

select user#
from
 sys.user$ where name = 'OUTLN'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          2          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          2          0           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 2)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID OBJ#(22) (cr=2 r=0 w=0 time=55 us)
      1   INDEX UNIQUE SCAN OBJ#(41) (cr=1 r=0 w=0 time=27 us)(object id 41)

********************************************************************************

BEGIN :p := CTX_QUERY.PREFERENCE;END;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse      191      0.05       0.08          0          0          0           0
Execute    191      0.04       0.03          0          0          0         191
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      382      0.09       0.11          0          0          0         191

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 19     (recursive depth: 1)
********************************************************************************

SELECT/*+INDEX(T "DR$R_RF_STATE_CONTEXT_IX$X")*/ DISTINCT TOKEN_TEXT FROM
  "ACR_TEST"."DR$R_RF_STATE_CONTEXT_IX$I" T WHERE TOKEN_TEXT LIKE :lkexpr and
  TOKEN_TYPE NOT IN (1, 2, 5, 9)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse      382      0.02       0.01          0          0          0           0
Execute  14898      1.02       1.14          0          0          0           0
Fetch    14898      0.56       0.62          0      14898          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    30178      1.60       1.79          0      14898          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 19     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  SORT UNIQUE NOSORT (cr=33 r=0 w=0 time=1139 us)
      0   INDEX RANGE SCAN DR$R_RF_STATE_CONTEXT_IX$X (cr=33 r=0 w=0 time=967
us)(object id 10710)

********************************************************************************

SELECT IDX_DOCID_COUNT
FROM
 DR$INDEX WHERE IDX_ID = :idxid

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse      191      0.01       0.00          0          0          0           0
Execute    191      0.02       0.01          0          0          0           0
Fetch      191      0.03       0.00          0        382          0         191
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      573      0.06       0.03          0        382          0         191

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 19     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID DR$INDEX (cr=2 r=0 w=0 time=48 us)
      1   INDEX UNIQUE SCAN DRC$IDX_KEY (cr=1 r=0 w=0 time=26 us)(object id
3204)

********************************************************************************

SELECT /*+ INDEX(i) */ TOKEN_FIRST,TOKEN_LAST,TOKEN_COUNT,ROWID
FROM
 "ACR_TEST"."DR$R_RF_STATE_CONTEXT_IX$I" i  WHERE TOKEN_TEXT = :word AND
  TOKEN_TYPE = :wtype  ORDER BY TOKEN_TEXT, TOKEN_TYPE, TOKEN_FIRST

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse      191      0.00       0.00          0          0          0           0
Execute    191      0.01       0.01          0          0          0           0
Fetch      191      0.01       0.00          0        191          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      573      0.02       0.03          0        191          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 19     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  INDEX RANGE SCAN DR$R_RF_STATE_CONTEXT_IX$X (cr=1 r=0 w=0 time=32
us)(object id 10710)

********************************************************************************
Followup:
the four queries only, lose the other stuff.
Rating: 4
Recursive calls after DB upgrade May 12, 2003
Reviewer: A reader
and here's the rest for TEST, with the rewritten query. Please let us know if
you also need the ones from PROD.

Thank you very much.

    SELECT  RING.RINGINT_ID,:"SYS_B_0"
    FROM    RING, (select state_abbr from rf_state where
contains(RF_STATE.STATE_DESC, :"SYS_B_1",:"SYS_B_2") > :"SYS_B_3") rf_state
    WHERE   RING.RING_PROV_ST = rf_state.state_abbr

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.04       0.03          0          0          0           0
Fetch        1      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.04       0.03          0          0          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 35

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS BY INDEX ROWID RING (cr=0 r=0 w=0 time=216 us)
      1   NESTED LOOPS  (cr=0 r=0 w=0 time=206 us)
      0    TABLE ACCESS BY INDEX ROWID RF_STATE (cr=0 r=0 w=0 time=201 us)
      0     DOMAIN INDEX  R_RF_STATE_CONTEXT_IX (cr=0 r=0 w=0 time=197 us)
      0    INDEX RANGE SCAN R_RING_PROV_ST_IX (cr=0 r=0 w=0 time=0 us)(object id
10684)

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        3      0.00       0.00          0          0          0           0
Execute      4      0.04       0.03          0          0          0           0
Fetch        2      6.33       6.59          0         24          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        9      6.37       6.62          0         24          0           0

Misses in library cache during parse: 0

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse      957      0.08       0.11          0          0          0           0
Execute  15473      1.09       1.20          0          0          0         191
Fetch    15282      0.60       0.64          0      15475          0         193
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    31712      1.77       1.96          0      15475          0         384

Misses in library cache during parse: 0

  959  user  SQL statements in session.
    2  internal SQL statements in session.
  961  SQL statements in session.
********************************************************************************
Trace file: accuring_ora_20032.trc
Trace file compatibility: 9.00.01
Sort options: default

       1  session in tracefile.
     959  user  SQL statements in trace file.
       2  internal SQL statements in trace file.
     961  SQL statements in trace file.
      10  unique SQL statements in trace file.
   36934  lines in trace file.
Followup:
based on the recursive sql, it is optimizer related and was supposed to have had
the fix in 9203 which you say you have.  Suggest you contact support and open a
tar, reference <Bug:2617683>  which looks like it (continue to use your
workaround of course)
Rating: 4
Recursive calls after DB upgrade May 12, 2003
Reviewer: A reader
Hi Tom,

Here's the tkprof from PROD.

SELECT RING.RINGINT_ID,'RING.LOPEZ'
FROM
 RING, RF_STATE WHERE (RING.RING_PROV_ST = RF_STATE.STATE_ABBR AND
  contains(RF_STATE.STATE_DESC, '!?({LOPEZ})',1) > 0)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.07       0.08          0          0          0           0
Fetch        1      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.07       0.08          0          0          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 26

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  NESTED LOOPS
      1   TABLE ACCESS BY INDEX ROWID RF_STATE
      1    DOMAIN INDEX
      0   TABLE ACCESS FULL RING

********************************************************************************

select metadata
from
 kopm$  where name='DB_FDO'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          2          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          2          0           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID KOPM$
      1   INDEX UNIQUE SCAN (object id 287)

********************************************************************************

select user#
from
 sys.user$ where name = 'OUTLN'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          2          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          2          0           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 2)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID USER$
      1   INDEX UNIQUE SCAN (object id 41)

********************************************************************************

BEGIN :p := CTX_QUERY.PREFERENCE;END;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      2      0.00       0.00          0          0          0           2
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          0          0           2

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 19     (recursive depth: 1)
********************************************************************************

SELECT/*+INDEX(T)*/ DISTINCT TOKEN_TEXT FROM
  "ACR_PROD"."DR$R_RF_SATE_CONTEXT_IX$I" T WHERE TOKEN_TEXT LIKE :lkexpr and
  TOKEN_TYPE NOT IN (1, 2, 5)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        4      0.01       0.00          0          0          0           0
Execute    156      0.02       0.02          0          0          0           0
Fetch      156      0.01       0.01          0        156          0           4
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      316      0.04       0.03          0        156          0           4

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 19     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT UNIQUE NOSORT
      1   INDEX RANGE SCAN (object id 5800)

********************************************************************************

SELECT IDX_DOCID_COUNT
FROM
 DR$INDEX WHERE IDX_ID = :idxid

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          4          0           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        6      0.00       0.00          0          4          0           2

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 19     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID DR$INDEX
      2   INDEX UNIQUE SCAN (object id 2999)

********************************************************************************

SELECT /*+ INDEX(i) */ TOKEN_FIRST,TOKEN_LAST,TOKEN_COUNT,ROWID
FROM
 "ACR_PROD"."DR$R_RF_SATE_CONTEXT_IX$I" i  WHERE TOKEN_TEXT = :word AND
  TOKEN_TYPE = :wtype  ORDER BY TOKEN_TEXT, TOKEN_TYPE, TOKEN_FIRST

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.01       0.00          0          0          0           0
Execute      2      0.00       0.01          0          0          0           0
Fetch        2      0.00       0.00          0          2          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        6      0.01       0.01          0          2          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 19     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  INDEX RANGE SCAN (object id 5800)

********************************************************************************

    SELECT  RING.RINGINT_ID,'RING.LOPEZ'
    FROM    RING, (select state_abbr from rf_state where
contains(RF_STATE.STATE_DESC, '!?({LOPEZ})',1) > 0) rf_state
    WHERE   RING.RING_PROV_ST = rf_state.state_abbr

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.06       0.06          0          0          0           0
Fetch        1      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.06       0.06          0          0          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 26

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  NESTED LOOPS
      1   TABLE ACCESS BY INDEX ROWID RF_STATE
      1    DOMAIN INDEX
      0   TABLE ACCESS FULL RING

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        3      0.00       0.00          0          0          0           0
Execute      4      0.13       0.15          0          0          0           0
Fetch        2      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        9      0.13       0.15          0          0          0           0

Misses in library cache during parse: 0

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       12      0.02       0.00          0          0          0           0
Execute    164      0.02       0.03          0          0          0           2
Fetch      162      0.01       0.01          0        166          0           8
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      338      0.05       0.04          0        166          0          10

Misses in library cache during parse: 0

   14  user  SQL statements in session.
    2  internal SQL statements in session.
   16  SQL statements in session.
********************************************************************************
Trace file: accuring_ora_2032.trc
Trace file compatibility: 8.00.04
Sort options: default

       1  session in tracefile.
      14  user  SQL statements in trace file.
       2  internal SQL statements in trace file.
      16  SQL statements in trace file.
      10  unique SQL statements in trace file.
     455  lines in trace file.
Rating: 4
Recursive calls after DB upgrade May 12, 2003
Reviewer: A reader
Hi Tom,

Thank you very much for the quick reply. We are already in touch with support,
but haven't really gotten further than they can't reproduce it, even after
sending the same information that we have sent, including test data for them to
use. We will check the referenced bug # for more information.

Please ignore our posting re PROD -- although it's too late by now :-).

Again, thanks a lot, and keep up the good work.
Rating: 4
Yes, it's a bug May 13, 2003
Reviewer: Jeff from NJ
Turns out it is a bug in the optimizer.
Our problem seemed to be related to bug# 2853398
The workaround is to set optimizer_features_enable = 8.1.7
Once we set it, the queries ran fine.

Thanks again for the help.
Rating: 5
32 Recursive calls- even after multiple run July 22, 2003
Reviewer: Saminathan Seerangan from NJ,USA
Dear Tom,

Thanks for all your help.

I am egtting 32 recursive calls, even after executing the same SQL over and over
again. Could you please explain the reason.

SQL> l
  1  SELECT  *  FROM (
  2     select a.*, rownum rnum from
  3             ( select /*+USE_HASH(RW_RFX_HEADER RW_RFX_BIDDER) */  RW_RFX_HEA
DER.*
  4             from    RW_RFX_HEADER RW_RFX_HEADER,
  5                     RW_RFX_BIDDER RW_RFX_BIDDER
  6             where   RW_RFX_BIDDER.RFX_ID  = RW_RFX_HEADER.ID
  7             AND     (
  8                     (RW_RFX_HEADER.STATUS=1014 AND UPPER(RW_RFX_HEADER.OWNER
_ID)='USER_00004M0I')
  9             AND     RW_RFX_HEADER.ARCHIVE_STATUS  <> 1015  AND  1 = 1
 10                     )
 11             and     (RW_RFX_BIDDER.LAST_BDDER_REV_NUM >= 2 or RW_RFX_BIDDER.
BIDDER_STATUS = 4816 )
 12             MINUS
 13             select /*+USE_HASH(RW_RFX_HEADER RW_RFX_BIDDER)*/  RW_RFX_HEADER
.*
 14             from    RW_RFX_HEADER RW_RFX_HEADER,
 15                     RW_RFX_BIDDER RW_RFX_BIDDER
 16             where   RW_RFX_BIDDER.RFX_ID  = RW_RFX_HEADER.ID
 17             AND     (
 18                     (RW_RFX_HEADER.STATUS=1014 AND UPPER(RW_RFX_HEADER.OWNER
_ID)='USER_00004M0I')
 19             AND     RW_RFX_HEADER.ARCHIVE_STATUS  <> 1015  AND  1 = 1
 20                     )
 21             and     (RW_RFX_BIDDER.LAST_BDDER_REV_NUM >= 2 or RW_RFX_BIDDER.
BIDDER_STATUS = 4816 )
 22             and     RW_RFX_HEADER.ID in (
 23                     select /*+USE_HASH(award header) */ award.RFX_ID
 24                     from    RW_RFX_AWARD award , RW_RFX_HEADER header
 25                     where   award.RFX_ID = header.ID
 26                     and     header.RFX_TYPE=1667
 27                     and     award.STATUS not in (6801,1015)
 28                     and     (award.RFX_ID, award.LAST_MODIFIED_DATE)
 29                             in (
 30                             select  RFX_ID, max(LAST_MODIFIED_DATE)
 31                             from    RW_RFX_AWARD
 32                             group by RFX_ID
 33                             )
 34                     )
 35             ) a
 36     where rownum <=17)
 37* WHERE rnum > 0
SQL> /

17 rows selected.

Elapsed: 00:00:06.06

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=268 Card=12 Bytes=14
          712)

   1    0   VIEW (Cost=268 Card=12 Bytes=14712)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=268 Card=12 Bytes=14556)
   4    3         MINUS
   5    4           SORT (UNIQUE) (Cost=86 Card=11 Bytes=3773)
   6    5             HASH JOIN (Cost=84 Card=11 Bytes=3773)
   7    6               TABLE ACCESS (FULL) OF 'RW_RFX_HEADER' (Cost=5
          3 Card=8 Bytes=2584)

   8    6               TABLE ACCESS (FULL) OF 'RW_RFX_BIDDER' (Cost=2
          6 Card=9781 Bytes=195620)

   9    4           SORT (UNIQUE) (Cost=182 Card=1 Bytes=408)
  10    9             HASH JOIN (Cost=180 Card=1 Bytes=408)
  11   10               HASH JOIN (Cost=149 Card=1 Bytes=388)
  12   11                 HASH JOIN (Cost=94 Card=1 Bytes=372)
  13   12                   HASH JOIN (Cost=65 Card=9 Bytes=3114)
  14   13                     TABLE ACCESS (FULL) OF 'RW_RFX_HEADER' (
          Cost=53 Card=8 Bytes=2584)

  15   13                     TABLE ACCESS (FULL) OF 'RW_RFX_AWARD' (C
          ost=10 Card=1494 Bytes=34362)

  16   12                   VIEW OF 'VW_NSO_1' (Cost=27 Card=1382 Byte
          s=35932)

  17   16                     SORT (GROUP BY) (Cost=27 Card=1382 Bytes
          =27640)

  18   17                       TABLE ACCESS (FULL) OF 'RW_RFX_AWARD'
          (Cost=10 Card=1684 Bytes=33680)

  19   11                 TABLE ACCESS (FULL) OF 'RW_RFX_HEADER' (Cost
          =53 Card=2518 Bytes=40288)

  20   10               TABLE ACCESS (FULL) OF 'RW_RFX_BIDDER' (Cost=2
          6 Card=9781 Bytes=195620)

Statistics
----------------------------------------------------------
         32  recursive calls
         39  db block gets
       1501  consistent gets
         48  physical reads
          0  redo size
       4815  bytes sent via SQL*Net to client
       1486  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          1  sorts (memory)
          2  sorts (disk)
         17  rows processed

SQL>
Followup:
what is your hash_area_size used?  could well be space mgmt for temp --
sql_trace it and see what you see in the trace file.
Rating: 5
question about plsql procedure use July 22, 2003
Reviewer: A reader
"PLSQL very efficiently caches cursors in the hope we will reuse them."

Looks like this is another reason (apart from other advantages in terms of
security, privilege, centralized
code, easy tuning etc.) to use plsql for code instead
of directly sending, say, queries from JDBC for example.
If you write your query in JDBC java code and execute
it (using PreparedStatement of course) then tuning it
is usually a pain! But looks like there are performance
bemefits as well if you put it in a procedure and
return a ref cursor instead.

Thanx Tom!
Followup:
but -- ref cursors are ALWAYS parsed each time, have to be (think about it --
you can call the same stored procedure that returns a ref cursor 1,000 times
before fetching a row from ANY of the cursors - it cannot cache them)
Rating: 5
thanx Tom July 23, 2003
Reviewer: A reader
"but -- ref cursors are ALWAYS parsed each time, have to be "

OF course! Sorry to have missed that line of thought -
which I learnt from your site only. You are right - then I must be
misunderstanding something.

Suppose, I have a JDBC program, in which I execute
the following using prepared statement.

"select * from emp".

I open, bind, fetch and close. Since in this case, I closed
the cursor, the next time I open, fetch etc., it needs to
be soft parsed. Now, if I did the following:

open
bind
fetch
execute
execute...
...
close (after this cursor is not needed)

then I avoid soft parsing. The above is not very easy
to do (esp. if you are using Connection Pooling.)

Now if I use plsql procedure to return a ref cursor,
it can not cache it again and we have the same issue.
So I suppose the cases where plsql cursor caching concept helps is in the
"intermittent" selects in a procedural logic and all other dml statements in
them (that dont return a ref cursor to the client) . So for example,
If I was doing a select * from emp and then isntead of returning a ref cursor, I
was returning another result based on that - THEN, there is an advantage in
putting
the logic in procedure versus executing the logic in JDBC
as it automatically caches my cursor.

So in jdbc, it would look like
In a loop

prepare statement "select * from emp"
fetch/execute
close
use the result to calculate something else
end loop

If you have to close the prepared statement (cause say
if you are using connection pooling and you dont have
a connection level cache for statements implemented)
then we have to close the statement in the loop cos the
connection that you get each time may be different.

Please see the following link to understand
what I am saying.
http://asktom.oracle.com/pls/ask/f?p=4950:8:1284523728552771800::::F4950_P8_DISPLAYID:10094489919905
Soooo, in this case, plsql procedure is a better way
as it automatically caches your "select * from emp" cursor.

Phew!
Does this make sense?

THANK YOU TOM!!
Followup:
(it is easy to do with a hash table based on the connection pool object -- you
can easily close everything after you are done -- but anyway)

You see - I don't think it matters in JDBC with connection pooling as you will
NOT be executing a select more then once in the java program.  If you find you
are -- you have code that SHOULD be in a stored procedure (where it will be
nicely cached).

In a single connection grab -- you will not have the same query executed over
and over -- if you do, you've done something wrong.
Rating: 5
thank you July 23, 2003
Reviewer: A reader
"In a single connection grab -- you will not have the same query executed over
and over -- if you do, you've done something wrong. "

Well, how about this scenario.

we grab a connection.
we do an operation in UI that results in a code
that executes "select * from emp".
after some time we execute the same ui operation for
something else - same query but different "bind variables".
again, we execute the same query in the same session.
Now the connection could potentially be the same exact
connection that you used earlier or a new one. In this
case we are using the same connection grab for the same
query but since we closed the cursor in our earlier
"iteration", we will have to soft parse again.
So it is possible to execute the same query in the same
section again with the same connection (but with
different bind variables) I think I am missing something
here definitely.

Thanx again!
Followup:
seems somewhat "doubtful" that you would have the same exact sql SELECT on the
same exact screen - give me "real world"
Rating: 5
here you go July 23, 2003
Reviewer: A reader
"seems somewhat "doubtful" that you would have the same exact sql SELECT on the
same exact screen - give me "real world"

Say you have a screen where you are comparing some
record of employee Tom and Alex (that requires
a complicated transformation invloving the above select
* from emp) The above logic
that I mentioned is executed as part of this comparison
then in the same screen you choose two different employees
Foo and Bar and execute the same logic for them.
This must be a fairly common scenario - the example
may be contrived but you get the idea. You have a screen
that executes same logic but for different "bind variables".

In the sam
Followup:
i would "join" or i would "fetch two rows"
Rating: 5
well.. July 24, 2003
Reviewer: A reader
"i would "join" or i would "fetch two rows"

The UI screen has logic for doing complex transformation
of two employees at a time.
You go there - ask for the transformation of two employees
Jack and Jill. It carries out the task.
After going through some other screens (unrelated) - you again come back and ask
the same operation but for two other employees this time (Joe and Tim.)

You are clearly executing the same logic but for different
bind variables here. If your transformation logic
is in plsql procedure - you are saved (not just in terms
of performance but in terms of all other benefits of
plsql procedure.) If your transformation logic is in
client code then you run into the problem of not
having saved cursors at the minimum.

Thanx!
Followup:
still - fetch two rows = complex transformation

give me the REAL WORLD case you are hitting, I'll gladly respond to that.

Hypothetical situations, not.
Rating: 5
ok July 24, 2003
Reviewer: A reader
well, we had a situation where we were comparing
two "entities" with different attributes. The entities got stored in multiple
normalized tables.

So Entity E1, E2 were stored in, say ->
entity_parent_table
   -> entity_child_table1,
   -> entity_child_table2,
   -> entity_child_table3
each of the child table has fk relationship with the
entity_parent_table.

Now the comparison logic has to compare these two
entities in one screen - somthing like (simplified here).

Choose Entity # 1 to compare = E1
Choose Entity # 2 to compare = E2
Press <Compare> to start comparison.

The comparison involved comparing each of the tables
for records containing the entities. This of course
should be done in plsql and entire results returned
in the format so that we can display it.
But another (less efficient) way could be,
1. Select the two entities E1, E2 from database - store
   them in Java objects.
2. Do the comparison in middle tier.
3. Show the result to the user.

If you take this approach, then every time, I would
"bind" with different values for the entities
being compared in the same session.

Here the "transformation logic" is that of comparison.

Thanx!
Followup:
this is a stored procedure then isn't it.  no worries about binding at all
there.

back to square one in this discussion.  I don't go for known to be "less
efficient" (in so many ways)
Rating: 5
thanx July 25, 2003
Reviewer: A reader
"no worries about binding at all there."
Actually, I was not talking about binding - I was talking
about plsql procedure versus having an sql in java and
then submitting it using JDBC. The latter will benefit
from "saving of cursors" - former would not.

In the above case, we put the "comparison" logic in
plsql eventually but before that we had the logic
that was the "inefficient" way.

So in short - if you are getting some rows and
doing something with it and then returning it to the client
for display - do the entire logic in plsql and
then return it - else (apart from other disadvantages)
you lose out on "cursor saving" advantage of plsql too.

Have a great vacation and thanx for entertaining qs
even during your vacation! I can literally hear your wife and
kids screaming at you (*LEAVE THAT LAPTOP ALONE!!*:)
Followup:
that is exactly why it would be in a stored procedure, so we are back to the
"discussion is moot point", the process belongs in the database apparently :)
Rating: 4
HIGH SQL Parses September 02, 2003
Reviewer: juancarlosreyesp@yahoo.com from Bolivia
Hi Tom I found a script from Burleston
select sql_text,parse_calls,executions
from v$sqlarea
where parse_calls > 300 and executions <2*parse_calls
He says a good starting point for detailed SQL TUNING

from V$sqlarea

SQL_TEXT
SELECT DB_SGR_TIENE_ROL('CON_USO_CTA_RESTRINGIDA')   FROM DUAL
(the function DB_SGR_TIENE_ROL('CON_USO_CTA_RESTRINGIDA'), is a simple
function.)

PARSE_CALLS 2494
EXECUTIONS 2494
ROWS PROCESSED 2494
LOADS 1
BUFFER GETS 84798

1) My question is, if there is a function in the query
then is normal to get a relation parse_calls vs. executions like this? (because
soft parses)

2) Why 84798 buffer gets, 84798*8 = 678384 K???

Thanks
Followup:
his query says "find queries that were parsed more then 300 times in your system
and only executed on average 1 or 2 times"

this is sound.  you would like to

a) parse once
b) execute over and over

it is much harder to achieve in "3 tier" with connection pooling -- but you
should do it where possible.

your query did:

ops$tkyte@ORA920> select 84798/2494 from dual;

84798/2494
----------
34.0008019

34 logical IO's per execution.  that means in addition to the LIO's for reading
from dual (why would you select this from dual????? just run the darn thing! --
no need to add the extra LIO's that dual adds!!!) your function did about 30
logical IO's itself every time it was run.
Rating: 4
One question more please September 02, 2003
Reviewer: A reader
Sorry I missed this question, that continues to the previous

Why elapsed time is as much
14534381 100th seconds

select 14534381/(100*60)
from dual

2422.39 minutes??

Thank you
Followup:
that only you can answer.  why is your function taking so long to run -- you'll
have to tell us that.
Rating: 5
:) Thanks tom good luck in Otawa September 03, 2003
Reviewer: A reader
Rating: 5
Can you give to me a hint on how to optimize that function :) September 03, 2003
Reviewer: juancarlosreyesp@yahoo.com from Bolivia
FUNCTION  db_sgr_tiene_rol  ( cRol  VARCHAR2 , cUser VARCHAR2 DEFAULT USER )
RETURN  VARCHAR2 IS
 cRet    VARCHAR2(1) ;
 nCount  NUMBER;
BEGIN
 SELECT COUNT(*) INTO nCount
 FROM DBA_ROLE_PRIVS
 WHERE GRANTED_ROLE = cRol AND GRANTEE = cUser ;

 IF nCount = 0 THEN
  cRet := 'F';
 ELSE
  cRet := 'T';
 END IF;
 RETURN cRet ;
END;
Followup:
you'll be hard pressed to make dba_role_privs a high performing sql query on a
big system.

i see one below this -- haven't verified it but -- it should just be:

FUNCTION DB_SGR_TIENE_ROL  ( cRol  VARCHAR2 , cUser VARCHAR2 DEFAULT USER )
RETURN  VARCHAR2 IS
 cRet    VARCHAR2(1) := 'F';
BEGIN

select /*+ ordered USE_NL (sa) */
          'T'
INTO cRet
      from SYS.user$ u2
        , SYS.defrole$ ud
        , SYS.sysauth$ sa
        , SYS.user$ u1
      where sa.grantee# = ud.user#(+)
        and sa.privilege# = ud.role#(+)
        and u1.user# = sa.grantee#
        and u2.user# = sa.privilege#
        and u2.name = cRol
        and decode(sa.grantee#, 1, 'PUBLIC', u1.name) = cUser
        AND ROWNUM = 1;
  return cRet;
exception
  when no_data_found then return cRet;
end;
Rating: 5
I got this September 03, 2003
Reviewer: A reader
I improved the function so:
( the reason because I use dual, is because there is a bug in developer 2000 6i
when calling functions with default values, so I have to use dual table)

FUNCTION DB_SGR_TIENE_ROL  ( cRol  VARCHAR2 , cUser VARCHAR2 DEFAULT USER )
RETURN  VARCHAR2 IS

 cRet    VARCHAR2(1) ;
 nCount  NUMBER;
BEGIN

select /*+ ordered USE_NL (sa) */
          COUNT(*)
INTO ncount
      from SYS.user$ u2
        , SYS.defrole$ ud
        , SYS.sysauth$ sa
        , SYS.user$ u1
      where sa.grantee# = ud.user#(+)
        and sa.privilege# = ud.role#(+)
        and u1.user# = sa.grantee#
        and u2.user# = sa.privilege#
        and u2.name = cRol
        and decode(sa.grantee#, 1, 'PUBLIC', u1.name) = cUser;

 IF nCount = 0 THEN
  cRet := 'F';
 ELSE
  cRet := 'T';
 END IF;

 RETURN cRet ;

END;

Thanks :)
Rating: 5
;) Thanks your query is better September 03, 2003
Reviewer: A reader
Rating: 4
Recursive calls with GLOBAL temporary table December 24, 2003
Reviewer: Sami from NJ,USA
Dear Tom,

Is there any specific reason why GLOBAL TMPORARY TABLE have more recursive
calls? Will performace would be bad in temp table if we insert huge number of
records into TEMP table?

SQL> CREATE TABLE t ( a VARCHAR2(10) );
Table created.

SQL> INSERT INTO t SELECT 'x' FROM all_objects;
25698 rows created.

Statistics
----------------------------------------------------------
         80  recursive calls
        954  db block gets
     160298  consistent gets
          0  physical reads
     380236  redo size
        853  bytes sent via SQL*Net to client
        574  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      25698  rows processed

SQL>  CREATE GLOBAL TEMPORARY TABLE gtt1 ( a VARCHAR2(10) ) ON COMMIT PRESERVE
ROWS;
Table created.

SQL>  INSERT INTO gtt1  SELECT 'x' FROM all_objects;
25699 rows created.

Statistics
----------------------------------------------------------
        208  recursive calls
        970  db block gets
     160313  consistent gets
          0  physical reads
      90572  redo size
        853  bytes sent via SQL*Net to client
        579  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      25699  rows processed

Thanks
Sami
Followup:
run them again, you are seeing hard parse effects there.
Rating: 5
GTT recursive calls are always higher than NON-TEMP table even after multiple run December 24, 2003
Reviewer: Sami from NJ,USA
Deat Tom,

GTT recursive calls are always higher than NON-TEMP table even after multiple
run (V8.1.7.3).
I just wanted to know that is there any performance impact because of this?

SQL> INSERT INTO gtt1  SELECT 'x' FROM all_objects;---TEMP TABLE

25701 rows created.

----------------------------------------------------------
        184  recursive calls
        969  db block gets
     160257  consistent gets
          0  physical reads
      91772  redo size
        854  bytes sent via SQL*Net to client
        578  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      25701  rows processed

SQL> INSERT INTO gtt1  SELECT 'x' FROM all_objects; ---TEMP TABLE
25701 rows created.

Statistics
----------------------------------------------------------
        177  recursive calls
        953  db block gets
     160314  consistent gets
          0  physical reads
      90616  redo size
        853  bytes sent via SQL*Net to client
        578  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      25701  rows processed

177 is consistent after this (I did execute 10 times)

SQL> INSERT INTO t SELECT 'x' FROM all_objects; -- NON-TEMP TABLE
 25701 rows created.

Statistics
----------------------------------------------------------
        130  recursive calls
        961  db block gets
     160359  consistent gets
          0  physical reads
     382036  redo size
        855  bytes sent via SQL*Net to client
        574  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      25701  rows processed

SQL> INSERT INTO t SELECT 'x' FROM all_objects; -- NON-TEMP TABLE

25701 rows created.

Statistics
----------------------------------------------------------
        130  recursive calls
        956  db block gets
     160358  consistent gets
          0  physical reads
     381552  redo size
        857  bytes sent via SQL*Net to client
        574  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      25701  rows processed

SQL>
Followup:
time for you to use sql_trace and see whats up with that.  i see no reason for
the recursive sql.

ops$tkyte@ORA817DEV> set autotrace on statistics
ops$tkyte@ORA817DEV> insert into t select 'x' from all_objects;

26868 rows created.

Statistics
----------------------------------------------------------
          7  recursive calls
        333  db block gets
     105547  consistent gets
        336  physical reads
     337180  redo size
        996  bytes sent via SQL*Net to client
        815  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      26868  rows processed

ops$tkyte@ORA817DEV> insert into gtt select 'x' from all_objects;

26868 rows created.

Statistics
----------------------------------------------------------
          0  recursive calls
        332  db block gets
     105537  consistent gets
        334  physical reads
      72948  redo size
       1009  bytes sent via SQL*Net to client
        817  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      26868  rows processed

ops$tkyte@ORA817DEV> set autotrace off

so trace it and see what the sql is in the background.
December 24, 2003 Lara -- Thanks for the question regarding "return resultset as an array from function", version 8.1.5
You Asked
I want to return a resultset from a function. I saw your example
on how to return it as a ref-cursor, but I need to return it as
an array and then retreive the array in Java. Do you have an
example of this?

Thanks,
Lara
and we said...
Well, jdbc can deal with result sets from stored procedures --
it would be easiest to do the result set in virtually EVERY
case.  You can do it as an Array, you must use Oracle8i object
types to do this.

The java code to retrieve an array can look like this (comments
contain the sql you need to run before executing the java)

import java.sql.*;
import oracle.jdbc.driver.*;
import oracle.sql.*;

//  create or replace type SimpleArray
//  as table of varchar2(30)
//  /
//
//  create or replace function getSimpleArray
//  return SimpleArray
//  as
//      l_data  simpleArray := simpleArray();
//  begin
//      for i in 1 .. 10 loop
//          l_data.extend;
//          l_data(l_data.count) := 'entry ' || i;
//      end loop;
//      return l_data;
//  end;
//  /

class Array
{

public static void main(String args[]) throws Exception
{

DriverManager.registerDriver
              (new oracle.jdbc.driver.OracleDriver());

Connection conn =
    DriverManager.getConnection
                     ("jdbc:oracle:thin:@aria:1521:ora8i",
                      "scott",
                      "tiger");

OracleCallableStatement stmt =
      (OracleCallableStatement)conn.prepareCall
                     ( "begin ? := getSimpleArray; end;" );

    stmt.registerOutParameter( 1,
                               OracleTypes.ARRAY,
                               "SIMPLEARRAY" );
    stmt.executeUpdate();

    ARRAY simpleArray = stmt.getARRAY(1);

    System.out.println ("Array is of type " +
                         simpleArray.getSQLTypeName());

    System.out.println ("Array element is of type code " +
                         simpleArray.getBaseType());

    System.out.println ("Array is of length " +
                         simpleArray.length());

    String[] values = (String[])simpleArray.getArray();

    for( int i = 0; i < values.length; i++ )
        System.out.println( "row " + i + " = '" +
                             values[i] + "'" );

    stmt.close();
    conn.close();
}
}

that routine for example prints out this:

$ java Array
Array is of type SCOTT.SIMPLEARRAY
Array element is of type code 1
Array is of length 10
row 0 = 'entry 1'
row 1 = 'entry 2'
row 2 = 'entry 3'
row 3 = 'entry 4'
row 4 = 'entry 5'
row 5 = 'entry 6'
row 6 = 'entry 7'
row 7 = 'entry 8'
row 8 = 'entry 9'
row 9 = 'entry 10'

For additional information on this feature, please see
http://technet.oracle.com/doc/java.815/a64685/oraext7.htm#1040060
which is the jdbc manual, section on "Working with Arrays"
Review & Followup

Rating: 3
Nice! April 04, 2001
Reviewer: Mihai Manuta from Paris
It’s one of the most unanswered questions in the forums. Unfortunately your
answer raised another question: when I do the following:

cstmt=(OracleCallableStatement)conn.prepareCall("{call test(?,?)}");

cstmt.setObject(1,new String("ceva"),java.sql.Types.VARCHAR);
cstmt.registerOutParameter((2,OracleTypes.ARRAY,"PKG_DATATYPES.TV20");

cstmt.execute();

I get an exception:
Fail to construct descriptor: Unable to resolve type: "PKG_DATATYPES.TV20"
The PKG_DATATYPES.TV20 type is declared in the database (but  wasn’t declared by
me). Can you help me on that?
Rating: 4
May 27, 2001
Reviewer: Wai from Hong Kong
Rating: 4
array passwing between java client & stored procedure May 27, 2001
Reviewer: Wai from Hong Kong
The example is good because the
code is simple and it worked (for 8.1.x only).

Unfortunately, when I apply the sample code to
8.0.4 database.  I got the similar
result as "Mihai Manuta from Paris" have
(unable to resolve type).
Then I downloaded the most recent
Oracle thin client driver (use 8.1.7
driver conntecting to 8.0.4 database),
the program overcome the resolve type problem.
However it introduce another problem againt,
the result is in Hexdecimal format instead of
ASCII string!!!

Is there any special setting require at
both database server/java client level?
Rating: 4
How ARRAY type can be used using 8.0.5 JDBC driver? July 16, 2001
Reviewer: Avijit Saha from Kolkata, INDIA
Without changing JDBC 8.0.x driver how can I use ARRAY type? Now I'm working
with Oracle client version 8.0.6 and JDBC version is 8.0.5 ( e.g.
DatabaseMetaData meta = conn.getMetaData ();
    System.out.println("JDBC driver version is " + meta.getDriverVersion()); ).
Without using 8.1.x JDBC driver how can I get PL/SQL table as ARRAY type? Can
any body help me?
Rating: 5
July 23, 2002
Reviewer: A reader
The array in the example is a simple array of table type varchar2(). what about
a complex array..say String[][]
How would you go about it.
Followup:
two dimensional arrays are not supported until 9i (and string[][] is a 2 dim
array) then they would map to a complex object type and you would use jpub to
create a mapping class cause the code gets really ugly.

Read about jpublisher.
Rating: 3
July 24, 2002
Reviewer: A reader from Frankfurt
Does that mean 2 Dim arrays cannot be passed by a java stored proc/function in
8i. Is there a work around.
Followup:
since everything has to go through the SQL layer and in 8i there is no such
thing as an array of arrays -- yes, you are correct, there is no way to do it.

You must pass single dimensioned arrays OR use remote procedure calls (RMI over
IIOP).

see
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/java.817/a83728/04jserv2.htm#1007944
(but not recommended)
Rating: 3
July 26, 2002
Reviewer: ossie j
so if 9i supports double dimensional arrays, is there any documentation or any
sample program which demonstrates this, particularly in java egsample.
Followup:
yes, the plsql guide shows collections of collections and you'll want to use
jpublisher to create the java mapping class.
Rating: 4
How to Receive Arrays from Java in PL/SQL February 07, 2003
Reviewer: Chandra S.Reddy from India
Hi Tom,
I have one oustanding issue reg passing this arrays from Java to PL/SQL.

Am using Weblogic and Tomcat as application servers.So we obviously their(WL or
Tomcat) connection pools.But not Oracle pool.
Plese look at the code below.
drop table t1 ;

create table t1 (a number(1), b varchar2(5));

drop type Num_List ;
create TYPE Num_List as TABLE OF NUMBER;           --  create array of number
/

CREATE OR REPLACE PROCEDURE SP_PoC_Test_Array (in_val Num_List) IS
   BEGIN
    FORALL i IN in_val.FIRST..in_val.LAST
        INSERT INTO T1 (A) VALUES (in_val(i)) ;
END SP_PoC_Test_Array;
/
-- Java program.
import java.sql.*; import oracle.jdbc.driver.*; import oracle.sql.*;import
oracle.jdbc.oracore.Util;import java.math.BigDecimal;

public class ArrayTest1
{
  public static void main (String args [])
                     throws SQLException, ClassNotFoundException
  {

       String driver_class = "oracle.jdbc.driver.OracleDriver";
      String connect_string = "jdbc:oracle:thin:@spindc4:1521:java1";

      Connection conn;
      Class.forName(driver_class);
      conn = DriverManager.getConnection(connect_string, "chandra", "chandra");

      CallableStatement cstmt = conn.prepareCall("{CALL
sp_cmsa_test_array(?)}");
    int elements[] = { 1, 2, 3, 4, 5 };
    ArrayDescriptor desc = ArrayDescriptor.createDescriptor("NUM_LIST", conn);
    ARRAY newArray = new ARRAY(desc, conn, elements);
  ((OraclePreparedStatement)cstmt).setARRAY(1, newArray);
    cstmt.execute();
    cstmt.close();

  }
}

The above implementations are working fine, when we tested  as a standalone
programs.

But, while developing the application, we should use Weblogic connection or
Tomcat connection. Not Oracle driver.
In the above code "ArrayDescriptor" object is Oracle's own
implementation(correct if am wrong).
There actually we are landing into errors.

Is there anyway to overcome this.
Shall we do this Java SPs or any other kind.
Another restriction, we have is there should not be any SQL, PL/SQL work inside
in Java.Everything must be inside Stored procs.

*******
Corrently we are implementing this issue like below.

TYPE t_Roles IS TABLE OF NUMBER(3) INDEX BY BINARY_INTEGER; -- declare
collection element for Ids and Flags.
 v_Roles     t_Roles ;

    v_Indx := 0;

    LOOP
        v_Indx    := v_Indx+1;

        v_Position := INSTR(v_AllRoles, '~'); -- find position of delimiter
(tild character). if string has any value, at least one delimiter is always
there with it.

        EXIT WHEN v_Position = 0 ;  -- exit loop when delimiter not found

        v_Roles(v_Indx) := TO_NUMBER(SUBSTR(v_AllRoles, 1, v_Position - 1)) ; --
take string before the position of delimiter

        v_AllRoles := SUBSTR(v_AllRoles, v_Position + 1) ; -- reassign remaining
string to same variable

    END LOOP ;
    -- more code here---

Where "v_AllRoles" is an IN parameter. I get values for this from application
with '~' as a delimeter(ex:- '1~2~3~4~0'). In PL/SQL I tokenize the
string(v_AllRoles) and construct an array v_Roles as in the above code.
And use the the "v_Roles" to do other transactions.

This kind of IN strings(~ delimited strings)are 'n' in number some times. Then I
need to add 'n' number of loops.Finally Code looks very ugly.

Please suggest us.
Followup:
array's are proprietary -- if a database has them, they are implemented in their
fashion.

jdbc is "open" -- meaning the spec only defines that which is at the lowest
common denominator.

We have extensions in our jdbc driver to allow you to use all of our
functionality.

They do not.

Now, that aside:

...But, while developing the application, we should use Weblogic connection or
Tomcat connection. Not Oracle driver....

is something I don't agree with -- you don't have to, you are free to use OUR
driver if you like.  No reason you shouldn't -- especially if you use this
construct alot.
Rating: 4
nested table of object type as parameter to proc April 10, 2003
Reviewer: A reader
if i have a nested table of an object type can i map that to java?
meaning:
create type test_obj as object
(col1 varchar2(10),col2 number,col3 number);

create type test_tbl as table of test_obj;

can java use this test_tbl for mapping?
or is this considered as a two dimensional array?

how do i handle such situaions?
Followup:
Yes you can -- jpublisher is useful for doing so as it writes the code for you.
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-JPU
Rating: 5
Returning a pl/sql table from a procedure and function September 17, 2003
Reviewer: A reader
Returning a pl/sql table from a procedure and function, i.e. the solution you
gave above in pl/sql

Can you give a simple example for the above.
Rating: 3
returning a result set....The best way December 24, 2003
Reviewer: Dan White from Philadelphia, PA USA
Tom,

   From what I have read there are 2 possible ways of returning result sets from
Oracle stored procedures to Java(ref cursors and Arrays)(oracle 8.1.7.4). We are
having an enternal debate at my client about the best way to go about this. We
need to return multiple columns of data with multiple rows. We (java and Oracle
Developers) would like to use ref cursors to return data due to the dynamic
nature of our application. The DBA's here would like us to use arrays(nested
table) to pass information back to the java client. there reasoning is that if
the connection to the webserver is lost then there will be an open cursor left.
If a session is activly retrieving data from a webserver and the connection to
that server is lost what will happen to the session?
Followup:
there is only one way to return a result set -- ref cursor.

to return an array is to return an array (no result set semantics about it).

The DBA's would rather you use scads of memory?  To be really inefficient.  The
only correct method is a ref cursor, really ( wrote about this in my new book
Effective Oracle By Design and showed the reasons you don't want to consider the
arrays -- its ugly ).

How would there be any open cursors?  they are worried about something that
isn't going to happen.  Say the browser crashes -- the jsp or whatever finishes
and wah-lah, gives up the connection and all cursors would be closed.

Say the connection from the app server to the db is lost.  Well, thats what DCD
(dead client detection) will handle, database will clean up.

A lost connection will not result in open cursors lying about (and even if they
do, so what?  cursors are session specific -- the session would be left open --
no one else would be able to get to that session -- THAT is the problem they
want to focus on and it is a problem you have with both techniques.

The only way to do this is a ref cursor.
Rating: 5
Tom Execelent answer December 29, 2003
Reviewer: Dan White from Philadelphia PA USA
Tom,

  Thank you for the Answer, This is exactally what I have been trying to get
across to our DBA staff.

Thank you
Dan
December 24, 2003 Senthil -- Thanks for the question regarding "regular table to partition table", version 8.1.7
You Asked
Tom,

I have a situation where i need your expertise advice. Our product, at the
client site, creates table and populates data. The table creation happens once
in a while depending upon the client's requirement.

As Oracle releases with a lot of feature in each versions, we would like to make
use of it and convert all regular tables into partitioned tables. We are looking
for a various options, to reduce the downtime, fast migration and with no
problems. These regular tables might be having millions rows. The following
information might give a better idea, what we have now and what we are looking
for...

Existing environment:

App  Ref.  TableName        Structure
ID
-----------------------------------------
1     1    TABLE_1
1     2    TABLE_2          Same as TABLE_1
1     3    TABLE_3          Same as TABLE_1
1     4    TABLE_4
1     5    TABLE_5          Same as TABLE_4
1     6    TABLE_6          Same as TABLE_4
1     7    TABLE_7          Same as TABLE_4
1     8    TABLE_8          Same as TABLE_4
1     9    TABLE_9
1     10   TABLE_10         Same as TABLE_1
1     11   TABLE_11         Same as TABLE_1
1     12   TABLE_12
..    ...  ...
..    ...  ...
..    ...  ...
..    ...  ...

As you may have noticed several tables have the same structure of the previous
or old tables. Now, we wanted have partitioned tables as follows...

Need to be converted as:

App  Ref.   TableName       Comments
ID
------------------------------------------------------------------
1     1     TABLE_1         will also be having TABLE_2, TABLE_3,
                            TABLE_10 & TABLE_11 data
1     4     TABLE_4         will also be having TABLE_5, TABLE_6,
                            TABLE_7 & TABLE_8 data
1     12    TABLE_12        only its data

My questions are,
1. How do we convert all regular tables into partitioned tables. Hints are basic
steps would do fine.
2. We are using one of the date columns as a partition key. What would be the
fastest and safest method to migrate.

thanks,
senthil
and we said...
pretend t1, t2, t3 are your existing tables.
T is the table you want (you can drop t1 and rename t to t1 later if you need)

Then, use the following approach:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t1 as select sysdate dt,
all_objects.* from all_objects;

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t2 as select
add_months(sysdate,-12) dt, all_objects.* from all_objects;

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t3 as select
add_months(sysdate,-24) dt, all_objects.* from all_objects;

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t( dt, OWNER, OBJECT_NAME,
SUBOBJECT_NAME,
  2   OBJECT_ID, DATA_OBJECT_ID, OBJECT_TYPE, CREATED,
  3   LAST_DDL_TIME, TIMESTAMP, STATUS, TEMPORARY,
  4   GENERATED, SECONDARY )
  5  partition by range(dt) (
  6  partition part2000 values less than ( to_date( '01-jan-2001',
'dd-mon-yyyy') ),
  7  partition part2001 values less than ( to_date( '01-jan-2002',
'dd-mon-yyyy') ),
  8  partition part2002 values less than ( to_date( '01-jan-2003',
'dd-mon-yyyy') )
  9  )
 10  as
 11  select sysdate dt, all_objects.* from all_objects where 1=0;

Table created.

just create an empty partitioned table, partitioned by date.  The ranges are
such that t1 fits in one partition, t2 in another and so on

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t
  2  exchange partition part2000
  3  with table t3
  4  without validation
  5  /

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t
  2  exchange partition part2001
  3  with table t2
  4  without validation
  5  /

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t
  2  exchange partition part2002
  3  with table t1
  4  without validation
  5  /

Table altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from t;

  COUNT(*)
----------
     70848

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from t1;

  COUNT(*)
----------
         0

Now, T is a table with partitions formally known as tables T1, T2, T3!
Review & Followup

Rating: 5
Wow that was cool... September 24, 2002
Reviewer: A reader from BG
Tom,
   That was great. Also, can you explain what the usage of MAXVALUE (Iam not
sure...or is it MAXDATA) in the PARTITION clause.

regards
Followup:
Maxvalue is used to create a partition that takes "all of the other stuff".

For example, in my table I had:

  5  partition by range(dt) (
  6  partition part2000 values less than ( to_date( '01-jan-2001',
'dd-mon-yyyy') ),
  7  partition part2001 values less than ( to_date( '01-jan-2002',
'dd-mon-yyyy') ),
  8  partition part2002 values less than ( to_date( '01-jan-2003',
'dd-mon-yyyy') )
  9  )
 10  as

so,

part2000 gets negative infinity UPTO but not including 01-jan-2001.
part2001 gets 01-jan-2001 UPTO but not including 01-jan-2002.
part2002 gets 01-jan-2002 UPTO but not including 01-jan-2003

If you tried to insert 01-jan-2003 .. positive inifinity -- it would fail.  You
could set up another partition that takes "upto maxvalue" that would get all of
the ones that do not fit in any other range.

The first partition gets all of the low values.
A last partition with maxvalue gets all of the high values.
the partitions in between have discrete start and stop points.
Rating: 5
September 24, 2002
Reviewer: A reader
Rating: 5
Partioning tables September 25, 2002
Reviewer: Ashwani Singh from delhi India
Hi Tom,
    Great as always!!!!,
We ALSO HAve a situation where in we have the following table structure.
SQL> desc category_date_wise_email
 Name                          Null?    Type
 EMAIL_ID             NOT NULL VARCHAR2(100)
 CATEGORY             NOT NULL VARCHAR2(100)
            date_of_mail         not null   date FLAG
      CHAR(1) NAME                                  VARCHAR2(100)
primary key (email_id,category,date_of_mail);

In the above table i would like to create partition on the date_of_mail column
in the following way:
1) Firstly on Year basis.
2) Secondly on the monthly basis which will be the sub partitions of the year
partition. Since the table already contains records, Colud U kidly guide me as
to how to do it in the most efficient way. Could u kindly, also guide me as to
how to create the sub partitions in the most effective way?
Will i have to create twelve subpartitions of every year Partitions.
For example if I create five year partitions then will i hvae to create 12*5=60
subpartitions in all??

Eagerly waiting for ur reponse.

Regards and thnax in advance,
Ashwani Singh.
Followup:
Since you have a single table, not one table for each partition, you'll be
rebuilding the table.

You do not subpartition by range, you have only one layer of partitioning
according to your specs above.

Yes, if you have 5 years and want to partition by month, you'll have 60
partitions (not subpartitions).

You'll be doing SOMETHING like:

create table new_table ( email_id, category, date_of_mail, name )
partition by range(date_of_mail)
(
partition jan2001 values less than ( to_date( '01-feb-2001', 'dd-mon-yyyy') ),
partition feb2001 values less than ( to_date( '01-mar-2001', 'dd-mon-yyyy') ),
partition mar2001 values less than ( to_date( '01-apr-2001', 'dd-mon-yyyy') ),
...
partition sep2002 values less than ( to_date( '01-oct-2002', 'dd-mon-yyyy') ),
....
)
as
select * from category_date_wise_email;

drop table category_date_wise_email;

rename new_table to category_date_wise_email;
Rating: 5
few more September 25, 2002
Reviewer: Senthil from Boston, Massachusetts
well done Tom!

i think that would release my hassle. but would like to hear more from you for
the following complexity related to partitions.

like i said in my post, the tables are loaded with data. most likely the tables
data may not be organized. i mean, say for e.g.,

TABLE_1 : having data between 01-jan-2002 and 11-jan-2002
TABLE_2 : having data between 12-jan-2002 and 26-jan-2002
TABLE_3 : having data between 27-feb-2002 and 13-feb-2002
TABLE_4 : having data between 14-feb-2002 and 28-feb-2002
TABLE_5 : having data between 01-mar-2002 and 15-mar-2002
...
...

my questions would be...
1. can i create tiny partitions to hold each tables data
2. say if i create tiny partitions and exchange partition with table data. at
the end, would it be possible to merge all partitions into a
single(monthly/quarterly or whatever) partition. i know we could merge the
partition, but i am worried about the downtime, the time taken for merge
process. or, is this the right approach for this environment. could you please
enlighten on this...

thanks.
Followup:
1) yup

2) if that was your goal, just create the table you wanted and do insert /*+
APPENDS */ into it (you'll be rewriting the entire table)

You can just keep these as individual partitions however.
Rating: 5
Excellent September 25, 2002
Reviewer: John from Austin, TX
Great example, as usual.
Rating: 5
Partioning table September 26, 2002
Reviewer: ashwani from india
Thanx a lot !
Great answer.

Regards,
Ashwani Singh
Rating: 5
Still confused with MAXVALUE and composite range partition key September 26, 2002
Reviewer: Graham Halsey from UK
Tom,

I want to partition a large table containing financial transactions by the tax
year in which the transaction was created. However, our system is growing at an
increasing rate hence the partition for tax year 2002 would still contain a
significant percentage of the total number of rows in the table.

We need to keep queries about transactions in the last 2 weeks running very
fast, so I want a partition just for that as well as the normal taxyear
partitioning. Here's what I'm trying:

- 1 partition contains transactions in the last 14 days
- 1 partition contains transactions in the current tax year but > 14 days old
- 1 partition for each previous tax year.

Here's a small example of what I'm trying:

drop table part_table cascade constraints
/
create table part_table
(
        id                      number(10)              not null,
        historical              number(1)               null,
        timestamp               date                    null,
        constraint pk_part_table
                primary key ( id )
)
partition by range ( historical, timestamp )
(
        partition active values less than (0,maxvalue),
        partition taxyear_2001 values less than (2,to_date('06/04/2001
00:00:00','dd/mm/yyyy hh24:mi:ss')),
        partition taxyear_2002 values less than (2,to_date('06/04/2002
00:00:00','dd/mm/yyyy hh24:mi:ss'))
)
/
insert into part_table (id,historical,timestamp) values
(1,1,to_date('07/04/2000','dd/mm/yyyy'));
insert into part_table (id,historical,timestamp) values
(2,1,to_date('07/04/2001','dd/mm/yyyy'));
insert into part_table (id,historical,timestamp) values
(3,1,to_date('07/04/2002','dd/mm/yyyy'));
insert into part_table (id,historical,timestamp) values (4,1,sysdate-15);
insert into part_table (id,historical,timestamp) values (5,0,sysdate-13);
commit;

The plan is that we schedule a daily job to do something like "update part_table
set historical=1 where timestamp < sysdate-14 and historical=0;" ... this gives
us the "rolling" partition.

The "historical" field takes values 0=false, 1=true.

However, after the above inserts I have this in each partition:

sb_owner@sb_deva> select * from part_table partition (active);

    ID HISTORICAL TIMESTAMP
---------- ---------- -------------------
     5        0 13/09/2002 10:33:47

sb_owner@sb_deva> select * from part_table partition (taxyear_2001);

    ID HISTORICAL TIMESTAMP
---------- ---------- -------------------
     1        1 07/04/2000 00:00:00
     2        1 07/04/2001 00:00:00
     3        1 07/04/2002 00:00:00
     4        1 11/09/2002 10:33:47

sb_owner@sb_deva> select * from part_table partition (taxyear_2002);

no rows selected

My "active" partition is working fine, but I'm seeing rows in the "taxyear_2001"
partition that should be in the "taxyear_2002" partition.

Have I defined my partitions incorrectly?

Oh, RDBMS 8.1.6.1.0.

TIA,
Graham.
Followup:
You'll be moving data from partition to partition constantly.

what is wrong with a partition for every 2 weeks or every 1 month even -- as
most, you'll be looking at 2 partitions of data when looking for the "last 14
days"
Rating: 5
What does this imply? September 26, 2002
Reviewer: A reader from BG
Tom,
   A partitioned table on my database is done in this manner.

PARTITION BY RANGE (EFF_DT)
  (
PARTITION P_0012 VALUES LESS THAN (MAXVALUE) TABLESPACE PART_DATA
PCTFREE 10
   PCTUSED 40
   INITRANS 1
   MAXTRANS 255
 STORAGE (
   INITIAL 20971520
   NEXT 20971520
   PCTINCREASE 0
   MINEXTENTS 1
   MAXEXTENTS 505
 )
);

What does this mean? How many partitions are here? Only one?
Followup:
one partition.

it means you have one partition.
Rating: 5
One more thing... September 26, 2002
Reviewer: Ik from BG
Thanks a ton, Tom, for the speedy response.

If it has only partition, then is there any real use for that? What is the data
that lies on either side of the partition?
Is that almost like having NO PARTITION at all?
Followup:
Well, it opens up the interesting possibility of:

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter table t
  2  exchange partition p_0012
  3  with table NEW_DATA
  4  without validation
  5  /

-- you could easily swap a new set of data in there at the drop of a hat (and
not lose grants, etc)....

You could also turn this table with a single partition into a multi-partitioned
table without a dump and reload (or using the dbms redefinition package in 9i)
Rating: 5
performance improvement tips... September 30, 2002
Reviewer: Senthil from Boston, Massachusetts
Tom-

we like to go with with insert (with hint) approach. but we have a few questions
though. say we created a partitioned table with nologging and inserted data from
various regular tables into it. now, i wanted to add a few check constraints,
indices and primary key, as it is in regular tables. i dont have a problem
adding indices, think i can use nologging option again. but what about check
constraint and primary key. would that take more time to create and enable. or
is there hints to improve the performance. assume we have a tons of millions of
rows, and data are valid.

thanks always.
Followup:
check constraints do not generate redo, they simply read data and verify it.

a primary key MIGHT create an index (or it might just take over an existing
one).  In any case, you can index the primary key columns using nologging and
then create the constraint or use the USING INDEX clause of the CREATE
CONSTRAINT statement to specify nologging and other attributes.

If you *know* the data is valid, you can enable constraints without validation.
Suggest you read through the data warehousing guide for tips and techniques.
Rating: 5
Perfomance implication? October 09, 2002
Reviewer: Graham Halsey from UK
Hi Tom,

Thanks for the advice earlier in this question. I have another one for you>>

I'm re-creating my production database as part of a move from one database
server to another - mainly so I can change the db_block_size but also to do
things like switch DMTs to LMTs etc...

One of the things I'm contemplating is to re-create nearly all my tables with 1
partition in them - just so I can easily make them multi-partitioned later
should the need arise (since I can't partition a non-partitioned table quite so
easily).

Is there likely to be any performance degradation through having all tables with
1 partition in them, rather than just having them non-partitioned?
Followup:
make sure you are ready to use the CBO on everything -- every every everything.

That'll be one of the most obvious impacts.  The only optimizer used EVER with
partitioned tables is the CBO.  (in 10i, that'll be the case as well from whats
been announced)

Now, what you might do is reconsider this in light of the fact that in 9i you
have online redefs so you can partition a table easily while it is being used.

Also -- since all data would be in one partition, a simple:

create table partitioned ( ......

alter table partitioned exchange the single partition with the existing table

drop the "existing table" (which is now empty)

rename partitioned to existing table

I cannot put my finger on it -- but it just doesn't sound like a good idea --
especially since I can make a non-partitioned table into a partitioned table
with all data in a single partition (as you would have) pretty fast.
Rating: 5
where to use partition October 10, 2002
Reviewer: Nikunj Thaker from India
i have oracle EE 8.1.7.
i have below table

SQL> desc item
 Name                            Null?    Type
 ------------------------------- -------- ----
 ITCODE                          NOT NULL VARCHAR2(60)
 ITDES                           NOT NULL VARCHAR2(100)
 BRAND                           NOT NULL VARCHAR2(20)
 SILVER                          NOT NULL VARCHAR2(1)
 SAFFRON                         NOT NULL VARCHAR2(1)
 PACKAGING                       NOT NULL VARCHAR2(20)
 UNITPACK                        NOT NULL NUMBER(12,2)

SQL> desc invoice_mast
 Name                            Null?    Type
 ------------------------------- -------- ----
 SRNO                            NOT NULL NUMBER(20)
 INV_TYPE                                 VARCHAR2(5)
 INV_DATE                                 DATE
 CUSTCODE                        NOT NULL NUMBER(10)
 ORDNO                                    NUMBER(10)
 ORDDATE                                  DATE
 TRANS                                    VARCHAR2(80)
 DISCITY                                  VARCHAR2(80)

SQL> desc invoice_tran
 Name                            Null?    Type
 ------------------------------- -------- ----
 SRNO                            NOT NULL NUMBER(20)
 ITCODE                          NOT NULL VARCHAR2(60)
 NO_OF_CARTON                    NOT NULL NUMBER(13,3)
 UNITRATE                        NOT NULL NUMBER(12,2)
 ASS_VALUE                       NOT NULL NUMBER(12,2)

now i am taking reports through below query's

1). select
brand,unitpack,packaging,to_date(to_char(inv_date,'Mon-YYYY'),'Mon-YYYY') month,
    sum(no_of_carton) carton from invoice_mast a,invoice_tran b,item c
    where a.srno=b.srno and b.itcode=c.itcode and inv_date between &sdate and
&edate
    group by
brand,unitpack,packaging,to_date(to_char(inv_date,'Mon-YYYY'),'Mon-YYYY');
2). select silver,to_date(to_char(inv_date,'Mon-YYYY'),'Mon-YYYY') month,
    sum(no_of_carton) carton from invoice_mast a,invoice_tran b,item c
    where a.srno=b.srno and b.itcode=c.itcode and inv_date between &sdate and
&edate
    group by silver,to_date(to_char(inv_date,'Mon-YYYY'),'Mon-YYYY');
3). select custcode,to_date(to_char(inv_date,'Mon-YYYY'),'Mon-YYYY') month,
    sum(no_of_carton) carton from invoice_mast a,invoice_tran b,item c
    where a.srno=b.srno and b.itcode=c.itcode and inv_date between &sdate and
&edate
    group by custcode,to_date(to_char(inv_date,'Mon-YYYY'),'Mon-YYYY');

shall i have to use partition for improve performance ?
if i use the partition which type of partion i need to use ?

if u give me steps its fine for me.
Followup:
Well, at a glance, it would look like a range partition on the inv_date field
*MAY* be of use.

The steps you need to use -- that I can do.  here is your reading assignment:

o Jonathan Lewis's "Practical Oracle8i -- building efficient databases"
o optional: My chapter on partitioning in "Expert one on one Oracle"
o MANDATORY: Server concepts guide
o MANDATORY: Designing and tuning for performance guide

You need to get a good understanding of the physical mechanics of partitioning
before even attempting to use it.  Partitioning is predominantly a high
availability, easier to manage solution.  It can sometimes contribute to better
performance.
Rating: 5
how to identify performance October 10, 2002
Reviewer: Nikunj Thaker from India
Thanks tom,

after creating partition on my table invoice_mast how can i identify truly the
performace has been increase or not of the resonce of the query ?
Followup:
"of the resonce of the query" ???

you would have gathered some metrics before doing this operation (eg: logical
ios per query, cpu time per query, whatever)

you would do the operation

you would then see if after doing the operation, the logical io's and cpu times
and whatever where improved.
Rating: 5
October 11, 2002
Reviewer: A reader
I have a question on partition (Oracle 9.0)

ALTER TABLE orders
EXCHANGE PARTITION q1_2000
WITH TABLE orders q1_2000
UPDATE GLOBAL INDEXES
PARALLEL (DEGREE 4);

Will all valid global indexes on the orders table retain their USABLE status
after the statement completes...

TIA...
Followup:
that is the purpose of the update global indexes, it'll maintain them during the
alter -- yes.
Rating: 5
not expert October 12, 2002
Reviewer: Nikunj from India
>you would then see if after doing the operation, the >logical io's and cpu
times and whatever where improved.

tom, i am not expert how can i gathered above information.

thanks,
nikunj
Followup:
Either read chapter 10 of my book "Expert one on one Oracle" or read
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76992/toc.htm
and learn about sql_trace, tkprof and autotrace
Rating: 5
December 03, 2002
Reviewer: A reader
I have a range partitioned table. Is it possible to create a hash sub partition
on another column within each partition using 'ALTER TABLE syntax?'. I have data
in the tables and lot of indexes and constraints.

I know it is possible by creating another table with the new partition scheme
and then doing exchange partition. But I will have to re-create all indexes and
constraints then. Is it possible to re-define the partitioning scheme of a table
online (just like the 'alter table move command'?). Thanks for your input.

Rgds,

SA
Followup:
You would have to by definition recreate all of the indexes -- by hashing the
data, you will in fact MOVE every row -- they all get new rowids -- bamm, new
indexes.

You can put the constraints on with enable rely to have them go on and ASSUME
the existing data is OK (which is must be -- if it was before).
Rating: 3
December 03, 2002
Reviewer: A reader
Sorry, but I don't really understand what you say. Are you saying that the
partition scheme can be redefined online (using an alter table)? If yes then can
you give the syntax. Re-creating the indexes would be fine. Thanks.
Followup:
In 9i, the partitioning scheme can be redefined online using dbms_redefinition,
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4636779130376
for an example.

In 8i and before, it is an offline situation -- you have to reorg it by
rebuilding it offline (no modifications while you do it, offline operation --
you are rebuilding the structure)
Rating: 4
December 04, 2002
Reviewer: A reader
But in 8.1.7, can we use 'Alter table' to change the partitioning scheme from
range to composite(range+hash)? Or we have to export the data and import it back
to an empty table with the new partitioning definition?

I understand it has to be done offline 'cos it is the modification of the
structure. But I don't know the syntax. Shall be greatful if you can explain
with an example. Thanks again for the help.
Followup:
the partitioning scheme would have to be in place from the beginning.  there is
no alter table command to take a range partitioned table and convert it into a
composite.  That is a rebuild.
Rating: 4
Which is better? June 05, 2003
Reviewer: Raghu from Wisconsin, USA
If I can summarize, we have following ways to convert a non paritioned table
into a partitioned table
1. create TABLE <new_Table> As SELECT * FROM <Old_Table> - where u can create
and load table all at once.
2. Create TABLE As SELECT * from  <Old_table> Where 1 = 0 (Empty table) and use
INSERT /*+ APPEND */

Which is the better (efficient) and faster approach?
Followup:
you have

1)
2)
3) dbms_redefinition in 9i
4) export, drop, create new table, imp
5) alter table exchange partition

None of them are "better".  It I was to pick between 1 and 2 above i would
choose 1 since it involves less typing
Rating: 5
To partition now or later August 25, 2003
Reviewer: Alvin from Philippines
I'm the dba of an OLTP system.

Our customers transactions are flagged as 'failed' if it cannot insert data in
our transaction table within 6 second time limit. Since our organization earns a
certain percentage per transactions INSERTS are of paramount importance.

Our transaction tables are ever growing and i would like to change the table
into a partitioned one.

My dilema is for me to be able to do change it to a partitioned table via CTAS
i'd have to

1. Freeze the orig transaction table ( lock on exclusive mode ) so i'd have a
consistent snapshot of the transaction table
2. CTAS to another table and define range partitioning
3. Rename and unfreeze orig transaction table
4. Rename New table as transaction table

Our transaction tables has grown (and still growing) and in doing step 1
(locking) I would be rejecting transactional inserts for a long period of time.

Since there are no Parent/foreign key defined on any of the column in this
transaction table this table seems to be 'independent' of other table. And
because this table is 'Independent'..

1. deleting ranges of data from the transactional table would not affect or
block inserts therefore minimize or eliminate loss of income for the company.

I was thinking of

1. CTAS a range (annual data) from the transactional table to another table
2. Delete from the transactional table.
3. Create another DB containing historical data
and transfer data from production to archive db.

Is this approach viable ?

I need to trim the trasaction table because it's not fully normalized and some
queries full scans the table. Redesign is not viable at this pt.
Followup:
how about dbms_redefinition

search for that term on this site to read about it.
Rating: 3
dmms_redefinition August 25, 2003
Reviewer: Alvin from Philippines
I've glanced over it. Tried probing further but i can't find it in my 8i docs.

Is this one of those non-documented but available on 8i packages ?
Rating: 4
partition table with huge data 120millions November 25, 2003
Reviewer: Alex from UK
Tom

I want to insert the 2Millions record into the table having the 120 millions
records already and the insert woould be for one partiton. This table has
indexes also.
Can I use the insert ..  select or is thera any other way to load the data fast
or do i need to disable the index before loading the data.

can you please tell me all the steps how to make fast load of the data.
thanks
Followup:
is this the only data in this partition (eg: are you loading an empty partition)

if so, you can load a table (direct path)
index it (parallel nologging)

and then alter table exchange partition to slide the newly loaded data into the
table.

Else, just insert append it, 2 million rows will go in pretty fast -- major
limiter = number of indexes, but 2 millions rows -- not too long.
Rating: 4
partition table insert November 26, 2003
Reviewer: alex from UK
Yes when I am inserting the data then this partition is empty, I tried to load
data 1M but it took 2.30 hours for the 1Million.
I could not understand the meaning of this
"if so, you can load a table (direct path)
index it (parallel nologging)

and then alter table exchange partition to slide the newly loaded data into the
table."

please tell me the steps.

Also indexes disabling and enabling after load would be helpful or not.
And how much time index enabling would take for the table 40 partiton and
200Million data in it. or can be do the partition wise enableing and disabling
of the index.

parallel insert in partition table and only insert would go in one partition ,
does it realy work?

thanks
Followup:
i did tell you the steps????

a) load a table using a direct path load

b) index that table in parallel nologging

c) alter table (partitioned table) and exchange the empty partition with the
full table.
Rating: 4
Is there a difference December 03, 2003
Reviewer: pj from UK
We use this approach
disable bitmap indexes / truncate partition / select into partition (append)/
rebuild indexes

Would it be better to change this to:
insert into empty table / index / exchange partitions ?

DW - 600 M row insert only local bitmap indexes no global indexes on partitioned
table
Followup:
benchmark it!

(but I think approach 1 is just fine, what you are doing)
Rating: 3
exchange partition December 22, 2003
Reviewer: alex from UK
Tom

create table test1 (x number, y number, z varchar2(30), constraint xpk (x,y))
partition by range(x)
(
partition tab_1 values less than (1),
partition tab_2 values less than (2),
partition tab_3 values less than (3),
partition tab_4 values less than (4),
partition tab_5 values less than (5))
/

insert into test1 values(-1,1,'AB1');
insert into test1 values(0,2,'AB2');
insert into test1 values(1,3,'AB3');
insert into test1 values(2,4,'AB4');
insert into test1 values(3,5,'AB5');

create table temp1(x number, y number, z varchar2(30), constraint xpk1 (x,y));

insert into temp1 values(4,6,'AB6');

alter table test1
exchange partition tab_5
with table temp1;

when now I say
select * from test1 where x = 5;

then I get the following error
*
ERROR at line 1:
ORA-01502: index 'XPK' or partition of such index is in unusable
state

why?.
and now I have to rebuild XPK index on the test1. Then only it allow to work.
Test1 has global index.

thanks
Followup:
is there a question in there somewhere?

you have a global index.

you did partition operations on that table and did not maintain the global
index.

hence global index is invalid and must be rebuilt by you.
Rating: 3
Exchange Partition December 24, 2003
Reviewer: Alex from UK
I want to say that I am exchanging the partition having global index.
It does exchange the partition with the temp table but then
all the indexes on main table becomes unusable.

Why?

Is there any way we can do this. Otherwise the purpose of exchanging the
partition is lost, building the partition would take more time.

hope it is clear now.
Followup:
because the global index by default is not maintained.

In 9i, you can do the partition operation AND maintain the global indexes
(documentation has all of the details on the syntax)

In 8i and before -- global indexes by definition will go invalid and require a
rebuild after partition level operations like that.
December 25, 2003 Chandan -- Thanks for the question regarding "Pfile or SPFile", version 9.0.1
You Asked
Hello Sir
Is there any view avaiable through which we can know from which file our
instance has started.Is it pfile or spfile.Without making any changes to file
and then seeing it by opening it.
Thanks
and we said...
we can use v$spparameter to see this.  Consider:

sys@ORA920.US.ORACLE.COM> select isspecified, count(*) from v$spparameter group
by isspecified;

ISSPEC   COUNT(*)
------ ----------
FALSE         221
TRUE           39

that would indicate that we are using an SPFILE -- why?  because isspecified
is TRUE for some settings in there.  They were specified in the spfile...

If we startup with a pfile:

sys@ORA920.US.ORACLE.COM> shutdown
Database closed.
Database dismounted.
ORACLE instance shut down.
sys@ORA920.US.ORACLE.COM> startup pfile = initora920.ora
ORACLE instance started.

Total System Global Area  143725064 bytes
Fixed Size                   451080 bytes
Variable Size             109051904 bytes
Database Buffers           33554432 bytes
Redo Buffers                 667648 bytes
Database mounted.
Database opened.
sys@ORA920.US.ORACLE.COM> select isspecified, count(*) from v$spparameter group
by isspecified;

ISSPEC   COUNT(*)
------ ----------
FALSE         258

then no parameter "is specified" in the spfile -- they all come from the
pfile...

going back to the spfile

sys@ORA920.US.ORACLE.COM> sys@ORA920.US.ORACLE.COM>
sys@ORA920.US.ORACLE.COM> shutdown
Database closed.
Database dismounted.
ORACLE instance shut down.
sys@ORA920.US.ORACLE.COM> startup
ORACLE instance started.

Total System Global Area  143725064 bytes
Fixed Size                   451080 bytes
Variable Size             109051904 bytes
Database Buffers           33554432 bytes
Redo Buffers                 667648 bytes
Database mounted.
Database opened.
sys@ORA920.US.ORACLE.COM>  select isspecified, count(*) from v$spparameter group
by isspecified;

ISSPEC   COUNT(*)
------ ----------
FALSE         221
TRUE           39

sys@ORA920.US.ORACLE.COM>

we have some specified, hence we are using an SPFILE...

So:

sys@ORA920.US.ORACLE.COM> select decode(count(*), 1, 'spfile', 'pfile' )
  2    from v$spparameter
  3   where rownum=1
  4     and isspecified='TRUE'
  5  /

DECODE
------
spfile

will tell us
Review & Followup

Rating: 5
very helpful November 30, 2002
Reviewer: Chandan Singh from Ludhiana-India
It's Quite easy way to know from which file instance has started.
Thanks
Rating: 5
The sequence of pfiles July 22, 2003
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
I have read the sequence of files used during startup,

1 spfile<instance_name>.ora
2 spfile.ora
3 init.ora

To test it I have just renamed the spfile and getting the following error,

SQL> startup
ORA-01078: failure in processing system parameters
LRM-00109: could not open parameter file 'D:\ORA9I\DATABASE\INITORA9I.ORA'

Although it will be successful if I use
SQL> startup pfile='D:\ora9i\admin\ora9i\pfile\init.ora'

It seems the parameter file selection is not auto.

OS Win2000 and Oracle 9i Rel 2
Followup:
there is no way.
Rating: 5
To check the current pfile July 22, 2003
Reviewer: Sikandar Hayat from Pindi Bhattian - Pakistan
SQL> show parameter spfile

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
spfile                               string      %ORACLE_HOME%\DATABASE\SPFILE%
                                                 ORACLE_SID%.ORA
SQL> shutdown immediate
Database closed.
Database dismounted.
ORACLE instance shut down.
SQL> startup pfile='D:\ora9i\admin\ora9i\pfile\init.ora';
ORACLE instance started.

Total System Global Area  143727516 bytes
Fixed Size                   453532 bytes
Variable Size             109051904 bytes
Database Buffers           33554432 bytes
Redo Buffers                 667648 bytes
Database mounted.
Database opened.
SQL> show parameter spfile

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
spfile                               string
SQL> shutdown immediate
Database closed.
Database dismounted.
ORACLE instance shut down.
SQL> startup
ORACLE instance started.

Total System Global Area  143727516 bytes
Fixed Size                   453532 bytes
Variable Size             109051904 bytes
Database Buffers           33554432 bytes
Redo Buffers                 667648 bytes
Database mounted.
Database opened.
SQL> show parameter spfile

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
spfile                               string      %ORACLE_HOME%\DATABASE\SPFILE%
                                                 ORACLE_SID%.ORA
SQL>

Note: Dear TOM I am waiting for the response to the above post.
Rating: 4
mys spfile is corrupt December 24, 2003
Reviewer: Ahmad from USA
My spfile is corrupt and now I cannot start my database running on my laptop. Is
there a way to build spfile again? I followed your new book Chapter 4 but could
not get any success. How can I create spfile using pfile ?

Thanks

Ahmad
Followup:
if you are on unix,

$ cd $ORACLE_HOME/dbs
$ strings spfilename  temp_pfile.ora

edit the temp_pfile.ora, clean it up if there is anything "wrong" with it.

then

SQL> startup pfile=temp_pfile.ora
SQL> create spfile from pfile;
SQL> shutdown
SQL> startup

On windows -- just try editing the spfile, create a pfile from it.  save it, and
do the same.
Rating: 4
I edited spfile December 24, 2003
Reviewer: Ahmad from USA
Thanks for quick response. I have win2000 with 9i Rel2. Thats what I did, in
order to install OWB , I chnaged some parameter in spfile and then it got
corrupted. I can use NT services and it works OK, but when I try to conenct to
db , I get Oracle not available error. Since, I donot have my laptop right now,
I cannot give you exact error but it was related with "Oracle not available"?

Then what I did is that I copied the spfile from different machine which has
same data , same directory structure and tried to run but still no success.

Do you think if Oracle is not available I can still create pfile the way you
described.
Followup:
yes, you can startup the db from the command line using sqlplus

create a pfile, do a manual startup (start the oracle service, then use sqlplus
to start the database)
Rating: 5
SPFILE question December 24, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom
I am going through your book Effective Oracle by Design. It says that one of the
reasons to use spfile is if I use OEM/OMS to start/stop database, the PFILE
needs to be on the machine where OMS is installed or PFILE should be accessible
to the machine running OMS. I have run into this problem in the past. One copy
of PFILE on OMS server and one copy on the database server. Keeping these copies
in sync was difficult.

If I use SPFILE, would I still need one copy of SPFILE on the OMS server? How
would the OMS know where to find the SPFILE?

Thanks.
Followup:
the spfile will ALWAYS be on the database server -- the spfile SOLVES this
problem of here a pfile there a pfile everywhere a pfile.
Rating: 5
December 24, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
We have several databases running on one server. We have a common.ora, which
contains parameters which are common for every database. Then we have database
specific parameter file. In the PFILE, using ifile=... we refer to both these
files and some parameters are in the SPFILE also. Is this a good scheme to have
parameters in three files? Would SPFILE support this scheme?

Thanks.
Followup:
I would fix the first sentence :)

A server should have no more then one database.

So, I have no best practices in this area since it is something I would not do.
Sorry.
Rating: 4
December 24, 2003
Reviewer: Jeff Hunter from greenwich.ct.us
OK, then how about this:

I run several Oracle Apps instances on several machines (one per machine).
Since we want our Oracle Apps environments to have the same behaviour
(relatively), we use a common init.ora file for several of the optimizer
paramters.  Each init.ora file includes an entry like
"ifile=/usr/local/oracle_config/apps_optimizer.par" with entries like
optimizer_max_permutations=2000, _complex_view_merging=true,
_sort_elimination_cost_ratio=5, etc.  How would these instances (on seperate
boxes) share the common ifile, but still be able to have seperate parameters
like db_name, log_archive_dest, etc. with an SPFILE?
Followup:
they would not -- i don't have any best practices for sharing an init.ora
amongst many instances cause

a) i would not run them on a single machine
b) i would not use network drives as that gives me a nasty single point of
failure so sharing with ifile is still not "relevant"

I've just never had the need (or desire) to share config files between multiple
instances in my experience.
Rating: 4
no luck December 24, 2003
Reviewer: Suhail from USA
Tom,
As you mentioned, I did but I have no luck. I am running 9iRel2 on win2000 and
my services are all running but could not connect. My spfile is broken but could
not fix. Here are what I tried:
SQL> startup
ORA-01078: failure in processing system parameters
LRM-00109: could not open parameter file 'D:\9IDB\DATABASE\INITSA1.ORA'
SQL> shutdown
ORA-01034: ORACLE not available
ORA-27101: shared memory realm does not exist
SQL> startup pfile=init.ora
LRM-00109: could not open parameter file 'init.ora'
ORA-01078: failure in processing system parameters
SQL> startup pfile='D:\9IDB\DATABASE\INIT.ORA'
LRM-00109: could not open parameter file 'D:\9IDB\DATABASE\INIT.ORA'
ORA-01078: failure in processing system parameters
SQL> create spfile from pfile
  2  ;
create spfile from pfile
*
ERROR at line 1:
ORA-01078: failure in processing system parameters
LRM-00109: could not open parameter file 'D:\9IDB\DATABASE\INITSA1.ORA'

SQL> create pfile from spfile='SPFILESA1';
create pfile from spfile='SPFILESA1'
*
ERROR at line 1:
ORA-27041: unable to open file
OSD-04002: unable to open file
O/S-Error: (OS 2) The system cannot find the file specified.

SQL>  create pfile from spfile='SPFILESA1';
 create pfile from spfile='SPFILESA1'
*
ERROR at line 1:
ORA-27041: unable to open file
OSD-04002: unable to open file
O/S-Error: (OS 2) The system cannot find the file specified.

SQL> create pfile from spfile='SPFILESA1';
create pfile from spfile='SPFILESA1'
*
ERROR at line 1:
ORA-27041: unable to open file
OSD-04002: unable to open file
O/S-Error: (OS 2) The system cannot find the file specified.

Any help greatly appreciated as always.

Suhail
Followup:
you didn't create your pfile -- that is why.

the error seems obvious to me?  your init.ora parameter file just quite simply
"isn't there".

put it there -- create it.
December 25, 2003 Andre -- Thanks for the question regarding "db cache advice", version 9.0.1
You Asked
How are the ON, READY and OFF values supposed to be used ? At first I had the
impression I had to do: OFF -> READY -> ON, so READY would allocate memory to be
used. Should I do: OFF -> ON -> READY (to maintain memory allocated) -> ON ?
and we said...
Here are the settings as you would make them in the spfile for a startup:

If you don't want advice -- set to off.  No memory allocated in the SGA, no
overhead of computing the advice.  If you have it off -- you MAY not be able to
set it to READY or ON without restarting the database.

If you BELIEVE you may want advice but don't want to incurr the overhead of
actually doing the computations right now -- set it to READY.  Memory is set
aside, but not maintained.  You can turn it ON later (and then back to ready to
analyze the results).  When you boot up with READY, you can go READY/ON/READY/ON
at will without error

If you want it on, set it on.  You can set it to READY if you like later.
Review & Followup

Rating: 5
cont'd June 18, 2002
Reviewer: Andre from Brazil
Thanks ! Only a bit awkward... but okay.
Rating: 5
cont'd June 19, 2002
Reviewer: Andre from Brazil
Does db cache advice replace the old calculation to find the buffer cache hit
ratio (consistent reads, physical reads, block gets from v$sysstat) ? These
values from v$sysstat can still be used combined with db cache advice to tune
the db ?
Followup:
apples and toaster ovens.

You have a cache hit ratio (that is what consistent read etc will give you).

No, once you have that cache hit ratio -- tell me -- what would be the cache hit
ratio if we increased the size of the buffer cache?  what would it be if we
lowered it?  You don't know, you have insufficient data.  You can guess but that
is it.

db cache advice is a more intelligent guess based on facts.  It'll tell you if
you increase the buffer cache, expect this.  If you lower it, expect that.

One computes a cache hit ratio.

The other tells you the likely outcome of resizing the buffer cache, what effect
it'll have on physical IO.
Rating: 5
Forget about v$sysstat ? June 19, 2002
Reviewer: Andre from Brazil
Are you telling me to forget about the old v$sysstat method and use only db
cache advice ? That's what I always thought, at least.
Followup:
No.

I'm saying apples and oranges.

Do you want to know your cache hit ratio.  Do the math.

Do you want to know what impact adding 50% to the buffer cache would most like
be on your physical reads -- use db_cache_advice.

One things gives you a number -- you are getting a XX.YY% cache hit.

The other says, "you know, if you reduced the buffer cache by 20% you would get
the same number of physical IO's" (for example)
Rating: 5
cont'd June 19, 2002
Reviewer: Andre from Brazil
What I understand from above and the documentation is that:

(1) once in possession of the cache hit ratio,
(2) you can use the db cache advice to know if resizing the buffer cache would
be effective.

Is that correct ?
Followup:
no.

you can do #2 without knowing anything at all about #1.
Rating: 5
cont'd June 20, 2002
Reviewer: Andre from Brazil
I see your point, but there are some things I am missing.

Up to 8i, the buffer cache hit math was used to know whether rezing the cache
would be effective. Now in 9i,  this functionality is achieved by the db cache
advisory, which I suppose has rendered the old db buffer pool statistics
obsolete. I think the math is a mere "curiosity": just to "know" what the XX.YY%
is. Period.

1) In short, do you see any usefulness for the v$sysstat db cahe hit ratio math,
now that db cache advice seems to be replacing its primary role ?

2) When do I know I won't have any benefits increasing the db cache ? Is it when
the physical read factor stays very close to 1.00 ?
Followup:
how was the cache hit ratio effective in that regards.

If I told you I had a cache hit ratio of 50% -- tell me,

a) would increasing the buffer cache have any effect?
b) would decreasing the buffer cache have any effect?

The only reasonable answer to a) and b) is "maybe, maybe not, I don't know,
insufficient data"

They are (as I keep saying) apples and oranges.  One is a metric (cache hit), an
overused and abused and many times misleading (based on advice from 'experts')
metric.  But -- it is just a metric

The other is "advice".  If you do this, you can expect that.  If you do this
otherthing, you can expect this outcome.

1) i disagree with your analysis.  Neither is a replacement for the other.  The
METRIC can be used to validate the advice.  The advice is not driven by the
metric.  The metric might cause you to ask for advice.   They are just totally
different things.

2) if the number of estimated physical reads = number of reads in the real
cache, this ratio becomes 1, so yes -- that would be a point at which you would
not consider going.
Rating: 5
shared pool advice June 20, 2002
Reviewer: Andre from Brazil
In 9iR2, should I forget about all the previous calculations to know the best
sizing of the all the shared pool component ? Up to 8i, we would have to compute
those percentages (lib cache misses, etc) -- don't I have to do any more of that
?
Followup:
this is DB cache advice, not shared pool advice.  Nothing different there, other
then you can resize it online.
Rating: 5
v$shared_pool_advice and v$pga_target_advice June 20, 2002
Reviewer: Andre from Brazil
Tom, finally you told me what I wanted to know. As you said, I think the only
useful thing about the buffer cache metric NOW is "validating" the resizing done
by using the db cache advice -- different from 8i, where I HAD TO USE the metric
to perform all the sizing stuff. See my point ?

As to v$shared_pool_advice, from the tuning guide 9iR2:

[Quote]

Shared Pool Advisory Statistics
The amount of memory available for the library cache can drastically affect the
parse rate of an Oracle instance. With Oracle9i, Release 2 (9.2) or higher, the
shared pool advisory statistics provide a database administrator with
information about library cache memory and predict how changes in the size of
the shared pool can affect the parse rate.

The shared pool advisory statistics track the library cache's use of shared pool
memory and predict how the library cache will behave in shared pools of
different sizes. Two fixed views provide the information to determine how much
memory the library cache is using, how much is currently pinned, how much is on
the shared pool's LRU list, as well as how much time might be lost or gained by
changing the size of the shared pool.

The following views of the shared pool advisory statistics are available. These
views display any data when shared pool advisory is on. These statistics reset
when the advisory is turned off.

V$SHARED_POOL_ADVICE
This view displays information about estimated parse time savings in different
sizes of shared pool. The sizes range from 50% to 200% of current shared pool
size, in equal intervals. The value of the interval depends on current shared
pool size.

Parse time saved refers to the amount of time saved by keeping library cache
memory objects in the shared pool, as opposed to having to reload these object.

[End Quote]

There's also v$pga_target_advice, for pga_aggregate_target.

Again, what I would like to know about shared pool advice is if I can forget the
previous calculations to find all those percentages (lib cache hit ratio, etc)
and rely only on the advice to find a better sizing. We can forget the bd cache
ratio to find a better sizing, now we use db cache advice. Can we do the same
with v$shared_pool_advice ?
Followup:
Haven't played with that at all -- no comment yet.
Rating: 5
August 26, 2002
Reviewer: A reader
Hi Tom

What is your Opinion regarding
http://groups.google.com/groups?dq=&hl=en&lr=&ie=UTF-8&threadm=1030379681.28629.0.nnrp-14.9e984b29%40news.demon.co.uk&prev=/groups%3Fhl%3Den%26group%3Dcomp.databases.oracle.server
Jonathan mention about consistent_gets examinations.
What is that?
Thanks
Followup:
Opinion?  It is mostly statement of fact in that thread - opinions don't count.

Anyway, it is when we look at a block.
Rating: 3
the result is opposition October 07, 2003
Reviewer: sunbird from china
my operation system is win2000 and oracle is 9.2
i can switch off/ready/on random,that is off->ready->on,on-rady-of etc... when
'off' or 'ready' or 'on' when startup,and i can't get error message ora-4031
why! thinks
Followup:
well, you don't want the ora-4031?

do you mean you "get ora-4031's"?  if so, all that means is "shared pool was set
too small for what you were trying to do, make it bigger"
Rating: 5
advisories December 24, 2003
Reviewer: Reader
The advisories for buffer cache, shared pool, pga target, and mttr are available
only in 9iR2 Enterprise edition only if statistics_level=typical (the default)
is set? Is my understanding correct Tom? If so, is it correct that these
advisories are not available in 9i R1 because statistics_level is a 9i R2
parameter? Thanks.
Followup:
the advisors are there in 9ir1, just more sophisticated and feature rich in
9ir2.
December 25, 2003 lalain -- Thanks for the question regarding "BIND VARIABLES&HOST VARIABLES", version 8.0
You Asked
HI sir

please tell me about

Q. What is basic difference between bind &host variables?

sir please sent me a complete answer of this question with its examples because
i am so confussed to differ them.
but examples should be useable in our environment because yours previous
procedures examples could not be executed in our environment and displayed some
errors those still not removed by any possible actions.

THANKS

WAITING FOR YOUR POSITIVE REPLY IN MY INBOX.
and we said...
A bind variable is an INPUT into the query you provide.

A host variable is where you put your data.

In the query:

  select * from emp where ename = :x

:X is the bind variable.  In a pro*c program I might have some code like:

exec sql begin declare section;
  VARCHAR   x[20];
  VARCHAR   eno[20];
exec sql end declare section;

  strcpy( x.arr, "KING" );
  x.len = strlen(x.arr);

  exec sql select empno into :eno from emp where ename = :x;

Here, :eno and :x are bind variables in the query.  x and eno are the C
variables that are the HOST variables.

In PLSQL, the following:

declare
    x   varchar2(20);
    eno number;
begin
   x := 'KING';

   select empno into eno from emp where ename = x;
end;
/

eno and x are the bind variables AND the host variables (the distinction blurs
in plsql -- its very close to sql)
Review & Followup

Rating: 3
Very useful August 04, 2001
Reviewer: Girish Hulikanitmath from Karnataka, INDIA
This is very good solution, many people have the confusion
abt the difference between those two.
Rating: 2
follow up August 09, 2001
Reviewer: A reader
Tom

In your pl/sql answer you mean  eno is the host variable and x is the bind
variable.

why do you say that the "the distinction blurs
in plsql -- its very close to sql"

what do you mean by saying "its very close to sql"
Rating: 3
specify more precisely August 09, 2001
Reviewer: A reader
Can you more preceisely state what is bind and what is host variable between x
and eno , and why
Rating: 4
is host var which is not also a bind var possible within a sql? September 20, 2001
Reviewer: Mikito Harakiri from SF,CA
Therefore, a variable inside a sql statement is always called "bind variable". A
variable outside of embedded sql is a "host variable". In case if they are
literally the same then we call them both "host and bind", right? It looks like
the syntax confuses symantics.
Rating: 4
How to use "memset" function in Pro*C to assign '\0' for VARCHAR data type? August 06, 2003
Reviewer: Tony from India
I use VARCHAR data type extensively in Pro*C for bulk fetch and update. After
each bulk fetch (ex: 100 rows in each fetch), I use a loop to add '\0' character
for all VARCHAR data type columns for each row as below:

VARCHAR ename[30][100];
VARCHAR occpn[20][100];
.........
EXEC SQL DECLARE c1 CURSOR FOR SELECT ENAME, OCCUPATION FROM EMP;
....
EXEC SQL FETCH c1 INTO :ename, :occpn;
no_of_recs = sqlca.sqlerrd[2];
....
for (i = 0; i < no_of_recs ; i++)
{
  ename[i].arr[ename[i].len] = '\0';
  occpn[i].arr[occpn[i].len] = '\0';
}

As billons of rows are processed in this way, I want to minimize time taken for
assigning '\0' character for each row. I try to use "memset" function for the
above purpose to avoid the loop, but it doesn't work. Can you please let me know
how to use "memset" function in this scenario?. Please let me know if there is
any alternative.
Followup:
memset works on contigous memory.  the zeros you want to add are randomly
accessed.

One might question why you do this?  If you want a null terminated string, why
not use an array of char?

here is what I would have done:

void process( void )
{
typedef     char enameType[30];
exec sql    type enameType is string(30);
enameType   ename[100];

char  job[100][20];
exec sql var job is string(20);

int    i;

    exec sql whenever sqlerror do sqlerror_hard();

    exec sql declare c cursor
    for
    select ename, job
      from emp;

    exec sql open c;
    for( ; sqlca.sqlcode == 0; )
    {
        exec sql fetch c into :ename, :job;
        for( i = 0; i < sqlca.sqlerrd[2]; i++ )
            printf( "'%s', '%s'\n", ename[i], job[i] );
    }
    exec sql close c;
    exec sql commit;
}

That shows two different ways.

typedef     char enameType[30];
exec sql    type enameType is string(30);
enameType   ename[100];

is nice if you have lots of homogenous sized variables as it associates the
datatype with a string.

char  job[100][20];
exec sql var job is string(20);

is nice if you don't.  if most variables are "their own size".

The output of that is:

[tkyte@tkyte-pc-isdn t]$ ./t

Connected to ORACLE as user: /

'SMITH', 'CLERK'
'ALLEN', 'SALESMAN'
'WARD', 'SALESMAN'
'JONES', 'MANAGER'
'MARTIN', 'SALESMAN'
'BLAKE', 'MANAGER'
'CLARK', 'MANAGER'
'SCOTT', 'ANALYST'
'KING', 'PRESIDENT'
'TURNER', 'SALESMAN'
'ADAMS', 'CLERK'
'JAMES', 'CLERK'
'FORD', 'ANALYST'
'MILLER', 'CLERK'
Rating: 5
Why and When to use VARCHAR data type in Pro*C? August 06, 2003
Reviewer: Tony from INDIA
Thanks a lot for your excellent  quick answer.

VARCHAR data type is extensively used in all our existing pro*c applications
that do bulk fetch and bulk update. I'm in the process of tuning them.
1) Will there be any problem if I change all VARCHAR data type to char data type
as given in your example?.
2)Will there be any definite performance gain if I use char data type instead
VARCHAR?
3) If char data type is sufficient, Why and when do we use VARCHAR data type?
Followup:
1) from the pro*c side?  no.

from your code side?  perhaps -- it'll change your code and if you made
assumptions that "this is varchar" in your code and don't catch that, you'll
have a bug of course.

2) you'd have to benchmark.  I've never benchmarked it.

3) you tell me, you did it :)
Rating: 5
Bulk bind using c structure in pro*C August 12, 2003
Reviewer: Tony from INDIA
1.Is it possible to bulk bind (fetch & update) using structure? if yes, Could
you provide an example please?

2. Where to specify PREFETCH=100 during compilation of pro*c programs? While
calling proc or gcc or make?
Followup:
1)
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a97269/pc_08arr.htm#1988
2) proc command line.
Rating: 4
Structure for bulk update doesn't work August 12, 2003
Reviewer: Tony from India
Thanx a lot Tom,
Bulk fetch into structure works, but bulk update using structure doesn't work.
Any clue please?
Followup:
my car won't start this morning.  any clues?

but yes, in general -- the semantics of an UPDATE are not conducive to an array
of structs.

A struct of arrays, yes.
Rating: 3
Ok December 25, 2003
Reviewer: Ram from Bangalore,India
Dear sir,
I work under SQL*PLUS in the windows environment.When I issue a command like
 sql> host cls
 It never clears the screen but it clears the screen in
MS-DOS prompt.How to correct this?
Thanks in advance.
Bye!
Followup:
use the command line version?

the GUI version is not running "in dos", it is running in the window.  the "host
cls" command is just firing up a dos window, clearing the screen and exiting.

I never liked that gui version, i always just use the command line version when
forced to use windows.
December 25, 2003 x -- Thanks for the question regarding "Querying the Object Views", version 8/9i
You Asked
Is there a way to query an object view which has a nested table.
The objective is to query the object view and apply filter on both the
attributes in the object view as well as the attributes in the nested table .

The object types are say :
o_list_item
item_no    number
quantity   number
price      number

o_Purchase_order
Purchase_order_no     number
list_item             nt_list_item

The resultset however should not be flattened( I tried the cursor method as well
as using the TABLE function) .
eg :
if the data for purchase order 1001 looks like :
Purchase_order_no          list_item
-----------------          --------------------------------------
1001                       nt_list_item(o_list_item
                           (11,200,43.00),o_list_item(22,200,43.00),
                           o_list_item(33,200,43.00))

on applying filter through the query the output should hane just one element in
the nested table(say for id 22), and the output should look like :

Purchase_order_no          list_item
-----------------           --------------------------------------
1001                        nt_list_item(o_list_item(22,200,43.00))

Please do respond if anyone have some pointers to this !
and we said...
Not without flattening it or writing a method that does this subsetting
procedurally.

Consider it like this -- the nested table is just an attribute, a column.  If it
was the ENAME column and you were just interested in part of it -- you would use
substr.  We'll have to do something similar.

I guess we could "flatten" and "unflatten" like this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace type Order_Type as object (
id int )
  2  /

Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace type OrderNT as table of
Order_Type
  2  /

Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace type PO as object ( po_num
int, some_order OrderNT )
  2  /

Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t of PO nested table some_order
store as some_order_nt
  2  /

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( PO( 1, OrderNT(
Order_type( 1 ), Order_type( 22 ) ) ) );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select t.po_num,  cast( multiset( select o.*
  2                                       from table( t.some_order ) O
  3                                      where id = 22 ) as orderNt )
  4    from t t
  5  /

    PO_NUM
----------
CAST(MULTISET(SELECTO.*FROMTABLE(T.SOME_ORDER)OWHEREID=22)ASORDERNT)(ID)
---------------------------------------------------------------------------------
--------------------------------------------------
         1
ORDERNT(ORDER_TYPE(22))

as well....
Review & Followup

Rating: 5
Querying object views September 13, 2001
Reviewer: x from CA USA
Thanks a lot for a speedy response....
I needed it badly...

Thanks again
Rating: 5
Need another example December 23, 2003
Reviewer: Tommy W. from Mississippi, USA
I have the following tables with a one to many relationship between
c_hdr_common_t and c_li_exc_t.

SQL> desc msdss.c_hdr_common_t
 Name                                      Null?    Type
 ----------------------------------------- -------- ------------
 C_HDR_PD_DT                               NOT NULL DATE
 B_SYS_ID                                  NOT NULL NUMBER(9)
 C_TCN_NUM                                 NOT NULL CHAR(17)
 C_TOT_REIMB_AMT                                    NUMBER(11,2)
..................

SQL> desc msdss.c_li_exc_t
 Name                                      Null?    Type
 ----------------------------------------- -------- -----------
 C_HDR_PD_DT                               NOT NULL DATE
 B_SYS_ID                                  NOT NULL NUMBER(9)
 C_TCN_NUM                                 NOT NULL CHAR(17)
 C_LI_NUM                                  NOT NULL NUMBER(4)
 R_CLM_EXC_CD                              NOT NULL CHAR(4)
..............

I want to be able to create an object view (or maybe something else) that will
allow me to return 1 occurence of c_hdr_common_t that matches a list of
R_CLM_EXC_CD's.

such as:
SELECT a.B_SYS_ID,a.C_HDR_PD_DT,a.C_TCN_NUM,a.
FROM msdss.clm_hdr_view a
WHERE c_hdr_pd_dt = '22-DEC-2003'
AND  R_CLM_EXC_CD IN ('0423','0230')

I have tried creating the object views below, but still can't get it to the
point of returning
one occurence. Simply adding DISTINCT to the above query is not an option. I
want to be able to use
this view to group by and sum on other data in c_hdr_common_t.

CREATE TYPE msdss.clm_exc_t AS OBJECT
(
  R_CLM_EXC_CD CHAR(4)
);

CREATE TYPE msdss.clm_exc_list_t AS TABLE OF clm_exc_t;

CREATE TYPE msdss.clm_hdr_t AS OBJECT
(   C_HDR_PD_DT DATE,
    B_SYS_ID NUMBER(9),
    C_TCN_NUM CHAR(17),
    clm_exc_list   clm_exc_list_t
);

CREATE VIEW msdss.clm_hdr_view OF clm_hdr_t WITH OBJECT IDENTIFIER
(C_HDR_PD_DT,B_SYS_ID,C_TCN_NUM) AS
    SELECT a.C_HDR_PD_DT,a.B_SYS_ID,a.C_TCN_NUM,
            CAST( MULTISET (
                           SELECT DISTINCT(b.R_CLM_EXC_CD)
                           FROM msdss.c_li_exc_t b
                           WHERE a.C_HDR_PD_DT = b.C_HDR_PD_DT
                           AND a.B_SYS_ID = b.B_SYS_ID
                           AND a.C_TCN_NUM = b.C_TCN_NUM)
                        AS clm_exc_list_t)
                   AS clm_exc_list
   FROM   msdss.c_hdr_common_t a

thanks.
Followup:
is the goal to get the records from the HDR table that have a set of children
records which completely cover the "in list"??

is that the goal (rather then show me the proposed -- but not working --
solution, phrase the question with a little more detail -- tell us exactly what
the desired output is and what the inputs are).

If you say "given 0423, 0230 -- i would like all of the HRD records that have a
child record for each of those codes", we can do that.
Rating: 5
example December 24, 2003
Reviewer: Tommy W from Mississippi, USA
"If you say "given 0423, 0230 -- i would like all of the HRD records that have a
child record for each of those codes"

That's what I'm looking for, but I need all the logic hidden in a view, so the
join of the two tables appears to be flattened, and the criteria can be
expressed as R_CLM_EXC_CD IN ('0423','0230')

Also, if the HRD has more than one child that matches the criteria, I only want
to see one HDR.
ie.. it is possible that a HDR can have child records with values like
(0423,0230,0423)

thanks
Followup:
you are trying to apply SET semantics (in) to a single row -- ain't going to
work.

this is not something you can "hide in a view", not going to happen.
Rating: 5
example December 24, 2003
Reviewer: Tommy W. from MS, USA
Given that I need to be able to use ad-hoc reporting tools to query this data,
and a view will not work, and I would prefer not to create a new
table/materialized view, what would be your recommendation.

thanks
Followup:
this is NOT going to happen in a view.  I don't know what to tell you.  I don't
know the capabilities of your tool.

The query is going to be:

select hdr.columns...
  from hdr, dtl
 where hdr.key = dtl.key
   and dtl.column in ( 'a','b','c')
 group by hdr.columns....
having count(*) = 3

for example of

select ....
  from hdr
 where key in ( select key from dtl where column in ( 'a','b' ) group by key
having count(*) = 2 ) )

see how the INLIST changes.
see how the count(*) = changes.

this is not going to be a view.  I can two step it like this:

create view v
as
select *
  from hdr
 where key in ( select key
                  from dtl
                 where column in ( select * from gtt )
                 group by key
                having count(*) = (select count(*) from gtt) ) )
/

then the user must fill gtt (stands for global temporary table) with the values
of interest before running the query against the view.

Another way would be to use Oracle text (believe it or not).  Would the ad-hoc
tools be able to use the CONTAINS operator?
Rating: 5
example December 24, 2003
Reviewer: Tommy W from MS, USA
The adhoc tool is Business Objects. I'm not the Business Objects designer, but I
believe you can define an object using any available sql.
Followup:
sure, you can define an object -- but -- BO isn't going to query it nor will it
be what you would term "fast" since your query is on what would be the nested
table.

this is not going to happen in a view in any way that is

a) usable
b) performant

BO is going to need to not use a view, it'll query hdr and dtl and it'll be the
query above.

sorry -- but I don't see any efficient or effective alternative here.

we can use text to index this and use the contains clause -- but your back to
"bo is database independent, it isn't going to recognize most features provided
by the database"
Rating: 4
Same thing but using a Pl/SQl Table December 25, 2003
Reviewer: Suleiman Akbar from UAE
Tom,

It's nice to see ur replies everytime u answer to all IT nerds...pls. keep up
the good work.

Request u to pls. have a look for my problem...

pls. find the problem enclosed beneath.

Question)

Declare
 TYPE col1 Is Table of <tab.column_name> Index By Binary_Integer;
 TYPE col2 Is Table of <tab.column_name> Index By Binary_Integer;

pCol1 col1;
pCol2 col2;
pCol3 col2;

Begin
   Select <pk_col_name> Bulk Collect Into pCol1 From <tab1> Where <unq_key_col>
= <value>;
   Select <pk_col_name> Bulk Collect Into pCol2 From <tab2> Where <col_name> =
<value>;

 -- Would like to make u aware that pCol1 has 100 rows.
 -- and pCol2 has only 4 rows.
 -- I want to load remaining 96 rows into pCol3...
   j := 1;
   For i in 1..pCol1.Count Loop
       If pCol1(i) <> pCol2(i) Then
          pCol3(j) := pCol1;
          j := j+1;
       end If;
   End Loop;
End;

-- But it's giving me some error...don't know what's wrong in this code... I
should execute it but when I debug this with Pl/SQL developer, I have noticed
that after comparing the 4 values of pCol2 it's returning it as NULL and it's
going to a no_data_found exception again and again..

Tom request u to solve my query at the earliest...

Hope to see ur reply ASAP..

Suleiman khatib atavur
Followup:
your logic is a bit "botched" there.  you are using I as the index to both pcol1
and pcol2 -- but pcol1 has 100 entries whereas pcol2 has 4!

anyway, here are two approaches

ops$tkyte@ORA9IR2> create or replace procedure method_1
  2  as
  3      type array is table of number index by binary_integer;
  4
  5      l_col1 array;
  6      l_col2 array;
  7      l_col3 array;
  8
  9      function is_in( p_array in array, p_value in number ) return boolean
 10      is
 11      begin
 12          for i in 1 .. p_array.count
 13          loop
 14              if ( p_array(i) = p_value )
 15              then
 16                  return TRUE;
 17              end if;
 18          end loop;
 19          return FALSE;
 20      end is_in;
 21  begin
 22      select object_id bulk collect into l_col1 from all_objects where rownum
<= 100;
 23      select object_id bulk collect into l_col2 from all_objects where rownum
<= 4;
 24
 25      for i in 1 .. l_col1.count
 26      loop
 27          if ( NOT is_in( l_col2, l_col1(i) ) )
 28          then
 29              l_col3(l_col3.count+1) := l_col1(i);
 30          end if;
 31      end loop;
 32
 33      dbms_output.put_line( 'added ' || l_col3.count || ' array entries' );
 34  end;
 35  /

Procedure created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace type myArrayType as table of number
  2  /

Type created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace procedure method_2
  2  as
  3      l_col1 myarrayType;
  4      l_col2 myarrayType;
  5      l_col3 myarrayType;
  6  begin
  7      select object_id bulk collect into l_col1 from all_objects where rownum
<= 100;
  8      select object_id bulk collect into l_col2 from all_objects where rownum
<= 4;
  9
 10      select * bulk collect into l_col3
 11        from ( select * from TABLE( cast( l_col1 as myArrayType ) )
 12               MINUS
 13               select * from TABLE( cast( l_col2 as myArrayType ) )
 14             );
 15
 16      dbms_output.put_line( 'added ' || l_col3.count || ' array entries' );
 17  end;
 18  /

Procedure created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec method_1
added 96 array entries

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec method_2
added 96 array entries

PL/SQL procedure successfully completed.
December 25, 2003 Saminathan -- Thanks for the question regarding "Snapshot with indexes/constraints", version 8.1.7.3
You Asked
Dear Tom,

Could you tell me under what scenario the snapshots can have
indexes/constraints.

I was under impression that snapshot normally does not have any
indexes/constraints but I saw that some snapshots are creating indexes while
creating the snapshots.

Thanks
Sami
and we said...
you can fully index snapshots -- in any manner you want (unique indexes should
not be used, but regular indexes are just fine)

If you add constraints (only supported on UPDATABLE snapshots - only time it
makes sense in fact), you should add them as "deferrable" so the snapshot
refresh process (which sets all constraints "deferred") won't have any issues
with them.
December 25, 2003 Matt -- Thanks for the question regarding "Performance Investigation Approach when using STATSPACK", version 9.0.1.4.0
You Asked
This is just an sanity check on my appraoch...

I've been asked to look at a site who have just carried out a DB upgrade. The
feeling is that there has been some performance degradation since the upgrade.
The performance hit has not been quantified yet but is 'felt throughout the
day'.

This site employs a backup strategy that requires them to shutdown the database
nightly. The disks are then cloned and the clones backed up to tape. This
obviously impact DB performance due to the loss of library and buffer cache
contents and will be something that I will recommend changing (but is does not
address the upgrade performance issue).

My plan is to gather STATSPACK snapshot data every 30 minutes for one of these
re-start cycles and then investigate the performance stats.

My approach (after gathering all the user info about the problem) is to discard
the first 8 or so hours of data as the re-start will skew my analysis (lots of
PIO due to warming up the cache and lots of CPU usage due to parsing).

So, my question is, of the 16 hours of remaining data what is the best approach
to investigating high level performance issues? Should I;

1) Generate a SNAP for the whole 16 hour range and use this for all
investigations?
2) Start with a SNAP for the 16 hour range and then drill into other sections of
this range as a sanity check (binary search the range to check for unusual
behaviour)?
3) Pick the last 30 minute interval and use this for investigations?

My intention is to carry out 2), the initial investigation will focus on the top
wait events and from here drill into the area of the report that might indicate
what is causing these waits. The 24 hour snapshot will then be used as a
baseline for any performance changes that may get implemented as a result of my
investigation.

Can you highlight any potential issues with this approach and anything else that
I might be able to do during the investigation but may not have considered
above?

Thanks and Regards.
and we said...
1) nope, that would be meaningless -- waits and everything would be averaged out
over far too long a period of time.

2) nope

3) nope

take a level 10 every 15 minutes for a day.  Then, pick a representative 15
minute window when performance was "felt to be at its worst" and analyze that
one.

But -- since you don't have anything to compare it to, you are just "tuning".
You will not be able to say "ah-hah, here is the cause from the upgrade" cause
you don't know if that specific "thing" happened during the the old version or
not.

The reason I say that is to go back to:

...(but is does not address the upgrade performance issue).

Well, nothing will from this exercise as you have no baseline from which to
compare.  So, things like "don't do stupid things like cold backups" are just as
important as "hey, tons of log file syncs, lets speed up those disks".  You are
just doing tuning here, you are not going to "find the cause induced by an
upgrade" -- thats just not possible in your case.

(frequent issue -- you upgrade software but forget to look at RAM, which
generally needs to increase as well)
Review & Followup

Rating: 4
Additional Information... February 02, 2003
Reviewer: Matt from Australia
I've already capture data with a vanilla STATSPACK configuration. Some of the
SQL I am seeing pulled out seems to be from the STATSPACK snapshot queries. As a
result (see SNIP below) I am a little wary of increasing the snapshot level at
this time since the doco highlights that extra work will be carried out, I have
some data now, before I take this next step I want to see whether I can make
good use of what I have already.

I'll provide some additional information and then can you please confirm this
approach of increasing the level to 10?

[SNIP From ?/rdbms/admin/spdoc.txt]
Levels  >= 10 Additional statistics:  Parent and Child latches
    This level includes all statistics gathered in the lower levels, and
    additionally gathers Parent and Child Latch information.  Data
    gathered at this level can sometimes cause the snapshot to take longer
    to complete i.e. this level can be resource intensive, and should
    only be used when advised by Oracle personnel.
[END]

OK, I can't point the finger at what changed in the upgrade but surely I can
highlight where gains can be made (and get a feel for how much gain might be
felt by the end users).

Can you provide some guidance as far as the following SNAP is concerned? It was
a taken as described above.

The system is a HYBRID - 80% batch processing (reports and data loads); 20%
OLTP.

STATSPACK report for

DB Name         DB Id    Instance     Inst Num Release     Cluster Host
------------ ----------- ------------ -------- ----------- ------- ------------
xxxx          xxxxxxxxxx xxxx                1 9.0.1.4.0   NO      xxxxxx.xxxxx
                                                                   .com

            Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:      66 31-Jan-03 16:43:01       25      15.4
  End Snap:      67 31-Jan-03 17:13:04       23      11.6
   Elapsed:               30.05 (mins)

Cache Sizes (end)
~~~~~~~~~~~~~~~~~
               Buffer Cache:       393M      Std Block Size:        16K
           Shared Pool Size:        80M          Log Buffer:       160K

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:                347.06             78,219.00
              Logical reads:                 42.38              9,552.25
              Block changes:                  0.21                 47.13
             Physical reads:                  0.56                125.38
            Physical writes:                  0.32                 72.38
                 User calls:                  3.62                815.00
                     Parses:                  0.24                 53.50
                Hard parses:                  0.00                  0.00
                      Sorts:                  1.12                252.50
                     Logons:                  0.00                  0.38
                   Executes:                  4.30                969.13
               Transactions:                  0.00

  % Blocks changed per Read:    0.49    Recursive Call %:    45.56
 Rollback per transaction %:   62.50       Rows per Sort:     5.93

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:   99.66
            Buffer  Hit   %:   99.25    In-memory Sort %:  100.00
            Library Hit   %:  100.00        Soft Parse %:  100.00
         Execute to Parse %:   94.48         Latch Hit %:  100.00
Parse CPU to Parse Elapsd %:   84.62     % Non-Parse CPU:   99.35

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   93.99   93.95
    % SQL with executions>1:   67.62   67.33
  % Memory for SQL w/exec>1:   36.57   36.39

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                            Wait     % Total
Event                                               Waits  Time (s)   Wt Time
-------------------------------------------- ------------ ----------- -------
control file parallel write                           604          16   49.30
db file parallel write                                178           7   20.24
db file sequential read                               383           4   11.78
log file parallel write                               100           2    6.39
async disk IO                                          15           1    3.29
          -------------------------------------------------------------

What I see is:

1) % Blocks changed per Read:    0.49 - very low, a reporting process must be
running.
2) Recursive Call %:    45.56 - We use mostly PL/SQL packages so that should be
attributable to this
3) Rollback per transaction %:   62.50 - rather high and requires investigation

4) Execute to Parse %:   94.48 - For each parse we execute 1.06 times (could be
the nature of the running report)
5) Parse CPU to Parse Elapsd %:   84.62 - low. we are waiting for something -
see WAITS below
6) % Non-Parse CPU:   99.35 - OK

7) Memory Usage %:   93.99   93.95 - a little high
8) % SQL with executions>1:   67.62   67.33 - OK given HYBRID system.
9) % Memory for SQL w/exec>1:   36.57   36.39 - ?????

[SNIP DB Reference...]
Control file Parallel Write:

This event occurs while the session is writing physical blocks to all control
files. This happens when:
  The session starts a control file transaction (to make sure that the control
files are up to date in case the session crashes before committing the control
file transaction)

The session commits a transaction to a control file
Changing a generic entry in the control file, the new value is being written to
all control files
Wait Time: The wait time is the time it takes to finish all writes to all
control files
[END]

Should I investigate this further, and if so how?

Do you have any other comments on the report?
Followup:
My comments:

o the reason the statspack queries were the top is....

o ZERO tps -- this system was not active at all during the measurement time.
suggest you find a busy time, measure then (and use level 10 - statspack.snap
runs for what?  a couple of seconds.  that is the ONLY point in time it affects
the performance at all! when .snap is actually running)

o the rollback % could be attributable to the fact that there were so few actual
transactions....

o 16 seconds of wait time in 30 minutes is less then bothersome, it is noise.
just make sure care has been taken to spread IO out for controlfiles just as you
would for log files.  we write to them continously, you don't want them on busy
file systems.

If there was a performance issue during this window, it's not evident in this
snippet.
Rating: 3
Apologies (and some valid STATSPACK data). February 04, 2003
Reviewer: Matt from Australia
A lesson learnt there. Check the validity of your data. I had missed the zero
transaction count and had assumed that the system was in use 24/7 and so any
SNAP would have highlighted an issue. Apologies for wasting your time.

I now have a correct SNAP for a data load process that takes 55 minutes to
complete (and is reported to have taken approximately 50% less time in the
past).

This problem data load started before and finished after the SNAP IDs used to
generate this.

STATSPACK report for

DB Name         DB Id    Instance     Inst Num Release     Cluster Host
------------ ----------- ------------ -------- ----------- ------- ------------
xxxx          xxxxxxxxxx xxxx                1 9.0.1.4.0   NO      xxxxxxxxxxx
                                                                   .com

            Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:     205 03-Feb-03 05:29:01       29      14.7
  End Snap:     206 03-Feb-03 05:59:05       28      18.1
   Elapsed:               30.07 (mins)

Cache Sizes (end)
~~~~~~~~~~~~~~~~~
               Buffer Cache:       393M      Std Block Size:        16K
           Shared Pool Size:        80M          Log Buffer:       160K
Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:            172,724.94            478,641.77
              Logical reads:              4,331.81             12,003.97
              Block changes:                978.82              2,712.42
             Physical reads:                 12.98                 35.96
            Physical writes:                 15.66                 43.41
                 User calls:              1,352.03              3,746.65
                     Parses:                  9.82                 27.21
                Hard parses:                  0.00                  0.01
                      Sorts:                  5.83                 16.16
                     Logons:                  0.00                  0.01
                   Executes:              2,746.23              7,610.13
               Transactions:                  0.36

  % Blocks changed per Read:   22.60    Recursive Call %:    77.13
 Rollback per transaction %:    0.00       Rows per Sort:     1.95

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:   99.22       Redo NoWait %:   99.98
            Buffer  Hit   %:   99.86    In-memory Sort %:  100.00
            Library Hit   %:  100.00        Soft Parse %:   99.95
         Execute to Parse %:   99.64         Latch Hit %:   97.39
Parse CPU to Parse Elapsd %:   67.47     % Non-Parse CPU:   99.93

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   53.36   53.77
    % SQL with executions>1:   68.11   69.37
  % Memory for SQL w/exec>1:   71.10   70.79

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                            Wait     % Total
Event                                               Waits  Time (s)   Wt Time
-------------------------------------------- ------------ ----------- -------
buffer busy waits                                  60,866         699   22.89
latch free                                        219,813         515   16.87
log file parallel write                            10,751         494   16.17
db file sequential read                             9,711         319   10.44
db file parallel write                              2,446         278    9.10
          -------------------------------------------------------------
^LWait Events for DB: NSL1  Instance: NSL1  Snaps: 205 -206
-> s  - second
-> cs - centisecond -     100th of a second
-> ms - millisecond -    1000th of a second
-> us - microsecond - 1000000th of a second
-> ordered by wait time desc, waits desc (idle events last)

                                                                   Avg
                                                     Total Wait   wait    Waits
Event                               Waits   Timeouts  Time (s)    (ms)     /txn
---------------------------- ------------ ---------- ---------- ------ --------
buffer busy waits                  60,866         73        699     11     93.5
latch free                        219,813     30,717        515      2    337.7
log file parallel write            10,751      7,337        494     46     16.5
db file sequential read             9,711          0        319     33     14.9
db file parallel write              2,446      2,446        278    114      3.8
log buffer space                    1,375         73        260    189      2.1
control file parallel write         1,672          0        112     67      2.6
control file sequential read       21,147          0         78      4     32.5
async disk IO                         638          0         66    103      1.0
log file sync                         808         11         56     69      1.2
direct path write                   5,266          0         51     10      8.1
log file switch completion            163          0         49    302      0.3
log file sequential read              431          0         39     90      0.7
LGWR wait for redo copy               144         80         15    104      0.2
direct path read                   12,760          0         13      1     19.6
db file scattered read                 89          0          4     46      0.1
enqueue                               883          2          3      4      1.4
log file single write                 124          0          3     26      0.2
process startup                         2          0          0    200      0.0
buffer deadlock                        68         68          0      0      0.1
SQL*Net more data to client             4          0          0      0      0.0
SQL*Net break/reset to clien            2          0          0      0      0.0
SQL*Net message from client     2,439,175          0     12,751      5  3,746.8
pipe get                            4,344      3,648      9,845   2266      6.7
jobq slave wait                        45         43        136   3031      0.1
SQL*Net message to client       2,439,173          0         26      0  3,746.8
SQL*Net more data from clien        2,568          0          0      0      3.9
          -------------------------------------------------------------
^LBackground Wait Events for DB: NSL1  Instance: NSL1  Snaps: 205 -206
-> ordered by wait time desc, waits desc (idle events last)

                                                                   Avg
                                                     Total Wait   wait    Waits
Event                               Waits   Timeouts  Time (s)    (ms)     /txn
---------------------------- ------------ ---------- ---------- ------ --------
log file parallel write            10,750      7,337        494     46     16.5
db file parallel write              2,446      2,446        278    114      3.8
control file parallel write         1,672          0        112     67      2.6
async disk IO                         638          0         66    103      1.0
direct path write                   5,266          0         51     10      8.1
log file sequential read              431          0         39     90      0.7
control file sequential read        2,956          0         36     12      4.5
LGWR wait for redo copy               144         80         15    104      0.2
direct path read                   12,760          0         13      1     19.6
log buffer space                       91          1         11    118      0.1
log file single write                 124          0          3     26      0.2
enqueue                                 7          2          1    151      0.0
latch free                             40         39          1     19      0.1
db file scattered read                 12          0          1     58      0.0
db file sequential read                 5          0          0     16      0.0
rdbms ipc message                  17,264     11,117      8,400    487     26.5
smon timer                              6          6      1,843 ######      0.0
pmon timer                            604        604      1,804   2987      0.9
          -------------------------------------------------------------

This was generated for

My conclusions:

1) There is some work being done!
2 A very small percentage of logical reads resulted in physical I/O (<<1%).
3) 78% of blocks read are unchanged
4) Recursive calls most likely due to using P/SQL packages

5) Parse CPU to Parsed elapsed indicates that there are some waits occuring

6) Some memory in the shared pool seems to be wasted (but actually the shared
pool hasn't filled with SQL since the last reboot)

We have some waits:

buffer busy waits       - 40% of the 30 minute period
latch free              - 28% of the 30 minute period
log file parallel write - 27% of the 30 minute period
db file sequential read - 18% of the 30 minute period
db file parallel write  - 15% of the 30 minute period

I assume that if we fix the buffer busy waits. Then the knock on effect will
reduce (some) of the remaining waits.

Buffer Busy Waits:
Wait until a buffer becomes available. This event happens because a buffer is
either being read into the buffer cache by another session (and the session is
waiting for that read to complete) or the buffer is the buffer cache, but in a
incompatible mode (that is, some other session is changing the buffer).
Wait Time: Normal wait time is 1 second. If the session was waiting for a buffer
during the last wait, then the next wait will be 3 seconds.

Questions:
==========

Do my conclusions  above make sense?
How do I identify which buffers are being waited for?
How do I reduce these waits?
Is there anything else that I need to look at?

Thanks and Regards,
Followup:
how many cpus do you have?
how many processes run in parallel to do this load (and if one - why???)
what were the statistics for CPU in the statspack report?
Rating: 3
Information as requested and some additional stuff February 04, 2003
Reviewer: Matt from Australia
The machine has 4 cpu's and 4Gb of Memory.

There are several (12) processes that carry out the data load. For the period of
this snapshot there were 6 processes loading concurrently ( roughly half of
these complete midway through the snapshot).

When you ask for the CPU statistics, I think that you want the Instance Activity
Stats. Here they are (plus some extra):

^LInstance Activity Stats for DB: NSL1  Instance: NSL1  Snaps: 205 -206

Statistic                                      Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------
CPU used by this session                     522,826          289.8        803.1
CPU used when call started                   522,829          289.8        803.1
CR blocks created                              8,513            4.7         13.1
DBWR buffers scanned                           2,792            1.6          4.3
DBWR checkpoint buffers written               21,349           11.8         32.8
DBWR checkpoints                                  62            0.0          0.1
DBWR free buffers found                        1,640            0.9          2.5
DBWR lru scans                                   140            0.1          0.2
DBWR make free requests                          158            0.1          0.2
DBWR revisited being-written buff                  0            0.0          0.0
DBWR summed scan depth                         2,792            1.6          4.3
DBWR transaction table writes                    405            0.2          0.6
DBWR undo block writes                         8,364            4.6         12.9
SQL*Net roundtrips to/from client          2,438,683        1,351.8      3,746.1
background checkpoints completed                  62            0.0          0.1
background checkpoints started                    62            0.0          0.1
background timeouts                            2,019            1.1          3.1
buffer is not pinned count                 2,322,395        1,287.4      3,567.4
buffer is pinned count                     3,784,897        2,098.1      5,814.0
bytes received via SQL*Net from c        600,699,809      332,982.2    922,734.0
bytes sent via SQL*Net to client         415,529,514      230,337.9    638,294.2
calls to get snapshot scn: kcmgss          3,453,833        1,914.5      5,305.4
calls to kcmgas                               23,186           12.9         35.6
calls to kcmgcs                                7,451            4.1         11.5
change write time                             30,285           16.8         46.5
cleanouts and rollbacks - consist              8,283            4.6         12.7
cleanouts only - consistent read                 147            0.1          0.2
cluster key scan block gets                  322,248          178.6        495.0
cluster key scans                             47,441           26.3         72.9
commit cleanout failures: callbac                 11            0.0          0.0
commit cleanout failures: cannot                  72            0.0          0.1
commit cleanouts                              30,681           17.0         47.1
commit cleanouts successfully com             30,598           17.0         47.0
consistent changes                         1,700,678          942.7      2,612.4
consistent gets                            6,840,920        3,792.1     10,508.3
consistent gets - examination              4,432,309        2,456.9      6,808.5
cursor authentications                            20            0.0          0.0
data blocks consistent reads - un          1,699,869          942.3      2,611.2
db block changes                           1,765,784          978.8      2,712.4
db block gets                                973,743          539.8      1,495.8
deferred (CURRENT) block cleanout             23,958           13.3         36.8
dirty buffers inspected                          492            0.3          0.8
enqueue conversions                                6            0.0          0.0
enqueue releases                              19,106           10.6         29.4
enqueue requests                              21,250           11.8         32.6
enqueue timeouts                               2,117            1.2          3.3
enqueue waits                                    833            0.5          1.3
exchange deadlocks                                68            0.0          0.1
execute count                              4,954,193        2,746.2      7,610.1
free buffer inspected                            495            0.3          0.8
free buffer requested                         29,411           16.3         45.2
hot buffers moved to head of LRU              18,758           10.4         28.8
immediate (CR) block cleanout app              8,430            4.7         13.0
immediate (CURRENT) block cleanou              4,980            2.8          7.7
leaf node splits                                  66            0.0          0.1
logons cumulative                                  4            0.0          0.0
^LInstance Activity Stats for DB: NSL1  Instance: NSL1  Snaps: 205 -206

Statistic                                      Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------
messages received                             12,452            6.9         19.1
messages sent                                 12,452            6.9         19.1
no buffer to keep pinned count                     0            0.0          0.0
no work - consistent read gets             1,390,113          770.6      2,135.4
opened cursors cumulative                     17,636            9.8         27.1
parse count (failures)                             1            0.0          0.0
parse count (hard)                                 9            0.0          0.0
parse count (total)                           17,716            9.8         27.2
parse time cpu                                   363            0.2          0.6
parse time elapsed                               538            0.3          0.8
physical reads                                23,407           13.0         36.0
physical reads direct                         12,834            7.1         19.7
physical writes                               28,259           15.7         43.4
physical writes direct                         5,266            2.9          8.1
physical writes non checkpoint                22,372           12.4         34.4
pinned buffers inspected                           0            0.0          0.0
prefetched blocks                                772            0.4          1.2
process last non-idle time             2,088,423,277    1,157,662.6  3,208,023.5
recovery blocks read                               0            0.0          0.0
recursive calls                            8,225,672        4,559.7     12,635.4
recursive cpu usage                          186,061          103.1        285.8
redo blocks written                          314,595          174.4        483.3
redo buffer allocation retries                 1,555            0.9          2.4
redo entries                                 890,378          493.6      1,367.7
redo log space requests                          200            0.1          0.3
redo log space wait time                       4,924            2.7          7.6
redo ordering marks                                2            0.0          0.0
redo size                                311,595,792      172,724.9    478,641.8
redo synch time                                5,553            3.1          8.5
redo synch writes                              1,806            1.0          2.8
redo wastage                               5,667,272        3,141.5      8,705.5
redo write time                               81,272           45.1        124.8
redo writer latching time                      1,495            0.8          2.3
redo writes                                   10,751            6.0         16.5
rollback changes - undo records a                  3            0.0          0.0
rollbacks only - consistent read                 502            0.3          0.8
rows fetched via callback                     34,940           19.4         53.7
session connect time                   2,088,423,277    1,157,662.6  3,208,023.5
session logical reads                      7,814,584        4,331.8     12,004.0
session uga memory max                     3,031,480        1,680.4      4,656.7
shared hash latch upgrades - no w            897,857          497.7      1,379.2
shared hash latch upgrades - wait             10,013            5.6         15.4
sorts (memory)                                10,523            5.8         16.2
sorts (rows)                                  20,511           11.4         31.5
summed dirty queue length                      2,312            1.3          3.6
switch current to new buffer                     401            0.2          0.6
table fetch by rowid                       2,835,932        1,572.0      4,356.3
table fetch continued row                      3,098            1.7          4.8
table scan blocks gotten                      95,475           52.9        146.7
table scan rows gotten                     5,390,773        2,988.2      8,280.8
table scans (long tables)                          0            0.0          0.0
table scans (short tables)                    51,298           28.4         78.8
user calls                                 2,439,071        1,352.0      3,746.7
user commits                                     651            0.4          1.0
user rollbacks                                     0            0.0          0.0
write clones created in backgroun                  1            0.0          0.0
^LInstance Activity Stats for DB: NSL1  Instance: NSL1  Snaps: 205 -206

Statistic                                      Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------
write clones created in foregroun                794            0.4          1.2

(...snip)

Buffer wait Statistics for DB: NSL1  Instance: NSL1  Snaps: 205 -206
-> ordered by wait time desc, waits desc

                                 Tot Wait    Avg
Class                    Waits   Time (s) Time (ms)
------------------ ----------- ---------- ---------
data block              55,376        664        12
undo block               5,405         45         8
undo header                 36          0        10
segment header              87          0         1
          -------------------------------------------------------------

PGA Memory Stats for DB: NSL1  Instance: NSL1  Snaps: 205 -206
-> WorkArea (W/A) memory is used for: sort, bitmap merge, and hash join ops

Statistic                                  Begin (M)          End (M)     % Diff
----------------------------------- ---------------- ---------------- ----------
maximum PGA allocated                         48.387           48.387        .00
          -------------------------------------------------------------
^LEnqueue activity for DB: NSL1  Instance: NSL1  Snaps: 205 -206
-> Enqueue stats gathered prior to 9i should not be compared with 9i data
-> ordered by waits desc, requests desc

                                                       Avg Wt        Wait
Eq     Requests    Succ Gets Failed Gets       Waits  Time (ms)    Time (s)
-- ------------ ------------ ----------- ----------- ----------- ------------
TX       14,186       14,185           0         816        2.78            2
HW          228          228           0          10        1.70            0
CF        1,743        1,726          17           7      152.29            1
          -------------------------------------------------------------
^LRollback Segment Stats for DB: NSL1  Instance: NSL1  Snaps: 205 -206
->A high value for "Pct Waits" suggests more rollback segments may be required
->RBS stats may not be accurate between begin and end snaps when using Auto Undo
  managment, as RBS may be dynamically created and dropped as needed

        Trans Table       Pct   Undo Bytes
RBS No      Gets        Waits     Written        Wraps  Shrinks  Extends
------ -------------- ------- --------------- -------- -------- --------
     0            7.0    0.00               0        0        0        0
     2          180.0    0.00         106,798        0        0        0
     3          198.0    0.00         172,758        0        0        0
     4          172.0    0.00          73,484        0        0        0
     5        2,466.0    0.00      10,223,994        2        0        0
     6        8,150.0    0.00      27,858,772        5        0        4
     7        9,057.0    0.00      29,412,858        6        0        5
     8          203.0    0.00         244,494        0        0        0
     9        9,109.0    0.01      29,223,536        6        0        5
    10        2,392.0    0.00       9,777,734        2        0        1
    11        6,333.0    0.00      22,915,372        5        0        4
          -------------------------------------------------------------

^LRollback Segment Storage for DB: NSL1  Instance: NSL1  Snaps: 205 -206
->Optimal Size should be larger than Avg Active

RBS No    Segment Size      Avg Active    Optimal Size    Maximum Size
------ --------------- --------------- --------------- ---------------
     0       1,556,480           4,915                       1,556,480
     2      10,469,376               0      10,485,760      36,683,776
     3      10,469,376               0      10,485,760      10,469,376
     4      10,469,376               0      10,485,760      10,469,376
     5      15,712,256       4,678,219      10,485,760      20,955,136
     6      31,440,896       7,197,523      10,485,760      31,440,896
     7      36,683,776       9,346,924      10,485,760      36,683,776
     8      10,469,376               0      10,485,760      10,469,376
     9      36,683,776       9,346,924      10,485,760      36,683,776
    10      15,712,256       1,518,960      10,485,760      15,712,256
    11      31,440,896       7,199,803      10,485,760      31,440,896
          -------------------------------------------------------------
^LLatch Activity for DB: NSL1  Instance: NSL1  Snaps: 205 -206
->"Get Requests", "Pct Get Miss" and "Avg Slps/Miss" are statistics for
  willing-to-wait latch get requests
->"NoWait Requests", "Pct NoWait Miss" are for no-wait latch get requests
->"Pct Misses" for both should be very close to 0.0
-> ordered by Wait Time desc, Avg Slps/Miss, Pct NoWait Miss desc

                                           Pct    Avg   Wait                 Pct
                              Get          Get   Slps   Time       NoWait NoWait
Latch                       Requests      Miss  /Miss    (s)     Requests   Miss
------------------------ -------------- ------ ------ ------ ------------ ------
shared pool                      73,360    0.0    0.0      0            0
messages                         50,845    0.1    0.0      0            0
redo allocation                 914,048    0.3    0.0      0            0
session idle bit              4,878,547    0.0    0.1      0            0
library cache                28,579,396    4.6    0.1      0            0
cache buffer handles             39,898    0.0    0.2      0            0
latch wait list               1,100,299    0.6    0.2      0            0
undo global data                124,805    0.0    0.3      0            0
redo writing                     32,727    0.1    0.3      0            0
cache buffers chains         17,523,755    0.4    0.4      0       30,543    0.0
list of block allocation         27,350    0.0    0.5      0            0
transaction allocation           41,136    0.1    0.5      0            0
checkpoint queue latch          146,571    0.0    0.5      0            0
enqueues                         51,443    0.0    0.6      0            0
enqueue hash chains              41,265    0.1    0.7      0            0
cache buffers lru chain          26,876    0.0    1.0      0       28,229    0.1
job workq parent latch                0                    0            2    0.0
process allocation                    3    0.0             0            3    0.0
redo copy                             0                    0      892,071    0.0
FAL request queue                   122    0.0             0            0
FIB s.o chain latch                 494    0.0             0            0
FOB s.o list latch                  769    0.0             0            0
child cursor hash table             101    0.0             0            0
user lock                             8    0.0             0            0
transaction branch alloc             28    0.0             0            0
sort extent pool                     34    0.0             0            0
session timer                       605    0.0             0            0
session switching                    28    0.0             0            0
session allocation                1,476    0.0             0            0
sequence cache                    2,043    0.0             0            0
row cache objects                 2,614    0.0             0            0
ncodef allocation latch              28    0.0             0            0
multiblock read objects             260    0.0             0            0
loader state object free            420    0.0             0            0
library cache load lock              30    0.0             0            0
ktm global data                       6    0.0             0            0
job_queue_processes para             33    0.0             0            0
hash table column usage               4    0.0             0            0
file number translation          18,170    0.0             0            0
event group latch                     3    0.0             0            0
process group creation                7    0.0             0            0
post/wait queue latch             1,949    0.0             0            0
dml lock allocation               1,588    0.0             0            0
channel operations paren            544    0.0             0            0
channel handle pool latc              7    0.0             0            0
SQL memory manager worka             67    0.0             0            0
active checkpoint queue           2,175    0.0             0            0
archive control                     186    0.0             0            0
archive process latch               123    0.0             0            0
^LLatch Activity for DB: NSL1  Instance: NSL1  Snaps: 205 -206
->"Get Requests", "Pct Get Miss" and "Avg Slps/Miss" are statistics for
  willing-to-wait latch get requests
->"NoWait Requests", "Pct NoWait Miss" are for no-wait latch get requests
->"Pct Misses" for both should be very close to 0.0
-> ordered by Wait Time desc, Avg Slps/Miss, Pct NoWait Miss desc

                                           Pct    Avg   Wait                 Pct
                              Get          Get   Slps   Time       NoWait NoWait
Latch                       Requests      Miss  /Miss    (s)     Requests   Miss
------------------------ -------------- ------ ------ ------ ------------ ------
          -------------------------------------------------------------
^LLatch Sleep breakdown for DB: NSL1  Instance: NSL1  Snaps: 205 -206
-> ordered by misses desc
Latch Name                       Requests      Misses      Sleeps Sleeps 1->4
-------------------------- -------------- ----------- ----------- ------------
library cache                  28,579,396   1,311,420     188,628 1140566/1542
                                                                  09/15624/102
                                                                  1/0
cache buffers chains           17,523,755      78,497      29,264 0/0/0/0/0
latch wait list                 1,100,299       6,836       1,564 5291/1526/19
                                                                  /0/0
redo allocation                   914,048       3,102         131 2979/115/8/0
                                                                  /0
session idle bit                4,878,547       1,816         172 0/0/0/0/0
messages                           50,845          60           2 58/2/0/0/0
transaction allocation             41,136          37          19 18/19/0/0/0
undo global data                  124,805          37          10 0/0/0/0/0
enqueue hash chains                41,265          32          22 10/22/0/0/0
redo writing                       32,727          23           8 15/8/0/0/0
enqueues                           51,443          18          11 7/11/0/0/0
checkpoint queue latch            146,571          17           9 8/9/0/0/0
cache buffer handles               39,898           5           1 4/1/0/0/0
cache buffers lru chain            26,876           3           3 0/3/0/0/0
list of block allocation           27,350           2           1 1/1/0/0/0
^LLatch Miss Sources for DB: NSL1  Instance: NSL1  Snaps: 205 -206
-> only latches with sleeps are shown
-> ordered by name, sleeps desc

                                                     NoWait              Waiter
Latch Name               Where                       Misses     Sleeps   Sleeps
------------------------ -------------------------- ------- ---------- --------
cache buffer handles     kcbzfs                           0          1        1
cache buffers chains     kcbgtcr: kslbegin                0     18,616   10,949
cache buffers chains     kcbgcur: kslbegin                0      7,188    1,447
cache buffers chains     kcbchg: kslbegin: bufs not       0      1,529    4,296
cache buffers chains     kcbzwb                           0      1,086      225
cache buffers chains     kcbrls: kslbegin                 0        560    7,639
cache buffers chains     kcbchg: kslbegin: call CR        0        188    4,610
cache buffers chains     kcbget: pin buffer               0         21       12
cache buffers chains     kcbcge                           0          6        5
cache buffers chains     kcbzgb: scan from tail. no       0          5        0
cache buffers chains     kcbget: exchange                 0          2        2
cache buffers chains     kcbnlc                           0          2       36
cache buffers chains     kcbgtcr                          0          2        0
cache buffers chains     kcbzib: finish free bufs         0          1        0
cache buffers lru chain  kcbzgb: multiple sets nowa       0          3        0
checkpoint queue latch   kcbklbc: Link buffer into        0          7        5
checkpoint queue latch   kcbbxsv: move to being wri       0          2        2
enqueue hash chains      ksqrcl                           0         17       12
enqueue hash chains      ksqgtl3                          0          3        5
enqueue hash chains      ksqcmi: get hash chain lat       0          2        5
enqueues                 ksqdel                           0          5        1
enqueues                 ksqrcl                           0          5        8
enqueues                 ksqgtl2                          0          1        3
latch wait list          kslges add                       0      1,346      257
latch wait list          kslges delete                    0        218    1,307
library cache            kglupc: child                    0     63,235   59,894
library cache            kglpnc: child                    0     59,487   61,164
library cache            kglpnp: child                    0     33,337   31,786
library cache            kglpndl: child: before pro       0     29,344   31,674
library cache            kglpnal: child: alloc spac       0      1,421    1,425
library cache            kgllkdl: child: cleanup          0        908      544
library cache            kglhdgc: child:                  0        374      853
library cache            kglhdgn: child:                  0        247       89
library cache            kgllkdl: child: free pin         0        139    1,097
library cache            kglic                            0        118       91
library cache            kglpsl: child                    0          6        7
library cache            kgldte: child 0                  0          3       13
library cache            kglini: child                    0          2        2
library cache            kglpin                           0          2        1
library cache            kglati                           0          1        0
library cache            kgldti: 2child                   0          1        0
library cache            kgldtld: 2child                  0          1        0
library cache            kglget: child: KGLDSBRD          0          1        0
list of block allocation ktlabl                           0          1        0
messages                 ksaamb: after wakeup             0          1        2
messages                 ksarcv                           0          1        0
redo allocation          kcrfwr: redo allocation          0        127      107
redo allocation          kcrfwi: before write             0          3       14
redo allocation          kcrfwi: more space               0          1       10
redo writing             kcrfsr                           0          8        0
session idle bit         ksupuc: clear busy               0         90      105
session idle bit         ksupuc: set busy                 0         82       66
transaction allocation   ktcxba                           0         13       13
^LLatch Miss Sources for DB: NSL1  Instance: NSL1  Snaps: 205 -206
-> only latches with sleeps are shown
-> ordered by name, sleeps desc

                                                     NoWait              Waiter
Latch Name               Where                       Misses     Sleeps   Sleeps
------------------------ -------------------------- ------- ---------- --------
transaction allocation   ktcdso                           0          6        6
undo global data         ktubnd                           0          7        0
undo global data         ktudnx: KSLBEGIN                 0          3        3
          -------------------------------------------------------------
^LDictionary Cache Stats for DB: NSL1  Instance: NSL1  Snaps: 205 -206
->"Pct Misses"  should be very low (< 2% in most cases)
->"Cache Usage" is the number of cache entries being used
->"Pct SGA"     is the ratio of usage to allocated size for that cache

                                Get    Pct   Scan   Pct      Mod      Final  Pct
Cache                      Requests   Miss   Reqs  Miss     Reqs      Usage  SGA
---------------------- ------------ ------ ------ ----- -------- ---------- ----
dc_files                          1    0.0      0              0          1    9
dc_free_extents                 202    7.4     19   0.0       19         99   27
dc_object_ids                    58    0.0      0              0        531  100
dc_objects                       33   18.2      0              0      1,755  100
dc_profiles                       2    0.0      0              0          1   33
dc_rollback_segments            168    0.0      0              0         15   79
dc_segments                      77    0.0      0             19        511   99
dc_sequences                     33    0.0      0             33          7   78
dc_tablespaces                  113    0.9      0              0         55   87
dc_used_extents                  19  100.0      0             19         22   92
dc_user_grants                    6   16.7      0              0         23   66
dc_usernames                     10   20.0      0              0         12   52
dc_users                         19    5.3      0              0         27   90
          -------------------------------------------------------------
Library Cache Activity for DB: NSL1  Instance: NSL1  Snaps: 205 -206
->"Pct Misses"  should be very low

                         Get  Pct        Pin        Pct               Invali-
Namespace           Requests  Miss     Requests     Miss     Reloads  dations
--------------- ------------ ------ -------------- ------ ---------- --------
BODY                      29    0.0             29    0.0          0        0
CLUSTER                    5    0.0              8    0.0          0        0
PIPE                   3,237    0.0          3,933    0.0          0        0
SQL AREA              17,602    0.0      9,152,079    0.0          0        0
TABLE/PROCEDURE           86   10.5      4,870,564    0.0          0        0
          -------------------------------------------------------------
^LSGA Memory Summary for DB: NSL1  Instance: NSL1  Snaps: 205 -206

SGA regions                       Size in Bytes
------------------------------ ----------------
Database Buffers                    419,430,400
Fixed Size                              435,752
Redo Buffers                            180,224
Variable Size                       150,994,944
                               ----------------
sum                                 571,041,320
          -------------------------------------------------------------
(...snip)

I think that there may be a couple of issues here (non of which are upgrade
related), but I guess I need some confirmation:

1) Freelist contention for the tables being inserted/updated (if this is the
case how do I track this - look at the INSERTS UPDATES is the SQL part of the
report? How do I fix it - LMS with auto segment mangement?)

2) Possibly some 'Checkpoint not complete' errors - nothing in the alert log,
but from memory I don't think that 9i logs these in the alert.log by default.
How do I confirm this?

3) Lots of indexes used for any queries (currently using RBO)

Can you please comment on the additional information I have provided?
Followup:
1) just add more freelists, ALTER TABLE and ALTER INDEX let you do that.

2) they would be, you would see log waits big time if there were.

3) i was looking to see if you are cpu bound.  you are not, looks like you were
less than 75% utilized (2.8 cpu seconds used per second, you have 4 cpu seconds
per second to use)

bump up freelists, increase load processes.

freelists will reduce buffer busy waits
more loads will use the CPU you have idle.
Rating: 4
Clarification February 04, 2003
Reviewer: Matt from Australia
OK, I can add more freelists to the tables and indexes involved. I guess 10-20
will be a reasonable number depending on how many processes insert/update
concurrently.

I understand the 'checkpoint not complete' is not an issue here. The message
would be logged in the alert.log.

As far as your CPU comments are concerned:

If I understand you correctly since we have 4 cpus, for every elapsed second we
have 4 cpu seconds.

How did you calculate that only 2.8 CPU seconds are used per second? The data I
see is:

Statistic                                      Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------
CPU used by this session                     522,826          289.8        803.1
CPU used when call started                   522,829          289.8        803.1

Is this really 289.8 seconds per second? Ah, I see it now this is actually
289.8% CPU per second which is 289.8/4=72% per CPU. So we are wasting CPU
cycles.

Is this correct?

Many thanks.
Followup:
The stats for cpu are in hsecs.  I divided by 100 2.898 ~ 2.8.  Using pct's
worked since you really divide them by 100 as well.

You are not "wasting" them
You are just not "using them"

You have 7200 cpu seconds in that 30 minute window (30*60*4), you are using
5,228 of them.
Rating: 4
February 04, 2003
Reviewer: A reader
Hi Tom,

Am going through your book Chapt 10 Tuning Strategies and Tools, page 480

"On the system reported on, one out of every 345 transaction resulted in
rollback - this is acceptable"

Tom how did you calculate that number 345 ??

Thanks..
Followup:
rollback per transaction = 0.29%

  1* select 29/100, round( 1/345*100, 2 ) from dual
ops$tkyte@ORA920> /

    29/100 ROUND(1/345*100,2)
---------- ------------------
       .29                .29

1 out of 345 is 0.29% of the time...
Rating: 4
Additional on initrans and maxtrans February 04, 2003
Reviewer: Matt from Australia
I've just been investigating the required changes.

Most of the systems tables have an initrans of 1 and a maxtrans of 255. Which I
guess are the defaults.

Is there any good reason why this might be required???

My understanding is that this will seriously impact the concurrency of updates
to data that is found on the same block.

Is there any advantage to these current settings?

A specific set (included in this performance problem) have
initrans of 0 and a maxtrans of 0.

select table_name, ini_trans, max_trans, freelists, freelist_groups from
user_tables

TABLE_NAME                      INI_TRANS  MAX_TRANS  FREELISTS FREELIST_GROUPS
------------------------------ ---------- ---------- ---------- ---------------
TABLE_XXXXXXXXXXXXXXX03                 0          0          1               1
Followup:
for most systems, the defaults are just fine as there is generally enough room
on a block for the transaction entries to grow if need be.

where you might be needing to fine tune them is on a system that uses
serializable transactions heavily.  There, we may need to pre-allocate the
transaction slots on the block to avoid them being reused -- much in the same
way we preallocate rollback to avoid it being reused.  There it is not a
concurrency thing in as much as providing us the ability to provide the
serializable feature.  You can get "cannot serialize access" errors if we reuse
the transaction slots during your transaction (just like you can get ora-1555 if
we reuse rollback you need during your transaction).
Rating: 3
Error in previous post. February 05, 2003
Reviewer: Matt from Australia
Sorry the point of the above post was that I have found a number of tables with
initrans and maxtrans of zero in the database.

Is this unusual and if so why might someone have decided to set these values?

It seems to me that this must be an error as I can't think of a good reason why
someone might want to limit block updates like this?
Followup:
Those tables are not segments -- not like normal tables.  ini trans and max
trans of 0 aren't "real" (they should have nulled them out instead of decoding
them to zero)

They could be tables in a CLUSTER (look at C_OBJ#)

Or they could be index organized tables (IOTs)

Both of thos "table" types are tables that do not have segments -- but rather
live in or use some other segment.

The cluster tables "live in" the cluster segment.  the Cluster would have the
ini/max trans.

The IOT tables only have an index, no table -- the ini/max trans would be found
with the index.
Rating: 5
February 05, 2003
Reviewer: Mike from TX
"
Statistic                                      Total     per Second    per Trans
    --------------------------------- ------------------ --------------
------------
    CPU used by this session                     522,826          289.8
803.1
    CPU used when call started                   522,829          289.8
803.1

"

Instead of using statpack, can we use some v$ to find out the same/similar stat
infor?
Thanks.
Followup:
sure, all statspack does is query v$ tables after all.

v$sesstat joined to v$statname by statistic# is what you are looking for.
Rating: 4
Spot on. February 05, 2003
Reviewer: Matt from Australia
Yes these are clustered tables (I looked at dba_tables.cluster_name and
dba_clusters).

initrans set to 2 and maxtrans set to 255 (the defaults)
Rating: 5
STATSPACK WAIT STATS February 07, 2003
Reviewer: Zoran Martic from Dublin, Ireland
Hi all,

Be aware of statspack top wait event statistics:

These statistics are based on all Oracle processes, foreground (your server
processes) and background (LGWR, DBWR, ...).
You can be very very confused sometimes by top waits
because they are not excluding background processing time that not directly
slowdown your foreground processes.

In regards of this you can see control file parallel write as the top wait
event, but in the real life this event is not the case to slowdown your database
at all.

YOU NEED TO THINK ONLY ABOUT WAITS THAT CAUSE YOUR FOREGROUND PROCESSES TO
SLOWDOWN.

This is what is wrong in statspack and many scripts around based on wait event
method.

The only scripts I know that are showing you this difference are Steve Adams
scripts www.ixora.com.au.

See the output from statspack that Matt from Australia post.
First three events will maybe not be on that list at all if you not count work
that DBWR (db file parallel write) or LGWR (log file parallel write) or CKPT
(99% of control file parallel write) did.
If DBWR can write in the background and not causing foreground processes to
slowdown you DO NOT CARE.

YOU WILL NOT FIND THIS FROM STATSPACK.

See now one of more useful scripts to show you only statistics on foreground
processes:

SQL> @response_time_breakdown

MAJOR    MINOR         WAIT_EVENT                                SECONDS    PCT
-------- ------------- ---------------------------------------- -------- ------
CPU time parsing       n/a                                           201  3.85%
         reloads       n/a                                           435  8.33%
         execution     n/a                                          2508 48.04%

disk I/O normal I/O    db file sequential read                       549 10.52%
         full scans    db file scattered read                         47   .89%
         direct I/O    direct path read                                0   .00%
         other I/O     control file sequential read                    0   .00%
                       control file parallel write                     0   .00%

waits    DBWn writes   rdbms ipc reply                               680 13.02%
                       free buffer waits                              66  1.27%
         LGWR writes   log file switch completion                     10   .20%
         enqueue locks enqueue                                         0   .01%
         other locks   latch free                                     33   .62%
                       sort segment request                            1   .02%
                       undo segment extension                          0   .00%
                       buffer busy waits                               0   .00%
                       library cache load lock                         0   .00%

latency  commits       log file sync                                 337  6.45%

MAJOR    MINOR         WAIT_EVENT                                SECONDS    PCT
-------- ------------- ---------------------------------------- -------- ------
latency  network       SQL*Net more data to client                   267  5.12%
                       SQL*Net message to client                      73  1.40%
                       SQL*Net more data from client                   9   .17%
                       SQL*Net break/reset to client                   2   .03%
         file ops      file open                                       2   .04%
                       file identify                                   0   .00%
         process ctl   process startup                                 0   .01%
         misc          refresh controlfile command                     0   .00%

In my statspack for the same database I will get totally wrong waits to look.

THERE ARE WAIT METHODS THAT WILL WASTE YOUR TIME
AND THERE ARE CORRECT WAIT METHODS THAT WILL POINT YOU TO TUNE FIRST FOREGROUND
WAIT EVENTS.

Sometimes you need to tune where and how DBWR is writing, to wich disk if you
want to optimize reads to be faster, or to optimize LGWR to write faster (if you
wait on LGWR).

Be smart. From statspack you will go and tune stupid CKPT, LGWR and DBWR waits
without notice that maybe only part of these events are slowing down your
application (it could be 10%).
YOU ALWAYS NEED TO IDENTIFY THE BIGGEST WAIT.
You will never find this one with statspack.

Regards,
Zoran
Followup:
well, while i agree with lots said here - i would say that statspack does tell
you this.

Waits are just tricky -- control file writes, well, you might actually be doing
them.

sqlnet message from client - could be an idle wait, could mean the client is a
huge bottleneck.  sometimes you need to ignore, sometimes not.

statspack shows you the waits -- all of them.  Yes, you need to use your
knowledge of the system in order to figure out the wheat from the chaff -- but
to just ignore them?  nah, you want to see them and ignore if them if you find
them to not be the cause.

All of the waits are there. You can even tell statspack to conside these other
waits as "idle waits" so they don't show up in the top 5 if you like.
Rating: 5
Statspack WAIT clarification February 08, 2003
Reviewer: Zoran Martic from Dublin, Ireland
Hi Tom,

Just to not be misunderstand.
The point is to get all WAITS that are causing your foreground processes to
slowdown.

Of course that it could be controlfile writes or DBWR writes (on log switch when
the next redo log is active, or if you need to read from disk and you have high
disk utilization and this slowdown sequential/scattered reads).

But this will not in most cases be TOP WAIT EVENT at all.
It could be 20 or 30 in many databases.

What statspack is giving you is not what you need to tune first.
Statspack is great for top SQL's but for waits.

Imagine this.

IF Oracle just create this script and say put first TOP WAITS for foreground
(that includes times from background if for example your commit after delete
needs to wait for LGWR to switch logfile first to write log buffer to the new
redo). This is very easy by only eliminating times for backgrounds from
v$session_event (it will always be the time that it is not directly included in
foreground waits).

I was using statspack for a long time and used to (it was stupid from me, but I
did not think too much at that time)
tune top waits, ususally in 8i is always controlfile wait, DBWR writing, .....
Usually few of them are not at all top waits.

Oracle is build to work that background processes work in optimal way as
possible, that means controlfile writes are usually >90% (maybe more) CKPT wait
event.

Do you mean that this part of CKPT time will be the TOP event as normally in
statspack.

You said you can exclude some events from statspack.
But in this case you will loose a part of time of that event that is causing
foreground processes to slowdown (to say like this it could be 10% of DBWR
writes to cause your application to slowdown).

Do you mean that all DBWR write time is causing slowdown?

Something is in OS that you cannot see that easy, like you have reads
(sequential/scattered) always causing foregrounds to slowdown, but you can
spread better (not like SAM method) all write IO from the disk with maximum
reads to other disks and speed up reads because this disk is less utilized.

Hard game is this.

This guy Steven Adams is very smart. When I realized that many sites are not
dealing with waits in proper way (I used for a long time scripts from orapub,
but this was with the same problem as statspack).

What do you think?

I do not think that I am on the wrong way.

Also Eneterprise Manager, Quest tools, .... are using all WAIT EVENTS startegy.

Just think what is your response time: client, network and foreground process
time (including all waits).

STATSPACK is telling us are we going to tune CKPT, DBWR or foreground (server)
processes.
What is more important to tune CKPT or foreground processes?
Statspack is not telling that for now as I know.

Regards,
Zoran
Rating: 5
Staspack review February 08, 2003
Reviewer: Zoran Martic from Dublin, Ireland
Hi Tom,

In any case STATSPACK is useful as free tool.
You just need to see the background waits in the report and to remove these from
top waits (or all waits).

I am not sure how many people look into background waits.

Just to not say hard words, maybe I cannot see the reason behind why in top
waits are not only top waits for foreground processes as natural.

I spent a lot of time tuning stupid events in the past just because I watched
top waits by default without looking into background waits.
At the end I am guilty because WE DID OR DO NOT TO LOOK PROPERLY statspack
reports. I am not sure am I the only one.

Regards,
Zoran
Rating: 2
Enqueues... February 10, 2003
Reviewer: Matt from Australia
Ok, behind the freelist issue there is another issue relating to enqueues. See
the top 5 waits below.

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                            Wait     % Total
Event                                               Waits  Time (s)   Wt Time
-------------------------------------------- ------------ ----------- -------
enqueue                                               410       1,169   21.63
db file sequential read                            56,729       1,119   20.71
log file parallel write                            17,359         789   14.61
log buffer space                                    2,457         787   14.57
db file parallel write                              5,081         563   10.42

enqueues:
=========
Enqueues are locks that serialize access to database resources. This event
indicates that the session is waiting for a lock that is held by another
session.

^LEnqueue activity for DB: NSL1  Instance: NSL1  Snaps: 580 -581
-> Enqueue stats gathered prior to 9i should not be compared with 9i data
-> ordered by waits desc, requests desc

                                                       Avg Wt        Wait
Eq     Requests    Succ Gets Failed Gets       Waits  Time (ms)    Time (s)
-- ------------ ------------ ----------- ----------- ----------- ------------
UL        3,609        2,209       1,400          25       48.08            1
TX       15,618       15,616           2           5  230,535.20        1,153

So, from above I am concluding that I am seeing TX enqueues on the system. I
have not seen these for other data loads.

I think that I have waits for an Interested Transaction List (ITL).
How can I prove this given the remaining information in the report?
How can I identify which tables need an increased INITTRANS/MAXTRANS?

BTW:

The following parameters are set in the init.ora (I cannot find reference on the
system as to why these may have initially been set):
enqueue_resources             30000
_enqueue_locks                30000

The system is the same as earlier.
Followup:
I would be more inclined to think you have classic blocking/locking as waits for
itls are rather rare.  You had 5 waits for loooonnngggg times (on average).
Looks more like a classic "blocker/blockee -- I've got the row you want to have
already locked".

Now - fortunately for you -- these waits are very long -- so you can in fact
monitor the system and see what the blocker is (and what they are doing) and who
the blockee is and what they are TRYING to do.  That'll give you a very good
clue.
Rating: 2
Additional on INITRANS February 11, 2003
Reviewer: Matt from Australia
For any INITRANS Changes to take effect MUST the table be re-created?

BTE previously there were approcx 100 concurrent loads.
Followup:
it'll affect newly added blocks, but if you want it on the existing blocks --
yes.
Rating: 3
Investigation locks February 11, 2003
Reviewer: Matt from Australia
Can you suggest how I highlight the blockers and blockees?

I've been using:

SELECT s.sid
      ,s.serial#
      ,sw.event
--      ,sw.p1text
      ,sw.p1
--      ,sw.p2text
      ,sw.p2
--      ,sw.p3text
      ,sw.p3
--      ,sw.wait_time
      ,sw.seconds_in_wait
      ,s.sql_hash_value
      ,s.sql_address
FROM V$SESSION s
    ,V$SESSION_WAIT sw
WHERE s.sid = sw.sid
AND s.status = 'ACTIVE'
AND sw.wait_time = 0
order by sw.event

To try and catch active waits for all events (and then filter only on the
enqueues). I then have the info here to find the session info (and the SQL...and
the locks etc).

Is this what you had in mind?

If this is application related. What can I possibly do to fix it?
Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:839412906735
You can use v$lock to see this easily...

If this is application related, we'll have to look at the app and see how it is
they want to work on the same exact rows and change that somehow.
Rating: 5
Calculating CPU usage from STATSPACK March 21, 2003
Reviewer: Logan Palanisamy from Santa Clara, CA USA
Tom,

Here is the CPU releated stats from my STATSPACK output for a four hour period
on a 4 CPU box. There is only once instance on this machine.

Statistic                                    Total   per Second    per Trans
--------------------------------- ---------------- ------------ ------------
CPU used by this session               136,025,771      9,478.5      9,065.4
CPU used when call started               2,607,877        181.7        173.8

How do I calculate CPU usage? CPU used by this session or CPU used when call
started?

Total available CPU seconds = 4 * 60 * 60 * 4 = 57,600 seconds.

CPU Usage when call started% = 26,078/57,600 * 100 = 45%

"CPU used by this session" gives over 100% which doesn't seem correct. What is
it then?
Followup:
4 hours is 3 hours and 45minutes too long for a statspack.  but anyway...

CPU used by this session is normal -- but tell me, do you have some really LONG
running things?  Looks more like an overlap problem then anything else here.

(but generally, you want to use OS TOOLS to measure OS statistics like CPU
utilization)
Rating: 5
CPU Usage March 22, 2003
Reviewer: Logan Palanisamy from Santa Clara, CA USA
Tom,

The reason we have a 4-hour snapshot interval is because it is an Oracle
Applications database with over 300 tablespaces. If we do a snapshot every 15
minutes, it fills up space pretty quickly. Of course, whenever there is any
special need we take a manual on-demand snapshot.

If we measure the CPU usage at the OS level then it gives it at the machine
level instead of at the database level. That is the reason we are calculating
the CPU usage using STATSPACK's data.
Followup:
you only need two snapshots -- 15 minutes apart.

No one said you had to take one every 15 minutes -- just that when you take one,
you want one 15 minutes later (unless the one you just took is the one that is
"15 minutes later")

It'll be extremely hard since CPU usage is measured after calls complete and in
your case, you have reallly long running sessions.  long running calls/sessions
that span your observation window will mess you up -- as they have here.

what you really care about is overall machine utilization, the OS level should
be what you are looking at.

But anyway -- consider this example:

create or replace procedure run_until( p_date in date )
as
begin
    while sysdate < p_date
    loop
        null;
    end loop;
end;
/
exec run_until( sysdate + 1/24/60 * 3 )

So, that procedure just "burns CPU" for 3 minutes as called.

I took a snapshot a little before it started....
I took a snapshot right before it ended...
I took a snapshot right after it finished...

and the results are:

            Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:      21 22-Mar-03 12:24:42       16       4.0
  End Snap:      24 22-Mar-03 12:29:23       16       4.3
   Elapsed:                4.68 (mins)

Statistic                                      Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------
CPU used by this session                       1,092            3.9        273.0
CPU used when call started                     1,093            3.9        273.3

apparently my system was virtually idle.... OR was it:

            Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:      21 22-Mar-03 12:24:42       16       4.0
  End Snap:      25 22-Mar-03 12:29:45       16       4.4
   Elapsed:                5.05 (mins)

Statistic                                      Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------
CPU used by this session                      18,619           61.5      3,723.8
CPU used when call started                    18,619           61.5      3,723.8

Wow, in less then 1/2 of a minute, I used gobs of CPU time...

            Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:      24 22-Mar-03 12:29:23       16       4.3
  End Snap:      25 22-Mar-03 12:29:45       16       4.4
   Elapsed:                0.37 (mins)

Statistic                                      Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------
CPU used by this session                      17,527          796.7     17,527.0
CPU used when call started                    17,526          796.6     17,526.0

And that shows a caveat of statspack -- some statistics are just not reported
until they are done.  So, looking at your 4 hour window -- all that had to
happen was a couple of 8 hour long jobs FINISHED and dumped all of their CPU
time into your window.
Rating: 4
March 23, 2003
Reviewer: Matt from Australia
Very interesting. I was using Metalink Note:223117.1 and the COE performance
approach for some tuning. Basically using a 9.0.1 statspack and breaking down
the response tim einto the constituent components to highlight where the most
benefits could be gotten.

These were my wait events:
Top 5 Wait Events
~~~~~~~~~~~~~~~~~ Wait % Total
Event Waits Time (s) Wt Time
-------------------------------------------- ------------ ----------- -------
db file sequential read 48,481 1,054 23.37
log buffer space 3,824 1,002 22.21
log file parallel write 21,373 858 19.01
latch free 448,006 453 10.04
db file parallel write 2,612 420 9.30
.
This was my CPU usage:
Statistic Total per Second per
Trans
--------------------------------- ------------------ --------------
------------
CPU used by this session 679,596 188.9
678.2

So, response time = service time + wait time

Calculating the total wait time in centi-seconds (using the number for the db
file sequential read entry) we have:

(105400/23.37)*100 = 451006

Service time in centi-seconds is 679596

So, response time = 451006 + 679596 = 1130602  centi-seconds

So calculating percentages (of total response time) for each response time
component we get:

CPU Time = (679596/1130602)*100 = 60%
db file sequential read (105400/1130602)*100 = 9%
log buffer space (100200/1130602)*100 = 8%
...

So, suddenly I am focused on tuning CPU usage rather than anything else (and the
log buffer space wait as well)

Now, there were a number of queries with a large ratio of LIO to executions
(500:1), which was where I was going to start my tuning - these were the problem
statements.

What is a 'good' LIO to execution ratio? Obviously it depends on the number of
rows returned. Is is possible to find out how many rows were processed by an
individual SQL statement using STATSPACK?

The system itself has a number of long running processes which may have been
contributing to the CPU stat by 'dropping in' on my snap, definately worth being
aware of this (in my case, I believe that CPU is indeed in need of tuning).
Followup:
so now you see that all of those CPU related numbers are potentially "suspect"
on any system.  Just takes some long running things to muck them all right....

But -- why would this make you tune CPU usage more then anything else?  I would
be looking at my indexing setup on this database with the goal to reduce the db
file sequential reads.

A good LIO to execution ratio (man do i hate ratios, there is one "ratio" i use
and thats the soft parse one) like all raios has no single "good goal number".
For if we propose one -- there will be some guy walking forward in to a corner
trying to get massive query from heck to fit the ratio (fruitlessly).

As you pointed out - it is a function of the number of rows returned (for one)
but also the number of tables joined (for another).  eg:

select * from emp, dept where emp.deptno = dept.deptno and emp.empno = :x;

I would expect

2-3 lio's to read the emp index
  1 lio to table access by rowid the emp table
2-3 lio's to read the dept index
  1 lio to table access by rowid the dept table
---
  6-8 lios

Now, if that were:

select * from emp, dept where emp.deptno = dept.deptno and dept.deptno = 10;

I would expect

2-3 lio's to read the dept index
  1 lio to read the dept table
2-3 lio's to read the index on emp(deptno) and start range scanning
ceil(1*N/num-rows-per-leaf-block) lio's to scan said index
1*N lio's to table access by rowid the emp table, n = number of rows to return

A 3 table join would take more, a query that returns 100 rows takes more, the
array fetch size will impact this, yadda yadda yadda.

You need to look at that query and say to yourself "what is its job, what does
it do".

Remember a query that has a 5000 to 1 ratio of lios to execute might not be the
one you want to tune first -- especiall if there is one that has a 10 to 1
ratio..  What?  Well, if you execute the 5000/1 once a day but the 10 to 1 a
hundred times/second -- knocking even 1 lio off of that one to get it to 9:1 is
what you want to do.
Rating: 3
CPU usage March 24, 2003
Reviewer: Matt from Australia
"I would be looking at my indexing setup on this database with the goal to
reduce the db file sequential reads."

Agreed, but in this case, fixing the LIO's would fix the db file sequential
reads (and vice-versa). This wasn't really evident from the info posted by me.

The problem query itself is core to all our data loads, so should be the focus
of attention.

What is happening is the use of "PARTITION VIEWS", which are not implemented
correctly. This is causing painful index range scans of multiple partitions - no
partition pruning! This causes the db file sequential reads and the
LIO's.....still working towards those partitioned tables.

Yes, I understood that LIO's were a function of many things. I don't really
think that my thoughts had crystallised into complete sense. I was thinking
about trying to pull out high LIO SQL using a more complicated criteria, but as
you have indicated. The actual criteria is so complicated as to be 'too hard'.

All my conclusions are based on application knowledge and the way that the data
is used. All R.O.T have to be put in context.
Followup:
well said "all ROT have to be put in context"...  That may be my epitaph --
either that or "use binds"
Rating: 5
from performance tuning guide in 8.1.7 April 01, 2003
Reviewer: A reader
Hi
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76992/ch18_cpu.htm#669
from the doc it says this

Read Consistency
Your system may spend excessive time rolling back changes to blocks in order to
maintain a consistent view. Consider the following scenarios:

If there are many small transactions and an active long-running query is running
in the background on the same table where the inserts are happening, then the
query may need to roll back many changes.

If the number of rollback segments is too small, then your system could also be
spending a lot of time rolling back the transaction table. Your query may have
started long ago; because the number of rollback segments and transaction tables
is very small, your system frequently needs to reuse transaction slots.

A solution is to make more rollback segments, or to increase the commit rate.
For example, if you batch ten transactions and commit them once, then you reduce
the number of transactions by a factor of ten.

============================================================

My question is:

for the first statement why the query has to rollback many times?!?!?!

and for the second, why Oracle has to spend a lot of time rolling back the
transaction table? how is transaction table size related t RBS size?

And finally, in the solutions it says increase commit rate, isnt it the other
way round?
Followup:
well, the increase the commit rate is poorly worded at best.  The explanation of
what to do (don't commt batch/batch/batch - do ten batches then commit) tells
you the correct advice -- commit LESS OFTEN, the goal -- reduce the number of
commits.

"increase the commit rate" should probably read decrease.

As for the questions

it is not that it is rolling back many times, but rather that it is rolling back
many changes.  Suppose you have a query that takes 10 minutes.  In that 10
minutes, block X was modified by 500 different transactions.  Block X is the
last block you are going to read.  We have to rollback 500 times.  Consider this
example:

ops$tkyte@ORA817DEV> create table t ( x char(255) );
Table created.

ops$tkyte@ORA817DEV> insert into t values ( 0 );
1 row created.

ops$tkyte@ORA817DEV> create or replace procedure update_t_lots( p_times in
number )
  2  is
  3          pragma autonomous_transaction;
  4  begin
  5          for i in 1 .. p_times
  6          loop
  7                  update t set x = i;
  8                  commit;
  9          end loop;
 10  end;
 11  /
Procedure created.

ops$tkyte@ORA817DEV> create or replace procedure run_demo( p_times in number )
  2  as
  3          type rc is ref cursor;
  4
  5          l_cur rc;
  6          l_data t.x%type;
  7  begin
  8          open l_cur for
  9          'select * from t update_' || p_times;
 10
 11          update_t_lots( p_times );
 12
 13          fetch l_cur into l_data;
 14          close l_cur;
 15  end;
 16  /
Procedure created.

ops$tkyte@ORA817DEV> alter session set sql_trace=true;
Session altered.

ops$tkyte@ORA817DEV> exec run_demo( 0 );
PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> exec run_demo( 500 );
PL/SQL procedure successfully completed.

tkprof shows:

select * from t update_0

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          1          4           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          1          4           1
********************************************************************************
select * from t update_500

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.01       0.01          0        496          4           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.01       0.01          0        496          4           1

See how the second select did 496 lio's to read that one row?  that was the
rolling back of the intervening 500 transactions to reconstruct that block as it
was when the cursor was opened.

The transaction table is just "data" itself.  It is rolled back to the point in
time needed in order to process the rbs data.
Rating: 5
read ITL 500 times? April 01, 2003
Reviewer: A reader
Hi

in your example where 496 LIO took place, as far as I understand is when we need
a consistent view of any modified data block we first read ITL located at the
block header where the row is located and from that we can find out the
transaction slot in the corresponding transaction table in that RBS therefore
find the undo to build the consistent view for in our query. Does this mean,
with your example that Oracle does this process 500 times? Once for each row
change?
Followup:
there were 500 some odd changes to unwind through -- so yes, there was a whole
lot of rollback reading going on to interatively read through all of the
changes.

it wasn't that it read the ITL 500 times, it read through the change of undo to
get back in time to JUST BEFORE the query was opened (and no further).  It has
to walk the list of changes.
Rating: 5
CPU Utilisation is 100% April 02, 2003
Reviewer: Vivek Sharma from Bombay, India
Hi Tom,

This article of yours was very helpful. Thanks. Going from ur calculation, the
statistics of one of my database (statspack report) have db file scattered read
as the top most event in the top 5 events about 76% and latch free being 14%.

CPU Statistics is :
 Instance Activity Stats for DB: CMSOB  Instance: cmsob  Snaps: 35 -41

Statistic                                    Total   per Second    per Trans
--------------------------------- ---------------- ------------ ------------
CPU used by this session                 3,562,881        123.7         85.8
CPU used when call started               3,562,881        123.7         85.8

There are 2 CPU's.
Going thru ur calculations we are utilizing 1.2 cpu seconds per second. The
Snapshot was taken for an interval of 8 hours (I know it is too high and will
reduce it to 15-30 minutes). So the CPU Cycle was 2 * 60 * 60 * 8 = 57600.
So 35628/57600 is 62%. But I have seen that the CPU is 100% and oracle.exe takes
around 99% of CPU (Windows NT Server). If we are using only 62% CPU then why is
oracle showing 100%. But let me tell u that when this statspack report was
generated, I did not checked the CPU Utlisation physically on the server. Shall
I run the statspack when the CPU is 100% and check. And what is the solution to
reduce the CPU Utilisation and how much should it be ? Because when the CPU is
high, users complaint about the slowdown of the database.

Awaiting your reply.

Thanks in Advance
Vivek Sharma.
Followup:
read through this:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7641015793792#8768340190291
and you'll see that computing CPU utilization with statspack can be unreliable.

Suppose you have 5 points in time:

t1  -- jobs 1 and 2 are running

t2  -- you take a snapshot

t3  -- job 1 completes

t4  -- you take another snapshot 15 minutes later.

t5  -- job 2 completes

And those are the only two jobs.

Now, say T1 and T2 are separated by 2 hours.  Statspack would should around 2
hours of CPU time used in that 15 minute snapshot.  Job 1 contributes all of its
runtime when it completes.  If you had 8 cpus, you would say "wow, I was 100%
utilized".  If you had 4 cpus -- you would be wondering what sort of magic
allowed you to be 200% utilized.  And if you were 16 CPUS, you be thinking "hmm,
50%".

Truth was -- if you were 8 cpus, you were 25% (at most, if job 1 finished near
the end of the window) utilized.

4 cpus -- 50%

16 1/8th

and so on...

The way to reduce cpu utilization is -- to use less.

Look at your low hanging fruit.  If you take a query that does 5 LIO's and is
executed 100,000 times -- and reduce that to 3 LIO's by removing a table access
by rowid or something -- that might do it.  Taking the query from heck and
reducing its work -- that might do it.  You are looking to remove things here,
to reduce the need for this shared resource.

Well, say t1 is far far before t2 (an hour). Well stats
Rating: 4
Shared Pool Statistics April 04, 2003
Reviewer: Vivek Sharma from Bombay, India
Hi Tom,

Can u please tell us something about what does these indicate in the statspack
report.

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   79.11   80.14
    % SQL with executions>1:   74.05   67.74
  % Memory for SQL w/exec>1:   32.10   18.82

What should be the value of these and their corrective action.
Followup:
from spdoc.txt in rdbms/admin:

  Instance Efficiency - Shared Pool Statistics are shown for the begin and
  end snapshots.

  o  Memory Usage %:  The percentage of the shared pool which is used.

  o  % SQL with executions>1:  The percentage of reused SQL (i.e. the
     percentage of SQL statements with more than one execution).

  o  % Memory for SQL w/exec>1:  The percentage of memory used for SQL
     statements with more than one execution.

  This data is newly gathered by the 8.1.7 Statspack for level 5 snapshots
  and above, and so will not evident if the report is run against older
  data captured using the 8.1.6 Statspack.

There is no single "corrective action".

You would like ALL of your sql to be reused -- but some won't be -- just because
it isn't (infrequently run queries for example).

The first number is how much of the shared pool you are using.
The second two are described above.
It is information you can use to see if you think things are happening the way
they should.  A really low 2cnd/3rd number coupled with a low soft parse pct
would indicate you are not using bind variables and are hard parsing way too
much.
Rating: 4
Too much hard parsing April 06, 2003
Reviewer: Vivek Sharma from Bombay, India.
Hi Tom,
Thanks for your reply.

Please investigate this for me. The soft parse ratio is 91.43% and the last
value in the Shared Pool Statistics shows too less.
Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Soft Parse %:   91.43
Execute to Parse %: -106.97
Parse CPU to Parse Elapsd %:   61.99

 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   79.11   80.14
    % SQL with executions>1:   74.05   67.74
  % Memory for SQL w/exec>1:   32.10   18.82

From the statspack top sql session, I have seen all the queries using literals
instead of bind variables. Is this low value because of not using bind
variables. I have already asked the application developers to modify their
scripts. It means, I am correct in my approach. Also I have seen Latch Free
event in top 5 wait events. So using bind variables should also reduce this. am
i correct ?

Secondly, the Execute to Parse ratio is -106.97. So according to your formula,
which i understood, for every single 2.07 parses there are 1 executions. am i
correct ?

Thanks
Vivek Sharma
Followup:
yes, yes and yes and yes.  all yes's.

yes - that soft parse ratio is very low.
yes - they are parsing more then they execute.
yes - the latch frees are almost certainly from the high parse counts

but adding binds is only 50% of the solution (a big 50% none the less).  You
must also teach them to parse ONCE and execute OVER AND OVER.
Rating: 4
What did you upgrade ? April 07, 2003
Reviewer: Randy from Chicago,USA
I faced similar problem when I upgraded from 734 to 817. I can share my
experience if it makes sense. What kind of migration you have done and how the
application is written ? What's the interface language ?

Randy
Rating: 5
Need Your Advice April 15, 2003
Reviewer: Riaz Shahid from PRAL, Islamabad, Pakistan
Hello Tom !

Consider the following statspack report (for 24 minutes):

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Buffer Nowait %:  100.00       Redo NoWait %:   99.83
Buffer  Hit   %:   80.38    In-memory Sort %:   99.59
Library Hit   %:   97.57        Soft Parse %:   82.06
Execute to Parse %:   86.54         Latch Hit %:   99.96
Parse CPU to Parse Elapsd %: 0.86  % Non-Parse CPU:   99.99

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
SQL*Net message from dblink 4,372      121,921   91.68
db file scattered read      9,060        2,670    2.01
db file sequential read     7,771        2,116    1.59
db file parallel write      192        1,349    1.01
control file parallel write 619        1,102     .83

My questions:

(1) Why there is less "Parse CPU to Parse Elapsd" ratio ?
(2) Look at the top 5 wait events...I have understanding  that db file scattered
read & db file sequential read tell us something about the setting of
optimizer_index_caching & optimizer_index_cost_adj. So if they are among top 5
wait events, does that mean that the current values of these parameters are set
wrong ???

Admin@STARR.LHR> show parameter optimizer

NAME                                 TYPE    VALUE
==================================== ======= ======
optimizer_features_enable            string  8.1.7
optimizer_index_caching              integer 100
optimizer_index_cost_adj             integer 5
optimizer_max_permutations           integer 80000
optimizer_mode                       string  CHOOSE
optimizer_percent_parallel           integer 0

(3) There are triggers on some tables (before insert) that run the code like
select count(*) into a from t where check_code='ABC';
Where ABC is fixed. My question in this regard is: will this code be re-used if
another user tried to insert data in the table having trigger ? My understanding
is that it should re-use the code since its 100% same in both cases.

Morover, will cacheing table t (which is used in triggers) will improve the
performance or not ?

Waiting for your precious advice

Riaz Shahid
Followup:
1) my question - why is your soft parse % so poor.  It should be 99.5%+

You are waiting on parses, the elapsed time to parse it high relative to the cpu
time for the same.  This happens when you hard parse way too much (you are
waiting in line to parse queries)

2) no they don't (tell you about the setting of anything).  they indicate you
are reading single blocks (sequential read) or multi-block io (scattered read).

Looking at your top 5 waits, they are not relevant.  they are 3.6% of the waits.
your major wait is message from dblink.  as far as I can see, IO isn't an issue
for you -- the network is.

3) the code will be reused, shared.

I would question why you need to do a select count(*) though.  I find 99.999% of
the time -- the code surrounding a count(*) is either

o wrong
o inefficient at best

alter table cache modifies the way the blocks are treated in the buffer cache --
it affects full scans of LARGE tables only.  The way to improve that would be
not to full scan a large table.
Rating: 5
Great April 15, 2003
Reviewer: Riaz Shahid from PRAL, Islamabad, Pakistan
Tom !

Many thanks for your quick response...

Actually we are using 256K ISDN connection and users are remotely accessing the
database. So i agree this is due to network speed. We need to upgrade it.

The reson behind using count(*) is that we use the code:

----other code---
select count(*) into a from check_codes where check_code='ABC';
if a=1 then
------
------
------
end if;

Hope you will suggest it to:

----other code---
begin
select 1 into a from check_codes where check_code='ABC';
------
------
------
exception
when no_data_found then
......
....
end;

(am i right ?)
If so what will be the performance benefit.

What if i make function for that like :

function myfunction(chkid char(6)) return number is
a number(1);
begin
select 1 into a from check_codes where check_id=chkid;
return a;
exception
when no_data_found then return 0; (--there is exactly one row in check_codes
table against each check_id and check_id is PK)
end;

and in trigger:

...............
...............
...............
a:=myfunction('ABC');
if a=1 then
............
............
............
else
............
............
end if;

Please advise
Followup:
what is the PURPOSE of the count(*).  why do you do it in the first place.  It
looks alot like "RI" to me.
Rating: 5
... April 15, 2003
Reviewer: Riaz Shahid from PRAL, Islamabad, Pakistan
Hello Tom !

cr@STARR.LHR> desc check_codes
 Name                    Null?    Type
 ----------------------- -------- ----------------
 CHECK_ID                NOT NULL VARCHAR2(7)
 CHECK_DESCRIPTION                VARCHAR2(200)
 ENABLE_FLAG                      VARCHAR2(1)
 AUTO_FLAG                        VARCHAR2(1)
 ENABLED_DATE                     DATE
 REMARKS                          VARCHAR2(100)

We run the various checks against the rows that are being inserted in the table
t. Before running a check, we need to know that whether that specific check is
enabled or not. So for that purpose we use:

select count(*) into a from check_Codes
where check_id='ABC' and enable_flag='1'

(sorry for not providing you the complete detail i.e; enable_flag)

a=1 ==> (the check is enabled)
a=0 ==> (the check is not enabled and so it should not be run)

Regards
Followup:
ok, this falls into the 'inefficient' arena.

Here is what you should do:

create package checks
as

   g_abc_enabled   boolean default false;
   ...
   procedure ABC( .... );
   ....
end;
/

create package body checks
as

procedure ABC( ..... )
is
begin
   if ( NOT g_abc_enabled ) then return; end if;
   ....
end;

BEGIN
   for x in ( select 1 from check_codes
               where check_id = 'ABC' and enable_flag='1' )
   loop
      g_abc_enabled := true;
   end loop;
   ....
end;
/

That way -- you peek at that table ONCE, any SQL in CHECKS.ABC will be parsed
once per session (different from the trigger) and the trigger would be coded as:

begin
    if ( checks.g_abc_enabled ) then checks.abc( ..... ); end if;
Rating: 5
To Riaz .... April 16, 2003
Reviewer: J. Laurindo Chiappa from Sao Paulo, SP Brazil
PMFJI, but as I understand, you do ** NOT ** want to know the EXACT number of
records with the count, you ONLY want to know IF at least one record exists,
right ? So, *** WHY *** to use COUNT and read the table in full ? Consider :

scott@PO8I:SQL>select count(*) from big_table where c1=1;

  COUNT(*)
----------
         1

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE'

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

scott@PO8I:SQL>select 1 from big_table where c1=1 and rownum =1;

         1
----------
         1

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   COUNT (STOPKEY)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE'

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

scott@PO8I:SQL>

[]s

 Chiappa
Rating: 5
to Chiapa... April 16, 2003
Reviewer: Riaz Shahid from PRAL, Islamabad, Pakistan
Thankx chiapa for your help. But Consider:

cr@STARR.LHR> set autotrace on
cr@STARR.LHR> select count(*) from check_codes
  2  where check_id='200052I';

  COUNT(*)
==========
         1

Elapsed: 00:00:00.97

Execution Plan
==========================================================
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=8)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (UNIQUE SCAN) OF 'PK_CHECK_CODE' (UNIQUE)

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

cr@STARR.LHR> ed
Wrote file afiedt.buf

  1  select 1 from check_codes
  2  where check_id='200052I'
  3* and rownum=1
cr@STARR.LHR> /

         1
==========
         1

Elapsed: 00:00:00.56

Execution Plan
==========================================================
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=8)
   1    0   COUNT (STOPKEY)
   2    1     INDEX (UNIQUE SCAN) OF 'PK_CHECK_CODE' (UNIQUE)

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

As autotrace shows, there's no difference in first & second approaches. Whats
your opinion abt it ?

Thankx
Rating: 5
for Chaiapa April 16, 2003
Reviewer: Riaz Shahid from PRAL, Islamabad, Pakistan
Please add this to above comment:

"and the table check_codes is not a big table...it contains only 66 rows.
Further it is not expected that it will grow much more (maximum 1 row per 5
months)."

Regards

Riaz
Rating: 5
Flip switch after update... April 16, 2003
Reviewer: Jan from Houston, TX, USA
Keep in mind that the global variable g_abc_enabled will not change if you
update check_codes.enable_flag. You might want to add a procedure in that
package to do that and add an after_update trigger on check_codes.enable_flag if
that's a concern.
Rating: 5
Some comments more , to Riaz April 16, 2003
Reviewer: J. Laurindo Chiappa from Sao Paulo, SP, Brazil
Well, my line of thinking was : COUNT always MUST read until the end of the
table, SO ask to read just the first record (the rownum in my case, OR a cursor
reading just the one matching the key), does just this. BUT 66 rows means just a
fistful of blocks, sooo small, probably you will not see any RADICAL performance
improvements here...
  Anyway, by any means follow Tom´s tip and implement it in a stored PL/SQL, as
he said it will help in reducing parses (*** ALWAYS *** a very very good thing),
and that´s it : you will be doing the best possible regarding
performance/scalability. Maybe the bottleneck in your system resides somewhere
(network, some other SQL, hardware usage...) and this action will not improve
anything greatly, BUT at least you are doing all the things at your disposal to
alleviate it.

  []s

   Chiappa
Rating: 5
??? April 16, 2003
Reviewer: Riaz Shahid from PRAL, Islamabad, Pakistan
Tom !

I was a little bit confused with your solution. I want the package to be
dynamic.i;e;

give check_id and pprocedure should tell whether this check is enabled or not
and thats it. Your code is static (i.e; only for check_code='ABC')

I don't know very much abt PL/SQL (like global variables in Packages) so a bit
confused with ur code.

Please advise
Followup:
you have a set KNOWN universe of check codes (you must, else you would not have
any code to execute for the CHECK)...

So, the "...." in the code represent you setting booleans for check='XYZ',
check='LMN' and so on

If you don't know about packages and global variables et. al -- time to read
that plsql book (its short).  Without that knowledge, you'll not be writing
efficient code in any regards.

all my code does it set a bunch of booleans -- driven by a lookup table -- to
tell us whether a check is enabled or not in our session.
Rating: 5
Great April 19, 2003
Reviewer: Riaz Shahid from PRAL, Islamabad, Pakistan
Hello Tom !

You are really great.

I am now seriously thinking about implementing your solution. But what is meant
by (as Mr. chiappa said):

"Keep in mind that the global variable g_abc_enabled will not change if you
update check_codes.enable_flag. You might want to add a procedure in that
package to do that and add an after_update trigger on check_codes.enable_flag if
that's a concern. "

Does it means that a user tries to insert into table and while insertion i
enable some check, then that check will not run on that isnertion (i mean on the
records that are inserted after the updation of enable_flg of that check).

OR

does that means if i enable a check, then all the users hace to reconenct to DB
in order to rhar check to be run on the rows.

OR

does that means i'll have to do "something" (like writing a routine) else that
check wouldn't be run on any of records (no matter the current session or the
next ones).

Regards
Followup:
a trigger would be meaningless in this context.

This package would set its values ONCE per session -- which, for a data load,
should be more then sufficient.  Why in the middle of a load would you turn on a
check -- only for part of it.

If you understand how global package variables work -- what I've done above, you
can make it do whatever you need it to do.
Rating: 4
Performance problem after upgrade May 08, 2003
Reviewer: GM from Germany
Hi Tom,

As I met also some problems with the performance after upgrade, I would like to
ask you for help. The problem is that one of sql statement - create table ... as
select .... from .... where...  now needs abt 7 hours to finish vs 1.5 before
the upgrade. Statement uses db link to tables in remote databases. I use
statspack to collect statistics, but can you tell me what to look exactly for in
the report, which values? I am still not so good report reader and need to gain
more experience. I created snaps during this create statement and this is
extract from the report-5 top events
Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
SQL*Net message to dblink                       2,341,402     617,778    85.75
SQL*Net message from dblink                     2,341,403      28,891     4.01
db file sequential read                         1,969,684      22,457     3.12
direct path write                                  99,163      15,543     2.16
direct path read                                  502,504      12,232     1.70
Isn't is too high value for Waits for SQL*Net message to/from dblink? What else
to look for in the report?

many thanks in advance
Followup:
do you have the plans from before and after for this CTAS?
Rating: 3
CTAS May 08, 2003
Reviewer: A reader
No, to my best regret! I tryed to execute this CTAS in other database (just
copyed tables used in statement ) and result was the same abt 8 hours. Maybe I
should try to install 8i on one test machine , import required tables and see
how much time CTAS needs. But what could cause this long wait?

thanks and regards,
Followup:
look at the plan, does the current plan look "reasonable"
Rating: 3
CTAS - more info May 08, 2003
Reviewer: GM from Germany
After some time the status of the session executing this CTAS  in the base
databse is
ACTIVE, while in remote database in INACTIVE. In v$session view in the base
database, the seq# for this session is growing constantly and value for the
event is 'SQL*Net message from dblink'.
In v$open_cursor there are few rows for this SID. The value for SQL_TEXT for
one if them is SAVEPOINT DOA__LOCKPOINT - I have never seen it before.

regards,
Rating: 3
CTAS May 09, 2003
Reviewer: GM from Germany
Hi Tom,

Now everything seems OK - I just delete/create statistics for all tables.

regards,
Rating: 4
May 13, 2003
Reviewer: Steve from UK
Hi Tom

Following on from the first post above, we have a production database where the
'Rollback per transaction %' is constantly in the 70%-80% range.  How can I go
about investigating the user(s)/process(es) that will be causing this?
Thanks.
Oh, we're on 8.1.7. and W2K (ugh!)  ;-)
Followup:
you can look at v$sesstat -- join to v$statname and look for "user rollbacks"

find sessions that have lots, find out what apps those sessions are running.
Rating: 4
Waits for dblink May 14, 2003
Reviewer: David Pujol from Barcelona, Catalonia
Hi Tom, I'am studing one system where there are a lot of "SQL*Net message from
dblink" (60% of response time). I'd like to know how can I or what ways exist to
minimize these waits?. Can I compute this waits "how" iddle waits?

Thanks and regards
Rating: 5
May 21, 2003
Reviewer: Sean from Atlanta
I got the follow from stackpack:
Snap Id     Snap Time      Sessions Curs/Sess Comment
            ------- ------------------ -------- --------- -------------------
Begin Snap:     988 21-May-03 09:49:37    2,156      25.6
  End Snap:     989 21-May-03 10:04:40    2,160      25.6
   Elapsed:               15.05 (mins)

It says there were >2000 sessions the begin and the end snap shot. It was not
the case, I checked over and over time, we have never had over 150 database
sessions, which includes the active and inactive,  at any given time. The DB is
9.1.0.3.0.

Thanks
Followup:
do a sanity check on your logons current statistic in v$sysstat then.  it just
pulls right from there on the snap.
Rating: 4
May 21, 2003
Reviewer: Sean from Atlanta
""
do a sanity check on your logons current statistic in v$sysstat then.  it just
pulls right from there on the snap.
""

On my 9iR1:
SQL> select count(*) from v$session;

  COUNT(*)
----------
       132

SQL> select * from v$sysstat where name like '%logon%';

STATISTIC# NAME                           CLASS      VALUE
---------- ------------------------- ---------- ----------
         0 logons cumulative                  1      12122
         1 logons current                     1       2194

There was the inconsistance of v$session and v$sysstat, the session numbers from
v$session shall be correct in my database.

On my 9i R2

SQL> select count(*) from v$session;

  COUNT(*)
----------
        79

SQL> select * from v$sysstat where name like '%logon%';

STATISTIC# NAME                           CLASS      VALUE
---------- ------------------------- ---------- ----------
         0 logons cumulative                  1     798604
         1 logons current                     1         78

They were both correct.

Do you think I shall file TAR for the R1?

Thanks
Followup:
sure, if it is wrong, it is wrong.  I see a documented issue with that statistic
in conjunction with parallel query.
Rating: 5
RE: logons current May 21, 2003
Reviewer: Mark A. Williams from Indianapolis, IN USA
As a possibility, check out bug #2327249 (V$SYSSTAT SHOWS HUGE VALUES) - fixed
in 10i

Searching metalink for "v$sysstat logons current" turned up alot of bug reports
for high values in v$sysstat.

HTH,

Mark
Rating: 4
any doc August 22, 2003
Reviewer: Reader
Tom, could you provide any link to doc to interpret the terminology in statspack
report. For example, what do the following terminology mean:

waits (is it like these many times server process waited for a particular
event?)

timeouts means?

Thanks.
Followup:
don't over analyze things here.  timeouts and waits are exactly what you think
they are.  they were WAITS and timeouts waiting for sometime.

Waits:

I waited for IO (db file scattered read for example).
I waited for Latches (latch free for example)
I waited N times, for so many seconds for that resource.

Timeouts:

Timeouts mean "i was waiting.  I was willing to wait only so long.  I timed out
waiting, what I was waiting for wasn't available in the period of time I was
will to wait"
Rating: 5
reason for waits August 23, 2003
Reviewer: Reader
Tom, what are the possible reasons that server process waits for events like db
file sequential read and db file scattered read? Thanks.
Followup:
your disks do not instantaneously return data upon request.

so we wait.

db file sequential reads are usually caused by index accesses (single block IO)

db file scattered reads are usually caused by scanning (multi-block IO)
Rating: 5
who decides how much time should a process wait ? August 23, 2003
Reviewer: reader
"I timed out waiting, what I was waiting for wasn't available in the period of
time I was
will to wait"

Does oracle have a default value for timeout in milliseconds, for example, after
which it would let a process to take a timeout and try again? If i see a larger
number in timeouts column for example in db file sequential read or scattered
read events, what would be the reason and should one approach. Thanks.
Followup:
we decide how long to wait for what and where.

enqueues time out every 3 seconds for example.
latches based on a spin

not all waits have timeouts.

larger number then what?  no single number by itself means anything.  large
waits on IO related events mean you want to look for techniques to

o reduce the amount of io you do
o make your io faster
Rating: 5
What does it mean by latches based on a spin? Thanks. August 23, 2003
Reviewer: Mani from Chennai, India
Followup:
when we go to get a latch

and we cannot get it right away.

we "spin" -- do a tight loop if you will -- just burning some cpu -- to see if
after we spin a bit, we can get the latch.  if after spinning some number of
times, we don't get it, we goto sleep to be timed out (woken up) later to try
again.  These are "sleeps"
Rating: 5
how to set spin_count? August 23, 2003
Reviewer: reader

http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76992/ch18_cpu.htm#4404
from the above link, "In some cases, the spin count may be set too high." How to
find out what is set? Thanks.
Followup:
i would ignore that spurious comment.  no need to change it.
Rating: 4
CPU data August 24, 2003
Reviewer: Reader
Tom, in the statspack report, is "CPU used by this session" value in column "per
second" and "per transaction" for n number of CPUs? If so,  should I divide that
value by n to get a value for one CPU?

How accurate oracle gets the data with regard to CPU consumption? Thanks.
Followup:
it is the amount of cpu used by those sessions -- aggregate over the number of
cpus.

you cannot really "divide", not sure what meaning you would attempt to even
derive from that?  one of the cpus might have been 100% and the other 10% or
both at 55%.  You can only multiply up, not really divide down.

we get this info from the OS itself.
Rating: 5
What does it mean cpu used when call started? Thanks August 24, 2003
Reviewer: Ramu from Atlanta, Ga
Followup:
the amount of cpu time you already used when before the current call (if any)
started.
Rating: 5
current call means the start of the snapshot? August 24, 2003
Reviewer: Reader
Followup:
start of call
Rating: 5
call? August 24, 2003
Reviewer: Venkat from Harlingen, TX
If both CPU used by the session and CPU used when call started have the same
value, how do I interpret them? Thanks.
Followup:
consider them the same for all intents and purposes.
Rating: 5
What does call mean? Is it the start of the execution of SQL? Thanks August 24, 2003
Reviewer: Reader
Followup:
any client call to the database.

parse
execute
run this plsql block
fetch me a row
close that cursor
commit
.....
Rating: 5
August 27, 2003
Reviewer: A reader
Tom,

Most of web sites say to ignore "slave wait" events when tuning the system, in
my database its on the top of list..

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
slave wait                                          3,657      376,505   99.99
control file parallel write                           305           17     .00
io done                                                 2            2     .00
log file parallel write                                 2            2     .00
control file sequential read                           37            1     .00
          -------------------------------------------------------------

Can you please elaborate what is "slave wait" event and why is it caused.

Thanks...
Followup:
it is an io slave waiting to be told to do something, yours are not doing
anything so they wait and wait and wait.

you basically appear to have an idle system there.
Rating: 4
block splits August 28, 2003
Reviewer: Reader
I heard that If I do not set a proper pctfree for index, oracle might have to do
block splits? How do I know whether Oracle did block splits? Is there any
statistic that I can look at? Thanks.
Followup:
even if you set it, Oracle might have to do block splits.  they are normal,
natural and happen all of the time.

for every leaf and branch block you see in an index -- we split at some time.

the index starts as a single block.
it fills
splits -- becomes more then one block.
they fill, split -- becomes yet more blocks.

so, a simple count of blocks used in the index will tell you how many splits.
Rating: 4
Load Issue!! October 20, 2003
Reviewer: Sriram from Florida
Tom,
    We are running Oracle Applications 11i on Windows 2000 Servers. We are
having some serious performance issues during peak load. Is there a way i can
identify the maximum user limit that the server can take? What kind of solution
would you recommend in this case?RAC?

Will statspack reveal anything that could tell me that it is a load related
performance issue.

Thanks,
Sriram
Followup:
well, given the level of detail here -- not too much can be said.

you might be interested in Cary Millsaps new book on amazon.

a 10 minute stats pack might be somewhat informative.

but given the info here (i can run windows on a laptop, i can run windows on
bigger machines. I can have apps 11i with 5 users, I can have it with 50,000
users.  Lots of room in these analog measures -- not really sure where you are)

I cannot possibly recommend anything short of "find out whats wrong", "discover
if you were even in the realm of realistic in sizing".  If you have services on
site -- they are fairly adept at sizing exercises for something as well known as
apps -- they would be the most appropriate path to take.
Rating: 4
How to find the CPU Utilisation October 21, 2003
Reviewer: Praveen from India
Hi Tom,

      I have one question.. Suppose I have a server with two databases on it. I
want to find out the CPU utilised by each database, so that I can set certain
parameters in the init parameter file to evenly allocate the CPU across two
databases. In this way I can utilise the CPU appropriately. Or is there any
other method to find the CPU utilisation by each database.

Regards,
Praveen.
Followup:
there are no init.ora parameters that would accomplish this.  none.

the only way to effectively control resource utilization is to -- well -- have a
SINGLE INSTANCE per server.  You'll never be sorry for going that way.
Rating: 3
Praveen - Have a look at the Oracle doco for resource manager October 21, 2003
Reviewer: Matt from Australia
I think Tom is correct. A single instance is the best way to achieve what you
want - that and resource manager will allow you to allocate server resources
amongst applications/application users.

Mat.
Rating: 5
How well my database is doing? November 25, 2003
Reviewer: Subbu from MN
IS there some kind of report that has say some fancy charts to show for
management that oracle provides - may be part of OEM? How can i tell others that
database is performing well based on such charts/graphs?
My experiance with such things is it varies from day after day depending on what
else was happening at the same time. Is there any standard things that can be
pulled from database everyday to tell /convince someone that system is ok or not
ok and the reason is so and so.
I know how it sounds asking such questions, but is there a way that you can help
me with this.
Key thing is staspack report with some numbers will not be useful here as this
is not for other DBAs to understand but for other folks who know nothing about
database.
I'm looking for some thing ready to present that comes with OEM or some other
simple way of getting some information.
Thanks for your help.
Subbu
Followup:
you cannot.

it is a falicy to think you can.

one persons ideal system is anothers nightmare system.

since it is for the pointy haired manager, any graph will do, just steal some
from quotes.yahoo.com or something, they'll be as meaningful as anything else.

A real manager would want to know

o whats the end user experience
o how many transactions have I historically done, how long did they take and how

  are we doing now

for example -- note that NEITHER of those can be captured in the database!!!
they can ONLY be captured by the application itself.

On asktom, I'm very interested in:

1) how many pages did I serve up in the last minute, hour, day, week, month
2) take 1 and compare the day to yesterday (are we up or down), the week to last
week, the month to last month
3) whats the average, minimum and maximum page render time BY page type and
aggregated
4) how many users have i had in the last hour, day, week, month
5) take numbers 3 and 4 and compare to historical values

etc -- as you can see, none of them are database metrics, they are APPLICATION
metrics through and through. This is what I measure, this is what I report on.
It is the only meaningful thing.

Tell me -- you show a graph that shows the cache hit ratio in the database was
99.99%.  Does that translate into anything "useful" when reporting on how well
your application (and remember, it is the application that is paramount,
relevant here) performed?

it could be an indication of really bad performance
it could be an indication of really good performance
it could be totally irrelevant to anything (most probable)

If you have a 99.99% cache hit but the application takes 30 seconds to tab from
field to field -- who cares?  The end users will be telling you "it is very
slow"

So, your database "doing well", nope, not going to go there (pet peeve of mine).
 Your application (and I'll bet you cannot get this information -- developers,
for whatever reason loathe to instruement their code) must supply you the pretty
charts.

Thats what I really like about html db (the tool we used to build this site).
It does this all for me - the entire application is 100% instruemented so we can
report on its use, performance and fine tune it when needed!
Rating: 5
November 25, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom,
I always used to get the hash value from spreport(start id, end id) and then run
sprepsql(start id, end id, hash value) to get detailed information about a sql
including execution plan. Today I ran sprepsql and the report had a big list of
All Optimizer Plan(s) for this Hash Value. Then there were three different plans
with different plan hash value. Which plan actually represents the plan for the
query? How can there be multiple plans for the same query? What is plan hash
value and how do I get the plan for a specific PHV?

Thanks.
Followup:
they all do.

select * from emp;

run by 50 people -- each of which own a table EMP -- 50 plans.

select * from emp;

run by 50 people using the CBO each with different sort_area_sizes -- 50 plans.

there is a view v$sql_shared_cursor that can help you pinpoint the differences
(why there are child cursors)
Rating: 5
well said but..... November 25, 2003
Reviewer: Subbu from MN
Tom,
Thanks for your response. Well said.
Our monitoring here is matured enough in capturing what is going on with
application and analyzing the historical data and trend. The application even
capture oracle time and stores in database. Application team produces the chart
to show how that particular page was performing.
Now there are few days where there are variation in the overall response time.
It can be attributed to database or network or other app server or cpu or memory
of one of the other component etc.
What is required is some trend information for the database to say that if i see
this wait time goes from x to y then there is some problem.
Thinking about this over and over i thought of providing information about
various wait time and plot them.
Again i've a problem here if the usage is less then the wait time is going to be
less.
Which then leads to taking average wait time which may or may not tell exact
information about the health status.
Thinking about it again and again i wonder what will be best to share with
others to make them understand that database is doing fine.
Any thought?
Thanks
Subbu
Followup:
excellent -- rare to have that happen!

you need to trace the application -- not the database.  database = big thing.
application = thing that has measurable performance variation.

averages are virtually useless (you might want to get Cary Millsaps new book
http://www.amazon.com/exec/obidos/tg/detail/-/059600527X/
) you want the application to spit out a trace (10046 trace) when this happens.
statspack can only get you so far.  its really nice but not the right tool for
this in my opinion.  I'd want an application trace here.
Rating: 5
latch related question November 25, 2003
Reviewer: Sudhir from Lexington, KY
Can you please comment on why so many latches are used?
And will it be a performance problem .. Thanks

SQL> select * from dba_sequences where sequence_name like 'LT%';

SEQUENCE_OWNER                 SEQUENCE_NAME                   MIN_VALUE
MAX_VALUE INCREMENT_BY C O
------------------------------ ------------------------------ ----------
---------- ------------ - -
CACHE_SIZE LAST_NUMBER
---------- -----------
LT                       LT_SEQ_NBR                        1 1000000000
  1 N N
    100000    53445258

SQL> select * from stats where name like 'LATCH.lib%';

NAME                                                    VALUE
-------------------------------------------------- ----------
LATCH.library cache                                 965461615
LATCH.library cache pin                             612216914
LATCH.library cache pin allocation                  172640540
LATCH.library cache load lock                            4203

SQL> declare
n number := 0;
  2    3  begin
 for i in 1..100000
 loop
  select LT.LT_seq_nbr.nextval into n from dual;
 end loop;
end;
/
  4    5    6    7    8    9

PL/SQL procedure successfully completed.

SQL> SQL> select * from stats where name like 'LATCH.lib%';

NAME                                                    VALUE
-------------------------------------------------- ----------
LATCH.library cache                                 965965393
LATCH.library cache pin                             612618872
LATCH.library cache pin allocation                  172642010
LATCH.library cache load lock                            4203

SQL> select 965965393-965461615, 612618872-612216914 from dual;

965965393-965461615 612618872-612216914
------------------- -------------------
             503778              401958

SQL>
Followup:
latches are lightweight serialization devices

getting a sequence is going to be a tiny "one at a time" type of operation.

Also, with the default cache of 20, you had tons and tons of recursive sql to
update seq$ and commit that tiny transaction.

ops$tkyte@ORA920> create sequence little_cache;

Sequence created.

ops$tkyte@ORA920> create sequence big_cache cache 1000000;

Sequence created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec runStats_pkg.rs_start

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> declare
  2      n number;
  3  begin
  4      for i in 1 .. 100000
  5      loop
  6          select little_cache.nextval into n from dual;
  7      end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runStats_pkg.rs_middle

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> declare
  2      n number;
  3  begin
  4      for i in 1 .. 100000
  5      loop
  6          select big_cache.nextval into n from dual;
  7      end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runStats_pkg.rs_stop(20000);
Run1 ran in 2855 hsecs
Run2 ran in 2132 hsecs
run 1 ran in 133.91% of the time

Name                                  Run1        Run2        Diff
STAT...session logical reads       320,725     300,569     -20,156
LATCH.shared pool                  225,252     200,295     -24,957
LATCH.row cache objects             30,038          36     -30,002
LATCH.enqueue hash chains           30,052          44     -30,008
LATCH.library cache pin            440,205     400,204     -40,001
LATCH.library cache                560,353     500,374     -59,979
LATCH.cache buffers chains         670,983     604,673     -66,310
STAT...recursive calls             170,125     100,131     -69,994
STAT...session pga memory         -131,072      65,536     196,608
STAT...redo size                 4,388,524      70,492  -4,318,032

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
2,354,253   2,007,468    -346,785    117.27%

PL/SQL procedure successfully completed.

some amount of latching is going to be 100% unavoidable.  Your goal is to use
techniques that minimize latching
Rating: 5
Tom, thank you. One comment though November 25, 2003
Reviewer: Sudhir from Lexington, KY USA
Even in your example, min 500k library cache latches were obtained for 100k seq
nextvals. More like 5 latches for 1 nextval? Should the 5 then include all child
latches etc.

I like the stats view you have so much, I am almost using it as much I can to
just get some idea on various things that are just hard to understand.

Thanks
Followup:
you were running SQL, forget about the sequence for a moment.  You did stuff in
the database and virtually everything is going to take some latching.

ops$tkyte@ORA920> exec runStats_pkg.rs_start

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> declare
  2      n number;
  3  begin
  4      for i in 1 .. 100000
  5      loop
  6          select i into n from dual;
  7      end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runStats_pkg.rs_middle

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> declare
  2      n number;
  3  begin
  4      for i in 1 .. 100000
  5      loop
  6          select big_cache.nextval into n from dual;
  7      end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runStats_pkg.rs_stop(20000);
Run1 ran in 2063 hsecs
Run2 ran in 2415 hsecs
run 1 ran in 85.42% of the time

Name                                  Run1        Run2        Diff
STAT...session pga memory max       65,536     131,072      65,536
LATCH.shared pool                  100,170     200,263     100,093
STAT...session pga memory         -131,072           0     131,072
LATCH.library cache pin            200,106     400,223     200,117
LATCH.sequence cache                     0     300,002     300,002
LATCH.library cache                200,228     500,418     300,190

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
1,106,842   2,008,294     901,452     55.11%

PL/SQL procedure successfully completed.
Rating: 5
Related to Statspack November 26, 2003
Reviewer: Sudhir
Is there some way to save statpack data to remote database rather than the
current one where you are collecting it? That really will open up a door for us.
We have many instances and each takes up Gigs of space for statspack. Partly due
to 6mo to 1yr retention policy of statspack data. And then we have extentions
running as well.

Thank you
Followup:
statspack is just a bunch of tables and a script to query them.

Sure, you could export/import it into various schemas on another instance -- but
-- it'll take the same amount of space at the end of the day?

I would (for ease of use reasons) just leave it where it is.  Adding a couple of
gigs to a database isn't going to "hurt" it.
Rating: 5
November 26, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
This is a production system and we have only one copy of each table. We use
pga_aggregate target and  WORKAREA_SIZE_POLICY=AUTO. The only other thing I can
think of is can FGAC cause this behaviour?

Other thing I observed is the statspack queries the Oracle version when spreport
is run. I can have snapshots taken with version 9.2.0.3 showing up as 9.2.0.4
because I upgraded. Is this correct?

Thanks...

<quote>
they all do.

select * from emp;

run by 50 people -- each of which own a table EMP -- 50 plans.

select * from emp;

run by 50 people using the CBO each with different sort_area_sizes -- 50 plans.

there is a view v$sql_shared_cursor that can help you pinpoint the differences
(why there are child cursors)
</quote>
Followup:
look in the view i told you about, it'll tell you why.

FGAC can definitely do this -- IF your predicate policy returns N different
predicates, you have N different queries that all "look" the same in v$sql but
are different under the covers (the hidden predicates)

statspack is just reporting the version of the database it is connected to, so
yes, 9204 would be "correct"
Rating: 5
November 26, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
Sorry, didn't mean to ignore your advise. I did check the v$sql_shared_cursor
view and was trying to interpret the results among other things. I ran the query
as:
SELECT
  *
FROM
  v$sql_shared_cursor
WHERE
  kglhdpar in ( select address from v$sql where hash_value = 3949466389)

The AUTH_CHECK_MISMATCH=Y. Can this be due to FGAC? This database uses FGAC
extensively and the predicates do vary. The different plans shown by sprepsql
also vary quite a bit.

One more question. If sprepsql reports 6 distinct plans, then why all plans are
not shown in the report? The reports only had two or three plans.

Thanks
Followup:
means different users accessing different objects (the select * from emp example
where there is more than one emp table)

or the predicate policy references other tables so the comprehensive set of
tables in the query is different from query to query.

statspack only shows that which was executed during the time measured.  if the
other 3 or 4 plans were not used....
Rating: 5
Is statspack right tool for DW also? December 18, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom,
Can I use statspack as the starting point to identify resource intensive queries
in a DW database? For OLTP databases, I take snapshots not more than 15 minutes
apart. Is this ok for DW also? Please give me some pointers to keep in mind when
tuning DW queries.

Thanks.
Followup:
you can, but you'll probably just want to look at v$sql directly as you'll have
much less shared sql in a DW (more one off queries) and will need to look for
patterns.  a tool like OEM might help you here as well.
Rating: 4
Negative soft parse ratio! December 18, 2003
Reviewer: A reader
Hi Tom,
Snippet from statspack report shows negative soft parse ratio as below:
Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        Buffer Nowait Ratio:       99.99
        Buffer  Hit   Ratio:       92.57
        Library Hit   Ratio:       91.21
        Redo   NoWait Ratio:      100.00
       In-memory Sort Ratio:      100.00
           Soft Parse Ratio:   -1,356.50
            Latch Hit Ratio:       98.16

What could be the reason for this?
Thanks
Followup:
the formula is

round(100*(1-:hprs/:prse),2)                   pctval

so, look UP in your report to:

      Parses:                  0.24                 53.50
 Hard parses:                  0.00                  0.00

and based on the number of seconds you ran for -- determine how many hard and
how many parses you did and we'll work from there.
Rating: 4
More info... December 18, 2003
Reviewer: A reader
Thanks for quick response.
Yes, there are more hard parses, as below:

Start Id    End Id       Start Time             End Time         (Minutes)
--------  --------  --------------------  --------------------  -----------
      82        85  19-Dec-03 08:57:21    19-Dec-03 09:42:44          45.38

Cache Sizes
~~~~~~~~~~~
           db_block_buffers:       32768
              db_block_size:       16384
                 log_buffer:    10485760
           shared_pool_size:        100M

Load Profile
~~~~~~~~~~~~

Cache Sizes
~~~~~~~~~~~
           db_block_buffers:       32768
              db_block_size:       16384
                 log_buffer:    10485760
           shared_pool_size:        100M

Load Profile
~~~~~~~~~~~~
                                       Per Second      Per Transaction
                                  ---------------      ---------------
                  Redo size:             2,377.99             4,072.49
              Logical reads:               415.02               710.75
              Block changes:                 8.47                14.50
             Physical reads:                30.83                52.80
            Physical writes:                 0.12                 0.21
                 User calls:                 5.82                 9.96
                     Parses:                 4.32                 7.39
                Hard parses:                62.89               107.71
                      Sorts:                 1.70                 2.90
               Transactions:                 0.58

              Rows per Sort:       47.03
  Pct Blocks changed / Read:        2.04
         Recursive Call Pct:       94.05
 Rollback / transaction Pct:        0.00

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        Buffer Nowait Ratio:       99.99
        Buffer  Hit   Ratio:       92.57
        Library Hit   Ratio:       91.21
        Redo   NoWait Ratio:      100.00
       In-memory Sort Ratio:      100.00
           Soft Parse Ratio:   -1,356.50
            Latch Hit Ratio:       98.16
It is DW system, and I ran report at the peak morning time.
What should I do to improve this, being a DW system?
Followup:
one of the counters must have rolled over -- ignore this report, do it at
another peak.

In a DW, hard parsing is *normal* and expected.  You are running queries that
take many seconds (minutes/hours) instead of many queries per second.  Hard
parsing is not only OK in a DW, but many times mandatory (to take full advantage
of histograms, star transformations and the like)
Rating: 4
Still negative values December 22, 2003
Reviewer: A reader
Tom,
I ran at various times, and every time I got negative soft parse ratio:
STATSPACK report for

DB Name        DB Id     Instance    Inst Num  Release     OPS   Host
----------  -----------  ----------  --------  ----------  ----  ----------
PEDW         1372283050  pedw               1  8.1.6.3.0   NO    laedw001

                                                                Snap Length
Start Id    End Id       Start Time             End Time         (Minutes)
--------  --------  --------------------  --------------------  -----------
     468       470  23-Dec-03 09:00:13    23-Dec-03 09:30:21          30.13

Cache Sizes
~~~~~~~~~~~
           db_block_buffers:       32768
              db_block_size:       16384
                 log_buffer:    10485760
           shared_pool_size:        100M

Load Profile
~~~~~~~~~~~~
                                       Per Second      Per Transaction
                                  ---------------      ---------------
                  Redo size:             2,570.95             4,960.81
              Logical reads:               614.46             1,185.64
              Block changes:                 9.15                17.66
             Physical reads:                46.62                89.96
            Physical writes:                 0.14                 0.27
                 User calls:                 5.18                 9.99
                     Parses:                 5.24                10.11
                Hard parses:               204.17               393.95
                      Sorts:                 2.62                 5.06
               Transactions:                 0.52

              Rows per Sort:       45.94
  Pct Blocks changed / Read:        1.49
         Recursive Call Pct:       96.06
 Rollback / transaction Pct:        0.00

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        Buffer Nowait Ratio:       99.99
        Buffer  Hit   Ratio:       92.41
        Library Hit   Ratio:       91.67
        Redo   NoWait Ratio:      100.00
       In-memory Sort Ratio:      100.00
           Soft Parse Ratio:   -3,795.84
            Latch Hit Ratio:       97.21

__________________
Further, I get lots of waits for Library cache pin, how to reduce this?
Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
library cache pin                                 639,106      229,914   78.42
latch free                                        901,144       50,436   17.20
library cache load lock                                65        7,402    2.52
db file scattered read                              1,503        4,614    1.57
log file sync                                         913          452     .15
         -------------------------------------------------------------
Followup:
look further down and see what numbers are being fed into this -- also, is there
a chance that some of the v$ numbers have "wrapped around" (they are in general
32 bit integers so sometimes on a DB that has been up for a long time -- they
"roll around, go negative, approach zero and go for another loop".  that can
easily cause things like this)
Rating: 2
trace vs. perception December 23, 2003
Reviewer: j.
hi tom,

we 've traced a procedure that used to run some hundred seconds but now
consistently takes about 12000 seconds and got the following summary for such a
long run:

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          0          0           0

Misses in library cache during parse: 0

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       24      0.09       0.06          0          0          0           0
Execute 102853      8.62       7.97          0      14678        915        2348
Fetch   102848     12.27      11.57          0     963803          0       72465
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   205725     20.98      19.61          0     978481        915       74813

Misses in library cache during parse: 4

   19  user  SQL statements in session.
    7  internal SQL statements in session.
   26  SQL statements in session.
********************************************************************************
Trace file: deut_ora_338494.trc
Trace file compatibility: 9.00.01
Sort options: default

       1  session in tracefile.
      19  user  SQL statements in trace file.
       7  internal SQL statements in trace file.
      26  SQL statements in trace file.
      21  unique SQL statements in trace file.
  206127  lines in trace file.

from this we (DBAs are involved) can't figure out the reason why it is taking
that much time. we double-checked the structures and the data processed, but
didn't find a clue ...

so we now don't have an idea how to go on.

what would you suggest?
Followup:
according to that, it took 20 seconds in the database.

means -- database not the issue.  the network OR the application is the issue.
Rating: 3
that 's why it is some kind of mystery to us December 25, 2003
Reviewer: j.
we have a 100% server side application. we only submit an anonymous block
containing the call of one package procedure. the executed code processes
"local" data only (no database links involved).

one thing is making us (very) confused: the trace file grows to nearly its final
size in the first few minutes although the application is still running for
hours after that (we examined active statements) ...
Followup:
what is your max dump file size and is the last line in the trace file "hey,
this file was truncated"
Rating: 5
what's going on with my database December 25, 2003
Reviewer: A reader
Hi

I am looking a statspack report from last month in from my database and I am
puzzled

                Snap Id     Snap Time      Sessions
                ------- ------------------ --------
 Begin Snap:       2612 13-Nov-03 16:48:38      340
   End Snap:       2613 13-Nov-03 17:22:27      340
    Elapsed:                  33.82 (mins)

Top 5 Wait Events
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits  Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
enqueue                                               657      200,756   70.10
db file sequential read                           109,537       33,992   11.87
latch free                                         40,310       20,274    7.08
db file scattered read                             79,659       15,955    5.57
log file sync                                       6,827        5,080    1.77

Statistic                                    Total   per Second    per Trans
--------------------------------- ---------------- ------------ ------------
CPU used by this session                   234,822        115.7         20.4
CPU used when call started                 234,905        115.8         20.5

How come my waits are using more CPU than my system...?! 200 seconds of waits in
33 minutes snap, my enqueues must be very bad, there must be many users waiting
huh

This application uses BLOBs, may be that is the problem?
Followup:
that is not 200 seconds, that is 2007 seconds of wait time for enqueues.

that could have been one user waiting pretty much the entire time (2029
seconds)...

or it could have been 2000 users each waiting a second....

or any number/combo in between.

Now, enqueues are LOCKS (select * from emp for update in session one.  Now, do
the same in session 2, session 2 blocks.  it'll accumulate enqueue waits until
session 1 commits or rolls back)

this is nothing with blobs.  you have a concurrency issue (maybe -- if just one
guy was blocked the entire time, you might just have had a strange occurence.  I
lean towards that -- that one user was blocked for the entire time, but only
cause the numbers are so close, the total time and the total wait time)

Look for blockers and blockees.
Rating: 5
I meant 2000 seconds December 25, 2003
Reviewer: A reader
waits in 33 minutes snap

sorry!
December 26, 2003 Charanjiv -- Thanks for the question regarding "Char Vs Varchar", version 8.1.5
You Asked
Hi,

I am a little confused here, as far as I understood, the
VARCHAR2 datatype was supposed use memory equaling to the data
in it. But the followig lines from "PL/SQL User’s Guide and
Reference" Version 8.1.5 for NT tells a different story.

" However, for a VARCHAR2(< 2000) variable, PL/SQL preallocates
enough memory to hold a maximum-size value. So, for example, if
you assign the same 500-byte value to a VARCHAR2(2000) variable
and to a VARCHAR2(1999) variable, the latter uses 1499 bytes
more memory. "

Does this mean there is no difference between a CHAR(1999) and
VARHAR2(1999)

Charanjiv
and we said...
A varchar2 datatype, when stored in a database table, uses only
the space allocated to it.  If you have a varchar2(1999) and put
50 bytes in the table, we will use 52 bytes (leading length
byte).

A char datatype, when stored in a database table, always uses
the maximum length and is blank padded.  If you have char(1999)
and put 50 bytes into it, it will consume 2001 bytes (leading
length field is present on char's as well).

In the database -- a CHAR is a VARCHAR that is blank padded to
its maximum length.

In PLSQL, what they are saying is that for optimization purposes
-- a varchar2 less then 2000 bytes will allocate a fixed amount
of memory.  It will preallocate the maximum size upon
instantiation.  This cuts down on the reallocations as different
sized strings are assigned to it.  On the other hand, a varchar2
greater then 2000 characters will truely be varying sized and as
you assign different sized strings to it it will grow
dynamically as needed.

One takes a little more ram (if you never use the entire string)
and performs a little better, the other uses only what ram it
needs but if you cause the string to grow frequently will not
perform as well due to the reallocs to resize the buffer.

There are great differences between a char(1999) and
varchar2(1999) as the CHAR is always 1999 characters long
physically AND logically.  the varchar is physically 1999 bytes
but logically can be any size in between 0 and 1999.  The char
is always blank padded, not so for the varchar.

You can measure this for yourself to see this effect:

tkyte@TKYTE816> declare
  2      x varchar2(1999);
  3      l_start number default dbms_utility.get_time;
  4  begin
  5      for i in 1 .. 100
  6      loop
  7          for j in 1..1999
  8          loop
  9              x := rpad('x',j,'x');
 10          end loop;
 11      end loop;
 12      dbms_output.put_line
 13      ( 'Elapsed: ' ||
             round( (dbms_utility.get_time-l_start)/100, 2 ) );
 14  end;
 15  /
Elapsed: .99

PL/SQL procedure successfully completed.

tkyte@TKYTE816> declare
  2      x varchar2(2001);
  3      l_start number default dbms_utility.get_time;
  4  begin
  5      for i in 1 .. 100
  6      loop
  7          for j in 1..1999
  8          loop
  9              x := rpad('x',j,'x');
 10          end loop;
 11      end loop;
 12      dbms_output.put_line
 13      ( 'Elapsed: ' ||
              round( (dbms_utility.get_time-l_start)/100, 2 ) );
 14  end;
 15  /
Elapsed: 1.21

PL/SQL procedure successfully completed.

The use of the varchar2(1999) was slightly more efficient in
this worst case, fake case to demonstrate a point.  In general,
you will never notice this different as you probably don't make
assignments of every single size to a string 199,900 times in
real life.
Review & Followup

Rating: 4
Char VS Varchar April 03, 2002
Reviewer: kiran from cincinnati, OH USA
In all your replys to Char and varchar2 questions you mentioned that Varchar2
supports all the char properties.
I noticed a weired problem with Varchar2.
I have a table test1(tname char(20),my_name varchar2(20));
I inserted two records into this table
('kiran','kiran')
('kiran ','kiran ')---Notice there is a space after kiran.
Now I am selecting from the table using the below sql
select * from test1 where tname = 'kiran';
 ---this returned the above two records
  but when I am select the same table using the below sql
select * from test1 where my_name = 'kiran'
  ---it returned only one record(1st one). I tested this on 7.3 and also on 8I
could you explain why it is happening?
another interesting thing is select * from test1 where tname like 'kiran' is not
returning any recors.When the "=" is working fine, why the like is not working?
Followup:
varchar2's can store trailing blanks -- IF You put them there.

the answer is simple.

'a ' <> 'a'

tried this in 8i (r2 and r3)

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x char(10), y varchar2(10)
);

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 'x', 'x ' );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where x like 'x';

X          Y
---------- ----------
x          x

ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where y like 'x';

no rows selected

works as expected for me.
Rating: 5
Is there a Leading Length field in RAW Datatype ? April 04, 2002
Reviewer: Pascal from Hamburg , Germany
Hi Tom,

Thanks for a Great answer!

If i could just post a followup  on that expecting a short YES/NO  answer-

Is there a Leading  Length field  in  RAW  Datatype ?

and

Is it True that when i use RAW  type in my tables as an automatically generated
Primary Key - by selecting sys_guid() from dual ,  from within a Trigger  - then
i would not be able to  Create  Materialized  Views with columns of RAW type ?

Thanks in advance
Followup:
you can either

select length( raw_field )/2 from t

or

select utl_raw.length(raw_field) from t

to get the length.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( pk raw(16) primary key, data
int );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create materialized view t_mv
  2  as
  3  select * from t;

Materialized view created.

seems to work.
Rating: 4
Are PL/SQL LONG variables similar? April 04, 2002
Reviewer: Brent from Hartford, CT, USA
I was searching for documentation on the memory behavior with respect to LONG
variables, and found the same snippet at the top of this question for VARCHARs,
but nothing about LONGs.

Do the memory allocation rules operate the same way for LONG variables, with the
2000-byte threshold?  Or if it is different for LONGs, what are the differences?

Thanks.

(I know Tom knows what I mean but just to clarify for the readers -- I am
referring to the PL/SQL LONG datatype which has a 32K maximum size, and not the
database LONG column type which has a 2GB limit).
Followup:
From standard.sql:

...
  type VARCHAR2 is NEW CHAR_BASE;
  pragma PACK(VARCHAR2);

  subtype VARCHAR is VARCHAR2;
  subtype STRING is VARCHAR2;

  subtype LONG is VARCHAR2(32760);

  subtype RAW is VARCHAR2;
  subtype "LONG RAW" is RAW(32760);

  subtype ROWID is VARCHAR2(256);
....

a plsql long is just a varchar2 in disguise.  So, yes, they are just a varchar2
that is bigger then 2000.
Rating: 4
Can preallocate varchar2(>2000)? April 04, 2002
Reviewer: benedict from australia
Thanks for the informative answer Tom.

Supposing I was building up a large varchar2 variable (declared >2000)
characters by appending text to itself... e.g.
declare
   big varchar(30000);
begin
  for i := 1..100 loop
    big := big || ' something';
  end loop;
end;

I understand from your answer that since it's been declared larger than 2000,
pl/sql will reallocate memory each time it gets longer. My question is: is it
possible to force pl/sql to allocate tons of room to begin with? And does it
make a big enough difference for me to care?

Will pl/sql reallocate if the varchar2 gets smaller?
e.g. will

...
begin
  big := rpad(null, 30000);
  big := null;
...

do the trick?

Cheers
Followup:
Based on this test, I would say 'yes, it'll have an effect'.

But -- only if you do this really degenerate case like I did.  I mean -- how
many times do you make that string grow a byte at a time??

Also -- setting to NULL sees to be "free" -- it shrinks it right back.

I ran this a couple of times:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure p_big_no_init
  2  as
  3      x varchar2(30000);
  4  begin
  5      for j in 1..30000
  6      loop
  7        x := rpad('x',j,'x');
  8      end loop;
  9  end ;
 10  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure p_big_init
  2  as
  3      x varchar2(30000) := rpad( 'x', 30000, 'x' );
  4  begin
  5      for j in 1..30000
  6      loop
  7        x := rpad('x',j,'x');
  8      end loop;
  9  end ;
 10  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure
p_big_init_with_null
  2  as
  3      x varchar2(30000) := rpad( 'x', 30000, 'x' );
  4  begin
  5          x := null;
  6      for j in 1..30000
  7      loop
  8        x := rpad('x',j,'x');
  9      end loop;
 10  end ;
 11  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_start number default dbms_utility.get_time;
  3  begin
  4      for i in 1 .. 5 loop p_big_no_init; end loop;
  5      dbms_output.put_line
  6      ( 'Elapsed: ' ||
  7          round( (dbms_utility.get_time-l_start)/100, 2 ) );
  8
  9      l_start := dbms_utility.get_time;
 10      for i in 1 .. 5 loop p_big_init; end loop;
 11      dbms_output.put_line
 12      ( 'Elapsed: ' ||
 13          round( (dbms_utility.get_time-l_start)/100, 2 ) );
 14
 15      l_start := dbms_utility.get_time;
 16      for i in 1 .. 5 loop p_big_init_with_null; end loop;
 17      dbms_output.put_line
 18      ( 'Elapsed: ' ||
 19          round( (dbms_utility.get_time-l_start)/100, 2 ) );
 20  end;
 21  /
Elapsed: 22.85
Elapsed: 22.06
Elapsed: 24.69

PL/SQL procedure successfully completed.
Rating: 4
What about when constants are declared in PL/SQL? April 10, 2002
Reviewer: jon from Hartford, CT USA
If you declare a constant in PL/SQL, would it be better to size it as
VARCHAR2(2001), so that you would get the actual size, instead of 2000 bytes
allocated?  Or is it smart enough to recognize that constants can't be changed
and allocate the correct amount of memory.

In other words, is
usps_code_connecticut constant varchar2(2001) := 'CT';
any different than
usps_code_connecticut constant varchar2(2) := 'CT';
?
Followup:
regardless of whether it was constant or not -- you should use varchar2(2).

The varchar2(2001) will allocate storage for 2 characters when it is
initialized.

The varchar2(2) will allocate storage for 2 characters when it is initialized.

Now, if you had coded:

usps_code_connecticut containt varchar2(1000) := 'CT';

then 2001 and might be better.  But the real fix would be to declare it properly
as a varchar2(2).
Rating: 4
Constants. January 21, 2003
Reviewer: Kashif from Houston, TX
Hi Tom,

Is using the CONSTANT keyword a performance benefit? Any examples would be
helpful. Also, can you explain whether a declaration such as:

l_filename constant varchar2 (7) default to_char (l_start_date, 'MONYYYY');

is advisable/logical. The constant and default keywords are used in the same
declaration. Thanks in advance.

Kashif
Followup:
it is a compiler directive saying "don't let them modify it".

I've never seen any sort of performance benefit.  It is still a plsql variable.
Rating: 5
Assiging CHAR type to VARCHAR2 March 04, 2003
Reviewer: CSR from India
Hi Tom,

Is there any problem if we assign CHAR type variable directly to a VARCHAR2 type
variable.
I have table and SP like below.

chandu@chandra> desc t_clips_audit_info
 Name                         Null?    Type
 --------------------------- -------- ---------------
 AUDIT_SEQUENCE              NOT NULL NUMBER(10)
 CALL_TYPE                            CHAR(3)
 CALL_RETURN_ERR                      CHAR(3)
 ERROR_DATE_TIME                      DATE
 REQUESTOR_ID                         VARCHAR2(20)
 CALL_STRING                          VARCHAR2(250)
 CALL_RETURN                          VARCHAR2(2000)

PROCEDURE sp(
                ic_or_number    IN  VARCHAR2,
                in_on_number    IN  NUMBER,
                in_amt          IN  NUMBER,
                oc_is_valid     OUT CHAR,
                oc_is_or_valid  OUT CHAR)
As

 vc_call_string t_clips_audit_info.call_string%TYPE := NULL;
 vc_call_return t_clips_audit_info.call_return%TYPE := NULL;

 begin

--------- many lines. here -------

-- #.1
      vc_call_return :=    vc_call_return || ' ' ||'oc_is_obgr_obgt_valid: '||
                                              oc_is_obgr_obgt_valid;

--
 end ;
/

When I execute the above SP from SQL*> with a sql client(exec sp) it is giving
me proper results.
But java developers are getting the error showing below.

error is :ORA-06502: PL/SQL: numeric or value errorCode:-6502

After debugging I found the exception at occurred the #.1.

If I use SUBSTR(oc_is_obgr_obgt_valid,0,1) in the code, I don't get any errors.

Could you please suggest us.
Followup:
The java developers are binding a string that is too small to hold the result.
chars are blank padded to the maximum length and the java guys bind variable
isn't big enough I guess.

one would actually need a small, yet complete testcase to say exactly.

almost forgot to say - i hate char, don't use them, just use varchar2.
Rating: 5
CHAR(1) vs VARCHAR2(1) March 04, 2003
Reviewer: Trevor
Tom...
I was told once that if you only need a single char
use CHAR(1) since using VARCHAR2(1) has overhead
due to the Oracle having to maintain how long
the value is in a VARCHAR2 field

Is this a load of rubbish or is there some truth
to it?
Followup:
that is rubbish.

A char(n) is a varchar2(n) that is blank padded to it's maximum length.  The
implementation of a char physically on disk is identical to a varchar2 -- there
is a length byte for a char(1) just as there is for a varchar2(1).
Rating: 5
Binned March 05, 2003
Reviewer: Trevor
Thanks Tom
Rating: 3
VARCHAR2(10) vs. VARCHAR2(2000) for a 10 char string June 03, 2003
Reviewer: Tajana from Austria, Vienna
Tom,

clear about the difference between VARCHAR2 and CHAR.

But what about defining VARCHAR2(10) and, let's say VARCHAR2(2000) for a column
that is expected not to be "wider" than 10 characters ?

One advantage of defining such a column actually as what it is (VARCHAR2(10))
for me is, that I can easily list a table containing many of such columns in
SQL*Plus (Worksheet) without having to scroll right, till my fingers get
wounded.

Disadvantage: If my text gets wider, I'll have to redefine the column; means: I
have to maintain that.

Defining such a column as - let's say: 2000, would mean: No or less maintenance.

But much more interesting is:
Are there any
    space
    index
    optimizer issues , or
any other important issues with this ?
Any other arguments for defining a 10 char column as VARCHAR2(10) and not
VARCHAR2(2000) ?
Or: Any arguments why it is better to generally define VARCHAR2 columns to its
maximum ?

Thanks in advance,

Tajana
Followup:
well, the real issue is -- what is the business requirement.  What the heck is
in that field and what is its length.

If you say "use a varchar2(2000)" then you know what will happen?  someone will
put 2000 characters in there someday.  Is that OK?

varchar2(10) says "this field accepts 10 bytes of data"

Since you can make a field larger easily with ALTER, maintenance isn't an
excuse.

You define a field to be correct and proper.  Period.  This field is a
varchar2(10).

think about the poor client tools that say "oh look - a varchar2(2000) and an
array size of 100.  We need to allocate 200,000 bytes of storage (instead of
1000).
Rating: 5
VARCHAR2(10) vs. VARCHAR2(2000) for a 10 char string June 04, 2003
Reviewer: Tajana from Vienna, Austria
Tom,

in german you would say: You a running against my open doors.
Means: We are of the same opinion.

My intention was to prevent development colleagues to define hundreds of
VARCHAR2(255) columns (which they did, before I "jumped" in the project, and
still trying to do).
So I needed arguments.
You gave me one - that with client tools, arraysize and allocation.

But before I discuss that with colelagues, I would like to know this a little
bit more in detail:
Do you mean memory allocation by the client tool at the client ?
Can you explain that matter a little bit further, please ?

Any other considerations (optimizer, indexes ?)

Thanks a lot,

Tajana
Followup:
there is only one reason necessary:

o it is the only sensible and professional thing to do.

but the technical reasons are:

o it is called data integrity.  if you make it 255, you are saying that field
legitimately contains upto 255 bytes of data.  If you make it 10, you are saying
otherwise.  If you make it 255, someone WILL -- not might -- WILL put 255 bytes
in there some day.  Then what?

o say you have 20 255 varchar2 columns instead of 20 right sized ones.  Pull
that up in an ad hoc tool.  Have you done the end user a serious dis-service
here?  they'll spend the next 5 minutes formatting data - data that should have
been, could have been formatted already.

o say you move onto the next project -- leaving all of these 255 byte columns.
Who is going to tell the next guys "c1 is a 10 character field, c2 is 12, c3 is
44" and so on.

o when the client application, which wants to run fast, decides "I'll array
fetch 100 rows at a time", it'll have to allocate 100 * 255 * 20 bytes of data
for the buffering of such data (remember, no one KNOWS c1 is just 10 characters,
it is 255 characters).  That is as opposed to allocating just what you really
need.

I've got an idea for these guys -- don't use numbers or dates either.  What if
later on you want to store "hello world" in that date column.  (only kidding)
Rating: 5
June 04, 2003
Reviewer: Dave from Colorado
In the same kidding spirit as "don't use numbers or dates either", you could use
VarChar2(2000)  for all your columns, and then put a check constraint on the
column "length(my_column) < 21". Ta Da! Same as VarChar2(20)!!!
Rating: 5
I like that one.. June 04, 2003
Reviewer: Jose Cleto from Santo Domingo, Dominican Republic
It reads like "American funnies Scripts"
Rating: 5
Funnies scripts June 05, 2003
Reviewer: Tajana from Vienna, Austria
Thanks, Tom.
============
This was what I needed.

Jose,
It reads like:
What funny ideas developers sometimes have, and what a DBA / data designer has
not only to clean after them, but also to convince them of on the first place
...
(Tenor: "Don't change this, my procedure won't work at all anymore !!! And, by
the way, it is yet implemented at the cust site !")

In this case: European funnies scripts, but I am sure, you have your own ones
too ;-)

Dave,
I will evaluate your suggestion and present it to my dev colleagues ;-)
We will see, what they are gonna to say...

Cheers, Tajana
Rating: 5
Sorry if I miss the point... July 30, 2003
Reviewer: Jose Cleto from Dominican Republic
We all have fallen in this situation:

-  "Let's do poor req. and anal. and then go with the code"
-  "I know that is better, but this is faster"
-  "We don't have time for that now"

An the list goes on...

The true, all the true and nothing but the true is that developers, DBA, and the
like all we, are payed to do our job the better we can.

Why pay like a pro if some one will work like a novice.  We most work all the
time to get the next level.  Computers is a "science", like math, chemestry, we
have discipline CMM, OOP, Database Design, Structure programming, UML, etc., all
this exists is for guide us in doing our work, but only our self could make our
work.

Making the king of VARCHAR2(2000) to hold just 20 is silly. So mr, Tajana, Your
job "if You decide to accept it", is to convince your developers to be as near
as possble to the reallity and make use of all the things they are suppose to
know.

BTW.: In my case is "Dominican Funniest scripts"
Rating: 3
Char is returing vaules that are not padded? September 11, 2003
Reviewer: Vivek
Tom, I created a table with a column of char(10).  When I select a row with a
where clause of the "col" not padded with spaces it still returns the row.  I
would think the row should not be found if I do not pad.  Am I wrong?

SQL> create table x(col char(10));

Table created.

SQL> insert into x values('0');

1 row created.

SQL> insert into x values('1     ');

1 row created.

SQL> insert into x values('A');

1 row created.

SQL> select length(col),'x'||col||'x' from x;

LENGTH(COL) 'X'||COL||'X
----------- ------------
         10 x0         x
         10 x1         x
         10 xA         x

SQL>
SQL> DECLARE
  2     x   CHAR (14);
  3  BEGIN
  4     DBMS_OUTPUT.put_line ('0');
  5
  6     SELECT col
  7       INTO x
  8       FROM x
  9      WHERE col = '0';
 10
 11     DBMS_OUTPUT.put_line ('1');
 12
 13     SELECT col
 14       INTO x
 15       FROM x
 16      WHERE col = '1';
 17
 18     DBMS_OUTPUT.put_line ('A');
 19
 20     SELECT col
 21       INTO x
 22       FROM x
 23      WHERE col = 'A';
 24      DBMS_OUTPUT.put_line ('NO Errors');
 25  EXCEPTION
 26     WHEN NO_DATA_FOUND THEN
 27        DBMS_OUTPUT.put_line ('no_data_found');
 28        RAISE;
 29     WHEN OTHERS THEN
 30        DBMS_OUTPUT.put_line ('others');
 31        RAISE;
 32  END;
 33  /
0
1
A
NO Errors

PL/SQL procedure successfully completed.

SQL> spool off

thanks
Followup:
character string literals like that are coerced to the proper datatype
automagically

'A' is really 'A        '

it is the ansi defined behaviour.
Rating: 5
number vs date in datawarehouse November 03, 2003
Reviewer: A reader
Hi

I have seen many Datamarts/Datawarehouses using number for date columns. For
example instead of having

2003-AUG-01 23:00:00

they have

20030801230000 in number

Is this good practice? I find it so hassle because when I have to calculate
aggreates with time functions I always have to convert that to date first!

I have never seen this in OLTP but many times in DWH, is there some directive to
do so or what?
Followup:
In fact, I write about this very topic in my book "Effective Oracle By Design"
where I state very loudly

USE NUMBERS FOR NUMBERS

USE DATES FOR DATES

don't use anything else, use the correct proper datatypes.

Not only do you spend 1/2 your time converting that number to a date -- but I'll
assure you that one day, you'll get a date in there that IS NOT A VALID DATE and
then what?

Not only that but tell me, how many values are there between

20031231115959

and

20040101000000

well, the optimizer thinks there could be 8869884041 values -- but we all know
there are no values between those two dates!!!  You can totally mess up the
optimizer with things like "between" and "<", ">" doing this silly thing of
putting dates into numbers or strings.....

can you tell ?  i'm dead set against this "common practice"
Rating: 5
November 03, 2003
Reviewer: Dave from Colorado
"Date folding" is related good reason, as if another one were needed, for using
date types for dates, especially in a DW environment ...
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96520/qr.htm#50228

Rating: 5
November 03, 2003
Reviewer: Dave from Colorado
By the way, there seems to be some very poor advice in that documentation, in
the "Note:" section.
Rating: 4
example from your new book November 14, 2003
Reviewer: A reader
Hi

you have an example showing using proper datatypes in chapter about effective
schema design

I did a small test using my own script and saw something not predicted, opposite
from your example

create table test_date
(
 varchar_fecha varchar2(9),
 number_fecha number,
 date_fecha date
)
pctfree 70
pctused 10;

insert /*+ APPEND */ into test_date
select to_char( to_date( '19980101', 'YYYYMMDD' ) + rownum, 'YYYYMMDD' ),
       to_number( to_char( to_date( '19980101', 'YYYYMMDD' ) + rownum,
'YYYYMMDD' ) ),
       to_date( '19980101', 'YYYYMMDD' ) + rownum
  from dba_source
 where rownum <= trunc(sysdate) + 36500 - to_date( '19940101', 'YYYYMMDD' );

create index test_date_idx1 on test_date(varchar_fecha);

create index test_date_idx2 on test_date(number_fecha);

create index test_date_idx3 on test_date(date_fecha);

analyze table test_date compute statistics for all indexes for table for all
columns;

set autotrace trace exp stat

select * from test_date
  2   where number_fecha between 20011231 and 20050101;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=1318 Bytes=26360)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'TEST_DATE' (Cost=6 Card=1318
Bytes=26360)
   2    1     INDEX (RANGE SCAN) OF 'TEST_DATE_IDX2' (NON-UNIQUE) (Cost=5
Card=1318)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        166  consistent gets
          0  physical reads
          0  redo size
      50919  bytes sent via SQL*Net to client
       8528  bytes received via SQL*Net from client
         75  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       1098  rows processed

select * from test_date
  2   where date_fecha between to_date('20011231', 'YYYYMMDD') and
to_date('20050101', 'YYYYMMDD');

1098 filas seleccionadas.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=46 Card=1097 Bytes=21940)
   1    0   TABLE ACCESS (FULL) OF 'TEST_DATE' (Cost=46 Card=1097 Bytes=21940)

Statistics
----------------------------------------------------------
          0  recursive calls
         15  db block gets
        545  consistent gets
          7  physical reads
          0  redo size
      50919  bytes sent via SQL*Net to client
       8528  bytes received via SQL*Net from client
         75  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       1098  rows processed

as you can see the proper datatype in this case date is working worse than
number datatype... interesting no?
Followup:
My point was the information the optimizer sees

look Card=1097 when using dates -- and reality says 1098 rows.
     Card=1318 when using numbers -- and reality says 1098 rows.

using the number gave the optimizer bad information here.  That said (i was
demonstrating something wholly different there - that the optimizer is getting
bad bad data) -- I do not observe the same thing as you:

ops$tkyte@ORA920PC> create table test_date
  2  (
  3   varchar_fecha varchar2(9),
  4   number_fecha number,
  5   date_fecha date
  6  )
  7  pctfree 70
  8  pctused 10;

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert /*+ APPEND */ into test_date
  2  select to_char( to_date( '19980101', 'YYYYMMDD' ) + rownum, 'YYYYMMDD' ),
  3         to_number( to_char( to_date( '19980101', 'YYYYMMDD' ) + rownum,
'YYYYMMDD' ) ),
  4         to_date( '19980101', 'YYYYMMDD' ) + rownum
  5    from dba_source
  6   where rownum <= trunc(sysdate) + 36500 - to_date( '19940101', 'YYYYMMDD'
);

40104 rows created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create index test_date_idx1 on test_date(varchar_fecha);

Index created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create index test_date_idx2 on test_date(number_fecha);

Index created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> create index test_date_idx3 on test_date(date_fecha);

Index created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> analyze table test_date compute statistics for all indexes
for table for all
  2  columns;

Table analyzed.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> set autotrace traceonly
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select * from test_date where number_fecha between 20011231
and 20050101;

1098 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=21 Card=1318 Bytes=26360)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'TEST_DATE' (Cost=21 Card=1318
Bytes=26360)
   2    1     INDEX (RANGE SCAN) OF 'TEST_DATE_IDX2' (NON-UNIQUE) (Cost=5
Card=1318)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        163  consistent gets
         14  physical reads
          0  redo size
      36016  bytes sent via SQL*Net to client
       1302  bytes received via SQL*Net from client
         75  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       1098  rows processed

ops$tkyte@ORA920PC> select * from test_date where date_fecha between
to_date('20011231', 'YYYYMMDD') and to_date('20050101', 'YYYYMMDD');

1098 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=17 Card=1096 Bytes=21920)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'TEST_DATE' (Cost=17 Card=1096
Bytes=21920)
   2    1     INDEX (RANGE SCAN) OF 'TEST_DATE_IDX3' (NON-UNIQUE) (Cost=4
Card=1096)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        163  consistent gets
          0  physical reads
          0  redo size
      36016  bytes sent via SQL*Net to client
       1302  bytes received via SQL*Net from client
         75  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       1098  rows processed

ops$tkyte@ORA920PC> set autotrace off
ops$tkyte@ORA920PC>

It seems unusual to me that the query that selects less rows (is estimated to
select less rows) would use a FULL SCAN (given that both indexes by definition
would have the same cluster factors).  You must have lots of non-standard
optimizer init.ora settings going on there.

so, use the 10053 trace event (in chapter 6) and show us your optimizer
settings.  also, a version is ALWAYS relevant.
Rating: 5
For Dave from Colorado... November 14, 2003
Reviewer: Jan M. van Mourik from Houston, TX
You thought you were kidding, Dave?
How about this:

CREATE TABLE BFX_EMPGROUP (
    EMPLOYEE_GROUPID  VARCHAR2(150) NOT NULL,
    NAME              VARCHAR2(150) NOT NULL,
    START_DATE        DATE,
    END_DATE          DATE);

ALTER TABLE BFX_EMPGROUP ADD (
    CHECK (LENGTH(EMPLOYEE_GROUPID)<=50),
    CHECK (LENGTH(NAME)<=50));

From PeopleSoft HR...
Rating: 5
November 25, 2003
Reviewer: A reader
Rating: 5
is there any reason to use CHAR December 01, 2003
Reviewer: A reader
Tom, is there any situation in which it is better to use a CHAR datatype instead
of VARCHAR2?  I am trying to convince developers to stop using it but they seem
to think that if the number of characters in a string column will always be the
same, like in state abbreviations, then it is better to use CHAR.  Is this a
valid argument?
Followup:
nope.

ask them for the proof or at least the reason why they think "better"
Rating: 5
please disregard previous post December 01, 2003
Reviewer: A reader
I found the answer in the following posting-
http://asktom.oracle.com/pls/ask/f?p=4950:8:856236758633628034:::::

Rating: 5
correction to link above December 01, 2003
Reviewer: A reader
sorry for this, the link above points back to this page. The link where I found
the answer was here:
http://asktom.oracle.com/pls/ask/f?p=4950:8:856236758633628034::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:123212348063,

Rating: 4
GR8 December 26, 2003
Reviewer: Bipin Ganar from INDIA
Hi Tom,
Thanx for the Difference.
I got the difference for char and varchar2 But Still not clear about Char and
varchar.
So, can you highlight more on difference between Char and varchar
Followup:
there are none.  varchar2 and varchar are the same right now.
December 26, 2003 andrew -- Thanks for the question regarding "How to get current schema", version 9.0.0
You Asked
I used alter "session set current_schema = xx" to change schema in a block of
code. It is not working and I suspect that the schema has not been changed for
some reason.

How can I query the system to return the current schema I am in? Is there a
parameter or v$sysstat variable or something? I want to display that value after
the above statement to confirm that that schema has indeed been changed.

Thanks.
and we said...
It is working, give me an example.

select sys_context( 'userenv', 'current_schema' ) from dual;

ops$tkyte@ORA817DEV.US.ORACLE.COM> begin
  2     execute immediate 'alter session set current_schema=scott';
  3     for x in ( select sys_context('userenv','current_schema') x from dual )
  4     loop
  5          dbms_output.put_line( x.x );
  6     end loop;
  7
  8
  9     execute immediate 'alter session set current_schema=ops$tkyte';
 10     for x in ( select sys_context('userenv','current_schema') x from dual )
 11     loop
 12          dbms_output.put_line( x.x );
 13     end loop;
 14  end;
 15  /
SCOTT
OPS$TKYTE

PL/SQL procedure successfully completed.

Now, if your "block of code" is actually a stored procedure and it is further
a DEFINERS RIGHTS procedure (the default kind) then expect this (logged in as
ops$tkyte by the way):

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure p
  2  as
  3  begin
  4     execute immediate 'alter session set current_schema=scott';
  5     for x in ( select sys_context('userenv','current_schema') x from dual )
  6     loop
  7          dbms_output.put_line( x.x );
  8     end loop;
  9
 10
 11     execute immediate 'alter session set current_schema=ops$tkyte';
 12     for x in ( select sys_context('userenv','current_schema') x from dual )
 13     loop
 14          dbms_output.put_line( x.x );
 15     end loop;
 16  end;
 17  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec p
OPS$TKYTE
OPS$TKYTE

PL/SQL procedure successfully completed.

you cannot change your identity in a definers rights procedure, it is fixed AT
COMPILE time by definition (if you have my book "expert one on one", read the
chapter on invoker vs definer rights).

Now, try this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure p
  2  AUTHID CURRENT_USER
  3  as
  4  begin
  5     execute immediate 'alter session set current_schema=scott';
  6     for x in ( select sys_context('userenv','current_schema') x from dual )
  7     loop
  8          dbms_output.put_line( x.x );
  9     end loop;
 10
 11
 12     execute immediate 'alter session set current_schema=ops$tkyte';
 13     for x in ( select sys_context('userenv','current_schema') x from dual )
 14     loop
 15          dbms_output.put_line( x.x );
 16     end loop;
 17  end;
 18  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec p
SCOTT
OPS$TKYTE

PL/SQL procedure successfully completed.

but make sure you understand the ramifications of doing that -- it makes
procedures act more like anonymous blocks (and limits the ability of the shared
pool to allow you to scale!)
Review & Followup

Rating: 4
Another reason August 26, 2002
Reviewer: Rui Brito from Portugal
Hi
You need to give grants to all objects who want access by the oher user. Set
current_schema is only a part of the solution, grants is the other part.

Rui Brito
Rating: 4
A related question March 27, 2003
Reviewer: John from New Jersey, USA
We have 10 database schemas for 10 Weblogic applications (will have more in the
future).  Each schema has a set of stored procedures and tables.  If we don't
want to repeat the stored procedures in each schema (for maintenance reasnon),
is there a way to use the same set of stored procedures to access tables under
different schema?

For instance, I create stored procedures in schema SP, and for user1 to user10,
create private synonyms for each of them to access these stored procedures (of
course grant execute privilege first), and then add the following code in each
stored procedure so that the stored procedures will access data from tables
under different schema depending on who execute the stored procedure (e.g. user1
through user10).  I got some errors with the following simple code, and haven't
got a chance to work on it further.  But would like to hear your opinion on
this.

CREATE OR REPLACE PROCEDURE MYtest
IS
   v_schema   SYS.user_users.username%TYPE;
BEGIN
   SELECT username
     INTO v_schema
     FROM user_users;

   EXECUTE IMMEDIATE 'ALTER SESSION SET CURRENT_SCHEMA = v_schema';
   DBMS_OUTPUT.put_line ('v_schema = ' || v_schema);
END MYtest;
/
Followup:
wrong approach.  use fine grained access control:
http://asktom.oracle.com/~tkyte/article2/index.html
to implement VPD for your database.  one schema. one set of tables. one set of
procedures.  Every group of users thinks they have their own schema, their own
database...
Rating: 4
Thanks for your prompt response. March 27, 2003
Reviewer: John from New Jersey, USA
The requirement is that physical data must be separated from each other.  So
fine grained access control will not allow me to achieve the goal.  Is there a
way in Oracle to access tables under different schema through one set of stored
procedures?
Followup:
the requirement is silly -- revisit it.  why or how does "physical" separation

a) help you
b) do anything for you

it only seems to be impeding you.

There is -- but it is hugely inefficient.  Pitty that common sense is prevented
in blind pursuit of "some phantom requirement"

I would enjoy hearing the business justification for such a restriction.  Many
times "requirements" are the result of lack of knowledge and once knowledge is
let out of the box -- requirements disappear.

I have a feeling someone did not understand what was possible, came up with a
design predicated on physical separation (which could be achieved via
partitioning!) and made it a rule.  Perhaps once shown what is possible the
"rule" disappears.

If you have my book "Expert one on one Oracle" -- read the chapter on Invoker
rights routines.  But it will be

o a management nightmare
o really ugly
o not very performant
o the wrong way.
Rating: 4
Thanks, Tom March 28, 2003
Reviewer: John from New Jersey, USA
Let me try to explain this to you.  Our company provides prescription drug
related consulting services to pharmaceutical companies.  Each of our client
firmly requires that their data to be separated from others.  Our software was
designed based on that requirement.  That is when we launch our new product,
each of our clients will have their own database instance.

The question I'm asking is for our pilot functional demo.  We will allow our
clients to login to our system via the web based application, manipulate data,
examine the output, etc.  Instead of creating a separate database instance for
each of them, I decided to create a separate schema for each of them.  Since we
want each client to have their own play environment, and our software is
designed to handle one client at a time, we have to have a separate set of
tables for each client.  The natural way would be to have a separate set of
stored procedures as well.  It would be nice if we can use one set of stored
procedures to access data from different schema, so that it's easier for us to
debugging, update stored procedures.

In your first reply, you said my original approach is wrong.  Could you explain
why it is so? Is it not doable or not perform well?
Followup:
Ok -- then FGAC is *exactly* what you are looking for i still think... Since the
"requirement" isn't a requirement at all.

Charge these guys lots -- running 100 instances costs you many times more then
running 1.

Anyway -- I would go with the stored procedures in each schema approach.
Reasoning:

a) this is a demo
b) using invoker rights routines, which would permit this, would terribly
complicate your life
c) for a demo....

In this case, the simple "just copy it over and over" solution is the best
solution.  It would add much time to your debug stage to provide Invoker right
support for a demo environment.

I would myself find it easier to augment each table with a CUSTOMER_NAME,
default the column to the USER, and use FGAC to restrict what each user sees.
Rating: 5
Thanks, Tom. March 28, 2003
Reviewer: John from New Jersey, USA
I am on your side.  The original idea was not mine any way.  My plan was to copy
it over and over.

The original design was to run one database server for each client!  I have
since suggested to them they can run multiple instances on the same server.

I don't even want to get into the debate of either using one database for one
client or one database for multiple clients at this point.  But thank you again
for your help.
Rating: 4
Tom, I have to ask you again on this. April 01, 2003
Reviewer: John from New Jersey, USA
My question was how to use one set of stored procedures to access data from
different user schema, and the idea was to use " SESSION SET CURRENT_SCHEMA"
command at the beginning of each stored procedure so that the session is
dynamically set each time when a stored procedure is called.  Something like
this:

   SELECT username
     INTO v_schema
     FROM user_users;

   EXECUTE IMMEDIATE 'ALTER SESSION SET CURRENT_SCHEMA = v_schema';

And you said this is a wrong approach.  Can you explain a little bit why.  Is it
not going to work (I haven't been able to make it work) or is it not a good
practice?  I am trying to explain this to my non-Oracle, non-database
colleagues.  Thanks in advance.
Followup:
beyond the fact that it doesn't work?

well, it "could" work but it would require authid current_user (invoker rights
routines) which requires a totally different development mindset and set of
testing.  what I mean is -- it adds a ton of complexity to your code that is
not relevant for anything beyond a demo - which would be a bad idea.

I would never change my production code just to make a tiny little throw away
demo area "cleaner"

If you have my book "Expert one on one Oracle" -- you find out why this is bad
(and when it is good -- it is not 100% evil).

So, it can be done but

a) it complicates your development unnecessarily
b) you get very very very little benefit from it in your case
c) it would not be a good practice.

ops$tkyte@ORA920> drop user a cascade;

User dropped.

ops$tkyte@ORA920> drop user b cascade;

User dropped.

ops$tkyte@ORA920> drop table t;

Table dropped.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create user a identified by a;

User created.

ops$tkyte@ORA920> create user b identified by b;

User created.

ops$tkyte@ORA920> grant resource to a;

Grant succeeded.

ops$tkyte@ORA920> grant resource to b;

Grant succeeded.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table a.t as select 'table owned by a' data from dual;

Table created.

ops$tkyte@ORA920> create table b.t as select 'table owned by b' data from dual;

Table created.

ops$tkyte@ORA920> create table t as select 'table owned by ' || user data from
dual;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace procedure definer_rights( p_user in varchar2
)
  2  as
  3  begin
  4          execute immediate 'alter session set current_schema = ' || p_user;
  5
  6          for x in ( select * from t )
  7          loop
  8                  dbms_output.put_line( p_user || ': ' || x.data );
  9          end loop;
 10  end;
 11  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace procedure invoker_rights( p_user in varchar2
)
  2  AUTHID CURRENT_USER
  3  as
  4  begin
  5          execute immediate 'alter session set current_schema = ' || p_user;
  6
  7          for x in ( select * from t )
  8          loop
  9                  dbms_output.put_line( p_user || ': ' || x.data );
 10          end loop;
 11  end;
 12  /

Procedure created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec ops$tkyte.definer_rights( 'A' );
A: table owned by OPS$TKYTE

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec ops$tkyte.definer_rights( 'B' );
B: table owned by OPS$TKYTE

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec ops$tkyte.definer_rights( user );
OPS$TKYTE: table owned by OPS$TKYTE

PL/SQL procedure successfully completed.

in a definer rights procedure -- the SQL is 100% bound to the underlying
objects at compile time -- with no hope of changing what they are bound to,
invoker rights are different:

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec ops$tkyte.invoker_rights( 'A' );
A: table owned by a

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec ops$tkyte.invoker_rights( 'B' );
B: table owned by b

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec ops$tkyte.invoker_rights( user );
OPS$TKYTE: table owned by OPS$TKYTE

PL/SQL procedure successfully completed.

but now you have the "grant from heck" problem cropping up -- you need to
make sure that the person RUNNING THE PROCEDURE has privs on the underlying
objects directly -- no longer is "execute on p" good enough -- they need
"execute on p, insert on t1, select on t2, update on t3 and so on" -
UUUUGGGGGLLLLY and unnecessary.
Rating: 5
Thank you for your clear illustration. April 02, 2003
Reviewer: John from New Jersey, USA
I'm sorry I used the phrase you hate most - it doesn't work.  It didn't work for
some reasons, either I didn't do it properly, or it's a dead end.  I posted my
problem here anyway, just want to hear from you if I'm heading the right
direction.

I have twisted your code a bit.

Login as JW

evig01:jw> create or replace procedure invoker_rights( p_user in varchar2
  2  )
  3  AUTHID CURRENT_USER
  4  as
  5  begin
  6          for x in ( select * from t )
  7          loop
  8                  dbms_output.put_line( p_user || ': ' || x.data );
  9          end loop;
 10  end;
 11  /

Procedure created.

evig01:jw> grant execute on jw.invoker_rights to public;

Grant succeeded.

evig01:jw> grant connect to a;

Grant succeeded.

evig01:jw> grant connect to b;

Grant succeeded.

Login as A:

evig01:a> exec jw.invoker_rights( user );
A: table owned by a

PL/SQL procedure successfully completed.

Login as B:

evig01:b> exec jw.invoker_rights( user );
B: table owned by b

PL/SQL procedure successfully completed.

This is exactly what I need.  The procedure are created under "JW".  When user A
execute the procedure, it access data from schema A, whereas when user B execute
the procedure, it access data from schema B.

I have two more questions:

1) What's the difference of "AUTHID CURRENT_USER" and "execute immediate 'alter
session set current_schema = ' || p_user;"?  You've used both of them in your
procedure.

2) When you call your procedure, you pass "user" as the parameter?  It looks
like a built in variable, but when I do "print user", I got:

evig01:jw> print user
SP2-0552: Bind variable "USER" not declared.

But I can do:

evig01:jw> select user from dual;

USER
------------------------------
JW

1 row selected.

Could you explain what is "user" here?
Followup:
1) well, they are apples and oranges...

authid current_user -- that changes the way stored procedures work all together.
 The procedure runs with the privileges of the current invoker (hence "invoker
rights").

set current_schema -- that simply changes the default schema name used to
resolve objects when they need to be resolved.  eg:

SQL> select * from emp;

will use the current_schema to figure out "schema.emp".  In a definer rights
procedure, the current schema is MEANINGLESS as the objects are all permanently
bound to a single object at compile time.  In an invoker rights routine -- they
are used (as shown above).

2) user is a function.  it returns a string, it is the name of the user logged
into the database.
Rating: 5
Thanks Tom. They were well explained. April 03, 2003
Reviewer: John from New Jersey, USA
Rating: 4
What is the way out to avoid creating synonym May 07, 2003
Reviewer: Riyaz H from Chennai, India
Hi Tom, I have a question for you.

MY INTENTION: Avoid Creating SYNONYMS (because it leads to lot of confusion &
difficult to maintain as you also stressed in many places)

I tried out the following
--------------------------
1. User 'a' has table t1
2. User 'b' has table t2
3. t2 rights granted to user a. (from user b)
4. Currently connect in user a
5. I am creating a procedure
create or replace procedure p_1 authid current_user
as
n1,n2 number;
begin
alter session set current_schema=a;
select count(1) into n1 from t1;
alter session set current_schema=b;
select count(1) into n1 from t2;
end;

It is giving error, not able to get the table t2.

6. So I tried different method. Written 2 different procedure
alter session set current_schema=a;

create or replace procedure p_1a authid current_user
as
n1,n2 number;
begin
 select count(1) into n1 from t1;
end;

alter session set current_schema=b;

create or replace procedure p_1a authid current_user
as
n1,n2 number;
begin
 select count(1) into n1 from t2;
end;

-- Got created
7. Now when tried to execute
alter session set current_schema=a;
execute p_1;
alter session set current_schema=b;
--execute permission should be there
execute p_2;

8. But when try to call create a procedure again gave error, not able to get
p_2. What is the reason. (I have given execute permission already, and also
changing the schema)

Create or replace procedure p_3 is
begin
    execute immediate 'alter session set current_schema=a';
 p_1;
    execute immediate 'alter session set current_schema=b';
 p_2;
end;

MY OBJECTIVE IN NUTSHELL:
------------------------
1. I need to write a SINGLE PROCEDURE
2. Inside that, tables of both the users ( a & b) will be accessed. (sepeartely)
3. Grant permission will be given
4. I need to switch the user using 'alter sesion' command
5. I don't want to use synonyms.

I want to write a procedure similar to below: (a & b are schemas). This is
basically I am doing TO AVOID CREATING SYNONYMS

create or replace procedure p_1 authid current_user
as
n1,n2 number;
begin
alter session set current_schema=a;
select count(1) into n1 from t1;
alter session set current_schema=b;
select count(1) into n1 from t2;
end;

WHAT IS THE WAY OUT?

Thanks in advance.
Followup:
 current schema is ALWAYS the owner (definer) of the procedure in a definers
rights routine (as you have coded)

i do not recommend or suggest invokers rights routines.

here I would either

a) create a private synonym
b) create a view <<<=== my personal choice

from A to B.

A view is what you are looking for.
Rating: 5
August 28, 2003
Reviewer: A reader
Rating: 5
Execute immediate for ddl August 29, 2003
Reviewer: A.Titievsky from Louisville,KY
Drop works,create - does not ?
----------------
(HUMTEST) /home/axt0830> sqlplus medrec

SQL*Plus: Release 9.2.0.3.0 - Production on Tue Aug 26 08:56:27 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Enter password:

Connected to:

Oracle9i Enterprise Edition Release 9.2.0.3.0 - 64bit Production

With the Partitioning, OLAP and Oracle Data Mining options

JServer Release 9.2.0.3.0 - Production

SQL> begin

  2  execute immediate 'create table t2(n char)';

  3  end;

  4  /

PL/SQL procedure successfully completed.

SQL> drop table t2;

Table dropped.

SQL>  create or replace procedure prc

  2  as

  3 begin

 4  execute immediate 'create table t2(n char)';

  5  end;

   6  /

Procedure created.

SQL> set serverout on
SQL> exec prc
BEGIN prc; END;

*
ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "MEDREC.PRC", line 4
ORA-06512: at line 1

SQL> create or replace procedure prc as
  2  begin
  3  execute immediate 'drop table t2';
  4  end;
5 /

Procedure created.

SQL> create table t2(note char);

Table created.

SQL> exec prc;

PL/SQL procedure successfully completed.

SQL> select * from t2;
select * from t2
              *
ERROR at line 1:
ORA-00942: table or view does not exist
Followup:
you need a priv to create
you need only to own to drop

you need privs granted to you in order to do it in a procedure, see
http://asktom.oracle.com/~tkyte/Misc/RolesAndProcedures.html

Rating: 4
FGAC August 30, 2003
Reviewer: Matt from Australia
I was looking at your comments above regardng placing multiple sets of
application data into one instance and using FGAC to ensure that each set of
application users only get to see data their own application data. That is,
there are two copies of the same application that rely on the same data
structures.

As usual I am just checking my understanding. To implement this scheme I
understand that:

1) I would need to store a mapping of sets of users to applications
2) I would need to add a new column to tables to record the source application
for the data
3) I would need to create a security policy on each table to check the value if
this new column and generate the appropriate predicate
4) To manage the data (and performance) I could list partition each table on the
"application id" so that queries from a particular application prune the
unwanted data, so that this data is not considered in the query. I would also
need local indexes.

Is this correct?

Thanks in advance.
Followup:
yup, that is a valid implementation at a high level.
Rating: 3
one schema for multiple users October 20, 2003
Reviewer: wasey from USA
I would like to implement a a single schema in which different users can create
the objects . The owner of the objects  should grant the select privileges to
other users to see its objects otherwise the owners of the objects should see
there own objects.

How do I impelemnt it? any ideas are really appreciated
Followup:
you don't.  it defeats the purpose of a "single schema".  a schema is the way to
control these things.

by your very very definition (problem definition) you in fact do NOT want a
single schema.
Rating: 3
one schema for multiple users October 22, 2003
Reviewer: wasey from USA
What I really meant to do is to let  all the users create
there tables through an application in a schema, and the owners should have
access to there own tables only through an aplication no other tools. The owner
can grant select priveleges to other others to have them view the tables.
This way I can have a single place holder for all the tables
belonging to different users.

I really appreciate the response
Followup:
you are doing it "wrong".  i hear you saying what you "want", but it "doesn't
work that way".

once you put a table into a schema -- it belongs to that schema, not to the guy
who created it.  the "owner" is the schema, the "owner" is NOT the guy who
created it.  the guy who created it would not be able to grant on it, it would
have no connection back to them.

rethink this approach.  see how the software actually works and use it that way.
Rating: 4
how to find out the databse the user belongs December 26, 2003
Reviewer: p.s.vinodh from India, chennai
hi tom,
i would like to know, how to find out the database name for particular user as i
logged in . and i dont' have permission to see the v$database table.

can you tell me how to find out how to trace that.. if my system has more than
one database.

thanks in advance.
Followup:
ops$tkyte@ORA9IR2> show parameter db_name

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
db_name                              string      ora9ir2
ops$tkyte@ORA9IR2> select name from v$database;

NAME
------------------------------
ORA9IR2

ops$tkyte@ORA9IR2> variable x number
ops$tkyte@ORA9IR2> variable y varchar2(25)
ops$tkyte@ORA9IR2> exec :x := dbms_utility.get_parameter_value( 'db_name', :x,
:y );

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> print y

Y
---------------------------------------------------------------------------------
--------------------------------------------------
ora9ir2

that last method should work for all since dbms_utility has execute to public by
default.
Rating: 5
thanks December 29, 2003
Reviewer: P.S.Vinodh from India, chennai
December 26, 2003 Andre -- Thanks for the question regarding "Autmatic Undo Management", version 9.0.1
You Asked
What's your opinion regarding Automatic Undo Management ? The problem with
"automatic" things is they sometimes (ie very often) don't work the way we'd
like them to, as with your pet peeves like AUTOALLOCATE and
CURSOR_SHARING=FORCE. Don't you think they're making this big hype over AUM, but
there may be some big drawbacks that aren't explicit ?

Thanks
and we said...
I think they are awesome, have not had any problems with them.

My pet peeve with cursor sharing is not the way it works (it works as it works
-- as I expect it to work, that is the problem).  It is a "solution" for a
problem that should not exist (improper use of bind variables in an
application).  It attempts to be a band-aid -- a quick fix -- a silver bullet,
for a problem that should never have existed in the first place.  It's side
effects are quite understandable and 100% expected (by me anyway).  That is the
problem though, they have side effects.

with undo tablespaces, I have no issues.  I've always said (over and over I've
said) -- you gotta configure sufficient rollback for the job you do.  undo
tablespaces help enforce that 100%.  In a system where the retention period is
set properly -- it is the end of the ora-1555!
Review & Followup

Rating: 4
continued June 17, 2002
Reviewer: Andre from Brazil
I am saying that because we used to size RBS extents according to some rules to
make the best of them, like using the 20-or-so-extents-per-RBS rule (which is
statistically proven to enhance performance). Using AUM we just have no control
over this. I am affraid we may encounter some side-effect. Just like
AUTOALLOCATE isn't the best answer if you know what you are doing, I am affraid
UAM is not either.
Rating: 5
Some idiosyncracies with AUM June 17, 2002
Reviewer: Connor from UK
I very much like the concept of AUM - still a couple of things maybe for Oracle
to iron out (not that I've tested them with 9.2 yet).

a) You always start with (is it 8?) undo segments.  In a heavy system this
automatically ramps up to 'n', but as soon as you shutdown, you're back down to
8 and some associated contention as it ramps it way back up to 'n'

b) seems to be 'aggressive' on enforcing the undo retention.  As soon as some rb
info expires, it gets excised out, so (imho) you tend to get a fair few more
shrinks that an equivalent manual scheme.

That aside, its a lovely way to do rollback - any maybe 9.2 will reveal some
better things.

Connor
Rating: 5
continued -- To Connor June 17, 2002
Reviewer: Andre from Brazil
Yep, Connor. I think, after all, the new undo manageability makes up for any
eventual overhead.
Rating: 5
Automatic Undo Management October 19, 2002
Reviewer: Natalia
I have two confusion relating to AUM.

1)UNDO_MANAGEMENT=AUTO, if UNDO_TABLESPACE parameter is omitted and there is
also no undo tablespace then the instance starts and use rollback segments from
SYSTEM tablespace. Does the instance will start in AUM and would the automatic
undo will be active?

2) suppose UNDOTBS1 is switched to UNDOTBS2 and due to some pending transactions
its status is PENDING OFFLINE thenonly the existing transactions can continue to
use it so what about the new user transactions? will they use new undotbs2?

Thanks
Followup:
1) i have confusion about the question.  I cannot parse that last sentence...

2) do what I would do -- test it!  Anyway, it would switch to the new tblspace,
all new transactions would use the new tablespace and as soon as the old
transaction in undotbs1 was done, it would finish offlining the entire old undo
tablespace rollback segments.
Rating: 5
correction October 19, 2002
Reviewer: Natalia from Pakistan
sorry for the ambiguity in my question (1).
actually I want to know that will the instance use SYSTEM tablespace for
automatic undo management?
Thanks
Followup:
it won't  -- not for auto (that is set to an UNDO tablespace and system isn't an
undo tablespace).

Now, that said, if the tablespace the undo is for is offlined or otherwise made
unavailable, we will as normal copy the undo information into the system
rollback segments so we can rollback any outstanding transactions upon
re-onlining or recovering that tablespace.
Rating: 5
set transaction use rollback segment March 17, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
If I use AUM, what should I do with SET TRANSACTION USE ROLLBACK SEGMENT
statements in the code.

Thanks
Followup:
set undo_suppress_errors to true so that the statement is silently ignored by
the database.
Rating: 5
undo_suppress_error March 17, 2003
Reviewer: Chandru from Harlingen, TX
Tom,

Please see below.

SQL> show parameter undo_management

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
undo_management                      string      AUTO

SQL> show parameter undo_tablespace

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
undo_tablespace                      string      UNDOTBS1

SQL> show parameter undo_suppress_errors;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
undo_suppress_errors                 boolean     FALSE

SQL> set transaction use rollback segment SYSSMU1$;
set transaction use rollback segment SYSSMU1$
*
ERROR at line 1:
ORA-30019: Illegal rollback Segment operation in Automatic Undo mode

SQL> alter session set undo_suppress_errors = true;

Session altered.

SQL> set transaction use rollback segment SYSSMU1$;

Transaction set.

***********************
It looks like the database does not "ignore" it but gives the feedback as
transaction is set. Is it misleading? I tried the same by setting optimal
parameter as below and the behavior is same.

SQL> alter session set undo_suppress_errors = false;

Session altered.

SQL> alter rollback segment SYSSMU8$ storage (optimal 1M);
alter rollback segment SYSSMU8$ storage (optimal 1M)
*
ERROR at line 1:
ORA-30019: Illegal rollback Segment operation in Automatic Undo mode

SQL> alter session set undo_suppress_errors = true;

Session altered.

SQL> alter rollback segment SYSSMU8$ storage (optimal 1M);

Rollback segment altered.

Even though it suppresses the error, the feed back is like the database is doing
what is asked. Could you please clarify? However, if I checked the v$rollstat as
shown below, optimal is not actually set as it gives null value for optsize even
though it said rollback segment altered.

SQL> select usn,optsize,status from v$rollstat;

       USN    OPTSIZE STATUS
---------- ---------- ---------------
         0            ONLINE
         1            ONLINE
         2            ONLINE
         3            ONLINE
         4            ONLINE
         5            ONLINE
         6            ONLINE
         7            ONLINE
         8            ONLINE
         9            ONLINE
        10            ONLINE

11 rows selected.
****************************************
Followup:
that is what I meant by "ignores it".  The database is acting like a teenager..

You say to the database:

"do this thing"

Database replies

"Yeah, whatever"

You ask:

"so did you do it"

Database replies

"Yeah, sure I did" (lying through its teeth)

That would be the very definition of ignoring you.  It ignores the command.
Rating: 5
Great Reply! March 18, 2003
Reviewer: Chandru from Harlingen, TX
Thanks as always for your response. I am clear now.
Chandru
Rating: 5
Ooo! Someone has a teenager! March 19, 2003
Reviewer: A reader
Rating: 5
Very informative April 23, 2003
Reviewer: Robert
Is the default undo tablespace installed when running dbca sufficient for a
database or does it still need to be checked for files/size/undo retention etc?

Basically, if you were not to go with the default what would you recommend
changing?

Cheers
Robert
Followup:
well, for the GUI inclined you would fire up OEM during your peak processing
times and ask OEM "if I wanted an undo retention period of 1 hour, how much disk
do I need to buy".

You can do the same by using the v$ views and such to size it out yourself.

The default is ok since it is done with autoextend datafiles -- so given you
have enough freespace, it'll grow as needed.  Most implementations I would
suspect might be inclined to size it a little more "precisely" using simulations
and load tests -- so they know how much to buy.

undo retention -- is personal.  only you know what it needs be for your system.

file size -- is again, something you do need to know, but OEM will do it for you
or you can derive the information from the database itself under load.
Rating: 5
questions on rollback segments July 08, 2003
Reviewer: A reader from ca
Hi Tom
Thanx for your continuing support to Oracle community!!

"I've always said (over and over I've
said) -- you gotta configure sufficient rollback for the job you do.  undo
tablespaces help enforce that 100%."

Could you kindly explain how undo tablespaces help enforce the sufficient
rollback configuration automatically 100%?

Could you also comment on what happens if we have operations (say a Foreign Key
cascade delete) that deletes tables with multiple rows - each of which have
clobs in them ( each clob could contain 4G of data) Won't this generate lots of
undo - resulting in one needing to configure huge RBSs just for this purpose?

Is this a disadvantage of using clobs?
Followup:
they use an retention period.  assuming you've the space on disk physically to
hold it, they grow to accomidate you.

look at the amount of UNDO generated by a delete on a clob, you might be
surprised ;)  clobs are undone a little differently (eg: the lob index changes
are undoable -- the lob data just isn't recycled until you commit - no undo for
4gig)

ops$tkyte@ORA920> create table t ( x int, y clob );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          l_clob  clob;
  3          l_data  long default rpad( '*', 32760, '*' );
  4  begin
  5          insert into t values ( 1, empty_clob() )
  6          returning y into l_clob;
  7
  8          for i in 1 .. 1000
  9          loop
 10                  dbms_lob.writeAppend( l_clob, length(l_data), l_data );
 11          end loop;
 12          commit;
 13  end;
 14  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select x, dbms_lob.getlength(y) from t;

         X DBMS_LOB.GETLENGTH(Y)
---------- ---------------------
         1              32760000

ops$tkyte@ORA920> select used_ublk from v$transaction;

no rows selected

ops$tkyte@ORA920> delete from t;

1 row deleted.

ops$tkyte@ORA920> select used_ublk from v$transaction;

 USED_UBLK
----------
        14

ops$tkyte@ORA920> commit;

Commit complete.
Rating: 5
thank you! July 08, 2003
Reviewer: A reader
This is very interesting -
Could you kindly elaborate on "clobs are undone a little differently (eg: the
lob index changes are undoable -- the lob data just isn't recycled until you
commit - no undo for 4gig)"

If there is no undo for 4gig - what are the implications
for the data value - I am sure it is retained for
rollback cases. Why is it that in the above example of
yours the undo generated is only 14 blocks for a clob
tha has approx 30mb of data.

Thanx!!!
Followup:
we just unlink the pointers to the lob data -- it remains safely in the lob
segment, if we need to rollback -- we just point to it again.
Rating: 5
great !! thank you so much!!! July 09, 2003
Reviewer: a fan
Rating: 5
undo for insert of clob August 17, 2003
Reviewer: A reader
In the above experiment if I do a select for undo space usage before the commit
- I get the undo generated for the insert operation of clob - in my experiment I
got a number
greater than that for the delete. Usually undo for a delete is the maximum. Why
is this not true for clobs?

Thanx!
Followup:
because of the way lobs are managed -- the insert has undo for the lob index,
the delete likewise.  inserts and deletes from indexes are each "intense" and
inserts can be much harder then deletes (splits and such).

The lob data itself is versioned in the lob space, not in undo.
Rating: 5
Re: undo for insert of clob August 17, 2003
Reviewer: A reader
Is it because in the above case, we did 1000 separate inserts that would perhaps
generate more undo as compared
to one insert. Since there was only one delete operation,
this may be a valid point?

thanx!
Rating: 5
thank you Tom! August 17, 2003
Reviewer: A reader
"because of the way lobs are managed -- the insert has undo for the lob index,
the delete likewise.  inserts and deletes from indexes are each "intense" and
inserts can be much harder then deletes (splits and such).

The lob data itself is versioned in the lob space, not in undo"

Could you elaborate on the following points:
1. this implies that each lob has an index associated with it?
2. deletes from indexes can be less intense than inserts
   because inserts can result in splitting of blocks
   whereas deletes need not. How does that affect the
   RBS generated? Isn't RBS a function of data that changes
   in the end - not the intermediate results such
   as "splitting" of blocks?
3. Kindly elaborate a little more on what you meant by "The lob data itself is
versioned in the lob space, not in undo"? What is versioning here?

Thanx a million for your awesome site and unparalleled
generosity!
Followup:
1) thats a fact

2) to undelete an index entry is pretty easy.  to un-insert it, harder.  The
actual LOB data itself, well, that doesn't need to go into rbs.  it is sort of
like an "insert append" operation.  only the DML that affects the management of
space (the index mods) needs to be rolled back, the actual data that was
inserted (the lob) doesn't -- we just "unpoint" to it.

to rollback a lob delete -- we rollback the changes to the lob index, lob
"reappears".

to rollback a lob insert -- we rollback the changes to the lob index, lob
"unappears"

3) just like the versioning we do in rbs for "regular" data.  we keep
multiple-copies of lob data in the lob area.
Rating: 5
sorry stil not clear:( August 18, 2003
Reviewer: A reader
"to rollback a lob delete -- we rollback the changes to the lob index, lob
"reappears".

to rollback a lob insert -- we rollback the changes to the lob index, lob
"unappears"
"

Insert operations int indexes are more intense due to splitting of blocks,
right? Does this mean that un-inserting it involves rejoining of split blocks -
which
makes it more "intense" - Hence RBS of insert has more
data?

Thanx!!
Followup:
ops$tkyte@ORA817DEV> create table t as select * from all_objects where rownum <
100;

Table created.

ops$tkyte@ORA817DEV> create index t_idx on t(object_name);
Index created.

ops$tkyte@ORA817DEV> analyze index t_idx validate structure;
Index analyzed.

ops$tkyte@ORA817DEV> @printtbl8 'select * from index_stats';

HEIGHT                        : 1
BLOCKS                        : 64
NAME                          : T_IDX
PARTITION_NAME                :
LF_ROWS                       : 99
LF_BLKS                       : 1
LF_ROWS_LEN                   : 3821
LF_BLK_LEN                    : 7996
BR_ROWS                       : 0
BR_BLKS                       : 0
BR_ROWS_LEN                   : 0
BR_BLK_LEN                    : 0
DEL_LF_ROWS                   : 0
DEL_LF_ROWS_LEN               : 0
DISTINCT_KEYS                 : 99
MOST_REPEATED_KEY             : 1
BTREE_SPACE                   : 7996
USED_SPACE                    : 3821
PCT_USED                      : 48
ROWS_PER_KEY                  : 1
BLKS_GETS_PER_ACCESS          : 2
PRE_ROWS                      : 0
PRE_ROWS_LEN                  : 0
-----------------

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> set verify on
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert into t select * from all_objects;
26833 rows created.

ops$tkyte@ORA817DEV> rollback;
Rollback complete.

ops$tkyte@ORA817DEV> analyze index t_idx validate structure;
Index analyzed.

ops$tkyte@ORA817DEV> @printtbl8 'select * from index_stats';
HEIGHT                        : 2
BLOCKS                        : 192
NAME                          : T_IDX
PARTITION_NAME                :
LF_ROWS                       : 99
LF_BLKS                       : 176
LF_ROWS_LEN                   : 3821
LF_BLK_LEN                    : 7996
BR_ROWS                       : 175
BR_BLKS                       : 1
BR_ROWS_LEN                   : 2768
BR_BLK_LEN                    : 8028
DEL_LF_ROWS                   : 0
DEL_LF_ROWS_LEN               : 0
DISTINCT_KEYS                 : 98
MOST_REPEATED_KEY             : 1
BTREE_SPACE                   : 1415324
USED_SPACE                    : 6589
PCT_USED                      : 1
ROWS_PER_KEY                  : 1.01020408163265306122448979591836734694
BLKS_GETS_PER_ACCESS          : 3.00510204081632653061224489795918367347
PRE_ROWS                      : 0
PRE_ROWS_LEN                  : 0
-----------------

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV> set verify on

we didn't "unsplit" them, we in effect deleted them from the index.  see the
size increase...
Rating: 5
hmmm.. August 18, 2003
Reviewer: A reader
"we didn't "unsplit" them, we in effect deleted them from the index.  see the
size increase... "

If we did not "unsplit" them, then the rollback of insert
should not be very intensive compared to delete rollback?

sorry not getting it...

thanx a lot for your tireless efforts!!
Rating: 5
September 23, 2003
Reviewer: A reader from orlando,FL
Tom,

Pls validate my understanding of an AUM environment:

a) there can be only one undo tablespace
b) None of the ASSM parameters are applicable to the undo tablespace.

Thanks as always...
Followup:
a) correct, just stripe it.
b) correct, it is a big circular buffer -- no segment space to manage really.
it is not like a table.
Rating: 4
beginner question November 11, 2003
Reviewer: Fernando from Madrid, Spain
I'm a programmer, not a DBA but I've been told to find out how to solve a
situation.

Testing a developing environment DB, accidentally a 3 million records
transaction (that rarely lasted around 12 hours when other times had only lasted
around 3 hours) made our UNDO tablespace datafile increase so much that the
machine where our DB is became 100% full, when it used to be only 92% (we share
space with other applications).

As our application will never allow such "big" transactions to happen in the
production environment, I've been told to find a way to "rebuild" the undo
tablespace in order to have a datafile limit (something like a 25% increase of
the initial size), so that if some "accident" happens again the transaction will
fail but the disk space will not increase so much.

(I don't know if what I've been asked to to is a good idea)

In the create database stament whe have the following undo tablespace:

UNDO TABLESPACE undo DATAFILE 'UNDO.dbf'
SIZE 200M REUSE AUTOEXTEND ON NEXT 5120K MAXSIZE UNLIMITED

Another problem, we have now, is that we need releasing disk space, if we want
to go on working, and we can´t resize the undo tablespace to its initial size,
why?

Thanks in advance.
Followup:
to resize an undo tablespace:

a) create a new one of the desired size
b) alter system to switch to it
c) when the last active transaction finished in the old, drop it.

if you want the maxsize to be 125% of the orginal size, use a maxsize of 125% of
the original, NOT unlimited.
Rating: 5
undo retention December 24, 2003
Reviewer: Reader
Is there any performance issue if I set undo_retention to a large value? My
understanding is that I should provide adequate undo space to support
undo_retention. Other than the need for more disk space for undo, would a larger
value for the retention affect the database performance in any way? Thanks.
Followup:
http://www.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&threadm=BykX9.29812%24jM
5.77141%40newsfeeds.bigpond.com&rnum=8&prev=/groups%3Fq%3D%2B%2522large%2Brollbac
k%2Bsegments%2522%2Bgroup:comp.databases.oracle.*%26hl%3Den%26lr%3D%26ie%3DUTF-8%
26oe%3DUTF-8%26selm%3DBykX9.29812%2524jM5.77141%2540newsfeeds.bigpond.com%26rnum%
3D8

there can be some overhead, yes.

if the rollbacks are kept "small", we can reuse (new) a block of undo without
ever having written it -- that is, we could cycle through the RBS data in memory
and not really write it out.

However, in order to not get other errors (1555) you need to have more then
sufficient space allocated.

read that thread, you'll see all of the viewpoints.  For me, the ease -- and
error free (no 1555's) processing I gain from having more then adequately sized
undo more than offsets anything else.  We can tune IO (and it happens in the
background, dbwr is the guy waiting, not us in general)
Rating: 5
Reader December 26, 2003
Reviewer: A reader
Care to give us the subject title in comp.database.oracle
Followup:

http://www.google.com/groups?selm=BykX9.29812%24jM5.77141%40newsfeeds.bigpond.com&oe=UTF-8&output=gplain
smaller url....
December 26, 2003 Igor -- Thanks for the question regarding "Index Organized Tables.", version Oracle 8.1.5
You Asked
My question is - how to reuse space in Index Organized Table
(IOT) after deleting substantial amount of rows.
When I deal with regular table, after deleting a lot of rows I
rebuild the indexes in order not to leave gaps in the index
blocks, because we know that this empty space in the indexes
will not be reused (unlike, when it is reused in table blocks
after PCTUSED threshold met in the block).
So, what could be done with IOT in order to prevent constant
growth, even after a lot of rows being deleted?

TIA
and we said...
The answer to this is pretty neat actually -- Oracle8i release
8.1 allows you to do 2 new things that make this interesting.

o an online index rebuild
o 'move' a table

Since an IOT is just really an index.... We can actually rebuild
the index by moving the table and do this in an online fashion
(eg: people are modifying this data while we are rebuilding...)

Here is an example:

ops$tkyte@dev8i> create table demo_iot
  2  ( object_id int primary key,
  3    oname     varchar2(30),
  4    owner     varchar2(30),
  5    status    varchar2(30) )
  6  organization index;

Table created.

ops$tkyte@dev8i> insert into demo_iot
  2  select object_id, object_name, owner, status
  3    from all_objects
  4  /

22525 rows created.

ops$tkyte@dev8i> column index_name new_value iname
ops$tkyte@dev8i> select index_name
  2    from user_indexes
  3   where table_name = 'DEMO_IOT'
  4  /

INDEX_NAME
------------------------------
SYS_IOT_TOP_87241

ops$tkyte@dev8i> analyze index &iname validate structure;
old   1: analyze index &iname validate structure
new   1: analyze index SYS_IOT_TOP_87241 validate structure
Index analyzed.

ops$tkyte@dev8i> select height, blocks, del_lf_rows from
index_stats;

    HEIGHT     BLOCKS DEL_LF_ROWS
---------- ---------- -----------
         2        192           0

Now we will just delete half of the rows in the table more or
less.  Trying for "every other row"

ops$tkyte@dev8i> delete from demo_iot where mod(object_id,2) =
1;
11270 rows deleted.

ops$tkyte@dev8i> analyze index &iname validate structure;
old   1: analyze index &iname validate structure
new   1: analyze index SYS_IOT_TOP_87241 validate structure

Index analyzed.

ops$tkyte@dev8i> select height, blocks, del_lf_rows from
index_stats;

    HEIGHT     BLOCKS DEL_LF_ROWS
---------- ---------- -----------
         2        192       11270

So, we have lots of deleted rows in the index (none of the
blocks got totally empty).  How can we "clean" that up...

ops$tkyte@dev8i> alter table demo_iot move online;
Index altered.

ops$tkyte@dev8i> analyze index &iname validate structure;
old   1: analyze index &iname validate structure
new   1: analyze index SYS_IOT_TOP_87241 validate structure

Index analyzed.

ops$tkyte@dev8i> select height, blocks, del_lf_rows from
index_stats;

    HEIGHT     BLOCKS DEL_LF_ROWS
---------- ---------- -----------
         2        128           0

There we go -- its all cleaned up now.  As a test, you should
have other sessions open when we do the alter table move -- just
to see that the table is online and available for all DML
Review & Followup

Rating: 3
August 23, 2001
Reviewer: A reader
you are talking two method
one is
an online index rebuild

i tried this one but it didn't work.

alter index SYS_IOT_TOP_87241 rebuild
*
ERROR at line 1:
ORA-28650: Primary index on an IOT cannot be rebuilt
Followup:
See
http://technet.oracle.com/doc/oracle8i_816/server.816/a76956/tables.htm#1913
It is done via:

ALTER TABLE docindex MOVE ONLINE;
Rating: 5
May 10, 2002
Reviewer: Kimberly from Reston, VA USA
I have been searching for this answer for 2 days. I should have come here first.
This is exactly what I was looking for.
Rating: 5
Deleting rows from IOTs with foreign keys May 16, 2002
Reviewer: Brigitte from Germany
Hallo Tom,

your "answer and reply" is very useful to learn many details!

I had changed some tables to IOT some month before.
Now I had problems with deleting more than one rows in the IOTs. The IOTs have
foreign keys with on delete cascade to one table (TestDok).
I don't know whether the problem is new or not because normally rows don't be
deleted in TestDok, they were signed.

SQL> r
  1  delete from TESTDok
  2* where objektid = '38060'
delete from TESTDok
*
ERROR at line 1:
ORA-00600: internal error code, arguments: [25012], [8], [10], [], [], [], [],
[]

I saw in the trc-file the problem occurs when oracle must delete the rows from
the child table.

SQL> select * from TESTDokH where objektid = '38060' or superobjektid = '38060';

OBJEKTID                         SUPEROBJEKTID
-------------------------------- --------------------------------
27654                            38060
28830                            38060

2 rows selected.

SQL> delete from TESTDokH where superobjektid = '38060';
delete from TESTDokH where superobjektid = '38060'
                    *
ERROR at line 1:
ORA-00600: internal error code, arguments: [25012], [8], [10], [], [], [], [],
[]

The tables are now :

/* TESTDok */
CREATE TABLE TESTDok
(
    OBJEKTID        VARCHAR2(32)
            CONSTRAINT PK_TESTDok PRIMARY KEY
            using index tablespace user_i,
    MANDANT              VARCHAR2(30) NOT NULL,
    NOTESID         VARCHAR2(32)
            CONSTRAINT UK_TESTDok UNIQUE
            using index tablespace user_i,
    DELETED             VARCHAR2(1) DEFAULT '0',
. . .
    AID_MZ         VARCHAR2(32),
    AID_EF         VARCHAR2(32),
CONSTRAINT FK_TESTDok1 FOREIGN KEY (AID_MZ)
        REFERENCES TESTAR (FE_ID)
        ON DELETE SET NULL,
CONSTRAINT FK_TESTDok2 FOREIGN KEY (AID_EF)
        REFERENCES TESTAR (FE_ID)
        ON DELETE SET NULL
) tablespace user_tg pctfree 8 pctused 86
/

create bitmap index BM_TESTDok_mandant ON TESTDok (mandant)
tablespace user_i
/

/* TESTDokH IOT*/
CREATE TABLE TESTDokH
(
    OBJEKTID        VARCHAR2(32),
    SUPEROBJEKTID    VARCHAR2(32),
  CONSTRAINT IOTPK_TESTDokHiera PRIMARY KEY (OBJEKTID, SUPEROBJEKTID),
  CONSTRAINT FKTESTDokHiera FOREIGN KEY (OBJEKTID)
        REFERENCES TESTDOK (OBJEKTID)
        ON DELETE CASCADE,
  CONSTRAINT FKTESTDokHiera2 FOREIGN KEY (SUPEROBJEKTID)
        REFERENCES TESTDOK (OBJEKTID)
        ON DELETE CASCADE
)ORGANIZATION INDEX PCTTHRESHOLD 5
tablespace user_tg pctfree 10
/

What is wrong?

Thanks
Followup:
as with all internal errors, you should contact support.  Many times,  a patch
exists for this problem.
Rating: 4
IOT pk modification. July 15, 2003
Reviewer: Alvin from Philippines
If i have say an Index organized table 'T' who has columns a,b,c,d and declared
a,b to be the primary key.

What i really needed columns a,b,c to be the primary key. Is there an alter
table command that allows me to alter IOT and add another column in the
composite primary key ?

Many thanks.
Followup:
in Oracle9i, you can use an online rebuild.

In 8i, you'll have to use a Create table as select, drop old, rename new.
Rating: 4
prefix... July 15, 2003
Reviewer: Alvin from Philippines
If i have say an Index organized table 'T' who has columns a,b,c,d

this time i've a,b,c as primary key in that order also.

And i found out that majority of my queries has a,c in the predicate will the
composite key of a,b,c be useful ? say compared to composite key a,c ?
Followup:
arg, a primary key is a primary key  -- that is what decides what is the key.

if the majority of your queries access the table via a,c -- then the primary key
should be on a,c,b
Rating: 5
follow up question.. July 15, 2003
Reviewer: Alvin from Philippines
Half of my queries are a,c and the other half is a,b

Will making a,b,c as composite primary key work for both ? a,b has a little bit
more queries than a,c.

I tried in our developmental server the effects of using an IOT as compared to
the heap organized table and the difference via tkprof was enough to convince me
to implement it on our production server.

It's a 24x7 system and downtime should be kept to a minimum.

How will i freeze the said table ? is there a 'alter table <tablename> read only
command can't seem to find it on 8i. Or will the CTAS freeze the source table ?

I was planning to do the following on our off-peak times.

1. rename the orgininal table to <temp>- so no further changes would be
introduced
2. create table <original name> as select from <temp>

I did check for other object dependencies and found none. The table's pk are not
enforced as fk on other tables and the said table doesn't contain any columns
defined as fk's.
Followup:
in one session

lock table t in exclusive mode;

in another, copy it.

then in the fist, drop it and rename the other.

in 8i, if you have queries of the type a,b and a,c, you'll probably want to
index

a,b,c
c

the reason only "c" is because all indexes on an IOT have the primary keys in
them already.  consider:

ops$tkyte@ORA817DEV> create table t ( a int, b int, c int, d char(20), primary
key (a,b,c) ) organization index;

Table created.

ops$tkyte@ORA817DEV> insert into t
  2  select rownum, -rownum, rownum*2, 'x'
  3  from all_objects where rownum <= 5000;

5000 rows created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create index t_idx on t(c);

Index created.

ops$tkyte@ORA817DEV> analyze table t compute statistics for table for all
indexes for all indexed columns;

Table analyzed.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> set autotrace traceonly explain
ops$tkyte@ORA817DEV> select a,c from t where c = 44 and a = 22;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=6)
   1    0   INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=1 Bytes=6)

ops$tkyte@ORA817DEV> set autotrace off

See how it was able to use the index in its entirety to answer the question?  it
did not need to goto the table to get A, it was already there.
Rating: 4
Index rebuild required? July 16, 2003
Reviewer: Tony from India
why should we rebuild indexes?. You're against index rebuild. Refer your answer
http://asktom.oracle.com/pls/ask/f?p=4950:8:444551828551181757::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:6601312252730,
Your comment please.
Followup:
interesting - one might think you would have just posted this on that
question...

anyway

I'm not AGAINST rebuilding ever.

I'm against the scheduled rebuilding of indexes under the premise that "well,
everyone knows you should".

I'm against doing things that have not been shown to

a) make the system better
b) not cause adverse effects

bitmap indexes -- may need to be rebuilt after some amount of DML

my text index on asktom -- i rebuild that from time to time after lots of DML
(it in fact is very similar to a bitmap index)

b*tree indexes, hardly ever (hint read about COALESCE, most of the benefit,
fraction of the work)
Rating: 4
IOT OVERFLOW July 16, 2003
Reviewer: michael from SAN DIEGO, CA
Tom,

Thank you for the great services you provide.

I created IOT as such for use as a drop down list

create table category
  (name VARCHAR2(50)
     CONSTRAINT category$name$nn NOT NULL
  ,display_yn VARCHAR2(1)
     CONSTRAINT category$display$nn NOT NULL
  ,CONSTRAINT category$pk
     PRIMARY KEY (name)
  )
  organization index tablespace indx
  including display_yn overflow;

alter table category add constraint category$display$ck01
  check(display_yn in ('Y','N'));

create unique index category$name$x on category(UPPER(name))
  tablespace indx;

My questions are:

1. Basically, there are two indexes for the table on the same column. Is this
efficient and how can I get around with using only one index?

NEREUS@mdl1> select index_name, index_type from dba_indexes
  2  where owner='NEREUS' and index_name like 'CATEGORY%';

INDEX_NAME                     INDEX_TYPE
------------------------------ ---------------------------
CATEGORY$NAME$X                FUNCTION-BASED NORMAL
CATEGORY$PK                    IOT - TOP

2.  How do I name the OVERFLOW table?

NEREUS@mdl1> select table_name, iot_name from dba_tables
  2  where owner='NEREUS'
  3  and table_name='CATEGORY'
  4  or IOT_NAME='CATEGORY';

TABLE_NAME                     IOT_NAME
------------------------------ -----------------------------
CATEGORY
SYS_IOT_OVER_27251             CATEGORY

Any recommendation you can make for performance design as well would be great.
Followup:
1) well, there is only ONE index that I see -- the fbi you created.  the other
"index" is the table really.

2) you do not need, want, nor desire the overflow in this case.  In fact, I
would say if you need an overflow, chances are you might not really want an IOT
(there are of course exceptions to that).

If you always search by upper, you might consider

create table category
  (upper_name varchar2(50) constraint cat$pk primary key,
   name VARCHAR2(50)  CONSTRAINT category$name$nn NOT NULL,
   display_yn VARCHAR2(1) CONSTRAINT category$display$nn NOT NULL
  )
  organization index;

and just inserting the upper name, name and display_yn

that way you have

o just the IOT
o no ambiguities -- eg right now, you could have 'hello' and 'Hello' in your
table as a "primary key" value
Rating: 5
How oracle locates a row in IOT? August 23, 2003
Reviewer: Samuel Bernard from Salt Lake City
Tom, does oracle use PK value to locate the row fast in IOT? it does not store
rowid as conventional btree index does. Could you explain the underlying
mechanism? Thanks.
Followup:
the "rowid" is a universal rowid -- not a real rowid rowid.

as such, it actually incorporates the primary key value.  Consider:

ops$tkyte@ORA920> create table t ( x varchar2(50) primary key ) organization
index;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( 'xxx' );

1 row created.

ops$tkyte@ORA920> insert into t values ( rpad('x',50,'x') );

1 row created.

ops$tkyte@ORA920> select x, rowid from t;

X                                                  ROWID
--------------------------------------------------
-------------------------------------------------------------------------------
xxx                                                *BAGAAjoDeHh4/g
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
*BAGAAjoyeHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHj+

see how big a rowid can be...
Rating: 5
Great Info August 23, 2003
Reviewer: Samuel Bernard from Salt Lake City
Tom, if I create an index on a column other than the primary key columns in IOT,
what does the index just created on the column store? Does it store what you
have shown in the demo? I don't know much about using IOT so I would appreciate
your time in explaining this concept. Thanks much.
Followup:
an index on an IOT always includes the primary key.
Rating: 4
index key contains ..... August 23, 2003
Reviewer: David from Austin, TX
Tom, does it mean that the index entry for the indexed column (secondary index)
in IOT will have key value, primary key value, rowid (logical?)

thanks.
Followup:
yes, note how in the following example, only the index T_IDX is accessed --
never the table.  even tho the index is on Y and Y alone

ops$tkyte@ORA920> create table t ( x int primary key, y int ) organization
index;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index t_idx on t(y);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec dbms_stats.set_table_stats( user, 'T', numrows => 100000
);

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920>
ops$tkyte@ORA920> select x from t where y = 1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1000 Bytes=26000)
   1    0   INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=1000
Bytes=26000)

ops$tkyte@ORA920> select y from t where y = 1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1000 Bytes=13000)
   1    0   INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=1000
Bytes=13000)

ops$tkyte@ORA920> select rowid from t where y = 1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1000 Bytes=30000)
   1    0   INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=1000
Bytes=30000)

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace off
ops$tkyte@ORA920>
Rating: 5
what does set do? August 24, 2003
Reviewer: Reader
ops$tkyte@ORA920> exec dbms_stats.set_table_stats( user, 'T', numrows => 100000
);

Tom, what does set_table_stats do? Thanks.
Followup:
sets the stats.  i told the optimizer "please trust me, there are 100,000 rows
in this table"
Rating: 5
set stats August 24, 2003
Reviewer: Reader
Is it used to study the execution plan for certain number of rows even though
the table is empty? Thanks.
Followup:
it can be.

it can be used when you know the stats already (instead of analyzing to save
time)
Rating: 5
Size for IOT October 16, 2003
Reviewer: A reader
Hi Tom,

I am considering converting some "big and lean" tables of more than 10000 rows
to IOT and I found it helps the performance a lot.

In your opinion, do you think it makes sense to convert tables of less than
10000 rows to IOT? Will it give me much benefit, compared to using heap table
with indexes?

Thanks.
Followup:
yes, definitely.  you could end up doing 1/3 the logical IO on a lookup.  If you
do that lookup lots -- 100 rows, 10,000 rows -- doesn't really matter.
Rating: 5
COALESCE vs MOVE ONLINE October 30, 2003
Reviewer: Jan from Europe
1)When to use ALTER iot_table COALESCE and when ALTER iot_table MOVE ONLINE? Is
there some circumstance when using of COALESCE is reasonable?

2) If there is an index on iot_table and we do MOVE ONLINE do we need to rebuild
also that index?

Thank you, Jan
Followup:
1) use coalesce to "compact" the iot.

use move to MOVE it. total rebuild.  2x the space.

2) indexes on iots are based on primary keys -- the move doesn't change the key,
so no.

ops$tkyte@ORA920LAP> create table heap( x int primary key, y int );

Table created.

ops$tkyte@ORA920LAP> create index heap_idx on heap(y);

Index created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create table iot( x int primary key, y int ) organization
index;

Table created.

ops$tkyte@ORA920LAP> create index iot_idx on iot(y);

Index created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> insert into heap select rownum, rownum from all_objects;

30694 rows created.

ops$tkyte@ORA920LAP> insert into iot  select rownum, rownum from all_objects;

30694 rows created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> analyze table heap compute statistics for table;

Table analyzed.

ops$tkyte@ORA920LAP> analyze table iot  compute statistics for table;

Table analyzed.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> set autotrace on explain
ops$tkyte@ORA920LAP> select * from heap where y = 55;

         X          Y
---------- ----------
        55         55

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=307 Bytes=3684)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'HEAP' (Cost=2 Card=307 Bytes=3684)
   2    1     INDEX (RANGE SCAN) OF 'HEAP_IDX' (NON-UNIQUE) (Cost=1 Card=123)

ops$tkyte@ORA920LAP> select * from iot  where y = 55;

         X          Y
---------- ----------
        55         55

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=307 Bytes=4298)
   1    0   INDEX (RANGE SCAN) OF 'IOT_IDX' (NON-UNIQUE) (Cost=1 Card=307
Bytes=4298)

ops$tkyte@ORA920LAP> set autotrace off
ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> alter table heap move;

Table altered.

ops$tkyte@ORA920LAP> alter table iot  move online;

Table altered.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> set autotrace on explain
ops$tkyte@ORA920LAP> select * from heap where y = 55;
select * from heap where y = 55
*
ERROR at line 1:
ORA-01502: index 'OPS$TKYTE.HEAP_IDX' or partition of such index is in unusable
state

ops$tkyte@ORA920LAP> select * from iot  where y = 55;

         X          Y
---------- ----------
        55         55

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=307 Bytes=4298)
   1    0   INDEX (RANGE SCAN) OF 'IOT_IDX' (NON-UNIQUE) (Cost=1 Card=307
Bytes=4298)

ops$tkyte@ORA920LAP> set autotrace off

the heap table needs to have indexes rebuilt (rowids changed) the iot does not
Rating: 3
Index with Large Block size December 26, 2003
Reviewer: Jagjeet Singh from INDIA
Hi,

SQL> Select Tablespace_name,block_size from Dba_Tablespaces
  2  /

TABLESPACE_NAME                BLOCK_SIZE
------------------------------ ----------
....
TEST                                 2048
SIXTEEN_K                           16384

6 rows selected.

SQL> Alter user j default tablespace test;

SQL> create table a(  a  varchar2(700) primary key )
     organization index;

Table created.

SQL> alter table a modify (a   varchar2(4000));
alter table a modify (a   varchar2(4000))
                      *
ERROR at line 1:
ORA-01404: ALTER COLUMN will make an index too large  <<--

SQL> Select index_name,table_name,tablespace_name from user_indexes;

INDEX_NAME         TABLE_NAME  TABLESPACE_NAME
------------------ ----------- ----------------
SYS_IOT_TOP_25958  A           TEST     <<--- 2k Tablespace

SQL> Alter table a move tablespace sixteen_k;

Table altered.

SQL> select index_name,table_name,tablespace_name from user_indexes ;

INDEX_NAME        TABLE_NAME TABLESPACE_NAME
----------------- ---------- ---------------
SYS_IOT_TOP_25958 A          SIXTEEN_K

SQL> alter table a modify (a   varchar2(4000));

Table altered.

SQL>
SQL> alter table a move tablespace test ;   <<--- Moving back in 2k

Table altered.

SQL> select index_name,table_name,tablespace_name from user_indexes ;

INDEX_NAME        TABLE_NAME TABLESPACE_NAME
----------------- ---------- ---------------
SYS_IOT_TOP_25958 A          TEST   ??????

how can an index can have a 4000+ bytes within 2k block size
for a single entry.
Followup:
looks like a bug, they forgot to check.  if the table had rows that exceeded the
max size, you would have gotten:

alter table t move tablespace ts_2k
            *
ERROR at line 1:
ORA-00600: internal error code, arguments: [17023], [0x5CC5E94C], [], [], [],
[], [], []

if the alter move succeeds, you will get:

insert into t values ( rpad('*',4000,'*') )
            *
ERROR at line 1:
ORA-00600: internal error code, arguments: [kdisle:nrows], [1], [], [], [], [],
[], []

upon trying to insert the too large row.

There is no data corruption or anything with this -- just a missed check before
moving.  I've file a bug against this issue for you with this testcase:

create tablespace ts_2k datafile size 1m blocksize 2k;
create tablespace ts_16k datafile size 1m blocksize 16k;

                               create table t ( x varchar2(500) primary key )
organization index tablespace ts_2k;

                               alter table t modify x varchar2(4000);

                               alter table t move tablespace ts_16k;

                               alter table t modify x varchar2(4000);

                               insert into t values ( rpad('*',4000,'*') );
alter table t move tablespace ts_2k;
delete from t;
alter table t move tablespace ts_2k;
insert into t values ( rpad('*',4000,'*') );
December 26, 2003 Krishnamoorthy -- Thanks for the question regarding "Database users & Partitions", version 9.0.2
You Asked
Hi Tom,

We are working on finanical package , where the number of users are 600. We have
created only user at the database and the other users are at the application
level. So when ever the application is accessed by the different application
users, many sessions shall be created for the same user. Now we are planning to
replace the application users by database users. what would be the impact of
this in the application. What are the things i suppose to take care when i
change like this.

I read the chapter "KNOW WHEN TO USE PATIIONING" from your book "EFFECTIVE
ORACLE BY DESIGN". Really it was depicted very nicely the practical problems.Is
there any minimum limit on the size of parition?. For example the partitions are
required when the table volume touches 1 GB etc.,

Regards
K.Sankar
and we said...
I can think of nothing in general to "watch out for" -- other then the obvious
impact to your software design.

You should strive to grant AS LITTLE AS HUMANLY POSSIBLE to your database users.
 Use well defined and well designed roles.  Do not overgrant.  Do not use
connect, resource, dba -- create your own very custom roles.

Partitioning is not related to "size".
Partitioning is related to "goals", eg:

o need partition elimination in my data warehouse
o need to do rolling windows for purges with least amount of effort
o need to make this really large (large is in the eye of the beholder) table
  more manageable

size is not really the factor.
Review & Followup

Rating: 4
Partition December 28, 2003
Reviewer: Sankar from Malaysia
The reply is very useful.Thank a ton
December 26, 2003 Chris -- Thanks for the question regarding "Calculating hours worked between two dates", version 9.2.0.4
You Asked
Hi Tom,
I am trying to calculate the hours a person may work between two dates.
For example we have an employee that starts work at 8.00 and ends work at 17:00.
He works Monday to Friday.
He may start to do a job at 11:40 on Thursday and finishes at 14:00 the
following Monday. I want to be able to calculate the hours he spent doing that
particular job.

What's the best way of working it out?
and we said...
well, here is a SQL and a PLSQL based approach -- test them each out and see
which is "best" for you.

I'm using table T as a "parameter table" here -- you could of course just use
binds in the query instead of joining to T.

ops$tkyte@ORA9IR2> create table t ( stime date, etime date, clockin number,
clockout number );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t values ( to_date( '18-dec-2003 11:40',
'dd-mon-yyyy hh24:mi'),
  2                         to_date( '22-dec-2003 14:00', 'dd-mon-yyyy
hh24:mi'),
  3                         8, 17 );

1 row created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> @bigdate
ops$tkyte@ORA9IR2> alter session set nls_date_format = 'dd-mon-yyyy hh24:mi:ss';

Session altered.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select r,
  2         greatest( stime, trunc(stime)+clockin/24+r ),
  3         least( trunc(stime)+r+clockout/24, etime ),
  4         (least( trunc(stime)+r+clockout/24, etime )-greatest( stime,
trunc(stime)+clockin/24+r ))*24 elap
  5    from t,
  6         (select rownum-1 r
  7            from all_objects
  8           where rownum <= (select trunc(etime)-trunc(stime)+1
  9                              from t) )
 10   where to_char(stime+r,'Dy') not in ( 'Sat', 'Sun' )
 11  /

         R GREATEST(STIME,TRUNC LEAST(TRUNC(STIME)+R       ELAP
---------- -------------------- -------------------- ----------
         0 18-dec-2003 11:40:00 18-dec-2003 17:00:00 5.33333333
         1 19-dec-2003 08:00:00 19-dec-2003 17:00:00          9
         4 22-dec-2003 08:00:00 22-dec-2003 14:00:00          6

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select sum(least( trunc(stime)+r+clockout/24, etime
)-greatest( stime, trunc(stime)+clockin/24+r )) * 24
  2    from t,
  3         (select rownum-1 r
  4            from all_objects
  5           where rownum <= (select trunc(etime)-trunc(stime)+1
  6                              from t) )
  7   where to_char(stime+r,'Dy') not in ( 'Sat', 'Sun' )
  8  /

SUM(LEAST(TRUNC(STIME)+R+CLOCKOUT/24,ETIME)-GREATEST(STIME,TRUNC(STIME)+CLOCKIN/2
4+R))*24
---------------------------------------------------------------------------------
--------

20.3333333

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace
  2  function hours_worked( p_stime in date, p_etime in date, p_clockin number,
p_clockout number, p_ignore_days in varchar2 )
  3  return number
  4  as
  5      l_ignore_days long := '/'||upper(p_ignore_days)||'/';
  6      l_tot number := 0;
  7  begin
  8      for i in 0 .. trunc(p_etime)-trunc(p_stime)
  9      loop
 10          if ( instr( l_ignore_days, '/'||to_char(p_stime+i,'DY')||'/' ) = 0
)
 11          then
 12              l_tot := l_tot + least( trunc(p_stime)+i+p_clockout/24, p_etime
)-greatest( p_stime, trunc(p_stime)+p_clockin/24+i );
 13          end if;
 14      end loop;
 15      return l_tot*24;
 16  end;
 17  /

Function created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select hours_worked( stime, etime, clockin, clockout,
'Sat/Sun' )
  2    from t;

HOURS_WORKED(STIME,ETIME,CLOCKIN,CLOCKOUT,'SAT/SUN')
----------------------------------------------------
                                          20.3333333
Review & Followup

Rating: 5
Great solution December 26, 2003
Reviewer: Chris from Australia
Hi Tom,
I love the SQL solution, thanks a lot.
Rating: 3
Great!! December 27, 2003
Reviewer: Sanjay Raj from USA
The SQL solution is great!!
December 26, 2003 raghu -- Thanks for the question regarding "paging through results on the web.", version
You Asked
I have a Search field in my form. When my user enters a
value in the Search field, I want to display the first 10 rows
which match the query, then the next 10..and so on. I will give
him 2 button (Previous 10 & Next 10) options. How do I go about it?
and we said...
You don't mention what the user interface is built using.  I'll assume the WEB
then and that state is not maintained between pages.  There are a couple of
methods.

o Lets say the query is something like "select * from T where x = :host_variable
order by y" and the query is relatively 'fast'.  I can then write a query:

select *
  from ( select a.*, rownum r
           from ( select *
                    from t
                   where x = :host_variable
                   order by y ) a
          where rownum < :HigerBound )
 where r > :LowerBound
/

That query gets all of the rows between LowerBound and HigherBound from the
ordered result set in the innermost query.  This only works with Oracle8i,
release 8.1 and up (order by in subqueries not recognized until then).

o Lets say the query is like the above however it takes a 'long' time to
execute.  I would set up a 'hits' table like the following:

create table hits( sess_id number, seqno number, rid rowid, primary
key(sess_id,seqno) );

Then, when I wanted to search I would code something like:

   cnt := 0;
   for y in ( select t.rowid rid from t where x = :host_variable order by y )
   loop
      cnt := cnt + 1;
      insert into hits values ( userenv('sessionid'), cnt, y.rid );
   end loop;

You have to pass that userenv('sessionid') from page to page as a hidden
variable then to identify the result set.

And to display results from this i would query:

   select * from t, hits
    where hits.seqno between :lowBound and :highBound
      and hits.sess_id = :theSessionId
   order by hits.sess_id, hits.seqno

Normally I keep another 'parent' table to go along with hits and I store the
session id, the TIME of the query and such in there as well.  I put a foreign
key from hits back to this parent with ON DELETE CASCADE and run a job
(scheduled via dbms_jobs) to issue "delete from parent_table where time <
sysdate-1/24" -- that cleans out queries older then 1 hour.  Occasionally, i
truncate the table to lower the high water mark as well.

followup to comment one below

the rowid is simply a primary key.  Use the true primary key instead.  Lets say
you needed to do this for EMP and DEPT, you might do:

  insert into hits
  select SESSION_ID, rownum, empno, deptno
    from ( select ename, dname
             from emp, dept
            where emp.deptno = dept.deptno
            order by emp.ename );

Now, to display rows 100 - 110 you would:

  select emp.ename, dept.dname
    from hits, emp, dept
   where emp.empno = hits.empno
     and dept.deptno = hits.deptno
     and sequence_col between :min and :max
     and session_id = :session_id;

You can use the rowids of the underlying tables as well.
Review & Followup

Rating: 3
Implementing Page wise Concept in web based technology May 28, 2001
Reviewer: Shiva Ram Krishna Sistla from INDIA
Dear Sir,
 Initially i tried with 'from select' option of yours, but that query as you
said is a performance issue. ours is a financial project, where we using complex
queries to retrieve data. Then i have gone for implementing ur second method
that is hits table method. because of my complex queries i am not able to
retrieve rowid field. Generally how do we go about retrieving Complex queries
data, is there anyother method. Sir i have 'Previous', 'Next' and 'No. of
records' button. if u want i can send my queries also. Thanks for ur help in
advance. I require the solution very badly sir..
Rating: 4
June 18, 2001
Reviewer: Still Study from USA
Rating: 5
Extremely helpful hint October 05, 2001
Reviewer: Phil Wheeler from Cambridge England
The key for me was the use of 2 levels of subquery, and renaming the rownum
pseudocolumn. (select a.*, rownum r...)

This approach has led to an effective paging mechanism for my ASP application.
The performance far exceeds an ASP-only approach, as only the rows of interest
are transferred to the web server.

Without this article, I would not have hit upon the correct SQL to make this
work, AND to retain the sort order. Thanks very much!
Rating: 4
Can it be improved with temporary tables so that no archive logs are generated? January 09, 2002
Reviewer: Mihail Daskalov from Sofia, Bulgaria
Hi,
the second method with a table hits is what I like to use.
Can it somehow be implemented so that inserting into table hits doesn't generate
redo logs.

Can it be used together with global temporary tables in Oracle 8.1.7 and
mod_plsql. How can the session be preserved so that temporary table contents are
not lost between clicks.

What will be the pros and cons of such a method.

Is it better to do it with Java Servlets with scrollable JDBC result sets?

Thanks a lot for all of your helpful responses.
Followup:
temporary tables last for the life of a session.  This solution is geared
towards the stateless web environment where a session comes and goes.  A
temporary table would be very inappropriate.

I would use a stateless environment personally with the "real" table.  Frankly I
find the times when I need to save the result set very very few and far between.
 On asktom here for example, when you page - I rerun the query.  A search query
should be optimized to find the first rows fast -- then you need not save
anything.

I don't like having a state maintained in the middle tier.  It kills
scalability.  I can do more on this small machine that I have without state
management then you would need to preserve that state and use java.
Rating: 5
Using session can be a bug. January 18, 2002
Reviewer: Min-Koo, Seo. from Republic of KOREA
Primary key of the table is 'SESSION_ID, SEQ'.
I think this could leads to some bug.

Let me see.
User 'A' log in and did some query and log-off.
User 'B' log in and do some query.

What happens when the session ID of User 'A' and User 'B'
is equal?

We use DBMS_JOB to erase data which was populated 1 day
ago. So, if same SESSION_ID is generated by WEB-SERVER
with in a day, User 'B' will not be able to do query,
because of the primary key constraints.

I think the page(jsp or asp or whatever), should include
some logic to erase the data in the table.
Followup:
how would the sessionid's be the same?  userenv('sessionid') is fed off of a
sequence.  They won't be the same.

I use this, I've used it, I will continue to use it -- it works.

There is NO way a page (jsp/asp or whatever) could include logic to "erase" it.
You would defeat the entire purpose -- to cache the answer and be able to
paginate rapidly.  If you had a page that erased it -- you would lose your
answer -- then what?  You cannot RELY on a page erasing it after you are done --
there is nothing stopping me from jumping right out of your application and
never getting to the "erase data" page.

Just use a unique sessionid -- very very very common in this web based
environment.  If you are using a servlet solution -- they use sessionids as
well, typically in a  cookie.  You can use your own sequence, or you can use
userenv('sessionid') (if you create a session/page as many solutions do)
Rating: 5
Very interesting January 18, 2002
Reviewer: Yogeeraj from Mauritius
Can anyone just guide me through the process of creating the html page and
deploy it on my web/application server?

i am using 9iAS, Oracle 8.1.7 on a Unix Box

thanks
yogeeraj
Rating: 5
record paging January 20, 2002
Reviewer: Mo from Reston, VA
Tom:

1. How do you define :min and :max?  Do you pre-define it in the Pl/SQL program
if we are doing html/plsql.

2.  Also, I have a problem printing a question answer when viewing each week's
results.  It seems to print all answers.  ANy idea on only to print selected
question page?

Thank you,
Followup:
1) :min and :max are just bind variables, if it were a plsql procedure,  they
would just be parameters to the procedure:

create procedure p ( p_min in number, p_max in number, p_session_id in number )
as
begin
for x in (  select emp.ename, dept.dname
    from hits, emp, dept
   where emp.empno = hits.empno
     and dept.deptno = hits.deptno
     and sequence_col between P_MIN and P_MAX
     and session_id = P_SESSION_ID )
loop
......

2) well, I didn't really have that feature -- it was just one big page.  So, I
added a link "article only" after the date in the list.   That'll take you to a
specific page just for that article.
Rating: 4
Implement Paging February 27, 2002
Reviewer: Amarjeet Singh
The following sql was very helpful:

select *
  from ( select a.*, rownum r
           from ( select *
                    from t
                   where x = :host_variable
                   order by y ) a
          where rownum < :HigerBound )
 where r > :LowerBound

It works fine for smaller tables and where the 'order by' clause has a numeric
field. One of my table has 120,000 records. I have to sort (order by) that table
by a column, service_name( varchar2(20)) and then display the page with few
records.  It takes more then 1 minute in my localhost environment to execute
that query.  Would some one can suggest me a faster way of sorting if the table
field is not numeric.
Followup:
It works great -- if the rows from the inner most query can come back quickly.
It matters not if the datatype of the column in the order by is a number,
date, string, whatever.  If the proper indexes are in place and the first row
of the inner most query can be returned before the last row is figured out --
it'll be fast.

Start with a table like this:

create table t as select * from all_objects;
insert /*+ append */ into t select * from t;
commit;
...
insert /*+ append */ into t select * from t;
commit;

Now run:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from t
  2  /

  COUNT(*)
----------
    274704
ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_owner_oname_idx on
t(owner,object_name)
  2  /
ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics
  2  /
ops$tkyte@ORA817DEV.US.ORACLE.COM>

Now, lets get the "first" page and the "last" page and see the time to get each:

ops$tkyte@ORA817DEV.US.ORACLE.COM> variable high number
ops$tkyte@ORA817DEV.US.ORACLE.COM> variable low  number
ops$tkyte@ORA817DEV.US.ORACLE.COM> variable host_variable varchar2(30)

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec :high := 10; :low := 1; :host_variable
:= 'SYS'

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> set timing on
ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2    from ( select a.*, rownum r
  3             from ( select /*+ FIRST_ROWS */ owner, object_name, created
  4                      from t
  5                     where owner = :host_variable
  6                     order by object_name ) a
  7            where rownum < :High )
  8   where r > :Low
  9  /

OWNER      OBJECT_NAME                    CREATED            R
---------- ------------------------------ --------- ----------
SYS        /1005bd30_LnkdConstant         28-AUG-01          2
SYS        /1005bd30_LnkdConstant         28-AUG-01          3
SYS        /1005bd30_LnkdConstant         28-AUG-01          4
SYS        /1005bd30_LnkdConstant         28-AUG-01          5
SYS        /1005bd30_LnkdConstant         28-AUG-01          6
SYS        /1005bd30_LnkdConstant         28-AUG-01          7
SYS        /1005bd30_LnkdConstant         28-AUG-01          8
SYS        /1005bd30_LnkdConstant         28-AUG-01          9

8 rows selected.

Elapsed: 00:00:00.01

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=272809 Card=10989
   1    0   VIEW (Cost=272809 Card=10989 Bytes=615384)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=272809 Card=10989 Bytes=472527)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=272809 Card=10989
   5    4           INDEX (FULL SCAN) OF 'T_IDX1' (NON-UNIQUE) (Cost=1364

ops$tkyte@ORA817DEV.US.ORACLE.COM> set timing off
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec :high := 100010; :low := 100000;
:host_variable := 'SYS'

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set timing on
ops$tkyte@ORA817DEV.US.ORACLE.COM> /

OWNER      OBJECT_NAME                    CREATED            R
---------- ------------------------------ --------- ----------
SYS        V_$HS_PARAMETER                28-AUG-01     100001
SYS        V_$HS_PARAMETER                28-AUG-01     100002
SYS        V_$HS_PARAMETER                28-AUG-01     100003
SYS        V_$HS_PARAMETER                28-AUG-01     100004
SYS        V_$HS_PARAMETER                28-AUG-01     100005
SYS        V_$HS_PARAMETER                28-AUG-01     100006
SYS        V_$HS_PARAMETER                28-AUG-01     100007
SYS        V_$HS_PARAMETER                28-AUG-01     100008
SYS        V_$HS_PARAMETER                28-AUG-01     100009

9 rows selected.

Elapsed: 00:00:06.68

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=272809 Card=10989
   1    0   VIEW (Cost=272809 Card=10989 Bytes=615384)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=272809 Card=10989 Bytes=472527)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=272809 Card=10989
   5    4           INDEX (FULL SCAN) OF 'T_IDX1' (NON-UNIQUE) (Cost=1364

ops$tkyte@ORA817DEV.US.ORACLE.COM> set timing off
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off

so, as you can see -- if you get the query such that the first row can be
returned fast -- as in this exmaple -- it'll be great.  If we have to find the
entire result set (140,000 rows were SYS owned in my example here) AND THEN
sort, it'll obviously take a bit.

Proper indexing -- the judicious use of hints (this is one time where a hint
might be called for -- to say "i want the first rows fastest" due to the nature
of what you are doing) will make this fast.
Rating: 4
Unique seeion id July 09, 2002
Reviewer: Vikas Sharma from Delhi India
Hi, Tom
I am using mod_plsql in my web application of 8.1.7.0. Kindly clear me the
following.
When I call my search page. Which is called by apache http server, the
userenv(‘sessionid’) or sys_context(‘userenv’,’sessionid’) value  can be same
for two users. So if the value of the host variable (search criteria) in select
statement is different then in my insert (insert into hits values (
userenv('sessionid'), cnt, y.rid ) the session id will be same but the rows will
be inserted for both search criteria. Which will cause non desirable result for
search when paging.

As I know the when apache sends the first request to  database it gets a session
id. And for a stateless application the session status becomes INACTIVE when
your request is over. And for any other request the by any other user(not oracle
user, I mean another user’s request with same dad) session id will be same till
this session exists in v$session. In the case of ASP or JSP every time you open
a new window you get a unique session id unlike this case.
Followup:
Then don't use userenv('sessionid').

create a sequence and use select seq.nextval from dual;
Rating: 5
How to get the total count July 18, 2002
Reviewer: Bala
Hi Tom,

I am using your first example,
and i want to return the total count also.
I am using a function that returns a ref cursor.

Thanks
Followup:
the only way to get the total count is to actually fetch the last row.  No one
knows (not even Oracle) how many rows will be fetched until it actually fetches
the last row.
Rating: 5
Sorry for asking that question July 18, 2002
Reviewer: Bala
Hi Tom
I have gone through your other discussions about
doing count on the resultset.
I understand now, why its not a good idea...

Thanks
Rating: 5
We will need N number of hits table, if we are having N number of searches January 21, 2003
Reviewer: Siva from NJ, USA
Paging seems to be one of the major issues in all Web apps.

To make sure, we understand correctly.

We will need as many hit tables as there are searches.

The following hits table is for one search.

 insert into hits
  select SESSION_ID, rownum, empno, deptno
    from ( select ename, dname
             from emp, dept
            where emp.deptno = dept.deptno
            order by emp.ename );

If we have another search, we will need another hits table.
Followup:
only if you actually need to materialize the results into a table -- 99% or more
you shouldn't -- if it takes a long time to get that first page, most of the
times you would not have been using that in an interactive application anyway.
Rating: 5
We will need N number of hits table, if we are having N number of searches January 21, 2003
Reviewer: Siva from NJ, USA
Paging seems to be one of the major issues in all Web apps.

To make sure, we understand correctly.

We will need as many hit tables as there are searches.

The following hits table is for one search.

 insert into hits
  select SESSION_ID, rownum, empno, deptno
    from ( select ename, dname
             from emp, dept
            where emp.deptno = dept.deptno
            order by emp.ename );

If we have another search, we will need another hits table.
Rating: 5
paging January 21, 2003
Reviewer: mo
Tom:

1. Can you implement method 1 for ref cursor. E.G.

l_query = 'select * from emp order by empno'

Do you append the string as :
l_query :=
select * from (select a.*,rownum r from ('||l_query||')a
where rownum < :highbound ) where r > lowerbound '

open l_cursor for l_query;

2. For a table of 3000 records would you use the method or you would still check
how fast query us running. It is strange that inline views would be slower than
two table join?

Thank you,
Followup:
1) it works for many queries -- yes.  Consider:

scott@ORA920> variable x refcursor
scott@ORA920>
scott@ORA920> begin
  2          open :x for
  3          'select *
  4             from ( select a.*, rownum r
  5                      from ( SELECT * FROM EMP ORDER BY EMPNO ) a
  6                             where rownum <= 5 )
  7            where r >= 1';
  8  end;
  9  /

PL/SQL procedure successfully completed.

scott@ORA920> begin
  2          open :x for
  3          'select *
  4             from ( select a.*, rownum r
  5                                  from ( SELECT * FROM EMP, DEPT ) a
  6                             where rownum <= 5 )
  7            where r >= 1';
  8  end;
  9  /
begin
*
ERROR at line 1:
ORA-00918: column ambiguously defined
ORA-06512: at line 2

If the query could have been used to create a view, this technique will work --
else the original query MUST have uniquely named column names....

2) does not compute -- don't understand what you are asking.  what is the
relationship between an inline view (which could have a two table join), two
table join (which could itself be an inline view), and 3,000 records (which is
nothing)
Rating: 5
paging January 22, 2003
Reviewer: mo
Tom:

Do you know why I can not  access the rownum 'R' column within the loop? The
l_next statement keeps creating an error that it does not recognize r. I see it
in sql*plus fine.

 l_query := 'select * from (select a.*,rownum r from ('||l_query||') a
               where rownum <= '||l_total||' ) where r >= '||p_start  ;

 htp.p('p_start = '||p_start);

  OPEN l_cursor for l_query;
  LOOP
     FETCH l_cursor into l_rec;
     exit when l_cursor%notfound;
     l_next := l_rec.r =  p_start+l_pagesize;

2. What I meant before is that why would select * from (select * from ) be
slower than creating a hits table and loading it with records and making a join.
It seems method 2 is more overhead but you say it is faster.

THank you,
Followup:
1) umm, look at your definition of l_rec I guess.  Must not have R in it.

ops$tkyte@ORA920> declare
  2          l_rec emp%rowtype;
  3          l_query varchar2(255) := 'select emp.*, rownum r from emp';
  4          l_rc    sys_refcursor;
  5  begin
  6          open l_rc for l_query;
  7          fetch l_rc into l_rec;
  8          dbms_output.put_line( l_rec.ename );
  9  end;
 10  /
SMITH

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> declare
  2          l_rec emp%rowtype;
  3          l_query varchar2(255) := 'select emp.*, rownum r from emp';
  4          l_rc    sys_refcursor;
  5  begin
  6          open l_rc for l_query;
  7          fetch l_rc into l_rec;
  8          dbms_output.put_line( l_rec.r );
  9  end;
 10  /
        dbms_output.put_line( l_rec.r );
                                    *
ERROR at line 8:
ORA-06550: line 8, column 30:
PLS-00302: component 'R' must be declared
ORA-06550: line 8, column 2:
PL/SQL: Statement ignored

(this seems to be an exercise in programming??)

2) re-read the answer.  I said:

o Lets say the query is like the above however it takes a 'long' time to
execute.  I would set up a 'hits' table like the following:

the query takes a long time to execute.  You only want to run it once -- not
once per page.  different strokes for different folks.
Rating: 5
The Second Option February 03, 2003
Reviewer: Siva from NJ USA
The first option solves most of the problem.

The second option, I do not understand your earlier follow up to my query.

--------------------------------
To make sure, we understand correctly.

We will need as many hit tables as there are searches.

The following hits table is for one search.

 insert into hits
  select SESSION_ID, rownum, empno, deptno
    from ( select ename, dname
             from emp, dept
            where emp.deptno = dept.deptno
            order by emp.ename );

If we have another search, we will need another hits table.

Followup:
only if you actually need to materialize the results into a table -- 99% or more

you shouldn't -- if it takes a long time to get that first page, most of the
times you would not have been using that in an interactive application anyway.
----------------------------------

To me , the second option can be implemented only by materializing the primary
key of the result. How do you implement the second option without materializing
the primary key.

Siva
Followup:
that is what i did -- empno, deptno = primary keys of emp and dept.  i should
have had empno and deptno in the inline view -- that is an oversight.
Rating: 4
paging through results on the web March 13, 2003
Reviewer: jose antonio morcillo from SPAIN
very useful method of paging
Rating: 5
March 27, 2003
Reviewer: A reader
o Lets say the query is something like "select * from T where x = :host_variable

order by y" and the query is relatively 'fast'.  I can then write a query:

select *
  from ( select a.*, rownum r
           from ( select *
                    from t
                   where x = :host_variable
                   order by y ) a   <<<=====
          where rownum < :HigerBound )
 where r > :LowerBound
/

/***************************************/

hi tom, when ever I want the result set ordered by,
we really need to operate on the whole result set?

 because if i am browsing the data page by page it is
most likely that I will not go at the end of the page like
google so I don't want to fetch 1m-2m search result
just show 1st 50 (but those must be sorted !!) so
just to sort DO I HAVE TO fetch 1m-2m where my user is looking prob. 1st 20-25
results ??

TIA
Followup:
didn't you see the "and the query is relatively fast"

I'm assuming that perhaps the query uses an index on T(x,y).  that'll be "fast"
to get the first rows via the index.

Also, Oracle has TOP-N optimizations in it (9i and up) whereby it doesn't build
a 1,000,000 row result set AND THEN return the first 10.  It just builds the
first 10 and returns them -- as long as you write the query I as I describe
above anyway.
Rating: 5
how ? March 27, 2003
Reviewer: A reader
if you don't fatch it how would you sort it ?
and give 1st 10-result sorted by name_key ?

please explain this technique !!
Followup:
you run a top N query:

select *
  from ( select ...
           from ...
          where ...
          order by ... )
 where rownum <= :n;

Oracle sees that and say "ah hah, only :n rows will be returned". I'll therefore
just set up (this is conceptual remember) an array of :N in size.

Then, I'll start running the query (without the order by).  The first :n rows
will be sorted into this array. when I get the :N+1st row -- I'll see if it is
less then the :Nth row -- if so, I'll put it in the array and bump one of the
existing rows out.  Repeat this process for the rest of the rows.

So, instead of sorting and storing 1,000,000 rows -- we store :n rows and only
keep those that "fit in there".
Rating: 5
March 27, 2003
Reviewer: A reader
thanks tom,
                       (N-:N)
1.)  so are we doing (n)   comparisons/page  ?
2.)  are we doing it  for each  page(of say 20 records) ?
Followup:
1) no.  say you had 100 rows.  You would get the first 10 and sort them.  Then
the next 90 would come and if all of them were > the last row -- done.

2) obviously -- each time you run the query, the answer could be different.

Tell you what, rather then trying to talk yourself out of it -- benchmark it,
apply the technique, observe the results.  Reality counts, hypothetical doesn't.
 You might like it sometimes, you might not like it others.  It is NOT a black
and white decision.
Rating: 5
March 27, 2003
Reviewer: A reader
1) no.  say you had 100 rows.  You would get the first 10 and sort them.

" Then the next 90 would come "

 and if all of them were > the last row -- done.

/************************************************/

so after getting 1st 10 rows and sotring them
we do have to compare the last row with rest of the
rows (90 rows) right ?

 how would u compare it without fethcing it ?
Followup:
the server is doing all of this work for us.

Consider (this is 9i functionality here)

ops$tkyte@ORA920> create table t ( object_name char(2000), object_id number,
owner varchar2(30) );
Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert /*+ append */ into t
  2  select object_name, object_id, owner
  3    from all_objects
  4   where rownum < 1001;
1000 rows created.

ops$tkyte@ORA920> commit;
Commit complete.

ops$tkyte@ORA920> alter session set sql_trace=true;
Session altered.

...
 10      for i in 1 .. 100
 11      loop
 12          for x in ( select *
 13                        from ( select * from t order by object_name )
 14                       where rownum <= 10 )
 15          loop
 16              null;
 17          end loop;
 18      end loop;
...
 24      for i in 1 .. 100
 25      loop
 26                  l_cnt := 0;
 27          for x in ( select * from t order by object_name )
 28          loop
 29              l_cnt := l_cnt+1;
 30              exit when l_cnt >= 10;
 31          end loop;
 32      end loop;
.....

and tkprof says:

select *
                      from ( select * from t order by object_name )
                     where rownum <= 10

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute    100      0.00       0.01          0          0          0           0
Fetch     1100      1.74       1.81      33301      33803          1        1000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1201      1.75       1.83      33301      33803          1        1000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 309     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
   1000  COUNT STOPKEY
   1000   VIEW
   1000    SORT ORDER BY STOPKEY
 100000     TABLE ACCESS FULL T

that step is the "magic" here..

select *
from
 t order by object_name

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute    200      0.04       0.04          0          0          0           0
Fetch     1000      5.81      16.66      34091      33800        200        1000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1201      5.86      16.71      34091      33800        200        1000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 309     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
   1000  SORT ORDER BY
 100000   TABLE ACCESS FULL T
Rating: 5
Thanks, March 28, 2003
Reviewer: A reader
good moring tom,

thanks for your proof by example... :)

but, it is still magic for me do you
or matalink have the algorithm ?

I am on 8.1.7 (linux 73)

Thanks,
Followup:
have what algorithm?
Rating: 5
March 28, 2003
Reviewer: A reader
that does

select * from ( select * from t order by object_name )
                    where rownum <= 10
faster

AND

 select * from t order by object_name
slower ?
Followup:
they are not faster nor slower -- they are different??

Above I explained conceptually what is happening, right below that --
benchmarked it.

If the goal is the get the first 10 rows AFTER sorting -- rownum is superior.
it tells the db -- Hey, only interested in 10 rows.  so the db only generates
those 10 rows for us, much more efficient.
Rating: 5
cool March 28, 2003
Reviewer: A reader
thanks tom
Rating: 5
Smart Sort March 28, 2003
Reviewer: Logan Palanisamy from Santa Clara CA USA
Here is further proof that, it is actually sorting only the the firtst N (e.g
10, 100, 150) records, replacing (1000 - N) as they are retrieved to get the top

N records

  1* select * from (select * from t order by object_name) where rownum < 10
SQL> /

9 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   COUNT (STOPKEY)
   2    1     VIEW
   3    2       SORT (ORDER BY STOPKEY)
   4    3         TABLE ACCESS (FULL) OF 'T'

Statistics
----------------------------------------------------------
          0  recursive calls
         18  db block gets
        340  consistent gets
        334  physical reads
          0  redo size
      18932  bytes sent via SQL*Net to client
        424  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          9  rows processed

SQL> c/10/100/
  1* select * from (select * from t order by object_name) where rownum < 100
SQL> /

99 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   COUNT (STOPKEY)
   2    1     VIEW
   3    2       SORT (ORDER BY STOPKEY)
   4    3         TABLE ACCESS (FULL) OF 'T'

Statistics
----------------------------------------------------------
          0  recursive calls
         18  db block gets
        340  consistent gets
        334  physical reads
          0  redo size
     204516  bytes sent via SQL*Net to client
       1090  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         99  rows processed

SQL> c/100/150/
  1* select * from (select * from t order by object_name) where rownum < 150
SQL> /

149 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   COUNT (STOPKEY)
   2    1     VIEW
   3    2       SORT (ORDER BY STOPKEY)
   4    3         TABLE ACCESS (FULL) OF 'T'

Statistics
----------------------------------------------------------
          0  recursive calls
         20  db block gets
        340  consistent gets
        372  physical reads
          0  redo size
     307579  bytes sent via SQL*Net to client
       1423  bytes received via SQL*Net from client
         11  SQL*Net roundtrips to/from client
          0  sorts (memory)
          1  sorts (disk)
        149  rows processed

If all the 1000 records were sorted, all the three cases should have been "disk"

sorts. In the examples above, only when I want the TOP150, it does disk sort.
When I want top 10 or 100, it does memory sort proving that it is sorting the
first 10 or 100, replacing the sorted list with the remaining only when they are

greater.
Rating: 4
April 07, 2003
Reviewer: A reader
I have a similar paging query. But I need to get row count also to show the
number of available records (like page 1-25). User can change page size (the
following query shows 30) and sort column name (the example query used column 1,
user can change it to any column).

I used the following query to get my results.

SELECT * FROM (  SELECT Z.*,  RANK() OVER (ORDER BY First_column ASC, ROWNUM) AS
 RNK,  MAX(ROWNUM) OVER (ORDER
 BY First_column ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
AS  MAX_ROWNUM  FROM (
--My Qyery to get the data
) Z ) WHERE RNK BETWEEN 1 AND 30

Currently it is taking long time when the data is huge. Is my query is right? Or
Can you suggest any better method/query?

Thanks in advance.
Followup:
yuck, i refuse to tell them exactly how many rows -- since it is wrong the split
instant you show them it.  look at google "1 thru 10 of about..."

When your result is huge, it must materialize the ENTIRE RESULT SET.  What a
waste of resources.  This is one practice that if I could stomp it out would
save the world trillions of dollars in hardware costs.  The amount of computing
power we use to tell a user "hey, you selected way more data then you will ever
actually look at" is amazing.
Rating: 4
April 07, 2003
Reviewer: A reader
:) Thanks for your quick response, I got your point and I will accept it for
huge search results.

But, most of the times my query is going to return 100-300 rows. But the
original query is going to access around 10 huge tables to get these results.

I need to show the definite row count when the results are 100 and users can
easily go to the required page.

Could you please comment based on the above scenario?
Followup:
Your system is doing (since a normal page is 10 rows) 10 to 30 times as much
work as it should then.  That is yet another way to look at this.  10 to 30
times as much work as it should.

Do you want to buy a machine that is 10 to 30 times more powerful for this?

You got the money -- and the hardware -- go for it.

Me, I've always found that knowing:

a) I have some data to look at
b) there is more data to look at if I like
c) there are prior pages of data I've already looked at

is not only sufficient -- but down right pleasant.  Look at the way this worked
in client server days -- we forgot something there.  Forms for example -- it
fetched 15 rows -- STOP.  When you tried to goto row 16 it fetched 15 more --
STOP.

Maybe what you need is a push button that says "show me about how many rows this
search returns", when the user hits it you put up a dialog that says "deposit
25cents to cover the cost of this query" and then you do a count(*) for them...

Its like Cary Millsap says "The fastest way to speed something up is to not do
it at all"
Rating: 5
April 07, 2003
Reviewer: A reader
I do agree with your comments, it’s not a good idea to show row count for search
results.

Could you please take a look at the following two queries and let me know which
is better for paging? (I saw mixed results in my testing)

1. Using rank analytical function

SELECT * FROM (  SELECT Z.*,  RANK() OVER (ORDER BY First_column ASC, ROWNUM) AS
 RNK
FROM (
--My Qyery to get the data
) Z ) WHERE RNK BETWEEN 1 AND 10

2. Using rownum

select *
  from ( select a.*, rownum r
           from ( My Query
                   order by First_column ) a
          where rownum < :HigerBound )
 where r > :LowerBound

btw, what if I need to show the list of users in an web based application.
Shouldn’t you accept it is a better idea to show the count of available users
and provide a paging mechanism with 10 records per each page?
Followup:
rownum is faster, cheaper, better.

No, I don't agree (sorry -- thought I was fairly clear on that particular
opinion!).  All I need to know is:

a) I've some users to look at on my screen
b) there are more users to look at (or not)

that there are 123 users -- so?
Rating: 3
Performance Issue September 05, 2003
Reviewer: Jap Boon Churn from Malaysia
Hi Tom,

1 year plus ago, i read your reply to this question
and found you great idea on storing search result in a hits table. So, i apply
the technique to my application.

I use this technique because each searches take quite some time (18 secs) and
therefore, i don't want the user to wait for the same amount of time for every
next pages that move forward.

However, after sometime when our data grows, i found that insertion of search
result into my hits table result in some performance issue. For example, i have
query that result more than 3000 rows. When my users play around with the system
by continuously submitting the searches, i found that it will degrade the
database performance, because every searches will have 3000 insert statements
executed, and what more to say if we have a lot of concurrent submission.

Is there any turnaround solution for this?
or there's somewhere that i can fine-tune it.

Please advise.
Thanks.
Followup:
quote from the original answer:

Normally I keep another 'parent' table to go along with hits and I store the
session id, the TIME of the query and such in there as well.  I put a foreign
key from hits back to this parent with ON DELETE CASCADE and run a job
(scheduled via dbms_jobs) to issue "delete from parent_table where time <
sysdate-1/24" -- that cleans out queries older then 1 hour.  Occasionally, i
truncate the table to lower the high water mark as well.

I clean the table out from time to time.
Rating: 4
Use cellsprint or use pagination query December 25, 2003
Reviewer: robert from CT
>>select *
  from ( select a.*, rownum r
           from ( My Query
                   order by First_column ) a
          where rownum < :HigerBound )
 where r > :LowerBound
--------------------------
Merry Christmas, Tom.
I do pagination via owa_util.cellsprint.
I see you code the pagination by fetching the whole set and actually "skipping"
unwanted records...

I am wondering if it's worth the effort to write a wrapper module that wrap the
dynamic query string in the query above, bind and then fetch'em...is there much
to again ?

Thanks
Followup:
when i first wrote cellsprint (i called it "cells_from_query") it was 1995, 7.3
was just being release :)

order by in a subquery would not exist for 4 more years.

If asked to do it over again, I would use the pagination query yes.

throw a first_rows hint in there (trim the string, add first rows after the
keyword select).

wrap in the layers of inline views.

execute it (in fact that is what bc4j does now in java).
Rating: 4
more records to paginate.. December 26, 2003
Reviewer: robert from CT
"cellsprint" tells you if there are more records....
How do I know that using the pagination query ?

thanks
Followup:
>>select *
  from ( select a.*, rownum r
           from ( My Query
                   order by First_column ) a
          where rownum < :HigerBound+1 )
 where r > :LowerBound

just ask for one more row then you really wanted.
Rating: 5
jumping through pages ... December 26, 2003
Reviewer: Sudhir
Tom,

I have a case here where knowing your data may allow you to use the simpler SQL
(may be). See the following:

SQL> create table t nologging as select * from dba_objects where object_id is
not null;

Table created.

SQL> create index t_idx on t(object_id) ;

Index created.

SQL> analyze table t compute statistics for table
  2  for all indexes
  3  for all indexed columns;

SQL> set autot trace exp

SQL> select min(object_id) from t;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=32 Card=1 Bytes=4)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FULL SCAN (MIN/MAX)) OF 'T_IDX' (NON-UNIQUE) (Cost=32
Card=15048 Bytes=60192)

SQL> select max(object_id) from t;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=32 Card=1 Bytes=4)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FULL SCAN (MIN/MAX)) OF 'T_IDX' (NON-UNIQUE) (Cost=32
Card=15048 Bytes=60192)

SQL> set autot off

SQL> l
  1* select max(object_id) from t
oracle@ssp:SQL> /

MAX(OBJECT_ID)
--------------
        561149

I just guessed next jump number << This is the gray area

SQL> select owner,object_id from t where object_id < 125 and rownum < 6;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=5 Bytes=105)
   1    0   COUNT (STOPKEY)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=5 Card=5 Bytes=105)
   3    2       INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=5)

SQL> set autot off
SQL> /

OWNER                           OBJECT_ID
------------------------------ ----------
SYS                                    98
SYS                                    99
SYS                                   100
SYS                                   101
SYS                                   107
SQL> select owner,object_id from t where object_id > 107 and object_id < 125 and

  2  rownum < 6;

OWNER                           OBJECT_ID
------------------------------ ----------
SYS                                   121

SQL> set autot trace exp
SQL> /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=3 Bytes=63)
   1    0   COUNT (STOPKEY)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=4 Card=3 Bytes=63)
   3    2       INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=3)

SQL> select owner,object_id from t where object_id < 200 and object_id > 121
  2  and rownum < 6;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=10 Card=5 Bytes=105)
   1    0   COUNT (STOPKEY)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=10 Card=15 Bytes=315)
   3    2       INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=15)

SQL> set autot off

SQL> /

OWNER                           OBJECT_ID
------------------------------ ----------
SYS                                   125
PUBLIC                                141
SYS                                   142
PUBLIC                                145
PUBLIC                                148

So I think if you start with a "guess" such as 125 and then get the five rows.
Then jump to next 5 by limiting. Only one problem is some "Next>" pages may
contain just one row and not a fixed number of rows per page.

There is another approach mentioned by Jonathan Lewis in his book using
object_type where by the array holds all the data, you just skip back and forth
through the array. Kind of lime memory temp table.

Thanks for your thoughts
December 26, 2003 -- Thanks for the question regarding "Multiple Processes processing rows", version 8.0.6
You Asked
Tom:

I have a table that contains rows representing work to be "retried".  If the
status column contains -1 that means the row has to be resubmitted to a process
that takes about 2 minutes to complete.

This has to be working 24x7.

Currently, I have 10 different machines each running the same process that is
looking for rows with status = -1, and submitting them for "retry" processing.
This is the main cursor SQL in each of these processes:

   select * from items where status = -1 for update;

When one of the processes receives rows back from this query, it processes the
first one of the row set.  We're using "for update" in order to keep some other
process from grabbing and processing the same row.  But, suppose there are 5
rows with status = -1.  This SQL locks all 5, and none of the other process
instances can get to them.  Remember, each retry processing take 2 minutes to
complete, so, the result is all these rows are locked up for 2 minutes at a
time.  Basically, right now, we are getting no payoff from having 10 machines
doing this work.  Only one row ends up being worked on at a time anyway.

I was thinking about doing something like this in each thread instead (this is
pseudo code):

open cursor:
SELECT item_id from items where status = -1;
got_lock = false;
for each rec in cursor
   select * from items where item_id = rec.item_id for update nowait;
   if (resource not already locked) then
      got_lock = true;
      exit loop;
end loop;

if (got_lock) then
   process_the_retry_that_takes_2_minutes (rec.item_id);
   commit;
end if;

This basically looks for any of the rows that are not already locked.  Being in
a locked state means a thread is already handling it.

Is this the best way to handle the general problem of a queued up set of rows
waiting to be processed by several threads?  We have the several threads only
for the purpose of redundancy and performance, and the volume of rows to be
processed is high.

A followup question... the 3rd party API apparently (as far as we can tell now)
has no way for us to indicate "nowait" on the cursor.  Do you have an idea of
how to do this without using "nowait"?  I'd still like to also know the right
way to do it, without this handicap.

Thank you.
Tommy
and we said...
Well, the RIGHT way is to use the queueing software built into the database
(Advanced Queues -- AQ).  It solves this problem completely
http://technet.oracle.com/doc/oracle8i_816/appdev.816/a76938/toc.htm
Short of that, your approach will work:

create procedure get_record( p_rec out items%rowtype )
is
  pragma exception_init( resource_busy, -54 );
begin
  for x in ( select rowid rid from items where status = -1 )
  loop
       begin
           select * into p_rec from items where rowid = x.rid for update nowait;
           exit;
       exception
           when resource_busy then
               null;
       end;
  end loop;
end;

Without NOWAIT, I don't see a way to do this.
Review & Followup

Rating: 4
suggestion ... October 28, 2001
Reviewer: A reader
select * from items where status = -1
and rownum=1
for update;

can this not just lock one row at a time?
Followup:
yes it will lock just one row at a time -- but if 5 sessions execute it at the
same time, they'll ALL try to lock the same row and 4 out of 5 will block.
You'd be right back where you started.

The procedure below is the second best way to do it (AQ being the first)
Rating: 4
Looks good to me... October 29, 2001
Reviewer: Padders from UK
...but in your example it appears you have omitted to declare the resource_busy
exception.
Followup:
You are right!  good eye.  we need to declare an exception in there.
Rating: 5
x October 29, 2001
Reviewer: Tom Best from PA
I'll look into AQ.  And thanks for confirming my non-AQ solution.
Rating: 4
AQ solution January 25, 2002
Reviewer: DK from India
Dear Tom,
How exactly to code AQ solution. It is surprisingly not in your book too.
Regards,
DK
Rating: 3
AQ with example please May 13, 2002
Reviewer: A reader
Can u please give an Example- solution for the above problem using AQ. As i see
it is one of the most underutilized feature of oracle as it is difficult to find
good sample programs with use cases.Apart from me I think even the Oracle Corp
doc writers dont know it so well. I may be wrong.
Thanx
Followup:
Look in the application developers set of docs.  There is an ENTIRE MANUAL on
nothing but AQ!!!

search for dbms_aq -- there are some examples here.
Rating: 5
Example please ... May 14, 2002
Reviewer: Pasko from Hamburg,Germany
Hi Tom

Thanks for the above solution of using a  LOOP and NOWAIT...

I  searched your site for dbms_aq but didn't find any  examples related to this

Oracle Docs  require  a whole lot of time and that's why we drop in at your
site...

An example would be greatly appreciated
I'm a Great fan of Learn-By-Example ...

Regards

Pascal
Followup:
I'm a great fan of reading the documentation.  You know, if I show you a snippet
of code -- you'll learn 1/10th of 1% of what you should know -- and you'll try
to apply that snippet of code to every problem.

Be smart -- read the docs.  Knowledge takes time, experience.  How do you
think I do what I do (hint hint hint: I read the docs)

Since you are so hesitant to do it I'll make it easier for you:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76938/adq08qap.htm#83406
Chapter 8 A Sample Application Using AQ

For the entire book:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76938/toc.htm
this hole concept of "i don't want to take the time to learn" scares me.  You
are missing 99% of the picture.

I'm a fan of learn by example as well -- after reading the documentation to get
the foundation (and then lo and behold -- I'll discover there are actually
examples in the documentation!)
Rating: 3
We Want an Example ;We Want an Example ;We Want an Example ; May 14, 2002
Reviewer: A reader
Yes please give us an Example of AQ for the above solution.
Why people shy away from giving examples.Are your concepts of AQ still not
clear.
(Well I am just provoking u to reply with an example,just kidding, Atleast this
will give us an Example from the GREAT TOM at any cost.).
Come on Every body repeat after me.
We Want an Example ;We Want an Example ;We Want an Example ;We Want an Example
;We Want an Example ;We Want an Example ;We Want an Example ;We Want an Example
;We Want an Example ;We Want an Example ;We Want an Example ;We Want an Example
;We Want an Example ;We Want an Example ;We Want an Example ;We Want an Example
;We Want an Example ;We Want an Example ;We Want an Example ;
Followup:
no.

I could just cut and paste this in:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76938/toc.htm
but i won't.
Rating: 5
RE: We want an example May 16, 2002
Reviewer: Mark A. Williams from Indianapolis, IN USA
<soapbox>

You want an example?

Here's an example:

Tom answers a question.
He provides links to the documentation.
The documentation contains examples (plus alot more).
Someone who can't type "you" screams "we want an example".

Now, the question is, of what is this an example?

Do you want to learn to fish, or do you want Tom to just send you a fish
everyday?

- Mark

</soapbox>
Rating: 5
An Example related to the Question above... May 17, 2002
Reviewer: A reader
Hi Tom

I read the whole Documentation and i could not find an example related to a
direct approach where i could solve the question above, without changing my
Application Design Totally ...i mean switch to Objects and all..

Plus, it seems even with ADQ..we still have to put some locking mechanisms if we
want our Queue to be accessed by only one Subscriber at a time ...
(refer to the BooksOnline Example)

I don't want to totally change my Application Design...i was looking for an
example where i could use Oracle's  ADQ
to solve this Locking issue without using oracle Objects/ or Pro C/C++ code..
Followup:
since you are just "a reader" -- i have no idea to what question you might be
refering.

You do not have to switch to objects, the message payload can be a LONG RAW
(whatever you want).

If you want the queue to be accessed by one subscriber at a time -- you by
definition do not need, or even want a queue, you apparently want a plain old
database table.
Rating: 3
Time and Again May 17, 2002
Reviewer: A reader
The Question in Question is obviously the the above Question "Multiple Processes
processing rows" and the Non AQ solution given.All the readers are asking again
and again is that "HOW to implement the same situation Using AQ". Simple.
  The Evasive solution so far has been the link to the Doc.
Thanx Tom.
Followup:
Well, no it is NOT OBVIOUSLY that.  "a reader" (sort of evasive there, hiding
behind "a reader") has been begging for "a custom example just for them so they
don't have to waste time learning themselves".

You would

a) create a queue.
b) use dbms_aq.enqueue to queue a message.
c) have consumer(s) running that use dbms_aq.dequeue to dequeue and process
messages.

Hey -- there is your example.

(and you know what -- the booksonline example I keep trying to get you to read
not evasively, as if I'm doing something wrong here is exactly the
implementation of the example you keep begging for.

It is the example!  It is the example!  Lets see what that example does:

a) it shows how to create the user with the ability to create and manage the
queue.  Thats a good start

b) it shows how to create a queue in that user....

c) it then shows how to use this queue with plsql and java/jdbc and C to
enqueue/dequeue a message.

Hmmm, looks like an example to me doesn't it.

I hate wasting time -- I have lots to do.  When a perfectly good, comprehensive
example exists -- I go for it, I use it.  Re-inventing wheels -- I give seminars
on how NOT TO reinvent a wheel.
Rating: 1
Tom you are missed the guys point June 30, 2003
Reviewer: E.Elliott from UK
The guy wanted an example that showed a queue being processed by N consumers.
With each message on the queue being processed by only one consumer. I have
looked for such examples and have not found them. The idea is to simulate a
javaSpace where you can scale the number of consumers.
Followup:
well, sigh, if you would read the links - but ok, I was finally beat into total
submission.

open wide
insert spoon

read:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:540822468136#7436461525790
it is literally cut and pasted from the above referenced material - but you
might have to "port" it to windows as it uses a shell script to mimick many
dequeuers...

really -- the docs don't totally stink (and they even predate java!)
Rating: 4
Multiple customers on AQ July 02, 2003
Reviewer: Elliott from UK
Ah, The queue is not quite a QUEUE!
The AQ table select does select for update SKIP LOCKED
So if we have multiple customers on a queue, each will get the next not locked
record. When the record is obtained it is locked. Only one session can have
exclusive update lock on a record.
So while customer 1 gets and locks the FIRST item on the queue. Customer 2 will
skip the locked record and get FIRST not locked on the queue.

select  /*+ FIRST_ROWS */   tab.rowid, tab.msgid, tab.corrid, tab.priority,
tab.delay,   tab.expiration, tab.retry_count, tab.exception_qschema,
tab.exception_queue, tab.chain_no, tab.local_order_no, tab.enq_time,
tab.time_manager_info, tab.state, tab.enq_tid, tab.step_no,   tab.sender_name,
tab.sender_address, tab.sender_protocol,   tab.dequeue_msgid
from GGGGGG.OBJMSGS80_QTAB tab
where q_name = :1 and state = :2
order by q_name, state, enq_time, step_no, chain_no, local_order_no
FOR UPDATE SKIP LOCKED

(The example in the Docs shows multiple clients on publish subscribe queue, each
item on the queue is processed by all registered clients)

Thanks
Rating: 5
single consumer model December 26, 2003
Reviewer: reader from usa
Tom,

related, to this thread, I have a very similar question/requirement.

A user orders ten items. (inserts ten rows into a table).  The insert kicks of a
job that selects for update all unfilled orders.  Each order takes 5 minutes to
complete processing.  Processing doesn't change any attribute values of those
rows of data that were inserted, it just loads some related binary data into
another table.  I am fine with these orders being processed serially by a single
consumer (the database job).  This is actually preferred since the processing
puts a strain on the hierarhcial storage management system the data is requested
from.  (it can only handle a few requests at a time). However, I would like to
make available the binary data that is loaded as a result of the processing of
each of these rows prior to all ten finishing.

 Will an autonomous transaction in the cursor for loop that iterates through the
open orders satisfy this?  I think it will allow me to insert/commit the binary
data in the associated table making it available for dissemination, without the
problem of not being able to commit in a for update cursor loop.

or should I be thinking differently.  I know AQ with a single consumer would
ensure that each message only is dequeued once, and that each dequeued message
could result in a single transaction that commited its data as soon as it was
finished. So maybe I should bite the bullet and start reading the dbms_aq guide.
Followup:
well, does it make transactional SENSE for some of the data to start appearing
before the transaction is completed?

I mean -- what happens if 1/2 way through, the system fails?  what then?  are
you able to "recover from that" or will you double order (sounds like a double
order to me).

I like AQ for it's "loose coupling" here -- process each transaction as a
message, commit at the end.  Everyone wins.
Rating: 4
non-AQ solution December 29, 2003
Reviewer: Dan Clamage from Pittsburgh PA
A non-AQ solution I've used successfully is to:
1. Request a lock via DBMS_LOCK.request (the package executable section, run
once at instantiation, allocates a unique handle with DBMS_LOCK.allocate_unique
(saved in a private-global variable) for a constant string, so that every
session will use the same lock). The default params give you an Exclusive lock.
2. After obtaining the lock, I SELECT just one row (limiting the rows returned
with ROWNUM=1) meeting the desired criteria AND not found in a "lock" table (by
primary key -- you need some way to uniquely identify the row). I don't care
which one I get, so long as I get one.
3. Insert the row's primary key in the "lock table".
4. Commit and release the lock with DBMS_LOCK.release.

I've just created a "Mutex", a program area that is mutually exclusive to
sessions. Only one session can enter that section of code at a time. It doesn't
do a whole lot, so the time spent in that code section is small (doesn't hold up
everyone too much). If lots of sessions try to obtain the lock, the internal
queuing mechanism inherent in these locks gives me the "FIFO" (First In, First
Out) behavior I want.

I've tested this with 16 sessions beating on it ("locking" a row to process,
processing it quickly, then removing it from the "lock" table), and it worked
well enough for our multiuser environment (actually an Intranet web GUI), where
requests would never come in in such heavy volume (but that's the way to test
it).

Don't forget to put in logic to handle timeouts (I think in your case you'd
merely want to retry obtaining the lock, although in practice you will probably
find it not necessary, even with a timeout of 2 secs), no work to perform (no
rows meeting your criteria), releasing the lock in case of a fatal error, etc.

And be sure to read the Supplied PL/SQL Packages doc!
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96612/d_lock.htm#998100

December 27, 2003 Geoffrey -- Thanks for the question regarding "questions about rollbacks/snapshot too old", version 7.3.4
You Asked
Tom,

I am looking into ORA-1555 that we are getting and need
clarification on a few points.

1/. does Oracle try to recreate the snapshot based on the SCN of
the whole block, or of a row. That is, if row A is updated and
committed, but then a running cursor needs to access row B in
the same data block, can we get ora-1555 if the rollback info
for the row A change has been overwritten, ever though we do not
want row A.

2/. if you update a column in one row, is just the previous
column value stored in the rollback segment, or the whole row,
or whole block?

I am confused as to why Delayed Logging Block Cleanouts get such
a big mention in all the texts I am reading re snapshot too old.

My understanding is that when a transaction is committed,the
rollback segment header status goes to committed and a new SCN
is assigned, but the data block status remains uncommitted until
the next operation on the block.
3/. is this possibly mins, hours, days away?

If the next operation finds the block status is uncommitted, it
must determine whether or not it still is by querying the
rollback segments. If the rollback info has been overwritten
already,
I guess the assumption is that the block has committed. I have
read that Oracle can use an algorithim for determining a block's
SCN during block cleanout even when the rollback segment has
been overwritten, so I assume it can clean up the datablock with
the new SCN and status.

This would then give us the latest SCN for the block, but no
rollback data and then we would get then get ora-1555 depending
upon whether this new SCN is before or after the current query
SCN.

4/. I cannot see how this could be any different if there was an
immediate cleanout.

In anticipation that you have corrected me on the above, here is
my next question.
5/. Some of the books suggest to avoid the above by doing a full
table scan before launching into your big cursor. Wont this just
mean that the full table scan will get the ora-1555 instead?

Fetching across commits also gets the finger pointed at it, but
isn't this just an example of the generic problem of running
long queries against lots of update activity. Is it particuarly
relevant whether your session or another one is doing the
updates.

Thanks for your help,

Geoff
and we said...
ahh, one of my favorite topics ;)

See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:275215756923
1) yes, delayed logging block cleanout can cause this without
any updates to the block in fact (just lots of other
transactions happening).  Also, we rollback the block, not just
a row in the block.

2) changed bytes only.  An insert stuffs a rowid (to be deleted
upon rollback), an update just the changed columns, a delete the
whole row.

as for:

"I am confused as to why Delayed Logging Block Cleanouts get
such
a big mention in all the texts I am reading re snapshot too old.

My understanding is that when a transaction is committed,the
rollback segment header status goes to committed and a new SCN
is assigned, but the data block status remains uncommitted until

the next operation on the block. "

that is answered in great part by the link above.  It is the
very ACT of cleaning out a block that did not get cleaned out
that causes the 1555.  I can get an ora-1555 against a table
EVEN THOUGH there have been no modifications to the table since
my query began (the last modification to that table committed
before my query began and NO ONE else has touched that table).

3) depends on your system but yes.

that is correct.

4) the immediate cleanout happens AFTER the transaction commits,
but before the block is flushed from the buffer cache.  It is as
if the transaction never happened.  The next guy to come along
to do a READ (not a write, a write will still do a cleanout
since the immediate cleanout is a fast cleanout that generates
no redo log) will see the block "clean".  It will not need to do
the read aside to the rbs and hence cannot get the 1555.

5) see the link above, it suggests courses of action.

"fetching across commits" -- yes and no.  With the fetching
across commits what is happening is something like this:

  for x in ( select * from t where x = some_value )
  loop
      update t set y = f(y) where t.pk = x.pk;
      commit;
  end loop;

here we are doing lots of tiny updates to the table we are
reading.  We are creating an artificial situation that should
not happen.  Normally, if I just ran:

  for x in ( select * from t here x = some_value )
  loop
      .... do something but don't update t ....
  end loop

we would not get the 1555 because the volume of updates to T is
small, by doing the update to the table we are reading, we cause
an artificial -- not normal situation whereby the table we are
reading is getting updated by tons of tiny transactions.

If you think you are SAVING space by commiting in a cursor for
loop, you are only deluding yourself.  you NEED that space in
order to keep the query going.  You aren't saving a byte of
storage, you shouldn't commit inside of the cursor for loop like
that (how the HECK do you restart something like that???  if it
is restartable -- you can then break the big query into a bunch
of little queries and run the queries+updates in a single
transaction -- avoiding the fetch across commit again.

My advice on this topic, because of what I typically find in
most cases, is that people are trying to "save space" and they
are using as small as possible RBS's as they can.  You need to
be generous with them.  You need to size them so that they do
not wrap faster then your longest query.  You do not size them
so that your concurrent transactions are OK -- that is only half
of the picture.  Lets say your longest query is 5 minutes.  On
that system I would want to have at least "10 minutes" of
rollback -- at least enough rollback permanently allocate
(minextents set out that high) so that during peak it would take
10 minutes to wrap around.
Review & Followup

Rating: 5
questions about rollbacks/snapshot too old March 13, 2001
Reviewer: Geoffrey Gray from Melbourne, Australia
Can I first say that I am staggered that I could receive a reply to this within
a matter of minutes. Thanks very much.

It has answered all my questions, but raised one more.

Getting an ora-1555 because of some updates done 3 weeks ago is intriguing. If
there are 1000's of uncleaned blocks,
wont it take ages to catch them one by one.

If the block is cleaned out as you get the ora-1555, is the answer to scan thru
a table ignoring 1555's so you can clean everything up.

Thanks very much
Geoff Gray (ggray@mits.com.au)
Rating: 3
delayed cleanout June 04, 2002
Reviewer: kiro
Tom
How can I clean out blocks after so big update?
select count(*) from table will work or not (for oracle 8.1.6)?
thanks
kiro.
Followup:
it'll just happen over time (a select *, resulting from say an export, would do
it)
Rating: 5
June 04, 2002
Reviewer: Mr. Rory B. Concepcion from Philippines
Is it advisable to have a parameter of 20 minextents for the rollback? Doesn't
the rollback segments allocate another extent when short in space? This was the
advice to us by an Oracle tech support. Please explain the difference of having
only 2 minextents against 20. Thanks and God bless you!
Followup:
There are two factors here:

o how many extents
o the size of each extent.

If the extents are 1gig apiece -- this would be a 20gig RBS, pretty sizable.
If the extents are 1m apiece, this would be 20m, too small for practical use
perhaps.

I personally start with 25, 1m extents and let them grow.  You want lots of
extents in a RBS generally, yes.  20 is smallish.  But the point here is that
the direct path insert shouldn't need any rbs (well, one block maybe).  We are
trying to figure out why it is blowing out the rbs.
Rating: 3
does high Rollback segment sizing make database unstable? October 01, 2003
Reviewer: Pallabi from India
we have occasionally been getting the ora-1555 error and despite increasing the
size of the rollback segment the problem doesn't seem to have disappeared. we
have a database size of around 240gb and 6 rollback segments of 1.5gb each
amounting to a total rollback segment of 9gb. the query that we run retrieves
data in an iterative manner (select...insert) with about 0.75 million records
from a from a table containing 9-10 million records per iteration.

we tried intermediate commits (fetch across commits) to solve the problem and
implemented a startup mechanism to take care of process failure and the problem
did seem solved for a while but now it has started occuring again and there
seems to be no end to increasing the size of the rollback segment.

it appears we will have to increase the rollback segment size anyway. whether we
retain the fetch across commit processing or not.

question is, will a size of 4gb for each rollback segment with a total of 8
rollback segments be too big for the current scenario? will it result in any
unstability for the database?

over this if we remove the fetch across commits process, will we need to
increase the size even further?

we are running an enviornment that is a mix of a database warehouse and an OLTP.

is there any other solution to solving the problem? need urgent help.
Followup:
"we tried intermediate commits (fetch across commits) to solve the problem"

that CAUSES the problem!!!!

you need enough rollback configured in order to NOT wrap a rollback segment in
the time it takes to run your long running query.
Rating: 3
Independent rollback segment selection. October 02, 2003
Reviewer: alvin from Philippines
16:38:56 rtbetelephony@RTBE.SUNFIRE01> SET TRANSACTION USE ROLLBACK SEGMENT
RBS_BATCH;

Transaction set.

Elapsed: 00:00:00.70
16:39:08 rtbetelephony@RTBE.SUNFIRE01> exec pg_cdr2br.pc_trig_cleanup (
1064484047000,106539839999
9, 20655465 );
BEGIN pg_cdr2br.pc_trig_cleanup (   1064484047000,1065398399999, 20655465 );
END;

*
ERROR at line 1:
ORA-01555: snapshot too old: rollback segment number 2 with name "RBS1" too
small
ORA-06512: at "RTBETELEPHONY.PG_CDR2BR", line 459
ORA-06512: at line 1

Elapsed: 04:287:17233.01

Why did it choose rollback segment 1 when i specifically specified that
rbs_batch is to be used ?

Am i missing some settings in the init.ora parameter ? i had to comment out the
'set transaction...' on my package and specifically set it on sqlplus prior to
calling the package.

I am using oracle 8.1.7.0.0 on Solaris 5.8. And prior to running the
pg_cdr2br.pc_trig_cleanup packaged procedure i successfully ran another
procedure using the same session.

I now tried running it on a fresh session.
Followup:
it did not.

you have a long running query in that procedure.

that query needed undo information from rbs1.

rbs1 is "too small", it wrapped around and was reused while your query was
running.

the set transaction told oracle where to put the UNDO YOU GENERATED.

Your queries need undo from virtually all rbs's (where OTHER people put their
undo)

The probability of a 1555 is directly proportional to the size of the SMALLEST
rbs.

I *hate* "use rollback segment".  I firmly believe all rbs's should be equisized
and sized so as to not wrap for the length of your longest running query.  I
like automatic undo management in 9i for helping to solve this easily.
Rating: 4
Still confused over block cleanout. October 21, 2003
Reviewer: Nick from NYC
You say:
"I can get an ora-1555 against a table EVEN THOUGH there have been no
modifications to the table since my query began (the last modification to that
table committed
before my query began and NO ONE else has touched that table). "

How so? It seems to me that there should be no problem generating the
read-consistent view in this case. What am I missing ?
Followup:
the problem is it cannot TELL at that point.  the information it needs to verify
it doesn't need a read consistent rollback operation is gone.
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:895410916429
is an example
Rating: 4
ORA-1555 with SCN December 27, 2003
Reviewer: A reader from IN
Hi,

    On our production server we had 45 rollback segments.
    Every day on particular time we are getting following error some time with
SELECT statement or INSERT..SELECT statement:
    ORA-01555 caused by SQL statement below (SCN: 0x0001.8bbac31a):
    Thu Dec 25 22:30:20 2003

    In Error, we are getting SCN number but not any rollback segment number.

    During that period we ran statpack report for @45min. In report, Pct Waits
is 0(Zero) and Avg Active size is also not crossing Optimal Size. But still we
received an error, after that we decided to increase no of rollback segment upto
75 with INITIAL 10M and NEXT 5MB, Problem is not solved.
    We tried to check whether it is happening because of Block clean out process
by executing individual select on the tables but we got same redo size before
and after select statement.

    We don't want to set particular rollback segment to transaction.

    How to solve this problem, what are the things need to look now?

    ->A high value for "Pct Waits" suggests more rollback segments may be
required
    ->RBS stats may not be accurate between begin and end snaps when using Auto
Undo
      managment, as RBS may be dynamically created and dropped as needed

            Trans Table       Pct   Undo Bytes
    RBS No      Gets        Waits     Written        Wraps  Shrinks  Extends
    ------ -------------- ------- --------------- -------- -------- --------
         0           10.0    0.00               0        0        0        0
         2          256.0    0.00         655,860        0        0        0
         3           20.0    0.00             806        0        0        0
         4           44.0    0.00           7,242        0        0        0
         5           52.0    0.00           3,434        0        0        0
         6           48.0    0.00           8,248        0        0        0
         7           60.0    0.00           4,000        0        0        0
         8           24.0    0.00           1,050        0        0        0
         9           24.0    0.00           3,818        0        0        0
        10           14.0    0.00             368        0        0        0
        11           20.0    0.00           1,694        0        0        0
        12           30.0    0.00           4,724        0        0        0
        13           30.0    0.00           2,322        0        0        0
        14           22.0    0.00             888        0        0        0
        15           14.0    0.00             276        0        0        0
        16           22.0    0.00           1,108        0        0        0
        17           18.0    0.00             728        0        0        0
        18           18.0    0.00             552        0        0        0
        19           18.0    0.00             668        0        0        0
        20           31.0    0.00          19,268        0        0        0
        21           30.0    0.00           1,524        0        0        0
        22           18.0    0.00             588        0        0        0
        23           32.0    0.00           1,566        0        0        0
        24           30.0    0.00           4,958        0        0        0
        25           16.0    0.00             426        0        0        0
        26           20.0    0.00             702        0        0        0
        27           44.0    0.00           2,762        0        0        0
        28           52.0    0.00           9,528        0        0        0
        29           50.0    0.00           3,144        0        0        0
        30           62.0    0.00          12,064        0        0        0
        31           25.0    0.00           8,236        0        0        0
        32           24.0    0.00           1,114        0        0        0
        33           16.0    0.00             426        0        0        0
        34           20.0    0.00             746        0        0        0
        35           28.0    0.00           1,502        0        0        0
        36           28.0    0.00           2,840        0        0        0
        37           24.0    0.00           1,194        0        0        0
        38           16.0    0.00             642        0        0        0
        39           20.0    0.00             714        0        0        0
        40           18.0    0.00             740        0        0        0
        41           18.0    0.00             576        0        0        0
        42           18.0    0.00             716        0        0        0
        43           26.0    0.00           2,340        0        0        0
        44           30.0    0.00           2,546        0        0        0
        45           16.0    0.00             470        0        0        0
              -------------------------------------------------------------
     Rollback Segment Storage for DB: O05CSW3  Instance: O05CSW3  Snaps: 29 -30
    ->Optimal Size should be larger than Avg Active

    RBS No    Segment Size      Avg Active    Optimal Size    Maximum Size
    ------ --------------- --------------- --------------- ---------------
         0         401,408               0                         401,408
         2      15,720,448       5,245,490      15,728,640      47,177,728
         3      15,720,448       5,369,973      15,728,640      20,963,328
         4      15,720,448       5,303,413      15,728,640      36,691,968
         5      15,720,448       5,363,618      15,728,640      31,449,088
         6      15,720,448       6,849,381      15,728,640      57,663,488
         7      15,720,448       5,483,326      15,728,640      26,206,208
         8      15,720,448       5,532,050      15,728,640      26,206,208
         9      15,720,448       5,553,493      15,728,640      57,663,488
        10      15,720,448       5,267,081      15,728,640      31,449,088
        11      15,720,448       5,665,448      15,728,640      26,206,208
        12      15,720,448       5,268,269      15,728,640     110,092,288
        13      15,720,448       5,800,168      15,728,640      41,934,848
        14      15,720,448       6,006,413      15,728,640      47,177,728
        15      15,720,448       7,062,102      15,728,640      52,420,608
        16      15,720,448       5,705,462      15,728,640      41,934,848
        17      15,720,448       5,462,843      15,728,640      57,663,488
        18      15,720,448       5,309,849      15,728,640      31,449,088
        19      15,720,448       6,119,094      15,728,640     445,636,608
        20      15,720,448       5,268,105      15,728,640     445,636,608
        21      15,720,448       5,450,980      15,728,640      47,177,728
        22      15,720,448       7,020,310      15,728,640      47,177,728
        23      15,720,448       7,002,246      15,728,640      41,934,848
        24      15,720,448       5,346,636      15,728,640     183,492,608
        25      15,720,448       5,987,975      15,728,640      41,934,848
        26      15,720,448       5,333,102      15,728,640      31,449,088
        27      15,720,448       5,462,412      15,728,640      47,177,728
        28      15,720,448       5,247,884      15,728,640      47,177,728
        29      15,720,448       5,241,468      15,728,640      36,691,968
        30      15,720,448       5,380,344      15,728,640      31,449,088
        31      15,720,448       5,281,938      15,728,640      26,206,208
        32      15,720,448       5,395,238      15,728,640      31,449,088
        33      15,720,448       5,290,287      15,728,640      62,906,368
        34      15,720,448       5,256,007      15,728,640      41,934,848
        35      15,720,448       5,243,383      15,728,640      20,963,328
        36      15,720,448       5,266,780      15,728,640      41,934,848
        37      15,720,448       5,295,158      15,728,640      73,392,128
        38      15,720,448       5,668,529      15,728,640      52,420,608
        39      15,720,448       6,501,956      15,728,640      47,177,728
        40      15,720,448       5,570,874      15,728,640      20,963,328
        41      15,720,448       5,393,767      15,728,640      31,449,088
        42      15,720,448       5,366,338      15,728,640      36,691,968
        43      15,720,448       5,476,919      15,728,640      26,206,208
        44      15,720,448       5,973,525      15,728,640      41,934,848
        45      15,720,448       5,425,109      15,728,640      52,420,608
          -------------------------------------------------------------

SQL> select sum(bytes),tablespace_name from dba_data_files where tablespace_name
like 'RBS_%' group by tablespace_name;

SUM(BYTES) TABLESPACE_NAME
---------- ------------------------------
4194304000 RBS_01
4194304000 RBS_02

Thanks,
Followup:
the ora 1555 means "rbs still not big enough"

I would not recommend adding MORE rbs -- I would suggest going back to your
original number (or less -- depending on the average active number of
transactions) and set their MINEXTENTS higher.  That is, have fewer large RBS's.
December 27, 2003 Bob -- Thanks for the question regarding "Best way to remove a partitioned structure that includes CLOBS?", version 9.0.1
You Asked
Tom,
we have a table, that was partitioned several years ago. The structure has
proven not ideal, and it was decided to remove the partitions, and place the
data in a more "normal" structure. What is the best way to go about this? What I
have and what I've done is this:
1) a table, with a blob data type in one column, containing a document.
2) a partiton structure, with 128 partitions.
3) a new table, with no partitions, but the blob and data fields defined in
different tablespaces.
4) almost 2 million records, at around 8 to 10g in storage.

What I've tried was something simple. Create the table, then
insert /*+ append */ into new_table select * from old_table;

and it seems to work, except it takes an excessive amount of time, and I've run
out of undo twice while testing. I've set the undo up for 14400 and have enough
tablespace defined, but it seems to take an inordinate amount of time to finish.
Or not finish in this case. I get the snapshot too old issue.
When I've done this with no blob data, I've gotten millions of rows in minutes.
Is there a way to speed this up? I've defined the blob in the table to not log.
I can show you the partition structure if it matters or helps.
and we said...
the issue is going to center around the LOB INDEX.  When you have a  lob column,
there are really three segments -- a) the table, b) the lob INDEX, c) the lob
data itself.

So, loading a table with a lob is like loading a table with an index on it --
the index will always be logged, the index will always generate undo.

Now, we can significantly cut down on the redo generated using a CREATE TABLE AS
SELECT instead of an INSERT /*+ APPEND */  (significantly cut down)

consider (my blobs were each 32k in size)

ops$tkyte@ORA9IR2> drop table t2;
Table dropped.

ops$tkyte@ORA9IR2> column value new_val V
ops$tkyte@ORA9IR2> column writes new_val W

ops$tkyte@ORA9IR2> select b.value, (select sum(writes) from v$rollstat) writes
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and a.name = 'redo size'
  5  /

     VALUE     WRITES
---------- ----------
 524740968   31489860

ops$tkyte@ORA9IR2> CREATE TABLE T2 (DATA, BINARY_DATA ) TABLESPACE TESTING
NOLOGGING
  2  LOB (BINARY_DATA) STORE AS (TABLESPACE TESTING ENABLE STORAGE IN ROW CHUNK
8192 PCTVERSION 10 NOCACHE )
  3  AS
  4  select * from t1;

Table created.

Elapsed: 00:00:52.76

ops$tkyte@ORA9IR2> select b.value, round( (b.value-&V)/1024/1024, 1 )
redo_DIFF_mbytes,
  2         round( (select sum(writes)-&W from v$rollstat)/1024, 1 )
writes_diff_Kbytes,
  3             (select count(*) from t2) cnt_t2
  4  from v$statname a, v$mystat b
  5  where a.statistic# = b.statistic#
  6  and a.name = 'redo size'
  7  /

     VALUE REDO_DIFF_MBYTES WRITES_DIFF_KBYTES     CNT_T2
---------- ---------------- ------------------ ----------
 528207364              3.3                859       6400

Elapsed: 00:00:00.14

so, it took about 52 seconds on my pc -- generated 3.3m of REDO and 859k of
UNDO (i'm single user, all of the undo on my system is all about me)

ops$tkyte@ORA9IR2> truncate table t2;
Table truncated.

Elapsed: 00:00:00.52
ops$tkyte@ORA9IR2> select b.value, (select sum(writes) from v$rollstat) writes
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4  and a.name = 'redo size'
  5  /

     VALUE     WRITES
---------- ----------
 528362928   32412112

Elapsed: 00:00:00.05
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert /*+ APPEND */ into t2
  2  select * from t1;

6400 rows created.

Elapsed: 00:02:00.46
ops$tkyte@ORA9IR2> commit;

Commit complete.

Elapsed: 00:00:00.06

the direct path insert took a little more then 2 times as long and..

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select b.value, round( (b.value-&V)/1024/1024, 1 )
redo_DIFF_mbytes,
  2         round( (select sum(writes)-&W from v$rollstat)/1024, 1 )
writes_diff_Kbytes,
  3             (select count(*) from t2) cnt_t2
  4  from v$statname a, v$mystat b
  5  where a.statistic# = b.statistic#
  6  and a.name = 'redo size'
  7  /

     VALUE REDO_DIFF_MBYTES WRITES_DIFF_KBYTES     CNT_T2
---------- ---------------- ------------------ ----------
 741944204            203.7              892.4       6400

generated tons more redo -- about the same undo

Also, you can throw some parallel query at that create table as select as well
-- this'll have reduce the 1555 risk and if you have sufficient IO and CPU
resources -- speed up this long running process.

Another option is to employ the "DO IT YOURSELF" parallelism (DIY) outlined in
my new book "effectivie one on one Oracle".  There I demonstrate how to generate
N rowid pairs, such that the rowid pairs "cover" the table.  Then, you would be
able to (in serial or in parallel sessions) issue:

insert /*+ append */ into new
select * from old where rowid between :x and :y;

breaking the problem into smaller bite sized chunks.
December 27, 2003 Satya -- Thanks for the question regarding "How to stop truncating the table through trigger", version 9.1
You Asked
Hi Tom,
   Upto my knowledge, there are database triggers on INSERT, UPDATE, DELETE,
INSTEAD OF , start up, shutdown and some schema level triggers.But There is no
trigger on TRUNCATE operation.How to stop a user from truncating a table.

Thank for quick reply.

Regards
Satya.
and we said...

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96590/adg14evt.htm#1004390
there are before/after truncate triggers.

there are many triggers -- all listed there.
Review & Followup

Rating: 5
December 27, 2003
Reviewer: A reader
Rating: 5
December 27, 2003
Reviewer: A reader
December 27, 2003 -- Thanks for the question regarding "function based indexes", version 8.1.X
You Asked
I'd like to use function based indexes on index organized tables.
Can you explain why a function based index isn't allowed on a
primary key? Is there a workaround?

Thanks
and we said...
Well, a PRIMARY KEY isn't allowed to be a function, but you can most certainly
create a function based index on an IOT's primary key.

If you need the primary key to be some function of the COLUMNS of the IOT then
what you need to do is add a column to the IOT and use a trigger to maintain the
value of that COLUMN.  It'll be the same exact thing as having a function based
primary key (it will be a function based primary key!)

to show you can have an FBI on an IOT's primary key:

tkyte@TKYTE816> create table emp
  2  (empno , ename primary key, job, mgr, hiredate, sal, comm, deptno )
  3  organization index
  4  as
  5  select * from scott.emp;
Table created.

tkyte@TKYTE816> update emp set ename = initcap(ename);
14 rows updated.

tkyte@TKYTE816> commit;
Commit complete.

tkyte@TKYTE816> create index emp_upper_idx on emp(upper(ename));
Index created.

tkyte@TKYTE816> analyze table emp compute statistics
  2  for table
  3  for all indexed columns
  4  for all indexes;
Table analyzed.

tkyte@TKYTE816> alter session set QUERY_REWRITE_ENABLED=TRUE;
Session altered.

tkyte@TKYTE816> alter session set QUERY_REWRITE_INTEGRITY=TRUSTED;
Session altered.

tkyte@TKYTE816> set autotrace on explain
tkyte@TKYTE816> select ename, empno, sal from emp where upper(ename) = 'KING';

ENAME           EMPNO        SAL
---------- ---------- ----------
King             7839       5000

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=31)
   1    0   INDEX (RANGE SCAN) OF 'SYS_IOT_TOP_31966' (UNIQUE) (Cost=1 Card=1
   2    1     INDEX (RANGE SCAN) OF 'EMP_UPPER_IDX' (NON-UNIQUE) (Cost=1 Card=1)
Review & Followup

Rating: 3
- October 14, 2002
Reviewer: A reader from BG
Tom,
   Suppose i have a function based index on a column. How do i know which column
and what is the function used, from the database. I do not get these information
user_ind_columns. It gives me a SYS_ name for the column. Also, it is not
available in all_source.

From where do i get that information assuming the fuction/column for the index
was undocumented.

regards
Followup:
USER_IND_EXPRESSIONS
Rating: 5
function based index January 28, 2003
Reviewer: mo
Tom:

I read you section on function based indexes and got the impression that by
creating an index on a user-defined function I may be able to improve the speed
of a query except I do not see how it happens.  I have a table with 1000 stock
items on it.  Then I have a query that uses a function several times (different
warehouse) to compute different total of each item in each warehouse (3
warehouses) and how much each consumed per month. How would the index help me in
this case since I have to read every single stock item (is it because the index
has pre-calculated the value) , run the fucntion and return the result. The
report is taking 25 seconds for the web page but I may get it faster with this.
Do you think I need to create indexes on each function and make the functions
determinstic.

        FOR x in ( select class1,stock_number1,description1,
                   INS1,MSE1,MSW1,total,used_this_fy1,used_prev_fy1,
                   used_per_month1,round(total/used_this_fy1,0) months_left1,
                   resp1  from

                       ( select class class1,stock_number stock_number1,
                          description description1,
                          INS INS1, MSE MSE1, MSW MSW1, INS+MSE+MSW total,
                          used_this_fy used_this_fy1,
                          used_prev_fy used_prev_fy1,
                          used_per_month used_per_month1,
                          resp resp1
                            from
                    (select class,a.stock_number,
                   substr(description,1,50) description,
                   compute_qty_org('INS',a.stock_number) INS,
                   compute_qty_org('CA11',a.stock_number) MCE,
                   compute_qty_org('VA11',a.stock_number) MCW,
                   compute_used_this_fy(a.stock_number) used_this_fy,
                   compute_used_prev_fy(a.stock_number) used_prev_fy,
                   compute_used_per_month(a.stock_number) used_per_month,
                   b.person_responsible resp
                   from stock_item a,stock_warehouse b
                   where a.stock_number = b.stock_number(+) and
                   a.stock_number <> '99999')
Followup:
let me see the code for compute_qty_org and compute_used_this_fy and
compute_used_per_month.
Rating: 5
odd May 12, 2003
Reviewer: A reader
Hi

in
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/sql_1016.htm#28529
it says this

When you need to use SQL functions on filters or join predicates, do not use
them on the columns on which you want to have an index; rather, use them on the
opposite side of the predicate, as in the following statement:

TO_CHAR(numcol) = varcol
rather than
varcol = TO_CHAR(numcol)

Now.... I dont see the difference at all. May you explain a bit?
Followup:
that does look like a typo.  they meant

use:

indexed_number_column = to_number( varcol )

rather then

to_char( indexed_number_column ) = varcol
Rating: 3
two function indexes on a table July 30, 2003
Reviewer: Fubar from New York, New York
I have a table with two function indexes on first and last name with an upper.
If I combine the indexes or use the index with another index in the table my
session crashes with end of file communication ora-3113.  Any thoughts.
Followup:
thoughts = 3113 always means "tar"
Rating: 4
my FBI doesn't in effect August 21, 2003
Reviewer: lancelot from China
I don't understand why the "second" not use the FBI, but the "thrid" use the
FBI?

SQL> show parameter query

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
query_rewrite_enabled                boolean TRUE
query_rewrite_integrity              string  trusted
SQL> show parameter compatible

NAME                                 TYPE    VALUE
------------------------------------ ------- ------------------------------
compatible                           string  8.1.0

SQL> show user
USER &#20026;"SYSTEM"
SQL> create table dept (deptno number, dname varchar2(10),loc varchar2(10));

Table created.

SQL> insert into dept values(30, 'SALES', 'CHICAGO');

1 row created.

SQL> create index upper_dept
2 on dept(UPPER(dname));

Index created.

first&#65306;
SQL> set autotrace on
SQL> select *
2 from dept
3 where UPPER(dname) = 'SALES';

DEPTNO DNAME LOC
---------- ---------- ----------
30 SALES CHICAGO

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

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

second&#65306;
SQL> analyze table dept compute statistics;

Table analyzed.

SQL> analyze index upper_dept compute statistics;

Index analyzed.

SQL> select *
2 from dept
3 where UPPER(dname) = 'SALES';

DEPTNO DNAME LOC
---------- ---------- ----------
30 SALES CHICAGO

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=14)
1 0 TABLE ACCESS (FULL) OF 'DEPT' (Cost=1 Card=1 Bytes=14)

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

third&#65306;
SQL> select count(*)
2 from dept
3 where UPPER(dname) = 'SALES';

COUNT(*)
----------
1

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=5)
1 0 SORT (AGGREGATE)
2 1 INDEX (RANGE SCAN) OF 'UPPER_DEPT' (NON-UNIQUE) (Cost=1
Card=1 Bytes=5)

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

SQL>
Followup:
because DEPT is tiny,

because DEPT is a single block.

because the second plan would have involved:

   1) index range scan
   2) table access by rowid

which would take more io then

   1) full scan table

in this case.

the last one used the index cause the index could be used to answer the entire
question without hitting the table itself.
Rating: 4
oracle version lead to different result August 21, 2003
Reviewer: lancelot from China
my friend get the same result with me .
In oracle817, the "second" and the "third" return the different result.
But in Oracle920, the "second" and the "third" return the same result , that
they all use FBI.
Followup:
it is a function of the amount of data.

on a set of 4 rows -- using an index is not always the wisest thing to do.

remember and keep repeating:

indexes are not goodness incarnate
table scans are not evil
(repeat above two lines until you believe it)
Rating: 4
Case Construct in Function based index September 09, 2003
Reviewer: A reader
Hi Tom,

 Can a case structure be used in function based index?
I am using following case construct in most of my queries.
(CASE WHEN CP_ACC_PROPERTY_HDR.ACCPRPTY_EFFECTIVE_DATE < 500000
THEN (20000000 + CP_ACC_PROPERTY_HDR.ACCPRPTY_EFFECTIVE_DATE)
ELSE (19000000 + CP_ACC_PROPERTY_HDR.ACCPRPTY_EFFECTIVE_DATE)END)

I need this check as I want to use max of this date. ACCPRPTY_EFFECTIVE_DATE is
number(6) in our source system.
Followup:
you can use case, yes.
Rating: 5
what is difference between Lead function and prior by connect clause September 11, 2003
Reviewer: suresh from India
   what is the difference between Lead function and Prior by connect clause in
Oracle ?

   what are the data dictionary tables(systems tables) ?
Followup:
they are apples and oranges.

one (connect by prior) lets you do a hierarchy

the other lets you reference the values of a prior (or future) row in the result
set in the current row.

you can create a hierarchy with connect by prior.  you cannot compare YOUR
salary to the salary of the last person hired right before you with connect by
prior.

you cannot create a hiearchy with lag/lead.  you CAN compare your salary......

all_objects for example is a data dictionary table.
sys.obj$ is another.
Rating: 4
Behaviour of Function based Index and Normal Index December 27, 2003
Reviewer: Vivek Sharma from Bombay, India
Dear Tom,

I have a query as regards to the behaviour of Function based indexes.
The parameters query_rewrite_enabled is set to true and query_rewrite_integrity
is set to integrity. Now I have 2 tables with same number of rows. These are

scott@ORCL>show parameter query

NAME                                 TYPE    VALUE
------------------------------------ ------- ---------------
query_rewrite_enabled                boolean TRUE
query_rewrite_integrity              string  TRUSTED

create table test as
select * from all_objects;

create table test1 as
select * from all_objects;

create index test1_idx on test1(created);

create index test_idx on test(to_char(created,'dd-mon-yyyy'));

analyze table test compute statistics for table for all indexes for all indexed
columns;

analyze table test1 compute statistics for table for all indexes for all indexed
columns;

So my Test table has a function based index and test1 has normal btree index.

When I fire a query

scott@ORCL>explain plan for
  2  select count(created) from test;

Explained.

scott@ORCL>@utlxpls

Plan Table
--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |     1 |      |     52 |       |       |
|  SORT AGGREGATE           |          |     1 |      |        |       |       |
|   TABLE ACCESS FULL       |TEST      |    24K|      |     52 |       |       |
--------------------------------------------------------------------------------

6 rows selected.

scott@ORCL>explain plan for
  2  select count(created) from test1;

Explained.

scott@ORCL>@utlxpls

Plan Table
--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |     1 |      |     11 |       |       |
|  SORT AGGREGATE           |          |     1 |      |        |       |       |
|   INDEX FAST FULL SCAN    |TEST1_IDX |    24K|      |     11 |       |       |
--------------------------------------------------------------------------------

6 rows selected.

My Question :

Why does it goes for a full table scan for a table with Function based index
whereas it does a Index Fast Full Scan ?

For my normal queries with where clause it uses indexes on both the tables. But
for count it behaves differently. Why ? What could be the reason ?

Your explanation will be highly appreciated.

Thanks and Regards
Vivek Sharma
Followup:
it is not 100% known to the optimizer that your function never returns NULLS.

Since entirely NULL entries are not indexed, there is the chance (not in your
case, but in general) that the index would have a different number of elements
in it then the table.

Hence, it won't use it.

With the "normal" b*tree, the optimizer says "ah hah, index on a NOT NULL
COLUMN, lets count rows using that"

You should know that in later releases, it is much smarter with BUILTIN
functions.  Not only is query rewrite not needed, but it knows which will return
NULLS and which will NOT.  Hence:

ops$tkyte@ORA9IR2> create table t as select * from all_objects;

Table created.

ops$tkyte@ORA9IR2> create index t_idx on t(to_char(created,'dd-mon-yyyy'));

Index created.

ops$tkyte@ORA9IR2> analyze table t compute statistics for table;

Table analyzed.

ops$tkyte@ORA9IR2> set autotrace traceonly explain
ops$tkyte@ORA9IR2> select count(*) from t;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FAST FULL SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=4 Card=29526)

ops$tkyte@ORA9IR2> set autotrace off
ops$tkyte@ORA9IR2> show parameter query

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
query_rewrite_enabled                string      FALSE
query_rewrite_integrity              string      enforced
December 27, 2003 Learco -- Thanks for the question regarding "reading files in a directory -- how to get a list of available files.", version 8.1.5
You Asked
How do I read files from a certain directory with PL/SQL,
without
knowing the exact name ?

My program must interface with another system which puts files
in
a directory on the server. UTL_FILE only reads a file when you
know the name of the file, but I don't know the name in advance.
Is it possible to use wildcards (eg. '*') in the name of the
file?

The platform is Windows NT and we have the Jserver option.

Thanks.
and we said...
We cannot do this with PLSQL directly however, using Java (or a
C extproc) we can do this pretty easily.

The interface I came up with uses a global temporary table which
will "lose" its rows every time you commit.  You'll call a
stored procedure providing a DIRECTORY to scan and I'll put a
list of all of the files that are in that directory into this
temp table.  If you want to "filter" the files (eg: only
interested in *.txt files), you'll use SQL "select * from
dir_list where filename like '%.txt'" to do so.

The implementation is:

ops$tkyte@8i> GRANT JAVAUSERPRIV to ops$tkyte
  2  /

Grant succeeded.

That grant must be given to the owner of the procedure..
Allows them to read directories.

ops$tkyte@8i> create global temporary table DIR_LIST
  2  ( filename varchar2(255) )
  3  on commit delete rows
  4  /

Table created.

ops$tkyte@8i> create or replace
  2     and compile java source named "DirList"
  3  as
  4  import java.io.*;
  5  import java.sql.*;
  6
  7  public class DirList
  8  {
  9  public static void getList(String directory)
 10                     throws SQLException
 11  {
 12      File path = new File( directory );
 13      String[] list = path.list();
 14      String element;
 15
 16      for(int i = 0; i < list.length; i++)
 17      {
 18          element = list[i];
 19          #sql { INSERT INTO DIR_LIST (FILENAME)
 20                 VALUES (:element) };
 21      }
 22  }
 23
 24  }
 25  /

Java created.

ops$tkyte@8i>
ops$tkyte@8i> create or replace
  2  procedure get_dir_list( p_directory in varchar2 )
  3  as language java
  4  name 'DirList.getList( java.lang.String )';
  5  /

Procedure created.

ops$tkyte@8i>
ops$tkyte@8i> exec get_dir_list( '/tmp' );

PL/SQL procedure successfully completed.

ops$tkyte@8i> select * from dir_list where rownum < 5;

FILENAME
------------------------------------------------------
data.dat
.rpc_door
.pcmcia
ps_data

And thats it...
Review & Followup

Rating: 5
reading files in a directory July 09, 2001
Reviewer: Marcos from Netherlands
Better option than I came up with
Thanks
Rating: 5
Great explanation ! February 26, 2002
Reviewer: Sven Bleckwedel from Santos, SP/Brazil
Hi,

Tom, tks for your valuable explanation about this feature, using Java and the
bit tricky use of "global temporary table DIR_LIST...on commit delete rows" !
Suppose that i had created these procedures in one schema (and the init.ora
parameter "utl_file_dir" was configured properly).  How can the other users use
it ?  I had granted "execute on get_dir_list" to another user and received the
following error:

SQL> grant execute on get_dir_list to scott;

Grant succeeded.

SQL> connect scott
Enter password: *****
Connected.
SQL> exec get_dir_list( 'C:\Temp\Extract' );
BEGIN get_dir_list( 'C:\Temp\Extract' ); END;
      *
ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00201: identifier 'GET_DIR_LIST' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

SQL> desc dir_list
ERROR:
ORA-04043: object dir_list does not exist

Could you explain my mistake ?  I think that the access to this table is
inherited by the procedure, but...

Tks in adv,
Sven
Rating: 5
Reviewing steps... February 27, 2002
Reviewer: Sven Bleckwedel from Santos, SP/Brazil
Hi,

Excuse me, but i had found the reason of my mistake...

SQL> connect system
Enter password:
Connected.
SQL> grant select on dir_list to scott;

Grant succeeded.

SQL> connect scott
Enter password:
Connected.
SQL> desc system.dir_list;
 Name                                      Null?    Type
 ----------------------------------------- -------- -------------

 FILENAME                                           VARCHAR2(255)

SQL> exec system.get_dir_list( 'C:\Temp\Extract' );

PL/SQL procedure successfully completed.

SQL> desc system.dir_list;
 Name                                      Null?    Type
 ----------------------------------------- -------- -------------

 FILENAME                                           VARCHAR2(255)

SQL> select * from system.dir_list;

FILENAME
-----------------------------------------------------------------

LOG_ERRO.CDX
LOG_ERRO.DBF

SQL>

I made this test in one test instance only and i know the security problems that
can arise when giving to another users the ability to use procedures and tables
from another user...

Rgds,
Sven
Rating: 5
Extremely helpful! March 06, 2002
Reviewer: Mike Jones from Columbia, MD USA
Helpful, easy and worked first time in 8.1.6.
Great initial exposure to Java in the database.
Rating: 5
Great! Helped me solve one of my problems May 07, 2002
Reviewer: Yogeeraj from Mauritius
Hi,

Helped me solve one of my problems where i had to query the existence of a file
on the OS in one of my applications.

Helped me also build a list of files from several directories.

(only missing part on which i have to investigate is how to include the relative
path to each filename.)

I can also understand that files must be residing on the same box as the
database server.

The first java procedure in my database ;)
thanks a lot.

Best Regards
Yogeeraj
Rating: 4
NullPointerException May 09, 2002
Reviewer: Charlie
Hi Tom,

I followed the exact procedure you listed here and I know "c:\temp" folder
exists on the database server.  Why do I get this NullPointerException?  Any
clue?

Thanks,

SQL> exec get_dir_list('c:\temp');
BEGIN get_dir_list('c:\temp'); END;

*
ERROR at line 1:
ORA-29532: Java call terminated by uncaught Java exception:
java.lang.NullPointerException
ORA-06512: at "TU1.GET_DIR_LIST", line 0
ORA-06512: at line 1
Followup:
version and all?
Rating: 4
version May 10, 2002
Reviewer: Charlie
Followup:
version and all?
--------------------
Connected to:
Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production
With the Partitioning option
JServer Release 8.1.7.0.0 - Production

OS: Windows2000/Pro SP2

Thanks,
Followup:
Sorry but the only way I can reproduce this is when the directory in fact DOES
NOT exist:

a@ORA817DEV.US.ORACLE.COM> @connect /
a@ORA817DEV.US.ORACLE.COM> set termout off
ops$tkyte@ORA817DEV.US.ORACLE.COM> REM GET afiedt.buf NOLIST
ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout on
ops$tkyte@ORA817DEV.US.ORACLE.COM> drop user a cascade;

User dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> grant create session, create table, create
procedure to a identified by a;

Grant succeeded.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_java.grant_permission( 'A',
'java.io.FilePermission','/tmp','read' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_java.grant_permission( 'A',
'java.io.FilePermission','/tmp_NOT','read' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> @connect a/a
ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout off
a@ORA817DEV.US.ORACLE.COM> REM GET afiedt.buf NOLIST
a@ORA817DEV.US.ORACLE.COM> set termout on
a@ORA817DEV.US.ORACLE.COM>
a@ORA817DEV.US.ORACLE.COM>
a@ORA817DEV.US.ORACLE.COM> create global temporary table DIR_LIST
  2  ( filename varchar2(255) )
  3  on commit delete rows
  4  /

Table created.

a@ORA817DEV.US.ORACLE.COM>
a@ORA817DEV.US.ORACLE.COM>
a@ORA817DEV.US.ORACLE.COM> create or replace
  2     and compile java source named "DirList"
  3  as
  4  import java.io.*;
  5  import java.sql.*;
  6
  7  public class DirList
  8  {
  9  public static void getList(String directory)
 10                     throws SQLException
 11  {
 12      File path = new File( directory );
 13      String[] list = path.list();
 14      String element;
 15
 16      for(int i = 0; i < list.length; i++)
 17      {
 18          element = list[i];
 19          #sql { INSERT INTO DIR_LIST (FILENAME)
 20                 VALUES (:element) };
 21      }
 22  }
 23
 24  }
 25  /

Java created.

a@ORA817DEV.US.ORACLE.COM>
a@ORA817DEV.US.ORACLE.COM> create or replace
  2  procedure get_dir_list( p_directory in varchar2 )
  3  as language java
  4  name 'DirList.getList( java.lang.String )';
  5  /

Procedure created.

a@ORA817DEV.US.ORACLE.COM>
a@ORA817DEV.US.ORACLE.COM>
a@ORA817DEV.US.ORACLE.COM> exec get_dir_list( '/tmp' );

PL/SQL procedure successfully completed.

a@ORA817DEV.US.ORACLE.COM> select * from dir_list where rownum < 5;

FILENAME
---------------------------------------------------------------------------------
--------------------------------------------------
lost+found
.rpc_door
ps_data
.pcmcia

a@ORA817DEV.US.ORACLE.COM>
a@ORA817DEV.US.ORACLE.COM>
a@ORA817DEV.US.ORACLE.COM> exec get_dir_list( '/tmp_NOT' );
BEGIN get_dir_list( '/tmp_NOT' ); END;

*
ERROR at line 1:
ORA-29532: Java call terminated by uncaught Java exception:
java.lang.NullPointerException
ORA-06512: at "A.GET_DIR_LIST", line 0
ORA-06512: at line 1

a@ORA817DEV.US.ORACLE.COM>
Rating: 5
It works fine May 10, 2002
Reviewer: Amitabha Bhaumik from Boston,MA USA
It works GREAT but when I created the java procedure
instead of saying

"java created"

it said

"Operation 160 succeeded."

any idea.
Followup:
You are using a pre-8i sqlplus version.  You should upgrade the client software
to take advantage of the database new features correctly.
Rating: 5
Solution from Heart May 11, 2002
Reviewer: Parag from India
Too good man.
I think this is one of the "THE BEST" site for Oracle related
Issues/Quesries/Problems.

One Suggestion Tom , Can you incorporate following changes in your site.

1. Like Other site , as and when new qustsion is posted on your site , can you
give ALERT email to whole asktom communities with the Question Link.

2. Can your site , send a ORA (Oracle) TIP OF THE DAY , to all registered asktom
users. The question will be given with multi choice answere selection.

3. Can you intoduced "RATING SYSTEM". Say those who are posting maximum number
of questions on your site which you are going to publish on your site , which
really carry some weight.

4. Is it possibel to provide " ON LINE CHAT WITH Mr.TOM " on specified date and
time regarding oracle topics.

All this are just thought's .

Butanyway Thank a lot for offering such nice services to ORA COMMUNITIES.

Regards
Rating: 5
Yea, Yea !! I got one too! July 03, 2002
Reviewer: John from MA
#5. How bout you stop over my place of business, fix my sloppy code (not as bad
as it use to be, thanks for that!)
teach my DBA the importance of access to the V$ views so we can better do our
job,ooh! actually, since your taking suggestions and have all this time on your
hands, how bout rewritting the 30,000 some odd manuals that oracle has into an
easier, beer on the beach kind of read.

Love the site, Tom and thanks for all the help.
(OK OK how bout just the books)
Rating: 5
Documentation? October 07, 2002
Reviewer: Robert from Memphis, USA
Tom,

Can you tell us where to find documentation on the specific JAVA functions
(objects?)...
...Specifically the 'FILE' (function, object, 'thing'?), from your example.
I am wanting to investigate a way to return the privilege and datestamp info
from the UNIX box, similar to what you would see with a 'ls -l' command.

Thanks,

Robert.
Followup:

http://java.sun.com/
under documentation.  Takes about a year to read it all.  Java isn't "small".
Rating: 4
Need your suggestion October 08, 2002
Reviewer: Daniel from India
Hi Tom,
 I get comma-separated values (e.g. 3,4,5,6,) as input. I parse these values,
with using some oracle functions. After reading this Java source example,
thought of using java source to parse this data. Because I read that parsing in
java program is efficient than parsing in pl/sql. So which will be the efficient
way? Using oracle function or using java source? Please, I need your suggestion

Thanks in advance

daniel
Followup:
I'll race your java code with my plsql code -- we'll see who wins.

Parsing a comma delimited string is TRIVIAL in plsql.  Use the language you feel
comfortable in.
Rating: 4
MalFormedInputException October 08, 2002
Reviewer: A reader
Hi Tom,

A Similar Program that i had written and tried to upload into the Oracle
Database Using the Create Or Replace Java Stmt. I received a
MalFormedInputException.

My Database CharSet is UTF8.

Contacted support but did not get a convincing answer on this.

Regards,
Ganesh R

-------------------Code of the Java Programm---------------

Create Or Replace and compile Java Source Named FileHandling
As
import java.io.*;

class FileHandling extends Object implements FileFilter
{
    public FileHandling(String dir, String newFile) throws IOException
    {
        openFiles(dir, newFile);
    }

    public void openFiles(String dir, String fileNew) throws IOException
    {
        File    dirFile;
        File     sqlFile;
        File     newFile;
        File    dirListing[];

        FileWriter     fw;
        FileReader     fr;

        dirFile=new File(dir);

        newFile = new File(fileNew);
        fw=new FileWriter(newFile.toString(),false);

        int i=0;

        if (dirFile.isDirectory())
        {
            System.out.println("Parsing Directory... " + dirFile + " \n");
            dirListing = dirFile.listFiles(this);
            for (i = 0;i< dirListing.length;i++)
            {
                if (dirListing[0].isDirectory())
                {
                    openFiles(dirListing[i].toString(), fileNew);
                }
                else
                {
                    fr=new FileReader(dirListing[i]);

                    System.out.print("Writing File " + dirListing[i] + " ");
                    if (!appendFiles(fw,fr))
                    {
                        System.out.println("Unable to Write File : " +
dirListing[i] + " \t\t");
                    }
                    else
                    {
                        //System.out.println("File " + dirListing[i] + "
Successfully Written");
                    }

                }
            }
            System.err.println("\nRead " + i + " Files/Directory from Directory
" + dirFile);
        }
        else
        {
            System.err.println("Uanble To Parse Directory... " + dirFile);
        }
    }

    public boolean accept(File testFile)
    {
        if (testFile.isDirectory())
        {
            return true;
        }
        else if (testFile.isFile())
        {
            if (testFile.getName().endsWith(".sql"))
            {
                return true;
            }
        }
        return false;
    }

    public boolean appendFiles(FileWriter fw, FileReader fr) throws IOException
    {
        if (fr.ready())
        {
            int i = 0;
            int x=0;
            do
            {
                i=fr.read();
                x++;
                if ((x == 16384))
                {
                    x=0;
                    System.err.print(".");
                }
                if (i != -1)
                    fw.write(i);
            }while(i != -1);
            fw.write("\n".toCharArray());
            fw.write("/*".toCharArray());
            for(int j=0;j<118;j++)
            {
                fw.write('*');
            }
            fw.write("*/".toCharArray());
            fw.write("\n".toCharArray());
            fw.flush();
            fr.close();
            System.err.println("");
            return true;
        }
        else
        {
            System.err.println("");
            return false;
        }
    }

    public static void ConcatNow(String newFile, String dirFile) throws
IOException
    {
        new FileHandling(dirFile,newFile);
    }
}

------------------------End Of Java Code-------------------
Followup:
I didn't have any issues running your code in Oracle 9iR1 with UTF8.  It ran
verbaitim.

You didn't give me the error message, version, platform, tar# from support, etc
etc etc... so thats all I can say.
Rating: 4
Sorry About That ... October 08, 2002
Reviewer: A reader
Here is the error. It is an error when i try to run it.

Ganesh@Db9i.Gtfsgulf.Com on 9.0.1.3.1> ED
Wrote file afiedt.buf

  1  cREATE OR REPLACE Procedure ConcatFile(newFile Varchar2, dirFile Varchar2)
  2  As
  3* Language Java Name
'FileHandling.ConcatNow(java.lang.String,java.lang.String)';
Ganesh@Db9i.Gtfsgulf.Com on 9.0.1.3.1> /

Procedure created.

Ganesh@Db9i.Gtfsgulf.Com on 9.0.1.3.1> eXEC CONCATfILE('a.TXT','D:\iFLEX');
BEGIN CONCATfILE('a.TXT','D:\iFLEX'); END;

*
ERROR at line 1:
ORA-29532: Java call terminated by uncaught Java exception:
sun.io.MalformedInputException
ORA-06512: at "GTFSDXB\GANESH.CONCATFILE", line 0
ORA-06512: at line 1

Ganesh@Db9i.Gtfsgulf.Com on 9.0.1.3.1>

Regards,
Ganesh R
Followup:
See -- this is why I always always always want a straight cut and paste.  First
you say:

<quote>
A Similar Program that i had written and tried to upload into the Oracle
Database Using the Create Or Replace Java Stmt. I received a
MalFormedInputException.
</quote>

which I reasonably took to mean "Hey, I cannot get this to compile -- I get this
error "malformedinputexception"...

but lo and behold, it is not a compile time error at ALL!!! its a runtime
error...

Ok, run your code like this and debug it (I get a NullPointerException myself).
There is nothing "wrong" at the Oracle end here -- there is something wrong in
your code somewhere.

Use dbms_java so you can see your system.out.printlns and add copious amounts of
"debug" statements to see what is causing it to fail, eg:

ops$oracle9i@ORA9IUTF.WORLD> CREATE OR REPLACE Procedure ConcatFile(newFile
Varchar2, dirFile Varchar2)
  2  As
  3  Language Java Name
'FileHandling.ConcatNow(java.lang.String,java.lang.String)';
  4  /

Procedure created.

ops$oracle9i@ORA9IUTF.WORLD> exec dbms_java.set_output( 1000000 )
PL/SQL procedure successfully completed.

ops$oracle9i@ORA9IUTF.WORLD> set serveroutput on size 1000000

ops$oracle9i@ORA9IUTF.WORLD> exec concatfile( 'a.txt', '/tmp/' );
Parsing Directory... /tmp
Parsing Directory... /tmp/lost+found
java.lang.NullPointerException
at FileHandling.openFiles(FILEHANDLING:31)
at FileHandling.openFiles(FILEHANDLING:35)
at FileHandling.<init>(FILEHANDLING:7)
at FileHandling.ConcatNow(FILEHANDLING:117)
BEGIN concatfile( 'a.txt', '/tmp/' ); END;

*
ERROR at line 1:
ORA-29532: Java call terminated by uncaught Java exception:
java.lang.NullPointerException
ORA-06512: at "OPS$ORACLE9I.CONCATFILE", line 0
ORA-06512: at line 1

So, I can see we are dying (in my case) after looping 2 times

After correcting the code to deal with the NULL pointer exception via:

            dirListing = dirFile.listFiles(this);
            for (i = 0;dirListing != null && i< dirListing.length;i++)
            {
                if (dirListing[0].isDirectory())

I get:

ops$oracle9i@ORA9IUTF.WORLD> exec concatfile( 'a.txt', '/tmp/' );
Parsing Directory... /tmp
Parsing Directory... /tmp/lost+found
Read 0 Files/Directory from Directory /tmp/lost+found
Parsing Directory... /tmp/.rpc_door
Read 0 Files/Directory from Directory /tmp/.rpc_door
Parsing Directory... /tmp/.pcmcia
Read 0 Files/Directory from Directory /tmp/.pcmcia
Parsing Directory... /tmp/.X11-unix
Read 0 Files/Directory from Directory /tmp/.X11-unix
Parsing Directory... /tmp/.X11-pipe
Read 0 Files/Directory from Directory /tmp/.X11-pipe
Uanble To Parse Directory... /tmp/a.sql
Uanble To Parse Directory... /tmp/flat10674.sql
Uanble To Parse Directory... /tmp/x.sql
Uanble To Parse Directory... /tmp/t.sql
Parsing Directory... /tmp/test
Writing File /tmp/test/runall.sql
Read 1 Files/Directory from Directory /tmp/test
Parsing Directory... /tmp/kk
Read 0 Files/Directory from Directory /tmp/kk
Uanble To Parse Directory... /tmp/gork.sql
Uanble To Parse Directory... /tmp/test.sql
Parsing Directory... /tmp/empty
Read 0 Files/Directory from Directory /tmp/empty
Parsing Directory... /tmp/clntst8.7259
Read 0 Files/Directory from Directory /tmp/clntst8.7259
Parsing Directory... /tmp/clntst8.15037
Read 0 Files/Directory from Directory /tmp/clntst8.15037
Parsing Directory... /tmp/clntst8.21730
Read 0 Files/Directory from Directory /tmp/clntst8.21730
Uanble To Parse Directory... /tmp/flat2465.sql
Uanble To Parse Directory... /tmp/flat20540.sql
Uanble To Parse Directory... /tmp/installhelp.sql
Uanble To Parse Directory... /tmp/alter_rbs8i.sql
Parsing Directory... /tmp/clntst8.8455
Read 0 Files/Directory from Directory /tmp/clntst8.8455
Parsing Directory... /tmp/clntst9.22241
Parsing Directory... /tmp/clntst9.22241/objs
Read 0 Files/Directory from Directory /tmp/clntst9.22241/objs
Read 1 Files/Directory from Directory /tmp/clntst9.22241
Parsing Directory... /tmp/.removable
Read 0 Files/Directory from Directory /tmp/.removable
Parsing Directory... /tmp/clntst8.15137
Read 0 Files/Directory from Directory /tmp/clntst8.15137
Uanble To Parse Directory... /tmp/obj.sql
Uanble To Parse Directory... /tmp/xw.sql
Uanble To Parse Directory... /tmp/10565.sql
Uanble To Parse Directory... /tmp/10593.sql
Uanble To Parse Directory... /tmp/10599.sql
Parsing Directory... /tmp/demo
Read 0 Files/Directory from Directory /tmp/demo
Parsing Directory... /tmp/tkyte
Read 0 Files/Directory from Directory /tmp/tkyte
Parsing Directory... /tmp/clntst9.23657
Parsing Directory... /tmp/clntst9.23657/objs
Read 0 Files/Directory from Directory /tmp/clntst9.23657/objs
Read 1 Files/Directory from Directory /tmp/clntst9.23657
Read 33 Files/Directory from Directory /tmp

PL/SQL procedure successfully completed.

so it ran thru OK for me.
Rating: 4
arrange by files created order October 08, 2002
Reviewer: rajesh
hi tom

while displaying is that is possible to obtain
the list in such a manner as
the file created last comes first and so on

thanks in adv

raj
Followup:
Don't know, I don't program java extensively (just as little as I need).  That
is more of a "java" question (eg: in java, how can I get the timestamp or can
you just feed me the files in sorted order)....

Maybe devtrends.oracle.com can help
Rating: 5
Parsing in PLSQL October 08, 2002
Reviewer: Randy
You threw in that parsing a csv in PLSQL is trivial.  I'm not feeling
particularly clever today so I'd like to see your method.  Even though what I
have works (barely), I'm sure yours is better than what I have.

Thanks.
Followup:
search this site for

str2table
Rating: 5
Thanks October 10, 2002
Reviewer: Gururaj Kulkarni from Bangalore,Karnataka, India
Tom,

You said: You are using a pre-8i sqlplus version.  You
should upgrade the client software to take advantage of the
database new features correctly.

Could you tell us what are those features ?

Thanks
-Gururaj
Followup:
well, the timing would be one.

Ability to create "new types of things" in sqlplus without sqlplus getting in
the way (eg: consider

$ sqlplus scott/tiger@ora817dev

SQL*Plus: Release 8.0.6.0.0 - Production on Thu Oct 10 06:50:01 2002

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production

scott@ORA817DEV.US.ORACLE.COM> set echo on
scott@ORA817DEV.US.ORACLE.COM> @rand
scott@ORA817DEV.US.ORACLE.COM> create or replace and compile java source named
"RandomNumGenerator"
  2  as
  3  public class RandomNumGenerator
  4  {
  5      public static double random( java.lang.Short max )
  6      {
  7          double rand     = Math.random();
create or replace and compile java source named "RandomNumGenerator"
                                                *
ERROR at line 1:
ORA-29536: badly formed source: Encountered "<EOF>" at line 5, column 40.
Was expecting one of:
"instanceof" ...

hundreds of errors chopped

SQLPlus doesn't understand the new commands and chokes on them.

and so on.
Rating: 4
Old SQL*Plus version is better for basic stuff! October 10, 2002
Reviewer: Robert from Memphis, USA
Tom, and all,

One thing that the 7.3.4 version of GUI sql*plus has over 8i... is that the old
version has a 'Cancel' button so you can stop your query without killing your
sql*plus session and sql*plus screen... thus losing all your buffered output.
Why even use GUI sql*plus? ... because its easier to copy/paste (windoze
notwithstanding)... and it has the nifty variable selection feature that lets
you copy individual columns for a set of rows instead of the entire vertical
chunk.

Thanks,

Robert
Followup:
I find the DOS window trivial to cut and paste from -- I just selected text and
hit enter (copy).  I just hit "alt space" ep  and it pasted.

The DOS window copies with "squares" as well ;)

CTL-C always works in the dos window to cancel a query.
Rating: 4
May we following the rabbit trail a little further? October 22, 2002
Reviewer: Robert from Memphis, USA
Tom,

I respect your opinion so I am considering looking into using the DOS sql*plus
program.

I have been able to size the buffer width appropriatly (1000 chars wide so I can
issue 'select *' on wide tables), so far so good...

...but it seems a pain to copy and paste.
With the GUI sql*plus you can just do a cntl-c, cntl-v... very simple... but
with DOS, you have to go thru the whole rigaramarole of 'marking', copying, then
pasting.....

..... Could you please give an example for some of us who may be a little slower
to catch on... an actual example of how you 'mark', copy and paste in a DOS
sql*plus session...including all keystrokes, mouse-clicks, etc.

Like I said... GUI sql*plus is a snap... swipe with the mouse, cntl-c,
cntl-v.....DONE! .......

Your reply would be greatly appreciated!

Thanks,

Robert.
Followup:
Well, I don't actually have windows installed anywhere anymore so this is from
memory.

You can adjust the properties to "smart select" or something like that -- at
least on windows NT.  It was the first thing I always did to dos windows (it
could be made "sticky" so all dos windows would have it)

Then when you clicked and dragged with a mouse - it would select text.

So in a windows NT dos window it was:

o select text
o hit enter -->> selected text in copy buffer

o alt-space
o e
o p

and it would paste it.
Rating: 4
RE: Old SQL*Plus version is better for basic stuff! October 23, 2002
Reviewer: Ed from Cheshire, UK
Robert,

You can still cancel queries in newer versions of Windows SQL*Plus by using the
menu (File -> Cancel).

Another great thing about Windows SQL*Plus is the facility to copy and paste
with a single mouse operation:  Select what you want using the left mouse button
and dragging - then, whilst the left button is still depressed, click the right
button and what you had selected is copied straight to the SQL*Plus command line
(this is internal to SQL*Plus - it doesn't copy to the Windows clipboard
buffer).

I use this feature dozens of times a day when I want to quickly re-sun a SQL
statement that I ran half an hour ago.
Followup:
I just hit the f7 button or arrow up in character mode to get a history of
commands myself (well, I used to anyway when I ran windows)
Rating: 3
RE: Old SQL*Plus version is better for basic stuff! October 23, 2002
Reviewer: Ed from Cheshire, U.K.
I don't think that's ever worked in Windows SQL*Plus - that's one of the
criticisms one of my non-Oracle colleagues has of it.  If it works in the DOS
version, I'd be tempted to change.
Rating: 4
If not Windows then what ?! October 23, 2002
Reviewer: Robert from Memphis, USA
Tom,

You said you don't have Windows installed anywhere.

Can you please tell us what OS you do use.

Also, If you don't use Windows... what do you do for spreadsheets, word
processors, etc.

Thanks,

Robert.
Followup:
see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6090133761547
and
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:6066234722893
(search for crossover on that one)

I also use open office (but I need word for authoring with WROX, the comment
facility is different between the two -- but 99.999% of the functionality is
there)
Rating: 5
copy & paste from dos window October 24, 2002
Reviewer: John from San Francisco, CA
Robert,

I usually do the following on XP/2000 (been a while since I've used NT, so the
exact wording may have changed) for dos windows.

Start --> Run --> cmd

You should now have a dos window.  Right click on the title bar, and select
Properties.  You want to enable QuickEdit mode (I also adjust the layout
properties such as window size--so it's usable).  Click OK, and save properties
for future windows with same title.

Now you should be able to select text with the left mouse button, click the
right mouse button to copy the selected text, another click of the right mb will
paste the text into this dos window or any other (or ctrl-v into other apps).
Rating: 5
AWESOME!! November 06, 2002
Reviewer: Russell Hertzberg from Roanoke, VA
This was a great code snippet!  I've been looking for code like this for a
while...nice, simple and to the point!!  Now I can take this and expand it to
meet my needs.  Thank You so much!
Rating: 5
December 19, 2002
Reviewer: A reader
Excellent example!!

 can we read directory from machine other then oracle server?

We have requirment to read App server directories from PL/SQL.

Thanks
Followup:
the directory needs to be mounted on the server in order for the server to read
it.

you need to make it accessible to Oracle if you want a stored procedure to read,
yes.
Rating: 3
Further Java reading December 20, 2002
Reviewer: G Johnson from UK
<advert>
For people asking about further Java reading, I learnt Java from Sun Press' Just
Java 2 by Peter Van der Linden. As a DBA I found this book extremely useful for
getting enough Java under my belt without feeling like I was about to have to
change careers to learn a little something useful!

Paperback - 1113 pages (1 December, 2001)
Prentice Hall PTR; ISBN: 0130320722
</advert>
Rating: 5
Great approach. February 07, 2003
Reviewer: Chandra S.Reddy from India
Tom,
You are really great.

This example is highly useful and highly reliable.

Thanks
Rating: 5
reading files in a directory -- how to get a list of available files. April 23, 2003
Reviewer: Artur from LITHUANIA
Very Very helpful even for those who do not know java and not very familiar in
ORACLE :]
(works great on 9i (9.2.0.2.0))

THANX
Rating: 5
Superb!!! May 13, 2003
Reviewer: man2002ua from Ukraine
Tom, you are BEST!
Rating: 5
need some help July 08, 2003
Reviewer: binu from (TN)India
i am tring to unzip the zipped file from c:\test_java\ to c:\test
this program executes prefectly in java but when i execute thru PL/SQL
i face some problem (i.e.) it shows "PL/SQL procedure successfully completed."
but no files are unzipped into c:\test.

can u explain where did i go wrong??

create or replace and compile java source
named "unzip_xml"
as
import java.io.*;
import java.util.*;
import java.util.zip.*;
import java.text.*;
import java.lang.*;

class unzip_xml
{
    public static void unzip(String zip_name,String Extract_to)
    throws Exception
    {
        try
        {
            String str;
            String xls_file;
                int n=0;
            ZipEntry ze;
            File f_xml=new File(Extract_to);
            String xmlfls[]=f_xml.list();
            for(int k=0;k<xmlfls.length;k++)
            {
                File f=new File(Extract_to+xmlfls[k]);
                if (!(f.isDirectory()))
                    f.delete();
            }
            FileInputStream fis=new FileInputStream(zip_name);
            ZipInputStream zis=new ZipInputStream(fis);
            while (true)
            {
                ze=zis.getNextEntry();
                if (ze==null)
                    break;
                str=ze.getName();
                            int x = fis.available();
                            byte [] rgb = new byte [x];
                            FileOutputStream fout = new
FileOutputStream(Extract_to+get_zip_xml_file(str));
                while((n=zis.read(rgb))>-1)
                {
                    fout.write(rgb,0,n);
                }
                fout.close();
                zis.closeEntry();
            }
            zis.close();
            fis.close();
        }
        catch (Exception e)
        {}
    }//end of unzip_xml()

    public static String get_zip_xml_file(String str)
    {
        int n=str.indexOf('.');
        String file_name="";
        for (int j=(n-1);j>=0;j--)
        {
            if (str.charAt(j)!='/')
                file_name=str.charAt(j)+file_name;
            else if (str.charAt(j)=='/')
            {
                file_name=str.substring(j+1,str.length());
                break;
            }
        }
        return file_name;
    }//end of get_zip_xml_file()
}
/
create or replace procedure proc_unzip_xml(p_zip_name varchar2,p_extract_to
varchar2)
as language java name 'unzip_xml.unzip(java.lang.String,java.lang.String)';
/
exec proc_unzip_xml('c:\\\\test_java\\\\xml-55-0001-08072003
9-36-36.zip','c:\\\\test\\\\');

regards
Binu
Followup:
        catch (Exception e)
        {}

thats a beauty eh?  "on error, any error, just kindly ignore it -- pretend it
doesn't exist"

You might consider deleting those lines of code -- letting the error, which
might be due to \\\\ or lack of privs -- to propagate up.

PLSQL is not Java -- \ is not a "special character", \\ is not \, \\ is \\ in
plsql/sql.

(and remember, we catch exceptions IF and ONLY IF we are

a) expecting them
b) can do something about them

else we let them propagate up and out of our code.  better to fail with a
message then APPEAR to succeed)
Rating: 5
need more help July 08, 2003
Reviewer: binu from (TN)India
i tried your first sample program

i get an error as
SP2-0734: unknown command beginning "sql { INSE..." - rest of line ignored.
and compile java source named "DirList"
                              *
ERROR at line 2:
ORA-29536: badly formed source: Encountered "( :" at line 16, column 8.
Was expecting one of:
";" ...
"++" ...
"--" ...
"." ...
"[" ...
"(" <INTEGER_LITERAL> ...
"(" <FLOATING_POINT_LITERAL> ...
"(" <CHARACTER_LITERAL> ...
"(" <STRING_LITERAL> ...
"(" "true" ...
"(" "false" ...
"(" "null" ...
"(" "this" ...
"(" "super" ...
"(" "(" ...
"(" "new" ...
"(" "void" ...
"(" "boolean" ...
"(" "char" ...
"(" "byte" ...
"(" "short" ...
"(" "int" ...
"(" "long" ...
"(" "float" ...
"(" "double" ...
"(" <IDENTIFIER> ...
"(" "+" ...
"(" "-" ...
"(" "++" ...
"(" "--" ...
"(" "~" ...
"(" "!" ...
"(" "#sql" ...
"(" ")" ...
":" ...
<IDENTIFIER> ...
"=" ...
"*=" ...
"/=" ...
"%=" ...
"+=" ...
"-=" ...
"<<=" ...
">>=" ...
">>>=" ...
"&=" ...
"^=" ...
"|=" ...

Thanking u
Followup:
guess you need to look at your cut and paste and figure out where in your cut
and paste you messed up?  at least show US your cut and paste -- just like I
show you (you don't need to put the entire error message -- but the code is
important, you cut and pasted the code wrong)
Rating: 5
follow up July 08, 2003
Reviewer: binu from (TN)India
my oracle version is
Oracle8i Enterprise Edition Release 8.1.6.1.0 - Production
PL/SQL Release 8.1.6.0.0 - Production
CORE    8.1.6.0.0       Production
TNS for Linux: Version 8.1.6.0.0 - Production
NLSRTL Version 3.4.0.0.0 - Production
Rating: 4
Array vs temp table. July 08, 2003
Reviewer: Darren from London, UK
Tom,

you inserted the values into a temporary table  (no bulk binds either ;)) .. is
there any (dis)advantage of returning this result set in an array rather than
inserts into sql tables?
Followup:
figured there would not be hundreds or thousands of directory entries.

I liked the temp table here cause I wanted to write as little java as humanly
possible, as fast as I could -- hence insert was easier then messing about with
IN OUT collection types.

Matter of preference.
Rating: 4
Interacting with the OS -- what to learn? July 08, 2003
Reviewer: Brian from Phoenix
From your book, I take it that Java is the first choice in interacting with the
OS.  However, the Oracle documentation suggests more than one route for
programming in Java.

One requires the client to install software; one does not.

I am not sure what to learn first for using Java in Oracle.  I'm okay with SQL
now; I'm starting to get a grasp of PL/SQL (I still need to get a better handle
on packages -- reminds me a lot of C++).  I would like to learn Java in August.
But I'm not sure where to start.

I am using JServer 9.2.

The only two reasons why I can see using Java at all is for interacting with the
OS and maybe GUI stuff.

Thanks
Followup:
do you mean java inside the database?

I don't follow you -- you talk about Oracle interacting with the OS which seems
to imply java stored procedures....

then you talk about client software?  I don't see the connection.
Rating: 4
Clarification July 08, 2003
Reviewer: Brian from Phoenix
Yep, I'm having a problem following me to.

Here's the documentation I am referring to:
http://download-west.oracle.com/docs/cd/B10501_01/java.920/a96656/invokeap.htm#1007092
It's says to learn to write stored procedures first then to worry about SQLJ and
JDBC.

But when I go out to www.bookpool.com and start looking at Java books.  The
above starts to blend together and the confusion starts.

I read your chapter on java stored procedures, but I don't understand the java.
So do I go out and pick up a book on Java 2 and download the stuff from Sun?  Or
is there a book that discusses Java 2 with Oracle using store procedures?

I'm just confused as how to learn this stuff.

Do I go out and pick up a book on Java 2?  Read through it and learn it, and
then read the Oracle documentation.  Or do I go out and find a Oracle specific
book on Java?

I would like to be able to understand your java stored procedures for
interacting with the OS and maybe write some GUI stuff later on.

Thanks.
Followup:
Java is just a programming language, like C or C++.

You can write stored procedures in java.

You can write client side applications (guis or command line programs) in java.

You can write code that runs in an application server in java.

Java is just a language.

You will not be writing j2ee stuff inside the database, you would be doing
little stored procedures (only when PLSQL cannot do it, as the most efficient
language in the database for manipulating data will be that -- in fact, it would
be the most efficient inside OR out actually).

If you want to do little snippets of java, java stored procedures are great.
All you need is sqlplus and an editor.

If you want to write full blown java apps -- then you need the client side
tools.
Rating: 4
clarification July 10, 2003
Reviewer: reader from (Delhi)India
thank u tom as usual u r most helpful for me. but one question about this
this can read the files only in the oracle server but if at all i want
to read the files from my client machine how to go about by??

Sorry if i am wrong

rajeev
Followup:
(hey, your keyboard is suffering some serious failures.  It is dropping
exceeding important characters, namely "vowels".  without vowels -- I feel like
I'm reading a cell phone.)

In order to read files on the client, one writes a client side application or
one uses the operating system to make the client side files visible to the
server.

Otherwise, we'd have what some people might term "a virus" as it could be used
to steal any file we wanted.....  Oracle cannot just reach out and grab files
from a client, the client has to give them to us.
Rating: 5
Help With Next Step September 24, 2003
Reviewer: Rob H. from Boston, MA
What technique would you then use to loop through the file names returned from
dir_list... before passing the filename to UTL_FILE.FOPEN for processing?
Followup:
for x in ( select * from dir_list )
loop
    ... process x.filename ....
Rating: 4
Great information December 01, 2003
Reviewer: Mike from St. Louis
Tom:

The original poster was after a way to feed an O/S directory listing into
his/her PL/SQL code.  Is an external language call still required given Oracle
9.2 and Oracle directory objects?

Maybe its wishful thinking but since we can CREATE DIRECTORY with full path to
the disk location, maybe we can interrogate that directory object (from PL/SQL)
to have it yield a file listing (or a listing limited by a file pattern) without
having to code external routines.  Is that possible?

Thanks,

-Mike
Followup:
still needed to read the contents of a directory, yes.
Rating: 4
Date and Time? December 03, 2003
Reviewer: Mike from St. Louis
Thanks again Tom.  Follow up question from a non-Java guy... the Java code and
global temp table solution above work great.  Does anyone know how to extend the
code to also extract the file modification date and size for each file in the
directory?  -Mike
Rating: 4
Does this routine work for Unix December 10, 2003
Reviewer: Suhail from NY, USA
Tom,

Does this example of creating global table and then a java stored procedure and
pl/sql wrapper works for UNIX environment too?

Thanks

Suhail
Followup:
well, the example was written on unix (linux) the only OS i have installed.

so, yes.

Oracle is Oracle is Oracle is Oracle.

You want the ultimate in portability?  do it in the database.
Rating: 3
Oracle is Oracle and Miracle too December 11, 2003
Reviewer: Suhail from Albany, USA
Thanks , Oracle is Oracle and Oracle is Miracle too. I love Oracle and its
features. I tested this routine on NT and it works OK. On Unix platform I asked
my DBA to grant my schema with DBMS_JAVA using
EXEC DBMS_JAVA.GRANT_PERMISSION('MYSCHEMA',
'java.io.FilePermission','/export/stage/data','read,write');

and he said he granted but when I dis exec get_dir_list( '/export/stage/data' );
I got all sorts of Java errors.

I did not ask for GRANT JAVAUSERPRIV to myschema.

Any idea why it snot working on UNIX box.

Thanks Tom
Followup:
i cannot see your terminal from way over here, so no -- i've no idea since I've
no clue what the error messages might be trying to tell us!
Rating: 4
Sorry I did not paste the error December 11, 2003
Reviewer: Suhail from Albany, NY
Here are my error listings,
SQL> exec get_dir_list( '/export/home/staging');
java.security.AccessControlException: the Permission (java.io.FilePermission
/export/home/staging read) has not been granted to DBT. The PL/SQL to
grant this is dbms_java.grant_permission( 'DBT',
'SYS:java.io.FilePermission', '/export/home/staging', 'read' )
at
java.security.AccessControlContext.checkPermission(AccessControlContext.java:207
)
at java.security.AccessController.checkPermission(AccessController.java:403)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
at
oracle.aurora.rdbms.SecurityManagerImpl.checkPermission(SecurityManagerImpl.java
:267)
at java.lang.SecurityManager.checkRead(SecurityManager.java:864)
at
oracle.aurora.rdbms.SecurityManagerImpl.checkRead(SecurityManagerImpl.java:156)
at java.io.File.list(File.java:771)
at DirList.getList(DirList:9)
BEGIN get_dir_list( '/export/home/staging'); END;

*
ERROR at line 1:
ORA-29532: Java call terminated by uncaught Java exception:
java.security.AccessControlException: the Permission (java.io.FilePermission
/export/home/staging read) has not been granted to DBT. The PL/SQL to
grant this is dbms_java.grant_permission( 'DBT',
'SYS:java.io.FilePermission', '/export/home/staging', 'read' )
ORA-06512: at "DBT.GET_DIR_LIST", line 0
ORA-06512: at line 1

I can read files using UTL_FILE option so I have access to this directory.

Thanks
Followup:
the error message:

SQL> exec get_dir_list( '/export/home/staging');
java.security.AccessControlException: the Permission (java.io.FilePermission
/export/home/staging read) has not been granted to DBT. The PL/SQL to
grant this is dbms_java.grant_permission( 'DBT',
'SYS:java.io.FilePermission', '/export/home/staging', 'read' )

it pretty clear is it not??????

it gives you the exact command that need to be executed!

You may have asked the dba to execute:

EXEC DBMS_JAVA.GRANT_PERMISSION('MYSCHEMA',
'java.io.FilePermission','/export/stage/data','read,write');

but either:

a) they did not
b) they did it to 'MYSCHEMA' and 'MYSCHEMA' != 'DBT'
c) they did it on a different server.

UTL_FILE uses utl_file_dir

Java uses DBMS_JAVA (much much more granular) for it's permissions.  because you
can utl_file it DOES NOT in any way imply java can read it.
Rating: 4
You are right December 12, 2003
Reviewer: Suhail from NY, USA
Actually our DBA has granted me EXECUTE on DBMS_JAVA and not the actual
permission. Sorry for the confusion.

Thanks a lot.
Rating: 5
for Mike: date and time December 13, 2003
Reviewer: Barbara Boehmer from Riverside County, CA USA
Here is some java by Cameron O'Rourke to list directories, including date and
time:
http://marvel.oracle.com/pls/otn/f?p=17000:8:::::F17000_P8_DISPLAYID:14855931322
Rating: 5
pl/sql and reading remote files December 26, 2003
Reviewer: Sangeeta from Germany
Database Version: Oracle 8.1.7.4 (on HP Unix)

Hi Tom,
I want to be able to read a windows directory through a PL/SQL function
(Database is on Unix..so
it is not a local directory on oracle server)..
and then do a string search on the directory..like find all the files that
begins with the word
"SPECIAL_PROJECT".  I know that the list of files expected won't be more than
three to four at a
time..
so i want these file names to be returned in a comma delimited format..
like 'SPECIAL_PROJECT1993.pdf,SPECIAL_PROJECT1994.pdf,SPECIAL_PROJECT1995.pdf'
Have you done anything like this..

Thanks...
Followup:
there will be 2 problems with this

a) how will the server get access to this remote filesystem.  Solve that problem
first (and it has nothing whatsoever to do with the database -- when you can
"ls" this windows directory, you can read step b below to continue on).  SAMBA
can be used to mount the windows directory.

Unless you can read the directory at the os level, Oracle cannot either.

b) use the approach above -- all of the code to do 100% of what you ask is
there.
Rating: 5
how to get a list of available files.", version 8.1.5 December 27, 2003
Reviewer: Leonard Anukam from Finland
Hei, Tom very great again. But do we have to create the DB directory pointing to
the OS directory before using this script.

Thanks
Followup:
no, this does not need a "direct object" in the database.

it does however require the appropriate dbms_java grant be issued.
December 27, 2003 Justis -- Thanks for the question regarding "Full outer join in 8i", version 8.1.6
You Asked
Hello,

We have 2 tables that hold person information (id,dept,name,email, ...), one is
the base table, the other is the pending table.  The pending table contains all
updates during the day to base table records and also any new records that are
created.  Each record has a status of active or inactive.  An inactive record in
the pending table is essentially a delete to the base table, so it cancels out
the matching base table record.

Everyday the pending records are sent to another system, processed, and the
remote system refreshes our base table with any changes.  Then the pending table
is flushed.

We would like to query both tables and get all the active records in the base
table and all records in pending table, then for the records that match on
primary key, we would return the data from the pending table, since it the most
recent.

Similarly, we would like to adjust the query to a certain dept or
id.

It seems 9i's full outer join would help get us started in creating such a
query, but we currently only have 8.1.6

base_table
id - pk
dept
name
email
status

pending table
id
dept
name
email
status

Some example data for day 1:

Base table

id dept name email    status
-- ---- ---- -------- ------
1  hr   joe  j@j.com  active
2  sale mary  m@m.com active
4  hr   bob   b@b.com active

Pending table

id dept name email     status
-- ---- ---- --------- ------
1  hr   joe  joe@j.com active
3  hr   tom  t@t.com   active
4  hr   bob  b@b.com   inactive

Our result set for all active people would be:

id dept name email
-- ---- ---- -----
1  hr   joe  joe@j.com
2  sale mary m@m.com
3  hr   tom  t@t.com

On day 2 the base table would look like

id dept name email   status
-- ---- ---- ------- --------
1  hr   joe  j@j.com active
2  sale mary m@m.com active
3  hr   tom  t@t.com active
4  hr   bob  b@b.com inactive

Any help is much appreciated.

Thank You,
Justis
and we said...
a full outer join would be:

select *
  from emp, dept
 where emp.deptno = dept.deptno(+)
union all
select *
  from emp, dept
 where emp.deptno(+) = dept.deptno
   and emp.deptno IS NULL;

for example -- this is the work we do in 9i (an outer join concatenated with an
ANTI-JOIN) -- just the syntax is "nicer".

Consider:

scott@ORA920> select *
  2    from emp, dept
  3   where emp.deptno = dept.deptno(+)
  4  union all
  5  select *
  6    from emp, dept
  7   where emp.deptno(+) = dept.deptno
  8     and emp.deptno IS NULL;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=10 Card=28 Bytes=1400)
   1    0   UNION-ALL
   2    1     HASH JOIN (OUTER) (Cost=5 Card=14 Bytes=700)
   3    2       TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=14 Bytes=448)
   4    2       TABLE ACCESS (FULL) OF 'DEPT' (Cost=2 Card=4 Bytes=72)
   5    1     FILTER
   6    5       HASH JOIN (OUTER)
   7    6         TABLE ACCESS (FULL) OF 'DEPT' (Cost=2 Card=4 Bytes=72)
   8    6         TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=14 Bytes=448)

scott@ORA920>
scott@ORA920> select *
  2    from emp full outer join dept
  3      on (emp.deptno = dept.deptno);

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=10 Card=15 Bytes=1755)
   1    0   VIEW (Cost=10 Card=15 Bytes=1755)
   2    1     UNION-ALL
   3    2       HASH JOIN (OUTER) (Cost=5 Card=14 Bytes=700)
   4    3         TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=14 Bytes=448)
   5    3         TABLE ACCESS (FULL) OF 'DEPT' (Cost=2 Card=4 Bytes=72)
   6    2       HASH JOIN (ANTI) (Cost=5 Card=1 Bytes=20)
   7    6         TABLE ACCESS (FULL) OF 'DEPT' (Cost=2 Card=4 Bytes=72)
   8    6         TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=14 Bytes=28)

so, they are about the same "planwise"
Review & Followup

Rating: 5
outer join not working in 9i? December 27, 2003
Reviewer: A reader
hello

I am worried because my outer joins are not working properly...

SYSADM@PS8>select b.column_name, b.index_name, b.index_owner
  2  from dba_ind_columns b
  3  where b.table_name in (
  4  'PS_PA_BC_EXT',
  5  'PS_BO_REL');

COLUMN_NAME          INDEX_NAME                 INDEX_OWNER
-------------------- -------------------------- ------------------------------
BO_ID_2              PSBBO_REL                  SYSADM
BO_ID                PS_PA_BC_EXT               SYSADM
REL_TYPE_ID          PS_BO_REL                  SYSADM
BO_ID_1              PS_BO_REL                  SYSADM
ROLE_TYPE_ID_1       PS_BO_REL                  SYSADM
BO_ID_2              PS_BO_REL                  SYSADM
ROLE_TYPE_ID_2       PS_BO_REL                  SYSADM
BO_ID_1              PSABO_REL                  SYSADM
PA_LOPD_LEVEL        PS0PA_BC_EXT               SYSADM
BO_ID                PS0PA_BC_EXT               SYSADM
PA_LOPD_LEVEL        PSAPA_BC_EXT               SYSADM
PA_ACCOUNT_NO        PS_BC_ACC_IND              SYSADM

12 rows selected.

SYSADM@PS8>select owner, name, column_name
  2  from dba_part_key_columns
  3  where name in (
  4  'PS_PA_BC_EXT',
  5  'PS_BO_REL')
  6  and object_type='INDEX';

OWNER        NAME                                           COLUMN_NAME
------------ ---------------------------------------------- --------------------
SYSADM       PS_PA_BC_EXT                                   BO_ID

SYSADM@PS8>select b.column_name, c.column_name
  2  from dba_ind_columns b, dba_part_key_columns c
  3  where c.owner(+) = b.index_owner
  4  and c.name(+) = b.index_name
  5  and c.column_name(+) = b.column_name
  6  and c.owner = 'SYSADM'
  7  and c.object_type = 'INDEX'
  8  and b.table_name in (
  9  'PS_PA_BC_EXT',
 10  'PS_BO_REL');

COLUMN_NAME          COLUMN_NAME
-------------------- --------------------
BO_ID_1              BO_ID_1
BO_ID                BO_ID

As you can see the last join should return 12 rows but it returns 2... what am I
doing wrong?

TIA
Followup:
anytime I see:

where some_column(+) = some_other_column
  and some_column = <anything>

I know the query is wrong :)

if we "make up" some_column due to the outer join -- some_column will be NULL,
hence the second part can never ever be satistifed!!!

Not sure WHAT you were trying to do -- but I can say that by putting the
predicate

  6  and c.owner = 'SYSADM'
  7  and c.object_type = 'INDEX'

you "removed" the outer joined-ness of the query alltogether.
December 27, 2003 -- Thanks for the question regarding "join example, new vs old syntax", version 8.1.7
You Asked
Hi Tom,
I know in oracle9i we have the cross join and full outer join. Do they exist for
8i if so can you point me to the documentation. If they don't can you give an
example/breakdown of how to rewrite in 8i.

Thanks in advance,
and we said...
to join in 8i, just use a where clause:

select *
  from emp, dept
 where emp.deptno = dept.deptno;

in 9i and up, you could

select ename, dname, emp.deptno, dept.deptno
  from SCOTT.EMP inner join SCOTT.DEPT
    on emp.deptno = dept.deptno

to outer join 8i, use a (+)

select * from emp, dept where emp.deptno = dept.deptno(+)

is the same as the 9i syntax of:

select ename, dname, emp.deptno, dept.deptno
  from SCOTT.EMP LEFT outer join SCOTT.DEPT
    on emp.deptno = dept.deptno

whereas

select * from emp, dept where emp.deptno(+) = dept.deptno

is the same as:

select ename, dname, emp.deptno, dept.deptno
  from SCOTT.EMP RIGHT outer join SCOTT.DEPT
    on emp.deptno = dept.deptno

In 8i, a full outer join would be:

select * from emp, dept where emp.deptno = dept.deptno(+)
UNION ALL
select * from emp, dept where emp.deptno(+) = dept.deptno AND emp.deptno is
null;

versus the 9i syntax (which does the same amount of work -- no magic there)

select *
  from SCOTT.EMP FULL outer join SCOTT.DEPT
    on emp.deptno = dept.deptno
Review & Followup

Rating: 5
join example, new vs old syntax November 22, 2002
Reviewer: A reader
Tom excellent examples thank you very much.
One last thing I wanted to confirm cross join is the same as a cartesian
correct.
Followup:
I don't know what a "cross" join is myself -- not familar with that particular
terminology.

but it probably means "cross product" which would put it in a class with the
cartesian product - yes.
Rating: 4
Why November 22, 2002
Reviewer: Vladimir from Albany, US
Why did they come up with this confusing syntax?
How would I interpret
select ename, dname, emp.deptno, dept.deptno
  from SCOTT.DEPT LEFT outer join SCOTT.EMP
    on emp.deptno = dept.deptno
?
How would I join several tables?
Followup:
from (dept left out join emp on emp.deptno = dept.deptno ) left outer join T on
.....

ANSI came up with the syntax, not us.  We are just conforming to it.
Rating: 5
Where can I find these examples in 9i Manuals November 22, 2002
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom
I read an article about the ANSI syntax and how Oracle conforms to it in 9i.
However, the article was not exhaustive. Please let me know where can I find
more details in 9i manuals.
Followup:
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96540/statements_103a.htm#2065648
Rating: 4
Cross Product May 02, 2003
Reviewer: Jignesh from India
Hi Tom,
Recently i have cleared my first ocp paper 1z0001.  Of which i was asked the
following question -->

Which of the following statements regarding column subqueries is true?

a.    A pair wise comparison produces a cross product.
b.    A non-pair wise comparison produces a cross product.
c.    In a pair wise query, the values returned from the subquery are compared
individually to the value in the outer query.
d.    In a non-pair wise query, the values returned from the subquery are
compared as a group to the values in the outer query.

I dint manage to answer this question.  Could you please elaborate more on
"Cross Product".  I have searched the documentation but dont find any good
explanation on that.
Followup:
I've no clue what a "column subquery" is in the first place.  There is no such
thing, it is a made up term.

I hate multiple guess tests...

this question -- in my humble opinion -- is none sensical (and none too
relevant)

a cross product is a cartesian join -- but how you get from a subquery (and a
"column subquery" whatever the heck that is) to join semantics is beyond me.
Rating: 3
Cross join Algorithm September 23, 2003
Reviewer: Asim Naveed from Pakistan
Hi

I am working in java language and java script,
as front end and
oracle as backend , its a web application.
There is a table "mytable" in my database which have 30
columns.

A user inputs a number (say N) in the internet browser,
then I have to display the data of
first N columns of the "mytable".

I issue the following SQL through ODBC regardless
of the value of N.

SELECT col1, col2, col3, .... col30 FROM mytable

Then when fetching rows, i use
getstring(1), getstring(2), ..... getstring(N).
to get only required no. of columns.

The list is displayed to the user, after that the user
click on NEXT button and then I have to cross join
all the displayed columns and display one long list.

One way I can do this is by using the following SQL.

SELECT * FROM
(SELECT col1 from mytable),
(SELECT col2 from mytable),
.
.
.

(SELECT colN from mytable)

BUT
I want to write a crossjoining algorithm in my front
end, and dont want to go back to database to get the
crossjoin.

Can you please point me to such algorithm,
any links, any books, any utility you have etc.

Thanks
Followup:
and of what possible use would this cartesian product be???

if you want to do it on the client - go for it.  it is just "code", you'll have
to write it.

i've never seen the need for it, so i have no links, books, utilities.
Rating: 1
Use of cartisian product September 24, 2003
Reviewer: Asim Naveed from Pakistan
Actually its a segmentation part of my application, which is
usually used in sales analysis. Note that the extra
dashes are just to make the output clear, <tab> dosent
seem to work properly.

SELECT * FROM mytable

sales===========Cost============Transaction Count
Ranges==========Ranges==========Ranges
-----===========----============-----------------
1-100-----------1-200-----------1-300
101-200---------201-400---------301-600
200-300---------401-600
301-400

Note that the 3 columns are not related in way, you can
just assume them independant arrays.

Then when the user see the above result, he presss NEXT,
then after cross joining all ranges, and getting no. of
Items sold for each segment, I will display the report like
this.

Segment No======Segment Description=============No. of Itms
----------======-------------------=============------------

1---------------Sales = 1-100---------------------------200
----------------Cost  = 1-200
----------------Transactions = 1-300

2---------------Sales = 1-100---------------------------30
----------------Cost  = 1-200
----------------Transactions = 301-600

3---------------Sales = 1-100---------------------------40
----------------Cost  = 1-200
----------------Transactions = 1-300

4---------------Sales = 1-100---------------------------200
----------------Cost  = 1-200
----------------Transactions = 1-300
.
.
.
.
.
all possible segments
.
.
.
.
Followup:
i'll say it one last time...

if you want to do this in the client -- go for it.

it is procedural code.
you own it.
you write it.

go for it.
Rating: 1
Sorry, a little correction September 24, 2003
Reviewer: Asim Naveed from Pakistan
Please correct he above output,

Description of Segment no. 3 and 4 should be

Segment no.3
-------------
Sales = 1-100
Cost  = 201-400
Transactions = 1-300

Segment no. 4
--------------
Sales = 1-100
Cost  = 201-400
Transactions = 301-600

Also didnt oracle use a cross join algorithm when
se do
SELECT * FROM TABLEA, TABLEB

?

Thanks
Rating: 4
Inner join v/s Equi join September 24, 2003
Reviewer: Anil Pant
Is inner join means a simple equi join ?
select a.col1, a.col2, b.col3
from t1 a, t2 b
where a.col1 = b.col3

is this known as inner join as Im bit confused ?If Im wrong pls correct
Followup:
thats an inner join, yes.
Rating: 3
Appologize September 25, 2003
Reviewer: Asim Naveed from Pakistan
I am extremly sorry , but you got me wrong, the
reason for reposting was NOT to ask you the question
again, BUT to answer your following question:-

"and of what possible use would this cartesian product
be???"

Thanks
Rating: 5
November 14, 2003
Reviewer: A reader
Rating: 5
difference between these queries December 23, 2003
Reviewer: A reader
Hi

I am struggling understanding what does the AND after left outer join on does,
consider these two queries

select ename, dname, b.deptno
from emp a, dept b
where a.deptno(+) = b.deptno
  and b.deptno in (10, 40)

ENAME      DNAME              DEPTNO
---------- -------------- ----------
CLARK      ACCOUNTING             10
KING       ACCOUNTING             10
MILLER     ACCOUNTING             10
           OPERATIONS             40

select ename, dname, a.deptno
from dept a
left outer join emp b
             on a.deptno = b.deptno
            and a.deptno in (10, 40)

ENAME      DNAME              DEPTNO
---------- -------------- ----------
CLARK      ACCOUNTING             10
KING       ACCOUNTING             10
MILLER     ACCOUNTING             10
           RESEARCH               20
           SALES                  30
           OPERATIONS             40

I am lost here, why deptno 20 and 30 appears?
Followup:
it appears to be an interesting side effect of "ON"

you seem to want to be using "where"

Think of it this way:

scott@ORA9IR2> select ename, dname, a.deptno
  2  from dept a
  3  left outer join emp b
  4               on a.deptno = b.deptno
  5              and 1=0
  6  /

ENAME      DNAME              DEPTNO
---------- -------------- ----------
           ACCOUNTING             10
           RESEARCH               20
           SALES                  30
           OPERATIONS             40

the left outer join syntax basically says "all rows in DEPT will be output".
Period.  All rows in DEPT will be output.

Now, each row in DEPT is joined to EMP based on the join condition -- which in
this case is:

on a.deptno = b.deptno and a.deptno in (10, 40)

if NO matching rows for that join condition are to be found -- MAKE UP a row
with null values and join to that.

That A is the "driving table" isn't material.

It is instructive in 9ir2 to use explain plan to see the actual predicates
applied in each case:

scott@ORA9IR2> delete from plan_table;

6 rows deleted.

scott@ORA9IR2> explain plan for
  2  select ename, dname, b.deptno
  3  from emp b, dept a
  4  where b.deptno(+) = a.deptno
  5    and a.deptno in (10, 40)
  6  /

Explained.

scott@ORA9IR2> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
--------------------------------------------------

--------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
--------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             |       |       |       |
|   1 |  MERGE JOIN OUTER    |             |       |       |       |
|   2 |   SORT JOIN          |             |       |       |       |
|*  3 |    TABLE ACCESS FULL | DEPT        |       |       |       |
|*  4 |   SORT JOIN          |             |       |       |       |
|   5 |    TABLE ACCESS FULL | EMP         |       |       |       |
--------------------------------------------------------------------

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

   3 - filter("A"."DEPTNO"=10 OR "A"."DEPTNO"=40)
   4 - access("B"."DEPTNO"(+)="A"."DEPTNO")
       filter("B"."DEPTNO"(+)="A"."DEPTNO")

Note: rule based optimization

20 rows selected.

there it is clear that the filter (3) is applied AND THEN the rows are
returned to join to emp (i've reversed your A/B in this first query to keep is
consistent with the others!!  A is DEPT and B is EMP

scott@ORA9IR2>
scott@ORA9IR2> delete from plan_table;

6 rows deleted.

scott@ORA9IR2> explain plan for
  2  select ename, dname, a.deptno
  3  from dept a
  4  left outer join emp b
  5               on a.deptno = b.deptno
  6              and a.deptno in (10, 40)
  7  /

Explained.

scott@ORA9IR2> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
--------------------------------------------------

--------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
--------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             |    82 |  2378 |   166 |
|   1 |  NESTED LOOPS OUTER  |             |    82 |  2378 |   166 |
|   2 |   TABLE ACCESS FULL  | DEPT        |    82 |  1804 |     2 |
|   3 |   VIEW               |             |     1 |     7 |     2 |
|*  4 |    TABLE ACCESS FULL | EMP         |     1 |    20 |     2 |
--------------------------------------------------------------------

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

   4 - filter("A"."DEPTNO"="B"."DEPTNO" AND ("A"."DEPTNO"=10 OR "
              A"."DEPTNO"=40) AND ("B"."DEPTNO"=10 OR "B"."DEPTNO
              "=40))

Note: cpu costing is off

19 rows selected.

Now, thats interesting -- the predicate  it applied to a view of EMP, it is
not really applied to DEPT at all -- this is the semantics of the ON clause in
an OUTER JOIN, compare to an ON + WHERE:

scott@ORA9IR2> delete from plan_table;

6 rows deleted.

scott@ORA9IR2> explain plan for
  2  select ename, dname, a.deptno
  3  from dept a
  4  left outer join emp b on a.deptno = b.deptno
  5   where a.deptno in (10, 40)
  6  /

Explained.

scott@ORA9IR2> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
--------------------------------------------------

-----------------------------------------------------------------------------
| Id  | Operation                     |  Name       | Rows  | Bytes | Cost  |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |             |     1 |    42 |     4 |
|*  1 |  HASH JOIN OUTER              |             |     1 |    42 |     4 |
|   2 |   INLIST ITERATOR             |             |       |       |       |
|   3 |    TABLE ACCESS BY INDEX ROWID| DEPT        |     1 |    22 |     1 |
|*  4 |     INDEX RANGE SCAN          | PK_DEPT     |     1 |       |     2 |
|   5 |   TABLE ACCESS FULL           | EMP         |    82 |  1640 |     2 |
-----------------------------------------------------------------------------

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

   1 - access("A"."DEPTNO"="B"."DEPTNO"(+))
   4 - access("A"."DEPTNO"=10 OR "A"."DEPTNO"=40)

Note: cpu costing is off

19 rows selected.

scott@ORA9IR2>

Here we see the predicate is applied to the table DEPT, not EMP.
Rating: 5
is this a bug or a feature? December 23, 2003
Reviewer: A reader
Hi

You are correct, I want to use where, however I ended up using AND instead of
WHERE because of Oracle University SQL course notes, it says:

Additional Conditions:
Applying Additional Conditions
You can apply additional conditions in the WHERE clause. The example shown
performs a join on the EMPLOYEES and DEPARTMENTS tables, and, in addition,
displays only employees with a manager ID equal to 149.

SELECT e.employee_id, e.last_name, e.department_id,
d.department_id, d.location_id
FROM employees e JOIN departments d
ON (e.department_id = d.department_id)
AND e.manager_id = 149;

This example however uses a simple equijoin I am not sure the difference.

I tried very hard to find out the meaning of AND after on and only found this
tiny note, nothing on Metalink nor the documentation (I am using 9.2.0.4 FYI)

I wonder, is this a bug or a feature? If it's a feature certainly it's not well
documentated!

If you try

select ename, dname, a.deptno
from dept a
left outer join emp b
             on a.deptno = b.deptno
            and a.deptno in (10, 40)

instead of a.deptno = b.deptno you put b.deptno = b.deptno or deptno = b.deptno
we all end up getting same result set. I checked the execution plan with
dbms_xplan and I was puzzled too!

Last question, why you delete plan table after run an explain? I never do it and
it works.... :-0
Followup:
thats the difference -- a simple JOIN does not have the rule that "all rows from
one of the tables are going to be sent down to the where clause no matter what"

I just delete to keep it "empty"
Rating: 5
forgot to say December 23, 2003
Reviewer: A reader
if you try the last part of my previous post I think this statement is not true,
that is not longer the join condition...!

a.deptno = b.deptno and a.deptno in (10, 40)
b.deptno = b.deptno and a.deptno in (10, 40)
deptno = b.deptno and a.deptno in (10, 40)

all three returns same results

=====================================================
on a.deptno = b.deptno and a.deptno in (10, 40)
if NO matching rows for that join condition are to be found -- MAKE UP a row
with null values and join to that.
Followup:
huh?  not sure where you are going there?
Rating: 4
For Jignesh above December 24, 2003
Reviewer: umesh from blore india
The answer is 2) the non pair subquery retuerns a cross product
pair or ( multiple) column  subquery is some thing like
select * from emp
where ( sal,comm) in ( select sal, comm from emp1 where ...)
Rating: 5
a bug or a feature December 24, 2003
Reviewer: A reader
Hi

I think that's a bug, how can we fill a bug to Oracle Tom?
Followup:
it is not a bug -- i thought I talked thru what is happening there.

A left outer join B on ( any set of conditions )

will return ALL rows from A and whenever the "any set of conditions" is
satisfied -- the rows from B.  If no rows in B exist after evaluating "any set
of conditions" -- then a NULL row is made up.

But - ALL rows from A will be returned.  By the very definition of an outer
join.
Rating: 1
do not agree December 24, 2003
Reviewer: A reader
It's true the very definition of OUTER join is return all rows even not matched
however we are using a predicate to restrict that

select ename, dname, a.deptno
from dept a
left outer join emp b
             on a.deptno = b.deptno
            and a.deptno in (10, 40) --> restriction, clearly saying we only
want deptno 10 and 40 from DEPT (and dont understand why this is applied to EMP
ALWAYS?!)

Which is not taken into consideration at all by Oracle I mean who on earth want
a result set which does not fit what he wants in the query? What is the use of
this kind of query....?! In version before 9i we never get this kind of result
sets!
Followup:
this whole thing is begining to remind me of the people who get mad at the
results of this query:

select * from emp where empno in ( select empno from dept );

but anyway....

you are NOT using a predicate.  if you were, i would agree with you.

WHERE begins a predicate.  If you used a predicate, you observe the behaviour
you seem to expect.

ON is a join clause, a new thing in Oracle SQL with 9i.  It restricts the rows
returned from EMP -- not DEPT -- in the context of an outer join.

You are just quite simply not understanding the syntax here.  ON != WHERE.

T1 left outer join T2 on ( any condition )

will by the very very very definition of an ANSI outer join return ALL ROWS of
T1 -- all of them -- regardless of what "any condition" specifies since "any
condition" is a join criteria for going from T1 to T2 -- it is not a predicate,
it does not restrict the rows being returned.

The reason you never "got this before 9i" is because quite simply there was no
syntax even remotely similar to this with the ON clause.  It is new.  In 8i
and before everything was in the predicate.  Now, everything ISN'T in the
predicate.

Now however, you understand how it works.  So, you can code correct sql.

The result set fits EXACTLY what you asked for -- you just didn't understand the
semantics here.

and you know -- we don't "make up" this syntax.  it is sort of the way it was
told to us that it should be.  This is the ANSI outer join syntax, not the
Oracle outer join syntax.
Rating: 4
can we find this explanation in any documentation? December 24, 2003
Reviewer: A reader
Hi

Is this new outer join feature explained anywhere in the docs?
Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96540/queries7.htm#2054625

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96540/statements_103a.htm#2080356
An outer join extends the result of a simple join. An outer join returns all
rows that satisfy the join condition and also returns some or all of those rows
from one table for which no rows from the other satisfy the join condition.
Rating: 5
how can we do this in 8i December 25, 2003
Reviewer: A reader
Hi

How can we get this result using 8i?

select ename, dname, a.deptno
from dept a
left outer join emp b
             on a.deptno = b.deptno
            and a.deptno in (10, 40)

ENAME      DNAME              DEPTNO
---------- -------------- ----------
CLARK      ACCOUNTING             10
KING       ACCOUNTING             10
MILLER     ACCOUNTING             10
           RESEARCH               20
           SALES                  30
           OPERATIONS             40
Followup:
scott@ORA9IR1> select ename, dname, a.deptno
  2  from dept a, (select * from emp where deptno in (10,40)) b
  3  where a.deptno = b.deptno(+);

ENAME      DNAME              DEPTNO
---------- -------------- ----------
CLARK      ACCOUNTING             10
KING       ACCOUNTING             10
MILLER     ACCOUNTING             10
           RESEARCH               20
           SALES                  30
           OPERATIONS             40

6 rows selected.
Rating: 4
To the reader who "does not agree" December 25, 2003
Reviewer: vc
As defined by the SQL'92 standard.

Assuming you have an outer join like this:

select * from t1 left join t2 on t1.x=t2.x and t2.y=15 where t2.z=17

't1' is the "preserved" table and 't2' is the "unpreserved" table.
Conceptually (not really),  the process is like this:

1. A Cartesian join is built.
2. Each resulting row is scanned and, if the join condition tests TRUE, the row
is kept;  if the condition tests FALSE,  then all the columns from t1 are kept,
the columns from t2 are converted to NULLs and possible duplicates are
eliminated.
3. The predicate from the WHERE clause is applied to the result set obtained in
Step 2.

For non-outer (inner) joins, it does not matter where you specify the conditions
to filter the Cartesian join.  The result will be the same whether you give the
conditions in the ON part or in the WHERE clause because there is no conceptual
"preservation" in Step 2.

VC
Rating: 5
which syntax is recommended December 27, 2003
Reviewer: A reader
Hi

With new join syntaxes which one should we use? From the documentation Oracle
seems to recommend the new syntax... any specific reason?
Followup:
it is entirely up to you.  99.99% matter of choice.

I find it easier to read the new style, but, i've been doing the other one so
long i don't use it myself -- especially for "regular joins".

for outer joins, i'll definitely consider it, the syntax is easier.

for full outer joins -- well, i hope never to have to use them (so expensive),
but if I do, i'll use the full outer join as that syntax makes it really easy.
Rating: 5
how´s the predicate applied December 27, 2003
Reviewer: A reader
I did some tests, here are the results

select e.first_name, e.last_name, d.department_name, e.department_id
from employees e
    right join departments d
            on e.department_id = d.department_id
           and d.department_id in (60, 90)

select e.first_name, e.last_name, d.department_name, e.department_id
from departments d
     left join employees e
            on e.department_id = d.department_id
           and d.department_id in (60, 90)

hr@ora92>explain plan for
  2  select e.first_name, e.last_name, d.department_name, e.department_id
  3  from employees e
  4      right join departments d
  5              on e.department_id = d.department_id
  6             and d.department_id in (60, 90);

Explained.

hr@ora92>@plan

PLAN_TABLE_OUTPUT

---------------------------------------------------------------------
| Id  | Operation            |  Name        | Rows  | Bytes | Cost  |
---------------------------------------------------------------------
|   0 | SELECT STATEMENT     |              |    27 |  1485 |    56 |
|   1 |  NESTED LOOPS OUTER  |              |    27 |  1485 |    56 |
|   2 |   TABLE ACCESS FULL  | DEPARTMENTS  |    27 |   432 |     2 |
|   3 |   VIEW               |              |     1 |    39 |     2 |
|*  4 |    TABLE ACCESS FULL | EMPLOYEES    |     1 |    18 |     2 |
---------------------------------------------------------------------

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

   4 - filter("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID" AND
              ("D"."DEPARTMENT_ID"=60 OR "D"."DEPARTMENT_ID"=90) AND
              ("E"."DEPARTMENT_ID"=60 OR "E"."DEPARTMENT_ID"=90))

Note: cpu costing is off

19 rows selected.

hr@ora92>explain plan for
  2  select e.first_name, e.last_name, d.department_name, e.department_id
  3  from departments d
  4       left join employees e
  5              on e.department_id = d.department_id
  6             and d.department_id in (60, 90);

Explained.

hr@ora92>@plan

PLAN_TABLE_OUTPUT

---------------------------------------------------------------------
| Id  | Operation            |  Name        | Rows  | Bytes | Cost  |
---------------------------------------------------------------------
|   0 | SELECT STATEMENT     |              |    27 |  1485 |    56 |
|   1 |  NESTED LOOPS OUTER  |              |    27 |  1485 |    56 |
|   2 |   TABLE ACCESS FULL  | DEPARTMENTS  |    27 |   432 |     2 |
|   3 |   VIEW               |              |     1 |    39 |     2 |
|*  4 |    TABLE ACCESS FULL | EMPLOYEES    |     1 |    18 |     2 |
---------------------------------------------------------------------

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

   4 - filter("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID" AND
              ("D"."DEPARTMENT_ID"=60 OR "D"."DEPARTMENT_ID"=90) AND
              ("E"."DEPARTMENT_ID"=60 OR "E"."DEPARTMENT_ID"=90))

Note: cpu costing is off

19 rows selected.

it seems that the AND always applies to EMPLOYEES, how so?
Followup:
I'm not sure what you point is here??

the left and right joins both work the same -- the results are the same.

what are you trying to show?
Rating: 5
the point is the AND after ON December 27, 2003
Reviewer: A reader
Hi

what I would like to know why the AND d.department_id in (60, 90) is always
applied to EMPLOYEES table no matter the order of join, doesnt it suppose to
apply to the outer table? For example

from employees e
    right join departments d

applies to employees e

from departments d
     left join employees e

applies to department d

but from the execution plan it always applies to employees e
Followup:
it is applied to the table being outer joined to.

e right join d

is the same as

d left join e

they are identical.
December 27, 2003 -- Thanks for the question regarding "Generating Dates between two date ranges", version 9.0.0
You Asked
Hi Tom ,

I need to write a query which will give me a list of dates between two date
ranges (start date and end date) .

For Example if my start date is 12/01/2003 and end Date is 12/31/2003. i need to
generate my output as

Date
----
12/01/2003
12/02/2003
12/03/2003
12/04/2003
12/05/2003
12/06/2003
12/07/2003
12/08/2003
.
.
.
so on till
12/31/2003

Can you tell me how to go about generating such a list using simple SQL query.

Thanks in advance
SV
and we said...
ops$tkyte@ORA9IR2> variable start_date varchar2(25)
ops$tkyte@ORA9IR2> variable end_date varchar2(25)
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec :start_date := '01-dec-2003'; :end_date :=
'31-dec-2003';

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select to_date(:start_date,'dd-mon-yyyy') + rownum -1
  2    from all_objects
  3   where rownum <=
to_date(:end_date,'dd-mon-yyyy')-to_date(:start_date,'dd-mon-yyyy')+1
  4  /

TO_DATE(:
---------
01-DEC-03
02-DEC-03
03-DEC-03
04-DEC-03
05-DEC-03
06-DEC-03
07-DEC-03
08-DEC-03
09-DEC-03
10-DEC-03
11-DEC-03
12-DEC-03
13-DEC-03
14-DEC-03
15-DEC-03
16-DEC-03
17-DEC-03
18-DEC-03
19-DEC-03
20-DEC-03
21-DEC-03
22-DEC-03
23-DEC-03
24-DEC-03
25-DEC-03
26-DEC-03
27-DEC-03
28-DEC-03
29-DEC-03
30-DEC-03
31-DEC-03

31 rows selected.
Review & Followup

Rating: 5
Thanks December 28, 2003
Reviewer: A reader from New Jersey
Thanks Tom for answering the question . That was a very quick response.

Regards
SV
December 28, 2003 Suresh -- Thanks for the question regarding "DBMS_STATS", version 9i
You Asked
Tom,
   I created a table and tried to run monitoring and GATHER STALE option on the
table as below. Everything worked fine, except that the last_analyzed column
does not get updated in the user_tables table when I run the DBMS_STATS. Why is
it not getting updated? Also, I can know from user_tab_modifications how many
rows are inserted, updated, deleted from the table that has the monitoring
turned on. Can I know from any dictionary tables that those are the only rows
that get analyzed when we run DBMS_STATS Gather stale option and not the other
rows that were already there without any modifications.

SQL> create table emp as select * from scott.emp;
Table created.

SQL> select table_name, last_analyzed, monitoring from user_tables where
table_name = 'EMP';

TABLE_NAME                     LAST_ANAL MON
------------------------------ --------- ---
EMP                                      NO

SQL> begin
dbms_stats.gather_schema_stats ('test',  options => 'gather stale');
end;
/
PL/SQL procedure successfully completed.

SQL> select table_name, last_analyzed, monitoring from user_tables where
table_name = 'EMP';
TABLE_NAME                     LAST_ANAL MON
------------------------------ --------- ---
EMP                                      NO

SQL> alter table emp monitoring;
Table altered.

SQL> select table_name, last_analyzed, monitoring from user_tables where
table_name = 'EMP';

TABLE_NAME                     LAST_ANAL MON
------------------------------ --------- ---
EMP                                      YES

SQL> insert into emp(empno, ename) select rownum, 'abc' from all_objects where
rownum < 1000;
999 rows created.

SQL> commit;
Commit complete.

SQL> begin
dbms_stats.gather_schema_stats ('test',  options => 'gather stale');
end;
/
PL/SQL procedure successfully completed.

SQL> select table_name, last_analyzed, monitoring from user_tables where
table_name = 'EMP';
TABLE_NAME                     LAST_ANAL MON
------------------------------ --------- ---
EMP                                      YES

SQL> select table_name, inserts, deletes, updates from user_tab_modifications;

TABLE_NAME                        INSERTS    DELETES    UPDATES
------------------------------ ---------- ---------- ----------
EMP                                   999          0          0

Thanks,
Suresh
and we said...
answering in reverse.

A table is analyzed -- not just "new" or "modified" rows.  A TABLE is analyzed.

As for the results above -- you did not have the table analyzed in the first
place, hence gather stale is not able to tell when the table is in fact stale
(is 999 new rows 10% of the table?  I don't know -- do you???)

Anyway, you need to analyze once to "prime the pump" if you will.  Consider:

ops$tkyte@ORA9I.WORLD> drop table emp;

Table dropped.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> create table emp as select * from scott.emp;

Table created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> select table_name, to_char(last_analyzed,'dd-mon
hh24:mi:ss'), monitoring from user_tables where table_name = 'EMP';

TABLE_NAME                     TO_CHAR(LAST_AN MON
------------------------------ --------------- ---
EMP                                            NO

ops$tkyte@ORA9I.WORLD> exec dbms_stats.gather_schema_stats (user,  options =>
'gather stale');

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> select table_name, to_char(last_analyzed,'dd-mon
hh24:mi:ss'), monitoring from user_tables where table_name = 'EMP';

TABLE_NAME                     TO_CHAR(LAST_AN MON
------------------------------ --------------- ---
EMP                                            NO

ops$tkyte@ORA9I.WORLD> alter table emp monitoring;

Table altered.

ops$tkyte@ORA9I.WORLD> insert into emp(empno,ename) select rownum, 'abc' from
all_objects where rownum < 1000;

999 rows created.

ops$tkyte@ORA9I.WORLD> commit;

Commit complete.

ops$tkyte@ORA9I.WORLD> exec dbms_stats.gather_schema_stats (user,  options =>
'gather stale');

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> select table_name, to_char(last_analyzed,'dd-mon
hh24:mi:ss'), monitoring from user_tables where table_name = 'EMP';

TABLE_NAME                     TO_CHAR(LAST_AN MON
------------------------------ --------------- ---
EMP                                            YES

ops$tkyte@ORA9I.WORLD> analyze table emp compute statistics;

Table analyzed.

ops$tkyte@ORA9I.WORLD> select table_name, to_char(last_analyzed,'dd-mon
hh24:mi:ss'), monitoring from user_tables where table_name = 'EMP';

TABLE_NAME                     TO_CHAR(LAST_AN MON
------------------------------ --------------- ---
EMP                            07-jul 21:03:10 YES

ops$tkyte@ORA9I.WORLD> insert into emp(empno,ename) select rownum, 'abc' from
all_objects where rownum < 1000;

999 rows created.

ops$tkyte@ORA9I.WORLD> commit;

Commit complete.

ops$tkyte@ORA9I.WORLD> exec dbms_lock.sleep(1);

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> exec dbms_stats.gather_schema_stats (user,  options =>
'gather stale');

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> select table_name, to_char(last_analyzed,'dd-mon
hh24:mi:ss'), monitoring from user_tables where table_name = 'EMP';

TABLE_NAME                     TO_CHAR(LAST_AN MON
------------------------------ --------------- ---
EMP                            07-jul 21:03:11 YES

ops$tkyte@ORA9I.WORLD>

See, the last gather stale worked just fine -- because the table was analyzed in
the first place.
Review & Followup

Rating: 5
You are the best July 07, 2002
Reviewer: Suresh
Tom,
   Thanks for your prompt reply. My understanding is that when you use GATHER
STALE option, the modified and newly added rows only get analyzed and not the
full table as a whole. Is my understanding correct? If so, when you say

"A table is analyzed -- not just "new" or "modified" rows.  A TABLE is analyzed"
then what is the point in using GATHER STALE option.

Thanks,
Suresh
Followup:
Let's say you have 100 tables.

You modify 10 of them.  Of the 10, you modify about 10% or more in 5 of them.

You gather stale.  Now you gather stats on 5 tables. The other 5 modified tables
did not change very much, we skipped them.  the other 90 tables that did not
change at all -- we skipped them as well.

The point here -- you skipped analyzing 95 tables out of 100 -- pretty good eh?

The point of gather stale is to gather stats only on those tables that NEED to
have stats gathered -- all without you having to figure out what tables those
might be.  Thats all.

We cannot, do not, just analyze "new rows" or "modified rows" (be hard to figure
out what those DELETED rows used to be to "subtract" them from the stats --
wouldn't it?  think of things like histograms and such -- where we would need to
actually track the before and after image of the changes -- the overhead would
be HUGE)
Rating: 5
Totally unrelated... October 26, 2002
Reviewer: A reader from BG
Iam sorry for posting this here...but thought you might want to have a look into
this.

Just wanted to bring to notice that the number of days shown next to the
question, when you do a search is not always right.

Score(100) Sun Jul 07, 2002 (Sun Jul 07, 2002, 0 days old)
Followup:
probably more of a bad use of terminology on my part.  the "days old" is really
days between updates.
Rating: 3
DBMS_STATS March 18, 2003
Reviewer: Ted from Philadelphia, PA
Tom,

I was playing around with the DBMS_STATS package and it was failing on
ORA-01467.  Some of the tables were analyzed by the DBMS_STATS before the error
was returned.  I get the same behavior with " dbms_stats.gather_SCHEMA_stats(
user );"

Any ideas?

I am on version 8.1.7.2.

Thanks

USVAL156>begin
  2  dbms_stats.gather_SCHEMA_stats( user, NULL );
  3  end;
  4  /
begin
*
ERROR at line 1:
ORA-01467: sort key too long
ORA-06512: at "SYS.DBMS_STATS", line 4479
ORA-06512: at "SYS.DBMS_STATS", line 4610
ORA-06512: at "SYS.DBMS_STATS", line 4720
ORA-06512: at "SYS.DBMS_STATS", line 4702
ORA-06512: at line 2

USVAL156>
Followup:
Hdr: 2336505 8.1.7.3 PERFORMANC 11.5.6 PRODID-510 PORTID-59 1709806
Abstract: GETTING ORA-1467 WHEN PERFORMING FND_STATS.GATHER_TABLE_STATS

basically -- dbms_stats is building a query with lots of aggregates.  on tables
with lots of large character strings -- this would blow up depending on your
block size -- an analyze would work on that table.  The fix was to substr just
the first 32 characters (thats all we use) and it would work.  It is fixed, in
the terminal release of Oracle8iR3.  You can contact support, reference that
number and tell them you hit that.

Until then, you can analyze the tables using dbms_stats one by one -- and find
the problem table, use analyze on that one instead.
Rating: 5
Some questions regarding dbms_stats March 21, 2003
Reviewer: Riaz Shahid from PRAL, Islamabad, Pakistan
Hello Tom !

I've two questions regarding dbms_stats:

(1) does dbms_stats also analyzes the indexed columns ?

(2) what is the purpose of analyzing indexs ???

Hope you wouldn't mind for asking such "silly" questions. :(

Riaz
Followup:
1) if you ask it to, sure.
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5792247321358#7417227783861
2) to see their size, their cluster factor, number of keys, how empty or full
they are.  same reason you analyze a table.
Rating: 4
dbms_stats use in 9.2.0.3 May 28, 2003
Reviewer: Baqir Hussain from Emeryville, CA USA
Tom,
One of the tables in production gets updated more than 10% every night.  I am
not sure whether I should gather stats every night?
What is the best dbms_stats parameters should I use in this case (10%-15% change
in data)?

Please advise
Followup:
use table monitoring and dbms_stats and let it decide.
Rating: 4
dbms_stats monitoring May 28, 2003
Reviewer: Baqir Hussain from Emeryville, CA USA
Tom,
Thanks for the prompt response.  I did the same as you suggested last week as
follows:

1. exec dbms_stats.ALTER_SCHEMA_TAB_MONITORING('BDR',TRUE);
2. Run cron jobs once every night to get the stale tables:

set serveroutput on size 50000
spool /home/oracle/db_scripts/stale_object.list
declare
  t_stale_objects   dbms_stats.objecttab;
BEGIN
   dbms_stats.gather_schema_stats (
              ownname => 'BDR',
              options => 'LIST STALE',
              objlist => t_stale_objects);
FOR i IN 1 .. t_stale_objects.COUNT
LOOP
dbms_output.put_line(t_stale_objects (i).objname);
END LOOP;
END;
/
exit
spool off

I do not know the next step you suggested.
Please advise.
thanks
Followup:
all you did was list the stale objects.

now gather their stats.  options => gather stale
Rating: 4
sys stats ? August 13, 2003
Reviewer: sravan from MA
Tom,
I have a database where the sys tables are analyzed. I know it is not
recommended to analyze the sys objects. Now,
. Should I delete the statistics from the sys objects ? or
. just leave them in there ?
. If I leave the statistics in the database, will it hurt the database
performace ?

Why shouoldn't the sys objects be analyzed in the first place ?
Followup:
that advice varies by version.

are you experiencing any "bad things"?
is something in need of repair?
Rating: 5
sys stats question August 13, 2003
Reviewer: sravan from MA
No. We haven't seen any performace degrade.
We just realised that some of the tables were analyzed in the past and stats are
collected.
We were just curious if leaving them in there might cause problems.
Rating: 5
dbms_stats.objecttab --> where is it defined? August 15, 2003
Reviewer: Syltrem from Montréal, QC, Canada
Example taken from one reviewer of this article:

declare
  t_stale_objects   dbms_stats.objecttab;
BEGIN
   dbms_stats.gather_schema_stats (
              ownname => 'BDR',
              options => 'LIST STALE',
              objlist => t_stale_objects);
...
...

Q1) How do I find out that the object type for parameter objlist is
dbms_stats.objecttab
Q2) Where can I get a description for dbms_stats.objecttab ? I see (from the
xeample) that it contains a column objname, but what else?

I have spent many an hour looking in the doc and have not happened to stumble
over the right page.

Thanks so much!
Followup:
in dbms_stats -- it is "dbms_stats.objecttab"

covered in the documentation:
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96612/d_stats.htm#1000552
Rating: 5
Many thanks for quick reply August 15, 2003
Reviewer: Syltrem from Montréal, QC, Canada
Can`t figure how how I could miss it (I read all the next page in the manual) -
I find printed doc so much easier to browse...
Rating: 4
method_opt September 30, 2003
Reviewer: Sravan from Northampton, MA
Tom,
1) I looked around for a good explanation of method_opt parameter of dbms_stats
package - but found nothing.
Can you please point me to a good/detailed explanation resource ?

Most of the books/documents only explain in one line about what different
options exist for that parameter. I am looking for an in-depth look.

2) I read in one of your articles that specifying "degree" value greater than
CPU count can improve performance. Can you enhance that fact. Please !!

Thanks
Followup:
1)
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5792247321358#7417227783861
2) degree specifies the number of threads/processes that will be used.  since
these processes spend lots of time doing IO -- not using CPU, you would find
that your CPU is not pegged, not fully utilized, if you set degree = #cpu --
since the processes spend lots of time in IO  waits.  In order to more fully
utilize this resource (that you cannot put in the bank and use later -- its a
use or lose resource), you would use a higher degree.
Rating: 4
io also uses cpu..... October 01, 2003
Reviewer: Reader
When we do IO, it also uses cpu. Right?
Followup:
not using "real" disks no -- an IDE drive used the system cpu but a SCSI drive
for example won't.

so, on any real system, no, IO does not use the system cpu in general.
Rating: 5
table monitoring and performance impact October 01, 2003
Reviewer: Jerry from US
A question about dbms_stats:
I have a reporting application where agents populate data for the database.
When the agent first starts working, the data increased from zero rows to
something like several hundred.  The report query hangs because of the data
change, although minor in absolute number but huge from percentage perspective.
I am thinking of using gather_stales.  But that would require turn on table
monitoring on almost all tables in the database.  Would there a strong negative
performance impact if I turn on table monitoring for lots of tables?

Thanks!
Followup:
monitoring is very very non-intrusive, very light weight -- turn it on.
Rating: 5
why do we have to turn on table monitoring .... October 01, 2003
Reviewer: Reader
is this required if I decide to let Oracle make a decision when to collect stats
for tables? Thanks.
Followup:
yes.  if you want it to figure out when the table needs new stats, you need to
enable this.

it is a simple call to dbms_stats to enable for a schema.
Rating: 5
monitoring October 01, 2003
Reviewer: Sam
Is it like I have to do alter table table_name monitoring for all of the tables
in a schema and then use dbms_stats package proc gather_stale procedure for a
schema? Thanks.
Followup:
there is a dbms_stats procedure to enable monitoring for a schema.
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96612/d_stats2.htm#1012974
Rating: 5
RE: Is it like I have to do alter table... October 01, 2003
Reviewer: Mark A. Williams from Indianapolis, IN USA
> Is it like I have to do alter table...

If you have a release prior to 9i, yes.  But a simple script can create a spool
file and then run that file.  Something like:

select 'alter table ' || owner || '.' || table_name || ' monitoring;'
from all_tables
where owner in ('ABC','XYZ')
order by owner, table_name;

you get the idea...

Plus if you make the tablespace 'monitoring' you don't have to remember to set
the 'monitoring' flag on any new tables that get created in that tablespace.

Of course, if you are 9i, then you can just use
dbms_stats.alter_schema_table_monitoring as Tom has referenced.

HTH,

Mark
Followup:
you cannot make a tablespace monitoring tho?
Rating: 5
monitoring October 01, 2003
Reviewer: Mark A. Williams from Indianapolis, IN USA
Tom,

Yep, my bad... not sure what I was thinking there... there is, of course, no
alter tablespace <name> monitoring clause...  Please disregard.  Now, the
question is, "what was I thinking?"

Thanks,

Mark
Rating: 5
generate n only December 05, 2003
Reviewer: A reader
Hi

How can we gather histograms only for a table using dbms_stats?

Since METHOD_OPT seems that it only has

FOR COLUMNS
FOR ALL INDEXED COLUMNS
FOR ALL COLUMNS

options, so FOR TABLE is always run???? (which I dont want, I only want column
statistics, histograms)
Followup:
well, why wouldn't you want them?  you are going to be scanning the object
anyhow?

but no, i don't see a way (never really even thought to look before) to do this
with dbms_stats.
Rating: 5
because I already have table stats December 05, 2003
Reviewer: A reader
Hi

I want to do it because sometimes I forgot to gather histograms after analyze
the table!

With analyze we can why not with dbms_stats?
Followup:
well, if you use dbms_stats to gather the histograms -- you can forget about
collecting table stats.

move the goal post here, there is no reason to "forget" (thats not a good reason
really, this is a process, not something you just "key in")

use dbms_stats to gather histograms and "accidently" get your table stats.

i don't see how you can "forget".

but bottom line, you are already doing the heavy work needed to analyze the
table to compute the histograms.  You would not be "saving" much of anything
here.  so, basically -- non issue.

  1* select count(*) from big_table
big_table@ORA920> /

  COUNT(*)
----------
   1833792

Indexes on big_table

Index                          Is
Name                           Unique COLUMNS
------------------------------ ------ --------------------------------
BIG_TABLE_ID                   No     OWNER
OBJECT_TYPE_IDX                No     OBJECT_TYPE
BIG_TABLE_OBJECT_ID_IDX        No     OBJECT_ID

analyze table big_table compute statistics
for table
for all indexed columns

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.20          0          0          0           0
Execute      1     39.10      48.96      31720      22060         38           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2     39.10      49.17      31720      22060         38           0

********************************************************************************

analyze table big_table compute statistics
for all indexed columns

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.02          0          0          0           0
Execute      1     36.96      47.77      28875      22060         37           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2     36.96      47.80      28875      22060         37           0
Rating: 5
method_opt in 9i December 27, 2003
Reviewer: A reader
Hi

We have some new METHOD_OPT options in 9i, I have difficulty differentiate
between two of them:

AUTO--Oracle determines the columns to collect histograms based on data
distribution and the workload of the columns.

SKEWONLY--Oracle determines the columns to collect histograms based on the data
distribution of the columns.

The only difference is the word WORKLOAD, what workload are we talking about
here? The number of times these columns are queried...? If that's the case where
is this statistic stored?
Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5792247321358#7417227783861
Rating: 5
from the link you have given December 28, 2003
Reviewer: A reader
it seems SIZE AUTO is the way to go huh, unless the shared pool is being flushed
or aged out continuously
December 28, 2003 Arindom -- Thanks for the question regarding "Question on Logminer", version 8.1.6
You Asked
Hi,
    We are trying to use logminer as an audit trail ; to list
out
sqls issued agaist a table etc..
    Apparently ; the logfilename has to be entered like :

exec dbms_logmnr.add_logfile(
            LogFileName =>
'/opt/ora/oraback/arch/vpndw2404.arc',
            Options     => dbms_logmnr.NEW);

   Is there a way to automate this? Would it be possible to
execute a procedure similar to above automatically on a log
switch?

Thanks.
and we said...
Yes there is but it wouldn't do anything for you anyway.

Adding a logfile adds the logfile to a v$ (in memory) table in
your session only.  Once that session exits -- its gone.  It
does not permanently put the data into a "real" table anywhere.
For any session that wants to inspect the contents of a logfile
-- they must run this command.  If that session logs out -- you
must rerun that command in the new session to reload that
logfile.

So, even though we could do this with dbms_job -- it wouldn't do
anything for us.  You would not want to put it into a real table
either inside the job -- that would create a really vicious
"feedback" loop.  The job to load logs generates log which will
be loaded -- generating more log.  Pretty soon the only thing
the database is doing is loading log files that show you write
to the table that has the logfiles in it lots....
Review & Followup

Rating: 5
DDL support in LogMiner May 14, 2002
Reviewer: Andre Whittick Nasser from Brazil
On the DDL support in 9i's LogMIner:

DDL_DICT_TRACKING

If the dictionary in use is a flat file or in the redo log files, LogMiner
ensures that its internal dictionary is updated if a DDL event occurs. This
ensures that correct SQL_REDO and SQL_UNDO information is maintained for objects
that are modified after the LogMiner dictionary is built.

This option cannot be used in conjunction with the DICT_FROM_ONLINE_CATALOG
option.

NO_DICT_RESET_ONSELECT
 This option is only valid if the DDL_DICT_TRACKING option is also specified. It
prevents LogMiner from reloading its internal dictionary at the beginning of
each select operation on V$LOGMNR_CONTENTS. This can be an advantage because it
can be time consuming to refresh the dictionary if a DDL operation has updated
the dictionary.

If you use this option, be aware that because the dictionary has not been
refreshed for subsequent select operations, you may get incompletely
reconstructed SQL_REDO and SQL_UNDO information for objects that are modified in
the redo log files. Such incomplete reconstructions produce SQL that cannot be
executed.

Q:

If I got it right, DDL_DICT_TRACKING "refreshes" either the offline dictionary
or the online dictionary in the redo logs, in the case of a new DDL. What I'm
supposing NO_DICT_RESET_ON SELECT is stating is that with with option not set,
this refersh ocurs at each select. And when I set this option, the refresh
doesn't happens at every new select on the dictionary.

After all, when is the dictionary refershed: at every new DDL or every new
SELECT ?
Rating: 5
More on LogMiner in 9i ... May 14, 2002
Reviewer: Andre Whittick Nasser from Brazil
If I want to have PK columns appearing in my logminer contents, instead of only
rowid, I can use "supplemental" log groups with,  for example:

ALTER TABLE ... ADD SUPPLEMENTAL LOG GROUP ... ( ..., ...);

or

ALTER DATABASE ADD SUPPLEMENTAL LOG DATA (PRIMARY KEY) COLUMNS;

I find this terminology to be strange. It's not in a fact a "supplemental" log
group in physical terms (like a redo log file group),  but rather additional log
data, mapping the additional columns.

Am I right ?
Rating: 5
logminer on noarchivelog database December 27, 2003
Reviewer: reader
Can Logminer be used on a NOARCHIVELOG database to analyze the current redolg
files? thanks.
Followup:
the current -- not really.  the unused ones -- yes.

the current one is one you don't really want to touch.  it is being written to.
December 28, 2003 Inayat -- Thanks for the question regarding "Calling external procedure to concatenate a string", version 8.1.7
You Asked
Hi Tom,

I am trying to call a C external routine from PL/SQL stored procedure using one
IN OUT parameter. My objective is to pass a string from PL/SQL, concatenate this
string with a second string inside the C routine and return the concatenated
string back to PL/SQL.
PASSASTRING is the PL/SQL wrapper to the C dll.
When I execute my procedure CALLPASSASTRING, the following error shows up:

The following error has occurred:

ORA-06525: Length Mismatch for CHAR or RAW data
ORA-06512: at "SAMPLE.PASSASTRING", line 0
ORA-06512: at "SAMPLE.CALLPASSASTRING", line 14
ORA-06512: at line 2

I am using Oracle 8i and MSVC++ to build my C dll. I selected Win32 Dynamic Link
Library and Simple dll as my options in MSVC++ IDE.

I have tried to follow your example that uses one IN and one IN OUT parameter
modifying the function prototype and C code. I would like to get this one
working with only one IN OUT parameter. I am missing something and I am not able
to figure out where I am going wrong.

Here is my stored procedure CALLPASSASTRING:
---------------
CREATE OR REPLACE procedure CallPassAString
as
c char(100);
BEGIN
    c:= 'WhiteHouse';
    PassAString(c);
    dbms_output.put_line(c);
END;
/
---------------
Here is my PL/SQL wrapper:
----------------------------
CREATE OR REPLACE procedure PassAString (p_out in out  char)
    as
    language C
    name "GetName"
    library stringLib
    with context
    parameters ( CONTEXT,
                 p_out  STRING,
                 p_out  INDICATOR short,
                 p_out  MAXLEN int);
/
------------------------------

and here is my C dll code:
----------------------------------

// ConcatEx.cpp : Defines the entry point for the DLL application.

#include <iostream.h>
using namespace std;
#include <fstream.h>

#include "stdafx.h"
#include <stdio.h>
#include <string>
#include <oci.h>
#include <ociextp.h>
#include <stdlib.h>

BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    return TRUE;
}

extern "C" void __declspec(dllexport) GetName(OCIExtProcContext * ctx,
    /* IN OUT PARAMETER */
    char * p_ostr,
    short * p_ostr_i,
    int * p_ostr_l,
    int * p_ostr_ml)

{
    char * lstr = "Testing...";

    int newlen = strlen(p_ostr) + strlen(lstr) + 2;

    char * tmpstr;
    tmpstr = (char *) malloc(newlen);

    strcpy(tmpstr,p_ostr);
    strcat(tmpstr,lstr);

    //free(p_ostr);            //TOAD hangs

    /*strcpy(p_ostr, tmpstr);*/

    int tmpstr_l = strlen(tmpstr)+1;

    if (*p_ostr_i == OCI_IND_NOTNULL)
    {
             memmove( p_ostr, tmpstr, tmpstr_l );
            *p_ostr_l = tmpstr_l;
            *p_ostr_ml= tmpstr_l;

    }
}
---------------------------------------------

Thank you very much for your help. - Inayat
and we said...
the problems here are a couple -- using CHAR is strong (vehemently) discouraged
by me.

You are passing a fixed width string that is 100 bytes always.  CHAR's are blank
padded.  Always use VARCHAR2, never use CHAR.

So, your input length is already at your max length!  it would be physically
impossible to concatenate here.  Even in PLSQL:

ops$tkyte@ORA9IR2> declare
  2          c char(100) := 'Hello World';
  3  begin
  4          c := c || 'x';
  5  end;
  6  /
declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at line 4

so, don't use char, use varchar.

And then -- don't free OUR MEMORY.  (in fact, do not use free, malloc, etc in an
extproc.  use the provided memory allocation functions and then free only that
which you allocate yourself!)  Toad isn't hanging, you've destroyed the extproc
by doing that free.  C is very very picky about memory managment.

Here is some sample code, I'm using the template I used in Expert One on One
Oracle (gives you a debugf routine, raise_application_error, state mgmt and so
on).  It appears you have access to it since you are using some of my
conventions there.  This routine expects on IN OUT string.  If you do not have
my template (downloadable from www.apress.com), you can just read about the OCI
functions that raise errors and replace the raise application errors with that:
...

#define ERROR_OCI_ERROR     20001
#define ERROR_STR_TOO_SMALL 20002
#define ERROR_RAW_TOO_SMALL 20003
#define ERROR_CLOB_NULL     20004
#define ERROR_ARRAY_NULL    20005

/*
 * Passing strings back and forth is a little differnt then
 * Dates and Numbers.  It is a little easier perhaps.  We get normal C
 * Null terminated strings.  On the other hand, we must (as always in
 * C) be mindful of potential buffer overwrites.  Therefore, we always
 * have the extproc pass in the MAXLEN variable with every string that
 * is an IN/OUT or OUT parameter.  Without it -- we would not have any
 * sure way of knowing the max length.
 *
 * Notice the lack of an OCIExtProcGetEnv call -- it is simply not
 * needed for this routine since we are not making any complex
 * OCI calls as we did above.
 */

#ifdef WIN_NT
_declspec (dllexport)
#endif
void pass_str
 ( OCIExtProcContext * ctx        /* CONTEXT */,

   char *             p_ostr     /* STRING */,
   short *            p_ostr_i   /* INDICATOR short */,
   int *              p_ostr_ml  /* MAXLEN int */
 )
{
myCtxStruct*myCtx;
char  * stringToAdd = " ta dah";

    if ( (myCtx = init( ctx )) == NULL ) return;
    debugf( myCtx,  "Enter Pass Str" );
    /*
     * Now, we will make the output string = upper(input string)
     * if the output buffer is big enough and
     */
    if ( *p_ostr_i == OCI_IND_NOTNULL )
    {
        if ( (strlen(p_ostr) + strlen(stringToAdd) + 1) <= *p_ostr_ml )
            strcat( p_ostr, stringToAdd );
        else
           raise_application_error
           ( myCtx, ERROR_STR_TOO_SMALL, "String too small" );
    }
    else
    {
        if ( (strlen(stringToAdd) + 1) <= *p_ostr_ml )
            strcpy( p_ostr, stringToAdd );
        else
           raise_application_error
           (  myCtx, ERROR_STR_TOO_SMALL, "String too small" );
    }
    term(myCtx);
}

Now, you just need plsql like this:
ops$tkyte@ORA9IR2> create or replace
  2      procedure pass(  p_out  in out  varchar2 )
  3      as
  4      language C name "pass_str" library demoPassing
  5      with context parameters
  6      ( CONTEXT,
  7        p_out  STRING, p_out INDICATOR short, p_out  MAXLEN int );
  8
  9       -- void pass_str
 10       --   (
 11       --  OCIExtProcContext  *,  /*   1 : With-Context  */
 12       --               char  *,  /*   4 : P_OUT  */
 13       --              short  *,  /*   5 : P_OUT (Indicator) */
 14       --                int  *   /*   6 : P_OUT (Maxlen) */
 15       --   );
 16  /

Procedure created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> variable x varchar2(100)
ops$tkyte@ORA9IR2> exec :x := 'Hello World';

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec pass( :x )

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> print x

X
---------------------------------------------------------------------------------
-----------------------------------------------
Hello World ta dah

ops$tkyte@ORA9IR2> exec :x := rpad( 'x', 95, 'x' )

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> exec pass( :x )
BEGIN pass( :x ); END;

*
ERROR at line 1:
ORA-20002: String too small
ORA-06512: at "OPS$TKYTE.PASS", line 0
ORA-06512: at line 1

and there you go.
Review & Followup

Rating: 5
Getting Oracle error mapping function December 27, 2003
Reviewer: Inayat from Union, NJ USA
I was pleasantly surprised to see your quick response.

Let me tell you that I am a newbie to C/C++. I earnestly request to you to
please bear with me if you notice me doing something funny!, I assure you
that I will take a class in C/C++ and also purchase your book Expert One On One
as a valuable reference for my department!

I want to clearly spell out what exactly I did so you would better be able to
help me accomplish my goal.

I downloaded your template from apress.com.

1.     I created a new project in MSVC++ selecting Win32 DLL and Simple DLL as
    my options from the IDE.
2.    I copied the function pass_str from your answer (to my question),and
    the myCtx struct definition, the functions lastOciError, init, term,
    debugf, and raise_application_error into my new project.
3.    I got tons of type conversion compile errors that I fixed by type
    casting them. Please see my comments to the right of the statements
    where I have made such a change.
4.    I prefixed debugf with an _ (underscore) in your pass_str function code.
    Did you mean not to have an underscore? There is a _debugf function but
    there is no function called debugf.
5.    In the init function, I renamed the variable false as false1 as the
    compiler wouldn't compile. I felt that the compiler was thinking it to
    be a keyword as it appeared in blue font in MSVC++. Did you mean it to be
    false and not something else? To have the code compiled, I also had to
    prefix (void *) to false1  in the call to OCIExtractSetKey inside
    the init function. Is this okay?

    When I executed the procedure, here is the error I get:

    SQL> variable x varchar2(100)
    SQL> exec :x := 'Hello World';

    PL/SQL procedure successfully completed.

    SQL> exec pass( :x );
    BEGIN pass( :x ); END;

    *
    ERROR at line 1:
    ORA-06521: PL/SQL: Error mapping function
    ORA-06522: Unable to load symbol from DLL
    ORA-06512: at "SAMPLE.PASS", line 0
    ORA-06512: at line 1

Now, here are my questions:

1.    The _debugf function shows a ... after fmt meaning we can pass addn'al
    parameters to it. If I choose not to use additional parameters for now,
    could you please verify that the calls to _debugf are proper for use
    with just the two shown myCtx and fmt?

        void _debugf( myCtxStruct * myCtx, char * fmt)

2.    I also notice a similar thing for the function raise_application_error.
    For now I would like to have just the 3 parameters as below:

    static int raise_application_error(  myCtxStruct * myCtx,
                                     int           errCode,
                                     char *        errMsg)

The bottom line is that I WANT TO SEE MY C CODE WORKING 100% that I could use
with joy and cheer.

I would be very thankful to you from the bottom of my heart if you could
please take a look at my C code and point out the mistakes if any, to make it
working?
I want to see "Hello World ta dah" on my screen! that would be awesome !!!

Thanks - Inayat

------------------------ attached C code compiled with MSVC++ ---------------

I HAVE PUT A COMMENT "added .... ABOVE EACH LINE WHERE I HAVE DONE TYPE-CASTING

-----------------------------------------------------------------------------

// TResponse.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>

#include <oci.h>

#ifdef WIN_NT
#define -_FILE_NAME "c:\\temp\\extproc.ini"
#else
#define INI_FILE_NAME "/export/home/tkyte/src/demo_passing/extproc.ini"
#endif

#define strupr(a) {char * cp; for(cp=a;*cp;*cp=toupper(*cp), cp++);}

BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    return TRUE;
}

typedef struct myCtx
{
  OCIExtProcContext * ctx;    /* Context passed to all external procs */
  OCIEnv *            envhp;    /* OCI environment handle */
  OCISvcCtx *         svchp;    /* OCI Service handle */
  OCIError *          errhp;    /* OCI Error handle */

  int                 curr_lineno;
  char *              curr_filename;

  ub1                 debugf_flag;
  char                debugf_path[255];
  char                debugf_filename[50];

  /* add your own state variables here... */
}
    myCtxStruct;

void _debugf( myCtxStruct * myCtx, char * fmt, ... )
{
va_list         ap;
OCIFileObject * fp;
time_t          theTime = time(NULL);
char            msg[8192];
ub4             bytes;

   //added (unsigned char *) for parameters 4 and 5 below
    if ( OCIFileOpen( myCtx->envhp, myCtx->errhp, &fp,
                      (unsigned char *)myCtx->debugf_filename,

                      (unsigned char *)myCtx->debugf_path,
                      OCI_FILE_WRITE_ONLY, OCI_FILE_APPEND|OCI_FILE_CREATE,
                      OCI_FILE_TEXT ) != OCI_SUCCESS ) return;

    strftime( msg, sizeof(msg),
             "%y%m%d %H%M%S GMT ", gmtime(&theTime) );
    OCIFileWrite( myCtx->envhp, myCtx->errhp, fp, msg, strlen(msg), &bytes );

    va_start(ap,fmt);
    vsprintf( msg, fmt, ap );
    va_end(ap);
    strcat( msg,"\n");

    OCIFileWrite( myCtx->envhp, myCtx->errhp, fp, msg, strlen(msg), &bytes );
    OCIFileClose( myCtx->envhp, myCtx->errhp, fp );
}

static int raise_application_error(  myCtxStruct * myCtx,
                                     int           errCode,
                                     char *        errMsg,...)

{

char    msg[8192];
va_list ap;

    va_start(ap,errMsg);
    vsprintf( msg, errMsg, ap );
    va_end(ap);

    _debugf( myCtx,  "raise application error( %d, %s )", errCode, msg );

    //added (unsigned char *) to parameter 3
   if (OCIExtProcRaiseExcpWithMsg(myCtx->ctx,errCode,(unsigned char *)msg,0)==
                                                OCIEXTPROC_ERROR )
    {
      _debugf( myCtx,  "Unable to raise exception" );

    }
    return -1;
}

static char * lastOciError( myCtxStruct * myCtx )
{
sb4       errcode;
char      * errbuf = (char*)OCIExtProcAllocCallMemory( myCtx->ctx, 256 );

    strcpy( errbuf, "unable to retrieve message\n" );
    //added (unsigned char *) to parameter 5
    OCIErrorGet( myCtx->errhp, 1, NULL, &errcode, (unsigned char *)errbuf,
                 255, OCI_HTYPE_ERROR );
    errbuf[strlen(errbuf)-1] = 0;
    return errbuf;
}

static myCtxStruct * init( OCIExtProcContext * ctx )
{
ub1          false1 = 0;    //225
myCtxStruct *myCtx = NULL;
OCIEnv      *envhp;
OCISvcCtx   *svchp;
OCIError    *errhp;
ub4          key = 1;

    if ( OCIExtProcGetEnv( ctx, &envhp, &svchp, &errhp ) != OCI_SUCCESS )
    {
        //added (unsigned char *) to parameter 3
         OCIExtProcRaiseExcpWithMsg(ctx,20000,
                            (unsigned char *)"failed to get OCI Connection",0);

         return NULL;
    }
    if ( OCIContextGetValue( envhp, errhp, (ub1*)&key, sizeof(key),
                             (dvoid**)&myCtx ) != OCI_SUCCESS )
    {
        //added (unsigned char *) to parameter 3
        OCIExtProcRaiseExcpWithMsg(ctx,20000,
                (unsigned char *)"failed to get OCI Context",0);
        return NULL;
    }

    if ( myCtx == NULL )
    {
        if ( OCIMemoryAlloc( envhp, errhp, (dvoid**)&myCtx,
                             OCI_DURATION_PROCESS,
                             sizeof(myCtxStruct),
                             OCI_MEMORY_CLEARED ) != OCI_SUCCESS )
        {
            //added (unsigned char *) to parameter 3
            OCIExtProcRaiseExcpWithMsg(ctx,20000,
                          (unsigned char *)"failed to get OCI Memory",0);
            return NULL;
        }
        myCtx->ctx   = ctx;
        myCtx->envhp = envhp;
        myCtx->svchp = svchp;
        myCtx->errhp = errhp;
        if ( OCIContextSetValue( envhp, errhp,
                                 OCI_DURATION_SESSION, (ub1*)&key,
                                 sizeof(key), myCtx ) != OCI_SUCCESS )
        {
            raise_application_error(myCtx, 20000, "%s", lastOciError(myCtx));
            return NULL;
        }

        if (( OCIExtractInit( envhp, errhp ) != OCI_SUCCESS )  ||
            ( OCIExtractSetNumKeys( envhp, errhp, 3 ) != OCI_SUCCESS ) ||
        //added (unsigned char *) to parameter 3
            ( OCIExtractSetKey( envhp, errhp, (unsigned char *)"debugf",
                          OCI_EXTRACT_TYPE_BOOLEAN,
           //added (void *), removed the "&" prefixing false

                          0, (void *)false1, NULL, NULL ) != OCI_SUCCESS ) ||
               //added (unsigned char *) to parameter 3
            (OCIExtractSetKey(envhp, errhp, (unsigned char *)"debugf_filename",
                                OCI_EXTRACT_TYPE_STRING,
                                0, "extproc.log",
                                NULL, NULL ) != OCI_SUCCESS )  ||
               //added (unsigned char *) to parameter 3
            ( OCIExtractSetKey( envhp, errhp, (unsigned char *)"debugf_path",

                                OCI_EXTRACT_TYPE_STRING,
                                0, "", NULL, NULL ) != OCI_SUCCESS )  ||
            ( OCIExtractFromFile( envhp, errhp, 0,
              //added (unsigned char *) to parameter 4
                         (unsigned char *)INI_FILE_NAME ) != OCI_SUCCESS ) ||
              //added (unsigned char *) to parameter 3
            ( OCIExtractToBool( envhp, errhp, (unsigned char *)"debugf", 0,

                                &myCtx->debugf_flag ) != OCI_SUCCESS ) ||
              //added (unsigned char *) to parameter 3 and 5
            (OCIExtractToStr( envhp, errhp, (unsigned char *)"debugf_filename",

                             0,(unsigned char *)myCtx->debugf_filename,

                               sizeof(myCtx->debugf_filename ) )
                                                     != OCI_SUCCESS ) ||
              //added (unsigned char *) to parameter 3 and 5

            ( OCIExtractToStr( envhp, errhp, (unsigned char *)"debugf_path",

                               0, (unsigned char *)myCtx->debugf_path,

                               sizeof(myCtx->debugf_path ) )
                                                     != OCI_SUCCESS ) ||
            ( OCIExtractTerm( envhp, errhp ) != OCI_SUCCESS ))
        {
            raise_application_error(myCtx, 20000, "%s", lastOciError(myCtx));
            return NULL;
        }
    }
    else
    {
        myCtx->ctx   = ctx;
        myCtx->envhp = envhp;
        myCtx->svchp = svchp;
        myCtx->errhp = errhp;
    }
    if ( OCIFileInit( myCtx->envhp, myCtx->errhp ) != OCI_SUCCESS )
    {
        raise_application_error(myCtx, 20000, "%s", lastOciError(myCtx));
        return NULL;
    }
    return myCtx;
}

static void term( myCtxStruct * myCtx )
{
    OCIFileTerm( myCtx->envhp, myCtx->errhp );
}

#define ERROR_OCI_ERROR     20001
#define ERROR_STR_TOO_SMALL 20002
#define ERROR_RAW_TOO_SMALL 20003
#define ERROR_CLOB_NULL     20004
#define ERROR_ARRAY_NULL    20005

#ifdef WIN_NT
_declspec (dllexport)
#endif
void pass_str
 ( OCIExtProcContext * ctx        /* CONTEXT */,

   char *             p_ostr     /* STRING */,
   short *            p_ostr_i   /* INDICATOR short */,
   int *              p_ostr_ml  /* MAXLEN int */
 )
{
myCtxStruct*myCtx;
char  * stringToAdd = " ta dah";

    if ( (myCtx = init( ctx )) == NULL ) return;
    _debugf( myCtx,  "Enter Pass Str" );
    /*
     * Now, we will make the output string = upper(input string)
     * if the output buffer is big enough and
     */
    if ( *p_ostr_i == OCI_IND_NOTNULL )
    {
      //added unsigned int to *p_ostr_ml
     if ((strlen(p_ostr) + strlen(stringToAdd) +1) <= unsigned int(*p_ostr_ml))

            strcat( p_ostr, stringToAdd );
     else
           raise_application_error
           ( myCtx, ERROR_STR_TOO_SMALL, "String too small" );
    }
    else
    {
        //added unsigned int to *p_ostr_ml
        if ( (strlen(stringToAdd) + 1) <= unsigned int(*p_ostr_ml) )
            strcpy( p_ostr, stringToAdd );
        else
           raise_application_error
           (  myCtx, ERROR_STR_TOO_SMALL, "String too small" );
    }
    term(myCtx);
}
--------------------------------------the end----------------------------------
Followup:
sorry -- but external procedures are probably the last thing I would choose as a
method to cut my teeth on in C.  The last thing.

I'm not really going to look at pages of C code that is slightly modified from
what I provided (that I know to work "out of the box", tested thoroughly) and
look for bugs!

to get over your current error -- you need to define WIN_NT

#ifdef WIN_NT
_declspec (dllexport)
#endif

to get the _declspec to be there (else the function is not exported), or just
remove the #ifdef/#endif (i write code that works on all OS's - making
exceptions for windows non-portability when necessary like that)

debugf is a macro that should be in the code after _debugf:

/*
 * this macro is a more convienent way to use debugf. Instead of have
 * to pass the __LINE__, __FILE__ each time we call -- we just code:
 *
 *     debugf( myCtx, "This is some format %s", some_string );
 *
 * and this macro will set them in our context and then call _debugf for us.
 *
 */
void _debugf( myCtxStruct * myCtx, char * fmt, ... );
#define debugf  \
if ((myCtx!=NULL) && (myCtx->debugf_flag)) \
    myCtx->curr_lineno = __LINE__, \
    myCtx->curr_filename = __FILE__, \
    _debugf
Rating: 5
Got it working!! December 30, 2003
Reviewer: Inayat Jilani from Union, NJ USA
Hi Tom,

A big fat thanks to you!. Your code is working exactly as is with the only
exception of type-casting I had to do to compile it in MSVC++. As you suggested
I also had to removed the #Ifdef and #endif, as the OS I am working on is XP.

#ifdef WIN_NT
_declspec (dllexport)
#endif

and I my 'Hello Ta da' made it's merry way to the SQL Plus window!

Thanks again, Inayat
December 28, 2003 carsten -- Thanks for the question regarding "firewall rules for net8 connection", version 8.1.5
You Asked
hi ..
which ports must be opened on the firewall to communicate
with a oracle8-db using net8 and tcp? or, how is is the pool
of ports defined?

we are using 1521 as the initial portnumber for
net8-communication
with the db. if we open only this portnumber there is no
connection
possible. with a netsniffer we found out that only the initial
contact is done on the 1521-port followed by a portswitch to
e.g. 1051 or something else. each new connection-attempt
is using a new portnumber. our firewall has no prebuild rule
for this communication-model. where can I get such information?

thanks carsten
and we said...
Are you on NT?  The following can be used to help solve this
issue.  It explains what the issue is and then offers ways
around it..

PURPOSE

  This article describes how to get around firewall problems
with SQL*Net/Net*8 on NT Servers

Listeners on NT commonly listen on port 1521.  It is a common
misconception that if you allow access in to and out of the
firewall by enabling access through port 1521, that SQL*Net
clients will be able to connect. To understand why the connect
will fail, it is necessary to understand how a SQL*Net
connection on NT works.

When a client initiates a connect, a TCP connection is
established with port 1521. A TNS CONNECT packet is then sent to
the listener. On UNIX systems the listener process will fork a
new Oracle process to deal with the new incoming
connection. With UNIX, forked processes will inherit the
resources owned by the parent process, in other words file
handles and TCP sockets.

Earlier releases of SQL*Net for Windows NT used the WINSOCK V1.1
API. With this version of WINSOCK there is no capabaility of
passing a TCP socket between two  processes, and no way to
inherit a TCP socket. To work around this restriction
a new thread of execution is created by the main Oracle process
and a local  connection is made between the listener and this
new thread. The newly created Oracle thread randomly selects a
new TCP port, for example port 1087, to use for the connection
request and informs the listener of the new port to be used.

The listener now needs to inform the client that they need to
REDIRECT the connection attempt to this newly selected
networking endpoint. The listener now sends a TNS REDIRECT
packet to the client with details of the new port to reconnect
to. The client drops the existing TCP connection and then issues
a TCP Connect sequence to the new TCP port, and this is then
followed by a TNS Connect packet. If all is well and the Oracle
server is able to process the incoming connection request, then
the server thread will respond with a TNS ACCEPT packet and data
will begin to flow.

So, if you enable connects through port 1521 on your firewall,
you can now see that after the REDIRECT packet has been sent to
the client, the connect will fail as port 1087 is not enabled in
the firewall. As the REDIRECT port that gets generated is
entirely at random, you cannot enable access through multiple
ports in the firewall as you have no idea which ports will get
allocated.

To workaround this problem there are several options:

1. Configure the firewall to limit IP addresses rather than port
numbers. This is not a very secure option.

2. Use Connection Manager so the TNS CONNECT following the
REDIRECT happens the server side of the firewall.

3. If you are on Oracle 8, you can use a WINSOCK V2 API feature
called Shared Sockets . This allows a socket to be shared (or
passed) between multiple processes. To use this functionality in
a single Oracle Home enviroment, set USE_SHARED_SOCKET = TRUE in
the HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE section of the registry.
If you are using Multiple Oracle Homes, change to the desired
Oracle 8 Home and view the oracle.key file in ORACLE_HOME\BIN to
find which registry key to add USE_SHARED_SOCKET  to.

   Please Note that as WINSOCK V2 allows a socket to be shared
between multiple processes, you cannot restart the listener
without taking the database down first.
Review & Followup

Rating: 4
very help full October 08, 2001
Reviewer: Pinakin from NY, USA
We just move all database server from one palce to
another(NT ,Unix),Then we got problem with Database
on NT box, we did not connect to database on NT from
outside, so I set USE_SHARED_SOCKET = TRUE in
Regdet32 and it works
Rating: 2
Firewall Rules January 04, 2002
Reviewer: T.Thiagarajan from India
The answer was quite useful for WinNT box, but what about the solaris. Can u
tell me how to configure firewall rules for solaris.
Followup:
use dedicated servers and you have no problems like you do on NT.

steps 1 and 2 apply to unix, step 3 is unneccessary, unix always has done
"shared_sockets"
Rating: 4
I think options 1 and 2 are not necessary on Unix boxes January 15, 2003
Reviewer: Rajidhar Etta from VA, USA
Tom,
From the document , it seems we need to implement one of 3 options only if OS is
Windows NT, as only 1521 port is to be opened for Unix.
Right now our application server (9iAS) communicates with DB server which is
behind a firewall with out using connection manager (and only 1521 port is
opened).
However, we did experienced firewall time out problem for which we are
considering
1. Oracle Dead Connection Detection (DCD)
2. Our system administrator suggested tweaking NDD settings for 1521 on
client/server boxes so that OS can send ACK packets periodically.
3. Write Custom code to loop through each connection in the middle tier and
execute dummy SQL for every "N" minutes.

Is my approach correct?
Followup:
well, the article did start off with

  This article describes how to get around firewall problems with SQL*Net/Net*8
on NT Servers

on NT servers....

1521 is not a fixed port, you can pick and choose it.

And it only applies with dedicated server, if you use shared server - you are
back to CMAN again, even on unix.

It is funny how your company has a security policy that times out idle
connections -- but you are trying to find ways to subvert such a policy...  Why
not have the connection pool time out its connections -- eg: if you havent been
used in 5 minutes, disconnect, we don't need you anymore.

That way you would be in compliance with your companies desires.
Rating: 4
Thanks , January 16, 2003
Reviewer: Rajidhar Etta from VA, USA
Thanks Tom for the reply,
I missed the point that those options are for shared server mode only - thanks
for clarfication.
Our application is hosted in a shared environment and firewall rules cannot be
changed easily for each customer- thats why we are figuring out ways to solve
this problem.
Rating: 3
and for Win2k? May 23, 2003
Reviewer: Si from London, England
I think I understand the gist of what you have said about 1521 and firewalls on
NT.  I am trying to install the same on Win2k.  Could you suggest any solutions
to get past the block?
Thank you.
Followup:
this applies verbaitim to win2k.
Rating: 3
Re: Win2k troubles May 24, 2003
Reviewer: Si from London, England
Thank you for answering so quickly.
Unfortunately, setting USE_SHARED_SOCKET = TRUE did not do anything.  I have
since seen other posts online saying that this does not work for most people, I
am not sure why.
I am also unsure as to where the connection manager is.  A search for the file
cman.ora on my drives turned up no results.
Would you have any pointers?
Thanks.
Followup:
the documentation -- see the net admin guide.

have you actually tested that 1521 is in fact OPEN, you know, telnet to it and
see if telnet can get to the listener.  if not, it has nothing to do with shared
sockets.
Rating: 4
Re: Win2k troubles May 24, 2003
Reviewer: Si from London, England
1521 is open so I am ok there.
The Oracle CD I have is a freebie I got from the website many moons ago, and so
I do not have any documentation links with me.  Do you know where I can find
this online?  I also don't have a MetaLink account.
Thanks
Followup:
otn.oracle.com
Rating: 4
Re: Win2k troubles May 24, 2003
Reviewer: Si from London, England
Thank you.
Can you tell me how to telnet to 1521 and check if it is open?
My firewall settings say that it is but a second opinion would be good.
Followup:
telnet hostname 1521

if you see:

[tkyte@tkyte-pc-isdn Desktop]$ telnet hostname 1521
Trying X.Y.Z.A...
Connected to hostname
Escape character is '^]'

it is open, if you see:

[tkyte@tkyte-pc-isdn Desktop]$ telnet tkyte-pc-isdn 1522
Trying 192.168.0.3...
telnet: connect to address 192.168.0.3: Connection refused
[tkyte@tkyte-pc-isdn Desktop]$

it isn't.
Rating: 4
Re:Win2k troubles May 24, 2003
Reviewer: Si from London, England
I'm having trouble with this.
When I open the window, I get a prompt saying
Microsoft Telnet>
I then type in telnet x 1521 where x is my computer name and I get an Invalid
Command error.
I type in the same but where x is the hostname given by my ISP and I get the
same error.
Am I not in the right window?
Rating: 5
May 24, 2003
Reviewer: A reader
Si, You should go to command prompt not telnet prompt.

Just want to save Toms time :-)

Thanks
Rating: 5
Re:Win2k troubles May 24, 2003
Reviewer: Si from London, England
Thank you very much to both Tom and the kind reader.
Your help is greatly appreciated.
Rating: 5
Getting Beyond Firewall July 01, 2003
Reviewer: A reader from Ca, USA
We have a situation where if we run the following command from our Oracle 8i
Database, we get the following result:

SQL> select utl_http.request('
http://elocation.oracle.com'/
 from dual;

UTL_HTTP.REQUEST('HTTP://ELOCATION.ORACLE.COM')
--------------------------------------------------------------------------------
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

<HTML><HEAD><TITLE>Oracle eLocation Services</TITLE>

<META http-equiv=Content-Type content="text/html; charset=windows-1252">

<META content="MSHTML 5.50.4134.600" name=GENERATOR><LINK

href="newimages/marlin.css" type=text/css rel=stylesheet>

</HEAD>

<BODY class=OraBody>

<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>

  <TR>

    <TD vAlign=bottom rowSpan=2><IMG src="newimages/oraclelogo_sizewithprodbrand
.gif"><BR><IMG height=69 src="newimages/elocation.gif" width=340></TD>

    <TD align=right>

      <TABLE cellSpacing=0 cellPadding=0 align=right border=0>

      <TBODY>

      </TBODY>

      </TABLE>

    </TD>

  </TR>

  <TR>

    <TD vAlign=bottom>

      <TABLE border=0 cellPadding=0 cellSpacing=0 width="98%">

        <TBODY>

        <TR>

          <TD>&nbsp;</TD>

          <TD width=50>&nbsp;</TD>

          <TD align=right noWrap width=16><IMG height=21

            src="newimages/blue-head.gif" width=12 border=0></TD>

          <TD background=newimages/blue-content.gif noWrap width="1%" class=OraN
av1Selected>Legal Notices</TD>

          <TD align=right bgColor=#336699 vAlign=top width="12"><IMG height=21

            src="newimages/blue-end.gif" width=12 border=0></TD>

        </TR>

        </TBODY>

      </TABLE>

    </TD>

  </TR>

</TABLE>

<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>

<TBODY>

  <TR>

    <TD vAlign=bottom height=24>

      <TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>

      <TBODY>

        <TR>

          <TD vAlign=bottom noWrap width="52%" bgColor=#336699 height=30>&nbsp;<
/TD>

          <TD vAlign=top noWrap width="48%" background="newimages/asysrtb.gif" h
eight=30><IMG height=30 src="newimages/asysrt.gif" width=40 border=0></TD>

          <TD width=8 height="30" xbackground="newimages/rhshadow.gif"><IMG heig
ht=30 src="newimages/asylrhs.gif" width=8 border=0></TD>

        </TR>

        <TR>

          <TD vAlign=t

However, when our client issued the same command from their Oracle8i Database,
they got:

SQL> select utl_http.request('
http://elocation.oracle.com'/
 from dual;

UTL_HTTP.REQUEST('HTTP://ELOCATION.ORACLE.COM')
----------------------------------------------------------------------------
----

1 row selected.

I was told that, on their database server, they need userid/password to access
the internet. What can they do in this situation where they need to access the
internet from a stored procedure in the datbase?

thanks
Followup:
so you are saying that their proxy server challenges them with a user/pass
prompt or that they need to use a wallet?

If the proxy needs a user/password, its going to be a bit hard.  You would need
to either

o write to the proxy server directly using utl_tcp, you would implement a small
http client

o load up some java packages into the database that support this functionality.

But the really STICKY question you have to answer is "where do I get the
user/password from".  Hmm, you would need to get it from them - it would be
messy at best.
Rating: 5
July 01, 2003
Reviewer: A reader from Ca, USA
Rating: 5
Getting Beyond the Firewall July 01, 2003
Reviewer: A reader from Ca, USA
Hi Tom,

Is there no database configuration setting that takes proxy info (including
userid/password) and allows the database server to access the Internet?

thanks
Followup:
no. not with utl_http and definitely not in 8i (where utl_http was a tad
"primitive"
Rating: 3
authenticate proxy July 01, 2003
Reviewer: Sudhakar Nagisetty from Bristol, CT, USA
here is the code to authenticate to proxy...

handle exceptions accordingly.....

req       utl_http.req;
        resp      utl_http.resp;
        name      VARCHAR2(256);
        value       clob ;
        tValue       clob ;
        data      VARCHAR2(255);
        my_scheme VARCHAR2(256);
        my_realm  VARCHAR2(256);
        my_proxy  BOOLEAN;

-- When going through a firewall, pass requests through  this host.
-- Specify sites inside the firewall that don't need the proxy host.
utl_http.set_proxy(proxy_info); -- this is your proxy ip address

-- Ask UTL_HTTP not to raise an exception for 4xx and 5xx status codes,
         -- rather than just returning the text of the error page.
         utl_http.set_response_error_check(FALSE);

         -- Begin retrieving this web page.

         req := utl_http.begin_request(strUrl);

-- Identify ourselves. Some sites serve special pages for particular browsers.
         utl_http.set_header(req, 'User-Agent', 'Mozilla/4.0');

utl_http.set_authentication(req, username, password,'Basic',true);

-- Now start receiving the HTML text.
              resp := utl_http.get_response(req);

              -- Look for client-side error and report it.
              IF (resp.status_code >= 400) AND (resp.status_code <= 499) THEN

                   -- Detect whether the page is password protected, and we
didn't supply
                 -- the right authorization.
                 IF (resp.status_code = utl_http.HTTP_UNAUTHORIZED) THEN
                     utl_http.get_authentication(resp, my_scheme, my_realm,
my_proxy);
                    IF (my_proxy) THEN
                       dbms_output.put_line('Web proxy server is protected.');
                       dbms_output.put('Please supply the required ' ||
my_scheme ||
                         ' authentication username/password for realm ' ||
my_realm ||
                           ' for the proxy server.');
                    ELSE
                        dbms_output.put_line('Web page ' || strUrl || ' is
protected.');
                        dbms_output.put('Please supplied the required ' ||
my_scheme ||
                                  ' authentication username/password for realm '
|| my_realm ||
                                    ' for the Web page.');
                    END IF;
                ELSE
                    dbms_output.put_line('Check the URL.');
                END IF;

                utl_http.end_response(resp);

                -- Look for server-side error and report it.
               ELSIF (resp.status_code >= 500) AND (resp.status_code <= 599)
THEN

                  dbms_output.put_line('Check if the web site is up.');
                  utl_http.end_response(resp);

               END IF;

              -- Keep reading lines until no more are left and an exception is
raised.
               tvalue := strUrl;
                begin
                     LOOP
--                          utl_http.read_line(resp, value);
                          utl_http.read_text(resp, value);
                         if tvalue = strUrl then
                             tvalue := value;
                         else
                              dbms_lob.append(tvalue,value);
                         end if;
                     END LOOP;
                exception
                    WHEN utl_http.end_of_body THEN
                         utl_http.end_response(resp);
                    when others then
                         utl_http.end_request(req);
                end;

             EXCEPTION
                      WHEN utl_http.end_of_body THEN
                            utl_http.end_response(resp);
                           --        return value;
                      when others then
                            utl_http.end_request(req);
             --                return value;
             END;
Rating: 3
authenticate proxy July 01, 2003
Reviewer: Sudhakar Nagisetty from Bristol, CT, USA
Sorry forgot to mention, the code is for Oracle 9i
Rating: 3
How about Port on Client September 17, 2003
Reviewer: LJ from oh, usa
How about the ports on the client side?  Which port is used by Oracle client,
e.g. sqlplus, OCI?
Followup:
they use the ports you told them to -- "connect to host X port 1521 and request
the service foo"

the tns connect string tells you which host/port they will use.
Rating: 3
i do all the steps that you said but i still have problem December 25, 2003
Reviewer: aldobai mostafa from yemen
thanks Tom

my db server oracle 8.1.7 is on one side of firewall
in the other side of the firewall there are my clients and
DNS server ,DHCP server
i configure the firewall to pass any packets (for test prpose only) when i ping
the database server i get replay
but when i try to use tnsping to the db server i get
ora-12560 errors
what shall i do to solve this problem ?

with my best regards
Followup:
[tkyte@tkyte-pc-isdn tkyte]$ oerr ora 12560
12560, 00000, "TNS:protocol adapter error"
// *Cause: A generic protocol adapter error occurred.
// *Action: Check addresses used for proper protocol specification. Before
// reporting this error, look at the error stack and check for lower level
// transport errors.For further details, turn on tracing and reexecute the
// operation. Turn off tracing when the operation is complete.
Rating: 4
JDBC connection across firewall December 28, 2003
Reviewer: Ramadan from Dhahran, Saudi Arabia
We have a webMethods adapter which links between our SAP server and Oracle db
(8.1.5 on NT4.0 WS) using JDBC connection. The connection string looks like
this... jdbc:oracle:thin:@10.20.30.40:1521:TFMS
We experienced the same communication problem when we configured only port 1521
in the firewall. Will USE_SHARED_SOCKET = TRUE solve the problem? I checked the
registery and didn't find it there. Should I add it?
Followup:
it could be it, we use port redirection, use shared socket should let us
"inherit" the connection.
December 28, 2003 -- Thanks for the question regarding "Interview questions", version 817
You Asked
Hi Tom
I havent come across a guy like you in my 4 yrs of little Oracle DBA experience
who just knows everything about Oracle . So I guess if someone faces you as an
interviewer that would be the REAL ultimate challenge.
Do you have a few of your favorite or really important questions that you would
ask an Oracle DBA for 817/9i?

Im not sure if anyone has asked you this question in the past but that would
make my day(lots of my days).

Thank You
TS
and we said...
search for

"interview questions"

on this site.  And you know what -- every day I learn that I don't know
everything about Oracle.  Everyday -- literally -- I learn something new.
Either from researching a question -- or being shown I was wrong about some
conclusion I had made...

Question everything you hear or see from everyone.  Don't believe ANYTHING
unless it comes with a proof -- and even then try to break the proof.

Jonathan Lewis just did that to me.  At the hotsos conference, I showed that
using session_cached_cursors can be used to alleviate some of the overhead of
recursive SQL (say the sql oracle does to update seq$ when you get a sequence).

Well, it was the truth, but not the WHOLE truth.  My "proof" worked 100% of the
time, he just moved things about a tiny bit and the "proof" fell apart.  It
turns out -- in the some circumstances session_cached_cursors can help but not
in the general case!

Question Authority.  Don't believe anything unless you can see it, prove it,
experience it.  If a test provided doesn't reproduce similar findings for you --
or by modifying the test just a little breaks it -- the hypothesis is incorrect.
Review & Followup

Rating: 5
What was Jonathon's Response? March 01, 2003
Reviewer: A reader
Tom,

Would you mind sharing with us how Jonathon "broke" the proof of what you said.
This should be interesting and (needless to say) highly educating!
Followup:
Sure, here was the script I ran, using my runstats framework.  The goal was to
use this to show that session_cached_cursors reduces the work done, reduces the
LATCHES used by heavy recursive sql processing.  I use a NOCACHE sequence to
force artifically high recursive SQL:

ops$tkyte@ORA920> create table run_stats ( runid varchar2(15), name
varchar2(80), value int );
Table created.

ops$tkyte@ORA920> create or replace view stats
  2  as select 'STAT...' || a.name name, b.value
  3        from v$statname a, v$mystat b
  4       where a.statistic# = b.statistic#
  5      union all
  6      select 'LATCH.' || name,  gets
  7        from v$latch;

View created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> drop sequence seq;

Sequence dropped.

ops$tkyte@ORA920> create sequence seq nocache;

Sequence created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter system flush shared_pool;

System altered.

ops$tkyte@ORA920> /*
DOC>now, we'll see about tons of recursive sql. one easy way to see that
DOC>will be with a sequence that is not cached (updates to seq$ --
DOC>tons of them)...
DOC>*/
ops$tkyte@ORA920> pause

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      l_start number;
  3      l_run1  number;
  4      l_run2  number;
  5
  6         l_seq   number;
  7  begin
  8      insert into run_stats select 'before', stats.* from stats;
  9
 10      l_start := dbms_utility.get_time;
 11      execute immediate 'alter session set session_cached_cursors=0';
 12      for i in 1 .. 10000
 13      loop
 14          select seq.nextval into l_seq from dual;
 15      end loop;
 16      l_run1 := (dbms_utility.get_time-l_start);
 17      dbms_output.put_line( l_run1 || ' hsecs' );
 18
 19      insert into run_stats select 'after 1', stats.* from stats;
 20      l_start := dbms_utility.get_time;
 21      execute immediate 'alter session set session_cached_cursors=100';
 22      for i in 1 .. 10000
 23      loop
 24          select seq.nextval into l_seq from dual;
 25      end loop;
 26      l_run2 := (dbms_utility.get_time-l_start);
 27      dbms_output.put_line( l_run2 || ' hsecs' );
 28      dbms_output.put_line
 29      ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) || '% of the time' );
 30
 31      insert into run_stats select 'after 2', stats.* from stats;
 32      execute immediate 'alter session set session_cached_cursors=0';
 33  end;
 34  /
1620 hsecs
1466 hsecs
run 1 ran in 110.5% of the time

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> pause

ops$tkyte@ORA920>
ops$tkyte@ORA920> select a.name, b.value-a.value run1, c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11     and abs( (c.value-b.value)-(b.value-a.value)) > 250
 12   order by abs( (c.value-b.value)-(b.value-a.value))
 13  /

NAME                                 RUN1       RUN2       DIFF
------------------------------ ---------- ---------- ----------
STAT...buffer is not pinned co      10354      10084       -270
unt

STAT...session logical reads        70823      70533       -290
LATCH.enqueue hash chains           40620      40928        308
STAT...consistent gets              40459      40132       -327
LATCH.session allocation              535       1216        681
STAT...recursive calls             151473     150566       -907
LATCH.cache buffers chains         194203     195242       1039
STAT...redo size                  7267048    7268796       1748
STAT...session cursor cache hi          0      10004      10004
ts

LATCH.library cache pin            121182     100900     -20282
LATCH.shared pool                   71584      51083     -20501
LATCH.library cache pin alloca      40792        692     -40100
tion

LATCH.library cache                162211     111753     -50458
STAT...session pga memory max       65536          0     -65536

14 rows selected.

ops$tkyte@ORA920> select run1, run2, diff, round( run1/run2*100, 2 )
run1_pct_of_run2
  2    from (
  3  select sum(b.value-a.value) run1, sum(c.value-b.value) run2,
  4         sum( (c.value-b.value)-(b.value-a.value)) diff
  5    from run_stats a, run_stats b, run_stats c
  6   where a.name = b.name
  7     and b.name = c.name
  8     and a.runid = 'before'
  9     and b.runid = 'after 1'
 10     and c.runid = 'after 2'
 11     and (c.value-a.value) > 0
 12     and (c.value-b.value) <> (b.value-a.value)
 13     and a.name like 'LATCH%'
 14         )
 15  /

      RUN1       RUN2       DIFF RUN1_PCT_OF_RUN2
---------- ---------- ---------- ----------------
    784762     656052    -128710           119.62

so, that seems to nail it right?  got 10,000 cached cursor hits -- those are
my updates to seq$ that are happening.  Got lots less latches too -- all well
and fine.

Well, Jonathan ran the test like this

ops$tkyte@ORA920> alter system flush shared_pool;

System altered.

ops$tkyte@ORA920> /*
DOC>now, we'll see about tons of recursive sql. one easy way to see that
DOC>will be with a sequence that is not cached (updates to seq$ --
DOC>tons of them)...
DOC>*/
ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      l_start number;
  3      l_run1  number;
  4      l_run2  number;
  5      l_seq   number;
  6  begin
  7      insert into run_stats select 'before', stats.* from stats;
  8
  9      l_start := dbms_utility.get_time;
 10      execute immediate 'alter session set session_cached_cursors=0';
 11      for i in 1 .. 10000
 12      loop
 13          select seq.nextval into l_seq from dual;
 14      end loop;
 15      l_run1 := (dbms_utility.get_time-l_start);
 16      dbms_output.put_line( l_run1 || ' hsecs' );
 17  end;
 18  /
1803 hsecs

PL/SQL procedure successfully completed.

see, broke it into 2 bits here

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2      l_start number;
  3      l_run1  number;
  4      l_run2  number;
  5      l_seq   number;
  6  begin
  7      insert into run_stats select 'after 1', stats.* from stats;
  8      l_start := dbms_utility.get_time;
  9      execute immediate 'alter session set session_cached_cursors=100';
 10      for i in 1 .. 10000
 11      loop
 12          select seq.nextval into l_seq from dual;
 13      end loop;
 14      l_run2 := (dbms_utility.get_time-l_start);
 15      dbms_output.put_line( l_run2 || ' hsecs' );
 16      dbms_output.put_line
 17      ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) || '% of the time' );
 18
 19      insert into run_stats select 'after 2', stats.* from stats;
 20      execute immediate 'alter session set session_cached_cursors=0';
 21  end;
 22  /
1654 hsecs
run 1 ran in % of the time

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select a.name, b.value-a.value run1, c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11     and abs( (c.value-b.value)-(b.value-a.value)) > 250
 12   order by abs( (c.value-b.value)-(b.value-a.value))
 13  /

NAME                                 RUN1       RUN2       DIFF
------------------------------ ---------- ---------- ----------
STAT...buffer is not pinned co      10346      10075       -271
unt

STAT...free buffer requested            5        298        293
LATCH.library cache load lock         308         12       -296
STAT...session logical reads        70793      70497       -296
STAT...bytes sent via SQL*Net         300          0       -300
to client

STAT...consistent gets              40446      40120       -326
LATCH.enqueue hash chains           40992      40626       -366
LATCH.cache buffers lru chain         263        784        521
LATCH.checkpoint queue latch          993       1539        546
LATCH.child cursor hash table         744         90       -654
STAT...recursive calls             151407     150535       -872
STAT...bytes received via SQL*       1106          0      -1106
Net from client

LATCH.row cache enqueue latch       21520      20210      -1310
STAT...CPU used by this sessio       1532          0      -1532
n

STAT...CPU used when call star       1532          0      -1532
ted

LATCH.row cache objects             61794      60226      -1568
LATCH.library cache pin alloca      43698      40528      -3170
tion

LATCH.library cache pin            126892     120692      -6200
LATCH.cache buffers chains         201966     195086      -6880
LATCH.shared pool                   78339      70891      -7448
LATCH.enqueues                        128      10046       9918
LATCH.session idle bit                 11      10004       9993
LATCH.session allocation            35296      21060     -14236
STAT...session pga memory max       65536          0     -65536
STAT...redo size                  7267188    8668040    1400852

25 rows selected.

See we are missing the cached cursor hits, we didn't get them and worse:

ops$tkyte@ORA920> select run1, run2, diff, round( run1/run2*100, 2 )
run1_pct_of_run2
  2    from (
  3  select sum(b.value-a.value) run1, sum(c.value-b.value) run2,
  4         sum( (c.value-b.value)-(b.value-a.value)) diff
  5    from run_stats a, run_stats b, run_stats c
  6   where a.name = b.name
  7     and b.name = c.name
  8     and a.runid = 'before'
  9     and b.runid = 'after 1'
 10     and c.runid = 'after 2'
 11     and (c.value-a.value) > 0
 12     and (c.value-b.value) <> (b.value-a.value)
 13     and a.name like 'LATCH%'
 14         )
 15  /

      RUN1       RUN2       DIFF RUN1_PCT_OF_RUN2
---------- ---------- ---------- ----------------
    857464     835761     -21703            102.6

we didn't fix the latching.... So, basically it "works sometimes" but not
consistently

Conclusion -- it can work to help sometimes -- so the "whole truth" is:

session_cached_cursors will help you increase the scalability of YOUR
application when YOU don't cache the cursors.  It can SOMETIMES, under SOME
conditions help with recursive SQL.

Fortunately, I was most interested in the "YOUR APPLICATION" with "YOUR
UNCHACHED cursors" -- the recursive sql was just a nice side effect, that
apparently isn't 100% effective.

(please don't ask me why the above is true, it just is ;)
Rating: 5
Gotta finish the job :-) March 02, 2003
Reviewer: Connor McDonald from Perth
"please don't ask me why the above is true, it just is"

come on - you can't just leave it that!
Followup:
oh yes i can ;)

I just did for that one.  (it looks flaky, but I have a hard time decribing it
as a "product issue")
Rating: 3
Nice December 28, 2003
Reviewer: Jacob from CT,USA
Hello sir,
 How can we use dbms_stats package to generate statistics
for all tables in a schema?Does that need a loop structure?
 like
   sql> begin
        for x in (select table_name from tabs) loop
            exec dbms_stats.get_table_stats('scott',x.table_name)
  ....
Moreover I feel comfortable with
  sql>exec dbms_utility.analyze_schema('SCOTT','COMPUTE');
Do you find any performance tradeoff in using so?
Waiting for your reply.
Bye!
Followup:
hows about checking out all of the features of dbms_stats -- one of which is
dbms_stats.gather_schema_stats

...... see the docs!
December 28, 2003 Mudassar -- Thanks for the question regarding "Network Configuration Parameters", version 8.1.6
You Asked
The definition of the parameter CONNECT_TIMEOUT in the
listener.ora file is:
Determines the amount of time the listener will wait for a valid
connection request after a connection has been started.
My question is what will it do after waiting for this amout of
time?

Secondly, the service_names parameter, will it only register by
default with the default listener running on port 1521?
This listener is it always supposed to be running regardless of
the database?
If we start the listener after the database, will the
service_names register the database at an interval? If yes, how
to configure that interval?

Connect_time failover: Does this work also on a non-parallel
server when the parameter service_names is configured? If yes,
how?

connection load balancing: does this also work on a non-parallel
server when the parameter service_names is configure? If yes,
how?

PRESPAWN_MAX is the maximum number of server processes to start.
How many server processes start in the beginning? and how to
control these server processes?

Is it possible to use DNS with host naming to connect to
multiple databases in a single machine by using GLOBAL_DBNAME?

Although TNS_ADMIN parameter is supposed to work with Windows NT
also, but why this parameter does not work in Windows NT?
(atleast with me) Is there something special that needs to be
done.

What is the benefit of specifying the OUT parameter in
connection pooling? How does connection pooling happen in this
case?

I did read through the documentation and metalink about what is
DCE. Is DCE Oracle specific? or does it only revolve around
Oracle (or is it possible to create a DCE network only through
Oracle or we need other networking services) Because what I
undertand from DCE is that it is infact a client server network
providing various services like time service, diskless
workstation etc.
What is meant by authenticated RPC in DCE?

What is the point in having multiple passwords for the listener?

Congrats for the new house.
Thanks in advance and best regards,
Mudassar Shahzad
and we said...
1) it'll terminate the connection.

2) you can set init.ora parameters to tell the database where
the server is.

LOCAL_LISTENER = network_name
Parameter class: Static
Default value: (ADDRESS = (PROTOCOL=TCP)(HOST=)(PORT=1521))

if you start the listener after the database, the database will
ulimately get in touch with the listener and register itself.
Best to start the listener first if you have any control over
that.

Connection load balancing improves connection performance by
balancing the number of active connections among multiple
dispatchers. In an Oracle Parallel Server environment,
connection load balancing also has the capability to balance the
number of active connections among multiple instances.

So, yes, it applies in a non-OPS environment as well.  It
distributes the load over the dispatchers in a MTS setup.

As for connect time failover:

When more than one listener supports a service, a client can be
configured to failover the client request to a different
listener if the first listener fails. Reconnection attempts
continue until the client successfully connects to a listener.

So yes, it works in a non-ops environment as well.  You can have
>1 listener setup for the same database or if you have redundant
databases -- you can use this to get logged into the other
database if the first is not available.

You use the pool_size parameter to set the initial size of the
pool.

Host naming enables users to connect to an Oracle server by
using a host name alias. Host names are mapped to the server’s
global database name in an existing names resolution service,
such as Domain Name System (DNS), Network Information Service
(NIS), or a centrally-maintained set of /etc/hosts files.
The connection is established by using the default TCP/IP port
for the listener, 1521. Multiple databases per node and database
location transparency are supported through matching global
database names with host name aliases.

TNS_ADMIN works on NT for me.  Here I have moved my tnsnames.ora
from network\admin to \temp.  At first I cannot connect but when
I set tns_admin I can (perhaps your environment is not what you
think?):

C:\oracle\network\ADMIN>sqlplus scott/tiger@ora8i.us.oracle.com

SQL*Plus: Release 8.1.6.0.0 - Production on Sat Nov 18 11:28:48
2000
(c) Copyright 1999 Oracle Corporation.  All rights reserved.
ERROR:
ORA-12154: TNS:could not resolve service name

Enter user-name: ^Z

C:\oracle\network\ADMIN>set TNS_ADMIN=c:\temp\

C:\oracle\network\ADMIN>sqlplus scott/tiger@ora8i.us.oracle.com

SQL*Plus: Release 8.1.6.0.0 - Production on Sat Nov 18 11:29:00
2000

(c) Copyright 1999 Oracle Corporation.  All rights reserved.

Connected to:
Oracle8i Enterprise Edition Release 8.1.5.0.0 - Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.0.0 - Production

scott@8i> exit
Disconnected from Oracle8i Enterprise Edition Release 8.1.5.0.0
- Production
With the Partitioning and Java options
PL/SQL Release 8.1.5.0.0 - Production

C:\oracle\network\ADMIN>

As for "OUT for connection pooling" -- I assume you meant "set
MULTIPLEX=OUT for when enabling connection concentration for
connection manager".

That allows many physical INBOUND network connections to be
multiplexed into fewer OUTBOUND connections to the database.
Connection manager typically runs on a machine separate from the
database.  The database couldn't handle 50k inbound physical
connections - it could handle 5k easily.  You would setup a
couple of front end machines with connection manager on it
multiplexing inbound connections to reduce the 50k inbounds to
just 5k or less outbound physical connections to the database.

DCE is an open standard and stands for Distributed Computing
Environment.   See
http://www.opennc.org/dce/
for more info.

As for the point of having multiple passwords -- not sure of the
exact reasoning behind it.  Since it only works with cleartext,
it is not very strong either.
Review & Followup

Rating: 4
meaning of CONNECT_TIMEOUT September 26, 2002
Reviewer: A reader
Hi Tom

I read the docus about CONNECT_TIMEOUT but I dont understand what it is talking
about.

What does it mean  by saying the listener waits for a valid connection request?
Followup:
A client connects to a listener (eg: you can use telnet to connect to a listener
for example).

connect timeout is the period of time between the client making a physical
connection and the client actually transmitting a valid packet of data that says
"Hello Lucy -- I'm home".

Sort of like a phone call.  Connect timeout is the amount of time you are
willing to spend from the time you answer the phone and say "hello" -- until
someone on the other end responds with "hello".  Eg: a crank caller -- they
don't say anything, you wait 5 seconds and hang up.  Thats the connect timeout.
Rating: 4
forgot to ask something more September 26, 2002
Reviewer: A reader
Hi I just asked about meaning of connect_timeout... I wanted to ask somthing
more, in some servers when I try to connect such as

scott/tiger@test

if test is not defined in tnsnames.ora I get an error straight away however in
some servers it takes ages to tell me the error

which parameter defines that interval?
Followup:
look in your sqlnet.ora -- there is an entry like:

NAMES.DIRECTORY_PATH= (TNSNAMES, ONAMES, HOSTNAME)

so, for me, if tnsnames.ora lookup fails (no test), it'll try Oracle names
server, if that fails, it'll try the default hostname (connect to a machine
named test on a known port and try to connect to a sid named test)

that could take a while, just reduce it to tnsnames and it would fail straight
away.
Rating: 3
September 26, 2002
Reviewer: Sagi from India
Hi Tom !

My doubts might seem to silly. But still I would learn. Therfore i am posting it
here.

q1) Is CONNECT_TIMEOUT value Seconds or Minutes?

q2) I have given CONNECT_TIMEOUT = 1 in my listner.ora and restarted it.

Now I connected using sqlplusw and after 1 minute i issed

SELECT SYSDATE FROM DUAL ;

Surprisingly i get the output. Should'nt it give error saying disconnected.
Because  I have not sent any information till 1 minute?

Regards,
Sagi
Followup:
q1) documentation says......

Determines the amount of time in seconds the listener waits for a valid
connection request after a connection has been started

so, seconds...

q2) why is that a surprise?  you were connected - -this is used to time out a
"bogus" connection.

think of that phone call.  You get a call (connect to listener).  You say hello
(listener says "please tell me what to do").  Nothing comes back (crank call).
So, you wait 3 seconds and hang up.  That is the connect timeout.

Now say you get a call -- you say hello -- they say hello -- conversation
started.  You will not hang up, even if they take 15 seconds to think about a
question you asked.

If you want IDLE timeouts (kill a session that has been idle for 30 seconds) --
see the CREATE PROFILE sql command.
Rating: 4
stop user from connecting April 14, 2003
Reviewer: Ashutosh from India
Hi Tom,
Is there any way I can stop any particular machine or group of machines from
connecting to my Oracle database? If yes then please let me know the details.
Thanks in advance
Followup:

http://download-west.oracle.com/docs/cd/B10501_01/network.920/a96580/profile.htm#484990
In 8i -- this was done in the protocol.ora file (obsolete file in 9i, use
sqlnet.ora instead)
Rating: 5
connections September 09, 2003
Reviewer: mo
Tom:

In a web app using mod plsql we started getting
"Too many database connections.. Try again after a few minutes. " and IE shows
an error number 503.

Does this indicate a problem in oracle or it could be the IBM AIX server
configuration. If it is oracle how do you fix it?

Thank you
Followup:
talk to your dba, processes is set too low
Rating: 3
December 28, 2003
Reviewer: A reader
1) CONNECT_TIMEOUT_listenr_name,what does it mean?should you give me a example
like you do for CONNECT_TIMEOUT(a phone example)
2)why don't Static Service Registration support Connect-time failover?
3)For >1 Listener ,one Listener and >1 instance,how does Connect-time failover
work?
Followup:
1) the meaning is exactly the same.  you can "name" your listeners, I do not, to
I don't need to add "_listener_name" to anything.

2) to have more then one instance register as the same service, you register
dynamically (pmon registers with the listener)

3) you use > 1 listener for connect time failover at the listener level:

<quote>
Connect-Time Failover

The connect-time failover enables clients to connect to another listener if the
initial connection to the first listener fails. The number of listener protocol
addresses determines how many listeners are tried. Without connect-time
failover, Oracle Net attempts a connection with only one listener.
</quote>
Rating: 3
December 29, 2003
Reviewer: A reader
but for 2),for example,Listener.ora can like this:
LISTENER1 =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.21)(PORT = 1529))
  )
LISTENER =
    (DESCRIPTION =
      (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.21)(PORT = 1521))
    )
  )

SID_LIST_LISTENER1 =
  (SID_LIST =
    (SID_DESC =
      (GLOBAL_DBNAME = ora9i2b)
      (ORACLE_HOME = E:\oracle\ora92)
      (SID_NAME = ora9i2b)
    )
  )
SID_LIST_LISTENER =
    (SID_DESC =
      (GLOBAL_DBNAME = ora9i2b)
      (ORACLE_HOME = E:\oracle\ora92)
      (SID_NAME = ora9i2b)
    )
  )

it can register more than one listeners for the same services,doesn't it?
December 28, 2003 Hetal -- Thanks for the question regarding "How do i prevent end users from connecting to the database other than my application?", version 8.1.5
You Asked
Hi Tom,
  Thanks for your help.

We have a client/Server and a web interface. We want our end
users
to connect to the database only through our applications. How
can we prevent the user from connecting to the database using
SQL*PLUS or Microsoft acess using ODBC, or other third party
tool.
We have been restricting them through roles, but the way our
user requirements are, there needs to be atleast one role that
is always enabled which i see as a potential security flaw. Any
help, pointers is appreciated.
and we said...
The best method, in my opinion, is to put your application in
the database.  In that fashion -- your users never have access
to any of the base tables -- only your procedures and functions
(your application).  In this fashion -- even if they get into
the database -- they can only run your application.

Short of that, you might look at fine grained access control
(FGAC).  See
http://osi.oracle.com/~tkyte/article2/index.html
for some info on that.

For example, we used FGAC in an online review system we built.
We made it so that if a specific security context was not set up
-- all queries return 0 rows for all selects, updates and
deletes and prevented the users from inserting any data.  So, if
I log into sqlplus -- the security context was not setup (the
correct procedure with the correct inputs was not executed to
set the security context) -- hence the tables appear "empty".

There is no enforcible way to restrict access to the database by
"program".  For example -- if your program was "foo.exe" and you
only wanted foo.exe to connect -- all i would have to do is:

rename foo.exe tmp.exe
copy sqlplus.exe foo.exe

run foo.exe (aka sqlplus) and be done with it....  I'm right
in...
Review & Followup

Rating: 4
March 21, 2002
Reviewer: Mark from Ca
When you say 'There is no enforcible way to restrict access to the database by
"program"', are you inferring that there is something intrinsically bad about
the method. I was thinking of doing the following:

create or replace trigger catch_violators_on_<table_name> as
 BEFORE INSERT, update, delete ON <table_name>  FOR EACH ROW
BEGIN
select 'ok' into v1
from v$session sess, ops$oracle.runtime_exemptions re
where sess.audsid = userenv('sessionid') and
      (not userenv('isdba') or
       sess.schemaname in (<acceptable nondba schemas>) or
       sess.program in (<acceptable program names>));
exception when no_data_found then
-- either capture violation information and continue or
-- write out using utlfile and stop insert,update,delete action
insert into runtime_violations
select schemaname, program, osuser, terminal, machine, sysdate, ...
from v$session where sess.audsid = userenv('sessionid') ;
END;

I know this is not pretty, but it is fast to implement and seems to do the job.
The application software is old and unsupported and the original developers
didn't seem to have a high priority on implementing a solid security process in
the app. This is on 8.0.5. Other than the trigger hit on performance, is there
another reason this may not be wise?
Followup:
Try this

copy sqlplus.exe somethingelse.exe

and see what happens.
Rating: 4
March 21, 2002
Reviewer: mark from ca
here i took the time to look this article up and ended up reading right by the
point of the article...thanks tom...next time i will be a little more observant
Rating: 3
what about non-default roles with passwords? March 22, 2002
Reviewer: Alexander Rakhalski from Moscow, Russia
Hi again, Tom!
I'm regularly reading your forum and believe it is most productive way to raise
my skills in Oracle. Thank you very much. Now some my thoughts on question.
1. It seems not very good idea to rely on granting access only to executable
routines (not tables), because a lot of security-related logic may be enforced
in client. So, if I say: "You can execute any of my routines in any sequence and
with any parameters you wish" it is not same,if I say: "You can access database
through my application only".
2. What if I grant privileges (including CREATE SESSION) to users through
non-default roles with passwords? Role's passwords unknown by users, but are
ENABLEd during application startup. Here appears other problem - how can I hide
role's passwords within application, but maybe, such approach has some value?
Followup:
1) I do not believe any security should be enforced in the client -- that is the
entire goal of FGAC -- to put it in the server, where (IMHO) it belongs.

If the client does the security - you had better erase all third party ad-hoc
tools, including sqlplus, brio reports, discoverer, anything.

I myself put the application in the database...

2) it would take me about 2 seconds to defeat your approach, maybe a little
longer if you used advanced security option with encryption (but not too much
longer).
Rating: 3
sorry for persistence March 25, 2002
Reviewer: Alexander Rakhalsky from Moscow, Russia
1. If it would take you about 2 seconds to defeat my approach, maybe you agree
to spend 2 second + (some seconds for writing)?
2. I already pointed out above my doubts regarding "granting only EXECUTE on
server routines" approach. Now, if we examine "fine grained access control"
approach, it still rely on "some magic actions, hidden in the client and
executed at startup". If in my approach it is some calls to DBMS_SESSION, in
your it is some calls to application context package. I can (potentially) log on
with SQL*Plus and execute some routines in application context package (like it
do my client application). Some security is left on client.
Followup:
1) turn on sqlnet tracing (done on the client) and run your application.  The
trace file will have your password in it.  Bamm -- I'm in, thank you very much.

2) FGAC does not rely on some magic actions, hidden in the client and executed
at startup.  It is 100% server contained -- 100%.  There is NO security needed
in the client.  If you design it that way -- we have no way of protecting you.
The preferred mode of setting an application context is during the ON LOGON
trigger -- before the application can even do anything in the database.
Rating: 3
I don't undestand March 25, 2002
Reviewer: Alexander Rakhalsky from Moscow, Russia
I don't understand, how can ON-LOGON trigger determine, is connection made
trough application or SQL*Plus?
Followup:
I didn't say it could (in 9i with ntier proxy authentication we do have
APPLICATION specific VPD, thats new).  Nor did I say that it has to.  The
on-logon trigger could tell if the app server was being used to connect or not.
If not, no data.  If so, data.

It doesn't have to.  The data is secured in the same fashion through SQLPlus as
anything.  That is my entire point.  If the application security is REMOVED from
the application and put back with the data (where in my opinion is rightly
belongs) you can safely access the data from ANY CLIENT, ANY WHERE, ANY TIME.

If you lock the security in the client, you can only access the data via that
client.  You have totally locked yourself in.  You know, when this web thing
became exciting, the hardest thing for people was supporting this new "paradigm"
on top of their existing systems.  How could you build new apps on top of an
existing system where the security was buried in tons of legacy code.  Most
people still don't do that (build on top of their existing systems) due to this
-- they build an ENTIRELY new system, pump data into that then run that data
through the existing applications (like a batch system).  If they had the
security with the data, they wouldn't need to glue systems together in a
piecemeal approach.
Rating: 3
thank you March 26, 2002
Reviewer: Alexander Rakhalsky from Moscow, Russia
Thank you, Tom, for your quick feedback.
Rating: 5
How can you prevent a User connecting to the Database Using ODBC? March 26, 2002
Reviewer: Senthil Kumar from Qatar
Hi Tom
You mean to say that we cannot prevent a user from connecting to the database
using ODBC? oracle should come out with a solution for this. I hope you agree
with me
Followup:
How could we.  ODBC looks no different to us then any other connection.  ODBC is
just an API on top of OCI (Oracle's call interface).  There is no way for the
database to notice the difference between an ODBC client and sqlplus -- they
look EXACTLY the same to us.

That aside, if you put the security in the database, where it belongs (IMHO)....
It quite simply *does not matter* if you connect via odbc, oci, jdbc, etc, etc,
etc.  If the data is always protected (and not just protected by some EXTERNAL
logic hidden in an application somewhere), it is always protected.  You no
longer CARE what people use to connect.
Rating: 3
One more useful method.. March 27, 2002
Reviewer: Kiran Kumar Srirama from India
Hi all,

Suppose you have a situation like this:

Consider a software (say .. SQL*Plus) connects to a database and perform some
action.
Suppose it uses TEST as an Oracle User ID to connect with the database.
Its quiet obvious that user TEST could connect to database using say
TOAD/VB/VC++ etc.
Now task is to restrict any software other than SQL*Plus to connect to Oracle
database.

Oracle Job scheduler (dbms_job)  uses the following procedure to acheive this
task.

Thought of sharing this with you all. Hope its usefull !

/* Oracle Job Scheduler  DBMS_JOB */
create or replace procedure p_jobscheduler is
begin
/* Submit a job to DBMS_JOB  as below */
/* jobno is a  bind variable you need to declare before running this block*/
/* SYSDATE + 1/18000 makes the job run after every 5 seconds */
    DBMS_JOB.SUBMIT (:jobno,'P_SCHEDULE;', SYSDATE, 'SYSDATE+1/18000');
end;
/

/* Procedure should run in INTERNAL/SYS/SYSTEM in order to function*/
/* Author : Srirama Kiran */
CREATE OR REPLACE PROCEDURE P_SCHEDULE IS
    CURSOR C_SESSION IS  SELECT SID,SERIAL#,USERNAME,MODULE
                FROM V$SESSION
                WHERE USERNAME IS NOT NULL
                AND     USERNAME NOT IN ('SYS','SYSTEM','INTERNAL');
/* Users SYS,SYSTEM,INTERNAL have been allowed to connect via any software */
/* Retrieve all the relevant columns from v$session*/
BEGIN
     FOR C_KS IN C_SESSION LOOP
/* Cursor For loop */
          IF NOT C_KS.MODULE = 'SQL*Plus' THEN
            dbms_output.put_line(C_KS.MODULE);
/* You should be in Oracle 8i and above to make this statement work. */

            EXECUTE IMMEDIATE ('ALTER SYSTEM KILL SESSION ''' || C_KS.SID || ','
|| C_KS.SERIAL# || '''');
        END IF;
     END LOOP;
EXCEPTION
/* Oops..something went wrong  !! Have a look ... turn on your serveroutput */

     WHEN OTHERS THEN
          DBMS_OUTPUT.PUT_LINE('ERROR OCCURRED' || SQLERRM);
END;
/

Please inform me if there needs to be some enhancement needs to be done.

Thanks & Regards,
Kiran Srirama
Followup:
all I need to do is issue:

dbms_application_info.set_module( 'SQL*Plus', '' );

and you are defeated.  This is the holy grail -- "only let this application
connect to my database".  Well, that particular holy grail is not achievable
(prior to 8i with ntier authentication and 9i with ntier in java
authentication).

The only way it's achieveable before that is to put the application in the
database (packages), grant execute on the application in the database and let
anyone connect via any method they want.  Now, the only thing they can do is run
your application.
Rating: 3
Your method is not so flexible March 28, 2002
Reviewer: kiran kumar srirama from India
Hi,

dbms_application_info.set_module( 'SQL*Plus', '' );
does not offer a flexibility of avoiding SYS/SYSTEM/INTERNAL or infact any other
user to be unconstrained.
Guess your solution is defeated here !!
Anyway, your solution is good enough but not so flexible.

Regards
Kiran Srirama
Rating: 5
kiran kumar srirama [sorry you're defeated] look this! March 17, 2003
Reviewer: Marcio from Brazil
ops$mportes@MRP816> grant create session to comm identified by comm;

Grant succeeded.

ops$mportes@MRP816>
ops$mportes@MRP816>
ops$mportes@MRP816> @conn comm/comm
comm@MRP816>
comm@MRP816>

-- In 5 seconds I could execute this at least 5 times ;)

comm@MRP816>
comm@MRP816> exec dbms_application_info.set_module( 'MyApp*Plux', '' );

PL/SQL procedure successfully completed.

[In another session where I have permission to see v$session]

ops$mportes@MRP816> select username, rpad(program, 20) program, rpad(module, 20)
module
  2  from v$session;

USERNAME                       PROGRAM              MODULE
------------------------------ -------------------- --------------------
                               ORACLE.EXE
                               ORACLE.EXE
                               ORACLE.EXE
                               ORACLE.EXE
                               ORACLE.EXE
                               ORACLE.EXE

COMM                           SQLPLUS.EXE          MyApp*Plux <<----
OPS$MPORTES                    SQLPLUS.EXE          SQL*Plus
OPS$MPORTES                    SQLPLUSW.EXE         SQL*Plus

13 rows selected.

[back a mortal user]

comm@MRP816> exec dbms_application_info.set_module( 'SQL*Plux', '' );

PL/SQL procedure successfully completed.

[go see v$session]

ops$mportes@MRP816> /

USERNAME                       PROGRAM              MODULE
------------------------------ -------------------- --------------------
                               ORACLE.EXE
                               ORACLE.EXE
                               ORACLE.EXE
                               ORACLE.EXE
                               ORACLE.EXE
                               ORACLE.EXE

COMM                           SQLPLUS.EXE          SQL*Plux <<----
OPS$MPORTES                    SQLPLUS.EXE          SQL*Plus
OPS$MPORTES                    SQLPLUSW.EXE         SQL*Plus

13 rows selected.

ops$mportes@MRP816>

See -- Your app was broken and any mortal user could do that ;)
Rating: 5
does userenv('isdba') work? December 28, 2003
Reviewer: A reader
Hi

I am not sure if it's my problem or this is how it works, I am trying to
determine if my user has dba privileges using userenv('isdba') and getting FALSE
when my user has DBA

select * from session_roles;

ROLE
------------------------------
CONNECT
RESOURCE
DBA
SELECT_CATALOG_ROLE
HS_ADMIN_ROLE
EXECUTE_CATALOG_ROLE
DELETE_CATALOG_ROLE
EXP_FULL_DATABASE
IMP_FULL_DATABASE
GATHER_SYSTEM_STATISTICS
WM_ADMIN_ROLE
PLUSTRACE

select userenv('isdba') from dual;

USEREN
------
FALSE

It only works for SYS, only SYS returns TRUE. But the documentation says this is
how we determine if we have dba role enabled or not. I even tried
sys_context('userenv', 'isdba') and getting same result

Any comments?
Followup:
it is not for that purpose.  is should probably be labeled "issysdba"

it tells you about "sysdba", not the dba role.

you would query session_roles (as you did) to see what roles are enabled.
Rating: 5
looks like a similar thread to .. December 28, 2003
Reviewer: Sudhir

http://asktom.oracle.com/pls/ask/f?p=4950:8:11481561878888595834::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:1445204371337,

December 28, 2003 Nisheeth -- Thanks for the question regarding "Partition tables", version 9.2
You Asked
Greetings Tom,

We have two schemas in our PROD environmet. We need to create a JOB to copy data
from audit tables in schema1 to audit tables in schema2. In schema2, audit
tables have same structure but they are partitioned on a timestamp field.

What JOB should do is

1. Copy data(from schema1 tables to scehema2 tables)
2. Delete the data, which has been copied to schema2, from schema1 tables.

At tha same time if some user is inserting(there would not be any updates on
audit tables)data into tables, while oracle is working on step1, that data
should not be deleted by step2.

Thanks in advance.
and we said...
well, there are lots of ways to do this.  The easiest that comes to mind would
be to use partitioning in the PROD environment as well.  In this fashion, you
can have two partitions -- only one of which is active today.  you simply alter
exchange yesterdays partition with an empty table and then add that table you
just swapped out to the other partitioned table.  Least amount of work from the
DB perspective:

ops$tkyte@ORA9IR2> @connect u1/u1

u1@ORA9IR2> create table audit_tab
  2  ( time_stamp   timestamp default systimestamp,
  3    zero_or_one  number default mod(to_char(systimestamp,'j'),2),
  4    data         varchar2(20)
  5  )
  6  partition by range( zero_or_one )
  7  (
  8  partition p1 values less than ( 1 ),
  9  partition p2 values less than ( maxvalue )
 10  )
 11  /
Table created.

one day rows go into partition p1, the next into p2, and so on.  Here, this
shows we used P1 today...

u1@ORA9IR2> insert into audit_tab (data) values ( 'hello world' );
1 row created.

u1@ORA9IR2> select * from audit_tab partition (p1);

TIME_STAMP                          ZERO_OR_ONE DATA
----------------------------------- ----------- --------------------
26-DEC-03 01.11.35.447358 PM                  0 hello world

Now, lets pretend it is tomorrow, so p1 is inactive and we want to move it

u1@ORA9IR2> create table t as select * from audit_tab where 1=0;
Table created.

u1@ORA9IR2> alter table audit_tab
  2  exchange partition p1
  3  with table t
  4  /
Table altered.

there, P1 is "gone" now, it is empty in the audit_tab but we have this table T
full of stuff....

u1@ORA9IR2> @connect u2/u2

u2@ORA9IR2> create table audit_tab
  2  ( time_stamp   timestamp default systimestamp,
  3    zero_or_one  number default mod(to_char(systimestamp,'j'),2),
  4    data         varchar2(20)
  5  )
  6  partition by range( time_stamp )
  7  (
  8  partition a values less than ( to_date( '26-dec-2003', 'dd-mon-yyyy') ),
  9  partition b values less than ( to_date( '27-dec-2003', 'dd-mon-yyyy') ),
 10  partition c values less than ( to_date( '28-dec-2003', 'dd-mon-yyyy') )
 11  )
 12  /
Table created.

same table, just partitioned differently -- by date

u2@ORA9IR2> alter table audit_tab
  2  exchange partition b
  3  with table u1.t without validation
  4  /
Table altered.

and now it is FULL of the stuff that had been in u1.audit_tab -- can be done
without generating undo and redo, no massive amounts of work -- simple data
dictionary updates.

Another approach would be to use flashback query -

u1@ORA9IR2> REM create table audit_tab
u1@ORA9IR2> REM ( time_stamp   timestamp default systimestamp,
u1@ORA9IR2> REM   data         varchar2(20)
u1@ORA9IR2> REM )
u1@ORA9IR2> REM /
u1@ORA9IR2>
u1@ORA9IR2> delete from audit_tab;

1 row deleted.

u1@ORA9IR2> insert into audit_tab (data) values ( 'hello world' );

1 row created.

u1@ORA9IR2> commit;

Commit complete.

u1@ORA9IR2>
u1@ORA9IR2> @connect u2/u2
u1@ORA9IR2> set termout off
u2@ORA9IR2> REM GET afiedt.buf NOLIST
u2@ORA9IR2> set termout on
u2@ORA9IR2>
u2@ORA9IR2> create table audit_tab
  2  ( time_stamp   timestamp default systimestamp,
  3    data         varchar2(20)
  4  )
  5  partition by range( time_stamp )
  6  (
  7  partition a values less than ( to_date( '26-dec-2003', 'dd-mon-yyyy') ),
  8  partition b values less than ( to_date( '27-dec-2003', 'dd-mon-yyyy') ),
  9  partition c values less than ( to_date( '28-dec-2003', 'dd-mon-yyyy') )
 10  )
 11  /

Table created.

u2@ORA9IR2>
u2@ORA9IR2> declare
  2     l_scn number := dbms_flashback.get_system_change_number;
  3  begin
  4          insert into audit_tab
  5          select *
  6            from u1.audit_tab AS OF SCN l_scn;
  7
  8          insert into u1.audit_tab ( data ) values ( 'after insert...' );
  9
 10          delete from u1.audit_tab
 11           where rowid in (select rowid from u1.audit_tab as of scn l_scn );
 12
 13          commit;
 14  end;
 15  /

PL/SQL procedure successfully completed.

u2@ORA9IR2> select * from audit_tab;

TIME_STAMP                     DATA
------------------------------ --------------------
26-DEC-03 01.25.14.723035 PM   hello world

u2@ORA9IR2> select * from u1.audit_tab;

TIME_STAMP                     DATA
------------------------------ --------------------
26-DEC-03 01.25.15.227622 PM   after insert...

 this one is tricky to set up, since the U1.AUDIT_TAB must exist for about 5
minutes before we can flashback on it (thats why it is commented out this time).
 But you see the point, we are viewing U1.AUDIT_TAB as of that SCN

Another approach, a delete trigger:

u2@ORA9IR2> create table audit_tab
  2  ( time_stamp   timestamp default systimestamp,
  3    data         varchar2(20)
  4  )
  5  partition by range( time_stamp )
  6  (
  7  partition a values less than ( to_date( '26-dec-2003', 'dd-mon-yyyy') ),
  8  partition b values less than ( to_date( '27-dec-2003', 'dd-mon-yyyy') ),
  9  partition c values less than ( to_date( '28-dec-2003', 'dd-mon-yyyy') )
 10  )
 11  /

Table created.

u2@ORA9IR2> grant insert on audit_tab to u1;

Grant succeeded.

u2@ORA9IR2>
u2@ORA9IR2> @connect u1/u1
u2@ORA9IR2> set termout off
u1@ORA9IR2> REM GET afiedt.buf NOLIST
u1@ORA9IR2> set termout on
u1@ORA9IR2>
u1@ORA9IR2> create table audit_tab
  2  ( time_stamp   timestamp default systimestamp,
  3    data         varchar2(20)
  4  )
  5  /

Table created.

u1@ORA9IR2> create or replace trigger audit_tab_trigger
  2  after delete on audit_tab for each row
  3  begin
  4          insert into u2.audit_tab (time_stamp, data )
  5          values ( :old.time_stamp, :old.data );
  6  end;
  7  /

Trigger created.

u1@ORA9IR2>
u1@ORA9IR2> insert into audit_tab (data) values ( 'hello world' );

1 row created.

u1@ORA9IR2> commit;

Commit complete.

u1@ORA9IR2>
u1@ORA9IR2> select * from u1.audit_tab;

TIME_STAMP                     DATA
------------------------------ --------------------
26-DEC-03 01.28.14.450350 PM   hello world

u1@ORA9IR2> delete from audit_tab;

1 row deleted.

u1@ORA9IR2> select * from u1.audit_tab;

no rows selected

u1@ORA9IR2> select * from u2.audit_tab;

TIME_STAMP                     DATA
------------------------------ --------------------
26-DEC-03 01.28.14.450350 PM   hello world

Me, I'm partial to the first approach frankly.  Least amount of work on what
could be potentially large objects here.

Are there other ways?  sure -- all variations on the delete+insert and read
consistency approach, or slow by slow (opps meant row by row) approach.  eg:

u1@ORA9IR2> create table audit_tab
  2  ( time_stamp   timestamp default systimestamp,
  3    data         varchar2(20)
  4  )
  5  /

Table created.

u1@ORA9IR2>
u1@ORA9IR2> insert into audit_tab (data) values ( 'hello world' );

1 row created.

u1@ORA9IR2> insert into audit_tab (data) values ( 'hello world' );

1 row created.

u1@ORA9IR2> insert into audit_tab (data) values ( 'hello world' );

1 row created.

u1@ORA9IR2> insert into audit_tab (data) values ( 'hello world' );

1 row created.

u1@ORA9IR2> commit;

Commit complete.

u1@ORA9IR2>
u1@ORA9IR2>
u1@ORA9IR2> @connect u2/u2
u1@ORA9IR2> set termout off
u2@ORA9IR2> REM GET afiedt.buf NOLIST
u2@ORA9IR2> set termout on
u2@ORA9IR2>
u2@ORA9IR2> create table audit_tab
  2  ( time_stamp   timestamp default systimestamp,
  3    data         varchar2(20)
  4  )
  5  partition by range( time_stamp )
  6  (
  7  partition a values less than ( to_date( '26-dec-2003', 'dd-mon-yyyy') ),
  8  partition b values less than ( to_date( '27-dec-2003', 'dd-mon-yyyy') ),
  9  partition c values less than ( to_date( '28-dec-2003', 'dd-mon-yyyy') )
 10  )
 11  /

Table created.

u2@ORA9IR2>
u2@ORA9IR2> select * from u1.audit_tab;

TIME_STAMP                     DATA
------------------------------ --------------------
26-DEC-03 01.33.52.990748 PM   hello world
26-DEC-03 01.33.53.054581 PM   hello world
26-DEC-03 01.33.53.118780 PM   hello world
26-DEC-03 01.33.53.182356 PM   hello world

u2@ORA9IR2> select * from u2.audit_tab;

no rows selected

u2@ORA9IR2>
u2@ORA9IR2> declare
  2      type ts_array is table of timestamp;
  3      type vc_array is table of varchar2(4000);
  4      type rid_array is table of rowid;
  5
  6      l_ts ts_array;
  7      l_data vc_array;
  8      l_rid  rid_array;
  9
 10      cursor c is select rowid, time_stamp, data from u1.audit_tab;
 11  begin
 12      open c;
 13      loop
 14          fetch c bulk collect into l_rid, l_ts, l_data limit 500;
 15
 16          forall i in 1..l_rid.count
 17              insert into audit_tab(time_stamp,data)
 18              values ( l_ts(i), l_data(i) );
 19
 20          forall i in 1..l_rid.count
 21              delete from u1.audit_tab where rowid = l_rid(i);
 22
 23          exit when c%notfound;
 24      end loop;
 25      close c;
 26  end;
 27  /

PL/SQL procedure successfully completed.

u2@ORA9IR2>
u2@ORA9IR2> select * from u1.audit_tab;

no rows selected

u2@ORA9IR2> select * from u2.audit_tab;

TIME_STAMP                     DATA
------------------------------ --------------------
26-DEC-03 01.33.52.990748 PM   hello world
26-DEC-03 01.33.53.054581 PM   hello world
26-DEC-03 01.33.53.118780 PM   hello world
26-DEC-03 01.33.53.182356 PM   hello world

u2@ORA9IR2>
Review & Followup

Rating: 5
Partition tables December 26, 2003
Reviewer: Nisheeth Mitharwal from KY USA
Tom,
Could you Pl expalin more about

"this one is tricky to set up, since the U1.AUDIT_TAB must exist for about 5
minutes before we can flashback on it (thats why it is commented out this time)"
Followup:
i flashed back on the table.

you cannot flashback on a table right away, it's gotta be around for a bit.

ops$tkyte@ORA9IR2> create table t ( x int );

Table created.

ops$tkyte@ORA9IR2> select * from t as of timestamp sysdate-1/24/60;
select * from t as of timestamp sysdate-1/24/60
              *
ERROR at line 1:
ORA-01466: unable to read data - table definition has changed

wait a couple of minutes and that'll work.
Rating: 5
Partition tables December 26, 2003
Reviewer: Nisheeth Mitharwal from KY USA
Tom,
What if user is working on already created table. Does 5 minute window applies
to only new tables(within 5 minutes of that table creation)?
Followup:
in the real world -- this "flashback effect" won't matter a bit since the tables
won't have "just been created"

Its just new things.  It was a caveat I pointed out for people that try to run
the examples.  If they don't wait a bit, the flashback one will fail.  But in
normal day to day -- it'll work just dandy since you haven't done any DDL on the
objects and won't be doing so.
Rating: 5
Reader December 26, 2003
Reviewer: A reader
Your first method, can it be applied to a sub-partition (LIST) of a
range partition.

For example: Suppose schema1 has a table with range partition on DATE and
sub-partition on CODE
I like to move the sub-partition that belong to today's range partition and
sub-partition (LIST) of code 'P' to identical table on another schema2

Can it be done similar to your first approach easily
Followup:
yes, you can to the alters on subpartitions
Rating: 5
Partition Exchange Performance December 28, 2003
Reviewer: A reader
If the partition to be exchanged and the table to be
exchanged are on different tablespaces, how to estimate
how long the exchange will take. Does oracle reads and
writes every block. If so, is this done in direct path
mode
Followup:
it does not matter where they are -- it turns the table into a partition and the
partition into a table -- it does not physically "move" them, just changes "what
they are"
December 29, 2003 -- Thanks for the question regarding "cursor_sharing similar", version 9.2.0.1
You Asked
hi

I have some questions regarding new cursor_sharing "SIMILAR" option

I have emp table with index on deptno
If I have a query which says

select * from emp where deptno = 10

it uses range scan because deptno 10 is scarce (screwed distribution), if I use
deptno 20 then it full scans the table.

Would cursor_sharing similar help me if my app is not using bind variables?
What can go wrong if we use this feature? In old FORCE option CBO was under the
risk of choosing wrong plan how about SIMILAR?
and we said...
The best bet -- have the application use bind variables WHERE appropriate and
constants where NOT appropriate.

Nothing else will come even marginally close!!!

In the above example, the optimizer, under similar, should detect that binding
"deptno = constant" would be bad due to the skewed data and would leave the
statement "as is"

For example:

ops$tkyte@ORA920.US.ORACLE.COM> /*
DOC>
DOC>drop table emp;
DOC>
DOC>create table emp as select * from scott.emp;
DOC>
DOC>exec gen_data( 'EMP', 50000 )
DOC>
DOC>update emp set deptno = 99;
DOC>update emp set deptno =  1 where rownum = 1;
DOC>
DOC>create index dept_idx on emp(deptno);
DOC>*/
ops$tkyte@ORA920.US.ORACLE.COM>

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

Table analyzed.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> alter system flush shared_pool;

System altered.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA920.US.ORACLE.COM> select * from emp where deptno = 99;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=41 Card=49999 Bytes=2749945)
   1    0   TABLE ACCESS (FULL) OF 'EMP' (Cost=41 Card=49999 Bytes=2749945)

ops$tkyte@ORA920.US.ORACLE.COM> select * from emp where deptno = 1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=55)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=2 Card=1 Bytes=55)
   2    1     INDEX (RANGE SCAN) OF 'DEPT_IDX' (NON-UNIQUE) (Cost=1 Card=1)

that shows, given constants -- the queries would do different things... So, we
test:

ops$tkyte@ORA920.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> alter session set cursor_sharing=similar;

Session altered.

ops$tkyte@ORA920.US.ORACLE.COM> select * from emp where deptno = 1;

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
----------
       143 wFZVhMqDxB QjPZKgTZw       3520 15-SEP-03   17353.19   80462.15
   1

ops$tkyte@ORA920.US.ORACLE.COM> alter session set cursor_sharing=exact;

Session altered.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select sql_text from v$sql
  2   where sql_text like 'select * from emp where deptno =%';

SQL_TEXT
------------------------------------------------------------
select * from emp where deptno = 1
select * from emp where deptno = 99

and we can see that it left it be.  No binds.  Now, we try again but take away
some information:

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> alter system flush shared_pool;

System altered.

ops$tkyte@ORA920.US.ORACLE.COM> analyze table emp delete statistics;

Table analyzed.

ops$tkyte@ORA920.US.ORACLE.COM> analyze table emp compute statistics
  2  for table
  3  for all indexes
  4  /

Table analyzed.

ops$tkyte@ORA920.US.ORACLE.COM> alter session set cursor_sharing=similar;

Session altered.

ops$tkyte@ORA920.US.ORACLE.COM> select * from emp where deptno = 1;

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
----------
       143 wFZVhMqDxB QjPZKgTZw       3520 15-SEP-03   17353.19   80462.15
   1

ops$tkyte@ORA920.US.ORACLE.COM> alter session set cursor_sharing=exact;

Session altered.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select sql_text from v$sql
  2   where sql_text like 'select * from emp where deptno =%';

SQL_TEXT
------------------------------------------------------------
select * from emp where deptno = :"SYS_B_0"

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

and here we did bind -- because the plans would NOT change in this case.  The
optimizer didn't have enough data to tell if the plans would change

But remember -- it is all just software.  The right answer:  you use binds when
you want to, you don't use them when you don't want to.
Review & Followup

Rating: 5
statistics October 22, 2002
Reviewer: A reader
Hi

So I guess if statistics are up to date and Oracle have histograms information
then it would use literals am I right?

How does the optimizer determine that the explain plan could potentially change?
From index statistics I guess.... :-?
Followup:
would vs should....

will it always, that would be nice.
will it get it 100% right, that would be nice.

It is based on the statistics -- it looks to see if using a literal vs using a
bind would materially affect the outcome (the plan generated)
Rating: 5
Bind variable April 09, 2003
Reviewer: Suj from USA
Hi tom,

column data is skewed and histograms are available.
There are sevaral similar sql statements with different literal values in
predicates. It is suggested to use bind variables and this will help in avoiding
parsing, but the plan may not be optimal for all sql's. How do we overcome this
problem? In this case presence histograms may not be useful.

If we use bind variables, how optimizer will decide to use index access or table
access, while preparing plans?

Thanks!
Followup:
how many is "several" (eg: how many copies of basically the SAME sql do you
have)

are you saying you have a transactional system whereby histograms make an impact
(probably a problem there)...
Rating: 5
bind variable April 09, 2003
Reviewer: suj
I mean about 100 to 2000 copies of same sql with different predicate values.

example:

select * from order where product_id = 1;
.......
.........
select * from order where product_id = 54232;

There is an index on product_id column and product_id column values are skewed.
Also Histograms are available for product_id column.

If we use bind variable as given below:

select * from order where product_id = :product_id;

Under what conditions index will be used ?
Is it based on selectivity of the column ?

How to ensure that the plan prepared initially is optimal for all
possible values for the bind variable in the sql as the same plan
is shared ?
I believe histograms may not be useful if we use bind variable.

Thanks for reply.
Followup:
Well -- you are telling me "there is not any single plan that is optimal for all
possible values".

Histograms are useful even with binds.

How skewed is skewed. does the application expect to recieve millions, hundreds
of thousands, thousands, hundreds or single digit numbers of row from this sort
of query.  How big is the table in the first place.
Rating: 5
suj April 10, 2003
Reviewer: suj
The table is about 100 million rows. Based on values of bind variables the
query's resultset can be from sevaral thousands say (10,000 rows) to 20 million
rows.

Thanks!
Followup:
what they heck are people going to do with 10,000 to 20 million rows exactly???

tell us more, sounds suspicious -- must be missing some key piece of information
here.
Rating: 5
bind variable April 16, 2003
Reviewer: suj
This is only an example used to illustrate scenario, and the figures provided
are adhoc just to simulate different optimal access path for the fetching the
required information.

Thanks for your input.
Rating: 4
some doubts December 28, 2003
Reviewer: Lexus Locklear
Hi

Using 9iR2 (9.2.0.2 64 bit on HP-UX) so might have this bind peeking effect...

In your replies you mentioned to use bind variables always if the database is
OLTP and use literals when needed, my question is our database does what you
say, mostly binds and some literals however there is a module in the application
that does not use bind at all so we had to set cursor_sharing to FORCE while
waiting for the consulting company to change their code. We usually generate
histograms on indexed columns and I wonder can this information used by the
optimizer if cursor_sharing is set to FORCE? You said histograms are useful even
with binds... how?

I have another question about histograms, we have another database, very similar
characteristics mostly binds and some literals and using cursor_sharing EXACT.
We usually dont generate histograms for this database until one day after some
new codes entered production one of the query was damn slow, the predicate of
the query which slowed things down was like this

service_code = 2

service_code has only 4 distinct values, from 1 to 4, most of them is 1 (table
has around 1 million rows and 950000 rows has 1 as service_code) so this is
perfect to use histograms. So we proceeded and generated histograms using
method_opt FOR ALL INDEXED COLUMNS. Our surprise is many queries started to
slowing down while this specific query is now running smoothly. I dont
understand, we are using EXACT cursors how histograms can make things worse? We
had to modify our DBMS_STATS calling routine and instead of using
gather_schema_stats we had to use gather_table_stats to only generate histograms
for this specific indexed column.
Followup:
histograms with bind variable peeking -- think about it.  bind variable peeking
says "hey, first time you see select * from t where x = :SYS_BV you should PEEK
at the bind variable, and pretend it was in there as a literal"

histograms are used in joins and other places as well to estimate cardinalities.

I would have investigates the use of optimizer_index_* parameters.  The plans
changed, the plans changed based on the histograms, the database uses dozens of
parameters to determine what plan 'is best' given its inputs.  the
optimizer_index_caching and optimizer_index_cost_adjust init.ora parameters have
a great influence over the decisisions made.

We can use the OPTIMIZER_INDEX_CACHING parameter to tell Oracle the percentage
of index blocks it could expect to find in the buffer cache on average. The
default of zero causes Oracle to believe that the cache is devoid of index
blocks. A maximum value of 100 causes Oracle to believe that the cache has all
of the index blocks.

If you think of the OPTIMIZER_INDEX_CACHING parameter as being used to tell
Oracle the percentage of an index that is cached, you can think of the
OPTIMIZER_INDEX_COST_ADJ parameter as telling Oracle how much of the table data
will be cached. The lower the number, the less costly single-block table
accesses become; conversely, the higher this number, the more costly. A way to
think of this would be that this number reflects the cost of performing
multiblock I/O (associated with full-table scans, for example) versus the cost
of performing single-block I/O (associated with index reads). If you leave this
parameter at the default setting of 100, these operations are costed the same.
Setting this value to 50 causes the optimizer to consider a single-block table
access as half as expensive as multiblock I/O would be, effectively cutting in
half the cost of table access (50/100)
Rating: 5
optimizer parameters December 28, 2003
Reviewer: A reader
Hi

We have all those optimizer_* parameters at default values, what I dont
understand very well is how come generating the histograms slows many queries
down. I thought generating histograms is giving more choices to the optimizer
but it seems that it's choosing the wrong path.

The question is when and when not to generate histograms. I think the root of
cause is the bind variable peeking, it choose the wrong plan in the first time
and then everything goes wrong. What do you think?
Followup:
it is choosing a path based on an assumption that index blocks are not to be
found in the cache and the cost of single block IO is the same as multi-block
io.

are either of those true for you -- or is the case that your indexes are mostly
cached reads and you have a pretty good amount of data cached in general.
Rating: 5
but if we dont use histograms December 28, 2003
Reviewer: Lexus Locklear
Hi

If we dont generate histograms all queries run fast except one... I dont think
this has nothing to do with the optimizer_* parameters?
Followup:
you are missing my point.

The optimizer is a mathematical model.  Currently, given the inputs it has, it
comes up with "plan a".

You give it more inputs -- and given that it is assuming the cache is devoid of
index blocks (it'll do a physical IO) and that the cost of single block IO is
the same as multi-block IO -- it comes up with plan b.

Assuming that the assumptions it is using are not deadly accurate (there are
some, perhaps LOTS of cached index blocks and the cost of single block IO is
probably less then a lot of multi-block IO) -- it is apparently the "wrong"
plan.

Your choices:

a) correct its assumptions
b) allow the assumptions to stay in place but don't give it all of the
histograms

c) (which I should have mentioned) much like b, let the assumptions stay in
place, use dbms_stats.gather_schema_stats PLUS a call to dbms_stats to SET the
histograms (which you know) for this one column
Rating: 5
optimizer* parameters December 29, 2003
Reviewer: Dana Day from Phoenix, AZ USA
I have used optimizer* parameters to greatly influence the processing of
applications. It works with or without histograms.
The behavior indicates (no test case for proof, sorry) that the parameters work
independently from the histogram information, influencing the optimizer as it
costs various plans. I have seen queries vary by over 1000X in wall clock and
logical io consumption simply by changing the parameters.
Try a standard set of problem queries AND non-problem queries, adjusting the
parameters in session, and tracing. You will see repeatable results. Use the
spectrum of different queries to develop an optimal application performance
profile.
December 29, 2003 H -- Thanks for the question regarding "tkprof output", version 8.1.7
You Asked
Tom,
I am trying to interpret this output and it is not making much sense. The TRACE
table has only 220 rows. What is your take on this?

Thanks.
********************************************************************************

select TRACE01.TYPE ,TRACE01.STATUS ,TRACE01.SEQUENCE into :b0,:b1,:b2
from
 TRACE TRACE01 where TRACE01.TYPE=NVL(RTRIM(:b3,' '),' ')

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        4      0.01       0.00          0          0          0           0
Execute  19012      3.10       3.56          0          0          0           0
Fetch    19012   1056.38    1517.15          0   43282981      76048       19012
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    38028   1059.49    1520.71          0   43282981      76048       19012

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 33  (OPS$MAXBATCH)

Rows     Row Source Operation
-------  ---------------------------------------------------
   4905  TABLE ACCESS FULL TRACE

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
   4905   TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'TRACE'

********************************************************************************
and we said...
The high water mark (the biggest the table every was) is very high (about 2275
blocks).  There was alot more then 220 rows at one point.

A table must full scan upto its HWM, consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t pctfree 90 pctused 10
  2  as
  3  select * from all_objects;

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from t where object_id <> 55;

23663 rows deleted.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on
ops$tkyte@ORA817DEV.US.ORACLE.COM> select object_name from t;

OBJECT_NAME
------------------------------
I_UGROUP1

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

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

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off

See, 5,395 consistent gets -- because the table used to be that big.  You would
either drop the table and repopulate or truncate it and repopulate to lower the
HWM.  In this case however -- an index on TYPE would "fix" the problem as well.

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

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> truncate table t;

Table truncated.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select * from tmp;

1 row created.

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on
ops$tkyte@ORA817DEV.US.ORACLE.COM> select object_name from t;

OBJECT_NAME
------------------------------
I_UGROUP1

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

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

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off

back down to 1 consistent get...
Review & Followup

Rating: 5
How to reset HWM December 14, 2002
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom
I have a table which increases in size from, say 200 rows in the morning to
about 11,000 rows in the evening, when a batch process deletes rows from this
table. Since this table stores session data, it is used very heavily. What
approach should I adopt to make sure that statistics are up to date during
daytime and the HWM is reset every night so the CBO is not thrown off track as
you demonstrated in your example. Is there a way to reset HWM without losing
availability ?

In your example, if you analyzed t after delete, what would be the effect?
Thanks
Followup:
I showed you the two ways to reduce the HWM for a simple table like this.
truncate and drop.

Use the scratch table+truncate+insert if you wish to lower the HWM.

Looks like you always full scan this table, no indexes - so stats are perhaps
not relevant here.  But if they are -- analyze during the day from time to time.
 11,000 rows will take seconds.

see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5554725037775
for the last question
Rating: 3
Alter table move December 15, 2002
Reviewer: Marc from Switzerland
Hi Tom

Instead of the scratch table+truncate+insert to lower the HWM I prefer "alter
table t move". This is just one statement, and guaranteed no conflicts with any
constraints and indices, triggers, grants, etc.
Followup:
excellent point, thanks -- totally slipped my mind.

remember to rebuild any indexes afterwards, but yes, this is a better approach.
Rating: 3
High Water Mark December 16, 2002
Reviewer: H from Pakistan
Tom,
How did you get the 2275 blocks?
Followup:
ops$tkyte@ORA920> select 43282981/19012 from dual;

43282981/19012
--------------
    2276.61377

query mode gets divided by number of times the query was executed.
Rating: 3
No. Of Blocks December 16, 2002
Reviewer: H from Pak.
At the top of tkprof output query and current are said to
be buffers not blocks. This what is confusing me. In your book you refernece
them as blocks. I am assuming they are blocks, hope my assumption is correct.

Also, I think that there is a code issue here (sql within FOR loop) as this a
vendor app.

What stats can i look at in data dictionary to find the HWM?

Thanks.
Followup:
so, what is the difference between a buffer and a block in your mind?  think of
them as the same. A buffer is where we put blocks after reading them.

search this site for

show_space hwm
Rating: 3
No. Of Blocks December 16, 2002
Reviewer: H from Pak.
I think of a buffer as a variable space verses block which is fixed (in our case
8k), to me a buffer may have more than one block. I never thought of it as 1-1
mapping.

Thanks
Followup:
there is the buffer cache -- thats the big thing with lots of blocks in it.

there then is a block buffer -- a buffered block.  they are 1-1.
Rating: 5
Clarification December 17, 2002
Reviewer: A reader
"
query mode gets divided by number of times the query was executed.
"

Does this calculation not give # of blocks in CR /execute.
How can this give total # of blocks in the table.

Also why there is a difference in number of rows returned
in tkprof (19012) vs execution plan ( 4905 )

Thanks
Followup:
Rows     Row Source Operation
-------  ---------------------------------------------------
   4905  TABLE ACCESS FULL TRACE

TABLE ACCESS FULL -- for every execute, every block was read.  Take total number
of blocks read during the repeated executions of the query, divide by the number
of executions -- you get the number of blocks in the table....

The rows are not different -- the 4905 isn't part of the query plan.  It is the
number of rows flowing through that phase of the actual execution plans
execution at runtime (eg: it is reality, it is what happened).

Now, not that you should expect the ESTIMATED cardinality to be dead on or even
close all of the time.  it is an ESTIMATION.
Rating: 3
# of blocks December 17, 2002
Reviewer: H from PAK.
The DD shows blocks and empty blocks as 2 and 22 respectively, hence howcome the
number 2280 (total number of blocks(query and current)/executions).

Can you please give some expert thoughts to clear up this disconnect.
Followup:
erh?

what "dd" -- do you mean Data Dictionary??? I have to assume so, that you don't
mean dd the unix utility (just a gentle reminder to all -- acronyms are great
but.... rather then saving two or three keystrokes, best to be clear...)

blocks and empty blocks are as of the LAST ANALYZE.  They are out of date as
soon as you stuff more data in there.

Search this site for

"show_space"

to find a utility that will give you instant information as of right now on the
object.
Rating: 3
# of blocks December 17, 2002
Reviewer: H from PAK.
The DD shows blocks and empty blocks as 2 and 22 respectively, hence howcome the
number 2280 (total number of blocks(query and current)/executions).

Can you please give some expert thoughts to clear up this disconnect.
Rating: 5
Thanks .... December 17, 2002
Reviewer: A reader
Tom,

The calculation of # of blocks in the table make much sense now, thanks.

As far the number of rows: The original posting says the the table has only
220 rows. The rows flowing through during TABLE ACCESS FULL is 4905 ( > 220 ).
From the tkprof, the rows got from 19012 executions is 19012. Could you put
all these in cohesive fashion so that understand better

Thanks
Followup:
Ok, so it took some amount of imagining but here is their scenario replicated to
a tee:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int, y char(2000) )
pctfree 90 pctused 10;
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select rownum, rownum from
all_objects where rownum < 2275;
2274 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from t where x > 220;
2054 rows deleted.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec show_space( 'T' )
Free Blocks.............................2054
Total Blocks............................2304
Total Bytes.............................18874368
Unused Blocks...........................29
Unused Bytes............................237568
Last Used Ext FileId....................7
Last Used Ext BlockId...................38793
Last Used Block.........................35

PL/SQL procedure successfully completed.

Ok, there we go.  We have their table -- same number of blocks -- same number
of rows.... Now, let me simulate their process.

Remember -- they had 4 parses, so I'll set up 4 parses:

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter session set sql_trace=true;
Session altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
  2      l_rec t%rowtype;
  3  begin
  4      for i in 1 .. 19012
  5      loop
  6      begin
  7             if ( mod(i,4) = 0 )
  8             then
  9                  select * into l_rec from t where x = mod(i,220)+1;
 10             elsif ( mod(i,4) = 1 )
 11             then
 12                  select * into l_rec from t where x = mod(i,220)+1;
 13             elsif ( mod(i,4) = 2 )
 14             then
 15                  select * into l_rec from t where x = mod(i,220)+1;
 16             else
 17                  select * into l_rec from t where x = mod(i,220)+1;
 18             end if;
 19      exception
 20          when no_data_found then null;
 21      end;
 22      end loop;
 23  end;
 24  /

PL/SQL procedure successfully completed.

Now, I do a tkprof with default settings (eg: aggregate=YES, so there will be
one report for the above 4 queries -- their text is the same, roll them
together:

SELECT *
FROM
 T  WHERE X = MOD (:b1,220) + 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        4      0.00       0.00          0          0          0           0
Execute  19012      2.05       1.95          0          0          0           0
Fetch    19012    536.14     537.00          0   43234322     133084       19012
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    38028    538.19     538.95          0   43234322     133084       19012

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 605     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
   4753  TABLE ACCESS FULL T

look familar?  same number of query mode gets more or less (full scan of 2275
blocks over and over)....

Same number of rows processed, 19012

About the 4,753 rows in the query plan there.  Well, why is is that number and
not one of:

220*19012   OR
19012 ???

In order to understand that, you have to understand what that number is.  The
4,735 is the number of rows flowing OUT OF the full scan (so it would not be
220/full scan since the query gets 1 row/full scan -- at MOST we would expect it
to be 19012 in this case).

So, why is it 4,735?  That is a function of the fact they parsed the query 4
times (had 4 cursors for it at some point like I did) AND they used
aggregate=yes by default on the tkprof command line.

Aggregate = yes aggregates all of the EXEC records (with consistent gets, cpu
times, etc) but it cannot/does not aggregate the STAT records.  The STAT records
are the actual query plans with row counts of the rows that flowed out of that
step (note: in 8.0 and before -- it was the number of rows that flowed INTO that
step so beware).  There are 4 sets of stat records for this tkprof since there
were four queries.  You are seeing the stat records for just ONE OF THEM.  When
I run the tkprof with aggregate = no, I get:

SELECT *
FROM
 T  WHERE X = MOD (:b1,220) + 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute   4753      0.56       0.36          0          0          0           0
Fetch     4753    134.05     134.48          0   10809356      33271        4753
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     9507    134.61     134.84          0   10809356      33271        4753

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 605     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
   4753  TABLE ACCESS FULL T

--------------------------------------------------------------------------------

SELECT *
FROM
 T  WHERE X = MOD (:b1,220) + 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute   4753      0.60       0.62          0          0          0           0
Fetch     4753    133.89     134.01          0   10808322      33271        4753
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     9507    134.49     134.63          0   10808322      33271        4753

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 605     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
   4753  TABLE ACCESS FULL T

SELECT *
FROM
 T  WHERE X = MOD (:b1,220) + 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute   4753      0.42       0.44          0          0          0           0
Fetch     4753    134.19     134.15          0   10808322      33271        4753
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     9507    134.61     134.59          0   10808322      33271        4753

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 605     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
   4753  TABLE ACCESS FULL T

--------------------------------------------------------------------------------

SELECT *
FROM
 T  WHERE X = MOD (:b1,220) + 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute   4753      0.47       0.53          0          0          0           0
Fetch     4753    134.01     134.36          0   10808322      33271        4753
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     9507    134.48     134.89          0   10808322      33271        4753

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 605     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
   4753  TABLE ACCESS FULL T

See -- 4 plans, 4 queries -- 4 stat records each with 4,753 rows in the query
plan AND in the rows column.  They all add up but when "aggregated" by tkprof --
they are not added up
Rating: 3
# of blocks December 18, 2002
Reviewer: H from Pak.
Yes DD=Data Dictionary, I did use your utility and got the same results.
Followup:
then you must have rebuilt the object AFTER you ran the tkprof BEFORE you did
the analyze.  Take my example above that EXACTLY replicates your scenario:

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select blocks, empty_blocks from user_tables
where table_name = 'T';

    BLOCKS EMPTY_BLOCKS
---------- ------------
      2274           29

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select blocks, empty_blocks from user_tables
where table_name = 'T';

    BLOCKS EMPTY_BLOCKS
---------- ------------
       220           35

You rebuilt it to lower the HWM and did that after the tkprof, before the
analyze.
Rating: 5
# of Blocks December 18, 2002
Reviewer: H from Pak
Thanks for the very detailed reply to the previous readers questions. That was
going to be my next question. Thanks to your clear explanation, many things are
making sense now.

Anyway, you got me thinking, and i did check the data dictionary before any
rebuilts but ran your utility after alter table compute. But, I am not sure of
few things so may be what you are saying is the reason for inconsistent # of
blocks. The only diff. i remember is using compute option verses esitmate.
Anyway, i am running another test so i will keep all the knowledge gained from
this discussion in mind.

THANKS for your expert advice !
Rating: 5
# of blocks December 18, 2002
Reviewer: H from Pak
Would you be willing to share the process(secret) of accurate esitmation to
replicate our exact enviornment?
Followup:
what do you mean -- ?  do you mean "how did you manage to pretty much replicate
our situation, what was the thought process?"

the physical process isn't secret -- anyone would be able to replicate your
numbers exactly as I did simply by running the above script.
Rating: 5
Just Brilliant ! December 18, 2002
Reviewer: A reader
Tom,

Thanks very much for the great explanation and the
brilliant example to simulate the results

Thanks so much for your time and for  having gone
great length to prove the technical theory so that
we readers can comprehend it

I understand the arithmatic very well now. I
appreciate it very much.  One of the useful things I
learn from this website is learning the technics in
setting up test cases to prove theories.

Your example and the derivation of results are
superb !

Best Regards
Rating: 4
tkprof equal parses= execute how to overcome February 13, 2003
Reviewer: umesh from Bangalore India
my parses = execute=4 ,fetch =8 for a single row retrived
consistent gets =1
all other attributes from tkprof seem to be ok
SELECT ROWID,UNIT_CODE, NAME, DEPT, ADDR_1, ADDR, LOCATION, STATE,
  POSTAL_CODE, COUNTRY, COMPANY_DOCTOR, DOCTOR_ROLE, CONTACT_PHONE_NO,
  CONTACT_FAX_NO, FDA_REGISTRATION_NUMBER, DATABASE_ID, TYPE_COMPANY_UNIT,
  USER_ID, TO_CHAR(DATE_MODIFIED,'YYYY-MM-DD HH24:MI:SS'),
  TO_CHAR(DATE_CREATED,'YYYY-MM-DD HH24:MI:SS'), RECORD_ID, UNIT_TYPE_FLAG,
  DGFPS_NO, INTERCHANGE_ID, E2B_FILE_FORMAT
FROM
 COMPANY_UNIT  Where  UNIT_CODE = 'BIO'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        4      0.00       0.00          0          0          0           0
Execute      4      0.00       0.00          0          0          0           0
Fetch        8      0.00       0.00          0          8          0           4
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       16      0.00       0.00          0          8          0           4

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 43

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID COMPANY_UNIT
      1   INDEX UNIQUE SCAN (object id 25233)
according the book expert one on one you recommend using bind variables but here
i need a constant
in another run i get parse=execute=fetch=159 for the same number of rows
processed how do go about
Followup:
You need to find the programmer that wrote this code and teach them "parse once,
execute many, don't close that cursor so far"

That will fix the parse count -- it should be 1.  the fetch count should be 4 as
well -- they must have coded something like:

   prepare
   open
   fetch
   fetch
   close

they should

   if ( firsttime ) prepare
   open
   fetch using array fetch size of 2

Same with the other more egregious example of 159.  it is a programming error on
their part.  they are too quick to close.
Rating: 4
tkprof equal parses= execute how to overcome February 13, 2003
Reviewer: umesh from Bangalore India
my parses = execute=4 ,fetch =8 for a single row retrived
consistent gets =1
all other attributes from tkprof seem to be ok
SELECT ROWID,UNIT_CODE, NAME, DEPT, ADDR_1, ADDR, LOCATION, STATE,
  POSTAL_CODE, COUNTRY, COMPANY_DOCTOR, DOCTOR_ROLE, CONTACT_PHONE_NO,
  CONTACT_FAX_NO, FDA_REGISTRATION_NUMBER, DATABASE_ID, TYPE_COMPANY_UNIT,
  USER_ID, TO_CHAR(DATE_MODIFIED,'YYYY-MM-DD HH24:MI:SS'),
  TO_CHAR(DATE_CREATED,'YYYY-MM-DD HH24:MI:SS'), RECORD_ID, UNIT_TYPE_FLAG,
  DGFPS_NO, INTERCHANGE_ID, E2B_FILE_FORMAT
FROM
 COMPANY_UNIT  Where  UNIT_CODE = 'BIO'

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        4      0.00       0.00          0          0          0           0
Execute      4      0.00       0.00          0          0          0           0
Fetch        8      0.00       0.00          0          8          0           4
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       16      0.00       0.00          0          8          0           4

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 43

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID COMPANY_UNIT
      1   INDEX UNIQUE SCAN (object id 25233)
according the book expert one on one you recommend using bind variables but here
i need a constant
in another run i get parse=execute=fetch=159 for the same number of rows
processed how do go about
Rating: 4
parse=execute February 14, 2003
Reviewer: umesh from india
Tom

why am i getting parse=executes
I am the only user connected to database

scott>var dno number
scott>exec :dno:=10

PL/SQL procedure successfully completed.

scott>select * from emp where deptno=:dno;

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
----------
      7782 CLARK      MANAGER         7839 09-JUN-81       2450
  10
      7839 KING       PRESIDENT            17-NOV-81       5000
  10
      7934 MILLER     CLERK           7782 23-JAN-82       1300
  10

scott>/

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
----------
      7782 CLARK      MANAGER         7839 09-JUN-81       2450
  10
      7839 KING       PRESIDENT            17-NOV-81       5000
  10
      7934 MILLER     CLERK           7782 23-JAN-82       1300
  10

scott>/

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
----------
      7782 CLARK      MANAGER         7839 09-JUN-81       2450
  10
      7839 KING       PRESIDENT            17-NOV-81       5000
  10
      7934 MILLER     CLERK           7782 23-JAN-82       1300
  10

scott>/

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
----------
      7782 CLARK      MANAGER         7839 09-JUN-81       2450
  10
      7839 KING       PRESIDENT            17-NOV-81       5000
  10
      7934 MILLER     CLERK           7782 23-JAN-82       1300
  10

scott>/

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ----------
----------
      7782 CLARK      MANAGER         7839 09-JUN-81       2450
  10
      7839 KING       PRESIDENT            17-NOV-81       5000
  10
      7934 MILLER     CLERK           7782 23-JAN-82       1300
  10

********************************************************************************

BEGIN :dno:=10; END;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          0          0           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 35
********************************************************************************

BEGIN DBMS_OUTPUT.GET_LINES(:LINES, :NUMLINES); END;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.02          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.01       0.03          0          0          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 35
********************************************************************************

select *
from
 emp where deptno=:dno

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        5      0.01       0.01          0          0          0           0
Execute      5      0.00       0.00          0          0          0           0
Fetch       10      0.00       0.00          0         10         20          15
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       20      0.01       0.01          0         10         20          15

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 35

Rows     Row Source Operation
-------  ---------------------------------------------------
      3  TABLE ACCESS FULL EMP

********************************************************************************
Followup:
because you are using sqlplus, a simplistic command line tool that accepts a
query, parses it, describes it, executes it, prints it over and over and over.

it is not "smart enough" to know that you are doing the same query over and over
-- it just parses each time.

fortunately, developers are smarter then sqlplus and can do better -- they can
realize "hey, I'm going to execute this more then once" and will just parse it
once (well, the smart ones do!)
Rating: 5
Row source 0 March 31, 2003
Reviewer: Trevor
Tom,

I am sure you have mentioned somewhere on this site
why the "rows" output with row source operation
which is output from tkprof is sometimes 0
Can you point me to a prior article ...
I have had no luck searching

Regards
Trevor
Followup:
don't remember such an article personally...  if you see the plan in the tkprof
without using the explain=u/p, then the STAT records got written.

Perhaps you are using explain=u/p to generate the plan?  for example, if I:

SQL> alter session set sql_trace=true;
SQL> select * from emp;
SQL> host <run tkprof NOW with explain=u/p>

I see:

select * from  emp

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          4          0          14
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          4          0          14

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 315  (OPS$TKYTE)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   TABLE ACCESS (FULL) OF 'EMP'

then I exit and go back into sqlplus:

$ exit

SQL> exit

and quite sqlplus (closing the trace file) and run tkprof again, I see:

select * from  emp

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          4          0          14
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          4          0          14

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 315  (OPS$TKYTE)

Rows     Row Source Operation
-------  ---------------------------------------------------
     14  TABLE ACCESS FULL OBJ#(48637) (cr=4 r=0 w=0 time=226 us)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
     14   TABLE ACCESS (FULL) OF 'EMP'

So - maybe it is the Execution Plan you are seeing with zeroes and that just
means "all cursors were not closed OR trace file was truncated -- hit max dump
size"
Rating: 5
My TKPROF output April 03, 2003
Reviewer: Rory Concepcion from Philippines
Hi Tom,

     I executed a statement which I would use for my daily extraction for ftp to
datawarehouse. I traced it but I didnt wait for the statement to finish coz it
has been running for half hour but still no output. I have the statement here
and the tkprof report. Could you help me in optimizing this query. thanks.

SELECT 'A'||'|'||RTRIM(AC.CUSTNUMBER)||'|'||AD.STREETNAME||'|'||AD.CITY||
'|'||AD.POSTCODE||'|'||AD.FLOOR||'|'||AD.BUILDING||'|'||AD.NO||'|'||AD.COUNTRY||
'|'||AC.CHILDNUM||'|'||AC.HOUSEHOLDNUM||'|'||AC.YEARREV||'|'||AC.EMAIL||
'|'||AC.SALUTATION||'|'||AC.FIRSTNAME||'|'||AC.LASTNAME||'|'||AC.MARITALSTATUS||
'|'||M.TRANSLATION||'|'||TO_CHAR(CO.DATEOFBIRTH,'YYYYMMDDHH24MISS')||'|'||AC.SEX
||
'|'||AC.EDUCATION||'|'||GS.OTHRSUBS1||'|'||GS.OTHRSUBS2||'|'||GS.OTHRSUBS3||
'|'||GS.OTHRSUBS4||'|'||GS.OTHRSUBS5||'|'||AC.SPOUSENAME||'|'||AC.EMPNAME||
'|'||AC.EMPSTATUS||'|'||AC.PROFESSION||'|'||AC.SPOUSEOCCPTN
FROM ACCOUNT AC, CONTACT CO, ADDRESS AD, GSMCUSTACCTINFO GS, MESSAGES M
WHERE RTRIM(AC.PRIMARYKEY) = RTRIM(CO.FOREIGNKEY)
and GS.FOREIGNKEY (+) = AC.PRIMARYKEY
AND RTRIM(AC.PRIMARYKEY) = RTRIM(AD.ACCOUNTKEY)
AND AC.RECORDTYPE not in('ARTICMSTmp','ARTProspec')
AND AC.YEARREV IN('AIncomeA','AIncomeB','AIncomeC','AIncomeD','AIncomeE',NULL)
AND M.MSGCODE = CO.NATIONALITY
AND TO_CHAR(AD.CREATED,'YYYYMMDDHH24MISS') > '20030401010101'
AND AD.EXPIRYDATE IS NULL

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.57       6.78          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2   1019.56    1251.56    8238702    8635100       1200           3
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4   1020.13    1258.34    8238702    8635100       1200           3

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 70  (POINT)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  CONCATENATION
      0   HASH JOIN
    175    TABLE ACCESS FULL ADDRESS
      0    NESTED LOOPS OUTER
      1     HASH JOIN
  19279      HASH JOIN
  56870       TABLE ACCESS FULL MESSAGES
1400107       TABLE ACCESS FULL CONTACT
      0      TABLE ACCESS FULL ACCOUNT
      0     TABLE ACCESS BY INDEX ROWID GSMCUSTACCTINFO
      0      INDEX RANGE SCAN (object id 8484)
      0   NESTED LOOPS OUTER
      1    HASH JOIN
    175     TABLE ACCESS FULL ADDRESS
      1     HASH JOIN
      4      TABLE ACCESS FULL ACCOUNT
  19279      HASH JOIN
  56870       TABLE ACCESS FULL MESSAGES
1400107       TABLE ACCESS FULL CONTACT
      0    TABLE ACCESS BY INDEX ROWID GSMCUSTACCTINFO
      0     INDEX RANGE SCAN (object id 8484)
      0   NESTED LOOPS OUTER
      1    HASH JOIN
    175     TABLE ACCESS FULL ADDRESS
     15     HASH JOIN
     92      TABLE ACCESS FULL ACCOUNT
  19279      HASH JOIN
  56870       TABLE ACCESS FULL MESSAGES
1400107       TABLE ACCESS FULL CONTACT
      0    TABLE ACCESS BY INDEX ROWID GSMCUSTACCTINFO
      0     INDEX RANGE SCAN (object id 8484)
      0   NESTED LOOPS OUTER
      1    HASH JOIN
     38     HASH JOIN
  56870      TABLE ACCESS FULL MESSAGES
    173      HASH JOIN
    134       TABLE ACCESS FULL ACCOUNT
1400107       TABLE ACCESS FULL CONTACT
    175     TABLE ACCESS FULL ADDRESS
      0    TABLE ACCESS BY INDEX ROWID GSMCUSTACCTINFO
      0     INDEX RANGE SCAN (object id 8484)
      3   NESTED LOOPS OUTER
      4    HASH JOIN
    179     HASH JOIN
  56870      TABLE ACCESS FULL MESSAGES
    500      HASH JOIN
    330       TABLE ACCESS FULL ACCOUNT
1400107       TABLE ACCESS FULL CONTACT
    175     TABLE ACCESS FULL ADDRESS
      0    TABLE ACCESS BY INDEX ROWID GSMCUSTACCTINFO
      3     INDEX RANGE SCAN (object id 8484)
      0   NESTED LOOPS OUTER
      0    HASH JOIN
      0     HASH JOIN
      0      NESTED LOOPS
     82       TABLE ACCESS FULL ACCOUNT
     81       TABLE ACCESS FULL CONTACT
      0      TABLE ACCESS FULL MESSAGES
      0     TABLE ACCESS FULL ADDRESS
      0    TABLE ACCESS BY INDEX ROWID GSMCUSTACCTINFO
      0     INDEX RANGE SCAN (object id 8484)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   CONCATENATION
      0    HASH JOIN
    175     TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'ADDRESS'
      0     NESTED LOOPS (OUTER)
      1      HASH JOIN
  19279       HASH JOIN
  56870        TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'MESSAGES'
1400107        TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'CONTACT'
      0       TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'ACCOUNT'
      0      TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF
                 'GSMCUSTACCTINFO'
      0       INDEX   GOAL: ANALYZED (RANGE SCAN) OF
                  'GSMCAI_FOREIGNKEY' (NON-UNIQUE)
      0    NESTED LOOPS (OUTER)
      1     HASH JOIN
    175      TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'ADDRESS'
      1      HASH JOIN
      4       TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'ACCOUNT'
  19279       HASH JOIN
  56870        TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'MESSAGES'
1400107        TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'CONTACT'
      0     TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF
                'GSMCUSTACCTINFO'
      0      INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'GSMCAI_FOREIGNKEY'
                 (NON-UNIQUE)
      0    NESTED LOOPS (OUTER)
      1     HASH JOIN
    175      TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'ADDRESS'
     15      HASH JOIN
     92       TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'ACCOUNT'
  19279       HASH JOIN
  56870        TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'MESSAGES'
1400107        TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'CONTACT'
      0     TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF
                'GSMCUSTACCTINFO'
      0      INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'GSMCAI_FOREIGNKEY'
                 (NON-UNIQUE)
      0    NESTED LOOPS (OUTER)
      1     HASH JOIN
     38      HASH JOIN
  56870       TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'MESSAGES'
    173       HASH JOIN
    134        TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'ACCOUNT'
1400107        TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'CONTACT'
    175      TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'ADDRESS'
      0     TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF
                'GSMCUSTACCTINFO'
      0      INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'GSMCAI_FOREIGNKEY'
                 (NON-UNIQUE)
      3    NESTED LOOPS (OUTER)
      4     HASH JOIN
    179      HASH JOIN
  56870       TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'MESSAGES'
    500       HASH JOIN
    330        TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'ACCOUNT'
1400107        TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'CONTACT'
    175      TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'ADDRESS'
      0     TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF
                'GSMCUSTACCTINFO'
      3      INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'GSMCAI_FOREIGNKEY'
                 (NON-UNIQUE)
      0    NESTED LOOPS (OUTER)
      0     HASH JOIN
      0      HASH JOIN
      0       NESTED LOOPS
     82        TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'ACCOUNT'
     81        TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'CONTACT'
      0       TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'MESSAGES'
      0      TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'ADDRESS'
      0     TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF
                'GSMCUSTACCTINFO'
      0      INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'GSMCAI_FOREIGNKEY'
                 (NON-UNIQUE)

Hope you could help me on this. Dont have much idea on how to fix statements
with so much LIO's.
Followup:
would there be a whole bunch of views hiding in there.

too bad you killed it -- you could have been a microsecond from getting the
entire result set.

Tell you what -- when I'm taking a question -- feel free to post this as a
question there.  you would include relevant things like all of the create views
that go with it, database versions, configuration information (*_area_size
settings and their ilk and so on).  This isn't the right place to do this here.
Rating: 3
Consistent gets ? April 04, 2003
Reviewer: Deepak Gupta from India
Dear Mr. Tom,

     Hi,

From your reply I interpret that consistent gets  in large number is not very
good  and  it is due to very high HWM or  table is very big .
To solve this problem either use move command or export and import

My question is ,  How to know that table has very HWM and what should ratio
(approx.) between consistent gets or total no of rows or other parameter

Thanks in advance.
Followup:
It is the RARE, obscure case where it is due to a table scan upto the HWM.

It is about a billion times more common that the causes are:

o over use of indexes
o improperly indexed tables
o poorly designed datamodels that force us to do a zillion LIO's to get the
answer.

don't concentrate on a single fact, it is not always the HWM - in fact, it
almost always *isn't*.

concentrated on identifying the queries that have the most impact on your system
(statspack).  concentrated on reducing their LIOs -- which may be done by

a) executing them less often -- did you really need to run it?  (many times,
surprisingly the answer is "NO")

b) executing them more efficiently.
Rating: 5
April 08, 2003
Reviewer: Rory Concepcion from Philippines
Hi Tom,

    I didnt really get what you meant. But did you say that I should post my
problem as a question? If it's that, I'm really sorry for posting it here. Cant
catch your site when I'm free to ask questions. I'll try to post this as a
question. Thanks.
Followup:
yes, that is what I meant -- it is a new different question with lots of
supporting material.  doesn't fit nicely here.
Rating: 4
no row source operation May 14, 2003
Reviewer: Eka from Indonesia
Hi Tom,
  Please help, i don't understand why my tkprof report output doesn't have row
source operation? also, the explain plan shows rows=0.
i'm using oracle 8.1.7.0.0 under windows nt.
i run tkprof tracefile outputfile explain=user/password@servicename from command
prompt, so i'm sure there're no open cursors.

Here's the report :

INSERT INTO TMP$AGING_B SELECT A.ITMINVOICENO,A.ITMCUSTNO,DECODE(A.ITMTRXTYPE,
  1,B.ITMTRXDATE,A.ITMTRXDATE) ITMTRXDATE   FROM (SELECT ITMINVOICENO,
  ITMCUSTNO,MIN(ITMTRXDATE) ITMTRXDATE,MIN(ITMTRXTYPE) ITMTRXTYPE   FROM
  AR9ITEMS  WHERE ITMAPPYEAR = :b1  AND ITMAPPMONTH = :b2  AND ITMCUSTNO
  BETWEEN :b3 AND :b4  GROUP BY ITMINVOICENO,ITMCUSTNO ) A,(SELECT
  ITMINVOICENO,ITMCUSTNO,(ITMTRXDATE + ITMCRDPRD ) ITMTRXDATE   FROM AR9ITEMS
   WHERE ITMAPPYEAR = :b1  AND ITMAPPMONTH = :b2  AND ITMCUSTNO BETWEEN :b3
  AND :b4  AND ITMTRXTYPE = 1 ) B  WHERE A.ITMCUSTNO = B.ITMCUSTNO (+)    AND
  A.ITMINVOICENO = B.ITMINVOICENO (+)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.04          0         95          0           0
Execute      1      1.42       2.11          0      15365        449       13207
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      1.44       2.15          0      15460        449       13207

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 30  (JCI)   (recursive depth: 1)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  INSERT STATEMENT   GOAL: CHOOSE
      0   HASH JOIN (OUTER)
      0    VIEW
      0     SORT (GROUP BY)
      0      TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF
                 'AR9ITEMS'
      0       INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'XIE5AR9ITEMS'
                  (NON-UNIQUE)
      0    TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF 'AR9ITEMS'
      0     INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'XIE5AR9ITEMS'
                (NON-UNIQUE)

Am i missing setup something in my database? i've already set
timed_statistics=true.
Thank you very much in advance.
Followup:
most likely your trace file is truncated.

look at max_dump_file_size, you might have to alter session to set it higher.
Rating: 4
tkprof shows highCPU May 27, 2003
Reviewer: Dhamo
Hi Tom,

We have a customer using our oracle server monitoring tool.
For him, the following query takes nearly 45% of CPU in one oracle server, Sun
OS, Oracle8.1.7.
But takes very less CPU time for other(about 10) servers.

create table TBSP_CANT
storage (initial 256K next 256K pctincrease 0) as
select /*+ RULE */ c.tablespace_name,
        max(a.extsize * 8192) max_seg_next_extent,
        nvl(max(b.bytes), 0) max_free_bytes
from SYS.SEG$ a, DBA_FREE_SPACE b, AEXTS c, SYS.TS$ f

where a.ts# = f.ts#
and f.name = b.tablespace_name (+)
and f.name = c.tablespace_name
and not exists(select /*+ RULE */ 'X' from EXCL_OBJS g
            where ( g.TS# = a.TS#
               or   g.TS# is NULL )
              and ( g.FILE# = a.FILE#
               or   g.FILE# is NULL  )
              and ( g.BLOCK# = a.BLOCK#
               or   g.BLOCK# is NULL)
              and ( g.USER# = a.USER#
               or   g.USER# is NULL))
group by c.tablespace_name;

where AEXTS => is the table that contains name of tablespaces that contains
autoextensible datafiles.
 EXCL_OBJS  => is containing objects info that are excluded from monitoring
              (in this case "no rows" in this table)

When looking into tkprof output :

From HighCPU server,

db_block_size = 8192
db_block_buffers = 16384
shared_pool_size = 140928615
sort_area_size = 10000000    (values set in init.ora)

create table TBSP_CANT
storage (initial 256K next 256K pctincrease 0) as
select /*+ RULE */ c.tablespace_name,
       max(a.extsize * 8192) max_seg_next_extent,
       nvl(max(b.bytes), 0) max_free_bytes
from SYS.SEG$ a, DBA_FREE_SPACE b, AEXTS c, SYS.TS$ f
where a.ts# = f.ts#
and f.name = b.tablespace_name (+)
and f.name = c.tablespace_name
and not exists(select /*+ RULE */ 'X' from EXCL_OBJS g
               where ( g.TS# = a.TS#
                  or   g.TS# is NULL )
                 and ( g.FILE# = a.FILE#
                  or   g.FILE# is NULL  )
                 and ( g.BLOCK# = a.BLOCK#
                  or   g.BLOCK# is NULL)
                 and ( g.USER# = a.USER#
                  or   g.USER# is NULL))
group by c.tablespace_name

call     count       cpu    elapsed       disk
------- ------  -------- ---------- ---------- ---
Parse        8      0.04       0.00          0
Execute      8   2674.18    2798.53       1036
Fetch        0      0.00       0.00          0
------- ------  -------- ---------- ---------- ---
total       16   2674.22    2798.53       1036

  query    current        rows
------- ----------  ----------
     16          0           0
 840500     434984          48
      0          0           0
------- ----------  ----------
 840516     434984          48

Misses in library cache during parse: 8
Optimizer goal: RULE
Parsing user id: 42

From a lowCPU server,

create table TBSP_CANT storage (initial 256K next 256K pctincrease 0) as
select /*+ RULE */ c.tablespace_name,
       max(a.extsize * 8192) max_seg_next_extent,
       nvl(max(b.bytes), 0) max_free_bytes
from SYS.SEG$ a,
     DBA_FREE_SPACE b,
     AEXTS c,
     SYS.TS$ f
where a.ts# = f.ts#
and f.name = b.tablespace_name (+)
and f.name = c.tablespace_name
and not exists(select /*+ RULE */ 'X'
               from EXCL_OBJS g
               where ( g.TS# = a.TS#
                  or   g.TS# is NULL )
                 and ( g.FILE# = a.FILE#
                  or   g.FILE# is NULL  )
                 and ( g.BLOCK# = a.BLOCK#
                  or   g.BLOCK# is NULL)
                 and ( g.USER# = a.USER#
                  or   g.USER# is NULL))
group by c.tablespace_name

call     count       cpu    elapsed       disk
------- ------  -------- ---------- ----------
Parse        2      0.01       0.01          0
Execute      4      6.46       8.18        224
Fetch        0      0.00       0.00          0
------- ------  -------- ---------- ----------
total        6      6.47       8.19        224

     query    current        rows
---------- ----------  ----------
         4          0           0
   1506708      39508          62
         0          0           0
---------- ----------  ----------
   1506712      39508          62

Misses in library cache during parse: 2
Optimizer goal: RULE
Parsing user id: 54

Both the databases are sized around 7 GB.

(i) Need to tune the operating system such as swap memory etc., ?
(ii)  Need to tune the database ?
(iii) Need to tune the memory related parameters
    Please suggest me whether the set values are sufficient or need to increase.

(iv) or Need to tune the query
    Tuning not required I feel, since other servers are working fine with the
same query.

(v) Any other possibilities. that causing this issue ?

Thanks for your valuable time. Could you pls suggest me, to solve this issue.

Thanks a lot,
Dhamo
Followup:
do an autotrace traceonly explain

compare the outputs.

are they the same?  betcha what is happening is that DBA_FREE_SPACE, which has a
hint unfortunately in some releases -- makes it work well when USED ALONE but
can mess up otherwise -- is getting the CBO involved and in light of no stats --
the plan is a mess.

You know what the best way to tune this is?  DON'T DO IT.  use LMT's and space
managment becomes trivial.  No more "is the next extent of some object larger
then the biggest free chunk I have" (thats that query)
Rating: 4
tkprof shows highCPU May 28, 2003
Reviewer: Dhamo
Hi Tom,

Thanks for the useful information.

I will cross-check with autotrace output. I will post you the
Explain Plan once I get the customer output.
Actually, I was thinking of tuning the operating system.

But as you said, if Oracle is doing the tricks all the way with DBA_FREE_SPACE,
couldn't we do anything for this to reduce the High Cpu.?

Appreciate your valuable suggestions.

Thanks Tom,
Dhamo
Followup:
tuning the OS? to fix a bad query?

thats a bit "backwards"

you tune applications in isolation
then in a concurrent mode
then you can think about tuning the database instance
and then, lastly, tuning the OS.

the best way to speed something up is to not do it at all.  I see what this
query is for and it is quite simply -- not needed if you use best practices
(LMTS or at least make every extent = every other extent and that darn
pctincrease=0)
Rating: 4
tkprof shows highCPU June 03, 2003
Reviewer: Dhamo
Hi Tom,

Thanks for the info about the tuning steps.
"to fix a bad query?" -- Tom, you mean the query is bad one?

I'm not able to get the "Execution Plan" for that query,
since query involved with dba_free_space, the only user "SYS" can get the
Explain Plan( query is run as non-sys user) for dba_free_space, because an
underlying x$ table with dba_free_space. I hope i am right.

I checked with customer reg. OS related information such as swap memory, etc.
are huge enough.

When using "set timing on" for that query the HighCpu server shows:
Elapsed: 00:04:23.46
and
LowCpu server shows:
Elapsed: 00:00:02.47
The SYSTEM tablespace is DMT and pctincrease set to 0.

Somewhere Oracle is doing the trick, I found no way to reduce the High CPU
consumption of the query.

Thanks for you time.
Looking forward your valuable thoughts.

Many thanks,
Dhamo
Followup:
to tune it - don't run it.  that has been my point.
Rating: 5
some more tkprof doubts June 03, 2003
Reviewer: A reader
Hi

How do we determine if the process is not using array processing?

If I have a result of a query:

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute   3377      1.19       1.90          0          0          0        3377
Fetch     3377      1.82     342.39       4759      15243          0           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     6754      3.01     344.29       4759      15243          0        3379

Is that using array processing?

Also how can execute phase read from disk/buffer....?
Followup:
I doubt it -- but even if it were -- so what.  it only fetched two rows.

in general, divide rows/fetches.  that is the array size.

Here, it looks like someone is doing an inefficient lookup.

queries can have execute stages, depends on the nature of the query, but you
decided not to share that with us, so I'm not going to guess ;)

it is unusual to have rows filled in for execute and fetch however. be
interesting to see the test case that brings that result about.
Rating: 5
June 03, 2003
Reviewer: A reader
Rating: 5
forgot the query June 04, 2003
Reviewer: A reader
hi the query was

SELECT 1
  FROM msefa_elem_fase
 WHERE msped_id_pedido = :b1
   AND msfas_id_fase = :b2
   AND msfam_id_familia = 'TB'
   AND mscom_id_componente = 'CN'
   AND msefa_sec_componente = 1
   AND mspar_id_parametro = 'PEDCAN'
   AND msefa_sec_parametro = 1

sorry!

I dont understand fetch rows during execution phase though
:(
Rating: 4
divide rows/fetches June 04, 2003
Reviewer: A reader
Hello

When you say divide rows by fetches which rows should we use? The one in execute
phase or the one in fetch phase?
Followup:
i'm not able to reproduce his example.  since the parse row is zero, it looks
like a mucked up trace file to me.

use the one in total.
Rating: 5
high buffer gets and insert June 05, 2003
Reviewer: A reader
Hi

I would like to know what can cause high buffer gets in an insertion. I have a
tkprof result

INSERT INTO gcprt_scon_solic_cons
            (scon_num_solic, dcrp_grp_cons, dcrp_tipo_cons, scon_tipo_movi,
             scon_valo_cons, scon_param_cons, scon_estado_solic,
             scon_usuario, scon_timestamp)
     VALUES (gcprt_scon_num_seq.NEXTVAL, :b1, :b2, :b3,
             :b4, :b5, :b6,
             :b7, SYSDATE)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.02          0          0          0           0
Execute    530     11.40      69.05       7520      57698       6340         530
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      531     11.42      69.07       7520      57698       6340         530

The table has only one index (primary key, sngle column), current gets is not
high (so not many modifications in that table I assume) but the buffer gets is
around 110 per executions. I think that´s high for an insert. Wat can be the
cause?

Thank you
Followup:
so, lets see the create table and all -- thats alot of DISK reads (physical
IO's) and current mode gets for a table with a single index on it.
Rating: 5
table definition June 05, 2003
Reviewer: A reader
Hi

the create table is as follows

CREATE TABLE GCPRT_SCON_SOLIC_CONS
(
  SCON_NUM_SOLIC     NUMBER                     NOT NULL,
  DCRP_GRP_CONS      VARCHAR2(10)               NOT NULL,
  DCRP_TIPO_CONS     VARCHAR2(10),
  SCON_TIPO_MOVI     VARCHAR2(2)                NOT NULL,
  SCON_VALO_CONS     VARCHAR2(300)              NOT NULL,
  SCON_PARAM_CONS    VARCHAR2(500),
  SCON_ESTADO_SOLIC  VARCHAR2(2)                NOT NULL,
  SCON_USUARIO       VARCHAR2(8)                NOT NULL,
  SCON_TIMESTAMP     DATE                       NOT NULL
)
TABLESPACE ETGCPRT100
PCTUSED    85
PCTFREE    10
INITRANS   1
MAXTRANS   255
STORAGE    (
            INITIAL          10M
            NEXT             128M
            MINEXTENTS       1
            MAXEXTENTS       2147483645
            PCTINCREASE      0
            FREELISTS        1
            FREELIST GROUPS  1
           )
NOCACHE
NOPARALLEL;

ALTER TABLE GCPRT_SCON_SOLIC_CONS ADD (
  CONSTRAINT PK_GCPRT_SCON_SOLIC_CONS PRIMARY KEY (SCON_NUM_SOLIC)
    USING INDEX
    TABLESPACE EIGCPRT100
    PCTFREE    10
    INITRANS   2
    MAXTRANS   255
    STORAGE    (
                INITIAL          10M
                NEXT             10M
                MINEXTENTS       1
                MAXEXTENTS       2147483645
                PCTINCREASE      0
                FREELISTS        1
                FREELIST GROUPS  1
               ));

It has a trigger as well on SCON_ESTADO_SOLIC, can that be the cause? (Does
buffer gets we see in tkprof the sum of insert and trgger?)
Followup:
yes, the trigger is the cause.  consider:

ops$tkyte@ORA817DEV> create table t ( x int primary key, y char(80) );

Table created.

ops$tkyte@ORA817DEV> @trace
ops$tkyte@ORA817DEV> alter session set events '10046 trace name context forever,
level 12';

Session altered.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> insert into t /* test1 */ (x,y) values ( -1, 'x' );

1 row created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace trigger t_trigger
  2  before insert on t
  3  declare
  4          l_cnt number;
  5  begin
  6          select count(*) into l_cnt from all_objects;
  7  end;
  8  /

Trigger created.

ops$tkyte@ORA817DEV> insert into t /* test2 */ (x,y) values ( 1, 'x' );

1 row created.

insert into t /* test1 */ (x,y)
values
 ( -1, 'x' )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          1         10           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          1         10           1

insert into t /* test2 */ (x,y)
values
 ( 1, 'x' )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      2.76       6.16        370      64886          9           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      2.77       6.17        370      64886          9           1
Rating: 5
Back to tkprof output June 05, 2003
Reviewer: Robert Ware from St. Louis Mo.
Do you know why the bottom of my tkprof output file looks like this when in fact
I have several cursors that consumed resources?

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      0      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        0      0.00       0.00          0          0          0           0

Misses in library cache during parse: 0

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       30      0.14       0.99          0          0          2           0
Execute     94      0.71       1.02         14          7        479          35
Fetch       81      7.46       8.08      34984      53278        376          75
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      205      8.31      10.09      34998      53285        857         110

Misses in library cache during parse: 2

   26  user  SQL statements in session.
    7  internal SQL statements in session.
   33  SQL statements in session.
   15  statements EXPLAINed in this session.
********************************************************************************
Followup:
umm -- in a word -- no.

I don't know what you mean here at all.   I see there were 26 sql statements in
the trace and the over all totals are above?
Rating: 5
tkprof output June 05, 2003
Reviewer: Robert Ware from STL
All of the SQL being executed is in couple of Packages.

I seem to remember something about stored PL/SQL SQL statements rolling up their
totals in tkprof as recursive SQL. Is this correct or am I dreaming.

I have searched several resouces and I can't find anything stating this in so
many words.
Followup:
It just looks like you have a truncated trace file or a partial one is all.
look at the raw trace file, is it complete?  does it say "truncated" at the
bottom?
Rating: 5
tkprof output June 05, 2003
Reviewer: Robert Ware from STL
The raw trace file is complete.

I just wondered why the OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS are
zeroed out.
Followup:
I would have to see the raw trace file (no i don't want it here) -- but if you
have my book Expert One on One Oracle -- chapter 10 tells you how to read a raw
trace file.  You'd be able to see if any of the sql that is in there, isn't in
there.

check out the raw tracefile.  see what it says, dep= is the recursive sql depth.

PARSING IN CURSOR #1 len=18 dep=0 uid=366 oct=3 lid=366 tim=1030128952796386
hv=1333943659 ad='58bf4ec4'
select * from dual

would not be recursive sql, dep=0

=====================
PARSING IN CURSOR #8 len=19 dep=1 uid=366 oct=3 lid=366 tim=1030128985013586
hv=4083983875 ad='58bedd7c'
select * from dual
END OF STMT
PARSE #8:c=0,e=89

would be -- only difference is that I did the first select * from dual at the
sqlplus prompt, the second in a plsql block
Rating: 5
Thanks June 09, 2003
Reviewer: Robert Ware
Tom,

You were right since all of the SQL in the raw trace file was executed within
PL/SQL blocks the Recursive Depth for all the cursors were greater than zero.
Apparently tkprof does not distinguish between user and system recursive sql.

unix040@pgblrel/home/rdware/pgblrel> grep dep=  ora_12121_pgblrel.trc | wc -l
238

unix040@pgblrel/home/rdware/pgblrel> grep dep=0  ora_12121_pgblrel.trc | wc -l
0

unix040@pgblrel/home/rdware/pgblrel> grep dep=1  ora_12121_pgblrel.trc | wc -l
224

unix040@pgblrel/home/rdware/pgblrel> grep dep=2  ora_12121_pgblrel.trc | wc -l
14
Rating: 5
Why the count is different... June 14, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
I was trying your examples in your book "Expert One On One-Oracle" pages 236-239
and I was seeing the difference in the "Fetch Count" shown by tkprof.

In my tests, the Fetch Count was always one less than those in your examples.
For example, here is one output from the "single_table_emp" SELECT (page 239):

select count(ename)
from single_table_emp, random
where
    single_table_emp.empno = random.x

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          4          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      4.15       4.16          0     135682          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      4.15       4.16          0     135686          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 88  (OPS$KKISHORE)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=135682 r=0 w=0 time=4165935 us)
 100000   NESTED LOOPS  (cr=135682 r=0 w=0 time=3891922 us)
 100000    TABLE ACCESS FULL RANDOM (cr=366 r=0 w=0 time=301644 us)
 100000    TABLE ACCESS HASH SINGLE_TABLE_EMP (cr=135316 r=0 w=0 time=2777918
us)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   SORT (AGGREGATE)
 100000    NESTED LOOPS
 100000     TABLE ACCESS (FULL) OF 'RANDOM'
 100000     TABLE ACCESS (HASH) OF 'SINGLE_TABLE_EMP'

As you can see, I'm getting the total Fetch Count as 3 whereas in your example
it is 4.

Is this due to a change in Oracle9i (9.2.0.3) optimization?
Thanks,
Followup:
what tool are you using to run the tests with.  In sqlplus, a simple

select count(*) from dual;

will show two fetches in tkprof using sqlplus (9203).  what does yours show.
Rating: 5
This is surprising... June 14, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
When I use SQL*Plus, I get the following results which are in line with what
seems to be expected:
==========================================================
select count(*)
from
 dual

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.01       0.00          0          3          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.00          0          3          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 88  (OPS$KKISHORE)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=3 r=0 w=0 time=182 us)
      1   TABLE ACCESS FULL DUAL (cr=3 r=0 w=0 time=158 us)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   SORT (AGGREGATE)
      1    TABLE ACCESS (FULL) OF 'DUAL'

==========================================================

But when I use another application (PL/SQL Developer,
http://www.allroundautomations.nl/
, I get following
results:

==========================================================
select count(*)
from
 dual

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          3          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          3          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 88  (OPS$KKISHORE)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=3 r=0 w=0 time=156 us)
      1   TABLE ACCESS FULL DUAL (cr=3 r=0 w=0 time=133 us)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   SORT (AGGREGATE)
      1    TABLE ACCESS (FULL) OF 'DUAL'
==========================================================

I could not understand why would there be a difference. Both the applications
are OCI applications using the same OCI API library to connect to same Oracle
database and it is the server which is generating this trace output.

Whay should the results be different when using different front-end application?
(when it all happens on the server anyway)?
Thanks,
Followup:
sqlplus technically should only have one fetch -- they are "double fetching"
un-necessarily.  It (sqlplus) are using an array size of 15 by default.  On the
first fetch -- it already KNOWS that there is no more data -- it got one row, it
got the ora-1403, it need not have made the second fetch.

It is because of the different clients that you saw that "little difference" in
the number.  Consider:

ops$tkyte@ORA920> select count(*) from dual just_plus;

  COUNT(*)
----------
         1

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          l_cnt number;
  3  begin
  4          select count(*) INTO l_cnt
  5            from dual in_plsql;
  6  end;
  7  /

PL/SQL procedure successfully completed.

select count(*)
from
 dual just_plus

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          3          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          3          0           1

********************************************************************************

SELECT count(*)
from
 dual in_plsql

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          3          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          3          0           1

Plsql (a real programming environment) has that optimization that sqlplus
could, but does not, have.  they only needed a single fetch call
Rating: 5
tkprof output on your example on page 464 - Expert one-on-one Oracle June 21, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
When I tried the example that you have on page 464 (actually the entire PL/SQL
block after setting the ALTER SESSION SET EVENTS command) gave me following
output:

UPDATE emp set ename = lower(ename)
where
 empno = :b1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.07       0.06          0          0          0           0
Execute      1      0.00     100.18          0          1          2           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.07     100.25          0          1          2           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 88     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  UPDATE  (cr=1 r=0 w=0 time=49141 us)
      1   INDEX UNIQUE SCAN PK_EMP (cr=1 r=0 w=0 time=27 us)(object id 37436)

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  enqueue                                        33        3.12        100.13

Here it shows the information on the wait events too. I guess this is a recent
addition to the TKPROF with Oracle9i (9.2.0.3). Does this mean that we may no
longer have to poke into the raw trace files anymore?

When looking at the output above, it shows me the wait event on "enqueue" with
total time waited. My question is about the "Times Waited" column. Why is this
column says Times Waited is 33 ? How did it arrive at that number?
Thanks,
Followup:
yes, this is a nifty new feature of Oracle9i's tkprof (9.0 and up)

you would poke about in the trace file to find out what :B1's value is - but you
don't need to look there for the waits anymore.

We timed out on the enqueue wait every three seconds, looking to see "whats up"
and then went back to sleep waiting for that blocking session to commit or
rollback.
Rating: 5
3 seconds, can it be changed? June 21, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
Totally forgot about the bind variable values in the raw trace file. Thanks for
reminding me.
But then, tkprof could as well print this on to the output trace file easily (it
is right there in raw trace file). I wonder why would it not be able to pick
that up?

In your reply above you said:
<quote>
We timed out on the enqueue wait every three seconds, looking to see "whats up"
</quote>

Is this 3 seconds the standard wait timeout for Oracle? Any reasons why it is 3
seconds? I guess the idea might be that anything less might be too less and
anything longer might be too long for the waiting session. Correct?

1). Is this 3 second timeout standard for all Oracle versions or is different
(or might change in future) for different Oracle versions?

2). Is it in any way dependent on the server configuration?

3). Is it documented somewhere, or we just "know it"?
Thanks,
Followup:
tkprof does not print them out currently.

3 seconds is just what the developers have -- it is why it could take as long as
3 seconds to detect a deadlock for example.

1) it and everything is always "subject to change"

2) no

3) we just "know it" -- well, we can deduce it from things like the above.
Rating: 5
problem June 23, 2003
Reviewer: A reader
Hi,

I'm getting following error while doing tkprof

*******************************************************************************
*

SELECT /*+ CHOOSE */ POREQUESTID,POLDGREFNO,POPOL,POPOD,POCREQSTAT,POVOYNO,

  POVESSCODE,POCONTOPER,POFNLPORT,POCONTNO,POPREFIX,POSERIAL,POEQUIPCAT,

  POSTWPOS

FROM

 DCGCSTWV3 WHERE POVOYNO=RTRIM(:V1) AND PORQSTSTAT='A' ORDER BY POVOYNO ASC,

  POCONTOPER ASC,POCONTNO ASC,POLDGREFNO ASC,POPOD ASC

call     count       cpu    elapsed       disk      query    current
rows
------- ------  -------- ---------- ---------- ---------- ----------
----------
Parse        1      0.00       0.00          0          0          0
0
Execute      1      0.00       0.00          0          0          0
0
Fetch       21      0.05       0.05          0       1350          0
20
------- ------  -------- ---------- ---------- ---------- ----------
----------
total       23      0.05       0.05          0       1350          0
20

Misses in library cache during parse: 0

Optimizer hint: CHOOSE

Parsing user id: 411  (OPS$T020SBK)

error during parse of EXPLAIN PLAN statement

ORA-00942: table or view does not exist

parse error offset: 209

Where DCGCSTWV3 is a view and public synonym has been created for the view.

Could you tell me what is the error? and how to resolve

Thanks.
atul
Followup:
you don't have the privileges on the base table to do an explain on that query.
so, it fails on the explain plan.

As long as you have a "normal" trace file from a completed session -- you don't
*need* that anyway - -the explain plan will be in there (the real one)
Rating: 5
problem June 23, 2003
Reviewer: atul from India
Hi,

I am getting following errors while tkprof

*******************************************************************************
*

SELECT /*+ CHOOSE */ POREQUESTID,POLDGREFNO,POPOL,POPOD,POCREQSTAT,POVOYNO,

  POVESSCODE,POCONTOPER,POFNLPORT,POCONTNO,POPREFIX,POSERIAL,POEQUIPCAT,

  POSTWPOS

FROM

 DCGCSTWV3 WHERE POVOYNO=RTRIM(:V1) AND PORQSTSTAT='A' ORDER BY POVOYNO ASC,

  POCONTOPER ASC,POCONTNO ASC,POLDGREFNO ASC,POPOD ASC

call     count       cpu    elapsed       disk      query    current
rows
------- ------  -------- ---------- ---------- ---------- ----------
----------
Parse        1      0.00       0.00          0          0          0
0
Execute      1      0.00       0.00          0          0          0
0
Fetch       21      0.05       0.05          0       1350          0
20
------- ------  -------- ---------- ---------- ---------- ----------
----------
total       23      0.05       0.05          0       1350          0
20

Misses in library cache during parse: 0

Optimizer hint: CHOOSE

Parsing user id: 411  (OPS$T020SBK)

error during parse of EXPLAIN PLAN statement

ORA-00942: table or view does not exist

parse error offset: 209

While DCGCSTWV3 is a view..and public synonym has been
created for the view

Could you tell me how to resolve this error

Thank.
atul
Rating: 5
Tkprof Error July 11, 2003
Reviewer: atul from India
Hi,

I'm getting following errors in TKPROF output

1)
Misses in library cache during parse: 0
Optimizer hint: RULE
Parsing user id: 166  (POBATCH2)
error during parse of EXPLAIN PLAN statement
ORA-01039: insufficient privileges on underlying objects of the view

2)Misses in library cache during parse: 0

Optimizer hint: CHOOSE
Parsing user id: 166  (POBATCH2)
error during parse of EXPLAIN PLAN statement
ORA-00942: table or view does not exists

Kindly suggest me a solution for this..

Thanks & Regards.
atul
Followup:
you are using EXPLAIN= (which isn't necessary) and the user/pass you are using
isn't allowed to run an explain plan on the object.

the plan should be in there without explain= and explain= is very easily
"tricked" into giving you the wrong plan anyway.  don't use it.
Rating: 5
TKPROF does not show query plan July 24, 2003
Reviewer: Vera from Washington D.C.
Thank you, Tom for all your work. You are the best. For a long time I wanted to
ask you a question, but was never able to get through. Do you know why my TKPROF
report never generates query plan? I have to use autotrace traceonly (not when I
run trace file), but as you know it does not show rows that go through each
step.
My sql_trace and time_statistics are set to true I have
Oracle8i Enterprise Edition Release 8.1.7.4.1 - Production
PL/SQL Release 8.1.7.4.0 - Production
CORE    8.1.7.2.1       Production
TNS for 32-bit Windows: Version 8.1.7.4.0 - Production
NLSRTL Version 3.4.1.0.0 - Production
Thank you
Followup:
do you exit the session that generated the trace?

if not, the STAT records are not written until a cursor is closed.
Rating: 5
Thank you July 25, 2003
Reviewer: A reader
Yes, I was viewing TKPROF report without exiting my session.
Thank you!
Rating: 5
TKPROF output analysis September 30, 2003
Reviewer: Phil Quinn from England
Thanks Tom, most informative.  Can I throw my tuppence worth in here -

We have moved our development application/database onto a production server.  We
find that on production, the update suite takes approx. twice as long to run.
The database structure is exactly the same, including same parallelisation,
indexes, stats. The machines are virtually identical, apart from the Development
server has quicker CPUs (don't ask).  I ran a simple query on both to see if it
would flag up any obvious differences, which would affect run time
accumulatively.  Looking at the TKPROF output, the only thing I see of relevance
is the cpu time on Production is more for the fetch, the elapsed time is also
more, but not to the same percentage as the cpu usage.  I'd appreciate it if you
could cast your eye quickly over the stats to see if I'm missing something here.
Does this reflect the difference in cpu speed ?

Version : 9.2.0.3 Win2k

Production Server
-----------------

select count(*) from person p,member m
where m.purn = p.purn

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.06       0.08          0          4          3           0
Execute      1      0.00       2.52          0          3          0           0
Fetch        2      2.53      62.77          0       2753          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      2.59      65.37          0       2760          3           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 32

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE
      0   SORT AGGREGATE
      0    HASH JOIN
      0     TABLE ACCESS FULL PERSON
      0     INDEX FAST FULL SCAN NEW_MEMBER_PURN (object id 36374)

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  PX Deq: Join ACK                                8        0.00          0.00
  PX Deq: Parse Reply                             5        0.00          0.00
  SQL*Net message to client                       2        0.00          0.00
  PX Deq: Execute Reply                          69        2.10          8.87
  PX qref latch                                  86        1.03         49.22
  PX Deq Credit: send blkd                      523        0.58          0.83
  PX Deq Credit: need buffer                    483        0.00          0.11
  PX Deq: Table Q Normal                          1        0.00          0.00
  latch free                                      2        0.00          0.00
  PX Deq: Signal ACK                              7        0.10          0.11
  SQL*Net message from client                     2        0.29          0.29
********************************************************************************
*********************************************************************************
****************************************************

Development Server
------------------

select count(*) from person p,member m
where m.purn = p.purn

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.73          0          0          0           0
Execute      1      0.00       1.81          0          3          0           0
Fetch        2      1.28      53.68          0       2753          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      1.28      56.22          0       2756          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 31

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=2753 r=0 w=0 time=53683445 us)
      0   SORT AGGREGATE (cr=0 r=0 w=0 time=0 us)
      0    HASH JOIN  (cr=0 r=0 w=0 time=0 us)
      0     TABLE ACCESS FULL PERSON (cr=0 r=0 w=0 time=0 us)
      0     INDEX FAST FULL SCAN NEW_MEMBER_PURN (cr=0 r=0 w=0 time=0 us)(object
id 40085)

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  rdbms ipc reply                                 3        0.00          0.00
  PX Deq: Join ACK                                1        0.00          0.00
  enqueue                                         1        0.00          0.00
  PX Deq: Parse Reply                             2        0.00          0.00
  SQL*Net message to client                       2        0.00          0.00
  PX Deq: Execute Reply                          37        2.06         11.44
  PX Deq Credit: send blkd                      541        0.35          0.98
  PX Deq Credit: need buffer                   1620        0.00          0.13
  PX qref latch                                  49        1.03         37.91
  PX Deq: Table Q Normal                          1        0.00          0.00
  PX Deq: Signal ACK                              4        0.10          0.10
  SQL*Net message from client                     2        0.01          0.01
********************************************************************************
Followup:
can you do something "non parallel" -- things get scattered in many trace files
with PQ.  Be nice to see the stat records and all filled in.
Rating: 5
Recursive SQL in tkprof October 10, 2003
Reviewer: Sam from San Mateo, CA
Tom,

Can you clarify whether the number of buffer gets reported in tkprof includes
recursive sql or not.  In ch 10 of the Oracle 9i Performance Tuning Guide, it
says:
"The resources reported for a statement include those for all of the SQL issued
while the statement was being processed. Therefore, they include any resources
used within a trigger, along with the resources used by any other recursive SQL
(such as that used in space allocation). With the SQL Trace facility enabled,
TKPROF reports these resources twice. Avoid trying to tune the DML statement if
the resource is actually being consumed at a lower level of recursion."

However, I did a simple test:
select * from sto_tab
where id = sto_select.get_id(id)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.02       0.01          0         32          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.03       0.02          0         32          0           1

Misses in library cache during parse: 1

Inside sto_select.get_id, I execute a query:
function get_id(x in number) return number is
xx number;
begin
  select count(*) into xx from all_objects where rownum <= 100000;

  return 1;
end;

I can see this sql in the trace file:
SELECT count(*)
from
 all_objects where rownum <= 100000

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.01          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        2      8.34       8.49        556     579806          0           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      8.36       8.50        556     579806          0           2

So you can see the buffer gets are not included in my original query.  Is the
Performance Tuning Guide wrong?  How about in DML statements? (I did my test on
a 9.2.0.3 database)
Followup:
it "used" to be true.  Consider this example:

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production

ops$tkyte@ORA817DEV> set echo on
ops$tkyte@ORA817DEV> @test
ops$tkyte@ORA817DEV> drop table t;

Table dropped.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create table t ( x int );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace trigger t_trigger
  2  before insert on t
  3  begin
  4          for x in ( select count(*) from all_objects )
  5          loop
  6                  null;
  7          end loop;
  8  end;
  9  /

Trigger created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> @trace
ops$tkyte@ORA817DEV> alter session set events '10046 trace name context forever,
level 12';

Session altered.

ops$tkyte@ORA817DEV> insert into t values ( 1 );

1 row created.

tkprof says:

insert into t
values
 ( 1 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      2.54       5.83        359      64968         12           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      2.54       5.83        359      64968         12           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 742
********************************************************************************

SELECT COUNT(*)
FROM
 ALL_OBJECTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      2.54       5.79        358      64967          4           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      2.54       5.79        358      64967          4           1

so the consistent gets were aggregated.  but the same in 9ir2 reports:

insert into t
values
 ( 1 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          1         20           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          1         20           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 89

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                       1        0.00          0.00
  SQL*Net message from client                     1        0.00          0.00
********************************************************************************

select count(*)
from
 all_objects

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      3.52       3.43          0      75729          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      3.52       3.43          0      75729          0           1

so it no longer does the double count

I'll file a doc bug.
Rating: 4
SQL performance issue... October 20, 2003
Reviewer: Rafi from CA, USA
QUERY:
======

SELECT
  AL1.MATERIAL_NUMBER,
  SUM ( AL1.QUANTITY_IN_UNIT_OF_ENTRY * -1),
  AL3.PROFIT_CENTER,
  Decode(sign((trunc(last_day(add_months(sysdate,
  -1))+1))- trunc(AL1.ENTRY_DATE)),
  0,
  AL1.ENTRY_DATE,
  -1,
  AL1.ENTRY_DATE,
  '01/01/1900'),
  trunc(sysdate,
  'IW'),
  Decode(AL1.MOVEMENT_TYPE,
  '601',
  'Trade',
  '633',
  'Trade',
  'IC')
FROM
  REPORT_VIEWER.Q_MM_DOCS_ODS_V AL1,
  REPORT_VIEWER.Q_CATALOG_MATERIAL_V AL2,
  REPORT_VIEWER.Q_CATALOG_MATERIAL_PLANT_V AL3
WHERE
  (AL1.SOURCE_SYSTEM=AL2.SOURCE_SYSTEM AND
  AL1.MATERIAL_NUMBER=AL2.MATERIAL_NUMBER AND
  AL3.SOURCE_SYSTEM=AL1.SOURCE_SYSTEM AND
  AL1.PLANT=AL3.PLANT AND
  AL1.MATERIAL_NUMBER=AL3.MATERIAL_NUMBER_PLANT)  AND
  (AL1.SOURCE_SYSTEM='D7' AND
  AL1.PLANT IN ('D723', 'D729', 'D750') AND
  AL1.DIVISION IN ('8A', 'AK', 'C5', 'PQ') AND
  AL1.ENTRY_DATE>=trunc(Sysdate,'IW') -91 and
  AL1.ENTRY_DATE<=trunc(sysdate) AND
  AL1.MOVEMENT_TYPE IN ('601', '633', '901', '905', '911', '933') AND
  AL1.MATERIAL_NUMBER LIKE '%#ABA' AND
  AL2.MATERIAL_TYPE='FERT' AND
  AL3.MRP_TYPE IN ('X0', 'XP') AND AL3.OBJECT_VERSION='A' AND
  AL2.OBJECT_VERSION='A')
GROUP BY AL1.MATERIAL_NUMBER, AL3.PROFIT_CENTER,
Decode(sign((trunc(last_day(add_months(sysdate,-1))+1))-
trunc(AL1.ENTRY_DATE)),0,AL1.ENTRY_DATE,-1,AL1.ENTRY_DATE,'01/01/1900'),
trunc(sysdate,'IW'), Decode(AL1.MOVEMENT_TYPE,'601','Trade','633','Trade','IC')

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.05          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch       26    411.22    6929.18    1813780    2243392          0         361
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       28    411.25    6929.23    1813780    2243392          0         361

Misses in library cache during parse: 1
Optimizer goal: FIRST_ROWS
Parsing user id: SYS

Rows     Row Source Operation
-------  ---------------------------------------------------
    361  SORT GROUP BY
  27018   NESTED LOOPS
  27019    NESTED LOOPS
    428     INLIST ITERATOR
    430      TABLE ACCESS BY INDEX ROWID /BI0/PMAT_PLANT
   1034       INDEX RANGE SCAN (object id 124058)
  27445     TABLE ACCESS BY INDEX ROWID /BIC/AZMMDOCR00
3638693      INDEX RANGE SCAN (object id 3434085)
  27018    TABLE ACCESS BY INDEX ROWID /BI0/PMATERIAL
  54036     INDEX UNIQUE SCAN (object id 97361)

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.03       0.05          0          0          0           0
Execute      3      0.00       0.01          0          0          0           0
Fetch       26    411.22    6929.18    1813780    2243392          0         361
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       31    411.25    6929.24    1813780    2243392          0         361

Misses in library cache during parse: 2
Misses in library cache during execute: 1

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       15      0.01       0.00          0          0          0           0
Execute     45      0.01       0.00          0          0          0           0
Fetch      225      0.03       0.46         42        175          0         430
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      285      0.05       0.46         42        175          0         430

Misses in library cache during parse: 5

    3  user  SQL statements in session.
   15  internal SQL statements in session.
   18  SQL statements in session.
********************************************************************************
Trace file: mtd.trc
Trace file compatibility: 8.00.04
Sort options: default

       1  session in tracefile.
       3  user  SQL statements in trace file.
      15  internal SQL statements in trace file.
      18  SQL statements in trace file.
      12  unique SQL statements in trace file.
     483  lines in trace file.
===========================================================

EXPLAIN PLAN:
=============

Operation    Object Name    Rows    Bytes    Cost    Object Node    In/Out
PStart    PStop

SELECT STATEMENT Hint=HINT: FIRST_ROWS        1           3021

  SORT GROUP BY        1      99      3021

    NESTED LOOPS        1      99      3016

      NESTED LOOPS        1      79      3014

        INLIST ITERATOR
          TABLE ACCESS BY INDEX ROWID    /BI0/PMAT_PLANT    8      240      86

            INDEX RANGE SCAN    /BI0/PMAT_PLANT~0    8           32

        TABLE ACCESS BY INDEX ROWID    /BIC/AZMMDOCR00    3      147      366

          INDEX RANGE SCAN    /BIC/AZMMDOCR00~05    3           106

      TABLE ACCESS BY INDEX ROWID    /BI0/PMATERIAL    1 K    30 K    2

        INDEX UNIQUE SCAN    /BI0/PMATERIAL~0    1 K         1

===========================================================
Hi Tom,

I have been having performance issues with the query given above. I asked the
DBAs for tracing and TKPROF output which is given above. I am not experienced in
interpreting TKPROF outputs.
Can you please help know what is wrong with this query?
How to interpret and investigate TKPROF output?

THanks in advance.

Regards
Rafi
Rating: 5
question regarding trace file generation November 06, 2003
Reviewer: A reader
Hi Tom
Thanx for a wonderful site
Let us say amazon is using Oracle. A user is
running into a problem (for one moment, imagine we
are not using conneciton pooling.)
The user is one of the thousands who are logged in.
If I want to isolate the user session trace by creating
a new user, logging in, executing the problematic url,
waiting for it to complete and then logging out.
In this case, how do I find the session that needs to be traced in the first
place. Say in the logon trigger, how would I find out which user is the one
that just logged in (since the users may not be
database users - they may be just email addresses?

Hope I am clear in my question.
thanx!
Followup:
amazon is.... but they do not use a stateful session (it would kill them)

the application needs to help out here.  my url's all allow for SQL TRACING and
my own "instrumentation" to be turned on via flags on the fly.

so, if the application is correctly code, anticipating the need to be traced,
this is easy.

if not, well, its really hard.
Rating: 5
could you kindly answer the above q for 10G and 9ir2 ? November 06, 2003
Reviewer: A reader
thanx!
Followup:
this is a mighty long page at this point in time.

"what above 'q'"
Rating: 5
how about this? November 06, 2003
Reviewer: A reader
we create a test user and log in, do the problem
user action and log out. Simultaneously, we have a logon trigger. Even if we are
using proxy authentication,
is not it possible to 1
1. intercept the test username at the application server level and "tag" it in
some way
2. find out the user at the db level when you log in
by looking at the tag.

in other words why do we loose the "email address user id"
info due to proxy authentication?

What am i missing?
thanx!
Followup:
i'm not sure at all what you are asking??

what is "email address user id"??

what is the tie in to proxy authentication?

what is a test username???
Rating: 5
thanx tom! November 07, 2003
Reviewer: A reader
May be I am confused.
"i'm not sure at all what you are asking??
what is "email address user id"??"

the email address user id is the user id used in amazon.
From customer point of view, our user id is our
email address.

"what is the tie in to proxy authentication?"

I was assuming that amazon.com does not
create a database user for every "email user id" that we give. This may be a
wrong assumption.

"what is a test username??? "

The test username is the user we create to generate
trace file for a performance problem scenario. If
amazon.com had created a database user for every
"email id", then would not tracing a session be
as trivial as doing the following in the logon trigger?:

if( user = "test_user" ) then
  alter session to enable trace
end if

As far as proxy authentication, I admit I dont understand
it completely yet - will have to refer to your book.

Sorry for the confusion. Also, in 10G there is a dbms_monitor package using
which it becomes easier
to generate and identify trace files relevant to
your performance problem. Does this in anyway help
us in identifying the problematic session?

thanx!
Followup:
yes, you could use "test user" accounts (accounts that always enabled trace) but
the problem is -- the issue might only occur for SOME user or SOME USERS.  test
user might not be affected.

I will standby my statement "application better be able to help me here, if not,
application is worthless"

dbms_monitor still needs an "invoker".  the APPLICATION needs to be designed to
be able to help in the tuning phase.  It is all about instruementing the
application itself.
Rating: 5
I agree Tom! November 07, 2003
Reviewer: A reader
 There is no better solution to this than application
instrumentation. But I am trying to find a way to do it
without application instrumentation.

"yes, you could use "test user" accounts (accounts that always enabled trace)
but
the problem is -- the issue might only occur for SOME user or SOME USERS.  test
user might not be affected.
"

The test user could be generated and the same data that
the "problem" user has could be copied to the test
user. Then at the same time that the problem user
has a problem, you run the test user through the
problem user action and get the trace file. Is
there any loophole in this method?
Basically, copy the "local" environment (such as user
data of the problem user to the test user.

The global env is retained same by running user action
for test user at the time of day when the problem user
sees the problem.

But this is possible only if there are real database
users for each of the user. If we log them in as a
proxy user, this may not be feasible, right?

many thanx!
Followup:
you can do anything anyway...

everything is "possible"

it is just that there is the right way to do it and the really hard way...

creating a test user like that would be really really hard (and maybe not even
possible!)  does your application have a "clone user"?  doubt it -- since it
doesn't have "trace me" :)
Rating: 5
thanx Tom! I agree.. November 07, 2003
Reviewer: A reader
One more question though.
having proxy authentication (not having
real db users ) - does it further complicate the
above "theoretical" approach of creating test user?
In other words, is there a way for me to figure
out that user a@amazon.com logged in, even if I
am using proxy authentication?

thank you!
Followup:
test user, proxy user, whatever or not, in a login trigger -- yes it complicates
(as in obviates) the login trigger, because the application would have to TELL
the database what the user is (and the code that would have been in the login
trigger would have to be called AT THAT point to enable tracing -- meaning, back
to square one.  It is all about the application)
Rating: 5
analysis of the TKprof report November 17, 2003
Reviewer: Yogeeraj from Mauritius
Dear sir,

Please allow us ask a question concerning the analysis of our TKPROF report.

We have a case where the users are suffering from performance problems (SLOW)
with an application and we are thus anlysing using SQLTRACE and TKPROF first...

In your book (Expert One-on-One Oracle; Page 460), you clearly explained the
line "Misses in library cache during parse: 0".

<quote>
This is telling us that the query we executed was found in the shared pool - we
did not generate a miss on the library cache during this parse. It indicates
that we performed a soft parse of the query. The very first time a query is
executed, we would expect this count to be 1. If almost every query you execute
has a 1 for this value, it would indicate you are not using bind variables (and
you need to fix that). You are not reusing SQL.
</quote>

we have 2 questions:
1. can there be any other cases where we have a '1' value(apart from when "not
using the bind variables")?

2. What does the following line imply:
 "Misses in library cache during execute: 1"

Can we correlate the CPU/Elapsed values with it? (Does it impact on
performance?)

We are getting it with the following query:
============================================================
********************************************************************************

INSERT INTO WHT140 (
WRHNUM,TRANSTYPE,TRANSID,INPDATE,PALLETNO,PRODSEQ,SKU1QTY,SKU2QTY,SKU3QTY,PRODQTY
1,PRODQTY2,OUTRACK,OUTBIN,OUTLVL,STATUS )  SELECT
:b1,'OUT',:b2,SYSDATE,:b3,WHT100.PRODSEQ,WHT100.SKU1QTY,WHT100.SKU2QTY,WHT100.SKU
3QTY,WHT100.PRODQTY1,WHT100.PRODQTY2,WHT120.RACK,WHT120.BIN,WHT120.LVL,'NEW'
FROM WHT100,WHT120  WHERE WHT100.WRHNUM = :b1
AND WHT100.PALLETNO = :b3  AND WHT100.PALLETSTATUS = 'BIN'  AND WHT100.PALLETNO
= WHT120.PALLETNO
AND WHT100.WRHNUM = WHT120.WRHNUM

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      1      0.01       0.01          0         19          9           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        1      0.01       0.01          0         19          9           1

Misses in library cache during parse: 0
Misses in library cache during execute: 1
Optimizer goal: CHOOSE
Parsing user id: 41     (recursive depth: 1)
********************************************************************************

thank you in advance for your reply.

best regards
Yogeeraj
Followup:
in this case -- the entire report is suspect for this query since you didn't
have sql_trace enabled during the parse phase at all!
Rating: 5
More data November 17, 2003
Reviewer: Yogeeraj from Mauritius
$ grep -i "total" report.txt

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       74      0.02       0.04          0         90          0          30
total       25      0.02       0.06          3         27          0           9
total       33      0.02       0.04          4         50          0          10
total       51      0.00       0.00          0         68          0          24
total      105      0.00       0.03          1         29          0          80
total       42      0.01       0.06         10         50          0          14
total       15      0.00       0.00          0         10          0           0
total       36      0.00       0.00          0         36          0          21
total       77      0.00       0.01          0        102          0          30
total       18      0.07       0.24         96        474         48         219
total      127      0.03       0.03          1        168          0          56
total        2      0.07       0.10          1       6365          7           1
total        2      0.03       0.04          0       3616          5           1
total        2      0.12       0.18         25      13716          9           1
total        6      0.00       0.00          0          8          0           0
total        2      0.01       0.01          0          3          1           1
total       12      0.00       0.01          0          4         16           4
total        2      0.00       0.00          0          0          0           0
total        2      0.00       0.03          1          4          0           1
total       10      0.00       0.01          1         10          0           2
total       11      0.00       0.02          1         12          0           4
total       12      0.01       0.01          1          8          1           4
total        3      0.00       0.01          0          3          4           1
total        9      0.01       0.00          0         15          0           0
total       23      0.00       0.00          0          9          0          14
total        3      0.00       0.01          0         56          0           1
total        3      0.00       0.00          0          1          4           1
total        3      0.01       0.00          0          1          4           1
total       61      0.01       0.01          1         50         20          50
total       37      0.00       0.01          0         30         12          30
total       27      0.01       0.00          2         18         12         351
total       36      0.00       0.01          1         27         12         483
total       24      0.01       0.01          2         15         12         264
total        3      0.00       0.02          0          0          0           0
total       18      0.00       0.00          0          9         12         141
total        3      0.01       0.00          1          3          0           1
total       10      0.00       0.01          2         21          1           7
total       10      0.01       0.02          4         19          1           7
total        3      0.00       0.02          0        238          0           1
total        3      0.01       0.00          0         21          4           1
total        3      0.01       0.00          0          2          0           1
total        2      0.00       0.00          0          2          3           1
total        1      0.00       0.00          0          1          1           1
total        1      0.01       0.01          0         19          9           1
total        3      0.00       0.00          0         11          0           1
total        2      0.06       0.07          0       6364          7           1
total        2      0.00       0.00          0          2          1           1
total        2      0.12       0.13          0      12731         15           3
total        2      0.07       0.12          3       7231         25           3
total        2      0.23       0.29          3      27266         23           3
total        2      0.00       0.00          0          6          3           3
total        3      0.01       0.03          1        709          4           9
total       24      0.01       0.01          0         16          0           8
total       24      0.01       0.01          0         96         32           8
OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS
total      319      0.22       0.50        104       8151        204        1578
OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS
total      699      0.80       1.22         61      71691        104         332
$
Followup:
the totals are not extremely useful in diagnosing a hard parse issue.
Rating: 5
tkprof output November 20, 2003
Reviewer: A reader
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        4      0.01       0.00          0          0          0           0
Execute  19012      3.10       3.56          0          0          0           0
Fetch    19012   1056.38    1517.15          0   43282981      76048       19012
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    38028   1059.49    1520.71          0   43282981      76048       19012

My understanding was that the above times are in
centi seconds for 8i and microseconds in 9i - is that correct?

thanx!!!
Followup:
they are SECONDS in all versions.

the raw timing data is in centi/ micro for 8i/ 9i  the tkprof is always seconds.
Rating: 5
trace November 24, 2003
Reviewer: Yogeeraj from Mauritius
You are right!

the trace was initiated using:
exec sys.dbms_system.set_sql_trace_in_session(623,4339,TRUE);

best regards
Yogeeraj
Rating: 3
No of rows processed for an Insert December 08, 2003
Reviewer: Abu Ahmadh from Singapore
Hi Tom,

TKPROF output shows:

INSERT INTO CDRCS_EO (col1, col2, col3, col4, col5)
VALUES
(:v1, :v2, :v3, :v4, :v5)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse      291      1.25      14.77          0          0          0           0
Execute    291     15.40    1317.90      10011       1047     317037      105147
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      582     16.65    1332.67      10011       1047     317037      105147

Misses in library cache during parse: 0
Optimizer goal: RULE

Question:  The statement was executed 291 times. That means, there should be a
maximum of 291 rows (insert) processed.  Why did it process more number of rows
than number of times it is executed?

Secondly, what does it (Insert) have to do with disk?

Please explain.

Thanks!
Followup:
the developer did a nice array insert.  about 370 rows per insert (too bad they
didn't keep that cursor open!! should be 1 parse, 291 executes.  then it would
have been perfection)

as for "what does it have to do with disk" -- you need to insert the data into
"something".  When you insert, we go the free list, free list says "block 20 in
file 5 has space".  if block 20 isn't there -- we have to get it.  also, when
you insert into the table and the table has indexes, the data has to go into
very very specific blocks.  "name=frank" -- that goes on block 200 in file 20
(discovered that after reading index root and branch blocks).  block 200 from
file 20 is on disk -- read it so we can modify it.
Rating: 5
batch job elapsed time December 19, 2003
Reviewer: PINGU
Hi

We have a batch job which takes around 5 hours, however we trace the job and
tkprof the trace file and got this

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse   595504     65.83      81.66          0        531          0           0
Execute 595508    346.99     452.31          5         25         21           8
Fetch   891520   1379.24   11759.50    1069624    6984725          3      605893
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   2082532   1792.06   12293.48    1069629    6985281         24
605901

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse      525      0.10       0.13          0          0          0           0
Execute    947      0.36       0.41          0          0          0           1
Fetch     1819      0.16       1.37        123       3457          0        1345
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     3291      0.62       1.91        123       3457          0        1346

from TKPROF the process takes around 3.9 hours.... Is this the total elapsed
time inside the database? Or it has nothing to do with database batch process
elapsed time?

The 1.1 hour what can that be?

Cheers
Followup:
that 1.1 hours is network + client processing time.  probably mostly in the
client  given the modest number of rows returned (unless you have a really slow
network)

Your batch process should be parsing once and executing many times -- that the
parse is almost equal to execute is a bad thing (developers need to correct
that)
Rating: 5
May be SOFT parse December 19, 2003
Reviewer: A reader
<tom_answer>
Your batch process should be parsing once and executing many times -- that the
parse is almost equal to execute is a bad thing (developers need to correct
that)
</tom_answer>

In the above TKPROF output most of them may be SOFT PARSE. right? How the
developers will fix that?
Followup:
the developers will:

subroutine X
   static variable STMT

   if ( stmt is not prepared in this subroutine )
   then
      prepare the statement
   end if;

   bind statement
   execute statement

   /* never close it, keep it open */
end subroutine

a parse is a parse is a parse.   the fastest "parse" is NO PARSE

Ask the coders "would you COMPILE subroutine before each execution -- run it --
and then throw it away".  of course they would not.  so, why do they do that to
SQL?
Rating: 4
but in web based application December 20, 2003
Reviewer: A reader
<asktom_response>
/* never close it, keep it open */
</asktom_response>

But in web based application it is very dangerous NOT TO close the statement. It
is all stateless and short transactions. We don't know what is the next request
is going to be.
Followup:
you can keep them open for as long as possible.

to do anything else is to impact in a massively negative fashion your
performance.

No reason why -- if you parse and execute a statement 500 times in a single web
page you would NOT parse once and execute it 500 times.

No reason (other then "i'm lazy").

One approach is to build a hash table associated with your connection -- every
prepared/callable statement you open goes into this hash table.  Before you
prepare any statement -- you ask the hash table for the handle -- if you get it,
reuse it.  If not, prepare it and add it to the table.  Then, right before you
give the connection BACK to the pool (i'm assuming you are assuming a connection
pool which is not a valid 100% assumption but it must be what you mean), you
iterate over the hash table entries closing all statements.

You see, I use mod_plsql which nicely "resets the session state" at the end of
each call.  No cursor leakage from page to page.  If you use a more complex
language like Java - you'll have to manage it yourself but the price of NOT
managing it is bad performance and limited scalability.  So, in the end, it is
up to you.
Rating: 4
could you help me about this TLPROF result December 23, 2003
Reviewer: Suhail from NY
TOm , here is my TKPROF result. We are integrating a 3rd party tool and its
generating folloing TKPROF. It uses insert into global temp table. Why its
taking that mush of time.

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       63    500.00     560.81          0          0          0           0
Execute     63   4100.00    4163.51          0          0          0           0
Fetch      2601240500.00 1255899.00       8289    1920975          0       19852
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      3861245100.00 1260623.32       8289    1920975          0       19852

Misses in library cache during parse: 23

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse      289   5700.00    5181.08          0          0          0           0
Execute    344  25800.00   30757.39         61       7615       3066        2773
Fetch      272   1100.00     799.90          1        625          0         107
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      905  32600.00   36738.37         62       8240       3066        2880

Misses in library cache during parse: 39

  129  user  SQL statements in session.
  226  internal SQL statements in session.
  355  SQL statements in session.
********************************************************************************
Trace file: dref_ora_21090.trc
Trace file compatibility: 8.00.04
Sort options: default

       1  session in tracefile.
     129  user  SQL statements in trace file.
     226  internal SQL statements in trace file.
     355  SQL statements in trace file.
      43  unique SQL statements in trace file.
    3755  lines in trace file.

Thanks

Suhail
Followup:
gee, i don't know.

my car won't start this morning.  any ideas?

(with those big numbers, I would suspect that you used the 8.x TKPROF against a
9i trace file tho.  we went from centi to milli seconds.  bet if you divided by
1,000 the numbers are "more meaningful")
Rating: 4
elapsed time is less than CPU time?!? December 28, 2003
Reviewer: Sami from NJ,USA
Dear Tom,

How come elapsed   time is less than CPU time?!?
Is it possible?

BEGIN
         GENERATE_PRODUCT_KEYS (:1,:2,:3,:4)  ;
 END;

call     count       cpu    elapsed       disk      query    current
rows
------- ------  -------- ---------- ---------- ---------- ----------
----------
Parse     2737   1213.00    1184.60          0          0          0
0
Execute   2737     28.57      28.08          0          0          0
2737
Fetch        0      0.00       0.00          0          0          0
0
------- ------  -------- ---------- ---------- ---------- ----------
----------
total     5474   1241.57    1212.68          0          0          0
2737
Followup:
because things are measured using "ticks"

when you have lots of very short operations -- things that take a fraction of a
tick, you get measurement errors.

each of your parses took about .44318597 cpu seconds (funny -- you should be
more worried about "why the heck did I parse this silly statment 2,737 times -
why not just ONCE").

Now, say you are on 8i, where we measure things at a the centisecond level.
that means we can accurately measure .44 and .45 but not .44318597.  Hence, all
of the parses might appear to contribute + or - their time (they might over
contribute, they might undercontribute).

Same with elapsed -- things are happening so often that we over/under count.

The more we count, the more the "error" creeps in.  So, these numbers are close,
but not 100% accurate.  An unavoidable issue when measuring LOTS of really small
things.

But, the fix here is easy -- PARSE ONCE -- aren't you upset that your parse time
(time not spent doing anything useful for you or your business) is 43 TIMES that
spent doing useful work???
Rating: 5
Thanks Tom,Understood. December 29, 2003
Reviewer: Sami
Already fixed PARSE MANY issue. Just I was curious to know about "elapsed time
is less than CPU time".
December 29, 2003 Abhijit -- Thanks for the question regarding "Locking Mechanism in Oracle", version 8.1.6
You Asked
Hi Tom!

I have been following your site and its really invaluable to say how much your
solutions to various problems helped me. Thank U Tom for all U do. Pl. Keep up
the excellent work.

Now to start with, I am facing a problem releated to Locking in Oracle. My
primary aim is to lock a record by one person so that another person should not
be able to read the records even.

I will describe the problem a little more below:

My scenario is:

Say two persons in a Client-Server System(say, Credit Card Transaction
Operation)are simultaneously using the same application.

Both the persons are calling the same procedure at the same time. The procedure
looks like this.

1. Query a member data about the credit limit.
2. If the Transaction value < Credit limit fetched
2a. Make a transaction which takes sometime to complete, and
2b. Update the credit limit.
3. Exit the program

When the first person queries the table, say he retirves the credit limit value
as 20,000. He then proceeds for a transaction of 15,000, which is valid in this
case, and the system should allow. As the first person was doing the
transaction, second person queries the table data and gets the same credit limit
of 20,000 without realising that the first persons’ transaction is not over and
he is yet to update the record. The second person proceeds with his transaction
of an amount of 10,000. This transaction should not be allowd and the current
valid credit limit after first person's transaction stands at 5,000.

What are the ways to overcome this anomaly? What locking mechanism can be used
to overcome this problem?

To Note further:

As far as I know, None of the oracle Locking Mechanisms restricts the table or
rows from querying the data when locked. If we get a lock which restricts
another user to query the data, it solves the purpose.

Thanks again,
Abhijit
and we said...
Your transaction would look like this simply:

   select * into l_rec from table where member_id = XXXX for update;

   do your transaction processing here

   commit;

Only one person at a time will be able to do that select for update.  All other
users will still "read" the data -- but that is OK (desirable even!!!).

You use "for update" to prevent LOST UPDATES.

If you have my book "Expert one on one Oracle" -- i cover topics like this in
detail.
Review & Followup

Rating: 4
Thank U November 27, 2002
Reviewer: Abhijit Deb Roy from Mysore, India
Thanks a lot.....

Ur answer is really very helpful...

NOTE:
By the way, Can I get a soft copy of your book anywhere... (I don't know where
to get your book that U mentioned....)
Followup:
(sure, I'll just put it up on a website so everyone could download it for free
-- hmmm, think about it...)

Any bookstore would have it or be able to order it.  A link to it (for the ISBN
-- the code the bookstore would want) is on my home page.

There is an indian reprint out there.
Rating: 4
That heavy thing.... November 27, 2002
Reviewer: Robert from PA
>>...a soft copy of your book anywhere
...been wishing for something like that too, boy would that be great for
searching & lookup stuff !
It's on my Christmas wish list so I can keep a copy at work.
Tom, you understand your book is like an encyclopedia to us developers, right ?
Followup:
and I didn't organize it alphabetically or anything ;)
Rating: 4
"select for update" not always enough to prevent "lost update" November 28, 2002
Reviewer: Freek D' Hooge from Belgium
Tom,

you say: "You use "for update" to prevent LOST UPDATES."
This is true for this application, because the check is after we have obtained a
lock on the record. But in case of an application where a user alters a value on
the screen, the user may not have seen the latest values for this record.

It was my understanding that when performing the "select for update", one would
have to give the old values of the fields as well (at least the fields one can
alter on the its screen)

Again, in this particular application, the "select for update" is enough to
prevent that a user works with an 'old' credit limit.

greetings

Freek D
Followup:
Yes, if you have my book "expert one on one Oracle" -- i explain this in great
detail.  in a client/server style app -- you would:

select the data out, display to user

as user goes to type over a field -- before they actually change it you:

   select * from T where <columns> = <values on screen> and primary key = value
    for update nowait

   if 0 rows then Error, already changed, you must requery before changing
   if ora-54 then Error, row locked by another session, you must wait
   if 1 row returned, then OK, you got it, update it as you want
Rating: 5
online version of your book November 28, 2002
Reviewer: Hrishy from PA
Hi Tom

what we meant by soft copy was like..can you ask wrox publishers to give a CD of
the book..as the book is quite heavy with 1000 pages..and by the way even if you
do manage to put up a online version even then there are ocasions when
everything on good old paper always feels better..

Probably in the next edition can we hope to see a CD along with the online
version of the book
Followup:
No CD, but they are planning on making it available online to people who bought
the book.

putting on CD, it would take what about 5 seconds for someone to mount that on a
webserver somewhere...
Rating: 5
Toms Book November 28, 2002
Reviewer: Andrew Gilfrin from Dover Kent UK
On the subject of Toms book I suggest anybody who hasnt got it buys it now. If
your not convinced you need it you must must must read the first chapter which
is avaliable from the link on this websites home page and then you will know why
you must have this book.

I have used it many times already to show people at my company why things they
have "known" for years are actually wrong. One of the great things Tom does both
in the book and on this site is give detailed, working and accurate evidence to
what he has said many other books just say "It works like this so you should use
it" but Tom gives you the evidence and information as to why you use it.
Rating: 5
5 minutes to mount the CD November 28, 2002
Reviewer: hrishy from PA
Hi Tom

well i agree somebody might place the entire contents on a Cd on a webserver...i
agree but what about asktom.oracle.com which is

a)much wider knowledge base then expert one to one..Tom you gotta understand one
thing here...unknowingly you have become the "LINUS TROVALDS" of oracle.
Distrubuting and diseeminating oracle knowledge...you gotta convince your
publishers that by putting your book free /or sharing it online would only
increase its sales not decrease it .It would serve as advertisng campaign and
morever many of us would like not to let go of crispy old paper just because the
web has made heavy in roads into our daily lives...

think about it :-D..well am i logical or i have gone a bit far ;-D
Rating: 4
Nah, it wouldn't. November 28, 2002
Reviewer: Jer Smith from Salt Lake City Utah
We'd like to believe that putting things up for free online would increase sales
of a book, but it doesn't.  Ask the folks at Coriolis (where a lot of the books
were available for free) or ask O'Reilly about their books that are also
available for free (Linux Networking, Samba).

As a fact, though, it's pretty hard to put a book online (even for members-only)
that can't be sucked down with wget, or barring that, Perl's LWP package.  Not
to say that it isn't possible, but it usually involves making it really annoying
to read.

There are only about seven Oracle books outside the docs that are really worth
buying, so my advice is: cough up the money and purchase them.
Rating: 5
select for update , does the web change everything ? January 06, 2003
Reviewer: Hrishy from PA
Hi Tom

The above solution of using select for update works well in a client server
environment.But when we move to the web we are sometimes faced with this
peculiar problem.

Application level users connect to the database as user JDBC there are around 50
application users .when two users want to update the same row in the table i
suggested that we use select for update.so the other user will get a message
saying that this row is being cuurently edited by other user would you like to
open in read only mode.(this message would be displayed in the other users
browser after trapping the appropriate oracle error).The problem here stems from
the fact that this is web based environment..and the users who has locked the
rows might actually (do somethin funny) kill the browser and go home and since
the session time out value is set to 8hrs..the select for update would lock up
the rows for 8hrs. and other users who want to work may never get a chance to
update the work.So is there a workaround for this problem..I am not quite yet
familiar with the web technology..so i hope my question made sense.I did not
want to waste another thread on this question since it is related to the
question posted above only the context is web instead of client server.
Followup:
so, set the session timeout to something slightly more reasonable like
10minutes?

search this site for

optimistic pessimistic locking

for other techniques.
Rating: 5
Apps does it do that automatically January 07, 2003
Reviewer: Hrishy from PA
Hi Tom

I was talkin to a fellow DBA and he mentioned that our developers do not have to
think about pessimistic locking and optimistic locking if we use an App server
like Jrun,Tom Cat or Oracle9ias..is it true..coz..i am surprised if it is
so..then how come in one of the earlier post you suggested to use the following
package owa_opt_lock when it is already built into the app server..or am i
missing something.

And also in the same post you mention..that optimistic locking in stateless
environment is achived through date and sessionid.My doubt is why sessionid and
not just date ?

well both these threads are great source of knowledge..i am also eagerly lookin
forward to your book..I dont mind paying a few dollars for that book.and wish
you include a topic like this.."web changes everything"..coz i find this select
for update an interesting concept to look to in stateful and stateless
environments"
Followup:
this is why DBA's are responsible for backing up databases, not developing
applications against them.

app servers run code.

app servers run good, well designed code.
app servers run garbage code.

it is up to the developers to write good, well designed code.

there may be some facility builtin a TOOL they use to generate code that runs in
these app servers -- like when you use forms, forms instantly applies
pessimistic locking concurrency control automagicially.  But, if you don't use
forms -- just write code, you are on your own.

Tom Cat for example is a servlet engine.  It runs java servlets.  these servlets
just contain java code you write.  you can write code that destroys the
integrity the database, you can write code that works good.  It is up to you and
the app server does nothing to make you go one way or the other.

as for date+sessionid, i believe we were talking about timing out the locks
"held" by a given session after some reasonable time -- say 10 minutes.  If we
just used sessionid, we cannot time it out.  If we just use date, we cannot hold
it for a session. hence sessionid and date (but I'm guessing, lots of text on
this here page)
Rating: 5
why DBA's are responsible for backing up databases January 07, 2003
Reviewer: hrishy from PA
Hi Tom

really DBA's are responsible for backing up databases..LOL..
hope Oracle University people are listening..so ocp would have only one exam to
go..backup n recovery hehe..just kidding..

Tom as usual that was a cool response..from your side..thanks for the
explanation..well you are right would love to bother you for a while on this
thread..for now we are going ahead with your idea to the development team..

well by the way..Hrishy wunders even though he himself is a DBA..why he always
has a browser open at asktom ;-D.
Rating: 4
Does locking table in exclusive mode "prevent" row locking? October 14, 2003
Reviewer: Michel SALAIS from France
Thanks a lot for your wonderful site

Here is my questions concerning locking mecanisme :

1)When a transaction locks a table in exclusive mode and then it tries to modify
rows from that same table, is it necessary to acquire locks on these rows and
what does Oracle?

2)Is there a mean to test this?
Followup:
1) row locks are attributes of data -- we do not have a big list of row locks
anywhere, unlike most every other database.  So, as you visit the row, we leave
our fingerprint behind.  So yes, the rows are "locked" but there is no overhead
- no more then anything else.  it is just "natural" for us.

2) sure, you could dump blocks, but -- it really isn't worth your time.  row
locks are NOT an expensive item to be conserved in Oracle -- we are not db2 or
sqlserver.
Rating: 5
what humor, having great time with this thread, thanx October 14, 2003
Reviewer: Kevin Meade from USA
Hi Tom, me and Trevor are sitting here watching long jobs run slow and are
rolling over reading this thread.  Great job.  Keep it coming.

Kevin and Trevor.
Rating: 4
Locking a Record October 15, 2003
Reviewer: Arvind from India
It's Ok that Oracle allows to read the record by another user. But Can I check
before a user selects the record for Rad or update whether that record is in use
by another or not so that I can grant the rights to the user for the same. And
at the same time I can display a message to the User that the Particular is
record is being used by X user.
Followup:
you can use select for update NOWAIT to see if someone else has the record
locked, yes.
Rating: 4
Locking a Record October 15, 2003
Reviewer: Arvind from India
It's Ok that Oracle allows to read the record by another user. But Can I check
before a user selects the record for Read or update whether that record is in
use by another or not so that I can grant the rights to the user for the same.
And at the same time I can display a message to the User that the Particular
record is being used by X user.
Rating: 4
Remove Session locks October 15, 2003
Reviewer: Prince from UAE
Hi Tom,

  Very interesting to read your discussion on locking. I have used for update
clause in my program during selection of a record (for update of <column name>).

  Then i will update the table to increase the serial number by 1. After this i
give a commit.

   Now,in the same session, i need to add another entry, if i try to select the
table again, it goes to <no_data_found> exception part.

   I am able to do the same in a different session. Can i give nowait clause too
like,

  for update of <column nam> nowait;

  Please bring your online version of book early, since it is very bulky to
carry places?

Thanks

Prince
Followup:
You use for update NOWAIT in a select, but not in an update.

I don't understand the "no data found" comment -- one would need a full example
to comment.

My new book is "less bulky".   There will not be an online version of the book
-- slight issue with "if I can download it, why pay for it".
Rating: 3
Record Locking October 17, 2003
Reviewer: Arvind from India
With refence to the below question and your answer

Earlier Query
It's Ok that Oracle allows to read the record by another user. But Can I check
before a user selects the record for Read or update whether that record is in
use by another or not so that I can grant the rights to the user for the same.
And at the same time I can display a message to the User that the Particular is
record is being used by X user.

Today's Query
But I am selecting a record from LOV. My requirement is that as soon as I select
a record from LOV the system should check that whether some other user is using
that record or not. If using then system should propmt for the message that
record is already in use.

Thanks
Followup:
select for update NOWAIT that row.

if that returns the row, you got it, it is yours.

if that returns an error, you don't got it, it is someone elses.

if you say "but I don't want to lock it, just see if it is", I will respond
"that would be less then useful -- it would be misleading to say the least"
Rating: 4
Locking Record October 21, 2003
Reviewer: Arvind from India
Thanks Tom. It has solved my Problem.
Rating: 5
behaviour is different December 23, 2003
Reviewer: A reader
Hi Tom

  The scenario which i m facing is very similar to the one mentioned in the
above question.

We have a jobdetails tables (not for dbms_jobs) something like this

we have a job_details table with the following columns

job_id            status
======            ======
1            started
2            started
3            not started
4            not started
5            not started

our business rule is not to have more than 2 jobs at the same time

-- session1
select job_id from job_details where status='started' for update;
if we have 2 started  we dont do anything
where as if it is less than 2 we go ahead with the update & commit (i.e update
status for the next job to 'started')

now suppose another session has started at teh same time as session1 . session1
has locked the data but session2 is blocked by session1.

now session1 updates & commits. (lock released)

immediately session2 lock gets released but it sees only the same data which was
visible to session1
it cannot see the data updated & commited by session1

Since we have a limitation of no.of jobs to be started at the same time to only
2.
the above condition does not work as the data updated by session1 is not visible
to session 2 even with a commit.

is it because the select is pre-ordained

How i can get around with this

Sorry for asking the question through the review but it was a bit too urgent
Followup:
serialize at a higher level.

  get lock
  check records
  decide to proceed, or not

eg:

   select null from dual for update;  -- serialize HERE
   select * from t where status = 'started';  -- not HERE

you could use dbms_lock instead of dual if you like (better to use dbms_lock or
your own private table instead of dual)
Rating: 4
Optimistic lock better for high load December 23, 2003
Reviewer: Alex V from Montreal
For medium-high load applications
(let say 10-100 TPS against iinternet catalog)
optimistic lock is MUCH better than pessimistic.

According to all study, 50-85% transactions are
left unfinished in internet, so even 10 min timeout
gives tons of abandoned locks.

Moreover, if FOR UPDATE is used , all client must
use the same way, so go and check EVERY select around!

Better alternitives are:
--SERIALIZABLE isolation level (but more work for database)
--timestamp/sessionid/version (more work for application, triggers can help a
lot here)
Complete integrity is inforced from within DB!

So main idea: validate transaction in the end,
not in the beginning.

(I am sure Tom has this in his book)
Followup:
in a stateless, 3 tier web environment -- i totally agree.

in a heads down, data entry, client server environment -- i heartily disagree.

and it has nothing to do with TPS really.  It has everything to do with the
environment. (our highest load TPS's - the TPC-C's -- all pessimistic locking.
it would be slower the other way around.  it is driven by the duration of the
transaction more so then the volume)

10g -- you get an SCN with each row :)  no need for timestamp/etc in the near
future.
Rating: 3
Resource usage by locks December 28, 2003
Reviewer: A reader
You said

--x--
1) row locks are attributes of data -- we do not have a big list of row locks
anywhere, unlike most every other database.  So, as you visit the row, we leave
our fingerprint behind.  So yes, the rows are "locked" but there is no overhead
- no more then anything else.  it is just "natural" for us.

2) sure, you could dump blocks, but -- it really isn't worth your time.  row
locks are NOT an expensive item to be conserved in Oracle -- we are not db2 or
sqlserver.
--x--

This reminded me of a marketing "claim" made by a SQL Server/MS person recently.
He said, and I quote loosely.

<quote>
If you want to delete all rows from a million-row table and do 'delete from
table;', Oracle will grab a million row-level locks, one for each row. SQL
Server is "smart" and realizes that you are in fact touching the full table so
it just places a exclusive lock on the whole table.

Assuming Oracle row-level locks use 1 byte per lock, thats a million bytes of
memory/disk (some resource usage) that is just unnecessary.

</quote>

I couldnt refute it but it didnt quite seem right to me. Got me thinking...what,
if any, resources does a row-level lock, or any lock for that matter use in
Oracle? If the answer is NONE, how are they managed?
Followup:
well, the first rejoinder back to the MS guy should be "well, if I were going to
delete ALL of the rows -- I would certainly use truncate.  and if I were not
going to delete all of the rows -- I would certainly NOT want you to lock the
table, I'd want the other rows to be there for others to see.  And -- don't you
find it quite a pain that when you do the delete in MS sqlserver -- basically no
one can query that table -- the system sort of grinds to a screeching halt......
Oh, and if I'm deleting 1,000,000 rows out of 2,000,000 -- since you guys keep
locks in RAM, and there isn't just a table lock -- what then, aren't you using
gobs of memory, real memory (expensive)?"

but anyway.

1,000,000 bytes of disk costs what?  2, maybe 3 cents?  but are we being penny
wise, pound foolish as the old saying goes? we are talking about space
significantly smaller then that copy protection scheme called "the registry" (oh
wait, thats a feature, not a copy protection scheme isn't it....)

And that aside:
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96524/c03block.htm#2595
shows the oracle block structure, the row directory (which is allocated
regardless -- they'll have an equivalent thing) is what we use.  it is there
regardless.

we would be using the same amount of on disk storage with or without our locking
scheme really.  The benefits of our scheme include

o unlimited numbers of row locks
o ability to provide non-blocking reads - reads do not block writes, writes do
not block reads
o pretty darn good scalability
December 29, 2003 Abdal-Rahman -- Thanks for the question regarding "Comparison between Oracle and Others", version 8/8i
You Asked
Hi Tom,

   My question is something different. As an Oracle expert I always asked to
provide strong reasons why to use Oracle Database  and not to use for example
SQL Server.

   Please, can you tell where to go in the Internet to search about any site
provide details and technical differences and also the capabilities of Oracle
database over the other famous databases like SQL Server, Informix, Sybase, DB2
and Ingress?

Best regards,
   Abdal-Rahman Agha.
and we said...
Each database has literrally thousands of features and functions.  How they are
implemented and what features are there vary widely from vendor to vendor.
Having a simple little checklist is virtually useless unless you have an
excellent working knownledge of the "guts" of the database (something that can
only be gleaned by experience, actually using the products).

For example -- all of the above would get check marks for having "row level
locking".  Once you look at the implementations however, you'll find they are
way different.  In Oracle, the overhead of having 1 lock is the same as having
1billion locks (eg: none).  In the others there is a HUGE different between the
two as locks are a scare resource in those systems, they consume memory, take up
space, are stored in in-memory data structures.  Our implementation of the
feature is so different as to not be comparable.

For exmaple -- all of the above have the ability to return consistent (correct)
results.  Only one of the above can do so in a mixed workload environment (mixed
updates and reads) in a highly concurrent fashion.  In Oracle we employ
multi-versioning and read consistency.  The others use shared read locks (which
will lead to deadlocks and blocking).  We never block on a read -- they
frequently block on a read.  So, another feature with a check mark that is
virtually meaningless.

And the list goes on and on.

Here is my very short list of why Oracle.

1) informix and ingress -- they do not exist as entities any longer.  They have
been bought by companies that already have database products of their own.  I'm
not sure if you will recognize informix 3 years from now or maybe you will (as
it will be exactly the same as it is now).  I would tend to avoid these products
just for that reason.

2) sybase -- well, I spend a large amount of my time helping people convert from
Sybase to anything else.  Since they sold themselves to MS a couple of years
back and MS built a better sqlserver then sqlserver, they sort of put themselves
out of the database business.  I do not know personally of anyone doing new
development with this product.  Ask yourself, will this be here in 3-5 years.

3) DB2 -- they have taken the "we'll build a database for every problem"
approach.  Ask them how many database products they actually have (and be
sitting down when you do this as it will take a while to go through the list).
Even DB2 doesn't mean "DB2" -- the different flavors of DB2 are incompatible
with eachother (different features and functions).

4) MS -- this is easy.  Oracle is available on every platform of
any worth.  It always has been, always will be.  5 years ago, NT stood for NOT
THERE.  Unix was king.  Go back 5 more years.  Unix was just becoming cool, VAX
VMS ruled, mainframes were starting to be considered that old stodgy technology
(remember, PCs were still just starting to appear everywhere then as well).  5
Years before that -- mainframes rules, mini computer systems just starting out.

Now, fast forward 5 years from now.  To paraphrase and twist a famous comercial
ask yourself "where do you want to be".  In 5 years, I'll betch windows is not
the place - it'll be replaced by some new hardward/software combo and then where
are you?  you are with a database that limits you to exactly one OS -- their OS.

That is the major, #1 difference between us and them.  If your customer feels
windows is the flatform of the 21st century..... and always will be, I guess its

a safe choice.  If they look at history, really look at it, they might begin to
feel a little different.
Review & Followup

Rating: 5
Very detailed answer November 04, 2001
Reviewer: Gonela from Chicago,USA
Very clear answer. Thanks Tom for your detailed reasoning.
Rating: 4
Something which I allways was looking for November 04, 2001
Reviewer: Subhrajyoti Paul from India
I have worked only in Oracle, but I read throght Toms book and came to know how
it differs from(i wud rather say how it is better from) other databases. But
that was just me. My client wud not understand how oracle differs in the
implementation of locking and other stuff. But now I have exactly the words to
convince my client to go for Oracle...
Thanks a lot
Rating: 3
Has DB2 caught up with Oracle in terms of advanced features? November 04, 2001
Reviewer: Eric Ma from Berkeley Heights, NJ USA
Tom, what is your opinion on the following white paper about Oracle vs. DB2?
http://www-4.ibm.com/software/data/pubs/papers/orac91vsdb272/orac91vsdb272.pdf

Followup:
sorry the link did not show up (gotta bug to fix ;)  The link eric put in was
http://www-4.ibm.com/software/data/pubs/
I don't see any white paper there however are you referring to "a technical
comparision by pass consulting group"?  The group who refers to fine grained
access control as a new 9i feature (woops, only missed that by 3 major
releases)?

Or that makes biased statements like "Although Oracle supports the latest SQL
standards, preference is set on Oracle proprietary implementations." (what the
heck does that mean anyway?  does it mean we actually add functionality to the
database that others don't have?)

Or that says
<quote>
Indexes are extremely crucial for performance. Oracle has a number of index
types ? the index choice is based on the type of expected processing, if the
pattern changes, then an index could become a drag on performance instead. DB2
only has one index type ? but is able to create bitmap and hash-join indexes
dynamically during query execution when needed.
</quote>

How they can turn the fact that we have tons of index types into a "bad thing"
and praise DB2 for having a SINGLE index type and then having to actually INDEX
THE DATA EACH AND EVERY time you query it is totally beyond me!

And then they say "oh yes, Oracle has this nice materialized view that can
really make your queries fly, but they take some time to refresh so they really
aren't that good" -- give me a break.

Or that praises the heck out of DB2's static sql implementation which is totally
a kludge compared to shared sql and has no advantages over shared sql (in fact
has obvious downsides)

Is that the paper??? (i stopped reading it after a short bit, its fairly obvious
who paid for the report.  It is also obvious that don't know much about Oracle)

I personally say forget the advanced for a moment, I say they have a way to go
to catch up on the *basic* features -- quick q.  How many rows/sec can DB2 UDB
return if there is a locked row somewhere?  quick a. zero.

What do you have to do to get a correct answer in DB2?  Have your queries
deadlock with your updates.

What DB2 do you use on all platforms?  Answer -- none, they have different DB2's
for different architectures. (that report often seems to confuse things in
DB2/OS390 with DB2/UDB -- "features they've had for years", that OS390 -- UDB,
totally different code base, totally different architecture.

Actually -- I'm going to stop.  I've never been a fan of feature for feature,
tit for tat.  I like things simple, i like things concise.  I find one database,
any platform easier (far easier) then "what platform -- well, here is the flavor
of product we have there" or "what problem do you have - well, here is the
flavor of database you need for that, what -- your on that platform -- maybe
your should change because we don't have that flavor on that platform"....
Rating: 3
Has DB2 caught up with Oracle in terms of advanced features? November 04, 2001
Reviewer: Eric Ma from Berkeley Heights, NJ USA
Tom, what is your opinion on the following (
http://www-4.ibm.com/software/data/pubs/papers/orac91vsdb272/orac91vsdb272.pdf
 white paper about Oracle vs.
DB2?
Followup:
Yes, so it was that silly paper.

I urge all of you to read it if you want.  If you know Oracle, you know
somethings up.

Look at manageability, they love DB2 for:

"Other DB2 strengths are index management (automatic index balancing), user
management (os-level user authorization) and fine grained event handling."

Hmm, find anything in there Oracle doesn't do?

Or in interoperability:

"This area is a clear strength of DB2 because of its high level of integration
of heterogeneous sources. The DB2 optimizer can even optimize queries for
federated data sources ? the expectation is better performance, even and
especially when DB2 is the entry-point to non-DB2 DBMS. The adequate handling of
data and document-oriented XML data sources with the opportunity to store XML
documents as a column with a special data type or decomposed in relational
tables brings DB2 another advantage."

Umm, yes, we can do that as well -- and even the XML stuff (maybe they missed
that)

and it just goes on.
Rating: 4
Another two questions November 04, 2001
Reviewer: Eric Ma from Berkeley Heights, NJ USA
Tom, thanks very much for the quick response.  I agree the PASS paper is
somewhat biased towards DB2 - out of the 6 authors, only one or two claim to
have extensive knowledge of Oracle while all seemed to have worked exclusively
with DB2.  Now let me ask two more questions:

1. A lot has been written lately about how Oracle is losing ground to DB2 and MS
SQL Server in the RDBMS space, primarily because of the highest prices Oracle
charges for its database software.  A recent articel can be found here (
http://news.cnet.com/news/0-1003-200-7531191.html
.
Do you agree with the accessment?  I know we are technologists but unfortunately
many times marketing related issues creep into the decision making process.

2. Why do you think Sybase SQL Server is still hanging around, especially among
the largest financial firms on Wall Street?  I know one reason is the high cost
associated with switching to Oracle.  But do you think some of the
highly-publicized features (such as row-level locking and multi-version read
consistency) Oracle has over SQL Server are not so significant to certain users?
Followup:
1) Well, there are many ways to looks at the numbers here.  IBM is buying
marketshare (they bought informix, now when they report database marketshare
they'll lump informix -- all of the flavors, redbrick, cloudscape, -- the 5
databases they bought from them with the 4 different DB2's and IDMS and so on).
Also, the others are losing marketshare --  Sybase, Ingress, et. al.  Remember
also, when you are small, it is easier to grow.  I'm not a marketeer per say, I
don't follow this stuff real close, but our "shrinkage" qtr to qtr was due more
to less .com's then anything real shrinkage.  Time will tell I suppose.
Analysts (such as those that write the reports) aren't happy unless there is
controversy.

I also sort of smile when I read something like:

<quote>
Over the summer, however, Abebooks decided not to upgrade because it couldn't
afford Oracle's enterprise edition. The company's programmers wrote their own
software to sit on top of the Oracle database to replicate it. Besides, Snider
said, the work to migrate the company from Oracle to IBM would have taken weeks,
or possibly months, of work.

Oracle would have charged $500,000--five times more than IBM. But IBM also would
have charged extra for consulting services, Snider said. If it were just the
database, he said, "it'd be a no-brainer."
</quote>

I ask you to seriously consider what cost to the company having a bunch of
programmers sit around and write replication software in the year 2001 AND THEN
maintain it for the next umpteen years will be??  I'll lay odds that its a tad
more than that  (think about what a team of 5 -- do to development, testing,
document, and manage would cost for 1 year, you are already well over 500k but
and you haven't even begun to maintain it but oh well).

2) I see people moving from from Sybase personally (lots).  I'm not naming names
but I've worked on more then a couple of accounts in New York City.  I don't
know what you mean by the high cost of moving to Oracle so much -- it costs to
move from Sybase to anything else, it is so radically different from the other
databases out there.

Once people grasp the power of multi-versioning and how much easier it is to
program -- it makes a big difference.  The problem is, people become legendary
in Sybase groups for coming up with unique techniques to solve issues like this
-- having that inside knowledge of how to set up the perfect clustered index,
using timestamps, never lock data -- do the concurrency control yourself, all of
these neat "solutions" and then they discover "hey, you don't need to do that,
why would I want to go over there where I mean nothing, where all of the
knowledge I've gained over the last 10 years is useless".  Talking to those
guys, thats hard.  Its like trying to sell the first C compiler with an IDE to a
bunch of assembler programmers who love their punch cards.  A bit of resistance.
Rating: 5
toooo good November 05, 2001
Reviewer: Nishant Manchanda from Delhi,India
As an Oracle Profession I always wanted to answer people why Oracle and nothing
else.I have got my answer.
Rating: 3
Good up to "IDE" November 05, 2001
Reviewer: Harrison from Haymarket "Detroit without the glitter", VA USA
Everything was going pretty well (religous wars are more fun when they are about
databases), until I got to "Its like trying to sell the first C compiler with an
IDE..." Geesh.  Unix is the IDE.  Staying out late with DOS is going to lead to
no good end.  I'm just hoping that you meant to say "Visual Basic with an IDE".
That would be good. I haven't figured out how you could play with an IDE and
learn what strtok() does. It's like finding out that Mickey Mantle smokes
cigarettes. I hope no kids see it. (Readers wanting to do the right thing should
stay away from IDE's and read Tom's chapter on n-Tier Authentication, which was
clearly written prior to his becoming involved with IDE, and shows he was a good
C progammer before he went astray).
Followup:
I don't use any IDE's personally ;)  I don't use debuggers either.  However....
Many people do -- hence the analogy.

My favorite UI is a command line.
Rating: 5
Excellent Review November 05, 2001
Reviewer: Srimal from Trenton, USA
Hello Mr Tom,

Thank you for a great Precis of Analysis of Oracle in comparision to other
databases.

Srimal
Rating: 4
I always wanted this information November 06, 2001
Reviewer: Sarada Priya from Kuala Lumpur, Malaysia
Thanks Tom, That was a great Article indeed!!!
Rating: 3
Database Comparison December 31, 2001
Reviewer: kwalther from San Diego
Oups!!! Left out Mysql and other open source databases that work well with
ecommerce. Leaving this out of the equation is like making a sandwich without
the bread. Ecommerce is the bread and butter of the Internet. It's a billion
dollar enterprise that's growing. Mysql is functional for ecommerce and web
platforms. It has feature limitations compared to Oracle, but the focus of it's
design is the speed of select and update statements. With the latest version of
PhpAdmin, it's even easier to use. Don't forget the open source community.
Rating: 3
Replication December 10, 2002
Reviewer: John Snider from Victoria, BC Canada
I sort of have to smile when I read how people think it will take five
programmers a year to build replication capabilities.  I'm the guy from abebooks
in the CNET article.  I built the n-way replication procedures in less than one
month using PL/SQL over three years ago and we spend maybe an hour a month
checking the process.  I had to build it because the native Oracle replication
was too slow.
Followup:
good for you -- maybe you want to come work for us.

did you have to build a generic solution?  nah, didn't think so.  you wrote a
specific implementation for a specific set of tables, under very specific
conditions.

I've been there, done that.  I know it can be done -- that is besides the point.
 I've seen people write it (and write it and write it).  they try to write  a
generic solution (been done).
Rating: 5
nice comparison February 24, 2003
Reviewer: rajesh from india
hi tom

what is your conecrn about mysql

it can connect also with forms 6i also.

are u not interested in open source community.

i think we can not avoid the strength of open source people considering the
strength of linux now

considering the requirements of small clients oracle 9i costs aprroximately
70000 indian rs for 5 usr licence.

in this part of the world price is also an important factor

exciting toms reply

regards

rajesh
Followup:
I don't know, what is my concern with mysql?  I didn't write anything about it
one way or the other here.

It was not part of the question...  I didn't ignore it, it was never part of the
QUESTION.

I run linux, so what?  I paid for the linux I'm running.  Is it free?  NOT A
CHANCE.  It cost money to put on my machines (i need a supported version,
someone to call and say "hey, fix this".  I don't rewrite OS stuff so I cannot
do it).  It cost money for me to run out and buy a pcmcia modem since the silly
winmodem didn't work.  It cost money for my time to do all of this.  People who
think "open source" = "free" are so sadly mistaken.  It all costs money, all of
it does.  I'll betcha it costs more for me to run linux right now then windoze
--I'm only successful at doing it cause I actually know unix pretty well.  Most
people, most end users, well, they would not be able to do it.  It is getting
there but -- I would bet that the cost of me owning linux is higher then the
cost of me owning windows.  I'm in a big old "do it yourself" position.

If you are running a single database for 5 people, well, not sure that is the
right model.  We run single databases for thousands of people.  We use economies
of scale here.  We would not run Oracle and put 5 users on it.  We use a hosted
model for small user communities like that.  They pay a fee -- we run their
stuff in a database with tons of other people.  One professional DBA team that
knows their stuff, dozens/hundreds of customers per instance.  Running a
database for every 5 people just doesn't make sense.

What I find is "penny wise, pound foolish".  Most small companies, upon losing
their data in a disaster -- fail.

Of every 100 businesses that suffer a data center disaster
  - more than 40 will never reopen
  - fewer then 10 are still about after two year.

they never fully recover from these things.  I'm not saying mysql will lose data
- just that the small 5 person scenario paints a picture of "no data mgmt, no
backup and recovery, no offsite backup & recovery, extremely limited ability to
recover from failure".  It is the mindset here, not the software that causes
this.  The "gotta be cheap" -- well, you get exactly what you pay for.

I've nothing against open source.  Hey, if you want to download free, build it,
deploy it to run your business -- more power to you.  when that disk fails and
you haven't really put the infrastructure in place to put it all back together
-- when you've shown you aren't serious about your data, well, I guess thats
when you start looking for a new job cause the one you were in probably is going
away.

In all parts of the world, price an important factor.  You certainly have no
monopoloy on it over there.
Rating: 5
Enjoyable and Interesting Thread February 24, 2003
Reviewer: Mike from St. Louis, MO
Rating: 5
February 25, 2003
Reviewer: Helena Marková from Bratislava, Slovakia
Rating: 5
About MySQL February 25, 2003
Reviewer: Terry from San Jose, CA USA
Comments regarding MySQL:  I have seen MySQL many, many times fail in service
and lose the data in a table and the whole database completely.  As a consultant
I am often called in to install Oracle after some 'Genius fresh out of College'
has implemented MySQL or MS Access because that's what they learned in school.
The real world is nothing like school and losing your data is absolutely
catastrophic.  Database life is not a "do over" like some kind of high school or
college assignment.  It often means "game over" for a business.  Almost nobody
gets the fact that without the data the database=ZERO so protecting the data and
maintaining it's integrity is paramount. Until recently MySQL did not even
support the concept of a proper transaction and MS Access still does not support
a proper transaction.  The point is that if you want to put some kind of "Bill
Gates Evangelism" or "Open Source Kool Aid Stand RA RA" or "Anti-Larry Rhetoric"
ahead of a few thousand bucks for Oracle then get your resume ready you are
going to need it.  I will also say in this lousy economy if you have a job keep
it because there are very few out there and I would not go risking my neck on
"Freeware Databases" and "Dinky MS solutions" that are simply an accident
waiting to happen. All too often I hear people (especially developers and their
managers) whining about the cost of Oracle and then totally ignore the fact that
their solution takes 10 man years of effort (often reaching into millions of
dollars) to deploy.  You need to get some real perspective on this, the cost of
Oracle is totally cheap compared to the alternatives which include all the other
RDBMS Solutions available on earth including "out of business" due to total
failure somewhere down the road.
Rating: 5
Comments Regarding DB2: February 25, 2003
Reviewer: Terry from San Jose, CA USA
In addition to the comments above regarding DB2 and Oracle functionality one
should also remember DB2 on OS 390 and DB2 on unix are completely different
products and when you factor in AIX (yuck!!) the gap just widens.  Basically,
DB2 on unix is about the equivalent of freeware, IBM bought Informix with the
idea of getting a database that actually works on windoze, unix etc.  In fact
two years ago IBM were giving away DB2 on unix in Silicon Valley trying to break
the Oracle stronghold but it didn't work people saw the product was weak and
lacked functionality and manageability period.  DB2 on the mainframe is not bad
but we aren't seeing too many people running over to the local computer store or
asking IBM for a 3090 ESA these days.  In a nutshell IBM are almost out of the
database business (especially on unix) and they are selling predominently into
traditional IBM shops and nowhere else.  They keep trying to sell the old idea
"you can't get fired for buying IBM" but you can for deploying a large scale DB2
on junky AIX.
Rating: 4
wow that's wonderful March 07, 2003
Reviewer: subbu from MN, USA
nice comparison. very useful..
today i was working on using heterogenous services to access mysql database and
needed more info. so gave a search in oracle's oracle ASKTom :-). nice
material.. good selling points...
i had a small doubt on the TCO of open source database like my sql. any idea??
Rating: 5
On Oracle only March 14, 2003
Reviewer: Yan Courtois from Canada
Tom, i admire you patience vs other database product....
i meen you have to be politicly correct..

in my opinion, you simply can't drive a real database warehouse without Oracle..
The only reason to me where you would have to choose another system, is the
price of an Oracle lincence... but with there new billing orientation, i meen
hey, you will gain from it at the end of the line. Just make your PM understand
that. And there is no people who are willing to work with a database product
other than Oracle.

I meen get realist, if your buisness is serious, 9i rock so much you just can't
get something else..
Rating: 5
What about MySQL ? March 28, 2003
Reviewer: David
Tom, someone sent me this link with this article in E-Week comparing the
performance of some RDBMS. Oracle was at the top, followed by MySQL.
http://www.eweek.com/article2/0,3959,293,00.asp
I question the conditions under which these tests were carried out.

But this surprized me:

"MySQL 4.0.1's new, extremely fast query cache is also quite notable, as no
other database we tested had this feature. If the text of an incoming query has
a byte-for-byte match with a cached query, MySQL can retrieve the results
directly from the cache without compiling the query, getting locks or doing
index accesses. This query caching will be effective only for tables with few
updates because any table updates that clear the cache to guarantee correct
results are always returned."

They said: "...as no other database we tested had this feature..."

Q1: Isn't that out shared pool, after all ?

Q2: Would you please comment on MySQL X Oracle ?
Followup:
In Oracle we call this a "materialized view" I would think.

It is not the shared pool, that is where we cache execution plans.

I never go tit for tat like that.

Like I said:

...
Each database has literally thousands of features and functions.  How they are
implemented and what features are there vary widely from vendor to vendor.
Having a simple little checklist is virtually useless unless you have an
excellent working knownledge of the "guts" of the database (something that can
only be gleaned by experience, actually using the products).
......
Rating: 4
Non Stop SQL June 18, 2003
Reviewer: Irfan from Kuwait
Hi Tom,
In Tandem, they use only Non Stop SQL. What is your opinion about Non Stop SQL
and how can it be compared with Oracle. If you could give us some links or
benchmarks?, But we will appreciate your own points.
Thanks a lot.
Rating: 5
Help needed ! August 22, 2003
Reviewer: Golan Rabud from VA
Tom, my company has decided to "downgrade" from Oracle to MySQL. They are
betting on an open-source solution based on Linux and MySQL, ruling out Oracle.

I am a long-term Oracle DBA and I am afraid of the consequences. Ther say they
have an impact analysis on that.

Truth is... the systems are not sooo critical.

Their main claim is that thay need to reduce TCO and Oracle is too expensive.

What do you have to say about it and where can I find good arguments against
that crazyness (as it seems to me)?

Thank you !
Followup:
my only argument will be

one day, you will lose your data.  is that OK.  if so, go for it.  you never
needed a database anyway.

it wasn't until I went to the UK a decade ago that I understood the saying:

"penny wise, pound foolish"

I was always thing "pound" like 16 ounces -- a weight.

then I got over there and found out a penny is a pence (1 cent)
a pound is like one dollar (100 pence, 100 pennies)

your company is saving pennies at the expense of hundreds of pennies.  I would
be concerned about that company frankly.

Open source does not, has not, will not mean "free"

Linux actually costs money, if you want support, stability, yadda yadda yadda.
you know, what you want from an OS vendor.

Same with the DB.

If a couple of thousand for software is deemed too much, i would be worried what
my salary at such a company might be deemed someday???
Rating: 5
Reader November 16, 2003
Reviewer: A reader
<quote>
"Other DB2 strengths are index management (automatic index balancing), user
management (os-level user authorization) and fine grained event handling."

Hmm, find anything in there Oracle doesn't do?
<quote>

In the following URL from "oramag" is a picture of unbalanced index
http://www.oracle.com/oramag/webcolumns/2001/index.html?auto_index.html
Does index really get unblanced at all
Followup:
that article is grieviously mistaken in many ways.

indexes will never get into the state depicted in that diagram. NEVER.  it is
just not possible.
Rating: 5
What about Teradata November 16, 2003
Reviewer: Phil
Hello Tom,

Thank you very much for this clear cut answers. If you can some give answers how
Oracle is better than TERADATA, I will really appreciate. We are presently using
using Oracle 8i(8.1.7) as our database database is growing very fast, one group
is asking for TERADATA database, as I have never used that database, I cannot
defend them. That group claims that maintenance will be less and its very, very
fast when you scan billion rows. I am looking for some literature for
comparison.
Followup:
well, why would you compare old technlogy to new?  9iR2 (lots of stuff above and
beyond 8iR3) has been out for a year and 10g is coming soon.

Scanning a billion rows is somewhat, ahh, limited by your IO system more then
anything else.

Our goal actually is to not have to scan a billion rows -- with things like
materialized views, query rewrite, bitmapped indexes, 3 types of partitioning,
the ability to use clusters (without having to statically partition data),
simultaneous INGEST (load) and DIGEST (query) -- try that in teradata.

that the largest databases in the world run on Oracle
http://biz.yahoo.com/prnews/031114/sff029_1.html
(don't let teradata fool you -- they'll say "teradata has larger" but they count

a) all disk on the computer, not just database disk
b) the sum of all databases a customer is using -- not individual databases

which tends to somewhat "skew" numbers)  is a consideration as well.

search for teradata on www.oracle.com if you like.  At the end of the day -- ask
them for hard, factual, scienfically backed proof points.  Eg: list out N
maintenance things that are provably "less" in teradata.

Also -- remember, teradata works really well on proprietary, sole source, single
vendor hardware (made by -- well -- the same company that makes teradata).
Their big database implementation do not run on open systems, commodity
hardware.
Rating: 5
Oracle v/s Teradata November 16, 2003
Reviewer: Phil
Thank you very much Tom. Hats Off to you.
Rating: 3
hosted model, where can I get this? November 17, 2003
Reviewer: Dick Stins from The Netherlands
Tam said:
We use a hosted
model for small user communities like that.  They pay a fee -- we run their
stuff in a database with tons of other people.  One professional DBA team that
knows their stuff, dozens/hundreds of customers per instance.

Where can I find more information about the hosted model?
Rating: 5
November 17, 2003
Reviewer: A reader
Rating: 4
How about in-memory databases November 17, 2003
Reviewer: Ketan Bengali from Sunnyvale, CA USA
Hi Tom,

Our company is evaluating an option to use in-memory database instead of Oracle.
Some of these in-memory database companies, like Ants Software or TimesTen
software, are claiming that they are atleast 5 to 80 times faster than
relational databases.

Do you know any problems with using in-memory database? Does Oracle has any
product which is similar to in-memory database?

Thanks,

Ketan.
Followup:
:1,$s/claim/prove it using my applications/g

I don't see them publishing any TPC's or industry standard benchmarks for
comparision.

I'm very sure there is something very specific they can do faster.

I'm also very very sure there are lots of stuff we do much faster as well.

Question is -- are you doing something that you cannot do and excell at doing
with Oracle?  Most times, we can "fix" that in the implementation (your design).
 These database things are pretty darn fast when used properly.
Rating: 5
Speed isn't everything November 18, 2003
Reviewer: Jim from Portland, OR, USA
It would be fairly easy to design an in memory "database" that was "faster" than
Oracle.  This is assuming that all you want is a fast method to "store" data and
"retrieve" data.  It is assuming that you don't want:
1. Robust recovery
2. Data Consistancy
3. Transactions
4. and many other features.

I would not call it a database, maybe a memory structure or shared memory
structure that contains data, but not a database.
Rating: 3
A Reader, USA November 18, 2003
Reviewer: A reader
I think in momory database would be much much slower than a datbase using disks
as storage.
Rating: 5
In-memory databases November 19, 2003
Reviewer: Ketan Bengali from Sunnyvale, CA USA
I love working with Oracle. Some of the in-memory databases do provide
persistent data storage, recovery mechanism and real time data retrival.

I would like to convince the team who is evaluating in-memory databases that in
long run it is not going to scale and might turn out a bad decision. These
databases may not solve locking issues, may not help in all recovery scenario,
may not integrate with other tools, can't work on different platforms, like
Oracle it won't even allow you to look into internal structures to figure out
the problem.

I want to know if anyone had any bad experiences or heard any bad experiences
about such databases which I can share it with evaluating team.

Thanks,

Ketan.
Rating: 2
December 28, 2003
Reviewer: A reader
You said

--x--
That is the major, #1 difference between us and them.  If your customer feels
windows is the flatform of the 21st century..... and always will be, I guess its
a safe choice.  If they look at history, really look at it, they might begin to
feel a little different
--x--

I see that this is a pretty old thread. Technology has advaned a lot in the last
2 years, both in database and OS areas.

I am a hardcore Oracle professional, but am finding it increasingly dificult to
hold my own in a entirely MS shop. All the fileservers, printservers, desktops,
apps are MS. Shrink-wrapped applications we bring inhouse come with their own,
you guessed it, SQL Server database. The only "island" in this MS sea is our
data warehouse (Oracle on Sun hardware).

From a CFO point of view, it is very attractive to "consolidate" (read
eliminate) the Sun/Oracle licensing costs and embrace MS SQL Server on the
existing umpteen Windows 2000/3 servers we have.

How can I counter this? Like you said, it is futile to compare technical
features on a checklist since their implementation differs so much as to be a
meaningless comparison.

Are there any definitive high-level whitepapers targeted for upper management
that lean towards Oracle?

Thanks
Followup:
well, you have one data point -- to you, it looks like MS everywhere.

To me, I still see lots of choice out there (point being -- depends on who you
work for.  in the late 80's early 90's there were lots of Wang shops too -- buy
everything from a single vendor -- hmmm, not much wang left today).

On a single Sun/Oracle combo -- I can consolidate all of the umtpeen windows
teeny databases into a single one that costs infinitely less to manage.  Lots of
CFO's are seeing that as well (recognizing that the major costs of their systems
are ongoing maintenance, NOT software related at all).

It costs less, provided more value to have N databases than M databases where N
is significantly smaller than M.  Thats a fact.  I don't care whose database it
is.
December 29, 2003 SHANMUGAM -- Thanks for the question regarding "Import takes more time", version 8.1.5
You Asked
Hi, Mr.Tom,
            I have one user. I need to export and import that user. While
exporting it is very fast. The dmp file size about 1GB. For some reasons  I need
to drop that user, recreate the same user and import the same data from that
dmpfile. The user contains two tablespaces one for data and another for Indexes.
Previously I had a datafile size is around 2GB. While importing I faced two
problems,
1) Insufficient tablespce 2) It takes more than 7 hours.
I used the following scripts to import in Sun 250 server( os ver 5.7)
imp  user/pwd   file = /data1/data.dmp FULL=Y GRANTS=Y ROWS= Y COMMIT=Y
LOG=/data1/i_data.log   Can you help me ?

Thanks in advance
With best regards
shanmugam
and we said...
1) insufficient tablespace - that means you didn't have enough room.  this
typically happens if you do the default export which uses "compress=y" which
generates a create table statement that has an INITIAL extent equal to the sum
of all allocated extents.  Recommend you always export with compress=N.

2) Import is a serial process.  Your DMP file was 1gig.  That was just the data.
 The indexes, constraints, etc were all in there as DDL.  When you imported, it
imported the data, then it created all of the indexes, then it verified all of
the constraints, then it compiled all of your code, and so on.....

Its a big sequential process.

So, to "speed that up", you have to do things to make those processes faster

o make sure your alert log doesn't have any "checkpoint not complete" or
"archival required" messages.  If it does, you have insufficient log configured,
add more.

o check your sort_area_size for your index builds, consider increasing it during
the import.

o don't use a simple exp owner=, imp owner=.  Export table by table (in
parallel, just fire off export more then once).  Import table by table (in
parallel, just fire off import more then once).

o don't use import to create the indexes -- use imp ... indexfile=temp.sql to
generate a script with the create indexes.  then  modify them to have nologging
and parallel.  Run them in sqlplus faster.

Just some ideas.  I'm sure others will come along and put their ideas here as
well -- things they've done.  Me -- I find doing more then one export and more
then one import the faster/easiest (when forced to use exp/imp for this)

If the "some reason" is just to rebuild objects -- I mean after you are done you
end up with exactly what you started with -- I would just use "alter table T
move tablespace foo"  to rebuild the tables and "alter index I rebuild" to
rebuild the indexes.  No need to ever take the data out of the database anymor.
Review & Followup

Rating: 4
Import takes more time October 09, 2001
Reviewer: Shanmugam from Singapore
Thank you very much Mr.Tom.  The information is very useful to me.
Rating: 4
About COMPRESS option October 09, 2001
Reviewer: A reader from TX, USA
Could you explain the export processing for COMPRESS option ?
According to Oracle document, this manage the initial extent for table data.
Since Oracle 816 and up, if we use the locally management tablespace method,
The INITIAL and NEXT size are the same, what compress will do, and
Which option should we use ? The default is 'Y'.

Could you elaborate this option please.
1. If we choose default value 'Y', when import into another DB, what impact to
    the new DB tablespace storage setting?
2. If we choose value 'N', what impact when import into another DB.
Assume DB1 tablespace A is using EXTENT MANAGEMENT LOCAL UNIFORM SIZE 64K
the new DB2 tablespace is using the same storage setting. or
3. If I use the larger uniform size, ie 128K.
Followup:
I do not like compress=y.  Totally wish it did not exist.

Compress will do what it always did.  sum up the existing extents and ask for
that much space.  When you import, the LMT will look at your initial request,
divide by its extent size and give you that many extents.

1) it'll cause you to allocate lots of extents up front in order to satisfy the
compress=y.  Eg: if the existing table has 50m allocated to it and you exp with
compress=y -- the initial will be 50m.  If you import into an LMT with 1meg
extents -- it'll allocate 50 of them initially.

2) it'll look at the initial and do the same as #1 but the initial will be
smaller then in #1 so it'll allocate less extents.

3) same as two
Rating: 3
October 09, 2001
Reviewer: A reader
From the full database export dump file- Is there any benefit in importing
twice? First only the objects and its data. Then importing only the indexes with
rows=n, ignore=y and indexes=y. Or is it the same as doing a full import?
Followup:
That would be the same.

Benefits to be had would be via:

o don't use import to create the indexes -- use imp ... indexfile=temp.sql to
generate a script with the create indexes.  then  modify them to have nologging
and parallel.  Run them in sqlplus faster.
Rating: 3
import is slow April 06, 2002
Reviewer: A reader
Hi Tom

Recently I performed a database export from one machine and import to another,
used these parameters

userid=sys/sys
file=exp.dmp
log=imp.log
buffer=10000000
commit=y
ignore=y
full=y

sort_area_size set to 30MB in init.ora (bounced database afterwards)
sort_multiblock_read_count to 8
db_block_size 8k
redo log file size 100MB with 6 groups
all tablespace were locally managed except system and RBS (due to numerous bugs
when use LMT in RBS)

the dump file was 2GB big (it included a designer's repository), I have done
this twice before already but on other servers with RAID 10 (with these raid 10
srvewrs the tablespaces were dictionary managed), the one I did few days back
was RAID 5.

With RAID 10 I rememeber the dump file was about 1.7GB and it took roughly 2
hours but with 2GB dump file it took almost 9 hours in RAID 5!!! Does RAID 5
really make so much difference?

Also I didnt generate the indexfile because since I have one CPU only I didnt
consider it was necessary (ok I know I can use nologging but I didnt because I
was in rush and couldnt spend time on add nologging clause on around 1000
indexes). The creating index part is mistery to me as well, does the index of a
table gets created after the data is imported to table first or this does not
always happen (some indexes are created with table before data is imported)?

Later I performed another import of designer's respository, itself alone took 3
hours (what a nightmare tool!) because it had so many pl/sql packages that i
thought the data dictionary contetion was really high!

Do you think is a good idea use buffer and commit parameters in import or it is
better idea take all RBS offline and leave a big one online and ignore these two
parameters to decrease commit frequency?

Finally what is the parameter recalculate_statistics for? I thought analyze runs
analyze table during import already

Note: By the way if we set analyze=n we should speed up import? or table
statistics is obtained from dump file and not by analyze table etc etc, although
I read from documentation that setting analyze=y import actually runs

ANALYZE TABLE ETC

and I think the document is wrong in export parameter STATISTICS, it says that
by default is estimate but I did a test, I created a dummy table, ran analyze
table xxx compute statistics then export the table with default STATISTICS value
but when I check the dump file it actually says

ALTER TABLE XXX COMPUTE STATISTICS instead of estimate
Followup:
The file system type will have NO (zero, zippo) bearing on the size of the dmp
file.

RAID 5 is NOTORIOUS for being slow.  I've seen a create tablespace take 14 times
longer on raid 5 then 0+1.  So yes, the raid array could definitely be having a
serious impact on you.

Indexes are created after the data is loaded by IMP.

I do everything as a big transaction personally.  frequent commits slow you
down.

From the docs for imp:

<quote>
In certain situations, the importer might want to always use ANALYZE statements
rather than precomputed statistics. For example, the statistics gathered from a
fragmented database may not be relevant when the data is imported in a
compressed form. In these cases, the importer may specify
RECALCULATE_STATISTICS=Y to force the recalculation of statistics.
</quote>

By default, we'll just stuff the stats from the exported database into the new
database, recalc says "don't do that, compute them for real"

Also from the docs:

<quote>
If statistics are requested at export time and analyzer statistics are available
for a
table, Export will place the ANALYZE command to recalculate the statistics for
the
table into the dump file. In certain circumstances, Export will also write the
precalculated optimizer statistics for tables, indexes, and columns to the dump
file.
See the description of the Export parameter STATISTICS on page 1-24 and the
Import parameter RECALCULATE_STATISTICS on page 2-25.
</quote>

so analyze will sometimes just stuff the old stats in.

If you HAD stats, it'll gather them the way you did it, if you didn't have
stats, then defaults kick in.
Rating: 4
recalculate_statistics does work? April 07, 2002
Reviewer: A reader
Hi

I did a small test, created a table called STATS with 57344 rows did analyze
table .... sample 10 rows to yield 57936 in num_rows column in user_tables, good
this is what I want, I dont want exact statistics

I then export table with

userid=lsc/lsc
file=exp.dmp
log=exp.log
tables=(lsc.stats)

then import to another user with

userid=lsc/lsc
file=exp.dmp
log=imp.log
fromuser=lsc
touser=ctrl
analyze=y
recalculate_statistics=n

import actually ran analyze table compute statistics because instead of my
original stats of 57936 rows it got the right num_rows now, 57344 rows

so basically I was telling him not to recalculate statistics but it did, why?
And with COMPUTE (or otherwise how did import got the right statistics?)

select owner, num_rows, table_name from dba_tables
where table_name='STATS'

OWNER                            NUM_ROWS TABLE_NAME
------------------------------ ---------- ------------------CTRL
               57344 STATS
LSC                                 57936 STATS
Followup:
from the docs:

</quote>
However, the precomputed optimizer statistics will not be used at export time
if:

o A table has indexes with system-generated names (including LOB indexes)
on A table has columns with system-generated names
o There were row errors while exporting
o The client character set or NCHARSET does not match the server character set
or NCHARSET
o You have specified a QUERY clause
o Only certain partitions or subpartitions are to be exported
o Tables have indexes based upon constraints that have been analyzed (check,
unique, and primary key constraints)
o Tables have indexes with system-generated names that have been analyzed
(IOTs, nested tables, type tables that have specialized constraint indexes)

Note: Specifying ROWS=N does not preclude saving the precomputed statistics
in the Export file. This allows you to tune plan generation for queries in a
nonproduction database using statistics from a production database.
<quote>

so, which of these might be applying to you.

Here (my character set = nchar character set) we can see this can work if all of
the rules are followed:

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

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from
all_objects;

17227 rows created.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from
all_objects;

17227 rows created.

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

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from t;

  COUNT(*)
----------
     51681

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t estimate statistics sample 10
rows;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select num_rows from user_tables where
table_name = 'T';

  NUM_ROWS
----------
     51112

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> !exp userid=/ tables=t

Export: Release 8.1.7.2.0 - Production on Sun Apr 7 11:43:14 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.2.0 - Production
With the Partitioning option
JServer Release 8.1.7.2.0 - Production
Export done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set

About to export specified tables via Conventional Path ...
. . exporting table                              T      51681 rows exported
Export terminated successfully without warnings.

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

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> !imp userid=/ full=t recalculate_statistics=n

Import: Release 8.1.7.2.0 - Production on Sun Apr 7 11:43:19 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

Connected to: Oracle8i Enterprise Edition Release 8.1.7.2.0 - Production
With the Partitioning option
JServer Release 8.1.7.2.0 - Production

Export file created by EXPORT:V08.01.07 via conventional path
import done in WE8ISO8859P1 character set and WE8ISO8859P1 NCHAR character set
. importing OPS$TKYTE's objects into OPS$TKYTE
. . importing table                            "T"      51681 rows imported
Import terminated successfully without warnings.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select num_rows from user_tables where
table_name = 'T';

  NUM_ROWS
----------
     51112

ops$tkyte@ORA817DEV.US.ORACLE.COM>
Rating: 4
oops April 07, 2002
Reviewer: A reader from madrid
sorry import used analyze table stats estimate statistics, I verified in v$sql
but this got me another question because you said that import will use the
analyze method what you did with your original table so I did another test, I
did analyze table stats compute statistics then exported again and imported
again but instead of using compute it uses estimate (the default export value)
so I guess export uses his own default parameters?
Rating: 3
Compress=Y April 07, 2002
Reviewer: Pablo Rovedo from Argentina
You have said several times you hate compress=Y, and I'm completely agree about
this, but why is the default?.Is the default in 9i aswell?

Thanks
Pablo R.
Followup:
It's a hold over from the really olden days -- before unlimited extents.  When
2k blocks ruled and there was a hard limit of 121 extents/object -- the major
number one use of export was to "compress" a table down into fewer extents as it
got close to this number.

It's a legacy
Rating: 4
if you import to same user then statsitcis is not recalculated April 07, 2002
Reviewer: A reader from madrid
hi tom

I did an exact example as you and I did get your results so I was puzzled but
then if instead of importing the table to same user as you did I import to
another the statistics is recalculated. Import to different user, that is the
only difference from my prrevious example in my tests and yours
Followup:
Looks like we need add "and the owners change during the exp/imp process" to the
list.

Concurr -- imp of data using fromuser/touser will re-analyze.
Rating: 3
Import terminated successfully with warnings. February 12, 2003
Reviewer: A reader
Hi Tom

I am trying to import one table .. i am getting the following error
-----
ORA-01658: unable to create INITIAL extent for segment in tablespace TS01
Import terminated successfully with warnings.
-----
I am sure there is lot of free space in tablespace as well as the drive where
the datafile located then the dump file .
Could you please tell me what could be the wrong and how to overcome this..

Thanks
Kumar
Followup:
do this

imp userid=u/p tables=TABLE_THAT_IS_FAILING indexfile=t.sql

edit t.sql, grab the create table statement and run it in sqlplus.  It'll fail,
now you can fix the initial to be more reasonable, create that table again.

Then, you can use ignore=y to import the data.

You probably are using dictionary managed tablespaces and have some serious
fragmentation going on.  You may well have gobs of free space -- it is just not
contigous in the tablespace itself -- hence it is useless.

see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:285415955510
there is a script that shows you the free space by tablespace but very
importantly the LARGEST contigous free space.

Try to use LMT (locally managed tablespaces) instead, you'll never regret it.
Rating: 5
Import Slow (Think Hung) on "Skipping Table table_name" February 21, 2003
Reviewer: Meyer from Baltimore, MD
Tom,

Have a full export from 8.16, the size is just under 2 gigs on NT.

It seems to work fine until it gets to the third table it sits there (sits over
24 hours)...with

Skipping table table_name

I have tried the same file on the instance it came from and another 8.17 w/the
same result.

The structure of the table is

NUMBER(10)
NUMBER(10)
VARCHAR2(3)
VARCHAR2(14)
NUMBER(2)

With approx 1.8 million rows.

I have tried pressing "enter" in case it is a display problem".

These tests were done on the file using show=y full=y

With tables=(table) I did not have the output

But, it just sat there when I did tables=(table_name)

Also...the DB seems "healthy" for create tables (based on a version of the table
that will not work)...w/ very fast response times.

What type of thing could cause this, where do I start to troubleshoot. Any
solution that just gets me the one table I want would work. (But I would like to
understand what happened as well).

Thanks,
Meyer
Followup:
I'll have to refer you to support.

If it were unix with all of the great tools -- I could tell you how to see what
the process is doing (if anything).

Under NT, I don't know how to tell what a process is or isn't doing -- and that
is what you need to do here.

Did you ftp this file?  did you do it BINARY???
Rating: 5
MORE INFORMATION FOR ABOVE POST February 21, 2003
Reviewer: Meyer from Baltimore, MD
Tom, it only happens with a .dmp file that was copied across our network (not
FTP'd) using the win(use-your imagination) standard copy command. Then copied
back...to the same server....the original version of the file (that never left
the server)...it works fine. Don't ask why I did not use that one first...just
didn't.

Seen this before?

Thanks,
Meyer
Followup:
Nope -- are they the same size?  if you do a binary "diff" on them (fc /? will
tell you how to do that) are they the "same"?

maybe you got unlucky and have hit a bad chunk of disk and it is just waiting
for the disk to return from a read and it never is.
Rating: 5
Results of FC February 21, 2003
Reviewer: Meyer from Baltimore, MD
I do not think chunk of disk because I tried to import it on two servers (both
same result).

File sizes are the EXACT same bytes...
dates/times are 6 minutes apart (time to copy over our network)?

To test FC (never used)....I created two text files and put the same words in
them...With the /B it does the binary compare..

fc /B first_file second_file

With the text files...it said:

FC: no differences encountered

The FC on the export files give me (LOTS OF) this:

14682383: 06 00
14682385: 36 00
14682386: 32 00
14682387: 35 00
14682388: 37 00
14682389: 38 00
1468238A: 38 00
1468238B: FE 00
(just pasted a few lines...I did a ctrl-C)

Seems copying my export files are corrupting them? I also checked another export
copied and same...At this point, not expecting a solution...but do you have any
advice?

Thanks,
Meyer
Followup:
that would have to be a windows bug -- you are getting binary zeroes stuck in
there.  Garbage in, garbage out -- imp must be getting sent off the deep end
with a totally corrupt file.

you would expect the files to be 100% binary "the same", they are not and the
only thing you did was copy them.  that they are dmp files isn't relevant --
they could be word documents or anything.  windows is corrupting your files.
Rating: 5
ora 6550 and ora 922 during import February 25, 2003
Reviewer: Fernanda
Tom, can you tell me what may be causing the errors below (ora-6550 and ora-922)
? I'm both exporting from and importing to Oracle9i on Windows.

Connected to: Oracle9i Enterprise Edition Release 9.0.1.1.1 - Production
With the Partitioning option
JServer Release 9.0.1.1.1 - Production

Export file created by EXPORT:V09.02.00 via conventional path
import done in WE8MSWIN1252 character set and AL16UTF16 NCHAR character set
.. importing CIPLAN's objects into CIPLAN
IMP-00017: following statement failed with ORACLE error 6550:
 "BEGIN  "
 "sys.dbms_logrep_imp.instantiate_schema(schema_name=>'CIPLAN', export_db_nam"
 "e=>'WFROTA.US.ORACLE.COM', inst_scn=>'10126426');"
 "COMMIT; END;"
IMP-00003: ORACLE error 6550 encountered
ORA-06550: line 2, column 1:
PLS-00201: identifier 'SYS.DBMS_LOGREP_IMP' must be declared
ORA-06550: line 2, column 1:
PL/SQL: Statement ignored
IMP-00017: following statement failed with ORACLE error 922:
 "CREATE TABLE "ADIANT_V" ("CODIGO" VARCHAR2(13) NOT NULL ENABLE, "ITEM" VARC"
 "HAR2(3) NOT NULL ENABLE, "NFROTA" VARCHAR2(10), "NFUN" VARCHAR2(10), "DATA""
 " DATE, "VALOR" NUMBER(15, 4), "OBSERVACAO" VARCHAR2(20))  PCTFREE 10 PCTUSE"
 "D 40 INITRANS 1 MAXTRANS 255 STORAGE(INITIAL 65536 FREELISTS 1 FREELIST GRO"
 "UPS 1) TABLESPACE "CIPLAN" LOGGING NOCOMPRESS"
IMP-00003: ORACLE error 922 encountered
ORA-00922: missing or invalid option
IMP-00017: following statement failed with ORACLE error 922:
 "CREATE TABLE "CAMPOS" ("CA_ORDEM" NUMBER(15, 0) NOT NULL ENABLE, "TA_NOME" "
 "VARCHAR2(30) NOT NULL ENABLE, "CA_NOME" VARCHAR2(30) NOT NULL ENABLE, "CA_O"
 "BRIGATORIO" CHAR(1), "CA_APELIDO" VARCHAR2(120), "CA_SELECIONADO" CHAR(1), "
 ""CA_PROCURA" CHAR(1), "CA_SORT" CHAR(1), "CA_TIPO" VARCHAR2(20), "CA_FILTRO"
 "" CHAR(1), "CA_MANDANTE" CHAR(1), "CA_TAMANHO" NUMBER(15, 0), "CA_DATACRIAC"
 "AO" DATE, "CA_TIPOREL" VARCHAR2(20), "CA_MSG" VARCHAR2(50), "CA_CHAVE" CHAR"
 "(1), "CA_GRID" NUMBER(15, 0))  PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 25"
 "5 STORAGE(INITIAL 65536 FREELISTS 1 FREELIST GROUPS 1) TABLESPACE "CIPLAN" "
 "LOGGING NOCOMPRESS"
IMP-00003: ORACLE error 922 encountered
ORA-00922: missing or invalid option
(... several more ora-922 errors ... )
Import terminated successfully with warnings.
Followup:
problem is that the export was done with 9.2 software and the import is being
done with 9.0 stuff.

features like "nocompress" (the opposite of compress) were added in 9.2

DMP files are not backwards compatible -- only forwards.

You need to use 9.0 EXP to create a dmp file that 9.0 IMP can handle.
Rating: 3
imp slow another possible reason February 25, 2003
Reviewer: Kirill Richine from Richmond, VA USA
imp can be slowed down, very significantly, if you have the following
conditions:

- a table with long raw
- commit=y

Unlike other datatypes, long raw does not respect buffer=N, and does not perform
any bulk dml. Consequently, it will commit after every row inserted!!!

Typically, if this is the case, v$session_wait will show a lot of log_file_sync
waits for the imp session.

Overall, imp performance, just like any other activity, can be diagnosed by
reviewing v$session_wait and identifying the event most frequently waited on.
Rating: 5
Incremental Export February 25, 2003
Reviewer: shanmugam from Singapore
Mr.Tom,

If the current dmp file size is say  20-30 GB. Is it advisable to do incremental
export on every day and  full export on every 15 days?  Is any major
disadvantages on Incremental Backup?
Followup:
it would be advisiable to stop using EXP!

I hope you are not using exp as a backup tool.

Backups are the only backups.

If my dmp file got to be 20-30gig -- I would stop doing it.  what is the
purpose/goal of doing this?

incrementals are not supported anymore in the current software, so I would not
suggest using them
Rating: 5
import with Oracle 8.1.7 client February 26, 2003
Reviewer: A reader
Hi Tom,
Can we use 8.1.7 clinet import utility to import 8.0.6 dump into an 8.06
database ?
Followup:
no

you will use the 806 EXP against 8i
you will use the 806 IMP against 806
Rating: 5
what one unspecified parameter can do.... May 26, 2003
Reviewer: Alvin from Philippines
I'm havin trouble importing data... it takes forever. Will try the compress=N i
thought 'N' was the default.

And i get archiving errors that's because the developmental machine is much much
much much smaller in scale. Does Imp/Exp generate tbat much redo ? can i
minimize it ?

Thanks.
Followup:
imp generates enough redo to protect the inserts it is performing.  It is AS IF
you were in sqlplus typing the inserts in yourself.  That amount of redo will be
generated.
Rating: 1
I'm confused... June 02, 2003
Reviewer: Alvin from Philippines
You gave this answer to the first question.

---
1) insufficient tablespace - that means you didn't have enough room.  this
typically happens if you do the default export which uses "compress=y" which
generates a create table statement that has an INITIAL extent equal to the sum
of all allocated extents.  Recommend you always export with compress=N.
---

Now compress=N is the worst of both settings ? hmmm...
Followup:
sorry about that - i read it wrong.  fixed it.
Rating: 4
confused June 15, 2003
Reviewer: Thirumaran.G from INDIA
hi Tom ,

i am confused about what Alvin  from Philippines has asked about and the
solution  given by you???

Alvin  from Philippines
=========================
You gave this answer to the first question.
---
1) insufficient tablespace - that means you didn't have enough room.  this
typically happens if you do the default export which uses "compress=y" which
generates a create table statement that has an INITIAL extent equal to the sum
of all allocated extents.  Recommend you always export with compress=N.
---
Now compress=N is the worst of both settings ? hmmm...

Followup:
sorry about that - i read it wrong.  fixed it.

============================================================

why compress=N is the worst of both settings ??

I will be thankfull if you could explain this. Which is  recommedned to use
compress=y or compress=n ??

Thanks in advance
Thiru
Followup:
sorry for the confusion.

COMPRESS=Y <<<=== this is bad
COMPRESS=N <<<=== this is good
Rating: 4
need clarification June 16, 2003
Reviewer: thirumaran.g from india
dear tom

you suggest not to use consistent=y during export . you had also posted in a
form that you donot like to use this .

In this link below
http://asktom.oracle.com/pls/ask/f?p=4950:8:310171716770144362::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:832427034486,
you are suggesting to use consistent=y during full export .

[a complete database export (should use consistent=y).  That will generate an
export with everything in it.]

during full db export can i proceed with consistent=y or not .

seeking your valuable advice on this

thanks
thirumaran.g
Followup:
no, i've said "compress=Y" is bad.

I don't believe I've said anything one way or the other about consistent.  It is
an option, you might NEED to use it, you might not.
Rating: 5
July 28, 2003
Reviewer: A reader
Rating: 5
import very slow November 10, 2003
Reviewer: atul from India
Hi,

We are doing import using designer 6i client,
Its very slow.
I found out that sort_area_size is very low only 20K.

Is import process is related to sort_area_size parameter?
What other parameters in init.ora file can affect performance of import process?

Thanks.
Atul
Followup:
sort area size will affect index builds.

you need to find out WHY the import is slow however, not just start flipping
switches willy nilly to see if something might accidently sort of help a little
bit.

diagnose the issue -- see what import is waiting on, waiting for, find the
bottleneck.

then and only then can we "fix it"
Rating: 4
where to find ? November 10, 2003
Reviewer: Ashiq Shamsudeen from Trichy, Tamil Nadu, India
Hi Tom,

   From your quote "diagnose the issue -- see what import is waiting on, waiting
for, find the bottleneck."

    You told us to see the bottleneck ,but tell me where to find the bottleneck?
. I mean which file,etc..
Followup:
it won't be "a file"

you can either

o run a 10046 trace on the import with level 12 (get binds) and review the trace
file (in 9i, tkprof will format the wait events nicely)

o if you are "almost single user" use statspack -- take a snap, wait 10 minutes,
take a snap -- review report.
Rating: 5
command to do November 10, 2003
Reviewer: Reader
tom, what is the exact command to do <quote> 10046 trace on the import with
level 12 (get binds)<quote> in sqlplus. Thanks.
Followup:
if you install dbmssupp.sql in $ORACLE_HOME/rdbms/admin

exec dbms_support.start_trace_in_session( sid=> NN, serial => NN );

will do it. start the import, query v$session to get the sid/serial
Rating: 3
export with and/or consistent=y -- oracle9iR2 November 11, 2003
Reviewer: Baqir Hussain from Emeryville, CA USA
Tom,
We are running oracle9i as OLTP and I would to take an export of the whole
schema with CONSISTENT=N (by default).  Even at the quite time the database is
continually updated due to the concurrent users ~ 500.
The tables are being updated during export with consistent=N

My questions are:
1. If I need to import it to a new machine, would I be able to do that? Is there
any kind of consistency (any kind of SCN # ..) required during import?

2. Since the data in the tables are inconsistent could cause web application not
to  function properly. Can such a behavior be seen during export with
consistent=N

Your help in this regard will be highly appreciated.
Followup:
1) no, export just dumps data.

2) with consistent=N, you'll be seeing data in tables at different points in
time.    the tables will be inconsistent with eachother potentially.
Rating: 4
How to exclude LOBs in exp/imp November 11, 2003
Reviewer: A reader
LOBs have always been the slowest part both in export and import - in import
it's much much worse. Sometimes people just want to get an export or import
without LOBs. Is there a good way to exclude them in exp and/or imp? If only
there were a "LOB=n" option ;-).
Followup:
you would have to specify tables to export and/or tables to import
Rating: 4
what if there are hundreds or even thousands of "normal" tables ... November 12, 2003
Reviewer: A reader
and I just want to exclude one huge LOB table? More precisely, I want to exclude
the LOB column. Short of "LOB=n", I guess we can settle with
"excluded=<table_names>", if only it's there!
Followup:
there isn't.  you can use fine grained access control to temporary "pretend"
there are no rows.

but tell me, what would the use of such an export be?  if there isn't any data
in this big column??

what is the goal here, what are you using export for?
Rating: 3
An extra tip November 12, 2003
Reviewer: David Cowie from Richmond VA USA
For some reason no one ever seems to include using constraints=N as a tip when
people ask about exp/imp. Some common ERP applications have an excessive number
of constraints in their database and the constraints can be created using the
indexfile after the data is imported. I have seen this one parameter change a 60
hour import into a 8 hour import including building the constraints and indexes.
Results of this will vary based on the number of constraints in your database.
Rating: 5
Import slow December 17, 2003
Reviewer: atul from India
Hi,

we are doing import from a designer,so we can't change any parameter.

Our sort are parameters are given below:

sort_area_size
262144

sort_area_retained_size
65536

Could you suggest about that to increase import speed?

Thanks.
atul
Followup:
you can change parameters (who cares where the import is actually from, you can
change database parameters).

anyway, you can parallelize the import (run imp many times, using tables= or
such).

you could transport the tablespaces instead of conventional import.

use larger buffer=
use commit=n

sort area size will affect index creation, you might want it a tad larger then
256k
Rating: 3
long column too large for column buffer size December 27, 2003
Reviewer: jamil from Saudi
Dear Sir

  I am getting this message
IMP-00009: abnormal end of export file
imp-00020: long column too large for column buffer size <20>
imp-00028: partial import of previous table rolled back 1208 rows rolled back
import terminated successfully with warnings.

when I try to import the table that is exported in previous oracle 8 , and my
current oracle is

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, Oracle Label Security, OLAP and Oracle Data Mining
options
JServer Release 9.2.0.1.0 &#8211; Production

And the table structure that is I am trying to import  as follow :

APPLICATION_ID      VARCHAR2(15)
MENU_NAME           VARCHAR2(15)
OPTION_NUMBER       NUMBER(4)
OPTION_TEXT         VARCHAR2(70)
COMMAND_LINE        VARCHAR2(20)
PROGRAM_TYPE        NUMBER(2)
SPECIAL_COND        NUMBER(2)

How can I increase the buffer size can you give me example

Best regards

Jamil
Followup:
you'll need to contact support for this one.  make sure you binary ftp'ed the
file (that the file itself is exactly the same size as it was on the source
system)
Rating: 4
Export / import from Lower version December 29, 2003
Reviewer: Arvind from India
Dear Tom,

We have a problem regarding export/import. We have 10 offices located at
different geographical sites. And we have a heterogeneous Oracle databases. Some
on 8.0.3 and some even on 8.1.6 and 8.1.7

We have a program which transfers data between different offices using exp/imp
utilities. This program is to be executed on server. So I have to load the
oracle utilities of lowest version on all the oracle servers along with oracle
8i utilities. It means it requires to load oracle 8.0 uilities in all the
servers. I have Oracle already working on each servers. Please can you let me
know that how to install Oracle 8.0 utilities on all the running oracle 8i
servers.

Thanks
Followup:
thats going to be problemtic as 803 is so far out of support and not even
installable on the OS's of today in many cases.

I'd opt for plan "b" or plan "c"

plan "b" -- get everyone on supported software and keep them in sync.

plan "c" -- use flat files, see
http://asktom.oracle.com/~tkyte/
, and sqlldr.
December 29, 2003 Saradha -- Thanks for the question regarding "Using LOGON tigger on Schema", version 8.1.7.4
You Asked
Tom,

I would like to prevent a set of users logging into the
application whenever there is a need.

For example, while some batach jobs (or conversion programs) running on some
tables, I would like to prevent set of users who access those
tables while allowing other users to logon.

All the application users logging into the database through the application
connect to the database using the same SCHEMA (Schema name: BSARP)

But all the application user names are stored in a table with the region they
belong to.

USERADMIN table.

appuser      region
-----------  -------
PETER        NJ
TIGER        NJ
.....        ..
MILTON       NY
CHRIS        NY
....         NY

At a given time, I want to prevent users belong to a particular region logging
into the application.

So I thought of having a AFTER LOGON trigger enabled to prevent the user and
have the trigger disabled when we want to allow everybody in the system.

For example,
If I want to prevent all the users belonging to NY region.

CREATE OR REPLACE TRIGGER prevent_ny_users AFTER LOGON
ON SCHEMA
DECLARE
   app_user   VARCHAR2(30);
   app_region useradmin.region%TYPE;
BEGIN
   SELECT osuser INTO app_user FROM v$session
   WHERE audsid = USERENV('sessionid');

   SELECT region INTO app_region
   FROM   useradmin
   WHERE  userid = app_user;

   IF ( app_region = 'NY' ) THEN
       RAISE_APPLICATION_ERROR(-20001,'The system is not currently available for
this region');
   END IF;

END;
/

The above trigger is created successfully under BSARP schema but it is not
working. It seems the RAISE_APPLICATION_ERROR is not working here.

I searched for this in your archives and I did find a similar one but
that example of trigger is on DATABASE level.

Can you please guide me on how to correct this or suggest me any other better
approach to handle this situation.

The schema BSARP does not have DBA role and it has the privilege
for executing all the application stored programs and necessary privileges on
application tables and views.

Thanks
Saradha
and we said...
You'll have to use a DATABASE level trigger -- the trigger will not prevent the
owner from logging in (to avoid serious problems if the trigger is invalid or
otherwise failing).  It would work like this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop user bsarp cascade;
User dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create user bsarp identified by x;
User created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> grant connect, create table, unlimited
tablespace to bsarp;
Grant succeeded.

ops$tkyte@ORA817DEV.US.ORACLE.COM> @connect bsarp/x

bsarp@ORA817DEV.US.ORACLE.COM> create table useradmin ( region varchar2(5),
userid varchar(30) );
Table created.

bsarp@ORA817DEV.US.ORACLE.COM> insert into useradmin values ( 'NY', 'tkyte' );
1 row created.

bsarp@ORA817DEV.US.ORACLE.COM> grant select on useradmin to ops$tkyte;
Grant succeeded.

bsarp@ORA817DEV.US.ORACLE.COM> @connect /

ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE OR REPLACE TRIGGER prevent_ny_users
  2  AFTER LOGON
  3  ON database when (user='BSARP')
  4  DECLARE
  5     app_user   VARCHAR2(30);
  6     app_region useradmin.region%TYPE;
  7  BEGIN
  8     SELECT osuser INTO app_user FROM v$session
  9     WHERE audsid = USERENV('sessionid');
 10
 11     SELECT region INTO app_region
 12     FROM   bsarp.useradmin
 13     WHERE  userid = app_user;
 14
 15     IF ( app_region = 'NY' ) THEN
 16         RAISE_APPLICATION_ERROR(-20001,'The system is not currently
available for this region');
 17     END IF;
 18  END;
 19  /

Trigger created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> disconnect
Disconnected from Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production
ops$tkyte@ORA817DEV.US.ORACLE.COM> connect bsarp/x
ERROR:
ORA-00604: error occurred at recursive SQL level 1
ORA-20001: The system is not currently available for this region
ORA-06512: at line 13

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> @connect /
ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout off
ops$tkyte@ORA817DEV.US.ORACLE.COM> REM GET afiedt.buf NOLIST
ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout on
ops$tkyte@ORA817DEV.US.ORACLE.COM> drop trigger prevent_ny_users;

Trigger dropped.
Review & Followup

Rating: 4
August 20, 2002
Reviewer: Saradha Bavanandam from NJ,USA
Tom,

Thank you very much for your response.

My DBA does not want to take this responsibility of creating
the trigger and droping the trigger as he has to be available all the time
whenever we need this requirement and also does not want to grant the DBA role
to anybody else.
I initially thought that CREATE ANY TRIGGER privilege will allow to create this
trigger but it is not.

I do not know what I am left with now.

Is it possible for the DBA to grant only certain privileges to another schema to
create and drop this DATABASE LEVEL trigger.

If not then I will have to think of some other mechanism to handle this
requirement.

Thanks once again for your help.
Saradha
Followup:
As bsarp, create another table

create table regions_to_block ( region varchar2(30) );

Then, in the trigger code:

declare
   l_count number;
begin
   select count(*) into l_count
     from dual
    where exists ( select NULL
                     from bsarp.useradmin
                    where userid = (select osuser
                                      from v$session
                                     where audsid = userenv('sessionid')
                                    )
                      and region in ( select region
                                        from bsarp.regions_to_block )
                 );

   if (l_count = 1)
   then
       RAISE_APPLICATION_ERROR(-20001,
           'The system is not currently available for this region');
   end if;
end;

Now it is 100% table driven.  bsarp inserts a row -- no logging in for that
region.  bsarp deletes a row and logging in just happens.
Rating: 4
August 20, 2002
Reviewer: Saradha Bavanandam from NJ, USA
Tom,

I did more research and read more documentation and this is
what I found out as a posssible solution.

I have modified the following trigger with
AFTER LOGON BSARP.SCHEMA and compiled the trigger from
another schema who has CREATE ANY TRIGGER privilege but
does not have DBA role and it seems to be working.

I have logged on to the database using LADEV userid.
$sqlplus LADEV/LADEV

This schema has the following privileges.
SQL> SELECT * FROM SESSION_PRIVS;
     create session
     alter session
     create table
     select any table
     create view
     create sequence
     create procedure
     create trigger
     CREATE ANY TRIGGER
     execute any procedure
     administer resource manager

And I created the following trigger

SQL> CREATE OR REPLACE TRIGGER prevent_ny_users
2  AFTER LOGON
3  ON BSARP.SCHEMA
4  DECLARE
5     app_user   VARCHAR2(30);
6     app_region useradmin.region%TYPE;
7  BEGIN
8     SELECT osuser INTO app_user FROM v$session
9     WHERE audsid = USERENV('sessionid');
10
11     SELECT region INTO app_region
12     FROM   bsarp.useradmin
13     WHERE  userid = app_user;
14
15     IF ( app_region = 'NY' ) THEN
16         RAISE_APPLICATION_ERROR(-20001,'The system is not currently
available for this region');
17     END IF;
18  END;
19  /

Trigger created.

SQL> EXIT

Now I try to logon to the BSARP schema and PETER is the operating system user.

$whoami
PETER

$sqlplus BSARP/BSARP

SQL*Plus: Release 8.1.7.0.0 - Production on Tue Aug 20 13:49:32 2002

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

ERROR:
ORA-00604: error occurred at recursive SQL level 1
ORA-20001: The system is not currently available for this region
ORA-06512: at line 13

So with this, the trigger does not have to be a DATABASE LEVEL trigger.

May I please know why you may not have suggested this. Is there any risk or
serious problem involved in this method.

Thanks for your advise again.
Saradha
Followup:
Suggest you use the table driven approach I recommended above along with a
STATIC, one time trigger that is always there.

1) it is easier
2) it is more understandable
3) Anytime you need "ANY" type privs, you want to think really hard.

For example, given CREATE SESSION and EXECUTE ANY PROCEDURE I could take over
your databaes easily (just those two)

Give me CREATE SESSION and CREATE ANY TRIGGER and I could grant myself EXECUTE
ANY PROCEDURE without you know it and take over....

These are very powerful privs you are playing with.  Go with the solution that
requires NO new users and NO overly powerful users.
Rating: 4
Security issue... September 13, 2002
Reviewer: Sven Bleckwedel from Santos, SP - Brazil
Tom,

I had some doubts about this issue, because I´m looking in these scenarios right
now.  Can you do some examples, about this fact, as you described ?

"
For example, given CREATE SESSION and EXECUTE ANY PROCEDURE I could take over
your databaes easily (just those two)

Give me CREATE SESSION and CREATE ANY TRIGGER and I could grant myself EXECUTE
ANY PROCEDURE without you know it and take over....

These are very powerful privs you are playing with.  Go with the solution that
requires NO new users and NO overly powerful users.
"

As you described, some of these privileges are part of the DEFAULT roles (CREATE
SESSION in CONNECT and CREATE TRIGGER in RESOURCE).  What is the essential
difference for CREATE TRIGGER and CREATE ANY TRIGGER, in this point-of-view ?...

Rgds,
Sven
Followup:
rather not describe exactly how.

Just be wary of anyone with any of the "ANY" privs.
Rating: 4
Natural side effect of having this very POWERFUL privilege September 18, 2002
Reviewer: Sven Bleckwedel from Santos, SP Brazil
Tom,

Ok.  But, to me is natural to know what I have to do, when the possibility
exists:

>... if you give me:
>o create session
>o execute any procedure
>I can take over your database and get ANY priv I want.  It
>would take about 30 seconds for me to have DBA and
>anything else I wanted.
>----------------------------------------------------------
>alter user -- hmm, with that I rule the WORLD ;)  I can
>become SYS or anyone I want to....
>-----------------------------------------------------------
>I'd rather not say.  The knowledge that it is so should be
>more then enough hopefully to make people go out there and
>reconsider how they grant privs and what privs they grant.
>Think about it -- you can run ANY procedure in the
>database, any SYS owned procedures, whatever you want...
>----------------------------------------------------------
>The knowledge that the ability exists is more then enough
>knowledge.
>This really isn't a hole, it is a natural side effect of
>having this very POWERFUL privilege.
>-----------------------------------------------------------
>Now, give me create session, execute any procedure, xxxxx
>xxx xxxxxxxxx -- and I'm back in business ;)

Of course, "xxxxx xxx xxxxxxxxx" can be "grant any privilege" ?  Where can i
read more about these privs ?

Rgds,
Sven
Followup:
The SQL reference manual.
The ADMIN guide.
Rating: 4
Not a spoiler, but.... September 19, 2002
Reviewer: Mark J. Bobak from Dearborn Heights, MI USA
Tom,

I'm surprised you didn't mention that Oracle (at least 8iR3
and 9iR1) come with at least one default user/password
combination that has create session and execute any procedure.

Yep, they put it in the database for you, by default.

I'm willing to bet a huge majority of databases have this
security hole just sitting there.

So, if you never worried about those default users Oracle
creates when a new database is installed, you may want
to take a second look at them.....;-)

-Mark
Rating: 3
September 19, 2002
Reviewer: Sagi from India
Thanx Tom.

One more good example.

Mark. I did not get what u were talking about. Were you referring to SYS &
SYSTEM users with their default passwords?
Followup:
He means we should run:

select grantee from dba_sys_privs where privilege = 'EXECUTE ANY PROCEDURE';

and make sure the accounts that pop up don't have their default passwords still
there (eg: you've changed them).  if they do, watch out.
Rating: 5
RE: Not a spoiler... September 19, 2002
Reviewer: Mark A. Williams from Indianapolis, IN USA
Mark:

Very valid point!

Strong argument for:

1) not using a "default" database...

2) never installing "sample" schemas into prod. databases...

3) knowing what you are doing...

4) checking up on things...

5) reading this document:
http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/network.920/a96582/toc.htm
I regularly perform security audits on my databases and wonder why security
seems to take a back seat.  Oh so many people want to figure out how to turn of
logging of operations, but are numb to the fact that their database could be
"wide-open"...

- Mark (from Indy)

BTW, I wonder if it is possible to create a logon Winnie the Pooh?  (See title
from Original Poster - very bad joke).
Followup:
in 9iR2 -- you should be glad to hear, the default accounts are all locked and
sys/system won't have change_on_install and manager and sysdba/sys/sysoper
actions can be audited (and dba's can grant on others objects and the create
database command supports passwords for sys/system as well)
Rating: 5
Thanks Tom! September 19, 2002
Reviewer: Mark A. Williams from Indianapolis, IN USA
Tom:

Thanks for the feedback!  There are many benefits to 9iR2 from my perspective.
I have 2 9iR1 databases, but most new/upgrades are going straight to R2!  I was
glad to see the 07_dictionary_accessibility default to false beginning with R1
as well...  I have a long list of improvements that I appreciate.

One improvement that I really like is system managed segment space (ie, segment
management auto).  However, I recently read a post from Howard Rogers on c.d.o.s
wherein he felt that the parameter was only useful for a RAC config.  I use it
in non-RAC servers with no problems...  he didn't really give any reasoning
behind his assertion IIRC...

BTW, why does AskTom not set a cookie on my machine with my name/location/etc...
like it used to?

Oh, well, don't want to start a new thread here!

Thanks again,

- Mark W.
Rating: 3
September 19, 2002
Reviewer: zl from china
  you said "given CREATE SESSION and EXECUTE ANY PROCEDURE I could take over
your databaes easily (just those two)".
  how to do that?
  thanks.
Rating: 5
To zl from China September 20, 2002
Reviewer: Mark A. Williams from Indianapolis, IN USA
Visit this thread here for reasons why this is a potential issue (and why the
way to abuse it isn't "published").
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:862779019442
HTH,

Mark W.
Rating: 5
people are going to figure this out themselves July 02, 2003
Reviewer: A reader
All these mystifying and obscuring attempts only serve one purpose (not intended
by our kind hearted folks): you created more hype than anything regarding this
perceived "security issue".

Here is an easy one:

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production

12:39:26 SQL> sho user
USER is "SYSTEM"
12:39:28 SQL> sho parameter o7

NAME                     TYPE          VALUE
------------------------------------ --------------------
------------------------------
O7_DICTIONARY_ACCESSIBILITY         boolean          TRUE
12:39:33 SQL> create user lowlife identified by highlife default tablespace
users temporary tablespace temp;

User created.

Elapsed: 00:00:00.00
12:40:36 SQL> grant create session, execute any procedure to lowlife;

Grant succeeded.

Elapsed: 00:00:00.00
12:40:48 SQL> conn lowlife/highlife
Connected.
12:40:59 SQL> select * from session_privs;

PRIVILEGE
----------------------------------------
CREATE SESSION
EXECUTE ANY PROCEDURE

2 rows selected.

Elapsed: 00:00:00.00
12:41:05 SQL> exec dbms_repcat_admin.grant_admin_any_schema('lowlife')

PL/SQL procedure successfully completed.

Elapsed: 00:00:01.01
12:41:30 SQL> select * from session_privs;

PRIVILEGE
----------------------------------------
CREATE SESSION
ALTER SESSION
UNLIMITED TABLESPACE
CREATE ANY TABLE
ALTER ANY TABLE
DROP ANY TABLE
COMMENT ANY TABLE
SELECT ANY TABLE
INSERT ANY TABLE
UPDATE ANY TABLE
DELETE ANY TABLE
CREATE ANY CLUSTER
ALTER ANY CLUSTER
DROP ANY CLUSTER
CREATE ANY INDEX
ALTER ANY INDEX
DROP ANY INDEX
CREATE ANY SYNONYM
DROP ANY SYNONYM
CREATE PUBLIC SYNONYM
DROP PUBLIC SYNONYM
CREATE ANY VIEW
DROP ANY VIEW
CREATE ANY SEQUENCE
ALTER ANY SEQUENCE
DROP ANY SEQUENCE
CREATE DATABASE LINK
CREATE ANY PROCEDURE
ALTER ANY PROCEDURE
DROP ANY PROCEDURE
EXECUTE ANY PROCEDURE
CREATE ANY TRIGGER
ALTER ANY TRIGGER
DROP ANY TRIGGER
CREATE ANY SNAPSHOT
ALTER ANY SNAPSHOT
DROP ANY SNAPSHOT

37 rows selected.

Elapsed: 00:00:00.00
12:41:36 SQL>
Followup:
so why the air quotes

...
perceived "security issue".
.....

you just proved the entire point.  If I created "hype" -- excellent, hope I did.

It is not a perceived issue, you've just show exactly how "real" the issue is.
Rating: 5
full disclosure July 02, 2003
Reviewer: A reader
I mean, instead of saying (something like) "I'd rather not describe exactly how
it's done", how about simply disclose how it can be done? The reader, who
provided a link to the "reasons why this is a potential issue (and why the
way to abuse it isn't "published")", mystified the issue even further, which
only provokes people's curiosity. My opinion: don't obscure, just fully
disclose. Overall that's better for the public.
Followup:
no, you showed but one way.  there are many others.

Now, maybe will have a bunch of people that say "ok, i get rid of that package
and I'm safe"...

all you need to know is "if someone has these, they have the ability to do
anything they want"
Rating: 5
which public? July 02, 2003
Reviewer: Kamal Kishore from New Jersey, USA
<quote ID="Previous Poster">
My opinion: don't obscure, just fully
disclose. Overall that's better for the public.
</quote>

Well, which "public" are you serving here by this discloser?
The ones who are trying to protect the assets (here their database) and get
something useful done or the ones who have nothing else useful to do other than
to be "on the lookout" for such issues and are always looking for (or many times
create one) the step-by-step guide (which you provided as a generous gift to
them)?

Your example served the needs of the second class of "public" wereas we are
trying to serve the first class of "public".

Which side are you on, really?
Rating: 5
There is only ONE public July 02, 2003
Reviewer: A reader
It's very funny that some people always have the constant craving for taking
sides. You might as well suggest software companies deny the existence of any
security holes, because the mere mentioning of some particular security issues
is enough to serve those "second class" people that you referred to.

In this case, the simple disclosure made by Tom that "create session and execute
any procedure are two privileges that are enough for one to take over the whole
database" actually is quite enough for a knowledgeable person to find the way in
easily. Knowing how it is done is good for the public to understand the severity
of the issue and come up with ways to prevent it (turn
o7_dictionary_accessibility = false), or monitor it (audit the exec of any
dbms_* procs), therefore find the culprit. Think about it, if you don't even
know how people get in, how are you suppose to know which doors to secure and
which pathways to monitor? You are a big joker. Hope you don't work for any
security firm (this is joke ;-).
Rating: 5
How about... July 03, 2003
Reviewer: Kamal Kishore from New Jersey, USA
I don't think I can be as big a joke(er) than you have presented yourself. I'll
leave that spot for you.

How about this:
I, don't work for security firm.
You, don't work at all.

That would be best serving the industry.
Rating: 5
Yet another great discussion July 16, 2003
Reviewer: Arun Mathur from GA
Tom,

The person who originally posted the question mentioned that the
RAISE_APPLICATION_ERROR didn't work when the trigger was performed on the
schema. Can you go over why it didn't work?

All the best,
Arun
Followup:
it is in the 1st paragraph of my response.
Rating: 3
Prompt for Application Userid and Password September 02, 2003
Reviewer: A reader
Is there any wy i can prompt the user to enter the userid and password (Which is
application userid and pwd after the user has logged in) from sqlplus
Followup:
they would have to use

sqlplus /nolog

and all you need do is put "connect" in a script

[tkyte@tkyte-pc-isdn tkyte]$ sqlplus /nolog

SQL*Plus: Release 9.2.0.3.0 - Production on Wed Sep 3 06:57:13 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

idle> connect
Enter user-name: scott
Enter password:
Connected.
idle>
Rating: 5
How to use a Database Startup Trigger December 28, 2003
Reviewer: TH from Manama, BAH
Dear Tom,
I want to update a certain table in a particular schema when the Oracle starts.
Which is that trigger? Waiting for your reply,

TH, BAH
Followup:

http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96590/adg14evt.htm#998000
documents all of the system events, including the "after startup" trigger.
Rating: 5
December 29, 2003
Reviewer: Riaz Shahid from PRAL, Lahore, Pakistan
Tom !

Regarding to the original question, doesn't the
Alter System enable restricted session
will be enough ? (by giving retricted session grant to only the users which are
needed to be given permission)
Followup:
Nope:

"All the application users logging into the database through the application
connect to the database using the same SCHEMA (Schema name: BSARP)"

they all use the same userid in the database.
December 29, 2003 Saminathan -- Thanks for the question regarding "Two dimensional array(name-value pair) feature in 8.1.7.3.", version 8.1.7.3
You Asked
Dear Tom,
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1058232381458#10353825155270
I am exactly looking for this 2 dimensional array(name-value pair) feature in
8.1.7.3.

Could you please provide me the correlated_array code? I did search this site
for "correlated_array" but no luck.

<Tom_Answer>
For example, once upon a time ago I wrote a simple "correlated array" type.  It
was an object type with some getter/setter methods, a nested table of NAME/VALUE

pairs.  I could create a variable:

declare
   x  correlated_array := correlated_array();
begin
   x.set('somename','someval');
   x.set('someothername','someotherval');

   dbms_output.put_line( x.get('somename') );
   dbms_output.put_line( x.get('someothername') );
</Tom_Answer>

Thanks in advance,
Sami
and we said...
PROMPT CorrelationType
create or replace type CorrelationType as object
(   name    varchar2(50),
    value   varchar2(8192)
)
/

PROMPT CorrelationArrayType
create or replace type CorrelationArrayType as varray(255) of CorrelationType
/

PROMPT CorrelatedArray Spec
create or replace type CorrelatedArray as object
(
  vals  CorrelationArrayType,
  member function valueof( p_name in varchar2 ) return varchar2,
  pragma restrict_references(valueof,wnds,rnds),
  member procedure addpair( p_name in varchar2, p_value in varchar2 ),
  member procedure addHeaderline( p_line in varchar2 ),
  member procedure updateValue( p_name in varchar2, p_value in varchar2 )
)
/

PROMPT CorrelatedArray Body
create or replace type body CorrelatedArray as

  member procedure updateValue( p_name in varchar2, p_value in varchar2 ) is
    l_found boolean := FALSE;
  begin
    for i in 1 .. vals.count loop
      if vals(i).name = p_name then
        vals(i).value := p_value;
        l_found := TRUE;
        exit;
      end if;
    end loop;
    if not l_found then
      addpair( p_name, p_value );
    end if;
  end updateValue;

  member function valueof( p_name in varchar2 )  return varchar2 is
    l_name  varchar2(50) default upper(p_name);
    l_idx   number default 1;
  begin
    for i in 1 .. vals.count loop
      exit when ( vals(i).name = l_name );
      l_idx := l_idx+1;
    end loop;

    if ( l_idx <= vals.count ) then
      return vals(l_idx).value;
    else
      return NULL;
    end if;
  end valueof;

  member procedure addpair( p_name in varchar2, p_value in varchar2 ) is
  begin
    vals.extend;
    vals( vals.count ) := CorrelationType( upper(p_name), p_value );
  end addpair;

  member procedure addHeaderLine( p_line in varchar2 ) is
    l_n number default instr(p_line,':');
  begin
    addpair( ltrim(rtrim(substr( p_line, 1, l_n-1 ))),
             ltrim(rtrim(substr(p_line,l_n+1))) );
  end addHeaderLine;

end;
/

show error
Review & Followup

Rating: 5
Usage of "addHeaderLine" December 27, 2003
Reviewer: Sami from NJ,USA
Dear Tom, Thanks a million!

I understood all the methods(addpair,updateValue,Valueof) but NOT clear about
addHeaderLine member.

Coudl you please give me simple example how it will be used?

Thanks again,
Sami
Followup:
it was a routine I used when parsing internet style headers which are typically
in the form:

Name: Value

Like:

From: Tkyte@oracle.com
To: Someone@somewhere

it would just parse the header line around the ":"
Rating: 5
modified addpair() --vals.extend; December 28, 2003
Reviewer: Sami from NJ,USA
Dear Tom,

Your code is really useful in many ways in our dev.

A)
I modified addpair() like below. Otherwise the very 1st entry is getting stored
in array(vals) 2nd position.

  member procedure addpair( p_name in varchar2, p_value in varchar2 ) is
  l_count  number default 0;
  begin
    vals( vals.count ) := CorrelationType( upper(p_name), p_value,l_count );
    vals.extend;
  end addpair;

B)Also added new member to get "name-string" based on the position.

   member function valueAt( p_position in number )  return varchar2 is
  begin

    if ( p_position <= vals.count ) then
      return vals(p_position).name;
    else
      return NULL;
    end if;
  end valueAt;

Thanks for all your help!
Sami
Followup:
a) no, thats not right.

vals starts with 0 elements.

vals.extend makes it have 1
vals(vals.count) would be vals(1)

the extend should be before the assignment.
Rating: 5
Correct me If i am wrong? December 29, 2003
Reviewer: Saminathan Seerangan from NJ,USA
Dear Tom,

Kindly explain why CA.VALUEAT(1) becomes NULL here. Is something wrong in my
"ValueAt(p_position in nymber)" member body.

I think PL/SQL varray  is bit differ
(starting from position 1) from other languages
like C or JAVA. Even PL/SQL varray diagram dipicts the same[x(1) x(2)... x(10)]

Correct me if I am wrong?

SQL> DECLARE
  2      CT CORRELATIONTYPE;
  3      CAT CORRELATIONARRAYTYPE;
  4      CA  CORRELATEDARRAY;
  5      K NUMBER :=0;
  6  BEGIN
  7      DBMS_OUTPUT.ENABLE(1000000);
  8      CAT:=CORRELATIONARRAYTYPE(CT);
  9      CA:=CORRELATEDARRAY(CAT);
 10
 11      CA.ADDPAIR('AUTHOR','TOM KYTE');
 12      CA.ADDPAIR('WEBSITE','ASKTOM.ORACLE.COM');
 13      CA.ADDPAIR('COMPANY','ORACLE');
 14      CA.ADDPAIR('BOOKNAME','EFFECTIVE ORACLE BY DESIGN');
 15
 16      DBMS_OUTPUT.PUT_LINE( CA.VALUEAT(1)||' is
'||CA.VALUEOF(CA.VALUEAT(1)));
 17      DBMS_OUTPUT.PUT_LINE( CA.VALUEAT(2)||' is
'||CA.VALUEOF(CA.VALUEAT(2)));
 18      DBMS_OUTPUT.PUT_LINE( CA.VALUEAT(3)||' is
'||CA.VALUEOF(CA.VALUEAT(3)));
 19      DBMS_OUTPUT.PUT_LINE( CA.VALUEAT(4)||' is
'||CA.VALUEOF(CA.VALUEAT(4)));
 20      DBMS_OUTPUT.PUT_LINE( CA.VALUEAT(5)||' is
'||CA.VALUEOF(CA.VALUEAT(5)));
 21  END;
 22  /

is <======CA.VALUEAT(1)
AUTHOR is TOM KYTE <======CA.VALUEAT(2)
WEBSITE is ASKTOM.ORACLE.COM
COMPANY is ORACLE
BOOKNAME is EFFECTIVE ORACLE BY DESIGN

PL/SQL procedure successfully completed.

SQL>

==============VALUEAT BEGIN======================
 MEMBER FUNCTION VALUEAT( P_POSITION IN NUMBER )  RETURN VARCHAR2 IS
  BEGIN

    IF ( P_POSITION <= VALS.COUNT ) THEN
      RETURN VALS(P_POSITION).NAME;
    ELSE
      RETURN NULL;
    END IF;
  END VALUEAT;

==============VALUEAT END======================
Followup:
ops$tkyte@ORA9IR2> DECLARE
  2      CA  CORRELATEDARRAY := CorrelatedArray(CorrelationArrayType());
  3  BEGIN
  4      CA.ADDPAIR('AUTHOR','TOM KYTE');
  5      CA.ADDPAIR('WEBSITE','ASKTOM.ORACLE.COM');
  6      CA.ADDPAIR('COMPANY','ORACLE');
  7      CA.ADDPAIR('BOOKNAME','EFFECTIVE ORACLE BY DESIGN');
  8          for i in 1 .. ca.vals.count
  9          loop
 10          DBMS_OUTPUT.PUT_LINE( i || ') ' || CA.vals(i).name||' is
'||CA.vals(i).value );
 11          end loop;
 12  END;
 13  /

1) AUTHOR is TOM KYTE
2) WEBSITE is ASKTOM.ORACLE.COM
3) COMPANY is ORACLE
4) BOOKNAME is EFFECTIVE ORACLE BY DESIGN

PL/SQL procedure successfully completed.

you stuffed something in there in the "first" position in the way you
initialized the variable.  The above shows how I would initialize it and the
output (using MY code...)
Rating: 5
Fact speaks louder than theory December 29, 2003
Reviewer: Sami from NJ,USA
Thanks Tom,Really appreciated. I could not give you more than *****(the response
rate)
December 29, 2003 Prince -- Thanks for the question regarding "synonym for function inside a package", version 8.1.7.4
You Asked
Suppose I have defined a synonym for a function inside a package.
Is it possible to access the function thru the synonym?

SQL> create synonym myfun for mypkg.myfun ;

Synonym created.

SQL> select myfun(64) from dual ;
select myfun(64) from dual
       *
ERROR at line 1:
ORA-00904: invalid column name

I am wondering is it possible to call the functins directly, without prefixing
with the package, like the ones in STANDARD?
and we said...
No, synonyms are only for top level schema objects -- such as those found in
ALL_OBJECTS

A function in a package is not to be found there.

You would need to create a small standalone function "myfun" that simply calls
mypkg.myfun.
Review & Followup

Rating: 5
I got it now. Thanks Tom. July 22, 2002
Reviewer: A reader
Rating: 5
Why this? December 29, 2003
Reviewer: A reader
SQL> create public synonym current_timestamp for sysdate;
create public synonym current_timestamp for sysdate
                                            *
ERROR at line 1:
ORA-00995: missing or invalid synonym identifier
Followup:
sysdate is just an internal thingy, you cannot create an identifier for it, it
does not live in the data dictionary.  sysdate is a special word and we
recognize you cannot use it for an identifier:

ops$tkyte@ORA9IR2> create table sysdate ( x int);
create table sysdate ( x int)
             *
ERROR at line 1:
ORA-00903: invalid table name

sysdate is just not valid where an identifier is expected.

besides:

ops$tkyte@ORA9IR2> select current_timestamp from dual;

CURRENT_TIMESTAMP
---------------------------------------------------------------------------
29-DEC-03 01.06.46.405867 PM -05:00

current_timestamp already has meaning in the database.
December 29, 2003 Basharat -- Thanks for the question regarding "Real Application Clusters", version 8.1.6
You Asked
Hi Tom

You are doing a great job by providing solutions and help to Oracle users. I
want to ask you about 9i RAC, could you please explain how they work in
delivering 24/7 database availability.

Thanks

Basharat Mehmood
MashreqBank Dubai.
and we said...
You use redundancy to remove SPOF's - single points of failure.

You use raid for example to make sure the disks never fail.
You use dual NIC's and networks to make sure the network never fails.

RAC allows you to use more then one computer in a cluster to make sure the
failure of a single computer (n-1 computers actually) doesn't bring down the
database.  By running more then one instance of oracle on different computers --
all accessing the SAME database -- we can increase (greatly) the availability of
the system
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/rac.920/a96597/toc.htm
read that for more details.
Review & Followup

Rating: 3
How about the stability of RAC on linux? July 16, 2002
Reviewer: chao_ping from china
OPS is not that good as oracle said, for many customer's OPS database is slow or
frequent 600/hang/down. Maybe this is because of it is poorly implemented and is
used in oltp system, but it is not stable as single instance, this is the fact.
Now oracle said even OLTP can be used without application partition,i want to
know if there is some successful case that has used oracle9i rac? What about the
RAC on linux? Is it stable? It seems oracle want to take the low end market of
rac on linux, and we are going to implement one RAC cluster on linux with
2node/4cpu each, but as a dba, i am not sure whether it IS STABLE.
Followup:
OPS is not RAC, you had to design your applications for OPS (and we always said
that -  so what you had was a poorly designed OLTP app on OPS).

With RAC -- you do not have to physically design to scale up in a cluster --
very very different architecture, Read about it.  then -- give it a whirl
Rating: 5
Real RAC customers July 17, 2002
Reviewer: Alex from New York, NY USA
Chao,

Some of the customers that use RAC are:UPS, CERN, FAA( on Linux ) , DELL ( On
linux ), Austrian Railways, BACS, Bavarian Police Force,  e-Spatial, Navision
a/s, North Rhine-Westphalia Police Force, Orange Denmark, South African Police
Service, Syngenta, Top Toy, Wolfe Group and etc. From what I know there are more
than 800 customers who are implementing RAC and more than 250 of them live.If
you follow the news, recently SAP certtified RAC for their suite
( You just cannot get more OLTP than that )There are some pilot projects have
already started with mutually agreed-upon customers running SAP solutions on
Oracle9i.
According to Dell and FAA ( and those guys are really 24X7) they did not have
any issues with 9iRAC stability on Linux.
Rating: 5
in-house applications migration to 9i RAC March 14, 2003
Reviewer: A reader
Hi

We have several in-house designed application based on Oracle Developer 2000,
Pro*C and many PL/SQL packages, procedures and functions. We are running all
this in 7.3.4. We are considering migrate it to 9i and a couple of them to RAC.
I have been doing heavy research on RAC regarding migration issues, advantages
and disadvantages. Of course all I have found except one whitepaper all says RAC
is wonderful but none of them actually talks about the issues you should
consider. The whitepaper by Mr James Morle shows RAC can be much slower than
single instance databases.... I even did a heavy search on Metalink seeking for
migration issues we should look at but found nothing.

Do you know any whitepaper which Oracle Corp. has released stating some
migration issues we should have in mind? Is RAC totally 100% transparent to
applications?

Thanks in advance
Followup:
don't read whitepapers -- which are snippets of information

read the documentation:
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-REA
there is a performance/tuning guide which calls out some things you want to be
aware of.

Everything, when used inappropriately, can run really slow.  I can show you two
applications that do the same thing but do it differently.  One will run really
slow, one really fast.  Everything will affect you.

The thing with RAC is that a bad programming choice that affects you marginally
on a single instance may will be magnified on RAC (or vice versa).  So something
that runs "ok" in a single instance may fall apart on RAC (and vice versa)
Rating: 5
RAC and OPC March 15, 2003
Reviewer: A reader from Houston
How is RAC different(or similar) from OPC?  What is the difference between RAC
and OPC?
Followup:
I assume you mean OPS -- Oracle Parallel Server.

It is all about the way in which cache fusion works in a write write conflict.

see
http://technet.oracle.com/docs/products/oracle9i/doc_library/901_doc/rac.901/a89867/whatsnew.htm#983402
Rating: 5
RAC vs OPS March 16, 2003
Reviewer: GVN from Houston
Rating: 3
9i RAC vs 8i Advance replication June 23, 2003
Reviewer: Alvin from Philippines
Is RAC a newer more robust version of 8i's Advance Replication (AR) ?

Or is it totally different ?
Followup:
RAC is a new more robust OPS.

it is not replication, not close.

see
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/rac.920/a96597/toc.htm
Rating: 5
RAC versus OPS July 17, 2003
Reviewer: A reader
"OPS is not RAC, you had to design your applications for OPS (and we always said

that -  so what you had was a poorly designed OLTP app on OPS)."

Thanx Tom,
I take it that if your application is well developed
and tested etc., for development, we dont need to
run it on RAC. For example, if deployment uses MTS,
we should develop also on MTS. If deployment is on RAC
then from above, can I conclude that we dont need to
develop also on RAC?

Thanx!!!
Followup:
you would most certainly want to develop on RAC if you intend to deploy on RAC.
Rating: 5
thanx tom! July 17, 2003
Reviewer: A reader
Would you be kind enough to give some examples where
things may go wrong if a RAC is not being used in development and testing but
gets used in production?

Is it common for people to develop on RAC clusters
in such cases? How easy or difficult is it for
a developer to set up a RAC configuration?

Thank you!!
Followup:
would you develop, test on windows and go production on HP/UX?

it is just *different* code, the potential for something to be different exists.
Rating: 5
Re: RACs July 17, 2003
Reviewer: A reader
well, this is what the RAC documentation says
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/rac.920/a96597/psintro.htm#10947
"Transparency
The concept of transparency implies that Real Application Clusters environments
are functionally equivalent to single-instance Oracle database configurations.
In other words, you do not need to make code changes to deploy applications on
Real Application Clusters if your applications ran efficiently on
single-instance Oracle configurations.
"

This seems to imply that you could develop and tune in
one instance and then deploy it on RAC.

Thanx!
Followup:
go for it -- if you like risk.  if you like inflicting pain on your end users.
if you like looking "not good"

think about it.  would you really develop on platform X and then without testing
deploy onto Y?

(common sense must figure into the equation - I am a "risk adverse" person when
it comes to some things -- like testing, development, deploying a production
application.  You sort of want to make sure things will actually WORK before you
inflict them on your end users)
Rating: 5
RAC July 18, 2003
Reviewer: Tim Hall from UK
I've been using RAC on Tru64 in development and production for a few months now
and it's great. We've not had to do any RAC-specific modifications and it's been
really stable.

If cost is an option you should move to Linux. That way you can build a
development RAC using FireWire and save yourselves loads of cash :)

Cheers

Tim...
Followup:
and again, if you test on something totally different from production -- be
prepared for long days, longer nights, lots of gnashing of teeth and mad people.
Rating: 4
Applications on RAC October 06, 2003
Reviewer: A reader
Hi Tom,

We are developing Java Based appplication invloving J2EE architecture and Oracle
9iAS to be deployed upon Oracle9i RAC.

Recently we came across, while testing, a few limitations like global
application context cannot be applied on RAC. Similarly we cannot use cached
sequence numbers. Can you please help me by giving some details or link on what
all limitations are there if using RAC and java based applications.

Thanks and Regards
Followup:
why cannot you use cached sequences?
Rating: 2
Wrong wordings October 06, 2003
Reviewer: A reader
Hi Tom,

My mistake, i used wrong words in framing the statement. What i meant was one
node breakdown can cause the loss of unused numbers in the sequence as the cache
option default value is 20. And after going throught the following links i was
sure that it was not a good idea.
http://metalink.oracle.com/metalink/plsql/ml2_documents.showDocument?p_database_id=FOR&p_id=421566.996
http://metalink.oracle.com/metalink/plsql/ml2_documents.showDocument?p_id=1031850.6&p_database_id=NOT
Any way i m still looking into what limitations can be there for TAF. Just found
the following link.
http://metalink.oracle.com/metalink/plsql/ml2_documents.showDocument?p_database_id=NOT&p_id=97926.1
Can you suggest me some more links on this issue and how to handle such problems
if possible. As we think we need to do some rework in changing our application.

Thanks and Regards
Followup:
so?  sequences are random numbers -- you can loose hundreds of thousands per
second and still not exhaust one in trillions of years!!!!  don't worry about
that -- you only want to use cached sequences (use a big cache, it'll feel
better and run faster)

you will lose sequences NO MATTER WHAT -- cache or nocache -- NO MATTER WHAT.

treat sequences as what they are -- a way to generate unique keys.  The number
they return has no meaning as far as "age", "order", etc....

what "problems" -- i see a note on the limits of TAF -- but what "problems" do
you see?

it is very hard to guess what you "want"
Rating: 3
Limitaions again October 07, 2003
Reviewer: A reader
Thanks Tom, for clearing the doubt on sequences.

I think im not able to explain properly, let me try to explain it again. We are
using java based applications on J2EE archictecture involving JSP, Struts, JS
etc as said earlier. We are using 9iAS as the application server and Oracle9i
RAC as the database.

In our application we are using OID authentication during user login which will
be using user_context and application_context to assign roles, roleid, userid,
siteinfo, siteid, locationinfo, locationid, machineinfo, clientip, osuser etc to
the user who has logged in whenever a connection is made. We wil be storing
these information at the application server level. These information plays a
major role in our application for inserting data which involves tracking who has
inserted what data (This is our major concern).

Now some of these information like sid, serial#, session information etc will be
instance specific which will all be lost in case a TAF occurs due to NODE
failure. So what i was looking for is some way in which we could trap the
message comming from the NODE which has gone down and using that message make
the application fire the transaction again from the begining (which involves
assigning user_context and application_context again) without user intervention
or may be we could continue from where the connection was lost. Im little
confused at the moment on how this can be done on RAC.

Or one other thought is to treat RAC as a single NODE machine and let the
application behave as it would have done in a single NODE machine. Forget TAF
and user RAC just for scalability and load balancing.

Therefore i was looking into what all other such limitations are there.

I hope im able to explain my problem this time...

Thanks and Regards.
Followup:
this isn't really a "limitation" -- it is just the "way it works".

are you using a connection pool?
Rating: 3
Connection Pooling is there... October 07, 2003
Reviewer: A reader
Yes we are using java connetion pooling.
Followup:
so, you cannot cache this information in the session -- you'll have to cache it
in the middle tier state.  Every single call will get a different sid/serial#
anyway (RAC or not).

This has nothing to do with RAC now -- you have to look at what connection
pooling is going to mean.  You are going to migrate from session to session
anyhow.  You will be "TAF'ing" in effect with each and every "get me a
connection" call in java.
Rating: 4
same as above October 07, 2003
Reviewer: A reader
We have come up with a possible solution, i would like ur comments on this.

What we are planning is to have a EJB Session bean at the application server
level which will be passing the user context details (excluding the sid, serial#
etc which won't be needed as we just need to know who is inserting what data) to
the connection pool every time a connection is requested.

Its going to be like this.

Through OID we get authenticated (thats the initial and one time job), go to
webtier which will capture the user informations like userid, ipaddress (can be
a false ip, if user is behind the firewall or proxy) and pass on this
information to the EJB session bean.

This EJB session Bean will pass through a number of java classes collecting rest
of the informations necessary other than the userid and ipaddress to build a
statement (like a select, insert, update or delete statement) based upon the
user input on the screens thrown from the webtier and store a thread id in a
hash table at application server which will be removed after a connection is
returned back to the pool.

This statement will then make a connection, can be a pool connection or normal
connection based on the data-source.xml file.

So what we have here is all the necessary information regarding the user details
to be inserted in the tables as well. So now it does not matter if a NODE goes
down as we will be sending all the information even before the connection is
picked from a connection pool and will be sending the info every time.

Earlier we were planning to use the global application context to store the user
information.

So what i wanted to confirm was, now that the information will be passing
everytime, so will TAF take place the same way it happens for a normal
client-server transaction?

Thanks and Regards
Followup:
not sure where any TAF concerns come into play in this entire scenario?  Yes,
TAF will just be TAF in this scenario.

(i can say it sounds like a ton of work just to run a query that could be done
really effectively in a stored procedure -- hint hint hint.  make sure you
haven't gotten enamored of a technology to oversolve a simple problem)
Rating: 5
:) October 07, 2003
Reviewer: A reader
Thanks Tom,

Actually we are trying to build this application as a product so that in case
the client has different database other than oracle we could use the same
application without much rework. You can say we are not exploiting the power of
oracle as using stored procedure would make it Oracle specific. So we are trying
to keep the business logic at application level as much as possible. The current
client of ours is using RAC and 9iAS. We are also going to test this on
different RDBMS other than Oracle. Lets see where we land up.

Thanks and Regards
Followup:
you'll land up spending all of your time trying to make the thing work -- thats
my 2cents worth.

You can either run really well on one

Or
Modestly OK on one and really bad on the rest.

I think the BEST way to achieve portability is to put ALL logic into stored
procedures using the BEST practices of each vendor.  Databases are not created
equal -- we have different (radically so, radically so) concurrency models,
locking, transaction semantics, FEATURES.

Good luck.
Rating: 4
Thanks i need that :) October 07, 2003
Reviewer: A reader
Thanks for you valuable suggestions. I'll definitely keep that in mind and see
how much we can implement that.

Regards
Rating: 5
Real Application Clusters October 08, 2003
Reviewer: S.Darshan from Singapore
Thanks for your support to provide useful technical informattion in this site.

I have refered metalink document id 175465.1 for RAC, URL as follows,
http://metalink.oracle.com/metalink/plsql/ml2_documents.showDocument?p_database_id=NOT&p_id=175465.1
"<quote>2.7 Installing a Volume Manager

It is now necessary to install volume management software. Sun Cluster v3
supports two products:-

Sun's Solaris Volume Manager software (Solstice DiskSuite software) or VERITAS
Volume Manager (VxVM) software v3.0.4+ (32-bit RAC), v3.1.1+ (64-bit RAC) is
needed to provide shared disk access and distributed mirroring.
Although Sun's Solstice DiskSuite (SDS), integrated into Solaris Operating
System (SPARC) 9 onwards as Solaris Volume Manager (SVM), is supported by Sun
Cluster v3, neither SDS or SVM supports cluster wide volume management, it is
only for per node basis. Hence these products cannot be used for RAC."<quote>

         My question is shall we use Sun's Solaris Volume Manager for RAC in two
node configuration?  What is the difference between the above two volume
manager? Can you pls give some documentation or link for Sun Clusters?

With best regards
S.Darshan
Followup:
you would have to evaluate sun vs veritas with sun and veritas.

either product is capable of serving your basic needs (that of RAC)

you will want to evaluate the products based on your specific needs/wants
however.
Rating: 5
Is 10g the new RAC ? October 09, 2003
Reviewer: Neil from UK
Would it be a good thing to get hold of 10g or implement RAC?
Followup:
well, 10g isn't out

9iRac is

sooooooo, I'd be getting 9i RAC right and 10g sometime next year.....

10g is the next release of Oracle.  RAC is an option of the 10g database.  10g
encompasses some 360 some odd new bits and pieces of functionality -- much of it
in the manageability arena
Rating: 5
November 18, 2003
Reviewer: A reader
Tom,

From RAC Concepts

<DOC>
The Global Cache and Global Enqueue Services maintain information about each
resource within a cluster. The GCS and GES nominate one instance to manage all
information about a particular resource. This instance is called the resource
master.
<DOC>

If i have 4 instances how can i determine which instance is the resource master.

Thanks.
Followup:
every machine is a resource master of some resources - the resources are spread
across all nodes.
Rating: 4
November 21, 2003
Reviewer: A reader
Tom,

Sorry for not understanding your answer, i am trying to understand this quote

"The GCS and GES nominate one instance to manage all
information about a particular resource. This instance is called the resource
master"

Do they mean that one instance will be " resource master"
for all the instances.

If yes then, if i wanna change the resource master to some other instance how
can i do that.

Thanks.
Followup:
a PARTICULAR resource

not

ALL resources
Rating: 5
RAC - How to setup November 29, 2003
Reviewer: Sudhir
Tom,

I am hoping you or some of your readers have some document on how to setup RAC
on windows2000 (one cpu) single machine.
My PC is Dell, 300MB RAM with w2k. I have 2 disks. I would very much like to
install RAC and see for myself how it works.

Thank you in advance for any help.
Followup:
you need two computers to run RAC

it is a clustering thing, you need more then one computer.
Rating: 5
Instead of sequences in RAC environment November 29, 2003
Reviewer: Sudhir
Tom,

Can you see effectiveness of using

instance id + TIMESTAMP(with 9 precision) as a sequence
number? I am mean what is the chance the two timestamps will match. And if they,
I am sure thats not the only thing in the PK, correct? You have account numbers
etc as well.

May be add a bit of random number at end to spice things up?

Thanks for your thoughts
Followup:
no, i would just use a sequence.
Rating: 5
RAC on single node November 30, 2003
Reviewer: Sudhir
Tom,

I am disappointed to know I have no way to do that for w2k. It seems like some
folks have done that with Linux
http://www.polarisdb.com/rac.htm
As usual thanks for the great information!
Followup:
you are not on linux and you have a machine that is somewhat underpowered to run
windows let alone RAC!

and even though you can "in theory" get rac going on a single linux box, I've
never ever seen the point.  You cannot really learn very much from it.
Rating: 4
Thanks for the question regarding "Real Application Clusters", version 8.1.6 December 09, 2003
Reviewer: Yemi Ayofe from Dallas, TX. USA
Good to know RAC can be technically implemented on one machine. Though as Tom
said, I do not see the point of it. I would think 3 is more like it. Two to hold
each instance, and the third your storage. i.e disks where oracle database is.
Rating: 5
RAC test cases needed! December 25, 2003
Reviewer: reader
Tom,

I was hoping you can help me setting up some test cases for RAC. I have RAC
working on RH linux. Now comes the hard part of actually doing something with
it.

I would like to know a small test harness (sql's) that you would run from
node(s) to see/understand what is really happening in the clustered environment.

For example, I hear sequences may/may not be a problem.
Even if you give me just ideas I will be very glad.

Thanks
Followup:
you have it backwards.

you build a test case to prove something.  an idea, a hypothesis....

you need to have a goal in mind.

you can't really just take a bunch of test cases and run them and gleen anything
from that.

I would just take a REAL application, something you already wrote -- that
exists, that is used.  Host it on the RAC database.  Test it.  See how the "real
thing" performs.  Then, if you find issues build test cases to test out the
ideas as to "why" the issue is happening.
Rating: 5
December 29, 2003
Reviewer: A reader
Tom,

Recently somebody asked me this question "How can you find out  the number of
nodes that are participating in a clustered environment"

My answer was using srvctl command, is this right? and/or are there are  any
other commands that we can use.

Thanks.
Followup:
SQL> show parameter cluster_database_instances
SQL> select * from gv$instance;

would be two that popped into mind first.
Rating: 5
RAC and DW December 29, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom,
Since hard parsing is unavoidable in DW and in RAC library cache operations are
coordinated among all nodes, would RAC be a good choice for DW?
Thanks...
Followup:
one of RAC's "sweet spots" is a DW.

hard parsing in a DW is not an issue -- you parse queries that are destined to
run for many seconds/minutes/hours.

you are not parsing a thousand queries per second here.
December 29, 2003 Praveen -- Thanks for the question regarding "Difference between cursor and a ref cursor", version 8.1.6
You Asked
Hi Tom,

      Feels good that i got a chance to ask you a question. I attended an
interview for oracle pl/sql. There he asked one question as what is the
difference between cursor and ref cursor, and when would you appropriately use
each of these. Could you please tell me how can I effectively answer this
question in short.

Your input will be greatly appreciated.

Thanks and regards,
Praveen.
and we said...
technically, under the covers, at the most "basic level", they are the same.

A "normal" plsql cursor is static in defintion.

Ref cursors may be dynamically opened or opened based on logic.

Declare
   type rc is ref cursor;

   cursor c is select * from dual;

   l_cursor rc;
begin
   if ( to_char(sysdate,'dd') = 30 ) then
     open l_cursor for 'select * from emp';
   elsif ( to_char(sysdate,'dd') = 29 ) then
     open l_cursor for select * from dept;
   else
     open l_cursor for select * from dual;
   end if;
   open c;
end;
/

Given that block of code -- you see perhaps the most "salient" difference -- no
matter how many times you run that block -- cursor C will always be select *
from dual.  The ref cursor can be anything.

Another difference is a ref cursor can be returned to a client.  a plsql "cursor
cursor" cannot be returned to a client.

Another difference is a cursor can be global -- a ref cursor cannot (you cannot
define them OUTSIDE of a procedure / function)

Another difference is a ref cursor can be passed from subroutine to subroutine
-- a cursor cannot be.

Another difference is that static sql (not using a ref cursor) is much more
efficient then using ref cursors and that use of ref cursors should be limited
to
- returning result sets to clients
- when there is NO other efficient/effective means of achieving the goal

that is, you want to use static SQL (with implicit cursors really) first and use
a ref cursor only when you absolutely have to

Then sit back and say "anything else you wanted to know about them"
Review & Followup

Rating: 5
&#1705;&#1740;&#1575; &#1576;&#1575; &#1578; &#1726;&#1746;&#1748; December 06, 2003
Reviewer: میاں محمد احمد from Pakistan
Good Explain....

Tom last week I saw a review on this site in which writer name with unknown
language to me. Here I code the name and title of review in my mother language
"URDU-Pakistan" do you see it and most important if yes, that How I configure
oracle forms and reports for this.

Thanks
Followup:
I see escaped html entities in the title and something that looks like it might
be in your language for the name

so the title is like

&#1705;&#1740;&#1575; &#1576;&#1575; &#1578; &#1726;&#1746;&#1748;

you set your NLS_LANG on the client for forms and reports -- contact support for
total assistance on that, i don't really work with foreign charactersets
personally and don't have forms/reports around for testing.
Rating: 4
Thanks December 07, 2003
Reviewer: Praveen from India
Thanks tom for that great answer with an example. This will be really helpful.
Rating: 5
Terrific explanation December 08, 2003
Reviewer: DD from Chennai, India
that was a great explanation. You are simply the best .. :)
Rating: 5
Tom the Great December 08, 2003
Reviewer: Manish Upadhyay from Madison WI
I wanted to know the same difference and your explanation is what I needed.
Thanks a lot for being there and help so many people.
Rating: 5
Excellent explanation. December 08, 2003
Reviewer: Dilip Patel from Maryland, USA
Even though I knew before what pl/sql cursor is and what ref cursor is, your
explanation really completes my understanding on how they differentiate.

Thanks Much.
Rating: 4
Absolutely understandable.... December 11, 2003
Reviewer: Biswadip Seth from India
thanks,,,
your respose of this question is absolutely easy to understandable.even any
begginer's to the oracle can make out this article...
thanks a lot
Rating: 2
NLS Lang December 29, 2003
Reviewer: Arabic Masood Sultan Ali Betheri from KSA
You can use the NLS_LANG environment variable to customise the forms and
reports/sql plus on your platform. I have done it in Arabic. It really works!
Rating: 2
December 29, 2003
Reviewer: A reader
Rating: 4
Can U see this December 29, 2003
Reviewer: A reader
ÉãÅbàøÞ¼í ØâMV ØíxÞV ¦µÃæÎCßW

ÈÞÏµÈ¿zÞøßW ÆßÜàÉßÈá çÖ×ÎáU ÄÜÎáùÏßW ¯xÕáÎÇßµ¢ ÄÞøÎâÜc¢ §çMÞZ ÉãÅbàøÞ¼ßÈÞÃí.
ØßÈßÎÞ dÉÕVJµV §GáæµÞ¿áJ çùxÞÃí. µÝßEÕV×¢ ø¢·æJJßÏ ÉãÅbßÏáæ¿ ædµÁßxßW §çMÞZ
ºßdÄBZ

Can you interpret the above?
Followup:
nope, just a bunch of characters -- none of them ascii.
Rating: 5
December 29, 2003
Reviewer: A reader
Is that Russian ??
Rating: 5
$%^&* does not follow December 29, 2003
Reviewer: Harrison from Haymarket, "Like Detroit, without the glitter" VA, USA
I learned at least two things, a better grasp of when to use reference cursors,
and how to push unrelated questions onto the stack.
December 29, 2003 Kamal -- Thanks for the question regarding "Bind Variable values available to Optimizer in Oracle9i", version 9.2.0.3
You Asked
Hi Tom,
I read in the book "Oracle SQL Tuning - Pocket Reference" by Mark Gurry
(O'Reilly) on page 4, that:

<quote>
"Oracle 9i determines the value of the bind variable prior to deciding on the
execution plan."
</quote>

In this paragraph, he is talking about the "skewness problem" and ways to
overcome that. At that point he mentions that Oracle9i will get rid of the
"skewess" without using constant literals (you can safely use bind variables).

Again, On page 29, he mentions that:

<quote>
"Oracle9i will evaluate the bind variable value prior to deciding the execution
plan, obviating the need to hard-code literal values."
</quote>

My problem in understanding this is that if Oracle9i uses the value of bind
variable prior to deciding the execution plan, than for each new value of the
bind varaible, it will have to redo the execution plan, thus using bind variable
will be of no use, since the database will have to redo the execution plan for
each new value anyway.

Is this not the same thing as NOT using bind variables at all and always passing
in the unique queries to the database?

Maybe, I'm missing something that is not explained properly in his book? Maybe
this is not the whole story and there is more to it than that?

But If Oracle9i does that, Is this available via the DBMS_SQL package?
My understanding is that PARSE (and execution plan generation) happens much
before the binding. At PARSE time how will the optimizer get the actual values
of the bind variables that the program will use later.

As I understand this, we do a DBMS_SQL.PARSE call where the query plan is
decided and then we do a DBMS_SQL.BIND_VARIABLE to bind the values of the bind
variables used in our Query. In this case, it will already have the plan
decided, even before the values of the bind variables have been supplied. Does
this mean, than, that this new feature of Oracle9i is NOT accessible yet via the
DBMS_SQL package?

How would you code your application in order to make use of this new feature?
1). When using DBMS_SQL package?
2). When not using this package, in static and native dynamic SQL in PL/SQL?

Let me know if you need further information.
Thanks,
and we said...
He is discussing bind variable peeking, but if that is all he says, he has
missed 90% of the discussion.

see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7832114438832#9915391826680
Oracle will peek at the bind variable value *before the first hard parse* and
use that value in order to optimize the query plan.

Oracle will not peek at the bind variables during soft parses.  As demonstrated
in the examples in that link
Review & Followup

Rating: 5
Excellent... June 09, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
That discussion in the link was excellent. Explained the concept in a concise
and clear manner.
The question that now pops up in my mind is:
Will Oracle9i do this "bind variable peeking" ALWAYS first time for a hard parse
or will it do it only when it sees that the data might be skewed?

If it does it always, will it pose a problem for our queries when we migrate to
Oracle9i (soon)?
The concern that I have is if the first hard parse decides the execution plan
and it is cached, the subsequent executions for other data values may be
terrible?

Is this a problem that we need to address?
If yes, what we could (or rather should) do to get around this issue?
Thanks,
Followup:
it happens with the CBO during a hard parse.

As for the 9i migration -- you have that right now, without bind variable
peeking.  Consider the one example proposed in the link with "like".  without
bind variable peeking, it would never consider the index see, with it, it will.
It goes both ways.

But -- test test test -- that is something you need to do upon an upgrade....
Rating: 5
What if compatible = 8.1.7 is set for the Oracle9i development server... June 09, 2003
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
I just checked and found out that the development server that was migrated to
Oracle9i some time ago (in the process to migrate the production, some day), has
the compatible parameter set to 8.1.7.
I'm assuming that this bind variable peeking will not be available when this
parameter is set to this value?
Is this correct?

Do you see any problems with this setup? Do you suggest that the compatible
parameter should ideally be set to something like 9.0 or 9.2?
What would be your choice in this case?

The argument is that since production is still 8.1.7, we should set the
development server to compatible = 8.1.7 so as to avoid any problems during
migration.

Is this the correct way to approach a migration from Oracle8i to Oracle9i?
Any suggestions? Are we going in the right direction?
Thanks,
Followup:
compatible only affects "disk based structures" really -- it makes it so that if
we come up with some new format for persisting data -- we won't use it (cause
you cannot downgrade if we did).

features like bind variable peeking will work.

you should have the same setting on dev/test/prod.  Next upgrade cycle -- bump
it up.
Rating: 4
Does bv peeking affect Card estimates? July 28, 2003
Reviewer: Dr. Donald K. Demetrious from New York, NY USA
Shouldn't bind variable peeking affect the Card estimates on the execution plan?
In the following example, why is Card=1526 instead of Card=5?
From Oracle 9.2, with optimizer_features_enable set to 9.2.0:

SQL> create table test_cols as select owner, table_name, column_name from
dba_tab_columns;
Table created.
SQL> create index test_cols_n01 on test_cols (column_name);
Index created.
SQL> analyze table test_cols compute statistics;
Table analyzed.
SQL> set autotrace traceonly explain
SQL> select count(*) from test_cols where column_name like 'CHA%';

Execution Plan
----------------------------------------------------------
SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=10)
  SORT (AGGREGATE)
    INDEX (RANGE SCAN) OF 'TEST_COLS_N01' (NON-UNIQUE) (Cost=2 Card=5 Bytes=50)

SQL> variable p_test varchar2(30);
SQL> begin :p_test := 'CHA%'; end;
  2  /

PL/SQL procedure successfully completed.

SQL> select count(*) from test_cols where column_name like :p_test;

Execution Plan
----------------------------------------------------------
SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=10)
  SORT (AGGREGATE)
    INDEX (RANGE SCAN) OF 'TEST_COLS_N01' (NON-UNIQUE) (Cost=2 Card=1526
Bytes=15260)
Followup:
explain plan can lie, especially with bind variable peeking.

so, this is a little view I explain in my next book due out next month -- it is
useful in 9i especially 9ir2 with dbms_xplan:

ops$tkyte@ORA920> create or replace view dynamic_plan_table
  2  as
  3  select
  4   rawtohex(address) || '_' || child_number statement_id,
  5   sysdate timestamp, operation, options, object_node,
  6   object_owner, object_name, 0 object_instance,
  7   optimizer,  search_columns, id, parent_id, position,
  8   cost, cardinality, bytes, other_tag, partition_start,
  9   partition_stop, partition_id, other, distribution,
 10   cpu_cost, io_cost, temp_space, access_predicates,
 11   filter_predicates
 12   from v$sql_plan;

View created.

now, we'll run your simulation:

ops$tkyte@ORA920> drop table t;
Table dropped.

ops$tkyte@ORA920> create table t as select owner, table_name, column_name from
dba_tab_columns;
Table created.

ops$tkyte@ORA920> create index t_idx on t(column_name);
Index created.

ops$tkyte@ORA920> analyze table t compute statistics;
Table analyzed.

ops$tkyte@ORA920> set autotrace on explain
ops$tkyte@ORA920> select count(*) from t where column_name like 'CHA%';

  COUNT(*)
----------
       215

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=10 Card=1 Bytes=10)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=10 Card=2624
Bytes=26240)

so the estimated cardinality in the plan was 2,624 -- but using a bind:

ops$tkyte@ORA920> variable p_test varchar2(30)
ops$tkyte@ORA920> exec :p_test := 'CHA%'

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select /* LOOK FOR ME!!! */ count(*) from t where column_name
like :p_test;

  COUNT(*)
----------
       215

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=10)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=3 Card=1742
Bytes=17420)

it appears to be 1,742, but looking at the REAL plan used:

ops$tkyte@ORA920> set autotrace off
ops$tkyte@ORA920>
ops$tkyte@ORA920> select plan_table_output
  2    from TABLE( dbms_xplan.display
  3                ( 'dynamic_plan_table',
  4                  (select rawtohex(address)||'_'||child_number x
  5                     from v$sql
  6  where sql_text like 'select /* LOOK FOR ME!!! */ count(*) from t where
column_name like :p_test%' ),
  7                  'serial' ) )
  8  /

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
--------------------------------------------------

-------------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost (%CPU)|
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             |       |       |            |
|   1 |  SORT AGGREGATE      |             |     1 |    10 |            |
|*  2 |   INDEX RANGE SCAN   | T_IDX       |  2624 | 26240 |    10  (10)|
-------------------------------------------------------------------------

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

   2 - access("COLUMN_NAME" LIKE :P_TEST)
       filter("COLUMN_NAME" LIKE :P_TEST)

14 rows selected.

we see the 2,624 again.  explain plan doesn't "see" the peeked binds
Rating: 5
bind variable October 16, 2003
Reviewer: Pushparaj Arulappan from NJ, USA
Tom,

I have a SQL which is running very fast from the SQL PROMPT
but if I put the same SQL in a PL/SQL block, it is running
very very slow.

SQL> var var1 char(10);
SQL> var var2 char(7);
SQL> exec :var1 := '1234567890';
SQL> exec :var2 := '1234567';

SQL> SELECT ....
     ....
     FROM tab1 a,
          tab2 b
          ( SELECT ...
            FROM tab1 x,
                 tab3 y
            WHERE x.col1 = :var1
            AND   x.col2 = :var2
            AND   x.col1 = y.col1
            AND   x.col2 = y.col2
            ....
          ) zz
     WHERE a.col1 = :var1
     AND   a.col2 = :var2
     AND   a.col1 = b.col1
     AND   a.col2 = b.col2
     AND   zz.col1 = a.col1
     AND   zz.col2 = a.col2
     .....

This executes very fast...

But If I convert this into a PL/SQL program..

SQL>
DECLARE
   var1 CHAR(10);
   var2 CHAR(7);

   CURSOR C1 IS
      SELECT ....
             ....
      FROM tab1 a,
           tab2 b
           (SELECT ...
            FROM tab1 x,
                 tab3 y
            WHERE x.col1 = var1
            AND   x.col2 = var2
            AND   x.col1 = y.col1
            AND   x.col2 = y.col2
            ....
            ) zz
      WHERE a.col1 = var1
      AND   a.col2 = var2
      AND   a.col1 = b.col1
      AND   a.col2 = b.col2
      AND   zz.col1 = a.col1
      AND   zz.col2 = a.col2
      .....

BEGIN
   var1 := '1234567890';
   var2 := '12345678';

   FOR rec IN c1 LOOP
     ....
   END LOOP;
END;
/

This is running for ever and ever and I have to kill it..

I also want to tell you that the same works fine in SQL prompt and also in
PL/SQL if the WHERE clause value is different which will match less rows and
retrieve very less rows.

This is only a concern for the WHERE class values
which will join many rows and retrieve many rows.

What I should do to this SQL to perform the same way
in a PL/SQL block as it does in the SQL prompt for the WHERE
clause values that will match and retrieve many rows.

Thanks
Pushparaj
Followup:
where be the tkprof reports that would show us the different in execution
performance and query plans used?
Rating: 5
trace out October 16, 2003
Reviewer: Pushparaj Arulappan from NJ, USA
Tom,

The following is the trace output when I run it from the
SQL prompt. The SELECT columns are more, hence I only
put the FROM and WHERE clause of the original query here.

    FROM    TTSTCPK n,TTKLMTA x
    WHERE   x.nbn_num= :pNbnNum
    and     x.sgp_num= :pSgpNum
    and     n.nbn_num=x.nbn_num
    and     n.sgp_num=x.sgp_num
    and     n.pspl_num=x.pspl_num
    and     n.psps_num=x.psps_num
    GROUP BY x.nbn_num,
             x.sgp_num,
             x.pspl_num,
             x.psps_num) t
    WHERE   a.nbn_num        =   :pNbnNum
    and     a.sgp_num        =   :pSgpNum
    and     a.nbn_num        =   b.nbn_num
    and     a.sgp_num        =   b.sgp_num
    and     a.pspl_num =   b.pspl_num
    and     b.nbn_num        =   t.nbn_num
    and     b.sgp_num        =   t.sgp_num
    and     b.pspl_num =   t.pspl_num
    and     b.psps_num    =   t.psps_num

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch      111     18.68      18.27          0     227237          0        1643
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      113     18.68      18.27          0     227237          0        1643
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 28  (SALES1)

Rows     Row Source Operation
-------  ---------------------------------------------------
   1643  SORT GROUP BY (cr=227237 r=0 w=0 time=18259319 us)
   4929   NESTED LOOPS  (cr=227237 r=0 w=0 time=18152455 us)
   4929    NESTED LOOPS  (cr=123726 r=0 w=0 time=11201943 us)
   4929     NESTED LOOPS  (cr=108937 r=0 w=0 time=11096958 us)
   4929      TABLE ACCESS BY INDEX ROWID TTKLMTA (cr=497 r=0 w=0 time=22826 us)
   4929       INDEX RANGE SCAN IND_TTKLMTA (cr=60 r=0 w=0 time=5600 us)(object
id 391566)
   4929      TABLE ACCESS BY INDEX ROWID TTSTCPK (cr=108440 r=0 w=0
time=11051602 us)
   4929       INDEX RANGE SCAN IND_TTSTCPK (cr=103511 r=0 w=0 time=11016616
us)(object id 391563)
   4929     TABLE ACCESS BY INDEX ROWID TTCOLFP (cr=14789 r=0 w=0 time=84542 us)
   4929      INDEX RANGE SCAN IND_TTCOLFP (cr=9860 r=0 w=0 time=53351 us)(object
id 195300)
   4929    INDEX RANGE SCAN IND_TTSTCPK (cr=103511 r=0 w=0 time=6933006
us)(object id 391563)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
   1643   SORT (GROUP BY)
   4929    TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF 'TTSTCPK'
   4929     NESTED LOOPS
   4929      MERGE JOIN (CARTESIAN)
   4929       NESTED LOOPS
   4929        TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF
                   'TTKLMTA'
   4929         INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'IND_TTKLMTA'
                    (UNIQUE)
   4929        INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'IND_TTSTCPK'
                   (UNIQUE)
   4929       BUFFER (SORT)
   4929        TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF
                   'TTCOLFP'
   4929         INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'IND_TTCOLFP'
                    (UNIQUE)
      0      INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'IND_TTSTCPK' (UNIQUE)

********************************************************************************

But Tom, the PL/SQL program is still running since morning and it is not
complete yet and I will provide whatever so far in the trace file and you can
see the change in the execution plan already.

FROM    TTSTCPK n,TTKLMTA x
    WHERE   x.nbn_num= :b2
    and     x.sgp_num= :b1
    and     n.nbn_num=x.nbn_num
    and     n.sgp_num=x.sgp_num
    and     n.pspl_num=x.pspl_num
    and     n.psps_num=x.psps_num
    GROUP BY x.nbn_num,
             x.sgp_num,
             x.pspl_num,
             x.psps_num) t
    WHERE   a.nbn_num        =   :b2
    and     a.sgp_num        =   :b1
    and     a.nbn_num        =   b.nbn_num
    and     a.sgp_num        =   b.sgp_num
    and     a.pspl_num =   b.pspl_num
    and     b.nbn_num        =   t.nbn_num
    and     b.sgp_num        =   t.sgp_num
    and     b.pspl_num =   t.pspl_num
    and     b.psps_num    =   t.psps_num

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      1      0.02       0.01          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.02       0.02          0          0          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 28  (SALES1)   (recursive depth: 1)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   SORT (GROUP BY)
      0    TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF 'TTKLMTA'
      0     NESTED LOOPS
      0      NESTED LOOPS
      0       NESTED LOOPS
      0        TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF
                   'TTCOLFP'
      0         INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'IND_TTCOLFP'
                    (UNIQUE)
      0        TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF
                   'TTSTCPK'
      0         INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'IND_TTSTCPK'
                    (UNIQUE)
      0       INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'IND_TTSTCPK'
                  (UNIQUE)
      0      INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'IND_TTKLMTA' (UNIQUE)

********************************************************************************

Now, I ran the same PL/SQL program from another session
but with the different WHERE clause value which returns
joins very less rows and returns very less rows and the following is the trace
output for that.

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        6      0.00       0.00          0        109          0           5
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        8      0.00       0.00          0        109          0           5

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 28  (SALES1)   (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
      5  SORT GROUP BY (cr=109 r=0 w=0 time=1606 us)
     15   FILTER  (cr=109 r=0 w=0 time=1205 us)
     15    TABLE ACCESS BY INDEX ROWID TTKLMTA (cr=109 r=0 w=0 time=1174 us)
     41     NESTED LOOPS  (cr=107 r=0 w=0 time=1090 us)
     25      NESTED LOOPS  (cr=30 r=0 w=0 time=430 us)
      5       MERGE JOIN CARTESIAN (cr=8 r=0 w=0 time=219 us)
      1        TABLE ACCESS BY INDEX ROWID TTCOLFP (cr=4 r=0 w=0 time=73 us)
      1         INDEX RANGE SCAN IND_TTCOLFP (cr=3 r=0 w=0 time=47 us)(object id
195300)
      5        BUFFER SORT (cr=4 r=0 w=0 time=106 us)
      5         INDEX RANGE SCAN IND_TTSTCPK (cr=4 r=0 w=0 time=47 us)(object id
391563)
     25       TABLE ACCESS BY INDEX ROWID TTSTCPK (cr=22 r=0 w=0 time=176 us)
     25        INDEX RANGE SCAN IND_TTSTCPK (cr=17 r=0 w=0 time=96 us)(object id
391563)
     15      INDEX RANGE SCAN IND_TTKLMTA (cr=77 r=0 w=0 time=546 us)(object id
391566)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      5   SORT (GROUP BY)
     15    TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF 'TTKLMTA'
     15     NESTED LOOPS
     41      NESTED LOOPS
     25       NESTED LOOPS
      5        TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF
                   'TTCOLFP'
      1         INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'IND_TTCOLFP'
                    (UNIQUE)
      1        TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF
                   'TTSTCPK'
      5         INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'IND_TTSTCPK'
                    (UNIQUE)
      5       INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'IND_TTSTCPK'
                  (UNIQUE)
     25      INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'IND_TTKLMTA' (UNIQUE)
Followup:
what do you mean "the select columns are more"
Rating: 5
more October 16, 2003
Reviewer: A reader
Tom,

The list of columns in the SELECT is more and hence
instead of pasting the entire query as it will look very large query, I just
avoided that portion of the query here.

Thanks
Pushparaj
Followup:
so, the select lists were IDENTICAL right?
Rating: 4
SELECT LIST October 17, 2003
Reviewer: Pushparaj Arulappan from NJ, USA
Tom,
The SELECT list is exactly the same and
absoulyely no difference.

Thanks
Pushparaj
Followup:
what command exactly do you use to analyze all of the tables and are the
statistics all up to date?
Rating: 4
October 17, 2003
Reviewer: Pushparaj Arulappan from NJ, USA
Tom,
We use COMPUTE STATISTICS to collect the staticts
for all our tables and indexes.

SQL> select table_name,num_rows,last_analyzed from dba_tables where table_name
in ('TTSTCPK','TTKLMTA','TTCOLFP');

TABLE_NAME   NUM_ROWS  LAST_ANALYZED
------------ --------  -----------
TTCOLFP      4250390   2003-10-11.08.00.36
TTSTCPK      3807971   2003-10-12.16.13.05
TTKLMTA      1423913   2003-10-12.15.46.45

I have checked the indexes also and they all have statistics.

The above three are the tables joined in this query...

The following are the row counts for the bind variable
values used in the WHERE cluase and which is taking very long time.

TTCOLFP = 1
TTSTCPK = 1643
TTKLMTA = 9381

The following are the row counts for the bind variable
values used in the WHERE cluase and which executes in no
time.

TTCOLFP = 1
TTSTCPK = 5
TTKLMTA = 15

The query goes faster for fewer counts but for the large counts it takes hours.

Thanks
Pushparaj
Followup:
*exact* command please.  there are hundreds of possible ways to compute
statistics.
Rating: 5
analyze October 20, 2003
Reviewer: Pushparaj Arulappan from NJ, USA
Tom,

We use the following analyze command for collecting
statistics for tables and the indexes. We run this every
week during the weekend.

ANALYZE TABLE ttcolfp COMPUTE STATISTICS;
ANALYZE TABLE ttstcpk COMPUTE STATISTICS;
ANALYZE TABLE ttklmta COMPUTE STATISTICS;

Thanks for your help.
Pushparaj
Followup:
I've a feeling it is a bind variable "peeking" issue.

Can you try this

a) flush your shared pool
b) run the plsql with the BAD inputs first (with the values that make it run
slow)

what I think might be happening is -- you run it with inputs that should use a
merge join cartesian (you have histograms on those tables) and that plan
"sticks".  You try in sqlplus with "bad" values and get a different plan.

It could be that you don't want histograms in this particular case (compute
statistics takes the MOST resources -- you might consider using just for table
for all indexes -- and maybe for "some columns"

Here is a for example of bind variable peeking -- SAME query more or less
(differs only in the correlation name in the query) but run with different
starting inputs:

ops$tkyte@ORA920> create table t
  2  as
  3  select 1000 id, all_objects.* from all_objects;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t
  2  select 1 id, all_objects.* from all_objects where rownum = 1;

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create index t_idx on t(id);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table t compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;

Table analyzed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> alter system flush shared_pool;

System altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> variable x number
ops$tkyte@ORA920> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA920> set autotrace traceonly statistics
ops$tkyte@ORA920> exec :x := 1000

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from t x_1000 where id = :x;

30189 rows selected.

Statistics
----------------------------------------------------------
        593  recursive calls
          0  db block gets
       2530  consistent gets
        223  physical reads
          0  redo size
    2155534  bytes sent via SQL*Net to client
      22631  bytes received via SQL*Net from client
       2014  SQL*Net roundtrips to/from client
          7  sorts (memory)
          0  sorts (disk)
      30189  rows processed

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec :x := 1

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from t x_1 where id = :x;

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

ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace off

tkprof shows us:

select * from t x_1000 where id = :x

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     2014      0.34       0.36        223       2440          0       30189
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2016      0.35       0.37        223       2440          0       30189

Rows     Row Source Operation
-------  ---------------------------------------------------
  30189  TABLE ACCESS FULL T (cr=2440 r=223 w=0 time=193232 us)
********************************************************************************
select * from t x_1 where id = :x

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          1          4          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          1          4          0           1

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID T (cr=4 r=1 w=0 time=254 us)
      1   INDEX RANGE SCAN T_IDX (cr=3 r=1 w=0 time=214 us)(object id 41314)

different plans -- that will stay with that query until the shared pool is
flushed/it ages out
Rating: 5
Can we flush just one query plan from shared pool? December 29, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom,
If a bad execution plan gets into the shared pool because of bind variable
peeking, what are the ways to get rid of it? The query is heavily used and
chances of it's getting flushed out of shared pool are low. Is flushing the
entire shared pool our only option here?

Thanks.
Followup:
analyze the table, grant/revoke on the table (eg: grant select on it to scott,
revoke select on it from scott)
December 29, 2003 -- Thanks for the question regarding "How Can i get the peak concurrent session", version 8.1.5
You Asked
Tom:

     I want to get the maximun number of concurrent sessions during a week.
Which is the best(easier) way to do it.

Regards
Pablo
and we said...
select * From v$license;

session_highwater will tell you that.
Review & Followup

Rating: 5
session_highwater March 10, 2003
Reviewer: Mark A. Williams from Indianapolis, IN USA
Since session_highwater is the max. sessions since the instance *started*,
you'll want to make sure to do an unattended shutdown every Sunday night to
reset the start time, and that way you'll get weekly data.  That was not
serious, of course.  :)  However, that is a caveat - you're looking at data
since the instance started.

v$license is documented here:
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96536/ch393.htm#1116791
Followup:
ahh -- good point -- don't shutdown though but you are right, it is since the
instance is up.
Rating: 4
March 10, 2003
Reviewer: A reader
Ok, but the problem is that we run cold backup every night, i know that is not
ok do cold backup every day, but anyway i think that the v$license values will
reset every time we shutdown the database,Won't they?. What we want to obtain is
a meassure of concurrent license we'll need to buy. Do we have to pay for all
concurrent session or only for the concurrent "ACTIVE" session?

Thanks in Advance
Followup:
you'll have to snapshot this view every night then.

shutting down is horrible.

You'll have to talk to your sales guy about licensing issues.  normally it is by
the CPU.
Rating: 5
Not shutting down... March 10, 2003
Reviewer: Mark A. Williams from Indianapolis, IN USA
I was being tongue in cheek about the shutting down.  That's why I had hidden in
there "That was not serious, of course.  :)"  I know you love unattended
shutdowns about as much as not using bind variables...

- Mark
Rating: 3
Another way March 10, 2003
Reviewer: Mike Madland from Bellevue, WA USA
Here's another way to do it that will give you concurrent users based on the
time of logon/logoff (if logged on for at least a minute):

SQL> audit session;

Audit succeeded.

... wait for a week ...

SQL> define week_start=2003-03-09
SQL>
SQL> create table week_minutes (clock date primary key)
  2  organization index
  3  /

Table created.

SQL> begin
  2     for i in 1..10080 loop
  3         insert into week_minutes (clock)
  4         values (to_date('&week_start','yyyy-mm-dd') + (i * (1/(24*60))));
  5     end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

SQL>
SQL> SELECT max(concurrent_users)
  2    FROM (select clock, COUNT(*) concurrent_users
  3            FROM dba_audit_session, week_minutes
  4           WHERE clock between TIMESTAMP and logoff_time
  5             AND timestamp between to_date('&week_start','yyyy-mm-dd')
  6             and to_date('&week_start','yyyy-mm-dd') + 7 - (1/(24*60*60))
  7             and logoff_time - timestamp > (1/(24*60))
  8           GROUP BY clock)
  9  /

MAX(CONCURRENT_USERS)
---------------------
                   17
Rating: 4
Concurrent licenses December 29, 2003
Reviewer: John from Detroit, MI
Is the session_highwater actually concurrent users or just the maximum number of
users that logged into the database at different times?
Followup:
its the highwater for concurrent sessions -- they might all have been "idle" --
that is by definition "concurrent users"...
Rating: 4
One more way of knowing HWM of sessions after instance shutdown December 30, 2003
Reviewer: Manish Upadhyay from Madison , WI , USA
When you shutdown the oracle database, Alertlog is written with the HWM session
information.The entry look like this.

"License high water mark = 455"

So you can find out "HWM for sessions" even after the instance shutdown as long
as you have alertlog for that instance.
December 29, 2003 Rahul -- Thanks for the question regarding "Few Questions !!!", version 9.0.1
You Asked
Hi Tom,

1.How can i kill a user session using execute immediate..

2.This is the question I have am not sure about the answer

The Dynamic SGA feature allows the SGA  to grow and shrink dynamically according
to an ALTER SYSTEM command.
This avoids the previous need of shutting down the instance in order to modify
the components of the SGA, namely the buffer cache and shared pool components.
Which three statements are true for the Dynamic SGA feature.(choose three)
A.The maximum granule size is 4MB
B.The minimum SGA configuration is three granules
C.SGA memory  is based on granules by SGA components
D.The size of the SGA components is set by the SGA_MAX_SIZE parameter
E.The size of the SGA components is limited  by the setting of SGA_MAX_SIZE
parameter.

Thanks..
and we said...
1) see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:226613885088
and just use

  execute immediate 'alter system ....';

instead of dbms_sql

2) I would look it up in the documentation.  This is why I despise these sorts
of tests.  why why why in the world would anyone have a need to walk about with
this sort of "super specialized trivia" in their head???

So, I goto my handy dandy Admin guide:
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96521/create.htm#998095
and find:

<quote>
Setting Initialization Parameters that Affect the Size of the SGA

The initialization parameters discussed in this section affect the amount of
memory that is allocated to the System Global Area. Except for the SGA_MAX_SIZE
initialization parameter, they are dynamic parameters which values can be
changed by the ALTER SYSTEM statement. The size of the SGA is dynamic, and can
grow or shrink by dynamically altering these parameters.
Note:

The memory for dynamic components in the SGA is allocated in the unit of
granules. Granule size is determined by total SGA size. Generally speaking, on
most platforms, if the total SGA size is equal to or less than 128 MB, then
granule size is 4 MB. Otherwise, granule size is 16 MB.

However, there may be some platform dependency. For example, on 32-bit Windows
NT, the granule size is 8 MB for SGAs larger than 128 MB. Consult your operating
system specific documentation for more details.

You can query the V$SGA_DYNAMIC_COMPONENTS view to see the granule size that is
being used by an instance. The same granule size is used for all dynamic
components in the SGA.

If you specify a size for a component that is not a multiple of granule size,
Oracle will round the specified size up to the nearest multiple. For example, if
the granule size is 4 MB and you specify DB_CACHE_SIZE as 10 MB, you will
actually be allocated 12 MB.
</quote>

So,
a) is false (... otherwise granule size is 16mb)

b) is false.  sounds right (shared pool, large pool, buffer cache) BUT hey wait
-- isn't there more to an SGA then that?  (rhetorical question, of course there
is)

c) is false.  sounds right but it is not 100% right is it.  "The memory for
dynamic components in the SGA is allocated in the unit of granules" hmmm, maybe
it is, maybe it isn't (think "fixed size" component of the SGA for example, what
is yours?  mine sure isn't anywhere near 4m)

d) is false sort of, is true sort of.  We'll let this one slide.

e) is false.

I'll GUESS they wanted to hear:

b, c, d....  but b and c technically aren't right and I could argue d if I
really wanted to (because it is only telling PART OF the story -- eg: if
sga_max_size is X, what is the max size of the buffer cache -- is it X? (no, it
is some number LESS THEN x).  therefore sga_max_size is not the ONLY thing that
limits the size of the SGA components)
Review & Followup

Rating: 4
October 12, 2002
Reviewer: A reader
I am preparing for my 9i upgrade test, and this question was there in practice
tests..wasn't sure what they meant by that..Anyways thanks for your answer..
Rating: 5
great explanation September 07, 2003
Reviewer: Yakgna
Rating: 5
EXECUTE IMMEDIATE limits December 29, 2003
Reviewer: Chow from Boston, MA
Hello Tom,

I am creating a package using the EXECUTE IMMEDIATE command. The package body is
dynamically generated based on the user input. Occasionally, the package length
is greater than 32K chars and I am not able to use a VARCHAR/CLOB buffer with
the EXECUTE IMMEDIATE command. Before I break the package into multiple smaller
packages, are there any alternatives I should consider ?

Thanks,
-Chow.
Followup:
you need to use dbms_sql.

it'll let you parse a sql statement that is stored as elements in a plsql table.

it can be very very large.
December 30, 2003 ted -- Thanks for the question regarding "rman question", version 8.1.6
You Asked
thomas, happy holiday. I have question(s) related to rman

1. what is the difference between -
   to back up the current control file and
   to backup up control file copy(backup controlfile copy)

2. when using either of above command tag and file name
   can be specified. This is the same as hardcode the file name.
   Is there a way I can get format tag such as %u %p etc to work
   with backuped control file name.

3. when using sbt_tape. how do i finding out whether it is
working. when I trying to do it with veritas3.4(hard link with
oracle). rman just sit there and no i/o was observed.

thnx again
and we said...
for #1,

backup current controlfile creates a BACKUPSET containing
controlfile. You don't have to give the FILENAME

backup controlfile copy <filename> creates a BACKUPSET from a
copy of controlfile. You have to give the FILENAME.

If you backup "current control file" you backup control file
which is currently open by an instance.

If you backup "controlfile file copy" you backup the copy of
control file which is created either with SVRMGRL command "alter
system backup controlfile to .." or with RMAN command "copy
current controlfile to ...". In the other words, the control
file copy is not current controlfile

for #2,

FILENAME is only for 'backup controlfile copy'. TAG is used for
BACKUPSET. You could say

run {
allocate channel foo type sbt;
backup controlfile copy '/tmp/cf1.f' tag
                      CF21NOV format 'cf_%u_%p.bkp';
}

see
http://technet.oracle.com/doc/oracle8i_816/server.816/a76990/rmansyn6.htm#67367
for all formats and syntax.

For #3, I would suggest you to follow troubleshooting procedure
from RMAN manuals:
http://technet.oracle.com/doc/oracle8i_816/server.816/a76990/troubler.htm#440915
and check "Backup Job Is Hanging" section.
As the manuals stated, the hanging could be that "RMAN is
waiting for an event such as the insertion of a new cassette
into the tape device.".
Review & Followup

Rating: 4
August 12, 2002
Reviewer: A reader
Tom, I have seen that our RMAN backup in quite intrusive - means the query
performance on the database deteriorates considerably during the RMAN backup. Is
this normal? What can I do to find out more about the bottlenecks? Any
suggestions on how to improve it? Thaks.
Followup:
It this in comparision to doing an "old fashioned hot backup"?

Or are you saying "a hot backup impacts performance" and aren't really comparing
rman to anything else?

A hot backup will incurr a certain amount of physical IO (like most of the
database).  That can obviously lead to contention for that shared resource.

But -- there isn't enough information here to really comment on anything.
Rating: 3
August 13, 2002
Reviewer: A reader
I could not compare RMAN vs. hot backup on this database. This is on RMAN for
quite sometime. Because of tap server availability problems, our hotbackup
schedule overlaps with peak usage time for our overseas users. And they have
been complaining about performance deterioration. The queries that take 2 mins
would take 15 mins during backup. (But some other queries does not show thismuch
variation.).

I wanted to know from you how to find out more about what is happening. And what
would be the general guidelines for RMAN tuning. Thank you.
Followup:
Look for IO contention -- use OS utilities to see if you have massive IO
contention (that would be my very first guess).  RMAN is a tad disk intensive as
it tends to read the entire database.
Rating: 5
RMAN and backup of read only data files July 01, 2003
Reviewer: Reader
Tom, After I created whole database backup using rman for the first time, when I
 checked "report need backup", it lists the readonly datafile. Why RMAN did not
make a backup of readonly datafile? I thought for the first time when I take a
whole database backup, read only datafiles should have been backup. Thanks.

RMAN> show all;

RMAN configuration parameters are:
CONFIGURE RETENTION POLICY TO REDUNDANCY 1; # default
CONFIGURE BACKUP OPTIMIZATION OFF; # default
CONFIGURE DEFAULT DEVICE TYPE TO DISK; # default
CONFIGURE CONTROLFILE AUTOBACKUP OFF; # default
CONFIGURE CONTROLFILE AUTOBACKUP FORMAT FOR DEVICE TYPE DISK TO '%F'; # default
CONFIGURE DEVICE TYPE DISK PARALLELISM 3;
CONFIGURE DATAFILE BACKUP COPIES FOR DEVICE TYPE DISK TO 1; # default
CONFIGURE ARCHIVELOG BACKUP COPIES FOR DEVICE TYPE DISK TO 1; # default
CONFIGURE MAXSETSIZE TO UNLIMITED; # default
CONFIGURE SNAPSHOT CONTROLFILE NAME TO 'C:\ORACLE\ORA92\DATABASE\SNCFPRACTICE.OR
A'; # default

RMAN> backup database
2> format 'C:\oracle\admin\rmanbackup\df_%d_%s_%p.bus';

Starting backup at 01-JUL-03
using channel ORA_DISK_1
using channel ORA_DISK_2
using channel ORA_DISK_3
channel ORA_DISK_1: starting full datafile backupset
channel ORA_DISK_1: specifying datafile(s) in backupset
input datafile fno=00008 name=C:\ORACLE\ORADATA\PRACTICE\TOOLS01.DBF
input datafile fno=00010 name=C:\ORACLE\ORADATA\PRACTICE\XDB01.DBF
input datafile fno=00012 name=C:\ORACLE\ORADATA\PRACTICE\FREELIST_DEMO01.DBF
input datafile fno=00006 name=C:\ORACLE\ORADATA\PRACTICE\INDX01.DBF
input datafile fno=00009 name=C:\ORACLE\ORADATA\PRACTICE\USERS01.DBF
channel ORA_DISK_1: starting piece 1 at 01-JUL-03
channel ORA_DISK_2: starting full datafile backupset
channel ORA_DISK_2: specifying datafile(s) in backupset
input datafile fno=00002 name=C:\ORACLE\ORADATA\PRACTICE\UNDOTBS01.DBF
input datafile fno=00005 name=C:\ORACLE\ORADATA\PRACTICE\EXAMPLE01.DBF
input datafile fno=00003 name=C:\ORACLE\ORADATA\PRACTICE\CWMLITE01.DBF
input datafile fno=00004 name=C:\ORACLE\ORADATA\PRACTICE\DRSYS01.DBF
input datafile fno=00007 name=C:\ORACLE\ORADATA\PRACTICE\ODM01.DBF
channel ORA_DISK_2: starting piece 1 at 01-JUL-03
channel ORA_DISK_3: starting full datafile backupset
channel ORA_DISK_3: specifying datafile(s) in backupset
including current SPFILE in backupset
including current controlfile in backupset
input datafile fno=00001 name=C:\ORACLE\ORADATA\PRACTICE\SYSTEM01.DBF
input datafile fno=00013 name=C:\ORACLE\ORADATA\PRACTICE\DUMMY01.DBF
input datafile fno=00011 name=C:\ORACLE\ORADATA\PRACTICE\READONLY01.DBF
channel ORA_DISK_3: starting piece 1 at 01-JUL-03
channel ORA_DISK_1: finished piece 1 at 01-JUL-03
piece handle=C:\ORACLE\ADMIN\RMANBACKUP\DF_PRACTICE_1_1.BUS comment=NONE
channel ORA_DISK_1: backup set complete, elapsed time: 00:01:15
channel ORA_DISK_2: finished piece 1 at 01-JUL-03
piece handle=C:\ORACLE\ADMIN\RMANBACKUP\DF_PRACTICE_2_1.BUS comment=NONE
channel ORA_DISK_2: backup set complete, elapsed time: 00:02:58
channel ORA_DISK_3: finished piece 1 at 01-JUL-03
piece handle=C:\ORACLE\ADMIN\RMANBACKUP\DF_PRACTICE_3_1.BUS comment=NONE
channel ORA_DISK_3: backup set complete, elapsed time: 00:03:03
Finished backup at 01-JUL-03

RMAN> list backupset;

List of Backup Sets
===================

BS Key  Type LV Size       Device Type Elapsed Time Completion Time
------- ---- -- ---------- ----------- ------------ ---------------
1       Full    33M        DISK        00:00:43     01-JUL-03
        BP Key: 1   Status: AVAILABLE   Tag: TAG20030701T183645
        Piece Name: C:\ORACLE\ADMIN\RMANBACKUP\DF_PRACTICE_1_1.BUS
  List of Datafiles in backup set 1
  File LV Type Ckp SCN    Ckp Time  Name
  ---- -- ---- ---------- --------- ----
  6       Full 3949506    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\INDX01.DBF
  8       Full 3949506    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\TOOLS01.DBF
  9       Full 3949506    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\USERS01.DBF
  10      Full 3949506    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\XDB01.DBF
  12      Full 3949506    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\FREELIST_DEMO01.D
BF

BS Key  Type LV Size       Device Type Elapsed Time Completion Time
------- ---- -- ---------- ----------- ------------ ---------------
2       Full    247M       DISK        00:02:54     01-JUL-03
        BP Key: 2   Status: AVAILABLE   Tag: TAG20030701T183645
        Piece Name: C:\ORACLE\ADMIN\RMANBACKUP\DF_PRACTICE_2_1.BUS
  List of Datafiles in backup set 2
  File LV Type Ckp SCN    Ckp Time  Name
  ---- -- ---- ---------- --------- ----
  2       Full 3949589    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\UNDOTBS01.DBF
  3       Full 3949589    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\CWMLITE01.DBF
  4       Full 3949589    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\DRSYS01.DBF
  5       Full 3949589    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\EXAMPLE01.DBF
  7       Full 3949589    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\ODM01.DBF

BS Key  Type LV Size       Device Type Elapsed Time Completion Time
------- ---- -- ---------- ----------- ------------ ---------------
3       Full    344M       DISK        00:02:55     01-JUL-03
        BP Key: 3   Status: AVAILABLE   Tag: TAG20030701T183645
        Piece Name: C:\ORACLE\ADMIN\RMANBACKUP\DF_PRACTICE_3_1.BUS
  SPFILE Included: Modification time: 29-JUN-03
  List of Datafiles in backup set 3
  File LV Type Ckp SCN    Ckp Time  Name
  ---- -- ---- ---------- --------- ----
  1       Full 3949670    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\SYSTEM01.DBF
  11      Full 201816     05-JUN-03 C:\ORACLE\ORADATA\PRACTICE\READONLY01.DBF
  13      Full 3949670    01-JUL-03 C:\ORACLE\ORADATA\PRACTICE\DUMMY01.DBF

RMAN> report need backup;

RMAN retention policy will be applied to the command
RMAN retention policy is set to redundancy 1
Report of files with less than 1 redundant backups
File kps Name
---- ----- -----------------------------------------------------
11   0     C:\ORACLE\ORADATA\PRACTICE\READONLY01.DBF
Followup:
I cannot reproduce

RMAN> show all;

RMAN configuration parameters are:
CONFIGURE RETENTION POLICY TO REDUNDANCY 1;
CONFIGURE BACKUP OPTIMIZATION OFF;
CONFIGURE DEFAULT DEVICE TYPE TO DISK;
CONFIGURE CONTROLFILE AUTOBACKUP OFF;
CONFIGURE CONTROLFILE AUTOBACKUP FORMAT FOR DEVICE TYPE DISK TO '%F';
CONFIGURE DEVICE TYPE DISK PARALLELISM 3;
CONFIGURE DATAFILE BACKUP COPIES FOR DEVICE TYPE DISK TO 1;
CONFIGURE ARCHIVELOG BACKUP COPIES FOR DEVICE TYPE DISK TO 1;
CONFIGURE MAXSETSIZE TO UNLIMITED;
CONFIGURE SNAPSHOT CONTROLFILE NAME TO
'/usr/oracle/ora920/oradata/ora920snapshot_controlfile.ora';

RMAN> backup database format '/d02/oradata/bkup/df_%d_%s_%p.bus';

Starting backup at 02-JUL-03
using channel ORA_DISK_1
using channel ORA_DISK_2
using channel ORA_DISK_3
channel ORA_DISK_1: starting full datafile backupset
channel ORA_DISK_1: specifying datafile(s) in backupset
including current SPFILE in backupset
including current controlfile in backupset

input datafile fno=00011 name=/d02/oradata/ora920/o1_mf_big_tabl_z6kzqrc0_.dbf
  /* that is a read only thing */

....
piece handle=/d02/oradata/bkup/df_ORA920_6_1.bus comment=NONE
channel ORA_DISK_1: backup set complete, elapsed time: 00:06:07
Finished backup at 02-JUL-03

RMAN> list backupset
2> ;

List of Backup Sets
===================

BS Key  Type LV Size       Device Type Elapsed Time Completion Time
------- ---- -- ---------- ----------- ------------ ---------------
1       Full    79M        DISK        00:02:00     02-JUL-03
        BP Key: 1   Status: AVAILABLE   Tag: TAG20030702T072153
        Piece Name: /d02/oradata/bkup/df_ORA920_8_1.bus
  List of Datafiles in backup set 1
  File LV Type Ckp SCN    Ckp Time  Name
  ---- -- ---- ---------- --------- ----
  8       Full 25153633   02-JUL-03
/usr/oracle/ora920/oradata/ora920/tools01.dbf
  10      Full 25153633   02-JUL-03 /usr/oracle/ora920/oradata/ora920/xdb01.dbf
  14      Full 25153633   02-JUL-03 /d02/oradata/ora920/o1_mf_undo_zczykg8d_.dbf

BS Key  Type LV Size       Device Type Elapsed Time Completion Time
------- ---- -- ---------- ----------- ------------ ---------------
2       Full    138M       DISK        00:02:34     02-JUL-03
        BP Key: 2   Status: AVAILABLE   Tag: TAG20030702T072153
        Piece Name: /d02/oradata/bkup/df_ORA920_7_1.bus
  List of Datafiles in backup set 2
  File LV Type Ckp SCN    Ckp Time  Name
  ---- -- ---- ---------- --------- ----
  3       Full 25153633   02-JUL-03
/usr/oracle/ora920/oradata/ora920/cwmlite01.dbf
  4       Full 25153633   02-JUL-03
/usr/oracle/ora920/oradata/ora920/drsys01.dbf
  5       Full 25153633   02-JUL-03
/usr/oracle/ora920/oradata/ora920/example01.dbf
  6       Full 25153633   02-JUL-03 /usr/oracle/ora920/oradata/ora920/indx01.dbf
  7       Full 25153633   02-JUL-03 /usr/oracle/ora920/oradata/ora920/odm01.dbf

BS Key  Type LV Size       Device Type Elapsed Time Completion Time
------- ---- -- ---------- ----------- ------------ ---------------
3       Full    1G         DISK        00:06:03     02-JUL-03
        BP Key: 3   Status: AVAILABLE   Tag: TAG20030702T072153
        Piece Name: /d02/oradata/bkup/df_ORA920_6_1.bus
  SPFILE Included: Modification time: 01-JUL-03
  List of Datafiles in backup set 3
  File LV Type Ckp SCN    Ckp Time  Name
  ---- -- ---- ---------- --------- ----
  1       Full 25153633   02-JUL-03
/usr/oracle/ora920/oradata/ora920/system01.dbf
  9       Full 25153633   02-JUL-03
/usr/oracle/ora920/oradata/ora920/users01.dbf
  11      Full 25153562   02-JUL-03
/d02/oradata/ora920/o1_mf_big_tabl_z6kzqrc0_.dbf

RMAN> report need backup
2> ;

RMAN retention policy will be applied to the command
RMAN retention policy is set to redundancy 1
Report of files with less than 1 redundant backups
File kps Name
---- ----- -----------------------------------------------------

RMAN>

I guess it might be due to the extremely low SCN relative to the rest

Try this -- clean out the backups, make the tablespace read/write then read only
-- do it again.  If the file is not reported, that was it.
Rating: 5
clarification July 01, 2003
Reviewer: Reader
"how many redundant backups of that file do you have?  (none)"

for that matter I should not have any redundant backup of other files as well as
this was my first backup of all files. Why other files are not listed in "report
need backup" command? Hope I understood your point. If not, apologies. Thanks.
Rating: 5
Yes, YOU ARE 100% CORRECT SIR! July 02, 2003
Reviewer: Reader
<quote>I guess it might be due to the extremely low SCN relative to the rest

Try this -- clean out the backups, make the tablespace read/write then read only

-- do it again.  If the file is not reported, that was it. <quote>

I tried just like you said. Please see below.

RMAN> backup database;

Starting backup at 02-jul-2003 10:52:31
using channel ORA_DISK_1
using channel ORA_DISK_2
using channel ORA_DISK_3
channel ORA_DISK_1: starting full datafile backupset
channel ORA_DISK_1: specifying datafile(s) in backupset
input datafile fno=00008 name=C:\ORACLE\ORADATA\PRACTICE\TOOLS01.DBF
input datafile fno=00010 name=C:\ORACLE\ORADATA\PRACTICE\XDB01.DBF
input datafile fno=00012 name=C:\ORACLE\ORADATA\PRACTICE\FREELIST_DEMO01.DBF
input datafile fno=00006 name=C:\ORACLE\ORADATA\PRACTICE\INDX01.DBF
input datafile fno=00009 name=C:\ORACLE\ORADATA\PRACTICE\USERS01.DBF
channel ORA_DISK_1: starting piece 1 at 02-jul-2003 10:52:36
channel ORA_DISK_2: starting full datafile backupset
channel ORA_DISK_2: specifying datafile(s) in backupset
input datafile fno=00002 name=C:\ORACLE\ORADATA\PRACTICE\UNDOTBS01.DBF
input datafile fno=00005 name=C:\ORACLE\ORADATA\PRACTICE\EXAMPLE01.DBF
input datafile fno=00003 name=C:\ORACLE\ORADATA\PRACTICE\CWMLITE01.DBF
input datafile fno=00004 name=C:\ORACLE\ORADATA\PRACTICE\DRSYS01.DBF
input datafile fno=00007 name=C:\ORACLE\ORADATA\PRACTICE\ODM01.DBF
channel ORA_DISK_2: starting piece 1 at 02-jul-2003 10:52:39
channel ORA_DISK_3: starting full datafile backupset
channel ORA_DISK_3: specifying datafile(s) in backupset
including current SPFILE in backupset
including current controlfile in backupset
input datafile fno=00001 name=C:\ORACLE\ORADATA\PRACTICE\SYSTEM01.DBF
input datafile fno=00013 name=C:\ORACLE\ORADATA\PRACTICE\DUMMY01.DBF
input datafile fno=00011 name=C:\ORACLE\ORADATA\PRACTICE\READONLY01.DBF
channel ORA_DISK_3: starting piece 1 at 02-jul-2003 10:53:26
channel ORA_DISK_1: finished piece 1 at 02-jul-2003 10:53:41
piece handle=D:\RMANBACKUP\DF_PRACTICE_32_1.BUS comment=NONE
channel ORA_DISK_1: backup set complete, elapsed time: 00:01:06
channel ORA_DISK_2: finished piece 1 at 02-jul-2003 10:55:56
piece handle=D:\RMANBACKUP\DF_PRACTICE_33_1.BUS comment=NONE
channel ORA_DISK_2: backup set complete, elapsed time: 00:03:20
channel ORA_DISK_3: finished piece 1 at 02-jul-2003 10:56:21
piece handle=D:\RMANBACKUP\DF_PRACTICE_34_1.BUS comment=NONE
channel ORA_DISK_3: backup set complete, elapsed time: 00:03:41
Finished backup at 02-jul-2003 10:56:21

RMAN> list backupset;

List of Backup Sets
===================

BS Key  Type LV Size       Device Type Elapsed Time Completion Time
------- ---- -- ---------- ----------- ------------ --------------------
24      Full    40M        DISK        00:00:56     02-jul-2003 10:53:31
        BP Key: 24   Status: AVAILABLE   Tag: TAG20030702T105234
        Piece Name: D:\RMANBACKUP\DF_PRACTICE_32_1.BUS
  List of Datafiles in backup set 24
  File LV Type Ckp SCN    Ckp Time             Name
  ---- -- ---- ---------- -------------------- ----
  6       Full 4023867    02-jul-2003 10:52:37 C:\ORACLE\ORADATA\PRACTICE\INDX01
.DBF
  8       Full 4023867    02-jul-2003 10:52:37 C:\ORACLE\ORADATA\PRACTICE\TOOLS0
1.DBF
  9       Full 4023867    02-jul-2003 10:52:37 C:\ORACLE\ORADATA\PRACTICE\USERS0
1.DBF
  10      Full 4023867    02-jul-2003 10:52:37 C:\ORACLE\ORADATA\PRACTICE\XDB01.
DBF
  12      Full 4023867    02-jul-2003 10:52:37 C:\ORACLE\ORADATA\PRACTICE\FREELI
ST_DEMO01.DBF

BS Key  Type LV Size       Device Type Elapsed Time Completion Time
------- ---- -- ---------- ----------- ------------ --------------------
25      Full    247M       DISK        00:03:10     02-jul-2003 10:55:46
        BP Key: 25   Status: AVAILABLE   Tag: TAG20030702T105234
        Piece Name: D:\RMANBACKUP\DF_PRACTICE_33_1.BUS
  List of Datafiles in backup set 25
  File LV Type Ckp SCN    Ckp Time             Name
  ---- -- ---- ---------- -------------------- ----
  2       Full 4023869    02-jul-2003 10:52:40 C:\ORACLE\ORADATA\PRACTICE\UNDOTB
S01.DBF
  3       Full 4023869    02-jul-2003 10:52:40 C:\ORACLE\ORADATA\PRACTICE\CWMLIT
E01.DBF
  4       Full 4023869    02-jul-2003 10:52:40 C:\ORACLE\ORADATA\PRACTICE\DRSYS0
1.DBF
  5       Full 4023869    02-jul-2003 10:52:40 C:\ORACLE\ORADATA\PRACTICE\EXAMPL
E01.DBF
  7       Full 4023869    02-jul-2003 10:52:40 C:\ORACLE\ORADATA\PRACTICE\ODM01.
DBF

BS Key  Type LV Size       Device Type Elapsed Time Completion Time
------- ---- -- ---------- ----------- ------------ --------------------
26      Full    344M       DISK        00:03:38     02-jul-2003 10:56:18
        BP Key: 26   Status: AVAILABLE   Tag: TAG20030702T105234
        Piece Name: D:\RMANBACKUP\DF_PRACTICE_34_1.BUS
  SPFILE Included: Modification time: 29-jun-2003 21:06:24
  List of Datafiles in backup set 26
  File LV Type Ckp SCN    Ckp Time             Name
  ---- -- ---- ---------- -------------------- ----
  1       Full 4024024    02-jul-2003 10:53:27 C:\ORACLE\ORADATA\PRACTICE\SYSTEM
01.DBF
  11      Full 4023774    02-jul-2003 10:51:59 C:\ORACLE\ORADATA\PRACTICE\READON
LY01.DBF
  13      Full 4024024    02-jul-2003 10:53:27 C:\ORACLE\ORADATA\PRACTICE\DUMMY0
1.DBF

RMAN> report need backup;

RMAN retention policy will be applied to the command
RMAN retention policy is set to redundancy 1
Report of files with less than 1 redundant backups
File kps Name
---- ----- -----------------------------------------------------

RMAN>
Rating: 5
RMAN process September 10, 2003
Reviewer: Reader
Tom, As RMAN uses server processes on the server (where the database is) for its
operations, is there a command that I can use in unix to check these processes?
does ps -ef show? Also, when I open a Rman session, will there be a dedicated
server process created for the session? Thanks.
Followup:
yes, rman just creates a process like that (on unix).  every process is visible
via ps.
Rating: 5
catalog vs controlfile October 22, 2003
Reviewer: Shankar from Houston, TX
Tom, my understanding is that target database controlfile is the MASTER and by
default if I used RMAN to backup, the rman info is updated in the target
database controlfile whether I use catalog or not. Is it correct?

Is there any circumstance that info from catalog is transferred to the target
database controlfile?

If I use resync catalog, the rman catalog only PULLS info from the target
database. There is no PUSH and PULL. Is it correct? Thanks much.
Followup:
yes, the controlfile is maintained.

the controlfile knows all.

the sync is an update of the catalog from the controlfiles.
Rating: 5
overhead.... October 23, 2003
Reviewer: Sam Bernard from Salt Lake City
How much of overhead in running BACKUP VALIDATE DATABASE and RESTORE VALIDATE
DATABASE commands to check for block corruptions using RMAN? Can I run these
commands anytime? Any suggestion would be appreciated? Thanks.
Followup:
backup validate works against the backups, not against the live database - no
impact.

restore validate -- same thing.

they do not touch the real thing
Rating: 5
it is touching the real thing .... October 23, 2003
Reviewer: Sam Bernard from Salt Lake City
Tom, I practiced the command as shown below. It appears like it is touching the
live datafiles. Please review and clarify. Thanks.

RMAN> backup validate database;

Starting backup at 23-OCT-03
using channel ORA_DISK_1
channel ORA_DISK_1: starting full datafile backupset
channel ORA_DISK_1: specifying datafile(s) in backupset
including current SPFILE in backupset
including current controlfile in backupset
input datafile fno=00009 name=/home4/user20/ORADATA/u05/part_01.dbf
input datafile fno=00003 name=/home4/user20/ORADATA/u03/users_01.dbf
input datafile fno=00004 name=/home4/user20/ORADATA/u03/indx_01.dbf
input datafile fno=00001 name=/home4/user20/ORADATA/u01/system_01.dbf
input datafile fno=00005 name=/home4/user20/ORADATA/u02/sample_01.dbf
input datafile fno=00002 name=/home4/user20/ORADATA/u02/undo1_01.dbf
input datafile fno=00006 name=/home4/user20/ORADATA/u01/query_data_01.dbf
input datafile fno=00008 name=/home4/user20/ORADATA/u04/corrupt1.dbf
input datafile fno=00010 name=/home4/user20/ORADATA/u04/undo02_01.dbf
channel ORA_DISK_1: backup set complete, elapsed time: 00:00:37
Finished backup at 23-OCT-03
Followup:
never mind, got it backwards, yes, it is reading the files there.  it'll impact
inasmuch as touching them for the io impacts you.
Rating: 3
throttle IO on rman backup October 24, 2003
Reviewer: A reader from houston, TX
I also had the problem of poor performance during rman backups. I ended up
(reading the manual) and came up with the following command that goes after you
select a channel.
setlimit channel c1 readrate 20;
I had to experiment to find the "right" readrate for my system. By using this
command, you slow down the backup considerably, but users noticed very liitle
performance change.
Rating: 5
maxpiecesize October 25, 2003
Reviewer: Shankar from Houston, TX
Where is maxpiecesize useful? If I have a filesystem that has 5 disks each 2G
size. If I say,

allocate channel c1 device type disk maxpiecesize=2G;
backup datafile 2;

assuming the datafile size is 10G, will rman backup the datafile 2 in 5 pieces
and each piece will be in each of 5 disks? Does it mean that backupset will span
disks? Thanks.
Followup:
it means that rman will create 5 pieces of 2gig each for the 10gig of data.
It'll put it where ever you told it to put it.
Rating: 5
each piece in .... October 25, 2003
Reviewer: Reader
Tom, can I tell rman to put each piece in different location, say piece 1 in
u01/BACKUP/one_dir/, piece 2 in u02/BACKUP/another_dir and so on? If so, how can
it be done? Thanks.
Followup:
you would allocate separate channels for each and tell it what to put where.
Rating: 4
Backup piece October 25, 2003
Reviewer: Reader
Tom, please see below. RMAN puts all of backup pieces in one location. How do I
tell RMAN to put the pieces in different locations? In otherwords, can backup
set span disks? Thanks.

RMAN> run
2> {
3> allocate channel c1 device type disk format 'D:\rmanbackup\df_%s_%p.bak'
maxpiecesize=50K;
4> allocate channel c2 device type disk format 'C:\rmanbackup\df_%s_%p.bak'
maxpiecesize=50K;
5> backup datafile 'C:\oracle\oradata\practice\dummy01.dbf';
6> }

allocated channel: c1
channel c1: sid=16 devtype=DISK

allocated channel: c2
channel c2: sid=17 devtype=DISK

Starting backup at 25-OCT-03
channel c1: starting full datafile backupset
channel c1: specifying datafile(s) in backupset
input datafile fno=00013 name=C:\ORACLE\ORADATA\PRACTICE\DUMMY01.DBF
channel c1: starting piece 1 at 25-OCT-03
channel c1: finished piece 1 at 25-OCT-03
piece handle=D:\RMANBACKUP\DF_55_1.BAK comment=NONE
channel c1: starting piece 2 at 25-OCT-03
channel c1: finished piece 2 at 25-OCT-03
piece handle=D:\RMANBACKUP\DF_55_2.BAK comment=NONE
channel c1: starting piece 3 at 25-OCT-03
channel c1: finished piece 3 at 25-OCT-03
piece handle=D:\RMANBACKUP\DF_55_3.BAK comment=NONE
channel c1: starting piece 4 at 25-OCT-03
channel c1: finished piece 4 at 25-OCT-03
piece handle=D:\RMANBACKUP\DF_55_4.BAK comment=NONE
channel c1: starting piece 5 at 25-OCT-03
channel c1: finished piece 5 at 25-OCT-03
piece handle=D:\RMANBACKUP\DF_55_5.BAK comment=NONE
channel c1: backup set complete, elapsed time: 00:00:10
Finished backup at 25-OCT-03

Starting Control File and SPFILE Autobackup at 25-OCT-03
piece handle=C:\ORACLE\ORA92\DATABASE\C-2686660793-20031025-01 comment=NONE
Finished Control File and SPFILE Autobackup at 25-OCT-03
released channel: c1
released channel: c2
Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:7275792247455#10969431404165
has a "for example"
Rating: 5
backuppiece October 26, 2003
Reviewer: Reader
Tom, That example puts backupSET in different locations. My understanding is
that within a backupset, we can ask rman to create more than one backuppiece by
using maxpiecesize parameter. My question is, can rman place backupPIECE in
different locations if a backupset contains say two or three pieces and each
piece in different backup locations. I guess not. It appears like the usage of
maxpiecesize is for restricting the size of the piece as some operating system
has size limit for files. For example, old unix os has 2G file size limit. Is
this why we have maxpiecesize clause in rman to restrict the piece size? I am
trying to understand the usefulness of maxpiecesize. Sorry for bugging you on
this issue. Thanks.
Followup:
some people just don't like "big files".  maxpiecesize lets them control that.

some media may not support very large files, maxpiecesize lets us work around
that.

it is strictly for controlling the size of files.
Rating: 3
quick rman question November 19, 2003
Reviewer: Christo Kutrovsky from Ottawa, ON Canada
What is the easiest way, to find out what is the most recent time that one can
restore it's database , given that a catalog database is been used.

(9.2)
Followup:
the most recent time?  right now of course?  full recovery (or did i
misunderstand)
Rating: 5
Tape backup December 12, 2003
Reviewer: Shankar from Houston, TX
Tom, does 9i R2 database installation have any support for backup to tape using
RMAN without any additional purchase of media management software? I heard that
Oracle provides Legato tape backup software that comes with the 9i installation?
If this is true, can one backup to tape without doing any additional
installation of software for tape backups? Thanks.
Followup:
Not sure, -- support is awesome at answering this sort of question however,
suggest you file an itar for this (or maybe someone can answer).  we (i, my
group) are doing disk based backups right now with conventional "push it to tape
later".
Rating: 5
rman questions December 15, 2003
Reviewer: shankar from Houston, TX
Tom,

(1) My understanding is that based on the retention policy, rman makes the
backups obsolete. Is there a way to force rman to use these obsolete backups or
once it is marked obsolete, the backups are useless?

(2) Also, if I used change ... keep sysdate+30 for example, to keep a particular
backup overriding the retention policy, does Rman keep the record in the
controlfile for 30 days even if the backup exceeds the
control_file_record_keep_time? thanks.
Followup:
1) it is just a report, they are still there until you delete them

2) the controlfile will hold as much as it can, it won't wipe them out until it
is starved for space.

you really want to use a catalog instance, not a control file for managing
things.
Rating: 5
Glad to know Tom uses RMAN to write to disk vs. tape!!! December 16, 2003
Reviewer: Robert from Memphis, USA
This is what we do also.
Glad to know this method is just as 'legitimate' as writing to tape!
Rating: 5
RMAN question .. December 16, 2003
Reviewer: Reader
<quote>it is just a report, they are still there until you delete them<quote>

But, rman will not consider these obsolete bacups for restore. Right? Thanks.
Followup:
it'll try to use the most current ones (takes lots less time)
Rating: 4
Same snapshot controlfile for multiple db December 28, 2003
Reviewer: Saminathan Seerangan from NJ,USA
Dear Tom,

Can I use the same snapshot controlfile to backup multiple databases(one after
another) running on the same server?

The following RMAN script will be invoked from SHELL script loop(with different
ORACLE_SID)
-----------RMAN Script Begin--------------------------------
  set snapshot controlfile name to
'/u01/app/oracle/product/8.1.7/dbs/Rcatctrl.ctl';
      run {
      allocate channel t1 type 'SBT_TAPE';
      allocate channel t2 type 'SBT_TAPE';
      backup
        incremental level 0
        skip inaccessible
        tag hot_db_bk_level0
        filesperset 5
        format 'bk_%s_%p_%t'
        (database);
      }
-----------RMAN Script End---------------------------------
Followup:
this file is only use temporarily -- like a scratch file.  Only one rman session
can access the snapshot controlfile at any time -- so this would tend to
serialize your backups if you do that.

as it defaults nicely, why do you even want to set it?
Rating: 5
SNAPSHOT CONTROLFILE default looks for non-existing dir December 28, 2003
Reviewer: Sami from NJ,USA
Version 8.1.7.3

If I do not specify the SNAPSHOT CONTROLFILE explicitly it is giving the
following error.

RMAN-03006: non-retryable error occurred during execution of command: backup
RMAN-07004: unhandled exception during command execution on channel t1
RMAN-10035: exception raised in RPC: ORA-01580: error creating control backup
file /dev/vx/rdsk/dbdg13/Rcatctrl.ctl
ORA-27040: skgfrcre: create error, unable to create file
SVR4 Error: 2: No such file or directory
RMAN-10031: ORA-1580 occurred during call to
DBMS_BACKUP_RESTORE.CFILEMAKEANDUSESNAPSHOT

I don't know why it is looking for "/dev/vx/rdsk/dbdg13/Rcatctrl.ctl". I don't
know where it got stored?
The directory "/dev/vx/rdsk/dbdg13" does not exist in this server.

Thanks
Sami
Followup:
ahh, ok, this must have been upgraded from pre 817 when the name was burned into
the control file.  so, that was the name that was used in the past by this
database.  and that device no longer exists.

yes, you can do what you were doing (but you should have to do it once, it'll
remember it in the control files -- so you could just use rman to go into each
"old, upgraded" instance and burn the name into them -- for all new instances,
it'll use a nice default inside of $ORACLE_HOME/dbs)
Rating: 5
raw device? December 28, 2003
Reviewer: Sudhir
re. earlier post:

if you don't specify location, it goes to dbs, correct?
is this going to raw device?

/dev/vx/rdsk/dbdg13/Rcatctrl.ctl : or is it your volume manager issue? df -k
should give you some information.
Rating: 4
What is a proxy copy? Thanks. December 29, 2003
Reviewer: reader
Followup:
means someone else does the copy, instead of rman.

<quote from the rman docs>
Proxy Copy

Oracle has integrated proxy copy functionality into its media management API.
Vendors can use this API to develop media management software that takes control
of backup and restore operations. RMAN provides a list of files requiring backup
or restore to the media manager, which in turn makes all decisions regarding how
and when to move the data.
Rating: 4
proxy .. December 29, 2003
Reviewer: reader
Tom, does it mean that whenever i backup to tape, it is considered a proxy copy
as media management software is doing the backups for me? Thanks.
Rating: 5
Controlfile is not remembering SNAPSHOT CONTROLFILE name December 29, 2003
Reviewer: Sami from NJ,USA
Dear Tom, Really sorry for bothering you.

<asktom>
but you should have to do it once, it'll
remember it in the control files -- so you could just use rman to go into each
"old, upgraded" instance and burn the name into them
</asktom>

When you say "burn the name", you mean run the follwoing command. Am I correct?

set snapshot controlfile name to
'/u01/app/oracle/product/8.1.7/dbs/Rcatctrl.ctl';

FIRST TIME I ran RMAN backup using the following script and it executed fine.

<FIRST_TIME>
-----------RMAN Script Begin--------------------------------
  set snapshot controlfile name to
'/u01/app/oracle/product/8.1.7/dbs/Rcatctrl.ctl';
      run {
      allocate channel t1 type 'SBT_TAPE';
      allocate channel t2 type 'SBT_TAPE';
      backup
        incremental level 0
        skip inaccessible
        tag hot_db_bk_level0
        filesperset 5
        format 'bk_%s_%p_%t'
        (database);
      }
-----------RMAN Script End---------------------------------
</FIRST_TIME>

<SECOND_TIME>
-----------RMAN Script Begin--------------------------------
       run {
      allocate channel t1 type 'SBT_TAPE';
      allocate channel t2 type 'SBT_TAPE';
      backup
        incremental level 0
        skip inaccessible
        tag hot_db_bk_level0
        filesperset 5
        format 'bk_%s_%p_%t'
        (database);
      }
-----------RMAN Script End---------------------------------
</SECOND_TIME>
SECOND TIME I Took off
set snapshot controlfile name to
'/u01/app/oracle/product/8.1.7/dbs/Rcatctrl.ctl';
and got the below error again.

<Error>
RMAN-10035: exception raised in RPC: ORA-01580: error creating control backup
file /dev/vx/rdsk/dbdg13/Rcatctrl.ctl
ORA-27040: skgfrcre: create error, unable to create file
SVR4 Error: 2: No such file or directory
RMAN-10031: ORA-1580 occurred during call to
DBMS_BACKUP_RESTORE.CFILEMAKEANDUSESNAPSHOT
</Error>

Is there a way to check what is the SNAPSHOT CONTROLFILE name stored in control
file(record)?

Thanks
Sami
Followup:
run CONFIGURE SNAPSHOT CONTROLFILE NAME CLEAR.
Rating: 5
Tom, getting error (v 8.1.7.3) December 29, 2003
Reviewer: Sami from NJ,USA
RMAN> CONFIGURE SNAPSHOT CONTROLFILE NAME CLEAR

RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-00558: error encountered while parsing input commands
RMAN-01005: syntax error: found "snapshot": expecting one of: "compatible"
RMAN-01007: at line 1 column 11 file: standard input

RMAN>
Followup:
guess thats new with 9i, you'll have to set the name each time until then I
suppose.  yes, they can all use the same name -- you cannot run concurrent
backups in that case (you'll hit things like RMAN-08512: waiting for snapshot
controlfile enqueue if you do).
Rating: 4
8.1.7 does not support the follwoing RMAN commands. December 30, 2003
Reviewer: A reader
RMAN> CONFIGURE SNAPSHOT CONTROLFILE NAME CLEAR;

RMAN> SHOW SNAPSHOT CONTROLFILE NAME;

RMAN> CONFIGURE SNAPSHOT CONTROLFILE NAME TO
'/u01/app/oracle/product/8.1.7/dbs/Rcatctrl.ctl';
Rating: 5
Thanks Tom. December 30, 2003
Reviewer: Sami from NJ,USA
December 30, 2003 -- Thanks for the question regarding "Sending HTML using UTL_SMTP", version 8i
You Asked
Hi Tom

I hope I'm phrasing this correctly...

I'd like to be able to send an HTML formatted email from the database using the
UTL_SMTP package.  I don't see any way of setting the MIME type.

Is this beyond the scope of UTL_SMTP?

thanks in advance!

Ken
and we said...
I asked Tyler Muth (tyler.muth@oracle.com) to answer this one since I knew he
already had the code (we use it on our Oracle Calendar software shipped with the
email server to send reminders for appointments).  Tyler provided this answer
for us:

...

You are correct in you observation that there is no MIME type parameter in
UTL_SMTP, but this does not limit the types of email you can send.  It's
actually very flexible, in that UTL_SMTP allows you to contruct and pass in
the whole email message.  What this means to you is that constructing an
HTML message is possible using UTL_SMTP, but your going to have to do some
work to create it.

The folowing is the basic structure of the message you need to construct:

Normal Headers
Content-Type: multipart/alternative;
boundary="some_unique_string_not_in_email"

--some_unique_string_not_in_email
Content-Type: text/plain; charset=us-ascii

Text email message here.

--some_unique_string_not_in_email
Content-Type: text/html; charset=us-ascii

<html>
    <head>
        <title>some subject</title>
    </head>
    <body>
        Your <b>Html</b> email message here.
    </body>
</html>

--some_unique_string_not_in_email--

Ok, that looks hard, but if you use this procedure I wrote, its really quite
easy, it does all of the work for you:

create or replace procedure html_email(
    p_to            in varchar2,
    p_from          in varchar2,
    p_subject       in varchar2,
    p_text          in varchar2 default null,
    p_html          in varchar2 default null,
    p_smtp_hostname in varchar2,
    p_smtp_portnum  in varchar2)
is
    l_boundary      varchar2(255) default 'a1b2c3d4e3f2g1';
    l_connection    utl_smtp.connection;
    l_body_html     clob := empty_clob;  --This LOB will be the email message
    l_offset        number;
    l_ammount       number;
    l_temp          varchar2(32767) default null;
begin
    l_connection := utl_smtp.open_connection( p_smtp_hostname, p_smtp_portnum );
    utl_smtp.helo( l_connection, p_smtp_hostname );
    utl_smtp.mail( l_connection, p_from );
    utl_smtp.rcpt( l_connection, p_to );

    l_temp := l_temp || 'MIME-Version: 1.0' ||  chr(13) || chr(10);
    l_temp := l_temp || 'To: ' || p_to || chr(13) || chr(10);
    l_temp := l_temp || 'From: ' || p_from || chr(13) || chr(10);
    l_temp := l_temp || 'Subject: ' || p_subject || chr(13) || chr(10);
    l_temp := l_temp || 'Reply-To: ' || p_from ||  chr(13) || chr(10);
    l_temp := l_temp || 'Content-Type: multipart/alternative; boundary=' ||
                         chr(34) || l_boundary ||  chr(34) || chr(13) ||
                         chr(10);

    ----------------------------------------------------
    -- Write the headers
    dbms_lob.createtemporary( l_body_html, false, 10 );
    dbms_lob.write(l_body_html,length(l_temp),1,l_temp);

    ----------------------------------------------------
    -- Write the text boundary
    l_offset := dbms_lob.getlength(l_body_html) + 1;
    l_temp   := '--' || l_boundary || chr(13)||chr(10);
    l_temp   := l_temp || 'content-type: text/plain; charset=us-ascii' ||
                  chr(13) || chr(10) || chr(13) || chr(10);
    dbms_lob.write(l_body_html,length(l_temp),l_offset,l_temp);

    ----------------------------------------------------
    -- Write the plain text portion of the email
    l_offset := dbms_lob.getlength(l_body_html) + 1;
    dbms_lob.write(l_body_html,length(p_text),l_offset,p_text);

    ----------------------------------------------------
    -- Write the HTML boundary
    l_temp   := chr(13)||chr(10)||chr(13)||chr(10)||'--' || l_boundary ||
                    chr(13) || chr(10);
    l_temp   := l_temp || 'content-type: text/html;' ||
                   chr(13) || chr(10) || chr(13) || chr(10);
    l_offset := dbms_lob.getlength(l_body_html) + 1;
    dbms_lob.write(l_body_html,length(l_temp),l_offset,l_temp);

    ----------------------------------------------------
    -- Write the HTML portion of the message
    l_offset := dbms_lob.getlength(l_body_html) + 1;
    dbms_lob.write(l_body_html,length(p_html),l_offset,p_html);

    ----------------------------------------------------
    -- Write the final html boundary
    l_temp   := chr(13) || chr(10) || '--' ||  l_boundary || '--' || chr(13);
    l_offset := dbms_lob.getlength(l_body_html) + 1;
    dbms_lob.write(l_body_html,length(l_temp),l_offset,l_temp);

    ----------------------------------------------------
    -- Send the email in 1900 byte chunks to UTL_SMTP
    l_offset  := 1;
    l_ammount := 1900;
    utl_smtp.open_data(l_connection);
    while l_offset < dbms_lob.getlength(l_body_html) loop
        utl_smtp.write_data(l_connection,
                            dbms_lob.substr(l_body_html,l_ammount,l_offset));
        l_offset  := l_offset + l_ammount ;
        l_ammount := least(1900,dbms_lob.getlength(l_body_html) - l_ammount);
    end loop;
    utl_smtp.close_data(l_connection);
    utl_smtp.quit( l_connection );
    dbms_lob.freetemporary(l_body_html);
end;
/
show errors
Review & Followup

Rating: 4
How to October 29, 2001
Reviewer: A reader
Hi, Tom,

How to let the db recognize the 'UTL_SMTP.CONNECTION',
which procedure we should run before using UTL_STNP?

Thanks
Followup:
I don't understand this follow up at all.  What do you mean by "recognize".  If
you have 816, you have UTL_SMTP.  You need to have java installed and configured
on the server

See also
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:958229976043
...
Rating: 4
October 30, 2001
Reviewer: A reader
when i receive an email from tom i can't find any digital signuture also when i
receive some information from sales pepole i get the same is that mean oracle
mail havn't any signuture
Followup:
so?
Rating: 4
Hang up October 30, 2001
Reviewer: A reader
Hi, Tom,

When i execute the program, why is it always hangup?

I created the procedure without any error.
VER: 8.1.6

Thanks
Rating: 4
Plain text doesn't show... October 30, 2001
Reviewer: jan van mourik from Houston, Texas
Procedure works great! Only on my email, I don't see the plain text show up
(p_text). Only the html test comes out (p_html). Is that because the mail client
(both Lotus Notes and Yahoo! mail) can't handle it?

jan
Followup:
The email is sent with multiple representations of the email.  The email client
gets BOTH and picks which one it wants to use.  You could actually use this
technique to send an email that has the text of the email in

o plaintext
o html
o rtf
o word
o whatever

and the email client will look at all of the representations to see what it
wants to use, which it is best at rendereing.

you'll need to find a text based emailer to see what the text is looking like.
Its getting sent though!
Rating: 4
Now for attachments too... October 30, 2001
Reviewer: Andrew from CA, USA
I assume attachments are done in a similar manner. An example would be great if
you have one.
Rating: 5
re: Now for attachments too... October 30, 2001
Reviewer: A reader
If you click on "Search/Archives" in the upper right hand corner of the home
page, type in "email attachment" in the "Search" text box and click on "Go!" you
might be surprised what you can find.
Rating: 5
Sending HTML using UTL_SMTP", version 8i October 30, 2001
Reviewer: Casey Waters from Sunnyvale, CA
You guys a great!  Thanks!
Rating: 5
Way to go!! November 02, 2001
Reviewer: patrick from usa
Way to go on this one!! after a fight with
initjvm...etc i got this to work great!!
thanks
Rating: 5
Outstanding, thanks! March 14, 2002
Reviewer: Bob Yexley from Dayton, OH USA
I've implemented this, and it is working GREAT!  Thank you so much for your
help.  I am having some strange behavior with it though, that I was hoping that
you might be able to shed some light on.  When I send the e-mail (I've sent it
to myself at least a dozen times now) I've noticed that somehow the procedure
randomly inserts exclamation points (!) in the message, and I have no idea why
or where they're coming from.  They're not in every message, and when they are
in a message, they're never in the same place.  It's completely random as far as
I can tell.  Have you ever seen this behavior before?  If so, is there an
explanation and possibly a fix for it?  Any help would be greatly appreciated.

Thanks again.
Followup:
The !'s -- are they in the HTML or plaintext copy?

We use this internally at Oracle to send thousands of emails every day, haven't
had a single report of errant !'s in it.

versions, os's and such would be helpful.  An example of an email call that
caused this to happen would be as well.
Rating: 5
Followup to your followup March 14, 2002
Reviewer: Bob Yexley from Dayton, OH USA
The !s are showing up in the HTML formatted messages.  I haven't even actually
used this to send plaintext, HTML is preferred.  We are running this from an
8.1.7.0.0 database on Solaris, the client workstations are Windows 2000 (SP2)
with Outlook 2000 as the e-mail client.

I'd be happy to send you a copy of the code that calls this procedure, but there
is not enough space allowed in this forum.  I would gladly send you an e-mail
though.

I may be beginning to notice where the problems is coming in though, but I'm
still debugging it.  I have just put all of the text of my message on one line
in the code where I build the HTML message, and I've noticed that when I view
the source of the HTML, the !s appear where there has somehow been line breaks
inserted in the HTML.  I don't know if that made sense or not, but what I mean
by it, is that when you view the source of the HTML, at the end of every line
where the browser has somehow forced a linebreak (I didn't insert the line break
with <br> tags or anything), there appears the !s.  If you need more clarity I
can send it to you.  Thanks for the fast response.
Followup:
Sounds like it is OUTLOOK doing this then, not on our end.  Don't know what to
say on this one, we don't insert linebreaks, outlooks does that part.

sorry -- can you try it with a different email client?
Rating: 5
Last followup March 14, 2002
Reviewer: Bob Yexley from Dayton, OH USA
I probably could try it with a different mail client, but it wouldn't really do
me much good.  Outlook is the client that our customers are mandated to use, so
I'm going to have to figure out a way to make it work with outlook.  Thanks
though...I'm 99% of the way there.  :)
Rating: 5
Spurious characters inserted into message body May 06, 2002
Reviewer: Mark Russell-Brown from GENEVA, SWITZERLAND
Hello Tom,

A reviewer had complained that the html message had "!" characters apparently
inserted in the text randomly. I had the identical problem and it seems due to
the fact that the message text contains unprintable characters which are
translated by the client email tool in this way.

I run the following test using your Oracle colleague's smtp package plus your
wonderful utl_dir package (to get a file of the html that is generated).

It retrieves the "machine" column from v$session .. apparently this column
contains a trailing unprintable character when the data refers to an NT domain
logon.

Is there any way to fix this other than change the query to remove the trailing
unprintable character using for example:

DECODE(INSTR(Machine,'\'),0,Machine,SUBSTR(Machine,1,LENGTH(Machine)-1))

I'm wondering if there's any function or trick to remove "unprintable"
characters should these be present in a table column we wish to use in an html
email.

Many thanks ,
Mark.
--------------------------------
declare
    l_body         varchar2(32767);
    f         filetype;
begin
    EXECUTE IMMEDIATE 'ALTER SESSION SET NLS_DATE_FORMAT=''DD-MON-YYYY'' ';
    l_body:='<TABLE BORDER=1 BGCOLOR="#EEEEEE">';
    l_body:=l_body||'<TR BGCOLOR="BLACK">';
    l_body:=l_body||'<TH><FONT COLOR="WHITE">User</FONT>';
    l_body:=l_body||'<TH><FONT COLOR="WHITE">First Logon</FONT>';
    l_body:=l_body||'<TH><FONT COLOR="WHITE">Last Logon</FONT>';
    l_body:=l_body||'<TH><FONT COLOR="WHITE">Machine</FONT>';
    l_body:=l_body||'<TH><FONT COLOR="WHITE">No. Sessions</FONT>';
    l_body:=l_body||'</TR>';

    FOR c IN (
        SELECT
            Osuser,
            Machine,
            MIN(Logon_Time) First_Logon,
            MAX(Logon_Time) Last_Logon,
            Count(*) Total_Sessions
        FROM
            V$SESSION
        GROUP BY
            Osuser,
            Machine
        ORDER BY
            5 Desc, 1 Asc    )
    LOOP
        l_body:=l_body||'<TR>';
        l_body:=l_body||'<TD>'||c.Osuser||'</TD>';
        l_body:=l_body||'<TD>'||c.First_Logon||'</TD>';
        l_body:=l_body||'<TD>'||c.Last_Logon||'</TD>';
        l_body:=l_body||'<TD>'||c.Machine||'</TD>';
        l_body:=l_body||'<TD>'||c.Total_Sessions||'</TD>';
        l_body:=l_body||'</TR>';
    END LOOP;
    l_body:=l_body||'</TABLE>';

    f:=fileType.open( 'C:\Temp', 'test.html');
    f.Put_Line( l_body );
    f.close;

    html_email(
        p_to            => 'someone@somewhere',
        p_from              => 'someoneelse@somewhereelse',
        p_subject           => 'Usage Report',
        p_text              => 'A bit of text',
        p_html              => l_body,
        p_smtp_hostname     => '9.9.9.9',
        p_smtp_portnum      => '25');
end;
/
Followup:
There is no such builtin function -- "printable" is in the eye of the beholder I
guess.

I would make a plsql package that contains a global that has what your concept
of unprintable is, eg:

create package xxxx
as
   g_unprintable   varchar2(2000) default chr(0) || chr(1) || chr(2) || ....;
   g_spaces        varchar2(2000) default rpad( ' ', length(g_unprintable), ' '
);
end;

and then use:

   translate( your_variable, g_unprintable, g_spaces );

to "fix" the string.
Rating: 4
Solution for ! September 15, 2002
Reviewer: Vikas Sharma from Delhi India
Hi,

I also faced the problem of ! as Mark Russell-Brown  but later I found the
solution for this. This is because the your variable l_body is getting
concatenated in to a long string. What you should do just insert line break
(chr(10)) at few place or may be where you again concatenate the l_body
something like this :
l_body:=l_body||'<TD>'||c.Osuser||'</TD>'||chr(10);

This will solve your problem.

Thanks,

Vikas Sharma
Rating: 5
HTML email April 24, 2003
Reviewer: mo
Tom:

1.  How would HTML look like in a Microsoft Outlook email program when the user
opens it.  Or would this be a link that the user opens up in a browser.

2.  Is there any advantage of using HTML over ASCII for emails?

3.  Would I be able to take a prcoedure that generates an HTML form and feed it
into p_html parameter.

Thank you,
Followup:
1) you'd have to ask someone who uses MS software products -- in mozilla, I can
say they look like a web page.

2) some people consider them prettier.  Me, I like 8 point Courier...  and heavy
use of the <pre> tag in my html

3) you can send an html form as an email, yes.
Rating: 5
html email April 24, 2003
Reviewer: mo
Tom:

Is not Mozilla an internet brower.  You do not send email to a browser.  DO you
mean that some email programs can display HTML files just like a browser? Also,
it seems the majority of people use MS outlook for desktop mail program.

Thank you
Followup:
Mozilla is a web browser.
Mozilla is an email client.
Mozilla is a usenet newsgroup client.
Mozilla is an IRC/chat client.
Mozilla is an address book.
Mozilla is an html editor (composer).

That most people seem to use outlook isn't relevant.  I don't, so I cannot
answer as to what it would do.  I find it painful to attempt to run MS software
on my operating system of choice for my desktop and laptop.
Rating: 5
html email April 26, 2003
Reviewer: mo
Tom:

1. Can you please provide an example of sending the (select * from emp) in HTML
format via the above procedure.

2. If I have a pl/sql procedure that creates HTML output can I set a variable of
type long to the output of that procedure and then pass it here into the html
email procedure?
Followup:
1) do you see the input parameter p_html?  all you need to do is put into a
string the HTML you want to send.  There is nothing magical here, you need to
run the query, get the results, build a string with HTML in it and call this
procedure.

2) tell me -- does your procedure return a long? if so, sure, if not -- if it is
a "htp" type procedure, search this site for dump_page and use a technique
similar to what I do there.  I'm saving the output to a file, you would use your
creativity to save the output "to a string".
Rating: 5
html email April 28, 2003
Reviewer: mo
Tom:

1.  I tested your html_email procedure for (select * from emp) and it worked
fine.  However as said before the p_text did not show up at all.  Are you saying
that the client will either pick the text or the html and I can not show both in
he same message (MS outlook)?

2.  When you do HTML emailing, do you assume that all users email clients can
support HTML?  If the client does not support HTML would the message still show
up?  Would you only implement this if you know that users can display HTML in
email clients?

Thank you,
Followup:
1) when you do this -- you send in TEXT the same thing you send in HTML. The
email client will pick EITHER OR, but not both.

It either shows the HTML (if it is capable of doing so and the user has
permitted it) or it shows the TEXT.

2) you don't have to -- if they do not, the email client will how them the TEXT
version.

Me, I actually do not email html (as I said -- courier is the only font I use).
I send links

(you should know that -- you've seen the emails I send from asktom, just text)s
Rating: 5
1900 byte chunks ? May 14, 2003
Reviewer: Paul Druker from Fairfax, VA USA
I've seen this "magic" number - 1900 byte chunks - couple times on this site,
and I'm wondering where this number comes from.

The RFC 821 SMTP (
http://www.ietf.org/rfc/rfc0821.txt
 says "The maximum total length of a text line including
the <CRLF> is 1000 characters (but not counting the leading  dot duplicated for
transparency)." The same phrase is used in UTL_SMTP package description in
Supplied PL/SQL Packages guide.

Somewhere on the web I found that there is "a restriction of 2000 on the length
on a single call to utl_smtp.write_data"

However, I was able to send data chunks of more than 2000 characters. So what's
the maximum length of "data" parameter in utl_smtp.write_data that we should not
exceed?
Rating: 4
October 06, 2003
Reviewer: Randy from Warren, MI
Tom,
  Is there a limitation on how much data I can load into the p_html variable?  I
am running into a problem when my query generates long emails (around 70 rows).
The error I receive is:

ORA-21560: argument 2 is null, invalid, or out of range
ORA-06512: at "SYS.DBMS_LOB", line 775
ORA-06512: at "AP32ADMIN.HTML_EMAIL", line 63
ORA-06512: at line 35

Can you suggest a work around?

Thanks,
Randy
Followup:
32k is the largest PLSQL variable -- but if you exceeded that -- it would have
blown up BEFORE you called this routine.  thats not the cause.

put a little bit of debugging in there -- see what it is blowing up on.  my line
"63" is blank -- so, i'm not even really sure where it is failing in your code.
Rating: 5
October 06, 2003
Reviewer: Randy
Thanks for the quick response.  I think it has something to do with how I define
p_html.

p_html  => l_acq||'<br>'||l_eng||'<br>'||l_pa,

Line 63 is:

dbms_lob.write(l_body_html,length(p_html),l_offset,p_html);
Followup:
what is the length of

l_acq||'<br>'||l_eng||'<br>'||l_pa
Rating: 5
Sending HTML using UTL_SMTP October 16, 2003
Reviewer: Ray DeBruyn Enterprise Information Systems from Ottawa, Ontario, Canada
This example was outstanding. We send quite a few emails with Web Toolkit
Applications. Some email clients double-spaced the content and some did not,
making the appearance poor in some cases. HTML formatting would give a uniform
appearance. We also had problems with links in the email where the link wrapped
and the portion on the next line was no longer part of the link causing it to
fail. Savvy users had no problem, but we would get calls from others and have to
explain what to do over the phone. Now we can use anchors.
Rating: 5
add cc function in html_email procedure October 29, 2003
Reviewer: RICHARD from WASHINGTON
Hi Tom,

If I want to add cc: function in your html_email procedure, can I simply add the
following line

l_temp := l_temp || 'Cc: ' || p_to || chr(13) || chr(10);

Or I have to make other changes.

Please advice.
Followup:
that'll just update the headers -- you would need to add calls to

    utl_smtp.rcpt( l_connection, p_to );

to actually SEND them the email.  Those headers are just for "nice display on
the email client", they don't control who actually gets the mail
Rating: 4
Pl explain your comment "-- 1900 byte chunks" November 11, 2003
Reviewer: Vikas Sharma from Delhi India
Hi Tom,

Can you please explain your comment
-- Send the email in 1900 byte chunks to UTL_SMTP
Is this the max limit in a one call of utl_smtp.write_data.

But i was able to send more data w/o using the 1900 chunks.(while LOOP).
if i send the data in the 1900 chunk it slows the execution.

Regards,
Vikas Sharma
Followup:
you can pick any size upto 32k you want to pick
Rating: 5
December 01, 2003
Reviewer: Randy from Warren, MI
Tom,
  I've been having some problems sending HTML using this UTL_SMTP procedure.  I
think the problem is with the SMTP server, but I can not confirm.  I am able
send this procedure to one address successfully.  However, when I send the same
procedure using 22 addresses from a table with a cursor loop the message never
gets delivered.  Any ideas on why this is happening?  Is this a UTL_SMTP issue?

Thanks in advance!
Followup:
any errors?

what does the mail queue on the server look like?  how's about the "from
account", it should get any bounces.
Rating: 3
HTML not shown in outlook December 27, 2003
Reviewer: Vikas Sharma from Delhi India
Hi Tom,
Your routine works fine with the yahoo, hotmail client.
Is this is depend upon the domain also. Because when i send an HTML email to my
a/c vikas@m-tech.demon.co.uk and retrives it in the outlook express i get the
following.

begin

html_email(
     p_to            =>'vikas@m-tech.demon.co.uk',
     p_from          =>'vikas@m-tech.demon.co.uk',
     p_subject       =>'hemmm',
     p_text          => ' hemmlllllllllll',
     p_html          => '<B> Vikas <i> Sharma',
     p_smtp_hostname => 'mtlweb',
     p_smtp_portnum  =>25
 );
  end;

i get the following in my MS outlook.

--a1b2c3d4e3f2g1
content-type:text/html;

<B> Vikas <i> Sharma
--a1b2c3d4e3f2g1--

But when i send email using same routine to my hotmail a/c and retrive it in MS
outlook then it works fine.

Could you pl tell me what is wrong here.

Thanks and Regards,

Vikas Sharma
Followup:
sorry, i cannot really debug outlook.  we are just pumping out documented,
industry standard MIME encoded email.  They are not dealing with it properly.

I don't run any windows stuff so I cannot even take a look at it.  the standards
are all out there on the web, you'll have to play around and see what nuances
outlook is imposing on you (i do know that lots of people are using this with
outlook -- even inside oracle here where we use this procedure heavily -- and
other emails clients without issue.  could be an old version of outlook or
something).
Rating: 3
It worked December 28, 2003
Reviewer: Vikas Sharma from Delhi India
Hi Tom,

Thanks for your review.

I added the utl_tcp.CRLF after the following line in your routine.

l_temp := l_temp || 'Content-Type: multipart/alternative; boundary=' ||
            chr(34) || l_boundary ||  chr(34) || chr(13) ||
            chr(10)||utl_tcp.CRLF;

and it worked. I think after writing the header if we insert a blank line ie
utl_tcp.CRLF then outlook understands it.

Thanks and Regards,

Vikas Sharma
Followup:
thanks for the followup.

it should NOT require an extra blank line (we already have the crlf there --
chr(13) || chr(10) is a CR/LF pair.

but as least we know what tweak is required for outlook to be able to function.
Rating: 4
How to change some part of E-mail message to bold December 29, 2003
Reviewer: Arvind from India
Dear Tom,

I am using utl_smtp for generating mails from Oracle. Can you tell me that how
to change some portion of message to bold/underline.

Thanks
Followup:
you'd use HTML.

like above.
December 30, 2003 Anwar -- Thanks for the question regarding "Confusion in the interpretation of V$LIBRARYCACHE columns", version 7.3.2
You Asked
Hi Tom,

I'm a bit confused why two different columns are included in
V$LIBRARYCACHE to serve a single purpose i.e. PINS & GETS? If
the two are different then please explain the difference.

For your reference table structure is given below:

SQL> desc v$librarycache
 Name                            Null?    Type
 ------------------------------- -------- ----
 NAMESPACE                                VARCHAR2(15)
 GETS                                     NUMBER
 GETHITS                                  NUMBER
 GETHITRATIO                              NUMBER
 PINS                                     NUMBER
 PINHITS                                  NUMBER
 PINHITRATIO                              NUMBER
 RELOADS                                  NUMBER
 INVALIDATIONS                            NUMBER
 DLM_LOCK_REQUESTS                        NUMBER
 DLM_PIN_REQUESTS                         NUMBER
 DLM_PIN_RELEASES                         NUMBER
 DLM_INVALIDATION_REQUESTS                NUMBER
 DLM_INVALIDATIONS                        NUMBER
and we said...
They are very different and in general, have radically different
values:

ops$tkyte@8i> select namespace, gets, pins, pins-gets from
v$librarycache where gets <> pins;

NAMESPACE             GETS       PINS  PINS-GETS
--------------- ---------- ---------- ----------
SQL AREA           9568694  429224748  419656054
TABLE/PROCEDURE   22172223   64176942   42004719
BODY              31024133   31022250      -1883
TRIGGER              37165      37317        152
INDEX                 1236       1240          4
CLUSTER              48827      46019      -2808
PIPE               1582255    1582281         26

7 rows selected.

ops$tkyte@8i> select count(*) from v$librarycache;

  COUNT(*)
----------
         8

ops$tkyte@8i>

The GETS column indicates the number of times, since database
instance startup, that the system requests handles to library
objects in this namespace.

The PINS column indicates the number of times, since database
instance startup, that the system issues pin requests in order
to access objects in the cache.  This column shows the number of
times an item in the library cache was executed.  Whenever an
entity (cursor or db object) is accessed in cache it needs to be
pinned (PINS) to prevent it from being flushed out by the heap
manager.
Review & Followup

Rating: 3
PLS Explain more detail October 31, 2001
Reviewer: Frank from FL, US
Tom, Quote ur answer here:
"The GETS column indicates ... that the system requests handles ....
The PINS column indicates ... issues pin requests "

1)Could u give us the scenario when system requests handles, but does not issue
pin? or vise versa.
2)I think before the system issues pin requests, it has to request handle first,
so PINS should always greater than GETS. But that's not true according to the
data in the view, why?
Rating: 2
I'm still confused March 24, 2002
Reviewer: Erik Cook from seattle, wa
In tom's answer "The GETS column indicates the number of times, since database
instance startup, that the system requests handles to library objects in this
namespace." was confusing.

Franks question of: "I think before the system issues pin requests, it has to
request handle first, so PINS should always greater than GETS. But that's not
true according to the data in the view, why? " would help clarify things.

I guess we'll be confused for ever.
Rating: 3
pins and gets October 01, 2002
Reviewer: A reader
hi

if I run a query several times i can see gets and pins increasing so in that
case I dont understand how can  gets and pins differ from each other? I mean
under what situation would gets value differ from pins value

if I run

select * from emp

I will be requesting this query´s execution plan, parse tree etc (information)
so this would be a get, if then it´s executed (it is always executed, I dont see
under what circumstance it wont be executed) I get a pin so I will always have
one increment of gets and pins. I mean there wont be execution without previous
request no? So relationship between pins and gets wouldnt it be 1 to 1?
Followup:
You'll "get" more to parse.

You'll "pin" to run.

run over and over and "gets" will be less then the first time, so will pins as
you get and pin less objects for subsequent operations.

You might get 1000 things in order to parse something but pin them 2000 times
(eg: get me this definition, pin it -- unpin it -- pin it -- unpin it -- etc etc
etc)
Rating: 4
the term "pin" October 02, 2002
Reviewer: A reader
hello

I read this term "pin" quite a few times. What's the difference of pinning
stuffs in the shared pool with dbms_shared_pool and pin in v$librarycache?
Followup:
pinning something in the pool makes it "stick there"

when we pin something in the librarycache, we are making it "stick there" as
well but we pretty much go back and unpin it shortly after.  When you keep
something in the shared pool (some people call that pinning in the shared pool)
it stays there till you unkeep or physically change it
Rating: 5
Pins vs Gets October 03, 2002
Reviewer: Cindy from NY, USA
I found this in a Metalink forum. Is it accurate?

GETS - the total number of SQL requests sent to the database
PINS - the number of times requested SQL was found in memory (effectively the
number hits?)
RELOADS - total number of times requested SQL was not found in memory requiring
hard parse (effectively the number of misses?)

Thanks.
Followup:
No, what note is that?  we need to remove that.  look at v$librarycache -- most
of the entries don't have anything to do with just "sql".  It is too myoptic of
a view of this information.
Rating: 5
Gets vs Pins October 03, 2002
Reviewer: Cindy from NY, USA
Those definitions were from the Oracle Server-Enterprise and Standard Edition
Technical Forum. Someone asked (23-Oct-00) if those were correct interpretations
and Ken Robinson from Oracle replied "Yes, you have it".
Followup:
look at the namespace column, its not just about sql.

run some queryies in isolation and try to rectify the counts -- you won't be
able to.

there is no one to one correlation between "I submit a query and gets/pins will
be affected as such"
Rating: 3
Please give a specific, basic example October 03, 2002
Reviewer: Robert from Memphis, USA
Tom,

I think many of us are sympathetic to Frank and Eric's plight. Can you please
give us a specific, basic example that will illustrate gets, pins (and maybe
reloads) in the library cache.

Thanks,

Robert
Followup:
The definitions are here:

.....
The GETS column indicates the number of times, since database instance startup,
that the system requests handles to library objects in this namespace.

The PINS column indicates the number of times, since database instance startup,
that the system issues pin requests in order to access objects in the cache.
This column shows the number of times an item in the library cache was executed.

 Whenever an entity (cursor or db object) is accessed in cache it needs to be
pinned (PINS) to prevent it from being flushed out by the heap manager.

 ....

when we execute a query -- we need a handle to some library cache object(s).  If
we already GOT the handle on some previous call (eg: we have an open cursor we
are just re-executing) we won't have to GET it again.  We will have to PIN it
for a second, to check out the handle, make sure all is OK, nothings changed
(think of a PIN like a LOCK -- we need to lock it to execute/use it).

A reload is when something we had in the library cache went away (flushed,
shared pool too small perhaps) and we reloaded it -- OR the definition changed.
Rating: 5
is library cache lock latch used for pin? October 03, 2002
Reviewer: A reader
Hi

Which latch is used when a sql cursor is get or pinned?

Also, if when we query for some library objects (get) and we found it then we
dont need to get, it's then pinned however after get one objects is invalidated
so the pin become useless, isnt this a reload?
Followup:
library cache

your last paragraph is unparsable by my brain, no idea what you might have been
trying to say.
Rating: 4
reload and latches October 04, 2002
Reviewer: A reader
Hi

What I am saying is if the definition of reload is:

when Oracle look i shared pool for a SQL stmt, if it finds it then it's a get,
after that it send an execute call, if the SQL is invalidaded before the shared
cursor is pinned then there will be a reload, i.e the SQL has to be reparsed

Also regarding library cache latch, you said when we pin a shared cursor we
would need library cache latch to "lock" the cursor temporary, from other
threads I read that bind variables minimizes library cache latch contention. I
dont understand how bind variable can avoid latch contention because we always
have to pin (or lock) shared cursor if we want to execute it no? No matter bind
variables are used or not. So at the end of the day we end up with same number
of executions
Followup:
You might have to pin 1,000 times to do a hard parse -- cause you need to pin
hundreds of objects in the libary cache in order to do the parse (the definition
of the table, the columns, the indexes, etc etc etc)

When you use bind variable and we do a soft parse -- we do less work --
resulting in less pins.
Rating: 5
how can I demostrate this October 13, 2002
Reviewer: A reader
Hi

When executing a query, how can I demostrate If I already GOT the handle on some
previous call (eg: I have an open cursor I am just re-executing) I won't have to
GET it again

I opened two SQL*PLUS sessions and tried this

first session:
declare
a number;
b number;
begin
a := 7934;
select empno
into b
from emp  where empno = a;
end;
/

second session:
here just to see if GETS and GETHITS are increased whenever I run the PL/SQL in
another session

I always see GETS increasing even I suppose with my PL/SQL I have a open cursor.
When I query v$open_cursor in second session I see

SADDR           SID USER_NAME                      ADDRESS  HASH_VALUE SQL_TEXT
-------- ---------- ------------------------------ -------- ----------
------------------------------------------------------------
02200BC0          7 LSC                            0328FD78 3373673168 declare
a number; b number; begin a := 7934; select empno i
02200BC0          7 LSC                            0325CAB8 4151506772 SELECT
EMPNO   FROM EMP  WHERE EMPNO = :b1
02201C98          9 LSC                            03273858 4065929129 select *
from v$open_cursor

so I do have the cursor opened but then why GET still increases?
Followup:
You gotta GET it before you can execute it.

Why WOULDN'T the gets increase?  I'd be more worried if it didn't personally.  I
mean "where did the query plan come from, if we didn't 'get' it".

The soft parses on that query will increase by one every time you parse it --
thats the only thing you can look at. Don't over analyze this here.
Rating: 4
but you said if we have open cursor October 14, 2002
Reviewer: A reader
Hi

But you said if we have the cursor open then I dont need to GET again or I
missread... :-(
Followup:
you are running alot more then just a query there -- you are submitting an
anonymous block that just HAPPENS to have a query inside of it.

#1 The anonymous block is getting soft parsed

#2 The anonymous block is NOT a procedure, hence the cursor cannot be cached
(you NEVER really run a anonymous block twice see -- it is NOT a named object)

#3 Your other session is executing TONS of sql itself (to log in, to verify
security checks to query the v$ tables itself)!!!
Rating: 5
pseudo code for accessing the shared pool August 28, 2003
Reviewer: Alberto Dell'Era from Milano, Italy
So, if the kernel were written in Java instead of C, would the kernel developers
code something like the following in order to use the shared pool ?

Handle handle = libraryCache.get (....);

handle.pin();

handle.getAttribute (...);
handle.setAttribute (...);

handle.unPin();
Followup:
"if the kernel were written in Java"...  oh that sends chills down my spine!

yes, you set a mutex, do your work, release the mutex.
Rating: 5
pinning through the handle August 29, 2003
Reviewer: Alberto Dell'Era from Milan, Italy
> "if the kernel were written in Java"...  oh that sends chills down my spine!

ORA-0666 Garbage Collection in Progress - please retry later - thanks for your
cooperation

> yes, you set a mutex, do your work, release the mutex.

And all through the handle I suppose - you can't pin an object without an handle
(that was the main part of the question). Is this correct ?
Followup:
the handle "is" the object in a manner of speaking, so yes..

Instead of ora-666 with a simple message like that -- it would be the standard
java error stack of 5,000 or so lines...
Rating: 3
Q on INVALIDATIONS December 29, 2003
Reviewer: Anil Pant from India
Hi,
If value of Invalidations is 0 it means its not yet valid. For example if an
Select on Table T1. Now If I alter the table the value becomes 1. If I alter
multiple times does it mean that the counter increases. Means if I alter N times
the value will be N.
Am I correct ?
Followup:
no, not correct.

say you

a) parse a query against t
b) alter the table (that'll invalidate (a))
c) alter the table (that'll do nothing to (a), its already invalid)
d) alter the table (see (c))
e) parse that query again

invalidations of 1.
3 alters..
Rating: 4
Reg Invalidations December 29, 2003
Reviewer: Anil Pant from India
Hi,
Thks for the reply. Yes I got what u said, but for some of the rows the values
were greater than 1 in my DB. What does a value 3 indicate ? Or mean to you say
any thing other than 0 is invalid. That's it does not matter whether its 1,2,3
or 100. Am I correct ?
Followup:
if you (assuming empty shared pool at the beginning of this exercise)

select * from t;
alter table t; <<< invalidations = invalidations + 1;
select * from t;
alter table t; <<< invalidations = invalidations + 1;
alter table t;
select * from t;
select * from t;

so, 3 alters, invalidations increase by 2 in that case.  invalidations is a
COUNTER -- how many times did we invalide stuff.  this just shows it is not a
function of how many alters have been done, but rather how many statements have
been invalidated over time.
December 30, 2003 Shabbir -- Thanks for the question regarding "Redo Log Buffer Size - why is it 512k?", version 8.0.3
You Asked
Hi Tom,

This is what I came across for Oracle 8.0.3 Ent on NT 4.0, SP 4.

The size of the redo log buffer does not change but remains
constant at 50K. Whatever value I specify in the parameter file,
only 50K is the size that is displayed.

The value specified in the parameter file currently is 164K.
When u fire a select on v$parameter, it shows 164 K but on
running "show sga" from server manager, it shows 50K.

I even tried with a redo size of 8K as well. But the same thing
happens again.

Do get back at the earliest.

The following is the server manager output that u require.

Connected.
SVRMGR> select * from v$version;
BANNER
----------------------------------------------------------------
Oracle8 Release 8.0.3.0.0 - Production
PL/SQL Release 8.0.3.0.0 - Production
CORE Version 4.0.3.0.0 - Production
TNS for 32-bit Windows: Version 8.0.3.0.0 - Production
NLSRTL Version 3.3.0.0.0 - Production
5 rows selected.

SVRMGR> show sga
Total System Global Area                        148294072 bytes
Fixed Size                                          46136 bytes
Variable Size                                   105780608 bytes
Database Buffers                                 41943040 bytes
Redo Buffers                                       524288 bytes

SVRMGR> set charwidth 30
Charwidth                       30
SVRMGR> select name, value
     2> from v$parameter
     3> where name= 'log_buffer';
NAME                           VALUE
------------------------------ ------------------------------
log_buffer                     163840
1 row selected.

One more correction that I would like to make, this is Oracle 8
Std and not Ent as I had mentioned earlier in my query earlier.

Regards,
Shabbir.
and we said...
Your redo buffers are 512k, not 50k.

The log_buffer parameter can be set smaller than 512K but
if it is less than a port-specific value then it is increased to

this value. The default minimum is 512K but this can be changed
on a platform basis dependant upon what is felt to be the most
optimum value for the given port.  That value is compiled into
Oracle and cannot be changed.  On your platform -- it must be
512k.

So, if you set it larger then 512k, you'll see a difference in
show SGA.  Setting it lower will have no effect.
Review & Followup

Rating: 3
help, please December 24, 2002
Reviewer: nadya from Russia
How can we know port-specific walue?

Oracle 8.1.6.0.0 Ent on NT 4.0, SP 4.

1. system> show sga

Total System Global Area   67183884 bytes
Fixed Size                    70924 bytes
Variable Size              34267136 bytes
Database Buffers           32768000 bytes
Redo Buffers                  77824 bytes

2. Init.ora:
log_buffer = 32768 (this value was suggested Oracle during installation and we
agree).

3. system> select * from v$sgastat
  2  where name='log_buffer';

POOL        NAME                            BYTES
----------- -------------------------- ----------
            log_buffer                      66560

4. system> select name,value from v$sga where name='Redo Buffers';

NAME                      VALUE
-------------------- ----------
Redo Buffers              77824
5. system> select value from v$parameter where
  2  name='log_buffer';

VALUE
--------------------------------------------
32768

Why Redo Buffers=77824 bytes, not 512K, as documented?
And what does this strange number 32768 mean?
Followup:
You now know what the port specific minimum is -- you just found it.

32768 is not a "strange" number -- it is 32K

ops$tkyte@ORA920> select 32768/1024 from dual;

32768/1024
----------
        32

and the other numbers are also kbytes:

ops$tkyte@ORA920> select 66560/1024, 77824/1024 from dual;

66560/1024 77824/1024
---------- ----------
        65         76

Apparently, the smallest you are going to make your redo log buffers in that
version of Oracle will be 76K
Rating: 3
Thanks December 25, 2002
Reviewer: Nadya from Russia
Tom, thanks for your help,
I realize, of cource, that 77 824 bytes=76K and so on,
but I was confused because the discription of parameter
log_buffer is "....Default Value  : Maximum 500K or 128K * CPU_COUNT, whichever
is greater". We have one CPU, so I expected to see the default value 500K, not
76K.
Followup:
but you set it to 32k -- hence the default value is meaningless here in this
context.  You overrode the default, with a value that is too small -- so we
simply increased to the minimum allowed on your platform.
Rating: 5
Reader December 28, 2002
Reviewer: A reader
Tom,

The difference of 76 - 65 = 11k. Is the 11k blocks used
to protect log_buffer from corruption. If so , what is the
s/w logic used, so that the log_buffer corruption is
prevented in certain scenarios

Thanks
Followup:
some things are just rounded up (especially in 9i) but there are also "guard"
pages.

A number of the Oracle Database ports attempt to protect these global regions
(particularly the log buffer) from erroronious access by stray pointers (e.g.
pointers writing past the end of a valid region) using a guard page above and
below the region.
Rating: 3
About log_buffer December 30, 2003
Reviewer: Bobby
In the below example (taken from previous question)
select * from v$sgastat where name='log_buffer';

POOL        NAME                            BYTES
----------- -------------------------- ----------
            log_buffer                      66560

select value from v$parameter where name='log_buffer';

VALUE
--------------------------------------------
32768

Why is value from v$parameter is different from v$sgastat ?
Followup:
because we impose OS specific minimums on log_buffer.

(see original answer to this question!)
Rating: 3
CPU_COUNT December 30, 2003
Reviewer: Bobby from India
Hi,
Is there any way to find out CPU_COUNT from any of the v$ views ?
Followup:
ops$tkyte@ORA9IR2> show parameter cpu_count;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
cpu_count                            integer     1
ops$tkyte@ORA9IR2> select value from v$parameter where name = 'cpu_count';

VALUE
----------------------
1
December 30, 2003 Mannan -- Thanks for the question regarding "cummualtive sum", version 7.3.2
You Asked
how can i have cummulative sum of previous column of sql query??
and we said...
See
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:305416600201
...
Review & Followup

Rating: 5
Cummulative Sum December 30, 2003
Reviewer: ARC from IND
Hi Tom,
I am using below query to get cummulativies for month, quarter and year.
When I am using in SQL*Plus it is working fine. When using in stored procedure
to insert the result set into another table I am getting error.
QUERY:
     SELECT PL_TIER0_DSC,
               PL_TIER1_DSC,
               PL_TIER2_DSC,
               PL_TIER3_DSC,
               SF_TIER0_DSC,
               SF_TIER1_DSC,
               SF_TIER2_DSC,
               SF_TIER3_DSC,
               SF_TIER4_DSC,
               W_NUM,
               M_NUM,
               Q_NUM,
               Y_NUM,
               CY_SLS_AMT CY_WTD_AMT,
               PY_SLS_AMT PY_WTD_AMT,
               SUM(CY_SLS_AMT) OVER (PARTITION BY PL_TIER0_DSC, PL_TIER1_DSC,
PL_TIER2_DSC, PL_TIER3_DSC, SF_TIER0_DSC,
                                                     SF_TIER1_DSC, SF_TIER2_DSC,
SF_TIER3_DSC,SF_TIER4_DSC,
                                               M_NUM
                                   ORDER BY W_NUM) CY_MTD_AMT,
               SUM(PY_SLS_AMT) OVER (PARTITION BY PL_TIER0_DSC, PL_TIER1_DSC,
PL_TIER2_DSC, PL_TIER3_DSC, SF_TIER0_DSC,
                                                     SF_TIER1_DSC, SF_TIER2_DSC,
SF_TIER3_DSC,SF_TIER4_DSC,
                                               M_NUM
                                   ORDER BY W_NUM) PY_MTD_AMT,
            SUM(CY_SLS_AMT) OVER (PARTITION BY PL_TIER0_DSC, PL_TIER1_DSC,
PL_TIER2_DSC, PL_TIER3_DSC, SF_TIER0_DSC,
                                                     SF_TIER1_DSC, SF_TIER2_DSC,
SF_TIER3_DSC,SF_TIER4_DSC,
                                               Q_NUM
                                  ORDER BY W_NUM) CY_QTD_AMT,
            SUM(PY_SLS_AMT) OVER (PARTITION BY PL_TIER0_DSC, PL_TIER1_DSC,
PL_TIER2_DSC, PL_TIER3_DSC, SF_TIER0_DSC,
                                                     SF_TIER1_DSC, SF_TIER2_DSC,
SF_TIER3_DSC,SF_TIER4_DSC,
                                               Q_NUM
                                  ORDER BY W_NUM) PY_QTD_AMT,
            SUM(CY_SLS_AMT) OVER (PARTITION BY PL_TIER0_DSC, PL_TIER1_DSC,
PL_TIER2_DSC, PL_TIER3_DSC, SF_TIER0_DSC,
                                                     SF_TIER1_DSC, SF_TIER2_DSC,
SF_TIER3_DSC,SF_TIER4_DSC,
                                               Y_NUM
                                  ORDER BY W_NUM) CY_YTD_AMT,
            SUM(PY_SLS_AMT) OVER (PARTITION BY PL_TIER0_DSC, PL_TIER1_DSC,
PL_TIER2_DSC, PL_TIER3_DSC, SF_TIER0_DSC,
                                                     SF_TIER1_DSC, SF_TIER2_DSC,
SF_TIER3_DSC,SF_TIER4_DSC,
                                               Y_NUM
                                  ORDER BY W_NUM) PY_YTD_AMT

      FROM TB_EIP001_AGG_SLS_SUMMARY;
I am using Oracle version
Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production
PL/SQL Release 8.1.7.0.0 - Production
CORE    8.1.7.0.0       Production
TNS for 32-bit Windows: Version 8.1.7.0.0 - Production
NLSRTL Version 3.4.1.0.0 - Production
Please advise me to overcome this.
Thanks in advance.

ARC
Followup:
read
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3027089372477
you'll have to use dynamic sql with analytics in 8i
December 30, 2003 Pascal -- Thanks for the question regarding "Oracle8i on Unix or OPS 8i on Win2000", version 8.1.7
You Asked
We are running with Oracle Entreprise Edition 8i on Win2000 Advanced Server
(OLTP Application) :
4 CPU - 4 GB RAM - 3GB per process (/3GT)
We need computer more stronger than that we have today.
I would to know if we need to change to Unix (Solaris n CPU n GB Ram for
example)
or we can continue to use Win2000 with Oracle Parallel server and multiple node
Win2000 ?
Please give me an objective answer.
Pascal
and we said...
A question about operating systems and hardware and you want objectivity ;)

Hmmm....

You can build very large systems on either platform.  Unix does not imply "big",
Win2k -- in the properly, massively trained hands -- can be used to do big.

If you don't want to have to make architectural changes to your application, you
may want to consider 9i RAC instead of 8i OPS.  RAC is the next generation OPS
and does not mandate that you design your application to work well with it.  It
runs any "real application", not just specially designed ones.

So -- the objective answer -- you can scale with both.  A pre-cursor to this is
having a qualified team that has done it (be it on Unix or Win2k).
Review & Followup

Rating: 4
Question on RAC June 12, 2002
Reviewer: Andre from Brazil
I'm amazed at a situation regarding cache fusion on RAC you might clarify to me.
It's in a Consistent Read Block Transfer scenario. For example:

1) Instance A reads block at SCN 123 from disk
2) A commits a transaction and block is now at SCN 124
3) Instance B requests that block from GCS
4) Instance B receives the before image with SCN 123 from A

Why can't B just see the 124 block -- not 123 -- if it's already "committed" ?
Followup:
sorry -- no context on the example, fragment taken out of context.  cannot
(wont) comment on it.
Rating: 3
June 13, 2002
Reviewer: Tianhua Wu from Atlanta, GA
I am not sure for RAC because I have not use it.  But for OPS, check what is
your max_commit_propagation_delay, there could be a up to 7 second delay.
Rating: 3
June 05, 2003
Reviewer: A reader
Hi Tom,
I am looking for information on max_commit_propagation_delay parameter. Could
you pls suggest optimum value for this parameter? How much does it impact the
performance of OLTP and Batch operations?

PS: since this is the only thread with search word
"max_commit_propagation_delay", i posted the followup here. If you think its not
the appropriate here, i'll put it again as a new question.
Followup:
I wouldn't change it unless directed to.
Rating: 2
RAC on Win2k December 30, 2003
Reviewer: H.S.Anand from New Delhi, India
Hi Tom,
We are moving from Oracle 8i (8.1.7) to RAC (9.2.0.1.0) on windows 2000 adv.
server?
I understand that "active-active" setup means that both nodes must see
unpartitioned, shared disks at the same time - before even starting any oracle
related activity!

But I am able to see them only from one node.

Moreover my clustersetup.exe always fails at 83%. I am following it up "right
now" with metalink. tar#(3613614.999).

Can you please clarify once and forall, is my understanding of the active-active
configuraiton correect?

If this is out of context, please tell me the time in GMT+5:30 when I can see an
"i am accepting questions" mode on your site!!

I'd really appreciate any assistance!
Please help.
Followup:
for installation issues -- please work those through support.  I do not own any
computers that run windows at all.

Active Active means simply that both Oracle instances will be 100% able to read
and write the entire database simultaneously (both active).

They both need to be able to see the disks -- you will either be using a
clustered file system (both can see) or RAW devices.

if the setup is failing, you'll really have to work that with support.
Rating: 5
Thank you December 31, 2003
Reviewer: H.S.Anand from New Delhi, India
Thank you very much tom, for removing once and for all this confusion between
"active-active" mode and "active-passive" mode.

I am taking the setup issue with support.
December 30, 2003 PRATTY -- Thanks for the question regarding "CURSOR", version ORACLE 8i 8.1.5
You Asked
Hi Tom,
    I want to declare multiple cursors based on the values
passed through a procedure ie, only where conditions of the
cursors will change. The body of the procedure is
same for all the cursors.
  Pls. suggegst a solution.

Thanking you,

regards,
Pratty.
and we said...
Sounds like a good use of REF CURSORS to me.  Lets say you
wanted to build a generic routine that would look at the inputs
passed to it and build a WHERE clause for each NON-NULL
parameter passed.  This would result in a large number of
statically defined cursors so we'll use a ref cursor instead.

I'll demonstrate below.  I'll write a write that will print out
some EMP data.  This routine will take upto 3 inputs to
constrain the result set.  I want to have upto 8 different
cursors possible here

o 1 with NO where clause (all inputs null)
o 3 with a single predicate
o 3 with "pairs" of predicate conditions
o 1 with all three predicate conditions

Additionally, since the use of BIND VARIABLES is one of the MOST
important things in programming Oracle -- I'll want to make sure
I use them as well.  This will be trickly since I don't know if
I'll have 0, 1, 2, or 3 of them until runtime.  I'll use an
application context to solve that problem.

Here is a sample implementation:

ops$tkyte@ORA8I.WORLD> create or replace context MY_CTX using
MY_PROCEDURE
  2  /

Context created.

That created our application context and bound it to our yet
to be created procedure "MY_PROCEDURE".  Note that only
MY_PROCEDURE will be able to set values in this context.  See
http://osi.oracle.com/~tkyte/article2/index.html
for more info on application contexts and their use

ops$tkyte@ORA8I.WORLD> create or replace
     procedure p ( p_str in varchar2 )
  2  is
  3     l_str   long := p_str || chr(10);
  4     l_piece long;
  5     n       number;
  6  begin
  7      loop
  8          exit when l_str is null;
  9          n := instr( l_str, chr(10) );
 10          l_piece := substr( l_str, 1, n-1 );
 11          l_str   := substr( l_str, n+1 );
 12             loop
 13                exit when l_piece is null;
 14                dbms_output.put_line( substr( l_piece, 1,
                                                        250 ) );
 15                l_piece := substr( l_piece, 251 );
 16          end loop;
 17     end loop;
 18  end;
 19  /

Procedure created.

P is just a little procedure I use to print things out nicer
then dbms_output would.  I use it below to dump the dynamically
generated query so we can see what was built for each execution.
 It is not really relevant to the example, just part of the
demonstration...

ops$tkyte@ORA8I.WORLD> create or replace
  2  procedure my_procedure( p_ename   in varchar2 default NULL,
  3                          p_hiredate  in date default NULL,
  4                          p_sal       in number default NULL)
  5  as
  6      type rc is REF CURSOR;
  7
  8      l_cursor rc;
  9      l_query  varchar2(512)
 10               default 'select * from emp where 1 = 1 ';
 11
 12      cursor l_template is select * from emp;
 13      l_rec  l_template%rowtype;
 14

Here I use what I call a "TEMPLATE" cursor.  I like to use
these with my ref cursors.  I use them to define a record to
fetch into.  Here, in this simple example, I could have skipped
it and just defined l_rec as EMP%rowtype -- but I wanted to show
how this would work if you didn't select * from a single table
but had many columns from many tables.  This just helps me
create a nice record.  The template query ONLY has a SELECT and
a FROM.  I never put a WHERE clause on it (even when joining)
since I never use it any where.  I just use it to get the
default datatypes, names and so on for a record definition right
below it.

 15  begin
 16
 17      if ( p_ename is NOT NULL ) then
 18          dbms_session.set_context( 'MY_CTX', 'ENAME',
 19
'%'||upper(p_ename)||'%');
 20          l_query := l_query ||
 21               ' and ename like
 22                 sys_context( ''MY_CTX'', ''ENAME'' ) ';
 23      end if;
 24

for each input -- i'm inspecting it to see if it is non-null.
If it is, I add to the where clause and set the value in the
context.  Notice how in the where clause -- I always use the
SYS_CONTEXT function.  I NEVER put the literal value in to the
query (that would be very bad and would trash the shared pool --
very extremely important to use bind variables).  Note also the
use of '' to get a single ' into the where clause!

 25      if ( p_hiredate is NOT NULL ) then
 26          dbms_session.set_context( 'MY_CTX', 'HIREDATE',
 27                    to_char(p_hiredate,'yyyymmddhh24miss'));
 28          l_query := l_query ||
 29                ' and hiredate >
 30                    to_date(
 31                       sys_context( ''MY_CTX'',
 32                                    ''HIREDATE'' ),
 33                       ''yyyymmddhh24miss'') ';
 34      end if;

Note here how I am careful to preserve the date and time
component -- if necessary!  Always wrap the sys_context in a
TO_DATE call if you are comparing to a DATE to avoid implicit
conversions in the query at runtime!

 35
 36      if ( p_sal is NOT NULL ) then
 37          dbms_session.set_context( 'MY_CTX', 'SAL', p_sal);
 38          l_query := l_query ||
 39                ' and sal >
 40                   to_number(
 41                       sys_context( ''MY_CTX'',
 42                                    ''SAL'' )
 43                             ) ';
 44      end if;
 45

Same caveat for the NUMBER here.  Use TO_NUMBER to avoid
IMPLICIT conversions

 46      p( l_query );
 47
 48      open l_cursor for l_query;
 49
 50      loop
 51          fetch l_cursor into l_rec;
 52          exit when l_cursor%notfound;
 53
 54          dbms_output.put_line( l_rec.ename || ',' ||
 55                                l_rec.hiredate || ',' ||
 56                                l_rec.sal );
 57      end loop;
 58
 59      close l_cursor;
 60  end;
 61  /

Procedure created.

 and that is it.  I now have a routine that will open 1 of 8
possible different cursors.  Here is a small test run just to
see how it works

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> exec my_procedure
select * from emp where 1 = 1
SMITH,17-dec-1980 00:00:00,800
ALLEN,20-feb-1981 00:00:00,1600
WARD,22-feb-1981 00:00:00,1250
JONES,02-apr-1981 00:00:00,2975
MARTIN,28-sep-1981 00:00:00,1250
BLAKE,01-may-1981 00:00:00,2850
CLARK,09-jun-1981 00:00:00,2450
SCOTT,09-dec-1982 00:00:00,3000
KING,17-nov-1981 00:00:00,5000
TURNER,08-sep-1981 00:00:00,1500
ADAMS,12-jan-1983 00:00:00,1100
JAMES,03-dec-1981 00:00:00,950
FORD,03-dec-1981 00:00:00,3000
MILLER,23-jan-1982 00:00:00,1300
KING,,5

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD> exec my_procedure( p_ename => 'a' )
select * from emp where 1 = 1  and ename like
sys_context( 'MY_CTX', 'ENAME' )
ALLEN,20-feb-1981 00:00:00,1600
WARD,22-feb-1981 00:00:00,1250
MARTIN,28-sep-1981 00:00:00,1250
BLAKE,01-may-1981 00:00:00,2850
CLARK,09-jun-1981 00:00:00,2450
ADAMS,12-jan-1983 00:00:00,1100
JAMES,03-dec-1981 00:00:00,950

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD> exec my_procedure( p_sal => 1000 )
select * from emp where 1 = 1  and sal >
to_number(
sys_context( 'MY_CTX',
'SAL' )
)
ALLEN,20-feb-1981 00:00:00,1600
WARD,22-feb-1981 00:00:00,1250
JONES,02-apr-1981 00:00:00,2975
MARTIN,28-sep-1981 00:00:00,1250
BLAKE,01-may-1981 00:00:00,2850
CLARK,09-jun-1981 00:00:00,2450
SCOTT,09-dec-1982 00:00:00,3000
KING,17-nov-1981 00:00:00,5000
TURNER,08-sep-1981 00:00:00,1500
ADAMS,12-jan-1983 00:00:00,1100
FORD,03-dec-1981 00:00:00,3000
MILLER,23-jan-1982 00:00:00,1300

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD> exec my_procedure( p_hiredate =>
add_months(sysdate,-240) )
select * from emp where 1 = 1  and hiredate >
to_date(
sys_context( 'MY_CTX',
'HIREDATE' ),
'yyyymmddhh24miss')
ALLEN,20-feb-1981 00:00:00,1600
WARD,22-feb-1981 00:00:00,1250
JONES,02-apr-1981 00:00:00,2975
MARTIN,28-sep-1981 00:00:00,1250
BLAKE,01-may-1981 00:00:00,2850
CLARK,09-jun-1981 00:00:00,2450
SCOTT,09-dec-1982 00:00:00,3000
KING,17-nov-1981 00:00:00,5000
TURNER,08-sep-1981 00:00:00,1500
ADAMS,12-jan-1983 00:00:00,1100
JAMES,03-dec-1981 00:00:00,950
FORD,03-dec-1981 00:00:00,3000
MILLER,23-jan-1982 00:00:00,1300

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD> exec my_procedure( p_ename => 'a', p_sal
=> 2000 )
select * from emp where 1 = 1  and ename like
sys_context( 'MY_CTX', 'ENAME' )  and sal >
to_number(
sys_context( 'MY_CTX',
'SAL' )
)
BLAKE,01-may-1981 00:00:00,2850
CLARK,09-jun-1981 00:00:00,2450

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD>
Review & Followup

Rating: 5
Lots of help, I was adrift on reference cursors August 12, 2001
Reviewer: Harrison from Haymarket, VA USA
Thanks for the  help.  The example, as often happens with
your answers, taught me more than one thing (three or
four, in fact).  I started reading another response, followed a link to this and
picked up several things that
will help (the page linked here was a '1=1' question, which
was also interesting).
Rating: 5
August 20, 2001
Reviewer: Helena Markova from Bratislava, Slovakia
Rating: 4
Most advanced August 28, 2001
Reviewer: andrew from CA, USA
Interesting, I never knew about sys_context() in this context :) It certainly is
a common scenario and this approach overcomes having a separate query for each
permutation.
Rating: 5
August 30, 2001
Reviewer: Srinivas Atreya from Hyderabad, India
Simply Brilliant, never knew about the context stuff...--:)
Rating: 5
Is there a way to avoid giving the select multiple number of times November 06, 2001
Reviewer: Nag
function test ( params )  is

rc refcursor;

lv_rc rc;

begin

open rc for

Select column list

from

(select same set of columns
from same set of from same tables
where different conditions
union

select   same set of columns
from same set of from same tables
where different conditions
union

select same set of columns
from same set of from same tables
where different conditions
union
select
same set of columns
from same set of from same tables
where different conditions)

return rc;

end test;

Tom, as you see iam having to give the same select with different where clause
again and again, what is the best way  to handle this.

The objective is to reduce the code
Followup:
It is called an OR

select same set of columns
from same set of tables
where (condition1) or (condition2) or (condition3)

It could be somewhat more efficient then a UNION as well as:

A union B union C

is really

distinct( sort( a + b + c ) )

you'll skip a non-necessary sort/distinct step.

(please ask only relevant followups in this space.  I only answered this one
cause its sort of related and I really like the example -- by touching it, it
goes back onto the front page.  I'm not answering the other one about counting
commas -- it has nothing to do with the original question at all)
Rating: 5
Dynamic cursor November 07, 2001
Reviewer: William from Ontario Canada
This is really helpful and handy too. But I would like to know the advantage of
using context over building the predicate dynamically eg.

l_query := l_query || ' and empname like '''||p_ename||'''';

Thanks
Followup:
Get my book -- its all about bind variables.

Bind variables are SO crucial, so important -- you MUST use them.

If you do something like:

  for i in 1 .. 1000
  loop
     execute immediate 'insert into t values ( ' || i || ')';
  end loop;

90%, yes 90%, of your time will be spent parsing.  If you on the other hand:

execute immediate 'insert into t values ( :x )' using I;

you will spend a significantly less amount of time parsing.  And if you just:

insert into t values ( i );

you'll spend almost NO time.

Parsing (soft and hard) latches the library cache.  A latch is a lock by any
other name.  You are serializing operations by over parsing.  it will limit your
scalability, decrease your performance and you will FAIL if you do not use bind
variables!
Rating: 5
A non dynamic alternative November 08, 2001
Reviewer: Jeroen from Singapore
Hi Tom,

I always appreciate your answers, and I really appreciate the techniques you are
demonstrating.
But looking at this specific example, where the input parameters and table are
hardcoded, I was wondering whether the construct underneath would not solve the
problem as well.

create or replace
procedure my_procedure( p_ename     in varchar2 default NULL,
                        p_hiredate  in date default NULL,
                        p_sal       in number default NULL)
as

cursor c_emp
       (b_ename      varchar2
       ,b_hiredate   date
       ,b_sal        number
       )
is
select  *
from    emp    e
where   e.ename    = decode( b_ename
                            ,null,       e.ename
                            ,            b_ename
                            )
and     e.hiredate = decode( b_hiredate
                            ,null,       e.hiredate
                            ,            b_hiredate
                            )
and     e.sal     = decode( b_sal
                            ,null,       e.sal
                            ,            b_sal
                            );

begin
        for l_recin c_emp
           (b_ename      => p_ename
           ,b_hiredate   => p_hiredate
           ,b_sal        => p_sal
           )
        loop
          dbms_output.put_line( l_rec.ename || ',' ||
                                l_rec.hiredate || ',' ||
                                l_rec.sal );
       end loop;
end;
/
Followup:
Yes it would HOWEVER, in the real world it would not perform very well at all.

You see -- your approach would result in a query that could only be optimized by
using a FULL TABLE SCAN in all cases -- indexes and other techniques would be
ruled out!

The dynamic approach has the advantage that the optimizer has a good query to
work with -- it'll get queries like:

select * from emp where ename = :bv;

select * from emp where hiredate = :bv1 and sal = :bv2;

It can optimize each one (and it'll only do that once and then stash the plan
away for reuse later).  Each one can and probably will have a different plan.
One query can use an index on ENAME, the other on SAL or HIREDATE or SAL &
HIREDATE.

Your query obviates the use of indexes in 100% of the cases (even with an
function based index -- there would be no chance).

I would definitely 100% go for dynamic SQL in this case.
Rating: 5
How to reset complete context November 09, 2001
Reviewer: Frank from NL
Great example.
Is it possible to reset the entire context any other way then looping through
list_context output?
Followup:
Well, you could iterate over the rows in SESSION_CONTEXT as well (it is a table
with your current context values).

But yes, you'll be iterating over something to reset the entire context -- the
API only does entry by entry.
Rating: 4
CURSOR December 10, 2001
Reviewer: Niloufar from Austin, TX
Tom,

Thank you for your complete and helpful answer.

In another article I had asked you for your help in writing a procedure that can
handle a list of elements as the parameter to the procedure to be used in a
"where clause".  Could that same technique (creating a type in SQL and using the
in_list function, etc...) be used in "my_procedure" to create the "where
clause"?

Here is what I am trying to do:

- create procedure
  my_procedure(clause1_par IN varchar2(30),
               clause2_par IN varchar2(10))

  where clause1_par and clause2_par can be a list

- In the body of my_procedure I'd like to be able to create the sql statement:

  select * from table_name
  where field1 in (clause1_par)
  and field2 in (clause2_par)

Couple of questions:

1- Can I still use the in_list function in "my_procedure" to receive a list of
elements as parameter?

2- If I have two parameters and both are lists, how can I figure out where the
first parameter's elements end and the next parameter's list start?  Is this
doable?

Thanks again for all your help.  Please let me know if my question is not clear.
Rating: 5
Can this be applied to Execute Immediate? March 14, 2002
Reviewer: Tim Glasgow from Belfast
I expect that this can be applied to execute immediate also and it does not
necessarily only apply to ref_cursors.
Followup:
Yes, you can use this as a method to "bind" any SQL insert/update/delete/select
(and in 9i MERGE) command.
Rating: 5
Problems with dbms_session March 14, 2002
Reviewer: Tim Glasgow from Belfast
I am getting the following error when I use dbms_session.set_context. The user
has been granted execute privileges from sys and still this doesn't make any
difference. A contributor from RevealNet said:

"...they can run DBMS_SESSION OK (the error message indicates that they are in
it), but they're not allowed to do whatever it is that they are asking to do via
DBMS_SESSION"

ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_SESSION", line 58
ORA-06512: at "PROP.AGENTPROPERTYREPORTS", line 269
ORA-06512: at line 2
Followup:
The contributor from revealnet is dead on.

A context is bound to a plsql procedure/function or package.  You cannot call
dbms_session directly.  It works like this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace context my_ctx USING
my_procedure
  2  /

Context created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure my_procedure(
p_name in varchar2, p_value in varchar2 )
  2  as
  3  begin
  4          dbms_session.set_context( 'my_ctx', p_name, p_value );
  5  end;
  6  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_session.set_context( 'my_ctx',
'hello', 'world' );
BEGIN dbms_session.set_context( 'my_ctx', 'hello', 'world' ); END;

*
ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_SESSION", line 62
ORA-06512: at line 1

ops$tkyte@ORA817DEV.US.ORACLE.COM> select sys_context( 'my_ctx', 'hello' ) sc
from dual;

SC
--------------------

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec my_procedure( 'hello', 'world' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select sys_context( 'my_ctx', 'hello' ) sc
from dual;

SC
--------------------
world

So, I would have to assume that when you created the context, you did not
specify USING PROP.AGENTPROPERTYREPORTS -- hence that piece of code cannot set
the context.
Rating: 5
At last March 14, 2002
Reviewer: Tim Glasgow from Belfast
Tom, thanks very much that was the problem. Well done!

..obviously creating the context in a sqlplus session under the same user
doesn't work as I had done this before I attempted to execute the
package.procedure
(i.e. SQL> create or replace context MY_CTX using prop.AgentPropertyReports;

Operation 177 succeeded.)

I also had the create context in my procedure a built it using exec immediate,
but as you spotted didn't have the user prefix before the package.

Thanks very much for your help.
Tim
Followup:
Suggestion -- upgrade your SQLPlus version if you want to use new features.
Funky things can happen if you use an older sqlplus against a newer db -- for
example, sqlplus isn't recognizing "CREATE OR REPLACE CONTEXT" as a SQL command
properly -- that tells me you are using the v8.0 or before sqlplus against 8i.
Some commands might not work AT ALL.
Rating: 5
Followup March 15, 2002
Reviewer: Tim Glasgow from Belfast
Hi Tom,
I found application context to be absolutely superb, but I've run into a problem
with using 'IN' in my dynamic query.

----------------------------------------------------
dbms_session.set_context( 'MY_CTX', 'estateagentid', pc_estate_agent_id_in);

 sql_stmt := sql_stmt || ' and ea.estateagentid IN sys_context( ''MY_CTX'',
''estateagentid'')';

----------------------------------------------------

If I do pc_estate_agent_id_in => '139' -- its OK
If I do pc_estate_agent_id_in => '139,44' -- error:

SQL>  @unit_tests/properties/test_all_nulls
Input truncated to 1 characters
139,44
hi1
BEGIN
*
ERROR at line 1:
ORA-01722: invalid number
ORA-06512: at "PROP.AGENTPROPERTYREPORTS", line 343
ORA-06512: at line 2

I know it's something to do with the quotes or the comma and have tried every
combination of adding quotes to the context attribute and the parameter.
Followup:
read
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:139812348065
...
Rating: 4
March 28, 2002
Reviewer: Tanel Hiir from Estonia
Tom,

Oracle8i Application Developer's Guide - Fundamentals Chapter 11:

<blockquote>
Fine-grained access control with application context was designed to handle
static applications; that is, those for which the security attributes contained
within application contexts are static within the user sessions. In other words,
the feature is best used for applications in which the user logs in, his
application context is set for the session, and the context does not change
until the user logs off. This design principle enables application context to be
highly scalable, because many users can share the same fully-parsed, optimized
statement.
</blockquote>

Does it apply only to DBMS_RLS?
Or does it influence dynamic SQL with sys_contex use also?
Followup:
They are only taking about DBMS_RLS there.
Rating: 5
Excellent technique -- now I have a ton of tuning work to do!!! April 03, 2002
Reviewer: Keith Miller from Boston, MA
This works great.

However, I notice that the number of parse_calls for these are equal the number
of executions for each distinct query in v$sqlarea.  There are no invalidations
for any of the distinct queries.

I have my dynamic call wrapped in dbms_utility.get_time statements for timing.
The response time I get looks as if the statements are not being reparsed.  The
initial time the query is run the response is 7 hsecs, subsequent excutions
respond in 2 hsecs.

Is there anything I can do to reduce the parse_calls for these dynamic queries?
Followup:
REF CURSORS must be parsed each time -- by definition.  They cannot be cached
(they are potentially DIFFERENT with each execute!)

search this site for session_cached_cursors, that'll help you.

They are being SOFT PARSED, much better then a hard parse -- much worse then no
parse or a session_cached_cursor hit.
Rating: 5
More on cursors... April 04, 2002
Reviewer: Andre Whittick Nasser from Brazil
I have 2 questions on cursors. Maybe you are able to clarify them, both in 9i:

1) CURSOR_SHARING = SIMILAR -> The Oracle on-line books and the Oracle
University books (the only ones on 9i I really trust) are rather reticent on
this feature. They only say things like "the CBO examines the statement for safe
(?) literals" or "the CBO uses statistics on available structures, like
histograms". I know bind variable values are assigned only after execution plan
generation, so the CBO is not aware of these values. Also, I know how histograms
are used to find out about the selectivity of column values. Can you please
explain briefly how "similar" statements are treated ?

2) Could you elaborate a bit on "cached" execution plans ?

Thanks!
Followup:
SIMILAR
Causes statements that may differ in some literals, but are otherwise identical,
to share a cursor, unless the literals affect either the meaning of the
statement or the degree to which the plan is optimized.

What this means is that we will take a query such as:

select * from emp where sal > 100

and turn that into "select * from emp where sal > :x".  We will decide IF that
bind variable is "safe" -- that is, if changing the literal value could affect
the plan.  In many cases -- it could (eg: full tablescan for sal > 100, index
range scan for sal > 10000).  Hence, similar queries will not share the plan.

On the other hand, a query like:

select * from emp where empno = 5555;

that would be deemed "safe", that is, regardless of the literal value used, the
query plan will be the same (index unique scan on the empno index).  Hence the
queries

select * from emp where empno = 1
select * from emp where empno = 2

will be "similar" and will use shared sql.  The determination of whether a bind
variable would be "safe" or not is made before the optimizer kicks in.

Also, in 9i, the optimizer will "peek" at binds before generating the optimized
plan, this is new.  So -- the optimizer does have access to the binds *the first
time it opimizes a statement* (but obviously not so on subsequent parses since
they just reuse the optimized plan that already exists)

a cached execution plan is what is stored in the shared pool.  it is the "shared
sql".
Rating: 5
Cached execution plans and V$SQL_PLAN April 04, 2002
Reviewer: Andre Whittick Nasser from Brazil
Thanks for the explanation on CURSOR_SHARING=SIMILAR. The quirk I was missing
was the "peek" at binds by the CBO in 9i. Interesting.

Sorry for asking you about cached execution plans. It's a misnomer, in fact.
It's being described as being some new feature of 9i. Of course it's not -- in
essence at least. It's the name Oracle is giving to the possibility of viewing
the ACTUAL, "stored" execution plan, as opposed to that generated by EXPLAIN
PLAN. EXPLAIN PLAN is an ESTIMATE of what will be executed. On the other hand,
the new V$_SQL_PLAN shows the real, previously executed plan.

From the Performance Guide:

"
V$SQL_PLAN

This view provides a way of examining the execution plan for cursors that were
recently executed.

The information in this view is very similar to the output of an EXPLAIN PLAN
statement. However, EXPLAIN PLAN shows a theoretical plan that can be used if
this statement were to be executed, whereas V$SQL_PLAN contains the actual plan
used. The execution plan obtained by the EXPLAIN PLAN statement can be different
from the execution plan used to execute the cursor, because the cursor might
have been compiled with different values of session parameters (for example,
HASH_AREA_SIZE).
"

Thanks !
Followup:
Ahh -- but a$sql_plan is just showing you the cached plan from the shared pool
-- it's not really new (the cached plan), the ability to "see it" is all that is
new here.
Rating: 5
That's what I meant... April 04, 2002
Reviewer: Andre Whittick Nasser from Brazil
Rating: 5
Cached execution plans... April 04, 2002
Reviewer: Andre Whittick Nasser from Brazil
That's what I meant ! I have an Oracle document titled "Cached Execution Plans".
At first, I didn't know what was new about it, then I asked you. Later I saw
that what was new wasn't the cached stuff -- of course. The view was new.

You know what's interesting ?

V$SQL has a new column, PLAN_HASH_VALUE. Like with HASH_VALUE, which you can use
to compare 2 sql texts, you can use PLAN_HASH_VALUE to know if the plan has
changed for a specific cursor. In fact, you can do both to compare cursors
plans:

Either...

- compare V$SQL_PLAN rows or

- compare V$SQL.PLAN_HASH_VALUE column values

If you do:

column id          format 999 newline
column operation   format a20
column operation   format a20
column options     format a15
column object_name format a22 trunc
column optimizer   format a3  trunc

SELECT id
     , lpad (' ', depth) || operation operation
     , options
     , object_name
     , optimizer
     , cost
  FROM V$SQL_PLAN
 WHERE hash_value = 2446703096
   AND address    = '80E3A1D8'
 START WITH id = 0
 CONNECT BY
       (     prior id           = parent_id
         AND prior hash_value   = hash_value
         AND prior child_number = child_number
       )
 ORDER SIBLINGS BY id, position;

  ID OPERATION            OPTIONS         OBJECT_NAME            OPT       COST
---- -------------------- --------------- ---------------------- --- ----------
   0 SELECT STATEMENT                                            CHO
   1  SORT                ORDER BY                                            6
   2   NESTED LOOPS                                                           4
   3    HASH JOIN                                                             3
   8    TABLE ACCESS      BY INDEX ROWID  ORDERS                              1
   9     INDEX            RANGE SCAN      ORDERS_PK                           2
   4     TABLE ACCESS     BY INDEX ROWID  ORDERS_LINE_ITEM                    1
   5      INDEX           RANGE SCAN      ORDERS_LINE_ITEM_PK                 2
   6     TABLE ACCESS     BY INDEX ROWID  ORDERS_STATUS                       1
   7      INDEX           RANGE SCAN      ORDERS_STATUS_PK                    2

10 rows selected.

Then you have the plan for that specific child cursor. Here, we have only one
child cursor.

Thanks !
Rating: 4
Another approach using package variables April 05, 2002
Reviewer: James Turner from NY, NY USA
Seems like you could achieve the same results using package variables.  What
advantage is there to the application context approach over this alternative?
Followup:
You cannot do that in a dynamically opened ref cursor.

open cursor for 'select * from t where x = pkg.variable';

won't work.  you could code:

open cursor for 'select * from t where x = pkg.function_returning_variable';

but that can be highly "non-performant".  sys_context is treated just like a
bind variable -- using sys_context the code:

open cursor for 'select * from t where x = sys_context( ''n'', ''v'' )';

is treated as:

open cursor for 'select * from t where x = :bind_variable';
Rating: 5
Dyanmic Attribute? July 25, 2002
Reviewer: Robert Boyle
Hi Tom,
Thanks for this most useful piece of coding!  I was wondering is there anyway of
creating the attribute in dbms_session.set_context dynamically?  Reason I am
asking is that we are getting a string containing various words to search a
field for and I am building the sql using this procedure (along with checkin a
dozen other parameters) and using multiple likes.

eg:  l_query := l_query || ' and description like sys_context(''MY_CTX'',
''DESCRIPTION'')';

can DESCRIPTION be made dynamically to be DESCRIPTION1, DESCRIPTION2 etc...or am
I going about this the wrong way?  Should I use intermedia, or should I limit
the number of key words available for a search thus I would know the limit
required in the query?

Cheers
Robert
Followup:
Not sure what you are asking here?
Rating: 5
Dynamic Attribute July 26, 2002
Reviewer: Robert
Sorry, never was the best at explaining myself!

First of all let me explain the parameter I receive.  It will contain several
keywords to search a description field for, seperated by a space.

'myword yourword ourword anotherword'

I use the code you supplied to check whether it is null or not and whether I
need to append it to my dynamic query to run against the table.  (there are
other parameters too that are checked to see if they are null).

if ( p_keywords is NOT NULL ) then

This is where I am unsure of the best way to do things.  I need to get these
keywords and do a like comparison on each of them against the description field.
 So I create a loop and get the keyword into a local variable (l_keyword) and
then set the context:

dbms_session.set_context( 'MY_CTX', 'DESCRIPTION', '%'||l_keyword||'%');

My problem stems here.  I will not know how many keywords there might be and my
understanding is that if I assign l_keyword to the 'DESCRIPTION' attribute, I
will over write each l_keyword until I get to the final one in my loop.  So that
my l_query would have:
' and description like sys_context(''MY_CTX'', ''DESCRIPTION'')

several times in the query and therefore only check a like for the same word n
amount of times.

Therefore, I was wondering if

a) we should limit the amount of keywords (this I do at the moment) so that I
can loop and assign to attribute 'DESCRIPTION1', 'DESCRIPTION2' etc

b) we can create the attribute 'DESCRIPTION' || l_keyword_count on the fly (ie
have 'DESCRIPTON1', 'DESCRIPTION2' created dynamically in the code)

or

c)should we abandon using this and go for Intermedia, the desciption field is a
varchar2(2000) and so not that large.

Thanks for your help.
Robert
Followup:
Ok, you'll have to use the approach you are currently using -- description1,
description2 and so on -- making a unique name for each.

You can do this dynamically on the fly -- the variable names in the context are
just strings.  You make them up as you go along.

Intermedia won't do "%word%" searches -- the leading % is a bad thing, hard
problem to solve.  cannot be solved with an index.
Rating: 5
July 26, 2002
Reviewer: Robert
Hi Tom,

I tried creating the attribute dynamically...but maybe I am going about it
wrong?  Below is my code that loops and appends to the query:

WHILE l_description IS NOT NULL LOOP

  l_delimiter := INSTR(l_description, ' ');
  l_length    := LENGTH(l_description);
  IF l_delimiter = 0 THEN
     l_delimiter := l_length;
  ELSE
     NULL;
  END IF;

  l_desc_part := SUBSTR(l_description, 1, l_delimiter);
  l_desc_count := l_desc_count + 1;
  l_desc_context := 'DESCRIPTION' || l_desc_count;

  dbms_session.set_context('MY_CTX', l_desc_context, '%'||l_desc_part||'%');
  l_query := l_query || ' and description like sys_context(''BUGTRACK_CTX'',
l_desc_context)';
END LOOP;

When I run this I get :

ERROR at line 1:
ORA-00904: invalid column name
ORA-06512: at "ROBERT.MY_PKG", line 216
ORA-06512: at line 43

Any ideas?

Thanks
Robert
Followup:
when you build this query -- it'll be:

  select .... and description like
     sys_context('BUGTRACK_CTX', l_desc_context)

well, l_desc_context isn't known in the query!

what you need is:

l_query := l_query ||
        ' and description like sys_context(''BUGTRACK_CTX'', ''' ||
           l_desc_context || ''' )';

so your where clause reads:

  select .... and description like
     sys_context('BUGTRACK_CTX','DESCRIPTION1')

you need to concatenate in the VALUE of l_desc_context, not the word
l_desc_context!
Rating: 5
Dynamic Attribute July 29, 2002
Reviewer: Robert
Brilliant, thanks Tom.  It all seems so obvious now!
Rating: 5
question on the followup for -- a non dynamic alternative August 12, 2002
Reviewer: john
Hi Tom,

i just would like to know that why the index is not used in the following query.
because we have not used any function on on ename or sal or hiredate(which are
on left hand side)

select  *
from    emp    e
where   e.ename    = decode( b_ename
                            ,null,       e.ename
                            ,            b_ename
                            )
and     e.hiredate = decode( b_hiredate
                            ,null,       e.hiredate
                            ,            b_hiredate
                            )
and     e.sal     = decode( b_sal
                            ,null,       e.sal
                            ,            b_sal
                            );

i have used a query that get all the records for which either start date falls
in 2002 or end date falls in like below:

select * from my_table where strt_date =
to_date(to_char(strt_dt,'DDMM')||'2002','DDMMYYYY')
OR end_date = to_date(to_char(strt_dt,'DDMM')||'2002','DDMMYYYY')

now my question is will the above query not use the index on strt_date and
end_date.

if no, can this below query use the index:

select * from my_table where strt_date >= to_date('01012002','DDMMYYYY')
OR end_date <= to_date('31122002','DDMMYYYY')
Followup:
you most certainly did use a function on ename, sal and hiredate!!!!

where e.ename = function_of( ...., e.name, .... )
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

right there!

How could one use an index on:

  where x = decode( :bv, null, x, :bv );

the decode can return a different value for each and every row!!!!  if :bv is
NULL that decode is NOT a constant and you can only use an index when we have a
constant!

You want to use a technique like:

(well, I was going to cut and paste the url for this question here but then I
noticed you already found the answer!!!!)

use the technique above.

you want to query like this:

select *
  from t
 where strt_date between to_date('01-jan-2002')
                     and to_date('01-jan-2003')-1/24/60/60
    or end_date between to_date('01-jan-2002')
                     and to_date('01-jan-2003')-1/24/60/60

your approach *applies a function* to the column --
to_date(to_char(strt_dt,'DDMM')||'2002','DDMMYYYY') is potentially different for
each and every row -- no way to use an index with that (well, you could use a
function based index but that would be the wrong solution here, just use between
like I did)
Rating: 4
Another way? August 13, 2002
Reviewer: Darko Egersdorfer from South Africa
Can't you achieve the same (without using sys_context), creating dummy WHERE
condition if passed parameter has null value. So, you will always use the same
number of bind variables in USING clause.
      if ( p_ename is NOT NULL ) then
           l_query := l_query ||
                ' and ename like  ''%''||:p_ename||''%''';
       else
           l_query := l_query ||
                ' and 1 = DECODE(:p_ename,NULL,1,1)';
       end if;

 ...

   open l_cursor for l_query USING  p_ename,p_hiredate,p_sal;

(You can always TO_CHAR and TO_DATE date parameters using the same format)
Followup:
sure, but if you've gone to all of that work -- you might just as well not
include the predicate anyway.  Why add an extra decode if you need not?  Also,
sys context lets you do it when you have no clue how many binds you might have.

Yes, you can do it like this.
I would myself use sys_context.
Rating: 5
create context privillege August 13, 2002
Reviewer: john
tom,
when i try creating the context, i get an error

create or replace CONTEXT my_ctx using my_procedure

ORA-01031: insufficient privileges

is that enough if i have create or replace context privellege or do i also need
to have execute privilege on dbms_session.set_context as well.

please suggest
Followup:
ops$tkyte@ORA817DEV.US.ORACLE.COM> grant create any context to scott;

Grant succeeded.

is what you are missing.
Rating: 4
contexts vs. predicates using short-circuited OR August 13, 2002
Reviewer: Bill Coulam from CO, USA
I've never seen that context stuff used before. Very cool. I can't wait to dig
into it more.

Up until this point, whenever I've had a query that made use of bind variables
that were possibly empty, I've used the following approach, depending on the
short-circuiting nature of "OR" in PL/SQL:

   CURSOR c_mytab (<parmlist>) IS
   SELECT m.* FROM mytab m
   WHERE  (ln_id IS NULL
           OR
           m.myid = ln_id)
   AND    (ls_msg IS NULL
           OR
           m.myval = ls_msg)
   ...
   ;
BEGIN
   OPEN CURSOR c_mytab(<parmlist>);
   ...etc.

It looks and feels a little "lumpy", but it seems like less code and hassle than
the context stuff.

So far, the only drawback we've experienced is aesthetic. When adapted to
Dynamic SQL, there are two slots for each variable/parameter, e.g.

EXECUTE IMMEDIATE 'SELECT ...
' USING in_id, in_id, is_msg, is_msg;

That's not so hot.

Can you think of anything wrong with using the OR in this way?

bill coulam
Followup:
the optimized query plan from this OR stuff where you say

where ( bind_variable IS NULL or table.column = bind_variable )

will be really bad compared to the approach above which results in a finely
tuned query.

The optimizer looks at that OR and says -- hmm, maybe we will do the compare,
maybe no -- no index for you (it would not be able to use an index on
table.column effectively whereas my approach will)
Rating: 5
short circuited or September 03, 2002
Reviewer: A reader
but tom,

when i used the OR approach, i see optimizer using the index when the bind
variable is null. i gave a sample query like this

select * from emp where (null is null or empno = 1)

then i saw full table scan on emp

now when i modify the query:

select * from emp where (1 is null or empno = 1)

then i saw the index is being used on that..

do you still suggest that this or approach result bad optimizer plan. i don't
understand why?
Followup:
You did NOT use any bind variables.  That is why.  consider:

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

Table created.

ops$tkyte@ORA920.US.ORACLE.COM> alter table emp add constraint emp_pk primary
key(empno);

Table altered.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> variable x number
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> exec :x := null

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> select * from emp where ( :x is null or empno =
:x );

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

ops$tkyte@ORA920.US.ORACLE.COM> exec :x := 1

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> select * from emp where ( :x is null or empno =
:x );

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

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select * from emp where (null is null or empno =
1);

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

ops$tkyte@ORA920.US.ORACLE.COM> select * from emp where (1 is null or empno =
1);

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
   2    1     INDEX (UNIQUE SCAN) OF 'EMP_PK' (UNIQUE)

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

Bind variable = FULL SCAN.
Hard coded literals = new plan for every query = Maybe full scan, maybe not.
Rating: 5
short-circuited or September 03, 2002
Reviewer: A reader
thanks so much tom
Rating: 4
Privilege problem when calling DBMS_SESSION September 04, 2002
Reviewer: Mike Stacey-Gee from Llandeilo - UK
An elegant solution but I'm receiving an error whenever I access DBMS_SESSION
despite running as a user with DBA privilege (8i v8.1.7)

variable c REFCURSOR;
exec MSCR_PAYABLEEMPTIES( :c, 'O');

ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_SESSION", line 58
ORA-06512: at "MIS.MSCR_PAYABLEEMPTIES", line 16
ORA-06512: at line 1

Attached is a subset of the procedure that triggers the error:

CREATE OR REPLACE  PACKAGE "MIS"."P_REF_CURSOR"   is
   type ret_ref_cursor is ref cursor;
end p_ref_cursor;

CREATE OR REPLACE CONTEXT MSCR_CTX_PAYABLEEMPTIES USING MSCR_PAYABLEEMPTIES;

CREATE OR REPLACE  PROCEDURE "MIS"."MSCR_PAYABLEEMPTIES"                (
Result_Cursor IN OUT p_ref_cursor.ret_ref_cursor,
Direction IN VARCHAR2 DEFAULT NULL,
EndDate IN VARCHAR2 DEFAULT NULL
)
AS
  l_query varchar2(9999);
BEGIN

  l_query := 'SELECT DIRECTION,TRADE,VESSELCODE,VOYAGENUMBER,LOADPORTCODE,' ||
       'DISCHPORTCODE,EQUIPMENTTYPE,NOOFCONTAINERS,TEUS,CONTRTEUS ' ||
       'FROM MISFLATMVOYAGE WHERE COMMODITYCODE = ''RE00''';

  -- Check whether a direction parameter has been passed
  -- N.B. If passed from the web reports module it will contain '' instead of
NULL to
  --      indicate a blank parameter

  IF ( Direction IS NOT NULL ) AND ( Direction <> '''' ) THEN
    dbms_session.set_context('MSCR_CTX_PAYABLEEMPTIES', 'DIRECTION',
Upper(Direction));
    l_query := l_query ||
               ' AND DIRECTION = sys_context(''MSCR_CTX_PAYABLEEMPTIES'',
''DIRECTION'' )';
  END IF;

  open Result_Cursor FOR l_query;

END MSCR_PAYABLEEMPTIES;
Followup:
Well, it doesn't have anything to do with DBA/not DBA.

You can execute dbms_session -- the 1031 is coming from the fact that
"MIS"."MSCR_PAYABLEEMPTIES" is not able to set the context
MSCR_CTX_PAYABLEEMPTIES.

I notice your procedure and package have "MIS".  on them.  But the context does
not.  If you ran this script as a user OTHER THEN "MIS", then the context can
only be set by that SCHEMA.MSCR_PAYABLEEMPTIES -- not by
MIS.MSCR_PAYABLEEMPTIES;

Perhaps that is it.  Do a select * from dba_context and make sure the SCHEMA is
in fact MIS.

Here is a small test case showing the "issue"

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop context MSCR_CTX_PAYABLEEMPTIES;

Context dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop user a cascade;

User dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create user a identified by a;

User created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> grant create session to a
  2  /

Grant succeeded.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE OR REPLACE CONTEXT
MSCR_CTX_PAYABLEEMPTIES USING MSCR_PAYABLEEMPTIES;

Context created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE OR REPLACE  PROCEDURE
"A"."MSCR_PAYABLEEMPTIES"
  2  AS
  3  BEGIN
  4      dbms_session.set_context('MSCR_CTX_PAYABLEEMPTIES', 'DIRECTION', 'x' );
  5  END MSCR_PAYABLEEMPTIES;
  6  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> @connect a/a
ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout off
a@ORA817DEV.US.ORACLE.COM> REM GET afiedt.buf NOLIST
a@ORA817DEV.US.ORACLE.COM> set termout on
a@ORA817DEV.US.ORACLE.COM>
a@ORA817DEV.US.ORACLE.COM>
a@ORA817DEV.US.ORACLE.COM> exec MSCR_PAYABLEEMPTIES
BEGIN MSCR_PAYABLEEMPTIES; END;

*
ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_SESSION", line 62
ORA-06512: at "A.MSCR_PAYABLEEMPTIES", line 4
ORA-06512: at line 1

a@ORA817DEV.US.ORACLE.COM>
a@ORA817DEV.US.ORACLE.COM> @connect /
a@ORA817DEV.US.ORACLE.COM> set termout off
ops$tkyte@ORA817DEV.US.ORACLE.COM> REM GET afiedt.buf NOLIST
ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout on
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE OR REPLACE CONTEXT
MSCR_CTX_PAYABLEEMPTIES USING A.MSCR_PAYABLEEMPTIES;

Context created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> @connect a/a
ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout off
a@ORA817DEV.US.ORACLE.COM> REM GET afiedt.buf NOLIST
a@ORA817DEV.US.ORACLE.COM> set termout on
a@ORA817DEV.US.ORACLE.COM>
a@ORA817DEV.US.ORACLE.COM>
a@ORA817DEV.US.ORACLE.COM> exec MSCR_PAYABLEEMPTIES

PL/SQL procedure successfully completed.

a@ORA817DEV.US.ORACLE.COM>

See, until the context is bound to the "A" schema - the procedure owned by "A"
cannot set it.
Rating: 4
using the same context field value in two queries September 09, 2002
Reviewer: A reader
can we use the same context field in different queries

like

dbms_session.set_context('my_ctx','ename','john');
execute immediate
'select count(1) from emp where ename = sys_context('my_ctx','ename')';

open p_ref_cur for 'select * from emp where ename =
sys_context('my_ctx','ename')';

is the above possible? i am getting an error ' invalid column name
Followup:
sure you can but you have to write the code right!

to get a single quote in a string you have to use two quotes.

'select count(1) from emp where ename = sys_context(''my_ctx'',''ename'')';
Rating: 4
assigning context to a procedure September 30, 2002
Reviewer: ramu
can i assign the same context to more than one procedure or it should always be
used in assigning to a single proc?

can i do like :

create or replace context MY_CTX using MY_PROCEDURE1;

create or replace context MY_CTX using MY_PROCEDURE2;
Followup:
you can do that but only the last one COUNTS.

Only one procedure or PACKAGE (<<<=== thats how to allow more then one proceudre
access) at a time.
Rating: 5
ref cursor December 24, 2002
Reviewer: Mo
Tom:

In the example above you build the WHERE clause dynamically.  How about if I
want to build the columns in the SQL itself.  The situation I have is:

I have a table for organization and a  table for warehouse.
One org may have one or more warehouse.  I want to run a inventory report to do
total count for items for org and break it also by warehouse.  I have a function
that cimputes total quantity. But now after figuring out which org user belongs
to, I want to build my SQL statment based on how many warehouses he has. let us
say he has one warehouse only then it will be:

FOR x in (SELECT item,compute_qty(org,item) qty1 from stock_items)
LOOP
htp.p(item);
htp.p(qty1);

If there are two warehouses then I want:
FOR x in (SELECT item,compute_qty(org,item) qty1, compute_qty(warehouse1,item)
qty2,
compute_qty(warehouse2,item) qty3
 from stock_items)
LOOP
htp.p(item);
htp.p(qty1);
htp.p(qty2);
htp.p(qty3);
etc....

IS THIS SOMETHING TO BE DONE USING REF CURSORS in PL/SQL?

Thank you,
Followup:
not with ref cursors - they fetch into a known set of plsql variables or a fixed
record (eg: the OUTPUT of a ref cursor is known at compile in if plsql is
fetching)

using dbms_sql -- trivial.  Just look at the listprint code, or the cellsprint
code in owa_util.
Rating: 5
cursor December 24, 2002
Reviewer: mo
Tom:

Thanks. I will look into dbms_sql even though I remember you used to always say
use "Execute immediate instead".

1. getcode owa_util.listprint does not work.  Do i have to login as SYS.

THank you,
Followup:
Nope -- I never said that.  Read my chapter on dynamic sql in "Expert one on
one" -- there I show you when you will use native dynamic sql and when you will
use dbms_sql.

This is one of the cases that clearly falls into dbms_sql.

getcode uses USER_SOURCE. You would have to be logged in as the owner of the
package in order to use it or

select text from dba_source where name = 'OWA_UTIL' and type = 'PACKAGE BODY'
order by line
Rating: 4
I do it the following way when all the predicates are varchars December 26, 2002
Reviewer: Bala from KL, Malaysia
This one is regarding the original posting.
define a cursor for select * from tbl where col_v1 like nvl(v1,'%'||v1||'%') and
col_v2 like nvl(v2,'%'||v2||'%') and ..... so on.
Any 'Danger Will Robinson's  here?
Followup:
If you always put % on the front and back -- you can do that since indexes would
be pretty much useless where and the only plan will be a full scan (eg: the plan
will not change from query to query).

This solution above is good when you want:

select * from t where x = 5;

to be optimized DIFFERENTLY then

select * form t where y like 'hello%';

In this case -- we might make use of an index on X for one and Y on another.
That will NOT work with you do something like:

where x = nvl(bind_var_x,x)
  and y like nvl(bind_var_y,y)
Rating: 5
ref cursor January 13, 2003
Reviewer: mo
Tom:

Would this also work instead of using ref cursor. let us say you have three
inputs and you want to build AND SQL statement.

FOR x in (SELECT * from table where country=p_country
          intersect
          SELECT * from table where state=p_state
          intersect
          SELECT * from table where city=p_city)
LOOP
...
END LOOP;
Followup:
time it -- tell me what happens when you specify:

USA
CA
San Francisco

USA -- returns lots of records (lots of people in that country)
CA  -- returns a good percentage of those (lots of people in CA)
San Francisco -- returns the least number, but still sizable

Then, we put them together -- and figure out what is in all three and sort
distinct it (which may materially change the answer - sort/distinct)...

Or, we can just

select * from table where .....;
Rating: 5
context January 13, 2003
Reviewer: mo
Tom:

You are right. The interesect can be slow for large tables.

However, when I implement the context solution: I get

SQL> create or replace context proc_ctx using proc_list;
create or replace context pi_ctx using pi_list
*
ERROR at line 1:
ORA-01031: insufficient privileges

I asked DBA to grant context for dev account and he says it is not simple. Is it
difficult? is in it a simple grant?

Thank you,
Followup:
They should create the context for you.  It is that simple.

If they won't do it, ctl-f and search for

Another way?

on this page.
Rating: 5
set context January 15, 2003
Reviewer: mo
Tom:

To build a clause as :

where col1 between p_from_col1 and p_to_col1

Can you do in one if statement?

or you have to implement two ifs and two set_context.

Thanks,
Followup:
think about it...  this is just writing code mo, just writing code.  Seems like
it is one if, two sets doesn't it?

if ( p_from_col1 is not null and p_to_col1 is not null )
then
    dbms_session.set_ctx( 'my_ctx', 'p_from_col1', p_from_col1 );
    dbms_session.set_ctx( 'my_ctx', 'p_from_col2', p_from_col2 );
    l_query := l_query || ' and col1 between sys_context .......';
end if;

doesn't make any sense to have two ifs - if either of to or from is null - it
would not make sense to query on them.  You obviously need two context values --
hence you need two calls to set context.
Rating: 5
context January 16, 2003
Reviewer: mo
Tom

Thanks. What I meant is that whether I can assign two values in one statement of
dbms_session.set_ctx but obviously I can not.

THanks a lot,
Rating: 5
count January 22, 2003
Reviewer: mo
Tom:

For the above solution ,let us say you want to count records before you open ref
cursor. I used execute immediate and it does not work.

execute immediate 'select count(*) from '||l_query||' into l_cnt';

Is not this valid?
Followup:
why -- you do know the answer is immediately different - that count is obsolete
right away....

I hate that idea more then anything -- well, maybe not more then not using bind
variables....  but you get my point.

reread:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3489618933902
and read:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:4456921593765
to get my opinion on that very bad, wasteful, slow, wrong practice.
Rating: 5
count January 22, 2003
Reviewer: mo
Tom:

I see you point which makes sense.  However, the count is accurate when that
user started his search.

Are you also against the idea of doing a count for a the record subset (10
records) (if pagesize is 10) after a page is created to determine whether to
plae a NEXT button or not?

Are you always in favor of displaying a blank page?

Thanks,
Followup:
no it isn't -- the instant after you start that query that count can change.

do it the way you want.  what I am in favor of isn't really relevant here is it?

Don't do a count, just overfetch by one record to see if there is a next page.
Rating: 5
variable output parameters January 27, 2003
Reviewer: Sujata from Germany
building a procedure for advanced search...
This procedure will accept parameters from a web page...and returns a recordset
to the web page.
The output parameters will be what ever user wants..and user will also build the
where clause...for example "Employee Name like 'To%'.
The no of tables invloved are known...but the joins are built dynamically....
This is how I approached:

PROCEDURE EMP_ADVANCED_SEARCH (pCOL LONG := null,
                      pRELATION LONG := null,
                      pVALUE LONG := null,
                      pSELECT_COL LONG := null,
                      C_OUT_CUR OUT T_REFCUR)

AS

vSQL LONG;
vSELECT LONG;
vFROM LONG;
vWHERE LONG;

BEGIN

vSELECT := pSELECT_COL;

VSQL := 'SELECT ' || vSELECT || CHR(10) ||
        ' FROM ' || vFROM || CHR(10) ||
        ' WHERE ' || vWHERE;

WRITE_A_STRING (VSQL);
OPEN C_OUT_CUR FOR VSQL;

END EMP_ADVANCED_SEARCH;

Here is a sample of values for parameters:
pCOL = 'ENAME,DEPT_NAME'
pRELATION = 'LIKE,='
pVALUE = '%TO%,10'
pSELECT_COL = 'EMPLID, DEPT, ENAME, SALARY'

pCOL, pRELATION, pVALUE ARE THE PARAMETER TO BUILD WHERE CLAUSE..BASED ON THE
USER INPUT...
pSELECT_COL DESCRIBES THE COLUMNS THAT THE USER WANTS IN THE RECORDSET

vFROM, vWHERE CLAUSE WILL BE BUILT BASED ON pSELECT_COL,pCOL, pRELATION AND
pVALUE
PARAMETERS.

QUESTION:
I am lost on building FROM and WHERE clause dynamically part...
Here is what I am imagining:
I will run pCOL and pSELECT_COL through loops..and will check the column_name
against SOME_TABLE to find the table name the column is in
and will keep adding
to the VFROM and VWHERE clause...and I will check before I add a join
relationship to VWHERE
or a table to VFROM...to make sure that it hasn't been added before (instr
function).

SOME_TABLE will have table_names, column_names
as two columns...and I will check the column names in pCOL and pSELECT_COL
parameters against
this table.
In scott schema for example DEPTNO is in both tables EMP and DEPT.
but I think this custom table should have DEPTID reference only one time...so
that
we will hit one or the other when we are looking for DEPTID column..to see what
table does
it relate to..
We won't mention DEPT table in the from clause or where clause...unless DEPTNAME
is mentioned
either in pCOL parameter or pSELECT_COL parameter.

Is there a better way to accomplish this?

Thanks.
Rating: 3
sys_context not using index??? March 11, 2003
Reviewer: Paul from UK
Tom,

Can you explain the following, please? I'm very puzzled!

Oracle 8.1.7 - Customers has 4 million odd rows, indexed on postcode.
As per your book p716,  I have used sys_context when building up dynamic sql.
So why won't Oracle use the index when using sys_context when I'm using both OR
and LIKE, yet it does when hard-coding the values (see below)?  It also works
fine if I just use LIKE (no OR) with the sys_context statement.

SQL> select
  2  UPPER(sys_context('mycontext','postcode1'))||'%' postcode1,
  3  UPPER(sys_context('mycontext','postcode2'))||'%' postcode2
  4  from dual;

postcode1                       postcode2
------------------------------- ---------------------------------------
NT6 7EF%                        NT6 8AP%

SQL> set autotrace on
SQL> select cus_number from customers cus
  2  where cus.postcode like 'NT6 7EF%'
  3  or
  4  cus.postcode like 'NT6 8AP%';

CUS_NUMBER
-------------
3860001833844
3860005094542
3860005094543
3860005094544
3860002229221
3860002229878
3860002386195
3860002386196
3860002436754
3860002436844
0273000769335
0273000638189
0273000952026
0273002199257
0273002239570
0273002446665
0273000769334

17 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=16 Card=16 Bytes=2064)
   1    0   CONCATENATION
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (Cost=4 Card=1
Bytes=129)
   3    2       INDEX (RANGE SCAN) OF 'CUS_PC_I' (NON-UNIQUE) (Cost=3 Card=1)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (Cost=4 Card=1
Bytes=129)
   5    4       INDEX (RANGE SCAN) OF 'CUS_PC_I' (NON-UNIQUE) (Cost=3 Card=1)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         25  consistent gets
          0  physical reads
          0  redo size
       4239  bytes sent via SQL*Net to client
        529  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         17  rows processed

SQL>  select * from customers cus
  2  where cus.postcode like UPPER(sys_context('mycontext','postcode1'))||'%'
  3  or
  4  cus.postcode like UPPER(sys_context('mycontext','postcode2'))||'%';

CUS_NUMBER
-------------
3860001833844
3860005094542
3860005094543
3860005094544
3860002229221
3860002229878
3860002386195
3860002386196
3860002436754
3860002436844
0273000769335
0273000638189
0273000952026
0273002199257
0273002239570
0273002446665
0273000769334

17 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=28972 Card=545239
Bytes=70335831)
   1    0   TABLE ACCESS (FULL) OF 'CUSTOMERS' (Cost=28972 Card=545239
Bytes=70335831)

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
     190893  consistent gets
     189129  physical reads
          0  redo size
       4242  bytes sent via SQL*Net to client
        617  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
         17  rows processed
Followup:
what happens with:

variable x varchar2(20);
exec :x := 'NT6 7EF%';
variable y varchar2(20);
exec :y := 'NT6 8AP%';

select cus_number from customers cus
 where cus.postcode like :x
 or
 cus.postcode like :y;

(just to show it is not sys_context to get excited about first...)

You see -- with the binds, the optimizer says, you know, it could be:

where cus.postcode like '%'

you know, that would get quite a few rows.....

tell me -- what is the distribution of the postcode.  how do you analyze the
table.  and what does that other query do.

Also, what is the frequency by which you run this query with different values?
Rating: 4
More feedback March 11, 2003
Reviewer: Paul
Right again, Tom!

Using binds from SQL*Plus instead of the sys_context code, got the same plan!

1) Distribution of postcodes is like this:
SQL> select count(*),count(postcode),count(distinct postcode) from customers;

 COUNT(*) COUNT(POSTCODE) COUNT(DISTINCTPOSTCODE)
--------- --------------- -----------------------
  5592187         3981897                  852500

There are only 9 postcodes (excluding NULL) that occur more than 100 times.  The
most common postcode has 1855 occurrences.

2) Analyze done this way:
execute DBMS_STATS.GATHER_SCHEMA_STATS('${ORACLE_USER}',25,FALSE,'FOR ALL
INDEXED COLUMNS',NULL,'DEFAULT',TRUE);

3) The other query (i.e. using just 1 sys_context clause) is like this:
SQL>  select cus_number from customers cus
  2  where cus.postcode like UPPER(sys_context('mycontext','postcode1'))||'%';

CUS_NUMBER
-------------
3860002229221
3860002229878
0273000769335
0273002239570
0273000769334
3860001833844
3860005094542
3860005094543
3860005094544

9 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=22760 Card=279610
Bytes=5312590)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (Cost=22760 Card=279610
Bytes=5312590)
   2    1     INDEX (RANGE SCAN) OF 'CUS_PC_I' (NON-UNIQUE) (Cost=138
Card=279610)

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

4) This query is part of a more complex query for searching the customers table
by anything the user wants - surname, date of birth, town, postcode etc.  It had
been working "acceptably" until they requested being able to search on multiple
postcodes (which they provide in a comma-separated string and I cut up to build
up the Dynamic SQL).  I would guess it's used hundreds of times a day.
Followup:
what happens if you build a union all query instead?

  select *
    from customers cus
   where cus.postcode like UPPER(sys_context('mycontext','postcode1'))||'%'
   UNION ALL
  select *
    from customers cus
   where cus.postcode like UPPER(sys_context('mycontext','postcode2'))||'%';
Rating: 3
That works fine March 11, 2003
Reviewer: Paul
The only problem is that my actual query can be way, way more complicated than
this.  If the users enter lots of different criteria the SQL generated gets
large with several WHERE EXISTS clauses (which are causing me other performance
problems!)

If the user enters 5 postcodes, then the amount of SQL will be generated
fivefold.

Is it just the the optimizer isn't clever enough? ;-)  It seems odd that running
2 queries is quicker than 1!
Followup:
what is your optimizer_index_cost_adj/index_caching set to.
Rating: 3
More feedback March 12, 2003
Reviewer: Paul from UK
SQL> select * from v$parameter
  2  where name like 'optimizer_ind%';

      NUM NAME                                TYPE VALUE
--------- ------------------------------ --------- -----------
      392 optimizer_index_cost_adj               3 100
      393 optimizer_index_caching                3 0
Followup:
so, in your session, see what

alter session set optimizer_index_cost_adj = 25;
alter session set optimizer_index_caching = 80;

does for you -- in SQLPLUS using binds -- just test it out.
Rating: 4
Interesting - Different plan, but still poor performance March 12, 2003
Reviewer: Paul
SQL> alter session set optimizer_index_cost_adj = 25;

Session altered.

SQL> alter session set optimizer_index_caching = 80;

Session altered.

SQL>
SQL> variable x varchar2(20);
SQL> exec :x := 'NT6 7EF%';

PL/SQL procedure successfully completed.

SQL> variable y varchar2(20);
SQL> exec :y := 'NT6 8AP%';

PL/SQL procedure successfully completed.

SQL>
SQL> select cus_number from customers cus
  2   where cus.postcode like :x
  3   or cus.postcode like :y;

CUS_NUMBER
-------------
3860001833844
3860005094542
3860005094543
3860005094544
3860002229221
3860002229878
3860002386195
3860002386196
3860002436754
3860002436844
0273000769335
0273000638189
0273000952026
0273002199257
0273002239570
0273002446665
0273000769334

17 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6008 Card=222592
Bytes=4229248)
   1    0   CONCATENATION
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (Cost=318 Card=10600
Bytes=201400)
   3    2       INDEX (RANGE SCAN) OF 'CUS_PC_I' (NON-UNIQUE) (Cost=138
Card=10600)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (Cost=318 Card=10600
Bytes=201400)
   5    4       INDEX (RANGE SCAN) OF 'CUS_PC_I' (NON-UNIQUE) (Cost=138
Card=10600)

Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
     190893  consistent gets
     190874  physical reads
          0  redo size
        583  bytes sent via SQL*Net to client
        305  bytes received via SQL*Net from client
          7  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         17  rows processed
Followup:
but that is the same plan -- I think autotrace is lying, use tkprof and see what
that says...

SQL> select cus_number from customers cus
  2  where cus.postcode like 'NT6 7EF%'
  3  or
  4  cus.postcode like 'NT6 8AP%';

CUS_NUMBER
-------------
3860001833844
3860005094542
3860005094543
3860005094544
3860002229221
3860002229878
3860002386195
3860002386196
3860002436754
3860002436844
0273000769335
0273000638189
0273000952026
0273002199257
0273002239570
0273002446665
0273000769334

17 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=16 Card=16 Bytes=2064)
   1    0   CONCATENATION
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (Cost=4 Card=1
Bytes=129)
   3    2       INDEX (RANGE SCAN) OF 'CUS_PC_I' (NON-UNIQUE) (Cost=3 Card=1)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (Cost=4 Card=1
Bytes=129)
   5    4       INDEX (RANGE SCAN) OF 'CUS_PC_I' (NON-UNIQUE) (Cost=3 Card=1)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         25  consistent gets
          0  physical reads
          0  redo size
       4239  bytes sent via SQL*Net to client
        529  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         17  rows processed
Rating: 3
Whilst I'm waiting ... March 13, 2003
Reviewer: Paul
... for my DBA to give me the ability to set sql_trace=true and give me access
to the user_dump_dest directory, can you explain what you mean by "I think
autotrace is lying"?

Does this happen very often, and more to the point, why?
Followup:
it doesn't happen often -- it is because autotrace uses explain plan and that is
close, not not always 101%.

I think you'll find, based on the numbes, it was a full scan.
Rating: 4
Problem appears to have gone away!! March 19, 2003
Reviewer: Paul
Tom,

This is very odd.  I have only just got back from a few days away.  I ran the
identical query as above, before and after setting the optimizer_index
parameters.  Before - as slow as ever.  After, and all of a sudden it's working
like lightning!  I've included the autotrace output below (I've got the TKPROF
output too, but I don't suppose there's much point now that it's "working").  I
suppose I should be happy that it's working nicely now, but I wish I knew why,
and I'm a bit uneasy in case it happens again in production.

SQL> alter session set optimizer_index_cost_adj = 25;

Session altered.

SQL> alter session set optimizer_index_caching = 80;

Session altered.

SQL>
SQL> variable x varchar2(20);
SQL> exec :x := 'NT6 7EF%';

PL/SQL procedure successfully completed.

SQL> variable y varchar2(20);
SQL> exec :y := 'NT6 8AP%';

PL/SQL procedure successfully completed.

SQL>
SQL> select cus_number from customers cus
  2   where cus.postcode like :x
  3   or cus.postcode like :y;

CUS_NUMBER
-------------
3860001833844
3860005094542
3860005094543
3860005094544
3860002229221
3860002229878
3860002386195
3860002386196
3860002436754
3860002436844
0273000769335
0273000638189
0273000952026
0273002199257
0273002239570
0273002446665
0273000769334

17 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6008 Card=222592
Bytes=4229248)
   1    0   CONCATENATION
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (Cost=318 Card=10600
Bytes=201400)
   3    2       INDEX (RANGE SCAN) OF 'CUS_PC_I' (NON-UNIQUE) (Cost=138
Card=10600)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (Cost=318 Card=10600
Bytes=201400)
   5    4       INDEX (RANGE SCAN) OF 'CUS_PC_I' (NON-UNIQUE) (Cost=138
Card=10600)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         25  consistent gets
          0  physical reads
          0  redo size
        544  bytes sent via SQL*Net to client
        309  bytes received via SQL*Net from client
          7  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         17  rows processed
Followup:
the only thing I can think of is a cached cursor -- autotrace explained the
query (getting the index plan) but plus ran a cached cursor -- that was
associated with a full scan.
Rating: 5
character limit on dbms_session.set_context is 256? April 23, 2003
Reviewer: Sachin from Germany
Database: 8.1.7.4

Tom,
I have a varchar2(2000) column in a table...that I am trying to update through a
procedure that uses dbms_session.set_context.

The procedure has a statement like:

.........
ELSIF (pOPTION = 8) THEN
   dbms_session.set_context( 'CTX_CB_STRTG_SURVEY_PKG', 'C_OUTDOOR', pOUTDOOR);
   vSET := vSET || ' OUTDOOR = sys_context(''CTX_CB_STRTG_SURVEY_PKG'',
''C_OUTDOOR''),';

END IF;

vSET := RTRIM (vSET,',');

--dbms_output.put_line (vSET);

vWHERE := ' USERID = :XUSERID ';

vSQL := 'UPDATE ' || vUPDATE || CHR(10) ||
    ' SET ' || vSET || CHR(10) ||
    ' WHERE ' || NVL (vWHERE, '1=1');

--write_a_string (vSQL);

EXECUTE IMMEDIATE VSQL USING pUSERID;
......
.................

I am not being able to update OUTDOOR column to anything
more than 256 characters.
I can do it through an update statement outside the procedure...but not through
procedure...
So, does dbms_session.set_context has a limit on characters..
pOUTDOOR parameter does pass with the correct length...
If dbms_session.set_context does have a limit...how else would we bind parameter
in this case

Thanks.
Followup:
sys_context is 255

you need to use a bind variable here.  DBMS_SQL will be more appropriate as you
can do it procedurally.
Rating: 5
dbms_session.set_context limit... April 23, 2003
Reviewer: Sachin from Germany
Ok...I saw it in the docs...
So, is there another way to bind variables varchar2(something more than 256):

Syntax
DBMS_SESSION.SET_CONTEXT (
   namespace VARCHAR2,
   attribute VARCHAR2,
   value     VARCHAR2);

Parameters
Table 46-12 SET_CONTEXT Procedure Parameters
Parameter  Description
namespace

  Name of the namespace to use for the application context (limited to 30
bytes).

attribute

  Name of the attribute to be set (limited to 30 bytes).

value

  Value to be set (limited to 256 bytes).
Followup:
dbms_sql
Rating: 5
SET_CONTEXT.VALUE LIMIT April 24, 2003
Reviewer: Sachin from Germany
DBMS_SQL or go to 9i :)
SET_CONTEXT Procedure
This procedure sets or resets the value of a context attribute.

Syntax
DBMS_SESSION.SET_CONTEXT (
   namespace VARCHAR2,
   attribute VARCHAR2,
   value     VARCHAR2,
   username  VARCHAR2,
   client_id VARCHAR2);

Parameters
Table 65-18 SET_CONTEXT Procedure Parameters
Parameter Description
namespace
 Name of the namespace to use for the application context (limited to 30 bytes).

attribute
 Name of the attribute to be set (limited to 30 bytes).

value
 Value to be set (limited to 4 kilobytes).

username
 The username attribute of the application context

client_id
 The application-specific identifier of the current database session.
Rating: 5
cursor with like operator May 08, 2003
Reviewer: mo
Tom:

I have the following implicit cursor

  FOR x in (SELECT stock_number,description from stock
      where description like '%p_keyword%')
    LOOP

I am not getting any result?  i_keyword is the word or sentence that a user
passes from a web page to serach for records matching his criteria.

2.  If I want to make it case insensitive can I use upper function between the %
.

Thank you,
Followup:
do you have anything with the string p_keyword in it?

think you might mean to code:

for x in ( sel.... where description like '%' || p_keyword || '%' )
loop

but you probably really really want to look at intermedia text (simply TEXT in
9i) and use that instead.  what you have now is and will be a full scan which
will not scale very well over time.

you want to be able to query

select .... where contains( description, p_keyword ) > 0

and use an index.
Rating: 5
keyword May 09, 2003
Reviewer: mo
TOm:

1.  you mean this is done using intermedia.
<select .... where contains( description, p_keyword ) > 0>

DO i only ask the DBA to install internedia and use it?

2. Table is a lookup with 900 descriptions. Each description is about 30
characters. Would you still be using intermedia?

3. You were right about piping the parameter. However let us say the p_keyword
is "Key" . Woold not that find all phrases that have KeyWest in them too.  DO i
change it to the take the p_keyword and look for a space after it in order to do
an exact search"?

Thank you
Followup:
1) yes.  it should be installed by default, although they can choose to not
install it, do you have a schema ctxsys in all-users?

2) to avoid a full scan, sure.

3) you would use intermedia, it does words.

lets say you have this data:

key to power -- is knowledge
Having knowledge is key

if you used the "space" trick you might find the first, but not the last.
Rating: 4
DBMS_SESSION.SET_CONTEXT and ORA-01031 May 12, 2003
Reviewer: darren from London
Good quick fix, easy to locate and implement.

I'd created a new package containing DBMS_SESSION.SET_CONTEXT calls and was
getting ora-01031 errors.

simply executed:

SQL> create or replace context SEARCHNEW_CONTEXT USING SPSEARCHNEW;

and fixed!
(though the java side did let me down again
but hey! thats life! (and thats java!)
Rating: 3
Optimizer Settings Earlier in Post May 14, 2003
Reviewer: Matt from Australia
Earlier you suggested changed some optimizer parameters. I understand thst this
affect the weightings that the CBO applies to accessing tables via an index or
via a full scan. I am just curious why you chose these particular values.
Obviously you have seen these settings work well in the past, but was there any
other criteria that caused you to choose these particular values?

alter session set optimizer_index_cost_adj = 25;
alter session set optimizer_index_caching = 80;

I am working on converting queries from the RBO to the CBO and have been going
through a tuning exercise to alter these values. However, I have taken the
approach of only altering the optimizer_index_cost_adj parameter. I am seeing
improvements in CBO timings as I move optimizer_index_cost_adj from 100 down to
70 compared to the RBO but nothing significant. I am definately not seeing any
benefits over the CBO so far (I know that these will come when I find the right
magic values).

Surely,

alter session set optimizer_index_cost_adj = 25;
alter session set optimizer_index_caching = 80;

is not a rule of thumb? ( :o) )

Best Regards.
Followup:
they are two sides of the pendulum.

You want to see a big swing?  swing hard to the other side.  I just started
"low"
Rating: 3
Confirmation. May 14, 2003
Reviewer: Matt from Australia
I see.

So you are saying that:

optimizer_index_cost_adj = 25;
optimizer_index_caching = 80;

is a slight push to the optimizer to alter it's behaviour. A bigger push would
then be:

optimizer_index_cost_adj = 75;
optimizer_index_caching = 20;

Is it always necessary to alter these parameters as a pair?

If so, is it as simple as geometrically defining a straight line between these
two points above to find the sweet spot?

Matt
Followup:
no, it takes it HARD to port, not soft.

the defaults:

optimizer_index_caching              integer     0
optimizer_index_cost_adj             integer     100

the other end of the spectrum:
                                                 Hard     Really Hard
                                                 to port  to port
optimizer_index_caching              integer     80       90
optimizer_index_cost_adj             integer     25       10

cost_adj -> zero implies indexes are cheap, really cheap
index_caching -> 100 implies indexes incurr no PIO
Rating: 5
coun of cursor May 19, 2003
Reviewer: mo
Tom:

I remember you had an excellent article on explaining how to count records
inside/outside a cursor but I tried all kind of searches to find it and I can
not. Can you point me to it?

Thank you
Followup:
you must have been dreaming?  I don't know what you are refering to.

the only way to know the number of records in a "cursor" is to actually fetch
them all -- at which point the cursor is no longer a cursor, it is empty.

of course, you could add "count(*) over()" as a column but boy would that be a
huge mistake and kill your performance totally.
Rating: 5
cursor May 19, 2003
Reviewer: mo
Tom:

if i have

for x in ( select * from table)
LOOP
---HERE can i get how many records were retrieved
...
....

END LOOP;

Thank you,
Followup:
add rownum to the query or count them.
Rating: 5
Great Information May 23, 2003
Reviewer: Deanna from SF
This listing was very helpful in understanding how to use bind variable.  It is
one of the most valuable pieces of information out on the site!  Thank you

Do you know how to use this with Intermedia.  When applying the code I get
"Invalid Relational Operator"
I tried modifying the quotes but had no luck

Thanks

PROCEDURE REPORT_SEARCH (p_kw1 IN VARCHAR2 DEFAULT NULL)

AS
        TYPE RC IS REF CURSOR;
        v_query VARCHAR2(3000);
       l_cursor RC;
       l_query  VARCHAR2(3000)
               DEFAULT ('SELECT a.url

                       FROM test_table a
                       WHERE 1=1');

          cursor l_template IS
                        SELECT a.url
                        FROM  test_table a;

          l_rec l_template%ROWTYPE;

BEGIN
 IF ( p_kw1 IS NOT NULL ) THEN
           dbms_session.set_context( 'test_ctx', 'KW1',
                      'contains( url, p_kw1 ) > 0 ' );

    l_query := l_query ||
               ' and sys_context( ''test_ctx'', ''KW1'' ) ';
      END IF;
      OPEN l_cursor for l_query;
       LOOP
           FETCH l_cursor into l_rec;
           EXIT WHEN l_cursor%NOTFOUND;

            dbms_output.put_line( l_rec.url  );
       END LOOP;

      CLOSE l_cursor;
      END;
Followup:
read
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5225986313862#5261789008763
Rating: 3
May 23, 2003
Reviewer: Deanna from SF,CA
Hi Tom

Thanks for the reply
I read the information in the following link, and I still get "Invalid
relational Operator" at run time

I matched this syntax:
if ( p_ename is NOT NULL ) then
           l_query := l_query ||
                ' and contains( field, :p_ename ) > 0 '
with this syntax

 IF ( p_kw1 IS NOT NULL ) THEN
      dbms_session.set_context( 'report_search_ctx', 'KW1',
           'contains( url, :p_kw1 ) > 0' );
     l_query := l_query ||
        'and sys_context( ''report_search_ctx'', ''KW1'' ) ';
      END IF;

Is the problem with the sys_contex ?
Followup:
look at the query you would be generating.  you are basically saying:

select * from t where f(x,y);

f being a function.  that does not make sense.

you do not put the PREDICATE into sys_context, you put values to be passed to a
predicate in there.

You cannot do this:

   set_context( ...., ' empno = 1234 ' )

and expect "select * from emp where sys_context( ... )"  to work -- same with
contains
Rating: 5
May 25, 2003
Reviewer: A reader
tom, just to get it 100% right: the SYS_CONTEXT issue does NOT apply to code
like that?

open ref_cursor for
  select <some columns> from <some tables>
  where  <some joins>
    and  col1 = p_MyParam;

(but only to GENERATED SQL statements?)
Followup:
it applies to ANY sql statement, sys_context is just a function that returns a
string -- use it anywhere you would put a string (character string literal)
Rating: 3
May 25, 2003
Reviewer: Deanna from SF,CA
Hi Tom

Thanks again for the information... especially on a 3 day weekend :)

I tried this

IF ( p_kw1 IS NOT NULL ) THEN
           dbms_session.set_context( 'report_search_ctx', 'KW1',p_kw1 );

    l_query := l_query ||
            ' and contains (url, (sys_contex(''report_search_ctx'', ''KW1'' )))
>0 ' ;

Thinking that (sys_contex(''report_search_ctx'', ''KW1'' )) contained my value
therefore if I placed it as the value in the contains clause that would work.

Instead I get now Invalid column name

How would you resolve this problem?  Ill be working on it further to try to "get
the clues" in the last response
Followup:
just read:
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5225986313862#5261789008763
it shows exactly what you need to do.
Rating: 4
May 25, 2003
Reviewer: A reader
sorry for that, tom, but to me it 's still not 102% clear if one should use
SYS_CONTEXT rather than "normal" parameters (such as 'p_MyParam' in my sample
above):

is there any (major) difference between:

open ref_cursor for
  select <some columns> from <some tables>
  where  <some joins>
    and  col1 = p_MyParam;

and:

open ref_cursor for
  select <some columns> from <some tables>
  where  <some joins>
    and  col1 = SYS_CONTEXT('MyCtx', 'p_MyParam');

I've never thought of using SYS_CONTEXT that way but just only parameters: their
values get BOUND to the query - not their values "copied" into it, right?

so, can one summarize: use SYS_CONTEXT wherever string literals occur AND CHANGE
between several executions of the statement (otherwise there 's no advantage
too).
Followup:
I would use

open ref_cursor for
  select <some columns> from <some tables>
  where  <some joins>
    and  col1 = p_MyParam;

rather then

open ref_cursor for
  select <some columns> from <some tables>
  where  <some joins>
    and  col1 = SYS_CONTEXT('MyCtx', 'p_MyParam');

because it takes less code to do so.  sys_context() works just like a bind.
Rating: 5
cursor May 25, 2003
Reviewer: mo
Tom:

when you do:

l_query  varchars(2000)  default 'select * from table where user_id = p_user_id'

open l_cursor from l_query;

I got an error.  it does not seem to recognize that p_user_id is a parameter and
substitute the value.  I also tried this:

l_query  varchars(2000)  default 'select * from table where user_id
='||p_user_id;

and it did not work.  are you stuck with sys_context here?
Followup:
no, you are stuck reading the documentation ;)

l_query := 'select * from table where user_id = :p_user_id';
open l_cursor from l_query USING p_user_id;
Rating: 5
U tell me dude May 26, 2003
Reviewer: A reader
your code :
l_query  varchars(2000)  default 'select * from table where user_id = p_user_id'

open l_cursor from l_query;
correct :
l_query  varchars(2000)  default 'select * from table where user_id =
:p_user_id'

open l_cursor for l_query using p_user_id;
Rating: 5
dbms_session.set_context in a loop May 27, 2003
Reviewer: Sachin from Germany
(Version 8.1.7.4.0)

Tom,

I have a procedure "A" that builds a string and returns to procedure "B"
that calls it.

--CREATING cONTEXT
CREATE OR REPLACE CONTEXT CTX_WHERE USING A;

--CREATING PROCEDURE "A"
CREATE OR REPLACE PROCEDURE A (pCOL_ARRAY TYPES_PKG.ARRAY DEFAULT
TYPES_PKG.ARRAY(),
                               pRELATION_ARRAY TYPES_PKG.ARRAY DEFAULT
TYPES_PKG.ARRAY(),
                               pVALUE_ARRAY TYPES_PKG.ARRAY DEFAULT
TYPES_PKG.ARRAY(),
                               pQUERY OUT LONG) AS

V_SEP VARCHAR2(4000) := ' AND ';
vQUERY LONG := NULL;

v_REC_COL VARCHAR2(4000);
v_REC_RELATION VARCHAR2(4000);
v_REC_VALUE VARCHAR2(4000);

BEGIN

FOR I IN 1 .. pCOL_ARRAY.COUNT LOOP

  v_REC_COL := pCOL_ARRAY(I);

  v_REC_RELATION := pRELATION_ARRAY(I);

  dbms_session.set_context( 'CTX_WHERE', 'CTX_CATCH_ALL', pVALUE_ARRAY(I));
  v_REC_VALUE := 'Sys_context( ''CTX_WHERE'', ''CTX_CATCH_ALL'' )';
  --v_REC_VALUE :=  pVALUE_ARRAY(I);

   IF vQUERY IS NULL THEN
          vQUERY := v_REC_COL || ' ' || v_REC_RELATION || ' ' || v_REC_VALUE;

   ELSE
          vQUERY := vQUERY || V_SEP || v_REC_COL || ' ' || v_REC_RELATION || ' '
|| v_REC_VALUE;

   END IF;

END LOOP;

pQUERY := vQUERY;

END A;

So, For Example...if we pass procedure A values as follows:
pCOL_ARRAY as TOOLS_PKG.MAKE_AN_ARRAY('EMPNO','DEPTNO')
pRELATION_ARRAY AS TOOLS_PKG.MAKE_AN_ARRAY('=','=')
pVALUE_ARRAY AS TOOLS_PKG.'2812','10')

We should get...
EMPNO = 2812 AND DEPTNO = 10

and we do get that...if we don't use contexts...i.e. uncommenting
the statement "v_REC_VALUE :=  pVALUE_ARRAY(I)"...and commenting out
"dbms_session.set_context..."
but when do use contexts...I do net get the desired results..I can not see the
values
stored in context..but my query does not return any results.

What am I doing wrong here?
Should the context be created using procedure B..since the procedure B calls
procedure A, Could
that be the mistake?
Followup:
hows about this -- a simple, concise, yet complete example that demonstrates the
issue in a manner I too can reproduce on my system?

eg: a test case....

but actually -- the issue seems to be one of logic here....

You have one context variable 'CTX_CATCH_ALL'

it can have but one value at a time.  Your query ends up being:

.... empno = sys_context( 'ctx_where', 'ctx_catch_all' ) and deptno =
sys_context( 'ctx_where', 'ctx_catch_all' ) ....

well, ctx-catch-all will just have whatever LAST value you stuffed in it --
doubtful you have an empno = deptno and deptno = sys_context() -- but that is
what you coded in effect....

maybe

FOR I IN 1 .. pCOL_ARRAY.COUNT LOOP

  dbms_session.set_context
  ( 'CTX_WHERE', 'bv_'||i, pVALUE_ARRAY(I));

   IF vQUERY IS NULL THEN
          vQUERY := pCol_array(i) || ' ' || pRelation_array(i) ||
                   ' sys_context( ''ctx_where'', ''bv_' || i || ''' );
   ELSE
          vQUERY := vQUERY || V_SEP || pCol_array(i) || ' ' ||
                     pRelation_array(i) ||
                   ' sys_context( ''ctx_where'', ''bv_' || i || ''' );
   END IF;
END LOOP;
Rating: 5
Multiple Contexts or single May 27, 2003
Reviewer: Sachin from Germany
Thanks Tom,
Another Question...
In the Procedure A, I also evaluate the value of v_REC_RELATION (i.e.
pRELATION_ARRAY(I))....as follows
   IF (v_REC_RELATION = 'BEGINS_WITH') THEN

      v_REC_RELATION := ' LIKE ';
      dbms_session.set_context( 'CTX_WHERE', 'CTX_BEGIN', pVALUE_ARRAY(I) ||
'%');
      v_REC_VALUE := 'Sys_context( ''CTX_WHERE'', ''CTX_BEGIN'' )';

   ELSIF (v_REC_RELATION = 'ENDS_WITH') THEN

      v_REC_RELATION := ' LIKE ';
      dbms_session.set_context( 'CTX_WHERE', 'CTX_END', '%' || pVALUE_ARRAY(I));
      v_REC_VALUE := 'Sys_context( ''CTX_WHERE'', ''CTX_END'' )';

   ELSIF (v_REC_RELATION = 'PHRASE') THEN

      v_REC_RELATION := ' LIKE ';
      dbms_session.set_context( 'CTX_WHERE', 'CTX_PHRASE', '%' ||
pVALUE_ARRAY(I) || '%');
      v_REC_VALUE := 'Sys_context( ''CTX_WHERE'', ''CTX_PHRASE'' )';

   ELSIF (v_REC_RELATION = 'SOUNDS_LIKE') THEN

      v_REC_COL := 'SOUNDEX(' || pCOL_ARRAY(I) || ')';

      v_REC_RELATION := '=';
      dbms_session.set_context( 'CTX_WHERE', 'CTX_SOUND', 'SOUNDEX(' ||
pVALUE_ARRAY(I) || ')');
      v_REC_VALUE := 'Sys_context( ''CTX_WHERE'', ''CTX_SOUND'' )';

end if;

Question:
Is it better to set context just once:
as
"  dbms_session.set_context( 'CTX_WHERE', 'BV_' || I, pVALUE_ARRAY(I));"

then keep changing the value for v_REC_VALUE (inside an if condition) as
v_REC_VALUE := '%' || 'Sys_context( ''CTX_WHERE'', ''BV_' || I || ''' )';

v_REC_VALUE := 'Sys_context( ''CTX_WHERE'', ''BV_' || I || ''' )' || '%';

....
....
Followup:
up to you -- whatever makes more sense codewise to you
Rating: 4
Cursor in 8.1.7 May 31, 2003
Reviewer: Randy from Warren, MI
Hi Tom,
I have a cursor for loop which runs on a varchar2(4000) column.  In 9i the
cursor runs no problem.  However, in 8.1.7, the column returns a blank value
with > 1,000 characters.  I thought maybe this was the 32K PL/SQL limit, but
then why does this work in 9i?  I have been looking for days for the answer on
this one.  Any help you can provide will be greatly appreciated!
Followup:
you will have to provide a test case.

I've never observed what you say -- never.
Rating: 4
May 31, 2003
Reviewer: Randy from Warren
Thanks for your respose...
  I have the following PL/SQL statement which outputs data to MS word using
Oracle COM automation feature...

declare

i binary_integer;

filename varchar2(100);

begin

filename:= 'c:\test\report_'||sysdate;

i:=ORDWord.CreateWordObject('');

i:=ORDWord.FileNew();
i:=ORDWord.FormatFilePageSetup(1, 1, 54, 54, 54, 54, 0, 15840, 11520);
i:=ORDWord.FormatFontSize(12, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 1,
0);
i:=ORDWord.FormatFontParagraph(0, 0, 0, 0, 0, 0, 1);
i:=ORDWord.InsertText('*** FOR OFFICIAL USE ONLY ***');
i:=ORDWord.InsertNewLine();
i:=ORDWord.InsertText('PM Combat Systems Weekly Significant Activity Report');
i:=ORDWord.InsertNewLine();
i:=ORDWord.InsertNewLine();
i:=ORDWord.FormatFontSize(11, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 1,
0);
i:=ORDWord.InsertText('Period Ending: '||sysdate);
i:=ORDWord.InsertNewLine();
i:=ORDWord.InsertNewLine();
i:=ORDWord.InsertNewLine();
i:=ORDWord.InsertNewLine();

For c1_rec IN (SELECT ROWNUM, SUBJECT, ISSUE, RESPONSIBLE_POC, PHONE_NUMBER FROM
one_liner2 where status = 'Approved')
LOOP

i:=ORDWord.FormatFontSize(10, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 1,
0);
i:=ORDWord.FormatFontParagraph(0, 0, 0, 0, 0, 0, 0);
i:=ORDWord.InsertText(c1_rec.rownum||'. '||c1_rec.SUBJECT||':');
i:=ORDWord.FormatFontSize(10, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0,
0);
i:=ORDWord.FormatFontParagraph(0, 0, 0, 0, 0, 0, 0);
i:=ORDWord.InsertNewLine();
i:=ORDWord.InsertText(c1_rec.ISSUE);
i:=ORDWord.InsertNewLine();
i:=ORDWord.InsertNewLine();
i:=ORDWord.InsertText(c1_rec.RESPONSIBLE_POC||'/'||c1_rec.phone_number);
i:=ORDWord.InsertNewLine();
i:=ORDWord.InsertNewLine();

END LOOP;

i:=ORDWord.FileSaveAs(filename);

i:=ORDWord.FileClose();

end;

When I run the statement in 9i it works exactly the way it should.  However,
when I run in our current production environment any data stored in the issues
varchar2(4000) column that is > 1000 characters the cursor returns nothing.
Everything else returns fine.
Followup:
so, develop a small test case that shows where the issue is.  I mean -- I'll
betcha that the cursor is returning the data but the ORDWord thing is having
issues with it.

eg: either keep the cursor but remove the ordword calls and just dbms_output
substrs of the data.  If you see data -- then you've narrowed the problem down.

Once you do that, remove the sql and just make ordword calls.  You know, declare
a local variable of varchar2(4000) -- fill it up and use it instead of SQL (so
you have a reproducible test case that standsalone that you can give to support
and they too can run).

After that -- try the logical workaround -- writing out the string in 512 byte
chunks instead of in a single call.  Sort of like a procedure "P" I keep around
for dbms_output which only does 255 characters:

create or replace procedure p ( p_str in varchar2 )
is
   l_str   long := p_str;
begin
   loop
      exit when l_str is null;
      dbms_output.put_line( substr( l_str, 1, 250 ) );
      l_str := substr( l_str, 251 );
   end loop;
end;
/

and see if that doesn't work around your issue.

It is funny, if you go back to your text above:

...
I have a cursor for loop which runs on a varchar2(4000) column.  In 9i the
cursor runs no problem.  However, in 8.1.7, the column returns a blank value
with > 1,000 characters.  I thought maybe this was the 32K PL/SQL limit, but
then why does this work in 9i?  I have been looking for days for the answer on
this one.  Any help you can provide will be greatly appreciated!
......

you make no mention of ORDWord.  You never tested the "cursor" (it undoubtably
is NOT the issue here -- not a chance, it is not part of the problem at all).

You really need a TEST system that mirrors PRODUCTION -- developing in 9i and
deploying to 8i just is a recipe for disaster -- cannot be done.  You need a
system whereby you can isolate these issues (production isn't that environment).
 I certainly hope you have such a system -- you might have to run 5 or 6
variants of the above code to isolate the exact issue and then you need to
develop a workaround for it (i won't be able to -- you see, my computers don't
do "com" -- open systems, sure, not com.  My solution would be UTL_FILE, write
some HTML.  Pretty much every editor reads HTML.  "open", "easy", "portable" --
that is what it is all about)
Rating: 4
June 01, 2003
Reviewer: Randy
Hi Tom,
  Thanks for the response on Sunday:-).  I figured this was a cursor problem
related to 8.1.7 because it works in 9.  But from what you're telling me this
isn't the case.  I will try some of your suggestions and see where it gets me.
Again, thanks for taking the time to read my question!
Rating: 5
June 02, 2003
Reviewer: Randy
Hi Tom,
 You were right, it was the ORDWord causing the problem.  I tried your
suggestions and I think I figured out a possible work around. If I pull the data
using the ORDWord.inserttext function 1000 characters at a time it works fine.
However, if the data is < 1000 characters I get a fetched column value is NULL
for substr > 1000 characters?  Can I write an exception clause if
ORDWord.Inserttext(v_issue2) –- v_issue2 is the variable that holds > 1000
characters-- is null then do nothing?

Thanks,
Randy
Followup:
just use a function like this:

create or replace procedure p ( p_str in varchar2 )
is
   l_str   long := p_str;
begin
   loop
      exit when l_str is null;
      dbms_output.put_line( substr( l_str, 1, 250 ) );
      l_str := substr( l_str, 251 );
   end loop;
end;
/

using ordword instead of dbms_output -- that should not through any exceptions
Rating: 5
June 02, 2003
Reviewer: Randy
Tom,
  Thanks for all your help. Your example was exactly what I was looking for!!

Randy
Rating: 5
Something Global inside triggers June 05, 2003
Reviewer: Sachin from Germany
8.1.7.4.0
Tom,
We have some Auditing triggers on our tables..
USER is accessible to triggers automatically..
I don't know any way to pass parameters to triggers.
is there a way to have a variable that we can put in some kind of global
context..that will be accessible to the triggers...for a session.

Thanks.
Followup:
set a package variable.

create or replace package my_pkg
as
   some_variable varchar2(25);
end;

anything can access my_pkg.some_variable now.
Rating: 5
context for triggers June 05, 2003
Reviewer: Sachin from Germany
Database Version: 8.1.7.4.0
Sorry for not being clear...
Actually, I am trying to catch the persons identity who is logged in (to use it
for MODIFIEDBY column).
Database user who is logged into the app is always the same..so does not help us
for auditing.
When a user logs in the app catches their NT login name...
but it is not a USER parameter (Select user from dual) that is visible to the
trigger..so I was wondering if their is a way to catch that for a session..and
use it in a trigger..
instead of moving some of the auditing to procedures...
Front end is Active Server Pages
Followup:
not without the application itself telling us, we cannot reach out and just grab
this information.

You can use dbms_application_info.set_client_info (then userenv('client_info')
will have it)

You can create an application context and set it.

You can create a package with a "set user", "get user" function and use that.
Rating: 5
orword June 06, 2003
Reviewer: mo
Tom:

what is ordword or oracle com software? can i write a pl/sql procedure that will
dump my table into a MS Word file using that?
Followup:
you can, but I think that would be wrong.

generate open solutions -- make it so anything can use your output.

HTML -- open
XML -- open
COM -- not open (or any of its other names like dcom, activex, .nyet, whatever)
Rating: 5
COM June 07, 2003
Reviewer: mo
Tom:

Thanks, but is COM software part of oracle database? or it is some extra tool
that you have to get?
Followup:
COM would be a MS protocol.

On Windows, the Oracle server comes with a package that lets you make COM
callouts to COM enabled things.

On Unix and other platforms, it doesn't since that protocol is pretty much all
about -- well -- windows.
Rating: 5
SYS_CONTEXT June 12, 2003
Reviewer: Sachin from Germany
8.1.7.4.0
Tom,

What is the difference between these two:
SELECT USERENV( 'client_info' ) INTO VUSER from dual;
SELECT SYS_CONTEXT('USERENV','CLIENT_INFO') from dual;

Thanks.
Followup:
SELECT SYS_CONTEXT('USERENV','CLIENT_INFO') from dual;
is the new prefered way where as

SELECT USERENV( 'client_info' ) INTO VUSER from dual;
is the old, deprecated way.

since sys_context was added and lots of new functionality surrounding it is
avaiable, it is prefered that you use sys_context but userenv() will remain for
quite a while as there is lots of code out there that uses it (including the
oracle data dictionary views themselves)
Rating: 5
GREAT!!! June 12, 2003
Reviewer: lou from NEW YORK
The information was great!!! however, how can I put an
index to speed things up in this kind of situation. I am very
interested on how you andwer this because you are using LIKE thus preventing it
from using an index.

Thanks,

LNL
Followup:
like doesn't prevent an index?

not sure what you are asking for here?
Rating: 4
related to original question June 17, 2003
Reviewer: sam from NY ,USA
Hi tom

I am not sure if I posted this question earlier. I don't see it after 1 hour.

can we have dynamic cursors with 'for update / current of '
clauses ??

 TYPE reference_cur IS REF CURSOR;
      c1 reference_cur;

     open c1 for 'select cust_num from ' || table_name || ' for update'; --
table_name is procedure input parameter
     loop
        fetch c1 into custnum;
        exit when c1%notfound;
     -- some calculations here
      execute immediate 'update ' || table_name || ' set
      deleted_ind=' || '''N''' || ' where current of c1';

     end loop;

thanks
Followup:
look on the other page where you put this
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/server.920/a96521/tspaces.htm#19171
Rating: 4
I did not put this earlier June 17, 2003
Reviewer: sam from NY , USA
Tom

I searched but this is the only thread I have put my question in.

Also The link you provided as a answer is for tablespace link.

Please answer my question
Followup:
sorry but you did,

I pasted the wrong link, see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:584023239495#10489907277766
which is the other page you asked this on.
Rating: 5
Can this be bounded June 25, 2003
Reviewer: Sachin from Germany
8.1.7.4

Tom,
I wrote a procedure to build the where clause..
For example

procedure build_where (pCOL ARRAY,
                       pRELATION ARRAY,
                       pVALUE ARRAY,
                       pQUERY OUT LONG);

Then I should be able to pass array params to it like
pCOL := 'emplid','empname','dept'
pRELATION := '=','=','is'
pVALUE := '2030','SMITH','NULL'

and it should return:
emplid = '2030' and empname = 'SMITH'
AND dept is null

I am using dbms_context to bind values for pVALUE.
It all works fine for all the cases..except for
the third value:
dept is null.

The procedure does produce the string the way it supposed to..and I check the
values with in sys_context..it is 'NULL'

but when I open the dynamic query..I get this error:
ORA-00908: missing NULL keyword

Is it because it's a key word and only values can be bounded...Please help..

Thanks
Followup:
you'll loop over p_col right

  for i in 1 ... p_col.count
  loop
     l_where := l_where || ' and ' || pCol(i) || ' ' || pRelation(i);
     if ( pValue(i) is null )
     then
         l_where := l_where || ' null';
     else
         dbms_session.set_context( .... );
         l_where := l_where || '  sys_context( ''ctx'', .....
     end if;
  end loop

and set:

   pValue := '2030', 'SMITH', null

(or check for the STRING null).... Additionally - you need a 4th array --
DATATYPE of S, N, D (string number date) so you know when to wrap the
sys_context call by to_number and to_date!
Rating: 5
How to use session context in mod_plsql July 01, 2003
Reviewer: A reader from CA, USA
Tom,

If I use mod_pl/sql and session is stateless, how does the session context work?
since session is changing from page to page.
Followup:
well, in this example, it didn't matter that is was stateless.  we used the
context to "bind" with.

see
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5669294472933
for "global application contexts" which can persist over connections.
Rating: 5
Maharaj July 01, 2003
Reviewer: Maharaj ki Jai Ho
CREATE OR REPLACE FUNCTION test(loan NUMBER,loantype VARCHAR2)
 RETURN VARCHAR2
 IS
 CURSOR loanc(Cloan NUMBER) IS
 SELECT loan_total,loan_interest,loan_category
 FROM loans
 WHERE loan_KEY=cloan;

 fun_return VARCHAR2(1) := 'Y';
 BEGIN
 IF loantype='interim'  THEN
    FOR REC IN loanc(loan) LOOP
           IF REC.loan_total=0 OR (REC.loan_interest=0 AND loan_category <> 'I')
        THEN fun_return:='N';
        EXIT;
        END IF;

    END LOOP;
  END IF;

  RETURN fun_return
  END;

Tom, my requirment is that , just like that above function, I will have to do 6
more functions doing the same validation
but against different loan categories , and I will have to hit their respective
tables.

One straight forward way is to declare 6 cursors, and repeat the above for all
the 6 scenarios i.e. for all the 6 loan categories.

But I want to know if I can write a generic routine , which can account for a
single function taking care of all the 6 scenarios.

The function obvious should accept 6 different table names, one at a given time,
and also different column names.

The number of columns might also differ as in two cases we will be selecting
only the first two columns, in other cases there will be 3.

Can you suggest the most correct method to write this dynamic sql , which also
uses bind variables.

Thanks

Maharaj
Followup:
You "could", but you "shouldn't"

static sql is better then dynamic sql -- more performant, easier to
debug/maintain.  6 is a small number.

code should just be a single SELECT INTO, no procedural code needed or desired:

ops$tkyte@ORA920> create table loans ( loan_key int, loan_total int,
loan_interest int, loan_category char(1) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> variable loan number
ops$tkyte@ORA920> exec :loan := 55;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select decode( count(*), 1, 'N', 'Y' )
  2    from dual
  3   where exists ( select null
  4                    from loans
  5                   where loan_key = :loan
  6                     and ( loan_total = 0
  7                           or
  8                           (loan_interest = 0 and loan_category <> 'I')
  9                         )
 10                )
 11  /

D
-
Y

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into loans values ( 55, 0, 0, 'x' );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select decode( count(*), 1, 'N', 'Y' )
  2    from dual
  3   where exists ( select null
  4                    from loans
  5                   where loan_key = :loan
  6                     and ( loan_total = 0
  7                           or
  8                           (loan_interest = 0 and loan_category <> 'I')
  9                         )
 10                )
 11  /

D
-
N
Rating: 4
dynamic columns July 29, 2003
Reviewer: ramesh from Sweden
Tom,

This is in response to your suggestion about using DBMS_SQL for the situation
where different number of columns are to be selected at different times.

My question is when i dont know what columns i use until runtime how can i do
DBMS.define_column(....) !!!

My actual need is like this.

We have a table with coulmns for each month from 2001 year onwards.i.e like
c_200001,c_200002 .. c_200306
Every month we add a new column in a table and ppopulate values into column from
c_200001 to the latest added column.
I have a script to generate SQL .. but i got stuck at taking the values into
variables and then use them to update the table columns.
TIA
Rd
Followup:
dbms_sql.describe_columns tells you how many and what types they are.

this does sound like "a bad design", seems to me you want to add a ROW per
month, not a col
Rating: 4
dynamic columns July 30, 2003
Reviewer: ramesh from sweden
Hi Tom,

Thanks for the followup.

You may be right .. it may be a bad design..
But what i asked was about columns not rows.

Please suggest me a better way..

I am pasting some parts of the code ..
--** code starts
....
type mthstab is table of varchar2(7) index by binary_integer;
mths mthstab;
....
....
mths(1):='jan'; mths(7):='jul';
mths(2):='feb'; mths(8):='aug';
mths(3):='mar'; mths(9):='sep';
mths(4):='apr'; mths(10):='oct';
mths(5):='may'; mths(11):='nov';
mths(6):='jun'; mths(12):='dec';

    vSql:='select acct_num ';
    for y in 2000 .. to_char(add_months(sysdate,-1),'yyyymm')
    loop
        for m in 1 .. 12
        loop
              vSql:=vSql||',sum(case when period='||y||lpad(m,2,'0')||'
then balance else 0 end) balance'||'_'||mths(m)||'_'||substr(y,3,2);
        end loop;
    end loop;
    vSql:=vSql||' from accounts where acct_num=sys_context(''ACC_CTX'',''ACC'')
and period between ''200001'' and '||vper||' group by acct_num';
...
...
--** code ends

The above loop creates an SQL as follows..

select acct_num
       ,sum(case when period=200001 then balance else 0 end) balance_jan_00
...
...
       ,sum(case when period=200001 then balance else 0 end) balance_jul_03
from accounts
where acct_num=sys_context('ACC_CTX','ACC')
and period between '200001' and '200306' group by acct_num;

-- My source table (accounts) has records for each month ..
-- My target table has column for each month
-- My objective is to take this growing records of source table to growing
columns of target table.

Kindly suggest something.

Thanks
Rd
Followup:
the better way is what I did suggest -- that you fix the model.

else you are writing dynamic sql every single time.  nothing I can do to make
that easy, efficient, fast, sleek, or correct.

it looks like you have the data correctly stored and are being forced to pivot
it.

you've done all that you can do.
Rating: 4
dynamic columns July 30, 2003
Reviewer: ramesh from sweden
Hi ,

Thanks again ..
There is nothing i can do with the model.
We have gotto feed a SAS system and they need columns growing.

This loading will be monthly run .. and it would be a couple of thousand records
a month to process.

If you say its worst possible approach i look for a totally different approach.
Or you may suggect me how i can reacd the columns returned by Cursor in to array
or whatever so that i can update the target table.

I will have to use another dymanic SQL ( in the same manner i did) for updation
also for which i dont think i can do otherwise.

Thanks
Rd
Followup:
what you are doing -- with the pivot query -- is the approach to take to pivot
data like that.

a couple thousand records won't be very hard to do.
Rating: 5
Session contexts with shared connections August 22, 2003
Reviewer: Steve from UK
Tom
I have written a procedure to build a dynamic statement using the session
context method described here which works a treat. I am just a bit concerned how
this works when the connections are shared for multiple web users. Should I be
concerned about conexts from a previous user persisting in a session? It is not
possible to clear the contexts after execution of the procedure since the
ref_cursor returned to the calling app, will then return no rows. The
alternative is to clear the contexts at the start of each execution but is it
worth the overhead? Any contexts used in the new query will overwrite the old
anyway? Your advice will be much appreciated.
Followup:
context is uga memory - migrates with the session when using shared server.

if you are using a connection pool -- you need to clear/set the context after a
"grab connection" call.
Rating: 4
Which is more faster August 25, 2003
Reviewer: Mohan K from Bangalore, India
I am developing an application package in PL/SQL where I have two choices. One
to write static sql statements and the other to develop generic package where
the tablename and the column name is passed dynamically by the calling
application. My question is for a given query on a table which is faster, static
SQL or Dynamic SQL.

If the query is static SQL the everything can be written in one SQL statement.
If it is dynamic SQL then there will be a sequence of procedure calls in a loop.
Is there any way to make the performance of both mwthods same.

Mohan
Followup:
static sql wins hands down for so so so many reasons.

performance being one.
maintainence another.
dependency tracking a third.
"bug free code" for a fourth.

and so on.

use dynamic sql if and only if a static sql approach is not in the realm of
possible.

the only way to make a dynamic sql implementation of a routine that could be
done via static sql perform as well would be to recode it using static sql :)
Rating: 5
Hi Tom August 25, 2003
Reviewer: A reader
Can you please explain?
"performance being one.
maintainence another.
dependency tracking a third.
"bug free code" for a fourth.
"

I know performance is better in static?

But I don't understand the two points
  maintenance
  dependency tracking

Could you explain these a little more?

Thanx!
Rating: 5
September 04, 2003
Reviewer: A reader
Rating: 5
9.2.0.4 and sys_context September 21, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom
We are using sys_context on data from remote tables. Quoting from your example:

 begin
 16
 17      if ( p_ename is NOT NULL ) then
 18          dbms_session.set_context( 'MY_CTX', 'ENAME',
 19                                    '%'||upper(p_ename)||'%');
 20          l_query := l_query ||
 21               ' and ename like
 22                 sys_context( ''MY_CTX'', ''ENAME'' ) ';
 23      end if;

The context is set on the local database but the query refers to tables and
columns on the remote database through a database link. Till 9.2.0.3, Oracle was
fetching all records from remote table and filtering them on the local database
as seen in the explain plan. Since the context namespace was declared on the
local database, the query was giving correct results.

After applying the 9.2.0.4 patch, the optimizer started sending the entire where
clause including sys_context to the remote database. Since the context namespace
does not exist on the remote database, the where clause never finds any matching
data and query fails to return any results.

Any suggestions?

Thanks...
Followup:
use the driving site hint as a workaround, or  you can try the alternative
suggested above of:

...
Can't you achieve the same (without using sys_context), creating dummy WHERE
condition if passed parameter has null value. So, you will always use the same
number of bind variables in USING clause.
      if ( p_ename is NOT NULL ) then
           l_query := l_query ||
                ' and ename like  ''%''||:p_ename||''%''';
       else
           l_query := l_query ||
                ' and 1 = DECODE(:p_ename,NULL,1,1)';
       end if;

............

and then open the cursor with a fixed number of bind inputs.
Rating: 5
September 21, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
The driving_site hint is also sending the entire query including the where
clause to the remote database.

The other alternative won't work because of the number of  parameters vary from
all possible combinations of 0 to 9. This was the reason we used sys_context in
the first place.

The only workaround which seems to work is to replace all occurrences of
sys_context() with select sys_context() from dual. Is there any downside to
using this ?

This behavior gives rise to another question. When does Oracle populate bind
variables in queries issued against a remote database? The bind variable values
are user session specific. Thus Oracle should populate bind variables before
sending queries to the remote database. If sys_context is treated as a bind
variable, shouldn't Oracle evaluate it's value before sending to remote
database?

Thanks...
Followup:
the other altnative DOES work -- you would always have a fixed number of inputs
- it would work 100% (it has to, else sys_context would not either!)

selecting from dual works nicely.

If the query is optimized on the remote site, thats where it'll take place with
sys_context.  sys_context is "like" a bind, it is not a "bind"
Rating: 3
Why using sys_context for dynamic sql is slower? October 01, 2003
Reviewer: Jap Boon Churn from Malaysia
Hi Tom,

I was very happy to found this article yesterday and hoping i can apply and use
this new technique (sys_context) for my query tuning activity currently.

However, it does not turn out to be satisfying.
I created 2 procedures, exactly the same, except one uses sys_context to assign
the parameter value to dynamic sql statement, the other one just put in literal
value into the query. Both using oracle text. My database is 8.1.6.

The one not using sys_context finishes query in 1 or 2 seconds for 90 records.

The one using sys_context finishes query in 9 or 10 seconds for 90 records as
well.

I feel surprise to see this, cos that's a great difference.

Therefore, would appreciate if you can provide some advice for me, maybe there
are some other areas that i miss out.

Would appreciate your followup.
Thanks.
Followup:
tkprof will show us what the issue is.  lets see the tkprof of the two queries.
Rating: 5
9.2.0.4 and sys_context October 10, 2003
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom,
Please see my post above about 9.2.0.4 sending the sys_context to remote
database. I opened a TAR with support and development agreed that this was a
bug. This will be fixed in the next patchset.
Thanks.
Rating: 4
October 29, 2003
Reviewer: A reader
ops$tkyte@ORA920> select decode( count(*), 1, 'N', 'Y' )
  2    from dual
  3   where exists ( select null
  4                    from loans
  5                   where loan_key = :loan
  6                     and ( loan_total = 0
  7                           or
  8                           (loan_interest = 0 and loan_category <> 'I')
  9                         )
 10                )
 11  /

If we select the above selected value into a variable like the below, then do we
need an exception section to handle the no_data_found exception...

ops$tkyte@ORA920> select decode( count(*), 1, 'N', 'Y' )
                          into lv_variable
  2    from dual
  3   where exists ( select null
  4                    from loans
  5                   where loan_key = :loan
  6                     and ( loan_total = 0
  7                           or
  8                           (loan_interest = 0 and loan_category <> 'I')
  9                         )
 10                )
 11  /
Followup:
an aggregate without a group by ALWAYS returns at least one row - and at most
one row.

No exception handling needed -- you'll never get too_many_rows nor
no_data_found.

ops$tkyte@ORA920LAP> select count(*) from dual where 1=0;

  COUNT(*)
----------
         0

1 row selected.

ops$tkyte@ORA920LAP> select max(1) from dual where 1=0;

    MAX(1)
----------

1 row selected.
Rating: 5
cursor count December 03, 2003
Reviewer: mo
Tom:

AS a followup to how to count records and print total in an implicit cursor you
said to use rownum or count them.

For x in (select * from emp where soundex(last_name)= soundex(i_last_name) )
LOOP

END LOOP;

IF I count in the loop then I would not be able to print the total before the
result set like "Total Matches: 6" and then print them.

I do not see how rownum will five me the total.

Do I have t orun a separate count before I print the result set?

Thank you
Followup:
don't print the total "first" would be *my* answer

otherwise, you have to fetch all of the rows, and THEN print them.

if the result set is "small" enough (less then hundreds) then

select * BULK COLLECT into ...
  from emp
  where ....;

will work -- you'll fill up array(s) of data, which you can use the .count
attribute on to see "how many" and then print.
Rating: 5
count December 04, 2003
Reviewer: mo
Tom:

Is this "BULK COLLECT" new feature in 9i?

Do you mean to use it instead of using implicit cursor since it allows me to do
counts?

Thank you,
Followup:
no, it was in 8i as well.

i'm saying "don't use it, put the count at the bottom (bottom, top, side, -- who
cares really)"

but if you feel compelled to print it
and the result set is smallish (hundreds at most)
bulk collect it and you can use the .count attribute on the arrays to see "how
many there are"

ops$tkyte@ORA815> declare
  2      type array is table of varchar2(2000) index by binary_integer;
  3
  4      l_ename array;
  5      l_empno array;
  6  begin
  7      select ename, empno bulk collect into l_ename, l_empno
  8        from scott.emp;
  9
 10      dbms_output.put_line( l_ename.count || ' rows selected' );
 11
 12      for i in 1 .. l_ename.count
 13      loop
 14          dbms_output.put_line( l_ename(i) || ',' || l_empno(i) );
 15      end loop;
 16  end;
 17  /
16 rows selected
A,7369
ALLEN,7499
WARD,7521
JONES,7566
MARTIN,7654
BLAKE,7698
CLARK,7782
SCOTT,7788
KING,7839
TURNER,7844
ADAMS,7876
JAMES,7900
FORD,7902
MILLER,7934
ant,999
ant,9999

PL/SQL procedure successfully completed.
Rating: 5
set context December 15, 2003
Reviewer: mo
Tom:

I am trying to build a ref cursor as this.  Either fo a soundex search on last
name, or do a last name character search or both.

  dbms_session.set_context('LN_CTX','LAST_NAME',i_last_name);

    IF (i_soundex_search = 'Y' and i_lname_search is null ) THEN
       open l_cursor for select * from applicant where soundex(last_name) =
soundex(i_last_name);
    elsif ( i_soundex_search is null and i_lname_search = 'Y' ) THEN
       open l_cursor for select * from applicant where last_name like
sys_context('LN_CTX','LAST_NAME');
    elsif ( i_soundex_search is null and i_lname_search = 'Y' ) THEN
         open l_cursor for select * applicant where soundex(last_name) =
soundex(i_last_name)
            or last_name like sys_context('LN_CTX','LAST_NAME');
    END IF;

1.  I keep getting an error:
Mon, 15 Dec 2003 14:37:52 GMT

ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_SESSION", line 78
ORA-06512: at "IDEV.DDS_APPLICANT", line 1246
ORA-06512: at line 10

DBA says he gave access to IDEV on DBMS_SESSION and when I log in as IDEV and do
desc dbms_session i get results.

This is a 9i database.  Do you know what is the problem?

2.  When you build a query and want to use "like" it seems you do not have to
use "%" and the bind does it automatically? Is this correct.

3. Is these any other way to build ref cursor and bind variables without
dbms_session.

Thank you,
Followup:
1) A context is created bound to a package or procedure:

create or replace context LN_CTX using <procedure>;

ONLY that procedure is allowed to set it, this error indicates you are not
setting it in the procedure it was defined with.  It is not anything to do with
dbms_session -- you are "privileged" to run dbms_session (the error is INSIDE of
dbms_session -- if you weren't allowed to run it, the error wouldn't be INSIDE
of it!)

2) that is not correct.  if you want a % you better add a %, else there will be
no %

3) look at Darko Egersdorfer's comment above.
Rating: 4
set context December 15, 2003
Reviewer: mo
Tom:

I do login to sql*plus using IDEV account and run

SQL> create or replace context ln_ctx using dss_applicant.find_applicant
/
Context Created.

The name of my package is "DSS_APPLICANT".

The name of my procedure is "FIND_APPLICANT".

I still get the error.

Mon, 15 Dec 2003 16:27:36 GMT

ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_SESSION", line 78
ORA-06512: at "IDEV.DSS_APPLICANT", line 1246
ORA-06512: at line 10

2.  How do you insert the "%" with the bind variable.

do you do like this:

open l_cursor for select * from emp where lname like :last_name ||'%'

Thank you,
Followup:
that is wrong.  the "using" is either the

a) name of a package
b) name of a procedure (standalone)

you've just create a context that can be modified by a package (any function or
procedure in that package) or standalone procedure in the schema DSS_APPLICANT

I thing you just want to say "using dss_applicant"
Rating: 5
ref cursor December 17, 2003
Reviewer: mo
Tom:

You are right. Context should be created using package.

1. If I want to use an index in my search for this cursor Would I just create a
function based index on soundex(Last_name).  How about the 2nd and 3rd if clause
when I want to search by either soundex or like last name.

 dbms_session.set_context('LN_CTX','LAST_NAME',lower(i_last_name));
  IF (i_soundex_search = 'Y' and i_lname_search is null ) THEN
       open l_cursor for select * from applicant where soundex(last_name) =
soundex(i_last_name);
    elsif ( i_soundex_search is null and i_lname_search = 'Y' ) THEN
      open l_cursor for select * from applicant where lower(last_name) like
sys_context('LN_CTX','LAST_NAME')||'%';
    elsif ( i_soundex_search ='Y' and i_lname_search = 'Y' ) THEN
       open l_cursor for select * from applicant where soundex(last_name) =
soundex(i_last_name) or
              lower(last_name) like sys_context('LN_CTX','LAST_NAME')||'%';
    END IF;
Followup:
looks like you really might want a TEXT index on this (support the like, keyword
matches, soundex, fuzzy searches, etc)

but -- yes, a b*tree index on soundex would let the first query use an index

a b*tree index on lower(last_name) would let the second

and the third, could, if appropriate, use both.
Rating: 4
December 29, 2003
Reviewer: reader from India
Presently we have code which uses cursor as follows

procedure sample
declare
cursor c1 is select ....
begin
for rs in c1
loop
 processing;
end loop;
end;

now the query for cursor has to be dynamic, the values in list will be selected
from a table and the query string be formed so I am thinking of this way
just an example

declare
lemp emp.empno%type;
type emprec is ref cursor;
c1 emprec;
lstring varchar2(200) := 'select * from emp where empno in';
begin
lstring := lstring || '(999,888)';
open c1 for lstring;
loop
fetch c1 into lemp;
dbms_output.put_line(lemp);
exit when c1%notfound;
end loop;
close c1;
end;

I would like to have your suggestion on this is this ok to go with or should I
use dbms_sql. Please suggest.

Thanking you as always for all your help.

Best Regards.
Followup:
USE BIND VARIABLES

do not, repeat, do not even consider this approach.

either

a) search this site for

varying in list

to see a technique that'll let you bind a comma delimited list

b) use a global temporary table, insert the values from the list into the gtt
and then query using "where empno in (select * from gtt)"

In short -- stick with STATIC SQL, use BIND VARIABLES.
Rating: 4
Thanks Tom for your help December 30, 2003
Reviewer: reader from India
Hi Tom,
      Good day to you, well learning from you by your site and book I can't make
the mistake of ignoring bind variables, but I didn't gave proper example sorry
for that. I am a little confused with your last sentence "In short -- stick with
STATIC SQL, use BIND VARIABLES" if I am getting it right do you mean the cursor
definition should be like this
Cursor c1 is select * from emp where empno in (select empno from tempemp), if I
am wrong please do correct me, also this is just one condition there will be
other conditions too depending on the values in a table those have to be
incorporated in the query so in this case how should it be done.

Thanks again for your time and help.

Best Regards.
Followup:
that is great

select *
  from emp
 where empno in ( select empno from gtt );

if you have to dynamically build the predicate, then yes, you will have to use
dynamimc sql -- see the technique outlined in the original answer above.
December 30, 2003 Farhad -- Thanks for the question regarding "Question regarding restrict_references.", version pl/sql8
You Asked
Hi Tom,
    I am preparing for my OCP-DBA exam and I have a question.It
is as follows:
Can you please explain me the use of purity levels in Pl/Sql
8 stored procedures? And the use of Pragma Restrict References?

Thanks,
Farhad.
and we said...
Purity Levels come in 4 flavors:

o WNDS (Write No Database State)
o RNDS (Read No Database State)
o WNPS (Write No Package State -- does not modify global package

        variables)
o RNPS (Read No Package State -- does not read global package
        variables)

In order for a function to be called from SQL (eg: I would like
to be able to "select my_function(1) from dual" -- where
my_function is some plsql i wrote) it must at least promise to
WNDS.  It cannot modify the database (via an insert or update
for example).  It cannot commit.  It cannot perform DDL (which
both writes and commits the database state).

The other levels allow for more and more features to be used.
For example, a function called from SQL may be executed in
parallel if it promises to WNDS + WNPS + RNPS.  A function
called from SQL may be used in a where clause if it promises to
WNDS+WNPS.  And so on (see
http://technet.oracle.com/doc/server.804/a58241/ch7.htm#6048
for all of the details).

For standalone (non-packaged) procedures and functions, the
"purity" level is computed at compile time and stored in the
database.  For packaged functions however, the purity level is
not evaluated -- in Oracle8.0 and before -- you must assert the
purity level via the pragma restrict_references directive (see
link above for details) in order to call that function from SQL
(or to call a function that in turn calls your packaged
function).

It should be noted that in Oracle8i, release 8.1, the rules have
been relaxed a great deal.  The pragma is no longer mandatory on
packaged functions (we'll find out for you without being told)
but you can still use it to assert a certain purity level (so if
you assert that a function is WNDS+WNPS -- we'll tell you at
compile time if that is the case still but its not mandatory
that you do so).  Also, in Oracle8i release 8.1 -- it is
actually possibl to write to the database state using autonomous
transactions ( see
http://osi.oracle.com/~tkyte/autonomous/index.html
for a discussion of that feature)
Review & Followup

Rating: 4
restrict references March 22, 2002
Reviewer: bala from India
Can I make a statement now, after reading this, that the restrict references
pragma is not important at all in 8i,
if no where does it really needs to be used?
Followup:
I've not run into an occasion where I've needed to use it in 8i, no.  You
can use it to assert the purity level you are shooting for.  Eg: if you want to
assert that this piece of code in a package does not write to the database or
package and does not read or write the package state (so its the most flexible
of all -- can be called in the largest number of cases), you could do so.  The
compiler would tell you if you were wrong.

So, you can use it to "check yourself"
Rating: 4
thanx a ton March 24, 2002
Reviewer: bala from India
that answers it all. thanxs tom.
Rating: 5
PRAGMA RESTRICT_REFERENCES July 04, 2003
Reviewer: Alex from Brazil
Tom, could you help-me?

   I´m problem in to utility pragma restrict_references.
I need to do alteration in Pl/SQl program where.

create  or replace package pg_test as

function fn_test (pi_cod_city pg_type.cod_city)
         return pg_types.name_city;

pragma restrict_references (fn_test,wnds,wnps);

end;

create or replace package body pg_test as
FUNCTION fn_se_sector_name
    (pi_cod_city         IN  pg_types.tp_cod_city)
    RETURN  pg_types.tp_name_city IS
       v_CITY_NAME          pg_types.tp_nom_setor;
    BEGIN
      SELECT cesp_name_city
      INTO v_name_city
      FROM  vw_city
      WHERE cod_city = pi_cod_city;

      RETURN V_CITY_NAME;
    EXCEPTION
      WHEN NO_DATA_FOUND THEN
        return NULL;
    END;

im try update vw_city to vw_city_test,exception error,

Compilation errors for PACKAGE BODY SCPF_PROC.PG_TEST

Error: PLS-00452: Subprogram 'FN_city_NAME' violates its associated pragma
Line: 243
Text: FUNCTION fn_CITY_name

Error: PL/SQL: Compilation unit analysis terminated
Line: 0
Text: CREATE OR REPLACE PACKAGE BODY pg_TEST AS

tks a lot
Followup:
do you have a database version and create table to go along with that --
something I can just cut and paste myself.
Rating: 3
Not getting ORA-06571 when I should December 30, 2003
Reviewer: David from USA
Hi Tom.

I've got a package that includes one function.  Ordinarilly, we've always had to
restrict_references on the package and the function.  This morning, while
demonstrating this to a colleague, I commented out the pragmas, compiled the
package and the package body, and then ran the package.  But Oracle did NOT give
me an ORA-06571.  We're running 8.0.6.  And the procedure updates tables (the
function does not), but those tables are on a remote database.  Any reason why
this is allowed without the pragma?

Package:

as
  procedure set_reprice;
  procedure start_proc( p_procedure_name in varchar2, p_process_id out number );
  procedure end_proc( p_procedure_name in varchar2 );
  procedure set_step(p_procedure_name in varchar2, p_step in number);
  procedure start_step(p_procedure_name in varchar2, p_step in number,
            p_process_id in number);
  procedure end_step(p_proc_id in number, p_module_step in varchar2);
  procedure exec_sql( p_sql_string in varchar2 );
  procedure write_transaction( p_process_id in number, p_object_name in
varchar2,
              p_action in varchar2, p_data_char in varchar2, p_data_numeric in
number,
              p_object_module in varchar2);
  function recal_reprice  (p_as_of_date in date, p_reprice_freq in number,
              p_next_reprice_date in date) return date;

  procedure update_last_reprice;

  --pragma restrict_references( scrubs, wnds, rnds, wnps, rnps );
  --pragma restrict_references( recal_reprice, wnds, rnds, wnps );

end;
Followup:
they have been relaxing the rules on the pragmas over time.  the database is
figuring out what the purity level is.

The pragma should now be considered as meaning "I intend to code something that
does not do x -- compiler, tell me if I've achieved that", rather then asserting
to the compiler "this function doesn't do this"
December 30, 2003 -- Thanks for the question regarding "Restricting number of entries.", version 9.2.0
You Asked
I hope someone can help me & therefore thank you in advance if you can. Im using
Oracle 9 & the "EMP" table (see below) found in the "DemoBld" DB, of which i
need to ensure that no department is to have more than 8 employees & fewer than
3, except when a transaction reduces the number of employees to 0, of which is
to be reported. How can i do this? PL/SQL, Packages, Triggers? Fortunately the
"Emp" table does have a column called "DeptNo" & therefore only this tables is
required to be used. I look forward to any comments.
*****************************************************
CREATE TABLE EMP (
EMPNO NUMBER(4)
CONSTRAINT EMPNOGTZERO CHECK(EMPNO > 0),
ENAME VARCHAR2(10)
CONSTRAINT ENAMEUPPER CHECK(ENAME = UPPER(ENAME)),
JOB VARCHAR2(9),
MGR NUMBER(4)
CONSTRAINT EMP_SELF_KEY
REFERENCES EMP(EMPNO),
HIREDATE DATE,
SAL NUMBER(7,2),
COMM NUMBER(7,2),
DEPTNO NUMBER(2) NOT NULL,
CONSTRAINT EMP_FOREIGN_KEY FOREIGN KEY (DEPTNO)
REFERENCES DEPT (DEPTNO),
CONSTRAINT EMP_PRIMARY_KEY PRIMARY KEY (EMPNO)
)
/
*****************************************************
EMPNO ENAME     JOB          MGR HIREDATE       SAL     COMM    DEPTNO
---------- ---------- --------- ---------- --------- ---------- ------
7839 KING      PRESIDENT        17-NOV-81     5000                  10
7698 BLAKE     MANAGER     7839 01-MAY-81     2850                  30
7782 CLARK     MANAGER     7839 09-JUN-81     2450                  10
7566 JONES     MANAGER     7839 02-APR-81     2975                  20
7902 FORD      ANALYST     7566 03-DEC-81     3000                  20
7369 SMITH     CLERK       7902 17-DEC-80      800                  20
7499 ALLEN     SALESMAN    7698 20-FEB-81     1600      300         30
7521 WARD      SALESMAN    7698 22-FEB-81     1250      500         30
7654 MARTIN    SALESMAN    7698 28-SEP-81     1250     1400         30
7788 SCOTT     ANALYST     7566 09-DEC-82     3000                  20
7844 TURNER    SALESMAN    7698 08-SEP-81     1500        0         30
7876 ADAMS     CLERK       7788 12-JAN-83     1100                  20
7900 JAMES     CLERK       7698 03-DEC-81      950                  30
7934 MILLER    CLERK       7782 23-JAN-82     1300                  10

14 rows selected.
and we said...
warning: checks like this force serialization at the aggregate level.  you will
be only able to modiy records in the EMP table for a given DEPTNO by one session
at a time!

Here is one solution using deferable constraints:

scott@ORA920> create table count_by_dept
  2  (  deptno primary key ,
  3     cnt    ,
  4     constraint cnt_between_3_and_8_or_0
  5     check ( cnt between 3 and 8 OR cnt = 0 ) deferrable initially deferred
  6  )
  7  organization index
  8  as
  9  select deptno, count(*)
 10    from emp
 11   group by deptno
 12  /

Table created.

scott@ORA920>
scott@ORA920> create or replace trigger dept_trigger
  2  after insert or update of deptno or delete on emp for each row
  3  begin
  4      if (inserting or updating)
  5      then
  6          update count_by_dept set cnt = cnt+1 where deptno = :new.deptno;
  7          if ( sql%rowcount = 0 )
  8          then
  9              insert into count_by_dept(deptno,cnt) values (:new.deptno,1);
 10          end if;
 11      end if;
 12      if (updating or deleting)
 13      then
 14          update count_by_dept set cnt = cnt-1 where deptno = :old.deptno;
 15          if ( sql%rowcount = 0 )
 16          then
 17              raise_application_error( -20001, 'Impossible condition' );
 18          end if;
 19      end if;
 20  end;
 21  /

Trigger created.

scott@ORA920>
scott@ORA920> delete from emp where deptno = 10 and rownum = 1;

1 row deleted.

scott@ORA920> commit;
commit
*
ERROR at line 1:
ORA-02091: transaction rolled back
ORA-02290: check constraint (SCOTT.CNT_BETWEEN_3_AND_8_OR_0) violated

we cannot have just two in deptno = 10

scott@ORA920>
scott@ORA920> delete from emp where deptno = 10;

3 rows deleted.

scott@ORA920> commit;

Commit complete.

but we can have ZERO

scott@ORA920>
scott@ORA920> insert into emp select * from emp;

11 rows created.

scott@ORA920> commit;
commit
*
ERROR at line 1:
ORA-02091: transaction rolled back
ORA-02290: check constraint (SCOTT.CNT_BETWEEN_3_AND_8_OR_0) violated

but we cannot have more then 8 in deptno=30...
Review & Followup

Rating: 3
Restricting number of entries (continued) January 04, 2003
Reviewer: Smithy from UK
Thanks for you assistance so far, but once i have successfully created the table
& trigger without error i enter there exact command you enter but where as your
command is accepted, mine produces an error (see below).

SQL> delete from emp where deptno = 10 and rownum = 1;
delete from emp where deptno = 10 and rownum = 1
            *
ERROR at line 1:
ORA-04098: trigger 'SCOTT.EMP_TRIGGER' is invalid and failed re-validation

This error happens on any department or even if i try to delete all entries for
a dept (see below).

SQL> delete from emp where deptno = 10;
delete from emp where deptno = 10
            *
ERROR at line 1:
ORA-04098: trigger 'SCOTT.EMP_TRIGGER' is invalid and failed re-validation

How come im receiving these error messages, when you didnt & how can i resolve
the issue.

Thanks in advance.
Followup:
you typed something in wrong

SQL> show error trigger emp_trigger
Rating: 5
Wow January 05, 2003
Reviewer: Remi from Netherlands
Hi Tom

Can you believe this, I recieved this exact (and I mean exact) same question on
my newly created forum
http://askremi.ora-0000.com/
see:
http://askremi.ora-0000.com/htdocs/question/show/33
And I think your solution is far more better, I'll keep learning every day ;-)

Greate answer Great forum

Remi Visser
Followup:
don't know if you  would like a critique of your answer or not but... here goes:

a) the insert trigger will fail in many cases.

ops$tkyte@ORA920> insert into emp select * from scott.emp where rownum = 1;
insert into emp select * from scott.emp where rownum = 1
            *
ERROR at line 1:
ORA-04091: table OPS$TKYTE.EMP is mutating, trigger/function may not see it
ORA-06512: at "OPS$TKYTE.TRG_EMP_AFT_INS", line 6
ORA-04088: error during execution of trigger 'OPS$TKYTE.TRG_EMP_AFT_INS'

anytime the insertion might be more then one row -- you lose.  Mutating table.
The for each row trigger cannot read the table.  additionally -- it does not
handle multi-user databases AT ALL.  consider:

ops$tkyte@ORA920> select deptno, count(*) from emp group by deptno;

    DEPTNO   COUNT(*)
---------- ----------
        10          3
        20          5
        30          6

ops$tkyte@ORA920> insert into emp values ( 1, 'x', 'x', 1, sysdate, 1, 1, 30 );

1 row created.

ops$tkyte@ORA920> insert into emp values ( 1, 'x', 'x', 1, sysdate, 1, 1, 30 );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> REM this is another user, another transation:
ops$tkyte@ORA920> declare
  2          pragma autonomous_transaction;
  3  begin
  4          insert into emp values ( 1, 'x', 'x', 1, sysdate, 1, 1, 30 );
  5          commit;
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920> select deptno, count(*) from emp group by deptno;

    DEPTNO   COUNT(*)
---------- ----------
        10          3
        20          5
        30          9

ops$tkyte@ORA920>

whoops -- how did that get there???

I would say the same with your update or delete triggers -- very "single user",
excessively single user -- but nothing to make them single user.

Multi-user integrity check is heinously hard when you try to DIY.  Best left to
the server as you can see....

good luck with the site.  You should put the stuff in a database ;)
Rating: 5
It's also on Experts Exchange January 06, 2003
Reviewer: Gary from Sydney, Australia

http://www.experts-exchange.com/Developer/Databases/Oracle/Tools_Development/Q_20444092.html
But so far, Tom's answer looks the best.
I especially like the 'deferred' bit, which takes care of programs which may
send a series of single delete/update statements, rather one department level
one.

PS. I see dept_trigger is row_level, but if a statement level trigger applied to
both insert and update, does it get fired twice for a merge or just once ? If
once, are both 'inserting' and 'updating' true ?
Is there a possiblilty of
Followup:
Arg -- their solution has the same multi-user bugs as the other ones.

Triggers -- I have a love/hate relationship with them for this reason.  People
just DON'T even think about multi-versioning, read consistency, data integrity
when they do these things.  That trigger provided in that answer

a) doesn't take into consideration "transfers" (updates)
b) doesn't work at all for deletes (well, since it fails I guess you could say
it protects the data integrity)
c) doesn't work for mulit-row inserts
d) appears to work but damages data integrity for single row inserts

I just hope this question is homework from a really smart teacher who wants to
take the collective set of answers and show just exactly how complex this stuff
is -- that some amount of real thought is required for multi-user concurrency
issues -- that you HAVE to let the database do it -- that attempting to do it
via a trigger alone is not possible.

As for merge:

ops$tkyte@ORA920> create table emp as select * from scott.emp;

Table created.

ops$tkyte@ORA920> create table emp2 as select * from scott.emp;

Table created.

ops$tkyte@ORA920> update emp2 set empno = -empno where rownum <= 7;

7 rows updated.

ops$tkyte@ORA920> update emp2 set ename = lower(ename);

14 rows updated.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace trigger emp_bi
  2  before insert on emp
  3  begin
  4      dbms_output.put_line( 'BEFORE INSERT' );
  5  end;
  6  /

Trigger created.

ops$tkyte@ORA920> create or replace trigger emp_bifer
  2  before insert on emp for each row
  3  begin
  4      dbms_output.put_line( 'BEFORE INSERT for each row ' || :new.empno );
  5  end;
  6  /

Trigger created.

ops$tkyte@ORA920> create or replace trigger emp_aifer
  2  after insert on emp for each row
  3  begin
  4      dbms_output.put_line( 'AFTER INSERT for each row ' || :new.empno );
  5  end;
  6  /

Trigger created.

ops$tkyte@ORA920> create or replace trigger emp_ai
  2  after insert on emp
  3  begin
  4      dbms_output.put_line( 'AFTER INSERT' );
  5  end;
  6  /

Trigger created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace trigger emp_bu
  2  before update on emp
  3  begin
  4      dbms_output.put_line( 'BEFORE update' );
  5  end;
  6  /

Trigger created.

ops$tkyte@ORA920> create or replace trigger emp_bufer
  2  before update on emp for each row
  3  begin
  4      dbms_output.put_line( 'BEFORE update for each row ' || :new.empno );
  5  end;
  6  /

Trigger created.

ops$tkyte@ORA920> create or replace trigger emp_aufer
  2  after update on emp for each row
  3  begin
  4      dbms_output.put_line( 'AFTER update for each row ' || :new.empno );
  5  end;
  6  /

Trigger created.

ops$tkyte@ORA920> create or replace trigger emp_au
  2  after update on emp
  3  begin
  4      dbms_output.put_line( 'AFTER update' );
  5  end;
  6  /

Trigger created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> merge into EMP
  2  using EMP2
  3  on ( emp.empno = emp2.empno )
  4  when matched then
  5      update set emp.ename = emp2.ename
  6  when not matched then
  7      insert ( empno, ename, job, mgr, hiredate, sal, comm, deptno )
  8      values ( emp2.empno, emp2.ename, emp2.job, emp2.mgr,
  9               emp2.hiredate, emp2.sal, emp2.comm, emp2.deptno )
 10  /
BEFORE INSERT
BEFORE update
BEFORE update for each row 7788
AFTER update for each row 7788
BEFORE update for each row 7839
AFTER update for each row 7839
BEFORE update for each row 7844
AFTER update for each row 7844
BEFORE update for each row 7876
AFTER update for each row 7876
BEFORE update for each row 7900
AFTER update for each row 7900
BEFORE update for each row 7902
AFTER update for each row 7902
BEFORE update for each row 7934
AFTER update for each row 7934
BEFORE INSERT for each row -7566
AFTER INSERT for each row -7566
BEFORE INSERT for each row -7521
AFTER INSERT for each row -7521
BEFORE INSERT for each row -7654
AFTER INSERT for each row -7654
BEFORE INSERT for each row -7369
AFTER INSERT for each row -7369
BEFORE INSERT for each row -7499
AFTER INSERT for each row -7499
BEFORE INSERT for each row -7782
AFTER INSERT for each row -7782
BEFORE INSERT for each row -7698
AFTER INSERT for each row -7698
AFTER update
AFTER INSERT

14 rows merged.

ops$tkyte@ORA920>

does both as needed row by row.
Rating: 5
Thanks for the critique January 06, 2003
Reviewer: Remi Visser from Netherlands
Hi Tom

(this refers to your critique on my answer)

Thanks very much Tom for you review on the question, I will remove my answer and
post a hyperlink to your answer.

Remi
Rating: 5
Flawed solutions January 06, 2003
Reviewer: Tony from London, England
Tom,

I didn't understand all your points in your critique of the Experts forum
solution:
b) doesn't work at all for deletes (well, since it fails I guess you could say
it protects the data integrity)
- Seems to work for me (multi-user issues aside)
c) doesn't work for mulit-row inserts
- Again, appears OK to me (this is a statement level trigger)

And Remi - you acknowledge that your site was inspired by Tom's, but isn't
writing "more THEN 8" in your solution rather than "more THAN 8" taking the
homage too far! :)
Followup:
whoops -- sorry about that -- I see I goofed on (B), I thought it was a row
trigger, now I see it is an after statement trigger.  Let me be more thorough
(cause it is still a bad implementation)

So, allow me to change my response (with proofs this time!!!):

assume:

ops$tkyte@ORA920> drop table emp;

Table dropped.

ops$tkyte@ORA920> create table emp as select * from scott.emp;

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select deptno, count(*) from emp group by deptno;

    DEPTNO   COUNT(*)
---------- ----------
        10          3
        20          5
        30          6

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace trigger xxx
  2  after delete or insert on emp
  3  declare
  4  begin
  5
  6   for irec in (select deptno, count(*) emps
  7             from emp
  8             group by deptno
  9             having count(*) <3
 10                 or count(*) >8)
 11   loop
 12       RAISE_APPLICATION_ERROR(-20000, 'Departement '||irec.deptno||' has
'||irec.emps||' employees!');
 13   end loop;
 14
 15  end;
 16  /

Trigger created.

was run before each one!

a) doesn't take into consideration "transfers" (updates)

ops$tkyte@ORA920> update emp set deptno = 30 where empno in ( 7369, 7566, 7788
);

3 rows updated.

ops$tkyte@ORA920> select deptno, count(*) from emp group by deptno;

    DEPTNO   COUNT(*)
---------- ----------
        10          3
        20          2   <<<=====
        30          9   <<<=====

b) doesn't work for deletes with more than one user

ops$tkyte@ORA920> delete from emp where empno in ( 7369, 7566 );
2 rows deleted.

ops$tkyte@ORA920> declare
  2          pragma autonomous_transaction;
  3  begin
  4          delete from emp where empno = 7788;
  5          commit;
  6  end;
  7  /
PL/SQL procedure successfully completed.

ops$tkyte@ORA920> commit;
Commit complete.

ops$tkyte@ORA920> select deptno, count(*) from emp group by deptno;

    DEPTNO   COUNT(*)
---------- ----------
        10          3
        20          2   <<<========
        30          6

c) doesn't work for inserts with more than one user:

ops$tkyte@ORA920> insert into emp select * from emp where deptno = 30 and rownum
<= 2;

2 rows created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          pragma autonomous_transaction;
  3  begin
  4          insert into emp select * from emp where deptno = 30 and rownum <=
2;
  5          commit;
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select deptno, count(*) from emp group by deptno;

    DEPTNO   COUNT(*)
---------- ----------
        10          3
        20          5
        30         10   <<<<=======

about the then/than -- ouch ;)

I have been trying to watch that one.  slows me down typing though, everytime I
type then -- i have to stop and think....
Rating: 5
Similar but different June 13, 2003
Reviewer: Charlie
Hi Tom,

SQL> CREATE TABLE box_type(ID NUMBER(5), NAME VARCHAR2(20), max_position
NUMBER(4), is_ordered VARCHAR2(1));

SQL> INSERT INTO box_type VALUES(1, 'big-box', 8, 'N');
SQL> INSERT INTO box_type VALUES(2, 'small-box', 4, 'Y');

SQL> SELECT * FROM box_type;

        ID NAME                 MAX_POSITION IS_ORDERED
---------- -------------------- ------------ ----------
         1 big-box                         8 N
         2 small-box                       4 Y

SQL> CREATE TABLE stuff(ID NUMBER(5) PRIMARY KEY, box_type_id NUMBER(5), box_id
NUMBER(5), position NUMBER(4));
SQL> ALTER TABLE stuff ADD(CONSTRAINTS stuff_uq UNIQUE(box_id, position));

SQL> INSERT INTO stuff VALUES(1, 1, 100, 1);
SQL> INSERT INTO stuff VALUES(2, 1, 100, 10);
SQL> INSERT INTO stuff VALUES(3, 2, 200, 1);
SQL> INSERT INTO stuff VALUES(4, 2, 200, 2);

SQL> SELECT * FROM stuff;

        ID BOX_TYPE_ID     BOX_ID   POSITION
---------- ----------- ---------- ----------
         1           1        100          1
         2           1        100         10
         3           2        200          1
         4           2        200          2

SQL> COMMIT;

I have a requirement which is:
whenever doing insert or update on table "stuff", I want to make sure
based on box_type, if it is ordered(is_ordered='Y'), the input position
must be between 1 and box_type.max_position and take the input position from
GUI.
If box_type is unordered(is_ordered='N'), the position will be generated by
database
automatically and the total number of positions must be less than or equal to
box_type.max_position.  For example,

If I insert another box-100, based on box_type, we know its max_position is 8
and unordered.
So database will assgin a new position(via max(position)+1 or sequence), say, 11

and check total count, something like(
select count(*) cnt from stuff
where box_id = 100
), the cnt must be less than or equal to 8.

If I insert another box-200, we know its max_position is 4 and ordered.
So it will take THE USER's INPUT position(3) FROM GUI and make sure THIS INPUT
POSITION
between 1 and 4.

I tried a trigger solution, but it failed with mutating error.

Could you please shed some light on it?

thanks,
Followup:
well, i wouldn't use a trigger -- when the logic behind the trigger gets so
complex that you are in the world of "magic" to get it working, well, it isn't a
good maintainable solution anymore.

a PROCEDURE to insert/update this table would be called for.

But, we've already have half of it right -- the count limit part, which requires
you to add a column to box_type which is a current count of the child rows)....

doing the other part, based on that (when ordered make sure position between 1
and max) should be straightforward (but ugly, strongly encourage a PROCEDURE).

here is the insert trigger, you can work on the update (which has to deal with
an update to BOX_TYPE_ID, so some :new and :old references will be needed)

ops$tkyte@ORA817DEV> CREATE TABLE box_type
  2  (ID NUMBER(5) primary key,
  3   NAME VARCHAR2(20),
  4   max_position NUMBER(4) NOT NULL,
  5   is_ordered VARCHAR2(1) NOT NULL,
  6   cnt number default 0 NOT NULL,
  7   constraint cnt_check check ( is_ordered != 'N' OR cnt between 0 and
max_position )
  8  );

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> INSERT INTO box_type VALUES(1, 'big-box', 8, 'N', 0);

1 row created.

ops$tkyte@ORA817DEV> INSERT INTO box_type VALUES(2, 'small-box', 4, 'Y', 0);

1 row created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> SELECT * FROM box_type;

        ID NAME                           MAX_POSITION I        CNT
---------- ------------------------------ ------------ - ----------
         1 big-box                                   8 N          0
         2 small-box                                 4 Y          0

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> drop TABLE stuff;
drop TABLE stuff
           *
ERROR at line 1:
ORA-00942: table or view does not exist

ops$tkyte@ORA817DEV> CREATE TABLE stuff
  2  (ID NUMBER(5) PRIMARY KEY,
  3   box_type_id references box_type,
  4   box_id NUMBER(5),
  5   position NUMBER(4));

Table created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> ALTER TABLE stuff ADD(CONSTRAINTS stuff_uq UNIQUE(box_id,
position));

Table altered.

ops$tkyte@ORA817DEV> drop sequence s;

Sequence dropped.

ops$tkyte@ORA817DEV> create sequence s;

Sequence created.

ops$tkyte@ORA817DEV> create or replace trigger stuff_trigger
  2  before insert on stuff for each row
  3  declare
  4          l_rec box_type%rowtype;
  5  begin
  6          select * into l_rec from box_type where id = :new.box_type_id;
  7
  8          if (l_rec.is_ordered = 'Y')
  9          then
 10                  if ( NOT ( :new.position between 1 and l_rec.max_position )
)
 11          then
 12                          raise_application_error( -20001, 'not between 1 ..
max' );
 13                  end if;
 14          else
 15                  update box_type set cnt = cnt+1 where id =
:new.box_type_id;
 16                  select s.nextval into :new.position from dual;
 17          end if;
 18  end;
 19  /

Trigger created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> INSERT INTO stuff VALUES(1, 1, 100, 1);

1 row created.

ops$tkyte@ORA817DEV> INSERT INTO stuff VALUES(2, 1, 100, 10);

1 row created.

ops$tkyte@ORA817DEV> INSERT INTO stuff VALUES(3, 1, 100, 10);

1 row created.

ops$tkyte@ORA817DEV> INSERT INTO stuff VALUES(4, 1, 100, 10);

1 row created.

ops$tkyte@ORA817DEV> INSERT INTO stuff VALUES(5, 1, 100, 10);

1 row created.

ops$tkyte@ORA817DEV> INSERT INTO stuff VALUES(6, 1, 100, 10);

1 row created.

ops$tkyte@ORA817DEV> INSERT INTO stuff VALUES(7, 1, 100, 10);

1 row created.

ops$tkyte@ORA817DEV> INSERT INTO stuff VALUES(8, 1, 100, 10);

1 row created.

ops$tkyte@ORA817DEV> INSERT INTO stuff VALUES(9, 1, 100, 10);
INSERT INTO stuff VALUES(9, 1, 100, 10)
            *
ERROR at line 1:
ORA-02290: check constraint (OPS$TKYTE.CNT_CHECK) violated
ORA-06512: at "OPS$TKYTE.STUFF_TRIGGER", line 13
ORA-04088: error during execution of trigger 'OPS$TKYTE.STUFF_TRIGGER'

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> INSERT INTO stuff VALUES(100, 2, 200, 1);

1 row created.

ops$tkyte@ORA817DEV> INSERT INTO stuff VALUES(101, 2, 200, 2);

1 row created.

ops$tkyte@ORA817DEV> INSERT INTO stuff VALUES(102, 2, 200, 3);

1 row created.

ops$tkyte@ORA817DEV> INSERT INTO stuff VALUES(103, 2, 200, 4);

1 row created.

ops$tkyte@ORA817DEV> INSERT INTO stuff VALUES(104, 2, 200, 5);
INSERT INTO stuff VALUES(104, 2, 200, 5)
            *
ERROR at line 1:
ORA-20001: not between 1 .. max
ORA-06512: at "OPS$TKYTE.STUFF_TRIGGER", line 10
ORA-04088: error during execution of trigger 'OPS$TKYTE.STUFF_TRIGGER'

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select * from box_type;

        ID NAME                           MAX_POSITION I        CNT
---------- ------------------------------ ------------ - ----------
         1 big-box                                   8 N          8
         2 small-box                                 4 Y          0

ops$tkyte@ORA817DEV>
Rating: 4
almost there... June 14, 2003
Reviewer: Charlie
Thanks Tom!!

There is one thing can defeat this solution.

ops$tkyte@ORA817DEV> INSERT INTO stuff VALUES(8, 1, 100, 10);

1 row created.

Right after this statement, if I inserted another DIFFERENT box, say Box-500,
which also belongs to box_type(1), unordered and max_position is 8

INSERT INTO stuff VALUES(9, 1, 500, 50);

It will barf because the current "cnt" in box_type has been updated to 8

        ID NAME                 MAX_POSITION I        CNT
---------- -------------------- ------------ - ----------
         1 big-box                         8 N          8  <==
         2 small-box                       4 Y          0

However, since they are totally different boxes, I do want to insert this
box-500 into table "stuff".  How can I do that?

Thanks!
Followup:
i didn't follow that at all.  I thought you wanted 8 "big-boxes" and no more.

I don't understand your "different box"

but if you are saying -- there can be many collections of 8 boxes, you need yet
another table to maintain the count by "collection"

You have the information you need -- the technique is all there.  If you
understand it, you can apply it to your problem (and if you don't understand it,
you really need to before you use it so.... that leads us back to "you have the
technique, you can apply to to a broad range of problems...)
Rating: 5
stuck December 29, 2003
Reviewer: Mark from Buffalo
I am trying to enforce the business rule that if an employee has one or more
addresses, then one and only one address must be "preferred", that is, preferred
= 'Y'. All other addresses for the employee must have preferred = 'N'. An
address does not have to exist for an employee.

I have created the following for testing.

CREATE TABLE mark_EMP1 (
  EMPNO     NUMBER(4) PRIMARY KEY,
  ENAME     VARCHAR2(10),
  JOB       VARCHAR2(9),
  MGR       NUMBER(4),
  HIREDATE  DATE,
  SAL       NUMBER(7,2),
  COMM      NUMBER(7,2),
  DEPTNO    NUMBER(2));

CREATE TABLE mark_ADDR1 (
  TRANS        NUMBER(4) PRIMARY KEY,
  EMPNO     NUMBER(4),
  ADDR_LINE1  VARCHAR2(40),
  ADDR_LINE2  VARCHAR2(40),
  CITY        VARCHAR2(40),
  STATE       VARCHAR2(2),
  ZIP         VARCHAR2(40),
  PREFERRED   VARCHAR2(1));

ALTER TABLE mark_ADDR1 ADD CONSTRAINT mark_ADDR1_FK1 FOREIGN KEY (EMPNO)
REFERENCES mark_EMP1(EMPNO);
ALTER TABLE mark_ADDR1 ADD constraint chk_mark_addr1_preferred check (preferred
in ('N','Y'));

INSERT INTO MARK_EMP1 ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES (
7521, 'WARD', 'SALESMAN', 7698,  TO_Date( '02/22/1981 12:00:00 AM', 'MM/DD/YYYY
HH:MI:SS AM')
, 1250, 500, 30);

commit;

CREATE SEQUENCE mark_ADDR1_TRANSNUM INCREMENT BY 1 START WITH 1 MINVALUE 1
NOCYCLE NOCACHE NOORDER;

create table mark_addr_pref_cnt
    (  empno primary key,
       cnt    ,
       constraint mark_addr_pref_cnt_chk_1
       check ( cnt = 1 ) deferrable initially deferred
    )
    organization index
    as
    select empno, count(*)
     from mark_ADDR1
     WHERE preferred = 'Y'
    group by empno;

 create or replace trigger mark_ADDR1_pref_trigger
   after insert or update of PREFERRED or delete on mark_ADDR1
   for each row
   begin
     IF INSERTING
     OR (UPDATING AND :NEW.preferred = 'Y' AND :OLD.preferred = 'N')
     THEN
      UPDATE mark_addr_pref_cnt
         SET cnt = cnt + DECODE(:NEW.preferred, 'Y', 1, 0)
       WHERE empno = :NEW.empno;

      IF (SQL%ROWCOUNT = 0)
      THEN
         INSERT INTO mark_addr_pref_cnt
                     (empno, cnt)
              VALUES (:NEW.empno, DECODE(:NEW.preferred, 'Y', 1, 0));
      END IF;
     END IF;

     IF    (DELETING AND :OLD.preferred = 'Y')
      OR (UPDATING AND :OLD.preferred = 'Y' AND :NEW.preferred = 'N')
     THEN
      UPDATE mark_addr_pref_cnt
         SET cnt = cnt - 1
       WHERE empno = :OLD.empno;

     END IF;
   end;
/

I am having two problems.
1) Once an address is added to the employee, how can I delete all addresses from
the employee without violating the constraint on mark_addr_pref_cnt? In other
words, how can I delete the associated row in mark_addr_pref_cnt when all
addresses for an employee are deleted?

2) I am getting a constraint error on mark_addr_pref_cnt at unexpected times.:

SQL> insert into mark_ADDR1 (TRANS,EMPNO,ADDR_LINE1,CITY,STATE,ZIP,PREFERRED)
  2    values (mark_ADDR1_TRANSNUM.nextval,7521,'319 Lamarck
Dr.','Snyder','NY','14226','N');

1 row created.

SQL> insert into mark_ADDR1 (TRANS,EMPNO,ADDR_LINE1,CITY,STATE,ZIP,PREFERRED)
  2    values (mark_ADDR1_TRANSNUM.nextval,7521,'1410 Main
St.','Buffalo','NY','14209','Y');

1 row created.

SQL> select empno, trans, preferred
  2  from mark_addr1
  3  order by empno, preferred desc;

     EMPNO      TRANS P
---------- ---------- -
      7521          6 Y
      7521          5 N

SQL> select *
  2  from mark_addr_pref_cnt
  3  order by empno;

     EMPNO        CNT
---------- ----------
      7521          1

SQL> commit;
commit
*
ERROR at line 1:
ORA-02091: transaction rolled back
ORA-02290: check constraint (VOLUME.MARK_ADDR_PREF_CNT_CHK_1) violated

Thanks for your help.
Followup:
given the question:

I am trying to enforce the business rule that if an employee has one or more
addresses, then one and only one address must be "preferred", that is, preferred

= 'Y'. All other addresses for the employee must have preferred = 'N'.

My answer would be:

ops$tkyte@ORA9IR2> create UNIQUE index emp_addr_index on
  2  mark_addr1( decode( preferred, 'Y', empno ) );

Index created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into mark_addr1 values ( 1,   111, 'x', 'y', 'x', 'y',
'z', 'Y' );

1 row created.

ops$tkyte@ORA9IR2> insert into mark_addr1 values ( 2,   111, 'x', 'y', 'x', 'y',
'z', 'N' );

1 row created.

ops$tkyte@ORA9IR2> insert into mark_addr1 values ( 3,   111, 'x', 'y', 'x', 'y',
'z', 'N' );

1 row created.

ops$tkyte@ORA9IR2> insert into mark_addr1 values ( 4,   111, 'x', 'y', 'x', 'y',
'z', 'Y' );
insert into mark_addr1 values ( 4,   111, 'x', 'y', 'x', 'y', 'z', 'Y' )
*
ERROR at line 1:
ORA-00001: unique constraint (OPS$TKYTE.EMP_ADDR_INDEX) violated
Rating: 3
at least vs. at most December 30, 2003
Reviewer: j.
that would just enforce the uniqueness of 'preferred' state per "group".

but is there any non-trigger-solution to force AT LEAST one entry per "group" to
be marked as 'preferred'? or does this requirement have to be met by maintaining
an additional "preferred count" column the same way as in your
count-child-entry-pattern above?
Rating: 2
December 30, 2003
Reviewer: Mark from Buffalo
J. is right - in that there needs to be at least one preferred address,
preferred = 'Y'. That is why I went with the "cnt" table and the trigger. I
figured the deferrable initially deferred constraint was necessary because a
non-preferred address could be inserted first. Or, the preferred could be
deleted before a new preferred was inserted or updated. So, I am back to my
original problems.

Thanks again for your help
Followup:
well, we have it 80% solved then.  you just want to make sure that at least one
is Y if there exists at least one row.  we can do that by using a "mutating
table" workaround PLUS the unique index (we still need both).

Here is the approach:

ops$tkyte@ORA9IR2> CREATE TABLE mark_ADDR1 (
  2    TRANS        NUMBER(4) PRIMARY KEY,
  3    EMPNO     NUMBER(4),
  4    ADDR_LINE1  VARCHAR2(40),
  5    ADDR_LINE2  VARCHAR2(40),
  6    CITY        VARCHAR2(40),
  7    STATE       VARCHAR2(2),
  8    ZIP         VARCHAR2(40),
  9    PREFERRED   VARCHAR2(1));

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace package state_pkg
  2  as
  3      type array is table of number index by binary_integer;
  4
  5      g_data array;
  6      g_empty array;
  7
  8      procedure doit;
  9  end;
 10  /

Package created.

we'll maintain the list of modified empnos in these tables...

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace package body state_pkg
  2  as
  3
  4  procedure doit
  5  is
  6      l_idx  number;
  7      l_cnt1 number;
  8      l_cnt2 number;
  9  begin
 10      l_idx := g_data.first;
 11      while (l_idx is not null)
 12      loop
 13          select count(*), count(decode(preferred,'Y','Y'))
 14            into l_cnt1, l_cnt2
 15            from mark_addr1
 16           where empno = l_idx;
 17
 18          if ( l_cnt1 <> 0 and l_cnt2 <> 1 )
 19          then
 20              raise_application_error( -20001, 'At least one addr must be
preferred' );
 21          end if;
 22          l_idx := g_data.next(l_idx);
 23      end loop;
 24  end;
 25
 26  end;
 27  /

Package body created.

do it will simply check that either

a) there are ZERO rows for this employee
b) if there are not ZERO rows -- at least ONE of them will be 'Y'

It is not used for uniqueness -- it won't be able to do that as we'll see. thats
why the error message unambigously says "at least one" and not doing any sort of
duplication check

ops$tkyte@ORA9IR2> create or replace trigger t1
  2  before insert or update or delete on mark_addr1
  3  begin
  4      state_pkg.g_data := state_pkg.g_empty;
  5  end;
  6  /

Trigger created.

we need to reset before each update/insert/delete...

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace trigger t2
  2  after insert or update or delete on mark_addr1 for each row
  3  begin
  4      if (inserting or updating)
  5      then
  6          state_pkg.g_data( :new.empno ) := 1;
  7      end if;
  8      if (deleting or updating)
  9      then
 10          state_pkg.g_data( :old.empno ) := 1;
 11      end if;
 12  end;
 13  /

Trigger created.

simply remember the empnos...

ops$tkyte@ORA9IR2> create or replace trigger t3
  2  after insert or update or delete on mark_addr1
  3  begin
  4      state_pkg.doit;
  5  end;
  6  /

Trigger created.

and then do the check..

ops$tkyte@ORA9IR2> insert into mark_addr1 values ( 2,   111, 'x', 'y', 'x', 'y',
'z', 'N' );
insert into mark_addr1 values ( 2,   111, 'x', 'y', 'x', 'y', 'z', 'N' )
            *
ERROR at line 1:
ORA-20001: At least one addr must be preferred
ORA-06512: at "OPS$TKYTE.STATE_PKG", line 20
ORA-06512: at "OPS$TKYTE.T3", line 2
ORA-04088: error during execution of trigger 'OPS$TKYTE.T3'

so far, so good -- it detected we didn't have a preferred one, but:

ops$tkyte@ORA9IR2> insert into mark_addr1 values ( 1,   111, 'x', 'y', 'x', 'y',
'z', 'Y' );
1 row created.

ops$tkyte@ORA9IR2> insert into mark_addr1 values ( 2,   111, 'x', 'y', 'x', 'y',
'z', 'N' );
1 row created.

ops$tkyte@ORA9IR2> insert into mark_addr1 values ( 3,   111, 'x', 'y', 'x', 'y',
'z', 'N' );
1 row created.

ops$tkyte@ORA9IR2> insert into mark_addr1 values ( 4,   111, 'x', 'y', 'x', 'y',
'z', 'Y' );
insert into mark_addr1 values ( 4,   111, 'x', 'y', 'x', 'y', 'z', 'Y' )
            *
ERROR at line 1:
ORA-20001: At least one addr must be preferred
ORA-06512: at "OPS$TKYTE.STATE_PKG", line 20
ORA-06512: at "OPS$TKYTE.T3", line 2
ORA-04088: error during execution of trigger 'OPS$TKYTE.T3'

and it would "appear" that it does the duplication check -- but it doesn't
really:

ops$tkyte@ORA9IR2> rollback;

Rollback complete.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into mark_addr1 values ( 1,   111, 'x', 'y', 'x', 'y',
'z', 'Y' );

1 row created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2      pragma autonomous_transaction;
  3  begin
  4      insert into mark_addr1 values ( 4,   111, 'x', 'y', 'x', 'y', 'z', 'Y'
);
  5      commit;
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> commit;

Commit complete.

since the two sessions cannot see eachothers additions.... So -- we create the
index:

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create UNIQUE index emp_addr_index on
  2  mark_addr1( decode( preferred, 'Y', empno ) );
mark_addr1( decode( preferred, 'Y', empno ) )
                                    *
ERROR at line 2:
ORA-01452: cannot CREATE UNIQUE INDEX; duplicate keys found

just shows the data is "bad"

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> delete from mark_addr1;

2 rows deleted.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create UNIQUE index emp_addr_index on
  2  mark_addr1( decode( preferred, 'Y', empno ) );

Index created.

we create the index and retry:

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into mark_addr1 values ( 1,   111, 'x', 'y', 'x', 'y',
'z', 'Y' );

1 row created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2      pragma autonomous_transaction;
  3  begin
  4      insert into mark_addr1 values ( 4,   111, 'x', 'y', 'x', 'y', 'z', 'Y'
);
  5      commit;
  6  end;
  7  /
declare
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
ORA-06512: at line 4

if we were to use two sessions -- we would have seen these sessions serialize
instead of deadlock -- but you get the point.

ops$tkyte@ORA9IR2> commit;

Commit complete.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> delete from mark_addr1;

1 row deleted.

and we can delete cause we check for ZERO rows (thats OK) or count(Y) != 1..

ops$tkyte@ORA9IR2> commit;

Commit complete.

ops$tkyte@ORA9IR2>
Rating: 4
but how to "switch" preferred state between existing entries? December 30, 2003
Reviewer: j.
(without deleting all "group" entries and reinserting them with modified states)

checks are not performed at transaction level but statement level ...
Followup:
ops$tkyte@ORA9IR2> select trans, preferred from mark_addr1;

     TRANS P
---------- -
         1 Y
         2 N
         3 N

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> update mark_addr1
  2     set preferred = decode( trans, 1, 'N', 3, 'Y' )
  3   where trans in ( 1, 3 );

2 rows updated.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select trans, preferred from mark_addr1;

     TRANS P
---------- -
         1 N
         2 N
         3 Y

? it only takes a single update statment to modify as many rows as you want.  a
simple toggle is all you need.  More simply, it might be:

ops$tkyte@ORA9IR2> update mark_addr1
  2  set preferred = decode( trans, 1, 'Y', 'N' )
  3  where trans = 1
  4    or ( empno = 111 and preferred = 'Y' );

2 rows updated.

ops$tkyte@ORA9IR2>  select trans, preferred from mark_addr1;

     TRANS P
---------- -
         1 Y
         2 N
         3 N
In that -- you need to know the trans you want to be "preferred" and the empno
of the person it belongs to.  that way you don't need to know the "old" and the
"new", just the "new".
Rating: 5
my fault December 30, 2003
Reviewer: j.
that just reminds me of the lesson: don't do in "single row procedural mode"
what can be achieved with straight descriptive SQL ...
Rating: 5
thanks December 31, 2003
Reviewer: Mark
Excellent!! It works perfectly.

Thanks for your help Tom; I really appreciate it.

Thanks for your input J.
December 30, 2003 -- Thanks for the question regarding "Limit in the number of indexes", version 8.1.7.3
You Asked
Hi Tom,

Is it a real limit in the number of indexes that you can have in a table?
I was told that ORacle recomends not to have more than 5 indexes, specially when
the tables are huge (more than 1 gigabyte), because of performance issues at the
moment of the insert/updates/deletes.

The problem that I have right now is that I have a huge table
with 9 indexes and 10 constraints (between foreign keys and references)but the
constraints are not indexes and I would like to create indexes for those
constraints in order to improve performance.

I already check with the developers and all the indexes that we already have are
needed so we cannot get rid of them.
Taking into account this and this limit of 5 indexes that oracle
recommends,Should I create 10 more indexes for those constraints?
What do you suggest me?

Thanks for your help.
and we said...
The *size* of the table has absolutely no relevance on this.

I've never heard of a "limit", suggested or otherwise, as to the maximum number
of indexes per table.

It is true that each index will cause an INSERT/DELETE and maybe an UPDATE (if
you update the indexed columns) to run slower.

However -- consider:

   update t set x = 55 where y = 10;
   delete from t where y = 9;

Now, suppose T is a very very very large table.  Suppose Y is a selective
column.  Suppose Y is not indexed.  Now -- you tell me -- how will the
performance of those two statements be?  (poor).  Now, add an index on Y -- hows
the performance now? (probably tons better).

Now, also assume that is the 6th index -- should we drop one to get to 5?  No,
not if they are used in similar fashions (to make queries perform, to make other
DML faster).

This is exactly why I despise ROT (rules of thumb).  They are generalizations
that never seem to apply in the real world.

You should have as many indexes as you need for your system to perform in all
ways.  Not one more then that, not one less.  Consider life WITHOUT those
indexes -- what happens then?  Performance would really be down the tubes.

The right number of indexes per table is somewhere between 0 and infinity.
It'll vary based on need.  You never want to have more indexes then you really
need -- but you want to have every index you do.
Review & Followup

Rating: 3
But... September 22, 2002
Reviewer: A reader from Bangalore
Tom,
  My question is whether in any situation would indexes slow down the query or
will it cause the optimizer to take more time in arriving at the best path?
  Would having Indexes on columns when it is NOT very selective have negative
effects on Querying? Like say an index on a date field. There could be any
number of records having the same date. This question is because we often query
all records within a date range.

Other situation is having multiple indexes on one field with different
combinations. For eg:- i have an index on id,exp_dt combination and another on
id, some_other_col1, some_other_col2. Would this impact the queries which use
only id in the where clause?
This is with respect to OLAP application.

Thanks very much Tom...Really appreciate your efforts.
Followup:
Sure, absolutely, but that can happen with 1, 2, 3, 4, 5, ... 10000 indexes.
The *number* of indexes is not relevant here.

If you use the CBO and analyze your objects, this becomes less of a concern as
the optimizer will understand if the data is selective or not.

In your case, you said you already did the analysis, all indexes are needed --
hence they must be selective or you wouldn't need them.
Rating: 3
All Columns are indexed September 23, 2002
Reviewer: Tony from India
Hi Tom,

1.Is it good to index all columns in a table if required?
  Is there any alternative?
2. I heared that date columns should not be indexed. Is it true? Why?
Followup:
1) if it is required, although I'm pretty hard pressed to come up with an
example

2) myth, totally false.
Rating: 4
I have heard of the 5-index "limit" before September 23, 2002
Reviewer: Tony Andrews from UK
It used to be the case (I was told) that once the RBO had considered 5 indexes
on a table for use in a query, it picked the best of those 5 and did not
consider any others.  We had our database design checked by an Oracle consultant
"guru" some years ago, and he advised dropping all but 5 indexes on our main
table to "improve" performance, citing the reason above.  Since he was supposed
to be the expert, our manager insisted we follow his advice, despite us knowing
it to be nonsense (i.e. despite seeing an immediate degradation in performance).
 Once the consultant had left, I quietly reinstated the necessary indexes and no
more was ever heard about it...
Followup:

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76992/optimops.htm#39156
it is talking about being able to MERGE upto 5 single column indexes together.

for example:

ops$tkyte@ORA920.US.ORACLE.COM> create table t ( c1 int, c2 int, c3 int, c4 int,
c5 int, c6 int );

Table created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> begin
  2     for i in 1 .. 6
  3     loop
  4          execute immediate 'create index c'||i||'_idx on t(c'||i||')';
  5     end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA920.US.ORACLE.COM> select *
  2    from t
  3     where c1 = 55 and c2 = 32 and c3 = 21 and c4 = 34 and c5 = 121 and c6 =
6543;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T'
   2    1     AND-EQUAL
   3    2       INDEX (RANGE SCAN) OF 'C1_IDX' (NON-UNIQUE)
   4    2       INDEX (RANGE SCAN) OF 'C2_IDX' (NON-UNIQUE)
   5    2       INDEX (RANGE SCAN) OF 'C3_IDX' (NON-UNIQUE)
   6    2       INDEX (RANGE SCAN) OF 'C4_IDX' (NON-UNIQUE)
   7    2       INDEX (RANGE SCAN) OF 'C5_IDX' (NON-UNIQUE)

We'll merge upto 5 indexes together to get the answer.

Now, think about it -- should I drop c6_idx?

If you say yes, I say "no, no way".  Why?  because the most popular query in our
system is

select * from t where c6 = 42313;

If you say no, I say "yes, sure, go ahead".  Why?  because our system never
queries on c6 alone, in fact -- it is rare to use c6 in a predicate at all.

If you want to "tune" the above query, should you add a 7th index to the system?
 Could be.

ops$tkyte@ORA920.US.ORACLE.COM> create index c7_idx on t(c1,c2,c3,c4,c5,c6);

Index created.

ops$tkyte@ORA920.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA920.US.ORACLE.COM> select * from t where
  2   c1 = 55 and c2 = 32 and c3 = 21 and c4 = 34 and c5 = 121 and c6 = 6543;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   INDEX (RANGE SCAN) OF 'C7_IDX' (NON-UNIQUE)

would definitely be faster then 5 index scans plus a merge.

Here you are all seeing the genesis of a myth.  Virtually all Oracle myths
(you should have as few extents as possible, one being best for example) have a
nugget of truth hiding in them somewhere.

Here the number 5, being the number we can merge.  It has NOTHING WHATSOEVER to
do with "how many indexes should a table have" -- nothing.  But it has been
perverted to mean that over time.

The right number = the number that is right for you.  Somewhere between zero and
inifinity will do
Rating: 5
I despise these rules of thumb, too September 23, 2002
Reviewer: Keith from Boston, MA
One other point I like to make when people tell me that a table has too many
indexes.

Would you rather a table have too many or too few indexes?  I always error on
the side of too many.  Why, because a rogue query which is scanning a large
table for a small amount of rows will always impact the performance of the
system.  If the number of concurrent "bad" queries equals the number of CPU's on
your db server, everything gets bogged down, not just the resopnse time of the
query.
Followup:
There is no such thing as "too many" or "too few"

There is only one answer "just right".

Just right might be 100
Just right might be 1

It totally depends on your needs.  You do not want to have indexes for which
there is no requirements.  You need to have all indexes for which there is a
requirement.
Rating: 5
October 23, 2002
Reviewer: A reader
Hi Tom,
Will an insert take more time if I have a composite index of two columns rather
than one column?

Thanks
Followup:
It'll be a function of the size of the indexed columns -- the wider the key --
the longer it takes.

It is marginal.
Rating: 5
October 24, 2002
Reviewer: A reader
Thanks for answering me Tom!

Let me see if I understand correctly, when you say "it is marginal" does it mean
that difference between the time it will take to insert a row in a table with a
compositive index of two columns and the time to insert in the same table but
with an index of one column is so low that we can consider it insignificant?

Thanks
Followup:
Depends on the size.

char(5)

vs

char(5) || char(255)

might not be as "marginal"  but

char(5)

vs
char(5) || char(5)

marginal.
Rating: 5
Interesting! October 24, 2002
Reviewer: Jim
Very informative discussion Tom..
I think people for some reason people
believe indexes slow DML when in fact
indexes MAY slow down DML, but they MAY
also speed certain operations up
Rating: 4
On equestion regarding design December 29, 2003
Reviewer: Suhail from NY-USA
Tom,

We have a table which have several millions of rows almost 10 millions . It has
30 columns and out of 30 columns there are 25 indexes. This table consist of
several sub tables ie just think of Sub type , we have 8 sub type on this table
and if we enter data of one sub type then other 7 sub types data will not be
entered , meaning those will be NULL. Now when we generate this table from
Designer, it creates all the foreign keys links and indexes on all the sub type
columns.  So we have almost 25 indexs. This is very much similar to FACT table
in DW environment whereas we are building an OLTP application. Let me know is
this really a good design or should we break one table into multiple smaller
tables.

When I argued with my project manager( He is DBA too) that  if we break this
table into multiple tables instead of one table nthen we woul dbetter model. He
did not agree, he thinks that for reporting purpose this is best design, he
thinks that reports will be slow due to join so he wants to have one big flat
table. I donot agree, whats your opinion on this matter? Could you provide some
compelling arguments if you think my argument would give a better result?

Thanks

Suhail
Followup:
insufficient data to answer.

if reporting is "key, crucial, critical, the thing to design for" -- your
coworker may have a point.

It seems "ok" to me -- i've done this before -- but not knowing all of the
details, it is really hard to "say".  the 25 indexes don't scare me since we
only maintain the index when the key to be placed in there is NOT NULL so --
really, only the affected indexes for an insert/update will be bothered (not all
25).

so, you may both be right -- for different reasons, depends on the totality of
the circumstances.
Rating: 3
Indexs and sub types December 29, 2003
Reviewer: Suhail
Tom I agree that only the effecetd inderxs would be bothered in case of
index/update. But dont you think we are wasting lot of space here. If I enter
data for one type , other types data are empty in that row so in a row we only
enter 5 columns at a tiem and all other columns are going null. Whereas if
tables are divided into seperate independednt tables then we will not mix apple
with oranges.
I donot think its a good design at all specially in the OLTP environment.  We
have several complicated online screens along with reports so reports is not
only the key factor, we got to have good online screens.

Thanks for your any comments.
Followup:
you will "waste" 25 bytes per row with 25 nulls -- not really too much
considering.

My question to you is

are you really an OLTP environment with some casual reporting or are you a
system where there is some data entry to support massive reporting and reporting
is the key element.

The schema won't affect screen design at all, that I don't see the tie in
between.

Tell me why you think this will be "bad" -- I'm not saying its good, but I don't
see any "really bad things" here yet?
Rating: 3
its OLTP environment December 30, 2003
Reviewer: A reader
Well its OLTP environment, however we are suppose to load lot of data from
batch, online screens are there to do the same job but its one at a time not in
bulk. Well here is the design of the table

(it_id        number, PK -- sequence
 att_id       number, --FK
 bmp          number not null,
 emp          number not null,
 length       number not null,
 description  varchar2(100),
 att_value    varchar2(100),
 cty_code     varchar2(3)--FK
 Cty_date     date    , --FK
 muni_id      number, --FK
 dscrptn      varchar2(100),
 eff_date     date    ,
 reg_id       number  , --FK
 org_id       number  , --FK
 dvd_id       number, --FK
 tip_id       number, --FK
 dig_id       number, --FK
 lid_id       number, --FK
 tiv_id       number, --FK
 bltm         number,
 eltm         number,
 ....
  and so one , there are some more FKs and more columns.

For every PK it_id , at least all not null columns and att_id must be stored and
one of other Fks. All other FKs are null and plus other columns may be null.

Does it not make more sence to have smaller table similar to one as follows:

(it_id number , --PK

att_id       number not null,  FK
 bmp          number not null,
 emp          number not null,
 length       number not null,
 description  varchar2(100),
reg_id       number   --FK
)
similary more tables like this for every FKs ie we will have 23 smaller tables
in my case. It will be more normalised and better for OLTP environment. I had
looked the reporting needs and none of our reports can be directly developed
based on the above big table structure.  I have to create several temp tables to
pre process the data in order to run the reports.

We will be adding at least 5- millions rows on an yearly basis in this single
table. Initial load is 10 Millions if we load into one tables , if we create
several smaller tables then each table will have several thousands rows ( which
is not so bad) .

Just for a report format here is one example

BMP EMP LENGTH Description     Muni_id org_id  dig_id...
1    5   4     Atlanta          123     34      190

Just imagine how complicated it would be to create a report like above , meaning
I have to write a pl/sql Package to pre process the data into a temp table and
then run the report from the temp table. Whereas , if we store the data into
multiple smaller tables then we can just use view or simple join to create a
report like above.

Thanks
Followup:
what are those things fk's too?  i don't see the other part out there?
Rating: 3
FKs December 30, 2003
Reviewer: A reader
FKs are foreign keys from different lookup tables.
Followup:
right, i don't understand the model, i don't see the other tables -- tell you
what, this isn't working very well here, getting way off the original topic and
all.

I have no problems with one table in general.

I have problems if the design

a) cannot meet the required performance of the critical features (a simple
prototype would show that)

b) cannot supply the most frequently requested, important data efficiently (see
a, a simple prototype)

so, suggestion -- prototype both models and see what you see!
December 30, 2003 -- Thanks for the question regarding "DIFFERENCES BETWEEN PROCESSES, SESSIONS AND CONNECTIONS", version 8.1.7.4
You Asked
Hi Tom

What's the difference between connections, sessions and processes?

I read a note from Metalink about the difference but I simply dont get it!

May you give a brief explanation?

Thank you
and we said...
A process is a physical process or thread.

On unix, you can see a process with "ps" for example.  It is there.

There are many types of processes in Oracle -- background processes like SMON,
PMON, RECO, ARCH, CKPT, EMNn, DBWR, etc.....  And user processes like dedicated
servers or shared server (multi-threaded server -- aka MTS -- configuration)

A connection is a "physical circuit", a pathway to a database.  You can be
connected to a database yet have 0 or 1 or MORE sessions going on that
connection.  We can see that with sqlplus, consider (single user system here,
its all about me)

[tkyte@tkyte-pc-isdn tkyte]$ ps -auxww | grep oracleora920
[tkyte@tkyte-pc-isdn tkyte]$ sqlplus /nolog

SQL*Plus: Release 9.2.0.1.0 - Production on Sat Sep 28 10:36:03 2002

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

idle> !ps -auxww | grep oracleora920
tkyte    19971  0.0  0.1  2196  916 pts/1    S    10:36   0:00 /bin/bash -c ps
-auxww | grep oracleora920
tkyte    19973  0.0  0.1  1736  564 pts/1    S    10:36   0:00 grep oracleora920

no process, no nothing

idle> connect /
Connected.
idle> !ps -auxww | grep oracleora920
ora920   19974  1.5  2.2 230976 11752 ?      S    10:36   0:00 oracleora920
(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))
tkyte    19975  0.0  0.1  2196  916 pts/1    S    10:36   0:00 /bin/bash -c ps
-auxww | grep oracleora920
tkyte    19977  0.0  0.1  1736  564 pts/1    S    10:36   0:00 grep oracleora920

got my process now...

idle> disconnect
Disconnected from Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
idle> !ps -auxww | grep oracleora920
ora920   19974  0.6  2.3 230976 11876 ?      S    10:36   0:00 oracleora920
(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))
tkyte    19978  0.0  0.1  2196  916 pts/1    S    10:36   0:00 /bin/bash -c ps
-auxww | grep oracleora920
tkyte    19980  0.0  0.1  1736  564 pts/1    S    10:36   0:00 grep oracleora920

idle> select * from dual;
SP2-0640: Not connected

still have my process, but no session, the message is a little "misleading".
Technically -- I have a connection, I don't have a session

further, autotrace in sqlplus can be used to show that you can have
a) a connection
b) that uses a single process
c) to service two sessions:

ops$tkyte@ORA920.US.ORACLE.COM> select username from v$session where username is
not null;

USERNAME
------------------------------
OPS$TKYTE

one session, ME

ops$tkyte@ORA920.US.ORACLE.COM> select username, program from v$process;

USERNAME        PROGRAM
--------------- ------------------------------------------------
                PSEUDO
ora920          oracle@tkyte-pc-isdn.us.oracle.com (PMON)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (DBW0)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (LGWR)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (CKPT)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (SMON)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (RECO)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (CJQ0)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (QMN0)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (S000)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (D000)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (ARC0)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (ARC1)
tkyte           oracle@tkyte-pc-isdn.us.oracle.com (TNS V1-V3)

14 rows selected.

you can see all of the backgrounds and my dedicated server...

ops$tkyte@ORA920.US.ORACLE.COM> set autotrace on statistics;

Autotrace for statistics uses ANOTHER session so it can query up the stats for
your CURRENT session without impacting the STATS for that session!

ops$tkyte@ORA920.US.ORACLE.COM> select username from v$session where username is
not null;

USERNAME
------------------------------
OPS$TKYTE
OPS$TKYTE

see, two sessions but....

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

ops$tkyte@ORA920.US.ORACLE.COM> select username, program from v$process;

USERNAME        PROGRAM
--------------- ------------------------------------------------
                PSEUDO
ora920          oracle@tkyte-pc-isdn.us.oracle.com (PMON)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (DBW0)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (LGWR)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (CKPT)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (SMON)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (RECO)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (CJQ0)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (QMN0)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (S000)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (D000)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (ARC0)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (ARC1)
tkyte           oracle@tkyte-pc-isdn.us.oracle.com (TNS V1-V3)

14 rows selected.

same 14 processes...

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

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

I'll try to put it into a single, simple paragraph:

A connection is a physical circuit between you and the database.  A connection
might be one of many types -- most popular begin DEDICATED server and SHARED
server.  Zero, one or more sessions may be established over a given connection
to the database as show above with sqlplus.  A process will be used by a session
to execute statements.  Sometimes there is a one to one relationship between
CONNECTION->SESSION->PROCESS (eg: a normal dedicated server connection).
Sometimes there is a one to many from connection to sessions (eg: like
autotrace, one connection, two sessions, one process).  A process does not have
to be dedicated to a specific connection or session however, for example when
using shared server (MTS), your SESSION will grab a process from a pool of
processes in order to execute a statement.  When the call is over, that process
is released back to the pool of processes.
Review & Followup

Rating: 5
Clear, concise. September 28, 2002
Reviewer: JW from Colorado front range
Tom, you rock!  You are clear, concise, and complete.
Rating: 3
Reader September 28, 2002
Reviewer: A reader
How come there is still a bequeathe process even after you
idle> disconnect
ora920   19974  0.6  2.3 230976 11876 ?      S    10:36   0:00 oracleora920
(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))
Followup:
I'm still "connected" physically.

I just gave up my session.  (that was sort of the point here.  A connection may
or may not have a process, a process may or may not be associated with a
session, etc)
Rating: 4
Thanks September 28, 2002
Reviewer: A reader
Rating: 4
Very Nice explanation! Can you explain why the username is different in the V$PROCESS September 28, 2002
Reviewer: Prince
Oracle 9.2.0.1 on Win NT

SQL> select process, sid, paddr,username from v$session where username is not
null ;

PROCESS             SID PADDR    USERNAME
------------ ---------- -------- ------------------------------
1229:785              8 140FFF7C PRINCE

SQL> select username, spid, pid, program from v$process  where addr =
'140FFF7C';

USERNAME        SPID                PID PROGRAM
--------------- ------------ ----------
---------------------------------------------
SYSTEM          290                  11 ORACLE.EXE
Followup:
It is the OS username of the OS userid associated with the process itself.

If I connected dedicated server over sqlnet, it would show "ora920" as the
others do.  My OS user is tkyte.
Rating: 4
bequeath protocol September 29, 2002
Reviewer: A reader
Hi Tom

When is BEQUEATH protocol used? I think is when your client and server are in
same node however when I attended oracle net8i course in Oracle University in
the notes it says that by default Bequeath is used (even from a remote client).

There is a paragraph which says:

Using the Bequeath Method
• If a dedicated server does not have prespawned server processes, the Bequeath
session method is used by default.
• If, because of the operating system or protocol, a connection cannot pass
between two different processes on the same machine, this method cannot be used.

There is another method called Redirect session but from the course my feeling
is that it only works if there are prespawned server processes (the explanation
given in the notes used a case with prespawned server processes)

I am working in Unix and we dont have prespawned server processes, if I connect
from a Windows client will I be using Bequeath protocol or redirect?
Followup:
bequeath most likely.

It is just a "fork" by the listener and "exec" by the forked listener process.
The listener "bequeaths" (leaves in its "will" if you like) the connection you
established with it to the child process.
Rating: 4
in net8 documentation September 29, 2002
Reviewer: A reader
hi

in net8i doc it states:

Bequeathed Network Sessions Directly to Dedicated Servers:
If the client and server exist on the same machine, a client connection can be
bequeathed (passed) directly to a dedicated server process without going through
the listener. The application initiating the session spawns a dedicated server
process for the connection request using the Bequeath protocol.

So Bequeathed Network Sessions only occurs if server and client are in same
node? Because Oracle University net8i notes suggest other thing!
Followup:
It should continue on to fill out the entire story (and it does in fact):

If the client and server do not exist on the same machine, a client connection
can be bequeated from the listener to the dedicated server instaled.  The
listener initiates the session by spawing a dedicated server ........

see
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/network.817/a76933/concepts.htm#1028475
which does not conflict with:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/network.817/a76933/concepts.htm#1009622
It is just that the second link doesn't say "oh, and by the way, this isn't the
only way this happens"

I guess there is the confusion between the exactness of the "bequeath protocol"
and "bequeathing a connection".  Playing with fine grained semantics here but...
technically -- the "bequeath PROTOCOL" is used only on single servers.  We
BEQUEATH a connection using either the beq protocol or from a listener.

Say, if you are hooked on semantics --

* bequeath PROTOCOL -- single machine.  Your client bequeaths the connection
directly to the forked dedicated server.

* bequeathing a connection -- happens over the net with the listener or in a
single server without the listener.  bequeathing a connection is part of the
bequeath protocol.
Rating: 4
September 29, 2002
Reviewer: Bhagat Singh from Delhi India
This gave the clear cut understanding of the three. That was commendable.
Rating: 5
user process and server process January 30, 2003
Reviewer: learningOracle from Houston
What is the difference between a user process and server process?
Does the ini parameter processes include user, server and background processes?

What processes does the TOP command show?
Followup:
You might want to check out

the server concepts guide
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76965/toc.htm

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/nav/docindex.htm#index-CON
you'll find a wealth of information.  As well, books such as my own "Expert one
on one Oracle" cover topics like this in detail.

top shows all processes running on a system -- in unixland, a process is a
process is a process.

in Oracle speak there are background processes (those that are started when you
start the database like arch, pmon, smon).  Then there are server or user or
foreground processes (call them what you will) -- dedicated servers -- they run
for you and do the queries.
Rating: 4
What is pseudo program April 23, 2003
Reviewer: A reader
Hi Tom,

from your example above
[quote]
ops$tkyte@ORA920.US.ORACLE.COM> select username, program from v$process;

USERNAME        PROGRAM
--------------- ------------------------------------------------
                PSEUDO
ora920          oracle@tkyte-pc-isdn.us.oracle.com (PMON)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (DBW0)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (LGWR)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (CKPT)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (SMON)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (RECO)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (CJQ0)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (QMN0)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (S000)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (D000)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (ARC0)
ora920          oracle@tkyte-pc-isdn.us.oracle.com (ARC1)
tkyte           oracle@tkyte-pc-isdn.us.oracle.com (TNS V1-V3)
[/quote]

I have seen this PSEUDO many times but do not understand what it is? Its there
even when there is no connection or, say a just started database. I'm little
confused on this.

Thanks and Regards
Followup:
You used alter system kill session

Once an inactive session is killed its row in the v$session view is not
removed until you try to use that session again and "ORA-00028 your
session has been killed" is reported, then it is removed.  Until
that point, it is marked as killed in the status column and pseudo in the
server column.
Rating: 3
But why on a just started database... April 23, 2003
Reviewer: A reader
Hi Tom,

[code]
SYS@ACME.WORLD> startup
ORACLE instance started.

Total System Global Area  101784796 bytes
Fixed Size                   453852 bytes
Variable Size              75497472 bytes
Database Buffers           25165824 bytes
Redo Buffers                 667648 bytes
Database mounted.
Database opened.
SYS@ACME.WORLD> select username,program from v$process;

USERNAME        PROGRAM
--------------- ----------------------------------------------------------------
                PSEUDO
SYSTEM          ORACLE.EXE
SYSTEM          ORACLE.EXE
SYSTEM          ORACLE.EXE
SYSTEM          ORACLE.EXE
SYSTEM          ORACLE.EXE
SYSTEM          ORACLE.EXE
SYSTEM          ORACLE.EXE

8 rows selected.

SYS@ACME.WORLD> shutdown immediate
Database closed.
Database dismounted.
ORACLE instance shut down.
SYS@ACME.WORLD> startup
ORACLE instance started.

Total System Global Area  101784796 bytes
Fixed Size                   453852 bytes
Variable Size              75497472 bytes
Database Buffers           25165824 bytes
Redo Buffers                 667648 bytes
Database mounted.
Database opened.
SYS@ACME.WORLD> select username,program from v$process;

USERNAME        PROGRAM
--------------- ----------------------------------------------------------------
                PSEUDO
SYSTEM          ORACLE.EXE
SYSTEM          ORACLE.EXE
SYSTEM          ORACLE.EXE
SYSTEM          ORACLE.EXE
SYSTEM          ORACLE.EXE
SYSTEM          ORACLE.EXE
SYSTEM          ORACLE.EXE

8 rows selected.
[/code]

There was no user connected and this pseudo is still there. Why ??
Followup:
consider it the "init process" of Oracle -- just like there is an init in Unix.

it just is.
Rating: 5
i i Sir April 23, 2003
Reviewer: A reader
:)
Rating: 4
Server Process is an alias of "Oracle" like the background ones ? May 26, 2003
Reviewer: Robert
Tom, re-read "Architecture" in your book and have question about the nature of
the (decicatd) server process.

Obviously the server process does a lot of things.

Now, say on UNIX, is this process a "clone" of the Oracle program just like the
background processes ?
as you said on p55 about the background processes:
"..there is only one binary, named simply oracle. It is just executed many times
with different names"

Thanks
Followup:
yes, they are all the same binary.
Rating: 5
Follow up question. May 27, 2003
Reviewer: Saibabu Devabhaktuni from Palo Alto, CA USA
Hi Tom,

You said setting autotrace for statistics in a session add up another session
with the same process id, are there any other circumstances under which a
process can serve bunch of sessions( Not MTS)..., i.e. following query should
give count more than 1, "select count(*) from v$session where
paddr='ORACLE_PROCESS_ADDR".

Is it possible to have more that one process associated with a session at the
same time (Not MTS).

Thanks
Followup:
sure, forms does it all of the time.

ANY program that wants to can do it.  it is somewhat common.
Rating: 5
When in forms are multiple sessions opened against a single process? June 09, 2003
Reviewer: Subrata from Columbus, Ohio
Tom, Could you please elaborate how multiple sessions are opened in Forms which
are served by a single process? When a new block is opened or when a new query
is executed? when?

Thanks
Followup:
the debugging and tracing facilities use it.  I believe things like "open this
form, but in its own session" will use it.

if you program in C or use ntier proxy authentication in java, you would see it
is a pretty trivial thing to actually do in Oracle.  A connection is a two step
-- you "connect", then you establish a session (and a session and a session and
a session and so on)
Rating: 5
Are connections and process synonymous? June 10, 2003
Reviewer: SS from Columbus
A question regarding the excellent very first reply:
Are connections and processes synonymous? (No confusion about the sessions!)? If
no, can we have one connection attached to many processes or vice versa?
examples?

Thanks
Followup:
yes you can -- we call it "shared server" or MTS for example
Rating: 5
Are connections and process synonymous? June 10, 2003
Reviewer: SS from Columbus
A question regarding the excellent very first reply:
Are connections and processes synonymous? (No confusion about the sessions!)? If
no, can we have one connection attached to many processes or vice versa?
examples?

Thanks
Rating: 5
I'm getting ORA-00020 June 17, 2003
Reviewer: Alvin from Philippines
ORA-00020 maximum number of processes (string) exceeded

Cause: All process state objects are in use.

Action: Increase the value of the PROCESSES initialization parameter

I got this message last saturday and got it this wednesday. I had to bounce the
db because i can connect to internal but not be able to do anything. Shutdown
immediate doesn't work. I was compelled to use shutdown abort.

1. Do i attribute this to the number of sessions ?
2. Or its time that i configure the DB to be of MTS ?
3. Does the SPID column in the V$process table equals the number of shared
servers ?
Followup:
1) yes.
2) don't know, if processes is set to "10" (you set it), probably not.  if
processes is 10,000 -- maybe.  In between - don't know, don't know your
hardware.  Is is maxed out process wise?

3) no, there will be dedicated servers, shared servers, background processes in
there
Rating: 5
June 19, 2003
Reviewer: Alvin from Philippines
I set it as processes=415 and it was 315 before.

Base from the ora docs the number of sessions is computed as
(1.1*processes)+5. That means process=415 will give me sessions=461.

But i only get at most 150 users. I dunno how the processes got maxed out.

We're using
Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production
With the Partitioning option
JServer Release 8.1.7.0.0 - Production

Hardware wise its

# uname -a
SunOS sunfire01 5.8 Generic_108528-15 sun4u sparc SUNW,Sun-Fire-280R

# psrinfo -v
Status of processor 0 as of: 06/19/03 03:13:49
  Processor has been on-line since 01/29/03 05:12:46.
  The sparcv9 processor operates at 900 MHz,
        and has a sparcv9 floating point processor.
Status of processor 1 as of: 06/19/03 03:13:49
  Processor has been on-line since 01/29/03 05:12:47.
  The sparcv9 processor operates at 900 MHz,
        and has a sparcv9 floating point processor.

and it has 2G of memory.

1. Do machines have limits as to the number of processes it can launch ?
2. How would i know if i'm machine bound ?
3. Can i set sessions to time out ? if possible, will it commit before exiting ?
Followup:
you have unix "processes per user" settings, but there you would get a different
error

you need to research this a little further - you should look at the number of
sessions you have and the number of processes - if processes far exceeds
sessions (v$session v$process) you have a problem somewhere and you need to get
support involved to diagnose it.

for example, I saw a case where an 815 thin JDBC connection to an 817 database
could under some circumstances leave a v$process entry -- causing us to hit max
processes artificially.
Rating: 5
"You used alter system kill session" June 19, 2003
Reviewer: kate from PA
Tom,

if a user is hitting the max process limit, using "alter system kill session"
doesn't help anything than, does it? since the pseudo process is not removed
until potentially a later time.

someone told me when they hit this ora-00020 they couldn't even connect as
sysdba.  could this be because they weren't local to the db?  should they have
been able to even when ora-00020 is denying other connections?
Followup:
alter system kill session won't work that way. the session stays in v$session
marked as killed.

not having a cut and paste of an actual event leaves me at a disadvantage here,
they should have been able to connect (i have been) -- I can only assume they
tried to connect as sys not sysdba
Rating: 5
dedicated server August 03, 2003
Reviewer: Reader
Following is a quote from ch 3 of your next book.

<quote>
that does not mean every single session has a dedicated server, it is possible
for a single application, using a single physical connection (dedicated server)
to have many sessions concurrently active
<quote>

I always thought each session will have its own dedicated server process. If so,
in the above case, will I still see for each clientPID, there will be a
corresponding SPID from v$process and v$session.

THANKS.
Followup:
In Oracle there are two concepts

o a connection, a physical circut between you the client and the server
o a session, a logical thing

You can have many sessions going from a single client using a single connection.

The connection has the dedicated server.
Rating: 5
dedicated server continued August 03, 2003
Reviewer: Reader
<quote> You can have many sessions going from a single client using a single
connection.<quote>

In this case as well, each session will have its own dedicated server process.
Right? If you say no, I have to seriously review my understanding of dedicated
server concept. Thanks.
Rating: 5
got it! August 03, 2003
Reviewer: Reader
Please ignore my previous stupid question.

<quote>Autotrace for statistics uses ANOTHER session so it can query up the
stats for your CURRENT session without impacting the STATS for that
session<quote>

understood from your earlier demo in this thread.
Is there any other situation other than autotrace would use another session
still with the same process? Thanks. You are simply superb.
Followup:
forms does it for debugging all of the time.

ntier proxy authentication in 9i does it (the ability to connect "on behalf of"
someone else.

you can code it yourself in OCI.
Rating: 4
August 04, 2003
Reviewer: Anthony Reddy from KL, Malaysia
Rating: 4
post/wait initialization error August 04, 2003
Reviewer: Anthony Reddy from KL, Malaysia
Is this error related to number of processes.
i got this error, when i am creating a database.
Please help.
Followup:
read your installation guide, that means you haven't configured sufficient
system resources like semaphores, shared memory and such at the OS level for the
number of databases you wish to have concurrently running.
Rating: 2
post/wait initialization error August 05, 2003
Reviewer: Anthony Reddy
Thanks for your reply.

I am using hp-ux 11,

The memory usage is like this:

root:/>swapinfo -tm

       AVAIL(Mb)     USED(Mb)    FREE(Mb)    PCT USED
            371        133        238          36%

Here are the kernel settings:

root:/>kmtune | grep shm
shmem                 1
shmmax                1073741824
shmmni                300
shmseg                120

root:/>kmtune | grep sem
sema            1
semaem          16384
semmap          (SEMMNI+2)
semmni           300
semmns           600
semmnu           600
semume           75
semvmx           32767

This is a development server, here we got six instances,
five instances have processes = 100 in the init.ora,
and the lost one have processes = 50 in the init.ora file.

When i try to create the seventh database, i am getting the
above error.

please seggest the required kernel settings.

thanks in advance.
Followup:

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:454420165038
still hesistant to read that documentation eh...  oh well.
Rating: 4
August 08, 2003
Reviewer: A reader
Hi Tom,

I hope you can help me to understand something.
In a shared server database the parameter processes in the init.ora file should
be set in which of the following ways:

1- processes=dedicated connections + shared_servers=340

or

2- processes=dedicated connections + max_shared_sessions=2000

where:
max_shared_sessions=1700
dedicated connections=300
shared_servers=40

We are planning to allow 1700 shared connections and allow 300 dedicated
connections. I think the correct way is option #1, because if not it will make
more sense to stay in a dedicated mode instead of shared in this case. Am I
correct, is option#1 the correct answer? or option #2 is the correct way?

Any help with this it will be really appreciated.

Thanks
Followup:
I think you mean max_shared_servers, not _sessions

but you want processes to be based on the max number of os processes/threads you
will create which is bounded by the number of dedicated server sessions, the MAX
number of shared server processes and the backgrounds

So, you would actually want probably 2,050 processes -- 1700 for shared servers,
300 for dedicated servers and 50 for backgrounds like dbwr, lgwr, job queue
processes, etc.
Rating: 5
August 10, 2003
Reviewer: A reader
Hi Tom,

Thanks for answering me.
Well I really meant to say max_server_sessions. In the Oracle documentation says
that :
<quote>
SHARED_SERVER_SESSIONS
 Specifies the total number of shared server user sessions to allow. Setting
this parameter enables you to reserve user sessions for dedicated servers
<quote>
That is what confused me, I thought that I should have used this parameter
instead of the shared_servers.

1-Taking into account your answer I understand that I should use the
max_shared_servers instead of shared_servers (as I put in option#1). So if the
parameters settings are going to be the following:
max_shared_servers=100
shared_servers=40
dispatchers=40
max_shared_sessions=1700
dedicated servers =300

is It correct to set the "processes" parameter to 450 which means in my case 100
max_shared_servers + 300 dedicated servers + 50 backgound processes?

and two more question:
2-If I want to have no more that 1700 user shared sessions on the database is it
ok to set max_shared_sessions =1700?

3-based on I want to have 300 dedicated servers and no more than 1700 shared
users sessions, is it ok to set the parameter sessions=2000 in this case?

Thanks for your help on this.
Followup:
there is no init.ora parameter "max_server_sessions"

processes should be set to some number larger then the sum of

o the max number of shared server processes
o the max number of dedicated servers
o the max number of backgrounds (dbwr, lgwr, job queues, etc)
Rating: 5
ORA-12519 August 13, 2003
Reviewer: Ashok from USA
Hi,
I was trying to simulate a problem which we are getting in
informatica(ora-12519) during batch process .Informatica uses DBlink to connnect
to our database.We monitored the session and it's looks like it frequently
connects and disconnects during the batcg process and in the middle of the
process they are getting ora-12519.

To simulate this
I tried something in powerbuilder
 for i = 1 to 200
   connect to oracle;
   disconnect to oracle
 next

In the mean time i tried to connect to oracle with Dbartisan i got the same
error(ora-12519).Why is that happening.
I know it's stupid to do frequent disconnects and connects inside the batch
process but we cant control the third party tool(Informatica).
Rating: 5
process October 02, 2003
Reviewer: A reader
Why are some processes showing as n:n in the process columns -- why the colon ?
(Using 9ir2 on sun)

SQL> select username, process from v$session;

USERNAME                       PROCESS
------------------------------ ------------
                               1075
                               1079
                               1081
                               1087
                               1093
                               1097
                               1101
API_CONEXAO                    1144:1148
API_CONEXAO                    26340
API_CONEXAO                    1144:1148
SYSTEM                         1544:1832
API_CONEXAO                    3133
API_CONEXAO                    26341

13 rows selected.
Followup:
they are windows clients -- select the program out as well to see what program
they are.
Rating: 5
October 03, 2003
Reviewer: A reader
ok, they are windows clients -- but shouldn't there be a corresponding server
process on Solaris ? Shouldn't its pid be there ?
Followup:
that column is the clients process id, from the client machine.

ops$tkyte@ORA920> select a.spid dedicated_server,
  2             b.process clientpid
  3    from v$process a, v$session b
  4   where a.addr = b.paddr
  5     and b.audsid = userenv('sessionid')
  6  /

DEDICATED_SE CLIENTPID
------------ ------------
26581        26580

that gives you the "dedicated server" and client process id's
Rating: 5
Too many sessions. October 23, 2003
Reviewer: Alvin from Philippines
The system that i'm handling creates 3 oracle sessions per user login in the
front end (10 Front end user = 30 oracle users). Only one of them is used and
the other 2 sessions just consumes extra processes. I've hit the max process
error a couple of times already and resorted to shutdown abort.

What happens if there are many uneccessary idle sessions in a db ? how would
affect performance and how do i quantify it ? We're in a dedicated server mode.

I just finished reading the first chapter of your new book and i agree that data
independence idea stresses the DB and the DBA to much. What the our front end
does is query from user A and user B and it consolidate the result set which to
me seems to be the function of the db in the first place. It can easily be work
around by grants or other users objects but doing so would entail massive code
re-writes so it'll never be feasible for the developmental team.

It's a too taxing proposing some existing oracle technology at times because
they always ask me that other DB's should have the same features too. I only
know ORACLE.

They're only compelled to take my suggestion when things are already crawling.
Another thing also, they didn't normalize the most frequently accessed table
they tell me that they heard that 'joins' are slow. So what should've been query
big table with small table, they self join the big table.
Followup:
we cannot tell they are unnecessary.

you might consider shared server if you have too many processes, the idle ones
just will consume UGA memory in the large_pool (SGA) but not a process.
Rating: 4
processes October 28, 2003
Reviewer: Venkat from India
Tom,

We have a requirement saying that there would be 3400 concurrent users in the
system. It is internet banking application. It is a 3 tier architecture,
weblogic as app. server.

- What value should be set for processes in init.ora?
- What value should be set in the connection pool of weblogic?

If we set a higher value for processes in init.ora, will it consume any
resource?

Pl. clarify.

Regards,
Venkat
Followup:
o no clue
o no idea

3400 concurrent users is virtually *meaningless*

I have 5,000 concurrent users on asktom -- if I measure an hour.

I have 0-15 truely concurrent users on asktom if i measure a point in time.

So, my connection pool would only need to be about 15-20 at best and processes
very low.

But there are other systems with 5,000 concurrent users that have 100 truely
concurrent users (all hitting the enter key at the same time)

THAT is what you need to ascertain.  How many truely concurrent users do you
have -- it will not be anywhere near 3,400 (people think, thinktime...)

setting processes higher consumes a small (trivial, not to be concerned about)
amount of SGA memory.  Given that you need to restart to change it, you what
"bigger" in the event you need more.
Rating: 4
Shared servers in dedicated mode December 29, 2003
Reviewer: A reader
Hi Tom,
I have a database in MTS and all the shared servers are showed in dedicated
mode. Do you have any idea why this might happen?

SQL_cccc>select sid,substr(username,1,20) usrname,substr(osuser,1,20)
osuser,substr(program,1,20) pgram,server,status
  2  from v$session where username is not null
  3* order by server,status,sid
SQL_cccc>/

  SID USRNAME     OSUSER         PGRAM               SERVER    STATUS
----- ----------- -------------- ------------------- --------- --------
   48 SYS         USRA           sqlplusw.exe        DEDICATED ACTIVE
   10 USRT        USRAT104       oracle@cccc (S001)  DEDICATED INACTIVE
   14 USRT        USRAT113       oracle@cccc (S000)  DEDICATED INACTIVE
   15 USRT        usrcccc        batchp1.exe         DEDICATED INACTIVE
   18 USRT        USRAT111       oracle@cccc (S002)  DEDICATED INACTIVE
   19 USRT        USRJ           oracle@cccc (S000)  DEDICATED INACTIVE
   24 USRT        USRAT101       oracle@cccc (S001)  DEDICATED INACTIVE
   27 USRZ        Administrator  batchp2.exe         DEDICATED INACTIVE
   30 USRT        USRAT106       oracle@cccc (S000)  DEDICATED INACTIVE
   37 USRT        USRAT117       oracle@cccc (S001)  DEDICATED INACTIVE
   40 USRT        USRAT102       oracle@cccc (S001)  DEDICATED INACTIVE
   44 USRA        USRA           sqlplusw.exe        DEDICATED INACTIVE
   46 USRT        USRAT103       oracle@cccc (S000)  DEDICATED INACTIVE
   52 USRR        usrcccc        sqlplusw.exe        DEDICATED INACTIVE
   65 USRT        USRAT120       oracle@cccc (S000)  DEDICATED INACTIVE
   72 USRT        USRAT206       oracle@cccc (S000)  DEDICATED INACTIVE
   73 USRT        USRAT122       oracle@cccc (S000)  DEDICATED INACTIVE
   75 USRT        USRAT121       oracle@cccc (S000)  DEDICATED INACTIVE
   76 USRT        USRAT204       oracle@cccc (S000)  DEDICATED INACTIVE
   77 USRT        USRAT123       oracle@cccc (S000)  DEDICATED INACTIVE
   80 USRT        USRAT110       oracle@cccc (S000)  DEDICATED INACTIVE
   82 USRT        USRAT108       oracle@cccc (S000)  DEDICATED INACTIVE
   87 USRT        USRAT112       oracle@cccc (S000)  DEDICATED INACTIVE
  102 USRT        USRDB          appc.EXE            DEDICATED INACTIVE
  114 USRZ        usrcccc        batchp4.exe         DEDICATED INACTIVE
  122 USRT        USRAD          oracle@cccc (S000)  DEDICATED INACTIVE
  123 USRT        USRAT115       oracle@cccc (S001)  DEDICATED INACTIVE
  124 USRA        USRA           vms.exe             DEDICATED INACTIVE
  128 USRT        USRAT210       oracle@cccc (S000)  DEDICATED INACTIVE
   11 USRAT       USRAT205       APPF.EXE            NONE      INACTIVE
   16 USRAT       USRAT103       APPF.EXE            NONE      INACTIVE
   17 USRAT       USRAD          APPP.EXE            NONE      INACTIVE
   21 USRAT       USRAT116       APPF.EXE            NONE      INACTIVE
   25 USRAT       USRAT207       APPF.EXE            NONE      INACTIVE
   28 USRAT       USRAT204       APPF.EXE            NONE      INACTIVE
   29 USRAT       USRAT122       APPP.EXE            NONE      INACTIVE
   32 USRAT       USRAT108       APPP.EXE            NONE      INACTIVE
Thanks.
Followup:
databases do not run in "shared server mode".  A database can be configured to
accept shared server connections (and yours is, appp.exe is using a shared
connection).

regardless of whether a database is configured in shared server mode -- it is
ALWAYS configured for dedicated server.   The client can request this in the
tnsnames.ora entry (server=dedicated or server=shared).

Also, the database registers with a listener and can register a dedicated server
connection, as well as shared server connections.  then the tnsnames.ora entry
would specify which service it wants to use.
Rating: 4
Shared servers in dedicated mode December 29, 2003
Reviewer: A reader
Tom, Thanks for answering me.
In my case the entry in the tnsnames.ora file used to establish a connection to
the database has configured "server=shared".
Shouldn't the shared server processes be connected to the database in shared
mode due to the configuration in the tnsnames.ora entry?

Thanks for your help.
Followup:
you went to all of the machines of course and checked this out right?  not just
YOUR machine which will do you and you alone?

(USRAT is using shared server there)
Rating: 4
Shared servers in dedicated mode December 30, 2003
Reviewer: A reader
Thanks for answering Tom.

Well, I didn't check all the machines personally. But I did check the
tnsnames.ora file in the server and I checked with Systems and they said that
the machines have the entry set to "server=shared" in all the machines.
But what took my attention was that the shared servers were connected in
dedicated mode, for example:
10 USRT        USRAT104       oracle@cccc (S001)  DEDICATED INACTIVE.
After you answer me last time I change my query and I added the view v$process
in it, and I found out that the sessions with the program column in v$session
like "oracle@cccc (S000 or S001)", in the v$process the program column have a
value like "oracle@cccc (TNS V1-V3)". After this I checked all the dblinks in
the database and I found a couple of dblinks which are using a dedicated entry
to the same database using ipc, and that's why I see "oracle@cccc (S000)" in
v$session and I thought that the shared servers were connected as dedicated. The
only moment when this is happpening is when the applications use these dblinks
using the ipc entry. I will talk with the developers to have them change the
queries to fix this.
Now I have another question regarding this, do you know why I see "oracle@cccc
(S000)" on v$session and "oracle@cccc (TNS V1-V3)" on v$process for the same
session, when the shared server needs to use these dblinks? Shouldn't have the
program column the same value in both views?

Thanks for all your help on this.
Followup:
don't know why they have different names there -- sorry.
December 30, 2003 brenda -- Thanks for the question regarding "what kind of analyze is best", version 8.1.7.3.0
You Asked
Hi Tom:
we are having some issues with performance on our 500GB database environment. we
do analyze tables on a daily basis every 4 hours since data loading on different
time, not sure when the data will finish loading. I have seen different kinds of
'analyze':

a. analyze table xxxx estimate statistics sample 10 percent;

b. analyze table xxxx estimate statistics sample 10 percent for table for all
indexes for all indexed columns;

c. analyze table xxxx validate structure;

d. analyze table t compute statistics
   for table
   for all indexes
   for all indexed columns;

e. analyze table xxxx compute statistics for table

could you tell me 5 analyze differences? and what is the best for DW when we
have daily loading going on during the day and analyze job is running as well?
and we said...
I don't like any of them.

DBMS_STATS is what you want to use.  If you

ALTER TABLE <tname> MONITORING

then you can gather stats JUST on tables that need it (we'll keep track for you
-- and only analyze tables in which about 10% of the data has changed).  That
way the analyzes will go much faster.

Me, I'm sort of a fan of a method that would analyze the table, the indexes and
in most cases -- the indexed columns (although for unique or data with fairly
uniform distributions, you could be more specific and just analyze the indexes
that are on skewed data).

Here is a synopsis of the differences for the above (but, don't use them! use
dbms_stats to do what they do.  Which is best????  well, compute is prefered but
for really big things, not feasible.  I said what i like -- table, indexes and
the proper set of indexed columns)

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

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create index t_idx on t(object_id);

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t estimate statistics sample 10
percent;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select t.num_rows, i.num_rows, c.cnt
  2    from (select num_rows from user_tables where table_name = 'T') t,
  3         (select num_rows from user_indexes where table_name = 'T' ) i,
  4         (select count(distinct column_name) cnt from user_tab_histograms
where table_name = 'T' ) c
  5  /

  NUM_ROWS   NUM_ROWS        CNT
---------- ---------- ----------
     23634      23634         12

that shows we got the table analyzed, the index(es) analyzed and histograms
for all of the columns in the table that had at least one not null value
(expensive!)

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t delete statistics;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t estimate statistics sample 10
percent for table for all indexes for all indexed columns;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select t.num_rows, i.num_rows, c.cnt
  2    from (select num_rows from user_tables where table_name = 'T') t,
  3         (select num_rows from user_indexes where table_name = 'T' ) i,
  4         (select count(distinct column_name) cnt from user_tab_histograms
where table_name = 'T' ) c
  5  /

  NUM_ROWS   NUM_ROWS        CNT
---------- ---------- ----------
     23634      23634          1

Here we got the table, the indexes and a histogram on ONE column -- the one we
have an index on (not particularly relevant in this case as object id is unique
and hence distributed nicely, not skewed)

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t delete statistics;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t validate structure;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select t.num_rows, i.num_rows, c.cnt
  2    from (select num_rows from user_tables where table_name = 'T') t,
  3         (select num_rows from user_indexes where table_name = 'T' ) i,
  4         (select count(distinct column_name) cnt from user_tab_histograms
where table_name = 'T' ) c
  5  /

  NUM_ROWS   NUM_ROWS        CNT
---------- ---------- ----------
                               0

No stats, just a diagnostic tool -- not for statistics gathering!

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t delete statistics;

Table analyzed.

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

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select t.num_rows, i.num_rows, c.cnt
  2    from (select num_rows from user_tables where table_name = 'T') t,
  3         (select num_rows from user_indexes where table_name = 'T' ) i,
  4         (select count(distinct column_name) cnt from user_tab_histograms
where table_name = 'T' ) c
  5  /

  NUM_ROWS   NUM_ROWS        CNT
---------- ---------- ----------
     23634      23634          1

ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t delete statistics;

Table analyzed.

same as the estimate but does the full table -- if I had a bigger set, the
num_rows here might be more accurate using compute then estimate

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select t.num_rows, i.num_rows, c.cnt
  2    from (select num_rows from user_tables where table_name = 'T') t,
  3         (select num_rows from user_indexes where table_name = 'T' ) i,
  4         (select count(distinct column_name) cnt from user_tab_histograms
where table_name = 'T' ) c
  5  /

  NUM_ROWS   NUM_ROWS        CNT
---------- ---------- ----------
     23634      23634         12

same as the corresponding estimate but does the entire table
Review & Followup

Rating: 5
What do you think of this analyze script? October 08, 2002
Reviewer: Alan Rosenthal from CA USA
Hi Tom,

I use the following sqlplus script to analyze my db's.  I have a good window of
time when I can run this so I am not concerned with the time to run the analyze.
 Is this script good?  Is there anything more that I should do?

=====================================
prompt "Gathering all stats"
execute dbms_stats.gather_database_stats( cascade => TRUE ) ;

prompt "Deleting sys stats"
execute dbms_stats.delete_schema_stats( 'SYS' ) ;

prompt "Deleting system stats"
execute dbms_stats.delete_schema_stats( 'SYSTEM' ) ;

exit ;
===============================

I'm definitely going to look into monitoring.

Thanks,

Alan
Followup:
Instead of gathering on SYS and SYSTEM and then removing - you might

 for x in ( select username
              from dba_users
             where username not in ( 'SYS', 'SYSTEM' ) )
  loop
      dbms_stats.gather_SCHEMA_stats( ...... for that username )
  end loop;
Rating: 4
Review October 08, 2002
Reviewer: Irfan from Kuwait
Q1. Your advice is to use dbms_stat. There is another package
dbms_utility, Do you prefer dbms_stat over dbms_utility?
if yes then why ?

Q2. In your book expert one-on-one, in the appendix A regarding dbms_utility you
have written that "WE START BY FIRST DELETING AND THEN COLLECTING STATISTICS".
Why there is a need to delete the statistics? The oracle manual says that
ENTRIES ARE UPDATED FOR AN OBJECT EACH TIME WE ANALYZE THE OBJECT.

Thanks and regards.
Followup:
q1) dbms_utility has a "pre dbms stat" procedure that offers no good
functionality (nothing like dbms_stat)

ignore that dbms_utility procedure
use dbms_stat

q2) I did that (the delete) just to show "look, there is nothing up my sleeve".
I deleted -- showed there were no stats and then analyzed showing there are now
stats.

Thats all, just an example.
Rating: 5
Histograms with bind variables October 08, 2002
Reviewer: Todd from Los Angeles, CA
Tom, if we have built an app to use bind variables instead of literal values, is
there any benefit to building histograms on even just indexed columns?
Followup:
could be -- you can (and should sometimes) have literals in there, eg:

select count(*)
  from t
 where dname = 'SALES'
   and sal > :x;

There is nothing wrong with that -- if the query is executed over and over and
over.  A histogram on dname might well be appropriate.  A well tuned system uses
bind variables where appropriate and literals when it wants to.  See -- if that
query was ALWAYS executed with dname = 'SALES', there would be no point in
binding 'SALES' in -- we only bind in values that change from execution to
execution.
Rating: 5
DBMS_STATS October 08, 2002
Reviewer: brenda zhang from USA
Hi Tom,
thank you very much! very through.... but I still have oen question here: for
instance, we have 8 milions table cust_dim,we create a following procedure to
analyze table, but this table cust_dim will not get analyzed untill there are
more than 10% of data change as you said:
"you can gather stats JUST on tables that need it (we'll keep track for you and
only analyze tables in which about 10% of the data has changed).

so the statistics will not be accurate all the time if the data change is less
than 10%. do we still have to use scheduler to analyze on a regular basis beside
DBMS_STATS.GATHER_DATABASE_STATS?

create or replace procedure anal_schemas is
begin
    begin
    DBMS_STATS.GATHER_DATABASE_STATS (
        estimate_percent => 10,
               block_sample => FALSE,
              method_opt => 'FOR ALL INDEXED COLUMNS SIZE 1',
           degree => 8,
            granularity =>'DEFAULT',
               cascade => TRUE,
            stattab => NULL,
             statid => NULL,
             options => 'GATHER STALE'
    --       objlist =>'LIST',
    --    statown => NULL
    );
    end;

    begin
    DBMS_STATS.DELETE_SCHEMA_STATS  ('SYS');
    end;

    begin
    DBMS_STATS.DELETE_SCHEMA_STATS  ('SYSTEM');
    end;
end;
/
Followup:
well, I still would gather for SCHEMAS not database...

and yes, you'll need to schedule this to run from time to time.
Rating: 5
DBMS_STATS.GATHER_SCHEMA_STATS doesn't work October 08, 2002
Reviewer: Brenda Zhang from USA
Tom: there is a bug for DBMS_STATS.GATHER_SCHEMA_STATS??
<Bug 1890016>:  GATHER STALE DOES NOT SEEM TO BE WORKING FOR
DBMS_STATS.GATHER_SCHEMA_STATS
Followup:
but the bug also has in it:

Rediscovery Information:
You will see this bug when using dbms_stats.gather_xxx_stats and :
1. An invalid granularity is supplied
2. The GATHER STALE or GATHER EMPTY options are used
3. The procedure call does not seem to work
.
Workaround:
Correct the granularity parameter

simple workaround (and its fixed in the current releases)
Rating: 4
Review October 09, 2002
Reviewer: Irfan from Kuwait
You mean to say that <Bug 1890016>: will not affect if I ignore  GATHER STALE or
GATHER EMPTY. In this case following should work in Oracle 9.0.1.3 to compute
statistics of schema ?

execute DBMS_STATS.GATHER_SCHEMA_STATS('SCOTT',NULL);

Thanks
Followup:
yes.
Rating: 5
Advice on "SIZE 1" histogram October 14, 2002
Reviewer: Brad from dallas, tx
The manual describing the DBMS_STATS options makes the point that you must
specify "SIZE 1" in order to enable parallel processing for histograms.
Doesn't a 1 bucket histogram limit it's usefulness?

Any advice on this?

E.G.

  DBMS_STATS.GATHER_DATABASE_STATS (
        estimate_percent => 15,
        method_opt => 'FOR ALL INDEXED COLUMNS SIZE 1',
        degree => 8,                           ^^^^^^
        cascade => TRUE) ;
Followup:
There may be certain specific columns you won't do in parallel...  computing
histograms is very expensive -- do it only for those columns you have identified
a need for (eg: wanna make a system go faster at night, stop using "analyze
table compute statistics", you are computing histograms on everything)
Rating: 4
Review October 26, 2002
Reviewer: Irfan from Kuwait
I am using following to Analyze schema with COMPUTE option
(I am specifying NULL for COMPUTE.)
execute DBMS_STATS.GATHER_SCHEMA_STATS('SCOTT',NULL);

For gathering statistics on index as well, do I need to specify CASCADE=true ?
Or by default above will collect index Statistics ?

Thanks.
Followup:
Here is a way you can determine what is happening when you analyze (whats
getting gathered)

ops$tkyte@ORA920.US.ORACLE.COM> drop user a cascade;
User dropped.

ops$tkyte@ORA920.US.ORACLE.COM> grant dba to a identified by a;
Grant succeeded.

ops$tkyte@ORA920.US.ORACLE.COM> @connect a/a

a@ORA920.US.ORACLE.COM> create table t ( x int primary key, y int );
Table created.

a@ORA920.US.ORACLE.COM> insert into t values ( 1, 1 );
1 row created.

a@ORA920.US.ORACLE.COM> exec dbms_stats.gather_schema_stats( 'A', NULL );
PL/SQL procedure successfully completed.

a@ORA920.US.ORACLE.COM> select count(*) from user_tables where num_rows is not
null;

  COUNT(*)
----------
         1

indicates TABLE got analyzed

a@ORA920.US.ORACLE.COM> select count(*) from user_indexes where num_rows is not
null;

  COUNT(*)
----------
         0

but not any indexes

a@ORA920.US.ORACLE.COM> select count(distinct column_name) from
user_tab_histograms;

COUNT(DISTINCTCOLUMN_NAME)
--------------------------
                         2

a@ORA920.US.ORACLE.COM>

but it did get histograms for every column in the table
Rating: 4
which granularity should be used to analyze a schema? October 27, 2002
Reviewer: Leo from Austin, TX, USA
I have a schema that has partition tables and non-partition tables.  I want to
use DBMS_STATS.GATHER_SCHEMA_STATS to analyze all tables in the schema.  Which
granularity should be used, 'DEFAULT', 'ALL' or 'PARTITION'?
Followup:
only you can answer that.  which do you want to use (best to understand what
they do, and apply the one that is correct and proper for your environment)

if there was a single way that was superior, there wouldn't be a choice.

You might find gather schema stats to be too coarse in a higher end system.  you
may find you need to do some things table by table.
Rating: 1
October 27, 2002
Reviewer: leo
One advantage to use DBMS_STATS is that it can only analyze stale tables, but
this option is only available in GATHER_SCHEMA_STATS and GATHER_DATABASE_STATS
procedures.  Using the right granularity is critical for analyzing both
partition and non-partition tables in a schema.
Followup:
hmmm, well, lets see --

I can use LIST STALE on a schema and then do it table by table based on the
results.

So, while you must use stale at the database or schema level, you certainly are
not forced to analyze stale only at the schema or database level, you can just
ask for a list of stale "things" and analyze them as you see fit (based off of a
table perhaps you maintain of analyze options for a given segment?)

So, I disagree that stale is limited to schema or database.  It is that you
would ask for a list of stale objects at the database or schema level and then
proceed with analyzing if you like.
Rating: 5
Review October 27, 2002
Reviewer: Irfan from Kuwait
I went through your example to see want is happening when we analyze schema. I
used dbms_utility.analyze_schema with your example. Following is the outcome.

SQL> create table t ( x int primary key, y int );

Table created.

SQL> insert into t values ( 1, 1 );

1 row created.

SQL> execute dbms_utility.analyze_schema('A','COMPUTE');

PL/SQL procedure successfully completed.

SQL> select count(*) from user_tables where num_rows is not
  2  null;

  COUNT(*)
----------
         1

SQL> select count(*) from user_indexes where num_rows is not
  2  null;

  COUNT(*)
----------
         1
SQL> select count(distinct column_name) from
  2  user_tab_histograms;

COUNT(DISTINCTCOLUMN_NAME)
--------------------------
                         2

So, when I use DBMS_UTILITY.ANALYZE_SCHEMA I am getting statistics on INDEXES
also. Correct ?

Then in this case DBMS_UTILITY should be prefered over DBMS_STATS. Please
suggest.
Followup:
do not use dbms_utility -- it is there for backwards support to 7.3.

Use dbms_stats, ignore dbms_utility for analyzing tables.  It is never
preferred.  It has so few features as to make it just pale in comparision.

and remember this -- more is NOT better.  more is sometimes "more".

It is RARE that you want histograms on everything -- it is very very very time
consuming and resource consuming to compute them.
Rating: 5
REVIEW October 27, 2002
Reviewer: Irfan from Kuwait
So, I am going to use DBMS_STATS. I want to get the statistics on indexes also.
I this case how can I define the syntex of DBMS_STATS.

I tried to do following to Analyze ENRIRE schema with INDEXES including.

exec DBMS_STATS.GATHER_SCHEMA_STATS('SCOTT',NULL,FALSE,'FOR ALLCLOUMNS SIZE
1',NULL, 'DEFAULT',TRUE)

Is the above correct.(I could not understand block_sample and method_opt, that's
why I put default values.)

Is there any other better way to do this ?
Followup:
well, lets see:

@ORA920.US.ORACLE.COM> create table t ( x int primary key, y int );

Table created.

a@ORA920.US.ORACLE.COM> insert into t values ( 1, 1 );

1 row created.

a@ORA920.US.ORACLE.COM> exec DBMS_STATS.GATHER_SCHEMA_STATS('A',NULL,FALSE,'FOR
ALL COLUMNS SIZE 1',NULL, 'DEFAULT',TRUE)

PL/SQL procedure successfully completed.

a@ORA920.US.ORACLE.COM>
a@ORA920.US.ORACLE.COM> select count(*) from user_tables where num_rows is not
null;

  COUNT(*)
----------
         1

a@ORA920.US.ORACLE.COM> select count(*) from user_indexes where num_rows is not
null;

  COUNT(*)
----------
         1

a@ORA920.US.ORACLE.COM> select count(distinct column_name) from
user_tab_histograms;

COUNT(DISTINCTCOLUMN_NAME)
--------------------------
                         2

it does accomplish your goal of analyzing the indexes, yes.  But -- are you sure
you want 100% column level histograms -- that, only you can answer.
Rating: 5
Review October 27, 2002
Reviewer: Irfan from kuwait
Thanks for detailed explanation one more question. Instead of typing the all
default values, Can I use following to save me typing effort. It it take all the
default values?

execute dbms_stats.gather_schema_stats ('A',NULL,cascade=>true);

Thanks again.
Followup:
fortunately, we document all of the defaults and how to use these things:
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/appdev.817/a76936/dbms_st2.htm#1003995
is the documentation for gather schema stats -- just check it out and make sure
the defaults are the ones you want.

by the way .... most people would SCHEDULE this using dbms_job to happen on a
recurring basis -- so you would save on typing by scheduling it once or by
creating a script that has the "long version" in it
Rating: 5
is the 8.1.7 doc wrong? October 28, 2002
Reviewer: A reader
Hi

In SQL ref guide it says this about analyze command:

===========================================================
If you want to collect statistics on both the table as a whole and on one or
more columns, be sure to generate the statistics for the table first, and then
for the columns. Otherwise, the table-only ANALYZE will overwrite the histograms
generated by the column ANALYZE. For example, issue the following statements:

ANALYZE TABLE emp ESTIMATE STATISTICS;
ANALYZE TABLE emp ESTIMATE STATISTICS
FOR ALL COLUMNS;

===========================================================

I mean why we have to do twice if the first analyze command gets stats for all
columns already?????
Followup:
not technically wrong -- just a bad example.  The premise is right -- the
example is not "good". you do not need to do that twice.  It is pointing out
that if you do some analyzes in certain orders -- the last one "wins".  They
should have done it differently, yes.
Rating: 5
Clarification November 05, 2002
Reviewer: Steve from UK
Hi Tom

We've identified about 3 tables that have indexed columns that require
histograms - so we issue the following:

exec
dbms_stats.gather_table_stats(ownname=>'<schema>',tabname=>'<tabname>',method_opt
=>'FOR COLUMNS <columnname> SIZE 3');

If we want to analyse the entire schema - will we need to run the above command
after gathering stats for the entire schema or is there a way of getting the
database to look after it for us?

Cheers

Steve
Followup:
you would run it after -- not sure what you mean by "getting the database to
look after it for us"
Rating: 5
November 05, 2002
Reviewer: Steve from UK
Thanks Tom,

I guess what I meant was whether we had to run the command separately afterwards
as you've now said, or whether, once it's analysed with the extra buckets,
Oracle would always use the same number of buckets with a standard
dbms_stat.gather_schema_statistics command on those columns.
Rating: 5
Why DBMS_STATS.gather_schema_stats('SCHEMA') does not work? November 12, 2002
Reviewer: A Reader from USA
Hi Tom,
I have a database that is used on online transaction system.  Some thing about
the statictis I don't understand.  When I run
DBMS_STATS.gather_schema_stats('SCHEMA'), the tables in the schema are analyzed
(I check the last_analyzed in v$session).  However, when I run a query, the
performance does not improve at all.  I also rin the
dbms_utility.analyze_schema('SCHEMA','COMPUTE'), the same thing happen, no
performance gain.  Both methods worked in other databases. Since I know which
table I need to analyze, I run "analyze table t compute statistics".  After
this, the respose time for the query improve from 1.1 s to 80 ms.  The execution
plan never changes according to the plans generated before and after all the
analysis.  Another thing I notice is that, after a few logins using the
application interface, the query slow down again.  Questions:
1.  Wyh DBMS_STATS.gather_schema_stats('SCHEMA') or dbms_utility.analyze_schema
does not work and I need to analyze the specific table?
2.  Why the plan does not change but the query (which always uses full table
scan) performance changes?
3.  Why the query slow again after a few logins?  How can I "chache" the
staistics or the plan?

Thank you very much for your help.
Followup:
Oh - would it not be totally cool if just running dbms_stats was like setting
fast=true!

what you need to do is
a) run the queries with sql_trace before and after having statistics
b) compare the query plans and results

Same with all of the other questions you have -- sql_tracing will tell you what
is going on.

With the amount of detail provided here -- no tkprofs, no examples -- there is
not much more I can say (well, I can say that if you have my book "Expert one on
one Oracle" read chapter 10 and follow the procedures outlined there to see what
is going on)
Rating: 5
please guide November 29, 2002
Reviewer: Yogeeeraj from Mauritius
hello,

From above:
<quote>
I can use LIST STALE on a schema and then do it table by table based on the
results.
</quote>

Can you please direct me to the document where this is explained in more details
or give an example?

thanks a lot.
Best Regards
Yogeeraj
Followup:
have you read the supplied packages guide?  all supplied packages are documented
there.

this is a rather simple exercise in plsql programming.  Here is a simple
example:

ops$tkyte@ORA920.LOCALHOST> create table t as select * from all_objects where
rownum < 100;
Table created.

ops$tkyte@ORA920.LOCALHOST> alter table t monitoring;
Table altered.

-- that is needed for "staleness" measuring

ops$tkyte@ORA920.LOCALHOST> analyze table t compute statistics;
Table analyzed.

ops$tkyte@ORA920.LOCALHOST> declare
  2          l_objList  dbms_stats.objectTab;
  3  begin
  4          dbms_stats.gather_schema_stats
  5          ( ownname        => USER,
  6            options        => 'LIST STALE',
  7            objlist        => l_objList );
  8
  9          for i in 1 .. l_objList.count
 10          loop
 11                  dbms_output.put_line( l_objList(i).objType );
 12                  dbms_output.put_line( l_objList(i).objName );
 13          end loop;
 14  end;
 15  /

PL/SQL procedure successfully completed.

that tells me that no objects need stats right now.  If they did, we would only
list them out -- not actually gather stats on them

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> insert into t select * from t;

99 rows created.

ops$tkyte@ORA920.LOCALHOST> commit;

Commit complete.

ops$tkyte@ORA920.LOCALHOST>
ops$tkyte@ORA920.LOCALHOST> declare
  2          l_objList  dbms_stats.objectTab;
  3  begin
  4          dbms_stats.gather_schema_stats
  5          ( ownname        => USER,
  6            options        => 'LIST STALE',
  7            objlist        => l_objList );
  8
  9          for i in 1 .. l_objList.count
 10          loop
 11                  dbms_output.put_line( l_objList(i).objType );
 12                  dbms_output.put_line( l_objList(i).objName );
 13          end loop;
 14  end;
 15  /
TABLE
T

PL/SQL procedure successfully completed.

Ahh, now T needs stats, more then about 10% of the data was modified.  This
shows that we just listed this fact:

ops$tkyte@ORA920.LOCALHOST> select num_rows from user_tables where table_name =
'T';

  NUM_ROWS
----------
        99

ops$tkyte@ORA920.LOCALHOST> exec dbms_stats.gather_schema_stats( ownname =>
user, options => 'GATHER STALE' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.LOCALHOST> select num_rows from user_tables where table_name =
'T';

  NUM_ROWS
----------
       198

ops$tkyte@ORA920.LOCALHOST>

it told us to gather stats, it didn't actually do it when we used LIST STALE
Rating: 5
Great explanation !! November 30, 2002
Reviewer: Yogeeraj from Mauritius
Hello,

Thank you a lot for this detailed explanation. It really helps!

I will go through the supplied package guide again.

Best Regards
Yogeeraj
Rating: 5
New v9.2 GATHER_AUTO feature December 05, 2002
Reviewer: Brad from Dallas, TX
The GATHER_AUTO option is new in v9.x.   The 9.2 doc says that when you use the
GATHER_AUTO option you may not specify other options that direct which
statistics are gathered, and seems to imply that it decides which stats to
gather in an automated fashion.  Do you happen to know if it determines which
stats to gather on a table by table basis or if it just has a standard set of
stats that it gathers.

-- Turn on monitoring for all the tables in the schema.
-- This only needs to be done as new tables are added.
begin
  dbms_stats.alter_schema_tab_monitoring(
     ownname    => 'MYSCHEMA'
  );
end;
/

-- The GATHER AUTO option causes statistics to be gathered
-- for tables that Oracle thinks need updated stats.
begin
  dbms_stats.gather_schema_stats(
     ownname    => 'MYSCHEMA'
    ,options    => 'GATHER AUTO'
  );
end;
/
Followup:
it is table/table, column by column.  It is also wholly undocumented.  It
samples to decide what to do.

If you look at a sql_trace (be prepared to wait -- sql_trace really slows this
puppy down), you'll see really strange looking queries like:

select /*+ cursor_sharing_exact dynamic_sampling(0) no_monitoring no_expand
  index_ffs(t,"T1_IDX1") parallel_index(t,"T1_IDX1",32767) */ count(*) as nrw,
  count(distinct sys_op_lbid(34421,'L',t.rowid)) as nlb,null as ndk,null as
  clf
from
 "A"."T1"  sample block (.00001)  t where "OBJECT_ID" is not null

select /*+ cursor_sharing_exact dynamic_sampling(0) no_monitoring no_expand
  index_ffs(t,"T1_IDX1") parallel_index(t,"T1_IDX1",32767) */ count(*) as nrw,
  count(distinct sys_op_lbid(34421,'L',t.rowid)) as nlb,null as ndk,null as
  clf
from
 "A"."T1"  sample block (.001)  t where "OBJECT_ID" is not null

select /*+ cursor_sharing_exact dynamic_sampling(0) no_monitoring no_expand
  index_ffs(t,"T1_IDX1") parallel_index(t,"T1_IDX1",32767) */ count(*) as nrw,
  count(distinct sys_op_lbid(34421,'L',t.rowid)) as nlb,null as ndk,null as
  clf
from
 "A"."T1"  sample block (.1)  t where "OBJECT_ID" is not null

select  /*+ cursor_sharing_exact dynamic_sampling(0) no_monitoring parallel(t,
  32767)  parallel_index(t,32767) */ count(*)
from
 "A"."T1" sample block (.001)

select  /*+ cursor_sharing_exact dynamic_sampling(0) no_monitoring parallel(t,
  32767)  parallel_index(t,32767) */ count(*)
from
 "A"."T1" sample block (.1)

....
insert /*+ append */ into sys.ora_temp_1_ds_110 select  /*+
  cursor_sharing_exact dynamic_sampling(0) no_monitoring parallel(t,32767)
  parallel_index(t,32767) */ "OWNER","OBJECT_NAME","SUBOBJECT_NAME",
  "OBJECT_ID","DATA_OBJECT_ID","OBJECT_TYPE","CREATED","LAST_DDL_TIME",
  "TIMESTAMP","STATUS","TEMPORARY","GENERATED","SECONDARY" from "A"."T1"
  sample (14.70588235294117647058823529411764705882) t
....

(please don't ask me about undocumented hints and functions you see -- pretend
they do not exist because for all intents and purposes -- they don't!)
Rating: 5
Again dbms_stats vs analyze January 20, 2003
Reviewer: Igor from France
Hi

 We were doing daily
  dbms_stats.gather_schema_stats( OWNNAME => 'SCHEMA', DEGREE => 2, CASCADE =>
true, method_opt => 'for all columns size 1' )
  but found that some complex queries have suboptimal execution path.

 After running,
                analyze table &tab
                compute statistics
                for table
                for all indexes
                for all columns

 on tables in these queries optimizer found much efficient
 execution plan.
 My conclusion was that that analyze collected more info
 for CBO.
 What would be equivalent in dbms_stats to this analyze ?

                                                Igor

P.S. Also what confuses me a bit is that you are advertising dbms_stats but use
analyze on table
level instead of gather_table_stats in most
 of your examples (I dont mean things like chaining, validate structure and
such)

  Thanks for your clarification

                               Igor
Followup:
No,they just gathered DIFFERENT statistics.  your method opt in dbms_stats is
different then in the analyze.

In my "demos" -- I have toy tables.  I just find it easier to type in (as that
is what I do, type it in) analyze then the corresponding dbms_stats command.
Just laziness on my part as far as that goes.

do as i say, not as i do (in this case) ;)
Rating: 5
Yes, but January 20, 2003
Reviewer: Igor from France
Thanks for your fast response

 When you look at method opt option they are not
 as versatile as with 'analyze table'.
 I would like to run DBMS_STATS.GATHER_SCHEMA_STATS
 with a equal level collected by prior analyze table
 so that CBO is able to do efficient execution plan.
 Maybe is a good idea to play with number of buckets (size x) ? ( I don't know
what is number Oracle choose in ANALYZE statement)

  Thanks for your response

                                    Igor
Followup:
they are as versitile? what do you mean?
Rating: 5
More description of GATHER_SCHEMA_STATS method_opt January 20, 2003
Reviewer: David Penington from Melbourne, Australia
Tom,
I have read the 9iR1 Supplied PL/SQL packages documentation for DBMS_STATS, and
the chapter on gathering statistics in the 9iR1 Database Performance Guide. Is
there any more information available on the meaning and meaning and impact of
the METHOD_OPT of GATHER_SCHEMA_STATS and GATHER_TABLE_STATS ?
I know I could work it out by repeated testing, but something formal is nice
(and more reliable)
Eg. Does it always gather table stat's ?
What do SIZE REPEAT, SIZE AUTO, SIZE SKEWONLY do?
What will it do when a column is names but doesn't occur in some of the tables
(testing tells me what happens in the version I'm testing, but not on other
versions)?
Followup:
Ok, the method opt dictates how histograms will be computed.

DBMS_STATS in general always gets table statistics
(gather_table,gather_schema,gather_database all do).

If you use CASCADE=>TRUE, it'll get indexes.

METHOD_OPT dictates how histograms are done.

Take the analyze command for example:

analyze table T compute statistics
for table              <<=== dbms_stats does this when you gather
                             table,schema,database stats
for all indexes        <<=== when you do table/schema/database AND cascade=>
                             TRUE or you use gather index stats directly
for all columns size 1 <<== when you default method_opt, can be overriden

Now, we have method_opts of:

o for all columns  - gather histograms on all columns -- hugely expensive and
rarely should be used

o for all indexed columns - gather histograms on all columns that are currently
indexed.  I find this typically to be the "most useful" although you can
arguably skip nearly unique columns such as primary keys

o for all hidden columns - gather histograms on columns you cannot see, useful
if using the object relational features which incorporate tons of hidden columns
in your objects.

o for columns <list> - gather histograms on these specific columns, could be
useful to skip primary key indexes or just to get the 2 or 3 columns of interest

Each of the above can be modified with SIZE clauses where SIZE can be:

o SIZE <N>.  N is an integer between 1 and 254 representing the number of
buckets. See note at
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3126073805757
to understand what this means and what happens if for example you use N = 10 and
there are 5 distinct column values or you use N = 10 and there are 15 distinct
values...

o SIZE REPEAT -- says to do the histograms just like you did them last time.  It
reads the data dictionary to figure out what to do

o SIZE AUTO -- Oracle looks at the data and using a magical, undocumented and
changing algorithm, figures out all by itself what columns to gather stats on
and how many buckets and all.  It'll collect histograms in memory only for those
columns which are used by your applications (those columns appearing in a
predicate involving an equality, range, or like operators). we know that a
particular column was used by an application because at parse time, we'll store
workload information in SGA. Then we'll store histograms in the data dictionary
only if it has skewed data (and it worthy of a histogram)

o SIZE SKEWONLY  when you collect histograms with the SIZE option set to
SKEWONLY, we collect histogram data in memory for all specified columns (if you
do not specify any, all columns are used). Once an "in-memory" histogram is
computed for a column, it is stored inside the data dictionary only if it has
"popular" values (multiple end-points with the same value which is what we
define by "there is skew in the data").

don't know what you mean by "What will it do when a column is names but doesn't
occur in some of the tables (testing tells me what happens in the version I'm
testing, but not on other versions)?"... guess you mean "i listed a column name
in the method opt, but not such column exists" -- it should ignore it.
Rating: 5
January 22, 2003
Reviewer: Igor from France
Hi

  OK. Playing with buckets I got wanted result.
  Thank for your help
Rating: 5
gather schema & unusable indexes January 24, 2003
Reviewer: Igor from France
Hi

 Is there workaround without index rebuild ?
 (I would like to recollect statistics skipping unusable
 indexes, but doing it on the rest)

drop table t;

Table dropped.

SQL> create table t as select * from dba_objects;

Table created.

SQL> create index ti on t ( object_id );

Index created.

SQL> alter index ti unusable;

Index altered.

SQL> SQL> alter session set skip_unusable_indexes=true;

Session altered.

SQL> SQL> create table t2 as select * from dba_objects;
c
Table created.

SQL> reate index ti2 on t2 ( object_id );

Index created.

SQL> exec dbms_stats.gather_schema_stats( OWNNAME => 'T', DEGREE => 4, CASCADE
=> true, method_opt => 'for all columns size 1' )
BEGIN dbms_stats.gather_schema_stats( OWNNAME => 'T', DEGREE => 4, CASCADE =>
true, method_opt => 'for all columns size 1' ); END;

*
ERROR at line 1:
ORA-01502: index 'T.TI' or partition of such index is in unusable state
ORA-06512: at "SYS.DBMS_STATS", line 4481
ORA-06512: at "SYS.DBMS_STATS", line 4612
ORA-06512: at "SYS.DBMS_STATS", line 4722
ORA-06512: at "SYS.DBMS_STATS", line 4704
ORA-06512: at line 1

SQL> 1 select table_name, last_analyzed
SQL> /

TABLE_NAME                     LAST_ANAL
------------------------------ ---------
T                              24-JAN-03
T2

 Thanks for your answer

                                               Igor
Followup:
obvious solution:

cascade => false

for x in ( select * from user_indexes where status = 'VALID' )
loop
   dbms_stats.gather_index_stats()
Rating: 5
January 24, 2003
Reviewer: Igor from France
Hah, but it's not so elegant ;-)

 Anyway, what is a bit confusing is that even when
 I should skip these indexes (and really Oracle does it
 in DML statements ), when you start procedure to collect
 statistics, which is essentially select ( OK, a bit "strange" ) he doesn't.
 It smells like "feature", no ? :-)

                                          Igor
Followup:
well, the docs do say

...
This setting allows all operations (inserts, deletes, updates, and selects) on
tables with unusable indexes or index partitions.
......

nothing about other operations.
Rating: 4
March 06, 2003
Reviewer: A reader
Tom,

What should be the frequency of issuing analyze commands in OLTP vs DSS systems.

Thanks.
Followup:
I don't know.

Really.

I would recommend you use alter table T monitoring and let the system tell you
how often for OLTP....

In DW -- it is usually a little more clear -- after you load.
Rating: 5
Follow-up question March 17, 2003
Reviewer: Bob Rich from IN USA
Tom,
  I'm trying to test the gather_stale option on multiple tables having been
analyzed with different percentages.  I've set-up a test with two identical
tables with more that 1 million rows each.  I analyzed table 'A' using 100% of
the rows, and table 'B' using an estimate of 15%.  I then turned on table
monitoring, truncated and reloaded each table.  I then used
dbms_stats.gather_schema_stats( ownname => 'XXXX', options => 'GATHER STALE');
It appears each table was reanalyzed using 100% of the rows.  I expected table
'B' to use 15%.  Is this the way it is supposed to work?

Thank you
Followup:
yes it is.  If you want "custom collection methods" on the objects (different
percents, whatever) you would (could) use list stale and maintain a database
table of parameters to dbms_stats to be used

so you would list stale
iterate over the list and fetch the options
gather table stats for that object
Rating: 5
One more follow-up March 17, 2003
Reviewer: Bob Rich from IN USA
Tom,
  Okay, I can build a script to read the dba_tab_modifications table and analyze
the tables, but how do I "clean out" the dba_tab_modifications table once the
analyze script runs?  Or, do I only use table monitoring on tables that I am
willing to use estimate_percentage = 100%. I can't find anything on Metalink.
Rating: 5
Granularity April 24, 2003
Reviewer: Matt
Tom,

We have a v large partitioned table, around 1000 Million rows, each period we
add extra partitions an analyze them.

Initially we were doing a
dbms_stats.gather_stats specifying table name and the new partitions with
granularity set to 'ALL' - over time this has started to slow down.

I think we would be better changing this to 'PARTITION' and then updating the
global table stats once at the end, is this correct ? What is the best method
for gathering the global table stats, is there anyway we can get it to use the
already analyzed partition stats, or will it do a scan ?
Followup:
in order to gather global stats -- the data as a whole must be inspected (an
estimate will help there, maybe parallel as well).

We cannot correctly determine data skew and distributions from the individual
partition stats, we need to "globally" look at the table.

Now, if your queries all do partition pruning down to a single partition -- you
do not really need global stats...
Rating: 4
Tom, what's going on here ? July 15, 2003
Reviewer: Fan from PA
(9iR2/LINUX)

Tom, please help, thanks

begin DBMS_STATS.GATHER_SCHEMA_STATS('scott',NULL); end;

ORA-29913: error in executing ODCIEXTTABLEOPEN callout
ORA-29400: data cartridge error
KUP-04040: file client_UNW.bad in UTL_FILE_DIR not found
ORA-06512: at "SYS.DBMS_STATS", line 9136
ORA-06512: at "SYS.DBMS_STATS", line 9616
ORA-06512: at "SYS.DBMS_STATS", line 9800
ORA-06512: at "SYS.DBMS_STATS", line 9854
ORA-06512: at "SYS.DBMS_STATS", line 9831
ORA-06512: at line 1
Followup:
scott has an external table

the exteneral file is "bogus", doesn't work, gives an error when you query it.

either

a) fix it
b) skip it
c) drop it

query

USER_EXTERNAL_TABLES
Rating: 5
how does estimate statistics work? July 23, 2003
Reviewer: A reader
Hi

I would like to know how estimate statistics work? If I do

analyze table mytable estimate statistics sample 2000 rows

and myemp has 1000000 rows then how does Oracle estimate?
Followup:
by pulling a random sample of about 2000 rows from the table and using that as
the basis for the statistics.

You can see it looks at pretty much the entire thing to get this random sample
of rows via:

ops$tkyte@ORA920LAP> create table t1 as select * from big_table.big_table where
rownum <= 10000;

Table created.

ops$tkyte@ORA920LAP> create table t2 as select * from big_table.big_table where
rownum <= 50000;

Table created.

ops$tkyte@ORA920LAP> create table t3 as select * from big_table.big_table where
rownum <= 100000;

Table created.

ops$tkyte@ORA920LAP> create table t4 as select * from big_table.big_table where
rownum <= 500000;

Table created.

ops$tkyte@ORA920LAP> create table t5 as select * from big_table.big_table where
rownum <= 1000000;

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA920LAP> analyze table t1 estimate statistics sample 2000 rows;

Table analyzed.

ops$tkyte@ORA920LAP> analyze table t2 estimate statistics sample 2000 rows;

Table analyzed.

ops$tkyte@ORA920LAP> analyze table t3 estimate statistics sample 2000 rows;

Table analyzed.

ops$tkyte@ORA920LAP> analyze table t4 estimate statistics sample 2000 rows;

Table analyzed.

ops$tkyte@ORA920LAP> analyze table t5 estimate statistics sample 2000 rows;

Table analyzed.

analyze table t1 estimate statistics sample 2000 rows

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.08       0.84        139        148          2           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.08       0.84        139        148          2           0
********************************************************************************
analyze table t2 estimate statistics sample 2000 rows

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.13       0.88        734        726          2           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.14       0.88        734        726          2           0
********************************************************************************
analyze table t3 estimate statistics sample 2000 rows

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.20       1.41       1480       1439          3           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.20       1.41       1480       1439          3           0
********************************************************************************
analyze table t4 estimate statistics sample 2000 rows

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.42       3.42       6111       2009          2           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.42       3.42       6111       2009          2           0
********************************************************************************
analyze table t5 estimate statistics sample 2000 rows

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.58       1.88      10322       2009          2           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.59       1.88      10322       2009          2           0

The more data in the table, the more work it takes to get the required number of
rows.

also note the 2000 is "about" 2000:

ops$tkyte@ORA920LAP> select table_name, sample_size from user_tables;

TABLE_NAME                     SAMPLE_SIZE
------------------------------ -----------
T1                                    1724
T2                                    1914
T3                                    1914
T4                                    1983
T5                                    1838
Rating: 4
cool July 23, 2003
Reviewer: robert from PA
but aren't you on vacation ?
so what's all this VPNing :}
Rating: 5
I dont understand July 23, 2003
Reviewer: A reader
Hi

From your last example how does Oracle determine the total number of rows by
just reading roughly 2000 rows? Does it do this: determine how many blocks are
used to store these 2000 rows then get the average rows per block then multiply
that to number of blocks? If so where does it get the number of blocks?
Followup:
not documented, but it certainly knows where the HWM of the table is (it knows
how many blocks might contain rows) -- even the RBO needed that in order to full
scan.
Rating: 5
if HWM is always known July 24, 2003
Reviewer: A reader
Hi

As far as I know we must analyze the tables to get the HWM, if as you said RBO
also needs HWM then how does RBO obtain that data from?
Followup:
you do not need to analyze, the package dbms_space can be used to find this
information out.  it is all on the segment headers.

otherwise, the rbo would NOT be able to full scan!
Rating: 5
What happens if SYS/SYSTEM tables are analyzed also July 25, 2003
Reviewer: Dilbert from San Jose, CA
Hi Tom,

What I understand from your answers is that sys and system accounts should not
be analyzed as they work best with RULE. In one of our instances, I found that
sys and system have been analyzed. Would it have a negative effect on
performance ? I am a pl/sql developer so its not an easy task for me to play
around with the statistics to create a test case and prove :(
Followup:
in 9i -- it is OK and supported.  in 8i it might be an issue (but you would
know, it would be obvious that there is a problem).  in the next release, it'll
be the way to go.
Rating: 5
dbms_stats equivalent of estimate statistics July 25, 2003
Reviewer: Peter Tran from Houston, TX USA
Tom,

I've recommended for our company to convert over to using dbms_stats rather than
analyze table.

One question I got was what's the equivalent of dbms_stats parameter setting to
emulate analyze table estimate statistics runtime?

I tried:

DBMS_STATS.GATHER_TABLE_STATS (
    ownname=>NULL,
    tabname=><table_name>,
    partname=>NULL,
    estimate_percent=>DBMS_STATS.AUTO_SAMPLE_SIZE,
    method_opt=>'FOR ALL INDEXED COLUMNS SIZE AUTO',
    degree=>4,
    cascade=>TRUE);

On a 15 million rows table, the analyze table estimate statistics ran in
seconds, while the above dbms_stats.gather_table_stats() took over 3 minutes!

Are they so different that we should expect dbms_stats to run much longer than
analyze table <table_name> estimate statistics?

Is trying to compare the runtime meaningless, since one (dbms_stats) is much
better than the other?

Thanks,
-Peter
Followup:
3 minutes!  good gosh, so?  really -- if you would like, you can use a fixed
estimate percent just like before.  but hey, 3 minutes on a 15million row table
doesn't sound too bad given the level of stats you are gathering.

you do not give me the command you used to use -- did you cascade there? did you
gather histograms?  what?

are you sure parallel makes sense even -- is the data nicely spread out?
Rating: 5
The gory details July 25, 2003
Reviewer: Peter Tran from Houston, TX USA
SQL> select count(*) from big_table;

  COUNT(*)
----------
  15179090

SQL> analyze table big_table delete statistics;

Table analyzed.

SQL> analyze table big_table estimate statistics;

Table analyzed.

Elapsed: 00:00:02.00

SQL> @check_table

INDEX_NAME                     T_NUM_ROWS I_NUM_ROWS UQ_COLUMNS
------------------------------ ---------- ---------- ----------
big_table_PK                     15195279 14946619.2          5
big_table_IX                     15195279   12665663          5
big_table_UQ                     15195279   12390453          5

SQL> analyze table big_table delete statistics;

Table analyzed.

SQL> BEGIN
  2    DBMS_STATS.GATHER_TABLE_STATS (
  3       ownname=>NULL,tabname=>'big_table',
  4       partname=>NULL,
  5       estimate_percent=>DBMS_STATS.AUTO_SAMPLE_SIZE,
  6       method_opt=>'FOR ALL INDEXED COLUMNS SIZE AUTO',
  7       degree=>4,
  8       cascade=>true);
  9  END;
 10  /

PL/SQL procedure successfully completed.

Elapsed: 00:03:36.09

SQL> @check_table

INDEX_NAME                     T_NUM_ROWS I_NUM_ROWS UQ_COLUMNS
------------------------------ ---------- ---------- ----------
big_table_PK                     15179090   14712725          5
big_table_IX                     15179090   14593628          5
big_table_UQ                     15179090   14705608          5

Where @check_table.sql is:

select i.index_name,
       t.num_rows T_NUM_ROWS,
       i.num_rows I_NUM_ROWS, c.cnt UQ_COLUMNS
from (select num_rows from user_tables where table_name = 'big_table') t,
     (select index_name, num_rows from user_indexes where table_name =
'big_table' ) i,
     (select count(distinct column_name) cnt from user_tab_histograms where
table_name = 'big_table' ) c
/

I guess it's just a shock to the developers when they first start using
dbms_stat and realize that they're not going to get response time in the
seconds.

I thought the degree parameter is used to specify how much parallism you can use
while gathering the statistics.  I noticed that if I leave out the degree=>4
part, it takes double the time to execute the dbms_stats.

In the first version, I forgot to specify the estimate_percent, so it defaulted
to compute.  The job ran for more than 30 minutes.  I finally had to kill it.

-Peter
Followup:
you are if you check out the sample size, you'll find you were analyzing about
1064 rows with that estimate.  You might have been lucky, you might have the
most well behaved data on the planet whereby 1064 rows from 15million is
representative enough to represent your data.

In most cases, it will not be.  you can obviously set the estimate size and such
so as to "correct" this, to make the estimate go faster if you like.
Rating: 5
9i July 29, 2003
Reviewer: Tatiane
>in 9i -- it is OK and supported.  in 8i it might be an >issue (but you would
>know, it would be obvious that there is a problem).  in >the next release,
it'll
>be the way to go.

You mean in 9i (and beyond) could I simply do (without problems with sys or
system):

DBMS_STATS.GATHER_DATABASE_STATS (
   estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
   method_opt =>       'FOR ALL INDEXED COLUMNS SIZE AUTO',
   cascade =>          TRUE
);

?
Followup:
after TESTING in your TEST system, running your applications in TEST, yes.

although you might consider table monitoring.... and gather stale...

remember your data dictionary is not very volitile, it need not be analyzed
frequently.

so, gather database stats, not sure, schema stats -- yes.
Rating: 4
statistics connected as SYS August 01, 2003
Reviewer: Juancarlosreyesp@yahoo.com from Bolivia
Hi, Tom, which is your opinion about
connect as SYS to gather full statistics
with the following command.
begin
 DBMS_STATS.GATHER_DATABASE_STATS();
end;

I am almost completely sure I'm wrong, which privileges do you suggest, to
execute them, only DBA?
Followup:
i would prefer to gather them schema by schema using the gather stale option
instead of "gather everything".

have each schema gather its own stats on its own schedule.
Rating: 5
August 01, 2003
Reviewer: A reader
Thank you :)
Rating: 5
estimate vs compute August 15, 2003
Reviewer: Reader
Tom, If I use estimate statistics with some % in analyze table command, is there
anything like 25% or 30% for estimate that will do the analysis just the same if
I used compute statistics option. In other words, is there any optimum % oracle
recommends for estimate that would do the same as compute so that for really
"huge" tables we don't have to use compute option.

Also, analyze table t compute statistics command by default analyzes the table
as well for all the columns and indexes associated with it. Thanks.
Followup:
if there were an "optimum", that would be the only setting ;)

anything at 49 or above is COMPUTE.

anything below tries to do what you ask.  In many cases, given a large enough
evenly distributed set of data 10% can do it.  the more skewed the data, the
larger the sample required to get a feel for the degree of skewedness.

A simple test like this can show you what is getting gathered with what
commands:

ops$tkyte@ORA920> create table t ( x int primary key );

Table created.

ops$tkyte@ORA920> insert into t values(1);

1 row created.

ops$tkyte@ORA920> commit;

Commit complete.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select 'table', decode( num_rows, null, 'nope', 'yep' ) from
user_tables where table_name = 'T'
  2  union all
  3  select 'index', decode( num_rows, null, 'nope', 'yep' ) from user_indexes
where table_name = 'T'
  4  union all
  5  select 'histograms', decode( count(*), 0, 'nope', 'yep' ) from
user_tab_histograms where table_name = 'T' and rownum = 1
  6  /

'TABLE'    DECO
---------- ----
table      nope
index      nope
histograms nope

ops$tkyte@ORA920> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA920> select 'table', decode( num_rows, null, 'nope', 'yep' ) from
user_tables where table_name = 'T'
  2  union all
  3  select 'index', decode( num_rows, null, 'nope', 'yep' ) from user_indexes
where table_name = 'T'
  4  union all
  5  select 'histograms', decode( count(*), 0, 'nope', 'yep' ) from
user_tab_histograms where table_name = 'T' and rownum = 1
  6  /

'TABLE'    DECO
---------- ----
table      yep
index      yep
histograms yep

ops$tkyte@ORA920> analyze table t delete statistics;

Table analyzed.

ops$tkyte@ORA920> analyze table t compute statistics for table;

Table analyzed.

ops$tkyte@ORA920> select 'table', decode( num_rows, null, 'nope', 'yep' ) from
user_tables where table_name = 'T'
  2  union all
  3  select 'index', decode( num_rows, null, 'nope', 'yep' ) from user_indexes
where table_name = 'T'
  4  union all
  5  select 'histograms', decode( count(*), 0, 'nope', 'yep' ) from
user_tab_histograms where table_name = 'T' and rownum = 1
  6  /

'TABLE'    DECO
---------- ----
table      yep
index      nope
histograms nope

ops$tkyte@ORA920> analyze table t delete statistics;

Table analyzed.

ops$tkyte@ORA920> analyze table t compute statistics for all indexes;

Table analyzed.

ops$tkyte@ORA920> select 'table', decode( num_rows, null, 'nope', 'yep' ) from
user_tables where table_name = 'T'
  2  union all
  3  select 'index', decode( num_rows, null, 'nope', 'yep' ) from user_indexes
where table_name = 'T'
  4  union all
  5  select 'histograms', decode( count(*), 0, 'nope', 'yep' ) from
user_tab_histograms where table_name = 'T' and rownum = 1
  6  /

'TABLE'    DECO
---------- ----
table      nope
index      yep
histograms nope
Rating: 5
Very helpful. Thanks much. August 15, 2003
Reviewer: reader
Rating: 5
dbms_stats does it different .... August 15, 2003
Reviewer: reader
Tom, I did the above test with dbms_stats package as follows, and could please
sugest how do i get my index analyzed also. A is the table t in your above demo
with primary key defined.

SQL> exec dbms_stats.gather_table_stats('DEMO','A');

PL/SQL procedure successfully completed.

SQL> select 'table', decode( num_rows, null, 'NO', 'YES' ) from
  2  dba_tables where table_name = 'A'
  3    union all
  4  select 'index', decode( num_rows, null, 'NO', 'YES' ) from dba_indexes
  5  where table_name = 'A'
  6    union all
  7  select 'histograms', decode( count(*), 0, 'NO', 'YES' ) from
  8  dba_tab_histograms where table_name = 'A' and rownum = 1
  9  /

'TABLE'    DEC
---------- ---
table      YES
index      NO
histograms YES

SQL> analyze table a delete statistics;

Table analyzed.

SQL> select 'table', decode( num_rows, null, 'NO', 'YES' ) from
  2  dba_tables where table_name = 'A'
  3    union all
  4  select 'index', decode( num_rows, null, 'NO', 'YES' ) from dba_indexes
  5  where table_name = 'A'
  6    union all
  7  select 'histograms', decode( count(*), 0, 'NO', 'YES' ) from
  8  dba_tab_histograms where table_name = 'A' and rownum = 1
  9  /

'TABLE'    DEC
---------- ---
table      NO
index      NO
histograms NO

SQL> exec dbms_stats.gather_table_stats('DEMO','A', METHOD_OPT => 'FOR ALL
INDEXED cOLUMNS');

PL/SQL procedure successfully completed.

SQL> select 'table', decode( num_rows, null, 'NO', 'YES' ) from
  2  dba_tables where table_name = 'A'
  3    union all
  4  select 'index', decode( num_rows, null, 'NO', 'YES' ) from dba_indexes
  5  where table_name = 'A'
  6    union all
  7  select 'histograms', decode( count(*), 0, 'NO', 'YES' ) from
  8  dba_tab_histograms where table_name = 'A' and rownum = 1
  9  /

'TABLE'    DEC
---------- ---
table      YES
index      NO
histograms YES
Rating: 5
guess I have to use gather_index_stats proc? August 15, 2003
Reviewer: reader
Rating: 5
Figured it out by reading fully this thread August 15, 2003
Reviewer: reader
I should use cascade option. Tom, you are great!

SQL> exec dbms_stats.gather_table_stats('DEMO','A', CASCADE => TRUE);

PL/SQL procedure successfully completed.

SQL> select 'table', decode( num_rows, null, 'NO', 'YES' ) from
  2  dba_tables where table_name = 'A'
  3    union all
  4  select 'index', decode( num_rows, null, 'NO', 'YES' ) from dba_indexes
  5  where table_name = 'A'
  6    union all
  7  select 'histograms', decode( count(*), 0, 'NO', 'YES' ) from
  8  dba_tab_histograms where table_name = 'A' and rownum = 1
  9  /

'TABLE'    DEC
---------- ---
table      YES
index      YES
histograms YES
Rating: 4
difference between dbms_stats and analyze August 15, 2003
Reviewer: Ramu from Cincinnati
Other than analyzing using parallel option what are the advantages in using
dbms_stats package in analyzing tables and indexes?

When I use either analyze table command or dbms_stats.gather_table_stats
procedure, indexes associated with the table also get analyzed? If so, why there
is another command analyze index and another procedure
dbms_stats.gather_index_stats to analyze indexes? Thanks.
Followup:
analyze table T compute statistics

is much like

dbms_stats.gather_table_stats( user, 'T', method_opt=> 'for all columns',
cascade=>true);

analyze index i compute statistics

is much like

dbms_stats.gather_index_stats

and so on.  dbms_stats.gather_schema_stats -- well, that has no corresponding
equal in analyze.

you cannot export/import stats with analyze

you cannot gather system stats with analyze

you cannot set stats with analyze

you cannot gather STALE <<<=== big reason, huge reason >>> with analyze

it is the stated direction (dbms_stats is)
Rating: 5
stale? August 16, 2003
Reviewer: A Reader
<quote>you cannot gather STALE<quote>

Tom, what does stale do? Thanks.
Followup:
oracle can monitor tables.

you only need to gather stats on tables where the data changed enough to warrant
it.

dbms-stats can use this monitoring data to decide what tables are "stale" and in
need of gathering.

so, some nights, dbms_stats might gather stats on ZERO tables, others on all,
others on some -- the amount of work it does is proportional to the amount of
work it needs to do.

in general, some tables, but not all, will be stale -- so every night,
dbms_stats will gather stats on some subset of your tables, not all of them.

analyze does everything, regardless of whether it needs it or not.
Rating: 4
great explanation - how about new tables September 17, 2003
Reviewer: Sravan from Northampton, MA
Tom,
I am monitoring all the schema objects and using the stale option with "gather
AUTO" for dbms_stats job. They are running fine now on the existing tables.

How about the new tables which gets added to the database.
1) Does the job automatically recognizes the new table?
2) Will the table be monitored?( picks up automatically ? Or should I set a job
to run ALTER_SCHEMA_TAB_MONITORING frequently to add it the list ?)
3) Will gather_schema_stats recognises the new tables and gathers the statistics
?

=================================================
Monitoring of the tables is done using:

exec dbms_stats.ALTER_SCHEMA_TAB_MONITORING('SCOTT',TRUE);
================================================
dbms_stats definition looks like(runs 3 times a week):

exec dbms_stats.gather_schema_stats(
     ownname          => 'SCOTT',
     options          => 'GATHER AUTO',
     estimate_percent => dbms_stats.auto_sample_size,
     method_opt       => 'FOR ALL INDEXED COLUMNS SIZE 1'
     degree           => 3
   )
=================================================
Followup:
it'll get the new tables...
it will not be monitored
you might add the alter schema tab monitoring to the script...

ops$tkyte@ORA920> @connect a/a
ops$tkyte@ORA920> set termout off

a@ORA920> set termout on
a@ORA920>
a@ORA920> create table t1 ( x int );

Table created.

a@ORA920>
a@ORA920> exec dbms_stats.ALTER_SCHEMA_TAB_MONITORING('A',TRUE);

PL/SQL procedure successfully completed.

a@ORA920>
a@ORA920> create table t2 ( x int );

Table created.

a@ORA920>
a@ORA920> select table_name, monitoring from user_tables;

TABLE_NAME                     MON
------------------------------ ---
T1                             YES
T2                             NO

a@ORA920>
a@ORA920>
a@ORA920> begin dbms_stats.gather_schema_stats(
  2       ownname          => user,
  3       options          => 'GATHER AUTO',
  4       estimate_percent => dbms_stats.auto_sample_size,
  5       method_opt       => 'FOR ALL INDEXED COLUMNS SIZE 1',
  6       degree           => 3
  7     );
  8  end;
  9  /

PL/SQL procedure successfully completed.

a@ORA920>
a@ORA920> create table t3 ( x int );

Table created.

a@ORA920> select table_name, monitoring, num_rows from user_tables;

TABLE_NAME                     MON   NUM_ROWS
------------------------------ --- ----------
T1                             YES          0
T2                             NO           0
T3                             NO

stats on both tables -- no monitoring tho on t2 and t3

a@ORA920>
a@ORA920> begin dbms_stats.gather_schema_stats(
  2       ownname          => user,
  3       options          => 'GATHER AUTO',
  4       estimate_percent => dbms_stats.auto_sample_size,
  5       method_opt       => 'FOR ALL INDEXED COLUMNS SIZE 1',
  6       degree           => 3
  7     );
  8  end;
  9  /

PL/SQL procedure successfully completed.

a@ORA920>
a@ORA920> select table_name, monitoring, num_rows from user_tables;

TABLE_NAME                     MON   NUM_ROWS
------------------------------ --- ----------
T1                             YES          0
T2                             NO           0
T3                             NO           0

shows it picks up all tables -- but no monitoring

a@ORA920> exec dbms_stats.ALTER_SCHEMA_TAB_MONITORING('A',TRUE);

PL/SQL procedure successfully completed.

a@ORA920> select table_name, monitoring, num_rows from user_tables;

TABLE_NAME                     MON   NUM_ROWS
------------------------------ --- ----------
T1                             YES          0
T2                             YES          0
T3                             YES          0
Rating: 5
new tables - cleared September 18, 2003
Reviewer: A reader
Tom, great illustration. Thanks for your help.
Sravan
Rating: 5
Spendid info ! October 28, 2003
Reviewer: horace from UK
Thanks for the excellent tutorial

We have tables where all the columns are indexed.  We use dbms_stats with
cascade=true to analyze the tables and indexes.

The default for method_opt is 'for all columns size 1' with the other options
being.....

o for all columns
o for all indexed columns
o for all hidden columns
o for columns <list>

Is there a way of analyzing the table and indexes without gathering
histographical stats using dbms_stats, i.e., the equivalent of 'analyze table
table_name estimate statistics;'
Followup:
method_opt => null
Rating: 3
October 29, 2003
Reviewer: Dillip from CA USA
Hi Tom,

If I collect the stats using the following command, is there something missing
for the CBO to generate a good execution plan ? Or what is the best command to
collect the stats of a table (including indexes, histograms) so that the
optmizer gets all the information that is needed.

execute dbms_stats.gather_table_stats(ownname => 'TEST',
                      tabname => 'TEST',
                      method_opt => 'FOR ALL INDEXED COLUMNS',
                      granularity => 'ALL',
                      cascade => TRUE,
                      degree => DBMS_STATS.DEFAULT_DEGREE)
Followup:
that is a good "general purpose" one.  many times the histograms are in fact not
necessary -- that depends on your data and queries.
Rating: 4
DBMS_STATS was very useful for me..Thanks November 07, 2003
Reviewer: M S Reddy from USA.
Tom
  what happens if we run an explain plan while we are running dbms_stats to
gatheer a table statistics.i can see that the plan changes even before the
procedure is completed.does this do any harm or effect my gathered statistics on
that table.what happens if i run a simple select count(*) on that table while we
are gathering stats.
i expect that this should not cause any harm,but just wanted to confirm it from
you.
Thanks,
M S Reddy.
Followup:
stats are consulted to generate the plan, then they are not used.  so, it is
really only during parse that anything happens here -- the parse will read
whatever is in the data dictionary at that point in time.

no harm.
Rating: 5
December 15, 2003
Reviewer: A reader
Rating: 5
SKEWONLY usage December 16, 2003
Reviewer: A reader
Hi Tom, recently i received a tip from Burleston
at the end shows

--*******************************************************
-- SKEWONLY option ? Detailed analysis
--
-- Use this method for a first-time analysis
-- for skewed indexes
-- This runs a long time because all indexes are examined
--*******************************************************

begin
   dbms_stats.gather_schema_stats(
      ownname          => 'SCOTT',
      estimate_percent => dbms_stats.auto_sample_size,
      method_opt       => 'for all columns size skewonly',
      degree           => 7
   );
end;
/

My question is
If I'm using bind variables and get that kind of statistics
in theory I'm not going to get benefit from histogram
statistics
Or I'm wrong and this information helps the optimizer in some way?
Followup:
there is bind variable peeking that happens

there will be times you have constants.

so yes, they can and do come into play.

and it is funny his comment says "indexes" -- but this call doesn't even gather
stats on indexes, cascade defaults to false -- the for all columns is about the
table columns, not indexed columns..  it is not just looking at indexed columns
at all (which is OK, just the comment is muffed up)

using SIZE SKEWONLY  means that when you collect histograms with the SIZE option
set to SKEWONLY, we collect histogram data in memory for all specified columns
(if you do not specify any, all columns are used). Once an "in-memory" histogram
is computed for a column, it is stored inside the data dictionary only if it has

"popular" values (multiple end-points with the same value which is what we
define by "there is skew in the data")
http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:5792247321358
Rating: 5
More information December 17, 2003
Reviewer: A reader
Thanks Tom
Sorry I should had gave you the link
This is the tip
http://www.dba-oracle.com/oracle_tips_skewonly.htm
    Something funny is that you Burleston has in this page
http://www.dba-oracle.com/articles.htm#tips
    the tips untils january 24 2004 ;)

Let me clear my question, it was
Even if I only use bind variables (select a from b where c=:1), not
constants(select a from b where c='hi').
As I understand, maybe wrongly, the additional information got with skewonly
helps to the optimizer.
I'm right or not?.

Or the question in other way.
If I don't use constant values in the where clauses, required to get advantage
of histograms, will I benefit of using skewonly as is explained in the article?

Thanks Tom :)
Followup:
BIND VARAIBLE PEEKING -- yes, histograms are relevant even with binds.
Rating: 5
December 18, 2003
Reviewer: A reader
Thanks a lot tom :)
Rating: 5
a quote December 18, 2003
Reviewer: A reader
If you don't have any idea what is bind variable peeking,

"In summary, bind variable peeking in Oracle 9i does not help us when we need
different execution plans for different values of a bind variable. Bind variable
peeking allows the optimizer to take the value of a bind variable into account
when first generating an execution plan for a SQL statement. If we know that the
data distribution is heavily skewed and that future executions of the SQL
statement may involve different data distributions for the bind variable value,
we must still resort to using literals to get the best performance."

from here
http://www.dbspecialists.com/specialists/specialist2003-11.html
:)
Rating: 4
December 29, 2003
Reviewer: Dilllip from CA USA
Tom,

With the following command the column statisticts (NUM_DISTINCT,NUM_NULLS and
DENSITY) for the non-indexes columns are not collected, is there a way to have
the column statistics for all the columns and histograms for the indexed columns
in a single dbms_stats command ?

execute dbms_stats.gather_table_stats(ownname => 'TEST', tabname => 'TEST',
method_opt => 'FOR ALL INDEXED COLUMNS', granularity => 'ALL', cascade => TRUE,
degree => DBMS_STATS.DEFAULT_DEGREE)
Followup:
those are gathered as part of histogram processing -- column statistics &
histograms are sort of synonymous here.
Rating: 4
December 30, 2003
Reviewer: Dillip from CA USA
I am not clear how they are synonymous. Can you pls explain ? Pls see the test
case I have provided, I do not see any stats for non-indexed columns as method
of stat collection is only indexed columns, when I collect stats using the
analyze command, I see stats for all the columns for the table and then I
collect histograms for the indexes columns separately.

SQL> create table test as select * from dba_objects;

Table created.

SQL> create index test_idx on test(owner, object_name);

Index created.

SQL> create index test_idx1 on test(created);

Index created.

SQL> execute dbms_stats.gather_table_stats(ownname => 'ORAPERF', tabname =>
'TEST', method_opt => 'FOR ALL INDEXED COLUMNS', granularity => 'ALL', cascade
=> TRUE, degree => DBMS_STATS.DEFAULT_DEGREE);

PL/SQL procedure successfully completed.

SQL> @stat test

Table ....

TABNAME              LAST_ANALYZED         NUM_ROWS     BLOCKS AVG_ROW_LEN
SAMPLE_SIZE
-------------------- ------------------- ---------- ---------- -----------
-----------
TEST                 12-30-2003 18:55:36      23429        301         100
23429

Columns(s) ....

COLUMN_NAME                    LAST_ANALYZED       NUM_DISTINCT  NUM_NULLS
  DENSITY NUM_BUCKETS
------------------------------ ------------------- ------------ ----------
--------------- -----------
OWNER                          12-30-2003 18:55:36            5          0
.00002134           4
OBJECT_NAME                    12-30-2003 18:55:36        22055          0
.00004851          75
SUBOBJECT_NAME
OBJECT_ID
DATA_OBJECT_ID
OBJECT_TYPE
CREATED                        12-30-2003 18:55:36         3257          0
.00050874          75
LAST_DDL_TIME
TIMESTAMP
STATUS
TEMPORARY
GENERATED
SECONDARY

Index(s) ....

INDNAME            LAST_ANALYZED            LFBLK      DKEYS    ALVLBLK
AVGDBLK         CF     BLEVEL
------------------ ------------------- ---------- ---------- ----------
---------- ---------- ----------
TEST_IDX           12-30-2003 18:55:41        113      23062          1
1        414          1
                                                                      1
1
TEST_IDX1          12-30-2003 18:55:42         63       3257          1
1       3897          1
Followup:
you don't show your analyze, but analyze by default gets histograms:

ops$tkyte@ORA9IR2> create table t as select * from scott.emp;

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select count(*) from user_tab_histograms where table_name =
'T';

  COUNT(*)
----------
        16

so, your "collection of them after the fact" isn't useful.  they are already
there, for all columns.
Rating: 5
December 30, 2003
Reviewer: Dillip from CA USA
Pls see this, the two entries with end point as 0, 1 are collected by default.

Pls see below and comment

SQL> analyze table test delete statistics;

Table analyzed.

SQL> analyze table test compute statistics;

Table analyzed.

SQL> @stat test

Table ....

TABNAME              LAST_ANALYZED         NUM_ROWS     BLOCKS AVG_ROW_LEN
SAMPLE_SIZE
-------------------- ------------------- ---------- ---------- -----------
-----------
TEST                 12-30-2003 20:57:31      23429        301          90
23429

Columns(s) ....

COLUMN_NAME                    LAST_ANALYZED       NUM_DISTINCT  NUM_NULLS
  DENSITY NUM
------------------------------ ------------------- ------------ ----------
--------------- ---
OWNER                          12-30-2003 20:57:31            5          0
.20000000
OBJECT_NAME                    12-30-2003 20:57:31        22055          0
.00004534
SUBOBJECT_NAME                 12-30-2003 20:57:31            2      23378
.50000000
OBJECT_ID                      12-30-2003 20:57:31        23429          0
.00004268
DATA_OBJECT_ID                 12-30-2003 20:57:31        18441       4952
.00005423
OBJECT_TYPE                    12-30-2003 20:57:31           22          0
.04545455
CREATED                        12-30-2003 20:57:31         3257          0
.00030703
LAST_DDL_TIME                  12-30-2003 20:57:31         2110          0
.00047393
TIMESTAMP                      12-30-2003 20:57:31         3471          0
.00028810
STATUS                         12-30-2003 20:57:31            1          0
1.00000000
TEMPORARY                      12-30-2003 20:57:31            2          0
.50000000
GENERATED                      12-30-2003 20:57:31            2          0
.50000000
SECONDARY                      12-30-2003 20:57:31            1          0
1.00000000

Index(s) ....

INDNAME            LAST_ANALYZED            LFBLK      DKEYS    ALVLBLK
AVGDBLK         CF
------------------ ------------------- ---------- ---------- ----------
---------- ----------
TEST_IDX           12-30-2003 20:57:31        113      23062          1
1        305
                                                                      1
1
TEST_IDX1          12-30-2003 20:57:31         63       3257          1
1       3897
SQL> @hist test.object_id

Histograms ....

ENDPOINT_NUMBER ENDPOINT_VALUE
--------------- --------------
              0              2
              1         187780
SQL> analyze table test compute statistics for columns object_id;

Table analyzed.

SQL> @hist test.object_id

Histograms ....

ENDPOINT_NUMBER ENDPOINT_VALUE
--------------- --------------
              0              2
              1            325
              2            652
              3            967
              4           1280
              5           1593
              6           1906
              7           2221
              8           2534
              9           2847
             10           3167
             11           3489
             12           3823
             13           4144
             14           4916
             15           5249
             16           5569
             17           5882
             18           7895
             19           8213
             20           8526
             21           8839
             22           9156
             23          14043
             24          14501
             25          14875
             26          15206
             27          15576
             28          15913
             29          16234
             30          16549
             31          16881
             32          17360
             33          17744
             34          18147
             35          18546
             36          19003
             37          19395
             38          19726
             39          20053
             40          20373
             41          20808
             42          21160
             43          21492
             44          21867
             45          22207
             46          22642
             47          23052
             48          23522
             49          23972
             50          52147
             51          82430
             52         102176
             53         109848
             54         128984
             55         141920
             56         181841
             57         182154
             58         182467
             59         182780
             60         183093
             61         183406
             62         183719
             63         184032
             64         184345
             65         184658
             66         184971
             67         185284
             68         185597
             69         185910
             70         186223
             71         186536
             72         186849
             73         187162
             74         187475
             75         187780
Followup:
so -- you HAD histograms, the analyze command gathered histograms for all
columns.  dbms_stats can do that as well -- but that goes against your stated
desire, hence my point that "you cannot get there from here -- to get the data
you are asking for requires histograms!"
Rating: 5
December 30, 2003
Reviewer: Dillip from CA USA
Let me rephrase my question then.

I want to collect default histograms (two values) for
all columns and histograms for all indexes columns (75 buckets).

Can you let me know the dbms_stats command to do that ?
Followup:
it takes two of them.  just like with analyze.

ops$tkyte@ORA9IR2> create table t as select * from scott.emp;

Table created.

ops$tkyte@ORA9IR2> create index empno_idx on t(empno);

Index created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec dbms_stats.gather_table_stats( user, 'T', cascade=>TRUE
);

PL/SQL procedure successfully completed.

that is just like "analyze table compute statistics"

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select column_name, num_distinct
  2    from user_tab_columns
  3   where table_name = 'T'
  4  /

COLUMN_NAME                    NUM_DISTINCT
------------------------------ ------------
EMPNO                                    14
ENAME                                    14
JOB                                       5
MGR                                       6
HIREDATE                                 13
SAL                                      12
COMM                                      4
DEPTNO                                    3

8 rows selected.

ops$tkyte@ORA9IR2> select column_name, count(*)
  2    from user_tab_histograms
  3   where table_name = 'T'
  4   group by column_name
  5  /

COLUMN_NAME                      COUNT(*)
------------------------------ ----------
COMM                                    2
DEPTNO                                  2
EMPNO                                   2
ENAME                                   2
HIREDATE                                2
JOB                                     2
MGR                                     2
SAL                                     2

8 rows selected.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec dbms_stats.gather_table_stats( user, 'T', method_opt =>
'for all indexed columns size 75' );

PL/SQL procedure successfully completed.

and those are you "special" histograms

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select column_name, num_distinct
  2    from user_tab_columns
  3   where table_name = 'T'
  4  /

COLUMN_NAME                    NUM_DISTINCT
------------------------------ ------------
EMPNO                                    14
ENAME                                    14
JOB                                       5
MGR                                       6
HIREDATE                                 13
SAL                                      12
COMM                                      4
DEPTNO                                    3

8 rows selected.

ops$tkyte@ORA9IR2> select column_name, count(*)
  2    from user_tab_histograms
  3   where table_name = 'T'
  4   group by column_name
  5  /

COLUMN_NAME                      COUNT(*)
------------------------------ ----------
COMM                                    2
DEPTNO                                  2
EMPNO                                  14
ENAME                                   2
HIREDATE                                2
JOB                                     2
MGR                                     2
SAL                                     2

8 rows selected.
Rating: 5
December 30, 2003
Reviewer: Dillip from CA USA
Tom, Thanks a lot. So there is no single command to do this
in dbms_stats (that was my initial question).

Thanks again for the clarification.
December 30, 2003 Neric -- Thanks for the question regarding "unix time to oracle time", version 8.1.7
You Asked

http://osi.oracle.com/~tkyte/Misc/CTime.html
I have questions regarded the solution posed

so I must specific the time zone, but the time zone can be varied during the
time of the year such as PDT/PST ?

So is there a way that it can figure it out by itsself in a clean way

or I need to do a IF THEN ELSE thing to figure that out
and we said...
Well, in 817 you can use a small java stored procedure to get the timezone on
the server.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace and compile java source
named "demo"
  2  as
  3  import java.net.*;
  4  import java.io.*;
  5  import java.util.*;
  6  import java.text.*;
  7
  8  public class demo
  9  {
 10
 11  static public void java_get_timezone( String[] p_timezone )
 12  {
 13  Date d = new Date();
 14  DateFormat df2 = new SimpleDateFormat( "z" );
 15
 16          df2.setTimeZone( TimeZone.getDefault() );
 17          p_timezone[0] = df2.format(d);
 18  }
 19
 20  }
 21  /

Java created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package tz
  2  as
  3      procedure get_timezone( p_timezone   out varchar2 )
  4          as language java
  5          name 'demo.java_get_timezone( java.lang.String[] )';
  6
  7          function get_tz return varchar2;
  8  end;
  9  /

Package created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace package body tz
  2  as
  3
  4  g_tz varchar2(25);
  5
  6  function get_tz return varchar2
  7  as
  8  begin
  9          if g_tz is null then get_timezone(g_tz); end if;
 10          return g_tz;
 11  end;
 12
 13  end;
 14  /

Package body created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select tz.get_tz from dual;

GET_TZ
---------------------------------------------------------------------------------
--------------------------------------------------
EDT

In 9i, timezone support is native.
Review & Followup

Rating: 5
native September 16, 2001
Reviewer: Chen from USA
What do you mean by time zone support is native, what is the other kind of
support.
Followup:
In order to do timezones in 8i and before, you have to do it yourself.  You can
use the NEW_TIME builtin but you must not the timezone you are in and so on.
The DATE datatype has no timezone component.

In 9i there is a datatype "timestamp with timezone".  The timestamp can store
the date, time (down to 9 decimal places with seconds), and the TIMEZONE.  The
database "understands" timezones so if you get the difference between 1pm PST
and 4pm EST it understands that it is in fact the same time.  And so on.

In short, 9i supports and is timezone aware.  8i and before -- not so.
Rating: 4
October 02, 2001
Reviewer: A reader from USA
Rating: 5
August 24, 2003
Reviewer: A reader
Hi Tom,

 We have 4 production Oracle instance on one box.One of the instance we need to
set Time zone to UTC (GMT..). I came to know there is Solaris envt. variable
called $TZ which help us to do this. How this variable works in solaris?
Time zone set by this variable is session level?

Please elobarate and advice.

Thanks in advance
Followup:
the TZ environment variable is picked up by the dedicated or shared server.
these guys inherit their environment from whomever starts them.

So, as long as

a) the listener has tz set
b) the guy who starts the database has tz set
c) everyone connects via the listener

you'll be OK.  direct connections (no listener) will fork and exec their own
dedicated servers -- so they will get the TZ of the client.
Rating: 5
August 24, 2003
Reviewer: A reader
Hi Tom,
Thnaks for the reply

You mention

a) the listener has tz set
b) the guy who starts the database has tz set

Could you please explain above statements ?

Thnaks
Followup:
a) when you start the listener, make sure you've done something like:

export TZ=GMT
lsnrctl start

make sure the listeners environment has the TZ you desire to use

b) see (a), change listener to database.
Rating: 5
August 24, 2003
Reviewer: A reader
Hi Tom,

How can we set Timezone for Listener Process?

Thnaks
Followup:
by setting the environment, like you would for anything on unix
Rating: 5
August 25, 2003
Reviewer: A reader
Hi Tom

Here is our Scenerio.

 We have our prod DB server on EST timezone. We set one Oracle instance on GMT
by seeting $TZ unix envt. variable.
I restarted oracle instance and ran below query.

SQL> select sessiontimezone,dbtimezone from dual;

SESSION DBTIMEZ
------- -------
-04:00  +00:00

SQL> select sysdate from dual;

SYSDATE
--------------------
25-AUG-2003 09:29:54

-- --------------------
if i run  this (select sysdate from dual)I want SYSDATE in GMT.  I am not
geeting in GMT eventhogh I've set $TZ for the instacne to GMT.

Please advice

Thnaks in advance
Followup:
umm, change your session timezone perhaps?

given I have no clue what time gmt was when you did this -- really, I cannot
tell if sysdate is right, wrong, made up or what.

I've no idea what steps you went through here.

maybe you can cut and paste something like this:

[ora920@localhost ora920]$ export TZ=GMT
[ora920@localhost ora920]$ date
Mon Aug 25 09:55:17 EDT 2003

[ora920@localhost ora920]$ sysdba

SQL*Plus: Release 9.2.0.3.0 - Production on Mon Aug 25 13:55:22 2003

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to an idle instance.

idle> startup
ORACLE instance started.

Total System Global Area  168890964 bytes
Fixed Size                   451156 bytes
Variable Size             150994944 bytes
Database Buffers           16777216 bytes
Redo Buffers                 667648 bytes
Database mounted.
Database opened.

idle> @bigdate

Session altered.

idle> select sysdate from dual;

SYSDATE
--------------------
25-aug-2003 13:55:42

worked dandy for me.
Rating: 5
August 25, 2003
Reviewer: A reader
Hi Tom,

 Thnaks for the reply.

  How to change session time zone ?We are using oracle 8.1.7.3.

Thanks
Followup:
you do not in 8i, did not exist until 9i.

i had to assume from the prior reviews that the db was 9i as it was using the
new timezone functions for 9i.

in 8i, the TZ environment variable does it all.
Rating: 5
Here's how you can do it in PL/SQL December 30, 2003
Reviewer: Jim Nasby from Austin, TX
CREATE OR REPLACE PACKAGE time AS
    c_epoch_tz CONSTANT timestamp with time zone := to_timestamp_tz('1970-01-01
GMT', 'YYYY-MM-DD TZD');
    c_epoch CONSTANT timestamp := to_timestamp('1970-01-01', 'YYYY-MM-DD');

    FUNCTION t_s(time_in timestamp with time zone) RETURN int;
    FUNCTION t_ms(time_in timestamp with time zone) RETURN number;

    FUNCTION s_t(time_in int) RETURN timestamp;
    FUNCTION ms_t(time_in number) RETURN timestamp;

    FUNCTION s_tz(time_in int) RETURN timestamp with time zone;
    FUNCTION ms_tz(time_in number) RETURN timestamp with time zone;

    FUNCTION epoch RETURN timestamp;
    FUNCTION epoch_tz RETURN timestamp with time zone;
END time;
/
show err

CREATE OR REPLACE PACKAGE BODY time AS
    FUNCTION t_s(time_in timestamp with time zone) RETURN int
    AS
        diff interval day(9) to second(0) := time_in at time zone 'GMT' -
c_epoch_tz;
    BEGIN
        RETURN extract(day from diff) * 86400 + extract(hour from diff) * 3600 +
extract(second from diff);
    END;

    FUNCTION t_ms(time_in timestamp with time zone) RETURN number
    AS
        diff interval day(9) to second(0) := time_in at time zone 'GMT' -
c_epoch_tz;
    BEGIN
        RETURN extract(day from diff) * 86400000 + extract(hour from diff) *
3600000 + extract(second from diff) * 1000;
    END;

    FUNCTION s_t(time_in int) RETURN timestamp AS
    BEGIN
        RETURN c_epoch + time_in/86400;
    END;
    FUNCTION ms_t(time_in number) RETURN timestamp AS
    BEGIN
        RETURN c_epoch + time_in/86400000;
    END;

    FUNCTION s_tz(time_in int) RETURN timestamp with time zone AS
    BEGIN
        RETURN c_epoch_tz + time_in/86400;
    END;
    FUNCTION ms_tz(time_in number) RETURN timestamp with time zone AS
    BEGIN
        RETURN c_epoch_tz + time_in/86400000;
    END;

    FUNCTION epoch RETURN timestamp AS
    BEGIN
        RETURN c_epoch;
    END;

    FUNCTION epoch_tz RETURN timestamp with time zone AS
    BEGIN
        RETURN c_epoch_tz;
    END;
END time;
/
show err
Followup:
I would not encourage or even rely on that.  true, there is a bit of the TZ
supported undocumented that got slipped into 8iR3 BUT there are tons of known
(and never to be fixed) issues.

Undocumented
Not supported
Caveat Emptor
Rating: 5
December 30, 2003
Reviewer: Jim Nasby from Austin, TX
Oops, sorry, should have made it clear that that was a 9i only solution (and
probably not even the best one but this thread was about the closest I found
when trying to figure out how to do it in 9i).
Followup:
:)

it's ok then
Rating: 5
Wiping egg off face December 31, 2003
Reviewer: Jim Nasby from Austin, TX
Looks like I didn't quite test things enough; the seconds precision of 0 breaks
the millisecond functions and I also should have added deterministic and
parallel clauses.

Corrected code can be found at
http://jim.nasby.net/scripts/time.sql

December 30, 2003 Avinash -- Thanks for the question regarding "Listener Question", version 8.1.5
You Asked
Hi Tom,
This is an urgent query. Please let me know if the port 1521 is
default port for the TNSLinstener. I have a database server on
port 1527 how can I make the clients connect on this port or can
I have one listener service connect to listen for 2 servers.
For further clarification, please take a look at the attached
listener.ora file. Also let me know how I can check the names of
listeners running on a server (the command).

Thanks and Regards,
Avinash

#
# This file is written by OFS
# on Fri Mar 10 12:24:13 2000
#
LISTENER =
(ADDRESS_LIST =
(ADDRESS=
(PROTOCOL= IPC)
(KEY= oracle.world)
)
(ADDRESS=
(PROTOCOL= IPC)
(KEY= ORCL)
)
(ADDRESS=
(PROTOCOL= IPC)
(KEY= EXTPROC0)
)
(ADDRESS=
(COMMUNITY= NMP.world)
(PROTOCOL= NMP)
(SERVER= SCCPRCI)
(PIPE= ORAPIPE)
)
(ADDRESS=
(PROTOCOL= TCP)
(Host= sccprci)
(Port= 1521)
)
(ADDRESS=
(PROTOCOL= TCP)
(Host= sccprci)
(Port= 1526)
)
(ADDRESS=
(PROTOCOL= TCP)
(Host= 127.0.0.1)
(Port= 1521)
)
)

SID_LIST_LISTENER =
(SID_LIST =
(SID_DESC =
(GLOBAL_DBNAME = sccprci)
(SID_NAME = ORCL)
)
(SID_DESC =
(SID_NAME = extproc)
(PROGRAM=extproc)
)
)

CONNECT_TIMEOUT_LISTENER = 10
TRACE_LEVEL_LISTENER = 0
STARTUP_WAIT_TIME_LISTENER = 0
PASSWORDS_LISTENER = (oracle)

Fslsccora =
(ADDRESS_LIST=
(ADDRESS=
(PROTOCOL=IPC)
(KEY=PRD)
)
(ADDRESS=
(PROTOCOL=IPC)
(KEY=PRD.WORLD)
)
(ADDRESS=
(COMMUNITY=SAP.WORLD)
(PROTOCOL=TCP)
(Host=SCCORA)
(Port=1527)
)
)

SID_LIST_Fslsccora =
(SID_LIST=
(SID_DESC=
(SDU=32768)
(SID_NAME=PRD)
(ORACLE_HOME=E:\orant)
(PRESPAWN_MAX=10)
)
)

STARTUP_WAIT_TIME_Fslsccora = 0
TRACE_LEVEL_Fslsccora = OFF
CONNECT_TIMEOUT_Fslsccora = 10
and we said...
If you are using "host naming" convention (this is a method that
does not require the client to have a tnsnames.ora file at all.
You must be using tcp, you must only have one default database
per host.  The client only needs to know the hostname of the
server to connect) then yes, 1521 is the default and only
port.

If you are using tnsnames.ora, the Oracle nameserver, or any
other method to connec then no, 1521 is not a magic port, is
not a default port.  In this case, 1521 is simply the port
used by "convention".  The clients would, typically in their
tnsnames.ora, connect to the listener on some specified port
number.  1521 is the convention used by many people, it is
neither mandatory or necessary.

$ lsnrctl status

is the command that will show you the status of your listener.
Review & Followup

Rating: 5
Status unknown July 01, 2003
Reviewer: Reader
Tom, please see below. What is "status unknown" means? Thanks.

LSNRCTL> start
Starting tnslsnr: please wait...

TNSLSNR for 32-bit Windows: Version 9.2.0.1.0 - Production
System parameter file is C:\oracle\ora92\network\admin\listener.ora
Log messages written to C:\oracle\ora92\network\log\listener.log
Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(PIPENAME=\\.\pipe\EXTPROC0ipc
)))
Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=cvenkate-us.us.oracle.co
m)(PORT=1521)))

Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC0)))
STATUS of the LISTENER
------------------------
Alias                     LISTENER
Version                   TNSLSNR for 32-bit Windows: Version 9.2.0.1.0 - Produc
tion
Start Date                01-JUL-2003 11:28:59
Uptime                    0 days 0 hr. 0 min. 2 sec
Trace Level               off
Security                  OFF
SNMP                      OFF
Listener Parameter File   C:\oracle\ora92\network\admin\listener.ora
Listener Log File         C:\oracle\ora92\network\log\listener.log
Listening Endpoints Summary...
  (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(PIPENAME=\\.\pipe\EXTPROC0ipc)))
  (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=cvenkate-us.us.oracle.com)(PORT=1521
)))
Services Summary...
Service "PLSExtProc" has 1 instance(s).
  Instance "PLSExtProc", status UNKNOWN, has 1 handler(s) for this service...
Service "practice.world" has 1 instance(s).
  Instance "practice", status UNKNOWN, has 1 handler(s) for this service...
The command completed successfully
LSNRCTL>
Followup:
the database hasn't come back and told the listener about these services yet. No
one has used them. eg, when I start my listener:

[ora920@tkyte-pc admin]$ lsnrctl start

LSNRCTL for Linux: Version 9.2.0.3.0 - Production on 01-JUL-2003 12:42:17

Copyright (c) 1991, 2002, Oracle Corporation.  All rights reserved.

Starting /usr/oracle/ora920/bin/tnslsnr: please wait...

TNSLSNR for Linux: Version 9.2.0.3.0 - Production
System parameter file is /usr/oracle/ora920/network/admin/listener.ora
Log messages written to /usr/oracle/ora920/network/log/listener.log
Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC)))
Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=tkyte-pc)(PORT=1521)))

Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC)))
STATUS of the LISTENER
------------------------
Alias                     LISTENER
Version                   TNSLSNR for Linux: Version 9.2.0.3.0 - Production
Start Date                01-JUL-2003 12:42:17
Uptime                    0 days 0 hr. 0 min. 0 sec
Trace Level               off
Security                  OFF
SNMP                      OFF
Listener Parameter File   /usr/oracle/ora920/network/admin/listener.ora
Listener Log File         /usr/oracle/ora920/network/log/listener.log
Listening Endpoints Summary...
  (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC)))
  (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=tkyte-pc)(PORT=1521)))
Services Summary...
Service "PLSExtProc" has 1 instance(s).
  Instance "PLSExtProc", status UNKNOWN, has 1 handler(s) for this service...
The command completed successfully

and after a couple of seconds

[ora920@tkyte-pc admin]$ lsnrctl status

LSNRCTL for Linux: Version 9.2.0.3.0 - Production on 01-JUL-2003 12:45:53

Copyright (c) 1991, 2002, Oracle Corporation.  All rights reserved.

Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC)))
STATUS of the LISTENER
------------------------
Alias                     LISTENER
Version                   TNSLSNR for Linux: Version 9.2.0.3.0 - Production
Start Date                01-JUL-2003 12:42:17
Uptime                    0 days 0 hr. 3 min. 36 sec
Trace Level               off
Security                  OFF
SNMP                      OFF
Listener Parameter File   /usr/oracle/ora920/network/admin/listener.ora
Listener Log File         /usr/oracle/ora920/network/log/listener.log
Listening Endpoints Summary...
  (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC)))
  (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=tkyte-pc)(PORT=1521)))

(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=tkyte-pc)(PORT=8080))(Presentation=HTTP
)(Session=RAW))

(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=tkyte-pc)(PORT=2100))(Presentation=FTP)
(Session=RAW))
Services Summary...
Service "PLSExtProc" has 1 instance(s).
  Instance "PLSExtProc", status UNKNOWN, has 1 handler(s) for this service...
Service "ora920" has 1 instance(s).
  Instance "ora920", status READY, has 1 handler(s) for this service...
Service "ora920XDB" has 1 instance(s).
  Instance "ora920", status READY, has 1 handler(s) for this service...
The command completed successfully
[ora920@tkyte-pc admin]$

the database came back and said "hey, I'm here!"  No one used extprocs -- so it
is "unknown" until used.
Rating: 5
July 01, 2003
Reviewer: A reader
Rating: 5
web browser direct connection July 09, 2003
Reviewer: Reader
If time permits, could you please help me in finding more info on the subject
connection using HTTP. Is there a doc link that you can provide me with regard
to how to configure listener to accept http connections? It would be of great
help. Also, what applications use this kind of listener configuration.

Thanks for your time.
Followup:
see
http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96620/xdb24pro.htm#1656
Rating: 5
how to configure listener? July 09, 2003
Reviewer: Reader
Tom, I looked at the link but could not find info on how to configure the
listener to accept http connection. Could you please help? Thanks.
Followup:
I asked Sean Dillon, our resident XML Technologist, to take a look at this and
here's what he had to say:
---------------

Well, it depends on what you are trying to connect to... If you are just trying
to use an HTTP listener, the database is shipped w/ Apache which sits in the
code tree of the database in $ORACLE_HOME/Apache.  The other way, and the way I
THINK you're talking about, is through XML DB to get to the XML DB Repository...

In Oracle9i RELEASE 2, Oracle has implemented the "Protocol Server" (which
provides FTP, HTTP and WebDAV connection capabilities into the database) in the
XML DB Repository.  In order to configure the protocol server, you use XML DB
APIs to update the XML DB configuration file, /sys/xdbconfig.xml.  The reason
you use APIs to update this file is because it doesn't live on your OS or in the
$ORACE_HOME tree, it resides INSIDE the database in the XML DB Repository.  To
see this config, submit the following in sql*plus:
SQL> connect system/blahblah
Connected.

SQL> set long 100000
SQL> set lines 120
SQL> select dbms_xdb.cfg_get().getClobVal()
  2    from dual;

DBMS_XDB.CFG_GET().GETCLOBVAL()
--------------------------------------------------------------------------------
<xdbconfig xmlns="http: //xmlns.oracle.com/xdb/xdbconfig.xsd" xmlns:xsi="http:
//www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http:
//xmlns.oracle.com/xdb/xdbconfig.xsd                                     http:
//xmlns.oracle.com/xdb/xdbconfig.xsd">
  <sysconfig>
    <acl-max-age>900</acl-max-age>
    <acl-cache-size>32</acl-cache-size>
    <invalid-pathname-chars>,</invalid-pathname-chars>
    <call-timeout>300</call-timeout>
    <max-session-use>100</max-session-use>
    <default-lock-timeout>3600</default-lock-timeout>
    <resource-view-cache-size>1048576</resource-view-cache-size>
    <protocolconfig>
      <common> *cut for brevity * </common>
      <ftpconfig>
        <ftp-port>2100</ftp-port>
        <ftp-listener>local_listener</ftp-listener>
        <ftp-protocol>tcp</ftp-protocol>
        <session-timeout>6000</session-timeout>
      </ftpconfig>
      <httpconfig>
        <http-port>8080</http-port>
        <http-listener>local_listener</http-listener>
        <http-protocol>tcp</http-protocol>
        <session-timeout>6000</session-timeout>
        <server-name>XDB HTTP Server</server-name>
        <max-header-size>16384</max-header-size>
        <max-request-body>2000000000</max-request-body>
        <servlet-realm>Basic realm=&quot;XDB&quot;</servlet-realm>
        <webappconfig> * cut for brevity * </webappconfig>
      </httpconfig>
    </protocolconfig>
  </sysconfig>
</xdbconfig>

This SHOWS YOU what your ftp and http configuration is.  Specifically,
/xdbconfig/sysconfig/protocolconfig/httpconfig/http-port/text() is the HTTP port
your protocol server is listening on :-).  You can try to connect in a web
browser by specifying the hostname of your database server along w/ the port of
your protocol server's http setting, i.e. slaphappy.us.oracle.com:8080/.  This
will prompt me for a username/password (it's a DATABASE, after all ;-)), and
once I successfully authenticate I'm connected to the XML DB Repository.

To edit this configuration, you call an XML DB API:

DBMS_XDB.CFG_UPDATE( newconfig SYS.XMLTYPE )

You pass the new xdbconfig.xml file TO this procedure, which updates the
configuration.  This may be a bit daunting if you're not used to using Oracle's
XMLType, so here's an example of using this call to update the FTP & HTTP ports
of the protocol server:

begin
  dbms_xdb.cfg_update(
    updatexml(updatexml(dbms_xdb.cfg_get(),
                        '/xdbconfig/descendant::ftp-port/text()', '2121'),
              '/xdbconfig/descendant::http-port/text()', '19090'));
end;
/

This updates the FTP port to 2121 and the HTTP port to 19090.  For more
information on the database's protocol server, the FTP, HTTP and/or WebDAV
connection settings, XML DB, etc... see the XML Database Developer's Guide in
the Oracle9i Release2 documentation set (available on TechNet (otn.oracle.com).
In THAT document, Chapter 19 and Appendix A are YOUR friends :-).  I hope this
has helped, good luck!

________________________________________________________
Sean Dillon - Principal Technologist, Oracle Corporation
Author "Beginning Oracle Programming" www.amazon.com/exec/obidos/ASIN/186100690X
Rating: 5
wow! Great stuff July 12, 2003
Reviewer: Reader
Thanks
Rating: 5
ipc protocol July 12, 2003
Reviewer: Reader from Salt Lake City, UT USA
Tom, What is an IPC protocol and where and how it is used? I have experience
only in TCP/IP protocol. Is there any advantage in using IPC over TCP? Thanks.
Followup:
IPC is interprocess communication.

you have messages, pipes, socketpairs and so on -- it is alot like just using
sockets with tcp/ip.

ipc is generally limited to "a machine", not over a network.

ipc used to be a tad faster then tcp but recent tests have shown this to be less
and less true.
Rating: 5
ipc July 12, 2003
Reviewer: Reader
So, does it mean that tnsnames.ora configuration on clients machines can never
contain an entry for listener to receive connection for IPC protocol? If this is
true, where exactly this is configured? My understanding is that listener can
listen on both TCP/IP and IPC protocol? Thanks.
Followup:
it is configured in the listener and used for local processes, like external
procedures for example or local connections on the same machine.
Rating: 5
external procedures? July 12, 2003
Reviewer: Ramu from Alpharetta, GA
I am new to oracle DBA. When you say, external procedures, are they not part of
database? Could they be written in C or Java? Could you please help me
understanding about how listener can listen for external procedures -- any
example of listener config for ext procedures would be helpful. Thanks.
Followup:

http://technet.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96590/adg11rtn.htm#1000718
Rating: 4
More Question on Status Unknown July 18, 2003
Reviewer: Kuo-Chun from Peekskill, NY
Tom:

I have installed 3 releases on my development server with 3 different logins -
Oracle8i for 8.1.6, Oracle81 8.1.7, & Oracle 9i 9.2.0. I have set the
environment variable tns_admin to the listener.ora of 9.2.0 (see below):

# LISTENER.ORA Network Configuration File: /Apps/oracle/ora920/network/admin/lis
tener.ora
# Generated by Oracle configuration tools.

LISTENER =
  (DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS_LIST =
        (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC))
      )
      (ADDRESS_LIST =
        (ADDRESS = (PROTOCOL = TCP)(HOST = IPCDBENND001.ip2.entergy.com)(PORT =
1521))
      )
    )
  )

SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (SID_NAME = PLSExtProc)
      (ORACLE_HOME = /Apps/oracle/ora920)
      (PROGRAM = extproc)
    )
    (SID_DESC =
      (GLOBAL_DBNAME = ipcdmdev)
      (ORACLE_HOME = /Apps/oracle/ora816)
      (SID_NAME = ipcdmdev)
    )
    (SID_DESC =
      (GLOBAL_DBNAME = ipcdmd8i)
      (ORACLE_HOME = /Apps/oracle/ora817)
      (SID_NAME = ipcdmd8i)
    )
  )

When I login as Oracle81 and do lsnrctl status, I got:

IPCDBENND001.ip2.entergy.com> whoami
Oracle81
IPCDBENND001.ip2.entergy.com> lsnrctl status

LSNRCTL for DEC OSF/1 AXP: Version 8.1.7.4.0 - Production on 18-JUL-2003
20:27:24

(c) Copyright 1998 Oracle Corporation.  All rights reserved.

Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC)))
STATUS of the LISTENER
------------------------
Alias                     LISTENER
Version                   TNSLSNR for Compaq Tru64 UNIX: Version 9.2.0.3.0 -
Production
Start Date                18-JUL-2003 14:24:18
Uptime                    0 days 6 hr. 3 min. 5 sec
Trace Level               off
Security                  OFF
SNMP                      OFF
Listener Parameter File   /Apps/oracle/ora920/network/admin/listener.ora
Listener Log File         /Apps/oracle/ora920/network/log/listener.log
Services Summary...
  PLSExtProc            has 1 service handler(s)
  ipcdmd8i              has 1 service handler(s)
  ipcdmd8i              has 2 service handler(s)
  ipcdmdev              has 1 service handler(s)
  ipcdmdev              has 2 service handler(s)
The command completed successfully
IPCDBENND001.ip2.entergy.com>

When I login as Oracle9i and the result is:

IPCDBENND001.ip2.entergy.com> whoami
Oracle9i
IPCDBENND001.ip2.entergy.com> lsnrctl status

LSNRCTL for Compaq Tru64 UNIX: Version 9.2.0.3.0 - Production on 18-JUL-2003
20:28:10

Copyright (c) 1991, 2002, Oracle Corporation.  All rights reserved.

Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC)))
STATUS of the LISTENER
------------------------
Alias                     LISTENER
Version                   TNSLSNR for Compaq Tru64 UNIX: Version 9.2.0.3.0 -
Production
Start Date                18-JUL-2003 14:24:18
Uptime                    0 days 6 hr. 3 min. 52 sec
Trace Level               off
Security                  OFF
SNMP                      OFF
Listener Parameter File   /Apps/oracle/ora920/network/admin/listener.ora
Listener Log File         /Apps/oracle/ora920/network/log/listener.log
Listening Endpoints Summary...
  (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC)))

(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=ipcdbennd001.ip2.entergy.com)(PORT=1521
)))
Services Summary...
Service "PLSExtProc" has 1 instance(s).
  Instance "PLSExtProc", status UNKNOWN, has 1 handler(s) for this service...
Service "ipcdmd8i" has 2 instance(s).
  Instance "ipcdmd8i", status UNKNOWN, has 1 handler(s) for this service...
  Instance "ipcdmd8i", status READY, has 2 handler(s) for this service...
Service "ipcdmdev" has 2 instance(s).
  Instance "ipcdmdev", status UNKNOWN, has 1 handler(s) for this service...
  Instance "ipcdmdev", status READY, has 2 handler(s) for this service...
The command completed successfully
IPCDBENND001.ip2.entergy.com>

My questions are:

1. Why both results said that there are 2 instances for each of the database?

2. How do I get rid of the instance that has the status unknown?

Thank you for your help.
Followup:
you take the entries out of the listener.ora.
Rating: 5
lsnrctl status unknown July 19, 2003
Reviewer: Mark A. Williams from Indianapolis, IN USA
Metalink note 151223.1

An UNKNOWN status means that the instance is registered statically  (with a
SID_LIST) in the listener.ora file rather than dynamically with  service
registration.  Therefore, the status is not known.  The SID_LIST in the
listener.ora file is not required. One of the reasons why Oracle recommends not
using one, is because it may cause un-necessary overheads on the listener
process. The SID_LIST section only needs to be used for instances such as
PLSExtProc. It is not normally a problem having the instance duplicated in the
SID_LIST, but it is preferable not to have it.

HTH,

Mark
Rating: 5
Great Help July 19, 2003
Reviewer: Kuo-Chun from Peekskill, NY
Tom/Mark:

Thanks for the help. I was wondering about the dynamic service registration and
now I know what it is.
Rating: 3
No listener, but can connect to the database!! July 24, 2003
Reviewer: Praveen KV from Bangalore
Hi Tom,

1)
Here I have a very interesting problem. I am using Oracle version 9.0.1.1 on
Windows 2000. I stopped the
listener service from control panel->services.
Still I am able to connect to the database(ie without using the service name -
like 'connect scott/tiger')! But when I infact started the listener service and
used the service name-ORCL (ie, 'connect scott@ORCL/tiger') to connect to the
database, I get the 'TNS: no listener" error!!!

When I intially created the database (name=ORCL), I created the listener and
configured a service named ORCL to this listener (using Net Manager). I was able
to connect to the database with and without using a service name. But later I
installed Oracle 9iDS and then created additional listeners and services and
then uninstalled them. Since then this strange thing happens. How is it possible
to connect to the database WITHOUT a listener at all.

2) A similar problem had existed with  Oracle 9i DS also. I was able to create a
forms application using Oracle9i Forms.
I can compile it successfully. But when I run the application (web form), the
applet gets started, and asks for log in information - but then 'TNS: protocol
adaptor error'. JInitiator version is 1.3.1.9. Is this because of
version difference between the Oracle DataBase server(9.0.1.1) and Designer
Suite(9.0.2.1). Or am I a complete dump about the listener concepts?
Followup:
well, scott/tiger needs no listener and scott@ORCL/tiger does

so, no mystery there, it would be a mystery if the second worked.....

the listener is ONLY used when you use @dbname
Rating: 4
as for july 09, 2003 December 12, 2003
Reviewer: j
begin
  dbms_xdb.cfg_update(
    updatexml(updatexml(dbms_xdb.cfg_get(),
                        '/xdbconfig/descendant::ftp-port/text()', '2121'),
              '/xdbconfig/descendant::http-port/text()', '19090'));
end;

doesn't work (with my 9.2.0.1), but something like:

update XDB.XDB$Config
set    SYS_NC_RowInfo$ = UpdateXML(SYS_NC_RowInfo$,
'/xdbconfig/descendant::http-port/text()', '19090').

sets the http-port as needed.

please, tell me: how can one get the first statement to work? is there anything
wrong with the second approach?
Followup:
tell me what "does not work" mean???

also -- don't even think about updating system tables unless support tells you
to.  You have NO idea what ramifications/side effects that may cause.  don't
even dream about going there.
Rating: 3
December 15, 2003
Reviewer: j.
well, logged on as SYS i get ORA-06550 (updatexml has to be declared), which i
do not get in case i use updatexml in a SQL statement.
Followup:
do not log in as sys.  you never want to log in as sys.  sys is special.  don't
do it.

connect "as sysdba" to do maintenance (startup, shutdown, backups and the like)
-- install software when directed to install as sysdba

but nothing else.  NOTHING else.
Rating: 3
December 15, 2003
Reviewer: j.
that 's clear. but question remains: what do i have to do in order to get the
valid approach (using DBMS package in conjuntion with updatexml) to work? how do
i get the "updatexml must be declared" exception for that? isn't updatexml a
built-in function?

please help.
Rating: 3
j. December 18, 2003
Reviewer: j.
does one have to get "special privileges" granted to get this update to work?
Followup:
One of the guys I work with saw this and says:

Tom,

  I was on asktom looking for why the function updatexml is giving me problem
and found this question

The last question is the same I was looking for and the aanswer is that there’s
bug the doesn’t allow updatexml from  plsql so to answer the person’s question

So to change the http/ftp port od XML DB use the following

declare

 v_cfg xmltype;

BEGIN

 select     updateXML(

                updateXML(DBMS_XDB.CFG_GET(),

                         '/xdbconfig/descendant::ftp-port/text()', '2121'),

                         '/xdbconfig/descendant::http-port/text()', '19090')

   into v_cfg

   from dual;

  DBMS_XDB.CFG_UPDATE(v_cfg);

END;

/

Kris Rice....
Rating: 4
what privileges does kris have? December 18, 2003
Reviewer: j.
i 've tried the snippet and got ORA-31050: access denied ORA-06512: in
"XDB.DBMS_XDB", line 168.

granting execute on XDB.DBMS_XDB (with grant option) didn't help.

can you please provide me with a solution for that issue and the reference of
the bug mentioned?

thank you very much in advance.
Followup:
Hi, This is kris.

my account was granted DBA.
Rating: 4
has kris got more? December 20, 2003
Reviewer: j.
i create a new user, granted connect, logon and dba.

execution of the abovementioned script caused:
ORA-00604 - error occurred at recursive SQL level
ORA-00942 - table or view doesn't exist

???
Rating: 3
there must be another difference between SYS and kris December 22, 2003
Reviewer: j.
'cause SYS succeeds ...
Rating: 1
Could you please give me a hand on how to achieve the goal the RIGHT way? December 28, 2003
Reviewer: j.
it just seems to be a matter of privileges, isn't it?
Followup:
I ran into the same problem you have on my 9.2.0.1 database.  there is a bug in
the DB in 9.2.0.1.  I upgraded it to 9.2.0.4 and everything worked as follows.

I suggest upgrading to 9.2.0.4 which is the latest patchset available for the
9.2 series.

Here's what i did....
Connected to:
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
With the OLAP and Oracle Data Mining options
JServer Release 9.2.0.4.0 - Production

SQL> drop user klrice_xdb;

User dropped.

SQL> create user klrice_xdb identified by k;

User created.

SQL> grant dba,connect to klrice_xdb;

Grant succeeded.

SQL> conn klrice_xdb/k
Connected.
SQL> declare
  2
  3   v_cfg xmltype;
  4
  5  BEGIN
  6
  7   select     updateXML(
  8
  9                  updateXML(DBMS_XDB.CFG_GET(),
 10
 11                           '/xdbconfig/descendant::ftp-port/text()', '2121'),
 12
 13                           '/xdbconfig/descendant::http-port/text()',
'19090')
 14
 15     into v_cfg
 16
 17     from dual;
 18
 19    DBMS_XDB.CFG_UPDATE(v_cfg);
 20
 21  END;
 22  /

PL/SQL procedure successfully completed.

SQL>
Rating: 5
listener port December 29, 2003
Reviewer: atul from India
Hi,

How to know which port address database is using to listen?
apart from listener.ora or tnsnames.ora??

Thank.
Atul
Followup:
that is pretty much it.

you use the listener.ora to tell the listener what port to use.  thats it.
Rating: 3
question on SID_LIST in listener.ora December 30, 2003
Reviewer: A reader
Mark wrote ..
"Metalink note 151223.1

An UNKNOWN status means that the instance is registered statically  (with a
SID_LIST) in the listener.ora file rather than dynamically with  service
registration.  Therefore, the status is not known.  The SID_LIST in the
listener.ora file is not required. One of the reasons why Oracle recommends not
using one, is because it may cause un-necessary overheads on the listener
process. The SID_LIST section only needs to be used for instances such as
PLSExtProc. It is not normally a problem having the instance duplicated in the
SID_LIST, but it is preferable not to have it.
"

However, while using agentctl, some of my friends mentioned that discovery of
the node will get in trouble if you don't have SID_LIST. It looks in
/var/opt/oracle/oratab and listener.ora. Sure enough, I did not see some
entries. Added SID_LIST, stopped/started the agent the
network/agent/services.ora looks better and OEM can see the database on the node
as well. Any comments.

Thanks
Rating: 4
Listner port December 31, 2003
Reviewer: Madhu from Cincinnati,OH
Atul,
  You can also try using tnsping command , which displays the port being used .

C:\>tnsping idwdt301

TNS Ping Utility for 32-bit Windows: Version 9.2.0.1.0 - Production on
31-DEC-2003 09:00:06

Copyright (c) 1997 Oracle Corporation.  All rights reserved.

Used parameter files:
C:\Oracle\Ora91\network\admin\sqlnet.ora

Used ONAMES adapter to resolve the alias
Attempting to contact
(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(COMMUNITY=TCP.na.gpcom)(PROTOCOL=TCP)(Host=b
dhp423
1.na.gp.com)(Port=1521)))(CONNECT_DATA=(SID=idwdt301)))
OK (10 msec)

Thanks,
Madhu
Rating: 5
nt-service for oracle-http-server doesn't start anymore ... January 07, 2004
Reviewer: j.
as for december 28, 2003: thank you very much for your help. applying the patch
solved the problem (ports can now be set via DBMS package).

but: since the upgrade the oracle-http-server doesn't start anymore, and no
error message is reported.

could you please supply me with another hint?
Rating: 3
nt-event-log-entries for failing http-server-startups January 07, 2004
Reviewer: j.
The Apache service named c:\programs\oracle9i\Apache\Apache\apache.exe reported
the following error:
>>> FastCgiServer: redefinition of a previously defined FastCGI server
"c:/programs/oracle9i/bin/isqlplus" <<<
 before the error.log file could be opened.
 More information may be available in the error.log file.

unfortunately there 's no such error log file available ...
Rating: 1
issue "solved" January 07, 2004
Reviewer: j.
one of the entries following the abovementioned one pointed to a syntax error in
line 92 of file isql.conf (which in turn includes "FastCgiServer"). this file
was untouched. commenting out this line made at least the http-server run again
...


Subject | Article | Last Updated (East coast time)
Why can't 1redo log file | article only | Dec 31, 2003 08:58 am
connectivity to sql server | article only | Dec 31, 2003 09:23 am
Data Loading Approach | article only | Dec 31, 2003 09:29 am
efficient upsert | article only | Dec 31, 2003 09:34 am
Multi-language database | article only | Dec 31, 2003 09:38 am
Date Calculation | article only | Dec 31, 2003 09:50 am
What is the reason that implicit cursors are faster than explicit cursors | article only | Dec 31, 2003 10:08 am
Application contexts in a web environment | article only | Dec 31, 2003 10:11 am
outer join | article only | Dec 31, 2003 10:21 am
Indexes with reverse "like" predicate | article only | Dec 31, 2003 12:21 pm
All about the DUAL table | article only | Dec 31, 2003 02:35 pm
Sizing System Hardware | article only | Dec 31, 2003 04:37 pm
Index Rebuild and analyze | article only | Dec 31, 2003 05:08 pm
Add a new data type in Oracle | article only | Dec 31, 2003 05:50 pm
row(s) 1 - 14 of 14
December 31, 2003 Ashiq -- Thanks for the question regarding "Why can't 1redo log file", version 8.1.7
You Asked
Hi Tom,
 Yeah ,all we know mimimum me need TWO redo log files.If my db is in non archive
log mode ,absolutely there is no need to make copy of it before over writing
it.so Why? we need 2 redo logs even in non archive mode?

thanx
and we said...
online redo logs work for EXACTLY the same purpose in archivelog as non-archive
log mode.

They are there for instance recovery.  Plain and simple.  They perform the same
exact function.

Now, suppose you had just one.  Every time it filled -- the database would have
to "halt" -- that is "freeze" (as in STOP dead) whilst DBWR flushed blocks from
the buffer cache to disk.  All dirty blocks would have to be written out.

What we do instead is -- as you are filling redolog 2 -- we flush the blocks
protected by redolog 1 to disk in the background.  No stopping, no freeze, no
halt.
Review & Followup
Rating: 5
exellent answer November 27, 2002
Reviewer: Manish from Madison, WI, USA
Can you explain why there is minimum of 2 extents in every rollback segment?

Followup:
because a transaction cannot enter an extent where there is an active
transaction by design.

Hence, any transaction that was unfortunate enough to be near the "end" of an
extent and needed to advance forward would fail.

Rating: 5
November 27, 2002
Reviewer: Ashiq from Trichy, TN, India
Yeah  I got it

Rating: 5
archiving ... December 30, 2003
Reviewer: Reader
When the instance is shutdown cleanly, does oracle implicitly switch the current
redolog and make sure it is archived? This will be helpful when I restart the
instance, assuming if I lost a datafile and the redolog files also, I could
recover completely with info in archive log files without loosing any data.
Thanks.

Followup:
you don't need to -- media recovery involves archived and online redo logs --
you need BOTH.

why would an archive sitting off to the side in /d01 be any safer then an online
sitting in /d02.

suppose we did archive -- and you lost the archive and the datafile -- same
situation.

December 31, 2003 harsh -- Thanks for the question regarding "connectivity to sql server", version 8.1.6
You Asked
How to connect SqlServer through oracle using generic connectivity.
what changes require in ORacle_home\hs (folder).I mean in
inithsodbc,listener,tnsnames after i make System Dsn in odbc
and we said...
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76960/hs_genco.htm#173
describes the architecture and the setup necessary.
Review &
      Followup

      Rating: 2
Not
      useful  June 27, 2002
Reviewer:  harsh

Sir,Please describe with example because I read that docmentation but cannot be
able to apply.

          Followup:

Sorry, if you cannot follow the directions for setup and then provide some
useful infromation beyond "cannot be able to apply".

The setup is all there.

That chapter has an example (no more or less then I would be able to provide in
any case).

You provide no information about what your problem is, error message, erroneous
behavior, etc.  I cannot provide anything useful back to you either.

      Rating: 3
Read n tried ..
        July 23, 2002
Reviewer:  Charanjiv
       from India
I went through the URL and related chapters/pages butstill getting the error

scott@817>  select authid from authors@sqlsvr ;
 select authid from authors@sqlsvr
                            *
ERROR at line 1:
ORA-28509: unable to establish a connection to non-Oracle system
ORA-02063: preceding line from SQLSVR

Looking the error up in the error manual i have checked the following

1. The USING clause in the DB link and TNSNAMES is the same  as in the TNSNAMES
file

CREATE DATABASE LINK SQLSVR
    USING 'HSORA'

<tnsnames>
HSORA =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = BP)(PORT = 1433))
    )
    (CONNECT_DATA =
      (SID = HSORA)
    )
    (HS = OK)
  )

2. protocol-specific information in the service name definition is the same as
in the <LISTENER>

SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (GLOBAL_DBNAME = tsora)
      (ORACLE_HOME = C:\Oracle\Ora81)
      (SID_NAME = tsora)
    )
    (SID_DESC =
      (PROGRAM = extproc)
      (SID_NAME = PLSExtProc)
      (ORACLE_HOME = C:\Oracle\Ora81)
    )
    (SID_DESC =
      (PROGRAM = hsodbc)
      (SID_NAME = hsora)
      (ORACLE_HOME = C:\Oracle\Ora81)
    )
  )

LSNRCTL> start
Starting tnslsnr: please wait...

TNSLSNR for 32-bit Windows: Version 8.1.7.0.0 - Production
System parameter file is C:\Oracle\Ora81\network\admin\listener.ora
Log messages written to C:\Oracle\Ora81\network\log\listener.log
Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(PIPENAME=\\.\pipe\EXTPROC0ipc
)))
Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=258_stock)(PORT=1521)))
Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=258_stock)(PORT=2481))(P
ROTOCOL_STACK=(PRESENTATION=GIOP)(SESSION=RAW)))

Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC0)))
STATUS of the LISTENER
------------------------
Alias                     LISTENER
Version                   TNSLSNR for 32-bit Windows: Version 8.1.7.0.0 - Produc
tion
Start Date                23-JUL-2002 12:57:47
Uptime                    0 days 0 hr. 0 min. 2 sec
Trace Level               off
Security                  OFF
SNMP                      OFF
Listener Parameter File   C:\Oracle\Ora81\network\admin\listener.ora
Listener Log File         C:\Oracle\Ora81\network\log\listener.log
Services Summary...
  PLSExtProc            has 1 service handler(s)
  hsora         has 1 service handler(s)
  tsora         has 1 service handler(s)
The command completed successfully

3.SID=name in the service name definition (in the TNSNAMES.ORA file) must match
the value in the LISTENER.ORA file for the responding listener.
Was this the Service (hsora)?

The system DSN is called HSORA. Microsoft SQL Server ODBC Driver (Version
03.80.0194)

Running connectivity tests...

Attempting connection
Connection established
Verifying option settings
Disconnecting from server

TESTS COMPLETED SUCCESSFULLY!

I have run the HS script C:\Oracle\Ora81\RDBMS\ADMIN\caths.sql

The init file for HS has been modified and named appropriately
INITHSORA
#
# HS init parameters
#
HS_FDS_CONNECT_INFO = hsora
HS_FDS_TRACE_LEVEL = OFF

The SQL Server is on another machine. I am running this test from the Oracle
Server (8.1.7) and has the SQL server 2000 Client installed on it.

I am still trying ot find where i went wrong. in case you can help pl let me
know

      Rating:
      5
  July 23, 2002
Reviewer:
      A reader
Hi TOM,

 All the time if some one ask question regarding how to connect to non-oracle
database you just give us links.

   I think that is not enough.Almost every big company has at least one database
other then oracle.So it is importent for us if you explain with real examples
(No body knows how to use PL/SQL in this case)

 I would appreciate if you explain with an example.

 Thanks,

          Followup:

No -- I cannot give you an example.  The reason is just really simple.

Connecting to a non-oracle data source is NO DIFFERENT THEN CONNECTING TO
ANOTHER ORACLE DATA SOURCE.

You follow the setup, make sure the stuff is working and use dblinks.  It is NOT
any different then a normal DBLINK.

There is no "example" to be given.  Oh wait -- here is one:

create database link foo connect to user identified by pw using 'sqlserver';
select * from t@foo;

It is Just a database link.  You have to configure the configuration files and
that is something you need to discover how to do in your environment, given your
odbc drivers, etc etc etc (and something that the documentation actually does
walk you through step by step)

Configuration and setup -- support is excellent at helping you there.  I
personally (believe it or not) do not have sqlserver installed on my machines.
In fact, I don't have any machines that are physically capable of running
sqlserver since they only run on one OS.

      Rating:
      5
  July 23, 2002
Reviewer:
      A reader
Tom,

  We are using oracle 8.1.7. Is PL/SQL gateway is available
  in this release? If so, Can you give me link to that   documents

 Thanks,

          Followup:

plsql gateway -- do you mean mod_plsql?

      Rating: 3
got it
      ..  July 24, 2002
Reviewer:  Charanjiv
       from India
two things which i was doing wrong
1. in SQLNET instead of
SQLNET.AUTHENTICATION_SERVICES= (NTS)
use
SQLNET.AUTHENTICATION_SERVICES=(NONE)
look out for changes in connecting from localhost

2. In the hsodbc definition in tnsnames.ora the host needs to be the Oracle host
where I am trying to link from, not the SQL Server host that I am trying to
connect to

I also noticed one thing .. after i execute the script for HS my
DBMS_HS_PASSTHROUGH package is no longer to be found !! ..but now i can select
data from SQL Server

      Rating:
      5
  July 24, 2002
Reviewer:
      A reader
Mr. Chiranjiv,

  Can you list the step you perform to connect to non-oracle database in correct
order(With description)?

 Thanks,

      Rating: 3
as i did it
        July 24, 2002
Reviewer:  Charanjiv
       from india
as tom said .. it is important that you do read thru the URL so that you can diy

here are the steps i followed

1. initodbc.ora - modify it to incule the DSN you have created

HS_FDS_CONNECT_INFO=FILEDSN=file_dsn
When connecting using a System DSN, specify the value using:
HS_FDS_CONNECT_INFO=system_dsn

HS_FDS_TRACE_LEVEL = <trace_level> value of on or off

2.
http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76960/hs_admin.htm#15089
set up the LISTENER and TNSNAMES
as i said in my earlier post the tnsnames host is the localhost.
the listener setup sample is provided in the oracle_home\hs\admin directory. So
is the tnsnames sample

3.CREATE DATABASE LINK link_name
  CONNECT TO user IDENTIFIED BY password
  USING 'non_oracle_system';

again ref to my post above for the precautions you have to take

4. run hte caths.sql as sys or internal

thats it. !!!

      Rating: 3
Heterogenius
      Services  July 30, 2002
Reviewer:  Vish
       from London
I have been trying to get this working on my oracle 9i rel 2.0 datawarehouse
database. Its a shame oracle does'nt have a simple method to get this done ( I
was heckled by MS Access users with the ease with which they can set it up :( )
 I have a access database and another UNIDATA database for which I 'm trying to
access using Heterogenious services.
 My initavantiaccess.ora file in HSadmin folder and Tnsnames.ora in
network/admin folder are in place.

hsinitavantiaccess.ora
--

HS_FDS_CONNECT_INFO = avantiaccess
HS_FDS_TRACE_LEVEL = OFF

--

Networkadmin    nsnames.ora
--
avantiaccess =
      (DESCRIPTION=
        (ADDRESS=(PROTOCOL=tcp)(HOST=STRATSOURCING)(PORT=1521))
        (CONNECT_DATA=(SID=avantiaccess))
        (HS=OK)
      )

--
Listener.ora
--
(SID_DESC=
      (SID_NAME=avantiaccess)
      (ORACLE_HOME=C:OracleOra81)
      (PROGRAM=hsodbc.exe)
    )

 When I query I get this message :(
------
ERROR at line 1:
ORA-28545: error diagnosed by Net8 when connecting to an agent
Unable to retrieve text of NETWORK/NCR message 12564
ORA-02063: preceding 2 lines from AVANTIACCESS

------------
 I could tnsping the service name.

 I also created a db link. , I get this bug when I query... any idea ?  Have i
missed anything ?

My email id is vishkr73@yahoo.co.uk. A help in this would be highly appreciated.
I really really struggling here

Cheers
Vish

      Rating: 3
What product
      licenses do we need to do this?   September 30, 2002

Reviewer:  Cynthia  from Denver, USA
Is it possible to set this up using the products that
are included in the basic Oracle Server Enterprise Edition
install CD?
Or does this require purchase or download of additional
tools?

          Followup:

The generic connectivity is part of...

http://technet.oracle.com/docs/products/oracle8i/doc_library/817_doc/server.817/a76962/ch4.htm#74437
should not require anything else.  There are more advanced transparent gateways
available with 9iAS that provide broader functionality above and beyond this
capability.

      Rating: 3
Doc ID 114820.1
      at metalink.oracle.com  April 15, 2003
Reviewer:
      Saminathan Seerangan  from CA,USA
look for Doc ID:  Note:114820.1 at
http://metalink.oracle.com/
for more detailed information

          Followup:

actually for "less detailed".

The documentation has quite a bit more information - and all of the setup
steps...

      Rating: 4
THNX VERY VERY
      VERY MUTCH SIR  September 16, 2003
Reviewer:
      Aladdin Khalifa  from india
first i wanna thank you sir for ur effort in response us
and i realy found these material very very helpful to me
in connecting to sql server an ms access , but when i tried to connect to
borland interbase database and paradox databse
i found a bad news that oracle didnt recogmize the names of the tables that in
these datbases , so i tried to found them from the data dictionary view that its
names where found in the hs_class_dd and then i selected then rows from these
views i found a view called dictionary that stored the table names of the non
oracle database tables , so when i tried to select from these tables
he gave me exceptoin ora-00942, though these name of table alrady exist in the
dictionary table
so plz what should i do

          Followup:

is the table name in lower/mixed case?

try quoting it

select * from "t"

is very different from

select * from t

the quoted "t" will query a lowercase tablename, the unquoted t is T (uppercase)

      Rating:
      3
thnks  September 17, 2003
Reviewer:
      Aladdin  from india
thanks sir , but i realy tried these before i write my previous review , but it
doesnt make any diffencee , so plz
try to help me as soon as u can

          Followup:

sorry -- don't know, i don't run windows, i have none of those products to test
with.

      Rating: 4
Microsoft
      Access  December 15, 2003
Reviewer:  Abhi
       from INDIA
hello Sir,

I have two tables with simple structure
like EMP and DEPT [ Using 8.1.5 on win2000 ]

How can I write a Microsoft Access db file for that tables
through ORACLE or D2k?

Thanks
Abhi

          Followup:

see the link above?  it describes the generic connectivity that lets us access
an odbc data source.

d2k can code directly using odbc.

      Rating: 5
Compatiable SQL
      for Oracle and SQL Server  December 30, 2003

Reviewer:  A reader
Hi Tom,

Unfortunately our application has to support Oracle and SQL Server at the same
time. One trouble we had in SQL Server is that it doesn't support Oracle
hierarchy query like "CONNECT BY".

I came up with a function which returns the list of child IDs for given root ID,
which works in both databases.

For example, get_children(20) may returns a string of '21,23,30,220,22'.

Obviously this has some limitation such as the list cannot be too long. However
it's not the concern right now.

So at least now we can use the same code (writen in C#) for both databases,
which works like this:

1. get the list of child IDs for the given root ID.
2. compose the whole SQL statement such as 'SELECT * FROM orgs WHERE orgid IN ('
+ <list of IDs> + ')'

As compared to the original query in Oracle, which may look like
'SELECT * FROM orgs WHERE orgid IN (
   SELECT orgid FROM orgassociations
   START WITH parentorgid = 20
   CONNECT BY PRIOR orgid = parentorgid)'.

What I want to ask you:

1. Does this solution look workable to you?
2. Will Oracle "CONNECT BY" become ANSI standards sometime?
3. Is there a document that I can refer to for the "minimal intersect of SQL
syntax supported by all major RDBMS vendors"?

Thanks.

          Followup:

sorry -- i don't do "database independence"

all you get from that is an application that runs pretty bad on one database and
horrible on all of the rest.  whichever you develop on -- you run pretty bad on
and everyone else is hack.

You've basically totally crippled both products -- totally.

My solution would be to hide all of this stuff in stored procedures, have 0
lines of code in the proprietary language C# that start with SELECT, INSERT,
UPDATE, DELETE -- only call stored procedures that return result sets

and maximize the $$$'s you and your customers spend on the underly databases to
get the best possible performance from them.  This works -- it works well.

doen't matter if connect by is ansi or not.  ansi is soooo huge these days that
no vendor will ever be 100% compliant.

just speaking from the voice of "been there done that".  before Oracle I did
just database things using sqlserver, informix, db2, ingress, oracle and others.
 database independence -- nope -- doesn't happen unless you write your own
database like SAP or Peoplesoft do with their middle tier caches -- and even
then they can only support a small handful of databases using simple keyed
reads.

December 31, 2003 Rajesh -- Thanks for the question regarding "Data
      Loading Approach", version 8.1.7

      You Asked
Hi Tom,
I have to load a number of tables in my application. The source data is in a
flat file. At one particular time, there can be 1 record or 100 records or more
than that. Each table has a key and based on that key we have to load the data
in a loop to process all the records in the flat file.

Now there are 2 approaches in consideration now -
1. If the matching key data is found in target table then we have to compare
each field data from the flat file to that in oracle table and if there is any
change in any field then we have to update the record. If key from the flat file
is not present in the table then simply insert. Now the method under
consideration for this approach is as follows -
Try to insert the key in the table. If DUP_VAL_ON_INDEX happens then read the
record from the table and then do a field by field comparision of the read
record with the flat file values and if any field data is changed then update
otherwise skip to next record in flat file.

2. Try to Delete the data in the table based on the key found from the flat
file..then simply insert it.

Which approach is better in terms of REDO Activity, I/O and CPU activity ? As
per my knowledge, 1st is worse as it needs a row fetch for each row from the
physical file for each row to be processed.

Thanx in anticipation

      and we said...
Well, 1 is not worse for that reason (the delete has to fetch a row just as well
-- we need to read it to delete it).

For so few records (hundreds) the approach of:

begin
  insert into t values ( .... );
exception
   when dup_val_on_index then
       update ... (don't even BOTHER seeing if they differ, just update it)
end;

works very well.  In Oracle9i, there is a new MERGE command that'll let you do
this in one step as well.
Review & Followup

      Rating: 3
Loading data
      from excel to oracle work sheet  September 21, 2001

Reviewer:  Wells
Tom

This is kind of an emergency.

Can you tell us how to load data from a excel worksheet into an oracle table.
Can you tell us how to load data from a flat file into an oracle table.

          Followup:

file save the excel spreadsheet as delimited data.

Now its a flat file.

flat files are loaded using sqlldr - see $ORACLE_HOME/rdbms/demo/ulcase*.ctl for
example control files and the server utilities guide for complete details (or my
book -- i have a chapter that covers the ins and outs of sqlldr).

      Rating:
      5
DUP_VAL_ON_INDEX Insert/Update approach  January 29,
      2002
Reviewer:  Jiten Padhiar  from UK, England

We have got similar procedures (using Oracle 8.0.5), where we attempt to insert
and if the unique key is violated we then update the necessary rows.

How efficient would this approach be if we had million rows. What other
approaches are there?

We could use Insert..Select (bulk insert) but we need to report on any row level
errors.

Thanks in advance

          Followup:

Well, in 9i, there is a MERGE command

merge into EMP e1
using ANOTHER_TABLE e2
on ( e2.empno = e1.empno )
when matched then
    update set e1.sal = e2.sal
when not matched then
    insert ( empno, ename, job, mgr, hiredate, sal, comm, deptno )
    values ( e2.empno, e2.ename, e2.job, e2.mgr, e2.hiredate, e2.sal, e2.comm,
e2.deptno )
/

It would be the best way (not available until 9i however).

One method I use is:

o use sqlldr to insert all of the rows.  set errors= very very high and name the
bad file.  This step will load all of the records that don't yet exist.

o use sqlldr to load the BAD file (the duplicates) into a table that is
structurally identical to the table you want to update (some constraints,
primary keys, etc).

o do an update to update the real table with this temp table you just loaded.
see

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:288016031632
for various techniques.

      Rating: 4
Which is the
      correct approach ?  January 29, 2002
Reviewer:
      A reader
Dear Sir,

While inserting (or updating/deleting) a row into a table from pl/sql function,
which is the correct approach ? and why ?

(1) Check the record exists or not, then insert

begin
    select count(*) into v_RecordsFound
        from t
        where pk = p_pk;

    if v_RecordsFound > 0 then
        return 'Record already exists'
    else
        insert into t values (....);
    end if;
end;

OR

(2) direct insert record into table and handle the exception

begin
    insert into t values (....);
exception
    when dup_val_on_index then
        return 'Record already exists'
end;

          Followup:

insert, catch the dup_val_on_index and return.

the select check in the first piece of code is a bug.  There is nothing
preventing the SELECT from returning 0 and still having the insert fail on
dup_val_on_index (consider if two people run this at the same time with the same
values -- both will see zero rows, one will be able to insert, the other will
fail)

Do the least amount of work -- try the insert and check for failure.

      Rating:
      5
DUP_VAL_ON_INDEX  February 14, 2002

Reviewer:  Jiten  from UK
Just following up on the earlier review.

Since we can't use the MERGE (our database is on 8.0.5), if on the 'insert
statements' we use dup_val_on_index to capture violations then do 'updates',
what overhead is there (i.e. rollback, performance time)?  Is it better practice
to identify new and old sets of data before processing them appropriately -
insert on new data and update on old data.

Thanks in advance

          Followup:

It is the only way to do it (well there are two ways).

IF you believe most of the data should be inserted and few will be updated then:

   begin
       insert
   exception
       when dup_val_on_index then
           update
   end;

is correct.  If you believe most of the data will be UPDATED with a few inserts
then:

   update
   if ( sql%rowcount = 0 )
   then
      insert
   end if;

would be more appropriate.

      Rating: 4
Data Loading
      Approach  November 17, 2003
Reviewer:
      ARChary  from S'Pore
Hi,
Need your advise.
In my application I am loading 300,000 rows into a table for this i am using
direct insert (insert into <<tablename>> select col1, col2.. from <<tablename>>.
Don't have any calculation nothing. It is taking around 2hours to load. Could
you please suggest me how can I speed up this process??

Thanking you in advance.

ARChary

          Followup:

that is not a direct path insert (no append)

o does table being loaded have indexes
o what does SQL_TRACE show you, what is the query plan for the select -- is the
insert happening in 10 seconds but the query takes 2 hours

      Rating: 5
be wary of
      dumping excel files  November 19, 2003
Reviewer:
      Kevin  from Connecticut, USA
This all works well, we do lots of it where I work, but we have also found the
Excel has several quirks about it you must watch out for.  Just converting a
file to tab delimited (i.e. .txt options) may not be enough.  We find that for
text strings that contain certain characters (comma comes to mind), Excel wraps
the string in double quotes.  This is not wrong for Excel as it is the proper
definition of an ascii delimited file, but you need to make certain what ever
tool you use to load this data handles this file format correctly (sql*loader
would, but a home grown pl/sql procedure or 3GL load program might not).
Additionally, if there are unusual characters in your file they can get
translated to other characters.  I have seem many instances of question marks
popping up in my text files after dumping them from Excel.  Research seems to
indicate this is do to some poor character in the Excel file that does not
translate to simple text so Excel did his best.  Lastly, Excel is notorious for
dropping leading zeroes off numbers that you may want to keep.  The issue is
that although the string is in fact a number (all numeric digits), you don't
want to treat it like a number but rather as a text element.  You must indicate
this to Excel at the time the file is loaded into Excel, or created, not at dump
time.  Thus if you move the Excel file around and open it up or convert it many
times, this can get messed up real bad.  An example of same would be a code of
some kind that is all numeric and starts with a zero.  The bottom line is that
some examination of the text file is in order after you dump it.

Good luck

      Rating: 4
Good
      Aproach  December 30, 2003
Reviewer:
      Natasha  from UAE
Further to the above discussions, I have a new requirement
which has to import the details of Excel sheet details onto
Oracle database.

I have gone thru the shortcut which you have explained in
a good manner. Thxs alot from the bottom of my heart.

Currently I have few things which I wanna clarify from u..
Have a look at the below mentioned details and let me
know which practice will be the best suitable (performance)

-----------------------------------------------------------

1. A procedure which accepts the location of 'CSV' file
and by using 'SQLLDR80' I'll dump it into mapping tables (temporary) which has
the same structure of the original tables but I have removed the unique
constraints so as to upload whatever is there in the excel to all the four
different database tables.

2. Now the raw data is available in the respective db tables.

3. Pls. find the sample code which I'll be doing it inside..

   .......
   ...........
   Type Map_Table1 Is Table of <Temp_Map_Table_1>%RowType;
   Type Map_Table2 Is Table of <Temp_Map_Table_2>%RowType;
   Type Map_Table3 Is Table of <Temp_Map_Table_3>%RowType;
   Type Map_Table4 Is Table of <Temp_Map_Table_4>%RowType;

   rc_Tab1 Map_Table1;
   rc_Tab2 Map_Table2;
   rc_Tab3 Map_Table3;
   rc_Tab4 Map_Table4;

   Begin

   -- Thru Dynamic SQL, I'll call SQLLDR80 and upload the
      CSV file data into Temporary Mapping Tables.

   -- Once Upload is done, I'll check the size of BAD file.

   -- If it's more than 2 b, then it reads the bad file and
      stops proceeding further.

   -- If no contents are there in the bad file then I'll
      start dumping into our original tables with business
      validations.

  Select * Bulk Collect Into rc_Tab1 From <Map_Table_1>;

   For i in 1..4 Loop
       Execute Immediate 'Select * Bulk Collect Into
                          rc_Tab'||i||'  From
                          Map_Table_'||i;
   End Loop;

   For nCnt1 In 1..rc_Tab1.Count Loop
       -- Begin Validations
       ......
       .......
       -- End Validation

    Begin
          -- Insert into Master Table .
          -- If Exceptions occurs then it logs the details
             into Bad.file
          -- If everything went smoothly then logs the
             details into process.file

      -- Similarly all details gets uploaded likewise
             in the detailed tables.
        End
   End Loop;

Method 2: Should I use UTL_FILE utility and while reading
each column (separated in quotes), do the validations and
insert into master as well as respective detail tables..

Tom, request you to suggest if my approach is wrong. I would required expert
suggestions to enhance my skills.

Thxs in advance

Natasha

          Followup:

depends on how big the files are.

you won't be "calling" sqlldr via dynamic sql.

      Rating:
      3
Continuation  December 30, 2003
Reviewer:
      Natasha  from UAE
Tom,

how to give the complete file location for the infile parameter.

I tried to use like this..

HOST(SQLLDR Scott/Tiger@db Control=demo1.txt data=c:\personal\data\mydata.csv

Nothing is firing....I have recheck the location of the file and it's there
in the same directory what I have mentioned in the data parameter.

Pls. suggest

Natasha

          Followup:

what "infile" parameter?

perhaps you need to specify exactly where your control file is as well - since
forms current working directory might be something totally different then you
expect.

December 31, 2003 Leon -- Thanks for the question regarding
      "efficient upsert ", version 8.1.7

      You Asked
Hi Tom,

In order to implement upsert logic (update if exists/insert if does not) in
batch processing I use one of following
1)    try to insert, if fail on PK constraint, handle exception, update.
2)    try to update, count sql%rowcount, if 0 insert.

Merge statement is available now but difficult to use in stored procedure when
you need to execute statement one record at a time and source is not necessary
result of a query.

My questions are:
1)    What is more efficient ((1) or (2)) from minimizing redo logs point of
view? Does oracle generate redo for updates that return 0 rows and for insert
that fail due to constraint?
2)    Is merge statement efficient enough in order to consider starting to use
it and fool it by inserting data into global temporary table and calling merge?
Is it possible to use pl/sql table of records instead of temporary table? Is it
possible to use merge with PL/SQL variables as a source?

Thanks
Leon

      and we said...
If you know the data, you can optimize this processing

o if you believe MOST rows will be updated, update and if sql%rowcount=0 insert
o if you believe MOST rows will be inserted, insert and if error then update

if you don't know, you just have to guess -- the redo generated will be the same
either way.

Merge is very nice, only in 9i -- not 817 as you have listed.  In 9i you have
pipelined functions (search this site for pipelined) so yes, you can have plsql
act as a table source OR use global temporary tables.

Now, here is an example using 9i with the MERGE and a "temp" table.

ops$tkyte@ORA920.US.ORACLE.COM> create global temporary table ao
  2  on commit PRESERVE ROWS
  3  as
  4  select *
  5    from all_objects
  6   where 1=0;

Table created.

ops$tkyte@ORA920.US.ORACLE.COM> alter table ao
  2  add constraint
  3  ao_pk primary key(object_id);
Table altered.

ops$tkyte@ORA920.US.ORACLE.COM> insert into ao select * from all_objects;
29311 rows created.

ops$tkyte@ORA920.US.ORACLE.COM> create table t1
  2  as
  3  select *
  4    from all_objects
  5    where rownum<= (select count(*)/2 from all_objects);
Table created.

ops$tkyte@ORA920.US.ORACLE.COM> alter table t1 add constraint t1_pk primary
key(object_id);
Table altered.

ops$tkyte@ORA920.US.ORACLE.COM> analyze table t1 compute statistics
  2  for table for all indexes for all indexed columns;
Table analyzed.

ops$tkyte@ORA920.US.ORACLE.COM> create table t2
  2  as
  3  select *
  4    from all_objects
  5   where rownum<= (select count(*)/2 from all_objects);
Table created.

ops$tkyte@ORA920.US.ORACLE.COM> alter table t2 add constraint t2_pk primary
key(object_id);
Table altered.

ops$tkyte@ORA920.US.ORACLE.COM> analyze table t2 compute statistics
  2  for table for all indexes for all indexed columns;
Table analyzed.

So, t1 and t2 are for all intents and purposes the same -- we'll upsert them
from AO..

ops$tkyte@ORA920.US.ORACLE.COM> declare
  2      l_start number;
  3      l_run1  number;
  4      l_run2  number;
  5
  6      type rc is ref cursor;
  7      l_cur rc;
  8  begin
  9      insert into run_stats select 'before', stats.* from stats;
 10
 11      l_start := dbms_utility.get_time;
 12      merge into t1
 13      using ao on ( t1.object_id = ao.object_id )
 14      when matched then
 15          update set owner = ao.owner,
 16            object_name = ao.object_name,
 17            subobject_name = ao.subobject_name,
 18            data_object_id = ao.data_object_id,
 19            object_type = ao.object_type,
 20            created = ao.created,
 21            last_ddl_time = ao.last_ddl_time,
 22            timestamp = ao.timestamp,
 23            status = ao.status, temporary = ao.temporary,
 24            generated = ao.generated,
 25            secondary = ao.secondary
 26      when not matched then
 27          insert ( OWNER, OBJECT_NAME,
 28                   SUBOBJECT_NAME, OBJECT_ID,
 29                   DATA_OBJECT_ID, OBJECT_TYPE,
 30                   CREATED, LAST_DDL_TIME,
 31                   TIMESTAMP, STATUS, TEMPORARY,
 32                   GENERATED, SECONDARY )
 33          values ( ao.OWNER, ao.OBJECT_NAME,
 34                   ao.SUBOBJECT_NAME, ao.OBJECT_ID,
 35                   ao.DATA_OBJECT_ID, ao.OBJECT_TYPE,
 36                   ao.CREATED, ao.LAST_DDL_TIME,
 37                   ao.TIMESTAMP, ao.STATUS, ao.TEMPORARY,
 38                   ao.GENERATED, ao.SECONDARY);
 39      commit;
 40      l_run1 := (dbms_utility.get_time-l_start);
 41      dbms_output.put_line( l_run1 || ' hsecs' );
 42
 43      insert into run_stats select 'after 1', stats.* from stats;
 44      l_start := dbms_utility.get_time;
 45      for x in ( select * from ao )
 46      loop
 47          update t2 set ROW = x where object_id = x.object_id;
 48          if ( sql%rowcount = 0 )
 49          then
 50              insert into t2 values X;
 51          end if;
 52      end loop;
 53      commit;
 54      l_run2 := (dbms_utility.get_time-l_start);
 55      dbms_output.put_line( l_run2 || ' hsecs' );
 56      dbms_output.put_line
 57      ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) || '% of the time' );
 58
 59      insert into run_stats select 'after 2', stats.* from stats;
 60  end;
 61  /
424 hsecs
2116 hsecs
run 1 ran in 20.04% of the time

PL/SQL procedure successfully completed.

merge is faster wallclock wise then procedural code and...

ops$tkyte@ORA920.US.ORACLE.COM> select a.name, b.value-a.value run1,
c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

NAME                                 RUN1       RUN2       DIFF
------------------------------ ---------- ---------- ----------
...
STAT...redo entries                 30661      45670      15009
LATCH.redo allocation               30780      46012      15232
STAT...db block gets                47239      62630      15391
STAT...table scan blocks gotte        597      29311      28714
n

STAT...buffer is not pinned co        693      29409      28716
unt

STAT...index fetch by key               9      29320      29311
STAT...db block changes             60912      90825      29913
STAT...no work - consistent re        260      36398      36138
ad gets

STAT...calls to get snapshot s        450      44200      43750
cn: kcmgss

STAT...execute count                   63      44015      43952
LATCH.shared pool                     463      44606      44143
STAT...consistent gets - exami        729      51860      51131
nation

STAT...recursive calls                838      73844      73006
STAT...consistent gets               1748      88444      86696
LATCH.library cache pin               436      88558      88122
LATCH.library cache                   757      89093      88336
STAT...session pga memory           95732          0     -95732
STAT...session logical reads        48987     151074     102087
LATCH.cache buffers chains         212197     405774     193577
STAT...session pga memory max      947700          0    -947700
STAT...redo size                 12908776   16933156    4024380

100 rows selected.

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

it did less work -- generated 75% of the redo and so on...

Now, I ran that as a pipelined function (two merges -- merge vs merge) and the
results were that the merge using a TABLE bested the pipelined function with
about the same ratios.

...
ops$tkyte@ORA920.US.ORACLE.COM> create type myScalarType as object (
  2   OWNER               VARCHAR2(30),
  3   OBJECT_NAME         VARCHAR2(30),
  4   SUBOBJECT_NAME      VARCHAR2(30),
  5   OBJECT_ID           NUMBER,
  6   DATA_OBJECT_ID      NUMBER,
  7   OBJECT_TYPE         VARCHAR2(18),
  8   CREATED             DATE,
  9   LAST_DDL_TIME       DATE,
 10   TIMESTAMP           VARCHAR2(19),
 11   STATUS              VARCHAR2(7),
 12   TEMPORARY           VARCHAR2(1),
 13   GENERATED           VARCHAR2(1),
 14   SECONDARY           VARCHAR2(1)
 15  )
 16  /

Type created.

ops$tkyte@ORA920.US.ORACLE.COM> create type myArrayType as table of myScalarType
  2  /

Type created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace function ao_function return
myArrayType
  2  PIPELINED
  3  as
  4  begin
  5      for ao in (select * from all_objects)
  6      loop
  7          pipe row( myScalarType( ao.OWNER, ao.OBJECT_NAME,
  8                   ao.SUBOBJECT_NAME, ao.OBJECT_ID,
  9                   ao.DATA_OBJECT_ID, ao.OBJECT_TYPE,
 10                   ao.CREATED, ao.LAST_DDL_TIME,
 11                   ao.TIMESTAMP, ao.STATUS, ao.TEMPORARY,
 12                   ao.GENERATED, ao.SECONDARY) );
 13      end loop;
 14      return;
 15  end;
 16  /

Function created.

....
ops$tkyte@ORA920.US.ORACLE.COM> declare
  2      l_start number;
  3      l_run1  number;
  4      l_run2  number;
  5
  6      type rc is ref cursor;
  7      l_cur rc;
  8  begin
  9      insert into run_stats select 'before', stats.* from stats;
 10
 11      l_start := dbms_utility.get_time;
 12      merge into t1
 13      using ao on ( t1.object_id = ao.object_id )
 14      when matched then
 15          update set owner = ao.owner,
 16            object_name = ao.object_name,
 17            subobject_name = ao.subobject_name,
 18            data_object_id = ao.data_object_id,
 19            object_type = ao.object_type,
 20            created = ao.created,
 21            last_ddl_time = ao.last_ddl_time,
 22            timestamp = ao.timestamp,
 23            status = ao.status, temporary = ao.temporary,
 24            generated = ao.generated,
 25            secondary = ao.secondary
 26      when not matched then
 27          insert ( OWNER, OBJECT_NAME,
 28                   SUBOBJECT_NAME, OBJECT_ID,
 29                   DATA_OBJECT_ID, OBJECT_TYPE,
 30                   CREATED, LAST_DDL_TIME,
 31                   TIMESTAMP, STATUS, TEMPORARY,
 32                   GENERATED, SECONDARY )
 33          values ( ao.OWNER, ao.OBJECT_NAME,
 34                   ao.SUBOBJECT_NAME, ao.OBJECT_ID,
 35                   ao.DATA_OBJECT_ID, ao.OBJECT_TYPE,
 36                   ao.CREATED, ao.LAST_DDL_TIME,
 37                   ao.TIMESTAMP, ao.STATUS, ao.TEMPORARY,
 38                   ao.GENERATED, ao.SECONDARY);
 39      commit;
 40      l_run1 := (dbms_utility.get_time-l_start);
 41      dbms_output.put_line( l_run1 || ' hsecs' );
 42
 43      insert into run_stats select 'after 1', stats.* from stats;
 44      l_start := dbms_utility.get_time;
 45      merge into t2
 46      using (select * from TABLE(ao_function)) ao on ( t2.object_id =
ao.object_id )
 47      when matched then
 48          update set owner = ao.owner,
 49            object_name = ao.object_name,
 50            subobject_name = ao.subobject_name,
 51            data_object_id = ao.data_object_id,
 52            object_type = ao.object_type,
 53            created = ao.created,
 54            last_ddl_time = ao.last_ddl_time,
 55            timestamp = ao.timestamp,
 56            status = ao.status, temporary = ao.temporary,
 57            generated = ao.generated,
 58            secondary = ao.secondary
 59      when not matched then
 60          insert ( OWNER, OBJECT_NAME,
 61                   SUBOBJECT_NAME, OBJECT_ID,
 62                   DATA_OBJECT_ID, OBJECT_TYPE,
 63                   CREATED, LAST_DDL_TIME,
 64                   TIMESTAMP, STATUS, TEMPORARY,
 65                   GENERATED, SECONDARY )
 66          values ( ao.OWNER, ao.OBJECT_NAME,
 67                   ao.SUBOBJECT_NAME, ao.OBJECT_ID,
 68                   ao.DATA_OBJECT_ID, ao.OBJECT_TYPE,
 69                   ao.CREATED, ao.LAST_DDL_TIME,
 70                   ao.TIMESTAMP, ao.STATUS, ao.TEMPORARY,
 71                   ao.GENERATED, ao.SECONDARY);
 72      commit;
 73      l_run2 := (dbms_utility.get_time-l_start);
 74      dbms_output.put_line( l_run2 || ' hsecs' );
 75      dbms_output.put_line
 76      ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) || '% of the time' );
 77
 78      insert into run_stats select 'after 2', stats.* from stats;
 79  end;
 80  /
494 hsecs
1737 hsecs
run 1 ran in 28.44% of the time

PL/SQL procedure successfully completed.

 ops$tkyte@ORA920.US.ORACLE.COM> select a.name, b.value-a.value run1,
c.value-b.value run2,
  2         ( (c.value-b.value)-(b.value-a.value)) diff
  3    from run_stats a, run_stats b, run_stats c
  4   where a.name = b.name
  5     and b.name = c.name
  6     and a.runid = 'before'
  7     and b.runid = 'after 1'
  8     and c.runid = 'after 2'
  9     and (c.value-a.value) > 0
 10     and (c.value-b.value) <> (b.value-a.value)
 11   order by abs( (c.value-b.value)-(b.value-a.value))
 12  /

....
STAT...session pga memory          104256     232480     128224
STAT...session uga memory               0     130928     130928
STAT...session uga memory max           0     130928     130928
LATCH.row cache enqueue latch         362     177614     177252
LATCH.row cache objects               448     184995     184547
LATCH.cache buffers chains         211442     493338     281896
STAT...session pga memory max      956224     166944    -789280
STAT...redo size                 12876460   14459964    1583504

106 rows selected.

It is the removal of the procedural, handwritten code here that makes the
difference.  We should strive for ways to do things SET ORIENTED (as sql likes
that best).  The less procedural code we write in general the better.

Review &
      Followup

      Rating: 4
how nice is
      it  November 25, 2002
Reviewer:  Leon
      Rzhemovskiy  from Vancouver, BC, Canada
Thanks Tom, I appreciate your fast and as knowledgeable feedback
I am running 9i as well. As you understand my question is related not to a
problem but rather to best practice.
Would you be able to say that merge is not only nice but also reasonable to
implement with pipelining going forward in 9i.
What should I expect speed, processor usage, redo? Are you aware of any problem
with it?

Thanks
Leon

      Rating:
      5
outstanding  November 26, 2002
Reviewer:
      Leon Rzhemovskiy  from Vancouver, BC, Canada
Thanks for detail explanations and code examples.

Comment on handwritten code is unfortunately applicable only to performance
side. Practical application may require procedural approach due to exception
handling, auditing etc.

      Rating: 5
Good comparison
      stats  February 18, 2003
Reviewer:  Kevin
       from SD, CA USA
Hi Tom,

The comparison numbers are very impressive and convincible. But I can't find the
table structure for RUN_STATS and table/view STATS - is this a data dictionary
table/view?

Thanks.

Kevin

          Followup:

http://asktom.oracle.com/~tkyte/runstats.html

      Rating: 5
Merge +
      variables  March 13, 2003
Reviewer:  Robert
       from Ireland
Hi Tom,
Can you use variables in place of tables in the merge statement.  For example if
your table to be upserted has 4 fields and these 4 fields are passed in as
parameters.  How does that work as you can't have:

merge into t1
using ao on ( t1.object_id = ao.object_id )

when there is only one table.  I tried this using dual as the dummy table and
although it compiled it never seemed to work.

Robert.

          Followup:

identifiers cannot be bound in ANY statement -- the plan would totally change.

you would have to use dynamic sql.

      Rating: 4
Is Upsert
      possible for single table?  June 12, 2003

Reviewer:  Tony  from India
MERGE statement needs two tables to implement upsert.
Can't we do upsert in one table?
For example: In emp table, I want to update if record exists, else insert.

          Followup:

update it WITH WHAT?
insert it FROM WHERE?

you need that other table see -- it has the data to update with, and the data
that needs to be inserted.

EMP is the target, so, where is the source of the data?

If you mean "for a single row", use "select :bv1, :bv2, .... from dual" as the
source and bind in the single row values.  That'll either update or insert a
single row.

      Rating: 5
What's faster
      delete/insert or upsert?  June 12, 2003
Reviewer:
      Peter Tran  from Houston, TX USA
Assuming that all columns can be replaced, is it faster to bulk delete and bulk
inserts, or upsert using the merge technique you outlined above?

Thanks,
-Peter

          Followup:

truncate+insert /*+ append */ into a nologging table (with a backup of that
tablespace right afterwards of course) would most likely be faster, especially
if it were

truncate
disable all indexes
insert /*+ append */
rebuild in parallel/nologging the indexes
backup

      Rating: 5
Clarification
      to my earlier question  June 12, 2003
Reviewer:
      Peter Tran  from Houston, TX USA
Tom,

What if I can't do a complete truncate of the table?  What if my table currently
has 100 million rows, and now I have say a million records that can either be
new records or update of the existing records.

Currently, we would delete all duplicate records in the table and then bulk
insert the new 1 million records.

This was faster then to do an update which throws an exception if the record
doesn't exist, then do an insert.

Thanks,
-Peter

          Followup:

that is because row by row operations are almost always slower (there are cases,
but in general)...

a merge would be called for here.  I have a 3.8 million row table and want to
merge 1% more

big_table@ORA920> merge into big_table bt
  2  using merge_data md on ( bt.id = md.id )
  3  when matched then
  4    update set owner = md.owner,
  5               object_name = md.object_name,
  6               subobject_name = md.subobject_name,
  7               data_object_id = md.data_object_id,
  8               object_type = md.object_type,
  9               created = md.created,
 10               last_ddl_time = md.last_ddl_time,
 11               timestamp = md.timestamp,
 12               status = md.status, temporary = md.temporary,
 13               generated = md.generated,
 14               secondary = md.secondary
 15  when not matched then
 16     insert ( id, OWNER, OBJECT_NAME,
 17              SUBOBJECT_NAME, OBJECT_ID,
 18              DATA_OBJECT_ID, OBJECT_TYPE,
 19              CREATED, LAST_DDL_TIME,
 20              TIMESTAMP, STATUS, TEMPORARY,
 21              GENERATED, SECONDARY )
 22     values ( md.id, md.OWNER, md.OBJECT_NAME,
 23              md.SUBOBJECT_NAME, md.OBJECT_ID,
 24              md.DATA_OBJECT_ID, md.OBJECT_TYPE,
 25              md.CREATED, md.LAST_DDL_TIME,
 26              md.TIMESTAMP, md.STATUS, md.TEMPORARY,
 27              md.GENERATED, md.SECONDARY);

38172 rows merged.

Elapsed: 00:05:09.51

Statistics
----------------------------------------------------------
        414  recursive calls
      84182  db block gets
      96814  consistent gets
      45069  physical reads
   19120100  redo size
        791  bytes sent via SQL*Net to client
       1850  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      38172  rows processed

Or, I delete and insert:

big_table@ORA920> delete from big_table
  2  where id in ( select id from merge_data );

19086 rows deleted.

Elapsed: 00:08:33.78

Statistics
----------------------------------------------------------
        175  recursive calls
     135948  db block gets
    3872806  consistent gets
      78021  physical reads
   15651020  redo size
        793  bytes sent via SQL*Net to client
        832  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      19086  rows processed

big_table@ORA920> insert /*+ APPEND */ into big_table select * from merge_data;

38172 rows created.

Elapsed: 00:01:19.03

Statistics
----------------------------------------------------------
         39  recursive calls
      98282  db block gets
        640  consistent gets
      19373  physical reads
    9934016  redo size
        778  bytes sent via SQL*Net to client
        829  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
      38172  rows processed

and I even gave the insert the benefit of append, something you might not do
in the real world (that 1% of space isn't going to be reused with an append)

      Rating: 5
Efficient
      Upsert  June 12, 2003
Reviewer:  Mark
       from Cincinnati
Simply wonderful. I really admire your depth of your knowledge.

      Rating: 5
Awesome
      example  June 12, 2003
Reviewer:  Peter Tran
       from Houston, TX USA
I concur with Mark.  Your example does a wonderful job of proving your point.

One last follow up question, I'm assuming there's going to be overhead in
inserting data into the merge_data table.  Assuming that the merge_data table is
a GTT, then can I assume the following to be true?

runtime(insert into GTT + using merge) < runtime (delete/insert)

A simple yes/no would be sufficient.

Best,
-Peter

          Followup:

I assumed merge_data already exists?

but in general, populating merge_data+merging I would imagine would be more
efficient then DELETE by key, INSERT..

simple yes/no answers are things I hesitate to give ;)

      Rating: 4
Merge with
      TABLE(CAST...  July 16, 2003
Reviewer:  Jan

Dear Tom,

is the MERGE command not supported in following case or is there some other way
how to do this?:

MERGE INTO normal_table t
     USING TABLE(CAST(my_collection
                      AS my_collection_type))c
...
...

It gives me an error:
PL/SQL: ORA-00903: invalid table name

It works if I use normal table instead of collection.

Oracle version 9.2.0.1.0

Regards, Jan

          Followup:

You have to "select" from the function

ops$tkyte@ORA920LAP> create type myScalarType as object
  2  ( x int, y date )
  3  /

Type created.

ops$tkyte@ORA920LAP> create type myArrayType as table of myScalarType
  2  /

Type created.

ops$tkyte@ORA920LAP> create or replace function myfunction return myArrayType
  2  as
  3          l_data myArrayType :=
  4             myArrayType( myScalarType( 1, sysdate ),
  5                          myScalarType( 2, sysdate+2 ),
  6                          myScalarType( 3, sysdate+3 ) );
  7  begin
  8          return l_data;
  9  end;
 10  /

Function created.

ops$tkyte@ORA920LAP> create table t
  2  as
  3  select rownum x, sysdate-rownum y
  4    from all_objects
  5   where rownum <= 3;

Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> merge into t
  2  using ( select * from TABLE( myFunction ) ) c
  3  on ( t.x = c.x )
  4  when matched then update set y = c.y
  5  when not matched then insert ( x,y ) values ( c.x, c.y );

3 rows merged.

      Rating:
      3
merge  July 28, 2003
Reviewer:  vj
       from in
Hello Tom,

I require an help in Merge Command

My Table Script :

CREATE TABLE TRANSSRL (
  MY_CODE  VARCHAR2 (12)  NOT NULL,
  PREFIX     VARCHAR2 (3)  NOT NULL,
  YEAR       NUMBER (4)    NOT NULL,
  CURR_NO    NUMBER (16)   NOT NULL,
  CONSTRAINT PK_TRANS_SRL
  PRIMARY KEY ( CURR_NO, MY_CODE, PREFIX, YEAR ) )
/

--For your demo i insert a record

insert into transsrl values('PBM','GE',2002,31)
/

--My Merge Statment
merge INTO TRANSSRL a
USING
(
SELECT
       my_code,prefix,year,curr_no
FROM
     TRANSSRL
WHERE
      my_code = 'PBM' AND
      prefix = 'GE' AND
      year = 2002
) b
ON
(
  a.my_code = b.my_code AND
  a.prefix = b.prefix AND
  a.year = b.year
)
WHEN matched THEN UPDATE SET a.curr_no = NVL(curr_no,0) +1
WHEN NOT matched THEN INSERT (my_code,prefix,year,curr_no) VALUES
('PBM','GE',2002,1)

This statement executes properly, since data is available for this criteria ,
and it updates the currno column from 31 to 32..

If i try to change the year so that "when not matched" can happen...it fails..

i changed the year from 2002 to 2003

merge INTO TRANSSRL a
USING
(
SELECT
       my_code,prefix,year,curr_no
FROM
     TRANSSRL
WHERE
      my_code = 'PBM' AND
      prefix = 'GE' AND
      year = 2003
) b
ON
(
  a.my_code = b.my_code AND
  a.prefix = b.prefix AND
  a.year = b.year
)
WHEN matched THEN UPDATE SET a.curr_no = NVL(curr_no,0) +1
WHEN NOT matched THEN INSERT (my_code,prefix,year,curr_no) VALUES
('PBM','GE',2003,1)

Logically here "it is not matched" so i expected another record in the table
with

PBM,GE,2003,1

..But it is not happening..can you help me

          Followup:

umm, the query:

(
SELECT
       my_code,prefix,year,curr_no
FROM
     TRANSSRL
WHERE
      my_code = 'PBM' AND
      prefix = 'GE' AND
      year = 2003
)

returns no data -- that is why -- there is nothing to "match" or "not match" to
at all....

      Rating:
      3
  July 29, 2003
Reviewer:  vj
       from in
Tom,

What is the alternate for me to implement the merge in this scenario ??...i got
to check whether record exists for the Primary Key..if no record exists then
Insert Otherwise Update..Currently we have a block of pl/sql code to do the
same..Would be great if i can implement through merge as this would be called
very frequently.

          Followup:

if the set of data you are merging on CONTAINS NO DATA, then there is no data to
match to or insert.

that is the problem.  your data to merge with is EMPTY.  we do not "make up
data"

      Rating: 4
Great
      stuff  August 20, 2003
Reviewer:
      A reader  from East Coast
If I have a database (9i) and in the MERGE statement the USING clause selects
through DB Link (that database is 8i) is it possible to use MERGE?
Something like:

MERGE into T1
  (using Select * from T2@DB_LINK_T2 ) T2
 on (T1.Id = T2.Id)
WHEN MATCHED then
  update set  ...
WHEN NOT MATCHED then
  insert (id, ... )
      value (id, ... ) ;

          Followup:

ops$tkyte@ORA920> select * from v$version@ora817dev;

BANNER
----------------------------------------------------------------
Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
PL/SQL Release 8.1.7.4.0 - Production
CORE    8.1.7.0.0       Production
TNS for Solaris: Version 8.1.7.4.0 - Production
NLSRTL Version 3.4.1.0.0 - Production

ops$tkyte@ORA920> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
PL/SQL Release 9.2.0.3.0 - Production
CORE    9.2.0.3.0       Production
TNS for Linux: Version 9.2.0.3.0 - Production
NLSRTL Version 9.2.0.3.0 - Production

ops$tkyte@ORA920> @a
ops$tkyte@ORA920> merge into emp
  2  using emp@ora817dev e2 on ( emp.empno = e2.empno )
  3  when matched then update set emp.ename = e2.ename
  4  when not matched then insert ( empno ) values ( e2.empno )
  5  /

14 rows merged.

      Rating: 3
Using
      sql%rowcount to update user activity.  August 26, 2003

Reviewer:  ht  from California
Tom,
I'm using your technique to update records, retrieve the sql%rowcount, and
insert if sql%rowcount=0.

How can I track a range of times that a user is logged on?  For example, if the
user appears in v$session, I would like to insert sysdate into
useractivity.begin_time.  If the user is still logged on when the check runs
again, I would update useractivity.end_time.  If the user logs off, I would like
to do nothing (because the proc won't be called since v$session would return no
records for that user).  useractivity.end_time would always be updated with the
last time the proc found that the user was logged in.

I'm hung up on figuring out a way to have multiple records in the useractivity
table that would show 1 record for each duration of time the user was logged in.
 For example:
user begin end
__________________
user1 5pm 5:15pm
user1 5:35pm 6:55pm
etc ...

I'm trying to avoid:
user1 5pm
user1 5:01pm
user1 5:02pm
user1          5:15pm

I hope I've explained my problem clearly.

Thanks in advance,
ht

          Followup:

you can just enable auditing and audit connects.  that'll get the "begin time
and end times" for you.

a simple outer join to v$session -- or better yet a scalar subquery to v$session
-- can be used to fill in the missing end times.

      Rating: 4
Can you provide
      a sample?  August 27, 2003
Reviewer:  ht
       from California
Tom,
Thanks for the auditing tip.
Can you provide an example on how I could use an outer join or scalar subquery
to update a session's "end_time"?  I still am not able to figure out how I could
have a record for each duration that my proc captures a user's session.
I understand how to write 1 record each time the user is logged in but I would
like to have 1 record for each duration:

user1  5pm-6pm
user1  7pm-8pm

Currently, if my update fails, I insert.  The problem is that I end up with
this:

user1 5pm-8pm

This doesn't tell me that user1 was not on the system between 6pm and 7pm.

The reason I'm pursuing this with a non-auditing solution is that I would also
like to capture other non-auditable events such as the duration that
transactions/second are over 50.

Thanks,
ht

          Followup:

use auditing, you'll get the right answer, no muss, no fuss.

I cannot fix the 6-7pm problem, only auditing can.

      Rating: 3
How about
      tracking periods when a file is offline?  August 27, 2003

Reviewer:  ht  from California
Tom,
I can use auditing for the user activity tracking.  But what about tracking when
a datafile is offline (or other db events)?  Is there a way that I can have 1
record for the duration a datafile is offline without having to write 12 records
every hour (if I check every 5 minutes)?
TIA
ht

          Followup:

you would have to

a) query what you thought the status was -- online or offline
b) compare that to what the status is
c) if different (or a returns nothing) then log B

      Rating: 5
Of
      course!  August 27, 2003
Reviewer:  ht
       from California
Tom,
I was missing a simple flag that needs to updated before a new record is
inserted.  If an update returns no rows, I update the flag and insert a new
record.  Thanks for pointing out the obvious.
ht

      Rating:
      5
  November 07, 2003
Reviewer:
      A reader
Can you show the same logic (upsert ) with 8i (817)

          Followup:

takes an insert + update in 8i

ops$tkyte@ORA920PC> create table t1 ( x int primary key, y int );

Table created.

ops$tkyte@ORA920PC> create table t2 ( a int primary key, b int );

Table created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert into t1 values ( 1, 1 );

1 row created.

ops$tkyte@ORA920PC> insert into t2 values ( 1, 2 );

1 row created.

ops$tkyte@ORA920PC> insert into t2 values ( 2, 2 );

1 row created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select * from t1;

         X          Y
---------- ----------
         1          1

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> update ( select *
  2             from t1, t2
  3                    where t1.x = t2.a )
  4     set y = b;

1 row updated.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> insert into t1
  2  select *
  3    from t2
  4   where a not in ( select x from t1 )
  5  /

1 row created.

ops$tkyte@ORA920PC>
ops$tkyte@ORA920PC> select * from t2;

         A          B
---------- ----------
         1          2
         2          2

      Rating: 5
merge with
      rowtype array, is it possible?  December 16, 2003

Reviewer:  reader
Tom,

gtt(insert)+merge is great. However, I was thinking looking at rowtype data from
your link ->

http://asktom.oracle.com/pls/ask/f?p=4950:8:7709242489119134473::NO::F4950_P8_DISPLAYID,F4950_P8_CRITERIA:12226527910608,
from your link,

ops$tkyte@ORA920LAP> declare
  2          type array is table of emp%rowtype index by binary_integer;
  3
  4          l_data array;
  5  begin
  6          select * bulk collect into l_data from emp;
  7
  8          forall i in 1 .. l_data.count
  9                  insert into emp values l_data(i);
 10  end;
 11  /

Somehow, I can use l_data as source for the 'merge' command? That way I can do
all this in memory (with some reasonable size of the bulk collect)..

Thanks in advance

          Followup:

You'd need to use sql collection types for this, it could look like this:

ops$tkyte@ORA9IR2> create type myScalarType as object
  2  ( x int, y date, z varchar2(20) )
  3  /

Type created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create type myArrayType as table of myScalarType
  2  /

Type created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create table t1 ( x int, y date, z varchar2(20) );

Table created.

ops$tkyte@ORA9IR2> create table t2 ( x int, y date, z varchar2(20) );

Table created.

ops$tkyte@ORA9IR2> insert into t1
  2  select rownum, sysdate+rownum, rownum
  3    from all_users
  4   where rownum <= 5;

5 rows created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t2
  2  select rownum+3, sysdate+rownum+30, rownum+30
  3    from all_users
  4   where rownum <= 5;

5 rows created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select * from t1;

         X Y         Z
---------- --------- --------------------
         1 18-DEC-03 1
         2 19-DEC-03 2
         3 20-DEC-03 3
         4 21-DEC-03 4
         5 22-DEC-03 5

ops$tkyte@ORA9IR2> declare
  2      l_data myArrayType;
  3  begin
  4      select cast(
  5           multiset( select myScalarType(x,y,z) from t2 ) as myArrayType )
  6        into l_data
  7        from dual;
  8
  9      merge into t1
 10      using ( select t.x, t.y, t.z from TABLE(cast(l_data as myArrayType)) t
) t2
 11      on ( t1.x = t2.x )
 12      when matched then update set y = t2.y, z = t2.z
 13      when not matched then insert (x,y,z) values ( t2.x, t2.y, t2.z );
 14  end;
 15  /

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> select * from t1;

         X Y         Z
---------- --------- --------------------
         1 18-DEC-03 1
         2 19-DEC-03 2
         3 20-DEC-03 3
         4 17-JAN-04 31
         5 18-JAN-04 32
         6 19-JAN-04 33
         7 20-JAN-04 34
         8 21-JAN-04 35

8 rows selected.

      Rating: 5
Just
      great!  December 17, 2003
Reviewer:  reader

Tom,

Thanks a lot for making it so clear and simple.

Greatly appreciate your help

Have a great day!

      Rating: 4
how about
      efficient upsert in 8i ?  December 30, 2003

Reviewer:  ET  from HK
I'm still using 8i and therefore can't make use of "merge".
Can you kindly advice what's the efficient upsert code (with sample code) in 8i
?

          Followup:

takes an insert + update in 8i

look upwards on this page for that text and you'll see the example, answered
already.

December 31, 2003 James -- Thanks for the question regarding
      "Multi-language database", version 8.0.5

      You Asked
Hi, Tom

I am wondering is it possible to have a oracle database that
stores data in the database in
english, but displays the data in the localized language?

Or can we have a database that stores all data for example
product data
in various languages.

I would like to have a hub and spoke architect such that the
spokes are inn local languages
but they update the hub in english, i.e. the spoke data is
converted to english and then updated
into the hub.

Are you aware of any multi-language software for use with
oracle?

Thanks in advance!

James

      and we said...
Oracle can show error messages and such in different languages
but the data YOU put in comes out the way you put it in there.

You can have a database store text in more then one language --
its very common.  You must use a character set that supports all
of your intended languages (UTF8 supports them all including
multi-byte).

No, I am not of translation software -- but -- if you found one,
it is easy enough to integrate with the database using EXTERNAL
procedures (you can make it so that a PLSQL routine calls this
external code as if it were plsql so a trigger could store
multiple translations of the same text in the database).

Review &
      Followup

      Rating:
      5
utf8  September 05, 2001
Reviewer:
      Miguel Martinez  from Monterrey N.L.,Mexico
I'm looking for an answer about what character set i'm using and what characters
are available on my database, This answer help me to understand utf8 accept all
the characters.

thanks!!!

      Rating: 2
multi-language
      database  May 23, 2002
Reviewer:  Clara Riva
       from Spain
I think that the answer to the question is incomplete because it does not gives
hints on how to design a multilanguage database. The only hint given is the
characterset that has to be used to implement a multilanguage database.

      Rating:
      5
  September 08, 2002
Reviewer:  Abdel
      Ghani Al Masri
The answer from Tom is very useful, now oracle through utf8 supports multi
languages, which means you can write into any language in the world with the
same characterset utf8, no need to worry about the charachterset when you use
exporting and importing -if you are using utf8-, but to design multilanguage
database you should have multifields in your tables, every field contains
specified language, for example i want my syetm to support English, French,and
Arabic then when i create a table i do the following:
create table x(x_code number(2),
x_desc_english varchar2(50),
x_desc_french varchar2(50),
x_desc_arabic varchar2(50))
now the table contains three languages, the user can insert data in 3 languages
and browse to the data in any language

      Rating: 3
Coding in other
      language  October 09, 2003
Reviewer:  Reader

Hello,
Since Im from english speaking country I dont have any idea of Oracle for
Non-English. Regarding storing of data in database I could get it. How they
query in Japanese or French or any other language ? I mean Select * from
TabName. Is all the commands available in French or Japanese? And for how many
languages Oracle supports. I mean the SQL, PL/SQL and other tools.

          Followup:

they use SELECT just like we do.

it is the DATA stored in the DATABASE that is in the foreign language.

Their error messages from Oracle (which is just data) is in a foriegn language

but they use select insert update delete....

      Rating: 3
Q on multi
      language  December 31, 2003
Reviewer:  Anil
      Pant  from India
Hi,
I've gone thru ur previous response about multi language. We've an existing
database in English and client wants to view the data in French also. It means
that the same database will be used for both English and French. The front end
application is developed in PowerBuilder. While logging in the front end
application we'll have an drop down saying English or French. If the user
selects French then he'll see all the data in French. He may Insert or Update.
If the user selects English then its in English. But the data will be stored in
English only. We cannot afford to add columns. We're working on Oracle 9 on Win
NT. What we 're thinking is dynamically we'll change the NLS_Language parameter
to French or English for that session. Are we on the correct path? Do we need to
make some changes at the O/S level? I wud appreciate if u give some suggestions
?

          Followup:

umm, there is no builtin universal translator in the database.

we support YOU storing the same text in multiple languages.
we support presenting Oracle messages to you in multiple languages (cause we've
stored them in multiple languages)

If you want to show your applications text in french and english, the only thing
you can do is store both french and english text (it will require a schema
modification)

and further -- if the user enters text in english, thats is.  You won't be
seeing that in french (or vice versa) unless someone comes along and translates
it.

So, you can have all of your boiler plate text in both languages (definite
schema change!)

But, the data that is input will in general be in a single language -- unless
part of your software offering is a person who sits there and does translations
all day (don't even think that you can use software for this except for the
simpliest of text and even then....)

      Rating: 4
Multilanguage
      Database  December 31, 2003
Reviewer:
      A reader
Yes, You can store data in any language. Use UTF8 character set or a
characterset specified in the NLS specification for a specific language

December 31, 2003 satish -- Thanks for the question regarding "Date
      Calculation", version 8.1.7

      You Asked
Hello Tom,
   I was just wondering, as to how do you calculate the time which is displayed
in "AGE" column of the first page. because, I see, it in many formats as 3hrs, 9
months old, 2.3 years old, 19 hrs old etc. I'm a new commer to oracle and was
wondering what kind of date calculations you might be using..

With Warm Regards,
Sandeep

      and we said...
good old case statement:

       case when sysdate-timestamp < 1/24
            then round(24*60*(sysdate-timestamp)) || ' minutes old '
            when sysdate-timestamp < 1
            then round(24*(sysdate-timestamp)) || ' hours old '
            when sysdate-timestamp < 14
            then trunc(sysdate-timestamp) || ' days old '
            when sysdate-timestamp < 60
            then trunc((sysdate-timestamp)/7) || ' weeks old '
            when sysdate-timestamp < 365
            then round(months_between(sysdate,timestamp)) || ' months old '
            else round(months_between(sysdate,timestamp)/12,1) || ' years old '
        end age
Review & Followup

      Rating: 5
Date
      Calculation  June 03, 2003
Reviewer:  Satish

Good claricty, precise to the point. it what we all have been looking for.
Thanks Tom, you have defintely increased our learning curve on oracle.

      Rating: 5
Date
      Calucation  June 03, 2003
Reviewer:  Subbu
       from Belfast Ireland
As usual great response from Tom. Can we do the same in pre Oracle 9i? I think
we have to build a function for that with IF then ELSE loop? isn't?

          Followup:

CASE exists in 8i.

in PLSQL you might have to "hide" it in a VIEW or via dynamic sql.

      Rating: 5
Date
      Calucations  June 03, 2003
Reviewer:
      sandeep  from Delhi India
Excellent as always

      Rating: 5
Use
      DECODE  June 03, 2003
Reviewer:  Sajid Anwar
       from London
Subbu, you can use DECODE. No need to write function with IF-ELSE logic.

      Rating:
      5
Decode  June 03, 2003
Reviewer:
      sandeep  from Nagpur India
i think decode would be better option for this

          Followup:

ugg, you were only kidding right?

lets see that nasty nested decode using sign then....

      Rating: 5
PL/SQL 8i and
      DECODE  June 03, 2003
Reviewer:  Martin
      Burbridge  from MA
Aw, its not that nasty, I'd prefer DECODE in 8i over NDS, as you lose the
dependencies and other fun stuff. While I agree a view would be better it often
means involving a DBA.

SQL> select
  2      timestamp, sysdate,
  3      decode(sign(sysdate-timestamp - 1/24), -1,
  4          round(24*60*(sysdate-timestamp)) || ' minutes old ',
  5      decode(sign(sysdate-timestamp - 1), -1,
  6          round(24*(sysdate-timestamp)) || ' hours old ',
  7      decode(sign(sysdate-timestamp - 14), -1,
  8          trunc(sysdate-timestamp) || ' days old ',
  9      decode(sign(sysdate-timestamp - 60), -1,
 10          trunc((sysdate-timestamp)/7) || ' weeks old ',
 11      decode(sign(sysdate-timestamp - 365), -1,
 12          round(months_between(sysdate,timestamp)) || ' months old ',
 13          round(months_between(sysdate,timestamp)/12,1) || ' years old '
 14      ))))) age
 15  from t;

TIMESTAMP       SYSDATE         AGE
--------------- --------------- --------------------
03-JUN-03 07:58 03-JUN-03 08:05 7 minutes old
27-MAY-03 07:58 03-JUN-03 08:05 7 days old
24-MAY-03 07:58 03-JUN-03 08:05 10 days old
12-MAY-03 07:58 03-JUN-03 08:05 3 weeks old
03-JUL-01 07:58 03-JUN-03 08:05 1.9 years old
06-SEP-00 07:58 03-JUN-03 08:05 2.7 years old
24-APR-03 07:58 03-JUN-03 08:05 5 weeks old
03-JUN-03 07:59 03-JUN-03 08:05 6 minutes old

8 rows selected.

SQL>

          Followup:

a view means involving a DBA????

Boy do I feel so sorry for you.  That would just be silly.

      Rating:
      5
Nice  June 03, 2003
Reviewer:  Paul
       from UK
But wouldn't it be nicer to have "1 year, 6 months" rather than "1.5 years"?

      Rating: 5
Date
      Calculation and index usage  November 03, 2003

Reviewer:  Karma  from MA USA
Tom: How does this use index on timestamp ? i.e How would you answer question as
: Give me all records where age < = 2 which will use index on your column
timestamp ?

          Followup:

well, "age <= 2" doesn't make any sense in the context of the function I used.
I return a "character string with some text".  it is not "sortable" in any
fashion.

If I want everything less then 2 years old -- i would "where timestamp >=
add_months(sysdate,-24)"

i would never predicate on AGE, it doesn't make sense here, it is not "sortable"

but -- say it was, then you would be looking at creating a function based index
-- an index on (case .....)

      Rating: 5
Date
      Calculation and index  November 04, 2003
Reviewer:
      karma  from MA USA
Tom: I have employee table with brith_date stored as date. Now user need to
search based on 2 criteria i.e. Age and/or (bizzare requirement) to be searched
by only dd/mm. Now i am only storing birth_date so how would i index this table
to satisfy both requirements ?
I tried to create function based index for age requirement like follow and get
error : ora-01743 Only pure functions can be indexed.
CREATE INDEX CUST_BDATE
    ON employee(round(months_between(sysdate,birth_d)/12,1))
TABLESPACE INDEX01
STORAGE(BUFFER_POOL DEFAULT)
NOLOGGING
NOPARALLEL
NOCOMPRESS
/

What's the correct approach so that they can search like
a) give me all records where age > 30 and < 45 ?
b) give me all records whose b_date is on 04/11 or
c) give me all records whose b_date is tomorrow irrespective of their age ?
d) give me all records whose b_date is month from today ? etc.

Thanks

          Followup:

age -- NOT possible, think about it. your age right now is different then you
age RIGHT NOW.

age - not deterministic.

If age is in "years", then they must query:

select * from t where DOB >= add_months( sysdate, 12* :YEARS );

index DOB and done.

now, for dd/mm, you can index to_char(dob,'dd/mm')

so

a) you cannot, you must code the query correctly.  age is every changing --
always.

b) index to_char(dob)

c) see b, where to_char(dob,'dd/mm') = to_char(sysdate+1,'dd/mm')

d) where dob = add_months(sysdate,1)

      Rating: 3
forgot the
      minus sign  November 04, 2003
Reviewer:
      Freek  from Belgium
"If age is in "years", then they must query:
select * from t where DOB >= add_months( sysdate, 12* :YEARS );"

shouldn't that be:
select * from t where DOB >= add_months(sysdate, -12 * :YEARS);

regards

Freek

          Followup:

well, obviously I wanted you to bind -2 for 2 years

:)

only kidding, yes, it would be best to use -12

      Rating: 4
How To Display
      In Alternating Colours  December 01, 2003

Reviewer:  Hien  from Australia
Tom

I was just wondering what tricks you use to display the search result lines in
alternating background colours. That is, one line with white background and the
other with a shaded background.

I know one can do this with Reports but what do you use?

Thanks
Hien

          Followup:

Hien,

This actually a modified version of one of the report templates included in HTML
DB (the technology AskTom is built in).  Basically we took the report template
and modified it's classes to fit with Tom's color scheme.  The report templates
in HTML DB let you conditionally apply different row sub-templates.  In this
case there is one sub-template for even rows and one for odd rows.

What?!  You're not using HTML DB?  Well, you should at least give it a try, it
is free you know. I'm sure there are other ways to do this in other
technologies, I'm just not sure on the specifics.

There's a cool way to do it using only HTML and CSS3 selectors. The "nth-child"
pseudo class in CSS3 makes this trivial.  The only problem is, no browsers
support it yet (except MSN for Mac OS X?, what an odd combination).  If you are
interested in seeing how it works, go to

http://gallery.theopalgroup.com/selectoracle/
and type in:
ul li:nth-child(2n+1){color:red;}
ul li:nth-child(2n){color:green;}

The effect of this is to make every odd li red and every even li green.  But
again, the support for this is non-existent:

http://www.macedition.com/cb/resources/css3support_selectors.html
So, in the meantime (say, for the next few years), check out:

http://otn.oracle.com/products/database/htmldb/index.html
and

http://htmldb.oracle.com/
Hope this helps,
Tyler

      Rating: 4
"Nasty nested"
      DECODEs  December 31, 2003
Reviewer:  Jurij
      Modic  from Ljubljana, Slovenia
Just saw this thread mentioned in Oramag, together with Martin Burbridge's
proposed solution with nested decodes. Of course I'm not arguing the superiority
of the CASE over DECODE, but when we are forced to use DECODE (there are still
many pre-8i databases active), there realy is very few ocasions where ve are
forced to use nested decode functions. The majority of the IF-ELSIF-ELSE logic
can be solved with *a single decode* function, without nesting many of them.
This also makes code much more easier to understand and maintain. So Martin's
nested-decode query can be rewritten to use single decode function like this:

select
  timestamp, sysdate,
  decode
    (-1,
     sign(sysdate-timestamp - 1/24),
       round(24*60*(sysdate-timestamp)) || ' minutes old ',
     sign(sysdate-timestamp - 1),
       round(24*(sysdate-timestamp)) || ' hours old ',
     sign(sysdate-timestamp - 14),
       trunc(sysdate-timestamp) || ' days old ',
     sign(sysdate-timestamp - 60),
        trunc((sysdate-timestamp)/7) || ' weeks old ',
     sign(sysdate-timestamp - 365),
       round(months_between(sysdate,timestamp)) || ' months old ',
       round(months_between(sysdate,timestamp)/12,1) || ' years old '
    ) age
from t;

          Followup:

i find both of them equally unreadable :)

but yes, neat trick -- turning the decode inside out sort of in this case.

December 31, 2003 nag -- Thanks for the question regarding "What is
      the reason that implicit cursors are faster than explicit cursors",
      version 8.1.7

      You Asked
Hi
I tested and satisfied myself that implicit cursors are better than explicit
cursors. I did tkprof report , and what you said is right.

Now can you tell me the reason as to why the implicit cursors are faster, what
are the internal reasons. Is it because that we have to do a open curosor fetch
etc( but when we do a for records in cursor loop , we dont have to do it).

What are the actual reason. How much of a perfomance difference it makes, it my
case it was very neglible( but ofcourse I was retrieving only a few records).

Thanks a lot

      and we said...
Ok, the answer is pretty clear.

PLSQL, like java, is interpreted.  We have some C code that is a PLSQL VM
(virtual machine) that runs PLSQL.  The more PLSQL code you write, the more we
have to intrepret.  Intrepeted code runs slower then native code (object/binary
code) does.  Therefore the code:

declare
   cursor c is select * from dual;
   l_dummy dual.dummy%type;
begin
   open c;
   fetch c into l_dummy;
   if ( c%notfound ) then
       raise no_data_found;
   end if;

   fetch c into l_dummy;
   if ( c%found ) then
       raise too_many_rows;
   end if;

   close c;
end;

Is just a heck of alot more code than:

declare
   l_dummy dual.dummy%type;
begin
   select * into l_dummy from dual;
end;

the PLSQL VM has lots more to do, more CPU cycles, less is done in C, more is
done in the VM -- hence less performance.

It just goes right along with my mantra:

o if you can do it in a single sql statement - do so.
o if you cannot, do it in as little PLSQL as you can.
o if you cannot, because plsql cannot do it, try some java (eg: sending an email
with an attachment)
o if you cannot do it in java (too slow, or you have some existing 3gl code
already) do it in C as an extproc.

Review
      & Followup

      Rating:
      4
  August 20, 2001
Reviewer:  ashraf
      gamal  from kuwait

      Rating: 4
Very much
      clear.  August 20, 2001
Reviewer:  Nirmal
      Kumar  from Qatar

      Rating:
      5
  August 20, 2001
Reviewer:  Helena
      Markova  from Bratislava, Slovakia

      Rating: 4
great
      explanation as usual  August 20, 2001
Reviewer:
      umesh kasturi  from Bangalore , India
great mantra tom!!

      Rating: 3
Good Matra
      Tom  August 21, 2001
Reviewer:  C Vivek Anand
       from Hyderabad
This is really a nice example to make the point very
clear.Nice way of doing things tom.

      Rating:
      5
zist  September 27, 2001
Reviewer:
      A reader
I guess what you are trying to say is ..

'the plsql virtual machine is written in c, all the plsql code which we write is
interpreted and translated into c by this virtual machine.

So the less code you write , less is the work the plsql virtual machine has to
do, hence more improvement in performance ,

Iam I right.

But

"the PLSQL VM has lots more to do, more CPU cycles, less is done in C, more is
done in the VM -- hence less performance."

What you mean by "less is done in C, more is  done in the VM"

Are you and me saying the same thing.

          Followup:

yes, the less plsql code you write -- in general -- the faster it will go since
the C code behind the plsql engine is doing the work

      Rating: 2
Not exactly a
      real world example  September 28, 2001
Reviewer:
      Graeme King  from Philadelphia, PA USA
Obviously there is no doubt that your example is faster but in reality almost
no-one codes explicit cursors like that.

For example if u are much more likely to test for %NOTFOUND and then do
something else rather than raise a NO_DATA_FOUND exception. The same will go for
the TOO_MANY_ROWS exception also. What if u don't care if there is more than one
row that meets you criteria and all u want is the first one. You don't code a
TOO_MANY_ROWS exception and just fetch once. Using an implicit cursor u will
need to code that exception. You should not use exceptions to branch program
control that you could otherwise handle elegantly.

I see explicit cursors having the following advantages over implicit cursors :

1. They give u more control over the flow of a program.  This makes code easier
to read, debug and maintain.
2. They can be encapsulated within a package used by many programs thus reducing
code volume thus improving performance.
3. They seperate the SQL from the procedural side of your code allowing the
program flow to be more apparent...cursors can be large but the actual logic of
a program is not confused by a large SQL statement in the middle of your code as
part of an implicit cursor. The explicit cursor would instead be declared in the
declarations section or elsewhere in a standard cursor package for example.

I agree that performance should always be improved where possible but in this
case (where it is neglible) not at the expense of good programming style
(sequence, selection and iteration)

Thanks for excellent book on Oracle and your great work here.

          Followup:

If they do not code that way (that I did) -- then they have NO need for the
select into.  The code able is the real world code you would have to write in
order to do the select into logic.  What I was trying to demonstrate is why
implicit cursors are BETTER then explicit ones.  Look at all of the work they do
for you.

In any case - the IMPLICIT select into will always be faster then even just the

open
fetch
close

I've shown that time and time and time again on this site.  It is also more
readable, legible, understandable and (most importantly) the easist to code and
debug.

Also -- I don't care if you raise a no data found or not -- its the fact that
you must detect it procedurally with an if statement.  I can assure you that:

begin
   select x into y from t where ...;
exception
   when no_data_found then
         ....
end;

is faster than

   open c;
   fetch c into y;
   if ( c%notfound ) then
         ...
   end if;
   close c;

regardless.  People who do NOT do the TOO_MANY_ROWS test are the EXCEPTION (or
they should be -- most of the time there should be ONE row).  If you are just
looking for the EXISTENCE of a row:

   select count(*) into l_cnt from dual
     where exists ( YOUR_QUERY_HERE );

is the efficient way to do that.

I see NO benefit from explicit cursors.  They involve more code, require you to
do more work, leave more chance for bugs (because you forget to close them
whatever), are harder to code, and run less efficiently - slower then implicit
cursors.

IMPLICIT CURSORS rule.

I myself find:

   for x in ( select * from t )
   loop
      ....
   end loop

much much easier then

   open c;
   loop
        fetch c into x;
        exit when c%notfound
        ....
   end loop;
   close c;

In this case -- what the heck is C?  Oh, I have to page back up in the code to
see C's definition.  I have to exit from the loop (true, I could code:

   open c;
   fetch c into x;
   while ( c%found ) loop
         .....
         fetch c into x;
   end loop;
   close c;

but i find that awkward as well)....

2) not relevant.  The overhead of EXPLICIT cursor processing outweighs by a
large margin any possible performance speedup by reusing that cursor in a small
number of places.  How many places do you really use the same exact query over
and over in your code?  Not many in my experience.  The performance gain of the
implict cursor outweighs any gain here (which would be very very small)

3) The SQL statement IS the logic of your program.  Even so, you can:

declare
    c cursor for really large query;
begin
    for x in c
    loop
       ...
    end loop;
end;
/

I do that from time to time for MONSTER queries (although I prefer just to make
view for those and get the out of the code all together)

I just really disagree with you that implicit cursors take away from "good
programming".  I myself find the programs that use explicit cursors to be

- harder to read
- harder to code
- more error prone

Thanks for the feedback

      Rating: 4
I'm agree with
      you,but there is problem in Pro*c  September 29, 2001

Reviewer:  A reader
I'm agree with you,but there is problem in Pro*c.
I program in Pro*c (oracle 8.1.5 on solaris)implicit cursor:
SELECT PRODUCT_OBJ_ID0
           INTO :PROID
           FROM ACCOUNT_PRODUCTS_T
           WHERE OBJ_ID0=:ACCID AND STATUS=10100 AND ROWNUM=1 G
When I execute this program repeat and repeat,I recieved "ORA-00100
error:maximum open cusors".(I have set HOLD_CURSOR=NO ,RELEASE_CURSOR=YES).
But,I use emplicit cursor:
EXEC SQL DECLARE pro_cur CURSOR FOR
  SELECT PRODUCT_OBJ_ID0
   FROM ACCOUNT_PRODUCTS_T
   WHERE OBJ_ID0=:ACCID AND STATUS=10100;
EXEC SQL OPEN pro_cur;
EXEC SQL FETCH pro_cur INTO :PROID;
EXEC SQL CLOSE pro_cur;
It works well the first time run,the second time it stop at statment :EXEC SQL
OPEN pro_cur;
Why ?

          Followup:

Sorry -- i cannot find any logged issues, nor can I reproduce.  With a proc
compile:

proc ireclen=255 lines=yes hold_cursor=no release_cursor=yes
include=/export/home2/ora815/proc/lib include=/export/home2/ora815/rdbms/demo
include=/export/home2/ora815/rdbms/public
include=/export/home2/ora815/plsql/public
include=/export/home2/ora815/network/public iname=t

of this code:

static void process( int empno )
{
int x;
    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();
    exec sql select count(*) into :x
               from emp
              where empno = :empno;
}

main( argc, argv )
int argc;
char *  argv[];
{
EXEC SQL BEGIN DECLARE SECTION;
VARCHAR   oracleid[50];
int       value;
EXEC SQL END DECLARE SECTION;
int       i;

    strcpy( oracleid.arr, USERID );
    oracleid.len = strlen(oracleid.arr);

    EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();
    EXEC SQL CONNECT :oracleid;
    printf("\nConnected to ORACLE as user: %s\n\n", oracleid.arr);

    for( i = 0; i < 10000; i++ )
    {
        process(i);
        exec sql select value into :value
                   from v$mystat
                  where statistic# = ( select statistic#
                                         from v$statname
                                     where name = 'opened cursors current' );
        printf( "%d currently opened cursors\n", value );
        exec sql select value into :value
                   from v$mystat
                  where statistic# = ( select statistic#
                                         from v$statname
                                     where name = 'opened cursors cumulative' );
        printf( "%d cumulative opened cursors\n", value );
    }
    /* Disconnect from ORACLE. */
    EXEC SQL COMMIT WORK RELEASE;
    exit(0);
}

I see:

$ ./t

Connected to ORACLE as user: /

3 currently opened cursors
6 cumulative opened cursors
3 currently opened cursors
9 cumulative opened cursors
3 currently opened cursors
12 cumulative opened cursors
3 currently opened cursors
15 cumulative opened cursors
3 currently opened cursors
18 cumulative opened cursors
3 currently opened cursors
21 cumulative opened cursors
3 currently opened cursors
24 cumulative opened cursors
3 currently opened cursors
27 cumulative opened cursors
....

My current open cursor count never goes up (due to the use of hold_cursor=no,
release_cursor=yes which are the WORST options to use if you care even a tiny
bit about performance).  My CUMULATIVE count goes up -- all of the way to over
100,000 (which far exceeds my open_cursor init.ora setting of

ops$tkyte@ORA815.US.ORACLE.COM> show parameter open

NAME                           TYPE    VALUE
------------------------------ ------- --------------------
open_cursors                   integer 50

Something else must be happening here -- try my sample and see what happens with
you.

I ran this on 815 with solaris as well.

In short however, you should NOT use hold_cursor=no, release_cursor=yes.  thats
the WORST for performance -- especially for a query you execute over and over.

      Rating: 5
The NCOMP in
      9i  October 01, 2001
Reviewer:  Connor
       from Bonny Scotland
It'll be interesting to see what difference occurs between explict and implicit
under a natively compiled PL/SQL module (9i).

          Followup:

As there isn't much computationally expensive logic here (most of the code is
already "plsql opcodes" -- simple instructions for plsql to execute) there isn't
much of a speedup in this case (so its not a great example of NCOMP'ed plsql --
it is not significantly faster ncomped then not).  Here is the example I used
(shows how easy ncomping code is though!)

ops$tkyte@ORA9I.WORLD> alter session set plsql_compiler_flags = 'INTERPRETED';

Session altered.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> create or replace procedure explicit_interpreted( p_times
in number )
  2  as
  3     cursor c is select * from dual;
  4     l_dummy dual.dummy%type;
  5     l_start number default dbms_utility.get_time;
  6  begin
  7      for i in 1 .. p_times
  8      loop
  9         open c;
 10         fetch c into l_dummy;
 11         if ( c%notfound ) then
 12              raise no_data_found;
 13         end if;
 14
 15         fetch c into l_dummy;
 16         if ( c%found ) then
 17              raise too_many_rows;
 18         end if;
 19
 20         close c;
 21      end loop;
 22      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs...' );
 23  end;
 24  /

Procedure created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> create or replace procedure implicit_interpreted( p_times
in number )
  2  as
  3       l_dummy dual.dummy%type;
  4     l_start number default dbms_utility.get_time;
  5  begin
  6      for i in 1 .. p_times
  7      loop
  8         select * into l_dummy from dual;
  9      end loop;
 10      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs...' );
 11  end;
 12  /

Procedure created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> alter session set plsql_compiler_flags = 'NATIVE';

Session altered.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> create or replace procedure explicit_native( p_times in
number )
  2  as
  3     cursor c is select * from dual;
  4     l_dummy dual.dummy%type;
  5     l_start number default dbms_utility.get_time;
  6  begin
  7      for i in 1 .. p_times
  8      loop
  9         open c;
 10         fetch c into l_dummy;
 11         if ( c%notfound ) then
 12              raise no_data_found;
 13         end if;
 14
 15         fetch c into l_dummy;
 16         if ( c%found ) then
 17              raise too_many_rows;
 18         end if;
 19
 20         close c;
 21      end loop;
 22      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs...' );
 23  end;
 24  /

Procedure created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> create or replace procedure implicit_native( p_times in
number )
  2  as
  3       l_dummy dual.dummy%type;
  4     l_start number default dbms_utility.get_time;
  5  begin
  6      for i in 1 .. p_times
  7      loop
  8         select * into l_dummy from dual;
  9      end loop;
 10      dbms_output.put_line( (dbms_utility.get_time-l_start) || ' hsecs...' );
 11  end;
 12  /

Procedure created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> alter session set plsql_compiler_flags = 'INTERPRETED';

Session altered.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> exec explicit_interpreted( 50000 );
1461 hsecs...

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> exec implicit_interpreted( 50000 );
1047 hsecs...

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> exec explicit_native( 50000 );
1413 hsecs...

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> exec implicit_native( 50000 );
964 hsecs...

PL/SQL procedure successfully completed.

      Rating: 3
THE STORY OF
      NATIVE COMPILATION OF PL/SQL  October 01, 2001

Reviewer:  A reader
Tell us briefly the story behind( briefly) -

'natively compiled PL/SQL module (9i).'

(The only thing we know is that , all the features available in sql will be
available to pl/sql starting from 9i. (is it because the sql engine and the
pl/sql engine have been combined in 9i ))

          Followup:

Normally PLSQL is compiled into P-CODE (byte code) and then interpreted at
runtime, much like Java is.

Native compilation makes it so that the PLSQL is compiled into C code instead,
this C code is then compiled into native object code and linked into the
database.  Now when the PLSQL executes, it is running as native object code --
not dynamically interpreted.

You'll see a speed up for computationally expensive operations via this method,
for example:

ops$tkyte@ORA9I.WORLD> @test
ops$tkyte@ORA9I.WORLD> set echo off
=================================================================

make sure the settings are setup right for NCOMP'ed plsql

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> alter system set plsql_native_make_utility = 'make';

System altered.

ops$tkyte@ORA9I.WORLD> alter system set plsql_native_make_file_name =
'/export/home/oracle9i/plsql/spnc_makefile.mk';

System altered.

ops$tkyte@ORA9I.WORLD> alter session set plsql_compiler_flags = 'INTERPRETED';

Session altered.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> set echo off
=================================================================

we'll compile an interpreted one (default) first

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> alter session set plsql_compiler_flags = 'INTERPRETED';

Session altered.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> create or replace
  2  function factorial_interpreted( p_n in number ) return number
  3  is
  4  begin
  5      if ( p_n = 1 )
  6      then
  7          return 1;
  8      else
  9          return factorial_interpreted( p_n-1 ) * p_n;
 10      end if;
 11  end;
 12  /

Function created.

ops$tkyte@ORA9I.WORLD> create or replace procedure run_interpreted( p_times in
number )
  2  as
  3          l_answer number;
  4          l_start  number default dbms_utility.get_time;
  5  begin
  6          for i in 1 .. p_times
  7          loop
  8                  l_answer := factorial_interpreted( 50 );
  9          end loop;
 10      dbms_output.put_line( (dbms_utility.get_time-l_start) ||
 11          ' hsecs...' || l_answer );
 12  end;
 13  /

Procedure created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> set echo off
=================================================================

and now NATIVE (too easy)

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> alter session set plsql_compiler_flags = 'NATIVE';

Session altered.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> create or replace
  2  function factorial_native( p_n in number ) return number
  3  is
  4  begin
  5      if ( p_n = 1 )
  6      then
  7          return 1;
  8      else
  9          return factorial_native( p_n-1 ) * p_n;
 10      end if;
 11  end;
 12  /

Function created.

ops$tkyte@ORA9I.WORLD> create or replace procedure run_native( p_times in number
)
  2  as
  3          l_answer number;
  4          l_start  number default dbms_utility.get_time;
  5  begin
  6          for i in 1 .. p_times
  7          loop
  8                  l_answer := factorial_native( 50 );
  9          end loop;
 10      dbms_output.put_line( (dbms_utility.get_time-l_start) ||
 11          ' hsecs...' || l_answer );
 12  end;
 13  /

Procedure created.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> set echo off
=================================================================

and the race is on...

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> alter session set plsql_compiler_flags = 'INTERPRETED';

Session altered.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> exec run_interpreted(1);
0 hsecs...30414093201713378043612608166064768844300000000000000000000000000

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> exec run_native(1);
0 hsecs...30414093201713378043612608166064768844300000000000000000000000000

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD>
ops$tkyte@ORA9I.WORLD> exec run_interpreted(10000);
443 hsecs...30414093201713378043612608166064768844300000000000000000000000000

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> exec run_native(10000);
287 hsecs...30414093201713378043612608166064768844300000000000000000000000000

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> select 287/443 from dual;

   287/443
----------
 .64785553

Here, the native compiled procedure is about 40% faster then the interpreted
one.

      Rating: 4
Interpreted vs
      Native Compiled PL/SQL  October 03, 2001
Reviewer:
      Padders  from UK
Tom you explain the difference in performance between implicit and explicit
cursors as being simply due to the PL/SQL VM having to do more work. If so then
why does natively compiled code still have the same overhead? Even if explicit
cursor code generates more operations in C, wouldn't each of these operations
should be cheaper than when interpreted this making the discrepancy between
implicit and explicit cursors smaller, even if still present?

          Followup:

Hey -- more code is more code.

You are still running in the PLSQL VM and most of the logic was very basic PLSQL
operations however -- the explicit cursors had to interact with the PLSQL
runtime more frequently -- executed more "op codes" if you will.

Even when you turn that into C in this case -- it was STILL doing the same
amount of MORE work.

The more you can do in a single statement -- the faster it will be, regardless
of whether we use C or PLSQL here.  more code is -- more code.  More logic takes
longer to execute, regardless.

It is just much more efficient to

   select x into y from t;

then it is to ask plsql to :

   open c
   fetch c into y;
   if ....
   fetch c into y
   if ....
   close c

      Rating: 4
EXISTS vs
      counting?  October 18, 2002
Reviewer:
      Kashif  from Houston, TX USA
Hi Tom,

Enlightening discussion on implicit vs explicit, I always thought explicits were
a pain, and now I know they are also slower. Quick couple of questions, you
mention earlier in this thread:

"
If you are just
looking for the EXISTENCE of a row:

   select count(*) into l_cnt from dual
     where exists ( YOUR_QUERY_HERE );

is the efficient way to do that.
"

Is it more efficient than simply doing:

select count (*)
into l_cnt
from
YOUR_FROM_AND_WHERE_CLAUSES_HERE;

--OR

select count (*)
into l_cnt
from
(YOUR_QUERY_HERE);

I'm wondering whether evaluating the existence of a row using an EXISTS and then
counting is more efficient than simply doing a count on your query, or using an
inline view and then couting the results.

Secondly, I'd like to clarify whether a count (1) is more efficient than a count
(*). I've heard from our DBAs that the count (1) takes less time than a count
(*). For example:

select count (1)
into l_cnt
from dual;

Any feedback would be useful. Thanks.

Kashif

          Followup:

yes it is.

the where exists -- STOPS your query as soon as a row is hit.
your count from your query -- finds ALL rows, finishes your query, doesn't stop.

Your DBA's are not telling you the truth.  point them to

http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:1156151916789

      Rating: 4
Native
      compilaton, all or nothing ?  October 18, 2002

Reviewer:  Rob  from Pittsburgh, PA
Hi Tom:

I read the following web presentation on native compilation. In there they say
that you should natively compile all packages or none, that there is a
performance hit from calling non-native compiled packages from natively compiled
packages. Can you comment on this.

http://www.dbspecialists.com/presentations/native_plsql.ppt

          Followup:

It all depends doesn't it (why I don't like blanket statements in general...)

anyway, suppose this is true:

a) you have a largish plsql environment (in place)
b) you have a very intense package that is called and does a lot of work
c) it doesn't call out to lots of other things, it gets called -- munches the
data and returns
d) it could benefit greatly from native compilation

So, what do you do?  do you compile your entire existing system natively
(possibly introducing some other issue -- it is after all a running production
system right?).  Or, do you compile this one (or set of some) core routine?
Since it is called and then runs for a while and doesn't interact with other
stuff -- it would still benefit greatly from native compilation wouldn't it
(yes)

But it is interesting that they said that -- without proving that (especially
since they said "don't believe anything someone says, see the proof" essentially
in their presenatation.

Maybe what they observed was a misplaced "cause effect" situation.  The effect
was not caused by what they thought it was.  Here is a short test I did:

ops$tkyte@ORA920.US.ORACLE.COM> alter system set plsql_native_make_utility =
'make';
System altered.

ops$tkyte@ORA920.US.ORACLE.COM> alter system set plsql_native_make_file_name =
'/usr/oracle/ora920/OraHome1/plsql/spnc_makefile.mk';
System altered.

ops$tkyte@ORA920.US.ORACLE.COM> alter system set plsql_native_library_dir =
'/usr/oracle/ora920/OraHome1/native';
System altered.

ops$tkyte@ORA920.US.ORACLE.COM> alter session set plsql_compiler_flags =
'INTERPRETED';
Session altered.

ops$tkyte@ORA920.US.ORACLE.COM> alter session set plsql_compiler_flags =
'INTERPRETED';
Session altered.

ops$tkyte@ORA920.US.ORACLE.COM> create or replace procedure
do_something_interpreted( l_data in varchar2 )
  2  as
  3          l_tmp varchar2(255);
  4  begin
  5          for i in 1 .. 100
  6          loop
  7                  l_tmp := substr( l_data, 1, i );
  8          end loop;
  9  end;
 10  /
Procedure created.

ops$tkyte@ORA920.US.ORACLE.COM> alter session set plsql_compiler_flags =
'NATIVE';
Session altered.

ops$tkyte@ORA920.US.ORACLE.COM> create or replace procedure do_something_native(
l_data in varchar2 )
  2  as
  3          l_tmp varchar2(255);
  4  begin
  5          for i in 1 .. 100
  6          loop
  7                  l_tmp := substr( l_data, 1, i );
  8          end loop;
  9  end;
 10  /
Procedure created.

ops$tkyte@ORA920.US.ORACLE.COM> alter session set plsql_compiler_flags =
'INTERPRETED';
Session altered.

ops$tkyte@ORA920.US.ORACLE.COM> create or replace procedure
interpreted_calls_interpreted( p_times in number )
  2  as
  3  begin
  4          for i in 1 .. p_times
  5          loop
  6                  do_something_interpreted( 'Hello World!!!!' );
  7          end loop;
  8  end;
  9  /
Procedure created.

ops$tkyte@ORA920.US.ORACLE.COM> create or replace procedure
interpreted_calls_native( p_times in number )
  2  as
  3  begin
  4          for i in 1 .. p_times
  5          loop
  6                  do_something_native( 'Hello World!!!!' );
  7          end loop;
  8  end;
  9  /
Procedure created.

ops$tkyte@ORA920.US.ORACLE.COM> alter session set plsql_compiler_flags =
'NATIVE';
Session altered.

ops$tkyte@ORA920.US.ORACLE.COM> create or replace procedure
native_calls_interpreted( p_times in number )
  2  as
  3  begin
  4          for i in 1 .. p_times
  5          loop
  6                  do_something_interpreted( 'Hello World!!!!' );
  7          end loop;
  8  end;
  9  /
Procedure created.

ops$tkyte@ORA920.US.ORACLE.COM> create or replace procedure native_calls_native(
p_times in number )
  2  as
  3  begin
  4          for i in 1 .. p_times
  5          loop
  6                  do_something_native( 'Hello World!!!!' );
  7          end loop;
  8  end;
  9  /
Procedure created.

ops$tkyte@ORA920.US.ORACLE.COM> set timing on

ops$tkyte@ORA920.US.ORACLE.COM> exec interpreted_calls_interpreted( 10000 );
PL/SQL procedure successfully completed.
Elapsed: 00:00:01.23

ops$tkyte@ORA920.US.ORACLE.COM> exec interpreted_calls_interpreted( 10000 );
PL/SQL procedure successfully completed.
Elapsed: 00:00:01.26

ops$tkyte@ORA920.US.ORACLE.COM> exec interpreted_calls_native( 10000 );
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.79

ops$tkyte@ORA920.US.ORACLE.COM> exec interpreted_calls_native( 10000 );
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.75

ops$tkyte@ORA920.US.ORACLE.COM> exec native_calls_interpreted( 10000 );
PL/SQL procedure successfully completed.
Elapsed: 00:00:01.20

ops$tkyte@ORA920.US.ORACLE.COM> exec native_calls_interpreted( 10000 );
PL/SQL procedure successfully completed.
Elapsed: 00:00:01.14

ops$tkyte@ORA920.US.ORACLE.COM> exec native_calls_native( 10000 );
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.76

ops$tkyte@ORA920.US.ORACLE.COM> exec native_calls_native( 10000 );
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.77

which seems to bolster my point -- you have something that takes a bit of time
(my do_something routines).  You natively compile it.  It runs faster --
regardless of how it is called.

Argueably -- I did lots of context switching here (10,000 times each test).  The
bottom line was "if the do something was ncomped, it ran faster"

      Rating: 4
Going back to
      the original argument...  October 30, 2002

Reviewer:  Kashif  from Houston, TX
Hey Tom,

This is an extension to point #2 made by Graeme King earlier in the thread, and
then you're response to it. Now we've established that implicits are faster than
explicits. However, if we have a situation inside a stored pl/sql routine where
we have to do something like:

select col1 into var1 from tab1 where col2 = 'a';
..
select col1 into var2 from tab1 where col2 = 'b';
..
select col1 into var3 from tab1 where col2 = 'c';

we know that we're opening up a separate cursor for each of these selects, not
good. But we also don't want to declare one parameterized cursor and then keep
opening, fetching and closing either, per our discussion. Wouldn't it then be
better to simply declare a function with the required arguments, and then call
that function each time we need to select? E.g.:

function test (
param1 in tab1.col2%type)
return tab1.col1%type
is
l_var tab1.col1%type;
begin
   select col1
     into l_var
     from tab1
    where col2 := param1;
return (l_var);
end;
...
--and then...
...
var1 := test ('a');
...
var2 := test ('b');
...
var3 := test ('c');

I know I read this somewhere, but I was wondering what you're thoughts are on
this. In this manner we also avoid the potential that the pl/sql engine parses
two idential selects issued at different points in the routine separately,
creating two cursors for the same exact sql. What say you?

Kashif

          Followup:

I say that is the foundation of modular coding and I dig it -- it is the way I
program.

Yes, it would be sound to move this operation into a function like that.

      Rating: 4
A real
      performance gain?  April 14, 2003
Reviewer:
      Andy Barker  from York, UK
Having reviewed the threads on this site regarding the performance benefits of
implicit vs. explicit cursors I was motivated to run my own benchmark. While I
don't doubt the performance benefit demonstrated by your benchmarks, a more
"real world" test I ran resulted in no statistically significant difference in
the timing between implict and explicit cursors.

My test involved looping 40000 times and testing for the presence of a row. If
it existed the row was updated, if not a new one inserted. Each run resulted in
20000 updates and 20000 inserts. Three coding methods were used:

1. Explicit cursor
  open c_exists;
  fetch c_exists into i_dummy;
  b_exists := c_exists%FOUND;
  close c_exists;
  --
  if b_exists then
    -- UPDATE ...
  else
    -- INSERT ...
  end if;

2. Implict cursor selecting count of rows to branch
  SELECT
    count(*)
  INTO
    i_count
  FROM
    cursor_test
  WHERE
    col1 = p_input_id;
  --
  if i_count = 0 then
    -- INSERT ...
  else
    -- UPDATE ...
  end if;

3. Implicit cursor using NO_DATA_FOUND exception to branch
begin
  SELECT
    1
  INTO
    i_dummy
  FROM
    cursor_test
  WHERE
    col1 = p_input_id;
  --
  -- UPDATE ...
exception
  when NO_DATA_FOUND then
    -- INSERT ...
end;

Each test was run, the time noted and then rolled back. Thus each test was
hitting the same data blocks. Ignoring the first run of each type of test to
remove any parsing and caching overhead my results were as follows:

EXPLICIT
0:42  0:41  0:39  0:40  0:43

IMPLICIT
0:43  0:40  0:39  0:39  0:40

EXCEPTION
0:42  0:40  0:42  0:40  0:43

I would welcome your thoughts as to why I didn't observe the performance
differences you have demonstrated in your postings in this and other threads.

          Followup:

My major number one point is -- people have been trained (for whatever reason)
to believe "explicit cursors good, explicit cursors faster, explicit cursors
better"

I leveled the playing field.  You need never fear the implicit select into.

Explicit is not better.
Explicit is not faster.

Also -- i cannot see your entire test from front to back -- but since the vast
preponderance of runtime would be spent doing the insert/update -- one would
have to say "what were you comparing".  You were primarily comparing the
runtimes of the modifications as the select into in this case isn't the slow
thing.  Also why do you compare a count(*) to something totally different???
make them the same.  If you only care about the first row -- use

select x into y from z where ... AND ROWNUM=1;

don't do count(*)'s -- yuck.

You want to see if explicit is faster/slower/the same as implicit?
Then isolate your test to that functionality.

Is it going to make your code 2x as fast?
Nope.

Is it going to go slower?
Nope.

Is it going to go marginally faster?
Yup.

If that were my process, it would be:

  update ...
  if ( sql%rowcount = 0 )
  then
     insert ..
  end if;

and we wouldn't be doing a select into at all.

      Rating: 5
Count from dual
      vs count from table.  April 14, 2003
Reviewer:
      Darren  from London, UK
Tom,

Whilst i understand your point on
"   select count(*) into l_cnt from dual
     where exists ( YOUR_QUERY_HERE );"
bieng faster than
   select count(*)
     into l_cnt
     from  YOUR_TABLE
    where blah...

i usually code this as:
select count(*)
  into v_cnt
  from MY_TABLE
 where blah
   and rownum = 1;

whats the (dis)advantages of this approach? i have used tkprof on the dual
method and the rownum stopkey approach and they are virtually identical (the
exists approach has slightly extra IO from "dual").

p.s. i was testing on a RBO 8.1.7.2 database.

          Followup:

The where rownum = 1 works very well -- i use the interchangeably, depending on
my mood.

The where exists seems more "semantically correct"
The where rownum = 1 is less typing and removes 4 LIOs (in 8i) from the query
(to full scan DUAL)

      Rating: 4
Steven
      Feuerstein vs. Tom  June 24, 2003
Reviewer:
      Jens Kropp  from Germany
The PL/SQL Guru Steven Feuerstein wrote the exact opposite.

He would NEVER use implicit cursors. In his PL/SQL-Book he used the same
arguments we found here in this topic from some "explicit cursor fans" and he
also said that the performance gain with implicit cursors is minimal.

Hey, that would be a great discussion Guru Tom vs. Guru Feuerstein! What do you
think?

          Followup:

he has changed his mind in the recent past.

      Rating:
      5
  June 25, 2003
Reviewer:
      A reader
Tom is Guru of gurus.
Perhaps only Jonathan lewis can match his brilliance to some extent.....

          Followup:

I wish it were true, I feel not so smart standing next to him actually.

      Rating: 5
You are being
      modest.  June 26, 2003
Reviewer:
      A reader

      Rating: 5
Single row
      Cursor vs. SELECT statement  December 31, 2003

Reviewer:  Ashutosh
Tom,
I have come across an existing code where cursors are written to return single
row data, which can be achieved by writing a single SQL statement. example:

Definition of Cursor:

      cursor crd_lmt (cust_code varchar2) is
      select credit_limit
        from customer_credit_lmt
       where customer_code = cust_code
         and effective_date = (select max(effective_date)
                                 from customer_credit_lmt
                                where customer_code = cust_code
                                  and effective_date < sysdate);

Subsequent operations:

          begin
            open crd_lmt (o.customer_code);
            fetch crd_lmt into credit_lmt;
            if crd_lmt%NOTFOUND then
              credit_lmt := null;
            end if;
            close crd_lmt;
          end;

As I believe that by writing a simple SQL statement this result could have been
achieved.

I wanted to know how much performance enhancement (if any) I would gain if it
will be converted to single SQL statement call?

Thanks in advance,

          Followup:

that should clearly be:

begin
      select credit_limit into crd_lmt
        from customer_credit_lmt
       where customer_code = cust_code
         and effective_date = (select max(effective_date)
                                 from customer_credit_lmt
                                where customer_code = cust_code
                                  and effective_date < sysdate);
exception
      when no_data_found then null;
end;

and if concerned on the TOO_MANY_ROWS exception that could be thrown if two
records exist for the same customer_code/effective_date -- you would just add
"and rownum = 1" to the query (although I'd be concerned that there could be two
records -- which the current code won't detect but the select into will).

another way to code that would be:

begin
    select credit_limit into crd_lmt
      from (select credit_limit
              from customer_credit_lmt
             where customer_code = cust_code
               and effective_date < sysdate
             order by effective_date DESC)
      where rownum = 1;
exception
     when no_data_found then null;
end;

which can be much more efficient -- consider:

/*
drop table t;

create table t
as
select mod(rownum,1000) customer_code, created effective_date, object_id
credit_limit, object_name other_data
  from all_objects;

create index t_idx on t(customer_code, effective_date, credit_limit);

analyze table t compute statistics for table for all indexes for all indexed
columns;
*/

create or replace procedure proc1
as
    l_credit_limit1 number;
    l_credit_limit2 number;
begin
    for i in 0..999
    loop
    begin
        select credit_limit into l_credit_limit1
          from (select credit_limit
                  from t
                 where customer_code = i
                   and effective_date < sysdate
                 order by effective_date DESC)
          where rownum = 1;
    exception
        when no_data_found then l_credit_limit1 := null;
    end;
    begin
          select credit_limit into l_credit_limit2
            from t
           where customer_code = i
             and effective_date = (select max(effective_date)
                                     from t
                                    where customer_code = i
                                      and effective_date < sysdate);
    exception
        when no_data_found then l_credit_limit2 := null;
    end;
    if ( l_credit_limit1 <> l_credit_limit2 or
         (l_credit_limit1 is null and l_credit_limit2 is not null ) or
         (l_credit_limit2 is null and l_credit_limit1 is not null )  )
    then
        raise program_error;
    end if;
    end loop;
end;
/

alter session set sql_trace=true;
exec proc1

tkprof shows us:

SELECT credit_limit from (select credit_limit
                  from t
                 where customer_code = :b1
                   and effective_date < sysdate
                 order by effective_date DESC)
          where rownum = 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute   1000      0.23       0.26          0          0          0           0
Fetch     1000      0.09       0.08          0       2000          0        1000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2001      0.32       0.34          0       2000          0        1000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 147     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
   1000  COUNT STOPKEY (cr=2000 r=0 w=0 time=67652 us)
   1000   VIEW  (cr=2000 r=0 w=0 time=54803 us)
   1000    INDEX RANGE SCAN DESCENDING T_IDX (cr=2000 r=0 w=0 time=48422
us)(object id 32937)

********************************************************************************

SELECT credit_limit from t
           where customer_code = :b1
             and effective_date = (select max(effective_date)
                                     from t
                                    where customer_code = :b1
                                      and effective_date < sysdate)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute   1000      0.43       0.39          0       2000          0           0
Fetch     1000      0.01       0.03          0       2006          0        1000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2001      0.44       0.42          0       4006          0        1000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 147     (recursive depth: 1)

Rows     Row Source Operation
-------  ---------------------------------------------------
   1000  INDEX RANGE SCAN T_IDX (cr=2006 r=0 w=0 time=22097 us)(object id 32937)

the "order by" uses 1/2 the LIO's by avoiding the index scan twice.

but play around with different queries -- you might be surprised.

      Rating: 4
SELECT BULK
      COLLECT INTO  January 07, 2004
Reviewer:  Dan
      Clamage  from Pittsburgh, PA USA
A lot of explicit cursors can be rewritten as SELECT BULK COLLECT INTO. Then do
the looping on the collection.

If you expect a LOT of rows, then go with an explicit cursor and FETCH BULK
COLLECT INTO LIMIT in a loop.

Fewer context switches make for faster execution.

I really like the flexibility with bulk collects in 9.2.0.4.0. I can define a
nested table as an explicit cursor %rowtype, so that if the cursor's SELECT
clause changes, I don't have to change much else. Then just BULK COLLECT right
into the composite collection. Sweet.

Ref cursors also offer added flexibility. I use them when I want the calling
context to have the flexibility of determining what it wants to do with the
result set. I can bulk collect from the ref cursor too. Or maybe it just wants
the first 1 or N rows. Lower level routines shouldn't dictate much to the higher
level routines that call them.

There are appropriate times to use implicit or explicit cursors.

If your code becomes torturous and convoluted, there's probably a cleaner way.
Don't be afraid to rewrite entire sections of code. If you can do something more
cleanly or elegantly, it's worth the trouble to rewrite it. Small improvements
add up over time and effort.

Nothing worse than maintaining someone else's code and saying to oneself, "What
the heck is this guy trying to do here?". Or on the occasions where you get to
maintain your own code, "What the heck was I thinking?"

December 31, 2003 Daryl -- Thanks for the question regarding
      "Application contexts in a web environment", version 9.2.0

      You Asked
We have a web application that performs all its database accesses via stored
procedures. Like many applications, we're using an application server that
manages its own pool of connections, each of which is logged in using the same
username. We want to use application contexts to support Fine Grained Access
Control as well as things like triggers that do auditing.

Most books (including yours) show the following flow:
1. begin page generation/processing in application server
2. set context for database based on actual user (via stored procedure
   call)
3. do database stuff via normal SELECTs and DML

In our case, is there any problem with doing something like this:
1. begin page generation
2. invoke stored procedures needed
   2a. each stored procedure sets context based on incoming user id

The second method saves multiple stored procedure calls (and associated network
overhead). However, the latter method also potentially sets the same context
multiple times. In both cases (again assuming that in the second case, the app
server does no database access apart from the stored procedures), it seems that
contexts would be set properly for auditing triggers as well as FGAC.

For access apart from our application, I've considered maintaining real Oracle
users to match the "virtual" users in the application. If the real users then
connect directly (using something like Access or whatever), we could use a logon
trigger to set their context for the entire session, still protecting the data
via FGAC.

Are there any potential problems with the second approach (doing context
settings in the procedures)? The second approach requires less re-work on the
client side (no context calls), so clearly we'd like to know this will work.

Thanks for any comments you can provide.

      and we said...
Or, how about a GLOBAL APPLICATION context, new with 9i.

They are based on session ids.  If, upon logging into your application, you
assign a session id to each "session" (each login session) -- we can

a) set the context once (much better for performance, especially if it takes a
bit of work to set the context)

b) rejoin this context from any other DATABASE session that user account uses by
providing the session id

We would of course want to encrypt the session id, perhaps using
dbms_obfuscation_toolkit and set it as a cookie to it is available in all of the
subsequent pages after logon.

Here is a quick and dirty SQLPlus example showing how this works:

ops$tkyte@ORA920.US.ORACLE.COM> @test
=================================================================

we'll start by creating a "global" context - using the ACCESSED
globally clause

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace context App_Ctx using My_pkg
  2  ACCESSED GLOBALLY
  3  /

Context created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

now we'll create the package to use it.  This would have our
application logic to ensure we are who we say we are...

the session id could be anything your middle tier assigns to it.
the session id associated with your servlet instance for example.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace package my_pkg
  2  as
  3          procedure set_session_id( p_session_id in number );
  4
  5          procedure set_ctx( p_name       in varchar2,
  6                                             p_value      in varchar2 );
  7
  8          procedure close_session( p_session_id in number );
  9  end;
 10  /

Package created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

and now the package body.  The set session id calls set_identifer,
set_ctx sets the context value *for our session id and user only*,
close session deallocates the context

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> create or replace package body my_pkg
  2  as
  3          g_session_id number;
  4
  5          procedure set_session_id( p_session_id in number )
  6          is
  7          begin
  8                  g_session_id := p_session_id;
  9                  dbms_session.set_identifier( p_session_id );
 10          end;
 11
 12          procedure set_ctx( p_name       in varchar2,
 13                                             p_value      in varchar2 )
 14          as
 15          begin
 16                  dbms_session.set_context( 'App_Ctx', p_name, p_value, USER,
g_session_id );
 17          end;
 18
 19          procedure close_session( p_session_id in number )
 20          is
 21          begin
 22                  dbms_session.set_identifier( p_session_id );
 23                  dbms_session.clear_identifier;
 24          end;
 25  end;
 26  /

Package body created.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

Now we'll test it out.  We'll create a session 1234 and put some stuff
in it

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> exec my_pkg.set_session_id( 1234 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> exec my_pkg.set_ctx( 'Var1', 'Val1' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> exec my_pkg.set_ctx( 'Var2', 'Val2' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select sys_context( 'app_ctx', 'var1' ) var1,
  2         sys_context( 'app_ctx', 'var2' ) var2
  3    from dual
  4  /

VAR1       VAR2
---------- ----------
Val1       Val2

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

Now we'll log out/log in

At first, out context is empty - but we rejoin our session and there it is

ops$tkyte@ORA920.US.ORACLE.COM> disconnect
Disconnected from Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
ops$tkyte@ORA920.US.ORACLE.COM> connect /
Connected.
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select sys_context( 'app_ctx', 'var1' ) var1,
  2         sys_context( 'app_ctx', 'var2' ) var2
  3    from dual
  4  /

VAR1       VAR2
---------- ----------

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> exec my_pkg.set_session_id( 1234 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select sys_context( 'app_ctx', 'var1' ) var1,
  2         sys_context( 'app_ctx', 'var2' ) var2
  3    from dual
  4  /

VAR1       VAR2
---------- ----------
Val1       Val2

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

Now we'll show that this context is tied to our user (we specified
USER above, if we used null anyone can join this session).

this means only our "middle tier" user can rejoin this session

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> grant execute on my_pkg to scott;

Grant succeeded.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> connect scott/tiger
Connected.
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
scott@ORA920.US.ORACLE.COM>
scott@ORA920.US.ORACLE.COM> exec ops$tkyte.my_pkg.set_session_id( 1234 );

PL/SQL procedure successfully completed.

scott@ORA920.US.ORACLE.COM>
scott@ORA920.US.ORACLE.COM> select sys_context( 'app_ctx', 'var1' ) var1,
  2         sys_context( 'app_ctx', 'var2' ) var2
  3    from dual
  4  /

VAR1       VAR2
---------- ----------

scott@ORA920.US.ORACLE.COM>
scott@ORA920.US.ORACLE.COM>
scott@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

Now we'll come back and see our context again and then wipe it out

scott@ORA920.US.ORACLE.COM>
scott@ORA920.US.ORACLE.COM> connect  /
Connected.
scott@ORA920.US.ORACLE.COM> set echo off
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> exec my_pkg.set_session_id( 1234 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select sys_context( 'app_ctx', 'var1' ) var1,
  2         sys_context( 'app_ctx', 'var2' ) var2
  3    from dual
  4  /

VAR1       VAR2
---------- ----------
Val1       Val2

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> exec my_pkg.close_session( 1234 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select sys_context( 'app_ctx', 'var1' ) var1,
  2         sys_context( 'app_ctx', 'var2' ) var2
  3    from dual
  4  /

VAR1       VAR2
---------- ----------

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off

But -- that doesn't answer the "original" question.  What I would do is use the
global context above (for the web apps) and the one procedure you have that sets
it would be called by the other applications -- your procedure would look at the
USER psuedo column and if the USER = application user, it would set the context
using one set of logic, else it would set the application context using a
different code path (based on the "real" username).

Review &
      Followup

      Rating: 5
Closing
      sessions  December 08, 2002
Reviewer:  Mishka
      McCowan  from Alexandria, VA USA
Great example, as always, but I'm curious about one thing. How would you close
the session for those users that don't logout but simply close the browser or go
to another site? Perhaps a batch job that periodically closes the session of
users who haven't been active in x hours?

          Followup:

just setup a job (dbms_job) to periodically clean up "expired" sessions (keeping
a table of session ids in the database is useful for this -- just have a session
state variable "expires" and close out any session that expired.  Have the app
of course update the "expires" state variable on each page access

      Rating:
      5
LIST_CONTEXT  March 07, 2003
Reviewer:
      Yves Bergeron  from Québec
After creating the "global" context and storing some variables in it, I would
like to list all the variables stored in it.

I use the following code to do that:

set serveroutput on
declare
    list dbms_session.AppCtxTabTyp;   -- type defined in dbms_session pkg
    cnt number;
begin
    dbms_session.list_context (list, cnt);

    dbms_output.put_line('Number of contexts: ' || cnt);

    for i in 1..cnt loop
        dbms_output.put_line( list(i).namespace || ' ' ||
                              list(i).attribute || ' = '|| list(i).value );
    end loop;
end;
/

If my context is created ACCESSED GLOBALLY, my PL/SQL block will show me
nothing.

If my context is created without ACCESSED GLOBALLY, my PL/SQL block will show me
the variables stored in my context.

I'm sure there is a reasonable reason for that.  Can you explain that to me ?

          Followup:

cannot "explain it", but can show what to do about it.

use the views session_context and global_context.  union all them together if
you like.

consider:

ops$tkyte@ORA920> create or replace context App_Ctx using My_pkg
  2  ACCESSED GLOBALLY
  3  /

Context created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set echo off
Enter to continue

=================================================================

now we'll create the package to use it.  This would have our
application logic to ensure we are who we say we are...

the session id could be anything your middle tier assigns to it.
the session id associated with your servlet instance for example.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package my_pkg
  2  as
  3          procedure set_session_id( p_session_id in number );
  4
  5          procedure set_ctx( p_name       in varchar2,
  6                                             p_value      in varchar2 );
  7
  8          procedure close_session( p_session_id in number );
  9  end;
 10  /

Package created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set echo off
Enter to continue

=================================================================

and now the package body.  The set session id calls set_identifer,
set_ctx sets the context value *for our session id and user only*,
close session deallocates the context

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package body my_pkg
  2  as
  3          g_session_id number;
  4
  5          procedure set_session_id( p_session_id in number )
  6          is
  7          begin
  8                  g_session_id := p_session_id;
  9                  dbms_session.set_identifier( p_session_id );
 10          end;
 11
 12          procedure set_ctx( p_name       in varchar2,
 13                                             p_value      in varchar2 )
 14          as
 15          begin
 16                  dbms_session.set_context( 'App_Ctx', p_name, p_value, USER,
g_session_id );
 17          end;
 18
 19          procedure close_session( p_session_id in number )
 20          is
 21          begin
 22                  dbms_session.set_identifier( p_session_id );
 23                  dbms_session.clear_identifier;
 24          end;
 25  end;
 26  /

Package body created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> set echo off
Enter to continue

=================================================================

Now we'll test it out.  We'll create a session 1234 and put some stuff
in it

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec my_pkg.set_session_id( 1234 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec my_pkg.set_ctx( 'Var1', 'Val1' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec my_pkg.set_ctx( 'Var2', 'Val2' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select sys_context( 'app_ctx', 'var1' ) var1,
  2         sys_context( 'app_ctx', 'var2' ) var2
  3    from dual
  4  /

VAR1       VAR2
---------- ----------
Val1       Val2

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from global_context;

NAMESPACE                       ATTRIBUTE
------------------------------- -------------------------------
VALUE
---------------------------------------------------------------------------------
--------------------------------------------------
USERNAME                        CLIENT_IDENTIFIER
-------------------------------
-----------------------------------------------------------------
APP_CTX                         VAR1
Val1
OPS$TKYTE                       1234

APP_CTX                         VAR2
Val2
OPS$TKYTE                       1234

ops$tkyte@ORA920> select * from session_context;

NAMESPACE                      ATTRIBUTE
------------------------------ ------------------------------
VALUE
---------------------------------------------------------------------------------
--------------------------------------------------
LBAC$LABELS                    LBAC$LASTSEQ

ops$tkyte@ORA920> pause

      Rating: 4
9.2 App
      Contexts  April 07, 2003
Reviewer:  Ray
       from Ottawa,ON,CA
If the context is set by ONEBIGUSER
How can a dbms_job get rid of the context entries, if no log
out(Clear_identifier) occurs?

Does ORACLE automagically clean these out ever?

thanks for your help

          Followup:

by default a context is a "session thing".  there is nothing to clear out.

      Rating: 5
Clearing down
      GACs  April 08, 2003
Reviewer:  Martin
       from UK
Hi Tom,

this is something i've wondered about as well. How to "clear down" the values in
a global app. context? This may be useful if you, say, reuse session_ids, and
the data changes quickly enough to justify re-calculating the values at the
beginning of a "session".

Thanks in advance.

          Followup:

in the case of the data changing -- you would not be wanting to use a global
application context then.

in the case of reuse, just clear the identifier.

      Rating:
      5
Aaaahh!  April 08, 2003
Reviewer:
      Martin  from UK
Hi Tom,

ignore my previous comment, just realised how to do it. DBMS_JOB which loops
through the distinct "client_identifier" and issues a clear_identifier ? Is this
the correct approach, if not, how would you do it?

Also, how often would you suggest the DBMS_JOB ran in order to clear them down?

Thanks in advance

          Followup:

only you can answer the last question ;)

perhaps you have a table client_identifier, expire_time and your job just loops
over that expiring sessions as they expire.

      Rating: 5
How much work
      is setting an application context  April 09, 2003

Reviewer:  Tom
Tom,

If using an application context to do FGAC in a web environment, is the overhead
in setting up the context itself [ie not any user defined select statements
inside it] once per page significant, or will this not be a noticeable
performance drain?

          Followup:

setting a context takes virtually no time.

it is when the values you want to set in the context take a long time to gather
that you want to consider the "global" context.

      Rating: 5
9.2 app context
      clear identifier  April 09, 2003
Reviewer:
      Ray  from Ottawa,ON,CA
in response to the followup "Context is a session thing"

Who can issue clear_identifier beyond the USER that set the context variables?

My understanding of the accessed globally was for web app connection pooled
session to reaquire a session state. If 10 users create context vars for their
client id sessions and never clear the identifier they created context vars for,
what happens to them?
The DB probably doesnt assume they will not revisit within lets say one hour ,
and try to re-establish their session/context. There for it will keep the
context information until ?

Tia
Ray

          Followup:

The context's remain.

Typically this is used in Ntier situations -- so the contexts are "owned" by
that single magic user (this is where they make most sense).  That single magic
user however can clear them out as they "expire"

      Rating: 5
9.2 app context
      clear identifier   April 09, 2003
Reviewer:
      RAY  from Ottawa,ON,CA
OK.
1. If the contexts are created by 'ONEBIGUSER' , then ONEBIGUSER should be able
to set_identifier(N), clear_identifier for any existing Context entries correct?

2. Ntier where N > 2  I suppose. We just switched from 8.1.7 to 9.2. No middle
tier.  The app was using a session_table which held the app context data by
internal session id. As part of the switch my intention was to use the accessed
globally option of the create context. I thought this would allow me to avoid
the app session table updates. If I do a select from v$globalcontext I can see
trouble brewing if I can't clean these out some how. With my own table I can
just use a dbms_job to remove Old/inactive sessions. I would like to do the same
with the Context entries.
Can this be done? can you please give me a clue how?
WOuld you advise using context accessed globally for browser - http(mod_plsql)
db server only solutions
thanks for your help
Ray

          Followup:

1) yes.

2) no middle tier implies client server -- why wouldn't you just clear it on
exit?  (or in an on logoff trigger)

      Rating: 5
9.2 app context
      clear identifier   April 10, 2003
Reviewer:
      Ray
why wouldn't I clear them off in a log off trigger?

This is a web app. Log in to the database is via ONEBIGAPPUSER using mod_plsql.
The user signs into the application. Application context is set. The user closes
the browser. No logout occurs. The context remains.

The app does have a logout which does the set/clear but no guarantee the user
will use it.

I am having trouble getting rid of application context entries.

I can set the identifier, and see the context variables. but the
clear_identifier isn't deleting the context entry.
Is a commit required?

tia
Ray

          Followup:

Now you are confusing me.

1) said i'm one big user

2) said i'm a client server application

I thought we were talking about two things.

for case #1, no worries, you are onebiguser and onebiguser can clear them at
will.  Onebiguser can clear out expired sessions whenever onebiguser wants to.

      Rating: 5
9.2 app context
      clear identifier   April 10, 2003
Reviewer:
      A reader
Sorry for the confusion, it apparently comes from me, because I am really
confused on this especially.

This is what I am trying to do.

1. create context accessed globally.
2. mod_plsql auto logs into database as ONEBIGUSEr
3. Each user invoking the web app logs into the app not the database.
4. The app gens a session id and timestamp encrypts and writes a cookie
5. the app sets context variables with info about the user using the session id
as the client identifier.
6. For each subsequent page the app
-reads the cookie.
-sets the identifier to the session id from the cookie reestablishing the
context... no problem.
- resets the cookie with new timestamp .....
7. the user then closes the browser ending his session.
the application context remains.
select * from v$globalcontext
shows many context vars for many sessions
8. Logging in as ONEBIGUSER to sqlplus
I execute
begin
for i in 2250..2600 loop
dbms_session.set_identifier(i);
dbms_output.put_line (to_char(i) || ':' || sys_context('MYCONTEXT','MYVAR');
dbms_session.clear_identifier;
end loop;
end;

I can see the context var, but the clear_identifier
does not remove the context.

no errors ...

tia

Ray

          Followup:

apparently, I have some "product issues" to file....  it doesn't clear them out
from the sga apparently.  bummer -- thanks for pushing the issue, i'll look into
it.

      Rating: 4
Clearing
      context entries  April 10, 2003
Reviewer:
      Ray  from ottawa,on,ca
Ok I got rid of them by using clear_context on each of the
session id, attribute name pairs individually.
I followed this up with a clear_identifier
Clear_identifier alone did not remove the context entries for that identifier.
Still not sure if it's supposed to or not.
After this I RTFM and it said that clear_context without specifying an attribute
name, will clear all the attributes for the specified session.
hth
Ray

      Rating: 4
Clearing Global
      Contexts  April 11, 2003
Reviewer:  Ray
       from ottawa,on,ca
Not sure if you want toi investigate this or not.

In my effort to clear the context entries. I believe that I founf that USER A
can clear USER B's context entries as long as USER A had execute rights on the
trusted package. I did not retest this.

hth
Ray

      Rating: 4
Anything new
      yet on clearing identifiers from SGA?  December 23, 2003

Reviewer:  Bill Schwartz  from Montpelier, Vermont

Tom,
  Was ever so happy to find your site.  I am a newbie to Oracle db management,
and have been researching use of app. context w/FGAC in a web environment.
Looking for pitfalls to avoid, I have been scanning metalink forums, etc.  Came
across this post and was curious if anything ever came out of your last followup
msg.  Thanks so much for sharing your vast store of Oracle knowledge with the
rest of us!

          Followup:

the two messages below (the prior two to these) discuss how to clear them.

      Rating: 5
package used in
      application context  December 31, 2003
Reviewer:
      Laly  from France
Dear Tom,

I have a question concerning the package used in the create context : do the
specification of the package have to be always the same :
   procedure set_session_id( p_session_id in number );
   procedure set_ctx( p_name in varchar2, p_value in varchar2 );
   procedure close_session( p_session_id in number );

I cannot understand otherwise how Oracle knows how to use the provided package
when activating the context. However, i cannot find any documentation related to
this point.

Thanks in advance,

Laly

          Followup:

YOU "activate" the context

your procedure/function names can be whatever please you.

the package could be:

create package demo_pkg
as
   procedure foo;
end;

for all we care.  if you:

create context my_ctx using demo_pkg;

that just means that any procedure/function in demo_pkg will be successful in
using dbms_session.set_context to 'activate' it.

Unless you set it, it isn't "there", this just controls the name of the schema
object that is permitted to set it.

December 31, 2003 Nag -- Thanks for the question regarding "outer
      join", version 8.1.7

      You Asked
Tom

SQL> desc emp
 Name                                      Null?    Type
 ----------------------------------------- -------- -------------------------
 EMPNO                                     NOT NULL NUMBER(4)
 ENAME                                              VARCHAR2(10)
 JOB                                                VARCHAR2(9)
 MGR                                                NUMBER(4)
 HIREDATE                                           DATE
 SAL                                                NUMBER(7,2)
 COMM                                               NUMBER(7,2)
 DEPTNO                                             NUMBER(2)

SQL> alter table emp add (dname varchar2(20));

Table altered.

SQL> ed
Wrote file afiedt.buf

  1  select ename,emp.deptno,emp.dname
  2  from emp,dept
  3  where emp.deptno(+)=dept.deptno and  .. outer join
  4* emp.dname=dept.dname                 .. no outer join
SQL> /

no rows selected

SQL> update emp set dname='ACCOUNTING' ;

14 rows updated.

SQL> commit;

Commit complete.

SQL> select ename,emp.deptno,emp.dname
  2  from emp,dept
  3  where emp.deptno(+)=dept.deptno and       .. outer join

  4  emp.dname(+)=dept.dname                   .. outer join

  5  /

ENAME          DEPTNO DNAME
---------- ---------- --------------------
CLARK              10 ACCOUNTING
KING               10 ACCOUNTING
MILLER             10 ACCOUNTING

6 rows selected.

Tom , why is it necessary to give (+)  on all the join conditions between the
two tables if we want to outer join between two tables.

2.In 8i or 9i I heard that we can give outer join on both sides of the join. Can
you give us an example. What purpose does it serve, i.e. what kind of scenario
is it useful for...

Thanks

      and we said...
It is the very definition of an outer join, that is why.  It is the way they
"work".

If you see a query like this:

  from t1, t2
 where t1.x = t2.x(+)
   and t2.any_other_column <some condition>

you know for a fact that outer join is a waste, a performance drain, something
to be removed from the query.

Ask yourself this -- how can a row in T2 (EMP in your example) be both MISSING
(made up due to the outer join) AND have a column that is equal to something???
It cannot be.

The other one is called a FULL OUTER JOIN.  If you FULL OUTER JOIN T1 to T2 --
it means that ALL rows in T1 and ALL rows in T2 will appear in the result set.

Here is a demo of each:

ops$tkyte@ORA920.US.ORACLE.COM> @demo010
=================================================================

natural join, just a BAD idea
notice the deptno in the projection has no qualifier, in fact, it cannot!
this is a bug waiting to happen, relying on the "names" to join

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select ename, dname, deptno
  2    from SCOTT.EMP natural join SCOTT.DEPT
  3  /

ENAME      DNAME              DEPTNO
---------- -------------- ----------
SMITH      RESEARCH               20
ALLEN      SALES                  30
WARD       SALES                  30
JONES      RESEARCH               20
MARTIN     SALES                  30
BLAKE      SALES                  30
CLARK      ACCOUNTING             10
SCOTT      RESEARCH               20
KING       ACCOUNTING             10
TURNER     SALES                  30
ADAMS      RESEARCH               20
JAMES      SALES                  30
FORD       RESEARCH               20
MILLER     ACCOUNTING             10

14 rows selected.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

Inner join, the right idea. Same as a "natural" join but you
list the columns

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select ename, dname, emp.deptno, dept.deptno
  2    from SCOTT.EMP inner join SCOTT.DEPT
  3      on emp.deptno = dept.deptno
  4  /

ENAME      DNAME              DEPTNO     DEPTNO
---------- -------------- ---------- ----------
SMITH      RESEARCH               20         20
ALLEN      SALES                  30         30
WARD       SALES                  30         30
JONES      RESEARCH               20         20
MARTIN     SALES                  30         30
BLAKE      SALES                  30         30
CLARK      ACCOUNTING             10         10
SCOTT      RESEARCH               20         20
KING       ACCOUNTING             10         10
TURNER     SALES                  30         30
ADAMS      RESEARCH               20         20
JAMES      SALES                  30         30
FORD       RESEARCH               20         20
MILLER     ACCOUNTING             10         10

14 rows selected.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

outer join syntax.  the LEFT outer join uses EMP as the
driving table here. if there were a row in EMP that didn't
have a mate in DEPT, it would make one up

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select ename, dname, emp.deptno, dept.deptno
  2    from SCOTT.EMP LEFT outer join SCOTT.DEPT
  3      on emp.deptno = dept.deptno
  4  /

ENAME      DNAME              DEPTNO     DEPTNO
---------- -------------- ---------- ----------
MILLER     ACCOUNTING             10         10
KING       ACCOUNTING             10         10
CLARK      ACCOUNTING             10         10
FORD       RESEARCH               20         20
ADAMS      RESEARCH               20         20
SCOTT      RESEARCH               20         20
JONES      RESEARCH               20         20
SMITH      RESEARCH               20         20
JAMES      SALES                  30         30
TURNER     SALES                  30         30
BLAKE      SALES                  30         30
MARTIN     SALES                  30         30
WARD       SALES                  30         30
ALLEN      SALES                  30         30

14 rows selected.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

outer join syntax.  the RIGHT outer join uses DEPT as the
driving table here. if there were a row in DEPT that didn't
have a mate in EMP (and there is), it would make one up

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select ename, dname, emp.deptno, dept.deptno
  2    from SCOTT.EMP RIGHT outer join SCOTT.DEPT
  3      on emp.deptno = dept.deptno
  4  /

ENAME      DNAME              DEPTNO     DEPTNO
---------- -------------- ---------- ----------
SMITH      RESEARCH               20         20
ALLEN      SALES                  30         30
WARD       SALES                  30         30
JONES      RESEARCH               20         20
MARTIN     SALES                  30         30
BLAKE      SALES                  30         30
CLARK      ACCOUNTING             10         10
SCOTT      RESEARCH               20         20
KING       ACCOUNTING             10         10
TURNER     SALES                  30         30
ADAMS      RESEARCH               20         20
JAMES      SALES                  30         30
FORD       RESEARCH               20         20
MILLER     ACCOUNTING             10         10
           OPERATIONS                        40

15 rows selected.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

FULL outer join syntax. Every row in EMP and DEPT will appear  at least once
Note that I am making up DEPTNO's in EMP so there are EMP's without DEPT's
as well as DEPT's without EMP's

As you can imagine, this is EXPENSIVE

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set autotrace on explain
ops$tkyte@ORA920.US.ORACLE.COM> select nvl(ename,'**MADE UP**') ename,
  2         nvl(dname,'**MADE UP**') dname,
  3             emp.deptno, dept.deptno
  4    from (select ename, deptno+20 deptno
  5            from SCOTT.EMP ) EMP FULL outer join SCOTT.DEPT
  6      on emp.deptno = dept.deptno
  7  /

ENAME       DNAME              DEPTNO     DEPTNO
----------- -------------- ---------- ----------
MILLER      SALES                  30         30
KING        SALES                  30         30
CLARK       SALES                  30         30
FORD        OPERATIONS             40         40
ADAMS       OPERATIONS             40         40
SCOTT       OPERATIONS             40         40
JONES       OPERATIONS             40         40
SMITH       OPERATIONS             40         40
JAMES       **MADE UP**            50
TURNER      **MADE UP**            50
BLAKE       **MADE UP**            50
MARTIN      **MADE UP**            50
WARD        **MADE UP**            50
ALLEN       **MADE UP**            50
**MADE UP** RESEARCH                          20
**MADE UP** ACCOUNTING                        10

16 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=10 Card=163 Bytes=6846)
   1    0   VIEW (Cost=10 Card=163 Bytes=6846)
   2    1     UNION-ALL
   3    2       HASH JOIN (OUTER) (Cost=5 Card=82 Bytes=3444)
   4    3         TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=82 Bytes=1640)
   5    3         TABLE ACCESS (FULL) OF 'DEPT' (Cost=2 Card=82 Bytes=1804)
   6    2       HASH JOIN (ANTI) (Cost=5 Card=81 Bytes=2835)
   7    6         TABLE ACCESS (FULL) OF 'DEPT' (Cost=2 Card=82 Bytes=1804)
   8    6         TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=82 Bytes=1066)

ops$tkyte@ORA920.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA920.US.ORACLE.COM>

Review &
      Followup

      Rating: 5
is this inline
      view useless?  August 11, 2002
Reviewer:
      A reader
Hi Tom

As usual excellent explanation about outer joins!
I have a query which uses outer joins as follows

select view_services.destine_a,
       view_services.destine_b,
       tmp_unit_call.id_price,
       dm_bsc.id_contract
  from dm_bsc, -- 8000000 rows no index
       tmp_pre_call, -- 12000000 rows no index
       tmp_unit_call, -- 20 rows no index
       (select a.destine_a,
               a.destine_b
          from (select distinct destine_a, destine_b from tmp_pre_call) a,
               dm_services b, -- 40 rows no indes
               dm_services c -- 40 rows no index
         where a.destine_a = b.destine(+)
           and a.destine_b = c.destine(+)
        ) view_services
where tmp_pre_call.msisdn = dm_bsc.de_msisdn(+)
  and tmp_pre_call.id_cdr = tmp_unit_call.id_cdr(+)
  and tmp_pre_call.destine_a = view_services.destine_a(+)
  and tmp_pre_call.destine_b = view_services.destine_b(+)

As You can see there is an inline view called view_services, I think it is not
needed and it can be converted to

select view_services.destine_a,
       view_services.destine_b,
       tmp_unit_call.id_price,
       dm_bsc.id_contract
  from dm_bsc, -- 8000000 rows no index
       tmp_pre_call, -- 12000000 rows no index
       tmp_unit_call, -- 20 rows no index
       dm_services b, -- 40 rows no index
       dm_services c -- 40 rows no index
where tmp_pre_call.msisdn = dm_bsc.de_msisdn(+)
  and tmp_pre_call.id_cdr = tmp_unit_call.id_cdr(+)
  and tmp_pre_call.destine_a = b.destine(+)
  and tmp_pre_call.destine_b = c.destine(+)

Am I correct assumming that the outer join in the inline view makes this query
convertable? (If it was not an outer join I would filter out more rows in
tmp_pre_call)
The first query is slow because tmp_pre_call is full scanned twice, one in
inline view and the other in the query that is why I am looking to converting
it.

Cheers

          Followup:

Just a quick glance here -- these queries are not the same -- there is a
DISTINCT on tmp_pre_call in the inline view that is missing in the other query.
That could affect the cardinality of the result set.

It really depends on the QUESTION being asked.  Do this (this is how I write
queries myself) -- phrase the QUESTION in english.  Forget about any existing
queries and develop a query that answers the question.

      Rating:
      5
  November 18, 2002
Reviewer:
      A reader
Hi tom,

 can you please explain.....

  1   select e1.*, e2.ename from emp e1,dept d ,emp e2
  2   where e1.deptno = d.deptno(+)
  3*  and e1.mgr = e2.empno(+)
  4  /

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

15 rows selected.

SQL> select * from dept;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING     NEW YORK
        20 RESEARCH       DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS     BOSTON

How come one table emp from scott/tiger can be outer joined to two tables
emp(self joined) and dept ?

Thanks,

          Followup:

why not?

      Rating:
      4
  November 19, 2002
Reviewer:  Sagi
       from India
Hi Tom,

Thanx for the very good examples of JOINs in 9i.

I could not get what you ment by saying:

"If you see a query like this:

  from t1, t2
 where t1.x = t2.x(+)
   and t2.any_other_column <some condition>

you know for a fact that outer join is a waste, a performance drain, something
to be removed from the query."

Could you kindly explain in detail....

Regards,
Sagi

          Followup:

doesn't:

you know for a fact that outer join is a waste, a performance drain, something
to be removed from the query.

Ask yourself this -- how can a row in T2 (EMP in your example) be both MISSING
(made up due to the outer join) AND have a column that is equal to something???

It cannot be.

answer that?  I don't know how else to say it.  if you see:

where t1.x = t2.x(+)
  and t2.y = 5

you KNOW that outer join is a waste cause if we outer joined from t1 to t2 and
there was no matching row in t2 -- y would be NULL, NULL = 5 would cause the row
to disappear.  Hence if we outer join -- we'll discard it anyway.  Hence, outer
join is a waste of energy in that query.

      Rating: 5
can you please
      explain   November 19, 2002
Reviewer:
      A reader
Hi Tom,

 so  if   emp from scott/tiger is
1.) outer joined to dept all the rows will be returned from
    emp even a match is not found in dept
2.) emp1.mgr is outer joined to emp2.empno and all the rows
    will be returned from emp1 even a match is not found in
    emp2.empno

  so can I say that in both the cases the set {emp1} is
 constant and outer joined query will try to find matches
 and if found display it
  ( return all rows from emp1 and just get the mathces from all other outer
joined tables . no processing on emp1 ) ?

          Followup:

1) correct
2) correct

not -- not really (not in general).  if either of the join keys were NOT unique
in the joined to tables - the rows in emp could be MULTIPLIED (eg: if dept had 2
rows for deptno = 20)

Otherwise, if the joined to keys are unique -- then "yes"

      Rating: 5
But we need the
      record returned!!  November 19, 2002
Reviewer:
      John  from MA
<q>
If you see a query like this:

  from t1, t2
 where t1.x = t2.x(+)
   and t2.any_other_column <some condition>
</q>

wouldn't :

  from t1, t2
 where t1.x = t2.x(+)
   and (t2.y = ?any? or t2.y IS NULL)

work if a record was needed? we have changed some of
our programs to the or t2.y is null) for this very reason.

Is there a better way? (cost wise)

          Followup:

that is DIFFERENT then isn't it?

what I'm saying is I see literally TONS of code that has:

  from t1, t2
 where t1.x = t2.x(+)
   and t2.any_other_column = :value

All that is doing is precluding the optimizer from lots of paths it could
otherwise take.  It is a waste and tells me "I'm not really sure what I'm doing
here -- guess I'll outer join cause someone said I'll lose rows if I don't"

If you need it when ( t2.y = <some value> or t2.y is NULL ) that is totally very
100% different.

      Rating:
      3
PL/SQL  November 20, 2002
Reviewer:
      sriram  from india
Hi Tom

I am sorry for posting a slightly different question in this thread.

I want to write a PL/SQL procedure which when executed, will prompt for a
number, print "less than 100" if the entered number is lesser than 100 OR print
"equal/more than 100" if the entered number is equal/greater than 100.

Requirement is that, I cannot pass the same as arguments, rather, the code
should primpt for the inputs when executed (exec).

Please post the code for the same. This is urgently needed.

Regards
Sriram

          Followup:

won't be happening.  PLSQL runs in the database -- typically on a different
machine.  PLSQL isn't appropriate for writing an interactive program.

Unless of course you are using Oracle Forms on the client -- then it is
basically what Oracle Forms does.

So, if this is a database stored procedure -- no joy for you and no chance of
getting there from here.

If this is Oracle forms running plsql -- thats basically what it does.

      Rating: 4
outer join to
      subset  November 20, 2002
Reviewer:  Darko
       from RSA
"what I'm saying is I see literally TONS of code that has:

  from t1, t2
 where t1.x = t2.x(+)
   and t2.any_other_column = :value"

It can be valid (but with changed sintax) if you want to outer join
to the subset of the table.

Example is if DEPARTMENT table has one more column ACTIVE_IND (active
depratement Y/N).
Task is to get all employees and coresponding department names, but department
must be active, otherwise department name should be empty.

select a.emp_name, b.dep_name
from EMP a,
     DEP b
WHERE a.dep_id = b.dep_id (+)
  AND b.active_ind (+) = 'Y'

          Followup:

but that is not ANYTHING LIKE WHAT I HAVE TYPED IN

there is no outer join on the second predicate.  That is exactly my point.  When
I see code that VERBAITIM looks like:

  from t1, t2
 where t1.x = t2.x(+)
   and t2.any_other_column = :value

I know the person writing it did not understand for a moment what they were
really doing.

      Rating: 4
Full Tbale scan
      for FULL OUTER JOIN  November 21, 2002
Reviewer:
      Balaji  from India
Hi Tom
As i could see , do you mean to say that a full outer join on EMP and Dept will
always result in a full table scan on both the tables.
I think this would be the case because eventhough we give the condition say
d.dept_id(+) = 100 and e.emp_id (+) = 101
the optimizer has to fetch all the records that match the condition dept_id =
100 and also if dept_id <> 100 and smilarly for emp_id.
Regards
Balaji

          Followup:

I hesitate to say "always".

scott@ORA920.US.ORACLE.COM> select *
  2    from (select * from EMP where job = 'CLERK') emp
  3         full outer join
  4             (select * from DEPT where loc = 'BOSTON' ) dept
  5             on (emp.deptno = dept.deptno)
  6  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=4 Card=2 Bytes=234)
   1    0   VIEW (Cost=4 Card=2 Bytes=234)
   2    1     UNION-ALL
   3    2       NESTED LOOPS (OUTER) (Cost=2 Card=1 Bytes=117)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=1 Card=1
Bytes=87)
   5    4           INDEX (RANGE SCAN) OF 'JOB_IDX' (NON-UNIQUE) (Cost=1 Card=1)
   6    3         TABLE ACCESS (BY INDEX ROWID) OF 'DEPT' (Cost=1 Card=1
Bytes=30)
   7    6           INDEX (UNIQUE SCAN) OF 'PK_DEPT' (UNIQUE)
   8    2       NESTED LOOPS (ANTI) (Cost=2 Card=1 Bytes=49)
   9    8         TABLE ACCESS (BY INDEX ROWID) OF 'DEPT' (Cost=1 Card=1
Bytes=30)
  10    9           INDEX (RANGE SCAN) OF 'LOC_IDX' (NON-UNIQUE) (Cost=1 Card=1)
  11    8         TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=1 Card=1
Bytes=19)
  12   11           INDEX (RANGE SCAN) OF 'JOB_IDX' (NON-UNIQUE) (Cost=1 Card=1)

not a full scan to be found in that one.

      Rating: 5
Outer
      Join  March 10, 2003
Reviewer:  Anil
       from Malaysia
DearTom,
I am having a table test like this

select * from test;

ACCT_NO Type     AMOUNT
-----   -        ----------
6285    C         1000
6285    D         2000
6286    C         1000
6286    D         2000
6287    C         1000
6288    D         1000

C is Credit and D is Debit

I want a result set like this

Acct_No  Credit    Debit
6285     1000       2000
6286     1000       2000
6287     1000
6288                1000

I am using Oracle 8.1.7 so i  cannot use FULL OUTER JOIN FEATURE OF 9i. If i use
outer join i don't get all the 4 account numbers.

Can You please help in giving me a query.

          Followup:

why would you even consider joining?

select acct_no,
       sum( decode( type, 'C', amount, null ) ) credit,
       sum( decode( type, 'D', amount, null ) ) debit
  from test
 group by acct_no;

      Rating: 5
what if pk is
      of two columns ?  March 10, 2003
Reviewer:
      A reader
table dept

deptcd      number ,
dept_group_id number,
dept_nm     varchar2(50)  NULL,
dept_brname  varchar2(30) NULL,
dept_notes   varchar2(30)

alter table dept
add constraint pk_dept
 primary key(deptcd,dept_group_id);

table emp

empid        number primary key,
ename_name   varchar2(50),
e_deptcd     number,
entry_ts     date,
deptcd       number,
dept_group_id number,

alter table emp
add constraint fk_emp
 foreign key(deptcd ,dept_group_id)
     references dept(deptcd,dept_group_id);

**NOTE** : in emp table dept can be null **

Now,,

  I need to develop a qurey ( I think an outer join)
so show " emp by dept cd and also those who has dept null ".

how can i do it ?

 please advise !

Thanks,

          Followup:

your question

  I need to develop a qurey ( I think an outer join)
so show " emp by dept cd and also those who has dept null ".

doesn't make sense to me -- do you mean:

for all departements, print out emps and if there are no emps, print out the
dept information anyway.

If so, yes, it is just

select *
  from emp, dept
 where dept.deptcd = emp.deptcd(+)
   and dept.dept_group_id = emp.dept_group_id(+);

      Rating: 5
my
      try  March 10, 2003
Reviewer:  A reader

Hi tom,

 i tried this way

select deptcd,dept_group_id,empid
from emp e,dept d
where e.deptcd = d.deptcd(+)
and   e.dept_group_id = d.dept_group_id (+)

with a test data, it gave me correct result ?
can I trust  on it ?

 on it is just an one odd case ?

          Followup:

it is a correct outer join from DEPT to EMP given your schema.

      Rating: 5
Thanks
      tom  March 10, 2003
Reviewer:  A reader

      Rating: 5
How
      ?  March 10, 2003
Reviewer:  A reader

Hi tom,

 Thanks for your quick reply and sorry for ambiguous description of the
question.

 can you please tell me
from emp, dept
 where dept.deptcd = emp.deptcd(+)    <<-
   and dept.dept_group_id = emp.dept_group_id(+); <<-

how is it going to be eveluated ?

  I mean  isn't both conditions evaluationed seperately ?
if yes, then won't there be any dups  in the result set ?

can you please explain in detail ?

          Followup:

sigh, it is a join, it won't "duplicate anything".  it is just a join.  it
doesn't matter if your join key is 1 column or 20 columns -- it is just a join.

      Rating:
      5
Thanks,  March 10, 2003
Reviewer:
      A reader
thanks tom !!

I thought that
the oracle engine will take (evaluate) one predicate at a time. so i thought
that it will create one set

set A with dept.deptcd = emp.deptcd(+)

and then set b with

set B  dept.dept_group_id = emp.dept_group_id(+)

now both have outer join so I was wondering
how will it will join two outer joins perserving the
primary key

Thanks,
vivek

          Followup:

the predicate is an entity, it applies to the entire thing, it is not done a
piece at a time like that.

      Rating: 5
Thanks
      !!  March 10, 2003
Reviewer:  A reader

      Rating: 3
Why not this
      then...?  March 11, 2003
Reviewer:  Jon
       from Sydney
Following on from Darko, who wrote:

select a.emp_name, b.dep_name
from EMP a,
     DEP b
WHERE a.dep_id = b.dep_id (+)
  AND b.active_ind (+) = 'Y'

why wont Oracle allow us to extend this to:

select a.emp_name, b.dep_name
from EMP a,
     DEP b
WHERE a.dep_id = b.dep_id (+)
  AND (b.active_ind (+) = 'Y' OR b.active_ind (+) = 'X')

I can not see any logical reason why this could not be evaluated successfully,
but as it stands it needs to be coded around using a union.  Could you explain
please?

Thanks.

          Followup:

Either of:

ops$tkyte@ORA920> select ename, job, dname
  2    from emp, dept
  3   where dept.deptno = emp.deptno(+)
  4     and 'X' = decode(emp.job(+), 'ANALYST', 'X', 'SALESMAN', 'X' )
  5  /

ENAME      JOB       DNAME
---------- --------- --------------
                     ACCOUNTING
SCOTT      ANALYST   RESEARCH
FORD       ANALYST   RESEARCH
ALLEN      SALESMAN  SALES
WARD       SALESMAN  SALES
TURNER     SALESMAN  SALES
MARTIN     SALESMAN  SALES
                     OPERATIONS

8 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select ename, job, dname
  2    from (select * from emp where job in ( 'ANALYST', 'SALESMAN' ) ) emp,
  3         dept
  4   where dept.deptno = emp.deptno(+)
  5  /

ENAME      JOB       DNAME
---------- --------- --------------
                     ACCOUNTING
SCOTT      ANALYST   RESEARCH
FORD       ANALYST   RESEARCH
ALLEN      SALESMAN  SALES
WARD       SALESMAN  SALES
TURNER     SALESMAN  SALES
MARTIN     SALESMAN  SALES
                     OPERATIONS

8 rows selected.

works?

      Rating: 5
Evaluation of
      Predicate  March 11, 2003
Reviewer:  Jerry
       from Washington, DC USA
I know this is not outer-join related, but the discussion of predicates brought
a question to my mind.  I have a colleague who uses a varchar2 to store a date
value (I know, I know...).

Her code only works when the GT "space" condition is given first.  There are 2
values in the table, space, and a date value

This is 8.1.7.  Does Oracle sometimes enforce the predicate line-by-line?

SQL> select count(*) from ps_tl_rptd_elptime
  2  where to_date(user_field_3,'dd-mon-yyyy') = trunc(sysdate + 4)
  3  and user_field_3 > ' ';
where to_date(user_field_3,'dd-mon-yyyy') = trunc(sysdate + 4)
              *
ERROR at line 2:
ORA-01847: day of month must be between 1 and last day of month

SQL>
SQL> select count(*) from ps_tl_rptd_elptime
  2  where user_field_3 > ' '
  3  and to_date(user_field_3,'dd-mon-yyyy') = trunc(sysdate + 4);

  COUNT(*)
----------
         5

SQL>
SQL>
SQL> select user_field_3,count(*)
  2  from ps_tl_rptd_elptime
  3  group by user_field_3;

USER_FIELD_3      COUNT(*)
--------------- ----------
                        19
01-DEC-2002          18224
01-FEB-2003          11680
01-MAR-2003          12503
02-JUN-2002          19065
03-AUG-2002          11391
03-NOV-2002          18612

          Followup:

he he he.

if the table is not analyzed, analyze it and run again.  see what surprising
results you get.

If the table is analyzed, delete the stats for a minute.  see what surprising
results you get.

basically -- as the plans change so shall the order of evaluation of the
predicate.

the safe way to query that data would be:

where user_field_3 > ' '
  and CASE when user_field_3 > ' ' then to_date(user_field_3,'dd-mon-yyyy')
           else null
       end > trunc(sysdate+4);

      Rating: 5
Very
      cool...  March 11, 2003
Reviewer:  Jon
       from Sydney
that is some box of tricks you have at your disposal...

          Followup:

Let me clarify:

that is some box of tricks we all have at our disposal....

      Rating: 3
Data
      Compare  March 22, 2003
Reviewer:  Gov
       from Novoyta, CA
Tom ,
  I have  to compare compare  colums of two tables t1 , t2  and load the t1 if
the column pair does not exist in t2.
The colums( Product, term )  many be combinations of null.
for example
A123, null
null , 360
A123, 180
null, null
 are different records.
The table t1 is very huge.  I do not want to use Minus.
The query
t1 - a1, b1
t2 - a2 , b2

select

from t1 , t2
 t1.a1 = t2.a2(+) and
 t1.b1=t2.b2(+) and
  t2.a2 is null and t2.b2 is null does not give the same result as Minus  when
the data has nulls.

a1 ,a2  aree not primary keys on the tables.

How do you handle this.

Thanks in  advance.
Gov

          Followup:

why don't you want to use MINUS which is

a) brutally efficient
b) the right answer
c) the answer I'm going to give you....

      Rating: 3
In a Package
      ???  March 22, 2003
Reviewer:  Gov
       from Novoto, CA
I am using this inside an ETL package.Is That Ok?
Does the Sze of t1 and t1 matter. I have about 2.5 Million records in t1 and
about 100K on t2.

Thanks
Govind.

          Followup:

in a package, in a program, in sqlplus -- what difference would that make?

Anyway, on a "stock off the shelf $800 desktop PC"

big_table@ORA920> create table temp as
  2  select -rownum id, a.*
  3    from (select * from all_objects union all
  4          select * from all_objects union all
  5                  select * from all_objects union all
  6                  select * from all_objects ) a
  7  /

Table created.

Elapsed: 00:00:11.58
big_table@ORA920> analyze table temp compute statistics for table;

Table analyzed.

Elapsed: 00:00:01.39
big_table@ORA920>
big_table@ORA920> select count(*) from temp;

  COUNT(*)
----------
    119552

1 row selected.

Elapsed: 00:00:00.06
big_table@ORA920> select count(*) from big_table;

  COUNT(*)
----------
   3816192

1 row selected.

Elapsed: 00:00:02.13
big_table@ORA920>
big_table@ORA920> insert /*+ APPEND */ into big_table
  2  select * from temp
  3  MINUS
  4  select * from big_table;

119552 rows created.

Elapsed: 00:01:14.62
big_table@ORA920>

So, you should expect even better on your database server....

      Rating: 5
How to do an
      outer join on composite keys?  April 11, 2003

Reviewer:  A Reader
Hi Tom,
We have table t1 where PK has more than one column and table t2 that references
the PK in t1.  Some values in t1 are not in t2 and we want all the values in t1
show up in our query.  Since we cannot do "where t1.pk(1)=t2.fk(1)(+) and
t1.pk(2)=t2.fk(2)(+)" because only one outer join can be used in a query.  What
we do now is using inline views (or views) by concat the two PK columns into one
and then do an outer join.  Are there any other ways to do the outer join?
Thank you for your help.

          Followup:

umm, try again.

where t1.x = t2.x (+) and t1.y = t2.y(+)

most certainly does, has and will work.

      Rating: 4
Full Outer Join
      Bug ???  April 13, 2003
Reviewer:  C.A.
       from Vancouver, BC, Canada
I think full outer join is very cool but -if I'm not doing anything wrong- it's
not working very well with grouped by inline views.
--
select * from foj_test_01

    STORE       SKU TRAN_CODE  REF_NO_1 TRAN_DATE   RTD_UNITS
--------- --------- --------- --------- ----------- ---------
      938   2020582        20   3020362 17-MAR-2003         3
      938   2020582        22         7 31-MAR-2003         2
--
select * from foj_test_02

    STORE       SKU TRAN_CODE  REF_NO_1 TRAN_DATE   STD_UNITS
--------- --------- --------- --------- ----------- ---------
      938   2020582        22         7 31-MAR-2003         2
      938   2020582        22        13 27-MAR-2003         1
--
Direct table to table full outer join (works)
--
select nvl(std.store,rtd.store) store,nvl(std.sku,rtd.sku)
sku,nvl(std.tran_code,rtd.tran_code) tran_code
     , nvl(std.ref_no_1,rtd.ref_no_1) ref_no_1,std.tran_date
store_tran_date,rtd.tran_date rms_tran_date
      , std_units, rtd_units
  from foj_test_01 std
      full outer join
      foj_test_02    rtd
    on std.store=rtd.store
    and std.sku=rtd.sku
    and std.tran_code=rtd.tran_code
    and std.ref_no_1=rtd.ref_no_1
    and
std.tran_date=decode(std.tran_code,1,rtd.tran_date,std.tran_date)
/
    STORE       SKU TRAN_CODE  REF_NO_1 STORE_TRAN_ RMS_TRAN_DA STD_UNITS
RTD_UNITS
--------- --------- --------- --------- ----------- ----------- ---------
---------
      938   2020582        22         7 31-MAR-2003 31-MAR-2003         2
2
      938   2020582        20   3020362 17-MAR-2003
3
      938   2020582        22        13             27-MAR-2003         1
--
with group by inline views, each inline view retrieves same data with select *
from table. at the 3rd line all fields are populated but the sum one
(rtd_units=3)is not displayed.
--
select nvl(std.store,rtd.store) store,nvl(std.sku,rtd.sku)
sku,nvl(std.tran_code,rtd.tran_code) tran_code
     , nvl(std.ref_no_1,rtd.ref_no_1) ref_no_1,std.tran_date
store_tran_date,rtd.tran_date rms_tran_date
      , rtd_units , std_units
  from( select store,sku,tran_code,nvl(ref_no_1,1) ref_no_1,tran_date,
sum(std_units) std_units
          from foj_test_02
      group by store,sku,tran_code,ref_no_1,tran_date
        ) std
      full outer join
      ( select store,sku,tran_code,ref_no_1,tran_date, sum(rtd_units)
rtd_units
          from foj_test_01
      group by store,sku,tran_code,ref_no_1,tran_date
        )    rtd
    on std.store=rtd.store
    and std.sku=rtd.sku
    and std.tran_code=rtd.tran_code
    and std.ref_no_1=rtd.ref_no_1
    and
std.tran_date=decode(std.tran_code,1,rtd.tran_date,std.tran_date)
/
    STORE       SKU TRAN_CODE  REF_NO_1 STORE_TRAN_ RMS_TRAN_DA RTD_UNITS
STD_UNITS
--------- --------- --------- --------- ----------- ----------- ---------
---------
      938   2020582        22         7 31-MAR-2003 31-MAR-2003         2
2
      938   2020582        22        13 27-MAR-2003
1
      938   2020582        20   3020362             17-MAR-2003

--
and try last query with right outer join instead of full outer join. Works well
again.
--
    STORE       SKU TRAN_CODE  REF_NO_1 STORE_TRAN_ RMS_TRAN_DA RTD_UNITS
STD_UNITS
--------- --------- --------- --------- ----------- ----------- ---------
---------
      938   2020582        22         7 31-MAR-2003 31-MAR-2003         2
2
      938   2020582        20   3020362             17-MAR-2003         3

          Followup:

no versions...

in 9203 it did not reproduce, in 9201 and 9012 it did.  Contact support.

      Rating: 5
Selecting
      unmatched records  May 07, 2003
Reviewer:
      ramks  from India
Hi Tom

I have used outer join in my query, I want to filter out
only unmatched rows between tables. How to do that? and what are all the ways?

thanks in adv

          Followup:

select t1.*
  from t1, t2
 where t1.key = t2.key(+)
   and t2.key IS NULL;

or

select * from t1 where key NOT IN ( select key from t2 )

or

select * from t1
minus
select * from t2;

      Rating: 4
Can I achieve
      FULL OUTER JOIN in 7.3.4?  May 13, 2003
Reviewer:
      VL  from HK
Dear Tom,

Can I achieve FULL OUTER JOIN in 7.3.4?

Rgs,
VL

          Followup:

select *
  from t1, t2
 where t1.key = t2.key (+)
 UNION ALL
select *
  from t1, t2
 where t1.key(+) = t2.key
   and t1.key IS NULL;

      Rating:
      4
Excellent  June 12, 2003
Reviewer:
      David Jiang  from Houston, Texas, USA
Hi, Tom,
Your explanation is great.
Here I have a query with some kind of difficult on FULL OUTER JOIN. Would you
please tell me why it does not work?
Here is the one with Right outer join. It works fine.
SELECT a.INDEX_NAME, a.COLUMN_NAME ,  j.COLUMN_NAME
FROM ( SELECT * FROM (
SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION  FROM BASELINE_INDEXES
MINUS
SELECT INDEX_NAME, COLUMN_NAME,COLUMN_POSITION
FROM REPORT_INDEXES)
WHERE INDEX_NAME NOT IN( SELECT INDEX_NAME from BASELINE_INDEXES
minus
SELECT INDEX_NAME FROM REPORT_INDEXES)) a
right outer join
(SELECT * FROM (
SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION
FROM REPORT_INDEXES
MINUS
SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION
FROM BASELINE_INDEXES )
WHERE INDEX_NAME NOT IN( SELECT INDEX_NAME from REPORT_INDEXES
minus
SELECT INDEX_NAME FROM BASELINE_INDEXES)) j
ON a.INDEX_NAME = j.INDEX_NAME
and a.COLUMN_POSITION  =  j.COLUMN_POSITION;

INDEX_NAME      COLUMN_NAME      COLUMN_NAME
--------------- ---------------- ----------------
INDEX2_IN_BASEL USER_ID          INT2
INE

INDEX2_IN_BASEL INT1             INT3
INE

                                 INT4
Here is the one with FULL Outer Join
SELECT a.INDEX_NAME, a.COLUMN_NAME ,  j.COLUMN_NAME
FROM ( SELECT * FROM (
SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION  FROM BASELINE_INDEX
MINUS
SELECT INDEX_NAME, COLUMN_NAME,COLUMN_POSITION
FROM REPORT_INDEXES)
WHERE INDEX_NAME NOT IN( SELECT INDEX_NAME from BASELINE_INDEXES
minus
SELECT INDEX_NAME FROM REPORT_INDEXES)) a
full outer join
(SELECT * FROM (
SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION
FROM REPORT_INDEXES
MINUS
SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION
FROM BASELINE_INDEXES )
WHERE INDEX_NAME NOT IN( SELECT INDEX_NAME from REPORT_INDEXES
minus
SELECT INDEX_NAME FROM BASELINE_INDEXES)) j
ON a.INDEX_NAME = j.INDEX_NAME
and a.COLUMN_POSITION  =  j.COLUMN_POSITION;

It does not give me the expected result as right outer join:

INDEX_NAME      COLUMN_NAME      COLUMN_NAME
--------------- ---------------- ----------------
INDEX2_IN_BASEL USER_ID          INT2
INE

INDEX2_IN_BASEL INT1             INT3
INE
Thanks a lot.

          Followup:

no test case.  you should be able to simplify this, have the create tables,
insert intos and as small of a query as possible (like i do, for you, with the
examples)

      Rating: 2
Updated
      Information  June 12, 2003
Reviewer:  David
      Jiang  from Houston, Texas, USA
Sorry, Tom,

Here are the test case. Plese help me!
I have two tables.
table T1
INDEX_NAME      COLUMN_NAME      COLUMN_POSITION
--------------- ---------------- ---------------
INDEX2_IN_BASE INT2                           1
INDEX2_IN_BASE ROLE_ID                        2
INDEX2_IN_BASE INT3                           3
INDEX2_IN_BASE INT4                           4

table T2:
INDEX_NAME      COLUMN_NAME      COLUMN_POSITION
--------------- ---------------- ---------------
INDEX2_IN_BASE INT1                           3
INDEX2_IN_BASE ROLE_ID                        2
INDEX2_IN_BASE USER_ID                        1
INDEX2_IN_BASE INT7                           4

I want a query to use the full out joint(?) to get the result like the
following:

INDEX_NAME      COLUMN_NAME      INDEX_NAME      COLUMN_NAME
--------------- ---------------- --------------- -----------
INDEX2_IN_BASE USER_ID          INDEX2_IN_BASE    INT2

INDEX2_IN_BASE INT1             INDEX2_IN_BASE    INT3

                                INDEX2_IN_BASE    INT4
INDEX2_IN_BASE INT7

I could not get a right query to get the result in this format. Thanks a lot.

          Followup:

well, now you really lost me.  guess I'll back up.  what exactly is the question
you are trying to ask of this data -- forget your answer for a moment, what is
the "question"

I see no question that would give rise to the "answer"

      Rating:
      4
  June 13, 2003
Reviewer:  john123

Is the keywords "inner join", "outer join" just pseudo code and Oracle really
understand these?  I have tried it but it seems Oracle doesn't understand.  I am
using Oracle 8.1.7.

          Followup:

they are new Oracle9i syntax features.

      Rating: 3
Updated
      information  June 13, 2003
Reviewer:  David
      Jiang  from Houston, Tesxa USA
OK, Tom,
Let me start from the beginning.
I have two tables t1 and t2:
SQL> select * from t1;
INDEX_NAME      COLUMN_NAME      COLUMN_POSITION
--------------- ---------------- ---------------
INDEX2_IN_BASE  INT1                           3
INDEX2_IN_BASE  USER_ID                        1
INDEX_IN_BASE   ACTION_DATE                    2
INDEX_IN_REPORT ACTION_ID                      2
INDEX_IN_REPORT TEST1                          1
INDEX_IN_REPORT TEST2                          3
SQL>select * from t2;
INDEX_NAME      COLUMN_NAME      COLUMN_POSITION
--------------- ---------------- ---------------
INDEX2_IN_BASE  INT2                           1
INDEX2_IN_BASE  INT3                           3
INDEX2_IN_BASE  INT4                           4
INDEX_IN_REPORT ACTION_ID                      2
INDEX_IN_REPORT USER_ID                        1
INDEX2_IN_BASE  ACTION_DATE                    2

I want to list t1.INDEX_NAME, t1.column_name,t2.INDEX_NAME, t2.column_name from
t1 and t2 which have the same index_name, but different column_name according to
their column_position.

Here is my query and result:
SQL> SELECT  a.INDEX_NAME, a.COLUMN_NAME,j.INDEX_NAME, j.COLUMN_NAME
  2  FROM (
  3  SELECT INDEX_NAME, COLUMN_NAME,COLUMN_POSITION FROM t1
  4  MINUS
  5  SELECT  INDEX_NAME, COLUMN_NAME,COLUMN_POSITION FROM t2) a
  6  FULL OUTER JOIN
  7  (SELECT INDEX_NAME, COLUMN_NAME,COLUMN_POSITION  FROM t2
  8  MINUS
  9  SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION FROM t1) j
 10  on a.INDEX_NAME = j.INDEX_NAME
 11  and a.COLUMN_POSITION  =  j.COLUMN_POSITION;

INDEX_NAME      COLUMN_NAME      INDEX_NAME      COLUMN_NAME
--------------- ---------------- --------------- -----------
INDEX2_IN_BASE  USER_ID          INDEX2_IN_BASE  INT2
INDEX2_IN_BASE  INT1             INDEX2_IN_BASE  INT3
INDEX_IN_REPORT TEST1            INDEX_IN_REPORT USER_ID
INDEX_IN_REPORT TEST2
INDEX_IN_BASE   ACTION_DATE

I missed COLUMN_NAME=INT4 from t2. This query is the same as Left outer join
somehow. Here is my right outer join result:
SQL> SELECT  a.INDEX_NAME, a.COLUMN_NAME,j.INDEX_NAME, j.COLUMN_NAME
  2  FROM (
  3  SELECT INDEX_NAME, COLUMN_NAME,COLUMN_POSITION FROM t1
  4  MINUS
  5  SELECT  INDEX_NAME, COLUMN_NAME,COLUMN_POSITION FROM t2) a
  6  RIGHT OUTER JOIN
  7  (SELECT INDEX_NAME, COLUMN_NAME,COLUMN_POSITION  FROM t2
  8  MINUS
  9  SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION FROM t1) j
 10  on a.INDEX_NAME = j.INDEX_NAME
 11  and a.COLUMN_POSITION  =  j.COLUMN_POSITION;

INDEX_NAME      COLUMN_NAME      INDEX_NAME      COLUMN_NAME
--------------- ---------------- --------------- -----------
INDEX2_IN_BASE  INT1             INDEX2_IN_BASE  INT3
INDEX2_IN_BASE  USER_ID          INDEX2_IN_BASE  INT2
INDEX_IN_REPORT TEST1            INDEX_IN_REPORT USER_ID
                                 INDEX2_IN_BASE  INT4
                                 INDEX2_IN_BASE  ACTION_DATE
I really need a FULL OUTER JOIN query to show the extra COLUNM_NAME from both t1
and t2.
Hope this will help you to understand what I try to do so that you will help me
to get the right query.

Thank you very much.

          Followup:

you way overcomplicated it then -- here is an example.  I believe you are trying
to compare indexes in two different schema's or perhaps databases and finding
differences.  Here is how:

ops$tkyte@ORA920> create user a identified by a;

User created.

ops$tkyte@ORA920> create user b identified by b;

User created.

ops$tkyte@ORA920> alter user a default tablespace users quota unlimited on
users;

User altered.

ops$tkyte@ORA920> alter user b default tablespace users quota unlimited on
users;

User altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table a.t1( a int, b int, c int, d int, e int, f int );

Table created.

ops$tkyte@ORA920> create index a.idx1 on a.t1(a,b,c);

Index created.

ops$tkyte@ORA920> create index a.idx2 on a.t1(d,e,f);

Index created.

ops$tkyte@ORA920> create index a.idx3 on a.t1(d,c,b,e,f,a);

Index created.

ops$tkyte@ORA920> create index a.idx10 on a.t1(a,b,c,d,e,f);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table b.t1( a int, b int, c int, d int, e int, f int );

Table created.

ops$tkyte@ORA920> create index b.idx2 on b.t1(e,f,d);

Index created.

ops$tkyte@ORA920> create index b.idx3 on b.t1(d,c,e,b,f,a);

Index created.

ops$tkyte@ORA920> create index b.idx4 on b.t1(a,b,c);

Index created.

ops$tkyte@ORA920> create index b.idx10 on b.t1(a,b,c,d,e,f);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> break on index_name skip 1
ops$tkyte@ORA920>
ops$tkyte@ORA920> select nvl( t1.index_name, t2.index_name) index_name,
  2         t1.column_name,
  3         t2.column_name,
  4         nvl(t1.column_position,t2.column_position) pos
  5    from (select *
  6            from dba_ind_columns
  7           where index_owner = 'A') t1 full outer join
  8         (select *
  9            from dba_ind_columns
 10           where index_owner = 'B') t2
 11      on ( t1.index_name = t2.index_name
 12           and
 13           t1.column_position = t2.column_position )
 14   where t1.column_name <> t2.column_name
 15      or (t1.column_name is null and t2.column_name is not null)
 16          or (t1.column_name is not null and t2.column_name is null)
 17   order by  1, 4
 18  /

INDEX_NAME                     COLU COLU        POS
------------------------------ ---- ---- ----------
IDX1                           A                  1
                               B                  2
                               C                  3

IDX2                           D    E             1
                               E    F             2
                               F    D             3

IDX3                           B    E             3
                               E    B             4

IDX4                                A             1
                                    B             2
                                    C             3

11 rows selected.

      Rating:
      3
  June 13, 2003
Reviewer:  Susan
       from Watertown, MA
Tom, when you say

If you see a query like this:

  from t1, t2
 where t1.x = t2.x(+)
   and t2.any_other_column <some condition>

you know for a fact that outer join is a waste, a performance drain, something
to be removed from the query.

does that mean that an inner join will return the same results?  I didn't write
this view but was asked to tune it.

SELECT DISTINCT
 MV.*, ...
 AP.APPTDATETIME,
 AP.PERCODE AP_PERCODE,
 AP.COMMENTS...,
 FROM
   AREACODE_TIMEZONE AC,
  APPOINTMENTS AP,
   (select * from callrecord where (formstat_id,starttime) in (select
formstat_id,max(starttime) from callrecord group by formstat_id))
 CR,
   EVENTCAT EC,
   EVENTTYPE ET,
   EVENTS EV,
   FORM FO,
   FORMSTATUS FS,
   MAST_HOUS MV
WHERE
     (EV.EVENTTYPE_ID = ET.EVENTTYPE_ID)
 AND (FS.MASTER_ID = EV.MASTER_ID)
 AND (EV.EVENTTYPE_ID = FS.EVENTTYPE_ID)
 AND (FS.FORM_ID = FO.FORM_ID)
 AND (FO.ISCATI =1)
 AND (MV.MASTER_ID = EV.MASTER_ID)
 AND (FS.FORMSTAT_ID = CR.FORMSTAT_ID (+) )
 AND (ET.ECATCODE = EC.ECATCODE)
 AND (FS.MASTER_ID = AP.MASTER_ID (+))
 AND (FS.EVENTTYPE_ID = AP.EVENTTYPE_ID(+))
 AND(AP.APPT_TYPE_ID(+) = 800)
 AND (AP.APPTDATETIME(+)>TRUNC(SYSDATE))
 AND (ET.USECATI = 1)
 AND (AC.AREACODE = AreaCode (MV.CATIPHONE))
 AND (FS.STATUS != 'M')
ORDER BY MV.MASTER_ID, FS.FORMSTAT_ID;

I removed the outer joins on AP, but, the results differed.  Is there a better
way?  Thanks

          Followup:

how could you remove the outer join on AP?  it did not meet the criteria.

You always outer joined to it -- only if you saw:

where ap.x(+) = fs.eventtype_id
  and ap.y = 5

would you be able to safely remove it.

      Rating: 5
Outer Join
      Issue  July 26, 2003
Reviewer:  Sikandar
      Hayatq  from Pindi Bhattian - Pakistan
During using out joins I am facing the following issue which I am unable to
understand,

I am using two queries and just chanding e.name to e.empno and the query works
but why not working with e.ename.

SQL>  select e.ename, d.dname from emp e full outer join dept d
  2   on (e.deptno=d.deptno);
 on (e.deptno=d.deptno)
                      *
ERROR at line 2:
ORA-00600: internal error code, arguments: [qcscpqbTxt], [904], [], [], [], [],
[], []

SQL>  select e.empno, d.dname from emp e full outer join dept d
  2   on (e.deptno=d.deptno);

     EMPNO DNAME
---------- --------------
      7782 ACCOUNTING
      7839 ACCOUNTING
      7934 ACCOUNTING
      7369 RESEARCH
      7876 RESEARCH
      7902 RESEARCH
      7788 RESEARCH
      7566 RESEARCH
      7499 SALES
      7698 SALES
      7654 SALES
      7900 SALES
      7844 SALES
      7521 SALES
           OPERATIONS

15 rows selected.

SQL>

          Followup:

please contact support and file a tar for that one.

I tried in 9ir1 and 9ir2 and could not reproduce your findings.

      Rating: 4
Order of FULL
      OUTER JOINS  August 11, 2003
Reviewer:  Adrian
      Billington  from UK
Tom

Every day we get a bunch of source files to be loaded to an accounts table. We
have external tables over the current day's files and the previous day's files
and we MINUS them to get just the changes that have occurred since yesterday.
These deltas are inserted into GTTs.

I'm trying to MERGE the GTTs with the base table and because all the GTTs
contain different parts of an account record, I am trying full outer joins to
create a "logical" record for MERGE.

In my particular example, I have five delta GTTs ranging from 1 to 850 records
only. If I FULL OUTER JOIN the GTTs in record count descending order, my load
takes 34 CPU secs. If I FOJ in ascending order then it takes 790 CPU seconds.

How can I mitigate this ( short of programatically counting the records in the
delta GTTs every day and dynamically full outer joining them in the descending
order ). Is there some other method I should be thinking of, because FULL OUTER
JOIN sure does cost a bit...

Regards
Adrian

          Followup:

full outer joins are heinously expensive.

select * from emp full outer join dept on (emp.deptno = dept.deptno)

is processed much like:

select *
  from emp, dept
 where emp.deptno = dept.deptno(+)
 UNION ALL
select *
  from emp, dept
 where emp.deptno(+) = dept.deptno
   and emp.deptno IS NULL;

do that a couple of times and -- well -- it gets "hard"

are you using 9iR2?  if so, read about OPTIMIZER_DYNAMIC_SAMPLING and set it to
3 or above.

      Rating: 3
I have my
      stats...  August 11, 2003
Reviewer:  Adrian
       from UK
Tom

Yes, I am on 9.2.0.3 and am using dynamic sampling at level 5 ( I love this
feature the most of all ).

You're right - it starts to get horrible and seems to work much better when I
work in rowcount descending order, but I can't guarantee that order every day.
Perhaps I'm trying to be too clever, but if I don't make this stuff beat
PowerMart I can kiss goodbye to my Oracle skills... :o(

Thanks
Adrian

          Followup:

what does the FOJ look like here?

      Rating: 3
One hell of a
      plan...  August 12, 2003
Reviewer:  Adrian
      Billington  from UK
Tom

Trying to upload the output of running a FOJ in rowcount descending and
ascending orders but continually getting timeouts. Will mail it to
thomas.kyte@oracle.com instead.

As an alternative in the meantime, I'm thinking of preparing the records
up-front in a GTT, merging into the GTT one file (external table) at a time.
Then when it's all ready, MERGE into the base table once.

Regards
Adrian

      Rating: 5
display
      rowwise  September 25, 2003
Reviewer:  Tani
       from bangalore
hi tom ..
      is it possible to display the column values  of a table in a single row ..

suppose for emp table ..if i write

select sal from emp .. it will display like this

sal
---
100
200
300
400
500

i want to display the same on a single row ..

 100 200 300 400 500 ..

any suggesstion ....

though there is no relevance for this ..but somebody asks the same in interviews
..

Regards
Tani

          Followup:

only if you now the max number of rows, then yes you can.  else no you cannot
(well sort of, i can with object types -- cast it into a collection)

      Rating:
      5
thankx  September 26, 2003
Reviewer:
      tanmoy datta choudhury  from bangalore
thanks for the reply ...
now for emp table we knw that there are 14 rows ..
how to display sal in a single row ...then ..

Regards
Tani

          Followup:

select max( decode( rownum, 1, sal ) ),
       max( decode( rownum, 2, sal ) ),
       max( decode( rownum, 3, sal ) ),
       ....
       max( decode( rownum, 14, sal ) )
  from emp;

      Rating: 5
Substring and
      OuterJoin  October 28, 2003
Reviewer:
      Bhavani  from India
Hi Tom,

I have a query like this:

create table t_tracking
as
select a.f_id, a.f_access_time, a.f_dest_page, a.f_status, a.f_cid, a.f_web_id,
a.f_fe, b.f_id f_base_dest_page_id,
a.f_name from t_id_tracking1 a, t_id_category b
where a.f_access_time < '28-OCT-03'
and substr(f_dest_page, 1, instr(f_dest_page, '?') -1) (+) =
lower(b.f_base_destination_page);

But I am getting:

ERROR at line 6:
ORA-00920: invalid relational operator

Can't we use substring and Outer Join in a single query? or Am I doing some
thing wrong?

Thanks for your time.

Best Regards
Bhavani

          Followup:

i see so many wrong things

what is '28-oct-03' -- is that supposed to be a date?  please -- use to_date and
PLEASE use the absolute minimum number of characters needed to accurately and
unambigously specify a year -- 4.  2003, not 03.

You are joining based on functions. (ugh)

you seem to be storing data "willy nilly" -- the use of LOWER for example.  why
would you have to lower the data?

I'm assuming f_dest_page is in table A.  If so, why would you "outer join" in
one predicate -- but not the other?  That just seems plain wrong

You can use inline views as below -- but the performance of this query is going
to be absolutely horrible

ops$tkyte@ORA920PC> select dname, ename
  2    from emp, dept
  3   where (substr( emp.deptno, 1, 2 )) (+) = to_char( dept.deptno )
  4  /
 where (substr( emp.deptno, 1, 2 )) (+) = to_char( dept.deptno )
                                      *
ERROR at line 3:
ORA-00920: invalid relational operator

ops$tkyte@ORA920PC> select dname, ename
  2    from (select substr(deptno,1,2) deptno, ename from emp ) emp,
  3         (select to_char(deptno) deptno, dname from dept ) dept
  4   where emp.deptno (+) = dept.deptno
  5  /

DNAME          ENAME
-------------- ----------
ACCOUNTING     CLARK
ACCOUNTING     KING
ACCOUNTING     MILLER
RESEARCH       SMITH
RESEARCH       ADAMS
RESEARCH       FORD
RESEARCH       SCOTT
RESEARCH       JONES
SALES          ALLEN
SALES          BLAKE
SALES          MARTIN
SALES          JAMES
SALES          TURNER
SALES          WARD
OPERATIONS

15 rows selected.

      Rating: 4
full outer
      join  November 05, 2003
Reviewer:  Scott
      Wesley  from Perth, Western Australia
G'day Tom (long time, no post)

I hope I'm not flogging a dead horse here, but in regards to full outer joins on
the example of Customers with Sales and Budgets (3 tables).
Similar to Anil's debit/credit example I guess, but in separate tables.
9i would allow FULL OUTER JOIN syntax, which I'm not familiar with, since I have
8174.

What do you think of the following solution (I didn't find the 817 dept-emp
example appropriate):

select acc_nbr, sum(sales), sum(budget)
from
(select acc_nbr, sales, 0 budget
 from ths_sales
 union
 select acc_nbr, 0, budget
 from ths_budgets
)
group by acc_nbr, catgy;

in regards to performance, readability, alternatives...

We find this quite effective, and the only real solution to our problem where
customer might have sales and no budget, or budget and no sales.

Solution found in Metalink was not suitable for our more complex example (
http://metalink.oracle.com/cgi-bin/cr/getfile.cgi?p_attid=132416.1:275803
Yes, we are using Discovererer.

Thanks.

          Followup:

well, i would replace the UNION with UNION ALL:

drop table t1;
drop table t2;

create table t1 as select rownum acc_nbr, object_id sales, a.* from
all_objects a;
create table t2 as select rownum acc_nbr, data_object_id budget, a.* from
all_objects a;

analyze table t1 compute statistics for table;
analyze table t2 compute statistics for table;

select acc_nbr, sum(sales), sum(budget)
from
(select acc_nbr, sales, 0 budget
 from t1 ths_sales
  union
   select acc_nbr, 0, budget
    from t2 ths_budgets
    )
    group by acc_nbr

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     2014      1.19       1.16         53        898          0       30192
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2016      1.19       1.16         53        898          0       30192

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 211

Rows     Row Source Operation
-------  ---------------------------------------------------
  30192  SORT GROUP BY (cr=898 r=53 w=0 time=1013152 us)
  60383   VIEW  (cr=898 r=53 w=0 time=794692 us)
  60383    SORT UNIQUE (cr=898 r=53 w=0 time=643063 us)
  60383     UNION-ALL  (cr=898 r=53 w=0 time=398021 us)
  30191      TABLE ACCESS FULL T1 (cr=456 r=1 w=0 time=45277 us)
  30192      TABLE ACCESS FULL T2 (cr=442 r=52 w=0 time=48966 us)
********************************************************************************
select acc_nbr, sum(sales), sum(budget)
from
(select acc_nbr, sales, 0 budget
 from t1 ths_sales
  union all
   select acc_nbr, 0, budget
    from t2 ths_budgets
    )
    group by acc_nbr

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     2014      0.99       0.94         55        898          0       30192
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2016      0.99       0.95         55        898          0       30192

Rows     Row Source Operation
-------  ---------------------------------------------------
  30192  SORT GROUP BY (cr=898 r=55 w=0 time=798323 us)
  60383   VIEW  (cr=898 r=55 w=0 time=556844 us)
  60383    UNION-ALL  (cr=898 r=55 w=0 time=406987 us)
  30191     TABLE ACCESS FULL T1 (cr=456 r=1 w=0 time=45889 us)
  30192     TABLE ACCESS FULL T2 (cr=442 r=54 w=0 time=52493 us)

but yes, this technique works QUITE nicely.  You would be using it even in light
of the HUGELY expensive full outer join (i'm not hugely fond of them, i see them
as a forth coming performance issue.  developers who don't know if they need to
use it, will use it (like they use outer joins when its not needed) leading to
really expensive plans)

select nvl( ths_sales.acc_nbr, ths_budget.acc_nbr ), sum(sales), sum(budget)
  from t1 ths_sales full outer join t2 ths_budget on ( ths_sales.acc_nbr =
ths_budget.acc_nbr )
 group by  nvl( ths_sales.acc_nbr, ths_budget.acc_nbr )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     2014      1.33       1.33        140       1796          0       30192
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2016      1.33       1.33        140       1796          0       30192

Rows     Row Source Operation
-------  ---------------------------------------------------
  30192  SORT GROUP BY (cr=1796 r=140 w=0 time=1177893 us)
  30192   VIEW  (cr=1796 r=140 w=0 time=1031680 us)
  30192    UNION-ALL  (cr=1796 r=140 w=0 time=950802 us)
  30191     HASH JOIN OUTER (cr=898 r=67 w=0 time=424276 us)
  30191      TABLE ACCESS FULL T1 (cr=456 r=13 w=0 time=47248 us)
  30192      TABLE ACCESS FULL T2 (cr=442 r=54 w=0 time=55104 us)
      1     HASH JOIN ANTI (cr=898 r=73 w=0 time=372206 us)
  30192      TABLE ACCESS FULL T2 (cr=442 r=60 w=0 time=50634 us)
  30191      TABLE ACCESS FULL T1 (cr=456 r=13 w=0 time=48348 us)

      Rating: 5
To "Bhavani
      from India"  November 05, 2003
Reviewer:  Logan
      Palanisamy  from Santa Clara, CA USA
Bhavani,

You wrote
<Quote>
I have a query like this:

create table t_tracking
as
select a.f_id, a.f_access_time, a.f_dest_page, a.f_status, a.f_cid, a.f_web_id,
a.f_fe, b.f_id f_base_dest_page_id,
a.f_name from t_id_tracking1 a, t_id_category b
where a.f_access_time < '28-OCT-03'
and substr(f_dest_page, 1, instr(f_dest_page, '?') -1) (+) =
lower(b.f_base_destination_page);

But I am getting:

ERROR at line 6:
ORA-00920: invalid relational operator

Can't we use substring and Outer Join in a single query? or Am I doing some
thing wrong?
<End Quote>

Your problem is the outer join notation "(+)" should come close to the column
name, not before the = sign as you have specified.

The wrong way:
substr(f_dest_page, 1, instr(f_dest_page, '?') -1) (+) =

The right way:
substr(f_dest_page(+), 1, instr(f_dest_page, '?') -1) =

Of course, whatever Tom said are still valid.

      Rating: 5
To "Bhavani
      from India"  November 05, 2003
Reviewer:  Logan
      Palanisamy  from Santa Clara CA USA
Oops!!

The really correct one is

substr(f_dest_page(+), 1, instr(f_dest_page(+), '?') -1) = ...

      Rating: 5
outer join OR
      something else  December 30, 2003
Reviewer:
      Karma  from MA USA
Hi: I have question where i think i need outer join and also other condition
applied and dont know how to get correct answer: So i have 3 tables
ab_mstr,ab_trx,and ab_rest. I want
All rows from ab_mstr that DO NOT exist in ab_trx (key = f_firm_c,f_br_c,
f_ba_c) and
ALL rows from ab_trx where ab_trx.f_txn = 0 and discard or do not return this
records if ab_rest.rest_id = 'Q1' or 'Q2' where trx (firM_c,f_br_c,f_ba_c) =
rest.(firm_c,f_ba_c, f_br_c)

CREATE TABLE AB_MSTR
    (
      F_FIRM_C  VARCHAR2(4 BYTE)                 NOT NULL,
      F_BR_C  VARCHAR2(3 BYTE)                 NOT NULL,
      F_BA_C  VARCHAR2(6 BYTE)                 NOT NULL,
      EFF_D        DATE                             NOT NULL

   )
drop table ab_trx
create table ab_trx
( f_firm_c varchar2(4 byte) not null,
  f_br_c   varchar2 (3 byte) not null,
  f_ba_c   varchar2(6 byte) not null,
  f_txn    number,
  trx_dt   date
  )

 create table ab_rest
( f_firm_c varchar2(4 byte) not null,
  f_br_c   varchar2 (3 byte) not null,
  f_ba_c   varchar2(6 byte) not null,
  rest_id  varchar2(2)
  )

  insert into ab_mstr values ('0101','ABB','123456',sysdate)
  insert into ab_mstr values ('0101','BCC','234567',sysdate)

  insert into ab_trx values ('0101','ABB','123456',0,sysdate+5)
  insert into ab_trx values ('0101','EEE','123456',0,sysdate)

  insert into ab_rest values ('0101','EEE','123456','Q1')
  insert into ab_rest values ('0101','EEE','123456','Q2')
  insert into ab_rest values ('0101','EEE','123456','C1')

i do know this is wrong!!!

  select trx.f_firm_c, trx.f_br_c, trx.f_ba_c from ab_trx trx, ab_rest rest
  where trx.f_firm_c = rest.F_FIRM_C(+)
  and   trx.F_BR_C   = rest.F_BR_C(+)
  and   trx.F_BA_C   = rest.F_BA_C(+)
  and   trx.f_txn = 0
  and   rest.rest_id IN ('Q1','Q2')

Thanks

          Followup:

....
All rows from ab_mstr that DO NOT exist in ab_trx (key = f_firm_c,f_br_c,
f_ba_c) and
ALL rows from ab_trx where ab_trx.f_txn = 0 and discard or do not return this
records if ab_rest.rest_id = 'Q1' or 'Q2' where trx (firM_c,f_br_c,f_ba_c) =
rest.(firm_c,f_ba_c, f_br_c)
.......

you want all rows from T1 that do not exist in T2
AND
you want all rows in T2 subject to some criteria

that -- well, it quite simply doesn't compute to me.  don't see how they would
be presented in a result set.

      Rating: 5
outer join with
      what?  December 30, 2003
Reviewer:  karma
       from MA USA
In previous case i forgot to mention but correct answer would be:
1 row returned from ab_mstr since this row only exists in ab_mstr and not in
ab_trx.
('0101','BCC','234567')
also ONLY 1 row from ab_trx even though all 3 rows has f_txn = 0 but 2 rows gets
rejected since they have rest_id of 'Q1' or 'Q2' in ab_rest table.
  insert into ab_trx values ('0101','ABC','123456',0,sysdate+5)

      Rating: 5
outer join or
      core-related subquery  December 31, 2003
Reviewer:
      karma  from MA USA
Well here is what T1, T2 and T3 means. T1 has all accounts, T2 has data for
daily trading activity for accounts. T3 has list of restrictions codes assigned
to accounts. I want to generate inactive report so
 Given this any accounts that exists in T1 but not in T2 implies that this
accounts do not have any activity and thus should be included. I also want to
include any accounts in T2 where some count = 0 ( count = 0 means no trading
activity) and EXCLUDE any of this accounts from T2 who may have some
restrictions assinged to it in T3.

Hope this is clear

          Followup:

something like this maybe.

select ...
  from t1, t2
 where t1.key = t2.fkey(+)
   and ( t2.key is null -- get the t1 records that have no matches in t2
         or  -- get the records with count=0, and no restrictions
         (t2.key is not null and
          t2.count = 0 and
          not exists ( select null
                         from t3
                        where t3.key = t2.fkey )
       )

December 31, 2003 Timur -- Thanks for the question regarding
      "Indexes with reverse "like" predicate", version 8.1.7

      You Asked
Hello Tom.

I have a table with the phone number prefixes ending with %.
Example is

1234%
123%
35667%

The question is - how to find the match in this table
for the given phone number? Imagine we have phone number in
phone_number variable. If we use
select t.prefix from t where phone_number like t.prefix
then the index on t.prefix column doesn't work. Why the index
works in case of "t.prefix like phone_number" and doesn't work
in reverse case? I can't find any reason to not use it.

Thank you.

Tim.

      and we said...
we can use the index -- in an index fast full scan -- but tell me -- how would
you use an index to index range scan that data??????  you cannot -- it is
strings, not numbers, it is not "sortable" at all.

say the phone number is 123 456 7890

you have strings

X
----------
1%
12%
123%
1232%
1234%
13%

1232% sort of "gets in the way" there see.  you have to more or less inspect
THEM ALL.

Now, if there is some number of mandatory leading digits (eg: you must supply AT
LEAST 3 digits) something like:

select * from t where substr(t.prefix,1,3) = substr(phone_number,1,3) and
phone_number like t.prefix;

and you created an index on substr(t.prefix,1,3), that may well use an index.

Review &
      Followup

      Rating: 5
the problem
      essentially is  November 25, 2003
Reviewer:
      mikito harakiri
Given a set of intervals stored in the table as

table intervals (
   head integer,
   tail integer
)

find a subset of intervals that cover a given point.

For example, you have in your database records

[1,10]
[5,9]
[2,4]

and you are asked to find all them that cover 6. The answer is

[1,10]
[5,9]

This problem is known to be difficult. Although there are specialized indexing
schemes like R-Tree, none of them is as universally accepted as B-Tree.

      Rating:
      3
  November 25, 2003
Reviewer:  Timur
       from Russia
Thank you for your answer Tom.

Let me explain my question some more.
I have a prefixes in the table. It's a zone codes. Say we have that in our
table:

ID Zone   Zone_Name
1  1%     USA
2  1503%  USA, Oregon
3  7%     Russia
4  7095%  Russia, Moscow
5  %      Whole the world

There is an unique constraint on the zone column.

We have a phone number, now we have to find matching strings in that table. It
doesn't matter what should i do with them after that. I can get matching string
with the maximum length to get the actual match or i can use multiple rows -
doesn't matter. Right now i wonder is it possible to force using an index when
querrieng the multiple lines with 'like zone' predicate.
Say i have phone number "709533333333"

select * from zones_t where '7095333333333' like prefix
whould give me

3  7%     Russia
4  7095%  Russia, Moscow
5  %      Whole the world

This querry doesn't use index, although zones_t table is pretty big (3500 rows)
and it would grow even more. As you can see i don't know the prefix's length so
i can't use substr as you prompted in your answer.

Why are those strings not sortable? According to oracle docs, index is a
balanced binary tree with the values at the nodes. Character values forms the
sequence, isn't it? What prevents oracle to use the tree in our case? It can use
index very well when we querry

select * from zones_t where zone like '7095333333333'

but can't use it in the reverse case.

Well, may be it's possible to use some cartidge?

Thank you and sorry for my poor english.

          Followup:

i showed you why they are not sortable - they are strings, they sort string
wise, you don't want that.

X
----------
1%
12%
123%
1232%  <<<====== this guy is in the way when you are looking for 1234567890
1234%
13%

it cannot range scan.
it must inspect EACH entry.
your data does not SORT

do you see that there are possible an infinite set of values between:

  1  select *
  2   from ( select '7%' from dual union all
  3          select '7095%' from dual union all
  4             select '%' from dual )
  5*  order by 1
ops$tkyte@ORA920PC> /

'7%'
-----
%
7%
7095%

in between '%' and '7%' there are a gazillion possible values.

in between '7%' and '7095%' there are a gazillion more...

see the problem now?  you cannot "range scan" the index -- you have to look at
EACH entry -- all of them.

We can fast full scan the index (which may be faster then full scanning the
table) but this is going to be a rather inefficient approach.

Now, since this is a phone number thingy here, we have a finite set of digits to
deal with - suppose you decide "20 is the biggest phone number".  One approach
might be -- don't store a "like" string -- store the numbers

7
7095

then you query:

select *
  from t
 where prefix in ( :b, to_number(substr(:b,1,19)), to_number(substr(:b,1,18)),
                   ....., to_number(substr(:b,1,1)) );

that should do a nice inlist iterator for you.

      Rating:
      3
  November 25, 2003
Reviewer:  Tim

Sorry, Tom.
I just understude my mistake. Oracle can't use ordinary index on such a
querries, because it simply can't be sure that my column doesn't contain leading
'%' or '!'. Can i fix my situation with context cartridge?

      Rating: 3
no, not because
      of the leading %  November 25, 2003
Reviewer:
      Mikito Harakiri
the predicate

x like 'abc%'

is essentially the same as

x between 'abc' and 'abcZZZZZ'

where Z is the last symbol in the ASCII range.

This is why ordinary index range scan works in this case.

In the "inverse" problem you store the head and the tail of the interval into a
table like this

head  tail
----  ----
1234  1234ZZZZ
123   123ZZZZ
35667 35667ZZZZ

and query it with the predicate

'123' between head and tail.

Now think about any index that can help you.

          Followup:

hmmm, yes, using that think about any index that can help you -- i cannot think
of one.

the substr trick above will use a simple index.  The other way to think about
this is not a range -- but discrete values.

      Rating: 5
hierarchy can
      do it?  November 25, 2003
Reviewer:  igor
       from ME USA
What is the maximum length of a prefix? I guess 7. If so, we can create a table
with 10,000,000 records (it is not too many) with all possible prefix values,
organized as hierarchical tree, for example:

Zone  Zone_Name        Parent_Zone
----- ----------       ------------
-1    Whole the world  NULL
1     USA              -1
15    n/a              1
150   n/a              15
1503  USA, Oregon      150
.....
7     Russia           -1
70    n/a              7
709   n/a              70
7095  Russia, Moscow   709

(suppose the "whole world" zone has zone number -1 to avoid nulls)

Indexes on Zone and Zone_parent columns must be created of course. Now all the
matches can be found very effectively:

select zone, zone_name
from zones
connect by prior parent_zone=zone
start with zone=substr(phone_number,1,7);

      Rating: 4
...or
      maybe  November 26, 2003
Reviewer:  Padders
       from UK
Have done something similar to the original problem to analyze mobile phone call
data.

What we did was break the mobile numbers into constituent pieces based on
possible prefix lengths (held in a table) and then join back to the prefix
table. Something like this...

Oracle9i Enterprise Edition Release 9.2.0.1.0 - 64bit Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production

SQL> CREATE TABLE phone_prefix (
  2    id NUMBER (1),
  3    zone VARCHAR2 (6),
  4    zone_name VARCHAR2 (30));

Table created.

SQL> ALTER SESSION SET CURSOR_SHARING=FORCE;

Session altered.

SQL> INSERT INTO phone_prefix VALUES (1, '1%','USA');

1 row created.

SQL> INSERT INTO phone_prefix VALUES (2, '1503%', 'USA, Oregon');

1 row created.

SQL> INSERT INTO phone_prefix VALUES (3, '7%', 'Russia');

1 row created.

SQL> INSERT INTO phone_prefix VALUES (4, '7095%', 'Russia, Moscow');

1 row created.

SQL> INSERT INTO phone_prefix VALUES (5, '70953%', 'Russia, Moscow, Red
Square');

1 row created.

SQL> INSERT INTO phone_prefix VALUES (6, '%', 'Whole world');

1 row created.

SQL> CREATE UNIQUE INDEX i_phone_prefix ON phone_prefix (zone);

Index created.

SQL> CREATE TABLE valid_prefix_length (
  2    prefix_length NUMBER (1));

Table created.

SQL> INSERT INTO valid_prefix_length VALUES (0);

1 row created.

SQL> INSERT INTO valid_prefix_length VALUES (1);

1 row created.

SQL> INSERT INTO valid_prefix_length VALUES (2);

1 row created.

SQL> INSERT INTO valid_prefix_length VALUES (3);

1 row created.

SQL> INSERT INTO valid_prefix_length VALUES (4);

1 row created.

SQL> INSERT INTO valid_prefix_length VALUES (5);

1 row created.

SQL> INSERT INTO valid_prefix_length VALUES (6);

1 row created.

SQL> INSERT INTO valid_prefix_length VALUES (7);

1 row created.

SQL> COMMIT;

Commit complete.

SQL> VARIABLE phone_no VARCHAR2 (12)
SQL> EXEC :phone_no := '709533333333';

PL/SQL procedure successfully completed.

SQL> SELECT p.id, p.zone, p.zone_name
  2  FROM  (SELECT SUBSTR (:phone_no, 1, prefix_length) || '%' prefix
  3         FROM   valid_prefix_length) l, phone_prefix p
  4  WHERE  p.zone = l.prefix
  5  ORDER BY LENGTH (l.prefix);

        ID ZONE   ZONE_NAME
---------- ------ ------------------------------
         5 %      Whole world
         3 7%     Russia
         4 7095%  Russia, Moscow
         4 70953% Russia, Moscow, Red Square

SQL>

      Rating: 3
could somebody
      please clarify  December 01, 2003
Reviewer:
      Mikito Harakiri
Igor's solution is really nice, but could somebody please explain why a user
might want result set like this:

        ID ZONE   ZONE_NAME
---------- ------ ------------------------------
         5 %      Whole world
         3 7%     Russia
         4 7095%  Russia, Moscow
         4 70953% Russia, Moscow, Red Square

Doesn't "Russia, Moscow, Red Square" imply all the other records? What 3 other
records could possibly tell beyond the information that the user sees from the
last record?  (Let's ignore ID, which is a surrogate key, and ,therefore, is
meaningless).

      Rating: 3
A
      Reader  December 31, 2003
Reviewer:  alex
       from uk
Hi Tom

I have one table
xyz(x varchar2(10),y varchar2(2),z char(4), a1 char(2),....)
This table has primay key on(y,z)
This table has 100million records.
in column a1 for 99M the value is 'N' and for 1M values are
'Y1','Y2','Y3' etc..

I have the following query
"select y from xyz where z = '1234' and a1 like 'Y%'"

This query takes around 4 to 6 mins to fetch the selected data. Would you adivse
to have nonunique index on a1.
and use the folowing select

 "select /*+ INDEX(XYZ,XYZ_a1) */ y from xyz where z = '1234' and a1 like 'Y%'"
where XYZ_a1 is the index on a1.

please tell me the right way or if you have something good.

thanks

          Followup:

if getting these one million records is paramount, an index:

create index i1 on xyz( z, a1, y );

would be called for and the query would be answered entirely from the index and
hints are not necessary like that.

or even going a step further, we can index JUST the rows of interest, consider:

ops$tkyte@ORA9IR2> create table t ( x varchar2(10), y varchar2(2), z char(4), a1
char(2) );

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec dbms_stats.set_table_stats( user, 'T', numrows =>
100000000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create index t_idx on t( case when a1 like 'Y%' then z end,
  2                           case when a1 like 'Y%' then y end );

Index created.

since entirely null entries are not made in b*tree indexes -- this will index
ONLY the rows where a1 like 'Y%'

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec dbms_stats.set_index_stats( user, 'T_IDX', numrows =>
1000000, NUMDIST => 100000 );
PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace view v
  2  as
  3  select case when a1 like 'Y%' then y end y,
  4         case when a1 like 'Y%' then z end z
  5    from t
  6  /

View created.

you can use a view to hide the complexity or just query it straight

ops$tkyte@ORA9IR2> set autotrace traceonly explain
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select case when a1 like 'Y%' then y end y
  2    from t
  3   where case when a1 like 'Y%' then z end = '1234'
  4  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1000000 Bytes=13000000)
   1    0   INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=1000000
Bytes=13000000)

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select y from v where z = '1234'
  2  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1000000 Bytes=13000000)
   1    0   INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=1000000
Bytes=13000000)

ops$tkyte@ORA9IR2> set autotrace off
ops$tkyte@ORA9IR2>

December 31, 2003 Sam -- Thanks for the question regarding "All
      about the DUAL table ", version 8.1.7

      You Asked
Hi Tom

Feels good to post a question after quite some time.

Look at the following :

SQL> desc dual
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 DUMMY                                              VARCHAR2(1)

SQL> select * from dual;

D
-
X

SQL> select count(*) from dual;

  COUNT(*)
----------
         1

SQL> select 'sdfsdfsdfsdfsdfsdf' from dual;

'SDFSDFSDFSDFSDFSD
------------------
sdfsdfsdfsdfsdfsdf

SQL> select 3434334 from dual;

   3434334
----------
   3434334

SQL> truncate table dual;
truncate table dual
               *
ERROR at line 1:
ORA-04020: deadlock detected while trying to lock object
3510531527724648449742036

SQL> delete from dual;
delete from dual
            *
ERROR at line 1:
ORA-01031: insufficient privileges

SQL> connect system/manager
Connected.
SQL> /

1 row deleted.

SQL> commit;

Commit complete.

SQL> select * from dua;
select * from dua
              *
ERROR at line 1:
ORA-00942: table or view does not exist

SQL> select * from dual;

no rows selected

1.What is the dual table, what is its purpose.
2.Why does it contain only one column with datatype varchar2, why not
number .
3.Does it contain one row by default.
4.why do we
usually SELECT USER FROM DUAL,
why cant I do it like
SQL> select USER FROM EMP WHERE ROWNUM<2;

USER
------------------------------
SCOTT

5.Does this mean that we are using the dual table only for the convenience that
it has only one row, and it will return only one row back  , when we give
queries like

SELECT USER FROM DUAL

6.SQL> SELECT COUNT(*) FROM DUAL;

  COUNT(*)
----------
         2

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

1 row created.

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

1 row created.

SQL> COMMIT;

Commit complete.

SQL> SELECT COUNT(*) FROM DUAL;

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

SQL> SELECT * FROM DUAL;

D
-
X

Why is it returning only one row , it has to return 4 rows , when it has allowed
me to insert 4 rows.

7.SQL> delete from dual;

1 row deleted.

SQL> select * from dual;

D
-
X

SQL> select count(*) from dual;

  COUNT(*)
----------
         3

I want to delete all the rows , I do a delete from dual, and oooo mama
look at it , it deletes only one row, why?

8.SQL> delete from dual;

1 row deleted.

SQL> delete from dual;

1 row deleted.

SQL> select count(*) from dual;

  COUNT(*)
----------
         1

NOw we have only one row in dual.

lets create a example function.

SQL> create or replace function foo return number
  2  as
  3  x number;
  4  begin
  5  x:=1;
  6  return 1;
  7  end;
  8  /

Function created.

SQL> select foo from dual;

       FOO
----------
         1

SQL> insert into dual values ('X');

1 row created.

SQL> COMMIT;

Commit complete.

SQL> SELECT COUNT(*) FROM DUAL;

  COUNT(*)
----------
         2

SQL> select foo from dual;

       FOO
----------
         1

shouldnt it return 2 rows of value 1?

because , look below , I have got 14 rows back , because I had 14 rows in the
table.

SQL> SELECT FOO FROM EMP;

       FOO
----------
         1
         1
         1
         1
         1
         1
         1
         1
         1
         1
         1

       FOO
----------
         1
         1
         1

14 rows selected.

SQL>

Kindly elucidate the concept of dual table, and if possible illustrate it.

Thank you

      and we said...
Let me just start by saying -- DUAL is owned by SYS.  SYS owns the data
dictionary, therefore DUAL is part of the data dictionary.  You are not to
modify the data dictionary via SQL ever -- wierd things can and will happen --
you are just demonstrating some of them.  We can make many strange things happen
in Oracle by updating the data dictionary. It is neither recommend, supported
nor a very good idea.

1.What is the dual table, what is its purpose.

dual is just a convienence table.  You don't need to use it, you can use
anything you want.  The advantage to dual is the optimizer understands dual is a
special one row, one column table -- when you use it in queries, it uses this
knowledge when developing the plan.

2.Why does it contain only one column with datatype varchar2, why not
number .

truly, why no.  Why not a date you would ask then.  The column, its name, its
datatype and even its value are NOT relevant.  DUAL exists solely as a means to
have a 1 row table we can reliably select from.  Thats all.

3.Does it contain one row by default.

yes, when we build the database, we build dual and put a single row in it.

4.why do we
usually SELECT USER FROM DUAL,
why cant I do it like
SQL> select USER FROM EMP WHERE ROWNUM<2;

truly, why can't you?  is something preventing you from doing so??  You can if
you want.  Me, I'll stick with "select user from dual".  I know dual exists.  I
know it has at least 1 and at most 1 row.  I know the optimizer knows all about
dual and does the most efficient thing for me.

5) yes

6) the optimizer understands dual is a magic, special 1 row table.  It stopped
on the select * because there is to be one row in there.  Its just the way it
works.  Hopefully you reset dual back to 1 row after your testing or you just
totally broke your database!

7) like I said, duals magic, the optimizer knows all about what DUAL should be
and does things based on that.

8) dual = magic.  dual is a one row table however having more then 1 or less
then one is dangerous.  You are updating the data dictionary.  You should
naturally expect very bad things to happen.

Ok, here is some trivia for you out there.  How did I do this:

SVRMGR> select * from dual;
D
-
X
1 row selected.

SVRMGR> ????????????????????;
Statement processed.

SVRMGR> select * from dual;
ADDR     INDX       INST_ID    D
-------- ---------- ---------- -
01680288          0          1 X
1 row selected.

What was that magic command?
Review &
      Followup

      Rating: 5
was that some
      synonym you created?  October 07, 2001
Reviewer:
      A reader  from cincinnati
"each GV$ view contains an extra column named
INST_ID of datatype INTEGER" ..

      Rating: 5
Tom, this is
      the second time...  October 07, 2001
Reviewer:
      Mark A. Williams  from Indianapolis, IN USA
Tom:

Drum roll please...

SVRMGR> alter database close;
Statement processed.
SVRMGR> select * from dual;
ADDR     INDX       INST_ID    D
-------- ---------- ---------- -
00FA6E50          0          1 X
1 row selected.
SVRMGR>

That's 2 now... :) (the other was the "cluster" challenge)

- Mark

      Rating: 5
Weekly
      Challenge  October 08, 2001
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Tom:

Not that you don't already have enough to do...  but, maybe you could come up
with some kind of a weekly challenge?  I really enjoy figuring out your
challenges.  I certainly didn't know the answer to this one until you posed
it...

Anyway, thanks,

- Mark

          Followup:

Thats a cool idea -- we'll see if we can put one together (gotta write the
software for it ;)

      Rating: 5
Why Oracle
      would keep performance from us ?  October 08, 2001

Reviewer:  Connor  from Scotland
SQL> conn system/manager (or any other standard user)
Connected.
SQL> set autotrace on statistics
SQL> select * from dual;

D
-
X

Statistics
--------------------------------
          4  db block gets
          1  consistent gets

SQL> conn / as sysdba
SQL> set autotrace on statistics
SQL> select * from dual;

D
-
X

Statistics
--------------------------------
          0  db block gets
          0  consistent gets

I'd like to know why Oracle hasn't exposed the "fast dual access" to us mere
mortals.  For example, if you create your own dual table (as an IOT) you get
down to 1 get - maybe our good friends at Oracle could do this for us?
(Especially since they give us a Designer product which generates triggers which
have 'x := sysdate, y := user' in alarming numbers throughout - and poor ol'
dual take a hammering)

          Followup:

SYSDBA, SYSOPER, "internal" and sys in general shouldn't be used for anything
other then admin.  Things work differently (eg: no read only transactions), some
things don't work (eg: triggers), some things appear to work but don't really
(eg: set autotrace).

According to this, it takes no work for SYSDBA to do anything -- its not just
DUAL:

ops$tkyte@ORA817DEV.US.ORACLE.COM> connect / as sysdba
Connected.
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace on
ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from scott.emp;

  COUNT(*)
----------
        14

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FAST FULL SCAN) OF 'EMP_PK' (UNIQUE) (Cost=1 Card=14)

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

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from all_objects;

  COUNT(*)
----------
     16913

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     FILTER
   3    2       NESTED LOOPS
   4    3         TABLE ACCESS (FULL) OF 'USER$'
   5    3         TABLE ACCESS (BY INDEX ROWID) OF 'OBJ$'
   6    5           INDEX (RANGE SCAN) OF 'I_OBJ2' (UNIQUE)
   7    2       TABLE ACCESS (BY INDEX ROWID) OF 'IND$'
   8    7         INDEX (UNIQUE SCAN) OF 'I_IND1' (UNIQUE)
   9    2       NESTED LOOPS
  10    9         FIXED TABLE (FULL) OF 'X$KZSRO'
  11    9         TABLE ACCESS (BY INDEX ROWID) OF 'OBJAUTH$'
  12   11           INDEX (RANGE SCAN) OF 'I_OBJAUTH2' (NON-UNIQUE)
  13    2       FIXED TABLE (FULL) OF 'X$KZSPR'
  14    2       FIXED TABLE (FULL) OF 'X$KZSPR'
  15    2       FIXED TABLE (FULL) OF 'X$KZSPR'
  16    2       FIXED TABLE (FULL) OF 'X$KZSPR'
  17    2       FIXED TABLE (FULL) OF 'X$KZSPR'
  18    2       FIXED TABLE (FULL) OF 'X$KZSPR'
  19    2       FIXED TABLE (FULL) OF 'X$KZSPR'
  20    2       FIXED TABLE (FULL) OF 'X$KZSPR'
  21    2       FIXED TABLE (FULL) OF 'X$KZSPR'
  22    2       FIXED TABLE (FULL) OF 'X$KZSPR'
  23    2       FIXED TABLE (FULL) OF 'X$KZSPR'
  24    2       FIXED TABLE (FULL) OF 'X$KZSPR'
  25    2       FIXED TABLE (FULL) OF 'X$KZSPR'
  26    2       FIXED TABLE (FULL) OF 'X$KZSPR'

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

      Rating: 5
But why 5
      gets...?  October 08, 2001
Reviewer:  Connor
       from Bonny Scotland
I take your point, but as I said, an IOT dual gets you down to 1 get.  Surely
they could give us that?  Yes, I know, I can just go off any make my a MY_DUAL
iot and stop bugging Oracle and you :-), but it would be nice if they took
advantage of the very features they keep plugging for us.

          Followup:

demonstrate for me:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table my_dual ( x int primary key )
organization index;
Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into my_dual values ( 1 );
1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select sysdate from my_dual;

SYSDATE
---------
08-OCT-01

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1)
   1    0   INDEX (FAST FULL SCAN) OF 'SYS_IOT_TOP_19186' (UNIQUE) (Cost=1
Card=1)

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

ops$tkyte@ORA817DEV.US.ORACLE.COM> select sysdate from dual;

SYSDATE
---------
08-OCT-01

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

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

      Rating: 5
Test
      Results  October 08, 2001
Reviewer:  Mark A.
      Williams  from Indianapolis, IN USA
Well, not that anyone is asking, but this is what I got:

SQL> connect scott
Enter password:
Connected.
SQL> create table my_dual (dummy varchar2(1) primary key) organization index;

Table created.

SQL> insert into my_dual values('X');

1 row created.

SQL> commit;

Commit complete.

SQL> analyze table my_dual compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;

Table analyzed.

SQL> set autotrace on
SQL> select sysdate from my_dual;

SYSDATE
---------
08-OCT-01

1 row selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1)
   1    0   INDEX (FULL SCAN) OF 'SYS_IOT_TOP_31083' (UNIQUE) (Cost=1
          Card=1)

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

SQL> select sysdate from dual;

SYSDATE
---------
08-OCT-01

1 row selected.

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

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

SQL>

          Followup:

Yes, you are seeing the effect of an INDEX scan instead of a FULL scan.  See the
followup to connor's comment below for details....

      Rating: 5
no one has
      quite answered the trivia yet  October 08, 2001

Reviewer:  Doug  from Connecticut
I vauguely remember this from a year or two ago - but..
No one has explained
svrmgrl> ????????????????????
It's not a procedure with a normal name.. it's not a synonym that closes the
database so we can get are gv columns.  Still a piece missing I think.

          Followup:

Nope, Mark Williams (second comment) got it right with:

SVRMGR> alter database close;

closing the database makes this special dual table come into play (there for
RMAN to have a dual table to select from even when the database isn't fully up)

      Rating:
      5
x$dual  October 09, 2001
Reviewer:  Nick
      Josephson  from WV, USA
Tom,

My guess is that ????... denotes some sort of help
command and Oracle returns the row from x$dual.

However when I tried on W2K, Oracle 8.1.7, the command
does not return the expected result

-Nick

          Followup:

Nope, Mark Williams (second comment) got it right with:

SVRMGR> alter database close;

closing the database makes this special dual table come into play (there for
RMAN to have a dual table to select from even when the database isn't fully up)

      Rating: 5
Demo as
      requested  October 09, 2001
Reviewer:
      Connor  from miserable wet Scotland
(I've cut out the stats that are not important)

mcdonac@db1> create table xdual ( dummy varchar2(1) primary key ) organization
index;

Table created.

mcdonac@db1> insert into xdual values ('X');

1 row created.

mcdonac@db1> commit;

Commit complete.

mcdonac@db1> set autotrace on statistics;
mcdonac@db1> select * from dual;     <= NORMAL DUAL

D
-
X

Statistics
----------------------------------------------------------
          4  db block gets
          1  consistent gets

mcdonac@db1> select * from xdual;   <= MY VERSION (NOT ANALYZED)

D
-
X

Statistics
----------------------------------------------------------
          4  db block gets
          2  consistent gets

mcdonac@db1> analyze table xdual compute statistics;

Table analyzed.

mcdonac@db1> select * from xdual;  <= MY VERSION (ANALYZED)

D
-
X

Statistics
----------------------------------------------------------
          0  db block gets
          1  consistent gets

mcdonac@db1> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle8i Enterprise Edition Release 8.1.7.1.1 - Production
PL/SQL Release 8.1.7.1.0 - Production
CORE    8.1.7.0.0       Production
TNS for 32-bit Windows: Version 8.1.7.1.0 - Production
NLSRTL Version 3.4.1.0.0 - Production

          Followup:

Ok, here you go -- my 2 cents worth.

you are seeing the difference between a FULL scan and an INDEX scan.  In order
for the index scan to happen, you need to use CBO with stats.

Billions of apps rely on DUAL.  It is safe to say that EVERY Oracle application
in the world relies on DUAL.

Many sites do not yet fully use the CBO, they do not gather stats.

If we took the above approach, all sites would be flung immediately into CBO
mode as soon as they hit DUAL.  The effects would be enormous, people would be
really mad.

We told people for I don't know how many years "internal -- going away, svrmgrl
-- going away".  they are still in a state of shock when the install 9i and
internal is actually gone, svrmgrl actually doesn't exist.  Can you imagine if
all of a sudden the CBO was the default for many (but not all) queries?

Here is the classic example of backwards compatibility getting in the way of new
advances.  Fortunately dual being so small and frequently access, all is cached.

      Rating:
      4
Reader  October 09, 2001
Reviewer:
      Reader  from USA
What is the breakdown of db block gets anyway.
Does it comprise of data block as well as
data dictionary blocks

          Followup:

Its the reading of the segment header to perform the full scan.

      Rating:
      4
Reader  October 09, 2001
Reviewer:
      Reader  from USA
The segment header from dba_segments is one block.
Then why there are 4 dba block gets. obj$ does show
that DUAL is owned by PERFSTAT, SYS, PUBLIC (synonym) ...
Could this contribute to more than 1 db block gets

Thanks

          Followup:

They get it 4 times for whatever reason.  They used to get it two times:

ops$ora734@ORA734.WORLD> select * from dual;

D
-
X

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

Statistics
----------------------------------------------------------
          0  recursive calls
          2  db block gets

now 4.

PERFSTAT should not have DUAL -- we didn't create that, someone on your team
must have.  There should be only:

ops$tkyte@ORA8I.WORLD> select object_name, owner, object_type from dba_objects
where object_name = 'DUAL';

OBJECT_NAME                    OWNER                          OBJECT_TYPE
------------------------------ ------------------------------ ------------------
DUAL                           SYS                            TABLE
DUAL                           PUBLIC                         SYNONYM

Even so, it is not the reason for the >1 db block gets.  They are just getting
the block 4 times.

If you are querying obj$ and the type number = 10, thats a "NON-EXISTENT" trick
object we set up when you reference a synonym in your code.  we use it to track
dependencies.

Get in the habit of ignore the obj$ table and all SYS.tables -- in 9i, we make
it really hard to query them by default and the views are what you should use
day to day.

      Rating: 3
much ado about
      nothing  October 10, 2001
Reviewer:  Mikito
      Harakiri
4 block reads or 1, who cares?

Logically, 'dual' is a unit relation. Unit relation have no columns, but we
can't have tables without columns in oracle. Another important relation is the
one that has no rows, but we don't need that in practice.

BTW, the original poster didn't even care deleting "select 1 from dua" with
trivial error messages and everybody is reading that nonsence. There must be
posting guidelines!

      Rating: 5
Dual for
      variables  December 10, 2002
Reviewer:
      Meyer  from Baltimore, MD
Tom,

I have seen people do this in PL/SQL:

select 'ANY_VALUE'
  into LOCAL_VARIABLE
  from dual;

when they could just do this:

LOCAL_VARIABLE := 'ANY_VALUE' ;

Can you explain any overhead/performance or behind-the-scene-difference between
the way they are handled.

Thanks,
Meyer

          Followup:

there is huge overhead for that -- hugely huge overhead.

I think the people that wrote that code have been using Oracle for a long long
long time though.  I remember in version 5, with iag/iap - precursor to forms -
you did that cause you didn't HAVE plsql!

      Rating:
      5
orasiva  December 10, 2002
Reviewer:
      Siva  from CA,USA
Always great answers...

      Rating: 5
If dropped Dual
      table?  December 20, 2002
Reviewer:  Sikandar
      Hayat Awan  from Pindi Bhattian - Pakistan
One of my friend asked me that he has dropped dual table so now how to bring it
back?

Note: Dear TOM how can I enable cookies to this screen so Name, Email and Where
is the world are you? should come automatically as this is my personal machine?

          Followup:

step 1: revoke all privileges except for CREATE SESSION from this friend.
step 2: connect as sysdba
step 3:

create table dual
  (dummy varchar2(1))
  storage (initial 1)
/
insert into dual values('X')
/
create public synonym dual for dual
/
grant select on dual to public with grant option
/

      Rating: 5
Everything You
      Ever Wanted To Know About Dual...  December 20, 2002

Reviewer:  Jerry  from Washington, DC USA

          Followup:

but wait for 10i, when the rules all change ;)

      Rating:
      5
Thanks  December 20, 2002
Reviewer:
      Sikandar Hayat Awan  from Pindi Bhattian - Pakistan

The friend is not in my Domain so I can't. Thanks again.

      Rating: 4
Why this fails
      ...  December 21, 2002
Reviewer:  raju
       from london
Hi Tom,

There were no session connected to the database except for SYS, but the command
failed. Any ideas where I am going wrong.

SYS@ORA92:MYCOMP> conn sys/change_on_install@ora92 as sysdba
Connected.
SYS@ORA92:MYCOMP> alter database close;
alter database close
*
ERROR at line 1:
ORA-01093: ALTER DATABASE CLOSE only permitted with no sessions connected

Also, I tried shutting down the database and then restarting it. The shutdown
was successful but the startup failed.

SYS@ORA92:MYCOMP>
SYS@ORA92:MYCOMP> shutdown
Database closed.
Database dismounted.
ORACLE instance shut down.
SYS@ORA92:MYCOMP> startup
ORACLE instance started.
ORA-12516: TNS:listener could not find available handler with matching protocol
stack
SYS@ORA92:MYCOMP>

I am using 9.2.0.1 database on WindowsNT.

Thanks

          Followup:

and this has to do with DUAL how?

      Rating: 5
Dear
      Raju  December 21, 2002
Reviewer:  Sikandar
      Hayat Awan  from Pindi Bhattian - Pakistan
Dear Raju,
Here Mr. TOM means that this thread is dealing with DUAL while your question is
different from it.

      Rating: 5
He tried
      SVRMGR>alter database close;  December 22, 2002

Reviewer:  A reader
Hi Tom,
actually Raju tried the magic command to query dual table after closing the
database.

      Rating:
      4
Dual  April 09, 2003
Reviewer:  Som
       from India
It is great. Thanks Tom. I am learning lot of things.

      Rating: 5
Any links to
      what's coming...  April 11, 2003
Reviewer:  K
      K  from New Jersey, USA
>> but wait for 10i, when the rules all change ;)

Hi Tom,
Any links/pointers on what magic is coming our way in 10i?
Thanks,

          Followup:

http: //not.yet/

      Rating:
      4
????????????????????; == alter database close;
      ?  April 15, 2003
Reviewer:  robert
       from PA
Tom, so
"SVRMGR> ????????????????????;"
is equivalent to
"SVRMGR> alter database close;"  ?

Has to be the same length ?

thanks

          Followup:

read upwards in the reviews -- yes, it was alter database close

      Rating: 5
select user
      into vs l_user:=user  July 15, 2003
Reviewer:
      A reader
Hi

What´s the difference between

declare
  l_user varchar2(30);
begin
  l_user := user;
end;
/

declare
  l_user varchar2(30);
begin
  select user
  into l_user
  from dual;
end;
/

same ?

          Followup:

the second is more code?

      Rating:
      4
FGAC  September 05, 2003
Reviewer:
      Matt  from UK
Tracing some of our code we have noticed now that now we have introduced Fine
Grained Access Control quite a high proportion of our query io is on select
sys_context() from dual.

Looking at the differences with dual and my_dual - can you think of anyway to
drive all FGAC from my_dual. Considering the amount of times Dual is being hit
this may result in some not so insignificant performance gains.

We don't really want to replace sys.dual as I suspect support wouldnt be happy
bunnies :D !

          Followup:

can you show me how you are using sys_context?

in plsql, it is most performant to use plsql variables.
in sql, sys_context

perhaps you need to simply copy the sys_context values into plsql globals?

      Rating:
      4
Function  September 08, 2003
Reviewer:
      Matt  from UK
The function calls sys_context each time it is called - we could use a PL/SQL
global - but the problem is at what point do we refresh it based on a context
change within the application. We use Oracle Apps, and there would be
limitations on what we can do with their modules.

          Followup:

reduce the number of calls to the bearest minimum.  if you have say 3 context
items to look at, consider selecting them all from dual at once instead of
individual assignments.   consider:

ops$tkyte@ORA920LAP> create or replace context my_ctx using p
  2  /

Context created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create or replace procedure p ( p_name in varchar2, p_val
in varchar2 )
  2  as
  3  begin
  4          dbms_session.set_context( 'my_ctx', p_name, p_val );
  5  end;
  6  /

Procedure created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> exec p( 'x1', 'a' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec p( 'x2', 'b' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec p( 'x3', 'c' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> create or replace procedure p1
  2  as
  3          x1 varchar2(5) default sys_context( 'my_ctx', 'x1' );
  4          x2 varchar2(5) default sys_context( 'my_ctx', 'x2' );
  5          x3 varchar2(5) default sys_context( 'my_ctx', 'x3' );
  6  begin
  7          null;
  8  end;
  9  /

Procedure created.

ops$tkyte@ORA920LAP> create or replace procedure p2
  2  as
  3          x1 varchar2(5);
  4          x2 varchar2(5);
  5          x3 varchar2(5);
  6  begin
  7          select sys_context( 'my_ctx', 'x1' ), sys_context( 'my_ctx', 'x2'
), sys_context( 'my_ctx', 'x3' )
  8            into x1, x2, x3
  9            from dual;
 10  end;
 11  /

Procedure created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> exec runStats_pkg.rs_start

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec begin for i in 1 .. 10000 loop p1; end loop; end;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec runStats_pkg.rs_middle

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec begin for i in 1 .. 10000 loop p2; end loop; end;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920LAP> exec runStats_pkg.rs_stop(10000)
Run1 ran in 342 hsecs
Run2 ran in 120 hsecs
run 1 ran in 285% of the time

Name                                  Run1        Run2        Diff
LATCH.shared pool                   30,145      10,186     -19,959
STAT...recursive calls              30,001      10,002     -19,999
STAT...buffer is not pinned co      30,000      10,000     -20,000
STAT...execute count                30,005      10,005     -20,000
STAT...table scans (short tabl      30,000      10,000     -20,000
STAT...table scan rows gotten       30,000      10,000     -20,000
STAT...table scan blocks gotte      30,000      10,000     -20,000
STAT...no work - consistent re      30,000      10,000     -20,000
LATCH.library cache                 60,148      20,177     -39,971
LATCH.library cache pin             60,077      20,080     -39,997
STAT...session logical reads        90,522      30,530     -59,992
STAT...consistent gets              90,003      30,005     -59,998
STAT...calls to get snapshot s      90,001      30,001     -60,000
STAT...session pga memory                0      65,536      65,536
LATCH.cache buffers chains         182,587      62,586    -120,001

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
333,798     113,727    -220,071    293.51%

PL/SQL procedure successfully completed.

      Rating:
      3
Nice  December 28, 2003
Reviewer:
      Mary  from Boulder,USA
Dear sir,
well and wish the same from you.
I have a question regarding dual.
  How can we use the dual table to print 'Hello world' for
 10 times?Do you have a trick to do it?
Thanks in advance
Bye!

          Followup:

  1  select *
  2  from (
  3  select 'hello world'
  4  from dual
  5  group by cube( 1,2,3,4 )
  6  )
  7* where rownum <= 10
ops$tkyte@ORA9IR2> /

'HELLOWORLD
-----------
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world

10 rows selected.

(as offered by another reader on a different thread once...)

      Rating:
      3
OK  December 29, 2003
Reviewer:
      Mary  from Boulder,USA
Dear Sir,
Thanks for your response.But I don't have access to oracle
8i or above.Do you have a trick which works under oracle
7.3 or 8.0.Please do reply.
Thanks in advance.

          Followup:

whats the business case -- what problem are you trying to solve, give me the
original "question" you are trying to answer and perhaps we can give a solution.

      Rating: 5
10 rows from
      dual  December 30, 2003
Reviewer:  Tarun
       from India
Mary, You can do this for getting the rows in 7.3 or 8.0. Is this okey Tom?

select 'hello world' from dual
union all
select 'hello world' from dual
union all
select 'hello world' from dual
union all
select 'hello world' from dual
union all
select 'hello world' from dual
union all
select 'hello world' from dual
union all
select 'hello world' from dual
union all
select 'hello world' from dual
union all
select 'hello world' from dual
union all
select 'hello world' from dual
/

'HELLOWORLD
-----------
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world

10 rows selected.

          Followup:

it is, i'm just trying to figure out WHY the heck anyone would need to.

      Rating: 5
Why require
      SELECT FROM?  December 30, 2003
Reviewer:  Jim
      Nasby  from Austin, TX
Since this is an Oracle trivia type of question...

Some databases support SELECT without a FROM clause; seems much easier than
worrying about dual. Any idea why Oracle has never done this?

On the bright side, at least there is a dual... a certain other database
suggests you SELECT blah FROM TABLE( VALUES(1) AS a) or something similar. Yuck.

          Followup:

I've never "worried about dual".

I've worried about people doing silly things to it, updating a dictionary table
directly and breaking the system.

We can use := in the places they use select without a from!

their

   select 5 into @x

is our

   x := 5;

their select without a from is what we call "assignment"

You should not use dual as a method of assigning variable values.
You should use dual in queries (where they will force you to use FROM as well)

      Rating:
      2
Ok  December 31, 2003
Reviewer:
      Mary  from Boulder USA
Dear Tom and other reviewers,
 I don't know why you people get Annoyed.If you can do,
Do it or find a way out.
Bye!

          Followup:

huh???

who's annoyed?  confused or curious (as in "why would you want to do this, what
is the real world need -- maybe then I can give you a real world solution")
maybe..

      Rating:
      5
  December 31, 2003
Reviewer:
      A reader
Mary, we should be grateful to Tom for providing his expertise. He is doing a
favor by providing solutions which you won't find on any websites.

Thanks.

      Rating: 5
Guess I need to
      work on being more clear...  December 31, 2003

Reviewer:  Jim Nasby  from Austin, TX
:)

Yes, clearly in PL/SQL you'd use := and be done with it. Having support for
SELECT without FROM is more handy for command-line stuff, and other things
external to PL/SQL.

          Followup:

like what, give me a "for example" to work with -- where select without from
would be useful?

(or do you mean like turning the database into a big calculator -- where you
want to:

SQL> select 5+4

or something?)

      Rating:
      5
  December 31, 2003
Reviewer:
      A reader
I just love your strong wit and timing. I think this is what sets you apart as a
truly great author as much as your technical brilliance. Great work!!

December 31, 2003 Betty -- Thanks for the question regarding
      "Sizing System Hardware", version 8.1.6

      You Asked
Do you have a check list of what to ask a customer so that you
can size the system they will need.  Such as the number of
CPU's, amount of memory, disks, and disk controllers.

Thank You for your help
Betty

      and we said...
It really and truely depends.  I've never seen a checklist get
it right UNLESS you know the application inside out.  If you are
talking about installing Oracle applications -- then yes, we
have sizing metrics and such that we can fairly accurately
determine your needs.  If you are just talking "the database",
no -- there is no such checklist.

The problem is -- without knowing the entire application, we
don't know how much CPU horsepower you need for example.  If you
do lots of tiny inserts/updates/deletes with few indexes and no
triggers (so the operations themselves take a few hundreds of a
CPU second) you need much less CPU's then a system where you run
lots of queries that take 1 CPU second apiece -- or a stored
procedure that takes 30 CPU seconds to complete.  You need to
tell us how much CPU you will consume and how many people will
be doing it concurrently.  Then, the math is rather
straightforward.

As for things like "number of disk controllers" and number of
disks -- you only need 1.  Additional ones may increase
performance, again depending on the application itself.  Is it
IO intensive?  Read or Write? Does it access LOTS of data by
LOTS of users (indicates you might benefit from more
controllers) or is there a small amount of data accessed by a
small number of users (no benefit probably).

The rules of thumb are

o 80 users / CPU is not unheard of (RISC architecture).

o 3megs ram / user is a solid starting place (high but better
  safe then sorry).

o disks -- impossible to guess.  You really need to see how the
  application behaves (benchmark) to settle on the right number.

Your mileage will vary radically from the above -- only given an
application and a benchmark of that application (and the needed
response times and so no) can one reliably determine the number
of users/cpu, amount of physical ram needed and so on.
Review &
      Followup

      Rating:
      3
  March 14, 2001
Reviewer:  Martin
      Spellman  from UK

      Rating:
      4
Estimate/Calculate hardware configuration.  April
      11, 2002
Reviewer:  Nil  from India
Hi Tom,
1) As you said for oracle application size matrix is available, where I can get
it from.
2) “You need to tell us how much CPU you will consume” before running the
application in estimation stage how we can decide it OR Is this just guess work.
3) Here CPU second means 100% CPU utilization for 30 wall clock seconds or any
other.
4) Please see the following link

http://metalink.oracle.com/metalink/plsql/ml2_documents.showDocument?p_database_id=NOT&p_id=39466.1
Here they mention

The figures
-----------
From the base information below, the following table can be built up, as a guide
to the resources 5-6 concurrent users may require:
Start with the base operating system requirements, e.g. for Solaris its 12M RAM.
Then add the requirements for the Oracle7 (Oracle8?) server. See the Oracle
Server for (whatever) Installation and Configuration Guide, say this figure is
8M RAM. On top of these figures at the requirements for the Designer/2000 Shared
Pool Size (18M) and then for each user....

12 + 8 + 18 = 38MRAM

User    MIPS         RAM         DISC SwapSpace on Server
----    ----        ------       ------------------------
 1       3          say 38Mb        30Mb
 2     +2=5         +4=42          + 20=50
 3     +2=7         +4=46          + 10=60
 4     +2=9         +4=50          + 10=70
 5     +2=11        +4=54          + 10=80
 6     +2=13        +4=60          + 10=90
-------------------------------------------------

Both MIPs and RAM demands could increase rapidly if the users were making use of
Dictionary report facilities concurrently for example and if the database was to
support other client database applications at the same time e.g. payroll.

a)    Is 12M for to run the OS?
b)    Is 8M is min size to run ORACLE?
c)    How share pool size is decide as 18M ?
d)    During discussion with colleague one said, 1(one) CPU can handle 6000
transaction/minute. Is it right?
If yes, then is there any formula between transaction and MIPS. And how we can
find out one SQL statement generating how much MIPS.
If No, then why ‘2’ is added in MIPS column

e)    By, knowing MIPS and MHz of CPU can it possible to find no of CPU, and
How?

Thanks,

          Followup:

1) they are available when you sit down with the sales rep from Oracle AND your
hardware vendor.  As you can imagine sizing a compaq tru64 cluster is slightly
different then a solaris configuration.

2) is a statement of fact

3) is something, not sure what you are trying to say there...

4) that is a sizing chart from 1998 for designer/2000.  Tell me -- how do you
figure 'mips' these days (mainframes, mips -- sparc/risc/intel -- mghz)

a) 12m won't run any OS these days....
b) 8m isn't enough to run a database....
c) an 18m shared pool is the minimum these days...
d) there is no way anyone could reasonably say that without about 100 pages of
details defining what the CPU specs are, what the transaction involves.  What if
my transaction involves updating 100 records -- or 1000 or 1 -- can I do 6,000
of any of them?

MIPS just isn't a way we measure CPU performance on machines other then
mainframes really.

e) no, you have to benchmark.  everything else is a guess, an estimate, a swag.

      Rating:
      4
Errata  April 12, 2002
Reviewer:
      Nil  from India
Hi Tom,

Thanks for giving quick reply.

2) Is a statement of fact

Sorry this is mistake in presenting the sentence.

As you mentioned in your answer,
“You need to tell us how much CPU you will consume and how many people will be
doing it concurrently.  Then, the math is rather straightforward.”

Because of above statement I asked that question. For co-relating between thumb
rule you specify in the answer and example which is given in link for Sun
Solaris.

3) is something, not sure what you are trying to say there...

Here I tried to asked about what is definition of CPU second.

From above conversation I come to know that I have to make a benchmark at the
time of hardware estimation.

Thanks,

          Followup:

2) you benchmark your application.  You actually set it up -- run it, gather
system metrics and develop sizing guidelines.

3) a cpu second is just what it sounds like.  it is the work a cpu does in one
second.

      Rating:
      5
Benchmarking  August 06, 2003
Reviewer:
      Martin
Tom, this may be interesting to some people...

I have to migrate an existing 9ir2 database to either of two Sun boxes and I
would like to know which one would have the better performance.

I was looking on Google for some benchmark program I could run against Oracle,
but I did not find anything interesting.

Box 1: Sun 250, 2 x 400 MHz, 4 x 9 GB disks (Disk suite RAID-5), 2 GB RAM

Box 2: Sun Fire 280, 1 x 1200 MHz, 1 x 72 GB disk, 2 GB RAM

At a glance, "box 2" seems to be faster, especially because it is based on newer
technology. But I have to have metrics to prove that.

Questions:

1) Do you know where I could download such a benchmark application ?

2) How would you compare 2x400 MHz vesus 1x1200 MHz processors ? This is not a
linear tradeoff.

3) In his book, J. Lewis says to prefer many small disks to few larger ones.
Don't you think I could take advantage of parallelism having more disks -- in my
case with raid 5 ?

4) Or would the single disk be faster -- because it is based on newer technology
?

Thank you !

          Followup:

the only benchmark that counts is YOURS.

I look at that one disk at 72gig and shudder...  I fear where we are going with
bigger and bigger disks.

Sure it sounds like you have 400 more mhz -- but you have only one CPU -- many
applications work better with more slower cpus then one faster (and vice versa
is true as well).

      Rating: 3
Users per
      CPU...  December 30, 2003
Reviewer:  Patrick
      Chan  from New York City
Tom,
How many users per CPU are common with CISC Architecture?  Let's say 3GHz 32-bit
Intel CPUs.  You earlier gave an 80 user/CPU ballpark figure for CISC and was
wondering if it would be applicable.

Thanks in advance,
Patrick

          Followup:

it is so totally ballpark.  so very very ballpark.  it is also so dated by
now...

I would not use ballpark figures really -- you need to sort of benchmark your
application on your hardware to be able to figure this out.  every application
has a different sort of execution profile.

      Rating: 5
Why not use
      raid 5  December 31, 2003
Reviewer:
      juancarlosreyesp@yahoo.com  from Here :)
Hi Tom, can you please explain Oracle position about raid 5, in very few word.
I had read some about it, but I still don't understand the whole idea, because I
don't have enough background in hardware.
So I need a very brief an complete idea to give it to my customer, thanks :)

read it please.

http://www.smooth1.demon.co.uk/ifaq06.htm#6.58
pd I got it from burleston nice tips

          Followup:

"Oracle" does not have a position on raid 5.  raid 5 is just a disk technology.
we can use it.

it is slow for writes.

http://www.oaktable.net/fullNews.jsp?id=8

      Rating:
      5
Thanks  December 31, 2003
Reviewer:
      A reader
Thanks Tom
1.
Don Burleston says Oracle has a position about it, as I understand

http://www.dba-oracle.com/oracle_tips_raid5_bad.htm
"RAID 5 is universally discouraged for all Oracle database except (read-only
Oracle databases) because of the huge update penalty."
"Oracle officially recommends the SAME approach (Stripe And Mirror Everywhere),
essentially RAID O+1 with segregation of undo/redo and rollbacks on separate
spindles."
This means raid 0+1, not others including 5.
:) for example "use white, implies dont use black, dont use blue, etc."

2.
I had read some articles, including other questions in your site, but in a
simple way, how could you explain a customer RAID 5 is not the best.

I was thinking in something like this
"RAID 5 is slow for writes, Oracle can protect your data in other ways.
If you want to protect your data buy more disks, and save copy of logs and
control files.
Under any failure that is enough to recover your database. Because raid 5 can
replicate damaged blocks too(what I understand in my readings)."

If I'm wrong Tom, what will you say to a CEO to get they not use raid 5. Without
using technical words, in a short and simple explanation.

Thanks

happy new year!!!!

          Followup:

1) we prefer SAME, yes.  our new database feature ASM for 10g is built on that.

we do not however have an official stance.

i wholly disagree with a positive implying a negative for something else.  "use
white" does not mean "do not use black".

2) ask them "why raid 5, its slow, it doesn't offer significant protection"

      Rating: 5
Thanks
      Tom  January 02, 2004
Reviewer:
      A reader

December 31, 2003 Nitin -- Thanks for the question regarding "Index
      Rebuild and analyze", version 8.1.7.4

      You Asked
Hello Tom ,

I have a query regarding Index rebuild .
what according to you should be time lag between index rebuilds.
We are rebuilding indexes every week .but we found it is causing lot of
fragmentation.
is there any way we could find out whether we should rebuild the indexes and
also what indexes we should rebuild instead of rebuilding all the indexes.

Please also suggest whether it is good to drop and recreate the indexes ( to
avoid fragmentation ) or to rebuild them.

      and we said...
The time lag between index rebuilds should be approximately FOREVER.

Tell me -- why why why WHY are you rebuilding them on a schedule?  What is the
scientific basis in reality that is driving you to do this????

Here is an email I got once:

....
HELP!!!!  Riddle me this batman...

Why does an rebuilding an index cause increased redolog generation AFTER the
index has been built?

I have a table 35 million rows and an index (nothing is partitioned)
Transactions against this table are constant.  It's always 500,000 rows per day.
This generally creates 10 logs a day

Once a month the indexes are rebuit.  (Alter index rebuild)
On the day following the indexes rebuild 50 logs are created
On the following days 45...40...35...30....25....down to 10  at 10 logs this
remains constant at 10 Mining the logs we see that we have increase INTERNAL
INDEX UPDATES

Why does this happen??   Is this always the case??

So, what do you think was happening here?  Well, the fact is that indexes, like
people, have a certain "weight" they like to be at.  Some of us are chubby --
some skinny -- some tall -- some short.  Sure, we can go on a diet -- but we
tend to gravitate BACK to the weight we were.  The same is true for indexes --
what happened to this person is their index wanted to be wide and fat and EVERY
MONTH they rebuilt it (put it on a diet).  It would spend the first half of the
month then getting fat again and generating gobs of redo due to the block splits
it was undergoing to get there.

In this case, rebuilding the index on their system had these effects:

o the system would generate 4.5 times the redo
o the system would run slower
o the system would consume more resources (CPU, IO, latching, etc)
o the system would not be able to handle the same user load

until the system got back to where the system actually wanted to be.  And then
-- AND THEN -- they (the dba's) would do it all over again!!!! They would
destroy the equilibrium that the system worked so hard to get to.  Bravo!!

(can you tell what my opinion is on regularly scheduled index rebuilds???  I
despise them).

If you are suffering from fragmentation -- your problem is not the index
rebuilds.  It would be because you are using DICTIONARY managed tablespaces.
Switch over to locally managed tablespaces and you'll never have fragmentation
again.  So, my suggestion -- one last index rebuild:

o create a locally managed tablespace
o alter index <iname> rebuild tablespace LMT_FROM_ABOVE storage ( initial 1k );

and then forget about them.  You will rebuild an index in response to IDENTIFIED
and DEGRADED performance -- period.  You will not rebulid indexes based on a
schedule.  Period.

It is the RARE index that needs to be rebuilt.
It is not a rule that indexes need to be rebuilt.

There are extreme cases, particularly with regards bitmapped indexes after mass
data loads, where rebuilding is suggested.  But in a day to day system that is
transactional -- the need to rebuild indexes is sooooo rare (that I've never
actually done it myself in 15 years except to MOVE an index from one physical
location to another -- and even then, we don't need to do that very much today
at all with logical volumes and their like)

Review &
      Followup

      Rating: 5
index rebuild
      with Compute Statistics in program  November 24, 2002

Reviewer:  Prakash Eranki  from Singapore
Hi Tom
That was a fantastic explanation regarding Indexes. Thanks a ton for the
Knowledge Share.
I have a question related the same. In our Programs we are pulling out a chunks
of data from a huge tables to a work tables using a Bulk Fetch and a Bulk insert
respectively. Just before copying the data from the main table to work table, im
disabling the index on work table by using alter index ... unusable and setting
the sesion to skip unusable indexes. then bulk insert and then commit and then
rebuilding the index with alter index rebuild compute statistics. According to
your explanation above it should not have an impact. I cannot join huge tables
so thats why this excercise. i could redue the program time from 45minutes to 3
- 4 minutes . I could achieve a good performance but so far i did'nt see any
impact. will it impact in future . pls kindly suggest .

Thanks & Regards
Prakash Eranki

          Followup:

No, you missed my point.

We are talking about regularly scheduled index rebuilds on a normal system here.
 "Every week, lets rebuild everything".  Well, that might have been a super idea
for dbase -- but not for a real database.

What you describe above is more akin to a bulk load.  Here, instead of
maintaining the index whilst loading, you disable them -- bulk load -- and then
rebuild them (but the rebuild is really a CREATE since we have to go to the base
table to really build these indexes -- insufficient data in the index
structure).

So, I would say my explanation above has no bearing or impact on your situation
-- you would be applying apples to oranges if you take the above advice and
apply it to your situation which is totally different.

BTW: yes, yes you can join huge tables.  Just as efficiently (if not much more
so) then joining little tables over and over and over and over as you appear to
be.

      Rating:
      4
  December 22, 2002
Reviewer:
      A reader
Your analogy was very interesting. But can you offer more technical explanation
of what is happening? what do you mean by saying that the index rebuild will
destroy the equilibrium the system worked so hard to get to? Also what is the
difference between an index rebuild and re-creation of that index?

          Followup:

Don't know how to say it better -- the index wants to be big and fat with extra
space.  It is on a column you update -- moving the index entry from place to
place in the index.  One day the row has a code of "A", the next day the code is
"G", then "Z" then "H" and so on.  So the index entry for the row moves from
place to place in the index.  As it does so, it needs space -- will, if the
space isn't there, we split the block into two -- and make space.  Now the index
is getting fat.  Over time the index is 2-3x the size it was when you started
and is "half or more empty"  But that is OK since you move rows around.  Now
when we move the rows around, we no longer have to split blocks to make room --
the room is already available.

Then you come along and rebuild or drop and recreate the index (which have the
same effects -- just the rebuild is "safer" -- doesn't stand a chance of losing
the index and can be faster as the index can be rebuilt by scanning the existing
index instead of scanning the table and sorting and building a fresh index).
Now, all of that nice space is gone.  We start the process of splitting the
blocks all over again -- getting us right back to where we started.

You saved no space.
The index is right back the way it was.
You would just be wasting your time to rebuild it again causing this vicious
cycle to repeat itself.

      Rating: 5
I had it sooo
      wrong...  April 07, 2003
Reviewer:  Christo
      Kutrovsky  from Ottawa, ON Ontario
Since the begining, I was told that deleting a row from an indexed table is
wasting that space in the index. I was also told (or maybe had read it
somewhere...) that updating an indexed column is basically deleting it, and
creating it. Since then, I had never taken the time to dig in more.

Now, it seems that both those statements are correct, but not the way I imagined
it. Please correct me if I make a mistake:

1. Deleting a row from an index, is "wasting" the space, but this space will be
reused when a similar row needs to be inserted.
2. Updating a row in a index, is deleting the one on the old place, and placing
a new one at a new place. The space form the old row is "wasted" but used when a
new similar row is created.
3. Building an index with pctfree 50 is the same as having ALL the blocks
"splited" and will help in reducing REDO as we insert data (as the chance of
splitting a block reduces)
4. When Oracle detects that an index is created on a column, and all the values
are progressing (1,2,3,4) when splitting a block, it creates it 90% in old
block, 10% in the new block, not 50/50 as in any other case.
5. Rebuilding an index can be helpfull if:
  5a. The data is not going to be modified any more (or maybe just a little) to
improve the space usage and to improve LIOs.
  5b. After deleting many rows (50% for example) from a table (and no plan for
reinserting them), rebuilding will help.
  5c. If you have a range partitioned table, on a date basis (for example 1
partition per week), and you are progressively inserting and adding new
partitions, AND RARELY modify the data in past partitions, it's a good idea to
rebuild the indexes on those partitions.
  5c - adition - Is that even necessary if we are having "progressive" numbers
on the indexed columns

Thanks to you, I will not waste time rebuilding, due to my confusion that after
DML activity, rebuilding will help the system. (in the general case)

          Followup:

1) when a "similar row" (one that fits on that leaf block) or when all rows on a
leaf block are deleted (then that block goes onto the freelist and will be
reused elsewhere in the index structure)

2) it is not "wasted", no more then space in a table block is wasted when you
update 'long value in a varchar2' to 'short'.

3) assumming a normal distribution of values -- consider if this index were on a
column populated via a sequence.  THEN you would have accomplished that waste of
space as the "older" leaf blocks would never use their 50%

4) only in special cases, else it is more like a 60/40 split.  The 90/10 is a
special optimization for indexes on columns populated in a increasing fashion
(like those on a sequence for example).

5) (a) sure, like after a load into a data warehouse -- but you would have
       disabled them anyway...
   (b) coalesce would do the same
   (c) why?

      Rating: 3
why
      5c  April 08, 2003
Reviewer:  Christo
      Kutrovsky  from Ottawa, ON Canada
As we insert in that table partition (when it's current) the index gets "fat" as
we provide him food.

So after we finish loading data in that partition, some index blocks will not be
full (depending on the data), as they have splited as necessary.

Since we've finished loading data, we won't need that "reserved" space in the
index anymore, so we're rebuilding that partiion (on a local index) to get rid
of the empty slots.

From this point in time, this partition is only going to be queried, and we're
starting to fill up the next one.

          Followup:

tell me -- how much space do you anticipate getting back?

Sure, if you have a sliding window -- in a data warehouse for example -- one
would typically DISABLE indexes, load and rebuild.  totally different concept.

In a transactional system -- locally partitioned indexes are something are are
less useful -- many partitioned indexes are in fact global -- so no.

You see -- 5c is very vague, very general, you said:

  5c. If you have a range partitioned table, on a date basis (for example 1
partition per week), and you are progressively inserting and adding new
partitions, AND RARELY modify the data in past partitions, it's a good idea to
rebuild the indexes on those partitions.

i would say "good idea" -- maybe -- maybe not.  give me the details here.
global indexes?  local indexes?  local indexes that are not on an increasing
field (they do 90/10, no need there)?  local indexes that materially shrink
(most indexes you will find are "well fed", neither too fat, nor too skinny).

So, I will stick with "no".

      Rating: 4
How to
      determine index characteristics  May 02, 2003

Reviewer:  A reader
Tom:

I have a few Questions Re: Index characterisitcs. I'd grealty appreciate if you
could please explain with the help of some samples.

How can I determine if my index is
1) "Lob sided" and what does it mean ?
2) Index browning - what is it and how to detect it?
3) undergoing block splitting ?
4) sequencially increasing in its key value - and hence I can re-create it with
a low pctfree setting.

How can I determine the overhead of a bulk update/insert on the index
maintenance (i.e block splitting at leaf level and branch level etc...)?

Thanks

          Followup:

1) think you mean "lop sided" and for that I would ask you to ask the person
that said it what they mean and how to get one ;)

they really don't get that way.

2) a high percentage of deleted leaf rows to leaf rows (think of leaf rows like
leaves on a tree -- dead leafs (deleted row entries) are "brown".  If you have
lots of dead leaves, the tree looks brown)

This is a somewhat rare case -- it typically occurs when you have an increasing
key value (say a column populated by a sequence).  So you have index entries
1,2,3,4,..... and so on.  You tend to delete SOME but not all of the old entries
(leaving you with entries 2,4,6,7,8,10,.... for example -- say every other row).
 Now, since you'll never insert a low value ever again -- that deleted leaf row
may never be used again, it'll be "brown".

These indexes (rare as they are -- really, they are rare) may be coalesced,
rebuilding -- nah, not necessary.

3) they do it all of the time, it is how they grow.  If you look at your index
one day and it is bigger the next, it did some splitting.  It is normal,
amoeba's do it, indexes do it.

4) why?  we do a 90/10 split not a 50/50 or 60/40 split on those.  Indexes are
pretty smart structures and it is the rare index that needs extra special
attention.

      Rating:
      5
  May 29, 2003
Reviewer:  Michael
      Dinh  from San Diego, CA USA
According to the article at
http://www.dbazine.com/weeg16.shtml
The author rebuilds indexes based on certain
criterias.

You are against rebuilding indexes.

Who's wrong and who's right?

          Followup:

well, lets see, they've set a "4 extent limit" -- one might ask "why"?

why they would rebuild an index based on number of extents is beyond me.  I
don't see the reasoning.

their goal:

"We want to reclaim space, so we will rebuild all of the indexes that have too
much unused space. "

too bad for them that the indexes just grow all over again (see the riddle me
this above... not only do they simply tend to grow right back to where they were
-- they consume tons of resources in doing so!)

You know what is missing in that article?  Science.  I don't see any hypothesis,
proof, I see nothing that says "see -- my system is so much better", or "see I
save X megabytes PERMANENTLY of storage" (sure, they might have X megabytes more
today -- but come back in a week)

There are rare degenerate cases when indexes need to be rebuilt.

I see no discussion of that there.

All I see is "I want space" -- but isn't it funny that they need to do this over
and over (hence they get the space for like what, 5 minutes - then poof, its
going back to the indexes which want to reach their steady state, a state that
includes a bit of fluffiness -- but that it will always go back to).

Sort of self defeating eh?

Until I see the article that says:

a) I rebuild indexes
b) for this reason
c) and here is the proof that I achieve this goal in the long term
d) and here are the caveats to doing so, and times you don't want to do this
cause it is counter productive

I will keep saying "don't do it"...

I'm not from Missouri but I still say "show me"

I didn't see anything that showed me the "why" and that this did more good then
harm.

I can show you this doing more harm then good however...

Where are the metrics that show "and the very next day our LIO's were down 5%
under the same load"?

"And the very next day our redo generation stayed the same under the very same
load"

"And we had this extra space for months..." (this is the funniest reason to
rebuild, since they just get fat anyway -- all but the degenerate cases stop
growing.  if you put them on a diet by rebuilding, they just get fat again
anyway)

      Rating: 5
Rebuilding
      Indexes can be good  May 30, 2003
Reviewer:
      Angus McTavish aged 75  from Cumbria, England
Tom,
Your arguments against index rebuilding seem very sound, so what do you think of
this example where index rebuilding made a measureable improvement in an OLTP
system? (Sadly, the measurements in question are long gone, but trust me, the
index rebuilds made a significant improvement in response times.) The scenario I
describe is probably not that uncommon I guess, which is why I'm putting the
question, because you suggest that index rebuilds are necessary in rare cases
only.
We had a table Table A with about 20 million rows.
Stats were computed on this table weekly.
The primary key on the table is an ascending sequence.
There are six other indexed columns all of which are populated with primary key
values on other tables (where the primary keys are sequences).
Basically the values in all indexed columns are going up.
No updates, just inserts. About a year ago we removed all data in this table
over 60 days old. This removed about 15 million rows. There was an immediate
drop in performance when running standard queries against this table.
Reanalyzing had no effect. We rebuilt the indexes and hey presto! performance
returned to normal. We have a job which runs daily and removes all data over 60
days old so the size of the table remains fairly constant now. But if we don't
rebuild the indexes periodically (I now do it about once every couple of
months), performance degrades - not as dramatically as when we first removed 15
million rows, but it does degrade.
Now I imagine that it's quite common to delete data from tables in this way -
even non-partitioned tables as in this case. What are your thoughts, old chap?

          Followup:

My arguments against index rebuilding are not against index rebuilding

they are against the scheduled rebuild of indexes, especially ones that have no
metrics after the fact to show that they provided long term, beneficial
improvements.

You would be surprised how many rebuilds provide

a) short term (like hours) space reclaimation
b) longterm performance DECREASES

You should have COALESCED the indexes to reclaim free space.  Faster, easier,
doesn't require 2x the space.

Coalesce... reclaim the free space from mostly empty index leaf blocks that will
not be reused otherwise due to your increasing sequence.

"old chap?"

      Rating: 3
Who does Angus
      work for?  May 30, 2003
Reviewer:  Richard
       from Oxfordshire, UK
I want to work for Angus's company! A UK business employing 75 year-olds (apart
from DIY superstores) is as rare as hen's teeth. Well done, Angus, a job for
life!

(by the way, is that your real name?)

      Rating: 3
Who does Angus
      work for?  May 30, 2003
Reviewer:  Richard
       from Oxfordshire, UK
I want to work for Angus's company! A UK business employing 75 year-olds (apart
from DIY superstores) is as rare as hen's teeth. Well done, Angus, a job for
life!

(by the way, is that your real name?)

      Rating:
      5
  May 30, 2003
Reviewer:
      A reader
Good Explaination!!

      Rating: 5
Coalesce - of
      course!  June 04, 2003
Reviewer:  Angus McTavish
      aged 75  from Cumbria, England
Ah yes, old chap, you are quite right I'm sure. COALESCE would have done the
trick just as nicely - better all round in fact, for the reasons you mention.

      Rating: 5
Please don't
      take this the wrong way.  June 09, 2003
Reviewer:
      md
Tom,

I love you man :))

      Rating:
      5
  June 17, 2003
Reviewer:
      A reader

      Rating: 5
Is there a way
      to rename an existing index? Thanks.  August 15, 2003

Reviewer:  Reader

          Followup:

ops$tkyte@ORA920> alter index t3_b_idx rename to x;

Index altered.

ops$tkyte@ORA817DEV> alter index LOGICAL_DATE_PK rename to x;

Index altered.

      Rating: 5
Renaming index
      - is it new feature in 8i or 9i? Thanks.  August 16, 2003

Reviewer:  A Reader

          Followup:

i did it in 8i and 9i above....

      Rating: 4
Change of the
      execution plan after index is recreated  September 25, 2003

Reviewer:  Branka Bowman  from DC, USA
Following are two execution plans, one before I recreated index and one after. I
am trying to understand what can be possible reason that Oracle decides not to
use existing index for the execution plan. After I recreate index Oracle start
using that index again.

Statistics (before index is recreated)
----------------------------------------------------------
          0  recursive calls
          4  db block gets
       3349  consistent gets
       2299  physical reads
          0  redo size
        716  bytes sent via SQL*Net to client
        250  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
0    rows processed
1

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

          Followup:

hmmm, i don't know.  i don't even know what i'm looking at here.

hows about a test case - from start to finish.

      Rating: 4
Change of the
      execution plan after index is recreated   September 26, 2003

Reviewer:  Branka Bowman  from VA, USA
Index is on one table that is in use all the time. Record are inserting and
deleting from that table. Table has only around 500000 records.
Following is explanation that DBA gave to me:
“Since Oracle makes these determinations internally taking into account a
variety of factors it is difficult to pinpoint with 100% certainty, but one
possibility is fragmentation of the index over a period of time.  As you add and
delete records, which also results in having to update the index, over a period
of time the index becomes fragmented and the blocks become scattered around
rather than in one contiguous space.  At some point, Oracle may decide that it
is better to do a full table scan rather than search through all these scattered
blocks and ignore the index.”
Will using of locally managed tablespaces resolve this problem in the future?

This is SQL statement. I have execution plan for one that is using index.
Unfortunately, I did not save copy of one that is not using index. (before index
is recreated)

select TO_CHAR(BATCH01.ACCTG_DT,'YYYY-MM-DD') ,BATCH01.CREATE_USER_ID
,BATCH01.STAT_CD ,BATCH01.DOM_CHECK_AM ,BATCH01.FRGN_CHECK_AM ,
BATCH01.DOM_MONEY_ORDER_AM ,BATCH01.FRGN_MONEY_ORDER_A ,BATCH01.CR_CARD_AM
,BATCH01.ELECT_XFR_AM ,BATCH01.TOT_AM ,
BATCH01.LAST_UPDT_USER_ID ,BATCH01.SEQ_NO ,BATCH01.OPNG_CASH_DRAWER_A
,BATCH01.CASH_AM ,BATCH01.BILLS_AM ,
BATCH01.COINS_AM ,TO_CHAR(BATCH01.OPND_TS,'YYYY-MM-DDHH24.MI.SS')
,BATCH01.PYMNT_NO
from BATCH BATCH01
where (BATCH01.CREATE_USER_ID='AADOFO1'
and BATCH01.STAT_CD='O')

  Execution Plan (after index is recreated)
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=100 Card=136 Bytes=8
          024)

   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'BATCH' (Cost=100 Card=13
          6 Bytes=8024)

   2    1     INDEX (RANGE SCAN) OF 'BATPSK2' (NON-UNIQUE) (Cost=3 Car
          d=136)

Statistics (after index is recreated)
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          3  consistent gets
          0  physical reads
          0  redo size
        720  bytes sent via SQL*Net to client
        216  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          0  rows processed

Statistics (before index is recreated)
----------------------------------------------------------
          0  recursive calls
          4  db block gets
       3349  consistent gets
       2299  physical reads
          0  redo size
        716  bytes sent via SQL*Net to client
        250  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

          Followup:

their explanation is not accurate.

indexes are managed in single blocks always.
they are always "scattered all around" from the very beginning.

i need the other plan.

      Rating: 4
Change of the
      execution plan after index is recreated   September 26, 2003

Reviewer:  Branka Bowman  from VA, USA
I did not save other plan, but difference is that other plan is doing full table
search.
I have Statpack report before problem started and while we had problem.

          Followup:

statspack doesn't count.

we need the PLAN.  we need to see the estimated cardinality.

      Rating: 4
Change of the
      execution plan after index is recreated   September 28, 2003

Reviewer:  Branka Bowman  from VA, USA
Is it any way that I can now find what happen with execution plan?
What can I monitor to be sure that I will find problem before customers?

          Followup:

i can only keep saying "need to see said plan" before I can comment on possible
causes.

      Rating: 3
Change of the
      execution plan after index is recreated   September 28, 2003

Reviewer:  Branka Bowman  from VA,USA
I created one possible scenario (using part of one of your example) that could
be something that happens. Can you comment this scenario?

SQL> drop table t;

Table dropped.

SQL> create table t as select mod(rownum,3) X, a.* from all_objects a;

Table created.

SQL> insert /*+ append */ into t select * from t;

24694 rows created.

SQL> commit;

Commit complete.

SQL> insert /*+ append */ into t select * from t;

49388 rows created.

SQL> commit;

Commit complete.

SQL> select count(*) from t;

  COUNT(*)
----------
     98776

SQL> update t set x = 4 where rownum <= 10;

10 rows updated.

SQL> create index t_idx on t(x) ;

Index created.

SQL> analyze table t compute statistics;

Table analyzed.

SQL> set autotrace traceonly explain
SQL> select * from t where x = 4;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=212 Card=24694 Bytes
          =2222460)

   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=212 Card=24694 Bytes=2222
          460)

SQL> drop index t_idx;

Index dropped.

SQL> create index t_idx on t(x);

Index created.

SQL> select * from t where x = 4;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=207 Card=24694 Bytes
          =2222460)

   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=207 Card=24694
          Bytes=2222460)

   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=7 Card=
          24694)

          Followup:

yes, you took away information -- by dropping the index, you lost the
statistics.

bad stats = bad plans.

      Rating: 2
Change of the
      execution plan after index is recreated   September 28, 2003

Reviewer:  Branka Bowman  from VA, USA
I did not understand explanation. When I drop and create index, execution plan
start using index. Before that it was not using index and number of consistent
gets and physical reads is much smaller after index is dropped and created
again.
First I had index that execution plan should use, bud did not. When I drop and
create index execution plan start using index.
Estimated cardinality is not correct in both cases, but in second one execution
plan is using index.
Statistics are obvious wrong. What would be correct statistics for this example?
Is it any way that I can find out what statistics are made on one table (other
than to get that information from DBA)?

          Followup:

you took away all of the index statistics.  but anyway, use this:

ops$tkyte@ORA920> analyze table t compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;

or better yet

ops$tkyte@ORA920> exec dbms_stats.gather_table_stats( user, 'T', method_opt =>
'for all indexed columns', cascade => true );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select * from t where x = 4;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=10 Bytes=1000)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=10 Bytes=1000)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=10)

and you'll find it does the right thing from step 1.

I cannot reproduce your example in either of 8174 or 9203 btw.  In both, i get:

ops$tkyte@ORA817DEV> analyze table t compute statistics;

Table analyzed.

ops$tkyte@ORA817DEV> set autotrace traceonly explain
ops$tkyte@ORA817DEV> select * from t where x = 4;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=44 Card=26861 Bytes=2363768)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=44 Card=26861 Bytes=2363768)

ops$tkyte@ORA817DEV> drop index t_idx;

Index dropped.

ops$tkyte@ORA817DEV> create index t_idx on t(x);

Index created.

ops$tkyte@ORA817DEV> select * from t where x = 4;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=44 Card=26861 Bytes=2363768)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=44 Card=26861 Bytes=2363768)

Now, I have defaults for pretty much everything.

      Rating: 3
Create
      partition table  October 28, 2003
Reviewer:
      Hanna  from USA
I have tables in schema A for each year say Y99, Y00, Y01, Y02, Y03 and I am in
process to create a partitioned table in schema B with data from all the tables
in schema A.
can you give a demo with creating partition table along with the index creation
on each partion ( Say we have ID as the common column on all the tables)

          Followup:

i would "hope" you have more then ID as the commonality.  ALL columns would be
in common.

not sure what you are looking for?

a single create table that you would insert into

or a method to convert these existing tables into partitions of a partitioned
table

I have examples of both on this site.

      Rating:
      3
  October 29, 2003
Reviewer:  Reader
       from US
can you give the search words

          Followup:

partition existing table

      Rating: 5
More on Angus
      question.  October 29, 2003
Reviewer:  Sai.
       from Mountain View, CA
Hi Tom,

In this page Angus said: "We had a table Table A with about 20 million rows.
Stats were computed on this table weekly.
The primary key on the table is an ascending sequence.
There are six other indexed columns all of which are populated with primary key
values on other tables (where the primary keys are sequences).
Basically the values in all indexed columns are going up.
No updates, just inserts. About a year ago we removed all data in this table
over 60 days old. This removed about 15 million rows. There was an immediate
drop in performance when running standard queries against this table.
Reanalyzing had no effect. We rebuilt the indexes and hey presto! performance
returned to normal."

Why would deleting a lot of rows degrade's performance of standard queries they
were running. I thought, performance would still remain same, because Oracle
would still have to visit same number of blocks, as were before delete? Can you
please explain what could possible explain degrade in performance?

Thanks,

          Followup:

they would have achieved the same goal via a COALESCE of the index.

I'll assume their queries read "min" values lots.

ops$tkyte@ORA920LAP> create table t ( x int );

Table created.

ops$tkyte@ORA920LAP> insert into t select rownum from big_table.big_table;

1000000 rows created.

ops$tkyte@ORA920LAP> create index t_idx on t(x);

Index created.

ops$tkyte@ORA920LAP> analyze table t compute statistics for table for all
indexes;

Table analyzed.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> set autotrace on
ops$tkyte@ORA920LAP> select min(x) from t;

    MIN(X)
----------
         1

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=152 Card=1 Bytes=8)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FULL SCAN (MIN/MAX)) OF 'T_IDX' (NON-UNIQUE) (Cost=152
Card=1000000 Bytes=8000000)

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

ops$tkyte@ORA920LAP> set autotrace off
ops$tkyte@ORA920LAP> delete from t where x <= 999999;

999999 rows deleted.

ops$tkyte@ORA920LAP> commit;

Commit complete.

ops$tkyte@ORA920LAP> set autotrace on
ops$tkyte@ORA920LAP> select min(x) from t;

    MIN(X)
----------
   1000000

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=152 Card=1 Bytes=8)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FULL SCAN (MIN/MAX)) OF 'T_IDX' (NON-UNIQUE) (Cost=152
Card=1000000 Bytes=8000000)

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

ops$tkyte@ORA920LAP> set autotrace off
ops$tkyte@ORA920LAP> alter index t_idx coalesce;

Index altered.

ops$tkyte@ORA920LAP> set autotrace on
ops$tkyte@ORA920LAP> select min(x) from t;

    MIN(X)
----------
   1000000

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=152 Card=1 Bytes=8)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FULL SCAN (MIN/MAX)) OF 'T_IDX' (NON-UNIQUE) (Cost=152
Card=1000000 Bytes=8000000)

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

ops$tkyte@ORA920LAP> set autotrace off

What happened was the mass delete left lots of blocks empty -- but they would
not be moved to a freelist until later.  So, the min() had to look through lots
of "dead blocks".  the coalesce moved them (the empty ones) onto the freelist
for the index.

      Rating:
      5
freelist  October 30, 2003
Reviewer:
      Reader
<quote>but they would not be moved to a freelist until later<quote>

In the above example, after deleting most of the rows, exit the session and
login back and then if I performed the query, oracle by then would have put
those free index blocks in the free list? Thanks.

      Rating:
      5
Indexing  November 18, 2003
Reviewer:
      Suhas desai  from Mumbai ,Maharashtra, India
Simply fantastic

      Rating: 4
Execution
      Plan  December 31, 2003
Reviewer:  Branka
       from VA
What Cost in Execution Plan mean?

          Followup:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/optimops.htm#37379

December 31, 2003 M -- Thanks for the question regarding "Add a new
      data type in Oracle", version 8.1.7

      You Asked
Hi,

I would like to know how to create a user defined data type in
Oracle. For example, I want to add a new data type BOOLEAN.

In SQL server, we can use sp_addtype. But in Oracle, I didn't
find a similar one yet. "CREATE TYPE" seems to be used to create
a object type rather than a data type.

Thanks for any help.

      and we said...
An Object type is a datatype.

It is the way we do it in Oracle.

However if what you are looking for is a "macro pre-processor"
(eg: the sp_addtype lets you say "boolean is really a number(1)"
-- its an alias, not really a datatype) you can use the macro
pre-processor of SQLPlus.  Many people do this:

define boolean = number(1)
......

create table t ( x &boolean, y date, z varchar2(25) );

Don't forget however that boolean is already a datatype in
PLSQL, you may be introducing some major confusion using that
particular name.

followup to comment one

Parag - if you feel you could answer in a better, more complete
fashion -- please do so.  I'm just telling it like it is.
SQLServer does things *differently* then Oracle, Oracle does
things *differently* then SQLServer -- if we in fact were
identical, what would be the point of having two?  Software from
different vendors does different things.  You will do things in
a different fashion.

Review &
      Followup

      Rating:
      2
  August 09, 2001
Reviewer:  Parag
      Mehta  from India
Actually i am not highly Impressed by this Answere.

      Rating: 4
Keep up the
      good work  August 09, 2001
Reviewer:
      A reader  from Oregon USA
I read your site every business day.  Often I learn something valuable.  I
especially like your demonstrative examples.  BTW, Love your book.

      Rating: 3
Not quite
      powerful enough  August 08, 2003
Reviewer:
      Jonas  from SF, CA
While your preprocessor example works, it doesn't elegantly handle associating
named check constraints to your predefined type. If you change the type, it
would probably break the constraint.

If I have a "name" column defined in 20 tables, I sure would love to make sure
that they are all the same type (for example - varchar2(char 64)). While Object
types can meet this need, they provide an extra level of indirection. It seems
like this can become cumbersome if there are multiple object types within a
single table.

Many E/R tools support derived types, which is a beautiful
thing. But the problem is one has to rely on this tool for all schema changes,
which can be extremely limiting.

How would you go about solving this problem?

Do you consider object types a valid solution to standardize a type for a single
column?

I could use grep compare types between like named columns, but god forbid
someone chooses a non-standard naming convention for a given column.

Just looking for a robust solution.

Also, is there any way I can provide feedback to the product manager's that this
is a desired feature (in my book anways)?

Thanks for all your help.

          Followup:

Only a tool can enforce these things, as "limiting" (that is part of their
actual design goal, to limit) as they are.

Even if you had a "type" -- no one is forced to use that type correctly.  Only a
repository based tool that enforces strict control can do that.

      Rating: 5
Synonym/Alias
      for datatype  December 31, 2003
Reviewer:
      A reader
Hi Tom,

After reading this, am I right that there is not way (yet) in Oracle to create a
synonym or alias for another datatype such as date?

What I want to achieve is to create a "datetime" type which is the same as
"date".

<<Yes, I asked you earlier about how to write database compatible codes. So bad
even the date and time datatypes are named differently in Oracle and SQL Server.
Where is the ANSI standards... >>

          Followup:

there is not.  no.

You can order for a fee the ANSI standards however

a) there is no body that certifies anyone to them anymore
b) there are lots of levels of compliance
c) we are all at different levels of compliance

LCD is LCD is LCD.... lowest is lowest is lowest.  might as well just use grep,
sed and awk.  (oh, but that won't work on windows ;)


Oracle9i SQL Reference

雪狼 作品

资料来源

资料来源：http://asktom.oracle.com

收集整理：Snowywolf

整理时间：2004-01

版权声明

您可以自由地将此文档复制发行，但请保留此声明页。

请勿将此文档用于任何商业用途。

联系作者

ITPUB号：Snowywolf

E m a i l：lisj@supermap.com

N a m e ：李绍俊

雪狼 作品